轻量级Rpc框架设计--motan源码解析六:client端服务发现

article/2025/9/11 4:07:49

一, Client端初始化工作

client端通过RefererConfigBean类实现InitializingBean接口的afterPropertiesSet方法, 进行下面三项检查配置工作:

①checkAndConfigBasicConfig(); // 检查并配置basicConfig

②checkAndConfigProtocols(); //检查并配置protocols

③checkAndConfigRegistry(); //检查并配置registry

1.1 motan框架client端对xml配置文件解析

motan框架在client端对xml配置文件的解析, 与对server端xml配置文件中的<motan:registry>注册中心标签和<motan:protocol>通信协议标签解析是一样的, 有区别的就是server端对于服务暴露的通用基础配置标签是<motan:basicService>, 服务暴露的标签是<motan:service>, 而client端对于服务引用的通用基础配置标签是<motan:basicReferer>, 服务引用的标签是<motan:referer>, 最终motan框架通过解析spring xml文件的配置, 将配置文件中的每个标签都对应转换成其封装类, 下面列举出motan框架部分标签对应的封装类, 如下:

<!-- 公共配置 -->

<motan:registry>标签  == > 对应RegistryConfig.class封装类

<motan:protocol>标签 == > 对应ProtocolConfig.class封装类

<!-- server端配置-->

<motan:basicService>标签 == > BasicServiceInterfaceConfig.class封装类

<motan:service>标签 == > ServiceConfigBean.class封装类

<!-- client端配置 -->

           <motan:basicReferer>标签 == > BasicRefererInterfaceConfig封装类

           <motan:referer>标签 == >RefererConfigBean封装类

所以, client端使用motan框架时, motan框架的spring-support模块会对client端的配置文件内容进行解析, 将不同的标签转换成其对应的封装类对象, 并且以id或者name(当id不存在时)为key, 放入Spring容器.

motan框架对server端xml配置文件解析, 可查看以前的内容: 轻量级Rpc框架设计--motan源码解析二:自定义spring标签与解析

1.2 checkAndConfigBasicConfig;// 检查并配置basicConfig

<!-- 通用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" />
/*** 检查并配置basicConfig*/private void checkAndConfigBasicConfig() {if (getBasicReferer() == null) {for (String name : MotanNamespaceHandler.basicRefererConfigDefineNames) {BasicRefererInterfaceConfig biConfig = beanFactory.getBean(name, BasicRefererInterfaceConfig.class);if (biConfig == null) {continue;}if (MotanNamespaceHandler.basicRefererConfigDefineNames.size() == 1) {setBasicReferer(biConfig);} else if (biConfig.isDefault() != null && biConfig.isDefault().booleanValue()) {setBasicReferer(biConfig);}}}}

checkAndConfigBasicConfig()方法, 首先判断<motan:basicReferer>标签对应的BasicRefererInterfaceConfig.class封装类对象是否为空, 即判断client端配置文件中没有配置<motan:basicReferer>该标签. 否则从MotanNamespaceHandler.basicRefererConfigDefineNames全局变量中再次获取<motan:basicReferer>标签对应的name值, 有的话, 就从spring容器根据name获取BasicRefererInterfaceConfig.class封装类.

1.3 checkAndConfigProtocols(); //检查并配置protocols

/*** 检查是否已经装配protocols,否则按basicConfig--->default路径查找*/private void checkAndConfigProtocols() {if (CollectionUtil.isEmpty(getProtocols()) && getBasicReferer() != null&& !CollectionUtil.isEmpty(getBasicReferer().getProtocols())) {setProtocols(getBasicReferer().getProtocols());}if (CollectionUtil.isEmpty(getProtocols())) {for (String name : MotanNamespaceHandler.protocolDefineNames) {ProtocolConfig pc = beanFactory.getBean(name, ProtocolConfig.class);if (pc == null) {continue;}if (MotanNamespaceHandler.protocolDefineNames.size() == 1) {setProtocol(pc);} else if (pc.isDefault() != null && pc.isDefault().booleanValue()) {setProtocol(pc);}}}if (CollectionUtil.isEmpty(getProtocols())) {setProtocol(MotanFrameworkUtil.getDefaultProtocolConfig());}}
<!-- motan协议配置 --><motan:protocol default="true" name="motan" haStrategy="failover"loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/>

