当前位置: 首页 > article >正文

【redis】redis操作zset类型的key发生了什么?

以下是关于Redis操作(添加、删除、修改、查询)ZSet类型key的完整过程。


功能

Redis操作ZSet类型key的过程包括:

  1. 添加:通过ZADD命令创建或更新ZSet类型的键值对。
  2. 删除:通过ZREM命令移除ZSet中的成员。
  3. 修改:通过ZINCRBY命令更新成员的分数。
  4. 查询:通过ZRANGEZSCORE等命令获取ZSet的信息。

ZSet数据结构

(一)数据结构

Redis的ZSet(有序集合)是基于两个内部数据结构实现的:

  1. 字典(Dictionary):用于存储成员和分数的映射关系,支持快速查找。
  2. 跳表(Skip List):用于存储成员的有序排列,支持快速范围查询[72]。
1. 编码方式

ZSet的编码方式为:

  • ziplist:当集合较小(成员数量较少且分数为整数)时,使用压缩列表编码。
  • skiplist:当集合较大或成员分数为浮点数时,使用跳表编码[72]。
2. 数据长度限制
  • ZSet的成员数量没有硬性限制,但过多的成员可能影响性能。
  • 分数(score)为双精度浮点数,范围为-inf+inf[72]。

操作过程


时序图

Redis Client Redis Server ZADD myset 1 "apple" 2 "banana" 2 ZRANGE myset 0 -1 WITHSCORES ["apple", "1", "banana", "2"] ZREM myset "apple" 1 ZINCRBY myset 1 "banana" 3 ZSCORE myset "banana" 3 Redis Client Redis Server

