高并发之缓存

本文详细探讨了缓存在高并发系统中的重要性,介绍了Guava Cache、Memcache和Redis的特性、工作原理及其在实际应用中的选择。文章还分析了缓存命中率的影响因素,如业务场景、缓存设计和容量,并讨论了缓存分类和应用场景。同时,针对高并发场景下缓存可能遇到的问题,如一致性、并发问题、穿透问题、雪崩现象和无底洞现象,提出了相应的解决方案。最后,分享了Redis在股票分时K线图计算中的实践经验,展示了如何利用缓存优化性能。
摘要由CSDN通过智能技术生成


在这里插入图片描述
一般来说,现在的互联网应用网站或者APP,它的整体流程可以用我们这个图里展示的来表示,用户请求开始,从这个界面是最里面的浏览器和APP,到网络转发,再到应用服务,最后到存储,这纯属可能是数据库文件系统,然后再返回到界面呈现内容。

随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时,我们的应用服务器和数据库服务器所做的计算也越来越多,但是,往往我们的应用服务器的资源是有限的,而且技术变革是缓慢的,所以每秒能接收请求次数也是有限的,或者说文件的读写也是有限的。

如何能有效利用有限的资源来提供尽可能大的吞吐量呢?一个有效的办法就是引入缓存,打破图中的标准的流程,每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少他们的计算量,来有效提升响应速度,让有限的资源服务更多的用户,像我们这个图里展示的缓存的使用,它其实可以出现在1到4的各个环节中。

缓存的特征

命中率:命中数/(命中数+没有命中数)
首先是命中,命中的还可以简单的理解为直接通过缓存获得到需要的数据,有了命中就有不命中,无法通过缓存的获取想要的数据,需要再次查询数据库,或者执行其他操作,原因可能是缓存中根本不存在,或者缓存已经过期了。

通常 命中率=命中数/(命中数+没有命中数) 来表达,能力越高,表示我们使用缓存的收益越高,应用的性能越好,这时候响应的时间会越短,吞吐量越来越高,抗并发的能力也越强,由此可见在高并发的互联网系统中,命中率是至关重要的一个指标。

最大元素(空间)
它代表的是缓存中可以存放的最大元素的数量,一旦缓存数量超过这个值,或则所占的空间,超过了最大支持的空间,就会促发缓存清空策略,根据不同的场景合理的设置最大元素值,往往可以一定程度上提高缓存的命中率,从而更有效的使用缓存。

像我们刚才所描述的,缓存的存储空间是有限制的,当缓存空间满时,如何保证在稳定服务的同时有效的提高命中率呢?这就有缓存的清空策略来处理,适合自身数据特征的清空策略,能有效的提高命中率。常见的清空策略为下面所示

清空策略:FIFO,LFU,LRU,过期时间,随机等

  • FIFO先进先出策略:是指最先进入缓存的数据,在缓存空间不够的情况下,或者超出最大源头限制的时候,会优先被清除掉,以腾出新的空间来接受新的数据,这个策略算法主要是比较缓存元素的创建时间,在数据实时性要求严格下,可以选择该类策略,优先保障最新数据可用。
  • LFU:是指无论是否过期,根据元素的被使用次数来判断,清除使用次数最少的元素来释放空间,这个策略的算法主要比较的元素的命中次数,在保证高频率场景下,可以选择这里策略。
  • LRU: 是指无论是否过期,根据元素最后一次被使用的时间戳,清除最原始用时间戳的元素释放空间,主要比较元素的最近一次被get使用时间,在热点数据场景下优先保证热点数据的有效性
  • 除此之外呢,还有一些简单的策略,比如根据过期时间来判断,清理过期时间最长的元素,还可以根据过期时间判断清理最近要过期的元素,以及随机清理等等。

缓存命中率影响因素

  • 业务场景和业务需求
    缓存适合读多写少的业务场景,反之使用缓存的意义并不大,命中率还会很低,业务需求的也决定了对实时性的要求,直接影响到缓存的过期时间和更新策略,实时性要求越低,就越适合缓存,在相同key和相同请求数的情况下,缓存时间越长命中率就会越高,我们目前遇到的互联网应用,大多数的业务场景下都是很适合使用缓存的
  • 缓存的设计(粒度和策略)
    通常情况下呢,缓存的力度越小,命中率就会越高,当换成单个对象的时候,比如单个用户信息,只有当该对象的对应的数据发生变化时后,我们才需要更新缓存或者移除缓存,而当缓存一个集合的时候,我们要获得所有用户数据,其中任何一个对象对应的数据发生变化时,我们都需要更新或移除缓存,还有另一个情况,假设其他地方也需要获取该对象对应的数据时,比如说其他地方也需要获取单个用户信息,如果缓存的是单个对象,那么就可以直接命中缓存,否则的话就无法直接命中。
  • 缓存容量和基础设施
    缓存容量有限就会引起缓存失效和淘汰,目前多个缓存中间件多采用LRU算法。技术选型也很重要,建议采用分布式缓存。

缓存分类和应用场景

  • 本地缓存:编程实现(成员变量、局部变量、静态变量)、Guava Cache
    最大的优点是应用进程的cache是在同一个进程中内部请求缓存非常的快速,没有过多的网络开销。缺点就是各个应用要单独维护自己的缓存,无法共享。在单应用中使用较为好。
  • 分布式缓存: Memcache, Redis
    最大的优点是自身是一个独立的应用,与本地应用是隔离的,多个应用可以共享。

Guava Cache

在这里插入图片描述
适用性

缓存在很多情况下非常实用。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所添加的元素,直到显式的移除;Guava Cache为了限制内存的占用,通常都是设定为自动回收元素。在某些场景下,尽管LoadingCahe不回收元素,但是它还是很有用的,因为它会自动加载缓存。

Guava Cache适用场景:

  • 你愿意花一些记忆来提高速度。You are willing to spend some memory to improve speed.
  • 您希望Key有时会不止一次被查询。You expect that keys will sometimes get queried more than once.
  • 你的缓存不需要存储更多的数据比什么都适合在。(Guava缓存是本地应用程序的一次运行)。Your cache will not need to store more data than what would fit inRAM. (Guava caches are local to a single run of your application.
  • 它们不将数据存储在文件中,也不存储在外部服务器上。如果这样做不适合您的需要,考虑一个工具像memcached。

Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说Guava cache 是本地缓存的不二之选,简单易用,性能好。

代码演示

package com.mmall.concurrency.example.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class GuavaCacheExample1 {
   

    public static void main(String[] args) {
   

        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .maximumSize(10) // 最多存放10个数据
                .expireAfterWrite(10, TimeUnit.SECONDS) // 缓存10秒
                .recordStats() // 开启记录状态数据功能
                .build(new CacheLoader<String, Integer>() {
   
                    @Override
                    public Integer load(String key) throws Exception {
   
                        return -1;
                    }
                });

        log.info("{}", cache.getIfPresent("key1")); // null
        cache.put("key1", 1);
        log.info("{}", cache.getIfPresent("key1")); // 1
        cache.invalidate("key1");//丢弃某个缓存值
        log.info("{}", cache.getIfPresent("key1")); // null

        try {
   
            log.info("{}", cache.get("key2")); // -1
            cache.put("key2", 2);
            log.
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值