Motan RPC 服务治理

 

新浪于今年开源了其内部的轻量级RPC框架Motan,支持千亿级调用。Motan偏重于简洁实用的服务治理功能和优秀的RPC协议扩展能力,既可以提供高效的RPC远程调用,又能提供服务发现、服务高可用(High Available)、负载均衡、服务监控、管理等服务治理功能。

开源   https://github.com/weibocom/motan ,源码及相应的介绍文档都能在git上找到,有兴趣的可以下下来看看。

本篇主要分析下其在服务治理方面的实现。

而对于rpc中 常用 的网络通信,通信协议,序列化方式等不做详细的介绍。重点在于其提供服务治理 ,能够自动完成服务的发现与注册,且提供  HA(fastfail),提供多种LoadBalance策略配置。

这些都可以通过配置文件指定,通过自定义的 6个 sping标签(registry、protocol、service、baseService、referer、baseReferer), 注册中心(zookeeper,cousul)  registry 实现服务自动化的核心, 协议配置管理(如HA策略,balance,filter等) protocol。还有服务端的service,baseService;客户端的配置  referer,baseRefer。 

接下来 从  motan两个最为核心的操作 入手, 对应服务治理中的体现。 服务端的 服务导出 与  客户端服务的获取referer。在分析过程中会贴出部分核心源码。

Server export service 

两种方式配置服务导出

标签配置

    <!-- 业务具体实现类 -->
    <bean id="motanDemoServiceImpl" class="com.weibo.motan.demo.server.MotanDemoServiceImpl"/>

    <!-- 注册中心配置 使用不同注册中心需要依赖对应的jar包。如果不使用注册中心,可以把check属性改为false,忽略注册失败。-->
    <!--<motan:registry regProtocol="local" name="registry" />-->
    <!--<motan:registry regProtocol="consul" name="registry" address="127.0.0.1:8500"/>-->
    <motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" connectTimeout="2000"/>

    <!-- 协议配置。为防止多个业务配置冲突,推荐使用id表示具体协议。-->
    <motan:protocol id="demoMotan" default="true" name="motan"
                    maxServerConnection="80000" maxContentLength="1048576"
                    maxWorkerThread="800" minWorkerThread="20"/>

    <!-- 通用配置,多个rpc服务使用相同的基础配置. group和module定义具体的服务池。export格式为“protocol id:提供服务的端口”-->
    <motan:basicService export="demoMotan:8002"
                        group="motan-demo-rpc" accessLog="false" shareChannel="true" module="motan-demo-rpc"
                        application="myMotanDemo" registry="registry" id="serviceBasicConfig"/>

    <!-- 具体rpc服务配置,声明实现的接口类。-->
    <motan:service interface="com.weibo.motan.demo.service.MotanDemoService"
                   ref="motanDemoServiceImpl" export="demoMotan:8001" basicService="serviceBasicConfig">
    </motan:service>
    <motan:service interface="com.weibo.motan.demo.service.MotanDemoService"
                   ref="motanDemoServiceImpl" export="demoMotan:8002" basicService="serviceBasicConfig">
    </motan:service>

ServiceConfig 代码设置

ServiceConfig<MotanDemoService> motanDemoService = new ServiceConfig<MotanDemoService>();
        
        //设置接口及实现类
        motanDemoService.setInterface(MotanDemoService.class);  
        motanDemoService.setRef(new MotanDemoServiceImpl());

        // 配置服务的group以及版本号
        motanDemoService.setGroup("motan-demo-rpc");
        motanDemoService.setVersion("1.0");

        // 配置注册中心
        RegistryConfig registry = new RegistryConfig();
        registry.setRegProtocol("local");
        registry.setCheck("false"); //不检查是否注册成功
        motanDemoService.setRegistry(registry);

        // 配置RPC协议
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setId("motan");
        protocol.setName("motan");
        motanDemoService.setProtocol(protocol);
        motanDemoService.setExport("motan:8002");
        motanDemoService.export();//服务导出
        MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER, true);

两种方式背后执行的逻辑是一致的,配置完成后,需要将服务导出。

1)先检测配置,而后从配置从得到 registry 的urls,如zookeeper可以有多个注册中心。

2) 根据协议端口,导出服务 。一个服务可以设置通过不同协议设置不同端口提供service。

3) 执行某一协议的导出。组装参数到map,而后封装到URL 为serviceURl

ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
//ref为 接口interfaceClass的实现,
exporters.add(configHandler.export(interfaceClass, ref, urls));//usrls为注册中心的多个url,封装了serviceURL的pram

