成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

攜程中轉交通方案拼接性能優化

人工智能 新聞
本文將結合實例,介紹在中轉交通拼接性能優化過程中所遵循的原則、分析和優化方法,旨在為讀者提供有價值的參考和啟示。

作者簡介

簡言,攜程后端開發經理 ,關注技術架構、性能優化、交通規劃等領域。

一、背景介紹

由于交通規劃和運力資源的限制,用戶查詢的兩地之間可能沒有直達交通,或者在重大節假日時,直達交通都已售罄。不過,通過火車、飛機、汽車、船舶等兩程或多程中轉的方式,用戶仍然可以到達目的地。此外,中轉交通有時在價格和耗時方面更具有優勢。例如,對于從上海到運城,通過火車中轉可能比直達火車更加快捷和便宜。

圖片

圖1 攜程火車中轉交通列表

在提供中轉交通方案時,很重要的一個環節是將兩程或多程的火車、飛機、汽車、船舶等拼接起來組成可行的中轉方案。而中轉交通拼接的第一個難點是拼接空間極大,僅考慮上海做中轉城市,就可以產生近億種組合;另一個難點在于對實時性有要求,因為產線數據隨時變化,需要不斷地查詢火車、飛機、汽車、船舶的數據。中轉交通拼接需要大量的計算資源和IO開銷,因此,對其性能進行優化顯得尤為重要。

本文將結合實例,介紹在中轉交通拼接性能優化過程中所遵循的原則、分析和優化方法,旨在為讀者提供有價值的參考和啟示。

二、優化原則

性能優化需要在滿足業務需求的前提下,在各種資源和約束條件下去平衡和取舍,遵循一些大的原則有助于消除不確定性,去逼近解決問題的最優解。具體來說,中轉交通拼接優化過程中主要遵循以下三個原則:

2.1 性能優化是手段而不是目的

雖然本文是關于性能優化的,但仍需要在最開始強調:不要為了優化而優化。滿足業務需求的方式有很多,性能優化只是其中一種。有時候問題非常復雜,限制也很多,在不顯著影響用戶體驗的前提下,通過放寬限制或采用其他流程來減少對用戶的影響,這也是解決性能問題的好方法。在軟件開發中,存在許多通過犧牲少量性能來實現大幅降低成本的事例。例如,在Redis中用于基數統計(去重)的HyperLogLog算法,它在標準誤差為0.81%的前提下,只需要12K空間就能夠統計264的數據。

回到問題本身,由于需要頻繁地查詢產線數據,并且進行海量的拼接操作,那么如果要求每個用戶查詢時都立刻返回最新鮮的中轉方案,成本將會非常高。為了降低成本,需要在響應時間和數據新鮮度之間進行平衡。經過仔細考慮選擇可以接受分鐘級的數據不一致,對于一些冷門線路和日期,可能在首次查詢時沒有好的中轉方案,此時引導用戶重新刷新頁面即可。

2.2 不正確的優化是萬惡之源

Donald Knuth在《Structured Programming With Go To Statements》中提到:“程序員們浪費大量的時間去思考、擔憂非關鍵路徑的性能,而嘗試優化這部分性能,對整體代碼的調試和維護都有非常嚴重的負面影響,因此97%的情況,我們應該忘記小的優化點”。簡而言之,在沒有發現真正的性能問題之前,在代碼層面過度炫技式的優化,不僅不會提高性能,反而可能會導致更多的錯誤。然而作者同樣也強調:“對于剩下關鍵的3%,我們也不要錯過優化的機會”。因此,需要時刻關注性能問題,不做會影響性能的決策,并在必要的時候做正確的優化。

2.3 量化分析性能,明確優化方向

正如前一節所述,在進行優化之前,首先要量化性能并找出瓶頸,這樣優化的才更有針對性。量化分析性能可以借助耗時監控、Profiler性能分析工具、Benchmark基準測試工具等,重點關注耗時特別長或者執行頻率特別高的地方。正如阿姆達爾定律所述:“系統中對某一部件采用更快執行方式所能獲得的系統性能改進程度,取決于這種執行方式被使用的頻率,或所占總執行時間的比例”。

