Redis学习总结:Redis数据结构

目录

 

Redis学习总结:Redis数据结构

引言

String

1)内存数据结构

2)使用实例

List

1)内存数据结构

2)使用实例

Hash(MAP)

1)内存数据结构

2)使用实例

Set

1)内存数据结构

2)使用实例

Sorted-Set

1)内存数据结构

2)使用实例

HyperLogLog

1)内存数据结构

2)使用实例


Redis学习总结:Redis数据结构

引言

Redis是使用键值(Key-Value)存储数据的非关系型存储系统,Redis不同于Memcached将Value视作黑盒,Redis的value本身具有结构化的特点,用结构化的value满足业务多样性的需求。

如下图所示,Redis的value包含6种类型:string,list,set,hash,sorted-set,hyperLogLog。

String

在Redis中,string型value可以表示字节串,整数和浮点数三种值的类型。根据具体场景Redis自动完成互相间转换,并根据需要的选取底层的承载方式。例如,设置键k的值为“110”,可以把它当成数型运行incr命令,也可以当成字节串运行append命令。

127.0.0.1:6379> set k 110
OK
127.0.0.1:6379> incr k
(integer) 111
127.0.0.1:6379> append k redis
(integer) 8
127.0.0.1:6379> get k
"111redis"

 

1)内存数据结构

在Redis内部,作为字节串承载的string型value内部以int、SDS(simple dynamic string)作为结构存储。int用于存放整形数据,sds存放字节串、字符串和浮点型数据。结构如下:

typedef struct sdshdr{
    unsigned int len;
    unsigned int free;
    char buf[];
};

simple dynamic string(sds)结构体主要属性如有

len:buf数组的长度,通过此属性可以在O(1)的复杂度内得出数组的长度。

free: 余量内存,初始化value时多请求的内存,可以提升sds对字节串处理的性能(典型的空间换时间),减少处理过程中可能遇到的内存申请和内存释放次数。

buf[ ]: 存储了字节串的内容,以'\0'结尾作为字符串的定界符(通过转义字符,业务数据内容也可以包含'\0'字符)。buf数组的长度通常大于所存储内容的长度,即SizeOf(buf) = 1 + len + free.
 

buf 扩容与缩容的策略:为了合理分配内存,串的大小超过buf现有容量的时候,sds会对buf经行扩容。buf以业务操作完成后串的预期长度的两倍+1(‘\0’定界符)来扩容,但最大不超过1MB空间。即:

                                    buf = len*2 + 1

对于len大于1MB的长串,最大保留出1MB空间。

例如内存中存储了一个string类型的值“redis”,它的结构如图所示:

redis\0  

它的len为5,buf实际容量为8,而free为2。如果该值增长大小小于2,则无需申请新的内存,否则将触发扩容,变成下图所示的结构(命令:append rediskey "demo"):

redisdemo\0        

2)使用实例

某系统使用不大于7位的纯数字字符串作为用户账号,可以满足百万级的用户需求。用户注册新的账号,假如已经注册的号码已有百万级,且它们之间是无序的,如何快速生成一个未注册的号码,如何快速求出该系统的有多少用户?

解决方案:从数据库中读取这100万个号码缓存到内存中,使用bitmap存储这些号码,将取出来的数的实际值作为offset,并将该offset设置为1。举个例子,读取到8888888这个号码,则执行命令 “SETBIT account 8888888 1”,如下所示:

127.0.0.1:6379> setbit account 8888888 1
(integer) 0
127.0.0.1:6379> setbit account 7777777 1
(integer) 0
127.0.0.1:6379> setbit account 66666 1
(integer) 0
127.0.0.1:6379> bitcount account 
(integer) 3

遍历account字节串,当在偏移量x处的值不为1时,说明号码x未被注册,时间复杂度为O(1)。使用命令bitcount account得出的结果就是系统的用户总数;对于整个系统而言,即使是缓存百万数量的账号总数,需要申请的内存为百万字节(不超过10M)。

本例涉及到的命令有:

 setbit:对 key 所储存的字符串值,设置或清除指定偏移量上的位(setbit key offset value)。

 bitcount:计算给定字符串中,被设置为 1 的比特位的数量(bitcount key [start end])。

注:更多命令和具体使用方法可以查阅官方文档,目前网上已有完整的中文翻译版本(点击链接)。后面实例也不可能将命令全部演示,读者根据需要自己翻阅文档。

List

list是stiring序列,按照插入顺序排序,可以添加一个元素到序列的头部(rpush)尾部(lpush),一个list类型数据最多可以包含2^{32}-1个元素。

1)内存数据结构

list类型的value对象内部以linkedlist或者ziplist承载。

linkedlist内部实现是双向链表,ziplist的内部实现是顺序表。从数据结构可以看出,相对likedlist,ziplist对于rpush,rpop这样的操作,复杂度一致,都是O(1)。但是lpush/pop操作由于涉及到全列表元素的移动,复杂度较高O(N)。

因此当List的元素个数和单个元素的长度较小时,Redis采用ziplist实现以减少内存占用,否则采用linkedlist结构。这样的话,即便是复杂度较高的O(N),由于N本身不大,对性能的影响就是可控范围内的,且ziplist元素结构采用了可变长度的压缩方法,针对元素长度较小的string具有较好的压缩效果。

2)使用实例

用LPUSH插入六个元素到名为list的序列中,用LRANGE并返回集合的元素

