栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

2021-10-20

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

2021-10-20

架构师师兄再谈缓存修炼之路

文章目录
    • 架构师师兄再谈缓存修炼之路
  • 前言
  • 一、本地缓存
  • 二、分布式缓存
    • 1. 合理控制对象,择优读取策略
    • 2.分页列表查询
  • 三、多级缓存
  • 总结


前言

架构师师兄再谈修炼之路,基础之路就是Nginx+业务逻辑层+数据库+缓存层+消息队列,这种模型几乎能适配绝大部分的业务场景。今天就来谈一谈缓存。缓存的目的就是存储数据,弥补数据库的不足,更多的也是存储热点数据,方便取用。


一、本地缓存

1 . 页面级缓存
我使用缓存的时间很早,2010年左右使用过 OSCache,当时主要用在 JSP 页面中用于实现页面级缓存。伪代码类似这样:

 
      some jsp content 

中间的那段 JSP 代码将会以 key=“foobar” 缓存在 session 中,这样其他页面就能共享这段缓存内容。在使用 JSP 这种远古技术的场景下,通过引入 OSCache 之后 ,页面的加载速度确实提升很快。但随着前后端分离以及分布式缓存的兴起,服务端的页面级缓存已经很少使用了。但是在前端领域,页面级缓存仍然很流行。
2. 对象缓存
2011年左右,开源中国的红薯哥写了很多篇关于缓存的文章。他提到:开源中国每天百万的动态请求,只用 1 台 4 Core 8G 的服务器就扛住了,得益于缓存框架 Ehcache。这让我非常神往,一个简单的框架竟能将单机性能做到如此这般,让我欲欲跃试。于是,我参考红薯哥的示例代码,在商品订单服务上第一次使用了 Ehcache。
逻辑也很简单,就是将成功或者失败状态的订单缓存起来,这样下次查询的时候,不用再查询数据库了。伪代码类似这样:

相比页面缓存,它的粒度更细、更灵活,常用来缓存很少变化的数据,比如:全局配置、状态已完结的订单等,用于提升整体的查询速度。

3 . 刷新策略

上面这样一个配置中心的结构,为了让客户端以最快的速度读取配置, 本地缓存我们使用了 Guava,guava是google的一个开源java框架,其github地址是 https://github.com/google/guava。guava工程包含了若干被Google的Java项目广泛依赖的核心库,例如:
集合 [collections]
缓存 [caching]
原生类型支持 [primitives support]
并发库 [concurrency libraries]
通用注解 [common annotations]
字符串处理 [string processing]
I/O
我们这里主要就是利用了缓存的一个核心库。那本地缓存是如何更新的呢?有两种机制:
客户端启动定时任务,从配置中心拉取数据。
当配置中心有数据变化时,主动推送给客户端。这里我并没有使用websocket,而是使用了 RocketMQ Remoting 通讯框架。

其实正常使用的规范方式是三种:
▍zookeeper watch机制
admin 在启动的时候,会将数据全量写入 zookeeper,后续数据发生变更时,会增量更新 zookeeper 的节点。与此同时,web 会监听配置信息的节点,一旦有信息变更时,会更新本地缓存。

▍websocket 机制
websocket 和 zookeeper 机制有点类似,当网关与 admin 首次建立好 websocket 连接时,admin 会推送一次全量数据,后续如果配置数据发生变更,则将增量数据通过 websocket 主动推送给 web。

▍http 长轮询机制
http请求到达服务端后,并不是马上响应,而是利用 Servlet 3.0 的异步机制响应数据。当配置发生变化时,服务端会挨个移除队列中的长轮询请求,告知是哪个 Group 的数据发生了变更,网关收到响应后,再次请求该 Group 的配置数据。
在这几种更新数据的方式里:
pull 模式必不可少
增量推送大同小异
长轮询是一个有意思的话题 , 这种模式在 RocketMQ 的消费者模型也同样被使用,接近准实时,并且可以减少服务端的压力。

本地缓存一般作为单机模型时使用最多,主要就是因为单机模型的服务器就是本机,如果使用分布式,那么本地缓存就分布在多台机器上面了。

二、分布式缓存

关于分布式缓存, memcached 和 Redis 应该是最常用的技术选型。

