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

《亿级流量系统架构设计与实战》通用高并发架构设计 读场景

高并发架构设计的要点

场景分类

读多写少、写多读少,读多写多

高并发读场景方案1:数据库读/写分离

数据库承受的高并发请求压力,主要来自读请求。 我们可以把数据库按照读/写请求分成专门负责处理写请求的数据库(写库)和专门负责处理读请求的数据库(读库),让所有的写请求都落到写库,写库将写请求处理后的最新数据同步到读库,所有的读请求都从读库中读取数据。这就是数据库读/写分离的思路。

数据库读/写分离使大量的读请求从数据库中分离出来,减少了数据库访问压力,缩短了请求响应时间。

架构

用数据库主从复制,将主节点Master作为 “写库”,将从节点Slave作为“读库”,一个Master与多个Slave连接。

请求路由方式

写请求交给Master处理,而把读请求交给Slave处理, 那么由什么角色来执行这样的读/写请求路由呢? 一般可以采用如下两种方式。

基于数据库Proxy代理的方式

在业务服务和数据库服务器之间增加数据库Proxy代理节点,业务服务对数据库的一切操作都需要经过Proxy转发。Proxy收到业务服务的数据库操作请求后,根据请求中的SQL语句进行归类,将属于写操作的请求(如insert/delete/update语句) 转发到数据库Master,将属于读操作的请求(如select语句)转发到数据库任意一个Slave, 完成读/写分离的路由。开源项目如中心化代理形式的MySQL-Proxy和MyCat,以及本地代理形式的MySQL-Router等都实现了读/写分离功能。

基于应用内嵌的方式

基于应用内嵌的方式与基于数据库Proxy代理的方式的主要区别是,它在业务服务进程内进行请求读/写分离,数据库连接框架开源项目如gorm、shardingjdbc等都实现了此形式的读/写分离功能。

主从延迟与解决方案

数据库读/写分离架构依赖数据库主从复制技术,而数据库主从复制存在数据复制延迟(主从延迟),因此会导致在数据复制延迟期间主从数据的不一致,Slave获取不到最新数据。针对主从延迟问题有如下三种解决方案。

同步数据复制

数据库主从复制默认是异步模式,Master在写完数据后就返回成功了,而不管Slave 是否收到此数据。我们可以将主从复制配置为同步模式,Master在写完数据后,要等到全部Slave都收到此数据后才返回成功。

这种方案可以保证每次写操作成功后,Master和Slave都能读取到最新数据。

但是由于在处理业务写请求时,Master要等到全部Slave都收到数据后才能返回成功, 写请求的延迟将大大增加,数据库的吞吐量也会有明显的下滑。

强制读主

取决于具体业务场景

用户a刚刚发布了一条状态,他浏览个人主页时应该展示这条状态,这个场景不太能容忍主从延迟;而好友用户b此时浏览用户a的个人主页时,可以暂时看不到用户a最新发布的状态,这个场景可以容忍主从 延迟。我们可以对业务场景按照主从延迟容忍性的高低进行划分,对于主从延迟容忍性高的场景,执行正常的读/写分离逻辑;而对于主从延迟容忍性低的场景,强制将读请求路由到数据库Master,即强制读主

会话分离

比如某会话在数据库中执行了写操作,那么在接下来极短的一段时间内,此会话的读请求暂时被强制路由到数据库Master,与“强制读主”方案中的例子很像,保证每个用户的写操作立刻对自己可见。暂时强制读主的时间可以被设定为略高于数据库完成主从数据复制的延迟时间,尽量使强制读主的时间段覆盖主从数据复制的实际延迟时间。

高并发读场景方案2:本地缓存

基本的缓存淘汰策略

  • FIFO(First In First Out):优先淘汰最早进入缓存的数据。可以通过队列实现,缓存命中率低。

  • LFU(Least Frequently Used):优先淘汰最不常用的数据。为每条缓存数据维护一个访问计数,数据每被访问一次,其访问计数就加1,会淘汰计数最小的数据。适合缓存在短时间内会被频繁访问的热点数据,但是最近最新缓存的数据总会被淘汰,而早期访问频率高但最近一直未被访问的数据会长期占用缓存。

  • LRU(Least Recent Used):优先淘汰缓存中最近最少使用的数据。一般基于双向链表和哈希表配合实现。

java中就是LinkedHashMap了

​ 将最近被访问的数据放置在尾部,使缓存数据在双向链表中按照最近访问时间由远及近排序,每次被淘汰的都是位于双向链表头部的数据。

LRU策略和LFU策略的缺点是都会导致缓存命中率大幅下降。

W-TinyLFU策略

W-TinyLFU策略结合了 LFU策略和LRU策略的优点,兼具高缓存命中率与低内存占用,Redis和高性能的Java本地缓存Caffeine Cache组件都使用W-TinyLFU策略管理缓存。

W-TinyLFU将缓存的内存空间划分为两部分:

  • Window LRU段:此内存段使用LRU策略缓存数据,其占用的内存空间是总缓存内存空间的1%。

  • Segment LRU段:此内存段使用SLRU策略缓存数据,具体是将缓存段进一步划分为protected段(保护段)和probation段(试用段),其中probation段 负责存储最近被访问1次的缓存数据,protected段负责存储最近被访问至少2次的缓存数 据。Segment LRU段内存空间的80%被分配给protected段,剩余20%的内存空间被分配 给 probation 段。

工作流程:

  1. 将首次被访问的数据X缓存到Window LRU段。
  2. 当Window LRU段的内存空间已满时,使用LRU策略将被淘汰的数据移入 Segment LRU段中的probation段,之后数据X被访问时,再将其移入protected段。
  3. 当protected段的内存空间已满时,使用LRU策略将被淘汰的数据X移入 probation 段。
  4. 当数据X要被移入probation段,但是其内存空间已满时,使用LRU策略将被淘 汰的数据V取出,与数据X进行访问频率的对比,将访问频率高的数据留在proation段, 将访问频率低的数据淘汰。

W-TinyLFU策略使用Count-Min Sketch近似算法来保存每条缓存数据的访问频率

Count-Min Sketch算法的运行流程:

  1. 选定M个哈希函数,分配一个M行N列的二维数组作为哈希表。
  2. 当某数据的访问频率增加时,对数据Key分别使用M个哈希函数计算出哈希值, 再对N取模,然后将二维数组每一行对应的列位置的数值加1,即二维数组中M个位置的数值均被更新
  3. 当查询某数据的访问频率时,进行同样的哈希计算,将二维数组中M个位置的数值读出,选择其中的最小值作为此数据的访问频率。

此策略认为每条数据的访问频率达到15次就已经很高了,于是以4bit表示每条缓存数据的访问频率,最大值为15(2^4 - 1)

如果直接存访问次数,需要4/8字节(32/64)位操作系统

不过, 如果大量数据均达到15次的访问频率,那么就会使得访问频率的区分度大大降低。 W-TinyLFU策略采用基于滑动窗口的时间衰减设计机制来解决这个问题:此策略单独维护一个全局计数,每当二维数组更新1次时,此全局计数就加1;当全局计数达到某个阈值 时,将二维数组中的全部访问频率除以2,同时将全局计数除以2。

缓存击穿与SingleFilght

缓存击穿指的是缓存中一条热门数据在缓存失效的瞬间,对它的并发请求 会“击穿”缓存,直接访问数据库,导致数据库被高并发请求击垮。

Golang语言扩展包提供的同步原语SingleFlight能很好地解决缓存击穿问题。SingleFlight可以将对同一条数据的并发请求进行合并,只允许一个请求访问数据库中的数据,这个请求获取到的数据结果与其他请求共享。

SingleFilght

高并发读场景方案3:分布式缓存

本地缓存不需要网络开销,性能很高,但也存在限制:

  • 多个进程之间无法共享
  • 本地缓存与程序绑定,用Golang语言开发的本地缓存组件不可以 直接为用Java语言开发的服务器所使用。
  • 由于服务进程携带了数据,因此服务是有状态的。有状态的服务不具备较好的可扩展性。
  • 服务进程重启,缓存数据全部丢失。

我们需要一种支持多进程共享、与编程语言无关、可扩展、数据可持久化的缓存,这 种缓存就是分布式缓存。

选型 为什么选redis

Redis数据类型丰富、数据可持久化(RDB机制和AOF机制)、高可用(主从复制模式)、分布式能力( 官方出品的无中心分布式方案Redis Cluster ,业界也有豆瓣Codis和推特 Twemproxy的中心化分布式方案。)

如何使用Redis缓存

为什么向redis缓存存数据要设置过期时间?

  • 防止数据堆积,造成资源浪费
  • db与redis出现数据不一致时,过期时间是一个很好的兜底手段。例如,设置缓存数据的过期时间为10s,那么数据库和Redis 缓存即使出现数据不一致的情况,最多也就持续10s。

缓存穿透

大量访问缓存和db中都不存在的数据,可能会打崩db

解法:缓存空值。不过,如果黑客访问的不是一条非法数据,而是大量不同的非法数据,那么此方案会使得Redis缓存中存储大量无用的空数据,甚至会逐出较多的合法数据。-> 可以使用布隆过滤器。

虽然布隆过滤器对于“数据存在”有一定的误判,但是对于“数据不存在”的判定是 准确的o

缓存雪崩

在同一时间Redis缓存中的数据大面积过期,则会导致请求全部涌向数据库。

缓存雪崩与缓存穿透的区别是,前者是很多缓存数据不存在造成的,后者是一条缓存数据不存在导致的。

诱因:

  • 大量数据的过期时间相同:随机设置过期时间
  • redis宕机:选取去高可用的redis集群架构

高并发读场景总结:CQRS

无论是数据库读/写分离、本地缓存还是分布式缓存,其本质上都是读/写分离,这也是在微服务架构中经常被提及的CQRS模式。

CQRS (Command Query Responsibility Segregation,命令查询职责分离)是一种将数据的读取操作与更新操作分离的模式。

简要架构与实现

读请求由读数据存储来处理。

写请求由写数据存储来处理,数据变更后,发送到消息队列。

读数据存储监听消息队列,收到数据变更消息的时候,将数据写入自身。

写数据存储、读数据存储、数据传输通道均是较为宽泛的代称,其中写数据存储和读数据存储在不同的高并发场景下有不同的具体指代。

  • 对于数据库读/写分离来说,写数据存储是Master,读数据存储是Slave,消息队 列的实现形式是数据库主从复制。

  • 对于分布式缓存场景来说,写数据存储是数据库,读数据存储是Redis缓存,消息队列的实现形式是使用消息中间件监听数据库的binlog数据变更日志。

    flink cdc

更多的使用场景

搜索

搜索用户,搜"北京",会返回“北京日报” “北京大学” “这里是北京”等账号。账号信息被存储在数据库中,无法高效应对搜索昵称的业 务场景。

数据库作为写数据存储负责账号信息的管理,读数据存储适合用es,负责处理搜索用户昵称的请求。

选定读数据存储和写数据存储后,通过消息中间件为两者建立 数据关联:创建一个消费者服务并使用消息中间件监听数据库的binlog数据变更日志,在 筛选出用户昵称有更改的日志后,将最新用户昵称更新到es中。

多表关联查询

有些需要多层嵌套或使用join的sql,性能很差,如果直接对线上数据库执行join语句,则会严重影响其性能。

在这个场景下也非常适合使用CQRS模式,提前将需要多表关联的数据进行聚合计算,并将聚合结果单独存储到一个包含全部关联字段的宽表中,查询时直接读取宽表中的聚合结果,而不用执行join语句。

CQRS架构的特点

  • 写数据存储要选用写性能高的存储系统,而读数据存储要选用读性能高的存储系统
  • 读数据有延迟。写数据存储中的数据实时变更,而何时能从读数据存储中获取到 最新数据,依赖数据传输通道的传输延迟。无论是消息队列还是定时任务都会带 来一定的数据延迟,因此写数据存储和读数据存储仅保证数据的最终一致性。
http://www.xdnf.cn/news/1120375.html

相关文章:

  • 文心4.5开源之路:引领技术开放新时代!
  • Go从入门到精通(22) - 一个简单web项目-统一日志输出
  • 如何单独安装设置包域名
  • LeetCode--45.跳跃游戏 II
  • 雷卯针对灵眸科技RV1106G3开发板防雷防静电方案
  • AI数字人正成为医药行业“全场景智能角色”,魔珐科技出席第24届全国医药工业信息年会
  • 2024年中国公交网络数据集(Shp/分城市)
  • 【DOCKER】-6 docker的资源限制与监控
  • 【机器学习深度学习】Ollama vs vLLM vs LMDeploy:三大本地部署框架深度对比解析
  • ElasticSearch重置密码
  • LabVIEW浏览器ActiveX事件交互
  • JavaScript 性能优化实战:深入性能瓶颈,精炼优化技巧与最佳实践
  • aspnetcore Mvc配置选项中的ModelBindingMessageProvider
  • 多任务——协程
  • VictoriaMetrics 架构
  • VR样板间:房产营销新变革
  • 纯数学专业VS应用数学专业:这两个哪个就业面更广?
  • Cannot add property 0, object is not extensible
  • 【橘子分布式】Thrift RPC(理论篇)
  • iOS APP 上架流程:跨平台上架方案的协作实践记录
  • [Nagios Core] 通知系统 | 事件代理 | NEB模块,事件,回调
  • sqli-labs靶场通关笔记:第11-16关 POST请求注入
  • 迁移学习之图像预训练理解
  • 《大数据技术原理与应用》实验报告一 熟悉常用的Linux操作和Hadoop操作
  • OpenCV 视频处理与摄像头操作详解
  • iOS高级开发工程师面试——Objective-C 语言特性
  • 水务工程中自动化应用:EtherNet/IP转PROFIBUS DP连接超声波流量计
  • vscode 安装 esp ide环境
  • 云原生核心技术解析:Docker vs Kubernetes vs Docker Compose
  • 穿透、误伤与回环——Redis 缓存防御体系的负向路径与治理艺术