Redis:高级数据结构与进阶特性(Bitmaps、HyperLogLog、GEO、Pub/Sub、Stream、Lua、Module)

一、高级数据结构

(一)位图(Bitmaps)

Bitmap 是基于 String 的按位操作数据结构 ,它将字符串视为一串连续的二进制位(bit:0 或 1)。使用 Bitmap 时,一个用户/对象的状态只需要 1 bit 即可表示,因此 极大节省内存空间

例如:1 字节(byte) = 8 位(bits),存储 800 万个用户的状态信息只需要约 1 MB 空间。


1. 基本操作

(1)SETBIT key offset value

将 key 对应字符串的第 offset 位设置为 value(0 或 1)

时间复杂度:O(1)

示例:

复制代码
SETBIT user:sign:20251212 10086 1

表示用户 ID=10086 在 2025-12-12 当天签到。


(2)GETBIT key offset

获取 key 的第 offset 位的值(0 或 1)

时间复杂度:O(1)

示例

复制代码
GETBIT user:sign:20251212 10086

判断该用户当天是否签到。


(3)BITCOUNT key [start end]

统计指定位区间内值为 1 的数量

用于统计活跃用户数、签到总人数

时间复杂度:O(n)(n 为字节数,而非 bit 数)

示例:

复制代码
BITCOUNT user:sign:20251212

统计当天总签到人数。


(4)BITPOS key bit [start end]

查找第一个出现 bit(0 或 1)的位置

时间复杂度:O(n)

示例:

复制代码
BITPOS user:sign:20251212 1

返回第一个签到用户的 ID。


(5)BITOP operation destkey key1 [key2 ...]

对多个 bitmap 做按位运算:AND / OR / XOR / NOT

并把结果写入 destkey

示例:统计 3 天内至少签到过一次的用户

复制代码
BITOP OR user:sign:3days user:sign:20251210 user:sign:202501211 user:sign:20251212
BITCOUNT user:sign:3days

2. 应用场景:

Bitmap 非常适合记录 二元状态(是/否、1/0) 的场景。

(1)用户签到系统

每天一个 bitmap

offset = 用户 ID

value = 是否签到(1/0)

查询:

某用户是否签到 → GETBIT

当天签到人数 → BITCOUNT

连续签到天数 → 多天 bitmap 结合 BITOP


(2)活跃用户统计(UV 去重但不记录具体用户)

UV = Unique Visitor(独立访客)

意思是:

一天(或某个时间段)内,有多少个"不同的用户"访问过网站。

记录用户是否访问过

比 Set 节省数十倍空间

示例:

复制代码
SETBIT site:active:20251212 2888888 1

(3)用户行为统计(如是否点赞/收藏过某视频)

视频 ID → key

用户 ID → offset

示例:

复制代码
SETBIT video:like:1001 10086 1

判断是否点赞:

复制代码
GETBIT video:like:1001 10086

(4)权限位、任务完成度存储

使用 Bitfield(位域)存储多个任务状态组合

如某角色权限由多个二进制位组合而成


3. 位图的优点与限制

优点:

极高的存储效率:一个用户只需要 1 bit

高性能:bit 操作 O(1)

可进行位间运算(OR / AND / XOR)

易用于用户量数千万级的状态统计

限制:

offset 必须是整数类型

无法存储复杂数据,仅 0/1 信息

不适合存储稀疏 ID(ID 过大导致内存浪费)


4. Java(Spring Data Redis)示例代码

设置用户签到(setbit)

java 复制代码
stringRedisTemplate.opsForValue().setBit("sign:20251212", userId, true);

查询是否签到(getbit)

java 复制代码
Boolean isSign = stringRedisTemplate.opsForValue().getBit("sign:20251212", userId);

统计签到总人数(bitcount)

java 复制代码
Long count = redisTemplate.execute(
        (RedisCallback<Long>) conn -> conn.bitCount("sign:20251212".getBytes())
);

Bitmap 是一种基于 String 的高效二进制位操作数据结构,常用于存储百万级用户的二元状态数据(如签到、活跃、行为记录等)。它通过 SETBIT、GETBIT、BITCOUNT、BITPOS、BITOP 等指令,实现了极高效的状态操作与统计能力,同时极大节省内存,是 Redis 中最重要的高级数据结构之一。


