介绍

最近在了解网关相关的东西,发现了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选一个接口分别直接拜访和经过网关进行拜访来测试。

Apache ShenYu 入门

直接拜访接口是什么当时运用 http://localhost:8189/test/payment:

Apache ShenYu 入门

根据网关来接口拜访 http://localhdubbo负载均衡的几种方式ost:9195/api/test/test/payment:

Apache ShenYu 入门

经过拜访地址就能够指定上面装备的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框架接入咱们的网关中。

Apache ShenYu 入门

咱们这儿仍是持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来对恳求进行处理的。
这块还没做更深化的研讨,准备放到后面持续来学习。

总结

上文首要便是做了一些简略的入门和浅析,许多初始化是什么意思内容都参考官方文档来学习运用,后续会进一步深化了解和学习。