通用设计思想
scale out 横向扩展
如:数据库一主多从,分库分表,存储分片;
缓存
性能差异(大概)为:磁盘寻址
10ms -> 内存寻址
100ns;
现有的经典场景如:TLB、HTTP(浏览器缓存、代理缓存、CDN 缓存)、DNS、路由(路由信息,目标ip、下一跳等)、磁盘、cpu等;
业务常用两种缓存:动态(如 redis为代表,内存较大)、本地(内存较小),通常先访问后者再访问前者;
缓存的倾向一般是:热点数据,但同时需要注意这里缓存一致性、OOM 等问题;
可见本处的讨论:Consistency
异步
设计目标
高性能
原则:以问题为导向,找到 二八原则 中的病因,通过测试的性能数据作为支持,持续优化性能;
指标:降低的平响、提升的吞吐量,平均值、最大值、分位值(TP99 为200ms内,99.99 分位在1s内);
方法:
- 提高系统的处理核心数,核心在于从根本上提高并行处理的能力;
- cpu 密集型尝试使用优化算法;
- IO 密集型 使用监控(三件套 log+trace+metrics)分析整个 环节 的性能瓶颈,数据库、网络等;
池化
核心目的是对于频繁创建+销毁的非超大对象,为降低创建和回收的耗时,通过池维护对象,使对象可被复用 减少创建+销毁;
数据库连接池逻辑:
- 复用空闲连接;
- 如果当前连接数 小于 最小连接数,则创建新连接,大于 最小连接数但小于最大连接数时,如果此时没有可复用的连接则创建新连接;
- 如果当前连接数 大于 最大连接数,则等待直到超时;
db 连接池潜在的问题:
- db 域名对于 IP 发生变化,但池中连接没有更新,当原 IP 的 db 关闭后,旧连接的查询都会失败;
- 通常池会解决这个问题(即自动重连):
wait_timeou
代表连接空闲超过一定时间后,db 会主动关闭这条连接,注意使用方是无感知的(比如说用终端连接 db 时,挂久了不动,再尝试发生命令就会报错类似:MySQL server has gone away
);
线程(goroutine)池,使用方面:
- web 服务线程池,并发处理 http 请求(如 gin 会将每一个请求都创建一个 goroutine 去执行,就是普通的并发池);
- 计算密集型任务池,worker 的数量尽可能小于等于核心数(因为要一直算);
- IO 密集型任务池,worker 的数量尽可能大于等于核心数(因为要等);
线程(goroutine)池潜在问题:
- 任务堆积 -> 具体场景需要具体分析,如因为线程的调度策略导致过多线程的切换、干活线程太少(cpu 占用低,都在等)等情况;
- 注意在内存中尽可能不要使用无界队列(即 任务不会被丢失),潜在有 full GC 的问题(java 有,go 基本不会因为 GC 导致服务不可用?个人理解),但最终存在 OOM 这个最终 Boss;
分布式存储
主从读写分离
eg:ES 存储的索引分片会被复制到多个节点、HDFS 的文件也会被复制到多个数据块上;
NoSQL
如:
- redis、levelDB 这类的存储,前者是内存+IO多路复用,后者是内存+追加写/合并,相比于普通的数据库,读写性能强一大截;
- Hbase、Cassandra 这类的列式存储,适用于数据分析的场景;
- MongoDB 这类的文档型数据库,特点是 SchemaFree,因为可在一个集合中存储多个不同结构的数据,字段可以任意扩展;
LSM 不展开了,可见:DDIA ch.3
高可用
原则: 服务的稳定提供;
指标:
- MTBF(Mean Time Between Failure)平均故障间隔,越长说明系统越稳定;
- MTTR(Mean Time To Repair)表示故障的平均恢复时间,越短说明对用户影响越小;
- 可用性 = MTBF / (MTBF + MTTR)
3 个 9 就必须自动化了;
方法:
- failover:服务节点故障时,自动切换流量到其他 peer 节点;
- 容灾:主备不对等节点间,主节点主要负责提供服务,备节点负责故障转移(raft 最终一致性下,主提供读写、从提供读+故障转移),主要包括 对数据的备份(follower)+故障检测(heartRPC)+自动转移(选举);
- 超时控制:通过 TP99 等指标,确定静态或者动态的超时时间,防止长尾延迟,保证可用性;
- 断路器、限流/降级:前者直接干掉请求,直到故障恢复(过程是渐进的,不展开聊),后者分别是减少请求量和只走业务核心逻辑(某些情况下就是干掉耗时长的业务流程,举例就是返回默认值);
op 方面:
- 灰度(用户维度)、ab(用户维度,侧重比较效果)、分级(机器维度);
高扩展
原则:单机资源有限,分布式环境下数据一致性、缓存一致性、依赖第三方的能力、负载均衡、交换机/网络带宽等问题又接踵而至,但分布式是不可少的;
方法:
- 存储层面的扩展:分库分表,核心注意数据复制、数据迁移过程中出现的问题;
- 业务层面的扩展:即微服务的形式提供服务,单独的服务(包括集群)单独部署,使用单独的资源(注意 过微的情况下,考虑是否为了性能将部分相关性强的微服务合并);当服务特别庞大时,也可以考虑将服务分为核心服务和辅助服务,将其拆分到依赖不同的资源上;