摘要:分布式、微服务框架,高并发、高性能、高可用等各类中间件。
目录
[TOC]
分布式、微服务、中间件
没有统一定义,资料上的说法不一致,我的理解是。
架构演进
总体来说,系统的架构大致经历的演变为:
- 单体应用架构:在企业发展的初期,一般公司的网站流量都比较小,只需要一个应用,将所有的功能代码、模块打包成一个应用包,部署到一个Web服务器(Tomcat)上,与数据库共用一台服务器,就能支撑公司的业务。
- 优点:能够减少开发、部署和维护的成本。
- 缺点:性能低、可靠性差,扩展性和可维护性差 。
- 垂直应用架构:(根据业务属性)将一个大的单体应用拆分成多个模块或子系统,(通过负载均衡)形成多个独立的单体架构。
- 进行了部分解耦;
- 各个子系统相互依赖的代码和模块中,存在模块功能重复开发和重复代码拷贝的情况。
- 分布式架构(Distributed):
- SOA(全称
Service-Oriented Architecture)面向服务的架构:增加一个统一的调度中心对集群进行实时管理。将重复业务模块抽取出来,作为公共服务,供其他调用者消费,以实现服务的共享和重用。- 在分布式架构中,将系统整体拆分为服务层和表现层。服务层封装了具体的业务逻辑供表现层调用,表现层则负责处理与页面的交互操作。
- 优点:通过注册中心解决了各个服务之间服务依赖和调用关系的自动注册与发现。
- 缺点:服务之间的依赖与调用关系复杂,增加了测试和运维的成本。
- 微服务架构:
Dubbo 是 SOA 时代的产物,Spring Cloud 是微服务时代的产物。
分布式
分布式:把整个系统拆分成不同的服务、模块,分散、部署在不同的服务器上,而不是单纯依赖于一台计算机。
- 是一种 SOA 架构。是一种计算资源或任务在多个节点之间分散的方式。
- 目标是:减轻单体服务的压力、提高并发量和系统的性能、可靠性、可扩展性和容错性。
- 高并发、高性能、高可用。
- 比如应用与数据分离、数据库单独使用一台服务器,进一步多台服务器上数据库读写分离。
微服务
微服务架构:强调的一个重点是“业务需要彻底的组件化和服务化”。将原有的单个业务系统(应用程序)拆分为多个(可以独立开发、设计、运行的)小应用(微服务),微服务会部署在不同的服务器、不同的容器、甚至多数据中心。
- 每个服务都是独立运行的,都有自己的数据库、业务逻辑和用户界面。一系列独立运行的微服务共同构建了整个系统。
- 每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如用户管理,商品管理微服务
- 微服务之间通过一些轻量级的通信机制进行通讯,例如通过Restful API、RPC 协议进行调用。
- 技术栈不受限:可以使用不同的开发语言和数据存储技术
- 全自动的部署机制,可以使用Docker容器化进行多实例部署。
- 按需伸缩:根据需求和应用场景,实现细粒度的水平扩展
- 是在SOA架构的基础上进一步的扩展和拆分。
- 是一种软件架构风格,是以专注于单一责任与功能的小型功能区块为基础,利用模块化的方式组合出复杂的大型应用程序。
- 是相对于单体式应用的,表示一个应用程序内包含了所有需要的业务功能,并且使用像主从式架构(Client/Server)或是多层次架构(N-tier)实现,虽然它也是能以分布式应用程序来实现,但是在单体式应用内,每一个业务功能是不可分割的。
特征、设计原则
SOLID?
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发;
- 服务自治:团队独立、技术独立、数据独立、部署独立;
- 轻量级通讯机制
- 微服务粒度
- 面向服务:微服务对外暴露业务接口;
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题;
优点:服务彻底拆分,各服务独立打包、独立部署和独立升级。每个微服务负责的业务比较清晰,利于后期扩展和维护。
缺点:架构非常复杂,运维、监控、部署难度提高。开发的成本比较高。涉及到各服务的容错性问题、数据的一致性问题、分布式事务问题。
划分的粒度
微服务划分粒度也可以说是模块划分粒度,该如何确定?其实是一个没准确答案的问题。
大体上的思路有:
- 按照 DDD 领域驱动设计理论划分服务领域、设定服务限界上下文:
- 使用 DDD 中的限界上下文(bounded context)作为微服务的边界。也就是一个限界上下文内的自然就是一个微服务。
既然有了模块化,为什么还要微服务?
模块化和微服务化的本质区别就是是否独立部署。
- 我个人认为模块化应该是微服务化的前置条件。
分布式、微服务的关系
可以简单理解为:
- 集群:多节点、同模块。
- 分布式:多节点、多模块。
- 微服务:分布式主要是有多个服务器,微服务更强调服务的拆分(多个模块)。
- 可以是单节点多模块(只在同一个服务器上),情况不多见,也发挥不出微服务的性能优势;
- 通常更多的情况也是多节点多模块(部署在多个服务器上,也就是分布式的架构)。
- 分布式服务架构强调的是服务化以及服务的分散化,而微服务则更强调服务拆分的专业化和精细分工。
- 微服务和分布式的关系是一种演变关系。所以分布式也可以理解为属于微服务的一种,但可能服务拆分没那么细致,而微服务架构通常也是分布式的架构。
- 分布式和中间件的关系可以简单理解为父子关系,分布式是父,中间件是子。
是先有微服务,再有分布式,很简单,因为分布式用到了微服务这种架构模式。- 还有一些问题,我们会在具体的消息队列中间件上讨论,例如如何保证高并发、高性能、高可用。这些都是具体消息中间件相关的。
中间件
中间件、中介层维基:是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。中间件位于客户机服务器的操作系统之上,管理着计算资源和网络通信。
中间件:是一种应用于分布式系统的基础软件,位于应用与操作系统、数据库之间,主要用于解决分布式环境下数据传输、数据访问、应用调度、系统构建和系统集成、流程管理等问题,是分布式环境下支撑应用开发、运行和集成的平台。
- 简单来说:中间件就是一类为应用软件服务的软件,应用软件是为用户服务的,用户不会接触或者使用到中间件。
- 中间件:是指介于两个不同系统之间的软件组件,它可以在两个系统之间传递、处理、转换数据,以达到协同工作的目的。
常见分布式中间件
业界开源的优秀中间件非常多,通常会根据业务的需要在系统中引入若干,下面列举了一些常见的,都是必学的,非可选哈。
- 缓存:Redis(推荐)、Memcached
- 消息队列:Kafka(推荐)、RocketMQ、RabbitMQ、ActiveMQ、ZeroMQ
- 数据库中间件:ShardingSpere、Mycat
框架示意图




