分類
發燒車訊

Kafka needs no Keeper(關於KIP-500的討論)

寫在前面的

最近看了Kafka Summit上的這個分享,覺得名字很霸氣,標題直接沿用了。這個分享源於社區的,大體的意思今後Apache Kafka不再需要ZooKeeper。整個分享大約40幾分鐘。完整看下來感覺乾貨很多,這裏特意總結出來。如果你把這個分享看做是《三國志》的話,那麼姑且就把我的這篇看做是裴松之注吧:)

客戶端演進

首先,社區committer給出了Kafka Java客戶端移除ZooKeeper依賴的演進過程。下面兩張圖總結了0.8.x版本和0.11.x版本(是否真的是從0.11版本開始的變化並不重要)及以後的功能變遷:在Kafka 0.8時代,Kafka有3個客戶端,分別是Producer、Consumer和Admin Tool。其中Producer負責向Kafka寫消息,Consumer負責從Kafka讀消息,而Admin Tool執行各種運維任務,比如創建或刪除主題等。其中Consumer的位移數據保存在ZooKeeper上,因此Consumer端的位移提交和位移獲取操作都需要訪問ZooKeeper。另外Admin Tool執行運維操作也要訪問ZooKeeper,比如在對應的ZooKeeper znode上創建一個臨時節點,然後由預定義的Watch觸發相應的處理邏輯。

後面隨着Kafka的演進,社區引入了__consumer_offsets位移主題,同時定義了OffsetFetch和OffsetCommit等新的RPC協議,這樣Consumer的位移提交和位移獲取操作全部轉移到與位移主題進行交互,避免了對ZooKeeper的訪問。同時社區引入了新的運維工具AdminClient以及相應的CreateTopics、DeleteTopics、AlterConfigs等RPC協議,替換了原先的Admin Tool,這樣創建和刪除主題這樣的運維操作也完全移動Kafka這一端來做,就像下面右邊這張圖展示的:

至此, Kafka的3個客戶端基本上都不需要和ZooKeeper交互了。應該說移除ZooKeeper的工作完成了大部分,但依然還有一部分工作要在ZooKeeper的幫助下完成,即Consumer的Rebalance操作。在0.8時代,Consumer Group的管理是交由ZooKeeper完成的,包括組成員的管理和訂閱分區的分配。這個設計在新版Consumer中也得到了修正。全部的Group管理操作交由Kafka Broker端新引入的Coordinator組件來完成。要完成這些工作,Broker端新增了很多RPC協議,比如JoinGroup、SyncGroup、Heartbeat、LeaveGroup等。

  

此時,Kafka的Java客戶端除了AdminClient還有一點要依賴ZooKeeper之外,所有其他的組件全部擺脫了對ZooKeeper的依賴。

之後,社區引入了Kafka安全層,實現了對用戶的認證和授權。這個額外的安全層也是不需要訪問ZooKeeper的,因此之前依賴ZooKeeper的客戶端是無法“享用”這個安全層。一旦啟用,新版Clients都需要首先接入這一層並通過審核之後才能訪問到Broker,如下圖所示:

這麼做的好處在於統一了Clients訪問Broker的模式,即定義RPC協議,比如我們熟知的PRODUCE協議、FETCH協議、METADATA協議、CreateTopics協議等。如果後面需要實現更多的功能,社區只需要定義新的RPC協議即可。同時新引入的安全層負責對這套RPC協議進行安全校驗,統一了訪問模式。另外這些協議都是版本化的(versioned),因此能夠獨立地進行演進,同時也兼顧了兼容性方面的考量。

Broker間交互

說完了Clients端,我們說下Broker端的現狀。目前,應該說Kafka Broker端對ZooKeeper是重度依賴的,主要表現在以下幾個方面:

  • Broker註冊管理
  • ACL安全層配置管理
  • 動態參數管理
  • 副本ISR管理
  • Controller選舉

我們拿一張圖來說明,圖中有4個Broker節點和一個ZooKeeper,左上角的Broker充當Controller的角色。當前,所有的Broker啟動后都必須維持與ZooKeeper的會話。Kafka依賴於這個會話實現Broker端的註冊,而且Kafka集群中的所有配置信息、副本信息、主題信息也都保存在ZooKeeper上。最後Controller與集群中每個Broker都維持了一個TCP長連接用於向這些Broker發送RPC請求。當前的Controller RPC類型主要有3大類:

  • LeaderAndIsr:主要用於向集群廣播主題分區Leader和ISR的變更情況,比如對應的Broker應該是特定分區的Leader還是Follower
  • StopReplica:向集群廣播執行停止副本的命令
  • UpdateMetadata:向集群廣播執行變更元數據信息的命令

圖中還新增了一個AlterISR RPC,這是KIP-497要實現的新RPC協議。現階段Kafka各個主題的ISR信息全部保存在ZooKeeper中。如果後續要捨棄ZooKeeper,必須要將這些信息從ZooKeeper中移出來,放在了Controller一端來做。同時還要在程序層面支持對ISR的管理。因此社區計劃在KIP-497上增加AlterISR協議。對了,還要提一句,當前Controller的選舉也是依靠ZooKeeper完成的。

所以後面Broker端的演進可能和Clients端的路線差不多:首先是把Broker與ZooKeeper的交互全部幹掉,只讓Controller與ZooKeeper進行交互,而其他所有Broker都只與Controller交互,如下圖所示:

 

看上去這種演進路線社區已經走得輕車熟路了,但實際上還有遺留了一些問題需要解決。

Broker Liveness

首先就是Broker的liveness問題,即Kafka如何判斷一個Broker到底是否存活?在目前的設計中,Broker的生存性監測完全依賴於與ZooKeeper之間的會話。一旦會話超時或斷開Controller自動觸發ZooKeeper端的Watch來移除該Broker,並對其上的分區做善後處理。如果移除了ZooKeeper,Kafka應該採用什麼機制來判斷Broker的生存性是一個問題。

Network Partition

如何防範網絡分區也是一個需要討論的話題。當前可能出現的Network Partition有4種:1、單個Broker完全與集群隔離;2、Broker間無法通訊;3、Broker與ZooKeeper無法通訊;4、Broker與Controller無法通訊。下面4張圖分別展示了這4種情況:

 

我們分別討論下。首先是第一種情況,單Broker與集群其他Broker隔離,這其實並不算太嚴重的問題。當前的設計已然能夠保證很好地應對此種情況。一旦Broker被隔離,Controller會將其從集群中摘除,雖然可用性降低了,但是整個集群的一致性依然能夠得到保證。第二種情況是Broker間無法通訊,可能的後果是消息的備份機制無法執行,Kafka要收縮ISR,依然是可用性上的降低,但是一致性狀態並沒有被破壞。情況三是Broker無法與ZooKeeper通訊。Broker能正常運轉,它只是無法與ZooKeeper進行通訊。此時我們說該Broker處於殭屍狀態,即所謂的Zoobie狀態。因Zoobie狀態引入的一致性bug社區jira中一直沒有斷過,社區這幾年也一直在修正這方面的問題,主要對抗的機制就是fencing。比如leader epoch等。最後一類情況是Broker無法與Controller通訊,那麼所有的元數據更新通道被堵死,即使這個Broker依然是healthy的,但是它保存的元數據信息可能是非常過期的。這樣連接該Broker的客戶端可能會看到各種非常古怪的問題。之前在知乎上回答過類似的問題:4。目前,社區對這種情況並沒有太好的解決辦法,主要的原因是Broker的liveness完全交由ZooKeeper來做的。一旦Broker與ZooKeeper之間的交互沒有問題,其他原因導致的liveness問題就無法徹底規避。

第四類Network Partition引入了一個經典的場景:元數據不一致。目前每個Broker都緩存了一份集群的元數據信息,這份數據是異步更新的。當第四類Partition發生時,Broker端緩存的元數據信息必然與Controller的不同步,從而造成各種各樣的問題。

