2

微服务工程中,基础组件应用

 2 years ago
source link: https://blog.51cto.com/u_14439672/5080489
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、网关服务

1、网关模式

网关作为架构的最外层服务,用来统一拦截各个端口的请求,识别请求合法性,拦截异常动作,并提供路由和负载能力,保护业务服务;这种策略与外观模式异曲同工。

微服务工程中,基础组件应用_微服务

网关服务和门面类服务有部分的逻辑相似,网关服务的拦截侧重处理通用的策略和路由负载,而不同的门面聚合服务侧重场景分类,例如常见的几种门面服务:

  • Facade:服务产品开放的端口请求,例如Web,App,小程序等;
  • Admin:通常服务于内部的管理系统,例如Crm,BI报表,控制台等;
  • Third:聚合第三方的对接服务,例如短信,风控,动作埋点等;

不同的门面服务中,也会存在特定的拦截策略,如果把Facade、Admin、Third等校验都集成在网关中,很显然会加重网关服务的负担,不利于架构的稳定。

2、Gateway组件

如果微服务架构接触较早的话,初期网关中常采用的是Zuul组件,后来SpringCloud才发布Gateway组件,是当前常用选型。

  • 请求拦截:网关作为API请求的开放入口,完成请求的拦截、识别校验等是基础能力;
  • 定制策略:除常规身份识别,根据服务场景设计相应的拦截逻辑,尽量拦截异常请求;
  • 服务路由:请求通过拦截后转发到具体的业务服务,这里存在两个核心动作路由和负载;

作为微服务架构中常用的选型组件,下面从使用细节中详细分析Gateway网关的使用方式,与其他组件的对接流程和模式。

Nacos作为微服务的注册和配置中心,已经是当下常用的组件,并且Nacos也提供Gateway组件的整合案例,首先就是把网关服务注册到Nacos:

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
      discovery:
        server-addr: 127.0.0.1:8848

Nacos管理网关服务注册和相关配置文件,在项目中通常与nacos共用一套MySQL库,用来管理网关的服务路由数据,是当下比较常见的解决方案。

3、网关拦截

GlobalFilter:网关中的全局过滤器,拦截经过网关的所有请求,经过相应的校验策略,判断请求是否需要执行:

@Order(-1)
@Component
public class GatewayFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        return chain.filter(exchange);
    }
}

通常在网关中会执行一些必要共性拦截,例如:IP黑白名单,Token身份令牌,在请求中获取对应参数,执行相关服务的校验方法即可。

4、动态路由

4.1 路由定义

在服务路由的实现上,存在复杂的逻辑和策略,来适配各种场景;路由的概念如何定义,可以查阅Gateway组件的源码RouteDefinition对象,结构涉及到几个核心的属性:路由、断言、过滤、元数据:

  • Route路由:由ID、转发Uri、断言、过滤、元数据组成;
  • Predicate断言:判断请求和路由是否匹配;
  • Filter过滤:可以对请求动作进行修改,例如参数;
  • Metadata元数据:装载路由服务的元信息;
@Validated
public class RouteDefinition {
    private String id;
    @NotNull
    private URI uri;
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList<>();
    @Valid
    private List<FilterDefinition> filters = new ArrayList<>();
    private Map<String, Object> metadata = new HashMap<>();
}

通常把这些路由放在nacos库的config_route数据表中,围绕上述路由对象的结构,管理对应的表数据即可:

微服务工程中,基础组件应用_Gateway_02

关于转发的目标uri也有不同的配置,这里选择lb://服务注册名的模式,即把请求负载均衡分配到路由对应的服务节点,补充说明一下Nacos组件内部采用Ribbon负载算法,可以参考相关的文档。

4.2 管理路由

在路由的管理上有两个核心接口:Locator加载Writer增删,并且还提供了聚合的Repository接口:

public interface RouteDefinitionLocator {
    // 获取路由列表
    Flux<RouteDefinition> getRouteDefinitions();
}
public interface RouteDefinitionWriter {
    // 保存路由
    Mono<Void> save(Mono<RouteDefinition> route);
    // 删除路由
    Mono<Void> delete(Mono<String> routeId);
}
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter{}

这样通过定义路由管理组件,实现上述聚合接口,完成路由数据从数据表加载到应用的过程:

@Component
public class RouteFactory implements RouteDefinitionRepository {
    @Resource
    private RouteService routeService ;

    // 加载全部路由
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(routeService.getRouteDefinitions());
    }
}

RouteService则是路由管理的服务类,管理配置数据以及实体对象与路由定义对象的转换:

@Service
public class RouteServiceImpl implements RouteService {
    // 数据库路由实体,转换为网关路由的定义对象
    private RouteDefinition buildRoute (ConfigRoute configRoute){
        RouteDefinition routeDefinition = new RouteDefinition () ;
        // 基础
        routeDefinition.setId(configRoute.getRouteId());
        routeDefinition.setOrder(configRoute.getOrders());
        routeDefinition.setUri(URI.create(configRoute.getUri()));
        // 断言
        routeDefinition.setPredicates(JSONUtil.parseArray(configRoute.getPredicates()).toList(PredicateDefinition.class));
        // 过滤
        routeDefinition.setFilters(JSONUtil.parseArray(configRoute.getFilters()).toList(FilterDefinition.class));
        return routeDefinition ;
    }
}

