【Nacos源码系列】服务注册的原理

news2024/11/26 19:27:11

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

文章目录

  • Nacos介绍
  • 服务注册
    • 客户端注册
    • 服务端
  • 总结

Nacos介绍

Nacos是一个基于云原生的动态服务发现、配置管理和服务治理平台,由阿里巴巴开源。它提供了服务注册与发现、配置管理、动态DNS、流量管理、服务降级、负载均衡、限流、路由管理等一系列核心功能,可以帮助企业构建弹性可扩展的微服务架构。

Nacos 架构

本文将从源码的角度介绍一下Nacos服务注册原理。

服务注册

服务注册是指将某个服务的相关信息(如服务名称、IP地址、端口号等)注册到服务注册中心中,以便其他服务或客户端能够发现和访问该服务。

在微服务架构中,由于服务的数量众多,服务之间的调用关系也变得复杂且动态,因此需要一种统一和自动化的方式来管理和发现服务。而服务注册中心就是这样一种机制,它可以帮助服务消费者快速地找到可用的服务提供者,并完成服务调用。

本文将从客户端和服务端的角度介绍Nacos服务注册的原理。

  • 客户端注册主要介绍客户端是怎么注册到Nacos中的。
  • 服务端注册主要介绍接收到客户端注册请求后服务端是怎么处理的。
客户端服务端
版本Spring Cloud Alibaba 2021.0.1.01.4.0

客户端注册

Spring Cloud Alibaba Nacos客户端是基于Springboot自动装配功能实现的,了解Springboot自动装配原理的应该知道首先要查看 spring.factories 文件有哪些自动装配的类。

spring.factories

从名字可以看出NacosServiceRegistryAutoConfiguration类是进行服务注册的,源码如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosServiceRegistry(nacosDiscoveryProperties);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosRegistration nacosRegistration(
			ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
			NacosDiscoveryProperties nacosDiscoveryProperties,
			ApplicationContext context) {
		return new NacosRegistration(registrationCustomizers.getIfAvailable(),
				nacosDiscoveryProperties, context);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}
}

@ConditionalOnNacosDiscoveryEnabled 注解用于使@EnableDiscoveryClient 注解生效,但其实有没有在启动类使用@EnableDiscoveryClient都能让客户端生效。因为@ConditionalOnProperty 注解的属性matchIfMissing = true,也就是有没有对应的value值,自动配置都会生效。

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled",
		matchIfMissing = true)
public @interface ConditionalOnNacosDiscoveryEnabled {
}

从源码看到NacosServiceRegistryAutoConfiguration 类会注入以下3个类:

  1. NacosServiceRegistry :实现了Springcloud的ServiceRegistry接口,ServiceRegistry接口是Springcloud提供的一个服务注册的接口,如果想集成Springcloud注册中心必须要实现的接口。NacosServiceRegistry实现了它,并完成nacos服务注册功能。

  2. NacosRegistration :存储nacos服务端信息,它实现了Registration接口,Registration接口中没有任何东西,只是实现了ServiceInstance 接口,ServiceInstance 接口存储一些服务实例的信息。

  3. NacosAutoServiceRegistration:继承了AbstractAutoServiceRegistration抽象类,AbstractAutoServiceRegistration抽象类实现了AutoServiceRegistration , ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> ,通过spring的事件监听机制来完成服务的注册。

NacosAutoServiceRegistration

先来看下NacosAutoServiceRegistration的父类AbstractAutoServiceRegistration抽象类部分源码:

public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration,ApplicationContextAware,ApplicationListener<WebServerInitializedEvent>{
    // 具体的服务注册接口
    private final ServiceRegistry<R> serviceRegistry;
    // 自动注册配置
    private AutoServiceRegistrationProperties properties;

    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
            AutoServiceRegistrationProperties properties) {
        this.serviceRegistry = serviceRegistry;
        this.properties = properties;
    }

    // 监听接口
    @Override
    @SuppressWarnings("deprecation")
    public void onApplicationEvent(WebServerInitializedEvent event) {
        bind(event);
    }

    // 绑定WebServerInitializedEvent事件
    @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();
    }

    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 below
        if (!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);
        }
    }

    //销毁服务
    @PreDestroy
    public void destroy() {
        stop();
    }

    /**
    *  注册服务
    * Register the local service with the {@link ServiceRegistry}.
    */
    protected void register() {
        //真正注册服务的地方
        this.serviceRegistry.register(getRegistration());
    }
}

