暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

OceanBase 二次开发 之 Kunpeng + openEuler 适配优化(一)

原创 夏克 2022-04-04
3352

引子

由于时间和精力的原因,上一篇关于OceanBase二次开发的帖子Hello OceanBase!开启OB二次开发之后就很少在社区活动了。当然,还是要感谢社区小编和运营们不失时机的提醒与督促_。这两天在社区里面爬楼梯,翻了翻OB的相关帖子。近期OB社区的主旋律应该是性能相关主题——测试体验和性能调优等方面的。这个主题很好,很复杂,同时涉及的范围也很广,影响性能的因素也很多。我认为,如果把DB比作一辆车,那么车子能跑多快一般取决于三个方面:

  1. 车子的硬件条件,比如发动机的排量
  2. 车子本身的一些设计,比如变速箱的设计,涡轮增压,空气动力学的利用等一些工业设计
  3. 驾驶员本身的驾驶技能与技巧,以及对车子特点的熟悉程度

硬件条件对应数据库运行的基础设施(服务器,操作系统):目前主流的Intel X86,主频可以达到(3.7GHz~4.1GHz)。但是这东西毕竟是进口的发动机不能自主可控,那么对应国产的发动机有海光x86(2.0GHz~2.4GHz),鲲鹏ARM(2.6GHz)等,虽然排量低一点,但是有更多的气缸,比如主流的Kunpeng 920 2座*64 = 128核。在不同的场景下不同的发动机各有优势,这里不做对比。

车子本身的设计对应的就是数据库的设计与架构:包括各种优化器,算子下推,顺序写等等。

驾驶员就是指DBA和数据库使用者:这些直接的数据库用户对数据库的掌握和了解情况,一定程度上也会影响车子的速度。车子到手后是否能发挥最佳的性能,对于DBA这些老司机来说任重而道远。

本文将针对第一点进行探索,先换个底盘(openEuler)和发动机(Kunpeng)看看效果,而第二点是数据库大厂工程师们努力的目标,至于第三点就交给“老司机”吧~

概要

2022-03-30,OceanBase社区release了3.1.3_CE版本。值得注意的是“新增支持ARM平台”功能。我相信这一版本的发布是社区版OceanBase的一个重要里程碑,在信创的大背景下,ARM已成为主要的替代方案。严重点讲,缺失了对ARM的支持,可能会损失一半的市场。如前所述,如果把DB比作汽车,除了性能指标外,市场保有量才是决定是否可以长期稳定立足行业的关键,有了保有量就会有更多的用户反馈,用户反馈又决定了产品的质量,质量提高又会带动销量和保有量,这是一个良性循环的过程,也是目前各大国产数据库厂商努力推广,占领市场的原因吧。

image.png

image.png

目前ARM版支持的编译操作系统只有(alios和centos)两种,但主流的可适配ARM架构的操作系统还有很多,目前的国产操作系统中大部分是基于华为欧拉和阿里龙蜥两大开源社区的商业发行版,知名的包括麒麟Kylin v10,统信UOS(1021a:基于阿里龙蜥内核,1021e:基于华为openEuler内核)。如果使用ARM架构,大部分用户会选择基于openEuler内核的操作系统(针对鲲鹏有优化)。因此,虽然OceanBase 已经release了ARM版本(实测,可以直接运行在鲲鹏+欧拉上),但针对性的优化应该还不够完善,当然这只是时间问题,我相信很快就会有ARM的优化方案和版本推出。

本文将针对ARM版OceanBase迁移到Kunpeng+openEuler进行尝试与实践。目标是,通过源码重新编译3.1.3_CE for ARM,使用华为的毕昇编译器(HUAWEI BiSheng Compiler 2.1.0.B010 clang version 12.0.0 (clang-0749c5924208 flang-d6f2a3bc24a)替代原有的llvm 11.0.1。使用jemalloc替代原有的内存分配器,最终达到性能提升的目的。

写在前面

可能这又是一篇小众的帖子,社区里面的“老司机”们未必能看完。所以先把成果放在这里吧。

  1. 支持在鲲鹏+欧拉上可编译的源码仓库(fork 于社区版源码仓库):https://github.com/Frank-gh/oceanbase
    image.png

注意:切换至openEuler 分支

  1. 使用方式
git clone https://github.com/Frank-gh/oceanbase.git cd oceanbase git checkout openEuler sh build release --init --make
  1. 该仓库将持续维护,期待各位的测试结果和问题反馈。

总体目标

  • OceanBase源码可以在,鲲鹏 920 + openEuler系统上编译;
  • 替换通用的LLVM,CLANG,使用针对Kunpeng优化的毕昇编译器(基于clangv12.0.0);——后续会针对编译器升级进行性能测试
  • 增加鲲鹏优化的编译选项,并使用jemalloc替换自带的内存分配器;——后续会针对调整进行新能测试对比
  • 最终希望能得到一个在鲲鹏+欧拉上的定制优化版本,并提供给社区用户进行测试体验。

一、环境准备

使用华为的ECS,本次实践只为了编译,所以配置要求不高,但编译比较吃CPU和内存,太小的话会出现OOM的情况,建议比以下配置使用更多的CPU和内存(推荐:16c,32G)

CPU架构 操作系统 cpu(s) 内存 硬盘
Kunpeng-920 2.4GHz (ARM) openEuler 20.03 (LTS) 8 16G 40G

操作系统信息

[root@ecs-613f ~]# cat /etc/os-release NAME="openEuler" VERSION="20.03 (LTS)" ID="openEuler" VERSION_ID="20.03" PRETTY_NAME="openEuler 20.03 (LTS)" ANSI_COLOR="0;31"

二、编译准备

2.1 获取源码

git clone https://github.com/Frank-gh/oceanbase.git cd oceanbase ## 切换到 3.1.3_CE tag git checkout 3.1.3_CE ## 创建新的分支 git checkout -b openEuler

2.2 安装依赖

理想情况下,OceanBase所有的依赖会在编译前都自动下载deps/3rd/pkg目录并安装到deps/3rd/usr/local/oceanbase/depsdeps/3rd/usr/local/oceanbase/devtools下面,但实际操作,虽然在devtools下已安装,但是编译的时候并未cover全部。因此需要安装如下包,主要是语法解析和词法解析(lex,yacc)的包。

yum install flex bison bison-devel

另外一个可以选择安装,如果不安装可以设置环境变量来解决:

export LD_LIBRARY_PATH=$SCR_DIR/deps/3rd/usr/local/oceanbase/devtools/lib64:$LD_LIBRARY_PATH

或者直接安装

yum install libatomic

三、调试编译脚本

3.1 首次编译

[root@ecs-613f oceanbase]# ./build.sh release --init --make [ERROR] 'openEuler 20.03 (LTS) (aarch64)' is not supported yet.

调试: 使用 -x 调试build.sh脚本sh -x build.sh release --init --make

+ do_init + cd /root/oceanbase/deps/3rd + bash dep_create.sh [ERROR] 'openEuler 20.03 (LTS) (aarch64)' is not supported yet. + exit 1

发现在执行 dep_create.sh 脚本时报错,继续跟踪…

调试: 使用 -x 调试dep_create.sh脚本 bash -x dep_create.sh

[root@ecs-613f 3rd]# cd /root/oceanbase/deps/3rd [root@ecs-613f 3rd]# bash -x dep_create.sh + get_os_release + [[ aarch64x == \x\8\6\_\6\4\x ]] + [[ aarch64x == \a\a\r\c\h\6\4\x ]] + case "$ID" in + not_supported + echo '[ERROR] '\''openEuler 20.03 (LTS) (aarch64)'\'' is not supported yet.' [ERROR] 'openEuler 20.03 (LTS) (aarch64)' is not supported yet. + return 1 + exit 1

is not supported yet ,好吧,开始撸脚本。

可以看到下面的脚本中支持aach64上编译的操作系统目前只有alios和centos。

function get_os_release() { if [[ "${OS_ARCH}x" == "x86_64x" ]]; then case "$ID" in alinux) version_ge "2.1903" && compat_centos7 && return ;; alios) version_ge "8.0" && compat_centos8 && return version_ge "7.2" && compat_centos7 && return ;; anolis) version_ge "8.0" && compat_centos8 && return version_ge "7.0" && compat_centos7 && return ;; ubuntu) version_ge "16.04" && compat_centos7 && return ;; centos) version_ge "8.0" && OS_RELEASE=8 && return version_ge "7.0" && OS_RELEASE=7 && return ;; debian) version_ge "9" && compat_centos7 && return ;; fedora) version_ge "33" && compat_centos7 && return ;; opensuse-leap) version_ge "15" && compat_centos7 && return ;; #suse sles) version_ge "15" && compat_centos7 && return ;; uos) version_ge "20" && compat_centos7 && return ;; esac elif [[ "${OS_ARCH}x" == "aarch64x" ]]; then case "$ID" in alios) version_ge "8.0" && compat_centos8 && return version_ge "7.0" && compat_centos7 && return ;; centos) version_ge "8.0" && OS_RELEASE=8 && return version_ge "7.0" && OS_RELEASE=7 && return ;; esac fi

