dubbo源码分析

Dubbo插件化Dubbo的插件化实现非常类似于原生的JAVA的SPI:它只是提供一种协议,并没有提供相关插件化实施的接口。用过的同学都知道,它有一种java原生的支持类:ServiceLoader,通过声明接口的实现类,在META-INF/services中注册一个实现类,然后通过ServiceLoader去生成一个接口实例,当更换插件的时候只需要把自己实现的插件替换到META-INF/services中即可。 SPIDubo Spi 和Java SPi的使用和对比在Dubbo中,SPI是一个非常核心的机制,贯穿在几乎所有的流程中。弄明白这一块能够帮我们明白dubo源码 Dubbo是基于Java原生SPI机制思想的一个改进,所以,先从JAVA SPI机制开始了解什么时SPI以后再去学习Dubbo的SPI就比较简单 JAVASPISPI全称(service provider interface),是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如JDBC、日志框架都有用到; 简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用方来说,只需要集成对应厂商的插件,既可以完成对应规范的实现机制。 形成一种插拔式的扩展手段。 java原生spi实现需要在classpath下创建一个目录,该目录命名必须是:META-INF/services在该目录下创建一个properties文件,该文件需要满足以下几个条件 文件名必须是扩展的接口的全路径名称文件内部描述的是该扩展接口的所有实现类文件的编码格式是UTF-8通过java.util.ServiceLoader的加载机制来发现 首先定义api接口 接下来对应产商实现对应的接口,并且在resouces的META-INF/services中创建对应的文件,并且通过properties规则配置实现类的全路径 以及对应调用方引入api接口,和对应产商的jar 并且在对应的resouces中引入接口,如果引入了多个产商的jar,那么会取到多个产商的东西 SPI的实际应用 SPI在很多地方有应用,大家可以看看最常用的java.sql.Driver驱动。JDK官方提供了java.sql.Driver这个驱动扩展点,但是你们并没有看到JDK中有对应的Driver实现。 那在哪里实现呢? 以连接Mysql为例,我们需要添加mysql-connector-java依赖。然后,你们可以在这个jar包中找到SPI的配置信息。如下图,所以java.sql.Driver由各个数据库厂商自行实现。这就是SPI的实际应用。当然除了这个意外,大家在spring的包中也可以看到相应的痕迹 SPI的缺点JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因dubbo SPI规范 使用原生spi,如果路径下有多个实现都会加载进来,如果有一个加载失败,会比较麻烦 目录 Dubbo的SPI并非原生的SPI,Dubbo的规则是在 /META-INF/dubbo/META-INF/internal /META-INF/service并且基于SPI接口去创建一个文件下面以需要实现的接口去创建一个文件,并且在文件中以properties规则一样配置实现类的全面以及分配实现的一个名称。 文件名称和接口名称保持一致,文件内容和SPI有差异,内容是key对应value 我们看一下dubbo-cluster模块的META-INF.dubbo.internal: 实现自己的扩展点在resources目录下新建文件META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol文件,文件内容为 defineProtocol=com.gupaoedu.dubbo.protocol.DefineProtocol实现Protocolimport com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.rpc.Exporter;import com.alibaba.dubbo.rpc.Invoker;import com.alibaba.dubbo.rpc.Protocol;import com.alibaba.dubbo.rpc.RpcException;public class DefineProtocol implements Protocol { @Override public int getDefaultPort() { return 8888; } @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { return null; } @Override public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { return null; } @Override public void destroy() { }}调用public class App { public static void main(String[] args) throws IOException, InterruptedException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("dubbo-client.xml"); Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class). getExtension("defineProtocol"); System.out.println(protocol.getDefaultPort()); System.in.read(); }}SPI源码分析 切入点 ...

July 1, 2019 · 19 min · jiezi

聊聊dubbo的MonitorFilter

序本文主要研究一下dubbo的MonitorFilter MonitorFilterdubbo-2.7.2/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java @Activate(group = {PROVIDER, CONSUMER})public class MonitorFilter extends ListenableFilter { private static final Logger logger = LoggerFactory.getLogger(MonitorFilter.class); private static final String MONITOR_FILTER_START_TIME = "monitor_filter_start_time"; public MonitorFilter() { super.listener = new MonitorListener(); } /** * The Concurrent counter */ private final ConcurrentMap<String, AtomicInteger> concurrents = new ConcurrentHashMap<String, AtomicInteger>(); /** * The MonitorFactory */ private MonitorFactory monitorFactory; public void setMonitorFactory(MonitorFactory monitorFactory) { this.monitorFactory = monitorFactory; } /** * The invocation interceptor,it will collect the invoke data about this invocation and send it to monitor center * * @param invoker service * @param invocation invocation. * @return {@link Result} the invoke result * @throws RpcException */ @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (invoker.getUrl().hasParameter(MONITOR_KEY)) { invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis())); getConcurrent(invoker, invocation).incrementAndGet(); // count up } return invoker.invoke(invocation); // proceed invocation chain } // concurrent counter private AtomicInteger getConcurrent(Invoker<?> invoker, Invocation invocation) { String key = invoker.getInterface().getName() + "." + invocation.getMethodName(); AtomicInteger concurrent = concurrents.get(key); if (concurrent == null) { concurrents.putIfAbsent(key, new AtomicInteger()); concurrent = concurrents.get(key); } return concurrent; } //......}MonitorFilter继承了ListenableFilter,其invoke方法在invoker的URL中包含有monitor参数时会给invocation设置monitor_filter_start_time的attachment,然后递增当前并发的次数;其创建的listener为MonitorListenerMonitorListenerdubbo-2.7.2/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java ...

July 1, 2019 · 5 min · jiezi

聊聊dubbo的ClassLoaderFilter

序本文主要研究一下dubbo的ClassLoaderFilter ClassLoaderFilterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ClassLoaderFilter.java @Activate(group = CommonConstants.PROVIDER, order = -30000)public class ClassLoaderFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { ClassLoader ocl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader()); try { return invoker.invoke(invocation); } finally { Thread.currentThread().setContextClassLoader(ocl); } }}ClassLoaderFilter实现了Filter接口,其invoke方法首先获取当前线程的contextClassLoader,然后将其ContextClassLoader设置为invoker.getInterface().getClassLoader(),之后执行invoker.invoke方法,最后将当前线程的classLoader重置为原来的contextClassLoader实例dubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/ClassLoaderFilterTest.java public class ClassLoaderFilterTest { private ClassLoaderFilter classLoaderFilter = new ClassLoaderFilter(); @Test public void testInvoke() throws Exception { URL url = URL.valueOf("test://test:11/test?accesslog=true&group=dubbo&version=1.1"); String path = DemoService.class.getResource("/").getPath(); final URLClassLoader cl = new URLClassLoader(new java.net.URL[]{new java.net.URL("file:" + path)}) { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { return findClass(name); } catch (ClassNotFoundException e) { return super.loadClass(name); } } }; final Class<?> clazz = cl.loadClass(DemoService.class.getCanonicalName()); Invoker invoker = new MyInvoker(url) { @Override public Class getInterface() { return clazz; } @Override public Result invoke(Invocation invocation) throws RpcException { Assertions.assertEquals(cl, Thread.currentThread().getContextClassLoader()); return null; } }; Invocation invocation = Mockito.mock(Invocation.class); classLoaderFilter.invoke(invoker, invocation); }}这里invoker的interface设置为DemoService.class,然后验证invoke方法里头的Thread.currentThread().getContextClassLoader()为加载DemoService.class的URLClassLoader小结ClassLoaderFilter实现了Filter接口,其invoke方法首先获取当前线程的contextClassLoader,然后将其ContextClassLoader设置为invoker.getInterface().getClassLoader(),之后执行invoker.invoke方法,最后将当前线程的classLoader重置为原来的contextClassLoader ...

June 29, 2019 · 1 min · jiezi

聊聊dubbo的ValidationFilter

序本文主要研究一下dubbo的ValidationFilter ValidationFilterdubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/filter/ValidationFilter.java @Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000)public class ValidationFilter implements Filter { private Validation validation; /** * Sets the validation instance for ValidationFilter * @param validation Validation instance injected by dubbo framework based on "validation" attribute value. */ public void setValidation(Validation validation) { this.validation = validation; } /** * Perform the validation of before invoking the actual method based on <b>validation</b> attribute value. * @param invoker service * @param invocation invocation. * @return Method invocation result * @throws RpcException Throws RpcException if validation failed or any other runtime exception occurred. */ @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (validation != null && !invocation.getMethodName().startsWith("$") && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) { try { Validator validator = validation.getValidator(invoker.getUrl()); if (validator != null) { validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()); } } catch (RpcException e) { throw e; } catch (Throwable t) { return AsyncRpcResult.newDefaultAsyncResult(t, invocation); } } return invoker.invoke(invocation); }}ValidationFilter实现了Filter接口,其invoke方法在validation不为null且invoker的URL中的method参数中包含有validation,且方法名不是$开头的,那么就会从validation获取validator,然后进行校验Validationdubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/Validation.java ...

June 29, 2019 · 4 min · jiezi

聊聊dubbo的CacheFilter

序本文主要研究一下dubbo的CacheFilter CacheFilterdubbo-2.7.2/dubbo-filter/dubbo-filter-cache/src/main/java/org/apache/dubbo/cache/filter/CacheFilter.java @Activate(group = {CONSUMER, PROVIDER}, value = CACHE_KEY)public class CacheFilter implements Filter { private CacheFactory cacheFactory; /** * Dubbo will populate and set the cache factory instance based on service/method/consumer/provider configured * cache attribute value. Dubbo will search for the class name implementing configured <b>cache</b> in file org.apache.dubbo.cache.CacheFactory * under META-INF sub folders. * * @param cacheFactory instance of CacheFactory based on <b>cache</b> type */ public void setCacheFactory(CacheFactory cacheFactory) { this.cacheFactory = cacheFactory; } /** * If cache is configured, dubbo will invoke method on each method call. If cache value is returned by cache store * then it will return otherwise call the remote method and return value. If remote method's return valeu has error * then it will not cache the value. * @param invoker service * @param invocation invocation. * @return Cache returned value if found by the underlying cache store. If cache miss it will call target method. * @throws RpcException */ @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), CACHE_KEY))) { Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation); if (cache != null) { String key = StringUtils.toArgumentString(invocation.getArguments()); Object value = cache.get(key); if (value != null) { if (value instanceof ValueWrapper) { return AsyncRpcResult.newDefaultAsyncResult(((ValueWrapper) value).get(), invocation); } else { return AsyncRpcResult.newDefaultAsyncResult(value, invocation); } } Result result = invoker.invoke(invocation); if (!result.hasException()) { cache.put(key, new ValueWrapper(result.getValue())); } return result; } } return invoker.invoke(invocation); } /** * Cache value wrapper. */ static class ValueWrapper implements Serializable { private static final long serialVersionUID = -1777337318019193256L; private final Object value; public ValueWrapper (Object value) { this.value = value; } public Object get() { return this.value; } }}CacheFilter实现了Filter接口,它的invoke方法会先判断cacheFactory是否不为null且invoker的URL中包含cache参数,如果该条件成立则从cacheFactory获取cache,然后在从cache中获取value,如果value不为null则返回AsyncRpcResult.newDefaultAsyncResult,如果value为null则执行invoke成功之后将该value缓存到cache中实例dubbo-2.7.2/dubbo-filter/dubbo-filter-cache/src/test/java/org/apache/dubbo/cache/filter/CacheFilterTest.java ...

June 27, 2019 · 3 min · jiezi

聊聊dubbo的TokenFilter

序本文主要研究一下dubbo的TokenFilter TokenFilterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenFilter.java @Activate(group = CommonConstants.PROVIDER, value = TOKEN_KEY)public class TokenFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { String token = invoker.getUrl().getParameter(TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { Class<?> serviceType = invoker.getInterface(); Map<String, String> attachments = inv.getAttachments(); String remoteToken = attachments == null ? null : attachments.get(TOKEN_KEY); if (!token.equals(remoteToken)) { throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost()); } } return invoker.invoke(inv); }}TokenFilter实现了Filter接口,其invoke方法会判断invoker的URL中是否有token属性,如果该值不为空,则从attachments中获取remoteToken,然后对比两个token是否相同,不同则抛出RpcException,相同则执行invoker.invoke(inv)实例dubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/TokenFilterTest.java ...

June 26, 2019 · 2 min · jiezi

聊聊dubbo的ExecuteLimitFilter

序本文主要研究一下dubbo的ExecuteLimitFilter ExecuteLimitFilterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ExecuteLimitFilter.java public class ExecuteLimitFilter extends ListenableFilter { private static final String EXECUTELIMIT_FILTER_START_TIME = "execugtelimit_filter_start_time"; public ExecuteLimitFilter() { super.listener = new ExecuteLimitListener(); } @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { URL url = invoker.getUrl(); String methodName = invocation.getMethodName(); int max = url.getMethodParameter(methodName, EXECUTES_KEY, 0); if (!RpcStatus.beginCount(url, methodName, max)) { throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited."); } invocation.setAttachment(EXECUTELIMIT_FILTER_START_TIME, String.valueOf(System.currentTimeMillis())); try { return invoker.invoke(invocation); } catch (Throwable t) { if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RpcException("unexpected exception when ExecuteLimitFilter", t); } } } static class ExecuteLimitListener implements Listener { @Override public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) { RpcStatus.endCount(invoker.getUrl(), invocation.getMethodName(), getElapsed(invocation), true); } @Override public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) { RpcStatus.endCount(invoker.getUrl(), invocation.getMethodName(), getElapsed(invocation), false); } private long getElapsed(Invocation invocation) { String beginTime = invocation.getAttachment(EXECUTELIMIT_FILTER_START_TIME); return StringUtils.isNotEmpty(beginTime) ? System.currentTimeMillis() - Long.parseLong(beginTime) : 0; } }}ExecuteLimitFilter继承了ListenableFilter,其构造器初始化的listener为ExecuteLimitListenerinvoke方法先调用RpcStatus.beginCount方法来判断是否可以通过,不通过则抛出RpcException,通过则记录开始执行的时间,然后执行invoker.invoke方法,执行结束时会回调Listener的onResponse或onError方法ExecuteLimitListener的onResponse及onError方法均会调用RpcStatus.endCount;而该方法会通过getElapsed方法取出execugtelimit_filter_start_time值,计算执行耗时RpcStatusdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcStatus.java ...

June 25, 2019 · 2 min · jiezi

聊聊dubbo的TimeoutFilter

序本文主要研究一下dubbo的TimeoutFilter ListenableFilterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/ListenableFilter.java public abstract class ListenableFilter implements Filter { protected Listener listener = null; public Listener listener() { return listener; }}ListenableFilter声明实现Filter接口,它主要定义了listener()方法TimeoutFilterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TimeoutFilter.java @Activate(group = CommonConstants.PROVIDER)public class TimeoutFilter extends ListenableFilter { private static final Logger logger = LoggerFactory.getLogger(TimeoutFilter.class); private static final String TIMEOUT_FILTER_START_TIME = "timeout_filter_start_time"; public TimeoutFilter() { super.listener = new TimeoutListener(); } @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { invocation.setAttachment(TIMEOUT_FILTER_START_TIME, String.valueOf(System.currentTimeMillis())); return invoker.invoke(invocation); } static class TimeoutListener implements Listener { @Override public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) { String startAttach = invocation.getAttachment(TIMEOUT_FILTER_START_TIME); if (startAttach != null) { long elapsed = System.currentTimeMillis() - Long.valueOf(startAttach); if (invoker.getUrl() != null && elapsed > invoker.getUrl().getMethodParameter(invocation.getMethodName(), "timeout", Integer.MAX_VALUE)) { if (logger.isWarnEnabled()) { logger.warn("invoke time out. method: " + invocation.getMethodName() + " arguments: " + Arrays.toString(invocation.getArguments()) + " , url is " + invoker.getUrl() + ", invoke elapsed " + elapsed + " ms."); } } } } @Override public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) { } }}TimeoutFilter继承了ListenableFilter,其构造器初始化的listener为TimeoutListener;invoke方法先记录开始执行的时间,之后执行结束时会回调Listener的onResponse或onError方法;TimeoutListener的onResponse会取出timeout_filter_start_time值,如果存在则计算执行耗时,在设置了有效timeout且耗时大于该timeout时,会打印warn日志小结TimeoutFilter继承了ListenableFilter,其构造器初始化的listener为TimeoutListener;invoke方法先记录开始执行的时间,之后执行结束时会回调Listener的onResponse或onError方法;TimeoutListener的onResponse会取出timeout_filter_start_time值,如果存在则计算执行耗时,在设置了有效timeout且耗时大于该timeout时,会打印warn日志 ...

June 24, 2019 · 1 min · jiezi

聊聊dubbo的TPSLimiter

序本文主要研究一下dubbo的TPSLimiter TPSLimiterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/tps/TPSLimiter.java public interface TPSLimiter { /** * judge if the current invocation is allowed by TPS rule * * @param url url * @param invocation invocation * @return true allow the current invocation, otherwise, return false */ boolean isAllowable(URL url, Invocation invocation);}TPSLimiter定义了isAllowable方法DefaultTPSLimiterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/tps/DefaultTPSLimiter.java public class DefaultTPSLimiter implements TPSLimiter { private final ConcurrentMap<String, StatItem> stats = new ConcurrentHashMap<String, StatItem>(); @Override public boolean isAllowable(URL url, Invocation invocation) { int rate = url.getParameter(TPS_LIMIT_RATE_KEY, -1); long interval = url.getParameter(TPS_LIMIT_INTERVAL_KEY, DEFAULT_TPS_LIMIT_INTERVAL); String serviceKey = url.getServiceKey(); if (rate > 0) { StatItem statItem = stats.get(serviceKey); if (statItem == null) { stats.putIfAbsent(serviceKey, new StatItem(serviceKey, rate, interval)); statItem = stats.get(serviceKey); } else { //rate or interval has changed, rebuild if (statItem.getRate() != rate || statItem.getInterval() != interval) { stats.put(serviceKey, new StatItem(serviceKey, rate, interval)); statItem = stats.get(serviceKey); } } return statItem.isAllowable(); } else { StatItem statItem = stats.get(serviceKey); if (statItem != null) { stats.remove(serviceKey); } } return true; }}DefaultTPSLimiter实现了TPSLimiter,它使用ConcurrentHashMap来存储StatItem,其key为URL中的serviceKey;isAllowable方法从URL中读取tps参数,默认为-1,小于0则从ConcurrentHashMap中移除,大于0则创建或者获取StatItem,调用StatItem的isAllowable(重置或递减token并返回结果)StatItemdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/tps/StatItem.java ...

June 23, 2019 · 2 min · jiezi

聊聊dubbo的StatusChecker

序本文主要研究一下dubbo的StatusChecker Statusdubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/status/Status.java public class Status { private final Level level; private final String message; private final String description; public Status(Level level) { this(level, null, null); } public Status(Level level, String message) { this(level, message, null); } public Status(Level level, String message, String description) { this.level = level; this.message = message; this.description = description; } public Level getLevel() { return level; } public String getMessage() { return message; } public String getDescription() { return description; } /** * Level */ public enum Level { /** * OK */ OK, /** * WARN */ WARN, /** * ERROR */ ERROR, /** * UNKNOWN */ UNKNOWN }}Status定义了三个属性,分别是level、message、description;其中level有OK、WARN、ERROR、UNKNOWN四个级别StatusCheckerdubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/status/StatusChecker.java ...

June 22, 2019 · 2 min · jiezi

聊聊dubbo的ConcurrentHashSet

序本文主要研究一下dubbo的ConcurrentHashSet ConcurrentHashSetdubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ConcurrentHashSet.java public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>, java.io.Serializable { private static final long serialVersionUID = -8672117787651310382L; private static final Object PRESENT = new Object(); private final ConcurrentMap<E, Object> map; public ConcurrentHashSet() { map = new ConcurrentHashMap<E, Object>(); } public ConcurrentHashSet(int initialCapacity) { map = new ConcurrentHashMap<E, Object>(initialCapacity); } /** * Returns an iterator over the elements in this set. The elements are * returned in no particular order. * * @return an Iterator over the elements in this set * @see ConcurrentModificationException */ @Override public Iterator<E> iterator() { return map.keySet().iterator(); } /** * Returns the number of elements in this set (its cardinality). * * @return the number of elements in this set (its cardinality) */ @Override public int size() { return map.size(); } /** * Returns <tt>true</tt> if this set contains no elements. * * @return <tt>true</tt> if this set contains no elements */ @Override public boolean isEmpty() { return map.isEmpty(); } /** * Returns <tt>true</tt> if this set contains the specified element. More * formally, returns <tt>true</tt> if and only if this set contains an * element <tt>e</tt> such that * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>. * * @param o element whose presence in this set is to be tested * @return <tt>true</tt> if this set contains the specified element */ @Override public boolean contains(Object o) { return map.containsKey(o); } /** * Adds the specified element to this set if it is not already present. More * formally, adds the specified element <tt>e</tt> to this set if this set * contains no element <tt>e2</tt> such that * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>. If this * set already contains the element, the call leaves the set unchanged and * returns <tt>false</tt>. * * @param e element to be added to this set * @return <tt>true</tt> if this set did not already contain the specified * element */ @Override public boolean add(E e) { return map.put(e, PRESENT) == null; } /** * Removes the specified element from this set if it is present. More * formally, removes an element <tt>e</tt> such that * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>, if this * set contains such an element. Returns <tt>true</tt> if this set contained * the element (or equivalently, if this set changed as a result of the * call). (This set will not contain the element once the call returns.) * * @param o object to be removed from this set, if present * @return <tt>true</tt> if the set contained the specified element */ @Override public boolean remove(Object o) { return map.remove(o) == PRESENT; } /** * Removes all of the elements from this set. The set will be empty after * this call returns. */ @Override public void clear() { map.clear(); }}ConcurrentHashSet继承了AbstractSet,实现了Set、Serializable接口;它底层使用ConcurrentHashMap来实现,其value固定为PRESENT实例dubbo-2.7.2/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java ...

June 21, 2019 · 4 min · jiezi

聊聊dubbo的LRUCache

序本文主要研究一下dubbo的LRUCache LRUCachedubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java public class LRUCache<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = -5167631809472116969L; private static final float DEFAULT_LOAD_FACTOR = 0.75f; private static final int DEFAULT_MAX_CAPACITY = 1000; private final Lock lock = new ReentrantLock(); private volatile int maxCapacity; public LRUCache() { this(DEFAULT_MAX_CAPACITY); } public LRUCache(int maxCapacity) { super(16, DEFAULT_LOAD_FACTOR, true); this.maxCapacity = maxCapacity; } @Override protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { return size() > maxCapacity; } @Override public boolean containsKey(Object key) { lock.lock(); try { return super.containsKey(key); } finally { lock.unlock(); } } @Override public V get(Object key) { lock.lock(); try { return super.get(key); } finally { lock.unlock(); } } @Override public V put(K key, V value) { lock.lock(); try { return super.put(key, value); } finally { lock.unlock(); } } @Override public V remove(Object key) { lock.lock(); try { return super.remove(key); } finally { lock.unlock(); } } @Override public int size() { lock.lock(); try { return super.size(); } finally { lock.unlock(); } } @Override public void clear() { lock.lock(); try { super.clear(); } finally { lock.unlock(); } } public int getMaxCapacity() { return maxCapacity; } public void setMaxCapacity(int maxCapacity) { this.maxCapacity = maxCapacity; }}LRUCache继承了LinkedHashMap,其initialCapacity为16,maxCapacity默认是1000;它覆盖了removeEldestEntry方法,当size()大于maxCapacity时返回true;它还声明了一个ReentrantLock,对containsKey、get、put、remove、size、clear方法进行了加锁操作实例dubbo-2.7.2/dubbo-common/src/test/java/org/apache/dubbo/common/utils/LRUCacheTest.java ...

June 20, 2019 · 2 min · jiezi

借助-Cloud-Toolkit-快速创建-Dubbo-工程

Cloud Toolkit 是一个 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。在最新版的插件中,提供了快速创建 Dubbo 工程的功能,下面就来快速体验下吧。 Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展 进行加载。如果不想使用 Spring 配置,可以通过 API 的方式 进行调用。 功能预览 功能入口打开 IntelliJ IDEA,进入菜单:File - New - Project... 第一步:选择 JAVA SDK 版本 第二步:填写应用基本信息 包括选择 Dubbo 版本、Spring Boot 版本等。 第三步:确定创建 如下图所示,就完成了一个完整的 Dubbo 工程的创建了,此工程的结构和Apache Dubbo 官方样例工程完全一致。参考 Apache Dubbo 官方样例工程:https://dubbo.apache.org/zh-cn/docs/user/quick-start.html 立即点击下载 官网 https://toolkit.aliyun.com 亮点1:本地应用一键部署 Deploy to ECS开发者本地编写的应用程序,在图形化界面上进行配置,即可持续便利的部署到云端的 ECS 服务器上;在 Eclipse 中完成编码后,无须在 Maven 、Git 以及其他运维脚本和工具的之间切换,借助 Cloud Toolkit for Eclipse 插件,在 IDE 的图形界面上选择一个或若干个 ECS 实例,即可将应用程序部署至 ECS 指定目录Deploy to EDAS针对阿里云 EDAS 产品的开发者,我们也在插件上打通了本地应用程序和云端部署,在 Eclipse 中完成编码后,将 IDE 内的项目工程,关联上 EDAS 的应用,即可实现快速部署。Deploy to CS Kubernetes针对阿里云 容器服务 Kubernetes 产品的开发者,我们也在插件上打通了本地应用程序和云端Kubernetes部署,在 Eclipse 中完成编码后,将 IDE 内的项目工程,关联上 容器服务 Kubernetes 的部署,即可实现快速部署。亮点2:内置终端 Terminal ...

June 20, 2019 · 1 min · jiezi

聊聊dubbo的DataStore

序本文主要研究一下dubbo的DataStore DataStoredubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/store/DataStore.java @SPI("simple")public interface DataStore { /** * return a snapshot value of componentName */ Map<String, Object> get(String componentName); Object get(String componentName, String key); void put(String componentName, String key, Object value); void remove(String componentName, String key);}DataStore定义了get、put、remove方法,它有一个实现类为SimpleDataStoreSimpleDataStoredubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/store/support/SimpleDataStore.java public class SimpleDataStore implements DataStore { // <component name or id, <data-name, data-value>> private ConcurrentMap<String, ConcurrentMap<String, Object>> data = new ConcurrentHashMap<String, ConcurrentMap<String, Object>>(); @Override public Map<String, Object> get(String componentName) { ConcurrentMap<String, Object> value = data.get(componentName); if (value == null) { return new HashMap<String, Object>(); } return new HashMap<String, Object>(value); } @Override public Object get(String componentName, String key) { if (!data.containsKey(componentName)) { return null; } return data.get(componentName).get(key); } @Override public void put(String componentName, String key, Object value) { Map<String, Object> componentData = data.get(componentName); if (null == componentData) { data.putIfAbsent(componentName, new ConcurrentHashMap<String, Object>()); componentData = data.get(componentName); } componentData.put(key, value); } @Override public void remove(String componentName, String key) { if (!data.containsKey(componentName)) { return; } data.get(componentName).remove(key); }}SimpleDataStore使用ConcurrentHashMap来存储数据,每个key存储的是ConcurrentMap实例dubbo-2.7.2/dubbo-common/src/test/java/org/apache/dubbo/common/store/support/SimpleDataStoreTest.java ...

June 20, 2019 · 2 min · jiezi

聊聊dubbo的EagerThreadPool

序本文主要研究一下dubbo的EagerThreadPool EagerThreadPooldubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java public class EagerThreadPool implements ThreadPool { @Override public Executor getExecutor(URL url) { String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME); int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS); int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE); int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES); int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE); // init queue and executor TaskQueue<Runnable> taskQueue = new TaskQueue<Runnable>(queues <= 0 ? 1 : queues); EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS, taskQueue, new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url)); taskQueue.setExecutor(executor); return executor; }}EagerThreadPool实现了ThreadPool接口,其getExecutor创建的是EagerThreadPoolExecutor,它使用的queue为TaskQueue,使用的threadFactory为NamedInternalThreadFactory,使用的rejectedExecutionHandler为AbortPolicyWithReportEagerThreadPoolExecutordubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPoolExecutor.java ...

June 19, 2019 · 4 min · jiezi

Service-Mesh-时代Dubbo-架构该怎么跟进

原文链接:Service Mesh 时代,Dubbo 架构该怎么跟进?,来自于微信公众号:次灵均阁作为 Duboo 核心开发者,请先简单介绍下自己答:大家好,我是小马哥(mercyblitz),一名学习当爸爸的父亲,Java 劝退师,Apache Dubbo PMC、Spring Cloud Alibaba项目架构师,《Spring Boot 编程思想》的作者。目前主要负责集团中间件开源项目、微服务技术实施、架构衍进、基础设施构建等。 Spring Cloud 和 Duboo 在微服务方面的优劣分别是什么?答:在 Java 生态中,Spring Cloud 和 Dubbo 都是微服务框架。前者被业界常作为 Java 微服务的首选框架,而后者有时被错误地解读为服务治理的 RPC 框架。实际上,两者在微服务架构中并没有本质的差异,均是分布式应用服务治理的框架。 在开发体验方面,Spring Cloud 开箱即用的组件让人印象深刻。在 API 抽象和设计方面,流淌着 Spring 家族血液的 Spring Cloud 延续了父辈的荣耀。由此观之,Dubbo 与其存在差距。 然而随着实践的不断深入,Spring Cloud 功能的稳定性以及版本的兼容性等问题较为突出。当应用集群达到一定规模时,其分布式经验上的短板也随之暴露,尤其是 Spring Cloud Netflix 套件,比如 Eureka 与 Ribbon 之间的 90 秒延迟会影响服务调用的成功率,以及负载均衡算法缺少权重无法帮助 JVM 预热。简言之,在服务治理方面,Spring Cloud 相较于 Dubbo 而言,并不算太成熟。如果大家有兴趣了解更多的话,可参考「小马哥技术周报」。 总之,Spring Cloud 和 Dubbo 各有特色,过度地关注彼此优劣并不可取。为此,Spring Cloud Alibaba 项目综合两家之长,提供了一套名为 Dubbo Spring Cloud 的整容实现,使得 Dubbo 与 Spring Cloud 不再是互斥性选项。 ...

June 18, 2019 · 2 min · jiezi

HSFDubbo序列化时的LocalDateTime-Instant的性能问题

来源在对Dubbo新版本做性能压测时,无意中发现对用例中某个TO(Transfer Object)类的一属性字段稍作修改,由Date变成LocalDateTime,结果是吞吐量由近5w变成了2w,RT由9ms升指90ms。 在线的系统,拼的从来不仅仅是吞吐量,而是在保证一定的RT基础上,再去做其他文章的, 也就是说应用的RT是我们服务能力的基石所在, 拿压测来说, 我们能出的qps/tps容量, 必须是应用能接受的RT下的容量,而不是纯理论的数据,在集团云化的过程中计算过,底层服务的RT每增加0.1ms,在应用层就会被放大,整体的成本就会上升10%以上。 要走向异地,首先要面对的阿喀琉斯之踵:延时,长距离来说每一百公里延时差不多在1ms左右,杭州和上海来回的延迟就在5ms以上,上海到深圳的延迟无疑会更大,延时带来的直接影响也是响应RT变大,用户体验下降,成本直线上升。 如果一个请求在不同单元对同一行记录进行修改, 即使假定我们能做到一致性和完整性, 那么为此付出的代价也是非常高的,想象一下如果一次请求需要访问10 次以上的异地 HSF 服务或 10 次以上的异地 DB调用, 服务再被服务调用,延时就形成雪球,越滚越大了。 普遍性关于时间的处理应该是无处不在,可以说离开了时间属性,99.99%的业务应用都无法支持其意义,特别是像监控类的系统中更是面向时间做针对性的定制处理。 在JDK8以前,基本是通过java.util.Date来描述日期和时刻,java.util.Calendar来做时间相关的计算处理。JDK8引入了更加方便的时间类,包括Instant,LocalDateTime、OffsetDateTime、ZonedDateTime等等,总的说来,时间处理因为这些类的引入而更加直接方便。 Instant存的是UTC的时间戳,提供面向机器时间视图,适合用于数据库存储、业务逻辑、数据交换、序列化。LocalDateTime、OffsetDateTime、ZonedDateTime等类结合了时区或时令信息,提供了面向人类的时间视图,用于向用户输入输出,同一个时间面向不同用户时,其值是不同的。比如说订单的支付、发货时间买卖双方都用本地时区显示。可以把这3个类看作是一个面向外部的工具类,而不是应用程序内部的工作部分。简单说来,Instant适用于后端服务和数据库存储,而LocalDateTime等等适用于前台门面系统和前端展示,二者可以自由转换。这方面,国际化业务的同学有相当多的体感和经验。 在HSF/Dubbo的服务集成中,无论是Date属性还是Instant属性肯定是普遍的一种场景。 问题复现Instant等类的性能优势以常见的格式化场景举例 @Benchmark @BenchmarkMode(Mode.Throughput) public String date_format() { Date date = new Date(); return new SimpleDateFormat("yyyyMMddhhmmss").format(date); } @Benchmark @BenchmarkMode(Mode.Throughput) public String instant_format() { return Instant.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern( "yyyyMMddhhmmss")); }在本地通过4个线程来并发运行30秒做压测,结果如下。 Benchmark Mode Cnt Score Error UnitsDateBenchmark.date_format thrpt 4101298.589 ops/sDateBenchmark.instant_format thrpt 6816922.578 ops/s可见,Instant在format时性能方面是有优势的,事实上在其他操作方面(包括日期时间相加减等)都是有性能优势,大家可以自行搜索或写代码测试来求解。 Instant等类在序列化时的陷阱针对Java自带,Hessian(淘宝优化版本)两种序列化方案,压测序列化和反序列化的处理性能。 Hessian是集团内应用的HSF2.2和开源的Dubbo中默认的序列化方案。 @Benchmark @BenchmarkMode(Mode.Throughput) public Date date_Hessian() throws Exception { Date date = new Date(); byte[] bytes = dateSerializer.serialize(date); return dateSerializer.deserialize(bytes); } @Benchmark @BenchmarkMode(Mode.Throughput) public Instant instant_Hessian() throws Exception { Instant instant = Instant.now(); byte[] bytes = instantSerializer.serialize(instant); return instantSerializer.deserialize(bytes); } @Benchmark @BenchmarkMode(Mode.Throughput) public LocalDateTime localDate_Hessian() throws Exception { LocalDateTime date = LocalDateTime.now(); byte[] bytes = localDateTimeSerializer.serialize(date); return localDateTimeSerializer.deserialize(bytes); }结果如下。可以看出,在Hessian方案下,无论还是Instant还是LocalDateTime,吞吐量相比较Date,都出现“大跌眼镜”的下滑,相差100多倍;通过通过分析,每一次把Date序列化为字节流是6个字节,而LocalDateTime则是256个字节,这个放到网络带宽中的传输代价也是会被放大。 在Java内置的序列化方案下,有稍微下滑,但没有本质区别。 ...

June 18, 2019 · 1 min · jiezi

聊聊dubbo的Filter

序本文主要研究一下dubbo的Filter Filterdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Filter.java @SPIpublic interface Filter { /** * Does not need to override/implement this method. */ Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException; /** * Filter itself should only be response for passing invocation, all callbacks has been placed into {@link Listener} * * @param appResponse * @param invoker * @param invocation * @return */ @Deprecated default Result onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) { return appResponse; } interface Listener { void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation); void onError(Throwable t, Invoker<?> invoker, Invocation invocation); }}Filter定义了invoke、onResponse方法,另外还定义了Listener接口,该接口定义了onResponse、onError方法ProtocolFilterWrapperdubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java ...

June 18, 2019 · 3 min · jiezi

聊聊dubbo的Invoker-select

序本文主要研究一下dubbo的Invoker select InvokerInvocationHandler.invokedubbo-2.7.1-sources.jar!/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java public class InvokerInvocationHandler implements InvocationHandler { //...... public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); if (method.getDeclaringClass() == Object.class) { return method.invoke(invoker, args); } if ("toString".equals(methodName) && parameterTypes.length == 0) { return invoker.toString(); } if ("hashCode".equals(methodName) && parameterTypes.length == 0) { return invoker.hashCode(); } if ("equals".equals(methodName) && parameterTypes.length == 1) { return invoker.equals(args[0]); } return invoker.invoke(createInvocation(method, args)).recreate(); } //......}这里invoker为MockClusterInvokerMockClusterInvokerdubbo-2.7.1-sources.jar!/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java ...

June 16, 2019 · 5 min · jiezi

缘起-Dubbo-讲讲-Spring-XML-Schema-扩展机制

背景在 Dubbo 中,可以使用 XML 配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring 会读取配置文件,生成注入 相关 Bean。那 Dubbo 如何实现自定义 XML 被 Spring 加载读取? Spring XML Schema 扩展机制。从 Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机制,用于定义和配置 bean。 Spring XML Schema 扩展机制实现 Spring XML Schema 扩展,其实非常简单,只需要完成下面四步。 创建 XML Schema 文件,由于该文件后缀名为 xsd,下面称为 XSD 文件。编写实现一个或多个 BeanDefinitionParser 。编写NamespaceHandler实现类。注册 NamespaceHandler 以及 XSD 文件。我们按照以上步骤,最终完整 Spring 解析如下配置。 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:demo="http://www.test.com/demo" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.test.com/demo http://www.test.com/demo/demo.xsd"> <demo:application name="test" id="test"/></beans>创建 XSD 文件XSD 文件,主要用来定义 XML 格式,用来验证 XML 合法性。在 IDE 中,导入 XSD 文件,编辑 XML 文件可以获得相关提示。 ...

June 16, 2019 · 2 min · jiezi

从遇见到信任-Apache-Dubbo-的毕业之旅

所谓信任,就是多一次机会。 2018年2月16日,Apache Dubbo 加入 Apache 基金会孵化器。 ... 2019年5月16日,Apache 软件基金会董事会决议通过了 Apache Dubbo 的毕业申请,这意味着 Apache Dubbo 正式成为 Apache 的顶级项目。5月21日,Apache 官方发布了这一消息。这也是 阿里巴巴微服务 继 Apache RocketMQ 后的又一个 Apache 顶级项目。 What is Dubbo ?Apache Dubbo 起初的定位是一款轻量级、高性能的服务框架,自 2012 年开源以来,深受国内开发者的喜爱,并被国内许多企业选型作为服务化改造的方案首选和微服务架构的基石之一。其主要功能是: 提供基于RPC的高性能接口,对用户透明。智能负载均衡:支持多种开箱即用的负载均衡策略,可以感知下游服务状态,从而减少总体延迟并提高系统吞吐量。自动服务注册和发现:支持多个服务注册表,可以立即在线/离线检测服务。高可扩展性:微内核和插件设计确保可以通过协议,传输和序列化等核心功能轻松扩展第三方实施。运行时流量路由:可以在运行时配置,以便根据不同的规则路由流量,这样可以轻松支持蓝绿部署,数据中心感知路由等功能。可视化服务治理:为服务治理和维护提供丰富的工具,例如查询服务元数据,运行状况和统计信息。Dubbo meets Apache2018 年 2 月,阿里巴巴将 Apache Dubbo 捐献给 Apache 软件基金会,得到了社区广泛的好评。 在这1年多的孵化过程中,Dubbo 社区: 持续迭代,共计发布11个版本;多元化治理,新增了6位 PPMC Member (孵化项目管理管理会成员),他们来自阿里巴巴、京东、美团点评、去哪儿、网易、微店、有赞等企业;并发展了15位项目提交者(对 Dubbo 项目具有提交权限),他们来自阿里巴巴、曹操科技、滴滴出行、国美金融、韩都衣舍、华为、京东、Keep、科大讯飞、美团点评、去哪儿、融贯电商、网联清算、网易、微店、亚信安全等10多家公司;构建多元化社区,Dubbo 主项目的贡献者从70+提升到目前的200位;用户多元化,阿里巴巴、当当、滴滴、海尔、去哪儿、网联清算、网易考拉、微店、中国电信、中国工商银行、中国人寿、中国银联等140多家公司在 GitHub 上报告了已将 Apache Dubbo 运用于生产环境中 ;GitHub 上的 star 数从入住孵化器前的17520增加到26400+,fork 数更是达到了17500+,fork 数排在所有Java 项目中的第三位;孵化过程中,Dubbo 社区的多样性得到了极大的发展,并不断演进核心和丰富生态,旨在为开发者们构建微服务和云原生支撑的基石。 ...

June 14, 2019 · 2 min · jiezi

dubbo源码解析四十七服务端处理请求过程

2.7大揭秘——服务端处理请求过程目标:从源码的角度分析服务端接收到请求后的一系列操作,最终把客户端需要的值返回。前言上一篇讲到了消费端发送请求的过程,该篇就要将服务端处理请求的过程。也就是当服务端收到请求数据包后的一系列处理以及如何返回最终结果。我们也知道消费端在发送请求的时候已经做了编码,所以我们也需要在服务端接收到数据包后,对协议头和协议体进行解码。不过本篇不讲解如何解码。有兴趣的可以翻翻我以前的文章,有讲到关于解码的逻辑。接下来就开始讲解服务端收到请求后的逻辑。 处理过程假设远程通信的实现还是用netty4,解码器将数据包解析成 Request 对象后,NettyHandler 的 messageReceived 方法紧接着会收到这个对象,所以第一步就是NettyServerHandler的channelRead。 (一)NettyServerHandler的channelRead可以参考《dubbo源码解析(十七)远程通信——Netty4》的(三)NettyServerHandler public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 看是否在缓存中命中,如果没有命中,则创建NettyChannel并且缓存。 NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); try { // 接受消息 handler.received(channel, msg); } finally { // 如果通道不活跃或者断掉,则从缓存中清除 NettyChannel.removeChannelIfDisconnected(ctx.channel()); }}NettyServerHandler是基于netty4实现的服务端通道处理实现类,而该方法就是用来接收请求,接下来就是执行AbstractPeer的received。 (二)AbstractPeer的received可以参考《dubbo源码解析(九)远程通信——Transport层》的(一)AbstractPeer public void received(Channel ch, Object msg) throws RemotingException { // 如果通道已经关闭,则直接返回 if (closed) { return; } handler.received(ch, msg);}该方法比较简单,之前也讲过AbstractPeer类就做了装饰模式中装饰角色,只是维护了通道的正在关闭和关闭完成两个状态。然后到了MultiMessageHandler的received (三)MultiMessageHandler的received可以参考《dubbo源码解析(九)远程通信——Transport层》的(八)MultiMessageHandler public void received(Channel channel, Object message) throws RemotingException { // 如果消息是MultiMessage类型的,也就是多消息类型 if (message instanceof MultiMessage) { // 强制转化为MultiMessage MultiMessage list = (MultiMessage) message; // 把各个消息进行发送 for (Object obj : list) { handler.received(channel, obj); } } else { // 直接发送 handler.received(channel, message); }}该方法也比较简单,就是对于多消息的处理。 ...

June 8, 2019 · 11 min · jiezi

dubbo源码解析四十六消费端发送请求过程

2.7大揭秘——消费端发送请求过程目标:从源码的角度分析一个服务方法调用经历怎么样的磨难以后到达服务端。前言前一篇文章讲到的是引用服务的过程,引用服务无非就是创建出一个代理。供消费者调用服务的相关方法。本节将从调用方法开始讲解内部的整个调用链。我们就拿dubbo内部的例子讲。 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");context.start();DemoService demoService = context.getBean("demoService", DemoService.class);String hello = demoService.sayHello("world");System.out.println("result: " + hello);这是dubbo-demo-xml-consumer内的实例代码。接下来我们就开始来看调用demoService.sayHello方法的时候,dubbo执行了哪些操作。 执行过程(一)InvokerInvocationHandler的invokepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获得方法名称 String methodName = method.getName(); // 获得方法参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); // 如果该方法所在的类是Object类型,则直接调用invoke。 if (method.getDeclaringClass() == Object.class) { return method.invoke(invoker, args); } // 如果这个方法是toString,则直接调用invoker.toString() if ("toString".equals(methodName) && parameterTypes.length == 0) { return invoker.toString(); } // 如果这个方法是hashCode直接调用invoker.hashCode() if ("hashCode".equals(methodName) && parameterTypes.length == 0) { return invoker.hashCode(); } // 如果这个方法是equals,直接调用invoker.equals(args[0]) if ("equals".equals(methodName) && parameterTypes.length == 1) { return invoker.equals(args[0]); } // 调用invoke return invoker.invoke(new RpcInvocation(method, args)).recreate();}可以看到上面的源码,首先对Object的方法进行了处理,如果调用的方法不是这些方法,则先会 创建RpcInvocation,然后再调用invoke。 ...

June 4, 2019 · 7 min · jiezi

厉害了Dubbo-正式毕业

厉害了,2019/05/21 Apache软件基金会发表博文,宣布 Dubbo 在 2019/05/20 这天正式毕业,成为 Apache 的顶级项目。 参考:https://blogs.apache.org/foun...不过 Github 的名称还没改过来,还是叫 incubator-dubbo,坐等更新为:dubbo,估计快了。 栈长这里科普一下: Dubbo是阿里巴巴开源的 RPC 框架,后进入 Apache 开源孵化器,目前已在数十家公司中使用,包括阿里巴巴集团、中国人寿、中国电信、当当网、滴滴出行、海尔、中国工商银行等,大多是国内公司。 哈哈,上一张 Dubbo 的架构图,来自官网: 再来回顾下 Dubbo 的发展史: 2011/10/27: 阿里巴巴巴宣布 Dubbo 开源。 2012/10/23: 发布最后一个版本 2.5.3 并停止维护更新。 2017/07/31: 起死回生,官方宣布开启重新更新,并会得到重点维护,参考:起死回生的分布式神器—Dubbo。 2017/09/07: 发布起死回生的第一个版本:dubbo-2.5.4。 2018/01/08: 1、Dubbo 团队透露 Dubbo 3.0 宣布正式开工,参考:重大利好,Dubbo 3.0要来了。 2、发布了 dubbo-2.6.0 版本,主要合并了由当当网开源的 dubbox 项目分支。PS:dubbo停止维护期间,当当网基于 dubbo 开源了dubbox。 2018/01/22: Dubbo Spring Boot 版正式发布:dubbo-spring-boot-starter v1.0.0 公测版。 2018/02/09: Dubbo 通过投票正式进入 Apache 基金会孵化器,更新了 Apache 官方域名,也不再仅限于 Java 语言。 ...

May 22, 2019 · 1 min · jiezi

Congratulations-Apache®-Dubbo™-as-a-TLP

The Apache Software Foundation Announces Apache® Dubbo™ as a Top-Level ProjectThis article aims to record the important moment of Apache® Dubbo™. Wakefield, MA —20 May 2019— The Apache Software Foundation (ASF), the all-volunteer developers, stewards, and incubators of more than 350 Open Source projects and initiatives, announced today Apache® Dubbo™ as a Top-Level Project (TLP). This is really exciting news, congratulations! link:https://blogs.apache.org/foun...

May 21, 2019 · 1 min · jiezi

一个Dubbo泛化调用的Util

源码地址https://github.com/wheel-orga...作用可以更加灵活的调用其他dubbo接口 实现思路反射+泛化调用 局限性局限性: 传入的参数列表顺序必须和方法上的参数顺序相同(问题不大)拿不到具体类型的泛型反序列化还是会失败(这种情况很少)需要自己多写一个helper必须项目引入了对应的api(问题不大)dubbo源码有大量变动或结构性改变时,此util也要维护(Apache的dubbo可以兼容alibaba的dubbo,所以我用了alibaba的dubbo)如何使用注意:引入的时候一定要去掉api里的dubbo 入口及入参说明:唯一入口为: DubboGenericInvoker#invoke url为dubbo-admin的url methodName为调用方法名 params为参数列表List(顺序必须和方法定义的参数顺序相同,dto请序列化) 实际使用:配合swagger 代码单测调用

May 13, 2019 · 1 min · jiezi

Hibernate-Validator更简洁的参数校验及一个util

代码地址https://github.com/wheel-orga...简介hibernate-validator是Hibernate项目中的一个数据校验框架,是Bean Validation 的参考实现,hibernate-validator除了提供了JSR 303规范中所有内置constraint 的实现,还有一些附加的constraint。使用hibernate-validator能够将数据校验从业务代码中脱离出来,增加代码可读性,同时也让数据校验变得更加方便、简单。 官网地址:http://hibernate.org/validator/ 如何使用项目中已经引入了需要的api,无需重复引入<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version></dependency><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.16.Final</version></dependency>在要校验的POJO上加上以下注解即可 注解用途Valid递归的对关联的对象进行校验AssertFalse用于boolean字段,该字段的值只能为falseAssertTrue用于boolean字段,该字段只能为trueDecimalMax(value)被注释的元素必须是一个数字,只能大于或等于该值DecimalMin(value)被注释的元素必须是一个数字,只能小于或等于该值Digits(integer,fraction)检查是否是一种数字的(整数,小数)的位数Future检查该字段的日期是否是属于将来的日期FutureOrPresent判断日期是否是将来或现在日期Past检查该字段的日期是在过去PastOrPresent判断日期是否是过去或现在日期Max(value)该字段的值只能小于或等于该值Min(value)该字段的值只能大于或等于该值Negative判断负数NegativeOrZero判断负数或0Positive判断正数PositiveOrZero判断正数或0NotNull不能为nullNull必须为 nullPattern(value)被注释的元素必须符合指定的正则表达式Size(max, min)检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等Length(max, min)判断字符串长度CreditCardNumber被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性Email被注释的元素必须是电子邮箱地址Length(min=, max=)被注释的字符串的大小必须在指定的范围内NotBlank只能用于字符串不为null,并且字符串trim()以后length要大于0NotEmpty集合对象的元素不为0,即集合不为空,也可以用于字符串不为nullRange(min=, max=)被注释的元素必须在合适的范围内SafeHtmlclasspath中要有jsoup包ScriptAssert要有Java Scripting API 即JSR 223("Scripting for the JavaTMPlatform")的实现URL(protocol=,host=,port=,regexp=,flags=)被注释的字符串必须是一个有效的url更多功能,如:自定义校验规则、分组校验、关联参数联合校验请查看官网或百度 Dubbo中使用Hibernate Validator校验入参无需util,Dubbo接口配置上的validation为true即可 在客户端验证参数 <dubbo:reference id="xxxService" interface="xxx.ValidationService" validation="true" />在服务器端验证参数 <dubbo:service interface="xxx.ValidationService" ref="xxxService" validation="true" />在代码里校验入参//obj为包含Hibernate Validator注解的POJO//快速失败模式ValidResult validResult = ValidationUtil.fastFailValidate(obj);//obj为包含Hibernate Validator注解的POJO//全部校验模式ValidResult validResult = ValidationUtil.allCheckValidate(obj);样例public class ParamTestDTO implements Serializable { private static final long serialVersionUID = 7123882542534668217L; @AssertTrue(message = "Error True") private Boolean testTrue; @AssertFalse(message = "Error False") private Boolean testFalse; @DecimalMax(value = "10", message = "Error StrMax") private String testStrMax; @DecimalMin(value = "1", message = "Error StrMin") private String testStrMin; @Max(value = 10, message = "Error Max") private Integer testMax; @Min(value = 1, message = "Error Min") private Double testMin; @Digits(integer = 2, fraction = 3, message = "Error Dig") private BigDecimal testDig; @Past(message = "Error Past") private Date testPast; @Future(message = "Error Future") private Date testFuture; @Null(message = "Error Null") private String testNull; @NotNull(message = "Error NonNull") private String testNonNull; @Pattern(regexp = "^[0-9]?[0-9]$", message = "Error Pattern") private String testPattern; @Size(min = 1, max = 10, message = "Error Size") private List<String> testSize; @Length(min = 1, max = 10, message = "Error Length") private String testLength; @NotBlank(message = "Error Blank") private String testBlank; @NotEmpty(message = "Error NotEmpty") private String testEmpty; @Range(min = 1, max = 10, message = "Error Range") private String testRange;}单测:ValidationUtilTest ...

May 13, 2019 · 2 min · jiezi

Java并发9Lock和Condition下-Dubbo如何用管程实现异步转同步

在上一篇文章中,我们讲到 Java SDK 并发包里的 Lock 有别于 synchronized 隐式锁的三个特性:能够响应中断、支持超时和非阻塞地获取锁。那今天我们接着再来详细聊聊 Java SDK 并发包里的 Condition。 Condition 实现了管程模型里面的条件变量在之前我们详细讲过, Java 语言内置的管程里只有一个条件变量,而 Lock&Condition 实现的管程是支持多个条件变量的,这是二者的一个重要区别。 在很多并发场景下,支持多个条件变量能够让我们的并发程序可读性更好,实现起来也更容易。例如,实现一个阻塞队列,就需要两个条件变量。 这里我们温故知新下前面的内容。 public class BlockedQueue<T>{ final Lock lock = new ReentrantLock(); // 条件变量:队列不满 final Condition notFull = lock.newCondition(); // 条件变量:队列不空 final Condition notEmpty = lock.newCondition(); // 入队 void enq(T x) { lock.lock(); try { while (队列已满){ // 等待队列不满 notFull.await(); } // 省略入队操作... // 入队后, 通知可出队 notEmpty.signal(); }finally { lock.unlock(); } } // 出队 void deq(){ lock.lock(); try { while (队列已空){ // 等待队列不空 notEmpty.await(); } // 省略出队操作... // 出队后,通知可入队 notFull.signal(); }finally { lock.unlock(); } }}不过,这里你需要注意,Lock 和 Condition 实现的管程,线程等待和通知需要调用 await()、signal()、signalAll(),它们的语义和 wait()、notify()、notifyAll() 是相同的, 不要相互使用。 ...

May 12, 2019 · 2 min · jiezi

聊聊-Apache-Dubbo

本文来自于我的个人主页:Apache Dubbo,转载请保留链接 ;)在2011年10月27日,阿里巴巴开源了自己的SOA服务化治理方案的核心框架Dubbo,服务治理和SOA的设计理念开始逐渐在国内软件行业中落地,并被广泛应用。 Dubbo作为阿里巴巴内部的SOA服务化治理方案的核心框架,在2012年时已经每天为2000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。Dubbo自2011年开源后,已被许多非阿里系公司使用,其中既有当当网、网易考拉等互联网公司,也有中国人寿、青岛海尔等传统企业。本文是作者根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。 Dubbo 官网:https://dubbo.apache.org/zh-cn/index.html <!-- MarkdownTOC --> 一 重要的概念 1.1 什么是 Dubbo?1.2 什么是 RPC?RPC原理是什么?1.3 为什么要用 Dubbo?1.4 什么是分布式?1.5 为什么要分布式?二 Dubbo 的架构 2.1 Dubbo 的架构图解2.2 Dubbo 工作原理三 Dubbo 的负载均衡策略 3.1 先来解释一下什么是负载均衡3.2 再来看看 Dubbo 提供的负载均衡策略 3.2.1 Random LoadBalance(默认,基于权重的随机负载均衡机制)3.2.2 RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制)3.2.3 LeastActive LoadBalance3.2.4 ConsistentHash LoadBalance3.3 配置方式四 zookeeper宕机与dubbo直连的情况<!-- /MarkdownTOC --> 一 重要的概念1.1 什么是 Dubbo?Apache Dubbo (incubating) |db| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 Dubbo 目前已经有接近 25k 的 Star ,Dubbo的Github 地址:https://github.com/apache/incubator-dubbo 。 另外,在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中,Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。 ...

April 27, 2019 · 2 min · jiezi

dubbo源码解析四十五服务引用过程

dubbo服务引用过程目标:从源码的角度分析服务引用过程。前言前面服务暴露过程的文章讲解到,服务引用有两种方式,一种就是直连,也就是直接指定服务的地址来进行引用,这种方式更多的时候被用来做服务测试,不建议在生产环境使用这样的方法,因为直连不适合服务治理,dubbo本身就是一个服务治理的框架,提供了很多服务治理的功能。所以更多的时候,我们都不会选择绕过注册中心,而是通过注册中心的方式来进行服务引用。 服务引用过程 大致可以分为三个步骤: 配置加载创建invoker创建服务接口代理类引用起点dubbo服务的引用起点就类似于bean加载。dubbo中有一个类ReferenceBean,它实现了FactoryBean接口,继承了ReferenceConfig,所以ReferenceBean作为dubbo中能生产对象的工厂Bean,而我们要引用服务,也就是要有一个该服务的对象。 服务引用被触发有两个时机: Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务(饿汉式)在 ReferenceBean 对应的服务被注入到其他类中时引用(懒汉式)默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 <dubbo:reference> 的 init 属性开启。 因为ReferenceBean实现了FactoryBean接口的getObject()方法,所以在加载bean的时候,会调用ReferenceBean的getObject()方法 ReferenceBean的getObject()public Object getObject() { return get();}这个get方法是ReferenceConfig的get()方法 ReferenceConfig的get()public synchronized T get() { // 检查并且更新配置 checkAndUpdateSubConfigs(); // 如果被销毁,则抛出异常 if (destroyed) { throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!"); } // 检测 代理对象ref 是否为空,为空则通过 init 方法创建 if (ref == null) { // 用于处理配置,以及调用 createProxy 生成代理类 init(); } return ref;}关于checkAndUpdateSubConfigs()方法前一篇文章已经讲了,我就不再讲述。这里关注init方法。该方法也是处理各类配置的开始。 ...

April 26, 2019 · 10 min · jiezi

Dubbo-Spring-Cloud-重塑微服务治理

原文链接:Dubbo Spring Cloud 重塑微服务治理,来自于微信公众号:次灵均阁摘要在 Java 微服务生态中,Spring Cloud1 成为了开发人员的首选技术栈,然而随着实践的深入和运用规模的扩大,大家逐渐意识到 Spring Cloud 的局限性。在服务治理方面,相较于 Dubbo2 而言,Spring Cloud 并不成熟。遗憾的是,Dubbo 往往被部分开发者片面地视作服务治理的 PRC 框架,而非微服务基础设施。即使是那些有意将 Spring Cloud 迁移至 Dubbo 的小伙伴,当面对其中迁移和改造的成本时,难免望而却步。庆幸的是,Dubbo 生态体系已发生巨大变化,Dubbo Spring Cloud 作为 Spring Cloud Alibaba3 的最核心组件,完全地拥抱 Spring Cloud 技术栈,不但无缝地整合 Spring Cloud 注册中心,包括 Nacos4、Eureka5、Zookeeper6 以及 Consul7,而且完全地兼容 Spring Cloud Open Feign8 以及 @LoadBalanced RestTemplate,本文将讨论 Dubbo Spring Cloud 对 Spring Cloud 技术栈所带来的革命性变化。 注:由于 Spring Cloud 技术栈涵盖的特性众多,因此本文讨论的范围仅限于服务治理部分。简介Dubbo Spring Cloud 基于 Dubbo Spring Boot 2.7.19 和 Spring Cloud 2.x 开发,无论开发人员是 Dubbo 用户还是 Spring Cloud 用户,都能轻松地驾驭,并以接近“零”成本的代价使应用向上迁移。Dubbo Spring Cloud 致力于简化 Cloud Native 开发成本,提高研发效能以及提升应用性能等目的。 ...

April 26, 2019 · 7 min · jiezi

启用dubbo-validation后hessian反序列化异常解决方案

现象启用dubbo参数验证,当在provider端开启验证validation="true"。调用接口时,若接口参数为对象类型,对象属性验证失败,客户端会报如下异常。 2019-04-25T09:57:18.544+0800 [ERROR] business-service o.a.c.c.C.[.[.[.[dispatcherServlet] - - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root causecom.alibaba.dubbo.remoting.RemotingException: com.alibaba.com.caucho.hessian.io.HessianFieldException: org.hibernate.validator.internal.engine.ConstraintViolationImpl.constraintDescriptor: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiatedcom.alibaba.com.caucho.hessian.io.HessianFieldException: org.hibernate.validator.internal.engine.ConstraintViolationImpl.constraintDescriptor: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiated at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:167) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:410) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:276) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:203) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:526)... moreCaused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl' could not be instantiated at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:316) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:201) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406) ... 41 moreCaused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:312) ... 48 moreCaused by: java.lang.NullPointerException at org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.<init>(ConstraintDescriptorImpl.java:176) at org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.<init>(ConstraintDescriptorImpl.java:233) ... more版本<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.1</version></dependency>同时测试了2.6.5也有同样的问题。 ...

April 25, 2019 · 2 min · jiezi

提升不止一点点,Dubbo 3.0 预览版详细解读

Dubbo 自 2011 年 10 月 27 日开源后,已被许多非阿里系的公司使用,其中既有当当网、网易考拉等互联网公司,也不乏中国人寿、青岛海尔等大型传统企业。更多用户信息,可以访问Dubbo @GitHub,issue#1012: Wanted: who's using dubbo。 自去年 12 月开始,Dubbo 3.0 便已正式进入开发阶段,并备受社区和广大 Dubbo 用户的关注,本文将为您详细解读 3.0 预览版的新特性和新功能。 下面先解答一下两个有意思的与 Dubbo 相关的疑问。 为什么 Dubbo 一开源就是 2.0 版本?之前是否存在 1.0 版本?笔者曾做过 Dubbo 协议的适配兼容,Dubbo 确实存在过 1.x 版本,而且从协议设计和模型设计上都与 2.0 的开源版本协议是完全不一样的。下图是关于 Dubbo 的发展路径: 阿里内部正在使用 Dubbo 开源版本吗?是的,非常确定,当前开源版本的 Dubbo 在阿里巴巴被广泛使用,而阿里的电商核心部门是用的 HSF2.2 版本,这个版本是兼容了 Dubbo 使用方式和 Remoting 协议。当然,我们现在正在做 HSF2.2 的升级,直接依赖开源版本的 Dubbo 来做内核的统一。所以,Dubbo 是得到大规模线上系统验证的分布式服务框架,这一点毋容置疑。 Dubbo 3.0 预览版的要点Dubbo 3.0 在设计和功能上的新增支持和改进,主要是以下四方面: Dubbo 内核之 Filter 链的异步化这里要指出的是,3.0 中规划的异步去阻塞和 2.7 中提供的异步是两个层面的特性。2.7 中的异步是建立在传统 RPC 中 request – response 会话模型上的,而 3.0 中的异步将会从通讯协议层面由下向上构建,关注的是跨进程、全链路的异步问题。通过底层协议开始支持 streaming 方式,不单单可以支持多种会话模型,还可以在协议层面开始支持反压、限流等特性,使得整个分布式体系更具有弹性。综上所述,2.7 关注的异步更局限在点对点的异步(一个 consumer 调用一个 provider),3.0 关注的异步化,宽度上则关注整个调用链上的异步,高度上则向上又可以包装成 Rx 的编程模型。有趣的是,Spring 5.0 发布了对 Flux 的支持,随后开始解决跨进程的异步问题。 ...

April 22, 2019 · 6 min · jiezi

duubo报错:一个NoClassDefFoundError:factories/SerializerFactory问题解决

duubo编译错误 NoClassDefFoundError,factories/SerializerFactory说一个挺有意思的解释dubbo关系,dubbo分为服务者和消费者,服务者比作司机,消费者比作乘客,zookeeper比作滴滴APP,双方之间的建立关系都在这个APP体现)说正题了,昨天写了一个dubbo 提供的接口报错,百思不得其解。第一眼感觉是序列化的问题,实际已经加上了:1.序列化的问题,缺少Serializable(bean实现Serializable接口即可)Serialized class com.yykj.mall.dto.ProductListItemDTO must implement java.io.Serializable报错信息如下:Caused by: java.lang.NoClassDefFoundError: com/esotericsoftware/kryo/factories/SerializerFactoryat com.alibaba.dubbo.common.serialize.support.kryo.KryoFactory.createKryo(KryoFactory.java:74)at com.alibaba.dubbo.common.serialize.support.kryo.PooledKryoFactory.getKryo(PooledKryoFactory.java:43)at com.alibaba.dubbo.common.serialize.support.kryo.KryoObjectOutput.<init>(KryoObjectOutput.java:31)at com.alibaba.dubbo.common.serialize.support.kryo.KryoSerialization.serialize(KryoSerialization.java:43)at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeRequest(ExchangeCodec.java:240)at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:76)at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:39)at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalEncoder.encode(NettyCodecAdapter.java:81)at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.doEncode(OneToOneEncoder.java:66)at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.handleDownstream(OneToOneEncoder.java:59)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendDownstream(DefaultChannelPipeline.java:784)at org.jboss.netty.channel.SimpleChannelHandler.writeRequested(SimpleChannelHandler.java:292)at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.writeRequested(NettyHandler.java:99)at org.jboss.netty.channel.SimpleChannelHandler.handleDownstream(SimpleChannelHandler.java:254)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:582)at org.jboss.netty.channel.Channels.write(Channels.java:704)at org.jboss.netty.channel.Channels.write(Channels.java:671)at org.jboss.netty.channel.AbstractChannel.write(AbstractChannel.java:348)at com.alibaba.dubbo.remoting.transport.netty.NettyChannel.send(NettyChannel.java:98)… 52 moreCaused by: java.lang.ClassNotFoundException: com.esotericsoftware.kryo.factories.SerializerFactoryat java.net.URLClassLoader.findClass(URLClassLoader.java:382)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)… 73 more出现问题,总是感觉都配对了,很完美,嗯嗯嗯,但是就是一直这个错,感觉是一个心病一样缠绕。。。。所以,想一想列出几项错误:1.配置问题2.接口调用(使用命令)3.使用工具验证 (dubbo-admin)1.首先保证配置正确(好像是废话,但是我不删)2.使用dubbo-admin注册服务中心,等于zookeeper的可视化界面,服务端和消费端接口正常情况3.使用CMD或win10 PowerShell操作,telnet本地的dubbo端口,telnet 127.0.0.1 20880 回车(找之前的问题截屏,太费劲就手写了)dubbo> lsdubbo> com.dubbo.IDubboService// 你的接口dubbo> ls IDubboService // 查看你的方法是否存在select // 三个方法insertupdate dubbo> invoke select(“哈哈哈”)[{“哈哈哈”,“中国”,“xxx@qq.com”}] // 说明接口能调通,没有问题说了这么多,总之前面的问题还没有解决,从另一方面解决,保证其他项是没有问题的(如配置,接口实现等)想了好久,SerializerFactory感觉还是序列化出问题1.dubbo请求接口正常,说明配置是没有问题的,问题出现在我消费者调用服务者的时候或,我在启动消费者过程中发现这个错,表示转换有问题,不兼容,不匹配——-> 检查版本–查看zookper版本,dubbo引包的版本服务者消费者哦,果然是,总结:版本不兼容,确实是,替换一样的版本就好了,原因在于,项目过多,依赖好多jar不一定是这个版本,所以,保证双方之间版本一致性是很重要的,解决很多调用的时候,或者异常错误不明朗,高版本和低版本差异等,检查版本往往是有效果的,夜深了就说这么多了。

April 16, 2019 · 1 min · jiezi

RPC架构之SOA服务化架构学习(一)

传统垂直应用架构背景:传统垂直MVC项目简单分为展示层.业务逻辑层.数据访问层缺点:如1.复杂应用的开发维护成本变高,部署效率逐渐降低 2.团队协作效率差,部分公共功能重复开发,代码重复率居高不下 3.系统可靠性变差。随着业务的发展,访问量逐渐攀升,网络流量、负载均衡、数据库连接等都面临着巨大的压力.走向:当垂直引用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。同时将公共能力API抽取出来,作为独立的公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生具体可参考《分布式服务框架原理与实践》集群管理,负载均衡负载均衡有F5硬件负载均衡和软负载均衡.这里我简单讲下软负载均衡,nginx的反向代理服务很好的实现了集群管理,负载均衡.反向代理就是根据客户端的请求,从其关系的一组或多组后端服务器上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器簇的存在.session失效: nginx默认算法是轮询服务器,这有一个问题session会失效解决办法1:upstream里设置ip_hash即采用哈希算法则可以解决这个问题,某个用户请求了A服务器,接下来该用户只会请求A服务器,除非A挂了,则会请求转入别的服务器,这时候还是会存在session失效的问题.解决办法2:session共享,比如2个tomcat来说,session共享需要发生网络通信也就是会建立连接,如果集群有多个,多个请求同时到每个不同tomcat,比如100个请求到100个不同tomcat,则会把100个的session共享到另外99个tomcat,则此时连接就100了,集群越多性能反而大大降低了.因此nginx自身session共享不建议,轮询算法中可通过别的方法,如redis共享session.初步学习分布式,理解较为浅,后续还会改动~~~

April 10, 2019 · 1 min · jiezi

dubbo设置连接zookeeper权限

前言关于zookeeper知之甚少,少之又少,只是作为dubbo的注册中心连接,某天某检测机构随手一扫然后说你们zookeeper 没有设置任何安全验证,当时就懵了,还有这种操作。zookeeper设置ACL 权限查阅dubbo的官方文档dubbo-registry发现连接注册中心的时候是可以选择是否需要用户名密码,接下来就是要如何设置zookeeper的用户名跟密码进入zookeeper的bin文件夹运行客户端./zkCli.sh-help 查看指令[zk: localhost:2181(CONNECTED) 0] -helpZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port如果在dubbo中没有指定分组的话,dubbo会默认生成一个分组dubbo,也就是在zookeeper下面会有个子节点dubbo也可以自己手动创建create /dubboZookeeper的ACL通过scheme🆔permissions来构成权限scheme这边主要用到2种方式,另外还有设置ip和host,这几个没用到的这边就先不细说1.auth方式(密码明文)添加用户名和密码addauth digest onepay:onepay授予/dubbo auth权限setAcl /dubbo auth:onepay:onepay:rwadc配置dubbo连接zookeeper配置文件<dubbo:registry protocol =“zookeeper” address=“127.0.0.1:2181” username=“onepay” password=“onepay” client=“curator” />2.digest授权方式(方式跟auth差不多)授予/dubbo digest权限setAcl /dubbo digest:onepay:T+17ezPAW0kDvN6elPD5Tdzdm00=:cdrwaaddauth digest onepay:onepay配置zookeeper配置文件<dubbo:registry protocol =“zookeeper” address=“127.0.0.1:2181” username=“onepay” password=“onepay” client=“curator” />digest 密码生成方式:把密码进行sha1编码然后对结果进行base64编码BASE64(SHA1(password))查看zookeeper源码发现,其实包里面已经有现成的方法,直接调用这个类生成就行,idPassword字符串格式: username:passwordorg.apache.zookeeper.server.auth.DigestAuthenticationProvider static public String generateDigest(String idPassword) throws NoSuchAlgorithmException { String parts[] = idPassword.split(":", 2); byte digest[] = MessageDigest.getInstance(“SHA1”).digest( idPassword.getBytes()); return parts[0] + “:” + base64Encode(digest); }还有一个点就是要设置client=“curator"通过ZookeeperRegistry发现zookeeper的连接是通过zookeeperTransporter进行创建,zookeeperTransporter接口分别由CuratorZookeeperTransporterZkclientZookeeperTransporter实现,这2个分别创建CuratorZookeeperClient和ZkclientZookeeperClientpublic class ZkclientZookeeperTransporter implements ZookeeperTransporter { public ZookeeperClient connect(URL url) { return new ZkclientZookeeperClient(url); }}public class CuratorZookeeperTransporter implements ZookeeperTransporter { public ZookeeperClient connect(URL url) { return new CuratorZookeeperClient(url); }}查看源码发现ZkclientZookeeperClient是没有进行设置zookeeper的auth的账号和密码,CuratorZookeeperClient有去获取配置的相关用户信息。 public ZkclientZookeeperClient(URL url) { super(url); client = new ZkClient(url.getBackupAddress()); client.subscribeStateChanges(new IZkStateListener() { public void handleStateChanged(KeeperState state) throws Exception { ZkclientZookeeperClient.this.state = state; if (state == KeeperState.Disconnected) { stateChanged(StateListener.DISCONNECTED); } else if (state == KeeperState.SyncConnected) { stateChanged(StateListener.CONNECTED); } } public void handleNewSession() throws Exception { stateChanged(StateListener.RECONNECTED); } }); } public CuratorZookeeperClient(URL url) { super(url); try { Builder builder = CuratorFrameworkFactory.builder() .connectString(url.getBackupAddress()) .retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000)) .connectionTimeoutMs(5000); String authority = url.getAuthority(); if (authority != null && authority.length() > 0) { builder = builder.authorization(“digest”, authority.getBytes()); } client = builder.build(); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { public void stateChanged(CuratorFramework client, ConnectionState state) { if (state == ConnectionState.LOST) { CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); } else if (state == ConnectionState.CONNECTED) { CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); } else if (state == ConnectionState.RECONNECTED) { CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); } } }); client.start(); } catch (IOException e) { throw new IllegalStateException(e.getMessage(), e); } }cdrwa表示zookeeper的五种权限CREATE: 创建子节点READ: 获取节点数据或者当前节点的子节点列表WRITE: 节点设置数据DELETE: 删除子节点ADMIN: 节点设置权限如果用户名密码错误,或者没设置,会报KeeperErrorCode = NoAuth错误注:停止zookeeper,清除zookeeper文件夹下面的logs,或者用delete 删除节点 就可以清除权限以上参考文档Apache Zookeeper Setting ACL ...

April 3, 2019 · 2 min · jiezi

dubbo获取本机IP

发布Java应用到阿里云,启动失败,部分堆栈如下:at java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at java.net.InetAddress.checkLookupTable(InetAddress.java:1393)…at com.alibaba.dubbo.common.utils.NetUtils.getLocalAddress0(NetUtils.java:188)at com.alibaba.dubbo.common.utils.NetUtils.getLocalAddress(NetUtils.java:180)at com.alibaba.dubbo.common.utils.NetUtils.getLocalHost(NetUtils.java:146)at com.alibaba.dubbo.common.logger.support.FailsafeLogger.appendContextMessage(FailsafeLogger.java:40)at com.alibaba.dubbo.common.logger.support.FailsafeLogger.warn(FailsafeLogger.java:110)at com.alibaba.dubbo.common.utils.NetUtils.getLocalAddress0(NetUtils.java:220)at com.alibaba.dubbo.common.utils.NetUtils.getLocalAddress(NetUtils.java:180)at com.alibaba.dubbo.common.utils.NetUtils.getLocalHost(NetUtils.java:146)at com.alibaba.dubbo.common.logger.support.FailsafeLogger.appendContextMessage(FailsafeLogger.java:40)看了代码,发现是dubbo的小bug:getLocalHost失败会调用日志打印,日志打印的时候会自动带上本机域名(调用getLocalHost),造成循环调用。2种解决方法:在 /etc/hosts 指定域名解析(自定义了hostname,但是没设定对应IP)升级到dubbo 2.6.3或后续版本指定域名解析的方式还有其它用途:daily环境从内网迁移到阿里云,指定解析为外网IP,方便在内网调用。

April 1, 2019 · 1 min · jiezi

prometheus 集成dubbo

dubbo 自身的监控使用了dubbo 的拦截器,这里我们也使用dubbo 的拦截器来添加prometheus 监控首先需要dubbo 项目提供http的接口,为dubbo 项目添加 web依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>加入 micrometer prometheus <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>实现dubbo 的filter 接口,添加 @Activate(group = Constants.PROVIDER)注解,声明拦截所有服务提供者@Activate(group = Constants.PROVIDER)public class PrometheusFilter implements Filter { private Logger logger = LoggerFactory.getLogger(PrometheusFilter.class); @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { logger.info("—————-prometheus filter—————"); RequestTimeCollector requestTimeCollector = (RequestTimeCollector) ServiceBean.getSpringContext(). getBean(“dubboRequestTimeCollector”); RpcContext context = RpcContext.getContext(); boolean isProvider = context.isProviderSide(); String serviceName = invoker.getInterface().getName(); String methodName = RpcUtils.getMethodName(invocation); long start = System.currentTimeMillis(); try { // proceed invocation chain Result result = invoker.invoke(invocation); long duration = System.currentTimeMillis() - start; String status = “success”; if(result.getException()!=null){ status = result.getException().getClass().getSimpleName(); } requestTimeCollector.setValue(duration,serviceName,methodName,status); return result; } catch (RpcException e) { long duration = System.currentTimeMillis() - start; String result = “error”; if (e.isTimeout()) { result = “timeoutError”; } if (e.isBiz()) { result = “bisError”; } if (e.isNetwork()) { result = “networkError”; } if (e.isSerialization()) { result = “serializationError”; } requestTimeCollector.setValue(duration,serviceName,methodName,result); throw e; } }}配置拦截器扩展在 resourceMETA-INFdubbo 文件夹下创建com.alibaba.dubbo.rpc.Filter 文本文件添加 prometheus=com.rcplatform.livechat.dubbo.filter.PrometheusFilter文本启动项目访问/actuator/prometheus,即可看到监控项 ...

March 31, 2019 · 1 min · jiezi

dubbo源码解析(四十三)2.7新特性

DUBBO——2.7大揭秘目标:了解2.7的新特性,以及版本升级的引导。前言我们知道Dubbo在2011年开源,停止更新了一段时间。在2017 年 9 月 7 日,Dubbo 悄悄的在 GitHub 发布了 2.5.4 版本。随后,版本发布的非常迅速,Dubbo项目被重启了,经过大半年的更新,在2018年2月15日,Dubbo 获得了 14 张赞成票,在无弃权和反对票的情况下,正式通过投票,顺利成为 Apache 基金会孵化项目。现在的Dubbo社区非常活跃,版本进度也非常的快。从上图就可以看出dubbo现在的活跃度。现在dubbo项目有以下几个分支:2.5.x:该分支在近期投票决定不再维护。2.6.x:该分支现在还在维护中,包名前缀是com.alibaba,也是贡献给 Apache 之前的版本。2.7.1-release:一个临时分支。3.x-dev:将以 Streaming 为内核,重点的改变在服务治理和编程模型上。具体我也还没有深入研究,我也会跟踪该分支的变动,敬请期待吧。master:目前版本是2.7.x,包名前缀是:org.apache,也是 Dubbo 贡献给 Apache 的开发版本,接下来的分析也会在2.7.1上进行分析。关注dubbo社区的朋友应该也知道在2019.3.23在南京举办了Meetup,其中有一个专题就是讲2.7新特性介绍。我就在分享者的基础上讲解一下自己的理解。正文(一)JDK版本在所需的最小JDK版本从以前的1.6变成了1.8。(二)包重命名com.alibaba.dubbo - > org.apache.dubbo(三)异步支持优化我们知道dubbo协议本身支持三种发送请求方式:单向发送:执行方法不需要返回结果同步发送:执行方法后,等待结果返回,否则一直阻塞.异步发送:也就是当我发送调用后,我不阻塞等待结果,直接返回,将返回的future保存到上下文,方便后期使用。在异步发送中有两种方式分别是future:当请求有响应后,通过future.get()来获得响应结果,但是future.get()会导致线程阻塞,future从RpcContext获取。callback:设置一个回调线程,当接收到响应时,自动执行,不会对当前线程造成阻塞,自定义ResponseFuture支持callback。2.6.x版本的异步方式提供了一些异步能力,包括Consumer端异步调用、参数回调、事件通知等。但当前的异步方式存在以下问题:Future获取方式不够直接,只能在RpcContext中进行获取;Future只支持阻塞式的get()接口获取结果。Future接口无法实现自动回调,而自定义ResponseFuture虽支持callback回调但支持的异步场景有限,如不支持Future间的相互协调或组合等;不支持Provider端异步具体的可以参考该文章dubbo源码解析(二十四)远程调用——dubbo协议中的源码分析来理解其中存在的问题。那么在2.7.x版本,由于JDK版本升级到了1.8,引入了JDK1.8 中的CompletableFuture接口,CompletableFuture支持 future 和 callback 两种调用方式。关于CompletableFuture怎么被运用到dubbo中我会在后续的文章介绍。引入该接口后,做了以下优化:支持Provider端异步支持直接定义返回CompletableFuture的服务接口。通过这种类型的接口,我们可以更自然的实现Consumer、Provider端的异步编程。public interface AsyncService { CompletableFuture<String> sayHello(String name);}如果你不想将接口的返回值定义为Future类型,或者存在定义好的同步类型接口,则可以额外定义一个异步接口并提供Future类型的方法。public interface GreetingsService { String sayHi(String name);}@AsyncFor(GreetingsService.class)public interface GrettingServiceAsync extends GreetingsService { CompletableFuture<String> sayHiAsync(String name);}如果你的原始接口定义不是Future类型的返回值,Provider端异步也提供了类似Servlet3.0里的Async Servlet的编程接口: RpcContext.startAsync()public interface AsyncService { String sayHello(String name);}public class AsyncServiceImpl implements AsyncService { public String sayHello(String name) { final AsyncContext asyncContext = RpcContext.startAsync(); new Thread(() -> { asyncContext.write(“Hello " + name + “, response from provider.”); }).start(); return null; }}异步过滤器链回调。具体的实现原理我在后续文章中结合源码来讲解,注意:这些改动都仅仅支持dubbo协议。(四)元数据改造我们知道2.7以前的版本只有注册中心,注册中心的URL有数十个key/value的键值对,包含了一个服务所有的元数据。在越来越多的功能被增加,元数据也变得异常庞大,就出现了下面的问题:注册中心存储的URL过长:这会导致存储的压力骤增,数据庞大导致在修改元数据后的通知效率也下降,并且增加了消费者对于元数据解析的压力,尤其是在大规模场景下的内存增长显著注册中心承担了过多的服务治理配置的功能:初始配置的同步、存储各种运行期配置规则加剧了注册中心的压力,配置规则的灵活性也有所限制,阻碍了市场上的一些优秀微服务配置中心的集成和扩展。属性的功能定位不清晰:methods,pid,owner虽然是为了查询服务而注册的属性,但是这些简陋的信息很难满足查询服务治理需求,所以需要更加丰富的注册数据。例如methods,虽然方法列表的内容已经很长,但是在ops开发服务测试/mock功能时,发现需要的方法签名等数据还是无法获取。针对以上问题,在2.7中,将URL中的元数据划分了三个部分:元数据信息:接口的完整定义,包含接口名,接口所含的方法,以及方法所含的出入参信息。对于服务测试和服务mock有很重要作用。执行链路上数据:需要将参数从provider端传递给consume端,让consume端感知的到,比如token、timeout等服务自己持有的配置&Ops需求:只有provider端自己需要或者consume端自己需要的数据,比如executes、document等改造后,分别形成三大中心:注册中心:理想情况下,注册中心将只用于关键服务信息(核心链路)的同步,进一步减轻注册中心的存储压力,提高地址同步效率,同时缓解当前由于URL冗余在大规模推送时造成的Consumer端内存计算压力。配置中心:解决当前配置和地址信息耦合的问题,通过抽象动态配置层,让开发者可以对接微服务场景下更常用的、更专业的配置中心,如Nacos, Apollo, Consul, Etcd等;提供更灵活的、更丰富的配置规则,包括服务、应用不同粒度的配置,更丰富的路由规则,集中式管理的动态参数规则等服务查询治理中心:对于纯粹的服务查询相关的数据,包括Consumer的服务订阅数据,往往都是注册后不可变的并且不需要节点间的同步,如当前URL可以看到的methods、owner等key以及所有的Consumer端URL,目前支持 redis(推荐),zookeeper,将作为Dubbo-Admin支持的服务测试,模拟和其他服务治理功能的基础。(五)服务治理规则增强路由规则的增强Dubbo 提供了具有一定扩展性的路由规则,其中具有代表性的是条件路由和脚本路由。2.6.x及以下版本存在的问题:路由规则存储在注册中心只支持服务粒度的路由,应用级别无法定义路由规则支持路由缓存,但基本不具有扩展性一个服务或应用允许定义多条路由规则,服务治理无法管控实现上,每条规则生成一个Router实例并动态加载在2.7.x版本中,对路由规则做了增强:丰富的路由规则。条件路由:支持应用程序级别和服务级别条件。标记路由:新引入以更好地支持流量隔离,例如灰色部署配置中心对服务治理的加成将治理规则与注册表分离,也就是出现了配置中心,使配置中心更容易扩展。有Apollo和Zookeeper,2.7.1还支持了consul和etcd。应用程序级动态配置支持。使用YAML作为配置语言,更易于阅读和使用(六)新增配置中心配置中心(v2.7.0)在Dubbo中承担两个职责:外部化配置:启动配置的集中式存储 (简单理解为dubbo.properties的外部化存储)外部化配置目的之一是实现配置的集中式管理,这部分业界已经有很多成熟的专业配置系统如Apollo, Nacos等,Dubbo所做的主要是保证能配合这些系统正常工作。外部化配置和其他本地配置在内容和格式上并无区别,可以简单理解为dubbo.properties的外部化存储,配置中心更适合将一些公共配置如注册中心、元数据中心配置等抽取以便做集中管理服务治理:服务治理规则的存储与通知。配置的操作可以查看官方文档,由于现在dubbo支持多种配置方式,所以这里需要强调的是配置覆盖的优先级,从上至下优先级依此降低:(七)序列化扩展新增了Protobuf序列化支持。(八)其他其他的bug修复以及一些小细节优化请查看github上的Issues或者PR。后记升级2.7.0的引导请查看以下链接:http://dubbo.apache.org/zh-cn…该文章讲解了dubbo2.7的新特性,现在2.7.1已经发布,有兴趣的可以去看看2.7.1新增了什么。下一篇我就先从源码的角度来讲讲这个异步化的改造。 ...

March 26, 2019 · 1 min · jiezi

Dubbo实现原理分析-SPI&自适应扩展实现

Duboo基本概念Dubbo整体架构:evernotecid://D5D92FBE-C671-4B13-BAC8-4D01D3D20F5B/appyinxiangcom/8739769/ENNote/p68?hash=f07431ff8daafff6906882197d395a2aDubbo SPISPI 全称为 Service Provider Interface,一种服务提供发现机制。可以实现通过配置手段加载相应的实现类。JDK里提供了一种SPI实现,Dubbo并没有使用jdk的spi,而是自己实现了一个Dubbo SPI 服务发现机制。首先看一下JDK SPI实现方式定义一个接口类Travelpublic interface Travel { public void travel();}Travel有两个实现类public class CarTravel implements Travel { @Override public void travel() { System.out.println(“travel by car”); }}public class PlaneTravel implements Travel{ @Override public void travel() { System.out.println(“travel by plane”); }}接下来需要在 META-INF/services 文件夹下建立接口名称对应的文件,META-INF/services/com.demo.spi.jdk.Travel文件内容为接口实现类的名称com.demo.spi.jdk.CarTravelcom.demo.spi.jdk.PlaneTravel测试使用public class JdkSpiTest { public static void main(String[] args) { ServiceLoader<Travel> sServiceLoader = ServiceLoader.load(Travel.class); sServiceLoader.forEach(Travel::travel); }}测试结果travel by cartravel by planeJAVA SPI vs Dubbo SPI首先加载路径不同,java spi路径是 META-INF/services ,dubbo 是META-INF/services/,META-INF/services/internal,META-INF/dubbo/加载方式不同,java spi是全量加载,dubbo是按需加载接口实现类dubbo spi除了加载实现类外还增加了 IOC 和 AOP 特性下面我们看一下dubbo spi的实现首先接口需要有@SPI注解@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface SPI { /** * default extension name / String value() default “”;}@SPIpublic interface Travel { public void travel();}配置文件采用key=value的形式配置car=com.demo.spi.jdk.CarTravelplane=com.demo.spi.jdk.PlaneTravel测试duubo spi加载public class DubboSPITest { public static void main(String[] args) { ExtensionLoader<Travel> extensionLoader = ExtensionLoader.getExtensionLoader(Travel.class); Travel travel = extensionLoader.getExtension(“car”); travel.travel(); }}输出结果travel by carDubbo SPI实现1.扩展类的加载dubbo spi 是通过 ExtensionLoader 类来实现的,通过ExtensionLoader获取接口对应类型的扩展加载器ExtensionLoader.getExtensionLoader,第一次获取会新创建然后会缓存到 ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS 中,之后就在缓存中取就可以了 //获取扩展加载器ExtensionLoader,type必须是@SPI注解接口 //ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS 缓存 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException(“Extension type == null”); } if (!type.isInterface()) { throw new IllegalArgumentException(“Extension type (” + type + “) is not an interface!”); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException(“Extension type (” + type + “) is not an extension, because it is NOT annotated with @” + SPI.class.getSimpleName() + “!”); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }构造方法private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }获取到接口对应的ExtensionLoader后,通过getExtension(String name) 方法获取name对应的扩展对象public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException(“Extension name == null”); } if (“true”.equals(name)) { return getDefaultExtension(); } //根据名称 获取holder对象,如果不存在则创建一个holder //holder里缓存了name对应的实现类 Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); //双重检查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { //如果为空,创建 instance = createExtension(name); holder.set(instance); } } } return (T) instance; }扩展对象的创建private T createExtension(String name) { //1.getExtensionClasses() 获取所有扩展类 //根据name获取扩展类的Class Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { //2.通过反射newInstance()创建扩展类实例,放到EXTENSION_INSTANCES 缓存中 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //3.依赖注入 IOC injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; //4.循环创建 Wrapper 实例 AOP if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException(“Extension instance (name: " + name + “, class: " + type + “) couldn’t be instantiated: " + t.getMessage(), t); } }获取所有Extension getExtensionClassesprivate Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { //通过loadExtensionClasses 获取所有扩展类,并放到 //cachedClasses 缓存中 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }loadExtensionClassesprivate Map<String, Class<?>> loadExtensionClasses() { //缓存默认的扩展名 cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); //去dubbo spi所在的路径下META-INF/services/,META-INF/services/internal,META-INF/dubbo/ // 加载扩展类,放到extensionClasses 中 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); return extensionClasses; }loadDirectory->loadResource->loadClass 最终的类加载实现private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException(“Error occurred when loading extension class (interface: " + type + “, class line: " + clazz.getName() + “), class " + clazz.getName() + " is not subtype of interface.”); } // 如果类有@Adaptive 注解,缓存到cachedAdaptiveClass if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); } else if (isWrapperClass(clazz)) { //如果类的构造方法有加载类的类型,缓存到Set<Class<?>> cachedWrapperClasses; 实现aop的类 cacheWrapperClass(clazz); } else { //如果是普通类 clazz.getConstructor(); //获取扩展类name,为空就取默认name if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException(“No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0]); for (String n : names) { //缓存到cachedNames cacheName(clazz, n); //存储到extensionClasses saveInExtensionClass(extensionClasses, clazz, name); } } } }2. dubbo ioc 注入实现dubbo ioc aop 利用setter注入方式实现private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { //判断是否是setter方法 if (isSetter(method)) { /* * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { String property = getSetterProperty(method); //从objectFactory 获取依赖对象 Object object = objectFactory.getExtension(pt, property); if (object != null) { //setter注入 method.invoke(instance, object); } } catch (Exception e) { logger.error(“Failed to inject via method " + method.getName() + " of interface " + type.getName() + “: " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }3. dubbo aop 实现//遍历所有Wrapper类 创建 Wrapper 实例 //setter注入injectExtension if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } }Dubbo 扩展点自适应机制1. 什么自适应扩展自适应扩展的标记注解@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface Adaptive { String[] value() default {};}Adaptive 标记在类上,表示扩展点的加载是人工编码完成,目前只有两个类被@Adaptive, AdaptiveCompiler、AdaptiveExtensionFactoryAdaptive 标记在方法上,表示扩展点的加载是代理完成AdaptiveCompiler 示例@Adaptivepublic class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } @Override public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); }}2. 加载自适应扩展dubbo通过ExtensionLoader.getAdaptiveExtension() 获取自适应扩展实例public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { //创建自适应扩展,并缓存到cachedAdaptiveInstance instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException(“Failed to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException(“Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }createAdaptiveExtension()方法代码private T createAdaptiveExtension() { try { //反射创建AdaptiveExtensionClass,并注入依赖 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException(“Can’t create adaptive extension " + type + “, cause: " + e.getMessage(), e); } }获取Class 类 getAdaptiveExtensionClass()private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //创建自适应扩展类 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }createAdaptiveExtensionClass() 生成自适应扩展类private Class<?> createAdaptiveExtensionClass() { //1.生成自适应扩展类代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); //2.获取编译器 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //3.编译生成class return compiler.compile(code, classLoader); }Protocol 自适应扩展类生成代码 Protocol$Adaptivepackage org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException(“The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!”); } public int getDefaultPort() { throw new UnsupportedOperationException(“The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!”); } public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException(“url == null”); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol()); if (extName == null) throw new IllegalStateException(“Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (” + url.toString() + “) use keys([protocol])”); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument == null”); if (arg0.getUrl() == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument getUrl() == null”); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol()); if (extName == null) throw new IllegalStateException(“Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (” + url.toString() + “) use keys([protocol])”); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); }} ...

March 15, 2019 · 6 min · jiezi

探秘 Dubbo 的度量统计基础设施 - Dubbo Metrics

对服务进行实时监控,了解服务当前的运行指标和健康状态,是微服务体系中不可或缺的环节。Metrics 作为微服务的重要组件,为服务的监控提供了全面的数据基础。近日,Dubbo Metrics 发布了2.0.1版本,本文将为您探秘 Dubbo Metrics 的起源,及 7 大改进。Dubbo Metrics 的起源Dubbo Metrics(原Alibaba Metrics)是阿里巴巴集团内部广泛使用的度量埋点基础类库,有 Java 和 Node.js 两个版本,目前开源的是 Java 版本。内部版本诞生于2016年,经历过近三年的发展和双十一的考验,已经成为阿里巴巴集团内部微服务监控度量的事实标准,覆盖了从系统、JVM、中间件到应用各层的度量,并且在命名规则、数据格式、埋点方式和计算规则等方面,形成了一套统一的规范。Dubbo Metrics 的代码是基于 Dropwizard Metrics 衍生而来,版本号是3.1.0,当时决定 fork 到内部进行定制化开发的主要原因有两个。一是社区的发展非常缓慢,3.1.0之后的第3年才更新了下一个版本,我们担心社区无法及时响应业务需求;另一个更重要的原因是当时的3.1.0还不支持多维度的 Tag,只能基于 a.b.c 这样传统的指标命名方法,这就意味着 Dropwizard Metrics 只能在单维度进行度量。然后,在实际的业务过程中,很多维度并不能事先确定,而且在大规模分布式系统下,数据统计好以后,需要按照各种业务维度进行聚合,例如按机房、分组进行聚合,当时的 Dropwizard 也无法满足,种种原因使得我们做了一个决定,内部fork一个分支进行发展。Dubbo Metrics 做了哪些改进相对于 Dropwizard Metrics ,Dubbo Metrics 做的改进主要有以下几个方面:一、引入基于 Tag 的命名规范如前文所描述,多维度 Tag 在动态埋点,数据聚合等方面相对于传统的 metric 命名方法具有天然的优势,这里举一个例子,要统计一个 Dubbo 服务 DemoService 调用次数和 RT,假设这个服务叫做 DemoService,那么按照传统的命名方式,通常会命名为dubbo.provider.DemoService.qps和dubbo.provider.DemoService.rt。如果只有1个服务的话,这种方法并无太大的问题,但是如果一个微服务应用同时提供了多个 Dubbo 的 Service,那么要聚合统计所有Service 的 QPS 和 RT 就比较困难了。由于 metric 数据具有天然的时间序列属性,因此数据非常适合存储到时间序列数据库当中,要统计所有的 Dubbo 服务的 QPS,那么就需要查找所有名称为dubbo.provider..qps的指标,然后对他们进行求和。由于涉及到模糊搜索,这对于绝大部分数据库的实现都是比较费时的。如果要统计更加详细的 Dubbo 方法级别的 QPS 和 RT,那么实现上就会更加复杂了。Metric Key:用英文点号分隔的字符串,来表征这个指标的含义Metric Tag:定义了这个指标的不同切分维度,可以是单个,也可以是多个;tag key:用于描述维度的名称;tag value:用于描述维度的值;同时,考虑到一个公司所有微服务产生的所有指标,都会统一汇总到同一个平台进行处理,因此Metric Key 的命名方式为应当遵循同一套规范,避免发生命名冲突,其格式为appname.category[.subcategory].suffixappname: 应用名;category: 这个指标在该应用下的分类,多个单词用’‘连接,字母采用小写;subcategory: 这个指标在该应用下的某个分类下的子分类,多个单词用’‘连接,字母采用小写;suffix: 这个关键的后缀描述了这个指标所度量的具体类型,可以是计数,速率,或者是分布;在上述例子中,同样的指标可以命名为dubbo.provider.service.qps{service=“DemoService”},其中前面部分的名称是固定的,不会变化,括号里面的Tag,可以动态变化,甚至增加更多的维度,例如增加 method 维度dubbo.provider.service.qps{service=“DemoService”,method=“sayHello”},也可以是机器的 IP、机房信息等。这样的数据存储是时间序列数据库亲和的,基于这些数据可以轻松实现任意维度的聚合,筛选等操作。P.s. 2017年12月底,Dropwizard Metrics4.0 开始支持 Tag,Dubbo Metrics 中 ag 的实现参考了Dropwizard。spring-boot 2.0中提供的 MicroMeter 和 Prometheus 也均已引入了 Tag 的概念。二、添加精准统计功能Dubbo Metrics 的精准统计是和 Dropwizard,或者其他开源项目埋点统计库实现不太一样的地方。下面分别通过时间窗口的选择和吞吐率统计方式这两个纬度进行阐述。在统计吞吐率(如 QPS)的时候,Dropwizard的实现方式是滑动窗口+指数加权移动平均,也就是所谓的EWMA,在时间窗口上只提供1分钟、5分钟、15分钟的选择。固定窗口 vs 滑动窗口在数据统计的时候,我们需要事先定义好统计的时间窗口,通常有两种确立时间窗口的方法,分别是固定窗口和滑动窗口。固定时间窗口指的是以绝对时间为参考坐标系,在一个绝对时间窗口内进行统计,无论何时访问统计数据,时间窗口不变;而滑动窗口则是以当前时间为参考系,从当前时间往前推一个指定的窗口大小进行统计,窗口随着时间,数据的变化而发生变化。固定窗口的优点在于:一是窗口不需要移动,实现相对简单;二是由于不同的机器都是基于相同的时间窗口,集群聚合起来比较容易,只需按照相同的时间窗口聚合即可。其缺点是:如果窗口较大,实时性会受到影响,无法立即得到当前窗口的统计信息。例如,如果窗口为1分钟,则必须等到当前1分钟结束,才能得到这1分钟内的统计信息。滑动窗口的优点在于实时性更好,在任意时刻,能都看到当前时刻往前推演一个时间窗口内统计好的信息。相对而言,由于不同机器的采集时刻不同,要把不同机器上的数据聚合到一起,则需要通过所谓的 Down-Sampling 来实现。即按照固定时间窗口把窗口内收集到的数据应用到某一个聚合函数上。举个例子来说,假设集群有5台机器,按照15秒的频率按照平均值进行 Down-Sampling,若在00:00~00:15的时间窗口内,在00:01,00:03,00:06,00:09,00:11各收集到一个指标数据,则把这5个点的加权平均认为是00:00这个点的经过 Down- Sampling 之后的平均值。但在我们的实践过程中,滑动窗口仍会遇到了以下问题:很多业务场景都要求精确的时间窗口的数据,比如在双11的时候,想知道双11当天0点第1秒创建了多少订单,这种时候 Dropwizard 的滑动窗口很明显就不适用了。Dropwizard 提供的窗口仅仅是分钟级,双11的场景下需要精确到秒级。集群数据聚合的问题,每台机器上的滑动时间窗口可能都不一样,数据采集的时间也有间隔,导致聚合的结果并不准确。为了解决这些问题,Dubbo Metrics 提供了 BucketCounter 度量器,可以精确统计整分整秒的数据,时间窗口可以精确到1秒。只要每台机器上的时间是同步的,那么就能保证集群聚合后的结果是准确的。同时也支持基于滑动窗口的统计。瞬时速率(Rate) vs 指数移动加权平均(EWMA)经过多年的实践,我们逐渐发现,用户在观察监控的时候,首先关注的其实是集群数据,然后才是单机数据。然而单机上的吞吐率其实并没有太大意义。怎么理解呢?比如有一个微服务,共有2台机器,某个方法在某一段时间内产生了5次调用,所花的时间分别是机器1上的[5,17],机器2上的[6,8,8](假设单位为毫秒)。如果要统计集群范围内的平均 RT,一种方法可以先统计单机上的平均 RT,然后统计整体的平均 RT,按照这种方法,机器1上平均 RT 为11ms,机器2的平均 RT 为7.33ms,两者再次平均后,得到集群平均 RT 为9.17ms,但实际的结果是这样吗?如果我们把机器1和机器2上的数据整体拉到一起整体计算的话,会发现实际的平均 RT 为(5+17+6+8+8)/5=8.8ms,两者相差很明显。而且考虑到计算浮点数的精度丢失,以及集群规模扩大,这一误差会愈加明显。因此,我们得出一个结论:单机上的吞吐率对于集群吞吐率的计算没有意义,仅在在单机维度上看才是有意义的。而 Dropwizard 提供的指数加权移动平均其实也是一种平均,同时考虑了时间的因素,认为距离当前时间越近,则数据的权重越高,在时间拉的足够长的情况下,例如15分钟,这种方式是有意义的。而通过观察发现,其实在较短的时间窗口内,例如1s、5s,考虑时间维度的权重并没有太大的意义。因此在内部改造的过程中,Dubbo Metrics 做了如下改进:提供瞬时速率计算,反应单机维度的情况,同时去掉了加权平均,采用简单平均的方式计算;为了集群聚合需要,提供了时间窗口内的总次数和总 RT 的统计,方便精确计算集群维度的吞吐率;三、极致性能优化在大促场景下,如何提升统计性能,对于 Dubbo Metrics 来说是一个重要话题。在阿里的业务场景下,某个统计接口的 QPS 可能达到数万,例如访问 Cache 的场景,因此这种情况下 metrics 的统计逻辑很可能成为热点,我们做了一些针对性的优化:高并发场景下,数据累加表现最好的就是java.util.concurrent.atomic.LongAdder,因此几乎所有的操作最好都会归结到对这个类的操作上。避免调用 LongAdder#reset当数据过期之后,需要对数据进行清理,之前的实现里面为了重用对象,使用了LongAdder#reset进行清空,但实测发现LongAdder#reset其实是一个相当耗费cpu的操作,因此选择了用内存换 CPU,当需要清理的时候用一个新的 LongAdder 对象来代替。去除冗余累加操作某些度量器的实现里面,有些统计维度比较多,需要同时更新多个 LongAdder,例如 Dropwizard Metrics的 meter 实现里面计算1分/5分/15分移动平均,每次需要更新3个 LongAdder,但实际上这3次更新操作是重复的,只需要更新一次就行了。RT为0时避免调用Add方法大多数场景下对 RT 的统计都以毫秒为单位,有些时候当 RT 计算出来小于1ms的时候,传给metrics的 RT 为0。当我们发现 JDK 原生的 LongAdder 并没有对add(0)这个操作做优化,即使输入为0,还是把逻辑都走一遍,本质上调用的是sun.misc.Unsafe.UNSAFE.compareAndSwapLong。如果这个时候,metrics 判断 RT 为0的时候不对计数器进行 Add 操作,那么可以节省一次 Add 操作。这对于并发度较高的中间件如分布式缓存很有帮助,在我们内部某个应用实测中发现,在30%的情况下,访问分布式缓存的 RT 都是0ms。通过这个优化可以节约大量无意义的更新操作。QPS 和 RT 合并统计只需要对一个Long的更新,即可实现同时对调用次数和时间进行统计,已经逼近理论上的极限。经过观测发现,通常对于中间件的某些指标,成功率都非常高,正常情况下都在100%。为了统计成功率,需要统计成功次数和总次数,这种情况下几乎一样多,这样造成了一定的浪费,白白多做了一次加法。而如果反过来,只统计失败的次数,只有当失败的情况才会更新计数器,这样能大大降低加法操作。事实上,如果我们把每一种情况进行正交拆分,例如成功和失败,这样的话,总数可以通过各种情况的求和来实现。这样能进一步确保一次调用只更新一次计数。但别忘了,除了调用次数,还有方法执行 RT 要统计。还能再降低吗?答疑是可以的!假设 RT 以毫秒为单位进行统计,我们知道1个 Long 有64个bits(实际上Java里面的Long是有符号的,所以理论上只有63个 bits 可用),而 metrics 的一个统计周期最多只统计60s的数据,这64个 bits 无论怎样用都是用不掉的。那能不能把这63个 bits 拆开来,同时统计 count 和 RT 呢?实际上是可以的。我们把一个 Long 的63个 bits 的高25位用来表示一个统计周期内的总 count,低38位用于表示总 RT。——————————————| 1 bit | 25 bit | 38 bit || signed bit | total count | total rt |——————————————当一次调用过来来的时候,假设传过来的 RT 是n,那么每次累加的数不是1,也不是n,而是1 * 2^38 + n这么设计主要有一下几个考虑:count是每调用一次加一,RT 是每调用一次加N的操作,如果 count 在高位的话,每次加一,实际是一个固定的常数,而如果rt放在高位的话,每次都加的内容不一样,所以每次都要计算一次;25 bits 最多可以表示 2^25 = 33554432 个数,所以1分钟以内对于方法调用的统计这种场景来说肯定是不会溢出的;RT 可能会比较大,所以低位给了38bits, 2^38=274877906944 基本也是不可能超的。如果真的overflow了怎么办? 由于前面分析过,几乎不可能overflow,因此这个问题暂时没有解决,留待后面在解决。无锁 BucketCounter在之前的代码中,BucketCounter 需要确保在多线程并发访问下保证只有一个线程对 Bucket 进行更新,因此使用了一个对象锁,在最新版本中,对 BucketCounter 进行了重新实现,去掉了原来实现中的锁,仅通过 AtomicReference 和 CAS 进行操作,本地测试发现性能又提升了15%左右。四、全面的指标统计Dubbo Metrics 全面支持了从操作系统,JVM,中间件,再到应用层面的各级指标,并且对统一了各种命名指标,可以做到开箱即用,并支持通过配置随时开启和关闭某类指标的收集。目前支持的指标,主要包括:操作系统支持Linux/Windows/Mac,包含CPU/Load/Disk/Net Traffic/TCP。JVM支持classload, GC次数和时间, 文件句柄,young/old区占用,线程状态, 堆外内存,编译时间,部分指标支持自动差值计算。中间件Tomcat: 请求数,失败次数,处理时间,发送接收字节数,线程池活跃线程数等;Druid: SQL 执行次数,错误数,执行时间,影响行数等;Nginx: 接受,活跃连接数,读,写请求数,排队数,请求QPS,平均 RT 等;更详细的指标可以参见这里, 后续会陆续添加对Dubbo/Nacos/Sentinel/Fescar等的支持。五、REST支持Dubbo Metrics 提供了基于 JAX-RS 的 REST 接口暴露,可以轻松查询内部的各种指标,既可以独立启动HTTP Server提供服务(默认提供了一个基于Jersey+ sun Http server的简单实现),也可以嵌入已有的HTTP Server进行暴露指标。具体的接口可以参考这里:https://github.com/dubbo/metrics/wiki/query-from-http六、单机数据落盘数据如果完全存在内存里面,可能会出现因为拉取失败,或者应用本身抖动,导致数据丢失的可能。为了解决该问题,metrics引入了数据落盘的模块,提供了日志方式和二进制方式两种方式的落盘。日志方式默认通过JSON方式进行输出,可以通过日志组件进行拉取和聚合,文件的可读性也比较强,但是无法查询历史数据;二进制方式则提供了一种更加紧凑的存储,同时支持了对历史数据进行查询。目前内部使用的是这种方式。七、易用性和稳定性优化将埋点的API和实现进行拆分,方便对接不用的实现,而用户无需关注;支持注解方式进行埋点;借鉴了日志框架的设计,获取度量器更加方便;增加Compass/FastCompass,方便业务进行常用的埋点,例如统计qps,rt,成功率,错误数等等指标;Spring-boot-starter,即将开源,敬请期待;支持指标自动清理,防止长期不用的指标占用内存;URL 指标收敛,最大值保护,防止维度爆炸,错误统计导致的内存。如何使用使用方式很简单,和日志框架的Logger获取方式一致。Counter hello = MetricManager.getCounter(“test”, MetricName.build(“test.my.counter”));hello.inc();支持的度量器包括:Counter(计数器)Meter(吞吐率度量器)Histogram(直方分布度量器)Gauge(瞬态值度量器)Timer(吞吐率和响应时间分布度量器)Compass(吞吐率, 响应时间分布, 成功率和错误码度量器)FastCompass(一种快速高效统计吞吐率,平均响应时间,成功率和错误码的度量器)ClusterHistogram(集群分位数度量器)后续规划提供Spring-boot starter支持Prometheus,Spring MicroMeter对接Dubbo,Dubbo 中的数据统计实现替换为 Dubbo Metrics在 Dubbo Admin 上展示各种 metrics 数据对接 Dubbo 生态中的其他组件,如Nacos, Sentinel, Fescar等参考资料Dubbo Metrics @Github:https://github.com/dubbo/metricsWiki:https://github.com/dubbo/metrics/wiki (持续更新)本文作者:望陶,GitHub ID @ralf0131,Apache Dubbo PPMC Member,Apache Tomcat PMCMember,阿里巴巴技术专家。子观,GitHub ID @min,Apache Dubbo Commiter,阿里巴巴高级开发工程师,负责 Dubbo Admin 和 Dubbo Metrics 项目的开发和社区维护。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 15, 2019 · 2 min · jiezi

dubbo源码解析(四十二)序列化——开篇

序列化——开篇目标:介绍dubbo中序列化的内容,对dubbo中支持的序列化方式做对比,介绍dubbo-serialization-api下的源码前言序列化就是将对象转成字节流,用于网络传输,以及将字节流转为对象,用于在收到字节流数据后还原成对象。序列化的好处我就不多说了,无非就是安全性更好、可跨平台等。网上有很多总结的很好,我在这里主要讲讲dubbo中序列化的设计和实现了哪些序列化方式。dubbo在2.6.x版本中,支持五种序列化方式,分别是fastjson:依赖阿里的fastjson库,功能强大(支持普通JDK类包括任意Java Bean Class、Collection、Map、Date或enum)fst:完全兼容JDK序列化协议的系列化框架,序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右。hessian2:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式jdk:JDK自带的Java序列化实现。kryo:是一个快速序列化/反序列化工具,其使用了字节码生成机制(底层依赖了 ASM 库),因此具有比较好的运行速度,速度快,序列化后体积小,跨语言支持较复杂在dubbo最新的2.7.0版本中支持了protostuff,之前的版本dubbo还实现了自己的dubbo序列化,但是由于还不够成熟,所有暂时移除了dubbo序列化的实现。从性能上对比,fst和kryo>hessian2>fastjson>jdk。他们具体的实现我不讲解,因为很多都直接使用了对应的依赖裤,我只讲解dubbo序列化的接口设计。源码分析(一)DataInputpublic interface DataInput { /** * Read boolean. * 读取布尔类型 * @return boolean. * @throws IOException / boolean readBool() throws IOException; /* * Read byte. * 读取字节 * @return byte value. * @throws IOException / byte readByte() throws IOException; /* * Read short integer. * 读取short类型 * @return short. * @throws IOException / short readShort() throws IOException; /* * Read integer. * 读取integer类型 * @return integer. * @throws IOException / int readInt() throws IOException; /* * Read long. * 读取long类型 * @return long. * @throws IOException / long readLong() throws IOException; /* * Read float. * 读取float类型 * @return float. * @throws IOException / float readFloat() throws IOException; /* * Read double. * 读取double类型 * @return double. * @throws IOException / double readDouble() throws IOException; /* * Read UTF-8 string. * 读取UTF-8 string * @return string. * @throws IOException / String readUTF() throws IOException; /* * Read byte array. * 读取byte数组 * @return byte array. * @throws IOException / byte[] readBytes() throws IOException;}该接口是数据输入接口,可以看到定义了从 InputStream 中各类数据类型的读取方法。(二)DataOutputpublic interface DataOutput { /* * Write boolean. * 输出boolean类型 * @param v value. * @throws IOException / void writeBool(boolean v) throws IOException; /* * Write byte. * 输出byte类型 * @param v value. * @throws IOException / void writeByte(byte v) throws IOException; /* * Write short. * 输出short类型 * @param v value. * @throws IOException / void writeShort(short v) throws IOException; /* * Write integer. * 输出integer类型 * @param v value. * @throws IOException / void writeInt(int v) throws IOException; /* * Write long. * 输出long类型 * @param v value. * @throws IOException / void writeLong(long v) throws IOException; /* * Write float. * 输出float类型 * @param v value. * @throws IOException / void writeFloat(float v) throws IOException; /* * Write double. * 输出double类型 * @param v value. * @throws IOException / void writeDouble(double v) throws IOException; /* * Write string. * 输出string类型 * @param v value. * @throws IOException / void writeUTF(String v) throws IOException; /* * Write byte array. * 输出byte数组 * @param v value. * @throws IOException / void writeBytes(byte[] v) throws IOException; /* * Write byte array. * 输出byte数组中部分数据 * @param v value. * @param off offset. * @param len length. * @throws IOException / void writeBytes(byte[] v, int off, int len) throws IOException; /* * Flush buffer. * 刷新缓冲区 * @throws IOException / void flushBuffer() throws IOException;}该接口是数据输出接口,可以看到定义了向 InputStream 中,写入基本类型的数据。(三)ObjectOutputpublic interface ObjectOutput extends DataOutput { /* * write object. * 输入object类型 * @param obj object. / void writeObject(Object obj) throws IOException;}在 DataOutput 的基础上,增加写入object类型的数据。(四)ObjectInputpublic interface ObjectInput extends DataInput { /* * read object. * 读取object类型数据 * @return object. / Object readObject() throws IOException, ClassNotFoundException; /* * read object. * 根据class类型读取object类型数据 * @param cls object type. * @return object. / <T> T readObject(Class<T> cls) throws IOException, ClassNotFoundException; /* * read object. * 取object类型数据 * @param cls object type. * @return object. / <T> T readObject(Class<T> cls, Type type) throws IOException, ClassNotFoundException;}该接口是继承了DataInput 接口,在 DataInput 的基础上,增加读取object类型的数据。(五)Cleanablepublic interface Cleanable { /* * 清理 / void cleanup();}该接口是清理接口,定义了一个清理方法。目前只有kryo实现的时候,完成序列化或反序列化,需要做清理。通过实现该接口,执行清理的逻辑。(六)Serialization@SPI(“hessian2”)public interface Serialization { /* * get content type id * 获得内容类型编号 * @return content type id / byte getContentTypeId(); /* * get content type * 获得内容类型名 * @return content type / String getContentType(); /* * create serializer * 创建 ObjectOutput 对象,序列化输出到 OutputStream * @param url * @param output * @return serializer * @throws IOException / @Adaptive ObjectOutput serialize(URL url, OutputStream output) throws IOException; /* * create deserializer * 创建 ObjectInput 对象,从 InputStream 反序列化 * @param url * @param input * @return deserializer * @throws IOException / @Adaptive ObjectInput deserialize(URL url, InputStream input) throws IOException;}该接口是序列化接口,该接口也是可扩展接口,默认是使用hessian2序列化方式。其中定义了序列化和反序列化等方法(七)SerializableClassRegistrypublic abstract class SerializableClassRegistry { /* * 可序列化类类的集合 / private static final Set<Class> registrations = new LinkedHashSet<Class>(); /* * only supposed to be called at startup time * 把可序列化的类加入到集合 / public static void registerClass(Class clazz) { registrations.add(clazz); } /* * 获得可序列化的类的集合 * @return / public static Set<Class> getRegisteredClasses() { return registrations; }}该类提供一个序列化统一的注册中心,其实就是封装了可序列化类的集合(八)SerializationOptimizerpublic interface SerializationOptimizer { /* * 需要序列化的类的集合 * @return */ Collection<Class> getSerializableClasses();}该接口序列化优化器接口,在 Kryo 、FST 中,支持配置需要优化的类。业务系统中,可以实现自定义的 SerializationOptimizer,进行配置。或者使用文件来配置也是一个选择。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了dubbo支持的几种序列化方式,介绍了序列化的接口设计,具体的实现我不再讲述,因为大部分都是调用了不同的依赖库。接下来我会说一个分割线,我讲开始讲解2.7.x版本的新特性,然后分析新特性的实现,下一篇就先讲解一下dubbo2.7.0的大改动。 ...

March 14, 2019 · 4 min · jiezi

Dubbo Mesh 在闲鱼生产环境中的落地实践

本文作者至简曾在 2018 QCon 上海站以《Service Mesh 的本质、价值和应用探索》为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源、反哺开源”,也讲到了 Service Mesh 在阿里巴巴的发路径将经历以下三大阶段:撬动做透价值渗透实现技术换代Dubbo Mesh 在闲鱼生产环境的落地,分享的是以多语言为撬动点的阶段性总结。文章首发于「QCon」,阿里巴巴中间件授权转载。闲鱼场景的特点闲鱼采用的编程语言是 Dart,思路是通过 Flutter 和 Dart 实现 iOS、Android 两个客户端以及 Dart 服务端,以“三端一体”的思路去探索多端融合的高效软件开发模式。更多细节请参考作者同事陈新新在 2018 QCon 上海站的主题分享《Flutter & Dart 三端一体化开发》。本文将关注三端中的 Dart 服务端上运用 Dubbo Mesh 去解耦 Dubbo RPC 框架的初步实践成果。Dart 服务端是一个服务调用胶水层,收到来自接入网关发来的 HTTP 请求后,通过 C++ SDK 调用集团广泛提供的 Dubbo 服务完成业务逻辑处理后返回结果。然而,C++ SDK 的功能与 Java 的存在一定的差距,比如缺失限流降级等对于保障大促稳定性很重要的功能。从长远发展的角度,闲鱼团队希望通过 Dubbo Mesh 能屏蔽或简化不同技术栈使用中间件(比如,RPC、限流降级、监控、配置管理等)的复杂性。这一诉求的由来,是闲鱼团队通过几个月的实践,发现在 Dart 语言中通过 C++ SDK 逐个接入不同中间件存在定制和维护成本高的问题。值得说明,所谓的“定制”是因为 C++ SDK 的能力弱于 Java SDK 而要做补齐所致。Dart 服务端自身的业务逻辑很轻且在一些场景下需要调用 20 多次 Dubbo 服务,这对于 Dubbo Mesh 的技术挑战会显得更大。在 Dubbo Mesh 还没在生产环境落地过而缺乏第一手数据的情形下,其性能是否完全满足业务的要求是大家普遍关心的。架构与实现图中的虚框代表了一个Pouch容器(也可以是一台物理机或虚拟机)。左边两个容器部署了 Dubbo Mesh,剩下最右边的则没有。目前 Dubbo Mesh 主要包含 Bonder、Pilot、Envoy 三个进程,以及被轻量化的 Thin SDK。其中:Envoy 承担了数据平面的角色,所有 mesh 流量将由它完成服务发现与路由而中转。Envoy 由 Lyft 初创且目前成为了 CNCF 的毕业项目,我们在之上增加了对 Dubbo 协议的支持,并将之反哺到了开源社区(还有不少代码在等待社区 review 通过后才能进到 GitHub 的代码仓库)。Pilot 和 Bonder 共同承担控制平面的角色,实现服务注册、进程拉起与保活、集群信息和配置推送等功能。Pilot 进程的代码源于开源 Istio 的 pilot-discovery 组件,我们针对阿里巴巴集团环境做了一定的改造(比如,与Nacos进行适配去访问服务注册中心),且采用下沉到应用机器的方式进行部署,这一点与开源的集群化部署很不一样。背后的思考是,Pilot 的集群化部署对于大规模集群信息的同步是非常大的一个挑战,今天开源的 Istio 并不具备这一能力,未来需要 Nacos 团队对之进行增强,在没有完全准备好前通过下沉部署的方式能加速 Service Mesh 的探索历程。Thin SDK 是 Fat SDK 经过裁剪后只保留了对 Dubbo 协议进行编解码的能力。为了容灾,当 Thin SDK 位于 Consumer 侧时增加了一条容灾通道,细节将在文后做进一步展开。数据链路全部采用单条 TCP 长连接,这一点与非 mesh 场景是一致的。Pilot 与 Envoy 两进程间采用的是 gRPC/xDS 协议进行通讯。图中同时示例了 mesh 下的 Consumer 能同时调用 mesh 下的服务(图中以 www.mesh.com 域名做示例)和非 mesh 下的服务(图中以 www.non-mesh.com 域名做示例)。闲鱼落地的场景为了避免对 20 多个依赖服务进行改造,流量走的是 mesh 下的 Consumer 调用非 mesh 下的 Provider 这一形式,读者可以理解为图中最左边的容器部署的是 Dart 服务端,它将调用图中最右边容器所提供的服务去实现业务逻辑。容灾从 Dubbo Mesh 下的 Provider 角度,由于通常是集群化部署的,当一个 Provider 出现问题(无论是 mesh 组件引起的,还是 Provider 自身导致的)而使服务无法调通时,Consumer 侧的 Envoy 所实现的重试机制会将服务请求转发到其他 Provider。换句话说,集群化部署的 Provider 天然具备一定的容灾能力,在 mesh 场景下无需特别处理。站在 Dubbo Mesh 的 Consumer 立场,如果完全依赖 mesh 链路去调用 Provider,当 mesh 链路出现问题时则会导致所有服务都调不通,这往往会引发业务可用性问题。为此,Thin SDK 中提供了一个直连 Provider 的机制,只不过实现方式比 Fat SDK 轻量了许多。Thin SDK 会定期从 Envoy 的 Admin 接口获取所依赖服务的 Provider 的 IP 列表,以备检测到 mesh 链路存在问题时用于直连。比如,针对每一个依赖的服务获取最多 10 个 Provider 的 IP 地址,当 mesh 链路不通时以 round robin 算法向这些 Provider 直接发起调用。由于容灾是针对 mesh 链路的短暂失败而准备的,所以 IP 地址的多少并不是一个非常关键的点。Thin SDK 检测 mesh 链路的异常大致有如下场景:与 Envoy 的长连接出现中断,这是 Envoy 发生 crash 所致。所发起的服务调用收到 No Route Found、No Healthy Upstream 等错误响应。优化在闲鱼落地 Dubbo Mesh 的初期我们走了一个“弯路”。具体说来,最开始为了快速落地而采用了 Dubbo over HTTP 1.1/2 的模式,也即,将 Dubbo 协议封装在 HTTP 1.1/2 的消息体中完成服务调用。这一方案虽然能很好地享受 Envoy 已完整支持 HTTP 1.1/2 协议而带来的开发工作量少的好处,但性能测试表明其资源开销并不符合大家的预期。体现于,不仅 Consumer 侧使用 mesh 后带来更高的 CPU 开销,Provider 侧也因为要提供通过 HTTP 1.1/2 进行调用的能力而导致多出 20% 的 CPU 开销且存在改造工作。最终,我们回到让 Envoy 原生支持 Dubbo 协议的道路上来。Envoy 支持 Dubbo 协议经历了两大阶段。第一个阶段 Envoy 与上游的通讯并没有采用单条长连接,使得 Provider 的 CPU 开销因为多连接而存在不可忽视的递增。第二个阶段则完全采用单条长连接,通过多路复用的模式去除了前一阶段给 Provider 所带去的额外 CPU 开销。Dubbo Mesh 在闲鱼预发环境上线进行性能与功能验证时,我们意外地发现,Istio 原生 Pilot 的实现会将全量集群信息都推送给处于 Consumer 侧的 Envoy(Provider 侧没有这一问题),导致 Pilot 自身的 CPU 开销过大,而频繁的全量集群信息推送也使得 Envoy 不时会出现 CPU 负荷毛刺并遭受没有必要的内存开销。为此,我们针对这一问题做了集群信息按需加载的重大改造,这一优化对于更大规模与范围下运用 Dubbo Mesh 具有非常重要的意义。优化的大致思路是:Thin SDK 提供一个 API 供 Consumer 的应用在初始化时调用,周知其所需调用的服务列表。Thin SDK 通过 HTTP API 将所依赖的服务列表告诉 Bonder,Bonder 将之保存到本地文件。Envoy 启动时读取 Bonder 所保存的服务列表文件,将之当作元信息转给 Pilot。Pilot 向 Nacos 只订阅服务列表中的集群信息更新消息且只将这些消息推送给 Envoy。监控可观测性(observability)是 Service Mesh 非常重要的内容,在服务调用链路上插入了 Envoy 的情形下,愈加需要通过更强的监控措施去治理其上的所有微服务。Dubbo Mesh 的监控方案并没有使用 Istio/Mixer 这样的设计,而是沿用了阿里巴巴集团内部的方式,即信息由各进程以日志的形式输出,然后通过日志采集程序将之送到指定的服务端进行后期加工并最终展示于控制台。目前 Dubbo Mesh 通过 EagleEye 去跟踪调用链,通过ARMS去展示其他的监控信息。性能评估为了评估 Dubbo Mesh 的性能能否满足闲鱼业务的需要,我们设计了如下图所示的性能比对测试方案。其中:测试机器是阿里巴巴集团生产环境中的 3 台 4 核 8G 内存的 Pouch 容器。蓝色方框代表的是进程。测试数据全部从部署了 DartServer 和 Envoy 两进程的测试机 2 上获得。性能数据分别在非 mesh(图中红色数据流)和 mesh(图中蓝色数据流)两个场景下获得。显然,Mesh 场景下的服务流量多了 Envoy 进程所带来的一跳。DartServer 收到来自施压的 Loader 进程所发来的一个请求后,将发出 21 次到 Provider 进程的 RPC 调用。在评估 Dubbo Mesh 的性能时,这 21 次是串行发出的(下文列出的测试数据是在这一情形下收集的),实际闲鱼生产环境上线时考虑了进行并行发送去进一步降低整体调用时延(即便没有 mesh 时,闲鱼的业务也是这样实现的)。Provider 进程端并没有部署 Envoy 进程。这省去了初期引入 Dubbo Mesh 对 Provider 端的改造成本,降低了落地的工作量和难度。设计测试方案时,我们与闲鱼的同学共创了如何回答打算运用 Dubbo Mesh 的业务方一定会问的问题,即“使用 Dubbo Mesh 后对 RT(Response Time)和 CPU 负荷的影响有多大”。背后的动机是,业务方希望通过 RT 这一指标去了解 Dubbo Mesh 对用户体验的影响,基于 CPU 负荷的增长去掌握运用新技术所引发的成本。面对这一问题通常的回答是“在某某 QPS 下,RT 增加了 x%,CPU 负荷增加了 y%”,但这样的回答如果不进行具体测试是无法给出的(会出现“鸡和蛋的问题”)。因为每个业务的天然不同使得一个完整请求的 RT 会存在很大的差别(从几毫秒到几百毫秒),而实现业务逻辑所需的计算量又最终决定了机器的 CPU 负荷水平。基于此,我们设计的测试方案在于评估引入 Dubbo Mesh 后,每经过一跳 Envoy 所引入的 RT 和 CPU 增量。当这一数据出来后,业务方完全可以基于自己业务的现有数据去计算出引入 Dubbo Mesh 后的而掌握大致的影响情况。显然,背后的逻辑假设是“Envoy 对于每个 Dubbo 服务调用的计算量是一样的”,事实也确实如此。测试数据以下是 Loader 发出的请求在并发度为 100 的情形下所采集的数据。表中:Envoy 的 QPS 是 Loader 的 21 倍,原因在上面测试方案部分有交代。“单跳”的数据是从“21 跳合计”直接除以 21 所得,其严谨性值得商榷,但用于初步评估仍具参考价值(有数据比没有数据强)。“整机负荷”代表了在 mesh 场景下测试机器 2 上 DartServer 和 Envoy 两进程的 CPU 开销总和。测试表明,CPU 负荷高时 Envoy 带来的单跳 RT 增幅更大(比如表中 Loader 的 QPS 是 480 时)。给出整机负荷是为了提醒读者关注引入 mesh 前业务的正常单机水位,以便更为客观地评估运用 Dubbo Mesh 将带来的潜在影响。“CPU 负荷增幅”是指 CPU 增加的幅度。由于测试机是 4 核的,所以整机的 CPU 负荷是 400。从表中数据来看,随着机器整体负荷的增加“CPU 负荷增幅”在高段存在波动,这与 RT 在高段的持续增大存在相关,从 RT 在整体测试中完全符合线性增长来看整体数据合理。当然, 后面值得深入研究数据背后的隐藏技术细节以便深入优化。线上数据Dubbo Mesh 正式生产环境上线后,我们通过对上线前后的某接口的 RT 数据进行了全天的比对,以便大致掌握 mesh 化后的影响。2019-01-14 该接口全面切成了走 Dubbo Mesh,我们取的是 2019-01-20 日的数据。图中蓝色是 mesh 化后的 RT 表现(RT 均值 3.3),而橙色是 mesh 化前的 RT 表现(RT 均值 3.27,取的是 2019-01-13 的数据)。由于线上每天的环境都有所不同,要做绝对的比较并不可能。但通过上面的比较不难看出,mesh 化前后对于整体 RT 的影响相当的小。当整体 RT 小于 5 毫秒是如此,如果整体 RT 是几十、几百毫秒则影响就更小。为了帮助更全面地看待业务流量的波动特点,下面分别列出了两天非 mesh(2019-01-06 和 2019-01-13)和两天 mesh(2019-01-20 和 2019-01-23)的比对数据。总之,生产环境上的数据表现与前面性能评估方案下所获得的测试数据能很好地吻合。洞见Dubbo Mesh 在闲鱼生产环境的落地实践让我们收获了如下的洞见:服务发现的时效性是 Service Mesh 技术的首要关键。 以集群方式提供服务的情形下(这是分布式应用的常态),因为应用发布而导致集群中机器状态的变更如何及时准确地推送到数据平面是极具挑战的问题。对于阿里巴巴集团来说,这是 Nacos 团队致力于解决的问题。开源版本的 Istio 能否在生产环境中运用于大规模分布式应用也首先取决于这一能力。频繁的集群信息推送,将给控制平面和数据平面都带去负荷扰动,如何通过技术手段控制好扰动是需要特别关注的,对于数据平面来说编程语言的“确定性”(比如,没有 VM、没有 GC)在其中将起到不可忽视的作用。数据平面的软件实现最大程度地减少内存分配与释放将显著地改善性能。有两大举措可以考虑:逻辑与数据相分离。 以在 Envoy 中实现 Dubbo 协议为例,Envoy 每收到一个 RPC 请求都会动态地创建 fitler 去处理,一旦实现逻辑与数据相分离,filter 的创建对于每一个 worker 线程有且只有一次,通过这一个 filter 去处理所有的 RPC 请求。使用内存池。 Envoy 的实现中基本没有用到内存池,如果采用内存池对分配出来的各种 bufffer 通过链表进行缓存,这将省去大量的内存分配与释放而改善性能。再则,对于处理一个 RPC 请求而多次分散分配的动作整合成集中一次性分配也是值得运用的优化技巧。数据平面的 runtime profiling 是关键技术。 Service Mesh 虽然对业务代码没有侵入性,但对服务流量具有侵入性,如何在出现业务毛刺的情形下,快速地通过 runtime profiling 去发现问题或自证清白是非常值得关注的点。心得一年不到的探索旅程,让团队更加笃定“借力开源,反哺开源”的发展思路。随着对 Istio 和 Envoy 实现细节的更多掌握,团队很强列地感受到了走“站在巨人的肩膀上”发展的道路少走了很多弯路,除了快速跟进业界的发展步伐与思路,还将省下精力去做更有价值的事和创新。此外,Istio 和 Envoy 两个开源项目的工程质量都很高,单元测试等质量保证手段是日常开发工作中的基础环节,而我们也完全采纳了这些实践。比如,内部搭建了 CI 环境、每次代码提交将自动触发单元测试、代码经过 code review 并完成单元测试才能入库、自动化性能测试等。展望在 2019 年接下来的日子,我们将着手:与 Sentinel 团队形成合力,将 Sentinel 的能力纳入到 Dubbo Mesh 中补全对 HTTP 和 Dubbo 协议的限流、降级和熔断能力。在阿里巴巴集团大范围 Kubernetes(Sigma 3.1)落地的背景下,与兄弟团队探索更加优雅的服务流量透明拦截技术方案。迎合 Serverless 的技术发展趋势,深化通过 Dubbo Mesh 更好地轻量化应用,以及基于 Dubbo Mesh 对服务流量的天然敏感性去更好地实现 auto-scaling。在产品的易用性和工程效率方面踏实进取。未来,我们将及时与读者分享阿里巴巴集团在 Service Mesh 这一新技术领域的探索成果,也期待与大家有更多的互动交流。本文作者:至简,阿里巴巴中间件高级技术专家,是阿里巴巴集团 Service Mesh 方向的重要参与者和推动者。关于 Dubbo Mesh 的首次公开分享本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 11, 2019 · 3 min · jiezi

30 道 Dubbo 面试题及答案

Spring Eureka 从开源转变为闭源,Consul 正在崛起,而 Dubbo 又开始重新更新。目前市场上仍有不少公司使用dubbo我们也需要继续学习。1、为什么要用Dubbo?随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。就这样为分布式系统的服务治理框架就出现了,Dubbo也就这样产生了。2、Dubbo 的整体架构设计有哪些分层?接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router和LoadBlancce监控层(Monitor):RPC调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor和MonitorService 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker和Exporter信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和 Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为Channel、Transporter、Client、Server和Codec数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool3、默认使用的是什么通信框架,还有别的选择吗?默认也推荐使用netty框架,还有mina。4、服务调用是阻塞的吗?默认是阻塞的,可以异步调用,没有返回值的可以这么做。Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。5、一般使用什么注册中心?还有别的选择吗?推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。6、默认使用什么序列化框架,你知道的还有哪些?推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。7、服务提供者能实现失效踢出是什么原理?服务失效踢出基于zookeeper的临时节点原理。8、服务上线怎么不影响旧版本?采用多版本开发,不影响旧版本。9、如何解决服务调用链过长的问题?可以结合zipkin实现分布式服务追踪。10、说说核心的配置有哪些?配置配置说明dubbo:service服务配置dubbo:reference引用配置dubbo:protocol协议配置dubbo:application应用配置dubbo:module模块配置dubbo:registry注册中心配置dubbo:monitor监控中心配置dubbo:provider提供方配置dubbo:consumer消费方配置dubbo:method方法配置dubbo:argument参数配置11、Dubbo 推荐用什么协议?dubbo://(推荐)rmi://hessian://http://webservice://thrift://memcached://redis://rest://12、同一个服务多个注册的情况下可以直连某一个服务吗?可以点对点直连,修改配置即可,也可以通过telnet直接某个服务。13、画一画服务注册与发现的流程图?14、Dubbo 集群容错有几种方案?集群容错方案说明Failover Cluster失败自动切换,自动重试其它服务器(默认)Failfast Cluster快速失败,立即报错,只发起一次调用Failsafe Cluster失败安全,出现异常时,直接忽略Failback Cluster失败自动恢复,记录失败请求,定时重发Forking Cluster并行调用多个服务器,只要一个成功即返回Broadcast Cluster广播逐个调用所有提供者,任意一个报错则报错15、Dubbo 服务降级,失败重试怎么做?可以通过dubbo:reference 中设置 mock=“return null”。mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑16、Dubbo 使用过程中都遇到了些什么问题?在注册中心找不到对应的服务,检查service实现类是否添加了@service注解无法连接到注册中心,检查配置文件中的对应的测试ip是否正确17、Dubbo Monitor 实现原理?Consumer端在发起调用之前会先走filter链;provider端在接收到请求时也是先走filter链,然后才进行真正的业务逻辑处理。默认情况下,在consumer和provider的filter链中都会有Monitorfilter。1、MonitorFilter向DubboMonitor发送数据2、DubboMonitor将数据进行聚合后(默认聚合1min中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference> statisticsMap,然后使用一个含有3个线程(线程名字:DubboMonitorSendTimer)的线程池每隔1min钟,调用SimpleMonitorService遍历发送statisticsMap中的统计数据,每发送完毕一个,就重置当前的Statistics的AtomicReference3、SimpleMonitorService将这些聚合数据塞入BlockingQueue queue中(队列大写为100000)4、SimpleMonitorService使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将queue中的数据写入文件(该线程以死循环的形式来写)5、SimpleMonitorService还会使用一个含有1个线程(线程名字:DubboMonitorTimer)的线程池每隔5min钟,将文件中的统计数据画成图表18、Dubbo 用到哪些设计模式?Dubbo框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。工厂模式Provider在export服务时,会调用ServiceConfig的export方法。ServiceConfig中有个字段:private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了JDK SPI的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。装饰器模式Dubbo在启动和调用阶段都大量使用了装饰器模式。以Provider提供的调用链为例,具体的调用链代码是在ProtocolFilterWrapper的buildInvokerChain完成的,具体是将注解中含有group=provider的Filter实现,按照order排序,最后的调用顺序是:EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter -> ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter -> ExceptionFilter更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的ClassLoader,这是典型的装饰器模式。观察者模式Dubbo的Provider启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个listener。注册中心会每5秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个notify消息,provider接受到notify消息后,即运行NotifyListener的notify方法,执行监听器方法。动态代理模式Dubbo扩展JDK SPI的类ExtensionLoader的Adaptive实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是ExtensionLoader的createAdaptiveExtensionClassCode方法。代理类的主要逻辑是,获取URL参数中指定参数的值作为获取实现类的key。19、Dubbo 配置文件是如何加载到Spring中的?Spring容器在启动的时候,会读取到Spring默认的一些schema以及Dubbo自定义的schema,每个schema都会对应一个自己的NamespaceHandler,NamespaceHandler里面通过BeanDefinitionParser来解析配置信息并转化为需要加载的bean对象!20、Dubbo SPI 和 Java SPI 区别?JDK SPIJDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展吃实话很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了DUBBO SPI1,对Dubbo进行扩展,不需要改动Dubbo的源码 2,延迟加载,可以一次只加载自己想要加载的扩展实现。 3,增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。 3,Dubbo的扩展机制能很好的支持第三方IoC容器,默认支持Spring Bean。21、Dubbo 支持分布式事务吗?目前暂时不支持,可与通过 tcc-transaction框架实现介绍:tcc-transaction是开源的TCC补偿性分布式事务框架Git地址:https://github.com/changmingx…TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。22、Dubbo 可以对结果进行缓存吗?为了提高数据访问的速度。Dubbo提供了声明式缓存,以减少用户加缓存的工作量<dubbo:reference cache=“true” /> 其实比普通的配置文件就多了一个标签 cache=“true"23、服务上线怎么兼容旧版本?可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。24、Dubbo必须依赖的包有哪些?Dubbo 必须依赖 JDK,其他为可选。25、Dubbo telnet 命令能做什么?dubbo服务发布之后,我们可以利用telnet命令进行调试、管理。 Dubbo2.0.5以上版本服务提供端口支持telnet命令连接服务telnet localhost 20880 //键入回车进入Dubbo命令模式。查看服务列表dubbo>lscom.test.TestServicedubbo>ls com.test.TestServicecreatedeletequeryls (list services and methods)ls : 显示服务列表。ls -l : 显示服务详细信息列表。ls XxxService:显示服务的方法列表。ls -l XxxService:显示服务的方法详细信息列表。26、Dubbo 支持服务降级吗?以通过dubbo:reference 中设置 mock=“return null”。mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑27、Dubbo 如何优雅停机?Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。28、Dubbo 和 Dubbox 之间的区别?Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如加了服务可 Restful 调用,更新了开源组件等。29、Dubbo 和 Spring Cloud 的区别?根据微服务架构在各方面的要素,看看Spring Cloud和Dubbo都提供了哪些支持。|| Dubbo | Spring Cloud | |— |—— || 服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka || 服务调用方式 | RPC | REST API || 服务网关 | 无 | Spring Cloud Netflix Zuul || 断路器 | 不完善 | Spring Cloud Netflix Hystrix || 分布式配置 | 无 | Spring Cloud Config || 服务跟踪 | 无 | Spring Cloud Sleuth || 消息总线 | 无 | Spring Cloud Bus || 数据流 | 无 | Spring Cloud Stream || 批量任务 | 无 | Spring Cloud Task || …… | …… | …… |使用Dubbo构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;而Spring Cloud就像品牌机,在Spring Source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。30、你还了解别的分布式框架吗?别的还有spring的spring cloud,facebook的thrift,twitter的finagle等 ...

March 8, 2019 · 2 min · jiezi

dubbo

Dubbodubbo.iodubbo+spring boot +dockerdubbo能解决什么问题怎么去维护url通过注册中心去维护url(zookeeper、redis、memcache…)F5硬件负载均衡器的单点压力比较大软负载均衡怎么去整理出服务之间的依赖关系。自动去整理各个服务之间的依赖如果服务器的调用量越来越大,服务器的容量问题怎么去评估,扩容的指标需要一个监控平台,可以监控调用量、响应时间Dubbo是什么 dubbo是一个分布式的服务框架,提供高性能的以及透明化的RPC远程服务调用解决方法,以及SOA服务治理方案。Dubbo的核心部分: 远程通信 集群容错 服务的自动发现 负载均衡Dubbo的架构核心角色Provider暴露服务的服务提供方Consumer调用远程服务的服务消费方Registry服务注册与发现的注册中心Monitor统计服务的调用次数和调用时间的监控中心Container服务运行容器服务容器负责启动,加载,运行服务提供者。服务提供者在启动时,向注册中心注册自己提供的服务。服务消费者在启动时,向注册中心订阅自己所需的服务。注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心PAAS(platform-as-a-service)/IAAS(infrastucturre-as-a-service)/SAAS(软件即服务)如果你是一个网站站长,想要建立一个网站。不采用云服务,你所需要的投入大概是:买服务器,安装服务器软件,编写网站程序。现在你追随潮流,采用流行的云计算,如果你采用IaaS服务,那么意味着你就不用自己买服务器了,随便在哪家购买虚拟机,但是还是需要自己装服务器软件而如果你采用PaaS的服务,那么意味着你既不需要买服务器,也不需要自己装服务器软件,只需要自己开发网站程序如果你再进一步,购买某些在线论坛或者在线网店的服务,这意味着你也不用自己开发网站程序,只需要使用它们开发好的程序,而且他们会负责程序的升级、维护、增加服务器等,而你只需要专心运营即可,此即为SaaS。各个应用节点中的url管理维护很困难、 依赖关系很模糊 每个应用节点的性能、访问量、响应时间,没办法评估软负载均衡:nginx,apache nginx和apache集群:nginx+lvc ,nginx+keepalived进程依赖于存储: sqlserver oracle mysql/maraiDb mongoDB redis集群:Master-slave进程通信: kafkaDubbo的使用入门对外框架一定是基于tcp协议的dubbo://192.168.4.169:20880/com.zzjson.IOrderServices?anyhost=true&application=order-provider&dubbo=2.5.3&interface=com.zzjson.IOrderServices&methods=doOrder&owner=zzy&pid=57178&side=provider&timestamp=1551756860543, dubbo version: 2.5.3, current host: 127.0.0.1dubbo://177.1.1.82/20880/com.gupao.vip.mic.dubbo.order.IOrderServices%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.gupao.vip.mic.dubbo.order.IOrderServices%26methods%3DdoOrder%26owner%3Dmic%26pid%3D10804%26side%3Dprovider%26timestamp%3D1502890818766多了一个dubbo的目录 url 是临时节点,其他节点都是持久的,因为url是动态变化的Main方法怎么启动的日志怎么集成直接使用log4j放在工程目录下admin控制台的安装https://github.com/apache/inc…# centers in dubbo2.7admin.registry.address=zookeeper://116.62.221.6:2181?backup=39.98.95.85:2181,47.92.239.233:2181#admin.config-center=zookeeper://127.0.0.1:2181#admin.metadata-report.address=zookeeper://127.0.0.1:2181admin.registry.group=dubboadmin.apollo.token=e16e5cd903fd0c97a116c873b448544b9d086de9admin.apollo.appId=testadmin.apollo.env=devadmin.apollo.cluster=defaultadmin.apollo.namespace=dubbodubbo.registry.address=zookeeper的集群地址控制中心是用来做服务治理的,比如控制服务的权重、服务的路由、。。。simple监控中心Monitor也是一个dubbo服务,所以也会有端口和url修改/conf目录下dubbo.properties /order-provider.xmldubbo.registry.address=zookeeper://116.62.221.6:2181?backup=39.98.95.85:2181,47.92.239.233:2181监控服务的调用次数、调用关系、响应事件telnet命令telnet ip port ls、cd、pwd、clear、invoker

March 8, 2019 · 1 min · jiezi

dubbo2

启动服务检查如果提供方没有启动的时候,默认会去检测所依赖的服务是否正常提供服务比如说order依赖于pay,必须pay启动了,才能够使用order如果check为false,表示启动的时候不去检查。当服务出现循环依赖的时候(两个服务彼此依赖),check设置成falsedubbo:reference 属性: check 默认值是true 、falsedubbo:consumer check=”false” 没有服务提供者的时候,报错dubbo:registry check=false 注册订阅失败报错 dubbo:provider多协议支持dubbo支持的协议: dubbo、RMI、hessian、webservice、http、Thrift、memcached、redis、rest老项目协议无法改变,通过配置协议来解决hessian协议引入jar包<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.38</version></dependency><dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version></dependency><dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> <version>6.1.26</version></dependency>修改provider.xml<!–增加hessian协议–><dubbo:protocol name=“hessian” port=“8090” server=“jetty”/> 指定service服务的协议版本号<dubbo:service interface=“com.zzjson.IOrderServices” ref=“orderService” protocol=“hessian”/>消费端改造<dubbo:reference interface=“com.zzjson.IOrderServices” id=“orderServices” protocol=“Hessain”/>dubbo使用spring纯注解异常解决方案:<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.3</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> </exclusions></dependencyhessian%3A%2F%2F192.168.4.169%3A8080%2Fcom.zzjson.IOrderServices%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61276%26server%3Djetty%26side%3Dprovider%26timestamp%3D1551776679018dubbo%3A%2F%2F192.168.4.169%3A20880%2Fcom.zzjson.IOrderServices%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61276%26side%3Dprovider%26timestamp%3D1551776679988 多注册中心支持多版本支持客户端调用的时候dubbo%3A%2F%2F192.168.4.169%3A20880%2Fcom.zzjson.IOrderServices2%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61661%26revision%3D1.0%26side%3Dprovider%26timestamp%3D1551778295256%26version%3D1.0hessian%3A%2F%2F192.168.4.169%3A8080%2Fcom.zzjson.IOrderServices2%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61661%26revision%3D1.0%26server%3Djetty%26side%3Dprovider%26timestamp%3D1551778294832%26version%3D1.0dubbo%3A%2F%2F192.168.4.169%3A20880%2Fcom.zzjson.IOrderServices%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61661%26revision%3D2.0%26side%3Dprovider%26timestamp%3D1551778294284%26version%3D2.0hessian%3A%2F%2F192.168.4.169%3A8080%2Fcom.zzjson.IOrderServices%3Fanyhost%3Dtrue%26application%3Dorder-provider%26dubbo%3D2.5.3%26interface%3Dcom.zzjson.IOrderServices%26methods%3DdoOrder%26owner%3Dzzy%26pid%3D61661%26revision%3D2.0%26server%3Djetty%26side%3Dprovider%26timestamp%3D1551778293346%26version%3D2.0异步调用 调用接口过程可能比较慢,我们有时候并不需要同步等待,原理使用的是future异步回调机制async=“true"表示接口异步返回hessian协议,使用async异步回调会报错Future<DoOrderResponse> future = RpcContext.getContext().getFuture();System.out.println(“aaa”);DoOrderResponse response = future.get();主机绑定通过<dubbo:protocol host配置的地址去找<dubbo:protocol name=“dubbo” port=“20880” host=“192.168.4.169”/>host = InetAddress.getLocalHost().getHostAddress()通过socket发起连接连接到注册中心的地址。再获取连接过去以后本地的ip地址host = NetUtils.getLocalHost();serviceconfig .classdubbo服务只订阅 测试过程中,正在开发,有问题,注册到注册中心了,只需要订阅服务,只订阅,不注册,开发的时候设置为false,只订阅,不注册dubbo服务只注册 只提供服务,不订阅服务 多注册中心情况下,服务发布到两个注册中心,其中一个注册中心还没有发布,但是需要注册,dubbo只会选择其中一个可用的去使用<dubbo:registry subscribe=“false”/>负载均衡 在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用。可以自行扩展负载均衡策略 http://dubbo.apache.org/zh-cn...Random LoadBalance 随机,按权重设置随机概率。 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。RoundRobin LoadBalance 轮循,按公约后的权重设置轮循比率。 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。LeastActive LoadBalance 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。ConsistentHash LoadBalance 一致性Hash,相同参数的请求总是发到同一提供者。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。连接超时timeout 毫秒为单位 默认 1000 必须要设置服务的处理的超时时间集群容错 修改集群容错方式<dubbo:service cluster=“failsafe” />Failover cluster 失败的时候自动切换并重试其他服务器。 通过retries=2。 来设置重试次数Failfast cluster 快速失败,只发起一次调用 写操作。比如新增记录的时候, 非幂(服务调用后端某一接口发起多次结果不变)等请求 insert 唯一的key,影响行数只会影响一行Failsafe cluster 失败安全。 出现异常时,直接忽略异常 写日志,不一定要日志保存成功,失败了不能影响主程序的运行Failback cluster 失败自动恢复。 后台记录失败请求,定时重发 比如说消息通知,失败了一直发送Forking cluster 并行调用多个服务器,只要一个成功就返回。 只能应用在读请求 会浪费服务器的资源Broadcast cluster 广播调用所有提供者,逐个调用。其中一台报错就会返回异常配置的优先级如果消费端和服务端都设置了超时时间,那么谁的优先级最大消费端优先级别最高 – 服务端Reference method>servicemethod>reference>service>consumer>provider服务改造dubbo依赖spring版本是2.几太低,更改依赖优雅停机 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。原理服务提供方停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。服务消费方停止时,不再发起新的调用请求,所有新的调用在客户端即报错。然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。# dubbo.propertiesdubbo.service.shutdown.wait=15000 ...

March 8, 2019 · 1 min · jiezi

dubbo源码解析(四十一)集群——Mock

集群——Mock目标:介绍dubbo中集群的Mock,介绍dubbo-cluster下关于服务降级和本地伪装的源码。前言本文讲解两块内容,分别是本地伪装和服务降级,本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。而服务降级则是临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。源码分析(一)MockClusterWrapperpublic class MockClusterWrapper implements Cluster { private Cluster cluster; public MockClusterWrapper(Cluster cluster) { this.cluster = cluster; } @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建MockClusterInvoker return new MockClusterInvoker<T>(directory, this.cluster.join(directory)); }}该类是服务降级的装饰器类,对Cluster进行了功能增强,增强了服务降级的功能。(二)MockClusterInvoker该类是服务降级中定义降级后的返回策略的实现。1.属性private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);/** * 目录 /private final Directory<T> directory;/* * invoker对象 /private final Invoker<T> invoker;2.invoke@Overridepublic Result invoke(Invocation invocation) throws RpcException { Result result = null; // 获得 “mock” 配置项,有多种配置方式 String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); // 如果没有mock if (value.length() == 0 || value.equalsIgnoreCase(“false”)) { //no mock // 直接调用 result = this.invoker.invoke(invocation); // 如果强制服务降级 } else if (value.startsWith(“force”)) { if (logger.isWarnEnabled()) { logger.info(“force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl()); } //force:direct mock // 直接调用 Mock Invoker ,执行本地 Mock 逻辑 result = doMockInvoke(invocation, null); } else { //fail-mock // 失败服务降级 try { // 否则正常调用 result = this.invoker.invoke(invocation); } catch (RpcException e) { if (e.isBiz()) { throw e; } else { if (logger.isWarnEnabled()) { logger.warn(“fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e); } // 如果调用失败,则服务降级 result = doMockInvoke(invocation, e); } } } return result;}该方法是定义降级后的返回策略的实现,根据配置的不同来决定不用降级还是强制服务降级还是失败后再服务降级。3.doMockInvoke@SuppressWarnings({“unchecked”, “rawtypes”})private Result doMockInvoke(Invocation invocation, RpcException e) { Result result = null; Invoker<T> minvoker; // 路由匹配 Mock Invoker 集合 List<Invoker<T>> mockInvokers = selectMockInvoker(invocation); // 如果mockInvokers为空,则创建一个MockInvoker if (mockInvokers == null || mockInvokers.isEmpty()) { // 创建一个MockInvoker minvoker = (Invoker<T>) new MockInvoker(directory.getUrl()); } else { // 取出第一个 minvoker = mockInvokers.get(0); } try { // 调用invoke result = minvoker.invoke(invocation); } catch (RpcException me) { // 如果抛出异常,则返回异常结果 if (me.isBiz()) { result = new RpcResult(me.getCause()); } else { throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause()); } } catch (Throwable me) { throw new RpcException(getMockExceptionMessage(e, me), me.getCause()); } return result;}该方法是执行本地Mock,服务降级。4.selectMockInvokerprivate List<Invoker<T>> selectMockInvoker(Invocation invocation) { List<Invoker<T>> invokers = null; //TODO generic invoker? if (invocation instanceof RpcInvocation) { //Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachement needs to be improved) // 注意隐式契约(尽管描述被添加到接口声明中,但是可扩展性是一个问题。附件中的做法需要改进) ((RpcInvocation) invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.toString()); //directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return. // 如果调用中存在Constants.INVOCATION_NEED_MOCK,则目录将返回正常调用者列表,否则,将返回模拟调用者列表。 try { invokers = directory.list(invocation); } catch (RpcException e) { if (logger.isInfoEnabled()) { logger.info(“Exception when try to invoke mock. Get mock invokers error for service:” + directory.getUrl().getServiceInterface() + “, method:” + invocation.getMethodName() + “, will contruct a new mock with ’new MockInvoker()’.”, e); } } } return invokers;}该方法是路由匹配 Mock Invoker 集合。(三)MockInvokersSelector该类是路由选择器实现类。1.route@Overridepublic <T> List<Invoker<T>> route(final List<Invoker<T>> invokers, URL url, final Invocation invocation) throws RpcException { // 如果附加值为空,则直接 if (invocation.getAttachments() == null) { // 获得普通的invoker集合 return getNormalInvokers(invokers); } else { // 获得是否需要降级的值 String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK); // 如果为空,则获得普通的Invoker集合 if (value == null) return getNormalInvokers(invokers); else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) { // 获得MockedInvoker集合 return getMockedInvokers(invokers); } } return invokers;}该方法是根据配置来决定选择普通的invoker集合还是mockInvoker集合。2.getMockedInvokersprivate <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) { // 如果没有MockedInvoker,则返回null if (!hasMockProviders(invokers)) { return null; } // 找到MockedInvoker,往sInvokers中加入,并且返回 List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1); for (Invoker<T> invoker : invokers) { if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) { sInvokers.add(invoker); } } return sInvokers;}该方法是获得MockedInvoker集合。3.getNormalInvokersprivate <T> List<Invoker<T>> getNormalInvokers(final List<Invoker<T>> invokers) { // 如果没有MockedInvoker,则返回普通的Invoker 集合 if (!hasMockProviders(invokers)) { return invokers; } else { // 否则 去除MockedInvoker,把普通的Invoker 集合返回 List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(invokers.size()); for (Invoker<T> invoker : invokers) { // 把不是MockedInvoker的invoker加入sInvokers,返回 if (!invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) { sInvokers.add(invoker); } } return sInvokers; }}该方法是获得普通的Invoker集合,不包含mock的。4.hasMockProvidersprivate <T> boolean hasMockProviders(final List<Invoker<T>> invokers) { boolean hasMockProvider = false; for (Invoker<T> invoker : invokers) { // 如果有一个是MockInvoker,则返回true if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) { hasMockProvider = true; break; } } return hasMockProvider;}该方法是判断是否有MockInvoker。以上三个类是对服务降级功能的实现,下面两个类是对本地伪装的实现。(四)MockProtocol该类实现了AbstractProtocol接口,是服务final public class MockProtocol extends AbstractProtocol { @Override public int getDefaultPort() { return 0; } @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { throw new UnsupportedOperationException(); } @Override public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { // 创建MockInvoker return new MockInvoker<T>(url); }}(五)MockInvoker本地伪装的invoker实现类。1.属性/* * 代理工厂 /private final static ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();/* * mock 与 Invoker 对象的映射缓存 /private final static Map<String, Invoker<?>> mocks = new ConcurrentHashMap<String, Invoker<?>>();/* * 异常集合 /private final static Map<String, Throwable> throwables = new ConcurrentHashMap<String, Throwable>();/* * url对象 */private final URL url;2.parseMockValuepublic static Object parseMockValue(String mock, Type[] returnTypes) throws Exception { Object value = null; // 如果mock为empty,则 if (“empty”.equals(mock)) { // 获得空的对象 value = ReflectUtils.getEmptyObject(returnTypes != null && returnTypes.length > 0 ? (Class<?>) returnTypes[0] : null); } else if (“null”.equals(mock)) { // 如果为null,则返回null value = null; } else if (“true”.equals(mock)) { // 如果为true,则返回true value = true; } else if (“false”.equals(mock)) { // 如果为false,则返回false value = false; } else if (mock.length() >= 2 && (mock.startsWith(”"”) && mock.endsWith(""") || mock.startsWith("'") && mock.endsWith("'"))) { // 使用 ’’ 或 "" 的字符串,截取掉头尾 value = mock.subSequence(1, mock.length() - 1); } else if (returnTypes != null && returnTypes.length > 0 && returnTypes[0] == String.class) { // 字符串 value = mock; } else if (StringUtils.isNumeric(mock)) { // 是数字 value = JSON.parse(mock); } else if (mock.startsWith("{")) { // 是map类型的 value = JSON.parseObject(mock, Map.class); } else if (mock.startsWith("[")) { // 是数组类型 value = JSON.parseObject(mock, List.class); } else { value = mock; } if (returnTypes != null && returnTypes.length > 0) { value = PojoUtils.realize(value, (Class<?>) returnTypes[0], returnTypes.length > 1 ? returnTypes[1] : null); } return value;}该方法是解析mock值3.invoke@Overridepublic Result invoke(Invocation invocation) throws RpcException { // 获得 "mock" 配置项,方法级 > 类级 String mock = getUrl().getParameter(invocation.getMethodName() + “.” + Constants.MOCK_KEY); if (invocation instanceof RpcInvocation) { ((RpcInvocation) invocation).setInvoker(this); } // 如果mock为空 if (StringUtils.isBlank(mock)) { // 获得mock值 mock = getUrl().getParameter(Constants.MOCK_KEY); } // 如果还是为空。则抛出异常 if (StringUtils.isBlank(mock)) { throw new RpcException(new IllegalAccessException(“mock can not be null. url :” + url)); } // 标准化 "mock" 配置项 mock = normalizeMock(URL.decode(mock)); // 等于 “return " ,返回值为空的 RpcResult 对象 if (mock.startsWith(Constants.RETURN_PREFIX)) { // 分割 mock = mock.substring(Constants.RETURN_PREFIX.length()).trim(); try { // 获得返回类型 Type[] returnTypes = RpcUtils.getReturnTypes(invocation); // 解析mock值 Object value = parseMockValue(mock, returnTypes); return new RpcResult(value); } catch (Exception ew) { throw new RpcException(“mock return invoke error. method :” + invocation.getMethodName() + “, mock:” + mock + “, url: " + url, ew); } // 如果是throw } else if (mock.startsWith(Constants.THROW_PREFIX)) { // 根据throw分割 mock = mock.substring(Constants.THROW_PREFIX.length()).trim(); // 如果为空,则抛出异常 if (StringUtils.isBlank(mock)) { throw new RpcException(“mocked exception for service degradation.”); } else { // user customized class // 创建自定义异常 Throwable t = getThrowable(mock); // 抛出业务类型的 RpcException 异常 throw new RpcException(RpcException.BIZ_EXCEPTION, t); } } else { //impl mock try { // 否则直接获得invoker Invoker<T> invoker = getInvoker(mock); // 调用 return invoker.invoke(invocation); } catch (Throwable t) { throw new RpcException(“Failed to create mock implementation class " + mock, t); } }}该方法是本地伪装的核心实现,mock分三种,分别是return、throw、自定义的mock类。4.normalizedMockpublic static String normalizeMock(String mock) { // 若为空,直接返回 if (mock == null) { return mock; } mock = mock.trim(); if (mock.length() == 0) { return mock; } if (Constants.RETURN_KEY.equalsIgnoreCase(mock)) { return Constants.RETURN_PREFIX + “null”; } // 若果为 “true” “default” “fail” “force” 四种字符串,返回default if (ConfigUtils.isDefault(mock) || “fail”.equalsIgnoreCase(mock) || “force”.equalsIgnoreCase(mock)) { return “default”; } // fail:throw/return foo => throw/return if (mock.startsWith(Constants.FAIL_PREFIX)) { mock = mock.substring(Constants.FAIL_PREFIX.length()).trim(); } // force:throw/return foo => throw/return if (mock.startsWith(Constants.FORCE_PREFIX)) { mock = mock.substring(Constants.FORCE_PREFIX.length()).trim(); } // 如果是return或者throw,替换为" if (mock.startsWith(Constants.RETURN_PREFIX) || mock.startsWith(Constants.THROW_PREFIX)) { mock = mock.replace('’, ‘”’); } return mock;}该方法是规范化mock值。5.getThrowablepublic static Throwable getThrowable(String throwstr) { // 从异常集合中取出异常 Throwable throwable = throwables.get(throwstr); // 如果不为空,则抛出异常 if (throwable != null) { return throwable; } try { Throwable t; // 获得异常类 Class<?> bizException = ReflectUtils.forName(throwstr); Constructor<?> constructor; // 获得构造方法 constructor = ReflectUtils.findConstructor(bizException, String.class); // 创建 Throwable 对象 t = (Throwable) constructor.newInstance(new Object[]{“mocked exception for service degradation.”}); // 添加到缓存中 if (throwables.size() < 1000) { throwables.put(throwstr, t); } return t; } catch (Exception e) { throw new RpcException(“mock throw error :” + throwstr + " argument error.”, e); }}该方法是获得异常。6.getInvokerprivate Invoker<T> getInvoker(String mockService) { // 从缓存中,获得 Invoker 对象,如果有,直接缓存。 Invoker<T> invoker = (Invoker<T>) mocks.get(mockService); if (invoker != null) { return invoker; } // 获得服务类型 Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface()); // 获得MockObject T mockObject = (T) getMockObject(mockService, serviceType); // 创建invoker invoker = proxyFactory.getInvoker(mockObject, serviceType, url); if (mocks.size() < 10000) { // 加入集合 mocks.put(mockService, invoker); } return invoker;}该方法是获得invoker。7.getMockObjectpublic static Object getMockObject(String mockService, Class serviceType) { if (ConfigUtils.isDefault(mockService)) { mockService = serviceType.getName() + “Mock”; } // 获得类型 Class<?> mockClass = ReflectUtils.forName(mockService); if (!serviceType.isAssignableFrom(mockClass)) { throw new IllegalStateException(“The mock class " + mockClass.getName() + " not implement interface " + serviceType.getName()); } try { // 初始化 return mockClass.newInstance(); } catch (InstantiationException e) { throw new IllegalStateException(“No default constructor from mock class " + mockClass.getName(), e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); }}该方法是获得mock对象。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于mock实现的部分,到这里为止,集群部分就全部讲完了,这是2.6.x版本的集群,那在2.7中对于路由和配置规则都有相应的大改动,我会在之后2.7版本的讲解中讲到。接下来我将开始对序列化模块进行讲解。 ...

February 14, 2019 · 7 min · jiezi

dubbo源码解析(四十)集群——router

集群——router目标:介绍dubbo中集群的路由,介绍dubbo-cluster下router包的源码。前言路由规则 决定一次 dubbo 服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且支持可扩展 。源码分析(一)ConditionRouterFactorypublic class ConditionRouterFactory implements RouterFactory { public static final String NAME = “condition”; @Override public Router getRouter(URL url) { // 创建一个ConditionRouter return new ConditionRouter(url); }}该类是基于条件表达式规则路由工厂类。(二)ConditionRouter该类是基于条件表达式的路由实现类。关于给予条件表达式的路由规则,可以查看官方文档:官方文档地址:http://dubbo.apache.org/zh-cn…1.属性private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);/** * 分组正则匹配 /private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,])\s*([^&!=,\s]+)");/** * 路由规则 URL /private final URL url;/* * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 /private final int priority;/* * 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false 。 /private final boolean force;/* * 消费者匹配条件集合,通过解析【条件表达式 rule 的 =&gt; 之前半部分】 /private final Map<String, MatchPair> whenCondition;/* * 提供者地址列表的过滤条件,通过解析【条件表达式 rule 的 =&gt; 之后半部分】 /private final Map<String, MatchPair> thenCondition;2.构造方法public ConditionRouter(URL url) { this.url = url; // 获得优先级配置 this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); // 获得是否强制执行配置 this.force = url.getParameter(Constants.FORCE_KEY, false); try { // 获得规则 String rule = url.getParameterAndDecoded(Constants.RULE_KEY); if (rule == null || rule.trim().length() == 0) { throw new IllegalArgumentException(“Illegal route rule!”); } rule = rule.replace(“consumer.”, “”).replace(“provider.”, “”); int i = rule.indexOf("=>"); // 分割消费者和提供者规则 String whenRule = i < 0 ? null : rule.substring(0, i).trim(); String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim(); Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || “true”.equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule); Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || “false”.equals(thenRule) ? null : parseRule(thenRule); // NOTE: It should be determined on the business level whether the When condition can be empty or not. this.whenCondition = when; this.thenCondition = then; } catch (ParseException e) { throw new IllegalStateException(e.getMessage(), e); }}3.MatchPairprivate static final class MatchPair { /* * 匹配的值的集合 / final Set<String> matches = new HashSet<String>(); /* * 不匹配的值的集合 / final Set<String> mismatches = new HashSet<String>(); /* * 判断value是否匹配matches或者mismatches * @param value * @param param * @return / private boolean isMatch(String value, URL param) { // 只匹配 matches if (!matches.isEmpty() && mismatches.isEmpty()) { for (String match : matches) { if (UrlUtils.isMatchGlobPattern(match, value, param)) { // 匹配上了返回true return true; } } // 没匹配上则为false return false; } // 只匹配 mismatches if (!mismatches.isEmpty() && matches.isEmpty()) { for (String mismatch : mismatches) { if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) { // 如果匹配上了,则返回false return false; } } // 没匹配上,则为true return true; } // 匹配 matches和mismatches if (!matches.isEmpty() && !mismatches.isEmpty()) { //when both mismatches and matches contain the same value, then using mismatches first for (String mismatch : mismatches) { if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) { // 匹配上则为false return false; } } for (String match : matches) { if (UrlUtils.isMatchGlobPattern(match, value, param)) { // 匹配上则为true return true; } } return false; } return false; }}该类是内部类,封装了匹配的值,每个属性条件。并且提供了判断是否匹配的方法。4.parseRuleprivate static Map<String, MatchPair> parseRule(String rule) throws ParseException { Map<String, MatchPair> condition = new HashMap<String, MatchPair>(); // 如果规则为空,则直接返回空 if (StringUtils.isBlank(rule)) { return condition; } // Key-Value pair, stores both match and mismatch conditions MatchPair pair = null; // Multiple values Set<String> values = null; // 正则表达式匹配 final Matcher matcher = ROUTE_PATTERN.matcher(rule); // 一个一个匹配 while (matcher.find()) { // Try to match one by one String separator = matcher.group(1); String content = matcher.group(2); // Start part of the condition expression. // 开始条件表达式 if (separator == null || separator.length() == 0) { pair = new MatchPair(); // 保存条件 condition.put(content, pair); } // The KV part of the condition expression else if ("&".equals(separator)) { // 把参数的条件表达式放入condition if (condition.get(content) == null) { pair = new MatchPair(); condition.put(content, pair); } else { pair = condition.get(content); } } // The Value in the KV part. // 把值放入values else if ("=".equals(separator)) { if (pair == null) throw new ParseException(“Illegal route rule "” + rule + “", The error char ‘” + separator + “’ at index " + matcher.start() + " before "” + content + “".”, matcher.start()); values = pair.matches; values.add(content); } // The Value in the KV part. // 把不等于的条件限制也放入values else if ("!=".equals(separator)) { if (pair == null) throw new ParseException(“Illegal route rule "” + rule + “", The error char ‘” + separator + “’ at index " + matcher.start() + " before "” + content + “".”, matcher.start()); values = pair.mismatches; values.add(content); } // The Value in the KV part, if Value have more than one items. // 如果以.分隔的也放入values else if (",".equals(separator)) { // Should be seperateed by ‘,’ if (values == null || values.isEmpty()) throw new ParseException(“Illegal route rule "” + rule + “", The error char ‘” + separator + “’ at index " + matcher.start() + " before "” + content + “".”, matcher.start()); values.add(content); } else { throw new ParseException(“Illegal route rule "” + rule + “", The error char ‘” + separator + “’ at index " + matcher.start() + " before "” + content + “".”, matcher.start()); } } return condition;}该方法是根据规则解析路由配置内容。具体的可以参照官网的配置规则来解读这里每一个分割取值作为条件的过程。5.route@Overridepublic <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { // 为空,直接返回空 Invoker 集合 if (invokers == null || invokers.isEmpty()) { return invokers; } try { // 如果不匹配 whenCondition ,直接返回 invokers 集合,因为不需要走 whenThen 的匹配 if (!matchWhen(url, invocation)) { return invokers; } List<Invoker<T>> result = new ArrayList<Invoker<T>>(); // 如果thenCondition为空,则直接返回空 if (thenCondition == null) { logger.warn(“The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + “, service: " + url.getServiceKey()); return result; } // 遍历invokers for (Invoker<T> invoker : invokers) { // 如果thenCondition匹配,则加入result if (matchThen(invoker.getUrl(), url)) { result.add(invoker); } } if (!result.isEmpty()) { return result; } else if (force) { logger.warn(“The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + “, service: " + url.getServiceKey() + “, router: " + url.getParameterAndDecoded(Constants.RULE_KEY)); return result; } } catch (Throwable t) { logger.error(“Failed to execute condition router rule: " + getUrl() + “, invokers: " + invokers + “, cause: " + t.getMessage(), t); } return invokers;}该方法是进行路由规则的匹配,分别对消费者和提供者进行匹配。6.matchConditionprivate boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) { Map<String, String> sample = url.toMap(); // 是否匹配 boolean result = false; // 遍历条件 for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) { String key = matchPair.getKey(); String sampleValue; //get real invoked method name from invocation // 获得方法名 if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) { sampleValue = invocation.getMethodName(); } else { // sampleValue = sample.get(key); if (sampleValue == null) { sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key); } } if (sampleValue != null) { // 如果不匹配条件值,返回false if (!matchPair.getValue().isMatch(sampleValue, param)) { return false; } else { // 匹配则返回true result = true; } } else { //not pass the condition // 如果匹配的集合不为空 if (!matchPair.getValue().matches.isEmpty()) { // 返回false return false; } else { // 返回true result = true; } } } return result;}该方法是匹配条件的主要逻辑。(三)ScriptRouterFactory该类是基于脚本的路由规则工厂类。public class ScriptRouterFactory implements RouterFactory { public static final String NAME = “script”; @Override public Router getRouter(URL url) { // 创建ScriptRouter return new ScriptRouter(url); }}(四)ScriptRouter该类是基于脚本的路由实现类1.属性private static final Logger logger = LoggerFactory.getLogger(ScriptRouter.class);/* * 脚本类型 与 ScriptEngine 的映射缓存 /private static final Map<String, ScriptEngine> engines = new ConcurrentHashMap<String, ScriptEngine>();/* * 脚本 /private final ScriptEngine engine;/* * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 。 /private final int priority;/* * 路由规则 /private final String rule;/* * 路由规则 URL /private final URL url;2.route@Override@SuppressWarnings(“unchecked”)public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { try { List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers); Compilable compilable = (Compilable) engine; // 创建脚本 Bindings bindings = engine.createBindings(); // 设置invokers、invocation、context bindings.put(“invokers”, invokersCopy); bindings.put(“invocation”, invocation); bindings.put(“context”, RpcContext.getContext()); // 编译脚本 CompiledScript function = compilable.compile(rule); // 执行脚本 Object obj = function.eval(bindings); // 根据结果类型,转换成 (List<Invoker<T>> 类型返回 if (obj instanceof Invoker[]) { invokersCopy = Arrays.asList((Invoker<T>[]) obj); } else if (obj instanceof Object[]) { invokersCopy = new ArrayList<Invoker<T>>(); for (Object inv : (Object[]) obj) { invokersCopy.add((Invoker<T>) inv); } } else { invokersCopy = (List<Invoker<T>>) obj; } return invokersCopy; } catch (ScriptException e) { //fail then ignore rule .invokers. // 发生异常,忽略路由规则,返回全 invokers 集合 logger.error(“route error , rule has been ignored. rule: " + rule + “, method:” + invocation.getMethodName() + “, url: " + RpcContext.getContext().getUrl(), e); return invokers; }}该方法是根据路由规则选择invoker的实现逻辑。(五)FileRouterFactory该类是装饰者,对RouterFactory进行了功能增强,增加了从文件中读取规则。public class FileRouterFactory implements RouterFactory { public static final String NAME = “file”; /* * 路由工厂 */ private RouterFactory routerFactory; public void setRouterFactory(RouterFactory routerFactory) { this.routerFactory = routerFactory; } @Override public Router getRouter(URL url) { try { // Transform File URL into Script Route URL, and Load // file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=<file-content> // 获得 router 配置项,默认为 script String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // Replace original protocol (maybe ‘file’) with ‘script’ String type = null; // Use file suffix to config script type, e.g., js, groovy … // 获得path String path = url.getPath(); // 获得类型 if (path != null) { int i = path.lastIndexOf(’.’); if (i > 0) { type = path.substring(i + 1); } } // 读取规则 String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath()))); boolean runtime = url.getParameter(Constants.RUNTIME_KEY, false); // 获得脚本路由url URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type).addParameter(Constants.RUNTIME_KEY, runtime).addParameterAndEncoded(Constants.RULE_KEY, rule); // 获得路由 return routerFactory.getRouter(script); } catch (IOException e) { throw new IllegalStateException(e.getMessage(), e); } }}后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于路由规则实现的部分。接下来我将开始对集群模块关于Mock部分进行讲解。 ...

February 13, 2019 · 7 min · jiezi

dubbo源码解析(三十九)集群——merger

集群——merger目标:介绍dubbo中集群的分组聚合,介绍dubbo-cluster下merger包的源码。前言按组合并返回结果 ,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。这个时候就要用到分组聚合。源码分析(一)MergeableClusterpublic class MergeableCluster implements Cluster { public static final String NAME = “mergeable”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建MergeableClusterInvoker return new MergeableClusterInvoker<T>(directory); }}该类实现了Cluster接口,是分组集合的集群实现。(二)MergeableClusterInvoker该类是分组聚合的实现类,其中最关机的就是invoke方法。@Override@SuppressWarnings(“rawtypes”)public Result invoke(final Invocation invocation) throws RpcException { // 获得invoker集合 List<Invoker<T>> invokers = directory.list(invocation); /** * 获得是否merger / String merger = getUrl().getMethodParameter(invocation.getMethodName(), Constants.MERGER_KEY); // 如果没有设置需要聚合,则只调用一个invoker的 if (ConfigUtils.isEmpty(merger)) { // If a method doesn’t have a merger, only invoke one Group // 只要有一个可用就返回 for (final Invoker<T> invoker : invokers) { if (invoker.isAvailable()) { return invoker.invoke(invocation); } } return invokers.iterator().next().invoke(invocation); } // 返回类型 Class<?> returnType; try { // 获得返回类型 returnType = getInterface().getMethod( invocation.getMethodName(), invocation.getParameterTypes()).getReturnType(); } catch (NoSuchMethodException e) { returnType = null; } // 结果集合 Map<String, Future<Result>> results = new HashMap<String, Future<Result>>(); // 循环invokers for (final Invoker<T> invoker : invokers) { // 获得每次调用的future Future<Result> future = executor.submit(new Callable<Result>() { @Override public Result call() throws Exception { // 回调,把返回结果放入future return invoker.invoke(new RpcInvocation(invocation, invoker)); } }); // 加入集合 results.put(invoker.getUrl().getServiceKey(), future); } Object result = null; List<Result> resultList = new ArrayList<Result>(results.size()); // 获得超时时间 int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 遍历每一个结果 for (Map.Entry<String, Future<Result>> entry : results.entrySet()) { Future<Result> future = entry.getValue(); try { // 获得调用返回的结果 Result r = future.get(timeout, TimeUnit.MILLISECONDS); if (r.hasException()) { log.error(“Invoke " + getGroupDescFromServiceKey(entry.getKey()) + " failed: " + r.getException().getMessage(), r.getException()); } else { // 加入集合 resultList.add(r); } } catch (Exception e) { throw new RpcException(“Failed to invoke service " + entry.getKey() + “: " + e.getMessage(), e); } } // 如果为空,则返回空的结果 if (resultList.isEmpty()) { return new RpcResult((Object) null); } else if (resultList.size() == 1) { // 如果只有一个结果,则返回该结果 return resultList.iterator().next(); } // 如果返回类型是void,也就是没有返回值,那么返回空结果 if (returnType == void.class) { return new RpcResult((Object) null); } // 根据方法来合并,将调用返回结果的指定方法进行合并 if (merger.startsWith(”.”)) { merger = merger.substring(1); Method method; try { // 获得方法 method = returnType.getMethod(merger, returnType); } catch (NoSuchMethodException e) { throw new RpcException(“Can not merge result because missing method [ " + merger + " ] in class [ " + returnType.getClass().getName() + " ]”); } // 有 Method ,进行合并 if (!Modifier.isPublic(method.getModifiers())) { method.setAccessible(true); } // 从集合中移除 result = resultList.remove(0).getValue(); try { // 方法返回类型匹配,合并时,修改 result if (method.getReturnType() != void.class && method.getReturnType().isAssignableFrom(result.getClass())) { for (Result r : resultList) { result = method.invoke(result, r.getValue()); } } else { // 方法返回类型不匹配,合并时,不修改 result for (Result r : resultList) { method.invoke(result, r.getValue()); } } } catch (Exception e) { throw new RpcException(“Can not merge result: " + e.getMessage(), e); } } else { // 基于 Merger Merger resultMerger; // 如果是默认的方式 if (ConfigUtils.isDefault(merger)) { // 获得该类型的合并方式 resultMerger = MergerFactory.getMerger(returnType); } else { // 如果不是默认的,则配置中指定获得Merger的实现类 resultMerger = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(merger); } if (resultMerger != null) { List<Object> rets = new ArrayList<Object>(resultList.size()); // 遍历返回结果 for (Result r : resultList) { // 加入到rets rets.add(r.getValue()); } // 合并 result = resultMerger.merge( rets.toArray((Object[]) Array.newInstance(returnType, 0))); } else { throw new RpcException(“There is no merger to merge result.”); } } // 返回结果 return new RpcResult(result);}前面部分在讲获得调用的结果,后面部分是对结果的合并,合并有两种方式,根据配置不同可用分为基于方法的合并和基于merger的合并。(三)MergerFactoryMerger 工厂类,获得指定类型的Merger 对象。public class MergerFactory { /* * Merger 对象缓存 / private static final ConcurrentMap<Class<?>, Merger<?>> mergerCache = new ConcurrentHashMap<Class<?>, Merger<?>>(); /* * 获得指定类型的Merger对象 * @param returnType * @param <T> * @return / public static <T> Merger<T> getMerger(Class<T> returnType) { Merger result; // 如果类型是集合 if (returnType.isArray()) { // 获得类型 Class type = returnType.getComponentType(); // 从缓存中获得该类型的Merger对象 result = mergerCache.get(type); // 如果为空,则 if (result == null) { // 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。 loadMergers(); // 从集合中取出对应的Merger对象 result = mergerCache.get(type); } // 如果结果为空,则直接返回ArrayMerger的单例 if (result == null && !type.isPrimitive()) { result = ArrayMerger.INSTANCE; } } else { // 否则直接从mergerCache中取出 result = mergerCache.get(returnType); // 如果为空 if (result == null) { // 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。 loadMergers(); // 从集合中取出 result = mergerCache.get(returnType); } } return result; } /* * 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。 / static void loadMergers() { // 获得Merger所有的扩展对象名 Set<String> names = ExtensionLoader.getExtensionLoader(Merger.class) .getSupportedExtensions(); // 遍历 for (String name : names) { // 加载每一个扩展实现,然后放入缓存。 Merger m = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(name); mergerCache.putIfAbsent(ReflectUtils.getGenericClass(m.getClass()), m); } }}逻辑比较简单。(四)ArrayMerger因为不同的类型有不同的Merger实现,我们可以来看看这个图片:可以看到有好多好多,我就讲解其中的一种,偷懒一下,其他的麻烦有兴趣的去看看源码了。public class ArrayMerger implements Merger<Object[]> { /* * 单例 */ public static final ArrayMerger INSTANCE = new ArrayMerger(); @Override public Object[] merge(Object[]… others) { // 如果长度为0 则直接返回 if (others.length == 0) { return null; } // 总长 int totalLen = 0; // 遍历所有需要合并的对象 for (int i = 0; i < others.length; i++) { Object item = others[i]; // 如果为数组 if (item != null && item.getClass().isArray()) { // 累加数组长度 totalLen += Array.getLength(item); } else { throw new IllegalArgumentException((i + 1) + “th argument is not an array”); } } if (totalLen == 0) { return null; } // 获得数组类型 Class<?> type = others[0].getClass().getComponentType(); // 创建长度 Object result = Array.newInstance(type, totalLen); int index = 0; // 遍历需要合并的对象 for (Object array : others) { // 遍历每个数组中的数据 for (int i = 0; i < Array.getLength(array); i++) { // 加入到最终结果中 Array.set(result, index++, Array.get(array, i)); } } return (Object[]) result; }}是不是很简单,就是循环合并就可以了。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于分组聚合实现的部分。接下来我将开始对集群模块关于路由部分进行讲解。 ...

February 12, 2019 · 4 min · jiezi

dubbo源码解析(三十七)集群——directory

集群——directory目标:介绍dubbo中集群的目录,介绍dubbo-cluster下directory包的源码。前言我在前面的文章中也提到了Directory可以看成是多个Invoker的集合,Directory 的用途是保存 Invoker,其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Inovker,那在之前文章中我忽略了RegistryDirectory的源码分析,在本文中来补充。源码分析(一)AbstractDirectory该类实现了Directory接口,1.属性// loggerprivate static final Logger logger = LoggerFactory.getLogger(AbstractDirectory.class);/** * url对象 /private final URL url;/* * 是否销毁 /private volatile boolean destroyed = false;/* * 消费者端url /private volatile URL consumerUrl;/* * 路由集合 /private volatile List<Router> routers;2.list@Overridepublic List<Invoker<T>> list(Invocation invocation) throws RpcException { // 如果销毁,则抛出异常 if (destroyed) { throw new RpcException(“Directory already destroyed .url: " + getUrl()); } // 调用doList来获得Invoker集合 List<Invoker<T>> invokers = doList(invocation); // 获得路由集合 List<Router> localRouters = this.routers; // local reference if (localRouters != null && !localRouters.isEmpty()) { // 遍历路由 for (Router router : localRouters) { try { if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { // 根据路由规则选择符合规则的invoker集合 invokers = router.route(invokers, getConsumerUrl(), invocation); } } catch (Throwable t) { logger.error(“Failed to execute router: " + getUrl() + “, cause: " + t.getMessage(), t); } } } return invokers;}该方法是生成invoker集合的逻辑实现。其中doList是抽象方法,交由子类来实现。3.setRoutersprotected void setRouters(List<Router> routers) { // copy list // 复制路由集合 routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers); // append url router // 获得路由的配置 String routerkey = url.getParameter(Constants.ROUTER_KEY); if (routerkey != null && routerkey.length() > 0) { // 加载路由工厂 RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey); // 加入集合 routers.add(routerFactory.getRouter(url)); } // append mock invoker selector // 加入服务降级路由 routers.add(new MockInvokersSelector()); // 排序 Collections.sort(routers); this.routers = routers;}(二)StaticDirectory静态 Directory 实现类,将传入的 invokers 集合,封装成静态的 Directory 对象。public class StaticDirectory<T> extends AbstractDirectory<T> { private final List<Invoker<T>> invokers; public StaticDirectory(List<Invoker<T>> invokers) { this(null, invokers, null); } public StaticDirectory(List<Invoker<T>> invokers, List<Router> routers) { this(null, invokers, routers); } public StaticDirectory(URL url, List<Invoker<T>> invokers) { this(url, invokers, null); } public StaticDirectory(URL url, List<Invoker<T>> invokers, List<Router> routers) { super(url == null && invokers != null && !invokers.isEmpty() ? invokers.get(0).getUrl() : url, routers); if (invokers == null || invokers.isEmpty()) throw new IllegalArgumentException(“invokers == null”); this.invokers = invokers; } @Override public Class<T> getInterface() { return invokers.get(0).getInterface(); } @Override public boolean isAvailable() { if (isDestroyed()) { return false; } // 遍历invokers,如果有一个可用,则可用 for (Invoker<T> invoker : invokers) { if (invoker.isAvailable()) { return true; } } return false; } @Override public void destroy() { if (isDestroyed()) { return; } super.destroy(); // 遍历invokers,销毁所有的invoker for (Invoker<T> invoker : invokers) { invoker.destroy(); } // 清除集合 invokers.clear(); } @Override protected List<Invoker<T>> doList(Invocation invocation) throws RpcException { return invokers; }}该类我就不多讲解,比较简单。(三)RegistryDirectory该类继承了AbstractDirectory类,是基于注册中心的动态 Directory 实现类,会根据注册中心的推送变更 List<Invoker>1.属性private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class);/* * cluster实现类对象 /private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();/* * 路由工厂 /private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension();/* * 配置规则工厂 /private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension();/* * 服务key /private final String serviceKey; // Initialization at construction time, assertion not null/* * 服务类型 /private final Class<T> serviceType; // Initialization at construction time, assertion not null/* * 消费者URL的配置项 Map /private final Map<String, String> queryMap; // Initialization at construction time, assertion not null/* * 原始的目录 URL /private final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value/* * 服务方法集合 /private final String[] serviceMethods;/* * 是否使用多分组 /private final boolean multiGroup;/* * 协议 /private Protocol protocol; // Initialization at the time of injection, the assertion is not null/* * 注册中心 /private Registry registry; // Initialization at the time of injection, the assertion is not null/* * 是否禁止访问 /private volatile boolean forbidden = false;/* * 覆盖目录的url /private volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value/* * override rules * Priority: override>-D>consumer>provider * Rule one: for a certain provider <ip:port,timeout=100> * Rule two: for all providers <* ,timeout=5000> * 配置规则数组 /private volatile List<Configurator> configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference// Map<url, Invoker> cache service url to invoker mapping./* * url与服务提供者 Invoker 集合的映射缓存 /private volatile Map<String, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference// Map<methodName, Invoker> cache service method to invokers mapping./* * 方法名和服务提供者 Invoker 集合的映射缓存 /private volatile Map<String, List<Invoker<T>>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference// Set<invokerUrls> cache invokeUrls to invokers mapping./* * 服务提供者Invoker 集合缓存 */private volatile Set<URL> cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference2.toConfiguratorspublic static List<Configurator> toConfigurators(List<URL> urls) { // 如果为空,则返回空集合 if (urls == null || urls.isEmpty()) { return Collections.emptyList(); } List<Configurator> configurators = new ArrayList<Configurator>(urls.size()); // 遍历url集合 for (URL url : urls) { //如果是协议是empty的值,则清空配置集合 if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { configurators.clear(); break; } // 覆盖的参数集合 Map<String, String> override = new HashMap<String, String>(url.getParameters()); //The anyhost parameter of override may be added automatically, it can’t change the judgement of changing url // 覆盖的anyhost参数可以自动添加,也不能改变更改url的判断 override.remove(Constants.ANYHOST_KEY); // 如果需要覆盖添加的值为0,则清空配置 if (override.size() == 0) { configurators.clear(); continue; } // 加入配置规则集合 configurators.add(configuratorFactory.getConfigurator(url)); } // 排序 Collections.sort(configurators); return configurators;}该方法是处理配置规则url集合,转换覆盖url映射以便在重新引用时使用,每次发送所有规则,网址将被重新组装和计算。3.destroy@Overridepublic void destroy() { // 如果销毁了,则返回 if (isDestroyed()) { return; } // unsubscribe. try { if (getConsumerUrl() != null && registry != null && registry.isAvailable()) { // 取消订阅 registry.unsubscribe(getConsumerUrl(), this); } } catch (Throwable t) { logger.warn(“unexpeced error when unsubscribe service " + serviceKey + “from registry” + registry.getUrl(), t); } super.destroy(); // must be executed after unsubscribing try { // 清空所有的invoker destroyAllInvokers(); } catch (Throwable t) { logger.warn(“Failed to destroy service " + serviceKey, t); }}该方法是销毁方法。4.destroyAllInvokersprivate void destroyAllInvokers() { Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference // 如果invoker集合不为空 if (localUrlInvokerMap != null) { // 遍历 for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) { try { // 销毁invoker invoker.destroy(); } catch (Throwable t) { logger.warn(“Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t); } } // 清空集合 localUrlInvokerMap.clear(); } methodInvokerMap = null;}该方法是关闭所有的invoker服务。5.notify@Overridepublic synchronized void notify(List<URL> urls) { List<URL> invokerUrls = new ArrayList<URL>(); List<URL> routerUrls = new ArrayList<URL>(); List<URL> configuratorUrls = new ArrayList<URL>(); // 遍历url for (URL url : urls) { // 获得协议 String protocol = url.getProtocol(); // 获得类别 String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); // 如果是路由规则 if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { // 则在路由规则集合中加入 routerUrls.add(url); } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { // 如果是配置规则,则加入配置规则集合 configuratorUrls.add(url); } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { // 如果是服务提供者,则加入服务提供者集合 invokerUrls.add(url); } else { logger.warn(“Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); } } // configurators if (configuratorUrls != null && !configuratorUrls.isEmpty()) { // 处理配置规则url集合 this.configurators = toConfigurators(configuratorUrls); } // routers if (routerUrls != null && !routerUrls.isEmpty()) { // 处理路由规则 URL 集合 List<Router> routers = toRouters(routerUrls); if (routers != null) { // null - do nothing // 并且设置路由集合 setRouters(routers); } } List<Configurator> localConfigurators = this.configurators; // local reference // merge override parameters this.overrideDirectoryUrl = directoryUrl; if (localConfigurators != null && !localConfigurators.isEmpty()) { // 遍历配置规则集合 逐个进行配置 for (Configurator configurator : localConfigurators) { this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // providers // 处理服务提供者 URL 集合 refreshInvoker(invokerUrls);}当服务有变化的时候,执行该方法。首先将url根据路由规则、服务提供者和配置规则三种类型分开,分别放入三个集合,然后对每个集合进行修改或者通知6.refreshInvokerprivate void refreshInvoker(List<URL> invokerUrls) { if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { // 设置禁止访问 this.forbidden = true; // Forbid to access // methodInvokerMap 置空 this.methodInvokerMap = null; // Set the method invoker map to null // 关闭所有的invoker destroyAllInvokers(); // Close all invokers } else { // 关闭禁止访问 this.forbidden = false; // Allow to access // 引用老的 urlInvokerMap Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference // 传入的 invokerUrls 为空,说明是路由规则或配置规则发生改变,此时 invokerUrls 是空的,直接使用 cachedInvokerUrls 。 if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { invokerUrls.addAll(this.cachedInvokerUrls); } else { // 否则把所有的invokerUrls加入缓存 this.cachedInvokerUrls = new HashSet<URL>(); this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison } // 如果invokerUrls为空,则直接返回 if (invokerUrls.isEmpty()) { return; } // 将传入的 invokerUrls ,转成新的 urlInvokerMap Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map // 转换出新的 methodInvokerMap Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map // state change // If the calculation is wrong, it is not processed. // 如果为空,则打印错误日志并且返回 if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { logger.error(new IllegalStateException(“urls to invokers error .invokerUrls.size :” + invokerUrls.size() + “, invoker.size :0. urls :” + invokerUrls.toString())); return; } // 若服务引用多 group ,则按照 method + group 聚合 Invoker 集合 this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; this.urlInvokerMap = newUrlInvokerMap; try { // 销毁不再使用的 Invoker 集合 destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker } catch (Exception e) { logger.warn(“destroyUnusedInvokers error. “, e); } }}该方法是处理服务提供者 URL 集合。根据 invokerURL 列表转换为 invoker 列表。转换规则如下:如果 url 已经被转换为 invoker ,则不在重新引用,直接从缓存中获取,注意如果 url 中任何一个参数变更也会重新引用。如果传入的 invoker 列表不为空,则表示最新的 invoker 列表。如果传入的 invokerUrl 列表是空,则表示只是下发的 override 规则或 route 规则,需要重新交叉对比,决定是否需要重新引用。7.toMergeMethodInvokerMapprivate Map<String, List<Invoker<T>>> toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) { // 循环方法,按照 method + group 聚合 Invoker 集合 Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>(); // 遍历方法集合 for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) { // 获得方法 String method = entry.getKey(); // 获得invoker集合 List<Invoker<T>> invokers = entry.getValue(); // 获得组集合 Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>(); // 遍历invoker集合 for (Invoker<T> invoker : invokers) { // 获得url携带的组配置 String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, “”); // 获得该组对应的invoker集合 List<Invoker<T>> groupInvokers = groupMap.get(group); // 如果为空,则新创建一个,然后加入集合 if (groupInvokers == null) { groupInvokers = new ArrayList<Invoker<T>>(); groupMap.put(group, groupInvokers); } groupInvokers.add(invoker); } // 如果只有一个组 if (groupMap.size() == 1) { // 返回该组的invoker集合 result.put(method, groupMap.values().iterator().next()); } else if (groupMap.size() > 1) { // 如果不止一个组 List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>(); // 遍历组 for (List<Invoker<T>> groupList : groupMap.values()) { // 每次从集群中选择一个invoker加入groupInvokers groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList))); } // 加入需要返回的集合 result.put(method, groupInvokers); } else { result.put(method, invokers); } } return result;}该方法是通过按照 method + group 来聚合 Invoker 集合。8.toRoutersprivate List<Router> toRouters(List<URL> urls) { List<Router> routers = new ArrayList<Router>(); // 如果为空,则直接返回空集合 if (urls == null || urls.isEmpty()) { return routers; } if (urls != null && !urls.isEmpty()) { // 遍历url集合 for (URL url : urls) { // 如果为empty协议,则直接跳过 if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { continue; } // 获得路由规则 String routerType = url.getParameter(Constants.ROUTER_KEY); if (routerType != null && routerType.length() > 0) { // 设置协议 url = url.setProtocol(routerType); } try { // 获得路由 Router router = routerFactory.getRouter(url); if (!routers.contains(router)) // 加入集合 routers.add(router); } catch (Throwable t) { logger.error(“convert router url to router error, url: " + url, t); } } } return routers;}该方法是对url集合进行路由的解析,返回路由集合。9.toInvokersprivate Map<String, Invoker<T>> toInvokers(List<URL> urls) { Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>(); // 如果为空,则返回空集合 if (urls == null || urls.isEmpty()) { return newUrlInvokerMap; } Set<String> keys = new HashSet<String>(); // 获得引用服务的协议 String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); // 遍历url for (URL providerUrl : urls) { // If protocol is configured at the reference side, only the matching protocol is selected // 如果在参考侧配置协议,则仅选择匹配协议 if (queryProtocols != null && queryProtocols.length() > 0) { boolean accept = false; // 分割协议 String[] acceptProtocols = queryProtocols.split(”,”); // 遍历协议 for (String acceptProtocol : acceptProtocols) { // 如果匹配,则是接受的协议 if (providerUrl.getProtocol().equals(acceptProtocol)) { accept = true; break; } } if (!accept) { continue; } } // 如果协议是empty,则跳过 if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { continue; } // 如果该协议不是dubbo支持的,则打印错误日志,跳过 if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { logger.error(new IllegalStateException(“Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost() + “, supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions())); continue; } // 合并url参数 URL url = mergeUrl(providerUrl); String key = url.toFullString(); // The parameter urls are sorted if (keys.contains(key)) { // Repeated url continue; } // 添加到keys keys.add(key); // Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again // 如果服务端 URL 发生变化,则重新 refer 引用 Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key); if (invoker == null) { // Not in the cache, refer again try { // 判断是否开启 boolean enabled = true; // 获得enabled配置 if (url.hasParameter(Constants.DISABLED_KEY)) { enabled = !url.getParameter(Constants.DISABLED_KEY, false); } else { enabled = url.getParameter(Constants.ENABLED_KEY, true); } // 若开启,创建 Invoker 对象 if (enabled) { invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl); } } catch (Throwable t) { logger.error(“Failed to refer invoker for interface:” + serviceType + “,url:(” + url + “)” + t.getMessage(), t); } // 添加到 newUrlInvokerMap 中 if (invoker != null) { // Put new invoker in cache newUrlInvokerMap.put(key, invoker); } } else { newUrlInvokerMap.put(key, invoker); } } // 清空 keys keys.clear(); return newUrlInvokerMap;}该方法是将url转换为调用者,如果url已被引用,则不会重新引用。10.mergeUrlprivate URL mergeUrl(URL providerUrl) { // 合并消费端参数 providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters // 合并配置规则 List<Configurator> localConfigurators = this.configurators; // local reference if (localConfigurators != null && !localConfigurators.isEmpty()) { for (Configurator configurator : localConfigurators) { providerUrl = configurator.configure(providerUrl); } } // 不检查连接是否成功,总是创建 Invoker providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker! // The combination of directoryUrl and override is at the end of notify, which can’t be handled here // 合并提供者参数,因为 directoryUrl 与 override 合并是在 notify 的最后,这里不能够处理 this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters // 1.0版本兼容 if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0) && “dubbo”.equals(providerUrl.getProtocol())) { // Compatible version 1.0 //fix by tony.chenl DUBBO-44 String path = directoryUrl.getParameter(Constants.INTERFACE_KEY); if (path != null) { int i = path.indexOf(’/’); if (i >= 0) { path = path.substring(i + 1); } i = path.lastIndexOf(’:’); if (i >= 0) { path = path.substring(0, i); } providerUrl = providerUrl.setPath(path); } } return providerUrl;}该方法是合并 URL 参数,优先级为配置规则 > 服务消费者配置 > 服务提供者配置.11.toMethodInvokersprivate Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) { Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>(); // According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>(); if (invokersMap != null && invokersMap.size() > 0) { // 遍历调用者列表 for (Invoker<T> invoker : invokersMap.values()) { String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); // 按服务提供者 URL 所声明的 methods 分类 if (parameter != null && parameter.length() > 0) { // 分割参数得到方法集合 String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); if (methods != null && methods.length > 0) { // 遍历方法集合 for (String method : methods) { if (method != null && method.length() > 0 && !Constants.ANY_VALUE.equals(method)) { // 获得该方法对应的invoker,如果为空,则创建 List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null) { methodInvokers = new ArrayList<Invoker<T>>(); newMethodInvokerMap.put(method, methodInvokers); } methodInvokers.add(invoker); } } } } invokersList.add(invoker); } } // 根据路由规则,匹配合适的 Invoker 集合。 List<Invoker<T>> newInvokersList = route(invokersList, null); // 添加 newInvokersList 到 newMethodInvokerMap 中,表示该服务提供者的全量 Invoker 集合 newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); if (serviceMethods != null && serviceMethods.length > 0) { // 循环方法,获得每个方法路由匹配的invoker集合 for (String method : serviceMethods) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null || methodInvokers.isEmpty()) { methodInvokers = newInvokersList; } newMethodInvokerMap.put(method, route(methodInvokers, method)); } } // sort and unmodifiable // 循环排序每个方法的 Invoker 集合,排序 for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) { List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); Collections.sort(methodInvokers, InvokerComparator.getComparator()); newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); } // 设置为不可变 return Collections.unmodifiableMap(newMethodInvokerMap);}该方法是将调用者列表转换为与方法的映射关系。12.destroyUnusedInvokersprivate void destroyUnusedInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, Map<String, Invoker<T>> newUrlInvokerMap) { if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { destroyAllInvokers(); return; } // check deleted invoker // 记录已经删除的invoker List<String> deleted = null; if (oldUrlInvokerMap != null) { Collection<Invoker<T>> newInvokers = newUrlInvokerMap.values(); // 遍历旧的invoker集合 for (Map.Entry<String, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) { if (!newInvokers.contains(entry.getValue())) { if (deleted == null) { deleted = new ArrayList<String>(); } // 加入该invoker deleted.add(entry.getKey()); } } } if (deleted != null) { // 遍历需要删除的invoker url集合 for (String url : deleted) { if (url != null) { // 移除该url Invoker<T> invoker = oldUrlInvokerMap.remove(url); if (invoker != null) { try { // 销毁invoker invoker.destroy(); if (logger.isDebugEnabled()) { logger.debug(“destroy invoker[” + invoker.getUrl() + “] success. “); } } catch (Exception e) { logger.warn(“destroy invoker[” + invoker.getUrl() + “] faild. " + e.getMessage(), e); } } } } }}该方法是销毁不再使用的 Invoker 集合。13.doList@Overridepublic List<Invoker<T>> doList(Invocation invocation) { // 如果禁止访问,则抛出异常 if (forbidden) { // 1. No service provider 2. Service providers are disabled throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, “No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + “, please check status of providers(disabled, not registered or in blacklist).”); } List<Invoker<T>> invokers = null; Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { // 获得方法名 String methodName = RpcUtils.getMethodName(invocation); // 获得参数名 Object[] args = RpcUtils.getArguments(invocation); if (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) { // 根据第一个参数枚举路由 invokers = localMethodInvokerMap.get(methodName + “.” + args[0]); // The routing can be enumerated according to the first parameter } if (invokers == null) { // 根据方法名获得 Invoker 集合 invokers = localMethodInvokerMap.get(methodName); } if (invokers == null) { // 使用全量 Invoker 集合。例如,#$echo(name) ,回声方法 invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); } if (invokers == null) { // 使用 methodInvokerMap 第一个 Invoker 集合。防御性编程。 Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator(); if (iterator.hasNext()) { invokers = iterator.next(); } } } return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;}该方法是通过会话域来获得Invoker集合。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于directory实现的部分,关键是RegistryDirectory,其中涉及到众多方法,需要好好品味。接下来我将开始对集群模块关于loadbalance部分进行讲解。 ...

February 6, 2019 · 13 min · jiezi

dubbo源码解析(三十六)集群——configurator

集群——configurator目标:介绍dubbo中集群的配置规则,介绍dubbo-cluster下configurator包的源码。前言向注册中心写入动态配置覆盖规则 。该功能通常由监控中心或治理中心的页面完成。在最新的2.7.0版本中有新的配置规则,我会在后续讲解2.7.0新特性的时候提到。这里还是根据旧版本中配置规则来讲解,可以参考官方文档:http://dubbo.apache.org/zh-cn…源码分析(一)AbstractConfigurator该类实现了Configurator接口,是配置规则 抽象类,配置有两种方式,一种是没有时添加配置,这种暂时没有用到,另一种是覆盖配置。1.configure@Overridepublic URL configure(URL url) { if (configuratorUrl == null || configuratorUrl.getHost() == null || url == null || url.getHost() == null) { return url; } // If override url has port, means it is a provider address. We want to control a specific provider with this override url, it may take effect on the specific provider instance or on consumers holding this provider instance. // 如果覆盖url具有端口,则表示它是提供者地址。我们希望使用此覆盖URL控制特定提供程序,它可以在提供端生效 也可以在消费端生效。 if (configuratorUrl.getPort() != 0) { if (url.getPort() == configuratorUrl.getPort()) { return configureIfMatch(url.getHost(), url); } } else {// override url don’t have a port, means the ip override url specify is a consumer address or 0.0.0.0 // 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore; // 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider // 配置规则,URL 没有端口,意味着override 输入消费端地址 或者 0.0.0.0 if (url.getParameter(Constants.SIDE_KEY, Constants.PROVIDER).equals(Constants.CONSUMER)) { // 如果它是一个消费者ip地址,目的是控制一个特定的消费者实例,它必须在消费者一方生效,任何提供者收到这个覆盖url应该忽略; return configureIfMatch(NetUtils.getLocalHost(), url);// NetUtils.getLocalHost is the ip address consumer registered to registry. } else if (url.getParameter(Constants.SIDE_KEY, Constants.CONSUMER).equals(Constants.PROVIDER)) { // 如果ip为0.0.0.0,则此覆盖url可以在使用者上使用,也可以在提供者上使用 return configureIfMatch(Constants.ANYHOST_VALUE, url);// take effect on all providers, so address must be 0.0.0.0, otherwise it won’t flow to this if branch } } return url;}该方法是规则配置到URL中,但是关键逻辑在configureIfMatch方法中。2.configureIfMatchprivate URL configureIfMatch(String host, URL url) { // 匹配 Host if (Constants.ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) { String configApplication = configuratorUrl.getParameter(Constants.APPLICATION_KEY, configuratorUrl.getUsername()); String currentApplication = url.getParameter(Constants.APPLICATION_KEY, url.getUsername()); // 匹配 “application” if (configApplication == null || Constants.ANY_VALUE.equals(configApplication) || configApplication.equals(currentApplication)) { Set<String> conditionKeys = new HashSet<String>(); // 配置 URL 中的条件 KEYS 集合。其中下面四个 KEY ,不算是条件,而是内置属性。考虑到下面要移除,所以添加到该集合中。 conditionKeys.add(Constants.CATEGORY_KEY); conditionKeys.add(Constants.CHECK_KEY); conditionKeys.add(Constants.DYNAMIC_KEY); conditionKeys.add(Constants.ENABLED_KEY); // 判断传入的 url 是否匹配配置规则 URL 的条件。 for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 除了 “application” 和 “side” 之外,带有 "~" 开头的 KEY ,也是条件。 if (key.startsWith("") || Constants.APPLICATION_KEY.equals(key) || Constants.SIDE_KEY.equals(key)) { // 添加搭配条件集合 conditionKeys.add(key); if (value != null && !Constants.ANY_VALUE.equals(value) && !value.equals(url.getParameter(key.startsWith("") ? key.substring(1) : key))) { return url; } } } // 移除条件 KEYS 集合,并配置到 URL 中 return doConfigure(url, configuratorUrl.removeParameters(conditionKeys)); } } return url;}该方法是当条件匹配时,才对url进行配置。3.compareTo@Overridepublic int compareTo(Configurator o) { if (o == null) { return -1; } // // host 升序 int ipCompare = getUrl().getHost().compareTo(o.getUrl().getHost()); // 如果host相同,则根据priority降序来对比 if (ipCompare == 0) {//host is the same, sort by priority int i = getUrl().getParameter(Constants.PRIORITY_KEY, 0), j = o.getUrl().getParameter(Constants.PRIORITY_KEY, 0); return i < j ? -1 : (i == j ? 0 : 1); } else { return ipCompare; }}这是配置的排序策略。先根据host升序,如果相同,再通过priority降序。(二)AbsentConfiguratorpublic class AbsentConfigurator extends AbstractConfigurator { public AbsentConfigurator(URL url) { super(url); } @Override public URL doConfigure(URL currentUrl, URL configUrl) { // 当不存在时添加 return currentUrl.addParametersIfAbsent(configUrl.getParameters()); }}该配置方式就是当配置不存在的时候添加。(三)AbsentConfiguratorFactorypublic class AbsentConfiguratorFactory implements ConfiguratorFactory { @Override public Configurator getConfigurator(URL url) { // 创建一个AbsentConfigurator。 return new AbsentConfigurator(url); }}该类是不存在时添加配置的工厂类,用来创建AbsentConfigurator。(四)OverrideConfiguratorpublic class OverrideConfigurator extends AbstractConfigurator { public OverrideConfigurator(URL url) { super(url); } @Override public URL doConfigure(URL currentUrl, URL configUrl) { // 覆盖添加 return currentUrl.addParameters(configUrl.getParameters()); }}这种是覆盖添加。是目前在用的配置方式。(五)OverrideConfiguratorFactorypublic class OverrideConfiguratorFactory implements ConfiguratorFactory { @Override public Configurator getConfigurator(URL url) { // 创建OverrideConfigurator return new OverrideConfigurator(url); }}该类是OverrideConfigurator的工厂类,用来提供OverrideConfigurator实例。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于configurator实现的部分,讲了两种配置方式,分别是不存在再添加和覆盖添加。接下来我将开始对集群模块关于Directory部分进行讲解。 ...

February 5, 2019 · 3 min · jiezi

dubbo源码解析(三十五)集群——cluster

远程调用——cluster目标:介绍dubbo中集群容错的几种模式,介绍dubbo-cluster下support包的源码。前言集群容错还是很好理解的,就是当你调用失败的时候所作出的措施。先来看看有哪些模式:图有点小,见谅,不过可以眯着眼睛看稍微能看出来一点,每一个Cluster实现类都对应着一个invoker,因为这个模式启用的时间点就是在调用的时候,而我在之前的文章里面讲过,invoker贯穿来整个服务的调用。不过这里除了调用失败的一些模式外,还有几个特别的模式,他们应该说成是失败的措施,而已调用的方式。Failsafe Cluster:失败安全,出现异常时,直接忽略。失败安全就是当调用过程中出现异常时,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作Failover Cluster:失败自动切换,当调用出现失败的时候,会自动切换集群中其他服务器,来获得invoker重试,通常用于读操作,但重试会带来更长延迟。一般都会设置重试次数。Failfast Cluster:只会进行一次调用,失败后立即抛出异常。适用于幂等操作,比如新增记录。Failback Cluster:失败自动恢复,在调用失败后,返回一个空结果给服务提供者。并通过定时任务对失败的调用记录并且重传,适合执行消息通知等操作。Forking Cluster:会在线程池中运行多个线程,来调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。一般会设置最大并行数。Available Cluster:调用第一个可用的服务器,仅仅应用于多注册中心。Broadcast Cluster:广播调用所有提供者,逐个调用,在循环调用结束后,只要任意一台报错就报错。通常用于通知所有提供者更新缓存或日志等本地资源信息Mergeable Cluster:该部分在分组聚合讲述。MockClusterWrapper:该部分在本地伪装讲述。源码分析(一)AbstractClusterInvoker该类实现了Invoker接口,是集群Invoker的抽象类。1.属性private static final Logger logger = LoggerFactory .getLogger(AbstractClusterInvoker.class);/** * 目录,包含多个invoker /protected final Directory<T> directory;/* * 是否需要核对可用 /protected final boolean availablecheck;/* * 是否销毁 /private AtomicBoolean destroyed = new AtomicBoolean(false);/* * 粘滞连接的Invoker /private volatile Invoker<T> stickyInvoker = null;2.selectprotected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException { // 如果invokers为空,则返回null if (invokers == null || invokers.isEmpty()) return null; // 获得方法名 String methodName = invocation == null ? "" : invocation.getMethodName(); // 是否启动了粘滞连接 boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY); { //ignore overloaded method // 如果上一次粘滞连接的调用不在可选的提供者列合内,则直接设置为空 if (stickyInvoker != null && !invokers.contains(stickyInvoker)) { stickyInvoker = null; } //ignore concurrency problem // stickyInvoker不为null,并且没在已选列表中,返回上次的服务提供者stickyInvoker,但之前强制校验可达性。 // 由于stickyInvoker不能包含在selected列表中,通过代码看,可以得知forking和failover集群策略,用不了sticky属性 if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) { if (availablecheck && stickyInvoker.isAvailable()) { return stickyInvoker; } } } // 利用负载均衡选一个提供者 Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected); // 如果启动粘滞连接,则记录这一次的调用 if (sticky) { stickyInvoker = invoker; } return invoker;}该方法实现了使用负载均衡策略选择一个调用者。首先,使用loadbalance选择一个调用者。如果此调用者位于先前选择的列表中,或者如果此调用者不可用,则重新选择,否则返回第一个选定的调用者。重新选择,重选的验证规则:选择>可用。这条规则可以保证所选的调用者最少有机会成为之前选择的列表中的一个,也是保证这个调用程序可用。3.doSelectprivate Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException { if (invokers == null || invokers.isEmpty()) return null; // 如果只有一个 ,就直接返回这个 if (invokers.size() == 1) return invokers.get(0); // 如果没有指定用哪个负载均衡策略,则默认用随机负载均衡策略 if (loadbalance == null) { loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE); } // 调用负载均衡选择 Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation); //If the invoker is in the selected or invoker is unavailable && availablecheck is true, reselect. // 如果选择的提供者,已在selected中或者不可用则重新选择 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. // 如果重新选择失败,看下第一次选的位置,如果不是最后,选+1位置. int index = invokers.indexOf(invoker); try { //Avoid collision // 最后再避免选择到同一个invoker invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0); } 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;}该方法是用负载均衡选择一个invoker的主要逻辑。4.reselectprivate Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck) throws RpcException { //Allocating one in advance, this list is certain to be used. //预先分配一个重选列表,这个列表是一定会用到的. List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size()); //First, try picking a invoker not in selected. //先从非select中选 //把不包含在selected中的提供者,放入重选列表reselectInvokers,让负载均衡器选择 if (availablecheck) { // invoker.isAvailable() should be checked for (Invoker<T> invoker : invokers) { if (invoker.isAvailable()) { if (selected == null || !selected.contains(invoker)) { reselectInvokers.add(invoker); } } } // 在重选列表中用负载均衡器选择 if (!reselectInvokers.isEmpty()) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } } else { // do not check invoker.isAvailable() // 不核对服务是否可以,把不包含在selected中的提供者,放入重选列表reselectInvokers,让负载均衡器选择 for (Invoker<T> invoker : invokers) { if (selected == null || !selected.contains(invoker)) { reselectInvokers.add(invoker); } } if (!reselectInvokers.isEmpty()) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } } // Just pick an available invoker using loadbalance policy { // 如果非selected的列表中没有选择到,则从selected中选择 if (selected != null) { for (Invoker<T> invoker : selected) { if ((invoker.isAvailable()) // available first && !reselectInvokers.contains(invoker)) { reselectInvokers.add(invoker); } } } if (!reselectInvokers.isEmpty()) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } } return null;}该方法是是重新选择的逻辑实现。5.invoke@Overridepublic Result invoke(final Invocation invocation) throws RpcException { // 核对是否已经销毁 checkWhetherDestroyed(); LoadBalance loadbalance = null; // binding attachments into invocation. // 获得上下文的附加值 Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); // 把附加值放入到会话域中 if (contextAttachments != null && contextAttachments.size() != 0) { ((RpcInvocation) invocation).addAttachments(contextAttachments); } // 生成服务提供者集合 List<Invoker<T>> invokers = list(invocation); if (invokers != null && !invokers.isEmpty()) { // 获得负载均衡器 loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl() .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE)); } RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); return doInvoke(invocation, invokers, loadbalance);}该方法是invoker接口必备的方法,调用链的逻辑,不过主要的逻辑在doInvoke方法中,该方法是该类的抽象方法,让子类只关注doInvoke方法。6.listprotected List<Invoker<T>> list(Invocation invocation) throws RpcException { // 把会话域中的invoker加入集合 List<Invoker<T>> invokers = directory.list(invocation); return invokers;}该方法是调用了directory的list方法,从会话域中获得所有的Invoker集合。关于directory我会在后续文章讲解。(二)AvailableClusterpublic class AvailableCluster implements Cluster { public static final String NAME = “available”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建一个AbstractClusterInvoker return new AbstractClusterInvoker<T>(directory) { @Override public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { // 遍历所有的involer,只要有一个可用就直接调用。 for (Invoker<T> invoker : invokers) { if (invoker.isAvailable()) { return invoker.invoke(invocation); } } throw new RpcException(“No provider available in " + invokers); } }; }}Available Cluster我在上面已经讲过了,只要找到一个可用的,则直接调用。(三)BroadcastClusterpublic class BroadcastCluster implements Cluster { @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建一个BroadcastClusterInvoker return new BroadcastClusterInvoker<T>(directory); }}关键实现在于BroadcastClusterInvoker。(四)BroadcastClusterInvokerpublic class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class); public BroadcastClusterInvoker(Directory<T> directory) { super(directory); } @Override @SuppressWarnings({“unchecked”, “rawtypes”}) public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { // 检测invokers是否为空 checkInvokers(invokers, invocation); // 把invokers放到上下文 RpcContext.getContext().setInvokers((List) invokers); RpcException exception = null; Result result = null; // 遍历invokers,逐个调用,在循环调用结束后,只要任意一台报错就报错 for (Invoker<T> invoker : invokers) { try { result = invoker.invoke(invocation); } catch (RpcException e) { exception = e; logger.warn(e.getMessage(), e); } catch (Throwable e) { exception = new RpcException(e.getMessage(), e); logger.warn(e.getMessage(), e); } } if (exception != null) { throw exception; } return result; }}(五)ForkingClusterpublic class ForkingCluster implements Cluster { public final static String NAME = “forking”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建ForkingClusterInvoker return new ForkingClusterInvoker<T>(directory); }}(六)ForkingClusterInvokerpublic class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> { /* * 线程池 * Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread} * which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}. / private final ExecutorService executor = Executors.newCachedThreadPool( new NamedInternalThreadFactory(“forking-cluster-timer”, true)); public ForkingClusterInvoker(Directory<T> directory) { super(directory); } @Override @SuppressWarnings({“unchecked”, “rawtypes”}) public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { try { // 检测invokers是否为空 checkInvokers(invokers, invocation); final List<Invoker<T>> selected; // 获取 forks 配置 final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS); // 获取超时配置 final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 如果 forks 配置不合理,则直接将 invokers 赋值给 selected if (forks <= 0 || forks >= invokers.size()) { selected = invokers; } else { selected = new ArrayList<Invoker<T>>(); // 循环选出 forks 个 Invoker,并添加到 selected 中 for (int i = 0; i < forks; i++) { // TODO. Add some comment here, refer chinese version for more details. // 选择 Invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, selected); if (!selected.contains(invoker)) {//Avoid add the same invoker several times. // 加入到selected集合 selected.add(invoker); } } } // 放入上下文 RpcContext.getContext().setInvokers((List) selected); final AtomicInteger count = new AtomicInteger(); final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>(); // 遍历 selected 列表 for (final Invoker<T> invoker : selected) { // 为每个 Invoker 创建一个执行线程 executor.execute(new Runnable() { @Override public void run() { try { // 进行远程调用 Result result = invoker.invoke(invocation); // 将结果存到阻塞队列中 ref.offer(result); } catch (Throwable e) { // 仅在 value 大于等于 selected.size() 时,才将异常对象 // 为了防止异常现象覆盖正常的结果 int value = count.incrementAndGet(); if (value >= selected.size()) { // 将异常对象存入到阻塞队列中 ref.offer(e); } } } }); } try { // 从阻塞队列中取出远程调用结果 Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS); // 如果是异常,则抛出 if (ret instanceof Throwable) { Throwable e = (Throwable) ret; throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, “Failed to forking invoke provider " + selected + “, but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } return (Result) ret; } catch (InterruptedException e) { throw new RpcException(“Failed to forking invoke provider " + selected + “, but no luck to perform the invocation. Last error is: " + e.getMessage(), e); } } finally { // clear attachments which is binding to current thread. RpcContext.getContext().clearAttachments(); } }}(七)FailbackClusterpublic class FailbackCluster implements Cluster { public final static String NAME = “failback”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建一个FailbackClusterInvoker return new FailbackClusterInvoker<T>(directory); }}(八)FailbackClusterInvoker1.属性private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);// 重试间隔private static final long RETRY_FAILED_PERIOD = 5 * 1000;/* * 定时器 * Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread} * which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}. /private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2, new NamedInternalThreadFactory(“failback-cluster-timer”, true));/* * 失败集合 /private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();/* * future */private volatile ScheduledFuture<?> retryFuture;2.doInvoke@Overrideprotected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { try { // 检测invokers是否为空 checkInvokers(invokers, invocation); // 选择出invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, null); // 调用 return invoker.invoke(invocation); } catch (Throwable e) { logger.error(“Failback to invoke method " + invocation.getMethodName() + “, wait for retry in background. Ignored exception: " + e.getMessage() + “, “, e); // 如果失败,则加入到失败队列,等待重试 addFailed(invocation, this); return new RpcResult(); // ignore }}该方法是选择invoker调用的逻辑,在抛出异常的时候,做了失败重试的机制,主要实现在addFailed。3.addFailedprivate void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) { if (retryFuture == null) { // 锁住 synchronized (this) { if (retryFuture == null) { // 创建定时任务,每隔5秒执行一次 retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { // collect retry statistics try { // 对失败的调用进行重试 retryFailed(); } catch (Throwable t) { // Defensive fault tolerance logger.error(“Unexpected error occur at collect statistic”, t); } } }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS); } } } // 添加 invocation 和 invoker 到 failed 中 failed.put(invocation, router);}该方法做的事创建了定时器,然后把失败的调用放入到集合中。4.retryFailedvoid retryFailed() { // 如果失败队列为0,返回 if (failed.size() == 0) { return; } // 遍历失败队列 for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>( failed).entrySet()) { // 获得会话域 Invocation invocation = entry.getKey(); // 获得invoker Invoker<?> invoker = entry.getValue(); try { // 重新调用 invoker.invoke(invocation); // 从失败队列中移除 failed.remove(invocation); } catch (Throwable e) { logger.error(“Failed retry to invoke method " + invocation.getMethodName() + “, waiting again.”, e); } }}这个方法是调用失败的invoker重新调用的机制。(九)FailfastClusterpublic class FailfastCluster implements Cluster { public final static String NAME = “failfast”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建FailfastClusterInvoker return new FailfastClusterInvoker<T>(directory); }}(十)FailfastClusterInvokerpublic class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> { public FailfastClusterInvoker(Directory<T> directory) { super(directory); } @Override public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { // 检测invokers是否为空 checkInvokers(invokers, invocation); // 选择一个invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, null); try { // 调用 return invoker.invoke(invocation); } catch (Throwable e) { if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception. // 抛出异常 throw (RpcException) e; } // 抛出异常 throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, “Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + “, but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } }}逻辑比较简单,调用抛出异常就直接抛出。(十一)FailoverClusterpublic class FailoverCluster implements Cluster { public final static String NAME = “failover”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建FailoverClusterInvoker return new FailoverClusterInvoker<T>(directory); }}(十二)FailoverClusterInvokerpublic class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class); public FailoverClusterInvoker(Directory<T> directory) { super(directory); } @Override @SuppressWarnings({“unchecked”, “rawtypes”}) public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { // 复制一个invoker集合 List<Invoker<T>> copyinvokers = invokers; // 检测是否为空 checkInvokers(copyinvokers, invocation); // 获取重试次数 int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } // retry loop. // 记录最后一个异常 RpcException le = null; // last exception. List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers. Set<String> providers = new HashSet<String>(len); // 循环调用,失败重试 for (int i = 0; i < len; i++) { //Reselect before retry to avoid a change of candidate invokers. //NOTE: if invokers changed, then invoked also lose accuracy. // 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了, // 通过调用 list 可得到最新可用的 Invoker 列表 if (i > 0) { checkWhetherDestroyed(); copyinvokers = list(invocation); // check again // 检测copyinvokers 是否为空 checkInvokers(copyinvokers, invocation); } // 通过负载均衡选择invoker Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked); // 添加到 invoker 到 invoked 列表中 invoked.add(invoker); // 设置 invoked 到 RPC 上下文中 RpcContext.getContext().setInvokers((List) invoked); try { // 调用目标 Invoker 的 invoke 方法 Result result = invoker.invoke(invocation); if (le != null && logger.isWarnEnabled()) { logger.warn(“Although retry the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + “, but there have been failed providers " + providers + " (” + providers.size() + “/” + copyinvokers.size() + “) from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + “. Last error is: " + le.getMessage(), le); } return result; } catch (RpcException e) { if (e.isBiz()) { // biz exception. throw e; } le = e; } catch (Throwable e) { le = new RpcException(e.getMessage(), e); } finally { providers.add(invoker.getUrl().getAddress()); } } // 若重试失败,则抛出异常 throw new RpcException(le != null ? le.getCode() : 0, “Failed to invoke the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + “. Tried " + len + " times of the providers " + providers + " (” + providers.size() + “/” + copyinvokers.size() + “) from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + “. Last error is: " + (le != null ? le.getMessage() : “”), le != null && le.getCause() != null ? le.getCause() : le); }}该类实现了失败重试的容错策略,当调用失败的时候,记录下异常,然后循环调用下一个选择出来的invoker,直到重试次数用完,抛出最后一次的异常。(十三)FailsafeClusterpublic class FailsafeCluster implements Cluster { public final static String NAME = “failsafe”; @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { // 创建FailsafeClusterInvoker return new FailsafeClusterInvoker<T>(directory); }}(十四)FailsafeClusterInvokerpublic class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class); public FailsafeClusterInvoker(Directory<T> directory) { super(directory); } @Override public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { try { // 检测invokers是否为空 checkInvokers(invokers, invocation); // 选择一个invoker Invoker<T> invoker = select(loadbalance, invocation, invokers, null); // 调用 return invoker.invoke(invocation); } catch (Throwable e) { // 如果失败打印异常,返回一个空结果 logger.error(“Failsafe ignore exception: " + e.getMessage(), e); return new RpcResult(); // ignore } }}逻辑比较简单,就是不抛出异常,只是打印异常。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了集群中关于cluster实现的部分,讲了几种调用方式和容错策略。接下来我将开始对集群模块关于配置规则部分进行讲解。 ...

February 4, 2019 · 11 min · jiezi

dubbo源码解析(三十一)远程调用——rmi协议

远程调用——rmi协议目标:介绍rmi协议的设计和实现,介绍dubbo-rpc-rmi的源码。前言dubbo支持rmi协议,主要基于spring封装的org.springframework.remoting.rmi包来实现,当然最原始还是依赖 JDK 标准的java.rmi.包,采用阻塞式短连接和 JDK 标准序列化方式。关于rmi协议的介绍可以参考dubbo官方文档。地址:http://dubbo.apache.org/zh-cn…源码分析(一)RmiRemoteInvocation该类继承了RemoteInvocation,主要是在RemoteInvocation的基础上新增dubbo自身所需的附加值,避免这些附加值没有被传递,为了做一些验证处理。public class RmiRemoteInvocation extends RemoteInvocation { private static final long serialVersionUID = 1L; private static final String dubboAttachmentsAttrName = “dubbo.attachments”; /* * executed on consumer side / public RmiRemoteInvocation(MethodInvocation methodInvocation) { super(methodInvocation); // 添加dubbo附加值的属性 addAttribute(dubboAttachmentsAttrName, new HashMap<String, String>(RpcContext.getContext().getAttachments())); } /* * Need to restore context on provider side (Though context will be overridden by Invocation’s attachment * when ContextFilter gets executed, we will restore the attachment when Invocation is constructed, check more * 需要在提供者端恢复上下文(尽管上下文将被Invocation的附件覆盖 * 当ContextFilter执行时,我们将在构造Invocation时恢复附件,检查更多 * from {@link com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler} */ @SuppressWarnings(“unchecked”) @Override public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { // 获得上下文 RpcContext context = RpcContext.getContext(); // 设置参数 context.setAttachments((Map<String, String>) getAttribute(dubboAttachmentsAttrName)); try { return super.invoke(targetObject); } finally { // 清空参数 context.setAttachments(null); } }}(二)RmiProtocol该类继承了AbstractProxyProtocol类,是rmi协议实现的核心,跟其他协议一样,也实现了自己的服务暴露和服务引用方法。1.doExport@Overrideprotected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException { // rmi暴露者 final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter(); // 设置端口 rmiServiceExporter.setRegistryPort(url.getPort()); // 设置服务名称 rmiServiceExporter.setServiceName(url.getPath()); // 设置接口 rmiServiceExporter.setServiceInterface(type); // 设置服务实现 rmiServiceExporter.setService(impl); try { // 初始化bean的时候执行 rmiServiceExporter.afterPropertiesSet(); } catch (RemoteException e) { throw new RpcException(e.getMessage(), e); } return new Runnable() { @Override public void run() { try { // 销毁 rmiServiceExporter.destroy(); } catch (Throwable e) { logger.warn(e.getMessage(), e); } } };}该方法是服务暴露的逻辑实现。2.doRefer@Override@SuppressWarnings(“unchecked”)protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException { // FactoryBean对于RMI代理,支持传统的RMI服务和RMI调用者,创建RmiProxyFactoryBean对象 final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean(); // RMI needs extra parameter since it uses customized remote invocation object // 检测版本 if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()).equals(Version.getProtocolVersion())) { // Check dubbo version on provider, this feature only support // 设置RemoteInvocationFactory以用于此访问器 rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() { @Override public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) { // 自定义调用工厂可以向调用添加更多上下文信息 return new RmiRemoteInvocation(methodInvocation); } }); } // 设置此远程访问者的目标服务的URL。URL必须与特定远程处理提供程序的规则兼容。 rmiProxyFactoryBean.setServiceUrl(url.toIdentityString()); // 设置要访问的服务的接口。界面必须适合特定的服务和远程处理策略 rmiProxyFactoryBean.setServiceInterface(serviceType); // 设置是否在找到RMI存根后缓存它 rmiProxyFactoryBean.setCacheStub(true); // 设置是否在启动时查找RMI存根 rmiProxyFactoryBean.setLookupStubOnStartup(true); // 设置是否在连接失败时刷新RMI存根 rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true); // // 初始化bean的时候执行 rmiProxyFactoryBean.afterPropertiesSet(); return (T) rmiProxyFactoryBean.getObject();}该方法是服务引用的逻辑实现。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于rmi协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于thrift协议部分进行讲解。 ...

February 3, 2019 · 2 min · jiezi

dubbo源码解析(三十四)集群——开篇

集群——开篇目标:介绍接下来集群分哪几部分来描述,介绍dubbo在集群中涉及到的几个功能,介绍dubbo-cluster下跟各个功能相关的接口集群是什么?如果说分布式是爸爸住在杭州,妈妈住在上海,那么集群就是两个爸爸,一个住在杭州,一个住在上海。对于分布式和集群有高吞吐量、高可用的目标。对于分布式来说每个服务器部署的服务任务是不同的,可能需要这些服务器上的服务共同协作才能完成整个业务流程,各个服务各司其职。而集群不一样,集群是同一个服务,被部署在了多个服务器上,每个服务器的任务都是一样的,是为了减少压力集中的问题,而集群中就会出现负载均衡、容错等问题。dubbo的集群涉及到以下几部分内容:目录:Directory可以看成是多个Invoker的集合,但是它的值会随着注册中心中服务变化推送而动态变化,那么Invoker以及如何动态变化就是一个重点内容。集群容错:Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。路由:dubbo路由规则,路由规则决定了一次dubbo服务调用的目标服务器,路由规则分两种:条件路由规则和脚本路由规则,并且支持可拓展。负载均衡策略:dubbo支持的所有负载均衡策略算法。配置:根据url上的配置规则生成配置信息分组聚合:合并返回结果。本地伪装:mork通常用于服务降级,mock只在出现非业务异常(比如超时,网络异常等)时执行以上几部分跟《dubbo源码解析(一)Hello,Dubbo》的"(二)dubbo-cluster——集群模块“介绍有些类似,这里再重新讲一遍是为了明确我接下来介绍集群模块的文章内容分布,也就是除了本文之外,我会用七篇文章来讲解以上的七部分,不管内容多少,只是为了把相应内容区分开来,能让读者有选择性的阅读。那么本文要来讲的无非就是这几部分内容的一个大概,并且介绍一下这几部分内容涉及到的接口。集群的包结构我就不在这里展示了,就是《dubbo源码解析(一)Hello,Dubbo》的"(二)dubbo-cluster——集群模块“中的图片。下面我们直接对应各个部分来介绍相应的接口源码。目录关于目录介绍请查看《dubbo源码解析(一)Hello,Dubbo》的"(二)dubbo-cluster——集群模块“介绍。源码分析(一)Cluster@SPI(FailoverCluster.NAME)public interface Cluster { /** * Merge the directory invokers to a virtual invoker. * 将目录调用程序合并到虚拟调用程序。 * @param <T> * @param directory * @return cluster invoker * @throws RpcException / @Adaptive <T> Invoker<T> join(Directory<T> directory) throws RpcException;}该接口是集群容错接口,可以看到它是一个可扩展接口,默认实现FailoverCluster,当然它还会有其他的实现,每一种实现都代表了一种集群容错的方式,具体有哪些,可以看下面文章的介绍,他们都在support包下面,在本文只是让读者知道接口的定义。那么它还定义了一个join方法,作用就是把Directory对象变成一个 Invoker 对象用来后续的一系列调用。该Invoker代表了一个集群实现。似懂非懂就够了,后面看具体的实现会比较清晰。(二)Configuratorpublic interface Configurator extends Comparable<Configurator> { /* * get the configurator url. * 配置规则,生成url * @return configurator url. / URL getUrl(); /* * Configure the provider url. * 把规则配置到URL中 * * @param url - old rovider url. * @return new provider url. / URL configure(URL url);}该接口是配置规则的接口,定义了两个方法,第一个是配置规则,并且生成url,第二个是把配置配置到旧的url中,其实都是在url上应用规则。(三)ConfiguratorFactory@SPIpublic interface ConfiguratorFactory { /* * get the configurator instance. * 获得configurator实例 * @param url - configurator url. * @return configurator instance. / @Adaptive(“protocol”) Configurator getConfigurator(URL url);}该接口是Configurator的工厂接口,定义了一个getConfigurator方法来获得Configurator实例,比较好理解。(四)Directorypublic interface Directory<T> extends Node { /* * get service type. * 获得服务类型 * @return service type. / Class<T> getInterface(); /* * list invokers. * 获得所有服务Invoker集合 * @return invokers / List<Invoker<T>> list(Invocation invocation) throws RpcException;}该接口是目录接口,Directory 代表了多个 Invoker,并且它的值会随着注册中心的服务变更推送而变化 。一个服务类型对应一个Directory。定义的两个方法也比较好理解。(五)LoadBalance@SPI(RandomLoadBalance.NAME)public interface LoadBalance { /* * select one invoker in list. * 选择一个合适的调用,并且返回 * @param invokers invokers. * @param url refer url * @param invocation invocation. * @return selected invoker. / @Adaptive(“loadbalance”) <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;}该接口是负载均衡的接口,dubbo也提供了四种负载均衡策略,也会在下面文章讲解。(六)Merger@SPIpublic interface Merger<T> { /* * 合并T数组,返回合并后的T对象 * @param items * @return / T merge(T… items);}该接口是分组聚合,将某对象数组合并为一个对象。(七)Routerpublic interface Router extends Comparable<Router> { /* * get the router url. * 获得路由规则的url * @return url / URL getUrl(); /* * route. * 筛选出跟规则匹配的Invoker集合 * @param invokers * @param url refer url * @param invocation * @return routed invokers * @throws RpcException / <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;}该接口是路由规则的接口,定义的两个方法,第一个方法是获得路由规则的url,第二个方法是筛选出跟规则匹配的Invoker集合。(八)RouterFactory@SPIpublic interface RouterFactory { /* * Create router. * 创建路由 * @param url * @return router */ @Adaptive(“protocol”) Router getRouter(URL url);}该接口是路由工厂接口,定义了获得路由实例的方法。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章大致讲解了dubbo中集群模块的内容,并且讲解了相关接口的设计。接下来我将开始对cluster集群模块中的集群容错部分,也就是support中的源码进行讲解。 ...

February 1, 2019 · 2 min · jiezi

dubbo源码解析(三十三)远程调用——webservice协议

远程调用——webservice协议目标:介绍webservice协议的设计和实现,介绍dubbo-rpc-webservice的源码。前言dubbo集成webservice协议,基于 Apache CXF 的 frontend-simple 和 transports-http 实现 ,CXF 是 Apache 开源的一个 RPC 框架,由 Xfire 和 Celtix 合并而来。关于webservice协议的优势以及介绍可以查看官方文档,我就不多赘述。源码分析(一)WebServiceProtocol该类继承了AbstractProxyProtocol,是webservice协议的关键逻辑实现。1.属性/** * 默认端口 /public static final int DEFAULT_PORT = 80;/* * 服务集合 /private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();/* * 总线,该总线使用CXF内置的扩展管理器来加载组件(而不是使用Spring总线实现)。虽然加载速度更快,但它不允许像Spring总线那样进行大量配置和定制。 /private final ExtensionManagerBus bus = new ExtensionManagerBus();/* * http通信工厂对象 /private final HTTPTransportFactory transportFactory = new HTTPTransportFactory();/* * http绑定者 */private HttpBinder httpBinder;2.doExport@Overrideprotected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException { // 获得地址 String addr = getAddr(url); // 获得http服务 HttpServer httpServer = serverMap.get(addr); // 如果服务为空,则重新创建服务器。并且加入集合 if (httpServer == null) { httpServer = httpBinder.bind(url, new WebServiceHandler()); serverMap.put(addr, httpServer); } // 服务加载器 final ServerFactoryBean serverFactoryBean = new ServerFactoryBean(); // 设置地址 serverFactoryBean.setAddress(url.getAbsolutePath()); // 设置服务类型 serverFactoryBean.setServiceClass(type); // 设置实现类 serverFactoryBean.setServiceBean(impl); // 设置总线 serverFactoryBean.setBus(bus); // 设置通信工厂 serverFactoryBean.setDestinationFactory(transportFactory); // 创建 serverFactoryBean.create(); return new Runnable() { @Override public void run() { if(serverFactoryBean.getServer()!= null) { serverFactoryBean.getServer().destroy(); } if(serverFactoryBean.getBus()!=null) { serverFactoryBean.getBus().shutdown(true); } } };}该方法是服务暴露的逻辑实现,基于cxf一些类。3.doRefer@Override@SuppressWarnings(“unchecked”)protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException { // 创建代理工厂 ClientProxyFactoryBean proxyFactoryBean = new ClientProxyFactoryBean(); // 设置地址 proxyFactoryBean.setAddress(url.setProtocol(“http”).toIdentityString()); // 设置服务类型 proxyFactoryBean.setServiceClass(serviceType); // 设置总线 proxyFactoryBean.setBus(bus); // 创建 T ref = (T) proxyFactoryBean.create(); // 获得代理 Client proxy = ClientProxy.getClient(ref); // 获得HTTPConduit 处理“http”和“https”传输协议。实例由显式设置或配置的策略控制 HTTPConduit conduit = (HTTPConduit) proxy.getConduit(); // 用于配置客户端HTTP端口的属性 HTTPClientPolicy policy = new HTTPClientPolicy(); // 配置连接超时时间 policy.setConnectionTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT)); // 配置调用超时时间 policy.setReceiveTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)); conduit.setClient(policy); return ref;}该方法是服务引用的逻辑实现。4.WebServiceHandlerprivate class WebServiceHandler implements HttpHandler { private volatile ServletController servletController; @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 如果servletController为空,则重新加载一个 if (servletController == null) { HttpServlet httpServlet = DispatcherServlet.getInstance(); if (httpServlet == null) { response.sendError(500, “No such DispatcherServlet instance.”); return; } // 创建servletController synchronized (this) { if (servletController == null) { servletController = new ServletController(transportFactory.getRegistry(), httpServlet.getServletConfig(), httpServlet); } } } // 设置远程地址 RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); // 调用方法 servletController.invoke(request, response); }}该内部类实现了HttpHandler接口,是WebService协议的请求的处理类。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于webservice协议实现的部分,到这里关于rpc远程调用的部分就结束了,关于远程调用核心的几个内容就是代理、协议,再加上不同功能增强的过滤器等,关键是要把api中关于接口设计方面的内容看清楚,后面各类协议因为很多都是基于第三方的框架去实现,虽然方法逻辑有所区别,但是整体的思路和框架一定顺着api设计的去实现。接下来我将开始对cluster集群模块进行讲解。 ...

January 31, 2019 · 2 min · jiezi

dubbo源码解析(三十二)远程调用——thrift协议

远程调用——thrift协议目标:介绍thrift协议的设计和实现,介绍dubbo-rpc-thrift的源码。前言dubbo集成thrift协议,是基于Thrift来实现的,Thrift是一种轻量级,与语言无关的软件堆栈,具有用于点对点RPC的相关代码生成机制。Thrift为数据传输,数据序列化和应用程序级处理提供了清晰的抽象。代码生成系统采用简单的定义语言作为输入,并跨编程语言生成代码,使用抽象堆栈构建可互操作的RPC客户端和服务器。源码分析(一)MultiServiceProcessor该类对输入流进行操作并写入某些输出流。它实现了TProcessor接口,关键的方法是process。@Overridepublic boolean process(TProtocol in, TProtocol out) throws TException { // 获得十六进制的魔数 short magic = in.readI16(); // 如果不是规定的魔数,则打印错误日志,返回false if (magic != ThriftCodec.MAGIC) { logger.error(“Unsupported magic " + magic); return false; } // 获得三十二进制魔数 in.readI32(); // 获得十六进制魔数 in.readI16(); // 获得版本 byte version = in.readByte(); // 获得服务名 String serviceName = in.readString(); // 获得id long id = in.readI64(); ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); // 创建基础运输TIOStreamTransport对象 TIOStreamTransport transport = new TIOStreamTransport(bos); // 获得协议 TProtocol protocol = protocolFactory.getProtocol(transport); // 从集合中取出处理器 TProcessor processor = processorMap.get(serviceName); // 如果处理器为空,则打印错误,返回false if (processor == null) { logger.error(“Could not find processor for service " + serviceName); return false; } // todo if exception // 获得结果 boolean result = processor.process(in, protocol); ByteArrayOutputStream header = new ByteArrayOutputStream(512); // 协议头的传输器 TIOStreamTransport headerTransport = new TIOStreamTransport(header); TProtocol headerProtocol = protocolFactory.getProtocol(headerTransport); // 写入16进制的魔数 headerProtocol.writeI16(magic); // 写入32进制的Integer最大值 headerProtocol.writeI32(Integer.MAX_VALUE); // 写入Short最大值的16进制 headerProtocol.writeI16(Short.MAX_VALUE); // 写入版本号 headerProtocol.writeByte(version); // 写入服务名 headerProtocol.writeString(serviceName); // 写入id headerProtocol.writeI64(id); // 输出 headerProtocol.getTransport().flush(); out.writeI16(magic); out.writeI32(bos.size() + header.size()); out.writeI16((short) (0xffff & header.size())); out.writeByte(version); out.writeString(serviceName); out.writeI64(id); out.getTransport().write(bos.toByteArray()); out.getTransport().flush(); return result;}(二)RandomAccessByteArrayOutputStream该类是随机访问数组的输出流,比较简单,我就不多叙述,有兴趣的可以直接看源码,不看影响也不大。(三)ClassNameGenerator@SPI(DubboClassNameGenerator.NAME)public interface ClassNameGenerator { /** * 生成参数的类名 / public String generateArgsClassName(String serviceName, String methodName); /* * 生成结果的类名 * @param serviceName * @param methodName * @return / public String generateResultClassName(String serviceName, String methodName);}该接口是是可扩展接口,定义了两个方法。有两个实现类,下面讲述。(四)DubboClassNameGenerator该类实现了ClassNameGenerator接口,是dubbo相关的类名生成实现。public class DubboClassNameGenerator implements ClassNameGenerator { public static final String NAME = “dubbo”; @Override public String generateArgsClassName(String serviceName, String methodName) { return ThriftUtils.generateMethodArgsClassName(serviceName, methodName); } @Override public String generateResultClassName(String serviceName, String methodName) { return ThriftUtils.generateMethodResultClassName(serviceName, methodName); }}(五)ThriftClassNameGenerator该类实现了ClassNameGenerator接口,是Thrift相关的类名生成实现。public class ThriftClassNameGenerator implements ClassNameGenerator { public static final String NAME = “thrift”; @Override public String generateArgsClassName(String serviceName, String methodName) { return ThriftUtils.generateMethodArgsClassNameThrift(serviceName, methodName); } @Override public String generateResultClassName(String serviceName, String methodName) { return ThriftUtils.generateMethodResultClassNameThrift(serviceName, methodName); }}以上两个都调用了ThriftUtils中的方法。(六)ThriftUtils该类中封装的方法比较简单,就一些字符串的拼接,有兴趣的可以直接查看我下面贴出来的注释连接。(七)ThriftCodec该类是基于Thrift实现的编解码器。 这里需要大家看一下该类的注释,关于协议的数据: |<- message header ->|<- message body ->|* +—————-+———————-+——————+—————————+——————+* | magic (2 bytes)|message size (4 bytes)|head size(2 bytes)| version (1 byte) | header | message body |* +—————-+———————-+——————+—————————+——————+* |<- 1.属性/** * 消息长度索引 /public static final int MESSAGE_LENGTH_INDEX = 2;/* * 消息头长度索引 /public static final int MESSAGE_HEADER_LENGTH_INDEX = 6;/* * 消息最短长度 /public static final int MESSAGE_SHORTEST_LENGTH = 10;public static final String NAME = “thrift”;/* * 类名生成参数 /public static final String PARAMETER_CLASS_NAME_GENERATOR = “class.name.generator”;/* * 版本 /public static final byte VERSION = (byte) 1;/* * 魔数 /public static final short MAGIC = (short) 0xdabc;/* * 请求参数集合 /static final ConcurrentMap<Long, RequestData> cachedRequest = new ConcurrentHashMap<Long, RequestData>();/* * thrift序列号 /private static final AtomicInteger THRIFT_SEQ_ID = new AtomicInteger(0);/* * 类缓存 /private static final ConcurrentMap<String, Class<?>> cachedClass = new ConcurrentHashMap<String, Class<?>>();2.encode@Overridepublic void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException { // 如果消息是Request类型 if (message instanceof Request) { // Request类型消息编码 encodeRequest(channel, buffer, (Request) message); } else if (message instanceof Response) { // Response类型消息编码 encodeResponse(channel, buffer, (Response) message); } else { throw new UnsupportedOperationException(“Thrift codec only support encode " + Request.class.getName() + " and " + Response.class.getName()); }}该方法是编码的逻辑,具体的编码操作根据请求类型不同分别调用不同的方法。3.encodeRequestprivate void encodeRequest(Channel channel, ChannelBuffer buffer, Request request) throws IOException { // 获得会话域 RpcInvocation inv = (RpcInvocation) request.getData(); // 获得下一个id int seqId = nextSeqId(); // 获得服务名 String serviceName = inv.getAttachment(Constants.INTERFACE_KEY); // 如果是空的 则抛出异常 if (StringUtils.isEmpty(serviceName)) { throw new IllegalArgumentException(“Could not find service name in attachment with key " + Constants.INTERFACE_KEY); } // 创建TMessage对象 TMessage message = new TMessage( inv.getMethodName(), TMessageType.CALL, seqId); // 获得方法参数 String methodArgs = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class) .getExtension(channel.getUrl().getParameter(ThriftConstants.CLASS_NAME_GENERATOR_KEY, ThriftClassNameGenerator.NAME)) .generateArgsClassName(serviceName, inv.getMethodName()); // 如果是空,则抛出异常 if (StringUtils.isEmpty(methodArgs)) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, “Could not encode request, the specified interface may be incorrect.”); } // 从缓存中取出类型 Class<?> clazz = cachedClass.get(methodArgs); if (clazz == null) { try { // 重新获得类型 clazz = ClassHelper.forNameWithThreadContextClassLoader(methodArgs); // 加入缓存 cachedClass.putIfAbsent(methodArgs, clazz); } catch (ClassNotFoundException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } // 生成的Thrift对象的通用基接口 TBase args; try { args = (TBase) clazz.newInstance(); } catch (InstantiationException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } // 遍历参数 for (int i = 0; i < inv.getArguments().length; i++) { Object obj = inv.getArguments()[i]; if (obj == null) { continue; } TFieldIdEnum field = args.fieldForId(i + 1); // 生成set方法名 String setMethodName = ThriftUtils.generateSetMethodName(field.getFieldName()); Method method; try { // 获得方法 method = clazz.getMethod(setMethodName, inv.getParameterTypes()[i]); } catch (NoSuchMethodException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } try { // 调用下一个调用链 method.invoke(args, obj); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (InvocationTargetException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } // 创建一个随机访问数组输出流 RandomAccessByteArrayOutputStream bos = new RandomAccessByteArrayOutputStream(1024); // 创建传输器 TIOStreamTransport transport = new TIOStreamTransport(bos); // 创建协议 TBinaryProtocol protocol = new TBinaryProtocol(transport); int headerLength, messageLength; byte[] bytes = new byte[4]; try { // 开始编码 // magic protocol.writeI16(MAGIC); // message length placeholder protocol.writeI32(Integer.MAX_VALUE); // message header length placeholder protocol.writeI16(Short.MAX_VALUE); // version protocol.writeByte(VERSION); // service name protocol.writeString(serviceName); // dubbo request id protocol.writeI64(request.getId()); protocol.getTransport().flush(); // header size headerLength = bos.size(); // 对body内容进行编码 // message body protocol.writeMessageBegin(message); args.write(protocol); protocol.writeMessageEnd(); protocol.getTransport().flush(); int oldIndex = messageLength = bos.size(); // fill in message length and header length try { TFramedTransport.encodeFrameSize(messageLength, bytes); bos.setWriteIndex(MESSAGE_LENGTH_INDEX); protocol.writeI32(messageLength); bos.setWriteIndex(MESSAGE_HEADER_LENGTH_INDEX); protocol.writeI16((short) (0xffff & headerLength)); } finally { bos.setWriteIndex(oldIndex); } } catch (TException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } buffer.writeBytes(bytes); buffer.writeBytes(bos.toByteArray());}该方法是对request类型的消息进行编码。4.encodeResponseprivate void encodeResponse(Channel channel, ChannelBuffer buffer, Response response) throws IOException { // 获得结果 RpcResult result = (RpcResult) response.getResult(); // 获得请求 RequestData rd = cachedRequest.get(response.getId()); // 获得结果的类名 String resultClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class).getExtension( channel.getUrl().getParameter(ThriftConstants.CLASS_NAME_GENERATOR_KEY, ThriftClassNameGenerator.NAME)) .generateResultClassName(rd.serviceName, rd.methodName); // 如果为空,则序列化失败 if (StringUtils.isEmpty(resultClassName)) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, “Could not encode response, the specified interface may be incorrect.”); } // 获得类型 Class clazz = cachedClass.get(resultClassName); // 如果为空,则重新获取 if (clazz == null) { try { clazz = ClassHelper.forNameWithThreadContextClassLoader(resultClassName); cachedClass.putIfAbsent(resultClassName, clazz); } catch (ClassNotFoundException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } TBase resultObj; try { // 加载该类 resultObj = (TBase) clazz.newInstance(); } catch (InstantiationException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } TApplicationException applicationException = null; TMessage message; // 如果结果有异常抛出 if (result.hasException()) { Throwable throwable = result.getException(); int index = 1; boolean found = false; while (true) { TFieldIdEnum fieldIdEnum = resultObj.fieldForId(index++); if (fieldIdEnum == null) { break; } String fieldName = fieldIdEnum.getFieldName(); String getMethodName = ThriftUtils.generateGetMethodName(fieldName); String setMethodName = ThriftUtils.generateSetMethodName(fieldName); Method getMethod; Method setMethod; try { // 获得get方法 getMethod = clazz.getMethod(getMethodName); // 如果返回类型和异常类型一样,则创建set方法,并且调用下一个调用链 if (getMethod.getReturnType().equals(throwable.getClass())) { found = true; setMethod = clazz.getMethod(setMethodName, throwable.getClass()); setMethod.invoke(resultObj, throwable); } } catch (NoSuchMethodException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (InvocationTargetException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } if (!found) { // 创建TApplicationException异常 applicationException = new TApplicationException(throwable.getMessage()); } } else { // 获得真实的结果 Object realResult = result.getResult(); // result field id is 0 String fieldName = resultObj.fieldForId(0).getFieldName(); String setMethodName = ThriftUtils.generateSetMethodName(fieldName); String getMethodName = ThriftUtils.generateGetMethodName(fieldName); Method getMethod; Method setMethod; try { // 创建get和set方法 getMethod = clazz.getMethod(getMethodName); setMethod = clazz.getMethod(setMethodName, getMethod.getReturnType()); setMethod.invoke(resultObj, realResult); } catch (NoSuchMethodException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (InvocationTargetException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } if (applicationException != null) { message = new TMessage(rd.methodName, TMessageType.EXCEPTION, rd.id); } else { message = new TMessage(rd.methodName, TMessageType.REPLY, rd.id); } RandomAccessByteArrayOutputStream bos = new RandomAccessByteArrayOutputStream(1024); TIOStreamTransport transport = new TIOStreamTransport(bos); TBinaryProtocol protocol = new TBinaryProtocol(transport); int messageLength; int headerLength; //编码 byte[] bytes = new byte[4]; try { // magic protocol.writeI16(MAGIC); // message length protocol.writeI32(Integer.MAX_VALUE); // message header length protocol.writeI16(Short.MAX_VALUE); // version protocol.writeByte(VERSION); // service name protocol.writeString(rd.serviceName); // id protocol.writeI64(response.getId()); protocol.getTransport().flush(); headerLength = bos.size(); // message protocol.writeMessageBegin(message); switch (message.type) { case TMessageType.EXCEPTION: applicationException.write(protocol); break; case TMessageType.REPLY: resultObj.write(protocol); break; } protocol.writeMessageEnd(); protocol.getTransport().flush(); int oldIndex = messageLength = bos.size(); try { TFramedTransport.encodeFrameSize(messageLength, bytes); bos.setWriteIndex(MESSAGE_LENGTH_INDEX); protocol.writeI32(messageLength); bos.setWriteIndex(MESSAGE_HEADER_LENGTH_INDEX); protocol.writeI16((short) (0xffff & headerLength)); } finally { bos.setWriteIndex(oldIndex); } } catch (TException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } buffer.writeBytes(bytes); buffer.writeBytes(bos.toByteArray());}该方法是对response类型的请求消息进行编码。5.decode @Override public Object decode(Channel channel, ChannelBuffer buffer) throws IOException { int available = buffer.readableBytes(); // 如果小于最小的长度,则还需要更多的输入 if (available < MESSAGE_SHORTEST_LENGTH) { return DecodeResult.NEED_MORE_INPUT; } else { TIOStreamTransport transport = new TIOStreamTransport(new ChannelBufferInputStream(buffer)); TBinaryProtocol protocol = new TBinaryProtocol(transport); short magic; int messageLength; // 对协议头中的魔数进行比对 try {// protocol.readI32(); // skip the first message length byte[] bytes = new byte[4]; transport.read(bytes, 0, 4); magic = protocol.readI16(); messageLength = protocol.readI32(); } catch (TException e) { throw new IOException(e.getMessage(), e); } if (MAGIC != magic) { throw new IOException(“Unknown magic code " + magic); } if (available < messageLength) { return DecodeResult.NEED_MORE_INPUT; } return decode(protocol); } } /* * 解码 * @param protocol * @return * @throws IOException / private Object decode(TProtocol protocol) throws IOException { // version String serviceName; long id; TMessage message; try { // 读取协议头中对内容 protocol.readI16(); protocol.readByte(); serviceName = protocol.readString(); id = protocol.readI64(); message = protocol.readMessageBegin(); } catch (TException e) { throw new IOException(e.getMessage(), e); } // 如果是回调 if (message.type == TMessageType.CALL) { RpcInvocation result = new RpcInvocation(); // 设置服务名和方法名 result.setAttachment(Constants.INTERFACE_KEY, serviceName); result.setMethodName(message.name); String argsClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class) .getExtension(ThriftClassNameGenerator.NAME).generateArgsClassName(serviceName, message.name); if (StringUtils.isEmpty(argsClassName)) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, “The specified interface name incorrect.”); } // 从缓存中获得class类 Class clazz = cachedClass.get(argsClassName); if (clazz == null) { try { // 重新获得class类型 clazz = ClassHelper.forNameWithThreadContextClassLoader(argsClassName); // 加入集合 cachedClass.putIfAbsent(argsClassName, clazz); } catch (ClassNotFoundException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } TBase args; try { args = (TBase) clazz.newInstance(); } catch (InstantiationException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } try { args.read(protocol); protocol.readMessageEnd(); } catch (TException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } // 参数集合 List<Object> parameters = new ArrayList<Object>(); // 参数类型集合 List<Class<?>> parameterTypes = new ArrayList<Class<?>>(); int index = 1; while (true) { TFieldIdEnum fieldIdEnum = args.fieldForId(index++); if (fieldIdEnum == null) { break; } String fieldName = fieldIdEnum.getFieldName(); // 获得方法名 String getMethodName = ThriftUtils.generateGetMethodName(fieldName); Method getMethod; try { getMethod = clazz.getMethod(getMethodName); } catch (NoSuchMethodException e) { throw new RpcException( RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } // 加入参数类型 parameterTypes.add(getMethod.getReturnType()); try { parameters.add(getMethod.invoke(args)); } catch (IllegalAccessException e) { throw new RpcException( RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (InvocationTargetException e) { throw new RpcException( RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } // 设置参数 result.setArguments(parameters.toArray()); // 设置参数类型 result.setParameterTypes(parameterTypes.toArray(new Class[parameterTypes.size()])); // 创建一个新的请求 Request request = new Request(id); // 把结果放入请求中 request.setData(result); // 放入集合中 cachedRequest.putIfAbsent(id, RequestData.create(message.seqid, serviceName, message.name)); return request; // 如果是抛出异常 } else if (message.type == TMessageType.EXCEPTION) { TApplicationException exception; try { // 读取异常 exception = TApplicationException.read(protocol); protocol.readMessageEnd(); } catch (TException e) { throw new IOException(e.getMessage(), e); } // 创建结果 RpcResult result = new RpcResult(); // 设置异常 result.setException(new RpcException(exception.getMessage())); // 创建Response响应 Response response = new Response(); // 把结果放入 response.setResult(result); // 加入唯一id response.setId(id); return response; // 如果类型是回应 } else if (message.type == TMessageType.REPLY) { // 获得结果的类名 String resultClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class) .getExtension(ThriftClassNameGenerator.NAME).generateResultClassName(serviceName, message.name); if (StringUtils.isEmpty(resultClassName)) { throw new IllegalArgumentException(“Could not infer service result class name from service name " + serviceName + “, the service name you specified may not generated by thrift idl compiler”); } // 获得class类型 Class<?> clazz = cachedClass.get(resultClassName); // 如果为空,则重新获取 if (clazz == null) { try { clazz = ClassHelper.forNameWithThreadContextClassLoader(resultClassName); cachedClass.putIfAbsent(resultClassName, clazz); } catch (ClassNotFoundException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } } TBase<?, ? extends TFieldIdEnum> result; try { result = (TBase<?, ?>) clazz.newInstance(); } catch (InstantiationException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } try { result.read(protocol); protocol.readMessageEnd(); } catch (TException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } Object realResult = null; int index = 0; while (true) { TFieldIdEnum fieldIdEnum = result.fieldForId(index++); if (fieldIdEnum == null) { break; } Field field; try { field = clazz.getDeclaredField(fieldIdEnum.getFieldName()); field.setAccessible(true); } catch (NoSuchFieldException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } try { // 获得真实的结果 realResult = field.get(result); } catch (IllegalAccessException e) { throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e); } if (realResult != null) { break; } } // 创建响应 Response response = new Response(); // 设置唯一id response.setId(id); // 创建结果 RpcResult rpcResult = new RpcResult(); // 用RpcResult包裹结果 if (realResult instanceof Throwable) { rpcResult.setException((Throwable) realResult); } else { rpcResult.setValue(realResult); } // 设置结果 response.setResult(rpcResult); return response; } else { // Impossible throw new IOException(); } }该方法是对解码的逻辑。对于消息分为REPLY、EXCEPTION和CALL三种情况来分别进行解码。6.RequestDatastatic class RequestData { /* * 请求id / int id; /* * 服务名 / String serviceName; /* * 方法名 / String methodName; static RequestData create(int id, String sn, String mn) { RequestData result = new RequestData(); result.id = id; result.serviceName = sn; result.methodName = mn; return result; }}该内部类是请求参数实体。(八)ThriftInvoker该类是thrift协议的Invoker实现。1.属性/* * 客户端集合 /private final ExchangeClient[] clients;/* * 活跃的客户端索引 /private final AtomicPositiveInteger index = new AtomicPositiveInteger();/* * 销毁锁 /private final ReentrantLock destroyLock = new ReentrantLock();/* * invoker集合 /private final Set<Invoker<?>> invokers;2.doInvoke@Overrideprotected Result doInvoke(Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName; // 获得方法名 methodName = invocation.getMethodName(); // 设置附加值 path inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); // for thrift codec inv.setAttachment(ThriftCodec.PARAMETER_CLASS_NAME_GENERATOR, getUrl().getParameter( ThriftCodec.PARAMETER_CLASS_NAME_GENERATOR, DubboClassNameGenerator.NAME)); ExchangeClient currentClient; // 如果只有一个连接的客户端,则直接返回 if (clients.length == 1) { currentClient = clients[0]; } else { // 否则,取出下一个客户端,循环数组取 currentClient = clients[index.getAndIncrement() % clients.length]; } try { // 获得超时时间 int timeout = getUrl().getMethodParameter( methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); RpcContext.getContext().setFuture(null); // 发起请求 return (Result) currentClient.request(inv, timeout).get(); } catch (TimeoutException e) { // 抛出超时异常 throw new RpcException(RpcException.TIMEOUT_EXCEPTION, e.getMessage(), e); } catch (RemotingException e) { // 抛出网络异常 throw new RpcException(RpcException.NETWORK_EXCEPTION, e.getMessage(), e); }}该方法是thrift协议的调用链处理逻辑。(九)ThriftProtocol该类是thrift协议的主要实现逻辑,分别实现了服务引用和服务调用的逻辑。1.属性/* * 默认端口号 /public static final int DEFAULT_PORT = 40880;/* * 扩展名 /public static final String NAME = “thrift”;// ip:port -> ExchangeServer/* * 服务集合,key为ip:port */private final ConcurrentMap<String, ExchangeServer> serverMap = new ConcurrentHashMap<String, ExchangeServer>();private ExchangeHandler handler = new ExchangeHandlerAdapter() { @Override public Object reply(ExchangeChannel channel, Object msg) throws RemotingException { // 如果消息是Invocation类型的 if (msg instanceof Invocation) { Invocation inv = (Invocation) msg; // 获得服务名 String serviceName = inv.getAttachments().get(Constants.INTERFACE_KEY); // 获得服务的key String serviceKey = serviceKey(channel.getLocalAddress().getPort(), serviceName, null, null); // 从集合中获得暴露者 DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey); // 如果暴露者为空,则抛出异常 if (exporter == null) { throw new RemotingException(channel, “Not found exported service: " + serviceKey + " in " + exporterMap.keySet() + “, may be version or group mismatch " + “, channel: consumer: " + channel.getRemoteAddress() + " –> provider: " + channel.getLocalAddress() + “, message:” + msg); } // 设置远程地址 RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); return exporter.getInvoker().invoke(inv); } // 否则抛出异常,不支持的请求消息 throw new RemotingException(channel, “Unsupported request: " + (msg.getClass().getName() + “: " + msg) + “, channel: consumer: " + channel.getRemoteAddress() + " –> provider: " + channel.getLocalAddress()); } @Override public void received(Channel channel, Object message) throws RemotingException { // 如果消息是Invocation类型,则调用reply,否则接收消息 if (message instanceof Invocation) { reply((ExchangeChannel) channel, message); } else { super.received(channel, message); } }};2.export@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // can use thrift codec only // 只能使用thrift编解码器 URL url = invoker.getUrl().addParameter(Constants.CODEC_KEY, ThriftCodec.NAME); // find server. // 获得服务地址 String key = url.getAddress(); // client can expose a service for server to invoke only. // 客户端可以为服务器暴露服务以仅调用 boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true); if (isServer && !serverMap.containsKey(key)) { // 加入到集合 serverMap.put(key, getServer(url)); } // export service. // 得到服务key key = serviceKey(url); // 创建暴露者 DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); // 加入集合 exporterMap.put(key, exporter); return exporter;}该方法是服务暴露的逻辑实现。3.refer@Overridepublic <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { // 创建ThriftInvoker ThriftInvoker<T> invoker = new ThriftInvoker<T>(type, url, getClients(url), invokers); // 加入到集合 invokers.add(invoker); return invoker;}该方法是服务引用的逻辑实现。4.getClientsprivate ExchangeClient[] getClients(URL url) { // 获得连接数 int connections = url.getParameter(Constants.CONNECTIONS_KEY, 1); // 创建客户端集合 ExchangeClient[] clients = new ExchangeClient[connections]; // 创建客户端 for (int i = 0; i < clients.length; i++) { clients[i] = initClient(url); } return clients;}该方法是获得客户端集合。5.initClientprivate ExchangeClient initClient(URL url) { ExchangeClient client; // 加上编解码器 url = url.addParameter(Constants.CODEC_KEY, ThriftCodec.NAME); try { // 创建客户端 client = Exchangers.connect(url); } catch (RemotingException e) { throw new RpcException(“Fail to create remoting client for service(” + url + “): " + e.getMessage(), e); } return client;}该方法是创建客户端的逻辑。6.getServerprivate ExchangeServer getServer(URL url) { // enable sending readonly event when server closes by default // 加入只读事件 url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString()); // 获得服务的实现方式 String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER); // 如果该实现方式不是dubbo支持的方式,则抛出异常 if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) throw new RpcException(“Unsupported server type: " + str + “, url: " + url); ExchangeServer server; try { // 获得服务器 server = Exchangers.bind(url, handler); } catch (RemotingException e) { throw new RpcException(“Fail to start server(url: " + url + “) " + e.getMessage(), e); } // 获得实现方式 str = url.getParameter(Constants.CLIENT_KEY); // 如果客户端实现方式不是dubbo支持的方式,则抛出异常。 if (str != null && str.length() > 0) { Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(); if (!supportedTypes.contains(str)) { throw new RpcException(“Unsupported client type: " + str); } } return server;}该方法是获得server的逻辑实现。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于thrift协议实现的部分,要对Thrift。接下来我将开始对rpc模块关于webservice协议部分进行讲解。 ...

January 30, 2019 · 13 min · jiezi

dubbo源码解析(三十)远程调用——rest协议

远程调用——rest协议目标:介绍rest协议的设计和实现,介绍dubbo-rpc-rest的源码。前言REST的英文名是RepresentationalState Transfer,它是一种开发风格,关于REST不清楚的朋友可以了解一下。在dubbo中利用的是红帽子RedHat公司的Resteasy来使dubbo支持REST风格的开发使用。在本文中主要讲解的是基于Resteasy来实现rest协议的实现。源码分析(一)RestServer该接口是rest协议的服务器接口。定义了服务器相关的方法。public interface RestServer { /** * 服务器启动 * @param url / void start(URL url); /* * 部署服务器 * @param resourceDef it could be either resource interface or resource impl / void deploy(Class resourceDef, Object resourceInstance, String contextPath); /* * 取消服务器部署 * @param resourceDef / void undeploy(Class resourceDef); /* * 停止服务器 / void stop();}(二)BaseRestServer该类实现了RestServer接口,是rest服务的抽象类,把getDeployment和doStart方法进行抽象,让子类专注于中这两个方法的实现。1.start @Override public void start(URL url) { // 支持两种 Content-Type getDeployment().getMediaTypeMappings().put(“json”, “application/json”); getDeployment().getMediaTypeMappings().put(“xml”, “text/xml”);// server.getDeployment().getMediaTypeMappings().put(“xml”, “application/xml”); // 添加拦截器 getDeployment().getProviderClasses().add(RpcContextFilter.class.getName()); // TODO users can override this mapper, but we just rely on the current priority strategy of resteasy // 异常类映射 getDeployment().getProviderClasses().add(RpcExceptionMapper.class.getName()); // 添加需要加载的类 loadProviders(url.getParameter(Constants.EXTENSION_KEY, “”)); // 开启服务器 doStart(url); }2.deploy@Overridepublic void deploy(Class resourceDef, Object resourceInstance, String contextPath) { // 如果 if (StringUtils.isEmpty(contextPath)) { // 添加自定义资源实现端点,部署服务器 getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef)); } else { // 添加自定义资源实现端点。指定contextPath getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef), contextPath); }}3.undeploy@Overridepublic void undeploy(Class resourceDef) { // 取消服务器部署 getDeployment().getRegistry().removeRegistrations(resourceDef);}4.loadProvidersprotected void loadProviders(String value) { for (String clazz : Constants.COMMA_SPLIT_PATTERN.split(value)) { if (!StringUtils.isEmpty(clazz)) { getDeployment().getProviderClasses().add(clazz.trim()); } }}该方法是把类都加入到ResteasyDeployment的providerClasses中,加入各类组件。(三)DubboHttpServer该类继承了BaseRestServer,实现了doStart和getDeployment方法,当配置选择servlet、jetty或者tomcat作为远程通信的实现时,实现的服务器类1.属性/* * HttpServletDispatcher实例 /private final HttpServletDispatcher dispatcher = new HttpServletDispatcher();/* * Resteasy的服务部署器 /private final ResteasyDeployment deployment = new ResteasyDeployment();/* * http绑定者 /private HttpBinder httpBinder;/* * http服务器 /private HttpServer httpServer;2.doStart@Overrideprotected void doStart(URL url) { // TODO jetty will by default enable keepAlive so the xml config has no effect now // 创建http服务器 httpServer = httpBinder.bind(url, new RestHandler()); // 获得ServletContext ServletContext servletContext = ServletManager.getInstance().getServletContext(url.getPort()); // 如果为空 ,则获得默认端口对应的ServletContext对象 if (servletContext == null) { servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT); } // 如果还是为空 ,则抛出异常 if (servletContext == null) { throw new RpcException(“No servlet context found. If you are using server=‘servlet’, " + “make sure that you’ve configured " + BootstrapListener.class.getName() + " in web.xml”); } // 设置属性部署器 servletContext.setAttribute(ResteasyDeployment.class.getName(), deployment); try { // 初始化 dispatcher.init(new SimpleServletConfig(servletContext)); } catch (ServletException e) { throw new RpcException(e); }}3.RestHandlerprivate class RestHandler implements HttpHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 设置远程地址 RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); // 请求相关的服务 dispatcher.service(request, response); }}该内部类是服务请求的处理器4.SimpleServletConfigprivate static class SimpleServletConfig implements ServletConfig { // ServletContext对象 private final ServletContext servletContext; public SimpleServletConfig(ServletContext servletContext) { this.servletContext = servletContext; } @Override public String getServletName() { return “DispatcherServlet”; } @Override public ServletContext getServletContext() { return servletContext; } @Override public String getInitParameter(String s) { return null; } @Override public Enumeration getInitParameterNames() { return new Enumeration() { @Override public boolean hasMoreElements() { return false; } @Override public Object nextElement() { return null; } }; }}该内部类是配置类。(四)NettyServer该类继承了BaseRestServer,当配置了netty作为远程通信的实现时,实现的服务器。public class NettyServer extends BaseRestServer { /* * NettyJaxrsServer对象 / private final NettyJaxrsServer server = new NettyJaxrsServer(); @Override protected void doStart(URL url) { // 获得ip String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost()); if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { // 设置服务的ip server.setHostname(bindIp); } // 设置端口 server.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); // 通道选项集合 Map<ChannelOption, Object> channelOption = new HashMap<ChannelOption, Object>(); // 保持连接检测对方主机是否崩溃 channelOption.put(ChannelOption.SO_KEEPALIVE, url.getParameter(Constants.KEEP_ALIVE_KEY, Constants.DEFAULT_KEEP_ALIVE)); // 设置配置 server.setChildChannelOptions(channelOption); // 设置线程数,默认为200 server.setExecutorThreadCount(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)); // 设置核心线程数 server.setIoWorkerCount(url.getParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); // 设置最大的请求数 server.setMaxRequestSize(url.getParameter(Constants.PAYLOAD_KEY, Constants.DEFAULT_PAYLOAD)); // 启动服务 server.start(); } @Override public void stop() { server.stop(); } @Override protected ResteasyDeployment getDeployment() { return server.getDeployment(); }}(五)DubboResourceFactory该类实现了ResourceFactory接口,是资源工程实现类,封装了以下两个属性,实现比较简单。/* * 资源类 /private Object resourceInstance;/* * 扫描的类型 /private Class scannableClass;(六)RestConstraintViolation该类是当约束违反的实体类,封装了以下三个属性,具体使用可以看下面的介绍。/* * 地址 /private String path;/* * 消息 /private String message;/* * 值 /private String value;(七)RestServerFactory该类是服务器工程类,用来提供相应的实例,里面逻辑比较简单。public class RestServerFactory { /* * http绑定者 / private HttpBinder httpBinder; public void setHttpBinder(HttpBinder httpBinder) { this.httpBinder = httpBinder; } /* * 创建服务器 * @param name * @return / public RestServer createServer(String name) { // TODO move names to Constants // 如果是servlet或者jetty或者tomcat,则创建DubboHttpServer if (“servlet”.equalsIgnoreCase(name) || “jetty”.equalsIgnoreCase(name) || “tomcat”.equalsIgnoreCase(name)) { return new DubboHttpServer(httpBinder); } else if (“netty”.equalsIgnoreCase(name)) { // 如果是netty,那么直接创建netty服务器 return new NettyServer(); } else { // 否则 抛出异常 throw new IllegalArgumentException(“Unrecognized server name: " + name); } }}可以看到,根据配置的不同,来创建不同的服务器实现。(八)RpcContextFilter该类是过滤器。增加了对协议头大小的限制。public class RpcContextFilter implements ContainerRequestFilter, ClientRequestFilter { /* * 附加值key / private static final String DUBBO_ATTACHMENT_HEADER = “Dubbo-Attachments”; // currently we use a single header to hold the attachments so that the total attachment size limit is about 8k /* * 目前我们使用单个标头来保存附件,以便总附件大小限制大约为8k / private static final int MAX_HEADER_SIZE = 8 * 1024; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // 获得request HttpServletRequest request = ResteasyProviderFactory.getContextData(HttpServletRequest.class); // 把它放到rpc上下文中 RpcContext.getContext().setRequest(request); // this only works for servlet containers if (request != null && RpcContext.getContext().getRemoteAddress() == null) { // 设置远程地址 RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); } // 设置response RpcContext.getContext().setResponse(ResteasyProviderFactory.getContextData(HttpServletResponse.class)); // 获得协议头信息 String headers = requestContext.getHeaderString(DUBBO_ATTACHMENT_HEADER); // 分割协议头信息,把附加值分解开存入上下文中 if (headers != null) { for (String header : headers.split(”,”)) { int index = header.indexOf("="); if (index > 0) { String key = header.substring(0, index); String value = header.substring(index + 1); if (!StringUtils.isEmpty(key)) { RpcContext.getContext().setAttachment(key.trim(), value.trim()); } } } } } @Override public void filter(ClientRequestContext requestContext) throws IOException { int size = 0; // 遍历附加值 for (Map.Entry<String, String> entry : RpcContext.getContext().getAttachments().entrySet()) { // 如果key或者value有出现=或者,则抛出异常 if (entry.getValue().contains(",") || entry.getValue().contains("=") || entry.getKey().contains(",") || entry.getKey().contains("=")) { throw new IllegalArgumentException(“The attachments of " + RpcContext.class.getSimpleName() + " must not contain ‘,’ or ‘=’ when using rest protocol”); } // TODO for now we don’t consider the differences of encoding and server limit // 加入UTF-8配置,计算协议头大小 size += entry.getValue().getBytes(“UTF-8”).length; // 如果大于限制,则抛出异常 if (size > MAX_HEADER_SIZE) { throw new IllegalArgumentException(“The attachments of " + RpcContext.class.getSimpleName() + " is too big”); } // 拼接 String attachments = entry.getKey() + “=” + entry.getValue(); // 加入到请求头上 requestContext.getHeaders().add(DUBBO_ATTACHMENT_HEADER, attachments); } }}可以看到有两个filter的方法实现,第一个是解析对于附加值,并且放入上下文中。第二个是对协议头大小的限制。(九)RpcExceptionMapper该类是异常的处理类。public class RpcExceptionMapper implements ExceptionMapper<RpcException> { @Override public Response toResponse(RpcException e) { // TODO do more sophisticated exception handling and output // 如果是约束违反异常 if (e.getCause() instanceof ConstraintViolationException) { return handleConstraintViolationException((ConstraintViolationException) e.getCause()); } // we may want to avoid exposing the dubbo exception details to certain clients // TODO for now just do plain text output return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(“Internal server error: " + e.getMessage()).type(ContentType.TEXT_PLAIN_UTF_8).build(); } /* * 处理参数不合法的异常 * @param cve * @return / protected Response handleConstraintViolationException(ConstraintViolationException cve) { // 创建约束违反记录 ViolationReport report = new ViolationReport(); // 遍历约束违反 for (ConstraintViolation cv : cve.getConstraintViolations()) { // 添加记录 report.addConstraintViolation(new RestConstraintViolation( cv.getPropertyPath().toString(), cv.getMessage(), cv.getInvalidValue() == null ? “null” : cv.getInvalidValue().toString())); } // TODO for now just do xml output // 只支持xml输出 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(report).type(ContentType.TEXT_XML_UTF_8).build(); }}主要是处理参数不合法的异常。(十)ViolationReport该类是约束违反的记录类,其中就封装了一个约束违反的集合。public class ViolationReport implements Serializable { private static final long serialVersionUID = -130498234L; /* * 约束违反集合 / private List<RestConstraintViolation> constraintViolations; public List<RestConstraintViolation> getConstraintViolations() { return constraintViolations; } public void setConstraintViolations(List<RestConstraintViolation> constraintViolations) { this.constraintViolations = constraintViolations; } public void addConstraintViolation(RestConstraintViolation constraintViolation) { if (constraintViolations == null) { constraintViolations = new LinkedList<RestConstraintViolation>(); } constraintViolations.add(constraintViolation); }}(十一)RestProtocol该类继承了AbstractProxyProtocol,是rest协议实现的核心。1.属性/* * 默认端口号 /private static final int DEFAULT_PORT = 80;/* * 服务器集合 /private final Map<String, RestServer> servers = new ConcurrentHashMap<String, RestServer>();/* * 服务器工厂 /private final RestServerFactory serverFactory = new RestServerFactory();// TODO in the future maybe we can just use a single rest client and connection manager/* * 客户端集合 /private final List<ResteasyClient> clients = Collections.synchronizedList(new LinkedList<ResteasyClient>());/* * 连接监控 /private volatile ConnectionMonitor connectionMonitor;2.doExport@Overrideprotected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException { // 获得地址 String addr = getAddr(url); // 获得实现类 Class implClass = (Class) StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).get(url.getServiceKey()); // 获得服务 RestServer server = servers.get(addr); if (server == null) { // 创建服务器 server = serverFactory.createServer(url.getParameter(Constants.SERVER_KEY, “jetty”)); // 开启服务器 server.start(url); // 加入集合 servers.put(addr, server); } // 获得contextPath String contextPath = getContextPath(url); // 如果以servlet的方式 if (“servlet”.equalsIgnoreCase(url.getParameter(Constants.SERVER_KEY, “jetty”))) { // 获得ServletContext ServletContext servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT); // 如果为空,则抛出异常 if (servletContext == null) { throw new RpcException(“No servlet context found. Since you are using server=‘servlet’, " + “make sure that you’ve configured " + BootstrapListener.class.getName() + " in web.xml”); } String webappPath = servletContext.getContextPath(); if (StringUtils.isNotEmpty(webappPath)) { // 检测配置是否正确 webappPath = webappPath.substring(1); if (!contextPath.startsWith(webappPath)) { throw new RpcException(“Since you are using server=‘servlet’, " + “make sure that the ‘contextpath’ property starts with the path of external webapp”); } contextPath = contextPath.substring(webappPath.length()); if (contextPath.startsWith(”/”)) { contextPath = contextPath.substring(1); } } } // 获得资源 final Class resourceDef = GetRestful.getRootResourceClass(implClass) != null ? implClass : type; // 部署服务器 server.deploy(resourceDef, impl, contextPath); final RestServer s = server; return new Runnable() { @Override public void run() { // TODO due to dubbo’s current architecture, // it will be called from registry protocol in the shutdown process and won’t appear in logs s.undeploy(resourceDef); } };}该方法是服务暴露的方法。3.doReferprotected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException { // 如果连接监控为空,则创建 if (connectionMonitor == null) { connectionMonitor = new ConnectionMonitor(); } // TODO more configs to add // 创建http连接池 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); // 20 is the default maxTotal of current PoolingClientConnectionManager // 最大连接数 connectionManager.setMaxTotal(url.getParameter(Constants.CONNECTIONS_KEY, 20)); // 最大的路由数 connectionManager.setDefaultMaxPerRoute(url.getParameter(Constants.CONNECTIONS_KEY, 20)); // 添加监控 connectionMonitor.addConnectionManager(connectionManager); // 新建请求配置 RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT)) .setSocketTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)) .build(); // 设置socket配置 SocketConfig socketConfig = SocketConfig.custom() .setSoKeepAlive(true) .setTcpNoDelay(true) .build(); // 创建http客户端 CloseableHttpClient httpClient = HttpClientBuilder.create() .setKeepAliveStrategy(new ConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase(“timeout”)) { return Long.parseLong(value) * 1000; } } // TODO constant return 30 * 1000; } }) .setDefaultRequestConfig(requestConfig) .setDefaultSocketConfig(socketConfig) .build(); // 创建ApacheHttpClient4Engine对应,为了使用resteasy ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient/, localContext*/); // 创建ResteasyClient对象 ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build(); // 加入集合 clients.add(client); // 设置过滤器 client.register(RpcContextFilter.class); // 注册各类组件 for (String clazz : Constants.COMMA_SPLIT_PATTERN.split(url.getParameter(Constants.EXTENSION_KEY, “”))) { if (!StringUtils.isEmpty(clazz)) { try { client.register(Thread.currentThread().getContextClassLoader().loadClass(clazz.trim())); } catch (ClassNotFoundException e) { throw new RpcException(“Error loading JAX-RS extension class: " + clazz.trim(), e); } } } // TODO protocol // 创建 Service Proxy 对象。 ResteasyWebTarget target = client.target(“http://” + url.getHost() + “:” + url.getPort() + “/” + getContextPath(url)); return target.proxy(serviceType);}该方法是服务引用的实现。4.ConnectionMonitorprotected class ConnectionMonitor extends Thread { /** * 是否关闭 / private volatile boolean shutdown; /* * 连接池集合 */ private final List<PoolingHttpClientConnectionManager> connectionManagers = Collections.synchronizedList(new LinkedList<PoolingHttpClientConnectionManager>()); public void addConnectionManager(PoolingHttpClientConnectionManager connectionManager) { connectionManagers.add(connectionManager); } @Override public void run() { try { while (!shutdown) { synchronized (this) { wait(1000); for (PoolingHttpClientConnectionManager connectionManager : connectionManagers) { // 关闭池中所有过期的连接 connectionManager.closeExpiredConnections(); // TODO constant // 关闭池中的空闲连接 connectionManager.closeIdleConnections(30, TimeUnit.SECONDS); } } } } catch (InterruptedException ex) { // 关闭 shutdown(); } } public void shutdown() { shutdown = true; connectionManagers.clear(); synchronized (this) { notifyAll(); } }}该内部类是处理连接的监控类,当连接过期获取空间的时候,关闭它。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于rest协议实现的部分,关键是要对Resteasy的使用需要有所了解,其他的思路跟其他协议实现差距不大。接下来我将开始对rpc模块关于rmi协议部分进行讲解。 ...

January 29, 2019 · 8 min · jiezi

dubbo源码解析(二十九)远程调用——redis协议

远程调用——redis协议目标:介绍redis协议的设计和实现,介绍dubbo-rpc-redis的源码。前言dubbo支持的redis协议是基于Redis的,Redis 是一个高效的 KV 存储服务器,跟memcached协议实现差不多,在dubbo中也没有涉及到关于redis协议的服务暴露,只有服务引用,因为在访问服务器时,Redis客户端可以在服务器上存储也可以获取。源码分析(一)RedisProtocol该类继承了AbstractProtocol类,是redis协议实现的核心。1.属性/** * 默认端口号 */public static final int DEFAULT_PORT = 6379;2.export@Overridepublic <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException { // 不支持redis协议的服务暴露,抛出异常 throw new UnsupportedOperationException(“Unsupported export redis service. url: " + invoker.getUrl());}可以看到不支持服务暴露。3.refer@Overridepublic <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException { try { // 实例化对象池 GenericObjectPoolConfig config = new GenericObjectPoolConfig(); // 如果 testOnBorrow 被设置,pool 会在 borrowObject 返回对象之前使用 PoolableObjectFactory的 validateObject 来验证这个对象是否有效 // 要是对象没通过验证,这个对象会被丢弃,然后重新选择一个新的对象。 config.setTestOnBorrow(url.getParameter(“test.on.borrow”, true)); // 如果 testOnReturn 被设置, pool 会在 returnObject 的时候通过 PoolableObjectFactory 的validateObject 方法验证对象 // 如果对象没通过验证,对象会被丢弃,不会被放到池中。 config.setTestOnReturn(url.getParameter(“test.on.return”, false)); // 指定空闲对象是否应该使用 PoolableObjectFactory 的 validateObject 校验,如果校验失败,这个对象会从对象池中被清除。 // 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0) 的时候才会生效。 config.setTestWhileIdle(url.getParameter(“test.while.idle”, false)); if (url.getParameter(“max.idle”, 0) > 0) // 控制一个pool最多有多少个状态为空闲的jedis实例。 config.setMaxIdle(url.getParameter(“max.idle”, 0)); if (url.getParameter(“min.idle”, 0) > 0) // 控制一个pool最少有多少个状态为空闲的jedis实例。 config.setMinIdle(url.getParameter(“min.idle”, 0)); if (url.getParameter(“max.active”, 0) > 0) // 控制一个pool最多有多少个jedis实例。 config.setMaxTotal(url.getParameter(“max.active”, 0)); if (url.getParameter(“max.total”, 0) > 0) config.setMaxTotal(url.getParameter(“max.total”, 0)); if (url.getParameter(“max.wait”, 0) > 0) //表示当引入一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException; config.setMaxWaitMillis(url.getParameter(“max.wait”, 0)); if (url.getParameter(“num.tests.per.eviction.run”, 0) > 0) // 设置驱逐线程每次检测对象的数量。这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。 config.setNumTestsPerEvictionRun(url.getParameter(“num.tests.per.eviction.run”, 0)); if (url.getParameter(“time.between.eviction.runs.millis”, 0) > 0) // 指定驱逐线程的休眠时间。如果这个值不是正数( >0),不会有驱逐线程运行。 config.setTimeBetweenEvictionRunsMillis(url.getParameter(“time.between.eviction.runs.millis”, 0)); if (url.getParameter(“min.evictable.idle.time.millis”, 0) > 0) // 指定最小的空闲驱逐的时间间隔(空闲超过指定的时间的对象,会被清除掉)。 // 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。 config.setMinEvictableIdleTimeMillis(url.getParameter(“min.evictable.idle.time.millis”, 0)); // 创建redis连接池 final JedisPool jedisPool = new JedisPool(config, url.getHost(), url.getPort(DEFAULT_PORT), url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT), StringUtils.isBlank(url.getPassword()) ? null : url.getPassword(), url.getParameter(“db.index”, 0)); // 获得值的过期时间 final int expiry = url.getParameter(“expiry”, 0); // 获得get命令 final String get = url.getParameter(“get”, “get”); // 获得set命令 final String set = url.getParameter(“set”, Map.class.equals(type) ? “put” : “set”); // 获得delete命令 final String delete = url.getParameter(“delete”, Map.class.equals(type) ? “remove” : “delete”); return new AbstractInvoker<T>(type, url) { @Override protected Result doInvoke(Invocation invocation) throws Throwable { Jedis resource = null; try { resource = jedisPool.getResource(); // 如果是get命令 if (get.equals(invocation.getMethodName())) { // get 命令必须只有一个参数 if (invocation.getArguments().length != 1) { throw new IllegalArgumentException(“The redis get method arguments mismatch, must only one arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // 获得值 byte[] value = resource.get(String.valueOf(invocation.getArguments()[0]).getBytes()); if (value == null) { return new RpcResult(); } // 反序列化 ObjectInput oin = getSerialization(url).deserialize(url, new ByteArrayInputStream(value)); return new RpcResult(oin.readObject()); } else if (set.equals(invocation.getMethodName())) { // 如果是set命令,参数长度必须是2 if (invocation.getArguments().length != 2) { throw new IllegalArgumentException(“The redis set method arguments mismatch, must be two arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // byte[] key = String.valueOf(invocation.getArguments()[0]).getBytes(); ByteArrayOutputStream output = new ByteArrayOutputStream(); // 对需要存入对值进行序列化 ObjectOutput value = getSerialization(url).serialize(url, output); value.writeObject(invocation.getArguments()[1]); // 存入值 resource.set(key, output.toByteArray()); // 设置该key过期时间,不能大于1000s if (expiry > 1000) { resource.expire(key, expiry / 1000); } return new RpcResult(); } else if (delete.equals(invocation.getMethodName())) { // 如果是删除命令,则参数长度必须是1 if (invocation.getArguments().length != 1) { throw new IllegalArgumentException(“The redis delete method arguments mismatch, must only one arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // 删除该值 resource.del(String.valueOf(invocation.getArguments()[0]).getBytes()); return new RpcResult(); } else { // 否则抛出该操作不支持的异常 throw new UnsupportedOperationException(“Unsupported method " + invocation.getMethodName() + " in redis service.”); } } catch (Throwable t) { RpcException re = new RpcException(“Failed to invoke redis service method. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url + “, cause: " + t.getMessage(), t); if (t instanceof TimeoutException || t instanceof SocketTimeoutException) { // 抛出超时异常 re.setCode(RpcException.TIMEOUT_EXCEPTION); } else if (t instanceof JedisConnectionException || t instanceof IOException) { // 抛出网络异常 re.setCode(RpcException.NETWORK_EXCEPTION); } else if (t instanceof JedisDataException) { // 抛出序列化异常 re.setCode(RpcException.SERIALIZATION_EXCEPTION); } throw re; } finally { if (resource != null) { try { jedisPool.returnResource(resource); } catch (Throwable t) { logger.warn(“returnResource error: " + t.getMessage(), t); } } } } @Override public void destroy() { super.destroy(); try { // 关闭连接池 jedisPool.destroy(); } catch (Throwable e) { logger.warn(e.getMessage(), e); } } }; } catch (Throwable t) { throw new RpcException(“Failed to refer redis service. interface: " + type.getName() + “, url: " + url + “, cause: " + t.getMessage(), t); }}可以看到首先是对连接池的配置赋值,然后创建连接池后,根据redis的get、set、delete命令来进行相关操作。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于redis协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于rest协议部分进行讲解。 ...

January 28, 2019 · 3 min · jiezi

dubbo源码解析(二十八)远程调用——memcached协议

远程调用——memcached协议目标:介绍memcached协议的设计和实现,介绍dubbo-rpc-memcached的源码。前言dubbo实现memcached协议是基于Memcached,Memcached 是一个高效的 KV 缓存服务器,在dubbo中没有涉及到关于memcached协议的服务暴露,只有服务引用,因为在访问Memcached服务器时,Memcached客户端可以在服务器上存储也可以获取。源码分析(一)MemcachedProtocol该类继承AbstractProtocol,是memcached协议实现的核心。1.属性/** * 默认端口号 */public static final int DEFAULT_PORT = 11211;2.export@Overridepublic <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException { // 不支持memcached服务暴露 throw new UnsupportedOperationException(“Unsupported export memcached service. url: " + invoker.getUrl());}可以看到,服务暴露方法直接抛出异常。3.refer@Overridepublic <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException { try { // 获得地址 String address = url.getAddress(); // 获得备用地址 String backup = url.getParameter(Constants.BACKUP_KEY); // 把备用地址拼接上 if (backup != null && backup.length() > 0) { address += “,” + backup; } // 创建Memcached客户端构造器 MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(address)); // 创建客户端 final MemcachedClient memcachedClient = builder.build(); // 到期时间参数配置 final int expiry = url.getParameter(“expiry”, 0); // 获得值命令 final String get = url.getParameter(“get”, “get”); // 添加值命令根据类型来取决是put还是set final String set = url.getParameter(“set”, Map.class.equals(type) ? “put” : “set”); // 删除值命令 final String delete = url.getParameter(“delete”, Map.class.equals(type) ? “remove” : “delete”); return new AbstractInvoker<T>(type, url) { @Override protected Result doInvoke(Invocation invocation) throws Throwable { try { // 如果是获取方法名的值 if (get.equals(invocation.getMethodName())) { // 如果参数长度不等于1,则抛出异常 if (invocation.getArguments().length != 1) { throw new IllegalArgumentException(“The memcached get method arguments mismatch, must only one arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // 否则调用get方法来获取 return new RpcResult(memcachedClient.get(String.valueOf(invocation.getArguments()[0]))); } else if (set.equals(invocation.getMethodName())) { // 如果参数长度不为2,则抛出异常 if (invocation.getArguments().length != 2) { throw new IllegalArgumentException(“The memcached set method arguments mismatch, must be two arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // 无论任何现有值如何,都在缓存中设置一个对象 memcachedClient.set(String.valueOf(invocation.getArguments()[0]), expiry, invocation.getArguments()[1]); return new RpcResult(); } else if (delete.equals(invocation.getMethodName())) { // 删除操作只有一个参数,如果参数长度不等于1,则抛出异常 if (invocation.getArguments().length != 1) { throw new IllegalArgumentException(“The memcached delete method arguments mismatch, must only one arguments. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url); } // 删除某个值 memcachedClient.delete(String.valueOf(invocation.getArguments()[0])); return new RpcResult(); } else { // 不支持的操作 throw new UnsupportedOperationException(“Unsupported method " + invocation.getMethodName() + " in memcached service.”); } } catch (Throwable t) { RpcException re = new RpcException(“Failed to invoke memcached service method. interface: " + type.getName() + “, method: " + invocation.getMethodName() + “, url: " + url + “, cause: " + t.getMessage(), t); if (t instanceof TimeoutException || t instanceof SocketTimeoutException) { re.setCode(RpcException.TIMEOUT_EXCEPTION); } else if (t instanceof MemcachedException || t instanceof IOException) { re.setCode(RpcException.NETWORK_EXCEPTION); } throw re; } } @Override public void destroy() { super.destroy(); try { // 关闭客户端 memcachedClient.shutdown(); } catch (Throwable e) { logger.warn(e.getMessage(), e); } } }; } catch (Throwable t) { throw new RpcException(“Failed to refer memcached service. interface: " + type.getName() + “, url: " + url + “, cause: " + t.getMessage(), t); }}该方法是服务引用方法,基于MemcachedClient的get、set、delete方法来对应Memcached的get、set、delete命令进行对值的操作。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于memcached协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于redis协议部分进行讲解。 ...

January 27, 2019 · 2 min · jiezi

dubbo源码解析(二十七)远程调用——injvm本地调用

远程调用——injvm本地调用目标:介绍injvm本地调用的设计和实现,介绍dubbo-rpc-injvm的源码。前言dubbo是一个远程调用的框架,但是它没有理由不支持本地调用,本文就要讲解dubbo关于本地调用的实现。本地调用要比远程调用简单的多。源码分析(一)InjvmExporter该类继承了AbstractExporter,是本地服务的暴露者封装,其中实现比较简单。只是实现了unexport方法,并且维护了一份保存暴露者的集合。class InjvmExporter<T> extends AbstractExporter<T> { /** * 服务key / private final String key; /* * 暴露者集合 / private final Map<String, Exporter<?>> exporterMap; InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) { super(invoker); this.key = key; this.exporterMap = exporterMap; exporterMap.put(key, this); } /* * 取消暴露 / @Override public void unexport() { // 调用父类的取消暴露方法 super.unexport(); // 从集合中移除 exporterMap.remove(key); }}(二)InjvmInvoker该类继承了AbstractInvoker类,是本地调用的invoker实现。class InjvmInvoker<T> extends AbstractInvoker<T> { /* * 服务key / private final String key; /* * 暴露者集合 / private final Map<String, Exporter<?>> exporterMap; InjvmInvoker(Class<T> type, URL url, String key, Map<String, Exporter<?>> exporterMap) { super(type, url); this.key = key; this.exporterMap = exporterMap; } /* * 服务是否活跃 * @return / @Override public boolean isAvailable() { InjvmExporter<?> exporter = (InjvmExporter<?>) exporterMap.get(key); if (exporter == null) { return false; } else { return super.isAvailable(); } } /* * invoke方法 * @param invocation * @return * @throws Throwable / @Override public Result doInvoke(Invocation invocation) throws Throwable { // 获得暴露者 Exporter<?> exporter = InjvmProtocol.getExporter(exporterMap, getUrl()); // 如果为空,则抛出异常 if (exporter == null) { throw new RpcException(“Service [” + key + “] not found.”); } // 设置远程地址为127.0.0.1 RpcContext.getContext().setRemoteAddress(NetUtils.LOCALHOST, 0); // 调用下一个调用链 return exporter.getInvoker().invoke(invocation); }}其中重写了isAvailable和doInvoke方法。(三)InjvmProtocol该类是本地调用的协议实现,其中实现了服务调用和服务暴露方法,并且封装了一个判断是否是本地调用的方法。1.属性/* * 本地调用 Protocol的实现类key /public static final String NAME = Constants.LOCAL_PROTOCOL;/* * 默认端口 /public static final int DEFAULT_PORT = 0;/* * 单例 /private static InjvmProtocol INSTANCE;2.getExporterstatic Exporter<?> getExporter(Map<String, Exporter<?>> map, URL key) { Exporter<?> result = null; // 如果服务key不是 if (!key.getServiceKey().contains("*")) { // 直接从集合中取出 result = map.get(key.getServiceKey()); } else { // 如果 map不为空,则遍历暴露者,来找到对应的exporter if (map != null && !map.isEmpty()) { for (Exporter<?> exporter : map.values()) { // 如果是服务key if (UrlUtils.isServiceKeyMatch(key, exporter.getInvoker().getUrl())) { // 赋值 result = exporter; break; } } } } // 如果没有找到exporter if (result == null) { // 则返回null return null; } else if (ProtocolUtils.isGeneric( result.getInvoker().getUrl().getParameter(Constants.GENERIC_KEY))) { // 如果是泛化调用,则返回null return null; } else { return result; }}该方法是获得相关的暴露者。3.export@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // 创建InjvmExporter 并且返回 return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);}4.refer@Overridepublic <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { // 创建InjvmInvoker 并且返回 return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);}5.isInjvmReferpublic boolean isInjvmRefer(URL url) { final boolean isJvmRefer; // 获得scope配置 String scope = url.getParameter(Constants.SCOPE_KEY); // Since injvm protocol is configured explicitly, we don’t need to set any extra flag, use normal refer process. if (Constants.LOCAL_PROTOCOL.toString().equals(url.getProtocol())) { // 如果是injvm,则不是本地调用 isJvmRefer = false; } else if (Constants.SCOPE_LOCAL.equals(scope) || (url.getParameter(“injvm”, false))) { // if it’s declared as local reference // ‘scope=local’ is equivalent to ‘injvm=true’, injvm will be deprecated in the future release // 如果它被声明为本地引用 scope = local’相当于’injvm = true’,将在以后的版本中弃用injvm isJvmRefer = true; } else if (Constants.SCOPE_REMOTE.equals(scope)) { // it’s declared as remote reference // 如果被声明为远程调用 isJvmRefer = false; } else if (url.getParameter(Constants.GENERIC_KEY, false)) { // generic invocation is not local reference // 泛化的调用不是本地调用 isJvmRefer = false; } else if (getExporter(exporterMap, url) != null) { // by default, go through local reference if there’s the service exposed locally // 默认情况下,如果本地暴露服务,请通过本地引用 isJvmRefer = true; } else { isJvmRefer = false; } return isJvmRefer;}该方法是判断是否为本地调用。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于injvm本地调用的部分,三种抽象的角色还是比较鲜明的,服务暴露相关的exporter、服务引用相关的invoker、以及协议相关的protocol,关键还是弄清楚再设计上的意图,以及他们分别代表的是什么。那么看这些不同的协议实现会很容易看懂。接下来我将开始对rpc模块关于memcached协议部分进行讲解。 ...

January 25, 2019 · 3 min · jiezi

dubbo源码解析(二十六)远程调用——http协议

远程调用——http协议目标:介绍远程调用中跟http协议相关的设计和实现,介绍dubbo-rpc-http的源码。前言基于HTTP表单的远程调用协议,采用 Spring 的HttpInvoker实现,关于http协议就不用多说了吧。源码分析(一)HttpRemoteInvocation该类继承了RemoteInvocation类,是在RemoteInvocation上增加了泛化调用的参数设置,以及增加了dubbo本身需要的附加值设置。public class HttpRemoteInvocation extends RemoteInvocation { private static final long serialVersionUID = 1L; /** * dubbo的附加值名称 / private static final String dubboAttachmentsAttrName = “dubbo.attachments”; public HttpRemoteInvocation(MethodInvocation methodInvocation) { super(methodInvocation); // 把附加值加入到会话域的属性里面 addAttribute(dubboAttachmentsAttrName, new HashMap<String, String>(RpcContext.getContext().getAttachments())); } @Override public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { // 获得上下文 RpcContext context = RpcContext.getContext(); // 获得附加值 context.setAttachments((Map<String, String>) getAttribute(dubboAttachmentsAttrName)); // 泛化标志 String generic = (String) getAttribute(Constants.GENERIC_KEY); // 如果不为空,则设置泛化标志 if (StringUtils.isNotEmpty(generic)) { context.setAttachment(Constants.GENERIC_KEY, generic); } try { // 调用下一个调用链 return super.invoke(targetObject); } finally { context.setAttachments(null); } }}(二)HttpProtocol该类是http实现的核心,跟我在《dubbo源码解析(二十五)远程调用——hessian协议》中讲到的HessianProtocol实现有很多地方相似。1.属性/* * 默认的端口号 /public static final int DEFAULT_PORT = 80;/* * http服务器集合 /private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();/* * Spring HttpInvokerServiceExporter 集合 /private final Map<String, HttpInvokerServiceExporter> skeletonMap = new ConcurrentHashMap<String, HttpInvokerServiceExporter>();/* * HttpBinder对象 /private HttpBinder httpBinder;2.doExport@Overrideprotected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException { // 获得ip地址 String addr = getAddr(url); // 获得http服务器 HttpServer server = serverMap.get(addr); // 如果服务器为空,则重新创建服务器,并且加入到集合 if (server == null) { server = httpBinder.bind(url, new InternalHandler()); serverMap.put(addr, server); } // 获得服务path final String path = url.getAbsolutePath(); // 加入集合 skeletonMap.put(path, createExporter(impl, type)); // 通用path final String genericPath = path + “/” + Constants.GENERIC_KEY; // 添加泛化的服务调用 skeletonMap.put(genericPath, createExporter(impl, GenericService.class)); return new Runnable() { @Override public void run() { skeletonMap.remove(path); skeletonMap.remove(genericPath); } };}该方法是暴露服务等逻辑,因为dubbo实现http协议采用了Spring 的HttpInvoker实现,所以调用了createExporter方法来创建创建HttpInvokerServiceExporter。3.createExporterprivate <T> HttpInvokerServiceExporter createExporter(T impl, Class<?> type) { // 创建HttpInvokerServiceExporter final HttpInvokerServiceExporter httpServiceExporter = new HttpInvokerServiceExporter(); // 设置要访问的服务的接口 httpServiceExporter.setServiceInterface(type); // 设置服务实现 httpServiceExporter.setService(impl); try { // 在BeanFactory设置了所有提供的bean属性,初始化bean的时候执行,可以针对某个具体的bean进行配 httpServiceExporter.afterPropertiesSet(); } catch (Exception e) { throw new RpcException(e.getMessage(), e); } return httpServiceExporter;}该方法是创建一个spring 的HttpInvokerServiceExporter。4.doRefer@Override@SuppressWarnings(“unchecked”)protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException { // 获得泛化配置 final String generic = url.getParameter(Constants.GENERIC_KEY); // 是否为泛化调用 final boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class); // 创建HttpInvokerProxyFactoryBean final HttpInvokerProxyFactoryBean httpProxyFactoryBean = new HttpInvokerProxyFactoryBean(); // 设置RemoteInvocation的工厂类 httpProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() { /* * 为给定的AOP方法调用创建一个新的RemoteInvocation对象。 * @param methodInvocation * @return */ @Override public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) { // 新建一个HttpRemoteInvocation RemoteInvocation invocation = new HttpRemoteInvocation(methodInvocation); // 如果是泛化调用 if (isGeneric) { // 设置标志 invocation.addAttribute(Constants.GENERIC_KEY, generic); } return invocation; } }); // 获得identity message String key = url.toIdentityString(); // 如果是泛化调用 if (isGeneric) { key = key + “/” + Constants.GENERIC_KEY; } // 设置服务url httpProxyFactoryBean.setServiceUrl(key); // 设置服务接口 httpProxyFactoryBean.setServiceInterface(serviceType); // 获得客户端参数 String client = url.getParameter(Constants.CLIENT_KEY); if (client == null || client.length() == 0 || “simple”.equals(client)) { // 创建SimpleHttpInvokerRequestExecutor连接池 使用的是JDK HttpClient SimpleHttpInvokerRequestExecutor httpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor() { @Override protected void prepareConnection(HttpURLConnection con, int contentLength) throws IOException { super.prepareConnection(con, contentLength); // 设置读取超时时间 con.setReadTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)); // 设置连接超时时间 con.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT)); } }; httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor); } else if (“commons”.equals(client)) { // 创建 HttpComponentsHttpInvokerRequestExecutor连接池 使用的是Apache HttpClient HttpComponentsHttpInvokerRequestExecutor httpInvokerRequestExecutor = new HttpComponentsHttpInvokerRequestExecutor(); // 设置读取超时时间 httpInvokerRequestExecutor.setReadTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)); // 设置连接超时时间 httpInvokerRequestExecutor.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT)); httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor); } else { throw new IllegalStateException(“Unsupported http protocol client " + client + “, only supported: simple, commons”); } httpProxyFactoryBean.afterPropertiesSet(); // 返回HttpInvokerProxyFactoryBean对象 return (T) httpProxyFactoryBean.getObject();}该方法是服务引用的方法,其中根据url配置simple还是commons来选择创建连接池的方式。其中的区别就是SimpleHttpInvokerRequestExecutor使用的是JDK HttpClient,HttpComponentsHttpInvokerRequestExecutor 使用的是Apache HttpClient。5.getErrorCode@Overrideprotected int getErrorCode(Throwable e) { if (e instanceof RemoteAccessException) { e = e.getCause(); } if (e != null) { Class<?> cls = e.getClass(); if (SocketTimeoutException.class.equals(cls)) { // 返回超时异常 return RpcException.TIMEOUT_EXCEPTION; } else if (IOException.class.isAssignableFrom(cls)) { // 返回网络异常 return RpcException.NETWORK_EXCEPTION; } else if (ClassNotFoundException.class.isAssignableFrom(cls)) { // 返回序列化异常 return RpcException.SERIALIZATION_EXCEPTION; } } return super.getErrorCode(e);}该方法是处理异常情况,设置错误码。6.InternalHandlerprivate class InternalHandler implements HttpHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 获得请求uri String uri = request.getRequestURI(); // 获得服务暴露者HttpInvokerServiceExporter对象 HttpInvokerServiceExporter skeleton = skeletonMap.get(uri); // 如果不是post,则返回码设置500 if (!request.getMethod().equalsIgnoreCase(“POST”)) { response.setStatus(500); } else { // 远程地址放到上下文 RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); try { // 调用下一个调用 skeleton.handleRequest(request, response); } catch (Throwable e) { throw new ServletException(e); } } }}该内部类实现了HttpHandler,做了设置远程地址的逻辑。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于http协议的部分,内容比较简单,可以参考着官方文档了解一下。接下来我将开始对rpc模块的dubbo-rpc-dubbo关于injvm本地调用部分进行讲解。 ...

January 24, 2019 · 3 min · jiezi

dubbo源码解析(二十五)远程调用——hessian协议

远程调用——hessian协议目标:介绍远程调用中跟hessian协议相关的设计和实现,介绍dubbo-rpc-hessian的源码。前言本文讲解多是dubbo集成的第二种协议,hessian协议,Hessian 是 Caucho 开源的一个 RPC 框架,其通讯效率高于 WebService 和 Java 自带的序列化。dubbo集成hessian所提供的hessian协议相关介绍可以参考官方文档,我就不再赘述。文档地址:http://dubbo.apache.org/zh-cn…源码分析(一)DubboHessianURLConnectionFactory该类继承了HessianURLConnectionFactory类,是dubbo,用于创建与服务器的连接的内部工厂,重写了父类中open方法。public class DubboHessianURLConnectionFactory extends HessianURLConnectionFactory { /** * 打开与HTTP服务器的新连接或循环连接 * @param url * @return * @throws IOException / @Override public HessianConnection open(URL url) throws IOException { // 获得一个连接 HessianConnection connection = super.open(url); // 获得上下文 RpcContext context = RpcContext.getContext(); for (String key : context.getAttachments().keySet()) { // 在http协议头里面加入dubbo中附加值,key为 header+key value为附加值的value connection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key)); } return connection; }}在hessian上加入dubbo自己所需要的附加值,放到协议头里面进行发送。(二)HttpClientConnection该类是基于HttpClient封装来实现HessianConnection接口,其中逻辑比较简单。public class HttpClientConnection implements HessianConnection { /* * http客户端对象 / private final HttpClient httpClient; /* * 字节输出流 / private final ByteArrayOutputStream output; /* * http post请求对象 / private final HttpPost request; /* * http 响应对象 / private volatile HttpResponse response; public HttpClientConnection(HttpClient httpClient, URL url) { this.httpClient = httpClient; this.output = new ByteArrayOutputStream(); this.request = new HttpPost(url.toString()); } /* * 增加协议头 * @param key * @param value / @Override public void addHeader(String key, String value) { request.addHeader(new BasicHeader(key, value)); } @Override public OutputStream getOutputStream() throws IOException { return output; } /* * 发送请求 * @throws IOException / @Override public void sendRequest() throws IOException { request.setEntity(new ByteArrayEntity(output.toByteArray())); this.response = httpClient.execute(request); } /* * 获得请求后的状态码 * @return / @Override public int getStatusCode() { return response == null || response.getStatusLine() == null ? 0 : response.getStatusLine().getStatusCode(); } @Override public String getStatusMessage() { return response == null || response.getStatusLine() == null ? null : response.getStatusLine().getReasonPhrase(); } @Override public String getContentEncoding() { return (response == null || response.getEntity() == null || response.getEntity().getContentEncoding() == null) ? null : response.getEntity().getContentEncoding().getValue(); } @Override public InputStream getInputStream() throws IOException { return response == null || response.getEntity() == null ? null : response.getEntity().getContent(); } @Override public void close() throws IOException { HttpPost request = this.request; if (request != null) { request.abort(); } } @Override public void destroy() throws IOException { }(三)HttpClientConnectionFactory该类实现了HessianConnectionFactory接口,是创建HttpClientConnection的工厂类。该类的实现跟DubboHessianURLConnectionFactory类类似,但是DubboHessianURLConnectionFactory是标准的Hessian接口调用会采用的工厂类,而HttpClientConnectionFactory是Dubbo 的 Hessian 协议调用。当然Dubbo 的 Hessian 协议也是基于http的。public class HttpClientConnectionFactory implements HessianConnectionFactory { /* * httpClient对象 / private final HttpClient httpClient = new DefaultHttpClient(); @Override public void setHessianProxyFactory(HessianProxyFactory factory) { // 设置连接超时时间 HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), (int) factory.getConnectTimeout()); // 设置读取数据时阻塞链路的超时时间 HttpConnectionParams.setSoTimeout(httpClient.getParams(), (int) factory.getReadTimeout()); } @Override public HessianConnection open(URL url) throws IOException { // 创建一个HttpClientConnection实例 HttpClientConnection httpClientConnection = new HttpClientConnection(httpClient, url); // 获得上下文,用来获得附加值 RpcContext context = RpcContext.getContext(); // 遍历附加值,放入到协议头里面 for (String key : context.getAttachments().keySet()) { httpClientConnection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key)); } return httpClientConnection; }}实现了两个方法,第一个方法是给http连接设置两个参数配置,第二个方法是创建一个连接。(四)HessianProtocol该类继承了AbstractProxyProtocol类,是hessian协议的实现类。其中实现类基于hessian协议的服务引用、服务暴露等方法。1.属性/* * http服务器集合 * key为ip:port /private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();/* * HessianSkeleto 集合 * key为服务名 /private final Map<String, HessianSkeleton> skeletonMap = new ConcurrentHashMap<String, HessianSkeleton>();/* * HttpBinder对象,默认是jetty实现 */private HttpBinder httpBinder;2.doExport@Overrideprotected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException { // 获得ip地址 String addr = getAddr(url); // 获得http服务器对象 HttpServer server = serverMap.get(addr); // 如果为空,则重新创建一个server,然后放入集合 if (server == null) { server = httpBinder.bind(url, new HessianHandler()); serverMap.put(addr, server); } // 获得服务path final String path = url.getAbsolutePath(); // 创建Hessian服务端对象 final HessianSkeleton skeleton = new HessianSkeleton(impl, type); // 加入集合 skeletonMap.put(path, skeleton); // 获得通用的path final String genericPath = path + “/” + Constants.GENERIC_KEY; // 加入集合 skeletonMap.put(genericPath, new HessianSkeleton(impl, GenericService.class)); // 返回一个线程 return new Runnable() { @Override public void run() { skeletonMap.remove(path); skeletonMap.remove(genericPath); } };}该方法是服务暴露的主要逻辑实现。3.doRefer@Override@SuppressWarnings(“unchecked”)protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException { // 获得泛化的参数 String generic = url.getParameter(Constants.GENERIC_KEY); // 是否是泛化调用 boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class); // 如果是泛化调用。则设置泛化的path和附加值 if (isGeneric) { RpcContext.getContext().setAttachment(Constants.GENERIC_KEY, generic); url = url.setPath(url.getPath() + “/” + Constants.GENERIC_KEY); } // 创建代理工厂 HessianProxyFactory hessianProxyFactory = new HessianProxyFactory(); // 是否是Hessian2的请求 默认为否 boolean isHessian2Request = url.getParameter(Constants.HESSIAN2_REQUEST_KEY, Constants.DEFAULT_HESSIAN2_REQUEST); // 设置是否应使用Hessian协议的版本2来解析请求 hessianProxyFactory.setHessian2Request(isHessian2Request); // 是否应为远程调用启用重载方法,默认为否 boolean isOverloadEnabled = url.getParameter(Constants.HESSIAN_OVERLOAD_METHOD_KEY, Constants.DEFAULT_HESSIAN_OVERLOAD_METHOD); // 设置是否应为远程调用启用重载方法。 hessianProxyFactory.setOverloadEnabled(isOverloadEnabled); // 获得client实现方式,默认为jdk String client = url.getParameter(Constants.CLIENT_KEY, Constants.DEFAULT_HTTP_CLIENT); if (“httpclient”.equals(client)) { // 用http来创建 hessianProxyFactory.setConnectionFactory(new HttpClientConnectionFactory()); } else if (client != null && client.length() > 0 && !Constants.DEFAULT_HTTP_CLIENT.equals(client)) { // 抛出不支持的协议异常 throw new IllegalStateException(“Unsupported http protocol client="” + client + “"!”); } else { // 创建一个HessianConnectionFactory对象 HessianConnectionFactory factory = new DubboHessianURLConnectionFactory(); // 设置代理工厂 factory.setHessianProxyFactory(hessianProxyFactory); // 设置工厂 hessianProxyFactory.setConnectionFactory(factory); } // 获得超时时间 int timeout = url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 设置超时时间 hessianProxyFactory.setConnectTimeout(timeout); hessianProxyFactory.setReadTimeout(timeout); // 创建代理 return (T) hessianProxyFactory.create(serviceType, url.setProtocol(“http”).toJavaURL(), Thread.currentThread().getContextClassLoader());}该方法是服务引用的主要逻辑实现,根据客户端配置,来选择标准 Hessian 接口调用还是Dubbo 的 Hessian 协议调用。4.getErrorCode@Overrideprotected int getErrorCode(Throwable e) { // 如果属于HessianConnectionException异常 if (e instanceof HessianConnectionException) { if (e.getCause() != null) { Class<?> cls = e.getCause().getClass(); // 如果属于超时异常,则返回超时异常 if (SocketTimeoutException.class.equals(cls)) { return RpcException.TIMEOUT_EXCEPTION; } } // 否则返回网络异常 return RpcException.NETWORK_EXCEPTION; } else if (e instanceof HessianMethodSerializationException) { // 序列化异常 return RpcException.SERIALIZATION_EXCEPTION; } return super.getErrorCode(e);}该方法是针对异常的处理。5.HessianHandlerprivate class HessianHandler implements HttpHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 获得请求的uri String uri = request.getRequestURI(); // 获得对应的HessianSkeleton对象 HessianSkeleton skeleton = skeletonMap.get(uri); // 如果如果不是post方法 if (!request.getMethod().equalsIgnoreCase(“POST”)) { // 返回状态设置为500 response.setStatus(500); } else { // 设置远程地址 RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); // 获得请求头内容 Enumeration<String> enumeration = request.getHeaderNames(); // 遍历请求头内容 while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); // 如果key开头是deader,则把附加值取出来放入上下文 if (key.startsWith(Constants.DEFAULT_EXCHANGER)) { RpcContext.getContext().setAttachment(key.substring(Constants.DEFAULT_EXCHANGER.length()), request.getHeader(key)); } } try { // 执行下一个 skeleton.invoke(request.getInputStream(), response.getOutputStream()); } catch (Throwable e) { throw new ServletException(e); } } }}该内部类是Hessian的处理器,用来处理请求中的协议头内容。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于hessian协议的部分,内容比较简单,可以参考着官方文档了解一下。接下来我将开始对rpc模块的dubbo-rpc-dubbo关于hessian协议部分进行讲解。 ...

January 23, 2019 · 4 min · jiezi

dubbo源码解析(二十四)远程调用——dubbo协议

远程调用——dubbo协议目标:介绍远程调用中跟dubbo协议相关的设计和实现,介绍dubbo-rpc-dubbo的源码。前言Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。这是官方文档的原话,并且官方文档还介绍了为什么使用单一长连接和 NIO 异步通讯以及为什么不适合传输大数据的服务。我就不赘述了。我们先来看看dubbo-rpc-dubbo下的包结构:filter:该包下面是对于dubbo协议独有的两个过滤器status:该包下是做了对于服务和线程池状态的检测telnet:该包下是对于telnet命令的支持最外层:最外层是dubbo协议的核心源码分析(一)DubboInvoker该类是dubbo协议独自实现的的invoker,其中实现了调用方法的三种模式,分别是异步发送、单向发送和同步发送,具体在下面介绍。1.属性/** * 信息交换客户端数组 /private final ExchangeClient[] clients;/* * 客户端数组位置 /private final AtomicPositiveInteger index = new AtomicPositiveInteger();/* * 版本号 /private final String version;/* * 销毁锁 /private final ReentrantLock destroyLock = new ReentrantLock();/* * Invoker对象集合 /private final Set<Invoker<?>> invokers;2.doInvoke@Overrideprotected Result doInvoke(final Invocation invocation) throws Throwable { // rpc会话域 RpcInvocation inv = (RpcInvocation) invocation; // 获得方法名 final String methodName = RpcUtils.getMethodName(invocation); // 把path放入到附加值中 inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); // 把版本号放入到附加值 inv.setAttachment(Constants.VERSION_KEY, version); // 当前的客户端 ExchangeClient currentClient; // 如果数组内就一个客户端,则直接取出 if (clients.length == 1) { currentClient = clients[0]; } else { // 取模轮询 从数组中取,当取到最后一个时,从头开始 currentClient = clients[index.getAndIncrement() % clients.length]; } try { // 是否启用异步 boolean isAsync = RpcUtils.isAsync(getUrl(), invocation); // 是否是单向发送 boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); // 获得超时时间 int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 如果是单项发送 if (isOneway) { boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false); // 单向发送只负责发送消息,不等待服务端应答,所以没有返回值 currentClient.send(inv, isSent); RpcContext.getContext().setFuture(null); return new RpcResult(); } else if (isAsync) { // 异步调用 ResponseFuture future = currentClient.request(inv, timeout); // 保存future,方便后期处理 RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); return new RpcResult(); } else { // 同步调用,等待返回结果 RpcContext.getContext().setFuture(null); return (Result) currentClient.request(inv, timeout).get(); } } catch (TimeoutException e) { throw new RpcException(RpcException.TIMEOUT_EXCEPTION, “Invoke remote method timeout. method: " + invocation.getMethodName() + “, provider: " + getUrl() + “, cause: " + e.getMessage(), e); } catch (RemotingException e) { throw new RpcException(RpcException.NETWORK_EXCEPTION, “Failed to invoke remote method: " + invocation.getMethodName() + “, provider: " + getUrl() + “, cause: " + e.getMessage(), e); }}在调用invoker的时候,通过远程通信将Invocation信息传递给服务端,服务端在接收到该invocation信息后,要找到对应的本地方法,然后通过反射执行该方法,将方法的执行结果返回给客户端,在这里,客户端发送有三种模式:异步发送,也就是当我发送调用后,我不阻塞等待结果,直接返回,将返回的future保存到上下文,方便后期使用。单向发送,执行方法不需要返回结果。同步发送,执行方法后,等待结果返回,否则一直阻塞。3.isAvailable@Overridepublic boolean isAvailable() { if (!super.isAvailable()) return false; for (ExchangeClient client : clients) { // 只要有一个客户端连接并且不是只读,则表示存活 if (client.isConnected() && !client.hasAttribute(Constants.CHANNEL_ATTRIBUTE_READONLY_KEY)) { //cannot write == not Available ? return true; } } return false;}该方法是检查服务端是否存活。4.destroy@Overridepublic void destroy() { // in order to avoid closing a client multiple times, a counter is used in case of connection per jvm, every // time when client.close() is called, counter counts down once, and when counter reaches zero, client will be // closed. if (super.isDestroyed()) { return; } else { // double check to avoid dup close // 获得销毁锁 destroyLock.lock(); try { if (super.isDestroyed()) { return; } // 销毁 super.destroy(); // 从集合中移除 if (invokers != null) { invokers.remove(this); } for (ExchangeClient client : clients) { try { // 关闭每一个客户端 client.close(ConfigUtils.getServerShutdownTimeout()); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } finally { // 释放锁 destroyLock.unlock(); } }}该方法是销毁服务端,关闭所有连接到远程通信客户端。(二)DubboExporter该类继承了AbstractExporter,是dubbo协议中独有的服务暴露者。/* * 服务key /private final String key;/* * 服务暴露者集合 /private final Map<String, Exporter<?>> exporterMap;public DubboExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) { super(invoker); this.key = key; this.exporterMap = exporterMap;}@Overridepublic void unexport() { super.unexport(); // 从集合中移除该key exporterMap.remove(key);}其中对于服务暴露者用集合做了缓存,并且只重写了了unexport。(三)DubboProtocol该类是dubbo协议的核心实现,其中增加了比如延迟加载等处理。 并且其中还包括了对服务暴露和服务引用的逻辑处理。1.属性public static final String NAME = “dubbo”;/* * 默认端口号 /public static final int DEFAULT_PORT = 20880;/* * 回调名称 /private static final String IS_CALLBACK_SERVICE_INVOKE = “_isCallBackServiceInvoke”;/* * dubbo协议的单例 /private static DubboProtocol INSTANCE;/* * 信息交换服务器集合 key:host:port value:ExchangeServer /private final Map<String, ExchangeServer> serverMap = new ConcurrentHashMap<String, ExchangeServer>(); // <host:port,Exchanger>/* * 信息交换客户端集合 /private final Map<String, ReferenceCountExchangeClient> referenceClientMap = new ConcurrentHashMap<String, ReferenceCountExchangeClient>(); // <host:port,Exchanger>/* * 懒加载的客户端集合 /private final ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap = new ConcurrentHashMap<String, LazyConnectExchangeClient>();/* * 锁集合 /private final ConcurrentMap<String, Object> locks = new ConcurrentHashMap<String, Object>();/* * 序列化类名集合 /private final Set<String> optimizers = new ConcurrentHashSet<String>();//consumer side export a stub service for dispatching event//servicekey-stubmethods/* * 本地存根服务方法集合 /private final ConcurrentMap<String, String> stubServiceMethodsMap = new ConcurrentHashMap<String, String>();/* * 新建一个请求处理器 /private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() { /* * 回复请求结果,返回的是请求结果 * @param channel * @param message * @return * @throws RemotingException / @Override public Object reply(ExchangeChannel channel, Object message) throws RemotingException { // 如果请求消息属于会话域 if (message instanceof Invocation) { Invocation inv = (Invocation) message; // 获得暴露的invoker Invoker<?> invoker = getInvoker(channel, inv); // need to consider backward-compatibility if it’s a callback // 如果是回调服务 if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) { // 获得 方法定义 String methodsStr = invoker.getUrl().getParameters().get(“methods”); boolean hasMethod = false; // 判断看是否有会话域中的方法 if (methodsStr == null || methodsStr.indexOf(”,”) == -1) { hasMethod = inv.getMethodName().equals(methodsStr); } else { // 如果方法不止一个,则分割后遍历查询,找到了则设置为true String[] methods = methodsStr.split(”,”); for (String method : methods) { if (inv.getMethodName().equals(method)) { hasMethod = true; break; } } } // 如果没有该方法,则打印告警日志 if (!hasMethod) { logger.warn(new IllegalStateException(“The methodName " + inv.getMethodName() + " not found in callback service interface ,invoke will be ignored.” + " please update the api interface. url is:” + invoker.getUrl()) + " ,invocation is :” + inv); return null; } } // 设置远程地址 RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); // 调用下一个调用链 return invoker.invoke(inv); } // 否则抛出异常 throw new RemotingException(channel, “Unsupported request: " + (message == null ? null : (message.getClass().getName() + “: " + message)) + “, channel: consumer: " + channel.getRemoteAddress() + " –> provider: " + channel.getLocalAddress()); } /* * 接收消息 * @param channel * @param message * @throws RemotingException / @Override public void received(Channel channel, Object message) throws RemotingException { // 如果消息是会话域中的消息,则调用reply方法。 if (message instanceof Invocation) { reply((ExchangeChannel) channel, message); } else { super.received(channel, message); } } @Override public void connected(Channel channel) throws RemotingException { // 接收连接事件 invoke(channel, Constants.ON_CONNECT_KEY); } @Override public void disconnected(Channel channel) throws RemotingException { if (logger.isInfoEnabled()) { logger.info(“disconnected from " + channel.getRemoteAddress() + “,url:” + channel.getUrl()); } // 接收断开连接事件 invoke(channel, Constants.ON_DISCONNECT_KEY); } /* * 接收事件 * @param channel * @param methodKey / private void invoke(Channel channel, String methodKey) { // 创建会话域 Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey); if (invocation != null) { try { // 接收事件 received(channel, invocation); } catch (Throwable t) { logger.warn(“Failed to invoke event method " + invocation.getMethodName() + “(), cause: " + t.getMessage(), t); } } } /* * 创建会话域, 把url内的值加入到会话域的附加值中 * @param channel * @param url * @param methodKey * @return / private Invocation createInvocation(Channel channel, URL url, String methodKey) { // 获得方法,methodKey是onconnect或者ondisconnect String method = url.getParameter(methodKey); if (method == null || method.length() == 0) { return null; } // 创建一个rpc会话域 RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]); // 加入附加值path invocation.setAttachment(Constants.PATH_KEY, url.getPath()); // 加入附加值group invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY)); // 加入附加值interface invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY)); // 加入附加值version invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY)); // 如果是本地存根服务,则加入附加值dubbo.stub.event为true if (url.getParameter(Constants.STUB_EVENT_KEY, false)) { invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString()); } return invocation; }};该属性中关键的是实例化了一个请求处理器,其中实现了基于dubbo协议等连接、取消连接、回复请求结果等方法。2.export@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // export service. // 得到服务key group+”/"+serviceName+”:"+serviceVersion+”:"+port String key = serviceKey(url); // 创建exporter DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); // 加入到集合 exporterMap.put(key, exporter); //export an stub service for dispatching event Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false); // 如果是本地存根事件而不是回调服务 if (isStubSupportEvent && !isCallbackservice) { // 获得本地存根的方法 String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY); // 如果为空,则抛出异常 if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException(“consumer [” + url.getParameter(Constants.INTERFACE_KEY) + “], has set stubproxy support event ,but no stub methods founded.”)); } } else { // 加入集合 stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); } } // 打开服务 openServer(url); // 序列化 optimizeSerialization(url); return exporter;}该方法是基于dubbo协议的服务暴露,除了对于存根服务和本地服务进行标记以外,打开服务和序列化分别在openServer和optimizeSerialization中实现。3.openServerprivate void openServer(URL url) { // find server. String key = url.getAddress(); //client can export a service which’s only for server to invoke // 客户端是否可以暴露仅供服务器调用的服务 boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true); // 如果是的话 if (isServer) { // 获得信息交换服务器 ExchangeServer server = serverMap.get(key); if (server == null) { // 重新创建服务器对象,然后放入集合 serverMap.put(key, createServer(url)); } else { // server supports reset, use together with override // 重置 server.reset(url); } }}该方法就是打开服务。其中的逻辑其实是把服务对象放入集合中进行缓存,如果该地址对应的服务器不存在,则调用createServer创建一个服务器对象。4.createServerprivate ExchangeServer createServer(URL url) { // send readonly event when server closes, it’s enabled by default // 服务器关闭时发送readonly事件,默认情况下启用 url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString()); // enable heartbeat by default // 心跳默认间隔一分钟 url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); // 获得远程通讯服务端实现方式,默认用netty3 String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER); /* * 如果没有该配置,则抛出异常 / if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) throw new RpcException(“Unsupported server type: " + str + “, url: " + url); /* * 添加编解码器DubboCodec实现 / url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); ExchangeServer server; try { // 启动服务器 server = Exchangers.bind(url, requestHandler); } catch (RemotingException e) { throw new RpcException(“Fail to start server(url: " + url + “) " + e.getMessage(), e); } // 获得客户端侧设置的远程通信方式 str = url.getParameter(Constants.CLIENT_KEY); if (str != null && str.length() > 0) { // 获得远程通信的实现集合 Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(); // 如果客户端侧设置的远程通信方式不在支持的方式中,则抛出异常 if (!supportedTypes.contains(str)) { throw new RpcException(“Unsupported client type: " + str); } } return server;}该方法就是根据url携带的远程通信实现方法来创建一个服务器对象。5.optimizeSerializationprivate void optimizeSerialization(URL url) throws RpcException { // 获得类名 String className = url.getParameter(Constants.OPTIMIZER_KEY, “”); if (StringUtils.isEmpty(className) || optimizers.contains(className)) { return; } logger.info(“Optimizing the serialization process for Kryo, FST, etc…”); try { // 加载类 Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); if (!SerializationOptimizer.class.isAssignableFrom(clazz)) { throw new RpcException(“The serialization optimizer " + className + " isn’t an instance of " + SerializationOptimizer.class.getName()); } // 强制类型转化为SerializationOptimizer SerializationOptimizer optimizer = (SerializationOptimizer) clazz.newInstance(); if (optimizer.getSerializableClasses() == null) { return; } // 遍历序列化的类,把该类放入到集合进行缓存 for (Class c : optimizer.getSerializableClasses()) { SerializableClassRegistry.registerClass(c); } // 加入到集合 optimizers.add(className); } catch (ClassNotFoundException e) { throw new RpcException(“Cannot find the serialization optimizer class: " + className, e); } catch (InstantiationException e) { throw new RpcException(“Cannot instantiate the serialization optimizer class: " + className, e); } catch (IllegalAccessException e) { throw new RpcException(“Cannot instantiate the serialization optimizer class: " + className, e); }}该方法是把序列化的类放入到集合,以便进行序列化6.refer@Overridepublic <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { // 序列化 optimizeSerialization(url); // create rpc invoker. 创建一个DubboInvoker对象 DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); // 把该invoker放入集合 invokers.add(invoker); return invoker;}该方法是服务引用,其中就是新建一个DubboInvoker对象后把它放入到集合。7.getClientsprivate ExchangeClient[] getClients(URL url) { // whether to share connection // 一个连接是否对于一个服务 boolean service_share_connect = false; // 获得url中欢愉连接共享的配置 默认为0 int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0); // if not configured, connection is shared, otherwise, one connection for one service // 如果为0,则是共享类,并且连接数为1 if (connections == 0) { service_share_connect = true; connections = 1; } // 创建数组 ExchangeClient[] clients = new ExchangeClient[connections]; for (int i = 0; i < clients.length; i++) { // 如果共享,则获得共享客户端对象,否则新建客户端 if (service_share_connect) { clients[i] = getSharedClient(url); } else { clients[i] = initClient(url); } } return clients;}该方法是获得客户端集合的方法,分为共享客户端和非共享客户端。共享客户端是共用同一个连接,非共享客户端是每个客户端都有自己的一个连接。8.getSharedClientprivate ExchangeClient getSharedClient(URL url) { String key = url.getAddress(); // 从集合中取出客户端对象 ReferenceCountExchangeClient client = referenceClientMap.get(key); // 如果不为空并且没关闭连接,则计数器加1,返回 if (client != null) { if (!client.isClosed()) { client.incrementAndGetCount(); return client; } else { // 如果连接断开,则从集合中移除 referenceClientMap.remove(key); } } locks.putIfAbsent(key, new Object()); synchronized (locks.get(key)) { // 如果集合中有该key if (referenceClientMap.containsKey(key)) { // 则直接返回client return referenceClientMap.get(key); } // 否则新建一个连接 ExchangeClient exchangeClient = initClient(url); client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap); // 存入集合 referenceClientMap.put(key, client); // 从ghostClientMap中移除 ghostClientMap.remove(key); // 从对象锁中移除 locks.remove(key); return client; }}该方法是获得分享的客户端连接。9.initClientprivate ExchangeClient initClient(URL url) { // client type setting. // 获得客户端的实现方法 默认netty3 String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); // 添加编码器 url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); // enable heartbeat by default // 默认开启心跳 url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); // BIO is not allowed since it has severe performance issue. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { throw new RpcException(“Unsupported client type: " + str + “,” + " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " “)); } ExchangeClient client; try { // connection should be lazy // 是否需要延迟连接,,默认不开启 if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) { // 创建延迟连接的客户端 client = new LazyConnectExchangeClient(url, requestHandler); } else { // 否则就直接连接 client = Exchangers.connect(url, requestHandler); } } catch (RemotingException e) { throw new RpcException(“Fail to create remoting client for service(” + url + “): " + e.getMessage(), e); } return client;}该方法是新建一个客户端连接10.destroy@Overridepublic void destroy() { // 遍历服务器逐个关闭 for (String key : new ArrayList<String>(serverMap.keySet())) { ExchangeServer server = serverMap.remove(key); if (server != null) { try { if (logger.isInfoEnabled()) { logger.info(“Close dubbo server: " + server.getLocalAddress()); } server.close(ConfigUtils.getServerShutdownTimeout()); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } // 遍历客户端集合逐个关闭 for (String key : new ArrayList<String>(referenceClientMap.keySet())) { ExchangeClient client = referenceClientMap.remove(key); if (client != null) { try { if (logger.isInfoEnabled()) { logger.info(“Close dubbo connect: " + client.getLocalAddress() + “–>” + client.getRemoteAddress()); } client.close(ConfigUtils.getServerShutdownTimeout()); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } // 遍历懒加载的集合,逐个关闭客户端 for (String key : new ArrayList<String>(ghostClientMap.keySet())) { ExchangeClient client = ghostClientMap.remove(key); if (client != null) { try { if (logger.isInfoEnabled()) { logger.info(“Close dubbo connect: " + client.getLocalAddress() + “–>” + client.getRemoteAddress()); } client.close(ConfigUtils.getServerShutdownTimeout()); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } stubServiceMethodsMap.clear(); super.destroy();}该方法是销毁的方法重写。(四)ChannelWrappedInvoker该类是对当前通道内的客户端调用消息进行包装1.属性/* * 通道 /private final Channel channel;/* * 服务key /private final String serviceKey;/* * 当前的客户端 /private final ExchangeClient currentClient;2.doInvoke@Overrideprotected Result doInvoke(Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; // use interface’s name as service path to export if it’s not found on client side // 设置服务path,默认用接口名称 inv.setAttachment(Constants.PATH_KEY, getInterface().getName()); // 设置回调的服务key inv.setAttachment(Constants.CALLBACK_SERVICE_KEY, serviceKey); try { // 如果是异步的 if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { // may have concurrency issue // 直接发送请求消息 currentClient.send(inv, getUrl().getMethodParameter(invocation.getMethodName(), Constants.SENT_KEY, false)); return new RpcResult(); } // 获得超时时间 int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); if (timeout > 0) { return (Result) currentClient.request(inv, timeout).get(); } else { return (Result) currentClient.request(inv).get(); } } catch (RpcException e) { throw e; } catch (TimeoutException e) { throw new RpcException(RpcException.TIMEOUT_EXCEPTION, e.getMessage(), e); } catch (RemotingException e) { throw new RpcException(RpcException.NETWORK_EXCEPTION, e.getMessage(), e); } catch (Throwable e) { // here is non-biz exception, wrap it. throw new RpcException(e.getMessage(), e); }}该方法是在invoker调用的时候对发送请求消息进行了包装。3.ChannelWrapper该类是个内部没,继承了ClientDelegate,其中将编码器变成了dubbo的编码器,其他方法比较简单。(五)DecodeableRpcInvocation该类主要做了对于会话域内的数据进行序列化和解码。1.属性private static final Logger log = LoggerFactory.getLogger(DecodeableRpcInvocation.class);/* * 通道 /private Channel channel;/* * 序列化类型 /private byte serializationType;/* * 输入流 /private InputStream inputStream;/* * 请求 /private Request request;/* * 是否解码 /private volatile boolean hasDecoded;2.decode@Overridepublic void decode() throws Exception { // 如果没有解码,则进行解码 if (!hasDecoded && channel != null && inputStream != null) { try { decode(channel, inputStream); } catch (Throwable e) { if (log.isWarnEnabled()) { log.warn(“Decode rpc invocation failed: " + e.getMessage(), e); } request.setBroken(true); request.setData(e); } finally { // 设置已经解码 hasDecoded = true; } }}@Overridepublic Object decode(Channel channel, InputStream input) throws IOException { // 对数据进行反序列化 ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) .deserialize(channel.getUrl(), input); // dubbo版本 String dubboVersion = in.readUTF(); // 请求中放入dubbo版本 request.setVersion(dubboVersion); // 附加值内加入dubbo版本,path以及版本号 setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion); setAttachment(Constants.PATH_KEY, in.readUTF()); setAttachment(Constants.VERSION_KEY, in.readUTF()); // 设置方法名称 setMethodName(in.readUTF()); try { // 方法参数数组 Object[] args; // 方法参数类型数组 Class<?>[] pts; // 描述 String desc = in.readUTF(); // 如果为空,则方法参数数组和对方法参数类型数组都设置为空 if (desc.length() == 0) { pts = DubboCodec.EMPTY_CLASS_ARRAY; args = DubboCodec.EMPTY_OBJECT_ARRAY; } else { // 分割成类,获得类数组 pts = ReflectUtils.desc2classArray(desc); // 创建等长等数组 args = new Object[pts.length]; for (int i = 0; i < args.length; i++) { try { // 读取对象放入数组中 args[i] = in.readObject(pts[i]); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn(“Decode argument failed: " + e.getMessage(), e); } } } } // 设置参数类型 setParameterTypes(pts); Map<String, String> map = (Map<String, String>) in.readObject(Map.class); if (map != null && map.size() > 0) { // 获得所有附加值 Map<String, String> attachment = getAttachments(); if (attachment == null) { attachment = new HashMap<String, String>(); } // 把流中读到的配置放入附加值 attachment.putAll(map); // 放回去 setAttachments(attachment); } //decode argument ,may be callback for (int i = 0; i < args.length; i++) { // 如果是回调,则再一次解码 args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]); } setArguments(args); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read invocation data failed.”, e)); } finally { if (in instanceof Cleanable) { ((Cleanable) in).cleanup(); } } return this;}该方法就是处理Invocation内数据的逻辑,其中主要是做了序列化和解码。把读取出来的设置放入对对应位置传递给后面的调用。(六)DecodeableRpcResult该类是做了基于dubbo协议对prc结果的解码1.属性private static final Logger log = LoggerFactory.getLogger(DecodeableRpcResult.class);/* * 通道 /private Channel channel;/* * 序列化类型 /private byte serializationType;/* * 输入流 /private InputStream inputStream;/* * 响应 /private Response response;/* * 会话域 /private Invocation invocation;/* * 是否解码 /private volatile boolean hasDecoded;2.decode@Overridepublic Object decode(Channel channel, InputStream input) throws IOException { // 反序列化 ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) .deserialize(channel.getUrl(), input); byte flag = in.readByte(); // 根据返回的不同结果来进行处理 switch (flag) { case DubboCodec.RESPONSE_NULL_VALUE: // 返回结果为空 break; case DubboCodec.RESPONSE_VALUE: // try { // 获得返回类型数组 Type[] returnType = RpcUtils.getReturnTypes(invocation); // 根据返回类型读取返回结果并且放入RpcResult setValue(returnType == null || returnType.length == 0 ? in.readObject() : (returnType.length == 1 ? in.readObject((Class<?>) returnType[0]) : in.readObject((Class<?>) returnType[0], returnType[1]))); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; case DubboCodec.RESPONSE_WITH_EXCEPTION: // 返回结果有异常 try { Object obj = in.readObject(); // 把异常放入RpcResult if (obj instanceof Throwable == false) throw new IOException(“Response data error, expect Throwable, but get " + obj); setException((Throwable) obj); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: // 返回值为空,但是有附加值 try { // 把附加值加入到RpcResult setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS: // 返回值 try { // 设置返回结果 Type[] returnType = RpcUtils.getReturnTypes(invocation); setValue(returnType == null || returnType.length == 0 ? in.readObject() : (returnType.length == 1 ? in.readObject((Class<?>) returnType[0]) : in.readObject((Class<?>) returnType[0], returnType[1]))); // 设置附加值 setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: // 返回结果有异常并且有附加值 try { // 设置异常 Object obj = in.readObject(); if (obj instanceof Throwable == false) throw new IOException(“Response data error, expect Throwable, but get " + obj); setException((Throwable) obj); // 设置附加值 setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; default: throw new IOException(“Unknown result flag, expect ‘0’ ‘1’ ‘2’, get " + flag); } if (in instanceof Cleanable) { ((Cleanable) in).cleanup(); } return this;}@Overridepublic void decode() throws Exception { // 如果没有解码 if (!hasDecoded && channel != null && inputStream != null) { try { // 进行解码 decode(channel, inputStream); } catch (Throwable e) { if (log.isWarnEnabled()) { log.warn(“Decode rpc result failed: " + e.getMessage(), e); } response.setStatus(Response.CLIENT_ERROR); response.setErrorMessage(StringUtils.toString(e)); } finally { hasDecoded = true; } }}该方法是对响应结果的解码,其中根据不同的返回结果来对RpcResult设置不同的值。(七)LazyConnectExchangeClient该类实现了ExchangeClient接口,是ExchangeClient的装饰器,用到了装饰模式,是延迟连接的客户端实现类。1.属性// when this warning rises from invocation, program probably have bug./* * 延迟连接请求错误key /static final String REQUEST_WITH_WARNING_KEY = “lazyclient_request_with_warning”;private final static Logger logger = LoggerFactory.getLogger(LazyConnectExchangeClient.class);/* * 是否在延迟连接请求时错误 /protected final boolean requestWithWarning;/* * url对象 /private final URL url;/* * 请求处理器 /private final ExchangeHandler requestHandler;/* * 连接锁 /private final Lock connectLock = new ReentrantLock();// lazy connect, initial state for connection/* * 初始化状态 /private final boolean initialState;/* * 客户端对象 /private volatile ExchangeClient client;/* * 错误次数 /private AtomicLong warningcount = new AtomicLong(0);可以看到有属性ExchangeClient client,该类中很多方法就直接调用了client的方法。2.构造方法public LazyConnectExchangeClient(URL url, ExchangeHandler requestHandler) { // lazy connect, need set send.reconnect = true, to avoid channel bad status. // 默认有重连 this.url = url.addParameter(Constants.SEND_RECONNECT_KEY, Boolean.TRUE.toString()); this.requestHandler = requestHandler; // 默认延迟连接初始化成功 this.initialState = url.getParameter(Constants.LAZY_CONNECT_INITIAL_STATE_KEY, Constants.DEFAULT_LAZY_CONNECT_INITIAL_STATE); // 默认没有错误 this.requestWithWarning = url.getParameter(REQUEST_WITH_WARNING_KEY, false);}3.initClientprivate void initClient() throws RemotingException { // 如果客户端已经初始化,则直接返回 if (client != null) return; if (logger.isInfoEnabled()) { logger.info(“Lazy connect to " + url); } // 获得连接锁 connectLock.lock(); try { // 二次判空 if (client != null) return; // 新建一个客户端 this.client = Exchangers.connect(url, requestHandler); } finally { // 释放锁 connectLock.unlock(); }}该方法是初始化客户端的方法。4.request@Overridepublic ResponseFuture request(Object request) throws RemotingException { warning(request); initClient(); return client.request(request);}该方法在调用client.request前调用了前面两个方法,initClient我在上面讲到了,就是用来初始化客户端的。而warning是用来报错的。5.warningprivate void warning(Object request) { if (requestWithWarning) { // 每5000次报错一次 if (warningcount.get() % 5000 == 0) { logger.warn(new IllegalStateException(“safe guard client , should not be called ,must have a bug.”)); } warningcount.incrementAndGet(); }}每5000次记录报错一次。(八)ReferenceCountExchangeClient该类也是对ExchangeClient的装饰,其中增强了调用次数多功能。1.属性/* * url对象 /private final URL url;/* * 计数 /private final AtomicInteger refenceCount = new AtomicInteger(0);// private final ExchangeHandler handler;/* * 延迟连接客户端集合 /private final ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap;/* * 客户端对象 /private ExchangeClient client;2.replaceWithLazyClient// ghost clientprivate LazyConnectExchangeClient replaceWithLazyClient() { // this is a defensive operation to avoid client is closed by accident, the initial state of the client is false // 设置延迟连接初始化状态、是否重连、是否已经重连等配置 URL lazyUrl = url.addParameter(Constants.LAZY_CONNECT_INITIAL_STATE_KEY, Boolean.FALSE) .addParameter(Constants.RECONNECT_KEY, Boolean.FALSE) .addParameter(Constants.SEND_RECONNECT_KEY, Boolean.TRUE.toString()) .addParameter(“warning”, Boolean.TRUE.toString()) .addParameter(LazyConnectExchangeClient.REQUEST_WITH_WARNING_KEY, true) .addParameter("_client_memo”, “referencecounthandler.replacewithlazyclient”); // 获得服务地址 String key = url.getAddress(); // in worst case there’s only one ghost connection. // 从集合中获取客户端 LazyConnectExchangeClient gclient = ghostClientMap.get(key); // 如果对应等客户端不存在或者已经关闭连接,则重新创建一个延迟连接等客户端,并且放入集合 if (gclient == null || gclient.isClosed()) { gclient = new LazyConnectExchangeClient(lazyUrl, client.getExchangeHandler()); ghostClientMap.put(key, gclient); } return gclient;}该方法是用延迟连接替代,该方法在close方法中被调用。3.close@Overridepublic void close(int timeout) { if (refenceCount.decrementAndGet() <= 0) { if (timeout == 0) { client.close(); } else { client.close(timeout); } client = replaceWithLazyClient(); }}(九)FutureAdapter该类实现了Future接口,是响应的Future适配器。其中是基于ResponseFuture做适配。其中比较简单,我就不多讲解了。(十)CallbackServiceCodec该类是针对回调服务的编解码器。1.属性/* * 代理工厂 /private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();/* * dubbo协议 /private static final DubboProtocol protocol = DubboProtocol.getDubboProtocol();/* * 回调的标志 /private static final byte CALLBACK_NONE = 0x0;/* * 回调的创建标志 /private static final byte CALLBACK_CREATE = 0x1;/* * 回调的销毁标志 /private static final byte CALLBACK_DESTROY = 0x2;/* * 回调参数key /private static final String INV_ATT_CALLBACK_KEY = “sys_callback_arg-";2.encodeInvocationArgumentpublic static Object encodeInvocationArgument(Channel channel, RpcInvocation inv, int paraIndex) throws IOException { // get URL directly // 直接获得url URL url = inv.getInvoker() == null ? null : inv.getInvoker().getUrl(); // 设置回调标志 byte callbackstatus = isCallBack(url, inv.getMethodName(), paraIndex); // 获得参数集合 Object[] args = inv.getArguments(); // 获得参数类型集合 Class<?>[] pts = inv.getParameterTypes(); // 根据不同的回调状态来设置附加值和返回参数 switch (callbackstatus) { case CallbackServiceCodec.CALLBACK_NONE: return args[paraIndex]; case CallbackServiceCodec.CALLBACK_CREATE: inv.setAttachment(INV_ATT_CALLBACK_KEY + paraIndex, exportOrunexportCallbackService(channel, url, pts[paraIndex], args[paraIndex], true)); return null; case CallbackServiceCodec.CALLBACK_DESTROY: inv.setAttachment(INV_ATT_CALLBACK_KEY + paraIndex, exportOrunexportCallbackService(channel, url, pts[paraIndex], args[paraIndex], false)); return null; default: return args[paraIndex]; }}该方法是对会话域的信息进行编码。3.decodeInvocationArgumentpublic static Object decodeInvocationArgument(Channel channel, RpcInvocation inv, Class<?>[] pts, int paraIndex, Object inObject) throws IOException { // if it’s a callback, create proxy on client side, callback interface on client side can be invoked through channel // need get URL from channel and env when decode URL url = null; try { // 获得url url = DubboProtocol.getDubboProtocol().getInvoker(channel, inv).getUrl(); } catch (RemotingException e) { if (logger.isInfoEnabled()) { logger.info(e.getMessage(), e); } return inObject; } // 获得回调状态 byte callbackstatus = isCallBack(url, inv.getMethodName(), paraIndex); // 根据回调状态来返回结果 switch (callbackstatus) { case CallbackServiceCodec.CALLBACK_NONE: return inObject; case CallbackServiceCodec.CALLBACK_CREATE: try { return referOrdestroyCallbackService(channel, url, pts[paraIndex], inv, Integer.parseInt(inv.getAttachment(INV_ATT_CALLBACK_KEY + paraIndex)), true); } catch (Exception e) { logger.error(e.getMessage(), e); throw new IOException(StringUtils.toString(e)); } case CallbackServiceCodec.CALLBACK_DESTROY: try { return referOrdestroyCallbackService(channel, url, pts[paraIndex], inv, Integer.parseInt(inv.getAttachment(INV_ATT_CALLBACK_KEY + paraIndex)), false); } catch (Exception e) { throw new IOException(StringUtils.toString(e)); } default: return inObject; }}该方法是对会话域内的信息进行解码。4.isCallBackprivate static byte isCallBack(URL url, String methodName, int argIndex) { // parameter callback rule: method-name.parameter-index(starting from 0).callback // 参数的规则:ethod-name.parameter-index(starting from 0).callback byte isCallback = CALLBACK_NONE; if (url != null) { // 获得回调的值 String callback = url.getParameter(methodName + “.” + argIndex + “.callback”); if (callback != null) { // 如果为true,则设置为创建标志 if (callback.equalsIgnoreCase(“true”)) { isCallback = CALLBACK_CREATE; // 如果为false,则设置为销毁标志 } else if (callback.equalsIgnoreCase(“false”)) { isCallback = CALLBACK_DESTROY; } } } return isCallback;}该方法是根据url携带的参数设置回调的标志,以供执行不同的编解码逻辑。5.exportOrunexportCallbackServiceprivate static String exportOrunexportCallbackService(Channel channel, URL url, Class clazz, Object inst, Boolean export) throws IOException { // 返回对象的hashCode int instid = System.identityHashCode(inst); Map<String, String> params = new HashMap<String, String>(3); // no need to new client again // 设置不是服务端标志为否 params.put(Constants.IS_SERVER_KEY, Boolean.FALSE.toString()); // mark it’s a callback, for troubleshooting // 设置是回调服务标志为true params.put(Constants.IS_CALLBACK_SERVICE, Boolean.TRUE.toString()); String group = url.getParameter(Constants.GROUP_KEY); if (group != null && group.length() > 0) { // 设置是消费侧还是提供侧 params.put(Constants.GROUP_KEY, group); } // add method, for verifying against method, automatic fallback (see dubbo protocol) // 添加方法,在dubbo的协议里面用到 params.put(Constants.METHODS_KEY, StringUtils.join(Wrapper.getWrapper(clazz).getDeclaredMethodNames(), “,”)); Map<String, String> tmpmap = new HashMap<String, String>(url.getParameters()); tmpmap.putAll(params); // 移除版本信息 tmpmap.remove(Constants.VERSION_KEY);// doesn’t need to distinguish version for callback // 设置接口名 tmpmap.put(Constants.INTERFACE_KEY, clazz.getName()); // 创建服务暴露的url URL exporturl = new URL(DubboProtocol.NAME, channel.getLocalAddress().getAddress().getHostAddress(), channel.getLocalAddress().getPort(), clazz.getName() + “.” + instid, tmpmap); // no need to generate multiple exporters for different channel in the same JVM, cache key cannot collide. // 获得缓存的key String cacheKey = getClientSideCallbackServiceCacheKey(instid); // 获得计数的key String countkey = getClientSideCountKey(clazz.getName()); // 如果是暴露服务 if (export) { // one channel can have multiple callback instances, no need to re-export for different instance. if (!channel.hasAttribute(cacheKey)) { if (!isInstancesOverLimit(channel, url, clazz.getName(), instid, false)) { // 获得代理对象 Invoker<?> invoker = proxyFactory.getInvoker(inst, clazz, exporturl); // should destroy resource? // 暴露服务 Exporter<?> exporter = protocol.export(invoker); // this is used for tracing if instid has published service or not. // 放到通道 channel.setAttribute(cacheKey, exporter); logger.info(“export a callback service :” + exporturl + “, on " + channel + “, url is: " + url); // 计数器加1 increaseInstanceCount(channel, countkey); } } } else { // 如果通道内已经有该服务的缓存 if (channel.hasAttribute(cacheKey)) { // 则获得该暴露者 Exporter<?> exporter = (Exporter<?>) channel.getAttribute(cacheKey); // 取消暴露 exporter.unexport(); // 移除该缓存 channel.removeAttribute(cacheKey); // 计数器减1 decreaseInstanceCount(channel, countkey); } } return String.valueOf(instid);}该方法是在客户端侧暴露服务和取消暴露服务。6.referOrdestroyCallbackServiceprivate static Object referOrdestroyCallbackService(Channel channel, URL url, Class<?> clazz, Invocation inv, int instid, boolean isRefer) { Object proxy = null; // 获得服务调用的缓存key String invokerCacheKey = getServerSideCallbackInvokerCacheKey(channel, clazz.getName(), instid); // 获得代理缓存key String proxyCacheKey = getServerSideCallbackServiceCacheKey(channel, clazz.getName(), instid); // 从通道内获得代理对象 proxy = channel.getAttribute(proxyCacheKey); // 获得计数器key String countkey = getServerSideCountKey(channel, clazz.getName()); // 如果是服务引用 if (isRefer) { // 如果代理对象为空 if (proxy == null) { // 获得服务引用的url URL referurl = URL.valueOf(“callback://” + url.getAddress() + “/” + clazz.getName() + “?” + Constants.INTERFACE_KEY + “=” + clazz.getName()); referurl = referurl.addParametersIfAbsent(url.getParameters()).removeParameter(Constants.METHODS_KEY); if (!isInstancesOverLimit(channel, referurl, clazz.getName(), instid, true)) { @SuppressWarnings(“rawtypes”) Invoker<?> invoker = new ChannelWrappedInvoker(clazz, channel, referurl, String.valueOf(instid)); // 获得代理类 proxy = proxyFactory.getProxy(invoker); // 设置代理类 channel.setAttribute(proxyCacheKey, proxy); // 设置实体域 channel.setAttribute(invokerCacheKey, invoker); // 计数器加1 increaseInstanceCount(channel, countkey); //convert error fail fast . //ignore concurrent problem. Set<Invoker<?>> callbackInvokers = (Set<Invoker<?>>) channel.getAttribute(Constants.CHANNEL_CALLBACK_KEY); if (callbackInvokers == null) { // 创建回调的服务实体域集合 callbackInvokers = new ConcurrentHashSet<Invoker<?>>(1); // 把该实体域加入集合中 callbackInvokers.add(invoker); channel.setAttribute(Constants.CHANNEL_CALLBACK_KEY, callbackInvokers); } logger.info(“method " + inv.getMethodName() + " include a callback service :” + invoker.getUrl() + “, a proxy :” + invoker + " has been created.”); } } } else { // 销毁 if (proxy != null) { Invoker<?> invoker = (Invoker<?>) channel.getAttribute(invokerCacheKey); try { Set<Invoker<?>> callbackInvokers = (Set<Invoker<?>>) channel.getAttribute(Constants.CHANNEL_CALLBACK_KEY); if (callbackInvokers != null) { // 从集合中移除 callbackInvokers.remove(invoker); } // 销毁该调用 invoker.destroy(); } catch (Exception e) { logger.error(e.getMessage(), e); } // cancel refer, directly remove from the map // 取消引用,直接从集合中移除 channel.removeAttribute(proxyCacheKey); channel.removeAttribute(invokerCacheKey); // 计数器减1 decreaseInstanceCount(channel, countkey); } } return proxy;}该方法是在服务端侧进行服务引用或者销毁回调服务。(十一)DubboCodec该类是dubbo的编解码器,分别针对dubbo协议的request和response进行编码和解码。1.属性/* * dubbo名称 /public static final String NAME = “dubbo”;/* * 协议版本号 /public static final String DUBBO_VERSION = Version.getProtocolVersion();/* * 响应携带着异常 /public static final byte RESPONSE_WITH_EXCEPTION = 0;/* * 响应 /public static final byte RESPONSE_VALUE = 1;/* * 响应结果为空 /public static final byte RESPONSE_NULL_VALUE = 2;/* * 响应结果有异常并且带有附加值 /public static final byte RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS = 3;/* * 响应结果有附加值 /public static final byte RESPONSE_VALUE_WITH_ATTACHMENTS = 4;/* * 响应结果为空并带有附加值 /public static final byte RESPONSE_NULL_VALUE_WITH_ATTACHMENTS = 5;/* * 对象空集合 /public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];/* * 空的类集合 /public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];private static final Logger log = LoggerFactory.getLogger(DubboCodec.class);2.decodeBody@Overrideprotected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException { byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK); // get request id. long id = Bytes.bytes2long(header, 4); // 如果是response if ((flag & FLAG_REQUEST) == 0) { // decode response. // 创建一个response Response res = new Response(id); // 如果是事件,则设置事件,这里有个问题,我提交了pr在新版本已经修复 if ((flag & FLAG_EVENT) != 0) { res.setEvent(Response.HEARTBEAT_EVENT); } // get status. // 设置状态 byte status = header[3]; res.setStatus(status); try { // 反序列化 ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto); // 如果状态是响应成功 if (status == Response.OK) { Object data; // 如果是心跳事件,则按照心跳事件解码 if (res.isHeartbeat()) { data = decodeHeartbeatData(channel, in); } else if (res.isEvent()) { // 如果是事件,则 data = decodeEventData(channel, in); } else { // 否则对结果进行解码 DecodeableRpcResult result; if (channel.getUrl().getParameter( Constants.DECODE_IN_IO_THREAD_KEY, Constants.DEFAULT_DECODE_IN_IO_THREAD)) { result = new DecodeableRpcResult(channel, res, is, (Invocation) getRequestData(id), proto); result.decode(); } else { result = new DecodeableRpcResult(channel, res, new UnsafeByteArrayInputStream(readMessageData(is)), (Invocation) getRequestData(id), proto); } data = result; } // 把结果重新放入response中 res.setResult(data); } else { // 否则设置错误信息 res.setErrorMessage(in.readUTF()); } } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn(“Decode response failed: " + t.getMessage(), t); } res.setStatus(Response.CLIENT_ERROR); res.setErrorMessage(StringUtils.toString(t)); } return res; } else { // decode request. // 如果该消息是request Request req = new Request(id); // 设置版本 req.setVersion(Version.getProtocolVersion()); // 设置是否是双向请求 req.setTwoWay((flag & FLAG_TWOWAY) != 0); // 设置是否是事件,该地方问题也在新版本修复 if ((flag & FLAG_EVENT) != 0) { req.setEvent(Request.HEARTBEAT_EVENT); } try { Object data; // 反序列化 ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto); // 进行解码 if (req.isHeartbeat()) { data = decodeHeartbeatData(channel, in); } else if (req.isEvent()) { data = decodeEventData(channel, in); } else { DecodeableRpcInvocation inv; if (channel.getUrl().getParameter( Constants.DECODE_IN_IO_THREAD_KEY, Constants.DEFAULT_DECODE_IN_IO_THREAD)) { inv = new DecodeableRpcInvocation(channel, req, is, proto); inv.decode(); } else { inv = new DecodeableRpcInvocation(channel, req, new UnsafeByteArrayInputStream(readMessageData(is)), proto); } data = inv; } // 把body数据设置到response req.setData(data); } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn(“Decode request failed: " + t.getMessage(), t); } // bad request req.setBroken(true); req.setData(t); } return req; }}该方法是对request和response进行解码,用位运算来进行解码,其中的逻辑跟我在 《dubbo源码解析(十)远程通信——Exchange层》中讲到的编解码器逻辑差不多。3.encodeRequestData@Overrideprotected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException { RpcInvocation inv = (RpcInvocation) data; // 输出版本 out.writeUTF(version); // 输出path out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); // 输出版本号 out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); // 输出方法名称 out.writeUTF(inv.getMethodName()); // 输出参数类型 out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); // 输出参数 Object[] args = inv.getArguments(); if (args != null) for (int i = 0; i < args.length; i++) { out.writeObject(encodeInvocationArgument(channel, inv, i)); } // 输出附加值 out.writeObject(inv.getAttachments());}该方法是对请求数据的编码。4.encodeResponseData@Overrideprotected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException { Result result = (Result) data; // currently, the version value in Response records the version of Request boolean attach = Version.isSupportResponseAttatchment(version); // 获得异常 Throwable th = result.getException(); if (th == null) { Object ret = result.getValue(); // 根据结果的不同输出不同的值 if (ret == null) { out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE); } else { out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE); out.writeObject(ret); } } else { // 如果有异常,则输出异常 out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION); out.writeObject(th); } if (attach) { // returns current version of Response to consumer side. // 在附加值中加入版本号 result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); // 输出版本号 out.writeObject(result.getAttachments()); }}该方法是对响应数据的编码。(十二)DubboCountCodec该类是对DubboCodec的功能增强,增加了消息长度的限制。public final class DubboCountCodec implements Codec2 { private DubboCodec codec = new DubboCodec(); @Override public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException { codec.encode(channel, buffer, msg); } @Override public Object decode(Channel channel, ChannelBuffer buffer) throws IOException { // 保存读取的标志 int save = buffer.readerIndex(); MultiMessage result = MultiMessage.create(); do { Object obj = codec.decode(channel, buffer); // 粘包拆包 if (Codec2.DecodeResult.NEED_MORE_INPUT == obj) { buffer.readerIndex(save); break; } else { // 增加消息 result.addMessage(obj); // 记录消息长度 logMessageLength(obj, buffer.readerIndex() - save); save = buffer.readerIndex(); } } while (true); // 如果结果为空,则返回需要更多的输入 if (result.isEmpty()) { return Codec2.DecodeResult.NEED_MORE_INPUT; } if (result.size() == 1) { return result.get(0); } return result; } private void logMessageLength(Object result, int bytes) { if (bytes <= 0) { return; } // 如果是request类型 if (result instanceof Request) { try { // 设置附加值 ((RpcInvocation) ((Request) result).getData()).setAttachment( Constants.INPUT_KEY, String.valueOf(bytes)); } catch (Throwable e) { / ignore / } } else if (result instanceof Response) { try { // 设置附加值 输出的长度 ((RpcResult) ((Response) result).getResult()).setAttachment( Constants.OUTPUT_KEY, String.valueOf(bytes)); } catch (Throwable e) { / ignore / } } }}(十三)TraceFilter该过滤器是增强的功能是通道的跟踪,会在通道内把最大的调用次数和现在的调用数量放进去。方便使用telnet来跟踪服务的调用次数等。1.属性/* * 跟踪数量的最大值key /private static final String TRACE_MAX = “trace.max”;/* * 跟踪的数量 /private static final String TRACE_COUNT = “trace.count”;/* * 通道集合 */private static final ConcurrentMap<String, Set<Channel>> tracers = new ConcurrentHashMap<String, Set<Channel>>();2.addTracerpublic static void addTracer(Class<?> type, String method, Channel channel, int max) { // 设置最大的数量 channel.setAttribute(TRACE_MAX, max); // 设置当前的数量 channel.setAttribute(TRACE_COUNT, new AtomicInteger()); // 获得key String key = method != null && method.length() > 0 ? type.getName() + “.” + method : type.getName(); // 获得通道集合 Set<Channel> channels = tracers.get(key); // 如果为空,则新建 if (channels == null) { tracers.putIfAbsent(key, new ConcurrentHashSet<Channel>()); channels = tracers.get(key); } channels.add(channel);}该方法是对某一个通道进行跟踪,把现在的调用数量放到属性里面3.removeTracerpublic static void removeTracer(Class<?> type, String method, Channel channel) { // 移除最大值属性 channel.removeAttribute(TRACE_MAX); // 移除数量属性 channel.removeAttribute(TRACE_COUNT); String key = method != null && method.length() > 0 ? type.getName() + “.” + method : type.getName(); Set<Channel> channels = tracers.get(key); if (channels != null) { // 集合中移除该通道 channels.remove(channel); }}该方法是移除通道的跟踪。4.invoke@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 开始时间 long start = System.currentTimeMillis(); // 调用下一个调用链 获得结果 Result result = invoker.invoke(invocation); // 调用结束时间 long end = System.currentTimeMillis(); // 如果通道跟踪大小大于0 if (tracers.size() > 0) { // 服务key String key = invoker.getInterface().getName() + “.” + invocation.getMethodName(); // 获得通道集合 Set<Channel> channels = tracers.get(key); if (channels == null || channels.isEmpty()) { key = invoker.getInterface().getName(); channels = tracers.get(key); } if (channels != null && !channels.isEmpty()) { // 遍历通道集合 for (Channel channel : new ArrayList<Channel>(channels)) { // 如果通道是连接的 if (channel.isConnected()) { try { // 获得跟踪的最大数 int max = 1; Integer m = (Integer) channel.getAttribute(TRACE_MAX); if (m != null) { max = (int) m; } // 获得跟踪数量 int count = 0; AtomicInteger c = (AtomicInteger) channel.getAttribute(TRACE_COUNT); if (c == null) { c = new AtomicInteger(); channel.setAttribute(TRACE_COUNT, c); } count = c.getAndIncrement(); // 如果数量小于最大数量则发送 if (count < max) { String prompt = channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT); channel.send("\r\n” + RpcContext.getContext().getRemoteAddress() + " -> " + invoker.getInterface().getName() + “.” + invocation.getMethodName() + “(” + JSON.toJSONString(invocation.getArguments()) + “)” + " -> " + JSON.toJSONString(result.getValue()) + “\r\nelapsed: " + (end - start) + " ms.” + “\r\n\r\n” + prompt); } // 如果数量大于等于max - 1,则移除该通道 if (count >= max - 1) { channels.remove(channel); } } catch (Throwable e) { channels.remove(channel); logger.warn(e.getMessage(), e); } } else { // 如果未连接,也移除该通道 channels.remove(channel); } } } } return result;}该方法是当服务被调用时,进行跟踪或者取消跟踪的处理逻辑,是核心的功能增强逻辑。(十四)FutureFilter该类是处理异步和同步调用结果的过滤器。1.invoke@Overridepublic Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException { // 是否是异步的调用 final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation); fireInvokeCallback(invoker, invocation); // need to configure if there’s return value before the invocation in order to help invoker to judge if it’s // necessary to return future. Result result = invoker.invoke(invocation); if (isAsync) { // 调用异步处理 asyncCallback(invoker, invocation); } else { // 调用同步结果处理 syncCallback(invoker, invocation, result); } return result;}该方法中根据是否为异步调用来分别执行asyncCallback和syncCallback方法。2.syncCallbackprivate void syncCallback(final Invoker<?> invoker, final Invocation invocation, final Result result) { // 如果有异常 if (result.hasException()) { // 则调用异常的结果处理 fireThrowCallback(invoker, invocation, result.getException()); } else { // 调用正常的结果处理 fireReturnCallback(invoker, invocation, result.getValue()); }}该方法是同步调用的返回结果处理,比较简单。3.asyncCallbackprivate void asyncCallback(final Invoker<?> invoker, final Invocation invocation) { Future<?> f = RpcContext.getContext().getFuture(); if (f instanceof FutureAdapter) { ResponseFuture future = ((FutureAdapter<?>) f).getFuture(); // 设置回调 future.setCallback(new ResponseCallback() { @Override public void done(Object rpcResult) { // 如果结果为空,则打印错误日志 if (rpcResult == null) { logger.error(new IllegalStateException(“invalid result value : null, expected " + Result.class.getName())); return; } ///must be rpcResult // 如果不是Result则打印错误日志 if (!(rpcResult instanceof Result)) { logger.error(new IllegalStateException(“invalid result type :” + rpcResult.getClass() + “, expected " + Result.class.getName())); return; } Result result = (Result) rpcResult; if (result.hasException()) { // 如果有异常,则调用异常处理方法 fireThrowCallback(invoker, invocation, result.getException()); } else { // 如果正常的返回结果,则调用正常的处理方法 fireReturnCallback(invoker, invocation, result.getValue()); } } @Override public void caught(Throwable exception) { fireThrowCallback(invoker, invocation, exception); } }); }}该方法是异步调用的结果处理,把异步返回结果的逻辑写在回调函数里面。4.fireInvokeCallbackprivate void fireInvokeCallback(final Invoker<?> invoker, final Invocation invocation) { // 获得调用的方法 final Method onInvokeMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_METHOD_KEY)); // 获得调用的服务 final Object onInvokeInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_INSTANCE_KEY)); if (onInvokeMethod == null && onInvokeInst == null) { return; } if (onInvokeMethod == null || onInvokeInst == null) { throw new IllegalStateException(“service:” + invoker.getUrl().getServiceKey() + " has a onreturn callback config , but no such " + (onInvokeMethod == null ? “method” : “instance”) + " found. url:” + invoker.getUrl()); } // 如果不可以访问,则设置为可访问 if (!onInvokeMethod.isAccessible()) { onInvokeMethod.setAccessible(true); } // 获得参数数组 Object[] params = invocation.getArguments(); try { // 调用方法 onInvokeMethod.invoke(onInvokeInst, params); } catch (InvocationTargetException e) { fireThrowCallback(invoker, invocation, e.getTargetException()); } catch (Throwable e) { fireThrowCallback(invoker, invocation, e); }}该方法是调用方法的执行。5.fireReturnCallbackprivate void fireReturnCallback(final Invoker<?> invoker, final Invocation invocation, final Object result) { final Method onReturnMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_RETURN_METHOD_KEY)); final Object onReturnInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_RETURN_INSTANCE_KEY)); //not set onreturn callback if (onReturnMethod == null && onReturnInst == null) { return; } if (onReturnMethod == null || onReturnInst == null) { throw new IllegalStateException(“service:” + invoker.getUrl().getServiceKey() + " has a onreturn callback config , but no such " + (onReturnMethod == null ? “method” : “instance”) + " found. url:” + invoker.getUrl()); } if (!onReturnMethod.isAccessible()) { onReturnMethod.setAccessible(true); } Object[] args = invocation.getArguments(); Object[] params; // 获得返回结果类型 Class<?>[] rParaTypes = onReturnMethod.getParameterTypes(); // 设置参数和返回结果 if (rParaTypes.length > 1) { if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)) { params = new Object[2]; params[0] = result; params[1] = args; } else { params = new Object[args.length + 1]; params[0] = result; System.arraycopy(args, 0, params, 1, args.length); } } else { params = new Object[]{result}; } try { // 调用方法 onReturnMethod.invoke(onReturnInst, params); } catch (InvocationTargetException e) { fireThrowCallback(invoker, invocation, e.getTargetException()); } catch (Throwable e) { fireThrowCallback(invoker, invocation, e); }}该方法是正常的返回结果的处理。6.fireThrowCallbackprivate void fireThrowCallback(final Invoker<?> invoker, final Invocation invocation, final Throwable exception) { final Method onthrowMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_METHOD_KEY)); final Object onthrowInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_INSTANCE_KEY)); //onthrow callback not configured if (onthrowMethod == null && onthrowInst == null) { return; } if (onthrowMethod == null || onthrowInst == null) { throw new IllegalStateException(“service:” + invoker.getUrl().getServiceKey() + " has a onthrow callback config , but no such " + (onthrowMethod == null ? “method” : “instance”) + " found. url:” + invoker.getUrl()); } if (!onthrowMethod.isAccessible()) { onthrowMethod.setAccessible(true); } // 获得抛出异常的类型 Class<?>[] rParaTypes = onthrowMethod.getParameterTypes(); if (rParaTypes[0].isAssignableFrom(exception.getClass())) { try { Object[] args = invocation.getArguments(); Object[] params; // 把类型和抛出的异常值放入返回结果 if (rParaTypes.length > 1) { if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)) { params = new Object[2]; params[0] = exception; params[1] = args; } else { params = new Object[args.length + 1]; params[0] = exception; System.arraycopy(args, 0, params, 1, args.length); } } else { params = new Object[]{exception}; } // 调用下一个调用连 onthrowMethod.invoke(onthrowInst, params); } catch (Throwable e) { logger.error(invocation.getMethodName() + “.call back method invoke error . callback method :” + onthrowMethod + “, url:” + invoker.getUrl(), e); } } else { logger.error(invocation.getMethodName() + “.call back method invoke error . callback method :” + onthrowMethod + “, url:” + invoker.getUrl(), exception); }}该方法是异常抛出时的结果处理。(十五)ServerStatusChecker该类是对于服务状态的监控设置。public class ServerStatusChecker implements StatusChecker { @Override public Status check() { // 获得服务集合 Collection<ExchangeServer> servers = DubboProtocol.getDubboProtocol().getServers(); // 如果为空则返回UNKNOWN的状态 if (servers == null || servers.isEmpty()) { return new Status(Status.Level.UNKNOWN); } // 设置状态为ok Status.Level level = Status.Level.OK; StringBuilder buf = new StringBuilder(); // 遍历集合 for (ExchangeServer server : servers) { // 如果服务没有绑定到本地端口 if (!server.isBound()) { // 状态改为error level = Status.Level.ERROR; // 加入服务本地地址 buf.setLength(0); buf.append(server.getLocalAddress()); break; } if (buf.length() > 0) { buf.append(”,”); } // 如果服务绑定了本地端口,拼接clients数量 buf.append(server.getLocalAddress()); buf.append("(clients:”); buf.append(server.getChannels().size()); buf.append(”)”); } return new Status(level, buf.toString()); }}(十六)ThreadPoolStatusChecker该类是对于线程池的状态进行监控。@Activatepublic class ThreadPoolStatusChecker implements StatusChecker { @Override public Status check() { // 获得数据中心 DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension(); // 获得线程池集合 Map<String, Object> executors = dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY); StringBuilder msg = new StringBuilder(); // 设置为ok Status.Level level = Status.Level.OK; // 遍历线程池集合 for (Map.Entry<String, Object> entry : executors.entrySet()) { String port = entry.getKey(); ExecutorService executor = (ExecutorService) entry.getValue(); if (executor != null && executor instanceof ThreadPoolExecutor) { ThreadPoolExecutor tp = (ThreadPoolExecutor) executor; boolean ok = tp.getActiveCount() < tp.getMaximumPoolSize() - 1; Status.Level lvl = Status.Level.OK; // 如果活跃数量超过了最大的线程数量,则设置warn if (!ok) { level = Status.Level.WARN; lvl = Status.Level.WARN; } if (msg.length() > 0) { msg.append(”;”); } // 输出线程池相关信息 msg.append(“Pool status:” + lvl + “, max:” + tp.getMaximumPoolSize() + “, core:” + tp.getCorePoolSize() + “, largest:” + tp.getLargestPoolSize() + “, active:” + tp.getActiveCount() + “, task:” + tp.getTaskCount() + “, service port: " + port); } } return msg.length() == 0 ? new Status(Status.Level.UNKNOWN) : new Status(level, msg.toString()); }}逻辑比较简单,我就不赘述了。关于telnet下的相关实现请感兴趣的朋友直接查看,里面都是对于telnet命令的实现,内容比较独立。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于dubbo协议的部分,dubbo协议是官方推荐使用的协议,并且对于telnet命令也做了很好的支持,要看懂这部分的逻辑,必须先对于之前的一些接口设计了解的很清楚。接下来我将开始对rpc模块的dubbo-rpc-dubbo关于hessian协议部分进行讲解。 ...

January 22, 2019 · 28 min · jiezi

dubbo源码解析(二十三)远程调用——Proxy

远程调用——Proxy目标:介绍远程调用代理的设计和实现,介绍dubbo-rpc-api中的各种proxy包的源码。前言首先声明叫做代理,代理在很多领域都存在,最形象的就是现在朋友圈的微商代理,厂家委托代理帮他们卖东西。这样做厂家对于消费者来说就是透明的,并且代理可以自己加上一些活动或者销售措施,但这并不影响到厂家。这里的厂家就是委托类,而代理就可以抽象为代理类。这样做有两个优点,第一是可以隐藏代理类的实现,第二就是委托类和调用方的解耦,并且能够在不修改委托类原本的逻辑情况下新增一些额外的处理。代理分为两种,静态代理和动态代理。静态代理:如果代理类在程序运行前就已经存在,那么这种代理就是静态代理。动态代理:代理类在程序运行时创建的代理方式。动态代理关系由两组静态代理关系组成,这就是动态代理的原理。上述稍微回顾了一下静态代理和动态代理,那么dubbo对于动态代理有两种方法实现,分别是javassist和jdk。Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。我们来看看下面的图:我们能看到左边是消费者的调用链,只有当消费者调用的时候,ProxyFactory才会通过Proxy把接口实现转化为invoker,并且在其他层的调用都使用的是invoker,同样的道理,在服务提供者暴露服务的时候,也只有在最后暴露给消费者的时候才会通过Proxy 将 Invoker 转成接口。动态代理的底层原理就是字节码技术,dubbo提供了两种方式来实现代理:第一种jdk,jdk动态代理比较简单,它内置在JDK中,因此不依赖第三方jar包,但是功能相对较弱,当调用Proxy 的静态方法创建动态代理类时,类名格式是“$ProxyN”,N代表第 N 次生成的动态代理类,如果重复创建动态代理类会直接返回原先创建的代理类。但是这个以“$ProxyN”命名的类是继承Proxy类的,并且实现了其所代理的一组接口,这里就出现了它的一个局限性,由于java的类只能单继承,所以JDK动态代理仅支持接口代理。第二种是Javassist,Javassist是一款Java字节码引擎工具,能够在运行时编译生成class。该方法也是代理的默认方法。源码分析(一)AbstractProxyFactory该类是代理工厂的抽象类,主要处理了一下需要代理的接口,然后把代理getProxy方法抽象出来。public abstract class AbstractProxyFactory implements ProxyFactory { @Override public <T> T getProxy(Invoker<T> invoker) throws RpcException { return getProxy(invoker, false); } @Override public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException { Class<?>[] interfaces = null; // 获得需要代理的接口 String config = invoker.getUrl().getParameter(“interfaces”); if (config != null && config.length() > 0) { // 根据逗号把每个接口分割开 String[] types = Constants.COMMA_SPLIT_PATTERN.split(config); if (types != null && types.length > 0) { // 创建接口类型数组 interfaces = new Class<?>[types.length + 2]; // 第一个放invoker的服务接口 interfaces[0] = invoker.getInterface(); // 第二个位置放回声测试服务的接口类 interfaces[1] = EchoService.class; // 其他接口循环放入 for (int i = 0; i < types.length; i++) { interfaces[i + 1] = ReflectUtils.forName(types[i]); } } } // 如果接口为空,就是config为空,则是回声测试 if (interfaces == null) { interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class}; } // 如果是泛化服务,那么在代理的接口集合中加入泛化服务类型 if (!invoker.getInterface().equals(GenericService.class) && generic) { int len = interfaces.length; Class<?>[] temp = interfaces; interfaces = new Class<?>[len + 1]; System.arraycopy(temp, 0, interfaces, 0, len); interfaces[len] = GenericService.class; } // 获得代理 return getProxy(invoker, interfaces); } public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);}逻辑比较简单,就是处理了url中携带的interfaces的值。(二)AbstractProxyInvoker该类实现了Invoker接口,是代理invoker对象的抽象类。@Overridepublic Result invoke(Invocation invocation) throws RpcException { try { // 调用了抽象方法doInvoke return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments())); } catch (InvocationTargetException e) { return new RpcResult(e.getTargetException()); } catch (Throwable e) { throw new RpcException(“Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + “, cause: " + e.getMessage(), e); }}protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;该类最关键的就是这两个方法,一个是invoke方法,调用了抽象方法doInvoke,另一个则是抽象方法。该方法被子类实现。(三)InvokerInvocationHandler该类实现了InvocationHandler接口,动态代理类都必须要实现InvocationHandler接口,而该类实现的是对于基础方法不适用rpc调用,其他方法使用rpc调用。public class InvokerInvocationHandler implements InvocationHandler { private final Invoker<?> invoker; public InvokerInvocationHandler(Invoker<?> handler) { this.invoker = handler; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获得方法名 String methodName = method.getName(); // 获得参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); // 如果方法参数类型是object类型,则直接反射调用 if (method.getDeclaringClass() == Object.class) { return method.invoke(invoker, args); } // 基础方法,不使用 RPC 调用 if (“toString”.equals(methodName) && parameterTypes.length == 0) { return invoker.toString(); } if (“hashCode”.equals(methodName) && parameterTypes.length == 0) { return invoker.hashCode(); } if (“equals”.equals(methodName) && parameterTypes.length == 1) { return invoker.equals(args[0]); } // rpc调用 return invoker.invoke(new RpcInvocation(method, args)).recreate(); }}(四)StubProxyFactoryWrapper该类实现了本地存根的逻辑,关于本地存根的概念和使用在官方文档中都有详细说明。地址:http://dubbo.apache.org/zh-cn…public class StubProxyFactoryWrapper implements ProxyFactory { private static final Logger LOGGER = LoggerFactory.getLogger(StubProxyFactoryWrapper.class); /** * 代理工厂 / private final ProxyFactory proxyFactory; /* * 协议 / private Protocol protocol; public StubProxyFactoryWrapper(ProxyFactory proxyFactory) { this.proxyFactory = proxyFactory; } public void setProtocol(Protocol protocol) { this.protocol = protocol; } @Override public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException { return proxyFactory.getProxy(invoker, generic); } @Override @SuppressWarnings({“unchecked”, “rawtypes”}) public <T> T getProxy(Invoker<T> invoker) throws RpcException { // 获得代理类对象 T proxy = proxyFactory.getProxy(invoker); // 如果不是返回服务调用 if (GenericService.class != invoker.getInterface()) { // 获得stub的配置 String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY)); // 如果配置不为空 if (ConfigUtils.isNotEmpty(stub)) { Class<?> serviceType = invoker.getInterface(); if (ConfigUtils.isDefault(stub)) { // 根据local和stub来生成stub if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) { stub = serviceType.getName() + “Stub”; } else { stub = serviceType.getName() + “Local”; } } try { // 生成stub类 Class<?> stubClass = ReflectUtils.forName(stub); if (!serviceType.isAssignableFrom(stubClass)) { throw new IllegalStateException(“The stub implementation class " + stubClass.getName() + " not implement interface " + serviceType.getName()); } try { // 获得构造方法,该构造方法必须是带有代理的对象的参数 Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType); // 使用指定的初始化参数创建和初始化构造函数声明类的新实例 proxy = (T) constructor.newInstance(new Object[]{proxy}); //export stub service URL url = invoker.getUrl(); if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)) { url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), “,”)); url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString()); try { // 暴露stub服务 export(proxy, (Class) invoker.getInterface(), url); } catch (Exception e) { LOGGER.error(“export a stub service error.”, e); } } } catch (NoSuchMethodException e) { throw new IllegalStateException(“No such constructor "public " + stubClass.getSimpleName() + “(” + serviceType.getName() + “)" in stub implementation class " + stubClass.getName(), e); } } catch (Throwable t) { LOGGER.error(“Failed to create stub implementation class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + “, cause: " + t.getMessage(), t); // ignore } } } return proxy; } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException { return proxyFactory.getInvoker(proxy, type, url); } private <T> Exporter<T> export(T instance, Class<T> type, URL url) { return protocol.export(proxyFactory.getInvoker(instance, type, url)); }该类里面最重要的就是getProxy方法的实现,在该方法中先根据配置生成加载stub服务类,然后通过构造方法将代理的对象进行包装,最后暴露该服务,然后返回代理类对象。(五)JdkProxyFactory该类继承了AbstractProxyFactory,是jdk的代理工厂的主要逻辑。public class JdkProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings(“unchecked”) public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { // 调用了 Proxy.newProxyInstance直接获得代理类 return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // 创建AbstractProxyInvoker对象 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); } }; }}不过逻辑实现比较简单,因为jdk中都封装好了,直接调用Proxy.newProxyInstance方法就可以获得代理类。(六)JavassistProxyFactory该类是基于Javassist实现的动态代理工厂类。public class JavassistProxyFactory extends AbstractProxyFactory { @Override @SuppressWarnings(“unchecked”) public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { // 创建代理 return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // TODO Wrapper cannot handle this scenario correctly: the classname contains ‘$’ // 创建Wrapper对象 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); } }; }}在这里看不出什么具体的实现,感觉看起来跟JdkProxyFactory差不多,下面我将讲解com.alibaba.dubbo.common.bytecode.Proxy类的getProxy方法和com.alibaba.dubbo.common.bytecode.Wrapper类的getWrapper方法。(七)Proxy#getProxy()public static Proxy getProxy(Class<?>… ics) { // 获得代理类 return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);}/* * Get proxy. * * @param cl class loader. * @param ics interface class array. * @return Proxy instance. */public static Proxy getProxy(ClassLoader cl, Class<?>… ics) { // 最大的代理接口数限制是65535 if (ics.length > 65535) throw new IllegalArgumentException(“interface limit exceeded”); StringBuilder sb = new StringBuilder(); // 遍历代理接口,获取接口的全限定名并以分号分隔连接成字符串 for (int i = 0; i < ics.length; i++) { // 获得类名 String itf = ics[i].getName(); // 判断是否为接口 if (!ics[i].isInterface()) throw new RuntimeException(itf + " is not a interface.”); Class<?> tmp = null; try { // 获得与itf对应的Class对象 tmp = Class.forName(itf, false, cl); } catch (ClassNotFoundException e) { } // 如果通过类名获得的类型跟ics中的类型不一样,则抛出异常 if (tmp != ics[i]) throw new IllegalArgumentException(ics[i] + " is not visible from class loader”); // 拼接类 sb.append(itf).append(’;’); } // use interface class name list as key. String key = sb.toString(); // get cache by class loader. Map<String, Object> cache; synchronized (ProxyCacheMap) { // 通过类加载器获得缓存 cache = ProxyCacheMap.get(cl); if (cache == null) { cache = new HashMap<String, Object>(); ProxyCacheMap.put(cl, cache); } } Proxy proxy = null; synchronized (cache) { do { Object value = cache.get(key); // 如果缓存中存在,则直接返回代理对象 if (value instanceof Reference<?>) { proxy = (Proxy) ((Reference<?>) value).get(); if (proxy != null) return proxy; } // 是等待生成的类型,则等待 if (value == PendingGenerationMarker) { try { cache.wait(); } catch (InterruptedException e) { } } else { // 否则放入缓存中 cache.put(key, PendingGenerationMarker); break; } } while (true); } // AtomicLong自增生成代理类类名后缀id,防止冲突 long id = PROXY_CLASS_COUNTER.getAndIncrement(); String pkg = null; ClassGenerator ccp = null, ccm = null; try { ccp = ClassGenerator.newInstance(cl); Set<String> worked = new HashSet<String>(); List<Method> methods = new ArrayList<Method>(); for (int i = 0; i < ics.length; i++) { // 判断是否为public if (!Modifier.isPublic(ics[i].getModifiers())) { // 获得该类的包名 String npkg = ics[i].getPackage().getName(); if (pkg == null) { pkg = npkg; } else { if (!pkg.equals(npkg)) throw new IllegalArgumentException(“non-public interfaces from different packages”); } } // 把接口加入到ccp的mInterfaces中 ccp.addInterface(ics[i]); // 遍历每个类的方法 for (Method method : ics[i].getMethods()) { // 获得方法描述 这个方法描述是自定义: // 例如:int do(int arg1) => “do(I)I” // 例如:void do(String arg1,boolean arg2) => “do(Ljava/lang/String;Z)V” String desc = ReflectUtils.getDesc(method); if (worked.contains(desc)) continue; // 如果集合中不存在,则加入该描述 worked.add(desc); int ix = methods.size(); // 获得方法返回类型 Class<?> rt = method.getReturnType(); // 获得方法参数类型 Class<?>[] pts = method.getParameterTypes(); // 新建一句代码 // 例如Object[] args = new Object[参数数量】 StringBuilder code = new StringBuilder(“Object[] args = new Object[”).append(pts.length).append(”];”); // 每一个参数都生成一句代码 // 例如args[0] = ($w)$1; // 例如 Object ret = handler.invoke(this, methods[3], args); for (int j = 0; j < pts.length; j++) code.append(” args[”).append(j).append(”] = ($w)$").append(j + 1).append(";"); code.append(" Object ret = handler.invoke(this, methods[" + ix + “], args);”); // 如果方法不是void类型 // 则拼接 return ret; if (!Void.TYPE.equals(rt)) code.append(" return “).append(asArgument(rt, “ret”)).append(”;"); methods.add(method); ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); } } if (pkg == null) pkg = PACKAGE_NAME; // create ProxyInstance class. String pcn = pkg + “.proxy” + id; ccp.setClassName(pcn); // 添加静态字段Method[] methods ccp.addField(“public static java.lang.reflect.Method[] methods;”); ccp.addField(“private " + InvocationHandler.class.getName() + " handler;”); // 添加实例对象InvokerInvocationHandler hanler,添加参数为InvokerInvocationHandler的构造器 ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], “handler=$1;”); // 添加默认无参构造器 ccp.addDefaultConstructor(); // 使用toClass方法生成对应的字节码 Class<?> clazz = ccp.toClass(); clazz.getField(“methods”).set(null, methods.toArray(new Method[0])); // create Proxy class. // 生成的字节码对象为服务接口的代理对象 String fcn = Proxy.class.getName() + id; ccm = ClassGenerator.newInstance(cl); ccm.setClassName(fcn); ccm.addDefaultConstructor(); ccm.setSuperClass(Proxy.class); ccm.addMethod(“public Object newInstance(” + InvocationHandler.class.getName() + " h){ return new " + pcn + “($1); }”); Class<?> pc = ccm.toClass(); proxy = (Proxy) pc.newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { // release ClassGenerator // 重置类构造器 if (ccp != null) ccp.release(); if (ccm != null) ccm.release(); synchronized (cache) { if (proxy == null) cache.remove(key); else cache.put(key, new WeakReference<Proxy>(proxy)); cache.notifyAll(); } } return proxy;}Proxy是是生成代理对象的工具类,跟JdkProxyFactory中用到的Proxy不是同一个,JdkProxyFactory中的是jdk自带的java.lang.reflect.Proxy。而该Proxy是dubbo基于javassit实现的com.alibaba.dubbo.common.bytecode.Proxy。该方法比较长,可以分开五个步骤来看:遍历代理接口,获取接口的全限定名,并以分号分隔连接成字符串,以此字符串为key,查找缓存map,如果缓存存在,则获取代理对象直接返回。由一个AtomicLong自增生成代理类类名后缀id,防止冲突遍历接口中的方法,获取返回类型和参数类型,构建的方法体见注释创建工具类ClassGenerator实例,添加静态字段Method[] methods,添加实例对象InvokerInvocationHandler hanler,添加参数为InvokerInvocationHandler的构造器,添加无参构造器,然后使用toClass方法生成对应的字节码。4中生成的字节码对象为服务接口的代理对象,而Proxy类本身是抽象类,需要实现newInstance(InvocationHandler handler)方法,生成Proxy的实现类,其中proxy0即上面生成的服务接口的代理对象。(八)Wrapper#getWrapperpublic static Wrapper getWrapper(Class<?> c) { // 判断c是否继承 ClassGenerator.DC.class ,如果是,则拿到父类,避免重复包装 while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class. c = c.getSuperclass(); // 如果类为object类型 if (c == Object.class) return OBJECT_WRAPPER; // 如果缓存里面没有该对象,则新建一个wrapper Wrapper ret = WRAPPER_MAP.get(c); if (ret == null) { ret = makeWrapper(c); WRAPPER_MAP.put(c, ret); } return ret;}private static Wrapper makeWrapper(Class<?> c) { // 如果c不是似有类,则抛出异常 if (c.isPrimitive()) throw new IllegalArgumentException(“Can not create wrapper for primitive type: " + c); // 获得类名 String name = c.getName(); // 获得类加载器 ClassLoader cl = ClassHelper.getClassLoader(c); // 设置属性的方法第一行public void setPropertyValue(Object o, String n, Object v){ StringBuilder c1 = new StringBuilder(“public void setPropertyValue(Object o, String n, Object v){ “); // 获得属性的方法第一行 public Object getPropertyValue(Object o, String n){ StringBuilder c2 = new StringBuilder(“public Object getPropertyValue(Object o, String n){ “); // 执行方法的第一行 StringBuilder c3 = new StringBuilder(“public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + “{ “); // 添加每个方法中被调用对象的类型转换的代码 c1.append(name).append(” w; try{ w = ((”).append(name).append(”)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }”); c2.append(name).append(” w; try{ w = ((”).append(name).append(”)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }”); c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); Map<String, Class<?>> pts = new HashMap<String, Class<?>>(); // <property name, property types> Map<String, Method> ms = new LinkedHashMap<String, Method>(); // <method desc, Method instance> List<String> mns = new ArrayList<String>(); // method names. List<String> dmns = new ArrayList<String>(); // declaring method names. // get all public field. // 遍历每个public的属性,放入setPropertyValue和getPropertyValue方法中 for (Field f : c.getFields()) { String fn = f.getName(); Class<?> ft = f.getType(); // // 排除有static 和 transient修饰的属性 if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) continue; c1.append(" if( $2.equals("").append(fn).append("") ){ w.").append(fn).append("=").append(arg(ft, “$3”)).append("; return; }"); c2.append(" if( $2.equals("").append(fn).append("") ){ return ($w)w.").append(fn).append("; }"); pts.put(fn, ft); } Method[] methods = c.getMethods(); // get all public method. boolean hasMethod = hasMethods(methods); // 在invokeMethod方法中添加try的代码 if (hasMethod) { c3.append(" try{"); } // 遍历方法 for (Method m : methods) { // 忽律Object的方法 if (m.getDeclaringClass() == Object.class) //ignore Object’s method. continue; // 判断方法名和方法参数长度 String mn = m.getName(); c3.append(" if( "").append(mn).append("".equals( $2 ) “); // 方法参数长度 int len = m.getParameterTypes().length; // 判断方法参数长度代码 c3.append(” && “).append(” $3.length == “).append(len); // 若相同方法名存在多个,增加参数类型数组的比较判断 boolean override = false; for (Method m2 : methods) { if (m != m2 && m.getName().equals(m2.getName())) { override = true; break; } } if (override) { if (len > 0) { for (int l = 0; l < len; l++) { c3.append(” && “).append(” $3[").append(l).append("].getName().equals("") .append(m.getParameterTypes()[l].getName()).append("")"); } } } c3.append(" ) { “); // 如果返回类型是void,则return null,如果不是,则返回对应参数类型 if (m.getReturnType() == Void.TYPE) c3.append(” w.").append(mn).append(’(’).append(args(m.getParameterTypes(), “$4”)).append(");").append(" return null;"); else c3.append(" return ($w)w.").append(mn).append(’(’).append(args(m.getParameterTypes(), “$4”)).append(");"); c3.append(" }"); mns.add(mn); if (m.getDeclaringClass() == c) dmns.add(mn); ms.put(ReflectUtils.getDesc(m), m); } if (hasMethod) { c3.append(" } catch(Throwable e) { “); c3.append(” throw new java.lang.reflect.InvocationTargetException(e); “); c3.append(” }"); } c3.append(" throw new " + NoSuchMethodException.class.getName() + “("Not found method \""+$2+"\" in class " + c.getName() + “."); }”); // 处理get set方法 // deal with get/set method. Matcher matcher; for (Map.Entry<String, Method> entry : ms.entrySet()) { String md = entry.getKey(); Method method = (Method) entry.getValue(); if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { String pn = propertyName(matcher.group(1)); c2.append(” if( $2.equals("").append(pn).append("") ){ return ($w)w.").append(method.getName()).append("(); }"); pts.put(pn, method.getReturnType()); } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { String pn = propertyName(matcher.group(1)); c2.append(" if( $2.equals("").append(pn).append("") ){ return ($w)w.").append(method.getName()).append("(); }"); pts.put(pn, method.getReturnType()); } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { Class<?> pt = method.getParameterTypes()[0]; String pn = propertyName(matcher.group(1)); c1.append(" if( $2.equals("").append(pn).append("") ){ w.").append(method.getName()).append("(").append(arg(pt, “$3”)).append("); return; }"); pts.put(pn, pt); } } c1.append(" throw new " + NoSuchPropertyException.class.getName() + “("Not found property \""+$2+"\" filed or setter method in class " + c.getName() + “."); }”); c2.append(” throw new " + NoSuchPropertyException.class.getName() + “("Not found property \""+$2+"\" filed or setter method in class " + c.getName() + “."); }”); // make class long id = WRAPPER_CLASS_COUNTER.getAndIncrement(); ClassGenerator cc = ClassGenerator.newInstance(cl); cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + “$sw”) + id); cc.setSuperClass(Wrapper.class); // 增加无参构造器 cc.addDefaultConstructor(); // 添加属性 cc.addField(“public static String[] pns;”); // property name array. cc.addField(“public static " + Map.class.getName() + " pts;”); // property type map. cc.addField(“public static String[] mns;”); // all method name array. cc.addField(“public static String[] dmns;”); // declared method name array. for (int i = 0, len = ms.size(); i < len; i++) cc.addField(“public static Class[] mts” + i + “;”); // 添加属性相关的方法 cc.addMethod(“public String[] getPropertyNames(){ return pns; }”); cc.addMethod(“public boolean hasProperty(String n){ return pts.containsKey($1); }”); cc.addMethod(“public Class getPropertyType(String n){ return (Class)pts.get($1); }”); cc.addMethod(“public String[] getMethodNames(){ return mns; }”); cc.addMethod(“public String[] getDeclaredMethodNames(){ return dmns; }”); cc.addMethod(c1.toString()); cc.addMethod(c2.toString()); cc.addMethod(c3.toString()); try { // 生成字节码 Class<?> wc = cc.toClass(); // setup static field. // 反射,设置静态变量的值 wc.getField(“pts”).set(null, pts); wc.getField(“pns”).set(null, pts.keySet().toArray(new String[0])); wc.getField(“mns”).set(null, mns.toArray(new String[0])); wc.getField(“dmns”).set(null, dmns.toArray(new String[0])); int ix = 0; for (Method m : ms.values()) wc.getField(“mts” + ix++).set(null, m.getParameterTypes()); // // 创建对象并且返回 return (Wrapper) wc.newInstance(); } catch (RuntimeException e) { throw e; } catch (Throwable e) { throw new RuntimeException(e.getMessage(), e); } finally { cc.release(); ms.clear(); mns.clear(); dmns.clear(); }}Wrapper是用于创建某个对象的方法调用的包装器,利用字节码技术在调用方法时进行编译相关方法。其中getWrapper就是获得Wrapper 对象,其中关键的是makeWrapper方法,所以我在上面加上了makeWrapper方法的解释,其中就是相关方法的字节码生成过程。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于代理的部分,关键部分在于基于javassist实现的字节码技术来支撑动态代理。接下来我将开始对rpc模块的dubbo-rpc-dubbo关于dubbo协议部分进行讲解。 ...

January 15, 2019 · 11 min · jiezi

dubbo源码解析(二十二)远程调用——Protocol

远程调用——Protocol目标:介绍远程调用中协议的设计和实现,介绍dubbo-rpc-api中的各种protocol包的源码,是重点内容。前言在远程调用中协议是非常重要的一层,看下面这张图:该层是在信息交换层之上,分为了并且夹杂在服务暴露和服务引用中间,为了有一个约定的方式进行调用。dubbo支持不同协议的扩展,比如http、thrift等等,具体的可以参照官方文档。本文讲解的源码大部分是对于公共方法的实现,而具体的服务暴露和服务引用会在各个协议实现中讲到。下面是该包下面的类图:源码分析(一)AbstractProtocol该类是协议的抽象类,实现了Protocol接口,其中实现了一些公共的方法,抽象方法在它的子类AbstractProxyProtocol中定义。1.属性/** * 服务暴露者集合 /protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();/* * 服务引用者集合 ///TODO SOFEREFENCEprotected final Set<Invoker<?>> invokers = new ConcurrentHashSet<Invoker<?>>();2.serviceKeyprotected static String serviceKey(URL url) { // 获得绑定的端口号 int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort()); return serviceKey(port, url.getPath(), url.getParameter(Constants.VERSION_KEY), url.getParameter(Constants.GROUP_KEY));}protected static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) { return ProtocolUtils.serviceKey(port, serviceName, serviceVersion, serviceGroup);}该方法是为了得到服务key group+"/"+serviceName+":"+serviceVersion+":"+port3.destroy@Overridepublic void destroy() { // 遍历服务引用实体 for (Invoker<?> invoker : invokers) { if (invoker != null) { // 从集合中移除 invokers.remove(invoker); try { if (logger.isInfoEnabled()) { logger.info(“Destroy reference: " + invoker.getUrl()); } // 销毁 invoker.destroy(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } // 遍历服务暴露者 for (String key : new ArrayList<String>(exporterMap.keySet())) { // 从集合中移除 Exporter<?> exporter = exporterMap.remove(key); if (exporter != null) { try { if (logger.isInfoEnabled()) { logger.info(“Unexport service: " + exporter.getInvoker().getUrl()); } // 取消暴露 exporter.unexport(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } }}该方法是对invoker和exporter的销毁。(二)AbstractProxyProtocol该类继承了AbstractProtocol类,其中利用了代理工厂对AbstractProtocol中的两个集合进行了填充,并且对异常做了处理。1.属性/* * rpc的异常类集合 /private final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();/* * 代理工厂 /private ProxyFactory proxyFactory;2.export@Override@SuppressWarnings(“unchecked”)public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException { // 获得uri final String uri = serviceKey(invoker.getUrl()); // 获得服务暴露者 Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri); if (exporter != null) { return exporter; } // 新建一个线程 final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl()); exporter = new AbstractExporter<T>(invoker) { /* * 取消暴露 / @Override public void unexport() { super.unexport(); // 移除该key对应的服务暴露者 exporterMap.remove(uri); if (runnable != null) { try { // 启动线程 runnable.run(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } }; // 加入集合 exporterMap.put(uri, exporter); return exporter;}其中分为两个步骤,创建一个exporter,放入到集合汇中。在创建exporter时对unexport方法进行了重写。3.refer@Overridepublic <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException { // 通过代理获得实体域 final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url); Invoker<T> invoker = new AbstractInvoker<T>(type, url) { @Override protected Result doInvoke(Invocation invocation) throws Throwable { try { // 获得调用结果 Result result = target.invoke(invocation); Throwable e = result.getException(); // 如果抛出异常,则抛出相应异常 if (e != null) { for (Class<?> rpcException : rpcExceptions) { if (rpcException.isAssignableFrom(e.getClass())) { throw getRpcException(type, url, invocation, e); } } } return result; } catch (RpcException e) { // 抛出未知异常 if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) { e.setCode(getErrorCode(e.getCause())); } throw e; } catch (Throwable e) { throw getRpcException(type, url, invocation, e); } } }; // 加入集合 invokers.add(invoker); return invoker;}该方法是服务引用,先从代理工厂中获得Invoker对象target,然后创建了真实的invoker在重写方法中调用代理的方法,最后加入到集合。protected abstract <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException;protected abstract <T> T doRefer(Class<T> type, URL url) throws RpcException;可以看到其中抽象了服务引用和暴露的方法,让各类协议各自实现。(三)AbstractInvoker该类是invoker的抽象方法,因为协议被夹在服务引用和服务暴露中间,无论什么协议都有一些通用的Invoker和exporter的方法实现,而该类就是实现了Invoker的公共方法,而把doInvoke抽象出来,让子类只关注这个方法。1.属性/* * 服务类型 /private final Class<T> type;/* * url对象 /private final URL url;/* * 附加值 /private final Map<String, String> attachment;/* * 是否可用 /private volatile boolean available = true;/* * 是否销毁 /private AtomicBoolean destroyed = new AtomicBoolean(false);2.convertAttachmentprivate static Map<String, String> convertAttachment(URL url, String[] keys) { if (keys == null || keys.length == 0) { return null; } Map<String, String> attachment = new HashMap<String, String>(); // 遍历key,把值放入附加值集合中 for (String key : keys) { String value = url.getParameter(key); if (value != null && value.length() > 0) { attachment.put(key, value); } } return attachment;}该方法是转化为附加值,把url中的值转化为服务调用invoker的附加值。3.invoke@Overridepublic Result invoke(Invocation inv) throws RpcException { // if invoker is destroyed due to address refresh from registry, let’s allow the current invoke to proceed // 如果服务引用销毁,则打印告警日志,但是通过 if (destroyed.get()) { logger.warn(“Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, " + “, dubbo version is " + Version.getVersion() + “, this invoker should not be used any longer”); } RpcInvocation invocation = (RpcInvocation) inv; // 会话域中加入该调用链 invocation.setInvoker(this); // 把附加值放入会话域 if (attachment != null && attachment.size() > 0) { invocation.addAttachmentsIfAbsent(attachment); } // 把上下文的附加值放入会话域 Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { /* * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here, * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information). / invocation.addAttachments(contextAttachments); } // 如果开启的是异步调用,则把该设置也放入附加值 if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); } // 加入编号 RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); try { // 执行调用链 return doInvoke(invocation); } catch (InvocationTargetException e) { // biz exception Throwable te = e.getTargetException(); if (te == null) { return new RpcResult(e); } else { if (te instanceof RpcException) { ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION); } return new RpcResult(te); } } catch (RpcException e) { if (e.isBiz()) { return new RpcResult(e); } else { throw e; } } catch (Throwable e) { return new RpcResult(e); }}该方法做了一些公共的操作,比如服务引用销毁的检测,加入附加值,加入调用链实体域到会话域中等。然后执行了doInvoke抽象方法。各协议自己去实现。(四)AbstractExporter该类和AbstractInvoker类似,也是在服务暴露中实现了一些公共方法。1.属性/* * 实体域 /private final Invoker<T> invoker;/* * 是否取消暴露服务 /private volatile boolean unexported = false;2.unexport@Overridepublic void unexport() { // 如果已经消取消暴露,则之间返回 if (unexported) { return; } // 设置为true unexported = true; // 销毁该实体域 getInvoker().destroy();}(五)InvokerWrapper该类是Invoker的包装类,其中用到类装饰模式,不过并没有实现实际的功能增强。public class InvokerWrapper<T> implements Invoker<T> { /* * invoker对象 / private final Invoker<T> invoker; private final URL url; public InvokerWrapper(Invoker<T> invoker, URL url) { this.invoker = invoker; this.url = url; } @Override public Class<T> getInterface() { return invoker.getInterface(); } @Override public URL getUrl() { return url; } @Override public boolean isAvailable() { return invoker.isAvailable(); } @Override public Result invoke(Invocation invocation) throws RpcException { return invoker.invoke(invocation); } @Override public void destroy() { invoker.destroy(); }}(六)ProtocolFilterWrapper该类实现了Protocol接口,其中也用到了装饰模式,是对Protocol的装饰,是在服务引用和暴露的方法上加上了过滤器功能。1.buildInvokerChainprivate static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { Invoker<T> last = invoker; // 获得过滤器的所有扩展实现类实例集合 List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); if (!filters.isEmpty()) { // 从最后一个过滤器开始循环,创建一个带有过滤器链的invoker对象 for (int i = filters.size() - 1; i >= 0; i–) { final Filter filter = filters.get(i); // 记录last的invoker final Invoker<T> next = last; // 新建last last = new Invoker<T>() { @Override public Class<T> getInterface() { return invoker.getInterface(); } @Override public URL getUrl() { return invoker.getUrl(); } @Override public boolean isAvailable() { return invoker.isAvailable(); } /* * 关键在这里,调用下一个filter代表的invoker,把每一个过滤器串起来 * @param invocation * @return * @throws RpcException */ @Override public Result invoke(Invocation invocation) throws RpcException { return filter.invoke(next, invocation); } @Override public void destroy() { invoker.destroy(); } @Override public String toString() { return invoker.toString(); } }; } } return last;}该方法就是创建带 Filter 链的 Invoker 对象。倒序的把每一个过滤器串连起来,形成一个invoker。2.export@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // 如果是注册中心,则直接暴露服务 if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } // 服务提供侧暴露服务 return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));}该方法是在服务暴露上做了过滤器链的增强,也就是加上了过滤器。3.refer@Overridepublic <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { // 如果是注册中心,则直接引用 if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } // 消费者侧引用服务 return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);}该方法是在服务引用上做了过滤器链的增强,也就是加上了过滤器。(七)ProtocolListenerWrapper该类也实现了Protocol,也是装饰了Protocol接口,但是它是在服务引用和暴露过程中加上了监听器的功能。1.export@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // 如果是注册中心,则暴露该invoker if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } // 创建一个暴露者监听器包装类对象 return new ListenerExporterWrapper<T>(protocol.export(invoker), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class) .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));}该方法是在服务暴露上做了监听器功能的增强,也就是加上了监听器。2.refer@Overridepublic <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { // 如果是注册中心。则直接引用服务 if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } // 创建引用服务监听器包装类对象 return new ListenerInvokerWrapper<T>(protocol.refer(type, url), Collections.unmodifiableList( ExtensionLoader.getExtensionLoader(InvokerListener.class) .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));}该方法是在服务引用上做了监听器功能的增强,也就是加上了监听器。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用中关于协议的部分,其实就是讲了一些公共的方法,并且把关键方法抽象出来让子类实现,具体的方法实现都在各个协议中自己实现。接下来我将开始对rpc模块的代理进行讲解。 ...

January 11, 2019 · 5 min · jiezi

dubbo源码解析(二十一)远程调用——Listener

远程调用——Listener目标:介绍dubbo-rpc-api中的各种listener监听器的实现逻辑,内容略少,随便撇两眼,不是重点。前言本文介绍监听器的相关逻辑。在服务引用和服务发现中监听器处于的位置请看下面的图:服务暴露:服务引用:这两个监听器所做的工作不是很多,来看看源码理解一下。源码分析(一)ListenerInvokerWrapper该类实现了Invoker,是服务引用监听器的包装类。1.属性/** * invoker对象 /private final Invoker<T> invoker;/* * 监听器集合 /private final List<InvokerListener> listeners;用到了装饰模式,其中很多实现方法直接调用了invoker的方法。2.构造方法public ListenerInvokerWrapper(Invoker<T> invoker, List<InvokerListener> listeners) { // 如果invoker为空则抛出异常 if (invoker == null) { throw new IllegalArgumentException(“invoker == null”); } this.invoker = invoker; this.listeners = listeners; if (listeners != null && !listeners.isEmpty()) { // 遍历监听器 for (InvokerListener listener : listeners) { if (listener != null) { try { // 调用在服务引用的时候进行监听 listener.referred(invoker); } catch (Throwable t) { logger.error(t.getMessage(), t); } } } }}构造方法中直接调用了监听器的服务引用。3.destroy@Overridepublic void destroy() { try { // 销毁invoker invoker.destroy(); } finally { // 销毁所有监听的实体域 if (listeners != null && !listeners.isEmpty()) { for (InvokerListener listener : listeners) { if (listener != null) { try { listener.destroyed(invoker); } catch (Throwable t) { logger.error(t.getMessage(), t); } } } } }}该方法是把服务引用的监听器销毁。(二)InvokerListenerAdapterpublic abstract class InvokerListenerAdapter implements InvokerListener { /* * 引用服务 * @param invoker * @throws RpcException / @Override public void referred(Invoker<?> invoker) throws RpcException { } /* * 销毁 * @param invoker / @Override public void destroyed(Invoker<?> invoker) { }}该类是服务引用监听器的适配类,没有做实际的操作。(三)DeprecatedInvokerListener@Activate(Constants.DEPRECATED_KEY)public class DeprecatedInvokerListener extends InvokerListenerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(DeprecatedInvokerListener.class); @Override public void referred(Invoker<?> invoker) throws RpcException { // 当该引用的服务被废弃时,打印错误日志 if (invoker.getUrl().getParameter(Constants.DEPRECATED_KEY, false)) { LOGGER.error(“The service " + invoker.getInterface().getName() + " is DEPRECATED! Declare from " + invoker.getUrl()); } }}该类是当调用废弃的服务时候打印错误日志。(四)ListenerExporterWrapper该类是服务暴露监听器包装类。1.属性/* * 服务暴露者 /private final Exporter<T> exporter;/* * 服务暴露监听者集合 /private final List<ExporterListener> listeners;用到了装饰模式,其中很多实现方法直接调用了exporter的方法。2.构造方法public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) { if (exporter == null) { throw new IllegalArgumentException(“exporter == null”); } this.exporter = exporter; this.listeners = listeners; if (listeners != null && !listeners.isEmpty()) { RuntimeException exception = null; // 遍历服务暴露监听集合 for (ExporterListener listener : listeners) { if (listener != null) { try { // 暴露服务监听 listener.exported(this); } catch (RuntimeException t) { logger.error(t.getMessage(), t); exception = t; } } } if (exception != null) { throw exception; } }}该方法中对于每个服务暴露进行监听。3.unexport@Overridepublic void unexport() { try { // 取消暴露 exporter.unexport(); } finally { if (listeners != null && !listeners.isEmpty()) { RuntimeException exception = null; // 遍历监听集合 for (ExporterListener listener : listeners) { if (listener != null) { try { // 监听取消暴露 listener.unexported(this); } catch (RuntimeException t) { logger.error(t.getMessage(), t); exception = t; } } } if (exception != null) { throw exception; } } }}该方法是对每个取消服务暴露的监听。(五)ExporterListenerAdapterpublic abstract class ExporterListenerAdapter implements ExporterListener { /* * 暴露服务 * @param exporter * @throws RpcException / @Override public void exported(Exporter<?> exporter) throws RpcException { } /* * 取消暴露服务 * @param exporter * @throws RpcException */ @Override public void unexported(Exporter<?> exporter) throws RpcException { }}该类是服务暴露监听器的适配类,没有做实际的操作。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了在服务引用和服务暴露中的各种listener监听器,其中内容很少。接下来我将开始对rpc模块的协议protocol进行讲解。 ...

January 9, 2019 · 2 min · jiezi

Dubbo 源码分析 - 服务调用过程

注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章。1. 简介在前面的文章中,我们分析了 Dubbo SPI、服务导出与引入、以及集群容错方面的代码。经过前文的铺垫,本篇文章我们终于可以分析服务调用过程了。Dubbo 服务调用过程比较复杂,包含众多步骤。比如发送请求、编解码、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。限于篇幅原因,本篇文章无法对所有的步骤一一进行分析。本篇文章将会重点分析请求的发送与接收、编解码、线程派发以及响应的发送与接收等过程,至于服务降级、过滤器链和序列化大家自己自行进行分析,也可以将其当成一个黑盒,暂时忽略也没关系。介绍完本篇文章要分析的内容,接下来我们进入正题吧。2. 源码分析在进行源码分析之前,我们先来通过一张图了解 Dubbo 服务调用过程。首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体具体的服务。这就是一个远程调用请求的发送与接收过程。至于响应的发送与接收过程,这章图中没有表现出来。对于这两个过程,我们也会进行详细分析。2.1 服务调用方式Dubbo 支持同步和异步两种调用方式,其中异步调用还可细分为“有返回值”的异步调用和“无返回值”的异步调用。所谓的“无返回值”异步调用是指服务消费者只管调用,但不关心调用结果,此时 Dubbo 会直接返回一个空的 RpcResult。若要使用异步调用特性,需要服务消费方手动进行配置。默认情况下,Dubbo 使用同步调用方式。本节以及其他章节将会使用 Dubbo 官方提供的 Demo 分析整个调用过程,下面我们从 DemoService 接口的代理类开始进行分析。Dubbo 默认使用 Javassist 框架为服务接口生成动态代理类,因此我们需要想将代理类进行反编译才能看到源码。这里使用阿里开源 Java 应用诊断工具 Arthas 反编译代理类,结果如下:/** * Arthas 反编译步骤: * 1. 启动 Arthas * java -jar arthas-boot.jar * * 2. 输入编号选择进程 * Arthas 启动后,会输出 Java 应用进程列表,比如在我的电脑上输出如下: * [1]: 11232 org.jetbrains.jps.cmdline.Launcher * [2]: 22370 org.jetbrains.jps.cmdline.Launcher * [3]: 22371 com.alibaba.dubbo.demo.consumer.Consumer * [4]: 22362 com.alibaba.dubbo.demo.provider.Provider * [5]: 2074 org.apache.zookeeper.server.quorum.QuorumPeerMain * 这里输入编号 3,让 Arthas 关联到启动类为 com…..Consumer 的 Java 进程上 * * 3. 由于 Demo 项目中只有一个服务接口,因此此接口的代理类类名为 proxy0,此时使用 sc 命令搜索这个类名。 * $ sc .proxy0 * com.alibaba.dubbo.common.bytecode.proxy0 * * 4. 使用 jad 命令反编译 com.alibaba.dubbo.common.bytecode.proxy0 * $ jad com.alibaba.dubbo.common.bytecode.proxy0 * * 更多使用方法请参考 Arthas 官方文档: * https://alibaba.github.io/arthas/quick-start.html /public class proxy0 implements ClassGenerator.DC, EchoService, DemoService { // 方法数组 public static Method[] methods; private InvocationHandler handler; public proxy0(InvocationHandler invocationHandler) { this.handler = invocationHandler; } public proxy0() { } public String sayHello(String string) { // 将参数存储到 Object 数组中 Object[] arrobject = new Object[]{string}; // 调用 InvocationHandler 实现类的 invoke 方法得到调用结果 Object object = this.handler.invoke(this, methods[0], arrobject); // 返回调用结果 return (String)object; } / 回声测试方法 / public Object $echo(Object object) { Object[] arrobject = new Object[]{object}; Object object2 = this.handler.invoke(this, methods[1], arrobject); return object2; }}如上,代理类的逻辑比较简单。首先将运行时参数存储到数组中,然后调用 InvocationHandler 接口实现类的 invoke 方法,得到调用结果,最后将结果转型并返回给调用方。关于代理类的逻辑就说这么多,继续向下分析。public class InvokerInvocationHandler implements InvocationHandler { private final Invoker<?> invoker; public InvokerInvocationHandler(Invoker<?> handler) { this.invoker = handler; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); // 拦截定义在 Object 类中的方法(未被子类重写),比如 wait/notify if (method.getDeclaringClass() == Object.class) { return method.invoke(invoker, args); } // 如果 toString、hashCode 和 equals 等方法被子类重写了,这里也直接调用 if (“toString”.equals(methodName) && parameterTypes.length == 0) { return invoker.toString(); } if (“hashCode”.equals(methodName) && parameterTypes.length == 0) { return invoker.hashCode(); } if (“equals”.equals(methodName) && parameterTypes.length == 1) { return invoker.equals(args[0]); } // 将 method 和 args 封装到 RpcInvocation 中,并执行后续的调用 return invoker.invoke(new RpcInvocation(method, args)).recreate(); }}InvokerInvocationHandler 中的 invoker 成员变量类型为 MockClusterInvoker,MockClusterInvoker 内部封装了服务降级逻辑。下面简单看一下:public class MockClusterInvoker<T> implements Invoker<T> { private final Invoker<T> invoker; public Result invoke(Invocation invocation) throws RpcException { Result result = null; // 获取 mock 配置值 String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); if (value.length() == 0 || value.equalsIgnoreCase(“false”)) { // 无 mock 逻辑,直接调用其他 Invoker 对象的 invoke 方法, // 比如 FailoverClusterInvoker result = this.invoker.invoke(invocation); } else if (value.startsWith(“force”)) { // force:xxx 直接执行 mock 逻辑,不发起远程调用 result = doMockInvoke(invocation, null); } else { // fail:xxx 表示消费方对该服务的方法调用在失败后,再执行 mock 逻辑,不抛出异常 try { // 调用其他 Invoker 对象的 invoke 方法 result = this.invoker.invoke(invocation); } catch (RpcException e) { if (e.isBiz()) { throw e; } else { // 调用失败,执行 mock 逻辑 result = doMockInvoke(invocation, e); } } } return result; } // 省略其他方法}服务降级不是本文重点,因此这里就不分析 doMockInvoke 方法了。考虑到前文已经详细分析过 FailoverClusterInvoker,因此本节略过 FailoverClusterInvoker,直接分析 DubboInvoker。public abstract class AbstractInvoker<T> implements Invoker<T> { public Result invoke(Invocation inv) throws RpcException { if (destroyed.get()) { throw new RpcException(“Rpc invoker for service …”); } RpcInvocation invocation = (RpcInvocation) inv; // 设置 Invoker invocation.setInvoker(this); if (attachment != null && attachment.size() > 0) { // 设置 attachment invocation.addAttachmentsIfAbsent(attachment); } Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { // 添加 contextAttachments 到 RpcInvocation#attachment 变量中 invocation.addAttachments(contextAttachments); } if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { // 设置异步信息到 RpcInvocation#attachment 中 invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); } RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); try { // 抽象方法,由子类实现 return doInvoke(invocation); } catch (InvocationTargetException e) { // … } catch (RpcException e) { // … } catch (Throwable e) { return new RpcResult(e); } } protected abstract Result doInvoke(Invocation invocation) throws Throwable; // 省略其他方法}上面的代码来自 AbstractInvoker 类,其中大部分代码用于添加信息到 RpcInvocation#attachment 变量中,添加完毕后,调用 doInvoke 执行后续的调用。doInvoke 是一个抽象方法,需要由子类实现,下面到 DubboInvoker 中看一下。public class DubboInvoker<T> extends AbstractInvoker<T> { private final ExchangeClient[] clients; protected Result doInvoke(final Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName = RpcUtils.getMethodName(invocation); // 设置 path 和 version 到 attachment 中 inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); inv.setAttachment(Constants.VERSION_KEY, version); ExchangeClient currentClient; if (clients.length == 1) { // 从 clients 数组中获取 ExchangeClient currentClient = clients[0]; } else { currentClient = clients[index.getAndIncrement() % clients.length]; } try { // 获取异步配置 boolean isAsync = RpcUtils.isAsync(getUrl(), invocation); // isOneway 为 true,表示“单向”通信 boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 异步无返回值 if (isOneway) { boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false); // 发送请求 currentClient.send(inv, isSent); // 设置上下文中的 future 为 null RpcContext.getContext().setFuture(null); // 返回一个空的 RpcResult return new RpcResult(); } // 异步有返回值 else if (isAsync) { // 发送请求,获得 ResponseFuture 实例 ResponseFuture future = currentClient.request(inv, timeout); // 设置 future 到上下文中 RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); // 暂时返回一个空结果 return new RpcResult(); } // 同步调用 else { RpcContext.getContext().setFuture(null); // 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待 return (Result) currentClient.request(inv, timeout).get(); } } catch (TimeoutException e) { throw new RpcException(…, “Invoke remote method timeout….”); } catch (RemotingException e) { throw new RpcException(…, “Failed to invoke remote method: …”); } } // 省略其他方法}上面的代码包含了 Dubbo 对同步和异步调用的处理逻辑,搞懂了上面的代码,会对 Dubbo 的同步和异步调用方式有更深入的了解。Dubbo 实现同步和异步调用比较关键的一点就在于由谁调用 ResponseFuture 的 get 方法。同步调用模式下,由框架自身调用 ResponseFuture 的 get 方法。异步调用模式下,则由用户调用该方法。ResponseFuture 是一个接口,下面我们来看一下它的默认实现类 DefaultFuture 的源码。public class DefaultFuture implements ResponseFuture { private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>(); private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>(); private final long id; private final Channel channel; private final Request request; private final int timeout; private final Lock lock = new ReentrantLock(); private final Condition done = lock.newCondition(); private volatile Response response; public DefaultFuture(Channel channel, Request request, int timeout) { this.channel = channel; this.request = request; // 获取请求 id,这个 id 很重要,后面还会见到 this.id = request.getId(); this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 存储 <requestId, DefaultFuture> 键值对到 FUTURES 中 FUTURES.put(id, this); CHANNELS.put(id, channel); } @Override public Object get() throws RemotingException { return get(timeout); } @Override public Object get(int timeout) throws RemotingException { if (timeout <= 0) { timeout = Constants.DEFAULT_TIMEOUT; } if (!isDone()) { long start = System.currentTimeMillis(); lock.lock(); try { // 循环检测服务提供方是否成功返回了调用结果 while (!isDone()) { // 如果调用结果尚未返回,这里等待一段时间 done.await(timeout, TimeUnit.MILLISECONDS); // 如果调用结果成功返回,或等待超时,此时跳出 while 循环,执行后续的逻辑 if (isDone() || System.currentTimeMillis() - start > timeout) { break; } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } // 如果调用结果仍未返回,则抛出超时异常 if (!isDone()) { throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false)); } } // 返回调用结果 return returnFromResponse(); } @Override public boolean isDone() { // 通过检测 response 字段为空与否,判断是否收到了调用结果 return response != null; } private Object returnFromResponse() throws RemotingException { Response res = response; if (res == null) { throw new IllegalStateException(“response cannot be null”); } // 如果调用结果的状态为 Response.OK,则表示调用过程正常,服务提供方成功返回了调用结果 if (res.getStatus() == Response.OK) { return res.getResult(); } // 抛出异常 if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) { throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()); } throw new RemotingException(channel, res.getErrorMessage()); } // 省略其他方法}如上,当服务消费者还未接收到调用结果时,用户线程调用 get 方法会被阻塞住。同步调用模式下,框架获得 DefaultFuture 对象后,会立即调用 get 方法进行等待。而异步模式下则是将该对象封装到 FutureAdapter 实例中,并将 FutureAdapter 实例设置到 RpcContext 中,供用户使用。FutureAdapter 是一个适配器,用于将 Dubbo 中的 ResponseFuture 与 JDK 中的 Future 进行适配。这样当用户线程调用 Future 的 get 方法时,经过 FutureAdapter 适配后,最终会调用 ResponseFuture 实现类对象的 get 方法,也就是 DefaultFuture 的 get 方法。到这里关于 Dubbo 几种调用方式的代码逻辑就分析完了,下面来分析请求数据的发送与接收,以及响应数据的发送与接收过程。2.2 服务消费方发送请求2.2.1 发送请求本节我们来看一下同步调用模式下,服务消费方是如何发送调用请求的。在深入分析源码前,我们先来看一张图。这张图展示了服务消费方发送请求动作的部分调用栈,略为复杂。从上图可以看出,经过多次调用后,才将请求数据送至 Netty NioClientSocketChannel。这样做的原因是通过 Exchange 层为系统引入 Request 和 Response 语义,这一点会在接下来的代码分析过程中会看到。那其他的就说了,下面开始进行分析。首先分析 ReferenceCountExchangeClient 的源码。final class ReferenceCountExchangeClient implements ExchangeClient { private final URL url; private final AtomicInteger referenceCount = new AtomicInteger(0); public ReferenceCountExchangeClient(ExchangeClient client, ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap) { this.client = client; // 引用计数自增 referenceCount.incrementAndGet(); this.url = client.getUrl(); // … } @Override public ResponseFuture request(Object request) throws RemotingException { // 直接调用被装饰对象的同签名方法 return client.request(request); } @Override public ResponseFuture request(Object request, int timeout) throws RemotingException { // 直接调用被装饰对象的同签名方法 return client.request(request, timeout); } /* 引用计数自增,该方法由外部调用 / public void incrementAndGetCount() { // referenceCount 自增 referenceCount.incrementAndGet(); } @Override public void close(int timeout) { // referenceCount 自减 if (referenceCount.decrementAndGet() <= 0) { if (timeout == 0) { client.close(); } else { client.close(timeout); } client = replaceWithLazyClient(); } } // 省略部分方法}ReferenceCountExchangeClient 内部定义了一个引用计数变量 referenceCount,每当该对象被引用一次 referenceCount 都会进行自增。每当 close 方法被调用时,referenceCount 进行自减。ReferenceCountExchangeClient 内部仅实现了一个引用计数的功能,其他方法并无复杂逻辑,均是直接调用被装饰对象的相关方法。所以这里就不多说了,继续向下分析,这次是 HeaderExchangeClient。public class HeaderExchangeClient implements ExchangeClient { private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory(“dubbo-remoting-client-heartbeat”, true)); private final Client client; private final ExchangeChannel channel; private ScheduledFuture<?> heartbeatTimer; private int heartbeat; private int heartbeatTimeout; public HeaderExchangeClient(Client client, boolean needHeartbeat) { if (client == null) { throw new IllegalArgumentException(“client == null”); } this.client = client; // 创建 HeaderExchangeChannel 对象 this.channel = new HeaderExchangeChannel(client); // 以下代码均与心跳检测逻辑有关 String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY); this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith(“1.0.”) ? Constants.DEFAULT_HEARTBEAT : 0); this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3); if (heartbeatTimeout < heartbeat * 2) { throw new IllegalStateException(“heartbeatTimeout < heartbeatInterval * 2”); } if (needHeartbeat) { // 开启心跳检测定时器 startHeartbeatTimer(); } } @Override public ResponseFuture request(Object request) throws RemotingException { // 直接 HeaderExchangeChannel 对象的同签名方法 return channel.request(request); } @Override public ResponseFuture request(Object request, int timeout) throws RemotingException { // 直接 HeaderExchangeChannel 对象的同签名方法 return channel.request(request, timeout); } @Override public void close() { doClose(); channel.close(); } private void doClose() { // 停止心跳检测定时器 stopHeartbeatTimer(); } private void startHeartbeatTimer() { stopHeartbeatTimer(); if (heartbeat > 0) { heartbeatTimer = scheduled.scheduleWithFixedDelay( new HeartBeatTask(new HeartBeatTask.ChannelProvider() { @Override public Collection<Channel> getChannels() { return Collections.<Channel>singletonList(HeaderExchangeClient.this); } }, heartbeat, heartbeatTimeout), heartbeat, heartbeat, TimeUnit.MILLISECONDS); } } private void stopHeartbeatTimer() { if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) { try { heartbeatTimer.cancel(true); scheduled.purge(); } catch (Throwable e) { if (logger.isWarnEnabled()) { logger.warn(e.getMessage(), e); } } } heartbeatTimer = null; } // 省略部分方法}HeaderExchangeClient 中很多方法只有一行代码,即调用 HeaderExchangeChannel 对象的同签名方法。那 HeaderExchangeClient 有什么用处呢?答案是封装了一些关于心跳检测的逻辑。心跳检测并非本文所关注的点,因此就不多说了,继续向下看。final class HeaderExchangeChannel implements ExchangeChannel { private final Channel channel; HeaderExchangeChannel(Channel channel) { if (channel == null) { throw new IllegalArgumentException(“channel == null”); } // 这里的 channel 指向的是 NettyClient this.channel = channel; } @Override public ResponseFuture request(Object request) throws RemotingException { return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)); } @Override public ResponseFuture request(Object request, int timeout) throws RemotingException { if (closed) { throw new RemotingException(…, “Failed to send request …”); } // 创建 Request 对象 Request req = new Request(); req.setVersion(Version.getProtocolVersion()); // 设置双向调用标志为 true req.setTwoWay(true); // 这里的 request 变量类型为 RpcInvocation req.setData(request); // 创建 DefaultFuture 对象 DefaultFuture future = new DefaultFuture(channel, req, timeout); try { // 调用 NettyClient 的 send 方法发送请求 channel.send(req); } catch (RemotingException e) { future.cancel(); throw e; } // 返回 DefaultFuture 对象 return future; }}到这里大家终于看到了 Request 语义了,上面的方法首先定义了一个 Request 对象,然后在将该对象传给 NettyClient 的 send 方法,进行后续的调用。需要说明的是,NettyClient 中并未实现 send 方法,该方法继承自父类 AbstractPeer,下面直接分析 AbstractPeer 的代码。public abstract class AbstractPeer implements Endpoint, ChannelHandler { @Override public void send(Object message) throws RemotingException { // 该方法由 AbstractClient 类实现 send(message, url.getParameter(Constants.SENT_KEY, false)); } // 省略其他方法}public abstract class AbstractClient extends AbstractEndpoint implements Client { @Override public void send(Object message, boolean sent) throws RemotingException { if (send_reconnect && !isConnected()) { connect(); } // 获取 Channel,getChannel 是一个抽象方法,具体由子类实现 Channel channel = getChannel(); if (channel == null || !channel.isConnected()) { throw new RemotingException(this, “message can not send …”); } // 继续向下调用 channel.send(message, sent); } protected abstract Channel getChannel(); // 省略其他方法}默认情况下,Dubbo 使用 Netty 作为底层的通信框架,因此下面我们到 NettyClient 类中看一下 getChannel 方法的实现逻辑。public class NettyClient extends AbstractClient { // 这里的 Channel 全限定名称为 org.jboss.netty.channel.Channel private volatile Channel channel; @Override protected com.alibaba.dubbo.remoting.Channel getChannel() { Channel c = channel; if (c == null || !c.isConnected()) return null; // 获取一个 NettyChannel 类型对象 return NettyChannel.getOrAddChannel(c, getUrl(), this); }}final class NettyChannel extends AbstractChannel { private static final ConcurrentMap<org.jboss.netty.channel.Channel, NettyChannel> channelMap = new ConcurrentHashMap<org.jboss.netty.channel.Channel, NettyChannel>(); private final org.jboss.netty.channel.Channel channel; /* 私有构造方法 / private NettyChannel(org.jboss.netty.channel.Channel channel, URL url, ChannelHandler handler) { super(url, handler); if (channel == null) { throw new IllegalArgumentException(“netty channel == null;”); } this.channel = channel; } static NettyChannel getOrAddChannel(org.jboss.netty.channel.Channel ch, URL url, ChannelHandler handler) { if (ch == null) { return null; } // 尝试从集合中获取 NettyChannel 实例 NettyChannel ret = channelMap.get(ch); if (ret == null) { // 如果 ret = null,则创建一个新的 NettyChannel 实例 NettyChannel nc = new NettyChannel(ch, url, handler); if (ch.isConnected()) { // 将 <Channel, NettyChannel> 键值对存入 channelMap 集合中 ret = channelMap.putIfAbsent(ch, nc); } if (ret == null) { ret = nc; } } return ret; }}获取到 NettyChannel 实例后,即可进行后续的调用。下面看一下 NettyChannel 的 send 方法。public void send(Object message, boolean sent) throws RemotingException { super.send(message, sent); boolean success = true; int timeout = 0; try { // 发送请求 ChannelFuture future = channel.write(message); // sent 的值源于 <dubbo:method sent=“true/false” /> 中 sent 的配置值,有两种配置值: // 1. true: 等待消息发出,消息发送失败将抛出异常 // 2. false: 不等待消息发出,将消息放入 IO 队列,即刻返回 // 默认情况下 sent = false; if (sent) { timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 等待消息发出,若在规定时间没能发出,success 会被置为 false success = future.await(timeout); } Throwable cause = future.getCause(); if (cause != null) { throw cause; } } catch (Throwable e) { throw new RemotingException(this, “Failed to send message …”); } // 若 success 为 false,这里抛出异常 if (!success) { throw new RemotingException(this, “Failed to send message …”); }}经历多次调用,到这里请求数据的发送过程就结束了,过程漫长。为了便于大家阅读代码,这里以 DemoService 为例,将 sayHello 方法的整个调用路径贴出来。proxy0#sayHello(String) —> InvokerInvocationHandler#invoke(Object, Method, Object[]) —> MockClusterInvoker#invoke(Invocation) —> AbstractClusterInvoker#invoke(Invocation) —> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker<T>>, LoadBalance) —> Filter#invoke(Invoker, Invocation) // 包含多个 Filter 调用 —> ListenerInvokerWrapper#invoke(Invocation) —> AbstractInvoker#invoke(Invocation) —> DubboInvoker#doInvoke(Invocation) —> ReferenceCountExchangeClient#request(Object, int) —> HeaderExchangeClient#request(Object, int) —> HeaderExchangeChannel#request(Object, int) —> AbstractPeer#send(Object) —> AbstractClient#send(Object, boolean) —> NettyChannel#send(Object, boolean) —> NioClientSocketChannel#write(Object)在 Netty 中,出站数据在发出之前还需要进行编码操作,接下来我们来分析一下请求数据的编码逻辑。2.2.2 请求编码在分析请求编码逻辑之前,我们先来看一下 Dubbo 数据包结构。Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容。偏移量(Bit)字段取值0 ~ 7魔数高位0xda008 ~ 15魔数低位0xbb16数据包类型0 - Response, 1 - Request17调用方式仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用18事件标识0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包19 ~ 23序列化器编号2 - Hessian2Serialization3 - JavaSerialization4 - CompactedJavaSerialization6 - FastJsonSerialization7 - NativeJavaSerialization8 - KryoSerialization9 - FstSerialization24 ~ 31状态20 - OK30 - CLIENT_TIMEOUT31 - SERVER_TIMEOUT40 - BAD_REQUEST50 - BAD_RESPONSE……32 ~ 95请求编号共8字节,运行时生成96 ~ 127消息体长度运行时计算了解了 Dubbo 数据包格式,接下来我们就可以探索编码过程了。这次我们开门见山,直接分析编码逻辑所在类。如下:public class ExchangeCodec extends TelnetCodec { // 消息头长度 protected static final int HEADER_LENGTH = 16; // 魔数内容 protected static final short MAGIC = (short) 0xdabb; protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0]; protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1]; protected static final byte FLAG_REQUEST = (byte) 0x80; protected static final byte FLAG_TWOWAY = (byte) 0x40; protected static final byte FLAG_EVENT = (byte) 0x20; protected static final int SERIALIZATION_MASK = 0x1f; private static final Logger logger = LoggerFactory.getLogger(ExchangeCodec.class); public Short getMagicCode() { return MAGIC; } @Override public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException { if (msg instanceof Request) { // 对 Request 对象进行编码 encodeRequest(channel, buffer, (Request) msg); } else if (msg instanceof Response) { // 对 Response 对象进行编码,后面分析 encodeResponse(channel, buffer, (Response) msg); } else { super.encode(channel, buffer, msg); } } protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException { Serialization serialization = getSerialization(channel); // 创建消息头字节数组,长度为 16 byte[] header = new byte[HEADER_LENGTH]; // 设置魔数 Bytes.short2bytes(MAGIC, header); // 设置数据包类型(Request/Response)和序列化器编号 header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId()); // 设置调用方式(单向/双向) if (req.isTwoWay()) { header[2] |= FLAG_TWOWAY; } // 设置事件标识 if (req.isEvent()) { header[2] |= FLAG_EVENT; } // 设置请求编号,8个字节,从第4个字节开始设置 Bytes.long2bytes(req.getId(), header, 4); // 获取 buffer 当前的写位置 int savedWriteIndex = buffer.writerIndex(); // 更新 writerIndex,为消息头预留 16 个字节的空间 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); // 创建序列化器,比如 Hessian2ObjectOutput ObjectOutput out = serialization.serialize(channel.getUrl(), bos); if (req.isEvent()) { // 对事件数据进行序列化操作 encodeEventData(channel, out, req.getData()); } else { // 对请求数据进行序列化操作 encodeRequestData(channel, out, req.getData(), req.getVersion()); } out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } bos.flush(); bos.close(); // 获取写入的字节数,也就是消息体长度 int len = bos.writtenBytes(); checkPayload(channel, len); // 将消息体长度写入到消息头中 Bytes.int2bytes(len, header, 12); // 将 buffer 指针移动到 savedWriteIndex,为写消息头做准备 buffer.writerIndex(savedWriteIndex); // 从 savedWriteIndex 下标处写入消息头 buffer.writeBytes(header); // 设置新的 writerIndex,writerIndex = 原写下标 + 消息头长度 + 消息体长度 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len); } // 省略其他方法}以上就是请求对象的编码过程,该过程首先会通过位运算将消息头写入到局部变量 header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。本节的最后,我们再来看一下 Request 对象的 data 字段序列化过程,也就是 encodeRequestData 方法的逻辑,如下:public class DubboCodec extends ExchangeCodec implements Codec2 { protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException { RpcInvocation inv = (RpcInvocation) data; // 依次序列化 dubbo version、path、version out.writeUTF(version); out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); // 序列化调用方法名 out.writeUTF(inv.getMethodName()); // 将参数类型转换为字符串,并进行序列化 out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); Object[] args = inv.getArguments(); if (args != null) for (int i = 0; i < args.length; i++) { // 对运行时参数进行序列化 out.writeObject(encodeInvocationArgument(channel, inv, i)); } // 序列化 attachments out.writeObject(inv.getAttachments()); }}至此,关于服务消费方发送请求的过程就分析完了,接下来我们来看一下服务提供方是如何接收请求的。2.3 服务提供方接收请求前面说过,默认情况下 Dubbo 使用 Netty 作为底层的通信框架。Netty 检测到有数据入站后,首先会通过解码器对数据进行解码,并将解码后的数据传给下一个入站处理器的指定方法。所以在进行后续的分析之前,我们先来看一下数据解码过程。2.3.1 请求解码这里直接分析请求数据的解码逻辑,忽略中间过程,如下:public class ExchangeCodec extends TelnetCodec { @Override public Object decode(Channel channel, ChannelBuffer buffer) throws IOException { int readable = buffer.readableBytes(); // 创建消息头字节数组 byte[] header = new byte[Math.min(readable, HEADER_LENGTH)]; // 读取消息头数据 buffer.readBytes(header); // 调用重载方法进行后续解码工作 return decode(channel, buffer, readable, header); } @Override protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException { // 检查魔数是否相等 if (readable > 0 && header[0] != MAGIC_HIGH || readable > 1 && header[1] != MAGIC_LOW) { int length = header.length; if (header.length < readable) { header = Bytes.copyOf(header, readable); buffer.readBytes(header, length, readable - length); } for (int i = 1; i < header.length - 1; i++) { if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) { buffer.readerIndex(buffer.readerIndex() - header.length + i); header = Bytes.copyOf(header, i); break; } } // 通过 telnet 命令行发送的数据包不包含消息头,所以这里 // 调用 TelnetCodec 的 decode 方法对数据包进行解码 return super.decode(channel, buffer, readable, header); } // 检测可读数据量是否少于消息头长度,若小于则立即返回 DecodeResult.NEED_MORE_INPUT if (readable < HEADER_LENGTH) { return DecodeResult.NEED_MORE_INPUT; } // 从消息头中获取消息体长度 int len = Bytes.bytes2int(header, 12); // 检测消息体长度是否超出限制,超出则抛出异常 checkPayload(channel, len); int tt = len + HEADER_LENGTH; // 检测可读的字节数是否小于实际的字节数 if (readable < tt) { return DecodeResult.NEED_MORE_INPUT; } ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len); try { // 继续进行解码工作 return decodeBody(channel, is, header); } finally { if (is.available() > 0) { try { StreamUtils.skipUnusedStream(is); } catch (IOException e) { logger.warn(e.getMessage(), e); } } } }}上面方法通过检测消息头中的魔数书否与规定的魔数相等,提前拦截掉非常规数据包,比如通过 telnet 命令行发出的数据包。接着在对消息体长度,以及可读字节数进行检测。最后调用 decodeBody 方法进行后续的解码工作,ExchangeCodec 中实现了 decodeBody 方法,但因其子类 DubboCodec 覆写了该方法,所以在运行时 DubboCodec 中的 decodeBody 方法会被调用。下面我们来看一下该方法的代码。public class DubboCodec extends ExchangeCodec implements Codec2 { @Override protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException { // 获取消息头中的第三个字节,并通过逻辑与运算得到序列化器编号 byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK); Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto); // 获取调用编号 long id = Bytes.bytes2long(header, 4); // 通过逻辑与运算得到调用类型,0 - Response,1 - Request if ((flag & FLAG_REQUEST) == 0) { // 对响应结果进行解码,得到 Response 对象。这个非本节内容,后面再分析 // … } else { // 创建 Request 对象 Request req = new Request(id); req.setVersion(Version.getProtocolVersion()); // 通过逻辑与运算得到调用方式,并设置到 Request 对象中 req.setTwoWay((flag & FLAG_TWOWAY) != 0); // 通过位运算检测数据包是否为事件类型 if ((flag & FLAG_EVENT) != 0) { // 设置心跳事件到 Request 对象中 req.setEvent(Request.HEARTBEAT_EVENT); } try { Object data; if (req.isHeartbeat()) { // 对心跳包进行解码,该方法已被标注为废弃 data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is)); } else if (req.isEvent()) { // 对事件数据进行解码 data = decodeEventData(channel, deserialize(s, channel.getUrl(), is)); } else { DecodeableRpcInvocation inv; // 根据 url 参数判断是否在 IO 线程上对消息体进行解码 if (channel.getUrl().getParameter( Constants.DECODE_IN_IO_THREAD_KEY, Constants.DEFAULT_DECODE_IN_IO_THREAD)) { inv = new DecodeableRpcInvocation(channel, req, is, proto); // 在当前线程,也就是 IO 线程上进行后续的解码工作。此工作完成后,可将 // 调用方法名、attachment、以及调用参数解析出来 inv.decode(); } else { // 仅创建 DecodeableRpcInvocation 对象,但不在当前线程上执行解码逻辑 inv = new DecodeableRpcInvocation(channel, req, new UnsafeByteArrayInputStream(readMessageData(is)), proto); } data = inv; } // 设置 data 到 Request 对象中 req.setData(data); } catch (Throwable t) { // 若解码过程中出现异常,则将 broken 字段设为 true, // 并将异常对象设置到 Reqeust 对象中 req.setBroken(true); req.setData(t); } return req; } }}如上,decodeBody 对部分字段进行了解码,并将解码得到的字段封装到 Request 中。随后会调用 DecodeableRpcInvocation 的 decode 方法进行后续的解码工作。此工作完成后,可将调用方法名、attachment、以及调用参数解析出来。下面我们来看一下 DecodeableRpcInvocation 的 decode 方法逻辑。public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Decodeable { @Override public Object decode(Channel channel, InputStream input) throws IOException { ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) .deserialize(channel.getUrl(), input); // 通过反序列化得到 dubbo version,并保存到 attachments 变量中 String dubboVersion = in.readUTF(); request.setVersion(dubboVersion); setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion); // 通过反序列化得到 path,version,并保存到 attachments 变量中 setAttachment(Constants.PATH_KEY, in.readUTF()); setAttachment(Constants.VERSION_KEY, in.readUTF()); // 通过反序列化得到调用方法名 setMethodName(in.readUTF()); try { Object[] args; Class<?>[] pts; // 通过反序列化得到参数类型字符串,比如 Ljava/lang/String; String desc = in.readUTF(); if (desc.length() == 0) { pts = DubboCodec.EMPTY_CLASS_ARRAY; args = DubboCodec.EMPTY_OBJECT_ARRAY; } else { // 将 desc 解析为参数类型数组 pts = ReflectUtils.desc2classArray(desc); args = new Object[pts.length]; for (int i = 0; i < args.length; i++) { try { // 解析运行时参数 args[i] = in.readObject(pts[i]); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn(“Decode argument failed: " + e.getMessage(), e); } } } } // 设置参数类型数组 setParameterTypes(pts); // 通过反序列化得到原 attachments 的内容 Map<String, String> map = (Map<String, String>) in.readObject(Map.class); if (map != null && map.size() > 0) { Map<String, String> attachment = getAttachments(); if (attachment == null) { attachment = new HashMap<String, String>(); } // 将 map 与当前对象中的 attachment 集合进行融合 attachment.putAll(map); setAttachments(attachment); } // 对 callback 类型的参数进行处理 for (int i = 0; i < args.length; i++) { args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]); } // 设置参数列表 setArguments(args); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read invocation data failed.”, e)); } finally { if (in instanceof Cleanable) { ((Cleanable) in).cleanup(); } } return this; }}上面的方法通过反序列化将诸如 path、version、调用方法名、参数列表等信息依次解析出来,并设置到相应的字段中,最终得到一个具有完整调用信息的 DecodeableRpcInvocation 对象。到这里,请求解码的过程就分析完了。此时我们得到了一个 Request 对象,这个对象会被传送到下一个入站处理器中,我们继续往下看。2.3.2 调用服务解码器将数据包解析成 Request 对象后,NettyHandler 的 messageReceived 方法紧接着会收到这个对象,并将这个对象继续向下传递。这期间该对象会被依次传递给 NettyServer、MultiMessageHandler、HeartbeatHandler 以及 AllChannelHandler。最后由 AllChannelHandler 将该对象封装到 Runnable 实现类对象中,并将 Runnable 放入线程池中执行后续的调用逻辑。整个调用栈如下:NettyHandler#messageReceived(ChannelHandlerContext, MessageEvent) —> AbstractPeer#received(Channel, Object) —> MultiMessageHandler#received(Channel, Object) —> HeartbeatHandler#received(Channel, Object) —> AllChannelHandler#received(Channel, Object) —> ExecutorService#execute(Runnable) // 由线程池执行后续的调用逻辑考虑到篇幅,以及很多中间调用的逻辑并非十分重要,所以这里就不对调用栈中的每个方法都进行分析了。这里我们直接分析调用栈中的分析第一个和最后一个调用方法逻辑。如下:@Sharablepublic class NettyHandler extends SimpleChannelHandler { private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); private final URL url; private final ChannelHandler handler; public NettyHandler(URL url, ChannelHandler handler) { if (url == null) { throw new IllegalArgumentException(“url == null”); } if (handler == null) { throw new IllegalArgumentException(“handler == null”); } this.url = url; // 这里的 handler 类型为 NettyServer this.handler = handler; } public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { // 获取 NettyChannel NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler); try { // 继续向下调用 handler.received(channel, e.getMessage()); } finally { NettyChannel.removeChannelIfDisconnected(ctx.getChannel()); } }}如上,NettyHandler 中的 messageReceived 逻辑比较简单。首先根据一些信息获取 NettyChannel 实例,然后将 NettyChannel 实例以及 Request 对象向下传递。下面再来看看 AllChannelHandler 的逻辑,在详细分析代码之前,我们先来了解一下 Dubbo 中的线程派发模型。2.3.2.1 线程派发模型Dubbo 将底层通信框架中接收请求的线程称为 IO 线程。如果一些事件处理逻辑可以很快执行完,比如只在内存打一个标记,此时直接在 IO 线程上执行该段逻辑即可。但如果事件的处理逻辑比较耗时,比如该段逻辑会发起数据库查询或者 HTTP 请求。此时我们就不应该让事件处理逻辑在 IO 线程上执行,而是应该派发到线程池中去执行。原因也很简单,IO 线程主要用于接收请求,如果 IO 线程被占满,将导致它不能接收新的请求。以上就是线程派发的背景,下面我们再来通过 Dubbo 调用图,看一下线程派发器所处的位置。如上图,红框中的 Dispatcher 就是线程派发器。需要说明的是,Dispatcher 真实的职责创建具有线程派发能力的 ChannelHandler,比如 AllChannelHandler、MessageOnlyChannelHandler 和 ExecutionChannelHandler 等,其本身并不具备线程派发能力。Dubbo 支持 5 种不同的线程派发策略,下面通过一个表格列举一下。策略用途all所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等direct所有消息都不派发到线程池,全部在 IO 线程上直接执行message只有请求和响应消息派发到线程池,其它消息均在 IO 线程上执行execution只有请求消息派发到线程池,不含响应。其它消息均在 IO 线程上执行connection在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池默认配置下,Dubbo 使用 all 派发策略,即将所有的消息都派发到线程池中。下面我们来分析一下 AllChannelHandler 的代码。public class AllChannelHandler extends WrappedChannelHandler { public AllChannelHandler(ChannelHandler handler, URL url) { super(handler, url); } /* 处理连接事件 / @Override public void connected(Channel channel) throws RemotingException { // 获取线程池 ExecutorService cexecutor = getExecutorService(); try { // 将连接事件派发到线程池中处理 cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED)); } catch (Throwable t) { throw new ExecutionException(…, " error when process connected event .”, t); } } /* 处理断开事件 / @Override public void disconnected(Channel channel) throws RemotingException { ExecutorService cexecutor = getExecutorService(); try { cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED)); } catch (Throwable t) { throw new ExecutionException(…, “error when process disconnected event .”, t); } } /* 处理请求和响应消息,这里的 message 变量类型可能是 Request,也可能是 Response / @Override public void received(Channel channel, Object message) throws RemotingException { ExecutorService cexecutor = getExecutorService(); try { // 将请求和响应消息派发到线程池中处理 cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message)); } catch (Throwable t) { if(message instanceof Request && t instanceof RejectedExecutionException){ Request request = (Request)message; // 如果调用方式为双向调用,此时将 Server side … threadpool is exhausted // 错误信息封装到 Response 中,并返回给服务消费方。 if(request.isTwoWay()){ String msg = “Server side(” + url.getIp() + “,” + url.getPort() + “) threadpool is exhausted ,detail msg:” + t.getMessage(); Response response = new Response(request.getId(), request.getVersion()); response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR); response.setErrorMessage(msg); // 返回包含错误信息的 Response 对象 channel.send(response); return; } } throw new ExecutionException(…, " error when process received event .", t); } } /* 处理异常信息 / @Override public void caught(Channel channel, Throwable exception) throws RemotingException { ExecutorService cexecutor = getExecutorService(); try { cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception)); } catch (Throwable t) { throw new ExecutionException(…, “error when process caught event …”); } }}如上,请求对象会被封装 ChannelEventRunnable 中,ChannelEventRunnable 将会是服务调用过程的新起点。所以接下来我们以 ChannelEventRunnable 为起点向下探索。2.3.2.2 调用服务本小节,我们从 ChannelEventRunnable 开始分析,该类的主要代码如下:public class ChannelEventRunnable implements Runnable { private final ChannelHandler handler; private final Channel channel; private final ChannelState state; private final Throwable exception; private final Object message; @Override public void run() { // 检测消息类型是否为请求或响应 if (state == ChannelState.RECEIVED) { try { // 将 channel 和 message 传给 ChannelHandler 对象,进行后续的调用 handler.received(channel, message); } catch (Exception e) { logger.warn("… operation error, channel is … message is …"); } } // 其他消息类型通过 switch 进行处理 else { switch (state) { case CONNECTED: try { handler.connected(channel); } catch (Exception e) { logger.warn("… operation error, channel is …"); } break; case DISCONNECTED: // … case SENT: // … case CAUGHT: // … default: logger.warn(“unknown state: " + state + “, message is " + message); } } }}如上,请求和响应消息出现频率明显比其他类型消息高,所以这里对该类型的消息进行了针对性判断。ChannelEventRunnable 仅是一个中转站,它的 run 方法中并不包含具体的调用逻辑,仅用于将参数传给其他 ChannelHandler 对象进行处理,该对象类型为 DecodeHandler。public class DecodeHandler extends AbstractChannelHandlerDelegate { public DecodeHandler(ChannelHandler handler) { super(handler); } @Override public void received(Channel channel, Object message) throws RemotingException { if (message instanceof Decodeable) { // 对 Decodeable 接口实现类对象进行解码 decode(message); } if (message instanceof Request) { // 对 Request 的 data 字段进行解码 decode(((Request) message).getData()); } if (message instanceof Response) { // 对 Request 的 result 字段进行解码 decode(((Response) message).getResult()); } // 执行后续逻辑 handler.received(channel, message); } private void decode(Object message) { // Decodeable 接口目前有两个实现类, // 分别为 DecodeableRpcInvocation 和 DecodeableRpcResult if (message != null && message instanceof Decodeable) { try { // 执行解码逻辑 ((Decodeable) message).decode(); } catch (Throwable e) { if (log.isWarnEnabled()) { log.warn(“Call Decodeable.decode failed: " + e.getMessage(), e); } } } }}DecodeHandler 主要是包含了一些解码逻辑。2.2.1 节分析请求解码时说过,请求解码可在 IO 线程上执行,也可在线程池中执行,这个取决于运行时配置。DecodeHandler 存在的意义就是保证请求或响应对象可在线程池中被解码。解码完毕后,完全解码后的 Request 对象会继续向后传递,下一站是 HeaderExchangeHandler。public class HeaderExchangeHandler implements ChannelHandlerDelegate { private final ExchangeHandler handler; public HeaderExchangeHandler(ExchangeHandler handler) { if (handler == null) { throw new IllegalArgumentException(“handler == null”); } this.handler = handler; } @Override public void received(Channel channel, Object message) throws RemotingException { channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis()); ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel); try { if (message instanceof Request) { Request request = (Request) message; if (request.isEvent()) { // 处理事件 handlerEvent(channel, request); } // 处理普通的请求 else { // 双向通信 if (request.isTwoWay()) { // 向后调用服务,并得到调用结果 Response response = handleRequest(exchangeChannel, request); // 将调用结果返回给服务消费端 channel.send(response); } // 如果是单向通信,仅向后执行指定服务即可,无需返回执行结果 else { handler.received(exchangeChannel, request.getData()); } } } // 处理响应,服务消费方会执行此处逻辑,后面分析 else if (message instanceof Response) { handleResponse(channel, (Response) message); } else if (message instanceof String) { // telnet 相关,忽略 } else { handler.received(exchangeChannel, message); } } finally { HeaderExchangeChannel.removeChannelIfDisconnected(channel); } } Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException { Response res = new Response(req.getId(), req.getVersion()); // 检测请求是否合法,不合法则返回状态码为 BAD_REQUEST 的响应 if (req.isBroken()) { Object data = req.getData(); String msg; if (data == null) msg = null; else if (data instanceof Throwable) msg = StringUtils.toString((Throwable) data); else msg = data.toString(); res.setErrorMessage(“Fail to decode request due to: " + msg); // 设置 BAD_REQUEST 状态 res.setStatus(Response.BAD_REQUEST); return res; } // 获取 data,也就是 RpcInvocation 对象 Object msg = req.getData(); try { // 继续向下调用 Object result = handler.reply(channel, msg); // 设置 OK 状态码 res.setStatus(Response.OK); // 设置调用结果 res.setResult(result); } catch (Throwable e) { res.setStatus(Response.SERVICE_ERROR); res.setErrorMessage(StringUtils.toString(e)); } return res; }}到这里,我们看到了比较清晰的请求和响应逻辑。对于双向通信,HeaderExchangeHandler 首先向后进行调用,得到调用结果。然后将调用结果封装到 Response 对象中,最后再将该对象返回给服务消费方。如果请求不合法,或者调用失败,则将错误信息封装到 Response 对象中,并返回给服务消费方。接下来我们继续向后分析,把剩余的调用过程分析完。下面分析定义在 DubboProtocol 类中的匿名类对象逻辑,如下:public class DubboProtocol extends AbstractProtocol { public static final String NAME = “dubbo”; private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() { @Override public Object reply(ExchangeChannel channel, Object message) throws RemotingException { if (message instanceof Invocation) { Invocation inv = (Invocation) message; // 获取 Invoker 实例 Invoker<?> invoker = getInvoker(channel, inv); if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) { // 回调相关,忽略 } RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); // 通过 Invoker 调用具体的服务 return invoker.invoke(inv); } throw new RemotingException(channel, “Unsupported request: …”); } // 忽略其他方法 } Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException { // 忽略回调和本地存根相关逻辑 // … int port = channel.getLocalAddress().getPort(); // 计算 service key,格式为 groupName/serviceName:serviceVersion:port。比如: // dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880 String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY)); // 从 exporterMap 查找与 serviceKey 相对应的 DubboExporter 对象, // 服务导出过程中会将 <serviceKey, DubboExporter> 映射关系存储到 exporterMap 集合中 DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey); if (exporter == null) throw new RemotingException(channel, “Not found exported service …”); // 获取 Invoker 对象,并返回 return exporter.getInvoker(); } // 忽略其他方法}以上逻辑用于获取与指定服务对应的 Invoker 实例,并通过 Invoker 的 invoke 方法调用服务逻辑。invoke 方法定义在 AbstractProxyInvoker 中,下面我们看一下。public abstract class AbstractProxyInvoker<T> implements Invoker<T> { @Override public Result invoke(Invocation invocation) throws RpcException { try { // 调用 doInvoke 执行后续的调用,并将调用结果封装到 RpcResult 中,并 return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments())); } catch (InvocationTargetException e) { return new RpcResult(e.getTargetException()); } catch (Throwable e) { throw new RpcException(“Failed to invoke remote proxy method …”); } }}如上,doInvoke 是一个抽象方法,这个需要由具体的 Invoker 实例实现。Invoker 实例是在运行时通过 JavassistProxyFactory 创建的,创建逻辑如下:public class JavassistProxyFactory extends AbstractProxyFactory { // 省略其他方法 @Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { 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 { // 调用 invokeMethod 方法进行后续的调用 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }}Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,并实现 invokeMethod 方法,该方法最终会根据调用消息调用具体的服务。以 DemoServiceImpl 为例,Javassist 为其生成的代理类如下。/* Wrapper0 是在运行时生成的,大家可使用 Arthas 进行反编译 */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 Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException { DemoService demoService; try { // 对参数进行类型转换 demoService = (DemoService)object; } catch (Throwable throwable) { throw new IllegalArgumentException(throwable); } try { // 根据方法名调用指定的方法 if (“sayHello”.equals(string) && arrclass.length == 1) { return demoService.sayHello((String)arrobject[0]); } } catch (Throwable throwable) { throw new InvocationTargetException(throwable); } throw new NoSuchMethodException(new StringBuffer().append(“Not found method "”).append(string).append(”" in class com.alibaba.dubbo.demo.DemoService.”).toString()); }}到这里,整个服务调用过程就分析完了。最后把调用过程贴出来,如下:ChannelEventRunnable#run() —> DecodeHandler#received(Channel, Object) —> HeaderExchangeHandler#received(Channel, Object) —> HeaderExchangeHandler#handleRequest(ExchangeChannel, Request) —> DubboProtocol.requestHandler#reply(ExchangeChannel, Object) —> Filter#invoke(Invoker, Invocation) —> AbstractProxyInvoker#invoke(Invocation) —> Wrapper#invokeMethod(Object, String, Class[], Object[]) —> DemoServiceImpl#sayHello(String)2.4 服务提供方响应请求服务提供方调用指定服务后,会将调用结果封装到 Response 对象中,并将该对象返回给服务消费方。服务提供方也是通过 NettyChannel 的 send 方法将 Response 对象返回,这个方法在 2.2.1 节分析过,这里就不在重复分析了。本节我们仅需关注 Response 对象的编码过程即可,这里仍然省略一些中间调用,直接分析具体的编码逻辑。public class ExchangeCodec extends TelnetCodec { public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException { if (msg instanceof Request) { encodeRequest(channel, buffer, (Request) msg); } else if (msg instanceof Response) { // 对响应对象进行编码 encodeResponse(channel, buffer, (Response) msg); } else { super.encode(channel, buffer, msg); } } protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException { int savedWriteIndex = buffer.writerIndex(); try { Serialization serialization = getSerialization(channel); // 创建消息头字节数组 byte[] header = new byte[HEADER_LENGTH]; // 设置魔数 Bytes.short2bytes(MAGIC, header); // 设置序列化器编号 header[2] = serialization.getContentTypeId(); if (res.isHeartbeat()) header[2] |= FLAG_EVENT; // 获取响应状态 byte status = res.getStatus(); // 设置响应状态 header[3] = status; // 设置请求编号 Bytes.long2bytes(res.getId(), header, 4); // 更新 writerIndex,为消息头预留 16 个字节的空间 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); ObjectOutput out = serialization.serialize(channel.getUrl(), bos); if (status == Response.OK) { if (res.isHeartbeat()) { // 对心跳响应结果进行序列化,已废弃 encodeHeartbeatData(channel, out, res.getResult()); } else { // 对调用结果进行序列化 encodeResponseData(channel, out, res.getResult(), res.getVersion()); } } else { // 对错误信息进行序列化 out.writeUTF(res.getErrorMessage()) }; out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } bos.flush(); bos.close(); // 获取写入的字节数,也就是消息体长度 int len = bos.writtenBytes(); checkPayload(channel, len); // 将消息体长度写入到消息头中 Bytes.int2bytes(len, header, 12); // 将 buffer 指针移动到 savedWriteIndex,为写消息头做准备 buffer.writerIndex(savedWriteIndex); // 从 savedWriteIndex 下标处写入消息头 buffer.writeBytes(header); // 设置新的 writerIndex,writerIndex = 原写下标 + 消息头长度 + 消息体长度 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len); } catch (Throwable t) { // 异常处理逻辑不是很难理解,但是代码略多,这里忽略了 } }}public class DubboCodec extends ExchangeCodec implements Codec2 { protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException { Result result = (Result) data; // 检测当前协议版本是否支持带有 attachments 集合的 Response 对象 boolean attach = Version.isSupportResponseAttachment(version); Throwable th = result.getException(); // 异常信息为空 if (th == null) { Object ret = result.getValue(); // 调用结果为空 if (ret == null) { // 序列化响应类型 out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE); } // 调用结果非空 else { // 序列化响应类型 out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE); // 序列化调用结果 out.writeObject(ret); } } // 异常信息非空 else { // 序列化响应类型 out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION); // 序列化异常对象 out.writeObject(th); } if (attach) { // 记录 Dubbo 协议版本 result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); // 序列化 attachments 集合 out.writeObject(result.getAttachments()); } }}以上就是 Response 对象编码的过程,和前面分析的 Request 对象编码过程很相似。如果大家能看 Request 对象的编码逻辑,那么这里的代码逻辑也不难理解,这里就不多说了。接下来我们再来分析双向通信的最后一环 —— 服务消费方接收调用结果。2.5 服务消费方接收调用结果服务消费方再收到响应数据后,第一件要做的事情是对响应数据进行解码,得到 Response 对象。然后再将该对象传给下一个入站处理器,这个入站处理器就是 NettyHandler。接下来 NettyHandler 会将这个对象继续向下传递,最后 AllChannelHandler 的 received 方法会收到这个对象,并将这个对象派发到线程池中。这个过程和服务提供方接收请求的过程是一样的,因此这里就不重复分析了。本节我们重点分析两个方面的内容,一是响应数据的解码过程,二是 Dubbo 如何将调用结果传递给用户线程的。下面先来分析响应数据解码过程。2.5.1 响应数据解码响应数据解码逻辑主要的逻辑封装在 DubboCodec 中,我们直接分析这个类的代码。如下:public class DubboCodec extends ExchangeCodec implements Codec2 { @Override protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException { byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK); Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto); // 获取请求编号 long id = Bytes.bytes2long(header, 4); // 检测消息类型,若下面的条件成立,表明消息类型为 Response if ((flag & FLAG_REQUEST) == 0) { // 创建 Response 对象 Response res = new Response(id); // 检测事件标志位 if ((flag & FLAG_EVENT) != 0) { // 设置心跳事件 res.setEvent(Response.HEARTBEAT_EVENT); } // 获取响应状态 byte status = header[3]; // 设置响应状态 res.setStatus(status); // 如果响应状态为 OK,表明调用过程正常 if (status == Response.OK) { try { Object data; if (res.isHeartbeat()) { // 反序列化心跳数据,已废弃 data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is)); } else if (res.isEvent()) { // 反序列化事件数据 data = decodeEventData(channel, deserialize(s, channel.getUrl(), is)); } else { DecodeableRpcResult result; // 根据 url 参数决定是否在 IO 线程上执行解码逻辑 if (channel.getUrl().getParameter( Constants.DECODE_IN_IO_THREAD_KEY, Constants.DEFAULT_DECODE_IN_IO_THREAD)) { // 创建 DecodeableRpcResult 对象 result = new DecodeableRpcResult(channel, res, is, (Invocation) getRequestData(id), proto); // 进行后续的解码工作 result.decode(); } else { // 创建 DecodeableRpcResult 对象 result = new DecodeableRpcResult(channel, res, new UnsafeByteArrayInputStream(readMessageData(is)), (Invocation) getRequestData(id), proto); } data = result; } // 设置 DecodeableRpcResult 对象到 Response 对象中 res.setResult(data); } catch (Throwable t) { // 解码过程中出现了错误,此时设置 CLIENT_ERROR 状态码到 Response 对象中 res.setStatus(Response.CLIENT_ERROR); res.setErrorMessage(StringUtils.toString(t)); } } // 响应状态非 OK,表明调用过程出现了异常 else { // 反序列化异常信息,并设置到 Response 对象中 res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF()); } return res; } else { // 对请求数据进行解码,前面已分析过,此处忽略 } }}以上就是响应数据的解码过程,上面逻辑看起来是不是似曾相识。对的,我们在前面章节分析过 DubboCodec 的 decodeBody 方法中,关于请求数据的解码过程,该过程和响应数据的解码过程很相似。下面,我们继续分析调用结果的反序列化过程,如下:public class DecodeableRpcResult extends RpcResult implements Codec, Decodeable { private Invocation invocation; @Override public void decode() throws Exception { if (!hasDecoded && channel != null && inputStream != null) { try { // 执行反序列化操作 decode(channel, inputStream); } catch (Throwable e) { // 反序列化失败,设置 CLIENT_ERROR 状态到 Response 对象中 response.setStatus(Response.CLIENT_ERROR); // 设置异常信息 response.setErrorMessage(StringUtils.toString(e)); } finally { hasDecoded = true; } } } @Override public Object decode(Channel channel, InputStream input) throws IOException { ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) .deserialize(channel.getUrl(), input); // 反序列化响应类型 byte flag = in.readByte(); switch (flag) { case DubboCodec.RESPONSE_NULL_VALUE: break; case DubboCodec.RESPONSE_VALUE: // … break; case DubboCodec.RESPONSE_WITH_EXCEPTION: // … break; // 返回值为空,且携带了 attachments 集合 case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: try { // 反序列化 attachments 集合,并存储起来 setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; // 返回值不为空,且携带了 attachments 集合 case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS: try { // 获取返回值类型 Type[] returnType = RpcUtils.getReturnTypes(invocation); // 反序列化调用结果,并保存起来 setValue(returnType == null || returnType.length == 0 ? in.readObject() : (returnType.length == 1 ? in.readObject((Class<?>) returnType[0]) : in.readObject((Class<?>) returnType[0], returnType[1]))); // 反序列化 attachments 集合,并存储起来 setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; // 异常对象不为空,且携带了 attachments 集合 case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: try { // 反序列化异常对象 Object obj = in.readObject(); if (obj instanceof Throwable == false) throw new IOException(“Response data error, expect Throwable, but get " + obj); // 设置异常对象 setException((Throwable) obj); // 反序列化 attachments 集合,并存储起来 setAttachments((Map<String, String>) in.readObject(Map.class)); } catch (ClassNotFoundException e) { throw new IOException(StringUtils.toString(“Read response data failed.”, e)); } break; default: throw new IOException(“Unknown result flag, expect ‘0’ ‘1’ ‘2’, get " + flag); } if (in instanceof Cleanable) { ((Cleanable) in).cleanup(); } return this; }}本篇文章所分析的源码版本为 2.6.4,该版本下的 Response 支持 attachments 集合,所以上面仅对部分 case 分支进行了注释。其他 case 分支的逻辑比被注释分支的逻辑更为简单,所以这里就忽略了。我们所使用的测试服务接口 DemoService 包含了一个具有返回值的方法,所以正常调用下,线程会进入 RESPONSE_VALUE_WITH_ATTACHMENTS 分支中。线程首先会从 invocation 变量(大家探索一下 invocation 变量的由来)中获取返回值类型,接着对调用结果进行反序列化,并将序列化后的结果存储起来。最后对 attachments 集合进行反序列化,并存到指定字段中。到此,关于响应数据的解码过程就分析完了。接下来,我们再来探索一下响应对象 Response 的去向。2.5.2 向用户线程传递调用结果响应数据解码完成后,Dubbo 会将响应对象派发到线程池上。要注意的是,线程池中的线程并非用户的调用线程,所以要想办法将响应对象从线程池线程传递到用户线程上。我们在 2.1 节分析过用户线程在发送完请求后的动作,即调用 DefaultFuture 的 get 方法等待响应对象的到来。当响应对象到来后,用户线程会被唤醒,并通过调用编号获取属于自己的响应对象。下面我们就来看一下整个过程。public class HeaderExchangeHandler implements ChannelHandlerDelegate { @Override public void received(Channel channel, Object message) throws RemotingException { channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis()); ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel); try { if (message instanceof Request) { // 处理请求,前面已分析过,省略 } else if (message instanceof Response) { // 处理响应 handleResponse(channel, (Response) message); } else if (message instanceof String) { // telnet 相关,忽略 } else { handler.received(exchangeChannel, message); } } finally { HeaderExchangeChannel.removeChannelIfDisconnected(channel); } } static void handleResponse(Channel channel, Response response) throws RemotingException { if (response != null && !response.isHeartbeat()) { // 继续向下调用 DefaultFuture.received(channel, response); } }}public class DefaultFuture implements ResponseFuture { private final Lock lock = new ReentrantLock(); private final Condition done = lock.newCondition(); private volatile Response response; public static void received(Channel channel, Response response) { try { // 根据调用编号从 FUTURES 集合中查找指定的 DefaultFuture 对象 DefaultFuture future = FUTURES.remove(response.getId()); if (future != null) { // 继续向下调用 future.doReceived(response); } else { logger.warn(“The timeout response finally returned at …”); } } finally { CHANNELS.remove(response.getId()); } } private void doReceived(Response res) { lock.lock(); try { // 保存响应对象 response = res; if (done != null) { // 唤醒用户线程 done.signal(); } } finally { lock.unlock(); } if (callback != null) { invokeCallback(callback); } }}以上逻辑是将响应对象保存到相应的 DefaultFuture 实例中,然后再唤醒用户线程,随后用户线程即可从 DefaultFuture 实例中获取到相应结果。本篇文章在多个地方都强调过调用编号很重要,但一直没有解释原因,这里简单说明一下。一般情况下,服务消费方会并发调用多个服务,每个用户线程发送请求后,会调用不同 DefaultFuture 对象的 get 方法进行等待。 一段时间后,服务消费方的线程池会收到多个响应对象。这个时候要考虑一个问题,如何将每个响应对象传递给相应的 DefaultFuture 对象,且不出错。答案是通过调用编号。DefaultFuture 被创建时,会要求传入一个 Request 对象。此时 DefaultFuture 可从 Request 对象中取出调用编号,并将 <调用编号, DefaultFuture 对象> 映射关系存入到静态 Map 中,即 FUTURES。线程池中的线程在收到 Response 对象后,会根据 Response 对象中的调用编号到 FUTURES 集合中取出相应的 DefaultFuture 对象,然后再将 Response 对象设置到 DefaultFuture 对象中。最后再唤醒用户线程,这样用户线程即可从 DefaultFuture 对象中获取调用结果了。整个过程大致如下图:3. 总结本篇文章主要对 Dubbo 中的几种服务调用方式,以及从双向通信的角度对整个通信过程进行了详细的分析。按照通信顺序,通信过程包括服务消费方发送请求,服务提供方接收请求,服务提供方返回响应数据,服务消费方接收响应数据等过程。理解这些过程需要大家对网络编程,尤其是 Netty 有一定的了解。限于篇幅原因,本篇文章无法将服务调用的所有内容都进行分析。对于本篇文章未讲到或未详细分析的内容,比如服务降级、过滤器链、以及序列化等。对于这些内容,大家若感兴趣,可自行进行分析。并将分析整理成文,分享给社区。本篇文章就到这里了,感谢阅读。附录:Dubbo 源码分析系列文章时间文章2018-10-01Dubbo 源码分析 - SPI 机制2018-10-13Dubbo 源码分析 - 自适应拓展原理2018-10-31Dubbo 源码分析 - 服务导出2018-11-12Dubbo 源码分析 - 服务引用2018-11-17Dubbo 源码分析 - 集群容错之 Directory2018-11-20Dubbo 源码分析 - 集群容错之 Router2018-11-22Dubbo 源码分析 - 集群容错之 Cluster2018-11-29Dubbo 源码分析 - 集群容错之 LoadBalance2019-01-09Dubbo 源码分析 - 服务调用过程本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处作者:田小波本文同步发布在我的个人博客:http://www.tianxiaobo.com本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 ...

January 9, 2019 · 27 min · jiezi

dubbo源码解析(二十)远程调用——Filter

远程调用——Filter目标:介绍dubbo-rpc-api中的各种filter过滤器的实现逻辑。前言本文会介绍在dubbo中的过滤器,先来看看下面的图:可以看到红色圈圈不服,在服务发现和服务引用中都会进行一些过滤器过滤。具体有哪些过滤器,就看下面的介绍。源码分析(一)AccessLogFilter该过滤器是对记录日志的过滤器,它所做的工作就是把引用服务或者暴露服务的调用链信息写入到文件中。日志消息先被放入日志集合,然后加入到日志队列,然后被放入到写入文件到任务中,最后进入文件。1.属性private static final Logger logger = LoggerFactory.getLogger(AccessLogFilter.class);/** * 日志访问名称,默认的日志访问名称 /private static final String ACCESS_LOG_KEY = “dubbo.accesslog”;/* * 日期格式 /private static final String FILE_DATE_FORMAT = “yyyyMMdd”;private static final String MESSAGE_DATE_FORMAT = “yyyy-MM-dd HH:mm:ss”;/* * 日志队列大小 /private static final int LOG_MAX_BUFFER = 5000;/* * 日志输出的频率 /private static final long LOG_OUTPUT_INTERVAL = 5000;/* * 日志队列 key为访问日志的名称,value为该日志名称对应的日志集合 /private final ConcurrentMap<String, Set<String>> logQueue = new ConcurrentHashMap<String, Set<String>>();/* * 日志线程池 /private final ScheduledExecutorService logScheduled = Executors.newScheduledThreadPool(2, new NamedThreadFactory(“Dubbo-Access-Log”, true));/* * 日志记录任务 /private volatile ScheduledFuture<?> logFuture = null;按照我上面讲到日志流向,日志先进入到是日志队列中的日志集合,再进入logQueue,在进入logFuture,最后落地到文件。2.initprivate void init() { // synchronized是一个重操作消耗性能,所有加上判空 if (logFuture == null) { synchronized (logScheduled) { // 为了不重复初始化 if (logFuture == null) { // 创建日志记录任务 logFuture = logScheduled.scheduleWithFixedDelay(new LogTask(), LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS); } } }}该方法是初始化方法,就创建了日志记录任务。3.logprivate void log(String accesslog, String logmessage) { init(); Set<String> logSet = logQueue.get(accesslog); if (logSet == null) { logQueue.putIfAbsent(accesslog, new ConcurrentHashSet<String>()); logSet = logQueue.get(accesslog); } if (logSet.size() < LOG_MAX_BUFFER) { logSet.add(logmessage); }}该方法是增加日志信息到日志集合中。4.invoke@Overridepublic Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { try { // 获得日志名称 String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY); if (ConfigUtils.isNotEmpty(accesslog)) { // 获得rpc上下文 RpcContext context = RpcContext.getContext(); // 获得调用的接口名称 String serviceName = invoker.getInterface().getName(); // 获得版本号 String version = invoker.getUrl().getParameter(Constants.VERSION_KEY); // 获得组,是消费者侧还是生产者侧 String group = invoker.getUrl().getParameter(Constants.GROUP_KEY); StringBuilder sn = new StringBuilder(); sn.append("[").append(new SimpleDateFormat(MESSAGE_DATE_FORMAT).format(new Date())).append("] “).append(context.getRemoteHost()).append(”:").append(context.getRemotePort()) .append(" -> “).append(context.getLocalHost()).append(”:").append(context.getLocalPort()) .append(" - “); // 拼接组 if (null != group && group.length() > 0) { sn.append(group).append(”/"); } // 拼接服务名称 sn.append(serviceName); // 拼接版本号 if (null != version && version.length() > 0) { sn.append(":").append(version); } sn.append(" “); // 拼接方法名 sn.append(inv.getMethodName()); sn.append(”("); // 拼接参数类型 Class<?>[] types = inv.getParameterTypes(); // 拼接参数类型 if (types != null && types.length > 0) { boolean first = true; for (Class<?> type : types) { if (first) { first = false; } else { sn.append(","); } sn.append(type.getName()); } } sn.append(") “); // 拼接参数 Object[] args = inv.getArguments(); if (args != null && args.length > 0) { sn.append(JSON.toJSONString(args)); } String msg = sn.toString(); // 如果用默认的日志访问名称 if (ConfigUtils.isDefault(accesslog)) { LoggerFactory.getLogger(ACCESS_LOG_KEY + “.” + invoker.getInterface().getName()).info(msg); } else { // 把日志加入集合 log(accesslog, msg); } } } catch (Throwable t) { logger.warn(“Exception in AcessLogFilter of service(” + invoker + " -> " + inv + “)”, t); } // 调用下一个调用链 return invoker.invoke(inv);}该方法是最重要的方法,从拼接了日志信息,把日志加入到集合,并且调用下一个调用链。4.LogTaskprivate class LogTask implements Runnable { @Override public void run() { try { if (logQueue != null && logQueue.size() > 0) { // 遍历日志队列 for (Map.Entry<String, Set<String>> entry : logQueue.entrySet()) { try { // 获得日志名称 String accesslog = entry.getKey(); // 获得日志集合 Set<String> logSet = entry.getValue(); // 如果文件不存在则创建文件 File file = new File(accesslog); File dir = file.getParentFile(); if (null != dir && !dir.exists()) { dir.mkdirs(); } if (logger.isDebugEnabled()) { logger.debug(“Append log to " + accesslog); } if (file.exists()) { // 获得现在的时间 String now = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date()); // 获得文件最后一次修改的时间 String last = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date(file.lastModified())); // 如果文件最后一次修改的时间不等于现在的时间 if (!now.equals(last)) { // 获得重新生成文件名称 File archive = new File(file.getAbsolutePath() + “.” + last); // 因为都是file的绝对路径,所以没有进行移动文件,而是修改文件名 file.renameTo(archive); } } // 把日志集合中的日志写入到文件 FileWriter writer = new FileWriter(file, true); try { for (Iterator<String> iterator = logSet.iterator(); iterator.hasNext(); iterator.remove()) { writer.write(iterator.next()); writer.write("\r\n”); } writer.flush(); } finally { writer.close(); } } catch (Exception e) { logger.error(e.getMessage(), e); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } }}该内部类实现了Runnable,是把日志消息落地到文件到线程。(二)ActiveLimitFilter该类时对于每个服务的每个方法的最大可并行调用数量限制的过滤器,它是在服务消费者侧的过滤。@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得url对象 URL url = invoker.getUrl(); // 获得方法名称 String methodName = invocation.getMethodName(); // 获得并发调用数(单个服务的单个方法),默认为0 int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0); // 通过方法名来获得对应的状态 RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()); if (max > 0) { // 获得该方法调用的超时次数 long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0); // 获得系统时间 long start = System.currentTimeMillis(); long remain = timeout; // 获得该方法的调用数量 int active = count.getActive(); // 如果活跃数量大于等于最大的并发调用数量 if (active >= max) { synchronized (count) { // 当活跃数量大于等于最大的并发调用数量时一直循环 while ((active = count.getActive()) >= max) { try { // 等待超时时间 count.wait(remain); } catch (InterruptedException e) { } // 获得累计时间 long elapsed = System.currentTimeMillis() - start; remain = timeout - elapsed; // 如果累计时间大于超时时间,则抛出异常 if (remain <= 0) { throw new RpcException(“Waiting concurrent invoke timeout in client-side for service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, elapsed: " + elapsed + “, timeout: " + timeout + “. concurrent invokes: " + active + “. max concurrent invoke limit: " + max); } } } } } try { // 获得系统时间作为开始时间 long begin = System.currentTimeMillis(); // 开始计数 RpcStatus.beginCount(url, methodName); try { // 调用后面的调用链,如果没有抛出异常,则算成功 Result result = invoker.invoke(invocation); // 结束计数,记录时间 RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true); return result; } catch (RuntimeException t) { RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false); throw t; } } finally { if (max > 0) { synchronized (count) { // 唤醒count count.notify(); } } }}该类只有这一个方法。该过滤器是用来限制调用数量,先进行调用数量的检测,如果没有到达最大的调用数量,则先调用后面的调用链,如果在后面的调用链失败,则记录相关时间,如果成功也记录相关时间和调用次数。(三)ClassLoaderFilter该过滤器是做类加载器切换的。@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得当前的类加载器 ClassLoader ocl = Thread.currentThread().getContextClassLoader(); // 设置invoker携带的服务的类加载器 Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader()); try { // 调用下面的调用链 return invoker.invoke(invocation); } finally { // 最后切换回原来的类加载器 Thread.currentThread().setContextClassLoader(ocl); }}可以看到先切换成当前的线程锁携带的类加载器,然后调用结束后,再切换回原先的类加载器。(四)CompatibleFilter该过滤器是做兼容性的过滤器。@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 调用下一个调用链 Result result = invoker.invoke(invocation); // 如果方法前面没有$或者结果没有异常 if (!invocation.getMethodName().startsWith("$”) && !result.hasException()) { Object value = result.getValue(); if (value != null) { try { // 获得方法 Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); // 获得返回的数据类型 Class<?> type = method.getReturnType(); Object newValue; // 序列化方法 String serialization = invoker.getUrl().getParameter(Constants.SERIALIZATION_KEY); // 如果是json或者fastjson形式 if (“json”.equals(serialization) || “fastjson”.equals(serialization)) { // 获得方法的泛型返回值类型 Type gtype = method.getGenericReturnType(); // 把数据结果进行类型转化 newValue = PojoUtils.realize(value, type, gtype); // 如果value不是type类型 } else if (!type.isInstance(value)) { // 如果是pojo,则,转化为type类型,如果不是,则进行兼容类型转化。 newValue = PojoUtils.isPojo(type) ? PojoUtils.realize(value, type) : CompatibleTypeUtils.compatibleTypeConvert(value, type); } else { newValue = value; } // 重新设置RpcResult的result if (newValue != value) { result = new RpcResult(newValue); } } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } return result;}可以看到对于调用链的返回结果,如果返回值类型和返回值不一样的时候,就需要做兼容类型的转化。重新把结果放入RpcResult,返回。(五)ConsumerContextFilter该过滤器做的是在当前的RpcContext中记录本地调用的一次状态信息。@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 设置rpc上下文 RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) .setLocalAddress(NetUtils.getLocalHost(), 0) .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); // 如果该会话域是rpc会话域 if (invocation instanceof RpcInvocation) { // 设置实体域 ((RpcInvocation) invocation).setInvoker(invoker); } try { // 调用下个调用链 RpcResult result = (RpcResult) invoker.invoke(invocation); // 设置附加值 RpcContext.getServerContext().setAttachments(result.getAttachments()); return result; } finally { // 情况附加值 RpcContext.getContext().clearAttachments(); }}可以看到RpcContext记录了一次调用状态信息,然后先调用后面的调用链,再回来把附加值设置到RpcContext中。然后返回RpcContext,再清空,这样是因为后面的调用链中的附加值对前面的调用链是不可见的。(六)ContextFilter该过滤器做的是初始化rpc上下文。 @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得会话域的附加值 Map<String, String> attachments = invocation.getAttachments(); // 删除异步属性以避免传递给以下调用链 if (attachments != null) { attachments = new HashMap<String, String>(attachments); attachments.remove(Constants.PATH_KEY); attachments.remove(Constants.GROUP_KEY); attachments.remove(Constants.VERSION_KEY); attachments.remove(Constants.DUBBO_VERSION_KEY); attachments.remove(Constants.TOKEN_KEY); attachments.remove(Constants.TIMEOUT_KEY); attachments.remove(Constants.ASYNC_KEY);// Remove async property to avoid being passed to the following invoke chain. } // 在rpc上下文添加上一个调用链的信息 RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation)// .setAttachments(attachments) // merged from dubbox .setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); // mreged from dubbox // we may already added some attachments into RpcContext before this filter (e.g. in rest protocol) if (attachments != null) { // 把会话域中的附加值全部加入RpcContext中 if (RpcContext.getContext().getAttachments() != null) { RpcContext.getContext().getAttachments().putAll(attachments); } else { RpcContext.getContext().setAttachments(attachments); } } // 如果会话域属于rpc的会话域,则设置实体域 if (invocation instanceof RpcInvocation) { ((RpcInvocation) invocation).setInvoker(invoker); } try { // 调用下一个调用链 RpcResult result = (RpcResult) invoker.invoke(invocation); // pass attachments to result 把附加值加入到RpcResult result.addAttachments(RpcContext.getServerContext().getAttachments()); return result; } finally { // 移除本地的上下文 RpcContext.removeContext(); // 清空附加值 RpcContext.getServerContext().clearAttachments(); } }在《 dubbo源码解析(十九)远程调用——开篇》中我已经介绍了RpcContext的作用,角色。该过滤器就是做了初始化RpcContext的作用。(七)DeprecatedFilter该过滤器的作用是调用了废弃的方法时打印错误日志。private static final Logger LOGGER = LoggerFactory.getLogger(DeprecatedFilter.class);/* * 日志集合 /private static final Set<String> logged = new ConcurrentHashSet<String>();@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得key 服务+方法 String key = invoker.getInterface().getName() + “.” + invocation.getMethodName(); // 如果集合中没有该key if (!logged.contains(key)) { // 则加入集合 logged.add(key); // 如果该服务方法是废弃的,则打印错误日志 if (invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.DEPRECATED_KEY, false)) { LOGGER.error(“The service method " + invoker.getInterface().getName() + “.” + getMethodSignature(invocation) + " is DEPRECATED! Declare from " + invoker.getUrl()); } } // 调用下一个调用链 return invoker.invoke(invocation);}/* * 获得方法定义 * @param invocation * @return /private String getMethodSignature(Invocation invocation) { // 方法名 StringBuilder buf = new StringBuilder(invocation.getMethodName()); buf.append(”(”); // 参数类型 Class<?>[] types = invocation.getParameterTypes(); // 拼接参数 if (types != null && types.length > 0) { boolean first = true; for (Class<?> type : types) { if (first) { first = false; } else { buf.append(”, “); } buf.append(type.getSimpleName()); } } buf.append(”)”); return buf.toString();}该过滤器比较简单。(八)EchoFilter该过滤器是处理回声测试的方法。@Overridepublic Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { // 如果调用的方法是回声测试的方法 则直接返回结果,否则 调用下一个调用链 if (inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1) return new RpcResult(inv.getArguments()[0]); return invoker.invoke(inv);}如果调用的方法是回声测试的方法 则直接返回结果,否则 调用下一个调用链。(九)ExceptionFilter该过滤器是作用是对异常的处理。private final Logger logger;public ExceptionFilter() { this(LoggerFactory.getLogger(ExceptionFilter.class));}public ExceptionFilter(Logger logger) { this.logger = logger;}@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { // 调用下一个调用链,返回结果 Result result = invoker.invoke(invocation); // 如果结果有异常,并且该服务不是一个泛化调用 if (result.hasException() && GenericService.class != invoker.getInterface()) { try { // 获得异常 Throwable exception = result.getException(); // directly throw if it’s checked exception // 如果这是一个checked的异常,则直接返回异常,也就是接口上声明的Unchecked的异常 if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature // 如果已经在接口方法上声明了该异常,则直接返回 try { // 获得方法 Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); // 获得异常类型 Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // for the exception not found in method’s signature, print ERROR message in server’s log. // 打印错误 该异常没有在方法上申明 logger.error(“Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + exception.getClass().getName() + “: " + exception.getMessage(), exception); // directly throw if exception class and interface class are in the same jar file. // 如果异常类和接口类在同一个jar包里面,则抛出异常 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // directly throw if it’s JDK exception // 如果是jdk中定义的异常,则直接抛出 String className = exception.getClass().getName(); if (className.startsWith(“java.”) || className.startsWith(“javax.”)) { return result; } // directly throw if it’s dubbo exception // 如果 是dubbo的异常,则直接抛出 if (exception instanceof RpcException) { return result; } // otherwise, wrap with RuntimeException and throw back to the client // 如果不是以上的异常,则包装成为RuntimeException并且抛出 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn(“Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + e.getClass().getName() + “: " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { logger.error(“Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + e.getClass().getName() + “: " + e.getMessage(), e); throw e; }}可以看到除了接口上声明的Unchecked的异常和有定义的异常外,都会包装成RuntimeException来返回,为了防止客户端反序列化失败。(十)ExecuteLimitFilter该过滤器是限制最大可并行执行请求数,该过滤器是服务提供者侧,而上述讲到的ActiveLimitFilter是在消费者侧的限制。 @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得url对象 URL url = invoker.getUrl(); // 方法名称 String methodName = invocation.getMethodName(); Semaphore executesLimit = null; boolean acquireResult = false; int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0); // 如果该方法设置了executes并且值大于0 if (max > 0) { // 获得该方法对应的RpcStatus RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());// if (count.getActive() >= max) { /* * http://manzhizhen.iteye.com/blog/2386408 * use semaphore for concurrency control (to limit thread number) / // 获得信号量 executesLimit = count.getSemaphore(max); // 如果不能获得许可,则抛出异常 if(executesLimit != null && !(acquireResult = executesLimit.tryAcquire())) { throw new RpcException(“Failed to invoke method " + invocation.getMethodName() + " in provider " + url + “, cause: The service using threads greater than <dubbo:service executes="” + max + “" /> limited.”); } } long begin = System.currentTimeMillis(); boolean isSuccess = true; // 计数加1 RpcStatus.beginCount(url, methodName); try { // 调用下一个调用链 Result result = invoker.invoke(invocation); return result; } catch (Throwable t) { isSuccess = false; if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RpcException(“unexpected exception when ExecuteLimitFilter”, t); } } finally { // 计数减1 RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess); if(acquireResult) { executesLimit.release(); } } }为什么这里需要用到信号量来控制,可以看一下以下链接的介绍:http://manzhizhen.iteye.com/b…(十一)GenericFilter该过滤器就是对于泛化调用的请求和结果进行反序列化和序列化的操作,它是服务提供者侧的。@Overridepublic Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { // 如果是泛化调用 if (inv.getMethodName().equals(Constants.$INVOKE) && inv.getArguments() != null && inv.getArguments().length == 3 && !invoker.getInterface().equals(GenericService.class)) { // 获得请求名字 String name = ((String) inv.getArguments()[0]).trim(); // 获得请求参数类型 String[] types = (String[]) inv.getArguments()[1]; // 获得请求参数 Object[] args = (Object[]) inv.getArguments()[2]; try { // 获得方法 Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types); // 获得该方法的参数类型 Class<?>[] params = method.getParameterTypes(); if (args == null) { args = new Object[params.length]; } // 获得附加值 String generic = inv.getAttachment(Constants.GENERIC_KEY); // 如果附加值为空,在用上下文携带的附加值 if (StringUtils.isBlank(generic)) { generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY); } // 如果附加值还是为空或者是默认的泛化序列化类型 if (StringUtils.isEmpty(generic) || ProtocolUtils.isDefaultGenericSerialization(generic)) { // 直接进行类型转化 args = PojoUtils.realize(args, params, method.getGenericParameterTypes()); } else if (ProtocolUtils.isJavaGenericSerialization(generic)) { for (int i = 0; i < args.length; i++) { if (byte[].class == args[i].getClass()) { try { UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]); // 使用nativejava方式反序列化 args[i] = ExtensionLoader.getExtensionLoader(Serialization.class) .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA) .deserialize(null, is).readObject(); } catch (Exception e) { throw new RpcException(“Deserialize argument [” + (i + 1) + “] failed.”, e); } } else { throw new RpcException( “Generic serialization [” + Constants.GENERIC_SERIALIZATION_NATIVE_JAVA + “] only support message type " + byte[].class + " and your message type is " + args[i].getClass()); } } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof JavaBeanDescriptor) { // 用JavaBean方式反序列化 args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]); } else { throw new RpcException( “Generic serialization [” + Constants.GENERIC_SERIALIZATION_BEAN + “] only support message type " + JavaBeanDescriptor.class.getName() + " and your message type is " + args[i].getClass().getName()); } } } // 调用下一个调用链 Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments())); if (result.hasException() && !(result.getException() instanceof GenericException)) { return new RpcResult(new GenericException(result.getException())); } if (ProtocolUtils.isJavaGenericSerialization(generic)) { try { UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512); // 用nativejava方式序列化 ExtensionLoader.getExtensionLoader(Serialization.class) .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA) .serialize(null, os).writeObject(result.getValue()); return new RpcResult(os.toByteArray()); } catch (IOException e) { throw new RpcException(“Serialize result failed.”, e); } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { // 使用JavaBean方式序列化返回结果 return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD)); } else { // 直接转化为pojo类型然后返回 return new RpcResult(PojoUtils.generalize(result.getValue())); } } catch (NoSuchMethodException e) { throw new RpcException(e.getMessage(), e); } catch (ClassNotFoundException e) { throw new RpcException(e.getMessage(), e); } } // 调用下一个调用链 return invoker.invoke(inv);}(十二)GenericImplFilter该过滤器也是对于泛化调用的序列化检查和处理,它是消费者侧的过滤器。private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);/* * 参数集合 /private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{String.class, String[].class, Object[].class};@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得泛化的值 String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY); // 如果该值是nativejava或者bean或者true,并且不是一个返回调用 if (ProtocolUtils.isGeneric(generic) && !Constants.$INVOKE.equals(invocation.getMethodName()) && invocation instanceof RpcInvocation) { RpcInvocation invocation2 = (RpcInvocation) invocation; // 获得方法名称 String methodName = invocation2.getMethodName(); // 获得参数类型集合 Class<?>[] parameterTypes = invocation2.getParameterTypes(); // 获得参数集合 Object[] arguments = invocation2.getArguments(); // 把参数类型的名称放入集合 String[] types = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { types[i] = ReflectUtils.getName(parameterTypes[i]); } Object[] args; // 对参数集合进行序列化 if (ProtocolUtils.isBeanGenericSerialization(generic)) { args = new Object[arguments.length]; for (int i = 0; i < arguments.length; i++) { args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD); } } else { args = PojoUtils.generalize(arguments); } // 重新把序列化的参数放入 invocation2.setMethodName(Constants.$INVOKE); invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES); invocation2.setArguments(new Object[]{methodName, types, args}); // 调用下一个调用链 Result result = invoker.invoke(invocation2); if (!result.hasException()) { Object value = result.getValue(); try { Method method = invoker.getInterface().getMethod(methodName, parameterTypes); if (ProtocolUtils.isBeanGenericSerialization(generic)) { if (value == null) { return new RpcResult(value); } else if (value instanceof JavaBeanDescriptor) { // 用javabean方式反序列化 return new RpcResult(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value)); } else { throw new RpcException( “The type of result value is " + value.getClass().getName() + " other than " + JavaBeanDescriptor.class.getName() + “, and the result is " + value); } } else { // 直接转化为pojo类型 return new RpcResult(PojoUtils.realize(value, method.getReturnType(), method.getGenericReturnType())); } } catch (NoSuchMethodException e) { throw new RpcException(e.getMessage(), e); } // 如果调用链中有异常抛出,并且是GenericException类型的异常 } else if (result.getException() instanceof GenericException) { GenericException exception = (GenericException) result.getException(); try { // 获得异常类名 String className = exception.getExceptionClass(); Class<?> clazz = ReflectUtils.forName(className); Throwable targetException = null; Throwable lastException = null; try { targetException = (Throwable) clazz.newInstance(); } catch (Throwable e) { lastException = e; for (Constructor<?> constructor : clazz.getConstructors()) { try { targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]); break; } catch (Throwable e1) { lastException = e1; } } } if (targetException != null) { try { Field field = Throwable.class.getDeclaredField(“detailMessage”); if (!field.isAccessible()) { field.setAccessible(true); } field.set(targetException, exception.getExceptionMessage()); } catch (Throwable e) { logger.warn(e.getMessage(), e); } result = new RpcResult(targetException); } else if (lastException != null) { throw lastException; } } catch (Throwable e) { throw new RpcException(“Can not deserialize exception " + exception.getExceptionClass() + “, message: " + exception.getExceptionMessage(), e); } } return result; } // 如果是泛化调用 if (invocation.getMethodName().equals(Constants.$INVOKE) && invocation.getArguments() != null && invocation.getArguments().length == 3 && ProtocolUtils.isGeneric(generic)) { Object[] args = (Object[]) invocation.getArguments()[2]; if (ProtocolUtils.isJavaGenericSerialization(generic)) { for (Object arg : args) { // 如果调用消息不是字节数组类型,则抛出异常 if (!(byte[].class == arg.getClass())) { error(generic, byte[].class.getName(), arg.getClass().getName()); } } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { for (Object arg : args) { if (!(arg instanceof JavaBeanDescriptor)) { error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName()); } } } // 设置附加值 ((RpcInvocation) invocation).setAttachment( Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY)); } return invoker.invoke(invocation);}/* * 抛出错误异常 * @param generic * @param expected * @param actual * @throws RpcException /private void error(String generic, String expected, String actual) throws RpcException { throw new RpcException( “Generic serialization [” + generic + “] only support message type " + expected + " and your message type is " + actual);}(十三)TimeoutFilter该过滤器是当服务调用超时的时候,记录告警日志。@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 获得开始时间 long start = System.currentTimeMillis(); // 调用下一个调用链 Result result = invoker.invoke(invocation); // 获得调用使用的时间 long elapsed = System.currentTimeMillis() - start; // 如果服务调用超时,则打印告警日志 if (invoker.getUrl() != null && elapsed > invoker.getUrl().getMethodParameter(invocation.getMethodName(), “timeout”, Integer.MAX_VALUE)) { if (logger.isWarnEnabled()) { logger.warn(“invoke time out. method: " + invocation.getMethodName() + " arguments: " + Arrays.toString(invocation.getArguments()) + " , url is " + invoker.getUrl() + “, invoke elapsed " + elapsed + " ms.”); } } return result;}(十四)TokenFilter该过滤器提供了token的验证功能,关于token的介绍可以查看官方文档。@Overridepublic Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { // 获得token值 String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { // 获得服务类型 Class<?> serviceType = invoker.getInterface(); // 获得附加值 Map<String, String> attachments = inv.getAttachments(); String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY); // 如果令牌不一样,则抛出异常 if (!token.equals(remoteToken)) { throw new RpcException(“Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + “() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost()); } } // 调用下一个调用链 return invoker.invoke(inv);}(十五)TpsLimitFilter该过滤器的作用是对TPS限流。/* * TPS 限制器对象 /private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // 如果限流器不允许,则抛出异常 if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) { throw new RpcException( “Failed to invoke service " + invoker.getInterface().getName() + “.” + invocation.getMethodName() + " because exceed max service tps.”); } // 调用下一个调用链 return invoker.invoke(invocation);}其中关键是TPS 限制器对象,请看下面的分析。(十六)TPSLimiterpublic interface TPSLimiter { /* * judge if the current invocation is allowed by TPS rule * 是否允许通过 * @param url url * @param invocation invocation * @return true allow the current invocation, otherwise, return false / boolean isAllowable(URL url, Invocation invocation);}该接口是tps限流器的接口,只定义了一个是否允许通过的方法。(十七)StatItem该类是统计的数据结构。class StatItem { /* * 服务名 / private String name; /* * 最后一次重置的时间 / private long lastResetTime; /* * 周期 / private long interval; /* * 剩余多少流量 / private AtomicInteger token; /* * 限制大小 / private int rate; StatItem(String name, int rate, long interval) { this.name = name; this.rate = rate; this.interval = interval; this.lastResetTime = System.currentTimeMillis(); this.token = new AtomicInteger(rate); } public boolean isAllowable() { long now = System.currentTimeMillis(); // 如果限制的时间大于最后一次时间加上周期,则重置 if (now > lastResetTime + interval) { token.set(rate); lastResetTime = now; } int value = token.get(); boolean flag = false; // 直到有流量 while (value > 0 && !flag) { flag = token.compareAndSet(value, value - 1); value = token.get(); } // 返回flag return flag; } long getLastResetTime() { return lastResetTime; } int getToken() { return token.get(); } @Override public String toString() { return new StringBuilder(32).append(“StatItem “) .append("[name=”).append(name).append(”, “) .append(“rate = “).append(rate).append(”, “) .append(“interval = “).append(interval).append(”]”) .toString(); }}可以看到该类中记录了一些访问的流量,并且设置了周期重置机制。(十八)DefaultTPSLimiter该类实现了TPSLimiter,是默认的tps限流器实现。public class DefaultTPSLimiter implements TPSLimiter { /* * 统计项集合 */ private final ConcurrentMap<String, StatItem> stats = new ConcurrentHashMap<String, StatItem>(); @Override public boolean isAllowable(URL url, Invocation invocation) { // 获得tps限制大小,默认-1,不限制 int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1); // 获得限流周期 long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY, Constants.DEFAULT_TPS_LIMIT_INTERVAL); String serviceKey = url.getServiceKey(); // 如果限制 if (rate > 0) { // 从集合中获得统计项 StatItem statItem = stats.get(serviceKey); // 如果为空,则新建 if (statItem == null) { stats.putIfAbsent(serviceKey, new StatItem(serviceKey, rate, interval)); statItem = stats.get(serviceKey); } // 返回是否允许 return statItem.isAllowable(); } else { StatItem statItem = stats.get(serviceKey); if (statItem != null) { // 移除该服务的统计项 stats.remove(serviceKey); } } return true; }}是否允许的逻辑还是调用了统计项中的isAllowable方法。本文介绍了很多的过滤器,哪些过滤器是在服务引用的,哪些服务器是服务暴露的,可以查看相应源码过滤器的实现上的注解,例如ActiveLimitFilter上:@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)可以看到group为consumer组的,也就是服务消费者侧的,则是服务引用过程中的的过滤器。 例如ExecuteLimitFilter上:@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)可以看到group为provider组的,也就是服务消费者侧的,则是服务暴露过程中的的过滤器。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了在服务引用和服务暴露中的各种filter过滤器。接下来我将开始对rpc模块的监听器进行讲解。 ...

January 8, 2019 · 15 min · jiezi

利用Dubbo SPI中的Filter来处理异常

Dubbo异常处理处理Dubbo调用异常有两种方式:封装Pojo出参,使用状态码利用对象来返回错误吗的方式有些违背Dubbo的初衷,可以包含的信息过少,不方便定位问题定义业务异常类推荐方式,异常类可以包含更多信息,语义友好。Dubbo统一异常管理Dubbo提供了,基于SPI又提供了filter功能,此处不做过多解释,有兴趣的可以直接看Dubbo官方的SPI机制介绍http://dubbo.apache.org/zh-cn…。我们可以通过Dubbo的Filter机制,来实现异常统一处理。实现方式从ExtensionLoader类中可以发现,spi加载的目录可以看到,加载了三个目录private static final String SERVICES_DIRECTORY = “META-INF/services/";private static final String DUBBO_DIRECTORY = “META-INF/dubbo/";private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + “internal/";其中DUBBO_INTERNAL_DIRECTORY目录下在源码中有一份com.alibaba.dubbo.rpc.Filter文件,定义了Dubbo内部的默认过滤器,可以采用最简单的方式来处理异常:覆盖默认filter只需要在项目里定义一个META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter,然后覆盖源码中的定义即可com.alibaba.dubbo.rpc.Filtercache=com.alibaba.dubbo.cache.filter.CacheFiltervalidation=com.alibaba.dubbo.validation.filter.ValidationFilterecho=com.alibaba.dubbo.rpc.filter.EchoFiltergeneric=com.alibaba.dubbo.rpc.filter.GenericFiltergenericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFiltertoken=com.alibaba.dubbo.rpc.filter.TokenFilteraccesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilteractivelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilterclassloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFiltercontext=com.alibaba.dubbo.rpc.filter.ContextFilterconsumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter//此处修改为你自定义的ExceptionFilter即可exception=com.xx.xx.dubbo.ExceptionFilterexecutelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilterdeprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFiltercompatible=com.alibaba.dubbo.rpc.filter.CompatibleFiltertimeout=com.alibaba.dubbo.rpc.filter.TimeoutFiltertrace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilterfuture=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFiltermonitor=com.alibaba.dubbo.monitor.support.MonitorFilterExceptionFilter@Slf4j@Activate(group = Constants.PROVIDER,order = 10000)public class ExceptionFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // directly throw if it’s checked exception if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // for the exception not found in method’s signature, print ERROR message in server’s log. log.error(“Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + exception.getClass().getName() + “: " + exception.getMessage(), exception); // directly throw if exception class and interface class are in the same jar file. String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // directly throw if it’s JDK exception String className = exception.getClass().getName(); if (className.startsWith(“java.”) || className.startsWith(“javax.”)) { return result; } if(exception instanceof AppException){ return result; } // directly throw if it’s dubbo exception if (exception instanceof RpcException) { return result; } // otherwise, wrap with RuntimeException and throw back to the client return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { log.warn(“Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + e.getClass().getName() + “: " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { log.error(“Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + “. service: " + invoker.getInterface().getName() + “, method: " + invocation.getMethodName() + “, exception: " + e.getClass().getName() + “: " + e.getMessage(), e); throw e; } }}为什么要以覆盖的方式来定义filter?因为dubbo默认的exceptinFilter为了避免类型兼容问题,会将所有自定义异常转换为string,用RuntimeException包装后返回给consumer方,导致调用者拿不到真实的异常类,所以此处直接覆盖是最为轻松简单的办法。 ...

January 7, 2019 · 2 min · jiezi

dubbo源码解析(十九)远程调用——开篇

远程调用——开篇目标:介绍之后解读远程调用模块的内容如何编排、介绍dubbo-rpc-api中的包结构设计以及最外层的的源码解析。前言最近我面临着一个选择,因为dubbo 2.7.0-release出现在了仓库里,最近一直在进行2.7.0版本的code review,那我之前说这一系列的文章都是讲述2.6.x版本的源代码,我现在要不要选择直接开始讲解2.7.0的版本的源码呢?我最后还是决定继续讲解2.6.x,因为我觉得还是有很多公司在用着2.6.x的版本,并且对于升级2.7.0的计划应该还没那么快,并且在了解2.6.x版本的原理后,再去了解2.7.0新增的特性会更加容易,也能够品位到设计者的意图。当然在结束2.6.x的重要模块讲解后,我也会对2.7.0的新特性以及实现原理做一个全面的分析,2.7.0作为dubbo社区的毕业版,更加强大,敬请期待。前面讲了很多的内容,现在开始将远程调用RPC,好像又回到我第一篇文章 《dubbo源码解析(一)Hello,Dubbo》,在这篇文章开头我讲到了什么叫做RPC,再通俗一点讲,就是我把一个项目的两部分代码分开来,分别放到两台机器上,当我部署在A服务器上的应用想要调用部署在B服务器上的应用等方法,由于不存在同一个内存空间,不能直接调用。而其实整个dubbo都在做远程调用的事情,它涉及到很多内容,比如配置、代理、集群、监控等等,那么这次讲的内容是只关心一对一的调用,dubbo-rpc远程调用模块抽象各种协议,以及动态代理,Proxy层和Protocol层rpc的核心,我将会在本系列中讲到。下面我们来看两张官方文档的图:暴露服务的时序图:你会发现其中有我们以前讲到的Transporter、Server、Registry,而这次的系列将会讲到的就是红色框框内的部分。引用服务时序图在引用服务时序图中,对应的也是红色框框的部分。当阅读完该系列后,希望能对这个调用链有所感悟。接下来看看dubbo-rpc的包结构:可以看到有很多包,很规整,其中dubbo-rpc-api是对协议、暴露、引用、代理等的抽象和实现,是rpc整个设计的核心内容。其他的包则是dubbo支持的9种协议,在官方文档也能查看介绍,并且包括一种本地调用injvm。那么我们再来看看dubbo-rpc-api中包结构:filter包:在进行服务引用时会进行一系列的过滤。其中包括了很多过滤器。listener包:看上面两张服务引用和服务暴露的时序图,发现有两个listener,其中的逻辑实现就在这个包内protocol包:这个包实现了协议的一些公共逻辑proxy包:实现了代理的逻辑。service包:其中包含了一个需要调用的方法等封装抽象。support包:包括了工具类最外层的实现。下面的篇幅设计,本文会讲解最外层的源码和service下的源码,support包下的源码我会穿插在其他用到的地方一并讲解,filter、listener、protocol、proxy以及各类协议的实现各自用一篇来讲。源码分析(一)Invokerpublic interface Invoker<T> extends Node { /** * get service interface. * 获得服务接口 * @return service interface. / Class<T> getInterface(); /* * invoke. * 调用下一个会话域 * @param invocation * @return result * @throws RpcException / Result invoke(Invocation invocation) throws RpcException;}该接口是实体域,它是dubbo的核心模型,其他模型都向它靠拢,或者转化成它,它代表了一个可执行体,可以向它发起invoke调用,这个有可能是一个本地的实现,也可能是一个远程的实现,也可能是一个集群的实现。它代表了一次调用(二)Invocationpublic interface Invocation { /* * get method name. * 获得方法名称 * @return method name. * @serial / String getMethodName(); /* * get parameter types. * 获得参数类型 * @return parameter types. * @serial / Class<?>[] getParameterTypes(); /* * get arguments. * 获得参数 * @return arguments. * @serial / Object[] getArguments(); /* * get attachments. * 获得附加值集合 * @return attachments. * @serial / Map<String, String> getAttachments(); /* * get attachment by key. * 获得附加值 * @return attachment value. * @serial / String getAttachment(String key); /* * get attachment by key with default value. * 获得附加值 * @return attachment value. * @serial / String getAttachment(String key, String defaultValue); /* * get the invoker in current context. * 获得当前上下文的invoker * @return invoker. * @transient / Invoker<?> getInvoker();}Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。(三)Exporterpublic interface Exporter<T> { /* * get invoker. * 获得对应的实体域invoker * @return invoker / Invoker<T> getInvoker(); /* * unexport. * 取消暴露 * <p> * <code> * getInvoker().destroy(); * </code> / void unexport();}该接口是暴露服务的接口,定义了两个方法分别是获得invoker和取消暴露服务。(四)ExporterListener@SPIpublic interface ExporterListener { /* * The exporter exported. * 暴露服务 * @param exporter * @throws RpcException * @see com.alibaba.dubbo.rpc.Protocol#export(Invoker) / void exported(Exporter<?> exporter) throws RpcException; /* * The exporter unexported. * 取消暴露 * @param exporter * @throws RpcException * @see com.alibaba.dubbo.rpc.Exporter#unexport() / void unexported(Exporter<?> exporter);}该接口是服务暴露的监听器接口,定义了两个方法是暴露和取消暴露,参数都是Exporter类型的。(五)Protocol@SPI(“dubbo”)public interface Protocol { /* * Get default port when user doesn’t config the port. * 获得默认的端口 * @return default port / int getDefaultPort(); /* * Export service for remote invocation: <br> * 1. Protocol should record request source address after receive a request: * RpcContext.getContext().setRemoteAddress();<br> * 2. export() must be idempotent, that is, there’s no difference between invoking once and invoking twice when * export the same URL<br> * 3. Invoker instance is passed in by the framework, protocol needs not to care <br> * 暴露服务方法, * @param <T> Service type 服务类型 * @param invoker Service invoker 服务的实体域 * @return exporter reference for exported service, useful for unexport the service later * @throws RpcException thrown when error occurs during export the service, for example: port is occupied / @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; /* * Refer a remote service: <br> * 1. When user calls invoke() method of Invoker object which’s returned from refer() call, the protocol * needs to correspondingly execute invoke() method of Invoker object <br> * 2. It’s protocol’s responsibility to implement Invoker which’s returned from refer(). Generally speaking, * protocol sends remote request in the Invoker implementation. <br> * 3. When there’s check=false set in URL, the implementation must not throw exception but try to recover when * connection fails. * 引用服务方法 * @param <T> Service type 服务类型 * @param type Service class 服务类名 * @param url URL address for the remote service * @return invoker service’s local proxy * @throws RpcException when there’s any error while connecting to the service provider / @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /* * Destroy protocol: <br> * 1. Cancel all services this protocol exports and refers <br> * 2. Release all occupied resources, for example: connection, port, etc. <br> * 3. Protocol can continue to export and refer new service even after it’s destroyed. / void destroy();}该接口是服务域接口,也是协议接口,它是一个可扩展的接口,默认实现的是dubbo协议。定义了四个方法,关键的是服务暴露和引用两个方法。(六)Filter@SPIpublic interface Filter { /* * do invoke filter. * <p> * <code> * // before filter * Result result = invoker.invoke(invocation); * // after filter * return result; * </code> * * @param invoker service * @param invocation invocation. * @return invoke result. * @throws RpcException * @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation) / Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;}该接口是invoker调用时过滤器接口,其中就只有一个invoke方法。在该方法中对调用进行过滤(七)InvokerListener@SPIpublic interface InvokerListener { /* * The invoker referred * 在服务引用的时候进行监听 * @param invoker * @throws RpcException * @see com.alibaba.dubbo.rpc.Protocol#refer(Class, com.alibaba.dubbo.common.URL) / void referred(Invoker<?> invoker) throws RpcException; /* * The invoker destroyed. * 销毁实体域 * @param invoker * @see com.alibaba.dubbo.rpc.Invoker#destroy() / void destroyed(Invoker<?> invoker);}该接口是实体域的监听器,定义了两个方法,分别是服务引用和销毁的时候执行的方法。(八)Result该接口是实体域执行invoke的结果接口,里面定义了获得结果异常以及附加值等方法。比较好理解我就不贴代码了。(九)ProxyFactory@SPI(“javassist”)public interface ProxyFactory { /* * create proxy. * 创建一个代理 * @param invoker * @return proxy / @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; /* * create proxy. * 创建一个代理 * @param invoker * @return proxy / @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException; /* * create invoker. * 创建一个实体域 * @param <T> * @param proxy * @param type * @param url * @return invoker / @Adaptive({Constants.PROXY_KEY}) <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;}该接口是代理工厂接口,它也是个可扩展接口,默认实现javassist,dubbo提供两种动态代理方法分别是javassist/jdk,该接口定义了三个方法,前两个方法是通过invoker创建代理,最后一个是通过代理来获得invoker。(十)RpcContext该类就是远程调用的上下文,贯穿着整个调用,例如A调用B,然后B调用C。在服务B上,RpcContext在B之前将调用信息从A保存到B。开始调用C,并在B调用C后将调用信息从B保存到C。RpcContext保存了调用信息。public class RpcContext { /* * use internal thread local to improve performance * 本地上下文 / private static final InternalThreadLocal<RpcContext> LOCAL = new InternalThreadLocal<RpcContext>() { @Override protected RpcContext initialValue() { return new RpcContext(); } }; /* * 服务上下文 / private static final InternalThreadLocal<RpcContext> SERVER_LOCAL = new InternalThreadLocal<RpcContext>() { @Override protected RpcContext initialValue() { return new RpcContext(); } }; /* * 附加值集合 / private final Map<String, String> attachments = new HashMap<String, String>(); /* * 上下文值 / private final Map<String, Object> values = new HashMap<String, Object>(); /* * 线程结果 / private Future<?> future; /* * url集合 / private List<URL> urls; /* * 当前的url / private URL url; /* * 方法名称 / private String methodName; /* * 参数类型集合 / private Class<?>[] parameterTypes; /* * 参数集合 / private Object[] arguments; /* * 本地地址 / private InetSocketAddress localAddress; /* * 远程地址 / private InetSocketAddress remoteAddress; /* * 实体域集合 / @Deprecated private List<Invoker<?>> invokers; /* * 实体域 / @Deprecated private Invoker<?> invoker; /* * 会话域 / @Deprecated private Invocation invocation; // now we don’t use the ‘values’ map to hold these objects // we want these objects to be as generic as possible /* * 请求 / private Object request; /* * 响应 / private Object response;该类中最重要的是它的一些属性,因为该上下文就是用来保存信息的。方法我就不介绍了,因为比较简单。(十一)RpcException/* * 不知道异常 /public static final int UNKNOWN_EXCEPTION = 0;/* * 网络异常 /public static final int NETWORK_EXCEPTION = 1;/* * 超时异常 /public static final int TIMEOUT_EXCEPTION = 2;/* * 基础异常 /public static final int BIZ_EXCEPTION = 3;/* * 禁止访问异常 /public static final int FORBIDDEN_EXCEPTION = 4;/* * 序列化异常 /public static final int SERIALIZATION_EXCEPTION = 5;该类是rpc调用抛出的异常类,其中封装了五种通用的错误码。(十二)RpcInvocation/* * 方法名称 /private String methodName;/* * 参数类型集合 /private Class<?>[] parameterTypes;/* * 参数集合 /private Object[] arguments;/* * 附加值 /private Map<String, String> attachments;/* * 实体域 /private transient Invoker<?> invoker;该类实现了Invocation接口,是rpc的会话域,其中的方法比较简单,主要是封装了上述的属性。(十三)RpcResult/* * 结果 /private Object result;/* * 异常 /private Throwable exception;/* * 附加值 /private Map<String, String> attachments = new HashMap<String, String>();该类实现了Result接口,是rpc的结果实现类,其中关键是封装了以上三个属性。(十四)RpcStatus该类是rpc的一些状态监控,其中封装了许多的计数器,用来记录rpc调用的状态。1.属性/* * uri对应的状态集合,key为uri,value为RpcStatus对象 /private static final ConcurrentMap<String, RpcStatus> SERVICE_STATISTICS = new ConcurrentHashMap<String, RpcStatus>();/* * method对应的状态集合,key是uri,第二个key是方法名methodName /private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS = new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>();/* * 已经没用了 /private final ConcurrentMap<String, Object> values = new ConcurrentHashMap<String, Object>();/* * 活跃状态 /private final AtomicInteger active = new AtomicInteger();/* * 总的数量 /private final AtomicLong total = new AtomicLong();/* * 失败的个数 /private final AtomicInteger failed = new AtomicInteger();/* * 总计过期个数 /private final AtomicLong totalElapsed = new AtomicLong();/* * 失败的累计值 /private final AtomicLong failedElapsed = new AtomicLong();/* * 最大火气的累计值 /private final AtomicLong maxElapsed = new AtomicLong();/* * 最大失败累计值 /private final AtomicLong failedMaxElapsed = new AtomicLong();/* * 成功最大累计值 /private final AtomicLong succeededMaxElapsed = new AtomicLong();/* * Semaphore used to control concurrency limit set by executes * 信号量用来控制execution设置的并发限制 /private volatile Semaphore executesLimit;/* * 用来控制execution设置的许可证 /private volatile int executesPermits;以上是该类的属性,可以看到保存了很多的计数器,分别用来记录了失败调用成功调用等累计数。2.beginCount/* * 开始计数 * @param url /public static void beginCount(URL url, String methodName) { // 对该url对应对活跃计数器加一 beginCount(getStatus(url)); // 对该方法对活跃计数器加一 beginCount(getStatus(url, methodName));}/* * 以原子方式加1 * @param status /private static void beginCount(RpcStatus status) { status.active.incrementAndGet();}该方法是增加计数。3.endCountpublic static void endCount(URL url, String methodName, long elapsed, boolean succeeded) { // url对应的状态中计数器减一 endCount(getStatus(url), elapsed, succeeded); // 方法对应的状态中计数器减一 endCount(getStatus(url, methodName), elapsed, succeeded);}private static void endCount(RpcStatus status, long elapsed, boolean succeeded) { // 活跃计数器减一 status.active.decrementAndGet(); // 总计数器加1 status.total.incrementAndGet(); // 过期的计数器加上过期个数 status.totalElapsed.addAndGet(elapsed); // 如果最大的过期数小于elapsed,则设置最大过期数 if (status.maxElapsed.get() < elapsed) { status.maxElapsed.set(elapsed); } // 如果rpc调用成功 if (succeeded) { // 如果成功的最大值小于elapsed,则设置成功最大值 if (status.succeededMaxElapsed.get() < elapsed) { status.succeededMaxElapsed.set(elapsed); } } else { // 失败计数器加一 status.failed.incrementAndGet(); // 失败的过期数加上elapsed status.failedElapsed.addAndGet(elapsed); // 失败最大值小于elapsed,则设置失败最大值 if (status.failedMaxElapsed.get() < elapsed) { status.failedMaxElapsed.set(elapsed); } }}该方法是计数器减少。(十五)StaticContext该类是系统上下文,仅供内部使用。/* * 系统名称 /private static final String SYSTEMNAME = “system”;/* * 系统上下文集合,仅供内部使用 /private static final ConcurrentMap<String, StaticContext> context_map = new ConcurrentHashMap<String, StaticContext>();/* * 系统上下文名称 /private String name;上面是该类的属性,它还记录了所有的系统上下文集合。(十六)EchoServicepublic interface EchoService { /* * echo test. * 回声测试 * @param message message. * @return message. / Object $echo(Object message);}该接口是回声服务接口,定义了一个一个回声测试的方法,回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控,所有服务自动实现该接口,只需将任意服务强制转化为EchoService,就可以用了。(十七)GenericException该方法是通用的异常类。/* * 异常类名 /private String exceptionClass;/* * 异常信息 /private String exceptionMessage;比较简单,就封装了两个属性。(十八)GenericServicepublic interface GenericService { /* * Generic invocation * 通用的会话域 * @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is * required, e.g. findPerson(java.lang.String) * @param parameterTypes Parameter types * @param args Arguments * @return invocation return value * @throws Throwable potential exception thrown from the invocation */ Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;}该接口是通用的服务接口,同样定义了一个类似invoke的方法后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了远程调用的开篇,介绍之后解读远程调用模块的内容如何编排、介绍dubbo-rpc-api中的包结构设计以及最外层的的源码解析,其中的逻辑不负责,要关注的是其中的一些概念和dubbo如何去做暴露服务和引用服务,其中很多的接口定义需要弄清楚。接下来我将开始对rpc模块的过滤器进行讲解。 ...

January 7, 2019 · 7 min · jiezi

【Dubbo源码阅读系列】之 Dubbo XML 配置加载

今天我们来谈谈 Dubbo XML 配置相关内容。关于这部分内容我打算分为以下几个部分进行介绍:Dubbo XMLSpring 自定义 XML 标签解析Dubbo 自定义 XML 标签解析DubboBeanDefinitionParser.parse()EndDubbo XML在本小节开始前我们先来看下 Dubbo XML 配置文件示例:dubbo-demo-provider.xml<?xml version=“1.0” encoding=“UTF-8”?><!–<beans xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo=“http://dubbo.apache.org/schema/dubbo" xmlns=“http://www.springframework.org/schema/beans" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!– provider’s application name, used for tracing dependency relationship –> <dubbo:application name=“demo-provider”/> <!– use multicast registry center to export service –> <!–<dubbo:registry address=“multicast://224.5.6.7:1234”/>–> <dubbo:registry address=“zookeeper://10.14.22.68:2181”/> <!– use dubbo protocol to export service on port 20880 –> <dubbo:protocol name=“dubbo” port=“20880”/> <!– service implementation, as same as regular local bean –> <bean id=“demoService” class=“org.apache.dubbo.demo.provider.DemoServiceImpl”/> <!– declare the service interface to be exported –> <dubbo:service interface=“org.apache.dubbo.demo.DemoService” ref=“demoService”/></beans>在这段配置文件中有一些以 dubbo 开头的 xml 标签,直觉告诉我们这种标签和 dubbo 密切相关。那么这些标签的用途是什么?又是如何被识别的呢?我们结合 Spring 自定义 xml 标签实现相关内容来聊聊 Dubbo 是如何定义并加载这些自定义标签的。Spring 自定义 XML 标签解析Dubbo 中的自定义 XML 标签实际上是依赖于 Spring 解析自定义标签的功能实现的。网上关于 Spring 解析自定义 XML 标签的文章也比较多,这里我们仅介绍下实现相关功能需要的文件,给大家一个直观的印象,不去深入的对 Spring 自定义标签实现作详细分析。定义 xsd 文件XSD(XML Schemas Definition) 即 XML 结构定义。我们通过 XSD 文件不仅可以定义新的元素和属性,同时也使用它对我们的 XML 文件规范进行约束。在 Dubbo 项目中可以找类似实现:dubbo.xsdspring.schemas该配置文件约定了自定义命名空间和 xsd 文件之间的映射关系,用于 spring 容器感知我们自定义的 xsd 文件位置。http://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsdhttp://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsdspring.handlers该配置文件约定了自定义命名空间和 NamespaceHandler 类之间的映射关系。 NamespaceHandler 类用于注册自定义标签解析器。http://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandlerhttp://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler命名空间处理器命名空间处理器主要用来注册 BeanDefinitionParser 解析器。对应上面 spring.handlers 文件中的 DubboNamespaceHandlerpublic class DubboNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser(“application”, new DubboBeanDefinitionParser(ApplicationConfig.class, true)); // 省略… registerBeanDefinitionParser(“annotation”, new AnnotationBeanDefinitionParser()); }}BeanDefinitionParser 解析器实现 BeanDefinitionParser 接口中的 parse 方法,用于自定义标签的解析。Dubbo 中对应 DubboBeanDefinitionParser 类。Dubbo 解析自定义 XML 标签终于进入到本文的重头戏环节了。在介绍 Dubbo 自定义 XML 标签解析前,先放一张图帮助大家理解以下 Spring 是如何从 XML 文件中解析并加载 Bean 的。上图言尽于 handler.parse() 方法,如果你仔细看了上文,对 parse() 应该是有印象的。 没错,在前一小结的第五点我们介绍了 DubboBeanDefinitionParser 类。该类有个方法就叫 parse()。那么这个 parse() 方法有什么用? Spring 是如何感知到我就要调用 DubboBeanDefinitionParser 类中的 parse() 方法的呢?我们带着这两个问题接着往下看。BeanDefinitionParserDelegate上面图的流程比较长,我们先着重看下 BeanDefinitionParserDelegate 类中的几个关键方法。BeanDefinitionParserDelegate.javapublic BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 获取当前 element 的 namespaceURI // 比如 dubbo.xsd 中的为 http://dubbo.apache.org/schema/dubbo String namespaceUri = this.getNamespaceURI(ele); // 根据 URI 获取对应的 NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { this.error(“Unable to locate Spring NamespaceHandler for XML schema namespace [” + namespaceUri + “]”, ele); return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }}这个方法干了三件事获取 element 元素的 namespaceURI,并据此获取对应的 NamespaceHandler 对象。Dubbo 自定义标签(比如 Dubbo:provider) namespaceUri 的值为 http://dubbo.apache.org/schema/dubbo;根据 step1 获取到的 namespaceUri ,获取对应的 NamespaceHandler 对象。这里会调用 DefaultNamespaceHandlerResolver 类的 resolve() 方法,我们下面会分析;调用 handler 的 parse 方法,我们自定以的 handler 会继承 NamespaceHandlerSupport 类,所以这里调用的其实是 NamespaceHandlerSupport 类的 parse() 方法,后文分析;一图胜千言 在详细分析 step2 和 step3 中涉及的 resolver() 和 parse() 方法前,先放一张时序图让大家有个基本概念:DefaultNamespaceHandlerResolver.javapublic NamespaceHandler resolve(String namespaceUri) { Map<String, Object> handlerMappings = this.getHandlerMappings(); // 以 namespaceUri 为 Key 获取对应的 handlerOrClassName Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler)handlerOrClassName; } else { // 如果不为空且不为 NamespaceHandler 的实例,转换为 String 类型 // DubboNamespaceHandler 执行的便是这段逻辑 String className = (String)handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); // handlerClass 是否为 NamespaceHandler 的实现类,若不是则抛出异常 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException(“Class [” + className + “] for namespace [” + namespaceUri + “] does not implement the [” + NamespaceHandler.class.getName() + “] interface”); } else { // 初始化 handlerClass NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass); // 执行 handlerClass类的 init() 方法 namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } catch (ClassNotFoundException var7) { throw new FatalBeanException(“NamespaceHandler class [” + className + “] for namespace [” + namespaceUri + “] not found”, var7); } catch (LinkageError var8) { throw new FatalBeanException(“Invalid NamespaceHandler class [” + className + “] for namespace [” + namespaceUri + “]: problem with handler class file or dependent class”, var8); } }}resolve() 方法用途是根据方法参数中的 namespaceUri 获取对应的 NamespaceHandler 对象。这里会先尝试以 namespaceUri 为 key 去 handlerMappings 集合中取对象。如果 handlerOrClassName 不为 null 且不为 NamespaceHandler 的实例。那么尝试将 handlerOrClassName 作为 className 并调用 BeanUtils.instantiateClass() 方法初始化一个NamespaceHandler 实例。初始化后,调用其 init() 方法。这个 init() 方法比较重要,我们接着往下看。DubboNamespaceHandlerpublic void init() { registerBeanDefinitionParser(“application”, new DubboBeanDefinitionParser(ApplicationConfig.class, true)); registerBeanDefinitionParser(“module”, new DubboBeanDefinitionParser(ModuleConfig.class, true)); registerBeanDefinitionParser(“registry”, new DubboBeanDefinitionParser(RegistryConfig.class, true)); registerBeanDefinitionParser(“monitor”, new DubboBeanDefinitionParser(MonitorConfig.class, true)); registerBeanDefinitionParser(“provider”, new DubboBeanDefinitionParser(ProviderConfig.class, true)); registerBeanDefinitionParser(“consumer”, new DubboBeanDefinitionParser(ConsumerConfig.class, true)); registerBeanDefinitionParser(“protocol”, new DubboBeanDefinitionParser(ProtocolConfig.class, true)); registerBeanDefinitionParser(“service”, new DubboBeanDefinitionParser(ServiceBean.class, true)); registerBeanDefinitionParser(“reference”, new DubboBeanDefinitionParser(ReferenceBean.class, false)); registerBeanDefinitionParser(“annotation”, new AnnotationBeanDefinitionParser());}NamespaceHandlerSupportprivate final Map<String, BeanDefinitionParser> parsers = new HashMap();protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser);}DubboNamespaceHandler 类中的 init() 方法干的事情特别简单,就是新建 DubboBeanDefinitionParser 对象并将其放入 NamespaceHandlerSupport 类的 parsers 集合中。我们再回顾一下 parseCustomElement() 方法。BeanDefinitionParserDelegate.javapublic BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 省略… return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); // 省略…}这里会调用 NamespaceHandlerSupport 类的 parse() 方法。我们继续跟踪一下。public BeanDefinition parse(Element element, ParserContext parserContext) { return this.findParserForElement(element, parserContext).parse(element, parserContext);}private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal(“Cannot locate BeanDefinitionParser for element [” + localName + “]”, element); } return parser;}看到这里大家有没有一丝豁然开朗的感觉?之前的 resolve() 方法实际上就是根据当前 element 的 namespaceURI 获取对应的 NamespaceHandler 对象(对于 Dubbo 来说是 DubboNamespaceHandler),然后调用 DubboNamespaceHandler 中的 init() 方法新建 DubboBeanDefinitionParser 对象并注册到 NamespaceHandlerSupport 类的 parsers 集合中。然后 parser 方法会根据当前 element 对象从 parsers 集合中获取合适的 BeanDefinitionParser 对象。对于 Dubbo 元素来说,实际上最后执行的是 DubboBeanDefinitionParser 的 parse() 方法。DubboBeanDefinitionParser.parse()最后我们再来看看 Dubbo 解析 XML 文件的详细实现吧。如果对具体实现没有兴趣可直接直接跳过。private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false); String id = element.getAttribute(“id”); // DubboBeanDefinitionParser 构造方法中有对 required 值进行初始化; // DubboNamespaceHandler 类中的 init 方法会创建并注册 DubboBeanDefinitionParser 类 if ((id == null || id.length() == 0) && required) { String generatedBeanName = element.getAttribute(“name”); if (generatedBeanName == null || generatedBeanName.length() == 0) { if (ProtocolConfig.class.equals(beanClass)) { generatedBeanName = “dubbo”; } else { // name 属性为空且不为 ProtocolConfig 类型,取 interface 值 generatedBeanName = element.getAttribute(“interface”); } } if (generatedBeanName == null || generatedBeanName.length() == 0) { // 获取 beanClass 的全限定类名 generatedBeanName = beanClass.getName(); } id = generatedBeanName; int counter = 2; while (parserContext.getRegistry().containsBeanDefinition(id)) { id = generatedBeanName + (counter++); } } if (id != null && id.length() > 0) { if (parserContext.getRegistry().containsBeanDefinition(id)) { throw new IllegalStateException(“Duplicate spring bean id " + id); } // 注册 beanDefinition parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); // 为 beanDefinition 添加 id 属性 beanDefinition.getPropertyValues().addPropertyValue(“id”, id); } // 如果当前 beanClass 类型为 ProtocolConfig // 遍历已经注册过的 bean 对象,如果 bean 对象含有 protocol 属性 // protocol 属性值为 ProtocolConfig 实例且 name 和当前 id 值一致,为当前 beanClass 对象添加 protocl 属性 if (ProtocolConfig.class.equals(beanClass)) { for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); PropertyValue property = definition.getPropertyValues().getPropertyValue(“protocol”); if (property != null) { Object value = property.getValue(); if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) { definition.getPropertyValues().addPropertyValue(“protocol”, new RuntimeBeanReference(id)); } } } } else if (ServiceBean.class.equals(beanClass)) { // 如果当前元素包含 class 属性,调用 ReflectUtils.forName() 方法加载类对象 // 调用 parseProperties 解析其他属性设置到 classDefinition 对象中 // 最后设置 beanDefinition 的 ref 属性为 BeanDefinitionHolder 包装类 String className = element.getAttribute(“class”); if (className != null && className.length() > 0) { RootBeanDefinition classDefinition = new RootBeanDefinition(); classDefinition.setBeanClass(ReflectUtils.forName(className)); classDefinition.setLazyInit(false); parseProperties(element.getChildNodes(), classDefinition); beanDefinition.getPropertyValues().addPropertyValue(“ref”, new BeanDefinitionHolder(classDefinition, id + “Impl”)); } } else if (ProviderConfig.class.equals(beanClass)) { parseNested(element, parserContext, ServiceBean.class, true, “service”, “provider”, id, beanDefinition); } else if (ConsumerConfig.class.equals(beanClass)) { parseNested(element, parserContext, ReferenceBean.class, false, “reference”, “consumer”, id, beanDefinition); } Set<String> props = new HashSet<String>(); ManagedMap parameters = null; for (Method setter : beanClass.getMethods()) { String name = setter.getName(); if (name.length() > 3 && name.startsWith(“set”) && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1) { Class<?> type = setter.getParameterTypes()[0]; String propertyName = name.substring(3, 4).toLowerCase() + name.substring(4); String property = StringUtils.camelToSplitName(propertyName, “-”); props.add(property); Method getter = null; try { getter = beanClass.getMethod(“get” + name.substring(3), new Class<?>[0]); } catch (NoSuchMethodException e) { try { getter = beanClass.getMethod(“is” + name.substring(3), new Class<?>[0]); } catch (NoSuchMethodException e2) { } } if (getter == null || !Modifier.isPublic(getter.getModifiers()) || !type.equals(getter.getReturnType())) { continue; } if (“parameters”.equals(property)) { parameters = parseParameters(element.getChildNodes(), beanDefinition); } else if (“methods”.equals(property)) { parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); } else if (“arguments”.equals(property)) { parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); } else { String value = element.getAttribute(property); if (value != null) { value = value.trim(); if (value.length() > 0) { // 如果属性为 registry,且 registry 属性的值为"N/A”,标识不会注册到任何注册中心 // 新建 RegistryConfig 并将其设置为 beanDefinition 的 registry 属性 if (“registry”.equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(RegistryConfig.NO_AVAILABLE); beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig); } else if (“registry”.equals(property) && value.indexOf(’,’) != -1) { // 多注册中心解析 parseMultiRef(“registries”, value, beanDefinition, parserContext); } else if (“provider”.equals(property) && value.indexOf(’,’) != -1) { parseMultiRef(“providers”, value, beanDefinition, parserContext); } else if (“protocol”.equals(property) && value.indexOf(’,’) != -1) { // 多协议 parseMultiRef(“protocols”, value, beanDefinition, parserContext); } else { Object reference; if (isPrimitive(type)) { // type 为方法参数,type 类型是否为基本类型 if (“async”.equals(property) && “false”.equals(value) || “timeout”.equals(property) && “0”.equals(value) || “delay”.equals(property) && “0”.equals(value) || “version”.equals(property) && “0.0.0”.equals(value) || “stat”.equals(property) && “-1”.equals(value) || “reliable”.equals(property) && “false”.equals(value)) { // 新老版本 xsd 兼容性处理 // backward compatibility for the default value in old version’s xsd value = null; } reference = value; } else if (“protocol”.equals(property) && ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value) && (!parserContext.getRegistry().containsBeanDefinition(value) || !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) { // 如果 protocol 属性值有对应的扩展实现,而且没有被注册到 spring 注册表中 // 或者 spring 注册表中对应的 bean 的类型不为 ProtocolConfig.class if (“dubbo:provider”.equals(element.getTagName())) { logger.warn(“Recommended replace <dubbo:provider protocol="” + value + “" … /> to <dubbo:protocol name="” + value + “" … />”); } // backward compatibility ProtocolConfig protocol = new ProtocolConfig(); protocol.setName(value); reference = protocol; } else if (“onreturn”.equals(property)) { int index = value.lastIndexOf(”.”); String returnRef = value.substring(0, index); String returnMethod = value.substring(index + 1); reference = new RuntimeBeanReference(returnRef); beanDefinition.getPropertyValues().addPropertyValue(“onreturnMethod”, returnMethod); } else if (“onthrow”.equals(property)) { int index = value.lastIndexOf(”.”); String throwRef = value.substring(0, index); String throwMethod = value.substring(index + 1); reference = new RuntimeBeanReference(throwRef); beanDefinition.getPropertyValues().addPropertyValue(“onthrowMethod”, throwMethod); } else if (“oninvoke”.equals(property)) { int index = value.lastIndexOf("."); String invokeRef = value.substring(0, index); String invokeRefMethod = value.substring(index + 1); reference = new RuntimeBeanReference(invokeRef); beanDefinition.getPropertyValues().addPropertyValue(“oninvokeMethod”, invokeRefMethod); } else { // 如果 ref 属性值已经被注册到 spring 注册表中 if (“ref”.equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) { BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value); // 非单例抛出异常 if (!refBean.isSingleton()) { throw new IllegalStateException(“The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id="” + value + “" scope="singleton" …>”); } } reference = new RuntimeBeanReference(value); } beanDefinition.getPropertyValues().addPropertyValue(propertyName, reference); } } } } } } NamedNodeMap attributes = element.getAttributes(); int len = attributes.getLength(); for (int i = 0; i < len; i++) { Node node = attributes.item(i); String name = node.getLocalName(); if (!props.contains(name)) { if (parameters == null) { parameters = new ManagedMap(); } String value = node.getNodeValue(); parameters.put(name, new TypedStringValue(value, String.class)); } } if (parameters != null) { beanDefinition.getPropertyValues().addPropertyValue(“parameters”, parameters); } return beanDefinition; }上面这一大段关于配置的解析的代码需要大家自己结合实际的代码进行调试才能更好的理解。我在理解 Dubbo XML 解析的时候,也是耐着性子一遍一遍的来。 关于 ProtocolConfig 和 protocol 加载先后顺序的问题最后再集合一个小例子总结下吧: dubbo-demo-provider.xml <dubbo:protocol name=“dubbo” port=“20880”/>当我们先解析了 ProtocolConfig 元素时,我们会遍历所有已经注册 spring 注册表中 bean。如果 bean 对象存在 protocol 属性且与 name 和当前 ProtolConfig id 匹配,则会新建 RuntimeBeanReference 对象覆盖 protocol 属性。对于上面这行配置,最后会新建一个拥有 name 和 port 的 beanDefinition 对象。先解析了 protocol 元素,ProtocolConfig 未被解析。此时我们在 spring 注册表中找不到对应的 ProtocolConfig bean。此时我们将需要新建一个 ProtocolConfig 并将其 name 属性设置为当前属性值。最后将其设置为 beanDefinition 对象的 protocol 属性。后面加载到了 ProtocolConfig 元素时,会替换 protocol 的值。EndDubbo 对于自定义 XML 标签的定义和解析实际上借助了 Spring 框架对自定义 XML 标签的支持。本篇水文虽然又臭又长,但是对于理解 Dubbo 的初始化过程还是很重要的。后面我们会介绍关于 Dubbo 服务暴露相关内容。本BLOG上原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,否则属于侵权行为。https://juejin.im/post/5c1753… ...

January 2, 2019 · 8 min · jiezi

dubbo源码解析(十八)远程通信——Zookeeper

远程通讯——Zookeeper目标:介绍基于zookeeper的来实现的远程通信、介绍dubbo-remoting-zookeeper内的源码解析。前言对于zookeeper我相信肯定不陌生,在之前的文章里面也有讲到zookeeper来作为注册中心。在这里,基于zookeeper来实现远程通讯,duubo封装了zookeeper client,来和zookeeper server通讯。下面是类图:源码分析(一)ZookeeperClientpublic interface ZookeeperClient { /** * 创建client * @param path * @param ephemeral / void create(String path, boolean ephemeral); /* * 删除client * @param path / void delete(String path); /* * 获得子节点集合 * @param path * @return / List<String> getChildren(String path); /* * 向zookeeper的该节点发起订阅,获得该节点所有 * @param path * @param listener * @return / List<String> addChildListener(String path, ChildListener listener); /* * 移除该节点的子节点监听器 * @param path * @param listener / void removeChildListener(String path, ChildListener listener); /* * 新增状态监听器 * @param listener / void addStateListener(StateListener listener); /* * 移除状态监听 * @param listener / void removeStateListener(StateListener listener); /* * 判断是否连接 * @return / boolean isConnected(); /* * 关闭客户端 / void close(); /* * 获得url * @return / URL getUrl();}该接口是基于zookeeper的客户端接口,其中封装了客户端的一些方法。(二)AbstractZookeeperClient该类实现了ZookeeperClient接口,是客户端的抽象类,它实现了一些公共逻辑,把具体的doClose、createPersistent等方法抽象出来,留给子类来实现。1.属性/* * url对象 /private final URL url;/* * 状态监听器集合 /private final Set<StateListener> stateListeners = new CopyOnWriteArraySet<StateListener>();/* * 客户端监听器集合 /private final ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners = new ConcurrentHashMap<String, ConcurrentMap<ChildListener, TargetChildListener>>();/* * 是否关闭 /private volatile boolean closed = false;2.create@Overridepublic void create(String path, boolean ephemeral) { // 如果不是临时节点 if (!ephemeral) { // 判断该客户端是否存在 if (checkExists(path)) { return; } } // 获得/的位置 int i = path.lastIndexOf(’/’); if (i > 0) { // 创建客户端 create(path.substring(0, i), false); } // 如果是临时节点 if (ephemeral) { // 创建临时节点 createEphemeral(path); } else { // 递归创建节点 createPersistent(path); }}该方法是创建客户端的方法,其中createEphemeral和createPersistent方法都被抽象出来。具体看下面的类的介绍。3.addStateListener@Overridepublic void addStateListener(StateListener listener) { // 状态监听器加入集合 stateListeners.add(listener);}该方法就是增加状态监听器。4.close@Overridepublic void close() { if (closed) { return; } closed = true; try { // 关闭 doClose(); } catch (Throwable t) { logger.warn(t.getMessage(), t); }}该方法是关闭客户端,其中doClose方法也被抽象出。/* * 关闭客户端 /protected abstract void doClose();/* * 递归创建节点 * @param path /protected abstract void createPersistent(String path);/* * 创建临时节点 * @param path /protected abstract void createEphemeral(String path);/* * 检测该节点是否存在 * @param path * @return /protected abstract boolean checkExists(String path);/* * 创建子节点监听器 * @param path * @param listener * @return /protected abstract TargetChildListener createTargetChildListener(String path, ChildListener listener);/* * 为子节点添加监听器 * @param path * @param listener * @return /protected abstract List<String> addTargetChildListener(String path, TargetChildListener listener);/* * 移除子节点监听器 * @param path * @param listener /protected abstract void removeTargetChildListener(String path, TargetChildListener listener);上述的方法都是被抽象的,又它的两个子类来实现。(三)ZkclientZookeeperClient该类继承了AbstractZookeeperClient,是zk客户端的实现类。1.属性/* * zk客户端包装类 /private final ZkClientWrapper client;/* * 连接状态 /private volatile KeeperState state = KeeperState.SyncConnected;该类有两个属性,其中client就是核心所在,几乎所有方法都调用了client的方法。2.构造函数public ZkclientZookeeperClient(URL url) { super(url); // 新建一个zkclient包装类 client = new ZkClientWrapper(url.getBackupAddress(), 30000); // 增加状态监听 client.addListener(new IZkStateListener() { /* * 如果状态改变 * @param state * @throws Exception / @Override public void handleStateChanged(KeeperState state) throws Exception { ZkclientZookeeperClient.this.state = state; // 如果状态变为了断开连接 if (state == KeeperState.Disconnected) { // 则修改状态 stateChanged(StateListener.DISCONNECTED); } else if (state == KeeperState.SyncConnected) { stateChanged(StateListener.CONNECTED); } } @Override public void handleNewSession() throws Exception { // 状态变为重连 stateChanged(StateListener.RECONNECTED); } }); // 启动客户端 client.start();}该方法是构造方法,同时在里面也做了创建客户端和启动客户端的操作。其他方法都是实现了父类抽象的方法,并且调用的是client方法,为举个例子:@Overridepublic void createPersistent(String path) { try { // 递归创建节点 client.createPersistent(path); } catch (ZkNodeExistsException e) { }}该方法是递归场景节点,调用的就是client.createPersistent(path)。(四)CuratorZookeeperClient该类是Curator框架提供的一套高级API,简化了ZooKeeper的操作,从而对客户端的实现。1.属性/* * 框架式客户端 /private final CuratorFramework client;2.构造方法public CuratorZookeeperClient(URL url) { super(url); try { // 工厂创建者 CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() .connectString(url.getBackupAddress()) .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(5000); String authority = url.getAuthority(); if (authority != null && authority.length() > 0) { builder = builder.authorization(“digest”, authority.getBytes()); } // 创建客户端 client = builder.build(); // 添加监听器 client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState state) { // 如果为状态为lost,则改变为未连接 if (state == ConnectionState.LOST) { CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); } else if (state == ConnectionState.CONNECTED) { // 改变状态为连接 CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); } else if (state == ConnectionState.RECONNECTED) { // 改变状态为未连接 CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); } } }); // 启动客户端 client.start(); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); }}该方法是构造方法,同样里面也包含了客户端创建和启动的逻辑。其他的方法也一样是实现了父类的抽象方法,举个列子:@Overridepublic void createPersistent(String path) { try { client.create().forPath(path); } catch (NodeExistsException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); }}(五)ZookeeperTransporter@SPI(“curator”)public interface ZookeeperTransporter { /* * 连接服务器 * @param url * @return / @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) ZookeeperClient connect(URL url);}该方法是zookeeper的信息交换接口。同样也是一个可扩展接口,默认实现CuratorZookeeperTransporter类。(六)ZkclientZookeeperTransporterpublic class ZkclientZookeeperTransporter implements ZookeeperTransporter { @Override public ZookeeperClient connect(URL url) { // 新建ZkclientZookeeperClient实例 return new ZkclientZookeeperClient(url); }}该类实现了ZookeeperTransporter,其中就是创建了ZkclientZookeeperClient实例。(七)CuratorZookeeperTransporterpublic class CuratorZookeeperTransporter implements ZookeeperTransporter { @Override public ZookeeperClient connect(URL url) { // 创建CuratorZookeeperClient实例 return new CuratorZookeeperClient(url); }}该接口实现了ZookeeperTransporter,是ZookeeperTransporter默认的实现类,同样也是创建了;对应的CuratorZookeeperClient实例。(八)ZkClientWrapper该类是zk客户端的包装类。1.属性/* * 超时事件 /private long timeout;/* * zk客户端 /private ZkClient client;/* * 客户端状态 /private volatile KeeperState state;/* * 客户端线程 /private ListenableFutureTask<ZkClient> listenableFutureTask;/* * 是否开始 /private volatile boolean started = false;2.构造方法public ZkClientWrapper(final String serverAddr, long timeout) { this.timeout = timeout; listenableFutureTask = ListenableFutureTask.create(new Callable<ZkClient>() { @Override public ZkClient call() throws Exception { // 创建zk客户端 return new ZkClient(serverAddr, Integer.MAX_VALUE); } });}设置了超时时间和客户端线程。3.startpublic void start() { // 如果客户端没有开启 if (!started) { // 创建连接线程 Thread connectThread = new Thread(listenableFutureTask); connectThread.setName(“DubboZkclientConnector”); connectThread.setDaemon(true); // 开启线程 connectThread.start(); try { // 获得zk客户端 client = listenableFutureTask.get(timeout, TimeUnit.MILLISECONDS); } catch (Throwable t) { logger.error(“Timeout! zookeeper server can not be connected in : " + timeout + “ms!”, t); } started = true; } else { logger.warn(“Zkclient has already been started!”); }}该方法是客户端启动方法。4.addListenerpublic void addListener(final IZkStateListener listener) { // 增加监听器 listenableFutureTask.addListener(new Runnable() { @Override public void run() { try { client = listenableFutureTask.get(); // 增加监听器 client.subscribeStateChanges(listener); } catch (InterruptedException e) { logger.warn(Thread.currentThread().getName() + " was interrupted unexpectedly, which may cause unpredictable exception!”); } catch (ExecutionException e) { logger.error(“Got an exception when trying to create zkclient instance, can not connect to zookeeper server, please check!”, e); } } });}该方法是为客户端添加监听器。其他方法都是对于 客户端是否还连接的检测,可自行查看代码。(九)ChildListenerpublic interface ChildListener { /* * 子节点修改 * @param path * @param children / void childChanged(String path, List<String> children);}该接口是子节点的监听器,当子节点变化的时候会用到。(十)StateListenerpublic interface StateListener { int DISCONNECTED = 0; int CONNECTED = 1; int RECONNECTED = 2; /* * 状态修改 * @param connected */ void stateChanged(int connected);}该接口是状态监听器,其中定义了一个状态更改的方法以及三种状态。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了基于zookeeper的来实现的远程通信、介绍dubbo-remoting-zookeeper内的源码解析,关键需要对zookeeper有所了解。该篇之后,远程通讯的源码解析就先到这里了,其实大家会发现,如果能够对讲解api系列的文章了解透了,那么后面的文章九很简单,就好像轨道铺好,可以直接顺着轨道往后,根本没有阻碍。接下来我将开始对rpc模块进行讲解。 ...

December 29, 2018 · 4 min · jiezi

dubbo源码解析(十七)远程通信——Netty4

远程通讯——Netty4目标:介绍基于netty4的来实现的远程通信、介绍dubbo-remoting-netty4内的源码解析。前言netty4对netty3兼容性不是很好,并且netty4在很多的术语和api也发生了改变,导致升级netty4会很艰辛,网上应该有很多相关文章,高版本的总有高版本的优势所在,所以dubbo也需要与时俱进,又新增了基于netty4来实现远程通讯模块。下面讲解的,如果跟上一篇文章有重复的地方我就略过去了。关键还是要把远程通讯的api那几篇看懂,看这几篇实现才会很简单。下面是包的结构:源码分析(一)NettyChannel该类继承了AbstractChannel,是基于netty4的通道实现类1.属性/** * 通道集合 /private static final ConcurrentMap<Channel, NettyChannel> channelMap = new ConcurrentHashMap<Channel, NettyChannel>();/* * 通道 /private final Channel channel;/* * 属性集合 /private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();属性跟netty3实现的通道类属性几乎一样,我就不讲解了。2.getOrAddChannelstatic NettyChannel getOrAddChannel(Channel ch, URL url, ChannelHandler handler) { if (ch == null) { return null; } // 首先从集合中取通道 NettyChannel ret = channelMap.get(ch); // 如果为空,则新建 if (ret == null) { NettyChannel nettyChannel = new NettyChannel(ch, url, handler); // 如果通道还活跃着 if (ch.isActive()) { // 加入集合 ret = channelMap.putIfAbsent(ch, nettyChannel); } if (ret == null) { ret = nettyChannel; } } return ret;}该方法是获得通道,如果集合中没有找到对应通道,则创建一个,然后加入集合。3.send@Overridepublic void send(Object message, boolean sent) throws RemotingException { super.send(message, sent); boolean success = true; int timeout = 0; try { // 写入数据,发送消息 ChannelFuture future = channel.writeAndFlush(message); // 如果已经发送过 if (sent) { // 获得超时时间 timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 等待timeout的连接时间后查看是否发送成功 success = future.await(timeout); } // 获得异常 Throwable cause = future.cause(); // 如果异常不为空,则抛出异常 if (cause != null) { throw cause; } } catch (Throwable e) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “, cause: " + e.getMessage(), e); } if (!success) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “in timeout(” + timeout + “ms) limit”); }}该方法是发送消息,调用了channel.writeAndFlush方法,与netty3的实现只是调用的api不同。4.close@Overridepublic void close() { try { super.close(); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { // 移除通道 removeChannelIfDisconnected(channel); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { // 清理属性集合 attributes.clear(); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { if (logger.isInfoEnabled()) { logger.info(“Close netty channel " + channel); } // 关闭通道 channel.close(); } catch (Exception e) { logger.warn(e.getMessage(), e); }}该方法就是操作了四个步骤,比较清晰。(二)NettyClientHandler该类继承了ChannelDuplexHandler,是基于netty4实现的客户端通道处理实现类。这里的设计与netty3实现的通道处理器有所不同,netty3实现的通道处理器是被客户端和服务端统一使用的,而在这里服务端和客户端使用了两个不同的Handler来处理。并且netty3的NettyHandler是基于netty3的SimpleChannelHandler设计的,而这里是基于netty4的ChannelDuplexHandler。/* * url对象 /private final URL url;/* * 通道 /private final ChannelHandler handler;该类的属性只有两个,下面实现的方法也都是调用了handler的方法,我就举一个例子:@Overridepublic void disconnect(ChannelHandlerContext ctx, ChannelPromise future) throws Exception { // 获得通道 NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); try { // 断开连接 handler.disconnected(channel); } finally { // 从集合中移除 NettyChannel.removeChannelIfDisconnected(ctx.channel()); }}可以看到分了三部,获得通道对象,调用handler方法,最后检测一下通道是否活跃。其他方法也是差不多。(三)NettyServerHandler该类继承了ChannelDuplexHandler,是基于netty4实现的服务端通道处理实现类。/* * 连接该服务器的通道数 key为ip:port /private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>/* * url对象 /private final URL url;/* * 通道处理器 /private final ChannelHandler handler;该类有三个属性,比NettyClientHandler多了一个属性channels,下面的实现方法也是一样的,都是调用了handler方法,来看一个例子:@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception { // 激活事件 ctx.fireChannelActive(); // 获得通道 NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); try { // 如果通道不为空,则加入集合中 if (channel != null) { channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel); } // 连接该通道 handler.connected(channel); } finally { // 如果通道不活跃,则移除通道 NettyChannel.removeChannelIfDisconnected(ctx.channel()); }}该方法是通道活跃的时候调用了handler.connected,差不多也是常规套路,就多了激活事件和加入到通道中。其他方法也差不多。(四)NettyClient该类继承了AbstractClient,是基于netty4实现的客户端实现类。1.属性/* * NioEventLoopGroup对象 /private static final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory(“NettyClientWorker”, true));/* * 客户端引导类 /private Bootstrap bootstrap;/* * 通道 /private volatile Channel channel; // volatile, please copy reference to use属性里的NioEventLoopGroup对象是netty4中的对象,什么用处请看netty的解析。2.doOpen@Overrideprotected void doOpen() throws Throwable { // 创建一个客户端的通道处理器 final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this); // 创建一个引导类 bootstrap = new Bootstrap(); // 设置可选项 bootstrap.group(nioEventLoopGroup) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout()) .channel(NioSocketChannel.class); // 如果连接超时时间小于3s,则设置为3s,也就是说最低的超时时间为3s if (getConnectTimeout() < 3000) { bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); } else { bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout()); } // 创建一个客户端 bootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { // 编解码器 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this); // 加入责任链 ch.pipeline()//.addLast(“logging”,new LoggingHandler(LogLevel.INFO))//for debug .addLast(“decoder”, adapter.getDecoder()) .addLast(“encoder”, adapter.getEncoder()) .addLast(“handler”, nettyClientHandler); } });}该方法还是做了创建客户端,并且打开的操作,其中很多的参数设置操作。其他方法跟 dubbo源码解析(十六)远程通信——Netty3中写到的NettyClient实现一样。(五)NettyServer该类继承了AbstractServer,实现了Server。是基于netty4实现的服务器类private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);/* * 连接该服务器的通道集合 key为ip:port /private Map<String, Channel> channels; // <ip:port, channel>/* * 服务器引导类 /private ServerBootstrap bootstrap;/* * 通道 /private io.netty.channel.Channel channel;/* * boss线程组 /private EventLoopGroup bossGroup;/* * worker线程组 /private EventLoopGroup workerGroup;属性相较netty3而言,新增了两个线程组,同样也是因为netty3和netty4的设计不同。2.doOpen@Overrideprotected void doOpen() throws Throwable { // 创建服务引导类 bootstrap = new ServerBootstrap(); // 创建boss线程组 bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(“NettyServerBoss”, true)); // 创建worker线程组 workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS), new DefaultThreadFactory(“NettyServerWorker”, true)); // 创建服务器处理器 final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this); // 获得通道集合 channels = nettyServerHandler.getChannels(); // 设置ventLoopGroup还有可选项 bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE) .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { // 编解码器 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); // 增加责任链 ch.pipeline()//.addLast(“logging”,new LoggingHandler(LogLevel.INFO))//for debug .addLast(“decoder”, adapter.getDecoder()) .addLast(“encoder”, adapter.getEncoder()) .addLast(“handler”, nettyServerHandler); } }); // bind 绑定 ChannelFuture channelFuture = bootstrap.bind(getBindAddress()); // 等待绑定完成 channelFuture.syncUninterruptibly(); // 设置通道 channel = channelFuture.channel();}该方法是创建服务器,并且开启。如果熟悉netty4点朋友应该觉得还是很好理解的。其他方法跟《 dubbo源码解析(十六)远程通信——Netty3》中写到的NettyClient实现一样,处理close中要多关闭两个线程组(六)NettyTransporter该类跟 《dubbo源码解析(十六)远程通信——Netty3》中的NettyTransporter一样的实现。(七)NettyCodecAdapter该类是基于netty4的编解码器。1.属性/* * 编码器 /private final ChannelHandler encoder = new InternalEncoder();/* * 解码器 /private final ChannelHandler decoder = new InternalDecoder();/* * 编解码器 /private final Codec2 codec;/* * url对象 /private final URL url;/* * 通道处理器 /private final com.alibaba.dubbo.remoting.ChannelHandler handler;属性跟基于netty3实现的编解码一样。2.InternalEncoderprivate class InternalEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { // 创建缓冲区 com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out); // 获得通道 Channel ch = ctx.channel(); // 获得netty通道 NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); try { // 编码 codec.encode(channel, buffer, msg); } finally { // 检测通道是否活跃 NettyChannel.removeChannelIfDisconnected(ch); } }}该内部类是编码器的抽象,主要的编码还是调用了codec.encode。3.InternalDecoderprivate class InternalDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception { // 创建缓冲区 ChannelBuffer message = new NettyBackedChannelBuffer(input); // 获得通道 NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); Object msg; int saveReaderIndex; try { // decode object. do { // 记录读索引 saveReaderIndex = message.readerIndex(); try { // 解码 msg = codec.decode(channel, message); } catch (IOException e) { throw e; } // 拆包 if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { message.readerIndex(saveReaderIndex); break; } else { //is it possible to go here ? if (saveReaderIndex == message.readerIndex()) { throw new IOException(“Decode without read data.”); } // 读取数据 if (msg != null) { out.add(msg); } } } while (message.readable()); } finally { NettyChannel.removeChannelIfDisconnected(ctx.channel()); } }}该内部类是解码器的抽象类,其中关键的是调用了codec.decode。(八)NettyBackedChannelBuffer该类是缓冲区类。/* * 缓冲区 */private ByteBuf buffer;其中的方法几乎都调用了该属性的方法。而ByteBuf是netty4中的字节数据的容器。(九)FormattingTuple和MessageFormatter这两个类是用于用于格式化的,是从netty4中复制出来的,其中并且稍微做了一下改动。我就不再讲解了。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了基于netty4的来实现的远程通信、介绍dubbo-remoting-netty4内的源码解析,关键需要对netty4有所了解。下一篇我会讲解基于p2p形式实现远程通信部分。 ...

December 28, 2018 · 4 min · jiezi

dubbo源码解析(十六)远程通信——Netty3

远程通讯——Netty3目标:介绍基于netty3的来实现的远程通信、介绍dubbo-remoting-netty内的源码解析。前言现在dubbo默认的网络传输Transport接口默认实现的还是基于netty3实现的网络传输,不过马上后面默认实现就要改为netty4了。由于netty4对netty3对兼容性不是很好,所以保留了两个版本的实现。下面是包结构:源码分析(一)NettyChannel该类继承了AbstractChannel类,是基于netty3实现的通道。1.属性/** * 通道集合 /private static final ConcurrentMap<org.jboss.netty.channel.Channel, NettyChannel> channelMap = new ConcurrentHashMap<org.jboss.netty.channel.Channel, NettyChannel>();/* * 通道 /private final org.jboss.netty.channel.Channel channel;/* * 属性集合 /private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();2.getOrAddChannelstatic NettyChannel getOrAddChannel(org.jboss.netty.channel.Channel ch, URL url, ChannelHandler handler) { if (ch == null) { return null; } // 首先从集合中取通道 NettyChannel ret = channelMap.get(ch); // 如果为空,则新建 if (ret == null) { NettyChannel nc = new NettyChannel(ch, url, handler); // 如果通道连接着 if (ch.isConnected()) { // 加入集合 ret = channelMap.putIfAbsent(ch, nc); } if (ret == null) { ret = nc; } } return ret;}该方法是获得通道,当通道在集合中没有的时候,新建一个通道。3.removeChannelIfDisconnectedstatic void removeChannelIfDisconnected(org.jboss.netty.channel.Channel ch) { if (ch != null && !ch.isConnected()) { channelMap.remove(ch); }}该方法是当通道没有连接的时候,从集合中移除它。4.send@Overridepublic void send(Object message, boolean sent) throws RemotingException { super.send(message, sent); boolean success = true; int timeout = 0; try { // 写入数据,发送消息 ChannelFuture future = channel.write(message); // 如果已经发送过 if (sent) { // 获得超时时间 timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 等待timeout的连接时间后查看是否发送成功 success = future.await(timeout); } // 看是否有异常 Throwable cause = future.getCause(); if (cause != null) { throw cause; } } catch (Throwable e) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “, cause: " + e.getMessage(), e); } if (!success) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “in timeout(” + timeout + “ms) limit”); }}该方法是发送消息,其中用到了channe.write方法传输消息,并且通过返回的future来判断是否发送成功。5.close@Overridepublic void close() { try { super.close(); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { // 如果通道断开,则移除该通道 removeChannelIfDisconnected(channel); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { // 清空属性 attributes.clear(); } catch (Exception e) { logger.warn(e.getMessage(), e); } try { if (logger.isInfoEnabled()) { logger.info(“Close netty channel " + channel); } // 关闭通道 channel.close(); } catch (Exception e) { logger.warn(e.getMessage(), e); }}该方法是关闭通道,做了三个操作,分别是从集合中移除、清除属性、关闭通道。其他实现方法比较简单,我就讲解了。(二)NettyHandler该类继承了SimpleChannelHandler类,是基于netty3的通道处理器,而该类被加上了@Sharable注解,也就是说该处理器可以从属于多个ChannelPipeline1.属性/* * 通道集合,key是主机地址 ip:port /private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>/* * url对象 /private final URL url;/* * 通道 /private final ChannelHandler handler;该类的属性比较简单,并且该类中实现的方法都是调用了属性handler的方法,我举一个例子来讲,其他的可以自己查看源码,比较简单。@Overridepublic void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { // 获得通道实例 NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler); try { if (channel != null) { // 保存该通道,加入到集合中 channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()), channel); } // 连接 handler.connected(channel); } finally { NettyChannel.removeChannelIfDisconnected(ctx.getChannel()); }}该方法是通道连接的方法,其中先获取了通道实例,然后吧该实例加入到集合中,最好带哦用handler.connected来进行连接。(三)NettyClient该类继承了AbstractClient,是基于netty3实现的客户端类。1.属性private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);// ChannelFactory’s closure has a DirectMemory leak, using static to avoid// https://issues.jboss.org/browse/NETTY-424/* * 通道工厂,用static来避免直接缓存区的一个OOM问题 /private static final ChannelFactory channelFactory = new NioClientSocketChannelFactory(Executors.newCachedThreadPool(new NamedThreadFactory(“NettyClientBoss”, true)), Executors.newCachedThreadPool(new NamedThreadFactory(“NettyClientWorker”, true)), Constants.DEFAULT_IO_THREADS);/* * 客户端引导对象 /private ClientBootstrap bootstrap;/* * 通道 /private volatile Channel channel; // volatile, please copy reference to use上述属性中ChannelFactory用了static修饰,为了避免netty3中会有直接缓冲内存泄漏的现象,具体的讨论可以访问注释中的讨论。2.doOpen@Overrideprotected void doOpen() throws Throwable { // 设置日志工厂 NettyHelper.setNettyLoggerFactory(); // 实例化客户端引导类 bootstrap = new ClientBootstrap(channelFactory); // config // @see org.jboss.netty.channel.socket.SocketChannelConfig // 配置选择项 bootstrap.setOption(“keepAlive”, true); bootstrap.setOption(“tcpNoDelay”, true); bootstrap.setOption(“connectTimeoutMillis”, getConnectTimeout()); // 创建通道处理器 final NettyHandler nettyHandler = new NettyHandler(getUrl(), this); // 设置责任链路 bootstrap.setPipelineFactory(new ChannelPipelineFactory() { /* * 获得通道 * @return / @Override public ChannelPipeline getPipeline() { // 新建编解码 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this); // 获得管道 ChannelPipeline pipeline = Channels.pipeline(); // 设置解码器 pipeline.addLast(“decoder”, adapter.getDecoder()); // 设置编码器 pipeline.addLast(“encoder”, adapter.getEncoder()); // 设置通道处理器 pipeline.addLast(“handler”, nettyHandler); // 返回通道 return pipeline; } });}该方法是创建客户端,并且打开,其中的逻辑就是用netty3的客户端引导类来创建一个客户端,如果对netty不熟悉的朋友可以先补补netty知识。3.doConnect@Overrideprotected void doConnect() throws Throwable { long start = System.currentTimeMillis(); // 用引导类连接 ChannelFuture future = bootstrap.connect(getConnectAddress()); try { // 在超时时间内是否连接完成 boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); if (ret && future.isSuccess()) { // 获得通道 Channel newChannel = future.getChannel(); // 异步修改此通道 newChannel.setInterestOps(Channel.OP_READ_WRITE); try { // Close old channel 关闭旧的通道 Channel oldChannel = NettyClient.this.channel; // copy reference if (oldChannel != null) { try { if (logger.isInfoEnabled()) { logger.info(“Close old netty channel " + oldChannel + " on create new netty channel " + newChannel); } // 关闭 oldChannel.close(); } finally { // 移除通道 NettyChannel.removeChannelIfDisconnected(oldChannel); } } } finally { // 如果客户端关闭 if (NettyClient.this.isClosed()) { try { if (logger.isInfoEnabled()) { logger.info(“Close new netty channel " + newChannel + “, because the client closed.”); } // 关闭通道 newChannel.close(); } finally { NettyClient.this.channel = null; NettyChannel.removeChannelIfDisconnected(newChannel); } } else { NettyClient.this.channel = newChannel; } } } else if (future.getCause() != null) { throw new RemotingException(this, “client(url: " + getUrl() + “) failed to connect to server " + getRemoteAddress() + “, error message is:” + future.getCause().getMessage(), future.getCause()); } else { throw new RemotingException(this, “client(url: " + getUrl() + “) failed to connect to server " + getRemoteAddress() + " client-side timeout " + getConnectTimeout() + “ms (elapsed: " + (System.currentTimeMillis() - start) + “ms) from netty client " + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()); } } finally { // 如果客户端没有连接 if (!isConnected()) { // 取消future future.cancel(); } }}该方法是客户端连接服务器的方法。其中调用了bootstrap.connect。后面的逻辑是用来检测是否连接,最后如果未连接,则会取消该连接任务。4.doClose@Overrideprotected void doClose() throws Throwable { /try { bootstrap.releaseExternalResources(); } catch (Throwable t) { logger.warn(t.getMessage()); }/}在这里不能关闭是因为channelFactory 是静态属性,被多个 NettyClient 共用。所以不能释放资源。(四)NettyServer该类继承了AbstractServer,实现了Server,是基于netty3实现的服务器类。1.属性/* * 连接该服务器的通道集合 /private Map<String, Channel> channels; // <ip:port, channel>/* * 服务器引导类对象 /private ServerBootstrap bootstrap;/* * 通道 /private org.jboss.netty.channel.Channel channel;2.doOpen@Overrideprotected void doOpen() throws Throwable { // 设置日志工厂 NettyHelper.setNettyLoggerFactory(); // 创建线程池 ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory(“NettyServerBoss”, true)); ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory(“NettyServerWorker”, true)); // 新建通道工厂 ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); // 新建服务引导类对象 bootstrap = new ServerBootstrap(channelFactory); // 新建通道处理器 final NettyHandler nettyHandler = new NettyHandler(getUrl(), this); // 获得通道集合 channels = nettyHandler.getChannels(); // https://issues.jboss.org/browse/NETTY-365 // https://issues.jboss.org/browse/NETTY-379 // final Timer timer = new HashedWheelTimer(new NamedThreadFactory(“NettyIdleTimer”, true)); // 禁用nagle算法,将数据立即发送出去。纳格算法是以减少封包传送量来增进TCP/IP网络的效能 bootstrap.setOption(“child.tcpNoDelay”, true); // 设置管道工厂 bootstrap.setPipelineFactory(new ChannelPipelineFactory() { /* * 获得通道 * @return / @Override public ChannelPipeline getPipeline() { // 新建编解码器 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); // 获得通道 ChannelPipeline pipeline = Channels.pipeline(); /int idleTimeout = getIdleTimeout(); if (idleTimeout > 10000) { pipeline.addLast(“timer”, new IdleStateHandler(timer, idleTimeout / 1000, 0, 0)); }/ // 设置解码器 pipeline.addLast(“decoder”, adapter.getDecoder()); // 设置编码器 pipeline.addLast(“encoder”, adapter.getEncoder()); // 设置通道处理器 pipeline.addLast(“handler”, nettyHandler); // 返回通道 return pipeline; } }); // bind 绑定地址,也就是启用服务器 channel = bootstrap.bind(getBindAddress());}该方法是创建服务器,并且打开服务器。同样创建服务器的方式跟正常的用netty创建服务器方式一样,只是新加了编码器和解码器。还有一个注意点就是这里ServerBootstrap 的可选项。3.doClose@Overrideprotected void doClose() throws Throwable { try { if (channel != null) { // unbind.关闭通道 channel.close(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); } try { // 获得所有连接该服务器的通道集合 Collection<com.alibaba.dubbo.remoting.Channel> channels = getChannels(); if (channels != null && !channels.isEmpty()) { // 遍历通道集合 for (com.alibaba.dubbo.remoting.Channel channel : channels) { try { // 关闭通道连接 channel.close(); } catch (Throwable e) { logger.warn(e.getMessage(), e); } } } } catch (Throwable e) { logger.warn(e.getMessage(), e); } try { if (bootstrap != null) { // release external resource. 回收资源 bootstrap.releaseExternalResources(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); } try { if (channels != null) { // 清空集合 channels.clear(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); }}该方法是关闭服务器,一系列的操作很清晰,我就不多说了。4.getChannels@Overridepublic Collection<Channel> getChannels() { Collection<Channel> chs = new HashSet<Channel>(); for (Channel channel : this.channels.values()) { // 如果通道连接,则加入集合,返回 if (channel.isConnected()) { chs.add(channel); } else { channels.remove(NetUtils.toAddressString(channel.getRemoteAddress())); } } return chs;}该方法是返回连接该服务器的通道集合,并且用了HashSet保存,不会重复。(五)NettyTransporterpublic class NettyTransporter implements Transporter { public static final String NAME = “netty”; @Override public Server bind(URL url, ChannelHandler listener) throws RemotingException { // 创建一个NettyServer return new NettyServer(url, listener); } @Override public Client connect(URL url, ChannelHandler listener) throws RemotingException { // 创建一个NettyClient return new NettyClient(url, listener); }}该类就是基于netty3的Transporter实现类,同样两个方法也是分别创建了NettyServer和NettyClient。(六)NettyHelper该类是设置日志的工具类,其中基于netty3的InternalLoggerFactory实现类一个DubboLoggerFactory。这个我就不讲解了,比较好理解,不理解也无伤大雅。(七)NettyCodecAdapter该类是基于netty3实现的编解码类。1.属性/* * 编码者 /private final ChannelHandler encoder = new InternalEncoder();/* * 解码者 /private final ChannelHandler decoder = new InternalDecoder();/* * 编解码器 /private final Codec2 codec;/* * url对象 /private final URL url;/* * 缓冲区大小 /private final int bufferSize;/* * 通道对象 /private final com.alibaba.dubbo.remoting.ChannelHandler handler;InternalEncoder和InternalDecoder属性是该类的内部类,分别掌管着编码和解码2.构造方法public NettyCodecAdapter(Codec2 codec, URL url, com.alibaba.dubbo.remoting.ChannelHandler handler) { this.codec = codec; this.url = url; this.handler = handler; int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE); // 如果缓存区大小在16字节以内,则设置配置大小,如果不是,则设置8字节的缓冲区大小 this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;}你会发现对于缓存区大小的规则都是一样的。3.InternalEncoder@Sharableprivate class InternalEncoder extends OneToOneEncoder { @Override protected Object encode(ChannelHandlerContext ctx, Channel ch, Object msg) throws Exception { // 动态分配一个1k的缓冲区 com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer(1024); // 获得通道对象 NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); try { // 编码 codec.encode(channel, buffer, msg); } finally { NettyChannel.removeChannelIfDisconnected(ch); } // 基于buteBuffer创建一个缓冲区,并且写入数据 return ChannelBuffers.wrappedBuffer(buffer.toByteBuffer()); }}该内部类实现类编码的逻辑,主要调用了codec.encode。4.InternalDecoderprivate class InternalDecoder extends SimpleChannelUpstreamHandler { private com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception { Object o = event.getMessage(); // 如果消息不是一个ChannelBuffer类型 if (!(o instanceof ChannelBuffer)) { // 转发事件到与此上下文关联的处理程序最近的上游 ctx.sendUpstream(event); return; } ChannelBuffer input = (ChannelBuffer) o; // 如果可读数据不大于0,直接返回 int readable = input.readableBytes(); if (readable <= 0) { return; } com.alibaba.dubbo.remoting.buffer.ChannelBuffer message; if (buffer.readable()) { // 判断buffer是否是动态分配的缓冲区 if (buffer instanceof DynamicChannelBuffer) { // 写入数据 buffer.writeBytes(input.toByteBuffer()); message = buffer; } else { // 需要的缓冲区大小 int size = buffer.readableBytes() + input.readableBytes(); // 动态生成缓冲区 message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer( size > bufferSize ? size : bufferSize); // 把buffer数据写入message message.writeBytes(buffer, buffer.readableBytes()); // 把input数据写入message message.writeBytes(input.toByteBuffer()); } } else { // 否则 基于ByteBuffer通过buffer来创建一个新的缓冲区 message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer( input.toByteBuffer()); } NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler); Object msg; int saveReaderIndex; try { // decode object. do { saveReaderIndex = message.readerIndex(); try { // 解码 msg = codec.decode(channel, message); } catch (IOException e) { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; throw e; } // 拆包 if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { message.readerIndex(saveReaderIndex); break; } else { // 如果已经到达读索引,则没有数据可解码 if (saveReaderIndex == message.readerIndex()) { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; throw new IOException(“Decode without read data.”); } // if (msg != null) { // 将消息发送到指定关联的处理程序最近的上游 Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress()); } } } while (message.readable()); } finally { // 如果消息还有可读数据,则丢弃 if (message.readable()) { message.discardReadBytes(); buffer = message; } else { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; } NettyChannel.removeChannelIfDisconnected(ctx.getChannel()); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { ctx.sendUpstream(e); }}该内部类实现了解码的逻辑,其中大部分逻辑都在对数据做读写,关键的解码调用了codec.decode。(八)NettyBackedChannelBufferFactory该类是创建缓冲区的工厂类。它实现了ChannelBufferFactory接口,也就是实现类它的三种获得缓冲区的方法。public class NettyBackedChannelBufferFactory implements ChannelBufferFactory { /* * 单例 */ private static final NettyBackedChannelBufferFactory INSTANCE = new NettyBackedChannelBufferFactory(); public static ChannelBufferFactory getInstance() { return INSTANCE; } @Override public ChannelBuffer getBuffer(int capacity) { return new NettyBackedChannelBuffer(ChannelBuffers.dynamicBuffer(capacity)); } @Override public ChannelBuffer getBuffer(byte[] array, int offset, int length) { org.jboss.netty.buffer.ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(length); buffer.writeBytes(array, offset, length); return new NettyBackedChannelBuffer(buffer); } @Override public ChannelBuffer getBuffer(ByteBuffer nioBuffer) { return new NettyBackedChannelBuffer(ChannelBuffers.wrappedBuffer(nioBuffer)); }}可以看到,都是创建了一个NettyBackedChannelBuffer,下面讲解NettyBackedChannelBuffer。(九)NettyBackedChannelBuffer该类是基于netty3的buffer重新实现的缓冲区,它实现了ChannelBuffer接口,并且有一个属性:private org.jboss.netty.buffer.ChannelBuffer buffer;那么其中的几乎所有方法都是调用了这个buffer的方法,因为我在dubbo源码解析(十一)远程通信——Buffer中写到ChannelBuffer接口方法定义跟netty中的缓冲区定义几乎一样,连注释都几乎一样。所有知识单纯的调用了buffer的方法。具体的代码可以查看我的GitHub后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了基于netty3的来实现的远程通信、介绍dubbo-remoting-netty内的源码解析,关键需要对netty有所了解。下一篇我会讲解基于netty4实现远程通信部分。 ...

December 27, 2018 · 8 min · jiezi

dubbo源码解析(十五)远程通信——Mina

远程通讯——Mina目标:介绍基于Mina的来实现的远程通信、介绍dubbo-remoting-mina内的源码解析。前言Apache MINA是一个网络应用程序框架,可帮助用户轻松开发高性能和高可扩展性的网络应用程序。它通过Java NIO在各种传输(如TCP / IP和UDP / IP)上提供抽象的事件驱动异步API。它通常被称为NIO框架库、客户端服务器框架库或者网络套接字库。那么本问就要讲解在dubbo项目中,基于mina的API实现服务端和客户端来完成远程通讯这件事情。下面是mina实现的包结构:源码分析(一)MinaChannel该类继承了AbstractChannel,是基于mina实现的通道。1.属性private static final Logger logger = LoggerFactory.getLogger(MinaChannel.class);/** * 通道的key /private static final String CHANNEL_KEY = MinaChannel.class.getName() + “.CHANNEL”;/* * mina中的一个句柄,表示两个端点之间的连接,与传输类型无关 /private final IoSession session;该类的属性除了封装了一个CHANNEL_KEY以外,还用到了mina中的IoSession,它封装着一个连接所需要的方法,比如获得远程地址等。2.getOrAddChannelstatic MinaChannel getOrAddChannel(IoSession session, URL url, ChannelHandler handler) { // 如果连接session为空,则返回空 if (session == null) { return null; } // 获得MinaChannel实例 MinaChannel ret = (MinaChannel) session.getAttribute(CHANNEL_KEY); // 如果不存在,则创建 if (ret == null) { // 创建一个MinaChannel实例 ret = new MinaChannel(session, url, handler); // 如果两个端点连接 if (session.isConnected()) { // 把新创建的MinaChannel添加到session 中 MinaChannel old = (MinaChannel) session.setAttribute(CHANNEL_KEY, ret); // 如果属性的旧值不为空,则重新设置旧值 if (old != null) { session.setAttribute(CHANNEL_KEY, old); ret = old; } } } return ret;}该方法是一个获得MinaChannel对象的方法,其中每一个MinaChannel都会被放在session的属性值中。3.removeChannelIfDisconnectedstatic void removeChannelIfDisconnected(IoSession session) { if (session != null && !session.isConnected()) { session.removeAttribute(CHANNEL_KEY); }}该方法是当没有连接时移除该通道,比较简单。4.send@Overridepublic void send(Object message, boolean sent) throws RemotingException { super.send(message, sent); boolean success = true; int timeout = 0; try { // 发送消息,返回future WriteFuture future = session.write(message); // 如果已经发送过了 if (sent) { // 获得延迟时间 timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 等待timeout的连接时间后查看是否发送成功 success = future.join(timeout); } } catch (Throwable e) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “, cause: " + e.getMessage(), e); } if (!success) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “in timeout(” + timeout + “ms) limit”); }}该方法是最关键的发送消息,其中调用到了session的write方法,就是mina封装的发送消息。并且根据返回的WriteFuture对象来判断是否发送成功。(二)MinaHandler该类继承了IoHandlerAdapter,是通道处理器实现类,其中就是mina项目中IoHandler接口的几个 方法。/* * url对象 /private final URL url;/* * 通道处理器对象 /private final ChannelHandler handler;该类有两个属性,上述提到的实现IoHandler接口方法都是调用了handler来实现的,我就举例讲一个,其他的都一样的写法:@Overridepublic void sessionOpened(IoSession session) throws Exception { // 获得MinaChannel对象 MinaChannel channel = MinaChannel.getOrAddChannel(session, url, handler); try { // 调用接连该通道 handler.connected(channel); } finally { // 如果没有连接则移除通道 MinaChannel.removeChannelIfDisconnected(session); }}该方法在IoHandler中叫做sessionOpened,其实就是连接方法,所以调用的是handler.connected。其他方法也一样,请自行查看。(三)MinaClient该类继承了AbstractClient类,是基于mina实现的客户端类。1.属性/* * 套接字连接集合 /private static final Map<String, SocketConnector> connectors = new ConcurrentHashMap<String, SocketConnector>();/* * 连接的key /private String connectorKey;/* * 套接字连接者 /private SocketConnector connector;/* * 一个句柄 /private volatile IoSession session; // volatile, please copy reference to use该类中的属性都跟mina项目中封装类有关系。2.doOpen@Overrideprotected void doOpen() throws Throwable { // 用url来作为key connectorKey = getUrl().toFullString(); // 先从集合中取套接字连接 SocketConnector c = connectors.get(connectorKey); if (c != null) { connector = c; // 如果为空 } else { // set thread pool. 设置线程池 connector = new SocketConnector(Constants.DEFAULT_IO_THREADS, Executors.newCachedThreadPool(new NamedThreadFactory(“MinaClientWorker”, true))); // config 获得套接字连接配置 SocketConnectorConfig cfg = (SocketConnectorConfig) connector.getDefaultConfig(); cfg.setThreadModel(ThreadModel.MANUAL); // 启用TCP_NODELAY cfg.getSessionConfig().setTcpNoDelay(true); // 启用SO_KEEPALIVE cfg.getSessionConfig().setKeepAlive(true); int timeout = getConnectTimeout(); // 设置连接超时时间 cfg.setConnectTimeout(timeout < 1000 ? 1 : timeout / 1000); // set codec. // 设置编解码器 connector.getFilterChain().addLast(“codec”, new ProtocolCodecFilter(new MinaCodecAdapter(getCodec(), getUrl(), this))); // 加入集合 connectors.put(connectorKey, connector); }}该方法是打开客户端,在mina中用套接字连接者connector来表示。其中的操作就是新建一个connector,并且设置相应的属性,然后加入集合。3.doConnect@Overrideprotected void doConnect() throws Throwable { // 连接服务器 ConnectFuture future = connector.connect(getConnectAddress(), new MinaHandler(getUrl(), this)); long start = System.currentTimeMillis(); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); // 用于对线程的阻塞和唤醒 final CountDownLatch finish = new CountDownLatch(1); // resolve future.awaitUninterruptibly() dead lock // 加入监听器 future.addListener(new IoFutureListener() { @Override public void operationComplete(IoFuture future) { try { // 如果已经读完了 if (future.isReady()) { // 创建获得该连接的IoSession实例 IoSession newSession = future.getSession(); try { // Close old channel 关闭旧的session IoSession oldSession = MinaClient.this.session; // copy reference if (oldSession != null) { try { if (logger.isInfoEnabled()) { logger.info(“Close old mina channel " + oldSession + " on create new mina channel " + newSession); } // 关闭连接 oldSession.close(); } finally { // 移除通道 MinaChannel.removeChannelIfDisconnected(oldSession); } } } finally { // 如果MinaClient关闭了 if (MinaClient.this.isClosed()) { try { if (logger.isInfoEnabled()) { logger.info(“Close new mina channel " + newSession + “, because the client closed.”); } // 关闭session newSession.close(); } finally { MinaClient.this.session = null; MinaChannel.removeChannelIfDisconnected(newSession); } } else { // 设置新的session MinaClient.this.session = newSession; } } } } catch (Exception e) { exception.set(e); } finally { // 减少数量,释放所有等待的线程 finish.countDown(); } } }); try { // 当前线程等待,直到锁存器倒计数到零,除非线程被中断,或者指定的等待时间过去 finish.await(getConnectTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RemotingException(this, “client(url: " + getUrl() + “) failed to connect to server " + getRemoteAddress() + " client-side timeout " + getConnectTimeout() + “ms (elapsed: " + (System.currentTimeMillis() - start) + “ms) from netty client " + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion() + “, cause: " + e.getMessage(), e); } Throwable e = exception.get(); if (e != null) { throw e; }}该方法是客户端连接服务器的实现方法。其中用到了CountDownLatch来代表完成完成事件,它来做一个线程等待,直到1个线程完成上述的动作,也就是连接完成结束,才释放等待的线程。保证每次只有一条线程去连接,解决future.awaitUninterruptibly()死锁问题。其他方法请自行查看我写的注释。(四)MinaServer该类继承了AbstractServer,是基于mina实现的服务端实现类。1.属性private static final Logger logger = LoggerFactory.getLogger(MinaServer.class);/* * 套接字接收者对象 /private SocketAcceptor acceptor;2.doOpen@Overrideprotected void doOpen() throws Throwable { // set thread pool. // 创建套接字接收者对象 acceptor = new SocketAcceptor(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS), Executors.newCachedThreadPool(new NamedThreadFactory(“MinaServerWorker”, true))); // config // 设置配置 SocketAcceptorConfig cfg = (SocketAcceptorConfig) acceptor.getDefaultConfig(); cfg.setThreadModel(ThreadModel.MANUAL); // set codec. 设置编解码器 acceptor.getFilterChain().addLast(“codec”, new ProtocolCodecFilter(new MinaCodecAdapter(getCodec(), getUrl(), this))); // 开启服务器 acceptor.bind(getBindAddress(), new MinaHandler(getUrl(), this));}该方法是创建服务器,并且打开服务器。关键就是调用了acceptor的方法。3.doClose@Overrideprotected void doClose() throws Throwable { try { if (acceptor != null) { // 取消绑定,也就是关闭服务器 acceptor.unbind(getBindAddress()); } } catch (Throwable e) { logger.warn(e.getMessage(), e); }}该方法是关闭服务器,就是调用了acceptor.unbind方法。4.getChannels@Overridepublic Collection<Channel> getChannels() { // 获得连接到该服务器到所有连接句柄 Set<IoSession> sessions = acceptor.getManagedSessions(getBindAddress()); Collection<Channel> channels = new HashSet<Channel>(); for (IoSession session : sessions) { if (session.isConnected()) { // 每次都用一个连接句柄创建一个通道 channels.add(MinaChannel.getOrAddChannel(session, getUrl(), this)); } } return channels;}该方法是获得所有连接该服务器的通道。5.getChannel@Overridepublic Channel getChannel(InetSocketAddress remoteAddress) { // 获得连接到该服务器到所有连接句柄 Set<IoSession> sessions = acceptor.getManagedSessions(getBindAddress()); // 遍历所有句柄,找到要找的通道 for (IoSession session : sessions) { if (session.getRemoteAddress().equals(remoteAddress)) { return MinaChannel.getOrAddChannel(session, getUrl(), this); } } return null;}该方法是获得地址对应的单个通道。(五)MinaTransporterpublic class MinaTransporter implements Transporter { public static final String NAME = “mina”; @Override public Server bind(URL url, ChannelHandler handler) throws RemotingException { // 创建MinaServer实例 return new MinaServer(url, handler); } @Override public Client connect(URL url, ChannelHandler handler) throws RemotingException { // 创建MinaClient实例 return new MinaClient(url, handler); }}该类实现了Transporter接口,是基于mina的传输层实现。可以看到,bind和connect方法分别就是创建了MinaServer和MinaClient实例。这里我建议查看一下《dubbo源码解析(九)远程通信——Transport层》。(六)MinaCodecAdapter该类是基于mina实现的编解码类,实现了ProtocolCodecFactory。1.属性/* * 编码对象 /private final ProtocolEncoder encoder = new InternalEncoder();/* * 解码对象 /private final ProtocolDecoder decoder = new InternalDecoder();/* * 编解码器 /private final Codec2 codec;/* * url对象 /private final URL url;/* * 通道处理器对象 /private final ChannelHandler handler;/* * 缓冲区大小 */private final int bufferSize;属性比较好理解,该编解码器用到了ProtocolEncoder和ProtocolDecoder,而InternalEncoder和InternalDecoder两个类是该类的内部类,它们实现了ProtocolEncoder和ProtocolDecoder,关键的编解码逻辑在这两个类中实现。2.构造方法public MinaCodecAdapter(Codec2 codec, URL url, ChannelHandler handler) { this.codec = codec; this.url = url; this.handler = handler; int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE); // 如果缓存区大小在16字节以内,则设置配置大小,如果不是,则设置8字节的缓冲区大小 this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;}3.InternalEncoderprivate class InternalEncoder implements ProtocolEncoder { @Override public void dispose(IoSession session) throws Exception { } @Override public void encode(IoSession session, Object msg, ProtocolEncoderOutput out) throws Exception { // 动态分配一个1k的缓冲区 ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(1024); // 获得通道 MinaChannel channel = MinaChannel.getOrAddChannel(session, url, handler); try { // 编码 codec.encode(channel, buffer, msg); } finally { // 检测是否断开连接,如果断开,则移除 MinaChannel.removeChannelIfDisconnected(session); } // 写数据到out中 out.write(ByteBuffer.wrap(buffer.toByteBuffer())); out.flush(); }}该内部类是编码类,其中的encode方法中写到了编码核心调用的是codec.encode。4.InternalDecoderprivate class InternalDecoder implements ProtocolDecoder { private ChannelBuffer buffer = ChannelBuffers.EMPTY_BUFFER; @Override public void decode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception { int readable = in.limit(); if (readable <= 0) return; ChannelBuffer frame; // 如果缓冲区还有可读字节数 if (buffer.readable()) { // 如果缓冲区是DynamicChannelBuffer类型的 if (buffer instanceof DynamicChannelBuffer) { // 往buffer中写入数据 buffer.writeBytes(in.buf()); frame = buffer; } else { // 缓冲区大小 int size = buffer.readableBytes() + in.remaining(); // 动态分配一个缓冲区 frame = ChannelBuffers.dynamicBuffer(size > bufferSize ? size : bufferSize); // buffer的数据把写到frame frame.writeBytes(buffer, buffer.readableBytes()); // 把流中的数据写到frame frame.writeBytes(in.buf()); } } else { // 否则是基于Java NIO的ByteBuffer生成的缓冲区 frame = ChannelBuffers.wrappedBuffer(in.buf()); } // 获得通道 Channel channel = MinaChannel.getOrAddChannel(session, url, handler); Object msg; int savedReadIndex; try { do { // 获得读索引 savedReadIndex = frame.readerIndex(); try { // 解码 msg = codec.decode(channel, frame); } catch (Exception e) { buffer = ChannelBuffers.EMPTY_BUFFER; throw e; } // 拆包 if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { frame.readerIndex(savedReadIndex); break; } else { if (savedReadIndex == frame.readerIndex()) { buffer = ChannelBuffers.EMPTY_BUFFER; throw new Exception(“Decode without read data.”); } if (msg != null) { // 把数据写到输出流里面 out.write(msg); } } } while (frame.readable()); } finally { // 如果frame还有可读数据 if (frame.readable()) { //丢弃可读数据 frame.discardReadBytes(); buffer = frame; } else { buffer = ChannelBuffers.EMPTY_BUFFER; } MinaChannel.removeChannelIfDisconnected(session); } } @Override public void dispose(IoSession session) throws Exception { } @Override public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { }}该内部类是解码类,其中decode方法中关键的是调用了codec.decode,其余的操作是利用缓冲区对数据的冲刷流转。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了基于mina的来实现的远程通信、介绍dubbo-remoting-mina内的源码解析,关键需要对mina有所了解。下一篇我会讲解基于netty3实现远程通信部分。 ...

December 26, 2018 · 6 min · jiezi

dubbo源码解析(十四)远程通信——Http

远程通讯——Http目标:介绍基于Http的来实现的远程通信、介绍dubbo-remoting-http内的源码解析。前言本文我们讲解的是如何基于Tomcat或者Jetty实现HTTP服务器。Tomcat和Jetty都是一种servlet引擎,Jetty要比Tomcat的架构更简单一些。关于它们之间的比较,我觉得google一些更加方便,我就不多废话了 。下面是dubbo-remoting-http包下的类图:可以看到这个包下只提供了服务端实现,并没有客户端实现。源码分析(一)HttpServerpublic interface HttpServer extends Resetable { /** * get http handler. * 获得http的处理类 * @return http handler. / HttpHandler getHttpHandler(); /* * get url. * 获得url * @return url / URL getUrl(); /* * get local address. * 获得本地服务器地址 * @return local address. / InetSocketAddress getLocalAddress(); /* * close the channel. * 关闭通道 / void close(); /* * Graceful close the channel. * 优雅的关闭通道 / void close(int timeout); /* * is bound. * 是否绑定 * @return bound / boolean isBound(); /* * is closed. * 服务器是否关闭 * @return closed / boolean isClosed();}该接口是http服务器的接口,定义了服务器相关的方法,都比较好理解。(二)AbstractHttpServer该类实现接口HttpServer,是http服务器接口的抽象类。/* * url /private final URL url;/* * http服务器处理器 /private final HttpHandler handler;/* * 该服务器是否关闭 /private volatile boolean closed;public AbstractHttpServer(URL url, HttpHandler handler) { if (url == null) { throw new IllegalArgumentException(“url == null”); } if (handler == null) { throw new IllegalArgumentException(“handler == null”); } this.url = url; this.handler = handler;}该类中有三个属性,关键是看在后面怎么用到,因为该类是抽象类,所以该类中的方法并没有实现具体的逻辑,我们继续往下看它的三个子类。(三)TomcatHttpServer该类是基于Tomcat来实现服务器的实现类,它继承了AbstractHttpServer。1.属性/* * 内嵌的tomcat对象 /private final Tomcat tomcat;/* * url对象 /private final URL url;该类的两个属性,关键是内嵌的tomcat。2.构造方法 public TomcatHttpServer(URL url, final HttpHandler handler) { super(url, handler); this.url = url; // 添加处理器 DispatcherServlet.addHttpHandler(url.getPort(), handler); // 获得java.io.tmpdir的绝对路径目录 String baseDir = new File(System.getProperty(“java.io.tmpdir”)).getAbsolutePath(); // 创建内嵌的tomcat对象 tomcat = new Tomcat(); // 设置根目录 tomcat.setBaseDir(baseDir); // 设置端口号 tomcat.setPort(url.getPort()); // 给默认的http连接器。设置最大线程数 tomcat.getConnector().setProperty( “maxThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));// tomcat.getConnector().setProperty(// “minSpareThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS))); // 设置最大的连接数 tomcat.getConnector().setProperty( “maxConnections”, String.valueOf(url.getParameter(Constants.ACCEPTS_KEY, -1))); // 设置URL编码格式 tomcat.getConnector().setProperty(“URIEncoding”, “UTF-8”); // 设置连接超时事件为60s tomcat.getConnector().setProperty(“connectionTimeout”, “60000”); // 设置最大长连接个数为不限制个数 tomcat.getConnector().setProperty(“maxKeepAliveRequests”, “-1”); // 设置将由连接器使用的Coyote协议。 tomcat.getConnector().setProtocol(“org.apache.coyote.http11.Http11NioProtocol”); // 添加上下文 Context context = tomcat.addContext("/", baseDir); // 添加servlet,把servlet添加到context Tomcat.addServlet(context, “dispatcher”, new DispatcherServlet()); // 添加servlet映射 context.addServletMapping("/", “dispatcher”); // 添加servlet上下文 ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext()); try { // 开启tomcat tomcat.start(); } catch (LifecycleException e) { throw new IllegalStateException(“Failed to start tomcat server at " + url.getAddress(), e); } }该方法的构造函数中就启动了tomcat,前面很多都是对tomcat的启动参数以及配置设置。如果有过tomcat配置经验的朋友应该看起来很简单。3.close@Overridepublic void close() { super.close(); // 移除相关的servlet上下文 ServletManager.getInstance().removeServletContext(url.getPort()); try { // 停止tomcat tomcat.stop(); } catch (Exception e) { logger.warn(e.getMessage(), e); }}该方法是关闭服务器的方法。调用了tomcat.stop(四)JettyHttpServer该类是基于Jetty来实现服务器的实现类,它继承了AbstractHttpServer。1.属性/** * 内嵌的Jetty服务器对象 /private Server server;/* * url对象 /private URL url;该类的两个属性,关键是内嵌的sever,它是内嵌的etty服务器对象。2.构造方法 public JettyHttpServer(URL url, final HttpHandler handler) { super(url, handler); this.url = url; // TODO we should leave this setting to slf4j // we must disable the debug logging for production use // 设置日志 Log.setLog(new StdErrLog()); // 禁用调试用的日志 Log.getLog().setDebugEnabled(false); // 添加http服务器处理器 DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), handler); // 获得线程数 int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS); // 创建线程池 QueuedThreadPool threadPool = new QueuedThreadPool(); // 设置线程池配置 threadPool.setDaemon(true); threadPool.setMaxThreads(threads); threadPool.setMinThreads(threads); // 创建选择NIO连接器 SelectChannelConnector connector = new SelectChannelConnector(); // 获得绑定的ip String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost()); if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { // 设置主机地址 connector.setHost(bindIp); } // 设置端口号 connector.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); // 创建Jetty服务器对象 server = new Server(); // 设置线程池 server.setThreadPool(threadPool); // 设置连接器 server.addConnector(connector); // 添加DispatcherServlet到jetty ServletHandler servletHandler = new ServletHandler(); ServletHolder servletHolder = servletHandler.addServletWithMapping(DispatcherServlet.class, “/”); servletHolder.setInitOrder(2); // dubbo’s original impl can’t support the use of ServletContext// server.addHandler(servletHandler); // TODO Context.SESSIONS is the best option here? Context context = new Context(server, “/”, Context.SESSIONS); context.setServletHandler(servletHandler); // 添加 ServletContext 对象,到 ServletManager 中 ServletManager.getInstance().addServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext()); try { // 启动jetty服务器 server.start(); } catch (Exception e) { throw new IllegalStateException(“Failed to start jetty server on " + url.getParameter(Constants.BIND_IP_KEY) + “:” + url.getParameter(Constants.BIND_PORT_KEY) + “, cause: " + e.getMessage(), e); } }可以看到它跟TomcatHttpServer中构造函数不同的是API的不同,不过思路差不多,先设置启动参数和配置,然后启动jetty服务器。3.close@Overridepublic void close() { super.close(); // 移除 ServletContext 对象 ServletManager.getInstance().removeServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); if (server != null) { try { // 停止服务器 server.stop(); } catch (Exception e) { logger.warn(e.getMessage(), e); } }}该方法是关闭服务器的方法,调用的是server的stop方法。(五)ServletHttpServer该类继承了AbstractHttpServer,是基于 Servlet 的服务器实现类。public class ServletHttpServer extends AbstractHttpServer { public ServletHttpServer(URL url, HttpHandler handler) { super(url, handler); // /把 HttpHandler 到 DispatcherServlet 中,默认端口为8080 DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler); }}该类就一个构造方法。就是把服务器处理器注册到DispatcherServlet上。(六)HttpBinder@SPI(“jetty”)public interface HttpBinder { /** * bind the server. * 绑定到服务器 * @param url server url. * @return server. / @Adaptive({Constants.SERVER_KEY}) HttpServer bind(URL url, HttpHandler handler);}该接口是http绑定器接口,其中就定义了一个方法就是绑定方法,并且返回服务器对象。该接口是一个可扩展接口,默认实现JettyHttpBinder。它有三个实现类,请往下看。(七)TomcatHttpBinderpublic class TomcatHttpBinder implements HttpBinder { @Override public HttpServer bind(URL url, HttpHandler handler) { // 创建一个TomcatHttpServer return new TomcatHttpServer(url, handler); }}一眼就看出来,就是创建了个基于Tomcat实现的服务器TomcatHttpServer对象。具体的就往上看TomcatHttpServer里面的实现。(八)JettyHttpBinderpublic class JettyHttpBinder implements HttpBinder { @Override public HttpServer bind(URL url, HttpHandler handler) { // 创建JettyHttpServer实例 return new JettyHttpServer(url, handler); }}一眼就看出来,就是创建了个基于Jetty实现的服务器JettyHttpServer对象。具体的就往上看JettyHttpServer里面的实现。(九)ServletHttpBinderpublic class ServletHttpBinder implements HttpBinder { @Override @Adaptive() public HttpServer bind(URL url, HttpHandler handler) { // 创建ServletHttpServer对象 return new ServletHttpServer(url, handler); }}创建了个基于servlet实现的服务器ServletHttpServer对象。并且方法上加入了Adaptive,用到了dubbo SPI机制。(十)BootstrapListenerpublic class BootstrapListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { // context创建的时候,把ServletContext添加到ServletManager ServletManager.getInstance().addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { // context销毁到时候,把servletContextEvent移除 ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT); }}该类实现了ServletContextListener,是 启动监听器,当context创建和销毁的时候对ServletContext做处理。不过需要配置BootstrapListener到web.xml,通过这样的方式,让外部的 ServletContext 对象,添加到 ServletManager 中。(十一)DispatcherServletpublic class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 5766349180380479888L; /* * http服务器处理器 / private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>(); /* * 单例 / private static DispatcherServlet INSTANCE; public DispatcherServlet() { DispatcherServlet.INSTANCE = this; } /* * 添加处理器 * @param port * @param processor / public static void addHttpHandler(int port, HttpHandler processor) { handlers.put(port, processor); } public static void removeHttpHandler(int port) { handlers.remove(port); } public static DispatcherServlet getInstance() { return INSTANCE; } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获得处理器 HttpHandler handler = handlers.get(request.getLocalPort()); // 如果处理器不存在 if (handler == null) {// service not found. // 返回404 response.sendError(HttpServletResponse.SC_NOT_FOUND, “Service not found.”); } else { // 处理请求 handler.handle(request, response); } }}该类继承了HttpServlet,是服务请求调度servlet类,主要是service方法,根据请求来调度不同的处理器去处理请求,如果没有该处理器,则报错404(十二)ServletManagerpublic class ServletManager { /* * 外部服务器端口,用于 servlet 的服务器端口 / public static final int EXTERNAL_SERVER_PORT = -1234; /* * 单例 / private static final ServletManager instance = new ServletManager(); /* * ServletContext 集合 / private final Map<Integer, ServletContext> contextMap = new ConcurrentHashMap<Integer, ServletContext>(); public static ServletManager getInstance() { return instance; } /* * 添加ServletContext * @param port * @param servletContext / public void addServletContext(int port, ServletContext servletContext) { contextMap.put(port, servletContext); } /* * 移除ServletContext * @param port / public void removeServletContext(int port) { contextMap.remove(port); } /* * 获得ServletContext对象 * @param port * @return / public ServletContext getServletContext(int port) { return contextMap.get(port); }}该类是servlet的管理器,管理着ServletContext。(十三)HttpHandlerpublic interface HttpHandler { /* * invoke. * HTTP请求处理 * @param request request. * @param response response. * @throws IOException * @throws ServletException */ void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;}该接口是HTTP 处理器接口,就定义了一个处理请求的方法。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了内嵌tomcat和jetty的来实现的http服务器,关键需要对tomcat和jetty的配置有所了解。下一篇我会讲解基于mina实现远程通信部分。 ...

December 25, 2018 · 5 min · jiezi

【Dubbo源码阅读系列】之 Dubbo SPI 机制

最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解。如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出。Dubbo SPI 介绍Java SPI在阅读本文之前可能需要你对 Java SPI(Service Provider Interface) 机制有过简单的了解。这里简单介绍下:在面向对象的设计中,我们提倡模块之间基于接口编程。不同模块可能会有不同的具体实现,但是为了避免模块的之间的耦合过大,我们需要一种有效的服务(服务实现)发现机制来选择具体模块。SPI 就是这样一种基于接口编程+策略模式+配置文件,同时可供使用者根据自己的实际需要启用/替换模块具体实现的方案。Dubbo SPI 的改进点以下内容摘录自 https://dubbo.gitbooks.io/dub… Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点在 Dubbo 中,如果某个 interface 接口标记了 @SPI 注解,那么我们认为它是 Dubbo 中的一个扩展点。扩展点是 Dubbo SPI 的核心,下面我们就扩展点加载、扩展点自动包装、扩展点自动装配几方面来聊聊具体实现。Dubbo SPI 机制详解Dubbo 扩展点的加载在阅读本文前,如果你阅读过Java SPI 相关内容,大概能回忆起来有 /META-INF/services 这样一个目录。在这个目录下有一个以接口命名的文件,文件的内容为接口具体实现类的全限定名。在 Dubbo 中我们也能找到类似的设计。META-INF/services/(兼容JAVA SPI)META-INF/dubbo/(自定义扩展点实现)META-INF/dubbo/internal/(Dubbo内部扩展点实现)非常好~我们现在已经知道了从哪里加载扩展点了,再回忆一下,JAVA SPI是如何加载的。ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);类似的,在 Dubbo 中也有这样一个用于加载扩展点的类 ExtensionLoader。这一章节,我们会着重了解一下这个类到底是如何帮助我们加载扩展点的。我们先来看一段简短的代码。Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();在 Dubbo 的实现里面用到了大量类似的代码片段,我们只需要提供一个 type ,即可获取该 type 的自适应(关于自适应的理解在后文会提到)扩展类。在获取对应自适应扩展类时,我们首先获取该类型的 ExtensionLoader。看到这里我们应该下意识的感觉到对于每个 type 来说,都应该有一个对应的 ExtensionLoader 对象。我们先来看看 ExtensionLoader 是如何获取的。getExtensionLoader()public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException(“Extension type == null”); } if (!type.isInterface()) { throw new IllegalArgumentException(“Extension type(” + type + “) is not interface!”); } // 是否被 SPI 注解标识 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException(“Extension type(” + type + “) is not extension, because WITHOUT @” + SPI.class.getSimpleName() + " Annotation!"); } //EXTENSION_LOADERS 为一个 ConcurrentMap集合,key 为 Class 对象,value 为ExtenLoader 对象 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader;}private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}上面这一段的代码比较简单,根据 type 从 EXTENSION_LOADERS 集合中获取 loader ,如果返回的值为 null 则新建一个 ExtensionLoader 对象。这里的 objectFactory 获取也用到了类似的方法,获取到了 ExtensionFactory 的扩展自适应类。getAdaptiveExtension()public T getAdaptiveExtension() { //cachedAdaptiveInstance用于缓存自适应扩展类实例 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { // … } } } } return (T) instance;}getAdaptiveExtension()方法用于获取当前自适应扩展类实例,首先会从 cachedAdaptiveInstance 对象中获取,如果值为 null 同时 createAdaptiveInstanceError 为空,则调用 createAdaptiveExtension 方法创建扩展类实例。创建完后更新 cachedAdaptiveInstance 。createAdaptiveExtension()private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { // 省略异常 }}这里有两个方法值得我们关注,injectExtension() 和 getAdaptiveExtensionClass()。injectExtension() 看名字像是一个实现了注入功能的方法,而 getAdaptiveExtensionClass() 则用于获取具体的自适应扩展类。我们依次看下这两个方法。injectExtension()private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith(“set”) && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { //如果存在 DisableInject 注解则跳过 if (method.getAnnotation(DisableInject.class) != null) { continue; } //获取 method 第一个参数的类型 Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : “”; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error(“fail to inject via method " + method.getName() + " of interface " + type.getName() + “: " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance;}简单的总结下这个方法做了什么:遍历当前实例的 set 方法,以 set 方法第四位开始至末尾的字符串为关键字,尝试通过 objectFactory 来获取对应的 扩展类实现。如果存在对应扩展类,通过反射注入到当前实例中。这个方法相当于完成了一个简单的依赖注入功能,我们常说 Dubbo 中的 IOC 实际上也是在这里体现的。getAdaptiveExtensionClass()private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}接着看 getAdaptiveExtensionClass() 方法。首先调用 getExtensionClasses() 方法,如果 cachedAdaptiveClass() 不为 null 则返回,如果为 null 则调用 createAdaptiveExtensionClass() 方法。依次看下这两个方法。getExtensionClasses()private Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes;}内容比较简单,我们直接看 loadExtensionClasses() 方法。private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); // 只能有一个默认扩展实例 if (names.length > 1) { throw new IllegalStateException(“more than 1 default extension name on extension " + type.getName() + “: " + Arrays.toString(names)); } if (names.length == 1) { cachedDefaultName = names[0]; } } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace(“org.apache”, “com.alibaba”)); return extensionClasses;}绕来绕去这么久,终于要进入主题了。为什么说进入主题了呢?看看这几个变量的值DUBBO_INTERNAL_DIRECTORY:META-INF/dubbo/internal/DUBBO_DIRECTORY:META-INF/dubbo/SERVICES_DIRECTORY:META-INF/services/熟悉的配方熟悉的料。。没错了,我们马上就要开始读取这三个目录下的文件,然后开始加载我们的扩展点了。loadDirectory()private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) { String fileName = dir + type; try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { // … }}private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), “utf-8”)); try { String line; while ((line = reader.readLine()) != null) { //#对应注释位置,剔除存在的注释 final int ci = line.indexOf(’#’); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; //文件中的内容以 key=value 的形式保存,拆分 key 和 vlaue int i = line.indexOf(’=’); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { // … } } } } finally { reader.close(); } } catch (Throwable t) { // … }}private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // 用于判断 class 是不是 type 接口的实现类 if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException(“Error when load extension class(interface: " + type + “, class line: " + clazz.getName() + “), class " + clazz.getName() + “is not subtype of interface.”); } // 如果当前 class 被 @Adaptive 注解标记,更新 cachedAdaptiveClass 缓存对象 if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { // 省略异常 } } else if (isWrapperClass(clazz)) { // 这里涉及到了 Dubbo 扩展点的另一个机制:包装,在后文介绍 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { clazz.getConstructor(); // 如果 name 为空,调用 findAnnotationName() 方法。如果当前类有 @Extension 注解,直接返回 @Extension 注解value; // 若没有 @Extension 注解,但是类名类似 xxxType(Type 代表 type 的类名),返回值为小写的 xxx if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException(“No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { // @Activate 注解用于配置扩展被自动激活条件 // 如果当前 class 包含 @Activate ,加入到缓存中 Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } else { // support com.alibaba.dubbo.common.extension.Activate com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null) { cachedActivates.put(names[0], oldActivate); } } for (String n : names) { if (!cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { // 还记得文件内容长啥样吗?(name = calssvalue),我们最后将其保存到了 extensionClasses 集合中 extensionClasses.put(n, clazz); } else if (c != clazz) { // … } } } }}这一段代码真的相当长啊。。梳理下之后发现其实他做的事情也很简单:拼接生成文件名:dir + type,读取该文件读取文件内容,将文件内容拆分为 name 和 class 字符串如果 clazz 类中包含 @Adaptive 注解,将其加入到 cachedAdaptiveClass 缓存中 如果 clazz 类中为包装类,添加到 wrappers 中 如果文件不为 key=class 形式,会尝试通过 @Extension 注解获取 name 如果 clazz 包含 @Activate 注解(兼容 com.alibaba.dubbo.common.extension.Activate 注解),将其添加到 cachedActivates 缓存中最后以 name 为 key ,clazz 为 vlaue,将其添加到 extensionClasses 集合中并返回获取自适应扩展类getAdaptiveExtensionClass()private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}Ok,我们已经分析了 getExtensionClasses 方法,并且已经将扩展点实现加载到了缓存中。这个方法是由 getAdaptiveExtensionClass() 方法引出来的,它看起来是像是创建自适应扩展类的。这里会先判断缓存对象 cachedAdaptiveClass 是否会空,cachedAdaptiveClass 是什么时候被初始化的呢?回顾一下之前的代码:private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // 省略… if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { // 省略… } }}在 loadClass() 方法中如果发现当前 clazz 包含 @Adaptive 注解,则将当前 clazz 作为缓存自适应类保存。例如在 AdaptiveExtensionFactory 类中就有这么用,我们会将 AdaptiveExtensionFactory 类作为 ExtensionFactory 类型的自适应类缓存起来。@Adaptivepublic class AdaptiveExtensionFactory implements ExtensionFactory我们继续分析该方法的后部分。如果 cachedAdaptiveClass 为 null,则会调用 createAdaptiveExtensionClass() 方法动态生成一个自适应扩展类。private Class<?> createAdaptiveExtensionClass() { String code = createAdaptiveExtensionClassCode(); System.out.println(code); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader);}这一段代码在本次分享中不打算重点叙述,可以简单的理解为 dubbo 帮我生成了一个自适应类。我摘取了生成的一段代码,如下所示:package org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory { private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class); private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0); public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException { if (arg2 == null) throw new IllegalArgumentException(“url == null”); org.apache.dubbo.common.URL url = arg2; String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getInvoker(arg0, arg1, arg2); } public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument == null”); if (arg0.getUrl() == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument getUrl() == null”);org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getProxy(arg0, arg1); } public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument == null”); if (arg0.getUrl() == null) throw new IllegalArgumentException(“org.apache.dubbo.rpc.Invoker argument getUrl() == null”);org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter(“proxy”, “javassist”); if(extName == null) throw new IllegalStateException(“Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(” + url.toString() + “) use keys([proxy])”); org.apache.dubbo.rpc.ProxyFactory extension = null; try { extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); }catch(Exception e){ if (count.incrementAndGet() == 1) { logger.warn(“Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.”, e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(“javassist”); } return extension.getProxy(arg0); }}extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);这一段代码实际上才是自适应适配类的精髓,看看 extName 是怎么来的?String extName = url.getParameter(“proxy”, “javassist”);extName 又是从 url 中取得的,实际上 url 对于 Dubbo 来说是一种非常重要的上下文传输载体,在后续系列文章中大家会逐步感受到。public T getExtension(String name) { if (name == null || name.length() == 0) { throw new IllegalArgumentException(“Extension name == null”); } if (“true”.equals(name)) { return getDefaultExtension(); } // 从缓存中读取扩展实现类 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance;}上面的逻辑比较简单,这里也不赘述了,直接看 createExtension() 方法。private T createExtension(String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException(“Extension instance(name: " + name + “, class: " + type + “) could not be instantiated: " + t.getMessage(), t); }}getExtensionClasses() 方法在前文已经分析过了,但是需要注意的是:getExtensionClasses 返回给我们的不过是使用 Class.forName() 加载过的类而已,充其量执行了里面的静态代码段,而并非得到了真正的实例。真正的实例对象仍需要调用 class.newInstance() 方法才能获取。 了解了这些之后我们继续看,我们通过 getExtensionClasses() 尝试获取系统已经加载的 class 对象,通过 class 对象再去扩展实例缓存中取。如果扩展实例为 null,调用 newInstance() 方法初始化实例,并放到 EXTENSION_INSTANCES 缓存中。之后再调用 injectExtension() 方法进行依赖注入。最后一段涉及到包装类的用法,下一个章节进行介绍。扩展类的包装在 createExtension() 方法中有如下一段代码:private T createExtension(String name) { // ···省略··· Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; // ···省略···}还记得 wrapperClasses 在什么地方被初始化的吗?在前文中的 loadClass() 方法中我们已经有介绍过。再回顾一下:private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { // ···省略··· if (isWrapperClass(clazz)) { Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } // ···省略···}private boolean isWrapperClass(Class<?> clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; }}在看这个方法前我们先了解下 Dubbo 中 wrapper 类的定义。举个例子:class A { private A a; public A(A a){ this.a = a; }}我们可以看到 A 类有一个以 A 为参数的构造方法,我们称它为复制构造方法。有这样构造方法的类在 Dubbo 中我们称它为 Wrapper 类。继续看 isWrapperClass() 方法,这个方法比较简单,尝试获取 clazz 中以 type 为参数的构造方法,如果可以获取到,则认为 clazz 则是当前 type 类的包装类。再结合上面的代码,我们会发现在加载扩展点时,我们将对应 type 的包装类缓存起来。private T createExtension(String name) { // ···省略··· T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; // ···省略···}为了更好的理解这段代码,我们假设当前 type 值为 Protocol.class ,我们可以在 org.apache.dubbo.rpc.Protocol 文件中找到 Protocol 接口的包装类 ProtocolFilterWrapper 和 ProtocolListenerWrapper,他们会依次被添加到 cachedWrapperClasses 集合中。依次遍历 cachedWrapperClasses 集合,比如第一次取到的是 ProtocolFilterWrapper 类,则会以调用 ProtocolFilterWrapper 的复制构造方法将 instance 包装起来。创建完 ProtocolFilterWrapper 对象实例后,调用 injectExtension() 进行依赖注入。此时 instance 已经为 ProtocolFilterWrapper 的实例,继续循环,会将 ProtocolFilterWrapper 类包装在 ProtocolListenerWrapper 类中。因此我们最后返回的是一个 ProtocolListenerWrapper 实例。最后调用时,仍会通过一层一层的调用,最后调用原始 instance 的方法。这里的包装类有点类似 AOP 思想,我们可以通过一层一层的包装,在调用扩展实现之前添加一些日志打印、监控等自定义的操作。Dubbo 中的 IOC 机制上文中我们已经讨论过 Dubbo 中利用反射机制实现一个类 IOC 功能。在这一章节中,我们再回顾一下 injectExtension() 方法,仔细的来看看 Dubbo 中 IOC 功能的实现。createExtension()instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));private T injectExtension(T instance) { // ··· Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : “”; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } // ···}public class StubProxyFactoryWrapper implements ProxyFactory { // … private Protocol protocol; public void setProtocol(Protocol protocol) { this.protocol = protocol; } //…}在上一章节中我们已经讲过 wrapper 类,在这里我们举个例子说明一下。比如我们当前的 wrapperClass 类为 StubProxyFactoryWrapper,那么代码执行逻辑大致如下所示:创建 StubProxyFactoryWrapper 实例;获取流程1创建的实例作为 injectExtension() 的参数,执行;injectExtension() 方法循环遍历到 StubProxyFactoryWrapper 的 setProtocol()方法(此时 pt=Protocol.class,property=protocol),执行 objectFactory.getExtension(pt,property) 方法。objectFactory 在 ExtensionLoader 的构造方法中被初始化,在这里获取到自适应扩展类为 AdaptiveExtensionFactory。执行 AdaptiveExtensionFactory.getExtension()。AdaptiveExtensionFactory 类中有一个集合变量 factories。factories 在 AdaptiveExtensionFactory 的构造方法中被初始化,包含了两个工厂类:SpiExtensionFactory、SpringExtensionFactory。执行 AdaptiveExtensionFactory 类的 getExtension() 方法会依次调用 SpiExtensionFactory 和 SpringExtensionFactory 类的 getExtension() 方法。执行 SpiExtensionFactory 的 getExtension() 方法。上面有说到此时的 type=Procotol.class,property=protocol,从下面的代码我们可以发现 Protocol 是一个接口类,同时标注了 @SPI 注解,此时会获取 Protocol 类型的 ExtensionLoader 对象,最后又去调用 loader 的 getAdaptiveExtension() 方法。最终获取到的自适应类为 Protocol$Adaptive 动态类。objectFactory.getExtension(pt, property); 最后得到的类为 Protocol$Adaptive 类,最后利用反射机制将其注入到 StubProxyFactoryWrapper 实例中。@SPI(“dubbo”)public interface Protocol {}public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; }}END在最后,我们再回顾下开头关于 Dubbo SPI 基于 JAVA SPI 改进的那段话:Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。总结如下:Dubbo SPI 在加载扩展点,会以 key-value 的形式将扩展类保存在缓存中,但此时的扩展类只是调用 Class.forName() 加载的类,并没有实例化。扩展类会在调用 getExtension() 方法时被实例化。Dubbo 通过工厂模式和反射机制实现了依赖注入功能。Dubbo 中通过包装类实现了 AOP 机制,方便我们添加监控和打印日志。本BLOG上原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,否则属于侵权行为。https://juejin.im/post/5c0cd7… ...

December 25, 2018 · 10 min · jiezi

dubbo源码解析(十三)远程通信——Grizzly

远程通讯——Grizzly目标:介绍基于Grizzly的来实现的远程通信、介绍dubbo-remoting-grizzly内的源码解析。前言Grizzly NIO框架的设计初衷是帮助开发者更好地利用Java NIO API,构建强大的可扩展的服务器应用。关于Grizzly我也没有很熟悉,所以只能根据grizzly在dubbo的远程通讯中应用稍微讲解一下。下面是dubbo-remoting-grizzly下的包结构:源码分析(一)GrizzlyChannel1.属性private static final Logger logger = LoggerFactory.getLogger(GrizzlyChannel.class);/** * 通道key /private static final String CHANNEL_KEY = GrizzlyChannel.class.getName() + “.CHANNEL”;/* * 通道属性 /private static final Attribute<GrizzlyChannel> ATTRIBUTE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(CHANNEL_KEY);/* * Grizzly的连接实例 /private final Connection<?> connection;可以看到,该类中的ATTRIBUTE和connection都是Grizzly涉及到的属性,ATTRIBUTE中封装了GrizzlyChannel的实例还有Connection实例。Grizzly把连接的一些连接的方法定义在了Connection接口中,包括获得远程地址、检测通道是否连接等方法。2.send@Override@SuppressWarnings(“rawtypes”)public void send(Object message, boolean sent) throws RemotingException { super.send(message, sent); int timeout = 0; try { // 发送消息,获得GrizzlyFuture实例 GrizzlyFuture future = connection.write(message); if (sent) { // 获得延迟多少时间获得响应 timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 获得请求的值 future.get(timeout, TimeUnit.MILLISECONDS); } } catch (TimeoutException e) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “in timeout(” + timeout + “ms) limit”, e); } catch (Throwable e) { throw new RemotingException(this, “Failed to send message " + message + " to " + getRemoteAddress() + “, cause: " + e.getMessage(), e); }}该方法是发送消息的方法,调用了connection的write方法,它会返回一个GrizzlyFuture实例,GrizzlyFuture继承了java.util.concurrent.Future。其他方法比较简单,实现的方法都基本调用connection的方法或者Grizzly.DEFAULT_ATTRIBUTE_BUILDER的方法。(二)GrizzlyHandler该类是Grizzly的通道处理器,它继承了BaseFilter。/* * url /private final URL url;/* * 通道处理器 /private final ChannelHandler handler;该类有两个属性,这两个属性应该很熟悉了。而该类有handleConnect、handleClose、handleRead、handleWrite、exceptionOccurred的实现方法,分别调用了ChannelHandler中封装的五个方法。举个例子:@Overridepublic NextAction handleConnect(FilterChainContext ctx) throws IOException { // 获得Connection连接实例 Connection<?> connection = ctx.getConnection(); // 获得GrizzlyChannel通道 GrizzlyChannel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler); try { // 连接 handler.connected(channel); } catch (RemotingException e) { throw new IOException(StringUtils.toString(e)); } finally { GrizzlyChannel.removeChannelIfDisconnected(connection); } return ctx.getInvokeAction();}可以看到获得GrizzlyChannel通道就调用了handler.connected进行连接。而其他四个方法也差不多,感兴趣的可以自行查看代码。(三)GrizzlyClient该类是Grizzly的客户端实现类,继承了AbstractClient类/* * Grizzly中的传输对象 /private TCPNIOTransport transport;/* * 连接实例 /private volatile Connection<?> connection; // volatile, please copy reference to use可以看到属性中有TCPNIOTransport的实例,在该类中实现的客户端方法都调用了transport中的方法,TCPNIOTransport中封装了创建,连接,断开连接等方法。我们来看创建客户端的逻辑:@Overrideprotected void doOpen() throws Throwable { // 做一些过滤,用于处理消息 FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless(); filterChainBuilder.add(new TransportFilter()); filterChainBuilder.add(new GrizzlyCodecAdapter(getCodec(), getUrl(), this)); filterChainBuilder.add(new GrizzlyHandler(getUrl(), this)); // 传输构建者 TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance(); // 获得线程池配置实例 ThreadPoolConfig config = builder.getWorkerThreadPoolConfig(); // 设置线程池的配置,包括核心线程数等 config.setPoolName(CLIENT_THREAD_POOL_NAME) .setQueueLimit(-1) .setCorePoolSize(0) .setMaxPoolSize(Integer.MAX_VALUE) .setKeepAliveTime(60L, TimeUnit.SECONDS); // 设置创建属性 builder.setTcpNoDelay(true).setKeepAlive(true) .setConnectionTimeout(getConnectTimeout()) .setIOStrategy(SameThreadIOStrategy.getInstance()); // 创建一个transport transport = builder.build(); transport.setProcessor(filterChainBuilder.build()); // 创建客户端 transport.start();}可以看到首先是设置及一些过滤,在上面对信息先处理,然后设置了线程池的配置,创建transport,并且用transport来进行创建客户端。(四)GrizzlyServer该类是Grizzly的服务器实现类,继承了AbstractServer类。/* * 连接该服务器的客户端通道集合 /private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>/* * 传输实例 /private TCPNIOTransport transport;该类中有两个属性,其中transport用法跟GrizzlyClient中的一样。我也就讲解一个doOpen方法:@Overrideprotected void doOpen() throws Throwable { // 增加过滤器,来处理信息 FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless(); filterChainBuilder.add(new TransportFilter()); filterChainBuilder.add(new GrizzlyCodecAdapter(getCodec(), getUrl(), this)); filterChainBuilder.add(new GrizzlyHandler(getUrl(), this)); TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance(); // 获得线程池配置 ThreadPoolConfig config = builder.getWorkerThreadPoolConfig(); config.setPoolName(SERVER_THREAD_POOL_NAME).setQueueLimit(-1); // 获得url配置中线程池类型 String threadpool = getUrl().getParameter(Constants.THREADPOOL_KEY, Constants.DEFAULT_THREADPOOL); if (Constants.DEFAULT_THREADPOOL.equals(threadpool)) { // 优先从url获得线程池的线程数,默认线程数为200 int threads = getUrl().getPositiveParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS); // 设置线程池配置 config.setCorePoolSize(threads).setMaxPoolSize(threads) .setKeepAliveTime(0L, TimeUnit.SECONDS); // 如果是cached类型的线程池 } else if (“cached”.equals(threadpool)) { int threads = getUrl().getPositiveParameter(Constants.THREADS_KEY, Integer.MAX_VALUE); // 设置核心线程数为0、最大线程数为threads config.setCorePoolSize(0).setMaxPoolSize(threads) .setKeepAliveTime(60L, TimeUnit.SECONDS); } else { throw new IllegalArgumentException(“Unsupported threadpool type " + threadpool); } builder.setKeepAlive(true).setReuseAddress(false) .setIOStrategy(SameThreadIOStrategy.getInstance()); // 创建transport transport = builder.build(); transport.setProcessor(filterChainBuilder.build()); transport.bind(getBindAddress()); // 开启服务器 transport.start();}该方法是创建服务器,可以看到操作跟GrizzlyClient中的差不多。(五)GrizzlyTransporter该类实现了Transporter接口,是基于Grizzly的传输层实现。public class GrizzlyTransporter implements Transporter { public static final String NAME = “grizzly”; @Override public Server bind(URL url, ChannelHandler listener) throws RemotingException { // 返回GrizzlyServer实例 return new GrizzlyServer(url, listener); } @Override public Client connect(URL url, ChannelHandler listener) throws RemotingException { // // 返回GrizzlyClient实例 return new GrizzlyClient(url, listener); }}可以看到,bind和connect方法分别就是创建了GrizzlyServer和GrizzlyClient实例。这里我建议查看一下《dubbo源码解析(九)远程通信——Transport层》。(六)GrizzlyCodecAdapter该类是Grizzly编解码类,继承了BaseFilter。1.属性和构造方法/* * 编解码器 /private final Codec2 codec;/* * url /private final URL url;/* * 通道处理器 /private final ChannelHandler handler;/* * 缓存大小 /private final int bufferSize;/* * 空缓存区 */private ChannelBuffer previousData = ChannelBuffers.EMPTY_BUFFER;public GrizzlyCodecAdapter(Codec2 codec, URL url, ChannelHandler handler) { this.codec = codec; this.url = url; this.handler = handler; int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE); // 如果缓存区大小在16字节以内,则设置配置大小,如果不是,则设置8字节的缓冲区大小 this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;}2.handleWrite@Overridepublic NextAction handleWrite(FilterChainContext context) throws IOException { Connection<?> connection = context.getConnection(); GrizzlyChannel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler); try { // 分配一个1024的动态缓冲区 ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer(1024); // Do not need to close // 获得消息 Object msg = context.getMessage(); // 编码 codec.encode(channel, channelBuffer, msg); // 检测是否连接 GrizzlyChannel.removeChannelIfDisconnected(connection); // 分配缓冲区 Buffer buffer = connection.getTransport().getMemoryManager().allocate(channelBuffer.readableBytes()); // 把channelBuffer的数据写到buffer buffer.put(channelBuffer.toByteBuffer()); buffer.flip(); buffer.allowBufferDispose(true); // 设置到上下文 context.setMessage(buffer); } finally { GrizzlyChannel.removeChannelIfDisconnected(connection); } return context.getInvokeAction();}该方法是写数据,可以发现编码调用的是 codec.encode,其他的我都在注释里写明了,关键还是对前面两篇文章的一些内容需要理解。3.handleRead@Overridepublic NextAction handleRead(FilterChainContext context) throws IOException { Object message = context.getMessage(); Connection<?> connection = context.getConnection(); Channel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler); try { // 如果接收的是一个数据包 if (message instanceof Buffer) { // receive a new packet Buffer grizzlyBuffer = (Buffer) message; // buffer ChannelBuffer frame; // 如果缓冲区可读 if (previousData.readable()) { // 如果该缓冲区是动态的缓冲区 if (previousData instanceof DynamicChannelBuffer) { // 写入数据 previousData.writeBytes(grizzlyBuffer.toByteBuffer()); frame = previousData; } else { // 获得需要的缓冲区大小 int size = previousData.readableBytes() + grizzlyBuffer.remaining(); // 新建一个动态缓冲区 frame = ChannelBuffers.dynamicBuffer(size > bufferSize ? size : bufferSize); // 写入previousData中的数据 frame.writeBytes(previousData, previousData.readableBytes()); // 写入grizzlyBuffer中的数据 frame.writeBytes(grizzlyBuffer.toByteBuffer()); } } else { // 否则是基于Java NIO的ByteBuffer生成的缓冲区 frame = ChannelBuffers.wrappedBuffer(grizzlyBuffer.toByteBuffer()); } Object msg; int savedReadIndex; do { savedReadIndex = frame.readerIndex(); try { // 解码 msg = codec.decode(channel, frame); } catch (Exception e) { previousData = ChannelBuffers.EMPTY_BUFFER; throw new IOException(e.getMessage(), e); } // 拆包 if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { frame.readerIndex(savedReadIndex); // 结束调用链 return context.getStopAction(); } else { if (savedReadIndex == frame.readerIndex()) { // 没有可读内容 previousData = ChannelBuffers.EMPTY_BUFFER; throw new IOException(“Decode without read data.”); } if (msg != null) { // 把解码后信息放入上下文 context.setMessage(msg); // 继续下面的调用链 return context.getInvokeAction(); } else { return context.getInvokeAction(); } } } while (frame.readable()); } else { // Other events are passed down directly return context.getInvokeAction(); } } finally { GrizzlyChannel.removeChannelIfDisconnected(connection); }}该方法是读数据,直接调用了codec.decode进行解码。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了基于grizzly的来实现的远程通信、介绍dubbo-remoting-grizzly内的源码解析,关键需要对grizzly有所了解。下一篇我会讲解基于http实现远程通信部分。 ...

December 24, 2018 · 4 min · jiezi

dubbo源码解析(十二)远程通信——Telnet

远程通讯——Telnet目标:介绍telnet的相关实现逻辑、介绍dubbo-remoting-api中的telnet包内的源码解析。前言从dubbo 2.0.5开始,dubbo开始支持通过 telnet 命令来进行服务治理。本文就是讲解一些公用的telnet命令的实现。下面来看一下telnet实现的类图:可以看到,实现了TelnetHandler接口的有六个类,除了TelnetHandlerAdapter是以外,其他五个分别对应了clear、exit、help、log、status命令的实现,具体用来干嘛,请看官方文档的介绍。源码分析(一)TelnetHandler@SPIpublic interface TelnetHandler { /** * telnet. * 处理对应的telnet命令 * @param channel * @param message telnet命令 / String telnet(Channel channel, String message) throws RemotingException;}该接口上telnet命令处理器接口,是一个可扩展接口。它定义了一个方法,就是处理相关的telnet命令。(二)TelnetHandlerAdapter该类继承了ChannelHandlerAdapter,实现了TelnetHandler接口,是TelnetHandler的适配器类,负责在接收到HeaderExchangeHandler发来的telnet命令后分发给对应的TelnetHandler实现类去实现,并且返回命令结果。public class TelnetHandlerAdapter extends ChannelHandlerAdapter implements TelnetHandler { /* * 扩展加载器 / private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class); @Override public String telnet(Channel channel, String message) throws RemotingException { // 获得提示键配置,用于nc获取信息时不显示提示符 String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT); boolean noprompt = message.contains("–no-prompt"); message = message.replace("–no-prompt", “”); StringBuilder buf = new StringBuilder(); // 删除头尾空白符的字符串 message = message.trim(); String command; // 获得命令 if (message.length() > 0) { int i = message.indexOf(’ ‘); if (i > 0) { // 获得命令 command = message.substring(0, i).trim(); // 获得参数 message = message.substring(i + 1).trim(); } else { command = message; message = “”; } } else { command = “”; } if (command.length() > 0) { // 如果有该命令的扩展实现类 if (extensionLoader.hasExtension(command)) { try { // 执行相应命令的实现类的telnet String result = extensionLoader.getExtension(command).telnet(channel, message); if (result == null) { return null; } // 返回结果 buf.append(result); } catch (Throwable t) { buf.append(t.getMessage()); } } else { buf.append(“Unsupported command: “); buf.append(command); } } if (buf.length() > 0) { buf.append("\r\n”); } // 添加 telnet 提示语 if (prompt != null && prompt.length() > 0 && !noprompt) { buf.append(prompt); } return buf.toString(); }}该类只实现了telnet方法,其中的逻辑还是比较清晰,就是根据对应的命令去让对应的实现类产生命令结果。(三)ClearTelnetHandler该类实现了TelnetHandler接口,封装了clear命令的实现。@Activate@Help(parameter = “[lines]”, summary = “Clear screen.”, detail = “Clear screen.")public class ClearTelnetHandler implements TelnetHandler { @Override public String telnet(Channel channel, String message) { // 清除屏幕上的内容行数 int lines = 100; if (message.length() > 0) { // 如果不是一个数字 if (!StringUtils.isInteger(message)) { return “Illegal lines " + message + “, must be integer.”; } lines = Integer.parseInt(message); } StringBuilder buf = new StringBuilder(); // 一行一行清除 for (int i = 0; i < lines; i++) { buf.append("\r\n”); } return buf.toString(); }}(四)ExitTelnetHandler该类实现了TelnetHandler接口,封装了exit命令的实现。@Activate@Help(parameter = “”, summary = “Exit the telnet.”, detail = “Exit the telnet.")public class ExitTelnetHandler implements TelnetHandler { @Override public String telnet(Channel channel, String message) { // 关闭通道 channel.close(); return null; }}(五)HelpTelnetHandler该类实现了TelnetHandler接口,封装了help命令的实现。@Activate@Help(parameter = “[command]”, summary = “Show help.”, detail = “Show help.")public class HelpTelnetHandler implements TelnetHandler { /* * 扩展加载器 / private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class); @Override public String telnet(Channel channel, String message) { // 如果需要查看某一个命令的帮助 if (message.length() > 0) { if (!extensionLoader.hasExtension(message)) { return “No such command " + message; } // 获得对应的扩展实现类 TelnetHandler handler = extensionLoader.getExtension(message); Help help = handler.getClass().getAnnotation(Help.class); StringBuilder buf = new StringBuilder(); // 生成命令和帮助信息 buf.append(“Command:\r\n “); buf.append(message + " " + help.parameter().replace("\r\n”, " “).replace("\n”, " “)); buf.append("\r\nSummary:\r\n “); buf.append(help.summary().replace("\r\n”, " “).replace("\n”, " “)); buf.append("\r\nDetail:\r\n “); buf.append(help.detail().replace("\r\n”, " \r\n”).replace("\n”, " \n”)); return buf.toString(); // 如果查看所有命令的帮助 } else { List<List<String>> table = new ArrayList<List<String>>(); // 获得所有命令的提示信息 List<TelnetHandler> handlers = extensionLoader.getActivateExtension(channel.getUrl(), “telnet”); if (handlers != null && !handlers.isEmpty()) { for (TelnetHandler handler : handlers) { Help help = handler.getClass().getAnnotation(Help.class); List<String> row = new ArrayList<String>(); String parameter = " " + extensionLoader.getExtensionName(handler) + " " + (help != null ? help.parameter().replace("\r\n”, " “).replace("\n”, " “) : “”); row.add(parameter.length() > 50 ? parameter.substring(0, 50) + “…” : parameter); String summary = help != null ? help.summary().replace("\r\n”, " “).replace("\n”, " “) : “”; row.add(summary.length() > 50 ? summary.substring(0, 50) + “…” : summary); table.add(row); } } return “Please input "help [command]" show detail.\r\n” + TelnetUtils.toList(table); } }}help分为了需要查看某一个命令的帮助还是查看全部命令的帮助。(六)LogTelnetHandler该类实现了TelnetHandler接口,封装了log命令的实现。@Activate@Help(parameter = “level”, summary = “Change log level or show log “, detail = “Change log level or show log”)public class LogTelnetHandler implements TelnetHandler { public static final String SERVICE_KEY = “telnet.log”; @Override public String telnet(Channel channel, String message) { long size = 0; File file = LoggerFactory.getFile(); StringBuffer buf = new StringBuffer(); if (message == null || message.trim().length() == 0) { buf.append(“EXAMPLE: log error / log 100”); } else { String str[] = message.split(” “); if (!StringUtils.isInteger(str[0])) { // 设置日志级别 LoggerFactory.setLevel(Level.valueOf(message.toUpperCase())); } else { // 获得日志长度 int SHOW_LOG_LENGTH = Integer.parseInt(str[0]); if (file != null && file.exists()) { try { FileInputStream fis = new FileInputStream(file); try { FileChannel filechannel = fis.getChannel(); try { size = filechannel.size(); ByteBuffer bb; if (size <= SHOW_LOG_LENGTH) { // 分配缓冲区 bb = ByteBuffer.allocate((int) size); // 读日志数据 filechannel.read(bb, 0); } else { int pos = (int) (size - SHOW_LOG_LENGTH); // 分配缓冲区 bb = ByteBuffer.allocate(SHOW_LOG_LENGTH); // 读取日志数据 filechannel.read(bb, pos); } bb.flip(); String content = new String(bb.array()).replace("<”, “&lt;”) .replace(">”, “&gt;”).replace("\n”, “<br/><br/>”); buf.append("\r\ncontent:” + content); buf.append("\r\nmodified:” + (new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”) .format(new Date(file.lastModified())))); buf.append("\r\nsize:” + size + “\r\n”); } finally { filechannel.close(); } } finally { fis.close(); } } catch (Exception e) { buf.append(e.getMessage()); } } else { size = 0; buf.append("\r\nMESSAGE: log file not exists or log appender is console .”); } } } buf.append("\r\nCURRENT LOG LEVEL:" + LoggerFactory.getLevel()) .append("\r\nCURRENT LOG APPENDER:" + (file == null ? “console” : file.getAbsolutePath())); return buf.toString(); }}log命令实现原理就是从日志文件中把日志信息读取出来。(七)StatusTelnetHandler该类实现了TelnetHandler接口,封装了status命令的实现。@Activate@Help(parameter = “[-l]”, summary = “Show status.”, detail = “Show status.")public class StatusTelnetHandler implements TelnetHandler { private final ExtensionLoader<StatusChecker> extensionLoader = ExtensionLoader.getExtensionLoader(StatusChecker.class); @Override public String telnet(Channel channel, String message) { // 显示状态列表 if (message.equals("-l”)) { List<StatusChecker> checkers = extensionLoader.getActivateExtension(channel.getUrl(), “status”); String[] header = new String[]{“resource”, “status”, “message”}; List<List<String>> table = new ArrayList<List<String>>(); Map<String, Status> statuses = new HashMap<String, Status>(); if (checkers != null && !checkers.isEmpty()) { // 遍历各个资源的状态,如果一个当全部 OK 时则显示 OK,只要有一个 ERROR 则显示 ERROR,只要有一个 WARN 则显示 WARN for (StatusChecker checker : checkers) { String name = extensionLoader.getExtensionName(checker); Status stat; try { stat = checker.check(); } catch (Throwable t) { stat = new Status(Status.Level.ERROR, t.getMessage()); } statuses.put(name, stat); if (stat.getLevel() != null && stat.getLevel() != Status.Level.UNKNOWN) { List<String> row = new ArrayList<String>(); row.add(name); row.add(String.valueOf(stat.getLevel())); row.add(stat.getMessage() == null ? "" : stat.getMessage()); table.add(row); } } } Status stat = StatusUtils.getSummaryStatus(statuses); List<String> row = new ArrayList<String>(); row.add(“summary”); row.add(String.valueOf(stat.getLevel())); row.add(stat.getMessage()); table.add(row); return TelnetUtils.toTable(header, table); } else if (message.length() > 0) { return “Unsupported parameter " + message + " for status.”; } String status = channel.getUrl().getParameter(“status”); Map<String, Status> statuses = new HashMap<String, Status>(); if (status != null && status.length() > 0) { String[] ss = Constants.COMMA_SPLIT_PATTERN.split(status); for (String s : ss) { StatusChecker handler = extensionLoader.getExtension(s); Status stat; try { stat = handler.check(); } catch (Throwable t) { stat = new Status(Status.Level.ERROR, t.getMessage()); } statuses.put(s, stat); } } Status stat = StatusUtils.getSummaryStatus(statuses); return String.valueOf(stat.getLevel()); }}(八)Help该接口是帮助文档接口@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface Help { String parameter() default “”; String summary(); String detail() default “”;}可以看上在每个命令的实现类上都加上了@Help注解,为了添加一些帮助文案。(九)TelnetUtils该类是Telnet命令的工具类,其中逻辑我就不介绍了。(十)TelnetCodec该类继承了TransportCodec,是telnet的编解码类。1.属性private static final Logger logger = LoggerFactory.getLogger(TelnetCodec.class);/* * 历史命令列表 /private static final String HISTORY_LIST_KEY = “telnet.history.list”;/* * 历史命令位置,就是用上下键来找历史命令 /private static final String HISTORY_INDEX_KEY = “telnet.history.index”;/* * 向上键 /private static final byte[] UP = new byte[]{27, 91, 65};/* * 向下键 /private static final byte[] DOWN = new byte[]{27, 91, 66};/* * 回车 /private static final List<?> ENTER = Arrays.asList(new Object[]{new byte[]{’\r’, ‘\n’} / Windows Enter /, new byte[]{’\n’} / Linux Enter /});/* * 退出 /private static final List<?> EXIT = Arrays.asList(new Object[]{new byte[]{3} / Windows Ctrl+C /, new byte[]{-1, -12, -1, -3, 6} / Linux Ctrl+C /, new byte[]{-1, -19, -1, -3, 6} / Linux Pause */});2.getCharsetprivate static Charset getCharset(Channel channel) { if (channel != null) { // 获得属性设置 Object attribute = channel.getAttribute(Constants.CHARSET_KEY); // 返回指定字符集的charset对象。 if (attribute instanceof String) { try { return Charset.forName((String) attribute); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } else if (attribute instanceof Charset) { return (Charset) attribute; } URL url = channel.getUrl(); if (url != null) { String parameter = url.getParameter(Constants.CHARSET_KEY); if (parameter != null && parameter.length() > 0) { try { return Charset.forName(parameter); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } } // 默认的编码是utf-8 try { return Charset.forName(Constants.DEFAULT_CHARSET); } catch (Throwable t) { logger.warn(t.getMessage(), t); } return Charset.defaultCharset();}该方法是获得通道的字符集,根据url中编码来获得字符集,默认是utf-8。3.encode@Overridepublic void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException { // 如果需要编码的是 telnet 命令结果 if (message instanceof String) { //如果为客户端侧的通道message直接返回 if (isClientSide(channel)) { message = message + “\r\n”; } // 获得字节数组 byte[] msgData = ((String) message).getBytes(getCharset(channel).name()); // 写入缓冲区 buffer.writeBytes(msgData); } else { super.encode(channel, buffer, message); }}该方法是编码方法。4.decode@Overridepublic Object decode(Channel channel, ChannelBuffer buffer) throws IOException { // 获得缓冲区可读的字节 int readable = buffer.readableBytes(); byte[] message = new byte[readable]; // 从缓冲区读数据 buffer.readBytes(message); return decode(channel, buffer, readable, message);}@SuppressWarnings(“unchecked”)protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] message) throws IOException { // 如果是客户端侧,直接返回结果 if (isClientSide(channel)) { return toString(message, getCharset(channel)); } // 检验消息长度 checkPayload(channel, readable); if (message == null || message.length == 0) { return DecodeResult.NEED_MORE_INPUT; } // 如果回退 if (message[message.length - 1] == ‘\b’) { // Windows backspace echo try { boolean doublechar = message.length >= 3 && message[message.length - 3] < 0; // double byte char channel.send(new String(doublechar ? new byte[]{32, 32, 8, 8} : new byte[]{32, 8}, getCharset(channel).name())); } catch (RemotingException e) { throw new IOException(StringUtils.toString(e)); } return DecodeResult.NEED_MORE_INPUT; } // 如果命令是退出 for (Object command : EXIT) { if (isEquals(message, (byte[]) command)) { if (logger.isInfoEnabled()) { logger.info(new Exception(“Close channel " + channel + " on exit command: " + Arrays.toString((byte[]) command))); } // 关闭通道 channel.close(); return null; } } boolean up = endsWith(message, UP); boolean down = endsWith(message, DOWN); // 如果用上下键找历史命令 if (up || down) { LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY); if (history == null || history.isEmpty()) { return DecodeResult.NEED_MORE_INPUT; } Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY); Integer old = index; if (index == null) { index = history.size() - 1; } else { // 向上 if (up) { index = index - 1; if (index < 0) { index = history.size() - 1; } } else { // 向下 index = index + 1; if (index > history.size() - 1) { index = 0; } } } // 获得历史命令,并发送给客户端 if (old == null || !old.equals(index)) { // 设置当前命令位置 channel.setAttribute(HISTORY_INDEX_KEY, index); // 获得历史命令 String value = history.get(index); // 清除客户端原有命令,用查到的历史命令替代 if (old != null && old >= 0 && old < history.size()) { String ov = history.get(old); StringBuilder buf = new StringBuilder(); for (int i = 0; i < ov.length(); i++) { buf.append("\b”); } for (int i = 0; i < ov.length(); i++) { buf.append(" “); } for (int i = 0; i < ov.length(); i++) { buf.append("\b”); } value = buf.toString() + value; } try { channel.send(value); } catch (RemotingException e) { throw new IOException(StringUtils.toString(e)); } } // 返回,需要更多指令 return DecodeResult.NEED_MORE_INPUT; } // 关闭命令 for (Object command : EXIT) { if (isEquals(message, (byte[]) command)) { if (logger.isInfoEnabled()) { logger.info(new Exception(“Close channel " + channel + " on exit command " + command)); } channel.close(); return null; } } byte[] enter = null; // 如果命令是回车 for (Object command : ENTER) { if (endsWith(message, (byte[]) command)) { enter = (byte[]) command; break; } } if (enter == null) { return DecodeResult.NEED_MORE_INPUT; } LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY); Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY); // 移除历史命令 channel.removeAttribute(HISTORY_INDEX_KEY); // 将历史命令拼接 if (history != null && !history.isEmpty() && index != null && index >= 0 && index < history.size()) { String value = history.get(index); if (value != null) { byte[] b1 = value.getBytes(); byte[] b2 = new byte[b1.length + message.length]; System.arraycopy(b1, 0, b2, 0, b1.length); System.arraycopy(message, 0, b2, b1.length, message.length); message = b2; } } // 将命令字节数组,转成具体的一条命令 String result = toString(message, getCharset(channel)); if (result.trim().length() > 0) { if (history == null) { history = new LinkedList<String>(); channel.setAttribute(HISTORY_LIST_KEY, history); } if (history.isEmpty()) { history.addLast(result); } else if (!result.equals(history.getLast())) { history.remove(result); // 添加当前命令到历史尾部 history.addLast(result); // 超过上限,移除历史的头部 if (history.size() > 10) { history.removeFirst(); } } } return result;}该方法是编码。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了telnet的相关实现逻辑,本文有兴趣的朋友可以看看。下一篇我会讲解基于grizzly实现远程通信部分。 ...

December 23, 2018 · 9 min · jiezi

dubbo源码解析(十一)远程通信——Buffer

远程通讯——Buffer目标:介绍Buffer的相关实现逻辑、介绍dubbo-remoting-api中的buffer包内的源码解析。前言缓存区在NIO框架中非常重要,它作为字节容器,每个NIO框架都有自己的相应的设计实现。比如Java NIO有ByteBuffer的设计,Mina有IoBuffer的设计,Netty4有ByteBuf的设计。 那么在本文讲到的内容是dubbo对于缓冲区做的一些接口定义,并且做了不同的框架实现缓冲区公共的逻辑。下面是本文要讲到的类图:接下来我就按照类图上的一个一个分析,忽略test类。源码分析(一)ChannelBuffer该接口继承了Comparable接口,该接口是通道缓存接口,是字节容器,在netty中也有通道缓存的设计,也就是io.netty.buffer.ByteBuf,该接口的方法定义和设计跟ByteBuf几乎一样,连注释都一样,所以我就不再细说了。(二)AbstractChannelBuffer该类实现了ChannelBuffer接口,是通道缓存的抽象类,它实现了ChannelBuffer所有方法,但是它实现的方法都是需要被重写的方法,具体的实现都是需要子类来实现。现在我们来恶补一下这个通道缓存的原理,当然这个原理跟netty的ByteBuf原理是分不开的。AbstractChannelBuffer维护了两个索引,一个用于读取,另一个用于写入当你从通道缓存中读取时,readerIndex将会被递增已经被读取的字节数,同样的当你写入的时候writerIndex也会被递增。/** * 读索引 /private int readerIndex;/* * 写索引 /private int writerIndex;/* * 标记读索引 /private int markedReaderIndex;/* * 标记写索引 /private int markedWriterIndex;可以看到该类有四个属性,读索引和写索引的作用就是我上述介绍的,读索引和写索引的起始位置都为索引位置0。而标记读索引和标记写索引是为了做备份回滚,当对缓冲区进行读写操作时,可能需要对之前的操作进行回滚,我们就需要将当前的读写索引备份到相应的标记索引中。该类的其他方法都是利用四个属性来操作,无非就是检测是否有数据可读或者还是否有空间可写等方法,做一些前置条件的校验以及索引的设置,具体的实现都是需要子类来实现,所以我就不贴代码,因为逻辑比较简单。(三)DynamicChannelBuffer该类继承了AbstractChannelBuffer类,该类是动态的通道缓存区类,也就是该类是从ChannelBufferFactory工厂中动态的生成缓冲区,默认使用的工厂是HeapChannelBufferFactory。1.属性和构造方法/* * 通道缓存区工厂 /private final ChannelBufferFactory factory;/* * 通道缓存区 /private ChannelBuffer buffer;public DynamicChannelBuffer(int estimatedLength) { // 默认是HeapChannelBufferFactory this(estimatedLength, HeapChannelBufferFactory.getInstance());}public DynamicChannelBuffer(int estimatedLength, ChannelBufferFactory factory) { // 如果预计长度小于0 则抛出异常 if (estimatedLength < 0) { throw new IllegalArgumentException(“estimatedLength: " + estimatedLength); } // 如果工厂为空,则抛出空指针异常 if (factory == null) { throw new NullPointerException(“factory”); } // 设置工厂 this.factory = factory; // 创建缓存区 buffer = factory.getBuffer(estimatedLength);}可以看到,该类有两个属性,所有的实现方法都是调用了buffer的方法,不过该buffer产生是通过工厂动态生成的。并且从构造方法来看,默认使用HeapChannelBufferFactory。2.ensureWritableBytes@Overridepublic void ensureWritableBytes(int minWritableBytes) { // 如果最小写入的字节数不大于可写的字节数,则结束 if (minWritableBytes <= writableBytes()) { return; } // 新增容量 int newCapacity; // 此缓冲区可包含的字节数等于0。 if (capacity() == 0) { // 新增容量设置为1 newCapacity = 1; } else { // 新增容量设置为缓冲区可包含的字节数 newCapacity = capacity(); } // 最小新增容量 = 当前的写索引+最小写入的字节数 int minNewCapacity = writerIndex() + minWritableBytes; // 当新增容量比最小新增容量小 while (newCapacity < minNewCapacity) { // 新增容量左移1位,也就是加倍 newCapacity <<= 1; } // 通过工厂创建该容量大小当缓冲区 ChannelBuffer newBuffer = factory().getBuffer(newCapacity); // 从buffer中读取数据到newBuffer中 newBuffer.writeBytes(buffer, 0, writerIndex()); // 替换原来到缓冲区 buffer = newBuffer;}该方法是确保数组有可写的容量,该方法是重写了父类的方法,通过传入一个最小写入的字节数,来对缓冲区进行扩容,可以看到,当现有的缓冲区不够大的时候,会对缓冲区进行加倍对扩容,直到buffer的大小大于传入的最小可写字节数。3.copy@Overridepublic ChannelBuffer copy(int index, int length) { // 创建缓冲区,预计长度最小为64,或者更大 DynamicChannelBuffer copiedBuffer = new DynamicChannelBuffer(Math.max(length, 64), factory()); // 复制数据 copiedBuffer.buffer = buffer.copy(index, length); // 设置索引,读索引设置为0,写索引设置为copy的数据长度 copiedBuffer.setIndex(0, length); // 返回缓存区 return copiedBuffer;}该方法是复制数据,在创建缓冲区的时候,预计长度最小是64,,然后重新设置读索引写索引。其他方法都调用了buffer的方法或者调用了父类的方法,所以不再这里多说。(四)ByteBufferBackedChannelBuffer该方法继承AbstractChannelBuffer,该类是基于 Java NIO中的ByteBuffer来实现相关的读写数据等操作。/* * ByteBuffer实例 /private final ByteBuffer buffer;/* * 容量 /private final int capacity;public ByteBufferBackedChannelBuffer(ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(“buffer”); } // 创建一个新的字节缓冲区,新缓冲区的大小将是此缓冲区的剩余容量 this.buffer = buffer.slice(); // 返回buffer的剩余容量 capacity = buffer.remaining(); // 设置写索引 writerIndex(capacity);}上述就是该类的属性和构造函数,可以看到它有一个ByteBuffer类型的实例,并且capacity是buffer的剩余容量。还有其他的方法比如getByte方法是从buffer中读取数据方法,setBytes方法是把数据写入buffer,它们都有很多重载方法,为就不一一讲解了,它们都是调用了ByteBuffer中的一些方法,如果对于Java NIO中的ByteBuffer方法不是很熟悉的朋友,需要先了解一下Java NIO中的ByteBuffer。(五)HeapChannelBuffer该方法继承了AbstractChannelBuffer,该类中buffer是基于字节数组实现/* * The underlying heap byte array that this buffer is wrapping. * 此缓冲区包装的基础堆字节数组。 /protected final byte[] array;/* * Creates a new heap buffer with a newly allocated byte array. * 使用新分配的字节数组创建新的堆缓冲区。 * * @param length the length of the new byte array /public HeapChannelBuffer(int length) { this(new byte[length], 0, 0);}/* * Creates a new heap buffer with an existing byte array. * 使用现有字节数组创建新的堆缓冲区。 * * @param array the byte array to wrap /public HeapChannelBuffer(byte[] array) { this(array, 0, array.length);}/* * Creates a new heap buffer with an existing byte array. * 使用现有字节数组创建新的堆缓冲区。 * * @param array the byte array to wrap * @param readerIndex the initial reader index of this buffer * @param writerIndex the initial writer index of this buffer /protected HeapChannelBuffer(byte[] array, int readerIndex, int writerIndex) { if (array == null) { throw new NullPointerException(“array”); } this.array = array; setIndex(readerIndex, writerIndex);}该类有好几个构造函数,都是基于字节数组的,也就是在该类中包装了一个字节数组,把构造函数传入的字节数组传入到该属性中。其他方法逻辑比较简单。(六)ChannelBufferFactorypublic interface ChannelBufferFactory { /* * 获得缓冲区实例 * @param capacity * @return / ChannelBuffer getBuffer(int capacity); ChannelBuffer getBuffer(byte[] array, int offset, int length); ChannelBuffer getBuffer(ByteBuffer nioBuffer);}该接口是通道缓冲区工厂,其中就只定义了获得通道缓冲区的方法,比较好理解,它有两个实现类,我后续会讲到。(七)HeapChannelBufferFactory该类实现了ChannelBufferFactory,该类就是基于字节数组来创建缓冲区的工厂。public class HeapChannelBufferFactory implements ChannelBufferFactory { /* * 单例 / private static final HeapChannelBufferFactory INSTANCE = new HeapChannelBufferFactory(); public HeapChannelBufferFactory() { super(); } public static ChannelBufferFactory getInstance() { return INSTANCE; } @Override public ChannelBuffer getBuffer(int capacity) { // 创建一个capacity容量的缓冲区 return ChannelBuffers.buffer(capacity); } @Override public ChannelBuffer getBuffer(byte[] array, int offset, int length) { return ChannelBuffers.wrappedBuffer(array, offset, length); } @Override public ChannelBuffer getBuffer(ByteBuffer nioBuffer) { // 判断该缓冲区是否有字节数组支持 if (nioBuffer.hasArray()) { // 使用 return ChannelBuffers.wrappedBuffer(nioBuffer); } // 创建一个nioBuffer剩余容量的缓冲区 ChannelBuffer buf = getBuffer(nioBuffer.remaining()); // 记录下nioBuffer的位置 int pos = nioBuffer.position(); // 写入数据到buf buf.writeBytes(nioBuffer); // 把nioBuffer的位置重置到pos nioBuffer.position(pos); return buf; }}该类利用了单例模式,其中的方法比较简单,就是调用了ChannelBuffers中的方法,调用的方法实际上还是使用了HeapChannelBuffer中创建缓冲区的方法。(八)DirectChannelBufferFactory该类实现了ChannelBufferFactory接口,是直接缓冲区工厂,用来创建直接缓冲区。public class DirectChannelBufferFactory implements ChannelBufferFactory { /* * 单例 / private static final DirectChannelBufferFactory INSTANCE = new DirectChannelBufferFactory(); public DirectChannelBufferFactory() { super(); } public static ChannelBufferFactory getInstance() { return INSTANCE; } @Override public ChannelBuffer getBuffer(int capacity) { if (capacity < 0) { throw new IllegalArgumentException(“capacity: " + capacity); } if (capacity == 0) { return ChannelBuffers.EMPTY_BUFFER; } // 生成直接缓冲区 return ChannelBuffers.directBuffer(capacity); } @Override public ChannelBuffer getBuffer(byte[] array, int offset, int length) { if (array == null) { throw new NullPointerException(“array”); } if (offset < 0) { throw new IndexOutOfBoundsException(“offset: " + offset); } if (length == 0) { return ChannelBuffers.EMPTY_BUFFER; } if (offset + length > array.length) { throw new IndexOutOfBoundsException(“length: " + length); } ChannelBuffer buf = getBuffer(length); buf.writeBytes(array, offset, length); return buf; } @Override public ChannelBuffer getBuffer(ByteBuffer nioBuffer) { // 如果nioBuffer不是只读,并且它是直接缓冲区 if (!nioBuffer.isReadOnly() && nioBuffer.isDirect()) { // 创建一个缓冲区 return ChannelBuffers.wrappedBuffer(nioBuffer); } // 创建一个nioBuffer剩余容量的缓冲区 ChannelBuffer buf = getBuffer(nioBuffer.remaining()); // 记录下nioBuffer的位置 int pos = nioBuffer.position(); // 写入数据到buf buf.writeBytes(nioBuffer); // 把nioBuffer的位置重置到pos nioBuffer.position(pos); return buf; }该类中的实现方式与HeapChannelBufferFactory中的实现方式差不多,唯一的区别就是它创建的是一个直接缓冲区。(九)ChannelBuffers该类是缓冲区的工具类,提供创建、比较 ChannelBuffer 等公用方法。我在这里举两个方法来讲:public static ChannelBuffer wrappedBuffer(ByteBuffer buffer) { // 如果缓冲区没有剩余容量 if (!buffer.hasRemaining()) { return EMPTY_BUFFER; } // 如果是字节数组生成的缓冲区 if (buffer.hasArray()) { // 使用buffer的字节数组生成一个新的缓冲区 return wrappedBuffer(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); } else { // 基于ByteBuffer创建一个缓冲区(利用buffer的剩余容量创建) return new ByteBufferBackedChannelBuffer(buffer); }}该方法通过buffer来创建一个新的缓冲区。可以看出来调用的就是上述生成缓冲区的三个类中的方法,ChannelBuffers中很多方法都是这样去实现的,逻辑比较简单。public static boolean equals(ChannelBuffer bufferA, ChannelBuffer bufferB) { // 获得bufferA的可读数据 final int aLen = bufferA.readableBytes(); // 如果两个缓冲区的可读数据大小不一样,则不是同一个 if (aLen != bufferB.readableBytes()) { return false; } final int byteCount = aLen & 7; // 获得两个比较的缓冲区的读索引 int aIndex = bufferA.readerIndex(); int bIndex = bufferB.readerIndex(); // 最多比较缓冲区中的7个数据 for (int i = byteCount; i > 0; i–) { // 一旦有一个数据不一样,则不是同一个 if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) { return false; } aIndex++; bIndex++; } return true;}该方法就是比较两个缓冲区是否为同一个,重写了equals。(十)ChannelBufferOutputStream该类继承了OutputStream1.属性和构造方法/* * 缓冲区 /private final ChannelBuffer buffer;/* * 记录开始写入的索引 /private final int startIndex;public ChannelBufferOutputStream(ChannelBuffer buffer) { if (buffer == null) { throw new NullPointerException(“buffer”); } this.buffer = buffer; // 把开始写入数据的索引记录下来 startIndex = buffer.writerIndex();}该类中包装了一个缓冲区对象和startIndex,startIndex是记录开始写入的索引。2.writtenBytespublic int writtenBytes() { return buffer.writerIndex() - startIndex;}该方法是返回写入了多少数据。该类里面还有write方法,都是调用了buffer.writeBytes。(十一)ChannelBufferInputStream该类继承了InputStream1.属性和构造函数/* * 缓冲区 /private final ChannelBuffer buffer;/* * 记录开始读数据的索引 /private final int startIndex;/* * 结束读数据的索引 */private final int endIndex;public ChannelBufferInputStream(ChannelBuffer buffer) { this(buffer, buffer.readableBytes());}public ChannelBufferInputStream(ChannelBuffer buffer, int length) { if (buffer == null) { throw new NullPointerException(“buffer”); } if (length < 0) { throw new IllegalArgumentException(“length: " + length); } if (length > buffer.readableBytes()) { throw new IndexOutOfBoundsException(); } this.buffer = buffer; // 记录开始读数据的索引 startIndex = buffer.readerIndex(); // 设置结束读数据的索引 endIndex = startIndex + length; // 标记读索引 buffer.markReaderIndex();}该类里面包装了读开始索引和结束索引,并且在构造方法中初始化这些属性。2.readBytespublic int readBytes() { return buffer.readerIndex() - startIndex;}该方法是返回读了多少数据。3.available@Overridepublic int available() throws IOException { return endIndex - buffer.readerIndex();}该方法是返回还剩多少数据没读4.read@Overridepublic int read() throws IOException { if (!buffer.readable()) { return -1; } return buffer.readByte() & 0xff;}@Overridepublic int read(byte[] b, int off, int len) throws IOException { // 判断是否还有数据可读 int available = available(); if (available == 0) { return -1; } // 获得需要读取的数据长度 len = Math.min(available, len); buffer.readBytes(b, off, len); return len;}该方法是读数据,返回读了数据长度。5.skip@Overridepublic long skip(long n) throws IOException { if (n > Integer.MAX_VALUE) { return skipBytes(Integer.MAX_VALUE); } else { return skipBytes((int) n); }}private int skipBytes(int n) throws IOException { int nBytes = Math.min(available(), n); // 跳过一些数据 buffer.skipBytes(nBytes); return nBytes;}该方法是跳过n长度来读数据。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了Buffer的相关实现逻辑,我很多方法都没有贴出源码,因为很多都是基于Java NIO的ByteBuffer都设计实现,并且要注意AbstractChannelBuffer的三个子类,也就是生成缓冲区的三种形式,还有就是要注意两个创建缓冲区实例的工厂。下一篇我会讲解telnet部分。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。 ...

December 23, 2018 · 5 min · jiezi

Dubbo分析之Registry层

前言紧接上文Dubbo分析之Cluster层,本文继续分析dubbo的register层;此层封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory, Registry, RegistryService;Registry接口接口定义如下:public interface Registry extends Node, RegistryService {} public interface RegistryService { void register(URL url); void unregister(URL url); void subscribe(URL url, NotifyListener listener); void unsubscribe(URL url, NotifyListener listener); List<URL> lookup(URL url); }主要提供了注册(register),注销(unregister),订阅(subscribe),退订(unsubscribe)等功能;dubbo提供了多种注册方式分别是:Multicast ,Zookeeper,Redis以及Simple方式;Multicast:Multicast注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现;Zookeeper:Zookeeper是Apacahe Hadoop的子项目,是一个树型的目录服务,支持变更推送,适合作为Dubbo服务的注册中心,工业强度较高,可用于生产环境,并推荐使用;Redis:基于Redis实现的注册中心,使用 Redis的Publish/Subscribe事件通知数据变更;Simple:Simple注册中心本身就是一个普通的Dubbo服务,可以减少第三方依赖,使整体通讯方式一致;后面重点介绍官方推荐的Zookeeper注册方式;具体的Register是在RegistryFactory中生成的,具体看一下接口定义;RegistryFactory接口接口定义如下:@SPI(“dubbo”)public interface RegistryFactory { @Adaptive({“protocol”}) Registry getRegistry(URL url); }RegistryFactory提供了SPI扩展,默认使用dubbo,具体有哪些扩展可以查看META-INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory:dubbo=com.alibaba.dubbo.registry.dubbo.DubboRegistryFactorymulticast=com.alibaba.dubbo.registry.multicast.MulticastRegistryFactoryzookeeper=com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactoryredis=com.alibaba.dubbo.registry.redis.RedisRegistryFactory已推荐使用的Zookeeper为实例,查看ZookeeperRegistryFactory,提供了createRegistry方法:private ZookeeperTransporter zookeeperTransporter; public Registry createRegistry(URL url) { return new ZookeeperRegistry(url, zookeeperTransporter);}实例化ZookeeperRegistry,两个参数分别是url和zookeeperTransporter,zookeeperTransporter是操作Zookeeper的客户端组件包括:zkclient和curator两种方式@SPI(“curator”)public interface ZookeeperTransporter { @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) ZookeeperClient connect(URL url); }ZookeeperTransporter同样提供了SPI扩展,默认使用curator方式;接下来重点看一下Zookeeper注册中心。Zookeeper注册中心1.整体设计流程在dubbo的整体设计中,可以大致查看Registry层的大致流程,首先通过RegistryFactory实例化Registry,Registry可以接收RegistryProtocol传过来的注册(register)和订阅(subscribe)消息,然后Registry通过ZKClient来向Zookeeper指定的目录下写入url信息,如果是订阅消息Registry会通过NotifyListener来通知RegitryDirctory进行更新url,最后就是Cluster层通过路由,负载均衡选择具体的提供方;2.Zookeeper注册中心流程官方提供了dubbo在Zookeeper中心的流程图:流程说明:服务提供者启动时: 向/dubbo/com.foo.BarService/providers目录下写入自己的URL地址;服务消费者启动时: 订阅/dubbo/com.foo.BarService/providers目录下的提供者URL地址;并向/dubbo/com.foo.BarService/consumers目录下写入自己的URL地址;监控中心启动时: 订阅/dubbo/com.foo.BarService 目录下的所有提供者和消费者URL地址。下面分别从注册(register),注销(unregister),订阅(subscribe),退订(unsubscribe)四个方面来分析3.注册(register)ZookeeperRegistry的父类FailbackRegistry中实现了register方法,FailbackRegistry从名字可以看出来具有:失败自动恢复,后台记录失败请求,定时重发功能;下面具体看一下register方法:public void register(URL url) { super.register(url); failedRegistered.remove(url); failedUnregistered.remove(url); try { // Sending a registration request to the server side doRegister(url); } catch (Exception e) { Throwable t = e; // 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) && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol()); boolean skipFailback = t instanceof SkipFailbackWrapperException; if (check || skipFailback) { if (skipFailback) { t = t.getCause(); } throw new IllegalStateException(“Failed to register " + url + " to registry " + getUrl().getAddress() + “, cause: " + t.getMessage(), t); } else { logger.error(“Failed to register " + url + “, waiting for retry, cause: " + t.getMessage(), t); } // Record a failed registration request to a failed list, retry regularly failedRegistered.add(url); } }后台记录了失败的请求,包括failedRegistered和failedUnregistered,注册的时候将里面存放的url删除,然后执行doRegister方法,此方式在ZookeeperRegistry中实现,主要是在Zookeeper指定的目录下写入url信息,如果失败会记录注册失败的url,等待自动恢复;doRegister相关代码如下:protected void doRegister(URL url) { try { zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException(“Failed to register " + url + " to zookeeper " + getUrl() + “, cause: " + e.getMessage(), e); }}调用zkClient的create方法在Zookeeper上创建节点,默认创建临时节点,create方法在AbstractZookeeperClient中实现,具体源码如下:public void create(String path, boolean ephemeral) { if (!ephemeral) { if (checkExists(path)) { return; } } int i = path.lastIndexOf(’/’); if (i > 0) { create(path.substring(0, i), false); } if (ephemeral) { createEphemeral(path); } else { createPersistent(path); } }path指定需要创建的目录,ephemeral指定是否是创建临时节点,并且提供了递归创建目录,除了叶子目录其他目录都是持久化的;可以发现不管是创建临时目录还是持久化目录,都没有指定目录的Data,所有使用的是默认值,也就是本地ip地址;实例中创建的目录如下:/dubbo/com.dubboApi.DemoService/providers/dubbo%3A%2F%2F10.13.83.7%3A20880%2Fcom.dubboApi.DemoService%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.dubboApi.DemoService%26methods%3DsyncSayHello%2CsayHello%2CasyncSayHello%26pid%3D13252%26serialization%3Dprotobuf%26side%3Dprovider%26timestamp%3D1545297239027dubbo是一个根节点,然后是service名称,providers是固定的一个类型,如果是消费端这里就是consumers,最后就是一个临时节点;使用临时节点的目的就是提供者出现断电等异常停机时,注册中心能自动删除提供者信息;可以通过如下方法查询当前的目录节点信息:public class CuratorTest { static String path = “/dubbo”; static CuratorFramework client = CuratorFrameworkFactory.builder().connectString(“127.0.0.1:2181”) .sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).build(); public static void main(String[] args) throws Exception { client.start(); List<String> paths = listChildren(path); for (String path : paths) { Stat stat = new Stat(); System.err.println( “path:” + path + “,value:” + new String(client.getData().storingStatIn(stat).forPath(path))); } } private static List<String> listChildren(String path) throws Exception { List<String> pathList = new ArrayList<String>(); pathList.add(path); List<String> list = client.getChildren().forPath(path); if (list != null && list.size() > 0) { for (String cPath : list) { String temp = “”; if (”/".equals(path)) { temp = path + cPath; } else { temp = path + “/” + cPath; } pathList.addAll(listChildren(temp)); } } return pathList; }}递归遍历/dubbo目录下的所有子目录,同时将节点存储的数据都查询出来,结果如下:path:/dubbo,value:10.13.83.7path:/dubbo/com.dubboApi.DemoService,value:10.13.83.7path:/dubbo/com.dubboApi.DemoService/configurators,value:10.13.83.7path:/dubbo/com.dubboApi.DemoService/providers,value:10.13.83.7path:/dubbo/com.dubboApi.DemoService/providers/dubbo%3A%2F%2F10.13.83.7%3A20880%2Fcom.dubboApi.DemoService%3Fanyhost%3Dtrue%26application%3Dhello-world-app%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.dubboApi.DemoService%26methods%3DsyncSayHello%2CsayHello%2CasyncSayHello%26pid%3D4712%26serialization%3Dprotobuf%26side%3Dprovider%26timestamp%3D1545358401966,value:10.13.83.7除了最后一个节点是临时节点,其他都是持久化的;4.注销(unregister)同样在父类FailbackRegistry中实现了unregister方法,代码如下:public void unregister(URL url) { super.unregister(url); failedRegistered.remove(url); failedUnregistered.remove(url); try { // Sending a cancellation request to the server side doUnregister(url); } catch (Exception e) { Throwable t = e; // 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) && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol()); boolean skipFailback = t instanceof SkipFailbackWrapperException; if (check || skipFailback) { if (skipFailback) { t = t.getCause(); } throw new IllegalStateException(“Failed to unregister " + url + " to registry " + getUrl().getAddress() + “, cause: " + t.getMessage(), t); } else { logger.error(“Failed to uregister " + url + “, waiting for retry, cause: " + t.getMessage(), t); } // Record a failed registration request to a failed list, retry regularly failedUnregistered.add(url); } }注销时同样删除了failedRegistered和failedUnregistered存放的url,然后调用doUnregister,删除Zookeeper中的目录节点,失败的情况下会存储在failedUnregistered中,等待重试;protected void doUnregister(URL url) { try { zkClient.delete(toUrlPath(url)); } catch (Throwable e) { throw new RpcException(“Failed to unregister " + url + " to zookeeper " + getUrl() + “, cause: " + e.getMessage(), e); }} //CuratorZookeeperClient删除操作public void delete(String path) { try { client.delete().forPath(path); } catch (NoNodeException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); }}直接使用CuratorZookeeperClient中的delete方法删除临时节点;5.订阅(subscribe)服务消费者启动时,会先向Zookeeper注册消费者节点信息,然后订阅…/providers目录下提供者的URL地址;消费端也同样需要注册节点信息,是因为监控中心需要对服务端和消费端都进行监控;下面重点看一下订阅的相关代码,在父类FailbackRegistry中实现了subscribe方法: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 (urls != null && !urls.isEmpty()) { notify(url, listener, urls); logger.error(“Failed to subscribe " + url + “, Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.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); } }类似的格式,同样存储了失败了订阅url信息,重点看ZookeeperRegistry中的doSubscribe方法:private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>(); protected void doSubscribe(final URL url, final NotifyListener listener) { try { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { String root = toRootPath(); ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { @Override public void childChanged(String parentPath, List<String> currentChilds) { for (String child : currentChilds) { child = URL.decode(child); if (!anyServices.contains(child)) { anyServices.add(child); subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, Constants.CHECK_KEY, String.valueOf(false)), listener); } } } }); zkListener = listeners.get(listener); } zkClient.create(root, false); List<String> services = zkClient.addChildListener(root, zkListener); if (services != null && !services.isEmpty()) { for (String service : services) { service = URL.decode(service); anyServices.add(service); subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, Constants.CHECK_KEY, String.valueOf(false)), listener); } } } else { List<URL> urls = new ArrayList<URL>(); for (String path : toCategoriesPath(url)) { ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { @Override public void childChanged(String parentPath, List<String> currentChilds) { ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } zkClient.create(path, false); List<String> children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } notify(url, listener, urls); } } catch (Throwable e) { throw new RpcException(“Failed to subscribe " + url + " to zookeeper " + getUrl() + “, cause: " + e.getMessage(), e); } }在ZookeeperRegistry中定义了一个zkListeners变量,每个URL对应了一个map;map里面分别是NotifyListener和ChildListener的对应关系,消费端订阅时这里的NotifyListener其实就是RegistryDirectory,ChildListener是一个内部类,用来在监听的节点发生变更时,通知对应的消费端,具体的监听处理是在zkClient.addChildListener中实现的:public List<String> addChildListener(String path, final ChildListener listener) { ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.get(path); if (listeners == null) { childListeners.putIfAbsent(path, new ConcurrentHashMap<ChildListener, TargetChildListener>()); listeners = childListeners.get(path); } TargetChildListener targetListener = listeners.get(listener); if (targetListener == null) { listeners.putIfAbsent(listener, createTargetChildListener(path, listener)); targetListener = listeners.get(listener); } return addTargetChildListener(path, targetListener);} public CuratorWatcher createTargetChildListener(String path, ChildListener listener) { return new CuratorWatcherImpl(listener);} public List<String> addTargetChildListener(String path, CuratorWatcher listener) { try { return client.getChildren().usingWatcher(listener).forPath(path); } catch (NoNodeException e) { return null; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); }} private class CuratorWatcherImpl implements CuratorWatcher { private volatile ChildListener listener; public CuratorWatcherImpl(ChildListener listener) { this.listener = listener; } public void unwatch() { this.listener = null; } @Override public void process(WatchedEvent event) throws Exception { if (listener != null) { String path = event.getPath() == null ? "” : event.getPath(); listener.childChanged(path, StringUtils.isNotEmpty(path) ? client.getChildren().usingWatcher(this).forPath(path) : Collections.<String>emptyList()); } }}CuratorWatcherImpl实现了Zookeeper的监听接口CuratorWatcher,用来在节点有变更时通知对应的ChildListener,这样ChildListener就可以通知RegistryDirectory进行更新数据;6.退订(unsubscribe)在父类FailbackRegistry中实现了unsubscribe方法public void unsubscribe(URL url, NotifyListener listener) { super.unsubscribe(url, listener); removeFailedSubscribed(url, listener); try { // Sending a canceling subscription request to the server side doUnsubscribe(url, listener); } catch (Exception e) { Throwable t = e; // 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 unsubscribe " + url + " to registry " + getUrl().getAddress() + “, cause: " + t.getMessage(), t); } else { logger.error(“Failed to unsubscribe " + url + “, waiting for retry, cause: " + t.getMessage(), t); } // Record a failed registration request to a failed list, retry regularly Set<NotifyListener> listeners = failedUnsubscribed.get(url); if (listeners == null) { failedUnsubscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>()); listeners = failedUnsubscribed.get(url); } listeners.add(listener); } }同样使用failedUnsubscribed用来存储失败退订的url,具体看ZookeeperRegistry中的doUnsubscribe方法protected void doUnsubscribe(URL url, NotifyListener listener) { ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url); if (listeners != null) { ChildListener zkListener = listeners.get(listener); if (zkListener != null) { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { String root = toRootPath(); zkClient.removeChildListener(root, zkListener); } else { for (String path : toCategoriesPath(url)) { zkClient.removeChildListener(path, zkListener); } } } } }退订就比较简单了,只需要移除监听器就可以了;7.失败重试FailbackRegistry从名字可以看出来具有:失败自动恢复,后台记录失败请求,定时重发功能;在FailbackRegistry的构造器中启动了一个定时器:this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { // Check and connect to the registry try { retry(); } catch (Throwable t) { // Defensive fault tolerance logger.error(“Unexpected error occur at failed retry, cause: " + t.getMessage(), t); } } }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);实例化了一个间隔5秒执行一次重试的定时器,retry部分代码如下:protected void retry() { if (!failedRegistered.isEmpty()) { Set<URL> failed = new HashSet<URL>(failedRegistered); if (failed.size() > 0) { if (logger.isInfoEnabled()) { logger.info(“Retry register " + failed); } try { for (URL url : failed) { try { doRegister(url); failedRegistered.remove(url); } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry logger.warn(“Failed to retry register " + failed + “, waiting for again, cause: " + t.getMessage(), t); } } } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry logger.warn(“Failed to retry register " + failed + “, waiting for again, cause: " + t.getMessage(), t); } } } …省略…}定期检查是否存在失败的注册(register),注销(unregister),订阅(subscribe),退订(unsubscribe)URL,如果存在则重试;总结本文首先介绍了RegistryFactory, Registry, RegistryService几个核心接口,然后以Zookeeper为注册中心重点介绍了注册(register),注销(unregister),订阅(subscribe),退订(unsubscribe)方式。示例代码地址https://github.com/ksfzhaohui…https://gitee.com/OutOfMemory… ...

December 21, 2018 · 7 min · jiezi

dubbo源码解析(十)远程通信——Exchange层

远程通讯——Exchange层目标:介绍Exchange层的相关设计和逻辑、介绍dubbo-remoting-api中的exchange包内的源码解析。前言上一篇文章我讲的是dubbo框架设计中Transport层,这篇文章我要讲的是它的上一层Exchange层,也就是信息交换层。官方文档对这一层的解释是封装请求响应模式,同步转异步,以 Request, Response为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。这一层的设计意图是什么?它应该算是在信息传输层上又做了部分装饰,为了适应rpc调用的一些需求,比如rpc调用中一次请求只关心它所对应的响应,这个时候只是一个message消息传输过来,是无法区分这是新的请求还是上一个请求的响应,这种类似于幂等性的问题以及rpc异步处理返回结果、内置事件等特性都是在Transport层无法解决满足的,所有在Exchange层讲message分成了request和response两种类型,并且在这两个模型上增加一些系统字段来处理问题。具体我会在下面讲到。而dubbo把一条消息分为了协议头和内容两部分:协议头包括系统字段,例如编号等,内容包括具体请求的参数和响应的结果等。在exchange层中大量逻辑都是基于协议头的。现在对这一层的设计意图大致应该有所了解了吧,现在来看看exchange的类图:我讲解的顺序还是按照类图从上而下,分块讲解,忽略绿色的test类。源码解析(一)ExchangeChannelpublic interface ExchangeChannel extends Channel { ResponseFuture request(Object request) throws RemotingException; ResponseFuture request(Object request, int timeout) throws RemotingException; ExchangeHandler getExchangeHandler(); @Override void close(int timeout);}该接口是信息交换通道接口,有四个方法,前两个是发送请求消息,区别就是第二个发送请求有超时的参数,getExchangeHandler方法就是返回一个信息交换处理器,第四个是需要覆写父类的方法。(二)HeaderExchangeChannel该类实现了ExchangeChannel,是基于协议头的信息交换通道。1.属性private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeChannel.class);/** * 通道的key值 /private static final String CHANNEL_KEY = HeaderExchangeChannel.class.getName() + “.CHANNEL”;/* * 通道 /private final Channel channel;/* * 是否关闭 /private volatile boolean closed = false;上述属性比较简单,还是放一下这个类的属性是因为该类中有channel属性,也就是说HeaderExchangeChannel是Channel的装饰器,每个实现方法都会调用channel的方法。2.静态方法static HeaderExchangeChannel getOrAddChannel(Channel ch) { if (ch == null) { return null; } // 获得通道中的HeaderExchangeChannel HeaderExchangeChannel ret = (HeaderExchangeChannel) ch.getAttribute(CHANNEL_KEY); if (ret == null) { // 创建一个HeaderExchangeChannel实例 ret = new HeaderExchangeChannel(ch); // 如果通道连接 if (ch.isConnected()) { // 加入属性值 ch.setAttribute(CHANNEL_KEY, ret); } } return ret;}static void removeChannelIfDisconnected(Channel ch) { // 如果通道断开连接 if (ch != null && !ch.isConnected()) { // 移除属性值 ch.removeAttribute(CHANNEL_KEY); }}该静态方法做了HeaderExchangeChannel的创建和销毁,并且生命周期随channel销毁而销毁。3.send@Overridepublic void send(Object message) throws RemotingException { send(message, getUrl().getParameter(Constants.SENT_KEY, false));}@Overridepublic void send(Object message, boolean sent) throws RemotingException { // 如果通道关闭,抛出异常 if (closed) { throw new RemotingException(this.getLocalAddress(), null, “Failed to send message " + message + “, cause: The channel " + this + " is closed!”); } // 判断消息的类型 if (message instanceof Request || message instanceof Response || message instanceof String) { // 发送消息 channel.send(message, sent); } else { // 新建一个request实例 Request request = new Request(); // 设置信息的版本 request.setVersion(Version.getProtocolVersion()); // 该请求不需要响应 request.setTwoWay(false); // 把消息传入 request.setData(message); // 发送消息 channel.send(request, sent); }}该方法是在channel的send方法上加上了request和response模型,最后再调用channel.send,起到了装饰器的作用。4.request@Overridepublic ResponseFuture request(Object request) throws RemotingException { return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));}@Overridepublic ResponseFuture request(Object request, int timeout) throws RemotingException { // 如果通道关闭,则抛出异常 if (closed) { throw new RemotingException(this.getLocalAddress(), null, “Failed to send request " + request + “, cause: The channel " + this + " is closed!”); } // create request.创建请求 Request req = new Request(); // 设置版本号 req.setVersion(Version.getProtocolVersion()); // 设置需要响应 req.setTwoWay(true); // 把请求数据传入 req.setData(request); // 创建DefaultFuture对象,可以从future中主动获得请求对应的响应信息 DefaultFuture future = new DefaultFuture(channel, req, timeout); try { // 发送请求消息 channel.send(req); } catch (RemotingException e) { future.cancel(); throw e; } return future;}该方法是请求方法,用Request模型把请求内容装饰起来,然后发送一个Request类型的消息,并且返回DefaultFuture实例,DefaultFuture我会在后面讲到。cloes方法也重写了,我就不再多说,因为比较简单,没有重点,其他方法都是直接调用channel属性的方法。(三)ExchangeClient该接口继承了Client和ExchangeChannel,是信息交换客户端接口,其中没有定义多余的方法。(四)HeaderExchangeClient 该类实现了ExchangeClient接口,是基于协议头的信息交互客户端类,同样它是Client、Channel的适配器。在该类的源码中可以看到所有的实现方法都是调用了client和channel属性的方法。该类主要的作用就是增加了心跳功能,为什么要增加心跳功能呢,对于长连接,一些拔网线等物理层的断开,会导致TCP的FIN消息来不及发送,对方收不到断开事件,那么就需要用到发送心跳包来检测连接是否断开。consumer和provider断开,处理措施不一样,会分别做出重连和关闭通道的操作。1.属性private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeClient.class);/* * 定时器线程池 /private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory(“dubbo-remoting-client-heartbeat”, true));/* * 客户端 /private final Client client;/* * 信息交换通道 /private final ExchangeChannel channel;// heartbeat timer/* * 心跳定时器 /private ScheduledFuture<?> heartbeatTimer;// heartbeat(ms), default value is 0 , won’t execute a heartbeat./* * 心跳周期,间隔多久发送心跳消息检测一次 /private int heartbeat;/* * 心跳超时时间 /private int heartbeatTimeout;该类的属性除了需要适配的属性外,其他都是跟心跳相关属性。2.构造函数public HeaderExchangeClient(Client client, boolean needHeartbeat) { if (client == null) { throw new IllegalArgumentException(“client == null”); } this.client = client; // 创建信息交换通道 this.channel = new HeaderExchangeChannel(client); // 获得dubbo版本 String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY); //获得心跳周期配置,如果没有配置,并且dubbo是1.0版本的,则这只为1分钟,否则设置为0 this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith(“1.0.”) ? Constants.DEFAULT_HEARTBEAT : 0); // 获得心跳超时配置,默认是心跳周期的三倍 this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3); // 如果心跳超时时间小于心跳周期的两倍,则抛出异常 if (heartbeatTimeout < heartbeat * 2) { throw new IllegalStateException(“heartbeatTimeout < heartbeatInterval * 2”); } if (needHeartbeat) { // 开启心跳 startHeartbeatTimer(); }}构造函数就是对一些属性初始化设置,优先从url中获取。心跳超时时间小于心跳周期的两倍就抛出异常,意思就是至少重试两次心跳检测。3.startHeartbeatTimerprivate void startHeartbeatTimer() { // 停止现有的心跳线程 stopHeartbeatTimer(); // 如果需要心跳 if (heartbeat > 0) { // 创建心跳定时器 heartbeatTimer = scheduled.scheduleWithFixedDelay( // 新建一个心跳线程 new HeartBeatTask(new HeartBeatTask.ChannelProvider() { @Override public Collection<Channel> getChannels() { // 返回一个只包含HeaderExchangeClient对象的不可变列表 return Collections.<Channel>singletonList(HeaderExchangeClient.this); } }, heartbeat, heartbeatTimeout), heartbeat, heartbeat, TimeUnit.MILLISECONDS); }}该方法就是开启心跳。利用心跳定时器来做到定时检测心跳。因为这是信息交换客户端类,所有这里的只是返回包含HeaderExchangeClient对象的不可变列表,因为客户端跟channel是一一对应的,只有这一个该客户端本身的channel需要心跳。4.stopHeartbeatTimerprivate void stopHeartbeatTimer() { if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) { try { // 取消定时器 heartbeatTimer.cancel(true); // 取消大量已排队任务,用于回收空间 scheduled.purge(); } catch (Throwable e) { if (logger.isWarnEnabled()) { logger.warn(e.getMessage(), e); } } } heartbeatTimer = null;}该方法是停止现有心跳,也就是停止定时器,释放空间。其他方法都是调用channel和client属性的方法。(五)HeartBeatTask该类实现了Runnable接口,实现的是心跳任务,里面包含了核心的心跳策略。1.属性/* * 通道管理 /private ChannelProvider channelProvider;/* * 心跳间隔 单位:ms /private int heartbeat;/* * 心跳超时时间 单位:ms /private int heartbeatTimeout;后两个属性跟HeaderExchangeClient中的属性含义一样,第一个是该类自己内部的一个接口:interface ChannelProvider { // 获得所有的通道集合,需要心跳的通道数组 Collection<Channel> getChannels();}该接口就定义了一个方法,获得需要心跳的通道集合。可想而知,会对集合内的通道都做心跳检测。2.run@Overridepublic void run() { try { long now = System.currentTimeMillis(); // 遍历所有通道 for (Channel channel : channelProvider.getChannels()) { // 如果通道关闭了,则跳过 if (channel.isClosed()) { continue; } try { // 最后一次接收到消息的时间戳 Long lastRead = (Long) channel.getAttribute( HeaderExchangeHandler.KEY_READ_TIMESTAMP); // 最后一次发送消息的时间戳 Long lastWrite = (Long) channel.getAttribute( HeaderExchangeHandler.KEY_WRITE_TIMESTAMP); // 如果最后一次接收或者发送消息到时间到现在的时间间隔超过了心跳间隔时间 if ((lastRead != null && now - lastRead > heartbeat) || (lastWrite != null && now - lastWrite > heartbeat)) { // 创建一个request Request req = new Request(); // 设置版本号 req.setVersion(Version.getProtocolVersion()); // 设置需要得到响应 req.setTwoWay(true); // 设置事件类型,为心跳事件 req.setEvent(Request.HEARTBEAT_EVENT); // 发送心跳请求 channel.send(req); if (logger.isDebugEnabled()) { logger.debug(“Send heartbeat to remote channel " + channel.getRemoteAddress() + “, cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + “ms”); } } // 如果最后一次接收消息的时间到现在已经超过了超时时间 if (lastRead != null && now - lastRead > heartbeatTimeout) { logger.warn(“Close channel " + channel + “, because heartbeat read idle time out: " + heartbeatTimeout + “ms”); // 如果该通道是客户端,也就是请求的服务器挂掉了,客户端尝试重连服务器 if (channel instanceof Client) { try { // 重新连接服务器 ((Client) channel).reconnect(); } catch (Exception e) { //do nothing } } else { // 如果不是客户端,也就是是服务端返回响应给客户端,但是客户端挂掉了,则服务端关闭客户端连接 channel.close(); } } } catch (Throwable t) { logger.warn(“Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t); } } } catch (Throwable t) { logger.warn(“Unhandled exception when heartbeat, cause: " + t.getMessage(), t); }}该方法中是心跳机制的核心逻辑。注意以下几个点:如果需要心跳的通道本身如果关闭了,那么跳过,不添加心跳机制。无论是接收消息还是发送消息,只要超过了设置的心跳间隔,就发送心跳消息来测试是否断开如果最后一次接收到消息到到现在已经超过了心跳超时时间,那就认定对方的确断开,分两种情况来处理对方断开的情况。分别是服务端断开,客户端重连以及客户端断开,服务端断开这个客户端的连接。,这里要好好品味一下谁是发送方,谁在等谁的响应,苦苦没有等到。(六)ResponseFuturepublic interface ResponseFuture { Object get() throws RemotingException; Object get(int timeoutInMillis) throws RemotingException; void setCallback(ResponseCallback callback); boolean isDone();}该接口是响应future接口,该接口的设计意图跟java.util.concurrent.Future很类似。发送出去的消息,泼出去的水,只有等到对方主动响应才能得到结果,但是请求方需要去主动回去该请求的结果,就显得有些艰难,所有产生了这样一个接口,它能够获取任务执行结果、可以核对请求消息是否被响应,还能设置回调来支持异步。(七)DefaultFuture该类实现了ResponseFuture接口,其中封装了处理响应的逻辑。你可以把DefaultFuture看成是一个中介,买房和卖房都通过这个中介进行沟通,中介拥有着买房者的信息request和卖房者的信息response,并且促成他们之间的买卖。1.属性private static final Logger logger = LoggerFactory.getLogger(DefaultFuture.class);/* * 通道集合 /private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();/* * Future集合,key为请求编号 /private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();// invoke id./* * 请求编号 /private final long id;/* * 通道 /private final Channel channel;/* * 请求 /private final Request request;/* * 超时 /private final int timeout;/* * 锁 /private final Lock lock = new ReentrantLock();/* * 完成情况,控制多线程的休眠与唤醒 /private final Condition done = lock.newCondition();/* * 创建开始时间 /private final long start = System.currentTimeMillis();/* * 发送请求时间 /private volatile long sent;/* * 响应 /private volatile Response response;/* * 回调 /private volatile ResponseCallback callback;可以看到,该类的属性包含了request、response、channel三个实例,在该类中,把请求和响应通过唯一的id一一对应起来。做到异步处理返回结果时能给准确的返回给对应的请求。可以看到属性中有两个集合,分别是通道集合和future集合,也就是该类本身也是所有 DefaultFuture 的管理容器。2.构造函数public DefaultFuture(Channel channel, Request request, int timeout) { this.channel = channel; this.request = request; // 设置请求编号 this.id = request.getId(); this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // put into waiting map.,加入到等待集合中 FUTURES.put(id, this); CHANNELS.put(id, channel);}构造函数比较简单,每一个DefaultFuture实例都跟每一个请求一一对应,被存入到集合中管理起来。3.closeChannelpublic static void closeChannel(Channel channel) { // 遍历通道集合 for (long id : CHANNELS.keySet()) { if (channel.equals(CHANNELS.get(id))) { // 通过请求id获得future DefaultFuture future = getFuture(id); if (future != null && !future.isDone()) { // 创建一个关闭通道的响应 Response disconnectResponse = new Response(future.getId()); disconnectResponse.setStatus(Response.CHANNEL_INACTIVE); disconnectResponse.setErrorMessage(“Channel " + channel + " is inactive. Directly return the unFinished request : " + future.getRequest()); // 接收该关闭通道并且请求未完成的响应 DefaultFuture.received(channel, disconnectResponse); } } }}该方法是关闭不活跃的通道,并且返回请求未完成。也就是关闭指定channel的请求,返回的是请求未完成。4.receivedpublic static void received(Channel channel, Response response) { try { // future集合中移除该请求的future,(响应id和请求id一一对应的) DefaultFuture future = FUTURES.remove(response.getId()); if (future != null) { // 接收响应结果 future.doReceived(response); } else { logger.warn(“The timeout response finally returned at " + (new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss.SSS”).format(new Date())) + “, response " + response + (channel == null ? "” : “, channel: " + channel.getLocalAddress() + " -> " + channel.getRemoteAddress())); } } finally { // 通道集合移除该请求对应的通道,代表着这一次请求结束 CHANNELS.remove(response.getId()); }}该方法是接收响应,也就是某个请求得到了响应,那么代表这次请求任务完成,所有需要把future从集合中移除。具体的接收响应结果在doReceived方法中实现。5.doReceivedprivate void doReceived(Response res) { // 获得锁 lock.lock(); try { // 设置响应 response = res; if (done != null) { // 唤醒等待 done.signal(); } } finally { // 释放锁 lock.unlock(); } if (callback != null) { // 执行回调 invokeCallback(callback); }}可以看到,当接收到响应后,会把等待的线程唤醒,然后执行回调来处理该响应结果。6.invokeCallbackprivate void invokeCallback(ResponseCallback c) { ResponseCallback callbackCopy = c; if (callbackCopy == null) { throw new NullPointerException(“callback cannot be null.”); } c = null; Response res = response; if (res == null) { throw new IllegalStateException(“response cannot be null. url:” + channel.getUrl()); } // 如果响应成功,返回码是20 if (res.getStatus() == Response.OK) { try { // 使用响应结果执行 完成 后的逻辑 callbackCopy.done(res.getResult()); } catch (Exception e) { logger.error(“callback invoke error .reasult:” + res.getResult() + “,url:” + channel.getUrl(), e); } //超时,回调处理成超时异常 } else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) { try { TimeoutException te = new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()); // 回调处理异常 callbackCopy.caught(te); } catch (Exception e) { logger.error(“callback invoke error ,url:” + channel.getUrl(), e); } // 其他情况处理成RemotingException异常 } else { try { RuntimeException re = new RuntimeException(res.getErrorMessage()); callbackCopy.caught(re); } catch (Exception e) { logger.error(“callback invoke error ,url:” + channel.getUrl(), e); } }}该方法是执行回调来处理响应结果。分为了三种情况:响应成功,那么执行完成后的逻辑。超时,会按照超时异常来处理其他,按照RuntimeException异常来处理具体的处理都在ResponseCallback接口的实现类里执行,后面我会讲到。7.get@Overridepublic Object get() throws RemotingException { return get(timeout);}@Overridepublic Object get(int timeout) throws RemotingException { // 超时时间默认为1s if (timeout <= 0) { timeout = Constants.DEFAULT_TIMEOUT; } // 如果请求没有完成,也就是还没有响应返回 if (!isDone()) { long start = System.currentTimeMillis(); // 获得锁 lock.lock(); try { // 轮询 等待请求是否完成 while (!isDone()) { // 线程阻塞等待 done.await(timeout, TimeUnit.MILLISECONDS); // 如果请求完成或者超时,则结束 if (isDone() || System.currentTimeMillis() - start > timeout) { break; } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { // 释放锁 lock.unlock(); } // 如果没有收到响应,则抛出超时的异常 if (!isDone()) { throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false)); } } // 返回响应 return returnFromResponse();}该方法是实现了ResponseFuture定义的方法,是获得该future对应的请求对应的响应结果,其实future、请求、响应都是一一对应的。其中如果还没得到响应,则会线程阻塞等待,等到有响应结果或者超时,才返回。返回的逻辑在returnFromResponse中实现。8.returnFromResponseprivate Object returnFromResponse() throws RemotingException { Response res = response; if (res == null) { throw new IllegalStateException(“response cannot be null”); } // 如果正常返回,则返回响应结果 if (res.getStatus() == Response.OK) { return res.getResult(); } // 如果超时,则抛出超时异常 if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) { throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()); } // 其他 抛出RemotingException异常 throw new RemotingException(channel, res.getErrorMessage());}这代码跟invokeCallback方法中差不多,都是把响应分了三种情况。9.cancelpublic void cancel() { // 创建一个取消请求的响应 Response errorResult = new Response(id); errorResult.setErrorMessage(“request future has been canceled.”); response = errorResult; // 从集合中删除该请求 FUTURES.remove(id); CHANNELS.remove(id);}该方法是取消一个请求,可以直接关闭一个请求,也就是值创建一个响应来回应该请求,把response值设置到该请求对于到future中,做到了中断请求的作用。该方法跟closeChannel的区别是closeChannel中对response的状态设置了CHANNEL_INACTIVE,而cancel方法是中途被主动取消的,虽然有response值,但是并没有一个响应状态。10.RemotingInvocationTimeoutScanprivate static class RemotingInvocationTimeoutScan implements Runnable { @Override public void run() { while (true) { try { for (DefaultFuture future : FUTURES.values()) { // 已经完成,跳过扫描 if (future == null || future.isDone()) { continue; } // 超时 if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) { // create exception response.,创建一个超时的响应 Response timeoutResponse = new Response(future.getId()); // set timeout status.,设置超时状态,是服务端侧超时还是客户端侧超时 timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT); // 设置错误信息 timeoutResponse.setErrorMessage(future.getTimeoutMessage(true)); // handle response.,接收创建的超时响应 DefaultFuture.received(future.getChannel(), timeoutResponse); } } // 睡眠 Thread.sleep(30); } catch (Throwable e) { logger.error(“Exception when scan the timeout invocation of remoting.”, e); } } }}该方法是扫描调用超时任务的线程,每次都会遍历future集合,检测请求是否超时了,如果超时则创建一个超时响应来回应该请求。static { // 开启一个后台扫描调用超时任务 Thread th = new Thread(new RemotingInvocationTimeoutScan(), “DubboResponseTimeoutScanTimer”); th.setDaemon(true); th.start();}开启一个后台线程进行扫描的逻辑写在了静态代码块里面,只开启一次。(八)SimpleFuture该类实现了ResponseFuture,目前没有用到,很简单的实现,我就不多说了。(九)ExchangeHandler该接口继承了ChannelHandler, TelnetHandler接口,是信息交换处理器接口。public interface ExchangeHandler extends ChannelHandler, TelnetHandler { /* * reply. * 回复请求结果 * @param channel * @param request * @return response * @throws RemotingException / Object reply(ExchangeChannel channel, Object request) throws RemotingException;}该接口只定义了一个回复请求结果的方法,返回的是请求结果。(十)ExchangeHandlerDispatcher该类实现了ExchangeHandler接口, 是信息交换处理器调度器类,也就是对应不同的事件,选择不同的处理器去处理。该类中有三个属性,分别对应了三种事件:/* * 回复者调度器 /private final ReplierDispatcher replierDispatcher;/* * 通道处理器调度器 /private final ChannelHandlerDispatcher handlerDispatcher;/* * Telnet 命令处理器 /private final TelnetHandler telnetHandler;如果事件是跟通道处理器有关的,就调用通道处理器来处理,比如:@Override@SuppressWarnings({“unchecked”, “rawtypes”})public Object reply(ExchangeChannel channel, Object request) throws RemotingException { return ((Replier) replierDispatcher).reply(channel, request);}@Overridepublic void connected(Channel channel) { handlerDispatcher.connected(channel);}@Overridepublic String telnet(Channel channel, String message) throws RemotingException { return telnetHandler.telnet(channel, message);}可以看到以上三种事件,回复请求结果需要回复者调度器来处理,连接需要通道处理器调度器来处理,telnet消息需要Telnet命令处理器来处理。(十一)ExchangeHandlerAdapter该类继承了TelnetHandlerAdapter,实现了ExchangeHandler,是信息交换处理器的适配器类。public abstract class ExchangeHandlerAdapter extends TelnetHandlerAdapter implements ExchangeHandler { @Override public Object reply(ExchangeChannel channel, Object msg) throws RemotingException { // 直接返回null return null; }}该类直接让ExchangeHandler定义的方法reply返回null,交由它的子类选择性的去实现具体的回复请求结果。(十二)ExchangeServer该接口继承了Server接口,定义了两个方法:public interface ExchangeServer extends Server { /* * get channels. * 获得通道集合 * @return channels / Collection<ExchangeChannel> getExchangeChannels(); /* * get channel. * 根据远程地址获得对应的信息通道 * @param remoteAddress * @return channel / ExchangeChannel getExchangeChannel(InetSocketAddress remoteAddress);}该接口比较好理解,并且在Server接口基础上新定义了两个方法。直接来看看它的实现类吧。(十三)HeaderExchangeServer该类实现了ExchangeServer接口,是基于协议头的信息交换服务器实现类,HeaderExchangeServer是Server的装饰器,每个实现方法都会调用server的方法。1.属性protected final Logger logger = LoggerFactory.getLogger(getClass());/* * 线程池 /private final ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1, new NamedThreadFactory( “dubbo-remoting-server-heartbeat”, true));/* * 服务器 /private final Server server;// heartbeat timer/* * 心跳定时器 /private ScheduledFuture<?> heartbeatTimer;// heartbeat timeout (ms), default value is 0 , won’t execute a heartbeat./* * 心跳周期 /private int heartbeat;/* * 心跳超时时间 /private int heartbeatTimeout;/* * 信息交换服务器是否关闭 /private AtomicBoolean closed = new AtomicBoolean(false);该类里面的很多实现跟HeaderExchangeClient差不多,包括心跳检测等逻辑。看得懂上述我讲的HeaderExchangeClient的属性,想必这里的属性应该也很简单了。2.构造函数public HeaderExchangeServer(Server server) { if (server == null) { throw new IllegalArgumentException(“server == null”); } this.server = server; //获得心跳周期配置,如果没有配置,默认设置为0 this.heartbeat = server.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0); // 获得心跳超时配置,默认是心跳周期的三倍 this.heartbeatTimeout = server.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3); // 如果心跳超时时间小于心跳周期的两倍,则抛出异常 if (heartbeatTimeout < heartbeat * 2) { throw new IllegalStateException(“heartbeatTimeout < heartbeatInterval * 2”); } // 开始心跳 startHeartbeatTimer();}public Server getServer() { return server;}构造函数就是对属性的设置,心跳的机制以及默认值都跟HeaderExchangeClient中的一模一样。3.isRunningprivate boolean isRunning() { Collection<Channel> channels = getChannels(); // 遍历所有连接该服务器的通道 for (Channel channel : channels) { /* * If there are any client connections, * our server should be running. / // 只要有任何一个客户端连接,则服务器还运行着 if (channel.isConnected()) { return true; } } return false;}该方法是检测服务器是否还运行,只要有一个客户端连接着,就算服务器运行着。4.close@Overridepublic void close() { // 关闭线程池和心跳检测 doClose(); // 关闭服务器 server.close();}@Overridepublic void close(final int timeout) { // 开始关闭 startClose(); if (timeout > 0) { final long max = (long) timeout; final long start = System.currentTimeMillis(); if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) { // 发送 READONLY_EVENT事件给所有连接该服务器的客户端,表示 Server 不可读了。 sendChannelReadOnlyEvent(); } // 当服务器还在运行,并且没有超时,睡眠,也就是等待timeout左右时间在进行关闭 while (HeaderExchangeServer.this.isRunning() && System.currentTimeMillis() - start < max) { try { Thread.sleep(10); } catch (InterruptedException e) { logger.warn(e.getMessage(), e); } } } // 关闭线程池和心跳检测 doClose(); // 延迟关闭 server.close(timeout);}两个close方法,第二个close方法是优雅的关闭,有一定的延时来让一些响应或者操作做完。关闭分两个步骤,第一个就是关闭信息交换服务器中的线程池和心跳检测,然后才是关闭服务器。5.sendChannelReadOnlyEventprivate void sendChannelReadOnlyEvent() { // 创建一个READONLY_EVENT事件的请求 Request request = new Request(); request.setEvent(Request.READONLY_EVENT); // 不需要响应 request.setTwoWay(false); // 设置版本 request.setVersion(Version.getProtocolVersion()); Collection<Channel> channels = getChannels(); // 遍历连接的通道,进行通知 for (Channel channel : channels) { try { // 通过通道还连接着,则发送通知 if (channel.isConnected()) channel.send(request, getUrl().getParameter(Constants.CHANNEL_READONLYEVENT_SENT_KEY, true)); } catch (RemotingException e) { logger.warn(“send cannot write message error.”, e); } }}在关闭服务器中有一个操作就是发送事件READONLY_EVENT,告诉客户端该服务器不可读了,就是该方法实现的,逐个通知连接的客户端该事件。6.doCloseprivate void doClose() { if (!closed.compareAndSet(false, true)) { return; } // 停止心跳检测 stopHeartbeatTimer(); try { // 关闭线程池 scheduled.shutdown(); } catch (Throwable t) { logger.warn(t.getMessage(), t); }}该方法就是close方法调用到的停止心跳检测和关闭线程池。7.getExchangeChannels@Overridepublic Collection<ExchangeChannel> getExchangeChannels() { Collection<ExchangeChannel> exchangeChannels = new ArrayList<ExchangeChannel>(); // 获得连接该服务器通道集合 Collection<Channel> channels = server.getChannels(); if (channels != null && !channels.isEmpty()) { // 遍历通道集合,为每个通道都创建信息交换通道,并且加入信息交换通道集合 for (Channel channel : channels) { exchangeChannels.add(HeaderExchangeChannel.getOrAddChannel(channel)); } } return exchangeChannels;}该方法是返回连接该服务器信息交换通道集合。逻辑就是先获得通道集合,在根据通道来创建信息交换通道,然后返回信息通道集合。8.reset@Overridepublic void reset(URL url) { // 重置属性 server.reset(url); try { // 重置的逻辑跟构造函数一样设置 if (url.hasParameter(Constants.HEARTBEAT_KEY) || url.hasParameter(Constants.HEARTBEAT_TIMEOUT_KEY)) { int h = url.getParameter(Constants.HEARTBEAT_KEY, heartbeat); int t = url.getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, h * 3); if (t < h * 2) { throw new IllegalStateException(“heartbeatTimeout < heartbeatInterval * 2”); } if (h != heartbeat || t != heartbeatTimeout) { heartbeat = h; heartbeatTimeout = t; // 重新开始心跳 startHeartbeatTimer(); } } } catch (Throwable t) { logger.error(t.getMessage(), t); }}该方法就是重置属性,重置后,重新开始心跳,设置心跳属性的机制跟构造函数一样。9.startHeartbeatTimerprivate void startHeartbeatTimer() { // 先停止现有的心跳检测 stopHeartbeatTimer(); if (heartbeat > 0) { // 创建心跳定时器 heartbeatTimer = scheduled.scheduleWithFixedDelay( new HeartBeatTask(new HeartBeatTask.ChannelProvider() { @Override public Collection<Channel> getChannels() { // 返回一个不可修改的连接该服务器的信息交换通道集合 return Collections.unmodifiableCollection( HeaderExchangeServer.this.getChannels()); } }, heartbeat, heartbeatTimeout), heartbeat, heartbeat, TimeUnit.MILLISECONDS); }}该方法是开始心跳,跟HeaderExchangeClient类中的开始心跳方法唯一区别是获得的通道不一样,客户端跟通道是一一对应的,所有只要对一个通道进行心跳检测,而服务端跟通道是一对多的关系,所有需要对该服务器连接的所有通道进行心跳检测。10.stopHeartbeatTimerprivate void stopHeartbeatTimer() { if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) { try { // 取消定时器 heartbeatTimer.cancel(true); // 取消大量已排队任务,用于回收空间 scheduled.purge(); } catch (Throwable e) { if (logger.isWarnEnabled()) { logger.warn(e.getMessage(), e); } } } heartbeatTimer = null;}该方法是停止当前的心跳检测。(十四)ExchangeServerDelegate该类实现了ExchangeServer接口,是信息交换服务器装饰者,是ExchangeServer的装饰器。该类就一个属性ExchangeServer server,所有实现方法都调用了server属性的方法。目前只有在p2p中被用到,代码为就不贴了,很简单。(十五)Exchanger@SPI(HeaderExchanger.NAME)public interface Exchanger { /* * bind. * 绑定一个服务器 * @param url 服务器url * @param handler 数据交换处理器 * @return message server 数据交换服务器 / @Adaptive({Constants.EXCHANGER_KEY}) ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException; /* * connect. * 连接一个服务器,也就是创建一个客户端 * @param url 服务器url * @param handler 数据交换处理器 * @return message channel 返回数据交换客户端 / @Adaptive({Constants.EXCHANGER_KEY}) ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;}该接口是数据交换者接口,该接口是一个可扩展接口默认实现的是HeaderExchanger类,并且用到了dubbo SPI的Adaptive机制,优先实现url携带的配置。如果不了解dubbo SPI机制的可以看《dubbo源码解析(二)Dubbo扩展机制SPI》。那么回到该接口定义的方法,定义了绑定和连接两个方法,分别返回信息交互服务器和客户端实例。(十六)HeaderExchangerpublic class HeaderExchanger implements Exchanger { public static final String NAME = “header”; @Override public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { // 用传输层连接返回的client 创建对应的信息交换客户端,默认开启心跳检测 return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true); } @Override public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { // 用传输层绑定返回的server 创建对应的信息交换服务端 return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); }}该类继承了Exchanger接口,是Exchanger接口的默认实现,实现了Exchanger接口定义的两个方法,分别调用的是Transporters的连接和绑定方法,再利用这这两个方法返回的客户端和服务端实例来创建信息交换的客户端和服务端。(十七)Replier我们知道Request对应的是ExchangeHandler接口实现对象来处理,但有些时候我们需要不同数据类型对应不同的处理器,该类就是为了支持这一需求所设计的。public interface Replier<T> { /* * reply. * 回复请求结果 * @param channel * @param request * @return response * @throws RemotingException / Object reply(ExchangeChannel channel, T request) throws RemotingException;}可以看到该接口跟ExchangeHandler定义的方法也一一,只有请求的类型改为了范型。(十八)ReplierDispatcher该类实现了Replier接口,是回复者调度器实现类。/* * 默认回复者 /private final Replier<?> defaultReplier;/* * 回复者集合 /private final Map<Class<?>, Replier<?>> repliers = new ConcurrentHashMap<Class<?>, Replier<?>>();这是该类的两个属性,缓存了回复者集合和默认的回复者。/* * 从回复者集合中找到该类型的回复者,并且返回 * @param type * @return /private Replier<?> getReplier(Class<?> type) { for (Map.Entry<Class<?>, Replier<?>> entry : repliers.entrySet()) { if (entry.getKey().isAssignableFrom(type)) { return entry.getValue(); } } if (defaultReplier != null) { return defaultReplier; } throw new IllegalStateException(“Replier not found, Unsupported message object: " + type);}/* * 回复请求 * @param channel * @param request * @return * @throws RemotingException /@Override@SuppressWarnings({“unchecked”, “rawtypes”})public Object reply(ExchangeChannel channel, Object request) throws RemotingException { return ((Replier) getReplier(request.getClass())).reply(channel, request);}上述是该类中关键的两个方法,reply还是调用实现类的reply。根据请求的数据类型来使用指定的回复者进行回复。(十九)MultiMessage该类实现了实现 Iterable 接口,是多消息的封装,我们直接看它的属性:/* * 消息集合 /private final List messages = new ArrayList();该类要和《dubbo源码解析(九)远程通信——Transport层》的(八)MultiMessageHandler联合着看。(二十)HeartbeatHandler该类继承了AbstractChannelHandlerDelegate类,是心跳处理器。是用来处理心跳事件的,也接收消息上增加了对心跳消息的处理。该类是@Overridepublic void received(Channel channel, Object message) throws RemotingException { // 设置接收时间的时间戳属性值 setReadTimestamp(channel); // 如果是心跳请求 if (isHeartbeatRequest(message)) { Request req = (Request) message; // 如果需要响应 if (req.isTwoWay()) { // 创建一个响应 Response res = new Response(req.getId(), req.getVersion()); // 设置为心跳事件的响应 res.setEvent(Response.HEARTBEAT_EVENT); // 发送消息,也就是返回响应 channel.send(res); if (logger.isInfoEnabled()) { int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0); if (logger.isDebugEnabled()) { logger.debug(“Received heartbeat from remote channel " + channel.getRemoteAddress() + “, cause: The channel has no data-transmission exceeds a heartbeat period” + (heartbeat > 0 ? “: " + heartbeat + “ms” : “”)); } } } return; } // 如果是心跳响应,则直接return if (isHeartbeatResponse(message)) { if (logger.isDebugEnabled()) { logger.debug(“Receive heartbeat response in thread " + Thread.currentThread().getName()); } return; } handler.received(channel, message);}该方法是就是在handler处理消息上增加了处理心跳消息的功能,做到了功能增强。(二十一)Exchangers该类跟Transporters的设计意图是一样的,Transporters我在《dubbo源码解析(八)远程通信——开篇》的(十)Transporters已经讲到了。Exchangers也用到了外观模式。代码为就不贴了,可以对照着Transporters来看,很简单。(二十二)Request请求模型类,最重要的肯定是模型的属性,我们来看看属性:/* * 心跳事件 /public static final String HEARTBEAT_EVENT = null;/* * 只读事件 /public static final String READONLY_EVENT = “R”;/* * 请求编号自增序列 /private static final AtomicLong INVOKE_ID = new AtomicLong(0);/* * 请求编号 /private final long mId;/* * dubbo版本 /private String mVersion;/* * 是否需要响应 /private boolean mTwoWay = true;/* * 是否是事件 /private boolean mEvent = false;/* * 是否是异常的请求 /private boolean mBroken = false;/* * 请求数据 /private Object mData;由于心跳事件比较常用,所有设置为null。请求编号使用INVOKE_ID生成,是JVM 进程内唯一的。其他属性比较简单(二十三)Response响应模型,来看看它的属性:/* * 心跳事件 /public static final String HEARTBEAT_EVENT = null;/* * 只读事件 /public static final String READONLY_EVENT = “R”;/* * ok. * 成功状态码 /public static final byte OK = 20;/* * clien side timeout. * 客户端侧的超时状态码 /public static final byte CLIENT_TIMEOUT = 30;/* * server side timeout. * 服务端侧超时的状态码 /public static final byte SERVER_TIMEOUT = 31;/* * channel inactive, directly return the unfinished requests. * 通道不活跃,返回未完成请求的状态码 /public static final byte CHANNEL_INACTIVE = 35;/* * request format error. * 请求格式错误状态码 /public static final byte BAD_REQUEST = 40;/* * response format error. * 响应格式错误状态码 /public static final byte BAD_RESPONSE = 50;/* * service not found. * 服务找不到状态码 /public static final byte SERVICE_NOT_FOUND = 60;/* * service error. * 服务错误状态码 /public static final byte SERVICE_ERROR = 70;/* * internal server error. * 内部服务器错误状态码 /public static final byte SERVER_ERROR = 80;/* * internal server error. * 客户端错误状态码 /public static final byte CLIENT_ERROR = 90;/* * server side threadpool exhausted and quick return. * 服务器端线程池耗尽并快速返回状态码 /public static final byte SERVER_THREADPOOL_EXHAUSTED_ERROR = 100;/* * 响应编号 /private long mId = 0;/* * dubbo 版本 /private String mVersion;/* * 状态 /private byte mStatus = OK;/* * 是否是事件 /private boolean mEvent = false;/* * 错误信息 /private String mErrorMsg;/* * 返回结果 /private Object mResult;很多属性跟Request模型的属性一样,并且含义也一样,不过该模型多了很多的状态码。关键的是id跟请求一一对应。(二十四)ResponseCallbackpublic interface ResponseCallback { /* * done. * 处理请求 * @param response / void done(Object response); /* * caught exception. * 处理异常 * @param exception / void caught(Throwable exception);}该接口是回调的接口,定义了两个方法,分别是处理正常的响应结果和处理异常。(二十五)ExchangeCodec该类继承了TelnetCodec,是信息交换编解码器。在本文的开头,我就写到,dubbo将一条消息分成了协议头和协议体,用来解决粘包拆包问题,但是头跟体在编解码上有区别,我们先来看看dubbo 的协议头的配置:上图是官方文档的图片,能够清晰的看出协议中各个数据所占的位数:0-7位和8-15位:Magic High和Magic Low,类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包,就是一个固定的数字16位:Req/Res:请求还是响应标识。17位:2way:单向还是双向18位:Event:是否是事件19-23位:Serialization 编号24-31位:status状态32-95位:id编号96-127位:body数据128-…位:上图表格内的数据可以看到一个该协议中前65位是协议头,后面的都是协议体数据。那么在编解码中,协议头是通过 Codec 编解码,而body部分是用Serialization序列化和反序列化的。下面我们就来看看该类对协议头的编解码。1.属性// header length./* * 协议头长度:16字节 = 128Bits /protected static final int HEADER_LENGTH = 16;// magic header./* * MAGIC二进制:1101101010111011,十进制:55995 /protected static final short MAGIC = (short) 0xdabb;/* * Magic High,也就是0-7位:11011010 /protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];/* * Magic Low 8-15位 :10111011 /protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];// message flag./* * 128 二进制:10000000 /protected static final byte FLAG_REQUEST = (byte) 0x80;/* * 64 二进制:1000000 /protected static final byte FLAG_TWOWAY = (byte) 0x40;/* * 32 二进制:100000 /protected static final byte FLAG_EVENT = (byte) 0x20;/* * 31 二进制:11111 */protected static final int SERIALIZATION_MASK = 0x1f;可以看到 MAGIC是个固定的值,用来判断是不是dubbo协议的数据包,并且MAGIC_LOW和MAGIC_HIGH分别是MAGIC的低位和高位。其他的属性用来干嘛后面会讲到。2.encode@Overridepublic void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException { if (msg instanceof Request) { // 如果消息是Request类型,对请求消息编码 encodeRequest(channel, buffer, (Request) msg); } else if (msg instanceof Response) { // 如果消息是Response类型,对响应消息编码 encodeResponse(channel, buffer, (Response) msg); } else { // 直接让父类( Telnet ) 处理,目前是 Telnet 命令的结果。 super.encode(channel, buffer, msg); }}该方法是根据消息的类型来分别进行编码,分为三种情况:Request类型、Response类型以及其他3.encodeRequestprotected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException { Serialization serialization = getSerialization(channel); // header. // 创建16字节的字节数组 byte[] header = new byte[HEADER_LENGTH]; // set magic number. // 设置前16位数据,也就是设置header[0]和header[1]的数据为Magic High和Magic Low Bytes.short2bytes(MAGIC, header); // set request and serialization flag. // 16-23位为serialization编号,用到或运算10000000|serialization编号,例如serialization编号为11111,则为00011111 header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId()); // 继续上面的例子,00011111|1000000 = 01011111 if (req.isTwoWay()) header[2] |= FLAG_TWOWAY; // 继续上面的例子,01011111|100000 = 011 11111 可以看到011代表请求标记、双向、是事件,这样就设置了16、17、18位,后面19-23位是Serialization 编号 if (req.isEvent()) header[2] |= FLAG_EVENT; // set request id. // 设置32-95位请求id Bytes.long2bytes(req.getId(), header, 4); // encode request data. // // 编码 Request.data 到 Body ,并写入到 Buffer int savedWriteIndex = buffer.writerIndex(); buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); // 对body数据序列化 ObjectOutput out = serialization.serialize(channel.getUrl(), bos); // 如果该请求是事件 if (req.isEvent()) { // 特殊事件编码 encodeEventData(channel, out, req.getData()); } else { // 正常请求编码 encodeRequestData(channel, out, req.getData(), req.getVersion()); } // 释放资源 out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } bos.flush(); bos.close(); int len = bos.writtenBytes(); //检验消息长度 checkPayload(channel, len); // 设置96-127位:Body值 Bytes.int2bytes(len, header, 12); // write // 把header写入到buffer buffer.writerIndex(savedWriteIndex); buffer.writeBytes(header); // write header. buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);}该方法是对Request类型的消息进行编码,仔细阅读上述我写的注解,结合协议头各个位数的含义,好好品味我举的例子。享受二进制位运算带来的快乐,也可以看到前半部分逻辑是对协议头的编码,后面还有对body值的序列化。4.encodeResponseprotected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException { Serialization serialization = getSerialization(channel); // header. // 创建16字节的字节数组 byte[] header = new byte[HEADER_LENGTH]; // set magic number. // 设置前16位数据,也就是设置header[0]和header[1]的数据为Magic High和Magic Low Bytes.short2bytes(MAGIC, header); // set request and serialization flag. // 16-23位为serialization编号,用到或运算10000000|serialization编号,例如serialization编号为11111,则为00011111 header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId()); // 继续上面的例子,00011111|1000000 = 01011111 if (req.isTwoWay()) header[2] |= FLAG_TWOWAY; // 继续上面的例子,01011111|100000 = 011 11111 可以看到011代表请求标记、双向、是事件,这样就设置了16、17、18位,后面19-23位是Serialization 编号 if (req.isEvent()) header[2] |= FLAG_EVENT; // set request id. // 设置32-95位请求id Bytes.long2bytes(req.getId(), header, 4); // encode request data. // // 编码 Request.data 到 Body ,并写入到 Buffer int savedWriteIndex = buffer.writerIndex(); buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); // 对body数据序列化 ObjectOutput out = serialization.serialize(channel.getUrl(), bos); // 如果该请求是事件 if (req.isEvent()) { // 特殊事件编码 encodeEventData(channel, out, req.getData()); } else { // 正常请求编码 encodeRequestData(channel, out, req.getData(), req.getVersion()); } // 释放资源 out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } bos.flush(); bos.close(); int len = bos.writtenBytes(); //检验消息长度 checkPayload(channel, len); // 设置96-127位:Body值 Bytes.int2bytes(len, header, 12); // write // 把header写入到buffer buffer.writerIndex(savedWriteIndex); buffer.writeBytes(header); // write header. buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);}protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException { int savedWriteIndex = buffer.writerIndex(); try { Serialization serialization = getSerialization(channel); // header. // 创建16字节大小的字节数组 byte[] header = new byte[HEADER_LENGTH]; // set magic number. // 设置前0-15位为魔数 Bytes.short2bytes(MAGIC, header); // set request and serialization flag. // 设置响应标志和序列化id header[2] = serialization.getContentTypeId(); // 如果是心跳事件,则设置第18位为事件 if (res.isHeartbeat()) header[2] |= FLAG_EVENT; // set response status. // 设置24-31位为状态码 byte status = res.getStatus(); header[3] = status; // set request id. // 设置32-95位为请求id Bytes.long2bytes(res.getId(), header, 4); // 写入数据 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); // 对body进行序列化 ObjectOutput out = serialization.serialize(channel.getUrl(), bos); // encode response data or error message. if (status == Response.OK) { if (res.isHeartbeat()) { // 对心跳事件编码 encodeHeartbeatData(channel, out, res.getResult()); } else { // 对普通响应编码 encodeResponseData(channel, out, res.getResult(), res.getVersion()); } } else out.writeUTF(res.getErrorMessage()); // 释放 out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } bos.flush(); bos.close(); int len = bos.writtenBytes(); checkPayload(channel, len); Bytes.int2bytes(len, header, 12); // write buffer.writerIndex(savedWriteIndex); buffer.writeBytes(header); // write header. buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len); } catch (Throwable t) { // clear buffer buffer.writerIndex(savedWriteIndex); // send error message to Consumer, otherwise, Consumer will wait till timeout. //如果在写入数据失败,则返回响应格式错误的返回码 if (!res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) { Response r = new Response(res.getId(), res.getVersion()); r.setStatus(Response.BAD_RESPONSE); if (t instanceof ExceedPayloadLimitException) { logger.warn(t.getMessage(), t); try { r.setErrorMessage(t.getMessage()); // 发送响应 channel.send(r); return; } catch (RemotingException e) { logger.warn(“Failed to send bad_response info back: " + t.getMessage() + “, cause: " + e.getMessage(), e); } } else { // FIXME log error message in Codec and handle in caught() of IoHanndler? logger.warn(“Fail to encode response: " + res + “, send bad_response info instead, cause: " + t.getMessage(), t); try { r.setErrorMessage(“Failed to send response: " + res + “, cause: " + StringUtils.toString(t)); channel.send(r); return; } catch (RemotingException e) { logger.warn(“Failed to send bad_response info back: " + res + “, cause: " + e.getMessage(), e); } } } // Rethrow exception if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { throw new RuntimeException(t.getMessage(), t); } }}该方法是对Response类型的消息进行编码,该方法里面我没有举例子演示如何进行编码,不过过程跟encodeRequest类似。5.decode@Overridepublic Object decode(Channel channel, ChannelBuffer buffer) throws IOException { int readable = buffer.readableBytes(); // 读取前16字节的协议头数据,如果数据不满16字节,则读取全部 byte[] header = new byte[Math.min(readable, HEADER_LENGTH)]; buffer.readBytes(header); // 解码 return decode(channel, buffer, readable, header);}@Overrideprotected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException { // check magic number. // 核对魔数(该数字固定) if (readable > 0 && header[0] != MAGIC_HIGH || readable > 1 && header[1] != MAGIC_LOW) { int length = header.length; // 将 buffer 完全复制到 header 数组中 if (header.length < readable) { header = Bytes.copyOf(header, readable); buffer.readBytes(header, length, readable - length); } for (int i = 1; i < header.length - 1; i++) { if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) { buffer.readerIndex(buffer.readerIndex() - header.length + i); header = Bytes.copyOf(header, i); break; } } return super.decode(channel, buffer, readable, header); } // check length. // Header 长度不够,返回需要更多的输入,解决拆包现象 if (readable < HEADER_LENGTH) { return DecodeResult.NEED_MORE_INPUT; } // get data length. int len = Bytes.bytes2int(header, 12); // 检查信息头长度 checkPayload(channel, len); int tt = len + HEADER_LENGTH; // 总长度不够,返回需要更多的输入,解决拆包现象 if (readable < tt) { return DecodeResult.NEED_MORE_INPUT; } // limit input stream. ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len); try { // 对body反序列化 return decodeBody(channel, is, header); } finally { // 如果不可用 if (is.available() > 0) { try { // 打印错误日志 if (logger.isWarnEnabled()) { logger.warn(“Skip input stream " + is.available()); } // 跳过未读完的流 StreamUtils.skipUnusedStream(is); } catch (IOException e) { logger.warn(e.getMessage(), e); } } }}该方法就是解码前的一些核对过程,包括检测是否为dubbo协议,是否有拆包现象等,具体的解码在decodeBody方法。6.decodeBodyprotected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException { // 用并运算符 byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK); // get request id. // 获得请求id long id = Bytes.bytes2long(header, 4); // 如果第16位为0,则说明是响应 if ((flag & FLAG_REQUEST) == 0) { // decode response. Response res = new Response(id); // 如果第18位不是0,则说明是心跳事件 if ((flag & FLAG_EVENT) != 0) { res.setEvent(Response.HEARTBEAT_EVENT); } // get status. byte status = header[3]; res.setStatus(status); try { ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto); // 如果响应是成功的 if (status == Response.OK) { Object data; if (res.isHeartbeat()) { // 如果是心跳事件,则心跳事件的解码 data = decodeHeartbeatData(channel, in); } else if (res.isEvent()) { // 如果是事件,则事件的解码 data = decodeEventData(channel, in); } else { // 否则执行普通解码 data = decodeResponseData(channel, in, getRequestData(id)); } // 重新设置响应结果 res.setResult(data); } else { res.setErrorMessage(in.readUTF()); } } catch (Throwable t) { res.setStatus(Response.CLIENT_ERROR); res.setErrorMessage(StringUtils.toString(t)); } return res; } else { // decode request. // 对请求类型解码 Request req = new Request(id); // 设置版本号 req.setVersion(Version.getProtocolVersion()); // 如果第17位不为0,则是双向 req.setTwoWay((flag & FLAG_TWOWAY) != 0); // 如果18位不为0,则是心跳事件 if ((flag & FLAG_EVENT) != 0) { req.setEvent(Request.HEARTBEAT_EVENT); } try { // 反序列化 ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto); Object data; if (req.isHeartbeat()) { // 如果请求是心跳事件,则心跳事件解码 data = decodeHeartbeatData(channel, in); } else if (req.isEvent()) { // 如果是事件,则事件解码 data = decodeEventData(channel, in); } else { // 否则,用普通解码 data = decodeRequestData(channel, in); } // 把重新设置请求数据 req.setData(data); } catch (Throwable t) { // bad request // 设置是异常请求 req.setBroken(true); req.setData(t); } return req; }}该方法就是解码的过程,并且对协议头和协议体分开解码,协议头编码是做或运算,而解码则是做并运算,协议体用反序列化的方式解码,同样也是分为了Request类型、Response类型进行解码。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了Exchange层的相关设计和逻辑、介绍dubbo-remoting-api中的exchange包内的源码解,其中关键的是设计了Request和Response模型,整个信息交换都围绕这两大模型,并且设计了dubbo协议,解决拆包粘包问题,在信息交换中协议头携带的信息起到了关键作用,也满足了rpc调用的一些需求。下一篇我会讲解远程通信的buffer部分。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。 ...

December 21, 2018 · 19 min · jiezi

微服务架构实践:从零搭建网站扫码登录

微信扫码登录大家都是应用比较多的登录方式了,现在大的购物网站像京东、淘宝等都支持使用APP扫码登录网站了。今天就用APP扫码登录网站的实例来举例说明微服务架构的搭建过程。微服务架构应该是什么样子在这之前先看一看一个微服务架构落地以后应该是什么样子的。平常所有的微服务架构更多的是从框架来讲的像Dubbo,SpringCloud等,从整个SpringCloud的生态来讲它也只包含微服务的一部分。因为微服务的拆分不可避免的造成了系统的复杂性,团队间的合作管理和持续的交付等等,都是一项比较复杂的工程,如果没有好的团队管理规范和持续交付的流程等微服务是很难落地的。下面简单介绍一下上图中微服务架构的每一层的功能和作用:基础设施层,这一项除非自己搭建IDC,基本上现在的阿里云、腾讯云和百度云等都已经很好的支撑,特别是对于小的公司来说,更节省成本。平台服务层,对于现有的微服务能够快速动态部署那就是Docker了,再加上现有k8s等容器管理工具等,更是让微服务的部署如虎添翼,如果系统已经达到已经规模以后,可以考虑使用此种方式进行动态的扩容,一般情况下使用Docker就能解决部署问题了。支撑服务层,这一层跟微服务框架贴的非常近了,像SpringCloud已经自带了很多功能,像注册中心、配置中心、熔断限流和链路跟踪等,Dubbo也自带注册中心。业务服务层,这一层主要解决的是业务系统如何使用微服务进行解耦,各业务模块间如何进行分层交互等,形成了以基础服务模块为底层和以聚合服务为前端的“大中台小前台”的产品策略。网关服务层,这一层解决了权限控制、外部调用如何进行模块的负载均衡,可以实现在该层实现权限和流量的解耦,来满足不同的端的流量和权限不同的需求。接入层,该层主要是为了解决相同网关多实例的负载均衡的问题,防止单点故障灯。微服务开发框架,现在流行的微服务框架主要是SpringCloud和Dubbo,SpingCloud提供了更加完整的生态,Dubbo更适合内部模块间的快速高并发的调用。持续交付流水线,快速进行需求迭代,从提交代码到部署上线,能够快速的交付。工程实践与规范,这一项做不好,那整个微服务实施起来绝对是痛不欲生啊,基础模块如何定义,基础模块如何与其他模块解耦,如何进行版本的管理这个我在之前的使用Git和Maven进行版本管理和迭代的方法进行了说明。端到端的工具链,这里就是敏捷运维工具,从研发代码到最终上线到生产环境,任何一部都要有工具去实现完成,实现点一个按钮就能最终上线的系统。以上讲了实现微服务架构应该要做哪些事情,现在可以想想你的微服务架构到底落地到生成程度了,闲话少说,书归正传,今天是用APP扫码登录网站这个功能来进行举例说明应该从哪些方面进行微服务的落地实践。网站扫码登录功能这个功能是指在网站上选择使用二维码扫码登录,网站展示二维码,使用已经登录的应用APP扫码并确认登录后,网站就能登录成功,这既简单快捷,又提高了安全性。现在实现扫码登录网站的技术基本上有两种,一种就是轮询,另一种就是长连接,长连接又分为服务器端单向通信和双向通信两种,服务端单向通信只能由服务器端向客户端一直发送数据,双向通信是客户端和服务器端可以相互发送数据。像微信、京东和淘宝都是采用轮询的方式进行扫码登录的,一直使用轮询的方式在请求服务器端。今天我设计的这个扫码登录的功能,是采用的长连接能够双向通信的WebSocket的方式实现的。网站扫码实现流程1.用户在网站上登录时选择扫码登录。2.服务器端收到请求,生成一个临时的令牌,前端生成带令牌的链接地址的二维码,在浏览器上显示。3.PC端同时要与后台建立起websocket连接,等待后台发送登录成功的指令过来。4.用户用应用扫码,这个时候如果已经登陆过,后台就能获取到当前用户的token,如果没有登录到系统中,需要提前做登录。5.用户在应用APP上已经显示了是否确认登录的按钮。6.用户点击确认按钮,应用APP发起后端的api调用。7.后端接收到调用,根据临时名牌向websocket模块发送当前用户的token,pc端接收到登录成功,跳转到用户个人首页。如果用户点击了取消按钮,会根据uid向websocket模块发送取消登录的指令。技术的选型1.微服务框架的选择现在比较流行的是SpringCloud和Dubbo这两个框架,RPC的微服务框架还有Motan都不错,这里我使用SpringCloud和Dubbo这两个框架,使用SpringCloud实现网关和聚合服务模块并对外提供http服务,使用Dubbo实现内部模块间的接口调用。注册中心使用Zookeeper,Zookeeper能够同时支持SpringCloud和Dubbo进行注册。2.Websocket框架选择其实Spring现在已经具备websocket的功能了,但是我没有选择使用它,因为它只是实现了websocket的基本功能,像websocket的集群,客户端的管理等等,使用spring实现的话都得从零开始写。之前就一直使用netty-socketio做websocket的开发,它具备良好的集群、客户端管理等功能,而且它本身通知支持轮询和websocket两种方式,所以选它省事省时。3.存储的选择临时令牌存放在redis中,用来进行websocket连接时的验证,防止恶意的攻击,用户数据放在mysql中。4.源码管理工具和构建工具的选择使用Git作为代码管理工具,方便进行代码持续迭代和发布上线,使用Gitlab作为源码服务器端,可以进行代码的合并管理,使整个代码质量更容易把控。采用Maven做为构建工具,并使用nexus创建自己的Maven私服,用来进行基础服务版本的管理和发布。搭建Sonar服务器,Maven中集成Sonar插件进行代码质量的自动化检测。5.持续构建和部署工具采用Docker部署的方式,快速方便。采用Jekins做持续构建,可以根据git代码变更快速的打包上线。模块功能设计根据《微服务架构:如何用十步解耦你的系统?》中微服务解耦的设计原则:1.将Websocket作为服务独立出来只用来进行数据的通信,保证其功能的单一性,独立对外提供SocketApi接口,通过Dubbo的方式来调用其服务。2.将用户功能作为服务独立出来,进行用户注册和登录的功能,并对外提供UserApi接口,通过Dubbo的方式来调用。3.对外展示的功能包括页面和静态文件都统一到WebServer模块中,需要操作用户数据或者需要使用Websocket进行通信的都统一使用Dubbo调用。4.对于基本的权限认证和动态负载均衡都统一放到Gateway模块中,Gateway可以实现http的负载均衡和websocket的负载均衡。5.如果访问量非常大时,就考虑将Gateway分开部署,单独进行http服务和websocket服务,将两者的流量解耦。6.webserver端访问量大时,可以考虑将静态页面发布到CDN中,减少该模块的负载。开发规范解耦公共服务指定良好的开发管理规范,使用Git做好版本代码的分支管理,每个需求迭代使用单独的分支,保证每次迭代都可以独立上线,Maven私服中每次SocketApi和UserApi的升级都要保留历史版本可用,Dubbo服务做好多版本的兼容支持,这样就能将基础公共的服务进行解耦。总结微服务的引入不仅仅是带来了好处,同时也带来了系统的复杂性,不能只从框架和代码的角度来考虑微服务架构的落地,更要从整个管理的角度去考虑如何括地,否则使用微服务开发只会带来更多麻烦和痛苦。长按二维码,关注公众号煮酒科技(xtech100),输入数字11返回代码哟。

December 10, 2018 · 1 min · jiezi

微服务架构:如何用十步解耦你的系统?

导言:耦合性,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。高内聚低耦合,是软件工程中的概念,是判断设计好坏的标准,主要是面向对象的设计,主要是看类的内聚性是否高,耦合度是否低。SpringCloud和Dubbo都是现在比较成熟的微服务框架,如何使用两者构建搭建你的微服务系统呢?他们是如何将你的系统解耦的?又是怎么解耦的呢?请听我慢慢道来:第一步,解耦现有模块将现有耦合在一起的模块进行重新的设计,设计成可以独立部署的多个模块,使用微服务框架很容易做到,成熟的示例代码都特别多,这里不再多讲。下面是我的微服务实现的一个架构设计图。第二步,抽取公共模块架构设计原则之一就是反向依赖,只从上往下依赖,所以,我们将公共的重复功能的模块抽取出来。必须强调一点的是,公共模块必须足够的功能单一,不能有其他业务的逻辑判断在里面。在整个模块依赖关系里,应该是一棵树状结构的关系图,而不是一个网状的关系图。1)做好代码控制笔者之前就碰到过这种问题,模块划分完了,当需求变更的时候,研发人员根本不管是不是公共模块,只要能快速完成任务,哪里改的快就在哪里改。因此,这个需要内部要做好代码的权限管理,不应该开放所有的提交代码的权限给所有的人。后来我就将公共模块的合并代码的权限收回了,合并代码需要先提交申请,代码review过才能合并代码。这就保证了公共模块代码的功能单一。2)做好版本管理公共模块被多个模块模块使用,任何代码的修改都可能会导致到正在使用的模块无法使用。这个就需要做好各个模块的版本管理,我是使用maven进行版本管理的,定义一个总的父pom项目来进行各个模块的版本管理,任何被其他模块使用的开发包都要在父pom里进行版本管理。当新的需求来了以后,需要对公共模块进行修改时,要更新模块的版本号,同时更新父pom的版本号,需要使用公共模块新功能的模块就修改父pom的版本号,不需要使用公共模块新功能的模块就不用修改父pom的版本号,这样公共模块的新老版本都能使用,即使出现问题,也只会影响到使用新版本的模块。第三步,解耦迭代需求现在的代码迭代速度快,同时会面对多个需求,有的需求紧急,有的需求不紧急,而且紧急程度可能随时会调整,如果将所有的需求都放在一个分支,当只想上线其中几个需求的时候发现无法将不上线需求的代码拆分出来,是不是很尴尬,即使能拆分出来,代码修改过以后又要重新进行部署测试,很费时费力,所以要针对不同的需求重新建立研发分支,这样就将不同需求的分支解耦,保证想上哪个就上哪个,需要上多个需求的就将分支合并上线。第四步,配置解耦为每个模块每个环境配置一个配置文件,这样就可以把不同的环境的配置解耦,不用每次上线都更新一次。但是如果需要修改数据库配置,还是需要重新部署重启应用才能解决。使用微服务的配置中心就能解决这个问题了,比如使用ZooKeeper作为SpringCloud的配置中心,修改ZooKeeper中的节点数据就可以实时更新配置并生效。第五步,权限解耦当采用微服务架构把原来的系统拆分成多个系统以后,你会发现原来简单的问题,现在变的复杂了,比如功能的权限控制,原来是跟业务代码放到一起,现在如果每个业务模块都有功能权限的代码,将是一件非常麻烦的事情。那么解决办法就是将权限功能迁移出来,恰巧使用SpringCloudGateway就能完成这件事情,SpringCloudGateway能够进行负载均衡,各种路由拦截,只要将原来的权限控制代码迁移到Gateway里实现以下就可以了,权限配置管理界面和代码逻辑都不用变。如果是API接口呢,就需要将安全验证等功能放在Gateway里实现就好了。第六步,流量解耦当你的系统访问量越来越大的时候,你会发现每次升级都是一件非常麻烦的事情,领导会跟你说这个功能忙时不能停机影响用户使用呀,只能半夜升级呀,多么痛快的事情啊。有的时候运营人员也会发现,怎么我的后台访问怎么这么慢?问题出在哪里呢?问题就出在,所有的模块都用了一个Gateway,多端同时使用了相同的流量入口,当在举行大促时,并发量非常高,带宽占用非常大,那么其他的功能也会跟着慢下来。不能在举行大促时发券时,我线下支付一直支付不了,这是非常严重的事故了,客服电话会被打爆了。所以,必须要对流量进行拆分,各个端的流量不能相互影响,比如APP端、微信端、运营后台和商户后台等都要分配独立的Gateway,并接入独立的带宽,对于流量大的端可以使用弹性带宽,对于运营后台和商户后台就比较小的固定的带宽即可。这样就大大降低了升级时的难度,是不是再上线时就没那么紧张了?第七步,数据解耦系统刚上线的时候,数据量不大,所有的模块感觉都挺好的,当时间一长,系统访问量非常大的时候会发现功能怎么都变慢了,怎么mysql的cpu经常100%。那么恭喜你,你中招了,你的数据需要解耦了。首先要模块间数据解耦,将不同模块使用独立的数据库,保证各模块之间的数据不相互影响。其次就是冷热数据解耦,同一个模块运行时间长了以后也会积累大量的数据,为了保证系统的性能的稳定,要减少因为数据量太大造成的性能降低,需要对历史数据进行定期的迁移,对于完整数据分析汇总就在其他的库中实现。第八步,扩容解耦一个好的架构设计是要有好的横向扩展的能力,在不需要修改代码只通过增加硬件的方式就能提高系统的性能。SpringCloud和Dubbo的注册中心天生就能够实现动态添加模块的节点,其他模块调用能够实时发现并请求到新的模块节点上。第九步,部署解耦互联网开发在于能够快速的试错,当一个新的版本上线时,经常是需要先让一部分用户进行测试一下,这就是传说中的灰度发布,同一个模块先部署升级几台服务器到新版本,重启完成后流量进来以后,就可以验证当前部署的这几台服务器有没有问题,就继续部署其他的节点,如果有问题马上回滚到上一个版本。使用SpringCloudGateway的WeighRouterFilter就能实现这个功能。第十步,动静解耦当同一个模块的瞬间有非常高并发的时候,对,就是说的秒杀,纯粹的流量解耦还是不够,因为不能让前面的流量冲击后面真正的下单的功能,这个时候就需要更细的流量解耦,要将静态文件的访问通通抛给CDN来解决,动态和静态之间是通过定时器来出发的,时间未到之前一直刷新的是静态的文件,当时间到了之后,生成新的js文件,告诉静态页面可以访问下单功能了。总结在模块划分时,要遵循“一个模块,一个功能”的原则,尽可能使模块达到功能内聚。事实上,微服务架构短期来看,并没有很明显的好处,甚至短期内会影响系统的开发进度,因为高内聚,低耦合的系统对开发设计人员提出了更高的要求。高内聚,低耦合的好处体现在系统持续发展的过程中,高内聚,低耦合的系统具有更好的重用性,维护性,扩展性,可以更高效的完成系统的维护开发,持续的支持业务的发展,而不会成为业务发展的障碍。———— / END / ————

December 4, 2018 · 1 min · jiezi

超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务

Github 地址:https://github.com/Snailclimb/springboot-integration-examples ,欢迎各位 Star。目录:使用 SpringBoot+Dubbo 搭建一个简单分布式服务实战之前,先来看几个重要的概念什么是分布式?什么是 Duboo?Dubbo 架构什么是 RPC?为什么要用 Dubbo?开始实战 1 :zookeeper 环境安装搭建1. 下载2. 解压3. 进入zookeeper目录,创建data文件夹。4. 进入/zookeeper/conf目录下,复制zoo_sample.cfg,命名为zoo.cfg5. 修改配置文件6. 启动测试开始实战 2 :实现服务接口 dubbo-interface1. dubbo-interface 项目创建2. 创建接口类3. 将项目打成 jar 包供其他项目使用开始实战 3 :实现服务提供者 dubbo-provider1. dubbo-provider 项目创建2. pom 文件引入相关依赖3. 在 application.properties 配置文件中配置 dubbo 相关信息4. 实现接口5. 服务提供者启动类编写开始实战 4 :实现服务消费者 dubbo-consumer4. 编写一个简单 Controller 调用远程服务5. 服务消费者启动类编写6. 测试效果使用 SpringBoot+Dubbo 搭建一个简单分布式服务实战之前,先来看几个重要的概念开始实战之前,我们先来简单的了解一下这样几个概念:Dubbo、RPC、分布式、由于本文的目的是带大家使用SpringBoot+Dubbo 搭建一个简单的分布式服务,所以这些概念我只会简单给大家普及一下,不会做深入探究。什么是分布式?分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等。我们可以使用 Dubbo作为分布式系统的桥梁,那么什么是 Dubbo 呢?什么是 Duboo?Apache Dubbo (incubating) |db| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。Dubbo 目前已经有接近 23k 的 Star ,Dubbo的Github 地址:https://github.com/apache/inc…。另外,在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中,Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。下面我们简单地来看一下 Dubbo 的架构,加深对 Dubbo 的理解。Dubbo 架构下面我们再来看看 Dubbo 的架构,我们后面会使用 zookeeper 作为注册中心,这也是 Dubbo 官方推荐的一种方式。上述节点简单说明:Provider 暴露服务的服务提供方Consumer 调用远程服务的服务消费方Registry 服务注册与发现的注册中心Monitor 统计服务的调用次数和调用时间的监控中心Container 服务运行容器调用关系说明:服务容器负责启动,加载,运行服务提供者。服务提供者在启动时,向注册中心注册自己提供的服务。服务消费者在启动时,向注册中心订阅自己所需的服务。注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。我们在讲 Dubbo 的时候提到了 Dubbo 实际上是一款 RPC 框架,那么RPC 究竟是什么呢?相信看了下面我对 RPC 的介绍你就明白了!什么是 RPC?RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。为什么要用 Dubbo?如果你要开发分布式程序,你也可以直接基于 HTTP 接口进行通信,但是为什么要用 Dubbo呢?我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo:负载均衡——同一个服务部署在不同的机器时该调用那一台机器上的服务服务调用链路生成——服务之间互相是如何调用的服务访问压力以及时长统计——当前系统的压力主要在哪里,如何来扩容和优化服务降级——某个服务挂掉之后调用备用服务开始实战 1 :zookeeper 环境安装搭建我使用的是 CentOS 7.4 阿里云服务器,注意:如果你也同样阿里云服务器必须配置一个安全组,不然你的应用程序会无法访问你的 zookeeper 服务器,这一点我在后面也提到了。1. 下载通过 http://mirror.bit.edu.cn/apache/zookeeper/ 这个链接下载,然后上传到Linux上。(可以说那个 Xhell 附带的文件传输功能)或者直接在Linux中使用 wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.12/zookeeper-3.4.12.tar.gz 命令下载(版本号 3.4.12 是我写这篇文章的时候最新的稳定版本,各位可以根据实际情况修改)2. 解压tar -zxvf zookeeper-3.4.12-alpha.tar.gz解压完毕之后修改一下解压之后所得的文件夹名mv zookeeper-3.4.12 zookeeper删除 zookeeper 安装包rm -rf zookeeper-3.4.12.tar.gz3. 进入zookeeper目录,创建data文件夹。mkdir data进入 data 文件夹 然后执行pwd命令,复制所得的当前目录位置(就是我用红色圈出来的文字)4. 进入/zookeeper/conf目录下,复制zoo_sample.cfg,命名为zoo.cfgcp zoo_sample.cfg zoo.cfg5. 修改配置文件使用 vim zoo.cfg 命令修改配置文件vim 文件——>进入文件—–>命令模式——>按i进入编辑模式—–>编辑文件 ——->按Esc进入底行模式—–>输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)修改配置文件中的 data 属性:dataDir=/usr/local/zookeeper/data6. 启动测试进入 /zookeeper/bin 目录然后执行下面的命令./zkServer.sh start执行 ./zkServer.sh status 查看当前 zookeeper 状态。或者运行 netstat -lntup 命令查看网络状态,可以看到 zookeeper 的端口号 2181 已经被占用注意没有关闭防火墙可能出现的问题!!!如果你使用的阿里云服务器注意配置相关安全组:进入本实例安全组页面选择配置规则选择添加安全组规则,然后按照下图配置在开始实战之前提个建议:尽量新建一个文件夹,然后后面将接口项目、服务提供者以及服务消费者都放在这个文件夹。开始实战 2 :实现服务接口 dubbo-interface主要分为下面几步:创建 Maven 项目;创建接口类将项目打成 jar 包供其他项目使用项目结构:dubbo-interface 后面被打成 jar 包,它的作用只是提供接口。1. dubbo-interface 项目创建File->New->Module… ,然后选择 Maven类型的项目,其他的按照提示一步一步走就好。2. 创建接口类package top.snailclimb.service;public interface HelloService { public String sayHello(String name);}3. 将项目打成 jar 包供其他项目使用点击右边的 Maven Projects 然后选择 install ,这样 jar 宝就打好了。开始实战 3 :实现服务提供者 dubbo-provider主要分为下面几步:创建 springboot 项目;加入 dubbo 、zookeeper以及接口的相关依赖 jar 包;在 application.properties 配置文件中配置 dubbo 相关信息;实现接口类;服务提供者启动类编写项目结构:1. dubbo-provider 项目创建创建一个 SpringBoot 项目,注意勾选上 web 模块。不会创建的话,可以查看下面这篇文章:,可以说很详细了。https://blog.csdn.net/qq_34337272/article/details/795636062. pom 文件引入相关依赖需要引入 dubbo 、zookeeper以及接口的相关依赖 jar 包。注意将本项目和 dubbo-interface 项目的 dependency 依赖的 groupId 和 artifactId 改成自己的。dubbo 整合spring boot 的 jar 包在这里找dubbo-spring-boot-starter。zookeeper 的 jar包在 Maven 仓库 搜索 zkclient 即可找到。 <dependency> <groupId>top.snailclimb</groupId> <artifactId>dubbo-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!–引入dubbo的依赖–> <dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!– 引入zookeeper的依赖 –> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency>3. 在 application.properties 配置文件中配置 dubbo 相关信息配置很简单,这主要得益于 springboot 整合 dubbo 专属的@EnableDubboConfiguration 注解提供的 Dubbo 自动配置。# 配置端口server.port=8333spring.dubbo.application.name=dubbo-providerspring.dubbo.application.registry=zookeeper://ip地址:21814. 实现接口注意: @Service 注解使用的时 Dubbo 提供的而不是 Spring 提供的。另外,加了Dubbo 提供的 @Service 注解之后还需要加入package top.snailclimb.service.impl;import com.alibaba.dubbo.config.annotation.Service;import org.springframework.stereotype.Component;import top.snailclimb.service.HelloService;@Component@Servicepublic class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return “Hello " + name; }}5. 服务提供者启动类编写注意:不要忘记加上 @EnableDubboConfiguration 注解开启Dubbo 的自动配置。package top.snailclimb;import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication// 开启dubbo的自动配置@EnableDubboConfigurationpublic class DubboProviderApplication { public static void main(String[] args) { SpringApplication.run(DubboProviderApplication.class, args); }}开始实战 4 :实现服务消费者 dubbo-consumer主要分为下面几步:创建 springboot 项目;加入 dubbo 、zookeeper以及接口的相关依赖 jar 包;在 application.properties 配置文件中配置 dubbo 相关信息;编写测试类;服务消费者启动类编写测试效果项目结构:第1,2,3 步和服务提供者的一样,这里直接从第 4 步开始。4. 编写一个简单 Controller 调用远程服务package top.snailclimb.dubboconsumer;import com.alibaba.dubbo.config.annotation.Reference;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import top.snailclimb.service.HelloService;@RestControllerpublic class HelloController { @Reference private HelloService helloService; @RequestMapping("/hello”) public String hello() { String hello = helloService.sayHello(“world”); System.out.println(helloService.sayHello(“SnailClimb”)); return hello; }}5. 服务消费者启动类编写package top.snailclimb.dubboconsumer;import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@EnableDubboConfigurationpublic class DubboConsumerApplication { public static void main(String[] args) { SpringApplication.run(DubboConsumerApplication.class, args); }}6. 测试效果浏览器访问 http://localhost:8330/hello 页面返回 Hello world,控制台输出 Hello SnailClimb,和预期一直,使用SpringBoot+Dubbo 搭建第一个简单的分布式服务实验成功! ...

November 28, 2018 · 2 min · jiezi

始于阿里,回归社区|阿里巴巴的开源之路

破土而出的生命力,源自理想主义者心底对技术的信念。开源曾经帮助 Redhat 在传统软件市场奠定了其行业地位。无独有偶,作为云计算时代的赶超者,谷歌也拿起了开源的武器,试图打乱 AWS 和 Azure 的节奏。目前,这一策略似乎正在奏效。如今云原生技术正席卷全球,云原生基金会在去年 KubeCon +CloudNativeCon NA 的现场宣布:其正在孵化的项目已达 14 个,入驻的厂家或产品已超过 300 家,并吸引了 2.2 万开发者参与项目代码贡献,其明星产品 Kubenetes 的 GitHub 上 Authors 和 Issues 量已排行开源领域的第二名。而 Kubenetes 正是 Google 开源的一个容器编排引擎。今年,KubeCon + CloudNativeCon 首次来到中国。在2018 KubeCon + CloudNativeCon的现场,阿里云研究员伯瑜向在场的开发者们宣布,CNCF 已将阿里巴巴云原生镜像分发系统 Dragonfly 接纳为其沙箱项目(Sandbox),并有机会成为国内首个从 CNCF 毕业的开源项目。目前已经毕业的两个项目,一个是 Kubernetes,另一个是 Prometheus。据悉,目前阿里巴巴已经有 8 个项目进入 CNCF 云原生全景图,分别是分布式服务治理框架 Dubbo、分布式消息引擎 RocketMQ、流量控制组件Sentinel、企业级富容器技术 PouchContainer、服务发现和管理 Nacos、分布式消息标准 OpenMessaging、云原生镜像分发系统 Dragonfly 和高可用服务 AHAS。时间回到 2016 年2016 年的那届双11,RocketMQ 创始人冯嘉和他的团队首次将低延迟存储解决方案应用于双11的支撑,经受住了流量的大考,整个大促期间,99.996%的延迟落在了 10ms 以内,完成了保障交易稳定的既定目标。对于读写比例几乎均衡的分布式消息引擎来说,这一技术上的突破,即便是放在全球范围内,也绝对是值得称赞的。另一边,在历时3个月的开源重塑后,冯嘉和他的团队启动了 RocketMQ 向Apache 软件基金会的捐赠之路,但迈出这一步并不容易。“当时国内的开源氛围还没有现在那么活跃,开源之后,很多设计、源码和文档的维护工作还不够理想,但我们就是想证明国内的开源项目和开源社区也可以在世界的开源舞台上发挥价值。”经过近一年的努力,在2017年9月25日,Apache 软件基金会官方宣布,阿里巴巴捐赠给 Apache 社区的开源项目 RocketMQ 从 Apache 社区正式毕业,成为 Apache 顶级项目(TLP),这是国内首个非 Hadoop 生态体系的Apache 社区顶级项目。值得一提的是,根据项目毕业前的统计,RocketMQ 有百分八十的新特性与生态集成来自于社区的贡献。2017 年,消息领域出现一件里程碑事件。分布式消息领域的国际标准 OpenMessaging 开源项目正式入驻 Linux 基金会,这是国内首个在全球范围发起的分布式计算领域的国际标准。消息通讯已经成为现代数据驱动架构的关键环节,但在全球范围内,消息领域仍然存在两大问题:一是缺乏供应商中立的行业标准,导致各种消息中间件的高复杂性和不兼容性,相应地造成了公司的产品低效、混乱和供应商锁定等问题。二是目前已有的方案框架并不能很好地适配云架构,即非云原生架构,因此无法有效地对大数据、流计算和物联网等新兴业务需求提供技术支持。这也是冯嘉和他的团队在开源 RocketMQ 过程中,开发者和合作伙伴经常会提到的问题:“在消息领域,市场上出现了各类不同的开源解决方案,这导致了用户更高的接入和维护成本,为了确保各个消息引擎间能正常通信,还要投入大量的精力去做兼容。”这时候,建立一套供应商中立,和语言无关的消息领域的事实标准,成为各社区成员共同的诉求。此后,在 2017 年 9 月,阿里巴巴发起 OpenMessaging 项目,并邀请了雅虎、滴滴出行、Streamlio共同参与,一年后,参与 OpenMessaging 开源标准社区的企业达10家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、青云 QingCloud、Streamlio、微众银行、Yahoo、中国移动苏州研发中心(按首字母排序),此外,还获得了 RocketMQ、RabbitMQ 和 Pulsar 3 个顶级消息开源厂商的支持。相比于开源一个分布式消息项目,一套开源标准能被各家厂商所接受,对整个国内开源领域而言,是更具有里程碑意义的事件。2017 年 9 月,Dubbo 重启开源。Dubbo 是阿里巴巴于 2012 年开源的分布式服务治理框架,是国内影响力最大、使用最广泛的开源服务框架之一,在 2016 年、2017 年开源中国发起的最受欢迎的中国开源软件评选中,连续两年进入 Top10 名单。2017 年 9 月 7 日,在 Github 将版本更新至 2.5.4,重点升级所依赖的 JDK 及其组件,随后连续发布了 11 个版本。并于2018年2月捐献给 Apache 软件基金会,希望借助社区的力量来发展 Dubbo,打消大家对于 Dubbo 未来的顾虑。项目重启半年后,Dubbo 项目负责人阿里巴巴高级技术专家北纬在接受媒体采访的时候,从战略、社区、生态和回馈四个方面谈了 Dubbo 重启开源背后的原因。“集团近几年开始将开源提到了新的战略高度,这次投入资源重启开源,核心是希望让开源发挥更大的社会价值,并和广大开发者一起,建立一个繁荣的Dubbo 生态,普惠所有使用 Dubbo 的人和 Dubbo 本身。”Dubbo项目组成员朱勇在今年上海的技术沙龙上分享Dubbo未来发展的过程中提到,其后续的规划是要解决好两个问题。第一个问题是重点关注技术趋势,例如云原生对Dubbo开源现状的影响。第二个问题是 Dubbo 本身定位的问题,除了保持技术上的领先性,还需要围绕 Dubbo 核心发展生态,和社区成员一起将 Dubbo 发展成一个服务化改造的整体解决方案。2017 年 11 月,阿里自研容器技术 PouchContainer 开源。在开源不到一年的时间里,PouchContainer 1.0 GA 版本发布,达到可生产级别。今年 8 月,PouchContainer 被纳入开源社区开放容器计划 OCI;9 月,被收录进高校教材《云计算导论》;11 月,PouchContainer 团队携蚂蚁金服容器团队、阿里云 ACS 团队,与容器生态 Containerd 社区 Maintainer进行技术交流,有望发展成 Containerd 社区 Maintainer 席位,代表国内企业在世界容器技术领域发声。PouchContainer 发展速度之快,超出了宏亮的想象。宏亮是 Docker Swarm 容器集群项目的核心代码维护者(Maintainer),并于2015 年 8 月出版了《Docker 源码分析》一书,对 Docker 架构和源代码进行了深入的讲解,该书在 Docker 领域迅速成为畅销书籍。2017 年,宏亮承担起阿里自有容器技术的对内支持和对外技术布道的工作,秉承初心,希望在竞争激烈的容器开源领域能抢下属于国内容器技术的一席之地。在团队的努力下,阿里集团内部已实现 100%的容器化,并已经开始涉及离线业务,实现在、离线业务的混合调度与部署。整个集团能实现 100%的容器化,离不开阿里内部自研的 P2P 分发技术,该项目取名为 Dragonfly,寓意点与点之间的文件分发能如蜻蜓般轻盈和迅速,解决传统文件发布系统中的大规模下载、远距离传输、带宽成本和安全传输的问题。日前,Dragonfly 正式进入 CNCF, 并成为国内第三个被列为沙箱级别(Sandbox Level Project)的开源项目,可见,CNCF 在其云原生的技术版图中正希望借助Dragonfly等优秀的镜像分发技术,以提升企业微服务架构下应用的交付效率。始于阿里,回归社区。今年夏天,国内开源领域,迎来了两位新成员。作为微服务和云原生生态下的两款重要开源框架/组件,Nacos主打云原生应用中的动态服务发现、配置和服务管理,Sentinel 则是聚焦在限流和降级两个方面。Nacos 和 Sentinel 均是在阿里近 10 年的核心业务场景下沉淀所产生的,他们的开源是对微服务和元原生领域开源技术方案的有效补充,同时也非常强调融入开源生态,除了兼容 Dubbo和Sentinel,也支持对Spring Cloud 和 Kubenetes 等生态,以增强自身的生命力。“阿里巴巴早在 2007 年进行从 IOE 集中式应用架构升级为互联网分布式服务化架构的时候,就意识到在分布式环境中,诸如分布式服务治理,数据源容灾切换、异地多活、预案和限流规则等场景下的配置变更难题,因为在一个大型的分布式系统中,你没有办法把整个分布式系统停下来,去做一个软件、硬件或者系统的升级。”阿里巴巴高级技术专家坤宇在 2017 QCon 的现场分享到。在配置变更领域,我们从2008年的无 ConfigServer 时代,借用硬件负载设备 F5 提供的 VIP 功能,通过域名方式来实现服务提供方和调用方之间的通信,逐步经历了 ConfigServer 单机版、集群版的多次迭代,不断提高其稳定性。曾写下支付宝钱包服务端第一行代码的阿里高级技术专家慕义,在今年深圳的技术沙龙现场回忆了阿里注册中心自研的 10 年路:“这期间,集团业务经历了跨越式的发展,每年翻番的服务规模,不断的给 ConfigServer 的技术架构演进带来更高的要求和挑战,使得我们有更多的机会在生产环境发现和解决一个个问题的过程中,实现架构的一代代升级。Nacos 便是在这样的背景下,经过几代技术人的技术攻坚所产生的。”我们希望 Nacos 可以帮助开发者获得有别于原生或其他第三方服务发现和动态配置管理解决方案所提供的能力,满足开发者们在微服务落地过程当中对工业级注册中心的诉求,缩短想法到实现的路径。巧的是,一边是 Nacos宣布开源,另一边是 Spring Cloud 生态下的服务注册和发现组件 Netflix Eureka 宣布闭源,勇敢者的游戏充满了变数,但在坤宇和他的团队看来,这场游戏自己可以走到最后,因为我们并不是一个人在战斗,Nacos 只是阿里众多开源项目中的一员,随后还会有更多的开源项目反哺给社区,形成生态,例如轻量级限流降级组件 Sentinel。7月29日,Aliware Open Source•深圳站现场,只能容纳 400 人的场地,来了 700 多位开发者。阿里巴巴高级技术专家子矜在现场宣布了轻量级限流降级组件 Sentinel 的开源。作为阿里巴巴“大中台、小前台”架构中的基础模块,Sentinel 经历了 10 年双 11 的考验覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践。Sentinel 的出现,离不开阿里历届高可用架构团队的共同努力。“在双11备战中,容量规划是最重要也是最具挑战的环节之一。从第一年开始,双11的 0 点时刻就代表了我们的历史最高业务访问量,它通常是日常流量的几十倍甚至上百倍。因此,如何让一个技术和业务持续复杂的分布式站点去更平稳支撑好这突如其来的流量冲击,是我们这 10 年来一直在解的题。”阿里巴巴高可用架构团队资深技术专家游骥在今年的双11结束后分享道。这 10 年,容量规划经历了人工估算、线下压测、线上压测、全链路压测、全链路压测和隔离环境、弹性伸缩相结合的 5 个阶段。2013 年双11结束后,全链路压测的诞生解决了容量的确定性问题。作为一项划时代的技术,全链路压测的实现,对整个集团而言,都是一件里程碑事件。随后,基于全链路压测为核心,打造了一系列容量规划相关的配套生态,提升能力的同时,降低了整个环节的成本、提升效率。随着容量规划技术的不断演进,2018 年起,高可用架构团队希望可以把这些年在生成环境下的实践,贡献给社区,之后便有了 Sentinel 的开源。一边是作为发起者。将自己生产环境实践下沉淀出来的架构和技术贡献给社区。另一边是作为参与者。基于一些开源项目或云平台,输出可以解决开发者当前工作中存在的痛点的解决方案,例如近期新开源的项目 Spring Cloud Alibaba 和 开发者工具 Alibaba Cloud Toolkit。相同的是,技术理想主义者都希望技术可以为让世界变得更好,这才是技术人的兴奋点。“让世界的技术因为阿里巴巴而变得更美好一点点”。这是阿里巴巴系统软件、中间件、研发效能事业部负责人毕玄邮件签名中的一句话。他正和一群技术理想主义者,与太平洋另一边的技术高手们正面PK,在这场躲不开的战役中,一起认真一把。本文作者:amber涂南阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 22, 2018 · 2 min · jiezi

全新 Dubbo Admin 安装(开发版-Dubbo OPS)

这是正在研发的Dubbo Admin,这一版Dubbo Admin名称改成了Dubbo OPS,目前功能简陋,不建议使用(2018/11月)。架构上使用了前后端分离。前端使用Vue实现,后端使用Spring Boot实现。在安装之前你需要先安装Node和NPM用于运行前端项目。成功安装后长这样:前端部分项目:dubbo-admin-frontend使用Vue.js作为javascript框架,Vuetify作为UI框架后端部分项目:dubbo-admin-backend标准spring boot工程图下载项目GitHub 项目地址:https://github.com/apache/incubator-dubbo-opsgit clone https://github.com/apache/incubator-dubbo-ops.git把项目clone下来后有后端项目:dubbo-admin-backend前端项目:dubbo-admin-frontend生产环境配置1、修改注册中心地址在application-production.properties中指定注册中心地址dubbo-admin-backend/src/resources/application-production.properties2、构建项目mvn clean package3、启动项目mvn –projects dubbo-admin-backend spring-boot:run4、访问http://localhost:80805、Swagger 支持部署完成后,可以访问ip:port来查看所有的restful apihttp://localhost:8080/swagger-ui.html开发环境配置项目:dubbo-admin-backend是一个标准spring boot工程,可以在任何java IDE中运行它package org.apache.dubbo.admin;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class DubboAdminApplication { public static void main(String[] args) { ApplicationContext act = SpringApplication.run(DubboAdminApplication.class, args); SpringUtil.setApplicationContext(act); }}项目:dubbo admin frontend由npm管理和构建,在开发环境中,可以单独运行构建安装程序nodejs环境将自行安装,本文不介绍# install dependencies$ npm install# serve with hot reload at localhost:8081$ npm run dev> dubbo-admin-frontend@1.0.0 dev F:\OpenSource\incubator-dubbo-ops\dubbo-admin-frontend> webpack-dev-server –inline –progress –config build/webpack.dev.conf.js 95% emitting DONE Compiled successfully in 11312ms16:18:31 I Your application is running here: http://localhost:8081页面访问 Dubbo OPS访问 http://localhost:8081, 由于前后端分开部署,前端支持热加载,任何页面的修改都可以实时反馈,不需要重启应用。图1图2跨域问题为了方便开发,我们提供了这种前后端分离的部署模式,主要的好处是支持前端热部署,在这种模式下,前端会通过8080端口访问后端的restful api接口,获取数据, 这将导致跨域访问的问题。因此我们在dubbo-admin-frontend/config/index.js添加了支持跨域访问的配置,当前端通过npm run dev单独启动时,这些配置将被激活,允许跨域访问往期精彩文章双十一瞬间点击量过万,Redis热点 Key 问题发现与5种解决方案Redis 助力双十一背后电商秒杀系统Redis 集群下的RedLock算法(真分布式锁) 实践Redis 服务器被攻击后该如何安全加固Redis 缓存穿透,缓存雪崩解决方案分析理解:JWT鉴权的应用场景及使用建议架构:通过案例读懂 RESTful 架构风格架构:大数据推荐系统实时架构和离线架构数据库:MySQL 从删库到恢复,还用跑路吗微服务:架构下静态数据通用缓存机制微服务:小型系统如何“微服务”开发中间件:应用消息中间件设计可以解决哪些实际问题?面试题:40道Redis面试题含答案面试题:96道Java面试题和答案面试题:70道Spring面试题和答案 ...

November 14, 2018 · 1 min · jiezi

dubbo负载均衡策略及对应源码分析

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。我们还可以扩展自己的负责均衡策略,前提是你已经从一个小白变成了大牛,嘻嘻1、Random LoadBalance1.1 随机,按权重设置随机概率。1.2 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。1.3 源码分析package com.alibaba.dubbo.rpc.cluster.loadbalance;import java.util.List;import java.util.Random;import com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.rpc.Invocation;import com.alibaba.dubbo.rpc.Invoker;/** * random load balance. * * @author qianlei * @author william.liangf /public class RandomLoadBalance extends AbstractLoadBalance {public static final String NAME = “random”;private final Random random = new Random(); protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); // 总个数 int totalWeight = 0; // 总权重 boolean sameWeight = true; // 权重是否都一样 for (int i = 0; i < length; i++) { int weight = getWeight(invokers.get(i), invocation); totalWeight += weight; // 累计总权重 if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) { sameWeight = false; // 计算所有权重是否一样 } } if (totalWeight > 0 && ! sameWeight) { // 如果权重不相同且权重大于0则按总权重数随机 int offset = random.nextInt(totalWeight); // 并确定随机值落在哪个片断上 for (int i = 0; i < length; i++) { offset -= getWeight(invokers.get(i), invocation); if (offset < 0) { return invokers.get(i); } } } // 如果权重相同或权重为0则均等随机 return invokers.get(random.nextInt(length));}}说明:从源码可以看出随机负载均衡的策略分为两种情况a. 如果总权重大于0并且权重不相同,就生成一个1totalWeight(总权重数)的随机数,然后再把随机数和所有的权重值一一相减得到一个新的随机数,直到随机 数小于0,那么此时访问的服务器就是使得随机数小于0的权重所在的机器b. 如果权重相同或者总权重数为0,就生成一个1length(权重的总个数)的随机数,此时所访问的机器就是这个随机数对应的权重所在的机器2、RoundRobin LoadBalance2.1 轮循,按公约后的权重设置轮循比率。2.2 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。2.3 源码分析package com.alibaba.dubbo.rpc.cluster.loadbalance;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;import com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.common.utils.AtomicPositiveInteger;import com.alibaba.dubbo.rpc.Invocation;import com.alibaba.dubbo.rpc.Invoker; /** Round robin load balance.** @author qian.lei* @author william.liangf*/public class RoundRobinLoadBalance extends AbstractLoadBalance {public static final String NAME = “roundrobin”; private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();private final ConcurrentMap<String, AtomicPositiveInteger> weightSequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { String key = invokers.get(0).getUrl().getServiceKey() + “.” + invocation.getMethodName(); int length = invokers.size(); // 总个数 int maxWeight = 0; // 最大权重 int minWeight = Integer.MAX_VALUE; // 最小权重 for (int i = 0; i < length; i++) { int weight = getWeight(invokers.get(i), invocation); maxWeight = Math.max(maxWeight, weight); // 累计最大权重 minWeight = Math.min(minWeight, weight); // 累计最小权重 } if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样 AtomicPositiveInteger weightSequence = weightSequences.get(key); if (weightSequence == null) { weightSequences.putIfAbsent(key, new AtomicPositiveInteger()); weightSequence = weightSequences.get(key); } int currentWeight = weightSequence.getAndIncrement() % maxWeight; List<Invoker<T>> weightInvokers = new ArrayList<Invoker<T>>(); for (Invoker<T> invoker : invokers) { // 筛选权重大于当前权重基数的Invoker if (getWeight(invoker, invocation) > currentWeight) { weightInvokers.add(invoker); } } int weightLength = weightInvokers.size(); if (weightLength == 1) { return weightInvokers.get(0); } else if (weightLength > 1) { invokers = weightInvokers; length = invokers.size(); } } AtomicPositiveInteger sequence = sequences.get(key); if (sequence == null) { sequences.putIfAbsent(key, new AtomicPositiveInteger()); sequence = sequences.get(key); } // 取模轮循 return invokers.get(sequence.getAndIncrement() % length);}}说明:从源码可以看出轮循负载均衡的算法是:a. 如果权重不一样时,获取一个当前的权重基数,然后从权重集合中筛选权重大于当前权重基数的集合,如果筛选出的集合的长度为1,此时所访问的机器就是集合里面的权重对应的机器b. 如果权重一样时就取模轮循3、LeastActive LoadBalance3.1 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差(调用前的时刻减去响应后的时刻的值)。3.2 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大3.3 对应的源码package com.alibaba.dubbo.rpc.cluster.loadbalance;import java.util.List;import java.util.Random;import com.alibaba.dubbo.common.Constants;import com.alibaba.dubbo.common.URL;import com.alibaba.dubbo.rpc.Invocation;import com.alibaba.dubbo.rpc.Invoker;import com.alibaba.dubbo.rpc.RpcStatus;/*** LeastActiveLoadBalance* * @author william.liangf*/public class LeastActiveLoadBalance extends AbstractLoadBalance {public static final String NAME = “leastactive”;private final Random random = new Random();protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); // 总个数 int leastActive = -1; // 最小的活跃数 int leastCount = 0; // 相同最小活跃数的个数 int[] leastIndexs = new int[length]; // 相同最小活跃数的下标 int totalWeight = 0; // 总权重 int firstWeight = 0; // 第一个权重,用于于计算是否相同 boolean sameWeight = true; // 是否所有权重相同 for (int i = 0; i < length; i++) { Invoker<T> invoker = invokers.get(i); int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // 活跃数 int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 权重 if (leastActive == -1 || active < leastActive) { // 发现更小的活跃数,重新开始 leastActive = active; // 记录最小活跃数 leastCount = 1; // 重新统计相同最小活跃数的个数 leastIndexs[0] = i; // 重新记录最小活跃数下标 totalWeight = weight; // 重新累计总权重 firstWeight = weight; // 记录第一个权重 sameWeight = true; // 还原权重相同标识 } else if (active == leastActive) { // 累计相同最小的活跃数 leastIndexs[leastCount ++] = i; // 累计相同最小活跃数下标 totalWeight += weight; // 累计总权重 // 判断所有权重是否一样 if (sameWeight && i > 0 && weight != firstWeight) { sameWeight = false; } } } // assert(leastCount > 0) if (leastCount == 1) { // 如果只有一个最小则直接返回 return invokers.get(leastIndexs[0]); } if (! sameWeight && totalWeight > 0) { // 如果权重不相同且权重大于0则按总权重数随机 int offsetWeight = random.nextInt(totalWeight); // 并确定随机值落在哪个片断上 for (int i = 0; i < leastCount; i++) { int leastIndex = leastIndexs[i]; offsetWeight -= getWeight(invokers.get(leastIndex), invocation); if (offsetWeight <= 0) return invokers.get(leastIndex); } } // 如果权重相同或权重为0则均等随机 return invokers.get(leastIndexs[random.nextInt(leastCount)]);}}说明:源码里面的注释已经很清晰了,大致的意思就是活跃数越小的的机器分配到的请求越多4、ConsistentHash LoadBalance4.1 一致性 Hash,相同参数的请求总是发到同一提供者。4.2 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。4.3 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key=“hash.arguments” value=“0,1” />4.4 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key=“hash.nodes” value=“320” />4.5 源码分析暂时还没有弄懂,后面弄懂了再补充进来,有兴趣的小伙伴可以自己去看一下源码,然后一起交流一下心得如果想免费学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:478030634,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。5、dubbo官方的文档的负载均衡配置示例服务端服务级别 <dubbo:service interface="…" loadbalance=“roundrobin” />客户端服务级别 <dubbo:reference interface="…" loadbalance=“roundrobin” />服务端方法级别 <dubbo:service interface="…"> <dubbo:method name="…" loadbalance=“roundrobin”/> </dubbo:service>客户端方法级别 <dubbo:reference interface="…"> <dubbo:method name="…" loadbalance=“roundrobin”/> </dubbo:reference> ...

October 25, 2018 · 4 min · jiezi

记录一次亲身经历的dubbo项目实战

一、案例说明存在2个系统,A系统和B系统,A系统调用B系统的接口获取数据,用于查询用户列表。二、环境搭建安装zookeeper,解压(zookeeper-3.4.8.tar.gz)得到如下:然后进入conf将zoo_sample.cfg改名成zoo.cfg。并相关如下内容:该目录为存放数据的目录。然后启动,在bin目录下:三、工程创建1、搭建B工程1.导入依赖<dependencies><!– dubbo采用spring配置方式,所以需要导入spring容器依赖 –><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.1.3.RELEASE</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.4</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId><version>2.5.3</version><exclusions><exclusion><!– 排除传递spring依赖 –><artifactId>spring</artifactId><groupId>org.springframework</groupId></exclusion></exclusions></dependency><!– zookeeper依赖 –><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.3.3</version></dependency><dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId><version>0.1</version></dependency></dependencies>2.创建对象public class User implements Serializable{//序列化自动生成private static final long serialVersionUID = 1749666453251148943L;private Long id;private String username;private String password;private Integer age; //getter and setter}3.创建服务public class UserServiceImpl implements UserService {//实现查询,这里做模拟实现,不做具体的数据库查询public List<User> queryAll() {List<User> list = new ArrayList<User>();for (int i = 0; i < 10; i++) {User user = new User();user.setAge(10 + i);user.setId(Long.valueOf(i + 1));user.setPassword(“123456”);user.setUsername(“username_” + i);list.add(user);}return list;}}4.编写Dubbo的配置文件位置我放在根目录下dubbo/dubbo-server.xml,内容如下:<beans xmlns=“http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p=“http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx=“http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo=“http://code.alibabatech.com/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd"><!-- 提供方应用信息,用于计算依赖关系 –><dubbo:application name=“dubbo-b-server” /><!– 这里使用的注册中心是zookeeper –><dubbo:registry address=“zookeeper://127.0.0.1:2181” client=“zkclient”/><!– 用dubbo协议在20880端口暴露服务 –><dubbo:protocol name=“dubbo” port=“20880” /><!– 将该接口暴露到dubbo中 –><dubbo:service interface=“com.shen.dubbo.service.UserService” ref=“userServiceImpl” /><!– 将具体的实现类加入到Spring容器中 –><bean id=“userServiceImpl” class=“com.shen.dubbo.service.impl.UserServiceImpl” /></beans>5.编写Web.xml<?xml version=“1.0” encoding=“UTF-8”?><web-app xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://java.sun.com/xml/ns/javaee" xsi:schemaLocation=“http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id=“WebApp_ID” version=“2.5”><display-name>dubbo-b</display-name><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:dubbo/dubbo-.xml</param-value></context-param><!–Spring的ApplicationContext 载入 –><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>6.启动tomcat在控制台中将会看到如下内容:可以看到,已经将UserService服务注册到zookeeper注册中心,协议采用的是dubbo。2、搭建A工程1.拷贝基本文件从b系统中拷贝User对象、UserService接口到a系统2.编写Dubbo的配置文件<!– 提供方应用信息,用于计算依赖关系 –><dubbo:application name=“dubbo-a-consumer” /><!– 这里使用的注册中心是zookeeper –><dubbo:registry address=“zookeeper://127.0.0.1:2181” client=“zkclient”/><!– 从注册中心中查找服务 –><dubbo:reference id=“userService” interface=“com.shen.dubbo.service.UserService”/>3.编写UserService测试用例public class UserServiceTest {private UserService userService;@Beforepublic void setUp() throws Exception {ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“classpath:dubbo/.xml”);this.userService = applicationContext.getBean(UserService.class);}@Testpublic void testQueryAll() {List<User> users = this.userService.queryAll();for (User user : users) {System.out.println(user);}}}查看效果如下:可以看到,已经查询到10条数据,那么,也就是说A系统通过B系统提供的服务获取到了数据。在此我向大家推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多3、解决代码重复问题我们可以看到,在上面的案例中User实体和服务接口两个项目都需要使用,代码复用不高。那么我们可以将该部分代码抽取出来打成包,以供所有系统使用。故可以在创建一个工程项目名为dubbo-b-api。然后将相关的代码都放到该项目中,再在其它项目中导入该项目依赖即可。这也是我们在真实项目中应该做的事情,因为调用方未必知道细节。 ...

October 23, 2018 · 1 min · jiezi

Tomcat优化笔记

一千个人眼中就有一千个哈姆雷特。——伪西方谚语关于Tomcat的优化点之多,我估计没有上万,也有成千。不同的应用场景,不同的架构,不同的需求,都会对优化设置有不同要求。在这里我所记述的只是我自己在一些Tomcat应用中所设置的优化项,以备不时之需,并不是放之四海而皆准的准则。pom.xml对于maven项目来说,pom.xml设置是整个设置的核心,如果pom.xml设置不当,虽然有时候也可以编译运行,但总是会出现一些令人讨厌的警告。为了消除这些警告,还需要根治pom.xml。重复依赖首先要解决的是重复依赖问题,有时候我们会在编译项目时遇到下面的这样的警告:[WARNING][WARNING] Some problems were encountered while building the effective model for com.qiban.supplier:saas-supplier:war:1.0-SNAPSHOT[WARNING] ‘dependencies.dependency.(groupId:artifactId:type:classifier)’ must be unique: commons-codec:commons-codec:jar -> duplicate declaration of version 1.9 @ line 264, column 21[WARNING][WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.[WARNING][WARNING] For this reason, future Maven versions might no longer support building such malformed projects.[WARNING]解决的方法很简单:在pom.xml中搜索出现重复依赖的jar包名称,你肯定会发现对于同一个jar包,重复引用了多次,也许版本相同,也许版本不同,只要删除掉那些重复的就可以了。maven编译器版本有时候会遇到下面这样的错误:[WARNING][WARNING] Some problems were encountered while building the effective model for com.qiban.supplier:saas-supplier:war:1.0-SNAPSHOT[WARNING] ‘build.plugins.plugin.version’ for org.apache.maven.plugins:maven-compiler-plugin is missing. @ line 297, column 21[WARNING][WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.[WARNING][WARNING] For this reason, future Maven versions might no longer support building such malformed projects.[WARNING]这意思是说你没有在pom.xml里指定maven的版本,在pom.xml里添加maven的版本就可以了: <build> <finalName>saas-supplier</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>重点是上面那个<version>3.5.1</version>。jdom有的时候会遇到一行简单的警告:[WARNING] The artifact jdom:jdom🫙1.1 has been relocated to org.jdom:jdom🫙1.1这个的意思是说在你的pom.xml里,你需要把jdom的groupId改为org.jdom: <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1</version> </dependency>activemq-spring下面这个警告不会在编译时出现,但是会在运行时出现,也非常恶心:SLF4J: Class path contains multiple SLF4J bindings我们需要把pom.xml里的activemq-all改成activemq-spring: <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-spring</artifactId> <version>5.11.1</version> </dependency>详细解释可以看我的这篇文章。log4j.properties终于改完了pom.xml,我们开始处理log4j。xmemcached如果你使用了xmemcached,那么日志里会不断地出现xmemcached的警告,而这些警告对我们来说根本就不是警告,毫无意义,并且会掩盖真正的错误,所以我们通过修改log4j.properties文件屏蔽它:# xmemcachedlog4j.logger.com.google.code=OFFlog4j.logger.net.rubyeye.xmemcached=OFF我这里比较野蛮粗暴地直接使用了OFF选项,如果你不放心,可以改成ERROR选项,效果是一样的。webapp名称+%c如果你有多个webapp,为了准确显示到底是哪个webapp的哪个class报的错,我们需要在log4j.properties文件里注明我们的webapp名称,再加上一个%c符号:log4j.appender.STDOUT.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %-5p - activity - %c - %m%n这样下回再有任何错误,我们可以第一时间迅速定位到到底是哪个webapp的哪个class出的错误。logrotate如果我们不管不顾的话,Tomcat的日志文件几乎会无限制增长,最终会耗尽我们的硬盘空间,所以我们需要用logrotate来限制它一下,在/etc/logrotate.d文件夹下创建一个文件tomcat:/opt/tomcat1/logs/catalina.out/opt/tomcat2/logs/catalina.out{ copytruncate daily rotate 7 compress missingok size 10M}setenv.shTomcat不问青红皂白,上来就要占领我们主机整个物理内存的四分之一,我们需要限制它的大小,宁可浪费一些CPU和硬盘的时间去让它不断地垃圾回收,也不想让它占用这么多的内存,所以我们需要在/opt/tomcat/bin下建立一个setenv.sh文件,强制让它最多占用1G内存:export CATALINA_OPTS="$CATALINA_OPTS -Xms512m"export CATALINA_OPTS="$CATALINA_OPTS -Xmx1024m"这样我们一台16G内存的主机,可以同时运行16个Tomcat,而不像以前,最多只能同时运行4个Tomcat。jarsToSkipTomcat启动时会不断地扫描所有.jar文件,并且报一些不知所谓的警告:09-Dec-2017 20:03:14.289 FINE [localhost-startStop-1] org.apache.jasper.servlet.TldScanner$TldScannerCallback.scan No TLD files were found in [file:/home/apache-tomcat-8.5.4/lib/tomcat-redis-session-manager-master-2.0.0.jar]. Consider adding the JAR to the tomcat.util.scan.StandardJarScanFilter.jarsToSkip property in CATALINA_BASE/conf/catalina.properties file.直接在/opt/tomcat/conf/catalina.properties文件里把这一句话改成:tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar这个世界终于清静了,扫什么扫,有什么可扫的!startStopThreads当你有多个webapp的时候,Tomcat缺省会启完一个webapp再启下一个,这样太慢了,不可忍受,我们在/opt/tomcat/conf/server.xml文件里把它的启动线程数直接干到20个:<Host name=“localhost” appBase=“webapps” unpackWARs=“true” autoDeploy=“true” startStopThreads=“20” />Dubbo有时候Dubbo也会跳出来捣乱,在每一个不同的webapp下的consumer.xml文件里指定file:<dubbo:registry address=“zookeeper://${zookeeper.address}” file="${dubbo.cache}" />每个webapp的file名称各不相同,它们再也不会互相打架了。结语以上所述也不过只是冰山之一角,好记性不如烂笔头,记录下来作为以后每次优化时的依据,也许对遇到类似问题的你也略有启发吧。 ...

September 24, 2018 · 1 min · jiezi