一个服务架构的演化

都说架构是演化出来的,而不是设计出来的,有一定道理。

本文介绍一个服务系统如何从一开始的简单结构,逐步演化成为能同时服务上百
万人的系统。这个系统是一个通用物联网设备接入平台,设备可以接入平台,将
数据发往平台,或者将数据发给设备,也可以再平台内按照预定规则处理设备数
据。整个系统中,边缘网络和终端设备的工作很繁杂,但本文的重点是服务端的
演化过程。比较失败的设计就不多说了,能写一本书。有一定参考的价值,但如
果你想要做出一个一样的物联网平台,我可以给你一个忠告:这东西过气了。

验证

我们当然可以从头就设计一个一开始就能支持上百万并发的系统,但并没有这么
做,原因和成本以及时间有关。如果一开始我们就计划让10个富有经验的
序员开发半年以上,烧掉五百万才能见到第一个用户,那么创业第一年就死了,
因为根本拿不到投资。最开始,我们需要的是 "quick and dirty",能够验证有
足够多的用户对这种服务感兴趣并愿意花钱的东西。比如说,美团外卖从立项到
推出使用花了一周,就做了个订餐页面直接转客服。这个阶段做的东西只是为了
验证 idea 的商业价值,如果不靠谱,可以花很少的成本转向和关掉。所以,能
人工的就别写代码,有现成东西的就直接用现成的,能多简单就多简单。这个阶
段甚至很多公司根本不需要写代码,比如垂直电商,完全可以先用微信朋友圈和
excel 表格人工客服,如果靠谱能挣钱,再写代码也不迟。

我们的物联网平台,做了个最简单的能用的功能,就是 Rabbitmq+mqtt插件 套
个页面。这个阶段的东西不靠谱,但是能用。设计如图所示。

一个服务架构的演化_第1张图片

  1. Web 服务器用来给用户注册申请开通使用。
  2. 开通后 rabbitmq 上创建一个用户和命名空间给他,设备和手机可以使用
    mqtt 连接 rabbitmq 端口,实现数据互通。
  3. Web 也会连接 rabbitmq 接口,这样用户就能看到通过 rabbitmq 的数据了。

这个 Web 后台操作其实包括很多人工。实际使用中,这个 Web 只是用来注册和
配置,并发要求很低,在后续内容中,会将其忽略。上述架构只保留内核功能的
话,就只有:

一个服务架构的演化_第2张图片

简化之后的系统其实类似于即时通信,可以群聊或单聊,能查看历史数据,能看
设备列表。

这个阶段的服务有很多漏洞,但我们小范围验证后,马上就开始开发,抛
弃这个架构了。

MVP

MVP (Minimum Viable Product, 最小可行产品),是 Eric Ries 在
精益创业 里提出的一种软件产品开发迭代方法。简单地说,就是先开发一个满
足用户需求的最小产品,然后获取用户反馈,并持续迭代。它的目的是避免 "窝
在家里做没人要的产品,却自以为很有市场" 的问题。 MVP 过程中,必须每个版本
都是能用的,常有人误解为每个迭代版本做出一部分功能,最后才可用。

在这个物联网系统中,我们要求的最核心的功能是:

  1. 设备低成本快速接入互通。
  2. 隐私和数据安全。
  3. 开放和自由。

只要做到这几点,用户就能用,其他功能只能说是优化。核心功能先做出来,能
用就行,其他要求都不做,或者优先级调低,后续再做。这个阶段小需求变化非常
快,并发量还不在考虑范围内。

我们选用了市面上最常用的设备通信协议MQTT,因为几乎所有模组都有这个协议
的开源适配,我们通过几条规则,做好身份认证和加密即可。 Rabbitmq 虽然优
秀,但实际使用时并没有满足我们功能要求,所以自己开发了 MQTT Brocker。

一个服务架构的演化_第3张图片

如上图所示,我们开发了自己的设备接入服务。MySQL 用于存储设备列表、认证
信息、事件、设备数据。还有其它服务用于外部系统对接,满足"开放和自
由"这个要求,但这不在本文重点讨论的范围中,就先忽略了。

这个简单架构持续了很多个功能版本,直到我们推广阶段实在没办法满足并发要
求。

纵向扩展和横向扩展

这里,纵向扩展 (vertical scaling, scale up),指的给服务器是加 CPU,加
内存。横向扩展 (hozizontal scaling, scale out),指的是添加更多服务器。
早期并发不多的时候,纵向扩展是个不错的选择,但纵向扩展是有极限的:

  • 一个服务器能使用的CPU和内存数量是有限的。
  • 这一个服务器挂了,就全完了。

如果想要上百万并发,只能考虑横向扩展。

负载均衡

前面设备服务程序是单个服务器上的,早期迭代速度非常快,每天一个版本,更
新时经常重启这个程序,重启这段时间所有设备连接都会断开,连不上服务器,
直到重启完成。同时,这个服务程序承担了太多计算任务,包括加密解密、身份
认证、数据转发、数据转换规则匹配等,很快就将服务器的 CPU 消耗殆尽。于
是我们打算使用负载均衡器横向扩展它。

一个服务架构的演化_第4张图片

如图所示,负载均衡器会把连接分配到多个服务器上,这样就分担了单个服务器
的压力。当一个服务器重启时,负载均衡器也会将这个服务器的连接分配到另一
个服务器上。当两个服务器压力还是太大,也可以再添加一个服务器。

设备服务经常需要将一个设备的数据发送到另一个设备上,或者将一个设备的数
据发送到另一个设备服务以完成预定规则的匹配计算。这要求增加一个所有设备
服务都共享的信息:一个设备在哪个设备服务器上连接。我们使用 redis 来记
录这个信息。

使用时序数据库

时序数据库非常适合用来存储设备上传的数据。我们当时使用的 MySQL 并不适
合。当时一万个设备,一天就能上传上亿条数据了,放在 MySQL 里面索引都建
不起来。根据我们的使用习惯和扩展要求,我们使用 timescaledb 来保存设备
上传的数据。

一个服务架构的演化_第5张图片

如你所见,我们使用 timescaledb 来存储设备上传的数据点,系统运行中记录
的事件(离线重连升级等)。MySQL 用来存储设备列表、设备关系、用户关系、认
证信息。Redis 用来记录设备目前在哪个设备服务上连接。

数据库横向扩展

系统不久遇到了数据库瓶颈。

分布式数据库是个不错的选择,奶飞 选用 casandra 作为数据库系统,看起来
很稳定并发也没什么问题。对于大多数公司而言,MySQL一主多从能满足要求。
再这个物联网平台中,MySQL数据库时写少读多的场景,同时考虑到迁移成本,
我们决定使用 MySQL 主从读写分离,只需要加个 mysql 代理。

一个服务架构的演化_第6张图片

MySQL 可以通过binlog实现主从同步,再使用一个 mysql 代理,将 SELECT
求都分配到从数据库,将 UPDATE 请求都分配到主数据库。

后续如果设备数量增多,增长到接近亿时,就需要考虑分表 (Charding),这个
也可以使用 mysql 代理实现。但最终我们的系统并没有达到这个量级,没有分
表的必要。

使用缓存

数据库读写分离以后,我们发现一些请求非常频繁,导致数据库压力还是很大。
这些请求的数据通常变化较少,但是查询频繁,如用户和设备的绑定关系、设备
之间的通信关系等。我们决定将这些请求的数据写入到缓存中,以减少数据库压
力。

我们使用 Redis 作为缓存,每条数据都设置有效期。每次查询请求时,先查询
redis,如果有数据就直接返回;如果 redis 里查不到数据,就去 mysql 查询,
查到以后再将数据写回 redis,再返回。修改数据库同时,也修改 redis 内容。

一个服务架构的演化_第7张图片

这有几个问题:

  1. 一致性。一个修改请求,改动 redis。同时另读请求,redis没有数据,查
    询mysql写回。这样就可能造成 redis 里面存储旧数据的情况。只能等到数
    据缓存过期后才会同步。触发频率和影响范围在我们可接受的范围内。
  2. 缓存穿透。查询的数据不存在,会一直查询数据库。我们通过无效的key存储
    "null" 值解决了这个问题。

使用 CDN

我们接入方案里面包括了设备在线升级功能,设备可能再一段时间内大量请求下
载升级固件文件,占满了服务器流量资源。于是我们决定使用 CDN。

一个服务架构的演化_第8张图片

CDN 负责接受固件下载请求,如果文件在CDN服务器上,就直接返回,如果不在
服务器上,就去后端真正的服务器上下载后,再返回给设备。使用 CDN 减轻了
固件下载时我们服务器的压力,同时 CDN 也加快了文件的下载速度。

CDN 是三方服务器,如果请求太频繁,像物联网这样的场景,那么多设
备同时下载,可能会被当成 DDoS 攻击,还是要适当分配下载时间。对文件
的数据签名也是必要的,被人偷摸修改过的固件运行在那么多设备上,是个很麻
烦的事情,很可能需要跑到客户厂区跪着人工把设备拆开,再把正确的固件刷到
设备上。

拆分服务

原本设备服务包括了几乎所有的平台功能,随着功能增多,很多小的需求变化都
需要更新整个程序,部门间合作,以及更新程序时造成了很多麻烦。我们决定将
其拆分,首先拆分为三个部分:身份服务、接入服务、分析服务。

一个服务架构的演化_第9张图片

身份服务负责确定设备、用户之间的关系和权限;接入服务负责设备接入和即时
通信转发;分析服务负责即时或离线的数据分析。虽然这样划分是为了协调方便,
对性能和效率并没有多少提升。

横跨机房

承诺全球可用的服务,总要提供跨机房的方案,这是个不小的挑战。

一个服务架构的演化_第10张图片

我们使用 DNS 服务分配IP地址,设备开机会先请求DNS,从域名获取一个 IP 地
址。DNS 根据设备所在地区,返回一个最近机房的 IP 给设备,设备通过这个
IP 地址连接。

跨机房通信通常比较慢,经常涉及到跨国通信的延迟,所以不能像单个机房中一
样同步所有的数据,只能同步一部分。我们将设备认证信息、历史数据分配、绑
定关系等存储到 NoSQL 数据库中,并开启多实例同步。当设备转移了地区,或者
一个机房被炸了,这个设备就可以接入到其它机房。这样的转换有可能会丢失部
分数据,这部分不影响基本共功能使用,也可以花时间离线同步过去。

阅读原文

你可能感兴趣的:(后端iot物联网)