同步操作将从 papi林/java面试迷你版 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
应用场景:
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)
- noeviction:默认策略,不淘汰,如果内存已满,添加数据是报错。
- allkeys-lru:在所有键中,选取最近最少使用的数据抛弃。类似LinkedHashMap。
- allkeys-random: 在所有键中,随机抛弃。
- volatile-lru:在设置了过期时间的所有键中,选取最近最少使用的数据抛弃。
- volatile-random: 在设置了过期时间的所有键,随机抛弃。
- volatile-ttl:在设置了过期时间的所有键,抛弃存活时间最短的数据。
*LinkedHashMap构造函数accessOrder=true,在get/put的时候把数据放到最后,旧数据还在前,实现LRU。
持久化机制:RDB、AOF(将命令追加到AOF文件结尾。数据有改就写、每秒写一次、让操作系统决定)。
缓存雪崩:本身就要随机地设置过期时间。尽量保证redis集群高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。事中本地ehcache缓存+hystrix限流&降级,避免Mysql崩掉。事后利用redis持久化机制恢复缓存。平时也可以设置hz参数,加大每秒调用后台任务,减少一次性雪崩。
缓存击穿:一个key失效了被大量访问。
避免缓存击穿:
缓存穿透:增加数据校验避免错误参数访问数据库,null也缓存时间短些,但是被大量不存在的id访问过来还是会有问题可以在nginx上设置相同ip限制访问,布隆过滤器。
缓存一致:肯定不能读写串行化,一般是先更新数据库,再删除缓存(更新复杂,尤其涉及多表。写频繁就改频繁,再者也是赌你不会那么快读数据),但是在更新数据库的过程中,缓存的数据还是旧的。面对高并发之下的不一致(先删缓存再更数据),更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新执行"读取数据+更新缓存"的操作,根据唯一标识路由之后,也发送到同一个jvm内部队列中。一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行(一般就是会先执行更新数据库操作),这样的话,一个数据变更的操作,先删缓存,然后再去更新数据库,但是还没完成更新,如果一个读请求过来,没读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成,最好不重复放到队列中。
先删除缓存、再更新数据库方案:
- 采用上面说的内部队列。
- 分布式锁,写请求进来时,获取分布式锁,进行删除缓存和更新数据库操作。读请求起来,判断是否存在缓存,不存在缓存获取分布式锁,不成功等待判断缓存存在,成功就进入更新缓存,最后解锁。
redis分布式锁:
- 加锁:lua脚本,加锁的key,传入设置默认生存时间30s和加锁的客户端id,存在的时候不加锁。一般用hset key uid:1,用的hash结果,存的是1应该表示第一次加锁。
- 锁互斥:第一步判断key是否存在,已经存在的话,判断uid是否和自己的一样,不一样证明不是(一样的话就可以重入),同时返回锁剩余的生存时间,以此来决定是否需要while循环不停地尝试加锁。
- watch dog自动延期机制:场景是30s时间到了还想拥有锁,加锁成功的时候,就会启动一个看门狗,它是一个后台线程,每隔10s检查一下,如果还持有锁,那么就会不断延长锁的生存时间。
- 释放锁:重入锁次数减一,为0了就del。
- 缺点:主节点宕机的时候,可能导致多个客户端同时完成加锁。
- 其他:在单机/主从故障时有问题,于是采用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:
AOF:
Cache Aside Pattern:先读缓存,再读数据库;更新时更新数据库,再删缓存。
为什么不更新缓存:跨表字段复杂,是否每次更新都得更新缓存,读未必频繁。
但是会遇到后删缓存失败,办法是先删缓存,再更新数据库,这样就算数据库更新失败了,缓存是空的,再读取的时候,依旧是旧数据,再更新到缓存中。访问高发的时候会出现缓存不一致(数据库还没更新完缓存又生成了),办法:更新数据时,根据数据的唯一标识,发送到相应JVM内部队列(重复可去重),读取数据不在缓存中,那么将重新执行“读取数据+更新缓存”的操作, 根据唯一标识路由后,也发送到同一个JVM内部队列中。一个对列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条执行。(注意超时处理、内存积压)。
CAS问题:对同一个key多次写,但是顺序错了,造成最后数据有问题。一来分布式锁,写的时候只能一个写,二来数据库数据加时间戳,时间戳没有更晚就不覆盖缓存。
缓存与数据库不一致怎么办:先删缓存再写库则会造成从库数据还没完成,别的线程读到旧数据,解决办法是当从库有数据更新之后,把缓存删除。
主从数据库不一致如何解决:忽略、强制读主库、选择性读主库(缓存必须都主库的key,过期时间大概为主从同步时间,当缓存有这个数据,直接读取主库,不然从库读取)。
缓存一致性问题解决方案:
- 设置key过期时间,数据库更新时,redis缓存不删除。缺点是缓存数据不够及时。
- 设置key过期时间,数据库更新时,redis缓存删除。缺点缓存删除失败可能。
- 数据库更新,写消息队列,异步刷新缓存。缺点是延迟和消息顺序性。
- 监听binlog日志。将redis作为从库来做。缺点技术成本高。
修改配置不重启redis生效:config set命令。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。