分類
發燒車訊

設計模式系列之一:簡單工廠模式

1.1 定義

 簡單工廠模式屬於創建型模式,又稱為靜態工廠方法模式,在簡單工廠模式中,可以根據參數的不同,來返回不同類的實例,簡單工廠模式專門定義一個類來負責創建子類的實例,被創建的類通常有一個共同的父類

1.2 簡單工廠模式結構圖(簡版)

                             

 Factory:工廠類,簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象

 IProduct:抽象產品類,簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口

 Product:具體產品類,是簡單工廠模式的目標類

1.3 簡單工廠的實現一

 假設有一個電腦的代工生產商,它目前已經可以代工生產聯想電腦了,隨着業務的拓展,這個代工生產商還要生產惠普和mac電腦;

 這樣我們就需要用一個單獨的類來專門生產電腦,這就用到了簡單工廠模式,同時用到了繼承、封裝、多態等面向對象編程的思想,下面我們來實現簡單工廠模式

1.3.1 產品抽象類

public abstract class Computer {
    /**
     * 產品的抽象方法,由具體的產品類去實現
     */
    public abstract void start();

}

 

1.3.2  具體實現類

public class LenovoComputer extends Computer {

    @Override
    public void start() {
        System.out.println("lenovo computer run");
    }
}
public class MacComputer extends Computer {
    @Override
    public void start() {
        System.out.println("Mac computer run");
    }
}
public class HpComputer extends Computer {
    @Override
    public void start() {
        System.out.println("hp computer run");
    }
}

1.3.3  工廠類

public class ComputerFactory {
    public static Computer createComputer(String type) {
        Computer computer = null;
        switch (type) {
            case "lenovo":
                computer = new LenovoComputer();
                break;
            case "hp":
                computer = new HpComputer();
                break;
            case "Mac":
                computer = new MacComputer();
                break;
            default:
                break;
        }
        return computer;
    }
}

1.3.4  UML類圖

                          

1.4 優缺點

 優點:

 1. 工廠類含有必要的判斷邏輯,可以決定在什麼時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的責任,而僅僅“消費”產品;實現了對責任的分割,它提供了專門的工廠類用於創建對象

 2. 客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對於一些複雜的類名,通過簡單工廠模式可以減少使用者的記憶量。

 3. 通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。

缺點:

 1. 由於工廠類集中了所有產品創建邏輯,一旦不能正常工作,整個系統都要受到影響。

 2. 使用簡單工廠模式將會增加系統中類的個數,在一定程序上增加了系統的複雜度和理解難度。

 3. 系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,同樣破壞了“開閉原則”;在產品類型較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴展和維護

 4. 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構

1.5 適用場景

 1. 工廠類負責創建的對象比較少:由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。

 2. 客戶端只知道傳入工廠類的參數,對於如何創建對象不關心:客戶端既不需要關心創建細節,甚至連類名都不需要記住,只需要知道類型所對應的參數

1.6 模式應用

 1. JDK類庫中廣泛使用了簡單工廠模式,如工具類java.text.DateFormat,它用於格式化一個本地日期或者時間

public final static DateFormat getDateInstance(); 
public final static DateFormat getDateInstance(int style); 
public final static DateFormat getDateInstance(int style,Locale locale);

 2. 獲取不同加密算法的密鑰生成器

KeyGenerator keyGen=KeyGenerator.getInstance("DESede");

1.7 開閉原則

 對於上面兩種簡單工廠模式的實現方法,如果我們要添加新的 parser,那勢必要改動到 RuleConfigParserFactory 的代碼,那這是不是違反開閉原則呢?

 實際上,如果不是需要頻繁地添加新的 parser,只是偶爾修改一下 RuleConfigParserFactory 代碼,稍微不符合開閉原則,也是完全可以接受的

 儘管簡單工廠模式的代碼實現中,有多處 if 分支判斷邏輯,違背開閉原則,但權衡擴展性和可讀性,這樣的代碼實現在大多數情況下(比如,不需要頻繁地添加 parser,也沒有太多的 parser)是沒有問題的

 

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

分類
發燒車訊

03 . 二進制部署kubernetes1.18.4

簡介

目前生產部署kubernetes集群主要兩種方式

kubeadm

Kubeadm是一個K8s部署工具,提供kubeadm init和kubeadm join,用於快速部署Kubernetes集群。

二進制包

從github下載發行版的二進制包,手動部署每個組件,組成Kubernetes集群。

Kubeadm降低部署門檻,但屏蔽了很多細節,遇到問題很難排查。如果想更容易可控,推薦使用二進制包部署Kubernetes集群,雖然手動部署麻煩點,期間可以學習很多工作原理,也利於後期維護。

二進制部署K8s

List
CentOS7.3
cni-plugins-linux-amd64-v0.8.6.tgz
etcd-v3.4.9-linux-amd64.tar.gz
kube-flannel.yml
kubernetes-server-linux-amd64.tar.gz
角色 IP 組件
master 192.168.31.71 kube-apiserver,kube-controller-manager,kube-scheduler,etcd
Node1 192.168.31.74 kube-apiserver,kube-controller-manager,kube-scheduler
Node2 192.168.31.72 kubelet,kube-proxy,docker etcd

初始化環境

# 初始化
init_security() {
systemctl stop firewalld
systemctl disable firewalld &>/dev/null
setenforce 0
sed -i '/^SELINUX=/ s/enforcing/disabled/'  /etc/selinux/config
sed -i '/^GSSAPIAu/ s/yes/no/' /etc/ssh/sshd_config
sed -i '/^#UseDNS/ {s/^#//;s/yes/no/}' /etc/ssh/sshd_config
systemctl enable sshd crond &> /dev/null
rpm -e postfix --nodeps
echo -e "\033[32m [安全配置] ==> OK \033[0m"
}
init_security

init_yumsource() {
if [ ! -d /etc/yum.repos.d/backup ];then
    mkdir /etc/yum.repos.d/backup
fi
mv /etc/yum.repos.d/* /etc/yum.repos.d/backup 2>/dev/null
if ! ping -c2 www.baidu.com &>/dev/null    
then
    echo "您無法上外網,不能配置yum源"
    exit    
fi
    curl -o /etc/yum.repos.d/163.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
    curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo &>/dev/null
    yum clean all
    timedatectl set-timezone Asia/Shanghai
    echo "nameserver 114.114.114.114" > /etc/resolv.conf
    echo "nameserver 8.8.8.8" >> /etc/resolv.conf
    chattr +i /etc/resolv.conf
    yum -y install ntpdate
    ntpdate -b  ntp1.aliyun.com        # 對時很重要
    echo -e "\033[32m [YUM Source] ==> OK \033[0m"
}
init_yumsource

# 關掉swap分區
swapoff -a
# 如果想永久關掉swap分區,打開如下文件註釋掉swap哪一行即可.
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab #永久

# 配置主機名解析
tail -3 /etc/hosts
192.168.0.121 master
192.168.0.123 node1
192.168.0.124 node2

# 將橋接的IPv4流量傳遞到iptables的鏈
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system  # 生效


# 升級內核(非必須,只是性能更好)
wget https://cbs.centos.org/kojifiles/packages/kernel/4.9.220/37.el7/x86_64/kernel-4.9.220-37.el7.x86_64.rpm
  
rpm -ivh kernel-4.9.220-37.el7.x86_64.rpm
reboot

部署etcd集群

Etcd 是一個分佈式鍵值存儲系統,Kubernetes使用Etcd進行數據存儲,所以先準備一個Etcd數據庫,為解決Etcd單點故障,應採用集群方式部署,這裏使用3台組建集群,可容忍1台機器故障,當然,你也可以使用5台組建集群,可容忍2台機器故障。

節點名稱 IP
etcd-1 192.168.31.71
etcd-2 192.168.31.72
etcd-3 192.168.31.73

注:為了節省機器,這裏與K8s節點機器復用。也可以獨立於k8s集群之外部署,只要apiserver能連接到就行。

準備cfssl證書生成工具

cfssl是一個開源的證書管理工具,使用json文件生成證書,相比openssl更方便使用。

找任意一台服務器操作,這裏用Master節點。

wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64 cfssl-certinfo_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
mv cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo
生成etcd證書
創建工作目錄
mkdir -p ~/TLS/{etcd,k8s}

cd TLS/etcd
自簽CA
cat > ca-config.json << EOF
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "www": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

cat > ca-csr.json << EOF
{
    "CN": "etcd CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF
生成證書
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

ls *pem
ca-key.pem  ca.pem
使用自簽CA簽發etcd https證書

創建證書申請文件

cat > server-csr.json << EOF
{
    "CN": "etcd",
    "hosts": [
    "192.168.0.121",
    "192.168.0.123",
    "192.168.0.124"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing"
        }
    ]
}
EOF

注:上述文件hosts字段中IP為所有etcd節點的集群內部通信IP,一個都不能少!為了方便後期擴容可以多寫幾個預留的IP。

生成證書
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www server-csr.json | cfssljson -bare server

ls server*pem
server-key.pem  server.pem
下載etcd二進制文件
wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz
創建工作目錄並解壓二進制包
mkdir /opt/etcd/{bin,cfg,ssl} -p
tar zxvf etcd-v3.4.9-linux-amd64.tar.gz
mv etcd-v3.4.9-linux-amd64/{etcd,etcdctl} /opt/etcd/bin/

# 配置etcd
cat /opt/etcd/cfg/etcd.conf 
#[Member]
ETCD_NAME="etcd-1"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.0.121:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.0.121:2379"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.0.121:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.0.121:2379"
ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.0.121:2380,etcd-2=https://192.168.0.123:2380,etcd-3=https://192.168.0.124:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

# ETCD_NAME:節點名稱,集群中唯一
# ETCD_DATA_DIR:數據目錄
# ETCD_LISTEN_PEER_URLS:集群通信監聽地址
# ETCD_LISTEN_CLIENT_URLS:客戶端訪問監聽地址
# ETCD_INITIAL_ADVERTISE_PEER_URLS:集群通告地址
# ETCD_ADVERTISE_CLIENT_URLS:客戶端通告地址
# ETCD_INITIAL_CLUSTER:集群節點地址
# ETCD_INITIAL_CLUSTER_TOKEN:集群Token
# ETCD_INITIAL_CLUSTER_STATE:加入集群的當前狀態,new是新集群,existing表示加入已有集群
systemd管理etcd
cat > /usr/lib/systemd/system/etcd.service << EOF
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
EnvironmentFile=/opt/etcd/cfg/etcd.conf
ExecStart=/opt/etcd/bin/etcd \
--cert-file=/opt/etcd/ssl/server.pem \
--key-file=/opt/etcd/ssl/server-key.pem \
--peer-cert-file=/opt/etcd/ssl/server.pem \
--peer-key-file=/opt/etcd/ssl/server-key.pem \
--trusted-ca-file=/opt/etcd/ssl/ca.pem \
--peer-trusted-ca-file=/opt/etcd/ssl/ca.pem \
--logger=zap
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
拷貝剛生成證書及生成的文件拷貝到節點2,節點3
cp ~/TLS/etcd/ca*pem ~/TLS/etcd/server*pem /opt/etcd/ssl/

scp -r /opt/etcd/ node1:/opt/
scp -r /opt/etcd/ node2:/opt/
scp /usr/lib/systemd/system/etcd.service node1:/usr/lib/systemd/system/
scp /usr/lib/systemd/system/etcd.service node2:/usr/lib/systemd/system/
修改節點2和節點3etcd.conf配置文件
node-1
cat /opt/etcd/cfg/etcd.conf 
#[Member]
ETCD_NAME="etcd-2"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.0.123:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.0.123:2379"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.0.123:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.0.123:2379"
ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.0.121:2380,etcd-2=https://192.168.0.123:2380,etcd-3=https://192.168.0.124:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"


# node-2
#[Member]
ETCD_NAME="etcd-3"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.0.124:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.0.124:2379"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.0.124:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.0.124:2379"
ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.0.121:2380,etcd-2=https://192.168.0.123:2380,etcd-3=https://192.168.0.124:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

# 啟動服務並設置開機自啟
systemctl daemon-reload
systemctl start etcd
systemctl enable etcd
驗證etcd集群狀態
 /opt/etcd/bin/etcdctl --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.0.121:2379,https://192.168.0.123:2379,https://192.168.0.124:2379" endpoint healthhttps://192.168.0.124:2379 is healthy: successfully committed proposal: took = 13.213712ms
https://192.168.0.121:2379 is healthy: successfully committed proposal: took = 12.907787ms
https://192.168.0.123:2379 is healthy: successfully committed proposal: took = 12.168703ms
        
# 如果輸出上面信息,就說明集群部署成功。如果有問題第一步先看日誌:/var/log/message 或 journalctl -u etcd

安裝docker

下載安裝docker
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum -y install docker-ce-19.03.9-3.el7
配置docker鏡像源
mkdir /etc/docker
cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF
啟動並設置開機自啟
systemctl daemon-reload
systemctl start docker
systemctl enable docker

部署Master Node

生成kube-apiserver證書

1. 自簽證書頒發機構(CA)

cat > ca-config.json << EOF
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF
cat > ca-csr.json << EOF
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
EOF

# 生成證書
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

ls *pem
ca-key.pem  ca.pem
使用自簽CA簽發kube-apiserver https 證書
cat /root/TLS/k8s/server-csr.json 
{
    "CN": "kubernetes",
    "hosts": [
      "10.0.0.1",
      "127.0.0.1",
      "192.168.0.121",
      "192.168.0.123",
      "192.168.0.124",
      "192.168.0.125",
      "192.168.0.100",
      "kubernetes",
      "kubernetes.default",
      "kubernetes.default.svc",
      "kubernetes.default.svc.cluster",
      "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "BeiJing",
            "ST": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}

# 上述文件hosts字段中IP為所有Master/LB/VIP IP,一個都不能少!為了方便後期擴容可以多寫幾個預留的IP。

# 生成證書
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server

ls server*pem
server-key.pem  server.pem
下載解壓二進制包
wget https://dl.k8s.io/v1.18.4/kubernetes-server-linux-amd64.tar.gz
mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs} 
tar zxvf kubernetes-server-linux-amd64.tar.gz
cd kubernetes/server/bin
cp kube-apiserver kube-scheduler kube-controller-manager /opt/kubernetes/bin
cp kubectl /usr/bin/
部署kube-apiserver
cat /opt/kubernetes/cfg/kube-apiserver.conf 
KUBE_APISERVER_OPTS="--logtostderr=false \
--v=2 \
--log-dir=/opt/kubernetes/logs \
--etcd-servers=https://192.168.0.121:2379,https://192.168.0.123:2379,https://192.168.0.124:2379 \
--bind-address=192.168.0.121 \
--secure-port=6443 \
--advertise-address=192.168.0.121 \
--allow-privileged=true \
--service-cluster-ip-range=10.0.0.0/24 \
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \
--authorization-mode=RBAC,Node \
--enable-bootstrap-token-auth=true \
--token-auth-file=/opt/kubernetes/cfg/token.csv \
--service-node-port-range=30000-32767 \
--kubelet-client-certificate=/opt/kubernetes/ssl/server.pem \
--kubelet-client-key=/opt/kubernetes/ssl/server-key.pem \
--tls-cert-file=/opt/kubernetes/ssl/server.pem  \
--tls-private-key-file=/opt/kubernetes/ssl/server-key.pem \
--client-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \
--etcd-cafile=/opt/etcd/ssl/ca.pem \
--etcd-certfile=/opt/etcd/ssl/server.pem \
--etcd-keyfile=/opt/etcd/ssl/server-key.pem \
--audit-log-maxage=30 \
--audit-log-maxbackup=3 \
--audit-log-maxsize=100 \
--audit-log-path=/opt/kubernetes/logs/k8s-audit.log"


# –logtostderr:啟用日誌
# —v:日誌等級
# –log-dir:日誌目錄
# –etcd-servers:etcd集群地址
# –bind-address:監聽地址
# –secure-port:https安全端口
# –advertise-address:集群通告地址
# –allow-privileged:啟用授權
# –service-cluster-ip-range:Service虛擬IP地址段
# –enable-admission-plugins:准入控制模塊
# –authorization-mode:認證授權,啟用RBAC授權和節點自管理
# –enable-bootstrap-token-auth:啟用TLS bootstrap機制
# –token-auth-file:bootstrap token文件
# –service-node-port-range:Service nodeport類型默認分配端口範圍
# –kubelet-client-xxx:apiserver訪問kubelet客戶端證書
# –tls-xxx-file:apiserver https證書
# –etcd-xxxfile:連接Etcd集群證書
# –audit-log-xxx:審計日誌
拷貝剛生成證書
cp ~/TLS/k8s/ca*pem ~/TLS/k8s/server*pem /opt/kubernetes/ssl/
啟用TLS Bootstrapping機制

TLS Bootstraping:Master apiserver啟用TLS認證后,Node節點kubelet和kube-proxy要與kube-apiserver進行通信,必須使用CA簽發的有效證書才可以,當Node節點很多時,這種客戶端證書頒發需要大量工作,同樣也會增加集群擴展複雜度。為了簡化流程,Kubernetes引入了TLS bootstraping機制來自動頒發客戶端證書,kubelet會以一個低權限用戶自動向apiserver申請證書,kubelet的證書由apiserver動態簽署。所以強烈建議在Node上使用這種方式,目前主要用於kubelet,kube-proxy還是由我們統一頒發一個證書

TLS bootstraping 工作流程

創建上述配置文件中token文件

cat > /opt/kubernetes/cfg/token.csv << EOF
c47ffb939f5ca36231d9e3121a252940,kubelet-bootstrap,10001,"system:node-bootstrapper"
EOF

# 格式:token,用戶名,UID,用戶組

token也可自行生成替換

head -c 16 /dev/urandom | od -An -t x | tr -d ' '
systemd管理apiserver
cat > /usr/lib/systemd/system/kube-apiserver.service << EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-apiserver.conf
ExecStart=/opt/kubernetes/bin/kube-apiserver \$KUBE_APISERVER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
啟動設置開機啟動
systemctl daemon-reload
systemctl start kube-apiserver
systemctl enable kube-apiserver
授權kubelet-bootstrap用戶允許請求證書
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap

部署kube-controller-manager

創建配置文件
cat /opt/kubernetes/cfg/kube-controller-manager.conf 
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \
--v=2 \
--log-dir=/opt/kubernetes/logs \
--leader-elect=true \
--master=127.0.0.1:8080 \
--bind-address=127.0.0.1 \
--allocate-node-cidrs=true \
--cluster-cidr=10.244.0.0/16 \
--service-cluster-ip-range=10.0.0.0/24 \
--cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \
--cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem  \
--root-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \
--experimental-cluster-signing-duration=87600h0m0s"

# –master:通過本地非安全本地端口8080連接apiserver。
# –leader-elect:當該組件啟動多個時,自動選舉(HA)
# –cluster-signing-cert-file/–cluster-signing-key-file:自動為kubelet頒發證書的CA,與apiserver保持一致
systemd管理controller-manager
cat > /usr/lib/systemd/system/kube-controller-manager.service << EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-controller-manager.conf
ExecStart=/opt/kubernetes/bin/kube-controller-manager \$KUBE_CONTROLLER_MANAGER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
啟動設置開機啟動
systemctl daemon-reload
systemctl start kube-controller-manager
systemctl enable kube-controller-manager

部署kube-scheduler

創建配置文件
cat /opt/kubernetes/cfg/kube-scheduler.conf 
KUBE_SCHEDULER_OPTS="--logtostderr=false --v=2 --log-dir=/opt/kubernetes/logs --leader-elect --master=127.0.0.1:8080 --bind-address=127.0.0.1"

# –master:通過本地非安全本地端口8080連接apiserver。
# –leader-elect:當該組件啟動多個時,自動選舉(HA)
systemd管理scheduler
cat > /usr/lib/systemd/system/kube-scheduler.service << EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-scheduler.conf
ExecStart=/opt/kubernetes/bin/kube-scheduler \$KUBE_SCHEDULER_OPTS
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
啟動並設置開機自啟動
systemctl daemon-reload
systemctl start kube-scheduler
systemctl enable kube-scheduler
查看集群狀態
# 所有組件都已經啟動成功,通過kubectl工具查看當前集群組件狀態:
kubectl get cs
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok                  
controller-manager   Healthy   ok                  
etcd-2               Healthy   {"health":"true"}   
etcd-1               Healthy   {"health":"true"}   
etcd-0               Healthy   {"health":"true"} 

部署worker node

下面還是在Master Node上操作,即同時作為Worker Node

創建工作目錄並拷貝二進制文件

在所有worker node創建工作目錄

mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs} 

從master節點拷貝

cd kubernetes/server/bin
cp kubelet kube-proxy /opt/kubernetes/bin   # 本地拷貝,註釋這裏操作還是master節點,

部署kubelet

創建配置文件
cat /opt/kubernetes/cfg/kubelet.conf 
KUBELET_OPTS="--logtostderr=false \
--v=2 \
--log-dir=/opt/kubernetes/logs \
--hostname-override=master \
--network-plugin=cni \
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
--bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
--config=/opt/kubernetes/cfg/kubelet-config.yml \
--cert-dir=/opt/kubernetes/ssl \
--pod-infra-container-image=lizhenliang/pause-amd64:3.0"


# –hostname-override:显示名稱,集群中唯一
# –network-plugin:啟用CNI
# –kubeconfig:空路徑,會自動生成,後面用於連接apiserver
# –bootstrap-kubeconfig:首次啟動向apiserver申請證書
# –config:配置參數文件
# –cert-dir:kubelet證書生成目錄
# –pod-infra-container-image:管理Pod網絡容器的鏡像
配置參數文件
cat > /opt/kubernetes/cfg/kubelet-config.yml << EOF
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 0.0.0.0
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS:
- 10.0.0.2
clusterDomain: cluster.local 
failSwapOn: false
authentication:
  anonymous:
    enabled: false
  webhook:
    cacheTTL: 2m0s
    enabled: true
  x509:
    clientCAFile: /opt/kubernetes/ssl/ca.pem 
authorization:
  mode: Webhook
  webhook:
    cacheAuthorizedTTL: 5m0s
    cacheUnauthorizedTTL: 30s
evictionHard:
  imagefs.available: 15%
  memory.available: 100Mi
  nodefs.available: 10%
  nodefs.inodesFree: 5%
maxOpenFiles: 1000000
maxPods: 110
EOF
生成bootstrap.kubeconfig文件
KUBE_APISERVER="https://192.168.0.121:6443" # apiserver IP:PORT

TOKEN="c47ffb939f5ca36231d9e3121a252940" # 與token.csv里保持一致

# 生成 kubelet bootstrap kubeconfig 配置文件
kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/kubernetes/ssl/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig
  
  
kubectl config set-credentials "kubelet-bootstrap" \
  --token=${TOKEN} \
  --kubeconfig=bootstrap.kubeconfig
  
  
kubectl config set-context default \
  --cluster=kubernetes \
  --user="kubelet-bootstrap" \
  --kubeconfig=bootstrap.kubeconfig
  
  
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

# 拷貝到配置文件路徑
cp bootstrap.kubeconfig /opt/kubernetes/cfg
systemd管理kubelet
cat > /usr/lib/systemd/system/kubelet.service << EOF
[Unit]
Description=Kubernetes Kubelet
After=docker.service
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kubelet.conf
ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
啟動並設置開機自啟
systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet
批准kubelet證書並加入集群
# 查看kubelet證書請求
kubectl get csr
NAME                                                   AGE    SIGNERNAME                                    REQUESTOR           CONDITION
node-csr-uCEGPOIiDdlLODKts8J658HrFq9CZ--K6M4G7bjhk8A   6m3s   kubernetes.io/kube-apiserver-client-kubelet   kubelet-bootstrap   Pending

# 批准申請
kubectl certificate approve node-csr-uCEGPOIiDdlLODKts8J658HrFq9CZ--K6M4G7bjhk8A

# 查看節點
kubectl get node
NAME     STATUS   ROLES    AGE    VERSION
master   Ready    <none>   123m   v1.18.4

# 由於網絡插件還沒有部署,節點會沒有準備就緒 NotReady

部署kube-proxy

創建配置文件
cat > /opt/kubernetes/cfg/kube-proxy.conf << EOF
KUBE_PROXY_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--config=/opt/kubernetes/cfg/kube-proxy-config.yml"
EOF
創建參數文件
cat > /opt/kubernetes/cfg/kube-proxy-config.yml << EOF
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
metricsBindAddress: 0.0.0.0:10249
clientConnection:
  kubeconfig: /opt/kubernetes/cfg/kube-proxy.kubeconfig
hostnameOverride: k8s-master
clusterCIDR: 10.0.0.0/24
EOF
生成kube-proxy.kubeconfig文件

生成kube-proxy證書

# 切換工作目錄
cd TLS/k8s

# 創建證書請求文件
cat > kube-proxy-csr.json << EOF
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "BeiJing",
      "ST": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

# 生成證書
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

ls kube-proxy*pem
kube-proxy-key.pem  kube-proxy.pem
生成kubeconfig文件
KUBE_APISERVER="https://192.168.0.121:6443"

kubectl config set-cluster kubernetes \
  --certificate-authority=/opt/kubernetes/ssl/ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=kube-proxy.kubeconfig
  
kubectl config set-credentials kube-proxy \
  --client-certificate=./kube-proxy.pem \
  --client-key=./kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig
  
kubectl config set-context default \
  --cluster=kubernetes \
  --user=kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig
  
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

# 拷貝配置文件到指定路徑
cp kube-proxy.kubeconfig /opt/kubernetes/cfg/
systemd管理kube-proxy
cat > /usr/lib/systemd/system/kube-proxy.service << EOF
[Unit]
Description=Kubernetes Proxy
After=network.target
[Service]
EnvironmentFile=/opt/kubernetes/cfg/kube-proxy.conf
ExecStart=/opt/kubernetes/bin/kube-proxy \$KUBE_PROXY_OPTS
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
啟動並設置開機啟動
systemctl daemon-reload
systemctl start kube-proxy
systemctl enable kube-proxy

部署CNI網絡

準備二進制文件
wget https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz
  
# 解壓二進制文件並移動到默認工作目錄
mkdir /opt/cni/bin
tar zxvf cni-plugins-linux-amd64-v0.8.6.tgz -C /opt/cni/bin

# 部署cni網絡
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
sed -i -r "s#quay.io/coreos/flannel:.*-amd64#lizhenliang/flannel:v0.12.0-amd64#g" kube-flannel.yml

# 默認鏡像地址無法訪問,修改為docker hub鏡像倉庫。
kubectl apply -f kube-flannel.yml

kubectl get pods -n kube-system
NAME                          READY   STATUS    RESTARTS   AGE
kube-flannel-ds-amd64-2pc95   1/1     Running   0          72s

kubectl get node
NAME         STATUS   ROLES    AGE   VERSION
master   Ready    <none>   41m   v1.18.4
# 部署好網絡插件,Node準備就緒
授權apiserver訪問kubelet
cat > apiserver-to-kubelet-rbac.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
      - pods/log
    verbs:
      - "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF

kubectl apply -f apiserver-to-kubelet-rbac.yaml
新增加Worker Node

拷貝已部署好的Node相關文件到新節點

scp -r /opt/kubernetes/ node1:/opt/
scp -r /usr/lib/systemd/system/{kubelet,kube-proxy}.service node1:/usr/lib/systemd/system

scp -r /opt/cni/ node1:/opt/
scp /opt/kubernetes/ssl/ca.pem node1:/opt/kubernetes/ssl
刪除kubelet證書和kubeconfig文件
rm -f /opt/kubernetes/cfg/kubelet.kubeconfig 
rm -f /opt/kubernetes/ssl/kubelet*

# 這幾個文件是證書申請審批后自動生成的,每個Node不同,必須刪除重新生成。
修改主機名並設置開機自啟動
vi /opt/kubernetes/cfg/kubelet.conf
--hostname-override=node1

vi /opt/kubernetes/cfg/kube-proxy-config.yml
hostnameOverride: node1

# 啟動並設置開機啟動
systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet
systemctl start kube-proxy
systemctl enable kube-proxy
再master上批准Node kubelet證書申請
kubectl get csr
NAME                                                   AGE   SIGNERNAME                                    REQUESTOR           CONDITION
node-csr-4zTjsaVSrhuyhIGqsefxzVoZDCNKei-aE2jyTP81Uro   89s   kubernetes.io/kube-apiserver-client-kubelet   kubelet-bootstrap   Pending

kubectl certificate approve node-csr-4zTjsaVSrhuyhIGqsefxzVoZDCNKei-aE2jyTP81Uro

kubectl get node
NAME     STATUS   ROLES    AGE    VERSION
master   Ready    <none>   138m   v1.18.4
node1    Ready    <none>   120m   v1.18.4
node2    Ready    <none>   112m   v1.18.4

部署Dashboard

下載dashboard.yaml文件
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta8/aio/deploy/recommended.yaml
修改yaml配置文件使其端口暴露外部訪問
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta8/aio/deploy/recommended.yaml
  
# 默認Dashboard只能集群內部訪問,修改Service為NodePort類型,暴露到外部:

vi recommended.yaml
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 30001  # 修改這裏
  type: NodePort   # 修改這裏
  selector:
    k8s-app: kubernetes-dashboard

kubectl apply -f recommended.yaml

kubectl get pods,svc -n kubernetes-dashboard
NAME                                             READY   STATUS    RESTARTS   AGE
pod/dashboard-metrics-scraper-694557449d-69x7g   1/1     Running   0          111m
pod/kubernetes-dashboard-9774cc786-kwgkt         1/1     Running   0          111m

NAME                                TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
service/dashboard-metrics-scraper   ClusterIP   10.0.0.3     <none>        8000/TCP        111m
service/kubernetes-dashboard        NodePort    10.0.0.122   <none>        443:30001/TCP   111m
創建service account並綁定默認cluster-admin管理員集群角色
kubectl create serviceaccount dashboard-admin -n kube-system
kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin
kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/dashboard-admin/{print $1}')

# 接下來訪問https://node ip:30001
# 然後將上面過濾出來的token複製上面即可訪問dashboard

# 我們可以部署個Nginx測試下集群可用性
kubectl run --generator=run-pod/v1 nginx-test2 --image=daocloud.io/library/nginx --port=80 --replicas=1

kubectl get pods -o wide
kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
nginx-test2   1/1     Running   0          89m   10.244.2.2   node2   <none>           <none>

# 我們去相應的節點訪問指定IP即可訪問
[root@node1 ~]# curl -I -s  10.244.2.2 |grep 200
HTTP/1.1 200 OK

部署CoreDNS

CoreDNS用於集群內部Service名稱解析

kubectl apply -f coredns.yaml 
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns 
configmap/coredns created
deployment.apps/coredns created
DNS解析測試
kubectl run -it --rm dns-test --image=busybox:1.28.4 sh
If you don't see a command prompt, try pressing enter.
/ # nslookup kubernetes
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local

此篇文章借鑒於公眾號DevOps技術棧 ,作者阿良

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

分類
發燒車訊

jvm入門及理解(六)——垃圾回收與算法

一、jvm垃圾回收要做的事情

  • 哪些內存需要回收
  • 什麼時候回收
  • 怎麼回收

二、如何判斷對象已經死亡,或者說確定為垃圾

  1. 引用計數法:

    給對象中添加一個引用計數器,每當有一個地方引用它時,計數器的值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。這也就是需要回收的對象,簡單地說,即一個對象如果沒有任何與之關聯的引用,即他們的引用計數都為 0,則說明對象不太可能再被用到,那麼這個對象就是可回收的對象。

    引用計數法是對象記錄自己被多少程序引用,引用計數為零的對象將被清除。

    計數器表示的是有多少程序引用了這個對象(被引用數)。計數器是無符號的整數。

  2. 根搜索算法:

    通過一系列成為GC roots的點作為起點,向下搜索,當一個對象到任何GC Roots時沒有引用鏈相連,則說明對象已經死亡。

    如果在GC roots和一個對象之間沒有可達路徑(引用鏈),則稱該對象是不可達的。要注意的是,不可達對象不等價於可回收對象,不可達對象變為可回收對象至少要經過兩次標記 過程。兩次標記后仍然是可回收對象,則將面臨回收。

    jvm會將以下的對象定義為GC Roots:

 

  • Java虛擬機棧中引用的對象:比如方法裏面定義這種局部變量 User user= new User();
  • 方法區中的靜態屬性引用的對象:比如 private static User user = new User();
  • 常量引用的對象:比如 private static final  User user = new User();
  • 本地方法棧(JNI)中引用的對象

三、垃圾回收算法

  1. 標記清除算法(Mark-Sweep)

    是最基礎的垃圾回收算法,分為兩個階段,標註和清除。標記階段標記出所有需要回收的對象,清除階段回收被標記的對象所佔用的空間。首先從根開始將可能被引用的對象用遞歸的方式進行標記,然後將沒有標記到的對象作為垃圾進行回收。

 

 

從圖中我們就可以發現,該算法最大的問題是內存碎片化嚴重,後續可能發生大對象不能找到可利用空間的問題。

 

 

  2. 複製算法(copying)

   為了解決 Mark-Sweep 算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分為等大小 的兩塊。每次只使用其中一塊,當這一塊內存滿后將尚存活的對象複製到另一塊上去,把已使用 的內存清掉,如圖:

 

 

 

 

 

 這種算法雖然實現簡單,內存效率高,不易產生碎片,但是最大的問題是可用內存被壓縮到了原 本的一半。且存活對象增多的話,Copying 算法的效率會大大降低。

  

  3.標記整理算法(Mark-Compact)

   結合了以上兩個算法,為了避免缺陷而提出。標記階段和 Mark-Sweep 算法相同,標記后不是清 理對象,而是將存活對象移向內存的一端。然後清除端邊界外的對象。如圖:

 

 

 

四、分代收集算法

  介紹:分代收集法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的不同生命周期將內存 劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特點是每次垃圾回收時只有少量對象需要被回收,新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的算法。

  1. 新生代與複製算法

    目前大部分 JVM 的 GC 對於新生代都採取 Copying 算法,因為新生代中每次垃圾回收都要 回收大部分對象,即要複製的操作比較少,但通常並不是按照 1:1 來劃分新生代。一般將新生代 劃分為一塊較大的 Eden 空間和兩個較小的 Surviror 空間(From Space, To Space),每次使用 Eden 空間和其中的一塊 Survivor 空間,當進行回收時,將該兩塊空間中還存活的對象複製到另 一塊 Survivor 空間中。

  

 

 

 假設from space為s0,to space 為s1

算法過程:

  1. Eden+S0可分配新生對象;
  2. 對Eden+S0進行垃圾收集,存活對象複製到S1清理Eden+S0。一次新生代GC結束。
  3. Eden+S1可分配新生對象;
  4. 對Eden+S1進行垃圾收集,存活對象複製到S0清理Eden+S1。二次新生代GC結束。
  5. 循環1。

  2.老年代與標記整理算法

老年代因為每次只回收少量對象,因而採用 Mark-Compact 算法。

1. JAVA 虛擬機提到過的處於方法區的永生代(Permanet Generation),它用來存儲 class 類, 常量,方法描述等。對永生代的回收主要包括廢棄常量和無用的類。

2. 對象的內存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目 前存放對象的那一塊),少數情況會直接分配到老生代。

3. 當新生代的 Eden Space 和 From Space 空間不足時就會發生一次 GC,進行 GC 后,Eden Space 和 From Space 區的存活對象會被挪到 To Space,然後將 Eden Space 和 From Space 進行清理。

4. 如果 To Space 無法足夠存儲某個對象,則將這個對象存儲到老生代。

5. 在進行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反覆循環。

6. 當對象在 Survivor 區躲過一次 GC 后,其年齡就會+1。默認情況下年齡到達 15 的對象會被 移到老生代中。

 

五、分區收集算法

分區算法則將整個堆空間劃分為連續的不同小區間, 每個小區間獨立使用, 獨立回收. 這樣做的 好處是可以控制一次回收多少個小區間 , 根據目標停頓時間, 每次合理地回收若干個小區間(而不是 整個堆), 從而減少一次 GC 所產生的停頓。

 

六、java中的四種引用

  • 強引用

在 Java 中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引 用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即 使該對象以後永遠都不會被用到 JVM 也不會回收。因此強引用是造成 Java 內存泄漏的主要原因之一,直接new的對象就是強引用的。

  • 軟引用

軟引用需要用 SoftReference 類來實現,對於只有軟引用的對象來說,當系統內存足夠時它 不會被回收,當系統內存空間不足時它會被回收。軟引用通常用在對內存敏感的程序中。

  • 弱引用

弱引用需要用 WeakReference 類來實現,它比軟引用的生存期更短,對於只有弱引用的對象 來說,只要垃圾回收機制一運行,不管 JVM 的內存空間是否足夠,總會回收該對象佔用的內存。

  • 虛引用

虛引用需要 PhantomReference 類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛 引用的主要作用是跟蹤對象被垃圾回收的狀態。

 

七、GC 垃圾收集器

Java 堆內存被劃分為新生代和年老代兩部分,新生代主要使用複製和標記-清除垃圾回收算法; 年老代主要使用標記-整理垃圾回收算法,因此 java 虛擬中針對新生代和年老代分別提供了多種不 同的垃圾收集器,JDK1.6 中 Sun HotSpot 虛擬機的垃圾收集器如下:

 

新生代:

  1.  Serial 垃圾收集器(單線程、複製算法)

Serial(英文連續)是最基本垃圾收集器,使用複製算法,曾經是JDK1.3.1 之前新生代唯一的垃圾 收集器。Serial 是一個單線程的收集器,它不但只會使用一個 CPU 或一條線程去完成垃圾收集工 作,並且在進行垃圾收集的同時,必須暫停其他所有的工作線程,直到垃圾收集結束。 Serial 垃圾收集器雖然在收集垃圾過程中需要暫停所有其他的工作線程,但是它簡單高效,對於限 定單個 CPU 環境來說,沒有線程交互的開銷,可以獲得最高的單線程垃圾收集效率,因此 Serial 垃圾收集器依然是 java 虛擬機運行在 Client 模式下默認的新生代垃圾收集器。

   2.ParNew 垃圾收集器(Serial+多線程)

ParNew 垃圾收集器其實是 Serial 收集器的多線程版本,也使用複製算法,除了使用多線程進行垃 圾收集之外,其餘的行為和 Serial 收集器完全一樣,ParNew 垃圾收集器在垃圾收集過程中同樣也 要暫停所有其他的工作線程。 ParNew 收集器默認開啟和 CPU 數目相同的線程數,可以通過-XX:ParallelGCThreads 參數來限 制垃圾收集器的線程數。ParNew雖然是除了多線程外和Serial 收集器幾乎完全一樣,但是ParNew垃圾收集器是很多 java 虛擬機運行在 Server 模式下新生代的默認垃圾收集器。

  3.Parallel Scavenge 收集器(多線程複製算法、高效)

Parallel Scavenge 收集器也是一個新生代垃圾收集器,同樣使用複製算法,也是一個多線程的垃 圾收集器,它重點關注的是程序達到一個可控制的吞吐量(Thoughput,CPU 用於運行用戶代碼 的時間/CPU 總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)), 高吞吐量可以最高效率地利用 CPU 時間,儘快地完成程序的運算任務,主要適用於在後台運算而 不需要太多交互的任務。自適應調節策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個 重要區別。

 

老年代:

  4.Serial Old 收集器(單線程標記整理算法 )

Serial Old 是 Serial 垃圾收集器年老代版本,它同樣是個單線程的收集器,使用標記-整理算法, 這個收集器也主要是運行在 Client 默認的 java 虛擬機默認的年老代垃圾收集器。 在 Server 模式下,主要有兩個用途:

    1. 在 JDK1.5 之前版本中與新生代的 Parallel Scavenge 收集器搭配使用。

    2. 作為年老代中使用 CMS 收集器的後備垃圾收集方案。

新生代 Parallel Scavenge 收集器與 ParNew 收集器工作原理類似,都是多線程的收集器,都使 用的是複製算法,在垃圾收集過程中都需要暫停所有的工作線程。

  

  5.Parallel Old 收集器(多線程標記整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多線程的標記-整理算法,在 JDK1.6 才開始提供。 在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保證新生代的吞吐量優先,無法保證整體的吞吐量,Parallel Old 正是為了在年老代同樣提供吞 吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,可以優先考慮新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。

 

  6.Parallel Old 收集器(多線程標記整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多線程的標記-整理算法,在 JDK1.6 才開始提供。 在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保證新生代的吞吐量優先,無法保證整體的吞吐量,Parallel Old 正是為了在年老代同樣提供吞 吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,可以優先考慮新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。

  

  7.CMS 收集器(多線程標記清除算法)

Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾 回收停頓時間,和其他年老代使用標記-整理算法不同,它使用多線程的標記-清除算法。 最短的垃圾收集停頓時間可以為交互比較高的程序提高用戶體驗。

 

 

  8.G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與 CMS 收集器,G1 收 集器兩個最突出的改進是:

    1. 基於標記-整理算法,不產生內存碎片。

    2. 可以非常精確控制停頓時間,在不犧牲吞吐量前提下,實現低停頓垃圾回收。 G1 收集器避免全區域垃圾收集,它把堆內存劃分為大小固定的幾個獨立區域,並且跟蹤這些區域 的垃圾收集進度,同時在後台維護一個優先級列表,每次根據所允許的收集時間,優先回收垃圾 最多的區域。區域劃分和優先級區域回收機制,確保 G1 收集器可以在有限時間獲得最高的垃圾收 集效率。

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

分類
發燒車訊

【Spring】@Transactional 閑聊

菜瓜:上次的AOP理論知識看完收穫挺多的,雖然有一個自定義註解的demo,但還是覺得差點東西

水稻:我也覺得沒有跟一遍源碼還是差點意思,這次結合@Transactional註解深入源碼看一下

菜瓜:事務註解,這個平時用的挺多的

水稻:是嗎?來看看你的基礎咋樣

  1. 要保證一個方法中多個數據庫操作的原子性,要共用一個數據庫連接,但是coding時我們不用显示傳遞連接對象,這是咋弄的?
  2. 如果一個方法裏面只有查詢操作,是否不用開啟事務?
  3. 如何解決非事務方法調用本地事務方法失效的?
  4. 註解常用的傳播屬性,你知道他們的區別嗎

菜瓜:雖然沒看過源碼,我大膽猜測一下

  1. 隱式傳遞連接對象可以將其封裝到線程中,一般一次請求操作都是在一個線程中完成。使用ThreadLocal將連接和線程綁定
  2. 查詢操作也得看業務場景,如果多次查詢相同的數據要避免不可重複讀問題,可開啟只讀事務 (readOnly = true)
  3. 結合AOP的知識,這裏其實要解決調用事務方法的對象不是代理對象的問題。用代理對象調本地事務方法即可(注入自己)
    • /**
       * @author QuCheng on 2020/6/24.
       */
      @Service
      public class ItemServiceImpl implements ItemService {
      
          @Resource
          private IcbcItemMapper itemMapper;
      
          @Resource
          private ItemService itemService;
      
          @Override
          public void changeNameById(Long itemId) {
              // changeItemById(itemId);
              itemService.changeItemById(itemId);
          }
      
          @Transactional(rollbackFor = RuntimeException.class)
          @Override
          public void changeItemById(Long itemId) {
              itemMapper.updateNameById(itemId, "name4");
              int a = 10 / 0;
              itemMapper.updatePriceById(itemId, 100L);
          }
      }
  4. 傳播屬性這個沒了解過啊,數據庫事務裏面么得這個概念

水稻:可以啊,平時的代碼沒白寫

菜瓜:coding這種事情,easy啦!

水稻:這就飄了?來看這個問題

  • 如果我想在A事務方法中調用B事務方法,B方法如果回滾了,不能影響A事務繼續執行,但是A事務如果執行出問題了,B也要回滾,怎麼弄?

菜瓜:。。。這不就是大事務嵌套小事務嘛。。。我不會

水稻:不扯了,來看源碼吧,這個問題等解釋了傳播屬性你就知道了

  • 上回我們說到,@Transactional是AOP的典型應用,bean被實例化之後要創建代理(參考自定義註解),就少不了切面類Advisor對象。那麼它是誰,它在哪,它在干什麼?
  • 回到夢開始的地方,事務功能開啟的註解@EnableTransactionManagement
    • 沒錯,它肯定會有一個Import註解引入TransactionManagementConfigurationSelector類,它又引入了切面類
    • public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
       
         @Override
         protected String[] selectImports(AdviceMode adviceMode) {
            switch (adviceMode) {
               case PROXY:
                  return new String[] {AutoProxyRegistrar.class.getName(),
                        // 看這裏
                        ProxyTransactionManagementConfiguration.class.getName()};
               case ASPECTJ:
                  return new String[] {determineTransactionAspectClass()};
               default:
                  return null;
            }
         }
      。。。
      
      }
      
      
      @Configuration
      public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
      
         @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
         @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
         public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
            BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
            advisor.setTransactionAttributeSource(transactionAttributeSource());
            advisor.setAdvice(transactionInterceptor());
            if (this.enableTx != null) {
               advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
            }
            return advisor;
         }
      
         @Bean
         @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
         public TransactionAttributeSource transactionAttributeSource() {
            return new AnnotationTransactionAttributeSource();
         }
      
         @Bean
         @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
         public TransactionInterceptor transactionInterceptor() {
      // 增強 TransactionInterceptor interceptor
      = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; } } 
    • 切面類對象設置了事務的掃描器,也set了增強類TransactionInterceptor
    • public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
      。。。
      @Override
          @Nullable
          public Object invoke(MethodInvocation invocation) throws Throwable {
              // Work out the target class: may be {@code null}.
              // The TransactionAttributeSource should be passed the target class
              // as well as the method, which may be from an interface.
              Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
      
              // Adapt to TransactionAspectSupport's invokeWithinTransaction...
              return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
          }
      }
      
      public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
        
      。。。  
      @Nullable
      protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {
         。。。
      // ①創建事務,數據庫連接處理也在這 TransactionInfo txInfo
      = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try {
      // 調用目標方法 retVal
      = invocation.proceedWithInvocation(); } catch (Throwable ex) {
      // 異常後事務處理 completeTransactionAfterThrowing(txInfo, ex);
      throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } 。。。 } 

