redis之学习(二)使用场景以及性能分析

之前我们以及了解到redis的介绍与简单用法,本文将从各个数据结构的性能与操作redis名字的复杂度进行实际生产中的分析。

String类型

操作字符串类型命令的时间复杂度
命令时间复杂度
set key valueO(1)
get keyO(1)
del key [key …]O(k),k为key的个数
mset key value [key value …]O(k),k为key的个数
mget key [key …]O(k),k为key的个数
incr keyO(1)
decr keyO(1)
incrby key incrementO(1)
decrby key incrementO(1)
incrbyfloat key incrementO(1)
append key valueO(1)
strlen keyO(1)
setrange key offset valueO(1)
getrange key start endO(n),n是字符串的长度,由于获取字符串非常快,所以如果字符串不是很长,可以视为O(1)
字符串类型的内部编码有三种:
  • int 8个字节的长整型
  • embstr 小于等于39个字节的字符串 (redis版本3.0之前是39,redis3.2之后是44字节)
  • raw 大于39个字符串的字符串(redis版本3.0之前是39,redis3.2之后是44字节)
    redis会根据当前值的类型和长度决定使用哪种内部编码实现。

int类型:

127.0.0.1:6379> set test:int 15
OK
127.0.0.1:6379> get test:int
"15"
127.0.0.1:6379> type test:int
string
127.0.0.1:6379> object encoding test:int
"int"

embstr类型字符串长度小于等于39:

127.0.0.1:6379> set test:str hello
OK
127.0.0.1:6379> get test:str
"hello"
127.0.0.1:6379> object encoding test:str
"embstr"

当字符串长度大于39(3.0之前,3.2之后是44)的时候会变成raw,如下:

127.0.0.1:6379> set test:raw 12345678912345678912345678912345678912345678
OK
127.0.0.1:6379> strlen test:raw                      #我用的是redis5.0故是44
(integer) 44
127.0.0.1:6379> object encoding test:raw
"embstr"
127.0.0.1:6379> set test:raw 123456789123456789123456789123456789123456789
OK
127.0.0.1:6379> strlen test:raw
(integer) 45
127.0.0.1:6379> object encoding test:raw           #当长度大于44时候就转变成了raw
"raw"
适用场景
  1. 缓存功能。Redis支持高并发,所以使用redis做缓存层可以加速读写与降低后端压力
  2. 计数 Redis提供的incr命令可以实现快速的计数、查询缓存。同时redis 的数据可以异步到其他的数据源
  3. 共享session 。 一个分布式的web服务,如果将用户的session信息存储在各自的服务器上,处于负载均衡考虑分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次可能就会需要重新登陆,这是非常不合理的。故使用redis将用户的session进行集中管理,每次用户更新或者查询登陆信息都可以在redis中获取。单点登录就是这种做法。
  4. 限速 。一些应用处于安全考虑,在每次进行登陆的时候输入手机验证码确定用户是否是本人,但短信接口不可以频繁访问,利用redis可以很优雅的解决这个问题, redis提供的过期时间可以完美的限制ip、接口等在某一段时间内的 访问次数的限制。

哈希(hash)

操作hash命令的时间复杂度:
命令时间复杂度
hset key field valueO(1)
hget key fieldO(1)
hdel key field [field …]O(k),k是field的数量
hlen keyO(1)
hgetall keyO(n),n是field 的个数
hmget field [ field …]O(k),k是field的个数
hmset field value[ field value …]O(k),k是field的个数
hexists key fieldO(1)
hkeys keyO(n),n是field的总数
hvals keyO(n),n是filed的总数
hsetnx key field valueO(1)
hincrby key field incrementO(1)
hincrbyfloat key field incrementO(1)
hstrlen key fieldO(1)
哈希类型的内部编码:
  • ziplist(压缩列表),当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)同时所有的值都小于hash-max-ziplist-value配置(默认64字节)时。redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,在节省内存方面比hashtable更加优秀。
  • hashtable(哈希表)。当哈希类型无法满足ziplist条件的时候,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂为O(1)。

