Nacos服务注册总流程(源码分析)

news2025/1/10 3:09:30

文章目录

    • 服务注册
      • NacosClient找看源码入口
      • NacosClient服务注册源码
      • NacosServer处理服务注册

服务注册

服务注册 在线流程图

在这里插入图片描述



NacosClient找看源码入口

我们启动一个微服务,引入nacos客户端的依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

并配置文件中指定nacosServer的地址,并运行该微服务,那么服务是如何注册进nacosServer中去的嘞?

server:
  port: 9002
spring:
  application:
    name: stock-service

  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: localhost:8848
        # 指定集群名称
        cluster-name: SZ
        metadata:
          version: v3

我们应该怎么找源码的入口嘞?

我们知道SpringCloud是基于SpringBoot的,我们这里引入了这个nacos客户端nacos-discovery的依赖,我们就直接去找这个jar包下的spring.factories文件

在这里插入图片描述

这个文件中记录了很多的自动配置类,如果不知道应该看哪一个的话,那么就优先看和我们导入maven依赖名字相近的类

在这里插入图片描述


进入到这个NacosDiscoveryAutoConfiguration自动配置类后会发现这里会往容器中注入三个bean,看bean的名字好像都是和nacos注册相关的,按照经验来说,带有AutoXXXX这类方法一般都比较重要,再加上第三个方法还用到了上面两个bean,所以核心方法很大概率就是这个第三个方法。

在这里插入图片描述


进入到该类的构造方法之后,我们一般可以查看这个类的继承与实现结构,这样可以让我们更充分的了解该类

在这里插入图片描述


从上图中可以发现,NacosAutoServiceRegistration这个类还实现了ApplicationListener接口,这个接口我们都知道是Spring发布事件相关的接口

// ApplicationListener接口
package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

我们再找这个方法的具体实现,会发现NacosAutoServiceRegistration这个类它没有重写该方法,那么就要在它的父类中找该方法的实现

public void onApplicationEvent(WebServerInitializedEvent event) {
    this.bind(event);
}

// 方法调用,进入到bind(...)方法

public void bind(WebServerInitializedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    // 有if、并且有返回、并且后面还有业务代码的一般情况下这都是一个分支代码,可以先跳过
    if (context instanceof ConfigurableWebServerApplicationContext) {
        if ("management".equals(((ConfigurableWebServerApplicationContext) context)
                                .getServerNamespace())) {
            return;
        }
    }
    
    // 一般情况下,start()  init()     begin()这种方法名命名的方法一般都是比较重要的代码
    this.port.compareAndSet(0, event.getWebServer().getPort());
    this.start();
}

根据看源码的经验来说:

  • 有if,并且有返回值的,并且后面还有业务代码的一般情况下这都是一个分支代码,可以先跳过
  • 一般情况下,start() init() begin()这中方法名命名的方法一般都是比较重要的代码

接下来再进入到start()方法中

public void start() {
    // if + return + 后面有业务逻辑,大概率是一个分支代码 可以先跳过。这里实际上也只是记录了一个日志
    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 below
    if (!this.running.get()) {
        this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
        // 核心方法,可以发现在调用这个方法之前和调用方法之后都发布了一个事件
        // 正好我们要找的也就是注册相关的方法
        // 因为我们就是从NacosDiscoveryAutoConfiguration自动配置类的NacosAutoServiceRegistration这个和注册相关的bean进来的
        register();
        if (shouldRegisterManagement()) {
            registerManagement();
        }
        this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
        this.running.compareAndSet(false, true);
    }

}

接下来在点进register()方法,跳转几次之后就会进入到NamingService这个关键接口的registerInstance(...)方法中去




NacosClient服务注册源码

关键接口NamingService,它的registerInstance(...)方法就是服务注册。程序刚开始会进入到该接口的实现类NacosNamingServiceregisterInstance(String serviceName, String groupName, Instance instance)方法中

// 该方法 往NacosServer中注册一个微服务实例
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    // 调用该方法进行微服务注册,instance对象中保存着微服务的各种信息,比如ip、端口、访问权重、健康状态、是否上线等等
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

instance对象保存的内容如下图所示

在这里插入图片描述



public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
                       instance);

    // 把instance对象中的数据取出来,封装成一个HashMap
    final Map<String, String> params = new HashMap<String, String>(16);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, 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", JacksonUtils.toJson(instance.getMetadata()));

    // 发送一个post的http请求发送给NacosServer,进行服务注册
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);

}

