当前位置: 首页 > news >正文

技术派-项目亮点

caffeine+redis+Spring cache缓存

  • Caffeine:本地缓存,速度极快(微秒级),适合缓存「高频访问、变更少、非敏感」的数据(如首页热门文章、字典表);
  • Redis:分布式缓存,跨服务共享,适合缓存「分布式场景、需要一致性」的数据(如用户登录态、订单信息);
  • 会有一个CacheManager也就是CaffeineCacheManager,在这里我们会创建一个caffeine实例,然后设置一些底层策略,项目里设置的是5分钟后过期,也就是写入后过期,主要是为了避免本地缓存长时间不更新导致和数据库的数据不一致。然后还设置了初始化100条缓存条目,以及最大缓存条数200条,如果超过这个200条最大缓存条数就会采用W-TinyLFU算法淘汰
// 开启缓存注解功能
@EnableCaching
@Configuration
public class CaffeineCacheConfig {/*** 配置缓存管理器,对接Caffeine*/@Beanpublic CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();// 针对热点文章的缓存规则:// 1. 最大容量100(侧边栏热点文章数量不会太多)// 2. 写入后10分钟过期(热点文章更新频率低,避免缓存长期不刷新)// 3. 访问后5分钟过期(如果文章5分钟没人看,自动失效,节省内存)Caffeine<Object, Object> caffeine = Caffeine.newBuilder().maximumSize(100) // 最大缓存条目数.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后过期时间.expireAfterAccess(5, TimeUnit.MINUTES) // 访问后过期时间.recordStats(); // 开启统计(可选,用于监控缓存命中率)cacheManager.setCaffeine(caffeine);return cacheManager;}
}
  • LRU算法:淘汰最长时间没使用的,递增采用的是双向链表和哈希表,对于双向链表表尾是最久没有用到的,然后通过哈希表记录key到链表节点的映射,以O(1)的时间找到节点,哈希表主要是为了如果某个key最新更新了,就需要移动到表头。但是缺点比如某个低频的key突然被大量访问,但是之后长期不用,只因为最近使用过久会被LRU保存会挤占空间,所以适合那些最近访问了的还会再访问的,像什么新闻,临时会话
  • LFU淘汰算法:淘汰频率最低的。核心是给每个key维护一个访问评率的计数器,但是直接计数有两个问题,计数器无限增长会消耗内存,第二个旧数据高频计数会过期,比如说一个key半年前被高频访问,但是现在不用了,但是他的计数仍很高,就会被错误的保留。所以计数有一个对数衰减的策略,访问key的时候不是简单的+1,而是对数增长,避免溢出,然后长时间没访问的key计数会按照时间戳按照一定的比例下降,适用于一些爆款商品
  • W-TinyLFU算法:兼顾时间和频率,这个缓存分为主缓存由(LFU管理)以及一个准入窗口(LRU管理)新key要先进入准入窗口,只有在窗口内被多次缓存,才能进入LFU主缓存,避免新key直接被淘汰,解决LFU的冷启动问题。淘汰的时候优先从主缓存中选择频率最低的key,如果频率相同,再按照LRU淘汰
  • @Cacheable(查缓存)查缓存→命中返回→未命中执行方法→存缓存,@CachePut(更新缓存)执行方法→更新缓存,@CacheEvict(删缓存)执行方法(前 / 后)→删除缓存
  • 灵活的过期策略:支持写入过期(expireAfterWrite)、访问过期(expireAfterAccess)、容量限制(maximumSize);

JWT+Redis双重验证+Threadlocal

  • JWT结构:Header(一般就包括一个类型jwt,以及算法名比如HS256),PayLoad(发布者,有效期,),Signature(把经过base64编码的header以及payload使用前面用过的header算法加密生成标签,主要是为了验证消息在传递过程中有没有被更改),但是payload只是经过base64编码,绝对不能存储敏感信息
  • 传统的session用户端发送一个用户id,服务器就要存储每一个用户的信息,多台服务器分布式部署的时候多台服务器之间还要同步用户信息
  • JWT是无状态,后端失去了对这个JWT的管理权限,如果用户退出登录,这个JWT其实还是存在的,所以使用redis加入黑名单,如果再有这个请求过来就拒绝返回,然后比如用户登出的时候设置过期时间就和这个JWT的过期时间一致
  • 然后又有一个问题是接口拿到用户id之后很多地方需要更详细的信息,比如名称,权限等详细信息,就会去数据库查数据,如果有多个接口都需要查数据,就会导致数据库被频繁查询,所以就使用ThreadLocal封装用户的信息

