redis缓存学习笔记、双写一致性

    xiaoxiao2022-07-07  220

    为什么要用缓存?

    高性能、高并发

    redis 和 memcached 有啥区别?

    redis支持复杂的数据结构 redis原生支持集群模式, memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。redis 只使用单核,而 memcached 可以使用多核,每一个核上 redis 在存储小数据时比 memcached 性能更高。在 100k 以上的数据中,memcached 性能要高于 redis

    redis 的线程模型:

    内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程的模型。文件事件处理器的结构包含 4 个部分: 多个 socketIO 多路复用程序文件事件分派器事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

    多个socket可能会并发操作产生不同的操作,redis端IO多路复用程序(一个线程)监听多个server socket,将socket产生的事件放入队列中排队,事件分派器每次从队列中去一个事件,交给对应的事件处理器处理。

    个人理解:redis的线程模型和netty的线程模型相似,监听发起者和被监听者正好相反:

    redis的一个线程监听多个应用的socket。(用户线程通过select监听netty线程)netty则是用户线程通过select监听netty的多个socket。(redis监听用户线程)

    为啥 redis 单线程模型也能效率这么高?

    纯内存操作核心是基于非阻塞的 IO 多路复用机制单线程反而避免了多线程的频繁上下文切换问题

    redis 主要有以下几种数据类型:

    stringhash    list            可以基于 list 实现高性能分页查询set             两个人的粉丝列表整一个交集,看看俩人的共同好友是谁sorted set      去重但可以排序,写进去的时候给一个分数,自动根据分数排序,可以做排行榜

    redis 过期策略:

    定期删除:定期删除过期数据性删除:获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

    Redis的事务操作:

    MULTI:标记事务的开始,redis将后续命令放入队列中,使用EXEC命令原子化地执行这个命令。EXEC:在一个事务中执行所有先前放入队列的命令,然后回复正常的连接状态。DIDCARD:清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。WATCH:当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。UNWATCH:清除所有先前为一个事务监控的键。使用方法:首先使用MULTI进入一个rendis事务,之后可以发送给多个redis命令,一点调用EXEC命令,那么redis就会执行事务中的所有命令。相反,调用DISCARD命令将会清空事务队列,然后退出事务Redis使用WATCH命令实现事务的“检查再设置”(CAS)行为。 若redis没有incr命令            WATCH mykey            val = GET mykey            val = val + 1            MULTI            SET mykey $val            EXEC

    由上面伪代码可知,如果存在竞争状态,并且有另外一个客户端在我们调用WATCH命令和EXEC命令之间的时间内修改了val变量的结果,那么事务将会运行失败。

    redis内存淘汰机制:

    noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧。allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

    redis 持久化的两种方式:

    RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中。

    redis 原生支持的 redis 集群模式:

    redis cluster: 自动将数据进行分片,每个 master 上放一部分数据提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。gossip 协议:ping,pong,meet,fail等 分布式寻址算法: hash 算法(大量缓存重建)一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)redis cluster 的 hash slot 算法 edis cluster 的高可用与主备切换原理: 判断节点宕机从节点过滤从节点选举

    与哨兵比较:整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。

    Redis 主从架构:redis replication、redis sentinel

    redis replication:一个节点写,多个节点读redis sentinel:一个节点写,多个节点读

    缓存雪崩:

    缓存挂了,所有请求打到mysql上,数据库挂了缓存雪崩的事前事中事后的解决方案如下: 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

    缓存穿透:

    黑客发出的 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。这样的话,下次便能走缓存了。

    缓存与数据库的双写一致性

    一、Cache Aside Pattern:

    读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。

    二、最初级的缓存不一致问题及解决方案:

    问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

    三、比较复杂的数据不一致问题分析:

    更新操作:根据唯一标识路由到jvm内部队列->更新缓存->更新数据库查询操作:查询缓存(空)->根据唯一标识路由到jvm内部队列->等待更新操作完成->读取数据库/缓存队列中: 请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。优化:部署多个内存队列

    详细方案:

    遇到这种情况,可以用队列的去解决这个问题,创建几个队列,如20个,根据商品的ID去做hash值,然后对队列个数取摸,当有数据更新请求时,先把它丢到队列里去,当更新完后在从队列里去除。如果在更新的过程中,遇到查询场景,先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,这里有一个优化点,如果发现队列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个while(true)循环去查询缓存,循环个200MS左右,如果缓存里还没有则直接取数据库的旧数据,

    高并发的场景下,该解决方案要注意的问题:

    多服务实例部署的请求路由: 可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器路由到相同的服务实例上。比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 热点商品的路由问题,导致请求的倾斜对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理消息生产和消费。 更新操作:RabbitMQ->更新缓存->更新数据库查询操作:查询缓存(空)->RabbitMQ->等待更新操作完成->读取数据库/缓存

    四、canal同步数据库:

    打开sql的二进制文件,通过canal同步mysql数据到redis中网上不错的方案:https://blog.kido.site/2018/12/09/db-and-cache-04/

    redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗?

    可以基于 zookeeper 实现分布式锁。你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据

     

     

    最新回复(0)