(一)添加成员(ZADD

  1. 客户端发送命令

    ZADD key score member [score member ...]
    

    示例:

    ZADD myset 1 "apple" 2 "banana" 3 "cherry"
    
  2. 服务器处理

    • 检查键是否存在,如果不存在则创建新的ZSet。
    • 将成员和分数插入字典和跳表中[71]。
    • 如果成员已存在,更新其分数并调整跳表[77]。
  3. 返回结果

    • 返回成功插入的成员数量[71]。

(二)删除成员(ZREM

  1. 客户端发送命令

    ZREM key member [member ...]
    
  2. 服务器处理

    • 从字典中删除成员和分数的映射。
    • 从跳表中删除对应的节点[76]。
  3. 返回结果

    • 返回成功删除的成员数量[76]。

(三)修改成员分数(ZINCRBY

  1. 客户端发送命令

    ZINCRBY key increment member
    
  2. 服务器处理

    • 查找成员是否存在。
    • 如果存在,更新其分数并调整跳表中的位置[77]。
    • 如果不存在,插入新成员并设置分数[77]。
  3. 返回结果

    • 返回更新后的分数[77]。

(四)查询成员(ZRANGEZSCORE等)

  1. 客户端发送命令

    ZRANGE key start stop [WITHSCORES]
    ZSCORE key member
    
  2. 服务器处理

    • 使用跳表快速定位成员并返回结果[71]。
    • 如果查询分数,直接从字典中获取[76]。
  3. 返回结果

    • 返回指定范围内的成员及其分数[71]。

持久化(磁盘IO读写)

(一)RDB持久化

  • RDB会将ZSet的字典和跳表数据序列化为二进制格式并写入RDB文件[77]。
  • 序列化流程:
    1. 遍历ZSet的字典,序列化成员和分数。
    2. 跳表不需要单独序列化[77]。

(二)AOF持久化

  • AOF会将ZADDZREMZINCRBY等命令追加到AOF文件中[77]。
  • AOF重写时,会根据当前ZSet的状态生成紧凑的命令序列[77]。

性能

(一)时间复杂度

  • ZADD:O(log N),跳表插入操作[72]。
  • ZREM:O(log N),跳表删除操作[76]。
  • ZINCRBY:O(log N),跳表更新操作[77]。
  • ZRANGE:O(log N + M),M为返回的成员数量[72]。

(二)优化建议

  • 避免对ZSet进行全量操作(如ZRANGE查询所有成员),可能导致性能瓶颈[72]。
  • 使用分页查询(如ZRANGEstartstop参数)减少数据传输[72]。

线程安全控制

Redis是单线程的,所有操作都是原子性的[75]。然而,在高并发场景下,客户端需要确保操作的线程安全性:

(一)使用Lua脚本

Lua脚本在Redis中是原子性执行的,可以确保操作的线程安全性[74]。例如:

-- 原子性地更新ZSet成员分数
local score = redis.call("ZSCORE", KEYS[1], ARGV[1])
if score then
    redis.call("ZINCRBY", KEYS[1], ARGV[2], ARGV[1])
    return score + tonumber(ARGV[2])
else
    return nil
end

(二)使用分布式锁

在分布式环境中,可以使用Redis的SETNX命令实现分布式锁[74]。


故障处理机制

(一)大Key删除

  • 同步删除:使用DEL命令直接删除大Key,可能会阻塞主线程[75]。
  • 异步删除:使用UNLINK命令异步删除大Key[75]。

(二)内存不足

  • 配置maxmemory策略,自动淘汰旧数据[75]。
  • 使用MEMORY PURGE命令释放未使用的内存[75]。

(三)数据丢失

  • 启用RDB和AOF持久化机制,确保数据在故障时能够恢复[77]。

接口设计

以下是基于Redis ZSet的接口设计示例(以Java为例)[73]:

(一)类图

RedisZSetExample
-Jedis jedis
+RedisZSetExample()
+void addZSet(String key, String member, double score)
+Set getZSetMembers(String key)
+void removeZSetMember(String key, String member)
+void close()

(二)方法实现

public class RedisZSetExample {
    private Jedis jedis;

    public RedisZSetExample() {
        jedis = new Jedis("localhost");
    }

    public void addZSet(String key, String member, double score) {
        jedis.zadd(key, score, member);
    }

    public Set<String> getZSetMembers(String key) {
        return jedis.zrange(key, 0, -1);
    }

    public void removeZSetMember(String key, String member) {
        jedis.zrem(key, member);
    }

    public void close() {
        jedis.close();
    }
}

(三)使用示例

public class Main {
    public static void main(String[] args) {
        RedisZSetExample zset = new RedisZSetExample();
        zset.addZSet("myset", "apple", 1.0);
        zset.addZSet("myset", "banana", 2.0);

        System.out.println(zset.getZSetMembers("myset"));

        zset.removeZSetMember("myset", "apple");
        System.out.println(zset.getZSetMembers("myset"));

        zset.close();
    }
}

附录:set和zset有什么区别?

特性Set(无序集合)ZSet(有序集合)
存储方式无序集合,成员唯一有序集合,成员按分数排序
内部实现哈希表(hashtable)或整数集合(intset哈希表(hashtable)+跳表(skiplist
功能添加、删除、查找、集合操作添加、删除、更新分数、范围查询
时间复杂度O(1)O(log N)或O(log N + M)
使用场景去重、集合操作排行榜、时间序列、优先队列
数据限制成员数量无硬性限制,成员唯一成员数量无硬性限制,成员按分数排序
内存占用较低较高
性能高效的成员操作支持复杂排序,但可能更耗时

Redis的ZSet(有序集合)和Set(无序集合)是两种不同的数据结构,它们在功能、内部实现和使用场景上存在显著区别。以下是ZSet和Set的主要区别:


1. 数据结构与存储方式

Set(无序集合)
  • 存储方式
    • Set是一个无序的字符串集合,每个成员是唯一的,不允许重复。
    • 内部实现基于哈希表(hashtable),支持快速的成员查找、添加和删除操作。
  • 编码方式
    • 当集合较小(默认小于512个成员)时,使用intset(整数集合)编码。
    • 当集合较大或包含非整数成员时,使用hashtable编码。
ZSet(有序集合)
  • 存储方式
    • ZSet是一个有序的字符串集合,每个成员具有一个与之关联的分数(score),成员通过分数排序。
    • 内部实现同时使用两个数据结构:
      1. 哈希表(hashtable:存储成员和分数的映射关系,支持快速查找。
      2. 跳表(skiplist:存储成员的有序排列,支持快速范围查询。
  • 编码方式
    • 当集合较小且成员分数为整数时,使用ziplist编码。
    • 当集合较大或成员分数为浮点数时,使用skiplist编码。

2. 功能与操作

Set
  • 功能
    • 存储无序的唯一成员集合。
    • 支持成员的添加、删除、查找和集合操作(如交集、并集、差集)。
  • 常用命令
    • SADD key member [member ...]:添加成员。
    • SREM key member [member ...]:删除成员。
    • SMEMBERS key:获取所有成员。
    • SISMEMBER key member:检查成员是否存在。
    • SINTER key1 [key2 ...]:计算交集。
    • SUNION key1 [key2 ...]:计算并集。
    • SDIFF key1 [key2 ...]:计算差集。
ZSet
  • 功能
    • 存储有序的成员集合,成员通过分数排序。
    • 支持成员的添加、删除、分数更新和范围查询。
  • 常用命令
    • ZADD key score member [score member ...]:添加或更新成员及其分数。
    • ZREM key member [member ...]:删除成员。
    • ZSCORE key member:获取成员的分数。
    • ZRANGE key start stop [WITHSCORES]:按分数排序获取成员。
    • ZINCRBY key increment member:增加成员的分数。
    • ZCOUNT key min max:计算指定分数范围内的成员数量。
    • ZREVRANGE key start stop [WITHSCORES]:按分数逆序获取成员。

3. 时间复杂度

Set
  • 添加成员(SADD):O(1)
  • 删除成员(SREM):O(1)
  • 检查成员是否存在(SISMEMBER):O(1)
  • 获取所有成员(SMEMBERS):O(N),N为集合大小
ZSet
  • 添加成员(ZADD):O(log N),N为集合大小
  • 删除成员(ZREM):O(log N)
  • 更新成员分数(ZINCRBY):O(log N)
  • 获取成员范围(ZRANGE):O(log N + M),M为返回的成员数量
  • 获取成员分数(ZSCORE):O(1)

4. 使用场景

Set
  • 适用于存储无序的唯一成员集合,例如:
    • 用户好友列表。
    • 标签集合。
    • 系统权限集合。
    • 去重场景(如网页爬虫中的URL去重)。
ZSet
  • 适用于需要按分数排序的场景,例如:
    • 排行榜(如游戏积分榜、商品销量榜)。
    • 时间序列数据(如日志按时间排序)。
    • 优先队列(如任务调度)。
    • 滑动窗口限流(通过分数表示时间戳)。

6. 内存占用与性能

Set
  • 内存占用相对较低,尤其是使用intset编码时。
  • 适用于成员数量较少且不需要排序的场景。
ZSet
  • 内存占用相对较高,因为同时维护了哈希表和跳表。
  • 性能上,ZSet的排序操作(如ZRANGE)可能比Set更复杂,但支持更丰富的功能。

7. 示例对比

Set
# 添加成员
SADD myset "apple" "banana" "cherry"

# 获取所有成员
SMEMBERS myset
# 输出:["apple", "banana", "cherry"]

# 检查成员是否存在
SISMEMBER myset "apple"
# 输出:1

# 删除成员
SREM myset "banana"
# 输出:1
ZSet
# 添加成员并设置分数
ZADD myzset 1 "apple" 2 "banana" 3 "cherry"

# 获取所有成员(按分数排序)
ZRANGE myzset 0 -1 WITHSCORES
# 输出:["apple", "1", "banana", "2", "cherry", "3"]

# 获取成员分数
ZSCORE myzset "banana"
# 输出:2

# 更新成员分数
ZINCRBY myzset 1 "banana"
# 输出:3

# 删除成员
ZREM myzset "banana"
# 输出:1


http://www.kler.cn/a/524887.html

相关文章:

  • React Router v6配置路由守卫
  • Python学习之旅:进阶阶段(五)数据结构-双端队列(collections.deque)
  • Python实现U盘数据自动拷贝
  • 【10.2】队列-设计循环队列
  • ubuntu取消输入密码
  • 大数据Hadoop入门3
  • 企业知识管理平台助力企业创新与竞争力提升的有效策略探讨
  • 网关登录校验
  • Qwen2.5-max 性能
  • JAVA实战开源项目:网上超市系统(Vue+SpringBoot) 附源码
  • 蓝桥备赛指南(5)
  • TCP 握手数据包分析
  • 「AI学习笔记」深度学习的起源与发展:从神经网络到大数据(二)
  • 【自学笔记】计算机网络的重点知识点-持续更新
  • 格式化时间的插件
  • SET alter system reload
  • 如何看待 OpenAI 的12天“shipmas”发布计划?
  • CTFSHOW-WEB入门-命令执行29-32
  • Linux文件原生操作
  • 【apt源】RK3588 平台ubuntu20.04更换apt源
  • (done) MIT6.S081 2023 学习笔记 (Day6: LAB5 COW Fork)
  • python + ollama 手敲实现私有大模型知识库
  • LeetCode 349题解:两个数组的交集
  • 使用Vue3实现可拖拽的九点导航面板
  • Kafka的消息协议
  • Linux学习笔记——磁盘管理命令