Redis中的10种高级用法,直接起飞!

大家好,我是苏三,又跟大家见面了。

前言

在很多小伙伴的印象中,Redis就是一个用来做缓存的工具。

但如果你只把它当作缓存,那真是"杀鸡用牛刀"了。

Redis的强大远不止于此,它的高级特性可以让你的系统在性能、扩展性、可靠性上直接起飞。

今天这篇文章就跟大家聊聊Redis中10种高级用法,希望对你会有所帮助。

更多项目实战在项目实战网:java突击队

一、布隆过滤器

有些小伙伴在做高并发系统时,最怕的就是缓存穿透------大量请求直接打穿缓存,压垮数据库。

布隆过滤器就是解决这个问题的利器。

1.1 什么是布隆过滤器

布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于一个集合中。

它的特点是:

  • 极省内存:存储1亿个元素,只需要约100MB内存
  • 存在误判:判断"不在"一定准确;判断"在"可能误判(小概率)
  • 不可删除:不支持删除元素

1.2 原理

布隆过滤器的原理如图:

查询数据的流程如下图:

1.3 代码示例(使用Redisson)

typescript 复制代码
@Component
publicclass BloomFilterService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    private RBloomFilter<String> bloomFilter;
    
    @PostConstruct
    public void init() {
        bloomFilter = redissonClient.getBloomFilter("user:bloom");
        // 初始化:预计插入100万条数据,误判率0.01
        bloomFilter.tryInit(1000000L, 0.01);
    }
    
    // 添加白名单数据
    public void addUser(Long userId) {
        bloomFilter.add(userId.toString());
    }
    
    // 查询前进行拦截
    public User getUserById(Long userId) {
        // 如果不在布隆过滤器中,直接返回空,避免查库
        if (!bloomFilter.contains(userId.toString())) {
            returnnull;
        }
        // 存在则查库(缓存兜底)
        return queryFromDB(userId);
    }
}

优点:内存占用极小,能有效防止缓存穿透。

缺点:存在误判,不支持删除(如需删除可用计数布隆过滤器)。

适用场景:防止恶意请求穿透缓存、垃圾邮件过滤、爬虫URL去重。

二、Redisson分布式锁

Redis实现分布式锁,很多人还在用SET NX EX,但这在复杂场景下存在诸多隐患。

Redisson的分布式锁提供了更完善的解决方案。

2.1 为什么不用SET NX

简单SET NX的问题:

  • 锁过期时间设置不当,可能导致锁提前释放
  • 释放锁时未校验持有者,可能误删他人锁
  • 无法自动续期,长任务执行一半锁就没了

2.2 Redisson分布式锁原理

Redisson使用Lua脚本保证原子性,并内置了看门狗机制自动续期。

2.3 代码示例

