我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。

入手 Tenda Nova MW6(Mesh 路由器)

我之前在用的路由器是 Synology 的 RT1900ac,当时非常粉 Synology,于是也想试试他们家的路由器。但现在看来这绝对不是一个正确的选择 —— 我既然已经有了 Synology 的 NAS,他们家的路由器上的很多功能对我来说就显得重复了。

Mesh Wi-Fi

RT1900ac 的信号覆盖也并不理想 —— 我仔细想了一下,信号强度不仅仅在于路由器的发射强度,因为接入设备(例如手机)也是要将信号回传给路由器的,有可能回传才是瓶颈。因此,更好的做法是提高路由器(AP)的密度,缩短和接入设备之间的距离。

于是,我发现了 Mesh Wi-Fi 这项技术,也称作「分布式路由器」、「网状网络」、「多跳网络」,对应 IEEE 的 802.11s 标准。简单来说就是一个网络由多个节点(例如 3 个路由器)构成,任意两个节点之间都通过无线(Wi-Fi)或有线(所谓「有线回程」功能)连接。这些节点提供相同的 SSID,设备会接入最近的节点,网络流量可能会经过多个节点之间的传递,最后到达外网网关(接宽带猫的节点)。与桥接网络相比,在多个节点之间可以无缝漫游(IEEE 802.11v/r)。

这样的技术解决了前面提到的问题,即提高路由器的密度、提高信号覆盖、缩短接入设备和路由器之间的距离。一开始我是想买 UBNT 的 AmpliFi,可实在太贵了。于是我最后买了 Tenda Nova MW6(3 个节点),大概是 Amplifi 三分之一的价格,算是 Mesh Wi-Fi 解决方案里价格较低的了,其实 Tenda 还有更便宜的 Mesh Wi-Fi,不过是百兆的。

信号覆盖

为了量化信号覆盖的改善效果,我在切换路由器前先测量了 RT1900ac 的信号覆盖情况(5G 信号):

图中的数字是信噪比,越高表示信号越好。可以看到之前的 RT1900ac 因为放在客厅,在卧室中的信号是非常差的。接下来再看一下 Nova MW6 的信号覆盖情况(5G 信号):

我实际上是部署了 3 个节点的,但似乎 NetSpot 只识别出了其中 2 个节点。可以看到因为卧室也有一个节点,所以卧室的信噪比从之前最低的 15% 提高了现在的最低 57%。

带宽测试

外网表现上,是可以跑满运营商宽带 85Mbps 的下行和 3Mbps 的上行的:

内网表现上,我分别测试了很多情况,需要注意的是受到我手里设备类型的限制,这些测试并没有充分地控制变量:

  • 设备 有线连接 到不同的节点,节点之间无线连接:300Mbps
  • 设备 无线连接(5G)到 不同 的节点,节点之间无线连接:200Mbps
  • 设备 无线连接(5G)到 相同 的节点,节点之间无线连接:200Mbps

关于运营商

从四月末从北京搬到昆山以来,在昆山办理的联通宽带的质量非常差,下行跑不到标称的 100Mbps 不说,上行在绝大部分时间只有 2Mbps,而且一旦跑满 2Mbps 就会「断网」一段时间。

之前的 RT1900ac 在这种异常的「断网」之后不会自动重连,每次都需要登录路由器管理界面手动重播 PPPoE,这也是这次换路由器的原因之一,新的路由器在这种情况下会自动恢复,但断网的现象依旧存在。

如果要是我之前一个人的话,估计会嫌麻烦凑合用下去,但在蛋黄的反复报修、投诉下 —— 问题依然没有解决,只不过了解了更多的内情。联通的宽带安装人员表示我们小区靠南的几栋楼容量不足,本不应该允许办理 100Mbps 的宽带,如今必须扩容才能彻底解决这个容量问题,并表示会帮助我向上级反映。

使用体验

Tenda Nova MW6 被设计成只能通过手机应用来进行配置,而且配置界面非常简单,不能设置频段、不能单独开关 2.4G 网络、不能看到路由器的负载情况(CPU)。不过这也意味着没有乱七八糟的广告植入和强迫用户使用的功能,至少目前是这样

