跳至主要內容

系统设计

soulballad实践阅读Netty权威指南Netty约 9704 字大约 32 分钟

系统设计

功能分离

确保核心功能的高可用和高并发,通过对系统业务的仔细分析,进行功能的分离

分离维度

  • 重要程度: 按照功能的重要程度,划分核心功能和非核心功能,将核心功能和非核心功能物理隔离
  • 流量特点: 可以区分为流量突发型、流量平缓型的功能,对突发流量的功能做好隔离。

应对策略

隔离策略、重试策略、功能降级策略

功能隔离

如何隔离:

单独的域名,单独的接入层、隔离的服务层、单独的缓存,单独的数据库

域名隔离、 代理隔离、微服务隔离、缓存隔离、数据库隔离

功能降级:

当出现故障的时候,当出现瓶颈的时候,可以将非核心功能直接降级,保护核心功能不受影响

降级的实现方式通常有手动自动

  • 自动方式是程序调用发生问题时,自动降级,如调用某服务时,响应时间超过预订阀值,自动降级。微服务的熔断,就属于自动降级
  • 手动方式是使用配置中心,对系统中可降级的服务都设置好开关项,当需要降级时,在配置中心中进行操作,配置中心进行下发变更通知

可以开发了一个后台运维管理程序,当需要停用某个功能的时候,只需要在后台上点击一个按钮就能够完成,花费时间只需要几秒钟。

系统分层

常见互联网分层架构,分为:

  • 1)客户端层:客户端层是浏览器browser或者手机应用APP;
  • 2)接入层:系统入口,负载均衡、反向代理;web服务 ; dns、cdn
  • 3)服务层:实现核心应用逻辑,返回json或者html
  • 4)缓存层:缓存加速访问存储;
  • 5)数据库层:结构化db和异构db;
  • 6)中间件:zk、xxl-job,rocketmq

架构图| ProcessOn_亿级流量秒杀的分层设计方案open in new window

分层模型

客户层

客户层:支持PC浏览器、手机APP、H5页面。
差别是手机APP可以直接访问通过IP访问(而不仅仅是域名)访问接入层服务器。

接入层

流量分发、负载均衡
按照用户规模,流量规模(吞吐量规模),接入层的架构方案不一样

服务层

服务层:提供公用服务,比如用户服务,订单服务,支付服务等;

公共基础能力

服务治理,统一配置,统一监控

缓存层

请求大致分为两类: 读请求、写请求

  • 多级缓存:整个系统架构的不同系统层级进行数据缓存,以提升读取的效率。
  • Tomcat堆缓存(一级)、分布式缓存(二级)、Nginx本地缓存(三级)。
  • HTTP缓存:根据服务器端返回的缓存设置响应头将响应内容缓存到浏览器。减少浏览器端和服务器端

之间来回传输数据量,节省带宽。

数据库层

包含结构化数据(关系型)数据库集群(支持读写分离)

还包含异构数据(非关系型、NOSQL)集群,

还包含分布式文件系统集群;

  1. 大数据存储层:支持应用层和服务层的日志数据收集,关系数据库和NOSQL数据库的结构化和半结构化数据收集;
  2. 大数据处理层:通过Mapreduce进行离线数据分析或Storm实时数据分析,并将处理后的数据存入关系型数据库。

中间件

请求模型

直筒型

直筒型请求处理模型,指的是用户请求 1:1 的洞穿到 db 层,在比较简单的业务中,才会采用这个模型。适用于传统的 低并发、低性能、低可用项目。

直筒型请求处理模型的适用场景:

企业级应用。

特点:

  1. 用户规模较小
  2. 请求峰值和平均值相差不大
  3. 请求峰值不会超过数据层的处理能力

漏斗型

漏斗型业务,指的是,用户的请求,从客户端到 db 层,层层递减,递减的程度视业务而定。例如当10w 人去抢 1 00个物品时,db 层的请求在个位数量级 1000以内,这就是比较理想的模型。

漏斗型请求处理模型的适用场景:

互联网应用(如秒杀)。

特点:

  1. 用户规模大
  2. 请求峰值和平均值相差巨大
  3. 请求峰值远远超出最后一层(数据层)的处理能力

