Nacos深入原理从源码层面讲解

文章目录

  • 1 Nacos原理
    • 1.1 Nacos架构
    • 1.2 注册中心原理
    • 1.3 SpringCloud服务注册
    • 1.4 NacosServiceRegistry实现
      • 1.4.1 心跳机制
      • 1.4.2 注册原理
      • 1.4.3 总结
    • 1.5 服务提供者地址查询
    • 1.6 Nacos服务地址动态感知原理

1 Nacos原理

1.1 Nacos架构

图片

  • Provider APP:服务提供者
  • Consumer APP:服务消费者
  • Name Server:通过VIPVirtual IP)或DNS的方式实现Nacos高可用集群的服务路由
  • Nacos ServerNacos服务提供者,里面包含的Open API是功能访问入口,Conig ServiceNaming ServiceNacos提供的配置服务、命名服务模块。Consitency Protocol是一致性协议,用来实现Nacos集群节点的数据同步,这里使用的是Raft算法(Etcd、Redis哨兵选举)
  • Nacos Console:控制台

1.2 注册中心原理

注册中心原理:

  • 服务实例在启动时注册到服务注册表,并在关闭时注销
  • 服务消费者查询服务注册表,获得可用实例
  • 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求
    在这里插入图片描述

1.3 SpringCloud服务注册

Spring-Cloud-Common包中有一个类org.springframework.cloud. client.serviceregistry .ServiceRegistry ,它是Spring Cloud提供的服务注册的标准。集成到Spring Cloud中实现服务注册的组件,都会实现该接口。
在这里插入图片描述
该接口有一个实现类是 NacoServiceRegistry

SpringCloud集成Nacos的实现过程:
spring-clou-commons包的META-INF/spring.factories中包含自动装配的配置信息如下:

图片
其中AutoServiceRegistrationAutoConfiguration就是服务注册相关的配置类:

@Configuration(proxyBeanMethods = false)
@Import(AutoServiceRegistrationConfiguration.class)
@ConditionalOnProperty(value ="spring.cloud.service-registry.auto-registration.enabled",matchIfMissing = true)
public class AutoServiceRegistrationAutoConfiguration{@Autowired(required = false)private AutoServiceRegistration autoServiceRegistration;@Autowiredprivate AutoServiceRegistrationProperties properties;@PostConstructprotected void init() {if (this.autoServiceRegistration == null && this.properties.isFailFast()) {throw new IllegalStateException("Auto Service Registration has been requested,but there is no AutoServiceRegistration bean");
}}}

AutoServiceRegistrationAutoConfiguration配置类中,可以看到注入了一个AutoServiceRegistration实例,该类的关系图如下所示。
图片

可以看出, AbstractAutoServiceRegistration 抽象类实现了该接口,并且最重要的是NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration

看到EventListener我们就应该知道,Nacos是通过Spring的事件机制集成到SpringCloud中去的。

AbstractAutoServiceRegistration实现了onApplicationEvent抽象方法,并且监听WebServerInitializedEvent事件(当Webserver初始化完成之后) , 调用this.bind ( event )方法。

