> 文章列表 > JAVA开发(Redis的主从与集群)

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

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

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

我们看单机版的redis配置。

springBoot引入maven依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>

配置springboot  yml文件 redis连接

spring:redis:host: 127.0.0.1port: 6379password: 123456maxIdle: 20minIdle: 10maxTotal: 100database: 2busiDb: 9boeDb: 2eximportDb: 5session:store-type: rediscache:type: redis

操作redis的工具类:

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;import javax.annotation.Resource;import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import com.google.gson.Gson;import cn.ctg.common.enums.EnumType;
import cn.ctg.common.util.constants.Constant;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;/* Redis工具类/
@Component
public class RedisUtils {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate RedisTemplate redisTemplate;@Resource(name = "redisTemplate")private ValueOperations<String, String> valueOperations;@Autowiredprivate RedisExtendService redisExtendService;/ 加分布式锁的LUA脚本 */private static final String LOCK_LUA ="if redis.call('setNx',KEYS[1],ARGV[1])==1  then return redis.call('expire',KEYS[1],ARGV[2])  else  return 0 end";/ 计数器的LUA脚本 */private static final String INCR_LUA ="local current = redis.call('incr',KEYS[1]);" +" local t = redis.call('ttl',KEYS[1]); " +"if t == -1 then  " +"redis.call('expire',KEYS[1],ARGV[1]) " +"end; " +"return current";/ 解锁的LUA脚本 */private static final String UNLOCK_LUA ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";private static final Long SUCCESS = 1L;/ 互斥锁过期时间(分钟) */private static final long MUTEX_WAIT_MILLISECONDS = 50;/ 编号规则生成线程等待次数 ((10 * 1000) / 50) + 1 */public static final long RULE_CODE_THREAD_WAIT_COUNT = 200;/ 互斥锁等待时间(毫秒) */private static final long MUTEX_EXPIRE_MINUTES = 3;/* 不设置过期时长*/public final static long NOT_EXPIRE = -1;/* 默认过期时长,单位:秒 */public final static long DEFAULT_EXPIRE = 7200; // 2小时/* 会员卡缓存失效时间  2小时*/public final static long CARD_DEFAULT_EXPIRE = 7200;/* 默认过期时长,1天*/public final static long DEFAULT_A_DAY = 86400;/* 默认过期时长,1分钟*/public final static long DEFAULT_A_MIN = 60 ;/* 默认过期时长,2分钟*/public final static long DEFAULT_TWO_MIN = 120 ;/* 保存数据 @param key* @param value* @param expire 过期时间,单位s*/public void set(String key, Object value, long expire) {String valueJson = toJson(value);valueOperations.set(key, valueJson);if (expire != NOT_EXPIRE) {redisTemplate.expire(key, expire, TimeUnit.SECONDS);}redisExtendService.redisDataChange(key, valueJson, EnumType.CRUD_TYPE.CREATE.getValue());}/* 判断key是否存在 @param key*/public Boolean hasKey(String key) {if (StringUtils.isNotBlank(key)) {return valueOperations.getOperations().hasKey(key);}return Boolean.FALSE;}/* @param key* @param value*/public void set(String key, Object value) {set(key, value, NOT_EXPIRE);}public <T> T get(String key, Class<T> clazz, long expire) {String value = Convert.toStr(valueOperations.get(key));if (expire != NOT_EXPIRE) {redisTemplate.expire(key, expire, TimeUnit.SECONDS);}return value == null ? null : fromJson(value, clazz);}/* 批量从Redis中获取数据 @param valueMap 需要存储的数据集合* @param expire 过期时间,秒* @return java.util.List<T> 返回值*/public void batchSet(Map<String, String> valueMap, long expire) {valueOperations.multiSet(valueMap);if (expire != NOT_EXPIRE) {for (String key : valueMap.keySet()) {redisTemplate.expire(key, expire, TimeUnit.SECONDS);}}}/* 批量删除 @param keys 需要删除的KEY集合* @return void*/public void batchDelete(Collection<String> keys) {redisTemplate.delete(keys);}/* 批量从Redis中获取数据 @param keyList 需要获取的Key集合* @param clazz 需要转换的类型* @return java.util.List<T> 返回值*/public <T> Map<String, T> batchGet(List<String> keyList, Class<T> clazz) {List<String> objectList = valueOperations.multiGet(keyList);Map<String, T> map = new LinkedHashMap<>(objectList.size());for (int i = 0; i < keyList.size(); i++) {String value = Convert.toStr(objectList.get(i));if (!String.class.equals(clazz)) {map.put(keyList.get(i), fromJson(value, clazz));} else {map.put(keyList.get(i), (T)value);}}return map;}public <T> T get(String key, Class<T> clazz) {return get(key, clazz, NOT_EXPIRE);}/* 使用 父编码+当前编码获取+集团+语言 获取名称 @param code 父编码* @param dictCode 当前编码* @param language 语言 UserUtils.getLanguage()* @param groupId 集团ID*/public String get(String code, String dictCode, String language, String groupId) {if (StringUtils.isBlank(dictCode)) {return "";}String key = RedisKeys.getSysDictKey(code, dictCode, language, groupId);return get(key, NOT_EXPIRE);}public String get(String key, long expire) {String value = Convert.toStr(valueOperations.get(key));if (expire != NOT_EXPIRE) {redisTemplate.expire(key, expire, TimeUnit.SECONDS);}return value;}public String get(String key) {return get(key, NOT_EXPIRE);}public void delete(String key) {redisTemplate.delete(key);redisExtendService.redisDataChange(key, "", EnumType.CRUD_TYPE.DELETE.getValue());}/* Object转成JSON数据*/private String toJson(Object object) {if (object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double|| object instanceof Boolean || object instanceof String) {return String.valueOf(object);}return new Gson().toJson(object);}/* JSON数据,转成Object*/private <T> T fromJson(String json, Class<T> clazz) {return new Gson().fromJson(json, clazz);}/* 获取分布式锁,默认过期时间3分钟 @param key 锁的KEY* @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁*/public Boolean setMutexLock(String key) {return setMutexLockAndExpire(key, getMutexLockExpireMinutes(), TimeUnit.MINUTES);}/* 获取分布式锁,带Redis事务 @param key 锁的KEY* @param timeout 锁时效时间,默认单位:秒* @param unit 锁失效时间单位,为null则默认秒* @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁*/public Boolean setMutexLockAndExpire(String key, long timeout, TimeUnit unit) {return setMutexLockAndExpire(key, Constant.RESULT_1, timeout, unit);}/* 获取分布式锁,带Redis事务* 适用于同一业务,不同的请求用不同的锁,把value当成* @param key 锁的KEY* @param value 锁的值,一定要跟解锁的值一样,否则会导致无法解锁* @param timeout 锁时效时间,默认单位:秒* @param unit 锁失效时间单位,为null则默认秒* @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁*/public Boolean setMutexLockAndExpire(String key, String value, long timeout, TimeUnit unit) {value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value,"\\""),"\\"");Long result = executeLua(key, value, LOCK_LUA, timeout, unit, Long.class);return SUCCESS.equals(result);}/* 解锁 @param key 锁的Key* @return boolean*/public boolean unlock(String key) {return unlock(key, Constant.RESULT_1);}/* 解锁 @param key 锁的Key* @param value 锁的value,一定要跟加锁的value一致,否则会认为不是同一个锁,不会释放* @return boolean*/public boolean unlock(String key, String value) {value = StrUtil.appendIfMissing(StrUtil.prependIfMissing(value,"\\""),"\\"");Long result = executeLua(key, value, UNLOCK_LUA,null, null, Long.class);return SUCCESS.equals(result);}/* 获取等待锁,如果没有获取到锁就一直等待获取,直到超过waitTime的时间 @param key 锁的key* @param timeout 锁的超时时间* @param unit 锁的超时时间单位* @param waitTime 获取锁时的等待时间,一直等不超时则填-1,单位:毫秒* @return java.lang.Boolean true为获取到锁,可以下一步业务, false为没有获取到锁*/public Boolean setMutexWaitLock(String key, long timeout, TimeUnit unit, long waitTime) {long start = System.currentTimeMillis();while (true) {boolean result = setMutexLockAndExpire(key, timeout, unit);if (result) {return true;} else {long current = System.currentTimeMillis();// 超过等待时间还没获取到锁则返回falseif (waitTime > 0 && (current - start > waitTime)) {logger.warn("redis分布式锁获取失败,key[{}],等待时间[{}]", key, waitTime);return false;}// 等待100毫秒后重试ThreadUtil.sleep(100);}}}public long getMutexLockExpireMinutes() {return MUTEX_EXPIRE_MINUTES;}/* 获取自增序列号 @param key 序列号的KEY* @param seq 自增值,默认自增1* @return java.lang.Long 自增后的值*/public Long incr(String key, Long seq, long timeout, TimeUnit unit) {return executeLua(key, null, INCR_LUA, timeout, unit, Long.class);}/* 执行LUA脚本 @param key redisKey* @param value 值* @param lua lua脚本* @param timeout 超时时间* @param unit 超时单位* @param clazz 返回值类型* @return T 返回值*/public <T> T executeLua(String key, Object value, String lua, Long timeout, TimeUnit unit, Class<T> clazz){// 有时间单位则转成秒,否则默认秒if (unit != null) {timeout = unit.toSeconds(timeout);}List<String> args = new ArrayList<>(2);if(value != null){args.add(Convert.toStr(value));}if(timeout != null){args.add(Convert.toStr(timeout));}//spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本异常,此处拿到原redis的connection执行脚本T result = (T)redisTemplate.execute(new RedisCallback<T>() {@Overridepublic T doInRedis(RedisConnection connection) throws DataAccessException {Object nativeConnection = connection.getNativeConnection();// 集群模式和单点模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行// 集群if (nativeConnection instanceof JedisCluster) {return (T) ((JedisCluster) nativeConnection).eval(lua, Collections.singletonList(key), args);}// 单点else if (nativeConnection instanceof RedisProperties.Jedis) {return (T) ((Jedis) nativeConnection).eval(lua, Collections.singletonList(key), args);}return null;}});return result;}public void expire(String key, long timeout, TimeUnit unit) {try {redisTemplate.expire(key, timeout, unit);} catch (Exception e) {logger.error("设置缓存过期时间失败,key={},timeout={},unit={}", key, timeout, unit, e);}}/* 获取互斥线程等待时间 @return*/public long getMutexThreadWaitMilliseconds() {return MUTEX_WAIT_MILLISECONDS;}public Set<String> getKeys(String key) {return redisTemplate.keys(key);}/* 获取随机秒数 如:getRandomTime(30, 7)返回30天到第37天的随机秒数,即时效时间最小为30天,最大为37天 @param afterDays N天之后* @param rangeDay 日期范围* @return java.lang.Long 秒数*/public static Long getRandomTime(int afterDays, int rangeDay) {Calendar calendar = Calendar.getInstance();long curTime = calendar.getTimeInMillis();calendar.add(Calendar.DAY_OF_MONTH, afterDays);long minTime = calendar.getTimeInMillis();calendar.add(Calendar.DAY_OF_MONTH, rangeDay);long maxTime = calendar.getTimeInMillis();long randomTime = RandomUtil.randomLong(minTime, maxTime);return (randomTime - curTime) / 1000;}/* 获取30天内的随机秒数 @return long 返回1天后30天内的随机秒数*/public static long getRandomTime() {return getRandomTime(1, 30);}public void setnx(String key,String value){}}

