作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:详解SpringCloud微服务技术栈:认识微服务、服务拆分与远程调用
订阅专栏:微服务技术全家桶
希望文章对你们有所帮助
服务提供者:一次业务中,被其它微服务调用的服务(提供接口给其他微服务)
服务消费者:一次业务中,调用其它微服务的服务(调用其它微服务提供的接口)
若服务A调用服务B,服务B调用服务C,那么服务B是什么角色?
提供者与消费者的概念是相对的,一个服务既可以是提供者也可以是消费者。
上一节内容中服务调用存在问题,服务调用使用http请求,网址直接定死了,如果我们有多个服务集群,亦或是网址在后续开发过程中出现变更,就会产生不方便。如下所示:
我们需要解决以下三个问题:
服务消费者该如何获取服务提供者的地址信息?
如果有多个服务提供者,消费者该如何选择?
消费者如何得知服务提供者的健康状态?
而Eureka可以解决这个问题,Eureka的架构:
1、eureka-server:注册中心
2、eureka-client:
(1)服务消费者(集群)
(2)服务提供者(集群)
Eureka的作用及工作流程:
1、每个服务启动的时候,都会将注册服务信息记录在注册中心,例如端口号
2、服务信息都记住了,当服务消费者需要信息的时候,无须自己去记录,而是直接去Eureka-server中拉取,这样就可以得到服务提供者的信息
3、得到的服务提供者信息可能是个集群,包含多台服务的信息,这时候要做负载均衡去选取其中一个服务
4、消费者对提供者发起远程调用
这是核心的工作流程,另外,服务的提供者每隔30s就会向Eureka-server发送心跳续约,如果服务宕机了,那么Eureka-server就会将其剔除,这样能够保证服务消费者做远程调用的时候,能调用服务提供者都是健康的。
要首先Eureka服务,需要实现3点:
1、搭建注册中心(EurekaServer)
2、实现服务注册(将上一篇文章中的user-service、order-service注册到eureka)
3、实现服务发现(在order-service中完成服务拉取,然后通过负载均衡挑选一个服务,实现远程调用)
搭建EurekaServer的步骤如下:
1、创建项目,引入EurekaServer依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
2、编写启动类,添加@EnableEurekaServer注解:
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3、添加application.yml文件,编写下列配置:
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # 服务名称
eureka:
client:
service-url: # Eureka的地址信息
defaultZone: http://localhost:10086/eureka
Eureka自己也是微服务,所以配置Eureka也需要将Eureka本身给注册。
接着就可以启动Eureka服务,访问端口可以看到其界面信息:
可以发现注册到Eureka的实例,现在只有它本身。
将user-service注册到EurekaServer步骤如下:
1、将user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
2、在application.yml文件,编写下面的配置:
spring:
application:
name: userservice # 服务名称
eureka:
client:
service-url: # Eureka的地址信息
defaultZone: http://localhost:10086/eureka
同样的方式也可以将order-service注册。
刷新Eureka-server端网址:
如果你出现了java.lang.NoClassDefFoundError的问题,那可能是你的依赖导入错误了,我就不小心把依赖导入成下面这个:
记得要加上starter,才能被SpringBoot当成启动类。
另外,可以将user-service多次启动,模拟多实例部署,单位了避免端口冲突,需要修改端口设置:
将增加的端口也运行一下,工程看到注册了2个实例的实例列表:
服务注册总体来说分为两步:
1、引入eureka-client依赖
2、在application.yml中配置eureka地址
无论消费者还是提供者,引入eureka-client依赖,知道了eureka地址后,都可以完成服务注册。
服务拉取是基于服务名称获取服务列表,然后对服务列表做负载均衡。
1、修改OrderService代码,修改访问的url路径,用服务名来代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
2、在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
重启OrderApplication服务,按顺序访问:
http://localhost:8080/order/101
http://localhost:8080/order/102
可以看到UserApplication与UserApplication2各做了一次数据库查询操作:
所以,服务的拉取以及负载均衡已经完成。
服务发现的实现流程总结:
1、引入eureka-client依赖
2、在application.yml中配置eureka地址
3、给RestTemplate添加@LoadBalanced注解
4、给用户提供者的服务名称远程调用
在上面已经实现了服务的拉取,并且验证了负载均衡。那么其原理是怎样的,什么时候做的服务拉取,又是什么时候做的负载均衡,咱们都还不知道。
而其中,负载均衡是由Ribbon组件实现的。
首先思考,url已经做了修改:
String url = "http://userservice/user/" + order.getUserId();
然而访问一下http://userservice/user/1,并不能访问到,说明它并不是真实可用的地址。因此,中间肯定有组件拦截了这个请求并且做了处理,这个组件就叫做Ribbon。
整个负载均衡的流程如下:
而具体到底是什么时候接受这个请求的,又是怎么去做后序工作的,需要跟踪源码来做分析。
1、找到LoadBalancerInterceptor类,发现它实现了ClientHttpRequestInterceptor接口:
2、进入这个接口,查看注释:
其说明的大致意思是,当客户端有http请求发出的时候,立马就会被这个接口给拦截,那大概就可以知道,当实现类调用这个接口的实现方法的时候,应该要能够实现拦截
3、再回到其实现类,在实现类中打上断点,并debug模式重启OrderApplication:
4、在浏览器中发起请求:http://localhost:8080/order/101,可以发现请求确实成功被拦截了
5、跟踪下去,可以看到它解析出了我们要请求的提供者服务端是userservice:
6、解析完了就要开始进行拉取了,在这里我们可以看到Ribbon的词眼:
这是一个RibbonLoadBalancerClient对象,意为Ribbon负载均衡的客户端。
7、跟入execute方法,查看其执行的底层:
在得到动态服务列表均衡器(DynamicServerListLoadBalancer)后,可以看到这里已经成功拉取到了服务。而getServer方法就肯定是在做负载均衡了。
8、点击进入getServer方法,可以发现这里就是在做服务器的选择了:
9、跟进,可以看到它在实现父类的方法,可以猜想到,服务器的选择是基于某种规则的:
10、找到rule的对象,这是一个接口,按住Ctrl+H找到其实现类:
RoundRobinRule指代轮询负载均衡,RandomRule指代随机负载均衡,这些其实在nginx里面都是接触过的,到这里为止,其实现的机理就不在这里继续跟踪了,想知道的话直接学nginx就可以了。
1、order-service(消费者)发起请求,负载均衡拦截器LoadBalancerInterceptor的RibbonLoadBalancerClient会接收请求的url,获取url中的服务id
2、RibbonLoadBalancerClient把服务id交给DynamicServerListLoadBalancer
3、DynamicServerListLoadBalancer会去eureka-server中拉取userservice,并返回服务列表
4、DynamicServerListLoadBalancer需要在服务列表中选一个,这个选择即为负载均衡,交给了一个交IRule的接口对象。
5、IRule会选择一个方法在列表中选择服务,把值返回给RibbonLoadBalancerClient
6、RibbonLoadBalancerClient将会修改url,使得其实正确的真实地址,即可在前端成功访问了。
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每个子接口都是一种规则:
其中,ZoneAvoidanceRule是默认方式,其父类的父类是基于轮询的,因此也可以推测ZoneAvoidanceRule是基于轮询的。
ZoneAvoidanceRule:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。(Zone可以配置,没配置就默认所有服务都在一个Zone内)。
通过定义IRule可以修改负载均衡的规则,有2种方式:
1、代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
2、配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
测试的话大家自己多访问几次网址,看控制台就可以知道是不是随机负载均衡了。
这部分看不懂就去回顾一下设计模式吧。
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userservice
总结:
1、Ribbon负载均衡规则
(1)规则接口是IRule
(2)默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
2、负载均衡自定义方式
(1)代码方式:配置灵活,但修改时需要重新打包发布
(2)配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
3、饥饿加载
(1)开启饥饿加载
(2)指定饥饿加载的微服务名称