设置界面中有一个「智能 QoS」的功能,按我的理解,这这里设置宽带的上下行带宽之后,路由器应该在带宽快要被用满时限制占用带宽(对于我来说主要是上行)过多的设备。但实际表现上并不理想,依然经常出现上行带宽被占满,导致下行也无速度的情况,不过我并不确定这是路由器 QoS 不起作用,还是运营商在带宽快要被用满时的特殊行为。

而且在固件升级的过程中遇到了一些问题,一开始我在只启动了一个节点的情况下进行了固件升级。之后连上其他 2 个节点后,就无法对剩余两个节点进行固件升级了,最后我不得不重置了所有的节点,然后单独对剩余的两个节点进行升级。

关于无缝漫游,在实际使用中确实 Wi-Fi 信号总是满格,不会出现桥接网络时在两个路由器中间时信号较差的情况。我在整个屋子移动的过程中一直开着 ping 发现在切换节点时会出现一秒钟的延迟较高(从 50ms 增加到 200ms)的现象,但因为缺乏使用场景,我并不是很清楚对于更上层的应用(例如游戏),节点会有什么影响,反正如果只是刷刷网页、看看视频,这个效果应该足够了。

理论上既然是分布式 Wi-Fi,单个节点失效不应该影响其他节点(官方的介绍上也是这么说的),但我实测发现接入外网网关的节点实际上是「主节点」,一旦主节点关机,整个网络都会失效(无法搜索到 SSID),无法进行内网通讯。

游戏:城市天际线(Cities: Skylines)

欢迎关注 [我的 Steam 帐号][1],截至本文发布时的总游戏时间为 186 小时

因为 SimCity 2013 的失利,2015 年发售的城市天际线可以说是目前最好的城市建造类游戏了,三年以来也保持着稳定的更新(虽然每个 DLC 的价格都和游戏本体差不多了),相比于 SimCity 2013, 天际线没有无趣的「城市间交互」,取而代之的是更大的地图。

天际线的经营要素并不是很强,只要前期不作死 —— 不规划远超实际需要的基础设施,就不太可能出现破产的情况,从中后期开始主要的重心还是在交通上面。当然不同的人在玩这个游戏时会有不同的目标,我的目标是在不使用 Mod 的情况下、在默认的游戏规则下,建造一个看上去符合「常理」、运转起来也符合「常理」的城市。

天际线在一些方面提供了非常细致的模拟,例如有对市民的人生阶段的模拟:先上小学、再上中学、再上大学或开始工作,最后退休在家;也有对于每个市民的住所、学校或工作地点和通勤路线的模拟,市民会在步行、公共交通、私家车之间换乘;对于工业区也会模拟每个工厂进货(进口或来自其他工厂)和出货(出口或去往本地工厂、商店)的交通路径;不同的工作对于市民的教育程度有不同的要求;治安、消防、垃圾等市政服务的覆盖范围是取决于交通状况的。

在道路建设的方面,天际线提供了很大的自由度,所有的道路都可以提升为桥梁或下沉为隧道,自带的曲线工具和对齐工具可以帮助你建造出完美的道路和立交桥,但游戏也在桥梁的跨度、角度、坡度方面进行了一定的限制,不允许建造不符合常理的道路。游戏提供了三种等级(分别为 2、4、6 条车道)的城市道路,和几种不同样式的高速道路,车辆会更偏好选择高等级的道路来行驶,这样你便可以通过建造不同等级的道路来引导车流。游戏也提供了单行线、人行道(可提升为天桥或下沉为隧道)、非对称数量车道、公交专用道等各种不同类型的道路以供选用。