漏斗模型请求分层过滤

漏斗型请求处理模型的核心策略,对请求进行 分层过滤

而针对漏斗型请求处理模型(如秒杀场景)一 种核心策略,就是对请求进行分层过滤,从而过滤掉一些无效的请求。

案例:秒杀系统的分层过滤

比如,在秒杀系统中,请求分别经过 CDN、Nginx(商品详情)、微服务(如交seckill)和数据库 这几层,那么:

  1. 大部分数据和流量在用户浏览器或者 CDN 上获取,这一层可以拦截大部分静态资源的读取;
  2. 经过第二层 Nginx(商品详情)时,尽量得走 Nginx Cache,过滤一些可以直接访问Nginx缓存的请求;
    经过第二层 Nginx(商品详情)时,也可以进行流控,还可以进行黑名单过滤,拦截掉一些无效的流量;
  3. 再到服务层,进入微服务网关时,可以做用户的授权检验,对系统做好保护和限流,这样数据量和请求就 进一步减少;
  4. 业务层,还可以进行数据的有效性、一致性过滤,这里又减少了一些流量。

这样就像漏斗一样,尽量把数据量和请求量一层一层地过滤和减少了。

分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效 请求。而要达到这种效果,我们就必须对数据做分层的校验。

分层过滤的基本原则

  • 通过在不同的层次尽可能地过滤掉无效请求, 尽早处理掉请求。
  • 通过CDN过滤掉大量的图片,静态资源的请求。
  • 读请求尽量命中缓存,不要穿透到数据库;
  • 尽量将动态读数据请求,命中在三级缓存,或者二级缓存,过滤掉无效的数据读;
  • 对写入操作进行削峰,争取批量写入,提高写入的吞吐量;
  • 分层限流:防止系统雪崩,接入层、服务层 限流,根据各层的能力,对系统进行保护

分层架构幂等原则

幂等性的定义

所谓幂等性通俗的将就是一次请求和多次请求同一个资源产生相同的副作用。

通俗点说:对于以相同的请求调用这个接口一次或多次,需要给调用方返回一致的结果时,就要考虑将这个接口设计成幂等接口

需要幂等场景

  • 可能会发生重复请求或消费的场景,在分布式、微服务架构中是随处可见的。
  • 网络波动:因网络波动,可能会引起重复请求
  • 分布式消息消费:任务发布后,使用分布式消息服务来进行消费
  • 用户重复操作:用户在使用产品时,可能无意地触发多笔交易,甚至没有响应而有意触发多笔交易
  • 未关闭的重试机制:因开发人员、测试人员或运维人员没有检查出来,而开启的重试机制(如Nginx重试、RPC通信重试或业务层重试等)

幂等性解决方案

  • 全局唯一ID
  • 唯一索引(去重表)
  • 插入或更新(upsert)
  • 多版本控制
  • 状态机控制

单元化

单元化的定义

所谓单元,是指一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的所有服务,以及单元的数据分片

传统意义上的 SOA 化(服务化)架构,服务是分层的,每层的节点数量不尽相同,上层调用下层时,随机选择节点。

单元化架构下,服务仍然是分层的,不同的是每一层中的任意一个节点都属于且仅属于某一个单元,上层调用下层时,仅会选择本单元内的节点。

而要做到单元化,必须要满足以下要求:

  • 业务必须是可分片的,如 淘宝按照用户分片, 饿了么按照地理位置分片
  • 单元内的业务是自包含的,调用尽量封闭

单元化的基础技术组件

全局路由网关

由于实施单元化后,整个交易链路从前到后的分片规则都是一致的,因此需要在入口处识别用户请求的所属单元,直接将请求路由至目标单元处理。这就使得必须有一套机制或系统来专门完成在这项工作,而又因为是在网络入口处处理,因此需要一个全局路由网关。

此时,需要所有交易尽可能的带上分片键,以便全局路由网关判断当前交易属于哪个单元。然而实际应用过程中,并不是所有交易都能带上分片键,这种情况就需要应用跨单元交易转发组件来处理了。

跨单元数据同步组件