从源码可以看到 AbstractAutoServiceRegistration实现了ApplicationListener 监听接口,并监听了WebServerInitializedEvent 事件,WebServerInitializedEvent 事件在容器启动时会进行事件发布。

AbstractAutoServiceRegistration 启动流程是这样的:
容器启动时,发布了WebServerInitializedEvent 事件,AbstractAutoServiceRegistration会调用onApplicationEvent(WebServerInitializedEvent)方法,onApplicationEvent()方法会调用绑定了WebServerInitializedEvent 事件的bind(WebServerInitializedEvent) 方法,同时后续调用start()方法启动注册流程,start()方法会调用ServiceRegistry#register() 开始进行注册。

AbstractAutoServiceRegistration注册流程

NacosServiceRegistry实现了ServiceRegistry接口,所以 注册从NacosServiceRegistry#register()方法开始的。

@Override
public void register(Registration registration) {
    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
        return;
    }
    // 通过配置文件获取服务相关的信息
    NamingService namingService = namingService();

    String serviceId = registration.getServiceId();
    String group = nacosDiscoveryProperties.getGroup();
    // 通过配置文件获取实例相关的信息
    Instance instance = getNacosInstanceFromRegistration(registration);

    try {
        //服务注册
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                instance.getIp(), instance.getPort());
    }
    catch (Exception e) {
        if (nacosDiscoveryProperties.isFailFast()) {
            log.error("nacos registry, {} register failed...{},", serviceId,
                    registration.toString(), e);
            rethrowRuntimeException(e);
        }
        else {
            log.warn("Failfast is false. {} register failed...{},", serviceId,
                    registration.toString(), e);
        }
    }
}

NacosServiceRegistry#register()方法中,会先通过客户端的配置文件创建一个 NamingService ,然后把封装过服务实例的基本信息的 Registration 对象(Registration 接口只是空继承了 ServiceInstance 接口,类中没有任何方法)生成一个 Instance 对象,最后通过 NamingService#registerInstance() 将服务实例注册到Nacos注册中心去。

追溯源码可以发现 NamingService 是通过**NamingFactory#createNamingService(Properties)**方法 创建的:

public static NamingService createNamingService(Properties properties) throws NacosException {
    try {
        Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
        Constructor constructor = driverImplClass.getConstructor(Properties.class);
        NamingService vendorImpl = (NamingService) constructor.newInstance(properties);
        return vendorImpl;
    } catch (Throwable e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
}

createNamingService 方法通过反射调用**NacosNamingService(Properties)**构造方法创建对象,

public NacosNamingService(Properties properties) throws NacosException {
    init(properties);
}

//初始化操作
private void init(Properties properties) throws NacosException {
    //验证properties参数是否正确
    ValidatorUtils.checkInitParam(properties);
    //初始化namespace
    this.namespace = InitUtils.initNamespaceForNaming(properties);
    InitUtils.initSerialization();
    //初始化server 地址
    initServerAddr(properties);

    InitUtils.initWebRootContext();
    //初始化缓存目录
    initCacheDir();
    //初始化日志名
    initLogName(properties);

    //创建事件转发器
    this.eventDispatcher = new EventDispatcher();
    this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
    //初始化心跳反应
    this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
    this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
    isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}

再看一下 NacosNamingService#registerInstance() 方法:

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    // 临时节点
    if (instance.isEphemeral()) {
        //封装心跳信息
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        //加入定时任务
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    //实际进行代理注册服务
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

//添加定时任务
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    // 心跳key,形如:  DEFAULT_GROUP@@provider#192.168.71.70#9093
    String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());

    BeatInfo existBeat = null;
    //fix #1733
    if ((existBeat = dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }
    dom2Beat.put(key, beatInfo);
    //心跳定时任务 默认5s一次
    executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

NacosNamingService#registerInstance() 方法会对临时节点一个心跳封装,创建一个 BeatTask 心跳任务以保证客户端的健康,默认5s执行一次,最后通过NamingProxy#registerService执行服务注册。

进行服务注册的是
com.alibaba.nacos.client.naming.net.NamingProxy#registerService方法,且看源码:

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<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()));

    // 封装请求
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}

