在flink-connector-jdbc中增加对国产数据库达梦(V8)的支持

在flink-connector-jdbc中增加对国产数据库达梦(V8)的支持

​ 本文将展示如何在flink-connector-jdbc中增加对国产数据库达梦(V8)的支持。演示基于Java语言,使用Maven。

1. 关于flink-connector-jdbc

​ flink-connector-jdbc是Apache Flink框架提供的一个用于与关系型数据库进行连接和交互的连接器。它提供了使用Flink进行批处理和流处理的功能,可以方便地将关系型数据库中的数据引入Flink进行分析和处理,或者将Flink计算结果写入关系型数据库。

​ flink-connector-jdbc可以实现以下核心功能:

  • 数据源连接:可以通过flink-connector-jdbc连接到各种支持JDBC标准的关系型数据库,如MySQL、PostgreSQL、Oracle等。
  • 数据写入:可以将Flink的计算结果写入关系型数据库中,实现数据的持久化。
  • 数据读取:可以从关系型数据库中读取数据,并将其作为Flink计算的输入数据。
  • 数据格式转换:可以将关系型数据库中的数据转换为适合Flink计算的数据格式。
  • 并行处理:可以根据数据源的并行度将数据进行分区和并行处理,以加速数据处理的速度。

​ flink-connector-jdbc为Flink提供了与关系型数据库集成的能力,可以方便地进行数据的导入、导出和处理,为开发人员提供了更强大和灵活的数据处理能力。

2. flink-connector-jdbc包含对哪些关系型数据库的支持

​ 截止目前,flink最新版到flink-1.17.1,但是不管是flink-1.17.0还是flink-1.17.1,都没有找到关于flink-connector-jdbc的实现,从flink-1.16.2中能相关实现找到;

在这里插入图片描述

​ 可以看到,flink-connector-jdbc目前只支持4种关系型数据库:derby、mysql、oracle、psql,

3. 在flink-1.17中添加对flink-connector-jdbc支持

​ 这个不难,直接把flink-1.16.2中flink-connector-jdbc的代码实现拷贝到flink-1.17.0中相应位置即可,但注意修改flink-connectors和flink-connector-jdbc下的pom.xml文件

在这里插入图片描述

4. 在flink-connector-jdbc中添加对国产数据库达梦(V8)的支持

4.1 新增DamengRowConverter

​ 在flink-connector-jdbc模块的org.apache.flink.connector.jdbc.internal.converter包下新增DamengRowConverter.java

package org.apache.flink.connector.jdbc.internal.converter;

import org.apache.flink.connector.jdbc.converter.AbstractJdbcRowConverter;
import org.apache.flink.table.data.DecimalData;
import org.apache.flink.table.data.StringData;
import org.apache.flink.table.data.TimestampData;
import org.apache.flink.table.types.logical.DecimalType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;

import dm.jdbc.driver.DmdbBlob;
import dm.jdbc.driver.DmdbClob;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;

/**
 * Runtime converter that responsible to convert between JDBC object and Flink internal object for
 * Dameng.
 */
public class DamengRowConverter extends AbstractJdbcRowConverter {

    private static final long serialVersionUID = 1L;

    public DamengRowConverter(RowType rowType) {
        super(rowType);
    }

    @Override
    public JdbcDeserializationConverter createInternalConverter(LogicalType type) {
        switch (type.getTypeRoot()) {
            case NULL:
                return val -> null;
            case BOOLEAN:
            case FLOAT:
            case DOUBLE:
            case INTERVAL_YEAR_MONTH:
            case INTERVAL_DAY_TIME:
            case INTEGER:
            case BIGINT:
                return val -> val;
            case TINYINT:
                return val -> {
                    if (val instanceof Byte) {
                        return (Byte) val;
                    } else if (val instanceof Short) {
                        return ((Short) val).byteValue();
                    } else {
                        return ((Integer) val).byteValue();
                    }
                };
            case SMALLINT:
                // Converter for small type that casts value to int and then return short value,
                // since
                // JDBC 1.0 use int type for small values.
                return val -> val instanceof Integer ? ((Integer) val).shortValue() : val;
            case DECIMAL:
                final int precision = ((DecimalType) type).getPrecision();
                final int scale = ((DecimalType) type).getScale();
                // using decimal(20, 0) to support db type bigint unsigned, user should define
                // decimal(20, 0) in SQL,
                // but other precision like decimal(30, 0) can work too from lenient consideration.
                return val ->
                        val instanceof BigInteger
                                ? DecimalData.fromBigDecimal(
                                new BigDecimal((BigInteger) val, 0), precision, scale)
                                : DecimalData.fromBigDecimal((BigDecimal) val, precision, scale);
            case DATE:
                return val ->
                        (int) ((Date.valueOf(String.valueOf(val))).toLocalDate().toEpochDay());
            case TIME_WITHOUT_TIME_ZONE:
                return val ->
                        (int)
                                ((Time.valueOf(String.valueOf(val))).toLocalTime().toNanoOfDay()
                                        / 1_000_000L);
            case TIMESTAMP_WITH_TIME_ZONE:
            case TIMESTAMP_WITHOUT_TIME_ZONE:
                return val -> TimestampData.fromTimestamp((Timestamp) val);
            case CHAR:
            case VARCHAR:
                return val -> {
                    // support text type
                    if (val instanceof DmdbClob) {
                        try {
                            return StringData.fromString(
                                    inputStream2String(((DmdbClob) val).getAsciiStream()));
                        } catch (Exception e) {
                            throw new UnsupportedOperationException(
                                    "failed to get length from text");
                        }
                    } else if (val instanceof DmdbBlob) {
                        try {
                            return StringData.fromString(
                                    inputStream2String(((DmdbBlob) val).getBinaryStream()));
                        } catch (Exception e) {
                            throw new UnsupportedOperationException(
                                    "failed to get length from text");
                        }
                    } else {
                        return StringData.fromString((String) val);
                    }
                };
            case BINARY:
            case VARBINARY:
                return val ->
                        val instanceof DmdbBlob
                                ? ((DmdbBlob) val).getBytes(1, (int) ((DmdbBlob) val).length())
                                : val.toString().getBytes();
            case ARRAY:
            case ROW:
            case MAP:
            case MULTISET:
            case RAW:
            default:
                return super.createInternalConverter(type);
        }
    }

    @Override
    public String converterName() {
        return "Dameng";
    }

    /**
     * get String from inputStream.
     *
     * @param input inputStream
     * @return String value
     * @throws IOException convert exception
     */
    private static String inputStream2String(InputStream input) throws IOException {
        StringBuilder stringBuffer = new StringBuilder();
        byte[] byt = new byte[1024];
        for (int i; (i = input.read(byt)) != -1; ) {
            stringBuffer.append(new String(byt, 0, i));
        }
        return stringBuffer.toString();
    }
}

​ 在Flink的flink-connector-jdbc中,createInternalConverter是一个方法,用于创建将JDBC ResultSet中的数据转换为Flink的内部数据结构的转换器。这个方法通常在JDBCInputFormat中被调用。

​ 在Flink中,使用JDBCInputFormat从关系型数据库中读取数据时,它会将JDBC的ResultSet对象作为输入,然后通过createInternalConverter方法将ResultSet中的每一行数据转换为Flink的内部数据结构(例如Tuple或Row),以便后续的处理和计算。

​ createInternalConverter方法接受参数ResultSetExtractor,它是一个接口,定义了将ResultSet中的数据转换为Flink内部数据结构的方法。实际上,Flink的flink-connector-jdbc提供了一些默认的ResultSetExtractor实现,可以根据数据的类型自动选择适当的转换规则。例如,对于数字类型的数据,可以使用JDBCTypeInformation来进行转换,对于字符串类型的数据,可以使用JDBCTypeUtils进行转换。

​ 除了默认的转换器之外,也可以根据具体的需求自定义createInternalConverter方法。这样可以根据数据的特定类型或格式,定义自己的转换规则,并将ResultSet中的数据转换为特定的数据类型。