以mysql为例,业界最常用的做法就是利用binlog做数据同步,最具代表性的就是阿里开源的Canal+Otter数据同步方案。
对于常用的中间件,如redis、kafka、ZooKeeper等,需要考虑双机房如何进行数据同步。

流量架构

亿级用户量流量预估

根据二八定律,做出系统在不同用户量、不同场景下的流量(吞吐量)预估,含未来一段时期如两年。

二八定律

什么是二八定律

先来看一下定义。 二八定律又名80/20定律、帕累托法则(Pareto‘s principle)、巴莱特定律、朱伦法则(Juran’s Principle)、关键少数法则(Vital FeRule)、不重要多数法则(Trivial ManyRule)最省力的法则、不平衡原则等,被广泛应用于社会学及企业管理学等。

二八定律是19世纪末20世纪初意大利经济学家帕累托发现的。他认为,在任何一种事物中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的。

使用场景
  • 软件测试
  • 软件测试理论中,常提到2-8原则
    • 80%的测试成本花在20%的软件模块中
      所谓2-8原则,即80%的bug多发生在软件的20%的模块。所以,在回归测试的时候,这20%的高发地带是关注的重点!
    • 80%的错误是由20%的模块引起的
      站在用户角度,并非研发实现的角度,正确地选择重要模块作为测试重点,从而不偏离方向。
    • 80%的测试时间花在20%的软件模块中
      软件测试执行过程中需要将时间倾斜在重要模块的测试用例中,从而使测试更加有效,发现bug
  • 经济学场景
    • 从经济学上看,世界上80%的财富,都集中的20%的人手里
  • 心理学场景
    • 从心理学来说,人类80%的智慧,都集中在20%人身上。
    • 二八定律是一种社会准则,符合大多数社会现象的规律。同样也适用于互联网领域。
  • 互联网行为场景
    • 一个网站有成千上万的用户,但是80%的用户请求是发生在20%的时间内,比如大家去网上购物,基本也都集中在中午休息或晚上下班后。
    • 二八定律的核心原则是关注重要部分,忽略次要部分。系统性能如果能支撑发生在20%时间内的高并发请求,必然也能支持非高峰期的访问。

二八定律预估流量

通过用户量来预估QPS

首先先预估系统的每日总请求数,这个没有固定的方法,如果没有任何历史数据参考,一般是通过用户量或者其他关联系统来评估

【通过用户量来推算PV】

公式:( 总用户数 * 20% ) * 每天的大致点击次数(淘宝经验30-50次)= pv数
问:用户数是1000万,pv量是多少?
答:1000万 * 20% *30= 6000万

【PV推算QPS的公式】

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
问:每天6000万 PV ,多少QPS?
答:(6000万 * 0.8 ) / (86400 * 0.2 ) = 4800W/17280(QPS) =2700

【乘上冗余系数

评估出来指标后,为了更加保险一些,最好再乘以一个冗余系数(偏离系数),提高预期指标,防止人为评估造成预期指标偏低的情况。
这个冗余系数一般定为2-5之间(行业经验),上面计算出来的tps指标为2700,如果再乘以一个冗余系数4,那么最终tps指标就定为10800。

2700 (QPS) * 4 =10800 (QPS)

总结一下,二八定律的算法为 80%的请求 / 20%的时间 * 冗余系数

冗余系数的迭代

同时,将来项目上线后,可以通过对项目接口的峰值监控,来对比之前评估的算法结果,调整冗余系数,最终随着不断的数据积累,将会形成一套本项目的性能模型。

十万级用户量的压力预估

个假设这个网站预估的用户数是10万,那么根据28法则,每天会来访问这个网站的用户占到20%,也就是2万用户每天会过来访问。

【通过用户量来推算PV】

公式:( 总用户数 * 20% ) * 每天的大致点击次数(淘宝经验30-50次)= pv数问:用户数是10万,pv量是多少?
答:10万 * 20% * 30= 60万
通常假设平均每个用户每次过来会有30次的点击,那么总共就有60万的点击(PV)。

【PV推算QPS的公式】

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
问:5小时内会有48万点击 ,多少QPS?
答:60W * 0.8 / (5 * 3600) = 27 (QPS)

【乘上冗余系统 】

