Redis深入浅出

Linux162

Redis深入浅出

```bash;gutter:true;
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。


RDB的优势和劣势:

```bash;gutter:true;
RDB存在哪些优势呢?
1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB又存在哪些劣势呢?
1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

Redis深入浅出

```bash;gutter:true;
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。


AOF的优势和劣势:

```bash;gutter:true;
AOF的优势有哪些呢?
1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF的劣势有哪些呢?
1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

Original: https://www.cnblogs.com/handsomecui/p/13731261.html
Author: handsomecui
Title: Redis深入浅出



相关阅读1

Title: 大小端存储是什么鬼?

以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」 https://mp.weixin.qq.com/s/htYGddzO2xPl9kDN4lANpQ

大小端存储的划分是为了解决长度大于一个字节的数据类型内容在存储地址上以不同顺序分布的问题。

比如16位的short整形,32位的int整形,64位的long整形,它们在存储地址上,其实最小的划分单位是字节,那么高低位的字节排列在从低到高的存储地址上有什么规定呢?

如果最高位的字节数据存在最低地址上,而次高位的字节数据按次序排列在次低的地址上,那么这种存储方式就叫 大端存储

如果最低位的字节数据存在最低地址上,而次低位的字节数据按次序排列在次低的地址上,那么这种存储方式就叫 小端存储

那么怎么去判断当前系统属于大端存储还是小端存储呢?

判断方法一:利用单字节类型强制转换多字节类型变量获取返回值比较

下面让我们看看实例代码:

#include <iostream>

using namespace std;

bool IsSystemBigEndianStorage()
{
    short src = 1;
    char comp = (char)src;

    return (comp == 0);
}

int main()
{
    bool ret = IsSystemBigEndianStorage();
    if (ret) {
        cout << "big endian" << endl;
    } else {
        cout << "small endian" << endl;
    }

    return 0;
}
</iostream>

首先把单字节范围内的数据值(比如1)赋给更大长度的类型(比如2个字节的short)变量 src,然后利用单字节长度的数据类型(char)强制转换变量 src,会在内存空间上截取变量 src对应存储在最低地址的一个字节数据并返回。

bool IsSystemBigEndianStorage()
{
    short src = 1;
    char comp = (char)src;

    return (comp == 0);
}

可以看到变量src的高位字节数据为0,低位字节数据为1,各不相同。

Redis深入浅出

如果 (char)src的返回值等于0,就表示存储在最低地址的字节数据等于高位字节数据0x00,属于 &#x5927;&#x7AEF;&#x5B58;&#x50A8;,否则表示属于 &#x5C0F;&#x7AEF;&#x5B58;&#x50A8;

判断方法二:利用联合体类型union比较内部的单字节数据

修改一下上面的函数 IsSystemBigEndianStorage

bool IsSystemBigEndianStorage()
{
    union {
        short a;
        char b;
    } temp;
    temp.a = 1;

    return (temp.b == 0);
}

可以看到变量 temp.a的高位字节数据为0,低位字节数据为1,各不相同。

Redis深入浅出

根据内存空间中字节对齐的规律,联合体union类型,各成员变量的起始地址是一样的。即使各成员变量的数据长度不一样也不影响。

也就是说 temp.a最低地址空间的数据内容就是 temp.b的数据内容。

Redis深入浅出

如果 temp.b的值等于0,就表示存储在最低地址的字节数据等于高位字节数据0x00,属于 &#x5927;&#x7AEF;&#x5B58;&#x50A8;,否则表示属于 &#x5C0F;&#x7AEF;&#x5B58;&#x50A8;

关于网络字节顺序

网络中充斥着各种各样的终端设备或者中间代理路由等,数据利用网络进行传输,传输的基本数据单位也是字节,于是多字节类型的数据也会面临大小端的传输顺序定义。

所以,在传输前和传输后的设备怎么同步这个多字节类型数据的存储呢?由传输前后端的设备共同决定吗?

比如两个不同地区的人碰到一起,如果没有约定俗成的共同语言,一样不知如何去交流。

Redis深入浅出

在数据成功传送和解读完整前,数据两端的设备不会理解对方的意图,那么就有必要由第三方来统一明确定义传输顺序。

于是,TCP/IP 协议规定了网络传输多字节类型数据时,先传输高位的字节数据,次高位的字节数据在其后接着传输。而数据在被网络接口发送到网络时,需要从内存逐字节读取出来,从低地址往高地址开始发送。那么可见在网络传输中,数据的字节顺序形式是大端存储。

本地数据怎么和网络字节顺序转换?

下面针对本地系统为linux举个例子

从本地系统存储顺序转换为网络字节顺序

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