此外,還需要注意到性能優化是一場持久戰。隨著業務的不斷發展,架構和代碼也不停地變化,因此更需要持續量化性能,不斷分析瓶頸和評估優化效果。

三、性能分析之路

3.1 梳理業務流程

在性能分析之前,首先要梳理業務流程。中轉交通方案拼接主要包含以下四個步驟:

a.  加載線路圖,如北京經南京中轉到上海,只考慮線路本身的信息,與具體的班次無關;

b.  查火車、飛機、汽車、船舶的產線數據,包括出發時間、到達時間、出發站、到達站、價格和余票信息等;

c.  拼接出所有可行的中轉交通方案,主要是考慮換乘時間不能過短,以免無法完成換乘;同時也不宜過長,以免等待太久。拼接出可行的方案后,還需要完善業務字段,例如總價格、總耗時和換乘信息等;

d.  根據一定的規則,從拼接出的所有可行中轉方案中篩選出一些用戶可能感興趣的方案。

3.2 量化分析性能

(1)增加耗時監控?

耗時監控是一種最直觀的從宏觀角度觀察各個階段耗時情況的手段。它不僅可以查看業務流程各階段的耗時值與耗時占比,還可以長期觀察耗時變化趨勢。

耗時監控可以借助公司內部的指標監控告警系統,在中轉交通方案拼接的主要流程中增加耗時打點。這些流程包括加載線路圖、查詢班次數據并進行拼接、篩選和保存拼接方案等。各個階段的耗時情況如圖2所示,可以看到,拼接(含查產線數據)的耗時占比最高,因此成為未來重點優化的目標。

圖片

圖2 中轉交通拼接耗時監控

(2)Profiler性能分析?

耗時打點可能會侵入業務代碼,并對性能產生影響,因此不宜過多,更適合監控主要流程。與之對應的Profiler性能分析工具(例如Async-profiler),可以生成更具體的調用樹以及各函數的CPU占用比例,從而幫助關鍵路徑和性能瓶頸的分析與定位。

圖片

圖3 拼接調用樹與CPU占比

如圖3所示,拼接方案(combineTransferLines)占53.80%,查產線數據(querySegmentCacheable,已使用緩存)占21.45%。在拼接方案中, 計算方案評分(computeTripScore,占48.22%)、創建方案實體(buildTripBO,占4.61%)和檢查拼接可行性(checkCombineMatchCondition,占0.91%)是占比最大的三個環節。

圖片

圖4 方案打分調用樹和CPU占比

繼續分析占比最高的計算方案評分(computeTripScore)時,發現主要與自定義的字符串格式化函數(StringUtils.format)有關,包括直接調用(用于展示方案評分細節),以及通過getTripId間接調用(用于生成方案的ID)。自定義的StringUtils.format中占比最高的是java/lang/String.replace,Java 8原生的字符串替換是通過正則實現的,效率比較低(這一問題在Java9之后已經改進了)。

// 計算方案評分(computeTripScore) 中調用的StringUtils.format代碼示例
StringUtils.format("AAAA-{0},BBBB-{1},CCCC-{2},DDDD-{3},EEEE-{4},FFFF-{5},GGGG-{6},HHHH-{7},IIII-{8},JJJJ-{9}",
aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj)


// getTripId 中調用StringUtils.format代碼示例
StringUtils.format("{0}_{1}_{2}_{3}_{4}_{5}_{6}", aaaa, bbbb, cccc, dddd, eeee, ffff)


// 通過Java replace實現的自定義format函數
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
template = template.replace("{" + i + "}", parameters[i] + "");
}
return template;
}

(3)Benchmark基準測試?

借助Benchmark基準測試工具可以更準確地測量代碼的執行時間。在表1中,我們通過JMH(Java Microbenchmark Harness)對三種字符串格式化方法和一種字符串拼接方法進行耗時測試。測試結果表明,使用Java8的replace方法實現的字符串格式化性能最差,而使用Apache的字符串拼接函數性能最佳。

表1 字符串格式化與拼接性能對比

實現

執行1000次平均耗時(us)

使用Java8的replace實現的StringUtils.format

1988.982

使用Apache replace實現的StringUtils.format

656.537

Java8自帶String.format

1417.474

Apache的StringUtils.join

116.812

四、性能優化之路

通過以上的性能分析,我們發現拼接和查詢產線數據是性能瓶頸,字符串格式化影響尤其大。因此,我們將致力于優化這些部分,以提高性能表現。

4.1 優化代碼邏輯

優化代碼邏輯是最簡單且性價比最高的方法,可以是修正有問題的代碼或替換為更好的實現。不同的實現,哪怕減上幾納秒,累加起來也是很可觀的。借助一些經典算法或數據結構(如快速排序、紅黑樹等)可以在時間和空間復雜度方面帶來顯著優勢。回到中轉交通方案拼接性能優化本身,優化的代碼邏輯主要包括:

(1)優化字符串拼接性能?

如前面的JMH的結果所示,自定義的字符串格式化函數性能最差,因此作為重點優化目標。優化前后的對比如下所示:

// 優化前,通過Java replace實現的format函數
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
template = template.replace("{" + i + "}", parameters[i] + "");
}
return template;
}
// 優化后,通過Apache replace實現的format函數
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
String temp = new StringBuilder().append('{').append(i).append('}').toString();
template = org.apache.commons.lang3.StringUtils.replace(template, temp, String.valueOf(parameters[i]));
}
return template;
}

根據JMH的測試結果,即使是優化后的格式化函數,其性能也不是最優的。在不顯著影響可讀性的前提下,應盡量使用性能更優的StringUtils.join函數。

// 優化前
StringUtils.format("{0}_{1}_{2}_{3}_{4}_{5}_{6}", aaaa, bbbb, cccc, dddd, eeee, ffff)


// 優化后
StringUtils.join("_", aaaa, bbbb, cccc, dddd, eeee, ffff)

為進一步提升性能,可以在computeTripScore 函數中添加一個開關,僅在調試模式下才展示評分細節,這將確保該字符串格式化函數僅在需要時才被調用。

if (Config.getBoolean("enable.score.detail", false)) {
scoreDetail = StringUtils.format("AAAA-{0},BBBB-{1},CCCC-{2},DDDD-{3},EEEE-{4},FFFF-{5},GGGG-{6},HHHH-{7},IIII-{8},JJJJ-{9}",
aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj);
}

優化后的CPU占比如圖5所示,此時字符串格式化已經不再是性能瓶頸。

圖片

圖5 優化后的拼接調用樹和CPU占比

(2)增加索引降低拼接時間復雜度?

圖片

圖6 增加索引降低拼接時間復雜度

在中轉拼接過程中,我們需要將第一程每個班次的到達時間與第二程每個班次的出發時間進行比較,以判斷中轉時間是否過短或過長。為簡化說明,假設換乘時間間隔需要滿足大于30分鐘且小于6小時。以北京到上海經南京中轉的兩程火車為例,3月9日北京到南京有66個班次,南京到上海有275個班次,考慮到隔夜車,還需要算上3月10日南京到上海的275個班次,那么最多需要比較36300(66*275*2)次。

為避免頻繁比較,參考了MySQL B+樹索引的思想,將第二程南京到上海的所有火車班次數據構建成紅黑樹。其中,樹的鍵為秒級時間戳,例如2023-03-09 11:29出發的G367鍵為1677247680,值為G367的班次數據。有了索引樹,最多只需要10次比較,就可以找到最近的滿足最小換乘時間要求的班次。同理,最多需要10次比較,就能找到滿足最大換乘時間要求的最晚班次。兩者之間的所有班次都滿足耗時要求,直接進行拼接即可。改進后最多需要比較1320(66*(10+10))次,約為原來的1/27.5。

(3)使用多路歸并求Top-K算法?

在篩選方案時,會存在以下場景:有多個中轉點,每個中轉點都有數百個得分較高的方案(內部已按得分由高到低排序,通過小根堆實現)。最終需要將這些方案合并,并從中篩選出得分最高的K個方案。

最簡單的方法是使用快速排序將所有的方案排序,然后選取前K個,時間復雜度約為O(nlog2n)。然而,這并沒有利用到每個隊列自身有序的特點。通過多路歸并算法時間復雜度可降為O(nlog2k),具體步驟為:

a.  從每個隊列中拿出第一個元素(得分最高的方案),放入大根堆中;

b.  從大根堆堆頂拿出最大的元素,放到結果集中;

c.  如果該元素所在的隊列還有剩余元素,則將下一個元素加入堆中;

d.  重復步驟2和3,直到結果集中包含K個元素或所有的隊列都為空。

圖片

圖7 多路歸并求Top-K算法

4.2 構建多級緩存

緩存是一種典型的以空間換時間策略,可以緩存數據和計算結果,緩存數據可以提高訪問效率,緩存結果避免了重復計算。緩存在帶來性能提升的同時,又會引入新的問題:

  • 緩存容量有限,需要仔細斟酌數據的加載、更新、失效和替換策略;
  • 緩存架構的設計:通常來說內存緩存(如HashMap、Caffeine等)性能最高,而Redis等分布式緩存次之,RocksDB相對較慢,容量上限則正好相反,需要仔細選型并搭配使用;
  • 緩存不一致問題如何解決,能接受多久的不一致。

在中轉交通方案拼接過程中,需要使用大量的基礎數據(如車站、行政區域等),以及海量的動態數據(例如班次數據)。綜合以上因素并結合中轉交通拼接的業務特點,緩存架構做如下設計:

  • 基礎數據(如車站、行政區域等),因數據量小,變化頻率低,全量保存到HashMap中,周期全量更新;
  • 部分火車、飛機、汽車、船舶的班次數據緩存到Redis中,以提高訪問效率和穩定性。不同產線采取的緩存策略稍有不同,但總的來說是定時更新與搜索觸發更新相結合的方式;
  • 一次拼接過程中可能查詢數百次產線數據,Redis毫秒級的延遲累加起來也是非常大的。因此,希望在Redis之上再構建一層內存緩存以提高性能。通過分析發現拼接過程中存在非常明顯的熱點數據,熱門日期和線路的查詢占比非常高且數量相對有限。因此可以將這部分熱點數據保存到內存緩存中,使用LFU(Least Frequently Used)替換,最終產線數據內存緩存命中率達到45%以上,相當于降低近一半的IO開銷。
  • 因為可以接受分鐘級的數據不一致,所以將拼接結果緩存起來,在有效期內,如果下一個用戶查詢同一出發日期的相同線路,直接使用緩存數據即可。因為拼接的中轉方案數據相對較大,所以將拼接結果保存到RocksDB中,雖然性能不如Redis,但是對于單次查詢影響還可以接受。

圖片

圖8 多級緩存結構

4.3 預處理

盡管理論上可以選擇任意城市作為兩地的中轉點,但實際上大部分中轉城市都無法拼接出優質的方案。因此,先通過離線預處理篩選出部分高質量的中轉點,從而將求解空間從幾千降至數十。相對于動態變化的班次,線路數據是相對固定的,每天計算一次即可。此外離線預處理可以借助大數據技術,處理海量數據,相對對耗時不敏感。

4.4 多線程處理

在一次拼接過程中,需要處理數十條不同中轉點的線路。每個線路的拼接是相互獨立的,因此可以采用多線程處理,這樣可以最大程度地降低處理時間。但受線路班次數量和緩存命中率的影響,不同線路的拼接耗時很難一致。很多時候,分配相同任務數量的兩個線程,即使一個線程很快執行完,也要等待另外一個線程執行完才能進行下一步操作。為避免這種情況,這里借助ForkJoinPool的work-stealing機制。這個機制可以確保每個線程在完自己的任務后,還會分擔其他線程未完成的工作,提高并發效率,減少空閑時間。

但是多線程也不是萬能的,使用時需要注意:

  • 子任務的執行需要相互獨立、互不影響。如果存在依賴關系,則需要等待前一個任務執行完才能開始下一個任務,這樣會使多線程失去意義;
  • CPU核數決定了并發能力的上限,過多的線程會因頻繁切換上下文而降低性能,需要特別關注線程數、CPU使用率、CPU Throttled time等指標。

4.5 延遲計算

通過將計算推遲到必要的時刻,可能避免很多多余的開銷。例如,在拼接完中轉方案后,需要構建方案實體并完善業務字段,這部分也比較消耗資源。而且并非所有拼接的方案都會被篩選出來,這意味著這部分未被篩選的方案仍然需要耗費計算資源。因此延遲完整方案實體對象的構建,先將拼接過程中的數以萬計的方案保存為輕量的中間對象,只對篩選之后的數百個中間對象構建完整的方案實體。

4.6 JVM優化

中轉交通拼接項目是基于Java 8的,并使用G1(Garbage-First)垃圾收集器,部署在8C8G機器上。G1在實現高吞吐量的同時盡可能滿足停頓時間的要求,系統架構部門設置的默認參數已經能夠適用于大多數場景,通常不需要專門的優化。

但有些線路中轉方案過多,導致報文太大,超過Region大小的一半(8G 默認Region大小是2M),導致很多應該進入年輕代的大對象直接進入了老年代,為了避免這種情況,將Region大小改為16M。

五、總結

通過以上的分析和優化,拼接耗時變化如圖9所示:

圖片

圖9 中轉交通方案拼接性能優化效果

雖然每個業務和場景都有各自的特點,性能優化時也需要具體分析。但原理是相通的,依然可以參考本文所述的分析和優化方法。本文所有的分析和優化方法總結如圖10所示。

圖片

圖10 中轉交通方案拼接優化總結

責任編輯:張燕妮 來源: 攜程技術
相關推薦

2022-07-15 09:20:17

性能優化方案

2022-07-08 09:38:27

攜程酒店Flutter技術跨平臺整合

2016-09-01 09:39:20

攜程無線

2021-09-17 12:54:05

AI 數據人工智能

2023-06-09 09:54:36

攜程工具

2014-12-25 17:51:07

2013-12-02 15:37:18

華為浙大中控智慧交通

2023-07-07 14:18:57

攜程實踐

2022-03-30 18:39:51

TiDBHTAPCDP

2022-09-09 15:49:03

攜程火車票組件化管理優化

2022-04-07 17:30:31

Flutter攜程火車票渲染

2019-03-01 11:03:22

Lustre高性能計算

2022-10-27 09:42:22

數據庫SQL

2022-07-15 12:58:02

鴻蒙攜程華為

2016-10-11 14:57:33

攜程APP性能優化

2022-08-12 08:38:08

攜程小程序Taro跨端解決方案

2022-05-13 09:27:55

Widget機票業務App

2013-10-15 14:43:01

2014-12-24 10:45:05

攜程

2023-07-07 14:12:52

攜程開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: www.99热这里只有精品 | 中文字幕视频一区二区 | 男女网站免费 | 日韩黄a| 一区二区三区视频 | 国产在线成人 | 在线欧美亚洲 | 免费在线一区二区三区 | 在线观看中文字幕一区二区 | 亚洲成人一区二区在线 | 夜夜爆操 | 日韩欧美在 | 久久久久久久久久久国产 | 免费黄色日本 | 中文字幕国产日韩 | 国产乱码久久久 | 欧美色人 | 精久久久 | 婷婷丁香在线视频 | 亚洲人成人一区二区在线观看 | 国产一区二区精品在线观看 | 美国黄色毛片 | 成人在线免费观看视频 | 国产成人免费在线 | 国产一级视频在线播放 | 国产高清免费 | 精品国产成人 | 亚洲视频二区 | 久久久久国产一区二区三区 | 亚洲精品美女视频 | 国产一区二区精品在线观看 | 国产在线视频一区二区董小宛性色 | 高清免费在线 | 青娱乐av | 男人天堂99 | 欧美视频1 | 美日韩中文字幕 | 精品一区二区三区在线观看国产 | 伊人精品久久久久77777 | 成年人网站在线观看视频 | 国产亚洲精品精品国产亚洲综合 |