Analysis of advanced service release of sofarpc

Posted by Riseykins on Tue, 18 Jan 2022 03:57:55 +0100

Sofrpc service publishing principle

Sofrpc is the RPC framework in ant soffastack. The rise of each middleware is worth learning its design concept to expand our knowledge reserve. It should not be shown here. For those students who have not understood, please refer to: https://www.sofastack.tech/projects/sofa-rpc/overview/

This is based on zookeeper as the registration analysis.

Exposure services

After the interface is configured, it is as follows:

@SofaService(interfaceType = HelloSofaV2.class,
        bindings = {@SofaServiceBinding(bindingType = "bolt"),@SofaServiceBinding(bindingType = "rest")})
@Service
public class HelloSofav2Impl implements HelloSofaV2 {
    private Logger logger = LoggerFactory.getLogger(HelloSofav2Impl.class);
    @Override
    public String sayHello(String sofa) {
        logger.info("hello sofa...");
        return "hellosofa";
    }
}

The @ SofaService annotation will be identified on the interface implementation. Servicebeanfactoryprocessor is an extension of the spring beanfactoryprocessor extension point. Beans are defined programmatically

ServiceBeanFactoryPostProcessor#postProcessBeanFactory->transformSofaBeanDefinition->generateSofaServiceDefinitionOnClass->generateSofaServiceDefinition

private void generateSofaServiceDefinition(String beanId, SofaService sofaServiceAnnotation,
                                               Class<?> beanClass, BeanDefinition beanDefinition,
                                               ConfigurableListableBeanFactory beanFactory) {
        if (sofaServiceAnnotation == null) {
            return;
        }
        AnnotationWrapperBuilder<SofaService> wrapperBuilder = AnnotationWrapperBuilder.wrap(
            sofaServiceAnnotation).withBinder(binder);
    	//Placeholder resolution through proxy
        sofaServiceAnnotation = wrapperBuilder.build();

        Class<?> interfaceType = sofaServiceAnnotation.interfaceType();
        if (interfaceType.equals(void.class)) {
            Class<?> interfaces[] = beanClass.getInterfaces();

            if (beanClass.isInterface() || interfaces == null || interfaces.length == 0) {
                interfaceType = beanClass;
            } else if (interfaces.length == 1) {
                interfaceType = interfaces[0];
            } else {
                throw new FatalBeanException("Bean " + beanId + " has more than one interface.");
            }
        }

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        String serviceId = SofaBeanNameGenerator.generateSofaServiceBeanName(interfaceType,
            sofaServiceAnnotation.uniqueId());

        if (!beanFactory.containsBeanDefinition(serviceId)) {
            builder.getRawBeanDefinition().setScope(beanDefinition.getScope());
            builder.setLazyInit(beanDefinition.isLazyInit());
            builder.getRawBeanDefinition().setBeanClass(ServiceFactoryBean.class);
            builder.addPropertyValue(AbstractContractDefinitionParser.INTERFACE_CLASS_PROPERTY,
                interfaceType);
            builder.addPropertyValue(AbstractContractDefinitionParser.UNIQUE_ID_PROPERTY,
                sofaServiceAnnotation.uniqueId());
            builder.addPropertyValue(AbstractContractDefinitionParser.BINDINGS,
                getSofaServiceBinding(sofaServiceAnnotation, sofaServiceAnnotation.bindings()));
            builder.addPropertyReference(ServiceDefinitionParser.REF, beanId);
            builder.addPropertyValue(ServiceDefinitionParser.BEAN_ID, beanId);
            builder.addPropertyValue(AbstractContractDefinitionParser.DEFINITION_BUILDING_API_TYPE,
                true);
            builder.addDependsOn(beanId);
            ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(serviceId,
                builder.getBeanDefinition());
        } else {
            SofaLogger.error("SofaService was already registered: {}", serviceId);
        }
    }