27 (QPS) * 4 =108 (QPS)

百万级用户量的压力预估

个假设这个网站预估的用户数是100万,那么根据28法则,每天会来访问这个网站的用户占到20%,也就是2万用户每天会过来访问。

【通过用户量来推算PV】

公式:( 总用户数 * 20% ) * 每天的大致点击次数(淘宝经验30-50次)= pv数
问:用户数是100万,pv量是多少?
答:100万 * 20% * 30= 600万

通常假设平均每个用户每次过来会有30次的点击,那么总共就有600万的点击(PV)。

【PV推算QPS的公式】

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
问:5小时内会有48万点击 ,多少QPS?
答:600W * 0.8 / (5 * 3600) = 270 (QPS)

【乘上冗余系统 】

270 (QPS) * 4 =1080 (QPS)

千万级用户量的压力预估

这个假设这个网站预估的用户数是1000万,那么根据28法则,每天会来访问这个网站的用户占到20%,也就是200万用户每天会过来访问。

【通过用户量来推算PV】

公式:( 总用户数 * 20% ) * 每天的大致点击次数(淘宝经验30-50次)= pv数
问:用户数是1000万,pv量是多少?答:1000万 * 20% * 30= 6000万
通常假设平均每个用户每次过来会有30次的点击,那么总共就有6000万的点击(PV)。

【PV推算QPS的公式】

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
问:5小时内会有48万点击 ,多少QPS?
答:6000 W * 0.8 / (5 * 3600) = 2700 (QPS)

【加上冗余系统 】

2700 (QPS) * 4 =10800 (QPS)

亿级用户量的压力预估

这个假设这个网站预估的用户数是10000万,那么根据28法则,每天会来访问这个网站的用户占到20%,也就是2000万用户每天会过来访问。

【通过用户量来推算PV】

公式:( 总用户数 * 20% ) * 每天的大致点击次数(淘宝经验30-50次)= pv数
问:用户数是1000万,pv量是多少?
答:10000万 * 20% * 30= 60000万

通常假设平均每个用户每次过来会有30次的点击,那么总共就有60000万的点击(PV)。

【PV推算QPS的公式】

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
问:5小时内会有48万点击 ,多少QPS?
答:60000 W * 0.8 / (5 * 3600) = 27000 (QPS)

【加上冗余系统 】

27000 (QPS) * 4 =108000 (QPS)

实际与理论的差距

那么将来项目上线后,接口的访问量真的和计算的一模一样吗?
这个肯定不会,大家一定得知道一个原则,性能测试从来都不是一门非常精确的技术

二八定律也并不是100%适用于所有业务场景。在没有任何历史数据参考的背景下,二八定律相对来说是一种相对来说靠谱的算法,最起码有一定的理论依据,比拍脑袋猜的值靠谱多了。

亿级用户量流量架构

理论篇:一张价值10W的架构师知识图谱open in new window

各组件的并发能力基线值

  • 1)Nginx:一个高性能的Web-Server和实施反向代理的软件
  • 2)LVS:Linux Virtual Server,使用集群技术,实现在Linux操作系统层面的一个高性能、高可用、负载均衡服务器
  • 3)Keepalived:一款用来检测服务状态存活性的软件,常用来做高可用
  • 4)F5:一个高性能、高可用、负载均衡的硬件设备
  • 5)DNS轮询:通过在DNS-Server上对一个域名设置多个IP解析,来扩充Web-Server性能及实施负载均衡的技术
Tomcat

tomcat 默认配置的最大请求数是150,也就是说同时支持150个并发。具体能承载多少并发,需要看硬件的配置,CPU 越多性能越高,分配给JVM的内存越多性能也就越高,但也会加重GC的负担。当某个应用拥有 250 个以上并发的时候,应考虑应用服务器的集群

一般来说,虽然tomcat的io线程最多控制在400以内,如果每个请求要300ms,一个线程3qps,那么一个tomcat到达1000qps,还是可以的。

所以,tomcat参考的并发能力为 1000qps

Nginx

看到这里,这个框架的弱点仍然是Nginx结点的并发问题和单点故障。

