设计
设计模式
设计原则:开闭原则(对扩展开放,对修改关闭)、里氏替换原则(子类可以扩展父类的功能但不能改变父类原有的功能,即可新增方法不能覆写父类方法)、依赖倒置原则(面向接口编程,降低耦合)、单一职责原则(类单一功能,提高内聚)、接口隔离原则(接口不应过大应拆分成更小更具体的接口,提高内聚)、迪米特法则(最少知识,若两个类不同心则不应该相互调用,应该通过第三方,降低耦合);
模式分类:创建型(描述如何创建对象):单例(一个类一个实例)、原型(复制已有实例)、工厂、建造者(将一个复杂的对象分解为多个简单的对象一步一步构建);结构型(描述如何组织对象成更大的结构:代理(代理真实对象的访问)、适配器(转换不兼容的接口)、桥接(抽象组合不同类型的实现)、装饰(动态增加对象的职责)、门面(为多个不同系统提供统一接口)、享元(提取公共共享)、组合(组合部分整体);行为型(描述对象间协作):模板方法(定义算法骨架)、策略(一系列算法和算法职责分开)、命令(封装分割请求的发出和执行)、责任链(请求处理者通过前一对象记住下一个对象的引用而连成一条链)、状态(分离对象的状态)、观察者(对象状态改变时通知依赖对象)、中介者(定义中介者解耦原有对象的访问);
单例模式:一个类一个实例且由类自己创建;创建方式:饿汉式:static类加载时初始化,避免多线程问题;懒汉式:使用时初始化,线程不安全,可以方法加synchronized解决;双重检查模式:2次判空,第2次再synchronized,需要volatile;静态内部类:推荐使用,使用时再加载,线程安全;
工厂模式:解耦对象创建;简单工厂:根据类型if..else创建,违反了开闭原则,适用于固定,对象较少的场景;工厂方法:一个类型一个工厂,提供方法创建,违反了开闭原则;抽象工厂:类似工厂方法,一个类型一个工厂,但抽象了一系列方法;
UML:结构图:类图、用例图;交互图:时序图、协作图、状态图、活动图;实现图:构建图、部署图;
架构设计
设计原则:抽象(屏蔽内部实现细节、提供统一处理)、共享(复用相同资源,服务单一入口)、自治(组件自我完备,独立运行、部署)、冗余(冗余实例,避免单点故障)、分布(组件可分布部署)、自动(自管理,自优化);
N+1设计(避免组件单点故障)、回滚设计(向前兼容,可回滚)、禁用设计(通过配置快速下线服务)、监控设计(考虑监控的手段)、多活多地数据中心设计、采用成熟的技术、资源隔离设计(避免单一业务占用全部资源)、架构应能水平扩展、快速迭代、无状态设计、使用商用硬件(能有效降低硬件故障的机率);合适原则>简单原则>演化原则;
架构设计点:架构分层:接入层(API管理、限流、安全、监控、日志)、应用层(水平扩展、无状态、灰度发布)、服务层(分级管理:核心服务、重要服务等,幂等、异步、隔离、容错、水平扩展、独立部署测试)、数据层(数据和应用分离、数据异构、读写分离、统一数据视图);服务管理(自动化发布、测试、性能压测、灰度、应急措施)、服务监控(网络流量、系统、日志、应用、业务)、容灾(备份、多活、重试、雪崩);
分布式ID生成
核心问题:高效(可扩展、高性能)生成趋势时间有序(可省去时间字段)的全局唯一(分布式多服务共用)ID
生成方法:
1、数据库自增序列;优点:简单、有序;缺点:数据库单点可用性问题、数据库节点难以扩展;改进:增加数据库写节点,不同节点使用不同的初始值和相同的步长;改进后的缺点:丧失了ID的绝对有序性,数据库写压力依然较大;
2、单点批量ID生成服务;由于没有全局时钟,单点才能保证绝对时序;采用批量的方式降低数据库写压力(一次生成多个ID缓存在内存,数据库记录当前最大ID);优点:保证了ID绝对有序,降低了数据库写压力;缺点:服务任是单点,水平扩展困难,服务重启后中间出现ID空洞;改进:使用keepalived对ID生成服务做主备;
3、UUID;优点:本地生成,不需要远程调用,无性能和扩展问题;缺点:无法保证趋势递增,字符串过长,存储和检索效率低;改进:转换成较短的字符串或整数存储(参考微博短域名);
4、取当前时间:取当前毫秒、微秒或纳秒作为ID;优点:本地生成,不需要远程调用、趋势递增、整数检索效率高;缺点:毫秒只能抗1000并发(1秒等于1000毫秒);
5、类snowflake算法;参考雪花生成算法,一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号(1毫秒只能抗1000并发,通过相同毫秒内的序列号增加并发),根据业务情况可再细分位数;缺点:没有全局时钟,只能保证趋势递增;
容量设计
解决问题:根据业务发展需要做容量设计;产品运营活动如大促前做容量评估包括数据量、并发量、带宽、CPU/MEM/DISK等;
设计步骤:
1、评估总访问量:询问业务、产品、运营
2、评估平均访问量QPS:总访问量除以时间,一天算4w秒(只算白天)
3、评估高峰QPS:根据业务曲线图来
4、评估系统、单机极限QPS:压测
5、根据线上冗余度回答两个问题:估计冗余度与线上冗余度差值
线程数设置
共性认识:工作线程数不是设置的越大越好(CPU核数有限、线程切换频繁会导致性能下降)、Sleep和网络编程的read、accept等函数调用不占用CPU、单核CPU设置多线程也有意义(代码逻辑清晰、线程休息时其他线程可以继续工作);
一般工作线程的工作模式:本地处理(耗CPU)、访问Cache/下游服务/DB处理(包括网络传输和Cache/下游服务/DB本身的处理,不耗当前服务的CPU);
评估方法:量化工作线程本地处理时间X和等待时间Y(访问Cache/下游服务/DB),则工作线程数设置为 N核*(X+Y)/X;
单点系统优化
问题缘起:分布式系统中并不是所有组件都能水平扩展,有些系统避免不了单点,如Nginx、主从模型中的主节点是写的单点;单点系统存在非高可用和性能瓶颈的问题;
问题解决:
1、减少和单点系统的交互来提升性能:可通过批量写和缓存的方式较少交互;
2、shadow-master解决单点高可用:采用Keepalived+VIP的方式,对外暴露虚拟IP,内部主备节点心跳检测故障,做到无感知切换;
3、水平扩展根本上解决单点问题:如单点Nginx通过DNS轮训多个Nginx解决;
负载均衡
分层实现:客户端层、反向代理层、应用层、服务层、数据层;每一层与下层服务间都存在负载均衡;
客户端层->反向代理层:采用DNS轮训+F5+LVS+Keepalived实现;
反向代理层->站点层:采用Nginx配置实现;
站点层->服务层:采用服务连接池(注册中心)实现;
数据层负载均衡:包括数据存储量的均衡和请求量的均衡,包括按range水平拆分(简单、数据均衡、易扩展、但请求不一定均衡,新范围的用户请求较老用户多)、按ID哈希拆分(简单、数据均衡、请求均衡、但不易扩展,新增节点需要迁移数据);
异构服务器:不同服务器的处理能力不一样,可通过静态权重(无法自适应)、动态权重(初始权重一样,客户端根据每个请求处理成功/失败/超时来动态修改权重)、过载保护(根据动态权重设置策略,如权重很低时控制x时间内不请求某服务器)优化负载均衡;
高并发&高可用
高并发指通过设计保证系统能够同时并行处理很多请求;衡量指标有响应时间、吞吐量/QPS、并发用户数,提高并发的方式有垂直扩展(提升单机硬件或单机架构性能)、水平扩展(DNS轮训、Nginx、服务连接池、数据库分库分表);
高可用指通过设计减少系统不能提供服务的时间;提升可用性的方式有集群化/冗余+自动故障转移;包括反向代理层(keepalived+virtual IP)、应用层(Nginx)、服务层(注册中心)、缓存层(Redis哨兵)、读库(db-connection-pool自动检测连接不可用从而转移读库)、写库(keepalived+virtual IP);
数据库架构设计
数据存储结构优化(58同城方案):针对众多的分类以及不同分类下的大量异构属性存储,采用分类(大类、小类,用于区分Ext的不同含义)+ Ext(采用Json存储不同字段,方便字段扩展,Json的Key采用数字减少存储空间,数字Key用专门的数据字典表)的方案存储;提供专门的检索服务来支持对Ext中字段的搜索;
数据架构优化:业务初期用单库;读压力大,读高可用,用分组;数据量大,写线性扩容,用分片;属性短,访问频度高的属性,垂直拆分到一起;
分组:解决读写并发高的问题;包括一主多从,主从同步,读写分离;特点:主从通过binlog同步,不同实例数据库的结构和数据都完全相同;
分片:解决单库数据量大的问题;包括分库分表,优先考虑分库(分表还是读的相同的磁盘文件,不易扩展);分片算法可采用范围法和哈希法;特点:不同实例数据库间没有直接联系,数据结构相同但数据不相同;
垂直拆分:根据业务拆分,一般考虑属性的长度和访问频率(长度较短访问频率较高的放在一起、长度较长访问频度较低的放在一起);特点:不同实例数据库间没有直接联系,数据结构和数据都不相同;
反向依赖与解耦
公共库导致耦合:如果公共库是业务特性代码,进行公共库垂直拆分;如果公共库是业务共性代码,进行服务化下沉抽象
服务化不彻底导致耦合:个性代码放到业务层实现,将服务化更彻底更纯粹;
notify的不合理实现导致的耦合:通过MQ解耦;
配置中的ip导致上下游耦合:使用内网域名替代内网ip
数据库架构设计
基本概念:单库、分片(范围、哈希、专门的路由服务)、分组(一主多从、主从复制、读写分离),大部分互联网公司采用既哈希分片又分组的方式;
设计思路:1、保证数据的可用性:冗余读库解决读高可用,但读写延时会数据不一致;冗余写库解决高可用,采用双主互备(不同主库写入不同ID的数据,再同步);2、提高数据库的读性能:建立索引、增加从库(同步慢导致数据不一致)、增加缓存;3、保证数据一致性:主从数据库间通过中间件或强制读主解决,数据库和缓存间通过缓存双淘汰和过期时间解决;4、提供数据库的扩展性:考虑如何秒级扩容(如N台机器扩容到2N台:新增N个从库、开启同步、修改路由规则、解决同步、删除多余数据);
冗余表数据一致性
数据库分库分表之后通过冗余表解决查询效率;冗余表实现:服务同步写2个库、服务同步写1个库再异步写1个库、服务同步写1个库再通过binlog完成数据同步;先写哪个库的原则:如果出现不一致,谁先做对业务的影响较小,就谁先执行;
保证数据不一致:全量对比(线下启动一个离线的扫描工具,不停的比对正表T1和反表T2,如果发现数据不一致,就进行补偿修复)、增量对比(线下启动一个离线的扫描工具,不停的比对日志log1和日志log2,如果发现数据不一致,就进行补偿修复)、消息中间件(写库后发送消息,通过订阅消息并对比进行补偿修复);
缓存架构设计
更新缓存&淘汰缓存:更新缓存的逻辑可能较为复杂,淘汰缓存较为简单,知会带来一次缓存不命中,通用处理是淘汰缓存;
先操作数据库&先淘汰缓存:原则:如果出现不一致,谁先做对业务的影响较小,就谁先执行;如果先写数据库再淘汰缓存失败,则数据不一致;如果先淘汰缓存再写数据库,则知会带来一次缓存不命中,通用处理是先淘汰缓存;
缓存架构优化:服务化(通过服务层提供统一的数据访问接口,屏蔽数据操作的细节)、异步缓存更新(所有写走数据库,所有读走缓存,通过binlog异步同步);
幂等性
概念:一次和多次请求某一个资源对于资源本身应该具有同样的结果,简化了客户端实现,让服务端实现更复杂(增加幂等控制逻辑,并发改串行,降低了效率);实现:乐观锁、防重表、分布式锁、token令牌、缓冲区(异步处理,过滤重复)
无状态:有状态服务可以比较容易地实现事务,在不需要考虑水平扩展时,是比较好的选择;无状态服务的优势在于可以很方便地水平伸缩,但是在实现事务时,需要做一些额外的动作,通过session抽离到公共的地方实现;
零拷贝
传统的读写:4次用户态和内核态的切换,4次数据拷贝;mmap:内存映射,用户空间可以共享内核空间的数据,适合小数据量读写,需要4次上下文切换,3次数据拷贝;sendFile:数据不经过用户态,和用户态完全无关,适合大文件传输,需要3次上下文切换,最少2次数据拷贝;零拷贝:是从操作系统的角度来说的,因为内核缓冲区之间,没有数据是重复的;
Keepalived
Keepalived:一种高性能的服务器高可用或热备解决方案,用来防止服务器单点故障,可实现多个Nginx的负载均衡,通过虚拟IP对外提供服务,内部通过VRRP虚拟路由冗余协议将虚拟IP转发到真实的IP;
Nginx/HAProxy/LVS:Nginx/HAProxy工作在第七层,配置安装简单,LVS工作在第四层,配置复杂,F5/Array是商业硬件负载均衡;HAProxy只是负载均衡软件,Nginx还可作为服务器;Nginx可作为LVS的节点实现负载均衡;性能:Array>F5>LVS>Nginx/HAProxy
架构演进
1. Tomcat+DB同机部署,单体应用;问题:随着用户增长,单机的Tomcat和数据库存在资源竞争;2. Tomcat+DB分开部署,单体应用;问题:数据库并发读写成为瓶颈;3. Tomcat+DB+缓存(本地Memcached+分布式Redis),缓存热点数据,减小数据库压力;问题:随着用户增长,单机Tomcat成为瓶颈;4. 引入Nginx负载均衡,部署多台Tomcat;问题:并发量增加,数据库成为瓶颈;5. 使用数据库中间件MyCat实现数据库读写分离;问题:随着业务增加,不同业务竞争数据库资源,相互影响;6. 数据库按业务分库;问题:随着业务增加,单机写库成为瓶颈;7. 分库分表,使用MyCat和业务数据汇总,至此数据库和Tomcat都实现了水平扩展;问题:单点Ngnix成为瓶颈;8. 使用LVS/F5对外提供虚拟IP实现Nginx的负载均衡;问题:单机LVS成为瓶颈;9. DNS服务器配置多个域名实现LVS的负载均衡;问题:随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单靠数据库无法解决;10. 引入NoSQL和搜索引擎HDFS/Hbase/Redis/MongoDB/ES,至此解决了业务维度的扩充;问题:业务应用升级迭代困难;11. 应用拆分,大应用拆小应用,不同应用的公共部分抽成公共服务;问题:服务间调用复杂,逻辑混乱;12. 引入服务总线或API网关;问题:服务太多,部署困难,不能动态扩容;13. 引入容器化技术实现运行环境隔离与动态服务管理;问题:需要自己管理机器,管理本身和运维困难;14. 以云平台承载系统;
ServiceMesh
微服务架构的核心技术包括负载均衡和服务发现,可以通过代理实现,代理分为3种方式:集中式代理(如Nginx、F5)、客户端代理(如Ribbon、Dubbo)、主机独立进程代理(每个部署服务的机器部署代理进程);
服务网格类似于主机独立进程代理,在每个机器上部署代理(sidecar),由sidecar负责服务发现、负载均衡、容错限流等,组成一个服务网络,叫数据平面;同时会有一个控制平面控制流量路由;
Istio专注在控制面板的架构、功能、以及控制面板和数据面板之间API的标准化,它的控制面板功能主要包括:Istio-Manager:负责服务发现,路由分流,熔断限流等配置数据的管理和下发;Mixer:负责收集代理上采集的度量数据,进行集中监控;Istio-Auth:负责安全控制数据的管理和下发;Envoy是目前Istio主力支持的数据面板代理,其它主流代理如nginx/kong等也正在陆续加入这个阵营;kubernetes是目前Isito主力支持的容器云环境;
一致性Hash
概念:利用hash环改进余数hash,提高伸缩性;首先计算所有服务器的hash,再计算用户ID的hash,找到离该用户hash最近的服务器hash即为需要请求的服务器;特性:单调性(新增请求会落在原有或新增服务器上,不会落在原有其他服务器上)、分散性(尽量避免同一请求落在不同服务器)、平衡性(负载均衡,但存在倾斜即一个服务器处理了过多请求,增加虚拟节点解决);均匀一致性hash:优化虚拟节点算法,使每台服务器负载尽量均衡;