nacos原理

不要纠结于具体代码,随着版本变化源码多变,要学习的是基本原理和思想;

Nacos注册中心实现原理分析

Nacos架构图

图片

其中分为这么几个模块:

  • Provider APP:服务提供者。

  • Consumer APP:服务消费者。

  • Name Server:通过Virtual IP或者DNS的方式实现Nacos高可用集群的服务路由。

  • Nacos Server:Nacos服务提供者。

    • OpenAPI:功能访问入口。
    • Config Service、Naming Service:Nacos提供的配置服务、名字服务模块。
    • Consistency Protocol:一致性协议,用来实现Nacos集群节点的数据同步,使用Raft算法实现。

不重要的:

  • Nacos Console:Nacos控制台。

小总结:

  • 服务提供者通过VIP(Virtual IP)访问Nacos Server高可用集群,基于OpenAPI完成服务的注册和服务的查询。
  • Nacos Server的底层则通过数据一致性算法(Raft)来完成节点的数据同步。

注册中心的原理

这里对其原理做一个大致的介绍,在后文则从源码角度进行分析。

首先,服务注册的功能体现在:

  • 服务实例启动时注册到服务注册表、关闭时则注销(服务注册)。
  • 服务消费者可以通过查询服务注册表来获得可用的实例(服务发现)。
  • 服务注册中心需要调用服务实例的健康检查API来验证其是否可以正确的处理请求(健康检查)。

Nacos服务注册和发现的实现原理的图如下:

图片

Nacos源码分析

Alibaba (github.com)

先分析这两个部分:

  • 服务注册。
  • 服务发现

Nacos服务注册

首先看SpringCloud:spring-cloud-commons

图片

这个ServiceRegistry接口定义了SpringCloud提供的服务注册的标准,集成到SpringCloud中实现服务注册的组件,都需要实现这个接口。来看下它的结构:

public interface ServiceRegistry<R extends Registration> {  void register(R registration);  void deregister(R registration);  void close();  void setStatus(R registration, String status);  <T> T getStatus(R registration);  
}  

在Nacos中,该接口的实现类是NacosServiceRegistry,该类在这个pom包下:

图片

https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_commons_common_abstractions

自动装配

再回过头来看spring-cloud-commons包:

图片

spring.factories主要是包含了自动装配的配置信息,如图:

图片spring.factories中配置EnableAutoConfiguration的内容后,项目在启动的时候,会导入相应的自动配置类,那么也就允许对该类的相关属性进行一个自动装配。

在这里导入了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;  @Autowired  private AutoServiceRegistrationProperties properties;  @PostConstruct  protected 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

public abstract class AbstractAutoServiceRegistration<R extends Registration> implements AutoServiceRegistration,   
ApplicationContextAware,   
ApplicationListener<WebServerInitializedEvent> {  public void onApplicationEvent(WebServerInitializedEvent event) {  this.bind(event);  }  
}  

这里是springboot和Spring的事件监听机制,利用事件监听机制可以轻松实现观察者模式;此处简介一下原理。

了解前端event事件注册,或是了解观察者设计模式的同学可以触类旁通;

java标准包中就有Event和Listener两个类,Listener监听器可以监听某一事件(Event),我们编辑自定义的事件监听模式的代码时,只需要实现这两个接口,然后将自定义的Event为参数,传入Listener,就可以完成对某一事件的监听;

Spring、SpringBoot、SpringCloud都对此进一步封装,给自己的容器、生命周期等都定义了事件,如此处的WebServerInitializedEvent,就是spring-boot-starter-web中,WebServer初始化后的事件;

  • 这观察者模式同步异步?
  • 实现方式也是列表?似乎是调用onXXX方法;

这里实现了ApplicationListener接口,并且传入了WebServerInitializedEvent作为泛型,啥意思嘞,意思是:

  • NacosAutoServiceRegistration监听WebServerInitializedEvent事件。
  • WebServer初始化完成后,会调用对应的事件绑定方法,调用onApplicationEvent(),该方法最终调用NacosServiceRegistryregister()方法(NacosServiceRegistry实现了Spring的一个服务注册标准接口)。

对于register()方法,主要调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服务的注册。

public void register(Registration registration) {  //... 对registration.getServiceId()判空String serviceId = registration.getServiceId();  String group = this.nacosDiscoveryProperties.getGroup();  Instance instance = this.getNacosInstanceFromRegistration(registration);  try {  this.namingService.registerInstance(serviceId, group, instance);  //log} catch (Exception var6) {  //logReflectionUtils.rethrowRuntimeException(var6);  }  }  
}  public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {  if (instance.isEphemeral()) {  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 == 0L ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);  // 1.addBeatInfo()负责创建心跳信息实现健康监测。因为Nacos Server必须要确保注册的服务实例是健康的。  // 而心跳监测就是服务健康监测的一种手段。  this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);  }  // 2.registerService()实现服务的注册  this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);  
}  

我们看到 NamingClientProxy 的主要实现类有三个:

  • NamingClientProxyDelegate

    代理类。通过此类判断调用以下哪个 clientProxy。

  • NamingHttpClientProxy

    使用 Http 通信协议的客户端。

  • NamingGrpcClientProxy

    使用 Grpc 通信协议的客户端。

OK,我们回到主线上来,继续分析clientProxy.registerService()

// NamingClientProxyDelegate
@Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException {getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);}// 根据instance.isEphemeral()属性选择通信协议,此属性也来自yml配置,默认为true。
private NamingClientProxy getExecuteClientProxy(Instance instance) {return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;}

所以这里默认情况下选择了NamingGrpcClientProxy执行注册。

//  NamingGrpcClientProxy
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {....// 创建请求InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,NamingRemoteConstants.REGISTER_INSTANCE, instance);// 执行实例注册请求requestToServer(request, Response.class);// 缓存服务实例,断开重新连接时,进行重新注册namingGrpcConnectionEventListener.cacheInstanceForRedo(serviceName, groupName, instance);
}

到这里了,请求发送成功,待 Nacos Server 处理完逻辑,服务注册就完成了。

再来看一下心跳监测的方法addBeatInfo()

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {  LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);  String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());  BeatInfo existBeat = null;  if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {  existBeat.setStopped(true);  }  this.dom2Beat.put(key, beatInfo);  // 通过schedule()方法,定时的向服务端发送一个数据包,然后启动一个线程不断地检测服务端的回应。  // 如果在指定的时间内没有收到服务端的回应,那么认为服务器出现了故障。  // 参数1:可以说是这个实例的相关信息。  // 参数2:一个long类型的时间,代表从现在开始推迟执行的时间,默认是5000  // 参数3:时间的单位,默认是毫秒,结合5000即代表每5秒发送一次心跳数据包  this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);  MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());  
}  

心跳检查如果正常,即代表这个需要注册的服务是健康的,那么执行下面的注册方法registerInstance()

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {  //logMap<String, String> params = new HashMap(9);  params.put("namespaceId", this.namespaceId);  params.put("serviceName", serviceName);  params.put("groupName", groupName);  params.put("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()));  // 这里可以看出来,把上述服务实例的一些必要参数保存到一个Map中,通过OpenAPI的方式发送注册请求  this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");  
}  

下面直接Debug走一遍。

  • 启动一个Nacos服务。
  • 搞一个Maven项目,集成Nacos。

理解Nacos服务注册流程

1.项目初始化后,根据上文说法,会执行抽象类AbstractAutoServiceRegistration下面的onApplicationEvent()方法,即事件被监听到。

2.作为抽象类的子类实现NacosAutoServiceRegistration,监听到Web服务启动后, 开始执行super.register()方法。

3.执行NacosServiceRegistry下的register()方法(super),前面说过,集成到SpringCloud中实现服务注册的组件,都需要实现ServiceRegistry这个接口,而对于Nacos而言,NacosServiceRegistry就是具体的实现子类。执行注册方法需要传入的三个参数:

  • 实例名称serviceId。
  • 实例归属的组。
  • 具体实例

