当前位置: 首页 > news >正文

中间件多版本冲突的4种解决方案和我们的选择

背景

在小小的公司里面,挖呀挖呀挖。最近又挖到坑里去了。一个稳定运行多年的应用,需要在里面支持多个版本的中间件客户端;而多个版本的客户端在一个应用里运行时会有同名类冲突的矛盾。在经过询问chatGPT,百度,google,github,和各位大佬的文章后,进行了总结。大概有以下几种解决方案

业界方案1----更改类路径

低版本客户端,更改类路径;然后重新打包编译客户端;这样不同版本客户端,使用的类名就不同了

此解决方案
优点

1、适合简单的小项目:无需编写新代码

2、可能是最快的一种方式;只需要把代码下载下来;更改clients下的类路径,重新编译即可;属于不怎么费脑力,但有点费体力的方式。

缺点

1、遇到版本升级,需要把步骤2的过程重新人肉再来一遍。这个比较的那啥。。。。。。

2、不同版本客户端,可能也会依赖不同版本的三方jar包。这个也是蛮棘手的

此种方式适合小项目或者外包等一锤子买卖

解决方案2 -----自定义ClassLoader

使用ClassLoader进行类的隔离;不同版本客户端和依赖三方包,用不同classLoader进行加载和隔离;完全杜绝版本问题

此解决方案

1、属于自研;对ClassLoader的类加载机制需要有一定的了解

解决方案3------业界开源方案 sofa-ark

sofa-ark是动态热部署和类隔离框架,由蚂蚁集团开源贡献。主要提供应用模块的动态热部署和类隔离能力

提供功能:

1、包冲突的解决(能解决现有项目遇到的多版本问题)

2、合并部署:多个项目分工程开发,但可以合并部署;还支持静态合并部署和动态合并部署

资料传送门:

https://github.com/sofastack/sofa-ark

https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/

此解决方案:
优点

1、功能强大不仅能执行类的隔离;还能静动态的热部署;还能进行插件的热插拔;能解决现有工程遇到的问题;文档也挺齐全的

2、性能,稳定性,易用性,应该是所有保障的,毕竟蚂蚁内部也在使用;

缺点

1、虽号称是轻量级,但那是和OSGI这种重型框架相比,在sofa-ark里,也还有蛮多概念比如:Ark Container,ark包,插件包,biz包;如何打ark包,如何打插件包等等;有一定的学习成本,好在文档齐全能降低一定的入门门槛

2、需要对现有工程进行改造,以符合sofa-ark的规范;打包和部署上还需要遵循其规范。对于小公司主打的就是一个“自由”这种状态来说;有一点点束缚和被迫学习了,因为我们都比较的“懒”

此种方式适合大型项目;大型项目开发人员和开发应用众多;而sofa-ark制定了相应的biz包和插件包的开发规范;在代码复杂性,模块化开发,扩展性,项目维护,应用运行期等都进行了综合考虑。

解决方案4------OSGI

属于比较重型解决方案

OSGI 作为业内最出名的类隔离框架,自然是可以被用于解决上述包冲突问题,但是 OSGI 框架太过臃肿,功能繁杂。为了解决包冲突问题,引入 OSGI 框架,有牛刀杀鸡之嫌,反而使工程变得更加复杂,不利于开发。

我们的选择

解决方案这么多,我们该选择哪个方案了?

我们选择方案2。为什么了?

先说说为什么不选择其它方案

方案1: 此种方式不一定是最快的方式;因为后续考虑到每增加一个版本,或者社区有更新,都要去做一次,更名,打包等。还是挺麻烦,不太自动化,属于不费脑子费体力。

方案3: 功能强大:在模块化开发,类隔离,热部署等功能性上无比优异;性能和稳定性也有大公司在背书;但我们目前是个小公司,遇到的业务场景和技术场景,没有那么复杂,强大的功能给我们,我们不一定能用的上,用的不好可能还会被反噬;由于公司技术人员对该框架缺少实际使用经验;并且对该框架的实现原理也没人懂属于需要现学;使用后万一后续出现什么问题,问题定位和维护也挺麻烦;对小公司来说还是“太重”了;并且还需要对现有工程进行改造

