深入学习Motan系列(四)—— 客户端
困惑的袋鼠,对框架的把握有些茫然,但是仍然一步步向前,行动总比一直迷茫停止不前要好,您说呢,各位客官?
这篇开始客户端的分析。有些地方的代码,就不每段都标出了,中间有跳跃的地方,请自己对照代码来看。鄙人认为,光看武功秘籍没用,得动手debug,您说是不?
文章发了之前,斟酌了一会,因为自己认为写的不理想,有很多东西分析的不透彻,但是,决定还是发布出来,作为自己学习的历程,也希望大家多多给出建议。不断努力。
Demo
当然还是看源码中的tutorial demo了。
1 public class MotanApiClientDemo { 2 3 public static void main(String[] args) { 4 RefererConfig<MotanDemoService> motanDemoServiceReferer = new RefererConfig<MotanDemoService>(); 5 6 // 设置接口及实现类 7 motanDemoServiceReferer.setInterface(MotanDemoService.class); 8 9 // 配置服务的group以及版本号 10 motanDemoServiceReferer.setGroup("motan-demo-rpc"); 11 motanDemoServiceReferer.setVersion("1.0"); 12 motanDemoServiceReferer.setRequestTimeout(1000); 13 14 // 配置注册中心直连调用 15 RegistryConfig registry = new RegistryConfig(); 16 17 //use direct registry 18 //registry.setRegProtocol("direct"); 19 //registry.setAddress("127.0.0.1:8002"); 20 21 // use ZooKeeper registry 22 registry.setRegProtocol("zookeeper"); 23 registry.setAddress("127.0.0.1:2181"); 24 motanDemoServiceReferer.setRegistry(registry); 25 26 // 配置RPC协议 27 ProtocolConfig protocol = new ProtocolConfig(); 28 protocol.setId("motan"); 29 protocol.setName("motan"); 30 motanDemoServiceReferer.setProtocol(protocol); 31 // motanDemoServiceReferer.setDirectUrl("localhost:8002"); // 注册中心直连调用需添加此配置 32 33 // 使用服务 34 MotanDemoService service = motanDemoServiceReferer.getRef(); 35 System.out.println(service.hello("motan")); 36 37 System.exit(0); 38 } 39 }
前边的部分跟服务端的差不多,都是配置信息的设置,重点在34行和35行。注意,这边用的是RefererConfig这个类,它和ServiceConfig的关系如下:
(手残,打lol总输5555555555555,图形大家勉强看着,用StarUML画的)
motanDemoServiceReferer.getRef()这行代码中就是Referer的初始化方法
1 public T getRef() { 2 if (ref == null) { 3 initRef(); 4 } 5 return ref; 6 } 7 8 @SuppressWarnings({"unchecked", "rawtypes"}) 9 public synchronized void initRef() {
。。。省略。。。25 26 checkInterfaceAndMethods(interfaceClass, methods); 27 28 clusterSupports = new ArrayList<>(protocols.size()); 29 List<Cluster<T>> clusters = new ArrayList<>(protocols.size()); 30 String proxy = null; 31 32 ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE); 33 34 List<URL> registryUrls = loadRegistryUrls(); 35 String localIp = getLocalHostAddress(registryUrls); 36 for (ProtocolConfig protocol : protocols) { 37 Map<String, String> params = new HashMap<>(); 。。。省略。。。44 45 String path = StringUtils.isBlank(serviceInterface) ? interfaceClass.getName() : serviceInterface; 46 URL refUrl = new URL(protocol.getName(), localIp, MotanConstants.DEFAULT_INT_VALUE, path, params); 47 ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls); 48 49 clusterSupports.add(clusterSupport); 50 clusters.add(clusterSupport.getCluster()); 51 52 if (proxy == null) { 53 String defaultValue = StringUtils.isBlank(serviceInterface) ? URLParamType.proxy.getValue() : MotanConstants.PROXY_COMMON; 54 proxy = refUrl.getParameter(URLParamType.proxy.getName(), defaultValue); 55 } 56 } 57 58 ref = configHandler.refer(interfaceClass, clusters, proxy); 59 60 initialized.set(true); 61 }
上面的的代码重要的功能,我用图形表示一下:
上文的initRef()中最重要的当属Cluster的初始化。什么是Cluster?他其实代表provider服务集群,每一个具体的provider服务都被抽象成一个Referer接口,这个接口与客户端的Exporter相对应。因此,Cluster的本质是Referer的容器,并且提供了负载均衡和HA服务。每一个Cluster都被ClusterSupport封装起来,以提供对Cluster的刷新机制和生命周期管理。一个RefererConfig只会对应一个Cluster和一个ClusterSupport。(这段话,摘自http://kriszhang.com/motan-rpc-impl/)
createClusterSupport()
1 private ClusterSupport<T> createClusterSupport(URL refUrl, ConfigHandler configHandler, List<URL> registryUrls) { 2 List<URL> regUrls = new ArrayList<>(); 3 4 // 如果用户指定directUrls 或者 injvm协议访问,则使用local registry 5 if (StringUtils.isNotBlank(directUrl) || MotanConstants.PROTOCOL_INJVM.equals(refUrl.getProtocol())) {
。。。省略。。。
28 } else { // 通过注册中心配置拼装URL,注册中心可能在本地,也可能在远端 29 if (registryUrls == null || registryUrls.isEmpty()) {
。。。
34 } 35 for (URL url : registryUrls) { 36 regUrls.add(url.createCopy()); 37 } 38 } 39 40 for (URL url : regUrls) { 41 url.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(refUrl.toFullStr())); 42 }
// 这里,委托给SimpleConfigHandler进行ClusterSupport的创建 43 return configHandler.buildClusterSupport(interfaceClass, regUrls); 44 }
class ClusterSupport<T> implements NotifyListener
buildClusterSupport方法中主要是new ClusterSupport,然后进行初始化,我们看看他初始化里的内容。
1 public void init() { 2 long start = System.currentTimeMillis();
// 方法里,通过SPI机制,生成ClusterSpi、ConfigurableWeightLoadBalance、FailoverHaStrategy三个对象,然后设置到ClusterSpi的实例中。 3 prepareCluster(); 4 5 URL subUrl = toSubscribeUrl(url); 6 for (URL ru : registryUrls) { 7 8 String directUrlStr = ru.getParameter(URLParamType.directUrl.getName()); 9 // 如果有directUrl,直接使用这些directUrls进行初始化,不用到注册中心discover 10 if (StringUtils.isNotBlank(directUrlStr)) { 11 List<URL> directUrls = parseDirectUrls(directUrlStr); 12 if (!directUrls.isEmpty()) { 13 notify(ru, directUrls); 14 LoggerUtil.info("Use direct urls, refUrl={}, directUrls={}", url, directUrls); 15 continue; 16 } 17 } 18 19 // client 注册自己,同时订阅service列表 重点在这两行
// getRegistry方法中首先是通过SPI机制生成ZookeeperRegistryFactory实例,利用这个工厂模式,创建ZookeeperRegistry 20 Registry registry = getRegistry(ru);
// 这里做的是服务的订阅。方法中最终调用的是CommandFailbackRegistry的doSubscribe方法,相关类图的关系图请往下看 21 registry.subscribe(subUrl, this); 22 } 23 。。。 省略 。。。
38 }
服务发现与刷新
RegistryFactory相关类图关系如下:
Registry相关类图关系如下:
CommandFailbackRegistry doSubscribe()的内容如下:
1 protected void doSubscribe(URL url, final NotifyListener listener) { 2 LoggerUtil.info("CommandFailbackRegistry subscribe. url: " + url.toSimpleString()); 3 URL urlCopy = url.createCopy(); 4 CommandServiceManager manager = getCommandServiceManager(urlCopy); 5 manager.addNotifyListener(listener); 6 7 subscribeService(urlCopy, manager); 8 subscribeCommand(urlCopy, manager); 9 10 List<URL> urls = doDiscover(urlCopy); 11 if (urls != null && urls.size() > 0) { 12 this.notify(urlCopy, listener, urls); 13 } 14 }
AbstractRegistry
1 protected void notify(URL refUrl, NotifyListener listener, List<URL> urls) { 2 if (listener == null || urls == null) { 3 return; 4 } 。。。 省略。。。 25 26 for (List<URL> us : nodeTypeUrlsInRs.values()) {
// 重点在这里,调用了listener的notify方法。那么这个listerner是谁,就是ClusterSupport,它是在上面方法中的这两行代码里注册的
// Registry registry = getRegistry(ru);
// registry.subscribe(subUrl, this); this 指定的是ClusterSupport
// 说了一大堆就是,概括说明就是,通过retistry进行回调notify方法 ※ 27 listener.notify(getUrl(), us); 28 } 29 }
我们继续探索ClusterSupport中的notfiy方法:
下面的代码,它主要就是根据新的URL(从zk端获得)创建Referer对象,并且刷新整个集群。刷新操作主要将新的Referer加入集群,并将旧的Referer对象释放掉。需要注意,这里并没有直接释放Referer资源,而是采用了延迟机制,主要考虑到Referer可能正在执行中,马上销毁会影响正常请求。我们会一层层进入代码内部去分析。
1 /** 2 * <pre> 3 * 1 notify的执行需要串行 4 * 2 notify通知都是全量通知,在设入新的referer后,cluster需要把不再使用的referer进行回收,避免资源泄漏; 5 * 3 如果该registry对应的referer数量为0,而没有其他可用的referers,那就忽略该次通知; 6 * 4 此处对protoco进行decorator处理,当前为增加filters 7 * </pre> 8 */ 9 @Override 10 public synchronized void notify(URL registryUrl, List<URL> urls) { 。。。 省略。。。
22 // 通知都是全量通知,在设入新的referer后,cluster内部需要把不再使用的referer进行回收,避免资源泄漏 23 // //////////////////////////////////////////////////////////////////////////////// 24 25 // 判断urls中是否包含权重信息,并通知loadbalance。 26 processWeights(urls); 27 28 List<Referer<T>> newReferers = new ArrayList<Referer<T>>(); 29 for (URL u : urls) { 30 if (!u.canServe(url)) { 31 continue; 32 } 33 Referer<T> referer = getExistingReferer(u, registryReferers.get(registryUrl)); 34 if (referer == null) { 35 // careful u: serverURL, refererURL的配置会被serverURL的配置覆盖 36 URL refererURL = u.createCopy(); 37 mergeClientConfigs(refererURL);
// protocol 的类 :ProtocolFilterDecorator
// debug进入代码内部可以发现,里面是referer的创建和初始化操作。referer可以看成是RPC客户端的操作的类
// 里面,创建了NettyClient,在初始化操作中,跟server的类似,委托NettyClient进行transport层的操作 38 referer = protocol.refer(interfaceClass, refererURL, u); 39 } 40 if (referer != null) { 41 newReferers.add(referer); 42 } 43 } 44 45 if (CollectionUtil.isEmpty(newReferers)) { 46 onRegistryEmpty(registryUrl); 47 return; 48 } 49 50 // 此处不销毁referers,由cluster进行销毁 51 registryReferers.put(registryUrl, newReferers); 52 refreshCluster(); 53 }
netty的部分暂时不进行分析。那么,到这里为止,referer就可以成功生成,这样的话,在前面initRef方法中createClusterSupport的部分就结束了,接下来就是创建代理的部分了。
回顾一下initRef方法
1 public synchronized void initRef() { 。。。23
。。。28 for (ProtocolConfig protocol : protocols) { 。。。36 37 String path = StringUtils.isBlank(serviceInterface) ? interfaceClass.getName() : serviceInterface; 38 URL refUrl = new URL(protocol.getName(), localIp, MotanConstants.DEFAULT_INT_VALUE, path, params); 39 ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls); 40 41 clusterSupports.add(clusterSupport); 42 clusters.add(clusterSupport.getCluster()); 43 44 if (proxy == null) { 45 String defaultValue = StringUtils.isBlank(serviceInterface) ? URLParamType.proxy.getValue() : MotanConstants.PROXY_COMMON; 46 proxy = refUrl.getParameter(URLParamType.proxy.getName(), defaultValue); 47 } 48 } 49 50 ref = configHandler.refer(interfaceClass, clusters, proxy); 51 52 initialized.set(true); 53 }
上面50行的内容如下:
使用JDK的方法来创建动态代理。
1 public <T> T refer(Class<T> interfaceClass, List<Cluster<T>> clusters, String proxyType) { 2 ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(proxyType); 3 return proxyFactory.getProxy(interfaceClass, clusters); 4 }
当客户端初始化完毕之后,我们就能正常使用motan进行方法调用了。对接口的调用,实际上是被动态代理了,动态代理的执行入口是哪里呢?RefererInvocationHandler提供了这个入口。主要实现如下:
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 。。。
11 12 DefaultRequest request = new DefaultRequest(); 13 request.setRequestId(RequestIdGenerator.getRequestId()); 14 request.setArguments(args); 15 String methodName = method.getName(); 16 boolean async = false; 17 if (methodName.endsWith(MotanConstants.ASYNC_SUFFIX) && method.getReturnType().equals(ResponseFuture.class)) { 18 methodName = MotanFrameworkUtil.removeAsyncSuffix(methodName); 19 async = true; 20 } 21 request.setMethodName(methodName); 22 request.setParamtersDesc(ReflectUtil.getMethodParamDesc(method)); 23 request.setInterfaceName(interfaceName); 24 25 return invokeRequest(request, getRealReturnType(async, this.clz, method, methodName), async); 26 }
1 Object invokeRequest(Request request, Class returnType, boolean async) throws Throwable { 2 RpcContext curContext = RpcContext.getContext(); 3 curContext.putAttribute(MotanConstants.ASYNC_SUFFIX, async); 4 。。。17 18 // 当 referer配置多个protocol的时候,比如A,B,C, 19 // 那么正常情况下只会使用A,如果A被开关降级,那么就会使用B,B也被降级,那么会使用C 20 for (Cluster<T> cluster : clusters) { 21 String protocolSwitcher = MotanConstants.PROTOCOL_SWITCHER_PREFIX + cluster.getUrl().getProtocol(); 22 23 Switcher switcher = switcherService.getSwitcher(protocolSwitcher); 24 。。。34 35 Response response; 36 boolean throwException = Boolean.parseBoolean(cluster.getUrl().getParameter(URLParamType.throwException.getName(), URLParamType.throwException.getValue())); 37 try {
// 真正的执行入口在这里 38 response = cluster.call(request); 39 if (async) { 40 if (response instanceof ResponseFuture) { 41 ((ResponseFuture) response).setReturnType(returnType); 42 return response; 43 } else { 44 ResponseFuture responseFuture = new DefaultResponseFuture(request, 0, cluster.getUrl()); 45 if (response.getException() != null) { 46 responseFuture.onFailure(response); 47 } else { 48 responseFuture.onSuccess(response); 49 } 50 responseFuture.setReturnType(returnType); 51 return responseFuture; 52 } 53 } else { 54 Object value = response.getValue(); 55 if (value != null && value instanceof DeserializableObject) { 56 try { 57 value = ((DeserializableObject) value).deserialize(returnType); 58 } catch (IOException e) { 59 LoggerUtil.error("deserialize response value fail! deserialize type:" + returnType, e); 60 throw new MotanFrameworkException("deserialize return value fail! deserialize type:" + returnType, e); 61 } 62 } 63 return value; 64 } 65 }
。。。
85 }
Cluster.call()实际上是重头戏,他在方法执行内部代理给了haStrategy.call(),然后ha策略使用LoadBalance选择一个或一批Referer对象,并根据具体策略调用这个Referer的call()方法。Referer接口在motan协议的默认实现是DefaultRpcReferer,他在初始化的时候通过EndpointFactory创建了一个Client对象,他其实就是NettyClient,然后调用client.request(),从而使rpc请求顺利进入了transport层。(前面这段分析,参考了别的文章,写的比我有水平,自己汗颜,感觉到里面的差距)之后,通过transport层,一步步到达服务端。
参考文章:http://kriszhang.com/motan-rpc-impl/
这篇文章作者站在一个高度对Motan框架进行简明概要的分析,钦佩,学习。
对比自己,自己这只袋鼠仍然在扎根,加强自己知识的深度,至于广度,是未来下阶段的目标。