中國部分省市電動汽車充電服務費收費標準匯總

根據國家發改委下發的《關於電動汽車用電價格政策有關問題的通知》,充換電設施經營企業可向用戶收取電費和一定的充電服務費。政府希望通過這種方法吸引社會資本進入充電樁、充電站等設施的建設,甚至產生競爭,減少政府的投入。

以下是部分省市的充電服務收費標準:

北京

充電服務費按充電電量收取,每千瓦時收費上限標準為當日本市92號汽油每升最高零售價的15%。各經營單位可按照不超過上限標準,制定具體收費標準。

各充電設施經營單位應嚴格執行明碼標價規定,在經營場所的醒目位置標明相關的服務專案、服務內容、收費標準等事項。對擅自提高收費標準等價格違法行為,各級價格執法機構將依法予以查處。

上海

2020年前,電動汽車充電服務費執行政府指導價,暫定為每千瓦時不超過1.6元,試行一年,試行期滿後,結合《上海市鼓勵電動汽車充電設施發展暫行辦法》修訂並予以明確。今後將結合市場發展情況,逐步放開充電服務費,由市場競爭形成價格。

南京

由南京市人民政府辦公廳於2015年6月29日印發《南京市政府關於進一步支持新能源汽車推廣應用的若干意見》。

其中第11條,完善用電價格政策。落實新能源汽車充換電設施用電和充電服務價格政策。對向電網經營企業直接報裝接電的經營性集中式充電設施用電,執行大工業用電價格,暫免收基本電費;其他充電設施按其所在場所執行分類目錄電價。在國家、省相關政策出臺後,適時實施峰穀分時電價政策。

2020年前充換電服務費實行政府指導價管理,標準上限按不高於用油成本費用原則,具體標準由價格主管部門會同有關部門制定。目前南京的電動乘用車的充電收費標準為1.44元,公車收費為1.23元。

江西

2014年12月5日,由江西發展改革委員會下發的《江西省發展改革委關於電動汽車充換電服務費有關問題的通知》中明確了電動汽車充電的費用。

為便於充換電服務費計收和管理,提供充電服務模式的,按充電電度收取費用,包括電費和服務費用;提供換電服務模式的,按車輛行駛里程收取費用,包括電費、電池租賃和服務費用。

上限標準暫定為每千瓦時2.36元(含電費),其中電動公車(客車)上限標準暫定為每千瓦時1.36元(含電費)。

合肥

電動汽車充電價格由“電費服務費”組成,按電動汽車使用成本顯著低於燃油、低於燃氣汽車使用成本的原則核定。

其中對向電網經營企業直接報裝接電的經營性集中式充換電設施用電,執行大工業用電價格,2020年前,免收基本電費。其他充電設施按其所在場所執行分類目錄電價。電動汽車充換電設施用電執行峰穀分時電價政策。

直流快速充電樁充電服務費中准價格為0.90元/kwh,其中:集中向國家電網報裝並執行大工業電價可在上浮30%內協商確定,即最高1.17元/kwh,下降31%;

交流充電樁充電服務費在直流快速充電樁中准價格基礎上下浮30%,即0.63元/kwh,其中:利用電動汽車使用者單位場地等建設的充電樁,其充電服務費在不高於規定的交流充電樁充電服務費基礎上由雙方協商確定。

市物價局核定電動公車充電站充電服務費0.63元/kwh;充電電費執行大工業用電價格,免收基本電費;電動公車換電站換電服務價格按車輛行駛里程收取,含充電電費和充電服務費,換電服務價格為1.50元/公里。

青島

2015年5月15日,青島市物價局公佈了《山東省物價局轉發國家發展改革委關於電動汽車用電價格政策有關問題的通知》。

電動汽車充電服務費按“有傾斜、有優惠”的原則核定,確保電動汽車使用成本顯著低於燃油(或低於燃氣)汽車使用成本。電動汽車充電服務費按充電電度收取。其中,電動公車充電服務費最高為0.60元/千瓦時,電動乘用車充電服務費最高為0.65元/千瓦時,鼓勵充電設施建設運營單位在最高限價內給予用戶優惠。

河北

根據河北省物價局規定,河北省電動汽車充換電服務費標準實行政府指導價,並實行分類定價。類別暫劃分為純電動公車、七座及以下純電動乘用車、純電動環衛車。充換電服務費標準上限暫由省物價局制定,具體標準授權各設區市、擴權縣(市)價格主管部門按照“有傾斜、有優惠”,確保電動汽車使用成本顯著低於燃油(或燃氣)汽車使用成本原則確定,非經營性充換電設施,不得收取充換電服務費。

電動汽車充電服務費按充電電度收取,上限標準每千瓦時暫定為:純電動公車0.6元、七座及以下純電動乘用車和純電動環衛車1.6元。

電動汽車換電服務費按行駛里程收取,上限標準每公里暫定為:純電動公車1.8元、七座及以下純電動乘用車和純電動環衛車0.5元。

廣東

公車充電服務費0.8元/千瓦時,非公車充電服務費1.2元/千瓦時,充電時不再另收停車費用。

深圳

2015年12月28日,深圳市發改委發函稱“我市電動汽車的充電服務費標準繼續實行政府指導價管理,自2016年1月1日起,最高限價上調為每千瓦時1.00元(不區分車型)試行,允許下浮”。

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

詳解Kafka Producer

上一篇文章我們主要介紹了什麼是 Kafka,Kafka 的基本概念是什麼,Kafka 單機和集群版的搭建,以及對基本的配置文件進行了大致的介紹,還對 Kafka 的幾個主要角色進行了描述,我們知道,不管是把 Kafka 用作消息隊列、消息總線還是數據存儲平台來使用,最終是繞不過消息這個詞的,這也是 Kafka 最最核心的內容,Kafka 的消息從哪裡來?到哪裡去?都干什麼了?別著急,一步一步來,先說說 Kafka 的消息從哪來。

生產者概述

在 Kafka 中,我們把產生消息的那一方稱為生產者,比如我們經常回去淘寶購物,你打開淘寶的那一刻,你的登陸信息,登陸次數都會作為消息傳輸到 Kafka 後台,當你瀏覽購物的時候,你的瀏覽信息,你的搜索指數,你的購物愛好都會作為一個個消息傳遞給 Kafka 後台,然後淘寶會根據你的愛好做智能推薦,致使你的錢包從來都禁不住誘惑,那麼這些生產者產生的消息是怎麼傳到 Kafka 應用程序的呢?發送過程是怎麼樣的呢?

儘管消息的產生非常簡單,但是消息的發送過程還是比較複雜的,如圖

我們從創建一個ProducerRecord 對象開始,ProducerRecord 是 Kafka 中的一個核心類,它代表了一組 Kafka 需要發送的 key/value 鍵值對,它由記錄要發送到的主題名稱(Topic Name),可選的分區號(Partition Number)以及可選的鍵值對構成。

在發送 ProducerRecord 時,我們需要將鍵值對對象由序列化器轉換為字節數組,這樣它們才能夠在網絡上傳輸。然後消息到達了分區器。

如果發送過程中指定了有效的分區號,那麼在發送記錄時將使用該分區。如果發送過程中未指定分區,則將使用key 的 hash 函數映射指定一個分區。如果發送的過程中既沒有分區號也沒有,則將以循環的方式分配一個分區。選好分區后,生產者就知道向哪個主題和分區發送數據了。

ProducerRecord 還有關聯的時間戳,如果用戶沒有提供時間戳,那麼生產者將會在記錄中使用當前的時間作為時間戳。Kafka 最終使用的時間戳取決於 topic 主題配置的時間戳類型。

  • 如果將主題配置為使用 CreateTime,則生產者記錄中的時間戳將由 broker 使用。
  • 如果將主題配置為使用LogAppendTime,則生產者記錄中的時間戳在將消息添加到其日誌中時,將由 broker 重寫。

然後,這條消息被存放在一個記錄批次里,這個批次里的所有消息會被發送到相同的主題和分區上。由一個獨立的線程負責把它們發到 Kafka Broker 上。