scss 复制代码
@Service
publicclass OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public void processOrder(String orderId) {
        RLock lock = redissonClient.getLock("order:lock:" + orderId);
        
        try {
            // 尝试加锁,最多等待10秒,锁有效期30秒(自动续期)
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                // 执行业务逻辑
                doProcess(orderId);
            } else {
                thrownew RuntimeException("获取锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            thrownew RuntimeException("中断", e);
        } finally {
            // 必须释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

优点:自动续期,避免死锁;可重入;支持读写锁、红锁等。

缺点:引入Redisson依赖,比原生Redis略重。

适用场景:分布式任务调度、库存扣减、订单创建等需要互斥的场景。

三、Redisson延迟队列

很多场景需要延迟处理,比如订单30分钟未支付自动取消。

传统方案用定时任务扫表,效率低下且存在延迟。

Redisson的延迟队列基于Redis的Sorted Set实现,精准可靠。

3.1 原理图

3.2 代码示例

typescript 复制代码
@Component
public class DelayQueueService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    private RBlockingQueue<Order> blockingQueue;
    private RDelayedQueue<Order> delayedQueue;
    
    @PostConstruct
    public void init() {
        blockingQueue = redissonClient.getBlockingQueue("order:queue");
        delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
    }
    
    // 添加延迟任务
    public void addOrder(Order order, long delay, TimeUnit unit) {
        delayedQueue.offer(order, delay, unit);
    }
    
    // 消费者(单独线程)
    @Async
    public void startConsumer() {
        while (true) {
            try {
                Order order = blockingQueue.take();
                // 处理延迟到期的订单
                processExpiredOrder(order);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

优点:精准定时、分布式、自动持久化。

缺点:依赖Redis,消息消费失败需自行补偿。

适用场景:订单超时关闭、延迟通知、定时任务调度。

四、令牌桶限流

面对突发流量,限流是保护系统的关键。

Redis + Lua可以实现高性能的令牌桶算法。

4.1 令牌桶原理

4.2 Lua脚本实现

swift 复制代码
@Component
publicclass RateLimiterService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    privatestaticfinal String LUA_SCRIPT = 
        "local key = KEYS[1]\n" +
        "local limit = tonumber(ARGV[1])\n" +
        "local interval = tonumber(ARGV[2])\n" +
        "local current = redis.call('get', key)\n" +
        "if current and tonumber(current) >= limit then\n" +
        "    return 0\n" +
        "else\n" +
        "    redis.call('incr', key)\n" +
        "    redis.call('expire', key, interval)\n" +
        "    return 1\n" +
        "end";
    
    public boolean tryAcquire(String key, int limit, int intervalSec) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(key), limit, intervalSec);
        return result != null && result == 1L;
    }
}

优点:支持平滑突发流量,实现简单。

缺点:需要结合滑动窗口做更精细的限流。

适用场景:API限流、防刷、秒杀入口控制。

五、位图统计

位图(Bitmap)是Redis中一种非常高效的数据结构,适合统计布尔型数据。

它适合做:海量数据的极简统计。

5.1 应用场景

  • 统计日活用户:用用户ID作为偏移量,1表示活跃
  • 签到记录:一年365天,一个用户只需365个bit

5.2 代码示例

typescript 复制代码
@Component
publicclass BitmapService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 用户签到
    public void signIn(Long userId, LocalDate date) {
        String key = "sign:" + date.toString();
        redisTemplate.opsForValue().setBit(key, userId, true);
    }
    
    // 统计某天签到人数
    public Long countSignIn(LocalDate date) {
        String key = "sign:" + date.toString();
        return redisTemplate.execute(
            (RedisCallback<Long>) connection -> connection.bitCount(key.getBytes())
        );
    }
    
    // 统计连续签到天数(需结合Lua)
    public Long continuousSignDays(Long userId, LocalDate date) {
        // 使用BITFIELD命令获取连续签到天数
        // 实现略
    }
}

优点:内存占用极低(1亿用户只需12MB),运算速度快。

缺点:只能表示0/1状态,不适合复杂统计。

适用场景:用户签到、在线状态、布隆过滤器实现。

六、HyperLogLog

它是去重统计的"省内存神器"。

如果需要统计UV(独立访客),但数据量极大(千万级),用Set会占用大量内存。

HyperLogLog是解决方案。

6.1 原理

HyperLogLog是一种概率算法,用固定内存(约12KB)统计海量数据的基数,误差在0.81%左右。

6.2 代码示例

typescript 复制代码
@Component
publicclass UVService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 添加访问记录
    public void addVisit(String date, String userId) {
        String key = "uv:" + date;
        redisTemplate.opsForHyperLogLog().add(key, userId);
    }
    
    // 统计UV
    public Long countUV(String date) {
        String key = "uv:" + date;
        return redisTemplate.opsForHyperLogLog().size(key);
    }
    
    // 合并多天UV(如周活)
    public Long countWeeklyUV(List<String> dates) {
        String destKey = "uv:weekly:" + LocalDate.now();
        for (String date : dates) {
            redisTemplate.opsForHyperLogLog().union(destKey, "uv:" + date);
        }
        return redisTemplate.opsForHyperLogLog().size(destKey);
    }
}

优点:固定内存,适合超大规模去重。

缺点:不精确,不能单独判断某个元素是否存在。

适用场景:UV统计、网站访问量、大规模去重。

七、GEO地理位置

它在附近的人、门店查询功能中常用。

Redis 3.2开始支持地理位置功能,基于Sorted Set实现,可以轻松计算距离、搜索附近位置。

7.1 原理

使用GeoHash编码经纬度,以Score形式存储,支持按半径、按矩形范围搜索。

7.2 代码示例

typescript 复制代码
@Component
publicclass GeoService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 添加门店位置
    public void addStore(Long storeId, double lng, double lat) {
        String key = "stores:geo";
        redisTemplate.opsForGeo().add(key, new Point(lng, lat), storeId.toString());
    }
    
    // 搜索附近门店
    public List<Store> findNearbyStores(double lng, double lat, double radiusKm) {
        String key = "stores:geo";
        Circle circle = new Circle(new Point(lng, lat), new Distance(radiusKm, Metrics.KILOMETERS));
        GeoResults<RedisGeoCommands.GeoLocation<Object>> results = 
            redisTemplate.opsForGeo().radius(key, circle);
        
        List<Store> stores = new ArrayList<>();
        for (GeoResult<RedisGeoCommands.GeoLocation<Object>> result : results) {
            // 解析结果,构造Store对象
        }
        return stores;
    }
    
    // 计算两点距离
    public Distance distanceBetweenStores(Long storeId1, Long storeId2) {
        String key = "stores:geo";
        return redisTemplate.opsForGeo().distance(key, storeId1.toString(), storeId2.toString());
    }
}

