Toyota將在中國成立PHEV產線

日本豐田(Toyota)日前於北京發表會上表示,計畫在2018年時開始在中國市場推出PHEV(插電式油電混和車)產品,同時計畫轉移部份產能到中國。

在Toyota表示將在中國推出PHEV之際,因應在地生產的方針,Toyota也首度規劃在日本以外的國家生產PHEV電動車。去年秋天,Toyota已於中國發售Corolla、Levin兩款中國生產的混和車,未來將會推出這兩款車款的PHEV版本。

Toyota表示,2018年左右,會有更多品牌陸續在中國推出PHEV或純電動車,因此需搶先布局中國的相關產線。此外,在中國生產的車款將以中國市場為主,且以組裝為中國產線的主要業務。核心技術與配件則仍留在日本國內。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Model X 高價轉賣,買家是 Ford

據彭博社報導,Tesla 首批發售的 SUV Tesla  Model X 其中一輛的買家是汽車廠商 Ford,後者以高出定價 5.5 萬美元的價格買下了這輛電動車,Ford 是透過一家經銷商購買原車主轉賣的全新 Model X,通常購買競爭對手的新車主要是為了進行測試或者拆解。

Ford 購買 Model X 是 Founders 系列,起售價是 14.4 萬美元,由於首批發售的數量有限,每輛車都有一個編號,Ford 購買的這一輛是 Tesla 加州工廠生產第 64 輛 Model X。原車主是 Tesla 的老客戶、來自加州的 Wayne Skiles,他擁有一輛 Model S,並參加了向親友推薦購車的活動,由於他的推薦 Tesla 售出了 10 輛車,在 Model X 發售時他能夠有機會買到 Founders 系列的 Model X,當然他並沒有打算把這輛車留給自己,選擇在芝加哥交車,隨後就開到汽車店將 Model X 轉賣掉了,還賺了一筆錢。   這輛加州生產的 Model X 最終在底特律被發現,掛著密西根州的車牌,它的車主就是汽車廠商 Ford 公司,購買這輛 Model S,加上稅費和手續費,Ford 一共支付了 21.2 萬美元。為了能夠更早地買到這台車,支付比定價更高的費用是正常的,相信這筆錢對於 Ford 公司來說也不是什麼大數目,那 Ford 為什麼要買競爭對手的新車呢?   汽車廠商購買競爭對手的產品,主要是用於測試或是拆解,以了解對手的設計和技術,Tesla 在電動車的設計製造方面可謂是相當成功,Ford 購買新車來偷師也不足為奇。Ford 公司在聲明中稱,購買汽車廠商的新車進行測試正汽車產業非常常見,為了更早拿到產品,高於定價購買也很正常。   Ford 公司計劃在 2020 年投資 45 億美元研發電動車,至少推出 13 款電動車新品。

(本文授權轉載自《》─〈〉)

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

買電動車送補貼金?傳德國財政部提12億歐元獎勵方案

德國電動車銷售積弱不振,據傳財政部已提出總額12億歐元(相當於13.5億美元)的補貼法案,希望為電動車市重新注入活力,而所需費用將由政府及車商聯手買單。

華爾街日報25日引述未具名消息人士報導,德國政府高層和汽車產業代表即將在26日與德國總理梅克爾會面,討論刺激電動車銷售量的獎勵措施,另外也會考慮直接由政府出資、在全國建設充電站。

根據財政部提出的建議,在2016年至2018年夏季期間,購買電動車的民眾可獲得政府與車商補貼5,000歐元,購買油電混合動力車的民眾則可獲得3,000歐元的補助,在此之後補貼金額會分別下降至3,000歐元(電動車)、2,000歐元(油電混合動力車)。

消息顯示,售價高於60,000歐元的電動車不在補助範圍之內,以免獎勵措施淪為補貼富人買車的工具。另外,這個方案將遵循先買先補貼的原則,12億歐元的補助款用罄之後將不再補款。財政部也提議提供3億歐元的額外補貼,為電動車設立充電站。

Thomson Reuters報導,梅克爾甫於4月12日呼籲汽車製造商開出願望清單。梅克爾表示,官方將盡快清除法律障礙、讓他們可以在德國測試無人駕駛車。她透露,德國執政聯盟將在5月底開會討論自駕車法令規範。德國財長Wolfgang Schaeuble上個月說,官方將設法協助電動車開發、但可能無法讓所有車商感到滿意。車界領袖呼籲,德國若想維持全球領導地位、政府就得想辦法提振電動車買氣。

蘋果在德國的電動車開發案似乎正進行的如火如荼。AppleInsider 4月18日引述法蘭克福廣訊報(Frankfurter Allgemeine Zeitung)報導,蘋果已在柏林設立秘密開發實驗室,目前在當地擁有15-20名工程、軟體、硬體、行銷背景的德國汽車業頂尖人才。報導指出,蘋果進軍汽車業的第一款產品將是電動車、但初期不具備自駕功能。

(本文內容由授權提供)

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

Model 3 還不夠平價?Elon Musk:將推出更便宜的 Model 4

Tesla 新款電動車 Model 3 將在 2017 年下半年出貨,這款定價僅為 3.5 萬美元的電動車在預售期獲得了巨大的成功,這將是一款改變汽車市場規則的產品,Tesla CEO Elon Musk 在接受採訪時表示,Tesla 的下一款產品將比 Model 3 更便宜。

Model 3 是 Tesla 迄今為止推出的最便宜的電動車,這款車在性能和規格上與豪華車相比也不遜色,單次充電續航里程達到 200 英里,定價僅為 3.5 萬美元,早期訂購的消費者還可以享受到電動車的稅率補貼。   Model 3 定價吸引了大量消費者關注,Tesla CEO Elon Musk 最近接受採訪時透露,Tesla 早期推出的 Roadster、Model S 和 Model X 這款價格相對較高的電動車,是為積累資金,為推出一款平價的電動車打下基礎,但 Model 3 不會是 Tesla 唯一的一款平價電動車。   Tesla 的目標是讓更多的人買得起電動車,Model 3 的推出已經讓一半的人能夠買得起電動車,Tesla 的下一個目標就是把定價做得更低,第四代電動車車型更小,價格更低,使用更方便,最終達成讓每個人都偶買得起電動車的目標。   除了創造優質實惠的電動車給消費者外,Elon Musk 另一個計畫就是研發無人駕駛的公車,減少城市的壅堵。在更便宜的 Model 4 面世之前,Tesla 面對的最大挑戰是短期內提高產能,完成 Model 3 的訂單,據業內人士透露目前 Model 3 的訂單量已經超過了 Tesla 的產能,甚至最終都無法完全交付。

(首圖來源: CC BY 2.0)    (本文授權轉載自《》─〈〉)

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

通過javascript 執行環境理解她

1{icon} {views}

古往今來最難的學的武功(javascript)算其一。

欲練此功必先自宮,願少俠習的此功,笑傲江湖。

你將了解

  • 執行棧(Execution stack)
  • 執行上下文(Execution Context)
  • 作用域鏈(scope chains)
  • 變量提升(hoisting)
  • 閉包(closures)
  • this 綁定

執行棧

又叫調用棧,具有 LIFO(last in first out 後進先出)結構,用於存儲在代碼執行期間創建的所有執行上下文。

當 JavaScript 引擎首次讀取你的腳本時,它會創建一個全局執行上下文並將其推入當前的執行棧。每當發生一個函數調用,引擎都會為該函數創建一個新的執行上下文並將其推到當前執行棧的頂端。
引擎會運行執行上下文在執行棧頂端的函數,當此函數運行完成后,其對應的執行上下文將會從執行棧中彈出,上下文控制權將移到當前執行棧的下一個執行上下文。

我們通過下面的示例來說明一下

function one() {
  console.log('one')
  two()
}
function two() {
  console.log('two')
}
one()

當程序(代碼)開始執行時 javscript 引擎創建 GobalExecutionContext (全局執行上下文)推入當前的執行棧,此時 GobalExecutionContext 處於棧頂會立刻執行全局執行上下文 然後遇到 one() 引擎都會為該函數創建一個新的執行上下文 oneFunctionExecutionContext 並將其推到當前執行棧的頂端並執行,然後遇到two() twoFunctionExecutionContext 入棧並執行至出棧,回到 oneFunctionExecutionContext 繼續執行至出棧 ,最後剩餘一個 GobalExecutionContext 它會在程序關閉的時候出棧。

然後調用棧如下圖:

如果是這樣的代碼

function foo() {
  foo()
}
foo()

如下

當一個遞歸沒有結束點的時候就會出現棧溢出

什麼是執行上下文

