讯飞AI营销云微服务演进之路(一)

讯飞AI营销云微服务演进之路(一)

引言

讯飞广告平台成立于2014年,以语音交互广告和AI能力为自身独特优势,由ADX和DSP两大业务,共同组建成AI营销云平台。近两年,讯飞AI营销广告平台发展迅速,聚集了开屏、信息流等众多优质资源;同时接入国内各大主流ADX媒体,服务国内外品牌客户和中小广告主。形成了从媒体端到需求端的广告营销布局,广告资源包含PC、移动、OTV、DOOH、智能硬件等多种生活场景,覆盖开屏、贴片、信息流、横幅、音频广告、互动广告等广告形式。业务场景丰富,与之相配套的,是在发展过程中,逐步壮大和演进的广告技术架构。

广告投放业务流程

1.png
业务流程

微服务架构升级

2.png
讯飞广告平台2018年双11服务指标

服务承载越来越高的流量压力同时,也面对着更加复杂化的业务需求,如何保证服务的高效稳定、如何确保应用体系及组织结构向微服务时代快速、有序的跨越,是我们过去一年面对和思考的难题,下面会介绍讯飞广告投放团队在微服务转型上的一些经验。

架构演变概述

技术架构的演进是跟随业务一起发展的。在业务发展初期,我们的架构比较简单,如下图所示。
3.png
初期系统架构

随着流量的迅猛增长,此时整个后台服务,主要是引擎机器的压力变得非常大,为了抗住压力只能不断扩容,平行扩展后台服务节点。通过这种方式,整个软件系统很好的抗住业务压力。但是组件的增多,尤其是引擎、Redis节点等,导致业务扩展、故障节点迁移,变得异常复杂,需要投入较多精力。
另一方面,随着业务的快速发展,以往相对简单的功能变得复杂起来,这些功能除了有用户看得见的、也会包括很多用户看不见的,整体功能杂糅在一起,让代码的维护越来越困难,如果此时大家开发各自的业务功能还是用一套服务代码,那么功能开发、测试、上线都将是一个巨大的工作量。如果出现紧急问题,问题的定位也是令人头疼的事情。所以如何划分业务边界,进行业务拆分;如何进行框架的调整并进行合理的团队配置就变成了十分迫切的事情。
4.png
微服务架构

经过一系列的重构+扩展,整个系统架构最终形成了以引擎服务为中心的一套初步微服务软件系统。整个投放系统就基于SpringCloud初步完成了微服务体系的拆分,引擎、算法、监听、预算控制、订单管理抽离成独立的微型服务,各个服务也根据业务特点,做了边界划分。
在完成服务的拆分以后,原来功能逻辑之间的代码调用关系,转换成了服务间网络的调用关系,而各个微服务需要根据各自所承载的功能提供相应的服务,此时服务如何被其他服务发现并调用,就成了整个微服务体系中比较关键的部分, 在这里我们使用了Eureka来实现。另外在基于SpringCloud的架构体系中,我们使用了配置中心(ConfigServer)来管理配置文件。而原本的Nginx负载均衡服务,随着业务的增长,需要熔断、限流等更丰富的功能,逐步演变成网关服务。针对微服务组件中,大量的异步消息同步,我们采用了RocketMQ中间件,来解耦服务之间的关系。
下面我们将会介绍Eureka、ConfigServer、RocketMQ组件、网关在讯飞投放平台微服务改造中的应用。

注册服务(EureKa)

5.png
Eureka原理图(来自SpringCloud官网)

服务启动后向Eureka Server注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
Eureka的使用过程中,需要关注两个特性。在默认配置中,Eureka Server在默认90s没有得到客户端的心跳,则注销该实例,但是往往因为在微服务跨进程调用中,伴随着网络通信面临的各种问题,引起微服务状态异常;甚至在网络分区故障时,Eureka Server注销服务实例更会让大部分微服务不可用,进而可能导致服务大面积异常。可以通过配置eureka.server.enable-self-preservation=true启动自我保护机制解决。他在Eureka丢失大量节点时,不在注销任何微服务,等故障恢复之后,也会自动退出保护机制。
因为服务消费者本地缓存了服务提供者的地址,即使Eureka Server宕机,也不会影响服务之间的调用,但是一旦新服务上线,已经在缓存在本地的服务提供者不可用了,服务消费者也无法知道,所以保证Eureka Server的高可用还是很有必要的。
Eureka Server配置
6.png
Eureka client配置
7.png
Eureka还在server的指定端口上(默认8761)提供了非常好的可视化页面,可以实时观察注册服务的个数和状态:
8.png
如上图所示,其中的DISCOVERY-SERVER就是我们的Eureka Server,它在支持其他应用注册的同时,也可以自我注册,这样就可以观察到实际的发现服务节点的情况。

配置中心(ConfigServer)

配置中心是对微服务应用配置进行管理的服务,例如数据库的配置、Redis机器的配置、业务配置等等。在SpringCloud中ConfigServer是独立的服务组件,它与Eureka一样也是整个微服务体系中比较关键的一个组件,所有的微服务应用都需要通过调用其服务,从而获取应用所需的配置信息。
随着微服务应用规模的扩大,整个ConfigServer节点的访问压力也会逐步增加,与此同时,各个微服务的各类配置也会越来越多,我们面临了两个很直观的问题:

1.如何管理好这些配置文件以及它们的更新策略

我们可以单独通过git来管理应用配置文件,正常来说由ConfigServer直接通过网络拉取git仓库的配置供服务获取就可以了,这样只要git仓库配置更新,配置中心就能立刻感知到。针对每个节点进行默认配置,如:spring.cloud.config.profile:common;这样应用节点就会自动加载ConfigServer上的appName-common.properties配置文件;如果有其他定制需求,如test测试性新增配置,可以修改节点的启动文件为:spring.cloud.config.profile:common,test;这样即可方便的在编写一份配置文件appName-test.properties的同时,使所有配置了test的应用节点生效。
简单来说,在ConfigServer对应的Git仓库上维护各版本配置文件,并在实际的应用节点上指定版本即可。

2.确保ConfigServer集群高可用。

针对这个问题,相对比较好解决。因为ConfigServer主要是通过http访问配置文件的服务,不涉及节点选举、一致性同步这样的操作,只需要按照传统的方式搭建高可用配置中心,构建多个服务实例即可。
ConfigServer的配置文件如下:
9.png
Config Client的配置文件示例如下:
10.png
这里是和注册中心Eureka配套使用,在向注册中心查询之后,Config Client即可获得ConfigServer的地址信息,并通过spring.cloud.config.label的分支名称以及spring.cloud.config.profile指定的配置文件,到ConfigServer配置的spring.cloud.config.server.git.uri指定git服务器,获取实际配置信息。

消息中间件(RocketMQ)

在广告业务中,不仅有数据库、Redis集群、业务设置这种静态配置,还有大量的订单管理数据需要写入到引擎中,而这些数据都存储在数据库。如果每个实例都实时的去数据库读取数据,不仅给数据库增加了巨大的负担,也对数据的一致性提出了挑战。同时,监测收到的消费数据,需要实时汇总,结合订单的预算要求,对引擎进行实时的状态变更,这也产生了大量的异步消息流。
11.png
中控服务统一读取数据库订单信息,并写入RocketMQ的订单推送Topic中;实时预算信息更新,也写入到订单推送Topic中,这样可以保证数据源的输入是唯一线性的。
然后引擎机器根据自身的实例编号,订阅自己的group信息,保证自己能够获取到全量一致的信息。无论是服务实例故障重启还是大面积扩容,都不会对数据库产生冲击。

网关(Kong)

关于网关的选型,我们细致调研了Spring Cloud Zuul、Spring Cloud Gateway和Kong等框架/组件。

1.Spring Cloud Zuul

由于服务组件是基于Spring Boot框架开发,采用Spring Cloud生态的网关在集成方面会更加方便。首先调研了Spring Cloud Zuul,这是采用JVM + Servlet线程池的实现方案,优点是实现简单,缺点也很明显:Zuul采用一个连接一个线程的实现方式,在后端服务出现延迟等响应慢的场景下,网关的线程资源不会快速释放,导致请求积压、会话耗时加大,特别是广告这种大流量、低延迟的要求,对网关性能要求特别高。
12.png

2.Spring Cloud Gateway

Spring Cloud Gateway不是Spring Cloud的嫡系产品,而是开源社区为了解决Zuul的性能问题而贡献出的一款产品。Gateway在逻辑概念上采用了和Zuul类似的设计:pre filters、routing filters、post filters。只是在网络及线程上采用了NIO的方案,这样有效地降低了整体延迟和加大了吞吐。但Gateway毕竟不是Spring Cloud出品,在产品问题的修复、后续的演进上面都存在力度不够的情况。比如一个基本的导致连HTTP POST请求无法转发的问题都迟迟得不到修复。

3.Kong

最后我们调研了Kong。Kong是基于OpenResty进行开发的,先天拥有非常好的性能,限流、认证等功能都是通过lua插件的方式进行集成,并且可以方便地开发自定义插件。在服务发现上虽然没有Spring Cloud系的Eureka那样方便,但也可以通过Consul来实现,并且当后续服务通过K8S部署后,可以采用K8S的服务发现和负载功能。

spring cloud zuulspring cloud gatewaykong
云原生spring平台spring平台平台无关
动态路由支持支持支持
熔断支持支持支持
鉴权支持支持支持
SSL支持支持支持
限流支持
高性能
插件机制丰富

根据以上的对比,我们不难发现,对于SpringCloud的路由组件,包括Zuul以及SpringCloud GateWay框架,我们称之为组件OR框架,而Kong则称的上产品这个词。
举例而言,如果选择使用zuul,当需要为其添加限流功能,由于它本身只提供了基本的路由功能,开发者需要自己研发zuul filter。仅仅这一个功能可能还并不麻烦,但如果在此基础之上对zuul提出更多的要求,则开发者需要自行承担这些复杂性。而对于kong,限流功能就是一个插件,只需要简单的配置,即可使用。
Kong的插件机制是其高可扩展性的根源。更由于其运行在nginx之上,在使用kong之后,我们完全可以摒弃nginx,Kong的功能是nginx的父集。而zuul/gateway除了基础的路由特性及其本身和SpringCloud结合较为紧密之外,并无任何优势。

后记

基于SpringCloud的微服务架构体系,通过集成各种开源组件来为整个体系服务支持,但是在负载负载、熔断、限流的方面目前还是通过业务进行处理,下一步我们计划通过用Kong来全量替换原生nginx,将负载、熔断、限流的功能前置到网关上去。还有一种方案,是业内开始升温的Service Mesh(服务网格)的概念,Service Mesh的基本思路就是通过主机独立Proxy进行部署来解耦业务系统进程,这个Proxy除了负责服务发现和负载均衡(不在需要单独的注册组件,如Consul)外,还负责动态路由、容错限流、监控度量和安全日志等功能,这个也是我们下一步探索的方向。

添加新评论

我们会加密处理您的邮箱保证您的隐私. 标有星号的为必填信息 *