之前我已经写过关于Redis的基本使用,Redis高可用这些文章,本篇只做总结。
Memcache:代码层次类似Hash
1.支持简单的数据类型。
2.不支持数据持久化存储。
3.不支持主从。
4.不支持分片。
Redis:
1.数据类型丰富。
2.支持数据磁盘持久化存储。
3.支持主从。
4.支持分片。
Redis非常快,官方提供的数据是100000+QPS。
Redis安全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
数据结构简单,对数据操作也简单。
采用单线程,单线程也能处理高并发请求,想多核也可以启动多实例。(避免了锁竞争和上下文资源的切换)
使用多路I/O复用模型,非阻塞IO。
File Descriptor是文件描述符:一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元数据到文件本身的映射。
Select系统调用,能同时监控多个文件的描述符的可读可写情况。当其中的一些文件描述符可读可写时,Select方法就会返回这些文件描述符的个数。
class java.nio.channels.Selector是Java的非阻塞I/O实现的关键。它使用了事件通知API以确定在一组非阻塞套接字中有哪些已经就绪能够进行I/O相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态。与阻塞I/O模型相比,这种模型提供了更好的资源管理:
- 1.使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来的性能开销。
- 2.当没有I/O操作需要处理的问题时,线程也可以被用于其他任务。
Redis采用的I/O多路复用函数:epoll/kqueue/evport/select?
- 因地制宜,比如在linux环境下就采用epoll,在mac环境下就采用kqueue。
- 优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现。
- 以时间复杂度为O(n)的select作为保底。
- 基于react设计模式监听I/O事件。
从海量Key里查询出某一固定前缀的Key,不能使用keys pattern
,这样会造成redis卡顿阻塞。
我们可以通过SCAN cursor [MATCH pattern] [COUNT count]
、HSCAN key cursor [MATCH pattern] [COUNT count]
:
1.基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。
2.以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历。
3.不保证每次执行都返回某个给定数量的元素,支持模糊查询。
4.一次返回的数量不可控,只是大概率符合count参数。
如果使用Redis作为异步队列?可以使用List作为队列,RPUSH生成消息,LPOP消费消息。缺点就是LPOP不会等待队列有值才去消费的,可以通过在应用层引入Sleep机制去调用LPOP重试。也可以用BLPOP key[key...] timeout
阻塞直到队列有消息或者超时。
pub/sub的缺点:消息的发布是无状态的,无法保证可达。
RDB(快照)持久化:保存某个时间点的全量数据快照。
备份进程出错的,那么主进程会终止写入操作,这是为了保护持久数据的一致性问题。
stop-writes-on-bgsave-error yes
Redis本身是一个CPU密集型的服务器,在开启压缩后会增大CPU的消耗。比起硬盘的成本,CPU更值钱。
rdbcompression yes
如果要禁用RDB策略
save ""
RDB文件可以通过2个命令来生成:
1.SAVE:阻塞Redis的服务器进程,直到RDB文件被创建完毕。
2.BGSAVE:Fork出一个子进程来创建RDB文件,不阻塞服务器进程。
自动化触发RDB持久化的方式:
1.根据redis.conf配置里面的save m n
定时触发。(用的是bgsave)
2.主从复制时,从节点向主节点发送SYNC命令,主节点会进行持久化操作生成rdb文件。
3.执行Debug Reload
命令
4.执行
shutdown
且没有开启AOF持久化。BGSAVE原理图
系统调用fork():创建进程,实现了Copy-on-Write。传统方式下fork函数创建函数时把所有资源复制给子线程,这种效率低下,复制的资源有可能对子线程毫无用处。linux改进了fork的实现方式,当父进程创建子进程时,内核会对子进程程创建虚拟空间,父子两个进程用的是同一块物理空间。只有父进程发生更改时,才会为子进程分配独立的物理空间。
Copy-on-Write的定义:
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
当Redis需要做持久化时,会fork一个子进程,子进程将数据写到磁盘上的一个临时RDB文件中。当子进程完成写入临时文件后,会将原来的RDB文件替换掉。这样做的好处是可以实现Copy-On-Write,确保了Redis的性能。
RDB持久化的缺点:
1.内存数据的全量同步,数据量大会由于I/O而严重影响性能。
2.可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据。
AOF(Append-Only-File)持久化:保存写状态。
记录下除了查询以外的所有变更数据库状态的指令。
以append的形式追加保存到AOF文件中(增量)。
日志重写解决AOF文件大小不断增大的问题,原理如下:
1.调用fork(),创建一个子进程。
2.子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件。
3.主进程持续将新的变动写到内存和原来的AOF里。
4.主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动。
5.使用新的AOF文件替换掉旧的AOF文件。
RDB-AOF混合持久化方式:
-
BGSAVE做镜像全量持久化,AOF做增量持久化。
Pipeline和linux的管道有点类似。Redis基于请求/响应模型,单个请求处理需要一一应答。Pipeline批量执行指令,节省多次IO往返的时间。如果指令有顺序依赖的指令建议分批发送。
流言协议Gossip在杂乱无章中寻求一致:
1.每个节点都随机的与对方通信,最终所有节点的状态达成一致。
2.种子节点定期随机向其他节点发送节点列表以及需要传播的消息。
3.不保证信息一定会传递给所有节点,但是最终会趋于一致。
如何从海量数据里快速找到所需要的数据?
- 分片:按照某种规则去划分数据,分散存储在多个节点上。
- 常规的按照哈希划分无法实现节点的动态增减,会造成缓存失效的现象。
所以引入了一致性哈希算法,这里是将2^32取模,将哈希值空间组织成虚拟的圆环。本文参照一致性哈希算法原理
1.假设某哈希函数H的值空间为0-2^32 - 1,整个空间按顺时针方向组织。0和2^32-1在零点中方向重合。
2.可以选择将服务器的ip或者主机名作为关键字进行hash,这些每台机器就能确定其在哈希环上的位置。
3.将数据的key使用相同的函数进行hash,确认此数据在环上的位置,从此位置沿环顺时针行走,第一台遇到的服务器就是其应该定位到的服务器。
4.一致性哈希算法具有容错性和可扩展性,假设节点C宕机了,可以看到此时对象A、B、D都不会被影响,只有C对象被重新定位到节点D。如果在系统中增加一台服务器X,此时对象A、B、D不受影响,只有对象C需要重新定位到新的节点X上。
5.一致性哈希算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜问题。假如系统中有2台服务器,必然造成大量数据集中到节点A上,而只有极少量会定位到节点B上。
6.为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个hash,每个计算结果位置都放置一个此服务器节点,称为虚拟节点。具体做法可以在服务器ip或者主机名的后面增加编号来实现。