概述
缓存在计算机中是通过读取磁盘的数据放到内存中,然后进入cpu进行操作。这种机制可以在数据库查询等操作中,通过将一些热点数据放入缓存中,每次操作先判断该数据是否存在于缓存当中,减少对原始数据的访问,从而优化系统性能。
现在有的缓存形式一般分为本地缓存和分布式缓存。
在java单机缓存组件常见的有ehcache,guava cache,caffeine,在java spring5之后默认采用高性能的caffeine组件。
java ConcurrentHashMap缓存机制
java 中的锁
先补充一下多线程中锁的机制,借用其它文章来填充这一块多线程(4)什么是锁?锁机制,死锁等说明_简述线程锁和和死锁-CSDN博客
具体实现
锁
在java7之前,CocurrentHashMap使用分段锁来实现线程安全,它通过将整个Hash表分成不同的段,进行操作时只需获得操作元素对应段的锁,虽然避免了全局锁,但是锁存在一定的争用。
在java8之后,CocurrentHashMap采用更细粒度的锁,每一个哈希桶都可以被独立的锁定,当多个线程访问不同桶时可以并行执行,相比分段锁有优化。
Compare-And-Swap操作
该操作有三个参数:地址,预期值,新值
其具体过程是先找到地址所指的值,并将该值与预期值对比,只有相同的时候将该地址的值修改为新值。
而这种操作在原始值与预期值不相同的时候,该操作不会执行,只可以重试。这就保证了多个线程同时修改一个地址的值的时候,最终结果也是一致的。
当然这种机制也有一定的缺点,在高竞争环境下可能会存在活锁的情况
Node->TreeNode
在数据量过大的时候,java8之后将java7的链表节点改为了红黑树节点,减少了时间复杂度,并且每个TreeNode都运用了CAS操作来保证线程安全。
Caffeine组件
这个贴文章了java - 性能利器Caffeine缓存全面指南 - 宋小黑 - SegmentFault 思否
redis在java中
在Java中操作Redis(详细-->从环境配置到代码实现)_自己实现redisjava-CSDN博客
(合着windows有redis啊)
缓存一致性
此部分借鉴b站上的一个视频,用redis举例(实现最终一致性)
视频来源,讲的还不错:
面试官:项目中怎样保证redis的缓存和数据库数据一致性?java高频面试八股文,建议收藏!_哔哩哔哩_bilibili
redis常用场景
(终于知道再看redis命令的时候过期时间是拿来干嘛的了)
而只是访问不修改的话是不会存在缓存一致性的问题的,而只有读跟写同时并发的时候才会出现缓存一致性的问题
而实际上就是修改的时候先动缓存还是先动数据库的问题
先修改缓存再更新数据库
这种方式面临着一个问题:对于缓存中的数据是删除还是修改。而一般更推荐删除,删除了之后下一次查询直接访问数据库(由于缓存中没有该数据)然后再将数据库中的数据放回缓存。推荐删除的原因是修改可能会有逻辑上的操作,就会增加复杂度,而删除就会好很多
问题所在
两个客户端分别同时发起了一个读和一个改的请求,改的请求先删除了reis中的数据,然后读的请求这时跟上发现缓存没有,而读更快先到达了原始的数据库,最后得到了数据返回给了缓存,这时修改的请求姗姗来迟到了数据库修改了数据,这个时候redis中的数据和数据库中的数据就不一样了。(当然还有其它情况)
解决方法
在那次修改请求之后再删除一次缓存,那么就只会出现一次的数据不一致。这一次的不一致是使用redis所带来的高效无法避免的一次不一致(没必要为了这一次不一致去上锁),实现最终一致性。
这种操作称为双删,第二次删除需要一定的延迟——保证如果有同时性的访问操作,在访问操作中将数据库中的数据放回redis之后进行双删中的第二次删除。
先操作数据库再操作缓存
问题所在
很明显,在操作数据库未操作缓存之前,如果有其它请求进来访问,就会直接访问redis缓存,然后出现不一致性。
当然保证了最终一致性,也就不需要解决了,理由同上。
杂项
这个视频里还讲解了删除失败的情况,一种mq的高耦合方法,另一种利用阿里开发的canal去监听日志,这个我就没去研究了