写点什么

Java 王者修炼手册【Redis 篇 - 进阶数据类型】:吃透布隆过滤器 / Redisson 分布式锁 / Geo/Bitmap/HLL 核心原理与实战场景

作者:DonaldCen
  • 2025-12-12
    广东
  • 本文字数:10021 字

    阅读完需:约 33 分钟

Java 王者修炼手册【Redis 篇 - 进阶数据类型】:吃透布隆过滤器 / Redisson 分布式锁 / Geo/Bitmap/HLL 核心原理与实战场景

大家好,我是程序员强子。

今天学点有意思的,Redisson 封装了原生的 redis,改进了非常多的功能点,我们来了解一下呗~

  • 布隆过滤器底层:Bitmap 定义和使用场景

  • 使用 Redisson 实现 布隆过滤器分布式锁

  • Geo: 实现附近的人,附近商家功能

  • HyperLogLog: 网站 / APP 的 UV 统计 /页面独立 IP 访问数统计

干货很多,赶紧上车~

Bitmap

定义

  • 基于字符串(String)类型实现的二进制位操作机制,每个位只能是 0 或 1

  • 常用于高效存储海量二值状态(如用户签到、在线状态、是否阅读等)

使用场景

  • 用户签到key = user:{uid}:sign,offset = 日期(如当月第几天),1 = 签到,0 = 未签到,BITCOUNT 统计总签到天数。

  • 在线状态:key = online:users,offset = 用户 ID,1 = 在线,0 = 离线,BITOP 统计在线人数

  • 布隆过滤器:通过多个 BITSET 位运算实现海量数据去重(如判断用户是否已注册)。

  • 权限控制:每一位代表一个权限,BITOP 组合权限集。

在工作中,一般在程序上不直接用,而是使用封装好的 Redisson,我们深入了解一下 Redisson

使用 Redisson 实现布隆过滤器

本质

  • 无漏判:若返回 不存在,则元素一定不存在(100% 准确)

  • 有误判:若返回 存在,则元素 “可能存在”(误判率可量化、可控制)

  • 不支持删除:底层设计决定了删除会导致误判率飙升

  • 极致省内存:存储 1 亿个元素,误判率 1% 时,仅需约 12MB 内存(对比 HashSet 需数百 MB)

Redisson 布隆过滤器优势

  • 参数自动优化:根据预期元素数误判率自动计算最优的位图大小哈希函数个数,无需手动推导;

  • 分布式:基于 Redis 集群 实现,多实例部署时数据全局一致;

  • API 简洁:封装了 add、contains 等核心方法,无需手动操作 Bitmap;

  • 序列化支持:内置多种序列化方式,支持任意类型元素(如 String、Long、自定义对象);

  • 高性能:底层基于 Redis Pipeline 批量操作,减少网络开销

案例

通过 RedissonClient 获取 RBloomFilter 实例,封装通用的添加、查询方法

支持自定义布隆过滤器名称预期元素数误判率

