Thoughts, stories and ideas.

在 BearyChat 写代码是一种什么样的体验?

本文作者@hbc

  • 代号 xx,江湖人称老司机
  • 倍洽(BearyChat)后端工程师
  • 不爱吃福建人

本文起因于一个关于「创业公司技术选型」的提问。在大约两年前,熊小队 CTO 唐晓敏同学的 一个专访 里我们曾提及过这个问题,两年后再回顾,hbc 同学也从另外的视角为熊小队再次补充了答案 :P

工程部门概括,or: BearyChat 码仔的搬砖日常

BearyChat 工程部门可以按照不同方法来划分(当然人还是那么多的人)。
传统方法就是按照不同的职责来区分:

  • 前端天团
  • iOS/Android 客户端天团
  • 后端天团

顾名思义,不同天团的同事会负责对应方向的工作。例如我大部分时间都是作为后端天团的代码仔来上(搬)班(砖)的,所以主要会关注后端开发的事宜(下面也会介绍一下我们的后端架构的情况)。

但又因为目前的 BearyChat 技术团队还不算十分庞大,所以不一定会强制各位工程师只能做某个方向的事情。所以还可以按照以下方式划分:

  • 公有云 SaaS 开发天团
  • 私有部署开发天团
  • 基础架构天团

也就是说,在 BearyChat 做码仔,很大程度上都要身兼多个方向的研发任务(我们有萌系前端工程师速成 Clojure 的「奇迹」,也有后端工程师轮番上阵用 Angular/React 写搜索输入框的惨痛故事)

因为 BearyChat 产品功能比较多,所以日常开发任务都会按照功能模块来划分、以小组方式来组织开发(当然会带上稀缺资源设计师 XD)。典型的日常会是如下:

  1. BearyChat 的研发在与一生的敌人产品锦鲤讨论新功能的产品需求(包括用户故事、功能设定、边界条件等杂七杂八的东西)后会产出一份基本的产品文档;
  2. 工程师会在一生的敌人的帮助下根据产品文档把任务分拆到自己喜欢的整理工具中去;
  3. (前后端)工程师、设计师会根据拆分好的需求尽可能地并行做开发、设计的事情;
  4. 工程师在领取一个任务之后,会通过 GitHub Pull Request Flow 的形式来提交自己的实现给相关同事进行 review (原则上不少于两个), 同时 pull request 会有专门的 CI 流程进行测试;
  5. Review 同事在 CI 测试通过后会进行 code review, 这部分主要就是在 tracker/BearyChat 上吵架了 :P
  6. Code review 完成后,最后一位 review 的同事会 merge 代码到主干,并把相关任务标记为已完成;
  7. 代码自动部署到 stage 环境,相关同事会在 stage 环境上进行集成的测试;
  8. 每周二、周四对主干代码进行 release, 上线

Take away:

通常一个简单的需求从确认到开发再到上线周期一般在半个星期到一个星期左右。这套流程的基本概念从 BearyChat 创建伊始就开始使用,改动不大,主要有几个重点:

  1. 坚持 code review: code review 能帮助工程师找到很多盲点,盲点可以是代码上的,也可以是产品上的。同时 review 的过程也有利于不同的同事熟悉整个业务。
  2. 完整的 CI/CD 发布流程非常重要:因为 BearyChat 本身是一个涉及多个大功能模块的复杂系统,所以单独的测试不一定能把所有问题都暴露出来,因此一个完整的持续集成流程和测试环境能帮助工程师尽可能快地看到功能在完整环境上的运行效果,大大缩短了问题发现、重现周期。

后端架构

TL;DR:

  • Clojure + Erlang
  • MySQL + Redis + RabbitMQ + Elasticsearch
  • Thrift + HTTP
  • Ansible + Docker + Jenkins

最初的架构设计

最初的架构设计可以参考 CTO 唐晓敏在15 年一次访谈 创业公司的技术选型 中提到的草图:

这套架构的特点就是:简单 + 可扩展。

目前的架构设计

BearyChat 后端可以划分成几个部分:

  • 核心数据储存
  • API 服务
  • 即时通信 RTM 服务
  • 私有部署服务

