【Redisson分布式锁源码分析-3】

文章目录

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();

    }
相关推荐
Roye_ack16 小时前
【黑马点评 - 高级篇】Redis分布式缓存原理(Redis持久化 RDB AOF + 主从集群 哨兵 分片集群 + 多级缓存)
redis·分布式·缓存·aof·redis持久化·rdb·redis主从哨兵分片集群
b***594316 小时前
分布式WEB应用中会话管理的变迁之路
前端·分布式
斐硕人16 小时前
SQL滚动求和
数据库·sql·mysql·maxcompute
楠枬16 小时前
服务注册与发现——Eureka
spring cloud·eureka·服务发现
Z_Easen16 小时前
RabbitMQ 技术深度解析:从核心概念到可靠性实践
分布式·rabbitmq
optimistic_chen17 小时前
【Java EE进阶 --- SpringBoot】Spring事务传播机制
spring boot·后端·spring·java-ee·事务·事务传播机制
7***374517 小时前
HarmonyOS分布式能力的核心技术
分布式·华为·harmonyos
xiaoye-duck17 小时前
数据结构之排序-选择排序&交换排序
数据结构·排序算法
小此方17 小时前
笔记:树。
数据结构·笔记
q***751817 小时前
RabbitMQ 客户端 连接、发送、接收处理消息
分布式·rabbitmq·ruby