依赖
<!-- Redis客户端 start -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
controller
/**
* 车主端 确认支付
*
* @param requestDto
* @param rpcPacket
* @return
*/
@UrlMapping(url = "carOwner/confirmPayment")
public RpcPacket changeConfirmPaymentForCarOwner(RpcCarOwnerConfirmPaymentReqDto requestDto, RpcPacket rpcPacket) {
logger.info("车主端 确认支付,requestDto {},rpcPacket {}", JsonUtil.toJson(requestDto), JsonUtil.toJson(rpcPacket));
RpcPacket packet = new RpcPacket();
String lockKey = OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_LOKENAME + requestDto.getOrderId();
String lockName = "";
try {
//先分布锁一下 订单
lockName = redisCache.getLockLua(lockKey, OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_EXPIRETIME, OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_TIMEOUT);
if (!StringUtil.isEmpty(lockName)) {
Long oldCouponId = orderFeeService.updateBindCounpByOrderId(requestDto);
// if (oldCouponId!=null) {
// rpcServie.updateUnBindCouponByCouponId(oldCouponId);
// }
RpcCarOwnComfirmPayRetDto res = orderFeeService.changeConfirmPaymentForCarOwner(requestDto, rpcPacket);
packet.setData(res);
if (res != null) {
switch (res.getStatus()) {
case 1:
packet.setAnwserCode(new AnwserCode(1, "车主端 支付成功"));
break;
case 2:
packet.setAnwserCode(new AnwserCode(1, "车主端 请调起三方支付"));
break;
case 3:
packet.setAnwserCode(new AnwserCode(-2, "车主端 支付失败"));
break;
}
}
} else {
throw new ArgsException("支付处理中,请勿重复提交");
}
} catch (ArgsException e) {
logger.error("车主端 确认支付异常 {}", e.toString());
packet.setAnwserCode(e.getAnwserCode());
} catch (Exception e) {
logger.error("车主端 确认支付异常 {}", e.toString());
packet.setAnwserCode(OrderServiceAnwserCode.BUSS_ERROR_CALCULPAYMENT_CAROWNER);
} finally {
redisCache.releaseLock(lockKey, lockName);
}
logger.info("车主端 确认支付,packet {}", JsonUtil.toJson(packet));
return packet;
}
RedisCache
package com.wanshun.common.database.redis;
import com.wanshun.common.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.params.geo.GeoRadiusParam;
import java.io.IOException;
import java.util.*;
public class RedisCache {
private final static Logger logger = LoggerFactory.getLogger(RedisCache.class);
private JedisCluster jedisCluster;
private String prefixKey;
@Autowired
private RedisConfig redisConfig;
public RedisCache(String prefixKey, RedisConfig redisConfig) {
this.prefixKey = prefixKey + "_";
this.redisConfig = redisConfig;
this.init();
}
public void init() {
if (null == jedisCluster) {
if (StringUtil.isEmpty(prefixKey) || prefixKey.length() <= 1) {
logger.error("初始化redisCache失败,该模块的redis的key为空");
System.exit(0);
}
synchronized (RedisCache.class) {
if (null == jedisCluster) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setTestOnReturn(true);
Set<HostAndPort> shardInfoList = new HashSet<HostAndPort>();
try {
poolConfig.setMaxIdle(redisConfig.getMaxIdle());
poolConfig.setMinIdle(redisConfig.getMinIdle());
poolConfig.setTestOnReturn(redisConfig.getTestOnReturn());
poolConfig.setTestOnBorrow(redisConfig.getTestOnBorrow());
String[] shardedList = redisConfig.getShared().split(";");
for (String server : shardedList) {
String[] values = server.split(":");
HostAndPort node = new HostAndPort(values[0], Integer.parseInt(values[1]));
shardInfoList.add(node);
}
Integer timeout = redisConfig.getTimeout() == null ? 2000 : redisConfig.getTimeout();
String password = redisConfig.getPassword();
Integer maxactive = redisConfig.getMaxActive();
if (StringUtil.isNullOrBlank(password)) {
jedisCluster = new JedisCluster(shardInfoList, timeout, timeout, maxactive, poolConfig);
} else {
jedisCluster = new JedisCluster(shardInfoList, timeout, timeout, maxactive, password, poolConfig);
}
} catch (Exception e) {
logger.error("getShardedJedisPool", e);
jedisCluster = null;
throw e;
}
}
}
}
}
public String getSet(String key, String value) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.getSet(realKey, value);
} catch (Exception e) {
throw e;
}
}
public Boolean exists(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.exists(realKey);
} catch (Exception e) {
throw e;
}
}
public Boolean deleteKey(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.del(realKey) > 0;
} catch (Exception e) {
throw e;
}
}
public void deleteKeyNoPrefixKey(String key) {
try {
jedisCluster.del(key);
} catch (Exception e) {
throw e;
}
}
public Boolean deleteKeyNoPrefix(String key) {
try {
return jedisCluster.del(key) > 0;
} catch (Exception e) {
throw e;
}
}
public boolean set(String key, String value, Integer seconds) {
String realKey = this.prefixKey + key;
try {
jedisCluster.setex(realKey, seconds, value);
return true;
} catch (Exception e) {
throw e;
}
}
public boolean setnoexpire(String key, String value) {
String realKey = this.prefixKey + key;
try {
jedisCluster.set(realKey, value);
return true;
} catch (Exception e) {
throw e;
}
}
public String get(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.get(realKey);
} catch (Exception e) {
return null;
}
}
public Long ttl(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.ttl(realKey);
} catch (Exception e) {
return null;
}
}
/**
* 删除缓存
*
* @param key
* @return
*/
public boolean remove(String key) {
String realKey = this.prefixKey + key;
return deleteKey(realKey);
}
/**
* 设置可以的过期时间
*
* @param key
* @param seconds
*/
public boolean cacheExpire(String key, Integer seconds) {
String realKey = this.prefixKey + key;
try {
jedisCluster.expire(realKey, seconds);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* 获取序列化对象
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
String realKey = this.prefixKey + new String(key);
try {
return jedisCluster.get(realKey).getBytes();
} catch (Exception e) {
throw e;
}
}
/**
* 自增一
*
* @param key
* @return 自增后的值
*/
public Long incr(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.incr(realKey);
} catch (Exception e) {
throw e;
}
}
public Long incrBy(String key, long integer) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.incrBy(realKey, integer);
} catch (Exception e) {
throw e;
}
}
/**
* 自减一
*
* @param key
* @return 自减后的值
*/
public Long decr(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.decr(realKey);
} catch (Exception e) {
throw e;
}
}
/**
* 自减特定步长
*
* @param key
* @param integer
* @return 自减后的值
*/
public Long decrBy(String key, long integer) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.decrBy(realKey, integer);
} catch (Exception e) {
throw e;
}
}
/**
* 新建LIST
*
* @param key
* @param index
* @param value
* @return
*/
public boolean setList(String key, Long index, String value) {
String realKey = this.prefixKey + key;
if (value == null) {
return false;
}
try {
jedisCluster.lset(realKey, index, value);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* 设置hash 字段-值
*
* @param key 键
* @param field 字段
* @param value 值
* @return
*/
public boolean setHash(String key, String field, String value) {
String realKey = this.prefixKey + key;
if (value == null) {
return false;
}
try {
jedisCluster.hset(realKey, field, value);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* 获得HashSet对象
*
* @param key 键
* @param field 字段
* @return Json String or String value
*/
public String getHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hget(realKey, field);
} catch (Exception e) {
throw e;
}
}
/**
* 存储 map
*
* @param key
* @param map
* @return
*/
public boolean hmset(String key, Map<String, String> map) {
String realKey = this.prefixKey + key;
try {
jedisCluster.hmset(realKey, map);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* @param key
* @param fields
* @return
*/
public List<String> hmget(String key, String... fields) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hmget(realKey, fields);
} catch (Exception e) {
throw e;
}
}
/**
* 删除Hash 字段对象
*
* @param key 键
* @param field 字段
* @return 删除的记录数
*/
public long delHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hdel(realKey, field);
} catch (Exception e) {
throw e;
}
}
/**
* 删除Hash 多个字段对象
*
* @param key 键值
* @param field 字段
* @return 删除的记录数
*/
public long delHash(String key, String... field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hdel(realKey, field);
} catch (Exception e) {
throw e;
}
}
/**
* 判断key下的field是否存在
*
* @param key 键
* @param field 字段
* @return
*/
public boolean existsHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hexists(realKey, field);
} catch (Exception e) {
throw e;
}
}
/**
* 返回 key 指定的哈希集中所有字段的value值
*
* @param key
* @return
*/
public List<String> hvals(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hvals(realKey);
} catch (Exception e) {
throw e;
}
}
public Set<String> hkeys(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hkeys(realKey);
} catch (Exception e) {
throw e;
}
}
/**
* 返回 key指定的字段值总数
*
* @param key
* @return
*/
public long lenHash(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hlen(realKey);
} catch (Exception e) {
throw e;
}
}
/**
* list 添加
*
* @param key
* @param strings
*/
public Long lpush(String key, String... strings) {
String realKey = this.prefixKey + key;
return jedisCluster.lpush(realKey, strings);
}
/**
* list 获取长度
*/
public long llen(String key) {
String realKey = this.prefixKey + key;
return jedisCluster.llen(realKey);
}
//redis 监听消息通道===========================
/**
* 推入消息到redis消息通道
*
* @param String channel
* @param String message
*/
public Long publish(String channel, String message) {
return jedisCluster.lpush(channel, message);
}
/**
* 推入消息到redis消息通道
*
* @param byte[] channel
* @param byte[] message
*/
public Long publish(byte[] channel, byte[] message) {
return jedisCluster.lpush(channel, message);
}
/**
* 获取队列数据
*
* @param byte[] key 键名
* @return
*/
public byte[] rpop(byte[] key) {
String realKey = new String(key);
realKey = this.prefixKey + key;
byte[] bytes = null;
try {
bytes = jedisCluster.rpop(realKey.getBytes());
} catch (Exception e) {
//释放redis对象
e.printStackTrace();
}
return bytes;
}
/**
* 获取队列数据
*
* @param byte[] key 键名
* @return
*/
public String rpop(String key) {
String realKey = this.prefixKey + key;
String bytes = null;
try {
bytes = jedisCluster.rpop(realKey);
} catch (Exception e) {
//释放redis对象
e.printStackTrace();
}
return bytes;
}
/**
* 监听消息通道
*
* @param jedisPubSub - 监听任务
* @param channels - 要监听的消息通道
* @throws IOException
*/
public void subscribe(BinaryJedisPubSub jedisPubSub, byte[]... channels) throws IOException {
try {
jedisCluster.subscribe(jedisPubSub, channels);
} catch (Exception e) {
throw e;
} finally {
jedisCluster.close();
}
}
/**
* 监听消息通道
*
* @param jedisPubSub - 监听任务
* @param channels - 要监听的消息通道
* @throws IOException
*/
public void subscribe(JedisPubSub jedisPubSub, String... channels) throws IOException {
try {
jedisCluster.subscribe(jedisPubSub, channels);
} catch (Exception e) {
throw e;
} finally {
jedisCluster.close();
}
}
//redis 监听消息通道===========================
/**
* 删除指定元素
*
* @param key
* @param count
* @param value
*/
public boolean lrem(String key, int count, String value) {
String realKey = this.prefixKey + key;
try {
jedisCluster.lrem(realKey, count, value);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* list 指定元素 0,-1 所有
*
* @param key
* @param start
* @param end
* @return
*/
public List<String> lrange(String key, int start, int end) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.lrange(realKey, start, end);
} catch (Exception e) {
throw e;
}
}
/**
* add string to set
*
* @param key
* @param members
*/
public boolean sadd(String key, String... members) {
String realKey = this.prefixKey + key;
try {
jedisCluster.sadd(realKey, members);
return true;
} catch (Exception e) {
throw e;
}
}
/**
* exists value
*
* @param key
* @param member
* @return
*/
public boolean sismember(String key, String member) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.sismember(realKey, member);
} catch (Exception e) {
throw e;
}
}
/**
* select all
*
* @param key
* @return
*/
public Set<String> smembers(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.smembers(realKey);
} catch (Exception e) {
throw e;
}
}
/**
* delete value in set
*
* @param key
* @param value
*/
public boolean srem(String key, String value) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.srem(realKey, value) > 0;
} catch (Exception e) {
throw e;
}
}
public String ltrim(String key, int start, int end) {
try {
return jedisCluster.ltrim(this.prefixKey + key, start, end);
} catch (Exception e) {
throw e;
}
}
public Set<String> zrevrange(String key, long start, long end) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrange(realKey, start, end);
} catch (Exception e) {
throw e;
}
}
public boolean setNx(String key, String obj, int seconds) {
Long result;
try {
result = jedisCluster.setnx(key, obj);
if (result == 1) {
jedisCluster.expire(key, seconds);
return true;
} else {
return false;
}
} catch (Exception e) {
}
return false;
}
public Long zadd(String key, Long score, String value) {
String realKey = this.prefixKey + key;
Long result = 0L;
try {
result = jedisCluster.zadd(realKey, score.doubleValue(), value);
} catch (Exception e) {
}
return result;
}
/**
* 分页获取ZSET数据
*
* @param key
* @param start
* @param end
* @return
*/
public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {
logger.info("RedisCache zrange key={},start={},end={}", key, start, end);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrangeWithScores(realKey, start, end);
} catch (Exception e) {
logger.error("RedisCache zrange key={},start={},end={},e={}", realKey, start, end, e);
}
return Collections.emptySet();
}
/**
* 获取对应元素的排名
*
* @param key
* @param member
* @return
*/
public Long zrevrank(String key, String member) {
logger.info("RedisCache zrevrank key={},member={}", key, member);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrank(realKey, member);
} catch (Exception e) {
logger.error("RedisCache zrevrank key={},member={},e ={}", key, member, e);
}
return -1L;
}
/**
* 获取对应元素的分数
*
* @param key
* @param member
* @return
*/
public Double zscore(String key, String member) {
logger.info("RedisCache zscore key={},member={}", key, member);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zscore(realKey, member);
} catch (Exception e) {
logger.error("RedisCache zscore key={},member={},e ={}", key, member, e);
}
return 0.00;
}
public Long zrem(String key, String value) {
String realKey = this.prefixKey + key;
Long result = 0L;
try {
result = jedisCluster.zrem(realKey, value);
} catch (Exception e) {
}
return result;
}
/**
* 加锁
*
* @param key 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String lockWithTimeout(String key, long acquireTimeout, long timeout) {
String retIdentifier = null;
try {
// 随机生成一个value
String identifier = UUID.randomUUID().toString();
// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);
// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (jedisCluster.setnx(key, identifier) == 1) {
jedisCluster.expire(key, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (jedisCluster.ttl(key) == -1) {
jedisCluster.expire(key, lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
throw e;
}
return retIdentifier;
}
/**
* 加锁
* 运用lua脚本
*
* @param key 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String getLockLua(String key, long acquireTimeout, long timeout) {
return getLockLua(key, timeout);
}
public String getLockLua(String key, long timeout) {
List<String> keys = new ArrayList<>();
int lockExpire = (int) (timeout / 1000);
keys.add(key);
List<String> args = new ArrayList<>();
args.add(lockExpire + "");
args.add(UUID.randomUUID().toString());
return "OK".equals(jedisCluster.eval("return redis.call('set', KEYS[1],ARGV[2],'nx', 'ex', ARGV[1]) ", keys, args)) ? args.get(1) : "";
}
public String getLock(String key, long timeout) {
String retIdentifier = null;
try {
// 随机生成一个value
String identifier = UUID.randomUUID().toString();
// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);
if (jedisCluster.setnx(key, identifier) == 1) {
jedisCluster.expire(key, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (jedisCluster.ttl(key) == -1) {
jedisCluster.expire(key, lockExpire);
}
} catch (JedisException e) {
throw e;
}
return retIdentifier;
}
/**
* 释放锁
*
* @param key 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String key, String identifier) {
boolean retFlag = false;
try {
// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
if (identifier.equals(jedisCluster.get(key))) {
jedisCluster.del(key);
retFlag = true;
}
} catch (JedisException e) {
throw e;
}
return retFlag;
}
public Set<String> keys(String pattern) {
HashSet<String> keys = new HashSet<String>();
Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
for (String node : clusterNodes.keySet()) {
JedisPool jp = clusterNodes.get(node);
Jedis connection = jp.getResource();
try {
keys.addAll(connection.keys(pattern));
} catch (Exception e) {
} finally {
connection.close();
}
}
return keys;
}
//订单开始坐标存入redis
public Long addReo(double lon, double lat, String orderId) {
try {
return jedisCluster.geoadd("orderStation", lon, lat, orderId);
} catch (Exception e) {
logger.error("reids 缓存坐标异常:", e);
}
return null;
}
//查询坐标系附近的订单
public List<GeoRadiusResponse> queryReo(Double lon, Double lat, double radius) {
try {
return jedisCluster.georadius("orderStation", lon, lat, radius, GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
} catch (Exception e) {
logger.error("reids 查询坐标系附近的订单异常:", e);
}
return null;
}
//redis删除订单坐标
public Long delReo(String orderId) {
try {
return jedisCluster.zrem("orderStation", orderId);
} catch (Exception e) {
logger.error("reids 删除订单坐标异常:", e);
}
return null;
}
public JedisCluster getJedisCluster() {
return jedisCluster;
}
}
常量类
/**
* 确认支付加状态加锁
**/
public static final String LOCK_CAROWNER_COMFIRM_PAY_LOKENAME = orderService + "_LOCK_CAROWNE_COMFIRM_PAY:";
public static final long LOCK_CAROWNER_COMFIRM_PAY_EXPIRETIME = 10 * 1000L;
public static final long LOCK_CAROWNER_COMFIRM_PAY_TIMEOUT = 30 * 1000L;
redis的配置类
package com.wanshun.common.database.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("redisConfig")
public class RedisConfig {
private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
private Integer maxIdle;
private Integer minIdle;
private Boolean testOnReturn;
private Boolean testOnBorrow;
private String shared;
private Integer timeout;
private String password;
private Integer maxActive;
public Integer getMaxIdle() {
return maxIdle;
}
@Value("${redis.maxIdle:}")
public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}
public Integer getMinIdle() {
return minIdle;
}
@Value("${redis.minIdle:}")
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Boolean getTestOnReturn() {
return testOnReturn;
}
@Value("${redis.testOnReturn:}")
public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
@Value("${redis.testOnBorrow:}")
public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public String getShared() {
return shared;
}
@Value("${redis.shared:}")
public void setShared(String shared) {
this.shared = shared;
}
public Integer getTimeout() {
return timeout;
}
@Value("${redis.timeout:}")
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
public String getPassword() {
return password;
}
@Value("${redis.password:}")
public void setPassword(String password) {
this.password = password;
}
public Integer getMaxActive() {
return maxActive;
}
@Value("${redis.maxActive:}")
public void setMaxActive(Integer maxactive) {
this.maxActive = maxactive;
}
@Override
public String toString() {
return "RedisConfig [maxIdle=" + maxIdle + ", minIdle=" + minIdle + ", testOnReturn=" + testOnReturn
+ ", testOnBorrow=" + testOnBorrow + ", shared=" + shared + ", timeout=" + timeout + ", password="
+ password + ", maxactive=" + maxActive + "]";
}
}
//原理暂时不清楚,只是记录一下怎么写代码,稍后会补充上
流程
1.使用jedis cluster调用lua锁,如果能获取到对应的key,证明可以用,如果不能说明在redis内存中此key已被其他线程占用
2.最后不管是程序报错还是程序结束都要释放lua锁
推荐博客:Redis分布式锁实现【精要总结】_少年单排,记录点滴 -CSDN博客
Redis的分布式锁详解_张维鹏的博客-CSDN博客_redis分布式锁
Redisson实现分布式锁(1)---原理 - 雨点的名字 - 博客园 (cnblogs.com)