草庐IT

4小时上线一个接口,高效统一的携程酒店数据服务平台实践

小丰 2023-03-28 原文
​作者 | 小丰,携程研发总监,专注于分布式数据库研究,大数据领域实时计算和大数据应用的系统架构设计。

背景

随着携程酒店数据的膨胀以及个性化需求的增多,每个数据接口个性化的排期开发,因为没有标准化,从需求讨论,数据准备、接口封装、上线调试到接口api说明,期间需要花费大量的时间。一个接口的实现到生产上线至少需要2天甚至更多时间,这个时间成本不得不依赖排期开发;

随着历史接口的迭代,已对外提供的700多数据接口中,其中500多个还在使用,并且每年的增量在100多,开发和维护成本高,特别是在追溯上游离线数据逻辑的时候,过于依赖研发资源;

不同研发团队技术栈不一样,算法相关的研发更多偏向于python开发,对外输出的接口也是由python实现,但公司框架对java接口有更友好的支持,不同技术栈对外输出接口的稳定性存疑,特别是人员流动,团队职责变化后,同时也影响维护成本;

随着业务的发展,各个业务系统的数据需求越来越多,需求响应要求也越来越高;

通过历史接口的分析归类,80%以上的数据接口其实是针对离线数据或者实时数据加上需求方的检索条件返回数据,没有过多的加工逻辑或者过于复杂的业务逻辑在接口中实现;

为了能更快速支持业务个性化需求和降低研发成本,起到降本增效的效果,同时避免烟囱式数据接口开发,提高数据复用率,避免同样数据出现同样的多个接口,也避免不同的研发团队拿到同一份数据都在做自己场景的数据接口,减少数据孤岛情况。为此,我们设计了一套符合需求的数据服务平台。

一、平台介绍

  • 统一数据服务平台依托于公司soa服务基础之上构建,平台实现统一技术方案,降低运营成本,提升了接口稳定性,可维护性和持续性;
  • 运维配置,降低数据接口实现成本,从个性化开发的2d+ 降低到4h甚至更快的上线,这个实现基本上可以不强依赖资源排期;
  • 通过统一数据服务平台可视化界面配置,不依赖java开发人员介入,可由数仓团队产出hive表根据需求配置接口输出;
  • 统一数据源,保证了数据使用的一致性;
  • 为需求方申请接口提供标准模板,提升沟通效率以及需求方对大数据需求的满意度。
系统层面架构图:

接口的申请配置流程如下图:

    

二、如何实现

2.1 平台收口

减少数据接口的输出团队和技术方案;另外随着业务量、数据量的增长,业务类型的累积,现在的接口不是完全靠mysql能支撑的,平台统一规划技术方案,调用方不用关心底层服务是用clickhouse,es,starrocks,redis等任何数据库以及相关数据库的技术特性和语法特征。在实际配置中,我们需要结合调用方的场景以及不同的olap数据库的特性和优缺点来选择;比如:

  • ES:核心,高并发非KV结构的搜索场景;
  • Redis:核心,高并发KV结构场景;
  • MySql:核心,千万级以内小表简单查询并且是高并发场景;
  • starrocks:次核心,QPS不是非常高,单表数据量在千万级,亿级场景;
  • ClickHouse:非核心, QPS在100以内,数据量在千万级,亿级场景;
  • Trocks/ Hbase:非核心的KV结构场景;同时,对于不同的数据库,更新机制上也是需要我们注意的哪些适合于全量更新,哪些适合于增量更新;

2.2 加强数据利用

有些数据只要表同步过,下次在其他业务场景使用的时候只要配置不同的查询sql就可以对外提供使用,通过血缘关系的监控,减少离线数据的重复同步,提升一份数据的应用面,从而提升数据的可用性和一致性,让数据复用而不是复制。

2.3 接口安全验证

每个调用方appid需要提前申请对某个接口的应用权限,统一服务平台通过授权token的方式,验证appid+token的权限防止未申请接口权限的应用非法调用,其中appid是通过公司soa框架自动获取避免appid被串改的情况,保证接口数据的安全性和稳定性。

2.4 限流保护

在一个高并发系统中对流量的把控是非常重要的,特别是在统一服务平台,当某个接口因为外部爬虫原因导致流量超过设置的阀值而没有拦住,可能导致整个平台对外输出接口都不可用。

为此,我们引入Sentinel限流机制。Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、服务降级、系统负载保护等多个维度来帮助我们保障服务的稳定性。

实现原理是根据指定的时间内生成预先配置好的令牌数,每一个请求都会消耗一个令牌,令牌申领完后就会拒绝服务。目前每一个接口名都会有一个独立的令牌,各接口间限流互相不干扰实现对每个接口的流量控制,qps超过设置阀值接口自动熔断。

2.5 数据缓存

接口的配置信息,这些信息持久化的存入硬盘中,在接口调用时会被频繁使用,如何快速高效的获取这些配置信息,需要使用到缓存机制。通过建立主动和被动缓存,避免服务器负载过高。数据源的配置信息定时缓存,接口在使用时能快速取到基础数据,不需要初始化。

2.6 服务契约统一

通过本平台调用的接口,现在所有的请求都是由一个入口中来完成。接口收到请求后根据接口的配置信息自动的进行分流处理。请求契约中包含head和params两部分,head负责接口的基本信息,用于服务验证和业务中转。params参数为json字符串参数对象,服务会动态根据json的信息与配置信息匹配进行解析参数。response契约中包含接口成功标志和result部分,其中result为json的字符串参数对象,需要调用方收到后进行解析。

Request如下图所示:

Response 如下图所示:

2.7 数据服务配置和映射

一个服务接口由数据源、sql语句、请求参数及响应参数组成。其中sql语句中的参数使用 ?、{序号} 占位符替代,与请求参数一起使用,sql有多少个参数占位符,请求参数就需要配置多少,接口运行时会根据请求的参数自动匹配到sql参数中。响应参数为了在查询结果中映射字段,sql查询输出的结果 ,可以通过映射转换真正想要的输出参数,配置的响应参数就是接口服务返回的查询结果。如下图是配置sql的查询方式:

2.8 契约文档自动生成

个性化接口开发,需要对接口进行解释,告知调用方如何调用。结合接口输入和输出参数都是自定义的特点,定义一套服务文档展示模板,文档中包含所有的调用该接口的详细信息。只要定义好接口后,会动态的生成契约文档,申请使用该服务的团队会通过邮件方式发送信息,节省接口解释成本。文档在线效果如下图,同时也会以邮件形式推送给申请人。

2.9 服务监控

服务接口正常运行后,借助于公司的clog和ck日志框架来监控接口调用情况。Clog监控主是要记录请求接口从开始调用到返回所有的过程记录,包含每个过程节点的调用时长,请求参数及返回参数。方便定位接口request的整条链路。ck监控主是要记录接口层请求的参数,返回的参数和响应时间。每次请求只记录一次,可以统计,监控每个时段调用的次数,接口响应的时长等信息。

2.10 生产运行效果

2021年12月初上线至今,目前对接调用方appid 10个,提供100多个接口服务。请求量随着接口的增加趋势增长,目前每天的请求量达390多万次。每个接口上线周期为半天时间或更短。接口上线只需要根据需求方配置后立刻就可以在线使用,大大的减少了上线的周期。生产接口响应时间91.49%在10ms内,99.99%是在100ms以内。

三、后期展望

现在所有的接口都部署在一个集群,对于一些调用方,我们其实也可以区分高中低三个等级,将高优调用方部署在一个独立集群上,中等调用方部署在一个集群上,低优调用方部署在独立集群上,相互之间资源隔离。

实现测试环境的打通。由于大数据环境大部分只有生产环境,没有测试环境和测试数据,所以统一服务平台现在只能用于生产环境。开发环境或者测试环境无法调用联调,对于调用方只能通过mock的方式测试,这个也是后面我们需要考虑如何利用最低的成本实现测试环境的可用性,让调用方使用起来更加便捷。

有关4小时上线一个接口,高效统一的携程酒店数据服务平台实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