酒店

【国内酒店】离线数据模式接入说明,增量和全量的理解及使用更新时间:2023/02/10

有些艺龙分销商,并不想使用艺龙提供的搜索服务,或许因为自定义程度不高,或许是因为想要与自己已经拥有的酒店产品资源进行整合,此时可能需要使用到离线数据模式。
离线数据模式,即分销商将艺龙的产品信息拉取下来存到自己的服务器中,自己为用户提供酒店产品的检索服务,可以实现自由的进行酒店排名、根据用户偏好推荐酒店等个性化的服务。
需要注意的是,该模式接入成本相对较高,请分销商自行评估自己的技术能力和业务需求来决定是否接入该模式。


一、一些基本的概念
分销商需要拉取的信息大体上分为两种:静态信息和动态信息。静态信息指的是不经常变动的信息,比如酒店名称、房型、酒店基础设施、酒店营业时间等等,动态信息指的是实时变动的信息,比如酒店的产品、价格、库存、规则等信息。静态信息艺龙会通过xml文件的格式返回,动态信息则会以json格式返回。
离线数据模式与实时搜索模式相比需要更了解艺龙的业务,所以需要先介绍下艺龙的几个对象以及之间的关系。
1.HotelId
也叫mHotelId,艺龙为每个酒店都分配了唯一的HotelId,这个唯一指的是同一时间内,每个实体酒店的HotelId不重复,即全局唯一,但是假设一个酒店在艺龙关闭(不一定真的关闭,只是在艺龙平台关闭)了,那么艺龙会回收其HotelId并有可能分配给另一个新增的实体酒店。简单来讲,可以理解为一个HotelId唯一标识一家真正的酒店,是一一对应的。

2.HotelCode
也叫sHotelId,艺龙会从不同渠道(比如直签、携程、去哪儿等)拿到一家酒店的产品,为了同一个酒店的不同渠道,我们定义了一个新的概念,叫供应商酒店。显然,一个实体酒店可以有多个不同的供应商,所以一个HotelId也就对应了多个HotelCode,HotelCode在艺龙系统中也是全局唯一的,所以HotelCode的数量远多于HotelId,需要特别注意的是HotelId有可能与HotelCode相等,这个和HotelId的全局唯一性、HotelCode的全局唯一性并不冲突。一般而言,HotelId和HotelCode相等时,表示这个HotelCode是艺龙直签的,但是并不能完全保证。

3.RoomId
也叫mRoomId,一家酒店HotelId会有多种房型,比如大床房、双床房、无烟房等等,这些我们也叫做物理房型,在一个物理酒店HotelId下,会有多个但不重复的RoomId。

4.RoomTypeId
也叫sRoomId,一个供应商酒店HotelCode也会有多种房型,我们叫做销售房型。假如一家物理酒店HotelId拥有大床房(RoomId:0001),双床房(RoomId:0002)、无烟房(RoomId:0003)三种房型,供应商A只对艺龙提供了大床房(RoomId:0001),双床房(RoomId:0002),那么我们会给这个供应商A提供的两种房型打上RoomTypeId标识,比如大床房(RoomITyped:0001),双床房(RoomTypeId:0002),接下来又有一个供应商B只提供双床房(RoomId:0002),那么我们打上标识双床房(RoomTypeId:0003)。可以看到,RoomTypeId和RoomId并不一定一致,是分开计量的,同时,不同HotelCode下的RoomTypeId也是唯一的,更直白的说:
一个物理酒店HotelId下,会有多个但不重复的RoomTypeId,他们分属于不同的HotelCode。

一个物理房型RoomId,可以关联多个不同的RoomTypeId。

更正:同一物理酒店HotelId下,会出现相同的RoomTypeId,RoomTypeId只有在同一个供应商酒店HotelCode下才是唯一的。上述错误叙述暂时保留,以供之前查看过的分销商理解。


5.RatePlanId
也叫rpId,通常我们会叫做产品Id,商品Id,但实际上这些是不准确的,准确的讲应该叫做价格计划Id,因为这个Id标识的并不是一个产品,而是一个售卖计划,所谓售卖计划,就是讲一个房型应该怎么卖,比如是不是要促销啊,是不是要送礼啊,是不是可以接机之类的。因为这些歧义,导致有些分销商会将hotel.data.rp接口的RatePlan字段(文档链接:http://open.elong.com/doc/info/cn-api-meta-hotel_data_rp#RatePlan),当做一个产品来看待,实际上并非如此。艺龙定义一个确切的产品,是需要确定至少3个Id的,即:
HotelId+RoomTypeId+RatePlanId

之前说过,HotelId在艺龙系统全局唯一,RoomTypeId在HotelId下全局唯一,加上售卖计划RatePlanId,最终唯一确定了这个产品。

更正:同一物理酒店HotelId下,会出现相同的RoomTypeId,RoomTypeId只有在同一个供应商酒店HotelCode下才是唯一的。

不过HotelId+RoomTypeId+RatePlanId仍然可以唯一确定一个产品,因为RatePlanId+RoomTypeId在HotelId下全局唯一。

上述错误叙述暂时保留,以供之前查看过的分销商理解。



二、酒店静态信息的获取
静态信息,包含酒店名称、地址、简介、房型、图片等等信息。通常与酒店的物理属性对象关联比较密切,比如酒店名称、地址这些与HotelId相关联,房型名称、大小、图片与RoomId相关联,而这些与HotelCode和RoomTypeId没有什么关系。

1.HotelId的获取
想要获取艺龙的酒店产品,首先要获取艺龙所有可用的酒店的HotelId,因为酒店产品需要根据HotelId来获取。HotelId可以通过hotellist.xml接口(文档链接:http://open.elong.com/doc/info/cn-api-meta-hotellist_xml)来获取,这个接口会返回艺龙所有的酒店的HotelId、酒店状态、包含产品(可能不是很准确)、最后更新时间等信息。酒店状态指的是这个酒店是否还在艺龙售卖,最后更新时间指的是这个酒店的静态信息的最后更新时间,请将这个最后更新时间记录下来,用途之后说明。每天可以获取一次hotellist.xml文件,查看每个HotelId对应的最后更新时间,如果这个时间大于本地记录的最后更新时间,那么就需要重新获取这个酒店的hoteldetail.xml静态文件来更新自己本地的静态信息。

2.获取酒店的静态信息
如前所述,酒店的静态信息包括酒店名称、房型、酒店基础设施、酒店营业时间等等,这些信息通过hotelId.xml接口(文档链接:http://open.elong.com/doc/info/cn-api-meta-hotelId_xml)获取,获取后请保存到本地数据库中,以便为用户展示,同时请每天根据hotellist.xml文件来更新一次所有的酒店静态信息。


三、酒店动态信息的获取
动态信息,包含产品信息、库存信息和价格信息三种,每种信息对应一个全量接口和一个增量接口。动态信息与供应商相关的对象关联更为密切,比如库存是指销售房型RoomTypeId的数量而非物理房型RoomId的数量,成单时也是传入RoomTypeId而非RoomId,但是成单时并不需要传入HotelCode,因为在一家物理酒店HotelId下,RoomTypeId都是唯一的。

1.全量与增量
首先我们先了解下全量与增量的概念以及为什么这么做。
产品全量(即hotel.data.rp接口),在艺龙OpenApi离线数据模式下,意指一个酒店,在接口调用的这一时刻,所有的规则(包含担保规则、预付规则、预订规则、增值服务、礼品等等)、价格计划(或者说产品、售卖计划、RatePlan,都是同一个意思)信息,同理,价格全量(hotel.data.rate接口),指的是上述条件下的一个酒店的所有价格信息,库存全量(hotel.data.inventory接口)指同等条件下的一个酒店的所有库存信息。
增量,指的是某个信息从上一次更新点到最近的所有变化的信息。
每一个全量接口都会对应一个增量接口,如下示例:
hotel.data.rp <------------> hotel.incr.state
hotel.data.rate <------------> hotel.incr.rate
hotel.data.inventory <------------> hotel.incr.inv

2.为什么会区分全量和增量?
动态信息,顾名思义,就是不断变化的信息,对实时性要求较高,比如酒店新增或下架产品,更改某个产品的价格,对某个房型进行关房操作,或者有人预订了一个产品,导致这个房型产品的库存数量减少,都需要分销商来实时获取这些变化,以使得自己本地的数据是最新的。而由于艺龙的酒店数量很多,具体到产品的数量就更多了,分销商不可能每秒都拉取一遍所有酒店的产品、价格、库存信息,那意味着每秒将有上百万次的请求,分销商处理不过来,艺龙服务器也扛不住。所以我们提供了增量接口。总而言之,全量接口是用来完整的拉取艺龙酒店产品数据使用的,这个时间可能会很长,增量接口是用来平时轮询来让本地数据和艺龙同步使用的,是数据实时性的保证。

3.全量接口如何使用?
在上面的说明中已经可以知晓如果获取所有有效的HotelId,这里的全量将会根据已经获取的HotelId来补全产品、库存和价格信息,这样为用户提供检索服务的所有数据就完备了。
全量数据的拉取,首先从产品信息接口(即hotel.data.rp接口)开始,使用已经存储的状态有效的HotelId,作为参数传入hotel.data.rp接口,以获取这家酒店下所有的产品信息并保存下来,然后调用hotel.data.rate接口来获取一段时间内这家酒店所有产品的价格信息,最后调用hotel.data.inventory接口获取房型的库存信息,这样,这家酒店所有的信息就完成落地了。每家酒店都这样落地完成后,全量任务也就结束了,整个过程视调用频率大约需要6-10小时完成,这个是正常的。
然而,由于全量调用的时间很长,有些分销商会有疑问,我第一次请求的酒店,过去数个小时候,它的数据时效性肯定已经丧失了,也就是说,这家酒店的数据过时了,那是数小时前的,现在肯定不能用了,这该怎么办?这个问题,需要先回答增量接口的使用后,再来说明。

4.增量接口如何使用?
以产品增量为例,分销商可以每秒调用3-5次增量接口来获取自己上一次更新后到现在的更新,hotel.incr.state接口会返回更新的酒店id以及一些更细致的信息,比如具体是哪个产品或者房型或者规则有了更新,然后通过请求对应的hotel.data.rp接口来获取最新的数据覆盖本地对应的数据,如果不想解析到底应该更新哪个数据,那么也可以在请求hotel.data.rp后更新所有的产品信息。hotel.incr.rate和hotel.incr.inv接口不同,这两个接口会返回和hotel.data.rate和hotel.data.inventory接口相同的完整的价格和库存信息,所以接收到hotel.incr.rate和hotel.incr.inv接口增量后并不需要请求hotel.data.rate和hotel.data.inventory接口,直接将获取到的信息覆盖本地数据即可。
注意一:全量数据拉取的时效性问题,这个就是全量接口使用的最后提出的问题,解决方法是这样的,在全量开始的同时,将增量接口的轮询任务也开启,一旦发现有已经获取完信息的酒店在本地数据中,那么进行上述的增量更新,如果发现一个更新中的酒店在本地数据中没有找到,那么忽略掉。
注意二:一般而言,增量轮询开始后就不再停止,一旦服务出现问题导致增量停止,请保留最近一次的LastId,然后尽快修复,艺龙会保留最近24小时以内各项增量数据,如果24小时内没有修复,那么将永远丢失一部分增量,这里也有相应的解决办法,但是增量丢失意味着一定时间内数据时效性丧失,会导致大量用户可以查询到产品但无法预订的情况出现。解决方案以下说明。

四、增量接口的详细说明
增量接口的轮询方式,单纯的看文档而言是较为晦涩难明的,虽然实质上并不难理解,但是还是要详细说明一下。
1.LastId是什么?
LastId,字面意思是最后的Id,在增量接口中的意思,可以理解为最后一次更新的Id,但是这样说还是不清不楚。所以我这里先将LastId不准确的定义成时刻,大致可以认为时刻和LastId是一一对应的,这样就很容易理解,LastId和一个时刻对应,并且LastId也只会越变越大,所以,增量接口中传入上一次调用增量接口的返回值中的最大的LastId,可以等价为传入了上一次获取的最后的一条更新的时刻,这时返回值就是在这个时刻(LastId)之后的更新。然后再次将这次获取的最后一条更新的LastId传入,这样不断的轮询,就可以保证自己本地数据的时效性了。

2.第一次调用接口LastId应该传入什么?
这个问题其实文档中也有说明,但实际上我这里并不建议第一次传入0,因为传入0,返回的是20甚至30个小时前的增量数据,那些数据大多都失去了时效性,再去获取意义不大。可以想一下什么时候会第一次调用,之前讲到过,就是在全量拉取任务开始时,才会有第一次的增量调用,而之前也讲到过,LastId与时刻大致是一一对应的,所以我们提供了一个接口,从时刻获取LastId的接口,hotel.incr.id(文档链接:http://open.elong.com/doc/info/cn-api-meta-hotel_incr_id),这个接口使用方法和返回值处理很简单,看文档很容易理解。在这里,我们建议传入全量拉取任务开始时刻的前5分钟这个时刻作为入参,原因比较复杂,之后再进行说明。这时返回的LastId就是增量接口第一次应该传入的LastId。

3.增量接口的返回数量限制导致的问题
增量接口传入LastId,会返回这个LastId对应的时刻到当前时刻为止的更新数据,但是出于对接口响应速度的考虑,艺龙规定所有增量接口每次调用最多返回1000条更新数据,举例:假如10:00到12:00有5000条更新数据,分销商传入了10:00对应的LastId,然后发现到返回了1000条数据,最后一条对应的时刻是11:20,而后面11:20到12:00的4000条数据并没有返回,这样就需要分销商将11:20对应的LastId再次传入,以获取之后的更新数据。
分销商可能会疑惑为什么会出现这种情况,不是说只要一直不停的轮询就能保证数据实时性吗?怎么会拿很久之前的时刻来获取增量?原因大致可以分为两种,一种是分销商自己系统出现问题导致增量任务停止,重启后需要从最后一次获取的LastId来继续获取。
另一种则是由于艺龙增量数据的产生速度已经超过了分销商系统的处理速度。一般增量接口响应时间在100ms以内,假如分销商设计良好,使用异步处理的方式,那么处理这次请求的时间大约在200~300ms之间,也就是之前所说的每秒可以调用3-5次增量接口。这样算下来,分销商每秒最多处理1000*5条增量,如果艺龙每秒产生的增量超过5000,那么分销商将跟不上艺龙的更新速度,这种现象在库存增量中尤为明显。
第一种其实很容易解决,只需要不停的调用增量接口跟上就行,但是第二种并不容易解决,系统瓶颈摆在那里。艺龙这里也没有特别有效的办法,我们只能尽力的减少增量的产生。但是效果最明显的方法还是要分销商自己处理,方案如下:
调用增量接口后,关注下最后一条更新的时刻,如果这个时刻与当前时刻相差超过了2小时(或者更短比如半小时,代理自行决定),那么就丢弃这个LastId,使用hotel.incr.id接口,传入当前时间来获取最新的LastId,使用这个继续去拉取增量。有人会问,这中间丢弃的增量怎么办?这样导致的数据不准确怎么办?其实一般增量多到这种地步,后续基本都会有相同的酒店房型、产品的增量的,只要更新最新的数据就可以,如果后续没有了,那么确实会造成数据的不准确。这个时候,就需要开启全量更新任务,把所有的酒店数据重新刷一遍。当然,我们不建议丢弃增量后立即进行全量更新,那样不合适也做不到,我们建议遇到这种情况,每天早上(6点左右)和每天晚上(10点左右)各跑一次全量任务就可以了,中间丢弃增量造成的损失其实是可以接受的。

4.增量接口返回的时刻顺序导致的问题
之前有几次讲到,增量接口的LastId大致是和时刻一一对应的,这个大致的意思是,同一时刻确实有可能对应多个LastId,这种情况在增量较多时很容易出现。另外一个问题是,我们系统中增量产生后,推送到增量数据库时并不保证顺序性,而增量数据库接收到增量后会依次打上LastId,所以造成的结果是,LastId是有顺序的,但是其对应的时间实际上是乱序的。但是不用太在意这个,这个只会有两个影响,一种是第一次开启增量任务时,一种是重启增量任务时,重启增量任务就上上面说过的,一种是分销商系统中断,一种是增量太多主动丢弃。这两种情况下,之前也有说明,只需要在使用hotel.incr.id获取LastId时,传入所需时刻的前5分钟就可以。比如第一次拉取增量,那么传入的是全量更新开始时间的前5分钟,比如系统问题导致丢掉了LastId,那么传入系统宕机时间的前5分钟,特别注意的是,主动丢弃增量的情形并不需要往前推5分钟,原因可以自己想一想。

五、后续步骤
到了这里,其实后续就不需要再做说明了,后续的步骤,包括成单前校验、成单以及成单后的运营,这些都和实时搜索模式相同,不再赘述。其实最为关键的是分销商要做好自己的搜索服务才是。