國外實測新款 Surface Pro X 的 SQ2 處理器效能幾乎跟 SQ1 一樣

微軟在去年十月時正式更新旗下 Surface Pro X 平版電腦,處理器升級至 SQ2,一樣是高通 ARM 架構,不過當時微軟並沒有特別說明這顆處理器的效能表現,而最近終於有外媒實測到實機,但讓人意外的是,跑分結果竟然跟 SQ1 差不多,也就是新款 Surface Pro X 效能幾乎沒有什麼提升。

新款 Surface Pro X 的 SQ2 處理器效能幾乎跟 SQ1 一樣

稍早一間德國媒體網站 Dr. Windows 分享 Surface Pro X 2020 年版的實測報告,共使用三款跑分軟體進行測試,並與舊款 Surface Pro X 比較,結果如下。

首先是 Jetstream 2 瀏覽器測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 獲得 89,614
  • 搭載 SQ2 處理器的 Surface Pro X 2020 獲得 90,664

安兔兔測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 獲得 286,740
  • 搭載 SQ2 處理器的 Surface Pro X 2020 獲得 286,271

Geekbench 4 測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 單核心獲得 3,530、多核心獲得 11,927
  • 搭載 SQ2 處理器的 Surface Pro X 2020 單核心獲得 3,627、多核心獲得 12,042

三個測試結果中,Jetstream 2 與 Geekbench 4 雖然新款確實比較高,但也只有多一點點,安兔兔反而變低。整體來看,幾乎可以說搭載 SQ2 處理器的 Surface Pro X 2020 效能並沒有提升。

另外最近微軟官方的韌體更新中,也都合併支援 SQ1 與 SQ2 處理器:

不確定為何這兩顆效能會這麼接近,但也顯示著,對於注重效能的朋友來說,舊款 Surface Pro X 或許是 CP 值更高的選擇。

當然,除了效能,Surface Pro X 2020 還是有地方提升,像續航力官方就承諾可達到 15 小時,比舊款多 2 小時。

無論如何,有在關注新款 Surface Pro X 2020 的人,建議等更多實測報告現身再做決定。

資料來源:Dr. Windows

微軟最新的廣告,直白地跟你說 Surface Pro 7 就是比 MacBook Pro 好

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

都是13萬能買到 這兩款大眾轎車誰更好

乘坐感受速騰雖然用上了后獨立懸架,但是舒適性方面還是有所欠缺,關鍵是當顛簸比較多的時候,會發現懸架有點處理不過來,餘震較多。但是有一點比較好,速騰的底盤滲進車廂內的細碎震動不多。另外有一點值得表揚的就是,後排空間較大且座墊的長度足夠。

前言速騰和朗逸可以說是大眾家族中的兩張銷量王牌,那麼在14萬左右想買這兩者中的一輛,該如何來選呢,下面編者我就來對比一下。

一汽-大眾-速騰

2017款 230TSI 自動舒適型

指導價:16.08萬

優惠幅度:3萬元左右(僅供參考)

上汽大眾-朗逸

2015款 230TSI DSG舒適版

指導價:14.69萬

優惠幅度:1.3萬左右(僅供參考)

駕駛感受

兩輛車的動力總成都一樣,均為1.4T發動機+7速DSG變速箱,最大輸出131馬力和225牛米。略有區別的是最大扭矩轉速,朗逸在1400轉便爆發,而速騰要到1500轉,不過兩者都在3500轉以後開始衰減。

懸架的選用上兩者也有很大差別,速騰后懸架採用的是多連桿獨立懸架,而朗逸用的是扭力梁式非獨立懸架。儘管如此,一切還是要從實際表現出發。

速騰的發動機艙

速騰的動力還是很充足的,無論平時超車,還是高速上開,都不會覺得車子有乏力的情況。變速箱的換擋動作也很平順,不會給駕駛員帶來什麼滋擾。然而,懸架總體來說還是偏軟,高速過彎時的側傾會比較大。轉向則略微有點重,特別是在不停挪車時,轉向的力度有點讓人受不了。

朗逸的發動機艙

朗逸的駕駛感受其實也和速騰大同小異,1.4T的動力隨傳隨到,不拖泥帶水。懸架抑制側傾的能力依舊有限,不算很出色。轉向上會比速騰來得略輕一點點。

乘坐感受

速騰雖然用上了后獨立懸架,但是舒適性方面還是有所欠缺,關鍵是當顛簸比較多的時候,會發現懸架有點處理不過來,餘震較多。但是有一點比較好,速騰的底盤滲進車廂內的細碎震動不多。另外有一點值得表揚的就是,後排空間較大且座墊的長度足夠。

朗逸的表現就沒有速騰那麼好了,首先座墊偏短,乘客的大腿大部分位置得不到有效承托。不過,座椅靠背角度較斜,坐進去感覺還是挺放鬆。其次就是,從底盤傳入車廂內的細碎震動較多,過大坑時會顯得較為單薄,幸好在過小顛簸時懂得如何去以柔克剛。

配置對比

這個環節可以說是兩者差別最大的,因為速騰的優惠幅度更高,所以配置會更為豐富。不僅多了無鑰匙啟動與進入,還多了不少配置,詳情可看下錶。

油耗對比

由於動力總成完全一致,且車身尺寸差距不大,所以兩者的油耗很相近。速騰的百公里綜合油耗為7.3L,而朗逸則為7.2L。

編者總結:

其實,速騰與朗逸這兩台車的調性是很相似的,都是偏居家的風格,而速騰在舒適性與配置上會優於朗逸。但是,每次看到速騰,我腦海中都閃現出一句歌詞“有多少愛可以重來”,曾經的速騰斷軸門讓大家都傷透了心。儘管現在速騰已更換了后獨立懸架,但是心中的芥蒂是否就此放下呢?如果已經放下了,那速騰確是一款適合家用的車。如若依舊介懷,就不必買輛車讓自己提心吊膽。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

3分鐘了解清楚持續集成、持續交付、持續部署

近些年來,持續集成、持續交付以及持續部署這幾個熱詞總是在大家的眼前晃來晃去!在招聘信息和面試過程中也會經常提及!在這裏我就用三分鐘時間來帶大家了解他們!

 

1. 持續集成(CI:Continuous Integration)

持續集成強調開發人員提交了新代碼之後,立刻進行構建然後進行單元測試。根據測試結果,我們可以確定新代碼和原有代碼能否正確地集成在一起。

注意:這裏的測試重點是指開發人員進行的代碼級別測試!

  

2. 持續交付(CD:Continuous Delivery)

 

持續交付在持續集成的基礎上,將集成后的代碼部署到更貼近真實運行環境的類生產環境中。如果測試沒有問題,可以繼續手動部署到生產環境中。

注意:這裏的測試重點是指測試人員進行的產品級別的測試!往往在這個測試過程中普遍都會引入測試腳本進行自動化回歸測試,主要是進行接口測試和UI測試,

當然部分公司也會引入安全測試和性能測試。持續交付能夠以較短地周期完成需求的小粒度頻繁交付。

頻繁的交付周期帶來了更迅速的對軟件的反饋,並且在這個過程中,各個角色密切協作,相比於傳統的瀑布式軟件團隊更少浪費資源。

 

 

3. 持續部署(CD:Continuous Deployment)

 

持續部署則是在持續交付的基礎上,把部署到生產環境的過程自動化。整個過程無需人工參与!

 

4. 總結

