Dubbo源码解析-服务调用(七)

news2024/11/23 16:19:35

一、服务调用流程

服务在订阅过程中,把notify 过来的urls 都转成了invoker,不知道大家是否还记得前面的rpc 过程,protocol也是在服务端和消费端各连接子一个invoker,如下图:

这张图主要展示rpc 主流程,消费端想要远程调用时,他是调用invoker#invoke方法;服务端收到网络请求时,也是直接触发invoker#invoke 方法。,Dubbo 设计invoker 实体的初衷,就是想要统一操作,无论你要做什么方法调用,都请使用invoker 来包装后,使用invoker#invoke来调用这个动作,简化来看,rpc 过程即是如此:

消费端invoker#invoke ------>网络---->服务端invoker#invoke--->ref 服务

 上面的链路是个简化的路径,但在实际的dubbo 调用中,此链条可能会有局部的多层嵌套,如:

消费端invoker#invoke ------>容错策略--->网络---->服务端invoker#invoke--->ref服务

那么此时要重新定义链条吗?那不是个好主意。Dubbo 的做法是这样,将容错策略也包装成invoker 对象: 

FailfastClusterInvoker#invoke--->protocolInvoker.invoke-->网络---->服务端invoker#invoke--->ref 服务

依次类推,dubbo 内部有非常多的invoker 包装类,它们层层嵌套,但rpc流程不关心细节,只傻瓜式地调用其invoke 方法,剩下的逻辑自会传递到最后一个invoker 进行网络调用。 

服务引入时,最终又对生成invoker的做了一层代理,所以熟悉动态代理的知道,当执行服务调用时最先执行到的是InvokerInvocationHandler#invoke方法

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
	try {
		return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
	} catch (Throwable fromJavassist) {
		// try fall back to JDK proxy factory
		try {
			T proxy = jdkProxyFactory.getProxy(invoker, interfaces);
			logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy success. " +
				"Interfaces: " + Arrays.toString(interfaces), fromJavassist);
			return proxy;
		} catch (Throwable fromJdk) {
			logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
				"Interfaces: " + Arrays.toString(interfaces) + " Javassist Error.", fromJavassist);
			logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
				"Interfaces: " + Arrays.toString(interfaces) + " JDK Error.", fromJdk);
			throw fromJavassist;
		}
	}
}

二、Dubbo过滤器链

Filter(过滤器)在很多框架中都有使用过这个概念,基本上的作用都是类似的,在请求处理前或者处理后做一些通用的逻辑,而且Filter 可以有多个,支持层层嵌套。

Dubbo 的Filter 实现入口是在ProtocolFilterWrapper,因为ProtocolFilterWrapper 是Protocol 的包装类,所以会在SPI 加载的Extension 的时候被自动包装进来。当然filter 要发挥作用,必定还是要
在嵌入到RPC 的调用线中(你马上应该反应过来,嵌入的办法就是包装成invoker)
ProtocolFilterWrapper 作为包装类,会成为其它protocol 的修饰加强外层。因此,protocol 的export 和refer 方法,首先是调用ProtocolFilterWrapper 类的。

暴露服务代码:

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	if (UrlUtils.isRegistry(invoker.getUrl())) {
		return protocol.export(invoker);
	}
	FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());
	return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}

引入服务代码:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
	if (UrlUtils.isRegistry(url)) {
		return protocol.refer(type, url);
	}
	FilterChainBuilder builder = getFilterChainBuilder(url);
	return builder.buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}

可以看到,两者原来的invoker 对象,都由builder#buildInvokerChain 做了一层包装。
来看一下filterChain 的逻辑

public <T> Invoker<T> buildInvokerChain(final Invoker<T> originalInvoker, String key, String group) {
	Invoker<T> last = originalInvoker;
	URL url = originalInvoker.getUrl();
    //......(省略部分代码)

	if (!CollectionUtils.isEmpty(filters)) {
		for (int i = filters.size() - 1; i >= 0; i--) {
			final Filter filter = filters.get(i);
			final Invoker<T> next = last;
			last = new CopyOfFilterChainNode<>(originalInvoker, next, filter);
		}
		return new CallbackRegistrationInvoker<>(last, filters);
	}

	return last;
}

逻辑较为简单:
1、所有filter 包装进invoker 对象中,invoke 方法直接调对应的filter#invoke。
2、filter 对象首尾相联,前一个filter#invoke 参数,传入后一个filter 的invoker对象
3、最后一个filter.invoke 参数中,直接传原始的invoker 对象
4、filter 的所有获取,按扩展点方式得到
ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); 

