具体可以看⼀下我之前写的redis系列⽂章。
关于e p oll
关于边缘触发、⽔平触发这⾥也单独说⼀下,后⾯会多次提到:
边缘触发:
读缓冲区状态变化时, 读事件触发。写缓冲区状态变化时, 写事件触发。(只会提⽰⼀次)
accept新的连接, 同时监听读写事件,读事件到达, 需要⼀直读取数据, 直到返回EAGAIN,写事件到达, ⽆数据处理则不处理, 有数据待写⼊则⼀直写⼊,直到写完或者返回EAGAIN。
效率较⾼,不需要频繁开启关闭事件。编程⽐较复杂,处理不当存在丢失事件的风险。
⽔平触发:
读缓冲区不为空时, 读事件触发。写缓冲区不为满时, 写事件触发。
也就是accept新的连接, 监听读事件,读事件到达, 处理读事件。需要写⼊数据, 向fd中写数据, ⼀次⽆法写完, 开启写事件监听,写事件到达, 继续写⼊数据, 写完后关闭写事件。
编程简单,⼤量数据交互时会存在频繁的事件开关,所以相对边缘触发性能较低。
关于这两种epoll的模式不同的应⽤选择是不同的,也跟其定位相关:Redis-默认⽔平触发、nginx-边缘触发、go net 边缘触发、Java NIO-⽔平触发、Netty-边缘触发。
持久化
回到正题,Redis⽀持RDB、AOF持久化两种,当然了你也可以选择两种⼀起搞也就是混合持久化。
RDB说⽩了就是拉内存快照,然后持久化到内存中,是fork⼀个⼦进程来做这件事⼉(这⾥会有⼀个新的线程出现),很显⽽易见拉快照并保存的期间的发⽣的数据变化是没办法记录的,但是RDB这种⽅式恢复速度相对较快。
AOF是通过记录操作命令来进⾏持久化的,并且其中做了⼀些类似于命令合并的优化,体积相对RDB较⼤,并且AOF命令⼀多恢复时间会⽆⽐漫长。
最优的就是RDB记录内存快照,AOF记录RDB期间发⽣的命令,以此进⾏数据重演是最合适的。
分布式⽅案
⾼可⽤对于⼀个系统来说往往是最重要的衡量标准之⼀,⽽做⾼可⽤最简单的⽅式就是挂主从,然后在主从的基础上做⾃动检查和更替,redis就是这么搞的。
对于分布式系统⽽⾔,想突破处理极限,做数据分⽚是肯定的,有⽔平分也有垂直分,⽽对于Redis这种key-value的NoSQL数据库毫⽆疑问内部是⽔平分⽚(如何做⽔平切分就引⼊了⼀致性hash等问题,想⾃定义切分规则就有了hashtag等标⽰)
redis常见的集⽅案有:碗⾖浆codis⽅案、redis-cluster⽅案。
豌⾖荚 cod is⽅案
中⼼化配置存储,proxy那⼀层控制分⽚规则
codis-ha实时监测proxy的运⾏状态,如果有异常就会⼲掉,它包含了哨兵的功能(会依赖于类似k8s pod的功能,⾃动拉起)
codis-ha在Codis整个架构中是没有办法直接操作代理和服务,因为所有的代理和服务的操作都要经过dashboard处理
在Codis中使⽤的是Zookeeper来保存映射关系,由proxy上来同步配置信息,其实它⽀持的不⽌zookeeper,还有etcd和本地⽂件
Server group中包含了主从节点
image.png
codis是⽀持动态不停机扩容的,其实就是⼀个节点打标、历史数据rehash的过程,中间会涉及⽼hash值与新hash值请求转发的过程。
r e d is-clus te r⽅案
Redis-cluster是去中⼼化的没有代理,所以只能通过客户端分⽚。
槽跟节点的映射关系保存在每个节点上,每个节点每秒钟会ping⼗次其他⼏个最久没通信的节点,其他节点也是⼀样的原理互相PING ,PING的时候⼀个是判断其他节点有没有问题,另⼀个是顺便交换⼀下当前集的节点信息、包括槽与节点映射的关系等。
客户端操作key的时候先通过分⽚算法算出所属的槽,然后随机⼀个服务端请求。
image.png
图⽚来源于:wwwblogs/pingyeaa/p/11294773.html
⼀致性Has h
解决分布式系统中负载均衡的问题时候可以使⽤Hash算法让固定的⼀部分请求落到同⼀server上,这样每台server固定处理⼀部分请求,起到负载均衡的作⽤,但是普通的余数hash伸缩性较差(新增或者减少server时映射关系会变),所以需要对Hash算法进⾏改进实现所谓的⼀致性Hash
所谓的⼀致性Hash主要保证Hash算法的
单调性:有新的server加⼊到系统中时候,应保证原有的请求可以被映射到原有的或者新的server中去,⽽不会被映射到原来的其它server上去。
分散性:同⼀个⽤户的请求尽可能落到同⼀个服务器上处理
平衡性:是指客户端hash后的请求应该能够相对均匀分散到不同的server上去
为了做到这⼏点,在引⼊hash环的思路的基础上有引⼊了虚拟节点等措施来保证上述特性。
image.png
LevelDB
levelDB是同样也是⼀个Key-value数据库,但是相对于Redis、memcache来说,levelDB是基于内存-磁盘来实现的,但在⼤部分场景下也表现出了不逊⾊于Redis、Memcache的性能。levelDB由google实现并开源,轻松⽀持billion量级的数据,并且性能没有太⼤的衰退,下⾯来看⼀下LevelDB的具体实现。
LevelDB实现
既然是⼀个key-value 数据库,显⽽易见⽀持的api肯定有put/get/delete(delete实质上就是put⼀个具有删除标的key)等操作,从这三个API⼊⼿去看下levelDB的实现:
image.png
levelDB内部存储分为内存存储及磁盘存储,内存存储的依赖的数据结构是跳跃表(可以粗暴的理解为
key有序的set集合,默认字典序),⼀种查时可以近似做到log(n),具备链表的快速增删、数组的快速查等特性的数据结构。图中Mutable、Immutale实现都是跳表,Mutable是⼀种⽀持写⼊和读取的跳表,Mutable到达⼀定⼤⼩之后会触发冻结操作来产⽣Immutale(只读),然后Immutale会持久化到磁盘中产⽣SSTable file(只读),实际上就是⼀种不断下沉的过程。
跳表中的key是⼀种复合结构(包含value值)key: <internal_key_size,internal_key<key,sequence,type>,value_size,value>,需要单独说的是sequence,为全局⾃增序列号levelDB 遇到⼀个修改操作,全局序列号⾃动加⼀。levelDB 中存储了多个版本的value,就是靠这个序列号来标记键值对的版本,序列号越⼤,对应的键值对越新。
p ut/d e le te
put/delete操作时写⼊Mutable,当前Mutable已满会产⽣⼀个新的供写⼊,并且遵循write ahead log的原则,先写⼊⽇志后写⼊Mutable,如果发⽣机器故障时使⽤log⽂件恢复当前Mutable。
g e t
get操作时,先读Mutable(毫⽆疑问是最新的),然后读Immutale,最后读SSTable file,如果存在多个版本,则选择最新的版本(序列号最⼤)进⾏返回。
细说数据下沉
这就是叫levelDB的原因
上⾯提到了数据下沉的过程,下⾯来仔细看⼀下这个过程:
image.png
磁盘内的存储结构分为多层,层级的深度同容量成正⽐:capacity = level > 0 && 10^(level+1) M
由Mutable下沉到L0层(第⼀层磁盘⽂件)的过程称为minor compact,这个过程中完全是内存数据直接存储,多个L0 SSTable file 中会出现key值重叠的情况,查时需要⽐较版本号。
由n层下沉到n+1层的过程称为major compact, 这个过程会对于上⼀层的⽂件进⾏多路归并操作。
levelDB 性能优化上做了哪些事情
数据内存操作
写操作完全基于内存实现,速度⽆疑会很快,但是相对于Redis来看,由于是多线程或者多协程操作,会存在强锁问题。读操作,热点数据内存中⼤概率会读到,即使读不到也会有下⾯“磁盘顺序读写”来进⼀步保证性能。
redis支持的数据结构但是很显然levelDB是⼀种适合写多读少的NoSQL数据库。
磁盘顺序读写
磁盘随机读写和顺序读写的性能差异是惊⼈的,levelDB正是利⽤了这⼀点来做的。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论