import org.redisson.api.RBloomFilter;import org.redisson.api.RedissonClient;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import javax.annotation.Resource;
@Componentpublic class RedissonBloomFilter {
    /**     * Redisson 客户端(自动注入)     */    @Resource    private RedissonClient redissonClient;
    /**     * 示例:用户ID布隆过滤器(可定义多个不同业务的过滤器)     */    private RBloomFilter<String> userBloomFilter;
    /**     * 初始化布隆过滤器(PostConstruct 确保容器启动时初始化)     * 核心参数:     * - 预期元素数(expectedInsertions):预估要添加的元素总量     * - 误判率(falseProbability):允许的最大误判率(如 0.01 = 1%)     */    @PostConstruct    public void initBloomFilter() {        // 1. 获取布隆过滤器实例(指定唯一名称,区分不同业务)        userBloomFilter = redissonClient.getBloomFilter("bloom:filter:user:id");
        // 2. 初始化参数(仅首次调用有效,重复调用会抛出异常)        // 预期元素数:100万,误判率:1%        userBloomFilter.tryInit(1000000L, 0.01);    }
    /**     * 添加元素到布隆过滤器     * @param value 待添加的元素(如用户ID、订单号)     * @return true=添加成功,false=元素已存在(或过滤器初始化失败)     */    public boolean add(String value) {        if (value == null) {            return false;        }        return userBloomFilter.add(value);    }
    /**     * 判断元素是否存在于布隆过滤器     * @param value 待查询的元素     * @return true=可能存在(误判),false=一定不存在     */    public boolean contains(String value) {        if (value == null) {            return false;        }        return userBloomFilter.contains(value);    }
    /**     * 扩展:获取自定义布隆过滤器(适用于多业务场景)     * @param filterName 过滤器名称     * @param expectedInsertions 预期元素数     * @param falseProbability 误判率     * @return 布隆过滤器实例     */    public <T> RBloomFilter<T> getCustomBloomFilter(String filterName, long expectedInsertions, double falseProbability) {        RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(filterName);        bloomFilter.tryInit(expectedInsertions, falseProbability);        return bloomFilter;    }}
复制代码

单元测试

import org.junit.jupiter.api.Test;import org.redisson.api.RBloomFilter;import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTestpublic class RedissonBloomFilterTest {
    @Resource    private RedissonBloomFilter redissonBloomFilter;
    @Test    public void testDefaultBloomFilter() {        // 测试元素        String existUserId = "100001";        String notExistUserId = "999999";
        // 1. 添加元素        boolean addResult = redissonBloomFilter.add(existUserId);        assertTrue(addResult); // 首次添加返回true
        // 2. 重复添加同一元素        boolean addRepeatResult = redissonBloomFilter.add(existUserId);        assertFalse(addRepeatResult); // 重复添加返回false
        // 3. 验证存在的元素(返回true)        assertTrue(redissonBloomFilter.contains(existUserId));
        // 4. 验证不存在的元素(返回false)        assertFalse(redissonBloomFilter.contains(notExistUserId));    }
    @Test    public void testCustomBloomFilter() {        // 获取自定义布隆过滤器(订单号过滤,预期10万元素,误判率0.1%)        RBloomFilter<String> orderBloomFilter = redissonBloomFilter.getCustomBloomFilter(                "bloom:filter:order:no",                100000L,                0.001        );
        // 添加订单号        orderBloomFilter.add("ORDER_20251210_001");
        // 验证        assertTrue(orderBloomFilter.contains("ORDER_20251210_001"));        assertFalse(orderBloomFilter.contains("ORDER_20251210_999"));    }}
复制代码

底层原理

位图(Bitmap) + 多个独立的哈希函数

  • 1 个字节 = 8 位,1 亿位仅占 100,000,000 / 8 / 1024 / 1024 ≈ 12MB

  • 计算速度快,使用 MurmurHash3 等高效性哈希函数,而非 MD5/SHA1

初始化

流程图如下:

  • 参数校验

  • 最优参数计算

  • 组件初始化


查询数据流程

  • 哈希计算

  • 偏移量生成

  • 位图校验

  • 结果返回


添加元素


误判原理

不同元素经过哈希函数计算后,恰好生成了完全相同的偏移量组合,导致位图中对应位都被置 1,查询时误判为 存在

举个例子

  • 添加元素 A 后,位图 7、13、11 位置 1;

  • 未添加元素 B,但 h1 (B)=31→7、h2 (B)=57→13、h3 (B)=27→11

  • 查询 B 时会误判为 存在

不能删除底层原因

  • 位图中的一个位可能被多个元素共享,不同元素的哈希偏移量可能重合

  • 若将某元素对应的位置 0,会导致其他共享该位的元素被 误删除,查询时返回 不存在

Redisson 除了布隆过滤器,还有哪些好用的功能呢?我们继续了解一下~

Redisson 实现分布式锁

官方推荐的分布式锁实现方案,其核心是基于 Redis 的命令和数据结构

封装了以下特点的分布式锁体系

  • 可重入

  • 可自动续期

  • 支持公平 / 读写锁

  • 适配集群部署

原生 Redis 分布式锁常见问题

Redisson 对原生 Redis 锁的改进

解决死锁风险

  • 改进点:Redisson 的看门狗线程会动态续期锁的过期时间,业务执行多久,锁就续期多久(默认每 10 秒续期为 30 秒);

  • 兜底方案:若服务宕机,看门狗消失,锁会在默认 30 秒后自动过期,不会出现永久死锁;

  • 手动过期时间:也可通过 lock(long leaseTime, TimeUnit unit)指定固定过期时间,此时看门狗不会生效(需业务保证在过期前执行完)

解决非可重入问题

  • 原生问题:原生锁用 String 存储,仅能记录锁的持有者,无法记录重入次数

  • Redisson 改进:采用 Redis Hash 结构存储锁信息:Key:锁名称(如 lock:seckill:1001);Field:锁标识(UUID:threadId);Value:重入次数(int 类型,初始为 1,同一线程再次加锁则 + 1,释放锁则 - 1);

  • 效果:同一个线程可多次获取同一把锁,解决方法嵌套调用的场景

解决锁释放的原子性问题

  • 原生问题:GET+DEL 非原子,可能释放别人的锁;

  • Redisson 改进:所有加锁、释放锁、重入计数的操作,都通过 Lua 脚本执行(Redis 中 Lua 脚本是原子执行的),从根本上保证多命令的原子性

解决非公平锁问题

  • 原生问题:所有线程同时竞争锁,无法保证顺序;

  • Redisson 改进:RFairLock(公平锁)基于 Redis 有序集合(ZSet) 实现请求排队:线程加锁失败时,会将自己的请求信息(线程 ID + 时间戳)存入 ZSet(按时间戳排序);当锁释放时,ZSet 中排名第一的线程会被唤醒并加锁;

  • 效果:保证线程按请求顺序获取锁,避免饥饿问题(代价:性能略低于非公平锁)

解决主从切换锁丢失

  • 原生问题:主从同步延迟导致锁丢失;

  • Redisson 改进RedLock(红锁):线程需在至少 N/2+1 个独立 Redis 节点(如 3 个节点,需 2 个节点加锁成功)上加锁成功,才认为整体加锁成功;即使某个节点宕机,剩余节点仍能保证锁的有效性;MultiLock(联锁):将多个节点的锁组合成一个锁,需所有节点加锁成功才生效;

  • 适用场景:对锁一致性要求极高的场景(如金融交易),可选择 RedLock;普通场景用单节点锁 + 哨兵模式即可。

解决阻塞轮询消耗资源

  • 原生问题:线程加锁失败后只能轮询重试,消耗资源;

  • Redisson 改进:采用 Redis 的发布订阅(Pub/Sub) 机制:线程加锁失败时,订阅该锁的释放通知通道(如 redisson_lock__channel:{lock:seckill:1001});当锁被释放时,Redisson 会向通道发布通知,订阅的线程收到通知后立即重试加锁;

  • 效果:避免无效轮询,大幅降低 CPU 和 Redis 资源消耗

解决读写分离问题

  • 原生问题:原生锁是排他锁,读多写少场景性能差;

  • Redisson 改进:RReadWriteLock 分为读锁(RLock) 和写锁(WLock):读锁:多个线程可同时获取(共享锁),仅当有写锁时才会阻塞;写锁:排他锁,有读锁或其他写锁时,线程会阻塞;

  • 适用场景:商品详情缓存更新、新闻内容发布等读多写少场景,性能提升数倍

解决集群下锁一致性问题

  • 原生问题:集群下锁 key 散列到不同节点,无法保证锁唯一性;

  • Redisson 改进:Redisson 支持 Redis 单节点哨兵主从集群等所有部署模式;对于集群模式,Redisson 会将锁 key 映射到同一个槽位(通过{lockName}的哈希标签机制),保证锁 key 在集群的同一个节点上,从而保证锁的唯一性;

  • 效果:在 Redis 集群中,分布式锁仍能正常工作

单节点分布式锁的核心实现原理

基于 Redis 的字符串(String) 和 Lua 脚本

加锁流程


  • 锁标识:采用生成客户端 ID:threadId 的格式,保证不同线程、不同服务实例的锁标识唯一;

  • Lua 脚本原子性:加锁的 “判断锁是否存在 + 设置锁 + 设置过期时间” 是通过 Lua 脚本执行的,避免多命令的原子性问题;

  • 默认过期时间:加锁时默认设置 30 秒过期时间(可自定义),防止死锁

自动续期机制

看门狗(Watch Dog)自动续期机制

  • 当线程加锁成功后,若业务未执行完成,看门狗线程(后台定时线程)会每隔 10 秒(过期时间的 1/3)自动将锁的过期时间重置为 30 秒;

  • 当线程执行完业务释放锁后,看门狗线程会停止续期

  • 若服务实例宕机,看门狗线程消失,锁会在 30 秒后自动过期,避免死锁

释放锁


释放锁同样通过 Lua 脚本保证 “判断锁标识 + 删除锁 / 减少重入次数” 的原子性

多节点分布式实现

针对 Redis 主从、哨兵、集群部署的场景,Redisson 提供了两种解决方案

  • RedLock 算法(红锁):RedissonRedLock,需部署多个独立的 Redis 节点(至少 3 个),线程需在超过半数的节点上加锁成功,才认为整体加锁成功;解决主从切换导致的锁丢失问题,但性能略低,且对节点部署要求高。

  • MultiLock(联锁):RedissonMultiLock,将多个 RLock(对应不同 Redis 节点 / 服务)组合成一个锁,需所有锁都加锁成功才认为加锁成功;适用于跨 Redis 集群的锁场景

使用注意事项

  • 锁名称的唯一性:锁名称需与业务维度绑定(如 lock:seckill:goods:1001),避免不同业务共用锁导致冲突

  • 避免长时间占用锁:即使有看门狗,也应尽量缩短业务执行时间,防止锁长期被占用导致其他线程阻塞

  • 选择合适的锁类型:普通场景用 RLock(非公平锁,性能高),需顺序的场景用 RFairLock,读多写少用 RReadWriteLock;

  • RedLock 的适用场景:RedLock 需部署多个独立 Redis 节点,运维成本高,普通场景无需使用(哨兵模式 + 单节点锁已足够);

  • 异常处理:加锁后必须在 finally 块中释放锁,避免业务异常导致锁无法释放;

  • 序列化问题:Redisson 的锁标识采用序列化存储,需保证序列化方式的一致性(默认用 JSON 序列化,可自定义)

Redisson 中好用的其他功能

信号量(RSemaphore)

  • 功能分布式版的「资源计数器」,控制同时访问资源的线程数;

  • 典型场景限流(如同时允许 10 个请求访问某接口)资源池控制(如同时允许 5 个线程操作数据库连接)

// 1. 获取信号量(初始容量为10)RSemaphore semaphore = redissonClient.getSemaphore("semaphore:limited:interface");semaphore.trySetPermits(10); // 初始化容量(仅首次有效)
try {    // 2. 尝试获取许可(等待3秒,超时则拒绝)    boolean acquired = semaphore.tryAcquire(3, TimeUnit.SECONDS);    if (!acquired) {        return "当前请求过多,请稍后再试";    }    // 3. 执行业务逻辑    return "接口访问成功";} catch (InterruptedException e) {    Thread.currentThread().interrupt();    return "请求失败";} finally {    // 4. 释放许可(归还信号量)    semaphore.release();}
复制代码

闭锁(RCountDownLatch)

  • 功能:分布式版的「等待多任务完成」;

  • 作用:比如一个主任务,执行到一个环节后需要先等各个子任务全部完全后,再执行

  • 典型场景:服务启动前等待所有依赖服务初始化完成分布式任务的分阶段执行

示例:主任务需要等待 3 个子任务完成后才能执行,子任务可以在不同服务实例中调用

import org.redisson.api.RCountDownLatch;import org.redisson.api.RedissonClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;import java.util.concurrent.TimeUnit;
/** * Redisson RCountDownLatch 分布式倒计时闭锁 Demo */@RestControllerpublic class RedissonCountDownLatchDemo {    @Resource    private RedissonClient redissonClient;
    // 定义闭锁的唯一标识(分布式场景下,所有服务实例使用同一个标识)    private static final String LATCH_KEY = "distributed:latch:task:demo";    // 需要等待的子任务数量    private static final int TASK_COUNT = 3;
    /**     * 步骤1:主任务接口 - 等待所有子任务完成     * 访问该接口后,请求会阻塞,直到3个子任务都执行 countDown()     */    @GetMapping("/task/main")    public String mainTask() {        // 1. 获取分布式闭锁实例        RCountDownLatch latch = redissonClient.getCountDownLatch(LATCH_KEY);
        try {            // 2. 初始化闭锁计数(仅首次调用有效,重复调用会忽略)            latch.trySetCount(TASK_COUNT);            System.out.println("主任务开始等待,剩余子任务数:" + latch.getCount());
            // 3. 阻塞等待:直到计数归零,或超时(这里设置超时时间为60秒)            boolean awaitSuccess = latch.await(60, TimeUnit.SECONDS);
            if (awaitSuccess) {                return "所有子任务完成,主任务执行成功!";            } else {                return "等待子任务超时,主任务执行失败!";            }        } catch (InterruptedException e) {            Thread.currentThread().interrupt();            return "主任务被中断,执行失败!";        }    }
    /**     * 步骤2:子任务接口 - 完成一个子任务,计数减1     * 可在不同服务实例中调用,每次调用计数减1     */    @GetMapping("/task/child")    public String childTask() {        RCountDownLatch latch = redissonClient.getCountDownLatch(LATCH_KEY);
        // 校验闭锁是否已初始化(避免未调用mainTask就执行childTask)        if (latch.getCount() == 0 && !latch.trySetCount(TASK_COUNT)) {            return "闭锁未初始化,请先调用主任务接口!";        }
        // 原子操作:计数减1        latch.countDown();        long remaining = latch.getCount();        System.out.println("一个子任务完成,剩余子任务数:" + remaining);
        return "子任务完成,剩余子任务数:" + remaining;    }
    /**     * 重置闭锁(可选):重新设置计数,用于重复测试     */    @GetMapping("/task/reset")    public String resetLatch() {        RCountDownLatch latch = redissonClient.getCountDownLatch(LATCH_KEY);        latch.trySetCount(TASK_COUNT); // 重新设置计数(会覆盖原有计数)        return "闭锁已重置,计数恢复为:" + TASK_COUNT;    }}
复制代码
  • 主任务接口 - 等待所有子任务完成

  • 子任务接口 - 完成一个子任务,计数减 1

  • 直到调用完成或者调用超时

  • RCountDownLatch.await() 只会阻塞当前请求对应的业务线程,不会占用 Tomcat 线程池的所有线程;

  • 只要 Tomcat 线程池还有空闲线程,服务实例就能正常接收和处理其他请求;

GEO

底层依赖两个核心技术:GeoHash 算法 + Redis 有序集合(ZSet)

添加地理空间数据(GEOADD)

向指定的 key 中添加一个或多个经纬度与成员的映射关系

成员唯一,重复添加会覆盖

命令语法

GEOADD key longitude latitude member [longitude latitude member ...]
复制代码
  • key:地理空间集合的名称(如 store:geo);

  • longitude:经度(范围:-180° ~ 180°,超出会报错);

  • latitude:纬度(范围:-90° ~ 90°,超出会报错);

  • member:地理元素的唯一标识(如门店 ID、用户 ID)

示例

# 添加3个门店的经纬度到store:geo中127.0.0.1:6379> GEOADD store:geo 116.39748 39.90882 store_01 116.40748 39.90882 store_02 116.39748 39.91882 store_03(integer) 3  # 成功添加的成员数量
复制代码

GEOSEARCH

支持按半径查询和按矩形范围查询

命令语法

GEOSEARCH key   [FROMMEMBER member]  # 以已有成员为中心  [FROMLONLAT lon lat] # 以经纬度为中心  BYRADIUS radius m|km|mi|ft  # 按半径查询(二选一)  [BYBOX width height m|km|mi|ft]  # 按矩形范围查询(二选一,宽高为矩形的尺寸)  [ASC|DESC]  # 排序  [COUNT count]  # 限制数量  [WITHCOORD] [WITHDIST] [WITHHASH]  # 返回附加信息
复制代码

示例

# 1. 按经纬度为中心,1km半径查询127.0.0.1:6379> GEOSEARCH store:geo FROMLONLAT 116.39748 39.90882 BYRADIUS 1 km WITHDIST ASC1) 1) "store_01"   2) "0.0000"2) 1) "store_02"   2) "0.8439"
# 2. 按已有成员为中心,1km半径查询127.0.0.1:6379> GEOSEARCH store:geo FROMMEMBER store_01 BYRADIUS 1 km WITHDIST ASC1) 1) "store_01"   2) "0.0000"2) 1) "store_02"   2) "0.8439"
# 3. 按矩形范围查询(宽1km,高1km)127.0.0.1:6379> GEOSEARCH store:geo FROMLONLAT 116.39748 39.90882 BYBOX 1 1 km WITHDIST ASC
复制代码

请看以下的实现案例~

附近的人 / 好友

APP 显示 “附近 1km 内的用户”,支持按距离排序

主要涉及的命令

  • GEOADD

  • GEOSearch

实现步骤

  • 用户登录时,调用 GEOADD user:geo 经度 纬度 userID 上传经纬度

  • 用户点击 附近的人,调用 GEOSearch user:geo 我的经度 我的纬度 1 km ASC COUNT 20,查询 1km 内的 20 个用户,按距离升序返回;

附近的门店 / 商家

外卖 / 生鲜 APP 显示 “附近 3km 内的超市”,支持按距离排序

主要涉及的命令

  • GEOADD

  • GEOSearch

实现步骤

  • 商家入驻时,调用 GEOADD store:geo 经度 纬度 storeID 存储门店经纬度;

  • 用户端定位后,调用 GEOSearch store:geo 用户经度 用户纬度 3 km ASC,返回附近门店列表;

  • 结合业务数据(如评分、配送费)二次排序

物流轨迹定位

实时显示快递员的位置,查询 “配送范围内的快递员”

实现步骤

  • 快递员的设备定时(如 5 秒)调用 GEOADD courier:geo 经度 纬度 courierID 更新位置;

  • 用户查询时,调用 GEOSearch courier:geo 用户经度 用户纬度 1 km,获取附近 1km 内的快递员;

  • 结合快递员的配送状态(空闲 / 忙碌)过滤结果

HyperLogLog

简称 HLL,是用于基数统计的概率型数据结构

核心特性

统计集合中不重复元素的数量

  • 基数统计:只关心 集合中有多少个不重复元素,不关心具体是哪些元素;

  • 内存高效:无论统计的元素数量是百万级还是亿级,仅占用 12KB 内存;

  • 概率统计非精确统计,标准误差为 0.81%,可满足大部分非精确统计场景;

  • 支持合并:多个 HyperLogLog 可以合并为一个,用于统计跨维度的基数(如周 UV = 每日 UV 的合并)

适合场景

适合不需要精确统计追求内存效率的场景

  • 网站 / APP 的 UV 统计

  • 页面独立 IP 访问数统计

核心命令

PFADD:添加元素

  • 语法:PFADD key element [element ...]

  • 作用:将一个或多个元素添加到指定的 HyperLogLog 中(若 key 不存在,会自动创建);

  • 返回值:1 表示 HyperLogLog 的内部结构发生了更新,0 表示无更新(元素已存在或未改变结构)

示例(日 UV 统计)

# 向uv:20251211(12月11日的UV)中添加3个用户ID127.0.0.1:6379> PFADD uv:20251211 user_1001 user_1002 user_1003(integer) 1
# 重复添加user_1001,无更新127.0.0.1:6379> PFADD uv:20251211 user_1001(integer) 0
复制代码

PFCOUNT:统计基数

  • 语法:PFCOUNT key [key ...]

  • 作用:统计单个或多个 HyperLogLog 的基数(若传入多个 key,会先合并再统计基数);

  • 返回值:基数的估算值;

示例(统计日 UV)

# 统计12月11日的UV127.0.0.1:6379> PFCOUNT uv:20251211(integer) 3  # 对应之前添加的3个独立用户
# 再添加一个用户,重新统计127.0.0.1:6379> PFADD uv:20251211 user_1004(integer) 1127.0.0.1:6379> PFCOUNT uv:20251211(integer) 4
复制代码


总结

今天主要介绍了一系列的进阶的数据类型,以及 Redisson 的妙用~


主要希望能挖掘一些比较不错的功能点,加在项目上~


接下来会继续深挖 Redis 其他模块的功能点和底层原理~


如果觉得帮到你们,烦请 点赞关注推荐 三连,感谢感谢~~


熟练度刷不停,知识点吃透稳,下期接着练~

发布于: 刚刚阅读数: 3
用户头像

DonaldCen

关注

有个性,没签名 2019-01-13 加入

跟我在峡谷学Java 公众号:程序员悟空的宝藏乐园

评论

发布
暂无评论
Java 王者修炼手册【Redis 篇 - 进阶数据类型】:吃透布隆过滤器 / Redisson 分布式锁 / Geo/Bitmap/HLL 核心原理与实战场景_布隆过滤器_DonaldCen_InfoQ写作社区