016.Kubernetes二進制部署所有節點kube-proxy

一 部署 kube-proxy


kube-proxy 運行在所有節點上,它監聽 apiserver 中 service 和 endpoint 的變化情況,創建路由規則以提供服務 IP 和負載均衡功能。

1.1 安裝kube-proxy


提示:k8smaster01節點已下載相應二進制,可直接分發至node節點。

1.2 分發kube-proxy

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     scp kubernetes/server/bin/kube-proxy root@${all_ip}:/opt/k8s/bin/
  7     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建kube-scheduler證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-csr.json <<EOF
  3 {
  4   "CN": "system:kube-proxy",
  5   "key": {
  6     "algo": "rsa",
  7     "size": 2048
  8   },
  9   "names": [
 10     {
 11       "C": "CN",
 12       "ST": "Shanghai",
 13       "L": "Shanghai",
 14       "O": "k8s",
 15       "OU": "System"
 16     }
 17   ]
 18 }
 19 EOF
 20 #創建kube-scheduler的CA證書請求文件



解釋:

  • CN:指定該證書的 User 為 system:kube-proxy;
  • 預定義的 RoleBinding system:node-proxier 將User system:kube-proxy 與 Role system:node-proxier 綁定,該 Role 授予了調用 kube-apiserver Proxy 相關 API 的權限;
  • 該證書只會被 kube-proxy 當做 client 證書使用,所以 hosts 字段為空。




  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy		#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.4 創建和分發kubeconfig


kube-proxy 使用 kubeconfig 文件訪問 apiserver,該文件提供了 apiserver 地址、嵌入的 CA 證書和 kube-proxy 證書:

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# kubectl config set-cluster kubernetes \
  4   --certificate-authority=/opt/k8s/work/ca.pem \
  5   --embed-certs=true \
  6   --server=${KUBE_APISERVER} \
  7   --kubeconfig=kube-proxy.kubeconfig
  8 
  9 [root@k8smaster01 work]# kubectl config set-credentials kube-proxy \
 10   --client-certificate=kube-proxy.pem \
 11   --client-key=kube-proxy-key.pem \
 12   --embed-certs=true \
 13   --kubeconfig=kube-proxy.kubeconfig
 14 
 15 [root@k8smaster01 work]# kubectl config set-context default \
 16   --cluster=kubernetes \
 17   --user=kube-proxy \
 18   --kubeconfig=kube-proxy.kubeconfig
 19 
 20 [root@k8smaster01 work]# kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
 21 
 22 [root@k8smaster01 ~]# cd /opt/k8s/work
 23 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 24 [root@k8smaster01 work]# for node_name in ${NODE_NAMES[@]}
 25   do
 26     echo ">>> ${node_name}"
 27     scp kube-proxy.kubeconfig root@${node_name}:/etc/kubernetes/
 28   done


1.5 創建kube-proxy 配置文件


從 v1.10 開始,kube-proxy 部分參數可以配置文件中配置。可以使用 –write-config-to 選項生成該配置文件。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-config.yaml.template <<EOF
  3 kind: KubeProxyConfiguration
  4 apiVersion: kubeproxy.config.k8s.io/v1alpha1
  5 clientConnection:
  6   burst: 200
  7   kubeconfig: "/etc/kubernetes/kube-proxy.kubeconfig"
  8   qps: 100
  9 bindAddress: ##ALL_IP##
 10 healthzBindAddress: ##ALL_IP##:10256
 11 metricsBindAddress: ##ALL_IP##:10249
 12 enableProfiling: true
 13 clusterCIDR: ${CLUSTER_CIDR}
 14 hostnameOverride: ##ALL_NAME##
 15 mode: "ipvs"
 16 portRange: ""
 17 kubeProxyIPTablesConfiguration:
 18   masqueradeAll: false
 19 kubeProxyIPVSConfiguration:
 20   scheduler: rr
 21   excludeCIDRs: []
 22 EOF



解釋:

  • bindAddress: 監聽地址;
  • clientConnection.kubeconfig: 連接 apiserver 的 kubeconfig 文件;
  • clusterCIDR: kube-proxy 根據 –cluster-cidr 判斷集群內部和外部流量,指定 –cluster-cidr 或 –masquerade-all 選項后 kube-proxy 才會對訪問 Service IP 的請求做 SNAT;
  • hostnameOverride: 參數值必須與 kubelet 的值一致,否則 kube-proxy 啟動後會找不到該 Node,從而不會創建任何 ipvs 規則;
  • mode: 使用 ipvs 模式。

1.6 分發配置文件

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for (( i=0; i < 6; i++ ))
  4   do
  5     echo ">>> ${ALL_NAMES[i]}"
  6     sed -e "s/##ALL_NAME##/${ALL_NAMES[i]}/" -e "s/##ALL_IP##/${ALL_IPS[i]}/" kube-proxy-config.yaml.template > kube-proxy-config-${ALL_NAMES[i]}.yaml.template
  7     scp kube-proxy-config-${ALL_NAMES[i]}.yaml.template root@${ALL_NAMES[i]}:/etc/kubernetes/kube-proxy-config.yaml
  8   done


1.7 創建kube-proxy的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kube-proxy.service <<EOF
  4 [Unit]
  5 Description=Kubernetes Kube-Proxy Server
  6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes
  7 After=network.target
  8 
  9 [Service]
 10 WorkingDirectory=${K8S_DIR}/kube-proxy
 11 ExecStart=/opt/k8s/bin/kube-proxy \\
 12   --config=/etc/kubernetes/kube-proxy-config.yaml \\
 13   --logtostderr=true \\
 14   --v=2
 15 Restart=on-failure
 16 RestartSec=5
 17 LimitNOFILE=65536
 18 
 19 [Install]
 20 WantedBy=multi-user.target
 21 EOF


1.8 分發kube-proxy systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6     scp kube-proxy.service root@${all_name}:/etc/systemd/system/
  7   done						#分發system


二 啟動並驗證

2.1 啟動kube-proxy 服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "mkdir -p ${K8S_DIR}/kube-proxy"
  7     ssh root@${all_ip} "modprobe ip_vs_rr"
  8     ssh root@${all_ip} "systemctl daemon-reload && systemctl enable kube-proxy && systemctl restart kube-proxy"
  9   done						#啟動服務前必須先創建工作目錄


2.2 檢查kube-proxy 服務

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     ssh root@${all_ip} "systemctl status kube-proxy|grep Active"
  6   done


2.3 查看監聽端口


kube-proxy 監聽 10249 和 10256 端口:

  • 10249:對外提供 /metrics;
  • 10256:對外提供 /healthz 的訪問。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "sudo netstat -lnpt|grep kube-prox"
  7   done



2.4 查看ipvs 路由規則

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "/usr/sbin/ipvsadm -ln"
  7   done



可見所有通過 https 訪問 K8S SVC kubernetes 的請求都轉發到 kube-apiserver 節點的 6443 端口。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

PHP讀取Excel內的圖片

今天接到了一個從Excel內讀取圖片的需求,在網上查找了一些資料,基本實現了自己的需求,不過由於查到的一些代碼比較久遠,裏面一些庫方法已經被移除不存在了,所以不能直接移植到自己的項目里,需要稍加改動一下。

這裏介紹一下分別使用phpspreadsheet和PHPExcel擴展庫來實現讀取Excel內圖片的功能:

PHPSpreadsheet

首先安裝phpspreadsheet,由於線上服務器PHP版本是PHP5.6,所以需要安裝兼容PHP5.6的版本,這裏安裝1.8.2版本

composer require phpoffice/phpspreadsheet=1.8.2

然後就可以在項目里使用了

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;

$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) { //如果目錄不存在則遞歸創建
    mkdir($imageFilePath, 0777, true);
}

try {
    $inputFileName = './files/1.xlsx';  //包含圖片的Excel文件
    $objRead = IOFactory::createReader('Xlsx');
    $objSpreadsheet = $objRead->load($inputFileName);
    $objWorksheet = $objSpreadsheet->getSheet(0);
    $data = $objWorksheet->toArray();

    foreach ($objWorksheet->getDrawingCollection() as $drawing) {
        list($startColumn, $startRow) = Coordinate::coordinateFromString($drawing->getCoordinates());
        $imageFileName = $drawing->getCoordinates() . mt_rand(1000, 9999);

        switch ($drawing->getExtension()) {
            case 'jpg':
            case 'jpeg':
                $imageFileName .= '.jpg';
                $source = imagecreatefromjpeg($drawing->getPath());
                imagejpeg($source, $imageFilePath . $imageFileName);
                break;
            case 'gif':
                $imageFileName .= '.gif';
                $source = imagecreatefromgif($drawing->getPath());
                imagegif($source, $imageFilePath . $imageFileName);
                break;
            case 'png':
                $imageFileName .= '.png';
                $source = imagecreatefrompng($drawing->getPath());
                imagepng($source, $imageFilePath, $imageFileName);
                break;
        }
        $startColumn = ABC2decimal($startColumn);
        $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;
    }
    dump($data);die();
} catch (\Exception $e) {
    throw $e;
}

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

可以看到,圖片被讀取並存到了本地服務器中

PHPExcel

PHPExcel實現從Excel文件里讀取內容的方法和phpspreadsheet幾乎一樣,畢竟phpspreadsheet就是在PHPExcel基礎上寫的,不過PHPExcel由於已經被廢棄了,所以建議優先使用phpspreadsheet,如果原來項目里一直使用了PHPExcel也可以繼續使用PHPExcel的方法

use PHPExcel_IOFactory;
use PHPExcel_Cell;

try {
    $inputFileName = './files/1.xlsx';
    $inputFileType = PHPExcel_IOFactory::identify($inputFileName);
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objPHPExcel = $objReader->load($inputFileName);
} catch (\Exception $e) {
    die('加載文件發生錯誤:"'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}

$sheet = $objPHPExcel->getSheet(0);
$data = $sheet->toArray(); //該方法讀取不到圖片,圖片需單獨處理
$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) {
    mkdir($imageFilePath, 0777, true);
}

//處理圖片
foreach ($sheet->getDrawingCollection() as $img) {
    list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($img->getCoordinates()); //獲取圖片所在行和列
    $imageFileName = $img->getCoordinates() . mt_rand(1000, 9999);
    switch($img->getExtension()) {
        case 'jpg':
        case 'jpeg':
            $imageFileName .= '.jpeg';
            $source = imagecreatefromjpeg($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'gif':
            $imageFileName .= '.gif';
            $source = imagecreatefromgif($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'png':
            $imageFileName .= '.png';
            $source = imagecreatefrompng($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
    }
    $startColumn = ABC2decimal($startColumn);
    $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;

}
var_dump($data);

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

參考文章:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

Kubernetes概述

1. Kubernetes是什麼

Kubernetes是一個可移植的、可擴展的、用於管理容器化工作負載和服務的開源平台,它簡化(促進)了聲明式配置和自動化。它有一個龐大的、快速增長的生態系統。Kubernetes的服務、支持和工具隨處可見。

Kubernetes一詞源於希臘語,意為舵手或飛行員。2014年,谷歌開放了Kubernetes項目的源代碼。Kubernetes基於谷歌在大規模運行生產工作負載方面的15年經驗,以及來自社區的最佳想法和實踐。

使用 Kubernetes, 我們可以快速高效地響應客戶需求:

  • 快速、可預測地部署你的應用程序
  • 擁有即時擴展應用程序的能力
  • 不影響現有業務的情況下,無縫地發布新功能
  • 優化硬件資源,降低成本

Kubernetes的目標是構建一個軟件和工具的生態系統,以減輕你在公共雲或私有雲運行應用程序的負擔。

1.1. 時光倒流

讓我們把時間回溯到從前,看看為什麼Kubernetes是如此有用。

傳統部署時代在早期,組織在物理服務器上運行應用程序。無法為物理服務器中的應用程序定義資源邊界,這導致了資源分配問題。例如,如果一個物理服務器上運行多個應用程序,那麼在某些情況下,一個應用程序將佔用大部分資源,從而導致其他應用程序性能下降。對此的解決方案是在不同的物理服務器上運行每個應用程序。但是,由於資源沒有得到充分利用,而且維護許多物理服務器的成本很高,所以無法進行擴展。

虛擬化部署時代作為一種解決方案,引入了虛擬化。它允許你在單個物理服務器的CPU上運行多個虛擬機(vm)。虛擬化允許在vm之間隔離應用程序,並提供一定程度的安全性,因為一個應用程序的信息不能被另一個應用程序自由訪問。

虛擬化可以更好地利用物理服務器中的資源,並提供更好的可伸縮性,因為可以方便地添加或更新應用程序,從而降低硬件成本,等等。通過虛擬化,你可以將一組物理資源表示為一次性虛擬機集群。

每個VM是一個完整的機器,運行所有組件,包括它自己的操作系統,運行在虛擬硬件之上。

容器部署時代容器類似於vm,但是它們具有寬鬆的隔離屬性,以便在應用程序之間共享操作系統(OS)。因此,容器被認為是輕量級的。與VM類似,容器有自己的文件系統、CPU、內存、進程空間等等。由於它們與底層基礎設施解耦,因此可以跨雲和OS發行版移植。

容器已經變得很流行,因為它們提供了額外的好處,比如:

  • 敏捷應用程序創建和部署:與使用VM鏡像相比,增加了容器鏡像創建的方便性和效率
  • 持續開發、集成和部署:提供可靠且頻繁的容器鏡像構建和部署,具有快速且輕鬆的回滾
  • 開發和運維關注點分離:在構建/發布時而不是部署時創建應用程序容器鏡像,從而將應用程序與基礎設施分離
  • 可觀察性:不僅能显示操作系統級的信息和指標,還能显示應用程序的健康狀況和其他信號
  • 跨開發、測試和生產的環境一致性:在筆記本電腦上運行與在雲上運行完全相同
  • 雲和操作系統發布的可移植性:運行在Ubuntu, RHEL, CoreOS, on-prem,谷歌Kubernetes引擎,和其他任何地方
  • 以應用程序為中心的管理:將抽象級別從在虛擬硬件上運行操作系統提高到使用邏輯資源在操作系統上運行應用程序
  • 鬆散耦合、分佈式、彈性、解放的微服務:應用程序被分解成更小的獨立部分,可以動態地部署和管理——而不是運行在一台大型單用途機器上的單片堆棧
  • 資源隔離:可預測的應用程序性能
  • 資源利用:效率高,密度大 

1.2. 為什麼需要Kubernetes?它能做什麼?

容器是捆綁和運行應用程序的好方法。在生產環境中,你需要管理運行應用程序的容器,並確保沒有停機時間。例如,如果一個容器發生故障,則需要啟動另一個容器。如果這個行為由一個系統來處理不是更容易嗎?

Kubernetes為你提供了一個能夠彈性地運行分佈式系統的框架。它負責處理應用程序的擴展和故障轉移,提供部署模式,等等。

Kubernetes為你提供:

服務發現和負載平衡

  Kubernetes可以使用DNS名稱或自己的IP地址公開容器。如果到容器的通信量很高,Kubernetes能夠實現負載平衡並分配網絡通信量,從而使部署保持穩定

存儲編排

  Kubernetes允許你自動掛載自己選擇的存儲系統,比如本地存儲、公有雲提供商等等

自動滾動更新和回滾

  可以使用Kubernetes描述所部署容器的期望狀態,並且可以以受控的速率將實際狀態更改為期望狀態

自動包裝

  你只需為Kubernetes提供一組節點,它便可以使用這些節點來運行容器化的任務。告訴Kubernetes每個容器需要多少CPU和內存(RAM)。Kubernetes可以在你的節點上放置容器,以充分利用你的資源。 

自動重啟 

  Kubernetes會重新啟動失敗的容器,替換容器,殺死不響應用戶定義的健康檢查的容器,並且在它們準備好服務之前不會將他們暴露給客戶端。

秘密和配置管理

  Kubernetes允許你存儲和管理敏感信息,比如密碼、OAuth令牌和ssh密鑰。你可以部署和更新秘密和應用程序配置,而無需重新構建容器映像,也無需在堆棧配置中公開秘密。 

1.3. Kubernetes不是什麼

Kubernetes不是一個傳統的、包羅萬象的PaaS(平台即服務)系統。由於Kubernetes是在容器級別而不是在硬件級別操作的,所以它提供了一些PaaS產品常見的通用特性,例如部署、擴展、負載平衡、日誌記錄和監視。但是,Kubernetes不是單片的,這些默認的解決方案是可選的和可插拔的。Kubernetes為構建開發人員平台提供了構建塊,但是在重要的地方保留了用戶的選擇和靈活性。

  • 不限制所支持的應用程序類型。Kubernetes的目標是支持非常多樣化的工作負載,包括無狀態、有狀態和數據處理工作負載。如果一個應用程序可以在容器中運行,那麼它應該可以在Kubernetes上運行。
  • 不部署源代碼,也不構建應用程序。持續集成、交付和部署(CI/CD)工作流由組織文化和偏好以及技術需求決定。
  • 不提供應用級服務,如中間件(例如消息總線)、數據處理框架(例如Spark)、數據庫(例如mysql)、緩存,也不提供集群存儲系統(例如Ceph)作為內置服務。這些組件可以運行在Kubernetes上,並且/或者可以由運行在Kubernetes上的應用程序通過可移植的機制(如Open Service Broker)訪問。
  • Kubernetes 允許用戶選擇其他的日誌記錄,監控和告警系統 
  • 不提供也不強制要求配置語言/系統(例如,jsonnet)。它提供了一個聲明性API,可以被任意形式的聲明性規範作為目標。
  • 不提供也不採用任何全面的機器配置、維護、管理或自修復系統。
  • 此外,Kubernetes不僅僅是一個編排系統。事實上,它消除了對編排的需要。編排的技術定義是執行一個已定義的工作流:首先執行a,然後執行B,然後執行c。相反,Kubernetes包括一套獨立、可組合的控制過程,通過聲明式語法使其連續地朝着期望狀態驅動當前狀態。你怎麼從A點到c點都不重要,只要告訴到C的狀態即可。 

2. Kubernetes組件

當你部署Kubernetes的時候,你會得到一個集群。

一個集群是一組機器,稱為節點(nodes),它們運行由Kubernetes管理的容器化應用程序。

一個集群至少有一個工作節點(worker node)和一個主節點(master node)。

工作節點承載應用程序的組件。主節點管理集群中的工作節點和pods。多個主節點用於提供具有故障轉移和高可用性的集群。

下面是Kubernetes集群的關係圖: 

2.1. Master組件

Master組件提供集群的控制面板。Master組件對集群做出全局決策(例如,調度),Master組件檢測並響應集群事件(例如,當部署的replicas字段不滿足時啟動一個新的pod)

Master組件可以在集群中的任何機器上運行。但是,為了簡單起見,設置腳本通常在同一台機器上啟動所有Master組件,並且不在這台機器上運行用戶容器。

kube-apiserver 

  API服務器是一個Kubernetes面板控制組件,用於公開Kubernetes API。它是Kubernetes控制面板的前端。

  Kubernetes API服務器的主要實現是kube-apiserver。kube-apiserver被設計成水平伸縮——也就是說,它通過部署更多實例來伸縮。你可以運行kube-apiserver的多個實例,並在這些實例之間平衡流量。

etcd 

  一致性和高可用的鍵值存儲用作Kubernetes的所有集群數據備份存儲。

  如果你的Kubernetes集群使用etcd作為其備份存儲,請確保對這些數據有備份計劃。

kube-scheduler

  監視沒有分配節點的新創建的pod,併為它們選擇要運行的節點。 

kube-controller-manager

  運行控制器的組件,從邏輯上講,每個控制器都是一個單獨的進程,但是為了降低複雜性,它們都被編譯成一個單一的二進制文件,並在一個進程中運行。

這些控制器包括:

  • Node Controller:節點控制器負責發現和響應節點故障
  • Replication Controller:副本控制器負責為系統中的每個副本控制器對象維護正確的pod數量
  • Endpoints Controller:端點控制器填充端點對象(即,連接服務和pod)
  • Service Account & Token Controllers:為新的名稱空間創建默認帳戶和API訪問令牌 

cloud-controller-manager 

  雲控制器-管理器,運行與底層雲提供商交互的控制器

2.2. Node組件

Node組件在每個節點上運行,維護運行的pods並提供Kubernetes運行時環境。 

kubelet

  kubelet是一個代理,它運行在集群中的每個節點上,它確保容器在一個pod中運行。kubelet只管理由Kubernetes創建的容器。 

kube-proxy

  kube-proxy是運行在集群中每個節點上的網絡代理,是實現Kubernetes服務概念的一部分。

  kube-proxy維護節點上的網絡規則。這些網絡規則允許從集群內外的網絡會話與pod進行網絡通信。

  如果有可用的操作系統包過濾層,kube-proxy將使用它。否則,kube-proxy將轉發流量本身。

Container Runtime

  容器運行時是負責運行容器的軟件。

  Kubernetes支持多個容器運行時:DockercontainerdcriorktletKubernetes CRI(容器運行時接口)的任何實現。 

2.3. Addons(插件)

DNS

  雖然其他插件不是嚴格要求的,但所有Kubernetes集群都應該有集群DNS,因為許多示例都依賴於它。

Web UI (Dashboard)

  Dashboard是一個通用的、基於web的Kubernetes集群用戶界面。它允許用戶管理和排除集群中運行的應用程序和集群本身的故障。

Container Resource Monitoring

  容器資源監控記錄中央數據庫中容器的一般時間序列指標,並提供用於瀏覽該數據的UI。

Cluster-level Logging

  集群級別的日誌記錄機制負責將容器日誌保存到具有搜索/瀏覽界面的中央日誌存儲中。

3. 回顧&小結

Kubernetes:

  • 可移植: 無論公有雲、私有雲、混合雲還是多雲架構都全面支持
  • 可擴展: 它是模塊化、可插拔、可掛載、可組合的,支持各種形式的擴展
  • 自修復: 它可以自保持應用狀態、可自重啟、自複製、自縮放的,通過聲明式語法提供了強大的自修復能力

Kubernetes建立在Google公司15年的運維經驗基礎之上,Google所有的應用都運行在容器上 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

Non-local Neural Networks 原理詳解及自注意力機制思考

Paper:
 

Author:Xiaolong Wang, Ross Girshick, Abhinav Gupta, Kaiming He (CMU, FAIR)

1 創新點

這篇文章非常重要,個人認為應該算是cv領域裏面的自注意力機制的核心文章,語義分割裏面引入的各種自注意力機制其實都可以認為是本文的特殊化例子。分析本文的意義不僅僅是熟悉本文,而是了解其泛化思想。

不管是cv還是NLP任務,都需要捕獲長範圍依賴。在時序任務中,RNN操作是一種主要的捕獲長範圍依賴手段,而在CNN中是通過堆疊多個卷積模塊來形成大感受野。目前的卷積和循環算子都是在空間和時間上的局部操作,長範圍依賴捕獲是通過重複堆疊,並且反向傳播得到,存在3個不足:

(1) 捕獲長範圍依賴的效率太低;

(2) 由於網絡很深,需要小心的設計模塊和梯度;

(3) 當需要在比較遠位置之間來回傳遞消息時,這是局部操作是困難的.

故作者基於圖片濾波領域的非局部均值濾波操作思想,提出了一個泛化、簡單、可直接嵌入到當前網絡的非局部操作算子,可以捕獲時間(一維時序信號)、空間(圖片)和時空(視頻序列)的長範圍依賴。這樣設計的好處是:

(1) 相比較於不斷堆疊卷積和RNN算子,非局部操作直接計算兩個位置(可以是時間位置、空間位置和時空位置)之間的關係即可快速捕獲長範圍依賴,但是會忽略其歐式距離,這種計算方法其實就是求自相關矩陣,只不過是泛化的自相關矩陣

(2) 非局部操作計算效率很高,要達到同等效果,只需要更少的堆疊層

(3) 非局部操作可以保證輸入尺度和輸出尺度不變,這種設計可以很容易嵌入到目前的網絡架構中。

2 核心思想

由於我主要做2d圖片的CV需求,故本文的大部分分析都是針對圖片而言,而不是時間序列或者視頻序列。

本文的非局部操作算子是基於非局部均值操作而提出的,故很有必要解釋下非局部均值操作。我們在CNN或者傳統圖片濾波算子中涉及的都是局部操作,例如Sobel算子,均值濾波算子等等,其計算示意圖如下:

  圖片來源:吳恩達深度學習課程

可以看出每個位置的輸出值都是kernel和輸入的局部卷積計算得到的,而非局部均值濾波操作是: computes a weighted mean of all pixels in an image,非常簡單。核心思想是在計算每個像素位置輸出時候,不再只和鄰域計算,而是和圖像中所有位置計算相關性,然後將相關性作為一個權重表徵其他位置和當前待計算位置的相似度。可以簡單認為採用了一個和原圖一樣大的kernel進行卷積計算。下圖表示了高斯濾波,雙邊濾波和非局部均值處理過程:

 

可以看出對於待計算的中心紅色點,前兩種局部操作都是在鄰域計算,而非局部均值是和整個圖片進行計算的。但是實際上如果採用逐點計算方式,不僅計算速度非常慢,而且抗干擾能力不太好,故非局部均值操作是採用Block的思想,計算block和block之間的相關性。

 

可以看出,待計算的像素位置是p,故先構造block,然後計算其他位置block和當前block的相關性,可以看出q1和q2區域和q非常相似,故計算時候給予一個大權重,而q3給予一個小的權重。這樣的做法可以突出共性(關心的區域),消除差異(通常是噪聲)。

 
上圖可以看出非局部操作的優點,每一個例子中左圖是待計算像素點的位置,右圖是基於NL均值操作計算出來的權重分布圖,看(c)可以非常明顯看出,由於待計算點位置是在邊緣處,通過非局部操作后突出了全部邊緣。

上面的所有分析都是基於非局部操作來講的,但是實際上在深度學習時代,可以歸為自注意力機制Self-attention。在機器翻譯中,自我注意模塊通過關注所有位置並在嵌入空間中取其加權平均值來計算序列(例如,句子)中的位置處的響應,在CV中那就是通過關注圖片中(可以是特徵圖)所有位置並在嵌入空間中取其加權平均值來表示圖片中某位置處的響應。嵌入空間可以認為是一個更抽象的圖片空間表達,目的是匯聚更多的信息,提高計算效率。聽起來非常高級的樣子,到後面可以看出,是非常簡單的。

3 網絡結構

下面開始給出非局部操作的具體公式。首先在深度學習中非局部操作可以表達為:

 

 

 

i是輸出特徵圖的其中一個位置,通用來說這個位置可以是時間、空間和時空。j是所有可能位置的索引,x是輸入信號,可以是圖像、序列和視頻,通常是特徵圖。y是和x尺度一樣的輸出圖,f是配對計算函數,計算第i個位置和其他所有位置的相關性,g是一元輸入函數,目的是進行信息變換,C(x)是歸一化函數,保證變換前後整體信息不變。以上是一個非常泛化的公式,具體細節見下面。在局部卷積算子中,一般的  

由於f和g都是通式,故結合神經網絡特定,需要考慮其具體形式。
首先g由於是一元輸出,比較簡單,我可以採用1×1卷積,代表線性嵌入,其形式為:

 

對於f,前面我們說過其實就是計算兩個位置的相關性,那麼第一個非常自然的函數是Gaussian。

(1) Gaussian

 

對兩個位置進行點乘,然後通過指數映射,放大差異。

(2) Embedded Gaussian

 

前面的gaussian形式是直接在當前空間計算,而(2)更加通用,在嵌入空間中計算高斯距離。這裏:

   

 

 

前面兩個:  

 

 

仔細觀察,如果把C(x)考慮進去,那麼  

其實就是softmax形式,完整考慮是:

 

這個就是目前常用的位置注意力機制的表達式,所以說語義分割中大部分通道注意力機制都是本文的特殊化。

(3) Dot product

考慮一種最簡單的非局部操作形式:

 

其中C(x)=N,像素個數。可以看出(2) (3)的主要區別是是否含有激活函數softmax。

(4) Concatenation

參考 Relation Networks可以提出:

 

 

 

前面是基本的非局部操作算子,利用這些算子,下面開始構造成模塊。  

可以看出,上面構造成了殘差形式。上面的做法的好處是可以隨意嵌入到任何一個預訓練好的網絡中,因為只要設置W_z初始化為0,那麼就沒有任何影響,然後在遷移學習中學習新的權重。這樣就不會因為引入了新的模塊而導致預訓練權重無法使用

下面結合具體實例分析:

 

由於我們考慮的是圖片,故可以直接設置T=1,或者說不存在。首先網絡輸入是X= (batch, h, w, 1024) ,經過Embedded Gaussian中的兩個嵌入權重變換 , 得到(batch, h, w, 512), (batch, h, w, 512), 其實這裏的目的是降低通道數,減少計算量;然後分別對這兩個輸出進行reshape操作,變成(batch, hw, 512),后對這兩個輸出進行矩陣乘(其中一個要轉置),計算相似性,得到(batch, hw, hw),
然後在第2個維度即最後一個維度上進行softmax操作,得到(batch, hw, hw), 意這樣做就是通道注意力,相當於找到了當前圖片或特徵圖中每個像素與其他所有位置像素的歸一化相關性;然後將g也採用一樣的操作,先通道降維,然後reshape;然後和 (batch, hw, hw)進行矩陣乘,得到(batch, h, w, 512), 即將通道注意力機制應用到了所有通道的每張特徵圖對應位置上,本質就是輸出的每個位置值都是其他所有位置的加權平均值,通過softmax操作可以進一步突出共性。最後經過一個1×1卷積恢復輸出通道,保證輸入輸出尺度完全相同。

4 核心代碼實現

 

拷貝的代碼來源:

可以看出,具體實現非常簡單,就不細說了。

5 擴展

通讀全文,你會發現思路非常清晰,模塊也非常簡單。其背後的思想其實是自注意力機制的泛化表達,準確來說本文只提到了位置注意力機制(要計算位置和位置之間的相關性,辦法非常多)。

個人認為:如果這些自注意模塊的計算開銷優化的很小,那麼應該會成為CNN的基礎模塊。既然位置和位置直接的相關性那麼重要,那我是不是可以認為graph CNN才是未來?因為圖卷積網絡是基於像素點和像素點之間建模,兩者之間的權重是學習到的,性能肯定比這種自監督方式更好,後面我會寫文章分析。

本文設計的模塊依然存在以下的不足:

(1) 只涉及到了位置注意力模塊,而沒有涉及常用的通道注意力機制

(2) 可以看出如果特徵圖較大,那麼兩個(batch,hxw,512)矩陣乘是非常耗內存和計算量的,也就是說當輸入特徵圖很大存在效率底下問題,雖然有其他辦法解決例如縮放尺度,但是這樣會損失信息,不是最佳處理辦法。

 

 

6 實驗

Non-local Blocks的高效策略。我們設置Wg,,的channel的數目為x的channel數目的一半,這樣就形成了一個bottleneck,能夠減少一半的計算量。Wz再重新放大到x的channel數目,保證輸入輸出維度一致。 
還有一個subsampling的trick可以進一步使用,就是將(1)式變為:yi=1C(x^)∑∀jf(xi,x^j)g(x^j),其中x^x下採樣得到的(比如通過pooling),我們將這個方式在空間域上使用,可以減小1/4的pairwise function的計算量。這個trick並不會改變non-local的行為,而是使計算更加稀疏了。這個可以通過在圖2中的ϕg後面增加一個max pooling層實現。 
我們在本文中的所有non-local模塊中都使用了上述的高效策略。

6.1. 視頻分類模型

為了理解non-local networks的操作,我們在視頻分類任務上進行了一系列的ablation experiments。 
2D ConvNet baseline (C2D)。為了獨立開non-local nets中時間維度的影響vs 3D ConvNets,我們構造了一個簡單的2D baseline結構。 
Table 1給出了ResNet-50 C2D backbone。輸入的video clip是32幀,大小為224*224。Table 1中的所有卷積都是用的2D的kernel,即逐幀對輸入視頻進行計算。唯一和temporal有關的計算就是pooling,也就是說這個baseline模型簡單地在時間維度上做了一個聚合的操作。 

Inflated 3D ConvNet (I3D)。 Table 1中的C2D模型可以通過inflate的操作轉換成一個3D卷積的結構。具體地,一個2D k*k大小的kernel可以inflate成3D t*k*k大小的kernel,只要將其權重重複t次,再縮小t倍即可。

我們討論2種inflate的方式。一種是將residual block中的3*3的kernel inflate成3*3*3的,另一種是將residual block中的1*1的kernel inflate成3*1*1的。這兩種形式我們分別用I3D333I3D311表示。因為3D conv的計算量很大,我們只對每2個residual blocks中的1個kernel做inflate。對更多的kernel做inflate發現效果反而變差了。另外conv1層我們inflate成5*7*7。

Non-local network。 我們將non-local block插入到C2D或I3D中,就得到了non-local nets。我們研究了插入1,5,10個non-local blocks的情況,實現細節將在後面給出。

6.2 Non-local Network實現細節

Training。 我們的模型是在ImageNet上pretrain的,沒有特殊說明的話我們使用32幀的輸入。32幀是通過從原始長度的視頻中隨機選擇1個位置取出64個連續幀,然後每隔1幀取1幀得到的最終的32幀。spatial size是224*224大小,是將原始視頻rescale到短邊為[256,320]區間的隨機值,然後再random crop 224*224大小。我們在8卡GPU上進行訓練,每卡上有8 clips(也就是說總的batchsize是64 clips)。我們一共迭代了400k iterations,初始lr為0.01,然後每150k iterations lr下降1/10。momentum設為0.9,weight decay設為0.0001。dropout在global pooling層後面使用,dropout ratio設為0.5。 
我們finetune模型的時候 BN是打開的,這和常見的finetune ResNet的操作不同,它們通常是frozen BN。我們發現在我們的實驗中enable BN有利於減少過擬合。 
在最後一個1*1*1 conv層(表示Wz)的後面我們加了一個BN層,其他位置我們沒有增加BN。這個BN層的scale參數初始化為0,這是為了保證整個non-local block的初始狀態相當於一個identity mapping,這樣插入到任何預訓練網絡中在一開始都能保持其原來的表現。

Inference。 推理時,在我們將視頻rescale到短邊256進行推理。時域上我們從整個視頻中平均採樣10個clip,然後分別計算他們的softmax scores,最後做平均得到整個視頻的score。

6.3 實驗

關於視頻分類的實驗,我們在Kinetics上進行全面的實驗,另外也給出了Charades上的實驗結果,显示出我們的模型的泛化性。這裏只給出Kinetics上的結果,更多的請看原文。 
Table 2給出了ablation results。 

f的表現形式的影響。表2a比較了不同的non-local block的形式插入到C2D得到的結果(插入位置在res4的最後一個residual block之前)。發現即使只加一個non-local block都能得到~1%的提高。 
有意思的是不同的non-local block的形式效果差不多,說明是non-local block的結構在起作用,而對具體的表達方式不敏感。本文後面都採用embedded Gaussian進行實驗,因為這個版本有softmax,可以直接給出[0,1]之間的scores。

哪個階段加入non-local blocks?表2b比較了一個non-local block加在resnet的不同stage的效果,具體加在不同stage的最後一個residual block之前。發現在res2,res3,res4層上加non-local block效果類似,加在res5上效果稍差。這個的可能原因是res5的spatial size比較小,只有7*7,可能無法提供精確的spatial信息了。

加入更多的non-local blocks。表2c給出了加入更多non-local block的結果,我們在resnet-50上加1 block(在res4),加5 block(3個在res4,2個在res3,每隔1個residual block加1個non-local block),加10 block(在res3和res4每個residual block都加non-local block)。在resnet101的相同位置加block。發現更多non-local block通常有更好的結果。我們認為這是因為更多的non-local block能夠捕獲長距離多次轉接的依賴。信息可以在時空域上距離較遠的位置上進行來回傳遞,這是通過local models無法實現的。 
另外需要提到的是增加non-local block得到的性能提升並不只是因為它給base model增加了深度。為了說明這一點,表2c中resnet50 5blocks能夠達到73.8的acc,而resnet101 baseline是73.1,同時resnet50 5block只有resnet101的約70%的參數量和80%的FLOPs。說明non-local block得到的性能提升並不只是因為它增加了深度。

時空域上做non-local。我們的方法也可以處理時空域的信息,這一特性非常好,在視頻中相關的物體可能出現在較遠的空間和較長的時間,它們的相關性也可以被我們的模型捕獲。表2d給出了在時間維度,空間維度和時空維度分別做non-local的結果。僅在空間維度上做就相當於non-local的依賴僅在單幀圖像內部發生,也就是說在式(1)上僅對index i的相同幀的index j做累加。僅在時間維度上做也類似。表2d显示只做時間維度或者只做空間維度的non-local,都比C2D baseline要好,但是沒有同時做時空維度的效果好。

Non-local net vs. 3D ConvNet。表2e比較了我們的non-local C2D版本和inflated 3D ConvNets的性能。Non-local的操作和3D conv的操作可以看成是將C2D推廣到時間維度的兩種方式。 
表2e也比較了param的數量,FLOPs等。我們的non-local C2D模型比I3D更加精確(75.1 vs 74.4),並且有更小的FLOPs(1.2x vs 1.5x)。說明單獨使用時non-local比3D conv更高效。

Non-local 3D ConvNet. 不管上面的比較,其實non-local操作和3D conv各有各的優點:3D conv可以對局部依賴進行建模。表2f給出了在I3D311上插入5個non-local blocks的結果。發現NL I3D都能夠在I3D的基礎上提升1.6個點的acc,說明了non-local和3D conv是可以相互補充的。

更長的輸入序列。 最後我們也實驗了更長輸入序列的情況下模型的泛化性。輸入clip包含128幀連續幀,沒有做下採樣,是一般情況下取的32幀的4倍長度。為了將這個模型放入顯存中,每個GPU上只能放下2 clips。因為這麼小的batchsize的原因,我們freeze所有的BN層。我們從32幀訓練得到的模型作為初始化模型,然後用128幀進行finetune,使用相同的iterations數目(雖然batchsize減小了),初始lr為0.0025,其他設置和之前保持一致。 
表2g給出了128幀的實驗結果,和表2f的32幀的結果相比,所有模型都表現得更好,說明我們的模型在長序列上的效果也很好。

和state-of-the-art的比較。表3給出了Kinetics上各個方法的結果。 

 

 

來源: 

       https://www.jianshu.com/p/a9771abedf50

       https://blog.csdn.net/u010158659/article/details/78635219

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

【其他文章推薦】

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

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

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

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

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

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

MySQL InnoDB 鎖

MySQL 原理篇

數據準備:

/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.6.17 : Database - test
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `t2` */

DROP TABLE IF EXISTS `t2`;

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `t2` */

insert  into `t2`(`id`,`name`) values (1,'1'),(4,'4'),(7,'7'),(10,'10');

/*Table structure for table `teacher` */

DROP TABLE IF EXISTS `teacher`;

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

/*Data for the table `teacher` */

insert  into `teacher`(`id`,`name`,`age`) values (1,'seven11124',18),(2,'qingshan',18);

/*Table structure for table `user_account` */

DROP TABLE IF EXISTS `user_account`;

CREATE TABLE `user_account` (
  `id` int(11) NOT NULL DEFAULT '0',
  `balance` int(11) NOT NULL,
  `lastUpdate` datetime NOT NULL,
  `userID` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

/*Data for the table `user_account` */

insert  into `user_account`(`id`,`balance`,`lastUpdate`,`userID`) values (1,3200,'2018-12-06 13:27:57',1),(2,50,'2018-12-06 13:28:08',2),(3,1000,'2018-12-06 13:28:22',3);

/*Table structure for table `users` */

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  `phoneNum` varchar(32) NOT NULL,
  `lastUpdate` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_eq_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4;

/*Data for the table `users` */

insert  into `users`(`id`,`name`,`age`,`phoneNum`,`lastUpdate`) values (1,'seven',26,'13666666666','2018-12-07 19:22:51'),(2,'qingshan',19,'13777777777','2018-12-08 21:01:12'),(3,'james',20,'13888888888','2018-12-08 20:59:39'),(4,'tom',99,'13444444444','2018-12-06 20:34:10'),(6,'jack',91,'13444444544','2018-12-06 20:35:07'),(11,'jack1',33,'13441444544','2018-12-06 20:36:19'),(15,'tom2',30,'1344444444','2018-12-08 15:08:24'),(19,'iiii',30,'1344444444','2018-12-08 21:21:47');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

在運行下面的演示案例之前,先把表和數據準備好。

理解表鎖和行鎖

鎖是用於管理不同事務對共享資源的併發訪問。

表鎖與行鎖的區別

  • 鎖定粒度:表鎖 > 行鎖
  • 加鎖效率:表鎖 > 行鎖
  • 衝突概率:表鎖 > 行鎖
  • 併發性能:表鎖 < 行鎖

InnoDB 存儲引擎支持行鎖和表鎖(另類的行鎖),InnoDB 的表鎖是通過對所有行加行鎖實現的。

鎖的類型

  • 共享鎖(行鎖):Shared Locks
  • 排他鎖(行鎖):Exclusive Locks
  • 意向鎖共享鎖(表鎖):Intention Shared Locks
  • 意向鎖排它鎖(表鎖):Intention Exclusive Locks
  • 自增鎖:AUTO-INC Locks

行鎖的算法

  • 記錄鎖:Record Locks
  • 間隙鎖:Gap Locks
  • 臨鍵鎖:Next-key Locks

官網文檔:

共享鎖(Shared Locks)

定義

共享鎖:又稱為讀鎖,簡稱 S 鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。

通過如下代碼,加鎖和釋放鎖:

-- 加鎖
select * from users WHERE id=1 LOCK IN SHARE MODE;

-- 釋放鎖:提交事務 or 回滾事務
commit;
rollback;

演示案例

-- 共享鎖
-- 事務A執行
BEGIN;

SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE;

ROLLBACK;
COMMIT;

-- 事務B執行
SELECT * FROM users WHERE id=1;

UPDATE users SET age=19 WHERE id=1;
  • 事務A手動開啟事務,執行語句獲取共享鎖,注意這裏沒有提交事務
  • 事務B分別執行 SELECT 和 UPDATE 語句,查看執行效果

結論:UPDATE 語句被鎖住了,不能執行。在事務A獲得共享鎖的情況下,事務B可以執行查詢操作,但是不能執行更新操作。

排他鎖(Exclusive Locks)

定義

排它鎖:又稱為寫鎖,簡稱 X 鎖,排他鎖不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖、排他鎖),只有該獲取了排他鎖的事務是可以對數據行進行讀取和修改。(其他事務要讀取數據可來自於快照)

通過如下代碼,加鎖和釋放鎖:

-- 加鎖
-- delete / update / insert 默認加上X鎖
-- SELECT * FROM table_name WHERE ... FOR UPDATE
-- 釋放鎖:提交事務 or 回滾事務
commit;
rollback;

演示案例

-- 排它鎖
-- 事務A執行
BEGIN;

UPDATE users SET age=23 WHERE id=1;

COMMIT;
ROLLBACK;

-- 事務B執行
SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE;
SELECT * FROM users WHERE id=1 FOR UPDATE;
-- SELECT 可以執行,數據來自於快照
SELECT * FROM users WHERE id=1;
  • 事務A手動開啟事務,執行 UPDATE 語句,獲取排它鎖,注意這裏沒有提交事務
  • 事務B分別執行三條語句,查看執行效果

 

結論:事務B的第一條 SQL 和第二條 SQL 語句都不能執行,都已經被鎖住了,第三條 SQL 可以執行,數據來自於快照,關於這點後面會講到。

行鎖到底鎖了什麼

InnoDB 的行鎖是通過給索引上的索引項加鎖來實現的。

只有通過索引條件進行數據檢索,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖(鎖住索引的所有記錄)

通過普通索引進行數據檢索,比如通過下面例子中 UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';該 SQL 會在 name 字段的唯一索引上面加一把行鎖,同時會在該唯一索引對應的主鍵索引上面也會加上一把行鎖,總共會加兩把行鎖。

演示案例

演示之前,先看一下 users 表的結構和數據內容。

-- 案例1
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13666666666';

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE id=2;
UPDATE users SET lastUpdate=NOW() WHERE id=1;

-- 案例2
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE id=1;

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE id=2;
UPDATE users SET lastUpdate=NOW() WHERE id=1;

-- 案例3
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';

ROLLBACK;

-- 事務B執行
UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';
UPDATE users SET lastUpdate=NOW() WHERE id=1;
UPDATE users SET lastUpdate=NOW() WHERE `name`='qingshan';
UPDATE users SET lastUpdate=NOW() WHERE id=2;

注意:這裏演示的案例都是在事務A沒有提交之前,執行事務B的語句。

案例1執行結果如下圖所示:

案例2執行結果如下圖所示:

案例3執行結果如下圖所示:

意向共享鎖(Intention Shared Locks)& 意向排它鎖(Intention Exclusive Locks)

意向共享鎖(IS)

表示事務準備給數據行加入共享鎖,即一個數據行加共享鎖前必須先取得該表的 IS 鎖,意向共享鎖之間是可以相互兼容的。

意向排它鎖(IX)

表示事務準備給數據行加入排他鎖,即一個數據行加排他鎖前必須先取得該表的 IX 鎖,意向排它鎖之間是可以相互兼容的

意向鎖(IS 、IX)是 InnoDB 數據操作之前自動加的,不需要用戶干預。

意義:當事務想去進行鎖表時,可以先判斷意向鎖是否存在,存在時則可快速返回該表不能啟用表鎖。

演示案例

-- IS鎖的意義
-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate=NOW() WHERE id=1;

ROLLBACK;

-- 事務B執行
-- 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖
UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13777777777';

結論:事務B的 SQL 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖,在對錶加鎖之前會查看該表是否已經存在了意向鎖,因為事務A已經獲得了該表的意向鎖了,所以事務B不需要判斷每一行數據是否已經加鎖,可以快速通過意向鎖阻塞當前 SQL 的更新操作。

自增鎖(AUTO-INC Locks)

定義

針對自增列自增長的一個特殊的表級別鎖。

通過如下命令查看自增鎖的默認等級:

SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';

默認取值1,代表連續,事務未提交 ID 永久丟失。

演示案例

-- 事務A執行
BEGIN;
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('tom2',30,'1344444444',NOW());
ROLLBACK;

BEGIN;
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('xxx',30,'13444444444',NOW());
ROLLBACK;

-- 事務B執行
INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('yyy',30,'13444444444',NOW());

事務A執行完后,在執行事務B的語句,發現插入的 ID 數據不再連續,因為事務A獲取的 ID 數據在 ROLLBACK 之後被丟棄了。

臨鍵鎖(Next-Key Locks)

定義

當 SQL 執行按照索引進行數據的檢索時,查詢條件為範圍查找(between and、<、>等)並有數據命中,則此時 SQL 語句加上的鎖為 Next-key locks,鎖住索引的記錄 + 區間(左開右閉)

演示案例

演示之前,先看一下 t2 表的結構和數據內容。

臨鍵鎖(Next-key Locks):InnoDB 默認的行鎖算法。

t2 表中的數據行有4條數據:1,4,7,10,InnoDB 引擎會將表中的數據劃分為:(-∞, 1] (1, 4] (4, 7] (7, 10] (10, +∞),執行如下 SQL 語句:

-- 臨鍵鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE;

ROLLBACK

-- 事務B執行
BEGIN;

SELECT * FROM t2 WHERE id=4 FOR UPDATE; -- 可以執行
SELECT * FROM t2 WHERE id=7 FOR UPDATE; -- 鎖住
SELECT * FROM t2 WHERE id=10 FOR UPDATE; -- 鎖住
INSERT INTO `t2` (`id`, `name`) VALUES (9, '9'); -- 鎖住

SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE; 這條查詢語句命中了7這條數據,它會鎖住 (4, 7] 這個區間,同時還會鎖住下一個區間 (7, 10]。

為什麼 InnoDB 選擇臨鍵鎖作為行鎖的默認算法?

防止幻讀。當我們把下一個區間也鎖住的時候,這個時候我們要新增數據,就會被鎖住,這樣就可以防止幻讀。

間隙鎖(Gap Locks)

定義

當 SQL 執行按照索引進行數據的檢索時,查詢條件的數據不存在,這時 SQL 語句加上的鎖即為 Gap locks,鎖住數據不存在的區間(左開右開)

Gap 只在 RR 事務隔離級別存在。因為幻讀問題是在 RR 事務通過臨鍵鎖和 MVCC 解決的,而臨鍵鎖=間隙鎖+記錄鎖,所以間隙鎖只在 RR 事務隔離級別存在。

演示案例

-- 間隙鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE;
-- 或者
SELECT * FROM t2 WHERE id=6 FOR UPDATE;

ROLLBACK;

-- 事務B執行
INSERT INTO `t2` (`id`, `name`) VALUES (5, '5');
INSERT INTO `t2` (`id`, `name`) VALUES (6, '6');

 SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE; 這條查詢語句不能命中數據,它會鎖住 (4, 7] 這個區間。

記錄鎖(Record Locks)

定義

當 SQL 執行按照唯一性(Primary key、Unique key)索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句加上的鎖即為記錄鎖 Record Locks,鎖住具體的索引項

演示案例

-- 記錄鎖
-- 事務A執行
BEGIN;

SELECT * FROM t2 WHERE id=4 FOR UPDATE;

ROLLBACK;


-- 事務B執行
SELECT * FROM t2 WHERE id=7 FOR UPDATE;
SELECT * FROM t2 WHERE id=4 FOR UPDATE;

事務A執行 SELECT * FROM t2 WHERE id=4 FOR UPDATE; 把 id=4 的數據行鎖住。

當 SQL 執行按照普通索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句鎖住數據存在區間左開右開)

利用鎖解決事務併發帶來的問題

InnoDB 真正處理事務併發帶來的問題不僅僅是依賴鎖,還有其他的機制,下篇文章會講到,所以這裏只是演示利用鎖是如何解決事務併發帶來的問題,並不是 InnoDB 真實的處理方式。

利用鎖怎麼解決臟讀

在事務B的更新語句上面加上一把 X 鎖,這樣就可以有效的解決臟讀問題。

利用鎖怎麼解決不可重複讀

在事務A的查詢語句上面加上一把 S 鎖,事務B的更新操作將會被阻塞,這樣就可以有效的解決不可重複讀的問題。

利用鎖怎麼解決幻讀

在事務A的查詢語句上面加上一把 Next-key 鎖,通過臨鍵鎖的定義,可以知道這個時候,事務A會把 (-∞,+∞) 的區間數據都鎖住,事務B的新增操作將會被阻塞,這樣就可以有效的解決幻讀的問題。

死鎖

死鎖的介紹

  • 多個併發事務(2個或者以上);
  • 每個事務都持有鎖(或者是已經在等待鎖);
  • 每個事務都需要再繼續持有鎖;
  • 事務之間產生加鎖的循環等待,形成死鎖。

演示案例

-- 事務A執行
BEGIN;

UPDATE users SET lastUpdate = NOW() WHERE id =1;

UPDATE t2 SET `name`='test' WHERE id =1;

ROLLBACK;

-- 事務B執行
BEGIN;

UPDATE t2 SET `name`='test' WHERE id =1;

UPDATE users SET lastUpdate = NOW() WHERE id =1;

ROLLBACK;

事務A和事務B按照上面的執行步驟,最後因為存在相互等待的情況,所以 MySQL 判斷出現死鎖了。

死鎖的避免

  • 類似的業務邏輯以固定的順序訪問表和行。
  • 大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。
  • 在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。
  • 降低隔離級別,如果業務允許,將隔離級別調低也是較好的選擇
  • 為表添加合理的索引。可以看到如果不走索引將會為表的每一行記錄添加上鎖(或者說是表鎖)

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

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

【從今天開始好好學數據結構02】棧與隊列

目錄

我們今天主要來談談“棧”以及隊列這兩種數據結構。

回顧一下上一章中中,在數組中只要知道數據的下標,便可通過順序搜索很快查詢到數據,可以根據下標不同自由查找,然而今天要講的“棧”以及隊列這兩種數據結構訪問是受限制的,只允許在一端讀取、插入和刪除數據,這時候對它存在的意義產生了很大的疑惑。因為會覺得,相比數組和鏈表,棧帶給我的只有限制,並沒有任何優勢。那我直接使用數組或者鏈表不就好了嗎?為什麼還要用這個“操作受限”的“棧”呢?事實上,從功能上來說,數組或鏈表確實可以替代棧,但你要知道,特定的數據結構是對特定場景的抽象,而且,數組或鏈表暴露了太多的操作接口,操作上的確靈活自由,但使用時就比較不可控,自然也就更容易出錯。

@

1、理解棧與隊列

首先,如何理解“棧”?用現實一個通俗貼切的例子,我們平時放盤子的時候,都是從下往上一個一個放;取的時候,我們是從上往下一個一個地依次取,不能從中間任意抽出,先進后出,這就是典型的“”結構,當某個數據集合只涉及在一端插入和刪除數據,並且滿足後進先出、先進后出的特性,我們就應該首選“棧”這種數據結構。

其次如何理解隊列?同樣用現實一個通俗貼切的例子,平時在校的時候飯堂吃飯都是排隊,而且不能插隊,先進先出,這就是典型的隊列結構

2、用代碼談談棧

實際上,棧既可以用數組來實現,也可以用鏈表來實現。用數組實現的棧,我們叫作順序棧,用鏈表實現的棧,我們叫作鏈式棧。不管是順序棧還是鏈式棧,我們存儲數據只需要一個大小為n的數組就夠了。在入棧和出棧過程中,只需要一兩個臨時變量存儲空間,所以空間複雜度是O(1)。

注意,這裏存儲數據需要一個大小為n的數組,並不是說空間複雜度就是O(n)。因為,這n個空間是必須的,無法省掉。所以我們說空間複雜度的時候,是指除了原本的數據存儲空間外,算法運行還需要額外的存儲空間。

空間複雜度分析是不是很簡單?時間複雜度也不難。不管是順序棧還是鏈式棧,入棧、出棧只涉及棧頂個別數據的操作,所以時間複雜度都是O(1)。

還有一點,JVM內存管理中有個“堆棧”的概念。棧內存用來存儲局部變量和方法調用,堆內存用來存儲Java中的對象。那JVM裏面的“棧”跟我們這裏說的“棧”是不是一回事呢?如果不是,那它為什麼又叫作“棧”呢?知道的大牛請自覺評論區見面~

2.1、用數組實現的棧:順序棧

public class MyStack {
    
    //棧的底層我們使用數組來存儲數據
    int[] elements;

    public MyStack() {
        elements = new int[0];
    }
    
    //壓入元素
    public void push(int element) {
        // 創建一個新的數組
        int[] newArr = new int[elements.length + 1];
        // 把原數組中的元素複製到新數組中
        for (int i = 0; i < elements.length; i++) {
            newArr[i] = elements[i];
        }
        // 把添加的元素放入新數組中
        newArr[elements.length] = element;
        // 使用新數組替換舊數組
        elements = newArr;
    }
    
    //取出棧頂元素
    public int pop() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        //取出數組的最後一個元素
        int element = elements[elements.length-1];
        //創建一個新的數組
        int[] newArr = new int[elements.length-1];
        //原數組中除了最後一個元素的其它元素都放入新的數組中
        for(int i=0;i<elements.length-1;i++) {
            newArr[i]=elements[i];
        }
        //替換數組
        elements=newArr;
        //返回棧頂元素
        return element;
    }
    
    //查看棧頂元素
    public int peek() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        return elements[elements.length-1];
    }
    
    //判斷棧是否為空
    public boolean isEmpty() {
        return elements.length==0;
    }
    
}