4) 导出服务

    ExtensionLoader 根据接口名,找到配置文件,加载对应接口的实现类,而后根据 spiMeta的value找到所配置的实例,如protocol  配置的motan,则对应的protocol实例为 DefaultRpcProtocol。而configHandler为 SimpleConfigHandler。 

// SimpleConfigHandler   
 @Override
    public <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
        String serviceStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName()));
        URL serviceUrl = URL.valueOf(serviceStr);
        // export service
        // 利用protocol decorator来增加filter特性
        String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
        Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));
        Provider<T> provider = new DefaultProvider<T>(ref, serviceUrl, interfaceClass);//服务的实际提供者,构造方法会初始化interfaceClass中的Method
        Exporter<T> exporter = protocol.export(provider, serviceUrl);

        // register service
        register(registryUrls, serviceUrl);
        return exporter;
    }

执行protocol的 export , 从代码中看到,对DefaultRpcProtocol进行了封装,ProtocolFilterDecorator,在执行其封装的protocol前,对默认的provicder进行装饰,默认的provider 实现中,封装了接口的实现 类,因此其核心完成了该服务的实现,通过request 找到Method ,与服务实例ref,最终通过反射执行rpc调用。

@Override
    public <T> Exporter<T> export(Provider<T> provider, URL url) {
        return protocol.export(decorateWithFilter(provider, url), url);
    }

decorateWithFilter就是根据url 装饰provider,得到一个provider的链, 其最为核心的就是 对call方法进行了处理,执行了fiter,而在filter中调用下一个provider的call方法,这样一来在真正业务的call方法执行前要执行设定的filter,而filter 可以做一些日志,接口并发处理数限制等逻辑。(装饰模式)且通过匿名内部类生成装饰类 装饰所扩展的功能就是Filter 链实现 的功能。

 for (Filter filter : filters) {
            final Filter f = filter;
            final Provider<T> lp = lastProvider; //第一个lastProvider就为DefalutRPCProvider
            lastProvider = new Provider<T>() {
                @Override
                public Response call(Request request) {
                    return f.filter(lp, request);
                }

                @Override
                public String desc() {
                    return lp.desc();
                }
.......
}

得到了一个装饰后  Provider处理链之后 ,导出该Provider。 执行真正protocol的导出,首先生成Exporter,

DefaultRpcProtocol
    @Override
    protected <T> Exporter<T> createExporter(Provider<T> provider, URL url) {
        return new DefaultRpcExporter<T>(provider, url);
    }

构造初始化Exporter

 public DefaultRpcExporter(Provider<T> provider, URL url) {
            super(provider, url);

            ProviderMessageRouter requestRouter = initRequestRouter(url);
            endpointFactory =
                    ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
                            url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
            server = endpointFactory.createServer(url, requestRouter);
        }

第一步  初始化Router,用于找 provider(service),以IPPort为key,从ipPort2RequestRouter(表示一个端口提供路由服务列表)找到此前该端口是否提供了其它的service,如果没有,则new一个Router,有的话,则将provider加入到 一个服务的map中,key为标识该服务的servicekey

group/interface/version  用其表示 。找服务实现同样通过 该key找到provider执行 call方法。

synchronized (ipPort2RequestRouter) {
                requestRouter = ipPort2RequestRouter.get(ipPort);

                if (requestRouter == null) {
                    requestRouter = new ProviderProtectedMessageRouter(provider);
                    ipPort2RequestRouter.put(ipPort, requestRouter);
                } else {
                    requestRouter.addProvider(provider);
                }
            }

现在我们得到了一个提供多个servcice的requestRouter 。

第二步   createServrer,也就是netty的Server 端。处理 服务与server channel之间的关系。两个map

ipPort2ServerShareChannel   IPPort  对应 server

server2UrlsShareChannel    Server与 提供服务的set集合   

 根据参数 决定 是否共享端口,如果该端口之前打开,则会抛出异常。否则生成  Netty4Server(url, messageHandler);  这里的messageHandler就是上面的requestRouter, 封装到server中的 ChannelHandler中。处理请求。  但是如果之前 ipPort 有Channel,也就是之前已有service打开了channel,则只需要在server对应的value  set中  增加 本服务的servicekey   group/interface/version。

最后一步   初始化  如果之前端口没有打开,初始化  serverBootstrap,绑定端口,启动服务线程。然后以ServiceKey与该exporter加入到exporterMap 。

服务导出后,到 Registry进行服务注册。根据配置的注册中心urls,

 private void register(List<URL> registryUrls, URL serviceUrl) {
        for (URL url : registryUrls) {
            // 根据check参数的设置,register失败可能会抛异常,上层应该知晓
            RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol());
            if (registryFactory == null) {
                throw new MotanFrameworkException(new MotanErrorMsg(500, MotanErrorMsgConstant.FRAMEWORK_REGISTER_ERROR_CODE,
                        "register error! Could not find extension for registry protocol:" + url.getProtocol()
                                + ", make sure registry module for " + url.getProtocol() + " is in classpath!"));
            }
            Registry registry = registryFactory.getRegistry(url);
            registry.register(serviceUrl);
        }
    }

