Apache ShenYu 是一个异步的,高性能的,跨语言的,响应式的
API
网关。
在ShenYu
网关中,注册中心是用于将客户端信息注册到shenyu-admin
,admin
再通过数据同步将这些信息同步到网关,网关通过这些数据完成流量筛选。客户端信息主要包括接口信息
和URI信息
。
本文基于
shenyu-2.4.1
版本进行源码分析,官网的介绍请参考 客户端接入原理 。
1. 注册中心原理
当客户端启动时,读取接口信息和uri信息
,通过指定的注册类型,将数据发送到shenyu-admin
。
图中的注册中心需要用户指定使用哪种注册类型,ShenYu
当前支持Http
、Zookeeper
、Etcd
、Consul
和Nacos
进行注册。具体如何配置请参考 客户端接入配置 。
ShenYu
在注册中心的原理设计上引入了Disruptor
,Disruptor
队列在其中起到数据与操作解耦,利于扩展。如果注册请求过多,导致注册异常,也有数据缓冲作用。
如图所示,注册中心分为两个部分,一是注册中心客户端register-client
,负载处理客户端数据读取。另一个是注册中心服务端register-server
,负载处理服务端(就是shenyu-admin
)数据写入。通过指定注册类型进行数据发送和接收。
- 客户端:通常来说就是一个微服务,可以是
springmvc
,spring-cloud
,dubbo
,grpc
等。 register-client
:注册中心客户端,读取客户接口和uri
信息。Disruptor
:数据与操作解耦,数据缓冲作用。register-server
:注册中心服务端,这里就是shenyu-admin
,接收数据,写入数据库,发数据同步事件。- 注册类型:指定注册类型,完成数据注册,当前支持
Http
、Zookeeper
、Etcd
、Consul
和Nacos
。
本文分析的是使用Http
的方式进行注册,所以具体的处理流程如下:
在客户端,数据出队列后,通过http
传输数据,在服务端,提供相应的接口,接收数据,然后写入队列。
2. 客户端注册流程
当客户端启动后,根据相关配置,读取属性信息,然后写入队列。以官方提供的 shenyu-examples-http 为例,开始源码分析。官方提供的例子是一个由springboot
构建的微服务。注册中心的相关配置可以参考官网 客户端接入配置 。
2.1 加载配置,读取属性
先用一张图串联下注册中心客户端初始化流程:
我们分析的是通过http
的方式进行注册,所以需要进行如下配置:
shenyu:
register:
registerType: http
serverLists: http://localhost:9095
client:
http:
props:
contextPath: /http
appName: http
port: 8189
isFull: false
每个属性表示的含义如下:
registerType
: 服务注册类型,填写http
。serverList
: 为http
注册类型时,填写Shenyu-Admin
项目的地址,注意加上http://
,多个地址用英文逗号分隔。port
: 你本项目的启动端口,目前springmvc/tars/grpc
需要进行填写。contextPath
: 为你的这个mvc
项目在shenyu
网关的路由前缀, 比如/order
,/product
等等,网关会根据你的这个前缀来进行路由。appName
:你的应用名称,不配置的话,会默认取spring.application.name
的值。isFull
: 设置true
代表代理你的整个服务,false
表示代理你其中某几个controller
;目前适用于springmvc/springcloud
。
项目启动后,会先加载配置文件,读取属性信息,生成相应的Bean
。
首先读取到的配置文件是 ShenyuSpringMvcClientConfiguration
,它是shenyu
客户端http
注册配置类,通过@Configuration
表示这是一个配置类,通过@ImportAutoConfiguration
引入其他配置类。创建SpringMvcClientBeanPostProcessor
,主要处理元数据。创建ContextRegisterListener
,主要处理 URI
信息。
/**
* shenyu 客户端http注册配置类
*/
@Configuration
@ImportAutoConfiguration(ShenyuClientCommonBeanConfiguration.class)
public class ShenyuSpringMvcClientConfiguration {
//创建SpringMvcClientBeanPostProcessor,主要处理元数据
@Bean
public SpringMvcClientBeanPostProcessor springHttpClientBeanPostProcessor(final ShenyuClientConfig clientConfig,final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
return new SpringMvcClientBeanPostProcessor(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()), shenyuClientRegisterRepository);
}
// 创建ContextRegisterListener,主要处理 URI信息
@Bean
public ContextRegisterListener contextRegisterListener(final ShenyuClientConfig clientConfig) {
return new ContextRegisterListener(clientConfig.getClient().get(RpcTypeEnum.HTTP.getName()));
}
}
ShenyuClientCommonBeanConfiguration
是shenyu
客户端通用配置类,会创建注册中心客户端通用的bean
。
- 创建
ShenyuClientRegisterRepository
,通过工厂类创建而成。 - 创建
ShenyuRegisterCenterConfig
,读取shenyu.register
属性配置。 - 创建
ShenyuClientConfig
,读取shenyu.client
属性配置。
/**
* shenyu客户端通用配置类
*/
@Configuration
public class ShenyuClientCommonBeanConfiguration {
// 创建ShenyuClientRegisterRepository,通过工厂类创建而成。
@Bean
public ShenyuClientRegisterRepository shenyuClientRegisterRepository(final ShenyuRegisterCenterConfig config) {
return ShenyuClientRegisterRepositoryFactory.newInstance(config);
}
// 创建ShenyuRegisterCenterConfig,读取shenyu.register属性配置
@Bean
@ConfigurationProperties(prefix = "shenyu.register")
public ShenyuRegisterCenterConfig shenyuRegisterCenterConfig() {
return new ShenyuRegisterCenterConfig();
}
// 创建ShenyuClientConfig,读取shenyu.client属性配置
@Bean
@ConfigurationProperties(prefix = "shenyu")
public ShenyuClientConfig shenyuClientConfig() {
return new ShenyuClientConfig();
}
}
2.2 用于注册的 HttpClientRegisterRepository
上面的配置文件中生成的ShenyuClientRegisterRepository
是客户端注册的具体实现,它是一个接口,它的实现类如下。
HttpClientRegisterRepository
:通过http
进行注册;ConsulClientRegisterRepository
:通过Consul
进行注册;EtcdClientRegisterRepository
:通过Etcd
进行注册;NacosClientRegisterRepository
:通过nacos
进行注册;ZookeeperClientRegisterRepository
通过Zookeeper
进行注册。
具体是哪一种方式,是通过SPI
进行加载实现的,实现逻辑如下:
/**
* 加载 ShenyuClientRegisterRepository
*/
public final class ShenyuClientRegisterRepositoryFactory {
private static final Map<String, ShenyuClientRegisterRepository> REPOSITORY_MAP = new ConcurrentHashMap<>();
/**
* 创建 ShenyuClientRegisterRepository
*/
public static ShenyuClientRegisterRepository newInstance(final ShenyuRegisterCenterConfig shenyuRegisterCenterConfig) {
if (!REPOSITORY_MAP.containsKey(shenyuRegisterCenterConfig.getRegisterType())) {
// 通过SPI的方式进行加载,类型由registerType决定
ShenyuClientRegisterRepository result = ExtensionLoader.getExtensionLoader(ShenyuClientRegisterRepository.class).getJoin(shenyuRegisterCenterConfig.getRegisterType());
//执行初始化操作
result.init(shenyuRegisterCenterConfig);
ShenyuClientShutdownHook.set(result, shenyuRegisterCenterConfig.getProps());
REPOSITORY_MAP.put(shenyuRegisterCenterConfig.getRegisterType(), result);
return result;
}
return REPOSITORY_MAP.get(shenyuRegisterCenterConfig.getRegisterType());
}
}
加载类型通过registerType
指定,也就是我们在配置文件中指定的类型:
shenyu:
register:
registerType: http
serverLists: http://localhost:9095
我们指定的是http
,所以会去加载HttpClientRegisterRepository
。对象创建成功后,执行的初始化方法init()
如下:
@Join
public class HttpClientRegisterRepository implements ShenyuClientRegisterRepository {
@Override
public void init(final ShenyuRegisterCenterConfig config) {
this.serverList = Lists.newArrayList(Splitter.on(",").split(config.getServerLists()));
}
// 暂时省略其他逻辑
}
读取配置文件中的serverLists
,即sheenyu-admin
的地址,为后续数据发送做准备。类注解@Join
用于SPI
的加载。
SPI
全称为Service Provider Interface
, 是JDK
内置的一种服务提供发现功能, 一种动态替换发现的机制。shenyu-spi 是
Apache ShenYu
网关自定义的SPI
扩展实现,设计和实现原理参考了Dubbo
的 SPI扩展实现 。
2.3 构建元数据的 SpringMvcClientBeanPostProcessor
创建SpringMvcClientBeanPostProcessor
,负责元数据的构建和注册,它的构造函数逻辑如下:
/**
* spring mvc 客户端bean的后置处理器
*/
public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {
/**
* 通过构造函数进行实例化
*/
public SpringMvcClientBeanPostProcessor(final PropertiesConfig clientConfig,
final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
// 读取配置属性
Properties props = clientConfig.getProps();
// 获取端口信息,并校验
int port = Integer.parseInt(props.getProperty(ShenyuClientConstants.PORT));
if (port <= 0) {
String errorMsg = "http register param must config the port must > 0";
LOG.error(errorMsg);
throw new ShenyuClientIllegalArgumentException(errorMsg);
}
// 获取appName
this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
// 获取contextPath
this.contextPath = props.getProperty(ShenyuClientConstants.CONTEXT_PATH);
// 校验appName和contextPath
if (StringUtils.isBlank(appName) && StringUtils.isBlank(contextPath)) {
String errorMsg = "http register param must config the appName or contextPath";
LOG.error(errorMsg);
throw new ShenyuClientIllegalArgumentException(errorMsg);
}
// 获取 isFull
this.isFull = Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL, Boolean.FALSE.toString()));
// 开始事件发布
publisher.start(shenyuClientRegisterRepository);
}
// 暂时省略了其他逻辑
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
// 暂时省略了其他逻辑
}
}
在构造函数中,主要是读取属性信息,然后进行校验。
shenyu:
client:
http:
props:
contextPath: /http
appName: http
port: 8189
isFull: false
最后,执行了publisher.start()
,开始事件发布,为注册做准备。
- ShenyuClientRegisterEventPublisher
ShenyuClientRegisterEventPublisher
通过单例模式实现,主要是生成元数据
和URI
订阅器(后续用于数据发布),然后启动Disruptor
队列。提供了一个共有方法publishEvent()
,发布事件,向Disruptor队列发数据。
public class ShenyuClientRegisterEventPublisher {
// 私有变量
private static final ShenyuClientRegisterEventPublisher INSTANCE = new ShenyuClientRegisterEventPublisher();
private DisruptorProviderManage providerManage;
private RegisterClientExecutorFactory factory;
/**
* 公开静态方法
*
* @return ShenyuClientRegisterEventPublisher instance
*/
public static ShenyuClientRegisterEventPublisher getInstance() {
return INSTANCE;
}
/**
* Start方法执行
*
* @param shenyuClientRegisterRepository shenyuClientRegisterRepository
*/
public void start(final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
// 创建客户端注册工厂类
factory = new RegisterClientExecutorFactory();
// 添加元数据订阅器
factory.addSubscribers(new ShenyuClientMetadataExecutorSubscriber(shenyuClientRegisterRepository));
// 添加URI订阅器
factory.addSubscribers(new ShenyuClientURIExecutorSubscriber(shenyuClientRegisterRepository));
// 启动Disruptor队列
providerManage = new DisruptorProviderManage(factory);
providerManage.startup();
}
/**
* 发布事件,向Disruptor队列发数据
*
* @param <T> the type parameter
* @param data the data
*/
public <T> void publishEvent(final T data) {
DisruptorProvider<Object> provider = providerManage.