Redis 基础

  • 介绍
  • 特性
  • 使用场景
  • 存储结构
  • 重要 value 类型
  • 全局命令
  • String
  • Hash
  • List
  • Set
  • Zset
  • BitMap
  • 布隆过滤器
  • 小结
  • 三种方案实现用户信息存储优缺点
  • Redis VS Memcached
  • 单线程
  • 为什么这么快
  • 劣势


介绍

Redis 是一种基于键值对(key-value)的,worker 线程为单线程的内存数据库。其中 value 可以为 string、hash、list、set、zset,它们有自己的方法,可以满足很多应用场景。

还提供了键过期,发布订阅,事务,流水线等附加功能。

流水线:Redis的流水线功能允许客户端一次将多个命令请求发送给服务器,并将被执行的多个命令请求的结果在一个命令回复中全部返回给客户端,使用这个功能可以有效地减少客户端在执行多个命令时需要与服务器进行通信的次数。

特性

  1. 速度快,数据放在内存中,官方给出的读写性能10万/s(与机器性能也有关):
    数据放内存中是速度快的主要原因
    C语言实现,与操作系统距离近
    使用了单线程架构,预防多线程可能产生的竞争问题
  2. 键值对的数据结构服务器
  3. 功能丰富
  4. 简单稳定:单线程
  5. 持久化:发生断电或机器故障,数据可能会丢失,持久化到硬盘
  6. 主从复制:实现多个相同数据的redis副本
  7. 高可用和分布式:哨兵机制实现高可用,保证 redis节点故障发现和自动转移
  8. 客户端语言多:java php python c c++ nodejs等

使用场景

  1. 缓存:合理使用缓存加快数据访问速度,降低后端数据源压力
  2. 排行榜:按照热度排名,按照发布时间排行,主要用到列表和有序集合
  3. 计数器应用:视频网站播放数,网站浏览数,使用 redis 计数
  4. 社交网络:赞、踩、粉丝、下拉刷新
  5. 消息队列:发布和订阅

存储结构

redis多活方案 redis 多对多_Redis


Redis 的内部整体的存储结构就是一个大的 HashMap。每个 dictEntry 里都有 key 与 v,分别对应 set key value 里的 key 和 value。

重要 value 类型

全局命令

  1. 查看所有键:keys *
  2. 键总数:dbsize //如果存在大量键,线上禁止使用此指令
  3. 检查键是否存在:exists key0 //key0存在返回1,不存在返回0
  4. 删除键:del key0 key1 …//返回删除键个数,删除不存在键返回0
  5. 键过期:expire key0 seconds0
  6. 键的数据结构类型:type key0

String

字符串类型:实际上可以是字符串(包括XML、JSON),还有数字(整形浮点数),二进制(图片音频视频),最大不能超过512MB


设值命令

redis多活方案 redis 多对多_redis多活方案_02


EX:过期时间,秒

PX:过期时间,毫秒

NX:如果不存在 key,可以设置成功。成功返回1。

XX:如果存在 key,可以设置成功。成功返回1。

redis多活方案 redis 多对多_redis多活方案_03


效果等于上面的 set key value NX获值命令

redis多活方案 redis 多对多_Redis_04


批量设置

redis多活方案 redis 多对多_使用场景_05


批量获取

redis多活方案 redis 多对多_redis多活方案_06


计数命令

自增

redis多活方案 redis 多对多_redis多活方案_07


必须为整数自增1,非整数返回错误,无键从0自增返回1

使用场景的联想:可以用来做文章阅读量的自增。自增一段

redis多活方案 redis 多对多_redis多活方案_08


浮点自增一段

redis多活方案 redis 多对多_redis多活方案_09


自减

redis多活方案 redis 多对多_使用场景_10


必须为整数自减1,非整数返回错误,无键从0自减返回-1自减一段

redis多活方案 redis 多对多_数据_11


追加命令

redis多活方案 redis 多对多_使用场景_12


截取命令

redis多活方案 redis 多对多_数据_13


索引从 0 开始,截取包含 start 和 end


求字节长度

redis多活方案 redis 多对多_数据_14


