欢迎访问Spring Cloud中国社区

《重新定义Spring Cloud实战》由Spring Cloud中国社区倾力打造,基于Spring Cloud的Finchley.RELEASE版本,本书内容宽度足够广、深度足够深,而且立足于生产实践,直接从生产实践出发,包含大量生产实践的配置。欢迎加微信Software_King进群答疑,国内谁在使用Spring Cloud?欢迎登记

Spring Cloud 服务注册增强插件

admin · 4月前 · 2007 ·

Nepxion Discovery

Nepxion Discovery是一款对Spring Cloud Discovery的服务注册增强插件,目前暂时只支持Eureka。现在Spring Cloud服务可以方便引入该插件,不需要对业务代码做任何修改,只需要修改规则(XML)即可

开源项目地址:https://github.com/Nepxion/Discovery

简介

支持如下功能

  1. 1. 实现服务注册层面的控制,基于黑/白名单的IP地址过滤机制禁止对相应的微服务进行注册
  2. 2. 实现服务发现层面的控制,基于黑/白名单的IP地址过滤机制禁止对相应的微服务被发现;通过对消费端和提供端可访问版本对应关系的配置,进行多版本灰度访问控制
  3. 3. 实现通过下面两种推送方式,动态改变“服务发现层面的控制”
  4. 4. 实现通过XML进行规则定义
  5. 5. 实现通过事件总线机制(EventBus)异步对接远程配置中心,接受远程配置中心主动推送规则信息
  6. 6. 实现通过事件总线机制(EventBus)异步接受Rest主动推送规则信息
  7. 7. 实现通过Listener机制便于使用者扩展更多过滤条件,也可以利用Listener实现服务注册发现核心事件的监听

场景

  1. 1. 黑/白名单的IP地址注册的过滤
  2. 开发环境的本地服务(例如IP地址为172.16.0.8)不希望被注册到测试环境的服务注册发现中心,那么可以在配置中心维护一个黑/白名单的IP地址过滤(支持全局和局部的过滤)的规则
  3. 我们可以通过提供一份黑/白名单达到该效果
  4. 2. 黑/白名单的IP地址发现的过滤
  5. 开发环境的本地服务(例如IP地址为172.16.0.8)已经注册到测试环境的服务注册发现中心,那么可以在配置中心维护一个黑/白名单的IP地址过滤(支持全局和局部的过滤)的规则,该本地服务不会被其他测试环境的服务所调用
  6. 我们可以通过推送一份黑/白名单达到该效果
  7. 3. 多版本灰度访问控制
  8. A服务调用B服务,而B服务有两个实例(B1B2),虽然三者相同的服务名,但功能上有差异,需求是在某个时刻,A服务只能调用B1,禁止调用B2。在此场景下,我们在application.properties里为B1维护一个版本为1.0,为B2维护一个版本为1.1
  9. 我们可以通过推送A服务调用某个版本的B服务对应关系的配置,达到某种意义上的灰度控制,切换版本的时候,我们只需要再次推送即可

依赖

  1. <dependency>
  2. <groupId>com.nepxion</groupId>
  3. <artifactId>discovery-plugin-starter</artifactId>
  4. <version>${discovery.plugin.version}</version>
  5. </dependency>

规则配置

规则示例(请不要被吓到,我只是把注释写的很详细而已,里面配置没几行)

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <rule>
  3. <register>
  4. <!-- 服务注册的黑/白名单注册过滤,只在服务启动的时候生效。白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册。每个服务只能同时开启要么白名单,要么黑名单 -->
  5. <!-- filter-type,可选值blacklist/whitelist,表示白名单或者黑名单 -->
  6. <!-- service-name,表示服务名 -->
  7. <!-- filter-value,表示黑/白名单的IP地址列表。IP地址一般用前缀来表示,如果多个用“;”分隔,不允许出现空格 -->
  8. <!-- 表示下面所有服务,不允许10.10和11.11为前缀的IP地址注册(全局过滤) -->
  9. <blacklist filter-value="10.10;11.11">
  10. <!-- 表示下面服务,不允许172.16和10.10和11.11为前缀的IP地址注册 -->
  11. <service service-name="discovery-springcloud-example-a" filter-value="172.16"/>
  12. </blacklist>
  13. <!-- <whitelist filter-value="">
  14. <service service-name="" filter-value=""/>
  15. </whitelist> -->
  16. </register>
  17. <discovery>
  18. <!-- 服务发现的黑/白名单发现过滤,使用方式跟“服务注册的黑/白名单过滤”一致 -->
  19. <!-- 表示下面所有服务,不允许10.10和11.11为前缀的IP地址被发现(全局过滤) -->
  20. <blacklist filter-value="10.10;11.11">
  21. <!-- 表示下面服务,不允许172.16和10.10和11.11为前缀的IP地址被发现 -->
  22. <service service-name="discovery-springcloud-example-b" filter-value="172.16"/>
  23. </blacklist>
  24. <!-- 服务发现的多版本灰度访问控制 -->
  25. <!-- service-name,表示服务名 -->
  26. <!-- version-value,表示可供访问的版本,如果多个用“;”分隔,不允许出现空格 -->
  27. <version>
  28. <!-- 表示消费端服务a的1.0,允许访问提供端服务b的1.0和1.1版本 -->
  29. <service consumer-service-name="discovery-springcloud-example-a" provider-service-name="discovery-springcloud-example-b" consumer-version-value="1.0" provider-version-value="1.0;1.1"/>
  30. <!-- 表示消费端服务b的1.0,允许访问提供端服务c的1.0和1.1版本 -->
  31. <service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-version-value="1.0" provider-version-value="1.0;1.1"/>
  32. <!-- 表示消费端服务b的1.1,允许访问提供端服务c的1.2版本 -->
  33. <service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-version-value="1.1" provider-version-value="1.2"/>
  34. </version>
  35. </discovery>
  36. </rule>

