专业书籍精读 · DDIA · 第 4 章

编码与演化

Designing Data-Intensive Applications · Ch 4 · Martin Kleppmann · 2017

EN →

这一章讲什么?

你手机上的 App 差不多每周都在更新,可你去年发的朋友圈、三年前下的订单,今天刷开还得原样显示。这里藏着一个悄悄的难题:代码天天变,但旧数据得一直能被读懂。这一章讲的就是——数据在内存里是活生生的对象,一旦要存进磁盘、发上网络,就得先「压扁」成一串字节;而当代码升级了、数据的样子也想改一改时,怎么让新旧两代人还能互相看懂对方写的东西

先打个比方

把「存数据、发数据」想成寄快递:你桌上摊开的东西(内存里的对象)没法直接塞进邮筒,得先打包装箱——这一步叫编码;对方收到再拆箱还原——叫解码。麻烦的是,寄件方和收件方用的不是同一版说明书:你这边 App 已经更新、箱子里多塞了样新东西,对方 App 还是老版本、说明书上根本没这一项。怎么让老版本收到也不至于当场懵掉?这就是这一章的全部戏眼。

为什么这事儿这么拧巴

因为大系统没法说停就停、一次全换新。升级几百上千台服务器,只能一台一台滚着来(滚动升级)——于是必然有一段时间,新版代码和老版代码在同时干活。老代码写下的数据,新代码要能读(这叫向后兼容,照顾过去);反过来更别扭:新代码写下的数据,老代码也得能读,哪怕里面有它没见过的新字段(这叫向前兼容,照顾未来)。数据往往比代码活得更久——五年前入库的一条记录还躺在数据库里,早换了好几拨代码来读它。兼容做不好,升级就等于埋雷。

诀窍:给每个字段发个「行李牌」

聪明的格式(Google 的 Protocol Buffers、Facebook 的 Thrift)用了一个朴素到位的招:不靠字段的名字,而是给每个字段钉一个号码牌——就像机场托运,认的是行李条上的号,不是你行李箱长啥样。存下来的字节里只有「几号:什么值」,没有一长串字段名。这样一来:想加个新字段,就发一个没用过的新号;老代码扫到不认识的号,耸耸肩跳过去就是了,绝不崩溃。加字段、彼此不打架,靠的全是这块号码牌。唯一的铁规矩:号码牌一旦发出去,就永远不能改、不能换给别人用。

数据在哪三个地方「换手」

数据从一段代码流到另一段,无非三条路,条条都要过兼容这关:①存进数据库——写它的代码和以后读它的代码,可能隔了好几个版本;②调用服务——手机 App 发请求给后台,双方各自独立升级;③走消息队列——一个系统把消息扔进管道、另一个系统慢慢取,发和收根本不在同一时刻。只要收发两头版本可能不一致,编码格式就得替你把「互相看得懂」这件事兜住。

一句话记住

数据出内存前要打包成字节(编码),进内存再拆包(解码);难点在于代码在滚动升级、新旧版本同时在跑,所以格式必须让新代码读得懂老数据、老代码也扛得住新数据。靠的是给字段编号而非记名字——加字段用新号、老号永不复用,升级才不埋雷。

想看具体格式、字节怎么排、真实系统怎么用? → 切到精读版