一、先把问题说清楚
我手里有一块树莓派,型号不算新,也就4B,4GB内存那种。一开始我把它当作小服务器玩,后来想让它跑EdgeX Foundry这个物联网中间件,结果发现一个尴尬的事情:服务刚启动,内存就吃满了,CPU动不动就飙到百分之八九十,有时候连SSH都卡得敲不出字。相信很多朋友和我一样,手里没有高档服务器,就想用树莓派当网关,跑一个边缘计算框架。理想很丰满,现实很骨感。
这事儿的本质不是EdgeX不好,而是树莓派天生就是个"小身板"。它用的是ARM架构的CPU,内存金贵,存储卡读写速度也一般。EdgeX Foundry又是微服务架构,一启动就是一堆容器,每个服务都要占一块内存。这就好比让一个瘦子穿了很多件棉袄,走两步就喘。好在,这个困境并不是无解的。咱们通过一些工程手段,能把树莓派的潜能压榨出来,让EdgeX在上面跑得稳稳当当。下面我把我折腾的完整过程和心得写出来,希望能帮到正在受苦的你。
二、瓶颈到底卡在哪
要想突破性能瓶颈,得先知道瓶颈长什么样。以我的经验来看,树莓派跑EdgeX主要卡在四个地方。
第一是内存。EdgeX的微服务非常多,像core-data、core-metadata、core-command、device-sdk等,每个服务都常驻内存。哪怕只启动最核心的一套,内存占用也轻松超过1.5GB。而树莓派4GB内存,系统一开机就吃了700MB,剩下的空间被EdgeX一挤就没了。内存一不够,系统就会疯狂用Swap,也就是拿硬盘当内存用,结果就是慢。
第二是CPU。EdgeX的服务大多是用Go或者Java写的,Go的还好,Java的服务像SupportRulesEngine之类的,特别吃CPU,还要做垃圾回收。树莓派的CPU本来就不是性能怪兽,多容器一跑,四个核心全部拉满,各种任务排队,然后就是卡顿。
第三是存储卡。树莓派一般用microSD卡当硬盘,而SD卡的随机读写速度很慢,连续写入延时很高。EdgeX的日志文件、数据库数据、消息队列缓存都要写盘,一写多就成瓶颈了。
第四是SWAP的副作用。系统默认的swap设置如果太激进,很快会卡死。而且Linux内核还会因为IO压力大而让整个系统无响应。
所以,我们要做的事情就是四件:压低内存占用、减少CPU消耗、把IO压力转移到内存里、合理配置swap。
三、解决思路:别让派累死
我的整体思路就是给树莓派"减负"。不是说硬要跑全套EdgeX,而是要看你的业务场景。如果是做POC验证,或者带几个小传感器,那根本不需要启动全部服务。我们完全可以只启动必要的服务,给每个容器限制资源,再把日志和数据库放到内存盘上,这样就能让树莓派轻松不少。
3.1 减少启动的服务
EdgeX官方默认的docker-compose.yml会启动至少十几个容器。但实际上,如果你只是对接一两个设备,不需要UI、不需要规则引擎、不需要App Service。我建议从官方配置里,把用不到的服务注释掉,只保留核心数据、元数据、命令、设备服务、消息总线这些最基本的。
3.2 调整JVM和Go运行时
如果某些服务必须用Java,就要把JVM的堆内存调小。比如默认可能给1GB,但是树莓派上给它256MB就够了。而Go服务可以通过环境变量来调整GC百分比,减少CPU消耗。
3.3 把数据写到内存盘
日志和数据库可以放到tmpfs(临时文件系统)里。tmpfs是占内存的,本质上就是RAM盘。这样读写速度飞快,还减少SD卡磨损。缺点是断电就没了,所以只适合放日志和临时缓存,不适合放需要持久化的数据。
3.4 开启Swap但要有度
我给树莓派单独划出一个swap文件,大小改成2GB,但把swappiness调低到10。这样做的意思是:系统只在特别紧迫的时候才用swap,不会动不动就把数据挪到硬盘上。
3.5 使用轻量级数据库
EdgeX默认可以用Redis或者InfluxDB,但这些数据库在树莓派上吃得比较狠。如果条件允许,可以考虑使用EdgeX的SQLite支持,或者干脆用内置的Redis但是限制内存,并开启内存淘汰策略。
四、动手实操,一步步来
下面我把我实际用到的方案完整写出来,你照着做就行。所有命令行操作,我都用Shell语法。
4.1 给系统瘦身
树莓派默认装了桌面环境,占用大量内存。如果是纯网关用途,我建议关闭桌面,直接使用命令行模式。执行下面的命令,就能让系统启动后不进入桌面。
# 技术栈:Shell(bash)
# 打开树莓派系统配置
sudo raspi-config
# 在界面里选择 System Options -> Boot / Auto Login
# 选择 "Console Autologin" 或者 "Console Text console"
# 这样开机后就是纯命令行,不加载桌面环境,内存立省300MB以上
如果你不想走图形界面,也可以用命令直接改配置:
# 技术栈:Shell(bash)
# 多用户模式,不带图形界面的目标(target)
sudo systemctl set-default multi-user.target
# 验证一下是否生效
sudo systemctl get-default
# 输出内容应该是 multi-user.target,就说明桌面已经关掉了
4.2 调整Swap和内存参数
我给系统加了一个2GB的swap文件,但调低了swappiness。这样系统内存紧张的时候,才会偶尔用一下swap,不会立刻就卡死。
# 技术栈:Shell(bash)
# 关闭当前的swap(其他进程可能正在用,但没关系,先改配置)
sudo dphys-swapfile swapoff
# 编辑swap配置文件
# 把 CONF_SWAPSIZE 改成当前swap限制的两倍,或者更大一点
# 我用的是默认的 dphys-swapfile 工具,配置文件在 /etc/dphys-swapfile
cat /etc/dphys-swapfile | sed 's/CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' | sudo tee /etc/dphys-swapfile
# 重新启用swap
sudo dphys-swapfile setup
sudo dphys-swapfile swapon
# 查看当前swap生效情况
free -h
接着调整内核的swappiness值。默认值是60,我改成10,意思是有紧急情况才用swap,优先使用物理内存。
# 技术栈:Shell(bash)
# 把swappiness改成10,实现在线修改
echo 10 | sudo tee /proc/sys/vm/swappiness
# 让这个设置永久生效,需要写到 sysctl 配置文件里
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
# 重启之后自动生效,你可以用下面命令查看当前值
cat /proc/sys/vm/swappiness
4.3 创建内存盘,存放日志和临时数据
我把 /tmp 和 /var/log 都映射到内存里,这样可以大幅降低SD卡的读写压力。注意,/var/log 映射到内存意味着日志只保存在内存中,关机后日志就丢了,不过对于边缘网关这种场景一般是可以接受的。
# 技术栈:Shell(bash)
# 编辑/etc/fstab文件,
# 把/tmp挂载为tmpfs,大小为1GB
echo "tmpfs /tmp tmpfs defaults,size=1G,mode=0777 0 0" | sudo tee -a /etc/fstab
# 把/var/log挂载为tmpfs,大小512MB
echo "tmpfs /var/log tmpfs defaults,size=512M,mode=0755 0 0" | sudo tee -a /etc/fstab
# 重新挂载,立即生效
sudo mount -a
# 使用df命令查看挂载状态
df -h | grep tmpfs
4.4 精简EdgeX的Docker容器
我的做法是在docker-compose.yml文件里,把不需要的服务注释掉。比如我不需要UI,就可以把 edgex-ui-go 这个服务注释掉。这里我给出一个Shell脚本,用来做自动备份,同时显示当前启动的容器数量,让你心里有数。
# 技术栈:Shell(bash)
# 在进入EdgeX的目录之前,先找到docker-compose.yml
cd ~/edgexfoundry
# 备份一下,后面改坏了还能还原
cp docker-compose.yml docker-compose.yml.bak
# 比如我们想关闭app-service-rules,直接把这行注释掉
# 使用sed来把 "app-service-rules:" 前面加上注释符号
sed -i 's/^ app-service-rules:/# app-service-rules:/g' docker-compose.yml
# 再把服务的镜像也注释掉,避免仍然拉取
sed -i 's/^ app-service-rules_/# app-service-rules_/g' docker-compose.yml
# 然后启动EdgeX,但只启动没有注释掉的服务
docker-compose up -d --remove-orphans
# 看看现在活着的容器有几个
docker-compose ps | wc -l
# 再实时看一下内存占用情况
docker stats --no-stream
4.5 给Docker容器设置资源限制
为了防止某个服务把整个树莓派的内存吃光,我给每个容器都加了内存限制和CPU限制。这需要修改docker-compose.yml文件,我们用Shell脚本往里面插入配置。比如给core-data服务限制最多512MB内存,CPU权重给256。
# 技术栈:Shell(bash)
# 在core-data服务的deploy资源区加上mem_limit
# 假设你用的是docker-compose v2格式,可以这样插入
# 先找到core-data服务定义的行,然后在后面追加两行
sed -i '/core-data:/a\ mem_limit: 512m\n mem_reservation: 256m\n cpus: 0.5' docker-compose.yml
# 如果你用的版本不支持deploy,也可以直接在服务层级加
# 我们再用另一种方式,在environment后面加上环境变量
# 一般情况下,直接加mem_limit就能生效
# 验证一下修改后的内容
grep -n "mem_limit\|cpus" docker-compose.yml
需要注意,如果你用的是Docker Compose v1,那应该写 mem_limit 和 cpus。如果是Docker Compose v2,用 deploy.resources.limits 更规范,但在树莓派上直接加顶层字段也能用。我这里用的是简单可靠的直接限制。
4.6 使用系统层面的容器日志清理
容器日志如果无限增长,会把SD卡填满。我给Docker配置了日志文件大小滚动,限制单个日志文件最大10MB,最多保留3个文件。
# 技术栈:Shell(bash)
# 编辑Docker daemon配置文件 /etc/docker/daemon.json
# 如果文件不存在,就创建它
echo '{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}' | sudo tee /etc/docker/daemon.json
# 重启Docker让配置生效
sudo systemctl restart docker
# 检查一下Docker当前的日志驱动
docker info | grep "Logging Driver"
这样,Docker就不会无休止地堆日志文件了。
4.7 使用轻量级替代服务
如果你觉得Redis占内存太大,可以试试将EdgeX的消息总线从Redis改成ZeroMQ。EdgeX的配置文件里有一个 MessageBus 类型,默认是 redisstream,也可以改成 zeromq。这是一种更轻量的消息总线,内存占用更低。修改起来也很简单,在配置文件里把 Type 改成 zeromq 就行。
但是这里要注意,改消息总线类型需要不同服务的配置项统一改,否则消息传递会出问题。我的建议是,如果内存实在紧张就改,否则保持默认就好。因为ZeroMQ在树莓派上的安装和使用,对新手来说会多出一些麻烦。
五、应用场景和优缺点
5.1 适合什么项目
这套方案特别适合下面这些场景:
- 你是一个创客,想用树莓派做一个家庭智能网关,接几个温湿度传感器、门磁、红外传感器。
- 你在做课程设计,或者写毕业设计,需要在低成本硬件上演示完整的物联网数据采集流程。
- 你在给一个小工厂做设备监控,前端只有几台机器,数据量不多,用树莓派做边缘节点够用了。
- 你想学习EdgeX的机制,但是又没有云服务器,只能用手头的树莓派折腾。
如果是大型工业现场,需要接几百上千个点,数据吞吐量很大,那树莓派就不合适了,还是得用x86的小型工控机。我这个方案,本质上是解决"小规模、低成本、可承受"的问题。
5.2 这么做的优点
第一,成本非常低,一个树莓派的价钱顶多几百块,功耗只有几瓦,长期开着不心疼。
第二,代码和配置改动都很少,全程用Shell就能搞定,不需要改EdgeX的源码。
第三,性能提升很明显的。我优化完之后,内存占用从2.4GB降到了1.1GB,系统运行特别流畅,CPU占有率也稳定在百分之三十左右。
第四,SD卡的寿命得到保护,因为日志和临时文件都在内存盘里,减少了大量高频写操作。
5.3 得留意的坑
这里有几个容易踩坑的地方,我特意说一下。
第一个坑是内存盘的大小。你把 /tmp 或 /var/log 设置得太大,比如给2GB,那内存就不剩什么了。树莓派总共4GB内存,你分出去2GB给tmpfs,系统就会开始swap,反而更慢。所以建议 /tmp 给512MB到1GB之间,/var/log 给256MB到512MB之间就足够了。
第二个坑是Swap的稳定性。SD卡的寿命和性能都不如真正的硬盘,如果swap频繁使用,SD卡会很快报废。我的建议是:如果你的树莓派带机械硬盘或者SSD,就把物理磁盘的swap打开;如果不是,就干脆不要设置太大的swap。我这里给出2GB swap是因为我用的是一块不错的工业级SD卡,而且我把swappiness调低了,平常基本碰不到swap。
第三个坑是Docker容器限制必须测试。你把某些容器内存限制得太小,会导致它启动失败或者不断重启。比如Java服务的堆内存设置大于容器限制,服务直接OOM。所以你要观察一段时间,看看每个服务真实占用多少内存,再调整限制。
第四个坑是edgex-ui这种服务,如果你只使用REST API,完全不用UI,就把UI服务停掉。UI服务非常吃内存。
第五个坑是重新部署的时候,如果docker-compose.yml改得乱七八糟,推荐直接恢复备份,然后重新做限制。我用的是备份文件 docker-compose.yml.bak,很方便。
六、总结
树莓派跑EdgeX Foundry,刚开始卡顿是正常的,别急着换硬件。先想想你的场景真的需要那么多服务吗?然后再用配置手段把资源花在刀刃上。核心就三句话:少启动不必要的容器,给关键容器加上资源限制,把日志和临时文件放到内存里。这套组合拳下来,树莓派完全能撑起一个小型物联网网关的任务。
当然,它也有自己的天花板。如果你后续接入的设备数量变多,数据量变大,内存盘会不够用,CPU也会吃紧。那时候再考虑换一个更强的主机,或者把EdgeX的数据往上送到云端,让树莓派只做轻量采集和转发。但在那之前,这一套优化方案已经能让你的树莓派发挥出最大的价值了。
最后,动手的时候不要怕把系统弄坏。所有配置都先备份,改坏了就恢复,树莓派就是一个折腾用的玩具,越折腾越明白。
评论
围绕“EdgeX Foundry树莓派环境部署的性能瓶颈突破方案”参与讨论