脚本中获取操作系统版本信息的逻辑

[root@ecs-613f ~]# source /etc/os-release [root@ecs-613f ~]# PNAME=${PRETTY_NAME:-"${NAME} ${VERSION}"} [root@ecs-613f ~]# echo $PNAME openEuler 20.03 (LTS) [root@ecs-613f ~]# OS_ARCH="$(uname -m)" [root@ecs-613f ~]# PNAME="${PNAME} (${OS_ARCH})" [root@ecs-613f ~]# echo $PNAME openEuler 20.03 (LTS) (aarch64)

修改脚本,增加openEuler的分支,同时,依赖包列表会在同级目录下找到 oceanbase.el8.aarch64.deps ,当然也可以新建一个Euler的deps,这里就不做了,后续再完善。

elif [[ "${OS_ARCH}x" == "aarch64x" ]]; then case "$ID" in alios) version_ge "8.0" && compat_centos8 && return version_ge "7.0" && compat_centos7 && return ;; centos) version_ge "8.0" && OS_RELEASE=8 && return version_ge "7.0" && OS_RELEASE=7 && return ;; # add for openEuler by Shylock openEuler) version_ge "20.03" && OS_RELEASE=8 && return ;; esac

oceanbase.el8.aarch64.deps内容如下:(dep_create.sh通过配置中的repo下载对应的rpm包,并安装的指定目录)

os=8 arch=aarch64 repo=http://mirrors.aliyun.com/oceanbase/development-kit/el/8/aarch64/ [deps] devdeps-gtest-1.8.0-16.el8.aarch64.rpm devdeps-isa-l-static-2.22.0-17.el8.aarch64.rpm devdeps-libcurl-static-7.29.0-16.el8.aarch64.rpm devdeps-libunwind-static-1.6.2-12.el8.aarch64.rpm devdeps-mariadb-connector-c-3.1.12-16.el8.aarch64.rpm devdeps-openssl-static-1.0.1e-12.el8.aarch64.rpm devdeps-libaio-0.3.112-6.el8.aarch64.rpm devdeps-rapidjson-1.1.0-3.el8.aarch64.rpm [tools] obdevtools-binutils-2.30-7.el8.aarch64.rpm obdevtools-bison-2.4.1-9.el8.aarch64.rpm obdevtools-ccache-3.7.12-6.el8.aarch64.rpm obdevtools-cmake-3.20.2-15.el8.aarch64.rpm obdevtools-flex-2.5.35-10.el8.aarch64.rpm obdevtools-gcc-5.2.0-15.el8.aarch64.rpm obdevtools-llvm-11.0.1-40.el8.aarch64.rpm [tools-deps] devdeps-rocksdb-6.22.1-26.el8.aarch64.rpm

至此,基本的修改已经完成,可以再编译试试。

3.2 再次编译

回到源码根目录,重新编译。

[root@ecs-613f oceanbase]# ./build.sh release --init --make

如果顺利,再次编译会直接成功,成功后查看生成的observer。

[root@ecs-613f observer]# file observer observer: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[md5/uuid]=b936eff9babeed59708f2e5ccdd12518, with debug_info, not stripped [root@ecs-613f observer]# ldd observer linux-vdso.so.1 (0x0000fffd9e060000) libm.so.6 => /lib64/libm.so.6 (0x0000fffd9df80000) libpthread.so.0 => /lib64/libpthread.so.0 (0x0000fffd9df40000) libdl.so.2 => /lib64/libdl.so.2 (0x0000fffd9df10000) librt.so.1 => /lib64/librt.so.1 (0x0000fffd9dee0000) libatomic.so.1 => /root/oceanbase/deps/3rd/usr/local/oceanbase/devtools/lib64/libatomic.so.1 (0x0000fffd9deb0000) libc.so.6 => /lib64/libc.so.6 (0x0000fffd9dd20000) /lib/ld-linux-aarch64.so.1 (0x0000fffd9e070000)

查看pkg目录,可以看到已经下载的rpm包:

[root@ecs-613f 3rd]# tree pkg/ pkg/ ├── devdeps-gtest-1.8.0-16.el8.aarch64.rpm ├── devdeps-isa-l-static-2.22.0-17.el8.aarch64.rpm ├── devdeps-libaio-0.3.112-6.el8.aarch64.rpm ├── devdeps-libcurl-static-7.29.0-16.el8.aarch64.rpm ├── devdeps-libunwind-static-1.6.2-12.el8.aarch64.rpm ├── devdeps-mariadb-connector-c-3.1.12-16.el8.aarch64.rpm ├── devdeps-openssl-static-1.0.1e-12.el8.aarch64.rpm ├── devdeps-rapidjson-1.1.0-3.el8.aarch64.rpm ├── devdeps-rocksdb-6.22.1-26.el8.aarch64.rpm ├── obdevtools-binutils-2.30-7.el8.aarch64.rpm ├── obdevtools-bison-2.4.1-9.el8.aarch64.rpm ├── obdevtools-ccache-3.7.12-6.el8.aarch64.rpm ├── obdevtools-cmake-3.20.2-15.el8.aarch64.rpm ├── obdevtools-flex-2.5.35-10.el8.aarch64.rpm ├── obdevtools-gcc-5.2.0-15.el8.aarch64.rpm └── obdevtools-llvm-11.0.1-40.el8.aarch64.rpm

其中obdevtools-llvm-11.0.1-40.el8.aarch64.rpmobdevtools-gcc-5.2.0-15.el8.aarch64.rpm 这两个包实际上是可以考虑进行升级。对应Kunpeng上的定制编译器,分别是BiSheng Compiler 2.1.0(基于clang v12) 和 GCC for openEuler(基于gcc v9.3.0),这两款针对Kunpeng ARM架构都有不同程度的优化,也是本次实践想要得到的结果——即通过编译选项,内存分配器,操作系统参数的优化,是否可以提升OceanBase在鲲鹏 ARM上的性能。

3.3 编译器升级

下载毕昇编译器

wget -c https://mirrors.huaweicloud.com/kunpeng/archive/compiler/bisheng_compiler/bisheng-compiler-2.1.0-aarch64-linux.tar.gz

将tar包放在deps/3rd目录下。

修改dep_create.sh脚本

增加以下几行代码——下载,安装毕昇编译器。

image.png

修改依赖包列表oceanbase.el8.aarch64.deps

image.png

删掉obdevtools-llvm-11.0.1-40.el8.aarch64.rpm这行。

3.4 再来一次

回到源码根目录,重新编译。

[root@ecs-613f oceanbase]# ./build.sh release --init --make

解决编译报错

第三次编译会报错,原因为新的编译器不支持宏嵌套的使用语法。

In file included from /root/oceanbase/build_release/deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx:51: /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:14: error: '(' and '{' tokens introducing statement expression appear in different macro expansion contexts [-Werror,-Wcompound-token-split-by-macro] } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:5: note: expanded from macro 'FALSE_IT' (stmt); \ ^ /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:23: note: '{' token is here } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:6: note: expanded from macro 'FALSE_IT' (stmt); \ ^~~~ In file included from /root/oceanbase/build_release/deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx:51: /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:45: error: '}' and ')' tokens terminating statement expression appear in different macro expansion contexts [-Werror,-Wcompound-token-split-by-macro] } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:6: note: expanded from macro 'FALSE_IT' (stmt); \ ^~~~ /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:14: note: ')' token is here } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:10: note: expanded from macro 'FALSE_IT' (stmt); \ ^ [ 44%] Building CXX object deps/oblib/src/lib/compress/CMakeFiles/oblib_compress.dir/ob_stream_compressor.cpp.o 2 errors generated. make[2]: *** [deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/build.make:94: deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx.o] Error 1 make[1]: *** [CMakeFiles/Makefile2:4083: deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/all] Error 2

解决方案,修改源代码deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp

image.png

去掉语法糖的写法FALSE_IT ,修改后代码风格有点丑,逻辑应该没有问题,后续有时间回归一下单元测试,验证逻辑是否正确。

3.5 再再次编译

回到源码根目录,重新编译。

[root@ecs-613f oceanbase]# ./build.sh release --init --make

查看clang版本

[root@ecs-613f devtools]# /root/oceanbase/deps/3rd/usr/local/oceanbase/devtools/bin/clang -v HUAWEI BiSheng Compiler 2.1.0.B010 clang version 12.0.0 (clang-0749c5924208 flang-d6f2a3bc24a5) Target: aarch64-unknown-linux-gnu Thread model: posix InstalledDir: /root/oceanbase/deps/3rd/usr/local/oceanbase/devtools/bin Found candidate GCC installation: /root/oceanbase/deps/3rd/usr/local/oceanbase/devtools/bin/../lib/gcc/aarch64-redhat-linux/5.2.0 Found candidate GCC installation: /usr/lib/gcc/aarch64-linux-gnu/7.3.0 Selected GCC installation: /usr/lib/gcc/aarch64-linux-gnu/7.3.0 Candidate multilib: .;@m64 Selected multilib: .;@m64