对于Nginx的抗并发能力,官方给出的是5w并发量,即轻轻松松处理5w的并发访问。

对比tomcat,之所以有这么大抗并发,主要原因有两个:

一是Nginx只做请求和响应的转发而没有业务逻辑处理,大部分的时间花在与其他计算的I/O上;

二是Nginx的I/O采用的是单线程或少线程、异步非阻塞的模式(Tomcat是一个连接一个线程,同步阻塞的模式),避免了打开I/O通道等待数据传输的过程(仅仅是在数据传到了,再来接收即可),极大的缩短了线程调度和I/O处理的时间。

MySQL
  • 主键查询:千万级别数据 = 1~10 ms , 4核心 8线程 为 1000qps* 8=8000qps
  • 唯一索引查询:千万级别数据 = 10~100 ms , 4核心 8线程 为 100qps* 8=800qps
  • 非唯一索引查询:千万级别数据 = 100~1000 ms , 4核心 8线程 为 10qps* 8=80qps
  • 无索引:百万条数据 = 1000 ms+

综合来说,mysql的并发能力,大概在1500qps左右

Redis

Redis 单机为5w QPS左右。

SpringCloud Gateway

单机大概 5k QPS

系统的流量架构规划

进行部署架构的规划

按照系统未来的用户规模,进行部署架构的规范

比如说 每月增长10W,1年之内100W用户,未来的架构如何?

然后 2年之内1000W用户,未来的架构如何?

然后 3年之内10000W用户,未来的架构如何?

十万用户各层的部署架构

108qps

10w用户 -> 108QPS -> Nginx * 2(主备) -> SpringCloud Gateway * 2(主备) -> Tomcat * 2(主备)

百万用户的各层的部署架构

1080qps

千万用户的各层的部署架构

10800qps

上亿用户的各层的部署架构

108000qps

10000w用户 -> 108000QPS -> LVS * 2(负载均衡) -> Nginx * 8(负载均衡) -> SpringCloud Gateway * 20+(负载均衡) -> Tomcat * 100+(负载均衡)

异地多活的流量架构
  • 通过 dns + gslb (智能ldns)做负载均衡,将请求路由到最近的可用服务的机房。
  • 服务全部机房内自治,不进行跨机房访问
  • 不同的机房,进行数据异步双向复制

GSLB - > [LVS * 2(负载均衡) -> Nginx * 8(负载均衡) -> SpringCloud Gateway * 20+(负载均衡) -> Tomcat * 100+(负载均衡)] * 2

业务架构

数据架构

数据库服务器的参考能力

一台数据库服务器能够承受多大的并发量、数据量 ,受内外两方面因素影响。

内在因素

搞清楚需要估算的数据库服务器是什么配置:

  1. 确定数据库是MySQL还是Oracle亦或是DB2、PostgreSQL等;
  2. CPU是几核?现代数据库应用都充分的运用了多核CPU的并行处理能力;
  3. 内存多大?数据库的索引数据、缓存数据都会进入内存中;
  4. 磁盘IO能力:数据库文件都存储在磁盘中,所以磁盘的IO能力将是影响数据库性能的最直接因素;
  5. 网络带宽:网络的上行和下行带宽,数据库服务器可支持的最大连接数是多少

外在因素

无论是应用程序开发还是SQL查询、操作都遵循“天下武功,唯快不破”的道理。更快意味着服务器资源的快速释放,以便CPU能继续处理其他的任务请求。

做评估数据库的并发量的时候,需要结合使用数据库的程序,否则数据库服务器性能再好,也是属于纸上谈兵。

做数据库并发量评估最好的办法是做数据库压力测试,压力测试的时候,我们可以一点一点的加压力,逐步的得出数据库的:

  • QPS:Queries Per Second 每秒处理的查询数(如果是数据库,就相当于读取)
  • TPS:Transactions Per Second 每秒处理的事务数(如果是数据库,就相当于写入、修改)
  • IOPS:每秒磁盘进行的I/O操作次数