@Override
public void onApplicationEvent(WebServerInitializedEvent event) {bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {ApplicationContext context = event.getApplicationContext();if (context instanceof ConfigurableWebServerApplicationContext){if ("management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace )))return;}}this.port.compareAndSet( 0,event.getWebServer() .getPort());this.start();
}

最终会调用NacosServiceREgistry.register()方法进行服务注册。

public void start() {if (!isEnabled()) {if (logger.isDebugEnabled()) [logger.debug("Discovery Lifecycle disabled. Not starting");}return;}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer belowif (!this.running.get()){this.context.publishEvent(new InstancePreRegisteredEvent(this,getRegistration()));register();if (shouldRegisterManagement()){registerManagement();}this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));this.running.compareAndSet(false,true);}
}protected void register(){this.serviceRegistry.register(getRegistration());
}

1.4 NacosServiceRegistry实现

NacosServiceRegistry.registry方法中,调用了Nacos Client SDK中的namingService.registerInstance完成服务的注册。

@Override
public void register(Registration registration){if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");return;}String serviceId = registration.getServiceId();Instance instance = getNacosInstanceFromRegistration(registration):try{namingService.registerInstance(serviceId,instance);log.info("nacos registry,{} {} : {}register finished", serviceId,instance.getIp(),instance.getPort());}catch (Exception e) {log.error("nacos registry, {} register failed... {},",serviceId,registration.toString(),e);}
}

跟踪NacosNamingServiceregisterInstance()方法:

@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {registerInstance(serviceName,Constants.DEFAULT_GROUP,instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {if (instance.isEphemeralO){BeatInfo beatInfo = new BeatInfo();beatInfo. setServiceName(NamingUtils.getGroupedName(serviceName, groupName));beatInfo.setIp(instance.getIp());beatInfo.setPort(instance.getPort());beatInfo.setCluster(instance.getClusterName());beatInfo.setWeight(instance.getWeight());beatInfo.setMetadata(instance .getMetadata());beatInfo.setScheduled(false);long instanceInterval = instance.getInstanceHeartBeatInterval();beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);}
serverProxy.registerService(Namingutils.getGroupedName(serviceName, groupName), groupName, instance);
}

通过beatReactor.addBeatInfo()创建心跳信息实现健康检测, Nacos Server必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。
serverProxy.registerService()实现服务注册

1.4.1 心跳机制

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

从上述代码看,所谓心跳机制就是客户端通过schedule定时向服务端发送一个数据包 ,然后启动一个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacos服务端会根据客户端的心跳包不断更新服务的状态。

1.4.2 注册原理

Nacos 提供了SDKOpen API两种形式来实现服务注册。

Open API:

curl -X POST ‘http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=192.16813.1&port=8080’

SDK:

void registerInstance(String serviceName, String ip, int port) throws NacosException;

这两种形式本质都一样,底层都是基于HTTP协议完成请求的。所以注册服务就是发送一个HTTP请求:

public void registerService(String serviceName,
String groupName,Instance instance) throws NacosException {NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",namespaceId,serviceName,instance);final Map<String,String> params = new HashMap<>(9);params.put(CommonParams.NAMESPACE_ID,namespaceId);params.put(CommonParams.SERVICE_NAME,serviceName);params.put(CommonParams.GROUP_NAME,groupName);params.put(CommonParams.CLUSTERNAME,instance.getClusterName());params.put("ip",instance.getIp());params .put("port",String. valueOf(instance.getPort()));params.put("weight",String.valueOf(instance.getWeight()));params.put("enable",String.valueOf(instance.isEnabled()));params.put("healthy",String.valueOf(instance.isHealthy()));params.put("ephemeral",String.valueOf(instance.isEphemeral()));params.put("metadata",JSON.toJSONString(instance.getMetadata()));regAPI(UtilAndComs .NACOS_URL_INSTANCE,params,HttpMethod.POSD);
}

对于nacos服务端,对外提供的服务接口请求地址为nacos/v1/ns/instance,实现代码在nacos-naming模块下的InstanceController类中:

@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT+"/instance")
public class InstanceController{
//省略部分代码
@CanDistro
@PostMapping
public String register(HttpServletRequest request) throws Exception {String serviceName = WebUtils.required(request,CommonParams.SERVICENAME);String namespaceId = WebUtils.optional(request,CommonParams,NAMESPACE_ID,Constants.DEFAULT_NAMESPACE_ID);serviceManager.registerInstance(namespaceId,serviceName,parseInstance(request));return"ok";}
//省略部分代码
}
  • 从请求参数汇总获得serviceName(服务名)和namespaceId(命名空间Id)

  • 调用registerInstance注册实例

public void registerInstance(String namespaceld, String serviceName, Instance instance)throws NacosException{createEmptyService(namespaceId,serviceNameinstance.isEphemeral());Service service=getService(namespaceId,serviceName);if (service== null){throw new NacosException(NacosException.INVALID_PARAM,"service not found,namespace:"+namespaceId +",service:"+serviceName);}addInstance(namespaceId,serviceName,instance.isEphemeral(),instance);
}
  • 创建一个控服务(在Nacos控制台服务列表中展示的服务信息),实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
  • getService,从serviceMap中根据namespaceIdserviceName得到一个服务对象
  • 调用addInstance添加服务实例
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local,Cluster cluster) throws NacosException {Service service = getService(namespaceId,serviceName);if(service== null){service= new Service();service.setName(serviceName);service.setNamespaceId(namespaceId);service.setGroupName(NamingUtils.getGroupName(serviceName));service.setLastModifiedMillis(System.currentTimeMillis());service.recalculateChecksum();if(cluster != null){cluster.setService(service);service.getClusterMap().put(cluster.getName(),cluster);}service.validate();putServiceAndInit(service);if(!local){addOrReplaceService(service);}}
}
  • 根据namespaceIdserviceName从缓存中获取Service实例
  • 如果Service实例为空,则创建并保存到缓存中
