Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

缓存系统 #301

Open
Bpazy opened this issue Sep 28, 2023 · 6 comments
Open

缓存系统 #301

Bpazy opened this issue Sep 28, 2023 · 6 comments

Comments

@Bpazy
Copy link
Owner

Bpazy commented Sep 28, 2023

几个重点:

  1. 击穿、穿透、雪崩
  2. 二级缓存
  3. 布隆过滤器

image

@Bpazy
Copy link
Owner Author

Bpazy commented Sep 29, 2023

缓存雪崩

当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。
image

造成缓存雪崩的关键:在同一时间大规模的key失效。

出现缓存雪崩的原因:

  1. 可能是 Redis 宕机。

如果 Redis 集群中的某个节点宕机,会导致该节点上的所有 key 同时失效,进而导致请求全部转向其他节点,从而引发雪崩

  1. 可能是采用了相同的过期时间。

缓存雪崩的解决方案

  1. 搭建 Redis 集群,防止 Redis 宕机导致缓存雪崩的问题,提高 Redis 的容灾性。
  2. 在原有的失效时间上加一个随机值(比如 1-5 分钟随机),避免采用相同过期时间的 key 同时失效。
  3. 提高数据库的容灾能力,可以使用分库分表,读写分离的策略。
  4. 增加兜底措施(熔断机制、服务降级、布隆过滤器),防止过多请求打到数据库。
  5. 缓存后加锁。

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 8, 2023

缓存击穿

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

名词造的挺多,和雪崩的区别是热点 key 失效导致大量请求打到数据库:
image

解决方案和雪崩类似,另外可以考虑让热点缓存不要过期

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 8, 2023

缓存穿透

当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

缓存穿透的发生一般有这两种情况:

  • 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
  • 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

应对缓存穿透的方案,常见的方案有三种。

  • 第一种方案,非法请求的限制;
  • 第二种方案,缓存空值或者默认值;
  • 第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;

第一种方案,非法请求的限制

当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。

第二种方案,缓存空值或者默认值

当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。

我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。

即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

关于布隆过滤器的知识可以参考这里: #302

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 17, 2023

What ttl means?

Time to live,缓存存在时长

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 18, 2023

容量预估、热key、大key、穿透、缓存和数据库一致性问题、建议换新key(上线是否有兼容问题)、过期时间(本地缓存)、redis抖动

@Bpazy
Copy link
Owner Author

Bpazy commented Nov 2, 2023

堆外缓存

在互联网项目中,一般以堆内缓存的使用居多,无论是Guava,Memcache,还是JDK自带的HashMap,ConcurrentHashMap等,都是在堆内内存中做数据计算操作。这样做的好处显而易见,用户完全不必在意数据的分配,溢出,回收等操作,全部交由JVM来进行处理。由于JVM提供了诸多的垃圾回收算法,可以保证在不影响甚至微影响系统的前提下,做到堆内内存接近完美的管控。君不见,小如图书管理这样的系统,大如整个电商交易平台,都在JVM的加持下,服务于几个,十几个,乃至于上亿用户,而在这些系统中,堆内缓存组件所带来的收益可是居功至伟。在自下而上的互联网架构中,堆内缓存就像把卫这宫廷入口的剑士,神圣而庄严,真可谓谁敢横刀立马,唯我堆内缓存将军。

堆内缓存的劣势

但是,事物都是有两面性的,堆内缓存在JVM的管理下,纵然无可挑剔,但是在GC过程中产生的程序小停顿和程序大停顿,则像一把利剑一样,斩断了对构造出完美高并发系统的念想。简单的以HashMap这个JDK自带的缓存组件为例,benchmark结果如下:

Benchmark                                   Mode  Cnt          Score          Error             Units
localCacheBenchmark.testlocalCacheSet      thrpt   20      85056.759 ±   126702.544  ops/s

其插入速度最快为85056.759+126702.544=211759.303ops,最慢为0,也就是每秒插入速度最快为20w,最慢为0。之所以为0,是因为HashMap中的数据在快速的增长过程中,引起了频繁的GC操作,为了给当前HashMap腾出足够的空间进行插入操作,不得不释放一些对象。频繁的GC,势必对插入速度有不小的影响,造成应用的偶尔性暂停。所以这也能解释为啥最慢的时候,ops为0了。 同时从benchmark数据,我们可以看到误差率为126702.544ops,比正常操作的85056.756要大很多,说明GC的影响,对HashMap的插入操作影响特别的大。

由于GC的存在,堆内缓存操作的ops会受到不小的影响,会造成原本小流量下10ms能够完成的内存计算,大流量下500ms还未完成。如果内存计算过于庞杂,则造成整体流程的ops吞吐量降低,也是极有可能的事儿。所以从这里可以看出,堆内缓存组件,在高并发的压力下,如果计算量巨大,尤其是写操作巨大,使其不会成为护城的利剑,反而成了性能的帮凶,何其可惧。

为了缓解在高并发,高写入操作下,堆内缓存组件造成的频繁GC问题,堆外缓存应运而生。

堆外缓存优秀项目很多,比如 https://github.com/ben-manes/caffeine, native 代码部分依赖的是由 rust 编写的项目 https://github.sheincorp.cn/moka-rs/moka

引用: https://www.cnblogs.com/scy251147/p/9634766.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant