关于MongoShake项目说明:

https://github.com/alibaba/MongoShake/wiki

MongoShake发行版下载:

https://github.com/alibaba/MongoShake/releases

MongoShake遇到的问题FAQ:

https://github.com/alibaba/MongoShake/wiki/FAQ

  • 安装

项目相关软件包路径为/root/MongoShake,部署之前确保目标端实例已使用源端实例全备mongodump文件进行还原,MongoShake是通过抽取源端oplog应用到目标端以达到增量的同步,利用的是oplog中DML操作的幂等特性,部署项目需要用到的组件有:

1.go go1.12.5(二进制)

2.MongoShake 2.0(源码包)或MongoShake 2.0(二进制)

go的安装

MongoShake是基于go语言进行开发的,在进行源码编译的时候需要go的运行环境。本例的go语言环境配置使用的是二进制包方式。如果使用的MongoShake二进制包,解压即可得到MongoShake执行命令,则无需要安装go环境。

  1. 二进制包下载

# wget https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz

  1. 解压

# tar zxf go1.12.5.linux-amd64.tar.gz

# cp go /usr/local/go1.12 –ra

  1. 配置环境变量

# vim /etc/profile.d/go.sh

添加配置:

export GOROOT=/usr/local/go1.12

export GOPATH=/go

export PATH=/go/bin:$GOROOT/bin:$PATH

配置生效:

# . /etc/profile.d/go.sh

  1. 查看当前go环境

# go version

go version go1.12.5 linux/amd64

至此,go语言环境安装完成。

MongoShake的安装(源码)

因为MongoShake的项目源码是放在github上的,所以获取源码的方式可以通过git进行,也可以到项目地址下载zip源码压缩包。

  1. Git安装

首先确保安装了git,如果没有安装则使用yum进行安装。

# git version

git version 2.21.0

yum安装git:

# yum install git -y

  1. 源码包获取

因为国内环境访问github的速度较慢,所以可以先修改hosts文件,通过绑定host的方式来访问,可以加快对github访问的速度。

# vim /etc/hosts

文末添加:

151.101.44.249 github.global.ssl.fastly.net

192.30.253.113 github.com

103.245.222.133 assets-cdn.github.com

23.235.47.133 assets-cdn.github.com

203.208.39.104 assets-cdn.github.com

204.232.175.78 documentcloud.github.com

204.232.175.94 gist.github.com

107.21.116.220 help.github.com

207.97.227.252 nodeload.github.com

199.27.76.130 raw.github.com

107.22.3.110 status.github.com

204.232.175.78 training.github.com

207.97.227.243 www.github.com

185.31.16.184 github.global.ssl.fastly.net

185.31.18.133 avatars0.githubusercontent.com

185.31.19.133 avatars1.githubusercontent.com

192.30.253.120 codeload.github.com

注意每行行尾不能出现空格,否则绑定的host不生效!

获取源代码:

# mkdir /go/src/github.com/

# cd /go/src/github.com/

# git clone https://github.com/alibaba/MongoShake.git

Cloning into 'MongoShake'...

remote: Enumerating objects: 60, done.

remote: Counting objects: 100% (60/60), done.

remote: Compressing objects: 100% (49/49), done.

remote: Total 1341 (delta 14), reused 29 (delta 10), pack-reused 1281

Receiving objects: 100% (1341/1341), 18.56 MiB | 24.00 KiB/s, done.

Resolving deltas: 100% (694/694), done.

  1. 依赖包安装

MongoShake使用的是govendor解决依赖包问题,在编译MongoShake之前需要确认依赖包都已安装,而首先安装的是govendor。

# go get -u github.com/kardianos/govendor

# govendor -version

v1.0.9

govendor安装完成之后,开始安装MongoShake的依赖包。

# GOPATH= /go/src/github.com/MongoShake/

# cd /go/src/github.com/MongoShake/src/vendor

# govendor sync

  1. 编译

依赖包安装完成之后,则可以开始进行编译了。

# cd /go/src/github.com/MongoShake/

# ./build.sh

[ BUILD RELEASE ]

Build collector

mongoshake/collector/configure

vendor/github.com/eapache/queue

vendor/github.com/vinllen/log4go

vendor/github.com/gugemichael/nimo4go

vendor/github.com/vinllen/mgo/internal/json

vendor/github.com/vinllen/mgo/internal/scram