簡單地說:

  • 持續集成主要是在開發範圍,包括:構建>單元測試;
  • 持續交付涉及開發、測試、運維合作,包括:構建>單元測試>測試環境部署>測試(不涉及生產環境的自動化部署)
  • 持續部署是在持續交付的基礎上的延伸:包括:構建>單元測試>測試環境部署>測試>生產環境部署>生產環境測試(全流程自動化)

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

除了FastJson,你也應該了解一下Jackson(一)

在上月末的時候收到一條關於fastjson安全漏洞的消息,突然想到先前好像已經有好多次這樣的事件了(在fastjson上面)。關於安全方面,雖然中槍的機率微小,但是在這個信息越來越複雜的時代,安全性也變得越來越重要,就像DevSecOps的誕生,在軟件交付的整個價值流中我們也需要注重安全這方面。當然我們現在不談關於FastJson的優劣,因為我們本文的目標是讓大家了解和掌握Jackson。

概覽

Jackson是一個非常流行和高效的基於Java的庫,它可以序列化java對象或將java對象映射到JSON,反之亦然。當然除了Jackson,在Java中同類型的優秀的庫也有很多,比如:

  • Gson
  • json-io
  • Genson

關於哪一個最好或者哪一個最流行,沒有明確的答案。技術的種類繁多,每個人對與不同技術的態度也不一樣。言歸正傳,文章主要還是討論Jackson的。本文主要講解我們處理Json中最常見的兩個操作:

  • 將Java對象序列化為JSON
  • JSON字符串反序列化為Java對象

引入依賴

由於在Spring/SpringBoot中很多組件已經自帶了Jackson庫,所以很多情況下不需要手動引入Jackson的依賴。

手動引入依賴:

<dependency> 
  <groupId>com.fasterxml.jackson.core</groupId> 
  <artifactId>jackson-databind</artifactId> 
  <version>2.9.8</version>
</dependency>

這個依賴關係還將傳遞地向類路徑添加以下庫:

  1. jackson-annotations-2.9.8.jar
  2. jackson-core-2.9.8.jar
  3. jackson-databind-2.9.8.jar

JavaObject to Json

ObjectMapper

ObjectMapper是一個映射器(或數據綁定器或編解碼器),提供了在Java對象(bean的實例)和JSON之間進行轉換的功能。

首先定義一個簡單的Java類

public class Car {
    private String color;
    private String type;
    // standard getters setters
}

將Java對象轉換成Json

我們使用ObjectMapper的writeValue相關Api來對Java對象進行序列化操作

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
System.out.println(objectMapper.writeValueAsString(car));

此時輸出

{"color":"blue","type":"c1"}

更多

ObjectMapper的writeValue相關Api還提供了很多便利的Json序列化操作方法,比如:將對象序列化成Json字節數組的writeValueAsBytes()方法、自定義輸出源的writeValue()方法…

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
objectMapper.writeValue(new File("./xxx.txt"),car);

運行上述代碼,Java對象的序列化Json將被輸出到xxx.txt文件。

Json to JavaObject

將Json String轉換成Java Object

ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"color\":\"blue\",\"type\":\"c1\"}";
Car car = objectMapper.readValue(json, Car.class);

readValue()方法也接受其他形式的輸入,比如包含JSON字符串的文件:

ObjectMapper objectMapper = new ObjectMapper();
Car car = objectMapper.readValue(new File("./xxx.txt"), Car.class);
System.out.println(car);

JSON to Jackson JsonNode

JsonNode

一個JSON可以被解析成一個JsonNode對象,用來從一個特定的節點檢索數據.

使用readTree()方法,我們可以將Json字符串轉換成JsonNode

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
System.out.println(jsonNode.findValue("type").asText());
// 打印出“FAIT”

JSONArrayString to JavaList

ObjectMapper objectMapper = new ObjectMapper();
String jsonCarArray =
    "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : 3. \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>() {});

JSONString to JavaMap

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
});

Jackson庫最大的優點之一是高度可定製的序列化和反序列化過程。接下來將介紹一些高級特性,其中輸入或輸出JSON響應可以與生成或使用響應的對象不同。

配置序列化和反序列化特性

String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";

假設使用如上json字符串來反序列化成Java對象,按照默認解析過程將導致UnrecognizedPropertyException異常,因為其中存在Car類中未包含的新字段year。

通過配置序列化和反序列化特性來解決此問題:

ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);

如上,我們在ObjectMapper中配置了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES=false,從而實現忽略新的字段。

類似:另一個選項FAIL_ON_NULL_FOR_PRIMITIVES,它定義了是否允許原始值的空值;FAIL_ON_NUMBERS_FOR_ENUM控制是否允許enum值被序列化/反序列化為数字……

自定義序列化器或反序列化器

自定義序列化器

public static class CustomCarSerializer extends StdSerializer<Car> {
    public CustomCarSerializer() {
        this(null);
    }

    public CustomCarSerializer(Class<Car> t) {
        super(t);
    }

    @Override
    public void serialize(Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}

如上,通過繼承StdSerializer類,我們實現了一個自定義的序列化器。

使用自定義的序列化器:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "enault");
System.out.println(mapper.writeValueAsString(car));
//輸出{"car_brand":"enault"}

自定義反序列化器

public static class CustomCarDeserializer extends StdDeserializer<Car> {

        public CustomCarDeserializer() {
            this(null);
        }

        protected CustomCarDeserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Car deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            Car car = new Car();
            ObjectCodec codec = p.getCodec();
            JsonNode node = codec.readTree(p);
            // try catch block
            JsonNode colorNode = node.get("color");
            String color = colorNode.asText();
            car.setColor(color);
            return car;
        }
    }

如上,通過繼承StdDeserializer類,我們實現了一個自定義的序列化器。

使用自定義的反序列化器:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\"}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);
//此時的car {color='Black', type='null'}

處理時間格式

️:此處僅展示對於Java8的LocalDate&LocalDateTime的處理

首先創建一個帶日期時間字段的Car類

public class Car {
    private String color;
    private String type;
  	@JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDateTime produceTime;
    // standard getters setters
}

自定義時間格式處理

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Car car = new Car().setColor("blue").setType("c1").setProduceTime(LocalDateTime.now());
String carAsString = objectMapper.writeValueAsString(car);
System.out.println(carAsString);
//此時輸出:{"color":"blue","type":"c1","produceTime":"2020-06-06"}

處理集合

DeserializationFeature類提供的另一個小但有用的特性是能夠從JSON數組響應生成我們想要的集合類型。

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);

如上,我們將一個JsonArray字符串轉換成了對象數組。

我們也可以將其轉換成集合:

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

總結

Jackson是一個可靠而成熟的用於Java的JSON序列化/反序列化庫。ObjectMapper API提供了一種簡單的方法來解析和生成JSON響應對象,具有很大的靈活性。

歡迎訪問筆者博客:blog.dongxishaonian.tech

關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

Kubernetes-PV和PVC的原理和實踐

一、什麼是PV和PVC?

PV的全稱是Persistent Volume,翻譯過來為持久化存儲卷,是對底層的共享存儲的一種抽象,PV由管理員進行創建和配置,主要含存儲能力、訪問模式、存儲類型、回收策略、後端存儲類型等主要信息,它和具體的底層的共享存儲技術的實現方式有關,比如NFS、Hostpath、RBD等。

PVC的全稱是: PersistenVolumeClaim (持久化卷聲明),PVC是用戶存儲的一種聲明,PVC和Pod類似,Pod是消耗節點node資源,PVC消耗的是PV資源,Pod可以請求CPU的內存,而PVC可以請求特定的存儲空間和訪問模式。

