这篇文章要解决的问题
A
- 什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
- redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?
- Redis 有哪些架构模式?讲讲各自的特点
- Redis常用命令?
- 使用过Redis分布式锁么,它是怎么实现的?
- 使用过Redis做异步队列么,你是怎么用的?有什么缺点?
- mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
- redis常见性能问题和解决方案:
- redis事物的了解CAS(check-and-set 操作实现乐观锁 )?
B
- 为什么使用redis
- 使用redis有什么缺点
- 单线程的redis为什么这么快
- redis的数据类型,以及每种数据类型的使用场景
- redis的过期策略以及内存淘汰机制
- redis和数据库双写一致性问题
- 如何应对缓存穿透和缓存雪崩问题
- 如何解决redis的并发竞争问题
以上是我们使用redis会遇到的问题,这篇文章我们将以解决这些问题为最终目的来进行总结。
1,2的14次方是16384
redis数据类型
类型 | 说明 | 命令 | 使用场景 |
---|---|---|---|
String | 二进制安全的字符串,该类型可以接受任何格式的数据 | set key value | value较小、模型简单的 value可以使用String类型存储 |
List | 按照插入顺序排序的字符串链表 | lpush name value,rpush name value,lrem name index key,llen name | 在评级系统中,比如社会化新闻网站,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页;在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入进博客评论等等。 |
Set | 没有排序的字符串集合,和List类型一样,区别是不允许重复 | sadd name value | 可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP地址信息。对于此场景,仅需在每次访问该博客时将访问者的IP存入Redis中,Set数据类型会自动保证IP地址的唯一性。 |
SortedSet(zset) | 和Set相似,一个Set中。它们之间的主要差别是SortedSet中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序 | zadd name score value | 1,可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP TEN的用户信息。当然也可以利用ZRANK命令通过username来获取玩家的排行信息。最后将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。2,SortedSet类型还可用于构建索引数据。3,建立一个SortedSet中元素个数不要超过 1 W。 |
Hash | Redis hash 是一个键值(key=>value)对集合。 Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 | hmset name key1 value1 key2 value2 | 1,对于海量数据的情况,可以自己对数据进行分桶,然后使用Hash结构来存储。对于很多value为简单的字符串,做过测试,采用hash存储更节省空间。2,将对象存储为Hash结构而不是String,可以每次只更新、获取Hash中的一个field,这样可以提高效率。比如session比较适合用hash存放 |
redis持久化
RDB(默认) 全量模式
SAVE
客户端可以显式触发,单线程串行化执行命令
把redis的当前状态写入磁盘作为快照保存的过程。
BGSAVE
BGSAVE命令执行始于fork出一个子线程,在子线程成功启动之后修改一些redisServer对象的状态之后执行关闭。
AOF 增量模式
基于全量的持久化保存是数据的“状态”,而增量持久化保存的是状态的每一次变迁。
每次处理完写命令后,通过propagate函数触发,propagate方法将当前命令的内容append到redisServer对象的aof_buf变量中。主循环在下一个迭代进入多路复用的select方法搴,redis会通过flushAppendOnlyFile方法将aof_buf的内容write到AOF对应的文件中。再显式调用fsync()方法才能强制地让操作系统落地数据到磁盘。
redis的AOF包含三种同步策略:
- always:每个flushAppendOnlyFile方法直接触发fsync方法,强制数据落地到磁盘
- every second:每秒一步触发一次fsync方法
- no:不显式调用fsync,由操作系统决定什么时候落地到磁盘。
AOF优化
AOF文件会越来越大,降低回放加载效率。redis通过rewrite机制合并历史AOF记录。
处理方式:当增量数据积累到大于某个状态快照的程度,此时将这些增量用快照代替。但数据仍然存储在AOF文件中。
集群部署方式
主从复制(master-slave)
master节点对外提哦那个读写服务,slave节点作为master的数据备份,拥有master的全量数据,提供读服务
数据同步方式:BGSAVE
sentinel
sentinel节点间因为共同监视同一个maseter节点从而也关联了起来
sentinel节点通过定期地向master发送心跳包判断其存活状态,称为ping
proxy型
主备+sentinel+proxy
直连(拓扑结构)
16384个数据分片,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
去中心化,客户端可以和集群中任意节点链接。
redis cluster通过引入两个自增的epoch变量来使得集群配置在各个节点间达成最终一致。
节点的fail是通过集群中超过半数的节点检测失效时才生效。
redis 内存淘汰机制
在redis.conf中有一行配置
maxmemory-policy volatile-lru
该配置就是配内存淘汰策略的
- 1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
- 2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
- 3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
- 4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
- 5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
- 6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
redis事务
我们可以通过MULTI命令开启一个事务
可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作
WATCH命令可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务
执行失败
redis常见性能问题和解决方案:
- 1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
- 2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
- 3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
- 4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内
redis 非阻塞I/O多路复用机制
我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
单线程redis为什么快
- 纯内存操作
- 单线程避免了频繁的线程上下文切换
- 非阻塞多路复用io
缓存穿透和缓存雪崩
缓存穿透
黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
- (一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
- (二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
- (三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
缓存雪崩
- (一)给缓存的失效时间,加上一个随机值,避免集体失效。
- (二)使用互斥锁,但是该方案吞吐量明显下降了。
- (三)双缓存。
双缓存
我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点
- 从缓存A读数据库,有则直接返回
- A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
- 更新线程同时更新缓存A和缓存B。