各组件延时和带宽:

  • Cache(L1,L2,L3)
    • 延时: 0.5-15ns
    • 带宽: 20-60GB/s
  • 内存
    • 延时: 30-100ns
    • 带宽: 2-12GB/s
  • SSD硬盘
    • 延时: 10us-1ms
    • 带宽: 50MB-2GB/s
  • 普通硬盘
    • 延时: 5-20ms
    • 带宽: 50-200MB/s
  • 网卡
    • 延时: 100us-1ms
    • 带宽: 10MB-10GB/s

单表的参考数据量

一般来说电商的日订单都是百千万级甚至是亿万级别的了,小小的数据库肯定是撑不住的,这时候就要提前考虑分库分表了。

国内一般大厂规则参考:

  • 单表500万条记录,正常水平
  • 800万条警戒线
  • 1000万条必须要分库分表

单表太大,怎么办? 方法之一:减少请求数量; 方法之二: 分库分表。

亿级库表架构设计

表的数量规划

即便按照每天100万,2年内保持稳定的要求,进行表的数据量规划:

两年数据记录总量是7.3亿(每天100万*730天), 假设每张表的标准值为 500W,表的数据量平均是146张,换成2的幂,比较接近128张,或者说,单表570W(570=7.3亿/128),也是可以接受的,所以就按照128张规划。

库的数量规划

即便按照QPS峰值1W的要求,进行表的库的规划:

每个库正常承载的写入并发量是 1500,那么8个库就可以承载8 * 1500 = 12000 的写并发

悲观一点,如果每秒写入超过 5WQPS,可以通过MQ削峰+批写入的降级策略,MQ的写入吞吐量,可以轻松到达10W级别

总的规划

利用 16 * 8 来分库分表,即分为8 个库,每个库里一个表分为 16 张表。一共就是 128 张表。根据某个 id 先根据 8 取模路由到库,再根据16取模路由到库里的表。

百亿级库表架构设计

表的数量规划

即便按照每天1000万,2年内保持稳定的要求,进行表的数据量规划:

两年总量是73亿(每天1000万*730天), 假设每张表的标准值为500W,表的数据量平均是1460张,换成2的幂,比较接近1024张,或者说,单表692W(692=73亿/1024),也是可以接受的,所以就按照1024张规划。

库的数量规划

即便按照QPS峰值5W的要求,进行表的库的规划:

每个库正常承载的写入并发量是 1500,那么32个库就可以承载32 * 1500 = 48000 的写并发

悲观一点,如果每秒写入超过 5WQPS,可以通过MQ削峰+批写入的降级策略,MQ的写入吞吐,可以轻松到达10W级别。

总的规划

利用 32 * 32 来分库分表,即分为32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据32取模路由到库里的表。

数据迁移方案

停机迁移方案

先来一个最 low 的方案:

准备工作:迁移之前,装好新库,建好新表,已经使用导数的工具,然后将老表的数据出来,写到分库分表里面去。并且做完检查,通过脚本程序检验数据,看新库数据是否准确以及有没有漏掉的数据

凌晨 12 点开始运维,网站或者 app 挂个公告,说 0 点到早上 6 点进行运维,无法访问。接着到0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。

导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。

验证一下,迁移完成。

双写迁移方案

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,除了对老库增删改,都加上对新库的增删改,这就是所谓的双写,同时写俩库,老库和新库。

然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据gmt_modifified 这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。简单来说,就是不允许用老数据覆盖新数据。

导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。

数据同步方案

我们可以看到上面双写的方案比较麻烦,很多数据库写入的地方都需要修改代码。有没有更好的方案呢?

我们还可以利用Canal,DataBus等工具做数据同步。以阿里开源的Canal为例。

