DangerWind-RPC-framework---一、服务注册与发现

       服务的注册与发现借助Spring Bean的生命周期来完成。

       Spring Bean的生命周期的步骤中包含以下几步:调用aware接口、BeanPostProcessor 执行postProcessBeforeInitialization方法、BeanPostProcessor 执行postProcessAfterInitialization方法。现在需要利用这三个步骤来实现服务的注册与发现。

       为了完成服务的注册与发现,主要需要完成以下步骤:定义RPC服务(对应注解)的扫描包范围,装配RPC服务端服务的Bean到IOC容器当中;将服务发布到注册中心。这两步可以分别对应“调用aware接口、BeanPostProcessor 执行postProcessBeforeInitialization方法”。而“BeanPostProcessor 执行postProcessAfterInitialization方法这一步骤”可以用来生成并填充客户端动态代理类。

       首先设计注解RpcScan,其中定义包扫描范围。其中,Import注解是一个Spring提供的注解,用于导入一个或多个类到Spring容器中。这些类可以是配置类(带有@Configuration注解的类),也可以是实现了ImportSelector或ImportBeanDefinitionRegistrar接口的类(本次使用)。导入的类会被Spring容器处理,从而实现对Spring配置的扩展或自定义。@RpcScan注解定义时,通过@Import(CustomScannerRegistrar.class)引入CustomScannerRegistrar,意味着当任何一个Spring配置类上使用了@RpcScan注解,CustomScannerRegistrar也会被自动导入到Spring的配置中。这样,CustomScannerRegistrar的逻辑就会被执行,这通常涉及到自定义的扫描逻辑,比如扫描某些特定的注解,并将扫描到的类注册为Spring容器中的bean。只有标注了@RpcScan注解的类才会触发CustomScannerRegistrar中的逻辑。这是因为@RpcScan注解上使用了@Import(CustomScannerRegistrar.class),这意味着当Spring框架解析到某个类上使用了@RpcScan注解时,它会自动导入CustomScannerRegistrar类,并执行其中定义的逻辑。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomScannerRegistrar.class)
@Documented
public @interface RpcScan {

    String[] basePackage();

}

        注册Bean的CustomScannerRegistrar需要实现两个接口,ImportBeanDefinitionRegistrar和 ResourceLoaderAware,实现ResourceLoaderAware接口是为了获取ResourceLoader,以便加载资源(加载Bean)。实现ImportBeanDefinitionRegistrar允许在Spring容器启动时动态注册Bean定义。通过实现该接口,可以在运行时根据特定条件注册Bean,而不是在编译时静态定义。二者对应的setResourceLoader和registerBeanDefinitions都在BeanPostProcessor执行之前执行。

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;

    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // RpcScan注解上获取的map键值对封装到AnnotationAttributes对象中
        AnnotationAttributes rpcScanAnnotationAttributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName()));
        String[] rpcScanBasePackages = new String[0];
        if (rpcScanAnnotationAttributes != null) {
            // 获取属性
            rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
        }
        if (rpcScanBasePackages.length == 0) {
            // 没获取到,用注解所在包
            rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
        }
        // 扫描RpcService annotation
        CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class);
        // 扫描Component annotation
        CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class);
        if (resourceLoader != null) {
            // 设置加载用的resourceLoader
            rpcServiceScanner.setResourceLoader(resourceLoader);
            springBeanScanner.setResourceLoader(resourceLoader);
        }
        int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE);
        log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
        // 扫描到后会通过CustomScanner初始化时的beanDefinitionRegistry注册
        int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
        log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);

    }

           CustomScanner继承自ClassPathBeanDefinitionScanner,封装了构造器支持扫描特定注解并使用BeanDefinitionRegistry进行注册:

public class CustomScanner extends ClassPathBeanDefinitionScanner {

    public CustomScanner(BeanDefinitionRegistry registry, Class<? extends Annotation> annoType) {
        super(registry);
        super.addIncludeFilter(new AnnotationTypeFilter(annoType));
    }

