基础及应用
一.5种基础数据结构:
1.String
字符串string 表示一个字符数组。
2.list
Redis 的列表相当于Java 语言里面的LinkedList(有序) , 它是双向链表而不是数组(链表的插入删除操作非常快,索引定位很慢)
3.hash(集合)
跟java的HashMap结构相似,都是数组加链表结构,但redis的value只能是字符串,java的rehash采用一次性全部rehash(字典较大的原因),而redis为了追求性能,防止服务堵塞,采用渐进式rehash。
4.set
相当于java中的hashset,键是唯一且无序的。内部是一个特殊字典,key存储实际的值,而value是null,实现唯一性。
5.zset(有序集合)
类似java的sortset跟hashMap的结合,一方面set保证了唯一性,而其中的value存储了排序权重值,排序功能使用跳跃列表数据结构实现。
跳跃列表(Skip list):
[跳跃列表] https://blog.csdn.net/zzhongcy/article/details/107913509 跳跃列表
6.注意
list 、set 、hash 、zset这四种数据结构的类型,如果不存在容器就创建一个,容器里没有元素了就立即删除容器,释放内存。
二.过期时间
过期时间以对象为单位,是整个过期而不是其中的某个key。如果一个字符串已经有了过期时间,再使用set方法修改,过期时间会消失。
三.分布式锁
本质:在redis中为某个操作占一个操作位,当别的线程需要使用先检测此操作为有没有被占用,如果已占用只能等待或者放弃。
1.死锁
redis在占用操作位是执行代码出现异常线程中断,资源得不到释放,导致其他线程拿不到此资源的使用权。为了防止此问题发生,一般在获取锁的时候设置一个过期时间。
机器掉电或者认为杀掉服务进程时的,导致expire释放锁得不到执行,也会造成死锁,问题的根源就在于setnx和expire 是两条指令而不是原子指令。在redis2.8之后的版本,得setnx 和expire 指令可以一起执行,解决此问题。
2.超时问题
加锁和释放锁之间的逻辑执行得太长,以至于超出了锁的超时限制,就会出现问题。因为这时候第一个线程持有的锁过期了,临界区的逻辑还没有执行完,而同时第二个线程就提前重新持有了这把锁,导致临界区代码不能得到严格串行执行。
Redis的分布式锁不能解决超时问题
3.可重入性
可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁, 那么这个锁就是可重入的。比如Java语言里有个ReentrantLock就是可重人锁。
4.延时队列
对于只有一组消费者的消息队列可使用,但是redis的队列没有ACK保证,消息的可靠性低,对消息的可靠性要求高,则不能使用。
使用list结构进行消息队列的存储
延时队列使用Redis 的zset,们将消息序列化成一个字符串作为zset 的value ,这个消息的到期处理时间作为score。
四.位图
位图其实就是普通的字符串,也就是byte 数组。我们可以使用普通的ge的et 直接获取和设置整个位图的内窑,也可以使用位图操作getbit/setbit 等将byte 数组看成“位数组”来处理。
当某个状态只需要很短的存储空间(一个字符),但是总量很大时使用位图。
五.Hyperloglog
六.布隆过滤器
一个不怎么精确的set 结构,用于对精确度要求不高的过滤
七.简单限流
当系统的处理能力有限,需要避免调垃圾请求。比如:某个用户在一个时间段之内某个行为只能发生N次。
八.漏斗限流
存储信息不能超过漏斗大小,超过则等待漏斗有空余位置才能对容器进行数据填充。
九.GeoHash
可用来实现附近的人类似功能。
十.scan
找出某些拥有同个前缀的key
-
复杂度虽然也是o(n) 时但它是通过游标分步进行的不会阻塞线程。
-
提供limit 参数可以控制每次返回结果的最大条数,limit 只是个hint,返回的结果可多可少。
-
同keys 一样,它也提供模式匹配功能。
-
服务器不需要为游标保存状态,游标的唯一状态就是scan 返回给客户端的游标整数。
-
返回的结果可能会有重复需要客户端去重这点非常重要。
-
遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的。
-
单次返回的结果是空的并不意昧着遍历结束,而要看返回的游标值是否为零。
原理
一.线程IO模型
Redis是一个单线程程序,因为它的所有数据都在内存中,所有的运算都是内存级别的运算,所以它能这么快。小心使用时间复杂度位O(n)的指令,会导致redis卡顿。
1.非阻塞IO
在redis调用套接字读写方法时,是默认阻塞的。redis的非阻塞IO实现是在套接字对象提供了Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。能读多少取决于内核为套接字分配的读缓冲区内部的数据字节数,能写多少取决于内核为套接宇分配的写缓冲区的空闲空间字节数。
套接字读写流程:
2.事件轮询(多路复用)
非阻塞IO存在都一部分数据就返回了,线程不知道还是否继续读。事件轮询就是来解决此问题。
时间轮询实在一定时间内可读可写操作,提供了一个timeout 参数,如果没有任何事件到来,那么就最多等待timeout 的值的时间,线程处于阻塞状态。一旦期间有任何事件到来,就可以立即返回。时间过了之后还是没有任何事件到来,也会主即返回。
轮询流程
3.指令队列
Redis 会将每个客户端套接字都关联个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务。
4.响应队列
Redis 会为每个客户端套接字关联一个晌应队列,如果队列为空,那么意昧着连接暂时处于空闲状态,不需要去获取写事件,也就是可以将当前的客户端描述符从write_fds 里面移出来。等到队列有数据了,再将描述符放进去,避免select 系统调用立即返回写事件,结果发现没什么数据可以写,出现这种情况的线程会令CPU 消耗飘升。
5.定时任务
Redis 的定时任务会记录在一个被称为“最小堆”的数据结构中。在这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期里, Redis 都会对最小堆里面已经到时间点的任务进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是se lect 系统调用的timeout 参数。因为Redis 知道未来timeout 的值的时间内,没有其他定时任务需要处理所以可以安心睡眠timeout 的值的时间。
二.通信协议
1.RESP
阻SP 是Redis 序列化协议( Redis Serialization Protocol )的简写。将传输的结构数据分为5 种最小单元类型,单元结束时统一加上回车换行符号\r\n。
-
单行字符串以“+”符号开头。
-
多行字符串以“$”符号开头,后跟字符串长度。
-
整数值以“:”符号开头,后跟整数的字符串形式。
-
错误消息以“-”符号开头。
-
数组以“*”号开头,后跟数组的长度。
2.客户端->服务器
客户端向服务器发送的指令只有一种格式,多行字符串数组。比如一个简单的set 指令set author codehole 会被序列化成下面的字符串。
*3\r\n$3\r\nset\r\n$6\r\nauthor\r\n$8\r\ncodehole\r\n
翻译:
*3 $3 set $6 author $8 Codehole
3.服务器->客户端
由五种基本类型组成。
三.持久化
为了防止redis突然因为不可预料因素挂掉,数据丢失。
1.快照
redis存在一边持久化,一边响应客户端的情况,持久化的同时数据结构改变,这个时候就采用操作系统的多进程COW(copy on write)来实现快照持久化。
COW(copy on write)
2.Fork多进程(RDB)
Redis 在持久化时会调用glibc的函数fork 产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。这时你可以把父子进程想象成个连体婴儿,它们在共享身体。这是Linux操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。在进程分离的瞬间,内存的增长几乎没有明显变化。
子进程做数据持久化,不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到碰盘中。但是父进程必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。
子程序读取的为进程产生的一瞬间的数据,相当于看到进程内的数据的一瞬间就凝固,这也是”快照“名称的由来。
3.AOF原理
AOF 曰志存储的是Redis 服务器的顺序指令序列, AOF日志只记录对内存进行修改的指令记录。
4.AOF重写
其原理就是开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF曰志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF 曰志文件。
5.fsync
fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。防止AOF日志没有完全存到磁盘,机器挂掉出现日志丢失。但是不能每条指令都调用fsync,这样性能太低,一般一秒左右进行一次fsync,在保证性能的同时,保证数据的安全性。
四.管道
redis与客户端之间的通道。
1.管道本质
(1) 客户端进程调用write 将消息写到操作系统内核为套接字分配的发送缓冲send buffer中。
(2)客户端操作系统内核将发送缓冲的内窑发送到网卡,网卡硬件将数据通过“网际路由”送到服务器的网卡。
(3)服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recvbuffer 中。
(4) 服务器进程调用read 从接收缓冲中取出消息进行处理。
(5)服务器进程调用write 将响应消息写到内核为套接字分配的发送缓冲sendbuffer 中。
(6)服务器操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过“网际路由”送到客户端的网卡。
(7)客户端操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recvbuffer 中。
(8)客户端进程调用read 从接收缓冲中取出消息返回给上层业务逻辑进行处理。
(9)结束。
五.事务
1.基本事务指令
Redis指令分别是multi ,exec ,discard。multi指示事务的开始,exec 指示事务的执行, discard 指示事务的丢弃。
discard用于丢弃事务缓存队列中的所有指令,在exec 执行之前。
六.小对象压缩
redis使用32bit编译数据结构所用的指针空间比64bit少一半。
redis内部集合数据结构很小,它会使用紧凑存储形式进行压缩。比如HashMap 本来是二维结构,但是如果内部元素比较少,使用二维结构反而浪费空间,还不如使用一维数组进行存储,需要查找时,因为元素少,进行遍历也很快,甚至可以比HashMap 本身的查找还要快。
1.内存回收机制
如果当前Redis内存有10GB ,当你删除了1GB 的key后,再去观察内存,你会发现内存变化不会太大。原因是操作系统是以页为单位来回收内存的,这个页上只要还有一个key在使用,那么它就不能被回收。Redis 虽然删除了1GB 的key ,但是这些key 分散到了很多页面中,每个页面都还有其他key 存在,这就导致了内存不会被立即回收。
执行flushdb,内存会被回收,原因是所有key都被干掉了。
2.内存分配算法
默认使用jemalloc (facebook),也可以切换到tcmalloc (google )
集群
一.主从同步
1.CAP原理
C : Consistent , 一致性
A : Availability , 可用性
P : Partition tolerance ,分区容忍性
当网络分区时(网络断开),一致性与可用性两难全
2.最终一致
Redis的主从数据是异步同步的, 所以分布式的Redis系统并不满足一致性要求。当客户端在Redis 的主节点修改了数据后,立即返回, 即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务, 所以Redis 满足可用性。
为了最终保持一致性,从节点会一直追赶主节点,最终从节点跟主节点的状态会保持一致。
3.快照同步
在主节点上进行一次bgsave ,将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。
当新增一个节点时,必须先进行一次快照同步。
4.无盘复制
无盘复制是指主服务器直接通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点会一边遍历内存,一边将序列化的内容发送到从节点。
二.Sentinel(哨兵)
Sentinel 负责持续监控主从节点的健康,当主节点挂掉时,自动选择个最优的从节点切换成为主节点。
1.消息丢失
Redis 主从采用异步复制,意昧着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。
主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性。
2.基本用法
Sentinel的默认端口是26379,Sentinel 对象的discover xxx 方法可以发现主从地址,xxx for 方法可以从连接池中拿出一个连接来使用。
当主节点挂掉,Sentinel 进行主从切换,建立新的连接时,获取查询主节点地址,然后跟内存中主节点对比,如果变更了,断开所有链接,重新使用新地址进行连接
主节点没挂,主动切换主从节点时,没有新的连接建立,当主从切换后,所有修改性指令会抛出ReadonlyErro异常,然后关闭所有旧连接,再进行重连。
三.Codis
将众多小内存的Redis 实例整合起来,将分布在多台机器上的众多CPU 核心的计算能力聚集到一起,完成海量数据存储和高并发读写操作。
Codis 是无状态的,它只是一个转发代理中间件,这意昧着我们可以启动多个Codis 实例,供客户端使用,每个Codis 节点都是对等的,
1.codis分片原理
Codis 默认将所有的key 划分为1024 个槽位( slot ),它首先对客户端传过来的key 进行crc32 运算计算hash 值,再将hash 后的整数值对1024 这个整数进行取模得到一个余数,这个余数就是对应key 的槽位。
槽位数量默认是1024 ,它是可以设置的, 如果集群节点比较多,建议将这个数值设置大一些,比如2048 、4096 等。
2.codis分片同步
Codis 将槽位关系存储在zookeeper 中,并且提供了一个Dashboard 可以用来观察和修改槽位关系,当槽位关系变化时, Codis Proxy 会监听到变化并重新同步槽位关系,从而实现多个Codis Proxy 之间共享相同的槽位关系配置。
四.Cluster
Redis Cluster是去中心化的,该集群由三个Redis 节点组成,每个节点负责整个集群的一部分数据。Cluster 将所有数据划分为16384 个槽位,槽位的信息存储于每个节点中。
1.定位算法
Re di s Clu s ter 默认会对key 值使用crcl 6 算法进行hash ,得到一个整数值,然后用这个整数值对16 384 进行取模来得到具体槽位。
2.跳转
当节点收到指令的key不属于自己管理,会向客户端发送一个特殊的跳转指令,携带目标操作的节点地址,告诉客户端去这个地址获取数据。
3.迁移
Cluster 提供了工具redis -trib 可以让运维人员手动调整槽位的分配,Red is 迁移的单位是槽, Redis 一个槽一个槽地进行迁移,当一个槽正在迁移时,这个槽就处于中间过渡状态,状态为migrating。
4.容错
Re dis Cluster 可以为每个主节点设置若干个从节点,当主节点发生故障时,集群会自动将其中某个从节点提升为主节点。如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态。�点提升为主节点。如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态。