所以这里创建RpcInvocation,执行invoke方法,中间会经过一系列过滤器filter#invoke后,就会来到FailoverClusterInvoker#invoke方法中。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	RpcInvocation rpcInvocation = new RpcInvocation(serviceModel, method, invoker.getInterface().getName(), protocolServiceKey, args);
	String serviceKey = url.getServiceKey();
	rpcInvocation.setTargetServiceUniqueName(serviceKey);

	// invoker.getUrl() returns consumer url.
	RpcServiceContext.setRpcContext(url);

	if (serviceModel instanceof ConsumerModel) {
		rpcInvocation.put(Constants.CONSUMER_MODEL, serviceModel);
		rpcInvocation.put(Constants.METHOD_MODEL, ((ConsumerModel) serviceModel).getMethodModel(method));
	}

	if (ProfilerSwitch.isEnableSimpleProfiler()) {
		ProfilerEntry parentProfiler = Profiler.getBizProfiler();
		ProfilerEntry bizProfiler;
		boolean containsBizProfiler = false;
		if (parentProfiler != null) {
			containsBizProfiler = true;
			bizProfiler = Profiler.enter(parentProfiler, "Receive request. Client invoke begin.");
		} else {
			bizProfiler = Profiler.start("Receive request. Client invoke begin.");
		}
		rpcInvocation.put(Profiler.PROFILER_KEY, bizProfiler);
		try {
			return invoker.invoke(rpcInvocation).recreate();
		} finally {
			Profiler.release(bizProfiler);
			if (!containsBizProfiler) {
				int timeout;
				Object timeoutKey = rpcInvocation.getObjectAttachmentWithoutConvert(TIMEOUT_KEY);
				if (timeoutKey instanceof Integer) {
					timeout = (Integer) timeoutKey;
				} else {
					timeout = url.getMethodPositiveParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
				}
				long usage = bizProfiler.getEndTime() - bizProfiler.getStartTime();
				if ((usage / (1000_000L * ProfilerSwitch.getWarnPercent())) > timeout) {
					StringBuilder attachment = new StringBuilder();
					for (Map.Entry<String, Object> entry : rpcInvocation.getObjectAttachments().entrySet()) {
						attachment.append(entry.getKey()).append("=").append(entry.getValue()).append(";\n");
					}

					logger.warn(String.format("[Dubbo-Consumer] execute service %s#%s cost %d.%06d ms, this invocation almost (maybe already) timeout\n" +
							"invocation context:\n%s" +
							"thread info: \n%s",
						protocolServiceKey, methodName, usage / 1000_000, usage % 1000_000,
						attachment, Profiler.buildDetail(bizProfiler)));
				}
			}
		}
	}

	return invoker.invoke(rpcInvocation).recreate();
}

下面代码主要做了三件事情:

1、List<Invoker<T>> invokers = list(invocation);获取当前服务可执行的所有invokers数量。

2、LoadBalance loadbalance = initLoadBalance(invokers, invocation);利用SPI机制获取当前负载均衡策略LoadBalance。

3、doInvoke(invocation, invokers, loadbalance)。负载均衡策略会选择出一个invoker然后执行。 

public Result invoke(final Invocation invocation) throws RpcException {
	checkWhetherDestroyed();
	InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Router route.");
	
	List<Invoker<T>> invokers = list(invocation);
	InvocationProfilerUtils.releaseDetailProfiler(invocation);

	LoadBalance loadbalance = initLoadBalance(invokers, invocation);
	RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

	InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Cluster " + this.getClass().getName() + " invoke.");
	try {
		return doInvoke(invocation, invokers, loadbalance);
	} finally {
		InvocationProfilerUtils.releaseDetailProfiler(invocation);
	}
}

 我们知道一个服务的invokers都存在了registryDirectory中

protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
	return getDirectory().list(invocation);
}

public List<Invoker<T>> list(Invocation invocation) throws RpcException {
	if (destroyed) {
		throw new RpcException("Directory already destroyed .url: " + getUrl());
	}

	BitList<Invoker<T>> availableInvokers;
	// use clone to avoid being modified at doList().
	if (invokersInitialized) {
		availableInvokers = validInvokers.clone();
	} else {
		availableInvokers = invokers.clone();
	}

	List<Invoker<T>> routedResult = doList(availableInvokers, invocation);

	return Collections.unmodifiableList(routedResult);
}

然后回利用路由规则调用routerChain.route方法,路由出正确的invokers,具体路由功能后面再讲解。