下面簡要介紹一下元數據更新的過程。主要的流程就是Controller啟動時會同步地從ZooKeeper上拉取集群全量的元數據信息,之後再以異步的方式同步給其他Broker。其他Broker與Controller之間的同步往往有一個時間差,也就是說可能Clients訪問的元數據並不是最新的。我個人認為現在社區很多flaky test failure都是因為這個原因導致的。 事實上,實際使用過程中有很多場景是Broker端的元數據與Controller端永遠不同步。通常情況下如果我們不重啟Broker的話,那麼這個Broker上的元數據將永遠“錯誤”下去。好在社區還給出了一個最後的“大招”: 登錄到ZooKeeper SHELL,手動執行rmr /controller,強迫Controller重選舉,然後重新加載元數據,並給所有Broker重刷一份。不過在實際生產環境,我懷疑是否有人真的要這麼干,畢竟代價不小,而且最關鍵的是這麼做依然可能存在兩個問題:1. 我們如何確保Controller和Broker的數據是一致的?2. 加載元數據的過程通常很慢。

這裏詳細說說第二點,即加載元數據的性能問題。總體來說,加載元數據是一個O(N)時間複雜度的過程,這裏的N就是你集群中總的分區數。考慮到Controller從ZooKeeper加載之後還要推給其他的Broker,那麼做這件事的總的時間複雜度就是O(N * M),其中M是集群中Broker的數量。可以想見,當M和N都很大時,在集群中廣播元數據不是一個很快的過程。

Metadata as an Event Log

Okay,鑒於以上所提到的所有問題,當Kafka拋棄了ZooKeeper之後,社區應該如何解決它們呢?總體的思路就是Metadata as an Event Log + Controller quorum。我們先說metadata as an event log。如果你讀過Jay Kreps的《I ️Logs》,你應該有感觸,整個Kafka的架構其實都是構建在Log上的。每個topic的分區本質上就是一個Commit Log,但元數據信息的保存卻不是Log形式。在現有的架構設計中你基本上可以認為元數據的數據結構是KV形式的。這一次,社區採用了與消息相同的數據保存方式,即將元數據作為Log的方式保存起來,如下錶所示:

 

這樣做的好處在於每次元數據的變更都被當做是一條消息保存在Log中,而這個Log可以被視作是一個普通的Kafka主題被備份到多台Broker上。Log的一個好處在於它有清晰的前後順序關係,即每個事件發生的時間是可以排序的,配合以恰當的處理邏輯,我們就能保證對元數據變更的處理是按照變更發生時間順序處理,不出現亂序的情形。另外Log機制還有一個好處是,在Broker間同步元數據時,我們可以選擇同步增量數據(delta),而非全量狀態。現在Kafka Broker間同步元數據都是全量狀態同步的。前面說過了,當集群分區數很大時,這個開銷是很可觀的。如果我們能夠只同步增量狀態,勢必能極大地降低同步成本。最後一個好處是,我們可以很容易地量化元數據同步的進度,因為對Log的消費有位移數據,因此通過監控Log Lag就能算出當前同步的進度或是落後的進度。

採用Log機制后,其他Broker像是一個普通的Consumer,從Controller拉取元數據變更消息或事件。由於每個Broker都是一個Consumer,所以它們會維護自己的消費位移,就像下面這張圖一樣:

 這種設計下,Controller所在的Broker必須要承擔起所有元數據topic的管理工作,包括創建topic、管理topic分區的leader以及為每個元數據變更創建相應的事件等。既然社區選擇和__consumer_offsets類似的處理方式,一個很自然的問題在於這個元數據topic的管理是否能夠復用Kafka現有的副本機制?答案是:不可行。理由是現有的副本機制依賴於Controller,因此Kafka沒法依靠現有的副本機制來實現Controller——按照我們的俗語來說,這有點雞生蛋、蛋生雞的問題,屬於典型的循環依賴。為了實現這個,Kafka需要一套leader選舉協議,而這套協議或算法是不依賴於Controller的,即它是一個自管理的集群quorum(抱歉,在分佈式領域內,特別是分佈式共識算法領域中,針對quorum的恰當翻譯我目前還未找到,因此直接使用quorum原詞了)。最終社區決定採用Raft來實現這組quorum。這就是上面我們提到的第二個解決思路:Controller quorum。

Controller Quorum

與藉助Controller幫忙選擇Leader不同,Raft是讓自己的節點自行選擇Leader並最終令所有節點達成共識——對選擇Controller而言,這是一個很好的特性。其實Kafka現有的備份機制與Raft已經很接近了,下錶羅列了一下它們的異同:

 一眼掃過去,其實Kafka的備份機制和Raft很類似,比如Kafka中的offset其實就是Raft中的index,epoch對應於term。當然Raft中採用的半數機制來確保消息被提交以及Leader選舉,而Kafka設計了ISR機制來實現這兩點。總體來說,社區認為只需要對備份機製做一些小改動就應該可以很容易地切換到Raft-based算法。

下面這張圖展示Controller quorum可能更加直觀:

整個controller quorum類似於一個小的集群。和ZooKeeper類似,這個quorum通常是3台或5台機器,不需要讓Kafka中的每個Broker都自動稱為這個quorum中的一個節點。該quorum裏面有一個Leader負責處理客戶端發來的讀寫請求,這個Leader就是Kafka中的active controller。根據ZooKeeper的Zab協議,leader處理所有的寫請求,而follower是可以處理讀請求的。當寫請求發送給follower后,follower會將該請求轉發給leader處理。不過我猜Kafka應該不會這樣實現,它應該只會讓leader(即active controller)處理所有的讀寫請求,而客戶端(也就是其他Broker)壓根就不會發送讀寫請求給follower。在這一點上,這種設計和現有的Kafka請求處理機制是一致的。

現在還需要解決一個問題,即Leader是怎麼被選出來的?既然是Raft-based,那麼採用的也是Raft算法中的Leader選舉策略。讓Raft選出的Leader稱為active controller。網上有很多關於Raft選主的文章,這裏就不在贅述了,有興趣的可以讀一讀Raft的論文:《In Search of an Understandable Consensus Algorithm(Extended Version)》。

這套Raft quorum的一個好處在於它天然提供了低延時的failover,因此leader的切換會非常的迅速和及時,因為理論上不再有元數據加載的過程了,所有的元數據現在都同步保存follower節點的內存中,它已經有其他Broker需要拉取的所有元數據信息了!更酷的是,它避免了現在機制中一旦Controller切換要全量拉取元數據的低效行為,Broker無需重新拉取之前已經“消費”的元數據變更消息,它只需要從新Leader繼續“消費”即可。

另一個好處在於:採用了這套機制后,Kafka可以做元數據的緩存了(metadata caching):即Broker能夠把元數據保存在磁盤上,同時就像剛才說的,Broker只需讀取它關心的那部分數據即可。還有,和現在snapshot機制類似,如果一個Broker保存的元數據落後Controller太多或者是一個全新的Broker,Kafka甚至可以像Raft那樣直接發送一個snapshot文件,快速令其追上進度。當然大多數情況下,Broker只需要拉取delta增量數據即可。

Post KIP-500 Broker註冊

當前Broker啟動之後會向ZooKeeper註冊自己的信息,比如自己的主機名、端口、監聽協議等數據。移除ZooKeeper之後,Broker的註冊機制也要發生變化:Broker需要向active controller發送心跳來進行註冊。Controller收集心跳中包含的Broker數據構建整個Kafka集群信息,如下圖所示:

 同時Controller也會對心跳進行響應,顯式地告知Broker它們是否被允許加入集群——如果不允許,則可能需要被隔離(fenced)。當然controller自己也可以對自己進行隔離。我們針對前面提到的隔離場景討論下KIP-500是怎麼應對的。

Fencing

首先是普通Broker與集群完全隔離的場景,比如該Broker無法與controller和其他Broker進行通信,但它依然可以和客戶端程序交互。此時,fencing機制就很簡單了,直接讓controller令其下線即可。這和現在依靠ZooKeeper會話機制維持Broker判活的機制是一模一樣的,沒有太大改進。