通过每个url 找到registry注册中心,如果没有,则生成并加入缓存。而后注册 serviceURL.  完成service的 发布。client就可以通过 注册中心订阅本服务。

简单要介绍下zookeeper做为配置中心时的逻辑。判断服务的根路径是否存在,不存在则创建一个持久的node,而后在该node下建立一个本服务器所提供的该服务key为ip-port,value为url的值 。这样的一来,就不会影响到其它节点所提供的相同服务。服务的root Node为

toGroupPath(url) + MotanConstants.PATH_SEPARATOR + url.getPath() + ZkNodeType,而不同的server提供的服务为子node,在rootPath的基本上增加 对应的server的IP-Port。这样一来,客户端只需订阅rootPath。

    private void createNode(URL url, ZkNodeType nodeType) {
        String nodeTypePath = toNodeTypePath(url, nodeType);
        if (!zkClient.exists(nodeTypePath)) {
            zkClient.createPersistent(nodeTypePath, true);
        }
        zkClient.createEphemeral(toNodePath(url, nodeType), url.toFullStr());
    }

 

Client ref   

客户端   要实现rpc的调用,首先在客户端要生成 服务器端的代理,一般实现是 通过 动态代理 该接口,而后在invoke 方法里 根据  所调方法,参数 等序列化后 发送到server端 去请求。

motan将这这一过程服务化,只需在注册中心订阅服务 ,服务发布后会通知客户端。 用户 调用时 根据设定的策略找到某一个server的配置信息,发送请求,执行将result 返回。

对于motan client,两种方式配置c,

通过spring配置文件,根据定义的sping标签配置referer,

   <motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" connectTimeout="2000"/>

    <!-- motan协议配置 -->
    <motan:protocol default="true" name="motan" haStrategy="failover"
                    loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/>

    <!-- 通用referer基础配置 -->
    <motan:basicReferer requestTimeout="200" accessLog="false"
                        retries="2" group="motan-demo-rpc" module="motan-demo-rpc"
                        application="myMotanDemo" protocol="motan" registry="registry"
                        id="motantestClientBasicConfig" throwException="false" check="true"/>
    <!-- 具体referer配置。使用方通过beanid使用服务接口类 -->
    <motan:referer id="motanDemoReferer"
                   interface="com.weibo.motan.demo.service.MotanDemoService"
                   connectTimeout="300" requestTimeout="300" basicReferer="motantestClientBasicConfig"/>

  RefererConfig, 根据方法设置。官网上的示例如下:

RefererConfig<MotanDemoService> motanDemoServiceReferer = new RefererConfig<MotanDemoService>();

        // 设置接口及实现类
        motanDemoServiceReferer.setInterface(MotanDemoService.class);
        // 配置服务的group以及版本号
        motanDemoServiceReferer.setGroup("motan-demo-rpc");
        motanDemoServiceReferer.setVersion("1.0");
        motanDemoServiceReferer.setRequestTimeout(300);
        // 配置注册中心
        RegistryConfig registry = new RegistryConfig();
        registry.setRegProtocol("local");
        motanDemoServiceReferer.setRegistry(registry);
        // 配置RPC协议
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setId("motan");
        protocol.setName("motan");
        motanDemoServiceReferer.setProtocol(protocol);
        motanDemoServiceReferer.setDirectUrl("localhost:8002");
        // 使用服务
        MotanDemoService service = motanDemoServiceReferer.getRef();

背后是通过ClusterSupport 中ClusterSPI 提供服务,其内部是一组可用的Server在逻辑上的封装,包含若干可以提供RPC服务的Server代理,实际请求时会根据不同的高可用与负载均衡策略选择一个可用的Server发起远程调用。

过程大致如下:

        1)new URL,根据配置,定义一个refUrl,根据 ip,port,protocol,path等。

