【微服务】SpringCloud微服务续约源码解析

news2025/8/12 12:54:55

目录

一、前言

二、客户端续约

1、入口

1.1、构造初始化

1.2、initScheduledTasks() 调度执行心跳任务

2、TimedSupervisorTask组件

2.1、构造初始化

2.2、TimedSupervisorTask#run()任务逻辑

3、心跳任务

3.1、HeartbeatThread私有内部类

3.2、发送心跳

3、发送心跳到注册中心

3.1、构建请求数据发送心跳

三、服务端处理客户端续约

1、InstanceRegistry#renew()逻辑

2、PeerAwareInstanceRegistryImpl#renew()逻辑

3、AbstractInstanceRegistry#renew()逻辑

3.1、Lease#renew()逻辑


一、前言

    微服务续约都有通用的设计,就是(微服务)客户端使用心跳机制向注册中心报告自己还活着(可以提供服务),它们的心跳机制略有不同。而Eureka Client客户端会每隔 30 秒发送一次心跳来续约, 通过续约来告知 Eureka Server注册中心该 Eureka Client客户端正常运行,没有出现问题。那么心跳机制是什么呢、底层基于什么的?客户端发送心跳的代码在哪里?注册中心怎么处理的?

二、客户端续约

真正触发的还是SpringBoot的自动装配,这里不会过多赘述,下面直奔主题:

1、入口

1.1、构造初始化

    private final ScheduledExecutorService scheduler;
    private final ThreadPoolExecutor heartbeatExecutor;

    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config,
                    AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {

            ......此处省略n行代码.....
        try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh心跳和缓存刷新的默认大小分别为2-1
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            // 心跳执行者
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            ......此处省略n行代码.....

        // 最后,初始化调度任务(例如,集群解析器、 heartbeat、 instanceInfo replicator、 fetch
        initScheduledTasks();

            ......此处省略n行代码.....
}

主要逻辑:

  1. schedulerheartbeatExecutor都是DiscoveryClient的私有成员变量,并且是final的,故在构造方法中必须初始化。而DiscoveryClient的构造初始化前面也讲了,是在SpringBoot的自动装配过程调用的。
  2. 构造方法中:1)scheduler是交给jdk的Executors工具类创建的,核心线程数为2(心跳和缓存刷新需用到)。2)直接调用ThreadPoolExecutor原生构造方法初始化,核心线程数为1,使用SynchronousQueue队列。3)最后,初始化调度任务(例如,集群解析器、 心跳、 服务实例复制、 刷新),进入下面逻辑分析

