redis详解

Redis优势

速度快:读写性能达到10万/秒

所有数据存放在内存 使用C语言实现,距离操作系统更近 单线程架构,避免多线程产生的竞争问题,以及多线程切换性能问题

支持多种数据结构

支持字符串、哈希、列表、集合、有序集合,并演变出了Bitmaps、HyperLogLog、GEO结构

功能丰富

提供了键过期功能,可以用来实现缓存 提供了发布订阅功能,可以用来实现消息系统 支持Lua脚本功能,可以利用Lua创造出新的Redis命令 提供了简单的事务功能,能在一定程度上保证事务特性 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到Redis,减少了网络的开销。

提供多种客户端语言实现

提供了简单的TCP通信协议,许多编程语言可以便捷接入Redis,如Java、PHP、C、C++、Nodejs等

持久化

将数据放在内存中是不安全的,断电或故障数据可能会丢失。Redis提供了RDB和AOF两种将数据保存到硬盘中的策略

主从复制

Redis实现了复制功能,实现了多个相同数据的Redis副本

高可用和分布式

提供了高可用实现RedisSentinel、Redis Cluster

Redis常用数据结构及使用场景

Redis中5种基本数据结构分别是string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景会选择合适的内部编码,Redis的设计者实现了数据结构与命令的解耦,可以在改进内部编码而对外的数据结构和命令没有影响 String 字符串类型的值实际可以是字符串、Json、数字(整数、浮点数),甚至是二进制编码等,最大值不能超过512MB。

内部编码:

int:8个字节的长整型。embstr:小于等于39个字节的字符串。raw:大于39个字节的字符串。

Redis会根据当前值的类型和长度决定使用哪种内部编码实现

使用场景:

String是Redis缓存最常用的key/value数据结构,通常在一些场景中,会将一些热点数据进行缓存,一个请求进来先请求缓存,缓存不存在再查询DB,再写入缓存,返回 计数器,使用INCR命令将Key值对应的value加1,可用于统计视频播放量,系统限流等

List list是有序的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部或者尾部,也可以修改删除list中的元素,一个列表最多可以包含 2的32次方 - 1 个元素

内部编码:

·ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现 使用场景:

分页,基于list的有序,最常见的场景是使用lrange 命令实现高性能分页,类似一些网站里展示的不断下拉分页功能

消息队列,因为list是有序的,所以可以使用lpush、brpop命令插入或弹出元素,实现消息队列的功能

Hash hash 是一个 string 类型的 field(字段,注意不是key) 和 value(值) 的映射表,hash 特别适合用于存储对象。最多可以包含 2的32次方 - 1 个元素

内部编码:

ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1

使用场景:目前想到的是,对象需要序列化并持久化到数据库是,可以考虑使用Redis Hash的结构替换

Set set可以用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的

内部编码:

intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

使用场景:可以基于多个 set集合,进行一些交集、并集、差集的数据统计

Sorted Set Sorted Set是一种有序集合结构,同样不允许重复数据,元素可以排序,但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据

内部编码:

ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

使用场景:

排行榜

Redis缓存雪崩、穿透、击穿、并发竞争

Redis缓存雪崩

场景描述 :假设某系统每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机,缓存挂了,此时 5000 个请求全部落数据库。缓存在某一瞬间全部失效,大量请求全部打到数据库上,这就是缓存雪崩 解决方案:

Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃 系统限流 对于设定了同一过期时间的缓存,可以在过期时间中加入随机数,避免缓存同时失效造成数据库压力过大

Redis穿透

场景描述 :正常的使用缓存流程是对数据查询先进行缓存查询,如果 key 不存在或者key 过期,再对数据库进行查询,并把查询到的对象,放进缓存,如果数据库查询对象为空,则不放进缓存。大批量恶意查询一个数据库中一定不存在的数据,压力就会穿透缓存,打到数据库上。解决方案:

在接口层增加校验。不合法的参数直接返回 缓存空对象。缓存查不到,DB 中也没有的情况,可以将对应的 key 的 value 写为 null 。此方案使适用于数据频繁变化实时性高的场景,同时会导致一定时间内数据的一致性问题,可能存储层有但缓存中却为null 使用高级用户布隆过滤器(Bloom Filter)。将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力,Bloom Filter能够利用高效的数据结构和算法快速判断出你这个 Key 是否在 DB 中存在,不存在则返回,存在则查询DB刷新KV再返回,此方案适用于数据固定实时性相对低的场景

Redis击穿

场景描述 :热点数据缓存在某个时间点过期,恰好在这个时间点有大量并发请求这个key,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,大量请求会造成数据库压力剧增。解决方案:

互斥锁:可使用Redis的SETNX去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法

Redis并发竞争

场景描述 :多系统实例短时间内需要按顺序操作Redis中同一个key,可能因为网络抖动等原因导致Redis写数据顺序错误 解决方案:

分布式锁:在业务层进行控制,每个系统在操作Redis前获取分布式锁,拿到锁才能操作,确保同一时间,只有一个系统在操作某个key 时间戳:在写入时保存一个时间戳,写入前先比较自己的时间戳是不是早于现有记录的时间戳,如果早于,就不写入 消息队列:使用消息队列进行串行处理