(二)超日志(HyperLogLog)

HyperLogLog(HLL)是一种用于"基数统计"的概率算法------它的核心作用是:

在极小的空间内,以允许少量误差的方式,统计海量数据的"唯一元素数量"(Distinct Count、UV)。

它不是 Redis 特有,而是 Redis 内置了该算法并提供了非常高效的 API。


1. HyperLogLog 的特点

(1)空间极小

无论你向 HyperLogLog 添加 100 条、100 万条甚至 10 亿条元素,它的空间占用都非常稳定:

最小几百字节(稀疏)

最大 12 KB(稠密)

即便统计数 接近 2^64(约 1.8e19) 个不同元素,也只需要 12KB

这比使用 Set 保存所有去重元素节省了至少 数百倍以上的空间。


(2)允许小误差(约 0.81%)

HyperLogLog 是"概率估算",不是绝对准确统计。

误差率大约是 0.81%

对 UV(独立访客)这种场景完全够用

例如:

真实值 HLL 计算值
100,000 99,300
1,000,000 1,007,600

(3)自动在稀疏模式与稠密模式之间切换

Redis 的 HLL 有两种内部存储结构:

① 稀疏矩阵(Sparse)

当数据量小

占用空间仅几百字节,不到 1KB

② 稠密矩阵(Dense)

当计数达到某阈值

自动转换为 固定的 12 KB 存储结构

这个切换对用户完全透明。


2. 基本操作

(1)PFADD key element [element ...]

向 HLL 中添加一个或多个元素。

java 复制代码
PFADD page:uv:20251212 192.168.1.1
PFADD page:uv:20251212 192.168.1.2 192.168.1.3

Redis 不会存储这些元素本身,只会用这些值参与算法计算。


(2)PFCOUNT key [key ...]

计算一个或多个 HyperLogLog 的唯一值数量(基数)。

单个:

java 复制代码
PFCOUNT page:uv:20251212

多个(求并集基数):

java 复制代码
PFCOUNT page:uv:20251210 page:uv:20251211 page:uv:20251212

等价于 "三天 UV 去重后总人数"。


(3)PFMERGE destkey sourcekey [sourcekey ...]

将多个 HLL 合并成一个 HLL(不丢失信息)。

java 复制代码
PFMERGE page:uv:week page:uv:20251210 page:uv:20251211 page:uv:20251212

然后获取合并后的 UV:

java 复制代码
PFCOUNT page:uv:week

3. 应用场景:

HyperLogLog 非常适合需要统计 海量数据 Unique Count(去重计数) 的场景:

(1)UV(独立访客)统计

一个网站每天可能有上千万 IP,但只需要 12KB 就能统计 UV,不用存内容本身。

示例:

java 复制代码
page:uv:20251211
page:uv:20251212
...

(2)统计注册用户数、访问数量、搜索关键词数量

用户访问数做到亿级时,用 Set 会占用大量内存,用 HLL 只要 12KB。


(3)统计各类大规模唯一行为

如:

独立搜索用户数

独立点击数

独立点赞数

活跃用户量(DAU/WAU/MAU)

全站访客数


4. Java(Spring Data Redis)使用示例

添加元素(PFADD)

java 复制代码
stringRedisTemplate.opsForHyperLogLog()
    .add("uv:20251212", "192.168.1.1");

获取计数(PFCOUNT)

java 复制代码
long uv = stringRedisTemplate.opsForHyperLogLog()
    .size("uv:20251212");

合并(PFMERGE)

java 复制代码
stringRedisTemplate.opsForHyperLogLog()
    .union("uv:week", "uv:20251210", "uv:20251211", "uv:20251212");

5. 注意事项

不适合需要取出所有元素、或需要精准列表的场景。

误差为 0.81% 左右,可忽略,但不适用于精确金融业务。

合并操作(PFMERGE)会覆盖目标 key。


HyperLogLog 是一种用于统计大量唯一元素数量的概率算法。

它能在固定约 12 KB 的内存空间内统计接近 2^64 个不同元素,提供 PFADD、PFCOUNT、PFMERGE 这三个核心指令,适用于 UV、IP 访问量、去重计数等场景。


