1 Star 0 Fork 38

一世宿敌 / java面试迷你版

forked from papi林 / java面试迷你版 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
redis-缓存.md 9.74 KB
一键复制 编辑 原始数据 按行查看 历史
papi林 提交于 2020-05-19 23:23 . count1count*

redis/缓存

应用场景

list队列做一个FIFO双向队列,实现一个轻量级高性能消息队列服务。

用它的Set做高性能的tag系统。

会话缓存。

全页缓存。

排行榜/计数器。

发布/订阅。

像商品SPU做了缓存,每个商家的SPU可能还得缓存不一样,因为查商品需要,看订单也要,比较频繁。

对外的物流信息查询,一般短时间内变化过少,可以缓存半个小时,避免多次查询刷新。

实现延迟队列,用sorted set,时间戳做score,消费者获取指定时间的时间戳的数据。

使用redis的好处是,一处缓存多处使用,同时有丰富的数据类型。redis是单线程的,但是IO多路复用。

缺点:内存使用过多需要定期删除数据、完整同步RDB会占用CPU消耗带宽,修改配置重启时间较久并且期间不能提供服务。

数据类型:String、List、Hash、Set、Sorted Set、bitmap位图,geo地理位置,hyperloglog等

过期删除:定期扫描随机删除,惰性删除查的时候检查是否要删。

提供了一些内存淘汰机制,一般用allkey-lru(内存不足时,删除最近最少使用的key)

  1. noeviction:默认策略,不淘汰,如果内存已满,添加数据是报错。
  2. allkeys-lru:在所有键中,选取最近最少使用的数据抛弃。类似LinkedHashMap。
  3. allkeys-random: 在所有键中,随机抛弃。
  4. volatile-lru:在设置了过期时间的所有键中,选取最近最少使用的数据抛弃。
  5. volatile-random: 在设置了过期时间的所有键,随机抛弃。
  6. volatile-ttl:在设置了过期时间的所有键,抛弃存活时间最短的数据。

*LinkedHashMap构造函数accessOrder=true,在get/put的时候把数据放到最后,旧数据还在前,实现LRU。

持久化机制:RDB、AOF(将命令追加到AOF文件结尾。数据有改就写、每秒写一次、让操作系统决定)。

缓存雪崩:本身就要随机地设置过期时间。尽量保证redis集群高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。事中本地ehcache缓存+hystrix限流&降级,避免Mysql崩掉。事后利用redis持久化机制恢复缓存。平时也可以设置hz参数,加大每秒调用后台任务,减少一次性雪崩。

缓存击穿:一个key失效了被大量访问。

避免缓存击穿

  • 缓存不会更新则尝试将该热点数据设置为永不过期。
  • 若缓存更新不频繁,做互斥锁,少量请求到数据库,形成缓存,后面请求都走缓存。
  • 缓存频繁或构建时间长,利用定时线程过期前重新构建和延后过期时间。

缓存穿透:增加数据校验避免错误参数访问数据库,null也缓存时间短些,但是被大量不存在的id访问过来还是会有问题可以在nginx上设置相同ip限制访问,布隆过滤器。

缓存一致:肯定不能读写串行化,一般是先更新数据库,再删除缓存(更新复杂,尤其涉及多表。写频繁就改频繁,再者也是赌你不会那么快读数据),但是在更新数据库的过程中,缓存的数据还是旧的。面对高并发之下的不一致(先删缓存再更数据),更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新执行"读取数据+更新缓存"的操作,根据唯一标识路由之后,也发送到同一个jvm内部队列中。一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行(一般就是会先执行更新数据库操作),这样的话,一个数据变更的操作,先删缓存,然后再去更新数据库,但是还没完成更新,如果一个读请求过来,没读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成,最好不重复放到队列中。

先删除缓存、再更新数据库方案

  1. 采用上面说的内部队列。
  2. 分布式锁,写请求进来时,获取分布式锁,进行删除缓存和更新数据库操作。读请求起来,判断是否存在缓存,不存在缓存获取分布式锁,不成功等待判断缓存存在,成功就进入更新缓存,最后解锁。