第二種情況是Broker間的通訊中斷。此時消息無法在leader、follower間進行備份。但是對於元數據而言,我們不會看到數據不一致的情形,因為Broker依然可以和controller通訊,因此也不會有什麼問題。

第三種情況是Broker與Controller的隔離。現有機制下這是個問題,但KIP-500之後,Controller僅僅將該Broker“踢出場”即可,不會造成元數據的不一致。

最後一種情況是Broker與ZooKeeper的隔離, 既然ZooKeeper要被移除了,自然這也不是問題了。

部署

終於聊到KIP-500之後的Kafka運維了。下錶總結了KIP-500前後的部署情況對比:

很簡單,現在任何時候部署和運維Kafka都要考慮對ZooKeeper的運維管理。在KIP-500之後我們只需要關心Kafka即可。

Controller quorum共享模式

如前所述,controller改成Raft quorum機制后,可能使用3或5台機器構成一個小的quorum。那麼一個很自然的問題是,這些Broker機器還能否用作他用,是唯一用作controller quorum還是和其他Broker一樣正常處理。社區對此也做了解釋:兩種都支持!

如果你的Kafka集群資源很緊張,你可以使用共享controller模式(Shared Controller Mode),即充當controller quorum的Broker機器也能處理普通的客戶端請求;相反地,如果你的Kafka資源很充足,專屬controller模式(Separate Controller Mode)可能是更適合的,即在controller quorum中的Broker機器排它地用作Controller的選舉之用,不再對客戶端提供讀寫服務。這樣可以實現更好的資源隔離,適用於大集群。

Roadmap

最後說一下KIP-500的計劃。社區計劃分三步走:

第一步是移除客戶端對ZooKeeper的依賴——這一步基本上已經完成了,除了目前AdminClient還有少量的API依賴ZooKeeper之外,其他客戶端應該說都不需要訪問ZooKeeper了;第二步是移除Broker端的ZooKeeper依賴:這主要包括移除Broker端需要訪問ZooKeeper的代碼,以及增加新的Broker端API,如前面所說的AlterISR等,最後是將對ZooKeeper的訪問全部集中在controller端;最後一步就是實現controller quorum,實現Raft-based的quorum負責controller的選舉。

至於Kafka升級,如果從現有的Kafka直接升級到KIP-500之後的Kafka會比較困難,因此社區打算引入一個名為Bridge Release的中間過渡版本,如下圖所示:

這個Bridge版本的特點在於所有對ZooKeeper的訪問都集中到了controller端,Broker訪問ZooKeeper的其他代碼都被移除了。 

總結

KIP-500應該說是最近幾年社區提出的最重磅的KIP改進了。它幾乎是顛覆了Kafka已有的使用模式,摒棄了之前重度依賴的Apache ZooKeeper。就我個人而言,我是很期待這個KIP,後續有最新消息我也會在一併同步出來。讓我們靜觀其變吧~~~

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

分類
發燒車訊

噴藥害死蜜蜂 奧地利果農遭判刑

摘錄自2018年9月26日中央社報導

奧地利一名果農因違法噴灑殺蟲劑,隸屬鄰近2個養蜂人超過50個蜂群因此死亡。26日奧地利克拉根福法院(Klagenfurt)以「蓄意危害環境」,判處果農1年有期徒刑,至少需服刑4個月才可假釋,以及賠償超過2萬歐元(2萬3500美元)。

這名47歲果農針對他位於奧地利卡林西亞省(Carinthia)拉萬特地區(Lavanttal)的果樹噴灑藥效強大的殺蟲劑陶斯松(chlorpyrifos),當時果樹的花仍會吸引蜜蜂前去。法院指出,以他的經驗和訓練他人的角色,足以證明他知道自身行為會帶來何種後果。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

分類
發燒車訊

Faraday Future:量產車續航會超過特斯拉20%-30%

知名科技網站The Verge的記者Andrew J . Hawkins近日造訪了拉斯維加斯,參加了超級高鐵Hyperloop的公開首秀。

Faraday Future全球製造副總裁Dag Reckhorn,就Faraday Future未來的計畫,公眾對那輛造型誇張怪異、最大輸出功率1000馬力的Zero1概念車的反應,以及對中國投資方樂視造車的看法,等等大家十分好奇的事情聊了聊。

關於Faraday Future要造的車

未來Faraday Future要造的汽車,都會採用“可變平臺架構”(Variable Platform Architecture,簡稱VPA),這樣方便不同車型的大規模量產。所以概念車階段我們一般都會想得天馬行空一些,而等到量產時,只要增加不同的配置(傳統系統、電池等)就好,完全不需要根據不同的車型再重新設計底盤平臺。目前只是不斷地通過反覆運算的工程樣車,來一步步逼近量產車型的模樣。現在還無法給出量產車型發佈的具體時間,因為要將成熟的產品投放市場,總不會是那麼一帆風順。

關於Faraday Future的工廠

工廠已經奠基了,將很快開建。那一大片地都是我們的,從選址到設計,也是破費周折。Hyperloop在我們工廠旁邊,但只有一小塊地,而我們從六位不同的買家手裡拿到了七塊地皮,足以想像整個工廠的建設得多複雜。說實話,要建這麼一座大工廠,實在不是件簡單事,敲定設計方案就花了我不少時間,不過總算搞定了。目前進行的是整個工廠框架的工程搭建工作,就快要完工了。所以我們很快就要開始工廠主體部分的建設了。

選擇內華達州的原因

內華達州對Faraday Future建廠而言,是再好不過的選擇。實際上,也有很多其他州拋出了更好的優惠政策和條件,但我們希望工廠建在能夠吸引志趣相投的夥伴的地方,靠近15號高速公路,尤其要靠近西海岸。所以要找到360多公頃大的這麼一塊地,並不是件容易事。此外,這裡配套基礎設施建設完善意味著新品能夠迅速推向市場,而且內華達州每年都有近4100萬遊客資源,我們只要吸引其中的一小部分到工廠參觀並體驗我們的產品,這絕對是極佳的行銷工具,我們完全可以將Faraday Future的工廠變成一處景點。只要有人願意來,他總歸是想看點不一樣的東西。而且這裡有朝北的陽光直射進來,綠色環保的太陽能可以保證能源持續不斷的供給。

稅收優惠

Faraday Future將為內華達州提供4500份就業機會,為工廠項目投資10億美元…而只是工廠建設及運營的工作,就會消耗掉內華達州近50%的勞動力。至於優惠政策嘛,說實話FF不是沖著這個來的。因為我個人認為,一個專案如果需要這些外界各種各樣的支撐才能做下去的話,那它本身是有問題的。

據瞭解,Faraday Future已經得到了內華達州政府2億1千950萬美元的稅收優惠。還有額外的1億2千萬美元將用於對該公司三處獨立的基礎設施進行功能性提升。

關於中國投資方樂視

樂視是Faraday Future的合作夥伴,而樂視的生態系統內包含了電影、app、音樂等等幾乎你想得到的任何業務,它都有。而對FF而言,這些領域的資源和技術支援是十分重要的。Faraday Future和樂視是兩家獨立運作的公司,只不過這位FF的創始人又恰恰是樂視的老總罷了。而當初成立Faraday Future的時候,賈總就表示只是以投資人的身份參與,不會干涉任何公司業務的運作。至於樂視前段時間推出的LeSEE超級汽車,我認為這是一款迎合了中國消費市場需求的車型…這裡還要重申一下,FF和樂視建立了緊密開放的合作關係,這目前已經不是什麼秘密了。而關於這個問題,目前我也只能說這麼多了。

續航里程

我們有自己的目標,不過現在還不到向公眾透露的時機。我只能說,未來Faraday Future量產車型的續航里程會比市場上同類競爭者多出20%~30%。

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

分類
發燒車訊

回聲消除中的LMS和NLMS算法與MATLAB實現

  自適應濾波是数字信號處理的核心技術之一,在科學和工業上有着廣泛的應用領域。自適應濾波技術應用廣泛,包括回波抵消、自適應均衡、自適應噪聲抵消和自適應波束形成。回聲對消是當今通信系統中普遍存在的現象。聲回波引起的信號干擾會分散用戶的注意力,降低通信質量。本文重點介紹了LMS和NLMS算法的使用,以減少這種不必要的回聲,從而提高通信質量