了解 JavaScript 的執行上下文,有助於你理解更高級的內容比如變量提升、作用域鏈和閉包。既然如此,那到底什麼是“執行上下文”呢?

執行上下文是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念。

Javascript 中代碼的執行上下文分為以下三種:

  1. 全局執行上下文(Global Execution Context)- 這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境。
  2. 函數執行上下文(Function Execution Context) – 當執行一個函數時,運行函數體中的代碼。
  3. Eval – 在 Eval 函數內運行的代碼。

javascript 是一個單線程語言,這意味着在瀏覽器中同時只能做一件事情。當 javascript 解釋器初始執行代碼,它首先默認進入全局上下文。每次調用一個函數將會創建一個新的執行上下文。

javascript執行棧中不同執行上下文之間的詞法環境有一種關聯關係,從棧頂到棧底(從局部直到全局),這種關係被叫做作用域鏈

簡單的說,每次你試圖訪問函數執行上下文中的變量時,進程總是從自己上下文環境中開始查找。如果在自己的上下文中沒發現要查找的變量,繼續搜索下一層上下文。它將檢查執行棧中每一個執行上下文環境,尋找和變量名稱匹配的值,直到找到為止,如果到全局都沒有則拋出錯誤。

執行上下文的創建過程

我們現在已經知道,每當調用一個函數時,一個新的執行上下文就會被創建出來。然而,在 javascript 引擎內部,這個上下文的創建過程具體分為兩個階段:

創建階段 > 執行階段

創建階段

執行上下文在創建階段創建。在創建階段發生以下事情:

  1. LexicalEnvironment 組件已創建。
  2. VariableEnvironment 組件已創建。

因此,執行上下文可以在概念上表示如下:

ExecutionContext = {
  LexicalEnvironment = <詞法環境>,
  VariableEnvironment = <變量環境>,
}

詞法環境(Lexical Environment)

文檔將詞法環境定義為:

詞法環境是一種規範類型,基於 ECMAScript 代碼的詞法嵌套結構來定義標識符與特定變量和函數的關聯關係。詞法環境由環境記錄(environment record)和可能為空引用(null)的外部詞法環境組成。

簡而言之,詞法環境是一個包含標識符變量映射的結構。(這裏的標識符表示變量/函數的名稱,變量是對實際對象【包括函數類型對象】或原始值的引用)

詞法環境有兩種類型

  • 全局環境(在全局執行上下文中)是一個沒有外部環境的詞法環境。全局環境的外部環境引用為 null。它擁有一個全局對象(window 對象)及其關聯的方法和屬性(例如數組方法)以及任何用戶自定義的全局變量,this 的值指向這個全局對象。
  • 函數環境,用戶在函數中定義的變量被存儲在環境記錄中。對外部環境的引用可以是全局環境,也可以是包含內部函數的外部函數環境。

每個詞彙環境都有三個組成部分:

1)環境記錄(environment record)

2)對外部環境的引用(outer)

3) 綁定 this

環境記錄 同樣有兩種類型(如下所示):

  • 聲明性環境記錄 存儲變量、函數和參數。一個函數環境包含聲明性環境記錄。
  • 對象環境記錄 用於定義在全局執行上下文中出現的變量和函數的關聯。全局環境包含對象環境記錄

抽象地說,詞法環境在偽代碼中看起來像這樣:

詞法環境和環境記錄值是純粹的規範機制,ECMAScript 程序不能直接訪問或操縱這些值。

GlobalExectionContext = {
  // 詞法環境
  LexicalEnvironment:{
    // 功能環境記錄
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
     }
    outer:<null>,
    this:<global object>
  }
}
FunctionExectionContext = {
  LexicalEnvironment:{
    EnvironmentRecord:{
      Type:"Declarative",
      // Identifier bindings go here
     }
    outer:<Global or outer function environment reference>,
    this:<取決於函數的調用方式>
  }
}

變量環境:

它也是一個詞法環境,其 EnvironmentRecord 包含了由 VariableStatements 在此執行上下文創建的綁定。
如上所述,變量環境也是一個詞法環境,因此它具有上面定義的詞法環境的所有屬性。
在 ES6 中,LexicalEnvironment 組件和 VariableEnvironment 組件的區別在於前者用於存儲函數聲明和變量( let 和 const )綁定,而後者僅用於存儲變量( var )綁定。
讓我們結合一些代碼示例來理解上述概念:

let a = 20
const b = 30
var c

function multiply(e, f) {
  var g = 20
  return e * f * g
}

c = multiply(20, 30)

執行上下文如下所示:

GlobalExectionContext = {
  LexicalEnvironment:{
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
      a:<uninitialized>,
      b:<uninitialized>,
      multiply:<func>
    },
    outer:<null>,
    ThisBinding:<Global Object>
  },
  VariableEnvironment:{
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
      c:undefined,
    }
    outer:<null>,
    ThisBinding:<Global Object>
  }
}

在執行階段,完成變量賦值。因此,在執行階段,全局執行上下文將看起來像這樣。

// 執行
GlobalExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: 20,
      b: 30,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

當 multiply(20, 30)遇到函數調用時,會創建一個新的函數執行上下文來執行函數代碼。因此,在創建階段,函數執行上下文將如下所示:

// multiply 創建
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

在此之後,執行上下文將執行執行階段,這意味着完成了對函數內部變量的賦值。因此,在執行階段,函數執行上下文將如下所示:

// multiply 執行
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

函數完成后,返回的值賦值給c。因此,全局詞法環境得到了更新。之後,全局代碼完成,程序結束。

注: 在執行階段,如果 Javascript 引擎在源代碼中聲明的實際位置找不到 let 變量的值,那麼將為其分配 undefined 值。

變量提升

在網上一直看到這樣的總結: 在函數中聲明的變量以及函數,其作用域提升到函數頂部,換句話說,就是一進入函數體,就可以訪問到其中聲明的變量以及函數。這是對的,但是知道其中的緣由嗎?相信你通過上述的解釋應該也有所明白了。不過在這邊再分析一下。

你可能已經注意到了在創建階段 letconst 定義的變量沒有任何與之關聯的值,但 var 定義的變量設置為 undefined
這是因為在創建階段,代碼會被掃描並解析變量和函數聲明,其中函數聲明存儲在環境中,而變量會被設置為 undefined(在 var 聲明變量的情況下)或保持未初始化(在 letconst 聲明變量的情況下)。
這就是為什麼你可以在聲明之前訪問var 定義的變量(儘管是 undefined ),但如果在聲明之前訪問letconst 定義的變量就會提示引用錯誤的原因。
這就是我們所謂的變量提升

思考題:

console.log('step1:',a)
var a = 'artiely'
console.log('step2:',a)
function bar (a){
  console.log('step3:',a)
  a = 'TJ'
  console.log('step4:',a)
  function a(){
  }
}
bar(a)
console.log('step5:',a)

對外部環境的引用

上面代碼如果我們改用調用方式如下:

let a = 20
const b = 30
var c

function multiply() {
  var g = 20
  return a * b * g
}

c = multiply()

其實你會發現結果是一樣的
但是 multiply 的執行上下文卻發生一些變化

// 創建
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { length: 0},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}
// 執行
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { length: 0},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

multiply() 執行的時候會直接在 outer: <GlobalLexicalEnvironment>,中查找a,b

對外部環境的引用意味着它可以訪問其外部詞法環境。這意味着如果在當前詞法環境中找不到它們,JavaScript 引擎可以在外部環境中查找變量。這就是之前說的 作用域鏈

閉包

MDN 解釋 閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量

這是我認為對閉包最合理的解釋了,就看你怎麼理解閉包的機制了。
其實閉包與作用域鏈有着密切的關係。

首先我們來看看什麼樣的代碼會產生閉包。

function foo() {
  var name = 'artiely'
  function bar() {
    console.log(`hello `)
  }
  bar()
}
foo()

以上代碼是有閉包嗎?沒有~

function foo() {
  var name = 'artiely'
  function bar() {
    console.log(`hello ${name}`)
  }
  bar()
}
foo()

我們只做了微小的調整,現在就有閉包了,我們只是在bar中加入了name得引用
上面的代碼還可以寫成這樣

// 或者
function foo() {
  var name = 'artiely'
  return function bar() {
    console.log(`hello ${name}`)
  }
}
foo()()

對於閉包的形成我進行了如下的幾點歸納

  1. A 函數內必須有 B 函數的聲明;
  2. B 函數必須引用 A 函數的變量;
  3. B 函數被調用(當然前提是 A 函數被調用)

以上 3 點缺一不可

我們來分析一下上面代碼的執行上下文

