在 Raspberry Pi 4 上编译 64-bit 的 MongoDB Server

上次编译了 MongoDB 的 Embedded 版本,不过官方表示那个还只是一个实验中的程序,同时上次编译的还是 32-bit 的 Embedded 版本,因此这次就来编译一个真正可用的 64-bit 的 MongoDB 好了_(:3」∠)_

总的来说倒也没有想象中那么复杂,但是确实有几个可能踩到的的坑

首先我做的就是先让树莓派到 64-bit 环境中,可以参考我的这篇博客,让 Raspberry Pi 4 完全运行在 64-bit 模式下

确认 schroot 到 64-bit 环境中之后,同样的,通过 apt 安装必要的依赖

sudo apt install -y scons libssl-dev libffi-dev libcurl4-openssl-dev wget gcc g++ vim cmake python3 python3-pip

包括稍后会用到的 Python 的依赖

cat << EOF >~/build-requirements.txt
Cheetah3 # src/mongo/base/generate_error_codes.py
psutil
pymongo >= 3.0, != 3.6.0  # See PYTHON-1434, SERVER-34820
PyYAML >= 3.0.0
regex
requests >= 2.0.0
typing >= 3.6.4
EOF

sudo pip3 install -r ~/build-requirements.txt

在国内的话,可以指定使用清华大学 TUNA 镜像源

sudo pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r ~/build-requirements.txt

接着是编译 snappy ~

snappy 的源代码在其 GitHub Release 页面下载,https://github.com/google/snappy/releases,在写本文的时候,snappy 最新的 release 是在 17 年 8 月 25 号发布的 1.1.7,于是下载地址就是 https://github.com/google/snappy/archive/1.1.7.tar.gz

export SNAPPY_VER=1.1.7
wget https://github.com/google/snappy/archive/\${SNAPPY_VER}.tar.gz -O snappy-\${SNAPPY_VER}.tar.gz
tar xzf snappy-\${SNAPPY_VER}.tar.gz
cd snappy-\${SNAPPY_VER}
mkdir build && cd build
cmake -DBUILD_SHARED_LIBS=ON .. && make -j`nproc` && sudo make install

