从一个 issue 出发,带你玩图数据库 NebulaGraph 内核开发

news2024/11/24 1:11:12

如何 build NebulaGraph?如何为 NebulaGraph 内核做贡献?即便是新手也能快速上手,从本文作为切入点就够了。

NebulaGraph 的架构简介

为了方便对 NebulaGraph 尚未了解的读者也能快速直接从贡献代码为起点了解它,我把开发、贡献内核代码入手所需要的基本架构知识在这里以最小信息量的形式总结一下。作为前导知识,请资深的 NebulaGraph 玩家直接跳过这一章节。

服务、进程

NebulaGraph 的架构和 Google Spanner、TiDB 很相似,核心部分只有三种服务进程:Graph 服务、Meta 服务和 Storage 服务。它们之间彼此通过 TCP 之上的 Thrift RPC 协议进行通信。

https://docs-cdn.nebula-graph.com.cn/docs-2.0/1.introduction/2.nebula-graph-architecture/nebula-graph-architecture-1.png

计算层与存储层

NebulaGraph 是存储与计算分离的架构,Meta 服务和 Storage 服务共同组成了存储层,Graph 服务是内核提供的计算层。

这样的设计使得 NebulaGraph 的集群部署可以灵活按需分配计算、存储的资源。比如,在同一个集群中创建不同配置的两组 Graph 服务实例用来面向不同类型的业务。

同时,计算层解耦于存储层使得在 NebulaGraph 之上的构建不同的特定计算层成为可能。比如,NebulaGraph Algorithm、NebulaGraph Analytics 就是在 NebulaGraph 之上构建了异构的另一个计算层。任何人都可以按需定制专属计算层,从而满足统一图基础存储之上的复合、多样的计算需求。

Graph Service:nebula-graphd

Graph 服务是对外接收图库登录、图查询请求、集群管理操作、Schema 定义所直接连接的服务,它的进程名字叫 graphd,表示 nebula graph daemon。

Graph 服务的每一个进程是无状态的,这使得横向扩缩 Graph 服务的实例非常灵活、简单。

Graph 服务也叫 Query Engine,其内部和传统的数据库系统的设计非常相似,分为:解析、校验、计划、执行几部分。

Meta Service:nebula-metad

Meta 服务顾名思义负责元数据管理,进程名字叫 metad。这些元数据包括:

  • 所有的图空间、Schema 定义
  • 用户鉴权、授权信息
  • 集群服务的发现与服务的分布
  • 图空间中的数据分布

Meta 服务的进程可以单实例部署。在非单机部署的场景下,为了数据、服务的高 SLA ,以奇数个实例进行部署。通常来说 3 个 nebula-metad 就足够了,3 个 nebula-metad 通过 Raft 共识协议构成一个集群提供服务。

Storage Service:nebula-storaged

Storage 服务存储所有的图数据,进程名字叫 storaged。storaged 分布式地存储图数据,为 Graph 内部的图查询执行期提供底层的图语义存储接口,方便 Storage 客户端通过 Thrift RPC 协议面向涉及的 storaged 示例进行图语义的读写。

当 NebulaGraph 中图空间的副本数大于 1 的时候,每一个分区都会在不同 storaged 示例上有副本,副本之间则通过 Raft 协议协调同步与读写。

进程间通信、服务发现机制

在 NebulaGraph 中 graphd、metad、storaged 之间通过 Thrift 协议进行远程调用(RPC),下边给一些例子:

  • graphd 会通过 metaclient 调用 metad:将自己报告为一个正在运行的服务,以便被发现;再为用户(使用 graphclient )登录进行 RPC 调用;当它处理 nGQL 查询时,获取图存储分布情况;
  • graphd 会通过 storageclient 调用 storaged:当 graphd 处理 nGQL 时,先从 metad 获得所需的元信息,再进行图数据的读/写;
  • storaged 会通过 metaclient调用 metad:将 storaged 报告为一个正在运行的服务,以便被发现。