// 創建
fooFunctionExectionContext = {
LexicalEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    Arguments: { length: 0},
    bar: < func >,
  },
  outer: <GlobalLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    name: undefined
  },
  outer: <GlobalLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>
  }
}
// 執行 略
// 創建
barFunctionExectionContext = {
LexicalEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    Arguments: { length: 0},
  },
  outer: <fooLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
  },
  outer: <fooLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>
  }
}
// 執行 略

這裏因為bar的創建存在着對fooLexicalEnvironment里變量的引用,雖然foo可能執行已結束但變量不會被回收。這種機制被叫做閉包

閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量

我們結合上面例子重新分解一下這句話

閉包是由函數bar以及創建該函數foo的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量name

但是從chrome的理解,閉包並沒有包含所能訪問的所有局部變量,僅僅包含所被引用的變量。

this 綁定

在全局執行上下文中,值是 this 指全局對象。(在瀏覽器中,this 指的是 Window 對象)。

在函數執行上下文中,值 this 取決於函數的調用方式。如果它由對象引用調用,則將值 this 設置為該對象,否則,將值 this 設置為全局對象或 undefined(在嚴格模式下)。例如:

let person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear)
  }
}

person.calcAge()
// 'this' 指向 'person', 因為 'calcAge' 是被 'person' 對象引用調用的。

let calculateAge = person.calcAge
calculateAge()
// 'this' 指向全局 window 對象,因為沒有給出任何對象引用

注意所有的()()自調用的函數 this 都是指向Global Object的既瀏覽器中的window

最後

如果本文對你有幫助或覺得不錯請幫忙點贊,如有疑問請留言。

其他參考:

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

一文看懂 K8s 日誌系統設計和實踐

作者 | 元乙  阿里雲存儲服務技術專家

導讀:上一篇文章中我們介紹了為什麼需要一個日誌系統、為什麼雲原生下的日誌系統如此重要以及雲原生背景下日誌系統的建設難點,相信 DevOps、SRE、運維等同學看了之後深有體會。本篇文章單刀直入,會直接跟大家分享一下如何在雲原生的場景下搭建一個靈活、功能強大、可靠、可擴容的日誌系統。

需求驅動架構設計

技術架構,是將產品需求轉變為技術實現的過程。對於所有的架構師而言,能夠將產品需求分析透徹是非常基本也是非常重要的一點。很多系統剛建成沒多久就要被推翻,最根本的原因還是沒有解決好產品真正的需求。

我所在的日誌服務團隊在日誌這塊有近10年的經驗,幾乎服務阿里內部所有的團隊,涉及電商、支付、物流、雲計算、遊戲、即時通訊、IoT等領域,多年來的產品功能的優化和迭代都是基於各個團隊的日誌需求變化。

有幸我們最近幾年在阿里雲上實現了產品化,服務了數以萬計的企業用戶,包括國內各大直播類、短視頻、新聞媒體、遊戲等行業Top1互聯網客戶。產品功能從服務一個公司到服務上萬家公司會有質的差別,上雲促使我們更加深入的去思考:究竟哪些功能是日誌這個平台需要去為用戶去解決的,日誌最核心的訴求是什麼,如何去滿足各行各業、各種不同業務角色的需求…

需求分解與功能設計

上一節中我們分析了公司內各個不同角色對於日誌的相關需求,總結起來有以下幾點:

  1. 支持各種日誌格式、數據源的採集,包括非K8s
  2. 能夠快速的查找/定位問題日誌
  3. 能夠將各種格式的半結構化/非結構化日誌格式化,並支持快速的統計分析、可視化
  4. 支持通過日誌進行實時計算並獲得一些業務指標,並支持基於業務指標實時的告警(其實本質就是APM)
  5. 支持對於超大規模的日誌進行各種維度的關聯分析,可接受一定時間的延遲
  6. 能夠便捷的對接各種外部系統或支持自定義的獲取數據,例如對接第三方審計系統
  7. 能夠基於日誌以及相關的時序信息,實現智能的告警、預測、根因分析等,並能夠支持自定義的離線訓練方式以獲得更好的效果

為滿足上述這些功能需求,日誌平台上必須具備的功能功能模塊有:

  1. 全方位日誌採集,支持DaemonSet、Sidecar各種採集方式以應對不同的採集需求,同時支持Web、移動端、IoT、物理機/虛擬機各種數據源的採集;
  2. 日誌實時通道,這個是為了對接上下游所必備的功能,保證日誌能夠被多種系統所便捷的使用;
  3. 數據清洗(ETL: Extract,Transform,Load),對各種格式的日誌進行清洗,支持過濾、富化、轉換、補漏、分裂、聚合等;
  4. 日誌展現與搜索,這是所有日誌平台必須具備的功能,能夠根據關鍵詞快速的定位到日誌並查看日誌上下文,看似簡單的功能卻最難做好;
  5. 實時分析,搜索只能完成一些定位到問題,而分析統計功能可以幫助快速分析問題的根因,同時可以用於快速的計算一些業務指標;
  6. 流計算,通常我們都會使用流計算框架(Flink、Storm、Spark Stream等)來計算一些實時的指標或對數據進行一些自定義的清洗等;
  7. 離線分析,運營、安全相關的需求都需要對大量的歷史日誌進行各種維度的關聯計算,目前只有T+1的離線分析引擎能夠完成;
  8. 機器學習框架,能夠便捷、快速的將歷史的日誌對接到機器學習框架進行離線訓練,並將訓練后的結果加載到線上實時的算法庫中。

開源方案設計

藉助於強大的開源社區,我們可以很容易基於開源軟件的組合來實現這樣一套日誌平台,上圖是一個非常典型的以ELK為核心的日誌平台方案:

  • 利用FileBeats、Fluentd等採集Agent實現容器上的數據統一收集。
  • 為了提供更加豐富的上下游以及緩衝能力,可以使用kafka作為數據採集的接收端。
  • 採集到的原始數據還需要進一步的清洗,可以使用Logstash或者Flink訂閱Kafka中的數據,清洗完畢后再寫入kafka中。
  • 清洗后的數據可以對接ElasticSearch來做實時的查詢檢索、對接Flink來計算實時的指標和告警、對接Hadoop來做離線的數據分析、對接TensorFlow來做離線模型訓練。
  • 數據的可視化可以使用grafana、kibana等常用的可視化組件。

為什麼我們選擇自研

採用開源軟件的組合是非常高效的方案,得益於強大的開源社區以及龐大用戶群體的經驗積累,我們可以很快搭建出這樣一套系統,並且可以滿足我們絕大部分的需求。

當我們把這套系統部署好,能夠把日誌從容器上採集上來、elasticsearch上能夠查到、Hadoop上能夠成功執行SQL、Grafana上能看到圖、告警短信能收到……完成上述流程打通后,加加班可能只需要花費幾天的時間,當系統終於跑通的時候,這時候終於可以長舒一口氣,躺在辦公椅上放鬆放鬆。

然而理想很豐滿現實很骨感,當我們預發通了,測試完了上到生產,開始接入第一個應用,逐漸更多的應用接入,越來越多的人開始使用……這時候很多問題都可能暴露出來:

  • 隨着業務量的上漲,日誌量也越來越大,Kakfa和ES要不斷擴容,同時同步Kafka到ES的Connector也需要擴容,最煩的是採集Agent,每台機器上部署的DaemonSet Fluentd根本沒辦法擴容,到了單Agent瓶頸就沒辦法了,只能換Sidecar,換Sidecar工作量大不說,還會帶來一系列其他的問題,比如怎麼和CICD系統集成、資源消耗、配置規劃、stdout採集不支持等等。
  • 從剛開始上的邊緣業務,慢慢更多的核心業務接入,對於日誌的可靠性要求越來越高,經常有研發反應從ES上查不到數據、運營說統計出來的報表不準、安全說拿到的數據不是實時的……每次問題的排查都要經過採集、隊列、清洗、傳輸等等非常多的路徑,排查代價非常高。同時還要為日誌系統搭建一套監控方案,能夠即時發現問題,而且這套方案還不能基於日誌系統,不能自依賴。
  • 當越來越多的開發開始用日誌平台調查問題時,經常會出現因為某1-2個人提交一個大的查詢,導致系統整體負載上升,其他人的查詢都會被Block,甚至出現Full GC等情況。這時候一些大能力的公司會對ES進行改造,來支持多租戶隔離;或者為不同的業務部門搭建不同的ES集群,最後又要運維多個ES集群,工作量還是很大。
  • 當投入了很多人力,終於能夠把日誌平台維持日常使用,這時候公司財務找過來了,說我們用了非常多的機器,成本太大。這時候開始要優化成本,但是思來想去就是需要這麼多台機器,每天大部分的機器水位都在20%-30%,但是高峰的水位可能到70%,所以不能撤,撤了高峰頂不住,這時候只能搞搞削峰填谷,又是一堆工作量。