NamingProxy#registerService方法会将服务实例的信息放到Map 集合中,然后传递给reqApi方法。
reqApi方法调用callServer方法,callServer方法最终会把这些包含客户端信息的参数封装起来并生成一个url,通过Nacos提供的openAPI形式调用服务端接口完成注册。

请求参数

总结一下客户端注册流程,客户端启动时,会向Nacos注册中心发送注册请求。注册请求中包含了服务名、IP地址、端口号和其他元数据信息。客户端将自己的信息注册到Nacos中心,这样服务消费者才能够发现它。
在Nacos中,服务提供者注册的信息被称为服务实例(Instance),一个服务可以有多个实例,每个实例都有一个唯一的ID来标识自己。

服务端

客户端注册完成之后,服务端在接到注册请求之后会做什么呢?

通过actuator找到到客户端请求注册之后服务端的controller:

InstanceController

InstanceController类用于处理服务注册和发现的HTTP请求,包括注册、注销和查询服务实例列表等操作。

@Autowired
private ServiceManager serviceManager;

@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);
    //检查服务格式是否正确
    checkServiceNameFormat(serviceName);
    //将请求中的参数转化为实例信息
    final Instance instance = parseInstance(request);
    //服务端注册实例信息
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

InstanceController#register方法将客户端传过来的参数进行解析并进行一系列操作之后,通过ServiceManager#registerInstance方法进行实例注册。

ServiceManager 类提供了一些注册和管理服务的方法。来看下ServiceManager#registerInstance方法将一个服务实例注册到Nacos注册中心中,且看源码:

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    // 创建一个服务
    createEmptyService(namespaceId, serviceName, instance.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);
}

createEmptyService 方法会初始化一个服务,使服务能注册到nacos注册中心。

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

/**
    * Create service if not exist.
    *
    * @param namespaceId namespace
    * @param serviceName service name
    * @param local       whether create service by local
    * @param cluster     cluster
    * @throws NacosException nacos exception
    */
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
        throws NacosException {
    //根据namespace和服务名称获取服务,获取不到新建一个服务。
    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();
        //将服务放入serviceMap本地缓存中并进行初始化
        putServiceAndInit(service);

        if (!local) {
            addOrReplaceService(service);
        }
    }
}

Service对象

通过 createServiceIfAbsent 会进行创建服务,并在最后将创建好的服务放到本地缓存中。

/**
* Map(namespace, Map(group::serviceName, Service)).
* 同一个namespace下放到一起,同一个组的服务
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

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());
}

 /**
* Put service into manager.
*
* @param service service
*/
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);
}

可以看到最后nacos服务端将服务缓存到private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();中,nacos通过不同的 namespace 来管理服务,在同一个 namespace 下又使用不同的group来管理服务。

至此,服务端接收到客户端的简单流程就结束了,至于服务端怎么更新缓存、服务集群怎么更新实例、还有诸如心跳健康检查之类的后续再介绍。

总结一下服务端注册流程,Nacos注册中心接收到服务提供者的注册请求后,将服务实例的信息保存到本地缓存或持久化存储中。同时,Nacos还会根据服务名和实例的标识来生成一个全局唯一的服务实例ID。

总结

最后总结一下服务注册的流程:

  1. 服务启动时,根据spring.factories文件自动注入 spring-cloud-common包下的 AutoServiceRegistrationConfiguration 类,同时注入 Nacos的 NacosServiceRegistryNacosRegistration类。
  2. AutoServiceRegistrationConfiguration 类通过监听 WebServerInitializedEvent 容器启动事件,调用start()方法开始注册流程。
  3. start()方法会调用 ServiceRegistry 接口的实现类 NacosServiceRegistryregister() 进行注册。
  4. NacosServiceRegistry#register()方法中会创建一个 NamingServiceInstance 对象,然后通过NacosNamingService#registerInstance()方法将Instance 对象注册到注册中心。
  5. NacosNamingService#registerInstance()方法会给客户端
    开启一个心跳定时任务以保证服务端能感知客户端的存活,同时通过 NamingProxy#registerService将客户端服务名、IP地址、端口号和其他元数据信息以openAPI的形式请求给服务端,完成客户端的服务注册。
  6. 服务端在InstanceController#register方法中处理客户端的注册请求。
  7. 接收到客户端请求后ServiceManager#registerInstance方法会将客户端注册进来,并将客户端缓存到一个名为 serviceMap 数据结构为 ConcurrentHashMap 的本地缓存中。

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

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