(三) 地理空间(Geospatial / GEO)

Redis 的 GEO(地理空间)功能 用于存储和操作地点的经纬度信息,基于 Sorted Set + Geohash 实现,可以高效地计算距离、查找附近地点,是构建地图、定位、社交及物流类应用的重要功能。


1. GEO 的特点

(1)高效存储

GEO 底层使用 Sorted Set 存储,member 表示地点,score 使用 Geohash 编码。

Geohash 将经纬度映射为整数,存储空间小、查找速度快。

(2)快速距离计算

Redis 内置距离计算算法,可以直接计算两点之间的距离(支持 km、m、mi、ft)。

适合中小半径附近查询。

(3)方便范围查询

支持按圆形(半径)或矩形区域搜索附近地点。

可返回距离、坐标,支持升序或降序排序。


2. 基本操作

(1)GEOADD key longitude latitude member

添加一个或多个地点及经纬度:

java 复制代码
GEOADD city:geo 116.40 39.90 beijing
GEOADD city:geo 121.47 31.23 shanghai 113.27 23.13 guangzhou
(2)GEOPOS key member [member ...]

获取指定地点的经纬度:

java 复制代码
GEOPOS city:geo beijing

(3)GEODIST key member1 member2 [unit]

计算两个地点之间的距离,单位可选 km、m、mi、ft:

java 复制代码
GEODIST city:geo beijing shanghai km

(4)GEOSEARCH key FROMMEMBER|FROMLONLAT BYRADIUS|BYBOX ... [WITHDIST] [WITHCOORD] [ASC|DESC] [COUNT count]

查找指定位置附近的地点,可返回距离、坐标并排序:

java 复制代码
GEOSEARCH city:geo 
    FROMMEMBER beijing
    BYRADIUS 300 km
    WITHDIST WITHCOORD ASC

(5)GEOSEARCHSTORE destkey sourcekey ...

查找附近地点并保存到新的 key:

java 复制代码
GEOSEARCHSTORE near_beijing city:geo
    FROMMEMBER beijing
    BYRADIUS 300 km

(6)ZSet 操作(因为 GEO 底层是 Sorted Set)

删除地点:

java 复制代码
ZREM city:geo beijing

查看所有地点:

java 复制代码
ZRANGE city:geo 0 -1

3. 应用场景

(1)社交 APP 附近的人

查询用户 1km/3km 内的好友或用户。

(2)附近门店 / 商铺搜索

外卖、零售、地图应用查找用户周边门店。

(3)物流与配送

查找离用户最近的配送中心或骑手。

(4)共享交通

查找附近可用的共享单车、共享汽车。

(5)地图检索

查询周边景点、餐馆、服务设施。


4. Java(Spring Data Redis)使用示例

(1)添加地点(GEOADD)

java 复制代码
redisTemplate.opsForGeo().add(
    "city:geo",
    new Point(116.40, 39.90), // 北京经纬度
    "beijing"
);

(2)获取坐标(GEOPOS)

java 复制代码
List<Point> pos = redisTemplate.opsForGeo().position("city:geo", "beijing");

(3)计算距离(GEODIST)

java 复制代码
Distance dist = redisTemplate.opsForGeo()
    .distance("city:geo", "beijing", "shanghai", RedisGeoCommands.DistanceUnit.KILOMETERS);
System.out.println("北京到上海距离:" + dist.getValue() + " km");

(4)查附近地点(GEOSEARCH)

java 复制代码
GeoResults<RedisGeoCommands.GeoLocation<String>> results =
    redisTemplate.opsForGeo().search(
        "city:geo",
        RedisGeoCommands.GeoReference.fromMember("beijing"),
        new Distance(300, Metrics.KILOMETERS),
        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
            .includeCoordinates()
            .includeDistance()
            .sortAscending()
    );

for (GeoResult<RedisGeoCommands.GeoLocation<String>> r : results) {
    System.out.println(r.getContent().getName() + " -> " + r.getDistance().getValue() + "km");
}

(5)查附近并保存结果(GEOSEARCHSTORE)

java 复制代码
Long count = redisTemplate.opsForGeo().searchAndStore(
    "near_beijing", "city:geo",
    RedisGeoCommands.GeoReference.fromMember("beijing"),
    new Distance(300, Metrics.KILOMETERS),
    RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
);
System.out.println("保存数量:" + count);