Kafka Broker 在收到消息時會返回一個響應,如果寫入成功,會返回一個 RecordMetaData 對象,它包含了主題和分區信息,以及記錄在分區里的偏移量,上面兩種的時間戳類型也會返回給用戶。如果寫入失敗,會返回一個錯誤。生產者在收到錯誤之後會嘗試重新發送消息,幾次之後如果還是失敗的話,就返回錯誤消息。

創建 Kafka 生產者

要往 Kafka 寫入消息,首先需要創建一個生產者對象,並設置一些屬性。Kafka 生產者有3個必選的屬性

  • bootstrap.servers

該屬性指定 broker 的地址清單,地址的格式為 host:port。清單里不需要包含所有的 broker 地址,生產者會從給定的 broker 里查找到其他的 broker 信息。不過建議至少要提供兩個 broker 信息,一旦其中一個宕機,生產者仍然能夠連接到集群上。

  • key.serializer

broker 需要接收到序列化之後的 key/value值,所以生產者發送的消息需要經過序列化之後才傳遞給 Kafka Broker。生產者需要知道採用何種方式把 Java 對象轉換為字節數組。key.serializer 必須被設置為一個實現了org.apache.kafka.common.serialization.Serializer 接口的類,生產者會使用這個類把鍵對象序列化為字節數組。這裏拓展一下 Serializer 類

Serializer 是一個接口,它表示類將會採用何種方式序列化,它的作用是把對象轉換為字節,實現了 Serializer 接口的類主要有 ByteArraySerializerStringSerializerIntegerSerializer ,其中 ByteArraySerialize 是 Kafka 默認使用的序列化器,其他的序列化器還有很多,你可以通過 查看其他序列化器。要注意的一點:key.serializer 是必須要設置的,即使你打算只發送值的內容

  • value.serializer

與 key.serializer 一樣,value.serializer 指定的類會將值序列化。

下面代碼演示了如何創建一個 Kafka 生產者,這裏只指定了必要的屬性,其他使用默認的配置

private Properties properties = new Properties();
properties.put("bootstrap.servers","broker1:9092,broker2:9092");
properties.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties = new KafkaProducer<String,String>(properties);

來解釋一下這段代碼

  • 首先創建了一個 Properties 對象
  • 使用 StringSerializer 序列化器序列化 key / value 鍵值對
  • 在這裏我們創建了一個新的生產者對象,併為鍵值設置了恰當的類型,然後把 Properties 對象傳遞給他。

實例化生產者對象后,接下來就可以開始發送消息了,發送消息主要由下面幾種方式

直接發送,不考慮結果

使用這種發送方式,不會關心消息是否到達,會丟失一些消息,因為 Kafka 是高可用的,生產者會自動嘗試重發,這種發送方式和 UDP 運輸層協議很相似。

同步發送

同步發送仍然使用 send() 方法發送消息,它會返回一個 Future 對象,調用 get() 方法進行等待,就可以知道消息時候否發送成功。

異步發送

異步發送指的是我們調用 send() 方法,並制定一個回調函數,服務器在返迴響應時調用該函數。

下一節我們會重新討論這三種實現。

向 Kafka 發送消息

簡單消息發送

Kafka 最簡單的消息發送如下:

ProducerRecord<String,String> record =
                new ProducerRecord<String, String>("CustomerCountry","West","France");

producer.send(record);

代碼中生產者(producer)的 send() 方法需要把 ProducerRecord 的對象作為參數進行發送,ProducerRecord 有很多構造函數,這個我們下面討論,這裏調用的是

public ProducerRecord(String topic, K key, V value) {}

這個構造函數,需要傳遞的是 topic主題,key 和 value。

把對應的參數傳遞完成后,生產者調用 send() 方法發送消息(ProducerRecord對象)。我們可以從生產者的架構圖中看出,消息是先被寫入分區中的緩衝區中,然後分批次發送給 Kafka Broker。

發送成功后,send() 方法會返回一個 Future(java.util.concurrent) 對象,Future 對象的類型是 RecordMetadata 類型,我們上面這段代碼沒有考慮返回值,所以沒有生成對應的 Future 對象,所以沒有辦法知道消息是否發送成功。如果不是很重要的信息或者對結果不會產生影響的信息,可以使用這種方式進行發送。

我們可以忽略發送消息時可能發生的錯誤或者在服務器端可能發生的錯誤,但在消息發送之前,生產者還可能發生其他的異常。這些異常有可能是 SerializationException(序列化失敗)BufferedExhaustedException 或 TimeoutException(說明緩衝區已滿),又或是 InterruptedException(說明發送線程被中斷)

同步發送消息

第二種消息發送機制如下所示

ProducerRecord<String,String> record =
                new ProducerRecord<String, String>("CustomerCountry","West","France");

try{
  RecordMetadata recordMetadata = producer.send(record).get();
}catch(Exception e){
  e.printStackTrace();
}

這種發送消息的方式較上面的發送方式有了改進,首先調用 send() 方法,然後再調用 get() 方法等待 Kafka 響應。如果服務器返回錯誤,get() 方法會拋出異常,如果沒有發生錯誤,我們會得到 RecordMetadata 對象,可以用它來查看消息記錄。

生產者(KafkaProducer)在發送的過程中會出現兩類錯誤:其中一類是重試錯誤,這類錯誤可以通過重發消息來解決。比如連接的錯誤,可以通過再次建立連接來解決;無錯誤則可以通過重新為分區選舉首領來解決。KafkaProducer 被配置為自動重試,如果多次重試后仍無法解決問題,則會拋出重試異常。另一類錯誤是無法通過重試來解決的,比如消息過大對於這類錯誤,KafkaProducer 不會進行重試,直接拋出異常。

異步發送消息

同步發送消息都有個問題,那就是同一時間只能有一個消息在發送,這會造成許多消息無法直接發送,造成消息滯后,無法發揮效益最大化。

比如消息在應用程序和 Kafka 集群之間一個來回需要 10ms。如果發送完每個消息后都等待響應的話,那麼發送100個消息需要 1 秒,但是如果是異步方式的話,發送 100 條消息所需要的時間就會少很多很多。大多數時候,雖然Kafka 會返回 RecordMetadata 消息,但是我們並不需要等待響應。

為了在異步發送消息的同時能夠對異常情況進行處理,生產者提供了回掉支持。下面是回調的一個例子

ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>("CustomerCountry", "Huston", "America");
        producer.send(producerRecord,new DemoProducerCallBack());


class DemoProducerCallBack implements Callback {

  public void onCompletion(RecordMetadata metadata, Exception exception) {
    if(exception != null){
      exception.printStackTrace();;
    }
  }
}

首先實現回調需要定義一個實現了org.apache.kafka.clients.producer.Callback的類,這個接口只有一個 onCompletion方法。如果 kafka 返回一個錯誤,onCompletion 方法會拋出一個非空(non null)異常,這裏我們只是簡單的把它打印出來,如果是生產環境需要更詳細的處理,然後在 send() 方法發送的時候傳遞一個 Callback 回調的對象。

生產者分區機制

Kafka 對於數據的讀寫是以分區為粒度的,分區可以分佈在多個主機(Broker)中,這樣每個節點能夠實現獨立的數據寫入和讀取,並且能夠通過增加新的節點來增加 Kafka 集群的吞吐量,通過分區部署在多個 Broker 來實現負載均衡的效果。

上面我們介紹了生產者的發送方式有三種:不管結果如何直接發送發送並返回結果發送並回調。由於消息是存在主題(topic)的分區(partition)中的,所以當 Producer 生產者發送產生一條消息發給 topic 的時候,你如何判斷這條消息會存在哪個分區中呢?

這其實就設計到 Kafka 的分區機制了。

分區策略

Kafka 的分區策略指的就是將生產者發送到哪個分區的算法。Kafka 為我們提供了默認的分區策略,同時它也支持你自定義分區策略。

如果要自定義分區策略的話,你需要显示配置生產者端的參數 Partitioner.class,我們可以看一下這個類它位於 org.apache.kafka.clients.producer 包下

public interface Partitioner extends Configurable, Closeable {
  
  public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);

  public void close();
  
  default public void onNewBatch(String topic, Cluster cluster, int prevPartition) {}
}

