redis缓存和分布式锁

缓存

缓存就是将一部分访问最频繁的数据放在易于取用的地方。

关系型数据库性能不高

  • MySQL将数据存放在硬盘上,在查询数据的时候需要对硬盘进行IO读写,如果涉及随机访问,速度就更慢了。
  • 当查询数据的时候没有命中索引,就需要对整个表遍历一遍查询,效率就更低了。
  • MySQL数据库会对sql语句进行解析优化,这也是需要消耗时间的。
  • 对于一些复杂的查询,比如说联合查询,涉及笛卡尔积操作,效率就会降低很多。

缓存更新策略

定期生成 :会将访问的数据以日志的形式记录下来,记录完后,就可以定期对日志里访问的数据频率进行统计了,将前20%高频率访问的数据认为是热点词,将这些热点词放入类似于redis这样的缓存中。

这个方法的缺点是实时性不够,在出现突发性事件的时候,可能会突然出现一些热词,没有实时更新的话,可能会对后端服务器造成较大的负担。
实时生成 :如果在redis中查到了,就直接返回,如果在redis中没查到,就去数据库中查询,将查到的结果写入redis中。

但如果一直往redis写数据,就会慢慢达到内存上限,所以redis中会涉及内存淘汰的策略,也可以配置redis的内存上限。

内存淘汰策略:FIFO(先进先出),LRU(淘汰最久未使用的),LFU(淘汰访问频率最少的),Random(随机淘汰)。

注意事项

  • 缓存预热 :缓存中的数据是可以定期生成,也可以实时生成,如果是使用定期生成,就不涉及到缓存预热的问题。如果是实时生成,缓存服务器上最开始是没有数据的,就会将所有的请求发送给MySQL,就需要考虑此问题。
    缓存预热就是用来解决此问题的,将定期生成和实时生成结合一下,通过一些统计的途径,用离线的方式找到一些热点数据导入到redis中,就可以替redis分担压力了,随着时间的推移,逐渐使用新的热点数据淘汰掉旧的热点数据。
  • 缓存穿透 :查询的某个key,在redis中没有,在MySQL数据库中也没有,就会导致查询的时候,无论查询多少次都不存在这个key,如果这种数据存在很多,且需要进行反复查询,就会对MySQL造成很大压力。
    当业务设计不合理,或者开发/运维误操作将数据删了,或者黑客攻击都可能会出现缓存穿透的问题。
    如果要解决缓存穿透的问题,可以在MySQL中查不到key的时候,依旧将这个key写入redis中,但将value设为一个非法值。
    也可以引入布隆过滤器,在每次查询的时候,先在布隆过滤器上判断key是否存在。
  • 缓存雪崩 :由于在短时间内,redis上大量的key失效,大部分请求都发送给MySQL,导致数据库压力上升,甚至宕机。
    发生缓存雪崩的原因有,redis节点之间挂了,或者短时间内设置了很多key给redis,并且过期时间设置的一样。可以加强redis监控,保证redis的可用性,尽量减少设置过期时间等方法。
  • 缓存击穿 :相当于缓存雪崩的特殊情况,针对热点的key过期的情况,导致大量的请求直接访问数据库,导致数据库宕机。
    如果想避免缓存击穿的问题,可以将热点的key设置为永不过期,或者进行必要的服务降级,关闭一些不重要的功能,例如使用数据库的时候设置分布式锁,降低访问数据库的频率和同时请求数据库的并发数。

分布式锁

在一个分布式系统中,就会涉及到多个节点访问同一个线程资源的问题,此时需要通过锁来做互斥控制,避免出现类似于线程安全的问题。但c++和java中的锁只针对一个进程,分布式系统中在不同的机器上存在不同的进程,所以就需要引入分布式锁。

分布式锁也就是一个/一组服务器程序,为其他的服务器程序提供加锁的服务,redis是一种典型实现分布式锁的方案,但不是唯一一种,业内也可以使用MySQL等组件实现分布式锁。

