如何实现一个SQL解析器

news2024/11/20 1:46:34

作者:vivo 互联网搜索团队- Deng Jie

一、背景

随着技术的不断的发展,在大数据领域出现了越来越多的技术框架。而为了降低大数据的学习成本和难度,越来越多的大数据技术和应用开始支持SQL进行数据查询。SQL作为一个学习成本很低的语言,支持SQL进行数据查询可以降低用户使用大数据的门槛,让更多的用户能够使用大数据。

本篇文章主要介绍如何实现一个SQL解析器来应用的业务当中,同时结合具体的案例来介绍SQL解析器的实践过程。

二、为什么需要SQL解析器?

在设计项目系统架构时,我们通常会做一些技术调研。我们会去考虑为什么需要SQL解析器?怎么判断选择的 SQL 解析器可以满足当前的技术要求?

2.1 传统的SQL查询

传统的SQL查询,依赖完整的数据库协议。比如数据存储在MySQL、Oracle等关系型数据库中,有标准的SQL语法。我们可以通过不同的SQL语句来实现业务需求,如下图所示:

但是,在处理海量数据的时候,关系型数据库是难以满足实际的业务需求的,我们需要借助大数据生态圈的技术组件来解决实际的业务需求。

2.2 实际应用场景

在使用大数据生态圈的技术组件时,有些技术组件是自带SQL的,比如Hive、Spark、Flink等;而有些技术组件本身是不带SQL的,比如Kafka、HBase。下面,我们可以通过对比不带SQL和使用SQL解析器后的场景,如下图所示:

从上图中,我们可以看到,图左边在我们使用不带SQL的技术组件时,实现一个查询时,需要我们编写不同的业务逻辑接口,来与Kafka、HBase这些技术组件来进行数据交互。如果随着这类组件的增加,查询功能复杂度的增加,那边每套接口的复杂度也会随之增加,对于后续的扩展和维护也是很不方便的。而图右边在我们引入SQL解析器后,只需要一套接口来完成业务逻辑,对于不同的技术组件进行适配即可。

三、什么是SQL解析器?

在选择SQL解析器应用到我们实际的业务场景之前,我们先来了解一下SQL解析器的核心知识点。

3.1 SQL解析器包含哪些内容?

在使用SQL解析器时,解析SQL的步骤与我们解析Java/Python程序的步骤是非常的相似的,比如:

  • 在C/C++中,我们可以使用LEX和YACC来做词法分析和语法分析
  • 在Java中,我们可以使用JavaCC或ANTLR

在我们使用解析器的过程当中,通常解析器主要包括三部分,它们分别是:词法解析、语法解析、语义解析。

3.1.1 什么词法解析?

如何理解词法解析呢?词法解析我们可以这么来进行理解,在启动词法解析任务时,它将从左到右把字符一个个的读取并加载到解析程序里面,然后对字节流进行扫描,接着根据构词规则识别字符并切割成一个个的词条,切词的规则是遇到空格进行分割,遇到分号时结束词法解析。比如一个简单的SQL如下所示:

SQL示例

SELECT name FROM tab;

通过词法解析后,结果如下所示:

3.1.2 什么是语法解析?

如何理解语法解析呢?语法解析我们可以这么来进行理解,在启动语法解析任务时,语法分析的任务会在词法分析的结果上将词条序列组合成不同语法短句,组成的语法短句将与相应的语法规则进行适配,若适配成功则生成对应的抽象语法树,否则报会抛出语法错误异常。比如如下SQL语句:

SQL示例

SELECT name FROM tab WHERE id=1001;

约定规则如下:

上表中,红色的内容通常表示终结符,它们一般是大写的关键字或者符号等,小写的内容是非终结符,一般用作规则的命名,比如字段、表名等。具体AST数据结构如下图所示:

3.1.3 什么是语义解析?

如何理解语义解析呢?语义解析我们可以这么来进行理解,语义分析的任务是对语法解析得到的抽象语法树进行有效的校验,比如字段、字段类型、函数、表等进行检查。比如如下语句:

SQL示例

SELECT name FROM tab WHERE id=1001;

上述SQL语句,语义分析任务会做如下检查:

  • SQL语句中表名是否存在;
  • 字段name是否存在于表tab中;
  • WHERE条件中的id字段类型是否可以与1001进行比较操作。

上述检查结束后,语义解析会生成对应的表达式供优化器去使用。

四、 如何选择SQL解析器?

在了解了解析器的核心知识点后,如何选择合适的SQL解析器来应用到我们的实际业务当中呢?下面,我们来对比一下主流的两种SQL解析器。它们分别是ANTLR和Calcite。

4.1 ANTLR

ANTLR是一款功能强大的语法分析器生成器,可以用来读取、处理、执行和转换结构化文本或者二进制文件。在大数据的一些SQL框架里面有有广泛的应用,比如Hive的词法文件是ANTLR3写的,Presto词法文件也是ANTLR4实现的,SparkSQLambda词法文件也是用Presto的词法文件改写的,另外还有HBase的SQL工具Phoenix也是用ANTLR工具进行SQL解析的。

使用ANTLR来实现一条SQL,执行或者实现的过程大致是这样的,实现词法文件(.g4),生成词法分析器和语法分析器,生成抽象语法树(也就是我常说的AST),然后再遍历抽象语法树,生成语义树,访问统计信息,优化器生成逻辑执行计划,再生成物理执行计划去执行。

官网示例:

ANTLR表达式

assign : ID '=' expr ';' ;

解析器的代码类似于下面这样:

ANTLR解析器代码

void assign() {
  match(ID);
  match('=');
  expr();
  match(';');
}

4.1.1 Parser

Parser是用来识别语言的程序,其本身包含两个部分:词法分析器和语法分析器。词法分析阶段主要解决的问题是关键字以及各种标识符,比如INT(类型关键字)和ID(变量标识符)。语法分析主要是基于词法分析的结果,构造一颗语法分析数,流程大致如下:

因此,为了让词法分析和语法分析能够正常工作,在使用ANTLR4的时候,需要定义语法(Grammar)。

我们可以把字符流(CharStream),转换成一棵语法分析树,字符流经过词法分析会变成Token流。Token流再最终组装成一棵语法分析树,其中包含叶子节点(TerminalNode)和非叶子节点(RuleNode)。具体语法分析树如下图所示:

4.1.2 Grammar

ANTLR官方提供了很多常用的语言的语法文件,可以进行修改后直接进行复用:GitHub - antlr/grammars-v4: Grammars written for ANTLR v4; expectation that the grammars are free of actions.

在使用语法的时候,需要注意以下事项:

  • 语法名称和文件名要一致;
  • 语法分析器规则以小写字母开始;
  • 词法分析器规则以大写字母开始;
  • 用'string'单引号引出字符串;
  • 不需要指定开始符号;
  • 规则以分号结束;
  • ...

4.1.3 ANTLR4实现简单计算功能

下面通过简单示例,说明ANTLR4的用法,需要实现的功能效果如下:

ANTLR示例

1+2 => 1+2=3
1+2*4 => 1+2*4=9
1+2*4-5 => 1+2*4-5=4
1+2*4-5+20/5 => 1+2*4-5+20/5=8
(1+2)*4 => (1+2)*4=12

通过ANTLR处理流程如下图所示:

整体来说一个原则,递归下降。即定义一个表达式(如expr),可以循环调用直接也可以调用其他表达式,但是最终肯定会有一个最核心的表达式不能再继续往下调用了。

步骤一:定义词法规则文件(CommonLexerRules.g4)

CommonLexerRules.g4

// 定义词法规则
lexer grammar CommonLexerRules;
 
 定义词法
// 匹配ID
ID     : [a-zA-Z]+ ;
// 匹配INT
INT    : [0-9]+    ;
// 匹配换行符
NEWLINE: '\n'('\r'?);
// 跳过空格、跳格、换行符
WS     : [ \t\n\r]+ -> skip;
 
 运算符
DIV:'/';
MUL:'*';
ADD:'+';
SUB:'-';
EQU:'=';

步骤二:定义语法规则文件(LibExpr.g4)

LibExpr.g4

// 定于语法规则
grammar LibExpr;
 
// 导入词法规则
import CommonLexerRules;
 
// 词法根
prog:stat+ EOF?;
 
// 定义声明
stat:expr (NEWLINE)?         # printExpr
    | ID '=' expr (NEWLINE)? # assign
    | NEWLINE                # blank
    ;
 
// 定义表达式
expr:expr op=('*'|'/') expr # MulDiv
    |expr op=('+'|'-') expr # AddSub
    |'(' expr ')'           # Parens
    |ID                     # Id
    |INT                    # Int
    ;

步骤三:编译生成文件

如果是Maven工程,这里在pom文件中添加如下依赖:

ANTLR依赖JAR

<dependencies>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4</artifactId>
        <version>4.9.3</version>
    </dependency>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4-runtime</artifactId>
        <version>4.9.3</version>
    </dependency>
</dependencies>

然后,执行Maven编译命令即可:

Maven编译命令

mvn generate-sources

步骤四:编写简单的示例代码

待预算的示例文本:

示例文本

1+2
1+2*4
1+2*4-5
1+2*4-5+20/5
(1+2)*4

加减乘除逻辑类:

逻辑实现类

package com.vivo.learn.sql;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 重写访问器规则,实现数据计算功能
 * 目标:
 *     1+2 => 1+2=3
 *     1+2*4 => 1+2*4=9
 *     1+2*4-5 => 1+2*4-5=4
 *     1+2*4-5+20/5 => 1+2*4-5+20/5=8
 *     (1+2)*4 => (1+2)*4=12
 */
public class LibExprVisitorImpl extends LibExprBaseVisitor<Integer> {
    // 定义数据
    Map<String,Integer> data = new HashMap<String,Integer>();
 
    // expr (NEWLINE)?         # printExpr
    @Override
    public Integer visitPrintExpr(LibExprParser.PrintExprContext ctx) {
        System.out.println(ctx.expr().getText()+"="+visit(ctx.expr()));
        return visit(ctx.expr());
    }
 
    // ID '=' expr (NEWLINE)? # assign
    @Override
    public Integer visitAssign(LibExprParser.AssignContext ctx) {
        // 获取id
        String id = ctx.ID().getText();
        // // 获取value
        int value = Integer.valueOf(visit(ctx.expr()));
 
        // 缓存ID数据
        data.put(id,value);
 
        // 打印日志
        System.out.println(id+"="+value);
 
        return value;
    }
 
    // NEWLINE                # blank
    @Override
    public Integer visitBlank(LibExprParser.BlankContext ctx) {
        return 0;
    }
 
    // expr op=('*'|'/') expr # MulDiv
    @Override
    public Integer visitMulDiv(LibExprParser.MulDivContext ctx) {
        // 左侧数字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右侧数字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符号
        int opType = ctx.op.getType();
 
        // 调试
        // System.out.println("visitMulDiv>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判断是否为乘法
        if(LibExprParser.MUL==opType){
            return left*right;
        }
 
        // 判断是否为除法
        return left/right;
 
    }
 
    // expr op=('+'|'-') expr # AddSub
    @Override
    public Integer visitAddSub(LibExprParser.AddSubContext ctx) {
        // 获取值和符号
 
        // 左侧数字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右侧数字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符号
        int opType = ctx.op.getType();
 
        // 调试
        // System.out.println("visitAddSub>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判断是否为加法
        if(LibExprParser.ADD==opType){
            return left+right;
        }
 
        // 判断是否为减法
        return left-right;
 
    }
 
    // '(' expr ')'           # Parens
    @Override
    public Integer visitParens(LibExprParser.ParensContext ctx) {
        // 递归下调
        return visit(ctx.expr());
    }
 
    // ID                     # Id
    @Override
    public Integer visitId(LibExprParser.IdContext ctx) {
        // 获取id
        String id = ctx.ID().getText();
        // 判断ID是否被定义
        if(data.containsKey(id)){
            // System.out.println("visitId>>>>> id:"+id+",value:"+data.get(id));
            return data.get(id);
        }
        return 0;
    }
 
    // INT                    # Int
    @Override
    public Integer visitInt(LibExprParser.IntContext ctx) {
        // System.out.println("visitInt>>>>> int:"+ctx.INT().getText());
        return Integer.valueOf(ctx.INT().getText());
    }
 
}

Main函数打印输出结果类:

package com.vivo.learn.sql;
 
import org.antlr.v4.runtime.tree.ParseTree;
 
import java.io.FileNotFoundException;
import java.io.IOException;
import org.antlr.v4.runtime.*;
 
/**
 * 打印语法树
 */
public class TestLibExprPrint {
 
    // 打印语法树 input -> lexer -> tokens -> parser -> tree -> print
    public static void main(String args[]){
        printTree("E:\\smartloli\\hadoop\\sql-parser-example\\src\\main\\resources\\testCase.txt");
    }
 
 
    /**
     * 打印语法树 input -> lexer -> token -> parser -> tree
     * @param fileName
     */
    private static void printTree(String fileName){
        // 定义输入流
        ANTLRInputStream input = null;
 
        // 判断文件名是否为空,若不为空,则读取文件内容,若为空,则读取输入流
        if(fileName!=null){
            try{
                input = new ANTLRFileStream(fileName);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在,请检查后重试!");
            }catch(IOException ioe){
                System.out.println("文件读取异常,请检查后重试!");
            }
        }else{
            try{
                input = new ANTLRInputStream(System.in);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在,请检查后重试!");
 
            }catch(IOException ioe){
                System.out.println("文件读取异常,请检查后重试!");
            }
        }
 
        // 定义词法规则分析器
        LibExprLexer lexer = new LibExprLexer(input);
 
        // 生成通用字符流
        CommonTokenStream tokens = new CommonTokenStream(lexer);
 
        // 语法解析
        LibExprParser parser = new LibExprParser(tokens);
 
        // 生成语法树
        ParseTree tree = parser.prog();
 
        // 打印语法树
        // System.out.println(tree.toStringTree(parser));
 
        // 生命访问器
        LibExprVisitorImpl visitor = new LibExprVisitorImpl();
        visitor.visit(tree);
 
    }
 
}

执行代码,最终输出结果如下图所示:

4.2 Calcite

上述ANTLR内容演示了词法分析和语法分析的简单流程,但是由于ANTLR要实现SQL查询,需要自己定义词法和语法相关文件,然后再使用ANTLR的插件对文件进行编译,然后再生成代码(与Thrift的使用类似,也是先定义接口,然后编译成对应的语言文件,最后再继承或者实现这些生成好的类或者接口)。

4.2.1 原理及优势

而Apache Calcite的出现,大大简化了这些复杂的工程。Calcite可以让用户很方便的给自己的系统套上一个SQL的外壳,并且提供足够高效的查询性能优化。

  • query language;
  • query optimization;
  • query execution;
  • data management;
  • data storage;

上述这五个功能,通常是数据库系统包含的常用功能。Calcite在设计的时候就确定了自己只关注绿色的三个部分,而把下面数据管理和数据存储留给各个外部的存储或计算引擎。

数据管理和数据存储,尤其是数据存储是很复杂的,也会由于数据本身的特性导致实现上的多样性。Calcite抛弃这两部分的设计,而是专注于上层更加通用的模块,使得自己能够足够的轻量化,系统复杂性得到控制,开发人员的精力也不至于耗费的太多。

同时,Calcite也没有重复去早轮子,能复用的东西,都是直接拿来复用。这也是让开发者能够接受去使用它的一个原因。比如,如下两个例子:

  • 例子1:作为一个SQL解析器,关键的SQL解析,Calcite没有重复造轮子,而是直接使用了开源的JavaCC,来将SQL语句转化为Java代码,然后进一步转化成一棵抽象语法树(AST)以供下一阶段使用;
  • 例子2:为了支持后面会提到的灵活的元数据功能,Calcite需要支持运行时编译Java代码。默认的JavaC太重,需要一个更轻量级的编译器,Calcite同样没有选择造轮子,而是使用了开源了Janino方案。

上面的图是Calcite官方给出的架构图,从图中我们可以获取到的信息是,一方面印证了我们上面提到的,Calcite足够的简单,没有做自己不该做的事情;另一方面,也是更重要的,Calcite被设计的足够模块化和可插拔。

  • 【JDBC Client】:这个模块用来支持使用JDBC Client的应用;
  • 【SQL Parser and Validator】:该模块用来做SQL解析和校验;
  • 【Expressions Builder】:用来支持自己做SQL解析和校验的框架对接;
  • 【Operator Expressions】:该模块用来处理关系表达式;
  • 【Metadata Provider】:该模块用来支持外部自定义元数据;
  • 【Pluggable Rules】:该模块用来定义优化规则;
  • 【Query Optimizer】:最核心的模块,专注于查询优化。