二、PV和PVC的使用場景

 

 

 配圖來自K8S權威指南第四版

 

 存儲工程師把分佈式存儲系統上的總空間劃分成一個一個小的存儲塊,K8S的集群管理員將存儲塊和PV進行一一對應,用戶通過PVC對對存儲進行申請,比如可以指定具體容量的大小,訪問模式或者存儲類型,這樣的好處是用戶不需要關心底層的存儲實現細節,只需要直接申請使用PVC即可,若申請的PVC所對應的PV不能滿足用戶的要求,不會生效,直到有合適的PV生成,PVC會自動與PV完成綁定,存儲工程師、K8S管理員,用戶之間業務解耦,靈活性更強。

 

三、創建PV

PV支持多種不同類型的存儲,如:NFS、hostpath、RBD、ICCSI,本文以hostpath為例介紹如何創建PV

第一步:現在宿主機data目錄下data/pod/volume1,volume1將作為PV對應的hostpath本地存儲的目錄

第二步:通過yaml文件創建PV

 1 [root@k8s-master zhanglei]# cat pv-hostpath.yaml  2 kind: PersistentVolume #指定為PV類型  3 apiVersion: v1  4 metadata:  5   name: pv-statefulset #指定PV的名稱  6  labels: #指定PV的標籤  7  release: stable  8 spec:  9  capacity: 10     storage: 0.1Gi #指定PV的容量 11  accessModes: 12     - ReadWriteOnce #指定PV的訪問模式,簡寫為RWO,只支持掛在1個Pod的讀和寫 13  persistentVolumeReclaimPolicy: Recycle #指定PV的回收策略,Recycle表示支持回收,回收完成后支持再次利用 14  hostPath: #指定PV的存儲類型,本文是以hostpath為例 15     path: /data/pod/volume1 #指定PV對應後端存儲hostpath的目錄

說明:

 accessModes支持多種訪問模式

1)ReadWriteOnce(RWO):讀寫權限,但是只支持掛載在1個Pod

2)ReadOnlyMany(ROX):只讀權限,支持掛載在多個Pod

3)ReadWriteMany(RW):讀寫權限,支持掛載在多個Pod上

persistentVolumeReclaimPolicy的策略,指的是如果PVC被釋放掉后,PV的處理,這裏所說的釋放,指的是用戶刪除PVC后,與PVC對應的PV會被釋放掉,PVC個PV是一一對應的關係

1)Retain,PV的數據不會清理,會保留volume,如果需要清理,需要手動進行