Partitioner 類有三個方法,分別來解釋一下

  • partition(): 這個類有幾個參數: topic,表示需要傳遞的主題;key 表示消息中的鍵值;keyBytes表示分區中序列化過後的key,byte數組的形式傳遞;value 表示消息的 value 值;valueBytes 表示分區中序列化后的值數組;cluster表示當前集群的原數據。Kafka 給你這麼多信息,就是希望讓你能夠充分地利用這些信息對消息進行分區,計算出它要被發送到哪個分區中。
  • close() : 繼承了 Closeable 接口能夠實現 close() 方法,在分區關閉時調用。
  • onNewBatch(): 表示通知分區程序用來創建新的批次

其中與分區策略息息相關的就是 partition() 方法了,分區策略有下面這幾種

順序輪訓

順序分配,消息是均勻的分配給每個 partition,即每個分區存儲一次消息。就像下面這樣

上圖表示的就是輪訓策略,輪訓策略是 Kafka Producer 提供的默認策略,如果你不使用指定的輪訓策略的話,Kafka 默認會使用順序輪訓策略的方式。

隨機輪訓

隨機輪訓簡而言之就是隨機的向 partition 中保存消息,如下圖所示

實現隨機分配的代碼只需要兩行,如下

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());

先計算出該主題總的分區數,然後隨機地返回一個小於它的正整數。

本質上看隨機策略也是力求將數據均勻地打散到各個分區,但從實際表現來看,它要遜於輪詢策略,所以如果追求數據的均勻分佈,還是使用輪詢策略比較好。事實上,隨機策略是老版本生產者使用的分區策略,在新版本中已經改為輪詢了。

按照 key 進行消息保存

這個策略也叫做 key-ordering 策略,Kafka 中每條消息都會有自己的key,一旦消息被定義了 Key,那麼你就可以保證同一個 Key 的所有消息都進入到相同的分區裏面,由於每個分區下的消息處理都是有順序的,故這個策略被稱為按消息鍵保序策略,如下圖所示

實現這個策略的 partition 方法同樣簡單,只需要下面兩行代碼即可:

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return Math.abs(key.hashCode()) % partitions.size();

上面這幾種分區策略都是比較基礎的策略,除此之外,你還可以自定義分區策略。

生產者壓縮機制

壓縮一詞簡單來講就是一種互換思想,它是一種經典的用 CPU 時間去換磁盤空間或者 I/O 傳輸量的思想,希望以較小的 CPU 開銷帶來更少的磁盤佔用或更少的網絡 I/O 傳輸。如果你還不了解的話我希望你先讀完這篇文章 ,然後你就明白壓縮是怎麼回事了。

Kafka 壓縮是什麼

Kafka 的消息分為兩層:消息集合 和 消息。一個消息集合中包含若干條日誌項,而日誌項才是真正封裝消息的地方。Kafka 底層的消息日誌由一系列消息集合日誌項組成。Kafka 通常不會直接操作具體的一條條消息,它總是在消息集合這個層面上進行寫入操作。

在 Kafka 中,壓縮會發生在兩個地方:Kafka Producer 和 Kafka Consumer,為什麼啟用壓縮?說白了就是消息太大,需要變小一點 來使消息發的更快一些。

Kafka Producer 中使用 compression.type 來開啟壓縮

private Properties properties = new Properties();
properties.put("bootstrap.servers","192.168.1.9:9092");
properties.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties.put("compression.type", "gzip");

Producer<String,String> producer = new KafkaProducer<String, String>(properties);

ProducerRecord<String,String> record =
  new ProducerRecord<String, String>("CustomerCountry","Precision Products","France");

上面代碼錶明該 Producer 的壓縮算法使用的是 GZIP

有壓縮必有解壓縮,Producer 使用壓縮算法壓縮消息后併發送給服務器后,由 Consumer 消費者進行解壓縮,因為採用的何種壓縮算法是隨着 key、value 一起發送過去的,所以消費者知道採用何種壓縮算法。

Kafka 重要參數配置

