核心流程
前面在服务暴露与引用分析的时候,我们是基于直连的方式,没有使用注册中心,这就需要将url写死,这显然不符合我们的实际开发。
实际开发中,rpc的核心流程应该是这样的:
代理协议
有了注册中心后,就引出了RegistryProtocol这个概念,它继承了Protocol,但是它并不具备远程通信的能力,所以在它内部代理了一个Protocol,所以它是一个代理协议。通过下图来看看它和注册中心,DubboProtocol的关系
此时的url就类似于下面这种
"zookeeper://192.168.63.128:2181/org.apache.dubbo.registry.RegistryService?application=bootserver&dubbo=2.0.2&export=dubbo://127.0.0.1:20880/cxylk.dubbo.UserService&timeout=6000";
服务暴露
测试代码:
public class RegistryProtocolTest {
final String urlTest="zookeeper://192.168.63.128:2181/org.apache.dubbo.registry.RegistryService?\n" +
"application=boot-server&dubbo=2.0.2&\n" +
"export=dubbo://127.0.0.1:20880/cxylk.dubbo.UserService&timeout=6000";
@Before
public void init() {
ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("test"));
}
@Test
public void exportTest() throws IOException {
//环境准备
UserServiceImpl impl = new UserServiceImpl();
impl.name = "注册中心测试";
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceRepository = repository.registerService(UserService.class);
repository.registerProvider(
"cxylk.dubbo.UserService", impl, serviceRepository,
new ServiceConfig<>(), null
);
//初始化代理协议
//一个是目标协议,一个是注册工厂
RegistryProtocol protocol = new RegistryProtocol();
//代理的目标协议,这里是dubbo协议
protocol.setProtocol(new DubboProtocol());
ZookeeperRegistryFactory registryFactory = new ZookeeperRegistryFactory();
registryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());
protocol.setRegistryFactory(registryFactory);
//构建Invoker 使用ProxyFactory的方式
ProxyFactory proxyFactory=new JavassistProxyFactory();//动态生成代理类
Invoker<UserService> invoker=proxyFactory.getInvoker(impl,UserService.class, URL.valueOf(urlTest));
//dubbo协议 netty-server远程服务
Exporter<UserService> export = protocol.export(invoker);
//暴露服务 注册提供者
System.in.read();
}
}
上面有一个需要注意的地方,构建invoker有两种方式:
1、基于反射
ProxyFactory proxy=new JdkProxyFactory()
在JdkProxyFactory中通过getInvoker方法
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
Method method = proxy.getClass().getMethod(methodName, parameterTypes);
return method.invoke(proxy, arguments);
}
};
}
可以看到,就是基于一个反射实现的。
2、基于动态代理
ProxyFactory proxy=new JavassistProxyFactory()
在JavassistProxyFactory中:
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
关键就是这个wrapper.invokeMethod方法,最终是在Wrapper.class中实现代理的,关键代码在makeWrapper方法:
private static Wrapper makeWrapper(Class<?> c) {
...
try {
//拿到类对象
Class<?> wc = cc.toClass();
wc.getField("pts").set((Object)null, pts);
wc.getField("pns").set((Object)null, pts.keySet().toArray(new String[0]));
wc.getField("mns").set((Object)null, mns.toArray(new String[0]));
wc.getField("dmns").set((Object)null, dmns.toArray(new String[0]));
len = 0;
Iterator var48 = ms.values().iterator();
while(var48.hasNext()) {
Method m = (Method)var48.next();
wc.getField("mts" + len++).set((Object)null, m.getParameterTypes());
}
//得到实例
Wrapper var50 = (Wrapper)wc.newInstance();
return var50;
}
...
下面通过代码来测试下生成的代理类:
@Test
public void proxyFactoryTest() throws IOException {
ProxyFactory proxyFactory=new JavassistProxyFactory();//动态生成代理类
String urlTest="dubbo://127.0.0.1:20880/cxylk.dubbo.UserService";
Invoker<UserService> invoker=proxyFactory.getInvoker(new UserServiceImpl(),UserService.class,URL.valueOf(urlTest));
System.out.println(invoker);
System.in.read();
}
启动后,我们使用阿里的arthas工具,通过命令sc.*Wrapper找到warpper0(不是wrapper),然后再通过jad类名获取反编译代码,如下:
/*
* Decompiled with CFR.
*/
package org.apache.dubbo.common.bytecode;
import com.cxylk.UserServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.common.bytecode.NoSuchMethodException;
import org.apache.dubbo.common.bytecode.NoSuchPropertyException;
import org.apache.dubbo.common.bytecode.Wrapper;
public class Wrapper0
extends Wrapper
implements ClassGenerator.DC {
public static String[] pns;
public static Map pts;
public static String[] mns;
public static String[] dmns;
public static Class[] mts0;
public static Class[] mts1;
@Override
public String[] getMethodNames() {
return mns;
}
public Object invokeMethod(Object object, String string, Class[] classArray, Object[] objectArray) throws InvocationTargetException {
UserServiceImpl userServiceImpl;
try {
userServiceImpl = (UserServiceImpl)object;
}
catch (Throwable throwable) {
throw new IllegalArgumentException(throwable);
}
try {
if ("getUser".equals(string) && classArray.length == 1) {
return userServiceImpl.getUser((Integer)objectArray[0]);
}
if ("findUsersByLabel".equals(string) && classArray.length == 2) {
return userServiceImpl.findUsersByLabel((String)objectArray[0], (Integer)objectArray[1]);
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.cxylk.UserServiceImpl.").toString());
}
@Override
public String[] getDeclaredMethodNames() {
return dmns;
}
@Override
public String[] getPropertyNames() {
return pns;
}
public Class getPropertyType(String string) {
return (Class)pts.get(string);
}
@Override
public boolean hasProperty(String string) {
return pts.containsKey(string);
}
@Override
public Object getPropertyValue(Object object, String string) {
try {
UserServiceImpl userServiceImpl = (UserServiceImpl)object;
}
catch (Throwable throwable) {
throw new IllegalArgumentException(throwable);
}
throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or setter method in class com.cxylk.UserServiceImpl.").toString());
}
@Override
public void setPropertyValue(Object object, String string, Object object2) {
try {
UserServiceImpl userServiceImpl = (UserServiceImpl)object;
}
catch (Throwable throwable) {
throw new IllegalArgumentException(throwable);
}
throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or setter method in class com.cxylk.UserServiceImpl.").toString());
}
}
可以看到,它是通过调用的方法名来返回具体的方法。
暴露流程:
服务引用
@Test
public void referTest(){
//初始化代理协议
RegistryProtocol protocol=new RegistryProtocol();
protocol.setProtocol(new DubboProtocol());
ZookeeperRegistryFactory registryFactory=new ZookeeperRegistryFactory();
registryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());
protocol.setRegistryFactory(registryFactory);
//服务引用
Invoker<UserService> invoker = protocol.refer(UserService.class, URL.valueOf(refUrlText));
ProxyFactory proxyFactory=new JavassistProxyFactory();
UserService userService = proxyFactory.getProxy(invoker);
System.out.println(userService.getUser(111));
}
注意服务暴露和服务引用中invoker的含义
在服务暴露中:
Invoker<UserService> invoker=proxyFactory.getInvoker(impl,UserService.class, URL.valueOf(urlTest));
在服务引用中:
Invoker<UserService> invoker = protocol.refer(UserService.class, URL.valueOf(refUrlText));
注意这两者实现的区别:
假设现在有一个用户发起调用,那么会先走UserService&proxy代理,然后到达服务引用中的invoker,然后经过远程通信,到达服务暴露中的invoker,这个invoker就会去调用UserServiceImpl,然后将结果按上面路线返回给客户。
然后通过debug可以看看当前服务引用中的invoker:
directory也就是RegistryDirectory是注册表,看下它里面包含了什么:
invokers的数量取决于有几个服务的提供者,当前只开启一个服务,所以只有一个
而clients取决当前有几个连接,默认是1个,也就是我们前面提到过的共享连接,client底层就是nettyClient。
现在我们开启三个服务,然后在客户端设置连接数为3
dubbo.consumer.connections=3
这个时候去客户端的controller层调用方法,看看当前userService包含了什么
可以很清楚的看到当前invokers有3个,因为我们开启了三个服务,clients有3个,因为我们设置连接数为3。
当发起调用时,在AbstractClusterInvoker类中的invoke方法就会获取注册表中的invokers
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
}
//获取注册表中的invokers
List<Invoker<T>> invokers = list(invocation);
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
list方法:
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
return directory.list(invocation);
}
然后在invoke中的具体实现会交由子类的doInvoke方法完成,而子类的doInvoke方法又会调用select方法来根据从注册表中获取的invokers使用负载均衡算法进行选择具体的invoker,这个select方法在AbstractClusterInvoker类中实现,最终是由select方法中的doSelect完成
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
//负载均衡
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//If the `invoker` is in the `selected` or invoker is unavailable && availablecheck is true, reselect.
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rInvoker != null) {
invoker = rInvoker;
} else {
//Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
int index = invokers.indexOf(invoker);
try {
//Avoid collision
invoker = invokers.get((index + 1) % invokers.size());
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}
流程:
下面通过一张时序图来深入到源码中:
1.首先通过refer方法进入到doRefer方法中:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
//既然是注册表,那就需要设置注册中心,
//然后从注册中心拿到url后要进行连接,所以还需要设置协议
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(subscribeUrl);
//注册
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
directory.subscribe(toSubscribeUrl(subscribeUrl));
//通过注册表连接成一个invoker,这个invoker就是一个clusterInvoker
//而在 clusterInvoker中有一个属性就是registrtDirectory
Invoker<T> invoker = cluster.join(directory);
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
for (RegistryProtocolListener listener : listeners) {
listener.onRefer(this, registryInvokerWrapper);
}
return registryInvokerWrapper;
}
上面方法的参数cluster就是默认使用的FailoverCluster,不过它使用了一个MockClusterWrapper进行了封装。
2.register最后是由doRegister来实现的(ZookeeperRegistry类中)
@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
可以看到,它会通过zkClient来创建一个临时节点,因为参数为true。
3.create后就要进行subscibe了,回到第一步的代码:
directory.subscribe(toSubscribeUrl(subscribeUrl));
首先是在RegistryProtocol中对RegistryDirectory进行订阅,然后RegistryDirectory又反过来对ZookeeperRegistry进行订阅,因为只有注册中心才提供订阅的能力。
//registryDirectory类中
public void subscribe(URL url) {
setConsumerUrl(url);
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
//对zookeeperRegistry进行订阅
registry.subscribe(url, this);
}
默认会进入FailbackRegistry中的subscribe方法:
@Override
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
//删除订阅失败的节点
removeFailedSubscribed(url, listener);
try {
// Sending a subscription request to the server side
doSubscribe(url, listener);
} catch (Exception e) {
Throwable t = e;
List<URL> urls = getCacheUrls(url);
if (CollectionUtils.isNotEmpty(urls)) {
notify(url, listener, urls);
logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
} else {
// If the startup detection is opened, the Exception is thrown directly.
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true);
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
// Record a failed registration request to a failed list, retry regularly
addFailedSubscribed(url, listener);
}
}
上面方法传入了一个NotifyListener参数,这个NotifyListener表示当订阅事件触发之后执行监听操作。而当前类RegistryDirectory已经实现了NotifyListener这个类并且重写了notify方法
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener{...}
NotifyListener接口中的notify方法:
void notify(List<URL> urls);
所以这个listener就是RegistryDirectory本身。
4.进入ZookeeperRegistry中的doSubscribe方法,主要看这段实现:
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
//判断是否重复进行监听
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
//ChildListener,zookeeper本身还有个监听
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkClient.create(path, false);
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
ChildListener接口
public interface ChildListener {
void childChanged(String path, List<String> children);
}
就是去监听当前路径下变更的子节点。
5.notify方法
@Override
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
try {
doNotify(url, listener, urls);
} catch (Exception t) {
// Record a failed registration request to a failed list, retry regularly
//失败进行重新刷新
addFailedNotified(url, listener, urls);
logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
urls里面存放的都是提供者的一些信息,包括协议,地址,path等。
notify后就会进行第一次刷新,此时的NotifyListener就是RegistryDirectory。
进入doNotify方法
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
super.notify(url, listener, urls);
}
注意:这个时候会发现他调用了父类的notify方法,而父类的notify方法不是被子类重写了吗?也就是上面的notify方法,那不就是进入死循环了吗?
其实不会的,它会走父类AbstractRegistry的notify方法:
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
...
Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
listener.notify(categoryList);
// We will update our cache file after each notification.
// When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
saveProperties(url);
}
}
最终会进入RegistryDirectory的notify方法
@Override
public synchronized void notify(List<URL> urls) {
//这里是对url进行验证
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(this::judgeCategory));
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
toRouters(routerURLs).ifPresent(this::addRouters);
// providers
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
/**
* 3.x added for extend URL address
*/
ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
if (supportedListeners != null && !supportedListeners.isEmpty()) {
for (AddressListener addressListener : supportedListeners) {
providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
}
}
refreshOverrideAndInvoker(providerURLs);
}
上面代码会对url进行分组,分别分成configurators,routers,providers
6.refreshOverrideAndInvoker方法实现刷新
private void refreshOverrideAndInvoker(List<URL> urls) {
// mock zookeeper://xxx?mock=return null
overrideDirectoryUrl();
refreshInvoker(urls);
}
交给refreshInvoker方法实现
里面代码很长,关注最重要的实现:
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
...
try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
代码很长,首先是拿到url变成invoker,然后进行一些列的验证,最主要的是下面这行代码:
if (enabled) {
invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
}
上面这行代码才是真正去引用服务,建立连接,以及封装一个dubboInvoker。
然后回到上一步的destroyUnusedInvokers方法,会根据参数来判断是否销毁作废的提供者。
只有在注册中心删除了提供者provider,那么才会去真正去销毁invoker,否则会进行尝试重连。
通过上面的分析,来看下整个服务集群的结构:
完整代码:github,码云