1. redis和memcached的区别
- redis有更多的数据类型
- redis可以持久化
- redis的键值可以设置过期时间
- redis数据可以回复
- memcached用的还串行+多线程+锁,redis是单线程+多路IO复用
2. redis如何避免并发问题?redis是单线程吗?
redis是单线程(6改为多线程,但本质还是单线程)+多路IO复用技术
2.1 redis6之前是单线程,redis6在网络io阶段改为多线程
- 在redis4的时候,引入Lazy Free机制来开启线程处理一些耗时较久的命令。所以在redis6之前,并不能完全说是redis是单线程的。
- redis6中在网络io阶段(或者客户端请求及返回数据给客户端)改为多线程处理,但是在执行命令时,仍是单线程
2.2 多路复用
多路复用是指:使用一个线程来检查(通过poll或者select等函数)多个文件描述符(客户端连接到服务端生成的文件)的就绪状态,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
多路复用简单点说就是给请求监视效果:
当有多个连接访问redis的时候,可以把这些连接都加一个监视效果,监视这个连接是否准备完毕(是否发送数据请求),如果某个连接准备完毕,直接处理这个连接,处理完成当前连接后再处理其他连接。
没有阻塞的状态,一直在处理连接。
3. 阻塞IO、非阻塞IO、IO多路复用区别:
4. redis的优缺点:
4.1 优点:
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
4.2 缺点:
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
5. 为什么要用缓存并且使用redis作为你们的缓存?
- 高性能:直接从内存中读取数据,速度更快性能更高
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
- 高并发:缓存的并发性能优于数据库
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
6. redis为什么这么快?
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
- 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路 I/O 复用模型,非阻塞 IO;
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
7. redis有哪些数据类型?
- string:
- list
- 单个元素可以储存4GB数据
- list的元素个数最多为2^32-1个,也就是4294967295个
- 是双向链表
- set
- 元素个数最多为2^32-1个,也就是4294967295个。
- zst
- hash
- 键值对个数最多为2^32-1个,也就是4294967295个
- geo
- bitmaps
- hyperLogLog
8. redis的使用场景有哪些?
- string:
- 缓存功能:避免多次访问数据库
- 重点对象保存:比如将某个常用对象的json缓存在redis中
- 时效性数据如验证码:通过expire过期
- 计数器、秒杀:通过原子性,自增方法incr、decr
- 发布订阅系统:通过pub/sub模式
- 共享session:可以将用户登陆后的信息放到redis中避免重新登陆
- 分布式锁:setnx设置不存在设置成功返回1存在不设置返回0,可以用来实现分布式锁
- list:
- 最新的N个数据:通过list按自然时间排序来实现
- 构建队列:利用list集合实现
- 微信抢红包:发红包的时候将红包按数量拆为list,抢红包的时候pop出一个
- 商品列表:对于请求量极大地列表,比如聚划算首页的商品列表,可以将数据存到redis的list中,通过定时器定时更新,同时list也可以通过lpush和lrange来实现分页
- 推送帖子:微博上用户发微博时,先将微博推送到自己的文章list,再推送到粉丝的文章list,此时用户查看自己的文章列表和关注的文章列表都能看到此文章
- set:
- 去除大量数据中的重复数据:利用set特性
- 淘宝黑名单:用户填写评价的时候通过sismember判断是否是黑名单用户
- 京东京豆抽奖:将奖品放入set集合通过srandmember随机返回一个元素
- 微博榜单好友推荐的随机展示
- 帖子点赞与取消点赞:点赞就将点赞用户放入set,取消就弹出
- 关注与粉丝列表:我关注某人就将我放入某人的粉丝set,同时将某人放入我的关注列表
- zset:
- 排行榜:因为每个值都有分数,所以可以很容易实现排行榜
- TOP N
- hash:
- 存储java对象:和string存储的区别是是hash存储具有一定灵活性(可以修改键值,而string只能修改整个value了)
- string存储对象适用于频繁读取,无需修改
- hash适用于频繁写操作
- 短连接:类似银行让你办贷款的推送的短信,里面会有一个地址,将地址复制到浏览器后就变成一个长地址
- 购物车:key代表用户id,field代表商品,value代表数量
9. 你项目中哪些地方用到了redis?
10. Redis支持事务么?
支持,Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化,按顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
11. redis事务的三个阶段及redis事务相关命令
11.1 三个阶段:
- 事务开始:MULTI命令
- 命令入队:如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队;
- 事务执行:EXEC命令
11.2 相关命令:
- WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
- UNWATCH命令可以取消watch对所有key的监控。
12. redis事务的特性:
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序地执行。事务在执行地过程中,不会被其他客户端发来的命令请求所打断
- 没有隔离级别的概念
- 队列中的命令没有提交执行都不会被执行,因为事务提交前任何指令都不会被执行。也就不存在:事务内的查询要看到事务里的更新,在事务外查询不能看到这个问题
- 不保证原子性
- Redis事务中如果有一条命令执行失败,其后的命令仍会被执行,没有回滚
- 事务错误处理
- 组队中的某个命令出现了报告错误(输入错误),则命令队列不存在
- 执行阶段某个命令报错了错误(执行错误),则只有报错的命令不会被执行,其他的命令都会执行,不会回滚;比如给string类型执行incr a命令
13. redis的持久化机制有哪些?各自的优缺点是什么?
redis持久化有两种方式:RDB和AOF
13.1 RDB
- 在指定的间隔时间内,将内存中的数据集快照写入磁盘。对应产生的数据文件为dump.rdb。
- 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存中。
- 比如每隔10分钟,将现在redis的存储快照写到硬盘中去。
- 通过配置文件中的save参数来定义快照的周期。
13.1.1 优缺点:
- 优点:
- 适合大规模的数据恢复,比 AOF 的启动效率更高
- 适用于对数据完整性和一致性要求不高的情况
- 恢复速度快
- 只有一个rdb文件,方便持久化,节省磁盘空间
- 通过fork子进程来完成备份写操作,主进程继续处理命令,保证了redis的高性能
- 缺点:
- Fork的时候,内存中的数据被复制了一份,大致2倍的膨胀性需要考虑
- 虽然Fork在复制时使用了写时复制功能,但是数据庞大时还是比较消耗性能
- 备份是每间隔一段时间做一次备份,如果redis意外down掉,会丢失最后一次备份后间隔时间内的所有修改
13.2 AOF
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只可以追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就是根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
13.2.1 持久化流程:
- 客户端的写操作会被append到aof的缓冲区
- AOF缓冲区根据aof持久化策略(always、everysec、no)将操作sync同步到磁盘的aof文件中
- aof文件大小超过设置的aof重写策略或者手动重写时,会进行aof文件重写,压缩文件大小(Rewrite压缩)
- redis重启时,会加载aof文件中的写操作达到数据恢复的目的
13.2.2 Rewrite压缩
AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF的大小超过了所定的阙值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
比如set a a;然后又执行了set a b;只会记录set a b。
redis4.0版本后的重写,是指就是把rdb的快照,以二进制的形式附在新aof的头部,作为已有的历史数据,替换掉原来的流水账操作,同时发生的命令及rdb中未记录的命令还是会以命令的形式保存在aof中。
13.2.3 优缺点
- 优点:
- 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
- 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
- 可读的日志文本,通过操作AOF文件,可以处理误操作(比如删除其中的某些命令如flushall等)
- 缺点:
- 比起RDB占用更多的磁盘空间(AOF文件比RDB文件更大)
- 恢复备份速度更慢
- 每次写都同步,有一定性能压力
- 存在个别bug,造成无法恢复
13.3 如何选择使用哪个持久化方式?
- 官方推荐2个都用
- 如果对数据不敏感,可以单独用RDB
- 不建议单独用AOF,可能会有bug
- 如果只是做纯内存缓存,可以都不用
- 如果两个都配了优先加载AOF
- RDB性能更好
- AOF数据一致性更好,数据更大
13.4 Redis持久化数据和缓存怎么做扩容?
14. redis的主从复制
主从复制:就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
14.1 用处:
- 读写分离,性能扩展(主库用来写,从库用来读)
- 容灾快速恢复
14.2 一主二从相关问题
- 一个从服务器宕机关机了,之后主服务器进行了写操作,此时从服务器重新连接并设置为主服务器的从服务器;从服务器还有在这段时间内主服务器写入的值吗?
答案:有,无论什么时变成主服务器的从服务器,都有主服务器的所有数据
- 从机是否可写?
答案:不可以
- 主机shutdown后会发生什么?
答案:从机在原地等待等待主机上岗。
14.3 复制原理:(异步复制)
- 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
- 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
- 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
- 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
- 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致
注意:主从复制是异步复制的。如果master主节点还没来得及同步(主从数据一致)给slave节点时发生宕机,那么master内存中的数据会丢失。
14.4 哨兵模式(同样可以用于集群)
用处:
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵至少需要 3 个实例,来保证自己的健壮性。
后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
14.5 存在的问题
15. redis集群
- Redis集群实现了对Redis的水平扩容,即启动了N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N
- Redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
- 集群中的每个节点,也可以做成主从复制的模式
15.1 解决的问题
- 容量不够
- 并发写操作
15.2 插槽slot
redis集群存储查询数据时,对key的计算并没有使用一致性hash,而是采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行
15.2.1 为什么是16384个槽?
集群中的各节点在握手成功后,两个节点之间会定期发送ping/pong消息,交换数据信息,在redis节点发送心跳包时需要把所有的槽信息放到这个心跳包里,以便让节点知道当前集群信息,在发送心跳包时使用char进行bitmap压缩后是2k(16384÷8÷1024=2kb),也就是说使用2k的空间创建了16k的槽数。
虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是说需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。
如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。redis的集群主节点数量基本不可能超过1000个。集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。槽位越小,节点少的情况下,压缩率高,Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
15.2.2 redis集群支持的最大节点数是多少?
即16384个
15.3 集群下的值设定与访问
- 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
- 每份数据分片会存储在多个互为主从的多节点上
- 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
- 同一分片多个节点间的数据不保持一致性
- 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
- 扩容时时需要需要把旧节点的数据迁移一部分到新节点
例:
登录到6379,设置一个值,可以看到k设置的插槽是12706,这个k-v被存放到6381这个服务器上去了。
当我们想要访问这个值的时候,值在哪个服务器就会切换到对应的服务器,这个过程不需要我们去处理,我们只管访问数据就行了。
15.4 如何强行将2个键值设置到一个槽
例:aa和aaa都在组user中:
15.5 集群中节点是如何通信的?
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
15.6 为什么redis不使用一致性hash而用slot槽
15.6.1 slot槽:
使用哈希槽进行扩缩容就会很方便,如果我们想要新添加个节点D, 我们只需要从之前的节点分部分哈希槽到节点D上。 如果我想移除某个节点,只需要将该节点中的哈希槽移到另外两个节点上,然后将该节点从集群中移除即可。从一个节点将哈希槽移动到另一个节点并不会停止服务(渐进式rehash),所以无论添加或是删除节点都不会造成集群的不可用,这样就实现了动态扩缩容。
15.6.2 一致性hash
一致性哈希用于解决分布式缓存系统中的数据选择节点存储问题和数据选择节点读取问题以及在增删节点后减少数据缓存的消失范畴,防止雪崩的发生。它是一个0到2的32次方的闭合环型结构,占用4个字节,拥有2的32次方个桶空间,每个桶空间可以存储很多数据。
一致性哈希是采用的是如下步骤:
- 对节点进行hash,通常使用其节点的ip或者是具有唯一标示的数据进行hash(ip),将其值分布在这个闭合圆上。
- 将存储的key进行hash(key),然后将其值要分布在这个闭合圆上。
- 从hash(key)在圆上映射的位置开始顺时针方向找到的一个节点即为存储key的节点。如果到圆上的0处都未找到节点,那么0位置后的顺时针方向的第一个节点就是key的存储节点。
- 添加节点:如果在节点A和节点C中间增加一个节点D,那么在节点A和节点C之间的部分数据要存储的节点就会有所变化,在节点C到节点D之间的数据会从节点A转移到节点D。
- 删除节点:如果删除一个节点,就会把当前节点所有数据加到它的下一个节点上。这样会导致下一个节点使用率暴增,可能会导致挂掉,如果下一个节点挂掉,下下个节点将会承受更大的压力,最终导致集群雪崩。
- 节点太少:可能会造成数据倾斜,假设只有俩节点,可能会造成大量数据存放在node A节点上,而node B节点存储很少的数据。
15.6.3 对比:
- 一致性哈希的节点分布基于圆环,无法很好的手动设置数据分布,比如有些节点的硬件差,希望少存一点数据,这种很难操作。而哈希槽可以很灵活的配置每个节点占用哈希槽的数量
- 一致性哈希的某个节点宕机或者掉线后,当该机器上原本缓存的数据被请求时,会从数据源重新获取数据,并将数据添加到失效机器后面的机器,这个过程被称为 “缓存抖动” ,而使用哈希槽的节点宕机,会导致一定范围内的槽不可用,只能通过主从复制加哨兵模式保证高可用。
- 真是基于一致性哈希的特点,当某台机器宕机时,极易引起雪崩,如上述介绍中删除节点。
- Redis Cluster的槽位空间是可以用户手动自定义分配的,类似于 windows 盘分区的概念,可以手动控制大小。
- 相对于哈希槽,一致性哈希算法更复杂
15.7 集群故障恢复
- 如果主节点下线?从节点能否自动升为主节点?
答案:从节点会自动升为主主节点
- 主节点恢复后,主从关系如何?
答案:原来的主节点会变成从节点
- 如果某一端插槽内的主从节点都当掉,redis服务是否还能继续?
答案:那么那个地方的插槽就不能用了(获取不到保存在那些插槽内的值)
15.8 生产环境中的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 负责缓存集群的运维。
15.9 Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。如:
- 过期 key 被清理
- 最大内存不足,导致 Redis 自动清理部分 key 以节省空间
- 主从复制是异步复制的。如果master主节点还没来得及同步(主从数据一致)给slave节点时发生宕机,那么master内存中的数据会丢失。
- 单独的主备方案,网络不稳定触发哨兵的自动切换主从节点,切换期间会有数据丢失
15.10 什么是redis集群脑裂问题?
Redis的集群脑裂指在主从集群中,同时有两个master主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个master主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。此时Redis的集群脑裂就有可能会导致数据丢失。
15.10.1 为什么会发生脑裂?
- 网络问题:导致Redis Master节点跟Redis Slave节点和哨兵Sentinel集群处于不同的网络分区,此时因为Sentinel集群无法感知到master的存在,所以将Slave节点提升为Master节点。此时就存在两个不同的Master节点,就像一个大脑分裂成了两个。
- 主机资源问题:redis Master节点所在的服务器上的其他程序临时占用了大量资源(例如 CPU 资源),导致主库资源使用受限,短时间内无法响应心跳,于是Sentinel集群重新选举了新的Master,当其它程序不再使用资源时,旧Master节点又恢复正常,同一集群下出现两个Master。
- Redis 主节点阻塞:主库自身遇到了阻塞的情况,例如,处理 bigkey 或是发生内存 swap,短时间内无法响应心跳,还是会触发Sentinel机制,等主库阻塞解除后,又恢复正常的请求处理了。
15.10.2 解决:
在redis的配置文件中有两个参数我们可以设置:
min-slaves-to-write N
min-slaves-max-lag N
服务降级解决master拒绝写请求问题,但是那些写请求可以写到Mysql中,再把数据从MySQL加载出来,写到正常的mater去。
16. redis分区
16.1 Redis是单线程的,如何提高多核CPU的利用率?
可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
16.2 为什么要做Redis分区?
分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
16.3 你知道有哪些Redis分区实现方案?
- 客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
- 代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
- 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
16.4 Redis分区有什么缺点?
- 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
- 同时操作多个key,则不能使用Redis事务.
- 分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)
- 当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
- 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
17. 分布式问题
17.1 Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
- 当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
- SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
- 返回值:设置成功,返回 1 。设置失败,返回 0 。
使用SETNX完成同步锁的流程及事项如下:
- 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
- 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
- 释放锁,使用DEL命令将锁数据删除
17.2 如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
参考:https://www.jianshu.com/p/8bddd381de06
17.3 分布式Redis是前期做还是后期规模上来了再做好?为什么?
既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。
这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。
17.4 什么是 RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
- 安全特性:互斥访问,即永远只有一个 client 能拿到锁
- 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
- 容错性:只要大部分 Redis 节点存活就可以正常提供服务
18. 缓存异常
18.1 缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
- 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
18.2 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
18.3 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是大量不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
18.4 缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决方案:
- 直接写个缓存刷新页面,上线时手工操作一下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
18.5 缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
- 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
- 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
- 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
18.6 热点数据和冷数据
热点数据,缓存才有价值
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存
对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。
数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
18.7 缓存热点key
缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
- 对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
19. redis相关工具
19.1 Redis支持的Java客户端都有哪些?官方推荐用哪个?
Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
19.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)。
19.3 Jedis与Redisson对比有什么优缺点?
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
20. redis过期键处理
我们都知道,Redis是key-value数据库,我们可以通过expire命令
设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
- 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
- 优点:可以立即清除过期的数据,对内存很友好
- 缺点:会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键
- 优点:可以最大化地节省CPU资源
- 缺点: 却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存
- 定期删除: 每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多个过期键,以及要检查多少个数据库,则由算法决定
- 优点:该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果
- 缺点:定期删除策略的难点是确定删除操作执行的时长和频率。执行太频繁消耗太多CPU,执行太少浪费内存
第一种和第三种为主动删除策略,而第二种则为被动删除策略
Redis中同时使用了惰性过期和定期过期两种过期策略。
20.1 RDB对过期键的处理
- 生成RDB文件:在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中
- 载入RDB文件:
- 如果服务器以主服务器模式运行,未过期的键会被载入到数据库中,而过期键则会被忽略,所以过期键对载入RDB文件的主服务器不会造成影响
- 如果服务器以从服务器模式运行,所有键不论是否过期,都会被载入到数据库中。不过,因为主从服务器在进行数据同步的时候,从服务器的数据库就会被清空,所以一般来说,过期键在载入RDB文件的从服务器也不会造成影响
20.2 AOF对过期键的处理
- AOF文件写入:当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一个DEL命令,来显式地记录该键已被删除
- AOF文件重写:和生成RDB文件时类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中
20.3 复制对过期键的处理
- 主服务器在删除一个过期键之后,会显示地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键
- 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续处理未过期的键一样来处理过期键
- 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键
通过由主服务器来控制从服务器统一地删除过期键,可以保证主从服务器数据的一致性
21. redis内存相关
21.1 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
21.2 Redis的内存淘汰策略有哪些
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
Redis共提供了8中缓存淘汰策略,其中 volatile-lfu 和 allkeys-lfu 是Redis 4.0版本新增的。
- 不进行淘汰数据。
- noeviction:一旦缓存被写满,再有写请求进来,Redis就不再提供服务,而是直接返回错误。Redis 用作缓存时,实际的数据集通常都是大于缓存容量的,总会有新的数据要写入缓存,这个策略本身不淘汰数据,也就不会腾出新的缓存空间,我们不把它用在 Redis 缓存中。
- 进行淘汰数据
- 对设置了过期时间的数据进行淘汰:
- volatile-ttl:在设置了过期时间的键值对中,移除即将过期的键值对
- volatile-random:在设置了过期时间的键值对中,随机移除某个键值对。
- volatile-lru:在设置了过期时间的键值对中,移除最近最少使用的键值对
- volatile-lfu:在设置了过期时间的键值对中,移除最近最不频繁使用的键值对
- 对所有数据进行淘汰:
- allkeys-random:在所有键值对中,随机移除某个key。
- allkeys-lru:在所有的键值对中,移除最近最少使用的键值对。
- allkeys-lfu:在所有的键值对中,移除最近最不频繁使用的键值对
通常情况下推荐优先使用 allkeys-lru 策略。这样可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。
21.3 Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
21.4 Redis如何做内存优化?
可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面
22. redis线程模型
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
- 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
23. 布隆过滤器
23.1 特性
- 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。
- 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。
- 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。只判断是否存在,不存储元素本身
- 随着数据的越来越多,存在误判率,只能判断是否一定不存在,不能判断是否存在
23.2 添加元素步骤
- 将要添加的元素给 k 个哈希函数
- 得到对应于位数组上的 k 个位置
- 将这k个位置设为 1
23.3 查询元素步骤
- 将要查询的元素给k个哈希函数
- 得到对应于位数组上的k个位置
- 如果k个位置有一个为 0,则肯定不在集合中
- 如果k个位置全部为 1,则可能在集合中
23.4 应用:
- 防止数据库穿库
- 应对缓存击穿
- 判断用户是否读过某篇文章等
24. 其他问题
24.1 如何保证缓存与数据库双写时的数据一致性?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
问题场景 |
描述 |
解决 |
先写缓存,再写数据库,缓存写成功,数据库写失败 |
缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读 |
这个写缓存的方式,本身就是错误的,需要改为先写数据库,把旧缓存置为失效;读取数据的时候,如果缓存不存在,则读取数据库再写缓存 |
先写数据库,再写缓存,数据库写成功,缓存写失败 |
写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据 |
缓存使用时,假如读缓存失败,先读数据库,再回写缓存的方式实现 |
需要缓存异步刷新 |
指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候 |
确定哪些数据适合此类场景,根据经验值确定合理的数据不一致时间,用户数据刷新的时间间隔 |
24.2 Redis常见性能问题和解决方案?
- Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
- 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
- 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
- 尽量避免在压力较大的主库上增加从库
- Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
- 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。
24.3 一个字符串类型的值能存储最大容量是多少?
512M
24.4 Redis如何做大量数据插入?
Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。
24.5 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
24.6 使用Redis做过异步队列吗,是如何实现的
使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。
24.7 Redis如何实现延时队列
使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。
24.8 Redis回收进程如何工作的?
- 一个客户端运行了新的命令,添加了新的数据。
- Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
24.9 Redis回收使用的是什么算法?
LRU算法
24.10 redis支持存放多少key?
一个单实例的redis最多能支持2^32个键,差不多就是2.5亿个,每个key中的值也是可以存 2^32行数据,所以服务器的内存才是我们所担心的