1.2、initScheduledTasks() 调度执行心跳任务

    private void initScheduledTasks() {

        if (clientConfig.shouldRegisterWithEureka()) {
            /*  LeaseInfo:
                public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;
                // Client settings
                private int renewalIntervalInSecs = DEFAULT_LEASE_RENEWAL_INTERVAL;
             */
            // 默认30
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer心跳任务
            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            );
            // 默认的情况下会每隔30秒向注册中心 (eureka.instance.lease-renewal-interval-in-seconds)发送一次心跳来进行服务续约
            scheduler.schedule(
                    heartbeatTask,
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // InstanceInfo replicator实例信息复制任务
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            // 状态变更监听者
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    // Saw local status change event StatusChangeEvent [timestamp=1668595102513, current=UP, previous=STARTING]
                    logger.info("Saw local status change event {}", statusChangeEvent);
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
            // 初始化状态变更监听者
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            // 定时刷新服务实例信息和检查应用状态的变化,在服务实例信息发生改变的情况下向server重新发起注册
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

主要逻辑:

  1. 根据配置决定是否发送心跳,默认会发送。在LeaseInfo租约信息中维护发送心跳时间,默认间隔为30秒,可以在yml配置文件(eureka.instance.lease-renewal-interval-in-seconds)中更改默认值。
  2. 初始化heartbeatTask,真正的心跳任务为HeartbeatThread类型(下面3分析)。
  3. 调度执行心跳任务,默认的情况下会每隔30秒向注册中心 (eureka.instance.lease-renewal-interval-in-seconds)发送一次心跳来进行服务续约
  4. 本方法下面的逻辑上一节已经分析

2、TimedSupervisorTask组件

可见TimedSupervisorTask是Runnable类型的任务,那么它的任务逻辑在run()方法。

2.1、构造初始化

    public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
                               int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
        this.name = name;
        this.scheduler = scheduler;
        // heartbeatExecutor或cacheRefreshExecutor
        this.executor = executor;
        this.timeoutMillis = timeUnit.toMillis(timeout);
        // HeartbeatThread或CacheRefreshThread等类型任务
        this.task = task;
        this.delay = new AtomicLong(timeoutMillis);
        this.maxDelay = timeoutMillis * expBackOffBound;

        // Initialize the counters and register.
        successCounter = Monitors.newCounter("success");
        timeoutCounter = Monitors.newCounter("timeouts");
        rejectedCounter = Monitors.newCounter("rejectedExecutions");
        throwableCounter = Monitors.newCounter("throwables");
        threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
        Monitors.registerObject(name, this);
    }

初始化自己的一些字段,在阿里Java编程规范中是强烈建议给线程起别名的,这样便于监控排查问题等。name线程名字,在心跳任务中为heartbeat;scheduler字段传递进来是为了周期性执行任务;executor用于提交任务,下面分析;task任务,为HeartbeatThread或CacheRefreshThread类型任务;delay,用于存储以及计算延迟时间;最大延迟时间不能超过maxDelay。

2.2、TimedSupervisorTask#run()任务逻辑

    @Override
    public void run() {
        // Future模式
        Future<?> future = null;
        try {
            // 提交任务待执行
            future = executor.submit(task);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            // 阻塞直到完成或超时
            future.get(timeoutMillis, TimeUnit.MILLISECONDS);  
            // 更新以便计算延迟时间
            delay.set(timeoutMillis);
            // 更新
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            successCounter.increment();
        } catch (TimeoutException e) {
            // 任务主管超时了
            logger.warn("task supervisor timed out", e);
            timeoutCounter.increment();

            long currentDelay = delay.get();
            // 任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间
            long newDelay = Math.min(maxDelay, currentDelay * 2);
            // CAS更新延迟时间,考虑到多线程,所以用了CAS
            delay.compareAndSet(currentDelay, newDelay);

        } catch (RejectedExecutionException e) {
            // 一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略
            if (executor.isShutdown() || scheduler.isShutdown()) {
                // 线程池关闭,拒绝任务
                logger.warn("task supervisor shutting down, reject the task", e);
            } else {
                // 线程池拒绝任务
                logger.warn("task supervisor rejected the task", e);
            }

            rejectedCounter.increment();
        } catch (Throwable e) {
            if (executor.isShutdown() || scheduler.isShutdown()) {
                // 任务主管关闭,不能接受任务
                logger.warn("task supervisor shutting down, can't accept the task");
            } else {
                // 任务主管抛出了一个异常
                logger.warn("task supervisor threw an exception", e);
            }

            throwableCounter.increment();
        } finally {
            // 上面的异常catch了没有外跑,下面继续运行
            if (future != null) {
                // 中断
                future.cancel(true);
            }
            // 调度器没有关闭,延迟继续周期性执行任务。这样的周期性任务时间设置灵活
            if (!scheduler.isShutdown()) {
                scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
            }
        }
    }

主要逻辑:

  1. 提交任务待执行
  2. 阻塞直到完成或超时
  3. 更新以便计算延迟时间
  4. 异常处理:1)超时异常,任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间。CAS更新延迟时间,考虑到多线程,所以用了CAS。2)拒绝执行异常,一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略。3)其他异常
  5. 上面的异常catch了没有外跑,下面继续运行:1)丢弃当前任务;2)调度器没有关闭,延迟继续周期性执行任务。这样的周期性任务时间设置灵活

3、心跳任务

3.1、HeartbeatThread私有内部类

    private class HeartbeatThread implements Runnable {

        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }

HeartbeatThread实现了Runnable接口,也就是上面说的task,它的逻辑封装了出去:

3.2、发送心跳

    boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            // 发送心跳
            httpResponse = eurekaTransport.registrationClient
                    .sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            // 打印日志,如:心跳状态
            logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
            // 响应状态没有找到重新注册
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                REREGISTER_COUNTER.increment();
                logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == Status.OK.getStatusCode();
        } catch (Throwable e) {
            // 无法发送心跳!
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }

主要逻辑:

  1. EurekaTransport是DiscoevryClient的内部类,封装了几个与注册中心通信的XXXclient。获取通信类发送心跳请求,传入appName、唯一ID以及instanceInfo。
  2. 响应状态没有找到重新注册,毕竟当前客户端是运行中正常状态
  3. 返回是否发送心跳成功

3、发送心跳到注册中心

3.1、构建请求数据发送心跳

    @Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        // 拼接请求URL
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            // 构建请求资源
            WebResource webResource = jerseyClient.resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
            if (overriddenStatus != null) {
                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            // 请求注册中心
            response = requestBuilder.put(ClientResponse.class);
            EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
            if (response.hasEntity() &&
                    !HTML.equals(response.getType().getSubtype())) { //don't try and deserialize random html errors from the server
                eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
            }
            // 返回构建结果
            return eurekaResponseBuilder.build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP PUT {}{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                // 关闭资源
                response.close();
            }
        }
    }

主要逻辑:

  1. 拼接请求URL
  2. 构建请求资源数据
  3. 请求注册中心
  4. 返回构建结果
  5. 关闭资源

三、服务端处理客户端续约

InstanceRegistry父子关系图上一节也分析了,在使用super关键字时注意一下。

1、InstanceRegistry#renew()逻辑

	@Override
	public boolean renew(final String appName, final String serverId,
			boolean isReplication) {
		log("renew " + appName + " serverId " + serverId + ", isReplication {}"
				+ isReplication);
		List<Application> applications = getSortedApplications();
		for (Application input : applications) {
			if (input.getName().equals(appName)) {
				InstanceInfo instance = null;
				for (InstanceInfo info : input.getInstances()) {
					if (info.getId().equals(serverId)) {
						instance = info;
						break;
					}
				}
				// 发布服务实例处理心跳事件
				publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId,
						instance, isReplication));
				break;
			}
		}
		// 调用父类的处理心跳方法
		return super.renew(appName, serverId, isReplication);
	}

这里逻辑主要是委托父类处理心跳,具体逻辑见下面分析:

2、PeerAwareInstanceRegistryImpl#renew()逻辑

    public boolean renew(final String appName, final String id, final boolean isReplication) {
        if (super.renew(appName, id, isReplication)) {
            // 服务续约成功,将所有的Eureka操作复制到对等的Eureka节点,除了复制到此节点的流量。
            replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
            return true;
        }
        return false;
    }

PeerAwareInstanceRegistryImpl职责:处理将所有操作复制到 AbstractInstanceRegistry的peer Eureka节点,以保持所有操作同步。这里如果服务续约成功,将所有的Eureka操作复制到对等的Eureka节点,除了复制到此节点的流量。

3、AbstractInstanceRegistry#renew()逻辑

    public boolean renew(String appName, String id, boolean isReplication) {
        RENEW.increment(isReplication);
        // 根据appName从本地注册表服务实例信息
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToRenew = null;
        if (gMap != null) {
            leaseToRenew = gMap.get(id);
        }
        if (leaseToRenew == null) {
            // 没有找到租约
            RENEW_NOT_FOUND.increment(isReplication);
            // 注册: 租约不存在,注册资源:
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            // 获取服务实例信息
            InstanceInfo instanceInfo = leaseToRenew.getHolder();
            if (instanceInfo != null) {
                // touchASGCache(instanceInfo.getASGName());
                InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                        instanceInfo, leaseToRenew, isReplication);
                if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                    logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                            + "; re-register required", instanceInfo.getId());
                    RENEW_NOT_FOUND.increment(isReplication);
                    return false;
                }
                if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                    // 实例状态{}与实例{}的重写实例状态{}不同。因此将状态设置为覆盖状态
                    logger.info(
                            "The instance status {} is different from overridden instance status {} for instance {}. "
                                    + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
                                    overriddenInstanceStatus.name(),
                                    instanceInfo.getId());
                    instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);

                }
            }
            renewsLastMin.increment();
            // 续约
            leaseToRenew.renew();
            return true;
        }
    }