内部编码

redis多活方案 redis 多对多_使用场景_15


int:如果能转化为 long。

embstr:小于等于44字节的字符串(3.2版本)

raw:大于44字节的字符串。最大 512M

embstr和raw都是由SDS(Simple Dynamic String)动态字符串构成的。
区别是:raw是分配内存的时候,redisobject 和 sds 各分配一块内存,而embstr是redisobject和raw在一块儿内存中。

embstr:
分配内存和释放内存只需要1次。对 embstr 的 操作会使 embstr 转化为 raw。
44的由来:整个 redisObject 和 sdshdr 一共占 64 字节,redisObject 占16 字节,SDS 头部占 3 字节,还有结尾 ‘\0’ 占 1 字节。一共 20 字节不能用,所以还剩 44 字节。

为什么要分配 64 字节?因为内存分配器 jemalloc、tcmalloc 分配内存的大小都是按 2/4/8/16/32/64 字节来的。因为 embstr 的“无效”部分就占了 20 字节,如果只申请 32 字节,利用率未免太低。

redis多活方案 redis 多对多_数据_16

raw:

分配内存和释放内存需要2次。

redis多活方案 redis 多对多_redis多活方案_17

空间预分配
若修改之后sds长度小于1MB,则多分配现有len长度的空间
若修改之后sds长度大于等于1MB,则扩充除了满足修改之后的长度外,额外多1MB空间

惰性空间释放
为避免缩短字符串时候的内存重分配操作,sds在数据减少时,并不立刻释放空间。

Hash

是一个 string 类型的 field 和 value 的映射表,hash 特适合用于存储对象。


设值命令

redis多活方案 redis 多对多_使用场景_18


例:购物车场景

redis多活方案 redis 多对多_redis多活方案_19


购物车编号 001,选择了 1 件 01 号商品,1 件 02 号商品

redis多活方案 redis 多对多_使用场景_20


可以看出,hset 可以批量设值批量设值

redis多活方案 redis 多对多_redis多活方案_21


取值命令

redis多活方案 redis 多对多_使用场景_22


hget 不能批量取值批量取值

redis多活方案 redis 多对多_redis多活方案_23


删值命令

redis多活方案 redis 多对多_使用场景_24


字段个数

redis多活方案 redis 多对多_使用场景_25


使用场景的联想:查看购物车里物品数量

判断字段是否存在

redis多活方案 redis 多对多_使用场景_26


获取所有字段

redis多活方案 redis 多对多_使用场景_27


例:

redis多活方案 redis 多对多_redis多活方案_28


获取所有字段值

redis多活方案 redis 多对多_redis多活方案_29


例:

redis多活方案 redis 多对多_数据_30

获取所有字段与字段值

redis多活方案 redis 多对多_redis多活方案_31


例:

redis多活方案 redis 多对多_数据_32


自增一段

redis多活方案 redis 多对多_redis多活方案_33


使用场景的联想:购物车商品数 +1

redis多活方案 redis 多对多_数据_34

浮点自增一段

redis多活方案 redis 多对多_使用场景_35


例:

redis多活方案 redis 多对多_数据_36


内部编码

ziplist:当字段个数小于512且每个字段值小于64字节时

hashtableziplist:

redis多活方案 redis 多对多_Redis_37


hashtable

redis多活方案 redis 多对多_redis多活方案_38


字典的结构中包括两个hash表,h[0]和h[1],每次只是用h[0]就够了。若是需要扩容,就一部分一部分的迁移到h[1],在此过程中,若是客户端线程来删改查数据,先从h[0]找,没有的话去h[1],若是添加数据,直接添加到h[1],保证h[0]数据不增加。最后迁移完成释放h[0],将h[1]设置为h[0],再在h[1]新建一个空白 table。

List

用来存储多个有序的字符串,一个列表最多可存 232-1 个元素


左push

redis多活方案 redis 多对多_数据_39


使用场景的联想:消息推送

redis多活方案 redis 多对多_数据_40

右push

redis多活方案 redis 多对多_使用场景_41

左pop

redis多活方案 redis 多对多_数据_42