private void putServiceAndInit(Service service) throws NacosException{putService(service);service.init();consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(),service.getName(),true),service);consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(),service.getName(),false),service);Loggers.SRV_LOG.info("[NEW-SERVICE]{}",service.toJSON());
}
  • 通过putService()方法将服务缓存到内存
  • service.init()建立心跳机制
  • consistencyService.listen实现数据一致性监听

service.init()方法的如下图所示,它主要通过定时任务不断检测当前服务下所有实例最后发送心跳包的时间。如果超时,则设置healthyfalse表示服务不健康,并且发送服务变更事件。

在这里请大家思考一一个问题,服务实例的最后心跳包更新时间是谁来触发的?实际上前面有讲到, Nacos客户端注册服务的同时也建立了心跳机制。
在这里插入图片描述

putService方法,它的功能是将Service保存到serviceMap中:

public void putService(Service service)(if(!serviceMap.containsKey(service.getNamespaceId())){synchronized (putServiceLock){if(!serviceMap.containsKey(service,getNamespaceId())){serviceMap.put(service.getNamespaceId(),new ConcurrentHashMap<>(16));}}}serviceMap.get(service.getNamespaceId()).put(service.getName(),service);
}

继续调用addInstance方法把当前注册的服务实例保存到Service中:

addInstance(namespaceId,serviceName,instance.isEphemeral(),instance)

1.4.3 总结

  • Nacos客户端通过Open API的形式发送服务注册请求
  • Nacos服务端收到请求后,做以下三件事:
    • 构建一个Service对象保存到ConcurrentHashMap集合中
    • 使用定时任务对当前服务下的所有实例建立心跳检测机制
    • 基于数据一致性协议服务数据进行同步

1.5 服务提供者地址查询

Open API:

curl -X GET127.00.1:8848/nacos/v1/ns/instance/list?serviceName=example

SDK:

List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException;

InstanceController中的list方法:

@GetMapping("/list")
public JSONObject list(HttpServletRequest request) throws Exception {String namespaceId = WebUtils.optional(request,CommonParams,NAMESPACE_ID,Constants.DEFAULT_NAMESPACE_ID);String serviceName = WebUtils.required(request,CommonParams.SERVICE_NAME);String agent =WebUtils.getUserAgent(request);String clusters = WebUtils.optional(request,"clusters",StringUtils.EMPTY);String clientIP = WebUtils.optional(request,"clientIp", StringUtils.EMPTY);Integer udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort","0"));String env= WebUtils.optional(request,"env",StringUtils.EMPTY);boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request,"isCheck","false"));String app= WebUtils.optional(request,"app",StringUtils.EMPTY);String tenant = WebUtils.optional(request,"tid",StringUtils.EMPTY);boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request,"healthyOnly""false"));return doSrvIPXT(namespaceld, serviceName, agent, clusters, clientIP, udpPort, env,isCheck,app,tenant,healthyOnly);
}
  • 解析请求参数
  • 通过doSrvIPXT返回服务列表数据
public JSONObject doSrvIPXT(String namespaceld, String serviceName, String agent, String clusters,String clientIP,int udpPort,String env,boolean isCheck,String app,String tid,boolean healthyonly)
throws Exception {//以下代码中移除了很多非核心代码ClientInfo clientInfo = new ClientInfo(agent);JSONObject result=new JSONObject();Service service= serviceManager.getService(namespaceId,serviceName);List<Instance> srvedIPs;//获取指定服务下的所有实例 IPsrvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters,",")));Map<Boolean,List<Instance>>ipMap =new HashMap<>(2);ipMap.put(Boolean.TRUE,new ArrayList<>());ipMap.put(Boolean.FALSE,new ArrayList<>());for (Instance ip : srvedIPs){ipMap.get(ip.isHealthy()).add(ip);}//遍历,完成JSON字管中的纠装JSONArray hosts = new JSONArray();for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {List<Instance> ips = entry.getValue();if (healthyOnly && !entry.getKey()){continue;}for (Instance instance :ips) {if (!instanceisEnabled()) {continue;}JSONObject ipobj=new JSONObject();ipobj.put("ip",instance.getIp());ipObj.put("port",instance.getPort());ipObj.put("valid",entry.getKey());ipObj.put("healthy",entry.getKey());ipObj.put("marked",instance.isMarked());ipObj.put("instanceId",instance.getInstanceId());ipObj.put("metadata",instance.getMetadata());ipObj.put("enabled",instance.isEnabled());ipObj.put("weight",instance.getweight());ipObj.put("clusterName",instance.getClusterName());if(clientInfo.type== ClientInfo.ClientType.JAVA && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0."))>=0){ipObj.put("serviceName",instance.getServiceName());}else{ipObj.put("serviceName",NamingUtils.getServiceName(instance.getServiceName()));}ipObj.put("ephemeral",instance.isEphemeral());hosts.add(ipobj);}}result.put("hosts",hosts);result.put("name",serviceName);result.put("cacheMillis",cacheMillis);result.put("lastRefTime",System.currentTimeMillis());result.put("checksum",service.getChecksum());result.put("useSpecifiedURL",false);result.put("clusters",clusters);result.put("env",env);result.put("metadata",service.getMetadata());return result;
}
  • 根据namespaceIdserviceName获得Service实例
  • Service实例中基于srvIPs得到所有服务提供者实例
  • 遍历组装JSON字符串并返回

