基于微服务架构的ETC数据层平台的设计与实现

2021-07-16 08:02
计算机应用与软件 2021年7期
关键词:队列进程架构

于 曼 黄 凯 张 翔

(北京速通科技有限公司软件研发部门 北京 100000)

0 引 言

伴随着中国高速公路投资规模的不断扩大、建设里程的不断增加,高速公路管理所需的通信、监控和收费系统需求量在不断扩大。电子不停车收费(Electronic Toll Collection,ETC)技术是伴随着车辆增多和道路使用率提高而产生的一种旨在提高车辆通行能力的收费技术,适用于高速公路、交通繁忙的桥梁隧道或停车场等环境。除中国外,在美国、欧洲和日本等许多国家和地区,ETC技术也已经被广泛应用,并形成规模效益。作为高速公路联网电子不停车收费示范工程项目,北京市高速公路电子不停车收费系统是实现车辆不停车支付通行费功能的全自动收费系统,从2007年起已经平稳运行了十多年,至今已发展了400多万用户[1-4]。

近年来,北京ETC系统在高速公路、停车场和服务区收费中的应用已经开展了商业示范。取消省间收费站和用户自主办理、安装、激活ETC卡和标签等业务的出现进一步推动了用户量和业务量的快速增长。然而,随着业务的快速发展和用户量的不断增加,现有的ETC系统逐渐暴露出扩展性差、开发周期长、部署维护风险高等问题。通过对ETC系统的改造,解决原系统存在的严重问题是本文的最终研究目的。

1 关于ETC系统改造的分析

为了实现高速公路电子不停车收费的功能,ETC系统包括了发行系统、多路充值系统、传输记账系统、银行文件处理前置系统和清分系统等多个分支系统,其中发行系统是其核心部分,它是处理卡、电子标签发行和充值等相关业务的系统。系统的逻辑架构如图1所示。

图1 ETC系统逻辑架构

由于项目设计之初规模较小,结构简单,用户群体小及实时通信要求低等特点,各个子系统采用传统的单体架构模式设计,从而导致系统具有以下问题[5-7]。

(1) 代码耦合性高。随着业务量的不断提升,代码变得越发庞大,业务扩展变得愈发困难,对新需求的响应速度变慢,开发成本变高。

(2) 代码复用性低。存在大量重复开发的代码,各子系统间对数据库的操作相互独立,不支持代码复用。

(3) 每个子系统都可以直接访问核心数据,存在抢占数据库资源的现象。

(4) 系统整体进行统一打包及部署,不支持热更新,部署维护风险较高。

针对ETC系统中存在的上述问题,需在不影响系统正常运行的情况下,对其进行改造。本文搭建数据层平台,提出各子系统中与数据操作相关的程序,将其设计为独立的服务组件,解耦数据库操作层与业务服务层。改造后的架构如图2所示。

图2 改造后ETC系统架构

数据层平台的搭建可增强数据操作相关程序的复用性,增加可支持的数据库类型,且不局限于某些特定数据库。此外,数据库访问层的变更不会影响业务层逻辑的代码,最大程度减小部署和运维的风险。同时,新增负载均衡提升系统吞吐量和交易处理能力,可支持千万级的ETC用户量和与日俱增的交易量[8]。

2 微服务架构介绍

传统单体架构较适合简单的轻量级应用,不适用于复杂度高、业务需求量大的中、大型应用,因此,它已不再适用ETC数据层平台的开发。微服务架构理念的兴起,很好地解决了这一问题,微服务架构对复杂企业级应用的敏捷开发部署中存在巨大优势。

微服务指的是围绕业务构建,协同工作的小而自治的服务,具有高的内聚性、自治性和扩展性。微服务架构的基本思想是将传统的单体架构应用按照业务功能的不同拆分为一系列可以独立设计、开发、部署、运维的服务单元,服务间可通过RPC或API等方式进行通信或被调用[9-12]。API网关可以对前端用户身份进行认证和鉴别,并将前端发来的 HTTP请求转发至对应的服务中进行处理。其架构如图3所示[13]。

图3 微服务体系架构

微服务架构较传统单体架构更有利于应用的持续性开发。微服务系统中每个服务的更新和部署都独立于其他服务组件,大大降低了系统更新的风险。每个服务单元可应用不同的编程语言开发,并可以用不同的数据存储技术,也可由不同的开发团队管理,当有新技术出现时,可按照模块逐个升级,具有较好的灵活性。同时,技术的多元化使得每个服务可根据需求和行业发展状况选择适合的技术类型,并根据需求提供接口服务。服务间的独立性和松耦合,实现了单个微服务出现问题不会影响系统其他服务运行的目标,容错能力强大[14-21]。

3 数据层平台总体架构设计

ETC数据层平台基于微服务架构的设计理念,封装低粒度的数据库操作行为,为业务层提供数据操作服务。数据层平台主要包括通信管理层、工作处理层、主控管理层及数据库,其整体架构如图4所示。

图4 ETC数据层平台结构

通信管理层基于IO复用原则,负责外围系统请求的接入接出。当请求接入成功建立连接后,通信管理层采用多线程技术获取并解析请求, 根据业务类型的不同分配给对应的工作进程处理。消息队列机制与线程锁在保证线程安全的前提下,借助共享内存完成消息的传递、并行处理并转发。多线程能够合理高效利用服务器资源,可大幅度提升消息处理能力。在请求高峰期,消息队列可以有效缓解压力,避免因处理能力不足造成服务不可用等情况。共享内存作为通信模块与工作模块的桥梁,是目前效率最高的进程间通信技术, 所有交互完全在内存中完成且可自定义分配内存块大小,在合理设计数据结构的前提下具备了很高的适用性,并且Linux系统提供相关底层函数,保证了平台使用时的稳定性与安全性。

工作处理层是由多个独立进程实现,根据通信管理层转发来的请求调用动态库微服务,处理相关业务逻辑, 最后将处理结果回发至通信管理层。工作处理层将业务逻辑根据不同的功能拆分成不同的子模块,将各个模块封装为相互独立的动态库,依靠自身平台框架用外围系统请求参数实例化动态库中的服务,从而基于C++实现依赖注入控制反转的目的。该实现方式的本质类似于微服务架构,动态库中的微服务变更无须编译平台代码,服务之间互不影响,极大地降低了平台和服务的耦合性,提高了平台和服务的可扩展性,使得系统整体结构变得非常灵活,是目前C++框架所不具备的[21-23]。

主控管理层主要负责管理通信管理层与工作处理层以及监控服务的启动。当请求开始处理时,主控管理层负责监控服务处理的时长,根据特定服务类型及其自定义超时时间判定超时后的处理方法,减少调用等待时长,提升响应速度。同时主控管理层监控通信进程与工作进程存活状态,当意外挂掉进程时会重新启动,确保数据层平台的稳定性与安全性。

4 数据层平台关键技术

4.1 C++实现服务注入

一般C++框架中,如需要在实体A中使用实体B,会直接在实体A中创建实体B的对象,换而言之,是在实体A中去主动获取所需要的外部资源实体B,如图5所示。但若应用反向注入控制反转的方式,实体A如需使用实体B,无须主动创建实体B,而是被动地等待,等待容器获取一个B的实例,然后容器反向把实体B的实例注入到实体A中。这种在Java/.Net中是依靠IOC/DI的容器来实现,通过配置文件中的类名实例化业务类进行依赖注入的。

图5 实体A应用实体B的传统方式

ETC数据层平台是一种通过C++实现IOC(控制反转)模式,服务和系统松耦合,进程分发灵活的平台系统。平台将根据不同功能拆分成的各个模块封装为相互独立的动态库,依靠请求参数实例化动态库中的服务,将可用动态库服务注入到自定义容器中,从而达到依赖注入控制反转的目的,如图6所示。在调用服务时,从自定义容器中取出对象进行后续逻辑处理,可彻底切分开服务与平台间的耦合,使后期服务变动无需编译平台,平台编译无需编译服务。

图6 C++实现服务的依赖注入

4.2 负载均衡

系统的负载均衡器主要对分发服务的任务处理请求选择最优节点。该系统负载均衡的均衡策略为处理请求首先都暂存到阻塞队列中等待策略模块过滤转发,分发服务的数据处理请求,负载均衡器主要根据CPU占用率等进行过滤选择。当选择到最优的数据分析节点则将请求发送到对应的节点进行处理,而如果有多个符合条件的节点,则负载均衡器将随机选择其中一个转发处理请求[24]。

4.3 redis集群化

redis集群化基本原理是通过在多台服务器上部署redis, 每台服务器上启动多个redis节点, 利用keepalive做虚拟IP实现访问统一与高可用。其目的是在单台服务器故障时可自动切换并短信报警,利用ruby语言实现多节点数据互通与故障切换功能解决单点故障问题。

在基于上述高可用情况下,数据平台根据不同流水号分类成不同的key在redis中建立多个list序列, 根据查询数据库中最大值进行一定数量的自增, 自增后批量插入与流水号相对应的list序列中并定时检查redis中流水号数量进行补充。

在启动多平台时每个平台统一连接redis集群, 各平台接收到生成流水号的请求时根据不同流水号类型解析出不同key,然后利用redis acl库的API 调用pop方法弹出与其对应的value响应给业务层 ,pop操作为原子性, 可保证数据唯一性。

4.4 7×24小时不停机

数据层平台进程间通信基于共享内存机制是目前最快的进程间通信技术,共享内存是在linux系统级开辟的内存空间,只要服务器不重启,开辟的内存空间就不会被释放(手动释放例外)。基于本机制可以保证进程意外崩溃时利用监控机制重启进程后,进程根据配置文件计算得出共享内存地址后依旧可以通过其地址访问到共享内存,从而取出未处理的数据继续进行处理。理论上管理进程不崩溃,数据层平台可不间断运行并随时可以手动kill除管理进程外的任意进程。

基于流水号去中心化,共享内存持久化的技术架构下,结合Nginx进行多台负载可保证平台7×24小时不间断运行并随时可进行单台服务器的数据平台更新与维护工作,不会对线上业务产生任何影响。

此外,程序后台启动、切换用户、切换shell、关闭shell等操作不会对启动的进程造成影响,数据层平台所有进程都采用守护进程方式运行。除手动触发关闭外,系统不重启将一直运行。

运行期间,数据平台支持服务的热更新,不停止当前业务的前提下对动态库服务进行更新、停止和加载等操作。考虑业务量的与日俱增, 为保证流水号的唯一性设置主机与从机并支持热切换,在日后的使用场景中可保证接口不变的情况下自如切换主从。

4.5 服务分化

实际应用中通常读类业务耗时长、业务复杂,并且在大量请求到达时多线程解决方案并不理想,故大多选择队列缓存的方式进行处理。而因为读类业务的超时会导致队内后续业务无法及时处理,最终导致连接超时,业务执行失败或因阻塞导致更新不及时使处理逻辑混乱等问题,如充值、动账等核心业务,都可能因为读类业务的阻塞导致请求方认为该次请求无效而重复请求,最终导致错账等问题的出现。

服务分化主要是根据其对数据库操作,将服务划分成读与写的类型。现阶段对数据库的查询操作划分为读类业务,对数据库的其他操作划分为写类业务(如:新增、修改、删除)。在数据库中使用自定义表用于持久化所有业务类型信息,方便后期新增与维护,在业务服务器启动时会将所有业务服务进行初始化,通过读取数据库自定义表的内容将相关启用状态下的业务类型加载到内存中,使后续比对都在内存中进行,提高效率。具体业务细分成1=读、2=写、3=其他,加载到内存后,请求到达进行初步处理放入队列, 主线程从队列中取出消息后,根据请求的服务号在内存中计算得到对应业务类型,根据业务类型(读/写)分发到对应的队列中,后续工作进程会从队列中取出请求进行处理并响应。

服务分化成功地将读类业务与写类业务区分开来,互不影响,在实际应用中读类业务占到很大比例,通过分离的方式可以更具针对性地对读类业务进行优化,同时也解决了因读类业务的阻塞导致其他后续业务无法及时处理的问题。

4.6 服务监控

平台的管理进程模式,可监控通信进程与工作进程存活状态,监控服务调用时长。根据自定义时间判定超时服务后处理,目前生产环境是调用服务前记录时间管理进程监控。如果15 s后没有改变,则认为该服务超时,自动杀掉其工作进程,从而减少业务逻辑层等待时长,提升响应速度。

系统运行期间可控制日志输出级别,包括调试模式(所有日志都输出)、警告模式(只输出警告和错误日志)和错误模式(只输出错误日志)等日志级别。可通过更改配置文件灵活控制日志级别,对于问题服务可通过日志快速准确定位问题。此外,利用Redis转存日志、异步打印日志、减少输入输出等待时间来提升整体平台的处理能力,在redis不可用时可自主进行降级到本地输入输出打印,保证日志信息不丢失。

5 系统实现

原ETC系统的历史数据主要存储在DB2和mongo两种类型的数据库中,因此ETC数据层平台在开发实践中,为了最大程度减小数据迁移的风险,同时满足业务需要,依然应用DB2和mongo两种类型的数据库。在进行平台和服务划分时,分为DB2和mongo两个数据层平台,以便于根据数据库种类和性能的不同对服务器资源等进行分配。访问DB2数据库的数据操作层服务搭载在DB2数据平台上,访问mongo数据库的数据层服务搭载在mongo数据平台上。数据服务搭载平台,完成服务载入与卸载、请求接收、分发、响应生成,服务组件以动态库的形式封装不同的服务。

5.1 数据层服务工作流程

当一个请求由客户端发起后, 经由前置服务器转发至负载均衡服务后到达业务服务器,业务服务器进行逻辑判断后向后台发送请求,至此成功进入到新架构的服务器内部,开始正式处理及解析。服务的调用流程如图7所示。

图7 数据服务调用流程

1) 初始化阶段。数据层平台启动,初始化一个下标为8位数的数组(系统协议的服务号定长为8位)并将数组内容初始化为0,这些服务号是根据业务需求从10000000递增而来,并根据业务进行细分。如果涉及读数据库则将其类型标记为1,如果非读数据库行为则标记为2,在添加服务时已将这些信息持久化保存到数据库内。在平台初始化数组后, 读取数据库内服务类型信息,并根据服务号对其所在下标数组的值进行更改,如10000001为读服务对应的类型值为1,则将开始初始化为0的下标号为10000001的数组值从0改为1,以此类推, 至此初始化工作已经完成,所有业务需要调用的服务都已加载到内存中并根据类型进行了细化。

2) 请求到达。一个新的请求成功转发到数据层平台后,平台根据协议开始解析请求,截取服务号及消息体内容,根据解析得到的服务号可以得知请求的业务类型,也可以根据自定义的服务号来判断是否需要进行特别的逻辑处理,而且也可以结合服务号来定制相应规则。如:读类业务请求到达时,当前队列如果负载超过80%则拒绝本次请求等个性化功能,如果本次服务号无特殊处理逻辑也无拒绝连接特征,则将其放入通信队列当中,等待下一步处理。

3) 开始分发队列。数据层平台从通信队列里取出一条经上一步筛选过的请求报文后,将本次请求的服务号作为数组的下标,从中取出当前服务号下标数组的值,由此确定本次请求的操作类型。如果为读类型,则将本次请求消息体内容放入读队列中, 反之则放入写队列中。队列的传输媒介考虑到效率问题,采用当前最快的进程间通信方式即共享内存来完成,在此期间可根据业务需求自定义功能。

4) 请求处理阶段。从消息队列中获取请求,根据Key值(服务号)获取对应服务的内存地址,调用动态库中的服务对其进行处理,并将响应结果返回通信管理层,发送给前置。

至此数据层平台从一个请求的发起、分发、处理、返回响应的功能已介绍完毕。

此外,系统在添加或者更新服务组件时需要人工进行干预,处理流程如图8所示。整个过程简单易操作。主要操作流程是服务拷贝、服务列表更新、通信进程添加启动服务。这个过程对数据层平台无影响,也对其他正常运行的服务无影响,实现了服务的热更新。

图8 数据操作层人工干预流程

5.2 数据层平台应用优势分析

ETC数据层平台本质是应用C++框架实现了微服务理念,按照业务功能分化出细粒度的数据服务,同时依据现有的先进技术对框架进行不断完善。这种设计理念使得ETC系统在程序代码层面、系统层面、系统响应速度层面和部署维护层面展现出很大优势。

数据层平台改造前后性能对比如表1所示。数据服务的单独平台化增强了与数据库交互代码的复用性,同时实现了系统间的代码复用,可避免大量重复代码的出现。细粒度的服务更加便于功能的新增与修改,增加了系统的扩展性。数据层平台对前置系统的编程语言和架构技术没有要求,同时其可以与多种类型数据库灵活交互,并且可支持多种通信协议,平台接口相对灵活,具有很高的兼容性,增强了平台的可用性。负载均衡、消息队列、多线程和共享内存技术的使用很好地提高了通信速度,实践证明数据服务的响应速度较改造前系统可提高至微秒级以内。同时,数据层平台支持服务级别的热更新,进行不停机维护,服务新增或更新不会影响业务层逻辑代码,对其他服务以及系统的整体运行也无任何影响。此外,相较改造前被动地处理投诉方式修改生产bug,数据层平台可自主监控平台问题,并将监控结果记录,进行自查自修。

表1 数据层平台改造前后性能对比表

数据层平台是公司内部自主研发,相较于开源系统或委托给第三方开发的系统,自主研发的系统平台更具安全性以及应变灵活性,系统后期升级、优化的灵活性也更高,能够快速且准确地响应公司实际需求,可追踪服务状态记录, 进行自定义处理。

6 结 语

本文主要目的和创新是根据北京ETC系统的实际情况,基于微服务架构思想,利用C++框架搭建与数据库交互的数据层平台,实现对北京ETC系统的初步改造,从而解决原系统中的问题。数据层平台用动态库形式封装了微服务,利用C++框架实现了服务的依赖注入控制反转。数据层平台具有微服务框架的特点,同时是自主研发的框架,在新技术的应用上具有很灵活的选择权。数据层平台的搭建为ETC系统整体改造提供了条件,其松耦合的特点,更好地支持在传统架构下的业务逻辑层的代码重构。因此,下一步将重点考虑ETC系统业务逻辑部分的改造。

猜你喜欢
队列进程架构
功能架构在电子电气架构开发中的应用和实践
基于车车通讯的队列自动跟驰横向耦合模型
队列队形体育教案
构建富有活力和效率的社会治理架构
Dalvik虚拟机进程模型研究
快速杀掉顽固进程
不留死角 全方位监控系统
青春的头屑
中外民主法制进程专题复习
VIE:从何而来,去向何方