關鍵詞:自適應濾波器,自適應算法,回聲消除

1  引言

  當音頻信號在真實環境中產生混響時,就會產生聲學回聲,從而導致原始信號加上信號[1]的衰減、延時圖像。本文將重點研究通信系統中聲學回波的產生。

  自適應濾波器是一種動態濾波器,它不斷地改變其特性以獲得最優的輸出。自適應濾波算法通過改變參數使期望輸出d (n)與實際輸出y (n)之間的差值最小化。該函數稱為自適應算法的代價函數(loss)。圖1显示了自適應回聲抵消系統的框圖。其中,濾波器H(n)表示聲環境的脈衝響應,W(n)表示用來抵消回波信號的自適應濾波器。自適應濾波器的目標是使輸出的y(n)與期望的d(n)(在回聲環境中混響的信號)相等。在每次迭代中,誤差信號e(n)=d (n)-y (n)被反饋回濾波器,濾波器的特性也隨之改變。

自適應回聲消除系統

  自適應濾波器的目標是計算期望信號與自適應濾波器輸出之間的差值e(n)。該誤差信號反饋到自適應濾波器,並通過算法改變其係數,以最小化該差值的函數,即代價函數。在聲回波消除的情況下,自適應濾波器的最優輸出與不需要的回波信號等值。當自適應濾波器輸出等於期望信號時,誤差信號為零。在這種情況下,回顯信號將被完全取消,遠用戶將不會聽到他們的任何原始語音返回給他們。

2. 最小均方(LMS)算法

  最小均方(LMS)算法是由Widrow和Hoff在1959年通過對模式識別的研究首次提出的。由此成為自適應濾波中應用最廣泛的算法之一。LMS算法是一種基於隨機梯度的自適應濾波算法,它利用濾波器權重的梯度來收斂到最優的維納解[2-4]。由於其計算簡單而廣為人知並被廣泛使用。正是這種簡單性使它成為判斷所有其他自適應濾波算法的基準。

  隨着LMS算法的每次迭代,自適應濾波器的濾波抽頭(tap)權值按照如下公式進行更新。

$$公式1:w(n+1)=w(n)2\mu e(n)x(n)$$

  這裏x(n)是延時輸入值的輸入向量,$x(n)=[x_1(n)x_2(n)…x_N(n)]^T=[x(n)x(n-1)…x(n-N+1)]^T$。向量$w(n)=[w_0(n)w_1(n)w_2(n)…w_{N-1}(n)]^T$代表自適應FIR濾波器抽頭(tap)權向量在時刻n的係數。參數μ被稱為步長參數和小正的常數。此步長參數控制更新因子的影響。μ必須選擇一個合適的值LMS算法的性能,如果該值太小自適應濾波器的收斂時間會太長;如果μ太大自適應濾波器變得不穩定,導致其輸出發散[5 – 8]

2.1 LMS算法的實現

LMS算法的每次迭代都需要三個不同的步驟,順序如下:

1. FIR濾波器的輸出y(n)用公式2計算。

$$公式2:y(n)=\sum_{i=0}^{N-1}w(n)x(n-1)=w^T(n)x(n)$$

2. 誤差估計的值按公式3計算。

$$公式3:e(n)=d(n)-y(n)$$

3.更新FIR向量的抽頭tap權值,為下一次迭代做準備,如公式4所示。

$$公式4:w(n+1)=w(n)+2\mu e(n)x(n)$$

  LMS算法在自適應濾波中得到廣泛應用的主要原因是其計算簡單,比其他常用的自適應算法更易於實現。LMS算法每次迭代需要2N加法和2N + 1次乘法(N用於計算輸出y(N)),另一個用於通過向量乘法計算標量[9]

clear;
clc;
snr=20;     % 信噪比
order=8;    % 自適應濾波器的階數為8
Hn =[0.8783 -0.5806 0.6537 -0.3223 0.6577 -0.0582 0.2895 -0.2710 0.1278 ...     % ...表示換行的意思
    -0.1508 0.0238 -0.1814 0.2519 -0.0396 0.0423 -0.0152 0.1664 -0.0245 ...
    0.1463 -0.0770 0.1304 -0.0148 0.0054 -0.0381 0.0374 -0.0329 0.0313 ...
    -0.0253 0.0552 -0.0369 0.0479 -0.0073 0.0305 -0.0138 0.0152 -0.0012 ...
    0.0154 -0.0092 0.0177 -0.0161 0.0070 -0.0042 0.0051 -0.0131 0.0059 ...
    -0.0041 0.0077 -0.0034 0.0074 -0.0014 0.0025 -0.0056 0.0028 -0.0005 ...
    0.0033 -0.0000 0.0022 -0.0032 0.0012 -0.0020 0.0017 -0.0022 0.0004 -0.0011 0 0];
Hn=Hn(1:order);
mu=0.5;             % mu表示步長
N=1000;             % 橫坐標1000個採樣點
Loop=150;           % 150次循環
EE_NLMS=zeros(N,1); % 不同步長的初始化誤差
for nn=1:Loop       % epoch=150
    % 權重初始化w
    win_NLMS=zeros(1,order);         % NLMS四種步長測試,四個權重——1
    error_NLMS=zeros(1,N)';     % 初始化誤差
    % 均勻分佈的輸入值
    r=sign(rand(N,1)-0.5);          % shape=(1000,1)的(0,1)均勻分佈-0.5,sign(n)>0=1;<0=-1
    % 輸出:輸入卷積Hn得到 輸出
    output=conv(r,Hn);              % r卷積Hn,output長度=length(u)+length(v)-1
    output=awgn(output,snr,'measured');     % 將白高斯噪聲添加到信號中

    % N=1000,每個採樣點
    for i=order:N         % i=81000
      input=r(i:-1:i-order+1);  % 每次迭代取8個數據進行處理
      e_NLMS = output(i)-win_NLMS*input;
      win_NLMS=win_NLMS+e_NLMS*input'/(input'*input);   % NLMS更新權重
      error_NLMS(i)=error_NLMS(i)+e_NLMS^2;
    end
    
    EE_NLMS=EE_NLMS+error_NLMS;     % 把總誤差相加
end
% 對總誤差求平均值
error_NLMS=EE_NLMS/Loop;

figure;
error_NLMS=10*log10(error_NLMS(order:N));
plot(error_NLMS,'r');       % 紅色
axis tight;                 % 使用緊湊的坐標軸
legend('NLMS算法');           % 圖例
title('NLMS算法誤差曲線');     % 圖標題
xlabel('樣本');                     % x軸標籤
ylabel('誤差/dB');                  % y軸標籤
grid on;                            % 網格線

3 歸一化最小均方(NLMS)算法

  LMS算法的主要缺點之一是每次迭代都有一個固定的步長參數。這需要在開始自適應濾波操作之前了解輸入信號的統計信息。實際上,這是很難實現的。即使我們假設自適應回聲抵消系統的唯一輸入信號是語音,但仍有許多因素如信號輸入功率和振幅會影響其性能[10-12]

  歸一化最小均方算法(NLMS)是LMS算法的擴展,LMS算法通過計算最大步長值來繞過這個問題。步長值的計算公式如下

$$Step\ size = \frac{1}{dot\ product(input\ vector,\ input\ vector)}$$

這個步長與輸入向量x(n)的係數的瞬時值的總期望能量的倒數成正比。輸入樣本的期望能量之和也等於輸入向量與自身的點積,以及輸入向量自相關矩陣的跡R[13-15]。

$$公式5:tr[R]=\sum_{i=0}^{N-1}E[x^2(n-i)]\\ \quad\quad =E[\sum_{i=0}^{N-1}x^2(n-i)]$$

NLMS算法的遞歸公式如式6所示

$$公式6:w(n+1)=w(n)+\frac{1}{x^T(n)x(n)}e(n)x(n)$$

3.1 NLMS算法的實現

  NLMS算法已在Matlab中實現。由於步長參數是根據當前的輸入值來選擇的,因此NLMS算法在未知信號下具有更大的穩定性。該算法具有良好的收斂速度和相對簡單的計算能力,是實時自適應回波抵消系統[16]的理想算法

  由於NLMS是標準LMS算法的擴展,因此NLMS算法的實際實現與LMS算法非常相似。NLMS算法的每次迭代都需要按照以下順序執行這些步驟。

1. 計算了自適應濾波器的輸出

$$公式7:y(n)=\sum_{i=0}^{N-1}w(n)x(n-i)=w^T(n)x(n)$$

2. 誤差信號等於期望信號和濾波器輸出之間的差值。

$$公式8:e(n)=d(n)-y(n)$$

3.計算了輸入向量的步長值。

$$公式9:\mu(n)=\frac{1}{x^T(n)x(n)}$$

4. 濾波器抽頭權重更新,為下一次迭代做準備。

$$公式10:w(n+1)=w(n)+\mu(n)e(n)x(n)$$

NLMS算法的每次迭代都需要3N+1次乘法,僅比標準LMS算法多N次。考慮到所獲得的穩定性和回波衰減增益,這是一個可接受的增加。

clear;
clc;
snr=20;     % 信噪比
order=8;    % 自適應濾波器的階數為8
% Hn是濾波器權重
Hn =[0.8783 -0.5806 0.6537 -0.3223 0.6577 -0.0582 0.2895 -0.2710 0.1278 ...     % ...表示換行的意思
    -0.1508 0.0238 -0.1814 0.2519 -0.0396 0.0423 -0.0152 0.1664 -0.0245 ...
    0.1463 -0.0770 0.1304 -0.0148 0.0054 -0.0381 0.0374 -0.0329 0.0313 ...
    -0.0253 0.0552 -0.0369 0.0479 -0.0073 0.0305 -0.0138 0.0152 -0.0012 ...
    0.0154 -0.0092 0.0177 -0.0161 0.0070 -0.0042 0.0051 -0.0131 0.0059 ...
    -0.0041 0.0077 -0.0034 0.0074 -0.0014 0.0025 -0.0056 0.0028 -0.0005 ...
    0.0033 -0.0000 0.0022 -0.0032 0.0012 -0.0020 0.0017 -0.0022 0.0004 -0.0011 0 0];
Hn=Hn(1:order);
mu=0.5;             % mu表示步長
N=1000;             % 橫坐標1000個採樣點
Loop=150;           % 150次循環
% 不同步長的初始化誤差
EE_LMS = zeros(N,1);
EE_NLMS=zeros(N,1);
for nn=1:Loop       % epoch=150
    win_LMS = zeros(1,order);   % 權重初始化w
    error_LMS=zeros(1,N)';      % 初始化誤差
    % 均勻分佈的語音數據輸入
    r=sign(rand(N,1)-0.5);          % shape=(1000,1)的(0,1)均勻分佈-0.5,sign(n)>0=1;<0=-1
    % 輸出:輸入卷積Hn得到 輸出
    output=conv(r,Hn);              % r卷積Hn,output長度=length(u)+length(v)-1
    output=awgn(output,snr,'measured');     % 真實輸出=將白高斯噪聲添加到信號中

    % N=1000,每個採樣點
    for i=order:N         % i=81000
      input=r(i:-1:i-order+1);  % 每次迭代取8個數據進行處理
      e_LMS = output(i)-win_LMS*input;
      
      mu=0.02;      % 步長
      win_LMS = win_LMS+2*mu*e_LMS*input';
      error_LMS(i)=error_LMS(i)+e_LMS^2;
    end
    % 把總誤差相加
    EE_LMS = EE_LMS+error_LMS;

end
% 對總誤差求平均值
error_LMS = EE_LMS/Loop;

figure;
error1_LMS=10*log10(error_LMS(order:N));
plot(error1_LMS,'b.');  % 藍色
axis tight;         % 使用緊湊的坐標軸
legend('LMS算法');       % 圖例
title('LMS算法誤差曲線');  % 圖標題
xlabel('樣本');                     % x軸標籤
ylabel('誤差/dB');                  % y軸標籤
grid on;                            % 網格線

4 LMS算法的結果

  利用Matlab對LMS算法進行了仿真。圖2显示的是通過麥克風從計算機系統收集到的輸入語音信號。圖3显示了從輸入信號派生出的所需回波信號。圖4显示了自適應濾波器的輸出,它將減少輸入信號的回波信號。圖5显示了由濾波器輸出信號計算出的均方誤差信號。圖6是由回波信號對誤差信號的分割得到的衰減。

  自適應濾波器為1025階FIR濾波器。步長設置為0.02。MSE表明,隨着算法的發展,代價函數的平均值逐漸減小。

5 NLMS算法的結果

  用Matlab對NLMS算法進行了仿真。圖7显示了輸入信號。圖8显示了所需的信號。圖9显示了自適應濾波器輸出。圖10显示了均方誤差。圖11显示了衰減。

  自適應濾波器為1025階FIR濾波器。步長設置為0.1。

 

 NLMS算法在均方誤差和平均衰減方面優於LMS算法,其性能總結如表1所示。

 

6 結論

  由於其簡單性,LMS算法是最流行的自適應算法。然而,LMS算法存在收斂速度慢和數據依賴的問題。

  NLMS算法是LMS算法的一個同樣簡單但更健壯的變體,它在簡單性和性能之間表現出比LMS算法更好的平衡。由於其良好的性能,NLMS在實時應用中得到了廣泛的應用。

7. 參考

文章翻譯自論文《2011_adaptive algorithms for acoustic echo cancellation in speech processing》

[1]. Homana, I.; Topa, M.D.; Kirei, B.S.; “Echo cancelling using adaptive algorithms”, Design and Technology of Electronics Packages, (SIITME) 15th International Symposium., pp. 317-321, Sept.2009.

[2]. Paleologu, C.; Benesty, J.; Grant, S.L.; Osterwise, C.; “Variable step-size NLMS algorithms for echo cancellation” 2009 Conference Record of the forty-third Asilomar Conference on Signals, Systems and Computers., pp. 633-637, Nov 2009.

[3]. Soria, E.; Calpe, J.; Chambers, J.; Martinez, M.; Camps, G.; Guerrero, J.D.M.; “A novel approach to introducing adaptive filters based on the LMS algorithm and its variants”, IEEE Transactions, vol. 47, pp. 127-133, Feb 2008.

[4]. Tandon, A.; Ahmad, M.O.; Swamy, M.N.S.; “An efficient, low-complexity, normalized LMS algorithm for echo cancellation”, IEEE workshop on Circuits and Systems, 2004. NEWCAS 2004, pp. 161-164, June 2004.

[5]. Eneman, K.; Moonen, M.; “Iterated partitioned block frequency-domain adaptive filtering for acoustic echo cancellation,” IEEE Transactions on Speech and Audio Processing, vol. 11, pp. 143-158, March 2003.

[6]. Krishna, E.H.; Raghuram, M.; Madhav, K.V; Reddy, K.A; “Acoustic echo cancellation using a computationally efficient transform domain LMS adaptive filter,” 2010 10th International Conference on Information sciences signal processing and their applications (ISSPA), pp. 409-412, May 2010.

[7]. Lee, K.A.; Gan,W.S; “Improving convergence of the NLMS algorithm using constrained subband updates,” Signal Processing Letters IEEE, vol. 11, pp. 736-739, Sept. 2004.

[8]. S.C. Douglas, “Adaptive Filters Employing Partial Updates,” IEEE Trans.Circuits SYS.II, vol. 44, pp. 209-216, Mar 1997.

[9]. D.L. Duttweiler, “Proportionate Normalized Least Mean Square Adaptation in Echo Cancellers,” IEEE Trans. Speech Audio Processing, vol. 8, pp. 508-518, Sept. 2000.

[10]. E. Soria, J. Calpe, J. Guerrero, M. Martínez, and J. Espí, “An easy demonstration of the optimum value of the adaptation constant in the LMS algorithm,” IEEE Trans. Educ., vol. 41, pp. 83, Feb. 1998.

[11]. D. Morgan and S. Kratzer, “On a class of computationally efficient rapidly converging, generalized NLMS algorithms,” IEEE Signal Processing Lett., vol. 3, pp. 245–247, Aug. 1996.

[12]. G. Egelmeers, P. Sommen, and J. de Boer, “Realization of an acoustic echo canceller on a single DSP,” in Proc. Eur. Signal Processing Conf. (EUSIPCO96), Trieste, Italy, pp. 33–36, Sept. 1996.

[13]. J. Shynk, “Frequency-domain and multirate adaptive filtering,” IEEE Signal Processing Mag., vol. 9, pp. 15– 37, Jan. 1992.

[14]. Ahmed I. Sulyman and Azzedine Zerguine, “Echo Cancellation Using a Variable Step-Size NLMS Algorithm”, Electrical and Computer Engineering Department Queen’s University.

[15]. D. L. Duttweiler, “A twelve-channel digital echo canceller,” IEEE Trans. Commun., vol. 26, no. 5, pp. 647–653, May 1978.

[16]. J. Benesty, H. Rey, L. Rey Vega, and S. Tressens, “A nonparametric VSS NLMS algorithm,” IEEE Signal Process. Lett., vol. 13, pp. 581–584, Oct. 2006.

 

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

分類
發燒車訊

北汽規劃打造四大海外基地 南非新工廠將投產

據媒體報導,從北汽集團官方獲悉,北汽將斥資50億元在南非建設總裝 廠,該工廠擬於下月開始建設,計畫於2017年11月建成投產。按照規劃,北汽將打造包括南非、伊朗在內的四大海外運營基地並輻射周邊市場,形成四大屬地化產業運營集團。

未來北汽將海外市場拓展劃分了三條線,歸納為“一帶一路一洲 哥倫布航線”,其中“一帶一路”符合大環境下的海外發展趨勢,“一洲”則是“一帶一路”中涉及到的非洲,在此基礎上,北汽增加了“哥倫布航線”沿途的中南美地區。繼續細化,北汽將以“南非、伊朗、東南亞、墨西哥”四大重點專案為引領,建設輻射周邊市場的四大海外運營基地,逐步實現從“旅行者”到“定居者”的角色轉變。

與跨國車企在中國的發展要尋求本地車企進行合作類似,北汽在南非新建的工廠將由北汽集團與南非工業開發公司的合資企業負責運營。新工廠總投資金額達到50億元(7.73億美元),計畫於下月開始建設,有望於2017年11月建成投產,計畫年產能為10萬輛。

據介紹,南非工廠將作為試點,為後續北汽加速海外涉及12個國家的19個KD(散件組裝廠)專案建設積累經驗。北汽集團已於2013年在南非開設了一家小型SKD廠,位於斯普林斯鎮,該廠生產小型麵包計程車,此次在南非斥鉅資打造新工廠並作為試點也就不難理解。

北京汽車國際發展公司擁有五大核心業務,包括自主品牌整車和零部件產品的出口,技術、設備、整車的進口,此外還有產品改裝,境外投資以及國際合作,初期投放海外市場的產品也將以北汽自主品牌為主。這意味著即將建成的南非汽車生產廠將有望投產包括北京紳寶、北京牌和北汽威旺三大產品系列,初步形成對當地市場佈局的同時還將進行他國出口,擴大南非及周邊國家和地區市場份額。

在北汽擴大海外市場佈局後,未來市場銷量將成為企業諸多努力的體現。“十三五”期間北汽制定了“2030”戰略,戰略中指出企業要在2020年實現20萬輛整車出口,完成3個建設,包括國際化運營隊伍建設、合作夥伴隊伍建設、體系能力建設,從而實現品牌高端市場的突破。

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

分類
發燒車訊

數組與鏈表

前言

數組和鏈表是兩種數據結構,數組非常簡單易用但是它有兩個非常大的缺點,一個是數組一旦創建無法擴展,另一個則是數組的查找和刪除的速度很慢.

鏈表改善了一些數組的缺點,但是同樣的鏈表自身也存在一些自己的缺點.

本篇博客將為大家介紹一下這數組和鏈表特點及各自的優缺點.

閱讀前的準備工作

,一種粗略的評價計算機算法效率的方法.後面的內容會用到表示效率的方法.

1. 數組

我們按數組中的數組是否排序對數組進行劃分,將數組分為無序數組和有序數組.無序數組中的數組是無序的,而有序數組中的數據則是升序或者降序排序的.

1.1 無序數組

因為無序數組中的數據是無序的,往數組中添加數據時不用進行比較和移動數據,所以往無序數組裡面添加數據很快.無論是添加第一個數據還是第一萬個數據所需的時間是相同的,效率為O(1).

至於查找和刪除速度就沒有那麼快了,以數組中有一萬個數據項為例,最少需要比較1次,最多則需要比較一萬次,平均下來需要比較5000次,即N/2次比較,N代表數據量,大O表示法中常數可以忽略,所以效率為O(N).

結論:

  1. 插入很快,因為總是將數據插入到數組的空餘位置.
  2. 查找和刪除很慢,假設數組的長度為N,那麼平均的查找/刪除的比較次數為N/2,並且還需要移動數據.

1.2 有序數組

無序數組中存放的數據是無序的,有序數組裡面存放的數據則是有序的(有可能是升序有可能是降序).

因為有序數組中的數據是按升序/降序排列的,所以插入的時候需要進行排序並且移動數據項,所有有序數組的插入速度比無序數組慢. 效率為O(N).

刪除速度和無序數組一樣慢 效率為O(N).

有序數組的查找速度要比無序數組快,這是因為使用了一個叫做二分查找的算法.

二分查找: 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列.

有一個關於二分查找的形象類比 -> 猜數遊戲

假設要在0-100之間猜一個數,那麼你第一個要猜的数字就是100的一半50的時候,你的朋友會告訴你這個数字比要猜的数字是大還是小,如果比数字大,你接下來要猜的数字就是50的一半25,你的朋友說比這個数字要大,那麼你下面要猜的数字就是25-50中間的那個數37,以此類推…

使用二分查找可極大的提高查找的效率,假設一個有序數組有十億個數據,那麼查找到所需的数字,最多只需比較30次.

有序數組使用二分查找的效率為O(logN).有序數組也可以通過二分查找來新增和刪除數據以提高效率,但是依然需要在新增/刪除后移動數據項,所以效率依然會有影響.

總結:

  1. 有序數組的查找速度比無序數組高,效率為O(logN)
  2. 有序數組的刪除和新增速度很慢,效率為O(N)

1.3 數組總結

數組雖然簡單易用,但是數組有兩個致命的缺點:

  1. 數組存儲的數量有限,創建的過大浪費資源,創建的過小溢出
  2. 數組的效率比其他數據結構低
  • 無序數組插入效率為O(1)時間,但是查找花費O(N)時間
  • 有序數組查找花費O(logN)時間,插入花費O(N)時間
  • 刪除需要移動平均半數的數據項,所以刪除都是O(N)的時間

2. 鏈表

數組一經創建大小就固定住了,無法修改,鏈表在這方面做出了改善,只要內存夠用就可以無限制的擴大.

鏈表是繼數組之後應用最廣泛的數據結構.

2.1 鏈表的特點

鏈表為什麼叫鏈表呢? 因為它保存數據的方式就像一條鎖鏈

鏈表保存數據的方式很像上面的這一條鎖鏈,每一塊鎖鏈就是一個鏈節點,鏈節點保存着自己的數據同時通過自己的next()方法指向下一個鏈節點. 鏈表通過鏈節點不斷地調用next()方法就可以遍歷鏈表中的所有數據.

在鏈表中,每個數據項都被包含在”鏈節點”(link)中,一個鏈結點是某個類的對象,這個類可以叫做Link.因為一個鏈表中有許多類似的鏈結點,所以有必要用一個不同於鏈表的類來表達鏈結點.

