分布式微服务架构——一整套微服务组件架构的解决方案
SpringCloud——一系列微服务技术的组合和解决方案
SpringCloud升级后主要的组件选择方案
服务注册中心
Eureka
服务注册中心,应用架构图
Eureka Server
即注册中心
1 | <dependency> |
1 |
|
1 | server: |
多台eureka server之间需要相互注册,启动之后可以用ip+端口来访问对应注册中心结点的控制中心
Eureka Client
1 | <dependency> |
1 |
|
1 |
|
1 | server: |
调用方
1 | <!--调用方仍然属于eureka-server的客户端--> |
1 |
|
1 |
|
1 |
|
1 | server: |
Eureka自我保护机制
Eureka服务端会检查最近15分钟内所有Eureka 实例正常心跳占比,如果低于85%就会触发自我保护机制。触发了保护机制,Eureka将暂时把这些失效的服务保护起来,不让其过期,但这些服务也并不是永远不会过期。Eureka在启动完成后,每隔60秒会检查一次服务健康状态,如果这些被保护起来失效的服务过一段时间后(默认90秒)还是没有恢复,就会把这些服务剔除。如果在此期间服务恢复了并且实例心跳占比高于85%时,就会自动关闭自我保护机制。
配置调整
eureka server
1
2
3
4
5
6eureka:
server:
#关闭自我保护机制,保证不可用服务立即被踢出
enable-self-preservation: false
#自我保护机制下的检查时间间隔,2000表示2秒,默认90s
eviction-interval-timer-in-ms: 2000eureka client
这里的两项配置需要和eureka server的配置相对应,否则可能会自动失效
1
2
3
4
5
6eureka:
instance:
#服务续约时间,单位s,默认30s
lease-renewal-interval-in-seconds: 1
#服务失效时间,单位s,默认90s
lease-expiration-duration-in-seconds: 2
ZooKeeper
安装Zookeeper
1 | wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz |
到解压后目录下的bin目录中复制配置文件
1 | cp zoo_sample.cfg zoo.cfg #复制之后zookeeper默认会以zoo.cfg为配置文件进行启动 |
启动
1 | zkServer.sh start #启动zookeeper |
Zookeeper使用者
调用者和服务提供者都需要引入依赖
1 | <!--注意maven版本和zookeeper服务器版本对应问题--> |
1 | spring: |
使用方法与Eureka类似,使用RestTemplate进行Http请求调用
Consul
默认使用端口8500
1 | consul --version #检查consul版本 |
启动consul之后可以打开对应的http://ip:8500/
访问网页控制面板
Consul使用者
CAP
- Consistency:强一致性
- Availability:可用性
- Partition Tolerance:分区容错性
由于分布式架构都要保证分区容错性,因次只能再CA之中二选一
Eureka AP的体现:Eureka自我保护机制,当服务结点发送心跳请求中断时暂时不删除结点,宁可容忍节点状态的错误可能也要保证可用性
Zookeeper和Consul:结点掉线立刻删除,不保证可用性,而保证节点状态的正确性
小坑
使用Zookeeper和Consul时,如果使用RestTemplate作为调用方式,在注入RestTemplate时必须添加@LoadBalanced
注解,否则URL无法被正确解析
服务调用
Ribben + RestTemplate
Ribben作为本地的负载均衡组件工作方式:
消费者端向注册中心获取到所有可用的服务端,根据本地自身设定的负载均衡策略选择某一个服务端进行调用
与Nginx的区别
nginx是服务端的负载均衡,而ribben是客户端的负载均衡;nginx获取到请求自行选择服务端进行处理(反向代理),ribben则是客户端自行进行选择
依赖
1 | <dependency> |
然而使用eureka时,eureka的依赖已经自动帮我们引入了ribbon依赖,不需要再次手动去引入
Ribben负载均衡策略
RoundRobinRule轮询(默认)
RandomRule随机
RetryRule轮询重试(轮询获取服务失败则重试)
WeightedResponseTimeRule响应速度决定权重
BestAvailableRule最优可用(并发数最小)
AvailabilityFilteringRule可用性过滤规则(先过滤不可用的server实例,再选择并发数最小的实例)
ZoneAvoidanceRule区域内可用性能最优(基于AvailabilityFilteringRule,过滤server实例的同时需要判断server所在区域的性能)
更改负载均衡配置
自定义配置类
1 | //注意,如果只想让某一个springboot应用使用该规则,不能放在主启动类的目录和子目录下(@ComponentScan注解所扫描的范围) |
主启动类上添加注解,指定自定义规则
1 |
使用依旧是Ribben配置 + RestTemplate发送Http请求
OpenFeign
不同于之前的RestTemplate调用接口,Feign使用方式有点类似于Dubbo,通过接口调用来实现微服务之间的交互(服务接口绑定器)
OpenFeign的依赖中也包含了Ribben,因此也自然具备负载均衡功能,同样是客户端进行使用
依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>主启动类添加注解
@EnableFeignClients
服务调用这编写Service调用接口
1
2
3
4
5
6
7
8
9
//指定对应的服务名
public interface PaymentFeignService {
CommonResult<Payment> selectOne( Long id);
String getFeignTimeOut();
}Feign会通过
@FeignClient
和@Requestmapping
注解去注册中心获取到具体的服务提供者并进行调用配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19server:
port: 80
eureka:
client:
service-url:
defaultZone: http://www.eureka7002.com:7002/eureka/,http://www.eureka7001.com:7001/eureka/
register-with-eureka: false
feign:
client:
config:
default:
# 超时等待时间
ReadTimeout: 5000
# 连接等待时间
ConnectTimeout: 5000
# 启用日志
loggerLevel: FULL
服务降级熔断
Hystrix
Hystrix是一个用于处理分布式系统延迟和容错的开源库,在分布式系统的调用依赖中,Hystrix保证在一个依赖出现问题的情况下不会导致整体服务的失败,避免级联故障,提高分布式系统的弹性
当某个服务单元发生故障后,通过断路器的故障监控,向调用方返回符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或抛出调用方无法处理的异常,保证服务调用方的线程不被长时间不必要的占用,避免故障在分布式系统中的蔓延乃至雪崩
主要功能
服务降级:保证重要或整体服务的正常运行,将非重要服务延迟或暂停使用(FallBack)
服务熔断:通过断路器做代理进行访问,断路器会持续观察服务返回的成功、失败的状态,当失败超过设置的阈值时断路器打开,请求就不能真正地访问到服务了
服务限流
接近实时的监控
启用Hystrix
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
1
2
3
4
5
6
7
8
9
+ 客户端和服务端都可以启用Hystrix,主启动类添加注解@EnableHystrix
+ 如果使用的是Feign + Hystrix的组合,需要添加配置
```yaml
feign:
hystrix:
enabled: true #在feign中开启hystrix
服务降级
使用方法:
单方法指定降级@HystrixCommand
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String paymentInfo_ERROR( Integer id){
//业务逻辑
return null;
}
//fallBack方法,注意入参出参必须与上面调用的原方法完全一致
//这里运行时会使用Hystrix专用的线程,而不是原方法的Tomcat线程
public String paymentInfo_ERROR_Handler(Integer id){
return "80____paymentInfo_ERROR_Handler___异常返回";
}类方法统一指定@DefaultProperties
需要使用服务降级的方法添加@HystrixCommand,否则对应方法不会启用降级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//在类名上使用注解统一指定
public class OrderController {
//使用OpenFeign实现的接口
private PaymentHystrixService paymentHystrixService;
public String paymentInfo_OK( Integer id){
String res = paymentHystrixService.paymentInfo_OK(id);
System.out.println(res);
return res;
};
public String paymentInfo_ERROR( Integer id){
return paymentHystrixService.paymentInfo_ERROR(id);
}
public String waitForBack() {
return paymentHystrixService.justWait();
}
//这里是因为方法入参不同写了两个fallBack方法,入参相同则可以共用fallBack
public String paymentInfo_ERROR_Handler(Integer id){
return "80____paymentInfo_ERROR_Handler___异常返回";
}
public String paymentInfo_ERROR_Handler(){
return "80____paymentInfo_ERROR_Handler___异常返回";
}
}面向接口方法的降级措施
1
2
3
4
5
6
7
8
9
10
11
12
13
//指定对应的fallBack实现类
public interface PaymentHystrixService {
String paymentInfo_OK( Integer id);
String paymentInfo_ERROR( Integer id);
String justWait();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//实现对应的接口,根据方法名对应处理fallBack
public class PaymentFallbackHystrixService implements PaymentHystrixService {
public String paymentInfo_OK(Integer id) {
return "-------PaymentFallbackService fall back-paymentInfo_OK,o(╥﹏╥)o";
}
public String paymentInfo_ERROR(Integer id) {
return "-------PaymentFallbackService fall back-paymentInfo_TimeOut,o(╥﹏╥)o";
}
public String justWait() {
return null;
}
}
服务熔断
请求降级 -> 请求熔断 -> 超时重试
这里的closed和open类似于电路中的开关闭合,closed表示断路器闭合,正常使用;open表示断路器断开,不能正常响应请求
![TR$BO9@M$N`%D7O}8BI@J(K.png](https://cdn.jsdelivr.net/gh/lan5th/pics/blog_images/20220719165938.png)
在sleepWindowInMilliseconds
的时间之内,超过requestVolumeThreshold
个请求进行了调用,失败率超过errorThresholdPercentage
%,则断路器会从closed变成open,一段时间后(默认5s),断路器从open变为halfOpen状态,对于大部分新请求直接进行fallback响应,对于某个请求进行访问尝试,如果请求仍然响应失败,则继续变为open状态,并重试上一步
断路器是在服务端进行配置的
单独方法上@HystrixCommand
1
2
3
4
5
6
7统一配置@DefaultProperties + @HystrixCommand
1
2
3
4
5
6
Hystrix仪表盘
新建模块,依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>配置文件
1
2
3
4
5
6server:
port: 9001
hystrix:
dashboard:
proxy-stream-allow-list: "*"主启动类添加注解
@EnableHystrixDashboard
被监控的服务端需要注入ServletRegistrationBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目里配置下面的Servlet就可以了
* @return
*/
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}访问监控url:
http://ip:port/hystrix
![$9GL)MSDVA)6TH`]_O6P4NA.png](https://cdn.jsdelivr.net/gh/lan5th/pics/blog_images/20220719183348.png)
输入被监控的url:
http://localhost:8001/hystrix.stream
进入监控页面如果显示loading,需要客户端发送调用请求开始监控
网关
Gateway
核心逻辑:路由转发+过滤处理
Route(路由)
Predicate(断言)
Filter(过滤)
1 | <dependency> |
1 | server: |
路由+断言
配置文件方式
1
2
3
4
5
6
7
8
9
10
11
12spring:
cloud:
gateway:
routes:
- id: payment_routh
uri: lb://cloud-payment-service #lb:开头会使用负载均衡模式
predicates: #断言,满足条件才会进入路由
- Path=/payment/get/**
- id: payment_routh2 #可以配置多个路由
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/**配置类方式
1
2
3
4
5
6
7
8
9
10
11
public class GateWayConfig {
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route",
r->r.path("/guonei") //断言
.uri("https://www.baidu.com")); //路由
return routes.build();
}
}
过滤
一般用于添加全局日志等,权限鉴定等
1 |
|
配置中心
SpringCloud Config
结构:github配置仓库 + 配置服务端 + 配置客户端
启用
新建github仓库,添加配置文件
配置服务端
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27server:
port: 3344
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
ip-address: 127.0.0.1
prefer-ip-address: true
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
# Jgit低版本读取rsa秘钥有bug,只能用http方式
# uri: git@gitee.com:lan5th/springcloud-config.git
uri: https://gitee.com/lan5th/springcloud-config.git
search-paths:
- springcloud-config
force-pull: true
username: XXX #认证信息
password: XXX
label: master主启动类只需要添加@SpringBootApplication和@EnableConfigServer注解,作为springboot应用启动即可
启动完成后可以访问url访问获取到的各个配置文件内容
http://config-3344.com:3344/master/config-dev.yml
当github仓库中的配置文件被修改之后,配置服务端将自动拉取最新的配置文件,访问url得到的内容也随之更新
配置客户端
1
2
3
4
5
6
7
8<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>cy>bootstrap.yml,springApplication优先加载bootstrap,然后加载application,并和从配置服务端获取到的配置信息合并称为最终的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml
uri: http://config-3344.com:3344 #配置中心的地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
# 暴露actuator接口,用于刷新配置
management:
endpoints:
web:
exposure:
include: "*"需要用到的Controller
1
2
3
4
5
6
7
8
9
10
11
12
//刷新作用域
public class ConfigController {
private String configInfo;
public String getConfigInfo() {
return configInfo;
}
}
半自动刷新
配置服务客户端必须引入spring-boot-starter-actuator依赖,并在yaml中开启actuator接口
在更新github仓库中的配置文件后,配置服务端会自动拉取最新的配置文件,而配置客户端需要接收一个刷新的post请求才能刷新配置
1 | ip:port/actuator/refresh |
小坑
git仓库必须是public,private仓库会无法访问
低版本Jgit有bug,不能正确读取到生成的rsa文件,导致ssh方式报错Auth fail,只能使用Http方式进行配置
先确认本地terminal能否正常clone远程仓库,正确无误之后再启动配置中心
消息总线
SpringCloud Bus
一般与SpringCloud Config共同使用,用于实现配置的自动更新
需要与MQ搭配使用,这里以RabbitMQ为例
配置步骤
所有需要用到总线的微服务添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>cy>配置服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37server:
port: 3344
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
ip-address: 127.0.0.1
prefer-ip-address: true
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://gitee.com/lan5th/springcloud-config.git
search-paths:
- springcloud-config
force-pull: true
username: lan5th
password: Zyh20010605
label: master
rabbitmq:
host: XX.XX.XX.XX
port: 5672
username: user
password: password
#rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'配置客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml
uri: http://config-3344.com:3344 #配置中心的地址
rabbitmq:
host: XX.XX.XX.XX
port: 5672
username: user
password: password
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/配置RabbitMQ,这里注册的user和password就是上面所需填写的user
启动配置服务端,在RabbitMQ的管理界面看到springCloudBus即表示配置服务端连接成功
配置完成后,每当在gitee/github上更新配置文件后,配置服务端自动加载新配置文件,我们只需要手动向配置服务端发送POST请求,即可通过消息总线让所有的监听总线的微服务自动获取新的配置文件
1 localhost:3344/actuator/bus-refresh #高版本为busrefresh
SpringCloud Stream
为了适配底层不同MQ之间的差异,降低切换开发成本,统一的消息编程模型
暂时仅支持以下两种MQ:
ActiveMQ
RabbitMQ
通过Binder对象实现与底层消息中间件的交互,我们只需要操作Binder对象即可
这里不加以详细介绍
链路监控
Sleuth + zipkin
原理
各个微服务span通过parentId在链路中依次关联
TraceId:一次调用链路的唯一id
SpanId:各个微服务抽象成span,获得在链路中的微服务唯一id
ParentId:上一级调用的SpanId
使用
下载并启动zipkin
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/
直接使用
java -jar
启动即可,之后可以在9411端口看到管理界面依赖
1
2
3
4
5<!--包含了sleuth + zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>spring: zipkin: # 注册到的zipkin监控服务器 base-url: http://localhost:9411 sleuth: sampler: # 取样率在0~1之间,1表示全部取样 probability: 1
配置好之后全部启动即可在zipkin的监控页面根据不同条件查找到对应的请求记录