registerInstance()主要做两件事:

  • 检查服务的健康(this.beatReactor.addBeatInfo())。
  • 执行服务的注册(this.serverProxy.registerService())。

服务健康的检查:检查通过后,发送OpenAPI进行服务的注册:

服务注册小总结☆:

这里来做一个大框架式的梳理(也许前面写的有点乱,这里通过几个问答的形式来进行总结)

问题1:Nacos的服务注册为什么和spring-cloud-commons这个包扯上关系?

回答:

  1. 首先,Nacos的服务注册肯定少不了pom包:spring-cloud-starter-alibaba-nacos-discovery吧。
  2. 这个包下面包括了spring-cloud-commons包,那么这个包有什么用?
  3. spring-cloud-commons中有一个接口叫做ServiceRegistry,而集成到SpringCloud中实现服务注册的组件,都需要实现这个接口。
  4. 因此对于需要注册到Nacos上的服务,也需要实现这个接口,那么具体的实现子类为NacosServiceRegistry

问题2:为什么我的项目加了这几个依赖,服务启动时依旧没有注册到Nacos中?

回答:

  1. 本文提到过,进行Nacos服务注册的时候,会有一个事件的监听过程,而监听的对象是WebServer,因此,这个项目需要是一个Web项目!
  2. 因此查看你的pom文件中是否有依赖:spring-boot-starter-web

问题3:除此之外,spring-cloud-commons这个包还有什么作用?

回答:

  1. 这个包下的spring.factories文件中,配置了相关的服务注册的置类,即支持其自动装配。
  2. 这个配置类叫做AutoServiceRegistrationAutoConfiguration。其注入了类AutoServiceRegistration,而NacosAutoServiceRegistration是该类的一个具体实现。
  3. 当WebServer初始化的时候,通过绑定的事件监听器,会实现监听,执行服务的注册逻辑。

说白了:

  1. 第一件事情:引入一个Spring监听器,当容器初始化后,执行Nacos服务的注册。
  2. 第二件事情:而Nacos服务注册的方法的实现,其需要实现的接口来自于该包下的ServiceRegistry接口。

接下来就对Nacos注册的流程进行一个总结:

  1. 服务(项目)启动时,根据spring-cloud-commonsspring.factories的配置,自动装配了类AutoServiceRegistrationAutoConfiguration
  2. AutoServiceRegistrationAutoConfiguration类中注入了类AutoServiceRegistration,其最终实现子类实现了Spring的监听器。
  3. 根据监听器,执行了服务注册方法。而这个服务注册方法则是调用了NacosServiceRegistryregister()方法。
  4. 该方法主要调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服务的注册。
  5. registerInstance()方法主要做两件事:服务实例的健康监测和实例的注册。
  6. 通过schedule()方法定时的发送数据包,检测实例的健康。
  7. 若健康监测通过,调用registerService()方法,通过OpenAPI方式执行服务注册,其中将实例Instance的相关信息存储到HashMap中。

Nacos服务发现

有一点我们需要清楚:Nacos服务的发现发生在什么时候。例如:微服务发生远程接口调用的时候。一般我们在使用OpenFeign进行远程接口调用时,都需要用到对应的微服务名称,而这个名称就是用来进行服务发现的。

举个例子:

@FeignClient("test-application")  
public interface MyFeignService {  @RequestMapping("getInfoById")  R info(@PathVariable("id") Long id);  
}  

接下来直接开始讲重点,Nacos在进行服务发现的时候,会调用NacosServerList类下的getServers()方法:

public class NacosServerList extends AbstractServerList<NacosServer> {  private List<NacosServer> getServers() {  try {  String group = this.discoveryProperties.getGroup();  // 1.通过唯一的serviceId(一般是服务名称)和组来获得对应的所有实例。  List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, group, true);  // 2.将List<Instance>转换成List<NacosServer>数据,然后返回。  return this.instancesToServerList(instances);  } catch (Exception var3) {  //. }  }  
}  

接下来来看一下NacosNamingService.selectInstances()方法:

public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {  return this.selectInstances(serviceName, groupName, healthy, true);  
}  

该方法最终会调用到其重载方法:

public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters,   boolean healthy, boolean subscribe) throws NacosException {  // 保存服务实例信息的对象  ServiceInfo serviceInfo;  // 如果该消费者订阅了这个服务,那么会在本地维护一个服务列表,服务从本地获取  if (subscribe) {  serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));  } else {  // 否则实例会从服务中心进行获取。  serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));  }  return this.selectInstances(serviceInfo, healthy);  
}  

这里应该重点关注this.hostReactor这个对象,它里面比较重要的是几个Map类型的存储结构:

public class HostReactor {  private static final long DEFAULT_DELAY = 1000L;  private static final long UPDATE_HOLD_INTERVAL = 5000L;  // 存放线程异步调用的一个回调结果  private final Map<String, ScheduledFuture<?>> futureMap;  // 本地已存在的服务列表,key是服务名称,value是ServiceInfo  private Map<String, ServiceInfo> serviceInfoMap;  // 待更新的实例列表  private Map<String, Object> updatingMap;  // 定时任务(负责服务列表的实时更新)  private ScheduledExecutorService executor;  ....  
}  

再看一看它的getServiceInfo()方法:

public ServiceInfo getServiceInfo(String serviceName, String clusters) {  LogUtils.NAMING_LOGGER.debug("failover-mode: " + this.failoverReactor.isFailoverSwitch());  String key = ServiceInfo.getKey(serviceName, clusters);  if (this.failoverReactor.isFailoverSwitch()) {  return this.failoverReactor.getService(key);  } else {  // 1.先通过serverName即服务名获得一个serviceInfo  ServiceInfo serviceObj = this.getServiceInfo0(serviceName, clusters);  // 如果没有serviceInfo,则通过传进来的参数new出一个新的serviceInfo对象,并且同时维护到本地Map和更新Map  // 这里是serviceInfoMap和updatingMap  if (null == serviceObj) {  serviceObj = new ServiceInfo(serviceName, clusters);  this.serviceInfoMap.put(serviceObj.getKey(), serviceObj);  this.updatingMap.put(serviceName, new Object());  // 2.updateServiceNow(),立刻去Nacos服务端拉去数据。  this.updateServiceNow(serviceName, clusters);  this.updatingMap.remove(serviceName);  } else if (this.updatingMap.containsKey(serviceName)) {  synchronized(serviceObj) {  try {  serviceObj.wait(5000L);  } catch (InterruptedException var8) {  LogUtils.NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, var8);  }  }  }  // 3.定时更新实例信息  this.scheduleUpdateIfAbsent(serviceName, clusters);  // 最后返回服务实例数据(前面已经进行了更新)  return (ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey());  }  
}  

来看下scheduleUpdateIfAbsent()方法:

// 通过心跳的方式,每10秒去更新一次数据,并不是只有在调用服务的时候才会进行更新,而是通过定时任务来异步进行。  
public void scheduleUpdateIfAbsent(String serviceName, String clusters) {  if (this.futureMap.get(ServiceInfo.getKey(serviceName, clusters)) == null) {  synchronized(this.futureMap) {  if (this.futureMap.get(ServiceInfo.getKey(serviceName, clusters)) == null) {  // 创建一个UpdateTask的更新线程任务,每10秒去异步更新集合数据  ScheduledFuture<?> future = this.addTask(new HostReactor.UpdateTask(serviceName, clusters));  this.futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);  }  }  }  
}  

案例2:用Debug来理解Nacos服务发现流程

1.进行远程接口调用,触发服务的发现,调用NacosServerListgetServers()方法。传入的serviceId和对应Feign接口上的接口@FeignClient中的名称一致。

图片

例如,我这里调用的Feign接口是:

@FeignClient("gulimall-member")  
public interface MemberFeignService {  @RequestMapping("/member/member/info/{id}")  R info(@PathVariable("id") Long id);  
}  

这里可以看出来,返回的是一个Instance类型的List,对应的服务也发现并返回了。

2.这里则调用了NacosNamingServiceselectInstances()方法,我这里的subscribe值是true,即代表我这个消费者直接订阅了这个服务,因此最终的信息是从本地Map中获取,即Nacos维护了一个注册列表。

3.再看下HostReactor的getServiceInfo()方法:最终所需要的结果是从serviceInfoMap中获取,并且通过多个Map进行维护服务实例,若存在数据的变化,还会通过强制睡眠5秒钟的方式来等待数据的更新。

4.无论怎样都会调用this.scheduleUpdateIfAbsent(serviceName, clusters)方法。

5.通过scheduleUpdateIfAbsent()方法定时的获取实时的实例数据,并且负责维护本地的服务注册列表,若服务发生更新,则更新本地的服务数据。

服务发现小总结☆:

经常有人说过,Nacos有个好处,就是当一个服务挂了之后,短时间内不会造成影响,因为有个本地注册列表,在服务不更新的情况下,服务还能够正常的运转,其原因如下:

  1. Nacos的服务发现,一般是通过订阅的形式来获取服务数据。
  2. 而通过订阅的方式,则是从本地的服务注册列表中获取(可以理解为缓存)。相反,如果不订阅,那么服务的信息将会从Nacos服务端获取,这时候就需要对应的服务是健康的。(宕机就不能使用了)
  3. 在代码设计上,通过Map来存放实例数据,key为实例名称,value为实例的相关信息数据(ServiceInfo对象)。

最后,服务发现的流程就是:

  1. 以调用远程接口(OpenFeign)为例,当执行远程调用时,需要经过服务发现的过程。
  2. 服务发现先执行NacosServerList类中的getServers()方法,根据远程调用接口上@FeignClient中的属性作为serviceId传入NacosNamingService.selectInstances()方法中进行调用。
  3. 根据subscribe的值来决定服务是从本地注册列表中获取还是从Nacos服务端中获取。
  4. 以本地注册列表为例,通过调用HostReactor.getServiceInfo()来获取服务的信息(serviceInfo),Nacos本地注册列表由3个Map来共同维护。

本地Map–>serviceInfoMap,

更新Map–>updatingMap

异步更新结果Map–>futureMap,

最终的结果从serviceInfoMap当中获取。

  1. HostReactor类中的getServiceInfo()方法通过this.scheduleUpdateIfAbsent() 方法和updateServiceNow()方法实现服务的定时更新和立刻更新。
  2. 而对于scheduleUpdateIfAbsent()方法,则通过线程池来进行异步的更新,将回调的结果(Future)保存到futureMap中,并且发生提交线程任务时,还负责更新本地注册列表中的数据。
  • 说监听的是webServer,具体是怎么监听?
  • 拦截器、过滤器等,具体是怎么实现的?
  • 看来整个SpringCloud流程比较清晰了,,Cloud定义了一堆接口,如服务注册,然后各家把自己实现注册到Spring里,然后需要的再自己去Spring里获取这个Bean

NACOS架构与原理 电子书

Nacos 注册中心的设计原理 (yuque.com)

Zookeeper 没有针对服务发现设计数据模型,它的数据是以一种更加抽象的树形 K-V 组织的,因此理论上可以存储任何语义的数据。而 Eureka 或者 Consul 都是做到了实例级别的数据扩展,这可以满足大部分的场景,不过无法满足大规模和多环境的服务数据存储。Nacos 在经过内部多年生产经验后提炼出的数据模型,则是一种服务-集群-实例的三层模型。如上文所说,这样基本可以满足服务在所有场景下的数据存储和管理。

  • 展开讲讲这几个?

数据一致性

当我们选用服务注册中心的时候,并没有一种协议能够覆盖所有场景,例如当注册的服务节点不会定时发送心跳到注册中心时,强一致协议看起来是唯一的选择,因为无法通过心跳来进行数据的补偿注册,第一次注册就必须保证数据不会丢失。而当客户端会定时发送心跳来汇报健康状态时,第一次的注册的成功率并不是非常关键(当然也很关键,只是相对来说我们容忍数据的少量写失败),因为后续还可以通过心跳再把数据补偿上来,此时 Paxos 协议的单点瓶颈就会不太划算了,这也是 Eureka 为什么不采用 Paxos 协议而采用自定义的 Renew 机制的原因。

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

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

相关文章

k8s之StorageClass(NFS)

一、前言 1、环境 k8s v1.23.5 &#xff0c;服务器是centos7.9 192.168.164.20 k8s-master1 192.168.164.30 k8s-node1 192.168.164.40 k8s-node2 2、貌似storageClass在kubernetes v1.20就被砍了。 因为它比较慢&#xff0c;而且耗资源&#xff0c;但可以通过不同的实现镜…

Java并发机制的底层实现原理

一、前置知识 缓存一致性协议&#xff1a;每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了&#xff0c;当处理器发现自己缓存行对应的内存地址被修改&#xff0c;就会将当前处理器的缓存行设置成无效状态&#xff0c;当处理器对这个数据进行修改操作的时…

selenium常见等待机制及其特点和使用方法

目录 1、强制等待 2、隐式等待 3、显示等待 1、强制等待 强制等待是在程序中直接调用Thread.sleep(timeout) ,来完成的&#xff0c;该用法的优点是使用起来方便&#xff0c;语法也比较简单&#xff0c;缺点就是需要强制等待固定的时间&#xff0c;可能会造成测试的时间过…

“探索计算机世界:进程的基本概念与功能“

文章目录 前言什么是进程如何描述进程进程的属性1. 进程标识符2. 内存指针3. 文件描述符表4. 进程的状态5. 优先级6. 上下文7. 记账信息 内存分配并行和并发 前言 作为程序员&#xff0c;理解计算机的组成以及计算机是怎样运行的是很重要的&#xff0c;因为只有了解计算机我们…

Jenkins 使用

Jenkins 使用 文章目录 Jenkins 使用一、jenkins 任务执行二、 Jenkins 连接gitee三、Jenkins 部署静态网站 一、jenkins 任务执行 jenkins 创建 job job的名字最好是有意义的 restart_web_backend restart_web_mysql[rootjenkins ~]# ls /var/lib/jenkins/ config.xml …

QT--崩溃原因分析

本文为学习记录&#xff0c;若有错误&#xff0c;请联系作者&#xff0c;谦虚受教。 文章目录 前言一、目的二、实现步骤1 add2line.exe2 分析文件3 crash文件 三、相关代码1 pro文件2.ccrashstack.h3.ccrashstack.cpp4.main.cpp 总结 前言 你从来来去自由&#xff0c;若你不想…

大模型时代,如何重塑AI人才的培养?知名高校专家为您解答

当下&#xff0c;随着人工智能技术的快速发展&#xff0c;大模型已经成为了人工智能发展的新方向&#xff0c;同时也对新时代AI人才的需求和培养带来了新的思考与挑战&#xff0c;需要结合当下社会对复合型AI人才的需求进行新思考&#xff0c;创新AI人才培养模式&#xff0c;以…

【ARM64 常见汇编指令学习 15 -- ARM 标志位的学习】

文章目录 ARM 标志位介绍Zero Condition flag(零标志位)零标志位判断实例 上篇文章&#xff1a;ARM64 常见汇编指令学习 14 – ARM 汇编 .balign,.balignw,.balign 伪指令学习 下篇文章&#xff1a;ARM64 常见汇编指令学习 16 – ARM64 SMC 指令 ARM 标志位介绍 在ARM架构中&am…

深度对话|如何设计合适的网络经济激励措施

近日&#xff0c;我们与Mysten Labs的首席经济学家Alonso de Gortari进行了对话&#xff0c;讨论了如何在网络运营商和参与者之间找到激励措施的平衡&#xff0c;以及Sui的经济如何不断发展。 是什么让您选择将自己的经济学背景应用于区块链和Web3领域&#xff1f; 起初&…

YOLO相关原理(文件结构、视频检测等)

超参数进化(hyperparameter evolution) 超参数进化是一种使用了genetic algorithm&#xff08;GA&#xff09;遗传算法进行超参数优化的一种方法。 YOLOv5的文件结构 images文件夹内的文件和labels中的文件存在一一对应关系 激活函数&#xff1a;非线性处理单元 activation f…

爬虫014_文件操作_打开关闭_读写_序列化_反序列化---python工作笔记033

报错,没有指定路径,没有指定路径无法创建文件 这样可以在当前目录下创建一个可写的文件 可以看到找到刚才生成的文件,看看内容

探讨uniapp的navigator 页面跳转问题

navigator 页面跳转。该组件类似HTML中的<a>组件&#xff0c;但只能跳转本地页面。目标页面必须在pages.json中注册。 "tabBar": {"color": "#7A7E83","selectedColor": "#3cc51f","borderStyle": "bl…

SpringMVC的架构有什么优势?——控制器(三)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

走进知识图谱(二)【世界知识图谱篇】知识表示的经典模型与平移模型及基于复杂关系建模的知识表示学习

上篇文章提到&#xff0c;该系列文章将主要围绕世界知识图谱和语言知识图谱这两大类知识图谱进行展开&#xff0c;并且提到知识图谱的主要研究包括了知识表示学习、知识自动获取和知识的推理与应用三大部分。今天主要介绍世界知识图谱的知识表示学习&#xff0c;其中包括经典的…

uniapp 左右滑动切换页面并切换tab

实现效果如图 要实现底部内部的左右滑动切换带动上方tab栏的切换&#xff0c;并且下方内容要实现纵向滚动 &#xff0c;所以需要swiper&#xff0c;swiper-item,scroll-view组合使用 tab栏部分 <view class"tabs"><view class"tab_item" v-for&…

完成图像反差处理

bmp图像的前54字节为图像头&#xff0c;第19个字节开始4字节为图像宽&#xff0c;第23字节开始4字节为图像高&#xff0c;图像大小为&#xff1a;972*720*3542099574&#xff0c;为宽*高*像素点头&#xff0c;如下&#xff1a; 图像的反差处理

⌈算法进阶⌋图论::并查集——快速理解到熟练运用

目录 一、原理 1. 初始化Init 2. 查询 find 3. 合并 union 二、代码模板 三、练习 1、 990.等式方程的可满足性&#x1f7e2; 2、 1061. 按字典序排列最小的等效字符串&#x1f7e2; 3、721.账户合并 &#x1f7e1; 4、 839.相似字符串组&#x1f7e1; 5、 2812.找出最安全…

智能优化算法:猎豹优化算法-附代码

智能优化算法&#xff1a;猎豹优化算法 文章目录 智能优化算法&#xff1a;猎豹优化算法1.猎豹优化算法1.1 初始化1.2 搜索策略1.3坐等策略1.4攻击策略 2.实验结果3.参考文献4.Matlab5.python 摘要&#xff1a;CO算法是Mohammad AminAkbari等人于2022年受自然界猎豹狩猎启发而提…

JUL 日志 - 最简单易用的Java日志框架

在正式的生产环境下是不能使用 System.out 进行日志记录的 因为 System.out 不能提供时间、线程、执行过程 等信息&#xff0c;如果要手动打印输出则会非常麻烦 而日志就帮我们把这些事给干了 接下来我们学一个最简单的日志框架 JUL JUL全称Java util Logging是java原生的日志框…

支付整体架构

5.4 支付的技术架构 架构即未来&#xff0c;只有建立在技术架构设计良好的体系上&#xff0c;支付机构才能有美好的未来。如果支付的技术体系在架构上存在问题&#xff0c;那么就没有办法实现高可用性、高安全性、高效率和水平可扩展性。 总结多年来在海内外支付机构主持和参与…