相关文章

cppzmq编译和使用(ubuntu22.04)

libzmq libzmq&#xff0c;也称为 ZeroMQ&#xff0c;是一个高性能、异步消息传递库&#xff0c;用于构建分布式和并发系统。它提供了简单而强大的原语&#xff0c;使得开发者可以方便地在应用程序之间进行异步通信。 以下是对 libzmq 的详细介绍&#xff1a; 简介&#xff1…

VOC数据洞察在淘宝详情页的应用与实践

本专题共10篇内容&#xff0c;包含淘宝APP基础链路过去一年在用户体验数据科学领域&#xff08;包括商详、物流、性能、消息、客服、旅程等&#xff09;一些探索和实践经验&#xff0c;本文为该专题第二篇。 在商详页基于用户动线和VOC挖掘用户决策因子带来浏览体验提升&#x…

如何使用 PowerPoint 2021 制作演示文稿(PPT)?

软件安装&#xff1a;办公神器office2021安装教程&#xff0c;让你快速上手_正经人_____的博客-CSDN博客 引言 PowerPoint 是一款非常常用的演示文稿制作工具&#xff0c;它可以帮助您创建漂亮的幻灯片&#xff0c;展示您的想法和信息。如果您是 PowerPoint 的新手&#xff…

如何选择初始化向量【密码学】(5)

目录 一、分组算法如何计算 二、什么影响算法的安全性 三、密钥的使用次数限制 一、分组算法如何计算 分组算法包括3个部分&#xff1a;数据分组&#xff0c;分组运算和链接模式。 数据分组&#xff1a;将数据分割成加密函数能够处理的数据块&#xff0c;如果不能整分&#x…

【MYSQL篇】mysql不同存储引擎中索引是如何实现的?

前言 不同的存储引擎文件是不一样&#xff0c;我们可以查看数据文件目录&#xff1a; show VARIABLES LIKE datadir;每 张 InnoDB 的 表 有 两 个 文 件 &#xff08; .frm 和 .ibd &#xff09;&#xff0c; MyISAM 的 表 有 三 个 文 件 &#xff08;.frm、.MYD、.MYI&…

简单的PWN堆栈溢出的尝试

这是一道2018年西电CTF线下赛的一道ez_pwn的小题目&#xff0c;该题目为堆栈溢出漏洞的利用1 本次实验环境为 ubuntu 20.0.4 使用工具&#xff1a;GDB pwngdb 首先分析文件大致情况 checksec ez_pwn Arch: amd64-64-little 表示该二进制文件是 64 位的 。 RELRO: Partial R…

计算机网络课程设计——中小型网络工程设计

文件地址:https://github.com/Recursiondzl/Computer-Network github里面有课设文件&#xff0c;别白嫖&#xff0c;点个star哦 摘 要&#xff1a;本次计算机网络实践&#xff0c;完成了中小型网络工程设计与实现对计算机网络知识进行了系统的复习&#xff0c;实践能力获得了…

0基础学习VR全景平台篇第45篇:编辑器底部菜单- 关联场景功能操作

大家好&#xff0c;欢迎观看蛙色VR官方系列——后台使用课程&#xff01; 本期为大家带来蛙色VR平台&#xff0c;底部菜单—关联场景功能操作。 一、本功能将用在哪里&#xff1f; 关联场景&#xff0c;是某个场景下的子场景&#xff0c;也可以理解为VR漫游作品的三级分组&…

1-简单回归问题

一.梯度下降&#xff08;gradient descent&#xff09; 1.预测函数 这里有一组样本点&#xff0c;横纵坐标分别代表一组有因果关系的变量 我们的任务是设计一个算法&#xff0c;让机器能够拟合这些数据&#xff0c;帮助我们算出参数w 我们可以先随机选一条过原点的直线&#xf…

【GESP】2023年03月图形化一级 -- 小猫捉老鼠