(6)删除地点 / 查看所有地点

java 复制代码
redisTemplate.opsForZSet().remove("city:geo", "beijing");
Set<String> members = redisTemplate.opsForZSet().range("city:geo", 0, -1);

5. 注意事项

GEO 精度有限,适合中小半径范围查询。

大量频繁更新位置可能导致写操作开销增加。

不适合需要获取完整成员列表或复杂 GIS 计算的场景。

GEOSEARCHSTORE 会覆盖目标 key。


Redis GEO 利用 Sorted Set + Geohash 实现地理位置存储和操作,提供 GEOADD、GEOPOS、GEODIST、GEOSEARCH、GEOSEARCHSTORE 等命令,可实现距离计算、附近搜索、范围统计等功能。结合 Spring Data Redis,Java 开发者可以轻松实现社交、物流、地图及共享交通类应用。


(四)发布/订阅(Pub/Sub)

Redis 的 发布/订阅(Pub/Sub)机制 是一种消息通信模式,允许一个或多个发布者向频道发送消息,同时订阅者接收该频道的消息,实现实时通信


1. Pub/Sub 的特点

Redis 发布/订阅是一种消息传模式,其中发布者发送消息,而订阅者接收消息,传递消息的通道称为channel。

(1)实时性高

消息发布后,订阅者几乎立即接收到消息,适合实时通信场景。

(2)无需持久化

Redis Pub/Sub 不存储消息,订阅者必须在线才能接收到消息。

如果订阅者不在线,将无法收到历史消息。

(3)灵活的订阅模式

支持精确频道订阅,也支持模式匹配订阅(如 news.*)。

(4)轻量级通信

Redis 内部通过事件驱动和发布/订阅机制实现消息分发,无需额外消息队列系统。


2. 基本操作

命令 说明
SUBSCRIBE channel [channel ...] 订阅一个或多个频道的消息。
UNSUBSCRIBE channel [channel ...] 退订一个或多个频道。
PSUBSCRIBE pattern [pattern ...] 订阅匹配指定模式的频道。
PUNSUBSCRIBE pattern [pattern ...] 退订指定模式的频道。
PUBLISH channel message 将消息发布到指定频道。
PUBSUB 查看发布/订阅系统状态,如频道数、订阅者数。

3. Java(Spring Data Redis / Jedis)使用示例

(1)使用 Spring Data Redis 订阅消息

java 复制代码
@Component
public class RedisSubscriber implements MessageListener {
    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println("收到消息: " + message.toString());
    }
}
java 复制代码
@Autowired
private RedisMessageListenerContainer listenerContainer;

@Autowired
private RedisSubscriber subscriber;

public void subscribeChannel() {
    listenerContainer.addMessageListener(subscriber, new ChannelTopic("chat:room1"));
}

(2)发布消息

java 复制代码
@Autowired
private StringRedisTemplate redisTemplate;

public void publishMessage() {
    redisTemplate.convertAndSend("chat:room1", "Hello Redis Pub/Sub!");
}

(3)模式订阅(PSUBSCRIBE)

java 复制代码
listenerContainer.addMessageListener(subscriber, new PatternTopic("news.*"));

4. 应用场景

(1)实时聊天系统

用户在聊天室发送消息,通过 Redis Pub/Sub 广播给所有在线订阅者。

(2)通知系统

社交平台的新评论、新点赞、任务提醒等通知,实时推送给订阅者。

(3)分布式缓存更新通知

当缓存被更新或失效时,通过 Pub/Sub 通知各个服务刷新本地缓存。

(4)日志和事件分发

系统日志或事件产生后,广播给多个消费者进行处理。


5. 注意事项

**消息不持久化:**订阅者不在线时,无法接收到消息。

**不适合复杂消息队列:**无法保证消息顺序和消息丢失重试。

**订阅者阻塞:**传统 Jedis 的 subscribe 方法会阻塞线程,需使用独立线程或消息监听器容器。

**大规模发布:**大量频道和订阅者可能对 Redis 性能产生压力,适合中小型实时消息场景。


