[原创]分布式系统之缓存的微观应用经验谈(四) 【交互场景篇】

分布式系统之缓存的微观应用经验谈(四) 【交互场景篇】

前言

  近几个月一直在忙些琐事,几乎年后都没怎么闲过。忙忙碌碌中就进入了2018年的秋天了,不得不感叹时间总是如白驹过隙,也不知道收获了什么和失去了什么。最近稍微休息,买了两本与技术无关的书,其一是 Yann Martel 写的《The High Mountains of Portugal》(葡萄牙的高山),发现阅读此书是需要一些耐心的,对人生暗喻很深,也有足够的留白,有兴趣的朋友可以细品下。好了,下面回归正题,尝试写写工作中缓存技术相关的一些实战经验和思考。

正文

  在分布式Web程序设计中,解决高并发以及内部解耦的关键技术离不开缓存和队列,而缓存角色类似计算机硬件中CPU的各级缓存。如今的业务规模稍大的互联网项目,即使在最初beta版的开发上,都会进行预留设计。但是在诸多应用场景里,也带来了某些高成本的技术问题,需要细致权衡。本系列主要围绕分布式系统中服务端缓存相关技术,也会结合朋友间的探讨提及自己的思考细节。文中若有不妥之处,恳请指正。

  为了方便独立成文,原谅在内容排版上的一点点个人强迫症。

  第四篇打算作为系列最后一篇,这里尝试谈谈缓存的一些并发交互场景,包括与数据库(特指 RDBMS)交互,和一些独立的高并发场景相关补充处理方案(若涉及具体应用同样将主要以Redis举例)。

  另见:分布式系统之缓存的微观应用经验谈(三)(数据分片和集群篇)
    (https://yq.aliyun.com/u/autumnbing)
    (https://www.cnblogs.com/bsfz/)

  一、简单谈下缓存和数据库的交互流程

    为了便于后面的相关讨论,这里约定文中的数据库(Database)均指传统的 RDBMS,使用DB标识,同时需区别于缓存(Cache)里的DB划分空间。

    我在早前一篇缓存设计细节的文章里,有阐述关于 Cache 自身 CURD 时的一些具体细节,而这里将结合DB,就 DB 和 Cache 之间的并行 CURD 操作进行一些讨论。当然,这里面在交互层面上是一定会涉及到分布式事务(Distributed Transaction)相关的一致性话题,但为了避免表述出现模糊和不必要的边界放大,这里我尽可能剥离开来,专注在基于 Cache 的处理上。

    预先抽象这样一个基础场景:DB中存在一张资金关联表(FT),这里 FT 里存储的都是热点条目(属于极高频访问数据),在系统设计时,FT里的数据将与对应的 Cache 服务 C1 进行关联存储(这里仅指一级缓存),以达到提升一定的并发查询性能。

    1.1 向 FT 中新增(Create)一条数据

      通过 SQL 向 FT中插入一条数据:如果插入失败,则不需要对 C1有任何操作;如果插入成功,则此时需要判断,考虑是否在 C1中同步插入。

      这种情景一般比较简单,如果没有特别的情况,此刻不需对 C1 做主动插入,而是后续被动插入(后面会提到)。但是如果插入 FT 中的数据往后操作只有删除这个动作,并且 FT的数据经常被批量操作,那么个人建议同步执行对 C1的插入操作。

      (PS:这里也顺便申明下,如果需要往C1插入,但插入失败,请根据业务场景加入重试机制,后面对Cache的操作均包含这个潜在的动作。至于重试处理失败的情况,如往C1插入一条数据,个人建议是不再过度处理,最终默认是整体操作成功,并进行对应状态返回。这里注意不要与分布式事务的一致性进行混合类比,后面不再赘述。)

    1.2 准备更新(Update)一条数据

      当需要更新 FT 中的一条数据时,意味着之前 C1 中的数据已经无效,而在一个高并发环境中这里无法做到统一的直接更新 C1。首先就需要考虑的是 C1 的数据是主动更新还是被动更新,主动更新即更新完 FT后,同时将数据覆盖进 C1,而被动更新指的是更新完 FT 后,立即淘汰 C1 中的数据,并等待下次查询时重新写入C1。

      只要上述请求动作出现了任何并发,比如两个相同动作,动作1和动作2同时发生请求,那么会出现一个不一致的问题:动作1先操作 FT,动作2后操作 FT,然后动作2先操作了C1,动作1后操作了C1。

      这样存在不止一个线程并发的更新 FT 数据时,无法确认更新 FT 的顺序和最终更新 C1 的顺序是否保持一致,结果是一定会出现大量 FT 和 C1 中数据出现幻读,而这个在存在主从Cache的情况下这种概率会大大提升(可参见上一章主从复制的部分)。推荐的方式是,如果不考虑Cache 多次需要重写的损耗,在没有其他特殊要求下,可以直接淘汰 C1 中的数据,也额外照顾到了Cache在合适的时候完全命中(Hit)。

      其实到这里还没结束,当决定是淘汰 C1 的数据,那么就要选择一个淘汰时机:一种是先更新 FT,然后对C1 执行淘汰;一种则是,先对 C1 执行淘汰,然后才更新FT。

      虽然两种方式都有合适的场景,但这里需要权衡一种概率性问题:当对C1执行淘汰时,又并发了一个对C1的查询操作,此时,C1会从DB拉取数据重新写入,那么C1中即为脏数据,当并发越大,存在数据一直“脏”下去的概率更大。所以,这里更推荐的做法是选择前者。

      (注意,这里还有一些去讨论的细节并不打算在此话题延伸,比如关于 C1和FT之间的原子性问题,是否可以采用二阶段/三阶段提交等模拟事务方式和对业务造成的影响。)

    1.3 开始读取(Read)一条数据

      这里就没有太多特别,毕竟应用Cache 的目的就已经说明了读取数据时,只需要遵循“先读Cache再读DB”。即先从C1里拿取数据,如果C1里不存在该数据,则从FT中搜索,搜索完成如果依然不存在该数据,则直接返回Empty状态。如果存在,则同时将该数据保存进C1中,并返回对应状态。

      顺带提一下,可能有人会说,在某些场景下,即使 C1中有数据,也要先从 FT里优先获取。我赞同,没错,但注意这里不要混淆讨论的主题了,这本质是属于基于一种业务结果的导向,就类似在传统 RDBMS 读写分离情况下,在关键数据的验证处,直接请求主库获取并操作。所以上面说的其实并没有矛盾,我们讨论时要明确清晰,不要混淆。

    1.4 从FT 中删除(Delete)一条数据

      与Create相反的操作,通过 SQL 向 FT中移除一条数据:如果移除失败,则不需要对 C1 有任何操作,如删除成功,则将对应C1中数据移除(另外请类比1.2中的一些细节)。

  二、谈谈缓存的穿透雪崩等相关问题

    在项目发展到后期,一些业务场景整体都处于高并发状态,大量QPS对整体业务的负载要求很高,为了避免很多时候脱离架构优化的初衷,还需要在项目中做到很多预先性的规避和细节把控。

    2.1 优化防止缓存击穿

      当请求发来的查询 Key 在 Cache 中存在,但某一时刻数据过期了,并且此时出现了大量并发请求,那么这里因为 Cache 中 Miss,就会统一去 DB 中搜索,直接造成在很短的时间内,DB 的 QPS 压力会陡增。

      对于这种问题的预防和优化,往往从两方面入手:一是程序中加小粒度的锁/信号(去年有写过一篇关于商城系统里库存并发管控杂记,里面有具体话题的细节扩展,详见:https://www.cnblogs.com/bsfz/ );二是将 DB的读取延迟 和 Cache的写入时间尽可能拉到最低;三是对其中过于热点的数据采取一个较大的过期时间并做一定的随机性(这里非必要,可自行权衡)。其实还有一点,少数情况下,可根据场景是否限制,可以增加适当的到期自动刷新的策略,这里也可以考虑在程序中开启固定的线程通知维护。

    2.2 预防大量缓存穿透

      当请求发来的查询 Key 在 Cache 中 Miss,自然就会去 DB 里搜索,这里本身没问题,但是假如查询的 Key 在 DB 中也不存在,那么意味着每次请求实际上都是实打实落在了 DB 上。这种问题比较常见,并且即使并发不是很大的时候 DB 的连接数也轻松达到上限,而且本身也不符合我们设计为了提高QPS的初衷。

      对于这种漏洞性问题的解决方式,同样可以从两方面入手:一是程序可以在第一次从DB搜索数据为 NULL 的时候,直接将 NULL 或者一个标识符 Sign 缓存起来,同时个人建议尽量设置一个小范围的随机过期时间,避免不必要的长期内存占用;二是程序里限制过滤一些不可能存在的数据KEY,如借鉴 Bloom filter 思想,特别是在前端请求到后端的这里,尽量进行一次中间判断处理(如有时对不合法KEY直接返回NULL)。

    2.3 控制缓存雪崩

      这里会有某些细节和上面类似,但不完全。当Cache出现不可用,再或者大量数据同一场景里同一时刻失效,批量请求直接访问DB,并且此刻也等同于没有任何Cache措施了。

      为了规避这种偏极端的问题,主要可以考虑从三个方面入手:一是增加完善Cache 的高可用机制,并最好有单独的运维监控预警;二是类似上面针对Cache的时间再次作随机,特别是包含预热和批量的场景里。(ps:你看很多地方都有类似设计来降低一定概率,个人在设计时,即使是项目初期阶段的简化版本里也会包含进去。);三是,在部分场景增加多级Cache,但是在很多时候会增加其他的问题(如多级之前的同步问题),所以个人推荐优先增加到二级即可,然后稍微调整下时间尽量不高于一级Cache。

结语

  由于个人能力和经验均有限,自己也在持续学习和实践,文中若有不妥之处,恳请指正。
本系列告一段落,正好也要去忙一些事情,暂时可能不写相关的东西了。

  个人目前备用地址:
    社区1:https://yq.aliyun.com/u/autumnbing
    社区2:https://www.cnblogs.com/bsfz/

End.

原文地址:https://www.cnblogs.com/bsfz/p/9867952.html

时间: 2024-11-05 18:55:30

[原创]分布式系统之缓存的微观应用经验谈(四) 【交互场景篇】的相关文章

分布式系统之缓存的微观应用经验谈(一) 【基础细节篇】

分布式系统之缓存的微观应用经验谈(一) [基础细节篇] 前言 近几个月一直在忙些琐事,几乎年后都没怎么闲过.忙忙碌碌中就进入了2018年的秋天了,不得不感叹时间总是如白驹过隙,也不知道收获了什么和失去了什么.最近稍微休息,买了两本与技术无关的书,其一是Yann Martel 写的<The High Mountains of Portugal>(葡萄牙的高山),发现阅读此书是需要一些耐心的,对人生暗喻很深,也有足够的留白,有兴趣的朋友可以细品下.好了,下面回归正题,尝试写写工作中缓存技术相关的一

分布式系统之缓存的微观应用经验谈(二) 【主从和主备高可用篇】

分布式系统之缓存的微观应用经验谈(二) [主从和主备高可用篇] 前言 近几个月一直在忙些琐事,几乎年后都没怎么闲过.忙忙碌碌中就进入了2018年的秋天了,不得不感叹时间总是如白驹过隙,也不知道收获了什么和失去了什么.最近稍微休息,买了两本与技术无关的书,其一是 Yann Martel 写的<The High Mountains of Portugal>(葡萄牙的高山),发现阅读此书是需要一些耐心的,对人生暗喻很深,也有足够的留白,有兴趣的朋友可以细品下.好了,下面回归正题,尝试写写工作中缓存技

通过“分布式系统的8大谬误”反思APP的设计 第二篇 谬误2:网络没有时延

就在今天上午,我的系统的网络请求时延高达544毫秒到6937毫秒之间.而且这是在一个已激活的网络接口上.如果接口从省电模式开始激活的话,这还额外需要10秒钟的时间.因此为了提供良好的用户体验,App需要考虑至少十几秒的网络时延. 假如在app发起用户认证请求后,只有请求成功用户才能进入登录页面,这时已经过去7秒.如果接着App需要再发一条请求获取用户信息,那么用户被阻碍在登录页面总共多达14秒.所以我会尽可能打破这种必须前后按序发生的请求. 实际操作中,我会同时发送多个请求:尤其是有些请求,不占

缓存服务器设计与实现(番外篇)

这个系列又更新了. 今天谈一个问题,目前cache软件在业界的使用现状.cache系统其实最大的使用场景,还是主要集中在CDN厂商里. 大概在2010年之前,各大CDN厂商基本清一色的使用squid.那时候的squid是绝对的主力. squid的作为cache领域的鼻祖,正是由于历史的久远,很多近10年左右流行起来的很多系统特性,它本身并不支持.比如sendfile,splice和多核等方面的支持,由于这些特性属于核心架构方面的功能,后期如果想引进的话,需要对squid的核心做大量的修改.对于C

【原创】开源Math.NET基础数学类库使用(四)C#解析Matrix Marke数据格式

开源Math.NET系列文章目录: 1.开源.NET基础数学计算组件Math.NET(一)综合介绍  2.开源.NET基础数学计算组件Math.NET(二)矩阵向量计算  3.开源.NET基础数学计算组件Math.NET(三)C#解析Matlab的mat格式 4.开源.NET基础数学类库使用Math.NET(四)C#解析Matrix Marke数据格式 5.开源.NET基础数学类库使用Math.NET(五)C#解析Delimited Formats数据格式 6.开源.NET基础数学类库使用Mat

CentOS7.4搭建DNS缓存服务器和转发器(四)

(一)简述一,DNS缓存服务器:DNS缓存服务器,即用来存储计算机网络上的用户需要的网页.文件等信息的专用服务器.这种服务器不仅可以使用户得到他们想要的信息,而且可以减少网络的交换量.缓存服务器往往也是代理服务器.由解析器和域名服务器组成的,用于TCP/IP网络.一个dns服务器可以既不是某个域的master服务器,也不是某个域的slave服务器,一个服务器可以不包含任何域的配置信息,它将接收到的所有dns查询进行递归解析,将解析结果返回给查询客户端,并且将查询结果缓存下来,这样的dns服务器称

【原创】一文掌握 Linux 性能分析之 I/O 篇

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 一文掌握 Linux 性能分析之 CPU 篇 一文掌握 Linux 性能分析之内存篇 这是 Linux 性能分析系列的第三篇,前两篇分别讲了 CPU 和 内存,本篇来看 IO. IO 和 存储密切相关,存储可以概括为磁盘,内存,缓存,三者读写的性能差距非常大,磁盘读写是毫秒级的(一般 0.1-10ms),内存读

分布式缓存之Ehcache与terracotta - Terracotta服务器概念篇

1.介绍 Terracotta服务器为Terracotta产品提供分布式数据平台.Terracotta服务器集群被称为Terracotta服务器阵列(TSA).Terracotta服务器阵列可以从单个服务器,到一个用于高可用性(HA)的基本的双服务器串联,再到一个提供可配置的规模.高性能和深度故障转移覆盖的多服务器阵列. Terracotta服务器的主要特性包括: 分布式内存数据管理:在内存中管理比数据网格多10-100x的数据 不复杂的可伸缩性:简单的配置和部署选项,用于扩展以满足日益增长的需

大型网站架构系列:缓存在分布式系统中的应用(三)

本文是<缓存在分布式系统中的应用>第三篇文章. 上次主要给大家分享了,缓存在分布式系统中的应用,主要从不同的场景,介绍了CDN,反向代理,分布式缓存,本地缓存的常规架构和基本原理. 因为时间关于,原计划分享<缓存常见问题>的内容,没有讲.本次主要针对缓存的常见个问题,做一个介绍.主要有以下议题: 一.分享大纲 分享大纲 数据一致性 缓存高可用 缓存雪崩 缓存穿透 参考资料 分享总结 二.数据一致性 缓存是在数据持久化之前的一个节点,主要是将热点数据放到离用户最近或访问速度更快的介质