文章目录 小猫捉老鼠1. 准备工作2. 功能实现3. 设计思路与实现&#xff08;1&#xff09;角色、舞台背景设置a. 角色设置b. 舞台背景设置 &#xff08;2&#xff09;脚本编写a. 角色&#xff1a;Mouse1b. 角色&#xff1a;Cat 2 4. 评分标准 小猫捉老鼠 1. 准备工作 &#xff…

Vue3项目中使用vue-router

目录 1、Vue Router 的主要概念和功能2、什么是 vue-router&#xff1f;3、为什么需要 vue-router?4、基本概念和安装4.1 了解单页面应用&#xff08;SPA&#xff09;和路由的基本概念4.1.1单页面应用&#xff08;Single Page Application&#xff0c;SPA&#xff09;4.1.2路由…

【运维知识进阶篇】zabbix5.0稳定版详解3(监控Nginx+PHP服务状态信息)

这篇文章继续给大家介绍zabbix监控&#xff0c;监控Nginx、PHP等服务&#xff0c;其实非常简单&#xff0c;难点在于如何去取这个值&#xff0c;包括监控业务&#xff0c;难点在于思路是否清晰&#xff0c;思维是否活跃&#xff0c;如何去进行判断是否有这个业务&#xff0c;并…

小白到运维工程师自学之路 第三十四集 (redis的基本使用)

一、概念 Redis是一个开源的内存数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。Redis支持多种数据结构&#xff0c;如字符串、哈希表、列表、集合、有序集合等。Redis的特点是数据存储在内存中&#xff0c;因此读写速度非常快&#xff0c;同时也支持数据持…

【Vue3+Ts project】认识 @vueuse/core 库

目标: 根据屏幕宽度改变 实现动态获取盒子的宽度 目录 目标: 一、javascript实现 二、vueuse/core 库实现 一、javascript实现 1.首先 window.innerWidth 获取当前屏幕宽度&#xff0c;然后将 盒子宽度 除 375 乘 当前屏幕宽度 150 / 375 * window.innerWidth 2.将获取的…

千万不要跟随这 4 种领导!

​ 见字如面&#xff0c;我是军哥&#xff01; 最近有程序员读者问我&#xff0c;什么样的领导不能跟随&#xff1f;都有哪些坑&#xff01;这个我擅长哈&#xff0c;毕竟职场混迹 15 年&#xff5e; 第一种&#xff0c;技术能力不行还喜欢指手画脚的领导。 第二种&#xff0c;…

鹏云网络分布式块存储社区版问世,首发开源存储解决方案

2023年1月&#xff0c;南京鹏云网络科技有限公司&#xff08;简称&#xff1a;鹏云网络&#xff09;正式宣布开源ZettaStor DBS分布式块存储系统&#xff0c;开放了自研10余年的分布式块存储技术&#xff0c;自此踏上了“自研”与“开源”一体并行的生态闭环之路。 研发十年&am…

python程序获取最新的行政区划名称代码

一、实现目标 最近由于项目需要,需要获取最新的过去全国县以上行政区划的名称和代码。网上虽然有一些资料,但是不是需要积分就是需要会员,而且担心这些资料不是最新的。因此,想着使用程序从官方网站上获取最新的全国行政区划数据。 二、实现思路 1、找到官方最新发布的全国…

c++11 标准模板(STL)(std::basic_ios)(五)

定义于头文件 <ios> template< class CharT, class Traits std::char_traits<CharT> > class basic_ios : public std::ios_base 类 std::basic_ios 提供设施&#xff0c;以对拥有 std::basic_streambuf 接口的对象赋予接口。数个 std::basic_ios…

【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度

目录 前言&#xff1a; 引入&#xff1a; 时间复杂度&#xff1a; 案例&#xff1a; 空间复杂度&#xff1a; 案例&#xff1a; TIPS&#xff1a; 总结&#xff1a; 前言&#xff1a; 今天我们将来介绍时间复杂度和空间复杂度&#xff0c;我们代码的优劣就是依…

力扣算法刷题Day38|动态规划:斐波那契数 爬楼梯 使用最小花费爬楼梯

力扣题目&#xff1a;#509. 斐波那契数 刷题时长&#xff1a;参考答案后5min 解题方法&#xff1a;动态规划 复杂度分析 时间O(n)空间O(n) 问题总结 无 本题收获 动规五部曲思路 确定dp数组以及下标的含义&#xff1a;dp[i]的定义为&#xff0c;第i个数的斐波那契数值…