利用同步工具,就不需要开启双写了,服务层也不需要编写双写的代码,直接用Canal做增量数据同步即可。相应的步骤就变成了:

  1. 代码准备。
    准备Canal代码,解析binary log字节流对象,并把解析好的订单数据写入新库。
    准备迁移程序脚本,用于做老数据迁移。
    准备校验程序脚本,用于校验新库和老库的数据是否一致。
  2. 运行Canal代码,开始增量数据(线上产生的新数据)从老库到新库的同步。
  3. 利用脚本程序,将某一时间戳之前的老数据迁移到新库。
    时间戳一定要选择开始运行Canal程序后的时间点(比如运行Canal代码后10分钟的时间点),避免部分老数据被漏掉;
    迁移过程一定要记录日志,尤其是错误日志,如果有些记录写入失败,我们可以通过日志恢复数据,以此来保证新老库的数据一致。
  4. 第3步完成后,我们还需要通过脚本程序检验数据,看新库数据是否准确以及有没有漏掉的数据
  5. 数据校验没问题后,开启双读,起初给新库放少部分流量,新库和老库同时读取。由于延时问题,新库和老库可能会有少量数据记录不一致的情况,所以新库读不到时需要再读一遍老库。逐步将读流量切到新库,相当于灰度上线的过程。遇到问题可以及时把流量切回老库
  6. 读流量全部切到新库后,将写入流量切到新库(可以在代码里加上热配置开关。注:由于切换过程Canal程序还在运行,仍然能够获取老库的数据变化并同步到新库,所以切换过程不会导致部分老库数据无法同步新库的情况)
  7. 关闭Canal程序
  8. 迁移完成。

详细设计

客户层

前端:做静态页面的缓存,禁止重复提交。

接入层

流量走CDN、最多到达Ng

亿级用户,10WQPS,走到NG,那么就是 LVS+10-20个NG,就可以搞定

接入层安全

相信不少站长或多或少都经历过DDoS攻击(DDoS攻击是通过大量合法的请求占用大量网络资源,以达到瘫痪网络的目的),一旦你的网站被人DDoS攻击后,网站就会无法被访问了,严重时服务器都能卡死

  • 防火墙
  • 使用高防服务器

静态服务配置

前端工程配置

location ~ .*\.(htm|html)$ {        # 自动匹配到(htm|html)格式
    # 开发阶段,配置页面不缓存html和htm结尾的文件
    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
    root /vagrant/LuaDemoProject/src/www/static; #服务器路径
    default_type 'text/html';
}


location ~ .*\.(js|script)$ {        # 自动匹配到(jpg|gif|png)格式
    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
    root /vagrant/LuaDemoProject/src/www/static; #服务器路径
    default_type 'application/javascript';
}

使用 CDN加速

CDN图解(秒懂 + 史上最全) - 疯狂创客圈 - 博客园 (cnblogs.com)open in new window

阿里云CDN使用

如何提升读并发吞吐量

10W人,1秒内响应到商品的查询信息

每个NG都是 5WQPS ,2个NG节点,如果命中 本地缓存,则为 10WQPS

退一步说,每个NG都是 2.5WQPS ,4个NG节点,如果命中 本地缓存,则为 10WQPS

如果 100W人同时查看一个商品的话,那么就是 20-40个NG节点,

要点:

  • 读请求尽量命中Nginx的本地缓存
  • Nginx 还需要对 商品详情接口做 限流保护(根据商品限流),比如限制在1WQPS, 超过后降级,返回兜底的提示信息,前端可以做出重试,或者提示稍后重试

Demo: /stock-lua/gooddetail -> good_detail.lua -> /stock-provider/api/seckill/sku/detail/v1

如何提升写并发吞吐量

如何提升写并发吞吐量?一个高写并发吞吐量的原则:

  1. 有效流量锁定,过滤出有效请求
  2. 有效流量削峰,有效请求达到或者超出下一层能力瓶颈,同步处理变成(降级为)异步处理
  3. 有效流量限流,1000QPS,接入层、服务处、数据库
  4. 分段操作: 空间换时间,分段进行操作,把一行数据分成多个段

秒杀两段式操作

高并发操作迁移到并发量更高的 nginx+redis+lua,提交操作变成两段式:

  • 申请令牌阶段:
    • 第一阶段为锁定有效流量,将有效流量识别出来,过滤掉无效流量,申请令牌,申请预减库存,申请成功后,进入服务层
    • /getToken/v3 -> getToken_v3.lua -> seckill.lua(sha1) -> setToken
  • 秒杀下单阶段
    • 第二阶段为有效流量落地,进入服务层之后,进入消息队列,秒杀服务从消息队列拉取令牌,确认令牌,然后完成下单操作。查库存 -> 创建订单 -> 扣减库存。通过分布式锁保障多个provider示例并发下单不出现超卖问题。

