Airbnb/Booking 系统设计(high level architecture)
原文地址 CodeKarle: Airbnb System Design | Booking.com System Design
B站搜 “Airbnb System Design” 有视频版本
需求:
功能性需求
系统用户包括商家和客人。
Hotel - 商家(拥有hotel的人)
onboarding - 商家可以入住系统。
update - 商家可以修改hotel相关内容,如增加 room,修改 pricing,增加新的 images等。
booking - 查看预定,收入等。
User - 客人(预定hotel的人)
search - 搜索指定地点,符合搜索条件的 hotel (价格区间、星级等级、 海景房等)。
book - 进行预定
check booking - 查看预定。
Analytics - 一些系统分析。
非功能性需求
Low latency
High Availability
High Consistency - (一旦用户预定了hotel,应立刻看到结果)
Scale (Google 搜索得知 )
500k Hotels (全世界大概有 50万 家 hotel)
10M Rooms (大概共有 1000万房间)
1000 Rooms / Hotel (Max: 7500) (所以假设每个hotel 大概有 1000个 room,有些有 7500个room, 估的高点,是为了更好的处理一些 edge cases)
总体设计和数据流向
Hotel Service提供给商家入驻和修改等功能,上传的图片保存在 CDN服务器,数据库保存图片链接。MySQL采用一个Master,多个Slave集群。流量高峰时动态增加Slave缓解读取压力。Hotel Service亦可动态水平扩展。
商家新增一个 room,数据将会写入 kafka,然后 search consumer将会拉取这些数据,存储到search数据库。
Search这里使用的是 Elasticsearch,同类型的可以使用 Solr,这里使用 ES,主要是想支持模糊查询(fuzzy search)。假设用户搜索马尔代夫(Maldives)的hotel,用户可能输入错误的名字。这种情况最好也会有搜索结果。
ES 集群 之上是 Search Service,提供给用户 搜索功能。
若此时系统流量激增,我们可以进行水平扩展,增加Kafka集群的node数量、增加 search consumers数量、增加 ES 集群的node数量。
Booking Service 坐落于 一个MySQL集群之上,当预定发生时,预定数据保存在此MySQL集群中。同时调用 Payment Service,成功之后,预定状态为“已确认”。
此时,当预定发生时,应当通知Search Service这个房间已被预定,不应再次出现在搜索结果中。所以此预定数据流入Kafka集群,被Search Consumers读取,修改相关数据。
对于live data (也就是处于中间状态的预定记录)存放于MySQL数据库,一旦预定到达最终状态(如 已完成,或取消)则进入 Archival Service,然后到达 Cassandra 集群。
这里使用 Cassandra 是因为 Cassandra能够轻松处理大量读写操作。
Notification Service 从 Kafka 读取需要通知的事件,比如 预定成功通知商家,预定被取消要通知用户,通知用户费用清单等。
Booking Mgmt Service 负责商家查看预定,用户查看预定历史等。连接两个数据源,从MySQL获取所有 active bookings,从 Cassandra获取已经发生了的预定信息。
这里在MySQL前面加了个 Redis,用来缓存查询的用户拥有的Bookings的结果。这里缓存策略是 write-through,即当有新的booking进入时,先写Redis,然后同步更新MySQL。
对于系统数据分析部分,往往一开始并不能预知所有需要分析的需求。所以这里使用 Hadoop 集群,针对系统所有的事件(比如商家所有hotel的基本信息,商家所有的预定,所有的交易信息等)都写入 Kafka。所以需要Spark Streaming Consumer来从Kafka读取这些事件,放入到Hadoop集群,然后通过Hive queries或任何其他类型的queries来构建一些reporting。
Hotel Service
部分APIs
部分 DB deisgn,红色部分是 主键或外健。
hotel 表中的 original_images 指的是商户上传的图片链接, display_images是压缩过后的图片链接。
rooms 表中 price_min, price_max, 这里通过Hadoop集群,运行machine learning model做一些供需分析得出一个 optimal price。也就是说供应少,需求多,那么价格就上涨。所以这里的 price_min, price_max就是 hotel提供的价格区间, 价格根据需求再次范围浮动。
facilities 是 hotel和room所配备的设备。
hotel_facilities 和 room_facilities 是两张 mapping 表。多对多的关系。
此处我们并没有使用Redis进行缓存,因为 hotel service并不会有流量激增的情况,而是在 search service。
Booking Service
部分DB design, 红色为主键或外健。
available_rooms 中 room_id, date, initial_quantity 字段来自于 hotel_service。 available_quantity意思是特定room_id, 特定的date,剩余的房间数量。表上有约束(constraint)限制不能为负值。
booking 表中 status字段有四个值可选(reserved, booked, cancelled, completed)。
部分API设计:
一个预定API 以post的形式,包括5个参数,其中并不包括price参数,后面从数据库中获取。
这里不从 booking API中获取 price是因为 request 可能被篡改,而且从request获取price并不是一个很好的设计。
当一个 booking 请求到达时,先检查available_rooms表中的 available_quantity 是否有足够的room,进一步则进行锁定,进行支付。
这里插入booking表和 修改 available_room表中的 available_quantity是在同一个transaction中完成的。如果available_quantity 只有一个,且同时有多人争抢,则通过数据库的约束 available_quantity 不能为负值来保证只有一条记录能修改数量成功,也就是只有一条记录插入booking,也就是只有一个人会跳转到支付流程。
这里进行锁定5分钟,我们借助于 Redis的TTL(Time To Live)功能,key过期会有个callback。
当支付成功,标记status为 Booked,Payment Service返回的 invoice_id 保存到 booking表中。相对应的 events 写入kafka,以便其它消费者使用。
当支付失败,标记 status为 Cancelled, 恢复 available_quantity。
当Redis中key过期,收到通知 callback,同时 用户也被跳转至 payment 流程,且支付成功。这里分两种情况:
1,如果先收到payment success,那么 booking 状态被改成 Booked,此时再收到key 过期,修改booking status时需要判断是不是 reserved 状态,只有 reserved 才能被 cancel。
2,如果先收到 key 过期通知,那么booking 状态被改成了 cancelled,然后再收到 payment success 。要么 revert payment;要么判断是否 还有 available_quantity,修改 booking 状态成 booked。
这里还有个问题是 Redis这种key 过期的方式,并非能保证你能在精准的时间点收到 callback,多少都会有延迟,具体情况就要看对实时性的需求了。也可以每隔一秒去主动查询 Redis,不过CPU等的消耗也是一个问题。
其实无论payment 成功与否,在收到payment 结果时,就直接可以从redis中删除这个肯定会过期的key。
技术栈的替换
mysql -> 可以用其它任何支持ACID的数据库,如 Postgres。
Redis -> Memcache 等。
Cassandra-> 这里打算继续使用 Cassandra,技术上可以用 HBase代替,但更费操作。
Kafka -> ActiveMQ, Rabbit MQ, Amazon queue等。但Kafka更易于扩展。
系统监控使用 Grafana
跨地域传播
数据中心的相互备份等。。。.... 看不下去了,接触到再说吧。
也太high level了 😂