认识微服务
单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单,部署成本低
缺点:耦合度高(维护困难、升级困难)
分布式架构
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。
优点:降低服务耦合,有利于服务升级和拓展
缺点:服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
- 服务拆分的粒度如何界定?
- 服务之间如何调用?
- 服务的调用关系如何管理?
人们需要制定一套行之有效的标准来约束分布式架构。
## eureka-server(服务注册中心==服务端):记录和管理微服务(服务提供者和服务消费者==统称为客户端)
## eureka客户端:
* 提供者:1)注册服务信息(名称,端口号,ip地址),每隔30s发送一次心跳续约
* 消费者:
* 拉取服务,通过负载均衡进行远程调用
## eureka服务端:
* 保存注册信息
* 心跳监控:更新记录服务列表信息,心跳不正常即剔除
## 搭建eureka-server:
* 引入eureka-server的依赖
* 添加@EnableEurekaServer注解
* 在application.yml中配置eureka地址和端口号以及名称
## 服务注册:
* 引入eureka-client依赖
* 在application.yml中配置eureka地址和applicatioon.name
## 服务发现:
* 引入eruka-client依赖
* 在application.yml中配置eureka地址和applicatioon.name
* 给RestTemplate添加@LoadBalance注解
* 用服务提供者的服务名称远程调用
源码跟踪
为什么我们只输入了 service 名称就可以访问了呢?为什么不需要获取ip和端口,这显然有人帮我们根据 service 名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor
,这个类会在对 RestTemplate 的请求进行拦截,然后从 Eureka 根据服务 id 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务 id。
我们进行源码跟踪:
这里的 intercept()
方法,拦截了用户的 HttpRequest 请求,然后做了几件事:
request.getURI()
:获取请求uri,即 http://user-service/user/8originalUri.getHost()
:获取uri路径的主机名,其实就是服务iduser-service
this.loadBalancer.execute()
:处理服务id,和用户请求
这里的 this.loadBalancer
是 LoadBalancerClient
类型
继续跟入 execute()
方法:
getLoadBalancer(serviceId)
:根据服务id获取ILoadBalancer
,而ILoadBalancer
会拿着服务 id 去 eureka 中获取服务列表。getServer(loadBalancer)
:利用内置的负载均衡算法,从服务列表中选择一个。在图中可以看到获取了8082端口的服务
可以看到获取服务时,通过一个 getServer()
方法来做负载均衡:
我们继续跟入:
继续跟踪源码 chooseServer()
方法,发现这么一段代码:
我们看看这个 rule
是谁:
这里的 rule 默认值是一个 RoundRobinRule
,看类的介绍:
负载均衡默认使用了轮训算法,当然我们也可以自定义。
流程总结
SpringCloud Ribbon 底层采用了一个拦截器,拦截了 RestTemplate 发出的请求,对地址做了修改。
基本流程如下:
- 拦截我们的
RestTemplate
请求 http://userservice/user/1 RibbonLoadBalancerClient
会从请求url中获取服务名称,也就是 user-serviceDynamicServerListLoadBalancer
根据 user-service 到 eureka 拉取服务列表- eureka 返回列表,localhost:8081、localhost:8082
IRule
利用内置负载均衡规则,从列表中选择一个,例如 localhost:8081RibbonLoadBalancerClient
修改请求地址,用 localhost:8081 替代 userservice,得到 http://localhost:8081/user/1,发起真实请求
##负载均衡的原理
根据服务名获得服务列表,然后存到集合中,根据取余轮转调用服务
负载均衡加载策略
@Bean加载适用于全局
配置文件的加载方式适用于某个服务
##################
Ribbon采用的是懒加载,第一次访问时候才会去创建LoadBalanceClient
可以进行配置进行饥饿加载
开启了饥饿加载,第一次访问时间会缩短,但是因为要初始化springmvc容器还是需要一些时间.
代码配置 Ribbon
配置 Ribbon 最简单的方式就是通过配置文件实现。当然我们也可以通过代码的方式来配置。
通过代码方式来配置之前自定义的负载策略,首先需要创建一个配置类,初始化自定义的策略,代码如下所示。
-
@Configuration public class BeanConfiguration { @Bean public MyRule rule() { return new MyRule(); } }
创建一个 Ribbon 客户端的配置类,关联 BeanConfiguration,用 name 来指定调用的服务名称,代码如下所示。
-
@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class) public class RibbonClientConfig { }
可以去掉之前配置文件中的策略配置,然后重启服务,访问接口即可看到和之前一样的效果。
配置文件方式配置 Ribbon
除了使用代码进行 Ribbon 的配置,我们还可以通过配置文件的方式来为 Ribbon 指定对应的配置:
<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer(负载均衡器操作接口)
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule(负载均衡算法)
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing(服务可用性检查)
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList(服务列表获取)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter(服务列表的过滤)
重试机制
在集群环境中,用多个节点来提供服务,难免会有某个节点出现故障。用 Nginx 做负载均衡的时候,如果你的应用是无状态的、可以滚动发布的,也就是需要一台台去重启应用,这样对用户的影响其实是比较小的,因为 Nginx 在转发请求失败后会重新将该请求转发到别的实例上去。
由于 Eureka 是基于 AP 原则构建的,牺牲了数据的一致性,每个 Eureka 服务都会保存注册的服务信息,当注册的客户端与 Eureka 的心跳无法保持时,有可能是网络原因,也有可能是服务挂掉了。
在这种情况下,Eureka 中还会在一段时间内保存注册信息。这个时候客户端就有可能拿到已经挂掉了的服务信息,故 Ribbon 就有可能拿到已经失效了的服务信息,这样就会导致发生失败的请求。
这种问题我们可以利用重试机制来避免。重试机制就是当 Ribbon 发现请求的服务不可到达时,重新请求另外的服务。
1. RetryRule 重试
解决上述问题,最简单的方法就是利用 Ribbon 自带的重试策略进行重试,此时只需要指定某个服务的负载策略为重试策略即可:
ribbon-config-demo.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RetryRule
2. Spring Retry 重试
除了使用 Ribbon 自带的重试策略,我们还可以通过集成 Spring Retry 来进行重试操作。
在 pom.xml 中添加 Spring Retry 的依赖,代码如下所示。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置重试次数等信息:
# 对当前实例的重试次数
ribbon.maxAutoRetries=1
# 切换实例的重试次数
ribbon.maxAutoRetriesNextServer=3
# 对所有操作请求都进行重试
ribbon.okToRetryOnAllOperations=true
# 对Http响应码进行重试
ribbon.retryableStatusCodes=500,404,502