用一个请求串起来微服务与分布式系统组件
用户遇到的组件、理论与协议:
- 用户使用Web、APP、SDK,通过HTTP、TCP连接到系统。
- 主 Nignx 反向代理:负载均衡,为了高并发、高可用,一般都是多个节点提供相同的服务。通过负载均衡找到一个集群节点。
- DNS 域名解析
- CDN 内容分发网络
- LVS 四层负载均衡
- 微服务基础组件:
- API 网关:限流、降级、熔断,超时和重试机制,安全&访问控制
- 服务注册与发现:提供服务的节点向一个协调中心注册自己的地址,使用服务的节点去协调中心拉取地址。登录服务鉴权、流量控制。
- 分布式配置中心:
- skywalking 链路追踪
- XXL-Job 任务调度
- Web 应用服务器集群(Tomcat)
- RPC:
- MQ 消息队列集群:生产者怎么讲消息发送给消费者呢,RPC并不是一个很好的选择,因为RPC肯定得指定消息发给谁,但实际的情况是生产者并不清楚、也不关心谁会消费这个消息,这个时候消息队列就出马了。简单来说,生产者只用往消息队列里面发就行了,队列会将消息按主题(topic)分发给关注这个主题的消费者。消息队列起到了异步处理、应用解耦的作用。
- 数据存储:
- Redis 分布式缓存:如果缓存没有命中,那么需要去数据库拉取数据。
- 分布式数据库中间件:MySQL 读写分离、分库分表
- 冷热分离:冷数据:
- 分布式文件存储
- ES 分布式搜素引擎 大数据、ELK 日志
微服务组件
见其他文档。
- 负载均衡
- CDN 内容分发网络
- 业务服务集群:服务组件之间通过Feign来进行http调用,Feign集成Ribbon来实现客户端侧负载均衡。
- API 网关
- 限流、降级、熔断
- 服务注册与发现
- 分布式配置中心
- 分布式链路追踪和监控
- 任务调度中心
- 消息队列
- RPC 框架
- Dubbo 框架
- Spring Cloud
分布式架构
- 分布式事务
-
CAP 定理和 BASE 理论
-
Paxos 算法和 Raft 算法
-
- 分布式 ID
-
分布式 Session:见 Cookie、Session、Token、JWT 文档。
- 分布式锁
分布式搜索引擎、日志
mysql比较擅长存储关系型数据,项目中有需要存储结构性数据的场景,比如存储JSON字符串,这种场景通过mysql来存储显然事不合适的。
一般会采用Elasticsearch或者MangoDB来进行存储,如果业务中需要检索功能,更建议使用Elasticsearch。Elasticsearch支持DSL,有比较丰富查询检索功能,甚至能实现GIS空间检索功能。
分布式搜索引擎:
-
Elasticsearch
-
Solr
分布式日志
分布式日志:在微服务架构中,通过一个组件,比如说订单服务都是多节点分布式部署,每个节点的log日志都是存储在节点本地。如果要查询日志,不能登录到各个节点找到对应的日志信息。一般会引入ELK来做日志收集,和可视化展示查询。
Elasticsearch
分布式全文搜索引擎
分布式文件存储
最后,用户的操作完成之后,用户的数据需要持久化,但数据量很大,大到单个节点无法存储所有数据,那么这个时候就需要分布式存储。
分布式文件存储:将数据进行划分放在不同的节点上,同时,为了防止数据的丢失,每一份数据会保存多分。
- 传统的关系型数据库是单点存储,为了在应用层透明的情况下分库分表,会引用额外的代理层。
- 而对于NoSql,一般天然支持分布式。
常见方案有:
-
MinIO:是一款基于Go语言发开的高性能、分布式的对象存储系统。客户端支持Java、Net、Python、Javacript、Golang语言。
-
FastDFS:阿里,是一个开源的轻量级分布式文件系统,对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
-
OSS:阿里的,收费。
高并发 & 高性能
高并发、高性能、高可用是紧密相关的:都是现在互联网分布式框架设计必须要考虑的因素。三者界限不是十分明显,和分布式也是密切相关的。
根据搜集到的资料和自己的总结,大致分为:
- 提高系统性能,也相当于提高了系统的并发能力;
- 提高并发能力:保证系统能够同时并行处理很多请求。系统不可用的概率降低,相当于提高了可用性。
- 通过高可用:减少停工时间,保证系统在大部分时间(即使发生硬件故障、或系统升级时)都是可用的、可提供服务的。从而保证系统能(间接)提高并发处理。
- 分布式架构设计:
- API 网关:上面讲的是正向方式提升系统QPS,也可以逆向思维做减法,拦截非法请求,将核心能力留给正常业务!
- 服务注册与发现、配置中心、RPC 框架,分布式 ID、锁、事务、缓存等。
性能测试
高并发
高并发(High Con’currency):通过设计保证系统能够同时并行处理很多请求。
如何提高并发能力?
在笔者心中,消息队列,缓存,分库分表是高并发解决方案三剑客。
- 垂直扩展(
Scale Up)增强单机性能:- 硬件性能(优先):例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G。
- 单机架构:例如,使用Cache来减少IO次数,使用异步(消息队列)来增加单机服务吞吐量,使用无锁数据结构来减少响应时间。
- 二者都有一个致命的不足:单机性能总是有极限的。所以互联网高并发终极解决方案还是要依靠水平扩展(分布式架构设计)。
- 水平扩展(
Scale Out):增加服务器数量,就能线性扩充系统性能。通过系统分布式架构设计来管理增加的服务器,具体实现有:- 负载均衡:用于将用户请求(根据负载均衡算法)分配、转发到多台服务器集群;
- 静态资源:CDN 内容分发网络;
- 消息队列:应用解耦、流量削锋填谷、异步处理。
- 数据库架构优化:
- 读写分离:主要是为了将数据库的读操作和写操作分散到不同的数据库节点上。部署多台数据库,主服务器负责写,从服务器负责读,通常一主多从。主从数据库间(通过主从复制)进行数据同步。需要尽量避免主从延迟。
- 分库分表:按照业务划分数据库、按照规则拆分表到不同数据库,垂直拆分列、水平拆分行。用于解决由于库、表数据量过大导致数据库性能持续下降的问题。
- 冷热分离
- 池化技术
- 负载均衡:用于将用户请求(根据负载均衡算法)分配、转发到多台服务器集群;
高性能
高性能(High Performance):就是指程序处理速度快,所占内存少,cpu占用率低。
-
应用性能优化的时候,对于计算密集型和IO密集型还是有很大差别的,需要分开来考虑。
-
增加服务器资源(CPU、内存、服务器数量),绝大部分时候是可以提高应用的并发能力和性能 (前提是应用能够支持多任务并行计算、多服务器分布式计算才行),
怎么提高性能?
- 池化技术:避免创建、销毁、维护太多进程、线程,导致操作系统浪费资源在调度上。
- 消息队列(异步处理):避免因为IO阻塞让CPU闲置,导致CPU的浪费。
- 避免多线程间加锁来保证线程同步,导致并行系统串行化。
- 读写分离、分库分表:避免分布式系统中多服务器的关联,比如:依赖同一个mysql,程序逻辑中使用分布式锁,导致瓶颈在mysql,分布式又变成串行化运算。
负载均衡
见上。
消息队列
见上。
数据库架构优化
读写分离
分库分表
冷热分离
池化技术
复用单个连接无法承载高并发,如果每次请求都新建连接、关闭连接,考虑到TCP的三次握手、四次挥手,有时间开销浪费。
池化技术的核心:是资源的“预分配”和“循环使用”,常用的池化技术有线程池、进程池、对象池、内存池、数据库连接池、协程池。
连接池的几个重要参数:最小连接数、空闲连接数、最大连接数。
高可用
高可用(High Availability):通常是指,一个系统经过专门的设计,从而减少停工时间(不能提供服务的时间),而保持其服务的高度可用性。
- 保证系统在大部分时间(即使发生硬件故障、或系统升级时)都是可用的、可提供服务的。
目标性能举例:
- 很多公司的高可用目标是4个9,也就是99.99%:系统的年停机时间不能超过8.76个小时。
- 6个9的性能:全年停机不能超过31.5秒。
- 百度的搜索首页,是业内公认高可用保障非常出色的系统,甚至人们会通过百度能不能访问来判断“网络的连通性”。
如何保障系统的高可用?
- 架构设计的核心准则是:避免单点、使用集群(即冗余):
- 如果使用单个服务器,一旦意外宕机,将导致服务不可用;
- 如果有冗余备份,一台服务器挂了,还有其他后备服务器能够顶上。如 Redis的主从复制。
- 故障转移:心跳机制监控服务器状态,挂了就进行故障转移、自动切换、修复。
- 灾备和多活:高可用集群单纯是服务的冗余,并没有强调地域。同城、异地灾备和多活实现了地域上的冗余。
- 通过限流、降级、熔断减轻系统的承载压力。
- 超时和重试机制:多次发送相同的请求来避免瞬态故障和偶发性故障。核心思想是:通过消耗服务器的资源来尽可能获得请求(更大概率)被成功处理。
- 灰度发布: 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕。期间如果发现问题,只需要回滚已发布的一部分服务器即可。
- 通过高并发 & 高性能解决并发请求激增最终导致的不可用:比如用负载均衡、消息队列、读写分离、分库分表来实现。
导致系统不可用的情况
- 硬件:硬件故障,如服务器坏掉。
- 软件:网站架构某个重要角色,如 Nginx 或数据库突然不可用。
- 代码中的bug导致内存泄漏或其他问题导致程序挂掉。
- 并发量/用户请求量激增导致整个服务宕掉或部分服务不可用。(高并发 & 高性能)
- 自然灾害或人为破坏、黑客攻击。
冗余思想
冗余设计是保证系统和数据高可用的最常用的手段。
冗余的思想:
- 对于服务来说,就是相同的服务部署多份。如果正在使用的服务突然挂掉的话,系统可以很快切换到备份服务上,大大减少系统的不可用时间,提高系统的可用性。
- 对于数据来说,就是相同的数据备份多份,这样就可以很简单地提高数据的安全性。
冗余思想在高可用系统设计中最典型的应用:
高可用集群
高可用集群(High Availability Cluster,简称 HA Cluster) : 同一份服务部署两份或者多份组成集群,使用负载均衡(根据每个节点的负载情况)将用户请求转发到(集群中)合适的节点上,避免单一服务器的负载压力过大导致性能降低,从而保证服务的高可用。
按是否有中心可以分为:
- 中心化的:如灾备
- 去中心化的:如多活
故障自动切换
故障转移、故障自动切换:当某个节点故障时,实现不可用服务快速、自动地切换到可用服务。整个过程不需要人为干涉。
LVS 负载均衡服务器。
灾备和多活
高可用集群单纯是服务的冗余,并没有强调地域。
同城、异地灾备和多活实现了地域上的冗余。
灾备:备用服务不处理请求。
- (同城)灾备:相同服务部署在同一个城市的不同机房中。可以避免机房出现意外情况,比如停电、火灾。
- 异地灾备:类似于同城灾备,不同的是,相同服务部署在异地的不同机房中(通常距离较远,甚至是在不同的城市或者国家)。比如天津港爆炸,腾讯关闭了天津数据中心,因此造成腾讯视频服务中断。
多活:类似于灾备,不同的是,备用服务可以处理请求、所有站点都是同时在对外提供服务的。这样可以充分利用系统资源,提高系统的并发。
- 同城多活:类似于同城灾备。
- 异地多活 : 类似于同城多活,不同的是,服务部署在异地。
热备份
LVS 负载均衡服务器
隔离
限流、降级、熔断
见微服务。
超时机制
一般超时机制和重试机制二者搭配一起使用。
超时机制:一旦用户的请求超过某个时间得不到响应、就结束此次请求并抛出异常。
- A 调用 B,然后超时是 3s。
原因
为什么要做超时控制?需要举例子说明超时控制没做,会有什么影响;
超时控制的目标:高可用性
超时控制一般是为了两件事:
- 提升用户体验:确保调用者能够在预期的时间内拿到响应,(即便是一个超时响应也好过完全没有响应)。
- 例如 APP 首页等关键 API 上,在链路超时控制里面,设置整条链路的超时时间不超过 100ms。
- 及时释放资源:在超时之后,后续的步骤(正在执行的业务逻辑)应该会被中断掉,并且及时释放资源,例如 TCP 连接,数据库连接之类的。
- 例如在 RPC 调用里面或者数据库查询里面,设置超时时间。
- 如果没有设置的话,可能会导致线程长期阻塞,浪费资源。
计算超时时间
分析:这个问题是指,假如说你要发起一个 RPC 调用,A->B,那么设置多长的超时时间比较好。
答案:计算超时时间可以从两个角度考虑:
- 从用户体验的角度考虑(也是最佳方案):可以要求产品经理给出具体的超时参数,比如说用户能够最多容忍这个接口多长时间内返回数据。
- 一秒内,不管有没有处理完毕、拿到正常的响应,都要明确告诉用户。
- 也就是用户体验上的一个原则:一个坏消息都好过没有消息。
- 否则的话,我们研发人员可以考虑自己确定超时时间。
- 如果是调用一个已有的、已经上线的接口,那么可以利用观测到的响应时间数据,选用 99 线或者 999 线来作为超时时间。
- 但是不能选用中位数或者平均值之类的,因为这意味着有相当多的请求会超时。
- 中位数意味着一半的请求会超时,而平均值则是接近一半的请求会超时。
- 而如果一个接口还没有上线,那么可以考虑压测来确定超时时间(这很像限流的阈值也可以通过压测来确定)。
- 如果连压测也做不了,那么只能通过代码分析来确定超时时间。例如说 B 接口上有三次数据库查询,如果每次数据库查询的时间是 10ms,那么就可以估计 B 数据库查询就要花掉 30ms,再预留一些 buffer,就可以认为调用 B 的超时时间在 100ms。
- 加分:超时时间只是一个建议值,例如说设置了1秒钟作为超时时间,那么可能1.2秒才最终超时。大多数时候,我们并不能预期系统或者中间件的超时处理是很精确的,这主要是源于Java、SQL、C++ 的时间精确度问题(巴拉巴拉)。
- 如果是调用一个已有的、已经上线的接口,那么可以利用观测到的响应时间数据,选用 99 线或者 999 线来作为超时时间。
怎么实现超时控制
分析:一般来说,其实不太会直接问怎么实现(单次调用的)超时控制,一般都是问怎么实现全链路超时控制。
回答也分成两块:
- 和语言相关的进程内超时控制。
- 重点放在全链路超时控制上,核心要解决:
- 计算剩余超时时间;
- 跨进程传递剩余超时时间。

重试机制
重试机制:指的是多次发送相同的请求来避免瞬态故障和偶发性故障。核心思想是:通过消耗服务器的资源来尽可能获得请求(更大概率)被成功处理。
- 瞬态故障:可以理解为某一瞬间系统出现的故障,并不会持久。
- 偶然性故障:可以理解为在某些情况下偶尔出现的故障,频率通常较低。
由于二者是很少发生的,因此,重试对于服务器的资源消耗几乎是可以被忽略的。
超时和重试机制在实际项目中使用的话,需要注意保证重试幂等性(同一个请求没有被多次执行)。
见消息队列中消息重复消费的解决方法。