下一个则是 Mongo C Driver ♪(´ε` ) Mongo C Driver 的源代码也是在它的 GitHub Release 页面上,https://github.com/mongodb/mongo-c-driver/releases,在写这篇 post 的时候最新版本是 19 年 11 月 7 号发布的 1.15.2,下载链接则是 https://github.com/mongodb/mongo-c-driver/releases/download/1.15.2/mongo-c-driver-1.15.2.tar.gz

export MONGO_C_VER=1.15.2
wget https://github.com/mongodb/mongo-c-driver/releases/download/\${MONGO_C_VER}/mongo-c-driver-\${MONGO_C_VER}.tar.gz -O mongo-c-driver-\${MONGO_C_VER}.tar.gz
tar xzf mongo-c-driver-\${MONGO_C_VER}.tar.gz
cd mongo-c-driver-\${MONGO_C_VER}
mkdir release && cd release
cmake .. && make -j`nproc` && sudo make install

现在就是到了重头戏的地方了~编译 MongoDB~

MongoDB 的源代码则是在它的官网上下载,https://www.mongodb.com/download-center/community。在页面右侧会有一个 Download Source (tgz),下载到 Raspberry Pi 上即可~

截止这篇 post 写作的时候,MongoDB 在其官网上最新的版本是 4.2.1~于是

export MONGODB_VER=4.2.1
wget https://fastdl.mongodb.org/src/mongodb-src-r\${MONGODB_VER}.tar.gz -O mongodb-src-r${MONGODB_VER}.tar.gz
tar xzf mongodb-src-r\${MONGODB_VER}.tar.gz
cd mongodb-src-r\${MONGODB_VER}

在先前那篇编译 MongoDB 的 Embedded 版本中,我误以为 MongoDB 有一处 type-casting 的问题,其实因为当时是 32-bit 的环境,所以会出现问题。

但我们这里编译的命令确实需要做出一定的调整,不然编译到后面会出现这样的报错

虽然不是很清楚为什么 scons 找不到 core 这个编译依赖,但是我们编译 MongoDB Server 可以不用它

我们的编译命令需要改为如下

CCFLAGS="-mabi=lp64 -march=armv8-a+crc+simd" \
    python3 buildscripts/scons.py mongod \
    --disable-warnings-as-errors \
    --release=RELEASE \
    --dbg=off

但是!!!只是改了这里还不行,为什么呢?

因为这里的坑是 crc,我其实一开始第一行写的是 CCFLAGS="-mabi=lp64 -march=native",但是编译到 wiredtigercrc32-arm64 部分时,编译器突然报错说

Compiling build/opt/third_party/wiredtiger/src/checksum/arm64/crc32-arm64.o
/tmp/ccHQNUYS.s: Assembler messages:
/tmp/ccHQNUYS.s:40: Error: selected processor does not support `crc32cb w2,w2,w3'
/tmp/ccHQNUYS.s:70: Error: selected processor does not support `crc32cb w2,w2,x4'
/tmp/ccHQNUYS.s:95: Error: selected processor does not support `crc32cb w2,w2,w0'
scons: *** [build/opt/third_party/wiredtiger/src/checksum/arm64/crc32-arm64.o] Error 1
scons: building terminated because of errors.

然而我记得 Raspberry Pi 4 的 CPU 肯定是支持硬件 CRC 的,于是总之先找了一段使用硬件 crc32 的代码测试,代码来自 linux kernel arch/arm64/crypto/crc32-arm64.c

#include <stdlib.h>
#include <stdint.h>

#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))

uint32_t crc32_arm64_le_hw(uint32_t crc, const uint8_t *p, unsigned int len)
{
    int64_t length = len;
    while ((length -= sizeof(uint64_t)) >= 0) {
        CRC32X(crc, *((uint64_t *)p));
        p += sizeof(uint64_t);
    }
    if (length &amp; sizeof(uint32_t)) {
        CRC32W(crc, *((uint32_t *)p));
        p += sizeof(uint32_t);
    }
    if (length &amp; sizeof(uint16_t)) {
        CRC32H(crc, *((uint16_t *)p));
        p += sizeof(uint16_t);
    }
    if (length &amp; sizeof(uint8_t))
        CRC32B(crc, *p);
    return crc;
}

uint32_t crc32c_arm64_le_hw(uint32_t crc, const uint8_t *p, unsigned int len)
{
    int64_t length = len;
    while ((length -= sizeof(uint64_t)) >= 0) {
        CRC32CX(crc, *((uint64_t *)p));
        p += sizeof(uint64_t);
    }
    if (length &amp; sizeof(uint32_t)) {
        CRC32CW(crc, *((uint32_t *)p));
        p += sizeof(uint32_t);
    }
    if (length &amp; sizeof(uint16_t)) {
        CRC32CH(crc, *((uint16_t *)p));
        p += sizeof(uint16_t);
    }
    if (length &amp; sizeof(uint8_t))
        CRC32CB(crc, *p);
    return crc;
}

然后用如下命令在 Raspberry Pi 4 上编译~

gcc -mabi=lp64 -march=armv8-a+crc+simd -c crc32-arm64.c -o crc32-arm64.o

发现确实是没问题的,Raspberry Pi 4 的 CPU 支持硬件 CRC,也就是说,我们需要更改的是 wiredtiger 的相关文件~

这里因为我们是用 scons 编译的整个项目,因此我们要改的文件就是 wiredtiger 下面 scons 的配置~

wiredtiger 的源代码在 src/third_party/wiredtiger/ 下,其实那下面有两个 scons 的配置文件,一个是 SConscript,另一个是 SconstructSconstruct 是给 Windows 平台用的,所以我们忽略它就好

打开 Sconscript,之后,我们需要在对应编译环境判断语句后增加 -march=armv8-a+crc+simd 这个参数~

那么阅读这个编译脚本之后,我们添加的位置如下~(对应 r4.2.1 所附带的 wiredtiger 的配置文件在 76 行

# ...
elif env.TargetOSIs('linux'):
    if env.TargetOSIs('android'):
        env.Append(CPPPATH=["build_android"])
        env.Append(CPPDEFINES=["_GNU_SOURCE"])
    else:
        env.Append(CPPPATH=["build_linux"])
        env.Append(CPPDEFINES=["_GNU_SOURCE"])
        env.Append(CFLAGS=["-march=armv8-a+crc+simd"])
else:
# ...

其实另一个方法就是不用硬件 CRC,但是那样可能会损失一定的性能,再说反正就只增加一行代码就可以用硬件 CRC 的话,并不算麻烦2333333

在修改好这个文件之后,回到 mongodb-src-r4.2.1 目录,在正式开始前还有最后一个坑,就是 MongoDB 编译的时候需要的内存还比较大,哪怕我买的已经是 4GB 的 Raspberry Pi,还是出现过 Out-of-Memory 的情况

因此可以接个读写速度相对较快的 U 盘到 Raspberry Pi 上,确保 U 盘上有足够的空间,大约需要 8G 以上的剩余空间(如果你的是 1GB 的树莓派的话,可以酌情考虑使用 12G 的 swap 文件,把下文里命令对应的 8G 改成 12G 即可),我们会将那个 U 盘拿来做 swap

给 Raspberry Pi 增加 swap 的方法也很简单~

首先,在没有接 U 盘之前执行

ls /dev/sd*

因为 Raspberry Pi 会给外接的储存依次分配 /dev/sda/dev/sdb 等这样的路径,记录下上面的输出~

下一步则是接上 U 盘,然后同样执行 ls /dev/sd*,观察多出来的部分

比如我这里多出来的是 /dev/sda/dev/sda1/dev/sda2,因为我的 U 盘上分了 2 个区,所以会有 /dev/sda1/dev/sda2

我这里 U 盘上第二个分区是 Extfs 4 的格式,所以我就用它来放 swap 文件了~

mkdir -p /tmp/sd
sudo mount /dev/sda2 /tmp/sd
sudo fallocate -l 8G /tmp/sd/swapfile
sudo chmod 400 /tmp/sd/swapfile
sudo mkswap /tmp/sd/swapfile
sudo swapon /tmp/sd/swapfile

这样子就添加好了 8G 的 swap 空间~

现在一切准备好之后,就可以使用前面提到的命令开始编译了~

CCFLAGS="-mabi=lp64 -march=armv8-a+crc+simd" \
    python3 buildscripts/scons.py mongod \
    --disable-warnings-as-errors \
    --release=RELEASE \
    --dbg=off

整个过程大约 5-7 个小时,我忘了给它计时2333333

这个时候一来因为我们修改好了 wiredtiger,所以在编译 crc32-arm64.c 的时候不会再报错了

我编译完成的时候已经是晚上了,但是总算是编译完了♪(´ε` ) 而且理论上所有功能都可以正常使用~

编译好的 mongodbuild/opt/mongo/mongod

要测试一下的话,可以使用如下命令~

cd build/opt/mongo/
rm -rf ~/mongodb
mkdir -p ~/mongodb
./mongod --dbpath=${HOME}/mongodb --bind_ip=0.0.0.0

然后就能看到 MongoDB Server 正常运行了~顺便我还从 Mac 上连过去了一下,也是没问题的~

Raspberry Pi 4 那边 MongoDB Server 收到连接的 log 如下~

声明: 本文为0xBBC原创, 转载注明出处喵~

2 thoughts on “在 Raspberry Pi 4 上编译 64-bit 的 MongoDB Server”

Leave a Reply

Your email address will not be published. Required fields are marked *