分類
發燒車訊

Model 3將不會向使用者提供免費充電服務

據外媒報導,Model 3實惠的價格引發了消費者的熱烈預訂,不過特斯拉CEO艾隆•馬斯克表示,Model 3將不會向使用者提供免費充電服務,消費者需要購買額外的服務包。

目前為止,特斯拉對於所有購買了電動車的使用者均提供免費充電服務。這也是得益於他們在美國、澳大利亞中國等地建設了大量的充電站和充電樁。電力消耗成本低廉,特斯拉已經在把充電服務作為電動車的一個增值服務。

但是隨著特斯拉用戶的增加,充電站以及充電樁的數量卻不能無限的增長,這也給特斯拉帶來了極大的壓力。馬斯克在回答提問時表示,除非使用者購買額外充電服務,否則Model 3使用者將不會獲得免費長途充電服務。不過馬斯克並未詳細介紹充電服務包的內容和價格。

馬斯克認為,免費充電服務也是有一定的成本的,因此把Model 3本身的成本和充電的成本分開是非常有必要的。Model 3的付費充電服務會非常便宜,遠遠低於加油的費用。

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

Zabbix-(五)監控Docker容器與自定義jvm監控項

Zabbix-(五)監控Docker容器與自定義jvm監控項

一.前言

前文中講述了Zabbix對服務器硬件方面的監控功能,本文將講述利用Zabbix監控Docker容器中的Java Web服務,並通過自定義監控項,監控JVM老年代使用情況以及GC信息。Zabbix其實提供了,自帶了JMX模板能夠直接監控JVM信息,本文主要側重於自定義參數與自定義監控項,關於JMX會在之後的文章中介紹。

準備

  • Zabbix Server (Zabbix 4.4) (ip:192.168.152.140)
  • 運行Java應用的主機 以下簡稱Server-A (已被Zabbix監控) (ip:192.168.152.142)

二.開啟agent用戶自定義參數配置

  1. 修改配置

    使用自定義參數時,首先需要修改Server-A的agent配置

    # vim /etc/zabbix/zabbix_agentd.conf

    修改配置 UnsafeUserParameters=1

    UnsafeUserParameters=1
  2. 重啟zabbix-agent

    # systemctl restart zabbix-agent

三.運行tomcat容器

在Server-A運行tomcat容器

# docker run --name tomcat -p 8080:8080 -dit tomcat:jdk8-adoptopenjdk-hotspot

將zabbix賬號添加到docker組。參考

# sudo gpasswd  -a zabbix docker

外部訪問測試一下

四.創建自定義Docker模板

我們可以定義一個比較通用的Docker模板,有服務需要被監控時,直接鏈接該模板即可。

  1. 創建群組

    點擊【配置】-【主機群組】-【創建主機群組】

    定義一個組名 Docker Group

    配置項
    * 組名 Docker Group
  2. 創建模板

    創建一個自定義模板,模板名稱Docker Template,選擇上步驟創建的Docker Group群組

    配置項
    * 模板名稱 Docker Template
    * 群組 Docker Group

五.編寫腳本與自定義監控參數

我們需要編寫一個腳本,用於發現當前正在運行的docker容器(這裏使用容器名稱)。

  1. 在Server-A編寫發現運行容器的python腳本

    創建腳本

    # cd /data/zabbix
    # touch find_container.py
    # chmod a+x find_container.py
    # vim find_container.py

    腳本內容:

    #!/usr/bin/env python
    import os
    import json
    
    # 查看當前運行的docker容器
    t=os.popen(""" docker ps  |grep -v 'CONTAINER ID'|awk {'print $NF'} """)
    container_name = []
    for container in  t.readlines():
            r = os.path.basename(container.strip())
            container_name += [{'{#CONTAINERNAME}':r}]
    # 轉換成json數據
    print json.dumps({'data':container_name},sort_keys=True,indent=4,separators=(',',':'))

    運行腳本,查看一下json數據格式:

    {
        "data":[
            {
                "{#CONTAINERNAME}":"tomcat"
            }
        ]
    }
  2. 在Server-A自定義容器發現參數

    我們需要自定義一個鍵值對的配置類型,以便Zabbix可以通過鍵讀取到值。

    增加自定義參數

    # cd /etc/zabbix/zabbix_agentd.d
    # vim userparameter_find_container.conf
    docker.container /data/zabbix/find_container.py (腳本的運行結果)
    UserParameter=docker.container,/data/zabbix/find_container.py
  3. 在Server-A創建查看容器JVM GC情況的腳本

    我們可以使用jstat -gcutil 命令查看GC情況

    創建python腳本

    # cd /data/zabbix
    # touch monitor_gc.py
    # chmod a+x monitor_gc.py
    # vim monitor_gc.py

    腳本內容

    #!/usr/bin/python
    import sys
    import os
    
    def monitor_gc(container_name, keyword):
            cmd = ''' docker exec %s bash -c "jstat -gcutil 1" | grep -v S0 | awk '{print $%s}' ''' %(container_name, keyword)
            value = os.popen(cmd).read().replace("\n","")
            print value
    
    if __name__ == '__main__':
            # 參數1:容器的名稱
            # 參數2:查看第幾列(例如 Eden區在第3列傳入3,Full GC次數在第9列傳入9)
            container_name, keyword = sys.argv[1], sys.argv[2]
            monitor_gc(container_name, keyword)

    測試腳本,查看當前tomcat容器Full GC次數

    # /data/zabbix/monitor_gc.py 'tomcat' '9'

  4. 在Server-A自定義Zabbix JVM GC參數

    同樣,增加一個conf文件,表示自定義參數

    # cd /etc/zabbix/zabbix_agentd.d
    # touch userparameter_gc_status.conf
    # vim userparameter_gc_status.conf
    jvm.gc.status[*] /data/zabbix/monitor_gc.py $1 $2
    UserParameter=jvm.gc.status[*], /data/zabbix/monitor_gc.py $1 $2

    jvm.gc.status[*] 表示可以使用參數。其中$1表示參數1,即容器名稱;$2表示參數2,需要查看哪項GC信息,$1 $2都是通過Zabbix配置時傳遞的。

  5. 在Zabbix server上測試自定義參數

    為zabbix sever安裝zabbix-get

    # yum install -y zabbix-get

    測試自定義參數,如果有權限問題,可以參考

    # zabbix_get -s 192.168.152.142 -p 10050 -k docker.container
    # zabbix_get -s 192.168.152.142 -p 10050 -k "jvm.gc.status['tomcat', 9]"

六.Zabbix模板增加自動發現規則