编译通过~

四、升级内存分配器

修改cmake/ENV.cmake

image.png

  • -mtune=tsv110 :aarch流水线优化选项
  • -ljemalloc:内存分配器

最后一次编译

回到源码根目录,重新编译。

[root@ecs-613f oceanbase]# ./build.sh release --init --make

编译成功~

五、验证

5.1 链接情况验证

image.png

ldd可以看到libjemalloc可以成功链接。

5.2 查看observer编译版本信息

可以看到BUILD_VRANCH: openEuler

image.png

六、FQA

Q1:基础编译OceanBase报错

CMake Error at src/sql/parser/CMakeLists.txt:65 (add_library): Cannot find source file: sql_parser_mysql_mode_lex.c CMake Error at src/sql/parser/CMakeLists.txt:65 (add_library): No SOURCES given to target: ob_sql_proxy_parser_objects CMake Error at src/sql/parser/CMakeLists.txt:93 (add_library): No SOURCES given to target: ob_sql_server_parser_static CMake Error at src/sql/parser/CMakeLists.txt:72 (add_library): No SOURCES given to target: ob_sql_server_parser_objects CMake Error at src/sql/parser/CMakeLists.txt:88 (add_library): No SOURCES given to target: ob_sql_proxy_parser_static

A1:缺少语法、词法分析的依赖flex,bison

yum install flex bison bison-devel

Q2:更换clang版本后编译报错

In file included from /root/oceanbase/build_release/deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx:51: /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:14: error: '(' and '{' tokens introducing statement expression appear in different macro expansion contexts [-Werror,-Wcompound-token-split-by-macro] } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:5: note: expanded from macro 'FALSE_IT' (stmt); \ ^ /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:23: note: '{' token is here } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:6: note: expanded from macro 'FALSE_IT' (stmt); \ ^~~~ In file included from /root/oceanbase/build_release/deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx:51: /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:45: error: '}' and ')' tokens terminating statement expression appear in different macro expansion contexts [-Werror,-Wcompound-token-split-by-macro] } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:6: note: expanded from macro 'FALSE_IT' (stmt); \ ^~~~ /root/oceanbase/deps/oblib/src/rpc/obrpc/ob_rpc_processor_base.cpp:454:14: note: ')' token is here } else if (FALSE_IT({ NG_TRACE(transmit); })) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /root/oceanbase/deps/oblib/src/lib/utility/utility.h:29:10: note: expanded from macro 'FALSE_IT' (stmt); \ ^ [ 44%] Building CXX object deps/oblib/src/lib/compress/CMakeFiles/oblib_compress.dir/ob_stream_compressor.cpp.o 2 errors generated. make[2]: *** [deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/build.make:94: deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/Unity/unity_oblib_rpc_common/1_cxx.cxx.o] Error 1 make[1]: *** [CMakeFiles/Makefile2:4083: deps/oblib/src/rpc/CMakeFiles/oblib_rpc.dir/all] Error 2

A2:修改代码逻辑避免宏嵌套

image

七、总结

至此,整个迁移工作已经完成,使用时可以通过手工替换完成升级,后续会提供rpm的形式发布。文章逻辑是按照实操过程进行的记录,反复编译了5次,如果大家想节省时间的话可以看完整个操作流程,统一修改后,一次编译完成。当然,最简单的方法是直接去git仓库clone源码后切换分支进行编译。欢迎各位小伙伴和老司机们进行测试,该仓库会一直维护到官方有类似分支或版本后删除,期待各位的测试结果和问题反馈。

八、写在后面

海纳百川、有容乃大。信创的大背景下,国产硬件、操作系统、数据库、中间件等基础软件行业迎来了春天,这既是机遇又充满了挑战。头部的大厂既是水平领域的竞争对手又是垂直领域的合作伙伴。而作为这个行业的从业者,既要看到我们在基础软件领域的差距,又要砥砺前行,自强不息。

有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴; 与各位共勉。

感谢所有在信创领域一起奋斗的小伙伴们,让我们一起见证国产数据库百花齐放的春天,艳阳高照的夏天和硕果累累的秋天。

最后修改时间:2022-04-06 19:18:00
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论