小言_互联网的博客

JAVA开发(Redis的主从与集群)

395人阅读  评论(0)

现在web项目无处不在使用缓存技术,redis的身影可谓无处不在。但是又有多少项目使用到的是redis的集群?大概很多项目只是用到单机版的redis吧。作为缓存的一块,set ,get数据。用的不亦乐乎。但是对于高可用系统来说,数据集群是很有必要的。

我们看单机版的redis配置

springBoot引入maven依赖


  
  1. <dependency>
  2. <groupId>org.springframework.boot </groupId>
  3. <artifactId>spring-boot-starter-cache </artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot </groupId>
  7. <artifactId>spring-boot-starter-data-redis </artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>com.google.code.gson </groupId>
  11. <artifactId>gson </artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.session </groupId>
  15. <artifactId>spring-session-data-redis </artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>redis.clients </groupId>
  19. <artifactId>jedis </artifactId>
  20. </dependency>

配置springboot  yml文件 redis连接


  
  1. spring:
  2. redis:
  3. host: 127.0.0.1
  4. port: 6379
  5. password: 123456
  6. maxIdle: 20
  7. minIdle: 10
  8. maxTotal: 100
  9. database: 2
  10. busiDb: 9
  11. boeDb: 2
  12. eximportDb: 5
  13. session:
  14. store-type: redis
  15. cache:
  16. type: redis

