系统设计概要

所有事情都是权衡利弊的结果是系统设计最基本的概念之一。每家公司采用不同的架构,每种技术、每种模式适用于某些场景,换一种场景则不适用,关键是了解每种策略的优缺点。从实际出发,没有最佳的系统设计,但是有最佳实践,最佳实践由系统上线时间,系统复杂度,开发及维护成本,可用性等诸多因素决定。

在进行系统设计前,回顾曾用到的架构,注意其中使用了什么技术并继续调研新技术,明确该技术解决了什么问题,此技术的替代方案有哪些,分析该技术的优势及劣势。

系统设计步骤

1.约束和用例

系统设计的第一件事是明确约束并确定系统需要满足哪些用例,就系统需求范围达成一致。
明确问题的要求并设计可以解决问题的良好方案,永远不要假设没有明确说明的事情。
例如url短链服务可能只服务于几千个用户,但是每个用户可能共享数百万个url, 可能意味着要处理百万条以上的点击量,该服务可能要求提供每个url的统计信息,也可能不要求数据统计,以上问题都是要明确的,根据实际问题进行分析。

2.抽象设计

一旦确定了系统设计的范围,接下来应该进行高层次的抽象设计,目的是概述架构所需的所有重要组件。
可先绘制简单的思维导图和流程图等图表,说明用到的主要组件及其之间如何连接。首先确认抽象层级,然后运用业界成熟的组件实现。

3.确定瓶颈

考虑给定问题的限制,高层级的系统设计可能会有一个或多个瓶颈,不需要一开始全部解决,但是需要系统是可扩展的,可以使用标准工具和技术来改善此系统。
思考系统瓶颈常见问题有哪些,如系统需要负载均衡器,转发到各个机器处理用户请求;或者要处理的数据非常庞大,需要分布式数据计算和分布式数据存储。考虑策略的缺点,如是否有数据读写过慢的问题,是否需要引入缓存解决。以上只是部分问题的示例,需要从各个层次考虑,完善解决方案。每种解决方案都是某种权衡,改变某个事情可能让其他事情变得更糟,需要在给定约束和用例的情况下,衡量对系统的影响。

4.细化设计

当高层次的抽象设计达成一致时,可细化抽象设计。

强健的流程对解决系统设计的问题至关重要,以上步骤总结如下:

  • 1.确定问题的范围,不要做出假设,多问问题,理解约束和用例。
  • 2.绘制抽象设计,说明系统的基本组件及其之间的关系。
  • 3.思考系统扩展时面临的瓶颈。
  • 4.通过可扩展系统设计的基本原则解决这些瓶颈。

注:关于可扩展性常见如下

  • 垂直扩展
  • 水平扩展
  • 高速缓存
  • 负载均衡
  • 数据库复制
  • 数据库分区

细化设计时的一般思考

通信方式

1.同步还是异步
2.接口调用提供RestFul方式还是RPC(如Dubbo,HSF,GRpc)方式
3.长短连接
①Http单次请求,使用此种方式,若希望后台更新的数据,前台可实时拿到最新,则前台请求轮询, 包括非阻塞轮询(后台在每次接收请求时,立即返回数据)和阻塞轮询(后台接收请求后不返回,保持连接,新数据返回前台,超时或异常后,前台再次发起请求),区分未知服务的轮询方式可通过浏览器控制台的Network(请求时间短且频次多的为非阻塞轮询,请求时间长且连续请求的为阻塞轮询)
②长连接的WebSocket
③nio框架,如Netty
④消息队列,如Kafka,RabbitMQ,ActiveMQ

数据库

选用关系型数据库还是NoSql
常用数据库:mysql, oracle, postsql, mongodb, redis, influxdb

数据缓存

1.热点数据缓存
2.单节点本地缓存还是多节点缓存
3.可考虑使用Redis Cluster集群模式,存储session及其他一致性数据

注: Redis Cluster模式为多数据节点分散存储,每个数据节点使用哨兵模式(至少为1Master,2Slave,3Sentinel)保证高可用

注册中心

选择Zookeeper或Eureka

配置中心

