2010 年南非世界杯,「预言帝」章鱼保罗抢走了原本属于球星的风头,它精准的预测了德国国家队的比赛结果;现实中,我们每个人也会对比赛结果进行娱乐性的预测,关注自己喜欢的球队,而有这么一家专业的公司,却利用着大数据、人工智能、神经算法来给体育比赛结果预测,对体育进行服务 —— 智慧摩羯数据科技有限公司。
智慧摩羯数据科技有限公司,是一家专注于体育运动大数据分析服务的创新型科技公司,以赛事数据为基础,以大数据分析为引擎,运用机器学习、深度学习等人工智能算法来实现全球体育比赛结果预测、体育运动指导、电子竞技游戏支持等服务。
智慧摩羯数据科技有限公司联合创始人陈健,分享了如何解决随着业务发展,系统越来越臃肿、应用变得越来越不易维护、不易扩展等问题,并分享智慧摩羯在搭建企业微服务中的相关经验。以下为演讲正文:
大数据分析技术是企业的核心竞争力。主要的技术分为两部分:
- 第一部分是大数据技术;
- 第二部分是应用的技术,即大数据服务和应用服务。
大数据服务跟绝大多数公司差不多,首先是数据采集,我们有很多爬虫,可以直接在公网爬取全网数据,爬取到全网的体育数据后,我们对数据进行分析,然后就是数据处理和建模。早期我们通过大数据分析技术把这些数据跑出来,进行建模,进行结果预测。现在我们开始进行一些更深入地研究,像机器学习、深度学习,还有神经网络等相关算法都已经引进并运用。希望通过这些人工智能算法更智能、更科学地预测一场比赛,或者对体育进行服务。
正是基于核心技术,智慧摩羯布局了三条产品线:
一是体育比赛预测,就是刚跟大家分享的,通过去全网扒数据,运用人工智能算法分析进而预测体育比赛结果。
二是体育运动指导,通过收集所有运动员及体育爱好者的运动数据,对其进行专业地指导。
三是电竞游戏支持,我们发现电竞其实也可以进行各种各样的数据收集、分析和预测,智慧摩羯可以为电子竞技类游戏提供专业的数据支持,让游戏更加专业、真实和有趣。
技术架构
第一部分是技术的演化过程,主要讲种子期和成长期。
早期是把代码放在 GitHub 上,为了把业务拆得足够细,所以创建了很多代码库,但智慧摩羯当时只有一两个后台工程师,很快就拆出超过 10 个库。这样维护起来成本太高,代码重复,且没有精力合到公共的代码库里,我们发现这条路走不通。
这就是智慧摩羯早期的架构,特点是代码库特别多,我们有前端负载均衡,有 API 服务,有消息队列服务,定时任务服务,这时候 Redis 是单节点的,MySQL 也是单节点的,是很简单的架构。实际上代码库多对我们来讲是比较重的事情,然后我们就把多个代码库合成一个,解决公共库的问题,代码的冗余度降低了。虽然单个数据库根据业务划分多个 schema,但是还是在一个数据库里面,然后单个 Redis 节点,存放所有的缓存数据,这样也可以降低成本。
问题就是单体工程越来越庞大,一年我们就做了几十万行代码的量级,还是一个工程代码库。还有一个特点就是文档少,重复代码多。由于开发人员增加了,从两三个人变成了十几个人,导致重复代码越来越多,因为不是每个人都对代码理解,文档少,必然就会理解少,重复的代码多。数据库无法根据业务分摊压力,单失效节点多,事故也频繁出现。
这是智慧摩羯这时简单的架构图,GitHub 是单一的节点库,增加了很多运维脚本,当时 VM 在 QingCloud 上就有几十台。
微服务解决方案
没有问题就没有动力去改变这个事情,所以我们先来看下有哪些问题。
第一个特点是单个节点的数据库承受所有的应用层压力,没有进行拆分。这时候数据库其实是最大的瓶颈,一旦它发生阻塞,整个服务就瘫痪了。因为所有的连接一直在那儿阻塞,没办法接新的连接,整个服务就是完全阻塞的状态。
第二个特点,单个 Redis 承受所有的缓存的数据,服务瘫痪,恢复时间慢。单体工程代码量非常庞大,新人头疼,无从下手。开发人员变多,出现的事故概率增大,系统升级的风险变高。
代码的冲突也频繁发生,这时由于每个人不太了解,合并的时候频繁出现代码被覆盖的情况,我们的开发分支跟线上稳定分支总会出现各种各样的问题,升级的风险非常高。应用层都部署在 VM 上,上百节点的 VM 没有分开,这是我们的问题。
针对以上问题,两个核心的解决方案:第一个是隔离,第二个是解耦。一部分是静态的部分,就是代码的部分。然后动态的部分就是运维,如果不谈运维、不谈代码就有点空。首先是隔离,工程代码要隔离,然后代码跟代码之间不要互相干扰。第二个是运维事故要隔离。然后是解耦,代码要解耦,系统升级要解耦。
一个目标就是要放权,我对微服务的理解就是产生很多的子服务以后,首先你的代码库拆掉,之前代码库的管理权限是给到整个研发部,很多事情需要统一的授权。但是代码库拆掉后你就可以把权力下放到各个业务小组,由他们来决定代码的合并和代码的系统升级,这样的话就等于把这个权力下发下去了。整个微服务实施的步骤是数据库的 schema 拆分,接口流量拆分,微服务的注册发现,管理服务的生命周期。
首先是代码库,我们先把 GitHub 的代码库由一个工程 fork 出来很多子工程,其实都是一样的,因为短时间内如果想以最快的速度把工程拆掉,这是最简单的办法。
这是第一点,然后把一些公共的合起来,剩下的全部拆掉。拆掉的过程中肯定会有一些跟这个业务不相关的代码,但是至少保证了每一个工程可以独立的进行分支合并。
第二点是我们的数据库进行了比较大的改动,原来可能是单个数据库,就只有多 schema 的架构,现在我们把原来的数据库拆成八个,这些都是做事务的数据库,然后用 MariaDB,因为 MariaDB 支持多元复制,把这些子数据库,这些不同的 schema 合到一个数据库里面,这样可以把数据库分开。
另外就是分表的问题,我们通过阿里开源的 Otter 来解决,实际上是伪装成了一个 MySQL 的 Slave,不停地从 Master 里面同步数据。然后可以建立多张表跟一张表的映射关系,建立完毕以后把多张表合成一张,这样我们有分有合,用户查的是分表数据,但是我们后台统计的是合表数据,合表数据可以提供给管理后台的人员用,给做运营、做分析的人用,这样也能把数据库压力分散掉,线上就是线上,我们自己后台的分析就是后台的分析,把 OLTP 和 OLAP 分离。
我们规范了合并流程,原来只有两个分支,一个是 Master 分支,一个是 Develop 分支。相信大家比较了解,Develop 分支就是开发分支,主要开发新的 features,比如说我们要开发新版本,所有的 features 开发完毕以后,就会进入集成测试的分支。但是开发人员在测试阶段还可以改代码,这个事情如果没有规范好,会导致测试人员做很多无用功。最后我们觉得需要给测试人员足够的权限,他们来管理一个单独的分支。
这样的话所有的开发人员往这个分支合并代码的时候必须经过测试人员的同意,测试再去测这个独立分支的时候,如果测完没有问题,再往 Master 合并,能保证测的代码就是要升级的代码。同时为了保证流程,如果线上发现了一个 bug,应该以 M 打头,通过它 fork 出来一个子分支,然后往三个分支同时进行合并,依次合并。这个代码的分支合并的规范流程,其实对我们来讲还是很重要的。这里面解决了很多开发人员不规范,乱提交代码的问题。这个很有用,能保证代码的质量。
接着我们对线上所有的服务进行拆分,拆分之前我们就是一个单体服务,很麻烦。如图:
最前面是负载均衡器,这个就不用说了,用的是青云的负载均衡器。在负载均衡器后面,我们自己加了一层是 API gateway,我们把接口、定时任务、消息队列等这些模块按照功能为单位,依次进行划分,划分成几百个服务。
这几百个服务没必要每个服务一台机器。对哪些服务需要访问哪些资源连接进行整理归类,比如有的是访问用户的数据库,有的是访问订单数据库,但是他们访问的时候也会分读写,到底是读数据还是写数据。
然后我们再把所有的这些几百个服务、接口,每一个都仔细过了一遍,然后把哪个该走读,哪个该走写,全部归好类,把所有的配置配好,然后由 API gateway 转到不同的机型,可能配的是读库或者写库,把线上的几百个接口拆成几百个服务,每一个服务能做到对其他的服务最小力度地干扰。
原来数据库阻塞了,然后应用层组塞,整个用户就阻塞了。现在变成了我们可以逐步降级服务,给用户提供更稳定可靠的服务,不至于出问题。
这是我们用青云的 AppCenter 2.0 做的这个事情。在去年青云的 AppCenter 2.0 发布会上,我觉得能够帮助智慧摩羯实现业务。
我对它的理解就是,它在定义角色,定义生命周期。里面包含了存储的角色,计算的角色,包含 RDB、包含 Redis、包含 Zookeeper,可以包含很多的角色。然后这一个角色可以定义很多参数,因为这些参数就是配置参数,原来的配置参数可能不统一,比如说 Redis有 Redis 的配置参数、Zookeeper 有 Zookeeper 的配置参数。
整体的把所有的配置参数都抽象出来,然后生命周期里比如说服务的创建、服务的销毁、服务的启动、服务的关闭、服务的 Scale In&Scale Out,还有监控,健康检查,每一个过程定义好以后,这些过程都能够触发我们提前定义好的脚本,这个脚本能够在生命周期里做它该做的工作。
然后我们把我们的服务注册在青云的 AppCenter 2.0 里面,当服务启动以后我们的服务之间怎么访问呢,这是依赖于我们用的 consul 的框架,每一个节点启动以后会有一个 consul 的 Agent,Agent 用来获取其它服务的地址,完成微服务之间的访问。
同时服务的注册和发现,还有健康检查,也依赖于 consul 的 sever,例如,我要注册一个服务,我的 IP 是什么,我的信息是什么,放到哪里,其他的服务就能同步下来,并且找到这个服务。
下一步,原来传统的部署是程序对接资源,资源是指的是什么?比如说 VM,计算资源、存储资源都算资源,我理解的是,物理设备这些东西都叫资源。
然后程序直接对接资源,这会导致我们有很多的配置文件,由于存在不同的环境,可能不同的环境只是配置文件不同,那么这个配置文件不同就导致了多一个环境就多一套配置文件,所以需要对配置文件进行管理,配置文件发生变化以后需要对相应的服务进行更新和同步。比如说经常出现配置文件和实际部署的服务发生不一致,如果是不一致的话就会出问题。
而现在还有一个特点,当你管理几百个节点的时候会很痛苦,它们到底是属于哪一个服务的,你不清楚,因为对你而言它就是一大堆几百个节点的资源,都在青云的管理后台里,只能通过标签分类。但这并不准确,如果有人粗心把标签打错了,你就会误判,而且可能只有运维人员最了解,其他人都不懂。
中间经过 AppCenter 2.0 以后,我们做的微服务,相当于隔一层,程序直接不去对接资源,而是对接 AppCenter 2.0 里面的一个微服务。
这个微服务里面定义了需要的参数,这些参数不是配置文件,而是在服务启动的时候才会真正有这么一个东西,这是在微服务启动的时候,比如说我需要一个 MySQL,它是哪一个 IP 地址,主从是什么样的结构,我需要一个集群,它是什么样的配置参数,你在创建的时候再填写,这样的话由 AppCenter 2.0 去创建资源、创建服务,把这些资源提起来,并且管理服务的生命周期。
通过这些我们把几百个节点,完全按照以服务为角度进行归拢和聚类,方便我们的管理。
接下来谈谈还有哪些工作我们没有做,其实我们刚才说的这些工作都是比较初级的,是能帮助我们的最快的手段。开发人员没有投入太多的代码的改动,我们就把整个服务拆掉,拆成十几个微服务。我认为智慧摩羯接下来要做的事情,或者说智慧摩羯还没有完成的工作。
第一个就是认证授权,智慧摩羯的微服务之间现在虽然说是内部系统,但是互相之间也应该有一个授权操作,现在是因为公司的业务没有到一定的程度,到一定的程度的话是否允许互相认证访问是应该有授权的。
还有性能监控,拆掉之后每一个服务的性能,延迟还没有加监控。再就是流量控制,假如说某一个服务知道它大概能承受多少量级的压力,如果这个压力过线,我们就能自动进行熔断,而这个还没有做。
总结和反思
通过以上这些操作,我们消灭了单失效节点。MySQL 的每一个服务都有主从,可以把压力分担。应用层都解决了。解耦单体工程,每个微服务的代码库由每个小组进行维护,原来每一次升级都是一次大动作,所有的研发工程师都比较紧张,需要一起沟通交流,然后盯着升级,担心其是否会出现问题。
现在通过这种方式避免了各个模块之间的代码干扰,解决了之前有可能你改了这个代码却引起了另外一个你根本不知道的地方出现问题。各个代码库的合并授权下发给相关的组长,这样就解决了放权的问题,我们可以把研发部拆成很多的小组,他们可以独立进行自己的工作,不再受整个研发部统一的授权。
规范了代码的合并流程,这实际上解决了开发阶段的代码污染问题,包括开发阶段的代码污染、测试阶段的代码污染、以及线上的代码污染。
对上百个资源节点以微服务为单位进行分组管理,减少了各个模块之间的运维干扰,运维也可以以微服务为单位进行拆分放权,包括进行升级。
部署就可以以接口为单位,用接口随意切流量,虽然做不到智能的流量控制,但是如果线上某一个接口造成特别大的压力,可以让接口先返回一些友好的报送信息,从而解决线上某一个接口压力过大的问题。
第一,系统在不断地进步,每一个公司的系统都有不同的阶段,要针对现阶段找到最佳的解决方案。
第二,所有的道理大家都懂,每个人心中也都有解决方案,当你遇到问题的时候肯定有自己的想法,但是最核心的、最重要的事情就是干!