基于微服务的推荐系统
所谓的人工智能,“智能”是由模型来完成的,剩下的大量的“人工”是实实在在的脏活累活。如果把机器学习系统比喻做一个人,模型是人的心脏,那么人的其他部位就是软件架构。心脏再强大,也离不开其他器官和血肉。一套成熟的机器学习系统,归根到底,还是软件系统。 在这里,记录一下近些年参加的人工智能项目的得与失,以及改进方向。
为什么需要微服务
过去做软件,做游戏,往往是一个项目建一个工程,开发,运维,测试人员各司其职。机器学习项目,和过去的软件项目相比,有很多不一样的特点,比如:
- 快速学习:过去做项目,产品+服务端+客户端,搞定一切。但是做机器学习项目,如果职责分得太细致,难免产生"踢皮球"的现象。从数据的ETL,到数据清洗,排查数据问题,编写模型,实现服务,搭建试验平台,构建监控系统等,要求的技能都是不一样的,初创团队不可能具有方方面面都是"大牛"的人才。因此,快速学习,是做机器学习项目的要素
- 迭代要求快:模型部署到生产环境,至少需要观察几天才能看到是否有效,那么,快速更新模型,快速高质量的更新,是机器学习项目的重中之重。
- 硬件成本高:无论是GPU训练,还是EMR,都是一笔不小的开销。
综上,如果采用传统的研发+运维+测试的技术体系,沟通成本将大大增加,很难做到"快速迭代"。不同的职位,浸淫在自己的世界中,也无法"快速学习"。因此,我们团队采用研发,运维,测试一体化,按照功能划分微服务,单元测试+自动化集成测试解决测试难题,docker解决标准化交付,kubernetes解决服务编排。部门成立2年以来,各项研发总体进度符合预期,提供大几千级RPS的服务也是完全没有问题的。核心的数据工程师,软件工程师,算法工程师,数据分析师加在一起,只是个位数字。
为什么使用kubernetes
如果说2,3年前开始用kubernetes(以下简称k8s)时,还在争论是不是要用Spring Cloud,Dubbo等解决方案时,时至2019,已经完全无需争论了,k8s就是实至名归的,适用于绝大多数企业的解决方案。为什么呢?简单总结以下几点
商业面
- k8s是全球各大云平台的方案,很多云平台只支持k8s
- 如果你是企业主,你愿意每年花50w多雇佣一个运维工程师?还是动动嘴皮子让开发工程师顺便学一下k8s来运维?
技术面
- 我没有API网关怎么办?k8s的Ingress帮你解决
- 我想傻瓜化更新/回滚该怎么办?k8s的Scheduler帮你搞定
- 我想灰度发布怎么办?k8s配合Service Mesh完美解决
- 我没有“服务发现”怎么办?k8s的Service帮你解决
- 我的应用不会自动重启怎么办?k8s的健康检查帮你解决
- 我需要个简单的配置系统该如何做?k8s的ConfigMap可以搞定
- 我的应用没有监控系统怎么办?k8s帮不了你,但是Prometheus可以在k8s中完美使用。
- 进程隔离/资源限制/故障注入/打包发布。。。。
好了,列举不完,如果你经营的企业不是淘宝,不是12306,那么几乎所有和业务无关的,但必要的功能,k8s都有相应的解决方案。在使用了k8s之后,我担心的问题是,对于传统繁琐的运维方法
项目架构
数据特征层
特征 是机器学习的基础,一般要考虑
- 特征分类
- 用户特征:我们服务的对象,即用户的性别,年龄,地域等
- 物体特征:即被推荐的对象,广告的特征?游戏道具的特征?
- 如何抽取特征 有专门的特征抽取算法。当然,起初的特征都是容易收集到的数据,特征也不应过多。在进行一段时间的服务后,特征的主成分分析是非常必要的。减少重复的,不必要的特征,筛选中真正有用的特征。
- 如何更新特征 用户特征一般都是实时流更新,kafka读取到新的用户数据就可以更新。 物体特征一般在被推荐对象“版本更新”后进行更新,即“物体”变了,那么我们抽取新的特征。
- 如何处理冷启动 对于全新的物体,全新的用户,是不是在没有足够数据的情况下就无法提供推荐呢?目前有多种应对"冷启动"的方法,比如embed物体特征,这样,物体的特征就不再局限于其本身的特征了。
- 如何获取特征 开发特征SDK,为研究和训练提供基础服务。特征数据通常不会很大,需要尽可能高效的访问。
软件服务层
- AB Test AB Test肩负了2大使命
- "试验"分流:在推荐系统运行的大部分时间里,我们都需要对照不同算法之间的差异。通过AB Test设置对照组,选出优选的算法。即便是早期优选出来的算法,也不能代表这个算法一直都。因此,对照组中除了要对比不同的算法之外,也要存在一个对比基准(benchmark)。通常,纯随机推荐就是那个对比的基础
- “试验”记录:AB Test的结果同样需要被记录下来。由于机器学习算法预测的结果难以分析,如果没有记录对比实验结果,过不了多长时间,你会发现模型的行为变得难以分析,没有人知道究竟做了哪些调整,导致了模型的性能变好或变差。
- 推荐服务SDK 准备接入推荐服务的客户端,使用SDK和推荐服务通信。
- 路由服务 通过推荐服务SDK发过来的请求,统一请求路由服务。路由服务通过请求参数,查询路由表,决定当前用户应该使用哪个推荐服务,再将推荐服务转发到真正的推荐服务中。终,路由服务接受推荐服务的推荐,并返回给客户端。 借助kubernetes的自动伸缩功能,路由服务可以在高峰期自动顶住压力。
- 推荐服务 推荐服务中持有训练好的机器学习模型,实时将推荐结果返回给路由服务。 我们使用tensorflow serving进行推荐,需要注意的是,如果模型没有被预热(warm up),个请求会很慢,经常超过默认的5秒timeout时间。建议对模型进行预热,同时,如果需要被预热的模型签名很多,也会导致预热过慢。
- 推荐服务管理器,训练调度管理器 负责编排机器学习模型的训练,更新,并管理全部推荐服务。 此管理器经常需要和k8s集群交互,通常使用k8s java/python 客户端进行操作。操作k8s务必要管理好权限,使用token进行交互。一个教训是,默认情况下,客户端使用本地开发机的kube.config文件操作集群,这样很容易误操作生产环境的集群。
- 监控系统 一套与业务紧密关联的监控系统。传统的监控注重硬件,网络流量的监控。实际上,多数情况我们更需要监控的是业务。有多少个模型在训练?有多少个模型是近3天内更新的?日志中有多少条错误? 监控要做的,就是从多个维度,观察系统运行的稳定性,正确性,以便在时间发现问题。 我们选用了Prometheus + Grafana的方案,根据你的业务范围,去选择单台Prometheus,还是组成federate。我的经验是,Prometheus的federate功能相对鸡肋,并没有很好的解决扩展性问题。Thanos框架恰好解决了这个问题,而且可以把数据持久化到众多云平台。 Thanos毕竟增加了系统复杂性,建议初创团队先按业务划分Prometheus,有必要时,再切换到Thanos。
- 数据质量系统 机器学习的基础就是数据,现实中有各种各样的原因会造成数据错误。在系统设计初期,必须良好的记录下全套数据流程(data flow),自动化排查每一步可能出现的错误。 数据质量系统的终极目标是,自动检测出服务的模型和训练的模型在哪里有差别。实际要做到这一点很困难,但是,我们必须要保证相同的数据输入,在服务模型和训练模型中,能得出相同的输出,否则,任何预测都是毫无意义的。
数据层
- 推荐记录ETL 理想中的数据是美好的,现实中的数据是残酷的。不要抱怨客户端发来的数据为什么千差万别,推荐记录ETL是繁琐,也是容易出错的一步。日志服务收集到的推荐记录,通常不能直接用于训练,而是通过ETL处理之后,才能用于训练。ETL的执行效率,很可能成为瓶颈。大数据查询语句,需要经过反复的优化,核对。如果你的Hive查询了十几个小时还得不出结果,通常都是Hive SQL的实现问题。拆分中间表,使用ods表等优化方法,能够显著提升ETL效率。
- 数据清洗 ETL之后的数据,再用于训练前,务必进行清洗。假设数据总是对的,就相当于在生产环境放了一颗定时炸弹。
- 分析与验证 ETL处理之后的数据,除了用于训练,也可以直接提供给分析师进行分析。同时,该结果也是交叉验证的基础。我们可以假设数据在数据流(data flow)中每一步都会出错,我们就是要基于常识,计算推荐记录中的数据是否合理。 比如,benchmark中的随机推荐结果的分布是什么样的?到底是不是平均的随机?每天产生了多少条推荐记录?和产品DAU的情况比较是什么样的?
架构设计图
将上面的各部分简要绘制成图,如下
高可扩展性
在k8s出现前,扩容,缩容都是个苦力活,搞不好还会影响生产环境。记得那时,业务赶上推广,项目组经常全体通宵,目不转睛的盯着流量监控。 而在k8s时代,只要你的服务足够微小,互相之间没有状态的依赖关系,就可以通过设置自动伸缩组来轻易的解决扩容,缩容问题。
- 无状态服务 如果把单个服务想象成一辆公交车,乘客就是客户端的请求。现在有50名乘客,1辆公交车足矣。突然来了500名乘客,再调来10辆公交车即可。每辆公交车都是一样的,都是流水线生产的产品,谁也不依赖于谁,这就是无状态的服务。
- 伸缩组 k8s的伸缩组就是一个监控程序,你可以设置cpu高于一个阀值时,进行扩容。也可以自定义指标,比如结合Prometheus,当系统RPS高于某一个值时进行扩容。
高可用
上面提高的伸缩组一定程度上就是保证高可用,但是我们依然需要从多方面提供系统可用性,防止雪崩发生。
- 服务降级 对于推荐系统,假如推荐系统发生故障,至少,不应该影响客户端的使用。服务降级是一个退而求其次的过程,对于客户端而言,没有推荐也没关系嘛,随机产生被推荐对象就好了嘛。
- 设计图 详细规划流程图,仔细排查系统中的单点。 数据流图,网络拓扑图,系统架构图也可以很好的帮你发现可能存在的断路。 举一个生产环境上的事故,我们的服务在线上抛出了比平时多的error,由于某些原因,error的堆栈不够完整,于是我们只能猜测问题发生的位置。我们发现出现问题的服务,通过伸缩组伸缩了几十个,但只有少数几个服务存在问题。在排除了硬件问题之后,终发现是这几个问题服务到Redis的链路有问题。 解决这个问题固然简单,但我们花费了近1天的时间去排查问题。问题的根源还是监控做的不到位,监控忽略了服务和Redis的依赖性,默认云服务供应商的链路不会有问题。
- 监控系统 上面各种图中的问题,总不至于用肉眼来观测吧?基于Prometheus的检测系统,是目前的选择。如果业务简单,直接部署Prometheus即可。如果业务复杂,少数几台Prometheus不能满足,好配合类似Thanos的组件来使用。 和传统的监控服务相比较,Prometheus并不是以高效而著称,而是因为其简单,易用,便于集成和复用。
常见坑
- 伸缩组 对于不同云服务供应商,伸缩组的功能要由供应商自己实现。由于供应商水平的参差不齐,节点扩容,缩容本身也是技术难点,因此,云服务供应商的伸缩组功能可能不是很牢靠。除了伸缩组自身的bug之外,缺货,货源改变等问题也是经常出现的。所以,有比较频繁的伸缩需求的业务,好尽早和供应商沟通,做到货比三家。
- 容器化 容器化简化了部署和维护,缺使开发多了一些步骤,并且可能会出现难以调试但问题。所以,在准备动手开发前,务必先考虑好如何开发,如何测试,如何解决线上问题。 这里插播一条广告,arthas是令我爱不释手的一款解决线上故障的利器。
- 资源超卖 “超卖”的问题虽然人人皆知,但不能光吐槽,也应该知道“超卖”会如何影响你的服务。简单的说,“超卖”类似坐飞机时,美联航卖出的票比实际的座位要多,他们根据统计学,计算出某个航班预期有多少人不会来乘坐航班,从而多赚几张票钱。 云服务领域也是一样的,明明只有32核的机器,或许可以卖到33核,因为谁也不会让自己的服务常年的占满,能多卖点就多卖点。因此,如果你在云服务里购买了1核1GB的节点,那就要当心了。因为,如果你买的是32核的节点,少分配你1个核心,你也许不会察觉到,但是如果你只买了1个核,少分配的那1个核又恰恰在你的虚拟机上,那就要耽误事了。
后续
有关如何实现微服务,google提供了很好的demo(https://github.com/GoogleCloudPlatform/microservices-demo),建议初学者仔细研读一下。后面会详细总结一下当前的技术选型
原创不易,请保护版权。如需转载,请联系获得授权,并注明出处。
相关文章