以下例子可以看到当数量超过512时候就换转换为hashtable

127.0.0.1:6379> hlen hash:test1
(integer) 512
127.0.0.1:6379> hlen hash:test2
(integer) 512
127.0.0.1:6379> object encoding hash:test1
"ziplist"
127.0.0.1:6379> object encoding hash:test2
"ziplist"
127.0.0.1:6379> hset hash:test1 field_513 513
(integer) 1
127.0.0.1:6379> hlen hash:test1
(integer) 513
127.0.0.1:6379> object encoding hash:test1
"hashtable"

当其中某一个元素大小超过64字节的时候就会发现由ziplist转换为了hashtable

127.0.0.1:6379> hlen hash:test2
(integer) 512
127.0.0.1:6379> hget hash:test2 field_1
"1"
127.0.0.1:6379> set test:len "1234567891234567891234567891234567891234567891234561234567891234"
OK
127.0.0.1:6379> strlen test:len
(integer) 64
127.0.0.1:6379> hset hash:test2 field_1 "1234567891234567891234567891234567891234567891234561234567891234"
(integer) 0
127.0.0.1:6379> object encoding hash:test2
"ziplist"
127.0.0.1:6379> set test:len "12345678912345678912345678912345678912345678912345612345678912345"
OK
127.0.0.1:6379> strlen test:len
(integer) 65
127.0.0.1:6379> hset hash:test2 field_1 "12345678912345678912345678912345678912345678912345612345678912345"
(integer) 0
127.0.0.1:6379> object encoding hash:test2
"hashtable"

适用场景

缓存关系型数据。使用hash存储此类型的数据相比较字符串序列化存储信息更为直观。例如存储一个用户的登陆信息。但需要注意一下两点:

  • 哈希类型是稀疏的,而关系型数据库是完全结构化的
  • 关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂查询开发困难,维护成本较高。
    对于同样缓存用户信息有三种方案,各有优缺点,故实际开发应该结合场景去使用
  1. 原生字符串类型,每个属性一个键。此种方式简单直观,每个属性都支持更新操作,但是会占用过的的键与内存,内聚性比较差
  2. 序列化字符串类型。它可以简化编程,合理的序列化可以提高内存的使用。但是序列化与反序列化有一定的开销(相比较而言,开销很大),同时每次更新数据都需要进行将数据取出进行反序列化与序列化。
  3. 哈希类型。用户的信息属性使用field-value表示,但只用一个key保存。它简单直观,合理使用可以减少内存的使用。但需要控制ziplist与hashtable的转换,hashtable会消耗更多的内存。

列表(list)

操作list命令的时间复杂度
命令时间复杂度
rpush key value [value …]O(k),k是元素个数
lpush key value [value …]O(k),k是元素个数
linsert key before|after pivot valueO(n),n是pivot距离列表头或尾的距离
lrange key start endO(s+n),s是start偏移量,n是start到end的范围
lindex key indexO(n),n是索引的偏移量
llen keyO(1)
lpop keyO(1)
rpop keyO(1)
lremkey count valueO(n),n是列表的长度
ltrim key start endO(n),n是要裁减的长度
lset key index valueO(n),n是索引的偏移量
blpop brppO(1)
list的内部编码
  • ziplist(压缩列表),当list类型元素个数小于lit-max-ziplist-entries配置(默认512个)同时所有的值都小于list-max-ziplist-value配置(默认64字节)时。redis会使用ziplist作为list的内部实现。
  • linkedlist(链表)。当列表类型无法满足ziplist条件的时候,Redis会使用linkedlist作为列表的内部实现。
  • quicklist。它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势。这是redis3.2之后提供的,在redis3.2之后redis会使用这种类型当作list的内部编码。
适用场景
  1. 消息队列。redis的lpush+brpop命令组合可以实现阻塞队列,生产者客户端使用lpush从列表左侧插入元素,多个消费者使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消息的负载均衡与高可用性。
  2. 文章列表。用户属于自己的文章列表,当需要分页展示的时候可以考虑list。因为列表不但是有序的,而且支持按照索引范围的获取元素。

小提示:实际上列表的应用场景非常多,不仅仅是上述举例的两个。在具体使用的时候可以参考以下技巧:

  • lpush + lpop = Stack(栈)
  • lpush + rpop = Queue(队列)
  • lpush + ltrim = Capped Collection(有限集合)
  • lpush + brpop = message Queue(消息队列)

集合(set)

操作set命令的时间复杂度
命令时间复杂度
sadd key element [element …]O(k),k是元素个数
srem key element [element …]O(k),k是元素个数
scard keyO(1)
sismember key elementO(1)
srandmember key [count]O(count)
spop keyO(1)
smembers keyO(n),n是元素总个数
sinter key [key …] 或者 sinterstoreO(m*k),k是多个集合中元素最少的个数,m是键的个数
suinon key [key] 或者 suionstoreO(k),k是多个集合元素个数和
sdiff key [key …] 或者 sdiffstoreO(k),k是多个集合元素个数的和
内部编码
  • intset(整数集合)。当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,redis会选用intset来作为集合的内部实现。减少内存使用
  • hashtable(哈希表)。当集合类型无法满足intset的条件的时候,redis会使用hashtable作为集合的内部实现。
127.0.0.1:6379> scard set:test1
(integer) 512
127.0.0.1:6379> object encoding set:test1
"intset"
127.0.0.1:6379> sadd set:test1 513
(integer) 1
127.0.0.1:6379> SCARD set:test1
(integer) 513                //当数量超过512就转为了hashtable
127.0.0.1:6379> object encoding set:test1
"hashtable"
127.0.0.1:6379> sadd set:test2 1 2 3
(integer) 3
127.0.0.1:6379> scard set:test2
(integer) 3
127.0.0.1:6379> object encoding set:test2
"intset"
127.0.0.1:6379> sadd set:test2 hello        //当加入一个字符串类型的数据时转化为hashtable
(integer) 1
127.0.0.1:6379> scard set:test2
(integer) 4
127.0.0.1:6379> object encoding set:test2
"hashtable"
适用场景
  • 标签。set集合类典型的应用场景就是标签(tag),例如一个用户可能对娱乐、体育感兴趣,另一个对历史、新闻感兴趣。这些兴趣点就标签,有了这些数据就可以得到喜欢某一事物的人。这些数据对于用户体验以及增强用户黏度比较重要。set集合类的并交差集非常易于做这些事情。

有序集合

操作有序集合(zset)命令的时间复杂度
命令时间复杂度
zadd key score membeer [score member …]O(k*log(n)),k是添加成员的个数,n是当前有序集合的个数
zcard keyO(1)
zscore key memberO(1)
zrank key member | zrevrank key memberO(log(n)),n是当前有序集合成员个数
zrem key member [member …]O(k*long(n)),k是删除成员个数,n是当前有序集合成员个数
zincrby key increment memberO(log(n)),n是当前有序集合的成员个数
zrange key start end [witthscores] | zrevrange key start end [withscores]O(log(n)+k),n是当前集合成员个数,k是要获取的成员个数
zrangebyscore key min max [withscores] | zrevrangebyscore key max min [withscores]O(log(n)+k),n是当前集合成员个数,k是要获取的成员个数
zcountO(log(n)),n是当前有序集合成员个数
zremrangebyrank key start endO(log(n)+k),k是要删除的成员个数,n是当前有序集合成员个数
zremrangebyscore key min maxO(log(n)+k),k是要删除的成员个数,n是当前有序集合成员个数
内部编码
  • ziplist(压缩列表)。当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时所有的元素的值都小于zset-max-ziplist-value配置(默认64字节)时,redis会使用ziplist作为有序集合的内部实现,减少内存使用。
  • skiplist(跳跃表)。当ziplist条件不满足的时候,会使用skiplist。
适用场景:
  • 排行榜系统
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值