2)Recycle,會將數據進行清理,即 rm -rf /thevolume/*(只有 NFS 和 HostPath 支持),清理完成后,PV會呈available狀態,支持再次的bound

3)Delete,刪除存儲資源,會刪除PV及後端的存儲資源,比如刪除 AWS EBS 卷(只有 AWS EBS, GCE PD, Azure Disk 和 Cinder 支持)

四、創建PVC

[root@k8s-master zhanglei]# cat pvc-hostpath.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mppvc-01                  # 指定PVC的名稱
  namespace: default
spec:
 accessModes: ["ReadWriteOnce"]    # 指定PVC的訪問模式
 resources:
   requests: 
     storage: 0.05Gi               # PVC申請的容量

說明:

1)PVC聲明了accessModes訪問類型為ReadWriteOnce,創建后,系統會自動去找能夠支持ReadWriteOnce訪問類型的PV,若無符合條件的PV,則不會進行綁定,

2)PVC聲明了storage的大小為0.05Gi,創建后,系統會自動去找能夠支持此容量的PV,通常PV的容量至少要大於或者等於0.05Gi才會去進行綁定

從這裏來看,對於用戶來說,即只需要聲明訪問類型、容量、另外還可通過StorageClass聲明具體的PV類型即可完成對持久化存儲卷的申請,而不需要去維護和關注後端存儲

五、查詢PV和PVC的

經過前面的步驟我們創建了PV和PVC,現在來看下兩者是否已經完成了綁定,在PV的創建已經指定了其名稱為pv-statefulset,PVC的名稱為mppvc-01

 

 

[root@k8s-master zhanglei]# kubectl get pv |grep pv-statefulset
pv-statefulset      107374182400m   RWO            Recycle          Bound    default/mppvc-01 

 

[root@k8s-master zhanglei]# kubectl get pvc |grep mppvc-01
mppvc-01                            Bound    pv-statefulset      107374182400m   RWO                           13d

可以看到pv-statefulset這個PV已經和mppvc-01的PVC進行了綁定(Bound),RWO和Recycle也是之前PV和PVC聲明的狀態,說明綁定成功


[root@k8s-master zhanglei]# kubectl describe pv pv-statefulset
Name:            pv-statefulset
Labels:          release=stable
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Bound
Claim:           default/mppvc-01
Reclaim Policy:  Recycle
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        107374182400m
Node Affinity:   <none>
Message:         
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /data/pod/volume1
    HostPathType:  
Events:            <none>
[root@k8s-master zhanglei]# kubectl describe pvc mppvc-01
Name:          mppvc-01
Namespace:     default
StorageClass:  
Status:        Bound
Volume:        pv-statefulset
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      107374182400m
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    <none>
Events:        <none>

再來看下PV的詳細(describe)信息,可以看到type是hostpath類型,显示了數據卷在宿主機的/data/pod/volume1的目錄。

 

六、總結

創建PV和PVC分為二步:

第一步:創建PV,支持自定義不同的存儲大小和訪問模式(RWX,RWO)、存放路徑、後端服務server(如hostpath、或NAS盤的數據盤的掛載點)

第二步:創建PVC,綁定到PV。創建PVC的時候可以指定PVC的request storage,即申請的存儲的容量,會根據申請的storage和訪問模式自動匹配符合要求的PV

創建完PV和PVC主要是為了使用它來達到實現持久化存儲的目的,如何進行使用請看本作者後續與statufulset有關的文章,謝謝閱讀~

 

作者簡介:雲計算容器\K8S方向產品經理,學點技術,為更好地設計產品。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

機器學習——十大數據挖掘之一的決策樹CART算法

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是機器學習專題的第23篇文章,我們今天分享的內容是十大數據挖掘算法之一的CART算法。

CART算法全稱是Classification and regression tree,也就是分類回歸樹的意思。和之前介紹的ID3和C4.5一樣,CART算法同樣是決策樹模型的一種經典的實現。決策樹這個模型一共有三種實現方式,前面我們已經介紹了ID3和C4.5兩種,今天剛好補齊這最後一種。

算法特點

CART稱為分類回歸樹,從名字上我們也看得出來,它既能支持分類又可以支持回歸。的確如此,決策樹的確支持回歸操作,但是我們一般不會用決策樹來進行回歸。這裏面的原因很多,除了樹模型擬合能力有限效果不一定好之外,還與特徵的模式有關係,樹回歸模型受到特徵的影響非常大。這個部分我們不做太多深入,之後會在回歸樹的文章當中詳細探討。

正因為回歸樹模型效果表現都不太理想,所以CART算法實現決策樹基本都是用來做分類問題。那麼在分類問題上,它與之前的ID3算法和C4.5算法又有什麼不同呢?

主要細究起來大約有兩點,第一點是CART算法使用Gini指數而不是信息增益來作為劃分子樹的依據,第二點是CART算法每次在劃分數據的時候,固定將整份數據拆分成兩個部分,而不是多個部分。由於CART每次將數據拆分成兩個部分,所以它對於拆分的次數沒有限制,而C4.5算法對特徵進行了限制,限制了每個特徵最多只能使用一次。因為這一點,同樣CART對於剪枝的要求更高,因為不剪枝的話很有可能導致樹過度膨脹,以至於過擬合。

Gini指數

在ID3和C4.5算法當中,在拆分數據的時候用的是信息增益和信息增益比,這兩者都是基於信息熵模型。信息熵模型本身並沒有問題,也是非常常用的模型。唯一的問題是,在計算熵的時候需要涉及到log運算,相比於四則運算來說,計算log要多耗時很多

Gini指數本質上也是基於信息熵模型,只是我們在計算的時候做了一些轉化,從而避免了使用log進行計算,加速了計算的過程。兩者的內在邏輯是一樣的。那怎麼實現的加速計算呢?這裏用到了高等數學當中的泰勒展開,我們將log運算通過泰勒公式展開,轉化成多項式的計算,從而加速信息熵的計算。

我們來做一個簡單的推導:

\[\begin{aligned} \ln(x) \approx \ln(x_0) + (x-x_0)\ln'(x_0) + o(x) \end{aligned} \]

我們把\(x_0 =1\)代入,可以得到:\(\ln(x)=x – 1 + o(x)\),其中o(x)是關於x的高階無窮小。我們把這個式子套入信息熵的公式當中:

\[\begin{aligned} H(x) &= -\sum_{i=1}^k p_i\ln p_i \\ &\approx \sum_{i=1}^k p_i(1-p_i) \end{aligned} \]

這個就是Gini指數的計算公式,這裏的pi表示類別i的概率,其實就是類別i的樣本佔全體樣本的比例。那麼上面的式子也可以看成是從數據集當中抽取兩條樣本,它們類別不一致的概率。

因此Gini指數越小,說明數據集越集中,也就是純度越高。它的概念等價於信息熵,熵越小說明信息越集中,兩者的概念是非常近似的。所以當我們使用Gini指數來作為劃分依據的時候,選擇的是切分之後Gini指數盡量小的切分方法,而不是盡量大的。

從上面的公式當中,我們可以發現相比於信息熵的log運算,Gini指數只需要簡單地計算比例和基礎運算就可以得到結果了,顯然運算速度要快得多。並且由於是通過泰勒展開逼近的,整體的性能也並不差,我們可以看下下面這張經典的圖感受一下:

從上圖當中可以看出來,Gini指數和信息熵的效果非常接近,一樣可以非常好地反應數據劃分的純度。

拆分與剪枝

剛才我們介紹CART算法特性的時候提到過,CART算法每次拆分數據都是二分的,這點和C4.5處理連續性特徵的邏輯很像。但有兩點不同,第一點是CART對於離散型和連續性特徵都如此操作,另外一點是,CART算法當中一個特徵可以重複使用。

舉個例子,在之前的算法當中,比如說西瓜的直徑是一個特徵。那麼當我們判斷過西瓜的直徑小於10cm之後,西瓜的直徑這個特徵就會從數據當中移除,之後再也不會用到。但是在CART算法當中不是如此,比如當我們先後根據西瓜的直徑以及西瓜是否有藤這兩個特徵對數據進行拆分之後,對於ID3和C4.5算法來說,西瓜的直徑這個特徵已經不可以再用來作為劃分的依據了,但是CART算法當中可以,我們仍然可以繼續使用之前已經用過的特徵。

我們用一張圖來展示,大概是下面這個樣子:

我們觀察一下最左側的子樹,直徑這個特徵出現了不止一次,這其實是很合理的。然而這也會有一個問題,就是由於沒有了特徵只能用一次這個限制,這樣會導致這棵樹無限膨脹,尤其是在連續性特徵很多的情況下,很容易陷入過擬合。為了放置過擬合,增加模型的泛化能力,我們需要對生成的這棵樹進行剪枝。

剪枝的方案主流的有兩種,一種是預剪枝,一種是后剪枝。所謂的預剪枝,即是在生成樹的時候就對樹的生長進行限制,防止過度擬合。而後剪枝則是在樹已經生成之後,對過擬合的部分進行修剪。其中預剪枝比較容易理解,比如我們可以限制決策樹在訓練的時候每個節點的數據只有在達到一定數量的情況下才會進行分裂,否則就成為恭弘=叶 恭弘子節點保留。或者我們可以限制數據的比例,當節點中某個類別的佔比超過閾值的時候,也可以停止生長。

后剪枝相對來說複雜一些,需要我們在生成樹之後通過一些機制尋找可以剪枝的部分,對整棵樹進行修剪。比如在CART算法當中常用的剪枝策略是CCP,它的英文全寫是Cost-Complexity Pruning,即代價複雜度剪枝。這個策略設計了一個指標來衡量一棵子樹的複雜度代價,我們可以對這個代價設置閾值來進行剪枝。

這個策略的精髓在於下面這個式子:

\[c = \frac{R(t) – R(T_t)}{|N_t| – 1} \]

這個式子當中的c就是指的剪枝帶來的代價,t代表剪枝之後的子樹,\(T_t\)表示剪枝之前的子樹。R(t)表示剪枝之後的誤差代價\(R(T_t)\)表示剪枝之前的誤差代價。其中誤差代價的定義是:\(R(t) = r(t) * p(t)\),r(t)是節點t的誤差率,p(t)是t上數據占所有數據的比例。

我們來看個例子:

假設我們知道所有數據一共有100條,那麼我們代入公式算一下,可以得到\(R(t) = r(t) * p(t) = \frac{11}{23} * \frac{23}{100} = \frac{11}{100}\)

子樹的誤差代價是:

\[R(T_t) = \sum R(i)=(\frac{2}{6}*\frac{6}{100})+ (\frac{0}{3}*\frac{3}{100}) + (\frac{2}{8}*\frac{8}{100})=\frac{4}{100} \]

所以可以得到\(c=\frac{11/100 – 4/100}{3 – 1}=\frac{7}{200}\)

c越大說明剪枝帶來的偏差越大,也就是說越不能剪,相反c很小說明偏差不大,可以減掉。我們只需要設置閾值,然後計算每一棵子樹的c來判斷是否能夠剪枝即可。

代碼實現

我們之前已經實現過了C4.5算法,再來實現CART可以說是非常簡單了,因為它相比於C4.5還少了離散類型這種情況,可以全部當做是連續型類型來處理。

我們只需要把之前的信息增益比改成Gini指數即可:

from collections import Counter

def gini_index(dataset):
    dataset = np.array(dataset)
    n = dataset.shape[0]
    if n == 0:
        return 0
    # sigma p(1-p) = 1 - sigma p^2
    counter = Counter(dataset[:, -1])
    ret = 1.0
    for k, v in counter.items():
        ret -= (v / n) ** 2
    return ret

def split_gini(dataset, idx, threshold):
    left, right = [], []
    n = dataset.shape[0]
    # 根據閾值拆分,拆分之後計算新的Gini指數
    for data in dataset:
        if data[idx] < threshold:
            left.append(data)
        else:
            right.append(data)
    left, right = np.array(left), np.array(right)
   	# 拆分成兩半之後,乘上所佔的比例
    return left.shape[0] / n * gini_index(left) + right.shape[0] / n * gini_index(right)

然後選擇拆分的函數稍微調整一下,因為Gini指數越小越好,之前的信息增益和信息增益比都是越大越好。代碼的框架基本上也沒有變動,只是做了一些微調:

def choose_feature_to_split(dataset):
    n = len(dataset[0])-1
    m = len(dataset)
    # 記錄最佳Gini,特徵和閾值
    bestGini = 1.0
    feature = -1
    thred = None
    for i in range(n):
        threds = get_thresholds(dataset, i)
        for t in threds:
            # 遍歷所有的閾值,計算每個閾值的信息增益比
            ratio = split_gini(dataset, i, t)
            if ratio < bestGini:
                bestGini, feature, thred = ratio, i, t
    return feature, thred

建樹和預測的部分都和之前C4.5算法基本一致,只需要去掉離散類型的判斷即可,大家可以參考一下之前文章當中的代碼。

總結

到這裏,我們關於決策樹模型的內容就算是結束了,我們從基本的決策樹原理,再到ID3、C4.5以及CART算法,都已經囊括了。這些知識儲備足以應對面試當中關於決策樹模型的問題了

雖然在實際的生產過程當中,我們已經用不到決策樹了,還不是基本用不到,幾乎是完全用不到。但是它的思想非常重要,是後續很多模型的基礎,比如隨機森林、GBDT等模型,都是在決策樹的基礎上建立起來的。所以我們深入理解決策樹的原理對於我們後續的進階學習非常重要。

最後, 我把完整的代碼發在了paste.ubuntu上,需要的同學可以在公眾號後台回復“決策樹”獲取。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

Spring 源碼學習 – 單例bean的實例化過程

本文作者:geek,一個聰明好學的同事

1. 簡介

開發中我們常用@Commpont,@Service,@Resource等註解或者配置xml去聲明一個類,使其成為spring容器中的bean,以下我將用從源碼角度看以AnnotationConfigApplicationContext為例看spring如何把帶有註解的類生成spring中bean。

2. 示例代碼

public class TestContext {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		SingleBean singleBean = context.getBean(SingleBean.class);
		System.out.println("<=====>"+singleBean.getTestStr());
	}
}
@ComponentScan("com.geek")
public class AppConfig {
}
@Component
public class SingleBean {
	private String testStr = "testStr";

	public String getTestStr() {
		return testStr;
	}
}

注意:以上代碼僅需要引入spring-context依賴即可。

3. 源碼分析

​ 上面的demo在調用AnnotationConfigApplicationContext構造函數的時候,AppConfig類會被註冊到AnnotatedBeanDefinitionReader,由這個reader把AppConfig解釋為beanDefination,從而被spring獲取到要實例化的類信息,以下為bean生產的源碼及其註釋。(源碼基於springFramework 5.1.X)

3.1 創建入口

​ 單例bean的創建的入口為DefaultListableBeanFactory.java#preInstantiateSingletons,下面源碼可見創建的bean條件為非抽象,非@LazyInit註解,Scope為singleTon(默認為singleTon)。

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}
		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		//所有可能需要去實例化的class(lazy,scope)
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			/**
			 * 非抽象,非懶初始化,單例bean
			 */
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				//非工廠bean實例化
				else {
					getBean(beanName);
				}
			}
		}
		/**
		 * 實例化完成后觸發實現了SmartInitializingSingleton方法的bean
		 * 的afterSingletonsInstantiated方法
 		 */
		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