127.0.0.1:6379> lpush list 1 2 a b c
(integer) 5
127.0.0.1:6379> lpush list hello
(integer) 6
127.0.0.1:6379> lrange list 0 5
1) "hello"
2) "c"
3) "b"
4) "a"
5) "2"
6) "1"

本例涉及到的命令有:

  LPUSH:将一个或多个值 value 插入到列表 key 的表头(LPUSH key value [value ...])。

  LRANGE:返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定(lrange key start stop)。

Map(Hash)

map又叫hash,由于它的value还可以是hash,为了不产生混淆,我就叫他map。map包含若干个键值对(key-value),其中key不重复。Redis本身就是key-value结构,它的value还可以是hash型,可以满足许多丰富的业务场景。但是,hash内部的无法再嵌套hash了,只能是string型。

1)内存数据结构

map的value内部使用hashtable和ziplist两种承载方式实现。对于数量较小的map,采用ziplist实现,实现方式与list的ziplist相似,采用类似顺序表的数据结构实现。

ziplist实现的map大多数操作的复杂度不再是O(1)了,变成了O(N)。但是由于ziplist的map大小通常偏小,所以性能的损失可控,通常情况下,只有很少的几个kv对的map,采用ziplist效率反而更高,省去了hash计算、内存寻址等操作。尤其对于长字符串key,其hash值计算本身的开销甚至远大于顺序遍历时字符串比较的开销。

2)使用实例

在名为ipAdr的hash对象中存入地址和地址名,然后查出ipAdr中value的总数和所有的key

127.0.0.1:6379> hmset ipAdr localhost 127.0.0.1 testServer 192.111.12.6 remoteServer www.redis.com
OK
127.0.0.1:6379> hlen ipAdr
(integer) 3
127.0.0.1:6379> hkeys ipAdr
1) "localhost"
2) "testServer"
3) "remoteServer"

本例涉及到的命令有:

  HMSET:同时将多个 field-value (域-值)对设置到哈希表 key 中(HMSET key field value [field value ...])。

  HLEN :获取哈希表中字段的数量(HLEN key)。

  HKEYS :获取所有哈希表中的字段(HKEYS key),

Set

set类似list都是string的集合,不同的是,它是一个无序集合,其中元素不重复

1)内存数据结构

set在redis内部以intset或者hashtable存储,其中,hashtable的value永远为null,由于hashtable的key是不重复的,因此可以满足set的需求。当set中只包含整数型元素时,采用intset作为实现。

2)使用实例

往名为num的set对象中存入10个数字,并全部取出。

127.0.0.1:6379> sadd num 1 3 4 1
(integer) 3
127.0.0.1:6379> sadd num 6 4 5 2 4 8
(integer) 4
127.0.0.1:6379> smembers num
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "8"

本例涉及到的命令有:

SADD :将一个或多个 member 元素(在Redis2.4版本以前, sadd 只接受单个 member 值。)加入到集合 key 当中,已经存在于集合的 member 元素将被忽略(SADD key member [member ...])。

SMEMBERS :返回集合 key 中的所有成员(SMEMBERS key )

Sorted-Set

顾名思义,有序的set。sorted-set也是string的集合,不允许重复的元素,不同的是它的每一个元素都会关联一个浮点数score,sorted-set内部按照score从小到大排序。

1)内存数据结构

sorted-set内部以ziplist或skiplist+hashtable来实现。ziplist适用于元素个数不多,元素内容不大的场景。

其中,redis的skiplist和通用的跳表实现不同,redis为每一个level对象增加了span字段,表示该level指向forward节点和当前节点的距离,使得getByRank类的操作效率提升。

2)使用实例

往名为znum的sorted-set对象中存入4个元素,并全部取出。观察结果和set的区别

127.0.0.1:6379> zadd znum 1 redis
(integer) 1
127.0.0.1:6379> zadd znum 2 hello
(integer) 1
127.0.0.1:6379> zadd znum 4 hi
(integer) 1
127.0.0.1:6379> zadd znum 3 xxx
(integer) 1
127.0.0.1:6379> zrange znum 0 4
1) "redis"
2) "hello"
3) "xxx"
4) "hi"
127.0.0.1:6379>

从结果可以看出,取出来的顺序是按照score排序的。而不是插入顺序。

本例涉及到的命令有:

  ZADD:将一个或多个 member 元素及其 score 值加入到有序集 key 当中(ZADD key score member [[score member] [score member] ...])。

ZRANGE:返回有序集 key 中,指定区间内的成员(ZRANGE key start stop [WITHSCORES])。

HyperLogLog

Redis HyperLogLog 是在 2.8.9 版本添加的结构,用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

1)内存数据结构

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

什么是基数?比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

2)使用实例

添加四个元素{"hello","hi","redis","hello"}到hyperloglog类型的hhlkey中。

本例涉及到的命令有:

  PFADD:添加指定元素到HyperLogLog中(pfadd key element [element ...])。

  PFCOUNT:返回给定HyperLogLog的基数估计值( pfcount key [key ...])。

127.0.0.1:6379> pfadd hhlkey hello
(integer) 1
127.0.0.1:6379> pfadd hhlkey hi
(integer) 1
127.0.0.1:6379> pfadd hhlkey redis
(integer) 1
127.0.0.1:6379> pfadd hhlkey hello
(integer) 0
127.0.0.1:6379> pfcount hhlkey
(integer) 3

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值