关于后端:高并发系统设计之缓存

2次阅读

共计 4293 个字符,预计需要花费 11 分钟才能阅读完成。

本文已收录至 Github,举荐浏览 👉 Java 随想录

微信公众号:Java 随想录

这篇文章来讲讲缓存。缓存是优化网站性能的第一伎俩,目标是让访问速度更快。

说起缓存,第一反馈可能想到的就是 Redis。目前比拟好的计划是应用多级缓存,如 CPU→Ll/L2/L3→内存→磁盘就是一个典型的例子,CPU 须要数据时先从 L1 读取,如果没有找到,则查找 L2/L3 读取,如果没有,则到内存中查找,如果还没有,会到磁盘中查找。

堆缓存

应用 Java 堆内存来存储缓存对象。应用堆缓存的益处是没有序列化 / 反序列化,是最快的缓存。毛病也很显著,当缓存的数据量很大时,GC(垃圾回收)暂停工夫会变长,存储容量受限于堆空间大小。个别通过软援用 / 弱援用来存储缓存对象,即当堆内存不足时,能够强制回收这部分内存开释堆内存空间。个别应用堆缓存存储较热的数据。能够应用 Caffeine Cache 实现。

集成 Caffeine Cache 详情见我另外一片文章:本地缓存无冕之王 Caffeine Cache

Nginx 代理缓存

Nginx 的 Proxy Cache 能够实现代理缓存,特地须要阐明的是,Proxy Cache 机制依赖于 Proxy Buffer 机制,只有在 Proxy Buffer 机制开启的状况下 Proxy Cache 的配置才发挥作用,Proxy Buffer 默认是开启的,缓存内容将寄存在 tmpfs(内存文件系统)以晋升性能。

Proxy Buffer 相干参数:

proxy_buffering  on;
该参数设置是否开启 proxy 的 buffer 性能,参数的值为 on 或者 off,默认是 on。proxy_buffer_size  4k;
该参数用来设置一个非凡的 buffer 大小的。从被代理服务器上获取到的第一局部响应数据内容到代理服务器上,通常是 header,就存到了这个 buffer 中。如果该参数设置太小,会呈现 502 错误码,这是因为这部分 buffer 不够存储 header 信息。倡议设置为 4k。proxy_buffers  8  4k;
这个参数设置存储被代理服务器上的数据所占用的 buffer 的个数和每个 buffer 的大小。所有 buffer 的大小为这两个数字的乘积。proxy_busy_buffer_size 16k;
在所有的 buffer 里,咱们须要规定一部分 buffer 把本人存的数据传给服务器,这部分 buffer 就叫做 busy_buffer。proxy_busy_buffer_size 参数用来设置处于 busy 状态的 buffer 有多大。proxy_temp_path
语法:proxy_temp_path  path [level1 level2 level3]
定义 proxy 的临时文件存在目录以及目录的层级。

proxy_buffering 与 proxy_cache 的区别:

缓冲(buffer)

  • Nginx 将上游服务器的响应报文保留几秒钟,等整个接管之后,再发送给客户端。
  • 能够尽早与上游服务器断开连接,缩小其负载。然而会减少客户端期待响应的工夫。
  • 如果不启用缓冲,则 Nginx 收到上游服务器的一部分响应就会立刻发送给客户端,通信提早低。

缓存(cache)

  • Nginx 将上游服务器的响应报文保留几分钟,当客户端再次申请同一个响应报文时就间接回复,不用申请上游服务器。
  • 能够防止反复向上游服务器申请一些固定不变的响应报文,缩小上游服务器的负载,缩小客户端期待响应的工夫。

Proxy Cache 相干的参数配置:

  • proxy_cache_path:Nginx 应用该参数指定缓存地位,有两个必填参数,第一个参数为缓存目录。第二个参数 keys_zone 指定缓存名称和占用内存空间的大小。
  • proxy_cache:该参数为之前指定的缓存名称。
  • proxy_cache_key:该指令用来设置 web 缓存的 Key 值。
  • proxy_cache_min_uses:指定申请至多被发送了多少次以上时才缓存,能够避免低频申请被缓存。
  • proxy_cache_methods:指定哪些办法的申请被缓存。
  • proxy_cache_valid:该指令用于对于不同返回状态码的 URL 设置不同的缓存工夫。
  • proxy_cache_bypass:指定 Nginx 应用缓存的条件。

如下示例:

http {

    proxy_cache_path /data/nginx/cache keys_zone=one:10m max_size=10g;
    proxy_cache_methods GET HEAD POST PUT;
    proxy_cache_min_uses 1;
    proxy_cache_valid 200 302 10m;
    proxy_cache_key "$host:$server_port$uri$is_args$args";

    upstream www.baidu.com {
        server 127.0.0.1:8880;
        server 127.0.0.1:8881;
        server 127.0.0.1:8882;
    }
    server {
        listen 80 ;
        proxy_cache one ;
        server_name www.baidu.com;
        location / {
            proxy_pass http://www.baidu.com;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_cache_bypass $cookie_nocache $arg_nocache $arg_comment ;
        }
    }

}

多级缓存

