首页 > 技术文章 > 插件集成框架——多项目分布式模式下的框架设计

jing-yi 2021-12-23 21:57 原文

1、场景  

  作为一个网赚类小游戏开发公司,会快速的开发不同的游戏产品,整体架构采用微服务模式,中台模块包括三个功能模块(账户、用户、订单),所有游戏业务项目都与中台交互,从而只关注游戏业务端的功能开发即可。这是根据业务拆分的微服务模块。既然使用了微服务,就涉及到不同服务间的通信,这必然会使用到各种中间件(比如rpc-dubbo/motan,消息中间件-rocketMQ/kafka,Redis等)。

  试想几天就要开发一个新产品,如果没有一个专门的中间件集成框架,这些重复的中间件配置管理起来既混乱,又浪费开发人员时间,所以一个统一标准的中间件集成管理框架就显得非常重要。

2、集成框架pepper-boot

  集成框架最初命名caf,其中集成了apollo,motan,redis,rocketMQ,kafka,mybatis,prometheus(业务性能监控),后面将插件的集成抽出为pepper-boot,监控抽出为pepper-metrics,这里我们只介绍pepper-boot,pepper-metrics可阅读metrics模块。

  所有产品要做到独立隔离,自然就会想到namespace的概念,因此项目的配置、包括实例名都是通过namespace进行隔离的,每个项目拥有唯一的namespqce。理解了这一点对于下面涉及到的参数解析和实例名称的设置就清楚了。

motan集成

  定义了一个启用注解@EnableMotan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(EnableMotans.class)
@Import(MotanRegistrar.class)
public @interface EnableMotan {
    String namespace() default "default";
}

  其中通过@Import注入了MotanRegistrar类,这个类就是在注册类元信息的时候注册响应的配置处理类

public class MotanRegistrar extends AbstractRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        final String annotationName = EnableMotan.class.getName();
        final String annotationsName = EnableMotans.class.getName();
        registerBeanDefinitions(importingClassMetadata, registry, annotationName, annotationsName);
    }

    protected void dealOne(BeanDefinitionRegistry registry, AnnotationAttributes oneAttributes) {
        String namespace = oneAttributes.getString("namespace");
        Assert.isTrue(StringUtils.isNotEmpty(namespace), "namespace must be specified!");
     //可以看到这里注册了一个后置处理器,作用就是将apollo中配置的参数设置到各个config中 BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace
+ MotanBeanPostProcessor.class.getSimpleName(), MotanBeanPostProcessor.class );      //注册中心配置Bean,实例名加前缀namespace      BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + RegistryConfigBean.class.getSimpleName(), RegistryConfigBean.class );
     //协议配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace
+ ProtocolConfigBean.class.getSimpleName(), ProtocolConfigBean.class );
//服务端配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace
+ BasicServiceConfigBean.class.getSimpleName(), BasicServiceConfigBean.class );
//客户端配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace
+ BasicRefererConfigBean.class.getSimpleName(), BasicRefererConfigBean.class ); } }

  这里说下ImportBeanDefinitionRegistrar,spring提供这个类主要是用来动态注册bean的。

  所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先与依赖其的bean初始化的,也能被aop、validator等机制处理。

  另外可以看到在里面注册了一个后置处理器

public class MotanBeanPostProcessor extends BaseMotanConfiguration implements BeanPostProcessor, Ordered, EnvironmentAware, BeanFactoryAware {

