深入学习Redis

深入学习Redis

  • 入门了redis之后,(入门链接)来深入学习redis,并进行总结

Redis的数据结构应用场景

  • String:key-value结构中,value不仅可以是String,也可以是数字类型。可以应用在比如博客粉丝数量、评论数量、阅读数量的缓存。redis也提供了计数器类型的命令(incr、decr等)
  • Hash:Hash表中可以储存多个K-V结构元素,可以用来储存用户的信息模块。
key=User123
value={
  “id”: 1,
  “name”: “BengHiong”,
  “age”: 21,
  “location”: “guangdong”
}
  • List:链表结构,可以应用于显示某一列信息(如用户关注列表、粉丝列表、作品列表等),还可以通过lrange命令进行分页查询,由于redis速度快特性,实现用户不断下拉操作数据仍能快速呈现的效果。
  • Set:Set结构自带了排重功能,可以通过交集命令sinterstore实现多个用户共同好友、共同关注、共同爱好的功能,也可以用sdiffstore命令实现体现用户如Q群未加好友的陌生人。
  • SortedSet:相比Set多了个socre权重,实现了排序功能。可以应用于例如游戏排行榜、礼物排行榜功能。

Redis为什么能这么快?

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中。
  • 结构类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

为啥是单线程?
redis由于是内存操作,速度非常快,所以使用单线程不必过于担心某个操作时间占用较长导致其他操作长时间阻塞,而且单线程模式下可以减少线程间的切换和竞争开销,以及不需要加锁机制,提高了性能。(由于是单线程,尽量避免那些操作时间较长的命令如 keys *),多线程的话需要加锁,还需要线程间大量的cache同步。因为每一次命令执行时间短,所以综合下来,单线程才是最优方案
注意: redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块仍用了多个线程。(比如持久化操作需要fork一个子进程进行数据备份操作)

  • IO多路复用机制:当有多个redis客户端连接redis服务端的时候,为了防止客户端无操作导致线程阻塞情况,redis使用了IO多路复用,可以大大提高服务端处理的连接数量,提高并发性能
  • redis是使用C语言编写,性能高

Redis持久化

redis的一大特性就是支持持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
RDB和AOF的简单实现以及在入门redis进行讲述了,下面是对他们细节的一些总结。

  • RDB持久化:
    redis通过fork一个子进程将数据写入到临时的RDB文件中,当写入完成后将这个新的RDB文件替代掉原来的dump.rdb文件,数据是以二进制文件保存。

  • AOF持久化
    AOF持久化是以日志形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,由于是以文本的方式记录,所占用空间比RDB方式占用空间更大。

RDB和AOF的选择

  • 如果可以承受数分钟以内的数据丢失,可以是用RDB(RDB对性能影响相较于AOF更小),而如果你对数据同步要求高,要求数据尽量丢失少,可以使用AOF(可以使用每秒备份、每操作备份、不备份),每操作备份性能影响较大,推荐使用每秒备份,最多也只是丢失一秒的数据,而且性能会更好。
  • 如果数据很重要,可以建立一个从库进行AOF持久化,设置为每秒同步,尽量不要在主库进行同步操作。

Redis的内存淘汰机制

  • expire time:redis中会给每个key设定过期时间,时间到了之后会有redis进行移除工作,这在缓存中是非常必要的,因为缓存空间毕竟是有限的。那当到了过期时间时,redis是怎么将这个key移除的呢?这就要说到redis的两种删除方式了。定期删除和惰性删除

引用自:https://juejin.im/post/5ba591386fb9a05cd31eb85f

  • 定期删除:

redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!

  • 惰性删除:

定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!

下面说说redis中的6中内存淘汰策略:

redis 提供 6种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

Redis缓存雪崩、缓存击穿、缓存穿透

  • 缓存雪崩
    介绍:假设缓存都设定了失效时间,在同一时间内缓存大量失效.如果这时用户高并发访问.缓存命中率过低.导致全部的用户访问都会访问数据库,导致数据库压力过大出现宕机。
    解决方案
    简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

  • 缓存穿透
    介绍:当访问一个不存在的数据时,因为缓存中没有这个key,导致缓存形同虚设.最终访问后台数据库.但是数据库中没有该数据所以返回null.比如有黑客恶意频繁访问一个不存在的数据,导致命令都落在了Mysql数据库上,导致数据库压力过大出现宕机。
    解决方案
    1、使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,当访问一个一定不存在的数据时会被这个bitmap拦截,避免访问到底层数据库
    2、当查询数据为空时(不论是数据不存在还是系统异常),都将这个空结果进行缓存,但是这个缓存存活时间较短,一般设置在不超过五分钟

  • 缓存击穿
    如果给一个key设定了失效时间,当key失效时有一万的并发请求访问这个key,这时缓存失效,所有的请求都会访问后台数据库.数据库压力过大出现宕机。
    解决方案
    1、互斥锁:当并发访问某个过期的数据时,使用锁机制,只有获取锁的线程才能继续访问底层数据库并将数据缓存到数据库,而没有获取锁的则休眠一段时间后再继续访问内存(休眠时间设置为将数据从数据库缓存到内存时间再长一点点)
    2、对热点数据设置为“永不过期”:如不设置过期时间,就可以实现缓存不被清除,或者设置过期时间,当要快要过期的时候,设置后台线程进行缓存构建。

问题处理方案:https://blog.csdn.net/zeb_perfect/article/details/54135506

如何解决 Redis 的并发竞争 Key 问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!(如果是单系统就不需要考虑这个问题了,因为redis本身是单线程的)
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
引用链接:https://juejin.im/post/5ba591386fb9a05cd31eb85f

如何保证缓存与数据库双写时的数据一致性?

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

  • 保证一致性:
    将写操作和读操作串行化,当对缓存或数据库数据进行写入、修改操作完成的时候,马上执行同步操作,使缓存和数据库数据一致。这样会大大降低系统吞吐量,在没有要求强一致的情况下不必使用这种方案

下面是对不同场景所选择不同的处理方式

  • 读场景

先从缓存获取数据,如果缓存没有命中,则回源到数据库获取源数据。将数据放入到缓存,下次即可从缓存中获取数据。

  • 非高并发情况下的写场景

先将数据写入数据库,写入成功后立即同步将数据写入缓存。

  • 写多读少的写场景

先将数据写入数据库,写入成功后,将缓存数据过期/删除,下次读取时再加载缓存。这样的好处是避免了不必要的写缓存操作。

  • 高并发情况下的写场景

先写缓存,再定期更新数据库:异步化,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存。

引用:https://blog.csdn.net/Dustin_CDS/article/details/79595297

总结
redis是一种很流行的缓存工具,除了知道怎么使用,还要学习他一些功能实现的原理(如redis如何实现高可用的)以及在不同场景如何应用才能发挥其最大作用,redis缓存技术也不是完美的,我们需要知道它的漏洞并分析解决它,这样才能发挥出工具的真正强大功能(如缓存雪崩/击穿要如何应对、多系统下并发问题)。除了这些问题,redis还有关于主从模式下的功能问题值得拓展,我会在后续的文章中进行总结。

redis优缺点解决方案:https://blog.csdn.net/u013322876/article/details/53817757

猜你喜欢

转载自blog.csdn.net/qq_41797857/article/details/89185011