关于后端:使用Dubbo做网关设计

51次阅读

共计 4914 个字符,预计需要花费 13 分钟才能阅读完成。

应用 Dubbo 做网关设计

大家好,我是易安!明天咱们来谈谈应用 dubbo 做网关设计。

业务场景

假如你处在一个物流公司,快递物流行业的业务量能够比肩互联网,你刚好处在网关项目组,网关的外围工作就是确保可能疾速接入各个电商平台

解释一下下面这个图。

物流公司外部曾经基于 Dubbo 构建了订单核心微服务域,其中创立订单接口的定义如下:​

内部电商平台泛滥,每一家电商平台外部都有本人的规范,并不会遵循对立的规范。例如在淘宝中,当用户购买商品后,淘宝外部会定义一个对立的订单外派接口。它的申请包可能是这样的:

{
  "seller_id":189,
  "buyer":"dingwei",
  "order":[
    {
      "goods_name":"华为笔记本",
      "num":1,
      "price":500000
    },
    {
      "goods_name":"华为手表",
      "num":1,
      "price":200000
    }
  ]
}

但拼多多外部定义的订单外派接口,它的申请包可能是上面这样的:

<order>
  <seller_uid>189</seller_uid>
  <buyer_uid>dingwei</buyer_uid>
  <order_items>
    <order_item>
      <goods_name> 华为笔记本 </goods_name>
      <num>1</num>
      <price>500000</price>
    </order_item>
    <order_item>
      <goods_name> 华为手表 </goods_name>
      <num>1</num>
      <price>200000</price>
    </order_item>
  </order_items>
</order>

当电商的快递件占据快递公司总业务量的大半时,电商平台的话语权是高于快递公司的。也就是说,电商平台不论上游对接哪家物流公司,都会下发本人公司外部定义的订单派发接口,适配工作须要由物流公司本人来承当。

那站在物流公司的角度,应该怎么做呢?总不能每接入一个电商平台就为它们开发一套下单服务吧?那样的话,随着越来越多的电商平台接入,零碎的复杂度会越来越高,可维护性将越来越差。

设计方案

正是在这样的背景下,网关平台被立项开发进去了。这个网关平台是怎么设计的呢?在设计的过程中须要解决哪些常见的问题?

我认为,网关的设计至多须要包含三个方面,别离是 签名验证 服务配置 和限流。

先说签名验证。保障申请的平安是零碎设计须要优先思考的。业界有一种十分经典的通信安全校验机制:验证签名。

这种机制的做法是,客户端与服务端会首先采纳 HTTPS 进行通信,确保传输过程的私密性。

客户端在发送申请时,先将申请参数按参数名称进行排序,而后按程序拼接成字符串,格局为 key1=a & key2=b。接下来,客户端应用一个约定的密钥对拼接进去的参数字符串进行签名,生成签名字符串(咱们用 sign 示意签名字符串)并追加到 URL。通常,还会在 URL 中追加一个发送工夫戳(工夫戳不参加签名验证)。

服务端在接管到客户端的申请后,先从申请中解析出所有的参数,同样依照参数名对参数进行排序,而后应用同样的密钥对参数进行签名。失去的签名字符串须要与客户端计算的签名字符串进行比照,如果两者不同,则申请有效。与此同时,通常咱们还须要将服务端以后的工夫戳与客户端工夫戳进行比照,如果相差超过肯定的工夫,同样认为申请有效,这个操作次要是为了防止应用同一个连贯对网络进行间断攻打。

这整个过程里有一个十分重要的点,就是密钥从头至尾并没有在网络上进行过流传,它的安全性能够失去十足的保障。签名验证的流程大略能够用上面这张图示意:

如果要对验证签名进行产品化设计,咱们通常须要:

  1. 为不同的接入端(电商平台)创立不同的密钥,并通过平安的形式告知他们;
  2. 为不同的接入端(电商平台)配置签名算法。

在确保可能平安通信后,接下来就是网关设计最外围的局部了:服务接口配置化。 它次要包含两个要点:微服务调用协定(Dubbo 服务形容)和接口定义与参数映射。

咱们先来看一下微服务调用协定的配置,设计的原型界面如下图所示:

将所有的微服务(细化到办法级名称)保护到网关零碎中,网关利用就能够应用 Dubbo 提供的编程 API,依据这些元信息动静构建一个个消费者(服务调用者),进而通过创立的服务调用客户端发动 RPC 近程调用,最终实现网关利用的 Dubbo 服务调用。

基于这些元信息构建消费者对象的要害代码如下:

public static GenericService getInvoker(String serviceInterface, String version, List<String> methods, int retry, String registryAddr) {ReferenceConfig referenceConfig = new ReferenceConfig();
        // 对于消费者通用参数,能够从配置文件中获取,本示例勾销
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setTimeout(3000);
        consumerConfig.setRetries(2);
        referenceConfig.setConsumer(consumerConfig);
        // 应用程序名称
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("GateWay");
        referenceConfig.setApplication(applicationConfig);
        // 注册核心
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress(registryAddr);
        registry.setProtocol("zookeeper");
        referenceConfig.setRegistry(registry);
        // 设置服务接口名称
        referenceConfig.setInterface(serviceInterface);
        // 设置服务版本
        referenceConfig.setVersion(version);
        referenceConfig.setMethods(new ArrayList<MethodConfig>());
        for(String method : methods) {MethodConfig methodConfig = new MethodConfig();
            methodConfig.setName(method);
            referenceConfig.getMethods().add(methodConfig);
        }
        referenceConfig.setGeneric("true");// 开启 dubbo 的泛化调用
        return (GenericService) referenceConfig.get();}

通过 getInvoker 办法发动调用近程 RPC 服务,这样,网关利用就成为了对应服务的消费者

因为网关利用引入服务规约(API 包)不太事实,所以这里应用的是泛化调用,这样不便网关利用不受约束地构建消费者对象。

值得注意的是,ReferenceConfig 实例很重,它封装了与注册核心的连贯以及所有服务提供者的连贯,须要被缓存起来。因而,在实在的生产实践中,咱们须要将 ReferenceConfig 对象存储到缓存中。否则,反复生成的 ReferenceConfig 可能造成性能问题并随同着内存和连贯透露。

除了 ReferenceConfig,其实 getInvoker 生成对象也能够进行缓存,缓存的 key 通常为接口名称、版本和注册核心。

那如果配置信息动静产生了变动,例如须要增加新的服务,这时候网关利用如何做到动静感知呢?咱们通常能够用基于 MQ 的形式来解决这个问题。具体的解决方案如下:

也就是说,用户如果在网关经营平台上批改原有服务协定(Dubbo 服务)或者增加新的服务协定,变动后的协定会首先存储到数据库中,而后经营平台发送一条音讯到 MQ,紧接着 Gateway 的后盾过程以播送模式进行订阅。这样,所有后盾网关过程都能够感知。

如果是对已有服务协定进行批改,在具体实际时有一个小细节请你肯定留神。咱们先看看这段代码:

Map<String /* 缓存 key */,GenericService> invokerCache;
GenericService newInvoker = getInvoker(...);// 参数省略
GenericService oldInvoker = invokerCache.get(key);
invokerCache.put(newInvoker);// 先缓存新的 invoker
// 而后再销毁旧的 invoker 对象
oldInvoker.destory();

如果曾经存在对应的 Invoker 对象,为了不影响现有调用,应该先用新的 Invoker 对象去更新缓存,而后再销毁旧的 Invoker 对象。

下面的办法解决了网关调用公司外部的 Dubbo 微服务问题,但还有另外一个十分重要的问题,怎么配置服务接口相干参数呢?

分割之前的场景,咱们须要在页面上配置公司外部 Dubbo 服务与内部电商的接口映射。

为此,咱们专门建设了一条参数映射协定:

参数映射设计的阐明如下。

  • 申请类型:次要分为申请参数与响应参数;
  • 字段名称:Dubbo 服务对应的字段名称;
  • 字段类型:Dubbo 服务对应字段的属性;
  • 字段所属类:Dubbo 服务对应字段所属类型;
  • 节点名称:内部申请接口对应的字段名称;
  • 显示程序:排序字段。

因为网关采取了泛化调用,在编写转换代码时,次要是遍历传入的参数,依据每一个字段查问对应的转换规则,而后转换为 Map,返回值则刚好相同,是将 Map 转换为 XML 或者 JSON。

在真正申请调用时,依据映射规定构建出申请参数 Map 后,通过 Dubbo 的泛化调用执行真正的调用:

GenericService genericService = (GenericService) invokeBean;
Map invokerPams;// 省略转换过程
// 参数类型数组
String[] paramTypes = new String[1];
paramTypes[0]="java.util.Map";
// 参数值数组
Object[] paramValues = new Object[1];
​
invokerPams.put("class", "net.codingw.oms.vo.OrderItemVo");
paramValues[0] = invokerPams;
// 因为咱们曾经转化为 java.util.Map,并且 Map 中,须要有一个 key 为 class 的,示意服务端须要转化的类型,这个从协定转换器中获取
Object result = genericService.$invoke(this.getInvokeMethod(), paramTypes, paramValues);

这样,网关就具备了高扩展性和稳定性,能够非常灵活地撑持业务的扩大,为不同的电商平台配置不同的参数转换,从而在外部只须要开发一套接口就能够非常灵活地撑持业务的扩大,根本做到网关代码零批改。

总结

本文,我通过一个实在的场景,具体介绍了网关设计的需要背景,而后针对网关设计的痛点给出了设计方案。通过对这个计划中要害代码的解读,你应该可能更加粗浅地了解 Dubbo 泛化调用背地的逻辑,真正做到实践与理论相结合。值得注意的是,文中的转换协定,应用中括号来定义多层嵌套构造,使得该协定具备普适性。

本文由 mdnice 多平台公布

正文完
 0