介绍
最近在了解网关相关的东西,发现了shenyu这款异步的、高性能的、跨言语的、响应式的API
网关。
花了一两天时间做了一个入门体会,在此记录一下。
详细介绍咱们能够到官网去查阅:shenyu.apache.org/zh/docs/ind…
github地址:github.com/apache/incu…
本地运转
在本地发动之前需求先了解两个模初始化电脑块:
1-shenyu-admin : 插件和其他信息装备的管理后台,发动类如下
ShenyuAdminBootstrap
2-shenyu-bootstrap : 用于发动项目,发动类如dubbo面试题下
ShenyuBootstrapApplication
3-在发动之前需求先装备一下db的信息,这儿我挑选mysql,修正shenywebsocket协议u-admin下面的application.yml中的内容如下,然后装备applicatio接口是什么n-mysql.yml中的衔接信息即可。
spring:
profiles:
active: mysql
4-最后初始化一下SQL脚本:
incubator-shenyu/db/init/mysql/schema.sql
5-运转两个发动类 拜访地址: http://localhost:9095/#/home 用户名暗码 admin 123456
到这儿整个网关服务就在本地发动了,可是此时还没有websocket心跳检测咱们自己的服务接入进来。
服务接入入门
咱们能够直接在shenyu-examples中找到想接入的服务端demo。比方http,dubbogithub直播平台永久回家,motan,springmvc,springcloud等等。
这儿我挑选了shenyu-examples-http来进行websocket是什么测试,其实便是一个springboot项目. 由于咱们终究是需求经过网关拜访,需求让网关感知到,因此需求先做一些装备(example中现已装备好,能够挑选修正,这儿我修websocket是什么正了一下contextPath和appNamegithub官网)
application.yml:
shenyu:
register:
registerType: http #zookeeper #etcd #nacos #consul
serverLists: http://localhost:9095 #localhost:2181 #http://localhost:2379 #localhost:8848
props:
username: admin
password: 123456
client:
http:
props:
contextPath: /api/test
appName: testApp
port: 8189
上面首要是进行装备咱github下载们发动的服务怎样注册到网关: registerType代表类型包含http,zk,nacos等,这儿默许是http。 cl接口和抽象类的区别ient中则装备了当时服务在网关中的一些标识。 然后就能够websocket和socket区别将运用发动起来了。接下来能够运用p接口英文ostman进行调用测试.能够从demo中的Httgithub官网登陆入口pTestCgithub永久回家地址on接口crc错误计数troller选一个接口分别直接拜访和经过网关进行拜访来测试。
直接拜访接口是什么当时运用 http://localhost:8189/test/payment:
根据网关来接口拜访 http://localhdubbo负载均衡的几种方式ost:9195/api/test/test/payment:
经过拜访地址就能够指定上面装备的contextPath效果的什么了dubbo是什么。如果根据网关的拜访的途径前缀不是咱们装备的contextPath则会提示如下错: “message”: “初始化divide:Can not find selector, please chegithub中文官网网页ck your configuration!”
运用接入的原接口英文理浅析
上面做了最基础的入门之后,不由想探究一下其背后的原理。所以在接入的github永久回家地址htt初始化是什么意思p example的pom文件中发现其引入了一个starter(springboot中的starter就不介绍了) 官方介绍地址:shenyu.apache.org/zh/docs/des…
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-client-springmvc</artifactId>
<version>${project.version}</version>
</dependency>
然后找到dubbo协议这个starter模块,发现shenyu还供给了其他starter,比方dubbo的,motan的,能够让这些RPC框架接入咱们的网关中。
咱们这儿仍是持websocket协议续看shenyu-spring-boot-stadubbo和feign的区别rter-client-springmvc。在ShenyuSpringMvcClientConfiguration中界说了多个bean,首要看
SpringMvcClientBeanPostProcessor
完成了BeanPostProcessor接口,在bean实例化、依靠注入、初始化完毕时执行会调用postProcessAfterInitialization办法。详细源码如下:
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
// Filter out is not controller out
if (Boolean.TRUE.equals(isFull) || !hasAnnotation(bean.getClass(), Controller.class)) {
return bean;
}
//获取途径,先获取ShenyuSpringMvcClient注解上的,如果没有则获取RequestMapping上的
final ShenyuSpringMvcClient beanShenyuClient = AnnotationUtils.findAnnotation(bean.getClass(), ShenyuSpringMvcClient.class);
final String superPath = buildApiSuperPath(bean.getClass());
// Compatible with previous versions
if (Objects.nonNull(beanShenyuClient) && superPath.contains("*")) {
publisher.publishEvent(buildMetaDataDTO(beanShenyuClient, pathJoin(contextPath, superPath)));
return bean;
}
//获取办法上面先获取ShenyuSpringMvcClient注解,解析path
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
for (Method method : methods) {
final RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
ShenyuSpringMvcClient methodShenyuClient = AnnotationUtils.findAnnotation(method, ShenyuSpringMvcClient.class);
methodShenyuClient = Objects.isNull(methodShenyuClient) ? beanShenyuClient : methodShenyuClient;
// the result of ReflectionUtils#getUniqueDeclaredMethods contains method such as hashCode, wait, toSting
// add Objects.nonNull(requestMapping) to make sure not register wrong method
//
if (Objects.nonNull(methodShenyuClient) && Objects.nonNull(requestMapping)) {
publisher.publishEvent(buildMetaDataDTO(methodShenyuClient, buildApiPath(method, superPath)));
}
}
return bean;
}
//上面终究是将解析的注解构建为MetaDataRegisterDTO,并经过publisher.publishEvent发送出去
private MetaDataRegisterDTO buildMetaDataDTO(@NonNull final ShenyuSpringMvcClient shenyuSpringMvcClient, final String path) {
return MetaDataRegisterDTO.builder()
.contextPath(contextPath) //yml装备的
.appName(appName) //yml装备的
.path(path)
.pathDesc(shenyuSpringMvcClient.desc())
.rpcType(RpcTypeEnum.HTTP.getName())
.enabled(shenyuSpringMvcClient.enabled())
.ruleName(StringUtils.defaultIfBlank(shenyuSpringMvcClient.ruleName(), path))
.registerMetaData(shenyuSpringMvcClient.registerMetaData())
.build();
}
ShenyuClientRegisterEventPublisher
上面的publisher.publishEvent便是指ShenyuClientRegisterEventPublisher。
他是根据Disruptor高性能队列来完成的一个出产消费的模型。
供给了publishEvent办法来出产音讯
而且供给了QueueConsumer来进行异步消费
终究会由RegisterClientConsumerExecutor来进行消费
private final ShenyuClientRegisterEventPublisher publisher = ShenyuClientRegisterEventPublisher.getInstance();
//发动办法,指定了ShenyuClientMetadataExecutorSubscriber和ShenyuClientURIExecutorSubscriber,在RegisterClientConsumerExecutor消费的时分会运用.
public void start(final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
RegisterClientExecutorFactory factory = new RegisterClientExecutorFactory();
factory.addSubscribers(new ShenyuClientMetadataExecutorSubscriber(shenyuClientRegisterRepository));
factory.addSubscribers(new ShenyuClientURIExecutorSubscriber(shenyuClientRegisterRepository));
providerManage = new DisruptorProviderManage<>(factory);
providerManage.startup();
}
//ShenyuClientMetadataExecutorSubscriber的内容便是去向shenyu-admin注册Metadata了。
//ShenyuClientRegisterRepository 便是在starter中界说的bean,下面有介绍,总归咱们example获取到的是HttpClientRegisterRepository
private final ShenyuClientRegisterRepository shenyuClientRegisterRepository;
/**
* Instantiates a new shenyu client metadata executor subscriber.
*
* @param shenyuClientRegisterRepository the shenyu client register repository
*/
public ShenyuClientMetadataExecutorSubscriber(finalShenyuClientRegisterRepository shenyuClientRegisterRepository) {
this.shenyuClientRegisterRepository = shenyuClientRegisterRepository;
}
@Override
public DataType getType() {
return DataType.META_DATA;
}
//音讯类型是DataType.META_DATA的,顾客终究会调用此办法处理音讯
@Override
public void executor(final Collection<MetaDataRegisterDTO> metaDataRegisterDTOList) {
for (MetaDataRegisterDTO metaDataRegisterDTO : metaDataRegisterDTOList) {
shenyuClientRegisterRepository.persistInterface(metaDataRegisterDTO);
}
}
//详细的完成便是根据http恳求将metaData注册到了admin中
@Override
public void doPersistInterface(final MetaDataRegisterDTO metadata) {
doRegister(metadata, Constants.META_PATH, Constants.META_TYPE);
}
接口地址:
String META_PATH = "/shenyu-client/register-metadata";
咱们能够在shenyu-admin中的ShenyuClientHttpRegistryController中找到对应的地址。
shenyu-admin怎样接纳新的信息变化在后面会持续阐明。这儿先了解.
ContextRegisterListener
在发动的时分往publisher中出产URIRegisterDTO类型的音讯
@Override
public void onApplicationEvent(@NonNull final ContextRefreshedEvent contextRefreshedEvent) {
if (!registered.compareAndSet(false, true)) {
return;
}
if (Boolean.TRUE.equals(isFull)) {
publisher.publishEvent(buildMetaDataDTO());
}
try {
final int mergedPort = port <= 0 ? PortUtils.findPort(beanFactory) : port;
publisher.publishEvent(buildURIRegisterDTO(mergedPort));
} catch (ShenyuException e) {
throw new ShenyuException(e.getMessage() + "please config ${shenyu.client.http.props.port} in xml/yml !");
}
}
private URIRegisterDTO buildURIRegisterDTO(final int port) {
return URIRegisterDTO.builder()
.contextPath(this.contextPath)
.appName(appName)
.protocol(protocol)
.host(IpUtils.isCompleteHost(this.host) ? this.host : IpUtils.getHost(this.host))
.port(port)
.rpcType(RpcTypeEnum.HTTP.getName())
.build();
}
ShenyuClientRegisterRepository
依据装备来获取详细的完成,默许是http
/**
* New instance shenyu client register repository.
*
* @param shenyuRegisterCenterConfig the shenyu register center config
* @return the shenyu client register repository
*/
public static ShenyuClientRegisterRepository newInstance(final ShenyuRegisterCenterConfig shenyuRegisterCenterConfig) {
if (!REPOSITORY_MAP.containsKey(shenyuRegisterCenterConfig.getRegisterType())) {
//spi机制获取详细完成,咱们的demo中是HttpClientRegisterRepository
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());
}
到这儿咱们的运用现已将信息奉告到了shenyu-admin。
s初始化磁盘henyu-admin怎样接纳更新的音讯
shenyu-admin作为管理后台会将数据存储到db中,而且同步数据到网关服务。
在上面github官网登陆入口http example中现已知道了咱们的服务是根据 ShenyuClientRegisterRepository 来像shenyu-admin来注册Metgithub永久回家地址aData等信息的。
Shenydubbo面试题uClientRegisterRepository有多种完成依据咱们的装备来进行实例化。如http,na初始化游戏启动器失败cos… 现在就来看看admin这儿是怎样接纳注册音讯的。
根据http的注册方式是根github汤姆据ShenyuClientHttpRegistryCgithub官网ontroller里边的接口来接纳音讯完成注册的
@PostMapping("/register-metadata")
@ResponseBody
public String registerMetadata(@RequestBody final MetaDataRegisterDTO metaDataRegisterDTO) {
publisher.publish(metaDataRegisterDTO);
return ShenyuResultMessage.SUCCESS;
}
也能够看一下根据nacos的注册方式,如果是根据nacos进行注册,则shenyu-admin就会依靠 shengithub是什么yu-register-client-server-nacos模块来监听注册信息。
//shenyu admin发动的时分会初始化bean,和注册ShenyuClientRegisterRepository相呼应
@Bean(destroyMethod = "close")
public ShenyuClientServerRegisterRepository shenyuClientServerRegisterRepository(final ShenyuRegisterCenterConfig shenyuRegisterCenterConfig,
final List<ShenyuClientRegisterService> shenyuClientRegisterService) {
String registerType = shenyuRegisterCenterConfig.getRegisterType();
ShenyuClientServerRegisterRepository registerRepository = ExtensionLoader.getExtensionLoader(ShenyuClientServerRegisterRepository.class).getJoin(registerType);
RegisterClientServerDisruptorPublisher publisher = RegisterClientServerDisruptorPublisher.getInstance();
Map<String, ShenyuClientRegisterService> registerServiceMap = shenyuClientRegisterService.stream().collect(Collectors.toMap(ShenyuClientRegisterService::rpcType, e -> e));
publisher.start(registerServiceMap);
registerRepository.init(publisher, shenyuRegisterCenterConfig);
return registerRepository;
}
//根据nacos的完成类
NacosClientServerRegisterRepository
//上面声明bean的时分就会调用其init办法。终究会调用subscribe办法进行监听
try {
this.configService = ConfigFactory.createConfigService(nacosProperties);
this.namingService = NamingFactory.createNamingService(nacosProperties);
} catch (NacosException e) {
throw new ShenyuException(e);
}
subscribe();
//subscribe办法终究会调用到,便是根据nacos的API来监听
private void subscribeMetadata(final String serviceConfigName) {
registerMetadata(readData(serviceConfigName));
LOGGER.info("subscribe metadata: {}", serviceConfigName);
try {
configService.addListener(serviceConfigName, defaultGroup, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(final String config) {
registerMetadata(config);
}
});
} catch (NacosException e) {
throw new ShenyuException(e);
}
}
//终究仍是会调用publisher.publish,和根据http的接口 /register-metadata 终究的完成是相同的。
private void publishMetadata(final String data) {
LOGGER.info("publish metadata: {}", data);
publisher.publish(Lists.newArrayList(GsonUtis.getInstance().fromJson(data, MetaDataRegisterDTO.class)));
}
//这儿的publisher 和上面介绍服务接入中的publisher原理是相同的
也是根据Disruptor高性能队列来完成的一个出产消费的模型。
//顾客终究会调用 MetadataExecutorSubscriber中的
shenyuClientRegisterService.register(metaDataRegisterDTO);
//这儿register 了
public String register(final MetaDataRegisterDTO dto) {
//handler plugin selector
String selectorHandler = selectorHandler(dto);
String selectorId = selectorService.registerDefault(dto, PluginNameAdapter.rpcTypeAdapter(rpcType()), selectorHandler);
//handler selector rule
String ruleHandler = ruleHandler();
RuleDTO ruleDTO = buildRpcDefaultRuleDTO(selectorId, dto, ruleHandler);
ruleService.registerDefault(ruleDTO);
//handler register metadata
registerMetadata(dto);
//handler context path
String contextPath = dto.getContextPath();
if (StringUtils.isNotEmpty(contextPath)) {
registerContextPath(dto);
}
return ShenyuResultMessage.SUCCESS;
}
根据http的完成类是:ShenyuClientRegisterDivideServiceImpl
protected void registerMetadata(final MetaDataRegisterDTO dto) {
if (dto.isRegisterMetaData()) {
MetaDataService metaDataService = getMetaDataService();
MetaDataDO exist = metaDataService.findByPath(dto.getPath());
metaDataService.saveOrUpdateMetaData(exist, dto);
}
}
//终究会入库,而且进行了一个eventPublisher.publishEvent操作,这个操作便是同步信息到网关。后面概况阐明一下。
public void saveOrUpdateMetaData(final MetaDataDO exist, final MetaDataRegisterDTO metaDataDTO) {
DataEventTypeEnum eventType;
MetaDataDO metaDataDO = MetaDataTransfer.INSTANCE.mapRegisterDTOToEntity(metaDataDTO);
if (Objects.isNull(exist)) {
Timestamp currentTime = new Timestamp(System.currentTimeMillis());
metaDataDO.setId(UUIDUtils.getInstance().generateShortUuid());
metaDataDO.setDateCreated(currentTime);
metaDataDO.setDateUpdated(currentTime);
metaDataMapper.insert(metaDataDO);
eventType = DataEventTypeEnum.CREATE;
} else {
metaDataDO.setId(exist.getId());
metaDataMapper.update(metaDataDO);
eventType = DataEventTypeEnum.UPDATE;
}
// publish MetaData's event
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, eventType,
Collections.singletonList(MetaDataTransfer.INSTANCE.mapToData(metaDataDO))));
}
到这儿咱们大致了解websocket面试题了整个MetaData(URIRegister原理相同)数据从服务注册到shenyu-admin的整个流程,后面就能够看看是怎样将数据同步到网关的了。也便是上面github直播平台永久回家说到的:eventPublisher.publishEvent(new Da接口测试用例设计taChangedEvent接口类型 …..)
shenyu-admin同步数据到网关
上面的websocket原理eventPublisher接口文档是ApplicationEventPublisher,它是spring自带的发布监听的功用。
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, eventType,
Collections.singletonList(MetaDataTransfer.INSTANCE.mapToData(metaDataDO))));
//找到监听的当地DataChangedEventDispatcher,发布的音讯会在onApplicationEvent办法中接纳
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
case APP_AUTH:
listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
break;
case PLUGIN:
listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
break;
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
applicationContext.getBean(LoadServiceDocEntry.class).loadDocOnSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
break;
case META_DATA:
listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
//listener有多个完成类,究竟运用的是哪一个
//先来看看DataSyncConfiguration装备类,里边装备了经过哪种方式同步数据到网关
shenyu-admin中的application.yml中找到装备的当地:
默许是websocket:
sync:
websocket:
enabled: true
messageMaxSize: 10240
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
# http:
# enabled: true
还有nacos等...
如果是websocket则listener对应的是WebsocketDataChangedListener
如果是http则listener对应的是HttpLongPollingDataChangedListener
nacos对应的是NacosDataChangedListener
其他的能够自己检查一下。
如果是根据websocket,admin则会和网关服务建立了websocket衔接,然后发送音讯到网关。
shenyu-admin这儿的DataChangedListener会和网关的SyncDataService相呼应。比方WebsocketDataChangedListener 就对应了网关的WebsocketSyncDataService。
网关同步数据的功用都会集在shenyu-sync-data-center模块中,也是供给了多种完成对应admin中同步数据的方式。
也是根据装备来看看究竟运用哪个SyncDataService。下面是websocket的装备:
@Configuration
@ConditionalOnClass(WebsocketSyncDataService.class)
@ConditionalOnProperty(prefix = "shenyu.sync.websocket", name = "urls")
网关调用服务
关于网关是怎样调用到服务的,这块首要是根据ShenyuWebHandler来对恳求进行处理的。
这块还没做更深化的研讨,准备放到后面持续来学习。
总结
上文首要便是做了一些简略的入门和浅析,许多初始化是什么意思内容都参考官方文档来学习运用,后续会进一步深化了解和学习。