SPL 实践系列:跨库移植 SQL
背景
应用程序可能要基于不同数据库工作,各种数据库的 SQL 语法大体一致,但仍有些差别,结果就要改造这些 SQL,而这事通常只能手工调整,工作量大还容易出错。
完全自动改造 SQL 几乎是无法做到的,毕竟各种数据库很可能功能就不一样。
不过,梳理一下会发现,大部分问题都是由于 SQL 函数写法不同造成的。
特别是日期和字符串相关的函数,业界没有标准,各个数据库各行其是。比如将字符串 "2020-02-05" 转换成日期,不同数据库有不同的写法。
ORACLE:
select TO_DATE('2020-02-05', 'YYYY-MM-DD') from USER
SQL Server:
select CONVERT(varchar(100), '2020-02-05', 23) from USER
MySQL:
select DATE_FORMAT('2020-02-05','%Y-%m-%d') from USER
如果希望应用在不同的数据库之间切换,就需要改写 SQL 语句。
SPL 方案
SPL 针对这个场景提供了 SQL 转换功能,可以将某种标准 SQL 转换成不同数据库对应的语句,从而完成数据库切换时 SQL 无缝移植。
sql.sqltranslate(dbtype) 函数前面的 sql 是需要翻译的 SQL 语句,参数 dbtype 是数据库类型。函数要在 SPL 的简单 SQL 中定义过,未定义的不会被翻译。已定义的函数列表和数据库类型可查阅 sqltranslate 函数帮助:Function-sqltranslate ()
IDE 内使用
我们先在 SPL 的 IDE 内尝试一下,将
SELECT EID, NAME, BIRTHDAY, ADDDAYS(BIRTHDAY,10) DAY10 FROM EMP
转换成不同数据库对应的语法。
可以看到 ADDDAYS 这个函数被翻译成各个数据库不同的语法,实现了 SQL 在不同数据库之间移植。
我们再看一些例子。
月份加 10
SELECT EID, NAME, BIRTHDAY, ADDMONTHS(BIRTHDAY,10) DAY10 FROM EMP
通过 sqltranslate 翻译成不同数据库的语法:
ORACLE:
SELECT EID, NAME, BIRTHDAY, BIRTHDAY+NUMTOYMINTERVAL(10,'MONTH') DAY10 FROM EMP
SQLSVR:
SELECT EID, NAME, BIRTHDAY, DATEADD(MM,10,BIRTHDAY) DAY10 FROM EMP
DB2:
SELECT EID, NAME, BIRTHDAY, BIRTHDAY+10 MONTHS DAY10 FROM EMP
MYSQL:
SELECT EID, NAME, BIRTHDAY, BIRTHDAY+INTERVAL 10 MONTH DAY10 FROM EMP
POSTGRES:
SELECT EID, NAME, BIRTHDAY, BIRTHDAY+interval '10 months' DAY10 FROM EMP
TERADATA:
SELECT EID, NAME, BIRTHDAY, ADD_MONTHS(BIRTHDAY, 10) DAY10 FROM EMP
ADDMONTHS 函数在不同数据库的实现方式差异很大,SQLServer 有 DATEADD 函数,而 MySQL 和 PG 则直接加,Oracle 则采用两者相结合的方式实现。
求季度
SELECT EID,AREA,QUARTER(ORDERDATE) QUA, AMOUNT FROM ORDERS
转换后:
ORACLE:
SELECT EID,AREA,FLOOR((EXTRACT(MONTH FROM ORDERDATE)+2)/3) QUA, AMOUNT FROM ORDERS
SQLSVR:
SELECT EID,AREA,DATEPART(QQ,ORDERDATE) QUA, AMOUNT FROM ORDERS
POSTGRES:
SELECT EID,AREA,EXTRACT(QUARTER FROM ORDERDATE) QUA, AMOUNT FROM ORDERS
TERADATA:
SELECT EID,AREA,TD_QUARTER_OF_YEAR(ORDERDATE) QUA, AMOUNT FROM ORDERS
求季度的函数,不同数据库虽然都有函数实现,但函数名称和参数的定义又有很大差异。
类型转换
SELECT EID, NAME, DATETOCHAR(BIRTHDAY) FROM EMP
转换后:
ORACLE:
SELECT EID, NAME, TO_CHAR(BIRTHDAY,'YYYY-MM-DD HH:MI:SS') FROM EMP
SQLSVR:
SELECT EID, NAME, CONVERT(CHAR,BIRTHDAY,120) FROM EMP
DB2:
SELECT EID, NAME, TO_CHAR(BIRTHDAY,'YYYY-MM-DD HH:MI:SS') FROM EMP
MYSQL:
SELECT EID, NAME, DATE_FORMAT(BIRTHDAY, '%Y-%m-%d %H:%i:%S) FROM EMP
POSTGRES:
SELECT EID, NAME, TO_CHAR(BIRTHDAY,'YYYY-MM-DD HH:MI:SS') FROM EMP
TERADATA:
SELECT EID, NAME, TO_CHAR(BIRTHDAY,'YYYY-MM-DD HH:MI:SS') FROM EMP
类型转换函数各个数据库的函数名称和格式化形式有较大差异。
这些五花八门的差异都可以用 SPL 的 sqltranslate 来转换。
函数定义与扩展
SPL 支持的数据库类型和函数定义在发布包 esproc-bin.jar 中的字典文件 /com/scudata/dm/sql/function.xml 中。
<?xml version="1.0" encoding="utf-8"?>
<STANDARD><FUNCTIONS type="FixParam"><FUNCTION name="ADDDAYS" paramcount="2" value=""><INFO dbtype="ORACLE" value="?1+NUMTODSINTERVAL(?2,'DAY')"></INFO><INFO dbtype="SQLSVR" value="DATEADD(DD,?2,?1)"></INFO><INFO dbtype="DB2" value="?1+?2 DAYS"></INFO><INFO dbtype="MYSQL" value="?1+INTERVAL ?2 DAY"></INFO><INFO dbtype="HSQL" value="DATEADD('dd', ?2, ?1)"></INFO><INFO dbtype="TERADATA" value="?1+CAST(?2 AS INTERVAL DAY)"></INFO><INFO dbtype="POSTGRES" value="?1+interval '?2 days'"></INFO><INFO dbtype="ESPROC" value="elapse(?1,?2)"></INFO></FUNCTION></FUNCTIONS>
</STANDARD>
FUNCTIONS 节点代表一个函数组,type 是函数组类型,FixParam 表示参数个数固定的函数组。FUNCTION 节点代表一个简单 SQL 函数,name 是函数名,paramcount 是参数个数,value 是翻译本函数时的默认值,空串时表示无需翻译。INFO 节点代表一种数据库,dbtype 是数据库名称,空串时表示是 SPL 中的简单 SQL,value 是翻译到本数据库时的对应值。value 中的? 或?1 代表函数的第 1 个参数值,?2 代表函数的第 2 个参数值,依此类推。当 INFO 中的 value 值为空串时,则使用父节点 FUNCTION 的 value 值。
在翻译时,如果 FUNCTION 节点下没有指定数据库的 INFO 节点定义,则此函数保持原样,不会被翻译。
SPL 在 funtion.xml 中定义了很多函数,但并不是所有。实际使用中可能碰到新的,可以自行增加。
比如我们要增加函数来计算两个日期的相差天数,我们就可以增加 FUNCTION 节点,定义 DATEDIFF 函数名,然后在 INFO 节点分别配置不同数据库的写法。
<FUNCTION name="DATEDIFF" paramcount="2" value=""><INFO dbtype="ORACLE" value="?1-?2"></INFO><INFO dbtype="SQLSVR" value="DATEDIFF(day,?1,?2)"></INFO><INFO dbtype="MYSQL" value="DATEDIFF(?1,?2)"></INFO><INFO dbtype="POSTGRES" value="?1-?2"></INFO><INFO dbtype="ESPROC" value="interval(?2,?1)"></INFO>
</FUNCTION>
类似地,如果还要增加对其他数据库的支持,直接增加 INFO 节点信息,把新数据库配置上就可以。比如这里要增加对 SQLite 的支持,来完成日期相差天数的翻译。
<FUNCTION name="DATEDIFF" paramcount="2" value=""><INFO dbtype="ORACLE" value="?1-?2"></INFO><INFO dbtype="SQLSVR" value="DATEDIFF(day,?1,?2)"></INFO><INFO dbtype="MYSQL" value="DATEDIFF(?1,?2)"></INFO><INFO dbtype="POSTGRES" value="?1-?2"></INFO><INFO dbtype="ESPROC" value="interval(?2,?1)"></INFO><INFO dbtype="SQLite" value="JULIANDAY(?1) - JULIANDAY(?2)"></INFO>
</FUNCTION>
不固定个数参数情况
我们前面看到的都是函数参数个数固定的例子,但还有一些事先无法固定参数个数的情况,比如字符串连接,case when,以及取多个参数中的第一个非空值等。
SPL 对这种动态参数个数的情况也提供支持,将 FUNCTIONS 节点的 type 值配置成 AnyParam,也就是任意个数参数。
<FUNCTIONS type="AnyParam"><FUNCTION classname="com.scudata.dm.sql.simple.Case" name="case"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Case"></INFO></FUNCTION><FUNCTION classname="com.scudata.dm.sql.simple.Coalesce" name="coalesce"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Coalesce"></INFO></FUNCTION><FUNCTION classname="com.scudata.dm.sql.simple.Concat" name="concat"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Concat"></INFO></FUNCTION></FUNCTIONS>
我们要为每个数据库的对应函数编写相应的 Java 类。比如我们要为字符串函数 CONCAT 增加对 Oracle 的支持,我们就可以编写这样的代码:
public class Concat implements IFunction
{public String getFormula(String[] params){StringBuffer sb = new StringBuffer();for(int i = 0, len = params.length; i < len; i++) {if(params[i].isEmpty()) {throw new RQException("Concat function parameter cannot be empty");}if(i > 0) {sb.append(" || ");}sb.append(params[i]);}return sb.toString();}
}
编译后添加到 esproc-bin.jar 的 /com/scudata/dm/sql/oracle 路径下。
然后在 funtion.xml 中配置 oracle 对应的翻译类。
<FUNCTIONS type="AnyParam"><FUNCTION classname="com.scudata.dm.sql.simple.Case" name="case"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Case"></INFO></FUNCTION><FUNCTION classname="com.scudata.dm.sql.simple.Coalesce" name="coalesce"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Coalesce"></INFO></FUNCTION><FUNCTION classname="com.scudata.dm.sql.simple.Concat" name="concat"><INFO dbtype="ESPROC" classname="com.scudata.dm.sql.simple.Concat"></INFO><INFO dbtype="ORACLE" classname="com.scudata.dm.sql.oracle.Concat"></INFO>**</FUNCTION></FUNCTIONS>
Jar 包修改后重启 IDE,我们尝试一下。
可以看到连接 3 个参数,转换成 Oracle 语法时,变成了以双竖线的拼接方式,而目标数据库是 ESPROC 时则采用小写的 concat 来实现。
至此,我们已经学会了如何使用翻译函数,如何配置,以及如何新增函数和数据库,包括参数个数不定的情况。
与应用结合
接下来,我们来学习如何与应用相结合。
SPL 与应用集成非常简单,只需要将 [安装目录]\esProc\lib 下的:esproc-bin-xxxx.jar 和 icu4j-60.3.jar 两个 jar 包引入到应用中,然后复制 [安装目录]\esProc\config 下的 raqsoftConfig.xml 到应用的类路径下即可。
raqsoftConfig.xml 是 SPL 的核心配置文件,名称不可更改,后续的数据源和网关配置都需要用到。
单库情况
我们先看应用只有单一数据库的情况。
使用方式 1- 仅用 SQL 翻译
在应用中使用 SPL 的 SQL 翻译功能,最简单的方式就是用 sqltranslate 把 SQL 翻译成目标数据库的语法后执行。
SPL 中翻译 SQL 的 API 是 com.scudata.dm.sql.SQLUtil.translate 函数,直接使用它就可以实现 SQL 语法的翻译。
String sql = "select name, birthday, adddays(birthday,10) day10 from emp";
sql = com.scudata.dm.sql.SQLUtil.translate(sql, "MYSQL");
不过,需要说明的是,SPL 官方并不推荐直接使用 API,而是建议使用 SPL 的 JDBC 接口,但仅仅为了个字符串转换动作而写好几行代码连接 JDBC 确实有点麻烦,所以我们直接使用了 API。
另外,我们希望把 SQL 移植做到尽量透明,除了首次改写,以后再换数据库无需再更改代码重编译,只要维护配置文件即可。因此,我们把数据库类型维护在配置文件中。
比如,我们增加数据库类型配置文件 dbconfig.properties,里面配置数据库类型,如 MYSQL。
dbconfig.properties 内容:
database.type=MYSQL
然后封装一个翻译方法,调用 SPL 的 API 完成 SQL 翻译。
public static String translateSQL(String sql) {String dbType = null;try (InputStream input = SQLTranslator.class.getClassLoader().getResourceAsStream("dbconfig.properties")) {Properties prop = new Properties();if (input == null) {System.out.println("Sorry, unable to find dbconfig.properties");return null;}prop.load(input);dbType = prop.getProperty("database.type");} catch (Exception ex) {ex.printStackTrace();}return SQLUtil.translate(sql, dbType);
}
主程序调用,传入 SQL 并调用 SQL 翻译,后面的代码与原来完全一致,包括设置参数、执行 SQL、获取结果集等等。事实上,主程序代码仅仅增加了一句 sql = translateSQL(sql) 。
public static void main(String[] args) {……String sql = “SELECT name, birthday, adddays(birthday,10) day10 “+ “ FROM emp where dept=? and salary>?” ;sql = translateSQL(sql);pstmt.setString(1, "Sales"); pstmt.setDouble(2, 50000);……}
使用方式 2- 透明化并执行 SQL
前面的方法在调用时需要多做一步翻译,如果执行 SQL 的地方比较多,原程序的改动也会比较大。而且还使用了官方不推荐的接口,未来可能有不兼容的风险。
为了克服这些缺点,我们还可以采用更透明的方法,即把 SQL 翻译以及执行 SQL 获取结果集的动作也在 SPL 内完成。
SPL 提供了标准 JDBC 支持,只要将数据库驱动和 URL 都改成 SPL 的,其它代码可以完全不动,既不需要封装方法,也不需要显式翻译。
public static void main(String[] args) {String driver = "com.esproc.jdbc.InternalDriver";String url = "jdbc:esproc:local://";try {Class.forName(driver);Connection conn = DriverManager.getConnection(url);String sql = "SELECT orderid, employeeid, adddays(orderdate,10) day10,amount "+ "FROM orders WHERE employeeid > ? AND amount > ?";PreparedStatement st = conn.prepareStatement(sql);st.setObject(1,"506");st.setObject(2,9900);ResultSet rs = st.executeQuery();while (rs.next()) {String employeeid = rs.getString("employeeid");System.out.print(employeeid+",");}} catch (Exception e) {throw new RuntimeException(e);}
}
这段代码中并没有翻译的过程,那是怎么实现 SQL 翻译的呢?看起来似乎有点神奇。
关键点在于 SPL 的 JDBC 网关。我们事先配置一个 SPL 脚本,JDBC 中执行所有 SQL 语句都会先交给这个脚本处理执行。也就是说,SQL 的翻译和执行都是在脚本中完成的。
要使用 JDBC 网关,需要在 raqsoftConfig.xml 中的 JDBC 节点配置 SPL 脚本,比如这里配置的 gateway.splx。
<JDBC><load>Runtime,Server</load><gateway>gateway.splx</gateway></JDBC>
网关脚本需要两个参数,一个 sql 参数用于接收 SQL 语句,另一个 args 参数则用于接收 SQL 语句中的参数,也就是 JDBC 给 SQL 传递的参数。
下面有个“最后一个参数是动态参数” 的选项 要勾选,这样才能接收到 SQL 语句的多个参数。
我们来看看脚本内容。
A | B | |
---|---|---|
1 | if !ifv(dbName) | >call(“initGlobalVars.splx”) |
2 | =sql=trim(sql).sqltranslate(dbType) | |
3 | =argsN=args.len() | =(“sql “argsN.(“args(”/~/”)”)).concat@c() |
4 | =connect(dbName) | |
5 | if pos@hc(sql,“select”) | return A4.query@x(${B3}) |
6 | else | =A4.execute(${B3}) |
7 | >A4.close() |
A1 中判断 dbName 变量是否存在,如果不存在则在 B1 调用初始化脚本 initGlobalVars.splx:
A | |
---|---|
1 | >env(dbType,file(“dbconfig.properties”).property(“database.type”)) |
2 | >env(dbName,file(“dbconfig.properties”).property(“database.name”)) |
这个脚本读取配置文件中的数据源名称和数据库类型,用 ENV 函数放置在全局变量 dbType 和 dbName 中。
其中,配置文件 dbconfig.properties 内容:
database.type=MYSQL
database.name=MYDATASOURCE
A2 进行 SQL 翻译,这个方法大家已经不陌生了。
A3 计算参数个数。B3 将参数拼成一个串,比如两个参数的时候 B3 的结果是这样的。
A4 进行数据源连接,这个数据源是在 raqsoftConfig.xml 中配置的,增加 DB 节点配置相应数据源连接信息即可,多个数据源可以依次配置。
<DB name="MYDATASOURCE"><property name="url" value="jdbc:mysql://127.0.0.1:3306/mydb?useCursorFetch=true"></property><property name="driver" value="com.mysql.jdbc.Driver"></property><property name="type" value="10"></property><property name="user" value="root"></property><property name="password" value="root"></property><property name="batchSize" value="0"></property><property name="autoConnect" value="false"></property><property name="useSchema" value="false"></property><property name="addTilde" value="false"></property><property name="caseSentence" value="false"></property></DB>
A5 判断是否是 select 语句,我们要实现所有 SQL 的翻译和执行,而 DQL 和 DML 语句的执行方式不同,返回值也不同,所以要分别处理。
如果是 select 语句,B5 使用 db.query 函数进行查询并获得结果,@x 代表查询后关闭数据库连接。这里使用了 SPL 宏,宏替换的语句是这样。
A6 对于非 select 语句,需要使用 db.execute 函数执行 SQL 语句。
整体脚本并不是很复杂,而且以后修改脚本也不需要重启应用,因为 SPL 是解释执行的,支持热切换。
通过这个网关脚本,也可以执行 update 这类 DML 语句。
public static void main(String[] args) {String driver = "com.esproc.jdbc.InternalDriver";String url = "jdbc:esproc:local://";try {Class.forName(driver);Connection conn = DriverManager.getConnection(url);String sql = "update orders set customername = ? where orderid = ? ";PreparedStatement st = conn.prepareStatement(sql);st.setObject(1,"PTCAG001");st.setObject(2,"1");st.executeUpdate();} catch (Exception e) {throw new RuntimeException(e);}
}
我们在程序中执行 update 语句看一下,可以看到同样会被翻译成对应的数据库语句,并且更新成功。这意味着所有 SQL 都可以无缝移植。
多库情况
有些应用可能会涉及多个数据库,这种情况应该如何处理呢?
使用方式 1- 仅用 SQL 翻译
还是先看仅翻译的用法。
仍然要在配置文件中维护数据源名称和数据源类型。在 dbsconfig.properties 中添加如下内容:
database.oracleds.type=ORACLE
database.mysqlds.type=MYSQL
database.pgds.type=POSTGRESQL
等号前面的中间部分是数据源名称,如 oracleds,等号后面是数据源类型,如 ORACLE。由于存在多个数据库,我们需要根据数据源名称查找类型。
编写翻译方法,根据数据源名称查找类型,加载配置文件获得属性信息,进行 SQL 翻译:
public static String translateSQL(String sql, String dataSourceName) {try (InputStream input = SQLTranslator.class.getClassLoader().getResourceAsStream("dbsconfig.properties")) {Properties prop = new Properties();if (input == null) {System.out.println("Sorry, unable to find dbsconfig.properties");return null;}prop.load(input);String dbType = prop.getProperty("database." + dataSourceName.toLowerCase() + ".type");if (dbType == null) {throw new RuntimeException("Data source " + dataSourceName + " not configured in the configuration file.");}return SQLUtil.translate(sql, dbType);} catch (Exception ex) {ex.printStackTrace();return null;}
}
翻译方法与前面类似,这里不再赘述。
主程序使用时传递 SQL 语句和数据源名称,这里是 mysqlDS,还可以是其他不同数据源,然后翻译 SQL,接下来设置参数、执行 SQL、获取结果集等与原程序完全一致。
public static void main(String[] args) {String sql = "SELECT orderid, employeeid, adddays(orderdate,10) day10,amount "+ "FROM orders WHERE employeeid > ? AND amount > ?";String dataSourceName = "mysqlds"; String translatedSQL = translateSQL(sql, dataSourceName);System.out.println("Translated SQL: " + translatedSQL);……
}
使用方式 2-SPL 脚本翻译并执行 SQL
仅翻译的优缺点我们前面谈论过,下面我们看一下用 SPL 网关翻译并执行 SQL。
public static void main(String[] args) {String driver = "com.esproc.jdbc.InternalDriver";String url = "jdbc:esproc:local://";try {Class.forName(driver);String mysqlDsName = "mysqlds";Connection mysqlConn = DriverManager.getConnection(url);String setDS = "setds "+ mysqlDsName;PreparedStatement setst = mysqlConn.prepareStatement(setDS);setst.execute();String sql = "SELECT orderid, employeeid, adddays(orderdate,10) day10,amount "+ "FROM orders WHERE employeeid > ? AND amount > ?";PreparedStatement st = mysqlConn.prepareStatement(sql);st.setObject(1, "506");st.setObject(2, 9900);ResultSet rs = st.executeQuery();while (rs.next()) {String employeeid = rs.getString("employeeid");System.out.print(employeeid + ",");}} catch (Exception e) {throw new RuntimeException(e);}
这里为不同数据源分别建立 Connection,并增加一步设置数据源名称,数据源解析在网关脚本中处理,其余执行 SQL 部分则与原来程序完全一致。
网关脚本的参数与前面的单库网关脚本完全一致,sql 参数用于接收 SQL 语句,args 参数用于接收 SQL 参数。
网关脚本 gateway.splx 内容:
A | B | |
---|---|---|
1 | if !ifv(dbs) | >call(“initGlobalVarsMulti.splx”) |
2 | =sql=trim(sql) | |
3 | if pos@hc(sql,“setds”) | >env@j(dsName,lower(trim(mid(sql,7)))) |
4 | >env@j(dbType,dbs.select(name==“database.”+dsName+“.type”).value) | |
5 | return | |
6 | =sql=sql.sqltranslate(dbType) | |
7 | =argsN=args.len() | =(“sql “argsN.(“args(”/~/”)”)).concat@c() |
8 | =connect(dsName) | |
9 | if pos@hc(sql,“select”) | return A8.query@x(${B7}) |
10 | else | >A8.execute(${B7}) |
11 | >A8.close() |
处理多库的网关脚本增加了数据源名称设置过程。
B1 调用的 initGlobalVarsMulti.splx 初始化脚本读取配置文件:
A | |
---|---|
1 | >env(dbs,file(“dbsconfig.properties”).property()) |
结果如下:
A3 接收程序传递的设置数据源参数,也就是:setds mysqlds,如果是以 setds 开头,则在 B3 将数据源名称放置在任务变量 dsName 中,任务变量的作用域是同一个 Connection,接下来所有该数据源下的 SQL 语句可以直接运行,B4 类似,根据数据源列表 dbs 查找数据库类型并存入 dbType 任务变量中。
A6 开始的脚本与单库一样,此处不再赘述。
这个网关脚本仍然能处理所有 SQL 语句,全部能够无缝移植。
以上就是我们要学习 SPL 的 SQL 移植全部内容,有了 SPL 数据库切换不需要再更改代码,做到无缝移植。
当然,SPL 的能力还远不止于此,SPL 还支持并行执行 SQL 取数、完成跨库查询、支持数据库与其他非数据库混合计算,甚至可以借助 SPL 的计算能力对 SQL 进行性能优化,这些内容我们会在后面的专题逐渐介绍。
SPL 是开源软件,可以从 github 获取源码:https://github.com/SPLWare/esProc
相关文章:
SPL 实践系列:跨库移植 SQL
背景 应用程序可能要基于不同数据库工作,各种数据库的 SQL 语法大体一致,但仍有些差别,结果就要改造这些 SQL,而这事通常只能手工调整,工作量大还容易出错。 完全自动改造 SQL 几乎是无法做到的,毕竟各种…...
docker启动的rabbitmq搭建并集群和高可用
Docker 搭建 RabbitMQ 集群步骤 以下是使用 Docker 快速搭建 RabbitMQ 集群的详细步骤,包含配置文件、网络设置和集群组建过程。 1. 创建自定义网络 首先创建一个 Docker 网络,使容器间可以通过名称互相访问: docker network create rabb…...
ISCSI存储
ISCSI存储 一、iscsi简介 1. 基本概念 iSCSI(Internet Small Computer System Interface)即互联网小型计算机系统接口,它是一种基于 TCP/IP 协议的存储网络协议,将传统的 SCSI(Small Computer System Interface&…...
算法与数据结构:动态规划DP
文章目录 动态规划算法全面解析一、核心思想与基本概念二、动态规划与其他算法的区别三、动态规划的解题步骤四、经典案例解析1. **斐波那契数列(Fibonacci)**2. **0-1背包问题(0-1 Knapsack)**3. **最长公共子序列(LC…...
核心概念解析:AI、数据挖掘、机器学习与深度学习的关系
核心概念解析:AI、数据挖掘、机器学习与深度学习的关系 本节旨在厘清人工智能(AI)、机器学习(ML)、深度学习(DL)、数据挖掘等常被混淆的概念及其相互关系。 1. 从应用目标看:人工智…...
跨域视角下强化学习重塑大模型推理:GURU框架与多领域推理新突破
跨域视角下强化学习重塑大模型推理:GURU框架与多领域推理新突破 大语言模型(LLM)推理能力的提升是AI领域的重要方向,强化学习(RL)为此提供了新思路。本文提出的GURU框架,通过构建跨领域RL推理语…...
黑马python(十三)
目录: 1.文件编码概念 2.文件的读取操作 3.文件的写入操作 4.文件的追加写操作 5.文件操作的综合案例 1.文件编码概念 2.文件的读取操作 多次调用read或相关读取方法会接着上一次读取的记录读 如果文件没有关闭,只要程序还在运行,文件…...
Redis-CPP 5大类型操作
这篇文章不会讲解所有关于5大基础类型的所有命令,只讲解几个常用的命令操作。如果想看全部的命令操作,可以看其它的文章。 string set 先来看一看set操作在服务器端的函数原型: SET key value [expiration EX seconds|PX milliseconds] [N…...
Linux 下的 socket
1、简介 Socket,中文常称为“套接字”,是 UNIX 操作系统中引入的一种通信抽象接口,用于支持不同进程之间,特别是不同主机之间的通信。在 UNIX 哲学中,“一切皆文件”,包括网络通信也不例外。Socket 就是这种…...
链接脚本基础语法
目录 前言 ELF文件布局 链接脚本语法 段定义标准格式 地址计数器 . 地址计数器的动态特性 赋值 vs 引用 符号定义 通配符规则 COMMON块 COMMON 块的产生与处理 示例脚本 前言 由于嵌入式系统内存资源珍贵,链接脚本可指定代码段(.text &#…...
Python期末速成
一.基础内容 赋值语句: a 1 b "mayday" 标识符规则: 1.字母,数字,下划线,汉字组成。但数字不能开头 2.不能是保留字 3.特殊符号不行,*¥^等 注释是在语句前面加# …...
Python打卡训练营Day56
DAY 56 时序数据的检验 知识点回顾: 假设检验基础知识 原假设与备择假设P值、统计量、显著水平、置信区间 白噪声 白噪声的定义自相关性检验:ACF检验和Ljung-Box 检验偏自相关性检验:PACF检验 平稳性 平稳性的定义单位根检验 季节性检验 ACF检…...
没掌握的知识点记录
1、微内核的主要优点在于结构清晰、内核代码量少,安全性和可靠性高、可移植性强、可伸缩性、可扩展性高;其缺点是难以进行良好的整体优化、进程间互相通信的开销大、内核功能代码不能被直接调用而带来服务的效率低。 2、题目: 分页内存管理…...
Python商务数据分析——Python 入门基础知识学习笔记
一、简介 1.1 Python 特性 解释型语言:代码无需编译可直接运行,适合快速开发。 动态类型:变量类型在运行时确定(如x1后x"str"仍合法)。 面向对象:支持类、对象、继承等特性,代码可…...
企业级安全实践:SSL 加密与权限管理(二)
权限管理:企业数据的守护者 权限管理的基本概念与重要性 权限管理,是指根据系统设置的安全规则或策略,用户可以访问且仅能访问自己被授权的资源,不多不少 。它是企业信息安全体系的重要组成部分,旨在确保只有授权的人…...
JavaScript 的 “==” 存在的坑
(双等) 指的是宽松相等 — 会做隐式类型转换 举例:0 // true 5 5 // true (三等) 指的是严格相等 — 类型和值都相等才 true 举例:0 // false 5 5 // false 在业务逻辑里经常因为隐式转换导致条件误判,业界普遍推荐 一律用 / !。 举…...
深度解析云计算网络架构:VLAN+OVS+Bonding构建高可靠虚拟化平台
——从物理设备到虚拟机流量的全链路剖析 核心技术组合:VLAN逻辑隔离 OVS虚拟交换 Bonding链路聚合 超融合网络管理 一、架构全景:物理与虚拟网络的协同(附架构图) 核心设计哲学 #mermaid-svg-VbGP3fCgNnoLVMgH {font-family:&…...
Git使用总结
1.基本概念: Git中的区域: git中有几个区域;本地工作区;本地提交区;origin远端。 一般来说的工作上传顺序是: 将修改文件添加到工作区域----提交到本地提交区域----push到远端分支 Git中的分支 远端和…...
爬虫入门练习(文字数据的爬取)
爬取csdn用户的用户简介 学习一下 BeautifulSoup方法 from bs4 import BeautifulSoup html_content """ <html> <head><title>示例网页</title> </head> <body><h1 class"main-title">欢迎学习Beauti…...
MySQL学习(1)——基础库操作
欢迎来到博主的专栏:MySQL学习 博主ID:代码小豪 文章目录 数据库原理基础库操作增删数据库数据库编码与校验规则验证不同的校验规则对于库中数据的影响 备份与恢复数据库 数据库原理 mysql版本:mysql8.0 操作系统:ubuntu22.4 为了减少由于环境配置以及权限限制带来的使用问题&…...
P99延迟:系统性能优化的关键指标
理解P99延迟 当谈论系统性能时,延迟指标扮演着至关重要的角色。其中,P99延迟作为最重要的性能指标之一,能够帮助我们识别系统的性能瓶颈,优化用户体验。 构建一个功能完善的后端系统,通过了所有功能测试,准…...
人工智能、机器人最容易取哪些体力劳动和脑力劳动
人工智能、机器人最容易取哪些体力劳动和脑力劳动 人工智能和机器人的发展可以替代人类简单的体力劳动和脑力劳动,但很难替代复杂的体力劳动和脑力劳动。 肌肉收缩的原理和运动特点 人类的体力劳动是靠肌肉的收缩完成的,其工作原理是肌肉内的肌球蛋白…...
【代码解析】opencv 安卓 SDK sample - 1 - HDR image
很久没有写安卓了,复习复习。用的是官方案例,详见opencv-Android-sdk 包 // 定义包名,表示该类的组织路径 package org.opencv.samples.tutorial1;// 导入所需的OpenCV和Android类库 import org.opencv.android.CameraActivity; // OpenCV…...
管理综合知识点
比与比例涉及的问题 比与比例基础知识比例的转换正反比例浓度问题利润问题增长率问题比例与面积行程问题 一、比例转换与性质 核心公式: 若 a : b c : d a:b c:d a:bc:d或 a b c d \frac{a}{b} \frac{c}{d} badc → a d b c ad bc adbc(交…...
机器学习:特征向量与数据维数概念
特征向量与数据维数概念 一、特征向量与维数的定义 特征向量与特征类别 在机器学习和数据处理中,每个样本通常由多个特征(Feature) 描述。例如,一张图片的特征可能包括颜色、形状、纹理等;一个客户的特征可能包括年龄…...
《情感反诈模拟器》2025学习版
1.2 专业内容支持 67篇情感诈骗案例研究14万字心理学分析资料783条专业配音对白 二、安装与运行 2.1 系统要求 最低配置: 显卡:GTX 1060CPU:i5-8400存储:25GB空间 2.2 运行步骤 解压游戏文件(21.7GB)…...
C++ - 标准库之 <string> npos(npos 概述、npos 的作用)
一、std::string::npos 概述 std::string::npos 是一个静态常量,表示 size_t 类型的最大值 std::string::npos 用于表示字符串操作中的未找到的位置或无效位置 std::string::npos 属于 C 标准库中的 <string> 头文件 二、std::string::npos 的作用 std::s…...
策略设计模式
1. 什么是策略模式 策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端,客户端中的具体实现只需要了解上下文类。 2. 由什么组成 策略接口&…...
C++结构体初始化与成员函数实现语法详解
C结构体初始化与成员函数实现语法详解 一、结构体静态成员初始化语法 在C中,静态成员变量需要在类外部进行定义和初始化。提供的代码展示了如何为MAIN_PROPULSION_CAN类的静态成员变量进行初始化: MAIN_PROPULSION_CAN::VoltageThresholds MAIN_PROPU…...
第八章 网络安全
1 什么是网络安全 安全通信具有的性质: 机密性:只有发送方和希望的接收方能否理解传输的报文内容(发送方加密报文,接收方解密报文)认证(端点鉴别):发送方和接收方需要确认对方的身…...
开源 python 应用 开发(一)python、pip、pyAutogui、python opencv安装
最近有个项目需要做视觉自动化处理的工具,最后选用的软件为python,刚好这个机会进行系统学习。短时间学习,需要快速开发,所以记录要点步骤,防止忘记。 链接: 开源 python 应用 开发(一&#x…...
CMCC RAX3000M nand版 OpenWrt 可用空间变小的恢复方法
文章目录 问题背景尝试一、通过 Tftpd64 重新刷写 initramfs-recovery 镜像 (不成功)尝试二、重新分配 ubi 卷(此操作存在一定的危险,请查阅相关资料,避免影响到核心分区) 问题背景 CMCC RAX3000M Nand 版…...
云函数调测、部署及日志查看
1、调试云函数 业务函数开发完成后,需要验证函数代码的正确性,DevEco Studio工具支持本地调用和远程调用两种形式的调试函数方法,首先来看看通过本地调用方式调试函数。 1)通过本地调用方式调试云函数 为了验证函数的正确性以及…...
逆向某物 App 登录接口:还原 newSign 算法全流程
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ newSign 参数分析 通过 Hook Java 层加密算法得到 newSign 参数相关信息如下: 具体参考:逆向某物 App 登录接口:抓包分析…...
2140、解决智力问题
题目 解答 正向不好做,反向遍历。 定义:dp[i] [i,n)的分数 初始化:dp[n]0 递推:dp[i]max(dp[i1],questions[i][0]dp[iquestions[i][1]1]) 如果越界了,就截断到dp[n] 最后return dp[0]即可 class Solution { publ…...
肖臻《区块链技术与应用》第六讲:比特币网络
一、分层架构:应用层之下的P2P网络 比特币并非凭空运作,它的协议运行在互联网的应用层之上。而在其底层,支撑整个系统的是一个对等网络(Peer-to-Peer, P2P)。可以这样理解: 应用层 (Application Layer): …...
(C++)素数的判断(C++教学)(C语言)
源代码: #include <iostream> using namespace std;int fun(int num){if(num<1){return 1;}if(num%20){return 0;}else{return 2;} }int main(){while (1){int y0;int num0;cout<<"请输入一个整数:\n";cin>>num;yfun(nu…...
openai-agents实现input_guardrails
目录 版本模块引入自定义LLM模型input_guardrail设置main函数 代码: input_guardrails.ipynb 版本 import agents print(agents.__version__)0.0.19模块引入 from __future__ import annotationsfrom pydantic import BaseModelfrom agents import (Agent,Guardr…...
在高数中 导数 微分 不定积分 定积分 的意义以及联系
在高等数学中,导数、微分、不定积分、定积分是微积分的核心概念,它们既有明确的定义和几何/物理意义,又相互关联。下面分别说明它们的意义,并总结它们之间的联系。 导数的意义 定义: 函数 y f(x) 在点 x 处的导数定义…...
Linux系统基本操作指令
Linux系统基本操作指令 文章目录 Linux系统基本操作指令一、介绍二、基础设置2.1 设置ubuntu与window的共享目录2.2 ubuntu系统简单介绍 三、Linux命令及工具介绍3.1 目录管理命令(功能,格式,参数,系统参数)3.2 文件操作命令 四、网络命令4.1…...
「Linux文件及目录管理」vi、vim编辑器
知识点解析 vi/vim编辑器简介 vi:Linux默认的文本编辑器,基于命令行操作,功能强大。vim:vi的增强版,支持语法高亮、多窗口编辑、插件扩展等功能。vi/vim基本模式 命令模式:默认模式,用于移动光标、复制、粘贴、删除等操作。插入模式:按i进入,用于输入文本。末行模式:…...
等等等等等等
欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。 …...
JAVA集合篇--深入理解ConcurrentHashMap图解版
一、前言 在Java并发编程中,线程安全的Map实现一直是一个重要话题。虽然我们可以使用Collections.synchronizedMap()或者HashTable来获得线程安全的Map,但它们的性能在高并发场景下往往不尽人意。ConcurrentHashMap作为Java并发包中的重要组件࿰…...
Python嵌套循环
一、前言 在 Python 编程中,嵌套循环(Nested Loops) 是指在一个循环的内部再嵌套另一个循环。这种结构常用于处理多维数据结构(如二维数组、矩阵)、遍历组合数据、图形绘制等场景。 虽然嵌套循环在逻辑上更复杂&…...
linux编译安装nginx
1.到官网(nginx)下载nginx压缩包: 2.以(nginx-1.24.0.tar.gz)为例: 1.上传压缩包至linux服务器: rz 2.解压压缩包nginx-1.24.0.tar.gz: tar -zxvf nginx-1.24.0.tar.gz 3.在安装Nginx之前,需…...
算法-动态规划-钢条切割问题
钢条切割问题是一个经典的动态规划问题,旨在通过切割钢条获得最大收益。以下是详细解释和解决方案: 问题描述 给定长度为 n 的钢条和价格表 p,其中 p[i] 表示长度为 i 的钢条的价格(i 1, 2, ..., n)。目标ÿ…...
Java八股文——系统场景设计
如何设计一个秒杀场景? 面试官您好,设计一个秒杀系统,是对一个工程师综合技术能力的巨大考验。它的核心挑战在于,如何在极短的时间内,应对超高的并发请求,同时保证数据(尤其是库存)…...
如何在FastAPI中玩转GitHub认证,让用户一键登录?
title: 如何在FastAPI中玩转GitHub认证,让用户一键登录? date: 2025/06/22 09:11:47 updated: 2025/06/22 09:11:47 author: cmdragon excerpt: GitHub第三方认证集成通过OAuth2.0授权码流程实现,包含用户跳转GitHub认证、获取授权码、交换访问令牌及调用API获取用户信息四…...
[RPA] 影刀RPA实用技巧
1.给数字添加千分位分隔符 将变量variable的数值(2025.437)添加千分位分隔符,使其变为2,025.437 流程搭建: 关键指令: 2.删除网页元素 将bilibili官网的"动态"图标进行删除 流程搭建: 关键指令: 呈现效果…...
RA4M2开发IOT(7)----RA4M2驱动涂鸦CBU模组
RA4M2开发IOT.7--RA4M2驱动涂鸦CBU模组 概述视频教学样品申请硬件准备参考程序初始化 LSM6DSV16X 传感器初始化单双击识别主程序接口RA4M2接口生成UARTUART属性配置R_SCI_UART_Open()函数原型回调函数user_uart_callback0 ()变量定义更新敲击状态DP同步长按进入配网涂鸦协议解析…...