1. 合理控制对象,择优读取策略

首先我们思考一个问题:某些 key 缓存的 value 太大会造成什么问题,明确在配置存储较小的虚拟机下,会就很容易导致GC频繁。这个很容易理解,因为数据会被回收,如果单个数据就很大,那么整个使用下来,会导致虚拟机内存会很快被占满,从而导致频繁GC。
合适的处理策略:
1、数据格式非常精简,只返回给前端必要的数据,部分数据通过数组的方式返回。
2、使用 websocket,进入页面后推送全量数据,数据发生变化推送增量数据。
3、扩大新生代的内存。
首先我们看第一个,意思很明确,用数组返回数据而不是JSON一类的,因为JSON是键值对数据,如果字段很多的情况下,占用内存大。那么第二个,其实就是一种数据推送方式,也就是我们的读取策略。第三种就是解决表面问题,哪里出现问题,从哪里解决问题,当然了,这种方式我们并不推荐,因为这种方式只能解决表面问题,这个内容的根本是什么呢?其实就是我们没有合理地控制对象的大小,在分配策略上也没有采取更加主动的方式。为了彻底解决这个问题,必须使用了更精细化的缓存读取策略。我们把缓存拆成两个部分,第一部分是全量数据,第二部分是增量数据(数据量很小)。页面第一次请求拉取全量数据,当比分有变化的时候,通过 websocket 推送增量数据。这样每次增加的数据量是比较少的,就不会出现大量数据消亡的情况。

2.分页列表查询

列表如何缓存是我非常渴望和大家分享的技能点。「查询博客列表」的场景是经常使用的,如果是电商项目,那么也需要频繁查询商品列表。处理思路:对分页内容进行整体缓存。这种方案会按照页码和每页大小组合成一个缓存key,缓存值就是博客信息列表。假如某一个博客内容发生修改, 我们要重新加载缓存,或者删除整页的缓存。但是缓存的颗粒度比较大,如果博客更新较为频繁,则缓存很容易失效。比价适应用于数据极少发生变化的场景,比如排行榜,首页新闻资讯等。

后面设想的方案如果仅对博客进行缓存。流程大致如下:
1)先从数据库查询当前页的博客id列表,sql类似:

select id from blogs limit 0,10 

2)批量从缓存中获取博客id列表对应的缓存数据 ,并记录没有命中的博客id,若没有命中的id列表大于0,再次从数据库中查询一次,并放入缓存,sql类似:

select id from blogs where id in (noHitId1, noHitId2)

3)将没有缓存的博客对象存入缓存中
4)返回博客对象列表
理论上,要是缓存都预热的情况下,一次简单的数据库查询,一次缓存批量获取,即可返回所有的数据。另外,关于缓存批量获取,使用本地缓存:性能极高,for 循环即可。如果使用分布式缓存,我经常使用Redis,若缓存对象结构简单,使用 mget 、hmget命令;若结构复杂,可以考虑使用 pipleline,lua脚本模式。

三、多级缓存

本地缓存速度极快,但是容量有限,而且无法共享内存。分布式缓存容量可扩展,但在高并发场景下,如果所有数据都必须从远程缓存种获取,很容易导致带宽跑满,吞吐量下降。但是我们想要的是缓存离用户越近,这样缓存才更加高效。
看一下经典的两级缓存模式,同时利用了 guava 的惰性加载机制,整体架构如下图所示:

缓存读取流程如下:
1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。
2、由于步骤1已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。
3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5个最大线程,5个核心线程)去导购服务同步数据到本地缓存和 Redis 中。

但是这种设计的一个问题就是各个服务器本地缓存中的数据并非完成一致。我们需要协调一下:
1、惰性加载结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。
2、适当调大 LoadigCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数。


总结

缓存是非常重要的一个技术手段。大家其实都非常了解,但是也非常难以设计,希望工程人员不要盲目地去追求更高层次的设计方法,缓存一定是弥补数据库的不足,我们一定要结合自己的业务去思考。但是我建议自己平时要多去思考,多去设计,这个缓存就是一锤子买卖,一个优秀的缓存方案,即便缓存场景不同,使用时也是有共通性的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/340202.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号