使用redis集群时,每个节点都需要load一份lua脚本

服务层

服务治理

注册与发现(注册中心)、统一配置(配置中心),服务流控与降级,服务监控

服务扩展与负载均衡

服务的可扩展,可以水平添加机器将用户请求分担到不同的机器上去。

动态扩容与缩容(自动伸缩)

一个tomcat1k,如果10w流量过来来,怎么伸缩1000个实例呢,一个一个的部署,肯定不现实。

自动的伸缩很重要

缓存层

三级缓存架构

Redis与DB的数据一致性解决方案(史上最全) - 疯狂创客圈 - 博客园 (cnblogs.com)open in new window

Demo: /stock-lua/gooddetail -> good_detail.lua -> /stock-provider/api/seckill/sku/detail/v1

  • L1进程内缓存:如java
  • L2分布式缓存:如redis
  • L3本地缓存:如nginx

如果数据量不大且是极热数据,使用L3本地缓存

如果数据量较大,可是使用L2分布式缓存

数据一致性

  • 过期删除+数据回源
    • 被动更新策略,过了一段时间之后,缓存过期,然后回源到上游服务器获取
  • 主动更新+主动删除
    • 通过消息队列,业务系统主动更新+删除缓存

原则与方案

原则

尽量命中NG缓存, 最坏也需要命中 redis分布式缓存

方案:

可以通过消息队列,保持ng缓存与 db的数据一致性

前置知识

  • lua基础
  • lua操作redis
  • 《SpringCloud、Nginx高并发核心编程》

限流

算法

限流:计数器、漏桶、令牌桶 三大算法的原理与实战(史上最全) - 疯狂创客圈 - 博客园 (cnblogs.com)open in new window

  • 计数器算法 CounterLimiter
  • 漏桶算法 LeakBucketLimiter
  • 令牌桶算法 TokenBucketLimiter

级别

维度

  • 用户维度限流,可以在nginx上进行
    • 使用nginx限流内存来存储用户id,比用redis来存储效率更高
    • 大流量往往需要进行流量分片,在进行流量分片路由的时候,同一个用户id,尽量路由到同一个接入网关
  • 商品维度限流,可以在redis上进行
    • 不需要大量的计算访问次数的key
    • 不同的接入网关,需要通过redis,发出同一个商品的令牌
limit_req_zone  $http_user_id       zone=userzone:10m      rate=6r/m;
limit_req_zone  $server_name        zone=seckill_server:1m   rate=50000r/s;

limit_req  zone=userzone;
limit_req  zone=seckill_server;

access_by_lua_file luaScript/module/seckill/getToken_access_limit.lua;

流量削峰

使用rocketmq消息队列对请求进行缓冲,减小tomcat服务器的压力

负载均衡

接入层负载均衡

根据流量而定

流量大于10w qps

三级以上slb: gslb(智能dns) -> lvs -> nginx -> springcloud gateway

大于 5w qps

两级以上slb: lvs -> nginx -> springcloud gateway

<= 5w qps

一级slb: nginx -> springcloud gateway

Nginx负载均衡

upstream server location

Java高并发核心编程(卷3 加强版) Nginx

upstream zuul {
  # centos 自验证环境
  server "192.168.56.121:8888";
  server "192.168.56.122:8888";
  server "192.168.56.123:8888";
  keepalive 1000;
}

location  ^~ /seckill-provider/ {
  # proxy_pass http://localhost:7701/seckill-provider/ ;
  # 指向gateway
  proxy_pass http://zuul/seckill-provider/;
  proxy_set_header Host $host:$server_port;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

微服务网关负载均衡

spring:
 cloud:
  gateway:
      enabled: true
      discovery:
        locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          lower-case-service-id: true
          filters:
            - args[name]: serviceId
              name: Hystrix
          predicates:
            - args[pattern]: '"''/''+serviceId+''/**''"'
              name: Path
      routes:
        - id: blog
          uri: https://blog.csdn.net/
          predicates:
            - Path=/csdn
        - id: service_provider_demo_route
          uri: lb://service-provider-demo
          predicates:
            - Path=/provider/**
上次编辑于:
贡献者: soulballad