当前位置: 首页 > news >正文

缓存-变更事件捕捉、更新策略、本地缓存和热key问题

缓存-基础知识

熟悉计算机基础的同学们都知道,服务的存储大多是多层级的,呈现金字塔类型。通常来说本机存储比通过网络通信的外部存储更快(现在也不一定了,因为网络传输速度很快,至少可以比一些过时的本地存储设备速度快)。常见的本机存储和网络设备有:

  1. 本机:从快到慢有CPU寄存器、CPU多级缓存、内存、磁盘
  2. 网络设备:从快到慢有Redis这类的基于内存的缓存,MySQL这类持久化数据库,RPC调用(如果假设RPC调用的下游也需要调用缓存和数据库等组件)等

那么使用缓存有什么好处呢?通俗地讲就是“快”,准确地说是读取快,每秒承载的访问次数多,这样服务端每秒能处理的请求就更多,服务总体的吞吐量就大。具体来说,基于内存的存储比基于磁盘的快,所以大家常常使用Redis作为MySQL的缓存;而本机的内存缓存又会比通过网络调用Redis快,因此现在一些互联网公司也在搭建本地缓存(Local Cache)来缓解Redis的压力。

另外需要看到缓存的缺点:缓存增加了数据的副本份数,所以写入的份数也增加了,写入性能必然降低,一致性管理成本必然增加。所以缓存基本都是用于读多写少,且不要求强一致性的场景。

更新事件捕获与更新缓存的策略

在常见的在线服务调用中,我们会构建缓存来避免对数据库和下游的调用,来减少数据库压力和对下游服务的压力。当相关数据改变时,可以使缓存失效或者更新缓存。那么这里引入了两个问题,第一个是如何知道缓存该被更新了,第二个是怎么选择删除缓存和更新缓存的策略。

更新事件捕获

通常有两种方式来捕获更新事件:

  1. 服务代码主动更新:用户操作涉及修改数据时,处理用户请求的服务代码会刷新缓存和数据库里的数据
  2. 订阅数据库binlog延迟更新:数据库配置binlog,并通过一些binlog同步工具将数据库变更记录发送到消息队列。服务端订阅消息队列的指定话题获取变更记录,进而更新缓存数据

1#通常被用于更新自己团队的数据,而2#通常用于更新下游服务,或者作为缓存更新失败时的兜底逻辑。毕竟下游是数据的生产方,让下游更新缓存的时候通知所有上游不太现实,这种基于订阅模式的事件捕获机制有很强的扩展性。

更新策略选择

具体策略(删除或更新)的选择通常取决于数据是否在本次处理中已经计算好了。比方说收到一条变更消息,说用户的年龄已经被更新了。与年龄相关的可能还有用户对好友的可见性、用户是否可以接收其它用户的信息等内容,它们都可能随着年龄的更改而同步变化,而这些信息目前还是未知的——除非再次请求下游。因此对于这种情况,我们会将相关的数据直接删除。由于年龄的更新频率非常低,所以我们可以认为删除相关缓存的操作对其性能的总体影响很小。

但是如果需要更新的字段比较独立,不会影响别的字段,比方说用户昵称或者头像,那直接更新对应的数据即可

本地缓存(Local Cache)

说完了更新缓存的策略,我们再来看看最近业界比较流行的Local Cache的实现。这里的local,指的是服务器的单实例,也就是每个实例都会有自己的缓存副本。它的适用条件是读请求次数远远大于写,且数据总量较小。比如对于Redis来说,性能指标的表现就是内存利用率低,但是CPU占用率很高。这对于现在的微服务架构来说比较常见,每个服务需要管理的字段不多,但是请求量巨大,比如对于社交应用来说,用户年龄相关的字段就符合这种访问模式,每次请求基本都要读取年龄字段,但是数据量却很小。

读写过程

本地缓存的读取策略很简单,不过就是在Redis之前加了个读取本地缓存的过程,如果缓存不存在就从更高层级缓存拉取。

那如何更新缓存呢?由于分布式系统的特性,每个服务器实例都有自己的内存,除非使用特别的路由策略,让同一个用户的请求只打到同一台机器上,否则更新缓存是很头疼的事情:用户的缓存可能存在于不同机器上,如何让多台服务器的缓存同时失效?因此在分布式的世界中,使用Redis这样的集中缓存比较常见,只要存储是集中式的且从计算单元中分离出来,服务就可以做成无状态的,减少数据状态的管理成本。

回忆一下我们刚刚说过的捕获更新和缓存更新方式,其实本地缓存的更新也不出其右:用binlog+消息队列同步数据库变更,所有启用本地缓存的服务器实例,都需要监听消息队列里的变更消息,并刷新(或者删除)本地缓存。唯一的不同是,如果需要刷新Redis缓存,那我们可以把刷新缓存的服务和处理用户请求的服务独立开来;但是本地缓存的缓存更新逻辑得和服务器逻辑都放在同一个实例中。这也很好理解,Redis是集中式的缓存,而本地缓存和服务器实例牢牢捆绑在一起。