Redis Pub/Sub 提供了轻量级实时消息分发机制 ,通过 PUBLISH / SUBSCRIBE / PSUBSCRIBE 等命令,实现发布者和订阅者之间的消息通信。结合 Spring Data RedisJedis,Java 开发者可以快速构建聊天室、通知推送、事件广播等应用。


(五)Stream(流式消息队列)

Redis Stream 是 Redis 5.0 引入的 可持久化、可消费的消息队列 ,支持生产者-消费者模式。相比 Pub/Sub,Stream 支持 消息持久化、消费组、多消费者、消息确认,非常适合可靠的消息队列场景。


1. 特点

持久化消息:消息存储在 Redis 内部,可查询历史记录。

多消费者支持:通过 消费组(Consumer Group)实现多消费者协作。

可靠传递:支持消息确认(XACK),防止消息丢失。

自动 ID 生成:每条消息有唯一 ID(如 1607512345678-0),支持范围查询。

顺序保证:消息在 Stream 中按插入顺序保存,可按时间或 ID 读取。


2. 核心命令

命令 说明
XADD key * field value [field value ...] 添加消息到流,自动生成 ID
XREAD [COUNT n] [BLOCK ms] STREAMS key id 消费流数据,可阻塞等待新消息
XGROUP CREATE key groupname id 创建消费组,从指定 ID 开始读取
XREADGROUP GROUP groupname consumer [COUNT n] STREAMS key id 消费组读取消息
XACK key groupname id [id ...] 消费者确认已处理消息
XRANGE key start end [COUNT n] 按范围查询消息,按 ID 升序返回
XREVRANGE key end start [COUNT n] 按范围查询消息,按 ID 降序返回
XDEL key id [id ...] 删除指定消息
XTRIM key [MAXLEN ~ count] 修剪流,限制长度,避免无限增长

3. Java 示例(Spring Data Redis)

(1)添加消息

java 复制代码
Map<String, String> message = new HashMap<>();
message.put("event", "order_created");
message.put("orderId", "1001");

redisTemplate.opsForStream().add("orders:stream", message);

(2)读取消息(普通读取)

java 复制代码
List<MapRecord<String, Object, Object>> messages =
    redisTemplate.opsForStream().read(
        StreamOffset.fromStart("orders:stream")
    );

for (MapRecord<String, Object, Object> record : messages) {
    System.out.println(record.getId() + " -> " + record.getValue());
}

(3)消费组读取

java 复制代码
List<MapRecord<String, Object, Object>> groupMessages =
    redisTemplate.opsForStream().read(
        Consumer.from("orderGroup", "consumer1"),
        StreamOffset.create("orders:stream", ReadOffset.lastConsumed())
    );

for (MapRecord<String, Object, Object> record : groupMessages) {
    System.out.println(record.getId() + " -> " + record.getValue());
    // 确认处理
    redisTemplate.opsForStream().acknowledge("orders:stream", "orderGroup", record.getId());
}

(4)修剪流,限制长度

java 复制代码
redisTemplate.opsForStream().trim("orders:stream", 1000); // 保留最近 1000 条消息

4. 应用场景

消息队列、异步任务处理:订单处理、支付通知、任务分发。

日志收集与处理:集中收集应用日志、审计事件。

实时事件流分析:用户行为分析、点击流处理。

多消费者消费组:类似 Kafka,可实现负载均衡的消息消费。


5. 注意事项与最佳实践

(1)消息 ID 和顺序

消息 ID 自动生成,按插入顺序唯一且可用于范围查询。

消费组读取时,推荐使用 ReadOffset.lastConsumed() 避免重复消费。

(2)阻塞读取

XREAD BLOCK 可用于阻塞读取消息,适合实时处理。

阻塞时间要合理设置,防止线程长时间等待。

(3)流修剪

流无限增长可能占用大量内存,推荐使用 XTRIM 或 MAXLEN ~ count 修剪。

(4)消息确认(XACK)

消费者处理完消息必须调用 XACK 确认,否则消息会保持在待处理列表(PEL)。

可结合定期检查未确认消息重新处理(pending message)保证可靠性。

(5)高并发优化

流式消息在高并发下可能成为瓶颈,可通过多个流或分区(Sharding)分摊负载。