public List<Invoker<T>> doList(BitList<Invoker<T>> invokers, Invocation invocation) {
	if (forbidden && shouldFailFast) {
		// 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).");
	}

	if (multiGroup) {
		return this.getInvokers();
	}

	try {
		// Get invokers from cache, only runtime routers will be executed.
		List<Invoker<T>> result = routerChain.route(getConsumerUrl(), invokers, invocation);
		return result == null ? BitList.emptyList() : result;
	} catch (Throwable t) {
		logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
		return BitList.emptyList();
	}
}

三、负载均衡

下面这行代码会获取到当前配置的负载均衡策略

LoadBalance loadbalance = initLoadBalance(invokers, invocation)

最终也是利用SPI机制加载,ExtensionLoader#getExtension(DEFAULT_LOADBALANCE) ,而默认的负载均衡策略可以看到是随机。

protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
	ApplicationModel applicationModel = ScopeModelUtil.getApplicationModel(invocation.getModuleModel());
	if (CollectionUtils.isNotEmpty(invokers)) {
		return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(
			invokers.get(0).getUrl().getMethodParameter(
				RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE
			)
		);
	} else {
		return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
	}
}

然后继续执行来到FailoverClusterInvoker#doInvoke方法,首先获取执行次数for循环进行调用,只要不是正常返回,就再试一次,默认的len为3次,这也是dubbo基础常识,在这里可以体现出来。

然后调用下面的select方法获取到一个invoker,最终会执行到loadBalance#doSelect方法,当然不同的负载均衡策略获取invoker的算法也不同,这里应用了模板模式。

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
	List<Invoker<T>> copyInvokers = invokers;
	checkInvokers(copyInvokers, invocation);
	String methodName = RpcUtils.getMethodName(invocation);
	int len = calculateInvokeTimes(methodName);
	// 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.
		if (i > 0) {
			checkWhetherDestroyed();
			copyInvokers = list(invocation);
			// check again
			checkInvokers(copyInvokers, invocation);
		}
		Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
		invoked.add(invoker);
		RpcContext.getServiceContext().setInvokers((List) invoked);
		boolean success = false;
		try {
			Result result = invokeWithContext(invoker, invocation);
			if (le != null && logger.isWarnEnabled()) {
				logger.warn("Although retry the method " + methodName
						+ " 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);
			}
			success = true;
			return result;
		} catch (RpcException e) {
			if (e.isBiz()) { // biz exception.
				throw e;
			}
			le = e;
		} catch (Throwable e) {
			le = new RpcException(e.getMessage(), e);
		} finally {
			if (!success) {
				providers.add(invoker.getUrl().getAddress());
			}
		}
	}
	throw new RpcException(le.getCode(), "Failed to invoke the method "
			+ methodName + " 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.getMessage(), le.getCause() != null ? le.getCause() : le);
}

RandomLoadBalance#doSelect方法,具体代码逻辑不做详细解释,主要是根据服务设置的权重进行随机选取,权重大的被选中的概率就会大一点,有兴趣的可以断点研究下,这里只是让大家了解负载均衡就是这么简单的东西,我们也可以创建一个自己的负载均衡策略,只需要继承AbstractLoadBalance并实现自己的doSelect方法,然后还需要配置指定用我们所写的负载均衡策略,从代码流程上可以看出来,dubbo的负载均衡策略其实是客户端负载均衡。

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
	// Number of invokers
	int length = invokers.size();

	if (!needWeightLoadBalance(invokers,invocation)){
		return invokers.get(ThreadLocalRandom.current().nextInt(length));
	}

	// Every invoker has the same weight?
	boolean sameWeight = true;
	// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
	int[] weights = new int[length];
	// The sum of weights
	int totalWeight = 0;
	for (int i = 0; i < length; i++) {
		int weight = getWeight(invokers.get(i), invocation);
		// Sum
		totalWeight += weight;
		// save for later use
		weights[i] = totalWeight;
		if (sameWeight && totalWeight != weight * (i + 1)) {
			sameWeight = false;
		}
	}
	if (totalWeight > 0 && !sameWeight) {
		// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
		int offset = ThreadLocalRandom.current().nextInt(totalWeight);
		// Return a invoker based on the random value.
		for (int i = 0; i < length; i++) {
			if (offset < weights[i]) {
				return invokers.get(i);
			}
		}
	}
	// If all invokers have the same weight value or totalWeight=0, return evenly.
	return invokers.get(ThreadLocalRandom.current().nextInt(length));
}

 那么dubbo中已经支持的负载均衡策略也有很多,依次是ConsistentHashLoadBalance(一次性哈希),LeastActiveLoadBalance(最少活跃数),RandomLoadBalance(按照权重随机),RoundRobinLoadBalance(轮训),ShortestResponseLoadBalance(最短响应策略);不过生产环境中权重策略跟轮训策略是使用的最多的两种负载均衡方式。

