Rust服务端开发踩坑记录

上个月收到了份新需求,说是要临时搞个同步运营那边再用的几个仓库的数据的服务端程序,然后再提供一个界面可以把产品数据和库存数据聚合起来,问了下上级,对怎么写没啥要求,于是跟运营商讨好需求之后,决定用刚学了几个月的 Rust 写。

刚开始选型是服务端用 Rocket,请求用 reqwest,数据库用 rusqlite,后来服务端换成了 tower-web,请求库用 restson,因为 Rocket 需要 nightly 编译,restson 我看 cargo 也在用,应该还好,事实证明换掉这俩是错误的,数据库最后选用了 diesel,没碰到什么问题,很好用。

先说说 tower-web 的问题,它跟 Rocket 一样可以通过#[get("xxx")]这样的方式把一个函数定义为某个路由的响应函数,Rocket 因为使用了 nightly 特性,所以是直接在正常代码上标注就可以了,但是 tower-web 为了支持 stable 编译器,必须要先用一个他自已定义的 macro 把所有需要定义路由的函数包起来,这样就导致了 rls 不能实时推断,很多时候在编译的时候才会报错,同时被包起来的函数也无法格式化了,开发体验比较差。

然后是 restson,这个库提供了一个 trait,你可以对返回的请求结构体实现这个 trait,用来返回请求的链接,也就是说将返回数据跟请求链接绑定在一块了,这听起来很棒,你使用let ret: xxx = get(("abc", 123))就可以拿到正确序列化的数据,但实际上这需要你可以对后端 api 进行合理的设计,有很多设计不好的 api,会在不同的 api 里返回相同的结构。你如果想重用代码,要么给一个结构根据不同的输入参数返回不同的 api 地址--譬如说写个 enum,在不同的枚举类型里包含链接所需要的数据;要么实现多个不同的结构体,返回不同的地址。

这是第一个坑,第二个坑是,这玩意跟 tower-web 都使用了 tokio,restson 在调用的时候是 new 出一个 client,然后用这个 client 去 get 或者 post 或者其他请求,在他 new 出 client 的时候,内部把 tokio_core 自行 new 了一个出来,根据 tokio 旧版本的设计,一个线程只能有一个 core 在跑,所以如果我想在 tower-web 相应请求的时候用 restson 请求 api,就会直接 panic 掉,为了回避这个问题,我只能 spawn 出一个新进程,然后把需要的数据塞进去,再 new 一个 client 出来请求。

好吧,这临时的玩意,丑点就丑点了,但是测试环境跑起来了几天后就出现问题了,每跑个两三天,就会因为 socks 打开过多,直接崩掉进程,一开始还以为是同服务器下跑的 php 开的太多占光了 socks,后来换到了台空闲的服务器上跑也有问题,直觉上是 spawn 完成后 restson 没有把 socks 释放掉,但是我也懒得查了,在这需求跑测试的时候,我在另一个需求里使用了 Rocket+reqwest,感觉很稳妥,于是直接把这俩换上去了,现在跑了半个月了屁事都没有,好评。