Redis和Mysql的数据一致性

不好的方案

  • 先写mysql再写redis,比如同时有A先去更新数据库10,但是更新redis的时候卡了一下,在这个过程中线程B去更新mysql为11,并且redis也更新为11,这个时候A开始更新redis,又把redis更新成旧值10
  • 先写redis再写mysql,同上面一样,如果卡在A线程更新mysql和redis之间还是最后会更改成旧值,而且如果redis重启数据丢失了数据库还是旧值
  • 先删除redis,然后再写mysql,A先删除redis值,然后更新数据库为11,但是还没更新完,这个B就已经在redis没命中去mysql把旧值比如说10读取了,然后就回写到redis里面,就算A更新完了数据库redis也还是旧值,这种概率还是比较大,因为mysql更新确实要慢一些,而B进行的一个查询却很快

好的方案

  • 缓存双删:先删除redis,再更新mysql,最后再删除redis,网上说最后延迟500ms或者1s再去把这个最后一次缓存删除掉,但是这种风险完全不可控啊,那时间太短有可能B的就旧缓存还没写完,时间太长会影响性能。然后我查了一些博客,找到一个比较好的方案就是用一个消息队列存储删除缓存的请求,比如有A,C要有删除请求旧排队一个个删除,避免同时并发删除的混乱,然后增加一个重试机制,如果删除缓存失败就重试再次删除,这样就能保证脏数据及时删除,不用依赖这个过期时间
  • 先写mysql再删redis,但是A去更新数据库为11,还没来得及删除redis,这个B就去读缓存了,读了一个旧值,然后A再去删除缓存,第二次查询的话就可以读到这个真实值了,对于一次查询不一致,没有强一致性的任务还是能容忍的,但是秒杀库存等业务肯定是不行的。而且对于第二次查询还有可能如果缓存恰好失效,然后B去数据库查询所消耗的时长比A去更新数据库并且删除缓存的时间还长,那又有可能读到旧值,但是这种概率非常非常小,高并发场景比较推荐
  • 先写mysql,然后通过Canal监听Binlog异步更新redis,但是只能保持最终一致性,如果redis还没有更新的话还是会有数据不一致的问题

分布式锁

  • 分布式系统中有CAP理论,分别对应一致性,可用性,分区容错性,用redis实现的是AP也就是高可用性和分区容错性
  • 因为redis是单线程机制,使用SETNX获取锁成功返回1,失败返回0,这个命令是天然原子性的。万一某一个线程持有锁挂掉了,锁没有被释放,所以设置一个过期时间,通常结合expireTime设置。但是这样A线程时间到期了,B线程来持有锁,A线程在finally里面把锁给释放了,这就放错锁了,所以我们要设置vaule值,在释放锁的时候要比较一下这个锁的value是否和自己一直,但是这个比较的操作是非原子性的,所以我们采取lua脚本保证这个校验是原子性的。但是这个锁存在一些缺点:第一个锁没办法续期,如果执行方法时间很长但是过期时间很短就有可能锁提前释放。第二个:因为一些手动操作设置万一忘记设置过期时间,很容易产生死锁的。第三个SETNX不支持可重入。
  • redisson实现分布式锁:
    • 第一:设置唯一标识field是UUID+线程ID(集群环境下线程ID可能重复所以拼接UUID)以及设置过期时间 。
    • 第二:看门狗机制:加锁成功之后会设置一个定时任务向redis发送一个lua脚本,这个定时任务的时间是过期时间除以3,比如30s过期时间就在10s的时候触发定时任务,然后这个脚本会检查这个锁是不是归属当前的客户端,是的话就重置过期时间为30秒,设置为1/3是为了有一定的容错空间。然后他的生命周期是锁绑定的,如果解锁或者客户端崩溃就会自动取消掉这个定时任务,避免无限续期。
    • 第三:为了避免这个等待锁的不停自旋轮询对redis造成很大压力,当线程加锁失败就不去再轮讯了,订阅一个锁释放的通道,当锁释放的时候就发布通知对等待线程进行唤醒。
    • 第四:可重入锁的实现,有些递归嵌套方法调用,那么同一个锁就有可能被一个线程多次获得,如果锁不能重入那就自己把自己阻塞形成死锁。核心的数据结构使用了redis的hash结构,key就是锁的名字,然后哈希值的field就是UUID+线程ID,value就是重入的次数,,为什么不使用字符串存储呢,因为字符串要通过get去解析获得重入数,然后校验再set就不是原子性的就要再去使用lua脚本,但是hash的单个命令inrement增加就是原子性的,操作简单安全
    • 第五:锁丢失如果redis是一主多从的话,当setNX还没写到主节点的时候主节点就挂掉了,那就会选择一个从节点转从为主,但是新节点是没有加锁,但是服务器以为加锁成功,这个新节点没有锁数据那么其他线程还是能加锁,就会导致A,B都持有同一把锁。redisson有RedLock红锁,要求有多个独立的redis节点,要给半数以上的节点加锁成功来保证锁的有效性。但是红锁要部署多个主节点,运维很复杂,多个节点之间的数据一致性