Redis Stream 提供了 可靠、可持久化、支持消费组的消息队列能力 ,结合消费组、阻塞读取和消息确认机制,可替代轻量级消息队列(如 Kafka 或 RabbitMQ)处理部分实时任务、异步事件和日志收集场景。通过 Spring Data Redis,Java 开发者可以快速实现生产者-消费者模式,同时保证消息顺序与可靠性。


(六)Bitfield(位域操作)

Redis Bitfield 是对字符串类型进行位操作的高级接口,可以一次性操作多个位或整数。它通常用于高效的统计、权限管理和状态标记场景。


1. 特点

位级存储:可以将一个字符串当作二进制位数组使用,每个位都可单独操作。

灵活偏移操作:支持对任意偏移量的位或整数字段进行获取、设置、增减操作。

批量操作:一次命令可同时操作多个字段,减少网络开销。

内存节省:相比使用多个 key 或整型字段,Bitfield 占用空间小。

与其他位操作兼容:可结合 BITCOUNT、SETBIT、GETBIT 使用。


2. 核心命令

命令 说明
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] 获取、设置或增减指定偏移量的位或整数字段
BITCOUNT key [start end] 统计位为 1 的数量
SETBIT key offset value 设置单个位值(0 或 1)
GETBIT key offset 获取单个位值(0 或 1)

类型说明(type):

i8、i16、i32 ...:有符号整数

u8、u16、u32 ...:无符号整数

偏移量(offset)以比特为单位。


3. Java 示例(Spring Data Redis)

(1)设置位或整数值

java 复制代码
List<Long> result = redisTemplate.execute((RedisCallback<List<Long>>) connection ->
    connection.bitField("mybit",
        new BitFieldSubCommands()
            .set(BitFieldType.unsigned(8)).valueAt(0).to(100)  // 偏移量 0,8 位无符号整数,值 100
            .set(BitFieldType.unsigned(4)).valueAt(8).to(7)    // 偏移量 8,4 位无符号整数,值 7
    )
);
System.out.println(result);

(2)获取值

java 复制代码
List<Long> getResult = redisTemplate.execute((RedisCallback<List<Long>>) connection ->
    connection.bitField("mybit",
        new BitFieldSubCommands()
            .get(BitFieldType.unsigned(8)).valueAt(0)
    )
);
System.out.println(getResult.get(0));

(3)增减值

java 复制代码
List<Long> incrResult = redisTemplate.execute((RedisCallback<List<Long>>) connection ->
    connection.bitField("mybit",
        new BitFieldSubCommands()
            .incr(BitFieldType.unsigned(8)).valueAt(0).by(5)
    )
);
System.out.println(incrResult.get(0));

4. 应用场景

用户行为统计

签到、点赞、投票等行为记录

可用一个 key 记录整个月的每日签到状态

权限位管理

将用户权限存储为位标志,便于快速检查或修改

小数据量计数

按小时、按天统计状态标志或事件次数

特定事件状态标记

比如任务完成状态、设备开关状态等


5. 注意事项与最佳实践

  1. 偏移量单位为 bit,操作前需明确位数和类型。

  2. 批量操作优先,减少多次命令调用。

  3. 结合 BITCOUNT 使用,可快速统计指定区间内的活跃位。

  4. 注意溢出:

使用 INCRBY 时,如果整数超出类型范围,会发生截断或溢出。

  1. 内存占用:

对大量用户或大量天数的统计,Bitfield 占用更小,比单独的 Key 更高效。


Redis Bitfield 提供位级操作接口,可以把字符串当作二进制位数组,高效进行状态标记、权限控制和行为统计。结合 BITFIELD、BITCOUNT、SETBIT、GETBIT 等命令,Java 开发者可以轻松实现批量位操作和小数据量计数场景。


(七)Lua Script(脚本引擎)

Redis 内置 Lua 脚本引擎 ,可以在 Redis 服务器端执行多条命令逻辑,实现 原子操作、复杂逻辑减少网络开销


1. 特点

原子执行:脚本执行期间,Redis 保证原子性,不会被其他命令打断。

减少网络开销:客户端一次发送 Lua 脚本即可执行多条命令,减少多次请求。

返回复杂结果:支持数组、整数、字符串、布尔值等多种类型。