4.2 新增Dameng的dialect

4.2.1 DamengDialectFactory

package org.apache.flink.connector.jdbc.dialect.dameng;

import org.apache.flink.annotation.Internal;
import org.apache.flink.connector.jdbc.dialect.JdbcDialect;
import org.apache.flink.connector.jdbc.dialect.JdbcDialectFactory;

@Internal
public class DamengDialectFactory implements JdbcDialectFactory {
    @Override
    public boolean acceptsURL(String url) {
        return url.startsWith("jdbc:dm:");
    }

    @Override
    public JdbcDialect create() {
        return new DamengDialect();
    }
}

​ 在flink-connector-jdbc中,JdbcDialectFactory是一个工厂类,用于创建特定数据库的JdbcDialect实例。

​ JdbcDialectFactory的主要作用是根据用户提供的JDBC连接URL,确定要连接的数据库类型,并创建对应的JdbcDialect实例。JdbcDialect是一个接口,定义了与特定数据库相关的SQL语法和行为。不同类型的数据库可能具有一些特定的SQL方言,并且可能有不同的行为和限制。JdbcDialectFactory利用JDBC连接URL中所指定的数据库类型信息,根据配置中的各种数据库方言实现,创建适用于该数据库的JdbcDialect实例。

​ 通过JdbcDialect实例,flink-connector-jdbc可以为特定类型的数据库提供更高级的功能和最佳性能。例如,JdbcDialect可以优化生成的SQL查询,使用特定的语法和函数。它还可以检测数据库支持的特性,以避免不支持的操作。

​ 使用JdbcDialectFactory时,通常在flink-connector-jdbc的连接器配置中指定JDBC连接URL,以确定要连接的数据库类型。之后,会调用JdbcDialectFactory.create方法,提供JDBC连接URL,根据该URL创建并返回适当的JdbcDialect实例。然后,该JdbcDialect实例可以与JDBCInputFormat和JDBCOutputFormat等组件一起使用,以实现特定数据库的查询和操作。

4.2.2 DamengDialect

package org.apache.flink.connector.jdbc.dialect.dameng;

import org.apache.flink.connector.jdbc.converter.JdbcRowConverter;
import org.apache.flink.connector.jdbc.dialect.AbstractDialect;
import org.apache.flink.connector.jdbc.internal.converter.OracleRowConverter;
import org.apache.flink.table.types.logical.LogicalTypeRoot;
import org.apache.flink.table.types.logical.RowType;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/** JDBC dialect for Dameng. */
class DamengDialect extends AbstractDialect {

    private static final long serialVersionUID = 1L;

    private static final int MAX_TIMESTAMP_PRECISION = 9;
    private static final int MIN_TIMESTAMP_PRECISION = 1;

    private static final int MAX_DECIMAL_PRECISION = 38;
    private static final int MIN_DECIMAL_PRECISION = 1;

    @Override
    public JdbcRowConverter getRowConverter(RowType rowType) {
        return new OracleRowConverter(rowType);
    }

    @Override
    public String getLimitClause(long limit) {
        return "FETCH FIRST " + limit + " ROWS ONLY";
    }

    @Override
    public Optional<String> defaultDriverName() {
        return Optional.of("dm.jdbc.driver.DmDriver");
    }

    @Override
    public String dialectName() {
        return "Dameng";
    }

    @Override
    public String quoteIdentifier(String identifier) {
        return identifier;
    }