四、集群容错

集群容错的实现方式其实在生成invoker的时候已经看到了,dubbo会将生成的invoker最外层在包装一层ClusterInvoker,这里又利用到了一种设计模式叫做装饰者模式。因为远程服务调用过程总肯定会先执行到ClusterInvoker#doInvoke方法,那么每个容错ClusterInvoker实现类会按照自己的方式去执行,当然我们也能继承AbstractClusterInvoker方法,然后实现doInvoke方法,然后配置自己的集群容错策略,所以这个功能跟负载均衡一样简单。

Dubbo为应对不同的业务场景和需求,提供了以下几种集群容错策略,具体源码有兴趣的可以单独研究:

1. FailoverClusterInvoker(默认)
失败一次自动切换,自动重试其他invoker,默认三次。适用于读操作多、对实时性要求不高的场景。
2. Failfast
快速失败,立即报错,只发起一次调用。适用于非幂等性操作或实时性要求极高的场景。
3. Failsafe
失败安全,出现异常时直接忽略。适用于对结果不敏感的操作,如日志记录。
4. Failback
失败自动恢复,记录失败请求,定时重发。适用于可以异步处理且对实时性要求不高的操作。
5. Forking
并行调用多个服务器,只要一个成功即返回。注意提高响应速度的同时,需注意资源消耗和并行度控制。
6. Broadcast
广播调用所有服务提供者,任一报错即报错。适用于需要全局一致性的场景,如全局缓存更新。
选择合适的集群容错策略,对于优化Dubbo应用的性能和可靠性至关重要。

到此Dubbo的服务调用过程讲解完毕。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2246112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Postman之newman

系列文章目录 1.Postman之安装及汉化基本使用介绍 2.Postman之变量操作 3.Postman之数据提取 4.Postman之pm.test断言操作 5.Postman之newman Postman之newman 1.基础环境node安装1.1.配置环境变量1.2.安装newman和html报告组件 2.newman运行 newman可以理解为&#xff0c;没有…

shell脚本(五)

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

人口老龄化社区服务|基于springboot+vue的人口老龄化社区服务与管理平台(源码+数据库+文档)

目录 基于springbootvue的人口老龄化社区服务与管理平台 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c;阿里云…

初识WGCLOUD - 监测磁盘空间还能使用多久

WGCLOUD是一款免费开源的运维监控软件&#xff0c;性能优秀&#xff0c;部署简单&#xff0c;轻巧使用&#xff0c;支持大部分的Linux和Windows、安卓、MacOS等平台安装部署 最近发布的新版本 v3.5.4&#xff0c;WGCLOUD新增了可以自动计算每个磁盘剩余空间的可使用天数&#x…

Linux各种并发服务器优缺点

本文旨在介绍针对“无并发C/S模型”改进的方法总结以及各种改进方法的优缺点&#xff0c;具体函数的实现并不介绍。 1. 无并发C/S模型 创建服务器流程分析&#xff1a; socket()创建服务器的监听套接字bind()将服务器给服务器的监听套接字绑定IP地址和Port端口号listen()设置…

【PPTist】添加PPT模版

前言&#xff1a;这篇文章来探索一下如何应用其他的PPT模版&#xff0c;给一个下拉菜单&#xff0c;列出几个项目中内置的模版 PPT模版数据 &#xff08;一&#xff09;增加菜单项 首先在下面这个菜单中增加一个“切换模版”的菜单项&#xff0c;点击之后在弹出框中显示所有的…

【Python · PyTorch】卷积神经网络 CNN(LeNet-5网络)

【Python PyTorch】卷积神经网络 CNN&#xff08;LeNet-5网络&#xff09; 1. LeNet-5网络※ LeNet-5网络结构 2. 读取数据2.1 Torchvision读取数据2.2 MNIST & FashionMNIST 下载解包读取数据 2. Mnist※ 训练 LeNet5 预测分类 3. EMnist※ 训练 LeNet5 预测分类 4. Fash…

如何用通义灵码助力项目开发 | OceanBase obdiag 项目共建实践