3.2 創建前doGetBean代碼邏輯

getBean方法進來后便是直接調用doGetBean,doGetBean執行的源碼解釋如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		/**
		 * 獲取beanName
		 * 1,去掉factortoryBean前綴&
		 * 2,帶有別名的bean轉換為原來名字
		 */
		final String beanName = transformedBeanName(name);
		Object bean;
		// Eagerly check singleton cache for manually registered singletons.
		/**
		 * 從DefaultSingletonBeanRegistry的singletonObjects
		 * (spring內部用來緩存單例bean的currentHashMap)檢查是否存在該bean,不存在則創建
		 * 涉及單例模式下的循環依賴解決
		 */
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			/**
			 *獲取給定bean實例的對象,如果是Factory Bean,
			 * 則可以是bean實例本身或其創建的對象。
			 */
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
			// Fail if we're already creating this bean instance:
			// We're assembly within a circular reference.
			/**
			 * 原型模式下的bean存在循環依賴則會拋異常
			 */
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
			// Check if bean definition exists in this factory.
			/**
			 * 找不到則從父容器中查找
			 */
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
			try {
				/**
				 * 父容器中也找不到該bean,則需要重新實例化
				 * 1,獲取要實例化bean的beanDefinition,
				 * 2,檢查bean的實例化需要依賴的其他bean
				 */
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						/**
						 * 若給定的依賴 bean 已經註冊為依賴給定的bean
						 */
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							/**
							 * 遞歸調用getBean,優先創建依賴的bean
							 */
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}
				// Create bean instance.
				if (mbd.isSingleton()) {
					/**
					 * 通過調用DefaultSingletonBeanRegistry的getSingleton,從而調用核心方法createBean創建
					 */
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
		/**
		 * 檢查需要的bean類型是否符合
		 */
		// Check if required type matches the type of the actual bean instance.
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

doGetBean的操作流程如下:

1,執行transformedBeanName方法轉換beanName

​ 傳遞的參數可能是bean的alias或者為FactoryBean,transformedBeanName執行的操作:(1)若傳進來的FactoryBean(FactoryBean以&作為前綴標記),去掉&修飾符。(2)經過(1)的處理后,有alias的bean則從aliasMap中獲取bean的原始beanName。

2,從容器的緩存中獲取bean

​ getSingleton先從spring三級緩存中的第一級singletonObjects(Map結構)中獲取,若不存在,則檢查該bean是否正在創建isSingletonCurrentlyInCreation(beanName)) ,正在創建的bean會從二級緩存earlySingletonObjects(Map結構)獲取。獲取到緩存的bean後會調用AbstractBeanFactory#getObjectForBeanInstance轉換bean的實例本身返回。因為從緩存中拿到的可能是factoryBean,所以getObjectForBeanInstance需要把是通過從緩存factoryBeanObjectCache獲取或通過factory.getObject()獲得相應的bean返回。

3,bean實例化前檢查

​ (1)先檢查是否原型模式下的bean是否存在循環依賴,是則會拋異常。

​ (2)檢查父類工廠(parentBeanFactory)是否存在,存在則從parentBeanFactory中遞歸調用doGetBean。

​ (3)獲取改bean的beanDefinition,檢查該bean實例化過程中是否涉及依賴了其他的bean,若是則遞歸調用getBean,優先創建依賴的bean。(涉及單例下的循環以來解決,下篇文章詳細介紹)。

​ (4)對創建bean代碼加synchronized和執行beforeSingletonCreation(beanName)前置處理。

3.3 創建前createBean邏輯

​ 經過前面的doGetBean的一輪檢查與準備后,便在AbstractAutowireCapableBeanFactory#createBean中開始bean的創建。