上述這些是一家中等規模的互聯網企業在日誌平台建設中經常會遇到的問題,在阿里這些問題會放大非常多倍:

  • 比如面對雙十一的流量,市面上所有的開源軟件都無法滿足我們那麼大流量的需求。
  • 面對阿里內部上萬個業務應用,幾千名工程師同時使用,併發和多租戶隔離我們必須要做到極致。
  • 面對非常多核心的訂單、交易等場景,整個鏈路的穩定性必須要求3個9甚至4個9的可用性。
  • 每天如此大的數據量,對於成本的優化顯得極為重要,10%的成本優化帶來的收益可能就有上億。

阿里K8s日誌方案

針對上述的一些問題,我們經過多年的時間,開發並打磨出這樣一套K8s日誌方案:

  1. 使用我們自研的日誌採集Agent Logtail實現K8s全方位的數據採集,目前Logtail在集團內有數百萬的全量部署,性能、穩定性經過多次雙十一金融級考驗。
  2. 化繁為簡,數據隊列、清洗加工、實時檢索、實時分析、AI算法等原生集成,而不是基於各種開源軟件搭積木的形式實,大大降低了數據鏈路長度,鏈路長度的降低也意味着出錯可能性的減少。
  3. 隊列、清洗加工、檢索、分析、AI引擎等全部針對日誌場景深度定製優化,滿足大吞吐、動態擴容、億級日誌秒級可查、低成本、高可用性等需求。
  4. 對於流式計算、離線分析場景這種通用需求,無論是開源還是阿里內部都有非常成熟的產品,我們通過無縫對接的方式來支持,目前日誌服務支持了數十種下游的開源、雲上產品的對接。

這套系統目前支撐了整個阿里集團、螞蟻集團、雲上上萬家企業的日誌分析,每天寫入的數據量16PB+,開發、運維這樣一套系統問題和挑戰非常多,這裏就不再展開,有興趣的同學可以參考我們團隊的技術分享:。

總結

本篇主要從架構層面去介紹如何搭建一套K8s的日誌分析平台,包括開源方案以及我們阿里自研的一套方案。然而實際這套系統落地到生產環境並有效運行還有很多工作要做:

  1. K8s上以什麼樣的姿勢來打日誌?
  2. K8s上的日誌採集方案選擇,DaemonSet or Sidecar?
  3. 日誌方案如何與CICD去集成?
  4. 微服務下各個應用的日誌存儲如何劃分?
  5. 如何基於K8s系統的日誌去做K8s監控?
  6. 如何去監控日誌平台的可靠性?
  7. 如何去對多個微服務/組件去做自動的巡檢?
  8. 如何自動的監控多個站點並實現流量異常時的快速定位?

後續文章我們會一步一步來和大家分享如何把這套系統落地,敬請期待。

“ 阿里巴巴雲原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術公眾號。”

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

ASP.NET Core 3.0 gRPC 身份認證和授權

一.開頭聊騷

本文算是對於 ASP.NET Core 3.0 gRPC 研究性學習的最後一篇了,以後在實際使用中,可能會發一些經驗之文。本文主要講 ASP.NET Core 本身的認證授權和gRPC接入,認證方式採用目前主流的 JWT 結合 IdentityServer4。

二.服務端配置

我們首先需要在服務端配置認證和授權。gRPC基於此文的Demo來開始: ,IdentityServer 基於此文Demo: 。

配置

1.首先啟動 IdentityServer4 地址為:

2.為gRPC項目安裝Jwt組件:Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 3.0.0

3.為gRPC項目配置認證和授權服務

在 Startup 類的 ConfigureServices 方法中,配置如下代碼

services.AddAuthorization(options =>
{
    options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
    {
        policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireClaim("sub");
    });
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.Audience = "grpc1";
    });

4.啟用認證授權中間件

在 Startup 類的 Configure 方法中,配置如下代碼

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

請務必注意中間件順序

5.為gRPC服務啟用授權

我們在 LuCatService 的 SuckingCat 方法上,加上 [Authorize]特性,就和在MVC中一樣。

測試

運行客戶端調用服務端來進行測試,發現服務端返回了授權失敗,客戶端同樣獲得了錯誤。這證明我們的服務端配置是沒有問題的

三.客戶端配置

配置

客戶端首先需要從 IdentityServer 申請 Token,然後在調用 gRPC 服務時傳遞過去,這和 HTTP Api 調用一樣。

1.客戶端項目安裝組件 IdentityModel 獲得基於 HttpClient 的和 IdentityServer 的交互的封裝。

2.獲取Token

// discover endpoints from metadata
var client = new HttpClient();

var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
{
    Console.WriteLine(disco.Error);
    return;
}

// request token
var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
    Address = disco.TokenEndpoint,
    ClientId = "ro.client",
    ClientSecret = "secret",

    UserName = "alice",
    Password = "password",
    Scope = "grpc1"
});

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");

3.為 gRPC 客戶端請求設置 Token

和 HTTP Api 調用一樣,gRPC也是放在頭部的

var headers = new Metadata {{"Authorization", $"Bearer {tokenResponse.Json["access_token"]}"}};

var catClient = new LuCat.LuCatClient(channel);
var catReply = await catClient.SuckingCatAsync(new Empty(), headers);

主要就是在調用 SuckingCatAsync方法時,傳入了header。

測試

可以看到成功的進行了調用。

四.結束

本文所用代碼地址:

gRPC in Asp.Net Core :

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

打造最可靠的自動駕駛基礎架構

文章作者:莫璐怡 Pony.ai

編輯整理:Hoh Xil

內容來源:Pony.ai & DataFun AI Talk

出品社區:DataFun

注:歡迎轉載,轉載請在留言區留言。

導讀:本次分享的主題為打造最可靠的自動駕駛基礎架構。主要內容包括如何做 Pony.ai 自動駕駛系統的基礎架構,涉及到的技術困難,以及我們是如何克服的。

首先先了解下傳統互聯網公司的基礎架構:

數據基礎設施,會包括大規模的數據庫、分佈式的文件系統;

計算平台,可能會需要大量的服務器、大數據平台、容器的管理機制;

Web 服務管理,同時還會有各種各樣的 Web Service,不停的迭代來滿足新的業務發展。

這是傳統互聯網公司要做的事情,但是對於自動駕駛公司和 Pony.ai,在這樣的架構基礎上我們還會做哪些事情?

這是 Pony.ai 的基礎架構,包含了所有傳統互聯網公司要做的事情,除此之外,還需要做如下事情:

自動駕駛車載系統,如何支持各種各樣的AI技術、算法,如何控制車輛,這都依賴於自動駕駛車載系統來完成。

大規模仿真平台,Pony.ai 每天至少會跑 30W 公里的仿真測試(很多自動駕駛公司一年跑的里程可能只有百萬級別),這點對於自動駕駛測試來說非常重要。

車隊運營基礎平台,Pony.ai 要打造自己的移動出行服務,需要基礎平台來支持 Robotaxi 的運營。

可視化平台與人機接口,可視化平台是幫助我們了解系統到底是如何思考、運作的,或者當測試工程師做各種測試的時候都依賴於可視化平台;人機接口,自動駕駛車輛最終是要提供出行服務,是有乘客在裏面,這時會有一個可視化的界面,來告訴乘客車所感知的周圍環境,以及接下來的駕駛操作等等信息,同時還會提供人機交互的功能,讓乘客也能控制車輛,比如輸入目的地,或者需要停車等等。

Pony.ai 的目標是打造自動駕駛移動出行平台,我們希望可以在不同的城市,可以提供大規模自動駕駛車輛的運營,那麼我們的基礎架構會面臨以下挑戰:

車輛數量的增加,目前廣州已經有幾十輛車在進行測試,同時還在不停的增長着;

運營區域的擴大,剛開始只是在很小的區域進行測試,目前已經在幾百平方公里的區域進行測試;

數據量的增長,我們有很多的傳感器,以及車輛和運營區域的增加,都使得數據量的增長非常非常非常大;

工程師數量的增長,目前 Pony.ai 有廣深、北京、美國四個 office,工程師的數量每周都在增長,所以導致模塊數量和內部代碼的數量也在增長。

所有的這些增長都要求我們的技術棧是具有可擴展性的,來滿足快速增長所帶來的挑戰。

剛剛講了整個基礎架構,其中重要的一點就是車載系統,在講車載系統之前,先簡單介紹下自動駕駛系統:

傳感器及其他硬件:激光雷達、高分辨率攝像頭、毫米波雷達、GNSS/IMU、運算平台,我們可看到圖中標了不同的顏色,目前這些傳感器是通過 Supplier Partner 來得到的,我們自己不做傳感器,我們需要去購買他們的產品,但是購買之後需要做數據進一步的分析和整合,然後做後面的處理,然後對於運算平台除了 supplier 的一些應用外,我們自己也會做一些優化。傳感器主要要做的事情就是接收真實世界的數據,然後傳遞給 Pony.ai 自動駕駛系統中。

自動駕駛系統:首先,要做傳感器融合,進行時間同步,將多傳感器的數據融合在一起;然後是感知模塊,用來感知周圍的環境有什麼樣的障礙物和物體;接下來會進行行為預測,預測這樣的障礙物或物體之後的行為會是什麼樣的;然後才到我們的決策規劃模塊,按照之前的預測來決定之後車輛的動作,如急剎車、讓路、超車等動作;最後,就是我們的控制模塊,他會按照決策規劃模塊,告知我們的系統要怎麼做,然後決定怎麼踩剎車、油門,怎麼打方向盤。

車輛,我們本身是不造車的,所以車輛是由 OEM 提供的,但是整個控制的算法,是我們自研去做的。

除此之外,還有高精地圖與定位模塊,以及數據與系統架構(數據的處理,以及控制數據在不同模塊的流動)。

這裏介紹的是各個模塊,但最後把他們串聯起來,靠的是我們的自動駕駛軟件系統,這就是自動駕駛的車載系統。很多自動駕駛企業使用的是 ROS 的一套工業系統,而 Pony.ai 是從第一行代碼開始,寫了一套 PonyBrain,自研的多層次自動駕駛車載系統,最主要的做的事情有:

多模塊的調度運行,所有模塊的調度運行都是 Pony.ai 自己去做的。

模塊間的消息通信,如何把數據從激光雷達傳遞到傳感器融合的模塊,再把融合的結果放到感知模塊中,然後感知的數據怎麼告訴行為預測、決策規劃等等模塊,以及如何拿到高精地圖與定位的信息。

車載計算資源的分配與管理,對於自動駕駛來說反應速度是非常重要的,這就需要我們對內存、CPU、GPU 等有足夠的優化,做到定製化的車載計算資源分配與管理。

日誌記錄,同時我們需要完善的日誌記錄,我們所有的測試數據回來都需要一整套的 Pipeline 去做自動化的分析,然後幫我們評判出有意義的數據,給到測試工程師或者研發工程師,進行進一步的分析去使用,然後進一步提升我們的模型。

監控與報警,保證了我們自動駕駛的安全性。

車載系統的挑戰:

① 可靠性:車載系統必須足夠的可靠,不能有任何的內存泄露、代碼邏輯的錯位,這種都是零容忍的,一旦發生了這樣的事情,對整個自動駕駛系統來說是非常嚴重的事故,是有可能影響到安全性的,對於 Pony.ai 自動駕駛系統技術的發展來說,安全永遠是我們的第一位,所以所有影響安全性的事情,我們都是零容忍的,同時他也會影響車隊運營的效率;所以我們還需要系統監控與異常報警,一旦系統出現任何問題,我們需要及時提醒安全員,做出車輛接管的操作。

② 高性能:滿足模塊間通信的海量數據壓力,同時實現低延遲。

③ 靈活性:支持多種不同類型的計算資源的接入,以及不同類型模塊的接入,需要有靈活的系統來支持計算資源的高速迭代。

車載系統的實踐:

可靠性:

① 代碼質量要求高:對於可靠性來說我們有非常嚴格的 code review 和 unit test,相信這是在國內互聯網公司不太容易見到的一件事情,雖然會非常耗時,但是對可靠性的提升是有非常大的幫助的。

② 合理使用工具幫助發現問題:同時我們也會使用非常多的工具,如靜態分析、ASAN 等等,來做離線的分析,來保證系統的可靠性。

③ 多重系統可靠性檢查:包括系統啟動前校驗,系統運行時實時監控,系統運行后數據分析等。

④ 這是我們的持續集成與發布的平台:對於每一次代碼的修改,我們都會進行仿真測試;然後對於研發的迭代,我們每周會有 Release 版本的更新,保障版本的穩定性,同時,剛剛我們整個測試包括封閉,半封閉,高峰期的測試,整個測試流程怎麼持續集成與發布,也是保證系統可靠性的一種方法。

高性能:

① 合理的架構避免大數據拷貝等嚴重影響性能的邏輯。

② 依據模塊邏輯分配合適的計算資源,如內存、CPU、GPU 等。

③ 定期對整個系統 Profile 分析系統的性能瓶頸。

靈活性:

① 定義足夠通用的模塊公共接口。

② 定義足夠通用的消息通信接口。

為什麼需要仿真系統?因為仿真系統可以使得我們車還沒有上路的時候,就已經做了大規模的自動駕駛測試,無需路測和人力接入就可以評價系統的性能變化;由於沒有進行路測,不會引起路面事故;同時,仿真系統還提供了基於數據驅動快速迭代算法的可行性,新的算法可以先在仿真平台上做驗證,一些具體的指標和測試的信息都會在仿真平台上有所體現。

仿真系統數據的倆個不同來源:

① 支持真實路測收集的場景,我們的路測數據非常的多,數據回來之後,通過 Data Pipeline 自動更新這些有意義和有意思的場景,我們會根據當時的場景改動相應的模塊,然後會在仿真系統重跑當時的場景,來判斷新的方法是否 work;

② 支持人工和隨機生成的場景,這樣的一些仿真的場景,也是非常的重要的,因為雖然我們在做大規模的路測,但是不代表可以遇到所有的場景,很多場景無法在路測中收集到,這就需要我們通過人工去創造這樣的場景出來,給我們的系統一些樣本,來學習如何處理這樣的場景,保證我們新的 feature 在這樣的場景不會出現問題。

仿真平台的挑戰與實踐:

① 仿真結果的可靠性:首先仿真的結果必須是可靠的,如果不可靠,用它檢測出來的結果是沒有任何的意義的。整個仿真是在服務端模擬車載環境跑的,同時在服務端構建車輛動力學模型,保證測試的數據足夠可靠。

② 仿真數據的選擇與管理:當然我們會選擇合適的路測數據來幫助算法的迭代(這裏的選擇不是人工的選擇,是全自動化的選擇,幫我們在茫茫數據中挑選出有意義的數據);另外,我們還會規範的依據類別管理大規模的仿真數據,比如感知模塊的一些改動,到底需要測試哪些數據,才會更加的體現這個改動帶來多少影響,這裏我們會有內部的一個分類,我們不會對所有的數據進行無差別的仿真(這樣做意義不大)。

③ 仿真系統的性能:我們將整個仿真系統并行部署在分佈式計算平台中,這才可能滿足我們單天 30W 公里以上的仿真測試,並且這個數據還在不斷增長。

數據基礎架構:

數據是自動駕駛技術進步的核心驅動力,沒有數據,我們就看不到現在如此多的測試車輛在進行路測,數據本身有幾個重要的點:

① 如何存儲海量的數據,如何支持快速的訪問。

② 如何進行數據處理。

③ 如何進行數據同步,如何把不同區域、路測數據、車載數據同步到數據集中,如何讓不同辦公區的工程師都可以使用這些數據,對數據同步來說是一個很大的挑戰。

核心挑戰:

① 數據量大:我們有 PB 級別的數據,這裏只是以攝像頭為例,還包括其他傳感器數據,以及系統運作的中間數據等等。

② 數據屬性不同於互聯網數據:我們的數據由客戶端產生,有大量的傳感器數據、大量的模塊運行日誌,這與互聯網數據有本質的區別,所以對整個數據架構的要求也是不一樣的。

數據存儲的挑戰:

① 依據特定的使用場景設計合理的存儲格式的設計:以便於車載系統記錄、大規模數據分析(數據回來之後,需要有方法進行分析,找出有意義的數據)、部分數據訪問、文件系統存儲(如何高效的利用文件系統)等。

② 選擇合適的存儲系統:

針對冷/熱數據選擇不同方案

選擇高可用的存儲系統

選擇易於水平擴展,因為車輛規模是不停的在變大的,運營時間越來越長,數據的增長速度是遠超想象的,所以需要易於水平擴展的存儲系統。

控製成本,不能用過於昂貴的設備。

數據處理可以幫助收集性能指標,有 MPI(平均每次接管所需里程)、模塊運行效率、乘客舒適度體驗等,還有就是路測有趣場景的挖掘,如接管、急剎、感知算法識別、不合理的變道策略等用於模型訓練和仿真。

數據處理的挑戰:

① 減小數據採集到處理的全流程時間:如何以最快的速度把數據從車傳到中間處理系統,Data Pipeline 運行完之後,上傳到數據中心,這裏面我們做了非常多的工作。

② 依據不同類型數據處理任務選擇合適的處理系統:計算量要求比較高的我們選擇 CPU 密集型系統來處理;更多的會是車載的數據,我們會選擇 IO 密集型系統進行處理。

③ 通用的任務定義以支持靈活的添加新任務:幫我們檢測出來更多有意義的數據。

車隊運營基礎平台:

我們有一個 Pony Pilot 項目,在我們廣州所有的內部員工都可以使用,同時在北京和美國加州,也有同樣的服務已經上線,那麼支持這樣的服務,我們需要做哪些事情:

Fleet Control Center,車隊控制中心

Pony Pilot APP

Onboard system

各種各樣的 webapp,幫助我們觀察整個車隊的運營情況,幫助管理測試的車輛和人員。

車隊運營基礎平台的挑戰:

需要支持複雜需求變化的 web 框架,同時我們有大量的 web service 的部署與管理,這都需要我們去完善 web 服務通用組件,例如部署工具、日誌記錄平台隨時排查問題、監控平台保證所有 service 平台的高可容性。

容器與服務調度平台:

通過 Kubernetes 來幫我們做各種各樣的服務調度和集群支持。

可視化平台:

① 目標:方便人類理解無人車系統看到的世界

② 挑戰:首先,需要足夠的靈活,易於適配不同需求的工具;其次,需要有高性能的現實,如 3D 實時渲染的高效實現;最後,支持跨平台的可視化框架,如桌面系統、移動系統、Web 等多平台。

人機接口:

方便乘客使用的用戶界面,同時可以看到自動駕駛是如何了解世界,如何做決策,如何規劃之後的行為等等,給乘客更多的信息和信任。

總結:

① Pony.ai 的基礎架構工作包括:

傳統互聯網公司所需要解決的基礎架構挑戰。

自動駕駛技術特定的基礎架構挑戰。

② 在這裏工作你可以:

接觸自動駕駛系統的各個方面。

設計並實現滿足通用需求的單機和分佈式系統。

系統的保障自動駕駛技術的持續進步。

這是一個非常有意思的 team,裏面有很多有意思的工作,非常歡迎大家與我們一起來工作,推動整個自動駕駛的發展,謝謝大家!

歡迎關注DataFunTalk同名公眾號,收看第一手原創技術文章。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

Flutter 構建的 Mac 桌面應用上無法發出網絡?

在上一篇文章中我們分享了,如何開發桌面應用。在本章文章中,來解決一下為何在 Mac 中無法發出網絡情況的原因。

起因

事情​起因是這樣的:我總覺得寫一個 Demo 不足以體現我們開發同學的能力。直到最近,我發現了一個可以改善的小點,可幫助我們的測試同學提高測試效率。

大體情況就是在某天晚上,客戶端的一個小問題,需要進行驗證。但是呢,測試同學要做回歸測試,把所有的數據都重新創建了一遍。但是此過程用了很久,理論來說,應該十來分鐘搞定的事情。我就靜靜的在她身旁看着,內心捉急萬分,畢竟我想早點回家(睡覺,打遊戲,看電視)。最後晚上 11 點多打車回家,一路上我都在想,這個問題應該很快就能驗證好吧,為何要折騰這麼就呢?也許是流程真這麼長吧,那我們無法改變流程,那就做出能提升效率的工具吧。

次日,我把我的想法告訴了她,但是她說我們有自己的網頁工具,可以解決啊,只是現在沒時間創建腳本。隨後我擱置這個想法,但是這種提升效率的想法一直縈繞在我的心頭。

再然後,某天突然意識到,同樣是工具,大家都會使用順手好用的,幹嘛使用哪種傳統老土的工具呢?基於這一點,我開啟了新的探索。

我不僅要做客戶端的,還要做網頁的,更要做桌面應用(測試同學最關注)。我要體驗更好,不僅方便我自己測試,還要給所有的測試同學提供便利,減少加班是我的最終目的,哈哈哈。

想法有了,那就開始實施吧。之前遇到一個問題,一直沒解決,但是此刻必須解決了。那就是之前 Flutter 構建的 Mac 應用后,無法發出網絡請求。

在桌面應用無法發出請求?

添加 Dio 以後,會遇到這種情況:

提示,當前系統不支持該操作。

為何會有如此問題,在 Dio 上支持 Other,也就是支持 Mac ,Windows, Linux, 為何我們直接運行有錯誤呢?

具體原因

實際是因為 Mac 應用有沙箱限制,需要在對應文件中開啟即可。

<key>com.apple.security.network.client</key>
<true/>

添加以後,效果如下:

具體參考: https://github.com/google/flutter-desktop-embedding/issues/546

代碼示例:

import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// Sets a platform override for desktop to avoid exceptions. See
// https://flutter.dev/desktop#target-platform-override for more info.
void _enablePlatformOverrideForDesktop() {
  if (!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux)) {
    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  }
}

