【微服务】Nacos服务发现源码分析

news2024/11/24 6:07:17

💖Spring家族及微服务系列文章

✨【微服务】SpringBoot监听器机制以及在Nacos中的应用

✨【微服务】Nacos服务端完成微服务注册以及健康检查流程

✨【微服务】Nacos客户端微服务注册原理流程

✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理

✨【微服务】SpringBoot启动流程注册FeignClient

✨【微服务】SpringBoot启动流程初始化OpenFeign的入口

✨Spring Bean的生命周期

✨Spring事务原理

✨SpringBoot自动装配原理机制及过程

✨SpringBoot获取处理器流程

✨SpringBoot中处理器映射关系注册流程

✨Spring5.x中Bean初始化流程

✨Spring中Bean定义的注册流程

✨Spring的处理器映射器与适配器的架构设计

✨SpringMVC执行流程图解及源码

目录

💖Spring家族及微服务系列文章

💖前言

💖Nacos服务发现

✨流程图

✨服务发现的入口

💫SpringCloud原生项目spring-cloud-commons

💫Nacos是如何继承下来的?

💫NacosServiceDiscovery#getInstances()获取服务实例

✨NacosNamingService初始化流程

💖NacosNamingService构造初始化

💫HostReactor构造初始化

💖PushReceiver构造初始化

💫PushReceiver#run

✨从集成的client模块本地服务发现

💫获取服务实例列表

💖从本地缓存/发送http从服务端获取服务信息

💫从本地缓存获取

💫发送HTTP调用从Nacos服务端获取

💫scheduleUpdateIfAbsent()

💫UpdateTask#run()任务逻辑

💫queryList()发送http请求注册中心


💖前言

     这篇文章就介绍下,服务发现的入口是什么?本地缓存数据结构、缓存时机、如果缓存中没有如何处理?使用了定时任务,那定时任务的底层基于什么的、它是干什么的、定时间隔?监听服务端UDP通知、发送ACK?发送http请求到服务端,谁发的、如何接收?服务端如何推送服务实例的、采用什么方式?带着这些问题,下面我们来探究探究。

    注意:Nacos源码版本为1.x

💖Nacos服务发现

✨流程图

✨服务发现的入口

💫SpringCloud原生项目spring-cloud-commons

    你会发现@EnableDiscoveryClient注解也是在spring-cloud-commons项目,还有个discovery文件夹。我们本节注意下DiscoveryClient接口,以及其中声明的接口方法。SpringCloud是由几个关键项目组成的,spring-cloud-commons项目是其中之一。SpringCloud Alibaba也不是完全替代SpringCloud的,一些基本的规范还是继承下来了,做扩展等。

💫Nacos是如何继承下来的?

    Nacos是通过自己的spring-cloud-alibaba-nacos-discovery项目去集成到SpringCloud的以及基于SpringBoot的自动装配机制集成到SpringBoot项目的。而服务发现方面,NacosDiscoveryClient 实现了spring-cloud-commons项目的DiscoveryClient接口,即Nacos中服务发现入口是NacosDiscoveryClient类。

点击方法继续跟进到下面的逻辑

💫NacosServiceDiscovery#getInstances()获取服务实例

​
    public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
        // 获取配置文件组信息
        String group = this.discoveryProperties.getGroup();
        // 调用API模块中NamingService的selectInstances()方法,
        // 引用是NacosNamingService的反射获取,之前文章已分析
        List<Instance> instances = this.namingService().selectInstances(serviceId, group, true);
        // 将Nacos的服务实例适配为SpringCloud的ServiceInstance服务实例
        return hostToServiceInstanceList(instances, serviceId);
    }

​

主要逻辑:

  1. 获取配置文件组信息
  2. 调用API模块中NamingService接口的selectInstances()方法。引用是NacosNamingService的,通过反射获取,之前文章已详细分析。NacosNamingService是Nacos的client模块里面的一个组件,下面分析。
  3. 将Nacos的服务实例适配为SpringCloud的ServiceInstance服务实例

✨NacosNamingService初始化流程

    它的构造方法是在NamingFactory通过反射方式调用的,上面也提到了。因为这个流程也是不小的,故在获取服务实例前先讲解。

💖NacosNamingService构造初始化

    
    public NacosNamingService(Properties properties) throws NacosException {
        init(properties);
    }
    
    private void init(Properties properties) throws NacosException {
        ValidatorUtils.checkInitParam(properties);
        this.namespace = InitUtils.initNamespaceForNaming(properties);
        InitUtils.initSerialization();
        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));
        // 初始化hostReactor 
        this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
                isLoadCacheAtStart(properties), initPollingThreadCount(properties));
    }

初始化服务代理、心跳发送组件以及hostReactor,重点看hostReactor的构造初始化

💫HostReactor构造初始化

    public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, BeatReactor beatReactor,
            String cacheDir, boolean loadCacheAtStart, int pollingThreadCount) {
        // init executorService
        this.executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.client.naming.updater");
                return thread;
            }
        });
        this.eventDispatcher = eventDispatcher;
        this.beatReactor = beatReactor;
        this.serverProxy = serverProxy;
        this.cacheDir = cacheDir;
        // 初始化本地缓存
        if (loadCacheAtStart) {
            this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
        } else {
            this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
        }
        
        this.updatingMap = new ConcurrentHashMap<String, Object>();
        this.failoverReactor = new FailoverReactor(this, cacheDir);
        // 初始化pushReceiver
        this.pushReceiver = new PushReceiver(this);
    }

初始化本地缓存、pushReceiver,重点关注PushReceiver的构造方法

💖PushReceiver构造初始化

    public PushReceiver(HostReactor hostReactor) {
        try {
            this.hostReactor = hostReactor;
            // 初始化udp套接字
            this.udpSocket = new DatagramSocket();
            // 启动一个线程
            this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.naming.push.receiver");
                    return thread;
                }
            });
            // 执行任务,下面的run()
            this.executorService.execute(this);
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] init udp socket failed", e);
        }
    }

初始化udp套接字用于监听注册中心变更服务推送以及发送ack确认、启动一个线程死循环用于监听注册中心udp推送服务变更、执行任务,this就是PushReceiver的引用即任务,所以执行下面的run()逻辑。

💫PushReceiver#run

    @Override
    public void run() {
        while (!closed) {
            try {
                
                // byte[] is initialized with 0 full filled by default
                byte[] buffer = new byte[UDP_MSS];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                // 监听Nacos服务端服务实例信息变更后的通知
                udpSocket.receive(packet);
                
                String json = new String(IoUtils.tryDecompress(packet.getData()), UTF_8).trim();
                NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
                
                PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class);
                String ack;
                if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
                    // 将数据缓存到本地
                    hostReactor.processServiceJson(pushPacket.data);
                    
                    // send ack to server
                    ack = "{\"type\": \"push-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":"
                            + "\"\"}";
                } else if ("dump".equals(pushPacket.type)) {
                    // dump data to server
                    ack = "{\"type\": \"dump-ack\"" + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":"
                            + "\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap()))
                            + "\"}";
                } else {
                    // do nothing send ack only
                    ack = "{\"type\": \"unknown-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime
                            + "\", \"data\":" + "\"\"}";
                }
                // 发送ack到服务端
                udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length,
                        packet.getSocketAddress()));
            } catch (Exception e) {
                NAMING_LOGGER.error("[NA] error while receiving push data", e);
            }
        }
    }

主要逻辑:

  1. 监听Nacos服务端服务实例信息变更后的通知
  2. 解析注册中心推送的结果,组装回调ack报文,将注册中心推送的变更服务信息缓存到本地
  3. 发送ack到注册中心,以便注册中心决定是否需要重试。

✨从集成的client模块本地服务发现

    本节点讲解的就是客户端服务发现,之所以这样说是因为SpringBoot的自动装配将Nacos的client模块集成进来了,想了解更多去看前面的文章分析。

💫获取服务实例列表

调用重载的selectInstances()方法,healthy默认true即健康,subscribe默认true即订阅

    @Override
    public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,
            boolean subscribe) throws NacosException {
        
        ServiceInfo serviceInfo;
        // 默认订阅模式
        if (subscribe) {
            // 委托hostReactor处理
            serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
                    StringUtils.join(clusters, ","));
        } else {
            serviceInfo = hostReactor
                    .getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),
                            StringUtils.join(clusters, ","));
        }
        // 选取健康实例
        return selectInstances(serviceInfo, healthy);
    }

    默认使用订阅模式,但是委托hostReactor去获取服务信息,以服务名、分组拼接作为入参即Nacos可识别的服务名。