每個Link對象中都包含一個對下一個鏈結點引用的字段(通常叫做next).

鏈表本身的對象中有一個字段指向對第一個鏈結點的引用.

數據與鏈表查找數據的區別: 在數組中查找數據就像在一個大倉庫裏面一樣,一號房間沒有,我們去二號房間,二號房間沒有我們去三號房間,以此類推.. 按照地址找完所有房間就可以了.

而在鏈表中查找數據就像單線彙報的地下工作者,你是孤狼你想要彙報點情報給你的頂級上司毒蜂,但是你必須先報告給你的接頭人豬剛鬣,豬剛鬣在報告給它的單線接頭人土行孫,最後由土行孫報告給毒蜂.只能一個找一個,這樣最終完成任務.

2.2 Java代碼

鏈節點類:


/**
 * @author liuboren
 * @Title: 鏈節點
 * @Description:
 * @date 2019/11/20 19:30
 */
public class Link {
    //  保存的數據
    public int data;

    // 指向的下一個鏈節點
    public Link nextLink;

    public Link(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Link getNextLink() {
        return nextLink;
    }

    public void setNextLink(Link nextLink) {
        this.nextLink = nextLink;
    }
}

鏈表類


/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;

    public LinkList() {
        first = null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link link = new Link(data);
        link.setNextLink(first);
        first = link;
    }
}

在新增節點的時候,新增的link的next方法指向原來的first節點,並將鏈表類的first指向新增的節點.

2.4 其他鏈表

剛剛介紹的鏈表是單向鏈表,只能從后往前遍歷,其他的鏈表還有雙端鏈表、雙向鏈表、有序鏈表.

再簡單介紹一下雙端鏈表吧.

雙端鏈表就是在單向鏈表的基礎上,新增一個成員變量指向鏈表的最後一個對象.

雙端鏈表代碼:

/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;
    private Link last;

    public LinkList() {
        first = null;
    }

    public boolean isEmpty() {
        return first == null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link newLink = new Link(data);
        newLink.setNextLink(first);
        if (isEmpty()) {
            last = newLink;
        }
        first = newLink;

    }
}

雙向鏈表則是可以從first和last兩個方向進行遍歷,有序鏈表的數據都是按照關鍵字的順序排列的,本文不再展開了.

2.5 鏈表的效率

鏈表的效率:

  • 表頭插入和刪除速度都很快,花費O(1)的時間.
  • 平均起來,查找&刪除&插入在制定鏈節點後面都需要搜索一半的鏈節點需要O(N)次比較,雖然數組也需要O(N)次比較,但是鏈表讓然要快一些,因為不需要移動數據(只需要改變他們的引用)

3. 總結

鏈表解決了數組大小不能擴展的問題,但是鏈表自身依然存在一些問題(在鏈表的鏈節點後面查找&刪除&插入的效率不高),那麼有沒有一種數據結構即擁有二者的優點又改善了二者的缺點呢,答案是肯定的,下篇博客將為您介紹這種優秀的數據結構,敬請期待.

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

分類
發燒車訊

QQ是怎樣創造出來的?——解密好友系統的設計

本篇介紹筆者接觸的第一個後台系統,從自身見聞出發,因此涉及的內容相對比較基礎,後台大牛請自覺略過。

什麼是好友系統?

簡單的說,好友系統是維護用戶好友關係的系統。我們最熟悉的好友系統案例當屬QQ,實際上QQ是一款即時通訊工具,憑着好友系統沉澱了海量的好友關係鏈,從而鑄就了一個堅不可摧的商業帝國。好友系統的重要性可見一斑。

熟悉互聯網產品的人都知道,當產品有了一定的用戶量,往往會開發一個好友系統。其主要目的是增加用戶粘性(有了好友就會常來)或者增加社區活躍度(有了好友就會多交流)。

而我的後台開發生涯就是從這樣一個系統開始的。

那時候,好友系統對於我們團隊大部分人來說,都是一個全新的事物,因為我們大部分人都是應屆生。整個系統的架構自然不是我們一群黃毛小孩所能創造。當年的架構圖已經找不到了,但是憑着一點記憶和多年來的經驗積累,還是可以把當年的架構勾勒出來。

 

如圖,好友系統的架構是常見的3層結構,包括接入層、邏輯層和數據層。

我們先從數據層講起。

因為我們對QQ太熟悉了,我們可以很容易地列出好友系統的數據主要包括用戶資料、好友關係鏈、消息(聊天消息和系統消息)、在線狀態等。

互聯網產品往往要面對海量的請求併發,傳統的關係型數據庫比較難滿足讀寫需求。在存儲中,一般是讀多寫少的數據才會使用MySQL等關係型數據庫,而且往往還需要增加緩存來保證性能;NoSQL(Not Only SQL)應該是目前的主流。

對於好友系統,用戶資料和好友關係鏈都使用了kv存儲,而消息使用公司自研的tlist(可以用redis的list替代),在線狀態下面再介紹。

接着是邏輯層

在這個系統中複雜度最高的應該是消息服務(而這個服務我並沒有參与開發[捂臉])。

消息服務中,消息按類型分為聊天消息和系統消息(系統消息包括加好友消息、全局tips推送等),按狀態分為在線消息和離線消息。在實現中,維護3種list:聊天消息、系統消息和離線消息。聊天消息是兩個用戶共享的,系統消息和離線消息每個用戶獨佔。當用戶在線時,聊天消息和系統消息是直接發送的;如果用戶離線,就把消息往離線消息list存入一份,等用戶再次登錄時拉取。

這樣看來,消息服務並不複雜?其實不然,系統設計中常規的流程設計往往是比較簡單的,但是對於互聯網產品,異常情況才是常態,當把各種異常情況都考慮進來時,系統就會非常複雜。

這個例子中,消息發送丟包是一種異常情況,怎麼保證在丟包情況下,還能正常運行就是一個不小的問題。

常見的解決方法是收包方回復確認包,發送方如果沒收到確認包就重發。但是確認包又可能丟包,那又可以給確認包增加一個確認包,這是一個永無止境的確認。

解決方法可以參考TCP的重傳機制。那問題來了,我們為什麼不用TCP呢?因為TCP還是比較慢的,聊天消息的可靠性沒有交易數據要求那麼高,丟幾條消息並不會造成嚴重後果,但是如果用戶每次發送消息后都要等很久才能被收到,那體驗是很差的。

一個比較折中的方案是,收包方回復確認包,如果發送方在一定時間內沒有收到確認就重發;如果收包方收到兩個相同的包(自定義seq一樣),去重即可。

一個面試題引發的討論:

面試時我常常會問候選人一個問題:在分佈式系統中怎樣實現一個用戶同時只能有一個終端在線(用戶在兩個地方先後登錄賬號,后一次登錄可以把前一次登錄踢下線)?這是互聯網產品中非常基礎的一個功能,考察的是候選人基本的架構設計能力。

設計要先從接入服務器(下稱接口機)說起。接口機是好友系統對外的窗口,主要功能是維護用戶連接、登錄鑒權、加解密數據和向後端服務透傳數據等。用戶連接好友系統,首先是連接到接口機,鑒權成功后,接口機會在內存中維護用戶session,後續的操作都是基於session進行。

如圖所示,用戶如果嘗試登錄兩次,接口機通過session就可以將第一次的登錄踢下線,從而保證只有一個終端在線。

問題解決了嗎?

沒有。因為實際系統肯定不會只有一台接口機,在多台接口的情況下,上面的方法就不可行了。因為每個接口機只能維護部分用戶的session,所以如果用戶先後連接到不同的接口機,就會造成用戶多處登錄的問題。

 

自然可以想到,解決的方法就是要維護一個用戶狀態的全局視圖。在我們的好友系統中,稱為在線狀態服務。

在線狀態服務,顧名思義就是維護用戶的在線狀態(登錄時間、接口機IP等)的服務。用戶登錄和退出會通過接口機觸發這裏的狀態變更。因為登錄包和退出包都可能丟包,所以心跳包也用作在線狀態維護(收到一次心跳標記為在線,收不到n次心跳標記為離線)。