操作redis的工具类:


  
  1. import java.util.ArrayList;
  2. import java.util.Calendar;
  3. import java.util.Collection;
  4. import java.util.Collections;
  5. import java.util.LinkedHashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;
  9. import java.util.concurrent.TimeUnit;
  10. import javax.annotation.Resource;
  11. import org.apache.commons.lang.StringUtils;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
  16. import org.springframework.dao.DataAccessException;
  17. import org.springframework.data.redis.connection.RedisConnection;
  18. import org.springframework.data.redis.core.RedisCallback;
  19. import org.springframework.data.redis.core.RedisTemplate;
  20. import org.springframework.data.redis.core.ValueOperations;
  21. import org.springframework.stereotype.Component;
  22. import com.google.gson.Gson;
  23. import cn.ctg.common.enums.EnumType;
  24. import cn.ctg.common.util.constants.Constant;
  25. import cn.hutool.core.convert.Convert;
  26. import cn.hutool.core.thread.ThreadUtil;
  27. import cn.hutool.core.util.RandomUtil;
  28. import cn.hutool.core.util.StrUtil;
  29. import redis.clients.jedis.Jedis;
  30. import redis.clients.jedis.JedisCluster;
  31. /**
  32. * Redis工具类
  33. *
  34. */
  35. @Component
  36. public class RedisUtils {
  37. private final Logger logger = LoggerFactory.getLogger( this.getClass());
  38. @Autowired
  39. private RedisTemplate redisTemplate;
  40. @Resource(name = "redisTemplate")
  41. private ValueOperations<String, String> valueOperations;
  42. @Autowired
  43. private RedisExtendService redisExtendService;
  44. /** 加分布式锁的LUA脚本 */
  45. private static final String LOCK_LUA =
  46. "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
  47. /** 计数器的LUA脚本 */
  48. private static final String INCR_LUA =
  49. "local current = redis.call('incr',KEYS[1]);" +
  50. " local t = redis.call('ttl',KEYS[1]); " +
  51. "if t == -1 then " +
  52. "redis.call('expire',KEYS[1],ARGV[1]) " +
  53. "end; " +
  54. "return current";
  55. /** 解锁的LUA脚本 */
  56. private static final String UNLOCK_LUA =
  57. "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  58. private static final Long SUCCESS = 1L;
  59. /** 互斥锁过期时间(分钟) */
  60. private static final long MUTEX_WAIT_MILLISECONDS = 50;
  61. /** 编号规则生成线程等待次数 ((10 * 1000) / 50) + 1 */
  62. public static final long RULE_CODE_THREAD_WAIT_COUNT = 200;
  63. /** 互斥锁等待时间(毫秒) */
  64. private static final long MUTEX_EXPIRE_MINUTES = 3;
  65. /**
  66. * 不设置过期时长
  67. */
  68. public final static long NOT_EXPIRE = - 1;
  69. /**
  70. * 默认过期时长,单位:秒
  71. */
  72. public final static long DEFAULT_EXPIRE = 7200; // 2小时
  73. /**
  74. * 会员卡缓存失效时间 2小时
  75. */
  76. public final static long CARD_DEFAULT_EXPIRE = 7200;
  77. /**
  78. * 默认过期时长,1天
  79. */
  80. public final static long DEFAULT_A_DAY = 86400;
  81. /**
  82. * 默认过期时长,1分钟
  83. */
  84. public final static long DEFAULT_A_MIN = 60 ;
  85. /**
  86. * 默认过期时长,2分钟
  87. */
  88. public final static long DEFAULT_TWO_MIN = 120 ;
  89. /**
  90. * 保存数据
  91. *
  92. * @param key
  93. * @param value
  94. * @param expire 过期时间,单位s
  95. */
  96. public void set (String key, Object value, long expire) {
  97. String valueJson = toJson(value);
  98. valueOperations.set(key, valueJson);
  99. if (expire != NOT_EXPIRE) {
  100. redisTemplate.expire(key, expire, TimeUnit.SECONDS);
  101. }
  102. redisExtendService.redisDataChange(key, valueJson, EnumType.CRUD_TYPE.CREATE.getValue());
  103. }
  104. /**
  105. * 判断key是否存在
  106. *
  107. * @param key
  108. */
  109. public Boolean hasKey (String key) {
  110. if (StringUtils.isNotBlank(key)) {
  111. return valueOperations.getOperations().hasKey(key);
  112. }
  113. return Boolean.FALSE;
  114. }
  115. /**
  116. * @param key
  117. * @param value
  118. */
  119. public void set (String key, Object value) {
  120. set(key, value, NOT_EXPIRE);
  121. }
  122. public <T> T get (String key, Class<T> clazz, long expire) {
  123. String value = Convert.toStr(valueOperations.get(key));
  124. if (expire != NOT_EXPIRE) {
  125. redisTemplate.expire(key, expire, TimeUnit.SECONDS);
  126. }
  127. return value == null ? null : fromJson(value, clazz);
  128. }
  129. /**
  130. * 批量从Redis中获取数据
  131. *
  132. * @param valueMap 需要存储的数据集合
  133. * @param expire 过期时间,秒
  134. * @return java.util.List<T> 返回值
  135. */
  136. public void batchSet (Map<String, String> valueMap, long expire) {
  137. valueOperations.multiSet(valueMap);
  138. if (expire != NOT_EXPIRE) {
  139. for (String key : valueMap.keySet()) {
  140. redisTemplate.expire(key, expire, TimeUnit.SECONDS);
  141. }
  142. }
  143. }
  144. /**
  145. * 批量删除
  146. *
  147. * @param keys 需要删除的KEY集合
  148. * @return void
  149. */
  150. public void batchDelete (Collection<String> keys) {
  151. redisTemplate.delete(keys);
  152. }
  153. /**
  154. * 批量从Redis中获取数据
  155. *
  156. * @param keyList 需要获取的Key集合
  157. * @param clazz 需要转换的类型
  158. * @return java.util.List<T> 返回值
  159. */
  160. public <T> Map<String, T> batchGet (List<String> keyList, Class<T> clazz) {
  161. List<String> objectList = valueOperations.multiGet(keyList);
  162. Map<String, T> map = new LinkedHashMap<>(objectList.size());
  163. for ( int i = 0; i < keyList.size(); i++) {
  164. String value = Convert.toStr(objectList.get(i));
  165. if (!String.class.equals(clazz)) {
  166. map.put(keyList.get(i), fromJson(value, clazz));
  167. } else {
  168. map.put(keyList.get(i), (T)value);
  169. }
  170. }
  171. return map;
  172. }
  173. public <T> T get (String key, Class<T> clazz) {
  174. return get(key, clazz, NOT_EXPIRE);
  175. }
  176. /**
  177. * 使用 父编码+当前编码获取+集团+语言 获取名称
  178. *
  179. * @param code 父编码
  180. * @param dictCode 当前编码
  181. * @param language 语言 UserUtils.getLanguage()
  182. * @param groupId 集团ID
  183. */
  184. public String get (String code, String dictCode, String language, String groupId) {
  185. if (StringUtils.isBlank(dictCode)) {
  186. return "";
  187. }
  188. String key = RedisKeys.getSysDictKey(code, dictCode, language, groupId);
  189. return get(key, NOT_EXPIRE);
  190. }
  191. public String get (String key, long expire) {
  192. String value = Convert.toStr(valueOperations.get(key));
  193. if (expire != NOT_EXPIRE) {
  194. redisTemplate.expire(key, expire, TimeUnit.SECONDS);
  195. }
  196. return value;
  197. }
  198. public String get (String key) {
  199. return get(key, NOT_EXPIRE);
  200. }
  201. public void delete (String key) {
  202. redisTemplate.delete(key);
  203. redisExtendService.redisDataChange(key, "", EnumType.CRUD_TYPE.DELETE.getValue());
  204. }
  205. /**
  206. * Object转成JSON数据
  207. */
  208. private String toJson (Object object) {
  209. if (object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double
  210. || object instanceof Boolean || object instanceof String) {
  211. return String.valueOf(object);
  212. }
  213. return new Gson().toJson(object);
  214. }
  215. /**
  216. * JSON数据,转成Object
  217. */
  218. private <T> T fromJson (String json, Class<T> clazz) {
  219. return new Gson().fromJson(json, clazz);
  220. }
  221. /**
  222. * 获取分布式锁,默认过期时间3分钟
  223. *
  224. * @param key 锁的KEY
  225. * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
  226. */
  227. public Boolean setMutexLock (String key) {
  228. return setMutexLockAndExpire(key, getMutexLockExpireMinutes(), TimeUnit.MINUTES);
  229. }
  230. /**
  231. * 获取分布式锁,带Redis事务
  232. *
  233. * @param key 锁的KEY
  234. * @param timeout 锁时效时间,默认单位:秒
  235. * @param unit 锁失效时间单位,为null则默认秒
  236. * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
  237. */
  238. public Boolean setMutexLockAndExpire (String key, long timeout, TimeUnit unit) {
  239. return setMutexLockAndExpire(key, Constant.RESULT_1, timeout, unit);
  240. }
  241. /**
  242. * 获取分布式锁,带Redis事务
  243. * 适用于同一业务,不同的请求用不同的锁,把value当成
  244. * @param key 锁的KEY
  245. * @param value 锁的值,一定要跟解锁的值一样,否则会导致无法解锁
  246. * @param timeout 锁时效时间,默认单位:秒
  247. * @param unit 锁失效时间单位,为null则默认秒
  248. * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
  249. */
  250. public Boolean setMutexLockAndExpire (String key, String value, long timeout, TimeUnit unit) {
  251. value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value, "\""), "\"");
  252. Long result = executeLua(key, value, LOCK_LUA, timeout, unit, Long.class);
  253. return SUCCESS.equals(result);
  254. }
  255. /**
  256. * 解锁
  257. *
  258. * @param key 锁的Key
  259. * @return boolean
  260. */
  261. public boolean unlock (String key) {
  262. return unlock(key, Constant.RESULT_1);
  263. }
  264. /**
  265. * 解锁
  266. *
  267. * @param key 锁的Key
  268. * @param value 锁的value,一定要跟加锁的value一致,否则会认为不是同一个锁,不会释放
  269. * @return boolean
  270. */
  271. public boolean unlock (String key, String value) {
  272. value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value, "\""), "\"");
  273. Long result = executeLua(key, value, UNLOCK_LUA, null, null, Long.class);
  274. return SUCCESS.equals(result);
  275. }
  276. /**
  277. * 获取等待锁,如果没有获取到锁就一直等待获取,直到超过waitTime的时间
  278. *
  279. * @param key 锁的key
  280. * @param timeout 锁的超时时间
  281. * @param unit 锁的超时时间单位
  282. * @param waitTime 获取锁时的等待时间,一直等不超时则填-1,单位:毫秒
  283. * @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁
  284. */
  285. public Boolean setMutexWaitLock (String key, long timeout, TimeUnit unit, long waitTime) {
  286. long start = System.currentTimeMillis();
  287. while ( true) {
  288. boolean result = setMutexLockAndExpire(key, timeout, unit);
  289. if (result) {
  290. return true;
  291. } else {
  292. long current = System.currentTimeMillis();
  293. // 超过等待时间还没获取到锁则返回false
  294. if (waitTime > 0 && (current - start > waitTime)) {
  295. logger.warn( "redis分布式锁获取失败,key[{}],等待时间[{}]", key, waitTime);
  296. return false;
  297. }
  298. // 等待100毫秒后重试
  299. ThreadUtil.sleep( 100);
  300. }
  301. }
  302. }
  303. public long getMutexLockExpireMinutes () {
  304. return MUTEX_EXPIRE_MINUTES;
  305. }
  306. /**
  307. * 获取自增序列号
  308. *
  309. * @param key 序列号的KEY
  310. * @param seq 自增值,默认自增1
  311. * @return java.lang.Long 自增后的值
  312. */
  313. public Long incr (String key, Long seq, long timeout, TimeUnit unit) {
  314. return executeLua(key, null, INCR_LUA, timeout, unit, Long.class);
  315. }
  316. /**
  317. * 执行LUA脚本
  318. *
  319. * @param key redisKey
  320. * @param value 值
  321. * @param lua lua脚本
  322. * @param timeout 超时时间
  323. * @param unit 超时单位
  324. * @param clazz 返回值类型
  325. * @return T 返回值
  326. */
  327. public <T> T executeLua (String key, Object value, String lua, Long timeout, TimeUnit unit, Class<T> clazz){
  328. // 有时间单位则转成秒,否则默认秒
  329. if (unit != null) {
  330. timeout = unit.toSeconds(timeout);
  331. }
  332. List<String> args = new ArrayList<>( 2);
  333. if(value != null){
  334. args.add(Convert.toStr(value));
  335. }
  336. if(timeout != null){
  337. args.add(Convert.toStr(timeout));
  338. }
  339. //spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本异常,此处拿到原redis的connection执行脚本
  340. T result = (T)redisTemplate.execute( new RedisCallback<T>() {
  341. @Override
  342. public T doInRedis (RedisConnection connection) throws DataAccessException {
  343. Object nativeConnection = connection.getNativeConnection();
  344. // 集群模式和单点模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
  345. // 集群
  346. if (nativeConnection instanceof JedisCluster) {
  347. return (T) ((JedisCluster) nativeConnection).eval(lua, Collections.singletonList(key), args);
  348. }
  349. // 单点
  350. else if (nativeConnection instanceof RedisProperties.Jedis) {
  351. return (T) ((Jedis) nativeConnection).eval(lua, Collections.singletonList(key), args);
  352. }
  353. return null;
  354. }
  355. });
  356. return result;
  357. }
  358. public void expire (String key, long timeout, TimeUnit unit) {
  359. try {
  360. redisTemplate.expire(key, timeout, unit);
  361. } catch (Exception e) {
  362. logger.error( "设置缓存过期时间失败,key={},timeout={},unit={}", key, timeout, unit, e);
  363. }
  364. }
  365. /**
  366. * 获取互斥线程等待时间
  367. *
  368. * @return
  369. */
  370. public long getMutexThreadWaitMilliseconds () {
  371. return MUTEX_WAIT_MILLISECONDS;
  372. }
  373. public Set<String> getKeys (String key) {
  374. return redisTemplate.keys(key);
  375. }
  376. /**
  377. * 获取随机秒数 如:getRandomTime(30, 7)返回30天到第37天的随机秒数,即时效时间最小为30天,最大为37天
  378. *
  379. * @param afterDays N天之后
  380. * @param rangeDay 日期范围
  381. * @return java.lang.Long 秒数
  382. */
  383. public static Long getRandomTime (int afterDays, int rangeDay) {
  384. Calendar calendar = Calendar.getInstance();
  385. long curTime = calendar.getTimeInMillis();
  386. calendar.add(Calendar.DAY_OF_MONTH, afterDays);
  387. long minTime = calendar.getTimeInMillis();
  388. calendar.add(Calendar.DAY_OF_MONTH, rangeDay);
  389. long maxTime = calendar.getTimeInMillis();
  390. long randomTime = RandomUtil.randomLong(minTime, maxTime);
  391. return (randomTime - curTime) / 1000;
  392. }
  393. /**
  394. * 获取30天内的随机秒数
  395. *
  396. * @return long 返回1天后30天内的随机秒数
  397. */
  398. public static long getRandomTime () {
  399. return getRandomTime( 1, 30);
  400. }
  401. public void setnx (String key,String value){
  402. }
  403. }

