Redis基础概念和实现机制的总结


1. 数据类型


1.1.key

Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值。

关于key的几条规则:

太长的键值不是个好主意

太短的键值通常也不是好主意

最好坚持一种模式

”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。

1.2. string

值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能

超过512 MB。

INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值,类似的命令有INCRBY,

DECR 和 DECRBY。

INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情

况。

1.3. list

Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。

Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。

如果快速访问集合元素很重要,建议使用可排序集合(sorted sets).

list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。

1.4. set

Redis Set 是 String 无序,唯一的排列。

1.5. sorted set

参见: sorted set是index的 (根据score索引,score相同根据value的字典顺序索引)

有序集合,它在 set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,会自动重新按新的值调整顺序。

score相同的元素,根据value的字典排序作为排序顺序

1.6.hash

Hash 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。

1.7.bitmap

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR以及其它位操作。

bitmaps事实上并不是一种新的数据类型,而是基于字符串位操作的集合,由于字符串是二进制安全的,并且最长可支持512M

bitmaps最大的优势是在存储数据时可以极大的节省空间,比如在一个项目中采用自增长的id来标识用户,就可以仅用512M的内存来记录4亿用户的信息

1.8. HyperLogLogs

HyperLogLog是Probabilistic data

Structures的一种,这类数据结构的基本大的思路就是使用统计概率上的算法,牺牲数据的精准性来节省内存的占用空间及提升相关操作的性能。

redis的HyperLogLog特别是适合用来对海量数据进行unique统计,对内存占用有要求,而且还能够接受一定的错误率的场景.最典型的使用场景就是统计网站的每日UV。

1.9.GEO

使用 geohash 保存地理位置的坐标(把精度和维度重新编码后成一个字符串)

使用有序集合(sorted set)保存地理位置的集合,其score是GeoHash的52位整数值

2. HA


2.1. replica-主从模式


1,读写分离

2,容灾备份

3,配置slave node,不配置master node。 从库配置:slaveof 主库IP 主库端口

4, master node down掉, slave 不会自动跳转成master node

5,哨兵(sentinel )模式实现master slave 自动跳转

6,由于所有的写操作都是先在master上操作,然后同步更新到slave上,所以从master同步到slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,slave机器数量的增加也会使这个问题更加严重

7,主从模式, 数据不是强一致性,master节点存储数据后, 立即返回,然后在通知slave node保存数据,保证性能优先,牺牲一点点数据一致性。 鱼合熊掌不能兼得。

2.1.1. Master node

1, 可读可写

2,复制数据到slave node

2.1.2. Slave node

1,只读不写

2,数据从master复制过来

2.1.3. Sentinel哨兵

1, 一个或多个sentinel 独立进程组成集群

2,监控 Master 和Slave node

3, 如果Master 异常,则会进行Master-slave 转换,将其中一个Slave作为Master,将之前的Master作为Slave Sentinel的工作方式:

1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令

2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。

3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel要以每秒一次的频率确认Master的确进入了主观下线状态。

4):当有足够数量的

Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线

5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令

6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO命令的频率会从 10 秒一次改为每秒一次

7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

2.2. cluster- 分slot,采用一致性算法,写入数据

1,所有的redis节点彼此互联, 去中心化

2,节点的fail是通过集群中超过半数的节点检测失效时才生效

3,cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value

4,Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod16384的值,决定将一个key放到哪个桶中。

5,新增一个主节点:,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到新的node上,并把数据拷贝到新的node

6,删除一个节点也是类似,把将要删除的节点的slots,分摊到剩余的节点,移动完成后就可以删除这个节点了。

7,cluster中一个node down掉,数据就不完整,需要结合主从+哨兵模式,提高HA可用性。

3. 功能


3.1.分布式锁

具备3个特性就可以实现一个最低保障的分布式锁。

安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。

活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。

活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.

实现Redis分布式锁的最简单的方法就是在Redis中创建一个key,这个key有一个失效时间(TTL),以保证锁最终会被自动释放掉(这个对应特性2)。当客户端释放资源(解锁)的时候,会删除掉这个key(这个对应特性1)。

在cluster环境下,获取锁和释放锁必须得到 n/2+1个node确认后,方可认为成功(这个对应特性3)

3.2.发布,订阅-消息系统

3.3.缓存

3.4.数据库

参见: 数据持久化 (持久化)

4. 索引


4.1.key是有索引的

4.2. sorted set是index的

参见: sorted set (根据score索引,score相同根据value的字典顺序索引)

5. 数据持久化

5.1.RDB


RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

rdb 记录是key某一时刻的值,但不记录对这个key的操作, 适合用于快照,

5.1.1. save - 阻塞,手工

阻塞Redis的服务器进程,直到RDB文件被创建完毕

5.1.2. bgsave - 非阻塞, 手工

Fork出一个子进程来创建RDB文件,不阻塞服务器进程 lastsave 指令可以查看最近的备份时间.

Redis 是单线程工作,如果 重写rdb需要比较长的时间,那么在重写rdb期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将 rdb重写程序放到子程序中进行。通过fork出子程序,利用COW技术主程序和子程序公用数据内存,避免了额外的内存数据拷贝,也可以避免多线程通过锁机制保证数据的一致性。

5.1.3. 自动保存 - 非阻塞,自动

根据redis.conf配置里的save m n定时触发(用的是BGSAVE)

主从复制时,主节点自动触发

执行Debug Relaod时触发

执行Shutdown且没有开启AOF持久化时触发

5.2.AOF(Append Only File)


每当redis执行一个改变数据集的命令时,这个命令就会追加到aof文件的末尾。这样的话,当redis重新启动时,程序就会通过执行aof文件中的命令来达到重建数据集的目的。

aof文件里可能有太多“琐碎”指令,所以aof会定期根据内存的最新数据重新生成aof文件。例如有很多命令都是在不停的往一个set里面一个一个的添加元素,aof里面会有很sadd记录,aof重写后会合并这些记录到一条一次性加入多个元素,这样可以压缩aof文件。

Redis 是单线程工作,如果 重写 AOF 需要比较长的时间,那么在重写 AOF期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将AOF 重写程序放到子程序中进行通过fork出子程序,利用COW技术主程序和子程序公用数据内存,避免了额外的内存数据拷贝,也可以避免多线程通过锁机制保证数据的一致性。

Redis 服务器设置了一个 AOF重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。当子进程完成 AOF重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF重写缓冲区的内容都写到新的 AOF 文件中。

5.2.1. always

每次有新命令追加到aof文件时就执行一个持久化,非常慢但是安全

5.2.2. every second

每秒执行一次持久化,足够快(和使用rdb持久化差不多)并且在故障时只会丢失1秒钟的数据

5.2.3. no

从不持久化,将数据交给操作系统来处理。redis处理命令速度加快但是不安全。

5.3.RDB+AOF混合模式

redis4.0,混合持久化就是同时结合RDB持久化以及AOF持久化混合写入AOF文件混合持久化同样也是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。

当我们开启了混合持久化时,启动redis依然优先加载aof文件,aof文件加载可能有两种情况如下:

aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。

aof文件开头不是rdb的格式,直接以aof格式加载整个文件

6. 实现技术


6.1. IO多路复用技术

通过多路复用技术监听与redis sever创建连接的socket。监听的消息大致分为三类

1,有客户端与redis连接的消息

2,可以从客户端socket读取数据的消息

3,可以往客户端socket写入数据的消息

所有消息放入事件消息队列

6.2.事件处理队列


事件消息队列将消息与实践处理器做关联。

客户端请求连接的消息,交给连接应答处理器

数据可读消息,交给命令请求处理器

数据可写消息,交给命令回复处理器

因为消息处理是单线程的,每一时刻只有一个消息被处理,所以redis是一个单线程模型。一种事件会等待另一种事件执行完后,才开始执行,事件之间不会出现抢占.事件处理器先处理文件事件,再执行时间事件文件事件的等待时间,由距离到达时间最短的时间事件决定

6.2.1. 文件事件

用于处理 Redis 服务器和客户端之间的网络IO

当一个新的client连接到服务器时, server会给该client绑定读事件, 直到client断开连接后,该读事件才会被移除和client自始至终都关联着读事件不同, server只会在有命令结果要传回给client时,才会为client关联写事件, 并且在命令结果传送完毕之后, client和写事件的关联就会被移除.因为在同一次文件事件处理器的调用中,单个客户端只能执行其中一种事件(要么读,要么写,不能又读又写),当出现读事件和写事件同时就绪时,事件处理器优先处理读事件

6.2.2. 时间事件

Redis 服务器中的一些操作需要在给定的时间点执行,而时间事件就是处理这类定时操作的。事件为单次执行事件,该事件会在指定时间被处理一次,之后该事件就会被删除。

循环事件,该事件会在指定时间被处理,之后它会按照timeProc的返回值,更新事件的 when属性,让这个事件在之后某时间点再运行,以这种方式一直更新运行。

6.3.事件处理器

文件事件相关的一些具体的事件处理器.

连接请求处理器acceptTcpHandler:程序会为redisServer.eventLoop关联一个客户连接的事件处理器。

命令请求处理器readQueryFromClinet :当新连接来的时候,需要创建客户端,在其中为客户端套接字注册读事件,关联处理器readQueryFromClinet处理函数。

命令回复处理器sendReplyToClient :当Redis根据客户端的输入得到输出候后,注册客户端套接字写事件,当套接字可写时,触发sendReplyToClient发送命令回复。

7. 回收策略


7.1.过期策略


Redis采用惰性删除 + 定时任务删除机制实现过期键的内存回收。

7.1.1. 惰性删除

惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。

7.1.2. 定时任务删除

Redis内部维护一个定时任务,默认每秒运行10次。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例,使用快慢两种速率模式回收键。

比如:

定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止。

7.2.内存淘汰策略


当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemorypolicy参数控制,Redis支持6种策略。当用户set 一个key时,redis 检查所使用的内存是否超过maxmemory的设置,如果超过,通过执行设定的淘汰策略删除相应的的key,确保set成功执行。淘汰策略并不是针对所有数据, 只是采样其中部分数据,可以通过配置文件设置,这样做是保证redis可以高效运行,在准确性上做出了部分牺牲。

7.2.1. noeviction

默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

7.2.2. allkeys-lru

当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的Key。推荐使用,目前项目在用这种。

7.2.3. allkeys-random

当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用Key,去随机删。

7.2.4. volatile-lru

当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。

7.2.5. volatile-random

当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。

7.2.6. volatile-ttl

当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。

你可能感兴趣的:(Redis基础概念和实现机制的总结)