上述配置中,已經可以通過腳本獲取到已運行的容器信息,此步驟將通過Zabbix配置界面,在模板中添加自動發現規則,以發現被監控主機中正在運行的docker容器,並利用這些獲取的數據進一步監控容器中jvm數據。

  1. 創建自動發現規則

    點擊【配置】-【模板】-【Docker Template】

    點擊【自動發現規則】-【創建發現規則】

    先配置【自動發現規則】

    配置項
    * 名稱 發現正在運行的Docker容器規則
    類型 Zabbix 客戶端
    * 鍵值 docker.container (這是我們上述步驟中自定義的鍵值)
    其他配置 根據需要配置

    鍵值配置項是之前

    再配置【過濾器】

    則配置自定義腳本返回json數據中的

    配置項
    {#CONTAINERNAME}
  2. 添加監控項原型

    點擊新建的自動發現規則的【監控項原型】-【創建監控項原型】

    輸入參數

    配置項
    * 名稱 Tomcat Full GC次數監控項
    類型 Zabbix 客戶端
    * 鍵值 jvm.gc.status[{#CONTAINERNAME} , 9]
    其他配置項 根據需要填寫

    鍵值是定義的參數,{#CONTAINERNAME} 是jvm.gc.status的參數1,使用了自動發現規則,發現到的docker容器名稱(本文中即是 tomcat);參數2 9 則是表示需要查看FullGC次數,FGC列(第9列)

    除此之外,還可以添加Old老年代(對應第4列),Full GC時間(對應第10列)等監控項,這裏就不一一添加了,和上述過程基本一致,只需修改參數2即可(也可以利用剛新建的監控項原型進行【克隆】)。

七.鏈接模板

將上述鏈接到Server-A主機

八.DashBoard添加可視化圖形

回到Zabbix首頁可以為新增的自定義監控項,增加圖形(添加圖形步驟可以參考)

九.其他

部署問題

  • zabbix在執行腳本時,是使用的zabbix賬戶,因此可能要注意要給zabbix賬號賦予權限。

    例如,zabbix賬戶無法使用docker命令,將zabbix添加到docker組

    # sudo gpasswd -a zabbix docker
  • zabbix server無法執行agent自定義參數中的腳本

    為agent主機設置

    # setenforce 0

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

TCP time_wait close_wait問題(可能是全網最清楚的例子)

背景

公司群里,運維發現一個問題,task服務報錯(如下)

The stream or file \"/data/logs/adn_task/offer_service.log\" could not be opened:
failed to open stream: Too many open files

測試老大看到了,根據經驗就推測是應該是文件句柄使用完了,應該有TCP連接很多沒釋放,果真發現是很多CLOSE_WAIT的狀態

簡單認知

短鏈接,一次鏈接就會佔用一個端口,一個端口就是一個文件描述符;
文件描述符 又稱 句柄,linux系統最大的句柄數是65535,可以通過ulimit -a 查看

三次握手

TCP建立連接需要經過三次握手;
通俗版本:
A: 你好,你能聽見我說話嗎?
B: 能聽到,你能聽到我說話嗎?
A:我也能聽到,我們開始通信吧

專業版本:
建立TCP連接時,需要客戶端和服務器共發送3個包。

  • 第一次:客戶端發送初始序號x和syn=1請求標誌
  • 第二次:服務器發送請求標誌syn,發送確認標誌ACK,發送自己的序號seq=y,發送客戶端的確認序號ack=x+1
  • 第三次:客戶端發送ACK確認號,發送自己的序號seq=x+1,發送對方的確認號ack=y+1

四次揮手

TCP連接斷開需要經過四次揮手;
通俗版本:
前提A和B在通話
A:好的,我的話就說完了(FIN);
B:哦哦,我知道你說完啦(ACK),我還有說兩句哈;A: (沒說話,一直聽着)
B:哦了,我也說完了(FIN)
A:好的,我也知道你說玩了(ACK),掛電話吧

專業版本:

  • 第一次揮手:客戶端發出釋放FIN=1,自己序列號seq=u,進入FIN-WAIT-1狀態
  • 第二次揮手:服務器收到客戶端的后,發出ACK=1確認標誌和客戶端的確認號ack=u+1,自己的序列號seq=v,進入CLOSE-WAIT狀態
  • 第三次揮手:客戶端收到服務器確認結果后,進入FIN-WAIT-2狀態。此時服務器發送釋放FIN=1信號,確認標誌ACK=1,確認序號ack=u+1,自己序號seq=w,服務器進入LAST-ACK(最後確認態)
  • 第四次揮手:客戶端收到回復后,發送確認ACK=1,ack=w+1,自己的seq=u+1,客戶端進入TIME-WAIT(時間等待)。客戶端經過2個最長報文段壽命后,客戶端CLOSE;服務器收到確認后,立刻進入CLOSE狀態。

狀態流轉圖

實際例子

建立連接

linux上起了一個redis服務

本地起的6379端口

還是同一台機器上,通過python腳本連接該redis服務:

此時網絡連接如下:

關注這兩個網絡連接,第一個是redis-server的,第二是python腳本的,此時都是ESTABLISHED狀態,表示這兩個進程建立了連接

TIME_WAIT情況

現在斷掉python

之前的python的那個連接,是TIME_WAIT狀態
客戶端(主動方)主動斷開,進入TIME_WAIT狀態,服務端(被動方)進去CLOSE狀態,就是沒有显示了

等待2MSL(1分鐘)后,如下:

TIME_WAIT狀態的連接也消失了,TIME_WAIT回收機制,系統ing過一段時間會回收,資源重利用

CLOSE_WAIT情況

先建立連接,如下:

關掉redis服務,service redis stop

之前的redis-server的45370端口連接 進入了FIN_WAIT2狀態,而python端(被動關閉方)就進去了CLOSE_WAIT狀態

等待30s后,在看連接

只有python的那條CLOSE_WAIT

再次操作python端的腳本,再次get

關於6379端口(redis端口)的網絡連接都沒有了

TCP參數設置

如何快速回收TIME_WAIT和FIN_WAIT
/etc/sysctl.conf 包含以下配置項
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
root權限 執行/sbin/sysctl -p使之生效

經驗之談

個人經驗,不一定對,如有錯誤,請指正

  1. 當出現了CLOSE_WAIT大概率是業務代碼問題,代碼中沒有處理服務異常的情況,如上面的例子,python再次請求redis的時候,發現redis掛了,就會主動幹掉CLOSE_WAIT狀態
  2. 出現大量TIME_WAIT的情況,一般是服務端沒有及時回收端口,linux內核參數需要調整優化

參考資料

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

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

分類
發燒車訊

021.掌握Pod-Pod調度策略

一 Pod生命周期管理

1.1 Pod生命周期

Pod在整個生命周期過程中被系統定義了如下各種狀態。

狀態值 描述
Pending API Server已經創建該Pod,且Pod內還有一個或多個容器的鏡像沒有創建,包括正在下載鏡像的過程。
Running Pod內所有容器均已創建,且至少有一個容器處於運行狀態、正在啟動狀態或正在重啟狀態。
Succeeded Pod內所有容器均成功執行退出,且不會重啟。
Failed Pod內所有容器均已退出,但至少有一個容器退出為失敗狀態。
Unknown 由於某種原因無法獲取該Pod狀態,可能由於網絡通信不暢導致。

1.2 Pod重啟策略

Pod重啟策略(RestartPolicy)應用於Pod內的所有容器,並且僅在Pod所處的Node上由kubelet進行判斷和重啟操作。當某個容器異常退出或者健康檢查失敗時,kubelet將根據RestartPolicy的設置來進行相應操作。
Pod的重啟策略包括Always、OnFailure和Never,默認值為Always。

  • Always:當容器失效時,由kubelet自動重啟該容器;
  • OnFailure:當容器終止運行且退出碼不為0時,由kubelet自動重啟該容器;
  • Never:不論容器運行狀態如何,kubelet都不會重啟該容器。

       kubelet重啟失效容器的時間間隔以sync-frequency乘以2n來計算,例如1/2/4/8倍等,最長延時5min,並且在成功重啟后的10min后重置該時間。

Pod的重啟策略與控制方式關聯,當前可用於管理Pod的控制器包括ReplicationController、Job、DaemonSet及直接管理kubelet管理(靜態Pod)。
不同控制器的重啟策略限制如下:

  • RC和DaemonSet:必須設置為Always,需要保證該容器持續運行;
  • Job:OnFailure或Never,確保容器執行完成后不再重啟;
  • kubelet:在Pod失效時重啟,不論將RestartPolicy設置為何值,也不會對Pod進行健康檢查。








Pod包含的容器數 Pod當前的狀態 發生事件 Pod的結果狀態
RestartPolicy=Always RestartPolicy=OnFailure RestartPolicy=Never
包含1個容器 Running 容器成功退出 Running Succeeded Succeeded
包含1個容器 Running 容器失敗退出 Running Running Failed
包括兩個容器 Running 1個容器失敗退出 Running Running Running
包括兩個容器 Running 容器被OOM殺掉 Running Running Failed

1.3 Pod健康檢查

對Pod的健康檢查可以通過兩類探針來檢查:LivenessProbe和ReadinessProbe。
LivenessProbe探針:用於判斷容器是否存活(running狀態),如果LivenessProbe探針探測到容器不健康,則kubelet將殺掉該容器,並根據容器的重啟策略做相應處理。若一個容器不包含LivenessProbe探針,kubelet認為該容器的LivenessProbe探針返回值用於是“Success”。
ReadineeProbe探針:用於判斷容器是否啟動完成(ready狀態)。如果ReadinessProbe探針探測到失敗,則Pod的狀態將被修改。Endpoint Controller將從Service的Endpoint中刪除包含該容器所在Pod的Eenpoint。
kubelet定期執行LivenessProbe探針來診斷容器的健康狀態,通常有以下三種方式:

  • ExecAction:在容器內執行一個命令,若返回碼為0,則表明容器健康。

示例:通過執行”cat /tmp/health”命令判斷一個容器運行是否正常。容器初始化並創建該文件,10s后刪除該文件,15s秒通過命令判斷,由於該文件已被刪除,因此判斷該容器Fail,導致kubelet殺掉該容器並重啟。

  1 [root@uk8s-m-01 study]# vi dapi-liveness.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-liveness-pod
  6   labels:
  7     test: liveness-exec
  8 spec:
  9   containers:
 10     - name: dapi-liveness
 11       image: busybox
 12       args:
 13       - /bin/sh
 14       - -c
 15       - echo ok > /tmp/health; sleep 10; rm -rf /tmp/health; sleep 600
 16       livenessProbe:
 17         exec:
 18           command:
 19           - cat
 20           - /tmp/health
 21 
 22 [root@uk8s-m-01 study]# kubectl describe pod dapi-liveness-pod

  • TCPSocketAction:通過容器的IP地址和端口號執行TCP檢查,若能建立TCP連接,則表明容器健康。

示例:

  1 [root@uk8s-m-01 study]# vi dapi-tcpsocket.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-healthcheck-tcp
  6 spec:
  7   containers:
  8     - name: nginx
  9       image: nginx
 10       ports:
 11       - containerPort: 80
 12       livenessProbe:
 13         tcpSocket:
 14           port: 80
 15         initialDelaySeconds: 30
 16         timeoutSeconds: 1
 17 
 18 [root@uk8s-m-01 study]# kubectl create -f dapi-tcpsocket.yaml


提示:對於每種探測方式,都需要設置如下兩個參數,其包含的含義如下:

initialDelaySeconds:啟動容器後進行首次健康檢查的等待時間,單位為s;

timeoutSeconds:健康檢查發送請求后等待響應的超時時間,單位為s,當超時發生時,kubelet會認為容器已經無法提供服務,將會重啟該容器。

二 Pod調度

Kubernetes中,Pod通常是容器的載體,一般需要通過Deployment、DaemonSet、RC、Job等對象來完成一組Pod的調度與自動控制功能。

2.1 Depolyment/RC自動調度

Deployment或RC的主要功能之一就是自動部署一個容器應用的多份副本,以及持續監控副本的數量,在集群內始終維持用戶指定的副本數量。
示例:

  1 [root@uk8s-m-01 study]# vi nginx-deployment.yaml
  2 apiVersion: apps/v1beta1
  3 kind: Deployment
  4 metadata:
  5   name: nginx-deployment-01
  6 spec:
  7   replicas: 3
  8   template:
  9     metadata:
 10       labels:
 11         app: nginx
 12     spec:
 13       containers:
 14       - name: nginx
 15         image: nginx:1.7.9
 16         ports:
 17         - containerPort: 80
 18 
 19 [root@uk8s-m-01 study]# kubectl get deployments
 20 NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 21 nginx-deployment-01   3/3     3            3           30s
 22 [root@uk8s-m-01 study]# kubectl get rs
 23 NAME                             DESIRED   CURRENT   READY   AGE
 24 nginx-deployment-01-5754944d6c   3         3         3       75s
 25 [root@uk8s-m-01 study]# kubectl get pod | grep nginx
 26 nginx-deployment-01-5754944d6c-hmcpg   1/1     Running     0          84s
 27 nginx-deployment-01-5754944d6c-mcj8q   1/1     Running     0          84s
 28 nginx-deployment-01-5754944d6c-p42mh   1/1     Running     0          84s

2.2 NodeSelector定向調度

當需要手動指定將Pod調度到特定Node上,可以通過Node的標籤(Label)和Pod的nodeSelector屬性相匹配。
# kubectl label nodes <node-name> <label-key>=<label-value>
node節點創建對應的label后,可通過在定義Pod的時候加上nodeSelector的設置實現指定的調度。
示例:

  1 [root@uk8s-m-01 study]# kubectl label nodes 172.24.9.14 speed=io
  2 node/172.24.9.14 labeled
  3 [root@uk8s-m-01 study]# vi nginx-master-controller.yaml
  4 kind: ReplicationController
  5 metadata:
  6   name: nginx-master
  7   labels:
  8     name: nginx-master
  9 spec:
 10   replicas: 1
 11   selector:
 12     name: nginx-master
 13   template:
 14     metadata:
 15       labels:
 16         name: nginx-master
 17     spec:
 18       containers:
 19       - name: master
 20         image: nginx:1.7.9
 21         ports:
 22         - containerPort: 80
 23       nodeSelector:
 24         speed: io
 25 
 26 [root@uk8s-m-01 study]# kubectl create -f nginx-master-controller.yaml
 27 [root@uk8s-m-01 study]# kubectl get pods -o wide
 28 NAME                READY   STATUS    RESTARTS    AGE    IP            NODE
 29 nginx-master-7fjgj  1/1     Running   0           82s    172.24.9.71   172.24.9.14


提示:可以將集群中具有不同特點的Node貼上不同的標籤,實現在部署時就可以根據應用的需求設置NodeSelector來進行指定Node範圍的調度。

注意:若在定義Pod中指定了NodeSelector條件,但集群中不存在符合該標籤的Node,即使集群有其他可供使用的Node,Pod也無法被成功調度。

2.3 NodeAffinity親和性調度

親和性調度機制極大的擴展了Pod的調度能力,主要增強功能如下:

  1. 更具表達力,即更精細的力度控制;
  2. 可以使用軟限制、優先採用等限制方式,即調度器在無法滿足優先需求的情況下,會使用其他次條件進行滿足;
  3. 可以依據節點上正在運行的其他Pod的標籤來進行限制,而非節點本身的標籤,從而實現Pod之間的親和或互斥關係。

目前有兩種節點親和力表達:
requiredDuringSchedulingIgnoredDuringExecution:硬規則,必須滿足指定的規則,調度器才可以調度Pod至Node上(類似nodeSelector,語法不同)。
preferredDuringSchedulingIgnoredDuringExecution:軟規則,優先調度至滿足的Node的節點,但不強求,多個優先級規則還可以設置權重值。
IgnoredDuringExecution指:如果一個Pod所在的節點在Pod運行期間標籤發生了變化,不再符合該Pod的節點親和性需求,則系統將忽略Node上Label的變化,該Pod能繼續在該節點運行。
示例:
條件1:只運行在amd64的節點上;盡量運行在ssd節點上。

  1 [root@uk8s-m-01 study]# vi nodeaffinity-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: with-node-affinity
  6 spec:
  7   affinity:
  8     nodeAffinity:
  9       requiredDuringSchedulingIgnoredDuringExecution:
 10         nodeSelectorTerms:
 11         - matchExpressions:
 12           - key: kubernetes.io/arch
 13             operator: In
 14             values:
 15             - amd64
 16       preferredDuringSchedulingIgnoredDuringExecution:
 17       - weight: 1
 18         preference:
 19           matchExpressions:
 20           - key: disk-type
 21             operator: In
 22             values:
 23             - ssd
 24   containers:
 25   - name: with-node-affinity
 26     image: gcr.azk8s.cn/google_containers/pause:2.0


NodeAffinity操作語法;In、NotIn、Exists、DoesNotExist、Gt、Lt。NotIn和DoesNotExist可以實現互斥功能。
NodeAffinity規則設置注意事項:

  • 若同時定義nodeSelector和nodeAffinity,則必須兩個條件都滿足,Pod才能最終運行指定在Node上;;
  • 若nodeAffinity指定多個nodeSelectorTerms,則只需要其中一個能夠匹配成功即可;
  • 若nodeSelectorTerms中有多個matchExpressions,則一個節點必須滿足所有matchExpressions才能運行該Pod。

2.4 PodAffinity親和性調度

PodAffinity根據節點上正在運行的Pod標籤而不是Node標籤來判斷和調度,要求對節點和Pod兩個條件進行匹配。
規則描述為:若在具有標籤X的Node上運行了一個或多個符合條件Y的Pod,則Pod應該(或者不應該)運行在這個Node上。
X通常為Node節點的機架、區域等概念,Pod是屬於某個命名空間,所以條件Y表達的是一個或全部命名空間中的一個Label Selector。
Pod親和性定義與PodSpec的affinity字段下的podAffinity字段里,互斥性定義於同一層次的podAntiAffinity子字段中。
舉例:

  1 [root@uk8s-m-01 study]# vi nginx-flag.yaml	#創建名為pod-flag,帶有兩個標籤的Pod
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: pod-affinity
  6 spec:
  7   affinity:
  8     podAffinity:
  9       requiredDuringSchedulingIgnoredDuringExecution:
 10       - labelSelector:
 11           matchExpressions:
 12           - key: security
 13             operator: In
 14             values:
 15             - S1
 16         topologyKey: kubernetes.io/hostname
 17   containers:
 18   - name: with-pod-affinity
 19     image: gcr.azk8s.cn/google_containers/pause:2.0

  1 [root@uk8s-m-01 study]# vi nginx-affinity-in.yaml	#創建定義標籤security=S1,對應如上Pod “Pod-flag”。
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: pod-affinity
  6 spec:
  7   affinity:
  8     podAffinity:
  9       requiredDuringSchedulingIgnoredDuringExecution:
 10       - labelSelector:
 11           matchExpressions:
 12           - key: security
 13             operator: In
 14             values:
 15             - S1
 16         topologyKey: kubernetes.io/hostname
 17   containers:
 18   - name: with-pod-affinity
 19     image: gcr.azk8s.cn/google_containers/pause:2.0
 20 
 21 [root@uk8s-m-01 study]# kubectl create -f nginx-affinity-in.yaml
 22 [root@uk8s-m-01 study]# kubectl get pods -o wide


提示:由上Pod親和力可知,兩個Pod處於同一個Node上。

  1 [root@uk8s-m-01 study]# vi nginx-affinity-out.yaml	#創建不能與參照目標Pod運行在同一個Node上的調度策略
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: anti-affinity
  6 spec:
  7   affinity:
  8     podAffinity:
  9       requiredDuringSchedulingIgnoredDuringExecution:
 10       - labelSelector:
 11           matchExpressions:
 12           - key: security
 13             operator: In
 14             values:
 15             - S1
 16         topologyKey: failure-domain.beta.kubernetes.io/zone
 17     podAntiAffinity:
 18       requiredDuringSchedulingIgnoredDuringExecution:
 19       - labelSelector:
 20           matchExpressions:
 21           - key: security
 22             operator: In
 23             values:
 24             - nginx
 25         topologyKey: kubernetes.io/hostname
 26   containers:
 27   - name: anti-affinity
 28     image: gcr.azk8s.cn/google_containers/pause:2.0
 29 
 30 [root@uk8s-m-01 study]# kubectl get pods -o wide	#驗證

2.5 Taints和Tolerations(污點和容忍)

Taint:使Node拒絕特定Pod運行;
Toleration:為Pod的屬性,表示Pod能容忍(運行)標註了Taint的Node。
Taint語法:$ kubectl taint node node1 key=value:NoSchedule
解釋:為node1加上一個Taint,該Taint的鍵為key,值為value,Taint的效果為NoSchedule。即除非特定聲明可以容忍此Taint,否則不會調度至node1上。
Toleration示例:

  1 tolerations:
  2 - key: "key"
  3   operator: "Equal"
  4   value: "value"
  5   effect: "NoSchedule"

  1 tolerations:
  2 - key: "key"
  3   operator: "Exists"
  4   effect: "NoSchedule"

注意:Pod的Toleration聲明中的key和effect需要與Taint的設置保持一致,並且滿足以下條件:

  • operator的值是Exists(無須指定value);
  • operator的值是Equal並且value相等;
  • 空的key配合Exists操作符能夠匹配所有的鍵和值;
  • 空的effect匹配所有的effect。


若不指定operator,則默認值為Equal。
taint說明:系統允許在同一個Node上設置多個taint,也可以在Pod上設置多個toleration。Kubernetes調度器處理多個taint和toleration的邏輯順序:首先列出節點中所有的taint,然後忽略pod的toleration能夠匹配的部分,剩下的沒有忽略掉的taint就是對pod的效果。以下是幾種特殊情況:
若剩餘的taint中存在effect=NoSchedule,則調度器不會把該Pod調度到這一節點上;
若剩餘的taint中沒有NoSchedule效果,但有PreferNoSchedule效果,則調度器會嘗試不把這個Pod指派到此節點;
若剩餘taint的效果有NoSchedule,並且這個Pod已經在該節點上運行,則會被驅逐,若沒有在該節點上運行,也不會再被調度到該節點上。
示例:

  1 $ kubectl taint node node1 key=value1:NoSchedule
  2 $ kubectl taint node node1 key=value1:NoExecute
  3 $ kubectl taint node node1 key=value2:NoSchedule
  4 tolerations:
  5 - key: "key1"
  6   operator: "Equal"
  7   value: "value"
  8   effect: "NoSchedule"
  9 tolerations:
 10 - key: "key1"
 11   operator: "Equal"
 12   value: "value1"
 13   effect: "NoExecute"


釋義:此Pod聲明了兩個容忍,且能匹配Node1的taint,但是由於沒有能匹配第三個taint的toleration,因此此Pod依舊不能調度至此Node。若該Pod已經在node1上運行了,那麼在運行時設置了第3個taint,它還能繼續在node1上運行,這是因為Pod可以容忍前兩個taint。
通常,若node加上effect=NoExecute的taint,那麼該Node上正在運行的所有無對應toleration的Pod都會被立刻驅逐,而具有相應toleration的Pod則永遠不會被驅逐。同時,系統可以給具有NoExecute效果的toleration加入一個可選的tolerationSeconds字段,表明Pod可以在taint添加到Node之後還能在此Node運行多久。

  1 tolerations:
  2 - key: "key1"
  3   operator: "Equal"
  4   value: "value"
  5   effect: "NoSchedule"
  6   tolerationSeconds: 3600

釋義:若Pod正在運行,所在節點被加入一個匹配的taint,則這個pod會持續在該節點運行3600s后被驅逐。若在此期限內,taint被移除,則不會觸發驅逐事件。
Taints和Tolerations常用場景:

  • 獨佔節點:

給特定的節點運行特定應用。
$ kubectl taint nodes 【nodename】 dedicated=groupName:NoSchedule
同時在Pod中設置對應的toleration配合,帶有合適toleration的Pod允許同時使用其他節點一樣使用有taint的節點。

  • 具有特殊硬件設備的節點

集群中部分特殊硬件(如安裝了GPU),則可以把不需要佔用GPU的Pod禁止在此Node上調度。

  1 $ kubectl taint nodes 【nodename】 special=true:NoSchedule
  2 $ kubectl taint nodes 【nodename】 special=true:PreferNoSchedule

  • 定義Pod驅逐行為

NoExecute的taint對節點上正在運行的Pod有以下影響:

    1. 沒有設置toleration的pod會被立刻驅逐;
    2. 配置了對應toleration的pod,若沒有為tolerationSeconds賦值,則會一直保留在此節點中;
    3. 配置了對應toleration的pod,且為tolerationSeconds賦值,則在指定時間后驅逐。

2.6 DaemonSet

DaemonSet是在每個Node上調度一個Pod的資源對象,用於管理集群中每個Node僅運行一份Pod的副本實例。
常見場景:
在每個Node上運行一個GlusterFS存儲的Daemon進程;
在每個Node上運行一個日誌採集程序,例如Fluentd;
在每個Node上運行一個性能監控程序,採集該Node的運行性能數據,例如Prometheus。
示例:

  1 [root@uk8s-m-01 study]# vi fluentd-ds.yaml
  2 apiVersion: extensions/v1beta1
  3 kind: DaemonSet
  4 metadata:
  5   name: fluentd-cloud-logging
  6   namespace: kube-system
  7   labels:
  8     k8s-app: fluentd-cloud-logging
  9 spec:
 10   template:
 11     metadata:
 12       namespace: kube-system
 13       labels:
 14         k8s-app: fluentd-cloud-logging
 15     spec:
 16       containers:
 17       - name: fluentd-cloud-logging
 18         image: gcr.azk8s.cn/google_containers/fluentd-elasticsearch:1.17
 19         resources:
 20           limits:
 21             cpu: 100m
 22             memory: 200Mi
 23         env:
 24         - name: FLUENTD_ARGS
 25           value: -q
 26         volumeMounts:
 27         - name: varlog
 28           mountPath: /var/log
 29           readOnly: false
 30         - name: containers
 31           mountPath: /var/lib/docker/containers
 32           readOnly: false
 33       volumes:
 34       - name: containers
 35         hostPath:
 36           path: /var/lib/docker/containers
 37       - name: varlog
 38         hostPath:
 39           path: /var/log

2.7 Job批處理調度

通過Kubernetes Job資源對象可以定義並啟動一個批處理任務,批處理任務通過并行(或者串行)啟動多個計算進程去處理一批工作項。根據批處理方式不同,批處理任務可以分為如下幾種模式:
Job Template Expansion模式:一個Job對象對應一個待處理的Work item,有幾個work item就產生幾個獨立的Job。通常適合Work item數量少、每個Work item要處理的數據量比較大的場景。
Queue with Pod Per Work Item模式:採用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item。此模式下,Job會啟動N個Pod,每個Pod都對應一個Work item。
Queue with Variable Pod Count模式:採用一個任務隊列存放Work item,一個Job對象作為消費者去完成這些Work item,但此模式下Job啟動的數量是可變的。
Kubernetes將Job氛圍以下三類:

  • Non-parallel Jobs

通常一個Job只啟動一個Pod,除非Pod異常,才會重啟該Pod,一旦此Pod正常結束,Job將結束。

  • Parallel Jobs with a fixed completion count

并行Job會啟動多個Pod,此時需要設定Job的.spec.completions參數為一個正數,當正常結束的Pod數量達至此參數設定的值后,Job結束。同時.spec.parallelism參數用來控制并行度,即同時啟動幾個Job來處理Work Item。

  • Parallel Jobs with a work queue

任務隊列方式的并行Job需要一個獨立的Queue,Work Item都在一個Queue中存放,不能設置Job的.spec.completions參數,此時Job具有以下特性:

    1. 每個Pod都能獨立判斷和決定是否還有任務項需要處理;
    2. 如果某個Pod正常結束,則Job不會再啟動新的Pod;
    3. 如果一個Pod成功結束,則此時應該不存在其他Pod還在工作的情況。它們應該都處於即將結束、退出的狀態;
    4. 如果所有Pod都結束了,且至少有一個Pod成功結束,則整個Jod成功結束。

2.8 Cronjob定時任務

表達式:Minutes Hours DayofMonth Month DayofWeek Year
Minutes:可出現”,”、”_”、”*”、”/”,有效範圍為0~59的整數;
Hours:出現”,”、”_”、”*”、”/”,有效範圍為0~23的整數;
DayofMonth:出現”,”、”_”、”*”、”/”、”L”、”W”、”C”,有效範圍為0~31的整數;
Month:可出現”,”、”_”、”*”、”/”,有效範圍為1~12的整數或JAN~DEC;
DayofWeek:出現”,”、”_”、”*”、”/”、”L”、”W”、”C”、”#”,有效範圍為1~7的整數或SUN~SAT;
*: 表示匹配該域的任意值, 假如在Minutes域使用“*”, 則表示每分鐘都會觸發事件。
/: 表示從起始時間開始觸發, 然後每隔固定時間觸發一次,例如在Minutes域設置為5/20, 則意味着第1次觸發在第5min時, 接下來每20min觸發一次, 將在第25min、 第45min等時刻分別觸發。
示例:*/1 * * * * #每隔1min執行一次任務

  1 [root@uk8s-m-01 study]# vi cron.yaml
  2 apiVersion: batch/v2alpha1
  3 kind: CronJob
  4 metadata:
  5   name: hello
  6 spec:
  7   schedule: "*/1 * * * *"
  8   jobTemplate:
  9     spec:
 10       template:
 11         spec:
 12           containers:
 13           - name: hello
 14             image: busybox
 15             args:
 16             - /bin/sh
 17             - -c
 18             - date; echo Hello from the Kubernetes cluster
 19           restartPolicy: OnFailure

  1 [root@master study]# kubectl create -f cron.yaml
  2 [root@master study]# kubectl get cronjob hello
  3 NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
  4 hello   */1 * * * *   False     0        <none>          29s
  5 [root@master study]# kubectl get pods
  6 NAME                     READY   STATUS      RESTARTS   AGE
  7 hello-1573378080-zvvm5   0/1     Completed   0          68s
  8 hello-1573378140-9pmwz   0/1     Completed   0          8s
  9 [root@node1 ~]# docker logs c7					#node節點查看日誌
 10 Sun Nov 10 09:31:13 UTC 2019
 11 Hello from the Kubernetes cluster
 12 [root@master study]# kubectl get jobs				#查看任務
 13 NAME               COMPLETIONS   DURATION   AGE
 14 hello-1573378500   1/1           8s         3m7s
 15 hello-1573378560   1/1           4s         2m7s
 16 hello-1573378620   1/1           6s         67s
 17 hello-1573378680   1/1           4s         7s
 18 [root@master study]# kubectl get pods -o wide | grep hello-1573378680	#以job任務查看對應的pod
 19 [root@master study]# kubectl delete cj hello			#刪除cronjob

2.9 初始化容器

在很多應用場景中, 應用在啟動之前都需要進行如下初始化操作。

  • 等待其他關聯組件正確運行( 例如數據庫或某個後台服務) 。
  • 基於環境變量或配置模板生成配置文件。
  • 從遠程數據庫獲取本地所需配置, 或者將自身註冊到某个中央數據庫中。
  • 下載相關依賴包, 或者對系統進行一些預配置操作。

示例:以Nginx應用為例, 在啟動Nginx之前, 通過初始化容器busybox為Nginx創建一個index.html主頁文件。同時init container和Nginx設置了一個共享的Volume, 以供Nginx訪問init container設置的index.html文件。

  1 [root@uk8s-m-01 study]# vi nginx-init-containers.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: nginx
  6   annotations:
  7 spec:
  8   initContainers:
  9   - name: install
 10     image: busybox
 11     command:
 12     - wget
 13     - "-O"
 14     - "/work-dir/index.html"
 15     - http://kubernetes.io
 16     volumeMounts:
 17     - name: workdir
 18       mountPath: "/work-dir"
 19   containers:
 20   - name: nginx
 21     image: nginx:1.7.9
 22     ports:
 23     - containerPort: 80
 24     volumeMounts:
 25     - name: workdir
 26       mountPath: /usr/share/nginx/html
 27   dnsPolicy: Default
 28   volumes:
 29   - name: workdir
 30     emptyDir: {}

  1 [root@uk8s-m-01 study]# kubectl get pods
  2 NAME    READY   STATUS     RESTARTS   AGE
  3 nginx   0/1     Init:0/1   0          2s
  4 [root@uk8s-m-01 study]# kubectl get pods
  5 NAME    READY   STATUS    RESTARTS   AGE
  6 nginx   1/1     Running   0          13s
  7 [root@uk8s-m-01 study]# kubectl describe pod nginx		#查看事件可知會先創建init容器,名為install


init容器與應用容器的區別如下。
(1) init container的運行方式與應用容器不同, 它們必須先於應用容器執行完成, 當設置了多個init container時, 將按順序逐個運行, 並且只有前一個init container運行成功后才能運行后一個init container。 當所有init container都成功運行后, Kubernetes才會初始化Pod的各種信息, 並開始創建和運行應用容器。
(2) 在init container的定義中也可以設置資源限制、 Volume的使用和安全策略, 等等。 但資源限制的設置與應用容器略有不同。

  • 如果多個init container都定義了資源請求/資源限制, 則取最大的值作為所有init container的資源請求值/資源限制值。
  • Pod的有效(effective) 資源請求值/資源限制值取以下二者中的較大值。
    • 所有應用容器的資源請求值/資源限制值之和。
    • init container的有效資源請求值/資源限制值。
  • 調度算法將基於Pod的有效資源請求值/資源限制值進行計算,即init container可以為初始化操作預留系統資源, 即使後續應用容器無須使用這些資源。
  • Pod的有效QoS等級適用於init container和應用容器。
  • 資源配額和限制將根據Pod的有效資源請求值/資源限制值計算生效。
  • Pod級別的cgroup將基於Pod的有效資源請求/限制, 與調度機制

一致。
(3) init container不能設置readinessProbe探針, 因為必須在它們成功運行后才能繼續運行在Pod中定義的普通容器。在Pod重新啟動時, init container將會重新運行, 常見的Pod重啟場景如下。

  • init container的鏡像被更新時, init container將會重新運行, 導致Pod重啟。 僅更新應用容器的鏡像只會使得應用容器被重啟。
  • Pod的infrastructure容器更新時, Pod將會重啟。
  • 若Pod中的所有應用容器都終止了, 並且RestartPolicy=Always, 則Pod會重啟。

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

webpack優化之玩轉代碼分割和公共代碼提取

前言

開發多頁應用的時候,如果不對webpack打包進行優化,當某個模塊被多個入口模塊引用時,它就會被打包多次(在最終打包出來的某幾個文件里,它們都會有一份相同的代碼)。當項目業務越來越複雜,打包出來的代碼會非常冗餘,文件體積會非常龐大。大體積文件會增加編譯時間,影響開發效率;如果直接上線,還會拉長請求和加載時長,影響網站體驗。作為一個追求極致體驗的攻城獅,是不能忍的。所以在多頁應用中優化打包尤為必要。那麼如何優化webpack打包呢?

一、概念

在一切開始前,有必要先理清一下這三個概念:

  • module: 模塊,在webpack眼裡,任何可以被導入導出的文件都是一個模塊。
  • chunk: chunk是webpack拆分出來的:
    • 每個入口文件都是一個chunk
    • 通過 import、require 引入的代碼也是
    • 通過 splitChunks 拆分出來的代碼也是
  • bundle: webpack打包出來的文件,也可以理解為就是對chunk編譯壓縮打包等處理后的產出。

二、問題分析

首先,簡單分析下,我們剛才提到的打包問題:

  • 核心問題就是:多頁應用打包後代碼冗餘,文件體積大。
  • 究其原因就是:相同模塊在不同入口之間沒有得到復用,bundle之間比較獨立。

弄明白了問題的原因,那麼大致的解決思路也就出來了:

  • 我們在打包的時候,應該把不同入口之間,共同引用的模塊,抽離出來,放到一個公共模塊中。這樣不管這個模塊被多少個入口引用,都只會在最終打包結果中出現一次。——解決代碼冗餘。
  • 另外,當我們把這些共同引用的模塊都堆在一個模塊中,這個文件可能異常巨大,也是不利於網絡請求和頁面加載的。所以我們需要把這個公共模塊再按照一定規則進一步拆分成幾個模塊文件。——減小文件體積。
  • 至於如何拆分,方式因人而異,因項目而異。我個人的拆分原則是:
    • 業務代碼和第三方庫分離打包,實現代碼分割;
    • 業務代碼中的公共業務模塊提取打包到一個模塊;
    • 第三方庫最好也不要全部打包到一個文件中,因為第三方庫加起來通常會很大,我會把一些特別大的庫分別獨立打包,剩下的加起來如果還很大,就把它按照一定大小切割成若干模塊。

optimization.splitChunks

webpack提供了一個非常好的內置插件幫我們實現這一需求:CommonsChunkPlugin。不過在 webpack4 中CommonsChunkPlugin被刪除,取而代之的是optimization.splitChunks, 所幸的是optimization.splitChunks更強大!

三、 實現

通過一個多頁應用的小demo,我們一步一步來實現上述思路的配置。

demo目錄結構:

|--public/
|   |--a.html
|   |--index.html
|--src/
|   |--a.js
|   |--b.js
|   |--c.js
|   |--index.js
|--package.json
|--webpack.config.js

代碼邏輯很簡單,index模塊中引用了 ab 2個模塊,a 模塊中引用了 c 模塊和 jquery庫,b 模塊中也引用了 c 模塊和 jquery 庫,c 是一個獨立的模塊沒有其他依賴。

index.js代碼如下:

//index.js
import a from './a.js';
import b from './b.js';
function fn() {
    console.log('index-------');
}
fn();

a.js代碼如下:

//a.js
require('./c.js');
const $ = require('jquery')
function fn() {
    console.log('a-------');
}
module.exports = fn();

b.js代碼如下:

//b.js
require('./c.js');
const $ = require('jquery')
function fn() {
    console.log('b-------');
}
module.exports = fn();

c.js代碼如下:

//c.js
function fn() {
    console.log('c-------');
}
module.exports = fn();

1. 基本配置

webpack先不做優化,只做基本配置,看看效果。項目配置了2個入口,搭配html-webpack-plugin實現多頁打包:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/index.js',
        a: './src/a.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html'
        }),
        new HtmlWebpackPlugin({
            template: './public/a.html',
            filename: 'a.html'
        })
    ]
}