核心数据储存主要使用了 MySQL / Redis / Elasticsearch / products / elasticsearch . MySQL 储存了大部分业务数据(最大的消息数据表已经接近过亿条记录,所以不久之前我们也对该表进行了一次横向切分迁移,下次再给大家介绍相关细节)。 Redis 作为万金油既保存了部分非结构化的业务数据,也作为缓存用途保存了使用中产生的临时数据。 Elasticsearch 则索引了 BearyChat 的消息、文件等可搜索的实体。

Take away:

核心数据部分,BearyChat 采用了成熟的开源技术,好处就是大部分坑都被人踩过填过了,工程师不需要浪费太多时间就能得到很好的成效。

API 服务和即时通信 RTM 服务可以放到一起讲。这两个模块一「静」一「动」。

API 模块是没有状态的服务,对外通过 HTTP 协议暴露接口。API 服务主要用了 Clojure 来实现,跑在 JVM + Ubuntu 上。

RTM 模块则是一个大型的连接状态机,所以用了 Erlang 来实现,同时也是部署在 Ubuntu 上。对外可以通过 WebSocket / TCP 进行连接。

动静两个大模块之间使用了两种方式来进行跨服务通讯:

  • RPC (Thrift):服务间通过 RPC 调用来获取状态、信息
  • RabbitMQ:目前主要作为任务队列来使用,会把用户消息处理、搜索索引、推送等异步任务放到这上面来进行消费

Take away:

这两个模块从最开始设计就坚持以下要求:

  • 横向扩展(x 轴)要方便:API 服务无状态自然是简单的。而 RTM 服务因为采用了 Erlang, 所以在进行横向切分的时候也非常简单。
  • 纵向扩展(y 轴)要灵活:除了可以很方面加大机器性能来提升以外,还要在模块内尽可能地应用服务状态监控(目前使用了 ELK + Grafana stack. zipkin 等一大波 opentrace 工具正在路上)和进行业务解耦(API 模块正在做从 monolithic app 迁移成 micro service 的相关重构)。
  • 业务扩展(z 轴)要无痛:目前 BearyChat 产品是围绕团队这个概念来进行设计的,所以根据团队切分(公有云)、迁移到私有部署成本都会非常低。

私有部署服务主要用于 BearyChat 的私有部署实现、发布、交付。BearyChat 的 私有部署 版本和公有云版本产品功能基本相似,但会有其他针对大型组织体系实现的功能。同时因为私有部署环境不完全属于工程师可以直接控制的范围,所以目前搭配使用了以下技术:

  • Docker: 私有部署实例会通过 docker 镜像发布,同一个 docker 镜像可以单独作为完整服务启动,也支持作为集群服务的节点启动;
  • Ansible + Packer: 因为 dockerfile / docker composefile 本身表达语义和操作存在很大的局限性,所以私有部署镜像使用了 ansible 作为 provision 脚本、packer 作为 repreduceable/identical 镜像的构建器;
  • Jenkins: 目前私有部署的打包发布会涉及 BearyChat 所有模块,因此镜像的打包会通过若干个 Jenkins pipeline task 来完成串联、自动化。

Take away:

  1. docker 不保证兼容是 feature 不是 bug
  2. 构建复杂的工程需要关注变与不变的地方,通过把不变抽离出来能减少很多重复的步骤。在私有部署中,变的主要是核心模块的代码,不变(相对来说)的则是核心模块的底层依赖

展望

除了要为更多的用户提供更加高效的工作方法,BearyChat 的工程师也在以下几个领域不断推进:

  • 更加快速的产品迭代、交付
  • 微服务化 & GraphQL
  • 更加开放的机器人集成生态环境(机器人市场 & OpenAPI
  • 适应更加多组织企业的私有部署版本

在这个很详细的回答下面,我们也打个小广告:假如你认同 BearyChat 对高效工作的理念,也有兴趣做一些不那么 SoLoMo 但依旧很酷的东西,点击左下角「阅读原文」,熊小队正在寻找 Android 研发等小伙伴,欢迎随时跟我们联系 :P

comments powered by Disqus