在这里插入图片描述


再通过官方文档 / 接口详细文档就能发现这就是服务注册的接口

在这里插入图片描述



NacosServer处理服务注册

NacosClient向NacosServer发送了一个服务注册的post请求,url为/v1/ns/instance

NacosServer这个项目有很多很多的子工程,就拿下图所示,我应该如何去找嘞?

在这里插入图片描述


其实我们就可以双击shift键,直接搜索controller,一个一个文件夹找,或者是直接加上关键字,就比如我这里加上了instance这个关键字就找到了真正处理这个请求的controller,类名为InstanceController

在这里插入图片描述


还有一种方法就是,我们在NacosClient这边进行服务注册时会频繁的使用到NamingService,那我就在NacosServer端找有没有和Naming相关的子工程,最终发现还真就找到了

在这里插入图片描述



接下来是源码,首先直接查看该controller的post接口

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

    final String namespaceId = WebUtils
        .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);

    // 通过解析request请求参数,封装成instance对象
    final Instance instance = parseInstance(request);

    // 调用Service层方法进行服务实例注册
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

接下来进入到service层的registerInstance(...)方法逻辑

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {

    // 这里会去创建一个service,如果连该service对应的命名空间都不存在的话就会利用DCL双重锁检测机制去创建一个map<serviceName,service>
    // 再去添加service,添加命名空间对应的map<serviceName,service>中。命名空间对应的这个map是存在注册表serviceMap中的
    // 这里创建的只是一个空服务,它里面还没有instance服务实例
    // 在创建service过程中还会进行初始化,会添加一个延迟定时任务进行该服务下的所有实例的健康检查、修改服务健康状态、删除过时服务
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());

    // 这里再获取,就不应该为null了,如果为null也就该抛异常了
    // getService()会先根据命名空间取出一个Map,再从这个Map中根据serviceName取出service。service中包含instance微服务实例
    Service service = getService(namespaceId, serviceName);

    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                                 "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }

    // 进行服务实例instance添加操作
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}



createEmptyService(...)方法中,首先会去判断当前service是否存在,如果不存在则创建一个空的新的service,并且把这个service添加进namespaceId对应的Map中,如果在这个过程中,namespaceId命名空间不存在则使用DCL双重锁检测去创建一个map集合添加进注册表serviceMap中。接下来在对刚刚创建的service进行init()初始化操作,蛀牙就是开启一个延时的定时任务,定期对该服务中的实例进行健康检查,并修改不健康的状态或移除超时实例

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
    createServiceIfAbsent(namespaceId, serviceName, local, null);
}

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
    throws NacosException {
    // 先从服务注册表serviceMap中获取
    Service service = getService(namespaceId, serviceName);
    // 如果不存在就创建
    if (service == null) {

        Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
        service = new Service();
        service.setName(serviceName);
        service.setNamespaceId(namespaceId);
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        // now validate the service. if failed, exception will be thrown
        service.setLastModifiedMillis(System.currentTimeMillis());
        service.recalculateChecksum();
        if (cluster != null) {
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        service.validate();

        // 这里采用了DCL双重锁检测机制进行的添加操作
        putServiceAndInit(service);
        if (!local) {
            addOrReplaceService(service);
        }
    }
}

private void putServiceAndInit(Service service) throws NacosException {
    // 将service添加进对应的NamespaceId中
    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());
}

public void putService(Service service) {
    // DCL双重检测机制
    if (!serviceMap.containsKey(service.getNamespaceId())) {
        synchronized (putServiceLock) {
            if (!serviceMap.containsKey(service.getNamespaceId())) {
                serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
            }
        }
    }
    serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
}

public void init() {
    // NacosClient客户端的心跳检测任务
    // 这里就会开启一个线程去执行任务,clientBeatCheckTask属性就是一个task,run()方法中会进行实例的健康检查,后面会详细介绍
    HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
    for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
        entry.getValue().setService(this);
        entry.getValue().init();
    }
}



接下来就进入到了addInstance(...)添加实例的方法中了,这个方法中主要做的事就是将当前service的所有实例通过一个key存入一个Map中,然后保存在一个dataStore这个Map中,同时再把这个key和相关的操作类型存入一个阻塞队列中,至此NacosClient发送的请求就处理完成了。之后会有单独一个线程异步处理进行真正的服务注册。

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
    throws NacosException {

    // 根据ephemeral的值来决定生成上面key,默认情况下NacosClient传递过来的都是true,一般微服务的实例都是临时实例,不是持久化实例
    // 如果是持久化实例就没有下面的ephemeral这个字符串拼接
    // key = 一些字符串常量 + “ephemeral” + namespaceId + “##” + serviceName
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

    Service service = getService(namespaceId, serviceName);

    synchronized (service) {
        // 添加微服务的实例,并返回当前服务所有的实例
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        Instances instances = new Instances();
        instances.setInstanceList(instanceList);

        // key主要就是命名空间+服务名,通过这个key添加进consistencyService中
        consistencyService.put(key, instances);
    }
}



// 上面的Instances实现了Record接口,这里其实也就是一个封装
public void put(String key, Record value) throws NacosException {
    //  mapConsistencyService(key)这里会根据是否是临时实例进而去调用不同实现类的put()方法,这里以临时实例举例
    mapConsistencyService(key).put(key, value);
}



public void put(String key, Record value) throws NacosException {
    // 将key和所有服务实例封装的Record对象封装成一个datum对象,并保存到一个map集合中。
    // 同时还有一个key和DataOperation枚举操作类型添加进阻塞队列的操作。
    // 后续肯定有一个线程从这个注释队列中取出数据,然后根据key把datum对象取出来
    onPut(key, value);
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                        globalConfig.getTaskDispatchPeriod() / 2);
}



public void onPut(String key, Record value) {

    if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        Datum<Instances> datum = new Datum<>();
        datum.value = (Instances) value;
        datum.key = key;
        datum.timestamp.incrementAndGet();
        // 这里把key和datum存入了一个dataMap这个集合中
        // 这个datum对象中保存着我当前服务所有的服务实例instances
        dataStore.put(key, datum);
    }

    if (!listeners.containsKey(key)) {
        return;
    }

    // 将key和后面这个枚举参数封装成一个Pair对象,并添加进一个阻塞队列中
    notifier.addTask(key, DataOperation.CHANGE);
}


public void addTask(String datumKey, DataOperation action) {

    if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
        return;
    }
    if (action == DataOperation.CHANGE) {
        services.put(datumKey, StringUtils.EMPTY);
    }
    // 把key和dataOperation操作枚举添加进一个阻塞队列中,真正进行服务注册的是在下面的run()方法中
    tasks.offer(Pair.with(datumKey, action));
}

// 另一个线程异步进行处理,从阻塞队列中获取数据,真正进行服务注册操作
@Override
public void run() {
    Loggers.DISTRO.info("distro notifier started");

    // 该线程会一直死循环,从阻塞队列中取数据
    for (; ; ) {
        try {
            Pair<String, DataOperation> pair = tasks.take();
            handle(pair);
        } catch (Throwable e) {
            Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
        }
    }
}



接下来就是详细查看这个handle(pair)方法,查看另一个线程是如何进行服务注册的?

这里会从先判断操作类型,然后从handle()方法调用到onChange(...)方法;而在onChange(...)方法中首先会对服务实例的权重进行处理,再调用到updateIPs()方法;在updateIPs()方法中会对service中的cluster做一些处理,最终就会调用到updateIps(...)方法中,在这个方法中会通过CopyOnWrite思想真正更改保存instance实例的集合

// handle()方法调用到onChange(...)方法
private void handle(Pair<String, DataOperation> pair) {
    try {
        String datumKey = pair.getValue0();
        DataOperation action = pair.getValue1();

        services.remove(datumKey);

        int count = 0;

        if (!listeners.containsKey(datumKey)) {
            return;
        }

        for (RecordListener listener : listeners.get(datumKey)) {

            count++;

            try {
                if (action == DataOperation.CHANGE) {
                    // dataStore.get(datumKey)取出来的对象是Datum类型的对象,它其中保存在key+所有服务实例instances
                    // dataStore.get(datumKey).value 就是这个datumKey对应的所有服务实例instances
                    listener.onChange(datumKey, dataStore.get(datumKey).value);
                    continue;
                }

                if (action == DataOperation.DELETE) {
                    listener.onDelete(datumKey);
                    continue;
                }
            } catch (Throwable e) {
                ...
            }
        }
        ...
    } catch (Throwable e) {
        ...
    }
}


