赞
踩
目录
11.Redis的过期策略有哪些?大量key集中过期导致卡顿如何解决?
①Redis数据结构更丰富,支持string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)等数据结构存储,Memcached仅支持String数据类型。
②Redis支持数据的持久化,可以把内存中的数据持久化到硬盘中,而Memcached不支持持久化,数据只能存在内存中,重启后数据就没了。
③Memcached没有原生的集群模式,需要依靠客户端自己实现集群分片,而Redis原生支持集群模式。
④Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据刷回磁盘。所以 Redis 具有性能好和数据持久化的特征。 如果不将数据放在内存中,磁盘 I/O 速度会严重影响 Redis 的性能。
1)基于内存;
2)单线程减少上下文切换,同时保证原子性;
3)IO多路复用;
4)高级数据结构(如 SDS、Hash以及跳表等)。
官方答案
因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,单线程容易实现。
详细原因
1)不需要各种锁的性能消耗
Redis 的数据结构并不全是简单的 Key-Value,还有 List,Hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
2)单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。
所以单线程、多进程的集群不失为一个时髦的解决方案。
1)缓存空值
2)key 值校验,如布隆筛选器 ref 分布式布隆过滤器(Bloom Filter)详解(初版)
1)互斥锁
2)热点数据永不过期
3)熔断降级
1)热点数据不过期
2)随机分散过期时间
Redis 通过主从加集群架构,实现读写分离,主节点负责写,并将数据同步给其他从节点,从节点负责读,从而实现高并发。
答案很简单,因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。
但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。
如何解决
1)使用 incr、decr、setnx 等原子操作;
2)客户端加锁;
3)使用 Lua 脚本实现 CAS 操作。
Redis 在互联网产品中使用的场景实在是太多太多,这里分别对 Redis 几种数据类型做了整理:
1)String:缓存、限流、分布式锁、计数器、分布式 Session 等。
字符串 string 是 Redis 最简单的数据结构,可以存储字符串、整数或者浮点数。最常见的应用场景就是对象缓存,例如缓存用户信息,key是"userInfo"+#{用户ID},value是用户信息对象的json字符串。
2)Hash:用户信息、用户主页访问量、组合查询等。
Redis的hash结构相当于Java语言的HashMap,内部实现结构上与JDK1.7的HashMap一致,底层通过数据+链表实现。
3)List:简单队列、关注列表时间轴。
Redis 的列表相当于 Java 语言里面的 LinkedList。
LinkedList优点:插入性能高,不管是从末尾插入还是中间插入
LinkedList缺点:随机读性能差,例如LinkedList.get(10),这种操作,性能就很低,因为他需要遍历这个链表,从头开始遍历这个链表,直到找到index = 10的这个元素为止。
4)Set:赞、踩、标签等。
Redis的set集合相当于Java的HashSet。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。
补充:HashSet就是基于HashMap来实现的,HashSet,他其实就是说一个集合,里面的元素是无序的,他里面的元素不能重复的,HashMap的key是无顺序的,你插入进去的顺序,跟你迭代遍历的顺序是不一样的,而且HashMap的key是没有重复的,HashSet直接基于HashMap实现的。
5)ZSet:排行榜、好友关系链表。
sorted set 有序集合,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。使得它类似于Java的TreeSet和HashMap的结合体。
zset 是 Redis 中一个非常重要的数据结构,其底层是基于跳表(skip list) 实现的。
跳表是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为 O(logN)。简单说来跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能,正是这个跳跃的功能,使得在查找元素时,跳表能够提供 O(logN) 的时间复杂度。
跳表为了避免每次插入或删除带来的额外操作,不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。
————————————————
原文链接:https://blog.csdn.net/qq_35620342/article/details/119575020
我们知道,Redis 底层实现了很多高级数据结构,如简单动态字符串、双端链表、字典、压缩列表、跳跃表、整数集合等。
然而 Redis 并没有直接使用这些数据结构来实现键值对的数据库,而是在这些数据结构之上又包装了一层 RedisObject(对象),也就是我们常说的五种数据结构:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。
这样做有两个好处:
1)通过不同类型的对象,Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行该的命令。
2)可以针对不同的使用场景,为对象设置不同的实现,从而优化内存或查询速度。
1)在进行get或setnx等操作时,先检查key是否过期;
2)若过期,删除key,然后执行相应操作;
3)若没过期,直接执行相应操作。
其核心是对指定个数个库的每一个库随机删除小于等于指定个数个过期 key:
1)历每个数据库(就是 redis.conf 中配置的 “database” 数量,默认为16);
2)检查当前库中的指定个数个 key (默认是每个库检查 20 个,相当于该循环执行 20 次):
2.1)如果当前库中没有一个 key 设置了过期时间,直接执行下一个库的遍历;
2.2)随机获取一个设置了过期时间的 key,检查是否过期,如果过期则删除;
2.3)判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。
Redis 作为一个内存数据库,在内存空间不足的时候,为了保证命中率,就会和我们操作系统中的页面置换算法类似,选择一定的数据淘汰策略。
volatile(设置过期时间的数据集)
1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
allkeys(所有数据集)
5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction
8)no-enviction(驱逐):禁止驱逐数据,这也是默认策略。
意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用 no-enviction 策略可以保证数据不被丢失。
RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,也是默认的持久化方式。也就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。
RDB 支持 同步(save 命令)、后台异步(bgsave)以及自动配置三种方式触发。
优点
RDB 文件紧凑,全量备份,非常适合用于进行备份和灾难恢复
生成 RDB 文件时支持异步处理,主进程不需要进行任何磁盘IO操作
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
缺点
RDB 快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。且在快照持久化期间修改的数据不会被保存,可能丢失数据。
AOF工作机制更加简单:会将每一个收到的写命令追加到文件中。
随着时间推移,AOF 持久化文件也会变的越来越大。为了解决此问题,Redis 提供了 bgrewriteaof 命令,作用是 fork 出一条新进程将内存中的数据以命令的方式保存到临时文件中,完成对AOF 文件的重写。
AOF 也有三种触发方式:1)每修改同步 always 2)每秒同步 everysec 3)不同no:从不同步。
优点
AOF 可以更好的保护数据不丢失,一般 AOF 隔 1 秒通过一个后台线程执行一次 fsync 操作
AOF 日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损
AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写
AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复
缺点
对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大
AOF开启后,支持的写 QPS 会比RDB支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的
拓展:混合持久化
如果我们采用 RDB 持久化会丢失一段时间数据。如果我们采用 AOF 持久化,AOF日志较大,重放比较慢。
Redis 4.0 为了解决这个问题,支持混合持久化。将 RDB 文件的内容和增量的 AOF 日志文件存在一起。
混合持久化同样也是通过 bgrewriteaof 完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以 RDB 方式写入 AOF 文件,然后在将重写缓冲区的增量命令以 AOF 方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
我们可以部署多个副本节点,业务采用读写分离的方式,把读请求分担到多个副本节点上,提高访问性能。要实现读写分离,就必须部署多个副本,每个副本需要实时同步主节点的数据。
和 MySQL 需要主从复制的原因一样,Redis 虽然读写速度非常快,但是也会产生性能瓶颈,特别是在读压力上,为了分担压力,Redis 支持主从复制。Redis 的主从结构一主一从,一主多从或级联结构,复制类型可以根据是否是全量而分为全量同步和增量同步。
redis主从复制的优点主要是提高了可用性缺点。
redis主从复制优缺点:
优点:
缺点:
在主从复制实现之后,如果想对 master 进行监控,Redis 提供了一种哨兵机制,哨兵的含义就是监控 Redis 系统的运行状态,通过投票机制,从 slave 中选举出新的 master 以保证集群正常运行。还可以启用多个哨兵进行监控以保证集群足够稳健,这种情况下,哨兵不仅监控主从服务,哨兵之间也会相互监控。
Sentinel 哨兵 是Redis的高可用解决方案:一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在主服务器下线时可以自动切换从服务器升级为主服务器。
sentinel 哨兵主要用来监控redis主从集群,提高了redis 主从集群的可用性。
Sentinel 哨兵优缺点
优点:
缺点:
1、Redis较难支持在线扩容,在线扩容比较复杂。
Redis Cluster是一种服务器 Sharding 技术,redis 3.0版本开始正式提供。
Sentinel基本已经实现了高可用,但是每台机器都存储相同内容,很浪费内存,所以Redis Cluster实现了分布式存储。每台机器节点上存储不同的内容。
实现了分布式存储,节省了内存。
主从复制可以根据需要分为全量同步的增量同步两种方式。主从同步刚连接的时候进行全量同步,全量同步结束后开始增量同步。
Redis 全量复制一般发生在 slave 的初始阶段,这时 slave 需要将 master 上的数据都复制一份,具体步骤如下:
1)slave 连接 master,发送 SYNC 命令;
2)master 接到 SYNC 命令后执行 BGSAVE 命令生产 RDB 文件,并使用缓冲区记录此后执行的所有写命令;
3)master 执行完 BGSAVE 后,向所有的 slave 发送快照文件,并在发送过程中继续记录执行的写命令;
4)slave 收到快照后,丢弃所有的旧数据,载入收到的数据;
5)master 快照发送完成后就会开始向 slave 发送缓冲区的写命令;
6)slave 完成对快照的载入,并开始接受命令请求,执行来自 master 缓冲区的写命令;
7)slave 完成上面的数据初始化后就可以开始接受用户的读请求了。
增量复制实际上就是在 slave 初始化完成后开始正常工作时 master 发生写操作同步到 slave 的过程。增量复制的过程主要是 master 每执行一个写命令就会向 slave 发送相同的写命令,slave 接受并执行写命令,从而保持主从一致。
哨兵主要用于管理多个 Redis 服务器,主要有以下三个任务:监控、提醒以及故障转移。
每个哨兵会向其它哨兵、master、slave 定时发送消息,以确认对方是否还存活。如果发现对方在配置的指定时间内未回应,则暂时认为对方已挂。若“哨兵群”中的多数 sentinel 都报告某一 master 没响应,系统才认为该 master “彻底死亡”,通过一定的 vote 算法从剩下的 slave 节点中选一台提升为 master,然后自动修改相关配置。
5、哨兵模式故障迁移流程?
1)首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。
选点的依据依次是:
网络连接正常 -> 5 秒内回复过 INFO 命令 -> 10*down-after-milliseconds 内与主连接过的 -> 从服务器优先级 -> 复制偏移量 -> 运行id较小的。
2)选出之后通过 slaveif no ont 将该从服务器升为新主服务器;
3)然后再通过 slaveof ip port 命令让其他从服务器复制该信主服务器。
缺点
主从服务器的数据要经常进行主从复制,这样会造成性能下降
当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不可用的
其实现原理就是一致性 Hash。Redis Cluster 中有一个 16384 长度的槽的概念,他们的编号为 0、1、2、3 …… 16382、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster 中的每个 Master 节点都会负责一部分的槽,当有某个 key 被映射到某个 Master 负责的槽,那么这个 Master 负责为这个 key 提供服务。
至于哪个 Master 节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在 Redis Cluster 中,只有 Master 才拥有槽的所有权,如果是某个 Master 的 slave,这个slave只负责槽的使用,但是没有所有权。
为了使得集群能够水平扩展,首要解决的问题就是如何将整个数据集按照一定的规则分配到多个节点上。对于客户端请求的 key,根据公式 HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上。而对于 CRC16 算法产生的 hash 值会有 16bit,可以产生 2^16-=65536 个值。
Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
当一个 Redis 新节点运行并加入现有集群后,我们需要为其迁移槽和数据。首先要为新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀。
1)首先启动一个 Redis 节点,记为 M4。
2)使用 cluster meet 命令,让新 Redis 节点加入到集群中。新节点刚开始都是主节点状态,由于没有负责的槽,所以不能接受任何读写操作,后续给他迁移槽和填充数据。
3)对 M4 节点发送 cluster setslot { slot } importing { sourceNodeId } 命令,让目标节点准备导入槽的数据。
4)对源节点,也就是 M1,M2,M3 节点发送 cluster setslot { slot } migrating { targetNodeId } 命令,让源节点准备迁出槽的数据。
5)源节点执行 cluster getkeysinslot { slot } { count } 命令,获取 count 个属于槽 { slot } 的键,然后执行步骤 6)的操作进行迁移键值数据。
6)在源节点上执行 migrate { targetNodeIp} " " 0 { timeout } keys { key... } 命令,把获取的键通过 pipeline 机制批量迁移到目标节点,批量迁移版本的 migrate 命令在 Redis 3.0.6 以上版本提供。
7)重复执行步骤 5)和步骤 6)直到槽下所有的键值数据迁移到目标节点。
8)向集群内所有主节点发送 cluster setslot { slot } node { targetNodeId } 命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽执行新节点。
收缩节点就是将 Redis 节点下线,整个流程需要如下操作流程。
1)首先需要确认下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
2)当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记改节点后可以正常关闭。
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
缺点:
1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
解决方案:
1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
3、非阻塞的?搞一个while循环,直到insert成功再返回成功。
4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
缺点:
在这种场景(主从结构)中存在明显的竞态:
客户端A从master获取到锁,
在master将锁同步到slave之前,master宕掉了。
slave节点被晋级为master节点,
客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。安全失效!
缺点:
性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。
其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。)
上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度(从低到高):
数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高):
Zookeeper >= 缓存 > 数据库
从性能角度(从高到低):
缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低):
Zookeeper > 缓存 > 数据库
————————————————
原文链接:https://blog.csdn.net/wuzhiwei549/article/details/80692278
1、通过SQL语句实现,不过SQL过于复杂,也不好维护。
2、Redis也能实现排行榜功能,主要通过zset中的分数特性来实现。
3、通过SQL中的order by将查询的List根据某字段进行排好序,再将此List通过Java代码实现最终排行榜功能。
可以在Redis的配置文件redis.conf文件中,配置maxmemory(最大占用内存)的大小参数,一般小的公司都是设置为3G左右的大小。
除了在配置文件中配置生效外,还可以通过命令行参数的形式,进行配置,具体的配置命令行如下所示:
- //获取maxmemory配置参数的大小
- 127.0.0.1:6379> config get maxmemory
- //设置maxmemory参数为100mb
- 127.0.0.1:6379> config set maxmemory 100mb
倘若实际的存储中超出了Redis的配置参数的大小时,Redis中有淘汰策略,把需要淘汰的key给淘汰掉,整理出干净的一块内存给新的key值使用。
volatile(设置过期时间的数据集)
1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
allkeys(所有数据集)
5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction
8)no-enviction(驱逐):禁止驱逐数据,这也是默认策略。
意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用 no-enviction 策略可以保证数据不被丢失。
- 如何获取及设置内存淘汰策略
-
- 获取当前内存淘汰策略:
-
- 127.0.0.1:6379> config get maxmemory-policy
-
- 通过配置文件设置淘汰策略(修改redis.conf文件):
-
- maxmemory-policy allkeys-lru
-
- 通过命令修改淘汰策略:
-
- 127.0.0.1:6379> config set maxmemory-policy allkeys-lru
删除过期键策略
在Redis种有三种删除的操作此策略,分别是:
RDB和AOF 的淘汰处理
这两个命令都不会把过期的key保存到RDB文件中,这样也能达到删除过期key的效果。
在AOF模式下,Redis提供了Rewite的优化措施,执行的命令分别是REWRITEAOF和BGREWRITEAOF,这两个命令都不会把过期的key写入到AOF文件中,也能删除过期key。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。