注册中心
注册中心
基本原理
在微服务远程调用的过程中,包括两个角色:
- 服务提供者:提供接口供其它微服务访问,生产者
- 服务消费者:调用其它微服务提供的接口,消费者
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:
流程如下:
- 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
- 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
- 调用者自己对实例列表负载均衡,挑选一个实例
- 调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
- 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
- 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
- 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
- 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
Nacos
目前开源的注册中心框架有很多,国内比较常见的有:
- Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用
- Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用
- Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言
以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能(后面会学习),因此在国内使用较多,课堂中我们会Nacos为例来学习。
nacos配置:docker部署nacos
部署完nacos,注册中心就搭建好了,接下来就是服务注册与服务发现
服务注册
服务提供者需要进行注册。
很简单只需要两步
- 引入依赖
<!--Nacos 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>- 加入yaml配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848服务发现
服务消费者需要进行服务发现。
也很简单
- 引入依赖
<!--Nacos 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>- 加入yaml配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848- discoveryClient
private final DiscoveryClient discoveryclient;
private void handleCartItems(List<Cartv0> vos){
//1.根据服务名称,拉取服务的实例列表
List<ServiceInstance> instances = discoveryclient.getInstances("item-service");
//2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//3.获取实例的IP和端口
URI uri = instance.getUri();
}远程调用(OpenFeign)
远程调用说白了就是请求一个服务发送http请求,获取返回值。
可以用restTemplate,但是太麻烦,上面的discoveryClient也很麻烦还需要自己实现负载均衡。
最好用简单的是openfeign。
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。官方地址:https://github.com/OpenFeign/feign
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送。
有了openfeign可以直接将:
// 2.1.发现item-service服务的实例列
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 2.2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 2.3.发送请求,查询商品
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri() + "/items?ids={ids}", // 请求路径
HttpMethod.GET, // 请求方式
null, // 请求实体
new ParameterizedTypeReference<List<ItemDTO>>() {}, // 返回值类型
Map.of("ids", CollUtil.join(itemIds, ",") // 请求参数
);简化为:
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));快速入门
- 引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>- 通过@EnableFeignClients注解,启用OpenFeign功能
@EnableFeignClients
@SpringBootApplication
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}- 编写FeignClient接口
@FeignClient(value = "item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);}- 使用FeignClient,实现远程调用
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));连接池
OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其它的框架。这些框架可以自己选择,包括以下三种:
HttpURLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
具体源码可以参考FeignBlockingLoadBalancerClient类中的delegate成员变量。
OpenFeign整合OKHttp的步骤如下:
- 引入依赖
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>- 配置开启连接池功能
feign:
okhttp:
enabled: true # 开启OKHttp连接池支持最佳实践
如果一个服务被多个服务调用,那么每个调用者都需要写client代码重复
有两种方式去解决
将微服务自身整理出来三个模块,其他模块要使用直接调client和dto。

这种方法由开发者自己写自己拆,缺点是拆的比较麻烦使用一个统一api微服务,和common服务类似的通用模块放api

这种方式的优点是清晰一目了然,缺点是代码有点耦合了。
步骤:
- api通用模块引入openfeign相关pom
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>- dto,client加入api模块
- 启动类配置扫描包
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定FeignClient所在包@EnableFeignClients(basePackages = "com.hmall.api.clients")
方式二:指定FeignClient字节码@EnableFeignClients(clients = {UserClient.class})
日志输出
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}但此时这个Bean并未生效,因为不是配置类没有@Configuration注解。
要想配置某个FeignClient的日志,可以在@FeignClient注解中声明:@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在@EnableFeignClients注解中声明:@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
