一、Redis簡介
Redis是當前比較熱門的NOSQL系統(tǒng)之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(tǒng)(區(qū)別于MySQL的二維表格的形式存儲。)。和Memcache類似,但很大程度補償了Memcache的不足。和Memcache一樣,Redis數(shù)據(jù)都是緩存在計算機內存中,不同的是,Memcache只能將數(shù)據(jù)緩存到內存中,無法自動定期寫入硬盤,這就表示,一斷電或重啟,內存清空,數(shù)據(jù)丟失。所以Memcache的應用場景適用于緩存無需持久化的數(shù)據(jù)。而Redis不同的是它會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,實現(xiàn)數(shù)據(jù)的持久化。
https://spring.io/projects/spring-data-redis
Redis的特點:
Redis讀取的速度是110000次/s,寫的速度是81000次/s
原子 。Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。
支持多種數(shù)據(jù)結構:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
持久化,主從復制(集群)
支持過期時間,支持事務,消息訂閱。
官方不支持window,但是又第三方版本。
Redis與memcache的區(qū)別:
1、Redis和Memcache都是將數(shù)據(jù)存放在內存中,都是內存數(shù)據(jù)庫。不過memcache還可用于緩存其他東西,例如圖片、視頻等等。
2、Redis不僅僅支持簡單的k/v類型的數(shù)據(jù),同時還提供list,set,hash等數(shù)據(jù)結構的存儲。
3、虛擬內存--Redis當物理內存用完時,可以將一些很久沒用到的value 交換到磁盤
4、過期策略--memcache在set時就指定,例如set key1 0 0 8,即永不過期。Redis可以通過例如expire 設定,例如expire name 10
5、分布式--設定memcache集群,利用magent做一主多從;redis可以做一主多從。都可以一主一從
6、存儲數(shù)據(jù)安全--memcache掛掉后,數(shù)據(jù)沒了;redis可以定期保存到磁盤(持久化)
7、災難恢復--memcache掛掉后,數(shù)據(jù)不可恢復; redis數(shù)據(jù)丟失后可以通過aof恢復
8、Redis支持數(shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。
Lettuce與Jedis 比較:
Jedis 是直連模式,在多個線程間共享一個 Jedis 實例時是線程不安全的,如果想要在多線程環(huán)境下使用 Jedis,需要使用連接池,
每個線程都去拿自己的 Jedis 實例,當連接數(shù)量增多時,物理連接成本就較高了。
Lettuce的連接是基于Netty的,連接實例可以在多個線程間共享,所以,一個多線程的應用可以使用同一個連接實例,而不用擔心并發(fā)線程的數(shù)量。當然這個也是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。通過異步的方式可以讓我們更好的利用系統(tǒng)資源,而不用浪費線程等待網(wǎng)絡或磁盤I/O。
二、Maven依懶
<!-- lettuce pool 緩存連接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- spring boot redis 緩存引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
三、Redis配置
application.properties
# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=127.0.0.1
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-active=200
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-wait=-1ms
# 連接池中的最大空閑連接
spring.redis.lettuce.pool.max-idle=10
# 連接池中的最小空閑連接
spring.redis.lettuce.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=10000ms
三、Redis配置類
- 自動配置類:
@Configuration
public class RedisConfiguration {
@Resource
private LettuceConnectionFactory myLettuceConnectionFactory;
@Bean
public RedisTemplate<String, Serializable> redisTemplate() {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(myLettuceConnectionFactory);
return template;
}
}
- 實體類
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "t_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 20)
private String username;
@Column
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date createtime;
}
- 緩存測試
@Slf4j
public class UserServiceTest extends BaseApplicationTests {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisTemplate<String, Serializable> redisCacheTemplate;
@Test
public void redisTest() {
// TODO 測試線程安全
ExecutorService executorService = Executors.newFixedThreadPool(1000);
IntStream.range(0, 1000).forEach(i ->
executorService.execute(() -> stringRedisTemplate.opsForValue().increment("kk", 1))
);
stringRedisTemplate.opsForValue().set("k1", "v1");
final String k1 = stringRedisTemplate.opsForValue().get("k1");
log.info("[字符緩存結果] - [{}]", k1);
// TODO
String key = "battcn:user:1";
redisCacheTemplate.opsForValue().set(key, new User(1L, "hjj", new Date()));
// TODO 對應 String(字符串)
final User user = (User) redisCacheTemplate.opsForValue().get(key);
log.info("[對象緩存結果] - [{}]", user);
}
}