说说使用集群的情况。

redis的主从复制。

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

redis哨兵模式。

哨兵(sentinel):是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的 Master 并将所有 Slave 连接到新的 Master。所以整个运行哨兵的集群的数量不得少于3个节点。

选三台redis搭建集群

 

添加springBootmaven依赖


  
  1. <!--redis-->
  2. <dependency>
  3. <groupId>org.springframework.boot </groupId>
  4. <artifactId>spring-boot-starter-data-redis </artifactId>
  5. </dependency>
  6. <!--连接池依赖-->
  7. <dependency>
  8. <groupId>org.apache.commons </groupId>
  9. <artifactId>commons-pool2 </artifactId>
  10. </dependency>
  11. <!--web-->
  12. <dependency>
  13. <groupId>org.springframework.boot </groupId>
  14. <artifactId>spring-boot-starter-web </artifactId>
  15. </dependency>
  16. <!--lombok-->
  17. <dependency>
  18. <groupId>org.projectlombok </groupId>
  19. <artifactId>lombok </artifactId>
  20. <version>1.18.24 </version>
  21. </dependency>

集群yml文件配置


  
  1. server:
  2. port: 3035
  3. spring:
  4. redis:
  5. # redis哨兵配置
  6. sentinel:
  7. # 主节点名称
  8. master: mymaster
  9. nodes:
  10. - 192.168.200.150:6379
  11. - 192.168.200.150:6380
  12. - 192.168.200.150:6381
  13. # # 集群的部署方式
  14. # cluster:
  15. # nodes:
  16. # - 192.168.200.150:6379
  17. # - 192.168.200.150:6380
  18. # - 192.168.200.150:6381
  19. # # #最大重定向次数(由于集群中数据存储在多个节点,所以在访问数据时需要通过转发进行数据定位)
  20. # max-redirects: 2
  21. # lettuce:
  22. # pool:
  23. # max-idle: 10 # 连接池中的最大空闲连接
  24. # max-wait: 500 # 连接池最大阻塞等待时间(使用负值表示没有限制)
  25. # max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
  26. # min-idle: 0 # 连接池中的最小空闲连接
  27. # 服务应用名
  28. application:
  29. name: redis-cluster
  30. logging:
  31. pattern:
  32. console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'
  33. level:
  34. root: info
  35. io.lettuce.core: debug
  36. org.springframework.data.redis: debug