在開發模式下運行webpack:

可以看到,打包出兩個html和兩個體積很大的(300多K)的文件a.js,index.js

進入dist目錄檢查js文件:

  • a.js里包含c模塊代碼和jquery代碼
  • index.js里包含a模塊、b模塊、c模塊和jquery代碼

看,同樣的代碼cjquery被打包了2遍。

2. 初步添加splitChunks優化配置

首先解決相同代碼打包2次的問題,我們需要讓webpack把cjquery提取出來打包為公共模塊。

在webpack配置文件添加splitChunks:

//webpack.config.js

optimization: {
    splitChunks: {
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial'
            }
        }
    }
}

– cacheGroups

  • cacheGroupssplitChunks配置的核心,對代碼的拆分規則全在cacheGroups緩存組裡配置。
  • 緩存組的每一個屬性都是一個配置規則,我這裏給他的default屬性進行了配置,屬性名可以不叫default可以自己定。
  • 屬性的值是一個對象,裏面放的我們對一個代碼拆分規則的描述。

– name

  • name:提取出來的公共模塊將會以這個來命名,可以不配置,如果不配置,就會生成默認的文件名,大致格式是index~a.js這樣的。

– chunks

  • chunks:指定哪些類型的chunk參与拆分,值可以是string可以是函數。如果是string,可以是這個三個值之一:all, async, initialall 代表所有模塊,async代表只管異步加載的, initial代表初始化時就能獲取的模塊。如果是函數,則可以根據chunk參數的name等屬性進行更細緻的篩選。