1.6 Nacos服务地址动态感知原理

可以通过subscribe方法来实现监听,其中serviceName表示服务名、EventListener表示监听到的事件:

void subscribe(String serviceName, EventListener listener) throws NacosException;

具体调用方式如下:

NamingService naming = NamingFactory.createNamingService(System.getProperty("serveAddr"));
naming.subscribe("example",event->(if (event instanceof NamingEvent) {System.out.println(((NamingEvent) event).getServceName());System.out.printIn(((NamingEvent) event).getInstances());}
});

或者调用selectInstance方法,如果将subscribe属性设置为true,会自动注册监听:

public List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy,boolean subscribe)

在这里插入图片描述

Nacos客户端中有一个HostReactor类,它的功能是实现服务的动态更新,基本原理是:

  • 客户端发起时间订阅后,在HostReactor中有一个UpdateTask线程,每10s发送一次Pull请求,获得服务端最新的地址列表
  • 对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个Push消息给Nacos客户端,也就是服务端消费者
  • 服务消费者收到请求之后,使用HostReactor中提供的processServiceJSON解析消息,并更新本地服务地址列表

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何从第一性原则的原理分解数学问题

如何从第一性原则的原理分解数学问题 摘要&#xff1a;牛津大学入学考试题目展示了所有优秀数学家都使用的系统的第一原则推理&#xff0c;而GPT4仍然在这方面有困难 作者&#xff1a;Keith McNulty 我们中的许多人都熟悉直角三角形的边的规则。根据毕达哥拉斯定理&#xff0c;…

Kotlin File useLines nameWithoutExtension extension

