山葉 YAMAHA 與 Gogoro 㩗手合作!將用 Gogoro 電池交換結構在台推新車型

台灣知名電動機車品牌 Gogoro 將與日本著名機車品牌 YAMAHA 共同合作研發機車,此共同合作的車種將於 2019 年夏天發售,由台灣山葉機車發售,新車型將採用 Gogoro 的充換電結構與網路,Gogoro 這次與老牌機車廠合作無疑給其換電系統打了劑強心針,甚至有可能藉由 YAMAHA 之助在世界各地推動其系統,這個合作勢必也將對光陽(Kymco)與尚未選擇何種充換電系統的三陽(SYM)帶來更多壓力。

根據兩公司發出的資料顯示,該合作計畫的內容是有關電動機車的產品開發、委託製造以及電池交換系統的使用,雙方預計於今年內簽署正式合約。屆時將以 Gogoro 市面銷售的車款為基礎,進行 YAMAHA 品牌的電動機車設計,並委由 Gogoro 生產。所完成的車輛則交由台灣山葉機車的銷售通路進行銷售,預計 2019 年夏季將推出第一個車款。

同時,兩家公司的事業合作夥伴「住友商事株式會社」,在促成雙方合作上也扮演了重要的角色。

YAMAHA 自 1966 年起投入台灣機車市場,目前以台灣山葉機車所生產的機車為主,年間販賣 29 萬台(2017 年實績)。同時也製造、銷售電動機車 E-VINO,並輸出到日本。以開發機能來說,YAMAHA 在台灣設有 YAMAHA Motor R&D Taiwan,主要是負責台灣市場的速克達機車開發。此次和 Gogoro 的合作案,不只是想要在台灣市場擴充其包含燃油車種在內的產品種類,在電動車的部分,也想透過運用 Gogoro 所擁有的電池交換站網路,提高消費者使用上的便利性。

Gogoro 自 2015 年起投入台灣機車市場,以製造自有品牌的智慧雙輪和可方便交換電池的電池交換站,開展全新的電動機車商業模式。Gogoro 能源網路目前在台灣已設置超過 750 個電池交換站,預計 2019 年初將超過 1,000 站。並且,自產品上市以來,已達到 1,700 萬次以上的電池交換次數,透過此次的合作,Gogoro 可望擴大電動機車的產能。

YAMAHA 發動機株式會社 MC 事業本部長木下拓也表示:「此次和台灣 Gogoro 的合作,不只增加顧客對移動工具的選擇,同時透過共用先進的電池交換系統,將對新的移動工具服務和創造市場帶來挑戰。」

Gogoro 創辦人暨執行長陸學森表示:「Gogoro 為推動能源開放平台的創新品牌,並運用能源網路的建置來推進大型智慧城市的轉型。這次我們非常榮幸能與 YAMAHA 合作,向實現能源願景的目標邁出重要的一步。」