再次打包:

可以看到a.js,index.js從300多K減少到6點幾K。同時增加了一個common.js文件,並且兩個打包入口都自動添加了common.js這個公共模塊:

進入dist目錄,依次查看這3個js文件:

  • a.js里不包含任何模塊的代碼了,只有webpack生成的默認代碼。
  • index.js里同樣不包含任何模塊的代碼了,只有webpack生成的默認代碼。
  • common.js里有a,b,c,index,jquery代碼。

發現,提是提取了,但是似乎跟我們預料的不太一樣,所有的模塊都跑到common.js里去了。

這是因為我們沒有告訴webpack(splitChunks)什麼樣的代碼為公共代碼,splitChunks默認任何模塊都會被提取。

– minChunks

splitChunks是自帶默認配置的,而緩存組默認會繼承這些配置,其中有個minChunks屬性:

  • 它控制的是每個模塊什麼時候被抽離出去:當模塊被不同entry引用的次數大於等於這個配置值時,才會被抽離出去。
  • 它的默認值是1。也就是任何模塊都會被抽離出去(入口模塊其實也會被webpack引入一次)。

我們上面沒有配置minChunks,只配置了namechunk兩個屬性,所以minChunks的默認值 1 生效。也難怪所有的模塊都被抽離到common.js中了。