Kotlin File useLines nameWithoutExtension extension import java.io.Filefun main(args: Array<String>) {val filePath "myfile.txt"val file File(filePath)println(file.name) //文件名字&#xff0c;不包括路径println(file.isFile) //是文件吗pri…

VBA技术资料MF57:VBA_自动创建PowerPoint演示文稿

【分享成果&#xff0c;随喜正能量】会因为有情绪而烦闷&#xff0c;也因为没控制情绪而懊悔。莫道幽人一事无&#xff0c;闲中尽有静工夫。情绪就像水&#xff0c;宜疏不宜堵。学会控制情绪&#xff0c;不能把情绪看得过重&#xff0c;也不能一味遏制情绪的产生。倾听所有声音…

【Linux操作系统】信号的产生捕获

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ Linux       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1f6f0…

学习计算机网络中的一些疑问及解答

文章目录 前言一、为什么要进行三次握手二、三次握手的流程三、三次握手中seq和ack的值四、四次挥手流程五、四次挥手中seq和ack的值六、为什么要等待才回复七、为什么等待2MSL总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招&#xff0c;在学习计算机网络的过程中遇…

780. 到达终点;2360. 图中的最长环;1871. 跳跃游戏 VII

780. 到达终点 核心思想&#xff1a;正难则反&#xff0c;如果从起点到终点很难想。那么我们就考虑从终点到起点&#xff0c;由于起点为正数&#xff0c;那么终点&#xff08;x,y&#xff09;的上一步一定是&#xff08;x-y,y&#xff09;或者(x,y-x)很明显肯定是大值减去小的…

Vue 2 组件间的通信方式总结

引言 组件间的关系有父子关系、兄弟关系、祖孙关系和远亲关系。 不同的关系间&#xff0c;组件的通信有不同的方式。 一、prop 和 $emit prop向下传递&#xff0c;emit向上传递。 父组件使用 prop 向子组件传递信息。 ParentComponent.vue <template><div><…

5-2 Pytorch中的模型层layers

深度学习模型一般由各种模型层组合而成。 torch.nn中内置了非常丰富的各种模型层。它们都属于nn.Module的子类&#xff0c;具备参数管理功能。 例如&#xff1a; nn.Linear, nn.Flatten, nn.Dropout, nn.BatchNorm2d, nn.Embedding nn.Conv2d,nn.AvgPool2d,nn.Conv1d,nn.ConvTr…

用AI在小红书做早教启蒙,2个月涨粉11.7万,获赞10万的新流量玩法

本期是赤辰第29期AI项目教程&#xff0c;底部准备了9月粉丝福利&#xff0c;可以免费领取。母婴、教育一直以来是最不缺流量的两大“真香”赛道。那么AI时代下有怎样新流量红利和玩法&#xff1f;接下来就给大家拆解一个在小红书上做AI绘画启蒙早教资源变现的新玩法&#xff01…

Dajngo06_Template模板

Dajngo06_Template模板 6.1 Template模板概述 模板引擎是一种可以让开发者把服务端数据填充到html网页中完成渲染效果的技术 静态网页&#xff1a;页面上的数据都是写死的&#xff0c;万年不变 动态网页&#xff1a;页面上的数据是从后端动态获取的&#xff08;后端获取数据库…

每日一博 - 防范彩虹表攻击_数据库存储密码的秘密武器

文章目录 概述图解小结 概述 加盐&#xff08;salting&#xff09;是一种安全存储数据库中密码并验证其真实性的常见方法&#xff0c;它的主要目的是增加密码的安全性&#xff0c;以防止常见的密码攻击&#xff0c;如彩虹表攻击。以下是关于如何使用加盐技术的简要介绍&#x…

Eclipse开源代码下载

当前插件开发&#xff0c;需要修改eclipse源码&#xff0c;如需要修改remote相关的代码&#xff0c;所以需要下载相关源码。网上大多资料都说的不清不楚的&#xff0c;也可能我太小白&#xff0c;不明白&#xff0c;反正就是折腾了一两天才感觉有点思路&#xff0c;改如何找源码…

信息安全三级真题一

目录 一、单选题 二、填空题 三、综合题 一、单选题 二、填空题 三、综合题 知法懂法&#xff0c;请各位网络安全从业者遵守《网络安全法》、《个人信息保护法》 业%$务*$&联&#系 XHU3ZjUxXHU3ZWRjXHU4ZmQwXHU3ZWY0XHU2ZTE3XHU5MDBmXHU1NmUyXHU5NjFmXHUyMDBiXHU2M…

安卓抓jdwskey

安装京东&#xff0c;VNET VNET下载地址 https://www.vnet-tech.com/zh/ 2给权限 打开 VNET --点击右下角 ▶ --保存 CA.pem 证书 --打开手机系统设置搜索 证书–点击安装刚刚保存的 CA.pem 如果开始出现数据表示已经有权限抓包了&#xff0c;下面给权限跳过&#xff0c;直接开…

【PowerQuery】Excel的PowerQuery按需刷新

将数据通过PowerQuery 导入进来后,这里将进行数据分组运算,最终的数据计算结果将保存在Excel 表格中,图为销售统计结果。 在Excel中,如果我们希望进行销售统计的手动更新可以使用几种不同的方法来进行刷新: 刷新单一数据连接如果仅仅需要刷新单一数据连接的话我们可以通过…

MyBatis获取参数值的两种方式

5、MyBatis获取参数值的两种方式 MyBatis获取参数值的两种方式&#xff1a;KaTeX parse error: Expected EOF, got # at position 4: {}和#̲{} 5.1、单个字面量类型的…{}和#{}以任意的名称获取参数的值&#xff0c;注意${}需要手动加单引号 ${} #{} 测试代码&#xff1a; 实验…

CocosCreator3.8研究笔记(十五)CocosCreator 资源管理Asset Bundle

在资源管理模块中有一个很重要的功能&#xff1a; Asset Bundle&#xff0c;那什么是Asset Bundle &#xff1f;有什么作用&#xff1f;怎么使用 Asset Bundle呢 &#xff1f; 一、什么是Asset Bundle &#xff1f;有什么作用&#xff1f; 在日常游戏开发过程中&#xff0c;为了…

TCP详解之滑动窗口

TCP详解之滑动窗口 引入窗口概念的原因 我们都知道 TCP 是每发送一个数据&#xff0c;都要进行一次确认应答。当上一个数据包收到了应答了&#xff0c; 再发送下一个。 这个模式就有点像我和你面对面聊天&#xff0c;你一句我一句。但这种方式的缺点是效率比较低的。 如果你…

TS泛型的使用

函数中使用泛型&#xff1a; function identity<T>(arg: T): T {return arg; }let result identity<number>(10); // 传入number类型&#xff0c;返回number类型 console.log(result); // 输出: 10let value identity<string>(Hello); // 传入string类型&a…