在上一篇文章 中,我們主要介紹了一下 kafka 集群搭建的參數,本篇文章我們來介紹一下 Kafka 生產者重要的配置,生產者有很多可配置的參數,在文檔里(

key.serializer

用於 key 鍵的序列化,它實現了 org.apache.kafka.common.serialization.Serializer 接口

value.serializer

用於 value 值的序列化,實現了 org.apache.kafka.common.serialization.Serializer 接口

acks

acks 參數指定了要有多少個分區副本接收消息,生產者才認為消息是寫入成功的。此參數對消息丟失的影響較大

  • 如果 acks = 0,就表示生產者也不知道自己產生的消息是否被服務器接收了,它才知道它寫成功了。如果發送的途中產生了錯誤,生產者也不知道,它也比較懵逼,因為沒有返回任何消息。這就類似於 UDP 的運輸層協議,只管發,服務器接受不接受它也不關心。
  • 如果 acks = 1,只要集群的 Leader 接收到消息,就會給生產者返回一條消息,告訴它寫入成功。如果發送途中造成了網絡異常或者 Leader 還沒選舉出來等其他情況導致消息寫入失敗,生產者會受到錯誤消息,這時候生產者往往會再次重發數據。因為消息的發送也分為 同步異步,Kafka 為了保證消息的高效傳輸會決定是同步發送還是異步發送。如果讓客戶端等待服務器的響應(通過調用 Future 中的 get() 方法),顯然會增加延遲,如果客戶端使用回調,就會解決這個問題。
  • 如果 acks = all,這種情況下是只有當所有參与複製的節點都收到消息時,生產者才會接收到一個來自服務器的消息。不過,它的延遲比 acks =1 時更高,因為我們要等待不只一個服務器節點接收消息。

buffer.memory

此參數用來設置生產者內存緩衝區的大小,生產者用它緩衝要發送到服務器的消息。如果應用程序發送消息的速度超過發送到服務器的速度,會導致生產者空間不足。這個時候,send() 方法調用要麼被阻塞,要麼拋出異常,具體取決於 block.on.buffer.null 參數的設置。

compression.type

此參數來表示生產者啟用何種壓縮算法,默認情況下,消息發送時不會被壓縮。該參數可以設置為 snappy、gzip 和 lz4,它指定了消息發送給 broker 之前使用哪一種壓縮算法進行壓縮。下面是各壓縮算法的對比

retries

生產者從服務器收到的錯誤有可能是臨時性的錯誤(比如分區找不到首領),在這種情況下,reteis 參數的值決定了生產者可以重發的消息次數,如果達到這個次數,生產者會放棄重試並返回錯誤。默認情況下,生產者在每次重試之間等待 100ms,這個等待參數可以通過 retry.backoff.ms 進行修改。

batch.size

當有多個消息需要被發送到同一個分區時,生產者會把它們放在同一個批次里。該參數指定了一個批次可以使用的內存大小,按照字節數計算。當批次被填滿,批次里的所有消息會被發送出去。不過生產者井不一定都會等到批次被填滿才發送,任意條數的消息都可能被發送。

client.id

此參數可以是任意的字符串,服務器會用它來識別消息的來源,一般配置在日誌里

max.in.flight.requests.per.connection

此參數指定了生產者在收到服務器響應之前可以發送多少消息,它的值越高,就會佔用越多的內存,不過也會提高吞吐量。把它設為1 可以保證消息是按照發送的順序寫入服務器。

timeout.ms、request.timeout.ms 和 metadata.fetch.timeout.ms

request.timeout.ms 指定了生產者在發送數據時等待服務器返回的響應時間,metadata.fetch.timeout.ms 指定了生產者在獲取元數據(比如目標分區的首領是誰)時等待服務器返迴響應的時間。如果等待時間超時,生產者要麼重試發送數據,要麼返回一個錯誤。timeout.ms 指定了 broker 等待同步副本返回消息確認的時間,與 asks 的配置相匹配—-如果在指定時間內沒有收到同步副本的確認,那麼 broker 就會返回一個錯誤。

max.block.ms

此參數指定了在調用 send() 方法或使用 partitionFor() 方法獲取元數據時生產者的阻塞時間當生產者的發送緩衝區已捕,或者沒有可用的元數據時,這些方法就會阻塞。在阻塞時間達到 max.block.ms 時,生產者會拋出超時異常。

max.request.size

該參數用於控制生產者發送的請求大小。它可以指能發送的單個消息的最大值,也可以指單個請求里所有消息的總大小。

receive.buffer.bytes 和 send.buffer.bytes

Kafka 是基於 TCP 實現的,為了保證可靠的消息傳輸,這兩個參數分別指定了 TCP Socket 接收和發送數據包的緩衝區的大小。如果它們被設置為 -1,就使用操作系統的默認值。如果生產者或消費者與 broker 處於不同的數據中心,那麼可以適當增大這些值。

文章參考:

《Kafka 權威指南》

極客時間 -《Kafka 核心技術與實戰》

Kafka 源碼

關注公眾號獲取更多優質电子書,關注一下你就知道資源是有多好了

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

大眾放棄向日本市場投放純電動汽車electric-up!

德國大眾決定放棄向日本市場投放純電動汽車e-up!而這款在日本被稱為e-up!的車型其實就是目前在我國有售的大眾electric-up!純電動車。而在“e-Golf”車型方面,大眾則計畫投放續航距離延長至300km以上的改進款。

大眾2014年10月曾在日本舉行了electric-up!發佈會,宣佈其價格為366.9萬日元(含稅),並於2015年2月開始受理electric-up!的訂單,但electric-up!配備的快速充電系統與日本國內的部分快速充電器不匹配,因此大眾方面推遲了交車日期。

然而,大眾曝出了非法規避尾氣規定的問題之後,大眾開始將業務重點從柴油車輛向電動車輛轉移,決定致力於開發將續航距離延長至250~500km的車型。而electric-up!的續航距離在JC08模式下為185km,與大眾的新戰略並不契合,所以大眾決定放棄向日本市場投放該車型。

而對於與electric-up!一樣曾宣佈投放日本市場的EV“e-Golf”,大眾則計畫更新其配備的鋰離子電池,投放將續航距離延長至300km以上的改進款。已在歐美上市的現行e-Golfe-,其續航距離在JC08模式下為215km,按美國國家環境保護局(EPA)標準計算為134km。

大眾今後在專供日本市場的電動車輛方面將以插電混合動力車(PHEV)為主。繼2015年導入PHEV“高爾夫GTE”之後,計畫2016年內投放“帕薩特GTE”。

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

Elasticsearch系列—Elasticsearch的基本概念及工作原理

基本概念

Elasticsearch有幾個核心的概念,花幾分鐘時間了解一下,有助於後面章節的學習。

NRT

Near Realtime,近實時,有兩個層面的含義,一是從寫入一條數據到這條數據可以被搜索,有一段非常小的延遲(大約1秒左右),二是基於Elasticsearch的搜索和分析操作,耗時可以達到秒級。

Cluster

集群,對外提供索引和搜索的服務,包含一個或多個節點,每個節點屬於哪個集群是通過集群名稱來決定的(默認名稱是elasticsearch),集群名稱搞錯了後果很嚴重。命名建議是研發、測試環境、准生產、生產環境用不同的名稱增加區分度,例如研發使用es-dev,測試使用es-test,准生產使用es-stg,生產環境使用es-pro這樣的名字來區分。如果是中小型應用,集群可以只有一個節點。

Node

單獨一個Elasticsearch服務器實例稱為一個node,node是集群的一部分,每個node有獨立的名稱,默認是啟動時獲取一個UUID作為名稱,也可以自行配置,node名稱特別重要,Elasticsearch集群是通過node名稱進行管理和通信的,一個node只能加入一個Elasticsearch集群當中,集群提供完整的數據存儲,索引和搜索的功能,它下面的每個node分攤上述功能(每條數據都會索引到node上)。

shard

分片,是單個Lucene索引,由於單台機器的存儲容量是有限的(如1TB),而Elasticsearch索引的數據可能特別大(PB級別,並且30GB/天的寫入量),單台機器無法存儲全部數據,就需要將索引中的數據切分為多個shard,分佈在多台服務器上存儲。利用shard可以很好地進行橫向擴展,存儲更多數據,讓搜索和分析等操作分佈到多台服務器上去執行,提升集群整體的吞吐量和性能。
shard在使用時比較簡單,只需要在創建索引時指定shard的數量即可,剩下的都交給Elasticsearch來完成,只是創建索引時一旦指定shard數量,後期就不能再更改了。

replica

索引副本,完全拷貝shard的內容,shard與replica的關係可以是一對多,同一個shard可以有一個或多個replica,並且同一個shard下的replica數據完全一樣,replica作為shard的數據拷貝,承擔以下三個任務:

  1. shard故障或宕機時,其中一個replica可以升級成shard。
  2. replica保證數據不丟失(冗餘機制),保證高可用。
  3. replica可以分擔搜索請求,提升整個集群的吞吐量和性能。

shard的全稱叫primary shard,replica全稱叫replica shard,primary shard數量在創建索引時指定,後期不能修改,replica shard後期可以修改。默認每個索引的primary shard值為5,replica shard值為1,含義是5個primary shard,5個replica shard,共10個shard。
因此Elasticsearch最小的高可用配置是2台服務器。

Index

索引,具有相同結構的文檔集合,類似於關係型數據庫的數據庫實例(6.0.0版本type廢棄后,索引的概念下降到等同於數據庫表的級別)。一個集群里可以定義多個索引,如客戶信息索引、商品分類索引、商品索引、訂單索引、評論索引等等,分別定義自己的數據結構。
索引命名要求全部使用小寫,建立索引、搜索、更新、刪除操作都需要用到索引名稱。

type

類型,原本是在索引(Index)內進行的邏輯細分,但後來發現企業研發為了增強可閱讀性和可維護性,制訂的規範約束,同一個索引下很少還會再使用type進行邏輯拆分(如同一個索引下既有訂單數據,又有評論數據),因而在6.0.0版本之後,此定義廢棄。

Document

文檔,Elasticsearch最小的數據存儲單元,JSON數據格式,類似於關係型數據庫的表記錄(一行數據),結構定義多樣化,同一個索引下的document,結構盡可能相同。

工作原理

簡單地了解一下Elasticsearch的工作原理。

啟動過程

當Elasticsearch的node啟動時,默認使用廣播尋找集群中的其他node,並與之建立連接,如果集群已經存在,其中一個節點角色特殊一些,叫coordinate node(協調者,也叫master節點),負責管理集群node的狀態,有新的node加入時,會更新集群拓撲信息。如果當前集群不存在,那麼啟動的node就自己成為coordinate node。

應用程序與集群通信過程

雖然Elasticsearch設置了Coordinate Node用來管理集群,但這種設置對客戶端(應用程序)來說是透明的,客戶端可以請求任何一個它已知的node,如果該node是集群當前的Coordinate,那麼它會將請求轉發到相應的Node上進行處理,如果該node不是Coordinate,那麼該node會先將請求轉交給Coordinate Node,再由Coordinate進行轉發,搓着各node返回的數據全部交由Coordinate Node進行匯總,最後返回給客戶端。

集群內node有效性檢測

正常工作時,Coordinate Node會定期與拓撲結構中的Node進行通信,檢測實例是否正常工作,如果在指定的時間周期內,Node無響應,那麼集群會認為該Node已經宕機。集群會重新進行均衡:

  1. 重新分配宕機的Node,其他Node中有該Node的replica shard,選出一個replica shard,升級成為primary shard。
  2. 重新安置新的shard。
  3. 拓撲更新,分發給該Node的請求重新映射到目前正常的Node上。

小結

本篇章簡單的向大家介紹了一下Elasticsearch的基本概念和工作原理,讓大家有個比較淺顯的認識,後續會結合實際的例子,來了解一下Elasticsearch基本的用法。

專註Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公眾號:Java架構社區

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

特斯拉儲能系統肥單 健和興搶下綠能連接器供應

特斯拉(Tesla)電動車與儲能系統雙動能啟動,股價創下歷史新高,健和興接獲 Tesla 的綠能連接器,將從本季開始出貨,F-貿聯也可望挾供應鏈優勢,提高份額。   儲能系統在家庭應用已在許多地區推行,並且陸續獲得日本、德國、美國、中國大陸等政府補助。健和興過去只是小量送樣 Tesla 電動車零件,但在 Tesla 今年 4 月底發布的公用與家用儲能計畫中,健和興確定成為綠能連接器供應商,並已小量出貨,法人預估,供應鏈的首波放量時機將落在今年第 3 季末。   法人預估,健和興綠能連接器目前已獲大額訂單,可望為下半年每月營收貢獻逾千萬元台幣,並有逐月增加趨勢。

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

SpringBoot 源碼解析 (二)—– Spring Boot精髓:啟動流程源碼分析

本文從源代碼的角度來看看Spring Boot的啟動過程到底是怎麼樣的,為何以往紛繁複雜的配置到如今可以這麼簡便。

入口類

@SpringBootApplication public class HelloWorldMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
    
}

@SpringBootApplication我們上一篇文章中大概的講過了,有興趣的可以看看我第一篇關於SpringBoot的文章,本篇文章主要關注SpringApplication.run(HelloWorldMainApplication.class, args);,我們跟進去看看

// 調用靜態類,參數對應的就是HelloWorldMainApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return (new SpringApplication(sources)).run(args);
}

它實際上會構造一個SpringApplication的實例,並把我們的啟動類HelloWorldMainApplication.class作為參數傳進去,然後運行它的run方法

SpringApplication構造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //把HelloWorldMainApplication.class設置為屬性存儲起來
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //設置應用類型是Standard還是Web
    this.webApplicationType = deduceWebApplicationType(); //設置初始化器(Initializer),最後會調用這些初始化器
    setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //設置監聽器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();
}

先將HelloWorldMainApplication.class存儲在this.primarySources屬性中

設置應用類型

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

// 相關常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

這裏主要是通過類加載器判斷REACTIVE相關的Class是否存在,如果不存在,則web環境即為SERVLET類型。這裏設置好web環境類型,在後面會根據類型初始化對應環境。大家還記得我們第一篇文章中引入的依賴嗎?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web 的pom又會引入Tomcat和spring-webmvc,如下

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.5.RELEASE</version>
  <scope>compile</scope>
</dependency>

 我們來看看spring-webmvc這個jar包

很明顯spring-webmvc中存在DispatcherServlet這個類,也就是我們以前SpringMvc的核心Servlet,通過類加載能加載DispatcherServlet這個類,那麼我們的應用類型自然就是WebApplicationType.SERVLET

public enum WebApplicationType {
    NONE,
  SERVLET,
    REACTIVE;

    private WebApplicationType() {
    }
}

設置初始化器(Initializer)

//設置初始化器(Initializer),最後會調用這些初始化器
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));

我們先來看看getSpringFactoriesInstances( ApplicationContextInitializer.class)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 這裏的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set保存names來避免重複元素
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根據names來進行實例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 對實例進行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這裏面首先會根據入參type讀取所有的names(是一個String集合),然後根據這個集合來完成對應的實例化操作:

// 入參就是ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  String factoryClassName = factoryClass.getName();

  try {
      //從類路徑的META-INF/spring.factories中加載所有默認的自動配置類
      Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();

      while(urls.hasMoreElements()) {
          URL url = (URL)urls.nextElement();
          Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
          //獲取ApplicationContextInitializer.class的所有值
          String factoryClassNames = properties.getProperty(factoryClassName);
          result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }

      return result;
  } catch (IOException var8) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  }
}

這個方法會嘗試從類路徑的META-INF/spring.factories處讀取相應配置文件,然後進行遍歷,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:

org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

這兩個類名會被讀取出來,然後放入到Set<String>集合中,準備開始下面的實例化操作:

// parameterTypes: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            //確認被加載類是ApplicationContextInitializer的子類
 Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            //反射實例化對象
            T instance = (T) BeanUtils.instantiateClass(constructor, args); //加入List集合中
 instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

確認被加載的類確實是org.springframework.context.ApplicationContextInitializer的子類,然後就是得到構造器進行初始化,最後放入到實例列表中。

因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個接口是這樣定義的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    void initialize(C applicationContext);

}

在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體。

設置監聽器(Listener)

下面開始設置監聽器:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

我們還是跟進代碼看看getSpringFactoriesInstances

// 這裏的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以發現,這個加載相應的類名,然後完成實例化的過程和上面在設置初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

這10個監聽器會貫穿springBoot整個生命周期。至此,對於SpringApplication實例的初始化過程就結束了。

SpringApplication.run方法

完成了SpringApplication實例化,下面開始調用run方法:

public ConfigurableApplicationContext run(String... args) {
    // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    // 第一步:獲取並啟動監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:根據SpringApplicationRunListeners以及參數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
        Banner printedBanner = printBanner(environment);

        // 第三步:創建Spring容器
        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 第四步:Spring容器前置處理
 prepareContext(context, environment, listeners, applicationArguments,printedBanner); // 第五步:刷新容器
 refreshContext(context);
    
// 第六步:Spring容器後置處理 afterRefresh(context, applicationArguments);     // 第七步:發出結束執行的事件 listeners.started(context); // 第八步:執行Runners this.callRunners(context, applicationArguments); stopWatch.stop(); // 返回容器 return context; } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } }
  • 第一步:獲取並啟動監聽器
  • 第二步:根據SpringApplicationRunListeners以及參數來準備環境
  • 第三步:創建Spring容器
  • 第四步:Spring容器前置處理
  • 第五步:刷新容器
  • 第六步:Spring容器後置處理
  • 第七步:發出結束執行的事件
  • 第八步:執行Runners

 下面具體分析。

第一步:獲取並啟動監聽器

獲取監聽器

跟進getRunListeners方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

這裏仍然利用了getSpringFactoriesInstances方法來獲取實例,大家可以看看前面的這個方法分析,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

getSpringFactoriesInstances中反射獲取實例時會觸發EventPublishingRunListener的構造函數,我們來看看EventPublishingRunListener的構造函數:

 1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
 2     private final SpringApplication application;
 3     private final String[] args;
 4     //廣播器
 5     private final SimpleApplicationEventMulticaster initialMulticaster;  6 
 7     public EventPublishingRunListener(SpringApplication application, String[] args) {
 8         this.application = application;
 9         this.args = args;
10         this.initialMulticaster = new SimpleApplicationEventMulticaster();
11         Iterator var3 = application.getListeners().iterator(); 12 
13         while(var3.hasNext()) {
14             ApplicationListener<?> listener = (ApplicationListener)var3.next();
15             //將上面設置到SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中
16             this.initialMulticaster.addApplicationListener(listener); 17         }
18 
19     }
20     //略...
21 }

我們看到EventPublishingRunListener裏面有一個廣播器,EventPublishingRunListener 的構造方法將SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中,我們來看看是如何添加到廣播器:

 1 public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
 2     //廣播器的父類中存放保存監聽器的內部內
 3     private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false);
 4 
 5     @Override
 6     public void addApplicationListener(ApplicationListener<?> listener) {
 7         synchronized (this.retrievalMutex) {
 8             Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
 9             if (singletonTarget instanceof ApplicationListener) {
10                 this.defaultRetriever.applicationListeners.remove(singletonTarget);
11             }
12             //內部類對象
13             this.defaultRetriever.applicationListeners.add(listener); 14             this.retrieverCache.clear();
15         }
16     }
17 
18     private class ListenerRetriever {
19         //保存所有的監聽器
20         public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet(); 21         public final Set<String> applicationListenerBeans = new LinkedHashSet();
22         private final boolean preFiltered;
23 
24         public ListenerRetriever(boolean preFiltered) {
25             this.preFiltered = preFiltered;
26         }
27 
28         public Collection<ApplicationListener<?>> getApplicationListeners() {
29             LinkedList<ApplicationListener<?>> allListeners = new LinkedList();
30             Iterator var2 = this.applicationListeners.iterator();
31 
32             while(var2.hasNext()) {
33                 ApplicationListener<?> listener = (ApplicationListener)var2.next();
34                 allListeners.add(listener);
35             }
36 
37             if (!this.applicationListenerBeans.isEmpty()) {
38                 BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory();
39                 Iterator var8 = this.applicationListenerBeans.iterator();
40 
41                 while(var8.hasNext()) {
42                     String listenerBeanName = (String)var8.next();
43 
44                     try {
45                         ApplicationListener<?> listenerx = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
46                         if (this.preFiltered || !allListeners.contains(listenerx)) {
47                             allListeners.add(listenerx);
48                         }
49                     } catch (NoSuchBeanDefinitionException var6) {
50                         ;
51                     }
52                 }
53             }
54 
55             AnnotationAwareOrderComparator.sort(allListeners);
56             return allListeners;
57         }
58     }
59     //略...
60 }