checkAndConfigProtocols();方法, 首先检查client端xml配置文件中, 是否配置了<motan:protocol>, 是的话, 就使用该配置, 否则的话, 判断getBasicReferer() != null而且!CollectionUtil.isEmpty(getBasicReferer().getProtocols())也不能为空, 则就使用<motan:basicReferer>标签中对protocol通信协议的配置.

1.4 checkAndConfigRegistry(); //检查并配置registry

public void checkAndConfigRegistry() {if (CollectionUtil.isEmpty(getRegistries()) && getBasicReferer() != null&& !CollectionUtil.isEmpty(getBasicReferer().getRegistries())) {setRegistries(getBasicReferer().getRegistries());}if (CollectionUtil.isEmpty(getRegistries())) {for (String name : MotanNamespaceHandler.registryDefineNames) {RegistryConfig rc = beanFactory.getBean(name, RegistryConfig.class);if (rc == null) {continue;}if (MotanNamespaceHandler.registryDefineNames.size() == 1) {setRegistry(rc);} else if (rc.isDefault() != null && rc.isDefault().booleanValue()) {setRegistry(rc);}}}if (CollectionUtil.isEmpty(getRegistries())) {setRegistry(MotanFrameworkUtil.getDefaultRegistryConfig());}}
<motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" requestTimeout="5000"/>

检查并配置registry, 首先判断client端xml配置文件中没有配置<motan:registry>的话, 判断getBasicReferer() != null不能为空, 而且<motan:basicReferer>公共基础标签中配置了注册中心相关信息, 则!CollectionUtil.isEmpty(getBasicReferer().getRegistries())不为空.

二, client客户端服务引用的过程

2.1 通过getBean获取服务的代理对象时, 相当于FactoryBean#getObject()代理了getBean()方 法。

MotanDemoService service = (MotanDemoService) ctx.getBean("motanDemoReferer");

而RefererConfigBean实现了FactoryBean接口的getObject()方法, 如下:

public class RefererConfigBean<T> extends RefererConfig<T> implements FactoryBean<T>, BeanFactoryAware, InitializingBean, DisposableBean {private static final long serialVersionUID = 8381310907161365567L;private transient BeanFactory beanFactory;@Overridepublic T getObject() throws Exception {return getRef();}

2.2 getRef()方法分析

public T getRef() {if (ref == null) {initRef();}return ref;}
public synchronized void initRef() {if (initialized.get()) {return;}try {interfaceClass = (Class) Class.forName(interfaceClass.getName(), true, Thread.currentThread().getContextClassLoader());} catch (ClassNotFoundException e) {throw new MotanFrameworkException("ReferereConfig initRef Error: Class not found " + interfaceClass.getName(), e,MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);}if (CollectionUtil.isEmpty(protocols)) {throw new MotanFrameworkException(String.format("%s RefererConfig is malformed, for protocol not set correctly!",interfaceClass.getName()));}checkInterfaceAndMethods(interfaceClass, methods);clusterSupports = new ArrayList<ClusterSupport<T>>(protocols.size());List<Cluster<T>> clusters = new ArrayList<Cluster<T>>(protocols.size());String proxy = null;// 获取SimpleConfigHandlerConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);// 载入注册的配置, 并且转换成URL对象// [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc]List<URL> registryUrls = loadRegistryUrls();// 获取当前clien端的所在地址信息, 192.168.99.1String localIp = getLocalHostAddress(registryUrls);for (ProtocolConfig protocol : protocols) {Map<String, String> params = new HashMap<String, String>();params.put(URLParamType.nodeType.getName(), MotanConstants.NODE_TYPE_REFERER);params.put(URLParamType.version.getName(), URLParamType.version.getValue());params.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));// 从protocol, basicReferer, extConfig, this(即RefererConfig)这些对象中收集collect配置信息, 并且写入params参数collectConfigParams(params, protocol, basicReferer, extConfig, this);// 类似于dubbo框架对于client配置服务引用时, 提供方法级别的配置, 比如超时设置等,如下:// <dubbo:service> <dubbo:method name="" timeout="10000" retries="9" /><dubbo:service/> // 而motan框架也提供了client端引用服务时, 对方法级别上的配置, 如下:// <motan:referer><motan:method name="" retries=""/></motan:referer>, // collectMethodConfigParams()方法就是收集,client端服务引用时方法级别上的配置.collectMethodConfigParams(params, this.getMethods());URL refUrl = new URL(protocol.getName(), localIp, MotanConstants.DEFAULT_INT_VALUE, interfaceClass.getName(), params);// 根据服务引用配置信息refUrl, 处理对象configHandler, 注册中心registryUrls, client客户端创建集群支持, // 比如设置haStrategy高可用策略, loadBalance负载均衡策略等ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls);clusterSupports.add(clusterSupport);clusters.add(clusterSupport.getCluster());proxy = (proxy == null) ? refUrl.getParameter(URLParamType.proxy.getName(), URLParamType.proxy.getValue()) : proxy;}ref = configHandler.refer(interfaceClass, clusters, proxy);initialized.set(true);}

①首先根据client客户端xml配置文件中对服务引用时, 配置的接口全限定名称加载该接口,

interfaceClass = (Class) Class.forName(interfaceClass.getName(), true, Thread.currentThread().getContextClassLoader());

②如果client端配置服务引用时, 同时也在方法级别上做了配置, 则此处的checkInterfaceAndMethods(interfaceClass, methods);就是进行校验该接口是否包含配置的这些方法, 进行合法性校验的

checkInterfaceAndMethods(interfaceClass, methods);

③获取SimplConfigHandler, 根据配置转换URL对象, 获取当前client客户端节点所在的地址信息, 用于以后注册写到zk上.

// 获取SimpleConfigHandler
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);// 载入注册的配置, 并且转换成URL对象
// [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc]
List<URL> registryUrls = loadRegistryUrls();// 获取当前clien端的所在地址信息, 192.168.99.1
String localIp = getLocalHostAddress(registryUrls);

④从protocol, basicReferer, extConfig, this(即RefererConfig)这些对象中collect收集配置信息, 并且写入params参数

 // 从protocol, basicReferer, extConfig, this(即RefererConfig)这些对象中收集collect配置信息, 并且写入params参数
collectConfigParams(params, protocol, basicReferer, extConfig, this);

⑤收集client端服务引用时, 方法级别上的配置信息:

// 类似于dubbo框架对于client配置服务引用时, 提供方法级别的配置, 比如超时设置等,如下:
// <dubbo:service> <dubbo:method name="" timeout="10000" retries="9" /><dubbo:service/> 
// 而motan框架也提供了client端引用服务时, 对方法级别上的配置, 如下:
// <motan:referer><motan:method name="" retries=""/></motan:referer>, 
// collectMethodConfigParams()方法就是收集,client端服务引用时方法级别上的配置.
collectMethodConfigParams(params, this.getMethods());

类似于dubbo框架对于client配置服务引用时, 提供方法级别的配置, 比如超时设置等,如下: <dubbo:service> <dubbo:method name="" timeout="10000" retries="9" /><dubbo:service/>
 而motan框架也提供了client端引用服务时, 对方法级别上的配置, 如下:<motan:referer><motan:method name="" retries=""/></motan:referer>,

collectMethodConfigParams()方法就是收集,client端引用服务时在方法级别上的配置.

⑥关键代码, 初始化cluster集群环境, 比如, haStrategy高可用策略, loadBalance负载均衡策略等

// 根据服务引用配置信息refUrl, 处理对象configHandler, 注册中心registryUrls, 初始化cluster集群, 
// 比如初始化cluster集群的haStrategy高可用策略, loadBalance负载均衡策略等
ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls);

2.3 关键代码分析, 初始化cluster集群, createClusterSupport()方法.