如选用Ctrip Apollo(优势:可视化页面,权限系统)还是SpringCloud Config(gitlab或Svn存储配置)

测试

1.单元测试:每个模块测试,尽可能高的测试覆盖率,如使用Junit、SpringBootTest,涉及多系统交互的模块,测试前需构造模拟数据。
2.接口测试:如Mock, curl, PostMan, Chrome Rested。
3.系统测试:多个系统,入口发起,观察各服务节点执行情况,本地直接看日志,测试或线上环境可使用OpenTracing分布式链路追踪(如jeager)。
4.黑白盒测试。
5.压力测试:Jmeter, ab, 测试脚本。
6.金丝雀发布(即灰度测试, 每次上线新功能时,将小比例流量, 如%2, 导入到新服务器组,验证功能是否ok, 有无异常。)、蓝绿发布(双服务器组,流量一次性切换)、滚动发布(可选择单服务器组或双服务器组)、A/B 测试(在同一时间维度,将访问流量分别导入到两个或多个相似版本的系统中,收集各组的用户体验数据和业务数据,评估出最好的版本,正式采用。)、影子测试(成本较高,使用于较大重构系统的上线,对生产环境无影响,且易复现生产环境的问题)、配置功能开关等。

部署

1.考虑应用上流是否接入interface, interface实现功能包括
①日志统计
②访问监控及报警
③安全拦截
④限流: 考虑请求排队使用滑动窗口还是令牌桶策略。
整体请求次数限流;
单个接口限流;
根据参数或session的用户限流;
组限流(某些用户划分为一组,共用某些接口的请求次数,如qq群管理员共用某个接口);
⑤熔断:请求流量异常且非法,可熔断服务,返回预先配置的响应数据。
2.需接入应用层监控,监控关键业务、JVM、服务日志及各连接池状态等,如Grafana-Metrics-InfluDb, springboot发送监控数据集成micrometer(包含actuator数据)。
3.运维层接入应用层的监控,监控内存、CPU、流量等。
4.部署方式:单点部署还是多点部署; 虚拟机还是Docker(k8s管理, 区分不同环境的镜像私库); 云服务还是本地物理机;是否使用多云混合部署;是否使用服务网格(如Istio)。
5.定期备份数据(脚本dump)。
6.内网服务还是外网服务,网络连通性配置以及可替换的域名等资源。

其他

1.日志报警:如ELK sentil插件定时扫数据,webhook方式通知相关人。
2.线上问题处理,根据问题线索找bug共性,分析日志、监控及数据,考虑多系统交互的部分,多条线索同时推进,最后定位代码。
3.cpu及内存等监控报警, 如Grafana-Metrics-InfluxDb, Zabbix, 云监控等。
4.代码规范:
①编程规范
②防御性代码
③详细日志
④变动的接口以版本接口方式存在
⑤若有调用其他服务的模块,调用前认为其他服务(尤其是第三方)不稳定,随时有挂掉的可能,需有相应的重试机制(如指数时间的请求)、错误处理、报警及故障恢复
⑥代码为原子提交,尤其对于复杂度高的系统,原子提交在审查代码、cherry-pick、代码回滚都是有益的,且可用于控制变量的调试
5.混沌工程:线上故障预演及恢复,故障切换。

示例

客服系统

1.基本模块,包含操作台,信息查询,服务单管理,工单管理,客服管理,客服设置等。
2.权限:authorization & authentication
如功能权限,某个群的客服可将某个用户禁言(消息无法发出)或者将其消息屏蔽(该用户消息能发出去,但是别人都看不到)。
用户分角色,如客服,管理员,超级管理员,普通用户。
3.单聊及群聊,如群分区,可分华东一群,华东二群,华南一群等。
4.对内机器人及对外机器人。留言及工单处理。
5.接入渠道,如电话渠道、邮件转工单、在线会话/网页组件、微信渠道等。
6.系统依赖模块及系统调用方。
7.BI分析。

IO游戏

1.是否分区,分房间,地图划分,通信方式,数据量,统计。
2.使用同一物理引擎模拟角色运动如Box2D, 服务端校对位置。
3.客户端计算,服务端同步,每次用户操作通知服务器状态变更,服务器广播给其他用户。