菜瓜:懂,接下來的代碼邏輯就是在增強類TransactionInterceptor的invoke方法里

水稻:對

  • 先看數據庫連接的處理 – 驗證ThreadLocal
  • protected void doBegin(Object transaction, TransactionDefinition definition) {
       。。。
    // 如果連接是新的,就進行綁定
    if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder()); } 。。。 } class TransactionSynchronizationManager public static void bindResource(Object key, Object value) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null"); Map<Object, Object> map = resources.get(); // set ThreadLocal Map if none found if (map == null) { map = new HashMap<>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress a ResourceHolder that was marked as void... if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } if (logger.isTraceEnabled()) { logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]"); } }
  • 回過頭來看AB方法調用的回滾問題,直接給出答案(突然發現這個問題要講清楚篇幅會很大,就。。挺突然的。。挺突然der)
    • 在B方法上設置傳播屬性為NESTED即可,然後在A中catch住B的異常
    • 你肯定會問我不加NESTED去catch不行嗎?不行,非NESTED的方法拋出的異常是無法回滾的。
    • 不信你看
    • @Transactional(rollbackFor = RuntimeException.class)
          @Override
          public void changeNameById(Long itemId) {
              itemMapper.updateNameById(itemId, "A");
              try {
                  itemService.changeItemById(itemId);
      //            itemService.changeItemByIdNested(itemId);
              } catch (Exception e) {
                  System.out.println("我想繼續執行,不影響修改A");
              }
              itemMapper.updatePriceById(itemId, 1L);
          }
      
          @Transactional(rollbackFor = RuntimeException.class)
          @Override
          public void changeItemById(Long itemId) {
              itemMapper.updateNameById(itemId, "B+REQUIRED");
              itemMapper.updatePriceById(itemId, 10L);
              int a = 10 / 0;
          }
      
          @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.NESTED)
          @Override
          public void changeItemByIdNested(Long itemId) {
              itemMapper.updateNameById(itemId, "B+NESTED");
              itemMapper.updatePriceById(itemId, 100L);
              int a = 10 / 0;
          }
      
      
      -- 測試結果
      //①  itemService.changeItemById(itemId);  數據庫所有數據都不會改變
      我想繼續執行,不影響修改A
      org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
      
      // ② itemService.changeItemByIdNested(itemId); 第一個方法的修改會生效
      我想繼續執行,不影響修改A

菜瓜:這就是傳播屬性NESTED?默認的是REQUIRED,還有一個常用的REQUIRES_NEW呢?

水稻:搞清楚這個其實從數據庫連接入手其實就很清楚

  • REQUIRED修飾的方法和A使用同一個連接,A和B是掛一起的,誰回滾都會影響對方,且B方法的異常會被事務管理器標記為必須回滾
  • NESTED修飾的方法和A使用同一個連接,但是用到了數據庫的savePoint特性,它可以回滾到指定的點,如果是有回滾點的操作,拋出的異常可以被處理
  • REQUIRES_NEW修飾的方法和A使用的就不是一個連接了,回不回滾都不會影響對方,當然,要捕捉異常

菜瓜:傳播屬性了解。回滾的問題還得再看看,篇幅很大是很複雜嗎?

水稻:其實不複雜,就是要跟蹤源碼斷點調試。。。截圖搞來搞去,篇幅就很長,你自己去調的話其實很快

菜瓜:那我下去康康

 

總結

  • 這裏提到Transactional註解其實是為了鞏固AOP的,當然提到了一些注意點。譬如本地調用,譬如ThreadLocal的應用,還譬如傳播屬性
  • 傳播屬性其實用的少,但是聊起來就比較多了,可以聊事務的隔離級別,聊ACID的實現,聊MySQL的鎖

 

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