vendor/github.com/nightlyone/lockfile

vendor/github.com/davecgh/go-spew/spew

vendor/github.com/eapache/go-resiliency/breaker

vendor/github.com/golang/snappy

vendor/github.com/vinllen/mgo/bson

vendor/github.com/eapache/go-xerial-snappy

vendor/github.com/pierrec/lz4/internal/xxh32

vendor/github.com/pierrec/lz4

vendor/github.com/rcrowley/go-metrics

vendor/github.com/vinllen/mgo

mongoshake/oplog

vendor/github.com/Shopify/sarama

mongoshake/common

mongoshake/dbpool

mongoshake/collector/ckpt

mongoshake/executor

mongoshake/quorum

mongoshake/tunnel/kafka

mongoshake/tunnel

mongoshake/modules

mongoshake/collector

command-line-arguments

develop,7858a69529c89b04207a9913df7ac0e082b256af,release,go1.12.5,2019-06-19_13:19:39

Build receiver

mongoshake/receiver/configure

mongoshake/receiver

command-line-arguments

develop,7858a69529c89b04207a9913df7ac0e082b256af,release,go1.12.5,2019-06-19_13:19:39

至此,MongoShake编译完成。

MongoShake的安装(二进制)

因为获取依赖包访问github的速度很慢,所以项目也提供了编译好的二进制包,二进制包会使用相对比较稳定的源码进行编译,如果允许的话还是使用源码包进行编译。

  1. 二进制下载

# wget https://github.com/alibaba/MongoShake/releases/download/release-v2.0.0-20190619/mongoshake-2.0.tar.gz

  1. 解压

# tar zxf mongoshake-2.0.tar.gz -C /go/src/github.com/MongoShake-2.0

# ls /go/src/github.com/MongoShake-2.0/

ChangeLog  collector  collector.conf  hypervisor  mongoshake-stat  receiver  start.sh  stop.sh

至此,MongoShake的配置就完成了。

  • 配置

MongoShake同步任务配置根据原先项目的设计做了自定义配置,采用每个项目对应一个目录的方式进行,这样可以方便管理与维护,可以参考已成功完成迁移的实例bbs进行修改,本例以bbs配置进行修改演示,由于之前bbs项目是基于MongoShake 1.4.6版本完成的,collector配置文件与2.0稍有不同,可以参照2.0版本进行修改。

查看bbs对应目录结构

# tree /go/src/github.com/MongoShake/bbs/ -L 1

/go/src/github.com/MongoShake/bbs/

├── bbs_collector

├── bbs.conf

├── bbs_start.sh

├── bbs_stop.sh

├── diagnostic

└── logs

其中:

bbs_collector:MongoShake拉取oplog的主执行程序,通过默认collector复制重命名得到;

bbs.conf:MongoShake配置文件,针对bbs的配置;

bbs_start.sh:项目启动的shell脚本,结合默认的hypervisor启动项目;

bbs_stop.sh:项目停止的shell脚本;

logs:存放hypervisor日志和bbs_collector运行日志。

配置项目目录

本例以ask项目为例。

# mkdir -pv /go/src/github.com/MongoShake-2.0/ask/logs

# cd /go/src/github.com/MongoShake-2.0/ask

拷贝BBS相应的文件,并重命名。

# cp ../../MongoShake/bbs/bbs*  /go/src/github.com/MongoShake-2.0/ask

# mv bbs_collector  ask_collector

# mv bbs.conf  ask.conf

# mv bbs_start.sh  ask_start.sh

# mv bbs_stop.sh  ask_stop.sh

配置文件修改

修改ask.conf配置文件,也可以基于MongoShake 2.0版本默认的配置文件进行修改。

为了便于说明,先去除相应的注释与空行。

# cat ask.conf | grep -v "^$" | grep -v "^#"

mongo_urls = mongodb://root:sdfsdfsdf@10.10.111.1:27018,10.10.111.2:27230 #源端

collector.id = askSet

sync_mode = oplog

checkpoint.interval = 5000

http_profile = 20230

system_profile = 20231

log_level = info

log_file = askSet.log

log_buffer = true

filter.namespace.black =

filter.namespace.white =

oplog.gids =

shard_key = collection

syncer.reader.buffer_time = 1

worker = 1

worker.batch_queue_size = 32

adaptive.batching_max_size = 8192

fetcher.buffer_capacity = 128

worker.oplog_compressor = none