優化一下,在緩存組裡配置minChunks覆蓋默認值:

//webpack.config.js

optimization: {
    splitChunks: {
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2  //模塊被引用2次以上的才抽離
            }
        }
    }
}

然後運行webpack

可以看到有2個文件的大小發生了變化:common.js由314K減小到311K,index.js由6.22K增大到7.56K。

進入dist目錄查看:

  • a.js里依然不包含任何模塊的代碼(正常,因為a作為模塊被index引入了一次,又作為入口被webpack引入了一次,所以a是有2次引用的)。
  • index.js里出現了bindex模塊的代碼了。
  • common.js里只剩a,c,和jquery模塊的代碼。

現在我們把共同引用的模塊a, c, jquery,從aindex這兩個入口模塊里抽取到common.js里了。有點符合我們的預期了。

3. 配置多個拆分規則

3.1 實現代碼分離,拆分第三方庫

接下來,我希望公共模塊common.js中,業務代碼和第三方模塊jquery能夠剝離開來。

我們需要再添加一個拆分規則。

//webpack.config.js

optimization: {
    splitChunks: {
        minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模塊被引用2次以上的才抽離
                priority: -20
            },
            vendors: {  //拆分第三方庫(通過npm|yarn安裝的庫)
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            }
        }
    }
}

我給cacheGroups添加了一個vendors屬性(屬性名可以自己取,只要不跟緩存組下其他定義過的屬性同名就行,否則後面的拆分規則會把前面的配置覆蓋掉)。

– minSize

minSize設置的是生成文件的最小大小,單位是字節。如果一個模塊符合之前所說的拆分規則,但是如果提取出來最後生成文件大小比minSize要小,那它仍然不會被提取出來。這個屬性可以在每個緩存組屬性中設置,也可以在splitChunks屬性中設置,這樣在每個緩存組都會繼承這個配置。這裏由於我的demo中文件非常小,為了演示效果,我把minSize設置為30字節,好讓公共模塊可以被提取出來,正常項目中不用設這麼小。

– priority

priority屬性的值為数字,可以為負數。作用是當緩存組中設置有多個拆分規則,而某個模塊同時符合好幾個規則的時候,則需要通過優先級屬性priority來決定使用哪個拆分規則。優先級高者執行。我這裏給業務代碼組設置的優先級為-20,給第三方庫組設置的優先級為-10,這樣當一個第三方庫被引用超過2次的時候,就不會打包到業務模塊里了。

– test

test屬性用於進一步控制緩存組選擇的模塊,與chunks屬性的作用有一點像,但是維度不一樣。test的值可以是一個正則表達式,也可以是一個函數。它可以匹配模塊的絕對資源路徑或chunk名稱,匹配chunk名稱時,將選擇chunk中的所有模塊。我這裏用了一個正則/[\\/]node_modules[\\/]/來匹配第三方模塊的絕對路徑,因為通過npm或者yarn安裝的模塊,都會存放在node_modules目錄下。

運行一下webpack:

可以看到新產生了一個叫vendor.js的文件(name屬性的值),同時common.js文件體積由原來的311k減少到了861bytes!

進入dist目錄,檢查js文件:

  • a.js里不包含任何模塊代碼。
  • common.js只包含ac模塊的代碼。
  • index.js只包含bindex模塊的代碼。
  • vendor.js只包含jquery模塊的代碼。

現在,我們在上一步的基礎上,成功從common.js里把第三方庫jquery抽離出來放到了vendor.js里。

3.2 拆分指定文件

如果我們還想把項目中的某一些文件單獨拎出來打包(比如工程本地開發的組件庫),可以繼續添加拆分規則。比如我的src下有個locallib.js文件要單獨打包,假設a.js中引入了它。

//a.js
require('./c.js');
require('./locallib.js');  //引入自己本地的庫
const $ = require('jquery')
function fn() {
    console.log('a-------');
}
module.exports = fn();

可以這麼配置:

//webpack.config.js

optimization: {
    splitChunks: {
        minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模塊被引用2次以上的才抽離
                priority: -20
            },
            vendors: {  //拆分第三方庫(通過npm|yarn安裝的庫)
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            },
            locallib: {  //拆分指定文件
                test: /(src\/locallib\.js)$/,
                name: 'locallib',
                chunks: 'initial',
                priority: -9
            }
        }
    }
}

我在緩存組下又新增了一個拆分規則,通過test正則指定我就要單獨打包src/locallib.js文件,並且把優先級設置為-9,這樣當它被多次引用時,不會進入其他拆分規則組,因為另外兩個規則的優先級都比它要低。

運行webpack打包后:

可以看到新產生了一個locallib.js文件。進入dist目錄查看:

  • a.js里不包含任何模塊代碼。
  • common.js只包含ac模塊的代碼。
  • index.js只包含bindex模塊的代碼。
  • vendor.js只包含jquery模塊的代碼。
  • locallib.js里只包含locallib模塊的代碼。

現在我們又在上一步的基礎上獨立打包了一個指定的模塊locallib.js

至此,我們就成功實現了抽離公共模塊、業務代碼和第三方代碼剝離、獨立打包指定模塊。

對比一下,優化前,打包出來js一共有633KB:

優化后,打包出來js一共不到330KB:

優化打包后的文件分類清晰,體積比優化前縮小了幾乎50%,有點小完美是不是!擊掌!這還只是我舉的一個簡單例子,在實際多頁應用中,優化力度說不定還不止這麼多。

小結

webpack很強大,以上只是冰山一角,但是只要掌握了上述optimization.splitChunks的核心配置,我們就可以幾乎隨心所欲地按照自己的想法來拆分優化代碼控制打包文件了,是不是很酷?玩轉代碼拆分,你也可以!

如果覺得這些依然不能滿足你的需求,還想更精(bian)細(tai)地定製打包規則,可以到查看optimization.splitChunks的更多配置。

歡迎交流~

本文的完整webpack配置和demo源碼可以在這裏獲取:

歡迎轉載,轉載請註明出處:

本文同步發表於:

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

從最近面試聊聊我所感受的職業天花板

## 特別特別嚴肅的申明 (正經的)

未免引起誤解,標題已修改。

我一開始寫這篇文章,也純粹是有感而發。實在沒想到會引起如此多的關注。甚至還被社區大佬翻牌。說實話,誠惶誠恐。

再次申明一遍,我寫的也僅僅只是我個人的感受,我就是萬萬千千的普通碼農中的一個,所寫文章也僅僅是從自我角度出發。

不具備任何普適性參考性。請大家看清標題,只是個人感受,不適用.net整個行業。尤其請大家不要拿我的經歷作為語言選擇的參考。

。net也好,java也好,每個語言都有自己的生態,選擇語言之前請先確認自己的能力,請不要抱怨語言害了你,請先審視自己是否一直在努力進步,還是只是在隨大流。

博主就是一個普通碼農,太高端的層面很難觸及,找工作的途徑也只有朋友內推和招聘網站。

那麼我在這兩種途徑下所接觸到的信息,就是我目前的薪資已經到了這兩種渠道下的頂薪。所以,我自我感覺是我現在觸到我的天花板。