一種常用的方法是,採用bitmap存儲在線狀態,具體是指在內存中分配一塊空間,32位機器上的自然數一共有4294967296個,如果用一個bit來表示一個用戶ID(例如QQ號),1代表在線,0代表離線,那麼把全部自然數存儲在內存只要4294967296 / (8 * 1024 * 1024) = 512MB(8bit = 1Byte)。當然,實現中也可以根據需要給每個用戶分配更多的bit。

於是,踢下線功能如圖所示。

 

用戶登錄的時候,接口機首先查找本機上是否有session,如果有則更新session,接着給在線狀態服務發送登錄包,在線狀態服務檢查用戶是否已經在線,如果在線則更新狀態信息,並向上次登錄的接口機IP發送踢下線包;接口機在收到踢下線包時會檢查包中的用戶ID是否存在session,如果存在則給客戶端發送踢下線包並刪除session。

在實際中,踢下線功能還有很多細節問題需要注意。

又回到用戶先後登錄同一台接口機的情況:

 

圖中踢下線流程是正確的,但是如果步驟10和13調換了順序(在UDP傳輸中是常見的)會發生什麼?大家可以自己推演一下,後到的踢下線包會把第二次登錄的A’踢下線了。這不是我們期望的。怎麼辦呢?

解決方法分幾個細節,①接口機在收到13號登錄成功包時,先將session A替換成session A’,然後給客戶端A發生踢下線包(避免多處存活導致互相踢下線);②踢下線包中必須包含除用戶ID外的其他標識信息,session的唯一標識應該是ID+XXX的形式(我最開始採用的是ID+LoginTime),XXX是為了區分某次的登錄;③接口機在收到踢下線包的時候只要判斷ID+XXX是否吻合來決定是否給客戶端發踢下線包。

現實情況,問題總是千奇百怪的,好在辦法總比問題多。

比如我在項目中遇到過接口機和在線狀態服務時間漂移(差幾秒)的情況。這樣踢下線的唯一標識就不能是用戶ID+LoginTime的形式了。可以為每次的登錄生成一個唯一的UUID解決。類似的問題還有很多,不再贅述。

總結一下,本篇主要介紹了好友系統的整體架構和部分模塊的實現方式。分佈式系統中各個模塊的實現其實並不難,難點主要在於應對複雜網絡環境帶來的問題(如丟包、時延等)和服務器異常帶來的問題(如為了應對服務器宕機會增加服務器冗餘度,進而又會引發其它問題)。

好友系統雖然簡單,但麻雀雖小五臟俱全,架構設計的各種技術基本都有涉及。例如分層結構、負載均衡、平行擴展、容災、服務發現、服務器開發框架等方面,後面我會在各個不同的項目中介紹這些技術,敬請期待。

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

分類
發燒車訊

Java編程思想——第14章 類型信息(二)反射

六、反射:運行時的類信息

  我們已經知道了,在編譯時,編譯器必須知道所有要通過RTTI來處理的類。而反射提供了一種機制——用來檢查可用的方法,並返回方法名。區別就在於RTTI是處理已知類的,而反射用於處理未知類。Class類與java.lang.reflect類庫一起對反射概念進行支持,該類庫包含Field、Method以及Constructor(每個類都實現了Member接口)。這些類型是由JVM運行時創建的,用來表示未知類種對應的成員。使用Constructor(構造函數)創建新的對象,用get(),set()方法讀取和修改與Field對象(字段)關聯的字段,用invoke()方法調用與Method對象(方法)關聯的方法。這樣,匿名對象的類信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。

  其實,當反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,在做其他事情之前必須先加載這個類的Class對象。因此,那個類的.class文件對於JVM來說必須時可獲取的(在本地或網絡上)所以反射與RTTI的區別只在於:對於RTTI來說,編譯器在編譯時打開和檢查.class文件,而對於反射來說,.class文件在編譯時是不可獲得的,所以是運行時打開和檢查.class文件。反射在需要創建更動態的代碼時很有用。

七、動態代理

  代理是基本的設計模式:為其他對象提供一種代理,以便控制對象,而在對象前或后加上自己想加的東西。

interface Interface {
    void doSomething();

    void doSomeOtherThing(String args);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomeOtherThing(String args) {
        System.out.println("doSomeOtherThing" + args);
    }
}

class SimpleProxy implements Interface {

    private Interface proxyId;

    public SimpleProxy(Interface proxyId) {
        this.proxyId = proxyId;
    }

    @Override
    public void doSomething() {
        //將原有的doSomething 方法添加上了一個輸出 這就是代理之後新增的東西
        //就好比某公司代理遊戲后加的內購
        System.out.println("SimpleProxy doSomething");
        proxyId.doSomething();
    }

    @Override
    public void doSomeOtherThing(String args) {
        proxyId.doSomeOtherThing(args);
        //新增的東西可以在原有之前或之後都行
        System.out.println("SimpleProxy doSomeOtherThing" + args);
    }
}

public class SimpleProxyDemo {
    static void consumer(Interface i) {
        i.doSomething();
        i.doSomeOtherThing(" yi gi woli giao");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("-----  -----  -----");
        consumer(new SimpleProxy(new RealObject()));
    }
}

結果:

doSomething
doSomeOtherThing yi gi woli giao
-----  -----  -----
SimpleProxy doSomething
doSomething
doSomeOtherThing yi gi woli giao
SimpleProxy doSomeOtherThing yi gi woli giao

  因為consumer()接受的Interface,所以無論是RealObject還是SimpleProxy,都可以作為參數,而SimpleProxy插了一腳 代理了RealObject加了不少自己的東西。

  java的動態代理更前進一步,因為它可以動態創建代理並動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型並確定相應的對策。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();

    void doSomeOtherThing(String args);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomeOtherThing(String args) {
        System.out.println("doSomeOtherThing" + args);
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxyId;

    public DynamicProxyHandler(Object proxyId) {
        this.proxyId = proxyId;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
        if (args != null) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxyId, args);
    }
}

public class SimpleProxyDemo {
    static void consumer(Interface i) {
        i.doSomething();
        i.doSomeOtherThing(" yi gi woli giao");
    }

    public static void main(String[] args) {
        RealObject realObject = new RealObject();
        consumer(realObject);
        System.out.println("-----  -----  -----");
     //動態代理 可以代理任何東西 Interface proxy
= (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(realObject)); consumer(proxy); } }

結果:

doSomething
doSomeOtherThing yi gi woli giao
-----  -----  -----
**** proxy:class $Proxy0, methodpublic abstract void Interface.doSomething(), args:null
doSomething
**** proxy:class $Proxy0, methodpublic abstract void Interface.doSomeOtherThing(java.lang.String), 
args:[Ljava.lang.Object;@7ea987ac  yi gi woli giao
doSomeOtherThing yi gi woli giao

通過Proxy.newProxyInstance()可以創建動態代理,這個方法需要三個參數:

1. 類加載器:可以從已經被加載的對象中獲取其類加載器;

2. 你希望該代理實現的接口列表(不可以是類或抽象類,只能是接口);

3. InvocationHandler接口的一個實現;

在 invoke 實現中還可以根據方法名處對不同的方法進行處理,比如:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
        if (args != null) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        if (method.getName().equals("doSomething")) { System.out.println("this is the proxy for doSomething"); } return method.invoke(proxyId, args);
    }

還可以對參數或方法進行更多的操作因為 你已經得到了他們 盡情的使用你的代理權吧 ~~ 先加它十個內購。

九、接口與類型信息

  interface關鍵字的一種重要目標就是允許程序員隔離構件,進而降低耦合。反射,可以調用所有方法,甚至是private。唯獨final是無法被修改的,運行時系統會在不拋任何異常的情況接受任何修改嘗試,但是實際上不會發生任何修改。

    void callMethod(Object a, String methodName) throws Exception {
        Method method = a.getClass().getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(a);
    }

 

 

  

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?