方案4: 就不用在详说了,比方案3还重的解决方案

为什么方案2适合我们?

实现难度上: 对我们小公司来说虽然要自己写代码实现;但经过评估大概几百行代码就能搞定,技术上不是那么的高不可攀;

技术熟悉度: 团队内大家对ClassLoader的机制还蛮熟悉;

使用经验上: 有多个同事曾经用classLoader进行过这种隔离机制的实现,但业务场景不同;

复用性上: 写一次代码;后续在遇到多个版本冲突;经过简单的配置即可,不需要修改代码;更不需要修改三方依赖源码,比方案1好

在性能和稳定性上: 性能上不影响运行期,只会影响到代码加载期,所以性能这块还好;而在稳定性上,可通过测试环境长期稳定运行和一定的业务压测进行验证;而恰好我们有这样的测试环境和线上引流进行压测验证工具

设计图

类的加载机制.png

ClassLoader的原理: 从上图可看出,ClassLoader会影响JVM加载类的路径类的加载顺序
类的加载路径
bootStrap ClassLoader: 加载%JRE_HOME%\lib 下的jar,比如rt.jar等
Extendtion ClassLoader: 加载%JRE_HOME%\lib\ext 目录下的jar
AppClass ClassLoader: 加载应用classpath下的所有类,即工程里依赖三方jar和工程的class
加载顺序 :双亲委托机制;加载类时,先让父ClasserLoader进行加载,父Classloader找不到,才让子ClassLoader进行查找和加载。

主要想法: 自定义ClassLoader继承URLClassLoader(即图里的AppClassClassLoader);基础类的加载 还是用双亲委托机制,由父类去加载,自定义类实现findClass方法,在该方法里加载指定目录class和jar。

具体工具类-----应该可以拿来即用

该实现类主要参考了Cyber365大佬的文章;然后做了一些改动(简化类,抽取工具类,去除多重if嵌套)
主要由两个类进行实现

  • MiddlewareClassLoader 类 主要做了两件事
    1:读取Class文件内容;根据传入类名,从指定url中查找并读取到对应class文件内容
    2:生产Class对象: 传入class文件内容,调用底层defineClass方法生产

  • UrlUtils 类: 主要做了一件事
    1:根据指定的url,计算出该url和对应子目录下jar的url。

MiddlewareClassLoader 类

public class MiddlewareClassLoader extends URLClassLoader {private URL[] allUrl;public MiddlewareClassLoader(String[] paths){this(UrlUtils.getURLs(paths));}public MiddlewareClassLoader(URL[] urls) {this(urls, MiddlewareClassLoader.class.getClassLoader());}public MiddlewareClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);this.allUrl = urls;}protected Class<?> findClass(final String name)throws ClassNotFoundException{return loadExtendClass(name);}//    /**
//     * 不建议重新定义loadClass 方法,打破双亲委派机制,采用逆向双亲委派
//     *
//     * @param className 加载的类名
//     * @return java.lang.Class<?>
//     * @author Cyber
//     * <p> Created by 2022/11/22
//     */
//    @Override
//    public Class<?> loadClass(String className) throws ClassNotFoundException {
//        Class extClazz = loadExtendClass(className);
//        if(null != extClazz){
//            return extClazz;
//        }
//     return super.loadClass(className);
//    }public Class<?> loadExtendClass(String className) throws ClassNotFoundException {if(null == allUrl){return null;}String classPath = className.replace(".", "/");classPath = classPath.concat(".class");for (URL url : allUrl) {byte[] data = null;ByteArrayOutputStream baos = new ByteArrayOutputStream();InputStream is = null;try {File file = new File(url.toURI());if (!file.exists()) {continue;}JarFile jarFile = new JarFile(file);if (jarFile == null) {continue;}JarEntry jarEntry = jarFile.getJarEntry(classPath);if (jarEntry == null) {continue;}is = jarFile.getInputStream(jarEntry);byte[] buffer = new byte[1024 * 10];int length = -1;while ((length = is.read(buffer)) > 0) {baos.write(buffer, 0, length);}data = baos.toByteArray();System.out.println("********找到classPath=" + classPath + "的jar=" + url.toURI().getPath() + "*******");Class clazz =  this.defineClass(className, data, 0, data.length);return clazz;} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null) {is.close();}baos.close();} catch (IOException e) {e.printStackTrace();}}}return null;}
}