游戏中后期的主要内容就是解决交通拥堵了,游戏中的车流主要集中在商业区和工业区,而居民区的交通需求大部分可以用公共交通替代。工业区因为本身有污染、重型卡车比较多,在规划上会和其他区域分离,直接接入高速道路。高速道路负责区域间的长距离交通,和城市道路的互通都要靠无信号灯、车流交叉较少的立交来实现,保证高速道路不会出现拥堵;当然也会有一些级别稍低的高速路,出于占地的考量,是会出现和城市道路的路口的。在城市道路中,通过分级的路网来控制车流,在高等级道路上使用较高的路口间距,在低等级道路上不再设置信号灯等等。

参考链接:[交通规划指南][2]

在最近的一个交通 DLC 之后,已经可以为道路设置「主干道」属性,也可以单独控制每个路口有无信号灯了。但我仍对默认的交通系统有些怨念,比如不能为路口设置禁止左转 —— 通常一条小路汇入主干道时是禁止左转的;游戏中的信号灯对于直行和左转是同时放行的;车辆的车道变换也显得非常不自然。

在公共交通方面,天际线提供了公交、电车、地铁、轻轨、铁路、轮渡、飞机、飞艇、缆车、出租车,不过在我的实际使用中觉得很多交通工具提供的能力是重复的,城市内交通只要公交和地铁就可以了,城际交通(游客)靠飞机和铁路,货运靠铁路和货运港口。游戏中公交和地铁的线路和站点都是要自己规划的(也可以调整每条线路配置的车辆数量),并不像 SimCity 那样只要放置车站就可以了。这个设计带来了很多可玩性,设计得好的公共交通系统和设计得不好的交通系统将会有非常大的差别,你需要观察已有线路的客流数据(每个站点的客流量和等待人数)去进行调整、规划新的线路。

我对公共交通的怨念主要在于我认为地铁和轻轨其实是完全一样的,为何不直接让地铁可以提升到陆地或高架呢?以及游戏默认没有提供地铁的换乘车站,所以要么站外换乘,要么多个线路共用一个站台(会非常堵);以及目前游戏中不同公共交通的换乘还比较弱,火车总站和公交总站规模过大,没有中等规模的车站。

天际线默认提供了 36 平方公里的地图,使用 Mod 可以扩大到 300 平方公里以上,几乎相当于一个真正的城市了。对于这么大的地图,游戏也提供了丰富的管理工具,你可以为每个建筑、道路、公共交通线路、甚至市民命名,也可以划分行政区,在不同的行政区使用不同的政策和税率。

图中的城市是我近期耗时几十个小时建造的一个城市,目前有人口 16 万人(相比于城市规模,天际线的人口数量不是很真实,严重偏少)。

[1]: http://steamcommunity.com/id/jysperm
[2]: https://steamcommunity.com/sharedfiles/filedetails/?id=439725721

2017 年度小结(技术方面)

从今年年初开始,我就尝试在业余时间和一个朋友开发一个容器平台,更多地是实验一些新的技术,也希望能够通过它将自己的一些小应用管理起来,在基本完成后可能会考虑开源。之所以说是实验是因为我选择了一个我几乎完全不了解的技术栈:主要编程语言是 Golang、只使用 Etcd 作为数据库、基于 Docker Swarm 管理容器。

不得不说 Golang 是一个非常难用的语言,在语言层面,为了所谓的「简单」而没有添加 异常泛型 这两个对于高级编程非常重要的特性;在生态上仍没有统一出一个包管理器,如果只发布编译好的二进制程序倒是没问题,但如果发布源代码的话,缺少统一的包管理会带来很多麻烦,以至于很多开发者选择将 vendor 直接包含在版本控制中。

在这个项目中,没有异常和泛型真的给我带来了很大的困扰,几乎一半的代码都在进行繁琐的错误检查,没有泛型则很难实现一些通用的函数,或者不得不进行强制类型转换。这让我觉得 Golang 的使用场景非常受限:因为有 GC,它难以胜任对实时性要求较高的底层的工作;又因为缺少高层次的抽象手段,不适合业务逻辑复杂的应用编程(例如 Web 后端),可以说不上不下,只适合于一些业务逻辑不复杂的中间件,或者一些客户端命令行工具(毕竟在三个平台下都没有运行时依赖)。