多版本灰度规则策略

  1. 版本策略介绍
  2. 1. 标准配置,举例如下
  3. <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value="1.0,1.1"/> 表示消费端1.0版本,允许访问提供端1.0和1.1版本
  4. 2. 版本值不配置,举例如下
  5. <service consumer-service-name="a" provider-service-name="b" provider-version-value="1.0,1.1"/> 表示消费端任何版本,允许访问提供端1.0和1.1版本
  6. <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0"/> 表示消费端1.0版本,允许访问提供端任何版本
  7. <service consumer-service-name="a" provider-service-name="b"/> 表示消费端任何版本,允许访问提供端任何版本
  8. 3. 版本值空字符串,举例如下
  9. <service consumer-service-name="a" provider-service-name="b" consumer-version-value="" provider-version-value="1.0,1.1"/> 表示消费端任何版本,允许访问提供端1.0和1.1版本
  10. <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value=""/> 表示消费端1.0版本,允许访问提供端任何版本
  11. <service consumer-service-name="a" provider-service-name="b" consumer-version-value="" provider-version-value=""/> 表示消费端任何版本,允许访问提供端任何版本
  12. 4. 版本对应关系未定义,默认消费端任何版本,允许访问提供端任何版本
  13. 特殊情况处理,在使用上需要极力避免该情况发生
  14. 1. 消费端的application.properties未定义版本号(即eureka.instance.metadataMap.version不存在),则该消费端可以访问提供端任何版本
  15. 2. 提供端的application.properties未定义版本号(即eureka.instance.metadataMap.version不存在),当消费端在xml里不做任何版本配置,才可以访问该提供端

配置中心

跟远程配置中心整合

使用者可以跟携程Apollo,百度DisConf等远程配置中心整合,实现规则读取和订阅

  1. 1. 主动从本地或远程配置中心获取规则
  2. 2. 订阅远程配置中心的规则更新

继承ConfigAdapter.java

  1. public class DiscoveryConfigAdapter extends ConfigAdapter {
  2. // 从本地获取规则
  3. // 通过application.properties里的spring.application.discovery.remote.config.enabled=true,来决定主动从本地,还是远程配置中心获取规则
  4. @Override
  5. protected String getLocalContextPath() {
  6. // 规则文件放在resources目录下
  7. return "classpath:rule.xml";
  8. // 规则文件放在工程根目录下
  9. // return "file:rule.xml";
  10. }
  11. // 从远程配置中心获取规则
  12. @Override
  13. public InputStream getRemoteInputStream() {
  14. InputStream inputStream = ...;
  15. return inputStream;
  16. }
  17. // 订阅远程配置中心的规则更新(推送策略自己决定,可以所有服务都只对应一个规则信息,也可以根据服务名获取对应的规则信息)
  18. @PostConstruct
  19. public void publish() {
  20. InputStream inputStream = ...;
  21. publish(inputStream);
  22. }
  23. }

管理中心

单独推送规则信息

使用者可以通过Rest方式主动向一个微服务推送规则信息,但该方式只能每次推送到一个微服务上(注意:端口号为management.port的值)

  1. @RequestMapping(path = "config", method = RequestMethod.POST)
  2. public Object config(@RequestBody String config)
  1. Url:
  2. http://IP:[management.port]/admin/config

查看当前生效的规则信息

使用者可以通过Rest方式主动请求某个微服务当前生效的规则(注意:端口号为management.port的值)

  1. @RequestMapping(path = "view", method = RequestMethod.GET)
  2. public String view()
  1. Url:
  2. http://IP:[management.port]/admin/view

路由中心