tunnel = direct

tunnel.address = mongodb://root:csdfsdfsdfsd@172.20.16.1:27017 #目标端

context.storage = database

context.address = ckpt_default

context.start_position = 2019-06-18T09:20:00Z

master_quorum = false

transform.namespace =

dbref = false

replayer.dml_only = false

replayer.executor = 1

replayer.executor.upsert = true

replayer.executor.insert_on_dup_update = true

replayer.conflict_write_to = none

replayer.durable = true

replayer.collection_parallel = 3

replayer.document_parallel = 4

replayer.document_batch_size = 256

replayer.collection_drop = false

主要配置选项如下:

mongo_urls:配置源端实例mongodb连接信息,如果是副本集,将所有mongodb节点都写上,格式如上;

collector.id:项目进程名称,能唯一标识即可;

sync_mode:同步模式,2.0开始支持全量+增量,默认为oplog,即增量,对于一些较小的实例可以采用all方式;

http_profile:项目进程监控端口,确保唯一即可;

system_profile:项目进程监控端口,确保唯一即可;

log_file:collector日志文件名称,会在logs目录下生成;

tunnel.address:目标端mongodb实例连接信息,格式如上;

context.start_position:开始读取oplog的时间点,是UTC格式,比CST晚8小时,即开始同步时间需要比生成mongodump全备文件早8个小时开始同步,才能确保在oplog增量同步时覆盖mongodump备份这段时间的操作;

replayer.dml_only:是否只同步DML,如果需要同步DDL,则指定选项false。

配置文件其他选项保持默认就可以。

启动文件修改

修改ask_start.sh文件。

# vim ask_start.sh

#catalog=$(dirname "$0")

#cd "${catalog}"/../ || exit 1