    @Override
    public int scan(String... basePackages) {
        return super.scan(basePackages);
    }
}

       通过上述步骤将RPC服务端的实现类加载到了IOC容器中,接下来是向注册中心发起注册的过程,主要在BeanPostProcessor执行postProcessBeforeInitialization方法过程中实现。

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().isAnnotationPresent(RpcService.class)) {
            log.info("[{}] is annotated with  [{}]", bean.getClass().getName(), RpcService.class.getCanonicalName());
            // get RpcService annotation
            RpcService rpcService = bean.getClass().getAnnotation(RpcService.class);
            // build RpcServiceProperties
            RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
                    .group(rpcService.group())
                    .version(rpcService.version())
                    .service(bean).build();
            // 发布服务
            serviceProvider.publishService(rpcServiceConfig);
        }
        return bean;
    }

         首先获取注解当中的信息,之后发布服务到注册中心。publishService的过程如下:

    @Override
    public void publishService(RpcServiceConfig rpcServiceConfig) {
        try {
            String host = InetAddress.getLocalHost().getHostAddress();
            this.addService(rpcServiceConfig);
            serviceRegistry.registerService(rpcServiceConfig.getRpcServiceName(), new InetSocketAddress(host, NettyRpcServer.PORT));
        } catch (UnknownHostException e) {
            log.error("occur exception when getHostAddress", e);
        }
    }

    @Override
    public void addService(RpcServiceConfig rpcServiceConfig) {
        String rpcServiceName = rpcServiceConfig.getRpcServiceName();
        if (registeredService.contains(rpcServiceName)) {
            return;
        }
        registeredService.add(rpcServiceName);
        serviceMap.put(rpcServiceName, rpcServiceConfig.getService());
        log.info("Add service: {} and interfaces:{}", rpcServiceName, rpcServiceConfig.getService().getClass().getInterfaces());
    }

          首先对于addService方法,需要将服务名保存到本地内存中,使用线程安全的Set集合与Map集合进行存储。之后向注册中心注册,注册中心目前使用ZK,并且通过SPI(后续进行介绍)提供扩展机制。

    @Override
    public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) {
        String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString();
        CuratorFramework zkClient = CuratorUtils.getZkClient();
        CuratorUtils.createPersistentNode(zkClient, servicePath);
    }

    // rpcServiceName的生成方式
    public String getRpcServiceName() {
        return this.getServiceName() + this.getGroup() + this.getVersion();
    }

    // 通过ZK客户端创建持久化节点的过程
    public static void createPersistentNode(CuratorFramework zkClient, String path) {
        try {
            if (REGISTERED_PATH_SET.contains(path) || zkClient.checkExists().forPath(path) != null) {
                log.info("The node already exists. The node is:[{}]", path);
            } else {
                //eg: /danger-wind-rpc/github.dangerwind.XXService/127.0.0.1:9999
                zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path);
                log.info("The node was created successfully. The node is:[{}]", path);
            }
            REGISTERED_PATH_SET.add(path);
        } catch (Exception e) {
            log.error("create persistent node for path [{}] fail", path);
        }
    }

        由于ZK是树形结构, 因此RPC client端在拉取服务列表时可以通过拼接前缀与类名来拉取到对应服务的所有子节点,这样设计便于了客户端获取服务列表。

        按上述步骤就完成了RPC server端启动时向注册中心注册服务的过程。

           

相关推荐

  1. DangerWind-RPC-framework---服务注册发现

    2024-07-10 14:56:04       20 阅读
  2. DangerWind-RPC-framework---三、服务端下机

    2024-07-10 14:56:04       18 阅读
  3. DangerWind-RPC-framework---五、服务端的反射调用

    2024-07-10 14:56:04       21 阅读
  4. DangerWind-RPC-framework---四、SPI

    2024-07-10 14:56:04       18 阅读
  5. DangerWind-RPC-framework---七、序列化算法

    2024-07-10 14:56:04       20 阅读
  6. DangerWind-RPC-framework---六、负载均衡

    2024-07-10 14:56:04       18 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 14:56:04       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 14:56:04       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 14:56:04       45 阅读
  4. Python语言-面向对象

    2024-07-10 14:56:04       55 阅读

热门阅读

  1. spring监听事件

    2024-07-10 14:56:04       19 阅读
  2. 网络基础——udp协议

    2024-07-10 14:56:04       19 阅读
  3. Python从Excel表中查找指定数据填入新表

    2024-07-10 14:56:04       19 阅读
  4. 数据库和缓存基础(mysql,redis等)

    2024-07-10 14:56:04       17 阅读
  5. TortoiseSVN 使用教程

    2024-07-10 14:56:04       21 阅读
  6. led_strip例程分析

    2024-07-10 14:56:04       16 阅读
  7. 一个用于在虚拟桌面下跑chrome的docker镜像

    2024-07-10 14:56:04       17 阅读
  8. 东方通Tongweb发布vue前端,HSTS漏洞修复

    2024-07-10 14:56:04       17 阅读
  9. 【无标题】

    2024-07-10 14:56:04       17 阅读
  10. Redis教程(二十三):Redis的底层数据结构

    2024-07-10 14:56:04       21 阅读
  11. 博客网站目录网址导航自适应主题php源码

    2024-07-10 14:56:04       18 阅读