文章目录
Redisson公平锁
公平锁顾名思义就是按照公平的情况分配锁资源,而是按照排队顺序,轮到谁,谁就可以获取到锁资源,因此锁竞争不激烈,但是实现相对非公平锁来说比较复杂一点,需要有个队列来保证排队的顺序,下面让我们来细聊一下redisson实现非公平锁的源码实现。
公平锁的DEMO实现
java
@Test
public void test() throws Exception {
// 启动默认 Redis
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
//默认非公平锁
RLock lock = redisson.getFairLock("test");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
lock.lock(100, TimeUnit.MINUTES);
num++;
System.out.println("Lock acquired: " + num);
} catch (Exception e) {
System.out.println("============" + e.getMessage());
} finally {
lock.unlock();
}
}).start();
}
// 阻塞等待或做其他操作
new CountDownLatch(1).await();
// 关闭客户端
redisson.shutdown();
}
业务加锁
java
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return;
}
//订阅redisson_lock_channel:{lockname]通道
CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
//设置订阅超时时间
pubSub.timeout(future);
RedissonLockEntry entry;
if (interruptibly) {
//会阻塞直到订阅完成(RedissonLockEntry 就是该订阅的上下文对象,包含一个 CountDownLatch 用于等待通知
entry = commandExecutor.getInterrupted(future);
} else {
entry = commandExecutor.get(future);
}
try {
//死循环
while (true) {
//尝试获取锁
ttl = tryAcquire(-1, leaseTime, unit, threadId);
// 获取锁成功,结束
if (ttl == null) {
break;
}
//已经有别的线程获取到锁
if (ttl >= 0) {
try {
//阻塞等待ttl
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
entry.getLatch().acquire();
} else {
entry.getLatch().acquireUninterruptibly();
}
}
}
} finally {
//取消订阅
unsubscribe(entry, threadId);
}
// get(lockAsync(leaseTime, unit));
}
分析LOCK的源码
尝试异步获取锁
java
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture<Long> ttlRemainingFuture;
//用户是否指定过期时间
if (leaseTime > 0) {
//尝试异步获取锁,过期时间为用户指定的时间
ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//没有指定过期时间默认为30s
ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
//这一步主要是处理一些 Redisson 特殊的"同步机制"或分布式事务相关逻辑。
//简单说,它会对返回的 future 包一层,以便 Redisson 统一异步流程。
CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);
ttlRemainingFuture = new CompletableFutureWrapper<>(s);
CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// 获取到锁
if (ttlRemaining == null) {
if (leaseTime > 0) {
//中断时间
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
//没有指定过期时间,则开启看门狗线程
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
RedissonFairLock类是公平锁的具体实现类
java
public RedissonFairLock(CommandAsyncExecutor commandExecutor, String name, long threadWaitTime) {
super(commandExecutor, name);
this.commandExecutor = commandExecutor;//公共的执行器
this.threadWaitTime = threadWaitTime;//现成等待时间
threadsQueueName = prefixName("redisson_lock_queue", name);//线程队列名称
timeoutSetName = prefixName("redisson_lock_timeout", name);//线程有序集合名称
}
尝试异步获取锁:
java
@Override
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
long wait = threadWaitTime;
if (waitTime > 0) {
wait = unit.toMillis(waitTime);
}
long currentTime = System.currentTimeMillis();
if (command == RedisCommands.EVAL_LONG) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
// 移除过期的线程
"while true do " +
//从redisson_lock_queue:{lockname}中获取0位置的数据
"local firstThreadId2 = redis.call('lindex', KEYS[2], 0);" +
//如果不存在直接结束
"if firstThreadId2 == false then " +
"break;" +
"end;" +
//获取redisson_lock_timeout:{lockname}中的firstThreadId2的超时时间
"local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));" +
//如果小于等于当前时间
"if timeout <= tonumber(ARGV[4]) then " +
// 移除redisson_lock_timeout有序集合中超时的线程
"redis.call('zrem', KEYS[3], firstThreadId2);" +
//移除redisson_lock_queue最左侧存储的线程
"redis.call('lpop', KEYS[2]);" +
"else " +
"break;" +
"end;" +
"end;" +
// 判断当前的lockname存在并且redisson_lock_queue也存在,或者队列的最左侧的线程名称等于当前的线程
"if (redis.call('exists', KEYS[1]) == 0) " +
"and ((redis.call('exists', KEYS[2]) == 0) " +
"or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
// 移除当前队列最左侧的线程以及有序集合中的额线程
"redis.call('lpop', KEYS[2]);" +
"redis.call('zrem', KEYS[3], ARGV[2]);" +
// 获取有序集合中的全部数据
"local keys = redis.call('zrange', KEYS[3], 0, -1);" +
//循环遍历给当前的数据减去等待时间
"for i = 1, #keys, 1 do " +
"redis.call('zincrby', KEYS[3], -tonumber(ARGV[3]), keys[i]);" +
"end;" +
// 设置锁,并且设置过期时间
"redis.call('hset', KEYS[1], ARGV[2], 1);" +
"redis.call('pexpire', KEYS[1], ARGV[1]);" +
"return nil;" +
"end;" +
// 设置可冲入的锁,将当前的count+1
"if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
"redis.call('hincrby', KEYS[1], ARGV[2],1);" +
"redis.call('pexpire', KEYS[1], ARGV[1]);" +
"return nil;" +
"end;" +
// 无法获取锁
// 检查当前的线程是否在有序集合中
"local timeout = redis.call('zscore', KEYS[3], ARGV[2]);" +
"if timeout ~= false then " +
// the real timeout is the timeout of the prior thread
// in the queue, but this is approximately correct, and
// avoids having to traverse the queue
"return timeout - tonumber(ARGV[3]) - tonumber(ARGV[4]);" +
"end;" +
// add the thread to the queue at the end, and set its timeout in the timeout set to the timeout of
// the prior thread in the queue (or the timeout of the lock if the queue is empty) plus the
// threadWaitTime
"local lastThreadId = redis.call('lindex', KEYS[2], -1);" +
"local ttl;" +
"if lastThreadId ~= false and lastThreadId ~= ARGV[2] then " +
"ttl = tonumber(redis.call('zscore', KEYS[3], lastThreadId)) - tonumber(ARGV[4]);" +
"else " +
"ttl = redis.call('pttl', KEYS[1]);" +
"end;" +
"local timeout = ttl + tonumber(ARGV[3]) + tonumber(ARGV[4]);" +
"if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then " +
"redis.call('rpush', KEYS[2], ARGV[2]);" +
"end;" +
"return ttl;",
Arrays.asList(getRawName(), threadsQueueName, timeoutSetName),
unit.toMillis(leaseTime), getLockName(threadId), wait, currentTime);
}
throw new IllegalArgumentException();
}
获取锁失败,开始订阅channel:redisson_lock_channel:{lockname}
java
public CompletableFuture<E> subscribe(String entryName, String channelName) {
AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
CompletableFuture<E> newPromise = new CompletableFuture<>();
semaphore.acquire().thenAccept(c -> {
if (newPromise.isDone()) {
semaphore.release();
return;
}
E entry = entries.get(entryName);
if (entry != null) {
entry.acquire();
semaphore.release();
entry.getPromise().whenComplete((r, e) -> {
if (e != null) {
newPromise.completeExceptionally(e);
return;
}
newPromise.complete(r);
});
return;
}
E value = createEntry(newPromise);
value.acquire();
E oldValue = entries.putIfAbsent(entryName, value);
if (oldValue != null) {
oldValue.acquire();
semaphore.release();
oldValue.getPromise().whenComplete((r, e) -> {
if (e != null) {
newPromise.completeExceptionally(e);
return;
}
newPromise.complete(r);
});
return;
}
RedisPubSubListener<Object> listener = createListener(channelName, value);
CompletableFuture<PubSubConnectionEntry> s = service.subscribeNoTimeout(LongCodec.INSTANCE, channelName, semaphore, listener);
newPromise.whenComplete((r, e) -> {
if (e != null) {
s.completeExceptionally(e);
}
});
s.whenComplete((r, e) -> {
if (e != null) {
entries.remove(entryName);
value.getPromise().completeExceptionally(e);
return;
}
value.getPromise().complete(value);
});
});
return newPromise;
}
执行订阅的核心方法
执行订阅的核心方法
java
private void subscribeNoTimeout(Codec codec, ChannelName channelName, MasterSlaveEntry entry,
CompletableFuture<PubSubConnectionEntry> promise, PubSubType type,
AsyncSemaphore lock, AtomicInteger attempts, RedisPubSubListener<?>... listeners) {
//检查是否已有订阅连接
PubSubConnectionEntry connEntry = name2PubSubConnection.get(new PubSubKey(channelName, entry));
//如果该 channel + entry 已经订阅过(存在缓存连接)就直接把监听器 attach 上去,不重复 SUBSCRIBE。
if (connEntry != null) {
addListeners(channelName, promise, type, lock, connEntry, listeners);
return;
}
//freePubSubLock 是一个异步信号量,确保同时只有一个线程在分配或创建连接,避免出现两个线程同时判断"没有空闲连接",然后都去创建连接的竞态条件。
freePubSubLock.acquire().thenAccept(c -> {
//如果上层已经取消订阅任务,就及时释放资源。
if (promise.isDone()) {
lock.release();
freePubSubLock.release();
return;
}
//从连接池中尝试复用空闲连接
PubSubEntry freePubSubConnections = entry2PubSubConnection.getOrDefault(entry, new PubSubEntry());
PubSubConnectionEntry freeEntry = freePubSubConnections.getEntries().peek();
//如果没有空闲连接 → 新建连接
if (freeEntry == null) {
//调用 connect() 建立一个新的 Redis 连接;
//这是异步的;
//使用 HashedWheelTimer 定时器在一定时间后检测连接是否超时。
freePubSubLock.release();
//实现了自动重试逻辑(如果连接失败或超时)。
CompletableFuture<RedisPubSubConnection> connectFuture = connect(codec, channelName, entry, promise, type, lock, listeners);
connectionManager.getServiceManager().newTimeout(t -> {
if (!connectFuture.cancel(false)
&& !connectFuture.isCompletedExceptionally()) {
return;
}
trySubscribe(codec, channelName, promise, type, lock, attempts, listeners);
}, config.getRetryInterval(), TimeUnit.MILLISECONDS);
return;
}
//当连接被占用完毕(remainFreeAmount == 0)时,会从队列中移除。
int remainFreeAmount = freeEntry.tryAcquire();
if (remainFreeAmount == -1) {
throw new IllegalStateException();
}
//如果别的线程抢先订阅了同一个 channel,则复用旧连接,否则占用新的。
PubSubKey key = new PubSubKey(channelName, entry);
//记录这个 channel 是在哪个 Redis 节点上被订阅的。
PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(key, freeEntry);
if (oldEntry != null) {
//如果依旧有旧的订阅,将当前的连接释放
freeEntry.release();
freePubSubLock.release();
addListeners(channelName, promise, type, lock, oldEntry, listeners);
return;
}
Collection<MasterSlaveEntry> coll = name2entry.computeIfAbsent(channelName, k -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
coll.add(entry);
if (remainFreeAmount == 0) {
freePubSubConnections.getEntries().poll();
}
freePubSubLock.release();
//执行真正的subscribe
CompletableFuture<Void> subscribeFuture = addListeners(channelName, promise, type, lock, freeEntry, listeners);
freeEntry.subscribe(codec, type, channelName, subscribeFuture);
subscribeFuture.whenComplete((r, e) -> {
//如果报错,则取消订阅
if (e != null) {
unsubscribe(channelName, type);
}
});
});
}
接收发布事件
java
private RedisPubSubListener<Object> createListener(String channelName, E value) {
RedisPubSubListener<Object> listener = new BaseRedisPubSubListener() {
@Override
public void onMessage(CharSequence channel, Object message) {
if (!channelName.equals(channel.toString())) {
return;
}
PublishSubscribe.this.onMessage(value, (Long) message);
}
};
return listener;
}
LockPubSub类的执行
java
@Override
protected void onMessage(RedissonLockEntry value, Long message) {
//接收到的发布信息UNLOCK_MESSAGE,执行信号量的释放
if (message.equals(UNLOCK_MESSAGE)) {
Runnable runnableToExecute = value.getListeners().poll();
if (runnableToExecute != null) {
runnableToExecute.run();
}
value.getLatch().release();
} else if (message.equals(READ_UNLOCK_MESSAGE)) {
while (true) {
Runnable runnableToExecute = value.getListeners().poll();
if (runnableToExecute == null) {
break;
}
runnableToExecute.run();
}
value.getLatch().release(value.getLatch().getQueueLength());
}
}
取消订阅
执行取消订阅信息
java
public void unsubscribe(E entry, String entryName, String channelName) {
//获取定于信息的异步信号量
AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
semaphore.acquire().thenAccept(c -> {
//释放信号量,取消订阅信息
if (entry.release() == 0) {
entries.remove(entryName);
service.unsubscribeLocked(PubSubType.UNSUBSCRIBE, new ChannelName(channelName))
.whenComplete((r, e) -> {
semaphore.release();
});
} else {
semaphore.release();
}
});
}
unlock锁释放
java
public RFuture<Void> unlockAsync(long threadId) {
//锁释放的核心方法
RFuture<Boolean> future = unlockInnerAsync(threadId);
CompletionStage<Void> f = future.handle((opStatus, e) -> {
//移除看门狗线程
cancelExpirationRenewal(threadId);
//释放锁报错
if (e != null) {
throw new CompletionException(e);
}
//操作状态等于null
if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
throw new CompletionException(cause);
}
return null;
});
return new CompletableFutureWrapper<>(f);
}
锁释放的lua脚本
java
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// 移除过期的线程
"while true do "
//获取redisson_lock_queue:{lockname}最左侧的数据
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
//不存在直接结束
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
//存在判断一下是否在redisson_lock_timeout:{}有序集合中存在
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
//如果thread的过期时间小于当前时间,则直接从队列和有序集合中删除
+ "if timeout <= tonumber(ARGV[4]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
//判断当前的锁是否存在
+ "if (redis.call('exists', KEYS[1]) == 0) then " +
//不存在,获取下一个thread
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
//获取成功,发布订阅的事件
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; " +
"end;" +
//判断当前的key在hash中是否存在
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
//存在直接可重入数-1
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
//如果count还存在,则给他进行锁续期
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"end; " +
//count==0直接将此key进行删除,获取下一个线程名称,发布事件,使得下一个对象可以获取到锁
"redis.call('del', KEYS[1]); " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; ",
Arrays.asList(getRawName(), threadsQueueName, timeoutSetName, getChannelName()),
LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId), System.currentTimeMillis());
}
tryLock源码分析
java
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//等待时间转换
long time = unit.toMillis(waitTime);
//获取当前的时间
long current = System.currentTimeMillis();
//获取当前的线程
long threadId = Thread.currentThread().getId();
//尝试获取锁
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// 获取锁成功,直接结束
if (ttl == null) {
return true;
}
//剩余的等待时间
time -= System.currentTimeMillis() - current;
//小于0,直接结束
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
current = System.currentTimeMillis();
//开始执行订阅信息
CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
try {
//获取订阅信息
subscribeFuture.get(time, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
"Unable to acquire subscription lock after " + time + "ms. " +
"Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
subscribeFuture.whenComplete((res, ex) -> {
if (ex == null) {
unsubscribe(res, threadId);
}
});
}
//订阅失败,执行acquire失败方法
acquireFailed(waitTime, unit, threadId);
return false;
} catch (ExecutionException e) {
acquireFailed(waitTime, unit, threadId);
return false;
}
try {
time -= System.currentTimeMillis() - current;
//超时报错
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
//仅入循环
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// waiting for message
currentTime = System.currentTimeMillis();
if (ttl >= 0 && ttl < time) {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
//超时执行获取失败信息
acquireFailed(waitTime, unit, threadId);
return false;
}
}
} finally {
//取消订阅信息
unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
}
// return get(tryLockAsync(waitTime, leaseTime, unit));
}
RedissonFairLock类中acquireFailedAsync()方法
java
protected CompletableFuture<Void> acquireFailedAsync(long waitTime, TimeUnit unit, long threadId) {
long wait = threadWaitTime;
if (waitTime > 0) {
wait = unit.toMillis(waitTime);
}
RFuture<Void> f = evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
// 获取全部的redisson_lock_queue:{lockname} 的全部数据
"local queue = redis.call('lrange', KEYS[1], 0, -1);" +
// find the location in the queue where the thread is
"local i = 1;" +
//找到当前线程在线程队列中的位置
"while i <= #queue and queue[i] ~= ARGV[1] do " +
"i = i + 1;" +
"end;" +
// 移动到下一个元素(即当前线程之后的下一个等待者)
"i = i + 1;" +
// 对后续等待线程的 timeout 值减去当前线程占用的时间
"while i <= #queue do " +
"redis.call('zincrby', KEYS[2], -tonumber(ARGV[2]), queue[i]);" +
"i = i + 1;" +
"end;" +
//删除当前线程的等待信息
"redis.call('zrem', KEYS[2], ARGV[1]);" +
"redis.call('lrem', KEYS[1], 0, ARGV[1]);",
Arrays.asList(threadsQueueName, timeoutSetName),
getLockName(threadId), wait);
return f.toCompletableFuture();
}
bitMap分析
测试demo
java
@Test
public void test4() throws Exception {
//创建 Redisson 客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setDatabase(0);
RedissonClient redisson = Redisson.create(config);
//获取位图对象
RBitSet bitSet = redisson.getBitSet("user:login:20251029");
//设置位(模拟用户登录)
bitSet.set(1001, true); // 用户ID=1001 登录
bitSet.set(2002, true); // 用户ID=2002 登录
bitSet.set(3003, false); // 用户ID=3003 未登录
//获取位(判断是否登录)
boolean isLogin = bitSet.get(1001);
System.out.println("用户1001登录状态: " + isLogin);
//统计当天登录总人数
long loginCount = bitSet.cardinality();
System.out.println("登录总人数: " + loginCount);
//清空(重置)
bitSet.clear();
//关闭 Redisson
redisson.shutdown();
}
BitMap的介绍:
Bitmap 不是一种新的数据类型,它其实是基于 字符串(String)类型 的 位操作(bit-level operation)。
Redis 的字符串最大可存储 512MB,为了防止大key我们一般都会让我们的存储数据<=512;
512MB = 512 × 1024 × 1024 × 8 = 4,294,967,296 位(约 42 亿 bit);每个 bit 可以表示一个用户、一个事件、或一个状态。
BitMap用来存储精确地数据,一般用于签到,登录等场景。
布隆过滤器
测试demo
java
@Test
public void test5() throws Exception {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RBloomFilter<Object> bloomFilter = redisson.getBloomFilter("user");
// 初始化布隆过滤器(误判率、预期数量)
bloomFilter.tryInit(1000000L, 0.01);
// 添加元素
bloomFilter.add("user:123");
bloomFilter.add("user:456");
// 判断是否存在
boolean exist = bloomFilter.contains("user:123"); // true
boolean notExist = bloomFilter.contains("user:999"); // false
System.out.println("user:123是否存在" + exist);
System.out.println("user:999是否存在" + notExist);
redisson.shutdown();
}