功能模块的划分足够合理,也足够独立,使得不用完整集成,而是可以只选择其中的一部分使用,而基本上每个模块都支持自定义,也使得用户能够更多的定制系统。

上面列举的这些大数据常用的组件都Calcite均有集成,可以看到Hive就是自己做了SQL解析,只使用了Calcite的查询优化功能。而像Flink则是从解析到优化都直接使用了Calcite。

上面介绍的Calcite集成方法,都是把Calcite的模块当做库来使用。如果觉得太重量级,可以选择更简单的适配器功能。通过类似Spark这些框架里自定义的Source或Sink的方式,来实现和外部系统的数据交互操作。

上图就是比较典型的适配器用法,比如通过Kafka的适配器就能直接在应用层通过SQL,而底层自动转换成Java和Kafka进行数据交互(后面部分有个案例操作)。

4.2.2 Calcite实现KSQL查询Kafk

参考了EFAK(原Kafka Eagle开源项目)的SQL实现,来查询Kafka中Topic里面的数据。

1.常规SQL查询

SQL查询

select * from video_search_query where partition in (0) limit 10

预览截图:

2.UDF查询

SQL查询

select JSON(msg,'query') as query,JSON(msg,'pv') as pv from video_search_query where `partition` in (0) limit 10

预览截图:

4.3 ANTLR4 和 Calcite SQL解析对比

4.3.1 ANTLR4解析SQL

ANTLR4解析SQL的主要流程包含:定义词法和语法文件、编写SQL解析逻辑类、主服务调用SQL逻辑类。

1.定义词法和语法文件

可参考官网提供的开源地址:详情

2.编写SQL解析逻辑类

这里,我们编写一个实现解析SQL表名的类,具体实现代码如下所示:

解析表名

public class TableListener extends antlr4.sql.MySqlParserBaseListener {

    private String tableName = null;

    public void enterQueryCreateTable(antlr4.sql.MySqlParser.QueryCreateTableContext ctx) {
        List<MySqlParser.TableNameContext> tableSourceContexts = ctx.getRuleContexts(antlr4.sql.MySqlParser.TableNameContext.class);
        for (antlr4.sql.MySqlParser.TableNameContext tableSource : tableSourceContexts) {
            // 获取表名
            tableName = tableSource.getText();
        }
    }

    public String getTableName() {
        return tableName;
    }
}

3.主服务调用SQL逻辑类

对实现SQL解析的逻辑类进行调用,具体代码如下所示:

主服务

public class AntlrClient {

    public static void main(String[] args) {
        // antlr4 格式化SQL
        antlr4.sql.MySqlLexer lexer = new antlr4.sql.MySqlLexer(CharStreams.fromString("create table table2 select tid from table1;"));
        antlr4.sql.MySqlParser parser = new antlr4.sql.MySqlParser(new CommonTokenStream(lexer));
        // 定义TableListener
        TableListener listener = new TableListener();
        ParseTreeWalker.DEFAULT.walk(listener, parser.sqlStatements());
        // 获取表名
        String tableName= listener.getTableName();
        // 输出表名
        System.out.println(tableName);
    }
}

4.3.2 Calcite解析SQL

Calcite解析SQL的流程相比较ANTLR是比较简单的,开发中无需关注词法和语法文件的定义和编写,只需关注具体的业务逻辑实现。比如实现一个SQL的COUNT操作,Calcite实现步骤如下所示。

1.pom依赖

Calcite依赖JAR

<dependencies>
  <!-- 这里对Calcite适配依赖进行封装,引入下列包即可 -->
  <dependency>
    <groupId>org.smartloli</groupId>
    <artifactId>jsql-client</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>

2.实现代码

Calcite示例代码

package com.vivo.learn.sql.calcite;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.smartloli.util.JSqlUtils;