// 对服务实例的权重进行处理,再调用到updateIPs()方法,
public void onChange(String key, Instances value) throws Exception {

    Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);

    // 对instance服务实例的特殊权重做处理
    for (Instance instance : value.getInstanceList()) {

        if (instance == null) {
            // Reject this abnormal instance list:
            throw new RuntimeException("got null instance " + key);
        }

        if (instance.getWeight() > 10000.0D) {
            instance.setWeight(10000.0D);
        }

        if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {
            instance.setWeight(0.01D);
        }
    }

    updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));

    recalculateChecksum();
}


// 会对service中的cluster做一些处理,最终就会调用到updateIps(...)方法中
public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
    Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
    // 遍历所有集群
    for (String clusterName : clusterMap.keySet()) {
        ipMap.put(clusterName, new ArrayList<>());
    }

    for (Instance instance : instances) {
        try {
            if (instance == null) {
                Loggers.SRV_LOG.error("[NACOS-DOM] received malformed ip: null");
                continue;
            }

            // 实例instance的默认集群名字
            if (StringUtils.isEmpty(instance.getClusterName())) {
                instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME);
            }

            if (!clusterMap.containsKey(instance.getClusterName())) {
                Loggers.SRV_LOG
                    .warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
                          instance.getClusterName(), instance.toJson());
                Cluster cluster = new Cluster(instance.getClusterName(), this);
                cluster.init();
                getClusterMap().put(instance.getClusterName(), cluster);
            }

            List<Instance> clusterIPs = ipMap.get(instance.getClusterName());
            // 一个新的集群名处理
            if (clusterIPs == null) {
                clusterIPs = new LinkedList<>();
                ipMap.put(instance.getClusterName(), clusterIPs);
            }

            // 实例添加进对应的集群集合中
            clusterIPs.add(instance);
        } catch (Exception e) {
            Loggers.SRV_LOG.error("[NACOS-DOM] failed to process ip: " + instance, e);
        }
    }

    for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
        //make every ip mine
        List<Instance> entryIPs = entry.getValue();
        // 微服务实例真正的注册进服务注册表的方法
        clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
    }

    
    setLastModifiedMillis(System.currentTimeMillis());
     // 服务实例改变之后,会发布一个ServiceChangeEvent事件
    getPushService().serviceChanged(this);
    StringBuilder stringBuilder = new StringBuilder();

    for (Instance instance : allIPs()) {
        stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");
    }

    Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),
                         stringBuilder.toString());

}
/**
     * Update instance list.
     * 下面的方法中有CopyOnWrite的实现思想,真正存储实例的集合是ephemeralInstances,
     * 但是这里面基本上都是在围绕oldIpMap这个复制出来的副本集合进行相应的操作,最后拿最新的集合复制给ephemeralInstances
     *
     * @param ips       instance list
     * @param ephemeral whether these instances are ephemeral
     */
public void updateIps(List<Instance> ips, boolean ephemeral) {

    Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;

    // 先保存一份现有的实例列表
    HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());

    for (Instance ip : toUpdateInstances) {
        // 各个服务实例的ip和端口不一样,所以这里生成的key也不一样
        oldIpMap.put(ip.getDatumKey(), ip);
    }
    // 接下来的很多操作就是拿老的实例集合和新的实例集合做一些数据比对,如果是新产生的实例那么就进行添加操作,如果是已经存在了的实例则是修改操作
    // 最后将最终的结果赋值给ephemeralInstances或persistentInstances

    List<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());
    if (updatedIPs.size() > 0) {
        for (Instance ip : updatedIPs) {
            Instance oldIP = oldIpMap.get(ip.getDatumKey());

            // do not update the ip validation status of updated ips
            // because the checker has the most precise result
            // Only when ip is not marked, don't we update the health status of IP:
            if (!ip.isMarked()) {
                ip.setHealthy(oldIP.isHealthy());
            }

            if (ip.isHealthy() != oldIP.isHealthy()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),
                                     (ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());
            }

            if (ip.getWeight() != oldIP.getWeight()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP.toString(),
                                     ip.toString());
            }
        }
    }

    List<Instance> newIPs = subtract(ips, oldIpMap.values());
    if (newIPs.size() > 0) {
        Loggers.EVT_LOG
            .info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),
                  getName(), newIPs.size(), newIPs.toString());

        for (Instance ip : newIPs) {
            HealthCheckStatus.reset(ip);
        }
    }

    List<Instance> deadIPs = subtract(oldIpMap.values(), ips);

    if (deadIPs.size() > 0) {
        Loggers.EVT_LOG
            .info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),
                  getName(), deadIPs.size(), deadIPs.toString());

        for (Instance ip : deadIPs) {
            HealthCheckStatus.remv(ip);
        }
    }

    toUpdateInstances = new HashSet<>(ips);

    // 最终,微服务的实例会保存在下面这个集合中
    if (ephemeral) {
        ephemeralInstances = toUpdateInstances;
    } else {
        persistentInstances = toUpdateInstances;
    }
}

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

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