/**
	 * Central method of this class: creates a bean instance,
	 * populates the bean instance, applies post-processors, etc.
	 * 解析指定 BeanDefinition 的 class
	 * 處理 override 屬性
	 * 實例化的前置處理
	 * 創建 bean
	 * @see #doCreateBean
	 */
	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;

		// Make sure bean class is actually resolved at this point, and
		// clone the bean definition in case of a dynamically resolved Class
		// which cannot be stored in the shared merged bean definition.
		/**
		 * 解釋bean的class,看beanDefinition是否有class,否則load class
		 */
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
		/**
		 * 對bean不存在lookup-method 和 replace-method
		 * 標記其方法的overloaded為false
		 */
		// Prepare method overrides.
		try {
			mbdToUse.prepareMethodOverrides();
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
					beanName, "Validation of method overrides failed", ex);
		}

		try {
			/**
			 * 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,
			 * 有代理對象則直接返回代理對象
			 */
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
			/**
			 * 無需代理的bean實例化
			 */
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			// A previously detected exception with proper bean creation context already,
			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

createBean的操作流程如下:

1,resolveBeanClass

​ 解釋beanDefinition的class,並且保存在beanDefinition中。

2,prepareMethodOverrides

​ 處理bean中的lookup-method (在單例bean用 @Lookup註解標記的方法,註解的方法返回的對象是原型)和 replace-method( 標記的方法,標記bean中A的方法被實現被另外一個實現MethodReplacer接口的B方法替代)。

3,resolveBeforeInstantiation

​ 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,有代理對象則直接返回代理對象。(spring AOP則是基於此處實現)

3.4 bean的真正實例化createBeanInstance

​ 在AbstractAutowireCapableBeanFactory#createBeanInstance中,真正創建bean,源碼及註釋如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		/**
		 * 通過提供supplier回調方法創建
		 */
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		/**
		 * 通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠
		 */
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}
		// Shortcut when re-creating the same bean...
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				/**
				 * 查找已經bean已經緩存解析的構造函數或者工廠方法
				 */
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		/**
		 * 已經有緩存的構造函數或者工廠方法,直接實例化
		 */
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}
		/**
		 * 通過實現BeanPostProcessor的代理類autowired的構造函數實例化
		 */
		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		/**
		 * 通過本身帶有autowired的構造函數實例化,通過調用反射newInstance實現
		 */
		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}
		/**
		 * 無參構造函數實例化,通過調用反射newInstance實現
		 */
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

由上述源碼可以看出,實例化bean操作流程如下:

1,如果存在 Supplier 回調,則通過提供supplier回調方法創建,如以下方式定義的bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Spring5Application.class)
public class BeanRegistrationTest {
    @Autowired
    private GenericWebApplicationContext context;
    
    context.registerBean(A.class, () -> new A());
}

2,如果存在工廠方法,則通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠,如以下方式定義的bean:

public class AFactory implements FactoryBean<A> {
	@Override
	public A getObject() throws Exception {
		return new A();
	}
	@Override
	public Class<?> getObjectType() {
		return A.class;
	}
}
//或:
@Configuration
public class BeanConfigration {
	@Bean
	public A a(){
		return new A();
	}

}

3,已經有緩存的構造函數或者工廠方法,直接實例化。

4,以上三點都不存在,則使用帶參構造函數與無參構造函數實例化。如以下方式定義的bean:

@Commponet
public class A{}

4.總結

​ spring單例bean的實例化流程大概就是這樣,很多細節地方,包括循環依賴處理,bean屬性填充等細節點下一章介紹。

參考

  • 《Spring 源碼深度解析》- 郝佳
  • 《死磕spring源碼》-chenssy

查看更多文章關注公眾號:好奇心森林

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

akka-typed(4) – EventSourcedBehavior in action

  前面提到過,akka-typed中較重要的改變是加入了EventSourcedBehavior。也就是說增加了一種專門負責EventSource模式的actor, 最終和其它種類的actor一道可以完美實現CQRS。新的actor,我還是把它稱為persistentActor,還是一種能維護和維持運行狀態的actor。即,actor內部狀態可以存放在數據庫里,然後通過一組功能函數來提供對狀態的處理轉變,即持續化處理persistence。當然作為一種具備EventSourcedBehavior的actor, 普遍應有的actor屬性、方法、消息處理協議、監管什麼的都還必須存在。在這篇討論里我們就通過案例和源碼來說明一下EventSourcedBehavior是如何維護內部狀態及作為一種actor又應該怎麼去使用它。

我們把上一篇討論里購物車的例子拿來用,再增加一些消息回復response機制,主要是彙報購物車狀態:

object ItemInfo { case class Item(name: String, price: Double) } object MyCart { import ItemInfo._ sealed trait Command sealed trait Event extends CborSerializable sealed trait Response //commands
  case class AddItem(item: Item) extends Command case object PayCart extends Command case class CountItems(replyTo: ActorRef[Response]) extends Command //event
  case class ItemAdded(item: Item) extends Event case object CartPaid extends Event //state
  case class CartLoad(load: List[Item] = Nil) //response
  case class PickedItems(items: List[Item]) extends Response case object CartEmpty extends Response val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) => cmd match { case AddItem(item) => Effect.persist(ItemAdded(item)) case PayCart => Effect.persist(CartPaid) case CountItems(replyTo) => Effect.none.thenRun { cart => cart.load match { case Nil => replyTo ! CartEmpty case listOfItems => replyTo ! PickedItems(listOfItems) } } } } val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) => evt match { case ItemAdded(item) => state.copy(load = item :: state.load) case CartPaid => state.copy(load = Nil) } } def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ) } object Shopper { import ItemInfo._ sealed trait Command extends CborSerializable case class GetItem(item: Item) extends Command case object Settle extends Command case object GetCount extends Command case class WrappedResponse(res: MyCart.Response) extends Command def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx => val shoppingCart = ctx.spawn(MyCart(), "shopping-cart") val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse) Behaviors.receiveMessage { msg => msg match { case GetItem(item) => shoppingCart ! MyCart.AddItem(item) case Settle => shoppingCart ! MyCart.PayCart case GetCount => shoppingCart ! MyCart.CountItems(cartRef) case WrappedResponse(res) => res match { case MyCart.PickedItems(items) => ctx.log.info("**************Current Items in Cart: {}*************", items) case MyCart.CartEmpty => ctx.log.info("**************shopping cart is empty!***************") } } Behaviors.same } } } object ShoppingCart extends App { import ItemInfo._ val shopper = ActorSystem(Shopper(),"shopper") shopper ! Shopper.GetItem(Item("banana",11.20)) shopper ! Shopper.GetItem(Item("watermelon",4.70)) shopper ! Shopper.GetCount shopper ! Shopper.Settle shopper ! Shopper.GetCount scala.io.StdIn.readLine() shopper.terminate() }

實際上EventSourcedBehavior里還嵌入了回復機制,完成一項Command處理后必須回復指令方,否則程序無法通過編譯。如下:

private def withdraw(acc: OpenedAccount, cmd: Withdraw): ReplyEffect[Event, Account] = { if (acc.canWithdraw(cmd.amount)) Effect.persist(Withdrawn(cmd.amount)).thenReply(cmd.replyTo)(_ => Confirmed) else Effect.reply(cmd.replyTo)(Rejected(s"Insufficient balance ${acc.balance} to be able to withdraw ${cmd.amount}")) }

不過這個回復機制是一種副作用。即,串連在Effect產生之後立即實施。這個動作是在eventHandler之前。在這個時段無法回復最新的狀態。

說到side-effect, 如Effect.persist().thenRun(produceSideEffect): 當成功持續化event后可以安心進行一些其它的操作。例如,當影響庫存數的event被persist后可以馬上從賬上扣減庫存。

在上面這個ShoppingCart例子里我們沒有發現狀態轉換代碼如Behaviors.same。這隻能是EventSourcedBehavior屬於更高層次的Behavior,狀態轉換已經嵌入在eventHandler里了,還記着這個函數的款式吧  (State,Event) => State, 這個State就是狀態了。

Events persist在journal里,如果persist操作中journal出現異常,EventSourcedBehavior自備了安全監管策略,如下:

  def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds))