    @Override
    public Optional<String> getUpsertStatement(
            String tableName, String[] fieldNames, String[] uniqueKeyFields) {

        String sourceFields =
                Arrays.stream(fieldNames)
                        .map(f -> ":" + f + " " + quoteIdentifier(f))
                        .collect(Collectors.joining(", "));

        String onClause =
                Arrays.stream(uniqueKeyFields)
                        .map(f -> "t." + quoteIdentifier(f) + "=s." + quoteIdentifier(f))
                        .collect(Collectors.joining(" and "));

        final Set<String> uniqueKeyFieldsSet =
                Arrays.stream(uniqueKeyFields).collect(Collectors.toSet());
        String updateClause =
                Arrays.stream(fieldNames)
                        .filter(f -> !uniqueKeyFieldsSet.contains(f))
                        .map(f -> "t." + quoteIdentifier(f) + "=s." + quoteIdentifier(f))
                        .collect(Collectors.joining(", "));

        String insertFields =
                Arrays.stream(fieldNames)
                        .map(this::quoteIdentifier)
                        .collect(Collectors.joining(", "));

        String valuesClause =
                Arrays.stream(fieldNames)
                        .map(f -> "s." + quoteIdentifier(f))
                        .collect(Collectors.joining(", "));

        // if we can't divide schema and table-name is risky to call quoteIdentifier(tableName)
        // for example [tbo].[sometable] is ok but [tbo.sometable] is not
        String mergeQuery =
                " MERGE INTO "
                        + tableName
                        + " t "
                        + " USING (SELECT "
                        + sourceFields
                        + " FROM DUAL) s "
                        + " ON ("
                        + onClause
                        + ") "
                        + " WHEN MATCHED THEN UPDATE SET "
                        + updateClause
                        + " WHEN NOT MATCHED THEN INSERT ("
                        + insertFields
                        + ")"
                        + " VALUES ("
                        + valuesClause
                        + ")";

        return Optional.of(mergeQuery);
    }

    @Override
    public Optional<Range> decimalPrecisionRange() {
        return Optional.of(Range.of(MIN_DECIMAL_PRECISION, MAX_DECIMAL_PRECISION));
    }

    @Override
    public Optional<Range> timestampPrecisionRange() {
        return Optional.of(Range.of(MIN_TIMESTAMP_PRECISION, MAX_TIMESTAMP_PRECISION));
    }

    @Override
    public Set<LogicalTypeRoot> supportedTypes() {
        // The data types used in Dameng are list at:
        // https://www.techonthenet.com/oracle/datatypes.php

        return EnumSet.of(
                LogicalTypeRoot.CHAR,
                LogicalTypeRoot.VARCHAR,
                LogicalTypeRoot.BOOLEAN,
                LogicalTypeRoot.VARBINARY,
                LogicalTypeRoot.DECIMAL,
                LogicalTypeRoot.TINYINT,
                LogicalTypeRoot.SMALLINT,
                LogicalTypeRoot.INTEGER,
                LogicalTypeRoot.BIGINT,
                LogicalTypeRoot.FLOAT,
                LogicalTypeRoot.DOUBLE,
                LogicalTypeRoot.DATE,
                LogicalTypeRoot.TIME_WITHOUT_TIME_ZONE,
                LogicalTypeRoot.TIMESTAMP_WITHOUT_TIME_ZONE,
                LogicalTypeRoot.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
                LogicalTypeRoot.ARRAY);
    }
}

​ 在flink-connector-jdbc中,JdbcDialect是一个接口,用于定义与特定数据库相关的SQL语法和行为。每种不同类型的数据库可能有一些特定的SQL方言和行为,JdbcDialect提供了一种方式来处理这些差异,以确保在不同类型的数据库上执行的SQL操作正确执行,并且能够提供最佳的性能。

​ JdbcDialect接口定义了以下几种方法:

  • String quoteIdentifier(String identifier): 将标识符(例如表名、列名)包装在适当的引号中,以在SQL语句中正确引用它。这是为了处理不同数据库对标识符的命名规则的差异。
  • JdbcRowConverter getRowConverter(RowTypeInfo rowTypeInfo): 根据给定的RowTypeInfo,创建一个JdbcRowConverter实例,用于将Flink的Row数据对象转换为适用于特定数据库的JDBC数据对象。这是为了处理不同数据库对数据类型的差异。
  • Optional defaultDriverName(): 获取JDBC驱动程序的默认名称,以在使用未指定驱动程序名称的情况下与数据库建立连接。
  • Optional getUpsertStatement(String tableName, String[] fieldNames, String[] uniqueKeyFields): 用于生成用于"upsert"(插入或更新)操作的SQL语句。"Upsert"操作是指当目标表中存在指定的记录时,执行更新操作;如果不存在,则执行插入操作;在具体的JdbcDialect的实现中,getUpsertStatement方法会根据特定数据库的语法和行为生成相应的SQL语句。不同数据库对于"upsert"操作的语法可能有所不同,因此JdbcDialect会根据数据库类型来生成适当的语句。

​ JdbcDialect的具体实现类会根据特定数据库的特性来实现这些方法,以确保flink-connector-jdbc在不同类型的数据库上能够正确工作。例如,MySQLDialect、PostgresDialect和OracleDialect等都是JdbcDialect的实现类,分别处理MySQL、PostgreSQL和Oracle数据库的特定语法和行为。

5. 实测

​ 编译打包不难,这里略过,我们测试一下;

​ 我这边第一次测试时,就遇到一个大坑,数据写入失败,日志如下:

2023-09-01 17:38:58,545 ERROR org.apache.flink.connector.jdbc.internal.JdbcOutputFormat    [] - JDBC executeBatch error, retry times = 0
dm.jdbc.driver.DMException: Unbinded parameter: 0
	at dm.jdbc.driver.DBError.throwz(DBError.java:727) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at dm.jdbc.driver.DmdbPreparedStatement.checkBindParameters(DmdbPreparedStatement.java:347) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at dm.jdbc.driver.DmdbPreparedStatement.beforeExectueWithParameters(DmdbPreparedStatement.java:372) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at dm.jdbc.driver.DmdbPreparedStatement.do_executeLargeBatch(DmdbPreparedStatement.java:535) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at dm.jdbc.driver.DmdbPreparedStatement.do_executeBatch(DmdbPreparedStatement.java:514) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at dm.jdbc.driver.DmdbPreparedStatement.executeBatch(DmdbPreparedStatement.java:1494) ~[DmJdbcDriver18-8.1.2.79.jar:- 8.1.2.79 - Production]
	at org.apache.flink.connector.jdbc.statement.FieldNamedPreparedStatementImpl.executeBatch(FieldNamedPreparedStatementImpl.java:65) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at org.apache.flink.connector.jdbc.internal.executor.TableInsertOrUpdateStatementExecutor.executeBatch(TableInsertOrUpdateStatementExecutor.java:104) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at org.apache.flink.connector.jdbc.internal.executor.TableBufferReducedStatementExecutor.executeBatch(TableBufferReducedStatementExecutor.java:101) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at org.apache.flink.connector.jdbc.internal.JdbcOutputFormat.attemptFlush(JdbcOutputFormat.java:246) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at org.apache.flink.connector.jdbc.internal.JdbcOutputFormat.flush(JdbcOutputFormat.java:216) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at org.apache.flink.connector.jdbc.internal.JdbcOutputFormat.lambda$open$0(JdbcOutputFormat.java:155) ~[flink-connector-jdbc-1.17.0.jar:1.17.0]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_221]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [?:1.8.0_221]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_221]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [?:1.8.0_221]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_221]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_221]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_221]
2023-09-01 17:38:58,679 ERROR org.apache.flink.connector.jdbc.internal.JdbcOutputFormat    [] - JDBC executeBatch error, retry times = 1
dm.jdbc.driver.DMException: Unbinded parameter: 0

​ 这个异常很奇怪:dm.jdbc.driver.DMException: Unbinded parameter: 0

​ 总之,代码看不出问题来,正百思不得其解的时候,决定升级DmJdbcDriver试试,从8.1.2.79升到了8.1.2.141,终于成功了!

​ 原始代码下载可以参考: https://gitee.com/flink_acme/flink-connector-jdbc.git

6. 附

​ 达梦数据库版本:

SQL> select *,id_code from v$version;

LINEID     BANNER                    id_code                                
---------- ------------------------- ---------------------------------------
1          DM Database Server 64 V8  1-2-38-21.07.09-143359-10018-ENT  Pack1
2          DB Version: 0x7000c       1-2-38-21.07.09-143359-10018-ENT  Pack1