(合作媒體:。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

Gogoro 宣布與新北市政府合作擴充電池交換站,擴充大台北充電網路

Gogoro 致力於電池交換型式的供電系統,提供比傳統機車更為綠能的選擇。13 日 Gogoro 宣布與新北市政府合作建置電池交換站,加上上個月台北市年底前建置的千座電池交換站,大台北地區將逐漸追上加油站數量。

Gogoro 在新北市於明年第一季前預計完成建置 20 座電池交換站、兩座太陽能電池交換站,建置範圍涵蓋三重區、三峽區、中和區、汐止區、板橋區、林口區、新店區、新莊區、樹林區、蘆洲區等新北都會區域。

Gogoro 營運副總潘璟倫表示:「目前雙北市平均每月電池交換次數高達近 28 萬次,為全台灣使用率最高的地區。為了加速提供車主的能源需求,Gogoro 不斷積極的佈建電池交換站以及尋找適合的地點,這次非常感謝新北市政府給予機會讓我們建置足夠的電池換電站提供消費者使用,希望透過這樣的合作持續帶動電動機車的發展。」

Gogoro 在台北市電池交換站已經超越傳統加油站的數量,而與台北市政府合作的 1,000 座電池交換站將拉大差距。本月在大台北地區新開台北天母忠誠、新北新店民權兩家門市。大台北地區將共有 35 家銷售服務據點同時提供完整的服務,完善台北地區服務網路。

配合開學季,Gogoro 也於 9/15 – 10/14 推試騎送限量「All Pass 文具組」,只要消費者到門市體驗試乘,即可獲得此試騎好禮。

(合作媒體:。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

台達加入國際電動車倡議 EV100,推動全球低碳交通

電源管理及能源基礎解決方案廠商台達 26 日宣布響應國際電動車倡議 EV100,成為台灣第一家及全球第一家電動車能源基礎設施提供者的會員。台達承諾於 2030 年前,將在主要營運據點廣設電動車充電設施,提供員工及客戶電動車使用誘因,協助推動低碳交通轉型以對抗氣候變遷。

EV100 為氣候組織(The Climate Group)2017 年發起的全球倡議,目的為透過全球具有影響力的企業及政府組織,加速交通運輸之低碳轉型,以呼應全球升溫低於 2℃ 的聯合國目標。目前已有 HP、聯合利華、Ikea、百度等 20 家國際企業響應。

台達發言人暨企業永續發展周志宏資深協理表示,氣候變遷日益急劇,降低交通設施帶來的溫室氣體排放為另一項重要課題。電動車為低碳城市的解決方案之一,近年來受到外界許多關注,台達以「環保 節能 愛地球」為企業使命,致力為永續城市提供解決方案。運用核心能力持續研發高效能電源及整合式系統,為城市的智慧能源設施布局,並且成為全球領先的電動車製造商提供關鍵車用零組件。在企業內部,我們希望透過友善及便利的電動車充電樁與服務,提供員工及客戶使用電動車的誘因,減少環境負荷。台達至今已於全球總部、營運據點,以及全球生產據點,設置超過 40 支電動車充電樁,支援不同規格的充電需求,並已在廠區提供電動巴士作為交通車,預計將全面使用電動巴士,以降低員工交通的碳排。

因應全球電動車與充電網絡蓬勃發展的趨勢,台達整合內部 40 年以上的電源轉換與管理的技術能力,提供業界先進的電動車充電解決方案。日前更榮獲美國能源部研發專案經費,合作開發充電輸出功率可達 400kW 的高速電動車充電機(Extreme Fast Charger,XFC),預期不到 10 分鐘的快速充電即可為未來電動車款提供 180 英里的行駛里程(約 288 公里)。除了電動車充電技術的提升,此研究專案的數據和成果將能幫助汽車製造商、相關技術提供者、城市政府、與電力公司更加了解電動車高速充電如何影響電力需量反應規劃,以及充電站如何整合可再生能源,以避免大量的高速充電對電網基礎設施造成壓力。

(資訊、圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

川普關稅威脅奏效,SK Innovation 擬赴美設電動車電池廠

南韓能源業者 SK Innovation 週一宣布,為鞏固美國客戶的訂單,正在考慮赴美設置電動車電池廠。美國為全球最大電動車市場之一,電池需求可期。

美國總統川普五月根據 232 條款,以國安之名就進口汽車與零件啟動調查,雖然還未決定是否開徵 25% 的懲罰性關稅,但 SK Innovation 顯然已未雨綢繆,提早思考因應對策與未來投資佈局。

路透社報導,SK Innovation 發言人表示,設廠位置目前鎖定在美國南部地區,已有二至三個州列入考慮,但確切時間與其它設廠細節則還未敲定。

SK Innovation 競爭對手 LG Chem 目前已在美國設有生產據點,通用是主要客戶。

根據國際能源總署(International Energy Agency)五月底發佈報告顯示,2017 年全球電動車加油電混合動力車突破三百萬輛、年增 54%,中國為驅動成長主力,美國與北歐國家也快速成長中。(businessgreen.com)

(本文內容由 授權使用。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

抗暖化不遺餘力,丹麥 2030 年將禁售汽柴油車

丹麥首相拉斯穆森(Lars Løkke Rasmussen)日前向國會提案,計劃在 2030 年禁售汽柴油車,並同時達成 100 萬輛電動車與油電混合車販售目標,盼可藉此響應巴黎協議,進一步展現緩解氣候變遷的決心。

拉斯穆森指出,「這是一個具有挑戰性的目標,但這正是我們需要嘗試的原因。」,丹麥將在短短 12 年內禁止銷售新汽柴油車,而在 17 年內新販售車輛也都得是電動車與其他零排放車款。顯示混合動力車也有可能在 2035 年逐步停止販售。

丹麥可說是全球能源轉型先鋒,將於2020年把碳排放量降到1990年的66%,更設定在 2030 年將再生能源發電佔比提升至 55%,2050 年達成發電、交通、供暖製冷全再生能源供應。為了達路上零排放,丹麥也在電動車充電站、重工業氫氣與天然氣設備撥出 7,000 萬丹麥克朗(約 3.3 億新台幣)補助。

不過該提案尚未獲得國會批准,詳細內容則會在下週公布,若要讓國會通過新法案,丹麥首先得面臨是否要調整電動車補貼政策等難題。

為解決空氣污染與提升電動車購買率,各國通常會以補貼或是減稅等措施提供購買誘因,像是丹麥原先透過免除最高達 180% 進口稅來刺激電動車買氣,只不過由於受到傳統車廠抗議,丹麥於 2016 年調整補貼政策,此舉導致該國電動車購買量雪崩式下滑。

以電動車銷售為例,2015 年有高達 4,762 輛電動車登記銷售,但政策公布後,該數據在短短幾年內下降到 1,428,最後在跌至 1,000 以下、僅售出 913 輛,丹麥 2017年僅賣出 500 輛電動車,對此拉斯穆森在 2018 年 5 月也表示,由於電動車銷售量急遽下滑,政府已考慮是否調整電動車補貼政策,也不排除提高獎勵金額。

在節能減碳與環保世界趨勢下,目前各國紛紛設立禁售汽柴油車時間表,其中由挪威率先吹響號角,於 2016 年設定 2025 年禁售汽柴油車,之後德國、愛爾蘭、印度與荷蘭等國也計劃在 2030 年達標,法國、英國、台灣則預計在 2040 年達成路上零排放,希望可藉由禁售燃油車規範達成改善空氣污染、減緩全球暖化等目的。

彭博能源財經(BNEF)也預估,2020~2030 年電動車價格將可與傳統汽車相當,2030 年全球電動車銷售量有望突破 3,000 萬輛。

(首圖來源: CC BY-SA 2.0。文/DaisyChuang)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

光陽 Ionex 車能網商業版正式登台,柯勝峯:下一步進軍印度市場

光陽工業(KYMCO)12 日正式在台發表 Ionex 車能網商業版,提供 4 大套裝方案,切入全球商用電動機車領域;董事長柯勝峯更宣布進軍印度這個全球最大二輪車市場,並且全面拓展中國市場。

隨著環保意識不斷提升,電動化成為現代交通工具最重要的轉變,有效落實城市永續經營的目標。其中,商用電動車扮演極為關鍵的角色,據統計全球約有 1.5 億輛機車,其中約 500 萬輛機車為商業用途,商用機車佔比雖低,但每日騎乘距離是一般機車的 7 倍,維修換車頻率則是一般機車的 2 倍,能源消耗佔比較整體機車多 2 成。因此採行電動車,能夠真正協助企業進行營運模式的發展與轉型。

Ionex 車能網商業版 4 套裝方案公開

Ionex 車能網商業版可針對任何企業或政府機構的特定需求,進行量身訂製並快速導入;主要向客戶提供「基礎設施」、「商用車隊」、「共享車輛」、「大眾運輸」4 個套裝方案,內容含括電動機車、抽取式電池、能源交換站、作業系統、管理軟體、手機 App 以及其他客製化服務等等,視需求做出調整,以展現 Ionex 車能網的系統彈性與發展能力:

  • 基礎設施套裝方案:提供欲建置能源基礎建設以對特定市場提供充電服務的企業或政府所設計。
  • 商用車隊套裝方案:為需要商用電動車隊的企業所設計,尤其適合具有配送服務的物流或零售等業者。
  • 共享車輛套裝方案:針對共享業者所設計,加速共享業者將電動車導入市場的時間。
  • 大眾運輸套裝方案:有效協助政府將共享電動車租賃服務導入大眾運輸網路,提升大眾運輸工具的使用率。

今日的發表會上更展示 2 款商用電動機車、以及 1 款電動輔助自行車。

進軍印度電動機車市場

目前在企業客戶方面,光陽已有大型零售的盒馬鮮生、車輛共享的騎電科技、能源服務的張飛充電;而在發展智慧城市,光陽與中國的江蘇常州市、福建寧德市、浙江衢州市、廣東深圳市合作。光陽更將在下週於印度首都新德里,正式宣布進軍印度市場,與當地的合作夥伴加速拓展 Ionex 車能網。

(合作媒體:。圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分佈式事務

分佈式事務

分佈式環境下的事務

要了解分佈式事務,首先要了解分佈式環境

分佈式

如一網站,訪問一個服務A(查詢自己用戶信息), 提供服務A的服務器分別有A1(上海)A2(廣州) A3(新加坡)

同一個服務分佈在三個區域的服務器上,這就是分佈式。你可以訪問 上海的服務器,廣州的或者新加坡的,但是

三個服務器之前通信是有延遲的,所以數據同步需要一定時間

分佈式中的問題

舉例一個分佈式場景

如果用戶Y個人信息 名字為 “南柯一夢” Y改為 “南柯夢”, 同一時間,Y用戶好友查看Y的名字,好友查詢的結果是”南柯一夢” 還是 “南柯夢” 這是分佈式系統常見的問題(數據修改發生在上海,訪問發生在新加坡)。

CAP原則

CAP原則又稱CAP定理,指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多只能同時實現兩點,不可能三者兼顧。

參考文章
An Illustrated Proof of the CAP Theorem
CAP 定理的含義

以為網絡有延遲和不可測故障,因此分佈式系統是保持服務穩定的常用手段,但是分佈式因為服務機器分佈在不同地點,因此也會有分佈式的特點問題。

分佈式中 一致性 可用性 容災性 是三個指標

Consistency

數據一致性,分佈式環境下,不同地點的服務器,數據庫數據同步一致。

Availability

服務可用性,分佈式環境下,調用服務,都可用

Partition tolerance

分區容錯性,容災能力

分佈式環境多台服務器運行,其中一部分機器故障了,整個系統仍然可以正常運行提供服務

CAP不能同時滿足

必須滿足P

首先分佈式環境,系統需要穩定運行,一台服務器意外斷電,不應該影響系統整體功能正常,另一台或多台服務器還能穩定提供服務,所以分區容錯是必須要滿足。

滿足C

數據一致性,所指的是同一個服務所在不同服務器的數據是同步的。如上改名字的場景 南柯一夢 改為 南柯夢 (在上海的數據庫被修改) 那麼系統要做到滿足數據一致性,必須馬上同步廣州和新加坡的數據庫,這樣才能滿足廣州或者新加坡的訪問者獲得的結果也一致是 “南柯夢” 而不是”南柯一夢”

滿足A

服務可用性,指任何時候訪問服務,都返回結果

A與C是衝突的,上海服務器南柯一夢改為南柯夢后,為了服務可用,此時間訪問新加坡和廣州的服務器,返回的結果應該是南柯一夢(任何時候服務都返回結果) 但是嚴格上講,數據是錯誤的,因為用戶已經改了名字,改為南柯夢,但是數據在上海的才是正確的。

滿足數據一致性必須犧牲服務可用性 或者相反

要達到數據一致性的要求,必須在上海服務器修改數據的同時,同步廣州和新加坡的數據庫,並且在數據同步完成之前,訪問廣州和新加坡的數據庫中這條數據需要等待,返回同步后的結果(一致性)。

失去了服務可用性(這裏服務是等待數據同步完成才返回結果,而不是立刻返回)

因此CAP 要麼 滿足AP (分區服務可用)要麼 CP (分區數據一致)

分佈式中事務

商品購買中的事務

以商品購買生成訂單為例子

網絡上用戶A 購買 一雙鞋子 價格50 付款後生成消費訂單

事務中包含子的服務

這裏簡單設為三個服務,他們是事務相關的

1.商品信息服務

提供商品信息等服務

鞋子 顏色 價格 庫存數量等信息 這裏設 價格price為 50 庫存數 num 9

2.商家賬號收款服務

提供金額收入信息等服務

用戶購買鞋子,需要付款50元到商家賬號

3.用戶消費訂單服務

提供購買消費憑證信息等服務

首先分析用戶購買鞋子,三個服務分別要做什麼

@1 鞋子庫存減1

@2 商家賬號金額增加50

@3 生成 用戶購買鞋子的訂單記錄, 包括數量金額等信息

事務特性

原子性

@1 @2 @3 要麼同時發生,要麼都不發生

一致性

鞋子庫存減少1,收入增加50

隔離性

鞋子庫存減1,後續用戶最多只能購買(9-1=8)雙鞋子

持久性

動作執行成功后,訂單生效,收入新增50生效,庫存減1生效

上述三個服務他們可以在不同的地點,不同機器上部署的,並很常見。

保證數據正確

開啟事務

確定要執行的服務,每個服務的數據庫事務開啟

執行業務

調用庫存減1,轉賬,生成訂單等子服務

提交

業務執行過程中沒有意外,各子服務的數據庫提交事務,生效數據修改

回退

回退,如果服務調用出現了差錯,或者某個子服務執行失敗,可以通過回滾所有數據庫達到數據正確。

補償

某些情況下,某個子服務執行失敗,但是不影響整體業務,也可以提交事務,後續補償機制將失敗的子服務重新執行。

補償機制

個人認為就商品購買而言,補償機制多數情況可以使用且實用。(對強一致要求沒那麼高的情況下)

@1 庫存減1

@2 收入增加50

@ 3生成訂單記錄

如果這次執行的動作, 只有@3失敗,@1 @2成功 說明金額交易,商品庫存業務都沒問題,只是訂單記錄失敗,這是可以提交事務的,訂單錯誤可以生成一條記錄(攜帶商品,金額等信息),發送到MQ消息隊列(或者其他設計)通過消息隊列通知訂單相關服務,補償重新執行生成訂單,達到最終一致性。

分佈式事務控制問題

不同服務在不同區運行

不管是從安全性,穩定性,還是服務粒度細化方便維護等多因素考慮,都是很有必要讓不同的服務分開在不同服務區運行。

單體數據庫的事務不被支持,購買商品到生成訂單所有操作加起來算一個事務,涉及的數據在不同一服務(不同的數據庫),並且同一個服務可能運行在多台服務器上。

數據庫開啟事務針對的是單台服務器,多個服務多個數據庫,並不支持數據庫的事務,需要額外設計處理數據一致性問題(或者最終一致性)

同一個服務運行在多個區

不同服務不在一個服務器,同樣的,分佈式為穩定性可用而生,因此,一個服務大多有在多個區的服務器上運行,開啟事務的時候,如何保證事務開啟提交等事務相關命令每次發送到同一個區的同一個服務器,也是一定要考慮的問題。

分佈式事務處理方式

如上所述分佈式服務代表多個數據庫,不支持數據庫的事務,

如何保證事務中涉及的數據庫數據修改都提交生效或者都回滾。

建立控制中心

控制中心在執行業務時,統一發送開始事務的命令給三個服務,返回狀態

狀態沒問題執行數據修改,

都沒問題就發送給三個服務,提交事務,否在回滾事務

消息機制事務

MQ消息隊列,達到控制事務正確目的,項目中kafka聽的比較多,可在高併發環境下穩定運行,可以通過消息機制發送事務處理結果到子服務,子服務收到消息,通過分析消息內容,做出對應的操作,達到事務一致性或者最終一致性等目的
思考圖:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

一時技癢,擼了個動態線程池,源碼放Github了

闡述背景

線程池在日常工作中用的還挺多,當需要異步,批量處理一些任務的時候我們會定義一個線程池來處理。

在使用線程池的過程中有一些問題,下面簡單介紹下之前遇到的一些問題。

場景一:實現一些批量處理數據的功能,剛開始線程池的核心線程數設的比較小,然後想調整下,只能改完后重啟應用。

場景二:有一個任務處理的應用,會接收 MQ 的消息進行任務的處理,線程池的隊列也允許緩存一定數量的任務。當任務處理的很慢的時候,想看看到底有多少沒有處理完不是很方便。當時為了快速方便,就直接啟動了一個線程去循環打印線程池隊列的大小。

正好之前在我公眾號有轉發過美團的一篇線程池應用的文章(https://mp.weixin.qq.com/s/tIWAocevZThfbrfWoJGa9w),覺得他們的思路非常好,就是沒有開放源碼,所以自己就抽時間在我的開源項目 Kitty 中增加了一個動態線程池的組件,支持了 Cat 監控,動態變更核心參數,任務堆積告警等。今天就給大家分享一下實現的方式。

項目源代碼地址:https://github.com/yinjihuan/kitty

使用方式

添加依賴

依賴線程池的組件,目前 Kitty 未發布,需要自己下載源碼 install 本地或者私有倉庫。

<dependency>
    <groupId>com.cxytiandi</groupId>
    <artifactId>kitty-spring-cloud-starter-dynamic-thread-pool</artifactId>
</dependency>

添加配置

然後在 Nacos 配置線程池的信息,我的這個整合了 Nacos。推薦一個應用創建一個單獨的線程池配置文件,比如我們這個叫 dataId 為 kitty-cloud-thread-pool.properties,group 為 BIZ_GROUP。

內容如下:

kitty.threadpools.nacosDataId=kitty-cloud-thread-pool.properties
kitty.threadpools.nacosGroup=BIZ_GROUP
kitty.threadpools.accessToken=ae6eb1e9e6964d686d2f2e8127d0ce5b31097ba23deee6e4f833bc0a77d5b71d
kitty.threadpools.secret=SEC6ec6e31d1aa1bdb2f7fd5eb5934504ce09b65f6bdc398d00ba73a9857372de00
kitty.threadpools.owner=尹吉歡
kitty.threadpools.executors[0].threadPoolName=TestThreadPoolExecutor
kitty.threadpools.executors[0].corePoolSize=4
kitty.threadpools.executors[0].maximumPoolSize=4
kitty.threadpools.executors[0].queueCapacity=5
kitty.threadpools.executors[0].queueCapacityThreshold=5
kitty.threadpools.executors[1].threadPoolName=TestThreadPoolExecutor2
kitty.threadpools.executors[1].corePoolSize=2
kitty.threadpools.executors[1].maximumPoolSize=4

nacosDataId,nacosGroup

監聽配置修改的時候需要知道監聽哪個 DataId,值就是當前配置的 DataId。

accessToken,secret

釘釘機器人的驗證信息,用於告警。

owner

這個應用的負責人,告警的消息中會显示。

threadPoolName

線程池的名稱,使用的時候需要關注。

剩下的配置就不一一介紹了,跟線程池內部的參數一致,還有一些可以查看源碼得知。

注入使用

@Autowired
private DynamicThreadPoolManager dynamicThreadPoolManager;
dynamicThreadPoolManager.getThreadPoolExecutor("TestThreadPoolExecutor").execute(() -> {
    log.info("線程池的使用");
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "getArticle");

通過 DynamicThreadPoolManager 的 getThreadPoolExecutor 方法獲取線程池對象,然後傳入 Runnable,Callable 等。第二個參數是這個任務的名稱,之所以要擴展一個參數是因為如果任務沒有標識,那麼無法區分任務。

這個線程池組件默認集成了 Cat 打點,設置了名稱可以在 Cat 上查看這個任務相關的監控數據。

擴展功能

任務執行情況監控

在 Cat 的 Transaction 報表中會以線程池的名稱為類型显示。

詳情中會以任務的名稱显示。

核心參數動態修改

核心參數目前只支持 corePoolSize,maximumPoolSize,queueCapacity(隊列類型為 LinkedBlockingDeque 才可以修改),rejectedExecutionType,keepAliveTime,unit 這些參數的修改。

一般 corePoolSize,maximumPoolSize,queueCapacity 是最常要動態改變的。

需要改動的話直接在 Nacos 中將對應的配置值修改即可,客戶端會監聽配置的修改,然後同步修改先線程池的參數。

隊列容量告警

queueCapacityThreshold 是隊列容量告警的閥值,如果隊列中的任務數量超過了 queueCapacityThreshold 就會告警。

拒絕次數告警

當隊列容量滿了后,新進來的任務會根據用戶設置的拒絕策略去選擇對應的處理方式。如果是採用 AbortPolicy 策略,也會進行告警。相當於消費者已經超負荷了。

線程池運行情況

底層對接了 Cat,所以將線程的運行數據上報給了 Cat。我們可以在 Cat 中查看這些信息。

如果你想在自己的平台去展示,我這邊暴露了/actuator/thread-pool 端點,你可以自行拉取數據。

{
	threadPools: [{
		threadPoolName: "TestThreadPoolExecutor",
		activeCount: 0,
		keepAliveTime: 0,
		largestPoolSize: 4,
		fair: false,
		queueCapacity: 5,
		queueCapacityThreshold: 2,
		rejectCount: 0,
		waitTaskCount: 0,
		taskCount: 5,
		unit: "MILLISECONDS",
		rejectedExecutionType: "AbortPolicy",
		corePoolSize: 4,
		queueType: "LinkedBlockingQueue",
		completedTaskCount: 5,
		maximumPoolSize: 4
	}, {
		threadPoolName: "TestThreadPoolExecutor2",
		activeCount: 0,
		keepAliveTime: 0,
		largestPoolSize: 0,
		fair: false,
		queueCapacity: 2147483647,
		queueCapacityThreshold: 2147483647,
		rejectCount: 0,
		waitTaskCount: 0,
		taskCount: 0,
		unit: "MILLISECONDS",
		rejectedExecutionType: "AbortPolicy",
		corePoolSize: 2,
		queueType: "LinkedBlockingQueue",
		completedTaskCount: 0,
		maximumPoolSize: 4
	}]
}

自定義拒絕策略

平時我們使用代碼創建線程池可以自定義拒絕策略,在構造線程池對象的時候傳入即可。這裏由於創建線程池都被封裝好了,我們只能在 Nacos 配置拒絕策略的名稱來使用對應的策略。默認是可以配置 JDK 自帶的 CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy 這四種。

如果你想自定義的話也是支持的,定義方式跟以前一樣,如下:

@Slf4j
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.info("進來了。。。。。。。。。");
    }
}

要讓這個策略生效的話使用的是 SPI 的方式,需要在 resources 下面創建一個 META-INF 的文件夾,然後創建一個 services 的文件夾,再創建一個 java.util.concurrent.RejectedExecutionHandler 的文件,內容為你定義的類全路徑。

自定義告警方式

默認是內部集成了釘釘機器人的告警方式,如果你不想用也可以將其關閉。或者將告警信息對接到你的監控平台去。

如果沒有告警平台也可以在項目中實現新的告警方式,比如短信等。

只需要實現 ThreadPoolAlarmNotify 這個類即可。

/**
 * 自定義短信告警通知
 *
 * @作者 尹吉歡
 * @個人微信 jihuan900
 * @微信公眾號 猿天地
 * @GitHub https://github.com/yinjihuan
 * @作者介紹 http://cxytiandi.com/about
 * @時間 2020-05-27 22:26
 */
@Slf4j
@Component
public class ThreadPoolSmsAlarmNotify implements ThreadPoolAlarmNotify {
    @Override
    public void alarmNotify(AlarmMessage alarmMessage) {
        log.info(alarmMessage.toString());
    }
}

代碼實現

具體的就不講的很細了,源碼在https://github.com/yinjihuan/kitty/tree/master/kitty-dynamic-thread-pool,大家自己去看,並不複雜。

創建線程池

根據配置創建線程池,ThreadPoolExecutor 是自定義的,因為需要做 Cat 埋點。

/**
 * 創建線程池
 * @param threadPoolProperties
 */
private void createThreadPoolExecutor(DynamicThreadPoolProperties threadPoolProperties) {
    threadPoolProperties.getExecutors().forEach(executor -> {
        KittyThreadPoolExecutor threadPoolExecutor = new KittyThreadPoolExecutor(
                executor.getCorePoolSize(),
                executor.getMaximumPoolSize(),
                executor.getKeepAliveTime(),
                executor.getUnit(),
                getBlockingQueue(executor.getQueueType(), executor.getQueueCapacity(), executor.isFair()),
                new KittyThreadFactory(executor.getThreadPoolName()),
                getRejectedExecutionHandler(executor.getRejectedExecutionType(), executor.getThreadPoolName()), executor.getThreadPoolName());
        threadPoolExecutorMap.put(executor.getThreadPoolName(), threadPoolExecutor);
    });
}

刷新線程池

首先需要監聽 Nacos 的修改。

/**
 * 監聽配置修改,spring-cloud-alibaba 2.1.0版本不支持@NacosConfigListener的監聽
 */
public void initConfigUpdateListener(DynamicThreadPoolProperties dynamicThreadPoolProperties) {
    ConfigService configService = nacosConfigProperties.configServiceInstance();
    try {
        configService.addListener(dynamicThreadPoolProperties.getNacosDataId(), dynamicThreadPoolProperties.getNacosGroup(), new AbstractListener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                new Thread(() -> refreshThreadPoolExecutor()).start();
                log.info("線程池配置有變化,刷新完成");
            }
        });
    } catch (NacosException e) {
        log.error("Nacos配置監聽異常", e);
    }
}

然後再刷新線程池的參數信息,由於監聽事件觸發的時候,這個時候配置其實還沒刷新,所以我就等待了 1 秒鐘,讓配置完成刷新然後直接從配置類取值。

雖然有點挫還是可以用,其實更好的方式是解析 receiveConfigInfo 那個 configInfo,configInfo 就是改變之後的整個配置內容。因為不太好解析成屬性文件,就沒做,後面再改吧。

/**
 * 刷新線程池
 */
private void refreshThreadPoolExecutor() {
    try {
        // 等待配置刷新完成
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
    dynamicThreadPoolProperties.getExecutors().forEach(executor -> {
        ThreadPoolExecutor threadPoolExecutor = threadPoolExecutorMap.get(executor.getThreadPoolName());
        threadPoolExecutor.setCorePoolSize(executor.getCorePoolSize());
        threadPoolExecutor.setMaximumPoolSize(executor.getMaximumPoolSize());
        threadPoolExecutor.setKeepAliveTime(executor.getKeepAliveTime(), executor.getUnit());
        threadPoolExecutor.setRejectedExecutionHandler(getRejectedExecutionHandler(executor.getRejectedExecutionType(), executor.getThreadPoolName()));
        BlockingQueue<Runnable> queue = threadPoolExecutor.getQueue();
        if (queue instanceof ResizableCapacityLinkedBlockIngQueue) {
            ((ResizableCapacityLinkedBlockIngQueue<Runnable>) queue).setCapacity(executor.getQueueCapacity());
        }
    });
}

其他的刷新都是線程池自帶的,需要注意的是線程池隊列大小的刷新,目前只支持 LinkedBlockingQueue 隊列,由於 LinkedBlockingQueue 的大小是不允許修改的,所以按照美團那篇文章提供的思路,自定義了一個可以修改的隊列,其實就是把 LinkedBlockingQueue 的代碼複製了一份,改一下就可以。

往 Cat 上報運行信息

往 Cat 的 Heartbeat 報表上傳數據的代碼如下,主要還是 Cat 本身提供了擴展的能力。只需要定時去調用下面的方式上報數據即可。

public void registerStatusExtension(ThreadPoolProperties prop, KittyThreadPoolExecutor executor) {
    StatusExtensionRegister.getInstance().register(new StatusExtension() {
        @Override
        public String getId() {
            return "thread.pool.info." + prop.getThreadPoolName();
        }
        @Override
        public String getDescription() {
            return "線程池監控";
        }
        @Override
        public Map<String, String> getProperties() {
            AtomicLong rejectCount = getRejectCount(prop.getThreadPoolName());
            Map<String, String> pool = new HashMap<>();
            pool.put("activeCount", String.valueOf(executor.getActiveCount()));
            pool.put("completedTaskCount", String.valueOf(executor.getCompletedTaskCount()));
            pool.put("largestPoolSize", String.valueOf(executor.getLargestPoolSize()));
            pool.put("taskCount", String.valueOf(executor.getTaskCount()));
            pool.put("rejectCount", String.valueOf(rejectCount == null ? 0 : rejectCount.get()));
            pool.put("waitTaskCount", String.valueOf(executor.getQueue().size()));
            return pool;
        }
    });
}

定義線程池端點

通過自定義端點來暴露線程池的配置和運行的情況,可以讓外部的監控系統拉取數據做對應的處理。

@Endpoint(id = "thread-pool")
public class ThreadPoolEndpoint {
    @Autowired
    private DynamicThreadPoolManager dynamicThreadPoolManager;
    @Autowired
    private DynamicThreadPoolProperties dynamicThreadPoolProperties;
    @ReadOperation
    public Map<String, Object> threadPools() {
        Map<String, Object> data = new HashMap<>();
        List<Map> threadPools = new ArrayList<>();
        dynamicThreadPoolProperties.getExecutors().forEach(prop -> {
            KittyThreadPoolExecutor executor = dynamicThreadPoolManager.getThreadPoolExecutor(prop.getThreadPoolName());
            AtomicLong rejectCount = dynamicThreadPoolManager.getRejectCount(prop.getThreadPoolName());
            Map<String, Object> pool = new HashMap<>();
            Map config = JSONObject.parseObject(JSONObject.toJSONString(prop), Map.class);
            pool.putAll(config);
            pool.put("activeCount", executor.getActiveCount());
            pool.put("completedTaskCount", executor.getCompletedTaskCount());
            pool.put("largestPoolSize", executor.getLargestPoolSize());
            pool.put("taskCount", executor.getTaskCount());
            pool.put("rejectCount", rejectCount == null ? 0 : rejectCount.get());
            pool.put("waitTaskCount", executor.getQueue().size());
            threadPools.add(pool);
        });
        data.put("threadPools", threadPools);
        return data;
    }
}

Cat 監控線程池中線程的執行時間

本來是將監控放在 KittyThreadPoolExecutor 的 execute,submit 方法里的。後面測試下來發現有問題,數據在 Cat 上確實有了,但是執行時間都是 1 毫秒,也就是沒生效。

不說想必大家也知道,因為線程是後面單獨去執行的,所以再添加任務的地方埋點沒任務意義。

後面還是想到了一個辦法來實現埋點的功能,就是利用線程池提供的 beforeExecute 和 afterExecute 兩個方法,在線程執行之前和執行之後都會觸發這兩個方法。

@Override
protected void beforeExecute(Thread t, Runnable r) {
    String threadName = Thread.currentThread().getName();
    Transaction transaction = Cat.newTransaction(threadPoolName, runnableNameMap.get(r.getClass().getSimpleName()));
    transactionMap.put(threadName, transaction);
    super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
    super.afterExecute(r, t);
    String threadName = Thread.currentThread().getName();
    Transaction transaction = transactionMap.get(threadName);
    transaction.setStatus(Message.SUCCESS);
    if (t != null) {
        Cat.logError(t);
        transaction.setStatus(t);
    }
    transaction.complete();
    transactionMap.remove(threadName);
}

後面的代碼大家自己去看就行了,本文到這裏就結束了。如果感覺本文還不錯的記得轉發下哦!

多謝多謝。

最後感謝美團技術團隊的那篇文章,雖然沒有分享源碼,但是思路什麼的和應用場景都講的很明白。

感興趣的 Star 下唄:https://github.com/yinjihuan/kitty

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

JSR133提案-修復Java內存模型

目錄

  • 1. 什麼是內存模型?
  • 2. JSR 133是關於什麼的?
  • 3. 再談指令重排序
  • 4.同步都做了什麼?
  • 5. final字段在舊的內存模型中為什麼可以改變?
  • 6.“初始化安全”與final字段?
  • 7. 增強volatile語義
  • 8. 修復“double-checked locking”的問題
  • 9. 為什麼要關心這些問題?
  • 延伸閱讀

1. 什麼是內存模型?

在多處理器系統中,為了提高訪問數據的速度,通常會增加一層或多層高速緩存(越靠近處理器的緩存速度越快)。
但是緩存同時也帶來了許多新的挑戰。比如,當兩個處理器同時讀取同一個內存位置時,看到的結果可能會不一樣?
在處理器維度上,內存模型定義了一些規則來保證當前處理器可以立即看到其他處理器的寫入,以及當前處理器的寫入對其他處理器立即可見。這些規則被稱為緩存一致性協議
有些多處理器架構實現了強一致性,所有的處理器在同一時刻看到的同一內存位置的值是一樣的。
而其他處理器實現的則是較弱的一致性,需要使用被稱為內存屏障的特殊機器指令使來實現最終一致性(通過刷新緩存或使緩存失效)。
這些內存屏障通常在釋放鎖和獲取鎖時被執行;對於高級語言(如Java)的程序員來說,它們是不可見的。

在強一致性的處理器上,由於減少了對內存屏障的依賴,編寫併發程序會更容易一些。
但是,相反的,近年來處理器設計的趨勢是使用較弱的內存模型,因為放寬對緩存一致性的要求可以使得多處理器系統有更好的伸縮性和更大的內存。

此外,編譯器、緩存或運行時還被允許通過指令重排序改變內存的操作順序(相對於程序所表現的順序)。
例如,編譯器可能會往後移動一個寫入操作,只要移動操作不改變程序的原本語義(as-if-serial語義),就可以自由進行更改。
再比如,緩存可能會推遲把數據刷回到主內存中,直到它認為時機合適了。
這種靈活的設計,目的都是為了獲得得最佳的性能,
但是在多線程環境下,指令重排會使得跨線程可見性的問題變的更複雜。

為了方便理解,我們來看個代碼示例:

Class Reordering {
  int x = 0, y = 0;
  //thread A
   public void writer() {
        x = 1;
        y = 2;
    }

    //thread B
    public void reader() {
        int r1 = y;
        int r2 = x;
     }
}

假設這段代碼被兩個線程併發執行,線程A執行writer(),線程B執行reader()。
如果線程B在reader()中看到了y=2,那麼直覺上我們會認為它看到的x肯定是1,因為在writer()中x=1y=2之前 。
然而,發生重排序時y=2會早於x=1執行,此時,實際的執行順序會是這樣的:

y=2;
int r1=y;
int r2=x;
x=1;

結果就是,r1的值是2,r2的值是0。
從線程A的角度看,x=1與y=2哪個先執行結果是一樣的(或者說沒有違反as-if-serial語義),但是在多線程環境下,這種重排序會產生混亂的結果。

我們可以看到,高速緩存指令重排序提高了效率的同時也引出了新的問題,這顯然使得編寫併發程序變得更加困難。
Java內存模型就是為了解決這類問題,它對多線程之間如何通過內存進行交互做了明確的說明。
更具體點,Java內存模型描述了程序中的變量與實際計算機的存儲設備(包括內存、緩存、寄存器)之間交互的底層細節。
例如,Java提供了volatile、final和 synchronized等工具,用於幫助程序員向編譯器表明對併發程序的要求。
更重要的是,Java內存模型保證這些同步工具可以正確的運行在任何處理器架構上,使Java併發應用做到“Write Once, Run Anywhere”。

相比之下,大多數其他語言(例如C/C++)都沒有提供显示的內存模型。
C程序繼承了處理器的內存模型,這意味着,C語言的併發程序在一個處理器架構中可以正確運行,在另外一個架構中則不一定。

2. JSR 133是關於什麼的?

Java提供的跨平台內存模型是一個雄心勃勃的計劃,在當時是具有開創性的。
但不幸的是,定義一個即直觀又一致的內存模型比預期的要困難得多。
自1997年以來,在《Java語言規範》的第17章關於Java內存模型的定義中發現了一些嚴重的缺陷。
這些缺陷使一些同步工具產生混亂的結果,例如final字段可以被更改。
JSR 133為Java語言定義了一個新的內存模型,修復了舊版內存模型的缺陷(修改了final和volatile的語義)
JSR的主要目標包括不限於這些:

  1. 正確同步的語義應該更直觀更簡單。
  2. 應該定義不完整或不正確同步的語義,以最小化潛在的安全隱患
  3. 程序員應該有足夠的自信推斷出多線程程序如何與內存交互的。
  4. 提供一個新的初始化安全性保證(initialization safety)。
    如果一個對象被正確初始化了(初始化期間,對象的引用沒有逃逸,比如構造函數里把this賦值給變量),那麼所有可以看到該對象引用的線程,都可以看到在構造函數中被賦值的final變量。這不需要使用synchronized或volatile。

3. 再談指令重排序

在許多情況下,出於優化執行效率的目的,數據(實例變量、靜態字段、數組元素等)可以在寄存器、緩存和內存之間以不同於程序中聲明的順序被移動。
例如,線程先寫入字段a,再寫入字段b,並且b的值不依賴a,那麼編譯器就可以自由的對這些操作重新排序,在寫入a之前把b的寫入刷回到內存。
除了編譯器,重排序還可能發生在JIT、緩存、處理器上。
無論發生在哪裡,重排序都必須遵循as-if-serial語義,這意味着在單線程程序中,程序不會覺察到重排序的存在,或者說給單線程程序一種沒有發生過重排序的錯覺。
但是,重排序在沒有同步的多線程程序中會產生影響。在這種程序中,一個線程能夠觀察到其他線程的運行情況,並且可能檢測到變量訪問順序與代碼中指定的順序不一致。
大多數情況下,一個線程不會在乎另一個線程在做什麼,但是,如果有,就是同步的用武之地。

4.同步都做了什麼?

同步有很多面,最為程序員熟知的是它的互斥性,同一時刻只能有一個線程持有monitor。
但是,同步不僅僅是互斥性。同步還能保證一個線程在同步塊中的寫內存操作對其他持有相同monitor的線程立即可見。
當線程退出同步塊時(釋放monitor),會把緩存中的數據刷回到主內存,使主內存中保持最新的數據。
當線程進入同步塊時(獲取monitor),會使本地處理器緩存失效,使得變量必須從主內存中重新加載。
我們可以看到,之前的所有寫操作對後來的線程都是可見的。

5. final字段在舊的內存模型中為什麼可以改變?

證明final字段可以改變的最佳示例是String類的實現(JDK 1.4版本)。
String對象包含三個字段:一個字符串數組的引用value、一個記錄數組中開始位置的offset、字符串長度length。
通過這種方式,可以實現多個String/StringBuffer對象共享一個相同的字符串數組,從而避免為每個對象分配額外的空間。
例如,String.substring()通過與原String對象共享一個數組來產生一個新的對象,唯一的不同是length和offset字段。

String s1 = "/usr/tmp";
String s2 = s1.substring(4); 

s2和s1共享一個字符串數組”/usr/tmp”,不同的是s2的offset=4,length=4,s1的offset=0,length=8。
在String的構造函數運行之前,根類Object的構造函數會先初始化所有字段為默認值,包括final的length和offset字段。
當String的構造函數運行時,再把length和offset賦值為期望的值。
但是這一過程,在舊的內存模型中,如果沒有使用同步,另一個線程可能會看到offset的默認值0,然後在看到正確的值4.
結果導致一個迷幻的現象,開始看到字符串s2的內容是’/usr’,然後再看到’/tmp’。
這不符合我們對final語義的認識,但是在舊內存模型中確實存在這樣的問題。
(JDK7開始,改變了substring的實現方式,每次都會創建一個新的對象)

6.“初始化安全”與final字段?

新的內存模型提供一個新初始化安全( initialization safety)保障。
意味着,只要一個對象被正確的構造,那麼所有的線程都會看到這些在構造函數中被賦值的final字段。
“正確”的構造是指在構造函數執行期間,對象的引用沒有發生逃逸。或者說,在構造函數中沒有把該對象的引用賦值給任何變量。

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

示例中,初始化安全保證執行reader()方法的線程看到的f.x=3,因為它是final字段,但是不保證能看到y=4,因為它不是final的。
但是如果構造函數像這樣:

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  global.obj = this;  //  allowing this to escape
}

初始化安全不能保證讀取global.obj的線程看到的x的值是3,因為對象引用this發生了逃逸。

不僅如此,任何通過final字段(構造函數中被賦值的)可以觸達的變量都可以保證對其他線程可見。
這意味着如果一個final字段包含一個引用,例如ArrayList,除了該字段的引用對其他線程可見,ArrayList中的元素對其他線程也是可見的。

初始化安全增強了final的語義,使其更符合我們對final的直觀感受,任何情況下都不會改變。

7. 增強volatile語義

volatile變量是用於線程之間傳遞狀態的特殊變量,這要求任何線程看到的都是volatile變量的最新值。
為實現可見性,禁止在寄存器中分配它們,還必須確保修改volatile后,要把最新值從緩存刷到內存中。
類似的,在讀取volatile變量之前,必須使高速緩存失效,這樣其他線程會直接讀取主內存中的數據。
在舊的內存模型中,多個volatile變量之間不能互相重排序,但是它們被允許可以與非volatile變量一起重排序,這消弱了volatile作為線程間交流信號的作用。
我們來看個示例:

Map configs;
volatile boolean initialized = false;
. . .
 
// In thread A
configs  =  readConfigFile(fileName);
processConfigOptions( configs);
initialized = true;
. . .
 
// In thread B
while (initialized) {
    // use configs
}

示例中,線程A負責配置數據初始化工作,初始化完成后線程B開始執行。
實際上,volatile變量initialized扮演者守衛者的角色,它表示前置工作已經完成,依賴這些數據的其他線程可以執行了。
但是,當volatile變量與非volatile變量被編譯器放到一起重新排序時,“守衛者”就形同虛設了。
重排序發生時,可能會使readConfigFile()中某個動作在initialized = true之後執行,
那麼,線程B在看到initialized的值為true后,在使用configs對象時,會讀取到沒有被正確初始化的數據。
這是volatile很典型的應用場景,但是在舊的內存模型中卻不能正確的工作。

JSR 133專家組決定在新的內存模型中,不再允許volatile變量與其他任務內存操作一起重排序
這意味着,volatile變量之前的內存操作不會在其後執行,volatile變量之後的內存操作不會在其前執行。
volatile變量相當於一個屏障,重排序不能越過對volatile的內存操作。(實際上,jvm確實使用了內存屏障指令)
增強volatile語義的副作用也很明顯,禁止重排序會有一定的性能損失。

8. 修復“double-checked locking”的問題

double-checked locking是單例模式的其中一種實現,它支持懶加載且是線程安全的。
大概長這個樣子:

private static Something instance = null;

public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();//
    }
  }
  return instance;
}

它通過兩次檢查巧妙的避開了在公共代碼路徑上使用同步,從而避免了同步所帶來的性能開銷。
它唯一的問題就是——不起作用。為什麼呢?
instance的賦值操作會與SomeThing()構造函數中的變量初始化一起被編譯器或緩存重排序,這可能會導致把未完全初始化的對象引用賦值給instance。
現在很多人知道把instance聲明為volatile可以修復這個問題,但是在舊的內存模型(JDK 1.5之前)中並不可行,原因前面有提到,volatile可以與非volatile字段一起重排序。

儘管,新的內存模型修復了double-checked locking的問題,但仍不鼓勵這種實現方式,因為volatile並不是免費的。
相比之下,Initialization On Demand Holder Class更值得被推薦,
它不僅實現了懶加載和線程安全,還提供了更好的性能和更清晰的代碼邏輯。大概長這個樣子:

public class Something {
    private Something() {}
    //static innner class
    private static class LazyHolder {
        static final Something INSTANCE = new Something(); //static  field
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

這種實現完全沒有使用同步工具,而是利用了Java語言規範的兩個基本原則,
其一,JVM保證靜態變量的初始化對所有使用該類的線程立即可見;
其二,內部類首次被使用時才會觸發類的初始化,這實現了懶加載。

9. 為什麼要關心這些問題?

併發問題一般不會在測試環境出現,生成環境的併發問題又不容易復現,這兩個特點使得併發問題通常比較棘手。
所以你最好提前花點時間學習併發知識,以確保寫出正確的併發程序。我知道這很困難,但是應該比排查生產環境的併發問題容易的多。

延伸閱讀

1.JSR 133 (Java Memory Model) FAQ,2004
2.volatile關鍵字
3.Double-checked問題
4.內存屏障和volatile語義
5.修復Java內存模型
6.String substring 在jdk7中會創建新的數組
7.Memory Ordering
8.有MESI協議為什麼還需要volatile?
9.Initialization On Demand Holder Class
10.The JSR-133 Cookbook for Compiler Writers

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

TCP 重置攻擊的工作原理

原文鏈接:https://fuckcloudnative.io/posts/deploy-k3s-cross-public-cloud/

TCP 重置攻擊 是使用一個單一的數據包來執行的,只有幾個字節大小。攻擊者製作併發送一個偽造的 TCP 重置包來干擾用戶和網站的連接,欺騙通信雙方終止 TCP 連接。我們偉大的 xx 長城便運用了這個技術來進行 TCP 關鍵字阻斷。

理解 TCP 重置攻擊並不需要具備深厚的網絡知識功底,只需要一台筆記本就可以對自己進行模擬攻擊。本文將會帶你了解 TCP 重置攻擊的原理,同時會幫助你理解很多關於 TCP 協議的特性。本文主要內容:

  • 回顧 TCP 協議的基礎知識
  • 了解 TCP 重置攻擊的原理
  • 使用一個簡單的 Python 腳本來模擬攻擊

下面開始分析 TCP 重置攻擊原理。

1. 偉大的 xx 長城是如何利用 TCP 重置攻擊的?

這一段略過,原因你懂得,感興趣的請直接看原文。

2. TCP 重置攻擊的工作原理

在 TCP 重置攻擊中,攻擊者通過向通信的一方或雙方發送偽造的消息,告訴它們立即斷開連接,從而使通信雙方連接中斷。正常情況下,如果客戶端收發現到達的報文段對於相關連接而言是不正確的,TCP 就會發送一個重置報文段,從而導致 TCP 連接的快速拆卸。

TCP 重置攻擊利用這一機制,通過向通信方發送偽造的重置報文段,欺騙通信雙方提前關閉 TCP 連接。如果偽造的重置報文段完全逼真,接收者就會認為它有效,並關閉 TCP 連接,防止連接被用來進一步交換信息。服務端可以創建一個新的 TCP 連接來恢復通信,但仍然可能會被攻擊者重置連接。萬幸的是,攻擊者需要一定的時間來組裝和發送偽造的報文,所以一般情況下這種攻擊只對長連接有殺傷力,對於短連接而言,你還沒攻擊呢,人家已經完成了信息交換。

從某種意義上來說,偽造 TCP 報文段是很容易的,因為 TCP/IP 都沒有任何內置的方法來驗證服務端的身份。有些特殊的 IP 擴展協議(例如 IPSec)確實可以驗證身份,但並沒有被廣泛使用。客戶端只能接收報文段,並在可能的情況下使用更高級別的協議(如 TLS)來驗證服務端的身份。但這個方法對 TCP 重置包並不適用,因為 TCP 重置包是 TCP 協議本身的一部分,無法使用更高級別的協議進行驗證。

儘管偽造 TCP 報文段很容易,但偽造正確的 TCP 重置報文段並完成攻擊卻並不容易。為了理解這項工作的難度,我們需要先了解一下 TCP 協議的工作原理。

3. TCP 協議工作原理

TCP 協議的目標是向客戶端發送一份完整的數據副本。例如,如果我的服務器通過 TCP 連接向你的計算機發送我的網站的 HTML,你的計算機的 TCP 協議棧應該能夠以我發送的形式和順序輸出 HTML

然而現實生活中我的 HTML 內容並不是按順序發送的,它被分解成許多小塊(稱為 TCP 分組),每個小塊在網絡上被單獨發送,並被重新組合成原來發送的順序。這種重新組合后的輸出被稱為 TCP 字節流

將分組重建成字節流並不簡單,因為網絡是不可靠的。TCP分組可能會被丟棄,可能不按發送的順序到達客戶端,也可能會被重複發送、報文損壞等等。因此,TCP 協議的職責是在不可靠的網絡上提供可靠的通信。TCP 通過要求連接雙方保持密切聯繫,持續報告它們接收到了哪些數據來實現可靠通信,這樣服務端就能夠推斷出客戶端尚未接收到的數據,並重新發送丟失的數據。

為了進一步理解這個過程,我們需要了解服務端和客戶端是如何使用序列號(sequence numbers)來標記和跟蹤數據的。

TCP 序列號

TCP 協議的通信雙方, 都必須維護一個序列號(sequence numbers),對於客戶端來說,它會使用服務端的序列號來將接收到的數據按照發送的順序排列。

當通信雙方建立 TCP 連接時,客戶端與服務端都會向對方發送一個隨機的初始序列號,這個序列號標識了其發送數據流的第一個字節。TCP 報文段包含了 TCP 頭部,它是附加在報文段開頭的元數據,序列號就包含在 TCP 頭部中。由於 TCP 連接是雙向的,雙方都可以發送數據,所以 TCP 連接的雙方既是發送方也是接收方,每一方都必須分配和管理自己的序列號。

確認應答

當接收方收到一個 TCP 報文段時,它會向發送方返回一個 ACK 應答報文(同時將 TCP 頭部的 ACK 標誌位置 1),這個 ACK 號就表示接收方期望從發送方收到的下一個字節的序列號。發送方利用這個信息來推斷接收方已經成功接收到了序列號為 ACK 之前的所有字節。

TCP 頭部格式如下圖所示:

一個確認應答報文的 TCP 頭部必須包含兩個部分:

  • ACK 標誌位置位 1
  • 包含確認應答號(ACK number)

TCP 總共有 6 個標誌位,下文就會講到其中的 RST 標誌位。

TCP 頭部包含了多個選項,其中有一個選擇確認選項(SACK),如果使用該選項,那麼當接收方收到了某個範圍內的字節而不是連續的字節時,就會發送 SACK 告知對方。例如,只收到了字節 1000~30004000~5000,但沒有收到 3001~3999。為了簡單起見,下文討論 TCP 重置攻擊時將忽略選擇確認選項。

如果發送方發送了報文後在一段時間內沒有收到 ACK,就認為報文丟失了,並重新發送報文,用相同的序列號標記。這就意味着,如果接收方收到了重複的報文,可以使用序列號來判斷是否見過這個報文,如果見過則直接丟棄。網絡環境是錯綜複雜的,往往並不是如我們期望的一樣,先發送的數據包,就先到達目標主機,反而它很騷,可能會由於網絡擁堵等亂七八糟的原因,會使得舊的數據包,先到達目標主機。一般分兩種情況:

  1. 發送的數據包丟失了
  2. 發送的數據包被成功接收,但返回的 ACK 丟失了

這兩種情況對發送方來說其實是一樣的,發送方並不能區分是哪種情況,所以只能重新發送數據包。

只要不頻繁重複發送數據,額外的開銷基本可以忽略。

為偽造的重置包選擇序列號

構建偽造的重置包時需要選擇一個序列號。接收方可以接收序列號不按順序排列的報文段,但這種容忍是有限度的,如果報文段的序列號與它期望的相差甚遠,就會被直接丟棄。

因此,一個成功的 TCP 重置攻擊需要構建一個可信的序列號。但什麼才是可信的序列號呢?對於大多數報文段(除了重置包,即 RST 包)來說,序列號是由接收方的接收窗口大小決定的。

TCP 滑動窗口大小

想象一下,將一台上世紀 90 年代初的古老計算機,連接到現代千兆光纖網絡。閃電般快速的網絡可以以令人瞠目結舌的速度向這台古老的計算機傳送數據,速度遠遠超過該計算機的處理能力。但並沒有什麼卵用,因為只有接收方接收並處理了報文,才能認為這個報文已經被收到了。

TCP 協議棧有一個緩衝區,新到達的數據被放到緩衝區中等待處理。但緩衝區的大小是有限的,如果接收方的處理速度跟不上發送方的發送速度,緩衝區就會被填滿。一旦緩衝區被填滿,多餘的數據就會被直接丟棄,也不會返回 ACK。因此一旦接收方的緩衝區有了空位,發送方必須重新發送數據。也就是說,如果接收方的處理速度跟不上,發送方的發送速度再快也沒用。

緩衝區到底有多大?發送方如何才能知道什麼時候可以一次發送更多的數據,什麼時候該一次發送很少的數據?這就要靠 TCP 滑動窗口了。接收方的滑動窗口大小是指發送方無需等待確認應答,可以持續發送數據的最大值。 假設接收方的通告窗口大小為 100,000 字節,那麼發送方可以無需等待確認應答,持續發送 100,000 個字節。再假設當發送方發送第 100,000 個字節時,接收方已經發送了前 10,000 個字節的 ACK,這就意味着窗口中還有 90,000 個字節未被確認,發送方還可以再持續發送 10,000 個字節。如果發送了 10,000 個字節的過程中沒有收到任何的 ACK,那麼接收方的滑動窗口將被填滿,發送方將停止發送新數據(可以繼續發送之前丟失的數據),直到收到相關的 ACK 才可以繼續發送。

TCP 連接雙方會在建立連接的初始握手階段通告對方自己窗口的大小,後續還可以動態調整。TCP 緩衝區大的服務器可能會聲明一個大窗口,以便最大限度提高吞吐量。TCP 緩衝區小的服務器可能會被迫聲明一個小窗口,這樣做會犧牲一定的吞吐量,但為了防止接收方的 TCP 緩衝區溢出,還是很有必要的。

換個角度來看,TCP 滑動窗口大小是對網絡中可能存在的未確認數據量的硬性限制。我們可以用它來計算髮送方在某一特定時間內可能發送的最大序列號(max_seq_no):

max_seq_no = max_acked_seq_no + window_size

其中 max_acked_seq_no 是接收方發送的最大 ACK 號,它表示發送方知道接收方已經成功接收的最大序列號。window_size 是窗口大小,它表示允許發送方最多發送的未被確認的字節。所以發送方可以發送的最大序列號是:max_acked_seq_no + window_size

TCP 規範規定,接收方應該忽略任何序列號在接收窗口之外的數據。例如,如果接收方確認了所有序列號在 15,000 以下的字節,且接收窗口大小為 30,000,那麼接下來接收方只能接收序列號範圍在 15,000 ~ 45,000 之間的數據。如果一個報文段的部分數據在窗口內,另一部分數據在窗口外,那麼窗口內的數據將被接收確認,窗口外的數據將被丟棄。注意:這裏忽略了選擇確認選項,再強調一遍!

對於大多數 TCP 報文段來說,滑動窗口的規則告訴了發送方自己可以接收的序列號範圍。但對於重置報文來說,序列號的限制更加嚴格,這是為了抵禦一種攻擊叫做盲目 TCP 重置攻擊(blind TCP reset attack),下文將會解釋。

TCP 重置報文段的序列號

對於 TCP 重置報文段來說,接收方對序列號的要求更加嚴格,只有當其序列號正好等於下一個預期的序列號時才能接收。繼續搬出上面的例子,接收方發送了一個確認應答,ACK 號為 15,000。如果接下來收到了一個重置報文,那麼其序列號必須是 15,000 才能被接收。

如果重置報文的序列號超出了接收窗口範圍,接收方就會直接忽略該報文;如果其序列號在接收窗口範圍內,那麼接收方就會返回一個 challenge ACK,告訴發送方重置報文段的序列號是錯誤的,並告之正確的序列號,發送方可以利用 challenge ACK 中的信息來重新構建和發送重置報文。

其實在 2010 年之前,TCP 重置報文段和其他報文段的序列號限制規則一樣,但無法抵禦盲目 TCP 重置攻擊,後來才採取這些措施施加額外的限制。

盲目 TCP 重置攻擊

如果攻擊者能夠截獲通信雙方正在交換的信息,攻擊者就能讀取其數據包上的序列號和確認應答號,並利用這些信息得出偽裝的 TCP 重置報文段的序列號。相反,如果無法截獲通信雙方的信息,就無法確定重置報文段的序列號,但仍然可以批量發出盡可能多不同序列號的重置報文,以期望猜對其中一個序列號。這就是所謂的盲目 TCP 重置攻擊(blind TCP reset attack)。

在 2010 年之前 TCP 的原始版本中,攻擊者只需要猜對接收窗口內的隨便哪一個序列號即可,一般只需發送幾萬個報文段就能成功。採取額外限制的措施后,攻擊者需要發送數以百萬計的報文段才有可能猜對序列號,這幾乎是很難成功的。更多細節請參考 RFC-5963。

4. 模擬攻擊

以下實驗是在 OSX 系統中完成的,其他系統請自行測試。

現在來總結一下偽造一個 TCP 重置報文要做哪些事情:

  • 嗅探通信雙方的交換信息。
  • 截獲一個 ACK 標誌位置位 1 的報文段,並讀取其 ACK 號。
  • 偽造一個 TCP 重置報文段(RST 標誌位置為 1),其序列號等於上面截獲的報文的 ACK 號。這隻是理想情況下的方案,假設信息交換的速度不是很快。大多數情況下為了增加成功率,可以連續發送序列號不同的重置報文。
  • 將偽造的重置報文發送給通信的一方或雙方,時其中斷連接。

為了實驗簡單,我們可以使用本地計算機通過 localhost 與自己通信,然後對自己進行 TCP 重置攻擊。需要以下幾個步驟:

  1. 在兩個終端之間建立一個 TCP 連接。
  2. 編寫一個能嗅探通信雙方數據的攻擊程序。
  3. 修改攻擊程序,偽造併發送重置報文。

下面正式開始實驗。

建立 TCP 連接

可以使用 netcat 工具來建立 TCP 連接,這個工很多操作系統都預裝了。打開第一個終端窗口,運行以下命令:

$ nc -nvl 8000

這個命令會啟動一個 TCP 服務,監聽端口為 8000。接着再打開第二個終端窗口,運行以下命令:

$ nc 127.0.0.1 8000

該命令會嘗試與上面的服務建立連接,在其中一個窗口輸入一些字符,就會通過 TCP 連接發送給另一個窗口並打印出來。

嗅探流量

編寫一個攻擊程序,使用 Python 網絡庫 scapy 來讀取兩個終端窗口之間交換的數據,並將其打印到終端上。完整的代碼參考我的 GitHub 倉庫,代碼的核心是調用 scapy 的嗅探方法:

t = sniff(
        iface='lo0',
        lfilter=is_packet_tcp_client_to_server(localhost_ip, localhost_server_port, localhost_ip),
        prn=log_packet,
        count=50)

這段代碼告訴 scapylo0 網絡接口上嗅探數據包,並記錄所有 TCP 連接的詳細信息。