那麼我向上突破的方法,要麼向社區大佬學習,努力突破到那個圈子,但是對於這個自己實在沒底,大佬的圈子太高,感覺自己能力不夠,對自己實在沒信心。

第二種就是多語言發展,修鍊技術內功,管理內功,我覺着在努努力踮踮腳還是能夠到30K、40K月薪的小尾巴。

最後奉勸各位園友,語言從來都不是決定自己前途的關鍵因素,更多的還是要修鍊自己的內功不斷向上突破。

博主寫這篇文章的初衷只是想表述自己目前所面臨的職業困境,實在沒想到會有如此多的關注,用詞上的不當,給大家造成困擾,萬分抱歉。

 

#0 前言

入職新公司沒多久,閑來無事在博客園閑逛,看到園友分享的面試經歷,正好自己這段時間面試找工作,也挺多感想的,乾脆趁這個機會總結整理一下。

博主13年開始實習,14年畢業。到現在也工作五六年了。今年面試最大的感受就是觸及了.net的天花板。坐標,杭州。

 #1 背景

今年九月份從一家創業公司離職,原因么自然是公司創業失敗倒閉。

當初以技術合伙人的身份進入,雄心勃勃,然後挨了一頓社會毒打,從此老實做人,面朝黃土背朝天,老老實實去搬磚。

九月份出來,已經是中旬,開始刷新簡歷,準備穩坐釣魚台,等着電話信息轟炸。然後,等了两天,等了一首涼涼。直到這個時候博主才意識到,形式不對。

我的思維還停留在兩三年前,工 作遍地,只要更新下簡歷就會有無數的面試邀請。同志門,情況變了呀,行業寒冬真不只是說說而已。

沒辦法,只好花錢,刷新下簡歷,瀏覽崗位,主動出擊。中間接到了好幾個獵頭電話, 但特么都是java。好想吐槽一下,簡歷上.net辣么大的字,你們真的不識字么,21世紀了啊喂。

#2 某建築類軟件公司

主營業務:建築軟件,公司已上市。

技術框架:.net平台,具體的不是特別了解

招聘崗位:.net高級開發工程師

面試:一共四輪面試。

第一輪:就是HR了,簡單聊了下情況,為什麼離職,之前薪資多少,期望薪資多少。

第二輪:他們某業務線的部門經理和技術主管共同面試。

基本面試情況就是我在說他們在聽,我主要講解了項目的設計方案,使用的技術,遇到的困難,最終的解決方案。

技術面試官就問了兩個問題,一是從.net升級到netcore中間碰到過哪些問題。

第二個基於rabbitmq的分佈式事務是怎麼做的。

然後他們部門經理問了些團隊管理的問題。如何做團隊成員的任務分配,有團隊成員向你提出離職或者漲薪你怎麼處理,團隊的代碼質量如果管控

第三輪:他們的CTO,然後開始又是自我介紹。

只好把之前的又重複一遍,巴拉巴拉。最後就問了一個分佈式事務的解決方案有那些,平時是怎麼使用的。

最後聊了一下我的定位,就是進去是負責他們的平台架構,包括一些公用業務的架構封裝,老架構的netcore升級

第四輪:最後是他們的公司董事長,上來又是先自我介紹。然後問了下職業規劃。

接着就是拿着我的簡歷說這個工作跳動比較頻繁,尤其是從上一家比較大的公司跳槽到一個創業公司是基於一個什麼樣的考慮呢,感覺個人穩定性和職業性規劃都不夠。

博主當時內心的os是黑人問號臉??????我能是基於什麼樣的考慮,我為了世界和平好不好。

然後被大佬教育了一頓,灌輸了一些個人和公司共同體,什麼共贏發展什麼共同成長的理念。

結果:通過,HR小姐姐來談薪資。

只能給到20K,然後還是18k基本工資+2K的級別補貼,說是我進去之後定的級別是T3,

然後每年三四月份和九十月份可以申請調薪調級,強制要求995?????? 我特么跳槽不漲薪就算了你還給我降薪,還995,PASS。

#3 某醫美集團下轄子公司

主營業務:醫美行業的sass軟件

技術框架:GRPC

面試:一輪,技術主管。

招聘崗位:.net架構師

主要問題:依賴注入的生命周期,在框架設計中的應用場景有那些。

在技術選型時主要考慮的因素。

在框架設計時會應用到那些設計模式,主要應用場景是什麼。

對於netcore中間件的理解。

應對系統高併發的解決方案。

聊一聊對微服務的理解,基於netcore的微服務架構是怎麼設計的。

面試結果:通過。但薪資只有20K,哎呦喂,你都對不起你招聘崗位的名字呀。

#4 某物業管理軟件公司

主營業務:做小區物業管理軟件,公司兩百多人。

技術框架:.net mvc 三層

招聘崗位:.net副總監

面試:一輪。總監面試,但是木有問任何技術問題,也木有問任何團隊管理問題。逮者我之前的離職原因各種問。

面試結果:未通過。一臉懵逼的出來,都不知道為啥沒通過。老子也是信了你的邪。

#5 某電商初創企業。

主營業務:拍賣類的電商平台。公司是初創,技術團隊都沒組建完整。

面試:兩輪。

第一輪是他們的一個技術負責人,只是看看了簡歷,然後問了一個讓我哭笑不得問題,就是如果你進入公司,發現周圍人技術都比較菜的時候,你是不是會看不起別人。 笑哭!!!

第二輪是老闆,老闆就是主要負責畫大餅,聊前景,聊機遇。

結果:通過。工資待遇給到稅前24K。

但是我了解到老闆之前做互金,然後平台清盤。具體情況不清楚,大佬,惹不起,躲了躲了。

在這裏一定奉勸各位園友,互金平台或者老闆有互金背景的千萬小心。

我身邊已經不少朋友,被坑到,即使現在沒事,也說不定什麼時候就會被警察找上門。

就有朋友,剛入職公司沒多久,而且公司業務也不是做互金的,結果沒幾天,警察上門,老闆帶走就因為老闆之前做互金,還是出事兒了。

#6 某社交類公司

主營業務:付費社交app,主打東南亞市場

技術框架:.net 三層

招聘崗位:.net高級開發工程師

面試:三輪。

第一輪:部門的CTO面試,互相聊得挺愉快。

主要問了之前的項目微服務怎麼做的,服務拆分的粒度怎麼規劃,整個服務的架構怎麼規劃用到哪些技術。

然後問了數據庫方面的分庫分表怎麼做的,用的什麼中間件,分庫分表後主鍵id如何生成。

應對高併發架構上是怎麼處理的。如何保證redis的高併發高可用。面對緩存穿透、雪崩、擊穿怎麼解決的。

消息隊列的高可用、消息的冪等性,面對消息積壓如何處理。

接着就是聊團隊管理,還是人員管理,任務分配,質量保證這些問題。

接手一個新團隊后如何摸清各成員能力,不同能力的人工作上應該怎麼安排。

還有一個,就是你作為團隊主管你的工作時間是碎片化的,但同時你作為技術leader又要把控技術方案,而做技術是需要時間的連續性,你如何協調這兩者之間的衝突。

挺有意思,只有技術管理一肩挑的團隊才會遇到這種問題了。

最後介紹了一下團隊目前的組織架構,技術方向。嗯,要做.net升級,要做微服務。嗯,最後要轉java。誒,是不是有什麼奇怪的東西,.netcore它不香么。

第二輪:人事面試。嗯,就是問問離職原因,然後介紹了下公司業務發展,前景規劃,入職后的主要工作職能,然後談了下期望薪資。

第三輪:boos面。老闆,沒問什麼問題,就是聊了聊職業規劃,然後么他介紹公司發展方向,前景規劃,我作為一個負責任的捧哏, 當然舔着嘍。

面試結果:通過。薪資談到稅前24K。但五險一金都是最低標準繳納。年終獎說是0到12個月,看績效。

#7 某汽車製造公司的外包崗

面試:外包公司有個技術經理做了一個簡單電話面試。然後就約着到甲方的公司進行面試。面試兩輪,是甲方的兩個平台架構師。問題都大同小異,不贅述了。

面試結果:通過。但博主內心相當糾結,因為對於外包,網上實在是沒有好的評價,但是和兩個面試官聊得蠻愉快。

當初去面試了,也純粹是因為好奇,反正當時面試邀請也少,閑着也是閑着么。

薪資談到23k,對方說還是走了一個特別申請,甲方那邊兒再高給不了。五險一金都是最低標準。

但是HR說這個崗位是甲方為了儲備人才招聘的,我當天面試過後,甲方就把這個崗位招聘關了,只招我一個,等到明年三四月份內部編製出來,我是妥妥轉到甲方。

而且進去之後的工作也是和面試我的那個架構師一起工作,負責他們平台架構規劃。

一開始去面試之前我都說了工資要求和最低標準,滿口說沒問題,結果面試完了就又不行了。你個糟老頭子,壞的很,我信你個鬼。

#8 寫在最後

  中間也還有面試有其他幾家公司,套路問題都差不多,就不在寫出來了。找工作一共花費兩周時間,面試了也有八九家,但真正能給到期望工資的就那麼兩三家。這之間自己在網上主動投遞過,但基本都沒有回信。兩周過去,在回過頭來看,卻發現網上再找不到其他合適的崗位了,不是已經面試過,就是投遞了沒反應。到最後發現,我能選擇的就只有那麼幾家公司。而且,最嚴重的一個感受就是,我翻遍了所有的招聘網站,我目前所要的工資,已經是.net行業的天花板,往上沒有空間了。.net高級開發也好、.net架構師也好、技術經理也罷,能給到工資25K就已經是到頂了,而且崗位特別少。然後做cs方向的,價格開的比bs方向的還能高一些,頂薪能到三萬。做服務的.net被java搶佔了太多市場,即便有很多公司,初期是用.net做的,即便現在netcore已經跨平台,但公司做微服務還是要轉java,我真的好想問一句netcore它不香么,vs它不香么,都咋想的。

#9 尾篇

  最後的最後。整理一下博主在做netcore微服務所用到的相關技術,做個整體的總結。後續會一點一點具體介紹,希望能形成一個系列,希望最後能堅持寫完。

  服務註冊/發現:consul或zookeeper,各有優劣,個人傾向consul

  分佈式通訊:restful api形式或rpc。

  分佈式事件總線:推薦使用cap。cap同時支持 RabbitMQ,Kafka,Azure Service Bus 等進行底層之間的消息發送,同時內置了TCC實現。

       網關、熔斷、降級、限流:ocelot網關,應該是當下netcore平台下最火熱的網關開源項目了。同時集成了polly來滿足熔斷、降級、限流的功能要求。

  配置中心:攜程的開源項目Apollo。博主之前是為了業務需求自己寫的,不具通用性。

  微服務監控:分佈式調用鏈跟蹤zipkin和skywalking,同時還可監控服務性能。推薦使用skywalking,對代碼無侵入。

        日誌監控ELK,這個不需要多介紹了,文章太多了。

  持續集成自動部署:GitLab+Jenkins+k8s

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