分類
發燒車訊

中國政策放開電動車准入要求 小米或入局電動車

11月26日,國家發改委網站掛出《新建純電動乘用車生企業投資項目和生產準入管理的暫行規定(徵求意見稿)》(以下簡稱《規定》),計劃為純電動車生準入放開口子。   《規定》要求的三條基礎能力中,最核心的是:(申報企業)有3年以上純電動乘用車的研發基礎,具有專業研發團隊和整車正向研發能力,掌握整車控制系統、動力電池系統、整車整合和整車輕量化方面的核心技術以及相應的試驗驗證能力,擁有純電動乘用車自主智慧財產權和已授權的相關發明專利。另外還要求新建企業只能生產純電動轎車和純電動其他乘用車(包括增程式電動乘用車),插電式混合動力不在準入範圍內。   面對新一批電動車開放名單,包括小米、樂視與萬向等企業集團都展現出高度興趣,爭取成為入圍名單。其中小米的動向最受矚目。小米創辦人之一黎萬強早在10月底就悄悄飛往矽谷展開暖身動作,小米董事長雷軍更在拜會特斯拉後就對電動車念念不忘。事實上,小米電動車的諜照早就在網路上曝光,只是細節外界所知不多。不過,這次開放的限制很嚴,小米能否如願以償擠進電動車窄門,備受關注。   (照片來源: )

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