  • iface : 告訴 scapy 在 lo0(localhost)網絡接口上進行監聽。
  • lfilter : 這是個過濾器,告訴 scapy 忽略所有不屬於指定的 TCP 連接(通信雙方皆為 localhost,且端口號為 8000)的數據包。
  • prn : scapy 通過這個函數來操作所有符合 lfilter 規則的數據包。上面的例子只是將數據包打印到終端,下文將會修改函數來偽造重置報文。
  • count : scapy 函數返回之前需要嗅探的數據包數量。

發送偽造的重置報文

下面開始修改程序,發送偽造的 TCP 重置報文來進行 TCP 重置攻擊。根據上面的解讀,只需要修改 prn 函數就行了,讓其檢查數據包,提取必要參數,並利用這些參數來偽造 TCP 重置報文併發送。

例如,假設該程序截獲了一個從(src_ip, src_port)發往 (dst_ip, dst_port)的報文段,該報文段的 ACK 標誌位已置為 1,ACK 號為 100,000。攻擊程序接下來要做的是:

  • 由於偽造的數據包是對截獲的數據包的響應,所以偽造數據包的源 IP/Port 應該是截獲數據包的目的 IP/Port,反之亦然。
  • 將偽造數據包的 RST 標誌位置為 1,以表示這是一個重置報文。
  • 將偽造數據包的序列號設置為截獲數據包的 ACK 號,因為這是發送方期望收到的下一個序列號。
  • 調用 scapysend 方法,將偽造的數據包發送給截獲數據包的發送方。

對於我的程序而言,只需將這一行取消註釋,並註釋這一行的上面一行,就可以全面攻擊了。按照步驟 1 的方法設置 TCP 連接,打開第三個窗口運行攻擊程序,然後在 TCP 連接的其中一個終端輸入一些字符串,你會發現 TCP 連接被中斷了!

進一步實驗

  1. 可以繼續使用攻擊程序進行實驗,將偽造數據包的序列號加減 1 看看會發生什麼,是不是確實需要和截獲數據包的 ACK 號完全相同。
  2. 打開 Wireshark,監聽 lo0 網絡接口,並使用過濾器 ip.src == 127.0.0.1 && ip.dst == 127.0.0.1 && tcp.port == 8000 來過濾無關數據。你可以看到 TCP 連接的所有細節。
  3. 在連接上更快速地發送數據流,使攻擊更難執行。

總的來說,TCP 重置攻擊既深奧又簡單,祝你實驗順利。

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化