工具类UrlUtils

public class UrlUtils {/*** description 通过文件目录获取目录下所有的jar全路径信息** @param paths 文件路径* @return java.net.URL[]* @author Cyber* <p> Created by 2022/11/22*/public static URL[] getURLs(String[] paths) {if (null == paths || 0 == paths.length) {throw new RuntimeException("jar包路径不能为空.");}List<String> dirs = new ArrayList<String>();for (String path : paths) {dirs.add(path);collectDirs(path, dirs);}List<URL> urls = new ArrayList<URL>();for (String path : dirs) {urls.addAll(doGetURLs(path));}URL[] threadLocalurls = urls.toArray(new URL[0]);return threadLocalurls;}/*** description 递归获取文件目录下的根目录** @param path      文件路径* @param collector 根目录* @return void* @author Cyber* <p> Created by 2022/11/22*/private static void collectDirs(String path, List<String> collector) {if (null == path || "".equalsIgnoreCase(path)) {return;}File current = new File(path);if (!current.exists() || !current.isDirectory()) {return;}for (File child : current.listFiles()) {if (!child.isDirectory()) {continue;}collector.add(child.getAbsolutePath());collectDirs(child.getAbsolutePath(), collector);}}private static List<URL> doGetURLs(final String path) {if (null == path || "".equalsIgnoreCase(path)) {throw new RuntimeException("jar包路径不能为空.");}File jarPath = new File(path);if (!jarPath.exists() || !jarPath.isDirectory()) {throw new RuntimeException("jar包路径必须存在且为目录.");}FileFilter jarFilter = new FileFilter() {/*** description  判断是否是jar文件* @param pathname jar 全路径文件* @return boolean* @author Cyber* <p> Created by 2022/11/22*/@Overridepublic boolean accept(File pathname) {return pathname.getName().endsWith(".jar");}};File[] allJars = new File(path).listFiles(jarFilter);List<URL> jarURLs = new ArrayList<URL>(allJars.length);for (int i = 0; i < allJars.length; i++) {try {jarURLs.add(allJars[i].toURI().toURL());} catch (Exception e) {throw new RuntimeException("系统加载jar包出错", e);}}return jarURLs;}
}

kafka发送基础类

@Slf4j
public abstract class AbstractKafkaProducer {private String kafkaClassName = "org.apache.kafka.clients.producer.KafkaProducer";private String stringSerializerClassName = "org.apache.kafka.common.serialization.StringSerializer";private String serializerClassName = "org.apache.kafka.common.serialization.Serializer";private String producerRecordClassName = "org.apache.kafka.clients.producer.ProducerRecord";private MiddlewareClassLoader middlewareClassLoader;private Object producerObject = null;private Method sendMethod = null;private Constructor producerRecordConstructor = null;//类的加载和初始化public void init(String jarPath){middlewareClassLoader = new MiddlewareClassLoader(new String[]{jarPath});try {ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(middlewareClassLoader);Class kafkaProduceClazz = middlewareClassLoader.loadClass(kafkaClassName);Class kafkaStringSerializerClazz = middlewareClassLoader.loadClass(stringSerializerClassName);Class kafkaSerializerClazz = middlewareClassLoader.loadClass(serializerClassName);//加载KafkaProducer类Class ProducerRecordClazz = middlewareClassLoader.loadClass(producerRecordClassName);Constructor producerConstructor = kafkaProduceClazz.getConstructor(Map.class, kafkaSerializerClazz, kafkaSerializerClazz);Map<String,Object> produceConfigMap = new HashMap<String,Object>();produceConfigMap.put("retries",3);produceConfigMap.put("retry.backoff.ms",10000);produceConfigMap.put("acks","all");//回调方法,让自来可更改生产端配置addExtendConfig(produceConfigMap);producerObject = producerConstructor.newInstance(produceConfigMap,kafkaStringSerializerClazz.newInstance(),kafkaStringSerializerClazz.newInstance());sendMethod = kafkaProduceClazz.getMethod("send",new Class[]{ProducerRecordClazz});producerRecordConstructor =  ProducerRecordClazz.getConstructor(String.class,Object.class);Thread.currentThread().setContextClassLoader(threadClassLoader);System.out.println("========end======");} catch (Exception e) {e.printStackTrace();}}protected abstract void addExtendConfig(Map<String, Object> produceConfigMap);//发送消息public void send(String topic,String msg){try {sendMethod.invoke(producerObject,producerRecordConstructor.newInstance(topic,msg));} catch (Exception e) {throw new RuntimeException(e);}}
}

