缓存和redis

分布式缓存

1.为什么使用缓存

​ 使用缓存最大的一个原因是为了提高性能,如果不用缓存每次查询都要去数据库查询,性能差,使用缓存之后可以将查询的数据放入缓存,这样下次查询不需要访问数据库。
​ 在因学项目中,课程、教室的查询都用了缓存,因为这些信息类似商品,发布之后改动不大,更多的是用户的查询。

2.redis的数据类型及使用场景

数据类型形式特点
StringKey-value最简单的key-value,存放数据
hashkey-filed-value一般用于存放对象,可以直接操作对象的某个字段的值
SetKey-value无序去重,value类似set,可以用在并集的情况,例如两个人的共同好友。
ZSetKey-value去重但排序,在add的时候可以有一个分数,然后根据分数排序,用在排行榜这种情况。
Listkey-value可以用来存放例如某个人的好友、某个课程的上课学生;此外还可以用来做消息队列,实现也很简单只要用rpush和rpop命令就可以实现(因为list的数据结构就是双向链表)。

redis的过期策略和淘汰机制

1. 过期策略

​ 假如一批key设置了超时时间,当到了超时时间之后redis会有两种删除过期key的策略:定时删除和惰性删除。

​ 定时删除是redis每个100ms就随机检查一部分key,如果已经超时了就删除。这种方式是随机检查部分key,不可能对所有的key都进行检查,这样消耗太多性能了;这样也带来了可能已经过期的key没有被检查到,没有删除。

​ 惰性删除是在查询key的时候先检查key是否过期,如果过期了就删除。

2. 淘汰机制

​ 如果redis中很多过期但未删除的key或者不常用的key,而占用了很大的内存,那么在内存不足的时候就需要淘汰机制来处理。淘汰机制有6种(主要是第一个):

  1. allkey-lru:内存不足时,在所有key中移除最近最少使用的。(最常用
  2. volatile-lru:内存不足时,在设置了过期时间的key中移除最近最少使用的。
  3. allkey-random:内存不足时,在所有key中随机删除。
  4. volatile-random:内存不足时,在设置了过期时间的key中随机删除。
  5. noeviction:内存不足时,新写入数据时会报错。
  6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

​ 对于lru淘汰算法,可以通过LinkedHashMap实现,lru最关键的是用链表队列的形式去将新的访问的key放在队头,旧的在末尾,然后淘汰的时候淘汰末尾的数据。

redis的单线程模型

​ redis的线程模型是基于reactor模型的单线程实现,主要是4个部分:多个socket、一个IO复用部分、事件分发器和事件处理器。这种方式虽然是单线程处理,但是这种IO多路复用的方式可以处理较大的连接。

​ 为什么redis的性能这么好呢?

  1. 纯内存操作。
  2. 非阻塞的IO多路复用技术。
  3. 单线程模型避免了线程上下文切换的开销。

redis的集群实现

1. 高性能

​ 单机的redis最多支持几万的QPS,在一些高并发的场景下需要用redis集群架构来实现。redis的集群设计有两个核心,分区和主从。

主从:一个master和多个slaver,master负责读写,slaver只负责读。这种设计使得读写分离,能支持更大的并发,但是一个master的读写性能还是有限,并且master的内存容量也限制了redis集群的容量,为此需要分区的设计。

分区:redis虚拟设计了16384个槽,多个master会均分16348个槽,每个master只负责一部分槽。这种设计类似哈希表,对于key,经过hash计算后能得到0~16347中的某个值,也对应某个master,这样就实现了redis整体数据的分区。![image-20181020230752752](/Users/jerryq/Library/Application Support/typora-user-images/image-20181020230752752.png)

​ 结合主从和分区的设计,redis可以达到几十万的QPS,并且如果要提高读性能,可以扩展slaver;如果要扩展写性能,或提供更大的缓存空间,可以扩展master(redis支持动态扩容)。但master如果挂了,那么一部分数据可能就查不到了,为此需要下面的哨兵来实现高可用(redis集群设计默认有高可用-哨兵的实现)。

2. 高可用-哨兵

哨兵是redis的一个组件,主要有3个功能:

  1. 监控集群状态。
  2. 如果有节点挂了,通知开发人员。
  3. 如果master挂了,自动将其下的一个slaver用作master。

3. redis集群

  1. redis集群自动将数据进行分区。
  2. 按照用户配置文件实现主从。
  3. 默认实现选举方式的高可用,master和slaver之间通过ping-pong的方式维持连接,如果某个节点ping了之后没有回应,判断另个节点死掉了,如果半数以上都认为这个节点挂了那就是挂了,会让该节点的slaver充当master继续提供服务。
  4. 如果redis采用6379作为客户端访问端口,那么16379是集群的总线通讯端口,节点之间通过这个端口进行通信(gossip协议)。
  5. key寻址方式整个集群是去中心化的设计,每个master之间互相通信,外部客户端访问redis集群时将key计算CRC16值后取模,就可以得到hash slot,然后找到对应的master/slaver主从部分进行访问。

redis的持久化方案

  1. RDB

    redis每隔一段时间对数据进行持久化(可能在时间间隔内数据丢失)

  2. AOF

    redis会将所有的写命令追加在一个日志文件(redis恢复数据慢、降低了redis的写性能)

缓存穿透和缓存雪崩

1. 缓存穿透

​ 恶意攻击访问一个redis中不存在的key,因为缓存中没有这个key就需要去数据库查询,但数据库中并没有这条记录,导致缓存中一直没有,这样大量的访问就穿透了redis给数据库带来了压力。

​ 解决方法:1. 就算db中查询了没有也依然把这个key放入缓存。

​ 2. 布隆滤波器。在查询之前选取一个大的bitmap中查询是否有这个key。

2. 缓存雪崩

​ redis大量的key同时失效,但此时又发生了极高的并发请求,那么大量的请求要去访问数据库,给数据库带来压力。

​ 解决方法:1. 不同的key设置不同的过期时间。

​ 2. 服务降级和熔断。

缓存和数据库双写一致性问题

​ 这个问题一般是对于数据修改这种操作的场景,一般不会采用对缓存修改数据,而都采用删除key这种方式,这是因为修改缓存的方案主要有两点缺陷:1.对于写多读少的应用场景,需要频繁修改数据,频繁修改缓存,不如修改DB之后,当要查询的时候才将其放入缓存来的有效。2.存在线程安全的问题。

​ 通常对于修改数据这种,会先删除缓存,再去db修改数据;或先去db修改数据,在删除缓存,但无论是哪种对于读写都是并发的,就有可能出现缓存和数据数据不一致的问题:

  • 先删缓存再修改数据

    1. 线程A(修改)先删除缓存。
    2. 线程B(查询)去查询数据,发现缓存中没有key,去DB中将原来的值取出并放入了缓存中。
    3. 线程A去DB修改数据,数据为新值。

    可以看到最后的结果,缓存和DB中的值不一致。

  • 先修改数据再删除缓存(一般采用这个

    1. 线程A(修改)先去DB中修改数据。但删除缓存的时候出问题了,没有删除缓存。
    2. 线程B(查询)去缓存中查询,那么查询的一直是旧值。

    因为修改的线程挂了,导致最终缓存和DB中的值不一致。

解决方案:

  1. 对key设置超时时间,但这样会在key的存活时间内有数据不一致的问题。
  2. 设计一个重试的队列。这样保证删除缓存失败的时候,队列能提供再次删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值