void main() {
  _enablePlatformOverrideForDesktop();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String requestData = "";

  void _incrementCounter() {
    setState(() {
      _counter++;
      getHttp();
    });
  }

  void getHttp() async {
    try {
      Response response = await Dio().get("http://www.gdky005.com");
      requestData = response.toString();
      print(requestData);
    } catch (e) {
      print(e);
      requestData = e.toString();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            Container(
              height: 400,
              child: ListView(
                children: <Widget>[
                  Text(
                    '$requestData',
                  )
                ],
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

netty源碼解析(4.0)-29 Future模式的實現

  Future模式是一個重要的異步併發模式,在JDK有實現。但JDK實現的Future模式功能比較簡單,使用起來比較複雜。Netty在JDK Future基礎上,加強了Future的能力,具體體現在:

  1. 更加簡單的結果返回方式。在JDK中,需要用戶自己實現Future對象的執行及返回結果。而在Netty中可以使用Promise簡單地調用方法返回結果。
  2. 更加靈活的結果處理方式。JDK中只提供了主動得到結果的get方法,要麼阻塞,要麼輪詢。Netty除了支持主動get方法外,還可以使用Listener被動監聽結果。
  3. 實現了進度監控。Netty提供了ProgressiveFuture、ProgressivePromise和GenericProgressiveFutureListener接口及其實現,支持對執行進程的監控。

  吹了那麼多牛,有一個關鍵問題還沒弄清楚:Future到底是幹嘛的?io.netty.util.concurrent.Future代碼的第一行註釋簡潔第回答了這個問題:Future就是異步操作的結果。這裏面有三個關鍵字:異步,操作,結果。首先,Future首先是一個“結果”;其次這個結果產生於一個“操作”,操作具體是什麼可以隨便定義;最後這個操作是”異步”執行的,這就意味着“操作”可能在另一個線程中併發執行,也可能隨後在同一個線程中執行,什麼時候產生結果是一件不確定的事。

  異步調用過程的一般過程是:調用方喚起一個異步操作,在接下來的某個恰當的時間點得到的異步操作操作的結果。要正確地完成上述步驟,需要解決以下幾個問題:

  • 怎樣維護這個調用狀態?
  • 如何獲取異步操作的結果?
  • 何時處理結果?

  io.netty.util.concurrent.DefaultPromise是Future的默認實現,以上三個問題的答案都能在這個類的代碼中找到。

 

DefaultPromise的派生體系

  下面是DefaultPromis及其父類,接口的聲明:

  public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> 

  public abstract class AbstractFuture<V> implements Future<V>

  public interface Promise<V> extends Future<V> 

  public interface Future<V> extends java.util.concurrent.Future<V> 

   可以看出,DefaultPromise派生自AbstractFuture類,並實現了Promise接口。抽象類型AbstractFuture派生自Future, 接口Promise派生自Future。Future派生自JDK的Future接口。

  和JDK的Future相比,Netty的Future接口增加一些自己的方法:

   /**
     當操作成功時返回true*/
    boolean isSuccess();

    /**
   只有當操作可以被取消時返回true
*/ boolean isCancellable(); /** 返回操作的異常*/ Throwable cause(); /** 添加一個監聽器到future。當操作完成(成功或失敗都算完成,此事isDone()返回true)時, 會通知這個監聽器。如果添加時操作已經完成,
   這個監聽器會立即被通知。
*/ Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener); /** 和上個方法一樣,可以同時添加多個監聽器*/ Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners); /** 刪除指定的監聽器, 如果這個監聽器還沒被通知的話。*/ Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener); /** 功能和上個方法一樣,可以同時刪除多個監聽器。*/ Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners); /** 同步等待直到操作完成。會被打斷。 */ Future<V> sync() throws InterruptedException; /**    同步等着知道操作完成。不會被打斷。 */ Future<V> syncUninterruptibly(); /** 同sync*/ Future<V> await() throws InterruptedException; /** 同synUniterruptibliy*/ Future<V> awaitUninterruptibly(); /** 等待,直到操作完成或超過指定的時間。會被打斷。*/ boolean await(long timeout, TimeUnit unit) throws InterruptedException; /** 同上*/ boolean await(long timeoutMillis) throws InterruptedException; /** 同上,不會被打斷。*/ boolean awaitUninterruptibly(long timeout, TimeUnit unit); /** 同上。*/ boolean awaitUninterruptibly(long timeoutMillis); /** 立即得到結果,不會阻塞。如果操作沒有完成或沒有成功,返回null*/ V getNow();

  Netty的Future最大的特點是增加了Listener被動接收任務完成通知,下面是兩個Listener接口的定義:

public interface GenericFutureListener<F extends Future<?>> extends EventListener {
    void operationComplete(F future) throws Exception;
}

public interface GenericProgressiveFutureListener<F extends ProgressiveFuture<?>> extends GenericFutureListener<F> {
    void operationProgressed(F future, long progress, long total) throws Exception;
}

  把一個listener添加到future之後。當異步操作完成之後,listener會被通知一次,同時會回調operationComplete方法。參數future是當前通知的future,這意味這,一個listener可以被添加到多個future中。

  當異步操作進度發送變化時,listener會被通知,同時會回調operationProgressed方法。progress是當前進度,total是總進度。progress==total表示操作完成。如果不知道何時完成操作progress=-1。

  Promise定義的方法:

    /**
    設置結果。把這個future設置為success,通知所有的listener,
  如果這個future已經是success或failed(操作已經完成),會拋出IllegalStateException
*/ Promise<V> setSuccess(V result); /**
同上。只有在操作沒有完成的時候才會生效,且會返回true */ boolean trySuccess(V result); /** 設置異常。把這個future設置為failed狀態,通知所有的listener.
如果這個future已經完成,會拋出IllegalStateException
*/ Promise<V> setFailure(Throwable cause); /** 同上。只有在操作沒有完成時才會生效,且返回ture */ boolean tryFailure(Throwable cause); /** 設置當前前future的操作不能被取消。這個future沒有完成且可以設置成功或這個future已經完成,返回true。否則返回false */ boolean setUncancellable();

 

DefaultPromise的設計

關鍵屬性

  volatile Object result;

  異步操作的結果。可以通過它的值知道當前future的狀態。

  final EventExecutor executor;

  通知listener的線程。

  Object listeners;

  維護添加到當前future的listener對象。

  short waiters;

  記錄當前真正等待結果的線程數量。

  boolean notifyingListeners;

  是否正在通知listener,防止多線程併發執行通知操作。

 

狀態管理

  future有4種狀態: 未完成, 未完成-不能取消,完成-成功,完成-失敗。使用isDone()判斷是否完成,它代碼如下:

1     @Override
2     public boolean isDone() {
3         return isDone0(result);
4     }
5 
6     private static boolean isDone0(Object result) {
7         return result != null && result != UNCANCELLABLE;
8     }

  第7行是判斷當前完成狀態的。result != null 且 result != UNCANCELLABLE,表示處於完成狀態。

  result默認是null, 此時future處於未完成狀態。可以使用setUncancellable方法把它設置成為完成-不能取消狀態。

1     @Override
2     public boolean setUncancellable() {
3         if (RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE)) {
4             return true;
5         }
6         Object result = this.result;
7         return !isDone0(result) || !isCancelled0(result);
8     }

  第3行,使用原子操作設置result的值,只有result==null時才能把result設置成UNCANCELLABLE。當result==UNCANCELLABLE時,不允許取消異步操作。

  使用isSuccess方法判斷future是否處於完成-成功狀態。

1     @Override
2     public boolean isSuccess() {
3         Object result = this.result;
4         return result != null && result != UNCANCELLABLE && !(result instanceof CauseHolder);
5     }

  第4行是完成-成功狀態result的取值:除null, UNCANCELLABLE和CauseHolder對象的任何值。

  只有滿足isDone() && !isSuccess()時,future處於完成失敗狀態,可以使用cause方法獲取異常。

  調用setSuccess和trySuccess方法,能夠把狀態轉換成完成-成功。

 1     @Override
 2     public Promise<V> setSuccess(V result) {
 3         if (setSuccess0(result)) {
 4             notifyListeners();
 5             return this;
 6         }
 7         throw new IllegalStateException("complete already: " + this);
 8     }
 9     
10     private boolean setSuccess0(V result) {
11         return setValue0(result == null ? SUCCESS : result);
12     }

  第3行嘗試把狀態設置成完成-成功狀態。如果可以,在第4行通知所有的listener。否則第7行拋出錯誤。第11行給出了成功的默認值SUCCESS。trySuccess少了第7行,不會拋出異常。

  調用setFailure和tryFailure方法,能夠包狀態轉換成完成-失敗狀態。

 1     @Override
 2     public Promise<V> setFailure(Throwable cause) {
 3         if (setFailure0(cause)) {
 4             notifyListeners();
 5             return this;
 6         }
 7         throw new IllegalStateException("complete already: " + this, cause);
 8     }
 9 
10     private boolean setFailure0(Throwable cause) {
11         return setValue0(new CauseHolder(checkNotNull(cause, "cause")));
12     }

  第3行嘗試把專題設置成完成-失敗狀態。如果可以,在第4行通知所有listener。否則在第7行拋出異常。第11行把異常包裝成CauseHolder對象。tryFailure少了第7行,不會拋出異常。

 

獲取異步操作的結果

  當異步操作完成時,調用Promise提供的setSuccess和trySuccess設置成功的結果,調用setFailure和tryFailure設置異常結果。不論什麼結果,都會使用setValue0方法保存到result屬性上。

1     private boolean setValue0(Object objResult) {
2         if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
3             RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
4             checkNotifyWaiters();
5             return true;
6         }
7         return false;
8     }

  第2,3行,使用原子操作設置result的值,只有result==null或result==UNCANCELLABLE時,才能設置成功。如果設置成功,在第4行喚醒所有等待中的線程。可以使用get方法得到result值。如果isSucess()==true, result的值是SUCCESS或異步操作的結果。否則result的值是CauseHolder對象,此時可以調用cause方法得到異常對象。

  使用get或cause,只有在異步操作完成后才能順利得到結果。可以使用listener,被動等待操作完成通知。

 

使用listener異步通知處理結果

  Future的listener是必須實現GenericFutureListener接口,調用方法可以在operationComplete方法中處理異步操作的結果。

  listeners屬性用來保存使用addListener,addListeners方法添加到future的listener。listeners可能使用一個GenericFutureListener對象,也可能是一個GenericFutureListener數組。所有添加listener方法都會調用addListener0方法添加listener。