2.2、測試數組實現的棧

import demo2.MyStack;

public class TestMyStack {

    public static void main(String[] args) {
        //創建一個棧
        MyStack ms = new MyStack();
        //壓入數組
        ms.push(9);
        ms.push(8);
        ms.push(7);
        //最出棧頂元素
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        //查看棧頂元素
//      System.out.println(ms.peek());
        System.out.println(ms.isEmpty());
    }

}

2.3、基於鏈表實現的棧:鏈式棧

package stack;
/**
 * 基於鏈表實現的棧。
 */
public class StackBasedOnLinkedList {
  private Node top = null;

  public void push(int value) {
    Node newNode = new Node(value, null);
    // 判斷是否棧空
    if (top == null) {
      top = newNode;
    } else {
      newNode.next = top;
      top = newNode;
    }
  }

  /**
   * 我用-1表示棧中沒有數據。
   */
  public int pop() {
    if (top == null) return -1;
    int value = top.data;
    top = top.next;
    return value;
  }

  public void printAll() {
    Node p = top;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private int data;
    private Node next;

    public Node(int data, Node next) {
      this.data = data;
      this.next = next;
    }

    public int getData() {
      return data;
    }
  }
}

3、用代碼談談隊列

棧只支持兩個基本操作:入棧push()和出棧pop()。隊列跟棧非常相似,支持的操作也很有限,最基本的操作也是兩個:入隊enqueue(),放一個數據到隊列尾部;出隊dequeue(),從隊列頭部取一個元素。所以,隊列跟棧一樣,也是一種操作受限的線性表數據結構。隊列的概念很好理解,基本操作也很容易掌握。作為一種非常基礎的數據結構,隊列的應用也非常廣泛,特別是一些具有某些額外特性的隊列,比如循環隊列、阻塞隊列、併發隊列。它們在很多偏底層系統、框架、中間件的開發中,起着關鍵性的作用。比如高性能隊列Disruptor、Linux環形緩存,都用到了循環併發隊列;Java concurrent併發包利用ArrayBlockingQueue來實現公平鎖等。

