Spring Cloud是一个用于构建分布式系统的开发工具包,其中也包括了几个核心组件,如Ereka、Ribbon、Feign、Hystrix、Zuul等;
这里会讲解核心组件的实际应用于原理。
一、spring-cloud核心组件:Eureka
这里介绍一下Eureka的注册原理:
1) 服务提供者将自己的IP和端口注册到注册中心上
2) 服务提供者每隔一段时间向注册中心发送心跳包
3) 服务消费者调用提供者之前,先向注册中心查询提供者的IP和端口
4) 获得服务清单中的IP和端口后,消费者调用提供者
5) 服务提供者的IP和端口改变后,通过心跳机制更新注册中心上的服务清单
假如我们设置三个服务,eureka-service、order-service、proudct-serive,把ereka-service当成是我们的主服务。
这个时候我们想通过一个主服务去调用其他的服务该怎么办?
Eureka就可以帮我们解决这个问题,Eureka就是微服务架构中的注册中心,专门负责一些服务的注册和发现。
想要用到这个Eureka,我们需要导入相关依赖
首先你得让整个服务有一个主从关系,先往主服务中导入一个依赖如下
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
从服务导入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
这两者区别在于,eureka-service是一个注册中心,里面存有一个注册表,专门用来存放所注册的从服务器的ip和端口。从服务可以通过eureka-client来把信息存到rureka中
添加主服务的配置文件,且在主服务的启动类加 @EnableEurekaServer
server.port=8000
# 服务器域名
eureka.instance.hostname=127.0.0.1
# 设置不拉取服务清单
eureka.client.fetch-registry=false
# 设置不注册当前服务
eureka.client.register-with-eureka=false
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
添加从服务的配置文件,且从服务的启动类加 @EnableEurekaClient
# 服务名称
spring.application.name=xxxx
# 设置拉取服务清单
eureka.client.fetch-registry=true
# 设置注册当前服务
eureka.client.register-with-eureka=true
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka
通过127.0.0.1我们可以看到注册表中我们成功注册的两个从服务
这个地方我们只需要了解以下:
- Eureka Client:负责将这个服务的信息注册到Eureka Server中
- Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号
二、spring-cloud的核心组件:ribbon
我们发现从服务变多了呢,现在不知道该访问哪个呢?ribbon就可以解决这个问题。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。
SpringBoot2.7.2下使用Ribbon,2.7.2开始使用LoadBalancer
1) 导入依赖,在导入Eureka-client依赖时,自动导入Ribbon依赖
2) 使用 @LoadBalanced 后生效
3) 配置负载均衡策略
负载均衡策略
在使用前需要添加一个配置类
@Configuration
public class RestTemplateConfig {
/**
* 向容器提供HTTP客户端
* @return
*/
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
我选用的是一个随机选择一个可用的服务器
product-server.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
如果没有用这个策略的话,每次访问的都会按照一个顺序,这里用来ribbon之后,每次都会随机选择一个有用的进行访问了。
三、spring-cloud的核心组件:Hystrix
我们在实现一个服务的时候,会有很多线程进行访问,假如有100个线程在运行中,发现有一个服务出现了错误,访问不了,有没有设置跳过,就会卡在这,然后访问其他服务也会失败,导致会出现问题
如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。
出现这个问题,Hystrix就能解决,hystrix是一个可以实现隔离和熔断的框架,会生产众多线程池,就比如eureka-service想要访问order-service做修改,这个访问就是一个线程池,就专门只做访问做修改这一个操作。然后eureka-service想要访问product做添加操作就能正常执行,因为是两个完全不同的操作,所以这里毫无影响。
思考一下,如果我们在执行一系列流程的时候,中间一个流程出现了问题,但是每次都会进行访问,这样是不是会显得很浪费时间,所以比比避免这个问题,我们就把他熔断,就是说,在每次到这的时候返回,跳过,但是这个流程放在这肯定有它的用处,不能都跳过了,我们就加一个降级操作,可以将每次访问想要进行的操作返回到数据库,后面再单独处理。
想用到这个操作也需要
1)添加一个依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)启动类加 @EnableHystrix
3) 在需要熔断的方法上加 @HystrixCommand
4) 设置降级方法,返回兜底数据
@RestController
public class ProductController {
@Autowired
private ProductService productService;
Long start = System.currentTimeMillis();
@Value("${server.port}")
private Integer port;
// @HystrixCommand(fallbackMethod = "getProductByIdFallback")
@GetMapping("product/{id}")
public Product getProductById(@PathVariable Long id){
start = System.currentTimeMillis();
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
Product product = productService.getById(id);
product.setName(product.getName()+"-from:"+port);
System.out.println("查询了"+port+"服务器"+product);
return product;
}
public Product getProductByIdFallback(Long id){
long time = System.currentTimeMillis();
System.out.println("熔断时间"+(time-start));
Product product = new Product();
product.setId(id);
product.setName("兜底商品数据");
return product;
}
}
设置熔断时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
四、spring-cloud的核心组件:feign
问题:每个服务的功能我们都实现了,但是不能光有功能,不去用嘛,所以我们该怎么去调用功能呢,且每个服务之间也要联系哇,我们总是需要一些挥挥手就能干大事的东东,这个时候feign出现了,有了它,问题自然就简单多了。只需要写一个@FeignClient注解,人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。
光知道好用还不行,还得会用嘞
首先还是老规矩,导依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后就是编写客户端接口,
@FeignClient(value = "product-service",fallback = ProductServiceFallback.class)
public interface ProductServiceClient {
@GetMapping("product/{id}")
Product getProductById(@PathVariable Long id);
}
接口写好了,现在需要找到它,我们需要扫描,在启动类添加注解进行扫描
@EnableFeignClients(basePackages = "com.blb.common.feign")
在serviceImpl进行调用服务
@Autowired
private ProductServiceClient productServiceClient;
@Override
public Order getOrderById(Long id){
Order order = orderMapper.selectById(id);
Product productById = productServiceClient.getProductById(order.getProductId());
order.setProduct(productById);
return order;
}
feign这么强嘛!它的原理也是一样,
- 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
- 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
- Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
- 最后针对这个地址,发起请求、解析响应
五、spring-cloud的核心组件:gateway
最后我们说一下gateway,这个组件是专门负责网络路由的
如果我们有很多服务,这里就当有100个服务吧,100个服务就有100个路由,如果有人想访问,就需要记住这个路由,访问一个记住一个,估计没几个就绷不住了吧。所以嘛,我们不能这么不厚道,我们得用gateway。
我们可以设置一个网关,这样无论有多少服务,我们都得经过网关,网关会通过分析请求的一些数据,发送给后端一些对应的服务,这样就方便了很多,减少了一些不必要的操作。
而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。
gateaway的原理:
1、首先客户端会发送一个请求到gateway中
2、gateway会将请求发送到handlerMapping处理器映射
3、处理器映射会根据请求周到对应的handler执行这个请求
4、请求执行会经过一个过滤器链,过滤器分为两种:pre前置过滤器,主要用于鉴权;post后置过滤器,用于性能监控和日志记录
5、如果请求正常通过,就能访问代理的服务,最后把数据返回给客户端。
总结:这就是spring-cloud的几个核心组件
- Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
- Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
- Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
- Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
- Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务