💖从本地缓存/发送http从服务端获取服务信息

    public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
        // failover-mode:默认false
        NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (failoverReactor.isFailoverSwitch()) {
            return failoverReactor.getService(key);
        }
        // 从本地缓存serviceInfoMap获取
        ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
        // 如果本地缓存中没有,则发送HTTP调用从Nacos服务端获取
        if (null == serviceObj) {
            serviceObj = new ServiceInfo(serviceName, clusters);
            
            serviceInfoMap.put(serviceObj.getKey(), serviceObj);
            
            updatingMap.put(serviceName, new Object());
            // 更新服务
            updateServiceNow(serviceName, clusters);
            updatingMap.remove(serviceName);
            
        } else if (updatingMap.containsKey(serviceName)) {
            
            if (UPDATE_HOLD_INTERVAL > 0) {
                // hold a moment waiting for update finish等待更新完成
                synchronized (serviceObj) {
                    try {
                        serviceObj.wait(UPDATE_HOLD_INTERVAL);
                    } catch (InterruptedException e) {
                        NAMING_LOGGER
                                .error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                    }
                }
            }
        }
        // 开启一个定时任务,每隔1秒从Nacos服务端获取最新的服务实例信息,
        // 更新到本地缓存seriveInfoMap中
        scheduleUpdateIfAbsent(serviceName, clusters);
        // 从本地缓存serviceInfoMap中获取服务实例信息
        return serviceInfoMap.get(serviceObj.getKey());
    }

主要逻辑:

  1. 从本地缓存serviceInfoMap获取
  2. 如果本地缓存中没有,则发送HTTP调用从Nacos服务端获取
  3. 开启一个定时任务,每隔1秒从Nacos服务端获取最新的服务实例信息, 更新到本地缓存seriveInfoMap中
  4.  从本地缓存serviceInfoMap中获取服务实例信息

💫从本地缓存获取

    private ServiceInfo getServiceInfo0(String serviceName, String clusters) {
        
        String key = ServiceInfo.getKey(serviceName, clusters);
        // 本地缓存serviceInfoMap获取
        return serviceInfoMap.get(key);
    }

就单纯地从本地缓存serviceInfoMap获取

💫发送HTTP调用从Nacos服务端获取

    public void updateService(String serviceName, String clusters) throws NacosException {
        ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
        try {
            // 通过NamingProxy发送HTTP调用,获取服务信息
            String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUdpPort(), false);
            
            if (StringUtils.isNotEmpty(result)) {
                // 更新本地缓存serviceInfoMap
                processServiceJson(result);
            }
        } finally {
            if (oldService != null) {
                synchronized (oldService) {
                    oldService.notifyAll();
                }
            }
        }
    }

    通过NamingProxy发送HTTP调用,获取服务信息;响应结果不为空更新本地缓存serviceInfoMap

💫scheduleUpdateIfAbsent()

​
    public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
        if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
            return;
        }
        
        synchronized (futureMap) {
            if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
                return;
            }
            // 启动定时任务
            ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));
            futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
        }
    }

​

DEFAULT_DELAY默认1000在这里即1秒。启动定时任务,每隔1秒执行一次,任务逻辑如下:

💫UpdateTask#run()任务逻辑

        @Override
        public void run() {
            long delayTime = DEFAULT_DELAY;
            
            try {
                // 尝试从本地获取
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                
                if (serviceObj == null) {
                    // 本地还是没有则发送http从服务端获取,并缓存到本地
                    updateService(serviceName, clusters);
                    return;
                }

                // 过期服务(服务的最新更新时间小于等于缓存刷新时间),从注册中心重新查询
                if (serviceObj.getLastRefTime() <= lastRefTime) {
                    updateService(serviceName, clusters);
                    serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                } else {
                    // if serviceName already updated by push, we should not override it
                    // since the push data may be different from pull through force push
                    // 如果 serviceName 已经通过 push 更新,我们不应该覆盖它,
                    // 因为 push 数据可能与 pull through force push 不同
                    refreshOnly(serviceName, clusters);
                }
                // 刷新更新时间
                lastRefTime = serviceObj.getLastRefTime();
                
                if (!eventDispatcher.isSubscribed(serviceName, clusters) && !futureMap
                        .containsKey(ServiceInfo.getKey(serviceName, clusters))) {
                    // abort the update task
                    NAMING_LOGGER.info("update task is stopped, service:" + serviceName + ", clusters:" + clusters);
                    return;
                }
                if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
                    incFailCount();
                    return;
                }
                delayTime = serviceObj.getCacheMillis();
                // 重置失败数量为0
                resetFailCount();
            } catch (Throwable e) {
                incFailCount();
                NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
            } finally {
                // 下次调度刷新时间,下次执行的时间与failCount有关
                // failCount=0,则下次调度时间为6秒,最长为1分钟,即当无异常情况下缓存实例的刷新时间是6秒
                executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
            }
        }