当然,有状态的存储引擎内部也有集群同步的流量与通信。比如,storaged 与其他 storaged 有 Raft 连接;metad 与其他 metad 实例有 Raft 连接。

开发环境搭建

接下来,我们开始 NebulaGraph 的构建、开发环境的部分。

NebulaGraph 只支持在 GNU/Linux 分支中构建。目前来说,最方便的方式是在社区预先提供好了依赖的容器镜像的基础上在容器内部构建、调试 NebulaGraph 代码的更改和 Debug

创建一个容器化的 NebulaGraph 集群

为了更方便地调试代码,我习惯提前创建一个 NebulaGraph Docker 环境。推荐使用官方的 Docker-Compose 方式部署,也可以使用我在官方 Docker-Compose 基础之上弄的一键部署工具:nebula-up。

下面以 nebula-up 为例:

在 Linux 开发服务器中执行 curl -fsSL nebula-up.siwei.io/install.sh | bash 就可以了。

代码获取

NebulaGraph 的代码仓库托管在 GitHub 之上,在联网的情况下直接克隆:

git clone git@github.com:vesoft-inc/nebula.git
cd nebula

创建开发容器

有了 NebulaGraph 集群,我们可以借助 nebula-dev-docker 提供的开箱即用开发容器镜像,搭建开发环境:

export TAG=ubuntu2004
docker run -ti \
  --network nebula-net \
  --security-opt seccomp=unconfined \
  -v "$PWD":/home/nebula \
  -w /home/nebula \
  --name nebula_dev \
  vesoft/nebula-dev:$TAG \
  bash

其中,-v "$PWD" 表示当前的 NebulaGraph 代码本地的路径会被映射到开发容器内部的 /home/nebula,而启动的容器名字是 nebula_dev

待这个容器启动后,会自动进入到这个容器的 bash shell 之中。如果我们输入 exit 退出容器,它会被关闭。如果我们想再次启动容器,只需要执行:

docker start nebula_dev

之后的编译、Debug、测试工作都在 nebula_dev 容器内部进行。在容器是运行状态的情况下,可以随时新建一个容器内部的 bash shell 进程:

docker exec -ti nebula_dev bash

为了保持编译环境是最新版,可以定期删除、拉取、重建这个开发容器,以保持环境与代码相匹配。

编译环境

nebula_dev 这个容器内部,我们可以进行代码编译。进入编译容器:

docker exec -ti nebula_dev bash

用 CMake 准备 makefile。第一次构建时,为了节省时间、内存,我关闭了测试 -DENABLE_TESTING=OFF

mkdir build && cd build
cmake -DCMAKE_CXX_COMPILER=$TOOLSET_CLANG_DIR/bin/g++ -DCMAKE_C_COMPILER=$TOOLSET_CLANG_DIR/bin/gcc -DENABLE_WERROR=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTING=OFF ..

开始编译,根据服务器的空闲 CPU 个数和内存量力而行。比如,我在 72 核心的服务器上准备允许同时运行 64 个 job,则运行:

make -j64

第一次构建的时间会慢一些,在 make 成功之后,我们也可以执行 make install 把二进制安装到像生产安装时候一样的路径:

root@1827b82e88bf:/home/nebula/build# make install

root@1827b82e88bf:/home/nebula/build# ls /usr/local/nebula/bin
db_dump  db_upgrader  meta_dump  nebula-graphd  nebula-metad  nebula-storaged

root@1827b82e88bf:/home/nebula/build# ls /usr/local/nebula/
bin  etc  pids  scripts  share

调试 NebulaGraph

以 graphd 调试为例。

安装依赖

安装一些后边会方便 Debug 额外用到的依赖:

# 装一个 ping,测试一下 nebula-up 安装的集群可以访问
apt update && apt install iputils-ping -y
# ping graphd 试试看
ping graphd -c 4

# 安装 gdb gdb-dashboard
apt install gdb -y
wget -P ~ https://git.io/.gdbinit
pip install pygments

准备客户端

准备一个 NebulaGraph 的命令行客户端:

# 新开一个 nebula_dev 的 shell
docker exec -ti nebula_dev bash

# 下载 nebula-console 二进制文件,并赋予可执行权限,命名为 nebula-console 并安装到 /usr/bin/ 下
wget https://github.com/vesoft-inc/nebula-console/releases/download/v3.2.0/nebula-console-linux-amd64-v3.2.0
chmod +x nebula-console*
mv nebula-console* /usr/bin/nebula-console

连接到前边我们 nebula-up 准备的集群之上,加载 basketballplayer 这个测试数据:

nebula-console -u root -p nebula --address=graphd --port=9669
:play basketballplayer;
exit

gdb 运行 graphd

用 gdb 执行刚刚编译的 nebula-graphd 二进制,让它成为一个新的 graphd 服务,名字就叫 nebula_dev

首先启动 gdb:

# 新开一个 nebula_dev 的 shell
docker exec -ti nebula_dev bash

cd /usr/local/nebula/
mkdir -p /home/nebula/build/log
gdb bin/nebula-graphd

在 gdb 内部执行设置必要的参数,跟随 fork 的子进程:

set follow-fork-mode child

设置待调试 graphd 的启动参数(配置):

  • meta_server_addrs 填已经启动的集群的所有 metad 的地址;
  • local_ipws_ip 填本容器的域名,port 是 graphd 监听端口;
  • log_dir 是输出日志的目录,vminloglevel 是日志的输出等级;
set args --flagfile=/usr/local/nebula/etc/nebula-graphd.conf.default \
    --meta_server_addrs=metad0:9559,metad1:9559,metad2:9559 \
    --port=9669 \
    --local_ip=nebula_dev \
    --ws_ip=nebula_dev \
    --ws_http_port=19669 \
    --log_dir=/home/nebula/build/log \
    --v=4 \
    --minloglevel=0

如果我们想加断点在 src/common/function/FunctionManager.cpp 2783 行,可以再执行:

b /home/nebula/src/common/function/FunctionManager.cpp:2783

配置前边安装的 gdb-dashboard,一个开源的 gdb 界面插件。

# 设定在 gdb 界面上展示 代码、历史、回调栈、变量、表达几个部分,详细参考 https://github.com/cyrus-and/gdb-dashboard
dashboard -layout source history stack variables expressions

最后我们让进程通过 gdb 跑起来吧:

run

之后,我们就可以在这个窗口/shell 会话下调试 graphd 程序了。

修改 NebulaGraph 代码

这里,我以 issue#3513 为例子,快速介绍一下代码修改的过程。

读代码

这个 issue 表达的内容是在有一小部分用户决定把 JSON 以 String 的形式存储在 NebulaGraph 中的属性里。因为这种方式比较罕见且不被推崇,NebulaGraph 没有直接支持对 JSON String 解析。

由于不是一个通用型需求,这个功能是希望热心的社区用户自己来实现并应用在他的业务场景中。但在该 issue 中,刚好有位新手贡献者在里边回复、求助如何开始参与这块的功能实现。借着这个契机,我去参与讨论看了一下这个功能可以实现成什么样子。最终讨论的结果是可以做成和 MySQL 中的 JSON_EXTRACT 函数那样,改为只接受 JSON String、无需处理输出路径参数。

一句话来说就是,为 NebulaGraph 引入一个解析 JSON String 为 Map 的函数。那么,如何实现这个功能呢?

在哪里修改

显然,引入新的函数,项目变更肯定有很多。所以,我们只需要找到之前增加新函数的 PR 就可以快速知道在哪些地方修改了。

一般情况下,可以自底向上地了解 NebulaGraph 整体的代码结构,再一点点找到函数处理的位置。这时候,除了代码本身,一些面向贡献者的文章可能会帮助大家事半功倍对整体有一个了解。NebulaGraph 官方也除了一个系列文章,大家做项目贡献前不妨阅读了解下,参见:延伸阅读 5。

具体的实操起来呢?我从 pr#4526 了解到所有函数入口都被统一管理在 src/common/function/FunctionManager.cpp 之中。通过搜索、理解当中某个函数的关键词之后,可以很容易理解一个函数实体的关键词、输入/输出数据类型、函数体处理逻辑的代码在哪里实现

此外,在同一个根目录下,src/common/function/test/FunctionManagerTest.cpp 之中则是所有这些函数的单元测试代码。用同样的方式也可以知道新加的一个函数需要如何在里边实现基于 gtest 的单元测试。

开始改代码

在修改代码之前,确保在最新的 master 分支之上创建一个单独的分支。在这里的例子中,我把分支名字叫 fn_JSON_EXTRACT

git checkout master
git pull
git checkout -b fn_JSON_EXTRACT

通过 Google 了解与交叉验证 NebulaGraph 内部使用的 utils 库,知道应该用 folly::parseJson 把字符串读成 folly::dynamic。再 cast 成 NebulaGraph 内置的 Map() 类型。最后,借助于 Stack Overflow/GitHub Copilot,我终于完成了第一个版本的代码修改。

调试代码

我兴冲冲地改好了第一版的代码,信心满满地开始编译!实际上,因为我是 CPP 新手,即使在 Copilot 加持下,我的代码还是花了好几次修改才通过编译。

编译之后,我用 gdb 把修改了的 graphd 启动起来。用 console 发起 JSON_EXTRACT 的函数调用。先调通了期待中的效果,并试着跑几种异常的输入。在发现新问题、修改、编译、调试的几轮循环下让代码达到了期望的状态。

这时候,就该把代码提交到远端 GitHub 请项目的资深贡献者帮忙 review 啦!

提交 PR

PR(Pull Request)是 GitHub 中方便多人代码协作、代码审查中的一种方式。它通过把一个 repo 下的分支与这个审查协作的实例(PR)做映射,得到一个项目下唯一的 PR 号码之后,生成单独的网页。在这个网页下,我们可以做不同贡献者之间的交流和后续的代码更新。这个过程中,代码提交者们可以一直在这个分支上不断提交代码直到代码的状态被各方同意 approve,再合并 merge 到目的分支中。

这个过程可以分为:

  • 创建 GitHub 上远程的个人开发分支;
  • 基于分支创建目标项目仓库中的 PR;
  • 在 PR 中协作、讨论、不断再次提交到开发分支直到多方达到合并、或者关闭的共识;

提交到个人远程分支

在这一步骤里,我们要把当前的本地提交的 commit 提交到自己的 GitHub 分叉之中。

commit 本地修改

首先,确认本地的修改是否都是期待中的:

# 先确定修改的文件
$ git status
# 再看看修改的内容
$ git diff

再 commit,这时候是在本地仓库提交 commit:

# 添加所有当前目录(. 这个点表示当前目录)修改过的文件为待 commit
$ git add .
# 然后我们可以看一下状态,这些修改的文件状态已经不同了
$ git status
# 最后,提交在本地仓库,并用 -m 参数指定单行的 commit message
$ git commit -m "feat: introduce function JSON_EXTRACT"

提交到自己远程的分支

在提交之前,要确保自己的 GitHub 账号之下确实存在 NebulaGraph 代码仓库的分叉 fork。比如,我的 GitHub 账号是 wey-gu,那么我对 https://github.com/vesoft-inc/nebula 的分叉应该就是 https://github.com/wey-gu/nebula 。

如果还没有自己的分叉,可以直接在 https://github.com/vesoft-inc/nebula 上点击右上角的 Fork,创建自己的分叉仓库。

当远程的个人分叉存在之后,我们可以把代码提交上去:

# 添加一个新的远程仓库叫 wey
git remote add wey git@github.com:wey-gu/nebula.git
# 提交 JSON_EXTRACT 分支到 wey 这个 remote 仓库
git push wey JSON_EXTRACT

在个人远程分叉分支上创建 PR

这时候,我们访问这个远程分支:https://github.com/wey-gu/nebula/tree/fn_JSON_EXTRACT,就能找到 Open PR 的入口:

点击 Open pull request 按钮,进入到创建 PR 的界面了,这和在一般的论坛里提交一个帖子是很类似的:

提交之后,我们可以等待、或者邀请其他人来做代码的审查 review。往往,开源项目的贡献者们会从他们的各自角度给出代码修改、优化的建议。经过几轮的代码修改、讨论后,这时候代码会达到最佳的状态。

在这些审查者中,除了社区的贡献者(人类)之外,还有自动化的机器人。它们会在代码库中自动化地通过持续集成 CI 的方式运行自动化的审查工作,可能包括以下几种:

  • CLA:Contributor License Agreement,贡献者许可协议。PR 作者在首次提交代码到项目时,所需签署的协议。因为代码将被提交到公共空间,这份协议的签署意味着作者同意代码被分享、复用、修改;
  • lint:代码风格检查,这也是最常见的 CI 任务;
  • test:各种层面的测试检查任务。

通常来说,所有自动化审查机器人执行的任务全都通过后,贡献的代码状态才能被认为是可合并的。不出意外,我首次提交的代码果然有测试的失败提示。

调试 CI 测试代码

NebulaGraph 里所有的 CI 测试代码都能在本地被触发。当然,它们都有被单独触发的方式。我们需要掌握如何单独触发某个测试,而不是在每次修改一个小的测试修复、提交到服务器,就等着 CI 做全量的运行,这样会浪费掉几十分钟。

CTest

本次 PR 提交中,我修改的函数代码同一层级下的单元测试 CTest 就有问题。问题发生的原因有多种,可能是测试代码本身、代码变更破坏了原来的测试用例、测试用例发现代码修改本身的问题。

我们要根据 CTest 失败的报错进行排查和代码修改。再编译代码,在本地运行一下这个失败的用例:

# 我们需要进入到我们的编译容器内部的 build 目录下
$ docker exec -ti nebula_dev bash
$ cd build
# 在 -DENABLE_TESTING=ON 之中编译,如果之前的编译 job 数下内存已经跑满了的话,这次可以把 job 数调小一点,因为开启测试会占用更多内存
$ cmake -DCMAKE_CXX_COMPILER=$TOOLSET_CLANG_DIR/bin/g++ -DCMAKE_C_COMPILER=$TOOLSET_CLANG_DIR/bin/gcc -DENABLE_WERROR=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTING=ON ..
$ make -j 48

# 可以看到编译成功了 CTest 的单元测试二进制可执行文件
# [100%] Linking CXX executable ../../../../bin/test/function_manager_test
# [100%] Built target function_manager_test

# 执行重新修改过的单元测试!
$ bin/test/function_manager_test

[==========] Running 11 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 11 tests from FunctionManagerTest
[ RUN      ] FunctionManagerTest.testNull
[       OK ] FunctionManagerTest.testNull (0 ms)
[ RUN      ] FunctionManagerTest.functionCall
W20221020 23:35:18.579897 28679 Map.cpp:77] JSON_EXTRACT nested layer 1: Map can be populated only by Bool, Double, Int, String value and null, now trying to parse from: object
[       OK ] FunctionManagerTest.functionCall (2 ms)
[ RUN      ] FunctionManagerTest.time
[       OK ] FunctionManagerTest.time (0 ms)
[ RUN      ] FunctionManagerTest.returnType
[       OK ] FunctionManagerTest.returnType (0 ms)
[ RUN      ] FunctionManagerTest.SchemaRelated
[       OK ] FunctionManagerTest.SchemaRelated (0 ms)
[ RUN      ] FunctionManagerTest.ScalarFunctionTest
[       OK ] FunctionManagerTest.ScalarFunctionTest (0 ms)
[ RUN      ] FunctionManagerTest.ListFunctionTest
[       OK ] FunctionManagerTest.ListFunctionTest (0 ms)
[ RUN      ] FunctionManagerTest.duplicateEdgesORVerticesInPath
[       OK ] FunctionManagerTest.duplicateEdgesORVerticesInPath (0 ms)
[ RUN      ] FunctionManagerTest.ReversePath
[       OK ] FunctionManagerTest.ReversePath (0 ms)
[ RUN      ] FunctionManagerTest.DataSetRowCol
[       OK ] FunctionManagerTest.DataSetRowCol (0 ms)
[ RUN      ] FunctionManagerTest.PurityTest
[       OK ] FunctionManagerTest.PurityTest (0 ms)
[----------] 11 tests from FunctionManagerTest (5 ms total)

[----------] Global test environment tear-down
[==========] 11 tests from 1 test suite ran. (5 ms total)
[  PASSED  ] 11 tests.

成功!

将新的更改提交到远程分支上,在 PR 的网页中,我们可以看到 CI 已经在新的提交的触发下重新编译、执行了。过一会儿全部 pass,我开始兴高采烈地等待着 2 位以上的审查者帮忙批准代码,最后合并它!

但是,我收到了新的建议:

另一位贡献者请我添加 TCK 的测试用例。

TCK

TCK 的全称是 The Cypher Technology Compatibility Kit,它是 NebulaGraph 从 openCypher 社区继承演进而来的一套测试框架,并用 Python 做测试用例格式兼容的实现。

它的优雅在于,我们可以像写英语一样去描述我们想实现的端到端功能测试用例,像这样!

# tests/tck/features/function/json_extract.feature
Feature: json_extract Function

  Background:
    Test json_extract function

  Scenario: Test Positive Cases
    When executing query:
      """
      YIELD JSON_EXTRACT('{"a": "foo", "b": 0.2, "c": true}') AS result;
      """
    Then the result should be, in any order:
      | result                      |
      | {a: "foo", b: 0.2, c: true} |
    When executing query:
      """
      YIELD JSON_EXTRACT('{"a": 1, "b": {}, "c": {"d": true}}') AS result;
      """
    Then the result should be, in any order:
      | result                      |
      | {a: 1, b: {}, c: {d: true}} |
    When executing query:
      """
      YIELD JSON_EXTRACT('{}') AS result;
      """
    Then the result should be, in any order:
      | result |
      | {}     |

在添加了自己的一个新的 tck 测试用例文本文件之后,我们只需要在测试文件中临时增加标签,并在执行的时候指定标签,就可以单独执行新增的 tck 测试用例了:

# 还是在编译容器内部,进入到 tests 目录下
cd ../tests
# 安装 tck 测试所需依赖
python3 -m pip install -r requirements.txt
python3 -m pip install nebula3-python==3.1.0
# 运行一个单独为 tck 测试准备的集群
make CONTAINERIZED=true ENABLE_SSL=true CA_SIGNED=true up
# 给 tests/tck/features/function/json_extract.feature 以@开头第一行加上标签,比如 @wey
vi tests/tck/features/function/json_extract.feature
# 执行 pytest (包含 tck 用例),因为制定了 -m "wey",只有 tests/tck/features/function/json_extract.feature 会被执行
python3 -m pytest -m "wey"
# 关闭 pytest 所依赖的集群
make CONTAINERIZED=true ENABLE_SSL=true CA_SIGNED=true down

再次邀请 review

待我们把需要的测试调通、再次提交 PR 并且 CI 用例全都通过之后,我们可以再次邀请之前帮助审查代码的同学做做最后的查看,如果一切都顺利,代码就会被合并了!

就这样,我的第一个 CPP PR 终于被合并成功,大家能看到我留在 NebulaGraph 中的代码了。

