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維修中心

※超省錢租車方案

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

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

日本大鯢數量急減 雜交問題嚴重 水族館推大鯢布偶提升國民關注

文:宋瑞文

本站聲明:網站內容來源環境資訊中心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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

十來萬買車要能上班還能自駕游,你的城市適合哪輛車?

廣汽菲克-Jeep自由本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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

這款顏值超高的合資小車妹子看了都喜歡,而且可能7萬起

有了這種材質,某些司機要偷着樂了。相對於前臉、車身的個性,尾部的設計要平常許多了。內飾部分整體設計偏向矩形的設計,雙色搭配也是恰到好處,看起來和外觀一樣也是活力十足,C3的設計師說內飾的設計靈感主要來源於家庭室內設計,力求為C3設計出更為現代、舒適的座艙。

最近看到不少讀者在後台留言說想要了解一下雪鐵龍C3的情況,所以今天我們就來看一下這款個性十足的法系車-雪鐵龍C3。

說起雪鐵龍C3,大家的第一反應應該是C3-XR吧,但是這個C3可不是大家熟悉的那個小型SUV C3-XR,而是雪鐵龍的的一款小型車。

C3的歷史要比C3-XR更長,因為第一代的C3早在2002年的時候就推出了。一經推出就獲得了熱銷,到目前為止累計銷量近400萬台,當然,C3的主要戰場還是在歐洲。

全新一代的雪鐵龍C3早在今年9月份就發布了,一發布便引起了熱議,因為這款非常個性的小車給大家留下了深刻的印象。

C3的長度只有3990mm,其他尺寸暫時未知,長度還不到四米,所以C3看起來比較短小,但是較短的尺寸並不會影響法國人天馬行空的設計。C3的造型極其可愛,前臉渾圓一體,造型獨特。

比如前臉獨特的大燈組設計,LED日行燈和引擎蓋部分的鍍鉻裝飾條相互融合,大燈和霧燈造型為凹進去矩形設計,看起來比較別緻。

前臉的造型已經夠獨特了,側面的設計也充滿看點,轉向指示燈裝在了有着黑色塗裝的后視鏡上。整個車身側面的個性塗裝讓C3看起來更加與眾不同。

不過輪圈的造型,也會讓我過目不忘的。

既然喜歡玩個性,那就一玩到底,C3將會提供9種車身顏色和3種車頂顏色,總計36種顏色組合,滿足消費者對個性的需求。

新車還採用了Airbump技術,特殊的TpU柔軟材質可以抵抗輕微的刮蹭。有了這種材質,某些司機要偷着樂了。

相對於前臉、車身的個性,尾部的設計要平常許多了。

內飾部分整體設計偏向矩形的設計,雙色搭配也是恰到好處,看起來和外觀一樣也是活力十足,C3的設計師說內飾的設計靈感主要來源於家庭室內設計,力求為C3設計出更為現代、舒適的座艙。

空調出風口搞成這個樣子,估計只有法國人能幹出來。新車也會有全景天窗,只是不能開啟。

至於空間,肯定是比較局促的,正常體格的成年人坐在後排不管是頭部空間還是腿部空間都不會太寬敞。

C3的發動機為1.2T三缸汽油發動機和1.6T柴油發動機,其中1.2T發動機將會有三種不同的動力調教,變速箱為手動擋和6擋自動。

遺憾的是這個車子目前會在歐洲上市,中國未來只是有可能引進,畢竟雪鐵龍在國內沒有一款小型車。如果真的引進國內國產了,小編希望原封不動的引進,或者盡可能的保持原來的面貌。如果售價能七萬起,憑藉如此個性的C3,絕對會吸引年輕消費者的青睞。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

聽說這是20萬內老婆最滿意的車

它還是會打一個激靈再往前沖。不過調到運動模式之後,你才會更深的感受到這輛車的動力。儀錶盤隨即變為紅色主題,好像是有點鋼炮的意思。只不過它增加的是心理馬力,但是對於速度的提升還是很明顯的。V40的懸挂調校是那種偏硬的感覺,對於車身的支撐是相當足,但是舒適性和1系來比會打點折扣。

自從吉利把沃爾沃收了之後,大家對這個北歐品牌的熟悉程度就更高了。只不過知名度歸知名度,要真是讓你買車選一輛沃爾沃,可能大家都未必願意。所以今天就給大家試一下它的入門級車型-V40。

就在試駕車剛剛到公司,小喬就撲了上去,並引發了以下對話…

既然是北歐的廠商,那必然要帶有北歐特色才行。沒錯,“雷神之錘”大家都聽說過了吧,說的就是沃爾沃的頭燈,V40打開日間行車燈后,視覺效果確實很不錯,但這種畫風更適合年輕買家。相對於能見度較高的A3、1系或A級來說,V40就是小眾但不失氣質的存在。

V40提供了T3/T4/T5三個代號的車型選擇,分別搭載1.5T/2.0T四缸發動機。畢竟是輛買菜車,V40的動力輸出都是相當穩當般的存在。正常模式下想急加速?它還是會打一個激靈再往前沖。

不過調到運動模式之後,你才會更深的感受到這輛車的動力。儀錶盤隨即變為紅色主題,好像是有點鋼炮的意思。只不過它增加的是心理馬力,但是對於速度的提升還是很明顯的。

V40的懸挂調校是那種偏硬的感覺,對於車身的支撐是相當足,但是舒適性和1系來比會打點折扣。值得一提的是V40的隔音相當不錯,但問題是這個不錯只局限於風噪。正常行駛下路面的噪聲就直接從車底傳進來,這很尷尬,但也沒辦法。

V40的內飾還是保持了一貫的沃爾沃風格。最明顯的就是中控台萬年不變的数字鍵。而它又長又大的手剎把就成為了中控台的一道風景。

反觀V40的空間表現,還是能讓家人滿意的。最起碼它的後排空間沒有給人覺得任何憋屈的感覺。

V40作為一輛入門級車型,官方售價終端為18.89-30.99萬元,對於那些追求獨特的年輕車主來說,這是一個不小的誘惑。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

動力橫評|跑進10秒 海馬S5領先不止一點

在0-100公里加速上,海馬S5-9。81秒、長安CS35-11。02秒、寶駿560-12。01秒。從加速上來看,在8萬級別中,能進入10秒的真是屈指可數。相比長安CS35和寶駿560來說,在整個加速過程中,海馬S5在起步的瞬間動力來得更加直接,而且在中後段也不會出現乏力情況。

在市場表現上,售價8萬至10萬的8萬級SUV近兩年相當火爆。針對上述情況,找了能見度比較高的三款八萬級熱銷車型,在動力方面做個實測對比,(為什麼對比動力?因為注重內涵,外觀下次測)這次對比的三款車型分別是:長安CS35、海馬S5、寶駿560、均是1.5T動力總成

從上表的綜合數據來看,三者的動力系統都算主流,不謙虛地說也算是這個級別里相對靠前的,不過海馬S5的參數表現都要略優於其它兩款車型。三款對比車型均採用1.5T渦輪增壓發動機,海馬S5的最大馬力為163ps、最大扭矩223N·m;寶駿560的最大馬力150ps,最大扭矩230N·m;長安CS35的最大馬力156ps,最大扭矩215N·m。從賬面數據來看,海馬S5的優勢非常明顯。

此外,海馬S5的1.5T渦輪增壓發動機採用了新一代小慣量渦輪,恭弘=叶 恭弘輪採用最新一代合金材料,重量更輕,慣量降低35%,有效減小渦輪遲滯,提高系統響應性,滿足低轉速大扭矩驅動,發動機1600轉時就能輸出90%最大扭矩。

從發動機的動力表現,三者在實際駕駛中,均有着不錯的親和力,起步反饋輕快,低速行駛時動力與變速箱的配合也相當不錯。但海馬S5在動力上的體驗會來得更加地直接,當發動機轉速僅為1000rpm時,就能體驗到渦輪介入,在1800rpm狀態下,即能體驗到223N·m的最大扭矩輸出狀態,且能一直保持到4000rpm,動力輸出水平在同級車型中相當出色,且最大扭矩轉速區間很寬。相比之下,寶駿560的1.5T發動機,在2000rpm時才能達到最大扭矩輸出,最大扭矩轉速區間為2000-3800rpm,明顯劣於海馬S5。

在0-100公里加速上,海馬S5-9.81秒、長安CS35-11.02秒、寶駿560-12.01秒。從加速上來看,在8萬級別中,能進入10秒的真是屈指可數。相比長安CS35和寶駿560來說,在整個加速過程中,海馬S5在起步的瞬間動力來得更加直接,而且在中後段也不會出現乏力情況。

在剎車對比測試中,海馬S5進入了40米內,如此優秀的剎車成績,可以讓它在大部分同級別、同價位的競爭對手面前炫耀一番。須知道這個級別大部分車型的100-0km/h剎車距離都是40米開外,有的甚至是43米之多。除了剎車成績優秀以外,其剎車踏板反饋回來的腳感也很棒,不僅虛位小,而且回饋力度比較均勻線性。

從動力輸出到制動力的表現,可以看出在三款車型當中,海馬S5數據和實力明顯優於寶駿560和長安CS35。

寶駿560-低速扭矩力量足夠,1擋到4擋的提速感覺明顯有力,但是進入5擋,時速超過100公里/小時的時候,提速就會顯得很乏力。

長安CS35-發機機是三菱的4G15T改造而來,加速只能說夠用,不能深踩,高轉速時發動機的聲音讓人奔潰。

海馬S5-上車地板油S檔,兩千轉加速明顯,還有推背感,雖然不算強,但能有這個的體驗反饋給你,後段的力量儲備也是相對充足,在賽道直路最快跑165時速,底盤不會給你坐船的感覺,懸挂也是支撐到位,在高轉速時,發動機也是會有聲音但是,在一個可接受範圍。

回看市場,如今小型SUV由不入流,到成為主流,最多也不過5年,但是市場卻發生了翻天覆地的變化的變化,如今的小型SUV市場競爭已然也到了白熱化的地步。縱觀國內小型SUV細分市場,本田、現代/起亞、別克、鈴木、福特、雪佛蘭、Jeep等國際大廠都推出了合資小型SUV,在品質上力壓自主車型,同時價錢也在不斷下探,新老車型的格局正在逐漸形成,而這種格局勢必會引起小型SUV市場的一些變革,花無百日紅,新生代小型SUV正在陸續到來,如果產品力可以經得起考驗的情況下,小型SUV市場或將很快迎來一次大換血,讓我們拭目以待吧。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

如何監控 Linux 服務器狀態?

Linux 服務器我們天天打交道,特別是 Linux 工程師更是如此。為了保證服務器的安全與性能,我們經常需要監控服務器的一些狀態,以保證工作能順利開展。

本文介紹的幾個命令,不僅僅適用於服務器監控,也適用於我們日常情況下的開發。

1. watch

watch 命令我們的使用頻率很高,它的基本作用是,按照指定頻率重複執行某一條指令。使用這個命令,我們可以重複調用一些命令來達到監控服務器的作用。

默認情況下,watch 命令的執行周期是 2 秒,但我們可以使用 -n 選項來指定運行頻率,比如我們想要每隔 5 秒執行 date 命令,可以這麼執行:

$ watch -n 5 date

一台服務器肯定有多人在用,特別是本部門的小夥伴。對於這些小夥伴有沒渾水摸魚,我們可以使用一些命令來監控他們。

我們可以每隔 10 秒執行 who 命令,來看看都有誰在使用服務器。