Nginx 缓存→分布式 Redis 缓存(能够应用 Lua 脚本间接在 Nginx 里读取 Redis)→堆内存

  1. 接入 Nginx 将申请负载平衡到利用 Nginx,此处罕用的负载平衡算法是轮询或者一致性哈希。轮询能够使服务器的申请更加平衡,而一致性哈希能够晋升利用 Nginx 的缓存命中率。
  2. 利用 Nginx 读取本地缓存(本地缓存能够应用 Lua Shared Dict,Nginx Proxy Cache(磁盘 / 内存)、Local Redis 实现)。如果本地缓存命中,则间接返回,应用利用 Nginx 本地缓存能够晋升整体的吞吐量,升高后端压力,尤其应答热点问题十分无效。
  3. 如果 Nginx 本地缓存没命中,则会读取相应的分布式缓存(如 Redis 缓存,还能够思考应用主从架构来晋升性能和吞吐量),如果分布式缓存命中,则间接返回相应数据(并回写到 Nginx 本地缓存)。
  4. 如果分布式缓存也没有命中,则会回源到 Tomcat 集群,在回源到 Tomcat 集群时,也能够应用轮询和一致性哈希作为负载平衡算法。
  5. 在 Tomcat 利用中,首先读取本地堆缓存。如果有,则间接返回(并会写到主 Redis 集群)。
  6. 作为可选局部,如果步骤 4 没有命中,则能够再尝试一次读主 Redis 集群操作,目标是避免当从集群有问题时的流量冲击。
  7. 如果所有缓存都没有命中,则只能查问 DB 或相干服务获取相干数据并返回。
  8. 步骤 7 返回的数据异步写到主 Redis 集群。

整体分了三局部缓存:利用 Nginx 本地缓存、分布式缓存、Tomcat 堆缓存。每一层缓存都用来解决相干问题,如利用 Nginx 本地缓存用来解决热点缓存问题,分布式缓存用来缩小拜访回源率,Tomcat 堆缓存用于避免相干缓存生效 / 解体之后的冲击。

热点 Key 主动探测

热点数据其实能够分为:动态热点数据 和 动静热点数据

所谓“动态热点数据”,就是可能提前预测的热点数据。例如,咱们能够通过卖家报名的形式提前筛选进去,通过报名系统对这些热点商品进行打标。另外,咱们还能够通过大数据分析来提前发现热点商品,比方咱们剖析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是能够提前剖析进去的热点,剖析进去之后,咱们能够提前放入缓存中爱护起来,这在技术上是能够实现的。

所谓“动静热点数据”,就是不能被提前预测到的,零碎在运行过程中长期产生的热点。例如,卖家在抖音上做了广告,而后商品一下就火了,导致它在短时间内被大量购买。

所以如何动静预测热点数据就成了咱们缓存零碎的要害

这里我给出一个动静热点发现零碎的具体实现。

  1. 构建一个异步的零碎,它能够收集交易链路上各个环节中的中间件产品的热点 Key,如 Nginx、缓存、RPC 服务框架等这些中间件(一些中间件产品自身曾经有热点统计模块)。
  2. 建设一个热点上报和能够依照需要订阅的热点服务的下发标准,次要目标是通过交易链路上各个系统 (包含详情、购物车、交易、优惠、库存、物流等) 拜访的时间差把上游曾经发现的热点透传给上游零碎,提前做好爱护。比方,对于大促高峰期,详情零碎是最早晓得的,在对立接入层上 Nginx 模块统计的热点 URL。
  3. 将上游零碎收集的热点数据发送到热点服务台,而后上游零碎(如交易系统)就会晓得哪些商品会被频繁调用,而后做热点爱护,对于热点的统计能够很简略的对拜访的商品进行拜访计数,而后排序还有就是用通常的队列的淘汰算法如 lru 等都能够实现。

实现思路步骤如下:
1.接入 Nginx 将申请转发给利用 Nginx。
2.利用 Nginx 首先读取本地缓存。如果命中,则间接返回,不命中会读取分布式缓存、回源到 Tomcat 进行解决。
3.利用 Nginx 会将申请上报给实时热点发现零碎,如应用 UDP 间接上报申请,或者将申请写到本地 kafka,或者应用 flume 订阅本地 Nginx 日志。上报给实时热点发现零碎后,它将进行热点统计(能够思考 storm 实时计算)。
4.依据设置的阈值将热点数据推送到利用 Nginx 本地缓存。

咱们次要是依赖后面的导购页面(包含首页、搜寻页面、商品详情、购物车等)提前辨认哪些商品的访问量高,通过这些零碎中的中间件来收集热点数据,并记录到日志中。

咱们通过部署在每台机器上的 Agent 把日志汇总到聚合和剖析集群中,而后把合乎肯定规定的热点数据,通过订阅散发零碎再推送到相应的零碎中。你能够是把热点数据填充到 Cache 中,或者间接推送到应用服务器的内存中,还能够对这些数据进行拦挡,总之上游零碎能够订阅这些数据,而后依据本人的需要决定如何解决这些数据。

热点发现要做到靠近实时(3s 内实现热点数据的发现),因为只有做到靠近实时,动静发现才有意义,能力实时地对上游零碎提供爱护。如果 10s 内才发送热点就没意义了,因为 10s 内用户能够进行的操作太多了。工夫越长,不可控元素越多,热点缓存命中率越低。

京东在网上开源了热点 key 探测技术的具体实现:https://gitee.com/jd-platform…


本篇文章就到这里,感激浏览,如果本篇博客有任何谬误和倡议,欢送给我留言斧正。文章继续更新,能够关注公众号第一工夫浏览。

正文完
 0