http://www.jsqmd.com/news/112700/

相关文章:

  • 2025年市面上热门的星型卸料器订做厂家哪里有卖,星型卸料器/除尘器布袋/除尘器气包/电磁脉冲阀/通风阀门星型卸料器源头厂家排行榜单 - 品牌推荐师
  • 2025年年终深圳公司搬家推荐:专业服务榜单与全方位对比评测指南 - 十大品牌推荐
  • RHEL7 8 9 lvreduce lv
  • 拖车服务口碑排行榜TOP10,选对不踩坑!广东服务好的拖车平台技术实力与市场口碑领航者 - 品牌推荐师
  • python配置基础请求头以模拟浏览器
  • 2025年大连热门的表冷器设计推荐排行榜,工业暖风机/卡式风机盘管/新风换气机/表冷器/消防排烟风机/卧式暗装风机盘管/空气幕表冷器批发排行 - 品牌推荐师
  • 2025年成都排行前列的表冷器批发多少钱,工业暖风机/吊顶式空调机组/卡式风机盘管/空气幕/表冷器/卧式暗装风机盘管表冷器厂家有哪些 - 品牌推荐师
  • 01.从Powershell角度创建一个Windows计划任务都需要用到哪些cmdlet
  • Buildah 简明教程:让镜像构建更轻量,告别 Docker 依赖
  • AI Agent详解
  • 2025年年终北京搬家公司推荐:实力排行榜单与全方位服务对比评测 - 十大品牌推荐
  • ​人才为基,文化为魂:北京高瓴资投资管理有限公司的组织生命力之源 - 博客万
  • 2025年深圳靠谱的婚姻律所、口碑不错的婚姻律所排名推荐 - 工业推荐榜
  • 2025年年终济南公司搬家推荐:实力排行解析及关键维度对比 - 十大品牌推荐
  • 2025年最新盘点:国内领先的高温塑料回收企业TOP榜,高温塑料回收企业关键技术和产品信息全方位测评 - 品牌推荐师
  • 2025年膜盒压力表制造企业权威推荐榜单:数字压力表/隔膜压力表/防爆压力表源头厂家精选 - 品牌推荐官
  • 升级curl版本,及升级后引起的动态库链接不正常问题的解决
  • 回溯法
  • 【精选留言】公众号评论点赞自助业务是真的吗?文章评论点赞100条以上怎么弄? - 速递信息
  • 中医健康网网站声明 - 资讯焦点
  • 2025年年终上海搬家公司推荐:专业对比分析与优选榜单发布 - 十大品牌推荐
  • 2025年年终济南公司搬家推荐:权威榜单TOP5与全方位对比分析 - 十大品牌推荐
  • 国标GB28181算法算力平台EasyGBS构建森林防火智能监控系统
  • 国标GB28181算法算力平台EasyGBS构建森林防火智能监控系统
  • 2025年年终深圳公司搬家推荐:专业服务商榜单与多维度对比评测指南 - 十大品牌推荐
  • 2025年年终上海家庭搬家公司推荐:服务榜单深度解析与全方位对比评测 - 十大品牌推荐
  • 2025年十大护眼灯品牌口碑排名:三雄极光护眼灯亮度如何、与飞利浦对比哪家强? - mypinpai
  • 告别开发困局!用低代码破解中小企业68%的转型瓶颈
  • 2025年珠海管道疏通联系方式汇总:全市专业服务机构官方联系方式与高效服务指引 - 十大品牌推荐
  • 2025年靠谱的继承律师咨询推荐,权威继承律师事务所全解析 - 工业推荐榜