$ watch -n 10 who
Every 10.0s: who                             butterfly: Tue Jan 23 16:02:03 2019

shs      :0           2019-01-23 09:45 (:0)
dory     pts/0        2019-01-23 15:50 (192.168.0.5)
alvin     pts/1        2019-01-23 16:01 (192.168.0.15)
shark    pts/3        2019-01-23 11:11 (192.168.0.27)

如果發現系統運行很慢,我們可以調用 uptime 命令來查看系統平均負載情況。

$ watch uptime
Every 2.0s: uptime                           butterfly: Tue Jan 23 16:25:48 2019

 16:25:48 up 22 days,  4:38,  3 users,  load average: 1.15, 0.89, 1.02

一些關鍵的進程肯定不能掛,否則可能會影響到業務開展,所以我們可以重複統計服務器中的所有進程數量。

$ watch -n 5 'ps -ef | wc -l'
Every 5.0s: ps -ef | wc -l                   butterfly: Tue Jan 23 16:11:54 2019

245

想動態知道服務器內存使用情況,可以重複執行 free 命令。

$ watch -n 5 free -m
Every 5.0s: free -m                          butterfly: Tue Jan 23 16:34:09 2019

              total        used        free      shared  buff/cache   available
Mem:           5959         776        3276          12        1906        4878
Swap:          2047           0        2047

當然不僅僅是這些,我們還可以重複調用很多命令來對服務器一些關鍵參數進行監控,

2. top

使用 top 命令我們可以知道系統的很多關鍵參數,而且是動態更新的。默認情況下,top 監控的是系統的整體狀態,如果我們只想知道某個人的使用情況,可以使用 -u 選項來指定這個人。

$ top -u alvin
top - 16:14:33 up 2 days,  4:27,  3 users,  load average: 0.00, 0.01, 0.02
Tasks: 199 total,   1 running, 198 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.2 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   5959.4 total,   3277.3 free,    776.4 used,   1905.8 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   4878.4 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
23026 alvin      20   0   46340   7820   6504 S   0.0   0.1   0:00.05 systemd
23033 alvin      20   0  149660   3140     72 S   0.0   0.1   0:00.00 (sd-pam)
23125 alvin      20   0   63396   5100   4092 S   0.0   0.1   0:00.00 sshd
23128 alvin      20   0   16836   5636   4284 S   0.0   0.1   0:00.03 zsh

在這個結果里,你不僅僅可以看到 alvin 這個用戶運行的所有的進程數,也可以看到每個進程所消耗的系統資源(CPU,內存),同時依然可以看到整個系統的關鍵參數。

3. ac

如果你想知道每個用戶登錄服務器所使用的時間,你可以使用 ac 命令。這個命令需要你安裝 acct 包(Debian)或 psacct 包(RHEL,Centos)。

如果我們想知道所有用戶登陸服務器所使用的時間之和,我們可以直接運行 ac 命令,無需任何參數。

$ ac
        total     1261.72

如果我們想知道各個用戶所使用時間,可以加上 -p 選項。

$ ac -p
        shark                                5.24
        alvin                                5.52
        shs                               1251.00
        total     1261.76

我們還可以通過加上 -d 選項來查看具體每一天用戶使用服務器時間之和。

$ ac -d | tail -10
Jan 11  total        0.05
Jan 12  total        1.36
Jan 13  total       16.39
Jan 15  total       55.33
Jan 16  total       38.02
Jan 17  total       28.51
Jan 19  total       48.66
Jan 20  total        1.37
Jan 22  total       23.48
Today   total        9.83

小結

我們可以使用很多命令來監控系統的運行狀態,本文主要介紹了三個:watch 命令可以讓你重複執行某一條命令來監控一些參數的變化,top 命令可以查看某個用戶運行的進程數以及消耗的資源,而 ac 命令則可以查看每個用戶使用服務器時間。你經常使用哪個命令呢?歡迎留言討論!

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準