本文来自 obdiag 项目共建的用户分享 一、背景 我的数据库探索之旅始于OceanBase。作为一位满怀好奇心的DBA&#xff0c;我内心始终怀揣着对数据库内部运作机制的无尽向往。开源如同一把钥匙&#xff0c;为我们这些求知欲旺盛的“好奇猫”解锁了通往新知的神秘大门。在众多分布…

如何给 Apache 新站点目录配置 SELinux ?

在 web 服务器管理领域&#xff0c;确保服务器环境的安全性至关重要。SELinux (Security-Enhanced Linux) 是保护 Linux 服务器最有效的工具之一&#xff0c;它是一种强制访问控制 (MAC mandatory access control) 安全机制。当使用最流行的 web 服务器 Apache 提供 web 内容时…

【电子物证培训】龙信助力三明市公安局电子物证取证竞赛

文章关键词&#xff1a;电子数据取证、手机取证、电子物证、介质取证 为了进一步提升福建省三明市公安机关刑侦部门在电子物证领域的专业技能&#xff0c;强化队伍实战能力&#xff0c;三明市公安机关刑侦部门举办电子物证专业技能竞赛&#xff0c;龙信受邀为竞赛提供全方位的…

【海思Hi3519DV500】双目网络相机套板硬件规划方案

Hi3519DV500双目网络相机套板是针对该芯片设计的一款 IP 编码板 PCBA&#xff0c;硬件接口支持双目sensor 接入&#xff0c;SDIO3.0 接口、USB2.0、USB3.0、UART 接口以及丰富的 IO 扩展应用&#xff0c;可根据各种使用场景设计相应扩展板&#xff0c;丰富外围接口&#xff0c;…

【青牛科技】电流模式PWM控制器系列--D4870

概述&#xff1a; D4870是用于开关电源的电流模式PWM(PWM)控制器系列产品。 该电路待机功耗低&#xff0c;启动电流低。在待机模式下&#xff0c;电路进入间歇工作模式&#xff0c;从而有效地降低电路的待机功耗。 电路的开关频率为 65KHz&#xff0c;抖动的振荡频率&…

对象:是什么,使用,遍历对象,内置对象

对象使用&#xff1a; 对象访问&#xff1a;&#xff08;对象每个属性之间用逗号隔开&#xff09; 补充&#xff1a;也可以通过 对象名[‘属性名’] 对象方法&#xff1a; 方法名:匿名函数 调用方法不需要控制台打印&#xff0c;只要调用就自动输出值 遍历对象&#xff1a; …

tcp/ip异常断开调试笔记——lwip

问题一&#xff1a;异常掉线 异常断开模拟 1、单片机端做服务端&#xff08;只监听一个客户端&#xff09;&#xff0c;电脑做客户端连接 2、尝试连接确定通信正常&#xff0c;断开网线。电脑客户端点击断开 3、经过一段时间&#xff08;超过tcp/ip 3次握手时间&#xff09…

JavaScript获取URL参数常见的4种方法

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…

40分钟学 Go 语言高并发:并发下载器开发实战教程

并发下载器开发实战教程 一、系统设计概述 1.1 功能需求表 功能模块描述技术要点分片下载将大文件分成多个小块并发下载goroutine池、分片算法断点续传支持下载中断后继续下载文件指针定位、临时文件管理进度显示实时显示下载进度和速度进度计算、速度统计错误处理处理下载过…

《Object类》

目录 一、Object类 1.1 定义与地位 1.2 toString()方法 1.3 equals()方法 1.4 hashcode()方法 一、Object类 1.1 定义与地位 Object类是Java语言中的根类&#xff0c;所有的类&#xff08;除了Object类&#xff09;都直接或间接继承自Object。这就意味着在Java中&#xf…

Vercel 设置自动部署 GitHub 项目

Vercel 设置自动部署 GitHub 项目 问题背景 最近 Vercel 调整了其部署政策&#xff0c;免费版用户无法继续使用自动部署功能&#xff0c;除非升级到 Pro 计划。但是&#xff0c;我们可以通过配置 Deploy Hooks 来实现同样的自动部署效果。 解决方案 通过设置 Vercel 的 Dep…

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…

Laravel对接SLS日志服务

Laravel对接SLS日志服务&#xff08;写入和读取&#xff09; 1、下载阿里云的sdk #通过composer下载 composer require alibabacloud/aliyun-log-php-sdk#对应的git仓库 https://github.com/aliyun/aliyun-log-php-sdk2、创建sdk请求的service <?phpnamespace App\Ser…