Kafka09Producer

@Data
public class Kafka09Producer extends AbstractKafkaProducer {private String bootstrapServers;@Overrideprotected void addExtendConfig(Map<String, Object> produceConfigMap) {produceConfigMap.put("bootstrap.servers",bootstrapServers);}}

Kafka32Producer

@Data
public class Kafka32Producer extends AbstractKafkaProducer {private String bootstrapServers;@Overrideprotected void addExtendConfig(Map<String, Object> produceConfigMap) {produceConfigMap.put("bootstrap.servers",bootstrapServers);}}

核心测试代码

@Slf4j
public class MiddlewareClassLoaderTest {public static void main(String[] args) throws InterruptedException {//kafka 0.9的测试Kafka09Producer kafka09Producer = new Kafka09Producer();kafka09Producer.setBootstrapServers(KafkaConfig.getInstance().getProperty("bootstrap.servers"));kafka09Producer.init("/data/app/product-kafka/ext-lib/kafka090");kafka09Producer.send("topic090","msg090:" + System.currentTimeMillis());//kafka 3.2的测试Kafka32Producer kafka32Producer = new Kafka32Producer();kafka32Producer.setBootstrapServers(KafkaConfig.getInstance().getProperty("bootstrap.servers"));kafka32Producer.init("/data/app/product-kafka/ext-lib/kafka32");kafka32Producer.send("topic320","msg320:" + System.currentTimeMillis());Thread.sleep(60 * 1000);}
}

测试结果

kafka09类的加载
image.png

kafka32类的加载
image.png

总结

ClassLoader除了能加载指定版本jar包外;还可以做热部署和热更新;如果要再次加载同一个类达到热更新;可 new一个classLoader然后loadClass,再用该Class去实例化对象即可。
还有一个困扰新手较久的注意点:Class的加载和Object实例化需要分开去看待,ClassLoader只影响类的加载;类的实例化是另外一个问题。

原创不易,请点赞,留言,关注,收藏 4暴击 ^^

参考资料:

https://blog.csdn.net/briblue/article/details/54973413 ClassLoader类加载机制,类加载顺序

https://juejin.cn/post/7168678691839410213 Cyber365大佬的文章

相关文章:

中间件多版本冲突的4种解决方案和我们的选择

背景 在小小的公司里面&#xff0c;挖呀挖呀挖。最近又挖到坑里去了。一个稳定运行多年的应用&#xff0c;需要在里面支持多个版本的中间件客户端&#xff1b;而多个版本的客户端在一个应用里运行时会有同名类冲突的矛盾。在经过询问chatGPT&#xff0c;百度&#xff0c;googl…...

对 async/await 的理解

async/await 的理解 async/await 其实是 Generator 的语法糖&#xff0c;它能实现的效果都能用then 链来实现&#xff0c;它是为优化 then 链而开发出来的。从字面上来看&#xff0c;async 是“异步”的简写&#xff0c;await 则为等待&#xff0c;所以很好理解async用于申明一…...

Vue 整合 Element UI 、路由嵌套、参数传递、重定向、404和路由钩子(五)

一、整合 Element UI 1.1 工程初始化 使用管理员的模式进入 cmd 的命令行模式&#xff0c;创建一个名为 hello-vue 的工程&#xff0c;命令为&#xff1a; # 1、目录切换 cd F:\idea_home\vue# 2、项目的初始化&#xff0c;记得一路的 no vue init webpack hello-vue 1.2 安装…...

修改 Ubuntu 系统的时区

修改 Ubuntu 系统的时区 如果 Ubuntu 系统的时区设置不正确&#xff0c;您可以按照以下步骤进行调整&#xff1a; 1. 查看当前的时区设置&#xff0c;可以使用以下命令&#xff1a; timedatectl 这将显示当前系统的日期、时间和时区信息。 2. 如果时区设置不正…...

如何离线安装ModHeader - Modify HTTP headers Chrome插件?

如何离线安装ModHeader - Modify HTTP headers Chrome插件&#xff1f; 1.1 前言1.2 打开Chrome浏览器的开发者模式1.3 下载并解压打包好的插件1.4 解压下载好的压缩包1.5 加载插件1.6 如何使用插件? 1.1 前言 ModHeader 是一个非常好用的Chrome浏览器插件&#xff0c;可以用…...

在Linux中安装MySQL

在Linux中安装MySQL 检测当前系统中是否安装MySQL数据库 命令作用rpm -qa查询当前系统中安装的所有软件rpm -qa|grep mysql查询当前系统中安装的名称带mysql的软件rpm -qa | grep mariadb查询当前系统中安装的名称带mariadb的软件 RPM ( Red-Hat Package Manager )RPM软件包管理…...

python --windows获取启动文件夹路径/获取当前用户名/添加自启动文件

如何使用Python获取计算机用户名 一、Python自带的getpass模块可以用于获取用户输入的密码&#xff0c;但是它同样可以用来获取计算机用户名。 import getpassuser getpass.getuser() print("计算机用户名为&#xff1a;", user)二、使用os模块获取用户名 Python的…...

微信云托管(本地调试)⑥:nginx、vue刷新404问题

一、nginx默认路径 1.1、默认配置文件路径&#xff1a;/etc/nginx/nginx.conf 1.2、默认资源路径&#xff1a;/usr/share/nginx/html/index.html 二、修改nginx.conf配置 &#xff08;注意配置中的&#xff1a;include /etc/nginx/conf.d/*.conf; 里面包了一个server配置文件…...

数据结构 二叉树(一篇基本掌握)

绪论 雄关漫道真如铁&#xff0c;而今迈步从头越。 本章将开始学习二叉树&#xff08;全文共一万两千字&#xff09;&#xff0c;二叉树相较于前面的数据结构来说难度会有许多的攀升&#xff0c;但只要跟着本篇博客深入的学习也可以基本的掌握基础二叉树。 话不多说安全带系好&…...

​可视化绘图技巧100篇高级篇(四)-南丁格尔玫瑰图(二)

目录 前言 适用场景 不适用场景 ​堆积式南丁格尔玫瑰图( Nightingale Rose Diagram)...

Stable Diffusion - Candy Land (糖果世界) LoRA 提示词配置与效果展示

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132145248 糖果世界 (Candy Land) 是一个充满甜蜜和奇幻的地方&#xff0c;由各种各样的糖果和巧克力构成。在糖果世界&#xff0c;可以看到&…...

ES6学习-module语法

Module语法 CommonJS模块 let { readfile } require(fs) # 等同于 let _fs require(fs) let readfile _fs.readfile //这种加载称为“运行时加载”ES6模块 import { stat, exists, readFile } from fs;这种加载称为“编译时加载”或者静态加载 静态加载带来的各种好处 …...

Flutter 实现按位置大小比例布局的控件

文章目录 前言一、如何实现&#xff1f;1、数值转成分数2、RowFlexible布局横向3、ColumnFlexible布局纵向 二、完整代码三、使用示例1、基本用法2、四分屏3、六分屏4、八分屏5、九分屏6、414分屏 总结 前言 做视频监控项目时需要需要展示多分屏&#xff0c;比如2x2、3x3、414…...

ES6 - 对象新增的一些常用方法

文章目录 1&#xff0c;Object.is()2&#xff0c;Object.asign()3&#xff0c;Object.getOwnPropertyDescriptors()4&#xff0c;Object.setPrototypeOf()和getPrototypeOf()5&#xff0c;Object.keys()、values() 和 entries()6&#xff0c;Object.fromEntries()7&#xff0c;…...

半导体存储电路

存储电路 存储单元&#xff1a;只能存储一位数据 寄存器&#xff1a;存储一组数据 存储单元 静态存储单元&#xff1a;包含锁存器和触发器&#xff0c;只要不断电&#xff0c;静态存储单元的状态会一直保持下去。 动态存储单元&#xff1a;利用电容的电荷存储效应来存储数据。…...

web前端之CSS操作

文章目录 一、CSS操作1.1 html元素的style属性1.2 元素节点的style属性1.3 cssText属性 二、事件2.1 事件处理程序2.1.1 html事件2.1.2 DOM0事件&#xff08;适合单个事件&#xff09;2.1.3 DOM2事件&#xff08;适合多个事件&#xff09; 2.2 事件之鼠标事件2.3 事件之Event事…...

Python SQLAlchemy ( ORM )

From Python中强大的通用ORM框架&#xff1a;SQLAlchemy&#xff1a;https://zhuanlan.zhihu.com/p/444930067Python ORM之SQLAlchemy全面指南&#xff1a;https://zhuanlan.zhihu.com/p/387078089 SQLAlchemy 文档&#xff1a;https://www.sqlalchemy.org/ SQLAlchemy入门和…...

鉴源实验室丨汽车网络安全运营

作者 | 苏少博 上海控安可信软件创新研究院汽车网络安全组 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 01 概 述 1.1 背景 随着车辆技术的不断进步和智能化水平的提升&#xff0c;车辆行业正经历着快速的变革和技术进步。智能化…...

分布式链路追踪之SkyWalking详解和实战

SkyWalking 文章目录 SkyWalking1.SkyWalking概述2.SkyWalking架构设计3.SkyWalking部署4.应用程序接入SkyWalking5.SkyWalking配置应用告警5.1.告警规则5.2.Webhook&#xff08;网络钩子&#xff09;5.3.邮件告警实践 6.项目自动化部署接入SkyWalking6.1 整体思路6.2 启动参数…...

【工程实践】使用EDA(Easy Data Augmentation)做数据增强

工程项目中&#xff0c;由于数据量不够&#xff0c;经常需要用到数据增强技术&#xff0c;尝试使用EDA进行数据增强。 1.EDA简介 EDA是一种简单但是非常有效的文本数据增强方法&#xff0c;是由美国Protago实验室发表于 EMNLP-IJCNLP 2019 会议。EDA来自论文《EDA: Easy Data…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

基于 HTTP 的单向流式通信协议SSE详解

SSE&#xff08;Server-Sent Events&#xff09;详解 &#x1f9e0; 什么是 SSE&#xff1f; SSE&#xff08;Server-Sent Events&#xff09; 是 HTML5 标准中定义的一种通信机制&#xff0c;它允许服务器主动将事件推送给客户端&#xff08;浏览器&#xff09;。与传统的 H…...

使用python进行图像处理—图像滤波(5)

图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值&#xff0c;以达到平滑&#xff08;去噪&#xff09;、锐化、边缘检测等效果。滤波通常通过卷积操作实现。 5.1卷积(Convolution)原理 卷积是滤波的核心。它是一种数学运算&#xff0c;…...