右pop

redis多活方案 redis 多对多_数据_43


左阻塞pop

redis多活方案 redis 多对多_redis多活方案_44


右阻塞pop

redis多活方案 redis 多对多_使用场景_45


替换

redis多活方案 redis 多对多_使用场景_46


插入

redis多活方案 redis 多对多_redis多活方案_47


在 pivot 前|后 插入value查询

redis多活方案 redis 多对多_redis多活方案_48

范围查询

redis多活方案 redis 多对多_使用场景_49


查询索引 [start, stop] 的所有 value。[0, -1] 表示所有 value。长度

redis多活方案 redis 多对多_数据_50


删除指定数量的值

redis多活方案 redis 多对多_数据_51


从左到右删除 count 个 value保留范围内的值

redis多活方案 redis 多对多_Redis_52


内部编码

版本 3.2 以后: quicklist

redis多活方案 redis 多对多_redis多活方案_53


quicklist 可以看作一个双向列表,但是列表的每个节点都是一个ziplist。

可以配置双向列表两边 n 个节点不压缩。

Set

保存无序不重复元素,一个集合最多可存 232-1 个元素,除了支持增删改查,还支持集合交集、并集、差集.


redis多活方案 redis 多对多_使用场景_54


只会添加原本没有的 member,返回添加成功的 member 个数

使用场景的联想:黑名单,如抢红包,抢过的人加入黑名单。

redis多活方案 redis 多对多_数据_55


只会删除原本有的 member,返回删除成功的 member 个数随机pop

redis多活方案 redis 多对多_Redis_56


使用场景的联想:抽奖,从参与抽奖的人群里抽取几个查询成员个数

redis多活方案 redis 多对多_redis多活方案_57


查询所有成员

redis多活方案 redis 多对多_数据_58


查询成员是否存在

redis多活方案 redis 多对多_redis多活方案_59


随机pop一定数量的成员

redis多活方案 redis 多对多_使用场景_60


count 不能为负数随机查一定数量的成员

redis多活方案 redis 多对多_redis多活方案_61


如果 count 为负数,允许重复。求交集

redis多活方案 redis 多对多_redis多活方案_62


使用场景的联想:微关系,共同关注的人求交集并保存结果

redis多活方案 redis 多对多_redis多活方案_63


求并集

redis多活方案 redis 多对多_Redis_64


求并集并保存结果

redis多活方案 redis 多对多_redis多活方案_65


求差集

redis多活方案 redis 多对多_redis多活方案_66


key1 在前,key2 在后,就是 key1 的数据 - key2 的数据

使用场景的联想:QQ,可能想认识的人求差集并保存结果

redis多活方案 redis 多对多_数据_67


返回第一个 key 的 set 减去交集的结果

内部编码
intset:成员都为整数且少于512个
hashtable

Zset

用分值排序的 Set

与 List、Set 对比:

redis多活方案 redis 多对多_数据_68


redis多活方案 redis 多对多_数据_69


NX:key 必须不存在,用于添加

XX:key 必须存在,用于修改。与 INCR 一起使用。

CH:返回结果分值。与 INCR 一起使用。

INCR:接一个整数,增加分值。接浮点数,浮点数会不准。

redis多活方案 redis 多对多_数据_70

查范围

redis多活方案 redis 多对多_redis多活方案_71


索引在[start, stop]之间的成员。索引顺序与分值顺序一致。

WITHSCORES:显示分值。查成员个数

redis多活方案 redis 多对多_Redis_72


查名次

redis多活方案 redis 多对多_数据_73


名次从 0 开始。顺序从小到大。查排名

redis多活方案 redis 多对多_数据_74

查倒序名次

redis多活方案 redis 多对多_使用场景_75


顺序从大到小。查倒序排名

redis多活方案 redis 多对多_redis多活方案_76


查分数范围内成员

redis多活方案 redis 多对多_数据_77