    @Autowired
    protected CustomizedPropertiesBinder binder;
    private Environment environment;
    private BeanFactory beanFactory;

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RegistryConfigBean) {
            String sourceName = StringUtils.substringBefore(beanName, RegistryConfigBean.class.getSimpleName());

            RegistryConfigBean registryConfigBean = (RegistryConfigBean) bean;
            initRegistryConfig(registryConfigBean);

            String globalPrefix = PREFIX_APP_MOTAN + ".registry";
            String nsPrefix = PREFIX_APP_MOTAN + "." + sourceName + ".registry";

            if (StringUtils.isAllEmpty(environment.getProperty(globalPrefix + ".address"),
                    environment.getProperty(nsPrefix + ".address"))) {
                throw new IllegalArgumentException(String.format("%s or %s can not be null!!", globalPrefix, nsPrefix));
            }

            if (StringUtils.isNotEmpty(environment.getProperty(globalPrefix + ".address"))){
                Bindable<?> target = Bindable.of(RegistryConfigBean.class).withExistingValue(registryConfigBean);
                binder.bind(globalPrefix, target);
            }

            Bindable<?> target = Bindable.of(RegistryConfigBean.class).withExistingValue(registryConfigBean);
            binder.bind(nsPrefix, target);

        } else if (bean instanceof ProtocolConfigBean) {
            String sourceName = StringUtils.substringBefore(beanName, ProtocolConfigBean.class.getSimpleName());

            ProtocolConfigBean protocolConfigBean = (ProtocolConfigBean) bean;
            initProtocolConfig(protocolConfigBean);

            Bindable<?> target = Bindable.of(ProtocolConfigBean.class).withExistingValue(protocolConfigBean);
            binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".protocol", target);
            protocolConfigBean.setBeanName(beanName);

        } else if (bean instanceof BasicServiceConfigBean) {
            String sourceName = StringUtils.substringBefore(beanName, BasicServiceConfigBean.class.getSimpleName());

            String registryBeanName = sourceName + RegistryConfigBean.class.getSimpleName();
            RegistryConfigBean registryConfigBean =
                    beanFactory.getBean(registryBeanName, RegistryConfigBean.class);
            Assert.notNull(registryConfigBean, String.format("%s does not existed in spring context, pls check!", registryBeanName));

            String protocolBeanName = sourceName + ProtocolConfigBean.class.getSimpleName();
            ProtocolConfigBean protocolConfigBean = beanFactory.getBean(protocolBeanName, ProtocolConfigBean.class);
            Assert.notNull(protocolConfigBean, String.format("%s does not existed in spring context, pls check!", protocolBeanName));

            String portKey = PREFIX_APP_MOTAN + "." + sourceName + ".port";
            String port = environment.getProperty(portKey);
            if (StringUtils.isEmpty(port)) {
                port = "10010";
            }
            Assert.isTrue(StringUtils.isNotEmpty(port) && NumberUtils.isCreatable(port), String.format("%s=%s must be not null! and must be a number!", portKey, port));

            BasicServiceConfigBean basicServiceConfigBean = (BasicServiceConfigBean) bean;
            initBasicServiceConfig(registryConfigBean, protocolConfigBean, Integer.parseInt(port), basicServiceConfigBean);

            Bindable<?> target = Bindable.of(BasicServiceConfigBean.class).withExistingValue(basicServiceConfigBean);
            binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".basic-service", target);

        } else if (bean instanceof BasicRefererConfigBean) {
            String sourceName = StringUtils.substringBefore(beanName, BasicRefererConfigBean.class.getSimpleName());

            String registryBeanName = sourceName + RegistryConfigBean.class.getSimpleName();
            RegistryConfigBean registryConfigBean =
                    beanFactory.getBean(registryBeanName, RegistryConfigBean.class);
            Assert.notNull(registryConfigBean, String.format("%s does not existed in spring context, pls check!", registryBeanName));

            String protocolBeanName = sourceName + ProtocolConfigBean.class.getSimpleName();
            ProtocolConfigBean protocolConfigBean = beanFactory.getBean(protocolBeanName, ProtocolConfigBean.class);
            Assert.notNull(protocolConfigBean, String.format("%s does not existed in spring context, pls check!", protocolBeanName));

            BasicRefererConfigBean basicRefererConfigBean = (BasicRefererConfigBean) bean;
            initBasicRefererConfig(registryConfigBean, protocolConfigBean, basicRefererConfigBean);

            Bindable<?> target = Bindable.of(BasicRefererConfigBean.class).withExistingValue(basicRefererConfigBean);
            binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".basic-referer", target);

        }

        return bean;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

JedisCluster集成

  定义了一个启用注解@EnableJedisClusterClient

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(EnableJedisClusterClients.class)
@Import(JedisClusterClientRegistrar.class)
public @interface EnableJedisClusterClient {
    String namespace() default "default";
}

  其中通过@Import注入了JedisClusterClientRegistrar类,这个类就是在注册类元信息的时候注册响应的配置处理类,这里与motan的实现有些不一样,是先像beanDefines中注入了一个FactoryBean,然后通过这个工厂Bean获取实例时进行相关属性配置的。