优点:功能丰富,支持半径、矩形搜索,计算距离准确。

缺点:精度受GeoHash影响,索引更新需要重建。

适用场景:外卖骑手派单、附近店铺推荐、打车匹配。

八、Stream消息队列

Redis 5.0引入的Stream是一种强大的消息队列数据结构,支持消费组、消息确认、历史回溯等特性,可替代部分场景下的MQ。

它是轻量级MQ的替代方案。

8.1 架构图

8.2 代码示例(使用Redisson)

typescript 复制代码
@Component
publicclass StreamService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    private RStream<String, String> stream;
    
    @PostConstruct
    public void init() {
        stream = redissonClient.getStream("order-stream");
        // 创建消费组(如果不存在)
        stream.createGroup("order-group", StreamMessageId.AUTO);
    }
    
    // 生产者
    public void publish(String orderId) {
        Map<String, String> data = new HashMap<>();
        data.put("orderId", orderId);
        data.put("timestamp", String.valueOf(System.currentTimeMillis()));
        stream.add(data);
    }
    
    // 消费者(批量拉取)
    @Async
    public void consume() {
        while (true) {
            Map<StreamMessageId, Map<String, String>> messages = 
                stream.readGroup("order-group", "consumer-1", 10);
            for (Map.Entry<StreamMessageId, Map<String, String>> entry : messages.entrySet()) {
                // 处理消息
                process(entry.getValue());
                // 确认消费
                stream.ack("order-group", entry.getKey());
            }
            // 无消息时短暂休眠
            if (messages.isEmpty()) {
                try { Thread.sleep(1000); } catch (InterruptedException e) { break; }
            }
        }
    }
}

优点:支持消息持久化、消费组、消息确认、消息回溯。

缺点:相比专业MQ,功能较简单,适合轻量级场景。

适用场景:任务队列、日志收集、消息通知。

九、Lua脚本

有些复杂操作需要多个Redis命令原子执行,Lua脚本是最佳方案。

9.1 典型场景

  • 扣减库存:先检查库存,再扣减,防止超卖
  • 设置带条件的锁
  • 复杂数据聚合

9.2 代码示例