主要逻辑:

  1. 尝试从本地缓存获取
  2. 本地还是没有则发送http从服务端获取,并缓存到本地
  3. 过期服务,从注册中心重新查询;否则如果 serviceName 已经通过 push 更新,不应该覆盖它,因为 push 数据可能与 pull through force push 不同
  4. 刷新更新时间、重置失败数量为0等
  5. 下次调度刷新时间,下次执行的时间与failCount有关failCount=0,则下次调度时间为6秒,最长为1分钟,即当无异常情况下缓存实例的刷新时间是6秒

💫queryList()发送http请求注册中心

里面会调用重载的reqApi()方法,调用前组装入参、拼接URL等。

    public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)
            throws NacosException {
        
        final Map<String, String> params = new HashMap<String, String>(8);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put("clusters", clusters);
        params.put("udpPort", String.valueOf(udpPort));
        params.put("clientIP", NetUtils.localIP());
        params.put("healthyOnly", String.valueOf(healthyOnly));
        
        return reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, HttpMethod.GET);
    }

这篇文章是基于Nacos地1.x版本地,Nacos已经发布了新的2.x版本,官方也推荐使用新的。故还会出新的关于服务发现的文章。

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

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

相关文章

Ubuntu20.04安装k8s v1.21.0

1. 禁用swap分区, 修改网络配置 sudo vim /etc/fstab 把有swap的那一行注释掉即可&#xff0c;如下&#xff1a; 然后执行如下命令 cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 EOF …

12.帖子模块——使用peewee创建多表关联的结构,使用Tornado创建查询接口、增加接口

1.模型建立与数据初始化 1.1分析建立表所需要的字段 本次主要是添加一个帖子展示时&#xff0c;所需要的内容&#xff0c;这里就得创建一个mysql的数据表去存储它的内容。 1.2 使用peewee创建多表关联结构Model 模型建立 # forum/models.py # 用于创建数据表模型from peewe…

企业自研业务系统的登录如何添加动态口令,实施MFA双因子认证?

一、背景需求 不少企业因业务需要会自己研发业务系统&#xff0c;为保护业务数据安全&#xff0c;首先要确保能访问到业务数据的人员“身份”安全可信。 企业自研业务系统的账号密码基本是 IT 管理员单独管理维护&#xff0c;员工为了方便记忆&#xff0c;通常设置与其他商采系…

函数绘图仪 MathGrafix 12.1 Crack

函数绘图仪 MathGrafix 12.1 MatheGrafix 12.1于 2022 年 8 月 1 日发布&#xff0c;包含两个新模块&#xff1a; 公式函数模块支持具有一个变量和最多十个参数的函数方程。每个参数都可以使用自动运行的滑块进行调整。 在数据模块中&#xff0c;记录数据后&#xff0c;使用回…

网页制作基础大二dw作业HTML+CSS+JavaScript云南我的家乡旅游景点

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

【C++笔试强训】第二十一天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

装饰模式与职责链模式笔记

装饰模式&#xff08;Decorator&#xff09; 概念 动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰模式比生成子类更为灵活。UML类图&#xff1a; 代码 给人打扮 //人类(ConcreteComponent) public class Person {private String name;public…

[附源码]java毕业设计ssm实验教学资源管理系统

项目运行 环境配置&#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…

C语言笔记第03章:数组

了解更多关注中南林业科技大学软件协会官网&#xff1a;https://www.csuftsap.cn/ 来自软件协会编辑&#xff0c;注册会员即可获取全部开源.md资源&#xff0c;请勿转载&#xff0c;归软件协会所有。 任何问题联系软件协会。 文章目录:star: 数组1.八个老婆引出数组 - 为什么…

