何为后端开发?以一个网站为例,通常来说,前端研发注重页面的展示,交互逻辑。而后端研发,则注重在发生在前端背后(backend)的逻辑上,例如给前端返回数据,存储数据。对于一个电商网站,一个简单的下单动作,后端可能包括商品数据查询,优惠信息计算,库存维护,用户优惠券维护,订单生成,商家通知触发等等。在很多大公司前后端的配比是1:3甚至更高,因为一个复杂的业务系统,前端的展示仅仅是冰山一角,更复杂的业务逻辑都隐藏在后端。通常来说,当用户触发某个行为后,客户端会通过http/https请求,和我们的服务器建立连接,发送请求,往往这个请求首先会被链接到负载均衡(LB)上,负载均衡根据配置,将请求转发到内部的API服务上。这些API服务,根据不同的业务逻辑会请求其他服务(Service),这些服务各司其职,例如读写某 Mysql 表、读写缓存,甚至请求搜索引擎来完成相应的任务。而API服务在完成相应的步骤后,也会将数据返回给客户端,客户端根据前端逻辑完成相关的展示。下面这个图,简单的展示了服务端研发可能使用服务组织方式和相关技术栈,后续会对所有技术栈和大厂使用场景一一简述。
上面提到,如果只依赖 Thrift 我们可以实现通过指定IP端口的方式进行服务调用,但是显然这是不可行的,我们需要 Client 动态感知 Server 端服务的存在以及提供服务的所有实例。 服务注册中心就是解决这个问题而诞生的概念,可以简单理解注册中心就是一个保存着服务状态的”数据库“,服务成功启动后到注册中心去注册,并且保持和注册中心的心跳以维持服务在注册中心的新状态,当服务下线或者异常退出,服务可以主动通知注册中心下线或者被注册中心通过心跳失败感知到。 常见的服务注册中心例如 Spring Cloud 框架中官方提供的 Eureka,Dubbo 默认使用的 Zookeeper。Spring Cloud 和 Dubbo 也对 Consul 增加了原生支持,这里也主要介绍下Consul。具体对比可以参考[1]
Consul
Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。如果使用 Spring Cloud 或者 Dubbo 等微服务框架,可以通过配置实现使用 Consul 作为服务注册中心,服务启动后,在Consul提供的Web界面就可以查到相应的服务。服务客户端可以在次调用服务端前,通过Consul进行服务端实例的查询然后按照查询奥的服务实例进行远程调用。 Consul 的 web界面:微服务框架开源社区中知名的微服务框架包括 Spring Cloud, Dubbo 等,这些框架配合生态中的一切其他组件可以解决例如服务注册&发现、负载均衡、熔断限流、调用链追踪等绝大多数问题。不过当业务发展到一定阶段,还会有更多问题要解决,例如服务调用鉴权等问题。所以各个大厂几乎都自研自己的微服务框架,但是基本的做法都是在开源社区选择一部分,自己扩展一部分,例如通讯协议和RPC选择Thrift,服务注册中心选 Zookeeper/Consul,然后需要自研扩展的部分就是例如服务注册,服务发现,负载均衡,统一监控,鉴权等公司特色的需求。
数据库(Database)
对于一个小公司而言,可能会选择把所有数据保存在 Mysql 中,因为全部业务数据的容量可能也只有百G。但是对于大厂,每天产生的数据可能都是T级别的,如果都保存在Mysql中会有诸多问题,例如存储成本、后续的修改查询效率、高并发场景下存储的极限能力等。数据有它不同业务特性和使用场景,业务特性很好理解,例如我们不容忍交易数据发生丢失并且在很多操作它的场景,要求强一致性;而用户评论,则能容忍很小比例丢失,并且评论计数器和评论数目之前的如果出现微小差距,用户也很难察觉到;而服务日志数据,则能容忍更大程度的丢失,只要不影响开发Debug可能就不会有人追究。数据不同的使用场景,也对存储有不同方面的要求。例如同样是用户资料,用户资料查看自己的资料,一定要保证资料是用户新更新的,并且不能容忍出错,哪怕是页面相应速度略微慢一点;但是用在推荐场景作为用户画像作为模型输入的时候,就能容忍这个数据不是新的,但是要求数据访问速度要高,因为推荐场景往往对成千上万个候选排序,画像数据访问慢则直接拖累了整个推荐系统的效率。
Mysql
对于Web应用而言,关系型数据库的选型中 Mysql 无疑是佳选择。它体积小、速度快、开源授权使得它拥有成本低,支持多种操作系统,有丰富的社区支持,支持多种语言连接和操作。Mysql是保存业务数据的理想之选,首先关系型数据库在设计之初,从概念上就在支持业务数据建模的概念,关系型数据库能结构化的描述业务需求。Mysql 对ACID属性的支持,也能满足不同业务场景下对数据操作的不同诉求。但是在大厂背景下,Mysql也有它的限制。首先,对于在线大表DDL几乎不太可能,DDL指表结构变更之类的操作。对于一张动辄几千万数据的表,Alter Table 可能需要几小时,除非提供备案方案,否则服务在这段时间将不可用。其次,在线查询要注意性能优化,避免慢SQL拖垮DB的场景。常见的性能问题包括查询未命中索引而触发全表扫描;使用了聚合查询(group by)触发全表扫描等。 还有,大厂特别是ToC常见的大厂,每天产生的业务数据异常的大,Mysql存储超过几千万性能会下降,所以需要使用分库分表的方式来解决海量数据场景下的存储问题。
Mycat
Mycat就是一个解决数据库分库分表等问题的数据库中间件,也可以理解为是数据库代理。在架构体系中是位于数据库和应用层之间的一个组件,Mycat 实现了 Mysql 的原生协议,对于应用它感知不到连接了 Mycat,因为从协议来讲,两者是一样的。 而Mycat将应用的请求转发给它后面的数据库,对应用屏蔽了分库分表的细节。Mycat的三大功能:分表、读写分离、主从切换。 下图通过 Mycat schema 配置和实际 DB 部署来简要说明 Mycat 分库分表功能的实现:例如表 A,配置中它有两个数据节点,分别是 datanode1 和 datanode2,含义是逻辑表 A实际对应了两个物理存储,分别是 db1 和 db2。对于应用而言,应用连接 Mycat 感知到的时候逻辑表 A,而在底层 A 被拆分,存储在两个 db 中。
DRC
DRC 是 Data Replication Center 的缩写,在使用Mysql作为核心存储的场景下,我们可以使用Mysql原生的主备方案,实现同城灾备。但是如果 Mysql 部署在跨国,跨洲的场景下,原生的灾备方案就有诸多问题了,所以各大厂几乎都有自己的DRC方案。 不过,虽然各自有不同的实现,但是原理和依赖的核心组件基本相同,本文从互联网上找到饿了么DRC组件阐述其原理。本图中,异地机房分别为北京机房和上海机房。本地机房(图中为北京机房)会启动一个 DRC Replicator,它和Master节点通信并在通信协议上模拟 Mysql Slave,Replicator将Master数据库的binlog变更实时拉取到本地。然后把binlog解析,通过消息中间件将变更发送到异地机房(北京机房)。异地机房启动一个DRC Applier的应用消费数据变更消息,然后把这个变更操作同步到本机房的Master上,就完成了异地数据同步的操作。图中展示的是北京机房数据同步到上海机房的场景,实际反过来也是一样。DRC在设计和实践中常见的问题就是DB自增类型主键冲突,以及数据因为同步消息丢失而后导致的不一致,前者可以通过强制使用ID生成器或者自增ID设置相同的增加值和不同的初始值等方式解决。而后者要么采用一个规则同步终数据,或者进行人为数据干预。
前文讲述了 Mysql 和 Redis,或许对于大多数公司,这两类存储已经足够。前者用于保存业务数据,后者用于集中式缓存。但是对于大厂,还有若干场景上面两种存储无法满足:例如推荐系统在线预测场景,需要将用户画像、商品画像、商家画像、用户商户交叉画像在线加载预测上下文,特征处理后给到模型做预测打分。ToC的互联网产品的注册用户很可能过亿,所以用户画像总存储很可能百G甚至T。如果是这样规模的数据,Mysql的读性能肯定扛不住在线预测场景;Redis 是内存缓存,存储昂贵,同时在容灾恢复时候,Redis需要将AOF或者RDB数据载入内存后才能提供服务,数据量过大需要很长的恢复时间。所以需要另外一种存储能解决这个问题。几乎所有大厂都有属于自己的KV-DB,例如360开源的Pika,饿了么通过购买Tikv封装而成Ekv,字节跳动的 Abase。 Pika 和 Tikv在存储底层都使用了RocksDB作为数据存储,而RocksDB它是将数据存储在硬盘上的,Pika 和Tikv在上层构建的都是集群化方案,主从模式等,基于内存的一致性Cache等。下图是Pika架构图: