ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Microduck实践:基于Unix Socket与JSON-RPC的守护进程军团架构

Microduck实践:基于Unix Socket与JSON-RPC的守护进程军团架构 Microduck这个项目我接触到纯属偶然。当时手头正好要做一个多进程后台系统需要把采集、解析、上报、告警这些模块拆开又不希望引入太重的中间件和网络栈。刚开始看到“守护进程军团”这个名字我还笑了一下后来发现它实际上是把“多个守护进程 Unix socket JSON-RPC 2.0”这三样东西组合成了一套非常务实的本地微服务架构。先说它能解决什么问题。当你的程序需要拆成多个独立模块独立运行、独立崩溃、独立升级但所有模块其实又都跑在同一台机器上时通常会面临两个选择一是用HTTP接口互相调用简单但开销大二是用消息队列解耦可靠但部署复杂。Microduck走的是中间路线——每个功能模块都是一个守护进程进程间通过本地Unix socket通信协议用JSON-RPC 2.0。它无端口、无网络栈、无外部依赖本地调用的性能损耗几乎可以忽略不计调试又非常直观适合想做模块化改造、或者正在研究本地IPC与远程调用协议如何结合的开发者参考。这篇文章我打算从架构选型、协议细节、核心实现、完整实操再到排障经验完整讲一遍这套“守护进程军团”是怎么设计和跑起来的。1. 为什么是“守护进程军团”架构选型的真实动因1.1 单体服务拆分的真实痛点我最初接触的版本是个单体程序所有逻辑塞在一个进程里数据采集模块、文本解析模块、数据落库模块、异常告警模块全部揉在一起。平时不觉得有什么但线上出问题就很尴尬解析模块处理了某个畸形输入导致内存异常整个进程崩溃采集和告警也跟着一起挂了。想单独升级解析模块就得停掉整个服务重新编译、重启。更要命的是每个模块的生命周期不一样有的需要常驻监听有的只需要每天凌晨跑一次把它们强行放在同一个进程里调度逻辑会变得越来越绕。后来我意识到问题的本质是隔离和控制。进程本身就是操作系统提供的最好的隔离边界栈、堆、文件描述符、信号处理全都是进程级的。与其在一个进程里用线程和协程模拟隔离不如直接把模块拆成独立的进程。Microduck这个设计思路就是在回答一个很朴素的问题既然都是跑在同一台机器上的小服务那为什么不让它们各跑各的进程然后用最轻量的方式互相调用“守护进程军团”这个说法其实很形象。每个守护进程是一个独立作战单元有自己的生命周期、自己的日志、自己的重启策略它们通过一套约定的协议协作而不是靠共享内存或者全局变量。这种架构让每个模块的开发思路都非常简单——我只需要关心自己的输入和输出不需要知道其他模块内部在干什么。1.2 为什么用守护进程而不是同一个进程内的worker可能有人会问模块化不一定非要拆进程吧一个进程里搞几个线程、用线程池调度不也行吗行但要分场景。Microduck这套架构里不同的守护进程需要的运行时长、资源配额、失败处理策略完全不同。采集进程可能需要长时间运行反复连接传感器解析进程可能消耗大量CPU和内存上报进程则非常轻量只需要确保数据能送达。这些进程如果放在同一个进程里CPU密集型的解析任务就会拖垮IO密集型的采集任务一个模块的谁内存也会影响全局的稳定性。拆成独立进程之后隔离效果立竿见影。解析进程哪怕把内存打满也可以单独把它杀掉、调参、重启其他进程完全不受影响。而且守护进程的启动、停止、状态检查都有非常成熟的操作系统机制可以用比如pid文件、systemd服务单元、supervisor、runit不需要自己造轮子。对运维来说systemctl restart microduck-parser这种操作比在一个大进程里找某个模块的重启入口要直观太多。还有一个容易被忽略的点权限隔离。Microduck里有些进程需要访问设备节点有些进程需要读写数据库有些进程只需要读配置文件。如果所有逻辑都在一个进程里权限就是取最大集任何一个模块被攻破都等同于整个程序被攻破。拆成独立的守护进程之后可以给每个服务单独建系统用户设置独立目录权限把安全风险控制在最小范围内。1.3 为什么偏偏选Unix socket JSON-RPC而不是HTTP或gRPC这个选择值得说一下。本地进程间通信的手段不少共享内存、管道、Unix socket、TCP loopback、DBus等等。共享内存性能最好但同步复杂管道只能做简单流向通信DBus本身是另一个复杂的协议体系。Microduck选Unix socket JSON-RPC核心原因就是“够用且好用”。Unix socket相比TCP loopback有几点天然优势第一它不占用网络端口不会碰到端口冲突、防火墙拦截、网络栈性能损耗这些问题第二它的通信范围被限制在本机文件系统内外部机器想连也连不上安全性更好第三它可以通过文件系统权限来控制访问把socket文件的权限设成750就只有特定用户组的进程能调用第四读写Unix socket不走完整的网络协议栈延迟更低、吞吐更稳对本地高频调度非常合适。协议层面用JSON-RPC 2.0而不是gRPC也是刻意做的选择。gRPC功能强大但需要管理.proto文件、生成代码、处理HTTP/2帧整个链路重很多。对于Microduck这种所有服务都在一台机器上的场景用JSON-RPC 2.0反而更舒服协议规范少读一遍RFC就能掌握消息就是一段带method和params的JSON文本没有复杂的序列化规则出问题时抓一段日志直接就能看懂。提示如果你想快速验证某个Unix socket服务是否正常不需要写客户端代码直接用curl --unix-socket /path/to/socket配合-d参数就能发JSON-RPC请求。这一步带来的调试便利性是选择JSON-RPC最超值的地方。2. 通信协议设计Unix socket上的JSON-RPC细节2.1 JSON-RPC 2.0 消息模型请求、响应、通知与错误码JSON-RPC 2.0的完整规范其实很短核心就是请求对象和响应对象。请求方发一个JSON对象里面必须有四个字段jsonrpc固定是2.0method是被调用方法名params是参数可以是数组也可以是对象id用于将请求和响应对应起来。服务端处理完之后返回一个对象包含jsonrpc、result或error、以及和请求一致的id。一个最简单的请求长这样{ jsonrpc: 2.0, method: system.health, params: {}, id: 1 }对应的成功响应{ jsonrpc: 2.0, result: { status: ok, uptime: 3600 }, id: 1 }如果出错响应里的result要替换成error对象。error必须带code、message和可选的data字段。JSON-RPC2.0规范里预定义了几个错误码-32700是解析错误也就是收到的JSON文本本身不合法-32600是无效请求-32601是方法不存在-32602是参数无效-32603是内部错误。Microduck在预定义错误码之上把自定义的业务错误码从-32000到-32099这段保留区间分配给各个具体模块比如写入失败、上游超时、数据格式异常等。Microduck利用通知机制来做“发完即走”的调用。通知格式和普通请求几乎一样只是没有id字段服务端收到通知后不返回任何响应。一些非关键的事件上报比如“采集循环开始”“配置已重载”完全可以用通知来发省去等待响应的开销。不过要注意通知没有响应就意味着调用方无法知道对端是否真的处理成功所以只适合不关心结果或者结果可以异步确认的场景。2.2 Unix socket的地址、传输语义与权限模型Unix socket的地址不是一个IP加端口而是一个文件系统路径。服务进程在启动时bind到一个路径上比如/var/run/microduck/collector.sock客户端进程通过connect这个路径来建立连接。这条路径有长度限制Linux上sun_path通常最多108字节所以路径不能取得太深长否则会报unix(7) related address error。socket文件的权限非常关键因为任何能读写这个文件的进程都能发起调用。我的习惯是在/var/run/microduck目录下创建所有socket文件目录权限设为750属主是microduck用户属组是microduck组。只有受信任的进程在运行时会切换到microduck用户或加入这个组从而保证只有它们能访问socket。这种权限模型是TCP端口完全没有的也是本地服务能做到的最直观的访问控制方案。CC编程里创建Unix socket的代码我写一下大家能直观看到这个过程struct sockaddr_un addr; int fd socket(AF_UNIX, SOCK_STREAM, 0); addr.sun_family AF_UNIX; strncpy(addr.sun_path, /var/run/microduck/collector.sock, sizeof(addr.sun_path) - 1); unlink(addr.sun_path); bind(fd, (struct sockaddr *)addr, sizeof(addr)); listen(fd, 32);注意这里有个细节bind之前必须先unlink一次旧文件。因为如果上次进程异常退出socket文件会残留在文件系统里而有效socket的判定服务端是重启了还是没重启光看文件是否存在是看不出来的必须先把它删掉才能确保后续的bind不会失败。2.3 消息边界读一半和粘包是怎么解决的很多第一次接触Unix socket的开发者会踩同一个坑直接模仿HTTP里“请求-响应”的思路写一个recv收一次数据但实际上TCP流协议根本没有消息边界UDP才有。Unix socket也分两种SOCK_DGRAM数据报模式是保留消息边界的但数据报有长度上限且不可靠而SOCK_STREAM流模式就像字节流一样一条消息可能分多次收到也可能多条消息一次全到。microduck选的是SOCK_STREAM因为要复用成熟的select-poll-event loop模型也需要长连接所以必须自己解决边界问题。Microduck的解法很朴素每条JSON-RPC消息用换行符\n作为分隔符。发送时在消息体后面追加一个\n接收方维护一个缓冲区读到换行符就认为一条完整消息到达。这种方式实现成本极低而且天然支持逐行调试用日志工具抓数据流时一眼就能看出消息内容。唯一的限制是消息体内部不能包含裸换行符这对JSON-RPC完全不是问题因为JSON字符串里的控制字符都会被转义。为了防止某个进程恶意或异常地发送超大消息把接收方的内存吃满Microduck在接收逻辑里设置了一个硬限制单条消息最大不超过1MB超过这个长度直接断开连接并记录告警日志。1MB对于JSON-RPC调用几乎永远够用但足够拦截绝大多数异常场景。缓冲区则是动态扩容加最大上限的组合ssize_t n read(fd, buf used, buf_size - used - 1); // 检查 n 0 used n; char *pos; while ((pos memchr(buf, \n, used)) ! NULL) { *pos \0; handle_message(buf, pos - buf); memmove(buf, pos 1, used - (pos - buf) - 1); used - (pos - buf) 1; }这块逻辑是socket编程里最容易出错的地方之一凡是出现“服务端偶尔收到空数据”“多条消息挤在一起解析失败”的基本都是边界处理没做好。3. 核心实现进程管理、socket生命周期与请求分发3.1 socket文件的创建与清理防止残留文件引发的诡异问题Unix socket文件的生命周期管理是整个Microduck体系里最常见的故障源之一。正常退出时服务进程捕获退出信号在退出回调里unlink自己的socket文件这是大多数人会写到的。但线上系统最怕的是异常退出进程被SIGKILL杀死、机器突然断电、或者进程崩溃来不及清理socket文件就留在磁盘上了。残留文件带来的问题是下次服务启动时bind会失败提示“Address already in use”。有些开发者遇到这个问题会怀疑是端口被占但Unix socket不占用端口其实只是旧文件的残留。Microduck的解法是服务启动时先尝试connect一下这个socket路径如果connect失败说明旧的socket已经没人监听了就大胆unlink后重新创建如果connect成功说明有一个活着的实例在运行直接报错退出避免启动两个相同服务互相干扰。这段逻辑配合pid文件的检查效果更好我会同时在/var/run/microduck/下维护一个pid文件启动时先读pid文件判断进程是否存在双保险。3.2 请求分发、超时与错误处理的统一约定每个守护进程内部都保留一个轻量的路由表把JSON-RPC里的method字符串映射到本地函数指针。比如采集守护进程注册了collector.start、collector.stop、collector.status三个方法上报守护进程注册了reporter.push、reporter.flush、reporter.pending_count。在框架层收到一条JSON-RPC请求后先解析JSON再查路由表找到就调用找不到就返回-32601错误。这个路由结构其实就是一个哈希表却提供了完整的微服务边界。超时处理要分两层看。客户端在发送请求时可以设置socket的接收超时比如5秒内拿不到响应就返回错误。但更关键的是服务端也要有运行超时。Microduck的做法是每个方法在注册时可以声明自己的超时阈值服务端在独立的工作线程里执行具体方法主线程用poll同时监控所有健壮的接口和超时定时器。一旦某个方法执行时间超过声明阈值这次调用的响应就直接返回“timeout”执行线程本身不会被强杀而是被标记为“孤立执行”它最终跑完的结果会直接丢弃。这种方式可以避免为了止损杀掉线程而破坏共享数据结构的风险。错误处理也有统一约定。Microduck约定所有方法在内部只能返回“成功data”或“业务错误”框架层负责把业务错误转换成JSON-RPC的error对象。方法内部不允许自行捕获异常后返回非JSON对象因为这样会把格式搞乱。这个约定让整个架构的调用方只需要面对两类结果标准的成功响应或标准的错误响应不会有第三种情况。3.3 多服务进程之间的依赖关系与启动顺序“守护进程军团”里各个服务并不是完全对等的。有些服务是纯上游有些是纯下游还有的既接收请求又向其他服务发请求。比如采集服务从传感器读数据然后把原始数据发给解析服务解析服务再把结构化结果发给上报服务。这就形成了调用链上报服务启动后要确保解析服务的socket文件已经存在并且可以连通解析服务启动后又要确保采集服务已经注册好。Microduck对启动顺序的处理不是靠脚本里的“先sleep 5秒再启动下一个”这种笨办法而是在服务启动完成后会对依赖的下游服务做一次健康检查调用比如调reporter.ping如果返回成功再宣告自己启动完成。上层编排脚本只需要按依赖顺序依次启动并在每个服务启动日志里等待“ready”标记出现。这种健康检查机制既解决了启动顺序问题也为后续做自动拉起和故障转移打下了基础。4. 实操记录从零跑通Microduck守护进程军团的完整流程4.1 编译与基础进程启动整个项目在GitHub上有仓库克隆下来之后就是标准的构建流程。如果跑在普通x86服务器上直接执行make就能编译出所有守护进程的二进制文件。如果要在ED-330这类嵌入式盒子上跑则需要交叉编译或者直接在盒子上装编译工具链再源码编译我实测下来只要依赖库齐全过程基本一样。编译完成之后目录下会出现一套可执行文件以及一个名为microduck.conf的配置文件。配置文件的要修改的内容不多主要是各个socket文件的存放路径、日志路径、以及各服务的开关。我第一次配置时用的是默认路径/var/run/microduck/需要确保目录存在并且属主正确sudo mkdir -p /var/run/microduck sudo chown microduck:microduck /var/run/microduck sudo chmod 750 /var/run/microduck然后启动核心服务。Microduck的启动顺序通常是先启动依赖最底层的服务也就是其他服务都要调用的基础服务然后依次启动上层服务。假设整套军团包含四个守护进程md-core、md-collector、md-parser、md-reporter启动命令如下sudo -u microduck /usr/local/bin/md-core -c /etc/microduck/md-core.conf sudo -u microduck /usr/local/bin/md-collector -c /etc/microduck/md-collector.conf sudo -u microduck /usr/local/bin/md-parser -c /etc/microduck/md-parser.conf sudo -u microduck /usr/local/bin/md-reporter -c /etc/microduck/md-reporter.conf 每个进程启动后都会生成一个以服务名命名的socket文件。启动完可以确认一下socket文件是否都已就绪ls -l /var/run/microduck/*.sock正常情况下collector.sock、parser.sock、reporter.sock、core.sock这几个文件都会出现。这不代表服务已经可以对外服务了还需做一次健康检查。4.2 验证服务状态与JSON-RPC调用是否正常JSON-RPC的一个巨大好处是调试时完全不需要写专用客户端curl就够了。curl从7.40版本开始支持--unix-socket参数把HTTP over Unix socket这个能力直接提供出来我们在Microduck里复用它来发JSON-RPC请求。比如检查md-core服务的状态curl -s --unix-socket /var/run/microduck/core.sock \ -H Content-Type: application/json \ -d {jsonrpc:2.0,method:system.health,params:{},id:1} \ http://localhost/这里HTTP方法随便写因为我们实际使用的只有body里的JSON-RPC内容。返回结果应该类似{ jsonrpc: 2.0, result: { status: ok, uptime: 30, version: 0.4.2 }, id: 1 }如果返回的是error比如-32601表示方法未注册再加一个data字段说明具体原因是“method not found”这就说明路由表没问题但服务端没有暴露这个方法。顺手把collector、parser、reporter三个服务的健康检查也做了确认四个服务全部在线。健康检查通过之后可以模拟一条完整的数据流转。比如手动向md-collector发一个采集指令curl -s --unix-socket /var/run/microduck/collector.sock \ -d {jsonrpc:2.0,method:collector.trigger,params:{source:sensor01},id:2} \ http://localhost/如果采集成功它会通过内部客户端往parser.sock发转换请求转换完成后parser再调用reporter上报。最终从reporter查询数据是否成功落库就能验证整条链路是否跑通。这个过程里我只关心最初发出去的请求和最终查询的结果中间的跳转全部由架构内部处理这正是微服务化的价值所在。4.3 守护进程的常规守护化fork、setsid、umask与pid文件上面用启动进程只是简单演示。真正作为“守护进程军团”运行每个服务还必须完成常规的守护化流程。手动daemon化一般分几步第一次fork让进程脱离控制终端调用setsid创建新会话第二次fork确保不会再获取到控制终端然后chdir到固定的工作目录umask设置成022最后把stdin、stdout、stderr三个标准描述符全部重定向到日志文件或/dev/null。Microduck里的-d参数就是把这段逻辑封装起来。我给一个直接用C语言实现daemon化的参考片段static void daemonize(const char *pidfile) { pid_t pid fork(); if (pid 0) exit(EXIT_FAILURE); if (pid 0) exit(EXIT_SUCCESS); // 父进程退出 if (setsid() 0) exit(EXIT_FAILURE); umask(022); int nullfd open(/dev/null, O_RDWR); dup2(nullfd, STDIN_FILENO); dup2(nullfd, STDOUT_FILENO); dup2(nullfd, STDERR_FILENO); close(nullfd); // 写pid文件便于 systemd 或外部脚本管理 int fd open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); dprintf(fd, %d\n, getpid()); close(fd); }这段逻辑看起来简单但有不少细节值得注意。第一次fork后父进程直接退出让子进程由init进程收养避免僵尸。setsid是让进程成为新会话的领导彻底断开与终端的联系。umask设置成022是为了让后续创建的文件默认用户可读写、组可读、其他用户可读避免出现权限过宽的问题。pid文件建议用O_TRUNC截断重写确保不残留旧pid。4.4 用systemd托管守护进程实现崩溃自动拉起虽然手动daemonize可行但生产环境我更推荐用systemd管理这些守护进程。systemd天然支持进程超时退出自动重启、开机自启、标准输出重定向到journal还能精细控制服务之间的依赖顺序。Microduck的四个服务可以做成四个unit文件以md-parser.service为例[Unit] DescriptionMicroduck Parser Daemon Requiresmd-core.service Aftermd-core.service [Service] Usermicroduck Groupmicroduck ExecStart/usr/local/bin/md-parser -c /etc/microduck/md-parser.conf Restarton-failure RestartSec3 PIDFile/var/run/microduck/parser.pid [Install] WantedBymulti-user.target注意Requiresmd-core.service和Aftermd-core.service共同保证了依赖顺序只有md-core启动成功后md-parser才会被拉起。Restarton-failure配合RestartSec可以让进程在崩溃退出后自动重启。这种方式比脚本中的while true循环要优雅可靠得多systemd还会负责跟踪主进程pid不需要再额外写pid管理代码。提示如果服务里用了fork做守护化那systemd里必须设置Typeforking并指定PIDFile否则systemd会因为无法得知子进程的存活状态而误判服务启动失败。更好的做法是在systemd单元里直接用Typesimple然后让服务进程不要daemonize把前台运行交给systemd去管理。Microduck的二进制在检测到环境变量MICRODUCK_FOREGROUND1时会跳过daemonize逻辑直接前台运行。4.5 网络模拟启动顺序异常与依赖不可用时的表现实际操作中最容易遇到的问题是启动顺序乱掉。如果没配依赖关系直接在md-core还没起来时启动md-parsermd-parser启动时会尝试连接core.sockconnect失败后不会死等而是会进入重试循环并输出类似“waiting for core service on /var/run/microduck/core.sock, retry in 2s”的日志。等到md-core起来之后md-parser会在下一次重试时成功连接然后初始化内部路由宣告启动完成。这个设计避免了一种尴尬场景编排脚本必须保证绝对的启动顺序否则所有服务全部启动失败。允许依赖不可用但自动重试的方式让整套系统具备了一定的容错能力即使某个依赖服务晚了几秒启动整个军团也不会直接退化成不可用状态。5. 常见问题与排查实录5.1 socket创建失败、连接拒绝与权限不足我先整理一张速查表方便大家遇到问题时候对号入座。现象可能原因处理方式bind失败提示Address already in use上次进程异常退出socket残留启动时先执行unlink或rm -f /var/run/microduck/xxx.sockconnect失败提示No such file or directorysocket路径不对或服务未启动用ls -l /var/run/microduck/确认文件存在检查配置文件路径connect成功但发送后无响应服务进程阻塞或已僵死查看进程状态strace -p pid跟踪系统调用调用报权限错误当前用户没有socket文件访问权限检查socket和目录权限将调用方加入对应用户组路径过长导致创建失败Linux的sun_path最大108字节收缩socket目录层级比如/run/md/代替/var/run/microduck/第一类问题最常见。我自己的机器上出现过一次很诡异的现象进程明明已经杀了但新进程就是bind不上后来发现是一个旧进程变成了D状态不可中断睡眠没有真正退出所以旧socket文件一直被占用。这种情况unlink其实是无效的需要先确认旧进程确实不存在再处理文件。5.2 JSON解析失败和方法不存在JSON-RPC调用里格式错误类问题占到了日常故障的一半。常见的有把jsonrpc写成jsonRpc导致服务端判定为无效请求请求里少了id字段但调用方期望响应接收方则把它当成通知处理还有在JSON里用了单引号或者末尾多逗号服务端的JSON解析器直接返回-32700。方法不存在的-32601错误虽然直白但有时也有误导性。有一次我排查了半天最后发现请求发到了collector.sock但collector.trigger方法实际注册在md-core服务上。这个问题本质上是对服务职责边界不清楚。Microduck在实践里会很重视一个原则调用方代码里显式写明“我要调哪个服务的哪个方法”不要在调用代码里含糊地只传一个方法名这样即使发错也可以顺着日志快速定位。5.3 缓冲区溢出、消息截断与SIGPIPE字节流的消息边界问题前面讲过了这里讲一个容易被忽略的陷阱发送端一次send调用发送了很多字节接收端缓冲区不够读到一半消息解析失败然后连接被服务端关闭。这样的问题表现出来就是“有时候调用成功有时候失败失败的时候日志里出现partial message”。解法其实已经说过用换行符加动态缓冲区。Microduck在接收端维护了一个可增长缓冲区初始大小4KB每次解析失败时不会立即断连而是先检查是否有可能的换行符被截断了如果缓冲区填满了且仍然没有换行符才会主动断开并记录“message too long”。另一个经典坑是SIGPIPE。客户端在连接关闭后如果继续写socket进程会收到SIGPIPE信号默认行为是终止进程。在守护进程场景下一旦某个客户端异常断开服务端或中间转发进程就可能因为这个信号挂掉。处理方式是在程序初始化时忽略SIGPIPEsignal(SIGPIPE, SIG_IGN);忽略之后写socket返回EPIPE错误代码里检查返回值并做清理进程就不会再因为客户端断开而意外退出。这条经验我在多进程项目里反复踩过每次新成员加入团队第一周总会有人被SIGPIPE坑一次。5.4 性能与监控查看socket连接和进程状态本地Unix socket的性能远好于TCP loopback但也不是说什么场景都能随便造。Microduck在生产环境里常驻200个连接每秒处理大概400到600个JSON-RPC请求CPU占用在单个核的15%以内。数据量再往上走就要关注消息积压和背压了。用几个命令可以实时监控服务状态。ss -x可以查看Unix socket连接ss -x | grep microduck能看到每个socket文件下的连接数如果某个socket的连接数堆积明显说明这个服务的处理速度跟不上请求速度。pidstat -p pid 1可以看进程CPU和内存变化lsof -p pid | wc -l统计文件描述符数量。日志方面Microduck每条JSON-RPC请求都会按级别打印method、调用耗时和返回码配合grep就能做很粗粒度的链路追踪。如果要做更细致的监控可以在框架层给每个服务加一个system.stats方法返回当前打开连接数、消息处理速率、平均耗时和错误计数。这些数据不需要额外的监控系统直接在业务侧轮询即可。我还在Microduck里加过一个简单的“自愈”逻辑某个服务自己发现错误率超过阈值会自动向管理服务发一个通知请求管理服务收到后把对应的下游重启一遍。这套机制虽然粗暴但在无人值守的嵌入式环境里非常有效。结尾做完Microduck这套“守护进程军团”之后我最大的体感是本地微服务的最优解往往不是最时髦的方案而是最合适的那一个。Unix socket和JSON-RPC都是非常“老派”的技术组合起来却意外地顺手不需要引入庞大的框架不需要处理复杂的网络配置一行curl就能完成大部分的调试这种访问效率提升带来的开发体验是实实在在的。最后分享一个小技巧给每个公开方法都加上版本号前缀比如v1.collector.trigger而不是直接叫collector.trigger。这样升级接口时旧版本方法可以和新版本方法并存一段时间调用方渐进式迁移避免“发布当天所有服务全部报错”的惨剧。这套架构后续如果继续演进我大概率会先做服务间的认证机制和更细粒度的链路日志这两件事能让“军团”真正在复杂环境里站得更稳。
返回列表