上述方法定義在SimpleApplicationEventMulticaster父類AbstractApplicationEventMulticaster中。關鍵代碼為this.defaultRetriever.applicationListeners.add(listener);,這是一個內部類,用來保存所有的監聽器。也就是在這一步,將spring.factories中的監聽器傳遞到SimpleApplicationEventMulticaster中。我們現在知道EventPublishingRunListener中有一個廣播器SimpleApplicationEventMulticaster,SimpleApplicationEventMulticaster廣播器中又存放所有的監聽器。

啟動監聽器

我們上面一步通過getRunListeners方法獲取的監聽器為EventPublishingRunListener,從名字可以看出是啟動事件發布監聽器,主要用來發布啟動事件。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    private final SpringApplication application;
    private final String[] args;
    private final SimpleApplicationEventMulticaster initialMulticaster;

我們先來看看SpringApplicationRunListener這個接口

package org.springframework.boot;
public interface SpringApplicationRunListener {

    // 在run()方法開始執行時,該方法就立即被調用,可用於在初始化最早期時做一些工作
    void starting();
    // 當environment構建完成,ApplicationContext創建之前,該方法被調用
    void environmentPrepared(ConfigurableEnvironment environment);
    // 當ApplicationContext構建完成時,該方法被調用
    void contextPrepared(ConfigurableApplicationContext context);
    // 在ApplicationContext完成加載,但沒有被刷新前,該方法被調用
    void contextLoaded(ConfigurableApplicationContext context);
    // 在ApplicationContext刷新並啟動后,CommandLineRunners和ApplicationRunner未被調用前,該方法被調用
    void started(ConfigurableApplicationContext context);
    // 在run()方法執行完成前該方法被調用
    void running(ConfigurableApplicationContext context);
    // 當應用運行出錯時該方法被調用
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

SpringApplicationRunListener接口在Spring Boot 啟動初始化的過程中各種狀態時執行,我們也可以添加自己的監聽器,在SpringBoot初始化時監聽事件執行自定義邏輯,我們先來看看SpringBoot啟動時第一個啟動事件listeners.starting():

@Override
public void starting() {
    //關鍵代碼,先創建application啟動事件`ApplicationStartingEvent`
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

這裏先創建了一個啟動事件ApplicationStartingEvent,我們繼續跟進SimpleApplicationEventMulticaster,有個核心方法:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //通過事件類型ApplicationStartingEvent獲取對應的監聽器
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { //獲取線程池,如果為空則同步處理。這裏線程池為空,還未沒初始化。
        Executor executor = getTaskExecutor();
        if (executor != null) {
            //異步發送事件
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //同步發送事件
 invokeListener(listener, event);
        }
    }
}

這裡會根據事件類型ApplicationStartingEvent獲取對應的監聽器,在容器啟動之後執行響應的動作,有如下4種監聽器:

我們選擇springBoot 的日誌監聽器來進行講解,核心代碼如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    //在springboot啟動的時候
    if (event instanceof ApplicationStartedEvent) {
    onApplicationStartedEvent((ApplicationStartedEvent) event); } //springboot的Environment環境準備完成的時候
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }
    //在springboot容器的環境設置完成以後
    else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    //容器關閉的時候
    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
            .getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    }
    //容器啟動失敗的時候
    else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

因為我們的事件類型為ApplicationEvent,所以會執行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot會在運行過程中的不同階段,發送各種事件,來執行對應監聽器的對應方法。

第二步:環境構建

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

跟進去該方法:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    //獲取對應的ConfigurableEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置
 configureEnvironment(environment, applicationArguments.getSourceArgs()); //發布環境已準備事件,這是第二次發布事件
 listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    ConfigurationPropertySources.attach(environment);
    return environment;
}

來看一下getOrCreateEnvironment()方法,前面已經提到,environment已經被設置了servlet類型,所以這裏創建的是環境對象是StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) { return new StandardServletEnvironment(); } return new StandardEnvironment();
}

接下來看一下listeners.environmentPrepared(environment);,上面已經提到了,這裡是第二次發布事件。什麼事件呢?來看一下根據事件類型獲取到的監聽器:

主要來看一下ConfigFileApplicationListener,該監聽器非常核心,主要用來處理項目配置。項目中的 properties 和yml文件都是其內部類所加載。具體來看一下:

首先還是會去讀spring.factories 文件,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();獲取的處理類有以下四種:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

在執行完上述三個監聽器流程后,ConfigFileApplicationListener會執行該類本身的邏輯。由其內部類Loader加載項目制定路徑下的配置文件:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

至此,項目的變量配置已全部加載完畢,來一起看一下:

這裏一共6個配置文件,取值順序由上到下。也就是說前面的配置變量會覆蓋後面同名的配置變量。項目配置變量的時候需要注意這點。

第三步:創建容器

context = createApplicationContext();

繼續跟進該方法:

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

這裏創建容器的類型 還是根據webApplicationType進行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext

第四步:Spring容器前置處理

這一步主要是在容器刷新之前的準備動作。包含一個非常關鍵的操作:將啟動類注入容器,為後續開啟自動化配置奠定基礎。

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

繼續跟進該方法:

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    //設置容器環境,包括各種變量
 context.setEnvironment(environment); //執行容器後置處理
    postProcessApplicationContext(context);
    //執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
 applyInitializers(context);   //發送容器已經準備好的事件,通知各監聽器
 listeners.contextPrepared(context); //註冊啟動參數bean,這裏將容器指定的參數封裝成bean,注入容器
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    //設置banner
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }
    //獲取我們的啟動類指定的參數,可以是多個
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //加載我們的啟動類,將啟動類注入容器
    load(context, sources.toArray(new Object[0])); //發布容器已加載事件。
    listeners.contextLoaded(context);
}

調用初始化器

protected void applyInitializers(ConfigurableApplicationContext context) {
    // 1. 從SpringApplication類中的initializers集合獲取所有的ApplicationContextInitializer
    for (ApplicationContextInitializer initializer : getInitializers()) { // 2. 循環調用ApplicationContextInitializer中的initialize方法
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
       initializer.initialize(context);
    }
}

這裏終於用到了在創建SpringApplication實例時設置的初始化器了,依次對它們進行遍歷,並調用initialize方法。我們也可以自定義初始化器,並實現initialize方法,然後放入META-INF/spring.factories配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value中,這裏我們自定義的初始化器就會被調用,是我們項目初始化的一種方式

加載啟動指定類(重點)

大家先回到文章最開始看看,在創建SpringApplication實例時,先將HelloWorldMainApplication.class存儲在this.primarySources屬性中,現在就是用到這個屬性的時候了,我們來看看getAllSources()

public Set<Object> getAllSources() {
    Set<Object> allSources = new LinkedHashSet();
    if (!CollectionUtils.isEmpty(this.primarySources)) {
        //獲取primarySources屬性,也就是之前存儲的HelloWorldMainApplication.class
        allSources.addAll(this.primarySources);
    }

    if (!CollectionUtils.isEmpty(this.sources)) {
        allSources.addAll(this.sources);
    }

    return Collections.unmodifiableSet(allSources);
}

很明顯,獲取了this.primarySources屬性,也就是我們的啟動類HelloWorldMainApplication.class,我們接着看load(context, sources.toArray(new Object[0]));