表示从 x 到无穷大:(x +inf

表示从无穷小到 x:(x -inf倒序查分数范围内成员

redis多活方案 redis 多对多_数据_78


查分数范围内成员个数

redis多活方案 redis 多对多_使用场景_79


增加分数

不能直接改,只能在原分数上更改。除了用 zadd 以外,还有:

redis多活方案 redis 多对多_Redis_80


使用场景的联想:热搜排行删名次范围内成员

redis多活方案 redis 多对多_redis多活方案_81


删分数范围内成员

redis多活方案 redis 多对多_数据_82


求交集并保存

redis多活方案 redis 多对多_使用场景_83


destination:要保存的结果的 key 的名字

numkeys:key 个数

WEIGHTS:对应 key 的排列设值,代表各 key 里所有成员的分数权值。在后续的计算之前,将分数与对应权值相乘。

AGGREGATE:聚合方式,有求和、求最小值、求最大值。默认是求和。求并集并保存

redis多活方案 redis 多对多_Redis_84

内部编码
ziplist:当成员个数小于128且成员值小于64字节
skiplist + dict:skiplist 最大层数64,dict 是为了在O(1)的时间复杂度下通过元素成员得到分数

ziplist:

redis多活方案 redis 多对多_使用场景_85


skiplist:

redis多活方案 redis 多对多_数据_86

查找的效率平均是O(logN)

为什么用跳表而不用 BTree?

  1. 占用内存更小
  2. 对范围查找的支持更好
  3. 实现更容易

BitMap

redis多活方案 redis 多对多_使用场景_87


如:setbit key1 1 1

0 1 0 0 0 0 0 0

index 大小会决定字节多少

1的数量

redis多活方案 redis 多对多_redis多活方案_88


字节下标范围内有多少个1

使用场景的联想:时间窗口内天数、每天活跃用户数

逻辑操作

redis多活方案 redis 多对多_redis多活方案_89

如:bitop and andkey k1 k2

布隆过滤器

小结

redis多活方案 redis 多对多_数据_90

三种方案实现用户信息存储优缺点

  1. 原生
    set user:1:name Tom
    set user:1:age 23
    set user:1:sex male
    优点:简单直观,每个键对应一个值
    缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境
  2. 将对象序列化存入redis
    set user:1 serialize(userlnfo);
    优点:编程简单,若使用序列化合理内存使用率高
    缺点:序列化与反序列化有一定开销,更新属性时需要把 userlnfo 全取出来进行反序列化,更新后再序列化到redis
  3. 使用hash类型
    hmset user:1 name Tom age 23 sex male
    优点:简单直观,使用合理可减少内存空间消耗
    缺点:要控制 ziplist 与 hashtable 两种编码转换,且 hashtable 会消耗更多内存

总结:

  • 对于更新不多的情况下,可以使用序列化
  • 对于VALUE值不大于64字节可以使用 Hash 类型

Redis VS Memcached

Memcached 的 value 只能存储 String,数据向计算移动。
Redis 的 value 能存储多种类型,且每种类型有自己的方法,计算向数据移动。

移动的成本往往大于计算,Redis 的 IO 量更少,这是它的优势。

单线程

为什么这么快

  • 开发语言
    Redis 就是用 C 语言开发的,C 语言是非常贴近操作系统的语言,所以执行会比较快。
  • 纯内存访问
    Redis 将所有数据放在内存中,非数据同步正常工作时,是不需要从磁盘读取数据的。
  • worker 单线程
    第一,单线程简化算法的实现,并发的数据结构实现不但困难而且测试也麻烦。
    第二,单线程避免了线程切换以及加锁释放锁带来的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。
  • 多路复用
    Redis 使用 epoll 作为 I/O 多路复用技术的实现,再加上 Redis 自身的事件处理模型将 epoll 的 read、write、close 等都转换成事件,不在网络 I/O 上浪费过多的时间,从而实现对多个 FD 读写的监控,提高性能。
  • 计算向数据移动
    Redis 的 value 能存储多种类型,且每种类型有自己的方法。减少 IO 数据包的大小。

劣势

问题:不能发挥多核优势
解决:woker 线程计算,io threads 并行读写。
新问题:不同连接,读写先后顺序,会有问题。这个问题无法解决,回想一下多路复用器的 select。