从网络字节顺序转换为本地系统存储顺序

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

Original: https://www.cnblogs.com/englyf/p/16656222.html
Author: englyf八戒
Title: 大小端存储是什么鬼?

相关阅读2

Title: redis查看状态信息

redis查看状态信息

  • info all|default
  • Info 指定项

server服务器信息

  • redis_version : Redis 服务器版本
  • redis_git_sha1 : Git SHA1
  • redis_git_dirty : Git dirty flag
  • os : Redis 服务器的宿主操作系统
  • arch_bits : 架构(32 或 64 位)
  • multiplexing_api : Redis 所使用的事件处理机制
  • gcc_version : 编译 Redis 时所使用的 GCC 版本
  • process_id : 服务器进程的 PID
  • run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
  • tcp_port : TCP/IP 监听端口
  • uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
  • uptime_in_days : 自 Redis 服务器启动以来,经过的天数
  • lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理

clients已连接客户端信息

  • connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
  • client_longest_output_list : 当前连接的客户端当中,最长的输出列表
  • client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
  • blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量

memory内存信息

  • used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
  • used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
  • used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和top 、 ps 等命令的输出一致。
  • used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
  • used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
  • used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
  • mem_fragmentation_ratio :used_memory_rss 和 used_memory 之间的比率
  • mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
  • 在理想情况下, used_memory_rss 的值应该只比used_memory 稍微高一点儿。
  • 当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
  • 内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
  • 当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。
  • Because Redis does not have control over how its allocations are mapped to memory pages, highused_memory_rss is often the result of a spike in memory usage.

  • 当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。

  • 如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
  • 查看 used_memory_peak 的值可以验证这种情况是否发生。

4、persistence:RDB和AOF相关持久化信息

  • loading:0 一个标志值,记录了服务器是否正在载入持久化文件
  • rdb_changes_since_last_save:0 距离最后一次成功创建持久化文件之后,改变了多少个键值
  • rdb_bgsave_in_progress:0 一个标志值,记录服务器是否正在创建RDB文件
  • rdb_last_save_time:1338011402 最近一次成功创建RDB文件的UNIX时间
  • rdb_last_bgsave_status:ok 一个标志值,记录了最后一次创建RDB文件的结果是成功还是失败
  • rdb_last_bgsave_time_sec:-1 记录最后一次创建RDB文件耗费的秒数
  • rdb_current_bgsave_time_sec:-1 如果服务器正在创建RDB文件,那么这个值记录的就是当前的创建RDB操作已经耗费了多长时间(单位为秒)
  • aof_enabled:0 一个标志值,记录了AOF是否处于打开状态
  • aof_rewrite_in_progress:0 一个标志值,记录了服务器是否正在创建AOF文件
  • aof_rewrite_scheduled:0 一个标志值,记录了RDB文件创建完之后,是否需要执行预约的AOF重写操作
  • aof_last_rewrite_time_sec:-1 记录了最后一次AOF重写操作的耗时
  • aof_current_rewrite_time_sec:-1 如果服务器正在进行AOF重写操作,那么这个值记录的就是当前重写操作已经耗费的时间(单位是秒)
  • aof_last_bgrewrite_status:ok 一个标志值,记录了最后一次重写AOF文件的结果是成功还是失败

5、stats:一般统计信息

  • total_connections_received:1 服务器已经接受的连接请求数量
  • total_commands_processed:0 服务器已经执行的命令数量
  • instantaneous_ops_per_sec:0 服务器每秒中执行的命令数量
  • rejected_connections:0 因为最大客户端数量限制而被拒绝的连接请求数量
  • expired_keys:0 因为过期而被自动删除的数据库键数量
  • evicted_keys:0 因为最大内存容量限制而被驱逐(evict)的键数量
  • keyspace_hits:0 查找数据库键成功的次数
  • keyspace_misses:0 查找数据库键失败的次数
  • pubsub_channels:0 目前被订阅的频道数量
  • pubsub_patterns:0 目前被订阅的模式数量
  • latest_fork_usec:0 最近一次fork()操作耗费的时间(毫秒)

6、replication:主从复制信息,master上显示的信息

  • role:master #实例的角色,是master or slave
  • connected_slaves:1 #连接的slave实例个数
  • slave0:ip=192.168.64.104,port=9021,state=online,offset=6713173004,lag=0 #lag从库多少秒未向主库发送REPLCONF命令
  • master_repl_offset:6713173145 #主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟
  • repl_backlog_active:1 #复制积压缓冲区是否开启
  • repl_backlog_size:134217728 #复制积压缓冲大小
  • repl_backlog_first_byte_offset:6578955418 #复制缓冲区里偏移量的大小
  • repl_backlog_histlen:134217728 #此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小