相关文章

Science Robotics 麻省理工学院最新研究,从仿真中学习的精确选择、定位和抓放物体的视触觉方法

现有的机器人系统在通用性和精确性两个性能目标上难以同时兼顾&#xff0c;往往会陷入一个机器人解决单个任务的情况&#xff0c;缺乏"精确泛化"。本文针对精准和通用的同时兼顾提出了解决方法。提出了SimPLE(Pick Localize和placE的仿真模拟)作为精确拾取和放置的解…

C# 如何获取属性的displayName的3种方式

文章目录 1. 使用特性直接访问2. 使用GetCustomAttribute()方法通过反射获取3. 使用LINQ查询总结和比较 在C#中&#xff0c;获取属性的displayName可以通过多种方式实现&#xff0c;包括使用特性、反射和LINQ。下面我将分别展示每种方法&#xff0c;并提供具体的示例代码。 1.…

【Spring Cloud】一个例程快速了解网关Gateway的使用

Spring Cloud Gateway提供了一个在Spring生态系统之上构建的API网关&#xff0c;包括&#xff1a;Spring 5&#xff0c;Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的路由方式&#xff0c;并为它们提供一些网关基本功能&#xff0c;例如&…

轻松驾驭开发之旅:Maven配置阿里云CodeUp远程私有仓库全攻略

文章目录 引言一、为什么选择阿里云CodeUp作为远程私有仓库&#xff1f;二、Maven配置阿里云CodeUp远程私有仓库的步骤准备工作配置Maven的settings.xml文件配置项目的pom.xml文件验证配置是否成功 三、使用阿里云CodeUp远程私有仓库的注意事项 引言 在软件开发的世界里&#…

【Linux进程】命令行参数 环境变量(详解)

目录 前言 1. 命令行参数 什么是命令行参数? 2. 环境变量 常见的环境变量 如何修改环境变量? 获取环境变量 环境变量的组织方式 拓展问题 导入环境变量 3. 本地变量* 总结 前言 在使用Linux指令的时候, 都是指令后边根命令行参数, 每个指令本质都是一个一个的可执行程…

数学系C++ 排序算法简述(八)

目录 排序 选择排序 O(n2) 不稳定&#xff1a;48429 归并排序 O(n log n) 稳定 插入排序 O(n2) 堆排序 O(n log n) 希尔排序 O(n log2 n) 图书馆排序 O(n log n) 冒泡排序 O(n2) 优化&#xff1a; 基数排序 O(n k) 快速排序 O(n log n)【分治】 不稳定 桶排序 O(n…

Kaggle网站免费算力使用,深度学习模型训练

声明&#xff1a; 本文主要内容为&#xff1a;kaggle网站数据集上传&#xff0c;训练模型下载、模型部署、提交后台运行等教程。 1、账号注册 此步骤本文略过&#xff0c;如有需要可以参考其他文章。 2、上传资源 不论是上传训练好的模型进行预测&#xff0c;还是训练用的…

2024组装一台能跑AI大模型的电脑

title: 2024组装一台能跑AI大模型的电脑 tags: [组装电脑, AI大模型] categories: [其他, 电脑, windows] 这里不写组装步骤&#xff0c;哪里接线&#xff0c;购买什么品牌网上一大堆。 这里只写如何根据你自己的需求&#xff0c;选择合适的、兼容的配件。 概述 需求&#xff…

区间最值问题-RQM(ST表,线段树)

1.ST表求解 ST表的实质其实是动态规划&#xff0c;下面是区间最小的递归公式&#xff0c;最大只需将min改成max即可 f[i][j] min(f[i][j - 1], f[i (1 << j - 1)][j - 1]); 二维数组的f[i][j]表示从i开始连续2*j个数的最小/大值。 例如&#xff1a;我们给出一个数组…

iOS中多个tableView 嵌套滚动特性探索