延伸阅读:

  1. 基于 BDD 理论的 NebulaGraph 集成测试框架重构,https://nebula-graph.com.cn/posts/bdd-testing-practice
  2. 如何向 NebulaGraph 增加一个测试用例,https://nebula-graph.com.cn/posts/bdd-testing-practice-add-test-case
  3. NebulaGraph 文档之架构介绍,https://docs.nebula-graph.com.cn/master/1.introduction/3.nebula-graph-architecture/1.architecture-overview/
  4. NebulaGraph 源码解读系列,https://www.nebula-graph.com.cn/posts/nebula-graph-source-code-reading-00

谢谢你读完本文 (///▽///)

如果你想尝鲜图数据库 NebulaGraph,记得去 GitHub 下载、使用、(з)-☆ star 它 -> GitHub;和其他的 NebulaGraph 用户一起交流图数据库技术和应用技能,留下「你的名片」一起玩耍呀~

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

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

相关文章

synchronized详解

什么是线程安全问题? 简单来说就是当多个线程同时访问某个方法时,这个方法无法按照我们预期的行为来执行,那么我们认为这个方法是线程不安全的 导致线程不安全的主要原因 1.原子性 什么是原子性 比如在数据库事务的ACID特性中 当前操作中包含多个数据库事务操…

DolphinDB 诚挚招募实施伙伴

随着 DolphinDB 业务发展,为满足迅速增长的客户需求,我们现正式启动“实施伙伴招募计划”。DolphinDB 客户已经涵盖7家Top 10券商、头部公募及私募基金、知名银行、交易所、世界500强制造业客户、标杆能源企业等,我们非常期待和欢迎实施伙伴们…

毕业设计 基于stm32的智能平衡小车 - 单片机 物联网嵌入式

文章目录0 前言1 项目背景2 设计思路3 硬件设计4 软件设计4.1 核心PID控制4.2 直立控制程序设计4.3 速度控制程序设计4.4 方向控制程序设计4.5 关键代码5 最后0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点…

32、Java高级特性——日期操作类、Date类、SimpleDateFormat类、Calendar类

目录 一、日期操作类 二、Date类 1、Date类中的构造方法 2、获取系统当前时间 三、SimpleDateFormat类 1、SimpleDateFormat类中的构造方法 2、format(Date date,StringBuffer toAppendTo,FieldPosition pos)方法 四、Calendar类 1、Calendar类中的构造方法 2、Cal…

[附源码]Python计算机毕业设计SSM家政信息管理平台(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

Removing the Bias of Integral Pose Regression 阅读笔记

消除积分姿态回归的偏差 ICCV2021 论文链接 补充材料链接 参考链接 摘要: 尽管姿态估计的回归法更直观,但由于热图法的优越性能,使其在2D人体姿态估计中占主导地位。积分回归法在架构上使用隐式热图,拉近了热图法与回归法。这就…

GraalVM + Springboot3 + IDEA 在widow10 上完成构建本地化服务

GraalVM是开发人员编写和执行Java代码的工具。具体来说,GraalVM是由Oracle创建的Java虚拟机(JVM)和Java开发工具包(JDK)。它是一个高性能的运行时,可以提高应用程序的性能和效率。 GraalVM的目标包括&…

【数据结构】哈希表

目录 一、哈希函数的引入 二、解决哈希冲突的思路 2.1基于闭散列的思路 2.2基于开散列的思路 2.3负载因子 三、关于哈希函数的设计 四、基于拉链法实现哈希表 4.1哈希表的内部构造 4.2插入操作 4.3扩容操作 4.4搜索操作 4.5删除操作 哈希表其实就是基于数组衍生而来…

深度解读面试题:链表中环的入口结点(附代码,可过在线OJ)

在解读“链表中环的入口结点”前,我认为有必要明白关于它的一些用于打基础的问题(相交链表、判断链表中是否存在环) 相交链表 题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点…

快收藏!!整理了100个Python小技巧!!

下面,我就给大家分享100个Python小技巧,帮助大家更好的了解和学习Python,欢迎收藏、关注,点赞支持! ▍1、for循环中的else条件 这是一个for-else方法,循环遍历列表时使用else语句。下面举个例子&#xff…

根据平均值列出记录

AANSI SQL包括几个聚合函数,使您可以对一组值进行计算以将其结果作为单个值返回。他们包括Count(), Min(), Max(), Sum() and AVG(),以及其他。默认情况下,聚合函数适用于所有行,但是您可以通过将WHERE子句应用于SELECT语句来缩小字段的范围。…

直冲云霄,阿里大牛耗时49天整理12W字面试手册,押题准确率直冲95%

很多人都想进字节做开发,不论是技术还是薪资、福利都算得上TOP级~ 7月底官方再次启动扩招,发布了1200+后端工程师岗位! 那么本批有哪些优质岗位可选择?薪资待遇如何? 下面给大家列出几类具体的岗位要求&a…

软件项目管理指南:定义、5大过程、估算及进度管理方法等

本文将分享:1、软件项目管理的定义;2、软件项目管理的过程步骤;3、软件项目管理的内容;4、软件项目估算与进度管理方法;5、软件开发各生命周期阶段与文档、角色间的关系;6、软件开发项目中的各大角色职能&a…

深度学习-第P1周——实现mnist手写数字识别

深度学习-第P1周——实现mnist手写数字识别深度学习-第P1周——实现mnist手写数字识别一、前言二、我的环境三、前期工作1、导入依赖项并设置GPU2、导入数据集3、数据可视化四、构建简单的CNN网络五、训练模型1、设置超参数2、编写训练函数3、编写测试函数4、正式训练六、结果可…

ADSP-21489的图形化编程详解(7:延时、增益、分频、反馈、响度)

延时 21489 可以做延时,音频高手会运用此项算法来增强音效,我们做个最简单的,让大家知道怎么用它,至于怎么样嵌入到自己的系统里实现更好的效果,则需要各位调音师专业的耳朵来判断,调音无上限!…

MySQL之索引及其背后的数据结构

✨博客主页: 荣 ✨系列专栏: MySQL ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 索引的介绍1. 什么是索引2. 索引的使用二. 索引背后的数据结构1. 考虑使用哈希表2. 二叉搜索树3. N叉搜索树(B树, B树)4. 注意事项一. 索引的介绍 1. 什么是索引 索引 (Index) 是帮助…

[激光原理与应用-39]:《光电检测技术-6》- 光干涉的原理与基础

目录 第1章 概述 1.1 什么是光干涉 1.2 产生干涉的必要条件 1.3 非相干光 - 自发辐射无法产生干涉 1.4 相干光 - 受激辐射 1.5 时间相干性 1.6 空间相干性 它山之石 第1章 概述 1.1 什么是光干涉 它是指因两束光波相遇而引起光的强度重新分布的现象。 指两列或两列以上…

Verilog入门学习笔记:Verilog基础语法梳理

无论是学IC设计还是FPGA开发,Verilog都是最基本、最重要的必备技能。但任何一门编程语言的掌握都需要长期学习。并不是简简单单的随便读几本书,随便动动脑筋那么简单。Verilog是一门基于硬件的独特语言,由于它最终所实现的数字电路&#xff0…

基于AVDTP信令分析蓝牙音频启动流程

前言 公司项目edifier那边需要在原来音频SBC,AAC基础上增加LHDC5.0编码,在打通lhdc协议栈之前,学习记录一番AVDTP音频服务流程。 一、AVDTP音频流基础知识 分析音频流程首先应具备的最简单基础概念知识:AVDTP信令signal,流端点se…

【JVM】垃圾回收机制详解(GC)

目录一.GC的作用区域二.关于对象是否可回收1.可达性分析算法和引用计数算法2.四种引用类型三.垃圾收集算法1.标记-清除算法2.复制算法3.标记-整理算法4.分代收集算法四.轻GC(Minor GC)和重GC(Full GC)一.GC的作用区域 可以看jvm详解之后,再来理解这篇文章更好 堆和…