获取本地节点可访问其他节点(根据服务名)的实例列表

  1. @RequestMapping(path = "/instances/{serviceId}", method = RequestMethod.GET)
  2. public List<ServiceInstance> instances(@PathVariable(value = "serviceId") String serviceId)
  1. Url:
  2. http://IP:[server.port]/instances/{serviceId}

获取本地节点的路由信息(只显示当前节点的简单信息,不包含下级路由)

  1. @RequestMapping(path = "/info", method = RequestMethod.GET)
  2. public RouterEntity info()
  1. Url:
  2. http://IP:[server.port]/info

获取本地节点可访问其他节点(根据服务名)的路由信息列表

  1. Java:
  2. @RequestMapping(path = "/route/{routeServiceId}", method = RequestMethod.GET)
  3. public List<RouterEntity> route(@PathVariable(value = "routeServiceId") String routeServiceId)
  4. Url:
  5. http://IP:[server.port]/route/{routeServiceId}

获取指定节点(根据IP和端口)可访问其他节点(根据服务名)的路由信息列表

  1. Java:
  2. @RequestMapping(path = "/route/{routeServiceId}/{routeHost}/{routePort}", method = RequestMethod.GET)
  3. public List<RouterEntity> route(@PathVariable(value = "routeServiceId") String routeServiceId, @PathVariable(value = "routeHost") String routeHost, @PathVariable(value = "routePort") int routePort)
  4. Url:
  5. http://IP:[server.port]/route/{routeServiceId}/{routeHost}/{routePort}

获取全路径的路由信息(serviceIds按调用服务名的前后次序排列,起始节点的服务名不能加上去。如果多个用“;”分隔,不允许出现空格)

  1. Java:
  2. @RequestMapping(path = "/routeAll", method = RequestMethod.POST)
  3. public RouterEntity routeAll(@RequestBody String serviceIds)
  4. Url:
  5. http://IP:[server.port]/routeAll

扩展和自定义更多规则或者监听

使用者可以继承如下类

  1. AbstractRegisterListener,实现服务注册的扩展和监听
  2. AbstractDiscoveryListener,实现服务发现的扩展和监听

示例

场景描述

本例将模拟一个较为复杂的场景,如图1

  1. 1. 调用关系服务A->服务B->服务C
  2. 2. 服务A一个实例,服务B两个实例,服务C三个实例
  3. 3. 规则为服务A只能只能调用服务B的1.0和1.1版本,服务B的1.0版本只能调用服务C的1.0和1.1版本,服务B的1.1版本只能调用服务C的1.2版本
  4. 4. 当一切就绪后,动态切换规则,改变调用的版本对应关系

图1

上述服务分别见discovery-springcloud-example-xx字样的3个工程,对应的版本,端口号如下表

服务 服务端口 管理端口 版本
A 1100 8100 1.0
B1 1200 8200 1.0
B2 1201 8201 1.1
C1 1300 1.0
C2 1301 1.1
C3 1302 1.2
  1. 把discovery-springcloud-example-eureka,并在3个discovery-springcloud-example-xx工程中application.properties的Eureka地址替换掉本地地址

运行效果

黑/白名单的IP地址注册的过滤

  1. 1. 首先example-a或example-b在rule.xml把本地IP地址写入
  2. 2. 启动Application
  3. 3. 抛出禁止注册的异常,即本机不会注册到服务注册发现中心

黑/白名单的IP地址发现的过滤,多版本灰度访问控制(单个微服务需要推送多次,如果是远程配置中心,则推送一次够了)

  1. 1. 启动3个工程共6个Application
  2. 2. 通过Postman或者浏览器,执行GET http://localhost:1100/instances,查看当前A服务可访问B服务的列表
  3. 3. 通过Postman或者浏览器,执行GET http://localhost:1200/instances,查看当前B1服务可访问C服务的列表
  4. 4. 通过Postman或者浏览器,执行GET http://localhost:1201/instances,查看当前B2服务可访问C服务的列表
  5. 5. 通过Postman或者浏览器,执行POST http://localhost:1100/routeAll/,填入discovery-springcloud-example-b;discovery-springcloud-example-c,可以看到路由全路径,如图2
  6. 6. 通过Postman或者浏览器,执行POST http://localhost:8200/admin/config,发送新的规则XML,那么在B1服务上将会运行新的规则,再运行上述步骤,查看服务列表
  7. 7. 通过Postman或者浏览器,执行POST http://localhost:8201/admin/config,发送同样的规则XML,那么在B1服务上将会运行新的规则,再运行上述步骤,查看服务列表
  8. 8. 通过Postman或者浏览器,执行GET http://localhost:8200/admin/view,查看当前在B1服务已经生效的规则
  9. 9. 通过Postman或者浏览器,执行GET http://localhost:8201/admin/view,查看当前在B2服务已经生效的规则
  10. 10.再执行步骤5,可以看到路由全路径将发生变化

图2