值得注意的是:這個策略只適用於onPersistFailure(),從外部用Behaviors.supervisor()包嵌是無法實現處理PersistFailure效果的。但整個actor還是需要一種Backoff策略,因為在EventSourcedBehavior內部commandHandler,eventHandler里可能也會涉及一些數據庫操作。在操作失敗后需要某種Backoff重啟策略。那麼我們可以為actor增加監控策略如下:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

現在這個MyCart可以說已經是個安全、強韌性的actor了。

既然是一種persistentActor,那麼持久化的管理應該也算是核心功能了。EventSourcedBehavior通過接收信號提供了對持久化過程監控功能,如:

 def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) } } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

EventSourcedBehavior.receiveSignal是個偏函數:

  def receiveSignal(signalHandler: PartialFunction[(State, Signal), Unit]): EventSourcedBehavior[Command, Event, State]

下面是一個EventSourcedBehavior Signal 清單:

sealed trait EventSourcedSignal extends Signal @DoNotInherit sealed abstract class RecoveryCompleted extends EventSourcedSignal case object RecoveryCompleted extends RecoveryCompleted { def instance: RecoveryCompleted = this } final case class RecoveryFailed(failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure } final case class SnapshotCompleted(metadata: SnapshotMetadata) extends EventSourcedSignal { def getSnapshotMetadata(): SnapshotMetadata = metadata } final case class SnapshotFailed(metadata: SnapshotMetadata, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getSnapshotMetadata(): SnapshotMetadata = metadata } object SnapshotMetadata { /** * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ def apply(persistenceId: String, sequenceNr: Long, timestamp: Long): SnapshotMetadata =
    new SnapshotMetadata(persistenceId, sequenceNr, timestamp) } /** * Snapshot metadata. * * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ final class SnapshotMetadata(val persistenceId: String, val sequenceNr: Long, val timestamp: Long) { override def toString: String = s"SnapshotMetadata($persistenceId,$sequenceNr,$timestamp)" } final case class DeleteSnapshotsCompleted(target: DeletionTarget) extends EventSourcedSignal { def getTarget(): DeletionTarget = target } final case class DeleteSnapshotsFailed(target: DeletionTarget, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getTarget(): DeletionTarget = target } final case class DeleteEventsCompleted(toSequenceNr: Long) extends EventSourcedSignal { def getToSequenceNr(): Long = toSequenceNr } final case class DeleteEventsFailed(toSequenceNr: Long, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getToSequenceNr(): Long = toSequenceNr }

當然,EventSourcedBehavior之所以能具備自我修復能力其中一項是因為它有對持久化的事件重演機制。如果每次啟動都需要對所有歷史事件進行重演的話會很不現實。必須用snapshot來濃縮歷史事件:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) }.snapshotWhen { case (state,CartPaid,seqnum) => ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state) true
          case (state,event,seqnum) => false }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

下面是本次示範的源碼:

build.sbt

name := "learn-akka-typed"

version := "0.1"

scalaVersion := "2.13.1"
scalacOptions in Compile ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint")
javacOptions in Compile ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")

val AkkaVersion = "2.6.5"
val AkkaPersistenceCassandraVersion = "1.0.0"


libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-cluster-sharding-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-query" % AkkaVersion,
  "com.typesafe.akka" %% "akka-serialization-jackson" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-cassandra" % AkkaPersistenceCassandraVersion,
  "com.typesafe.akka" %% "akka-slf4j" % AkkaVersion,
  "ch.qos.logback"     % "logback-classic"             % "1.2.3"
)

application.conf

akka.actor.allow-java-serialization = on
akka {
  loglevel = DEBUG
  actor {
    serialization-bindings {
      "com.learn.akka.CborSerializable" = jackson-cbor
    }
  }
  # use Cassandra to store both snapshots and the events of the persistent actors
  persistence {
    journal.plugin = "akka.persistence.cassandra.journal"
    snapshot-store.plugin = "akka.persistence.cassandra.snapshot"
  }

}
akka.persistence.cassandra {
  # don't use autocreate in production
  journal.keyspace = "poc"
  journal.keyspace-autocreate = on
  journal.tables-autocreate = on
  snapshot.keyspace = "poc_snapshot"
  snapshot.keyspace-autocreate = on
  snapshot.tables-autocreate = on
}

datastax-java-driver {
  basic.contact-points = ["192.168.11.189:9042"]
  basic.load-balancing-policy.local-datacenter = "datacenter1"
}

ShoppingCart.scala

package com.learn.akka

import akka.actor.typed._
import akka.persistence.typed._
import akka.actor.typed.scaladsl.Behaviors
import akka.persistence.typed.scaladsl._
import scala.concurrent.duration._

object ItemInfo {
  case class Item(name: String, price: Double)
}

object MyCart {
 import ItemInfo._

  sealed trait Command
  sealed trait Event extends CborSerializable
  sealed trait Response

  //commands
  case class AddItem(item: Item) extends Command
  case object PayCart extends Command
  case class CountItems(replyTo: ActorRef[Response]) extends Command

  //event
  case class ItemAdded(item: Item) extends Event
  case object CartPaid extends Event

  //state
  case class CartLoad(load: List[Item] = Nil)

  //response
  case class PickedItems(items: List[Item]) extends Response
  case object CartEmpty extends Response

  val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) =>
    cmd match {
      case AddItem(item) =>
        Effect.persist(ItemAdded(item))
      case PayCart =>
        Effect.persist(CartPaid)
      case CountItems(replyTo) =>
        Effect.none.thenRun { cart =>
          cart.load match {
            case Nil =>
              replyTo ! CartEmpty
            case listOfItems =>
              replyTo ! PickedItems(listOfItems)
          }
        }
    }
  }

  val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) =>
    evt match {
      case ItemAdded(item) =>
         state.copy(load = item :: state.load)
      case CartPaid =>
        state.copy(load = Nil)
    }
  }

  def apply(): Behavior[Command] =
    Behaviors.supervise(
      Behaviors.setup[Command] { ctx =>
        EventSourcedBehavior[Command, Event, CartLoad](
          persistenceId = PersistenceId("10", "1013"),
          emptyState = CartLoad(),
          commandHandler = commandHandler,
          eventHandler = eventHandler
        ).onPersistFailure(
          SupervisorStrategy
            .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
            .withMaxRestarts(3)
            .withResetBackoffAfter(10.seconds)
        ).receiveSignal {
          case (state, RecoveryCompleted) =>
            ctx.log.info("**************Recovery Completed with state: {}***************",state)
          case (state, SnapshotCompleted(meta))  =>
            ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr)
          case (state,RecoveryFailed(err)) =>
            ctx.log.error("recovery failed with: {}",err.getMessage)
          case (state,SnapshotFailed(meta,err)) =>
            ctx.log.error("snapshoting failed with: {}",err.getMessage)
        }.snapshotWhen {
          case (state,CartPaid,seqnum) =>
            ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state)
            true
          case (state,event,seqnum) => false
        }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2))
      }
    ).onFailure(
      SupervisorStrategy
        .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
        .withMaxRestarts(3)
        .withResetBackoffAfter(10.seconds)
    )
}

object Shopper {

  import ItemInfo._

  sealed trait Command extends CborSerializable

  case class GetItem(item: Item) extends Command
  case object Settle extends Command
  case object GetCount extends Command

  case class WrappedResponse(res: MyCart.Response) extends Command

  def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx =>
    val shoppingCart = ctx.spawn(MyCart(), "shopping-cart")
    val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse)
    Behaviors.receiveMessage { msg =>
      msg match {
        case GetItem(item) =>
          shoppingCart ! MyCart.AddItem(item)
        case Settle =>
          shoppingCart ! MyCart.PayCart
        case GetCount =>
          shoppingCart ! MyCart.CountItems(cartRef)
        case WrappedResponse(res) => res match {
          case MyCart.PickedItems(items) =>
            ctx.log.info("**************Current Items in Cart: {}*************", items)
          case MyCart.CartEmpty =>
            ctx.log.info("**************shopping cart is empty!***************")
        }
      }
      Behaviors.same
    }
  }

}


object ShoppingCart extends App {
  import ItemInfo._
  val shopper = ActorSystem(Shopper(),"shopper")
  shopper ! Shopper.GetItem(Item("banana",11.20))
  shopper ! Shopper.GetItem(Item("watermelon",4.70))
  shopper ! Shopper.GetCount
  shopper ! Shopper.Settle
  shopper ! Shopper.GetCount
  scala.io.StdIn.readLine()

  shopper.terminate()

}

 

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

鋁複合材料能存氣體 氫氣車電池新招

摘錄自2020年4月19日聯合新聞網報導

美國西北大學法爾哈教授所帶領的團隊,最近成功研發一種以鋁做為主元素的複合材料:NU-1501,有望成為氫氣車大量儲存氣體的新選項。該材料在結構上與海綿相似,擁有許多小孔可儲存氣體,NU-1501可透過壓力的不同來儲存或釋放氣體。比起現有的氣體儲存槽更加安全、輕量與便宜,若加以應用,有極大潛力。

為了降低交通運輸業所造成的碳排放,可達到零碳排的氫氣車近年來成為重要的研究主軸。氫氣車主要透過氫和氧的化學反應來產生電,過程並不會產生任何二氧化碳。但因為氫氣十分易燃且體積龐大,在未加壓的狀況下,跑100公里需要11,000公升的氫氣。

目前業界主要採取將氫氣加壓到700巴,比汽車胎壓還要高出300倍,除了有安全疑慮外,特別設計過的氣體儲存槽也相當笨重與昂貴。而NU-1051除了能攜帶更多的氫氣外,也只需要比較低的壓力,因此十分具有發展潛力。

能源議題
環境經濟
能源轉型
循環經濟
國際新聞
氫氣車
海綿

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

前有強敵後有追兵 看起亞新一代K2如何破局

這樣的小細節,正是對起亞追求的時尚和年輕最好的體現。除此外,新一代K2的座椅也是一大亮點。作為一款小型車,亮棕色的運動座椅很好的提升了新車的檔次感。不僅如此,這張座椅的支撐性和包裹性都做的很不錯。整體來說,這套座椅無論是從觀感還是乘坐感受都達到了同級車型第一陣營,即便是對標部分高一級別車型也毫不遜色。

隨着國內購車群體逐年呈年輕化,如今不少“95后”都開始加入了“買車大軍”。那麼問題來了,人生第一台車該怎麼選?回答這個問題之前,我們要弄清楚年輕朋友們喜歡什麼。

看臉的時代,長得漂亮是首要因素。對於年輕朋友,甭管是找對象還是買車,第一眼肯定是看顏值;此外在互聯網下成長起來的年輕人,對車內的科技配置同樣非常看中,因此豐富的配置也是不可或缺的一點;除了以上兩點,還有最重要的就是價格,年輕朋友都買得起的合理售價才是一切的前提,否則都成為空談。

經過一輪篩選,叫獸發現起亞剛剛上市的新一代K2恰好滿足以上幾點。接下來大家就來看看它究竟有那些亮點可以列入到年輕朋友們的備選清單呢?

你如果夠漂亮,我才會對你產生興趣

韓系車一向走的是時尚年輕的路線,老款K2在當年絕對能算得上是同級里的“顏值擔當”,深得年輕朋友們的喜歡。新一代K2採用了起亞最新的家族前臉,中網和大燈變化令新車的視覺衝擊力更強,整體看上去大氣了不少。

側面相比老款變得更加修長,讓人又找到了“小K5”的影子。同時新一代K2的車身尺寸也有所加長,新車的長寬高分別達到4400/1740/1460mm,軸距則由2570mm提升為2600mm。對於一款小型車而言,這樣的變化令叫獸對它的空間表現非常期待。

車尾呈現了簡潔和精緻的一面,時下流行的貫穿式尾燈令新一代K2尾部的辨識度大大提高,同時也體現出東風悅達起亞對時尚和年輕不變的追求。同為年輕人的叫獸表示很喜歡這樣的設計。

叫獸一直在強調一個觀點,作為一款家用車,如果不能達到奔馳那般近乎完美的內飾設計,還是以簡約大方為主比較好,過於繁複反倒會降低乘客的好感度。新一代K2運用了環繞一體型的空間布局,大大強化了人體工程學的設計理念,不少細節處的變化則增添了幾分時尚氣息。正是這樣的設計風格和搭配很好的迎合了年輕客戶的審美。

例如這個全新的三幅式多功能運動方向盤,握感細膩並且大小適中,底部的亮色裝飾堪稱點睛之筆,融入了些許運動感。這樣的小細節,正是對起亞追求的時尚和年輕最好的體現。

除此外,新一代K2的座椅也是一大亮點。作為一款小型車,亮棕色的運動座椅很好的提升了新車的檔次感。不僅如此,這張座椅的支撐性和包裹性都做的很不錯。整體來說,這套座椅無論是從觀感還是乘坐感受都達到了同級車型第一陣營,即便是對標部分高一級別車型也毫不遜色。

沒想到小型車居然有這麼給力的空間

Surprise!

說到了座椅,當然不能少了小型車最受關注的空間問題。正如前文所說,新一代K2的車身尺寸有所加長,那它的空間表現究竟如何呢?

身高175cm的體驗者調整好坐姿以後,頭部尚有1拳以上的空間;來到後排,新一代K2的後排空間達到了接近2拳的距離,這樣的表現已經可以媲美不少緊湊級車型。

最令叫獸驚喜的是,新一代K2的後排隆起非常低,這樣一來後排中間的乘客再也不用憋屈着腿了。

互聯功能將是俘獲年輕朋友的秘密武器

諸如多功能真皮方向盤、倒車影像、日間行車燈甚至是後排出風口都能在新一代K2身上悉數見到,叫獸在這裏要着重講的是中間那塊多功能觸控屏。

在幾乎人手一部智能手機的時代,新一代K2的中控屏加入了CarLife和Carplay兩大智能互聯繫統。值得一提的是,這項配置再也不是蘋果用戶獨享,有了CarLife,安卓用戶同樣可以享用這項功能。這就意味着,99%的客戶都能通過手機在車內實現導航、無線音樂等等功能。要知道這對追求實用至上的小型車來說是一項多麼實用和好玩的配置。

可不只是徒有其表

老K2那套4AT變速箱放到如今來看的確有些不太入流,起亞當然意識到了這一點,於是給新一代K2換裝了全新6速手自一體變速箱和6擋手動變速箱。這樣的升級對新車的油耗和駕駛平順性無疑將會有非常好的改善。

面對強敵,新一代K2擁有媲美緊湊型車的大空間和高配置;面對追兵,新一代K2有着堪稱“顏值擔當”的造型設計以及品牌影響力。這麼一款長得漂亮,空間還大,配置夠潮夠豐富的小型車,我心動了,你呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準