分類
發燒車訊

菲律賓民答那峨島發生規模6.6強震

摘錄自2019年10月16日上報、29日自由時報報導

根據美國地質調查所(USGS)觀測顯示,菲律賓民答那峨島當地時間29日早9時許傳出規模6.6強震。震央位於民答那峨島的達沃市(Davao City)東南方,地震深度僅15.3公里。

截至下午4:00為止,麥格塞塞(Magsaysay)鎮發言人告訴法新社,一名十多歲的男學生逃離校舍時,遭倒下的牆壓死。另有學生在逃出教室時因「踩踏」受傷,所幸保住性命。 科羅納達爾(Koronadal)鎮當局也表示,當地一名66歲男性被倒下的牆壓死。 截至目前至少有50人遭掉落的瓦礫砸傷,包括一所小學的7名師生在逃離校舍時受傷。

本月16日,一場規模6.4的淺層強震才襲擊菲律賓南部,至少1人受傷。根據美國地質調查所,震源深度14公里,震央距離民答那峨島哥倫比歐市(Columbio)約7.7公里。地震發生於當地時間晚間7時37分。其中,基達帕灣市(Kidapawan City)達7級震度,預計仍會有餘震,該市市長宣布17日全面停課。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

分類
發燒車訊

巴黎共享滑板車亂象多 河中打撈竟成新興工作

摘錄自2019年10月26日民視新聞報導

巴黎目前有十多家業者、提供了大概2萬輛電動滑板車,可以自由租借,帶來方便的同時,也帶來了混亂,街頭隨處可見被人亂停、亂丟的電動滑板車,還有不少直接被扔進塞納河。負責清理、回收這些電動滑板車的「巡邏隊」,成了新興工作。

塞納河畔有人用力拉著繩索,他們可不是在釣魚,而是在「釣電動滑板車」,這家電動滑板車公司的員工,3個鐘頭從河裡打撈了足足15輛。電動滑板車公司員工表示:「就像那輛的手把卡在岸邊,我們不可能直接把它拉起來,你必須下到水裡,把手把轉向才能將它弄起來。」

 

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

分類
發燒車訊

寮國湄公河首座發電水壩啟動 大批民眾抗議

摘錄自2019年10月29日民視新聞報導

寮國湄公河下游的第一座水力發電水壩、沙耶武里水壩29日正式啟動,就迎來了大批抗議民眾,他們擔心水壩會對當地環境,造成難以預測的嚴重損害,尤其是加劇糧食跟水源匱乏的危機,大批民眾群聚泰國與鄰近寮國交界處的黎府,跟著師父唸經祈福,抗議沙耶武里水壩正式啟動。