主要逻辑:

  1. 根据appName从本地注册表服务实例信息
  2. 没有找到租约,返回false
  3. 获取到的服务实例状态为UNKNOWN,返回false;续约,更新续约字段,下面分析

3.1、Lease#renew()逻辑

    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;

    }

lastUpdateTimestamp是Lease租约的字段,维护租约时间,在服务剔除下线会根据该字段判断是否过期需要对服务剔除下线处理。下一篇我们就来探讨一下,敬请期待!!!

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

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

相关文章

使用OpenAPI提升网关安全的开源软件,诚邀小伙伴参与

看过我博客的人都知道&#xff0c;我们是一家推广OpenAPI的企业。 OpenAPI是一种用于定义API结构的规范&#xff0c;在Java里我们可以使用swagger进行自动生成。其他语言也可以&#xff08;Golang等&#xff09;。通过这种对开发人员零成本的工具&#xff0c;我们可以高效的获…

典型的偏微分方程数值解法

马上要参加亚太杯啦&#xff0c;听说今年亚太杯有经典的物理题&#xff0c;没什么好说的&#xff0c;盘它&#xff01; 偏微分方程的数值解十分重要 椭圆型偏微分方程&#xff08;不含时&#xff09; 数值解法 二维拉普拉斯方程 例 边界条件 import numpy as np import mat…

教你如何使用云服务器搭建我的世界Minecraft服务器(超级简单-10分钟完成)

一个人玩游戏没啥意思&#xff0c;和朋友一块联机呢&#xff0c;距离太远&#xff0c;家庭局域网宽带又没有公网ip&#xff0c;你的朋友没办法与你联机&#xff0c;然而你只需要一台服务器即可搞定了&#xff1b;但是很多用户没没接触过相关的内容&#xff0c;具体的该怎么操作…

怎样做音乐相册怎样制作?手把手教你制作

大家平时出门游玩的时候&#xff0c;会拍摄一些好看的照片吗&#xff1f;那你们会将这些照片分享在社交平台上吗&#xff1f;普通的照片分享&#xff0c;有时会显得比较枯燥单调&#xff0c;其实我们可以将这些照片制作成音乐相册&#xff0c;这样就可以丰富照片的内容&#xf…

传输层-用户数据报协议(UDP)

UDP协议概述 用户数据报协议 UDP 是 Internet 传输层协议&#xff0c;提供无连接、不可靠、数据报尽力传输服务。 无连接&#xff1a;因此在支持两个进程间通信时&#xff0c;没有握手过程。不可靠&#xff1a;当应用进程将一个报文发送近 UDP 套接字时&#xff0c;UDP 并不能…

python+vue+elementui固定资产管理系统django mysql

目 录 摘 要 I ABSTRACT I 目 录 III 第1章 绪论 1 1.1开发背景 1 1.2开发意义 1 1.3研究内容 1 第2章 主要技术和工具介绍 3 前端技术&#xff1a;nodejsvueelementui 我们最初的项目结构由五个文件组成&#xff1a; manage.py&#xff1a;使用…

为什么管理类硕士(MBA/MEM/MPA)报考会成为职场人的香饽饽?

没个硕士学位&#xff0c;将来出门可能真的都不好意思打招呼了。近些天传言2023年考研人数达到接近550万的信息满天飞&#xff0c;无论真假&#xff0c;从目前已公布报考人数的院校来看&#xff0c;在去年的457万基础上再涨一波的几率是很大的。这其中&#xff0c;报考管理类、…

电科大离散数学-2-命题逻辑-1

目录 2.1 什么是命题 2.1.1 命题的定义 2.1.2 复合命题 2.2 命题联结词 2.2.1 否定联结词 2.2.2 合取联结词 2.2.3 析取联结词 2.2.4 蕴涵联结词 2.2.5 等价联结词 2.3 命题符号化及应用 2.3.1 命题连接词总结 2.3.2 命题联结词的优先级 2.3.3 命题联接词与开关电…