可重复执行:支持 SHA1 哈希执行脚本,提高效率。

与事务(MULTI/EXEC)互补:Lua 脚本可实现更复杂逻辑,事务仅限命令序列执行。


  1. 核心命令
命令 说明
EVAL script numkeys key [key ...] arg [arg ...] 执行 Lua 脚本
EVALSHA sha1 numkeys key [key ...] arg [arg ...] 通过 SHA1 执行已加载脚本
SCRIPT LOAD script 加载脚本,返回 SHA1 哈希
SCRIPT EXISTS sha1 [sha1 ...] 检查脚本是否存在
SCRIPT FLUSH 删除所有已加载脚本
SCRIPT KILL 中止正在执行的脚本(需谨慎)

3. Java 示例(Spring Data Redis)

(1)简单设置键值

java 复制代码
String lua = "return redis.call('set', KEYS[1], ARGV[1])";
DefaultRedisScript<String> script = new DefaultRedisScript<>(lua, String.class);
String result = redisTemplate.execute(script, Collections.singletonList("mykey"), "myvalue");
System.out.println(result); // 输出 OK

(2)库存扣减示例(原子操作)

java 复制代码
String luaStock = "local stock = tonumber(redis.call('get', KEYS[1])); " +
                  "if stock >= tonumber(ARGV[1]) then " +
                  "  redis.call('decrby', KEYS[1], ARGV[1]); " +
                  "  return 1 " +
                  "else " +
                  "  return 0 " +
                  "end";

DefaultRedisScript<Long> stockScript = new DefaultRedisScript<>(luaStock, Long.class);
Long res = redisTemplate.execute(stockScript, Collections.singletonList("product:1001:stock"), "2");
System.out.println(res == 1 ? "扣减成功" : "库存不足");

(3)返回数组结果

java 复制代码
String luaArray = "return {redis.call('get', KEYS[1]), redis.call('get', KEYS[2])}";
DefaultRedisScript<List> scriptArray = new DefaultRedisScript<>(luaArray, List.class);
List<Object> results = redisTemplate.execute(scriptArray, Arrays.asList("key1", "key2"));
System.out.println(results);

4. 应用场景

原子性操作

库存扣减、抢购秒杀场景

多命令事务优化

通过一次脚本操作完成多条命令,避免事务锁开销

分布式锁实现

Lua 脚本保证释放锁和验证操作的原子性

限流、计数器逻辑

滑动窗口限流、请求计数、每日操作次数统计

批量计算或复杂条件逻辑

比如同时检查多个键值、进行条件修改或返回计算结果


5. 注意事项与最佳实践

(1)执行时间

Lua 脚本执行不可被中断,执行时间过长会阻塞 Redis 其他命令。

脚本建议在毫秒级完成,不适合复杂或耗时计算。

(2)原子性

Lua 脚本本身是原子操作,无需使用 MULTI/EXEC。

(3)避免大返回值

返回大量数据会增加网络和客户端内存压力,尽量返回必要字段或使用分页。

(4)SHA1 与缓存

对重复执行的脚本,先 SCRIPT LOAD,然后使用 EVALSHA 提高效率。

(5)异常处理

脚本执行失败会回滚整个操作,注意捕获异常并处理返回值。


Redis Lua Script 提供了在服务端执行原子化、多命令逻辑的能力,适合 库存扣减、限流、分布式锁、复杂条件操作 等场景。结合 Spring Data Redis,Java 开发者可实现高性能、原子化的业务逻辑,同时减少网络请求次数和事务开销。


(八)Module(模块扩展)

Redis Module 是 Redis 的功能扩展机制,允许第三方开发者或企业在不修改 Redis 核心代码的情况下,添加 新数据类型、命令和功能,极大地增强 Redis 的能力。


1. 特点

自定义数据类型和命令

可以实现 Redis 原生不支持的数据结构,如全文索引、图、JSON 等。

无需修改核心

Redis 本身保持轻量,功能扩展通过模块加载。

支持动态加载/卸载

可以在运行时加载或卸载模块,便于升级和管理。

性能优化

模块可以直接操作 Redis 内部数据结构,高效执行复杂逻辑。

兼容性