Redis 持久化策略之RDB与AOF 持久化主要用做灾难恢复、数据恢复。Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复 RDB RDB持久化是把当前进程数据生成快照保存到硬盘的过程,通常通过bgsave命令对Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束

RDB的优点:

RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景 ·Redis加载RDB恢复数据远远快于AOF的方式 RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis 保持高性能,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。

RDB的缺点:

RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高 RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题

AOF

以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)

AOF 优点

AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据。AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。

AOF缺点

对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,Redis 性能会大大降低) 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 merge 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

Redis 数据过期策略

Redis 过期策略是:定期删除+惰性删除。

定期删除,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。惰性删除,在获取某个 key 的时候,Redis 会检查这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西

当定期删除漏掉了很多过期 key,然后你也没及时去查,也也未发生惰性删除时,大量过期 key 堆积在内存里,导致 Redis 内存块耗尽,此时会触发内存淘汰机制

内存淘汰机制

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

对RDB的影响 持久化到RDB:在生成RDB文件是会先检查key是否过期,如过期则不会进入RDB 从RDB文件恢复:先对key先进行过期检查,如果过期,不导入数据库(主库情况)。对AOF的影响 持久化数据到AOF:当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令) 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉) AOF重写:重写时,会先判断key是否过期,已过期的key不会重写到aof文件 Redis高可用方案 Redis 主从 Redis主从的结构中,主节点可以进行读、写操作,而从节点只能进行读操作。由于主节点可以写,数据会发生变化,当主节点的数据发生变化时,会将变化的数据同步给从节点,这样从节点的数据就可以和主节点的数据保持一致了(基于Redis提供的replication功能)。一个主节点可以有多个从节点,但是一个从节点会只会有一个主节点。Redis主从会先尝试进行增量同步,如不成功,会进行全量同步 系统运行时,如果master挂掉了,可以在一个从库(如slave1)上手动执行命令slaveof no one,将slave1变成新的master;在其他从节点分别执行slaveof IP:PORT 将这机器的主节点指向的这个新的master;同时,挂掉的原master启动后作为新的slave也指向新的master上。

优点

支持主从复制,主机会自动将数据同步到从机,可以进行读写分离; 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务依然必须由Master来完成; Slave同样可以接受其他Slaves的连接和同步请求,这样可以有效地分载Master的同步压力; Master是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求; Slave同样是以阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。

缺点

Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复; 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性; 如果多个Slave断线了,需要重启的时候,尽量不要在同一时间段进行重启。因为只要Slave启动,就会发送sync请求和主机全量同步,当多个Slave重启的时候,可能会导致Master IO剧增从而宕机。Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂; Redis的主节点和从节点中的数据是一样的,降低的内存的可用性

Redis Sentinel(哨兵) 主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master宕机,哨兵会自动选举master并将其他的slave指向新的master 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器;当哨兵监测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让它们切换主机;然而一个哨兵进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

优点

哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。

缺点

具有主从模式的缺点,每台机器上的数据是一样的,内存的可用性较低。Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

Redis Cluster Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,对数据进行分片,也就是说每台 Redis 节点上存储不同的内容,对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。集群部署至少要 3 台以上的master节点,最好使用 3 主 3 从六个节点的模式 redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。当数据写入到对应的master节点后,这个数据会同步给这个master对应的所有slave节点。为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点。当其它主节点ping主节点master 1时,如果半数以上的主节点与master 1通信超时,那么认为master 1宕机了,就会启用master 1的从节点slave 1,将slave 1变成主节点继续提供服务。如果master 1和它的从节点slave 1都宕机了,整个集群就会进入fail状态,因为集群的slot映射不完整。如果集群超过半数以上的master挂掉,无论是否有slave,集群都会进入fail状态。redis-cluster采用去中心化的思想,没有中心节点的说法,客户端与Redis节点直连,不需要中间代理层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

优点

采用去中心化思想,数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布; 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除; 高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升; 降低运维成本,提高系统的扩展性和可用性。

缺点

Redis Cluster是无中心节点的集群架构,依靠Goss协议(谣言传播)协同自动化修复集群的状态 但 GosSIp有消息延时和消息冗余的问题,在集群节点数量过多的时候,节点之间需要不断进行 PING/PANG通讯,不必须要的流量占用了大量的网络资源。虽然Reds4.0对此进行了优化,但这个问题仍然存在。数据迁移问题 Redis Cluster可以进行节点的动态扩容缩容,这一过程,在目前实现中,还处于半自动状态,需要人工介入。在扩缩容的时候,需要进行数据迁移。而 Redis为了保证迁移的一致性,迁移所有操作都是同步操作,执行迁移时,两端的 Redis均会进入时长不等的阻塞状态,对于小Key,该时间可以忽略不计,但如果一旦Key的内存使用过大,严重的时候会接触发集群内的故障转移,造成不必要的切换。

你可能感兴趣的:(redis)