而隨著這座水壩正式運作,湄公河的命運也步入了重要轉折點。當地環保人士表示,「當沙耶武里水壩正式發電時,湄公河將發生變化,我們將無法預測這種變化的嚴重程度。」

雖然相關單位打包票,這項工程會保護到湄公河脆弱的生態系統,但環保組織卻反咬建商根本就是「工程先行,研究後補」。另外水壩的安全也令人憂心。去年7月,寮國東南部阿速坡省就發生水壩大潰堤,50億噸的洪水滾滾流向下游,造成40人死亡,失蹤至少上百人,6600人流離失所。

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

分類
發燒車訊

不畏油價跌 美電動車 12 月創最高單月銷售紀錄

儘管美國汽油零售價格跟隨國際原油同步下挫到每加侖 2 美元的低水準,2014 年美國電動車銷售強勁增加 23%,12 月並創下單月最佳紀錄;顯見電動車已蔚為一股持久的趨勢,不受油價影響。   根據電動車專業網站 InsideEVs 統計,2014 年 12 月美國賣出 12,874 輛電動車,為統計以來最高單月銷售紀錄。至於 2014 年全年,電動車銷售勁揚 23%,達到 119,710 輛。   就個別品牌來看,全美電動車銷售冠軍是日產 Leaf,2014 年總計售出 30,200 輛,遙遙領先亞軍通用汽車雪佛蘭 Volt 的 18,805 輛。起價介於 3.5 萬到 4 萬美元的特斯拉 Model S 排名第 3,全年銷量為 17,300 輛,市占不及 15%。不過,在 12 月 Model S 首度擊敗售價較低的日產 Leaf(約 3 萬美元)。   2014 年美國電動車市場唯一新上市車款是 BMW,分別在 5 月及 8 月推出 i3 與 i8 ,加總的銷量僅 6,647 輛。展望 2015 年,將有數款新型電動車上路,尤其是在休旅車和越野車市場。而特斯拉的 Model X 則預計 2015 年下半年登場,是今年最受期待的電動車。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

分類
發燒車訊

產能尚未滿載 特斯拉要到 2020 年才可能獲利

特斯拉執行長 Elon Musk 在世界汽車業大會 (Automotive News World Congress) 中表示,以目前狀況看來,特斯拉的電動車在 2020 年前都無法開始獲利。   根據華爾街日報報導,特斯拉的 Model 3 電動車在現有會計原則下,至少要等到 2020 年產能滿載時才能正式轉盈。Model 3 是特斯拉功能性較少的電動車,一台可能售價約介於 3 萬到 4 萬美元之間,比起其他 Model S 系列一台動輒超過 10 萬美元來說相對便宜,Model 3 預計在 2017 年推出。特斯拉的股價盤後下跌 7%,來到 189.95 美元。   對於電動車的發展,Musk 也要求其他汽車公司盡速推出自家電動車。特斯拉目前電動車銷售量約為 35,000 台,預計到 2025 年可賣出數百萬台。

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案