配置读写分离


  
  1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  2. import com.fasterxml.jackson.annotation.PropertyAccessor;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  5. import io.lettuce.core.ReadFrom;
  6. import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.context.annotation.Primary;
  10. import org.springframework.data.redis.connection.RedisConnectionFactory;
  11. import org.springframework.data.redis.connection.RedisSentinelConfiguration;
  12. import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  13. import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
  14. import org.springframework.data.redis.core.RedisTemplate;
  15. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  16. import org.springframework.data.redis.serializer.StringRedisSerializer;
  17. import java.text.SimpleDateFormat;
  18. import java.util.HashSet;
  19. @Configuration
  20. public class RedisConfiguration {
  21. /**
  22. *
  23. * 配置redis序列化json
  24. * @param redisConnectionFactory
  25. * @return
  26. */
  27. @Bean
  28. @Primary //若有相同类型的Bean时,优先使用此注解标注的Bean
  29. public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) {
  30. // 为了开发方便,一般直接使用<String, Object>
  31. RedisTemplate<String, Object> template = new RedisTemplate<>();
  32. template.setConnectionFactory(redisConnectionFactory);
  33. // 配置具体的序列化方式
  34. // JSON解析任意对象
  35. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  36. ObjectMapper om = new ObjectMapper();
  37. // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
  38. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  39. // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
  40. om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
  41. // 设置日期格式
  42. om.setDateFormat( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"));
  43. jackson2JsonRedisSerializer.setObjectMapper(om);
  44. // String的序列化
  45. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  46. //key采用String的序列化
  47. template.setKeySerializer(stringRedisSerializer);
  48. //hash的key也采用String的序列化
  49. template.setHashKeySerializer(stringRedisSerializer);
  50. //value的序列化方式采用jackson
  51. template.setValueSerializer(jackson2JsonRedisSerializer);
  52. //hash的value序列化方式采用jackson
  53. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  54. //设置所有配置
  55. template.afterPropertiesSet();
  56. return template;
  57. }
  58. /**
  59. * 配置读写分离
  60. * @param redisProperties
  61. * @return
  62. */
  63. @Bean
  64. public RedisConnectionFactory lettuceConnectionFactory (RedisProperties redisProperties) {
  65. // 配置哨兵节点以及主节点
  66. RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
  67. redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
  68. );
  69. // 配置读写分离
  70. LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
  71. // 读写分离,这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择
  72. // MASTER 仅读取主节点
  73. // MASTER_PREFERRED 优先读取主节点,如果主节点不可用,则读取从节点
  74. // REPLICA_PREFERRED 优先读取从节点,如果从节点不可用,则读取主节点
  75. // REPLICA 仅读取从节点
  76. // NEAREST 从最近节点读取
  77. // ANY 从任意一个从节点读取
  78. .readFrom(ReadFrom.REPLICA_PREFERRED)
  79. .build();
  80. return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
  81. }
  82. }