used time: 00:00:07.719. Execute id is 2300.
SQL> 
SQL> select * from v$instance;

LINEID     NAME     INSTANCE_NAME INSTANCE_NUMBER HOST_NAME SVR_VERSION                DB_VERSION         
---------- -------- ------------- --------------- --------- -------------------------- -------------------
           START_TIME          STATUS$ MODE$  OGUID       DSC_SEQNO   DSC_ROLE
           ------------------- ------- ------ ----------- ----------- --------
1          DMSERVER DMSERVER      1               bd161     DM Database Server x64 V8  DB Version: 0x7000c
           2023-08-28 13:51:41 OPEN    NORMAL 0           0           NULL


used time: 565.918(ms). Execute id is 2301.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2 月 7 日算法练习- 数据结构-树状数组上二分

问题引入 给出三种操作&#xff0c; 0在容器中插入一个数。 1在容器中删除一个数。 2求出容器中大于a的第k大元素。 树状数组的特点就是对点更新&#xff0c;成段求和&#xff0c;而且常数非常小。原始的树状数组只有两种操作&#xff0c;在某点插入一个数和求1到i的所有数的…

07-OpenFeign-HTTP压缩优化

gzip是一种数据格式&#xff0c;采用用deflate算法压缩数据&#xff1b;gzip是一种流行的数据压缩算法&#xff0c;应用十分广泛&#xff0c;尤其是在Linux平台。 当GZIP压缩到一个纯文本数据时&#xff0c;效果是非常明显的&#xff0c;大约可以减少70&#xff05;以上的数据…

基于hadoop+spark的大规模日志的一种处理方案

概述: CDN服务平台上有为客户提供访问日志下载的功能,主要是为了满足在给CDN客户提供服务的过程中,要对所有的记录访问日志,按照客户定制的格式化需求以小时为粒度(或者其他任意时间粒度)进行排序、压缩、打包,供客户进行下载,以便进行后续的核对和分析的诉求。而且CDN…

java Servlet 云平台教学系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP 云平台教学系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助 系统采用serlvet dao bean&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发 环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据…

【Python基础】案例分析:电影分析

电影分析 项目背景&#xff1a; 数据集介绍&#xff1a;movie_lens数据集是一个电影信息&#xff0c;电影评分的数据集&#xff0c;可以用来做推荐系统的数据集需求&#xff1a;对电影发展&#xff0c;类型&#xff0c;评分等做统计分析。目标&#xff1a;巩固pandas相关知识…

Redis——缓存设计与优化

讲解Redis的缓存设计与优化&#xff0c;以及在生产环境中遇到的Redis常见问题&#xff0c;例如缓存雪崩和缓存穿透&#xff0c;还讲解了相关问题的解决方案。 1、Redis缓存的优点和缺点 1.1、缓存优点&#xff1a; 高速读写&#xff1a;Redis可以帮助解决由于数据库压力造成…

Blender教程(基础)-顶点的移动、滑移-16

一、顶点的移动与缩放 ShiftA新建柱体、切换到编辑模式 点模式下&#xff0c;选择一个顶点、选择移动&#xff08;GZ&#xff09;&#xff0c;发现顶点严Z轴移动&#xff0c;如下图所示 GY 按数字键盘7切换视图&#xff0c;选择这个面的所有顶点 按S把面缩放大 Ctrl…

第九个知识点:内部对象

Date对象: <script>var date new Date();date.getFullYear();//年date.getMonth();//月date.getDate();//日date.getDay();//星期几date.getHours();//时date.getMinutes();//分date.getSeconds();//秒date.getTime();//获取时间戳&#xff0c;时间戳时全球统一&#x…

一键放置柱子护角,你get了吗?

今天写个番外篇&#xff0c;给柱子添加护角。 记得几年前刚开始做BIM的时候&#xff0c;有次做车库导视方案模型&#xff0c;记得好像是鼎伦设计的车库一体化方案&#xff0c;当时柱子护角就给了两种方案&#xff0c;而且基本上每颗柱子上都要放护角&#xff0c;然后甲方竟然要…