protected void load(ApplicationContext context, Object[] sources) {
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

private int load(Class<?> source) {
    if (isGroovyPresent()
            && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        // Any GroovyLoaders added in beans{} DSL can contribute beans here
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                GroovyBeanDefinitionSource.class);
        load(loader);
    }
    if (isComponent(source)) {
        //以註解的方式,將啟動類bean信息存入beanDefinitionMap,也就是將HelloWorldMainApplication.class存入了beanDefinitionMap
        this.annotatedReader.register(source); return 1;
    }
    return 0;
}

啟動類HelloWorldMainApplication.class被加載到 beanDefinitionMap中,後續該啟動類將作為開啟自動化配置的入口,後面一篇文章我會詳細的分析,啟動類是如何加載,以及自動化配置開啟的詳細流程。

通知監聽器,容器已準備就緒

listeners.contextLoaded(context);

主還是針對一些日誌等監聽器的響應處理。

第五步:刷新容器

執行到這裏,springBoot相關的處理工作已經結束,接下的工作就交給了spring。我們來看看refreshContext(context);

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用創建的容器applicationContext中的refresh()方法
    ((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        /**
         * 刷新上下文環境
         */
        prepareRefresh();

        /**
         * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
         */
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        /**
         * 為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier等
         * 添加ApplicationContextAwareProcessor處理器
         * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
         * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
         */
        prepareBeanFactory(beanFactory);

        try {
            /**
             * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
             */
            postProcessBeanFactory(beanFactory);

            /**
             * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
             * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
             */
            invokeBeanFactoryPostProcessors(beanFactory);

            /**
             * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
             * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
             */
            registerBeanPostProcessors(beanFactory);

            /**
             * 初始化上下文中的資源文件,如國際化文件的處理等
             */
            initMessageSource();

            /**
             * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
             */
            initApplicationEventMulticaster();

            /**
             * 給子類擴展初始化其他Bean
             */
            onRefresh();

            /**
             * 在所有bean中查找listener bean,然後註冊到廣播器中
             */
            registerListeners();

            /**
             * 設置轉換器
             * 註冊一個默認的屬性值解析器
             * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
             * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
             */
            finishBeanFactoryInitialization(beanFactory);

            /**
             * 通過spring的事件發布機制發布ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
             * 即對那種在spring啟動后需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
             * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
             * 另外,spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
             * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
             */
            finishRefresh();
        }

        finally {
    
            resetCommonCaches();
        }
    }
}

refresh方法在spring整個源碼體系中舉足輕重,是實現 ioc 和 aop的關鍵。我之前也有文章分析過這個過程,大家可以去看看

第六步:Spring容器後置處理

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}

擴展接口,設計模式中的模板方法,默認為空實現。如果有自定義需求,可以重寫該方法。比如打印一些啟動結束log,或者一些其它後置處理。

第七步:發出結束執行的事件

public void started(ConfigurableApplicationContext context) {
    //這裏就是獲取的EventPublishingRunListener
    Iterator var2 = this.listeners.iterator();

    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //執行EventPublishingRunListener的started方法
 listener.started(context);
    }
}

public void started(ConfigurableApplicationContext context) {
    //創建ApplicationStartedEvent事件,並且發布事件 //我們看到是執行的ConfigurableApplicationContext這個容器的publishEvent方法,和前面的starting是不同的
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}

獲取EventPublishingRunListener監聽器,並執行其started方法,並且將創建的Spring容器傳進去了,創建一個ApplicationStartedEvent事件,並執行ConfigurableApplicationContext 的publishEvent方法,也就是說這裡是在Spring容器中發布事件,並不是在SpringApplication中發布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的11個監聽器發布啟動事件。

第八步:執行Runners

我們再來看看最後一步callRunners(context, applicationArguments);

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    //獲取容器中所有的ApplicationRunner的Bean實例
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //獲取容器中所有的CommandLineRunner的Bean實例
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            //執行ApplicationRunner的run方法
 callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            //執行CommandLineRunner的run方法
 callRunner((CommandLineRunner) runner, args);
        }
    }
}

如果是ApplicationRunner的話,則執行如下代碼:

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
      runner.run(args);
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", var4);
    }
}

如果是CommandLineRunner的話,則執行如下代碼:

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        runner.run(args.getSourceArgs());
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", var4);
    }
}

我們也可以自定義一些ApplicationRunner或者CommandLineRunner,實現其run方法,並注入到Spring容器中,在SpringBoot啟動完成后,會執行所有的runner的run方法

至此,SpringApplication大概分析了一遍,還有很多細節和核心留在下面文章中講。

 

 

 

 

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

歐美電動車市場不同調,歐洲衝鋒美國疲軟

  美國在特斯拉領軍下,一度成為電動車的先鋒國家,不過風水輪流轉,美國電動車銷售開始牛皮化,反倒是歐洲電動車銷售吹起衝鋒號,據雷諾汽車零排放計畫( Renault Z.E.)統計,2015 年上半年歐洲電動車銷售較 2014 年同期大增 55%。   其中,英國的增幅最大,2015 年上半年銷售成長 80%,大約售出 5,000 輛電動車,不過銷售量最多的國家仍然是挪威,2015 年上半年銷售約 15,000 輛電動車,成長 47%。   而根據歐盟汽車業資料(European Automotive Industry Data),2015 年 1 到 4 月,西歐消費者總共購買了 26,808 輛電動車,24,578  輛油電混合車,總計 51,386 輛,而據美國汽車網站 HybridCars.com 統計,美國消費者在同期間購買 21,403 輛電動車,10,684 輛油電混合車。西歐電動車與油電混合車的銷售量超過美國 6 成,使得歐洲市場首度成為電動車市場的領頭羊,過去電動車市場由美國領銜,2014 年,美國電動車與油電混合車銷售超過 11.8 萬輛、歐洲市場則銷售 9.8 萬輛、中國市場銷售 74,763 輛。   美國市場之所以失去領先地位,最主要原因可能在於油價的落差,由於油價持續下跌,美國零售油價已經降至每(美制)加侖 2.6 美元,而歐洲的零售油價下降卻不如美國,例如英國油價換算為美制加侖時,相當於每加侖約 6.77 到 7 美元,油價越高,消費者轉換為電動車的意願也越高,這使得歐洲的電動車市場買氣強於美國;另一方面,美國雪佛蘭(Chevrolet)即將推 出 2016 年款 Volt,消費者等待新款,也成為消費遲滯的原因之一。   歐洲市場勝過美國的另一個可能原因,是歐洲的車款選擇較多,如三菱 Outlander 油電混合車,據 EV Obsession 網站統計,是 2015 年以來歐洲銷售最好的一輛電動與油電混合車,但是只在日本、歐洲、澳洲銷售,美國上市時間因故延後到 2015 年第四季,Outlander 油電混合車的缺席,多少對美國市場的總體銷售量有所影響。   歐洲市場的蓬勃發展讓更多車廠考慮推出電動車,如英國老牌跑車品牌奧斯頓‧馬丁(Aston Martin)就打算在 2 年內推出 Rapide 的電動車版本,售價可能在 20 萬 到 25 萬美元之間,目前 Rapide 的電動車版本已經有測試原型車,Rapide 電動車上市數年後,DBX 也將推出電動車。奧斯頓‧馬丁推出電動車的另一個考量是碳排放規範問題,為了平衡大馬力跑車的高碳排放,勢必要推出無碳排放的電動車來平衡,以免遭到包括中國在內的各國相關規範限制。各車廠為了類似原因,更積極推出電動車款,也將進一步推動歐洲電動車市場更加蓬勃發展。     本文全文授權轉載自《科技新報》─〈〉  

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

.NET Core 對龍芯的支持情況和對 .NET Core 開發嵌入式的思考

目錄

.NET Core 對龍芯的支持情況和對 .NET Core 開發嵌入式的思考

一,遺憾的嘗試

前些天看到了張隊公眾推送的《》,聯想到上一周與朋友在龍芯搗鼓 .NET Core,就想寫一下關於 .NET Core 在龍芯下的資料。

Jexus Web Server 能夠在龍芯服務器上跑,但是 ASP.NET 呢?.NET Core 呢?安裝什麼版本的 Mono ?Jexus 作者的文章表達有點模糊呀~