6、replication:主从复制信息,slave上显示的信息

  • role:slave #实例的角色,是master or slave
  • master_host:192.168.64.102 #此节点对应的master的ip
  • master_port:9021 #此节点对应的master的port
  • master_link_status:up #slave端可查看它与master之间同步状态,当复制断开后表示down
  • master_last_io_seconds_ago:0 #主库多少秒未发送数据到从库?

  • master_sync_in_progress:0 #从服务器是否在与主服务器进行同步

  • slave_repl_offset:6713173818 #slave复制偏移量
  • slave_priority:100 #slave优先级
  • slave_read_only:1 #从库是否设置只读
  • connected_slaves:0 #连接的slave实例个数
  • master_repl_offset:0
  • repl_backlog_active:0 #复制积压缓冲区是否开启
  • repl_backlog_size:134217728 #复制积压缓冲大小
  • repl_backlog_first_byte_offset:0 #复制缓冲区里偏移量的大小
  • repl_backlog_histlen:0 #此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小

7、cpu:cput计算量统计信息

  • used_cpu_sys:0.03 Redis服务器耗费的系统CPU
  • used_cpu_user:0.01 Redis服务器耗费的用户CPU
  • used_cpu_sys_children:0.00 Redis后台进程耗费的系统CPU
  • used_cpu_user_children:0.00 Redis后台进程耗费的用户CPU

8、commandstats:redis命令统计信息

  • cmdstat_get:calls=1664657469,usec=8266063320,usec_per_call=4.97 #call每个命令执行次数,usec总共消耗的CPU时长(单位微秒),平均每次消耗的CPU时长(单位微秒)

9、cluster:redis集群信息

  • cluster_enabled:1 #实例是否启用集群模式

10、keyspace:数据库相关的统计信息

  • db0:keys=2,expires=0,avg_ttl=0 0号数据库有2个键、已经被删除的过期键数量为0、以及带有生存期的key的数量

redis性能查看与监控常用工具

1.redis-benchmark
redis基准信息,redis服务器性能检测
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能

Original: https://www.cnblogs.com/nulige/p/10708900.html
Author: 努力哥
Title: redis查看状态信息

相关阅读3

Title: 《闲扯Redis十一》Redis 有序集合对象底层实现

一、前言

Redis 提供了5种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要。

原文解析

Redis深入浅出

备注: 本节中涉及到的跳跃表实现,已经在上节《闲扯Redis十》Redis 跳跃表的结构实现一文中详情分析过,本文中将直接引用,不再赘述。

二、命令实现

因为有序集合键的值为有序集合对象,所以用于有序集合键的所有命令都是针对有序集合对象来构建的。

命令 ziplist 编码的实现方法 zset 编码的实现方法 ZADD 调用 ziplistInsert 函数, 将成员和分值作为两个节点分别插入到压缩列表。 先调用 zslInsert 函数, 将新元素添加到跳跃表, 然后调用 dictAdd 函数, 将新元素关联到字典。 ZCARD 调用 ziplistLen 函数, 获得压缩列表包含节点的数量, 将这个数量除以 2 得出集合元素的数量。 访问跳跃表数据结构的 length 属性, 直接返回集合元素的数量。 ZCOUNT 遍历压缩列表, 统计分值在给定范围内的节点的数量。 遍历跳跃表, 统计分值在给定范围内的节点的数量。 ZRANGE 从表头向表尾遍历压缩列表, 返回给定索引范围内的所有元素。 从表头向表尾遍历跳跃表, 返回给定索引范围内的所有元素。 ZREVRANGE 从表尾向表头遍历压缩列表, 返回给定索引范围内的所有元素。 从表尾向表头遍历跳跃表, 返回给定索引范围内的所有元素。 ZRANK 从表头向表尾遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 从表头向表尾遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 ZREVRANK 从表尾向表头遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 从表尾向表头遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 ZREM 遍历压缩列表, 删除所有包含给定成员的节点, 以及被删除成员节点旁边的分值节点。 遍历跳跃表, 删除所有包含了给定成员的跳跃表节点。 并在字典中解除被删除元素的成员和分值的关联。 ZSCORE 遍历压缩列表, 查找包含了给定成员的节点, 然后取出成员节点旁边的分值节点保存的元素分值。 直接从字典中取出给定成员的分值。

三、结构解析

由前文和上图可知,有序集合的编码可以是 ziplist 或者 skiplist 。ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。

压缩列表方式

压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。

例如,如果我们执行以下 ZADD 命令, 那么服务器将创建一个有序集合对象作为 price 键的值:

redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3