容斥原理基础

文章目录 容斥原理的引入从集合的角度考虑推广例子不被2、3、5整除的数错排问题求不定方程的解Devu和鲜花 容斥原理的引入 从一个小学奥数问题引入&#xff1a; 一个班级有50人 喜欢语文的人有20人 喜欢数学的人有30人 同时喜欢语文数学的人有10人。 问题&#xff1a; 两门都不…

10个简单有效的编辑PDF文件工具分享

10个编辑PDF文件工具作为作家、编辑或专业人士&#xff0c;您可能经常发现自己在处理 PDF 文件。无论您是审阅文档、创建报告还是与他人共享工作&#xff0c;拥有一个可靠的 PDF 编辑器供您使用都非常重要。 10个简单适用的编辑PDF文件工具 在本文中&#xff0c;我们将介绍当今…

详细分析python中的 async 和 await(附Demo)

目录 前言1. 基本知识2. Demo2.1 Demo1&#xff08;同步&#xff09;2.2 Demo2&#xff08;错误&#xff09;2.3 Demo3&#xff08;不正确的异步&#xff09;2.4 Demo4&#xff08;正确异步&#xff09; 3. 完整版4. 拓展4.1 asyncio.create_task(coroutine)4.2 asyncio.gather…

FXTM富拓监管变更!2024开年连续3家交易商注销牌照

交易商的监管信息是经常发生变更的&#xff0c;即使第一次投资时查询平台监管牌照&#xff0c;投资者仍需持续关注其监管动态。千万不要以为第一步审核好后就万事大吉了&#xff01; 2024年开年&#xff0c;就有3家交易商的重要信息发生变更&#xff0c;注销其金融监管牌照&…

Java 将TXT文本文件转换为PDF文件

与TXT文本文件&#xff0c;PDF文件更加专业也更适合传输&#xff0c;常用于正式报告、简历、合同等场合。项目中如果有使用Java将TXT文本文件转为PDF文件的需求&#xff0c;可以查看本文中介绍的免费实现方法。 免费Java PDF库 本文介绍的方法需要用到Free Spire.PDF for Java…

编程实例分享,宠物诊所电子处方怎么开,兽医电子处方模板电子版操作教程

编程实例分享&#xff0c;宠物诊所电子处方怎么开&#xff0c;兽医电子处方模板电子版操作教程 一、前言 以下操作教程以 佳易王兽医电子处方软件V16.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、在系统 设置里可以设置打印参数&#x…

《动手学深度学习(PyTorch版)》笔记7.2

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

单片机学习笔记---LED点阵屏的工作原理

目录 LED点阵屏分类 LED点阵屏显示原理 74HC595的介绍 一片74HC595的工作原理 多片级联工作原理 总结 LED点阵屏由若干个独立的LED组成&#xff0c;LED以矩阵的形式排列&#xff0c;以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合&#xff0c;如汽…

go语言进阶篇——面向对象(一)

什么是面向对象 在我们设计代码时&#xff0c;比如写一个算法题或者写一个问题结局办法时&#xff0c;我们常常会使用面向过程的方式来书写代码&#xff0c;面向过程主要指的是以解决问题为中心&#xff0c;按照一步步具体的步骤来编写代码或者调用函数&#xff0c;他在问题规…

后端创建订单

package com.java1234.entity;import io.jsonwebtoken.Claims;/*** jwt验证信息* author java1234_小锋* site www.java1234.com* company Java知识分享网* create 2019-08-13 上午 10:00*/ public class CheckResult {private int errCode;private boolean success;private Cl…

Linux下的多线程

前面学习了进程、文件等概念&#xff0c;接下里为大家引入线程的概念 多线程 线程是什么&#xff1f;为什么要有线程&#xff1f;线程的优缺点Linux线程操作线程创建线程等待线程终止线程分离 线程间的私有和共享数据理解线程库和线程id深刻理解Linux多线程&#xff08;重点&a…