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

0 阅读9分钟

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

前言

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

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

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

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

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

一、布隆过滤器

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

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

1.1 什么是布隆过滤器

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

它的特点是:

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

1.2 原理

布隆过滤器的原理如图:

image.png

查询数据的流程如下图:

image.png

1.3 代码示例(使用Redisson)

@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脚本保证原子性,并内置了看门狗机制自动续期。

image.png

2.3 代码示例

@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 原理图

image.png

3.2 代码示例

@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 令牌桶原理

image.png

4.2 Lua脚本实现

@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 代码示例

@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 代码示例

@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 代码示例

@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 架构图

image.png

8.2 代码示例(使用Redisson)

@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 代码示例

@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)

@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突击队