说说使用集群的情况。

redis的主从复制。

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

redis哨兵模式。

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

选三台redis搭建集群

 

添加springBootmaven依赖

<!--redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
<!--web-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version>
</dependency>

集群yml文件配置

server:port: 3035
spring:redis:# redis哨兵配置sentinel:# 主节点名称master: mymasternodes:- 192.168.200.150:6379- 192.168.200.150:6380- 192.168.200.150:6381
#    # 集群的部署方式
#    cluster:
#      nodes:
#        - 192.168.200.150:6379
#        - 192.168.200.150:6380
#        - 192.168.200.150:6381
#      # #最大重定向次数(由于集群中数据存储在多个节点,所以在访问数据时需要通过转发进行数据定位)
#      max-redirects: 2
#    lettuce:
#      pool:
#        max-idle: 10   # 连接池中的最大空闲连接
#        max-wait: 500   # 连接池最大阻塞等待时间(使用负值表示没有限制)
#        max-active: 8   # 连接池最大连接数(使用负值表示没有限制)
#        min-idle: 0   # 连接池中的最小空闲连接# 服务应用名application:name: redis-cluster
logging:pattern:console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'level:root: infoio.lettuce.core: debugorg.springframework.data.redis: debug

配置读写分离

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.text.SimpleDateFormat;
import java.util.HashSet;@Configuration
public class RedisConfiguration {/  配置redis序列化json* @param redisConnectionFactory* @return*/@Bean@Primary    //若有相同类型的Bean时,优先使用此注解标注的Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 为了开发方便,一般直接使用<String, Object>RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 配置具体的序列化方式// JSON解析任意对象Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);// 设置日期格式om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));jackson2JsonRedisSerializer.setObjectMapper(om);// String的序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();//key采用String的序列化template.setKeySerializer(stringRedisSerializer);//hash的key也采用String的序列化template.setHashKeySerializer(stringRedisSerializer);//value的序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);//hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);//设置所有配置template.afterPropertiesSet();return template;}/* 配置读写分离* @param redisProperties* @return*/@Beanpublic RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {// 配置哨兵节点以及主节点RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes()));// 配置读写分离LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()// 读写分离,这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择// MASTER   仅读取主节点// MASTER_PREFERRED   优先读取主节点,如果主节点不可用,则读取从节点// REPLICA_PREFERRED   优先读取从节点,如果从节点不可用,则读取主节点// REPLICA   仅读取从节点// NEAREST   从最近节点读取// ANY   从任意一个从节点读取.readFrom(ReadFrom.REPLICA_PREFERRED).build();return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);}}

再使用工具类操作就行。


package com.vinjcent.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;@Component
public final class RedisUtils {private final RedisTemplate<String, Object> redisTemplate;/* 可按自己需求生成"起始时间戳"*/private static final long BEGIN_TIMESTAMP = 1640995200L;/* 用于时间戳左移32位*/public static final int MOVE_BITS = 32;@Autowiredpublic RedisUtils(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}//=============================common===================================/* 指定缓存失效时间* @param key   键* @param time  时间(秒)* @return  whether the key has expired*/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;}}/* 指定缓存失效时间(自定义时间单位)* @param key   键* @param time  时间(秒)* @return  whether the key has expired*/public boolean expire(String key, long time, TimeUnit unit){try {if(time > 0){redisTemplate.expire(key, time, unit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/* 根据key获取过期时间(默认获取的是秒单位)* @param key   键(不能为null)* @return      the remaining time, "0" means never expire*/public long getExpire(String key){Long time = redisTemplate.getExpire(key, TimeUnit.SECONDS);if (time != null) {return time;}return -1L;}/* 根据key获取过期时间(自定义时间单位)* @param key   键(不能为null)* @return      the remaining time, "0" means never expire*/public long getExpire(String key, TimeUnit unit){Long time = redisTemplate.getExpire(key, unit);if (time != null) {return time;}return -1L;}/* 判断key是否存在* @param key   键* @return      whether the key exist*/public boolean hasKey(String key) {Boolean flag = redisTemplate.hasKey(key);try {return Boolean.TRUE.equals(flag);} catch (Exception e) {e.printStackTrace();return false;}}/* 删除缓存* @param key   键,可以传递一个值或多个*/public void del(String... key) {if(key != null && key.length > 0){if (key.length == 1){redisTemplate.delete(key[0]);}else {redisTemplate.delete(Arrays.asList(key));}}}//=============================String===================================/* 普通缓存获取(泛型)* @param key   key键* @return      the value corresponding the key*/public Object get(String key){ return key == null ? null : redisTemplate.opsForValue().get(key);}/* 普通缓存获取(泛型)* @param key   key键* @return      the value corresponding the key* @param targetType    目标类型* @param <T>           目标类型参数* @return      the generic value corresponding the key*/public <T> T get(String key, Class<T> targetType){ return key == null ? null : JsonUtils.objParse(redisTemplate.opsForValue().get(key), targetType);}/* 普通缓存放入* @param key   键* @param value 值* @return      whether true or 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      whether true or 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 value 值* @param time  时间(秒) --- time要大于0,如果time小于0,将设置为无期限* @param timeUnit  时间单位* @return  whether true or false*/public boolean set(String key, Object value, long time, TimeUnit timeUnit){try {if(time > 0){redisTemplate.opsForValue().set(key, value, time, timeUnit);}else {set(key,value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/* 递增* @param key   键* @param delta 要增加几(大于0)* @return      the value after increment*/public long incr(String key, long delta){if(delta < 0){throw new RuntimeException("递增因子必须大于0");}Long increment = redisTemplate.opsForValue().increment(key, delta);return increment != null ? increment : 0L;}/* 递减* @param key   键* @param delta 要增加几(小于0)* @return      the value after decrement*/public long decr(String key, long delta){if(delta < 0){throw new RuntimeException("递减因子必须大于0");}Long increment = redisTemplate.opsForValue().increment(key, delta);return increment != null ? increment : 0L;    }//=============================Map===================================/* 根据hashKey获取hash列表有多少元素* @param key   键(hashKey)* @return      the size of map*/public long hsize(String key) {try {return redisTemplate.opsForHash().size(key);} catch (Exception e) {e.printStackTrace();return 0L;}}/* HashGet  根据"项 中的 键 获取列表"* @param key   键(hashKey)能为null* @param item  项不能为null* @return      the value of the corresponding key*/public Object hget(String key, String item){ return redisTemplate.opsForHash().get(key, item);}/* 获取HashKey对应的所有键值* @param key   键(hashKey)* @return      对应的多个键值*/public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key);}/* 获取HashKey对应的所有键值* @param key   键(hashKey)* @param keyType   键类型* @param valueType 值类型* @param <K>       键类型参数* @param <V>       值类型参数* @return      a map*/public <K, V> Map<K, V> hmget(String key, Class<K> keyType, Class<V> valueType) {return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);}/* HashSet  存入多个键值对* @param key   键(hashKey)* @param map   map 对应多个键值对*/public void hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key,map);} catch (Exception e) {e.printStackTrace();}}/* HashSet存入并设置时间* @param key   键(hashKey)* @param map   对应多个键值* @param time  时间(秒)* @return  whether true or 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表中放入数据,如果不存在将创建* @param key   键(hashKey)* @param item  项* @param value 值* @return  whether true or 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表中放入数据,如果不存在将创建,并设置有效时间* @param key   键(hashKey)* @param item  项* @param value 值* @param time  时间(秒)   注意: 如果已经在hash表有时间,这里将会替换所有的时间* @return  whether true or 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;}}/* 放入map集合数据,如果不存在将创建* @param key   键(hashKey)* @param value map集合* @param <K>   map集合键参数类型* @param <V>   map集合值参数类型* @return  whether true or false*/public <K, V> boolean hsetMap(String key, Map<K, V> value) {try {redisTemplate.opsForHash().putAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/* 获取key对应的所有map键值对* @param key   键(hashKey)* @return      the Map*/public Map<Object, Object> hgetMap(String key) {try {return redisTemplate.opsForHash().entries(key);} catch (Exception e) {e.printStackTrace();return null;}}/* 获取key对应的所有map键值对(泛型)* @param key   键(hashKey)* @param keyType   键类型* @param valueType 值类型* @param <K>       键类型参数* @param <V>       值类型参数* @return      the Map*/public <K, V> Map<K, V> hgetMap(String key, Class<K> keyType, Class<V> valueType) {try {return JsonUtils.mapParse(redisTemplate.opsForHash().entries(key), keyType, valueType);} catch (Exception e) {e.printStackTrace();return null;}}/* 删除hash表中的值* @param key   键(hashKey)   不能为null* @param item  项可以是多个    不能为null*/public void hdel(String key, Object... item){redisTemplate.opsForHash().delete(key,item);}/* 判断hash表是否有该项的值* @param key   键(hashKey)不能为null* @param item  项不能为null* @return  whether true or false*/public boolean hHasKey(String key, String item){return redisTemplate.opsForHash().hasKey(key, item);}/* hash递增,如果不存在,就会创建一个,并把新增后的值返回* @param key   键(hashKey)* @param item  项* @param by    要增加几(大于0)* @return  the value of the corresponding key after increment in one Map*/public double hincr(String key, String item, double by){return redisTemplate.opsForHash().increment(key, item, by);}/* hash递减* @param key   键(hashKey)* @param item  项* @param by    要减少几(小于0)* @return  the value of the corresponding key after decrement in one Map*/public double hdecr(String key, String item, double by){return redisTemplate.opsForHash().increment(key, item, -by);}//=============================Set===================================/* 根据key获取Set中的所有值* @param key   键* @return      all values in one Set*/public Set<Object> sGet(String key){try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/* 根据value从一个Set集合中查询一个值,是否存在* @param key   键* @param value 值* @return  whether true or false*/public boolean sHasKey(String key, Object value){try {Boolean flag = redisTemplate.opsForSet().isMember(key, value);return Boolean.TRUE.equals(flag);} catch (Exception e) {e.printStackTrace();return false;}}/* 将数据放入set缓存* @param key       键* @param values    值* @return  the number of adding successfully*/public long sSet(String key, Object... values){try {Long nums = redisTemplate.opsForSet().add(key, values);return nums != null ? nums : 0L;} catch (Exception e) {e.printStackTrace();return 0L;}}/* 将set数据放入缓存,并设置有效时间* @param key       键* @param time      时间(秒)* @param values    值,可以是多个* @return  the number of adding successfully*/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 != null ? count : 0L;} catch (Exception e) {e.printStackTrace();return 0L;}}/* 获取set缓存的长度* @param key   键* @return      the size of the Set*/public long sGetSetSize(String key){try {Long size = redisTemplate.opsForSet().size(key);return size != null ? size : 0L;} catch (Exception e) {e.printStackTrace();return 0L;}}/* 移除值为values的* @param key       键* @param values    值(可以是多个)* @return          the number of removal*/public long setRemove(String key, Object... values){try {Long nums = redisTemplate.opsForSet().remove(key, values);return nums != null ? nums : 0L;} catch (Exception e) {e.printStackTrace();return 0;}}//=============================List===================================/* 获取list列表数据* @param key       键* @return          all values of one List*/public List<Object> lget(String key) {try {return redisTemplate.opsForList().range(key, 0, -1);} catch (Exception e) {e.printStackTrace();return null;}}//* 获取list列表数据(泛型)* @param key       键* @param targetType    目标类型* @param <T>           目标类型参数* @return      all values of one List*/public <T> List<T> lget(String key, Class<T> targetType) {try {return JsonUtils.listParse(redisTemplate.opsForList().range(key, 0, -1), targetType);} catch (Exception e) {e.printStackTrace();return null;}}/* 获取list缓存的长度* @param key   键* @return      the length of the List*/public long lGetListSize(String key){try {Long size = redisTemplate.opsForList().size(key);return size != null ? size : 0L;} catch (Exception e) {e.printStackTrace();return 0L;}}/* 通过索引获取list中的值* @param key   键* @param index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推...    index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推* @return  the value of the specified index in one List*/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 index 索引 index >= 0 时, 0:表头, 1:第二个元素,以此类推...    index < 0 时, -1:表尾, -2:倒数第二个元素,以此类推* @return  the value of the specified index in one List* @param targetType    目标类型* @param <T>           目标类型参数* @return  the generic value of the specified index in one List*/public <T> T lgetIndex(String key, long index, Class<T> targetType) {try {return JsonUtils.objParse(redisTemplate.opsForList().index(key, index), targetType);} catch (Exception e) {e.printStackTrace();return null;}}/* 将list放入缓存* @param key   键* @param value 值* @return  whether true or false*/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  whether true or false*/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 values    值* @return  whether true or false*/public <T> boolean lSet(String key, List<T> values){try {Long nums = redisTemplate.opsForList().rightPushAll(key, values);return nums != null;} catch (Exception e) {e.printStackTrace();return false;}}/* 将list集合放入缓存,并设置有效时间* @param key       键* @param values    值* @param time      时间(秒)* @return  whether true or false*/public boolean lSet(String key, List<Object> values, long time){try {redisTemplate.opsForList().rightPushAll(key, values);if (time > 0){expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/* 根据索引修改list中的某条数据* @param key   键* @param value 值* @param index 索引* @return  whether true or false*/public boolean lUpdateIndex(String key, Object value, long index){try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/* 移除N个值为value* @param key       键* @param value     值* @param number    移除多少个* @return          返回移除的个数*/public long lRemove(String key, Object value, long number){try {Long count = redisTemplate.opsForList().remove(key, number, value);return count != null ? count : 0L;} catch (Exception e) {e.printStackTrace();return 0L;}}//=============================Lock===================================/* 解决缓存加锁问题* @param key   锁名称* @param value 锁值* @param timeout   超时时间* @param unit  时间单位* @param <T>   锁值的数据类型* @return  返回加锁成功状态*/public <T> boolean tryLock(String key, T value, long timeout, TimeUnit unit) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);return Boolean.TRUE.equals(flag);}/* 解决缓存解锁操作* @param key   锁名称* @return  返回解锁成功状态*/public boolean unLock(String key) {Boolean flag = redisTemplate.delete(key);return Boolean.TRUE.equals(flag);}/* 全局生成唯一ID策略* 设计: 符号位(1位) - 时间戳(32位) - 序列号(31位)* @param keyPrefix     key的前缀* @return  返回唯一ID*/public long globalUniqueKey(String keyPrefix) {// 1. 生成时间戳LocalDateTime now = LocalDateTime.now();// 东八区时间long nowSecond = now.toEpochSecond(ZoneOffset.UTC);// 相减获取时间戳long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2. 生成序列号(使用日期作为redis自增长超2^64限制,灵活使用年、月、日来存储)// 获取当天日期String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 自增长Long increment = redisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);long count = increment != null ? increment : 0L;// 3. 拼接并返回(使用二进制或运算)return timestamp << MOVE_BITS | count;}}