【併發編程】synchronized的使用場景和原理簡介

1. synchronized使用

1.1 synchronized介紹

在多線程併發編程中synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖。但是,隨着Java SE 1.6對synchronized進行了各種優化之後,有些情況下它就並不那麼重了。

synchronized可以修飾普通方法,靜態方法和代碼塊。當synchronized修飾一個方法或者一個代碼塊的時候,它能夠保證在同一時刻最多只有一個線程執行該段代碼。

  • 對於普通同步方法,鎖是當前實例對象(不同實例對象之間的鎖互不影響)。

  • 對於靜態同步方法,鎖是當前類的Class對象。

  • 對於同步方法塊,鎖是Synchonized括號里配置的對象。

當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

1.2 使用場景

synchronized最常用的使用場景就是多線程併發編程時線程的同步。這邊還是舉一個最常用的列子:多線程情況下銀行賬戶存錢和取錢的列子。

public class SynchronizedDemo {


    public static void main(String[] args) {
        BankAccount myAccount = new BankAccount("accountOfMG",10000.00);
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int var = new Random().nextInt(100);
                        Thread.sleep(var);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    double deposit = myAccount.deposit(1000.00);
                    System.out.println(Thread.currentThread().getName()+" balance:"+deposit);
                }
            }).start();
        }
        for(int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int var = new Random().nextInt(100);
                        Thread.sleep(var);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    double deposit = myAccount.withdraw(1000.00);
                    System.out.println(Thread.currentThread().getName()+" balance:"+deposit);

                }
            }).start();
        }
    }

    private static class BankAccount{
        String accountName;
        double balance;

        public BankAccount(String accountName,double balance){
            this.accountName = accountName;
            this.balance = balance;
        }

        public double deposit(double amount){
            balance = balance + amount;
            return balance;
        }

        public double  withdraw(double amount){
            balance = balance - amount;
            return balance;
        }

    }
}

上面的列子中,首先初始化了一個銀行賬戶,賬戶的餘額是10000.00,然後開始了200個線程,其中100個每次向賬戶中存1000.00,另外100個每次從賬戶中取1000.00。如果正常執行的話,賬戶中應該還是10000.00。但是我們執行多次這段代碼,會發現執行結果基本上都不是10000.00,而且每次結果 都是不一樣的。

出現上面這種結果的原因就是:在多線程情況下,銀行賬戶accountOfMG是一個共享變量,對共享變量進行修改如果不做線程同步的話是會存在線程安全問題的。比如說現在有兩個線程同時要對賬戶accountOfMG存款1000,一個線程先拿到賬戶的當前餘額,並且將餘額加上1000。但是還沒將餘額的值刷新回賬戶,另一個線程也來做相同的操作。此時賬戶餘額還是沒加1000之前的值,所以當兩個線程執行完畢之後,賬戶加的總金額還是只有1000。

synchronized就是Java提供的一種線程同步機制。使用synchronized我們可以非常方便地解決上面的銀行賬戶多線程存錢取錢問題,只需要使用synchronized修飾存錢和取錢方法即可:

private static class BankAccount{
        String accountName;
        double balance;

        public BankAccount(String accountName,double balance){
            this.accountName = accountName;
            this.balance = balance;
        }
        //這邊給出一個編程建議:當我們對共享變量進行同步時,同步代碼塊最好在共享變量中加
        public synchronized double deposit(double amount){
            balance = balance + amount;
            return balance;
        }
        
        public synchronized double  withdraw(double amount){
            balance = balance - amount;
            return balance;
        }

    }

2. Java對象頭

上面提到,當線程進入synchronized方法或者代碼塊時需要先獲取鎖,退出時需要釋放鎖。那麼這個鎖信息到底存在哪裡呢?

Java對象保存在內存中時,由以下三部分組成:

  • 對象頭
  • 實例數據
  • 對齊填充字節

而對象頭又由下面幾部分組成:

  • Mark Word
  • 指向類的指針
  • 數組長度(只有數組對象才有)

1. Mark Word
Mark Word記錄了對象和鎖有關的信息,當這個對象被synchronized關鍵字當成同步鎖時,圍繞這個鎖的一系列操作都和Mark Word有關。Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。

Mark Word在不同的鎖狀態下存儲的內容不同,在32位JVM中是這麼存的:

其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態。Epoch是指偏向鎖的時間戳。

JDK1.6以後的版本在處理同步鎖時存在鎖升級的概念,JVM對於同步鎖的處理是從偏向鎖開始的,隨着競爭越來越激烈,處理方式從偏向鎖升級到輕量級鎖,最終升級到重量級鎖。

JVM一般是這樣使用鎖和Mark Word的:

  • step1:當沒有被當成鎖時,這就是一個普通的對象,Mark Word記錄對象的HashCode,鎖標誌位是01,是否偏向鎖那一位是0。

  • step2:當對象被當做同步鎖並有一個線程A搶到了鎖時,鎖標誌位還是01,但是否偏向鎖那一位改成1,前23bit記錄搶到鎖的線程id,表示進入偏向鎖狀態。

  • step3:當線程A再次試圖來獲得鎖時,JVM發現同步鎖對象的標誌位是01,是否偏向鎖是1,也就是偏向狀態,Mark Word中記錄的線程id就是線程A自己的id,表示線程A已經獲得了這個偏向鎖,可以執行同步鎖的代碼。

  • step4:當線程B試圖獲得這個鎖時,JVM發現同步鎖處於偏向狀態,但是Mark Word中的線程id記錄的不是B,那麼線程B會先用CAS操作試圖獲得鎖,這裏的獲得鎖操作是有可能成功的,因為線程A一般不會自動釋放偏向鎖。如果搶鎖成功,就把Mark Word里的線程id改為線程B的id,代表線程B獲得了這個偏向鎖,可以執行同步鎖代碼。如果搶鎖失敗,則繼續執行步驟5。

  • step5:偏向鎖狀態搶鎖失敗,代表當前鎖有一定的競爭,偏向鎖將升級為輕量級鎖。JVM會在當前線程的線程棧中開闢一塊單獨的空間,裏面保存指向對象鎖Mark Word的指針,同時在對象鎖Mark Word中保存指向這片空間的指針。上述兩個保存操作都是CAS操作,如果保存成功,代表線程搶到了同步鎖,就把Mark Word中的鎖標誌位改成00,可以執行同步鎖代碼。如果保存失敗,表示搶鎖失敗,競爭太激烈,繼續執行步驟6。

  • step6:輕量級鎖搶鎖失敗,JVM會使用自旋鎖,自旋鎖不是一個鎖狀態,只是代表不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認啟用,自旋次數由JVM決定。如果搶鎖成功則執行同步鎖代碼,如果失敗則繼續執行步驟7。

  • step7:自旋鎖重試之後如果搶鎖依然失敗,同步鎖會升級至重量級鎖,鎖標誌位改為10。在這個狀態下,未搶到鎖的線程都會被阻塞。

2. 指向類的指針
該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。Java對象的類數據保存在方法區。

3. 數組長度
只有數組對象保存了這部分數據。該數據在32位和64位JVM中長度都是32bit。

synchronized對鎖的優化

Java 6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”的概念。在Java 6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏向鎖升級成輕量級鎖后不能降級成偏向鎖。

在聊偏向鎖、輕量級鎖和重量級鎖之前我們先來聊下鎖的宏觀分類。鎖從宏觀上來分類,可以分為悲觀鎖與樂觀鎖。注意,這裏說的的鎖可以是數據庫中的鎖,也可以是Java等開發語言中的鎖技術。悲觀鎖和樂觀鎖其實只是一類概念(對某類具體鎖的總稱),不是某種語言或是某個技術獨有的鎖技術。

樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重複讀-比較-寫的操作。java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。數據庫中的共享鎖也是一種樂觀鎖。

悲觀鎖是就是悲觀思想,即認為寫多,遇到併發寫的可能性高,每次去拿數據的時候都認為別人會修改,所以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中典型的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如ReentrantLock。數據庫中的排他鎖也是一種悲觀鎖。

偏向鎖

Java 6之前的synchronized會導致爭用不到鎖的線程進入阻塞狀態,線程在阻塞狀態和runnbale狀態之間切換是很耗費系統資源的,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖。為了緩解上述性能問題,Java 6開始,引入了輕量鎖與偏向鎖,默認啟用了自旋,他們都屬於樂觀鎖

偏向鎖更準確的說是鎖的一種狀態。在這種鎖狀態下,系統中只有一個線程來爭奪這個鎖。線程只要簡單地通過Mark Word中存放的線程ID和自己的ID是否一致就能拿到鎖。下面簡單介紹下偏向鎖獲取和升級的過程。

還是就着這張圖講吧,會清楚點。

當系統中還沒有訪問過synchronized代碼時,此時鎖的狀態肯定是“無鎖狀態”,也就是說“是否是偏向鎖”的值是0,“鎖標誌位”的值是01。此時有一個線程1來訪問同步代碼,發現鎖對象的狀態是”無鎖狀態”,那麼操作起來非常簡單了,只需要將“是否偏向鎖”標誌位改成1,再將線程1的線程ID寫入Mark Word即可。

如果後續系統中一直只有線程1來拿鎖,那麼只要簡單的判斷下線程1的ID和Mark Word中的線程ID,線程1就能非常輕鬆地拿到鎖。但是現實往往不是那麼簡單的,現在假設線程2也要來競爭同步鎖,我們看下情況是怎麼樣的。

  • step1:線程2首先根據“是否是偏向鎖”和“鎖標誌位”的值判斷出當前鎖的狀態是“偏向鎖”狀態,但是Mark Word中的線程ID又不是指向自己(此時線程ID還是指向線程1),所以此時回去判斷線程1還是否存在;
  • step2:假如此時線程1已經不存在了,線程2會將Mark Word中的線程ID指向自己的線程ID,鎖不升級,仍為偏向鎖;
  • step3:假如此時線程1還存在(線程1還沒執行完同步代碼,【不知道這樣理解對不對,姑且先這麼理解吧】),首先暫停線程1,設置鎖標誌位為00,鎖升級為“輕量級鎖”,繼續執行線程1的代碼;線程2通過自旋操作來繼續獲得鎖。

在JDK6中,偏向鎖是默認啟用的。它提高了單線程訪問同步資源的性能。但試想一下,如果你的同步資源或代碼一直都是多線程訪問的,那麼消除偏向鎖這一步驟對你來說就是多餘的。事實上,消除偏向鎖的開銷還是蠻大的。
所以在你非常熟悉自己的代碼前提下,大可禁用偏向鎖:

 -XX:-UseBiasedLocking=false

輕量級鎖

“輕量級鎖”鎖也是一種鎖的狀態,這種鎖狀態的特點是:當一個線程來競爭鎖失敗時,不會立即進入阻塞狀態,而是會進行一段時間的鎖自旋操作,如果自旋操作拿鎖成功就執行同步代碼,如果經過一段時間的自旋操作還是沒拿到鎖,線程就進入阻塞狀態。

1. 輕量級鎖加鎖流程
線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