public class JedisClusterClientRegistrar extends AbstractRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        final String annotationName = EnableJedisClusterClient.class.getName();
        final String annotationsName = EnableJedisClusterClients.class.getName();
        registerBeanDefinitions(importingClassMetadata, registry, annotationName, annotationsName);
    }

    protected void dealOne(BeanDefinitionRegistry registry, AnnotationAttributes oneAttributes) {
        String namespace = oneAttributes.getString("namespace");
        Assert.isTrue(StringUtils.isNotEmpty(namespace), "namespace must be specified!");
//这里是注入了一个工厂bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace
+ JedisClusterClient.class.getSimpleName(), JedisClusterClientFactoryBean.class ); } }

  来看下这个工厂Bean ——JedisClusterClientFactoryBean

public class JedisClusterClientFactoryBean extends BaseJedisConfiguration
        implements FactoryBean<JedisClusterClient>, EnvironmentAware, BeanNameAware {
    private Environment environment;
    private String beanName;

    @Autowired
    protected CustomizedPropertiesBinder binder;

    @Override
    public JedisClusterClient getObject() {
        String namespace = StringUtils.substringBefore(beanName, JedisClusterClient.class.getSimpleName());

        String addressKey = getPreFix() + "." + namespace + ".address";
        String address = environment.getProperty(addressKey);
        Assert.isTrue(StringUtils.isNotEmpty(address), String.format("%s=%s must be not null! ", addressKey, address));

        JedisPoolConfig jedisPoolConfig = createJedisPoolConfig();
        jedisPoolConfig.setTestOnReturn(false);
        Bindable<?> target = Bindable.of(JedisPoolConfig.class).withExistingValue(jedisPoolConfig);
        binder.bind(getPreFix() + "." + namespace + ".pool", target);

        String[] commonClusterRedisArray = address.split(",");
        Set<HostAndPort> jedisClusterNodes = new HashSet<>();
        for (String clusterHostAndPort : commonClusterRedisArray) {
            String host = clusterHostAndPort.split(":")[0].trim();
            int port = Integer.parseInt(clusterHostAndPort.split(":")[1].trim());
            jedisClusterNodes.add(new HostAndPort(host, port));
        }

        JedisPropsHolder.NAMESPACE.set(namespace);
        int defaultConnectTimeout = 2000;
        int defaultConnectMaxAttempts = 20;

        final Class[] argumentTypes = {
                Set.class,
                int.class,
                int.class,
                GenericObjectPoolConfig.class,
                String.class,
                String.class
        };
        final Object[] arguments = {jedisClusterNodes, defaultConnectTimeout, defaultConnectMaxAttempts, jedisPoolConfig, namespace, address};
        //之所以与motan采用不同的方式,这里是关键,因为这里要创建一个自定义的实现metrics功能的代理对象
        final JedisClusterClient jedisClusterClient = ExtensionLoader.getExtensionLoader(JedisClusterProxyFactory.class)
                .getExtension("cglib")
                .getProxy(JedisClusterClient.class, namespace, argumentTypes, arguments);
        JedisClusterHealthTracker.addJedisCluster(namespace, jedisClusterClient);

        return jedisClusterClient;
    }

    protected String getPreFix() {
        return PREFIX_APP_JEDIS_CLUSTER;
    }

    @Override
    public Class<?> getObjectType() {
        return JedisClusterClient.class;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

    生成代理对象采用的的pepper-metrics中的SPI机制,相关文章请参考metrics模块。

  再具体说下motan和jedisCluster两种集成方式的区别:

    主要原因就是对于metrics的实现方式不同。motan提供了可扩展的过滤器,所以可以通过添加自定义过滤器可以实现metrics的收集,但是jedisCluster没有可供扩展的类似过滤器的方式,只能通过自定义的aop方式实现metrics的收集。

    motan是在注入BeanDefinition时注入了一个自定义的后置处理器BeanPostProcessor,然后在类初始化过程中通过后置处理来将Apollo中配置的motan相关数据绑定到registerConfig、ProtocolConfig、serverConfig、refererConfig中,从而实现自动化装配。

    jedisCluster是在注入BeanDefinition时注入了注入了一个FactoryBean,这就要理解BeanPostProcessor和FactoryBean的不同了,后置处理器的两个postProcessBeforeInitialization和postProcessAfterInitialization是在InitializingBean初始化bean前后分别调用的,主要处理一些类属性问题,但是FactoryBean可以通过getObject()让我们定制化一个实例,对于jedisCluster我们就需要一个集成了metrics功能的代理对象。这就是两种不同实现方式的原因。

 

推荐阅读