再使用工具类操作就行。


  
  1. package com.vinjcent.utils;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.RedisTemplate;
  4. import org.springframework.stereotype.Component;
  5. import java.time.LocalDateTime;
  6. import java.time.ZoneOffset;
  7. import java.time.format.DateTimeFormatter;
  8. import java.util.Arrays;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Set;
  12. import java.util.concurrent.TimeUnit;
  13. @Component
  14. public final class RedisUtils {
  15. private final RedisTemplate<String, Object> redisTemplate;
  16. /**
  17. * 可按自己需求生成"起始时间戳"
  18. */
  19. private static final long BEGIN_TIMESTAMP = 1640995200L;
  20. /**
  21. * 用于时间戳左移32位
  22. */
  23. public static final int MOVE_BITS = 32;
  24. @Autowired
  25. public RedisUtils (RedisTemplate<String, Object> redisTemplate) {
  26. this.redisTemplate = redisTemplate;
  27. }
  28. //=============================common===================================
  29. /**
  30. * 指定缓存失效时间
  31. * @param key 键
  32. * @param time 时间(秒)
  33. * @return whether the key has expired
  34. */
  35. public boolean expire (String key, long time){
  36. try {
  37. if(time > 0){
  38. redisTemplate.expire(key, time, TimeUnit.SECONDS);
  39. }
  40. return true;
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. return false;
  44. }
  45. }
  46. /**
  47. * 指定缓存失效时间(自定义时间单位)
  48. * @param key 键
  49. * @param time 时间(秒)
  50. * @return whether the key has expired
  51. */
  52. public boolean expire (String key, long time, TimeUnit unit){
  53. try {
  54. if(time > 0){
  55. redisTemplate.expire(key, time, unit);
  56. }
  57. return true;
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. return false;
  61. }
  62. }
  63. /**
  64. * 根据key获取过期时间(默认获取的是秒单位)
  65. * @param key 键(不能为null)
  66. * @return the remaining time, "0" means never expire
  67. */
  68. public long getExpire (String key){
  69. Long time = redisTemplate.getExpire(key, TimeUnit.SECONDS);
  70. if (time != null) {
  71. return time;
  72. }
  73. return - 1L;
  74. }
  75. /**
  76. * 根据key获取过期时间(自定义时间单位)
  77. * @param key 键(不能为null)
  78. * @return the remaining time, "0" means never expire
  79. */
  80. public long getExpire (String key, TimeUnit unit){
  81. Long time = redisTemplate.getExpire(key, unit);
  82. if (time != null) {
  83. return time;
  84. }
  85. return - 1L;
  86. }
  87. /**
  88. * 判断key是否存在
  89. * @param key 键
  90. * @return whether the key exist
  91. */
  92. public boolean hasKey (String key) {
  93. Boolean flag = redisTemplate.hasKey(key);
  94. try {
  95. return Boolean.TRUE.equals(flag);
  96. } catch (Exception e) {
  97. e.printStackTrace();
  98. return false;
  99. }
  100. }
  101. /**
  102. * 删除缓存
  103. * @param key 键,可以传递一个值或多个
  104. */
  105. public void del (String... key) {
  106. if(key != null && key.length > 0){
  107. if (key.length == 1){
  108. redisTemplate.delete(key[ 0]);
  109. } else {
  110. redisTemplate.delete(Arrays.asList(key));
  111. }
  112. }
  113. }
  114. //=============================String===================================
  115. /**
  116. * 普通缓存获取(泛型)
  117. * @param key key键
  118. * @return the value corresponding the key
  119. */
  120. public Object get (String key){ return key == null ? null : redisTemplate.opsForValue().get(key);}
  121. /**
  122. * 普通缓存获取(泛型)
  123. * @param key key键
  124. * @return the value corresponding the key
  125. * @param targetType 目标类型
  126. * @param <T> 目标类型参数
  127. * @return the generic value corresponding the key
  128. */
  129. public <T> T get (String key, Class<T> targetType){ return key == null ? null : JsonUtils.objParse(redisTemplate.opsForValue().get(key), targetType);}
  130. /**
  131. * 普通缓存放入
  132. * @param key 键
  133. * @param value 值
  134. * @return whether true or false
  135. */
  136. public boolean set (String key, Object value){
  137. try {
  138. redisTemplate.opsForValue().set(key,value);
  139. return true;
  140. } catch (Exception e) {
  141. e.printStackTrace();
  142. return false;
  143. }
  144. }
  145. /**
  146. * 普通缓存放入并设置时间
  147. * @param key 键
  148. * @param value 值
  149. * @param time 时间(秒) --- time要大于0,如果time小于0,将设置为无期限
  150. * @return whether true or false
  151. */
  152. public boolean set (String key, Object value, long time){
  153. try {
  154. if(time > 0){
  155. redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
  156. } else {
  157. set(key,value);
  158. }
  159. return true;
  160. } catch (Exception e) {
  161. e.printStackTrace();
  162. return false;
  163. }
  164. }
  165. /**
  166. * 普通缓存放入并设置时间和时间单位
  167. * @param key 键
  168. * @param value 值
  169. * @param time 时间(秒) --- time要大于0,如果time小于0,将设置为无期限
  170. * @param timeUnit 时间单位
  171. * @return whether true or false
  172. */
  173. public boolean set (String key, Object value, long time, TimeUnit timeUnit){
  174. try {
  175. if(time > 0){
  176. redisTemplate.opsForValue().set(key, value, time, timeUnit);
  177. } else {
  178. set(key,value);
  179. }
  180. return true;
  181. } catch (Exception e) {
  182. e.printStackTrace();
  183. return false;
  184. }
  185. }
  186. /**
  187. * 递增
  188. * @param key 键
  189. * @param delta 要增加几(大于0)
  190. * @return the value after increment
  191. */
  192. public long incr (String key, long delta){
  193. if(delta < 0){
  194. throw new RuntimeException( "递增因子必须大于0");
  195. }
  196. Long increment = redisTemplate.opsForValue().increment(key, delta);
  197. return increment != null ? increment : 0L;
  198. }
  199. /**
  200. * 递减
  201. * @param key 键
  202. * @param delta 要增加几(小于0)
  203. * @return the value after decrement
  204. */
  205. public long decr (String key, long delta){
  206. if(delta < 0){
  207. throw new RuntimeException( "递减因子必须大于0");
  208. }
  209. Long increment = redisTemplate.opsForValue().increment(key, delta);
  210. return increment != null ? increment : 0L; }
  211. //=============================Map===================================
  212. /**
  213. * 根据hashKey获取hash列表有多少元素
  214. * @param key 键(hashKey)
  215. * @return the size of map
  216. */
  217. public long hsize (String key) {
  218. try {
  219. return redisTemplate.opsForHash().size(key);
  220. } catch (Exception e) {
  221. e.printStackTrace();
  222. return 0L;
  223. }
  224. }
  225. /**
  226. * HashGet 根据"项 中的 键 获取列表"
  227. * @param key 键(hashKey)能为null
  228. * @param item 项不能为null
  229. * @return the value of the corresponding key
  230. */
  231. public Object hget (String key, String item){ return redisTemplate.opsForHash().get(key, item);}
  232. /**
  233. * 获取HashKey对应的所有键值
  234. * @param key 键(hashKey)
  235. * @return 对应的多个键值
  236. */
  237. public Map<Object, Object> hmget (String key) { return redisTemplate.opsForHash().entries(key);}
  238. /**
  239. * 获取HashKey对应的所有键值
  240. * @param key 键(hashKey)
  241. * @param keyType 键类型
  242. * @param valueType 值类型
  243. * @param <K> 键类型参数
  244. * @param <V> 值类型参数
  245. * @return a map
  246. */
  247. public <K, V> Map<K, V> hmget (String key, Class<K> keyType, Class<V> valueType) {
  248. return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);}
  249. /**
  250. * HashSet 存入多个键值对
  251. * @param key 键(hashKey)
  252. * @param map map 对应多个键值对
  253. */
  254. public void hmset (String key, Map<String, Object> map) {
  255. try {
  256. redisTemplate.opsForHash().putAll(key,map);
  257. } catch (Exception e) {
  258. e.printStackTrace();
  259. }
  260. }
  261. /**
  262. * HashSet存入并设置时间
  263. * @param key 键(hashKey)
  264. * @param map 对应多个键值
  265. * @param time 时间(秒)
  266. * @return whether true or false
  267. */
  268. public boolean hmset (String key, Map<String, Object> map, long time){
  269. try {
  270. redisTemplate.opsForHash().putAll(key,map);
  271. if (time > 0){
  272. expire(key,time);
  273. }
  274. return true;
  275. } catch (Exception e) {
  276. e.printStackTrace();
  277. return false;
  278. }
  279. }
  280. /**
  281. * 向一张hash表中放入数据,如果不存在将创建
  282. * @param key 键(hashKey)
  283. * @param item 项
  284. * @param value 值
  285. * @return whether true or false
  286. */
  287. public boolean hset (String key, String item, Object value){
  288. try {
  289. redisTemplate.opsForHash().put(key, item, value);
  290. return true;
  291. } catch (Exception e) {
  292. e.printStackTrace();
  293. return false;
  294. }
  295. }
  296. /**
  297. * 向一张hash表中放入数据,如果不存在将创建,并设置有效时间
  298. * @param key 键(hashKey)
  299. * @param item 项
  300. * @param value 值
  301. * @param time 时间(秒) 注意: 如果已经在hash表有时间,这里将会替换所有的时间
  302. * @return whether true or false
  303. */
  304. public boolean hset (String key, String item, Object value, long time){
  305. try {
  306. redisTemplate.opsForHash().put(key, item, value);
  307. if (time > 0){
  308. expire(key,time);
  309. }
  310. return true;
  311. } catch (Exception e) {
  312. e.printStackTrace();
  313. return false;
  314. }
  315. }
  316. /**
  317. * 放入map集合数据,如果不存在将创建
  318. * @param key 键(hashKey)
  319. * @param value map集合
  320. * @param <K> map集合键参数类型
  321. * @param <V> map集合值参数类型
  322. * @return whether true or false
  323. */
  324. public <K, V> boolean hsetMap (String key, Map<K, V> value) {
  325. try {
  326. redisTemplate.opsForHash().putAll(key, value);
  327. return true;
  328. } catch (Exception e) {
  329. e.printStackTrace();
  330. return false;
  331. }
  332. }
  333. /**
  334. * 获取key对应的所有map键值对
  335. * @param key 键(hashKey)
  336. * @return the Map
  337. */
  338. public Map<Object, Object> hgetMap (String key) {
  339. try {
  340. return redisTemplate.opsForHash().entries(key);
  341. } catch (Exception e) {
  342. e.printStackTrace();
  343. return null;
  344. }
  345. }
  346. /**
  347. * 获取key对应的所有map键值对(泛型)
  348. * @param key 键(hashKey)
  349. * @param keyType 键类型
  350. * @param valueType 值类型
  351. * @param <K> 键类型参数
  352. * @param <V> 值类型参数
  353. * @return the Map
  354. */
  355. public <K, V> Map<K, V> hgetMap (String key, Class<K> keyType, Class<V> valueType) {
  356. try {
  357. return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);
  358. } catch (Exception e) {
  359. e.printStackTrace();
  360. return null;
  361. }
  362. }
  363. /**
  364. * 删除hash表中的值
  365. * @param key 键(hashKey) 不能为null
  366. * @param item 项可以是多个 不能为null
  367. */
  368. public void hdel (String key, Object... item){
  369. redisTemplate.opsForHash().delete(key,item);
  370. }
  371. /**
  372. * 判断hash表是否有该项的值
  373. * @param key 键(hashKey)不能为null
  374. * @param item 项不能为null
  375. * @return whether true or false
  376. */
  377. public boolean hHasKey (String key, String item){
  378. return redisTemplate.opsForHash().hasKey(key, item);
  379. }
  380. /**
  381. * hash递增,如果不存在,就会创建一个,并把新增后的值返回
  382. * @param key 键(hashKey)
  383. * @param item 项
  384. * @param by 要增加几(大于0)
  385. * @return the value of the corresponding key after increment in one Map
  386. */
  387. public double hincr (String key, String item, double by){
  388. return redisTemplate.opsForHash().increment(key, item, by);
  389. }
  390. /**
  391. * hash递减
  392. * @param key 键(hashKey)
  393. * @param item 项
  394. * @param by 要减少几(小于0)
  395. * @return the value of the corresponding key after decrement in one Map
  396. */
  397. public double hdecr (String key, String item, double by){
  398. return redisTemplate.opsForHash().increment(key, item, -by);
  399. }
  400. //=============================Set===================================
  401. /**
  402. * 根据key获取Set中的所有值
  403. * @param key 键
  404. * @return all values in one Set
  405. */
  406. public Set<Object> sGet (String key){
  407. try {
  408. return redisTemplate.opsForSet().members(key);
  409. } catch (Exception e) {
  410. e.printStackTrace();
  411. return null;
  412. }
  413. }
  414. /**
  415. * 根据value从一个Set集合中查询一个值,是否存在
  416. * @param key 键
  417. * @param value 值
  418. * @return whether true or false
  419. */
  420. public boolean sHasKey (String key, Object value){
  421. try {
  422. Boolean flag = redisTemplate.opsForSet().isMember(key, value);
  423. return Boolean.TRUE.equals(flag);
  424. } catch (Exception e) {
  425. e.printStackTrace();
  426. return false;
  427. }
  428. }
  429. /**
  430. * 将数据放入set缓存
  431. * @param key 键
  432. * @param values 值
  433. * @return the number of adding successfully
  434. */
  435. public long sSet (String key, Object... values){
  436. try {
  437. Long nums = redisTemplate.opsForSet().add(key, values);
  438. return nums != null ? nums : 0L;
  439. } catch (Exception e) {
  440. e.printStackTrace();
  441. return 0L;
  442. }
  443. }
  444. /**
  445. * 将set数据放入缓存,并设置有效时间
  446. * @param key 键
  447. * @param time 时间(秒)
  448. * @param values 值,可以是多个
  449. * @return the number of adding successfully
  450. */
  451. public long sSetAndTime (String key, long time, Object... values){
  452. try {
  453. Long count = redisTemplate.opsForSet().add(key, values);
  454. if(time > 0){
  455. expire(key, time);
  456. }
  457. return count != null ? count : 0L;
  458. } catch (Exception e) {
  459. e.printStackTrace();
  460. return 0L;
  461. }
  462. }
  463. /**
  464. * 获取set缓存的长度
  465. * @param key 键
  466. * @return the size of the Set
  467. */
  468. public long sGetSetSize (String key){
  469. try {
  470. Long size = redisTemplate.opsForSet().size(key);
  471. return size != null ? size : 0L;
  472. } catch (Exception e) {
  473. e.printStackTrace();
  474. return 0L;
  475. }
  476. }
  477. /**
  478. * 移除值为values的
  479. * @param key 键
  480. * @param values 值(可以是多个)
  481. * @return the number of removal
  482. */
  483. public long setRemove (String key, Object... values){
  484. try {
  485. Long nums = redisTemplate.opsForSet().remove(key, values);
  486. return nums != null ? nums : 0L;
  487. } catch (Exception e) {
  488. e.printStackTrace();
  489. return 0;
  490. }
  491. }
  492. //=============================List===================================
  493. /**
  494. * 获取list列表数据
  495. * @param key 键
  496. * @return all values of one List
  497. */
  498. public List<Object> lget (String key) {
  499. try {
  500. return redisTemplate.opsForList().range(key, 0, - 1);
  501. } catch (Exception e) {
  502. e.printStackTrace();
  503. return null;
  504. }
  505. }
  506. /**
  507. /**
  508. * 获取list列表数据(泛型)
  509. * @param key 键
  510. * @param targetType 目标类型
  511. * @param <T> 目标类型参数
  512. * @return all values of one List
  513. */
  514. public <T> List<T> lget (String key, Class<T> targetType) {
  515. try {
  516. return JsonUtils.listParse(redisTemplate.opsForList().range(key, 0, - 1), targetType);
  517. } catch (Exception e) {
  518. e.printStackTrace();
  519. return null;
  520. }
  521. }
  522. /**
  523. * 获取list缓存的长度
  524. * @param key 键
  525. * @return the length of the List
  526. */
  527. public long lGetListSize (String key){
  528. try {
  529. Long size = redisTemplate.opsForList().size(key);
  530. return size != null ? size : 0L;
  531. } catch (Exception e) {
  532. e.printStackTrace();
  533. return 0L;
  534. }
  535. }
  536. /**
  537. * 通过索引获取list中的值
  538. * @param key 键
  539. * @param index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推... index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推
  540. * @return the value of the specified index in one List
  541. */
  542. public Object lgetIndex (String key, long index){
  543. try {
  544. return redisTemplate.opsForList().index(key, index);
  545. } catch (Exception e) {
  546. e.printStackTrace();
  547. return null;
  548. }
  549. }
  550. /**
  551. * 通过索引获取list中的值(泛型)
  552. * @param key 键
  553. * @param index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推... index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推
  554. * @return the value of the specified index in one List
  555. * @param targetType 目标类型
  556. * @param <T> 目标类型参数
  557. * @return the generic value of the specified index in one List
  558. */
  559. public <T> T lgetIndex (String key, long index, Class<T> targetType) {
  560. try {
  561. return JsonUtils.objParse(redisTemplate.opsForList().index(key, index), targetType);
  562. } catch (Exception e) {
  563. e.printStackTrace();
  564. return null;
  565. }
  566. }
  567. /**
  568. * 将list放入缓存
  569. * @param key 键
  570. * @param value 值
  571. * @return whether true or false
  572. */
  573. public boolean lSet (String key, Object value){
  574. try {
  575. redisTemplate.opsForList().rightPush(key, value);
  576. return true;
  577. } catch (Exception e) {
  578. e.printStackTrace();
  579. return false;
  580. }
  581. }
  582. /**
  583. * 将list放入缓存
  584. * @param key 键
  585. * @param value 值
  586. * @param time 时间(秒)
  587. * @return whether true or false
  588. */
  589. public boolean lSet (String key, Object value, long time){
  590. try {
  591. redisTemplate.opsForList().rightPush(key, value);
  592. if (time > 0){
  593. expire(key, time);
  594. }
  595. return true;
  596. } catch (Exception e) {
  597. e.printStackTrace();
  598. return false;
  599. }
  600. }
  601. /**
  602. * 将list集合放入缓存
  603. * @param key 键
  604. * @param values 值
  605. * @return whether true or false
  606. */
  607. public <T> boolean lSet (String key, List<T> values){
  608. try {
  609. Long nums = redisTemplate.opsForList().rightPushAll(key, values);
  610. return nums != null;
  611. } catch (Exception e) {
  612. e.printStackTrace();
  613. return false;
  614. }
  615. }
  616. /**
  617. * 将list集合放入缓存,并设置有效时间
  618. * @param key 键
  619. * @param values 值
  620. * @param time 时间(秒)
  621. * @return whether true or false
  622. */
  623. public boolean lSet (String key, List<Object> values, long time){
  624. try {
  625. redisTemplate.opsForList().rightPushAll(key, values);
  626. if (time > 0){
  627. expire(key, time);
  628. }
  629. return true;
  630. } catch (Exception e) {
  631. e.printStackTrace();
  632. return false;
  633. }
  634. }
  635. /**
  636. * 根据索引修改list中的某条数据
  637. * @param key 键
  638. * @param value 值
  639. * @param index 索引
  640. * @return whether true or false
  641. */
  642. public boolean lUpdateIndex (String key, Object value, long index){
  643. try {
  644. redisTemplate.opsForList().set(key, index, value);
  645. return true;
  646. } catch (Exception e) {
  647. e.printStackTrace();
  648. return false;
  649. }
  650. }
  651. /**
  652. * 移除N个值为value
  653. * @param key 键
  654. * @param value 值
  655. * @param number 移除多少个
  656. * @return 返回移除的个数
  657. */
  658. public long lRemove (String key, Object value, long number){
  659. try {
  660. Long count = redisTemplate.opsForList().remove(key, number, value);
  661. return count != null ? count : 0L;
  662. } catch (Exception e) {
  663. e.printStackTrace();
  664. return 0L;
  665. }
  666. }
  667. //=============================Lock===================================
  668. /**
  669. * 解决缓存加锁问题
  670. * @param key 锁名称
  671. * @param value 锁值
  672. * @param timeout 超时时间
  673. * @param unit 时间单位
  674. * @param <T> 锁值的数据类型
  675. * @return 返回加锁成功状态
  676. */
  677. public <T> boolean tryLock (String key, T value, long timeout, TimeUnit unit) {
  678. Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
  679. return Boolean.TRUE.equals(flag);
  680. }
  681. /**
  682. * 解决缓存解锁操作
  683. * @param key 锁名称
  684. * @return 返回解锁成功状态
  685. */
  686. public boolean unLock (String key) {
  687. Boolean flag = redisTemplate.delete(key);
  688. return Boolean.TRUE.equals(flag);
  689. }
  690. /**
  691. * 全局生成唯一ID策略
  692. * 设计: 符号位(1位) - 时间戳(32位) - 序列号(31位)
  693. * @param keyPrefix key的前缀
  694. * @return 返回唯一ID
  695. */
  696. public long globalUniqueKey (String keyPrefix) {
  697. // 1. 生成时间戳
  698. LocalDateTime now = LocalDateTime.now();
  699. // 东八区时间
  700. long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
  701. // 相减获取时间戳
  702. long timestamp = nowSecond - BEGIN_TIMESTAMP;
  703. // 2. 生成序列号(使用日期作为redis自增长超2^64限制,灵活使用年、月、日来存储)
  704. // 获取当天日期
  705. String date = now.format(DateTimeFormatter.ofPattern( "yyyy:MM:dd"));
  706. // 自增长
  707. Long increment = redisTemplate.opsForValue().increment( "icr:" + keyPrefix + ":" + date);
  708. long count = increment != null ? increment : 0L;
  709. // 3. 拼接并返回(使用二进制或运算)
  710. return timestamp << MOVE_BITS | count;
  711. }
  712. }


转载:https://blog.csdn.net/dongjing991/article/details/128949033
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场