本地缓存的优势

之前说过,本地缓存适用于读远远大于写,且请求量大但是缓存数据总量较小的场景。为什么呢?因为假如CPU和内存利用率都很高,即使本地缓存降低了Redis的读取次数,那也只能降低Redis的CPU占用率,而内存利用率还是很高。内存利用率和缓存数据总量有关,所以多一层缓存不会减少缓存的数据总量。根据木桶原理,即使CPU利用率降低了,由于内存的需求还在,也不能缩减实例的数量,所以并不能节约太多钱(可以降低一下CPU规格,但是一台生产环境实例的基本价格在那,降低或提高规格带来的费用变化并不大)。

那有人问了,本地缓存不是也提高了服务端的内存占用,这方面的费用不算了吗?刚刚说过,生产环境里一台实例的基础费用不低,而且增加一些内存带来的费用相比于减少Redis实例数量来说很小。而且本来本地缓存就适合数据总量小的场景,因此增加一些内存容量(比如4-8G)就能有客观的命中率(比如50%+)。

我相信还有人会问,增加了更新链路不是也带来了额外费用吗?这就要再强调一下本地缓存的适用场景:读远远大于写,比如大了两个数量级。这样缓存的更新频率足够低,缓存更新链路所需的资源也足够少。

缓存常见问题

缓存如何解决热点问题

热点问题是缓存遇到的常见问题之一,比如强如Redis也有其吞吐量上限,而Redis同一个key只能存储在一个分片、一个进程里,可能还是无法应付一些热点用户的数据访问(比如某个热点问题,某个热点主播)。《数据密集型系统》给出两个比较笼统的方案:

  1. 给热key实例更多资源:比如将其单独存储在一个实例中,或者给它分配更好的机器。但是这种方案很容易就会达到上限,因为不具备水平扩展能力。
  2. 拆key:比如某个用户(id为1234)是热点,那就将它的数据存在100个副本中,每个副本的key是id后面加上两位数。这样当看到1234这个id时,服务端会自动在后面加上两位随机数,生成形如123401,123402…123499这样的id,再到Redis里访问。这相当于把一个热key的访问压力分散为原来的1/100。这种方式明显是优化了读请求。如果是写请求比较重,那也可以做类似的事情,将写分散到不同的实例上,但读的时候就需要读取所有的100个分片。

由于1#不具备水平扩展能力,所以一些业界流行的做法都采用了类似2#的方式,只不过实现起来各有特点,比方说在Redis之前加一层缓存代理,这层代理也是一个分布式服务,一旦有热key存在,就会将热key缓存到代理服务的本地缓存里,实现了类似于2#中拆key的效果(因为代理服务也是分布式的,多个实例都可以缓存热key的内容,进而分散了Redis单实例的压力)

http://www.lryc.cn/news/625853.html

相关文章:

  • 数据迁移:如何从MySQL数据库高效迁移到Neo4j图形数据库
  • 在CentOS系统中查询已删除但仍占用磁盘空间的文件
  • Docker 快速下载Neo4j 方法记录
  • 生信分析自学攻略 | R语言数据类型和数据结构
  • PG靶机 - Pebbles
  • 使用java做出minecraft2.0版本
  • 为了提高项目成功率,项目预算如何分配
  • Datawhale工作流自动化平台n8n入门教程(一):n8n简介与平台部署
  • LeetCode算法日记 - Day 16: 连续数组、矩阵区域和
  • 免费导航规划API接口详解:调用指南与实战示例
  • 海滨浴场应急广播:守护碧海蓝天的安全防线
  • Shopee本土店账号安全运营:规避封禁风险的多维策略
  • 云存储的高效安全助手:阿里云国际站 OSS
  • 技术攻坚全链铸盾 锁定12月济南第26届食品农产品安全高峰论坛
  • https如何保证传递参数的安全
  • 学习嵌入式的第二十一天——数据结构——链表
  • 乾元通渠道商中标六盘水应急指挥能力提升项目
  • 路由器最大传输速率测试
  • 首届机器人足球运动会技术复盘:从赛场表现看智能机器人核心技术突破
  • GTSAM中实现多机器人位姿图优化(multi-robot pose graph optimization)示例
  • 用机器人实现OpenAI GPT-5视觉驱动的闲聊:OpenAIAPI Key获取并配置启动视觉项目
  • sfc_os!SfcQueueValidationRequest函数分析之sfc_os!IsFileInQueue
  • 当MySQL的int不够用了
  • 差速转向机器人研发:创新驱动的未来移动技术探索
  • 实现进度条
  • 1分钟批量生成100张,Coze扣子智能体工作流批量生成人物一致的治愈系漫画图文(IP形象可自定义)
  • 华为鸿蒙系统SSH如何通过私钥连接登录
  • 如何成功初始化一个模块
  • Infusing fine-grained visual knowledge to Vision-Language Models
  • 传输层协议——UDP和TCP