URL refUrl = new URL(protocol.getName(), localIp, MotanConstants.DEFAULT_INT_VALUE, interfaceClass.getName(), params);
ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls);

        2)生成ClusterSpi(设置 HA,LoadBalance)。

    private void prepareCluster() {
        String clusterName = url.getParameter(URLParamType.cluster.getName(), URLParamType.cluster.getValue());
        String loadbalanceName = url.getParameter(URLParamType.loadbalance.getName(), URLParamType.loadbalance.getValue());
        String haStrategyName = url.getParameter(URLParamType.haStrategy.getName(), URLParamType.haStrategy.getValue());

        cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(clusterName);
        LoadBalance<T> loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
        HaStrategy<T> ha = ExtensionLoader.getExtensionLoader(HaStrategy.class).getExtension(haStrategyName);
        cluster.setLoadBalance(loadBalance);
        cluster.setHaStrategy(ha);
        cluster.setUrl(url);
    }

        3)subscribe url,注册中心  如zookeeper中订阅service。

  // client 注册自己,同时订阅service列表
   Registry registry = getRegistry(ru);
   registry.subscribe(subUrl, this);  //this 为clusterSupport
 String serverTypePath = toNodeTypePath(url, ZkNodeType.AVAILABLE_SERVER);
 List<String> currentChilds = zkClient.subscribeChildChanges(serverTypePath, zkChildListener);//订阅,zhChildListener 定义的节点事件监听者
 LoggerUtil.info(String.format("[ZookeeperRegistry] subscribe: path=%s, info=%s", toNodePath(url, ZkNodeType.AVAILABLE_SERVER), url.toFullStr()));
 notify(url, notifyListener, nodeChildsToUrls(serverTypePath, currentChilds));

zkChildListener 里的notify方法 里执行的也是 上面的notify方法,通知 ClusterSupport。提供服务的urls

       4)更新referer(封装了提供服务的netty Client,通过client发送rpc请求)

referer = protocol.refer(interfaceClass, refererURL, u);
    @Override
 protected <T> Referer<T> createReferer(Class<T> clz, URL url, URL serviceUrl) {
     return new DefaultRpcReferer<T>(clz, url, serviceUrl);
 }

生成rpcRefer,其内部封装了通过 endpointFactory 生成  netty client,

public DefaultRpcReferer(Class<T> clz, URL url, URL serviceUrl) {
     super(clz, url, serviceUrl);

     endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
     url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
     client = endpointFactory.createClient(url);
}

Referer中   根据url生成netty的客户端。如此refere中就有了netty client。

·      5)更新ClusterSPI中的referes,

public synchronized void onRefresh(List<Referer<T>> referers) {
        if (CollectionUtil.isEmpty(referers)) {
            return;
        }
        loadBalance.onRefresh(referers);
        List<Referer<T>> oldReferers = this.referers;
        this.referers = referers;
        haStrategy.setUrl(getUrl());
        if (oldReferers == null || oldReferers.isEmpty()) {
            return;
        }
        List<Referer<T>> delayDestroyReferers = new ArrayList<Referer<T>>();
        for (Referer<T> referer : oldReferers) {
            if (referers.contains(referer)) {
                continue;
            }
            delayDestroyReferers.add(referer);
        }
        if (!delayDestroyReferers.isEmpty()) {
            RefererSupports.delayDestroy(delayDestroyReferers);
        }
    }

        6)在根据protocols  生成ClusterSupport后,代理封装了ClusterSpi

ref = configHandler.refer(interfaceClass, clusters, proxy);

用户得到的service代理,

 @Override
    public <T> T refer(Class<T> interfaceClass, List<Cluster<T>> clusters, String proxyType) {
        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(proxyType);
        return proxyFactory.getProxy(interfaceClass, new RefererInvocationHandler<T>(interfaceClass, clusters));
    }

其中的RefererInvocatioHandler中的 invoke就是核心,代理了service的接口,我们看到 其 底层是通过 clusters  中的referers 来实现 真正的rpc。而  从clusters中 选择一个合适的referers 就是 服务治理,也就是HA,loadBalance 中生效的过程。 而clusters中的referers 是动态更新的,根据 在注册中心注册的服务,通知client。最终达到服务治理。

 

http://weibo.com/p/1001643875439147097368

http://tech.sina.com.cn/i/2016-05-10/doc-ifxryhhh1869879.shtml

https://github.com/weibocom/motan/blob/master/docs/wiki/zh_configuration.md   spring标签配置

https://www.kancloud.cn/kancloud/sina-boot-camp/64006

转载于:https://my.oschina.net/ovirtKg/blog/789310

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值