分布式架构设计的特征与问题
软件架构的更迭:单体架构 - 集群架构 - 分布式/微服务架构。
需要解决的问题:
怎么拆分服务?
分散的服务合作时怎么协同和通信?
存储数据库怎么拆分?
如何管理和调度硬件资源?
如何监控系统的关键指标?
架构设计的演进过程
业务驱动技术发展!
架构设计追求的目标:高并发、高可用、可伸缩、可扩展、够安全。
应用与数据一体模式
单体服务器或者托管模式(一个服务器多个应用,服务器宕机则所有应用瘫痪)。
LAMP 技术:Linux + Apache + MySQL + PHP,开发部署成本低,适合起步应用。
应用一个进程,数据库一个进程,两个进程部署在一个服务器上,进程间直接通信,非常简单。
应用与数据分离模式
应用与数据分开部署,一定程度上提升性能:
应用服务器优先考虑 CPU 和内存
数据库服务器优先考虑磁盘转速和内存
缓存与性能的提升
对热点数据的频繁读取使得数据库 IO 性能成为整个应用的瓶颈。解决办法:缓存技术。
基本的缓存技术:
客户端浏览器缓存:缓存请求
应用服务器本地缓存:进程缓存,托管堆缓存
缓存服务器缓存:进程外缓存,考虑使用大内存节点作为专用的缓存服务器
缓存数据一般位于内存中,数据库数据在磁盘上,所以读取缓存数据一般要快得多。
服务器集群处理并发
架构演进的核心目标:提高系统的性能和可用性。
并发问题:大量用户同时请求应用服务器。
解决办法:搞多个应用服务器,分担压力。即应用集群。
新的问题:怎么将大量请求分配到应用集群中去?解决办法:加负载均衡器。
负载均衡器使用特定的均衡算法(例如轮询、加权轮询等)将用户请求分发给不同的应用服务器:
请求尽量均匀分步到各个应用服务器上
同一个会话的所有请求分发到同一个服务器上
不同服务器的性能不一样时,动态调整流量
可以监控用户流量,因为用户请求都会首先到达负载均衡器
可以对请求的用户身份和权限进行验证
数据库读写分离
数据库本身读和写的性能是不一样的:
写数据需要锁行或者锁表,大量写数据时需要排队
读数据没有上述限制,还可以通过索引、缓存等技术加速
应用本身对数据库的读写需求也不一样:大部分应用读数据的场景远远多于写数据。
针对此问题,可考虑数据库的读写分离:
主数据库(master)用来写入数据,写入后通过同步 binlog 的方式将数据同步到从库中
从数据库(slave)用来读取数据
二进制日志(binlog)是 MySQL 中一种记录数据更改操作的日志,用于数据库备份、恢复、错误排查、数据同步等,例如主从复制(Master-Slave Replication)。
一主多从的结构可以大大提升读取效率。
主从数据库如何同步:同步复制、异步复制、半同步复制。
分散了就需要考虑可靠性问题:
主库挂了怎么办?可以投票让一个从库成为主库!
主库又恢复了怎么办?可以重新成为主库,或者成为一个普通的从库!
……
反向代理和 CDN
反向代理服务器位于负载均衡器之前,它直接接收用户请求,然后将其转发给负载均衡器。
反向代理服务器的作用:
更安全:保护应用服务器,用户请求通过反代服务器转发,对反代的攻击不会影响应用服务器
在互联网和内网之间起到网速转换的作用,屏蔽网络差异(没太理解这个作用)
内容分发网络(CDN, Content Delivery Network)可以就近(物理距离)向用户提供内容,一般存放静态数据。
浏览器能从 CDN 拿到的资源就不会发请求到应用服务器(反代服务器收不到该请求!),除非 CDN 也没有。
CDN 方式更新麻烦一点:需要同时更新应用服务器和 CDN 服务器。
分布式数据库与分表分库
系统长时间运行数据库会出现的问题:数据量增大、过程数据变多(操作数据和日志数据等)。
单表过大时可以考虑按行切割,或者按列切割(将不常用的列单独建表通过外键关联)。
不同的业务或者服务使用不同的数据库。
数据库一旦分库分表,对应用服务来说,读写数据库就变得困难,这时需要加入一个数据库中间件。
数据库中间件负责消除应用服务对数据库分布特性的感知,对于应用服务来说,数据库的读写接口没有任务变化!
分库意味着存在多个数据库服务器,此时还需要数据库中间件解决多个数据库服务之间的数据同步问题。
经典的数据库中间件:MyCat, Sharding JDBC。
分布式数据库引入的其他问题:
管理:可用性?
治理:如何扩容?
……
业务拆分
在微服务架构出现之前,我们通过应用服务器集群的模式将应用服务器复制多份以提升性能。
当应用本身变得庞大和复杂时,可以考虑将应用本身按照业务和服务进一步拆分,即多个微服务。
微服务之间需要协同和通信,此时需要引入队列、服务注册发现、消息中心等组件进行协调。
拆分后的服务大致可以分为:业务相关的服务,基础服务(例如消息推送、权限验证)。
应用微服务连同多个分布式的数据库服务一起,组成了完整的应用系统。
如何解决这些微服务之间的通信、协同、管理、监控问题?!
分布式与微服务
微服务架构将整个应用系统拆分成很小的业务模块,每个模块具有高内聚低耦合的特点。
每个业务模块内部可以使用独立的技术栈实现,即技术异构性。
模块之间通过协议和接口进行调用。
热点模块还可以水平扩展,提升可靠性和性能。
微服务架构的特定:业务精细化拆分、自治性、技术异构性、高性能、高可用。
微服务架构与分布式架构看起来很相似,都是拆,但是有所不同。
拆分目的不同:分布式架构解决的是单体服务器资源受限的问题,所以将不同技术栈的应用(如静态资源服务、API服务、数据库服务、缓存服务器等)拆开部署到不同的服务器上,从而降低高并发的压力。微服务架构则对服务内部的组件模块更加精细化的拆分解耦,进一步提高性能、可用性、伸缩性和扩展性。
拆分方式不同:分布式架构按照技术栈拆分,微服务架构在分布式的基础上进一步拆分,更细。
部署方式不同:分布式解决单体服务器性能受限的问题,拆完的服务一般会部署到不同的服务器上。微服务架构主要侧重于业务功能的拆分,并没有很明显的说要把微服务部署到不同的服务器上。
微服务架构是分布式架构的进阶版本,它们都面临如何拆分、融合协同和通信、如何管理调度多个服务和计算资源的问题。
一个简单的例子:分布式架构的组成
软件架构的演进方向:高性能、高可用、可扩展、可伸缩、够安全。
示例业务流程:浏览商品 -> 下单 -> 付款 -> 扣减库存 -> 通知用户。
架构概述与分层
客户端:客户端本地缓存 + CDN 提升用户体验。
接入层:负载均衡器。负载均衡、限流、身份验证……
应用层:请求进入应用层时首先进入 API 网关,即应用层面的负载均衡器。
API 网关负责将请求路由到不同的微服务进行处理。
微服务结构中需要服务注册发现组件、消息中心组件等辅助微服务间的通讯问题。
缓存服务可使用独立的大内存缓存服务器缓存应用数据。
存储层:数据库。分库分表、主从复制等。
应用层
API 网关
微服务架构中会将应用服务拆分的很细,热点服务还会水平扩展。
负载均衡器的请求进来之后哪个微服务来处理请求?需要一个中介在应用内部分配——API 网关。
如果处理请求的微服务存在水平扩展,该交给哪一个微服务?需要 API 网关做负载均衡!
敏感请求还需要 API 网关进行用户身份的鉴权。
两个服务之间因为接口协议不一致时,也可以由 API 网关进行协议转换。
因为有负载均衡的作用,当然也可以限流。
因为是单节点应用的出入口,可以记录日志。
实际上就是一个单节点应用对外提供服务的门户,与负载均衡器是一样的。
负载均衡器的分发对象是不同的应用服务器,API 网关分发的对象是单个应用服务器上不同的微服务。
服务协同与通信
用户通过订单服务下单时,也需要调用支付服务完成支付。此时涉及到两个服务之间怎么协同合作的问题。
单体服务器中只有一个应用进程,非常简单。微服务架构中,跨进程甚至跨服务器调用,相对麻烦。
此时需要一个服务注册发现中心组件辅助微服务间的通信问题。
支付服务到服务注册中心注册自己
订单服务从注册中心获取可用的服务列表
订单服务在获取到的服务列表中找到支付服务的地址,完成调用
支付完成之后,调用消息服务发消息给用户(这个过程是异步的)
消息服务一般与业务无关,是通用服务,考虑单独部署。
分布式互斥
用户下单时需要扣减库存,两个用户同时下单时需要同时扣减库存。
此时库存就是临界资源,扣减库存的操作就是竞态。
进程内表现为两个线程(一个用户请求即一个线程)争夺库存资源,解决办法是加线程锁。
微服务结构下,对临界资源的竞态可能发生在两个服务(进程)之间,此时属于跨进程的竞态。
ZooKeeper 等工具可以解决微服务架构下跨进程的竞态问题,使用独立的跨进程锁。
分布式事务
下订单和扣减库存应该需要同时成功,不能说一个用户下订单成功了但是没库存。
由于订单服务和库存服务是两个服务进程,但是它们逻辑上又是一个原子事务(要么都成功,要么都失败)。
这种事务就是分布式事务。
处理分布式事务需要一个额外的事务协调器。2PC, ACID, CAP, TCC, ...
存储层
分布式存储
数据库一旦分库分表,其读写会变得两队复杂,此时需要一个数据库中间件来帮助读写,如 MyCat。
分表可以按行分,也可以按列分,获取按其他规则分,这些都能通过中间件自动完成。
对于应用服务来说,数据库逻辑上还是一个单体服务,应用只和数据库中间件打交道。
读写分离与主从同步
数据库中间件管理分布式的数据库服务,支持读写分离模式。
数据库中间件会自动处理分布式带来的一系列问题:主库挂了怎么办?选举!
分布式架构的特征
分布性:对服务、存储的拆分和部署。
自治性:
每个应用服务对自己内部的资源拥有独立的支配权。
不同的服务可以使用独立的技术栈实现,对外通过服务注册中心、消息队列或者 API 提供服务。
分布式的特性天然导致自治性。
并行性:自治性使得服务之间解耦,服务可以大量并发。
全局性:分散的服务需要通过合作和沟通共同完成一个事务。
分布式架构的问题
分布式架构需要解决的八个问题:
拆分:怎么拆分?
调用:拆完了怎么调用?
协同:一个事务涉及多个服务时怎么协同?
计算:协同时如果需要大量计算怎么并行?
存储:算完了的结果怎么保存?
调度:在处理事务的过程中怎么管理度调度分布式资源?
高性能与可用性:如何利用缓存、限流、降级等手段进一步提升性能和可用性?
指标与监控:如何监控系统的性能指标?
🥝 1. 应用服务拆分
定义业务的边界是划分应用服务的关键:先划分业务,再针对划分后的业务进行技术实现!
领域驱动设计(DDD, Domain-Driven Design)是一种专注于复杂领域的设计思想,围绕业务概念构建领域模型,并复杂的业务进行分隔,再将分隔出的业务与代码实践做映射。总结:帮助我们找业务边界!
🥝 2. 分布式调用
调用的两个关键:
主调方怎么找到被调方?
用户请求怎么找到应用服务器?通过负载均衡器
来自负载均衡器的请求怎么找到处理请求的服务?通过 API 网关
一个服务怎么调用另一个服务?通过服务注册发现或者消息队列
调用时如何传递信息?各种通信协议:HTTP, RPC, RMI, NIO, ...
🥝 3. 分布式协同
互斥问题
分布式锁的实现
分布式事务的解决方案
分布式选举算法
经典工具 ZooKeeper 分布式系统的最佳实践
🥝 4. 分布式计算
两种计算模式:
MapReduce 模式:针对批量静态数据
Stream 模式:针对动态数据流
🥝 5. 分布式存储
分布式存储
分布式关系数据库
分布式缓存
🥝 6. 分布式资源管理与调度
资源划分和调度策略时如何工作的
分布式调度架构:K8S, ...
中心化调度:管理节点
两级调度
共享状态调度
K8S
🥝 7. 高性能与可用性
高性能和可用性是分布式架构的目的。
通过缓存提高性能:HTTP缓存、CDN缓存、负载均衡缓存、进程内缓存、分布式缓存、……
通过限流、降级、熔断等手段保证系统的高可用性
🥝 8. 指标与监控
仍然从性能和可用性两个角度来评估架构的好坏。
性能指标:延迟、流量、错误、饱和度……
分布式监控系统及流行工具的最佳实践
评论区