系统架构演变
为什么系统架构需要演进?
- 互联网的爆炸性发展
- 硬件设施的快速发展
- 需求复杂性的多样化
- 开发人员的急剧增加
- 计算机理论及技术的发展
单体架构
垂直应用架构
分布式架构
SOA架构
微服务架构
核心要素:服务治理(服务注册、负载均衡等)、可观测性(日志分析、异常报警等)、安全(身份认证、访问令牌、传输加密等)。
微服务架构的原理及特征
基本概念
- 服务: 一组具有相同逻辑的运行实体
- 实例: 一个服务中,每个实体即为一个实例
- 单体服务,一般是简单的函数调用,发送http协议
- 微服务间的通信是网络传输, 多数采用RPC(Thrift, gRPC)
Eureka注册中心
- EurekaServer:服务端,注册中心
-
- 记录服务信息
- 心跳监控
- EurekaClient:客户端
-
- Provider:服务提供者。例如案例中的 user-service
-
-
- 注册自己的信息到 EurekaServer
- 每隔 30 秒向 EurekaServer 发送心跳
-
-
- consumer:服务消费者。例如案例中的 order-service
-
-
- 根据服务名称从 EurekaServer 拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
-
问题 1:order-service 如何得知 user-service 实例地址?
user-service 服务实例启动后,将自己的信息注册到 eureka-server(Eureka 服务端)。即服务注册
eureka-server 保存服务名称到服务实例地址列表的映射关系
order-service 根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取。
问题 2:order-service 如何从多个 user-service 实例中选择具体的实例?
order-service 从实例列表中利用负载均衡算法选中一个实例地址
向该实例地址发起远程调用
问题 3:order-service 如何得知某个 user-service 实例是否依然健康,是不是已经宕机?
user-service 会每隔一段时间(默认 30 秒)向 eureka-server 发起请求,报告自己状态,称为心跳
当超过一定时间没有发送心跳时,eureka-server 会认为微服务实例故障,将该实例从服务列表中剔除
order-service 拉取服务时,就能将故障实例排除了
Nacos 注册中心
startup.cmd -m standalone
- Nacos 与 eureka 的共同点
-
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
Nacos 与 Eureka 的区别
- Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos 集群默认采用 AP 方式;当集群中存在非临时实例时,采用 CP 模式。而 Eureka 集群采用 AP 方式
核心服务治理功能
服务发布
- 概念: 让一个服务升级运行新的代码的过程
- 服务发布难点
- 服务不可用
- 服务抖动
- 服务回滚
- 蓝绿部署
- 将服务分成两个部分,分别先后发布
- 简单、稳定
- 但需要两倍资源(通常在服务器人数少的时候使用此方式)
- 灰度发布(金丝雀发布)
- 先发布少部分实例,接着逐步增加发布比例
- 不需要增加资源
- 回滚难度大,基础设施要求高
流量治理
- 流量控制
- 在微服务架构中,可以从各个维度对端到端的流量在链路上进行精确控制
- 控制维度
- 地区维度
- 集群维度
- 实例维度
- 请求维度
负载均衡
常见的LB策略
- Round Robin
- Ring Hash
- Random
Ribbon 负载均衡
负载均衡的实现(以Java项目举例)
基本流程如下:
-
- 拦截 RestTemplate 请求 http://user-service/user/1
- RibbonLoadBalancerClient 会从请求 url 中获取服务名称,也就是 user-service
- DynamicServerListLoadBalancer 根据 user-service 到 eureka 拉取服务列表
- eureka 返回列表,localhost:8081、localhost:8082
- IRule 利用内置负载均衡规则,从列表中选择一个,例如 localhost:8081
- RibbonLoadBalancerClient 修改请求地址,用 localhost:8081 替代 userservice,得到 http://localhost:8081/user/1,发起真实请求
负载均衡策略
1.默认的实现就是 ZoneAvoidanceRule,是一种轮询方案
2.RandomRule 随机选择一个可用的服务器
自定义负载均衡策略
通过定义 IRule 实现可以修改负载均衡规则,有两种方式:代码的方式和配置文件的方式。
/**
* 自定义负载均衡规则:此处设置为随机
*
* @return
*/
@Bean
public IRule randomRule() {
return new RandomRule();
}
// 构建一个随机负载均衡算法
type RandomRule struct {
}
// 实现IRule接口的Choose方法
func (r *RandomRule) Choose(servers []*Server) *Server {
if len(servers) == 0 {
return nil
}
// 生成随机数
rand.Seed(time.Now().UnixNano())
index := rand.Intn(len(servers))
return servers[index]
}
// 创建一个随机负载均衡算法的工厂方法
func NewRandomRule() IRule {
return &RandomRule{}
}
// main函数中使用随机负载均衡算法
func main() {
// 创建服务列表
servers := []*Server{
{Address: "192.168.0.1", Port: 8080},
{Address: "192.168.0.2", Port: 8080},
{Address: "192.168.0.3", Port: 8080},
}
// 创建负载均衡器
lb := LoadBalancer{Rule: NewRandomRule()}
// 循环调用Choose方法选择服务器
for i := 0; i < 10; i++ {
server := lb.Choose(servers)
fmt.Printf("第%d次选择的服务器为:%s:%d\n", i+1, server.Address, server.Port)
}
}
user-service: # 给某个微服务配置负载均衡规则
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则:此处设置为随机
package main
import (
"fmt"
"github.com/afex/hystrix-go/hystrix"
"github.com/afex/hystrix-go/plugins"
"github.com/afex/hystrix-go/hystrix/metric_collector"
"github.com/afex/hystrix-go/hystrix/rolling"
)
func main() {
hystrix.ConfigureCommand("user-service", hystrix.CommandConfig{
Timeout: 1000, // 超时时间(单位:毫秒)
MaxConcurrentRequests: 100, // 最大并发请求数
SleepWindow: 5000, // 隔离策略触发后,在该时间窗口内禁止请求
RequestVolumeThreshold: 10, // 触发熔断的最小请求数
ErrorPercentThreshold: 50, // 触发熔断的错误百分比阈值
})
// 注册自定义的熔断指标收集器,如 PrometheusCollector、StatsdCollector 等
metricCollector.Registry.Register(plugin.PrometheusCollector{})
metricRegistry := metricCollector.Registry.(*metricCollector.Registry)
// 自定义熔断指标的滚动窗口大小、时间跨度、时间刷新间隔
rolling.StatsPoller.TickDuration = 1
rolling.StatsPoller.FlushDuration = 2000
rolling.StatInterval = 2000
// 输出自定义熔断指标的报告
go func() {
for {
fmt.Println(metricRegistry.Get("user-service", "requests"))
fmt.Println(metricRegistry.Get("user-service", "successes"))
fmt.Println(metricRegistry.Get("user-service", "failures"))
fmt.Println(metricRegistry.Get("user-service", "timeouts"))
fmt.Println(metricRegistry.Get("user-service", "rejections"))
fmt.Println(metricRegistry.Get("user-service", "semaphore_rejected"))
fmt.Println(metricRegistry.Get("user-service", "short_circuited"))
fmt.Println(metricRegistry.Get("user-service", "fallback_successes"))
fmt.Println(metricRegistry.Get("user-service", "fallback_failures"))
fmt.Println(metricRegistry.Get("user-service", "total_duration"))
}
}()
// 执行用户服务请求
for i := 0; i < 100; i++ {
shystrix.Go("user-service", func() error {
// 这里可以是用户服务的具体调用代码
// ...
return nil
}, nil)
}
}
这段代码使用了 afex/hystrix-go 库来实现断路器(Circuit Breaker)的功能。断路器是用于提高系统稳定性和可用性的一种设计模式,当服务出现故障或者响应过慢时,断路器会中断该服务的调用,防止故障的传播,从而保护系统。
稳定性治理
- 限流 : 限制服务处理的最大 QPS,拒绝过多请求
- 熔断 : 中断请求路径,增加冷却时间从而让故障实例尝试恢复
- 过载保护 : 在负载高的实例中,主动拒绝一部分请求,防止实例被打挂
- 降级 : 服务处理能力不足时,拒绝低级别的请求,只响应线上高优请求
小结
微服务中还有许多内容需要更详细的了解,比如Redis,MQ, Docker等一系列中间件。微服务架构适用于大型应用程序和复杂业务场景,因为它可以提高开发效率、可维护性强和扩展性强等一系列优点。但是,微服务架构也有一些挑战,比如,微服务架构将应用程序拆分为多个独立的微服务,每个微服务都需要进行单独的部署、监控和管理。这增加了系统的复杂性和管理的难度,可能需要更多的开发人员和运维人员来维护和管理整个系统。微服务通常都有自己的数据库,这也使得在进行数据一致性和事务管理时变得更加困难。因此,在采用微服务架构之前,需要仔细考虑其优缺点。