scala

Scala 概述 Scala是一门以Java虚拟机&#xff08;JVM&#xff09;为运行环境并将面向对象和函数式编程的最佳特性结合在一起的 静态类型编程语言&#xff08;静态语言需要提前编译的如&#xff1a;Java、c、c等&#xff0c;动态语言如&#xff1a;js&#xff09;。 Scala是一…

4-20mA转RS-485,Modbus数据采集模块 YL121

特点&#xff1a; ● 模拟信号采集&#xff0c;隔离转换 RS-485输出 ● 采用12位AD转换器&#xff0c;测量精度优于0.1% ● 通过RS-485接口可以程控校准模块精度 ● 信号输入 / 输出之间隔离耐压1000VDC ● 宽电源供电范围&#xff1a;8 ~ 32VDC ● 可靠性高&#xff0c;…

equals与==判断相等

一、 判断相等&#xff0c;判断的是物理地址相等。 二、equals 判断相等 equals 与hashCode 都是Object的方法。 所有的类都继承于Object&#xff0c;如果不重写equals。equals判断相等&#xff0c;底层也是使用来判断物理地址相等。 public boolean equals(Object obj) {re…

影响MySQL索引B+树高度的是什么?

提到MySQL&#xff0c;想必大多后端同学都不会陌生&#xff0c;提到B树&#xff0c;想必还是有很大部分都知道InnoDB引擎的索引实现&#xff0c;利用了B树的数据结构。 那InnoDB 的一棵B树可以存放多少行数据&#xff1f;它又有多高呢&#xff1f; 到底是哪些因素会对此造成影…

【软件测试】测试人的职责,我就是不当背锅侠......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 测试的目的&#xf…

基于机器视觉的移动消防机器人(四)--实验验证

本文素材来源于北方民族大学 机电工程学院 作者&#xff1a;牟义达、黄瑞翔、李涛 指导老师&#xff1a;田国禾、张春涛 1. 自主行走功能验证 实验目的&#xff1a;让机器人小车行驶500ms后停500ms&#xff0c;循环重复。 实验器材&#xff1a;计算机、消防机器人小车。 实…

ACM MM ECCV 2022 | 美团视觉8篇论文揭秘内容领域的智能科技

人工智能技术正在成为内容领域的中台力量&#xff0c;其中视觉AI已经渗透到内容生产、内容审核、内容分发、用户互动、商业化变现等各个环节。美团视觉智能部以场景化的内容产品、智能化的内容工具助力产业&#xff0c;在内容的创作、内容分发等环节应用广泛。 前不久&#xff…

开源项目让你也可以尝试玩转工业物联网以及智慧工厂(智能制造),IOT开源网关、SCADA取数开源、PLC数据采集

物联网进入与传统产业深度融合发展的崭新阶段。未来10年内&#xff0c;全球物联网将创造10多万亿美元的价值&#xff0c;约占全球经济的1/10&#xff0c;并与城市管理、生产制造、汽车驾驶、能源环保等形成数个千亿级规模以上的细分市场。 随着物联网技术的快速发展&#xff0c…

win10怎么录屏?windows自带录屏功能怎么用

​相信很多小伙伴家里的电脑都是win10系统的&#xff0c;想要录制电脑上的画面&#xff0c;那么就需要用到了windows自带的录屏功能。win10怎么录屏&#xff1f;windows自带的录屏功能怎么用&#xff1f;别担心&#xff0c;今天小编就来教教大家如何在win10系统上录制电脑屏幕。…

Python程序员:代码写的好,丝滑的壁纸少不了

人生苦短&#xff0c;我用Python序言python批量下载最后序言 不知道大家的电脑桌面一般用的什么类型的壁纸&#xff1f; 早上来上班&#xff0c;打开电脑&#xff0c;被漂亮的桌面壁纸所吸引&#xff0c;年底将近&#xff0c;这又是哪个地方的节日&#xff1f; 才晓得&#x…

[附源码]java毕业设计美妆销售系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]java毕业设计农产品网络销售系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…