• 去哪儿网机票搜索系统的高并发架构设计(要点节选)


    • 前端做静态文件的压缩,优化Http请求连接数,以减小带宽,让页面更快加载出来。
    • 前后端做了数据分离,让搜索服务解耦,在高并发情况下更灵活做负载均衡。
    • 后端数据(航班数据)99%以上来自缓存,加载快,给用户更快的体验。

    搜索系统设计架构


    前台搜索

    主要读取缓存,解析,合并航班数据返回给用户端。
    前台搜索是基于Web服务,高峰期时候最大启动了50台左右的Tomcat实例。搜索的URL规则是:出发城市+到达城市+出发日期,这和缓存系统存储最小单元:出发城市+到达城市+出发日期是一致的。
    Tomcat服务我们是通过Nginx来做负载均衡,用Lua脚本区分是国际航线还是国内航线,基于航线类型,Nginx会跳转不同搜索服务器:主要是国际搜索、国内搜索(基于业务、数据模型、商业模式,完全分开部署)。不光如此,Lua还用来敏捷开发一些基本服务:比如维护城市列表、机场列表等。

    高并发多线程应用


    Java的多线程对于高并发系统有下面的优势:
    • Java Executor框架提供了完善线程池管理机制:譬如newCachedThreadPool、 SingleThreadExecutor 等线程池。
    • FutureTask类灵活实现多线程的并行、串行计算。
    • 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。

    高并发下数据传输

    最早航班数据用标准的XML、JSON存储,不过随着搜索量不断飙升,CPU和带宽压力很大了。后来采取自己定义一种txt格式来传输数据:一方面数据压缩到原来30%~40%,极大的节约了带宽。同时CPU的运算量大大减低,服务器数量也随之减小。
    在大用户量、高并发的情况下,是特别能看出开源系统的特点:比如机票的数据解析用到了很多第三方库,当时我们也用了Fastjson。在正常情况下,Fastjson 确实解析很快,一旦并发量上来,就会越来越吃内存,甚至JVM很快出现内存溢出。原因呢,很简单,Fastjson设计初衷是:先把整个数据装载到内存,然后解析,所以执行很快,但很费内存。

    后台搜索

    后台搜索系统的核心任务是从外部的GDS系统抓取航班数据,然后异步写入缓存。
    机票的源数据都来自于各种GDS系统,但每个GDS却千差万别:
    1. 服务器遍布全球各地:国内GDS主要有中航信的IBE系统、黑屏数据(去机场、火车站看到售票员输入的电脑终端系统),国际GDS遍布于东南亚、北美、欧洲等等。
    2. 通讯协议不一样,HTTP(API、Webservice)、Socket等等。
    3. 服务不稳定,尤其国外的GDS,受网路链路影响,访问很慢(十几分钟长连接很常见),服务白天经常性挂掉。
    4. 更麻烦的是:GDS一般付费按次查询,在大搜索量下,实时付费用它,估计哪家公司都得破产。而且就算有钱 , 各种历史悠久的GDS是无法承载任何的高并发查询。更苦的是,因为是创业公司,我们大都只能用免费的GDS,它们都是极其不稳定的。

    引入NIO框架

    考虑GDS访问慢,不稳定,导致很多长连接。我们大量使用NIO技术:
    NIO,是为了弥补传统I/O工作模式的不足而研发的,NIO的工具包提出了基于Selector(选择器)、Buffer(缓冲区)、Channel(通道)的新模式;Selector(选择器)、可选择的Channel(通道)和SelectionKey(选择键)配合起来使用,可以实现并发的非阻塞型I/O能力。
    HTTP、Socket 都支持了NIO方式,在和GDS通信过程中,和过去相比:
    • 通信从同步变成异步模式:CPU的开销、内存的占用都减低了一个数量级。
    • 长连接可以支持更长超时时间,对国外GDS通信要可靠多了。
    • 提高了后台搜索服务器的稳定性。

    消息队列

    为了异步完成航班数据更新到缓存,我们采用消息队列方式(主备AMQ)来管理这些异步任务。具体实现如下:

    具体原理:按照每个GDS服务器稳定性(通过轮休方式,不断Check它们的可用性)和查询性能,我们算出一个合理的权重,给它分配对应的一组虚拟的Node节点,这些Node节点由一个Node池统一管理。这样,不同的GDS系统都抽象成了资源池里面的一组相同的Node节点。
    那么它具体如何运转的呢?
    当缓存系统相关航班数据过期后,前台搜索告知MQ有实时搜索任务,MQ统一把异步任务交给Router,这个时候Router并不会直接请求GDS数据,而是去找Node池。Node池会动态分配一个Node节点给Router,最后Router查找Node节点映射的GDS,然后去请求数据,最后异步更新对应的缓存数据。通过技术的实现,我们把哪些不稳定的,甚至半瘫痪的GDS充分利用了起来(包含付费的一种黑屏终端,我们把它用成了免费模式,这里用到了某些黑科技,政策原因不方便透露),同时满足了前台上亿次搜索查询!

    监控系统

    鉴于机票系统的复杂度和大业务量,完备监控是很必要的:
    1、整个Qunar系统架构层级复杂,第三方服务调用较多(譬如GDS),早期监控系统基于CACTI+NAGIOS ,CACTI有很丰富的DashBoard,可以多维度的展示监控数据。除此以外,公司为了保证核心业务快速响应,埋了很多报警阈值。而且Qunar还有一个NOC小组,是专门24小时处理线上报警:记得当时手机每天会有各种系统上百条的报警短信。
    2、复杂系统来源于复杂的业务,Qunar除了对服务器CPU、内存、IO系统监控以外,远远是不够的。我们更关心,或者说更容易出问题是业务的功能缺陷。所以,为了满足业务需要,我们当时研发了一套业务监控的插件,它的核心原理如下图:

    它把监控数据先保存到内存中,内部定时程序每分钟上传数据到监控平台。同时它作为一个Plugin,可以即插即用。接入既有的监控系统,它几乎实时做到监控,设计上也避免了性能问题。后期,产品、运营还基于此系统,做数据分析和预测:比如统计出票正态分布等。因为它支持自定义统计,有很方便DashBoard实时展示。对于整个公司业务是一个很有力的支撑。

    转载地址:http://mp.weixin.qq.com/s/1ipdByfsaTcHpo_W5gJVug
  • 相关阅读:
    在最近在研究榴莲品种的人工智能识别
    iOS开发:多线程技术概述
    Objective-C开发编码规范
    为什么OC语言很难
    内存堆栈的区别
    HR筒子说:程序猿面试那点事
    Objective-C语言的一些基础特性
    学习swift语言的快速入门教程推荐
    性能测试告诉你 mysql 数据库存储引擎该如何选?
    【柠檬班】需要先登录的接口如何做性能测试?
  • 原文地址:https://www.cnblogs.com/archermeng/p/7537080.html
Copyright © 2020-2023  润新知