如果 price 键的值对象使用的是 ziplist 编码, 那么这个值对象将会是图 8-14 所示,, 而对象所使用的压缩列表则会是 8-15 所示。
Redis深入浅出Redis深入浅出

跳跃表和字典方式

skiplist 编码的有序集合对象使用 zset 结构作为底层实现, 一个 zset 结构同时包含一个字典和一个跳跃表:

typedef struct zset {

    zskiplist *zsl;

    dict *dict;

} zset;

zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素, 每个跳跃表节点都保存了一个集合元素: 跳跃表节点的 object 属性保存了元素的成员, 而跳跃表节点的 score 属性则保存了元素的分值。 通过这个跳跃表, 程序可以对有序集合进行范围型操作, 比如 ZRANK 、ZRANGE 等命令就是基于跳跃表 API 来实现的。

除此之外, zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员, 而字典的值则保存了元素的分值。 通过这个字典, 程序可以用 O(1) 复杂度查找给定成员的分值, ZSCORE 命令就是根据这一特性实现的, 而很多其他有序集合命令都在实现的内部用到了这一特性。

有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 double 类型的浮点数。 值得一提的是, 虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存

skiplist 编码的有序集合对象, 那么这个有序集合对象将会是图 8-16 所示, 而对象所使用的 zset 结构将会是图 8-17 所示。
Redis深入浅出Redis深入浅出

注意:
为了展示方便, 图 8-17 在字典和跳跃表中重复展示了各个元素的成员和分值, 但在实际中, 字典和跳跃表会共享元素的成员和分值, 所以并不会造成任何数据重复, 也不会因此而浪费任何内存。

四、编码转换

当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist 编码:

1.有序集合保存的元素数量小于 128 个;
2.有序集合保存的所有元素成员的长度都小于 64 字节;

不能满足以上两个条件的有序集合对象将使用 skiplist 编码。

注意:
以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 zset-max-ziplist-entries 选项和 zset-max-ziplist-value 选项的说明。对于使用 ziplist 编码的有序集合对象来说, 当使用 ziplist 编码所需的两个条件中的任意一个不能被满足时, 程序就会执行编码转换操作, 将原本储存在压缩列表里面的所有集合元素转移到 zset 结构里面, 并将对象的编码从 ziplist 改为 skiplist 。

1.&#x4EE5;&#x4E0B;&#x60C5;&#x51B5;&#x5C55;&#x793A;&#x4E86;&#x6709;&#x5E8F;&#x96C6;&#x5408;&#x5BF9;&#x8C61;&#x56E0;&#x4E3A;&#x5305;&#x542B;&#x4E86;&#x8FC7;&#x591A;&#x5143;&#x7D20;&#x800C;&#x5F15;&#x53D1;&#x7F16;&#x7801;&#x8F6C;&#x6362;&#xFF1A;
# 对象包含了 128 个元素
redis> EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
(nil)

redis> ZCARD numbers
(integer) 128

redis> OBJECT ENCODING numbers
"ziplist"

# 再添加一个新元素
redis> ZADD numbers 3.14 pi
(integer) 1

# 对象包含的元素数量变为 129 个
redis> ZCARD numbers
(integer) 129

# 编码已改变
redis> OBJECT ENCODING numbers
"skiplist"

2.&#x4EE5;&#x4E0B;&#x60C5;&#x51B5;&#x5C55;&#x793A;&#x4E86;&#x6709;&#x5E8F;&#x96C6;&#x5408;&#x5BF9;&#x8C61;&#x56E0;&#x4E3A;&#x5143;&#x7D20;&#x7684;&#x6210;&#x5458;&#x8FC7;&#x957F;&#x800C;&#x5F15;&#x53D1;&#x7F16;&#x7801;&#x8F6C;&#x6362;&#xFF1A;
# 向有序集合添加一个成员只有三字节长的元素
redis> ZADD blah 1.0 www
(integer) 1

redis> OBJECT ENCODING blah
"ziplist"

# 向有序集合添加一个成员为 66 字节长的元素
redis> ZADD blah 2.0 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
(integer) 1

# 编码已改变
redis> OBJECT ENCODING blah
"skiplist"

五、要点总结

1)有序集合的编码可以是 ziplist 或者 skiplist。

2)一个 zset 结构同时包含一个字典和一个跳跃表。

3)zset 结构跳跃表和字典通过指针来共享相同元素的成员和分值。

4)有序集合对象 ziplist 或者 skiplist编码,符合条件时可发生编码转换。

over
Redis深入浅出

Original: https://www.cnblogs.com/jstarseven/p/13636983.html
Author: jstarseven
Title: 《闲扯Redis十一》Redis 有序集合对象底层实现