跟棧一樣,隊列可以用數組來實現,也可以用鏈表來實現。用數組實現的棧叫作順序棧,用鏈表實現的棧叫作鏈式棧。同樣,用數組實現的隊列叫作順序隊列,用鏈表實現的隊列叫作鏈式隊列

3.1、數組實現隊列:順序隊列

package queue;

// 用數組實現的隊列
public class ArrayQueue {
  // 數組:items,數組大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的數組
  public ArrayQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 如果tail == n 表示隊列已經滿了
    if (tail == n) return false;
    items[tail] = item;
    ++tail;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示隊列為空
    if (head == tail) return null;
    // 為了讓其他語言的同學看的更加明確,把--操作放到單獨一行來寫了
    String ret = items[head];
    ++head;
    return ret;
  }

  public void printAll() {
    for (int i = head; i < tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

3.2、鏈表實現的隊列:鏈式隊列

package queue;

/**
 * 基於鏈表實現的隊列
 */
public class QueueBasedOnLinkedList {

  // 隊列的隊首和隊尾
  private Node head = null;
  private Node tail = null;

  // 入隊
  public void enqueue(String value) {
    if (tail == null) {
      Node newNode = new Node(value, null);
      head = newNode;
      tail = newNode;
    } else {
      tail.next = new Node(value, null);
      tail = tail.next;
    }
  }

  // 出隊
  public String dequeue() {
    if (head == null) return null;

    String value = head.data;
    head = head.next;
    if (head == null) {
      tail = null;
    }
    return value;
  }

  public void printAll() {
    Node p = head;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private String data;
    private Node next;

    public Node(String data, Node next) {
      this.data = data;
      this.next = next;
    }

    public String getData() {
      return data;
    }
  }

}

3.2、循環隊列

用數組來實現隊列的時候會有數據搬移操作,這樣入隊操作性能就會受到影響。那有沒有辦法能夠避免數據搬移呢?我們來看看循環隊列的解決思路。

循環隊列,顧名思義,它長得像一個環。原本數組是有頭有尾的,是一條直線。現在我們把首尾相連,扳成了一個環。

package queue;
public class CircularQueue {
  // 數組:items,數組大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的數組
  public CircularQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 隊列滿了
    if ((tail + 1) % n == head) return false;
    items[tail] = item;
    tail = (tail + 1) % n;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示隊列為空
    if (head == tail) return null;
    String ret = items[head];
    head = (head + 1) % n;
    return ret;
  }

  public void printAll() {
    if (0 == n) return;
    for (int i = head; i % n != tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

好了,到這裏,總結一下隊列,隊列最大的特點就是先進先出,主要的兩個操作是入隊和出隊。跟棧一樣,它既可以用數組來實現,也可以用鏈表來實現。用數組實現的叫順序隊列,用鏈表實現的叫鏈式隊列。特別是長得像一個環的循環隊列。在數組實現隊列的時候,會有數據搬移操作,要想解決數據搬移的問題,我們就需要像環一樣的循環隊列。

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

【其他文章推薦】

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

台灣空運大陸一條龍服務

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

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

雷佳音黑色襯衫搭配連帽牛仔服

牛仔服是一種很減齡的服飾,雖然很多牛仔服會被做舊,營造出比較復古的感覺,但是這也掩蓋不了牛仔服本身的時尚氣質。而且牛仔服適合大多數人,對年齡、膚色、氣質的要求都很低,同時男性和女性都可以穿。最近雷佳音現身機場,身上就穿了一件連帽牛仔外套,真的是太減齡了

雷佳音的牛仔服就是做舊的款式,有種褪色的感覺,但是這種設計反而增加了牛仔服的時尚氣質,越月“老”的服裝減齡效果反而越強,男性服裝中能夠起到減齡效果的實在是比較少,風格差異太明顯,有的只適合老年人,有的則只適合年輕人,而提到百搭的款式時,能夠想到的大概也就只有牛仔外套了,所以想要減齡的中年男性不妨給自己買一件牛仔外套

雷佳音這款牛仔外套還有一個很特別的地方,那就是它的帽子是紅色的,連帽衛衣會增加男性身上多樣的氣質,讓男性看起來更加的活潑、隨性,而帽子的顏色也能夠對人的氣質產生更進一步的影響。雷佳音的“小紅帽”很搶鏡,紅色是一種很有感染力的顏色,這個顏色極適合女性,也適合男性,所以男性穿紅色的衣服也會很好看

雷佳音的這個造型非常的低調,黑色襯衫搭配連帽牛仔服,下面穿得是黑色的運動褲,運動褲也能夠增加造型上的休閑感,讓雷佳音看起來活力十足,雷佳音的手上拎着一個顏色很復古的背包,腳上穿着運動鞋,整個造型雖然沒有經過特別精心的設計,但是卻沒有給人特別普通的感覺,氣質還是很出眾的。另外,從這個角度來看,雷佳音的頭真的不算大

雷佳音服裝顏色的搭配特別好,有低調的地方,也有顯眼的地方,而且不會喧賓奪主,每件服飾都發揮着自己的作用,除了實用性以外,觀賞性也是很強的,對雷佳音的氣質起到了優化以及美化的作用,雷佳音身上的休閑氣質大大提升,隨性而溫和,讓人感覺到了自然與質樸

這件牛仔連帽外套太年輕了,雷佳音的長相比較成熟,所以很容易讓人誤會他的年齡,但是在牛仔連帽外套的襯托下,郭京飛的年齡感降低,而且他的皮膚非常好,這氣質郭京飛可比不了

雷佳音之前也穿過牛仔外套,當時搭配了紅色的褲子,紅色與牛仔的搭配非常的養眼,看似有很大的反差,其實風格與個性氣質卻很突出。雷佳音的這件牛仔外套是拼接款的,將衛衣與牛仔拼接到了一起,牛仔與衛衣都屬於休閑風格的服裝,所以搭配在一起絕對不會相互排斥

牛仔显示出個性,衛衣显示出活力,兩種風格的融合也可以起到明顯的減齡效果。而且衛衣是深藍色的,而牛仔服是淺藍色了,兩種藍色的對比,讓時尚感大大增加,氣質變得更加多樣

紅色運動褲也增加了雷佳音身上的活力感,因為雷佳音的個人形象是穩重、質樸的,這和他本身的性格是不相符合的,那麼要想突出性格中的特點,就要通過個性化的穿着來展現,紅色與藍色的搭配能夠突出雷佳音身上的活力、活潑,讓雷佳音的氣質變得年輕

紅色與藍色的搭配是很養眼的,尤其是牛仔藍,這種搭配能夠激發出活力,讓人產生不一樣的視覺享受,個性突出,而且一點都不突兀,搭配十分和諧

30歲或者40歲以上的男性都可學習雷佳音這種配色方法,在穿牛仔服飾的時候,不妨點綴一點艷麗的顏色,比如紅色、橘黃色、黃色等,這些顏色都可以讓個人形象更加鮮明

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?

長款的針織外套一直給人一種氣質感

剛剛過了一年一度的雙十一,不知道你們是否跟我一樣開啟了可怕的購物模式,每次都告訴自己一定要控制好自己的手,然而並沒有用。不過大家一定要理智消費,買一些有用的東西、真正適合自己的東西,相信大家一定會在雙十一買了一些衣服吧!給大家分享一些秋冬的必備單品,把這些單品好好的運用起來,穿出屬於自己的魅力。

1、 襯衫外套

襯衫不僅僅單穿很好看,當做外套披在外面也能夠給人一種非常休閑的感覺,建議大家選擇一些米色、黑色等比較好搭配的顏色。選擇一件純色的內搭,或者選擇一件純色系的內搭會更好的和襯衫呼應,無論是搭配褲子還是搭配裙裝,都能夠給人一種清新又休閑的感覺,使你整個人看起來非常的清爽,搭配平底的單鞋或者是板鞋,穿起來帶來舒適的感覺,也會讓你看上去更好看!

2、 長大衣

長款的針織外套一直給人一種氣質感,可以選擇一件純色的長外套,無論是比較樸素的顏色還是比較亮麗的顏色都能夠給人眼前一亮的感覺,搭配一件修身上衣再配上一條闊腿牛仔褲能夠使你看上去更時尚。黑色白色是最百搭好穿的,如果不知道選什麼色彩,就選其中之一就好了。大衣還可以搭配一條針織的連衣裙,能夠更好的展示出女人味,使女人看上去更有魅力。大家選擇含灰的色調的大衣更能顯出高級感和不俗的氣場來。

3、 深色牛仔褲

牛仔褲是無論什麼季節都必須的款式,因為牛仔褲實在是太經典了,無論是搭配襯衫、衛衣等任何款式的服裝,都能夠給人們帶來不一樣的感覺。大家備一條深色系的牛仔褲更百搭,因為深色的牛仔褲能夠給人帶來一種復古的感覺,搭配一件簡約的襯衫在套上一件長款的外套選擇一雙細跟的鞋子,這樣就能夠展示出街頭潮人的感覺,選擇這樣的服飾去街拍再適合不過了。天氣冷了,牛仔褲搭配上短靴也十分美麗。

4、 皮衣

皮衣穿在女人身上,能夠使女人變得格外的帥氣,皮衣搭配一件修身內搭選擇一條黑色的修身褲,能夠很好的展示出我們的身材,給人的感覺看上去更酷炫、更有特點。皮衣搭配裙裝也是意外的好看,無論是半身裙還是連衣裙都可以選擇搭配皮衣,會給人一種柔中帶剛的美感,這樣的美感是生活之中比較少見的。

5、 毛衣

毛衣可以說是大家的”老朋友”了,應該每個人的衣櫃中都有很多件毛衣吧,秋冬天我們可以選擇一件酒紅色的毛衣,這樣的顏色是非常高級的,第一眼看上去就很有質感,能夠更好的展示出女人的溫柔感,再搭配一條西裝褲或者是闊腿褲會更好的突出造型的美感。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?

冬天外套穿黑色時尚短靴,個性時尚,簡單大方!

【第一套】

百塔款的開衫毛衣,BF風,冬天可做外套穿,搭配純色打底上衣,和黑色皮褲,黑色時尚短靴,個性時尚,簡單大方!

【第二套】

經典的圓領,領邊鏤空蝴蝶結裝飾,性感優雅,淑女范十足 撞色條紋元素,更顯時尚前衛,搭配毛呢半身裙,后美美的出街吧。

【第三套】

韓范針織開衫,簡約的色系,大翻領設計,各種類型的MM都能輕鬆駕馭! 內搭條紋針織衫,氣質甜美,下身搭配修身小腳褲,顯腿長。

【第四套】

超洋氣的寬鬆版型中長款針織衫,完全不挑人的樣式,內搭長款格子襯衫,疊穿字母T恤連衣裙,更具層次感,街頭范十足。

【第五套】

這款外套經典版型,不挑身材、簡約幹練,上身穿有灑脫知性美feel,搭配簡潔皮褲,帥氣時尚!

【第六套】

基本款打底單穿衛衣,經典的圓領設計,溫暖又隨性,胸前的抽象膠印也不單調。搭配破洞牛仔褲,時尚街拍范。

【第七套】

很有范的oversize版型,大大寬鬆的設計超級有氣場,長度也是剛好遮住肉肉的臀部和大腿根,搭配修身打底褲,超顯身材!

【第八套】

西裝領雙排扣毛呢大衣,搭配雙V領撞色針織衫+毛呢包臀靴褲+玫瑰金手鐲+粗跟靴子,時尚大方的潮流裝扮哦。

【第九套】

時尚修身開衫,穿着時尚有型,舒適養眼的色彩,搭配白襯衫,展現清新的魅力。

【第十套】

時髦又氣質的V領造型,卡其色系是今年一直非常流行的顏色,搭配毛呢高腰短褲,拉長身材比例,休閑又隨性加上裸色短靴很有范。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?

秋冬季節情侶搭配技巧

如果你和心愛的TA,可以穿着情侶服飾,走在校園的小路上,聽着落恭弘=恭弘=叶 恭弘 恭弘被碎的聲音,這樣的畫面是不是也非常美好呢?兩個人手拉着走,彷彿一生都將這樣甜蜜、幸福的走下去…

可是現實總是很骨感的!當你也想和心愛的TA穿上一些情侶服飾的時候,你們倆應該總會在服飾的選擇上產生一些分歧吧!男生普遍喜歡比較偏酷,帥氣的服飾,而女生就喜歡一點可愛風的服飾。小編為了讓你和TA不再因情侶服飾選擇產生分歧,特為大家備上這篇秋冬季情侶巧搭配攻略,快來看看吧!

LOOK1:情侶背包是首要

提到情侶配飾可少不了情侶書包,情侶們可以選擇一些外型設計比較簡約,同時配色比較豐富的書包。因為設計簡約的書包更受男生的喜歡,而豐富的配色可以給女生帶來更多的選擇。同時盡可能的要選擇帆布材質、錦綸材質的書包,這樣的書包背起來非常輕,同時還很耐磨、耐用。

LOOK2:情侶衛衣不能少

平時你可以和心愛的TA穿上同款的衛衣來吸睛,衛衣可以說是集百搭、時尚和保暖於一身的衣服了,我們平時穿條牛仔褲就可以很好的與之搭配起來。對於小編來說,則更喜歡穿連帽衛衣,因為連帽衛衣穿上身的效果和版型都要優於普通衛衣,而且連帽衛衣還更保暖哦!

LOOK3:情侶鞋子很重要

都說可以透過一個人的鞋子看出來她的品味,情侶黨如果穿上一雙高顏值的情侶鞋出門是不是非常拉風呢?平時情侶們可以選擇同款、同配色的鞋子穿出門,比如AJ系列的鞋子,或者是匡威的1970s系列都是不錯的選擇呢!因為這些鞋子的配色比較豐富,更能給你提供更多的可能性。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※聽過「電子菸」嗎?想知道與一般傳統香菸有何不同嗎?

電子煙能幫助戒菸嗎?專家學者以健康觀點帶您來了解 !

※新手該如何選擇電子菸口味及濃度呢?

※你應該要知道的電子煙懶人包!

電子煙有爭議?真相解密

※全台最大電子煙交易平台?