上一周與朋友在龍芯上面為了部署 .NET 項目,頗費心機。朋友公司中標政府項目,開發好 .NET Core 做的項目后,才發現要部署的服務器是龍芯的,.NET Core 無法在上面運行。

服務器有什麼有 Mono 4.x,可以創建簡單的 Proparm.cs ,編譯出程序,使用 mono xx.exe 運行,可是把項目放進去編譯不出來~想編譯安裝 Mono 6.x 也不行,中間有些過程報錯。

.NET Core 自然不用想了,完全無法編譯,通過 Google 查詢資料,要重寫 C++ 部分(移植),才能在 龍芯 下編譯出 CoreCLR。

官方 CoreCLR 源碼庫,可以看到一些腳本和編譯工具鏈。

RISV-C 是精簡指令集,MIPS 是指 基於 RISC-V 的 CPU 架構,龍芯服務器使用 MIPS 架構。

最終,無法部署 .NET 軟件,朋友公司改用 Java 開發。。。

之前筆者為了在 Armel 的 CPU 下運行 .NET Core ,花了很多時間手動編譯 .NET Core,最終還是失敗。我將編譯過程詳細寫了一篇文章,地址《》。

二,.NET Core在嵌入式下的幾點不足

18年7月張隊來我校組織了大灣區 .NET 交流會,從那時起開始學習 .NET ,19 年三月月份進入敢為實習轉正至此。

使用 .NET Core 開發半年的時間里,在嵌入式開發中,我個人總結當前 .NET Core 在嵌入式領域有幾個問題/建議。

1,不支持前幾年的CPU

.NET Core 無法在樹莓派 Zero上運行(Arm v6);

無法在華為海思A9芯片上運行(Armel Armv7);

這兩種芯片雖說是幾年前出的芯片,但是 .NET Core 大張旗鼓的說要搞 IoT,卻不兼容舊一些的 CPU,目前很多舊式設備依然會在未來一段時間內是主流的存在 。

微軟官方也說了:

Note: .NET Core 2.1 is supported on Raspberry Pi 2+. It isn’t supported on the Pi Zero or other devices that use an ARMv6 chip. .NET Core requires ARMv7 or ARMv8 chips, like the ARM Cortex-A53.

Arm 方面的支持還是不夠廣。

2,測試的硬件設備較少

官方對嵌入式設備的測試,主要在 樹莓派 2 / 3,還有很多開發板沒有測試~

3,支持兼容的系統版本較少

.NET Core 支持很多 Linux 系統,但是對應這些系統的支持,都是以最新版本的系統為主,例如 .NET Core 3.0 在Ubuntu 上是支持 16.x、18.x,14.x 和 17.x 被無情的拋棄了。

.NET Core 3.0 支持的系統如下:

4,體積依然太大

對於嵌入式開發來說, .NET Core 的體積依然太大,.NET Core 3.0 也拯救不了。。。哪怕只有一行 Hello World,也要 70MB+ 以上。

5,依賴庫比較傷腦筋

經常會出現 ICU、libssl、gcc 等依賴庫版本不一致或沒有安裝這些庫時的報錯信息,石頭哥曾經被這些問題搞得掉頭髮。

三,.NET Core 龍芯移植的進展和資料

根據大佬們的移植,在 11 月 9 號時,已經實現了 在龍芯上面運行 .NET Core 的 Hello World 實例,

The code base was upgraded to 3.0. Hello World and serveral tests in coreclr can run on MIPS64 now. 

這是對於 CoreCLR 的移植,還有很多問題等待大神解決。

對於 .NET Core 在 MIPS 上的移植討論,可以到 Issue 查看

不過,微軟官方目前沒有移植計劃,只能靠社區去完成了。

Microsoft currently has no plans or work in progress to support MIPS. Of course, we would be willing to accept external contributions towards that goal as appropriate. Note that it is, certainly, a significant amount of work to port .NET Core to a new platform.

還有另一個大神的作品

This project will focus on translating .NET IL for non-supported .NET targets. Portibility is a huge focus.

  • .NET Standard compatibility
  • Native C performance
  • C89: modern, legacy and embedded platforms (x86, MIPS, SPARK, RISC-V, PPC, AVR, etc)
  • CC65: 6502 platforms (Atari, C64, NES, Apple II, etc) [CS2X may be better suited]]
  • SDCC: Many targets (ColecoVision, etc) [CS2X may be better suited]
  • Assembly: CP1610 (Intellivision) [CS2X may be better suited]]
  • Retarget: Custom assembly targets via plugin system (FPGA CPU, 16bit bytes, etc)
  • Custom Standard lib(s) for various targets.
  • Documentation

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

「歐盟綠色政綱」行動路線圖重點:碳關稅、能源稅改、綠色轉型融資、氣候盟約

文:郭映庭(台大風險社會與政策研究中心研究員)

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

【其他文章推薦】

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

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

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

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

C-power 2016中國(上海)國際智慧充換電樁技術設施展覽會暨高層論壇

時 間:2016年5月24-26日  
地 點:上海新國際博覽中心(龍陽路2345號)
主辦單位:中國電工技術學會、上海市電機工程學會、上海電器行業協會
協辦單位:國家電網公司、南方電網公司、中石油、中石化、中國新能源汽車聯盟會、上汽集團、美國新能源汽車產業協會、德國能源產業協會、韓國新能源產業聯盟會
承辦單位:拜仁展覽(上海)有限公司

市場前景:

自2014年以來,國家相繼出臺了多種政策措施扶持新能源汽車和充電設施的發展,隨著各級政府對充電設施建設投入加大,充電設施市場即將迎來大爆發時期。

國家能源局發佈了《配電網建設改造行動計畫(2015-2020年》,未來五年新能源汽車充電設施配電建設將成為電能替代工作的重點,隨著一系列政策利好的落地,新能源汽車行業有望飛速發展。

按照《電動汽車充電基礎設施發展指南》(2015-2020),做好配電網規劃與充換電設施規劃的銜接,加強充換電設施配套電網建設與改造,保障充換電設施無障礙接入。2020年滿足1.2萬座充換電站、480萬台充電樁接入需求,為500萬輛電動汽車提供充換電服務。按照計畫,能源局將在五年內做好配電網規劃與充換電設施規劃的銜接,加強充換電設施配套電網建設與改造,保障充換電設施無障礙接入。2015年充電站市場規模預計將達到200億元,2016年400億元,到2020年將突破1000億元,充電站市場已成為企業掘金的藍海。

為迎合市場發展需求,創造供需雙方零接觸的洽談機會,由中國電工技術學會、上海市電機工程學會等權威單位共同主辦的“C-POWer2016上海國際智慧充換電樁技術設施展覽會暨高層論壇”,將於2016年5月24-26日在上海新國際博覽中心隆重上演。本屆展會將積極迎合市場新趨勢,為推動我國新能源汽車產業健康快速發展方面發揮更加積極的作用,智慧充電,商機無限!

展覽排程:
報到布展:2016年5月22-23日  09:00-18:00
展出時間:2016年5月24-26日  09:00-16:30
展商撤展:2016年5月26日   14:00-18:00
展覽地點:上海新國際博展中心(龍陽路2345號)

參展範圍:
◆充換電設備:充電樁、充電機、充電櫃、換電設備等;。
◆配電設備:變壓器、變頻器、配電櫃、高低壓保護設備、低壓開關、繼電器等;
◆濾波裝置、監控設備;
◆充電站監控系統:充電機監控管理系統、配電監控系統、通訊管理監控系統、安防系統;
◆儲能電池、動力電池及電池管理系統;
◆分散式微電網:光伏系統、儲能系統、控制系統、燃料電池等;
◆充電相關技術、連接器、電纜等;
◆無線充電樁產品和技術展示;
◆充電站智慧型網路專案規劃及成果展示;
◆相關諮詢和媒體。

C-POWer2016中國(上海)國際智慧充換電樁技術設施展覽會暨高層論壇大會聯絡處:
地  址:上海市浦東新區金橋路58號銀東大廈19樓B座
電  話:+86-21-68551123          傳  真:+86-21-68551157
連絡人:蔡 明13916931916          
網  址:    E-mail:

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開