redis分布式锁

  1. 加锁:lua脚本,加锁的key,传入设置默认生存时间30s和加锁的客户端id,存在的时候不加锁。一般用hset key uid:1,用的hash结果,存的是1应该表示第一次加锁。
  2. 锁互斥:第一步判断key是否存在,已经存在的话,判断uid是否和自己的一样,不一样证明不是(一样的话就可以重入),同时返回锁剩余的生存时间,以此来决定是否需要while循环不停地尝试加锁。
  3. watch dog自动延期机制:场景是30s时间到了还想拥有锁,加锁成功的时候,就会启动一个看门狗,它是一个后台线程,每隔10s检查一下,如果还持有锁,那么就会不断延长锁的生存时间。
  4. 释放锁:重入锁次数减一,为0了就del。
  5. 缺点:主节点宕机的时候,可能导致多个客户端同时完成加锁。
  6. 其他:在单机/主从故障时有问题,于是采用redlock算法(过半redis主节点加锁成功才成功)。

一亿个key,查出10w个以某前缀开头的:keys命令不可以会阻塞,用scan无阻塞但数据有一定的重复。

与memcached区别:redis支持复杂的数据结构,memcached只是String;redis原生支持集群模式,memcached需要依靠客户端实现集群发片写入数据。redis是单核,小数据性能更高,memcached储存100k以上的数据,性能更高。

线程模型:redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,它采用IO多路复用机制同时监听多个socket,将产生事件的socket压人内存队列中,事件分派器根据socket上的事件类型来选择对应的事件处理器进行处理。文件事件处理器的结构包括4个部分:多个socket、IO多路复用程序、文件事件分派器、事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。

效率高原因:纯内存操作,非阻塞IO多路复用,C语言实现,单线程避免多线程繁杂的上下文切换和竞争。

主从结构、哨兵模式、集群模式(一致性hash,每个节点保存一份元数据)。

单机redis能过承载QPS大概就在上万到几万不等。

RDB

  • 一个时刻生成一个redis数据,冷备份,可以存到本地或CDN上。
  • fork子进程去执行,对主程影响较小。
  • 恢复上更快。
  • 一般会丢失5分钟数据。
  • fork子进程如果遇上数据文件特别多,会导致对客户端提供服务暂停。

AOF

  • 一般一秒执行一次fsync,最多丢失1秒钟数据。
  • AOF日志文件以append-only模式写入,没有任何磁盘寻址的开销,写入性能高文件不容易损坏,易修复。
  • AOF过大会重写,对指令进行压缩,创建一份恢复数据最小日志。旧日志还会写,后面交换。
  • 做紧急修复,flushall清空了所有数据,把AOF中flushall命令删掉再恢复。
  • AOF数据大小给RDB大。
  • AOF给RDB性能低,因为一秒一次fsync,但仍然很性能较高的。
  • 为了避免错漏,AOF重做的数据是基于当时内存中的数据进行指令的重新构建。

Cache Aside Pattern:先读缓存,再读数据库;更新时更新数据库,再删缓存。

为什么不更新缓存:跨表字段复杂,是否每次更新都得更新缓存,读未必频繁。

但是会遇到后删缓存失败,办法是先删缓存,再更新数据库,这样就算数据库更新失败了,缓存是空的,再读取的时候,依旧是旧数据,再更新到缓存中。访问高发的时候会出现缓存不一致(数据库还没更新完缓存又生成了),办法:更新数据时,根据数据的唯一标识,发送到相应JVM内部队列(重复可去重),读取数据不在缓存中,那么将重新执行“读取数据+更新缓存”的操作, 根据唯一标识路由后,也发送到同一个JVM内部队列中。一个对列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条执行。(注意超时处理、内存积压)。

CAS问题:对同一个key多次写,但是顺序错了,造成最后数据有问题。一来分布式锁,写的时候只能一个写,二来数据库数据加时间戳,时间戳没有更晚就不覆盖缓存。

缓存与数据库不一致怎么办:先删缓存再写库则会造成从库数据还没完成,别的线程读到旧数据,解决办法是当从库有数据更新之后,把缓存删除。

主从数据库不一致如何解决:忽略、强制读主库、选择性读主库(缓存必须都主库的key,过期时间大概为主从同步时间,当缓存有这个数据,直接读取主库,不然从库读取)。

缓存一致性问题解决方案

  1. 设置key过期时间,数据库更新时,redis缓存不删除。缺点是缓存数据不够及时。
  2. 设置key过期时间,数据库更新时,redis缓存删除。缺点缓存删除失败可能。
  3. 数据库更新,写消息队列,异步刷新缓存。缺点是延迟和消息顺序性。
  4. 监听binlog日志。将redis作为从库来做。缺点技术成本高。

修改配置不重启redis生效:config set命令。

Java
1
https://gitee.com/yueyue_heart/javaLearn.git
git@gitee.com:yueyue_heart/javaLearn.git
yueyue_heart
javaLearn
java面试迷你版
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891