ISBN:978-7-121-30954-0
作者:张开涛
页数:447页
阅读时间:2021-11-28
推荐指数:★★★★★
Java架构师必读书目之一,
从原理出发,一步一步深入,
实战中的解决方案都能在这里面找到,
非常推荐仔细阅读。
在资源有限的情况下,一定是先解决当下最核心的问题。
系统设计是一个不断迭代的过程,在迭代中发现问题并修复问题,即满足需求的系统是不断迭代优化出来的,这是一个持续的过程,个人不相信存在完美架构银弹。
1.高并发原则
1.1 无状态
1.2 拆分
系统维度、功能维度、读写维度、AOP维度、模块维度
1.3 服务化
进程内服务->单机远程服务->集群手动注册服务->自动注册和发现服务->服务的分组/隔离/路由->服务治理放在括号内。
1.4 消息队列
1.5 数据异构
数据异构、数据闭环。
1.6 缓存银弹
流程节点 | 缓存技术 |
---|---|
客户端 | 使用浏览器缓存 |
客户端应用缓存 | |
客户端网络 | 代理服务器开启缓存 |
广域网 | 使用代理服务器(含CDN) |
使用镜像服务器 | |
使用P2P技术 | |
源站及源站网络 | 使用接入层提供的缓存机制 |
使用应用层提供的缓存机制 | |
使用分布式缓存 | |
静态化、伪静态化 | |
使用服务器操作系统提供的缓存机制 |
1.7 并发化
2 高可用原则
2.1 降级
开关集中化管理、可降级的多级读服务、开关前置化、业务降级
2.2 限流
2.3 切流量
DNS、HttpDNS、LVS/HaProxy、Nginx
2.4 可回滚
3 业务设计原则
3.1 防重设计
3.2 幂等设计
3.3 流程可定义
3.4 状态与状态机
3.5 后台系统操作可反馈
3.6 后台系统审批化
3.7 文档和注释
3.8 备份
4 负载均衡与反向代理
一个域名可以映射多个ip地址,但是当一个主机故障时,DNS有一定的缓存时间,故障后切换时间长,而且没有对后端服务进行心跳检查和失败重试的机制。
为了提升整体吞吐量,会在DNS和Nginx之间引入接入层,比如使用LVS(软件负载均衡)F5(硬件负载均衡)
接入层、反向代理服务器、负载均衡服务器都指Nginx。
上游服务器配置:upstream server配置
负载均衡算法:配置多个上游服务器负载均衡机制。
失败重试机制:当配置超时或不存活时,是否重试其他上游服务器。
服务器心跳检查:上游服务器健康 检查、心跳检查。
负载均衡算法:轮询、ip hash
Http动态负载均衡、静态配置。
Consul+Consul-template
Consul+OpenResty
5 隔离
系统隔离是为了在系统发生故障时,能限定传播范围和影响范围。
5.1 线程隔离(线程池隔离)
5.2 进程隔离
5.3 集群隔离
5.4 机房隔离
5.5 读写隔离
5.6 动静隔离
5.7 爬虫隔离
5.8 热点隔离
5.9 资源隔离
隔离实现技术:Hystrix、Servlet3
Servlet3请求解析和业务处理线程池分离、Servlet3异步化
可以使用BIO压测、Servlet3 NIO异步化压测。
Tomcat处理流程:
1.容器负责接收并解析请求为HttpServletRequest。
2.然后交给Servlet进行业务处理。
3.最后通过HttpServletResonse写出响应。
6 限流
限流算法:令牌桶算法、漏桶算法。
应用级限流:并发数、连接数、请求数。
分布式限流:Redis+Lua实现、Nginx+Lua实现
接入层限流:ngx_http_limit_conn_module、ngx_http_limit_req_module、lua-resty-limit-traffic
节流
1.throttleFirst/throttleLast
指在一个时间窗口内,如果有重复多个相同事件要处理,只处理第一个或最后一个。
前段可以使用jquery-throttle-debounce-plugin实现、Android可以用RxAndroid实现。
2.throttleWithTimeout
也叫做debounce(去抖),限制两个连续事件先后执行时间不得小于某个时间窗口。
Java可以使用RxJava实现。
7 降级
Hystrix可以实现降级、熔断。
Turbine WAR、Hystrix Dashboard WAR
名称 | 说明 |
---|---|
页面降级 | 切换到指定页面 |
页面片段降级 | 商品页商家部分信息错误,可以进行降级 |
页面异步请求降级 | 异步加载请求响应慢,可降级 |
服务功能降级 | 不太重要的服务异常时不获取信息。 |
读降级 | 多级缓存模式,后端有问题降级为只读。 |
写降级 | 只进行Cache更新,异步扣除库存信息到DB,保证最终一致性 |
爬虫降级 | 将爬虫流量导向静态页或返回空数据 |
风控降级 | 识别机器人 |
超时降级 | 相应慢自动降级 |
统计失败次数降级 | 到一定数量自动降级 |
故障降级 | 网络故障等 |
限流降级 | 访问量大时 |
8 超时与重试机制
代理层超时与重试:Haproxy、Nginx、Twemproxy
Web容器超时:Tomcat、Jetty
中间件客户端超时与重试:JSF、Doubbo、JMQ、CXF、Httpclient
数据库客户端超时:MySQL、Oracle
NoSQL客户端超时:Mongo、Redis
业务超时:任务型、服务调用型
前端Ajax超时
9 回滚机制
事务表、消息队列、补偿机制(执行/回滚)、TCC模式(预占/确认/取消)、Sagas模式(拆分事务+补偿机制)
9.1 代码库回滚
9.2 部署版本回滚
版本化、小版本增量发布、大版本灰度发布、架构升级并发发布、
9.3 数据版本回滚
9.4 静态资源版本回滚
10 压测与预案
10.1 系统压测
压测方案:压测接口、并发量、压测策略(突发、逐步加压、并发量)、压测指标(机器负载、QPS/TPS、响应时间)、压测报告、成功率、相关参数(JVM参数、压缩参数)。
压测工具:JMeter、Apache ab
线下压测、线上压测、全链路压测、读压测、仿真压测、隔离集群压测、单机压测。
10.2 系统优化和容灾
代码走查
10.3 应急预案
首先进行系统分级,然后进行全链路分析、配置监控报警,最后制定应急预案。
网络接入层、应用接入层、Web应用层和服务层
11 应用级缓存
11.1 缓存
经常读取数据、频繁访问数据、热点数据、IO瓶颈数据、计算昂贵数据、符合5分钟法则和局部性原理数据均可进行缓存。
CPU->L1/L2/L3->内存->磁盘
11.2 缓存命中率
缓存命中率 = 从缓存中读取次数/[总读取次数(从缓冲中读取次数+从慢速设备上读取次数)]
11.3 缓存回收策略
基于空间、容量、时间、Java对象引用、弱引用。
TTL(Time To Live):存活期,即缓存数据从创建开始直到到期的一个时间段(不管在这个时间段内有没有被访问,缓存数据都将过期)。
TTI(Time To Idle)空闲期,即缓存数据多久没被访问后移除缓存的时间。
回收算法:
FIFO(First In First Out)先进先出。
LRU(Least Recently Used)最近最少使用算法。
LFU(Least Frequently Used)最不常用算法。
实际中使用LRU比较多,如:Guava Cache、Ehcache。
堆缓存:使用Java堆内存来存储对象。好处是没有序列化/反序列化,是最快的缓存。缺点是数据量大时GC暂停时间会变长,存储容量受限于堆空间大小。
实现:Guava Cache、Ehcache 3.x、MapDB
堆外缓存:存储在堆外内存,可以减少GC暂停时间,可以支持更大的缓存空间,只受机器内存大小限制,不受堆空间影响。但是读取数据需要序列化/反序列化,比堆缓存慢很多。
实现:Ehcashe 3.x、MapDB
磁盘缓存:存储在磁盘上,JVM重启时数据还在,而堆缓存、堆外缓存都会丢失。
实现:Ehcache 3.x、MapDB
分布式缓存:
单机容量问题
数据一致性问题
数据不命中问题
单机:存储最热的数据到堆缓存,相对热的数据到堆外缓存,不热的数据到磁盘缓存。
集群:存储最热的数据到堆缓存,相对热的数据到堆外缓存,全量数据到分布式缓存。
缓存使用模式:
Cache-Aside、Cache-As-SoR、Read-Through、Write-Through、Write-Bhind、Copy Pattern
SoR(System-Of-Record):记录系统
可以用JMH进行测试。
11.4 HTTP缓存
Last-Modified文档最后修改时间。
F5刷新、Ctr+F5强制刷新、Age响应头、Vary代理层缓存、Via代理层、ETag发送到服务器进行内容变更验证。
11.5 HttpClient客户端缓存
Cache-Control设置说明
public | 共享缓存,客户端和代理服务器都可缓存,响应可被缓存。 |
---|---|
private | 私有缓存,客户端可缓存、代理服务器不能缓存,永恒私有内容不能共享。 |
no-cache | 允许缓存者缓存响应,但需要回源验证。 |
no-store | 请求和响应禁止缓存。 |
max-age | 保鲜期和Expires类似,根据该值校验缓存是否新鲜。 |
s-maxage | 与max-age区别是仅用于共享缓存,不新鲜时遇到此头要重新验证。 |
max-stale | 缓存的最大陈旧时间,如果缓存不新鲜但还在该最大陈旧时间内,则可以返回陈旧的内容。 |
min-fresh | 最小新鲜期,使用(保鲜期-当前Age)< min-fresh判断内容是否新鲜。 |
must-revalidate | 当缓存过了新鲜期后,必须回源重新验证。与no-cache类似,但更严格,不能使用后台重新验证,而no-cache允许后台重新验证。 |
proxy-revalidate | 与must-revalidate类似,但是,只对缓存代理服务器有效,客户端遇到此头需要回源重新验证。 |
stale-while-revalidate | 请求时,表示在指定的时间内可以先返回陈旧的内容,后台进行重新验证(异步验证)。 |
state-if-error | 在指定时间出现500、502、503、504时,可以使用陈旧内容。 |
only-if-cached | 该头表示只从缓存获取响应,如果没有则504,Gateway Timeout |
11.6 Nginx HTTP缓存设置
11.7 Nginx 代理层缓存
11.8 如何缓存数据
不过期缓存:首先写数据库,如果成功则写缓存。不要把写缓存放到事务中。
过期缓存:懒加载,先读取缓存,不命中则查询数据,异步写入缓存并设置过期时间。
11.9 大Value缓存
Memcached来缓存大Value;或者对Value进行压缩;或拆分多个小Value。
12 连接池
池子不能太大,会影响GC的扫描时间。
池化可以使用:Apache commons-pool2来实现。DBCP、Jedis都是使用这个实现。
12.1 数据库连接池
C3P0、DBCP、Druid、HikariCP
注意网络阻塞、不稳定时的级联效应,等待超时应该尽可能小点。
12.2 HttpClient连接池
实现:HttpClient
12.3 线程池
减少频繁创建和销毁线程来降低性能的损耗。
I/O密集型、CPU密集型。
13 异步并发
当服务比较慢时,让出线程和CPU来处理下一个请求。
当一个线程在处理任务时,通过Fork多个线程来处理任务并等待这些线程的处理结果,这种应用并不是真正的异步。异步是针对CPU和I/O的,当I/O没有就绪时要让出CPU来处理其他任务,这才是异步。
13.1 异步Future
阻塞主请求,高并发时会造成线程过多、CPU上下文切换。
13.2 异步Callback
通过回调机制实现,并不能提升性能,而是为了支撑大量并发连接或者提升吞吐量。
13.3 异步编排CompletableFuture
使用ForkJoinPool实现异步处理。
场景一是三个服务异步并发调用,然后对结果合并处理,不阻塞主线程。
场景二是两个服务并发调用,然后消费结果,不阻塞主线程。
场景三是Service1执行完成后,接着并发执行Service2和Service3,然后消费相关结果,不阻塞主线程。
13.4 异步Web服务实现
Servlet3、CompletableFuture实现异步Web服务。
13.5 请求缓存
将GetProductService包装一层JVM缓存。
13.6 请求合并
CompletableFuture必须提前构造好批量查询,而Hystrix支持将多个单个请求转换为单个批量请求。可以按照单个命令来请求,但实际是以批量请求模式执行。
14 如何扩容
单体应用垂直扩容:升级硬件配置
单体应用水平扩容:部署多个镜像
应用拆分、数据库拆分(分库分表、取模分区)、Sharding-jdbc读写分离、数据异构。
查询维度异构、聚合数据异构。
任务系统扩容:Thread、Timer、ScheduledExecutor、Quartz单机版即可。
分布式任务:Quartz集群版、tbschedule、Elastic-Job、Elastic-Job-Lite。
15 队列术
异步处理、系统解耦、数据同步、流量削峰、扩展性、缓冲。
缓冲队列、任务队列、消息队列、请求队列、数据总线队列、混合队列、优先级队列、副本队列、镜像队列、队列并发数、推送拉取。
实现:Disruptor+Redis队列、Canal实现数据异构。
16 架构
16.1 架构1.0
IIS+C#+SQL Server架构,扛不住时加了一层memcached来缓存数据。
16.1 架构2.0
MQ消息通知、Java Worker调用多个依赖系统、rsync同步到其他机器、Nginx静态页、接入层负责负载均衡。
16.1 架构3.0
迅速响应迅速变化的需求和各种变种的需求、支持各种垂直化页面改版、页面模块化、AB测试、高性能、水平扩容、多机房多活异地多活。
17 OpenResty
单DB架构、DB+Cache/数据库读写分离架构、OpenResty+Local Redis+MySQL集群架构、OpenResty+Redis集群+MySQL集群架构
Redis+Twemproxy
MySQL+Atlas
Nginx+Lua
技术选型:
MQ:ActiveMQ
RPC:Dubbo
KV:SSDB或者ARDB
缓存:Redis
前端模板拼装:OpenResty
SSDB/Redis分片使用Twemproxy