首页 / 面试 / 后端开发面试题(五)Redis篇
后端开发面试题(五)Redis篇
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了后端开发面试题(五)Redis篇,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含62606字,纯文字阅读大概需要90分钟。
内容图文
文章目录
- 一、概述
- 二、数据类型
- 三、持久化
- 四、过期键的删除策略
- 五、内存相关
- 六、线程模型
- 七、事务
- 八、集群方案
- 九、分区
- 十、分布式问题
- 十一、缓存异常
- 十二、常用工具
- 十三、其他问题
一、概述
1.1 什么是Redis
??Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
??Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
??与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
1.2 Redis有哪些优缺点
??Redis的优点:
???1、读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
???2、支持数据持久化,支持AOF和RDB两种持久化方式。
???3、支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
???4、数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
???5、支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
??
??Redis的缺点:
???1、数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
???2、Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
???3、主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
???4、Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
1.3 为什么要用 Redis /为什么要用缓存
??主要从“高性能”和“高并发”这两点来看待这个问题:
- 1、高性能
??假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可。
- 2、高并发
??直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
1.4 为什么要用 Redis 而不用 map/guava 做缓存?
??缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
??此处提一下Guava,Guava是Google开源的Java库,其中有很多库,如:集合、缓存、原生类型支持、并发库、通用注解、字符串处理、I/O。
??使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
1.4.1 Redis作为分布式缓存的优点
??作为分布式缓存,Redis有以下优点:
???1>redis可以独立部署,这样网站代码更新后redis缓存的数据还在,本地内存每次网站更新都会释放掉;
???2>数据存到redis,多个项目间可以共享缓存数据,如果是本地内存是无法跨项目共享的;
???3>本地缓存不方便查看及修改,redis有丰富工具管理缓存数据。
1.4.2 Redis和Map的比较
- 1、Redis 可以用几十 G 内存来做缓存,Map 不行,一般 JVM 也就分几个 G 数据就够大了
- 2、Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了
- 3、Redis 可以实现分布式的缓存,Map 只能存在创建它的程序里
- 4、Redis 可以处理每秒百万级的并发,是专业的缓存服务,Map 只是一个普通的对象
- 5、Redis 缓存有过期机制,Map 本身无此功能
- 6、Redis 有丰富的 API,Map 就简单太多了
1.5 Redis为什么这么快
- 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
??像mysql这样的成传统关系型数据库是索引文件存储来内存,数据文件存储在硬盘的,那么硬盘的性能和瓶颈将会影响到数据库。硬盘型数据库工作模式:
??内存型数据库工作模式:
??本身内存和硬盘的读写方式不相同导致了它们在读写性能上的巨大差异。 - 2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
- 3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
??这里的单线程指的是,Redis处理网络请求的时候只有一个线程,而不是整个Redis服务是单线程的。 - 4、使用多路 I/O 复用模型,非阻塞 IO;
???1>传统多进程并发模型: 每监听到一个Socket连接就会分配一个线程处理
???2>多路复用模型: 单个线程,通过记录跟踪每一个Socket连接的I/O的状态来同时管理多个I/O流。
?&emsp这里的I/O指的是网络I/O,多路指的是多个网络连接,复用指的是复用一个线程。Redis使用多路 I/O 复用模型的大致流程如下:
???1>在Redis中的I/O多路复用程序会监听多个客户端连接的Socket
???2>每当有客户端通过Socket流向Redis发送请求进行操作时,I/O多路复用程序会将其放入一个队列中。
???3>同时I/O多路复用程序会同步、有序、每次传送一个任务给处理器处理。
???4>I/O多路复用程序会在上一个请求处理完毕后再继续分派下一个任务。
??用图表示的话,如下:
- 5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
二、数据类型
2.1 Redis有哪些数据类型
??Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求:
数据类型 | 可以存储的值 | 操作 | 应用场景 |
---|---|---|---|
STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作 | 做简单的键值对缓存 |
LIST | 列表 | 从两端压入或者弹出元素 对单个或者多个元素进行修剪,只保留一个范围内的元素 | 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据 |
SET | 无序集合 | 添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 | 交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集 |
HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在 | 结构化的数据,比如一个对象 |
ZSET | 有序集合 | 添加、获取、删除元素 根据分值范围或者成员来获取元素 计算一个键的排名 | 去重但可以排序,如获取排名前几名的用户 |
2.2 Redis的应用场景
- 总结一
?1、计数器
??可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
?2、缓存
??将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
?3、会话缓存
??可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
?4、全页缓存(FPC)
??除基本的会话token之外,Redis还提供很简便的FPC平台。以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
?5、查找表
??例如 DNS 记录就很适合使用 Redis 进行存储。查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。
?6、消息队列(发布/订阅功能)
??List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。不过最好使用 Kafka、RabbitMQ 等消息中间件。
?7、分布式锁实现
??在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
?8、其它
??Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能。 - 总结二
??string——适合最简单的k-v存储,类似于memcached的存储结构,短信验证码,配置信息等,就用这种类型来存储。
??hash——一般key为ID或者唯一标示,value对应的就是详情了。如商品详情,个人信息详情,新闻详情等。
??list——因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等。因为list是有序的,适合根据写入的时间来排序,如:最新的***,消息队列等。
??set——可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:查找两个人共同的好友等。
??Sorted Set——是set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。
??如上所述,虽然Redis不像关系数据库那么复杂的数据结构,但是,也能适合很多场景,比一般的缓存数据结构要多。了解每种数据结构适合的业务场景,不仅有利于提升开发效率,也能有效利用Redis的性能。 - 总结三
??Redis在互联网公司一般有以下应用:
????String:缓存、限流、计数器、分布式锁、分布式Session
????Hash:存储用户信息、用户主页访问量、组合查询
????List:微博关注人时间轴列表、简单队列
????Set:赞、踩、标签、好友关系
????Zset:排行榜 - 总结示例
??如果现在做一个秒杀,那么,Redis应该如何结合进行使用?
???1>提前预热数据,放入Redis
???2>商品列表放入Redis List
???3>商品的详情数据 Redis hash保存,设置过期时间
???4>商品的库存数据Redis sorted set保存
???5>用户的地址信息Redis set保存
???6>订单产生扣库存通过Redis制造分布式锁,库存同步扣除
???7>订单产生后发货的数据,产生Redis list,通过消息队列处理
???8>秒杀结束后,再把Redis数据和数据库进行同步
??以上只是一个简略的秒杀系统和Redis结合的方案。
三、持久化
3.1 什么是Redis持久化?
??持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
3.2 Redis 的持久化机制是什么?各自的优缺点?
??Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制。
3.2.1 RDB
??RDB:是Redis DataBase缩写快照,RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb(二进制文件)。通过配置文件中的save参数来定义快照的周期。
??当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
???1、Redis 调用forks。同时拥有父进程和子进程。
???2、子进程将数据集写入到一个临时 RDB 文件中。
???3、当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
??RDB的三种主要触发机制:
- 1、save命令(同步数据到磁盘上)
??由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。因此很少在生产环境直接使用SAVE 命令,可以使用BGSAVE 命令代替。如果在BGSAVE命令的保存数据的子进程发生错误的时,用 SAVE命令保存最新的数据是最后的手段。 - 2、bgsave命令(异步保存数据到磁盘上)
??Redis使用Linux系统的fock()生成一个子进程来将DB数据保存到磁盘,主进程继续提供服务以供客户端调用。如果操作成功,可以通过客户端命令LASTSAVE来检查操作结果。 - 3、自动生成RDB
??除了手动执行 save 和 bgsave 命令实现RDB持久化以外,Redis还提供了自动自动生成RDB的方式。
??你可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。
??RDB的优点:
???1、只有一个文件 dump.rdb,方便持久化。
???2、容灾性好,一个文件可以保存到安全的磁盘。
???3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能。
???4.相对于数据集大时,比 AOF 的启动效率更高。
??RDB的缺点:数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)。
3.2.2 AOF
??AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
??当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
??AOF持久化的三种策略
- 1、always
??每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。 - 2、everysec
??每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。 - 3、no
??从不 fsync :将数据交给操作系统来处理,由操作系统来决定什么时候同步数据。更快,也更不安全的选择。
??AOF的优点:
???1、数据安全,aof 持久化可以配置 appendfsync 属性,如:always,每进行一次 命令操作就记录到 aof 文件中一次。
???2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
???3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
??AOF的缺点:
???1、AOF 文件比 RDB 文件大,且恢复速度慢。
???2、数据集大的时候,比 rdb 启动效率低。
3.2.3 两者的简单对比
3.3 如何选择合适的持久化方式
??一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
??如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
??有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的bug。
??如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
3.4 Redis持久化数据和缓存怎么做扩容?
??如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。
??如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
3.4.1 一致性哈希原理
??此处简单提一下一致性哈希,一致性Hash算法是对 2^32 取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2 ^ 32-1(即哈希值是一个32位无符号整形),整个哈希环如下,从 0 ~ 2 ^ 32-1 代表的分别是一个个的节点,这个环也叫哈希环:
??然后我们将我们的节点进行一次哈希,按照一定的规则,比如按照 ip 地址的哈希值,让节点落在哈希环上。比如此时我们可能得到了如下图的环:
??然后就是需要通过数据 key 找到对应的服务器然后存储了,我们约定,通过数据 key 的哈希值落在哈希环上的节点,如果命中了机器节点就落在这个机器上,否则落在顺时针直到碰到第一个机器。如下图所示 : A 的哈希值落在了 D2 节点的前面,往下找落在了 D2 机器上,D的哈希值 在 D1 节点的前面,往下找到了 D1 机器,B的哈希值刚好落在了D1 节点上,依次~~~
??一致性哈希主要就是解决当机器减少或增加的时候,大面积的数据重新哈希的问题,主要从下面 2 个方向去考虑的,当节点宕机时,数据记录会被定位到下一个节点上,当新增节点的时候 ,相关区间内的数据记录就需要重新哈希。
3.4.2 一致性哈希的问题及解决
??一致性Hash算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。比如只有 2 台机器,这 2 台机器离的很近,那么顺时针第一个机器节点上将存在大量的数据,第二个机器节点上数据会很少。如下图所示,D0 机器承载了绝大多数的数据:
??为了避免出现数据倾斜问题,一致性 Hash 算法引入了虚拟节点的机制,也就是每个机器节点会进行多次哈希,最终每个机器节点在哈希环上会有多个虚拟节点存在,使用这种方式来大大削弱甚至避免数据倾斜问题。同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“D1#1”、“D1#2”、“D1#3”三个虚拟节点的数据均定位到 D1 上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。这也是 Dubbo 负载均衡中有一种一致性哈希负载均衡的实现思想。
四、过期键的删除策略
4.1 Redis的过期键的删除策略
??Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。过期策略通常有以下三种:定时过期、惰性过期和定期过期。
- 1、定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作;
- 2、惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,那就返回该键;
- 3、定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及要检查多少个数据库,则由算法决定。
??下面我们来看看三种策略的优缺比较:
???定时删除策略对内存是最友好的:通过使用定时器,定时删除策略可以保证过期键会尽可能快地被删除,并释放过期键所占用的内存;但另一方面,定时删除策略的缺点是,他对CPU是最不友好的:在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分CPU时间,在内存不紧张但是CPU时间非常紧张的情况下,将CPU时间用在删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响;
???惰性删除策略对CPU时间来说是最友好的:程序只会在取出键时才对键进行过期检查,这可以保证删除过期键的操作只会在非做不可的情况下进行;惰性删除策略的缺点是,它对内存是最不友好的:如果一个键已经过期,而这个键又仍然保留在数据库中,那么只要这个过期键不被删除,它所占用的内存就不会释放;
???定时删除占用太多CPU时间,影响服务器的响应时间和吞吐量;惰性删除浪费太多内存,有内存泄漏的危险。定期删除策略是前两种策略的一种整合和折中:
???定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响;通过定期删除过期键,定期删除策略有效地减少了因为过期键而带来的内存浪费;定期删除策略的难点是确定删除操作执行的时长和频率。
??Redis中同时使用了惰性过期和定期过期两种过期策略。
4.2 Redis key的过期时间和永久有效分别怎么设置?
??EXPIRE和PERSIST命令。
4.3 对过期的数据怎么处理?
??在 redis 中,对于已经过期的数据,Redis 采用两种策略来处理这些数据,分别是惰性删除和定期删除。
- 1、惰性删除
??惰性删除不会去主动删除数据,而是在访问数据的时候,再检查当前键值是否过期,如果过期则执行删除并返回 null 给客户端,如果没有过期则返回正常信息给客户端。
??它的优点是简单,不需要对过期的数据做额外的处理,只有在每次访问的时候才会检查键值是否过期,缺点是删除过期键不及时,造成了一定的空间浪费。 - 2、定期删除
??Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。
??具体的算法如下:
???1>Redis配置项hz定义了serverCron任务的执行周期,默认为10,代表了每秒执行10次;
???2>每次过期key清理的时间不超过CPU时间的25%,比如hz默认为10,则一次清理时间最大为25ms;
???3>清理时依次遍历所有的db;
???4>从db中随机取20个key,判断是否过期,若过期,则逐出;
???5>若有5个以上key过期,则重复步骤4,否则遍历下一个db;
???6>在清理过程中,若达到了25%CPU时间,退出清理过程;
??虽然redis的确是不断的删除一些过期数据,但是很多没有设置过期时间的数据也会越来越多,那么redis内存不够用的时候是怎么处理的呢?这里我们就会谈到淘汰策略。
4.3.1 Redis内存淘汰策略
??当redis的内存超过最大允许的内存之后,Redis会触发内存淘汰策略,删除一些不常用的数据,以保证redis服务器的正常运行。
??在redis 4.0以前,redis的内存淘汰策略有以下6种:
???noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
???allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
???volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
???allkeys-random:加入键的时候如果过限,从所有key随机删除
volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
???volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
??在redis 4.0以后,又增加了以下两种:
???volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
???allkeys-lfu:从所有键中驱逐使用频率最少的键
??内存淘汰策略可以通过配置文件来修改,redis.conf对应的配置项是maxmemory-policy 修改对应的值就行,默认是noeviction。
五、内存相关
5.1 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
??要考虑两方面内容:1.Redis的内存淘汰策略。2.Redis的最大内存设置。
??思路:首先计算出20w数据所需的内存空间,设置最大内存,然后选择合适的内存淘汰策略。当redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
??内存淘汰策略有八种,上个小节已介绍。
??至于最大内存设置,在redis.conf配置文件中,可以对最大内存进行设置,单位为bytes:当使用的内存到达指定的限制时,Redis会根据内存淘汰策略删除键,以释放空间。
5.2 Redis的内存淘汰策略有哪些
??Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的键空间选择性移除
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
设置过期时间的键空间选择性移除
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
??Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。
5.3 Redis主要消耗什么物理资源?
??内存。redis的数据都是存储在内存当中。内存数据库相比一般的关系型数据库,读取速度要更快,但是消耗的内存资源会更多。
5.4 Redis的内存用完了会发生什么?
??这个跟 Redis 的内存回收策略有关。Redis 的默认回收策略是 noenviction,当内存用完之后,写数据会报错。或者可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
5.5 Redis如何做内存优化?
- 1、缩减键值对象
??缩减键(key)和值(value)的长度:
???key长度:如在设计键时,在完整描述业务情况下,键值越短越好。
???value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等,下图是JAVA常见序列化工具空间压缩对比。 - 2、共享对象池
??对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。 - 3、字符串优化
- 4、编码优化
- 5、控制key的数量
六、线程模型
6.1 Redis线程模型
6.1.1 Redis线程模型特点
- 1、Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
- 2、文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 3、当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
- 4、虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
6.1.2 Redis线程模型组成
6.1.3 Redis的处理流程
??Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
6.1.4 Redis是单线程模型为什么效率还这么高
- 1、纯内存访问:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。
- 2、非阻塞I/O:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。
- 3、单线程避免了线程切换和竞态产生的消耗。
- 4、Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库。
七、事务
7.1 Redis事务的概念
??Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
??总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
??Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
7.2 Redis事务的三个阶段
- 1、事务开始 MULTI
- 2、命令入队
- 3、事务执行 EXEC
??事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队。
7.3 Redis事务相关命令
??Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的。事务功能有以下特点:
???1、Redis会将一个事务中的所有命令序列化,然后按顺序执行。
???2、redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
???3、如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
???4、如果在一个事务中出现运行错误,那么正确的命令会被执行。
??Redis中的命令如下:
- WATCH :WATCH命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。其功能是:可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI:MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- DISCARD:通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
- UNWATCH:UNWATCH命令可以取消watch对所有key的监控。
7.4 事务管理
?1、一致性(Consistency)
??事务前后数据的完整性必须保持一致。
?2、隔离性(Isolation)
??多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
??Redis的事务总是具有一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有持久性(持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响)。
7.5 Redis事务支持隔离性吗
??Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
7.6 Redis事务保证原子性吗,支持回滚吗
??Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
??如果想实现回滚,就需要用WATCH命令,具体的做法是:需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。
??当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis都会取消执行事务前的WATCH命令。
7.7 Redis事务其他实现
??基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行,其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完。
??基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐。
八、集群方案
8.1 哨兵模式
8.1.1 哨兵模式简介
??sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
- 1、集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 2、消息通知:当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
- 3、故障转移:当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。
??哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的。
??Sentinel的工作方式:
???1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。
???2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。
???3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
???4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
???5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
???6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
???7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
??哨兵的核心知识:
- 哨兵至少需要 3 个实例,来保证自己的健壮性。
- 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
- 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
8.1.2 哨兵模式中的细节
- 1、主从复制
??sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master用于自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。其结构如下:
- 2、三个定时任务
??sentinel在内部有3个定时任务:
???1)每10秒每个sentinel会对master和slave执行info命令,这个任务达到两个目的:
????a>发现slave节点
????b>确认主从关系
???2)每2秒每个sentinel通过master节点的channel交换信息(pub/sub)。master节点上有一个发布订阅的频道(sentinel:hello)。sentinel节点通过__sentinel__:hello频道进行信息交换(对节点的"看法"和自身的信息),达成共识。
???3)每1秒每个sentinel对其他sentinel和redis节点执行ping操作(相互监控),这个其实是一个心跳检测,是失败判定的依据。 - 3、主观下线
??所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。
??主观下线就是说如果服务器在down-after-milliseconds给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(SDOWN )。
??sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例时候在线(对该sentinel来说是“主观在线”)。 - 4、客观下线
??客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断,然后开启failover。
??客观下线就是说只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(ODOWN)。
只有当master被认定为客观下线时,才会发生故障迁移。
??当sentinel监视的某个服务主观下线后,sentinel会询问其它监视该服务的sentinel,看它们是否也认为该服务主观下线,接收到足够数量(这个值可以配置)的sentinel判断为主观下线,即认为该服务客观下线,并对其做故障转移操作。
8.2 官方Redis Cluster 方案
??Redis Cluster是Redis官方提供的Redis集群功能。
8.2.1 为什么要实现Redis Cluster
- 主从复制不能实现高可用
- 随着公司发展,用户数量增多,并发越来越多,业务需要更高的QPS(每秒查询率),而主从复制中单机的QPS可能无法满足业务需求
- 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上
- 网络流量需求:业务的流量已经超过服务器的网卡的上限值,可以考虑使用分布式来进行分流
- 离线计算,需要中间环节缓冲等别的需求
8.2.2 数据分布
??全量数据,单机Redis节点无法满足要求,所以需要按照分区规则把数据分到若干个子集当中。
??常用的数据分布方式如下:
- 1、顺序分布
??比如:1到100个数字,要保存在3个节点上,按照顺序分区,把数据平均分配三个节点上:1号到33号数据保存到节点1上,34号到66号数据保存到节点2上,67号到100号数据保存到节点3上。顺序分区常用在关系型数据库的设计。 - 2、哈希分布
??例如1到100个数字,对每个数字进行哈希运算,然后对每个数的哈希结果除以节点数进行取余,余数为1则保存在第1个节点上,余数为2则保存在第2个节点上,余数为0则保存在第3个节点,这样可以保证数据被打散,同时保证数据分布的比较均匀。哈希分布方式又可以分为三个分区方式:
??1)节点取余分区
???比如有100个数据,对每个数据进行hash运算之后,与节点数进行取余运算,根据余数不同保存在不同的节点上。
???节点取余方式是非常简单的一种分区方式,但是设计到数据迁移时,可能工作量较大。
???节点取余分区的优点:1>客户端分片;2>配置简单:对数据进行哈希,然后取余。
???节点取余分区的缺点:1>数据节点伸缩时,导致数据迁移;2>迁移数量和添加节点数据有关,建议翻倍扩容。
??2)一致性哈希分区
???前面已介绍,不再赘述。
??3)虚拟槽分区
???虚拟槽分区是Redis Cluster采用的分区方式。预设虚拟槽,每个槽就相当于一个数字,有一定范围。每个槽映射一个数据子集,一般比节点数大。Redis Cluster中预设虚拟槽的范围为0到16383。
???虚拟槽分区步骤:
????1.把16384槽按照节点数量进行平均分配,由节点进行管理
????2.对每个key按照CRC16规则进行hash运算
????3.把hash结果对16383进行取余
????4.把余数发送给Redis节点
????5.节点接收到数据,验证是否在自己管理的槽编号的范围
??????如果在自己管理的槽编号范围内,则把数据保存到数据槽中,然后返回执行结果;
??????如果在自己管理的槽编号范围外,则会把数据发送给正确的节点,由正确的节点来把数据保存在对应的槽中;
???需要注意的是:Redis Cluster的节点之间会共享消息,每个节点都会知道是哪个节点负责哪个范围内的数据槽。
???虚拟槽分布方式中,由于每个节点管理一部分数据槽,数据保存到数据槽中。当节点扩容或者缩容时,对数据槽进行重新分配迁移即可,数据不会丢失。
???虚拟槽分区特点:
????使用服务端管理节点,槽,数据:例如Redis Cluster
????可以对数据打散,又可以保证数据分布均匀
??顺序分布与哈希分布的对比:
8.3 基于客户端分配
??Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool。
??Redis Sharding的优点:优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强。
??Redis Sharding的缺点:
- 由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。
- 客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化 。
8.4 基于代理服务器分片
??客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端。
??特征:
- 透明接入,业务程序不用关心后端Redis实例,切换成本低
- Proxy 的逻辑和存储的逻辑是隔离的
- 代理层多了一次转发,性能有所损耗
8.5 Redis 主从架构
8.5.1 简单介绍
??单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
??redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发。
8.5.2 redis replication 的核心机制
- redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
- 一个 master node 是可以配置多个 slave node 的;
- slave node 也可以连接其他的 slave node;
- slave node 做复制的时候,不会 block master node 的正常工作;
- slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
- slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
??注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
??另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
8.5.3 redis 主从复制的核心原理
??当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
??如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
- 1、主从复制的断点续传
??从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
??master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization。 - 2、无磁盘化复制
??master 在内存中直接创建 RDB,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可。 - 3、过期 key 处理
??slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。
8.5.4 复制的完整流程
??slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的host和ip,但是复制流程没开始。slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。然后 slave node 发送 ping 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node 第一次执行全量复制,将所有数据发给 slave node。而在后续,master node 持续将写命令,异步复制给 slave node。
- 1、 全量复制
??master 执行 bgsave ,在本地生成一份 rdb 快照文件。
??master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
??master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
??如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
client-output-buffer-limit slave 256MB 64MB 60
??slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
??如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
- 2、增量复制
??如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
??master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
??master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 - 3、heartbeat
??主从节点互相都会发送 heartbeat 信息。master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。 - 4、 异步复制
??master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
8.6 Redis集群的主从复制模型是怎样的?
??为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。
8.7 生产环境中的 redis 是怎么部署的?
??redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
??机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
??5 台机器对外提供读写,一共有 50g 内存。
??因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
??你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
??其实大型的公司,会有基础架构的 team 负责缓存集群的运维。
8.8 说说Redis哈希槽的概念?
??Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
??"用了哈希槽的概念,而没有用一致性哈希算法,不都是哈希么?这样做的原因是为什么呢?"Redis Cluster是自己做的crc16的简单hash算法,没有用一致性hash。Redis的作者认为它的crc16(key) mod 16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单,节点增删时处理起来也很方便。
8.9 Redis集群会有写操作丢失吗?为什么?
??Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
??以下情况可能导致写操作丢失:
- 过期 key 被清理
- 最大内存不足,导致 Redis 自动清理部分 key 以节省空间
- 主库故障后自动重启,从库自动同步
- 单独的主备方案,网络不稳定触发哨兵的自动切换主从节点,切换期间会有数据丢失
8.10 Redis集群之间是如何复制的?
??异步复制,具体的复制过程见于上文。
8.11 Redis集群最大节点个数是多少?
??16384个。
??Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 算法计算的结果,对 16384 取模后放到对应的编号在 0-16383 之间的哈希槽,集群的每个节点负责一部分哈希槽。
8.11.1 为什么redis集群的最大槽数是16384个?
??在redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 2K),也就是说使用2k的空间创建了16k的槽数。
??虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。
8.12 Redis集群如何选择数据库?
??Redis集群目前无法做数据库选择,默认在0数据库。
九、分区
??分区是将你的数据分发到不同redis实例上的一个过程,每个redis实例只是你所有key的一个子集。
9.1 Redis是单线程的,如何提高多核CPU的利用率?
??可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
9.2 为什么要做Redis分区?
??分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
9.3 你知道有哪些Redis分区实现方案?
??客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
??代理分区意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
??查询路由的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
9.4 Redis分区有什么缺点?
- 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
- 同时操作多个key,则不能使用Redis事务.
- 分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
- 当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
- 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
十、分布式问题
10.1 Redis分布式锁的实现原理
??Redisson实现Redis分布式锁的底层原理:
- 1、加锁机制
??如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。紧接着,就会发送一段lua脚本到redis上。 - 2、锁互斥机制
??在这个时候,如果客户端2来尝试加锁,执行了同样的一段lua脚本,获取到这个锁key的剩余生存时间。比如还剩15000毫秒的生存时间。此时客户端2会进入一个while循环,不停的尝试加锁。 - 3、watch dog自动延期机制
??客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。 - 4、释放锁机制
??如果执行lock.unlock(),就可以释放分布式锁,客户端2就可以尝试完成加锁了。般我们在生产系统中,可以用Redisson框架提供的这个类库来基于redis进行分布式锁的加锁与释放锁。
?上述Redis分布式锁的缺点:
??其实上面那种方案最大的问题,就是如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例。
??但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。此时就会导致多个客户端对一个分布式锁完成了加锁。这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。
??所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。
10.2 Redis实现分布式锁
??Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
??当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
??SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
??返回值:设置成功,返回 1 。设置失败,返回 0 。
??使用SETNX完成同步锁的流程及事项如下:
????1>使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
????2>为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
????3>释放锁,使用DEL命令将锁数据删除
10.3 如何解决 Redis 的并发竞争 Key 问题
??并发竞争key这个问题简单讲就是:同时有多个客户端去set一个key。
??解决方法常见的有四种:
- 1、乐观锁
??乐观锁适用于大家一起抢着改同一个key,对修改顺序没有要求的场景。watch 命令可以方便的实现乐观锁。watch 命令会监视给定的每一个key,当 exec 时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。
需要注意的是,如果你的 redis 使用了数据分片的方式,那么这个方法就不适用了。
- 2、分布式锁
??适合分布式环境,不用关心 redis 是否为分片集群模式。在业务层进行控制,操作 redis 之前,先去申请一个分布式锁,拿到锁的才能操作。分布式锁的实现方式很多,比如 ZooKeeper、Redis 等。 - 3、时间戳
??适合有序需求场景。 - 4、消息队列
??在并发量很大的情况下,可以通过消息队列进行串行化处理。这在高并发场景中是一种很常见的解决方案。
??总结这几种方案的话,适用场景:
- 乐观锁,注意不要在分片集群中使用
- 分布式锁,适合分布式系统环境
- 时间戳,适合有序场景
- 消息队列,串行化处理
10.4 分布式Redis是前期做还是后期规模上来了再做好?为什么?
??既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
??一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。
??这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。
10.5 什么是 RedLock
??Redlock:全名叫做 Redis Distributed Lock;即使用redis实现的分布式锁;使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)。
??它可以保证以下特性:
???1>安全特性:互斥访问,即永远只有一个 client 能拿到锁
???2>避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
???3>容错性:只要大部分 Redis 节点存活就可以正常提供服务
??多节点redis实现的分布式锁算法(假设有5个完全独立的redis主服务器),步骤为:
- 获取当前时间戳
- client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。
??比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁 - client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功
- 如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);
- 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁
??算法示意图如下:
??RedLock注意点(Safety arguments):
- 先假设client获取所有实例,所有实例包含相同的key和过期时间(TTL) ,但每个实例set命令时间不同导致不能同时过期,第一个set命令之前是T1,最后一个set命令后为T2,则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移;
- 对于以N/2+ 1(也就是一半以 上)的方式判断获取锁成功,是因为如果小于一半判断为成功的话,有可能出现多个client都成功获取锁的情况, 从而使锁失效;
- 一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务) ;只要在TTL时间内成功获取一半以上的锁便是有效锁;否则无效。
十一、缓存异常
11.1 缓存雪崩
??缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
??缓存失效的几种情况:
????1、缓存服务器挂了
????2、高峰期缓存局部失效
????3、热点缓存失效
??解决方案:
- 1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 2、增加互斥锁,控制数据库请求,重建缓存。
- 3、提高缓存的HA,如:redis集群。
11.2 缓存穿透
??缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
??解决方案
- 1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 2、缓存空对象从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 3、采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
??对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
??Bitmap: 典型的就是哈希表。缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
??布隆过滤器(推荐)
???就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
???它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
???Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
???Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
???Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
11.3 缓存击穿
??缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
??解决方案
- 1、设置热点数据永远不过期。
- 2、加互斥锁,互斥锁。
11.4 缓存预热
??缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
??解决方案
- 1、直接写个缓存刷新页面,上线时手工操作一下;
- 2、数据量不大,可以在项目启动的时候自动进行加载;
- 3、定时刷新缓存;
11.5 缓存降级
??当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
??缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
??在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
- 1、一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 2、警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
- 3、错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
- 4、严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
??服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
11.6 热点数据和冷数据
??热点数据,缓存才有价值
??对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
??对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
??数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
??那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
11.7 缓存热点key
??缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
??解决方案:
????对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
十二、常用工具
12.1 Redis支持的Java客户端都有哪些?官方推荐用哪个?
??Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
12.2 Redis和Redisson有什么关系?
??Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
12.3 Jedis与Redisson对比有什么优缺点?
??Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
十三、其他问题
13.1 Redis与Memcached的区别
??两者都是非关系型内存键值数据库,Redis 与 Memcached 主要有以下不同:
?总结一
对比参数 | Redis | Memcached |
---|---|---|
类型 | 1. 支持内存 2. 非关系型数据库 | 1. 支持内存 2. 键值对形式 3. 缓存形式 |
数据存储类型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗称ZSet】 | 1. 文本型 2. 二进制类型 |
查询【操作】类型 | 1. 批量操作 2. 事务支持 3. 每个类型不同的CRUD | 1.常用的CRUD 2. 少量的其他命令 |
附加功能 | 1. 发布/订阅模式 2. 主从分区 3. 序列化支持 4. 脚本支持【Lua脚本】 | 1. 多线程服务支持 |
网络IO模型 | 1. 单线程的多路 IO 复用模型 | 1. 多线程,非阻塞IO模式 |
事件库 | 自封转简易事件库AeEvent | 贵族血统的LibEvent事件库 |
持久化支持 | 1. RDB 2. AOF | 不支持 |
集群模式 | 原生支持 cluster 模式,可以实现主从复制,读写分离 | 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据 |
内存管理机制 | 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘 | Memcached 的数据则会一直在内存中,Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 |
适用场景 | 复杂数据结构,有持久化,高可用需求,value存储内容较大 | 纯key-value,数据量非常大,并发量非常大的业务 |
??1、memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
??2、redis的速度比memcached快很多
??3、redis可以持久化其数据
?总结二
- 1、类型
??Redis是一个开源的内存数据结构存储系统,用作数据库,缓存和消息代理。Memcached是一个免费的开源高性能分布式内存对象缓存系统,它通过减少数据库负载来加速动态Web应用程序。 - 2、数据结构
??Redis支持字符串,散列,列表,集合,有序集,位图,超级日志和空间索引;而Memcached支持字符串和整数。 - 3、执行速度
??Memcached的读写速度高于Redis。 - 4、复制
??Memcached不支持复制。而,Redis支持主从复制,允许从属Redis服务器成为主服务器的精确副本;来自任何Redis服务器的数据都可以复制到任意数量的从属服务器。 - 5、密钥长度
??Redis的密钥长度最大为2GB,而Memcached的密钥长度最大为250字节。 - 6、线程
??Redis是单线程的;而,Memcached是多线程的。
13.2 如何保证缓存与数据库双写时的数据一致性?
??你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
??一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
??串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
??最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先更新数据库,然后再删除缓存。
??**为什么是删除缓存,而不是更新缓存?**原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。另外更新缓存的代价有时候是很高的。
??问题1:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
??解决思路1:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。
??问题2:数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了。
??解决思路2:更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
13.3 Redis常见性能问题和解决方案?
- 1、Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
- 2、如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
- 3、为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
- 4、尽量避免在压力较大的主库上增加从库
- 5、为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。
13.4 一个字符串类型的值能存储最大容量是多少?
??512M
13.5 Redis如何做大量数据插入?
??Redis2.6开始,redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。
??使用pipe mode模式的执行命令如下:
cat data.txt | redis-cli --pipe
?pipe mode的工作原理:
- redis-cli –pipe试着尽可能快的发送数据到服务器
- 读取数据的同时,解析它
- 一旦没有更多的数据输入,它就会发送一个特殊的echo命令,后面跟着20个随机的字符。我们相信可以通过匹配回复相同的20个字符是同一个命令的行为
- 一旦这个特殊命令发出,收到的答复就开始匹配这20个字符,当匹配时,就可以成功退出了
13.6 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
??使用keys指令可以扫出指定模式的key列表。
??对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
??这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
13.7 使用Redis做过异步队列吗,是如何实现的
??一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。
??如果对方追问可不可以不用 sleep 呢?list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。
??如果对方追问能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现1:N 的消息队列。
??如果对方追问 pub/sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ等。
??如果对方追问** redis 如何实现延时队列**?使用 sortedset,拿时间戳作为score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。
13.8 Redis回收进程如何工作的?
??一个客户端运行了新的命令,添加了新的数据。
??Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。
??所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
13.9 Redis回收使用的是什么算法?
??LRU算法。LRU全称是Least Recently Used,即最近最久未使用的意思。
??LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
13.10 什么样的数据适合存储在非关系型数据库
??1、关系不是很密切的的数据,比如用户信息,班级信息,评论数量等等。
??2、量比较大的数据,如访问记录等。
??3、访问比较频繁的数据,如用户信息,访问数量,最新微博等 。
??
??参考链接:
??Redis面试题(2020最新版)
内容总结
以上是互联网集市为您收集整理的后端开发面试题(五)Redis篇全部内容,希望文章能够帮你解决后端开发面试题(五)Redis篇所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。