public class JSqlClient {
    public static void main(String[] args) {
        JSONObject tabSchema = new JSONObject();
        tabSchema.put("id","integer");
        tabSchema.put("name","varchar");

        JSONArray datasets = JSON.parseArray("[{\"id\":1,\"name\":\"aaa\",\"age\":20},{\"id\":2,\"name\":\"bbb\",\"age\":21},{\"id\":3,\"name\":\"ccc\",\"age\":22}]");

        String tabName = "userinfo";
        String sql = "select count(*) as cnt from \"userinfo\"";
        try{
           String result = JSqlUtils.query(tabSchema,tabName,datasets,sql);
            System.out.println("result: "+result);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

3.预览截图

4.3.3 对比结果

综合对比,我们从对两种技术的学习成本、使用复杂度、以及灵活度来对比,可以优先选择Calcite来作为SQL解析器来处理实际的业务需求。

五、总结

另外,在单机模式的情况下,执行计划可以较为简单的翻译成执行代码,但是在分布式领域中,因为计算引擎多种多样,因此,还需要一个更加贴近具体计算引擎的描述,也就是物理计划。换言之,逻辑计划只是抽象的一层描述,而物理计划则和具体的计算引擎直接挂钩。

满足上述场景,通常都可以引入SQL解析器:

  • 给关系型数据库(比如MySQL、Oracle)这类提供定制化的SQL来作为交互查询;
  • 给开发人员提供了JDBC、ODBC之类和各种数据库的标准接口;
  • 对数据分析师等不太会编程语言的但又需要使用数据的人;
  • 大数据技术组件不自带SQL的;

参考资料:

  1. https://github.com/smartloli/EFAK
  2. GitHub - antlr/antlr4: ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.
  3. GitHub - antlr/grammars-v4: Grammars written for ANTLR v4; expectation that the grammars are free of actions.
  4. GitHub - apache/calcite: Apache Calcite

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

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

相关文章

【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片

一、BMP图像介绍与显示 我们常用的图片格式有很多&#xff0c;一般最常用的有三种&#xff1a;JPEG(或 JPG)、PNG、BMP和GIF。其中 JPEG(或JPG)、PNG以及 BMP 都是静态图片&#xff0c;而 GIF 则可以实现动态图片。 BMP(全称 Bitmap)是Window操作系统中的标准图像文件格式&am…

计算机学院第五次ACM周赛题解

目录 HF的智能小车车 Do you like Van game&#xff1f; 好姐姐的三角形 帮帮小陈 卷点 签个到就下班 现在是摸鱼时间 现在是摸鱼时间 PLUS HF的智能小车车 签到题目&#xff0c; #include<iostream>using namespace std;int main() {string arr;cin>>ar…

学生会信息管理系统

1、项目介绍 学生会信息管理系统拥有两种角色&#xff1a;学生和管理员 学生&#xff1a;查看活动和新闻信息、发布活动、登录注册 管理员&#xff1a;公共新闻管理、用户管理、活动审核等 2、项目技术 后端框架&#xff1a; Servlet、mvc模式 前端技术&#xff1a;jsp、c…

Linux(centos)安装Jenkins

Jenkins 是一个开源的, 基于Java的自动服务器&#xff0c;提供了一种建立持续集成和持续交付(CI/CD)流水线的简单方法 。 持续集成 (CI)是DevOps实践中&#xff0c;团队成员定期将他们的代码更改提交到版本仓库, 然后运行自动化的构建和测试. 持续交付(CD)是自动构建、测试和部…

【附源码】计算机毕业设计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…

第九章 Ambari二次开发之自定义Flink服务 -- 自定义quiklinks链接

1、自定义quiklink链接功能概述 主要功能&#xff1a;用来配置第三方服务界面链接&#xff0c;如Yarn的管理界面。 服务可以通过在metainfo中加入一串quicklink的引用&#xff08;一串预定义的JSON字符串&#xff09;即可。ambari server能解析json文件并将其提供给UI&#xff…

CSS 2 CSS 选择器 - 2 2.5 属性选择器

CSS 文章目录CSS2 CSS 选择器 - 22.5 属性选择器2.5.1 CSS [attribute] 选择器2.5.2 CSS [attribute"value"] 选择器2.5.3 CSS [attribute~"value"] 选择器2.5.4 CSS [attribute|"value"] 选择器2.5.5 CSS [attribute^"value"] 选择器…

渠道数字化有什么好处?如何实现渠道数字化营销?

数字化转型是企业如今谋求商业升维的一种主要方式&#xff0c;即利用数字化技术和能力来赋能企业经营思维数字化、商业模式创新化&#xff0c;达到业务重构升级、实现业务的转型、创新&#xff0c;增长的目的。对于企业而言&#xff0c;数字化其实正是升维企业业务、企业流程及…

【电子通识】为什么高速信号传输要用差分

早期很多设备要接到计算机上,需要通过一些线径粗连接器笨重的总线。如GPIB接口(通用接口总线)是曾经大多台式仪器与计算机相连接的方式。 在文章:【蓝牙专题】蓝牙测试工具及性能标准 中,电脑就是通过USB转GPIB接口与MT8852进行连接,对低功耗蓝牙模块进行测试。 …

稳压二极管有什么特性?稳压二极管的特点

笔者电子信息专业硕士毕业&#xff0c;获得过多次电子设计大赛、大学生智能车、数学建模国奖&#xff0c;现就职于南京某半导体芯片公司&#xff0c;从事硬件研发&#xff0c;电路设计研究。对于学电子的小伙伴&#xff0c;深知入门的不易&#xff0c;特开次博客交流分享经验&a…

Java泛型——帮助你更好的读懂源码<dog>

泛型 泛型就相当于集合的标签&#xff0c;我们在使用集合<>的时候&#xff0c;就是在使用泛型 要知道的是在jdk1.5以前我们只能把集合中的元素设计为Object&#xff0c;之后引入了泛型的概念来解决&#xff0c;我们把元素的类型设计成一个参数&#xff0c;这个类型参数…

【PyTorch深度学习项目实战100例】—— Pytorch时间序列电力数据多输出预测 | 第70例

前言 大家好,我是阿光。 本专栏整理了《PyTorch深度学习项目实战100例》,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPy…

1024 你学算法了吗?

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

JUC - 多线程之 单例模式(八)

单例模式&#xff08;Singleton Pattern&#xff09;是一种非常简单的设计模式之一&#xff0c;当我们使用的对象要在全局唯一时就需要用到该模式&#xff0c;以保证对象的唯一性。除此之外&#xff0c;还能避免反复的实例化对象&#xff0c;减少内存开销 单例类提供了一种访问…

踩坑记录——USB键盘睡眠唤醒

踩坑记录——USB键盘睡眠唤醒 目录踩坑记录——USB键盘睡眠唤醒前言1 USB远程睡眠唤醒要注意的几个点2 MCU唤醒之后引起USB异常的几个点结束语前言 前段时间我用一个国产MCU实现了雷蛇键盘的效果&#xff0c;按键支持十键无冲&#xff0c;RGB灯支持单控任意一个灯任意一种颜色…

SpringAMQP (RabbitMQ五种模式 消息转换器)

一、简化Hello World模型实现步骤演示 代码步骤演示如下所示&#xff1a; 消息提供者publisher代码简化&#xff1a; 消息消费者consumer代码简化&#xff1a; 二、Work Queue 工作队列模型 消息提供者代码如下所示&#xff1a; 消息消费者代码如下所示&#xff1a; 消息…

SpringCloudAlibaba【六】微服务架构下的秒杀案例

背景 分布式微服务中的秒杀是怎么实现的呢&#xff1f;接着看下去吧 我们实现一个秒杀微服务&#xff0c;流程逻辑如下 项目搭建 MySQL create database if not exists demo;use demo;drop table if exists skill_goods;create table if not exists skill_goods (id bigint…

1024程序员节|【MySQL从入门到精通】【高级篇】(二十七)外连接和内连接如何进行查询优化呢?join的原理了解一波

您好&#xff0c;我是码农飞哥&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 ❤️ 2.网上优质的Python题库很少…

[面试宝典] Linux常见命令及面试题

前言&#xff1a; &#x1f604;作者简介&#xff1a;小曾同学.com,小伙伴们也可以叫我小曾&#xff0c;一个致力于测试开发的博主⛽️ 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。&#x1f60a; 座右铭&#xff1a;…

C语言 - 你一定能看懂的扫雷万字详解(加入了递归展开和手动标雷的功能)

C语言之扫雷详解&#xff08;包含递归展开和手动标雷功能&#xff0c;非常强大&#xff01;&#xff09; 文章目录一.写在前面二.效果展示三.整体逻辑四.详解1.进入主函数&#xff0c;打印菜单&#xff0c;玩家作出选择2.定义棋盘的数组并进行赋值3.棋盘的展示4.随机布雷5.开始…