分布式服务框架-服务发布与服务引入

538 阅读5分钟

这是我参与8月更文挑战的第20天,活动详情查看: 8月更文挑战

本文使用源码地址:simple-rpc

simple-rpc中提供了两种服务发布与引入的方式:自定义标签和自定义注解。

自定义标签

自定义标签是Spring为开发者提供的扩展功能,通过定制XML标签可以让我们更加简洁地实现配置。

自定义标签包含如下几步:

  • 编写.schemas文件,通知spring容器我们定义的xsd文件在哪里;
  • 编写.xsd文件,定义配置时可以使用的属性限制或者说支持的那些属性配置;
  • 编写.handlers 文件,扩展NamespaceHandler命名空间注册器和定义解析器;

这三个文件都存放在resources/META-INF目录下。

spring.schemas

http\://www.miracle.com/schema/simple-rpc.xsd=META-INF/simple-rpc.xsd

simple-rpc.xsd

... 略
    <xsd:element name="service">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="interface" type="xsd:string" use="required"/>
                    <xsd:attribute name="timeout" type="xsd:int"/>
                    <xsd:attribute name="ref" type="xsd:string" use="required"/>
                    <xsd:attribute name="weight" type="xsd:int"/>
                    <xsd:attribute name="workerThreads" type="xsd:int"/>
                    <xsd:attribute name="groupName" type="xsd:string"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
  • interface:服务接口:用于注册在服务注册中心,服务调用端获取后缓存再本地用于发起服务调用;
  • timeout:超时时间:控制服务端超时时间 ms;
  • ref:服务实现类:服务调用;
  • weight:服务提供者权重:配置该机器在集群中的权重,用于某些负载均衡算法;
  • workerThreads:服务端线程数:限制服务端改服务线程数,服务端限流;
  • groupName:服务分组组名:可用于分组灰度发布,配置不同分组,可以让调用都路由到配置了相同分组的路由上。
<xsd:element name="reference">
    <xsd:complexType>
        <xsd:complexContent>
            <xsd:extension base="beans:identifiedType">
                <xsd:attribute name="interface" type="xsd:string" use="required"/>
                <xsd:attribute name="timeout" type="xsd:int"/>
                <xsd:attribute name="consumeThreads" type="xsd:int"/>
                <xsd:attribute name="loadBalanceStrategy" type="xsd:string"/>
                <xsd:attribute name="remoteAppKey" type="xsd:string" use="required"/>
                <xsd:attribute name="groupName" type="xsd:string"/>
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:element>
  • interface:服务接口:匹配从注册中心获取到本地的服务提供者,得到服务提供者列表,再根据负载均衡策略选取一个发起服务调用;
  • timeout:超时时间:服务调用超时时间;
  • consumeThreads:调用者线程数;
  • loadBalanceStrategy:负载均衡策略 默认3:轮询;
  • remoteAppKey:服务提供者唯一标识;
  • groupName:服务分组组名。

spring.handlers

http\://www.miracle.com/schema/simple-rpc=com.miracle.srpc.spring.SRpcNamespaceHandler

上面的几个文件做了解即可,下面主要来说一下如何扩展自己的NamespaceHandler

@Slf4j
public class SRpcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("service", new ProviderFactoryBeanDefinitionParser());
        registerBeanDefinitionParser("reference", new DiscoverFactoryBeanDefinitionParser());
    }

    private class ProviderFactoryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

        @Override
        protected Class<?> getBeanClass(Element element) {
            return ProviderFactoryBean.class;
        }

        @Override
        protected void doParse(Element element, BeanDefinitionBuilder builder) {
            // ... builder.addPropertyValue过程 略
        }
    }

    private class DiscoverFactoryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

        @Override
        protected Class<?> getBeanClass(Element element) {
            return DiscoverFactoryBean.class;
        }

        @Override
        protected void doParse(Element element, BeanDefinitionBuilder builder) {
            // ... builder.addPropertyValue过程 略
        }
    }
    
}

代码其实也很简单,我们继承了NamespaceHandlerSupport类,实现了init()方法,在这个方法中注册了两个BeanDefinitionParser,分别对应我们在simple-rpc.xsd文件中定义的servicereference。可以看到 ,最后我们getBeanClass()方法返回的都是一个FatoryBean。在我们的开发工作中应该都见过或使用过FactoryBean这个类,也许你会看成了BeanFactory这个类。FactoryBeanBeanFactory虽然长的很像,但是他们的作用确实完全不像。这里你可以想象一下,你会在什么样的场景下使用FactoryBean这个接口?FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。

ProviderFactoryBeanDiscoverFactoryBean中还分别实现了InitializingBean接口。这个接口只有一个方法void afterPropertiesSet(),通过实现这个方法我们可以在bean执行初始化时执行一些特定逻辑,这正是我们想要的。我们可以将服务注册和服务发现的逻辑放入其中,以求在容器启动bean初始化时完成服务向zookeeper注册和将引入的服务拉取到本地缓存。

我们来看下具体实现:

// ProviderFactoryBean.class
@Override
public void afterPropertiesSet() {

    // 1.启动服务
    NettyServer.getInstance().startServer(SimpleRpcPropertiesUtil.getServerPort());
    // 2. 将服务打包注册到注册中心
    List<Provider> providers = buildProviderService();
    RegisterCenterImpl.getInstance().registerProvider(providers);
}

// DiscoverFactoryBean.class
@Override
public void afterPropertiesSet() throws Exception {

    // 1.通过注册中心,将服务提供者获取到本地缓存列表
    IRegisterCenter registerCenter = RegisterCenterImpl.getInstance();
    registerCenter.loadProviderMap(remoteAppKey, groupName);
    Map<String, List<Provider>> serviceMetadata = registerCenter.getServiceMetadata();
    // 2.初始化Netty连接池
    NettyChannelPoolFactory.getInstance().initChannelPoolFactory(serviceMetadata);
    // 3.获取服务提供者代理对象
    RevokerProxyBeanFactory revokerProxyBeanFactory = RevokerProxyBeanFactory.getInstance(targetItf, timeout, consumeThreads, loadBalanceStrategy);
    this.serviceObject = revokerProxyBeanFactory.getProxy();
    // 4.将服务消费者信息注册到注册中心
    Invoker invoker = Invoker.builder().groupName(groupName)
            .remoteAppKey(remoteAppKey)
            .targetItf(targetItf)
            .build();
    registerCenter.registerInvoker(invoker);
}

可以发现在服务发现的DiscoverFactoryBean类中,除了刚才所说的服务拉取任务,还在此时完成了Netty连接池和服务提供者代理对象的创建工作(关于这部分我们放在服务通信部分再说),并且还会将服务消费者信息注册到注册中心以备后续的监控、治理所使用。

自定义注解

注解与自定义标签的目标是完全一致的,只不过是为了简化配置,方便使用而做出的优化。我们先看下两个注解SRpcServiceSRpcReference

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SRpcService {
    Class<?> serviceItf();
    long timeout() default 3000L;
    String groupName() default "default";
    int weight() default 1;
    int workThreads() default 10;
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SRpcReference {
    Class<?> targetItf();
    long timeout() default 3000L;
    String loadBalanceStrategy() default "3";
    String remoteAppKey();
    int consumeThreads() default 10;
    String groupName() default "default";
}

具体的就不再赘述了,在源码中都有详细的注释。有了注解,那么我们要做的就是通过注解扫描分别完成ProviderFactoryBeanDiscoverFactoryBean的功能。因此,提供了两个类AnnotationServicesPublisherReferenceBeanPostProcessor,通过类名也能明白他们的作用。

AnnotationServicesPublisher中实现ApplicationListener<ApplicationContextEvent>接口,在监听到ContextRefreshedEvent事件时完成服务上报。

ReferenceBeanPostProcessor中实现了InstantiationAwareBeanPostProcessor接口,在bean实例化过程中对bean属性进行修改,并完成DiscoverFactoryBean类中afterPropertiesSet()方法中所完成的所有功能。

小结

利用Spring的特性,在容器启动时完成远程服务的发布和引入,使用自定义标签和自定义注解来简化配置方便使用。