模块 API 与 Redis 原生命令兼容,可与现有客户端协作使用。


2. 常用命令

命令 说明
MODULE LOAD path/to/module.so 加载模块
MODULE UNLOAD name 卸载模块
MODULE LIST 查看已加载模块列表
MODULE INFO name 获取模块详细信息
MODULE STATS name 获取模块运行统计信息(部分模块支持)

3. 常见模块示例

模块 功能
RediSearch 提供全文检索索引、模糊搜索、排序、聚合查询
RedisJSON JSON 数据类型支持,可直接存储和查询 JSON 对象
RedisGraph 图数据库,支持节点、边及图查询(类似 Cypher)
RedisTimeSeries 高性能时间序列存储与分析
RedisAI 在 Redis 内运行 AI/机器学习模型
RedisBloom 布隆过滤器、Cuckoo Filter 等概率数据结构扩展

4. Java 使用示例

(1)查看已加载模块

java 复制代码
// Jedis 示例
Jedis jedis = new Jedis("localhost", 6379);
List<Object> modules = jedis.sendCommand("MODULE", "LIST");
System.out.println(modules);

(2)加载模块

java 复制代码
jedis.sendCommand("MODULE", "LOAD", "/usr/local/lib/redisearch.so");

(3)卸载模块

java 复制代码
jedis.sendCommand("MODULE", "UNLOAD", "search");

(4)模块命令示例(RediSearch)

java 复制代码
// 创建索引
jedis.sendCommand("FT.CREATE", "idx:products", "SCHEMA", "name", "TEXT", "price", "NUMERIC");
// 添加文档
jedis.sendCommand("FT.ADD", "idx:products", "doc1", "1.0", "FIELDS", "name", "iPhone", "price", "999");

注意:不同模块提供不同命令,Java 客户端通常使用 sendCommand 或模块专用客户端(如 RediSearch Java client)。


5. 应用场景

高级数据结构扩展

全文检索、图计算、布隆过滤器、时间序列分析

JSON 数据存储与查询

直接存储和操作复杂 JSON 对象,避免对象序列化

时间序列数据分析

IoT 传感器数据、日志指标、金融行情数据

自定义业务逻辑扩展

例如在 Redis 内部实现复杂算法或微服务逻辑,减少客户端负担


6. 注意事项与最佳实践

(1)模块兼容性

不同 Redis 版本对模块 API 支持可能不同,升级 Redis 时需检查模块兼容性。

(2)模块安全

加载模块需要操作系统权限,确保模块来源可信。

(3)资源限制

模块可能占用内存或 CPU,要根据业务量合理评估。

(4)模块专用客户端

对于复杂模块(如 RediSearch、RedisJSON),使用模块提供的 Java 客户端更方便。

(5)动态加载/卸载

可用于测试或升级,但生产环境中卸载模块需谨慎,避免丢失数据。


Redis Module 提供了 Redis 核心功能的可扩展性,使 Redis 能够处理更复杂的场景,如全文检索、图数据库、时间序列和 JSON 存储。Java 开发者可以通过 Jedis、Lettuce 或模块专用客户端调用模块命令,实现自定义业务逻辑和高级数据处理,同时保持 Redis 高性能和轻量特性。

相关推荐
xiangzhihong82 小时前
Windows环境下安装使用Redis
数据库·windows·redis
嘟嘟w2 小时前
双亲委派的概念
java·后端·spring
SunnyDays10112 小时前
如何在 Java 中将 RTF 转换为 PDF (含批量转换)
java·rtf转pdf
IT_Octopus2 小时前
java <T> 是什么?
java·开发语言
islandzzzz2 小时前
从0开始的SQL表DDL学习(基础语法结构、索引/约束关键字)
数据库·sql·学习
月明长歌2 小时前
【码道初阶】Leetcode面试题02.04:分割链表[中等难度]
java·数据结构·算法·leetcode·链表
如竟没有火炬2 小时前
快乐数——哈希表
数据结构·python·算法·leetcode·散列表
silence2502 小时前
Maven Central 上传(发布)JAR 包流程
java·maven·jar
蒙奇D索大2 小时前
【数据结构】考研408 | B树探秘:从查找操作到树高性能分析
数据结构·笔记·b树·考研·改行学it