Etcd 是一个我之前没有接触过的数据库类型,它是分布式的键值数据库,可以在大多数节点存活的情况下保证读写的强一致性,也提供了事务、订阅修改、TTL、检索历史快照等功能。我在这个项目中直接使用 Etcd 作为唯一的数据库存储所有数据,也使用 Golang 对 Etcd 的 API 进行了简单的封装,以便更好地使用 JSON 和 Etcd 的事务。

因为毕竟是业余项目,这个项目一直进展缓慢,在今年的最后我还尝试在 Swarm 上实现高可用的有状态容器,例如 Redis 和 MongoDB。我在容器内用 Shell 编写了一系列的脚本,在启动时从 Etcd 获取集群信息和自己的角色,然后通过长轮询完成配置的切换,再运行一个 Nginx 将从节点的流量转发给主节点,容器的数量则由 Swarm 保证,实现了一个「自维护」的数据库容器。


在去年 Node.js 错误处理实践 的基础上,今年我又在继续探索错误处理和日志的最佳实践。之前的方法存在一个问题,即我特别关注于将错误对象原样地传递出去,但有时看到一个非常底层、非常细节的错误(例如 CONNTIMEOUT),则难以判断究竟发生了什么。虽然从异常的调用栈中可以看出调用路径,但并不能看到一些关键变量的值,例如这个连接错误是在请求哪个地址,主要参数是什么,这是因为在异常传递的过程中,我们并没有记录这个信息。最后只能得到一个非常细节的错误信息,而不知道这个错误发生在更上层的哪个环节。

于是我开始使用 verror 这个库,它最主要的功能是帮助你创建一个「异常链」,你可以在每个层级来向异常上补充路径信息(会被反映到 err.message 例如一个来自底层的错误信息可能是 request failed: failed to stat "/junk": No such file or directory 这样)。这个异常链信息也会和其他元信息一起以结构化的方式存储在错误对象上,这个库也提供了一些工具函数来获取这些结构化信息。我尝试使用 verror 来管理所有的异常,报告带有详细的、每一层级信息的异常。同时我也会向错误对象上附加一些元信息用来指示如何响应客户端、是否需要发到 Sentry、是否可以重试等。

除了异常,我也开始尝试使用 bunyan 打印结构化的日志,并存储到 Elasticsearch。通过 Kibana 的 Web UI 可以很简单地对日志进行筛选和查询,在排查问题时找到相关的那部分日志。对于一个既有的系统来说,调整异常和日志可以说是一个非常庞杂的工作,在调整的过程中也我也在不断地修正自己的实践,今年一整年我都在做这样的尝试。


对于一个稍微复杂一点的项目来说,并不是所有的数据都在事务的保护下 —— 其实很多互联网项目也并不会使用事务。这样就难免出现数据不一致的情况,这种不一致可能是数据的关系出现损坏、缓存和数据不一致,也可能是多种数据库甚至外部资源的状态没有同步。

今年我探索了解决这个问题的一种实践:编写脚本去自动地检查和恢复这种不一致,这种脚本是常态化运行的,例如我的一个项目中现在有 4 个脚本以每 10 分钟左右的频率在进行各种检查和恢复。这样不一致的数据会在很短的时间内被恢复(也会留下可查的记录),对于用户来说就是碰到问题的次数变少了,在一些重大的的故障发生时,这种脚本也可以帮助你快速地恢复服务。

这样自动地修复不一致也引入了一个问题:就是在核心业务中会不自觉地降低对一致性的追求 —— 反正有脚本来修复,问题不会暴露出来。目前只能是为检查和恢复的情况绘制图表,在不一致的频率超出预期时及时地发现。


因为云引擎的 负载均衡 逻辑比较复杂,之前是在一个开源的 Node.js 反向代理组件上进行了一些二次开发,但在高峰时的性能不是很理想,一直有想法换成 Nginx。于是今年年初我就开始基于 Openresty 用 Lua 重写了负载均衡组件,效果非常理想,只用了 Node.js 十分之一的 CPU 和内存,再也没有出现容量不足的情况。

