Redis 笔记

Redis是什么?

Redis 是一个 key-value 存储系统,它支持存储的 value 类型相对更多,包括 string、list、set、zset(sorted set --有序集合)和 hash。这些数据结构都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中,Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。


Redis 都有哪些使用场景?

  • 缓存:这应该是 Redis 最主要的功能了,也是大型网站必备机制,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力。
  • 共享Session:对于一些依赖 session 功能的服务来说,如果需要从单机变成集群的话,可以选择 redis 来统一管理 session。
  • 消息队列系统:消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务 解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列的功 能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功 能基本可以满足。比如在分布式爬虫系统中,使用 redis 来统一管理 url队列。
  • 分布式锁:在分布式服务中。可以利用Redis的 setNX 功能来编写分布式的锁,虽然这个可能不是太常用。 当然还有诸如排行榜、点赞功能都可以使用 Redis 来实现,但是 Redis 也不是什么都可以做,比如数据量特别大时,不适合 Redis,我们知道 Redis 是基于内存的,虽然内存很便宜,但是如果你每天的数据量特别大,比如几亿条的用户行为日志数据,用 Redis 来存储的话,成本相当的高。

Redis为什么这么快?

在这里插入图片描述

1. 基于内存存储实现

我们都知道内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。

2. 高效的数据结构

我们知道,MySQL 索引为了提高效率,选择了 B+ 树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。先看下 Redis 的数据结构&内部编码图:
在这里插入图片描述
SDS简单动态字符串
在这里插入图片描述

  • 字符串长度处理:Redis获取字符串长度,时间复杂度为O(1),而C语言中,需要从头开始遍历,复杂度为O(n);
  • 空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。
  • 惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。
  • 二进制安全:Redis可以存储一些二进制数据,在C语言中字符串遇到’\0’会结束,而 SDS中标志字符串结束的是len属性。

3. 合理的数据编码

Redis 支持多种数据数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样编码,是 redis 设计者总结优化的结果。

  • String:如果存储数字的话,是用 int 类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于 39 个字节,则是 raw 编码。
  • List:如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节(默认),使用 ziplist 编码,否则使用 linkedlist 编码
  • Hash:哈希类型元素个数小于512 个,所有值小于 64 字节的话,使用 ziplist 编码,否则使用 hashtable 编码。
  • Set:如果集合中的元素都是整数且元素个数小于 512 个,使用 intset 编码,否则使用 hashtable 编码。
  • Zset:当有序集合的元素个数小于 128 个,每个元素的值小于 64 字节时,使用 ziplist 编码,否则使用skiplist(跳跃表)编码

4. 合理的线程模型

4.1 I/O 多路复用

redis 内部使用 文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。

它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。

文件事件处理器的结构包含 4 个部分:

  • 多个 socket
  • IO 多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

来看客户端与 redis 的一次通信过程:
在这里插入图片描述

  • 客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01AE_READABLE 事件与命令请求处理器关联。
  • 假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器 从队列中获取到该事件,由于前面 socket01AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01AE_WRITABLE 事件与令回复处理器关联。
  • 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01AE_WRITABLE 事件与命令回复处理器的关联。

网络 I/O 多路复用 技术可以让单个线程高效的处理多个连接请求,而 Redis 使用用 epoll 作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

什么是 I/O 多路复用?
I/O :网络 I/O
多路 :多个网络连接
复用:复用同一个线程。
IO多路复用其实就是一种同步 IO 模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出 cpu。

select 是不断轮询监听 socket 对象, socket 个数有限制,一般为 1024 个。时间复杂度 O(n)。
poll 增强 select 模型,也是轮询监听,没有个数限制。时间复杂度 O(n)
epoll 不采用轮询监听,而是当 socket 有变化时,通过回调(调用)的方式主动告知用户进程,高效。时间复杂度O(1)。

4.2 单线程模型
Redis 是单线程模型的,而单线程避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。
Redis是面向快速执行场景的数据库,所以要慎用如 smembers 和 lrange、hgetall 等命令。
Redis 6.0 引入了多线程提速,它的执行命令操作内存的仍然是个单线程。

5. 虚拟内存机制

Redis直接自己构建了VM机制 ,不会像一般的系统会调用系统函数处理,会浪费一定的时间去移动和请求。


String (底层)

string 可以存储任意类型的数据,比如文本、数字、图片或者序列化的对象。

一个 string 类型的键最大可以存储 512 MB 的数据。

string 类型的底层实现是 SDS(simple dynamic string),它是一个动态字符串结构,由长度、空闲空间和字节数组三部分组成:

在这里插入图片描述

SDS 有 3 种编码类型:

  • embstr:占用 64Bytes 的空间,存储 44Bytes 的数据
  • raw:存储大于 44Bytes 的数据
  • int:存储整数类型

embstr 和 raw 存储字符串数据,int 存储整型数据

embstr结构

embstr 结构存储小于等于44个字节的字符串,embstr 每次开辟 64byte 的空间。

在这里插入图片描述

raw结构

在这里插入图片描述

embstr 和 raw 的转换

在存储字符串的时候,redis会根据数据的长度判断使用哪种结构:

  • 如果长度小于等于44个字节,就会选择embstr 结构
  • 如果长度大于44个byte,就会选择raw结构

在这里插入图片描述

127.0.0.1:6379> object encoding str
"embstr"

# str赋值44个字节的字符串
127.0.0.1:6379> set str 1234567890123456789012345678901234567890abcd
OK
127.0.0.1:6379> object encoding str
"embstr"

# str2赋值45个字节的字符串
127.0.0.1:6379> set str2 1234567890123456789012345678901234567890abcde
OK
127.0.0.1:6379> object encoding str2
"raw"

127.0.0.1:6379> set num 123
OK
127.0.0.1:6379> object encoding num
"int"

Hash(底层)

hash 是一个键值对集合,它可以存储多个字段和值,类似于编程语言中的 map 对象。一个 hash 类型的键最多可以存储 2^32 - 1 个字段。

Hash类型的底层实现有三种:

  • ziplist:压缩列表,当 hash 达到一定的阈值时,会自动转换为 hashtable 结构
  • listpack:紧凑列表,在 Redis7.0 之后,listpack 正式取代 ziplist。同样的,当 hash 达到一定的阈值时,会自动转换为 hashtable 结构
  • hashtable:哈希表,类似 map

ziplist(redis7.0之前使用)
listpack(redis7.0之后使用)

ziplist 结构

ziplist 是一种 紧凑的链表 结构,它将所有的字段和值顺序地存储在一块连续的内存中。
在这里插入图片描述

typedef struct {
   
  /* 当使用字符串时,slen表示为字符串长度 */
  unsigned char *sval;
  unsigned int slen;
  /* 当使用整形时,sval为NULL,lval为ziplistEntry的value */
  long long lval;
} ziplistEntry;

listpack 结构

在这里插入图片描述

hashTable

hashTable是一种散列表结构,它将字段和值分别存储在两个数组中,并通过哈希函数计算字段在数组中的索引
在这里插入图片描述

struct dict {
   
    dictType *type;
    dictEntry **ht_table[2];
    unsigned long ht_used[2];
    long rehashidx; /* 当进行rehash时,rehashidx为-1 */
    int16_t pauserehash; /* 如果rehash暂停,pauserehash则大于0,(小于0表示代码错误)*/
    signed char ht_size_exp[2]; /* 哈希桶的个数(size = 1<<exp) */
};

typedef struct dict {
   
    dictEntry **table;
    dictType *type;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
    void *privdata;
} dict;

typedef struct dictEntry {
   
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;

ziplist 和 hashTable 的转换

在这里插入图片描述

127.0.0.1:6379> hset h1 id 123456789012345678901234567890123456789012345678901234567890abcd
(integer) 1
127.0.0.1:6379> object encoding h1
"ziplist"
127.0.0.1:6379> hset h2 id 123456789012345678901234567890123456789012345678901234567890abcde
(integer) 1
127.0.0.1:6379> object encoding h2
"hashtable"

hashTable变得越来越长怎么办
扩容,hashTabel是怎么扩容的?使用的是渐进式扩容rehash。rehash会重新计算哈希值,且将哈希桶的容量扩大。

rehash 步骤
在这里插入图片描述

List(底层)

list 是一个有序的字符串列表,它按照插入顺序排序,并且支持在两端插入或删除元素。一个 list 类型的键最多可以存储 2^32 - 1 个元素。

redis3.2 以后,list 类型的底层实现只有一种结构,就是 quicklist

在讲解list结构之前,需要先说明一下list结构编码的更替,如下

  • 在Redis3.2之前,list使用的是linkedlist和ziplist
  • 在Redis3.2~Redis7.0之间,list使用的是quickList,是linkedlist和ziplist的结合
  • 在Redis7.0之后,list使用的也是quickList,只不过将ziplist转为listpack,它是listpack、linkedlist结合版

linkedlist 与 ziplist

在 Redis3.2 之前,linkedlistziplist 两种编码可以选择切换,它们之间的转换关系如图:
在这里插入图片描述
同样地,ziplist 转为 linkedlist 的条件可在 redis.conf 配置:

list-max-ziplist
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值