Create a ServiceFactoryBean through BeanDefinitionBuilder. By looking at the brief introduction of sofrpc, we know that @ SofaService annotation supports placeholders. Placeholders are parsed through the class PlaceHolderAnnotationInvocationHandler. The above annotation is about the implementation principle. In this regard, we need to turn our attention to ServiceFactoryBean, which inherits AbstractContractFactoryBean and implements InitializingBean and the design pattern of template through inheritance relationship

ServiceFactoryBean#doAfterPropertiesSet

@Override
    protected void doAfterPropertiesSet() {
        if (!apiType && hasSofaServiceAnnotation()) {
            throw new ServiceRuntimeException(
                "Bean " + beanId + " of type " + ref.getClass()
                        + " has already annotated by @SofaService,"
                        + " can not be registered using xml. Please check it.");
        }

        Implementation implementation = new DefaultImplementation();
        implementation.setTarget(ref);
        service = buildService();

        // default add jvm binding and service jvm binding should set serialize as true
        if (bindings.size() == 0) {
            JvmBindingParam jvmBindingParam = new JvmBindingParam().setSerialize(true);
            bindings.add(new JvmBinding().setJvmBindingParam(jvmBindingParam));
        }

        for (Binding binding : bindings) {
            service.addBinding(binding);
        }

        ComponentInfo componentInfo = new ServiceComponent(implementation, service,
            bindingAdapterFactory, sofaRuntimeContext);
        sofaRuntimeContext.getComponentManager().register(componentInfo);
    }

Focus on * * sofaruntimecontext getComponentManager(). register(componentInfo);** By calling, you will get to ComponentManagerImpl#doRegister

private ComponentInfo doRegister(ComponentInfo ci) {
        ComponentName name = ci.getName();
        if (isRegistered(name)) {
            SofaLogger.error("Component was already registered: {}", name);
            if (ci.canBeDuplicate()) {
                return getComponentInfo(name);
            }
            throw new ServiceRuntimeException("Component can not be registered duplicated: " + name);
        }

        try {
            ci.register();
        } catch (Throwable t) {
            SofaLogger.error("Failed to register component: {}", ci.getName(), t);
            return null;
        }

        SofaLogger.info("Registering component: {}", ci.getName());

        try {
            ComponentInfo old = registry.putIfAbsent(ci.getName(), ci);
            if (old != null) {
                SofaLogger.error("Component was already registered: {}", name);
                if (ci.canBeDuplicate()) {
                    return old;
                }
                throw new ServiceRuntimeException("Component can not be registered duplicated: "
                                                  + name);

            }
            if (ci.resolve()) {
                typeRegistry(ci);
                //Exposure services
                ci.activate();
            }
        } catch (Throwable t) {
            ci.exception(new Exception(t));
            SofaLogger.error("Failed to create the component {}", ci.getName(), t);
        }

        return ci;
    }

ci.activate() will rely on COM alipay. sofa. runtime. spi. binding. Binding violence service. Binding is an extension point in sofrpc. Package bindingadapter <? > through BindingAdapterFactory, adopt

Object outBinding(Object contract, T binding, Object target,
                  SofaRuntimeContext sofaRuntimeContext);

Exposure services. Take RpcBindingAdapter as an example

@Override
    public Object outBinding(Object contract, RpcBinding binding, Object target,
                             SofaRuntimeContext sofaRuntimeContext) {

        ApplicationContext applicationContext = sofaRuntimeContext.getSofaRuntimeManager()
            .getRootApplicationContext();
        ProviderConfigContainer providerConfigContainer = applicationContext
            .getBean(ProviderConfigContainer.class);
        ProcessorContainer processorContainer = applicationContext
            .getBean(ProcessorContainer.class);

        String uniqueName = providerConfigContainer.createUniqueName((Contract) contract, binding);
        ProviderConfig providerConfig = providerConfigContainer.getProviderConfig(uniqueName);
        processorContainer.processorProvider(providerConfig);

        if (providerConfig == null) {
            throw new ServiceRuntimeException(LogCodes.getLog(
                LogCodes.INFO_SERVICE_METADATA_IS_NULL, uniqueName));
        }

        try {
            //Exposure services
            providerConfig.export();
        } catch (Exception e) {
            throw new ServiceRuntimeException(LogCodes.getLog(LogCodes.ERROR_PROXY_PUBLISH_FAIL), e);
        }

        if (providerConfigContainer.isAllowPublish()) {
            providerConfig.setRegister(true);
            List<RegistryConfig> registrys = providerConfig.getRegistry();
            for (RegistryConfig registryConfig : registrys) {
                Registry registry = RegistryFactory.getRegistry(registryConfig);
                registry.init();
                registry.start();
                registry.register(providerConfig);
            }
        }
        return Boolean.TRUE;
    }

providerConfig#export

@Override
    public void export() {
        if (providerConfig.getDelay() > 0) { // Delayed loading in milliseconds
            Thread thread = factory.newThread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(providerConfig.getDelay());
                    } catch (Throwable ignore) { // NOPMD
                    }
                    doExport();
                }
            });
            thread.start();
        } else {
            doExport();
        }
    }
private void doExport() {
        if (exported) {
            return;
        }

        // Check parameters
        checkParameters();

        String appName = providerConfig.getAppName();

        //key  is the protocol of server,for concurrent safe
        Map<String, Boolean> hasExportedInCurrent = new ConcurrentHashMap<String, Boolean>();
        // Register the processor with the server
        List<ServerConfig> serverConfigs = providerConfig.getServer();
        for (ServerConfig serverConfig : serverConfigs) {
            String protocol = serverConfig.getProtocol();

            String key = providerConfig.buildKey() + ":" + protocol;

            if (LOGGER.isInfoEnabled(appName)) {
                LOGGER.infoWithApp(appName, "Export provider config : {} with bean id {}", key, providerConfig.getId());
            }

            // Note the same interface, the same uniqueId and different server s
            AtomicInteger cnt = EXPORTED_KEYS.get(key); // Counter
            if (cnt == null) { // Not published
                cnt = CommonUtils.putToConcurrentMap(EXPORTED_KEYS, key, new AtomicInteger(0));
            }
            int c = cnt.incrementAndGet();
            hasExportedInCurrent.put(serverConfig.getProtocol(), true);
            int maxProxyCount = providerConfig.getRepeatedExportLimit();
            if (maxProxyCount > 0) {
                if (c > maxProxyCount) {
                    decrementCounter(hasExportedInCurrent);
                    // If the maximum number is exceeded, an exception is thrown directly
                    throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_DUPLICATE_PROVIDER_CONFIG, key,
                        maxProxyCount));
                } else if (c > 1) {
                    if (LOGGER.isInfoEnabled(appName)) {
                        LOGGER.infoWithApp(appName, LogCodes.getLog(LogCodes.WARN_DUPLICATE_PROVIDER_CONFIG, key, c));
                    }
                }
            }

        }

        try {
            // Construct request caller
            providerProxyInvoker = new ProviderProxyInvoker(providerConfig);

            preProcessProviderTarget(providerConfig, (ProviderProxyInvoker) providerProxyInvoker);
            // Initialize registry
            if (providerConfig.isRegister()) {
                List<RegistryConfig> registryConfigs = providerConfig.getRegistry();
                if (CommonUtils.isNotEmpty(registryConfigs)) {
                    for (RegistryConfig registryConfig : registryConfigs) {
                        RegistryFactory.getRegistry(registryConfig); // Initialize Registry in advance
                    }
                }
            }
            // Register the processor with the server
            for (ServerConfig serverConfig : serverConfigs) {
                try {
                    Server server = serverConfig.buildIfAbsent();
                    // Register request caller
                    server.registerProcessor(providerConfig, providerProxyInvoker);
                    if (serverConfig.isAutoStart()) {
                        //Start service
                        server.start();
                    }

                } catch (SofaRpcRuntimeException e) {
                    throw e;
                } catch (Exception e) {
                    LOGGER.errorWithApp(appName,
                        LogCodes.getLog(LogCodes.ERROR_REGISTER_PROCESSOR_TO_SERVER, serverConfig.getId()), e);
                }
            }

            // Register with the registry
            providerConfig.setConfigListener(new ProviderAttributeListener());
            register();
        } catch (Exception e) {
            decrementCounter(hasExportedInCurrent);
            if (e instanceof SofaRpcRuntimeException) {
                throw e;
            }
            throw new SofaRpcRuntimeException(LogCodes.getLog(LogCodes.ERROR_BUILD_PROVIDER_PROXY), e);
        }

        // Record some cached data
        RpcRuntimeContext.cacheProviderConfig(this);
        exported = true;
    }