通过上述流程即可将路由信息持久化存储在数据库,如果服务节点很少,也可以直接在nacos的配置文件中管理。

4.3 匹配模式

Predicate断言其实就是一个匹配规则的设定,查阅PredicateDefinition相关源码可知,有Host、Path、Time等多种模式,通过上述数据可知本文采用的是路径匹配,

由于采用路径匹配的方式,会把/服务名拼接在uri起始位置,所以在配置过滤规则的时候设置StripPrefix去掉该路由标识。

微服务工程中,基础组件应用_Feign_03

请求进入网关之后,首先进入全局拦截器执行校验策略,检验通过之后匹配相关路由的服务,匹配成功之后进行过滤操作,最终将请求转发到相应的服务,这就是请求在网关中要执行的核心流程。

二、注册与配置

Nacos在整个微服务体系中,提供服务注册与配置管理两个核心能力,通常在代码工程中只保留核心的bootstrap配置文件即可,可以极大简化工程中的配置并且提高相关数据的安全性。

1、服务注册

Nacos支持基于DNS和基于RPC的服务发现,并提供对服务的实时健康检查,阻止向非健康的主机或服务实例发送请求:

微服务工程中,基础组件应用_微服务_04

在服务的注册列表中可以查看注册信息,实例数健康数等,并且可以删除注册服务;在详情中可以查看具体实例信息,可以对服务实例进行动态上下线和相关配置编辑。

2、配置文件

通常会采用namespace命名空间的管理,隔离开不同的服务的注册与配置,可以在nacos库中tenant_info查看,一般会存在如下几个分类:

微服务工程中,基础组件应用_微服务_05

这是最常见的命名空间,gateway网关比较独立,seate事务的配置比较复杂,serve业务服务具有大量的公共配置,通常采用如下的策略:

  • application.yml:所有服务的公共配置,例如mybatis;
  • dev||pro.yml:环境隔离配置,不同环境设置不同的中间件地址;
  • serve.yml:服务的个性化配置,比如连接的库,参数等;
spring:
  application:
    name: facade
  profiles:
    active: dev,facade
  cloud:
    nacos:
      config:
        prefix: application
        file-extension: yml
        server-addr: 127.0.0.1:8848
      discovery:
        server-addr: 127.0.0.1:8848

服务通过上述profiles参数会识别和加载对应的配置文件,在这种管理模式中,环境的差异只在dev||pro.yml配置文件中维护即可,其他配置会相对稳定许多。

三、服务间调用

1、Feign组件

Feign组件是声明式、模板化的HTTP客户端,可以让服务之间的调用变得更简单优雅,通常将服务提供Feign接口在独立的代码包中管理,方便被其他服务依赖使用:

/**
 * 指定服务名称
 */
@FeignClient(name = "account")
@Component
public interface FeignService {
    /**
     * 服务的API接口
     */
    @RequestMapping(value = "/user/profile/{paramId}", method = RequestMethod.GET)
    Rep<Entity> getById(@PathVariable("paramId") String paramId);
}
public class Rep<T> {
    // 响应编码
    private int code;
    // 语义描述
    private String msg;
    // 返回数据
    private T data;
}

通常会把Feign接口的响应格式做包装,实现返参结构统一管理,有利于调用端的识别,这里就涉及到泛型数据的处理问题。

2、响应解码

通过继承ResponseEntityDecoder类,实现自定义的Feign接口响应数据处理,例如返参风格,数据转换等:

/**
 * 配置解码
 */
@Configuration
public class FeignConfig {
    @Bean
    @Primary
    public Decoder feignDecoder(ObjectFactory<HttpMessageConverters> feignHttpConverter) {
        return new OptionalDecoder(
                new FeignDecode(new SpringDecoder(feignHttpConverter)));
    }
}
/**
 * 定义解码
 */
public class FeignDecode extends ResponseEntityDecoder {
    public FeignDecode(Decoder decoder) {
        super(decoder);
    }
    @Override
    public Object decode(Response response, Type type) {
        if (!type.getTypeName().startsWith(Rep.class.getName())) {
            throw new RuntimeException("响应格式异常");
        }
        try {
            return super.decode(response, type);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

3、请求解析

采用注解方式就轻松实现服务间的通信请求,查阅Feign组件的源码可以理解封装逻辑,其内在是把接口调用转换成HTTP请求:

  • FeignClientBuilder:不采用注解的方式直接构建Feign请求的客户端,该类有助于理解@FeignClient注解原理;
  • FeignClientsRegistrar:即项目中采用@FeignClient注解方式,该API中描述了注解的解析方式和服务请求的构建逻辑;

微服务工程的架构是一项复杂和持续的过程,其中涉及到的组件也十分繁杂,本文只是选取Gateway、Nacos、Feign三个基础组件做简单的总结,在其逻辑的理解上需要围绕该组件的核心功能和项目使用的API作为切入点,时常查阅源码和官方文档。

四、参考源码

应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

组件封装:
https://gitee.com/cicadasmile/butte-frame-parent

微服务工程中,基础组件应用_分布式_06


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK