Redis 是一个开源的,内存中的数据结构存储系统,它能够用做数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询等。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不一样级别的 磁盘持久化(persistence), 并经过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。前端
缓存是高并发场景下提升热点数据访问性能的一个有效手段,在开发项目时会常用到。redis
缓存的类型分为:本地缓存、分布式缓存和多级缓存。算法
本地缓存 就是在进程的内存中进行缓存,好比咱们的 JVM 堆中,本地缓存是内存访问,没有远程交互开销,性能最好,可是受限于单机容量,通常缓存较小且没法扩展。数据库
分布式缓存通常都具备良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是须要进行远程请求,性能不如本地缓存。数组
为了平衡这种状况,实际业务中通常采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其余的热点数据放在分布式缓存中。缓存
数据结构:Memcached只支持简单的key/value数据结构,不像Redis能够支持丰富的数据类型。服务器
持久化:Memcached没法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据所有丢失。网络
多线程:Redis 使用单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。数据结构
Memcache 存在着支持并发性很差、可运维性欠佳、原子性操做不够、在误操做时产生数据不一致等问题。多线程
因为 Redis 只使用单核,而 Memcached 可使用多核,因此 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,可是比起 Memcached,仍是稍有逊色。
分布式:Redis原生支持集群模式,Memcached没有原生的集群模式。
Redis 支持经过Replication进行数据复制,经过master-slave机制,能够实时进行数据的同步复制,支持多级复制和增量复制,master-slave机制是Redis进行HA的重要手段。
file event handler
,这个文件事件处理器是单线程的,因此 Redis 才叫作单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 Socket 可能会并发产生不一样的操做,每一个操做对应不一样的文件事件,可是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
为何Redis单线程也能效率这么高?
基本:String、Hash、List、Set、SortedSet
进阶:HyperLogLog、Geo、Pub/Sub、bitmaps
高级:BloomFilter
String:存储简单的对象序列化字符串,应用场景:缓存热点数据、计数器、会话token
Hash:保存无嵌套的对象属性
List:列表,应用场景:简单消息队列、分页
Set:无序集合,自动去重,应用场景:共同好友
Sorted Set:有序集合,自动去重,应用场景:排行榜、微博热搜
Geo:能够用来保存地理位置,并做位置距离计算或者根据半径计算位置等。 应用场景:附近的人
Pub/Sub:订阅/发布,应用场景:简单消息队列
HyperLogLog:用来作基数统计的算法 ,好比数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数统计就是在偏差可接受的范围内,快速计算基数。 应用场景:日活跃用户
Bitmap:支持按bit位来存储信息,等同于byte数组,计数效率高,应用场景:日活跃用户、布隆过滤器
bitmaps存储一亿用户须要12.5M内存
BloomFilter
布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。 存在误判,可能要查到的元素并无在容器中,可是hash以后获得的k个位置上值都是1。若是bloom filter中存储的是黑名单,那么能够经过创建一个白名单来存储可能会误判的元素。删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其余元素的判断。
布隆过滤器的原理是,当一个元素被加入集合时,经过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,咱们只要看看这些点是否是都是1就(大约)知道集合中有没有它了:若是这些点有任何一个0,则被检元素必定不在;若是都是1,则被检元素极可能在。这就是布隆过滤器的基本思想。
Bloom Filter跟单哈希函数Bit-Map不一样之处在于:Bloom Filter使用了k个哈希函数,每一个字符串跟k个bit对应。从而下降了冲突的几率。
Redis 用SDS(Simple Dynamic String)来保存字符串,SDS还被用做缓冲区(buffer)AOF模块中的AOF缓冲区。
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
复制代码
使用SDS的好处:
List的底层实现之一就是链表
typedef struct listNode{
struct listNode *prev;
struct listNode * next;
void * value;
}
复制代码
整数集合是集合(set)的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量很少时,redis就会使用整数集合intset做为集合的底层实现。
typedef struct intset{
//编码方式
uint32_t enconding;
// 集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
}
复制代码
整数集合的底层实现为数组,这个数组以有序,无重复的范式保存集合元素,在有须要时,程序会根据新添加的元素类型改变这个数组的类型。
字典,又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对的抽象数据结构。相似HashMap,当发生哈希冲突时,采用头插法向单向链表表头插入元素。
rehash的时候将ht[0]数据从新分配到ht[1]中,将ht[0]释放,将ht[1]设置成ht[0],最后为ht[1]分配一个空白哈希表。
渐进式rehash 的详细步骤:
一、为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
二、在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash 开始
三、在rehash 进行期间,每次对字典执行CRUD操做时,程序除了执行指定的操做之外,还会将ht[0]中的数据rehash 到ht[1]表中,而且将rehashidx加1
四、当ht[0]中全部数据转移到ht[1]中时,将rehashidx 设置成-1,表示rehash 结束
采用渐进式rehash 的好处在于它采起分而治之的方式,避免了集中式rehash 带来的庞大计算量。
跳表(skiplist)是一种有序数据结构,它经过在每一个节点中维持多个指向其余节点的指针,从而达到快速访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操做均可以在对数指望时间下完成,而且比起平衡树来讲,跳表的实现要简单直观得多。
Redis 只在两个地方用到了跳表,一个是实现有序集合键,另一个是在集群节点中用做内部数据结构。
RDB作镜像全量持久化,AOF作增量持久化。由于RDB会耗费较长时间,不够实时,在停机的时候会致使大量丢失数据,因此须要AOF来配合使用。
RDB对Redis的性能影响很是小,是由于在同步数据的时候他只是fork了一个子进程去作持久化的,fork是指redis经过建立子进程来进行RDB操做,cow指的是copy on write,子进程建立后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
RDB在数据恢复时比AOF快,由于数据文件小,每条记录只保存了一次,AOF一条记录可能保存屡次操做记录。RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不须要再进行数据编码工做,因此在CPU消耗上要远小于AOF日志的加载。
缺点:快照截屏间隔可能较久,若是采用RDB进行持久化,服务挂掉可能形成更多数据的丢失;在生成快照时若是文件很大可能致使客户端卡顿
SAVE命令由服务器进程直接执行保存操做,会阻塞服务器。BGSAVE命令由子进程执行保存操做,不会阻塞服务器。
服务器状态中会保存全部用save选项设置的保存条件,当任意一个保存条件被知足时,服务器会自动执行BGSAVE命令。
根据默认配置,RDB 五分钟一次生成快照,可是 AOF 是一秒一次去经过一个后台的线程fsync
操做,那最多丢这一秒的数据。
AOF在对日志文件进行操做的时候是以 append-only
的方式去写的,他只是追加的方式写数据,天然就少了不少磁盘寻址的开销了,写入性能惊人,文件也不容易破损。
AOF的日志是经过一个叫 很是可读 的方式记录的,这样的特性就适合作 灾难性数据误删除 的紧急恢复了,好比公司的实习生经过 flushall 清空了全部的数据,只要这个时候后台重写还没发生,你立刻拷贝一份 AOF 日志文件,把最后一条 flushall 命令删了就完事了。
缺点:同样的数据,AOF 文件比 RDB 还要大;AOF开启后,Redis支持写的QPS会比RDB支持写的要低,由于每秒异步刷新一第二天志
AOF文件经过保存全部修改数据库的写命令请求来记录服务器的数据库状态;命令请求会先保存到AOF缓冲区里面,以后再按期写入并同步到AOF文件。
AOF重写 首先从数据库中读取键如今的值,而后用一条命令去记录键值对,代替以前记录这个键值对的多条命令 ,产生一个新的AOF文件,新AOF文件和原AOF保存的数据库状态同样,但体积更小;
在执行BGREWRITEAOF命令时,Redis会维护一个AOF重写缓冲区,该缓冲区会在子进程建立新的AOF文件期间,记录服务器执行的全部写命令。当子进程完成建立新AOF文件以后,服务器会将重写缓冲区中的全部内容追加到新的AOF文件的末尾。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操做。
no-appendfsync-on-rewrite
是否在后台写时阻塞,默认值no(表示阻塞写操做)。no表示新的主进程的set操做会被阻塞掉,而yes表示新的主进程的set不会被阻塞,待整个后台写完成以后再将这部分set操做同步到aof文件中。但这可能会存在数据丢失的风险(机率很小),若是对性能有要求,能够设置为yes,仅在后台写时会异步处理命令。
auto-aof-rewrite-percentage
AOF文件的体积比上一次重写以后的增加比例,假设用户对Redis设置了配置选项auto-aof-rewrite-percentage 100,那么当AOF文件的体积比上一次重写以后的文件大小大了至少一倍(100%)的时候,Redis将执行BGREWRITEAOF命令。
auto-aof-rewrite-min-size
触发AOF文件重写的最小的文件大小,即最开始AOF文件必需要达到这个文件大小时才触发重写,后面的每次重写就不会根据这个变量了(根据上一次重写完成以后的大小) 。
Redis键的过时策略,是有按期删除+惰性删除两种。
按期好理解,默认100ms就 随机 抽一些设置了过时时间的key,去检查是否过时,过时了就删了。
惰性删除,查询时再判断是否过时,过时就删除键不返回值。
当新增数据发现内存达到限制时,Redis触发内存淘汰机制。
maxmemory
配置Redis存储数据时指定限制的内存大小,好比100m。当缓存消耗的内存超过这个数值时, 将触发数据淘汰。该数据配置为0时,表示缓存的数据量没有限制, 即LRU功能不生效。64位的系统默认值为0,32位的系统默认内存限制为3GB。
maxmemory_policy
触发数据淘汰后的淘汰策略
maxmemory_samples
随机采样的精度,也就是随即取出key的数目。该数值配置越大, 越接近于真实的LRU算法,可是数值越大,相应消耗也变高,对性能有必定影响,样本值默认为5。
真实LRU算法须要一个双向链表来记录数据的最近被访问顺序,比较耗费内存。
Redis 经过对少许键进行取样,而后回收其中的最久未被访问的键。经过调整每次回收时的采样数量maxmemory-samples,能够实现调整算法的精度。
Redis 的键空间是放在一个哈希表中的,要从全部的键中选出一个最久未被访问的键,须要另一个数据结构存储这些源信息,这显然不划算。最初,Redis只是随机的选3个key,而后从中淘汰,后来算法改进到N个key的策略,默认是5个。
Redis 3.0以后又改善了算法的性能,会提供一个待淘汰候选key的pool,里面默认有16个key,按照空闲时间排好序。更新时从Redis键空间随机选择N个key,分别计算它们的空闲时间 idle,key只会在pool不满或者空闲时间大于pool里最小的时,才会进入pool,而后从pool中选择空闲时间最大的key淘汰掉。
Redis为何不使用真实的LRU实现是由于这须要太多的内存。不过近似的LRU算法(approximated LRU)对于应用而言应该是等价的。
Redis支持三种分布式部署的方式:主从复制、哨兵模式、集群模式
Redis可使用主从同步,从从同步。第一次同步时,主节点作一次bgsave,并同时将后续修改操做记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操做记录同步到复制节点进行重放就完成了同步过程。后续的增量数据经过AOF日志同步便可,有点相似数据库的binlog。
能够进行读写分离,分担了主服务器读操做的压力
Redis不具有自动容错和恢复功能,主机从机的宕机都会致使前端部分读写请求失败,较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
当主服务器中断服务后,能够将一个从服务器升级为主服务器,以便继续提供服务,可是这个过程须要人工手动来操做。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
哨兵的做用就是监控Redis系统的运行情况。它的功能包括如下两个。
(1)监控主服务器和从服务器是否正常运行。 (2)主服务器出现故障时自动将从服务器转换为主服务器。
Sentinel是Redis的高可用性(HA)解决方案,由一个或多个Sentinel实例组成的Sentinel系统能够监视任意多个主服务器,以及这些主服务器属下的全部从服务器,并在被监视的主服务器进行下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,而后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis提供的sentinel(哨兵)机制,经过sentinel模式启动redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决,每一个sentinel只有一次选举的机会,当主库出现故障,哨兵会投票从库中选出一个承担主库的任务,剩下的仍是从库
redis集群是一个由多个主从节点群组成的分布式服务器群,它具备复制、高可用和分片特性。Redis集群不须要sentinel哨兵也能完成节点移除和故障转移的功能。须要将每一个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称能够线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于以前版本的哨兵模式,且集群配置很是简单。
集群模式有如下几个特色:
在哨兵模式中,仍然只有一个Master节点。当并发写请求较大时,哨兵模式并不能缓解写压力。 咱们知道只有主节点才具备写能力,那若是在一个集群中,可以配置多个主节点,缓解写压力,redis-cluster集群模式能达到此类要求。
在Redis-Cluster集群中,能够给每个主节点添加从节点,主节点和从节点直接遵循主从模型的特性。
当用户须要处理更多读请求的时候,添加从节点能够扩展系统的读性能。
Redis集群的主节点内置了相似Redis Sentinel的节点故障检测和自动故障转移功能,当集群中的某个主节点下线时,集群中的其余在线主节点会注意到这一点,并对已下线的主节点进行故障转移。 集群进行故障转移的方法和Redis Sentinel进行故障转移的方法基本同样,不一样的是,在集群里面,故障转移是由集群中其余在线的主节点负责进行的,因此集群没必要另外使用Redis Sentinel。
常见的集群分片算法有:通常哈希算法、一致性哈希算法以及Hash Slot算法,Redis采用的是Hash Slot
计算方式:hash(key)%N
缺点:若是增长一个redis,映射公式变成了 hash(key)%(N+1)
若是一个redis宕机了,映射公式变成了 hash(key)%(N-1)
在以上两种状况下,几乎全部的缓存都失效了。
先构造出一个长度为2^32整数环,根据节点名称的hash值(分布在[0,2^32-1])放到这个环上。如今要存放资源,根据资源的Key的Hash值(也是分布在[0,2^32-1]),在环上顺时针的找到离它最近的一个节点,就创建了资源和节点的映射关系。
优势:一个节点宕机时,上面的数据转移到顺时针的下一个节点中,新增一个节点时,也只须要将部分数据迁移到这个节点中,对其余节点的影响很小
删除一个节点
缺点:因为数据在环上分布不均,可能存在某个节点存储的数据比较多,那么当他宕机的时候,会致使大量数据涌入下一个节点中,把另外一个节点打挂了,而后全部节点都挂了
改进:引进了虚拟节点的概念,想象在这个环上有不少“虚拟节点”,数据的存储是沿着环的顺时针方向找一个虚拟节点,每一个虚拟节点都会关联到一个真实节点
Redis采用的是Hash Slot分片算法,用来计算key存储位置的。集群将整个数据库分为16384个槽位slot,全部key-value数据都存储在这些slot中的某一个上。一个slot槽位能够存放多个数据,key的槽位计算公式为:slot_number=CRC16(key)%16384,其中CRC16为16位的循环冗余校验和函数。
客户端可能会挑选任意一个redis实例去发送命令,每一个redis实例接收到命令,都会计算key对应的hash slot,若是在本地就在本地处理,不然返回moved给客户端,让客户端进行重定向到对应的节点执行命令
若是大量的key过时时间设置的过于集中,到过时的那个时间点,Redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,咱们通常须要在时间上加一个随机值,使得过时时间分散一些。
电商首页常常会使用定时任务刷新缓存,可能大量的数据失效时间都十分集中,若是失效时间同样,又恰好在失效的时间点大量用户涌入,就有可能形成缓存雪崩。
缓存雪崩:大量缓存的key同时失效,同时大批量的请求落到了数据库上,数据库扛不住挂了。(处理方法:失效时间加上一个随机值,避免同时失效)
缓存穿透:用户不断访问缓存和数据库都没有的数据。(处理方式:请求参数校验,将查询不到数据的key放到缓存中,value设为null)
缓存击穿:对于热点数据,若是一直有高并发的请求,恰好缓存失效,这时大量的请求就会落在数据库上(设置热点期间不过时)
Redis的单线程的。keys指令会致使线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。 使用scan指令能够无阻塞的提取出指定模式的key列表,可是会有必定的重复几率,在客户端作一次去重就能够了,可是总体所花费的时间会比直接用keys指令长 。
虽然redis实现了在读写操做时,辅助服务器进行渐进式rehash操做,可是若是服务器比较空闲,redis数据库将很长时间内都一直使用两个哈希表。因此在redis周期函数中,若是发现有字典正在进行渐进式rehash操做,则会花费1毫秒的时间,帮助一块儿进行渐进式rehash操做
Redis持久化的数据库文件便是RDB文件,若是开启了AOF,数据则持久化在AOF文件中
最近开始写博客文章,朋友们要是以为写得还能够,拜托你们点个赞让我知道本身没有白白花费时间,大家的赞是对我莫大的鼓舞,你们有什么意见建议也欢迎在评论区共同探讨。
不以浮沙筑高台,与诸君共勉。