K_V键值存储对比
memcached-键值存储
redis-键值存储
RocksDB-键值存储
KeyDB-键值存储
DynamoDB-键值存储
levelDB-键值存储
etcd-键值存储
名称 | 类型 | 数据存储选项 | 附加功能 |
---|---|---|---|
Redis | 基于内存存储的键值非关系型数据库 | 字符串、列表、散列、有序集合、无序集合 | 发布与订阅、主从复制、持久化存储等 |
Memcached | 基于内存存储的键值缓存型数据库 | 键值之间的映射 | 为提升性能构建了多线程服务器 |
MySQL | 基于磁盘的关系型数据库 | 每个数据库可以包含多个表,每个表可以包含多条记录; 支持第三方扩展。 |
支持 ACID 性质、主从复制和主主复制 |
MongoDB | 基于磁盘存储的非关系文档型数据库 | 每个数据库可以包含多个集合,每个集合可以插入多个文档 | 支持聚合操作、主从复制、分片和空间索引 |
一、redis与memcached对比
1、数据类型
redis数据类型丰富,⽀持set+liset等类型
memcache⽀持简单数据类型,需要客户端⾃⼰处理复杂对象
2、持久性
redis⽀持数据落地持久化存储
memcache不⽀持数据持久存储
3、分布式存储
redis⽀持master-slave复制模式
memcache可以使⽤⼀致性hash做分布式
4、value大小限制
memcache是⼀个内存缓存,key的长度⼩于250字符,单个item存储要小于1M,不适合虚拟机使用
5、数据一致性不同
redis使用的是单线程模式,保证顺序性
memcache需要使⽤cas保证数据⼀致性。CAS(Check+and+Set)是⼀个确保并发⼀致性的机制,属于“乐观锁”范畴;原理很简单:拿版本号,操作,对⽐版本号
如果一致九操作不一致就放弃操作
6、cpu的使用率
redis单线程模型只能使⽤⼀个cpu,可以开启多个redis进程
1.Redis中,并不是所有的数据都⼀直存储在内存中的,这是和Memcached相⽐⼀个最⼤的区别。
2.Redis不仅仅⽀持简单的k%2Fv类型的数据,同时还提供list,set,hash等数据结构的存储。
3.Redis⽀持数据的备份,即master-slave模式的数据备份。
4.Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤。
redis和memecache的不同在于:
存储方式
memecache+把数据全部存在内存之中,断电后会挂掉,数据不能超过内存⼤小
redis有部份存在硬盘上,这样能保证数据的持久性。
数据类型
redis比memecache类型多
使⽤底层模型不同:
新版本的redis直接⾃⼰构建了VM+机制,因为⼀般的系统调⽤系统函数的话,会浪费⼀定的时间去移动和请求。
运行环境不同
redis⽬前官⽅只⽀持LINUX+上去⾏,从⽽省去了对于其它系统的⽀持,
Redis和Memcache区别,优缺点对比
1、 Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。
2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
8、Redis支持数据的备份,即master-slave模式的数据备份。
关于redis和memcache的不同,下面罗列了一些相关说法,供记录:
redis和memecache的不同在于[2]:
1、存储方式:
memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。
2、数据支持类型:
redis在数据支持上要比memecache多的多。
3、使用底层模型不同:
新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4、运行环境不同:
redis目前官方只支持LINUX 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上
个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。
下面重点分析Memcached和Redis两种方案:
Memcached介绍
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度,现在已被LiveJournal、hatena、Facebook、Vox、LiveJournal等公司所使用。
Memcached工作方式分析
许多Web应用都将数据保存到 RDBMS中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。Memcached是高性能的分布式内存缓存服务器,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web等应用的速度、 提高可扩展性。下图展示了memcache与数据库端协同工作情况:
其中的过程是这样的:
1.检查用户请求的数据是缓存中是否有存在,如果有存在的话,只需要直接把请求的数据返回,无需查询数据库。
2.如果请求的数据在缓存中找不到,这时候再去查询数据库。返回请求数据的同时,把数据存储到缓存中一份。
3.保持缓存的“新鲜性”,每当数据发生变化的时候(比如,数据有被修改,或被删除的情况下),要同步的更新缓存信息,确保用户不会在缓存取到旧的数据。
Memcached作为高速运行的分布式缓存服务器,具有以下的特点:
1.协议简单
2.基于libevent的事件处理
3.内置内存存储方式
4.memcached不互相通信的分布式
如何实现分布式可拓展性?
Memcached的分布式不是在服务器端实现的,而是在客户端应用中实现的,即通过内置算法制定目标数据的节点,如下图所示:
Redis 介绍
Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、 list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步,当前 Redis的应用已经非常广泛,国内像新浪、淘宝,国外像 Flickr、Github等均在使用Redis的缓存服务。
Redis 工作方式分析
Redis作为一个高性能的key-value数据库具有以下特征:
1.多样的数据模型
2.持久化
3.主从同步
Redis支持丰富的数据类型,最为常用的数据类型主要由五种:String、Hash、List、Set和Sorted Set。Redis通常将数据存储于内存中,或被配置为使用虚拟内存。Redis有一个很重要的特点就是它可以实现持久化数据,通过两种方式可以实现数据持久化:使用RDB快照的方式,将内存中的数据不断写入磁盘;或使用类似MySQL的AOF日志方式,记录每次更新的日志。前者性能较高,但是可能会引起一定程度的数据丢失;后者相反。 Redis支持将数据同步到多台从数据库上,这种特性对提高读取性能非常有益。
Redis如何实现分布式可拓展性?
2.8以前的版本:与Memcached一致,可以在客户端实现,也可以使用代理,twitter已开发出用于Redis和Memcached的代理Twemproxy 。
3.0 以后的版本:相较于Memcached只能采用客户端实现分布式存储,Redis则在服务器端构建分布式存储。Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,各个节点地位一致,具有线性可伸缩的功能。如图给出Redis Cluster的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个 key的数值域分成16384个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是16384。
综合结论
应该说Memcached和Redis都能很好的满足解决我们的问题,它们性能都很高,总的来说,可以把Redis理解为是对Memcached的拓展,是更加重量级的实现,提供了更多更强大的功能。具体来说:
1.性能上:
性能上都很出色,具体到细节,由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比
Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。
2.内存空间和数据量大小:
MemCached可以修改最大内存,采用LRU算法。Redis增加了VM的特性,突破了物理内存的限制。
3.操作便利上:
MemCached数据结构单一,仅用来缓存数据,而Redis支持更加丰富的数据类型,也可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。
4.可靠性上:
MemCached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。Redis支持数据持久化和数据恢复,允许单点故障,但是同时也会付出性能的代价。
5.应用场景:
Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。
需要慎重考虑的部分
1.Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB
2.Memcached只是个内存缓存,对可靠性无要求;而Redis更倾向于内存数据库,因此对对可靠性方面要求比较高
3.从本质上讲,Memcached只是一个单一key-value内存Cache;而Redis则是一个数据结构内存数据库,支持五种数据类型,因此Redis除单纯缓存作用外,还可以处理一些简单的逻辑运算,Redis不仅可以缓存,而且还可以作为数据库用
4.新版本(3.0)的Redis是指集群分布式,也就是说集群本身均衡客户端请求,各个节点可以交流,可拓展行、可维护性更强大。
redis 和 memcache 的区别
1. Redis 和 memcache 的区别
1. memcache 多线程,需要依赖libevent这样的系统类 ,redis 单线程,Redis不需要依赖于操作系统中的类库
2. redis 支持5种数据类型,memcache只支持一种字符串类型
3. redis 支持两种持久化功能,rdb 和 aof , memcache 不支持持久化功能
4. redis 支持主从复制, memcache 不支持,但可以通过客户端实现
5. redis支持哨兵模式和集群模式实现故障自动迁移, memcache 分布式本身没有,需要通过客户端实现
6. redis和memcache内存管理的区别
2. memcached 的LRU内存驱逐 和 page move 页移动
测试page move 和键驱逐的情况
3.redis的LRU内存驱逐
2. memcached 的LRU内存驱逐 和 page move 页移动
memcached的LRU就是靠item连接成的双向链表
但是新版本做了一些改变,将LRU分段了(Segmented LRU):将LRU分成了:HOT, WARM, COLD, TEMP
为什么要分段?主要是为了降低锁竞争,提升效率。
每个 item 有一个 flag,存储在其元数据中,标识其活跃程度:
- FETCHED:如果一个 item 有请求操作,其 flag 等于 FETCHED。
- ACTIVE:如果一个 item 第二次被请求则会标记为 ACTIVE;当一个 item 发生 bump 或被删除了,flag 会被清空。
- INACTIVE:不活跃状态。
item在他们之间的变化规则是这样的:
- 新来的item都加到HOT中
- 一个item被访问两次就标记为active状态
- (随着新item不断的加入),如果item移动到了链表的bottom。
- 如果是在HOT LRU中且为active状态,则把这个item直接移入WARM,否则加入COLD;
- 如果是在WARM中,且是active状态那么把这个item提到WARM链表的开头,否则移动到COLD中;
- COLD中的item是最惨的,他们都是inactive状态。当内存满了的时候就要开始淘汰他们中的一些
- COLD中的item如果变成了active状态后,会被放入队列,然后异步(注意是异步的哦)移动到WARM中
- HOT和WARM的大小是受限的,占该slab class内存量的N%, COLD 大小是不受限的
3.redis的LRU内存驱逐
redis并没有实现LRU策略
为什么redis不实现LRU策略?
维护一个lru 双向队列太占内存,改用以下方案:
Redis并不是直接基于字符串、链表、字典等数据结构来实现KV数据库,而是在这些数据结构上创建了一个对象系统Redis Object。在redisObject结构体中定义了一个长度24bit的unsigned类型的字段,用来存储对象最后一次被命令程序访问的时间
最初Redis是这样实现的:
- 随机选三个Key,把idle time最大的那个Key移除。后来,把3改成可配置的一个参数,默认为N=5:
maxmemory-samples 5
- 它还是有缺点的:每次随机选择的时候,并没有利用历史信息。在每一轮移除(evict)一个Key时,随机从N个里面选一个Key,移除idle time最大的那个Key;下一轮又是随机从N个里面选一个Key...有没有想过:在上一轮移除Key的过程中,其实是知道了N个Key的idle time的情况的,那我能不能在下一轮移除Key时,利用好上一轮知晓的一些信息?
- 当每一轮移除Key时,拿到了这个N个Key的idle time,如果它的idle time比 pool 里面的 Key的idle time还要大,就把它添加到pool里面去。这样一来,每次移除的Key并不仅仅是随机选择的N个Key里面最大的,而且还是pool里面idle time最大的,并且:pool 里面的Key是经过多轮比较筛选的,它的idle time 在概率上比随机获取的Key的idle time要大,可以这么理解:pool 里面的Key 保留了"历史经验信息"
- 采用"pool",把一个全局排序问题 转化成为了 局部的比较问题。(尽管排序本质上也是比较,囧)。要想知道idle time 最大的key,精确的LRU需要对全局的key的idle time排序,然后就能找出idle time最大的key了。但是可以采用一种近似的思想,即随机采样(samping)若干个key,这若干个key就代表着全局的key,把samping得到的key放到pool里面,每次采样之后更新pool,使得pool里面总是保存着随机选择过的key的idle time最大的那些key。需要evict key时,直接从pool里面取出idle time最大的key,将之evict掉。这种思想是很值得借鉴的。
二、redis与RocksDB对比
1.简介
RocksDB项目起源于Facebook的一个实验项目,该项目旨在开发一个与快速存储器(尤为是闪存)存储数据性能至关的数据库软件,以应对高负载服务。
这是一个c++库,可用于存储键和值,能够是任意大小的字节流。它支持原子读和写。
RocksDB具备高度灵活的配置功能,能够经过配置使其运行在各类各样的生产环境,包括纯内存,Flash,硬盘或HDFS。它支持各类压缩算法,并提供了便捷的生产环境维护和调试工具。
RocksDB借鉴了开源项目LevelDB的重要代码和Apache HBase项目的重要思想。最初的代码来源于开源项目leveldb 1.5分叉。它借鉴了了Facebook的代码和思想。linux
2.假设和目标
性能:
RocksDB的主要设计目标是保证存取快速存储器和高负载服务器更高效,保证充分利用Flash或RAM子系统提供的高速率读写,
支持高效的查找和范围scan,支持高负载的随机读、高负载的更新操做或二者的结合。其架构应该支持高并发读写和容量大增时系统的一致性。c++
产品支持:
RocksDB内置了用于生产环境中部署和调试的工具和实用程序。大多数主要参数应该是可调整的,以即可以被不一样的应用程序在不一样的硬件中使用。。算法
向后兼容性:
这个软件的新版本应该是向后兼容的,所以,当升级到新版本时现有的应用程序不须要改变。数据库
3.高级体系结构
RocksDB是一个嵌入式键值存储器,其中键和值是任意的字节流。RocksDB中的全部数据是按序存放的。常见操做包括Get(key), Put(key), Delete(key) and Scan(key)。
RocksDB有三个基本结构:RocksDB memtable,sstfile和logfile。memtable是一个内存数据结构——新数据会插入到memtable和日志文件(可选)。日志文件是
顺序写入的,位于磁盘。当memtable写满后,数据会被刷新到磁盘上的sstfile文件,同时相应的日志文件能够安全地删除。sstfile中的数据通过排序的,目的
是为了加快键查找。缓存
4.特性
Gets,迭代器和快照
键和值被视为纯字节流.没有大小的限制。Get接口容许应用程序从数据库中获取一个键值。MultiGet接口容许应用程序从数据库中检索多个键值。MultiGet接口返回的key-value对都是相互匹配的。
全部数据库中的数据都按顺序存放。应用程序能够指定一个key比较方法来定义key的排序顺序。迭代器容许应用程序对数据库进行RangeScan。迭代器能够先定位一个指定的键,而后应用程序就能够从这个定位点开始一个一个扫描key。迭代器还能够用来对key作反向迭代。建立迭代器是会建立当前数据库的一个快照视图,所以,经过迭代器返回的全部键都来自同一个数据库视图。
Snapshot容许应用程序建立一个快照视图。Get和迭代器能够从一个指定的快照读取数据。在某种意义上,Snapshot和迭代器都提供了某个时间点上数据库的快照视图,但二者的实现是不一样的。短暂的扫描最好经过迭代器而耗时较长的扫描最好经过快照。迭代器记录了数据库当前视图对应文件——直到迭代器被释放才删除这些删除。
而快照并不能阻止文件删除;相反,compaction流程知道当前的快照而且不会删除任何现有快照中的key。
数据库重启后,快照将丢失。重载RocksDB(经过服务器重启)会释放全部先前的快照。安全
前缀迭代器
大多数LSM引擎没法支持一个高效RangeScan,由于它须要查找每个数据文件。但大多数应用程序不会对key进行随机扫描,而更多的是扫描给定前缀的key。
RocksDB利用了这种优点。应用程序能够经过prefix_extractor指定一个key的前缀。RocksDB用此来保存每一个key前缀的bloom,指定了前缀(经过ReadOptions)的迭代器将使用bloom二进制位来避免查找不包含指定key前缀的文件。服务器
更新
Put操做向数据库插入单个key-value。若是键已经存在,旧值将被覆盖。Writer操做容许将多个keys-values原子地插入到数据库中。数据库保证同一个Writer操做中的全部keys-values要么所有出入,要么都不插入。若是其中任何一个键已经存在于数据库中,旧值将被覆盖。数据结构
持久性
Put操做数据会存储在内存中的缓冲区称为memtable,也会选择性地插入到事务日志。每个Put操做都有一组标志(经过WriteOptions设置),这些标志指定Put操做数据是否应该插入到事务日志。WriteOptions也能够指定在put操做提交前一个同步调用是否写入事务日志。
在内部,RocksDB使用batch-commit机制批量写入事务日志,这样它可使用一个同步调用提交多个事务。多线程
容错
RocksDB使用校验和检测数据是否正确。每一个块(一般是4k到128k大小)都有本身的校验和。一块数据一旦写入将不会修改。
RocksDB经过硬件支持动态获取校验和的计算结果,以免须要是本身计算校验和。架构
多线程Compaction
Compactions能够删除同一key的多个副本,副本是应用程序覆盖现有key是产生的。能够删除key。经过配置可让Compaction也多线程方式运行。
LSM的写数据的总体吞吐量直接取决于Compaction的速度,特别是当数据存储在SSD或RAM这种存储器中。RocksDB能够处理多个线程并发的omopaction请求。
多线程Compaction场景下写数据的速率比单线程场景下的速率快10倍。
整个数据库存储在一组sstfiles。memtable写满时,它的内容会被写入Level-0层的一个文件中,在此过程当中,重复和被覆盖的key会被删除。
一些文件被按期压缩合并造成更大的文件——这就是所谓的compaction。
RocksDB支持两种不一样形式的compaction。广泛的作法是将全部文件按时间顺序保存在L0。compaction选择几个彼此相邻的文件并将它们合并成一个新文件L0。全部文件能够有重叠的key。
分层形式的compaction将数据存储在数据库中的多个层中。最新的数据存储在L0和最旧的数据存储在Lmax。L0层中的文件能够有重叠的key,但其余层中文件的key不能重叠。一次compaction过程就是选择Ln层的一个文件及它在Ln+1层的全部重叠文件进行压缩合并造成Ln+1层的新文件。相比层形式的compaction方法,
广泛的compaction方法写数据性能较低,但空间利用率较高。
MANIFEST文件记录了数据库的状态。compaction在添加新文件和从数据库删除现有的文件后,会将这些操做记录到MANIFEST文件。事务日志是被批量提交到MANIFEST文件中的,目的是为了减小对MANIFEST文件的重复同步访问。
Compaction过滤器
一些应用程序可能须要在Compaction过程当中处理某些key。例如,一个支持TTL的数据库可能删除过时的key,这能够经过定义一个Compaction过滤器完成。
若是应用程序想要不断删除旧数据,可使用Compaction过滤掉丢弃过时的记录。RocksDB Compaction过滤器能够容许应用程序去修改对应key的value或做为Compaction过程的一部分直接丢弃key。
只读的模式
数据库能够在只读的模式下打开。只读模式下应用程序不能修改任何数据。这能够保证更高的读取性能,由于避免了代码执行路径的切换和锁的使用。
数据库调试日志
RocksDB的详细日志被写入到名为LOG*的文件中。这些日志用于调试和分析运行中的系统。能够按配置的指定周期记录日志。
数据压缩
RocksDB支持snappy,zlib,bzip2 lz4和lz4_hc压缩算法。对不一样层的数据能够配置不一样的压缩算法。通常来讲,90%的数据保存在Lmax层。
一个典型的安装多是L0-L2层不配置压缩算法,中间层用snappy压缩算法,而Lmax层采用zlib压缩。
事务日志
RocksDB将事务日志保存在logfile文件中以防止系统崩溃。系统启动时会从新处理日志文件。logfile和_sstfile_s能够存放在不一样目录下,好比下面的场景,
当你但愿将全部数据文件存储在非持久但快速的存储设备中,同时把事务日志保存在存取速度慢但持久的存储设备中。
全量备份、增量备份和复制
RocksDB支持全量备份和增量备份。RocksDB是一个LSM数据库引擎,所以一旦被建立,数据文件从不会被覆盖,这使得获取某个时间点数据库快照很容易。DisableFileDeletions接口能够禁止RocksDB删除数据文件。Comopaction进行中,数据库中的废弃数据不会被删除。备份接口GetLiveFiles / GetSortedWalFiles能够提取数据库中现有文件并将数据复制到备份位置。
备份完成后,EnableFileDeletions接口可使能数据文件删除功能;数据库如今就能够回收不须要的全部文件。
增量备份和复制须要可以识别数据库的最近修改。GetUpdatesSince接口容许应用程序tail(理解同linux tail命令) RocksDB事务日志。它能够接二连三地获取RocksDB事务日志并将它们发送带远程备份端或远程复制端。
复制系统一般向Put操做增长元数据,这些元数据能够用来检测管道复制中的数据循环,也能够用于给事务打时间戳和按顺序排序事务。
这些元数据只存储在事务日志,而不存储在数据文件中。PutLogData接口用来添加元数据,GetUpdatesSince用于获取元数据。
RocksDB事务日志是在数据库目录中建立的。再也不须要的日志文件会被移动到归档目录。归档文件目录存在的缘由是由于一个复制速度较慢的复制流可能须要从日志文件中检索过去的事务。
GetSortedWalFiles接口返回事务日志文件的列表。
同一进程中支持多种嵌入式数据库
RocksDB一个常见的用法是应用程序将数据集划分为逻辑分区或分片。这种技术的有利于应用程序保持负载均衡和快速恢复故障。这意味着一个服务器进程必须可以同时操做多个RocksDB数据库。
这是经过环境变量Env实现的。除此以外,一个线程池与Env相关联。若是应用程序想要在多个数据库实例间共享同一个线程池,那么它应该使用相同的Env对象打开这些数据库。
一样,多个数据库实例能够共享同一缓存块。
缓存块——压缩的和未压缩的数据块
RocksDB使用LRU缓存块读取数据。缓存块被划分为两个单独的缓存:第一个缓存数据未压缩,第二个缓存数据压缩。若是缓存块配置了压缩,那么数据库不会在操做系统缓冲区中缓存数据。
表缓存
表缓存用于存放缓存打开的sstfiles文件描述符。应用程序能够指定表缓存的最大大小。
外部Compaction算法
LSM数据库的性能极大地依赖于Compaction算法及其实现。RocksDB支持两种Compaction压缩算法:层次类型和广泛类型的。开发人员能够开发和测试其余的Compaction算法。出于这个缘由,RocksDB提供了关闭内置Compaction算法的接口和应用开发者本身Compaction算法的接口。
若是设置了disable_auto_compaction选项,本地Compaction算法将被禁止使用。GetLiveFilesMetaData接口容许外部组件查看数据库中的每一个数据文件并决定合并和Compaction哪些数据文件。
DeleteFile接口容许应用程序删除过期的数据文件。
非阻塞数据库访问
一些应用程序架构以非阻塞方式访问数据库的,也就是说数据检索请求不须要从存储器读取数据。RocksDB在块缓存中缓存了数据库部分数据,若是应用程序从块缓存中查找到须要的数据,就从块缓存中读取数据;若是在块缓存中
没有找到,RocksDB返回适当的错误代码给应用程序。经过错误码,应用程序知道数据检索调用可能会阻塞存储IO(也许在一个不一样的线程上下文中),从而利用普通的Get/Next去获取数据。。
内存表:
可插入内存表:
内存表的默认实现是跳跃表skiplist。skiplist一个有序集合,这种结构有利于进行数据范围扫描。有些应用程序不交错写数据或扫描数据,有些应用程序不作范围扫面。
对于这些应用程序,有序集合没法提供最佳性能。出于这个缘由,RocksDB提供可插入接口容许应用程序本身实现内存表。数据库通常包含三种内存表:跳跃表内存表,向量内存表和prefix-hash内存表。
向量内存表适合批量加载数据。每次都是在向量的末尾插入一个新元素,每当要将内存表中数据写入到磁盘时,向量中的元素经排序后写入L0层的文件中。prefix-hash内存表适用于快速gets、puts和按key前缀扫描。
内存表管道
RocksDB支持配置任意数量的memtables。当内存表写满后,它将成为一个不可变的内存表,这时会启动后台线程开始将内存表中的数据刷新到磁盘。与此同时,新的数据会被写入新分配的内存表。若是新分配内存表写满到了容量极限,它也转化成不可变内存表并被插入到输出管道中。后台线程继续将全部管道的不可变内存表刷新到磁盘。这种管道写数据方式增长了RocksDB写数据的吞吐量。特别是当操做缓慢的存储设备时,这种方式更高效。
内存表Compaction:
当内存表被刷新到磁盘时,inline-compaction进程会删除输出流中的重复记录。一样,后来的删除操做会隐藏先来的插入操做,此插入操做不会被写入输出文件。这个特性下降了磁盘上的数据大小同时增长了数据写入量。当RocksDB用做生产者消费者队列尤为是当队列中元素存活时间很是短时,这个特性很是重要。
5.工具
有许多有趣的工具,用于支持生产环境上的数据库。sst_dump用于转储全部keys-values到sst文件中。ldb的工具能够put、get和scan数据库的内容。ldb也能够转储MANIFEST文件
的内容,也能够用来改变数据库中配置的层数。这能够用来手动压缩数据库。
6.测试
提供了不少测试数据库特定特性的单元测试。make check命令运行全部单元测试。这些单元测试用于测试RocksDB的特定特性,
不用于大规模数据正确性的测试场景。db_stress用来测试大规模数据的正确性。
7.性能
RocksDB性能是经过db_bench作基准测试的。db_bench是RocksDB源代码的一部分,典型场景的性能测试结果能够参考wiki。
三、redis与RocksDB对比
KeyDB项目是从redis fork出来的分支。众所周知redis是一个单线程的kv内存存储系统,而KeyDB在100%兼容redis API的情况下将redis改造成多线程。
网上公开的技术细节比较少,本文基本是通过阅读源码总结出来的,如有错漏之处欢迎指正。
多线程架构
线程模型
KeyDB将redis原来的主线程拆分成了主线程和worker线程。每个worker线程都是io线程,负责监听端口,accept请求,读取数据和解析协议。如图所示:
KeyDB使用了SO_REUSEPORT特性,多个线程可以绑定监听同个端口。
每个worker线程做了cpu绑核,读取数据也使用了SO_INCOMING_CPU特性,指定cpu接收数据。
解析协议之后每个线程都会去操作内存中的数据,由一把全局锁来控制多线程访问内存数据。
主线程其实也是一个worker线程,包括了worker线程的工作内容,同时也包括只有主线程才可以完成的工作内容。在worker线程数组中下标为0的就是主线程。
主线程的主要工作在实现serverCron,包括:
处理统计
客户端链接管理
db数据的resize和reshard
处理aof
replication主备同步
cluster模式下的任务
链接管理
在redis中所有链接管理都是在一个线程中完成的。在KeyDB的设计中,每个worker线程负责一组链接,所有的链接插入到本线程的链接列表中维护。链接的产生、工作、销毁必须在同个线程中。每个链接新增一个字段
int iel; /* the event loop index we're registered with */
用来表示链接属于哪个线程接管。
KeyDB维护了三个关键的数据结构做链接管理:
clients_pending_write:线程专属的链表,维护同步给客户链接发送数据的队列
clients_pending_asyncwrite:线程专属的链表,维护异步给客户链接发送数据的队列
clients_to_close:全局链表,维护需要异步关闭的客户链接
分成同步和异步两个队列,是因为redis有些联动api,比如pub/sub,pub之后需要给sub的客户端发送消息,pub执行的线程和sub的客户端所在线程不是同一个线程,为了处理这种情况,KeyDB将需要给非本线程的客户端发送数据维护在异步队列中。
同步发送的逻辑比较简单,都是在本线程中完成,以下图来说明如何同步给客户端发送数据:
如上文所提到的,一个链接的创建、接收数据、发送数据、释放链接都必须在同个线程执行。异步发送涉及到两个线程之间的交互。KeyDB通过管道在两个线程中传递消息:
int fdCmdWrite; //写管道
int fdCmdRead; //读管道
本地线程需要异步发送数据时,先检查client是否属于本地线程,非本地线程获取到client专属的线程ID,之后给专属的线程管到发送AE_ASYNC_OP::CreateFileEvent的操作,要求添加写socket事件。专属线程在处理管道消息时将对应的请求添加到写事件中,如图所示:
redis有些关闭客户端的请求并非完全是在链接所在的线程执行关闭,所以在这里维护了一个全局的异步关闭链表。
锁机制
KeyDB实现了一套类似spinlock的锁机制,称之为fastlock。
fastlock的主要数据结构有:
struct ticket
{
uint16_t m_active; //解锁+1
uint16_t m_avail; //加锁+1
};
struct fastlock
{
volatile struct ticket m_ticket;
volatile int m_pidOwner; //当前解锁的线程id
volatile int m_depth; //当前线程重复加锁的次数
};
使用原子操作__atomic_load_2,__atomic_fetch_add,__atomic_compare_exchange来通过比较m_active=m_avail判断是否可以获取锁。
fastlock提供了两种获取锁的方式:
try_lock:一次获取失败,直接返回
lock:忙等,每1024 * 1024次忙等后使用sched_yield 主动交出cpu,挪到cpu的任务末尾等待执行。
在KeyDB中将try_lock和事件结合起来,来避免忙等的情况发生。每个客户端有一个专属的lock,在读取客户端数据之前会先尝试加锁,如果失败,则退出,因为数据还未读取,所以在下个epoll_wait处理事件循环中可以再次处理。
Active-Replica
KeyDB实现了多活的机制,每个replica可设置成可写非只读,replica之间互相同步数据。主要特性有:
每个replica有个uuid标志,用来去除环形复制
新增加rreplay API,将增量命令打包成rreplay命令,带上本地的uuid
key,value加上时间戳版本号,作为冲突校验,如果本地有相同的key且时间戳版本号大于同步过来的数据,新写入失败。采用当前时间戳向左移20位,再加上后44位自增的方式来获取key的时间戳版本号。
结束语
云数据库Redis版(ApsaraDB for Redis)是一种稳定可靠、性能卓越、可弹性伸缩的数据库服务。基于飞天分布式系统和全SSD盘高性能存储,支持主备版和集群版两套高可用架构。提供了全套的容灾切换、故障迁移、在线扩容、性能优化的数据库解决方案。欢迎各位购买使用:云数据库 Redis 版
四、redis与DynamoDB对比
五、redis与RocksDB对比