原因当然是 Nginx 对内存有着非常细粒度的管理,只在请求开始和结束时申请和释放整块内存,也没有 GC,保持一个长链接几乎不需要消耗多少资源。Openresty 则将 Lua 嵌入到了 Nginx 中,在 Nginx 高性能的请求处理和丰富的 HTTP 功能的基础上,让你可以用 Lua 去实现一些逻辑,对于负载均衡肯定是够用了。


我之前一直有在使用 pass 这个基于 GPG 和 Git 的命令行密码管理器,并将密码仓库托管在 GitHub 上。之所以用它是因为它基于可靠的开源工具、本身也是开源的,同时它足够简单,简单到我不需要它也可以操作我的密码。

也一直有想法为它开发一个 UI, 于是今年九月我用 Electron 开发了一个名为 Elecpass 的密码管理器,在机制和数据格式上与 pass 完全兼容。之前其实我并没有用过 Electron, 但上手的体验还是相当不错的,没有遇到什么问题。因为 Electron 自带了 commonjs 的模块加载系统,也不再需要像前端开发那样复杂的构建过程。

目前 Elecpass 一共发布了两个版本,虽然还非常简陋而且有一些 Bug,但已经可以满足基本需求了,我自己也一直在使用,明年我应该会为它添加更多的功能。


今年年初腾讯发布了微信小程序,我代表公司在「小小程序,大有作为」的线下活动里做了一个主题为「在微信小程序中使用 LeanCloud」的分享,在准备期间我也了解了一下微信小程序。

可以说微信小程序就是腾讯为了在微信中构建一个封闭的「操作系统」的产物,但大家迫于微信本身的平台能力,比如用户信息、推送、支付,不得不使用它。作为一个平台,微信小程序绑定了一个数据绑定框架,也绑定了一套模板语言,同时和前端现有的工具链(编译打包)的整合也非常差,很难利用现有的 JavaScript 生态。作为结果,我相信微信小程序不会有什么技术层面的社区和生态,只能作为最末端的用户界面。


年初因为发现我司的 服务状态页 年久失修,我决心重写一个服务状态页,参考一下 GitHub 等网站。我希望它能同时展示三个节点的状态、能够展示过去一天的历史状态、允许运维同事在服务状态页上快速地发布通知。最后这个状态页也开源了出来,在 leancloud/leancloud-status

为了能够让服务状态页本身总是保持可用,我设计了一个比较有趣的架构:后端(检查器)分别运行在我们三个节点的云引擎上,交叉对所有节点进行检查,将结果和展示历史图表所需要的数据写入到 S3(或其他对象存储上);状态页面作为静态页面托管在 CDN 上,从 S3 分别拉取三个节点的检查结果和历史图表数据,对来自三个节点的数据进行汇总,决定显示为「正常」还是「故障」。

这样就保证了服务状态页本身的可用性和三个节点隔离,可用性仅依赖于 S3(理论上可以同时写入多个对象存储作为热备),检测程序又运行在我们自己的云引擎上(比单独部署在一台机器上更易于维护),架构又并不复杂。

为了在前端合并三个节点的时序数据并绘制图表,我其实是费了很大的功夫的,但在实际部署的过程中遇到了很多细节的问题,做了很多妥协。例如我们的美国节点到国内的访问一直不畅等等,最后并没有把我制作的历史图表展示出来。


之前几个北京的同事写了一个 聊天机器人 放在公司的 IM 上,每天看他们调戏机器人觉得挺幼稚的。但等我搬到北京之后也加入了他们的队伍,我给机器人加了几个有趣的功能,虽然实现上并不复杂,但你可以通过聊天的方式把它展示给别人看,也可以让别人参与人来,还是个非常有意思的事情。

首先我写了一个 帮助大家决定晚上吃什么 的功能,这一写我就来了兴趣,后来又写了 确认大家是否都准备好吃晚饭了帮助运维同事简单地更新服务状态页,还 为公司免费午餐的福利随机人选

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2025-02-10.