typescript 复制代码
@Component
publicclass LuaScriptService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // Lua脚本:库存扣减
    privatestaticfinal String DECREASE_STOCK_SCRIPT = 
        "local key = KEYS[1]\n" +
        "local count = tonumber(ARGV[1])\n" +
        "local stock = redis.call('get', key)\n" +
        "if not stock or tonumber(stock) < count then\n" +
        "    return 0\n" +
        "else\n" +
        "    redis.call('decrby', key, count)\n" +
        "    return 1\n" +
        "end";
    
    public boolean decreaseStock(String productId, int count) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(DECREASE_STOCK_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList("stock:" + productId), count);
        return result != null && result == 1L;
    }
    
    // 带超时的分布式锁
    privatestaticfinal String LOCK_SCRIPT = 
        "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then\n" +
        "    redis.call('expire', KEYS[1], ARGV[2])\n" +
        "    return 1\n" +
        "else\n" +
        "    return 0\n" +
        "end";
    
    public boolean lock(String key, String value, int expireSec) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(key), value, expireSec);
        return result != null && result == 1L;
    }
}

优点:原子性、网络传输少、性能高。

缺点:调试困难,脚本需谨慎编写。

适用场景:库存扣减、限流、复杂条件更新。

十、RedisJSON

RedisJSON模块支持将JSON文档直接存储在Redis中,并可对文档内的字段进行增删改查。

10.1 安装

RedisJSON需要作为模块加载(Redis Stack已包含),或单独编译安装。

10.2 代码示例(使用Lettuce)

typescript 复制代码
@Component
publicclass RedisJsonService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 存储JSON对象
    public void saveUser(Long userId, User user) {
        String key = "user:" + userId;
        redisTemplate.opsForValue().set(key, user);
        // 实际上RedisJSON需要专门命令,这里演示思路,真实使用时需使用Lettuce的JSON命令集
    }
    
    // 更新字段
    public void updateUserAge(Long userId, int age) {
        // 使用JSON.SET命令
        // redisTemplate.execute(connection -> connection.execute("JSON.SET", key, "$.age", String.valueOf(age)));
    }
}

优点:直接操作JSON内部字段,支持索引和查询(配合RediSearch)。

缺点:需要额外安装模块,内存占用较高。

适用场景:用户配置、会话信息、动态表单。

更多项目实战在项目实战网:java突击队

总结

以上10种高级用法,涵盖了缓存之外Redis的诸多强大能力:

用法 核心优势 典型场景
布隆过滤器 内存极省,防穿透 缓存击穿防护、垃圾邮件
Redisson分布式锁 自动续期,可重入 分布式互斥
延迟队列 精准定时 订单超时、延迟通知
令牌桶限流 平滑突发流量 API限流
位图 极省内存 签到、在线状态
HyperLogLog 固定内存 UV统计
GEO 地理位置搜索 附近的人
Stream 轻量MQ 任务队列
Lua脚本 原子操作 库存扣减
RedisJSON 文档存储 用户配置

在实际项目中,合理组合这些高级特性,可以让你用极小的成本实现高性能、高可用的分布式系统。

需要注意的是,Redis依然是内存数据库,数据量大的场景要考虑内存成本,必要时结合持久化方案。

希望这篇文章能帮你打开Redis高级用法的大门。

如果你有更好的Redis实践,欢迎评论区分享!

更多项目实战在项目实战网:java突击队

相关推荐
清汤饺子2 小时前
Spec Kit:让 AI 编程从 Vibe Coding 到 Spec First
前端·javascript·后端
三分恶2 小时前
序章:夜话江湖路
后端
Sammyyyyy2 小时前
Node.js、Bun 与 Deno,2026 年后端运行时选择指南
前端·后端·node.js·servbay
终生成长者2 小时前
视频链接生成工具
后端·测试工具
weixin_408099672 小时前
跨境电商OCR:3秒识别多语言商品标签
开发语言·图像处理·人工智能·后端·ocr·api·文字识别ocr
Lyyaoo.2 小时前
Spring中Bean的作用域与生命周期
java·后端·spring
RemainderTime3 小时前
基于 Spring AI + DeepSeek:构建AI Agent 企业级服务与底层原理解析
人工智能·后端·spring·ai