key-value加锁 :例如在进行买票的时候,买票服务器进行买票操作的时候,就会往redis上设置一个特殊的key-value,完成上述买票操作以后,再将这个key-value删除掉,当其他买票服务器需要进行买票操作的时候,也会往redis上设置一个key-value,但此时会发现key-value已经存在了,就认为加锁失败了(setnx和del搭配使用)。

  • 过期时间:如果在加锁处理完对应的逻辑以后,要解锁时程序崩溃了,可以使用try-catch-finally来解决,但如果在解锁的时候机器掉电了,就可以引入过期时间来解决此问题,当没有正常释放锁的时候,这个锁最多保存1000ms(或者其他时间)就会自动释放。
  • 校验ID:存在一个服务器加锁后,另外一个服务器马上解锁的情况,所以需要给每个服务器添加一个身份标识,在进行加锁的时候,key表示对xx资源加锁,value存储的是服务器编号,在解锁的时候需要判断当前解锁的服务器编号和value一样不一样,这样就可以避免误解锁的情况。
  • lua脚本 :验证-解锁操作不是原子的,一个服务器内部也可能是多线程的,所以就存在同一个服务器,两个线程都在进行验证-解锁的操作的情况。
    这里就存在一个问题,当引入新的服务器2时,线程A已经del进行解锁操作了,而线程B也校验完ID即将进行del操作了,此时锁已经不存在了,服务器2进行加锁操作是可以成功的,而加完锁后,线程B马上执行了del操作将服务器2的锁释放掉了。
    使用redis的事务可以解决上述的问题,但实际应用中更多使用的是lua脚本,r因为edis执行lua脚本的过程是原子的。
  • 过期时间的续约问题 :过期时间设置的短,可能业务逻辑还未执行完,就释放锁了,如果过期时间设置的长,可能导致释放锁不及时的问题。
    所以最好的方式是动态续约,初始情况下设置一个过期时间,当还剩下一点时间的时候,如果任务还没执行完,就把过期时间再续上1s,如果服务器中途崩溃了,没有人负责续约,这个锁也会在短时间内自动释放。
    redlock算法 :进行加锁,就是将key设置到主节点上,可能会出现当主节点刚收到设置的key,但没来得及同步给从节点时就挂了的情况。
    所以采用redlock算法,一个redis集群中存在多个主节点,此处加锁,就是按照一定顺序,针对这些节点进行加锁操作,如果遇到一个节点挂了就直接跳过,给下一个节点加锁即可,当加锁成功的节点超过半数,就视为加锁成功,就可以解决上述问题。
相关推荐
万象.2 小时前
redis集群算法,搭建,故障处理及扩容
redis·算法·哈希算法
白太岁2 小时前
Redis:(2) hiredis 使用、C++ 封装与连接池
c语言·c++·redis·缓存
听麟2 小时前
HarmonyOS 6.0+ 跨端会议助手APP开发实战:多设备接续与智能纪要全流程落地
分布式·深度学习·华为·区块链·wpf·harmonyos
专注VB编程开发20年7 小时前
c# vb.net Redis 左侧添加,右侧添加(添加到头部,添加到尾部)
redis·c#·.net
tod11310 小时前
Redis 分片与自动化部署:从哈希算法到生产级集群落地
redis·自动化·哈希算法
java1234_小锋10 小时前
Java高频面试题:什么是Redis哨兵机制?
java·redis·面试
专注VB编程开发20年12 小时前
c#,vb.net Redis vs ODBC/ADO 查库的速度差距,写入json数据和字典数据
redis·c#·.net
Tadas-Gao12 小时前
微服务注册中心选型深度分析:Eureka、Nacos与新一代替代方案
java·分布式·微服务·云原生·eureka·架构·系统架构
百锦再12 小时前
HashMap、Hashtable、TreeMap异同深度详解
jvm·spring boot·struts·spring cloud·缓存·kafka·tomcat