我正在 SegmentFault 上录制一些 视频课程,欢迎购买收看,这是支持我创作更多技术内容的好机会哦。
基于业界最成熟的加密和版本控制工具 —— GPG 和 Git 的密码管理器:Elecpass

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

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

2018/skylines-overview.png

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

2018/skylines-budget.png

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

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

2018/skylines-overpass.png

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

参考链接:交通规划指南

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

2018/skylines-crossing.png

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

2018/skylines-mass-traffic.png

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

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

2018/skylines-map-view.png

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

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

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

2017 年度小结

2017 应该算是我自己主动地寻求变化的一年,最后确实也发生了很多变化。

在三月份我随一个对旅行十分有热情的同事,和其他三个之前并不认识的女生,一起去潮汕玩了三天、吃牛肉火锅。在他们几位「老司机」和资深吃货的带领下,每天就是吃吃逛逛,安排得很满,也非常开心,这几乎是我参加的体验最好的旅行了。以前我并没有听说过潮汕牛肉火锅,刚吃的时候确实让我眼前一亮,后来在北京又吃了几次。

之后,因为实在不能继续忍受楼下和窗外的噪音,搬家到北京的计划被提前了,我四月中旬就去了北京,开始找房子。北京的房租果然很贵,调研了一周之后我选择了位于东四环、距离公司 7 公里的一个两居室,每月房租 5290。这个房子位于一个九十年代的小区,离地铁将近两公里,房子很旧,户型设计也不是很合理,但这个价格相对于两居室还是可以的。

之所以会选择两居室的房子,是因为我在北京找房子期间,在一个 AirBnB 的合租房间住了五天 —— 我睡在卧室、主人睡在客厅的帐篷里,体验还相当不错。于是我想能多一个闲置的房间还是会有很多可能、发生很多有趣的事情的,可以提供给来北京的朋友同事、也可以在 AirBnB 上出租,况且这个两居室并没有比一居室贵太多。

在订好房子之后我就返回昆山准备搬家了,相比于上次从上海搬离番茄土豆,这次我的家当要多了不少,而且距离还有几百公里之远。经过了几天的打包之后,我用物流将十几个箱子发出,然后带着皮蛋豆腐乘坐飞机一起前往北京。来到新家之后皮蛋豆腐依旧吓得不轻,但它们现在有了更大的阳台,后来甚至被允许在床上睡觉。说起来皮蛋豆腐已经两岁半了,明显地感到它们不如小时候那么充满好奇心了。

那个闲置的卧室后来确实派上了很多用场,在五月到七月,先后有四个朋友来北京住在了这里,也在 AirBnB 上租给过一个黑人小哥五天时间,那两个月安排得可是非常紧凑,再之后蛋黄就搬了过来,变成了她的房间。

在来北京之前,我对北京的交通也是有所耳闻,因此我准备把自行车作为首选的上下班交通工具,今年共享单车的发展速度非常迅猛,但上下班高峰时车子依然难找。所以我还是购入了一个小米助力车,从堵死的汽车旁边骑过去还是非常开心的,单程交通时间可以稳定控制在 35 分钟。但北京的空气和气候、交通和人流、同等价格下更差的居住条件、作为政治中心生活上的不便和精神上的压抑,还是让我想要离开北京,目前明年搬回昆山的计划已经在进行了。

在搬到北京没多久,就在 QQ 群 中认识了蛋黄,在七月初为了和她见面,计划了一次十多天的行程,从北京出发去了沈阳、昆山、南京,再返回北京。见面之后我们发现了很多的共同点,她也愿意搬来北京和我「合租」,从一个人变成了两个人,这是今年里最大的变化。

从今年四月份开始,已经凉了两年多的密码货币又迎来了一波新的行情,一直持续到年末,我甚至觉得这一波行情不会结束了。在 2016 年初我为了写 BlockChain 与 Ethereum 介绍 这篇文章,以 40 CNY 的价格购入了 40 个 Ether,而它的价格在今年翻了 80 倍,比特币的价格也翻了 20 倍。当然这些币我并没有全部持有到最后,否则恐怕我现在 95% 的资产都是密码货币了。

继去年开始尝试参与线下活动和线上的直播之后,今年我参加了三次线下活动、在 LeanCloud 进行了两次线上直播、还在 SegmentFault 尝试了一下付费的直播课程,进行了三次直播,可以说现在对于小规模的线下活动已经不会有一开始那么紧张了。

去年在昆山将近两年的时间,完全没碰过灶台(也没有锅),搬到北京之后在宜家买了一些厨房用品,在蛋黄的指导下,尝试了烤鸡翅、番茄意面、咖喱饭、水煮毛豆等等,偶尔自己动动手还是挺有意思的。就像我在 标准化商品构成的世界 中描述的那样,我非常喜欢宜家的风格,喜欢一贯的设计和稳定的质量(虽然不高)。今年搬到北京之后,一共去了六次宜家,我想之后再搬家的话,一定会买更多的宜家的商品。

在北京稳定下来之后,又买了水弹枪、小票打印机、超声波清洗机、小四轴飞行器之类的小玩具。当然花费最大的还是乐高,一共买了四套,我喜欢乐高这种整整齐齐、自由组合,还能满足收藏欲望的玩具。

12373

精子生于 1995.11.25, 21 岁,英文 ID jysperm.

订阅推送

通过邮件订阅精子的博客日志、产品和项目的最新动态,精子承诺每一封邮件都会认真撰写(历史邮件),有想和精子说的话也可以直接回复邮件。

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-SA 3.0  授权发布。最后生成于 2018-06-13.