if [ $# != 1 ] ; then

echo "USAGE: $0 [conf]"

exit 0

fi

# conf

hypervisor_path="/go/src/github.com/MongoShake-2.0/hypervisor"

collector_path="/go/src/github.com/ MongoShake-2.0/ask/ask_collector"

conf_path="/go/src/github.com/ MongoShake-2.0/ask/ask.conf"

logs_dir="/go/src/github.com/ MongoShake-2.0/ask/logs/"

task_name="askSet_collector"

if [ "Darwin" == "$(uname -s)" ];then

printf "\\nWARNING !!! MacOs doesn't supply to use this script, please use \"./%s -conf=config_file_name\" manual command to run\\n" "$nam

e"

exit 1

fi

GOMAXPROCS=0

if [ $GOMAXPROCS != 0 ] ; then

${hypervisor_path} --daemon --exec="GOMAXPROCS=$GOMAXPROCS ${collector_path} -conf=$1 2>&1 1>> $task_name.output" 1>>${logs_dir}hypervisor

.output 2>&1

else

${hypervisor_path} --daemon --exec="${collector_path} -conf=$1 2>&1 1>> $task_name.output" 1>>${logs_dir}hypervisor.output 2>&1

Fi

其中涉及修改的选项如下,配置参考以上说明:

hypervisor_path:hypervisor路径,以项目默认为主;

collector_path=:ask对应的collector执行文件;

conf_path=:ask对应的配置文件;

logs_dir=:ask对应的日志文件路径;

task_name=:ask任务名称。

至此,ask项目配置已完成。

  • 开启同步

本例中ask项目源端开始mongodump生成全备文件时的时间为:

2019-06-18T17:24:25.699+0800

所以ask配置的oplog同步时间为:

2019-06-18T09:20:00Z

在确保ask mongodump全备文件通过mongorestore还原至目标端实例成功完成之后,使用shell启动文件开始进行同步。

# cd /go/src/github.com/ MongoShake-2.0/ask/

# ./ask_start.sh ask.conf

查看ask_collector日志状态。

[2019/06/19 15:35:22 CST] [INFO] [common.(*ReplicationMetric).startup.func1:137] [name=askReplset, filter=2, get=211, consume=209, apply=209, failed_times=0, success=209, tps=0, ckpt_times=0, retransimit_times=0, tunnel_traffic=29KB, lsn_ckpt={0,1970-01-01 08:00:00}, lsn_ack={1560929701,2019-06-19 15:35:01}]

[2019/06/19 15:35:23 CST] [INFO] [collector.(*OplogSyncer).calculateWorkerLowestCheckpoint:122] worker offset [6704142017150058497] use lowest 6704142017150058497

[2019/06/19 15:35:23 CST] [INFO] [executor.(*Executor).doSync:231] Replayer-0 Executor-0 doSync oplogRecords received[1] merged[1]. merge to 100.00% chunks

[2019/06/19 15:35:23 CST] [INFO] [collector.(*Worker).transfer:179] Collector-worker-0 transfer retransmit:false send [1] logs. reply_acked [6704142107344371713], list_unack [0]

[2019/06/19 15:35:23 CST] [INFO] [ckpt.(*MongoCheckpoint).Insert:179] Record new checkpoint success [1560929701]

[2019/06/19 15:35:23 CST] [INFO] [collector.(*OplogSyncer).checkpoint:54] CheckpointOperation write success. updated from 6704125962562306050(1560925963) to 6704142017150058497(1560929701)

[2019/06/19 15:35:27 CST] [INFO] [common.(*ReplicationMetric).startup.func1:137] [name=askReplset, filter=3, get=213, consume=210, apply=210, failed_times=0, success=210, tps=0, ckpt_times=1, retransimit_times=0, tunnel_traffic=29KB, lsn_ckpt={1560929701,2019-06-19 15:35:01}, lsn_ack={1560929722,2019-06-19 15:35:22}]

[2019/06/19 15:35:30 CST] [INFO] [collector.(*OplogSyncer).calculateWorkerLowestCheckpoint:122] worker offset [6704142107344371713] use lowest 6704142107344371713

[2019/06/19 15:35:30 CST] [INFO] [executor.(*Executor).doSync:231] Replayer-0 Executor-0 doSync oplogRecords received[1] merged[1]. merge to 100.00% chunks

查看运行监控端口状态。

# netstat -lntup | grep ask

tcp6       0      0 :::20231                :::*                    LISTEN      5312/ask_collector

tcp6       0      0 :::20230                :::*                    LISTEN      5312/ask_collector

从collector日志可以看出实时的oplog增量检查点checkpoint,而这个checkpoint信息由MongoShake默认创建mongoshake数据库和ckpt_default集合来记录,记录的是当前增量同步oplog位置的时间信息。

cmsReplset:SECONDARY> db.ckpt_default.find()

{ "_id" : ObjectId("5d09f22ccee40923c7e59a50"), "name" : "cmsReplset", "ckpt" : Timestamp(1560931886, 3) }

  • 停止同步

使用ask_stop.sh文件停止ask项目的同步。

# ./ask_stop.sh askSet.pid

  • 数据量校验

为了校验MongoDB同步后各个数据库中集合文档数,这里编写了一个shell脚本mongodb_compare.sh,脚本已同步到DBA SVN中迁移百度云文件夹下,使用方法可以通过直接运行脚本文件得到。

# ./mongodb_compare.sh

USAGE:./mongodb_compare.sh 'src_MongoDB_Primary_ip:port' 'dst_MongoDB_Primary_ip:port' [db_name for check...]

Example: ./mongodb_compare.sh '192.168.58.3:27017' '172.20.3.6:27017' [db_name,[db_name]...]

这里演示校验ask实例源端和目标端集合文档数。

首先需确认当前mongoshell执行命令mongo的绝对路径,并修改脚本中涉及mongo命令的地方,假设当前mongo命令的绝对路径为/usr/local/mongodb3.4/bin/mongo,则修改mongodb_compare.sh脚本中涉及mongo命令处,并保存。如下:

src_conn="/usr/local/mongodb3.4/bin/mongo $src_ins -u$src_user -p$src_pwd --quiet --authenticationDatabase admin -eval "

dst_conn="/usr/local/mongodb3.4/bin/mongo $dst_ins -u$dst_user -p$dst_pwd --quiet --authenticationDatabase admin -eval "

运行脚本进行校检。(脚本见下一篇推文)

# ./mongodb_compare.sh '10.10.111.1:27017' '172.20.16.1:27017'

Enter the src_ins:10.10.111.1:27017 conn user: root       # 输入源端实例连接用户

Enter the src_ins:10.10.111.1:27017 user password:        # 输入源端实例连接用户密码

Enter the dst_ins:172.20.16.1:27017 conn user: root        # 输入目标端实例连接用户

Enter the dst_ins:172.20.16.1:27017 password:              # 输入目标端实例连接用户密码

10.10.111.1:27017 <----------> 172.20.16.1:27017

==============================================================

DB:cms

autoincre_system docs: 3 <----------> 3

cms_mall_product docs: 3824 <----------> 3824

pageslog docs: 58043 <----------> 58043

pageslog_list docs: 58043 <----------> 58043

system.indexes docs: 7 <----------> 0 different!

system.profile docs: 1701 <----------> 0 different!

system.users docs: 2 <----------> 2

test docs: 1 <----------> 1

==============================================================

DB:test

system.indexes docs: 2 <----------> 0 different!

system.profile docs: 0 <----------> 0

t docs: 3 <----------> 3

test docs: 1 <----------> 1

==============================================================

DB:OpenPlatform

a docs: 0 <----------> 0

app_log_10 docs: 450904 <----------> 450904

app_log_11 docs: 406611 <----------> 406611

app_log_12 docs: 160810 <----------> 160810

app_log_5 docs: 1324893 <----------> 1324893

app_log_6 docs: 965174 <----------> 965174

app_log_7 docs: 855134 <----------> 855134

app_log_8 docs: 715728 <----------> 715728

app_log_9 docs: 639511 <----------> 639511

auth_info docs: 247083 <----------> 247083

feedback docs: 49 <----------> 49

qdms docs: 0 <----------> 0

system.indexes docs: 13 <----------> 0 different!

system.profile docs: 1748 <----------> 0 different!

user_session docs: 24200 <----------> 24200

如果有文档数不同的集合则脚本通过红色显示,如果只是system集合红色显示,代表正常,因为system集合是不做同步的,所以其他集合出现红色时则证明两端集合的文档数不一致。在确保源端与目标端实例除system集合外所有集合的文档数一致后则表明数据一致。

当源端集合文档数与目标端不一致时,则以源端数据为主进行修复。主要修复步骤:

  1. 首先停止MongoShake对应项目的同步;
  2. 源端使用mongodump导出不一致的文档;
  3. 目标端通过mongorestore还原不一致的文档;
  4. 重新开启MongoShake对应项目的同步。

导出文档命令:

# mongodump -h 10.10.111.1 --port 28230 -uroot –p’root_password’ --authenticationDatabase admin –d cms –c pageslog –o ./cms_pageslog

导入文档命令:

# mongorestore -h 172.20.16.1 --port 27017 -uroot -p’root_password’ --authenticationDatabase admin -d cms -c pageslog  --drop  ./pageslog.bson

  • 用户迁移

如果源实例的admin数据库包含名为root的用户,并且该root用户为实例对应的拥有root角色超级权限的用户,则在使用mongorestore进行还原恢复至百度云mongodb实例时不能包括admin数据库,这会导致百度云实例上超级用户被修改,导致百度云后台技术错误,所以关于用户的迁移只能通过查找数据库连接配置文件对应项目中实例的连接用户,然后在百度云实例上admin或者相应数据库手动创建用户。

mongo上云迁移同步mongoshake相关推荐

  1. mysql 上云迁移方案_数据库上云迁移方案

    自建业务数据库正在面临的挑战 1. 有活动上线,压力突增,数据库却来不及扩容了: 2. 采用读写分离,刚更新的数据,却查询不到了: 3. 数据的主从复制经常中断: 4. 多台应用服务器用同一台数据库, ...

  2. 上云说丨上云迁移——快,准,稳!

    随着云计算的不断发展,政府部门.企业.银行等机构业务迁移至云端的需求越来越多,对迁移的高效稳定也有了更高的要求.浪潮云泽智能运维--业务迁移实施,可帮助用户实现高效(快).高成功率(准).稳定不影响业 ...

  3. 浪潮云说丨上云迁移实践

    随着云计算的快速发展,上云已成为数字化转型的重要手段,而上云迁移亦是其中的重要环节.为达成资源整合.统筹规划.数据互通共享的目的,某大数据局将整机迁移至浪潮云,从而进行高效一体化管理. 浪潮云上云迁移 ...

  4. 上云迁移-海量数据迁移解决方案

    摘要:传统数据存储在线下数据中心,存在成本高.运维难.性能难保障等等多方面的问题.阿里云提供闪电立方.OSS/NASImport.混合云存储阵列.镜像回源.302跳转.伪源站等六大解决方案为企业提供不 ...

  5. 政务云迁移实践 北明数科使用HyperMotion云迁移产品为某政府单位实施上云迁移项目,15天内完成近百套主机迁移

    项目概述 随着"十四五"大力推进"互联网+政务服务",政府信息化进入了一个新的发展阶段.政务信息化的目标是建设服务型政府,积极推进国家电子政务及云平台建设与应用 ...

  6. 天翼云从业认证(4.1)上云迁移实战

    了解天翼云服务器迁移工具的产品功能.产品优势.应用场景和操作流程: 天翼云服务器迁移工具 服务器迁移工具(CT-SMT Server Migration Tool)是天翼云为上云用户量身打造的零停机的 ...

  7. 一次数据库上云迁移性能下降的排查

    背景介绍: 某客户目前正在将本地的业务系统迁移上云,测试过程中发现后台运营系统,在rds上运行时间明显要比线下PC上自建数据库运行时间要慢1倍,导致客户系统割接延期的风险.用户线下一台PC服务器的性能 ...

  8. 金融行业云迁移实践 HyperMotion助力江苏农信银行实施金融专有云创新项目,实现跨地域,多网点,大数据量迁移上云

    项目概述 当前,互联网技术将经济社会带入了"平台经济"的时代.<银行业信息科技"十三五"发展规划监管指导意见>要求,"十三五"期 ...

  9. 叮咚买菜自建MongoDB上云实践

    随着近年来公有云技术及云基础设施的发展,越来越多的企业转为使用公有云来托管自己的服务.云数据库因为数据可靠性.资源弹性.运维便捷行,云上数据库服务也正成为企业数据管理的较好的选择. 本文以叮咚买菜自建 ...

  10. 重磅!腾讯云首次披露自研业务上云历程

    导语:传统行业转型的过程中,腾讯向来扮演的是数字化助手的角色,腾讯云作为帮助企业数字化转型的入口,也已经成为腾讯的"独角兽"业务.然而伴随着云业务的增长,腾讯内部业务如何上云,对于 ...

最新文章

  1. 老铁 666!快手上市暴涨 200%,超 4000 员工成为千万富翁
  2. 浅谈ButterKnife对Android性能的影响
  3. 架构师的英文缩写_架构师必备的20个英文缩写!看你知道几个?
  4. 读书随笔:The Book of Why——INTRODUCTION:MIND OVER DATA
  5. vue-cli学习入门_byKL
  6. [转载] 使用python完成冒泡排序_使用python实现-冒泡排序
  7. mysql bigint 转int_技术分享 | MySQL ?删库不跑路(建议收藏)
  8. 芒果广告不能用百度了,怎么办?
  9. 揭秘刷百度权重那些不为人知的事情
  10. js、html实现断点播放视频,视频资源在localStorage中
  11. 苹果Mac OS X 系统下屏幕截图快捷键
  12. 【BZOJ1503】郁闷的出纳员
  13. 用微信怎么定位别人手机位置
  14. org.springframework.dao.InvalidDataAccessApiUsageException:The given object has a null identifi的解决方案
  15. java基础-day08-面向对象(三) this关键字、继承、static关键字
  16. 端口号被占用:The Tomcat connector configured to listen on port “端口号“ failed to start.
  17. 一节计算机课作文500,难忘的一堂课作文500字5篇
  18. Pyinstaller 打包 Pytest项目及资源文件
  19. 2022深圳(软考中级)系统集成项目管理工程师报名
  20. 仿百度搜索显示下拉框(一)

热门文章

  1. CTRL键不能使用(非硬件问题)
  2. python语言标识符首字符不能是汉字_python二级电子教案 第2章 Python语言基本语法元素...
  3. 金融大数据分析平台Palantir Metropolis介绍
  4. python挑选以元音字母开头的单词,在Python中查找以特定字母开头的列表元素
  5. Android解决Can't create handler inside thread that has not called Looper.prepare()
  6. UE4-(蓝图)第一百二十课 贴花(蓝图生成示例开枪生成弹孔)
  7. STM32控制步进电机运三种方式控制源码详解:主从定时器+编码器闭环+GPIO模拟(基于【TB6600】【DRV8825】驱动器)
  8. 「镁客早报」微软总市值超过苹果亚马逊;Linux内核出现漏洞
  9. 【VUE/H5】H5调起数字键盘的坑,及手写移动端键盘代码
  10. 内存管理基础学习笔记 - 4.3 缺页中断处理 - handle_pte_fault