private ClusterSupport<T> createClusterSupport(URL refUrl, ConfigHandler configHandler, List<URL> registryUrls) {List<URL> regUrls = new ArrayList<URL>();// 如果用户指定directUrls 或者 injvm协议访问,则使用local registryif (StringUtils.isNotBlank(directUrl) || MotanConstants.PROTOCOL_INJVM.equals(refUrl.getProtocol())) {URL regUrl =new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, NetUtils.LOCALHOST, MotanConstants.DEFAULT_INT_VALUE,RegistryService.class.getName());if (StringUtils.isNotBlank(directUrl)) {StringBuilder duBuf = new StringBuilder(128);String[] dus = MotanConstants.COMMA_SPLIT_PATTERN.split(directUrl);for (String du : dus) {if (du.contains(":")) {String[] hostPort = du.split(":");URL durl = refUrl.createCopy();durl.setHost(hostPort[0].trim());durl.setPort(Integer.parseInt(hostPort[1].trim()));durl.addParameter(URLParamType.nodeType.getName(), MotanConstants.NODE_TYPE_SERVICE);duBuf.append(StringTools.urlDecode(durl.toFullStr())).append(MotanConstants.COMMA_SEPARATOR);}}if (duBuf.length() > 0) {duBuf.deleteCharAt(duBuf.length() - 1);regUrl.addParameter(URLParamType.directUrl.getName(), duBuf.toString());}}regUrls.add(regUrl);} else { // 通过注册中心配置拼装URL,注册中心可能在本地,也可能在远端if (registryUrls == null || registryUrls.isEmpty()) {throw new IllegalStateException(String.format("No registry to reference %s on the consumer %s , please config <motan:registry address=\"...\" /> in your spring config.",interfaceClass, NetUtils.LOCALHOST));}for (URL url : registryUrls) {regUrls.add(url.createCopy());}}for (URL url : regUrls) {url.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(refUrl.toFullStr()));}return configHandler.buildClusterSupport(interfaceClass, regUrls);}

首先,判断如果用户指定directUrls 或者 injvm协议访问,则使用local registry, 否则通过注册中心配置拼装URL,根据interfaceClass服务引用接口,注册中心的配置信息, SimpleConfigHandler类调用buildClusterSupport()方法, 初始化cluster集群.

①SimpleConfigHandler类初始化集群的buildClusterSupport()方法分析

public <T> ClusterSupport<T> buildClusterSupport(Class<T> interfaceClass, List<URL> registryUrls) {ClusterSupport<T> clusterSupport = new ClusterSupport<T>(interfaceClass, registryUrls);// init()方法, 实际上完成了三件事// 1 初始化cluster集群// 2 向zk注册// 3 订阅server服务变化通知clusterSupport.init();return clusterSupport;}

clusterSupport.init()方法总的来说, 完成了两件事, 一是, 初始化cluster集群, prepareCluster()方法, 当client客户端没有配置haStrategy和loadBalance策略时, 使用motan框架默认的策略, 二是, client端向zk注册自己(位置:/motan/group/xxxx/client/ip/). 三是, 通过watcher机制, 实现订阅server服务变化通知(服务下线, 配置变更), 并且会调用NotifyListener进行集群的刷新操作.

②clusterSupport.init()方法分析,

public void init() {// 准备集群环境, 即判断xml配置文件中是否配置hastrategy和loadbalance策略, // 没有配置的话, motan框架就使用自己默认的策略prepareCluster();URL subUrl = toSubscribeUrl(url);for (URL ru : registryUrls) {String directUrlStr = ru.getParameter(URLParamType.directUrl.getName());// 如果有directUrl,直接使用这些directUrls进行初始化,不用到注册中心discoverif (StringUtils.isNotBlank(directUrlStr)) {List<URL> directUrls = parseDirectUrls(directUrlStr);if (!directUrls.isEmpty()) {notify(ru, directUrls);LoggerUtil.info("Use direct urls, refUrl={}, directUrls={}", url, directUrls);continue;}}// 获取操作zk的客户端连接对象Registry registry = getRegistry(ru);// subscribe()方法完成了两件事, 一是, client端在zk的/motan/group/接口全限定名称/client/client节点ip, 注册自己(即写操作)// 二是, client端通过watch机制实现服务的订阅功能, 当服务发生变更(服务下线, 配置变更)时, client端就会调用NotifyListener进行集群的刷新操作.registry.subscribe(subUrl, this);}boolean check = Boolean.parseBoolean(url.getParameter(URLParamType.check.getName(), URLParamType.check.getValue()));if (!CollectionUtil.isEmpty(cluster.getReferers()) || !check) {cluster.init();if (CollectionUtil.isEmpty(cluster.getReferers()) && !check) {LoggerUtil.warn(String.format("refer:%s", this.url.getPath() + "/" + this.url.getVersion()), "No services");}return;}throw new MotanFrameworkException(String.format("ClusterSupport No service urls for the refer:%s, registries:%s",this.url.getIdentity(), registryUrls), MotanErrorMsgConstant.SERVICE_UNFOUND);}

首先准备集群环境prepareCluster(), 即判断xml配置文件中是否配置hastrategy和loadbalance策略,没有配置的话, motan框架就使用自己默认的策略. 接着获取操作zk的客户端连接对象getRegistry(ru);最后调用subscribe()方法完成两件事,  一是, client端在zk的/motan/group/接口全限定名称/client/client节点ip, 注册自己(即写操作),  二是, client端通过watch机制实现服务的订阅功能, 当服务发生变更(服务下线, 配置变更)时, client端就会调用NotifyListener进行集群的刷新操作.

③prepareCluster();方法, 配置集群环境, 如下:

首先从url对象获取xml配置文件中的负载均衡策略, 没有的话就默认使用activeWeight(低并发度优先: referer的某时刻的call数越小优先级越高).当然我们这里已经配置了haStrategy和loadbalance策略, 分别为:faileover(故障转移)和roundrobin(轮询), 所以motan框架就会使用这俩分别作为高可用策略和负载均衡策略.

<motan:protocol default="true" name="motan" haStrategy="failover"
loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/>
private void prepareCluster() {String clusterName = url.getParameter(URLParamType.cluster.getName(), URLParamType.cluster.getValue());// 首先从url对象获取xml配置文件中的负载均衡策略, 没有的话就默认使用activeWeight// 当然我们这里已经配置了<motan:protocol default="true" name="motan" haStrategy="failover"// loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/>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);}

④getRegistry();获取操作zk的客户端连接对象

protected Registry getRegistry(URL url) {RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol());return registryFactory.getRegistry(url);}

⑤subscribe()方法分析

public void subscribe(URL url, NotifyListener listener) {....doSubscribe(url.createCopy(), listener);}
public void doSubscribe(URL url, NotifyListener listener) {....try {concreteSubscribe(url, listener);} catch (Exception e) {....}}
protected void concreteSubscribe(final URL url, final NotifyListener notifyListener) {....// 监听到zk上server服务发生变化时listenerIZkChildListener zkChildListener = childChangeListeners.get(notifyListener);if (zkChildListener == null) {childChangeListeners.putIfAbsent(notifyListener, new IZkChildListener() {@Overridepublic void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {// 处理方法ZookeeperRegistry.this.notify(url, notifyListener, nodeChildsToUrls(parentPath, currentChilds));LoggerUtil.info(String.format("[ZookeeperRegistry] service list change: path=%s, currentChilds=%s", parentPath, currentChilds.toString()));}});zkChildListener = childChangeListeners.get(notifyListener);}// 写入Client结点....// /motan/group/xxxx/client/ip/// 创建临时节点zkClient.createEphemeral(clientNodePath, info);// toServerTypePath(url)方法将, motan://192.168.99.1:0/com.weibo.motan.demo.service.MotanDemoService?group=motan-demo-rpc// 转换成zk注册地址, /motan/motan-demo-rpc/com.weibo.motan.demo.service.MotanDemoService/server// 并且使用zkChildListener的handleChildChange()方法处理zk节点变化通知List<String> currentChilds = zkClient.subscribeChildChanges(toServerTypePath(url), zkChildListener);....// 触发一次Notify(), 通知ClusterSupport进行一次refreshCluster()操作notify(url, notifyListener, nodeChildsToUrls(toServerTypePath(url), currentChilds));}

subscribe()方法最主要完成了三件事, 一是, 创建监听到server节点变化时的处理方法zkChildListener#handleChildChange(). 二是, client端向zk注册自己zkClient.createEphemeral(). 三是, 监听server服务节点/motan/group/xxx/server/ip/下的subscribeChildChanges事件, 并交由zkChildListener进行处理监听到的事件.四是, 触发一次Notify()方法, 通知ClusterSupport进行一次refreshCluster()操作.

⑥ClusterSupport#notify() --> refreshCluster()方法 , 刷新集群

protected void concreteSubscribe(final URL url, final NotifyListener notifyListener) {....// 触发一次Notify(), 通知ClusterSupport进行一次refreshCluster()操作notify(url, notifyListener, nodeChildsToUrls(toServerTypePath(url), currentChilds));
}protected void notify(URL refUrl, NotifyListener listener, List<URL> urls) {....for (List<URL> us : nodeTypeUrlsInRs.values()) {listener.notify(getUrl(), us);}}public synchronized void notify(URL registryUrl, List<URL> urls) {....// 判断urls中是否包含权重信息,并通知loadbalance。processWeights(urls);....// 此处不销毁referers,由cluster进行销毁registryReferers.put(registryUrl, newReferers);refreshCluster();}
// 刷新provider集群
private void refreshCluster() {List<Referer<T>> referers = new ArrayList<Referer<T>>();for (List<Referer<T>> refs : registryReferers.values()) {referers.addAll(refs);}cluster.onRefresh(referers);}

ClusterSupport实现了NotifyListener该接口的notify()方法,从而能够感知到zk中有哪些已经注册的服务, 并且回调notify()方法.

综上, 经过上面的步骤后, motan框架的client客户端就已经实现了向zk注册(创建临时节点:/motan/group/xxx/client/ip),  监听服务变化(服务下线, 配置变更), notify()触发一次refreshCluster()刷新集群操作获取provider暴露的服务.
所以, 目前客户端就能就能监听provider端在zk上暴露服务的变化状态.

2.4 configHandler.refer()采用Jdk Proxy动态代理技术远程调用一个服务

 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));}

当client调用代理类中的方法时, 就会首先调用代理类的invoke()方法, 如下:

①client端首先调用代理类的hello()方法时, 会首先执行代理类的invoke()方法, 发起一次远程调用.

// client端调用代理类的hello()方法
for(int i = 0; i < Integer.MAX_VALUE; i++){System.out.println(service.hello("motan" + i));Thread.sleep(500);}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {DefaultRequest request = new DefaultRequest();....// 当 referer配置多个protocol的时候,比如A,B,C, // 降级 : 那么正常情况下只会使用A,如果A被开关降级,那么就会使用B,B也被降级,那么会使用Cfor (Cluster<T> cluster : clusters) {....try {response = cluster.call(request);return response.getValue();} catch (RuntimeException e) {....}}

核心代码: cluster.call()

②cluster.call()分析.

//1 public class ClusterSpi<T>#call()
public Response call(Request request) {if (available.get()) {try {return haStrategy.call(request, loadBalance);} catch (Exception e) {return callFalse(request, e);}}....}
//2 public class FailoverHaStrategy<T>#call()
public Response call(Request request, LoadBalance<T> loadBalance) {// 首先根据lb的策略, 选择referers引用List<Referer<T>> referers = selectReferers(request, loadBalance);....return refer.call(request);....}
// 根据不同的loadbalance负载策略, 选择一个或者是一批referers
protected List<Referer<T>> selectReferers(Request request, LoadBalance<T> loadBalance) {List<Referer<T>> referers = referersHolder.get();referers.clear();loadBalance.selectToHolder(request, referers);return referers;}
//3 public class DefaultRpcProtocol{// 内部类class DefaultRpcReferer<T> extends AbstractReferer<T>{// 最终调用doCall()方法protected Response doCall(Request request) {....// 采用NettyClient调用request()进行远程服务调用return client.request(request);....}}
}

cluster.call()方法调用历经三步最终使用NettyClient#request()方法进行远程服务调用, ClusterSpi#call() --> FailoverHaStrategy#call() --> AbstractReferer#call() --> 最终调用DefaultRpcReferer#doCall()方法完成远程服务调用, 并且返回远程服务的执行结果.

为了便于理解, 在网上找到了两个相当好的有关client客户端初始化以及方法调用的说明图, 作为对以上初始化cluster集群, 方法调用的一个总结, 如下:

motan框架对于集群的管理, 图如下:

三, 总结

本片分析了motan框架client客户端引用provider暴露的服务时, 历经了哪些过程. 首先按照client配置文件中的内容初始化一个cluster集群, 接着向zk写入client端节点信息(/motan/group/com.weibo.motan.demo.service.MotanDemoService/clinet/ip/), 利用zookeeper的watcher机制, 监听provider端在zk暴露服务时, 注册的节点信息以及其所有的ChildZnode节点 (/motan/group/com.weibo.motan.demo.service.MotanDemoService/server/ip/), 当发生服务变更(比如服务下线, 配置变更)时, motan框架在client端就会调用NotifyListener#notify()方法refreshCluster()刷新provider进行服务暴露的集群.最后就是client端进行服务引用, 通过NettyClient调用远程服务, 得到执行结果.

好了, 到这里就简单的分析了motan框架provider服务注册与发布, consumer进行服务引用.

 

 

 

 

 

 

 


http://chatgpt.dhexx.cn/article/8oHGQken.shtml

相关文章

java rpc motan_【RPC 专栏】Motan 中使用异步 RPC 接口

为什么慢&#xff1f; 多线程加速 异步调用 RPC 异步调用 总结 这周六参加了一个美团点评的技术沙龙&#xff0c;其中一位老师在介绍他们自研的 RPC 框架时提到一点&#xff1a;RPC 请求分为 sync&#xff0c;future&#xff0c;callback&#xff0c;oneway&#xff0c;并且需要…

Motan-远程调用的rpc框架的负载均衡策略

虽然我不会&#xff0c;但是偶然之间看到了Motan远程调用框架的一些内容&#xff0c;然后直接copy过来了&#xff0c;想着以后自己可能看看 集群中的loadbalance负载均衡策略 ActiveWeightLoadBalance"低并发优化" 负载均衡 /** Copyright 2009-2016 Weibo, Inc.** …

motan源码分析五:cluster相关

上一章我们分析了客户端调用服务端相关的源码&#xff0c;但是到了cluster里面的部分我们就没有分析了&#xff0c;本章将深入分析cluster和它的相关支持类。 1.clustersupport的创建过程&#xff0c;上一章的ReferConfig的initRef()方法中调用了相关的创建代码&#xff1a; fo…

java 微博 开源_微博开源框架Motan初体验

前两天&#xff0c;我在开源中国的微信公众号看到新浪微博的轻量Rpc框架——Motan开源了。上网查了下&#xff0c;才得知这个Motan来头不小&#xff0c;支撑着新浪微博的千亿调用&#xff0c;曾经在2014年的春晚中有着千亿次的调用&#xff0c;对抗了春晚的最高峰值。 什么是Mo…

搭建新浪RPC框架motan Demo

motan是新浪微博开源的RPC框架&#xff0c;github官网是&#xff1a;https://github.com/weibocom/motan 今天就先搭建一个Hello world demo&#xff0c;本demo基于motan 0.2.1版本 首先先去github下载源代码&#xff08;motan-manager报错请忽略&#xff0c;eclipse的web Mod…

微博RPC框架Motan

原文来自&#xff1a;http://blog.csdn.net/autfish/article/details/51374798 从14年开始就陆续看到新浪微博RPC框架Motan的介绍&#xff0c;时隔两年后&#xff0c;微博团队终于宣布开源轻量级RPC框架Motan&#xff0c;项目地址&#xff1a; https://github.com/weibocom/mot…

motan rpc 接口统一异常处理

1.hello word 一个Motan扩展 大概需要下面的三点&#xff1a; 实现SPI扩展点接口 package com.weibo.api.motan.filter; Spi public interface Filter {Response filter(Caller<?> caller, Request request); }业务代码实现Filter public class PlsProviderExceptionF…

motan用户开发指南

目录 基本介绍 架构概述 模块概述 配置概述 使用Motan 工程依赖 处理调用异常 配置说明 协议与连接&#xff08;motan:protocol) 介绍 Motan协议 本地调用 注册中心与服务发现(motan:registry) 介绍 使用Consul作为注册中心 使用Zookeeper作为注册中心 不使用…

从motan看RPC框架设计

kris的文章开始 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决 从零开发一款RPC框架&#xff0c;说难也难说简单也简单。难的是你的设计将如何面对实际中的复杂应用场景&#xff1b;简单的是其思想可以仅仅浓缩成一行方法调用。motan是今年(2016年)新浪微博…

motan与zookeeper框架

新浪科技讯 2016年5月10日&#xff0c;微博方面宣布&#xff0c;支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了。微博技术团队希望未来能有更多优秀的开源人入驻&#xff0c;并进一步完善优化。 搭建新浪RPC框架motan Demo&#xff1a;http://blog.csdn.net/linuu/arti…

java rpc motan_RPC框架motan使用

简介 motan是新浪微博开源的一套轻量级、方便使用的RPC框架 Hello World 使用的过程分为Server端和Client端&#xff0c;Server提供RCP的服务接口&#xff0c;Client端发起调用获取结果。 maven的pom文件配置 0.2.1 com.weibo motan-core ${motan.version} com.weibo motan-tra…

轻量级Rpc框架设计--motan源码解析一:框架介绍及框架使用初体验

一, 框架介绍 1.1 概况 motan是新浪微博开源出来的一套高性能、易于使用的分布式远程服务调用(RPC)框架。 1.2 功能 可以spring的配置方式与项目集成. 支持zookeeper服务发现组件, 实现集群环境下服务注册与发现. 保证高并发, 高负载场景下的稳定高性能, , 实现生产环境…

Motan原理、使用、JavaAPI简化、为什么使用Motan

前言&#xff0c;本文包括&#xff0c;rpc解释与为什么使用rpc、rpc性能对比、Motan依赖问题、Motan源码梳理、Motan功能、特点、使用。 主要中心&#xff1a;为什么使用Motan? 一、什么是RPC 官方解释&#xff1a;RPC&#xff08;Remote Procedure Call&#xff09;—远程…

jplayer自动播放

音乐网站的播放器一直都没有解决自动播放的问题&#xff0c;小哲说这样不行的&#xff0c;我也知道不可以这样&#xff0c;毕竟是自己提出要做的&#xff0c;所以要尽自己最大的能力去做好它&#xff01;本周末我一直都在围绕这个问题而研究。 我曾经想过在播放器初始化的时候…

JWPlayer

原文&#xff1a; http://www.cnblogs.com/yukui/archive/2009/03/12/1409469.html The JW MP3 Player (built with Adobes Flash) is the easiest way to add live music or podcasts to your website. It supports playback of a single MP3 file or an RSS, XSPF or ASX pla…

今天开始写些随笔,就从Jplayer开始吧

今天才开始用Jplayer&#xff0c;可能有点落伍了&#xff0c;但是看到网上千篇一律的使用说明&#xff0c;开始决定把自己的使用心得分享一下&#xff0c;废话不多说&#xff0c;开始吧。 Step1&#xff1a; 官网上有具体的搭建顺序&#xff0c;URL&#xff1a;http://www.jp…

关于播放器JPlayer的使用及遇到的问题

jPlayer是一个用于控制和播放mp3文件的jQuery插件。它在后台使用Flash来播放mp3文件&#xff0c;前台播放器外观完全可以使用XHML/CSS自定义。支持&#xff1a; 有一点比较好的是&#xff0c;在支持html5的浏览器上会使用html5的标签audio或者video&#xff0c;而不支持的浏览…

ijkplayer支持播放rtsp、jpeg、gif

ijkplayer版本&#xff1a;k.0.8.8 编译环境&#xff1a;Ubuntu 18.04.6 LTS 使用平台&#xff1a;android 支持rtsp播放 默认的ijkplayer并不支持rtsp流的播放&#xff0c;因为在编译ffmpeg的时候并没有开启rtsp的demuxer&#xff0c;所以在编译ffmpeg的时候需要开启rtsp的d…

【ijkplayer】介绍

【ijkplayer】介绍 0x1 系统架构 ijkplayer是由b站开源的播放器项目&#xff0c;底层基于ffmpeg, 支持Android和iOS。下面我们来简单介绍一下Android上的实现。 Android上的系统架构图如下。 下面分别对各个模块进行介绍&#xff1a; 0x11 ijkplayer-example app的实现&a…

一款简洁的 jplayer 音乐播放器完整版

一款简洁 jplayer 音乐播放器&#xff0c;做音乐站很漂亮&#xff0c;直接套用就好了。 效果图&#xff1a; 部分源代码&#xff1a; <div id"lei_jplayer"></div> <div id"jp_container_1"><div class"jp-controls">…