2. 輕量級鎖解鎖流程
輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

重量級鎖

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

鎖自旋

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。

但是線程自旋是需要消耗CPU的,說白了就是讓CPU在做無用功,線程不能一直佔用CPU自旋做無用功,所以需要設定一個自旋等待的最大時間。如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

自旋鎖盡可能的減少線程的阻塞,這對於鎖的競爭不激烈,且佔用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小於線程阻塞掛起操作的消耗!但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間佔用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是佔用cpu做無用功,線程自旋的消耗大於線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成cpu的浪費。

JDK7之後,鎖的自旋特性都是由JVM自身控制的,不需要我們手動配置。

鎖對比

鎖的優化

  • 減少鎖的時間:不需要同步的代碼不加鎖;
  • 使用讀寫鎖:讀操作加讀鎖,可以併發讀,寫操作使用寫鎖,只能單線程寫;
  • 鎖粗化:假如有一個循環,循環內的操作需要加鎖,我們應該把鎖放到循環外面,否則每次進出循環,都進出一次臨界區,效率是非常差的;

參考

  • https://blog.csdn.net/lkforce/article/details/81128115
  • 《併發編程藝術》

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

Java開發者學習技術體系

01基礎技術體系

我認為知識技能體系化是判斷技術是否過關的第一步。知識體系化包含兩層含義:

1、 能夠知道技術知識圖譜(高清版圖譜掃文末二維碼)的內容

比如分佈式系統中常用的RPC技術,其背後就涉及到網絡IO(Netty)、網絡協議、服務發現(Zookeeper配置中心)、RPC服務治理(限流、熔斷、降級)、負載均衡等。

2、 能夠理清各類技術概念之間的區別和聯繫

在分佈式系統領域中,有很多相似的概念,但又分佈在不同的產品或層級中。比如負載均衡這個詞,DNS、LVS、Ngnix、F5等產品都能實現,而且在大型分佈式系統中他們會同時存在,那麼就要搞清楚他們各自的位於什麼層級,解決了什麼問題。

再比如緩存這項技術,有分佈式緩存、本地緩存、數據庫緩存,在往下還有硬件層級的緩存。同樣都是緩存,他們之間的區別又是什麼?

如果你仔細去觀察,大廠的後端開發工程師總是能對整個技術體系了如指掌,從而在系統設計與技術選型階段就能夠做出較為合理的架構。 

02實踐經驗的積累

       能否快速解決實戰中的業務問題是判斷技術是否過關的第二步。
       大家在面試的過程中,都會有一種體會:我的知識體系已經建立了,但在回答面試官問題的時候,總感覺像在背答案,而且也沒有辦法針對性的回答面試官問題。比如在面試官問到這些問題時:

  1. 我們知道消息隊列可應用於耦系統,應對異步消費等場景,那如何在網絡不可靠的場景下保證業務數據處理的正確性?
  2. 我們都知道在分佈式系統會用到緩存,那該如何設置緩存失效機制才能避免系統出現緩存雪崩?
  3. 我們都或多或少的知道系統發布上線的流程,但在大流量場景下採用何種發布機制才能盡可能的做到平滑?

能完善的解決這些問題是區分一個程序員是否有經驗的重要標誌,知識的體系化是可以從書本不斷的凝練來獲得,但經驗的積累需要通過實戰的不斷總結

對很多人來說很為難的一點是,平時寫着的業務代碼,很少有機會接觸到大廠的優秀實踐,那麼這時候更需要從如下兩個角度逼問:

1、當流量規模再提高几個量級,那麼我的系統會出現什麼問題?

2、假如其中一個環節出現了問題,那麼該怎麼保證系統的穩定性?

03技術的原理

上面的提到都是將技術用於業務實踐,以及高效的解決業務中出現的問題。但這是否就意味着自己的技術已經過關了呢?我認為還不能。

判斷技術是否過關的第三步是能否洞察技術背後的設計思想和原理。

如果你參加過一些大廠面試,還會問到一些開放性的問題:

1、 寫一段程序,讓其運行時的表現為觸發了5次Young GC、3次Full GC、然後3次Young GC;

2、 如果一個Java進程突然消失了,你會怎麼去排查這種問題?

3、 給了一段Spring加載Bean的代碼片段,闡述一下具體的執行流程?

       是不是看上去很難,是不是和自己準備的“題庫”中的問題不一樣?不知道從何處下手?如果你有這種感覺,那麼說明你的技術還需要繼續修鍊。

       你要明白的是這種開放性的問題,提問的角度千變萬化,但最終落腳點卻都是基本原理。如果你不了解GC的觸發條件,你就肯定無法答出第一題;同樣,如果你對Spring啟動機制了解的很清楚,那麼無論他給出的是什麼樣的代碼,你都能回答出代碼經歷的過程。如果你能以不變應萬變,那麼恭喜你,你的技術過關了。

       上面提到了很多技術問題,這裏我不做詳細的解釋,都能在下面的技術圖譜中找到答案:

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

【其他文章推薦】

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

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

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

分類
發燒車訊

Linux開機過程

相關內容

開機過程

  開機過程指的是從按下電源鍵開始,到進入系統登錄畫面前所經歷的過程。

MBR與磁盤分區

  在目前x86的系統架構中,系統硬盤位於第0號磁道:0到511KB的區塊為MBR(硬盤中的每一個磁道容量為512KB),開機管理程序使用這塊區域來儲存第一階段開機引導程序(stage1)。接着位於1到62號磁道作為第1.5階段的開機引導程序(stage1.5),從第63號磁道開始才是操作系統的分區。

  主引導記錄(MBR,Master Boot Record)是位於磁盤最前邊的一段引導(Loader)代碼。它負責磁盤操作系統(DOS)對磁盤進行讀寫時分區合法性的判別、分區引導信息的定位,它由磁盤操作系統(DOS)在對硬盤進行初始化時產生。

  MBR的內容分為三部分:第一部分是0到445KB,是計算機的基礎導引程序,也稱為第一階段的導引程序;接着446KB到509KB為磁盤分區表,由四個分區表項構成(每個16個字節)。負責說明磁盤上的分區情況。內容包括分區標記、分區的起始位置、分區的容量以及分區的類型。最後一部分為結束標誌只佔2KB,其值為AA55,存儲時低位在前,高位在後。

從百度百科借了張圖:

 

 

MBR中緊跟在主引導程序后的主分區表這64字節(01BE~01FD)中包含了許多磁盤分區描述信息,尤其是01BE~01CD這16字節,包含了分區引導標誌bootid、分區起始源頭beghead、分區起始扇區relsect、分區起始柱面begcy1、操作系統類型systid、分區結尾磁頭endhead、分區結尾扇區begsect、分區結尾柱面begcy1、分區扇區起始位置relsect、分區扇區總數numsect。

其中分區引導標誌bootid表示當前分區是否可以引導,若為0x0,則表示該分區為非活動區;若為0x80,則為可開機啟動區。若有多個開機啟動區,則由用戶開機時的選擇而定(如GRUB的菜單)。

分區扇區起始位置relsect表示分區中第一個扇區相對於磁盤起始點的偏移位置。

開機管理程序

linux上的開機管理程序有LiLO和GRUB,前者是早期的產物,在近年來的Linux操作系統都以GRUB作為默認軟件包。

GNU GRUB(GRand Unified Bootloader簡稱“GRUB”)是一個來自GNU項目的多操作系統啟動程序。GRUB是多啟動規範的實現,它允許用戶可以在計算機內同時擁有多個操作系統,並在計算機啟動時選擇希望運行的操作系統。GRUB可用於選擇操作系統分區上的不同內核,也可用於向這些內核傳遞啟動參數。

運行層級

運行層級(run level)共有7個,分別為0、1、2、3、4、5、6,其中0表示關機、1表示單人模式、6表示重新啟動。中間的2、3、4、5因Linux發行商而異。

過程解析

 從按下電源開始到登錄畫面中所有的過程。

 登錄程序依序分為BIOS、GRUB、內核加載、與init程序四個步驟。

BIOS

當按下電源按鈕后,系統就會運行BIOS檢測,包含檢查系統的硬件配置、執行系統診斷程序、找出系統硬盤,把第0號磁道中的開機導引程序加載到內存中,之後就由GRUB接手後續的開機程序。

GRUB

GRUB是一個較大的程序,本身容量超過MBR的限制(512KB),因此GRUB將開機程序分割為stage1、stage2,並在1與2之中加上選用的程序stage1.5,如e2fs_stage1_5、fat_stage1_5等。

由BIOS接手后的GRUB,會由stage1轉接到stage2(或stage1.5),並找出和載入位於/boot的內核文件。內核文件位於/boot之下。

接着會將內存映像文件(.img)加載到內存中,並使用cpio命令將內容解壓縮到/boot之下。如果硬件的功能都別編入內核中,這個動作是不需要的;但若編譯為模塊且必須在開機時加載,這個步驟就是必要的。

將內核與必要的映像文件加載后,系統開機的過程就交給內核處理了。

內核載入

 內核接手系統開機的程序之後,會進行初始化,包括檢測硬件、設置硬件設備、時鐘設定、加載模塊等,這動作完成後會釋放出曾佔用的內存空間。

 接着啟動文件系統相關的設定,首先會掛接根目錄(“/”),再讀取分區表(/etc/fstab)並掛接所有的分區與啟動SWAP。最後系統啟動/sbin/init程序,並運行硬件與軟件相關的系統常駐程序。

 內核在開機的作用到此告一段落。

init程序

Init是系統的第一個進程,因此PID為0,也是所有進程的父進程,init啟動後會先執行etc/rc.d/rc.sysinit,並讀取配置文件/etc/inittab中的設定

 init的具體內容可參考:

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

【其他文章推薦】

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

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

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

分類
發燒車訊

新能源積分交易制度 或替代財政補貼

新能源汽車業發展日益成熟之時,單純的財政補貼帶來的車企騙補等諸多負面效應逐漸顯現。對此,有專家提出建議採用積分交易機制替代財政補貼政策,促進車企在傳統燃油車節能和新能源車技術進步兩方面共同發展。  
  中國汽車技術研究中心新能源汽車積分政策負責人時間表示,相比真金白銀的財政補貼,新能源積分交易制度靈活性更高,是當下國家推動新能源產業發展的一種可行方式。   新能源積分交易制度,即政府將企業年度“零排放”車型的銷售情況記錄成積分,以積分為依據來考核企業在節能減排方面是否達標。若企業積分不達標,可以購買同行業其餘公司的積分,或者向政府繳納高額罰款。   中國若要實施積分交易制度 政府部門職責需合理分配。   為此,積分政策負責人時間給出幾點建議:作為政策的制定者政府,需要部門間進行合理的職責分配,物質保障方面,中國新能源基礎設施建設尚需進一步完善。意識形態方面,當前,消費者對新能源車的認識還不充足,對制度實施造成一定阻礙。企業方面,不同規模的企業須在政策上區別對待,為中小企業的發展提供空間;當企業規模有所變更時,應當提供相應的扶持政策。   文章來源:人民網

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

【其他文章推薦】

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

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

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