Finally, start the service through BoltServer. The service will be released here.

Registration service

Through sofaboot. spring. You can see a sofarpcoautoconfiguration in the factories. A softbootrpcstartlistener is configured inside, and this listener listens to the softbootrpcstartevent. Softbootrpcstartevent is published by ApplicationContextRefreshedListener. ApplicationContextRefreshedListener listens to the ContextRefreshedEvent. That is to say, a softbootrpcstartevent will be published after spring boot is completed, and this event will be listened to by softbootrpcstartelistener

@Override
    public void onApplicationEvent(SofaBootRpcStartEvent event) {
        //choose disable metrics lookout
        disableLookout();

        //extra info
        processExtra(event);

        //start fault tolerance
        faultToleranceConfigurator.startFaultTolerance();

        Collection<ProviderConfig> allProviderConfig = providerConfigContainer
            .getAllProviderConfig();
        if (!CollectionUtils.isEmpty(allProviderConfig)) {
            //start server
            serverConfigContainer.startServers();
        }

        //set allow all publish
        providerConfigContainer.setAllowPublish(true);

        //register registry
        providerConfigContainer.publishAllProviderConfig();

        //export dubbo
        providerConfigContainer.exportAllDubboProvideConfig();
    }

You can see the providerconfigcontainer publishAllProviderConfig(); Register the service to the Registry through Registry

public void register(ProviderConfig config) {
        String appName = config.getAppName();
        if (!registryConfig.isRegister()) {
            if (LOGGER.isInfoEnabled(appName)) {
                LOGGER.infoWithApp(appName, LogCodes.getLog(LogCodes.INFO_REGISTRY_IGNORE));
            }
            return;
        }

        //release
        if (config.isRegister()) {
            registerProviderUrls(config);
        }

        if (config.isSubscribe()) {
            // Subscription configuration node
            if (!INTERFACE_CONFIG_CACHE.containsKey(buildConfigPath(rootPath, config))) {
                //Subscription interface level configuration
                subscribeConfig(config, config.getConfigListener());
            }
        }
    }

Through registerProviderUrls(config); We can see the specific publishing process and complete the service registration.

SPI

Next, we will focus on the of sofarpc spi

spi is a tool class in jdk, which is widely referenced by other frameworks, and sofarpc is no exception. Many designs of sofarpc should be based on plug-in microkernel designed by imitating dubbo, because jdk spi is relatively single, and many frameworks are extended based on jdk spi

ExtensionLoader is the toolbar of spi in sofarpc. ExtensionLoader can be obtained through ExtensionLoaderFactory

ConcurrentMap<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<Class, ExtensionLoader>();

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz, ExtensionLoaderListener<T> listener) {
        ExtensionLoader<T> loader = LOADER_MAP.get(clazz);
        if (loader == null) {
            synchronized (ExtensionLoaderFactory.class) {
                loader = LOADER_MAP.get(clazz);
                if (loader == null) {
                    loader = new ExtensionLoader<T>(clazz, listener);
                    LOADER_MAP.put(clazz, loader);
                }
            }
        }
        return loader;
    }