嵌套滚动的机制 目前的结构是这样的&#xff0c;整个页面是一个大的tableView, Cell 是整个页面的大小&#xff0c;cell 中嵌套了一个tableView 通过测试我们发现滚动的时候&#xff0c;系统的机制是这样的&#xff0c; 我们滑动内部小的tableView, 开始滑动的时候&#xff0c…

【驱动篇】龙芯LS2K0300之ADC驱动

实验目的 由于LS2K0300久久派开发板4.19内核还没有现成可用的ADC驱动&#xff0c;但是龙芯官方的5.10内核已经提供了ADC驱动&#xff0c;想要在4.19内核使用ADC就要参考5.10内核移植驱动&#xff0c;本次实验主要是关于ADC驱动的移植和使用 驱动移植 主要的驱动代码主要有3个…

ASRock Creator系列GPU:为AI推理及多GPU系统打造,采用16针电源接口的Radeon RX 7900系列显卡

ASRock 正在筹备推出专为人工智能推理和多GPU系统设计的AMD GPU——Creator系列显卡。这一系列显卡采用双槽位、吹风式设计&#xff0c;并配备16针电源连接器&#xff0c;首发产品包括基于Navi 31架构的AMD Radeon RX 7900XTX和RX 7900 XT型号。这些原属于WS系列的显卡最初在20…

网络安全设备——防火墙

网络安全设备防火墙是一种用来加强网络之间访问控制的特殊网络互联设备。以下是对防火墙的详细解释&#xff1a; 一、定义与基本概念 定义&#xff1a;防火墙是指设置在不同网络&#xff08;如可信任的企业内部网和不可信的公共网&#xff09;或网络安全域之间的一系列部件的…

力扣双指针算法题目:快乐数

目录 1.题目 2.思路解析 3.代码展示 1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2.思路解析 题目意思是将一个正整数上面的每一位拿出来&#xff0c;然后分别求平方&#xff0c;最后将这些数字的平方求和得到一个数字&#xff0c;如此循环&#xff0c;如果在此循环中…

【Linux】在线求助命令--help,man page , info page

我们知道Linux有很多的命令&#xff0c;那LInux要不要背命令&#xff1f; 答案是背最常用的那些就行了 那有的时候我们想查询一些命令的详细用法该怎么办呢&#xff1f; 这里我给出3种方法 1.--help --help的使用方法很简单啊 要查询的命令 --help 我们看个例子 这里我只…

DoIP-1 简介

1. 概述 DoIP-Diagnostic Over Internet Protocol &#xff0c;基于TCPIP协议族的诊断传输协议 DoIP国际标准定义为ISO 13400&#xff0c;总共由五部分组成&#xff1a;  ISO13400-1DoIP的综述  ISO13400-2DoIP的传输层和网络层服务&#xff08;主体部分&#xff09;  I…

vue-element-admin集成方案如何运行以及代码解读

文章目录 1.vue-admin-admin介绍2.运行方法3.代码解读3.1基础外层文件解读&#xff0c;以及eslint关闭&#xff08;如下图&#xff09;3.2内层src文件分析3.2.1 login流程解析 1.vue-admin-admin介绍 1.1 是一个后台前端解决方案(vueelementui)&#xff0c;内置i18国际化解决方…

Javascript常见数据结构和设计模式

在JavaScript中&#xff0c;常见的数据结构包括两大类&#xff1a;原始数据类型&#xff08;Primitive Types&#xff09;和对象类型&#xff08;Object Types&#xff09;。对象类型又可以进一步细分为多种内置对象、数组、函数等。下面是一些JavaScript中常见的数据结构&…

【PWN · ret2syscall | GoPwn】[2024CISCN · 华中赛区]go_note

一道GoPwn&#xff0c;此外便是ret2syscall的利用。然而过程有不小的曲折&#xff0c;参考 返璞归真 师傅的wp&#xff0c;堪堪完成了复现。复现过程中&#xff0c;师傅也灰常热情回答我菜菜的疑问&#xff0c;感谢&#xff01;2024全国大学生信息安全竞赛&#xff08;ciscn&am…

落日余晖映晚霞

落日余晖映晚霞&#xff0c;立于海滨&#xff0c;望夕阳余晖洒于波光粼粼之上&#xff0c;金光跳跃&#xff0c;若繁星闪烁&#xff0c;耀人心目。 海风轻拂&#xff0c;心境宁静&#xff0c;凡尘俗务皆于此刹那消散&#xff0c;思绪万干&#xff0c;或忆往昔点滴&#xff0c;或…