Dubbo源码学习
Dubbo源码学习
一、概述
Dubbo是一款高性能、轻量级基于Java的RPC开源框架。平时使用的非常多。但仅仅使用很难了解背后的原理,更不用提经常作为面试题出现在面试中,这篇文章主要从源码的角度解析dubbo源码比较重要的三个模块:服务导出、服务引入、负载均衡,其他的模块在以后有机会继续深入解析。
主要组件
Dubbo 在使用上有5个主要的组成部分:
- Provider: 服务提供方
- Consumer: 服务调用方
- Registry: 服务注册与发现的注册中心
- Monitor: 统计服务的调用次数和调用时间的监控中心
- Container: 服务运行容器
项目整体设计
- 左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口
- 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI
- 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类
- 蓝色虚线为初始化过程,即启动时组装链
- 红色实线为方法调用过程,即运行时调时链
- 紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法
- 主要的层级和说明
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
源码模块
源码地址:https://github.com/apache/dubbo.git
主要模块与说明:
- dubbo-registry——注册中心模块:各种注册中心(Multicast、Zookeeper、Redis、Simple)的实现和注册中心下发地址的实现。
- dubbo-cluster——集群模块:集群的抽象,以及 集群配置、负载均衡、容错、路由 的实现,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
- dubbo-common——公共逻辑模块:各种工具类和通用的模型。serialize 层放在 common 模块中,以便更大程度复用。
- dubbo-config——配置模块:Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节,Dubbo的4种配置方式:XML配置、属性配置、API配置、注解配置
- dubbo-rpc——远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。protocol 层和 proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
- dubbo-remoting——远程通信模块:Dubbo 协议的实现,提供了多种客户端和服务端通信功能,比如基于Grizzly、Netty、Tomcat等等,RPC用除了RMI的协议都要用到此模块。transport 层和 exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础
- dubbo-container——容器模块:一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为后台服务并不需要Tomcat/JBoss 等Web 容器的特性,没必要用 Web 容器去加载服务
- dubbo-monitor——监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
其他模块与说明
- dubbo-bootstrap——清理模块:这个模块只有一个类,是作为dubbo的引导类,并且在停止期间进行清理资源。
- dubbo-demo——示例模块:这个模块是快速启动示例,其中包含了服务提供方和调用方,注册中心用的是multicast,用XML配置方法。
- dubbo-filter——过滤器模块:一些过滤器。
- dubbo-plugin——插件模块。
- dubbo-serialization——序列化模块:封装了各类序列化框架的支持实现。
- dubbo-test——测试模块:封装了针对dubbo的性能测试、兼容性测试等功能。
二、拓展的基石 - dubbo SPI 源码解读
不同于java的SPI实现,dubbo 的 SPI 要求:
- 配置文件需放置在 META-INF/dubbo 路径下
- 格式是
name = 类的全路径限定名
格式,换行符分割,这样可以实现按需加载制定的类,如:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
- 被拓展的接口上需要 @SPI 注解
- 拓展类上可以有 @Adaptive 注解,如果有这个注解,dubbo会对拓展类实行“自适应拓展加载”(即:不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。其实现原理是:首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类)
Dubbo SPI 机制的入口是 dubbo-common 包的 org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader(String name),其接收一个"name",根据这个name 在 META-INF/dubbo 下对应接口名字的文件中加载指定的实现类。getExtensionLoader()的逻辑很简单,就是从本地缓存查待加载类的实例,存在则返回,不存在则调用 createExtension(String name) 创建,所以这个是其核心代码:
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()) {// 循环创建 Wrapper 实例for (Class<?> wrapperClass : wrapperClasses) {// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;} catch (Throwable t) {throw new IllegalStateException("...");}
}
通过代码,我们发现步骤如下:
- 通过 getExtensionClasses 获取所有的拓展类,getExtensionClasses会依次调用 loadExtensionClasses(), 调用 loadDirectory() 加载指定文件夹配置文件,并最终调用 loadResource() 加载资源
- 通过反射创建拓展对象
- 向拓展对象中注入依赖,Dubbo IOC 具体实现
- 将拓展对象包裹在相应的 Wrapper 对象中,AOP 具体实现
Dubbo IOC 的实现如下:
private T injectExtension(T instance) {try {if (objectFactory != null) {// 遍历目标类的所有方法for (Method method : instance.getClass().getMethods()) {// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 publicif (method.getName().startsWith("set")&& method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) {// 获取 setter 方法参数类型Class<?> pt = method.getParameterTypes()[0];try {// 获取属性名,比如 setName 方法对应属性名 nameString property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";// 从 ObjectFactory 中获取依赖对象Object object = objectFactory.getExtension(pt, property);if (object != null) {// 通过反射调用 setter 方法设置依赖method.invoke(instance, object);}} catch (Exception e) {logger.error("fail to inject via method...");}}}}} catch (Exception e) {logger.error(e.getMessage(), e);}return instance;
}
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中
三、服务导出
Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。
服务导出的入口在onApplicationEvent:
public void onApplicationEvent(ContextRefreshedEvent event) {// 是否有延迟导出 && 是否已导出 && 是不是已被取消导出if (isDelay() && !isExported() && !isUnexported()) {// 导出服务export();}
}
export()的逻辑比较简单就不贴代码了,主要根据状态判断立即导出还是延迟导出,在 export() 中的 doExport() 会做如下的校验:
- 检测 dubbo:service 标签的 interface 属性合法性,不合法则抛出异常
- 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
- 检测并处理泛化服务和普通服务类
- 检测本地存根配置,并进行相应的处理
- 对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常
校验完成后会执行真正的导出方法:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {// 省略无关代码if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) {// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 urlurl = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url);}String scope = url.getParameter(Constants.SCOPE_KEY);// 如果 scope = none,则什么都不做if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {// scope != remote,导出到本地if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {exportLocal(url);}// scope != local,导出到远程if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {if (registryURLs != null && !registryURLs.isEmpty()) {for (URL registryURL : registryURLs) {url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));// 加载监视器链接URL monitorUrl = loadMonitor(registryURL);if (monitorUrl != null) {// 将监视器链接作为参数添加到 url 中url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());}String proxy = url.getParameter(Constants.PROXY_KEY);if (StringUtils.isNotEmpty(proxy)) {registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);}// 为服务提供类(ref)生成 InvokerInvoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfigDelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);// 导出服务,并生成 ExporterExporter<?> exporter = protocol.export(wrapperInvoker);exporters.add(exporter);}// 不存在注册中心,仅导出服务} else {Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);Exporter<?> exporter = protocol.export(wrapperInvoker);exporters.add(exporter);}}}this.urls.add(url);
}
由于分支太多,每个分支的含义整理如下:
// 获取 ArgumentConfig 列表
for (遍历 ArgumentConfig 列表) {if (type 不为 null,也不为空串) { // 分支11. 通过反射获取 interfaceClass 的方法列表for (遍历方法列表) {1. 比对方法名,查找目标方法2. 通过反射获取目标方法的参数类型数组 argtypesif (index != -1) { // 分支21. 从 argtypes 数组中获取下标 index 处的元素 argType2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常} else { // 分支31. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数2. 添加 ArgumentConfig 字段信息到 map 中}}} else if (index != -1) { // 分支41. 添加 ArgumentConfig 字段信息到 map 中}
}
服务导出后会进行服务注册,具体的注册逻辑在刚才代码 export() 中:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {// ${导出服务}// 省略其他代码boolean register = registeredProviderUrl.getParameter("register", true);if (register) {// 注册服务register(registryUrl, registeredProviderUrl);ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);}final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);// 订阅 override 数据registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);// 省略部分代码
}
其中 register() 中 getRegistry() 会创建并返回注册中心,创建注册中心会调用 createRegistry(),createRegistry()是一个拓展点,取决于SPI加载的子类,以 ZK 为注册中心为例:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {super(url);if (url.isAnyHost()) {throw new IllegalStateException("registry address == null");}// 获取组名,默认为 dubboString group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);if (!group.startsWith(Constants.PATH_SEPARATOR)) {// group = "/" + groupgroup = Constants.PATH_SEPARATOR + group;}this.root = group;// 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporterzkClient = zookeeperTransporter.connect(url);// 添加状态监听器zkClient.addStateListener(new StateListener() {@Overridepublic void stateChanged(int state) {if (state == RECONNECTED) {try {recover();} catch (Exception e) {logger.error(e.getMessage(), e);}}}});
}
ZookeeperTransporter 的 connect() 创建Zookeeper 客户端,创建好后意味着注册中心就创建结束了。这里的 zookeeperTransporter 类型为自适应拓展类,因此 connect 方法会在被调用时决定加载什么类型的 ZookeeperTransporter 拓展,默认为 CuratorZookeeperTransporter,以CuratorZookeeperTransporter 为例:
public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> {private final CuratorFramework client;public CuratorZookeeperClient(URL url) {super(url);try {// 创建 CuratorFramework 构造器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());}// 构建 CuratorFramework 实例client = builder.build();// 添加监听器client.getConnectionStateListenable().addListener(new ConnectionStateListener() {@Overridepublic 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 (Exception e) {throw new IllegalStateException(e.getMessage(), e);}}
}
就是创建 CuratorZookeeperClient 并启动 CuratorFramework 实例,可参考 Curator 官方文档
服务调用
Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启
服务调用有两种方式:直连和基于注册中心进行引用。此处以注册中心引用为例。
当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject()中的 init(),并由该方法执行服务引用逻辑。得到 Invoker 实例,Invoker具备调用服务的功能但不能直接暴露给用户,会造成业务代码的入侵,所以还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。
init() 的方法比较长也不是很重要主要做下面的事情:
- 检测 ConsumerConfig 实例是否存在,如不存在则创建一个新的实例,然后通过系统变量或 dubbo.properties 配置文件填充 ConsumerConfig 的字段
- 检测泛化配置,并根据配置设置 interfaceClass 的值
- 从系统属性或配置文件中加载与接口名相对应的配置,并将解析结果赋值给 url 字段。url 字段的作用一般是用于点对点调用
- 收集各种配置,并将配置存储到 map 中
- 处理 MethodConfig 实例。该实例包含了事件通知配置,比如 onreturn、onthrow、oninvoke 等。
- 解析服务消费者 ip,以及调用 createProxy 创建代理对象
createProxy() 看名字以为是创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并 Invoker 实例,真正重要的是其中的 refer() 方法:
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {optimizeSerialization(url);// 创建 DubboInvokerDubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);invokers.add(invoker);return invoker;
}
其中,getClients() 用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等,默认情况下,Dubbo 使用 NettyClient 进行通信。
完成Invoker 创建后,接下来就是为服务接口生成代理对象,有了代理对象后就可以进行远程调用。代理对象生成的入口方法为 ProxyFactory 的 getProxy:
public static Proxy getProxy(Class<?>... ics) {// 调用重载方法return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {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 {// 重新加载接口类tmp = Class.forName(itf, false, cl);} catch (ClassNotFoundException e) {}// 检测接口是否相同,这里 tmp 有可能为空if (tmp != ics[i])throw new IllegalArgumentException(ics[i] + " is not visible from class loader");// 拼接接口全限定名,分隔符为 ;sb.append(itf).append(';');}// 使用拼接后的接口名作为 keyString key = sb.toString();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 {// 从缓存中获取 Reference<Proxy> 实例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 {// 放置标志位到缓存中,并跳出 while 循环进行后续操作cache.put(key, PendingGenerationMarker);break;}}while (true);}long id = PROXY_CLASS_COUNTER.getAndIncrement();String pkg = null;ClassGenerator ccp = null, ccm = null;try {// 创建 ClassGenerator 对象ccp = ClassGenerator.newInstance(cl);Set<String> worked = new HashSet<String>();List<Method> methods = new ArrayList<Method>();for (int i = 0; i < ics.length; i++) {// 检测接口访问级别是否为 protected 或 priveteif (!Modifier.isPublic(ics[i].getModifiers())) {// 获取接口包名String npkg = ics[i].getPackage().getName();if (pkg == null) {pkg = npkg;} else {if (!pkg.equals(npkg))// 非 public 级别的接口必须在同一个包下,否者抛出异常throw new IllegalArgumentException("non-public interfaces from different packages");}}// 添加接口到 ClassGenerator 中ccp.addInterface(ics[i]);// 遍历接口方法for (Method method : ics[i].getMethods()) {// 获取方法描述,可理解为方法签名String desc = ReflectUtils.getDesc(method);// 如果方法描述字符串已在 worked 中,则忽略。考虑这种情况,// A 接口和 B 接口中包含一个完全相同的方法if (worked.contains(desc))continue;worked.add(desc);int ix = methods.size();// 获取方法返回值类型Class<?> rt = method.getReturnType();// 获取参数列表Class<?>[] pts = method.getParameterTypes();// 生成 Object[] args = new Object[1...N]StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");for (int j = 0; j < pts.length; j++)// 生成 args[1...N] = ($w)$1...N;code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");// 生成 InvokerHandler 接口的 invoker 方法调用语句,如下:// Object ret = handler.invoke(this, methods[1...N], args);code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");// 返回值不为 voidif (!Void.TYPE.equals(rt))// 生成返回语句,形如 return (java.lang.String) ret;code.append(" return ").append(asArgument(rt, "ret")).append(";");methods.add(method);// 添加方法名、访问控制符、参数列表、方法代码等信息到 ClassGenerator 中 ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());}}if (pkg == null)pkg = PACKAGE_NAME;// 构建接口代理类名称:pkg + ".proxy" + id,比如 org.apache.dubbo.proxy0String pcn = pkg + ".proxy" + id;ccp.setClassName(pcn);ccp.addField("public static java.lang.reflect.Method[] methods;");// 生成 private java.lang.reflect.InvocationHandler handler;ccp.addField("private " + InvocationHandler.class.getName() + " handler;");// 为接口代理类添加带有 InvocationHandler 参数的构造方法,比如:// porxy0(java.lang.reflect.InvocationHandler arg0) {// handler=$1;// }ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");// 为接口代理类添加默认构造方法ccp.addDefaultConstructor();// 生成接口代理类Class<?> clazz = ccp.toClass();clazz.getField("methods").set(null, methods.toArray(new Method[0]));// 构建 Proxy 子类名称,比如 Proxy1,Proxy2 等String fcn = Proxy.class.getName() + id;ccm = ClassGenerator.newInstance(cl);ccm.setClassName(fcn);ccm.addDefaultConstructor();ccm.setSuperClass(Proxy.class);// 为 Proxy 的抽象方法 newInstance 生成实现代码,形如:// public Object newInstance(java.lang.reflect.InvocationHandler h) { // return new org.apache.dubbo.proxy0($1);// }ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");// 生成 Proxy 实现类Class<?> pc = ccm.toClass();// 通过反射创建 Proxy 实例proxy = (Proxy) pc.newInstance();} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {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;
}
比较复杂,需要根据注释仔细研读。以上就是服务的调用重要源码。
复杂均衡
Dubbo 提供了4种负载均衡实现,分别是基于权重随机算法的 RandomLoadBalance、基于最少活跃调用数算法的 LeastActiveLoadBalance、基于 hash 一致性的 ConsistentHashLoadBalance,以及基于加权轮询算法的 RoundRobinLoadBalance
Dubbo 中,所有负载均衡实现类均继承自 AbstractLoadBalance,该类实现了 LoadBalance 接口,并封装了一些公共的逻辑。select() 是负载均衡的入口方法。select() 中逻辑很简单主要是调用 SPI 实现类的 soSelect() 方法完成负载均衡,下面依次介绍四种负载均衡算法的实现:
RandomLoadBalance
public class RandomLoadBalance extends AbstractLoadBalance {public static final String NAME = "random";private final Random random = new Random();@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {int length = invokers.size();int totalWeight = 0;boolean sameWeight = true;// 下面这个循环有两个作用,第一是计算总权重 totalWeight,// 第二是检测每个服务提供者的权重是否相同for (int i = 0; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);// 累加权重totalWeight += weight;// 检测当前服务提供者的权重与上一个服务提供者的权重是否相同,// 不相同的话,则将 sameWeight 置为 false。if (sameWeight && i > 0&& weight != getWeight(invokers.get(i - 1), invocation)) {sameWeight = false;}}// 下面的 if 分支主要用于获取随机数,并计算随机数落在哪个区间上if (totalWeight > 0 && !sameWeight) {// 随机获取一个 [0, totalWeight) 区间内的数字int offset = random.nextInt(totalWeight);// 循环让 offset 数减去服务提供者权重值,当 offset 小于0时,返回相应的 Invoker。// 举例说明一下,我们有 servers = [A, B, C],weights = [5, 3, 2],offset = 7。// 第一次循环,offset - 5 = 2 > 0,即 offset > 5,// 表明其不会落在服务器 A 对应的区间上。// 第二次循环,offset - 3 = -1 < 0,即 5 < offset < 8,// 表明其会落在服务器 B 对应的区间上for (int i = 0; i < length; i++) {// 让随机值 offset 减去权重值offset -= getWeight(invokers.get(i), invocation);if (offset < 0) {// 返回相应的 Invokerreturn invokers.get(i);}}}// 如果所有服务提供者权重值相同,此时直接随机返回一个即可return invokers.get(random.nextInt(length));}
}
它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。
最小活跃数负载均衡 LeastActiveLoadBalance
public class LeastActiveLoadBalance extends AbstractLoadBalance {public static final String NAME = "leastactive";private final Random random = new Random();@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {int length = invokers.size();// 最小的活跃数int leastActive = -1;// 具有相同“最小活跃数”的服务者提供者(以下用 Invoker 代称)数量int leastCount = 0; // leastIndexs 用于记录具有相同“最小活跃数”的 Invoker 在 invokers 列表中的下标信息int[] leastIndexs = new int[length];int totalWeight = 0;// 第一个最小活跃数的 Invoker 权重值,用于与其他具有相同最小活跃数的 Invoker 的权重进行对比,// 以检测是否“所有具有相同最小活跃数的 Invoker 的权重”均相等int firstWeight = 0;boolean sameWeight = true;// 遍历 invokers 列表for (int i = 0; i < length; i++) {Invoker<T> invoker = invokers.get(i);// 获取 Invoker 对应的活跃数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) {// 使用当前活跃数 active 更新最小活跃数 leastActiveleastActive = active;// 更新 leastCount 为 1leastCount = 1;// 记录当前下标值到 leastIndexs 中leastIndexs[0] = i;totalWeight = weight;firstWeight = weight;sameWeight = true;// 当前 Invoker 的活跃数 active 与最小活跃数 leastActive 相同 } else if (active == leastActive) {// 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标leastIndexs[leastCount++] = i;// 累加权重totalWeight += weight;// 检测当前 Invoker 的权重与 firstWeight 是否相等,// 不相等则将 sameWeight 置为 falseif (sameWeight && i > 0&& weight != firstWeight) {sameWeight = false;}}}// 当只有一个 Invoker 具有最小活跃数,此时直接返回该 Invoker 即可if (leastCount == 1) {return invokers.get(leastIndexs[0]);}// 有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同if (!sameWeight && totalWeight > 0) {// 随机生成一个 [0, totalWeight) 之间的数字int offsetWeight = random.nextInt(totalWeight);// 循环让随机数减去具有最小活跃数的 Invoker 的权重值,// 当 offset 小于等于0时,返回相应的 Invokerfor (int i = 0; i < leastCount; i++) {int leastIndex = leastIndexs[i];// 获取权重值,并让随机数减去权重值 - ⭐️offsetWeight -= getWeight(invokers.get(leastIndex), invocation);if (offsetWeight <= 0)return invokers.get(leastIndex);}}// 如果权重相同或权重为0时,随机返回一个 Invokerreturn invokers.get(leastIndexs[random.nextInt(leastCount)]);}
}
代码过程如下:
- 遍历 invokers 列表,寻找活跃数最小的 Invoker
- 如果有多个 Invoker 具有相同的最小活跃数,此时记录下这些 Invoker
- 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等
- 如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可
- 如果有多个 Invoker 具有最小活跃数,且它们的权重不相等,此时处理方式和 RandomLoadBalance 一致
- 如果有多个 Invoker 具有最小活跃数,但它们的权重相等,此时随机返回一个即可
从代码可以看出来,除了最小活跃数,LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说,LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。举个例子说明一下,在一个服务提供者集群中,有两个性能优异的服务提供者。某一时刻它们的活跃数相同,此时 Dubbo 会根据它们的权重去分配请求,权重越大,获取到新请求的概率就越大。如果两个服务提供者权重相同,此时随机选择一个即可。
ConsistentHashLoadBalance
一致性hash算法原理是:首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 232 - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。
dubbo 对其的实现类 ConsistentHashLoadBalance的 doSelect()方法只做了一些前置工作,真正实现在 ConsistentHashSelector 的 select方法中,其中 virtualInvokers 是一个 treeMap,记录了各个节点的虚拟节点的 hash -> Invoker,初始化工作在 ConsistentHashSelector 的构造方法中完成,由于篇幅原因没有贴出来,下面的 select() 算法的实际步骤:
public Invoker<T> select(Invocation invocation) {// 将参数转为 keyString key = toKey(invocation.getArguments());// 对参数 key 进行 md5 运算byte[] digest = md5(key);// 取 digest 数组的前四个字节进行 hash 运算,再将 hash 值传给 selectForKey 方法,// 寻找合适的 Invokerreturn selectForKey(hash(digest, 0));
}private Invoker<T> selectForKey(long hash) {// 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 InvokerMap.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();// 如果 hash 大于 Invoker 在圆环上最大的位置,此时 entry = null,// 需要将 TreeMap 的头节点赋值给 entryif (entry == null) {entry = virtualInvokers.firstEntry();}// 返回 Invokerreturn entry.getValue();
}
加权轮询负载均衡 RoundRobinLoadBalance
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> indexSeqs = new ConcurrentHashMap<String, AtomicPositiveInteger>();@Overrideprotected <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;final List<Invoker<T>> invokerToWeightList = new ArrayList<>();// 查找最大和最小权重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 (weight > 0) {invokerToWeightList.add(invokers.get(i));}}// 获取当前服务对应的调用序列对象 AtomicPositiveIntegerAtomicPositiveInteger sequence = sequences.get(key);if (sequence == null) {// 创建 AtomicPositiveInteger,默认值为0sequences.putIfAbsent(key, new AtomicPositiveInteger());sequence = sequences.get(key);}// 获取下标序列对象 AtomicPositiveIntegerAtomicPositiveInteger indexSeq = indexSeqs.get(key);if (indexSeq == null) {// 创建 AtomicPositiveInteger,默认值为 -1indexSeqs.putIfAbsent(key, new AtomicPositiveInteger(-1));indexSeq = indexSeqs.get(key);}if (maxWeight > 0 && minWeight < maxWeight) {length = invokerToWeightList.size();while (true) {int index = indexSeq.incrementAndGet() % length;int currentWeight = sequence.get() % maxWeight;// 每循环一轮(index = 0),重新计算 currentWeightif (index == 0) {currentWeight = sequence.incrementAndGet() % maxWeight;}// 检测 Invoker 的权重是否大于 currentWeight,大于则返回if (getWeight(invokerToWeightList.get(index), invocation) > currentWeight) {return invokerToWeightList.get(index);}}}// 所有 Invoker 权重相等,此时进行普通的轮询即可return invokers.get(sequence.incrementAndGet() % length);}
}
代码逻辑是这样的:每进行一轮循环,重新计算 currentWeight。如果当前 Invoker 权重大于 currentWeight,则返回该 Invoker。下面举例说明,假设服务器 [A, B, C] 对应权重 [5, 2, 1]。
- 第一轮循环,currentWeight = 1,可返回 A 和 B
- 第二轮循环,currentWeight = 2,返回 A
- 第三轮循环,currentWeight = 3,返回 A
- 第四轮循环,currentWeight = 4,返回 A
- 第五轮循环,currentWeight = 0,返回 A, B, C
如上,这里的一轮循环是指 index 再次变为0所经历过的循环,这里可以把 index = 0 看做是一轮循环的开始。每一轮循环的次数与 Invoker 的数量有关,Invoker 数量通常不会太多,所以我们可以认为上面代码的时间复杂度为常数级。
以上就是 dubbo 四种负载均衡算法的实现
总结
Dubbo 是目前最流行的 rpc 框架之一,也是面试过程中经常遇到的面试题,了解其中的原理有利于更好的使用,更能化解面试中的尴尬,还能丰富自身的编程经验和架构经验。dubbo 还更多的源码值得深入分析,但服务提供和服务调用是其中比较重要的组成部分,其他还包括:通信协议、服务治理、服务路由、高可用集群搭建模式等等。将在以后逐步解析。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- SpringMVC_03
DAY3SSM 整合ssm整合Spring框架ssm整合SpringMVC框架Spring整合SpringMvc框架Spring整合MyBatis框架 SSM 整合ssm搭建环境 在pom.xml中导入项目所需坐标,创建所需要的包和类。ssm整合Spring框架想要将类交给spring容器接管,必须写一个配置文件,开启注解扫描<?xml versio…...
2024/4/11 20:03:08 - 关于BIO、NIO、AIO的理解
Java的BIO、NIO、AIO模型与linux的几种IO模型有点像,倒不如确切的说,我们使用的Java的IO模型,是java对os的各种IO模型的封装,我们只是使用的的IO的API,但是底层还是依赖与os层面的IO操作来实现。在linux2.6以后,Java中NIO与AIO都是通过epoll来实现的。在windows上,AIO是…...
2024/4/11 20:03:08 - Qt Creator模块学习——文件,目录和输入/输出(二)
Qt Creator模块学习——文件,目录和输入/输出(二) 这篇文章是读写文件的重点我用了三种方式来读写文件,有什么不足之处希望可以得到大家的建议 如果是初学的小伙伴可以看: Qt Creator模块学习——文件,目录和输入/输出(一) 2. 读写文件(采用的是基本方式QFile方式) 在…...
2024/4/25 13:29:45 - iOS 的事件处理机制
目录事件的种类事件响应链事件响应者如何成为事件响应者Hit-Test响应方法和响应事件传递事件处理机制的应用手势控制器手势控制器的种类使用Hit-Test来扩大点击范围处理兄弟控件的事件事件处理机制在系统控件中UIButtonUIScrollView参考资料 事件的种类Touch Events 触摸事件 M…...
2024/5/5 5:28:13 - JavaScript基础知识四
1、循环1、do...while()1、语法do{//循环体}while(条件);执行流程:1、先执行循环体2、再判断条件如果条件为真,则继续执行循环体如果条件为假,则跳出循环结构2、do...while 与 while 之间的比较1、do...while(至少1次)先执行循环体,再判断条件即便条件不满足,也要执行一次…...
2024/5/3 2:54:51 - Windows基础介绍
系统目录、服务、端口、注册表 系统目录 ~用户目录:存放创建的用户的配置文件 ~Windows:系统安装目录 system32:存放系统配置文件,不要轻易乱动 C:\Windows\System32\config\SAM:存放windows用户名和密码的文件,打不开。因为只要正常开机登录这个文件就会被…...
2024/4/22 6:18:49 - sessionStorage 存、取、删除、清空数据方法封装
存数据:sessionStorageSet // 存数据export const sessionStorageSet = (name, obj) => { if (typeof obj === undefined) { return false } // obj为undefined或null或空字符串不能存储,布尔值可存储,但取时为字符串 if(!obj && (typeof obj === undefined…...
2024/4/10 11:52:32 - c# des方式加密解密完整代码
c# des方式加密解密完整代码, 创建class 类 ,讲下面代码粘贴进去就可以直接 使用using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace DecryptCode { public class Dec…...
2024/5/4 17:31:56 - 【STM32】使用AC6编译RTThread工程出现 ‘cpuport.c(459): error: use of undeclared identifier ‘CMP‘‘的解决办法
错误现象 在cortex-m4系列芯片上使用AC6对RT-Thread编译会出现三个错误 rt-thread/libcpu/arm/cortex-m4/cpuport.c(457): error: expected ( after asm __asm int __rt_ffs(int value)^ rt-thread/libcpu/arm/cortex-m4/cpuport.c(457): error: expected ; after top-level as…...
2024/4/24 18:31:39 - 网站流量突然下降如何处理?
站长首先是应该分析出网站流量哪些来源出现减少的情况,在对比数据的过程中要注意日期时段的可比性,这样就会很容易分析出网站搜索流量、引荐流量、直接访问网站流量的3种流量中哪部分网站流量出现了有问题了。 网站流量出现突然下降如何分析? 网站流量分析最常见的应该就是当…...
2024/4/11 20:03:03 - 关于H5开发
文章目录H5开发概念 H5开发概念 什么是H5开发,H5开发的应用场景 H5开发=移动端页面开发具有移动端自适应能力,简单来说就是H5页面会根据不同的手机屏幕尺寸进行适配,以达到不同屏幕的最佳显示效果。应用场景答题测试、投票、用户调研等 好友生日祝福,节日祝福,宴会邀约等各…...
2024/4/11 20:03:02 - Centos7安装LAMP环境搭建WordPress
LAMP 1.安装Apache服务 1.确保服务器系统处于最新状态 [root@localhost ~]# yum -y update 如果显示以下内容说明已经更新完成 Replaced: grub2.x86_64 1:2.02-0.64.el7.centos grub2-tools.x86_64 1:2.02-0.64.el7.centos Complete! 2.重启服务器 [root@localhost ~]# reboo…...
2024/4/17 16:06:36 - LeetCode 35. 搜索插入位置(二分查找)
上一篇博客:LeetCode 28. 实现 strStr()(KMP、字符串)写在前面:大家好!我是ACfun,我的昵称来自两个单词Accepted和fun。我是一个热爱ACM的蒟蒻。最近萌生了刷LeetCode的想法,所以我打算从LeetCode简单的题目开始做起,攻陷LeetCode。如果博客中有不足或者的错误的地方欢迎…...
2024/4/11 20:03:00 - 09#.Dubbo Architecture Dubbo架构的初入门
...
2024/4/11 20:02:59 - 微信小程序实现人脸识别注册登录
前言这是一篇关于一个原创微信小程序开发过程的原创文章。涉及到的核心技术是微信小程序开发方法和百度云人脸识别接口。小程序的主体是一个用于个人密码存储的密码管理器,在登陆注册阶段,需要调用百度云人脸识别接口以及百度云在线人脸库的管理接口。本文主要涉及登陆注册模…...
2024/4/11 20:02:58 - 低危漏洞 X-Frame-Options 防止网页被 Frame 或 Object
1、X-Frame-Options HTTP响应头是用来给浏览器指示允许一个页面可否在<frame>, <iframe>或者<object>中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。2、解决方案:修改web…...
2024/4/27 3:49:49 - Swift控制流程
1、使用for循环遍历数组//不需要变量时,可以使用下划线_代替变量for _ in 1..<6{print("字符串")}for i in 1..<10{print(i)}for i in 1...10{print(i)}for item in [1, 2, 3, 4, 5]{print(item)}2、使用循环语句获得序列中的最小值let aaa = ["bbb"…...
2024/4/25 11:42:40 - JDK源码阅读项目的搭建
一、JDK源码的重要性JDK源码的重要性不言而喻,平时的面试、深入学习等都离不开JDK的源码。当然,JDK源码是非常优秀的代码,我们之所以阅读JDK源码,就是为了理解底层原理、学习优秀的设计模式和思想。不过JDK源码也是相当难啃的知识点,我们一定要有信心,不要畏难,硬着头…...
2024/4/23 1:59:14 - 非技术用户也能看懂的WAN基础-VeCloud
我们先从WAN技术的基础开始。 首先,WAN到底是什么? WAN或广域网是支持公司电信和网络流量的网络。它可以由不同类型的链接组成,包括Internet和MPLS,并且可以将较小的网络连接到局域网(LAN)或城域网(MAN)。这使您公司的工作人员可以连接并利用存储在网络其他区域(例如组…...
2024/4/12 23:08:49 - 111.二叉树的最小深度 -----力扣每日打卡Day17
目录1.题目2.题目分析3.代码实现 1.题目 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。 示例: 给定二叉树 [3,9,20,null,null,15,7],3/ \9 20/ \15 7返回它的最小深度 2. C语言函数头…...
2024/4/11 20:02:53
最新文章
- 【区块链】共识算法简介
共识算法简介 区块链三要素: 去中心化共识算法智能合约 共识算法作为区块链三大核心技术之一,其重要性不言而喻。今天就来简单介绍共识算法的基本知识。 最简单的解释,共识算法就是要让所有节点达成共识,保证少数服从多数&#x…...
2024/5/5 8:58:08 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 大话设计模式——23.备忘录模式(Memento Pattern)
简介 又称快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并且该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态 UML图 应用场景 允许用户取消不确定或者错误的操作,能够恢复到原先的状态游戏存档、…...
2024/5/1 14:16:30 - 巨控科技新品发布:全方位升级,引领智能控制新纪元
标签: #巨控科技 #智能控制 #新品发布 #GRM560 #OPC560 #NET400 在智能控制领域,巨控科技始终以其前沿技术和创新产品引领着市场的潮流。近日,巨控科技再次以其行业领先的研发实力,推出了三大系列的新产品,旨在为各行各业提供更…...
2024/5/1 13:03:39 - 小林coding图解计算机网络|基础篇01|TCP/IP网络模型有哪几层?
小林coding网站通道:入口 本篇文章摘抄应付面试的重点内容,详细内容还请移步: 文章目录 应用层(Application Layer)传输层(Transport Layer)TCP段(TCP Segment) 网络层(Internet Layer)IP协议的寻址能力IP协议的路由能力 数据链路层(Link Lay…...
2024/5/5 8:35:23 - 416. 分割等和子集问题(动态规划)
题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义:dp[i][j]表示当背包容量为j,用前i个物品是否正好可以将背包填满ÿ…...
2024/5/4 12:05:22 - 【Java】ExcelWriter自适应宽度工具类(支持中文)
工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...
2024/5/4 11:23:32 - Spring cloud负载均衡@LoadBalanced LoadBalancerClient
LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon…...
2024/5/4 14:46:16 - TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案
一、背景需求分析 在工业产业园、化工园或生产制造园区中,周界防范意义重大,对园区的安全起到重要的作用。常规的安防方式是采用人员巡查,人力投入成本大而且效率低。周界一旦被破坏或入侵,会影响园区人员和资产安全,…...
2024/5/4 23:54:44 - VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...
2024/5/4 12:10:13 - 【Objective-C】Objective-C汇总
方法定义 参考:https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...
2024/5/4 23:54:49 - 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】🌏题目描述🌏输入格…...
2024/5/4 23:54:44 - 【ES6.0】- 扩展运算符(...)
【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数࿰…...
2024/5/4 14:46:12 - 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?
文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕,各大品牌纷纷晒出优异的成绩单,摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称,在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁,多个平台数据都表现出极度异常…...
2024/5/4 14:46:11 - Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
2024/5/4 14:46:11 - 用欧拉路径判断图同构推出reverse合法性:1116T4
http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b,我们在 a i a_i ai 和 a i 1 a_{i1} ai1 之间连边, b b b 同理,则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然࿰…...
2024/5/5 2:25:33 - 【NGINX--1】基础知识
1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息,并安装一些有助于配置官方 NGINX 软件包仓库的软件包: apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...
2024/5/4 21:24:42 - Hive默认分割符、存储格式与数据压缩
目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限(ROW FORMAT)配置标准HQL为: ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...
2024/5/4 12:39:12 - 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法
文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中,传感器和控制器产生大量周…...
2024/5/4 13:16:06 - --max-old-space-size=8192报错
vue项目运行时,如果经常运行慢,崩溃停止服务,报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中,通过JavaScript使用内存时只能使用部分内存(64位系统&…...
2024/5/4 16:48:41 - 基于深度学习的恶意软件检测
恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞,例如可以被劫持的合法软件(例如浏览器或 Web 应用程序插件)中的错误。 恶意软件渗透可能会造成灾难性的后果,包括数据被盗、勒索或网…...
2024/5/4 14:46:05 - JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...
2024/5/5 3:37:58 - C++中只能有一个实例的单例类
C中只能有一个实例的单例类 前面讨论的 President 类很不错,但存在一个缺陷:无法禁止通过实例化多个对象来创建多名总统: President One, Two, Three; 由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目…...
2024/5/4 23:54:30 - python django 小程序图书借阅源码
开发工具: PyCharm,mysql5.7,微信开发者工具 技术说明: python django html 小程序 功能介绍: 用户端: 登录注册(含授权登录) 首页显示搜索图书,轮播图࿰…...
2024/5/4 9:07:39 - 电子学会C/C++编程等级考试2022年03月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...
2024/5/4 14:46:02 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57