A class will bind to an ExtensionLoader, and will cache and build an ExtensionLoader

protected ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) {
        if (RpcRunningState.isShuttingDown()) {
            this.interfaceClass = null;
            this.interfaceName = null;
            this.listeners = null;
            this.factory = null;
            this.extensible = null;
            this.all = null;
            return;
        }
        // The interface is empty and is neither an interface nor an abstract class
        if (interfaceClass == null ||
                !(interfaceClass.isInterface() || Modifier.isAbstract(interfaceClass.getModifiers()))) {
            throw new IllegalArgumentException("Extensible class must be interface or abstract class!");
        }
        this.interfaceClass = interfaceClass;
        this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
        this.listeners = new ArrayList<>();
        if (listener != null) {
            listeners.add(listener);
        }
        Extensible extensible = interfaceClass.getAnnotation(Extensible.class);
        if (extensible == null) {
            throw new IllegalArgumentException(
                    "Error when load extensible interface " + interfaceName + ", must add annotation @Extensible.");
        } else {
            this.extensible = extensible;
        }

        this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null;
        this.all = new ConcurrentHashMap<String, ExtensionClass<T>>();
        if (autoLoad) {
            List<String> paths = RpcConfigs.getListValue(RpcOptions.EXTENSION_LOAD_PATH);
            for (String path : paths) {
                loadFromFile(path);
            }
        }
    }
public @interface Extension {
    /**
     * Extension point name
     *
     * @return Extension point name
     */
    String value();

    /**
     * Extension point coding is not required by default. It is required when the interface needs coding
     *
     * @return Extension point coding
     * @see Extensible#coded()
     */
    byte code() default -1;

    /**
     * Priority sorting is not required by default
     *
     * @return sort
     */
    int order() default 0;

    /**
     * Overwrite other extensions with the same name with {@ link #order()}
     *
     * @return Overwrite other low sorted extensions with the same name
     * @since 5.2.0
     */
    boolean override() default false;

    /**
     * Exclude other extensions. You can exclude other low {@ link #order()} extensions
     *
     * @return Exclude other extensions
     * @since 5.2.0
     */
    String[] rejection() default {};
}

According to the priority, whether the same extension point exists, whether to overwrite, etc.

Extension points in sofarpc exist in META-INF/services / sof RPC / and META-INF/services / paths,

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-tdkd8cgw-1626429409833) (C: \ users \ subay \ appdata \ roaming \ typora user images \ image-20210716165212395. PNG)]

All extension points in sofarpc

load balancing

  1. consistentHash
  2. localPref
  3. random
  4. roundRobin
  5. weightRoundRobin
  6. weightConsistentHash
  7. auto

The default load balancing algorithm is random

Cluster fault tolerance

  1. failfast
  2. failover

Default fault tolerance policy failover

sofarpc global configuration

The default configuration of sofarpc is RPC config default JSON

If you want to override the default configuration, you need to go to sofa RPC / RPC config JSON or

META-INF/sofa-rpc/rpc-config. The configuration in JSON is overridden through RPC config. Order defines the priority

The basic process and key technical points of service release have been completed. In the next section, we will analyze the service reference process

The official account will also publish some spring stack,sofa stack source analysis articles.

spot

load balancing

  1. consistentHash
  2. localPref
  3. random
  4. roundRobin
  5. weightRoundRobin
  6. weightConsistentHash
  7. auto

The default load balancing algorithm is random

Cluster fault tolerance

  1. failfast
  2. failover

Default fault tolerance policy failover

sofarpc global configuration

The default configuration of sofarpc is RPC config default JSON

If you want to override the default configuration, you need to go to sofa RPC / RPC config JSON or

META-INF/sofa-rpc/rpc-config. The configuration in JSON is overridden through RPC config. Order defines the priority

The basic process and key technical points of service release have been completed. In the next section, we will analyze the service reference process

IT entrepreneurs who advance with official account numbers

Topics: Spring