Redis數(shù)據(jù)類型對應的操作方式:
opsForValue: 對應 String(字符串)
opsForZSet: 對應 ZSet(有序集合)
opsForHash: 對應 Hash(哈希)
opsForList: 對應 List(列表)
opsForSet: 對應 Set(集合)
四、 Redis工具類
直接用RedisTemplate操作Redis,需要很多行代碼,因此直接封裝好一個RedisUtils,這樣寫代碼更方便點。
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis工具類
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
//=============================common============================
/**
* 指定緩存失效時間
*
* @param key 鍵
* @param time 時間(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據(jù)key 獲取過期時間
*
* @param key 鍵 不能為null
* @return 時間(秒) 返回0代表為永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判斷key是否存在
*
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除緩存
*
* @param key 可以傳一個值 或多個
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通緩存獲取
*
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設置時間
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 將設置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 獲取hashKey對應的所有鍵值
*
* @param key 鍵
* @return 對應的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 鍵
* @param map 對應多個鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并設置時間
*
* @param key 鍵
* @param map 對應多個鍵值
* @param time 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
*
* @param key 鍵
* @param item 項
* @param value 值
* @param time 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除hash表中的值
*
* @param key 鍵 不能為null
* @param item 項 可以使多個 不能為null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項的值
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash遞增 如果不存在,就會創(chuàng)建一個 并把新增后的值返回
*
* @param key 鍵
* @param item 項
* @param by 要增加幾(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
*
* @param key 鍵
* @param item 項
* @param by 要減少記(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根據(jù)key獲取Set中的所有值
*
* @param key 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據(jù)value從一個set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將數(shù)據(jù)放入set緩存
*
* @param key 鍵
* @param values 值 可以是多個
* @return 成功個數(shù)
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 將set數(shù)據(jù)放入緩存
*
* @param key 鍵
* @param time 時間(秒)
* @param values 值 可以是多個
* @return 成功個數(shù)
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 獲取set緩存的長度
*
* @param key 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值為value的
*
* @param key 鍵
* @param values 值 可以是多個
* @return 移除的個數(shù)
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 獲取list緩存的內容
*
* @param key 鍵
* @param start 開始
* @param end 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取list緩存的長度
*
* @param key 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通過索引 獲取list中的值
*
* @param key 鍵
* @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數(shù)第二個元素,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據(jù)索引修改list中的某條數(shù)據(jù)
*
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N個值為value
*
* @param key 鍵
* @param count 移除多少個
* @param value 值
* @return 移除的個數(shù)
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
測試:
@Autowired
private RedisUtil redisUtil;
@Test
public void test(){
redisUtil.set("test","1234");
}
五、注解式緩存
(一) 配置
配置類上添加注解,啟用緩存:
@EnableCaching
(三) 注解使用
https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
1. @Cacheable
從緩存中查詢指定的key,如果有,從緩存中取,不再執(zhí)行方法.如果沒有則執(zhí)行方法,并且將方法的返回值和指定的key關聯(lián)起來,放入到緩存中:
主要參數(shù)說明:
(1)value :
緩存的名稱,在 spring 配置文件中定義,必須指定至少一個,例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}。
(2)key :緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數(shù)進行組合,例如:@Cacheable(value=”testcache”,key=”#userName”)。
(3)condition :(哪種情況緩存)緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存,例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2。
(4)unless (哪種情況不緩存)
//為空不緩存
unless = "#result==null"
//返回值為true進行緩存
unless = "!#result"
其中#result,代指函數(shù)的返回值。非(!)(返回值),都進行緩存。 就是說,當返回值不是false時,才進行緩存。
(5)Spring Cache提供的@Cacheable注解不支持配置過期時間,還有緩存的自動刷新。
https://github.com/yantrashala/spring-cache-self-refresh
@Override
@Cacheable(value="userredis", key="'users_'+#id")
public User getUser(Long id) {
Optional<User> optionalUser = userRepository.findById(id);
if(optionalUser.isPresent()){
return optionalUser.get();
}
return null;
}
用@Cacheable的value屬性指定具體緩存,并通過key將其放入緩存中.這里key非常靈活,支持spring的el表達式,可以通過方法參數(shù)產生可變的key。
2. @CacheEvict
@CacheEvict則是從緩存中清除指定的key對應的數(shù)據(jù):
主要參數(shù)說明:
(1)value , key 和 condition 參數(shù)配置和@Cacheable一樣。
(2)allEntries :是否清空所有緩存內容,缺省為 false,如果指定為 true,則方法調用后將立即清空所有緩存,例如:@CachEvict(value=”testcache”,allEntries=true)。
(3)beforeInvocation :是否在方法執(zhí)行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執(zhí)行的時候就清空緩存,缺省情況下,如果方法執(zhí)行拋出異常,則不會清空緩存,例如@CachEvict(value=”testcache”,beforeInvocation=true)。
@CacheEvict(value="thisredis", key="'users_'+#id",condition="#id!=1")
public void delUser(Integer id) {
// 刪除user
System.out.println("user刪除");
}
3. @CachePut
@CachePut:作用是主要針對方法配置,能夠根據(jù)方法的請求參數(shù)對其結果進行緩存,和 @Cacheable 不同的是,它每次都會觸發(fā)真實方法的調用 :
主要參數(shù)說明:
(1)value , key 和 condition 參數(shù)配置和@Cacheable一樣。
@CachePut("users")//每次都會執(zhí)行方法,并將結果存入指定的緩存中
public User find(Integer id) {
return null;
}
附:
六、Redis集群配置
1. 配置application.properties
# (普通集群,不使用則不用開啟)以逗號分隔的“主機:端口”對列表進行引導。
spring.redis.cluster.nodes=127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385
spring.redis.cluster.timeout=1000
# (普通集群,不使用則不用開啟)在群集中執(zhí)行命令時要遵循的最大重定向數(shù)目。
spring.redis.cluster.max-redirects=3
注意:一旦開啟了集群模式,那么基于單機的配置就會覆蓋。
2. 配置類
RedisConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
import java.io.Serializable;
@Configuration
public class RedisConfiguration {
@Resource
private LettuceConnectionFactory myLettuceConnectionFactory;
@Bean
public RedisTemplate<String, Serializable> redisTemplate() {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(myLettuceConnectionFactory);
return template;
}
}
RedisFactoryConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RedisFactoryConfig {
@Autowired
private Environment environment;
@Bean
public RedisConnectionFactory myLettuceConnectionFactory() {
Map<String, Object> source = new HashMap<String, Object>();
source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));
source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout"));
source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));
RedisClusterConfiguration redisClusterConfiguration;
redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
return new LettuceConnectionFactory(redisClusterConfiguration);
}
}
3. 測試
@Slf4j
public class UserServiceTest extends BaseApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void redisTemplate() throws Exception {
redisTemplate.opsForValue().set("author", "houjianjun");
}
}
結果:

常見錯誤:
java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
GenericObjectPoolConfig沒有找到是因為spring-data-redis版本太高,去掉指定的version即可。
pringboot2.0的redis整合包多出lettuce連接池,需要commons-pool2,所以項目pom依賴要添加commons-pool2
<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- io.lettuce.core.RedisException: Connection closed
找到redis的配置文件 redis.conf
vim redis.conf
修改 protected-mode yes
改為
protected-mode no
附:
SpringBoot集成redisson(單機,集群,哨兵)
- application.properties
spring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
#sentinel/cluster/single
spring.redis.mode=single
#連接池配置
spring.redis.pool.max-idle=16
spring.redis.pool.min-idle=8
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=3000
spring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10
#單機配置
spring.redis.single.address=192.168.60.23:6379
#集群配置
spring.redis.cluster.scan-interval=1000
spring.redis.cluster.nodes=
spring.redis.cluster.read-mode=SLAVE
spring.redis.cluster.retry-attempts=3
spring.redis.cluster.failed-attempts=3
spring.redis.cluster.slave-connection-pool-size=64
spring.redis.cluster.master-connection-pool-size=64
spring.redis.cluster.retry-interval=1500
#哨兵配置
spring.redis.sentinel.master=business-master
spring.redis.sentinel.nodes=
spring.redis.sentinel.master-onlyWrite=true
spring.redis.sentinel.fail-max=3
- 配置文件讀取
RedisProperties.java
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.redis", ignoreUnknownFields = false)
@Data
@ToString
public class RedisProperties {
private int database;
/**
* 等待節(jié)點回復命令的時間。該時間從命令發(fā)送成功時開始計時
*/
private int timeout;
private String password;
private String mode;
/**
* 池配置
*/
private RedisPoolProperties pool;
/**
* 單機信息配置
*/
private RedisSingleProperties single;
/**
* 集群 信息配置
*/
private RedisClusterProperties cluster;
/**
* 哨兵配置
*/
private RedisSentinelProperties sentinel;
}
池配置RedisPool.Properties
import lombok.Data;
import lombok.ToString;
/**
* @author Abbot
* @des redis 池配置
* @date 2018/10/18 10:43
**/
@Data
@ToString
public class RedisPoolProperties {
private int maxIdle;
private int minIdle;
private int maxActive;
private int maxWait;
private int connTimeout;
private int soTimeout;
/**
* 池大小
*/
private int size;
}
RedisSingleProperties.java
@Data
@ToString
public class RedisSingleProperties {
private String address;
}
集群配置RedisClusterProperties.java
@Data
@ToString
public class RedisClusterProperties {
/**
* 集群狀態(tài)掃描間隔時間,單位是毫秒
*/
private int scanInterval;
/**
* 集群節(jié)點
*/
private String nodes;
/**
* 默認值: SLAVE(只在從服務節(jié)點里讀取)設置讀取操作選擇節(jié)點的模式。 可用值為: SLAVE - 只在從服務節(jié)點里讀取。
* MASTER - 只在主服務節(jié)點里讀取。 MASTER_SLAVE - 在主從服務節(jié)點里都可以讀取
*/
private String readMode;
/**
* (從節(jié)點連接池大小) 默認值:64
*/
private int slaveConnectionPoolSize;
/**
* 主節(jié)點連接池大?。┠J值:64
*/
private int masterConnectionPoolSize;
/**
* (命令失敗重試次數(shù)) 默認值:3
*/
private int retryAttempts;
/**
*命令重試發(fā)送時間間隔,單位:毫秒 默認值:1500
*/
private int retryInterval;
/**
* 執(zhí)行失敗最大次數(shù)默認值:3
*/
private int failedAttempts;
}
哨兵配置 RedisSentinelProperties.java
@Data
@ToString
public class RedisSentinelProperties {
/**
* 哨兵master 名稱
*/
private String master;
/**
* 哨兵節(jié)點
*/
private String nodes;
/**
* 哨兵配置
*/
private boolean masterOnlyWrite;
/**
*
*/
private int failMax;
}
CacheConfiguration.java
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfiguration {
@Autowired
RedisProperties redisProperties;
@Configuration
@ConditionalOnClass({Redisson.class})
@ConditionalOnExpression("'${spring.redis.mode}'=='single' or '${spring.redis.mode}'=='cluster' or '${spring.redis.mode}'=='sentinel'")
protected class RedissonSingleClientConfiguration {
/**
* 單機模式 redisson 客戶端
*/
@Bean
@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "single")
RedissonClient redissonSingle() {
Config config = new Config();
String node = redisProperties.getSingle().getAddress();
node = node.startsWith("redis://") ? node : "redis://" + node;
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(node)
.setTimeout(redisProperties.getPool().getConnTimeout())
.setConnectionPoolSize(redisProperties.getPool().getSize())
.setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
/**
* 集群模式的 redisson 客戶端
*
* @return
*/
@Bean
@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "cluster")
RedissonClient redissonCluster() {
System.out.println("cluster redisProperties:" + redisProperties.getCluster());
Config config = new Config();
String[] nodes = redisProperties.getCluster().getNodes().split(",");
List<String> newNodes = new ArrayList(nodes.length);
Arrays.stream(nodes).forEach((index) -> newNodes.add(
index.startsWith("redis://") ? index : "redis://" + index));
ClusterServersConfig serverConfig = config.useClusterServers()
.addNodeAddress(newNodes.toArray(new String[0]))
.setScanInterval(
redisProperties.getCluster().getScanInterval())
.setIdleConnectionTimeout(
redisProperties.getPool().getSoTimeout())
.setConnectTimeout(
redisProperties.getPool().getConnTimeout())
.setFailedAttempts(
redisProperties.getCluster().getFailedAttempts())
.setRetryAttempts(
redisProperties.getCluster().getRetryAttempts())
.setRetryInterval(
redisProperties.getCluster().getRetryInterval())
.setMasterConnectionPoolSize(redisProperties.getCluster()
.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(redisProperties.getCluster()
.getSlaveConnectionPoolSize())
.setTimeout(redisProperties.getTimeout());
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
/**
* 哨兵模式 redisson 客戶端
* @return
*/
@Bean
@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "sentinel")
RedissonClient redissonSentinel() {
System.out.println("sentinel redisProperties:" + redisProperties.getSentinel());
Config config = new Config();
String[] nodes = redisProperties.getSentinel().getNodes().split(",");
List<String> newNodes = new ArrayList(nodes.length);
Arrays.stream(nodes).forEach((index) -> newNodes.add(
index.startsWith("redis://") ? index : "redis://" + index));
SentinelServersConfig serverConfig = config.useSentinelServers()
.addSentinelAddress(newNodes.toArray(new String[0]))
.setMasterName(redisProperties.getSentinel().getMaster())
.setReadMode(ReadMode.SLAVE)
.setFailedAttempts(redisProperties.getSentinel().getFailMax())
.setTimeout(redisProperties.getTimeout())
.setMasterConnectionPoolSize(redisProperties.getPool().getSize())
.setSlaveConnectionPoolSize(redisProperties.getPool().getSize());
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
}
}
使用:
@Autowired
RedissonClient redisson;