1.背景
由于业务需求最近准备将系统中原有的memcached改造为Redis,实现多数据类型的分布式缓存。
2.分布式缓存:
3.集成改造
1) Pom依赖 vcsp-genericProfessionServer\pom.xml
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.2</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.7.10.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
2) application-*.properties
#redis配置********************* spring.redis.hostName=192.168.1.123 spring.redis.port=6739 # REDIS (RedisProperties) # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器连接密码(默认为空) spring.redis.password=2123 # 连接池最大连接数(使用负值表示没有限制) # 连接超时时间(毫秒) spring.redis.timeout=5000 spring.redis.pool.max-active= 3000 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=5000 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=1000 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=200 spring.session.store-type=none #redis配置*********************
3) RedisConfiguration
@Configuration public class RedisConfiguration{ @Value("${spring.redis.hostName}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.timeout}") private int timeout; @Value("${spring.redis.pool.max-idle}") private int maxIdle; @Value("${spring.redis.pool.max-wait}") private long maxWaitMillis; @Value("${spring.redis.pool.max-active}") private int maxActive; @Value("${spring.redis.password}") private String password; 。。。。。。。。
4) MyRedisPool
@Component public class MyRedisPool { private static final Logger LOGGER = Logger.getLogger(MyRedisPool.class); private static String host; private static int port; private static int timeout; private static int maxIdle; private static long maxWaitMillis; private static int maxActive; private static String password; @Autowired RedisConfiguration redisConfiguration; //@PostConstruct public void iniProperties(){ sm4ScretKey= sm4ScretKeyUtils.getSm4ScretKey(redisConfiguration.getFileServerIntranetHost()); host=redisConfiguration.getHost(); port=redisConfiguration.getPort(); timeout=redisConfiguration.getTimeout(); maxIdle=redisConfiguration.getMaxIdle(); maxWaitMillis=redisConfiguration.getMaxWaitMillis(); maxActive=redisConfiguration.getMaxActive(); password=redisConfiguration.getPassword(); if(jedisPool==null){ initJedisPool(); } LOGGER.warn("初始化 jedisPool................"); } private static JedisPool jedisPool = null; public static void initJedisPool(){ try{ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(maxIdle);//最大空闲连接数 jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);//最大等待时间 jedisPoolConfig.setMaxTotal(maxActive);//最大活动线程数 LOGGER.error("初始化redis线程池工厂....................host:"+host+";port:"+port+";password:"+password+";maxActive:"+maxActive); jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,password); }catch (Exception e){ e.printStackTrace(); } } /** * 获取Jedis实例 * * @return */ public synchronized static Jedis getJedis() { try { if(jedisPool==null){ initJedisPool(); } return jedisPool.getResource(); } catch (Exception e) { jedisPool.close(); e.printStackTrace(); } return null; } /** * 释放jedis资源 * * @param jedis */ public static void returnResource(final Jedis jedis) { if (jedis != null) { jedisPool.returnResourceObject(jedis); } } /** * 销毁jedis连接 * * @param jedis */ public static void returnBrokenResource(final Jedis jedis) { if (jedis != null) { jedisPool.returnBrokenResource(jedis); } } }
5) RedisClient
/** * @author */ public class RedisClient { private volatile static RedisClient instance; private static final Logger LOGGER = Logger.getLogger(RedisClient.class); private RedisClient() { } public static RedisClient getInstance() { if (instance == null) { synchronized (RedisClient.class) { if (instance == null) { instance = new RedisClient(); } } } return instance; } /** * 自定义设置key * * @param key 键 * @param value 值 * @param exits NX/XX 值只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set * @param expx EX/PX 值只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒 * @param interval 过期时间,单位是expx所代表的单位。 * @return SET 在设置操作成功完成时,才返回 OK 。 * 如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply) */ public String set(String key, Object value, String exits, String expx, long interval) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { result = jedis.set(key, String.valueOf(value), exits, expx, interval); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 设置key * 需要传入key是否已经存在 * * @param key 键 * @param value 值 * @param expx EX/PX 值只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒 * @param interval 过期时间,单位是expx所代表的单位。 * @return SET 在设置操作成功完成时,才返回 OK 。 * 如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply) */ public String setXX(String key, Object value, String expx, long interval) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { result = jedis.set(key, String.valueOf(value), "XX", expx, interval); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 设置key * 需要传入key是否不存存在 * * @param key 键 * @param value 值 * @param expx EX/PX 值只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒 * @param interval 过期时间,单位是expx所代表的单位。 * @return SET 在设置操作成功完成时,才返回 OK 。 * 如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply) */ public String setNX(String key, Object value, String expx, long interval) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { result = jedis.set(key, String.valueOf(value), "NX", expx, interval); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 设将字符串值 value 关联到 key 。 * 如果 key 已经持有其他值, SET 就覆写旧值,无视类型。 * * @param key 键 * @param value 值 * @return SET 在设置操作成功完成时,才返回 OK 。 */ public String set(String key, Object value) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { result = jedis.set(key, String.valueOf(value)); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 设将字符串值 value 关联到 key 。 * 如果 key 已经持有其他值, SET 就覆写旧值,无视类型。 * * @param key 键 * @param value 值 * @return SET 在设置操作成功完成时,才返回 OK 。 */ public String set(String key, Object value, int interval) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { result = jedis.set(key, String.valueOf(value)); expire(key, interval); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 返回 key 所关联的字符串值 * 如果 key 不存在那么返回特殊值 nil 。 * * @param key * @return 当 key 不存在时,返回 nil ,否则,返回 key 的值。 * 如果 key 不是字符串类型,那么返回一个错误。 */ public Object get(String key) { Jedis jedis = MyRedisPool.getJedis(); Object o = null; try { o = jedis.get(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return o; } public List<Map<String,String>> getListBykey(String key) { Jedis jedis = MyRedisPool.getJedis(); List<Map<String,String>> list=new ArrayList<>(); try { Set<String> set = jedis.keys(key+"*"); for (String key1 : set){ String value1 =String.valueOf(jedis.get(key1)); Map<String,String> map=new HashMap<>(); map.put(key1,value1); list.add(map); } MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return list; } /** * 检查给定 key 是否存在。 * * @param key 键 * @return 若 key 存在,返回 true ,否则返回 false */ public boolean exists(String key) { Jedis jedis = MyRedisPool.getJedis(); boolean flag = false; try { flag = jedis.exists(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return flag; } /** * 判断是否存在,存在则返回对象,不存在返回null * * @param key * @return 若 key 存在,返回 1 ,否则返回 0 。 */ public Object retExists(String key) { Jedis jedis = MyRedisPool.getJedis(); Object o = null; try { if (jedis.exists(key)) { o = jedis.get(key); } MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return o; } /** * 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 * 可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。 * * @param key * @return 设置成功返回 1 。 * 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。 */ public Long expire(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.expire(key, -1); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 * 可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。 * * @param key * @param interval * @return 设置成功返回 1 。 * 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。 */ public Long expire(String key, int interval) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.expire(key, interval); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。 * * @param key * @return 当生存时间移除成功时,返回 1 . * 如果 key 不存在或 key 没有设置生存时间,返回 0 。 */ public Long persist(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.persist(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 删除给定的一个或多个 key 。 * 不存在的 key 会被忽略。 * * @param key 键 * @return 被删除 key 的数量。 */ public Long delete(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.del(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。 * 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。 * * @param key 键 * @param score 分数 * @param member 值 * @return 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。 */ public Long zadd(String key, double score, String member) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.zadd(key, score, member); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。 * 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。 * * @param key 键 * @param member 值 * @return 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。 */ public Long zadd(String key, String member) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.zadd(key, System.currentTimeMillis(), member); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 获取该key总数(全部) * * @param key * @return score 值在 min 和 max 之间的成员的数量 */ public Long zcount(String key) { Jedis jedis = MyRedisPool.getJedis(); Long count = 0L; try { count = jedis.zcount(key, "-inf", "+inf"); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return count; } /** * 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。 * * @param key 键 * @param min 最小值 * @param max 最大值 * @return score 值在 min 和 max 之间的成员的数量 */ public Long zcount(String key, double min, double max) { Jedis jedis = MyRedisPool.getJedis(); Long count = 0L; try { count = jedis.zcount(key, min, max); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return count; } /** * 返回有序集 key 中,指定区间内的成员。 * 其中成员的位置按 score 值递增(从小到大)来排序。 * 超出范围的下标并不会引起错误。 * * @param key 键 * @param start 开始分数 * @param end 结束分数 * @return 指定区间内,带有 score 值(可选)的有序集成员的列表。 */ public Set<String> zrange(String key, long start, long end) { Jedis jedis = MyRedisPool.getJedis(); Set<String> list = null; try { list = jedis.zrange(key, start, end); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return list; } /** * 设置 key 指定的哈希集中指定字段的值。 * 如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。 * 如果字段在哈希集中存在,它将被重写。 * * @param key * @param field * @param value * @return 如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。 * 如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。 */ public Long hset(String key, String field, String value) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.hset(key, field, value); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。 * 如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联 * * @param key * @param maps * @return 如果命令执行成功,返回 OK 。 * 当 key 不是哈希表(hash)类型时,返回一个错误。 */ public String hmset(String key, Map<String, String> maps) { Jedis jedis = MyRedisPool.getJedis(); String result = ""; try { if (maps == null) { throw new NullPointerException("maps为空!"); } if (!maps.isEmpty()) { result = jedis.hmset(key, maps); } MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 返回 key 指定的哈希集中所有的字段和值。 * * @param key * @return 以列表形式返回哈希表的域和域的值。 * 若 key 不存在,返回空列表。 */ public Map<String, String> hgetAll(String key) { Jedis jedis = MyRedisPool.getJedis(); Map<String, String> map = null; try { map = jedis.hgetAll(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return map; } /** * 对存储在指定key的数值执行原子的加1操作。 * 如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0。 * * @param key * @return 执行 INCR 命令之后 key 的值 */ public Long incr(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.incr(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。 * 如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。 * * @param key * @return 执行 DECR 命令之后 key 的值。 */ public Long decr(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.decr(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 * 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。 * 当 key 不是集合类型时,返回一个错误。 * * @param key * @param members * @return 被添加到集合中的新元素的数量,不包括被忽略的元素 */ public Long sadd(String key, String members) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.sadd(key, members); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 返回集合 key 的基数(集合中元素的数量)。 * * @param key * @return 集合的基数。当 key 不存在时,返回 0 。 */ public Long scard(String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.scard(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 返回集合 key 中的所有成员。不存在的 key 被视为空集合。 * * @param key * @return 集合中的所有成员。 */ public Set<String> smembers(String key) { Jedis jedis = MyRedisPool.getJedis(); Set<String> set = null; try { set = jedis.smembers(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return set; } /** * 判断 member 元素是否集合 key 的成员。 * * @param key * @return 如果 member 元素是集合的成员,返回 1 。 * 如果 member 元素不是集合的成员,或 key 不存在,返回 0 。 */ public Boolean sismember(String key, String members) { Jedis jedis = MyRedisPool.getJedis(); Boolean result = false; try { result = jedis.sismember(key, members); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。 * * @param key * @return 被成功移除的元素的数量,不包括被忽略的元素。 */ public Long srem(String key, String members) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.srem(key, members); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /** * 返回一个集合的全部成员,该集合是所有给定集合之间的差集。不存在的 key 被视为空集。 * 但它将结果保存到 destination 集合,而不是简单地返回结果集。如果 destination 集合已经存在,则将其覆盖。 * * @param dstkey * @param key * @return 结果集中的元素数量。 */ public Long sdiffstore(String dstkey, String key) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.sdiffstore(dstkey, key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } //从队列中左侧进入一个元素 public Long lpush(String key, String value) { Jedis jedis = MyRedisPool.getJedis(); Long result = 0L; try { result = jedis.lpush(key,value); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } //从队列中右侧出一个元素 public String rpop(String key) { Jedis jedis = MyRedisPool.getJedis(); String result = null; try { result = jedis.rpop(key); MyRedisPool.returnResource(jedis); } catch (Exception e) { MyRedisPool.returnBrokenResource(jedis); e.printStackTrace(); } return result; } /*******************分布式锁******************/ public boolean lock(String key, int exprie) { try { exprie = exprie <= 0 ? 60 : exprie; String value = JsonUtil2.obj2String(createMeta(exprie)); String lockKey = this.getLockKeyPrev() + key; String result=this.setNX(lockKey, value,"EX",exprie); if (result!=null && result.equals("OK")) { LOGGER.info("Get redis lock success, key =" + lockKey); return true; } Object obj = this.get(lockKey); if (obj==null) { this.delete(lockKey); LOGGER.info("Redis unlock success ,key = " + lockKey); Thread.sleep(1000); value = JsonUtil2.obj2String(createMeta(exprie)); String result1=this.setNX(lockKey, value,"EX",exprie); if (result1!=null && result1.equals("OK")) { this.expire(lockKey, exprie); LOGGER.info("Get redis lock success, key =" + lockKey); return true; } else { LOGGER.warn("Get redis lock fail, key =" + lockKey); return false; } } value = (String)obj; LockModel model = JsonUtil2.getObjectMapper().readValue(value, LockModel.class); if (model.isLose()) {// 已经超时 this.delete(lockKey); value = JsonUtil2.obj2String(createMeta(exprie)); String result2=this.setNX(lockKey, value,"EX",exprie); if (result2!=null && result2.equals("OK")) { this.expire(lockKey, exprie); LOGGER.info("Get redis lock success, key =" + lockKey); return true; } else { LOGGER.warn("Get redis lock fail, key =" + lockKey); return false; } } LOGGER.warn("Get redis lock fail, key =" + lockKey); return false; } catch (Exception ex) { ex.printStackTrace(); LOGGER.error(ex.getMessage()); return true; } } public void unlock(String key) { String lockKey = this.getLockKeyPrev() + key; try { delete(lockKey); } catch (Exception ex) { LOGGER.error(ex.getMessage()); } LOGGER.info("Redis unlock success ,key = " + lockKey); } private LockModel createMeta(int exprie) { LockModel meta = new LockModel(); meta.setExpireTime(exprie); meta.setLockTime(System.currentTimeMillis()); return meta; } public String getLockKeyPrev() { return "lock:"; } /*******************分布式锁******************/
4.Redis分布式锁
1.基于redis的setnx 命令实现分布式锁
2.lua脚本结合redis命令可以实现原子性(Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断)
代码使用样例:
try { boolean isLock= RedisClient.getInstance().lock(TASK_NAME,30); if (!isLock){ return; } handle(); } catch (Exception e) { log.error("error: e=" + e.getMessage()); } finally { RedisClient.getInstance().unlock(TASK_NAME); }
来源:https://www.cnblogs.com/brant/p/12496331.html