1.7.2、计算机网络体系结构分层的必要性

1.7.2、计算机网络体系结构分层的必要性 计算机网络是个非常复杂的系统\color{red}计算机网络是个非常复杂的系统计算机网络是个非常复杂的系统。早在最初的ARPANET设计时就提出了分层的设计理念。 "分层\color{red}分层分层"可将庞大而复杂的问题&#xff0c;转化为…

一专多能、创新力十足,南大通用GBase8c数据库获鲲鹏创新应用大赛金奖

被评为openGauss赛道金奖的多模多态分布式数据库GBase 8c其含金量表现在哪些方面&#xff1f;基于openGauss有哪些技术创新&#xff1f; 其商业价值是什么&#xff1f;在哪些场景发挥作用&#xff1f; 面向全球开发者的年度顶级赛事——鲲鹏应用创新大赛已经举办三个年头了。三…

DHCP协议从入门到部署DHCP服务器进行实验

目录 1、DHCP基本概念 2、DHCP的优点 3、DHCP的工作原理 4、通过抓包验证原理 5、在windows server上部署DHCP服务器 6、实验搭建 实验环境 配置R1的中继 常见报文学习 1、DHCP基本概念 路由器可以阻挡DHCP discover的广播报文 2、DHCP的优点 3、DHCP的工作原理 4、通过抓…

Java笔试复盘

目录 1. finalize的含义 2.Statement和PreparedStatement用法 3. 接口能用protected修饰吗 4.java实现分解质因数 今天去参加了一场国企单位的笔试&#xff0c;很基础&#xff0c;但是有些细节的地方还是觉得答得不好&#xff0c;所以复盘一下。 1. finalize的含义 final…

WPF几何绘图(2)

Path的各种线段&#xff1a; LineSegment 直线段。 ArcSegment 圆弧线段。 BezierSegment 三次贝塞尔曲线段&#xff08;默认贝斯尔曲线是指三次曲线&#xff09;。 QuadraticBezierSegment 二次贝塞尔曲线段。 PolyLineSegment 多直线段。 PolyBezierSegment 多三次方贝塞…

一篇博文,带你入门数据库SQL语言

目录 &#x1f4d6;前言 &#x1f388;SQL的分类 &#x1f3a8;数据库的分类 ✨SQL的基础操作 ⚽SQL常用数据类型 &#x1f3c9;数据库的操作 ⚾创建数据库 &#x1f94e;显示数据库 &#x1f3c0;使用数据库 &#x1f3d0;删除数据库 &#x1f37f;表的操作 &…

Emgu CV4图像处理之打开Tensorflow训练模型17(C#)

本文测试环境&#xff1a; win10 64位 vistual studio 2019 Emgu CV 4.6.0 环境配置准备&#xff1a; 1 新增控制台项目&#xff0c;.net framework为4.7.2 2 把win-x64目录的native目录下的文件全部拷贝到项目的运行目录Debug目录下 3 项目选择x64 4 添加项目引用…

(CVE-2019-0227)Axis<=1.4 远程命令执行漏洞

结尾附上本文的环境和PoC 一、漏洞介绍 漏洞本质是管理员对AdminService的配置错误。当enableRemoteAdmin属性设置为true时&#xff0c;攻击者可以构造WebService调用freemarker组件中的template.utility.Execute类&#xff0c;远程利用AdminService接口进行WebService发布&am…

Linux_gdb_进程概念

目录 进度条 学习过程 自主实现&#xff1a; Linux的git操作 .gitignore .git git add . git commit git push git log git status git pull 进度条 学习过程 首先创建源文件&#xff0c;这个源文件就是我们要写的进度条的定义 接下来&#xff0c;我们创建文件Ma…

化工行业供应商协同管理系统:助力企业打造良好营商环境,提升运营效率

化工行业是我国工业发展的重要基础&#xff0c;也是国民经济发展和生活不可或缺的一部分。化工产品的更新换代&#xff0c;产业的不断升级&#xff0c;对原材料的采购也提出了更高要求。 由于精细化工行业原材料的质量和成本&#xff0c;与供应商的选择与资质具有重要的关联性…

[附源码]计算机毕业设计JAVAjsp医院网上预约系统

[附源码]计算机毕业设计JAVAjsp医院网上预约系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM myb…