可以设置一个父工程统一管理依赖与版本
子工程继承父工程,导入依赖,不写代码
孙子工程继承子工程, 根据业务分出多个孙子工程
Q : 我们在微服务中数据独立, 每个服务对应一个自己的数据库, 那当我们在某个服务中需要其他服务的数据库中的数据, 或者其他的需要调用其他服务时怎么办?
A : 这就是服务之间的远程调用问题
为了解决远程调用问题, spring提供了RestTemplate可以在Java代码中发起http请求:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.ahua.order.mapper")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在service层中(或者mapper?)发起请求调用其他服务即可:
RestTemplate提供了一系列的ForObject(url, class)方法, 例如get请求就是GetForObject, Post就是PostForObject
其中参数一url是请求url(从http开始写起), 第二个参数为返回值类型, 例如User.class
示例:
// 请求url地址:
String url = "http://localhost:8080/user/...";
// 发起远程调用:
User user = restTemplate.GetForObject(url, User.class);
注意:
这种方法存在了硬编码问题,因此需要eureka作为注册中心, 后续补充eureka
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言
如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?
因此,服务B既可以是服务提供者,也可以是服务消费者
在上面的远程调用中, url是被写死的, 但实际情况下怎么准确得知另一个服务的ip地址与端口呢?
当服务提供者有多个示例地址时, 又要如何选择呢?
消费者又如何知道服务提供者是否依然健康,是不是已经宕机?
这些问题都需要利用SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka:
Eureka:
注册中心(为微服务注册), 其中EurekaServer是服务端,所有的微服务都是客户端, 因为一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
每个服务启动时都会向Eureka注册自己的服务信息(服务注册),eureka-server保存服务名称到服务实例地址列表的映射关系, 此时服务远程调用只需要向Eureka拉取对应的服务信息即可(服务拉取或发现 : 根据服务名称拉取实例地址列表)
如果拉取到了多个服务就使用负载均衡算法挑一个实例地址使用
同时拉取到的这个服务正常情况挂(极端情况有可能),因为每个服务每隔三十秒都会向Eureka发送自己还活着的信息,报告自己状态, 称为心脏,相当于心脏每三十秒跳一次发给大脑(Eureka)告诉他我还活着,如果三十秒之后还没有发消息Eureka就认为他挂了,把他从注册列表中剔除)
创建eureka服务首先需要注册中心服务端:eureka-server,这必须是一个独立的微服务
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
编写eureka-server的启动类, 注意:
一定要添加一个==@EnableEurekaServer注解,开启eureka的注册中心功能==:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
看到Eureka页面就成功了
(同时在父工程有一个boot的依赖库)
在其他服务的pom文件中,引入下面的eureka-client依赖
与上面的依赖名只有server与client的区别
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
在需要注册的服务中, 修改application.yml文件,添加服务名称、eureka地址(注意是eureka的地址而不是自己的)
也就是说,不管是eureka自己注册还是其他服务注册,都是配自己的服务名称以及eureka的服务地址(相对来说, Eureka自己配置时就是配置自己的地址, 而其他服务配置的是Eureka配置的地址):
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动服务, 查看eureka-server管理页面出现服务地址与名称即配置成功
首先需要完成自己的服务注册(eureka-client依赖与yml配置)
接着我们要去eureka-server中拉取服务提供者的实例列表,并且实现负载均衡
不过这些动作不用我们去做,只需要添加一些注解以及修改远程调用的路径:
在RestTemplate这个Bean添加一个@LoadBalanced注解(用于实现负载均衡):
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
修改消费者在Service层中远程调用的访问url路径, 将ip地址与端口修改为服务名称, 示例:
String url = "http://userservice/user/...";
最后当我们运行并访问消费者资源时spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡
三步走:
创建Eureka注册中心服务
1)导入eurek-server依赖
2)配置Eureka服务名称与服务地址,先将自己注册了
服务注册
1)导入eurek-client依赖
2)配置自己的服务名称与eureka的服务地址
服务拉取
1)在RestTemplate这个Bean添加一个@LoadBalanced注解
2)修改远程调用url,用服务名代替ip、端口
Q : 添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的, 而这个@LoadBalanced注解的作用就是让程序走Ribbon组件
Q : 以及我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?
显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id
可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:
request.getURI()
:获取请求uri,本例中就是 http://user-service/user/8originalUri.getHost()
:获取uri路径的主机名,其实就是服务id,user-service
this.loadBalancer.execute()
:处理服务id,和用户请求。这里的this.loadBalancer
是LoadBalancerClient类型,可以继续跟入源码:
跟入LoadBalancerClient的execute方法:
代码是这样的:
可以看到, 在execute方法中获取服务时通过一个getServer
方法来做负载均衡:
跟入getServer()方法发现使用了RibbonLoadBalancerClient类中的chooseServer方法
继续跟踪源码chooseServer方法,发现这么一段代码:
我们看看这个rule是谁:
这里的rule默认值是一个RoundRobinRule
,看类的介绍:
这不就是轮询的意思嘛
到这里,整个负载均衡的流程我们就清楚了
SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,从而实现了对地址的修改(去Eureka根据服务名拉取服务地址)以及负载均衡。用一幅图来总结一下:
基本流程如下:
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:
其中默认的是ZoneAvoidanceRule, 这个实现类是先根据Zone对服务器进行分类再进行轮循, 但由于机房一般都在同一个地方, 因此基本上理解为轮循即可
常用的内置规则类:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
通过定义IRule实现可以修改负载均衡规则,有两种方式:
代码方式:在服务消费者中的Application启动类或者配置中,定义一个新的IRule
需要什么规则在这个bean中返回什么规则即可
这样子配置的是这个服务的全局规则
@Bean
public IRule randomRule(){
return new RandomRule();
}
配置文件方式:在消费者的application.yml文件中,添加配置也可以修改规则
需要给哪个服务提供者配置策略就写哪个服务提供者的服务名称
此时配置的是这个消费者针对这个提供者的策略
userservice: # 给某个微服务提供者配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
注意,一般用默认的负载均衡规则,不做修改
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,此时需要去完成服务拉取等, 因此请求时间会很长
而第一次加载之后后面就不需要加载了, 因为会被缓存到内存中, 因此后面的访问速度就会变快了
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载
只需要将enabled设置为true, 同时需要指定饥饿加载的服务名称(也是消费者配置提供者)
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 配置对哪个服务进行饥饿加载
配置对多个服务进行饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
# 指定需要饥饿加载的服务名称:
clients:
- userservice
- service_acl