1     private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
2         if (listeners == null) {
3             listeners = listener;
4         } else if (listeners instanceof DefaultFutureListeners) {
5             ((DefaultFutureListeners) listeners).add(listener);
6         } else {
7             listeners = new DefaultFutureListeners((GenericFutureListener<? extends Future<V>>) listeners, listener);
8         }
9     }

  這段代碼中使用了一個DefaultFutureListeners類,它內部維護了一個GenericFutureListener數組。

  當一次操作完成時,會調用notifyListeners方法通知listeners中所有的listener,並調用listener的operationComplete方法。只有當isDone()==true時才會調用notifyListeners方法。觸發點在下面的一些方法中:

  addListener, addListeners。

  setSuccess, trySuccess。

  setFailure, tryFailure。

  notifyListeners的代碼如下:

 1     private void notifyListeners() {
 2         EventExecutor executor = executor();
 3         if (executor.inEventLoop()) {
 4             final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
 5             final int stackDepth = threadLocals.futureListenerStackDepth();
 6             if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
 7                 threadLocals.setFutureListenerStackDepth(stackDepth + 1);
 8                 try {
 9                     notifyListenersNow();
10                 } finally {
11                     threadLocals.setFutureListenerStackDepth(stackDepth);
12                 }
13                 return;
14             }
15         }
16 
17         safeExecute(executor, new Runnable() {
18             @Override
19             public void run() {
20                 notifyListenersNow();
21             }
22         });
23     }

  這段代碼的作用是調用notifyListenersNow。如果當前線程就是executor的線程,在第9行直接調用notifyListenerNow,否則在第20行,把notifyListnerNow放在executor中執行。第4-7行和11行的作用是防止遞歸調用導致線程棧溢出,MAX_LISTENER_STACK_DEPTH就是listener遞歸調用的最大深度。

  notifyListenerNow的作用是,確保沒有併發執行notifyListener0或notifyListners0方法,且所有的listener只能被通知一次。

 1     private void notifyListenersNow() {
 2         Object listeners;
 3         synchronized (this) {
 4             // Only proceed if there are listeners to notify and we are not already notifying listeners.
 5             if (notifyingListeners || this.listeners == null) {
 6                 return;
 7             }
 8             notifyingListeners = true;
 9             listeners = this.listeners;
10             this.listeners = null;
11         }
12         for (;;) {
13             if (listeners instanceof DefaultFutureListeners) {
14                 notifyListeners0((DefaultFutureListeners) listeners);
15             } else {
16                 notifyListener0(this, (GenericFutureListener<? extends Future<V>>) listeners);
17             }
18             synchronized (this) {
19                 if (this.listeners == null) {
20                     // Nothing can throw from within this method, so setting notifyingListeners back to false does not
21                     // need to be in a finally block.
22                     notifyingListeners = false;
23                     return;
24                 }
25                 listeners = this.listeners;
26                 this.listeners = null;
27             }
28         }
29     }

  第3-11行的作用是防止多個線程併發執行11行之後的代碼。

  結合第5,9,10行可知, listeners中的所有listener只能被通知一次。

  13-17行,通知所有listeners。notifyListener0通知一個listener,notifyListeners0通知所有的listener。

  最後,18-27行,檢查在通知listeners的過程中,是否有新的listener被添加進來。如果有,25,26行得到所有新添加的listener並清空listeners屬性,13-17行繼續通知新添加的listener。否則,運行22,23行結束通知過程。

 1     private void notifyListeners0(DefaultFutureListeners listeners) {
 2         GenericFutureListener<?>[] a = listeners.listeners();
 3         int size = listeners.size();
 4         for (int i = 0; i < size; i ++) {
 5             notifyListener0(this, a[i]);
 6         }
 7     }
 8 
 9     @SuppressWarnings({ "unchecked", "rawtypes" })
10     private static void notifyListener0(Future future, GenericFutureListener l) {
11         try {
12             l.operationComplete(future);
13         } catch (Throwable t) {
14             logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
15         }
16     }

  1-7行,notifyListeners0對每個listener調用一次notifyListener0,參數是當前的future。

  10-16,調用listener的operationComplete方法,捕獲了所有的異常,確保接下來可以繼續通知下一個listener。

 

使用await機制同步等待結果

  可以使用一系列的await,awaitXXX方法同步等待結果。這些方法可以分為: 能被打斷的,不能被打斷的。一直等待的,有超時時間的。await0方法是最複雜的等待實現,所有帶超時時間的await方法都會調用它。

 1     private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException {
 2         if (isDone()) {
 3             return true;
 4         }
 5 
 6         if (timeoutNanos <= 0) {
 7             return isDone();
 8         }
 9 
10         if (interruptable && Thread.interrupted()) {
11             throw new InterruptedException(toString());
12         }
13 
14         checkDeadLock();
15 
16         long startTime = System.nanoTime();
17         long waitTime = timeoutNanos;
18         boolean interrupted = false;
19         try {
20             for (;;) {
21                 synchronized (this) {
22                     if (isDone()) {
23                         return true;
24                     }
25                     incWaiters();
26                     try {
27                         wait(waitTime / 1000000, (int) (waitTime % 1000000));
28                     } catch (InterruptedException e) {
29                         if (interruptable) {
30                             throw e;
31                         } else {
32                             interrupted = true;
33                         }
34                     } finally {
35                         decWaiters();
36                     }
37                 }
38                 if (isDone()) {
39                     return true;
40                 } else {
41                     waitTime = timeoutNanos - (System.nanoTime() - startTime);
42                     if (waitTime <= 0) {
43                         return isDone();
44                     }
45                 }
46             }
47         } finally {
48             if (interrupted) {
49                 Thread.currentThread().interrupt();
50             }
51         }
52     }

  這個方法返回的條件有: (1)isDone()==true;(2)允許被打斷(interrupted==true)的情況下被打斷;(3)已經超時。2-12行分別檢查了這3種情況。

  25,35行管理waiters屬性,這個屬性用來記錄當前正在等待的線程數。inWaiters方法正常情況下會把waiters加1,當檢查到waiters==Short.MAX_VALUE時會拋出異常,防止過多的線程等待。

  27行,調用wait等待,經歷waitTime后超時返回。在等待過程中,會被setValue0方法調用notifyAll喚醒。

  29-33行,處理被打斷的異常,如果運行被打斷,在30行拋出這個異常返回。

  38-45行,不論什麼原因線程被喚醒,檢查是否滿足返回條件,如果不滿足,繼續循環等待。

  沒有超時的wait方法實現要簡單一些,只需判讀返回條件(1)(2)。

 

跟蹤異步操作的執行進度

  如果想要跟蹤異步操作的執行進度,future需要換成DefaultProgressivePromise對象,listener需要換成GenericProgressiveFutureListener類型。DefaultProgressivePromise派生自DefaultPromise同時實現了ProgressivePromise接口。GenericProgressiveFutureListener接口派生自GenericFutureListener接口。

  ProgressivePromise定義了setProgress和tryProgress方法用來更新進度,是不是很眼熟,和Promise接口定義返回結果的方法很類似。

ProgressivePromise<V> setProgress(long progress, long total);
boolean tryProgress(long progress, long total);

  GenericProgressiveFutureListener定義了operationProgressed方法用來處理進度更新通知。

     void operationProgressed(F future, long progress, long total) throws Exception;

  

  DefaultProgressivePromise自己只實現了setProgress和tryProgress方法,其它都是復用了DefaultPromise的實現。

 1     @Override
 2     public ProgressivePromise<V> setProgress(long progress, long total) {
 3         if (total < 0) {
 4             // total unknown
 5             total = -1; // normalize
 6             if (progress < 0) {
 7                 throw new IllegalArgumentException("progress: " + progress + " (expected: >= 0)");
 8             }
 9         } else if (progress < 0 || progress > total) {
10             throw new IllegalArgumentException(
11                     "progress: " + progress + " (expected: 0 <= progress <= total (" + total + "))");
12         }
13 
14         if (isDone()) {
15             throw new IllegalStateException("complete already");
16         }
17 
18         notifyProgressiveListeners(progress, total);
19         return this;
20     }

  3-12行,檢查progress和total的合法性。

  14行,如isDone()==true,拋出異常。只有在操作還沒完成的是否更新進度才有意義。

  18行,調用notifyProgressiveListeners觸發進度更新通知,這個方法在DefaultPromise中實現。

  notifyProgressiveListeners實現了觸發進度更新通知的主要流程:

 1     void notifyProgressiveListeners(final long progress, final long total) {
 2         final Object listeners = progressiveListeners();
 3         if (listeners == null) {
 4             return;
 5         }
 6 
 7         final ProgressiveFuture<V> self = (ProgressiveFuture<V>) this;
 8 
 9         EventExecutor executor = executor();
10         if (executor.inEventLoop()) {
11             if (listeners instanceof GenericProgressiveFutureListener[]) {
12                 notifyProgressiveListeners0(
13                         self, (GenericProgressiveFutureListener<?>[]) listeners, progress, total);
14             } else {
15                 notifyProgressiveListener0(
16                         self, (GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners, progress, total);
17             }
18         } else {
19             if (listeners instanceof GenericProgressiveFutureListener[]) {
20                 final GenericProgressiveFutureListener<?>[] array =
21                         (GenericProgressiveFutureListener<?>[]) listeners;
22                 safeExecute(executor, new Runnable() {
23                     @Override
24                     public void run() {
25                         notifyProgressiveListeners0(self, array, progress, total);
26                     }
27                 });
28             } else {
29                 final GenericProgressiveFutureListener<ProgressiveFuture<V>> l =
30                         (GenericProgressiveFutureListener<ProgressiveFuture<V>>) listeners;
31                 safeExecute(executor, new Runnable() {
32                     @Override
33                     public void run() {
34                         notifyProgressiveListener0(self, l, progress, total);
35                     }
36                 });
37             }
38         }
39     }

  第3行,從listeners中選出GenericProgressiveFutureListener類型的listener。

  10-38行。調用notifyProgressiveListeners0, notifyProgressiveListener0通知進度跟新。11-17行,在當前線程中調用。

  19-37行,在executor中調用。notifyProgressiveListener0隻是簡單地調用listener的operationProgressed方法。notifyProgressiveListeners0是對每個listener調用一次notifyProgressiveListener0。

  和完成通知相比,進度更新通知要更加簡單。進度更新通知沒有處理併發問題,沒有處理棧溢出問題。

  

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!