RocketMQ系列(六)批量發送與過濾

今天我們再來看看RocketMQ的另外兩個小功能,消息的批量發送和過濾。這兩個小功能提升了我們使用RocketMQ的效率。

批量發送

以前我們發送消息的時候,都是一個一個的發送,這樣效率比較低下。能不能一次發送多個消息呢?當然是可以的,RocketMQ為我們提供了這樣的功能。但是它也有一些使用的條件:

  • 同一批發送的消息的Topic必須相同;
  • 同一批消息的waitStoreMsgOK 必須相同;
  • 批量發送的消息不支持延遲,就是上一節說的延遲消息;
  • 同一批次的消息,大小不能超過1MiB;

好了,只要我們滿足上面的這些限制,就可以使用批量發送了,我們來看看發送端的代碼吧,

@Test
public void producerBatch() throws Exception {

    List<Message> messages = new ArrayList<>();
    for (int i = 0;i<3;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is batchMQ,my NO is "+i+"---"+new Date()).getBytes());
        messages.add(message);
    }
    SendResult sendResult = defaultMQProducer.send(messages);
    System.out.println("sendResult:" + sendResult.getSendStatus().toString());
}
  • 其實批量發送很簡單,我們只是把消息放到一個List當中,然後統一的調用send方法發送就可以了。

再來看看消費端的代碼,

@Bean(initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer pushConsumer()  {
    try {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DefaultMQPushConsumer");
        consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
        consumer.subscribe("cluster-topic", "*");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                System.out.println("msgs.size():"+msgs.size());
                if (msgs != null && msgs.size() > 0) {
                    for (MessageExt msg : msgs) {
                        System.out.println(new String(msg.getBody()));
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        return consumer;
    }catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
  • 消費端的代碼沒有任何的變化,正常的接收消息就可以了,我們只是打印出了msgs.size(),看看一次接收一個消息,還是一次可以批量的接收多個消息。

我們啟動項目,批量發送一下,看看效果吧,

發送端的日誌如下:

sendResult:SEND_OK

發送成功,看來我們批量發送的3個消息都進入到了隊列中,再看看消費端,是一次消費一個,還是一次消費3個,如下:

msgs.size():1
this is batchMQ,my NO is 0---Mon Jun 15 09:31:04 CST 2020
msgs.size():1
this is batchMQ,my NO is 1---Mon Jun 15 09:31:04 CST 2020
msgs.size():1
this is batchMQ,my NO is 2---Mon Jun 15 09:31:04 CST 2020

看樣子是一次只消費了一個消息,那麼能不能一次消費3個消息呢?當然是可以的,不過要進行特殊的設置,

consumer.setConsumeMessageBatchMaxSize(5);

在消費端,我們設置批量消費消息的數量是5,這個值默認是1。我們再看看消費端的日誌,

msgs.size():3
this is batchMQ,my NO is 0---Mon Jun 15 09:35:47 CST 2020
this is batchMQ,my NO is 1---Mon Jun 15 09:35:47 CST 2020
this is batchMQ,my NO is 2---Mon Jun 15 09:35:47 CST 2020

這次一次消費了3個消息,如果消息比較多的話,最大一次能消費5個。這就是RocketMQ的批量發送和批量消費。

消息過濾

其實我們在大多數情況下,使用tag標籤就能夠很好的實現消息過濾。雖然tag標籤咱們並沒有過多的介紹,其實也很好理解,就是一個子Topic的概念,咱們在構建消息message的時候,message.setTags("xxx")。然後在消費的時候,訂閱Topic的時候,也可以指定訂閱的tag,

consumer.subscribe("cluster-topic", "*");

看到那個”*”了嗎?它就是訂閱的tag,”*”代表全部的tag,如果您想訂閱其中的一個或幾個,可以使用這種方式”tagA || tagB || tagC”,這是訂閱了cluster-topic下的3個tag,其他的tag是不會被消費的。

這裏我們所說的消息過濾比tag要高級很多,是可以支持sql的,怎麼樣?高級吧。比如:我們訂閱”a > 5 and b = ‘abc'”的消息,如下圖:

但是,RocketMQ畢竟不是數據庫,它只能支持一些基礎的SQL語句,並不是所有的SQL都支持,

  • 数字型的支持,>, >=, <, <=, BETWEEN, =

  • 字符串支持,=, <>, IN

  • IS NULL或者IS NOT NULL

  • 邏輯判斷,ANDORNOT

字段的類型也只是簡單的幾種,

  • 数字型,支持123,543.123,整型、浮點都可以;
  • 字符串,必須使用單引號”括起來;
  • 空值,NULL;
  • 布爾型,TRUE或者FALSE;

並且對消費者的類型也有一定的限制,只能使用push consumer才可以進行消息過濾。好了,說了這麼多了,我們看看怎麼使用吧,消費端和生產端都要進行相應的改造,先看看生產端吧,

@Test
public void producerBatch() throws Exception {

    List<Message> messages = new ArrayList<>();
    for (int i = 0;i<3;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is batchMQ,my NO is "+i+"---"+new Date()).getBytes());

        int a = i+4;
        message.putUserProperty("a",String.valueOf(a));

        messages.add(message);
    }
    SendResult sendResult = defaultMQProducer.send(messages);
    System.out.println("sendResult:" + sendResult.getSendStatus().toString());
}

我們在之前批量發送的基礎上進行了修改,定義了a的值,等於i+4,這樣循環3次,a的值就是4,5,6。然後調用message.putUserProperty("a",String.valueOf(a))注意,在使用消息過濾的時候,這些附加的條件屬性都是通過putUserProperty方法進行設置。這裏,我們設置了a的值。再看看消費端,

consumer.subscribe("cluster-topic", MessageSelector.bySql("a > 5"));

消費端,整體上沒有變化,只是在訂閱的方法中,使用MessageSelector.bySql("a > 5"),進行了條件的過濾。有的小夥伴可能會有疑問,我既想用sql過濾又想用tag過濾怎麼辦?當然也是可以,我們可以使用MessageSelector.bySql("a > 5").byTag("xx),byTag和bySql不分前後,怎麼樣,很強大吧。我們運行一下程序,看看效果吧。

我們啟動一下服務,報錯了,怎麼回事?錯誤信息如下:

The broker does not support consumer to filter message by SQL92

隊列不支持過濾消息,我們查詢了RocketMQ源碼中的BrokerConfig類,這個類就是對broker的一些設置,其中發現了這兩個屬性,

// whether do filter when retry.
private boolean filterSupportRetry = false;
private boolean enablePropertyFilter = false;
  • filterSupportRetry是在重試的時候,是否支持filter;
  • enablePropertyFilter,這個就是是否支持過濾消息的屬性;

我們把這兩個屬性在broker的配置文件改為true吧,如下:

filterSupportRetry=true
enablePropertyFilter=true

然後,再重新部署一下我們兩主兩從的集群環境。環境部署完以後,我們再重啟應用,沒有報錯。在生產端發送一下消息看看吧,

sendResult:SEND_OK

生產端發送消息沒有問題,說明3個消息都發送成功了。再看看消費端的日誌,

msgs.size():1
this is batchMQ,my NO is 2---Mon Jun 15 10:59:37 CST 2020

只消費了一個消息,並且這個消息中i的值是2,那麼a的值就是2+4=6,它是>5的,滿足SQL的條件,所以被消費掉了。這完全符合我們的預期。

總結

今天的兩個小功能還是比較有意思的,但裡邊也有需要注意的地方,

  • 消息的批量發送,只要我們滿足它的條件,然後使用List發送就可以了;批量消費,默認的消費個數是1,我們可以調整它的值,這樣就可以一次消費多個消息了;
  • 過濾消息中,最大的坑就是隊列的配置里,需要設置enablePropertyFilter=true,否則消費端在啟動的時候報不支持SQL的錯誤;

我們在使用的時候,多加留意就可以了,有問題,評論區留言吧~

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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

Openshift 4.4 靜態 IP 離線安裝系列:初始安裝

上篇文章準備了離線安裝 OCP 所需要的離線資源,包括安裝鏡像、所有樣例 Image StreamOperatorHub 中的所有 RedHat Operators。本文就開始正式安裝 OCP(Openshift Container Platform) 集群,包括 DNS 解析、負載均衡配置、ignition 配置文件生成和集群部署。

OCP 安裝期間需要用到多個文件:安裝配置文件、Kubernetes 部署清單、Ignition 配置文件(包含了 machine types)。安裝配置文件將被轉換為 Kubernetes 部署清單,然後將清單包裝到 Ignition 配置文件中。 安裝程序使用這些 Ignition 配置文件來創建 Openshift 集群。運行安裝程序時,所有原始安裝配置文件都會修改,因此在安裝之前應該先備份文件。

1. 安裝過程

在安裝 OCP 時,我們需要有一台引導主機(Bootstrap)。這個主機可以訪問所有的 OCP 節點。引導主機啟動一個臨時控制平面,它啟動 OCP 集群的其餘部分然後被銷毀。引導主機使用 Ignition 配置文件進行集群安裝引導,該文件描述了如何創建 OCP 集群。安裝程序生成的 Ignition 配置文件包含 24 小時後過期的證書,所以必須在證書過期之前完成集群安裝。

引導集群安裝包括如下步驟:

  • 引導主機啟動並開始託管 Master 節點啟動所需的資源。
  • Master 節點從引導主機遠程獲取資源並完成引導。
  • Master 節點通過引導主機構建 Etcd 集群。
  • 引導主機使用新的 Etcd 集群啟動臨時 Kubernetes 控制平面。
  • 臨時控制平面在 Master 節點啟動生成控制平面。
  • 臨時控制平面關閉並將控制權傳遞給生產控制平面。
  • 引導主機將 OCP 組件注入生成控制平面。
  • 安裝程序關閉引導主機。

引導安裝過程完成以後,OCP 集群部署完畢。然後集群開始下載並配置日常操作所需的其餘組件,包括創建計算節點、通過 Operator 安裝其他服務等。

2. 準備服務器資源

服務器規劃如下:

  • 三個控制平面節點,安裝 Etcd、控制平面組件和 Infras 基礎組件。
  • 兩個計算節點,運行實際負載。
  • 一個引導主機,執行安裝任務,集群部署完成后可刪除。
  • 一個基礎節點,用於準備上節提到的離線資源,同時用來部署 DNS 和負載均衡。
  • 一個鏡像節點,用來部署私有鏡像倉庫 Quay
主機類型 操作系統 Hostname vCPU 內存 存儲 IP FQDN
鏡像節點 RHEL 7.6 registry 4 8GB 150GB 192.168.57.70 registry.openshift4.example.com
基礎節點 RHEL 7.6 bastion 4 16GB 120GB 192.168.57.60 bastion.openshift4.example.com
引導主機 RHCOS bootstrap 4 16GB 120GB 192.168.57.61 bootstrap.openshift4.example.com
控制平面 RHCOS master1 4 16GB 120GB 192.168.57.62 master1.openshift4.example.com
控制平面 RHCOS master2 4 16GB 120GB 192.168.57.63 master2.openshift4.example.com
控制平面 RHCOS master3 4 16GB 120GB 192.168.57.64 master3.openshift4.example.com
計算節點 RHCOS 或 RHEL 7.6 worker1 2 8GB 120GB 192.168.57.65 worker1.openshift4.example.com
計算節點 RHCOS 或 RHEL 7.6 worker2 2 8GB 120GB 192.168.57.66 worke2.openshift4.example.com

3. 防火牆配置

接下來看一下每個節點的端口號分配。

所有節點(計算節點和控制平面)之間需要開放的端口:

協議 端口 作用
ICMP N/A 測試網絡連通性
TCP 9000-9999 節點的服務端口,包括 node exporter 使用的 9100-9101 端口和 Cluster Version Operator 使用的 9099 端口
1025010259 Kubernetes 預留的默認端口
10256 openshift-sdn
UDP 4789 VXLAN 協議或 GENEVE 協議的通信端口
6081 VXLAN 協議或 GENEVE 協議的通信端口
90009999 節點的服務端口,包括 node exporter 使用的 9100-9101 端口
3000032767 Kubernetes NodePort

控制平面需要向其他節點開放的端口:

協議 端口 作用
TCP 23792380 Etcd 服務端口
6443 Kubernetes API

除此之外,還要配置兩個四層負載均衡器,一個用來暴露集群 API,一個用來暴露 Ingress:

端口 作用 內部 外部 描述
6443 引導主機和控制平面使用。在引導主機初始化集群控制平面后,需從負載均衡器中手動刪除引導主機 x x Kubernetes API server
22623 引導主機和控制平面使用。在引導主機初始化集群控制平面后,需從負載均衡器中手動刪除引導主機 x Machine Config server
443 Ingress Controller 或 Router 使用 x x HTTPS 流量
80 Ingress Controller 或 Router 使用 x x HTTP 流量

4. 配置 DNS

按照官方文檔,使用 UPI 基礎架構的 OCP 集群需要以下的 DNS 記錄。在每條記錄中,<cluster_name> 是集群名稱,<base_domain> 是在 install-config.yaml 文件中指定的集群基本域,如下錶所示:

組件 DNS記錄 描述
Kubernetes API api.<cluster_name>.<base_domain>. 此 DNS 記錄必須指向控制平面節點的負載均衡器。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
api-int.<cluster_name>.<base_domain>. 此 DNS 記錄必須指向控制平面節點的負載均衡器。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
Routes *.apps.<cluster_name>.<base_domain>. DNS 通配符記錄,指向負載均衡器。這個負載均衡器的後端是 Ingress router 所在的節點,默認是計算節點。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
etcd etcd-<index>.<cluster_name>.<base_domain>. OCP 要求每個 etcd 實例的 DNS 記錄指向運行實例的控制平面節點。etcd 實例由 值區分,它們以 0 開頭,以 n-1 結束,其中 n 是集群中控制平面節點的數量。集群中的所有節點必須都可以解析此記錄。
_etcd-server-ssl._tcp.<cluster_name>.<base_domain>. 因為 etcd 使用端口 2380 對外服務,因此需要建立對應每台 etcd 節點的 SRV DNS 記錄,優先級 0,權重 10 和端口 2380

DNS 服務的部署方法由很多種,我當然推薦使用 CoreDNS,畢竟雲原生標配。由於這裏需要添加 SRV 記錄,所以需要 CoreDNS 結合 etcd 插件使用。以下所有操作在基礎節點上執行。

首先通過 yum 安裝並啟動 etcd:

$ yum install -y etcd
$ systemctl enable etcd --now

然後下載 CoreDNS 二進制文件:

$ wget https://github.com/coredns/coredns/releases/download/v1.6.9/coredns_1.6.9_linux_amd64.tgz
$ tar zxvf coredns_1.6.9_linux_amd64.tgz
$ mv coredns /usr/local/bin

創建 Systemd Unit 文件:

$ cat > /etc/systemd/system/coredns.service <<EOF
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target

[Service]
PermissionsStartOnly=true
LimitNOFILE=1048576
LimitNPROC=512
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
User=coredns
WorkingDirectory=~
ExecStart=/usr/local/bin/coredns -conf=/etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

新建 coredns 用戶:

$ useradd coredns -s /sbin/nologin

新建 CoreDNS 配置文件:

$ cat > /etc/coredns/Corefile <<EOF
.:53 {  # 監聽 TCP 和 UDP 的 53 端口
    template IN A apps.openshift4.example.com {
    match .*apps\.openshift4\.example\.com # 匹配請求 DNS 名稱的正則表達式
    answer "{{ .Name }} 60 IN A 192.168.57.60" # DNS 應答
    fallthrough
    }
    etcd {   # 配置啟用 etcd 插件,後面可以指定域名,例如 etcd test.com {
        path /skydns # etcd 裏面的路徑 默認為 /skydns,以後所有的 dns 記錄都存儲在該路徑下
        endpoint http://localhost:2379 # etcd 訪問地址,多個空格分開
        fallthrough # 如果區域匹配但不能生成記錄,則將請求傳遞給下一個插件
        # tls CERT KEY CACERT # 可選參數,etcd 認證證書設置
    }
    prometheus  # 監控插件
    cache 160
    loadbalance   # 負載均衡,開啟 DNS 記錄輪詢策略
    forward . 192.168.57.1
    log # 打印日誌
}
EOF

其中 template 插件用來實現泛域名解析。

啟動 CoreDNS 並設置開機自啟:

$ systemctl enable coredns --now

驗證泛域名解析:

$ dig +short apps.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short x.apps.openshift4.example.com @127.0.0.1
192.168.57.60

添加其餘 DNS 記錄:

$ alias etcdctlv3='ETCDCTL_API=3 etcdctl'
$ etcdctlv3 put /skydns/com/example/openshift4/api '{"host":"192.168.57.60","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/api-int '{"host":"192.168.57.60","ttl":60}'

$ etcdctlv3 put /skydns/com/example/openshift4/etcd-0 '{"host":"192.168.57.62","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/etcd-1 '{"host":"192.168.57.63","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/etcd-2 '{"host":"192.168.57.64","ttl":60}'

$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x1 '{"host":"etcd-0.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'
$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x2 '{"host":"etcd-1.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'
$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x3 '{"host":"etcd-2.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'

# 除此之外再添加各節點主機名記錄
$ etcdctlv3 put /skydns/com/example/openshift4/bootstrap '{"host":"192.168.57.61","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master1 '{"host":"192.168.57.62","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master2 '{"host":"192.168.57.63","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master3 '{"host":"192.168.57.64","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/worker1 '{"host":"192.168.57.65","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/worker2 '{"host":"192.168.57.66","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/registry '{"host":"192.168.57.70","ttl":60}'

驗證 DNS 解析:

$ yum install -y bind-utils
$ dig +short api.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short api-int.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short etcd-0.openshift4.example.com @127.0.0.1
192.168.57.62
$ dig +short etcd-1.openshift4.example.com @127.0.0.1
192.168.57.63
$ dig +short etcd-2.openshift4.example.com @127.0.0.1
192.168.57.64

$ dig +short -t SRV _etcd-server-ssl._tcp.openshift4.example.com @127.0.0.1
10 33 2380 etcd-0.openshift4.example.com.
10 33 2380 etcd-1.openshift4.example.com.
10 33 2380 etcd-2.openshift4.example.com.

$ dig +short bootstrap.openshift4.example.com @127.0.0.1
192.168.57.61
$ dig +short master1.openshift4.example.com @127.0.0.1
192.168.57.62
$ dig +short master2.openshift4.example.com @127.0.0.1
192.168.57.63
$ dig +short master3.openshift4.example.com @127.0.0.1
192.168.57.64
$ dig +short worker1.openshift4.example.com @127.0.0.1
192.168.57.65
$ dig +short worker2.openshift4.example.com @127.0.0.1
192.168.57.66

5. 配置負載均衡

負載均衡我選擇使用 Envoy,先準備配置文件:

Bootstrap

# /etc/envoy/envoy.yaml
node:
  id: node0
  cluster: cluster0
dynamic_resources:
  lds_config:
    path: /etc/envoy/lds.yaml
  cds_config:
    path: /etc/envoy/cds.yaml
admin:
  access_log_path: "/dev/stdout"
  address:
    socket_address:
      address: "0.0.0.0"
      port_value: 15001

LDS

# /etc/envoy/lds.yaml
version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_openshift-api-server
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 6443
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: openshift-api-server
        cluster: openshift-api-server
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_machine-config-server
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 22623
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: machine-config-server
        cluster: machine-config-server
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_ingress-http
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 80
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: ingress-http
        cluster: ingress-http
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_ingress-https
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 443
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: ingress-https
        cluster: ingress-https
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout

CDS

# /etc/envoy/cds.yaml
version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: openshift-api-server
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: openshift-api-server
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.61
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.62
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.63
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.64
              port_value: 6443
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: machine-config-server
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: machine-config-server
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.61
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.62
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.63
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.64
              port_value: 22623
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: ingress-http
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: ingress-http
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.65
              port_value: 80
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.66
              port_value: 80
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: ingress-https
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: ingress-https
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.65
              port_value: 443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.66
              port_value: 443

配置看不懂的去看我的电子書:Envoy 中文指南

啟動 Envoy

$ podman run -d --restart=always --name envoy --net host -v /etc/envoy:/etc/envoy envoyproxy/envoy

6. 安裝準備

生成 SSH 私鑰並將其添加到 agent

在安裝過程中,我們會在基礎節點上執行 OCP 安裝調試和災難恢復,因此必須在基礎節點上配置 SSH key,ssh-agent 將會用它來執行安裝程序。

基礎節點上的 core 用戶可以使用該私鑰登錄到 Master 節點。部署集群時,該私鑰會被添加到 core 用戶的 ~/.ssh/authorized_keys 列表中。

密鑰創建步驟如下:

① 創建無密碼驗證的 SSH key:

$ ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/new_rsa

② 啟動 ssh-agent 進程作為後台任務:

$ eval "$(ssh-agent -s)"

③ 將 SSH 私鑰添加到 ssh-agent

$ ssh-add ~/.ssh/new_rsa

後續集群安裝過程中,有一步會提示輸入 SSH public key,屆時使用前面創建的公鑰 new_rsa.pub 就可以了。

獲取安裝程序

如果是在線安裝,還需要在基礎節點上下載安裝程序。但這裡是離線安裝,安裝程序在上篇文章中已經被提取出來了,所以不需要再下載。

創建安裝配置文件

首先創建一個安裝目錄,用來存儲安裝所需要的文件:

$ mkdir /ocpinstall

自定義 install-config.yaml 並將其保存在 /ocpinstall 目錄中。配置文件必須命名為 install-config.yaml。配置文件內容:

apiVersion: v1
baseDomain: example.com
compute:
- hyperthreading: Enabled
  name: worker
  replicas: 0
controlPlane:
  hyperthreading: Enabled
  name: master
  replicas: 3
metadata:
  name: openshift4
networking:
  clusterNetwork:
  - cidr: 10.128.0.0/14
    hostPrefix: 23
  networkType: OpenShiftSDN
  serviceNetwork:
  - 172.30.0.0/16
platform:
  none: {}
fips: false
pullSecret: '{"auths": ...}'
sshKey: 'ssh-rsa ...'
additionalTrustBundle: |
  -----BEGIN CERTIFICATE-----
  省略,注意這裏要前面空兩格
  -----END CERTIFICATE-----
imageContentSources:
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-release
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-v4.0-art-dev
  • baseDomain : 所有 Openshift 內部的 DNS 記錄必須是此基礎的子域,並包含集群名稱。
  • compute : 計算節點配置。這是一個數組,每一個元素必須以連字符 - 開頭。
  • hyperthreading : Enabled 表示啟用同步多線程或超線程。默認啟用同步多線程,可以提高機器內核的性能。如果要禁用,則控制平面和計算節點都要禁用。
  • compute.replicas : 計算節點數量。因為我們要手動創建計算節點,所以這裏要設置為 0。
  • controlPlane.replicas : 控制平面節點數量。控制平面節點數量必須和 etcd 節點數量一致,為了實現高可用,本文設置為 3。
  • metadata.name : 集群名稱。即前面 DNS 記錄中的 <cluster_name>
  • cidr : 定義了分配 Pod IP 的 IP 地址段,不能和物理網絡重疊。
  • hostPrefix : 分配給每個節點的子網前綴長度。例如,如果將 hostPrefix 設置為 23,則為每一個節點分配一個給定 cidr 的 /23 子網,允許 \(510 (2^{32 – 23} – 2)\) 個 Pod IP 地址。
  • serviceNetwork : Service IP 的地址池,只能設置一個。
  • pullSecret : 上篇文章使用的 pull secret,可通過命令 cat /root/pull-secret.json|jq -c 來壓縮成一行。
  • sshKey : 上面創建的公鑰,可通過命令 cat ~/.ssh/new_rsa.pub 查看。
  • additionalTrustBundle : 私有鏡像倉庫 Quay 的信任證書,可在鏡像節點上通過命令 cat /data/quay/config/ssl.cert 查看。
  • imageContentSources : 來自前面 oc adm release mirror 的輸出結果。

備份安裝配置文件,便於以後重複使用:

$ cd /ocpinstall
$ cp install-config.yaml  install-config.yaml.20200604

創建 Kubernetes 部署清單

創建 Kubernetes 部署清單后 install-config.yaml 將被刪除,請務必先備份此文件!

創建 Kubernetes 部署清單文件:

$ openshift-install create manifests --dir=/ocpinstall

修改 manifests/cluster-scheduler-02-config.yml 文件,將 mastersSchedulable 的值設為 flase,以防止 Pod 調度到控制節點。

創建 Ignition 配置文件

創建 Ignition 配置文件后 install-config.yaml 將被刪除,請務必先備份此文件!

$ cp install-config.yaml.20200604 install-config.yaml
$ openshift-install create ignition-configs --dir=/ocpinstall

生成的文件:

├── auth
│   ├── kubeadmin-password
│   └── kubeconfig
├── bootstrap.ign
├── master.ign
├── metadata.json
└── worker.ign

準備一個 HTTP 服務,這裏選擇使用 Nginx:

$ yum install -y nginx

修改 Nginx 的配置文件 /etc/nginx/nginx/.conf,將端口改為 8080(因為負載均衡器已經佔用了 80 端口)。然後啟動 Nginx 服務:

$ systemctl enable nginx --now

Ignition 配置文件拷貝到 HTTP 服務的 ignition 目錄:

$ mkdir /usr/share/nginx/html/ignition
$ cp -r *.ign /usr/share/nginx/html/ignition/

獲取 RHCOS 的 BIOS 文件

下載用於裸機安裝的 BIOS 文件,並上傳到 Nginx 的目錄:

$ mkdir /usr/share/nginx/html/install
$ wget https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/4.4/latest/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz -O /usr/share/nginx/html/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz

獲取 RHCOS 的 ISO 文件

本地下載 RHCOS 的 ISO 文件:https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/4.4/latest/rhcos-4.4.3-x86_64-installer.x86_64.iso,然後上傳到 vSphere。步驟如下:

① 首先登陸 vSphere,然後點擊『存儲』。

② 選擇一個『數據存儲』,然後在右邊的窗口中選擇『上載文件』。

③ 選擇剛剛下載的 ISO 文件,上傳到 ESXI 主機。

7. 安裝集群

Bootstrap

最後開始正式安裝集群,先創建 bootstrap 節點虛擬機,操作系統選擇『Red Hat Enterprise Linux 7 (64-Bit)』,並掛載之前上傳的 ISO,按照之前的表格設置 CPU 、內存和硬盤,打開電源,然後按照下面的步驟操作:

① 在 RHCOS Installer 安裝界面按 Tab 鍵進入引導參數配置選項。

② 在默認選項 coreos.inst = yes 之後添加(由於無法拷貝粘貼,請輸入仔細核對后再回車進行):

ip=192.168.57.61::192.168.57.1:255.255.255.0:bootstrap.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/bootstrap.ign 

其中 ip=... 的含義為 ip=$IPADDRESS::$DEFAULTGW:$NETMASK:$HOSTNAMEFQDN:$IFACE:none

如圖所示:

③ 如果安裝有問題會進入 emergency shell,檢查網絡、域名解析是否正常,如果正常一般是以上參數輸入有誤,reboot 退出 shell 回到第一步重新開始。

安裝成功后從基礎節點通過命令 ssh -i ~/.ssh/new_rsa core@192.168.57.61 登錄 bootstrap 節點,然後驗證:

  • 網絡配置是否符合自己的設定:
    • hostname
    • ip route
    • cat /etc/resolv.conf
  • 驗證是否成功啟動 bootstrap 相應服務:
    • podman ps 查看服務是否以容器方式運行
    • 使用 ss -tulnp 查看 6443 和 22623 端口是否啟用。

這裏簡單介紹一下 bootstrap 節點的啟動流程,它會先通過 podman 跑一些容器,然後在容器裏面啟動臨時控制平面,這個臨時控制平面是通過 CRIO 跑在容器里的,有點繞。。直接看命令:

$ podman ps -a --no-trunc --sort created --format "{{.Command}}"

start --tear-down-early=false --asset-dir=/assets --required-pods=openshift-kube-apiserver/kube-apiserver,openshift-kube-scheduler/openshift-kube-scheduler,openshift-kube-controller-manager/kube-controller-manager,openshift-cluster-version/cluster-version-operator
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
render --dest-dir=/assets/cco-bootstrap --cloud-credential-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:244ab9d0fcf7315eb5c399bd3fa7c2e662cf23f87f625757b13f415d484621c3
bootstrap --etcd-ca=/assets/tls/etcd-ca-bundle.crt --etcd-metric-ca=/assets/tls/etcd-metric-ca-bundle.crt --root-ca=/assets/tls/root-ca.crt --kube-ca=/assets/tls/kube-apiserver-complete-client-ca-bundle.crt --config-file=/assets/manifests/cluster-config.yaml --dest-dir=/assets/mco-bootstrap --pull-secret=/assets/manifests/openshift-config-secret-pull-secret.yaml --etcd-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:aba3c59eb6d088d61b268f83b034230b3396ce67da4f6f6d49201e55efebc6b2 --kube-client-agent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b --machine-config-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:31dfdca3584982ed5a82d3017322b7d65a491ab25080c427f3f07d9ce93c52e2 --machine-config-oscontent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:b397960b7cc14c2e2603111b7385c6e8e4b0f683f9873cd9252a789175e5c4e1 --infra-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:d7862a735f492a18cb127742b5c2252281aa8f3bd92189176dd46ae9620ee68a --keepalived-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:a882a11b55b2fc41b538b59bf5db8e4cfc47c537890e4906fe6bf22f9da75575 --coredns-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:b25b8b2219e8c247c088af93e833c9ac390bc63459955e131d89b77c485d144d --mdns-publisher-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:dea1fcb456eae4aabdf5d2d5c537a968a2dafc3da52fe20e8d99a176fccaabce --haproxy-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:7064737dd9d0a43de7a87a094487ab4d7b9e666675c53cf4806d1c9279bd6c2e --baremetal-runtimecfg-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:715bc48eda04afc06827189883451958d8940ed8ab6dd491f602611fe98a6fba --cloud-config-file=/assets/manifests/cloud-provider-config.yaml --cluster-etcd-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77
render --prefix=cluster-ingress- --output-dir=/assets/ingress-operator-manifests
/usr/bin/cluster-kube-scheduler-operator render --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-scheduler-bootstrap --config-output-file=/assets/kube-scheduler-bootstrap/config
/usr/bin/cluster-kube-controller-manager-operator render --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-controller-manager-bootstrap --config-output-file=/assets/kube-controller-manager-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
/usr/bin/cluster-kube-apiserver-operator render --manifest-etcd-serving-ca=etcd-ca-bundle.crt --manifest-etcd-server-urls=https://localhost:2379 --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --manifest-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:718ca346d5499cccb4de98c1f858c9a9a13bbf429624226f466c3ee2c14ebf40 --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-apiserver-bootstrap --config-output-file=/assets/kube-apiserver-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
/usr/bin/cluster-config-operator render --config-output-file=/assets/config-bootstrap/config --asset-input-dir=/assets/tls --asset-output-dir=/assets/config-bootstrap
/usr/bin/cluster-etcd-operator render --etcd-ca=/assets/tls/etcd-ca-bundle.crt --etcd-metric-ca=/assets/tls/etcd-metric-ca-bundle.crt --manifest-etcd-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:aba3c59eb6d088d61b268f83b034230b3396ce67da4f6f6d49201e55efebc6b2 --etcd-discovery-domain=test.example.com --manifest-cluster-etcd-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77 --manifest-setup-etcd-env-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:31dfdca3584982ed5a82d3017322b7d65a491ab25080c427f3f07d9ce93c52e2 --manifest-kube-client-agent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b --asset-input-dir=/assets/tls --asset-output-dir=/assets/etcd-bootstrap --config-output-file=/assets/etcd-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
render --output-dir=/assets/cvo-bootstrap --release-image=registry.openshift4.example.com/ocp4/openshift4@sha256:4a461dc23a9d323c8bd7a8631bed078a9e5eec690ce073f78b645c83fb4cdf74
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
$ crictl pods

POD ID              CREATED             STATE               NAME                                                                  NAMESPACE                             ATTEMPT
17a978b9e7b1e       3 minutes ago       Ready               bootstrap-kube-apiserver-bootstrap.openshift4.example.com             kube-system                           24
8a0f79f38787a       3 minutes ago       Ready               bootstrap-kube-scheduler-bootstrap.openshift4.example.com             kube-system                           4
1a707da797173       3 minutes ago       Ready               bootstrap-kube-controller-manager-bootstrap.openshift4.example.com    kube-system                           4
0461d2caa2753       3 minutes ago       Ready               cloud-credential-operator-bootstrap.openshift4.example.com            openshift-cloud-credential-operator   4
ab6519286f65a       3 minutes ago       Ready               bootstrap-cluster-version-operator-bootstrap.openshift4.example.com   openshift-cluster-version             2
457a7a46ec486       8 hours ago         Ready               bootstrap-machine-config-operator-bootstrap.openshift4.example.com    default                               0
e4df49b4d36a1       8 hours ago         Ready               etcd-bootstrap-member-bootstrap.openshift4.example.com                openshift-etcd                        0

如果驗證無問題,則可以一邊繼續下面的步驟一邊觀察日誌:journalctl -b -f -u bootkube.service

RHCOS 的默認用戶是 core,如果想獲取 root 權限,可以執行命令 sudo su(不需要輸入密碼)。

Master

控制節點和之前類似,先創建虛擬機,然後修改引導參數,引導參數調整為:

ip=192.168.57.62::192.168.57.1:255.255.255.0:master1.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/master.ign 

控制節點安裝成功後會重啟一次,之後同樣可以從基礎節點通過 SSH 密鑰登錄。

然後重複相同的步驟創建其他兩台控制節點,注意修改引導參數(IP 和主機名)。先不急着創建計算節點,先在基礎節點執行以下命令完成生產控制平面的創建:

$ openshift-install --dir=/ocpinstall wait-for bootstrap-complete --log-level=debug

DEBUG OpenShift Installer 4.4.5
DEBUG Built from commit 15eac3785998a5bc250c9f72101a4a9cb767e494
INFO Waiting up to 20m0s for the Kubernetes API at https://api.openshift4.example.com:6443...
INFO API v1.17.1 up
INFO Waiting up to 40m0s for bootstrapping to complete...
DEBUG Bootstrap status: complete
INFO It is now safe to remove the bootstrap resources

待出現 It is now safe to remove the bootstrap resources 提示之後,從負載均衡器中刪除引導主機,本文使用的是 Envoy,只需從 cds.yaml 中刪除引導主機的 endpoint,然後重新加載就好了。

觀察引導節點的日誌:

$ journalctl -b -f -u bootkube.service

...
Jun 05 00:24:12 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:12.108179       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:21 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:21.595680       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:26 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:26.250214       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:26 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:26.306421       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:29.097072       1 waitforceo.go:64] Cluster etcd operator bootstrapped successfully
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:29.097306       1 waitforceo.go:58] cluster-etcd-operator bootstrap etcd
Jun 05 00:24:29 bootstrap.openshift4.example.com podman[16531]: 2020-06-05 00:24:29.120864426 +0000 UTC m=+17.965364064 container died 77971b6ca31755a89b279fab6f9c04828c4614161c2e678c7cba48348e684517 (image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77, name=recursing_cerf)
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: bootkube.service complete

Worker

計算節點和之前類似,先創建虛擬機,然後修改引導參數,引導參數調整為:

ip=192.168.57.65::192.168.57.1:255.255.255.0:worker1.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/worker.ign 

計算節點安裝成功后也會重啟一次,之後同樣可以從基礎節點通過 SSH 密鑰登錄。

然後重複相同的步驟創建其他計算節點,注意修改引導參數(IP 和主機名)。

登錄集群

可以通過導出集群 kubeconfig 文件以默認系統用戶身份登錄到集群。kubeconfig 文件包含有關 CLI 用於將客戶端連接到正確的集群和 API Server 的集群信息,該文件在 OCP 安裝期間被創建。

$ mkdir ~/.kube
$ cp /ocpinstall/auth/kubeconfig ~/.kube/config
$ oc whoami
system:admin

批准 CSR

將節點添加到集群時,會為添加的每台節點生成兩個待處理證書籤名請求(CSR)。必須確認這些 CSR 已獲得批准,或者在必要時自行批准。

$ oc get node

NAME                             STATUS   ROLES           AGE     VERSION
master1.openshift4.example.com   Ready    master,worker   6h25m   v1.17.1
master2.openshift4.example.com   Ready    master,worker   6h39m   v1.17.1
master3.openshift4.example.com   Ready    master,worker   6h15m   v1.17.1
worker1.openshift4.example.com   NotReady worker          5h8m    v1.17.1
worker2.openshift4.example.com   NotReady worker          5h9m    v1.17.1

輸出列出了創建的所有節點。查看掛起的證書籤名請求(CSR),並確保添加到集群的每台節點都能看到具有 PendingApproved 狀態的客戶端和服務端請求。針對 Pending 狀態的 CSR 批准請求:

$ oc adm certificate approve xxx

或者執行以下命令批准所有 CSR:

$ oc get csr -ojson | jq -r '.items[] | select(.status == {} ) | .metadata.name' | xargs oc adm certificate approve

Operator 自動初始化

控制平面初始化后,需要確認所有的 Operator 都處於可用的狀態,即確認所有 Operator 的 Available 字段值皆為 True

$ oc get clusteroperators

NAME                                       VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE
authentication                             4.4.5     True        False         False      150m
cloud-credential                           4.4.5     True        False         False      7h7m
cluster-autoscaler                         4.4.5     True        False         False      6h12m
console                                    4.4.5     True        False         False      150m
csi-snapshot-controller                    4.4.5     True        False         False      6h13m
dns                                        4.4.5     True        False         False      6h37m
etcd                                       4.4.5     True        False         False      6h19m
image-registry                             4.4.5     True        False         False      6h12m
ingress                                    4.4.5     True        False         False      150m
insights                                   4.4.5     True        False         False      6h13m
kube-apiserver                             4.4.5     True        False         False      6h15m
kube-controller-manager                    4.4.5     True        False         False      6h36m
kube-scheduler                             4.4.5     True        False         False      6h36m
kube-storage-version-migrator              4.4.5     True        False         False      6h36m
machine-api                                4.4.5     True        False         False      6h37m
machine-config                             4.4.5     True        False         False      6h36m
marketplace                                4.4.5     True        False         False      6h12m
monitoring                                 4.4.5     True        False         False      6h6m
network                                    4.4.5     True        False         False      6h39m
node-tuning                                4.4.5     True        False         False      6h38m
openshift-apiserver                        4.4.5     True        False         False      6h14m
openshift-controller-manager               4.4.5     True        False         False      6h12m
openshift-samples                          4.4.5     True        False         False      6h11m
operator-lifecycle-manager                 4.4.5     True        False         False      6h37m
operator-lifecycle-manager-catalog         4.4.5     True        False         False      6h37m
operator-lifecycle-manager-packageserver   4.4.5     True        False         False      6h15m
service-ca                                 4.4.5     True        False         False      6h38m
service-catalog-apiserver                  4.4.5     True        False         False      6h38m
service-catalog-controller-manager         4.4.5     True        False         False      6h39m
storage                                    4.4.5     True        False         False      6h12m

如果 Operator 不正常,需要進行問題診斷和修復。

完成安裝

最後一步,完成集群的安裝,執行以下命令:

$ openshift-install --dir=/ocpinstall wait-for install-complete --log-level=debug

注意最後提示訪問 Web Console 的網址及用戶密碼。如果密碼忘了也沒關係,可以查看文件 /ocpinstall/auth/kubeadmin-password 來獲得密碼。

本地訪問 Web Console,需要添加 hosts:

192.168.57.60 console-openshift-console.apps.openshift4.example.com
192.168.57.60 oauth-openshift.apps.openshift4.example.com

瀏覽器訪問 https://console-openshift-console.apps.openshift4.example.com,輸入上面輸出的用戶名密碼登錄。首次登錄後會提示:

You are logged in as a temporary administrative user. Update the Cluster OAuth configuration to allow others to log in.

我們可以通過 htpasswd 自定義管理員賬號,步驟如下:

htpasswd -c -B -b users.htpasswd admin xxxxx

② 將 users.htpasswd 文件下載到本地。

③ 在 Web Console 頁面打開 Global Configuration

然後找到 OAuth,點擊進入,然後添加 HTPasswd 類型的 Identity Providers,並上傳 users.htpasswd 文件。

④ 退出當前用戶,注意要退出到如下界面:

選擇 htpasswd,然後輸入之前創建的用戶名密碼登錄。

如果退出后出現的就是用戶密碼輸入窗口,實際還是 kube:admin 的校驗,如果未出現如上提示,可以手動輸入 Web Console 地址來自動跳轉。

⑤ 登錄后貌似能看到 Administrator 菜單項,但訪問如 OAuth Details 仍然提示:

oauths.config.openshift.io "cluster" is forbidden: User "admin" cannot get resource "oauths" in API group "config.openshift.io" at the cluster scope

因此需要授予集群管理員權限:

$ oc adm policy add-cluster-role-to-user cluster-admin admin

Web Console 部分截圖:

如果想刪除默認賬號,可以執行以下命令:

$ oc -n kube-system delete secrets kubeadmin

8. 參考資料

  • OpenShift 4.2 vSphere Install with Static IPs
  • OpenShift Container Platform 4.3部署實錄
  • Chapter 1. Installing on bare metal

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

※回頭車貨運收費標準

分析ThreadLocal的弱引用與內存泄漏問題-Java8,利用線性探測法解決hash衝突

目錄

一.介紹

二.問題提出

  2.1內存原理圖

  2.2幾個問題

三.回答問題

  3.1為什麼會出現內存泄漏

  3.2若Entry使用弱引用

  3.3弱引用配合自動回收

四.總結  

 

 

 

一.介紹

  之前使用ThreadLocal的時候,就聽過ThreadLocal怎麼怎麼的可能會出現內存泄漏,具體原因也沒去深究,就是一種不清不楚的狀態。最近在看JDK的源碼,其中就包含ThreadLocal,在對ThreadLocal的使用場景介紹以及源碼的分析后,對於ThreadLocal中可能存在的內存泄漏問題也搞清楚了,所以這裏專門寫一篇博客分析一下。

  在分析內存泄漏之前,先了解2個概念,就是內存泄漏和內存溢出:

  內存溢出(memory overflow):是指不能申請到足夠的內存進行使用,就會發生內存溢出,比如出現的OOM(Out Of Memory)

  內存泄漏(memory lack):內存泄露是指在程序中已經動態分配的堆內存由於某種原因未釋放或者無法釋放(已經沒有用處了,但是沒有釋放),造成系統內存的浪費,這種現象叫“內存泄露”。

  當內存泄露到達一定規模后,造成系統能申請的內存較少,甚至無法申請內存,最終導致內存溢出,所以內存泄露是導致內存溢出的一個原因。

 

二.問題提出

2.1內存原理圖

  下圖是程序運行中的內存分布圖,簡要介紹一下這種圖:當前線程有一個threadLocals屬性(ThreadLocalMap屬性),該map的底層是數組,每個數組元素時Entry類型,Entry類型的key是ThreadLocal類型(也就是創建的ThreadLocal對象),而value是則是ThreadLocal.set()方法設置的value。

  

  需要注意的是ThreadLocalMap的Entry,繼承自弱引用,定義如下,關於Java的引用介紹,可以參考:Java-強引用、軟引用、弱引用、虛引用

/**
 * ThreadLocalMap中存放的元素類型,繼承了弱引用類
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    // key對應的value,注意key是ThreadLocal類型
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

 

2.2問題提出

  在看了上面ThreadLocal和ThreadLocalMap相關的內存分佈以及關聯后,提出這樣幾個問題:

  1.ThreadLocal為什麼會出現內存溢出?

  2.Entry的key為什麼要用弱引用?

  3.使用弱引用是否就能解決內存溢出?

  為了回答上面這3個問題,我寫了一段代碼,後面根據這段代碼進行分析:

public void step1() {
    // some action
    
    step2();
    step3();
    
    // other action
}

// 在stepX中都會創建threadLocal對象
public void step2() {
    ThreadLocal<String> tl = new ThreadLocal<>();
    tl.set("this is value");
}
public void step3() {
    ThreadLocal<Integer> tl = new ThreadLocal<>();
    tl.set(99);
}

  在step1中會調用step2和step3,step2和step3都會創建ThreadLocal對象,當step2和step3執行完畢后,其中的棧內存中ThreadLocal引用就會被清除。

 

三.回答問題

 

  

  現在針對這個圖,一步一步的分析問題,中途會得出一些臨時的結論,但是最終的結論才是正確的

 

3.1為什麼會出現內存泄露

  現在有2點假設,本小節的分析都是基於這兩個假設之上的:

  1.Entry的key使用強引用,key對ThreadLocal對象使用強引用,也就是上面圖中連線5是強引用(key強引用ThreadLocal對象);

  2.ThreadLocalMap中不會對過期的Entry進行清理。

  上面代碼中,如果ThreadLocalMap的key使用強引用,那麼即使棧內存的ThreadLocal引用被清除,但是堆中的ThreadLocal對象卻並不會被清除,這是因為ThreadLocalMap中Entry的key對ThreadLocal對象是強引用。

  如果當前線程不結束,那麼堆中的ThreadLocal對象將會一直存在,對應的內存就不會被回收,與之關聯的Entry也不會被回收(Entry對應的value也不會被回收),當這種情況出現數量比較多的時候,未釋放的內存就會上升,就可能出現內存泄漏的問題。

  上面的結論是暫時的,有前提假設!!!最終結論還需要看後面分析。

 

3.2若Entry使用弱引用

  

  仍舊有1個假設,就是ThreadLocalMap中不會對過期的Entry進行清理,陳舊的Entry是指Entry的key為null。

  按照源碼,Entry繼承弱引用,其Key對ThreadLocal是弱引用,也就是上圖中連線5是弱引用,連線6仍為強引用。

  同樣以上面代碼為例,step2和step3創建了ThreadLocal對象,step2和step3執行完后,棧中的ThreadLocal引用被清除了;由於堆內存中ThreadLocalMap的Entry key弱引用ThreadLocal對象,根據垃圾收集器對弱引用對象的處理:

當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

  此時堆中ThreadLocal對象會被gc回收(因為現在沒有對ThreadLocal的強引用,只有一個弱引用ThreadLocal對象),Entry的key為null,但是value不為null,且value也是強引用(連線6),所以Entry仍舊不能回收,只能釋放ThreadLocal的內存,仍舊可能導致內存泄漏

  在沒有自動清理陳舊Entry的前提下,即使Entry使用弱引用,仍可能出現內存泄漏。

 

3.3弱引用配合自動回收

  通過3.2的分析,其實只要陳舊的Entry能自動被回收,就能解決內存泄漏的問題,其實JDK就是這麼做的。

  如果看過源碼,就知道,ThreadLocalMap底層使用數組來保存元素,使用“線性探測法”來解決hash衝突,關於線性探測法的介紹可以查看:利用線性探測法解決hash衝突

  在每次調用ThreadLocal類的get、set、remove這些方法的時候,內部其實都是對ThreadLocalMap進行操作,對應ThreadLocalMap的get、set、remove操作。

  重點來了!重點來了!重點來了!

  ThreadLocalMap的每次get、set、remove,都會清理過期的Entry,下面以get操作解釋,其他操作也是一個意思,大致如下:

  1.ThreadLocalMap底層用數組保存元素,當get一個Entry時,根據key的hash值(非hashCode)計算出該Entry應該出在什麼位置;

  2.計算出的位置可能會有衝突,比如預期位置是position=5,但是position=5的位置已經有其他Entry了;

  3.出現衝突后,會使用線性探測法,找position=6位置上的Entry是否匹配(匹配是指hash相同),如果匹配,則返回position=6的Entry。

  4.在這個過程中,如果position=5位置上的Entry已經是陳舊的Entry(Entry的key為null),此時position=5的key就應該被清理;

  5.光清理position=5的Entry還不夠,為了保證線性探測法的規則,需要判斷數組中的其他元素是否需要調整位置(如果需要,則調整位置),在這個過程中,也會進行清理陳舊Entry的操作。

  上面這5個步驟就保證了每次get都會清理數組中(map)的陳舊Entry,清理一個陳舊的Entry,就是下面這三行代碼:

Entry.value = null; // 將Entry的value設為null
table[index] = null;// 將數組中該Entry的位置設置null
size--;	// map的size減一

  對於ThreadLocal的set、remove也類似這個原理。

  有了自動回收陳舊Entry的操作,需要注意的是,在這個時候,key使用弱引用就是至關重要的一點!!!

  因為key使用弱引用后,當弱引用的ThreadLocal對象被會回收后,該key的引用為null,則該Entry在下一次get、set、remove的時候就才會被清理,從未避免內存泄漏的問題。

  

四.總結

  在上面的分析中,看到ThreadLocal基本不會出現內存泄漏的問題了,因為ThreadLocalMap中會在get、set、remove的時候清理陳舊的Entry,與Entry的key使用弱引用密不可分。

  當然我們也可以在代碼中手動調用ThreadLocal的remove方法進行清除map中key為該threadLocal對象的Entry,同時清理過期的Entry。

  

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

養生吧,程序員!

由於寫作的原因,我認識了蠻多天南海北的朋友。空下來的時候,我就會主動找他們聊一聊,一呢,從他們那吸取成功的經驗,開拓一下眼界;二呢,讓自己的社交圈擴大一些,要知道,多一個朋友就多一條路;三呢,看看朋友們有沒有自己可以幫得上的忙;四呢,保持年輕的心態,不至於落伍。

老讀者都知道了,我在三線城市洛陽,雖然幸福感很足,但整體社交的圈子是有限的,只通過互聯網,很難得到一些不便於公開的信息,和朋友們聊一聊,不僅能夠加深感情,還能夠讓自己和一線城市保持同步。

昨天,我和一個非常優秀的年輕人聊天,他給我說,身體上出了一些狀況。他比我小七八歲,按理說身體素質倍棒才對,但大城市的生活壓力,讓他只能快馬加鞭。這就導致身體一直處於高負荷的運轉狀態,很容易出問題。

提起程序員,總免不了和一些段子關聯上,比如說“要變強,必變禿”,再比如說:

零基礎學編程→某編程語言入門→某編程語言進階→技術專家→頸椎病

以前,我對這些段子總是一笑而過,但回想起朋友說的那些話,總免不了一陣心酸。就讓我來寫一篇“程序員養生指南”吧,希望能給讀者朋友們一些警醒或者說參考吧。

心學鼻祖王陽明曾提出過一個觀點,叫做“知行合一”,我的理解就是,當你要做一件事情的話,你必須得對它有一定的認知,否則的話,你根本就不會去做這件事。

舉個例子來說,如果你不知道學習很重要,哪怕你天資聰慧,你也不可能去學習,你會把心思花在其他地方。

再比如說,如果你認知不到英語對於程序員的重要性,你永遠也擺脫不了對翻譯軟件的依賴症。

對吧?如果在你的認知里,意識不到健康的重要性,那你對待身體的態度可就會大有不同。

大二結束后,我要去蘇州參加培訓,臨走之前,同學們給我送行,結果我一下子喝大了。畢竟一起玩了兩年,又要去人生地不熟的地方,傷感在所難免,於是我把自己喝到了醫院。

喝完酒感覺沒啥事,結果睡覺前嘔吐的厲害,感覺整個人就要死翹翹了。我的一個好兄弟看我情況不妙,背着我就跑,大晚上也沒啥車,幸好醫院就在我們學校旁邊,算是保住了一條小命。

從那以後,我就特別討厭喝酒,儘管有的時候不得不喝一些,但都會盡量控制到不上頭的狀態。

我身邊有不少人,總是把鍛煉掛在嘴上,聊起來感覺特別注重身體。但過一段時間再問他,“夥計,你健身卡辦了沒?”“最近忙,過两天就去辦,感覺身體有點撐不住了。”

生存的壓力確實大,但如果身體跨了,生存的意義又是什麼呢?

改變一個人的認知,最壞的方式就是徹底摧毀他之前的認知。極端的例子就是大病初愈,那時候,人會迫切地想要鍛煉身體。

最好的方式,當然是聽得進去勸,把別人糟糕的經歷當做是自己前進的動力。認識到健康的重要性,然後一點一滴地去改善生活的習慣,最終達到知行合一的境界。

我曾看過一本書,裏面提到一個觀點,養成習慣的最好方式就是,連續不間斷地做一件事,周期為 21 天。這件事最好是一件小事,比如說,起床后疊被子這件事,它一點也不難,很容易就能做到。

但能不能堅持 21 天不間斷就需要一點點耐力了,21 天不算長,但絕對算得上是富有意義的考驗了,它對你培養飲食、作息、運動方面的良好行為習慣是一道開胃菜。

接下來說說飲食吧,我覺得一定不要暴飲暴食,或者餓着肚子。每天的三餐一定要及時,哪怕你早上起晚了,一定也要記得吃點東西,可以準備一些麵包或者牛奶,不要餓着肚子挺到中午,那樣的話,對身體不好,另外,餓的時候,工作狀態也會大打折扣。

現在天氣熱了,很多讀者的食慾都不會特彆強烈,尤其是到了中午的時候,可以選擇一些清淡的,比如說,我作為北方人,最喜歡的就是甜面片,伴着一點鹹菜吃,再喝點麵湯,感覺特別舒服。

到了晚上,不要因為餓就要找場子,覺得必須得吃回來,然後睡覺的時候就感覺特別撐,很難入睡,反而影響了睡眠質量。

我一般是十點半入睡,然後一覺睡到早上五六點。這個作息還是非常規律的,中午如果困的話,就再補個覺。最好不要超過半個小時,否則越睡越困,如果實在睡不着,就閉目養神會,就算是休息休息眼睛吧,畢竟做程序員的,每天對着電腦屏幕和手機屏幕,對眼睛的傷害還是蠻大的。

有不少讀者都睡得特別晚,一方面是公司上下班時間上的問題,一方面就是好不容易有點自己的自由時間,舍不得睡覺,於是熬一會,打一把遊戲,就到了凌晨一兩點。

我是怎麼知道的?通過留言的時間就知道了。讓我很心疼的一個事實是,有些留言的時間竟然是凌晨三四點。真的,看到這個時間點的留言,我都會勸讀者能早點睡就早點睡,不要熬夜。

二十五六歲之前,熬個夜,很快就恢復了,但再往後,真的是非常難,我以前熬夜看英超,結果第二天整個人都處於懵逼的狀態,效率特別差,要兩三天才能恢復正常,索性後來就戒了。

最後再說說鍛煉吧,年前我辦了一張健身卡,一周能堅持去兩到三次,但後來就中斷了。最近兩個月,我開始了騎自行車,感覺效果還不錯,瘦了七八斤的樣子,有一種返老還童的感覺,真的。

五六點起來,騎一個小時左右,回到家不耽誤上班。我在朋友圈曬過兩張圖,老讀者應該看到了。清晨,空氣特別好,路上人也不多,車也不多,氣溫也剛剛好,耳邊再聽着周杰倫或者王力宏,感覺真的非常愜意。

作為程序員,絕大部分時間都要坐在椅子上,再加上有些公司加班特別厲害,就導致久坐的時間特別長,於是頸椎病、肩周炎等等職業病隨着而來。

除了及時地走動,還是要盡量騰出一些時間去鍛煉一下,哪怕走走路,跑幾步,對身體的調整都卓有成效,業績是公司的,身體是自己的。

最後,就是堅持了。生活需要用心去對待,如果我們用心去愛她,她就會加倍地愛我們——養生吧,程序員!

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

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

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

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

【SEED Labs】Public-Key Infrastructure (PKI) Lab

Lab Overview

公鑰加密是當今安全通信的基礎,但當通信的一方向另一方發送其公鑰時,它會受到中間人的攻擊。根本的問題是,沒有簡單的方法來驗證公鑰的所有權,即,給定公鑰及其聲明的所有者信息,如何確保該公鑰確實屬於聲明的所有者?公鑰基礎設施(PKI)是解決這一問題的一種切實可行的方法。

通過這個實驗,我們應該能夠更好地了解PKI是如何工作的,PKI是如何用來保護網絡,以及PKI如何打敗中間人攻擊。此外,我們將能夠了解在公鑰基礎設施中信任的根源,以及如果這種根源信任被破壞會出現什麼問題。本實驗所涵蓋的主題如下:

• Public-key encryption

• Public-Key Infrastructure (PKI)

• Certificate Authority (CA) and root CA

• X.509 certificate and self-signed certificate

• Apache, HTTP, and HTTPS

• Man-in-the-middle attacks

Lab Environment

這個實驗在我kali VM和Ubuntu 16.04 VM上進行了測試.在這個實驗中,我們將使用openssl命令和庫。

Lab Tasks

Task1: Becoming a Certificate Authority(CA) 

證書頒發機構(CA)是發布数字證書的受信任實體。数字證書通過證書的命名主體來驗證公鑰的所有權。一些商業性的CAs被視為根類CAs;在撰寫本文時,VeriSign是最大的CA。想要獲得商業核證機關發出的数字證書的用戶需要向這些核證機關支付費用。

在這個實驗室,我們需要創建数字證書,但是我們不會支付任何商業CA,我們自己會成為一個根CA,然後用這個CA為其他人(比如服務器)頒發證書。在這個任務中,我們將使自己成為一個根CA,併為這個CA生成一個證書。RootCA的認證通常預裝在大多數操作系統、web瀏覽器和其他依賴於PKI的軟件中。根CA的證書是無條件信任的。

The Configuration File openssl.conf  

為了使用OpenSSL創建證書,必須有一個配置文件。配置文件通常有一個extension.cnf。它由三個OpenSSL命令使用:ca、req和x509。可以使用谷歌搜索找到openssl.conf的手冊頁。還可以從/usr/lib/ssl/openssl.cnf獲得配置文件的副本。將此文件複製到當前目錄后,需要按照配置文件中指定的方式創建子目錄(查看[CA default]部分): 

 

 

 對於index.txt文件,只需創建一個空文件。對於serial文件,在文件中放入一個字符串格式的数字(例如1000)。設置好配置文件openssl.cnf之後,就可以創建和頒發證書了。

CertificateAuthority(CA).

如前所述,我們需要為我們的CA生成一個自簽名證書,這意味着這個CA是完全可信的,它的證書將作為根證書。運行以下命令為CA生成自簽名證書:

系統將提示您輸入信息和密碼。不要丟失此密碼,因為每次要使用此CA為其他人簽名證書時,都必須鍵入口令。您還將被要求填寫一些信息,如國家名稱、常用名稱等。該命令的輸出存儲在兩個文件中:ca.key和ca.crt。文件CA .key包含CA的私鑰,而CA .crt包含公鑰證書。

 

 

 Task2: Creating a Certificate for SEEDPKILab2018.com 

現在,我們成為一個根CA,我們準備為我們的客戶簽署数字證書。我們的第一個客戶是一家名為SEEDPKILab2018.com的公司。該公司要從CA獲得数字證書,需要經過三個步驟。

Step 1: Generate public/private key pair  。公司需要首先創建自己的公鑰/私鑰對。可以運行以下命令來生成RSA密鑰對(私有和公共密鑰)。您還需要提供一個密碼來加密私鑰(使用AES-128加密算法,在命令選項中指定)。密鑰將存儲在文件服務器中。

 

server.key是一個編碼的文本文件(也是加密的),因此無法看到實際內容,例如模數、私有指數等。要查看這些,可以運行以下命令:

 

 

 

 

 Step 2: Generate a Certificate Signing Request (CSR). 一旦公司擁有了密鑰文件,它應該生成一個證書籤名請求(CSR),它基本上包括公司的公鑰。CSR將被發送到CA, CA將為密鑰生成證書(通常在確保CSR中的身份信息與服務器的真實身份匹配之後)。使用SEEDPKILab2018.com作為證書請求的通用名稱。

需要注意的是,上面的命令與我們為CA創建自簽名證書時使用的命令非常相似,唯一的區別是使用了-x509選項。沒有它,命令生成一個請求;使用它,該命令生成一個自簽名證書。

Step 3: Generating Certificates . CSR文件需要有CA的簽名才能形成證書。在現實世界中,CSR文件通常被發送到受信任的CA進行簽名。在這個實驗中,我們將使用我們自己的可信CA來生成證書。以下命令使用CA的CA .crt和CA .key將證書籤名請求(server.csr)轉換為X509證書(server.crt):

如果OpenSSL拒絕生成證書,您請求中的名稱很可能與ca的名稱不匹配。匹配規則在配置文件中指定(查看[policy match]部分)。您可以更改請求的名稱以符合策略,也可以更改策略。配置文件還包括另一個限制較少的策略(稱為任何策略)。您可以通過更改以下行來選擇該策略:

“policy = policy_match” change to “policy = policy_anything”.

Task3: Deploying Certificate in an HTTPS Web Server

在這個實驗中,我們將探索網站如何使用公鑰證書來保護web瀏覽。我們將使用openssl的內置web服務器建立一個HTTPS網站。

Step1: Configuring DNS .我們選擇SEEDPKILab2018.com作為我們的網站名稱。為了讓我們的計算機識別這個名稱,讓我們將下面的條目添加到/etc/hosts;這個條目基本上將主機名SEEDPKILab2018.com映射到本地主機(即127.0.0.1):

 

Step 2: Configuring the web server.  讓我們使用在上一個任務中生成的證書啟動一個簡單的web服務器。OpenSSL允許我們使用s_server命令啟動一個簡單的web服務器:

 

默認情況下,服務器將監聽端口4433。您可以使用-accept選項進行更改。現在,您可以使用以下URL訪問服務器:https://SEEDPKILab2018.com:4433/。最有可能的是,您將從瀏覽器中得到一條錯誤消息。在Firefox中,您將看到如下消息:“seedpkilab2018.com:4433使用了無效的安全證書。該證書不受信任,因為發布者證書未知”.

 

Step3: Getting the browser to accept our CA certificate. 如果我們的證書是由VeriSign分配的,就不會出現這樣的錯誤消息,因為VeriSign的證書很可能已經預加載到Firefox的證書存儲庫中了。很遺憾,SEEDPKILab2018.com的證書是由我們自己的CA,並且Firefox無法識別此CA。有兩種方法可以讓Firefox接受CA的自簽名證書。

  • 我們可以請求Mozilla將我們的CA證書包含在Firefox軟件中,這樣每個使用Firefox的人都可以識別我們的CA。不幸的是,我們自己的CA沒有足夠大的市場讓Mozilla包含我們的證書,所以我們不會追求這個方向。
  • 加載CA .crt到Firefox中:我們可以手動添加我們的CA證書到Firefox瀏覽器通過點擊下面的菜單序列:

             Edit -> Preference -> Privacy & Security -> View Certificates

您將看到一個已經被Firefox接受的證書列表。從這裏,我們可以“導入”我們自己的證書。請導入CA .crt,並選擇以下選項:“信任此CA識別網站”。您將看到我們的CA證書現在位於Firefox接受的證書列表中。

 Step4. Testing our HTTPS website . 現在,將瀏覽器指向https://SEEDPKILab2018.com: 4433,可以正常訪問

Task4: Deploying Certificate in an Apache-Based HTTPS Website

使用openssl的s_server命令設置HTTPS服務器主要用於調試和演示目的。在這個實驗中,我們基於Apache建立了一個真正的HTTPS web服務器。Apache服務器(已經安裝在我們的VM中)支持HTTPS協議。要創建一個HTTPS網站,我們只需要配置Apache服務器,這樣它就知道從哪裡獲得私鑰和證書。

一個Apache服務器可以同時託管多個網站。它需要知道網站文件存儲的目錄。這是通過它的VirtualHost文件完成的,該文件位於/etc/apache2/sites-available目錄中。要添加一個HTTP網站,我們需要在文件000-default.conf中添加一個虛擬主機條目。而要添加一個HTTPS網站,我們則需要在同一個文件夾的default-ssl.conf文件中添加一個VirtualHost條目。

 

 

ServerName條目指定網站的名稱,而DocumentRoot條目指定網站文件存儲的位置。在設置中,我們需要告訴Apache服務器證書和私鑰存儲在哪裡。

修改了default-ssl.conf文件之後,我們需要運行一系列命令來啟用SSL。Apache將要求我們輸入用於加密私鑰的密碼。一旦一切都設置好了,我們就可以瀏覽網站了,瀏覽器和服務器之間的所有流量都被加密了。

 

 Task5: Launching a Man-In-The-Middle Attack 

 

 在這個任務中,我們將展示PKI如何擊敗中間人(MITM)攻擊。下圖描述了MITM攻擊的工作原理。假設Alice想通過HTTPS協議訪問example.com。她需要從example.com服務器獲取公鑰;Alice將生成一個密鑰,並使用服務器的公鑰對該密鑰進行加密,然後將其發送到服務器。如果攻擊者可以攔截Alice和服務器之間的通信,則攻擊者可以用自己的公鑰替換服務器的公鑰。因此,Alice的秘密實際上是用攻擊者的公鑰加密的,因此攻擊者將能夠讀取秘密。攻擊者可以使用服務器的公鑰將密鑰轉發給服務器。秘密用於加密Alice和服務器之間的通信,因此攻擊者可以解密加密的通信。

 

 

Step 1: Setting up the malicious website. 在Task 4中,我們已經為SEEDPKILab2018.com建立了一個HTTPS網站。我們將使用相同的Apache服務器模擬example.com 。為此,我們將按照任務4中的指令向Apache的SSL配置文件添加一個VirtualHost條目:ServerName應該是example.com,但配置的其餘部分可以與任務4中使用的相同。我們的目標如下:當用戶試圖訪問example.com時,我們將讓用戶登陸我們的服務器,其中為example.com託管一個假網站。如果這是一個社交網站,虛假網站可以显示一個登錄頁面類似於目標網站。如果用戶不能分辨出兩者的區別,他們可能會在假網頁中輸入他們的帳戶憑據,本質上就是向攻擊者披露憑據。

Step2: Becoming the man in the middle 。有幾種方法可以讓用戶的HTTPS請求到達我們的web服務器。一種方法是攻擊路由,將用戶的HTTPS請求路由到我們的web服務器。另一種方法是攻擊DNS,當受害者的機器試圖找到目標網絡服務器的IP地址時,它會得到我們的網絡服務器的IP地址。在此任務中,我們使用“攻擊”DNS。我們只需修改受害者機器的/etc/hosts文件,以模擬DNS緩存設置攻擊的結果,而不是啟動實際的DNS緩存中毒攻擊。

Step3: Browse the target website  。一切都設置好了,現在訪問目標真實的網站。

可以看到,我們訪問到的是kali攻擊機的默認目錄。

訪問https服務的時候,由於kali攻擊機的證書不被信任,所以會有安全警告

 

 

 

Task6: Launching a Man-In-The-Middle Attack with a Compromised CA 

設計一個實驗來證明攻擊者可以在任何HTTPS網站上成功發起MITM攻擊。可以使用在Task 5中創建的相同設置,但是這一次,需要演示MITM攻擊是成功的。即,當受害者試圖訪問一個網站而進入MITM攻擊者的假網站時,瀏覽器不會引起任何懷疑。

 

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

CPU性能分析工具原理

轉載請保留以下聲明

  作者: 趙宗晟

  出處: https://www.cnblogs.com/zhao-zongsheng/p/13067733.html

很多軟件都要做性能分析和性能優化。很多語言都會有他的性能分析工具,例如如果優化C++的性能,我們可以用Visual Studio自帶的性能探測器,或者使用Intel VTune Profiler。了解性能分析工具的原理有助於了解工具給出的數據與結果,也能幫助我們在遇到異常結果時排查哪裡出了問題。這篇博客簡單總結一下常見的性能分析工具原理。

性能分析器原理分類

性能分析工具大部分都可以分為下面幾類

  1. 基於採樣(Sampling)
  2. 基於插裝(Instrumentation)
  3. 基於事件(Event-based)

1. 基於採樣

基於採樣的分析器會每隔一個固定時間間隔暫停所有線程的執行,然後分析有哪些線程正在執行,那些線程處於就緒狀態。對於這類線程,我們記錄每個線程的調用堆棧,以及其他需要的信息。我們稱這個行為為一次採樣,記錄下來的每個堆棧為一個樣本。然後在結束性能分析的時候我們統計記錄下載的堆棧,看每個堆棧被記錄了多少次,或者每個函數被記錄了多少次。統計學告訴我們,那些執行時間比較長的函數、堆棧,被記錄的次數會更多。如果堆棧A被記錄了200次,堆棧B被記錄了100次,那麼堆棧B的執行時間是堆棧A的2倍。我們可以計算某個堆棧樣本的數量佔總樣本數的比例,這個比例就是堆棧執行時間的佔比。用Visual Studio的性能探測器我們看到的百分比和数字就是值樣本的佔比(也就是時間佔比)和樣本次數。

很多性能分析工具都是基於採樣的方式。運行性能分析器是會影響被測程序的性能的,而基於採樣的有點是對性能影響比較小,不需要重新編譯程序,非常方便。

2.基於插裝

插裝是指通過修改程序,往裡面加入性能分析相關代碼,來收集、監控相關性能指標。例如我們可以在一個函數的開頭寫下計數代碼,我們就可以知道在運行中這個函數被執行了多少次。一般來說基於插裝的性能分析更為準確,但是對性能影響比較大。因為需要修改代碼,所以也不是很方便。另外,基於插裝的分析也可能會引起海森堡bug(heisenbug)。海森堡bug是指當你再運行程序的時候能遇到這個bug,但是試圖定位這個bug時卻遇不到這個bug。這個bug往往是因為在定位bug時修改了軟件的運行環境/流程,導致軟件執行和生產時不一樣,於是就遇不到這個bug了。這個命名的來源很有意思,海森堡是量子力學的著名科學家,他提出了不確定性原理,以及觀察者理論。這個理論認為,觀察會影響例子的狀態,導致觀察粒子和不觀察粒子會導致不同的結果,這個和海森堡bug的情形非常相似。關於觀察者理論,有興趣的人可以再了解一下。

回到正題,基於插裝也可以再進行劃分:

  • 人手修改源碼:這個是我們非常常用的性能分析方法。我們做性能分析有時候就會直接修改源碼,計算某一段代碼的執行時長,或者計算函數調用次數,分析哪段代碼耗時。
  • 工具自動修改源碼
  • 工具自動修改彙編/中間碼
  • 運行時注入
  • ……

3.基於事件

在軟件執行過程中,可能會拋出某些事件。我們通過統計事件出現的次數,事件出現的時機,可以得到軟件的某些性能指標。事件又可以分為軟件事件和硬件事件。軟件事件比如Java可以在class-load的時候拋出事件。硬件事件則是使用專門的性能分析硬件。現在很多CPU裏面都有用於分析性能的性能監控單元(PMU),PMU本身是一個寄存器,在某個事件發生時寄存器裏面的值會+1。例如我們可以設置為當運行中發生memory cache miss的時候PMU寄存器+1,這樣我們就知道一段時間內發生了多少次memory cache miss。性能分析器使用PMU時,會給PMU設置需要統計的事件類型,以及Sample After Value (SAV)。SAV是指寄存器達到什麼值之後出發硬件中斷。例如,我們可以給PMU設置SAV為2000,統計事件為memory cache miss。那麼當cache miss發生2000次時,發生硬件中斷。這個時候性能分析器就可以收集CPU的IP,調用堆棧,進程ID等等信息,分析器結束時進行統計分析。

基於硬件事件的優點是,對被測程序的影響非常小,比基於採樣還小,可以使用更短的時間間隔收集信息,而且可以收集CPU的非常重要的性能指標,例如cache miss,分支預測錯誤,等等。

但是基於硬件事件的分析器也有它的一些問題,導致數據上的誤差:

  • SAV一般會設置成很大的數值:
    像是Intel VTune Profiler一般會把SAV設置成10,000到2,000,000,發生中斷時我們能知道最後一次觸發該事件是哪段代碼引起的,但是在這之前的9,999到1,999,999次事件我們是不知道的。他會認為所有10,000到2,000,000次事件都是由同一處代碼引起的。如果發生了很多次中斷,收集了很多次信息,而某一行代碼出現了很多次,那麼根據統計學,我們能知道這行代碼觸發了多少次事件。但是如果某一行代碼只出現了一兩次,那麼我們很難知道這行代碼到底出發了多少次時間。
  • CPU一個核只有幾個PMU,但是可以統計的事件有幾十種:
    一個PMU可以統計一個事件,但是我們分析一個軟件可能需要統計幾十種事件。一般的處理方法是多路復用(Multiplexing)。比如說前10ms記錄事件A,后10ms記錄事件B,再后10ms由記錄事件A,……,這樣輪流記錄事件A和事件B,那麼一個PMU就可以同時統計兩個事件。多路復用可以解決少量PMU統計大量事件的問題,但是也會導致每種事件記錄的樣本數減少,倒是最後統計計算的結果誤差更大。
  • Intel® Hyper-Threading Technology導致記錄不準:
    Hyper-Threading技術可以讓一個CPU物理核變成兩個邏輯核,但是這兩個邏輯核會共享很多硬件,包括PMU。這會出現什麼問題呢?例如我們有兩個線程再兩個CPU核同時運行。我們覺得實在兩個核上執行,但是實際上是在同一個物理核上。所以PMU會同時統計兩個程序觸發的事件,我們很難區分到底是哪個邏輯核出發了多少事件,所以PMU記錄的數據就會不準確。另外,性能分析器計算性能指標時會使用一些硬件參數,Hyper-Threading技術會讓這些參數不準確。例如一般個CPU核能再一個clock執行4個uop,所以CPI(Cycle Per Instruction,每個clock執行的指令數)是0.25。但是如果使用了Hyper-Threading技術,實際的CPI會是0.5
  • Event Skid(事件打滑)會導致記錄的IP不準確:
    PMU記錄有些事件會出現一定延時,所以在執行分析器的中斷處理代碼時,可能被測程序已經又執行了好多指令,IP已經向後滑動了很遠了。一般如果我們只是統計函數的話不會太大問題,但是我們想統計指令時就會有很大問題了。比如我們可能會看到某個add指令導致了大量的分支預測錯誤,顯然這個是不可能的。往往這種時候我們可以看看前面一點的指令。
  • Interrupt Masking(中斷屏蔽),導致統計出來的空閑狀態比正常的高
    不同的中斷有不同的優先級,為了高優先級的中斷處理程序不被打斷,我們可以選擇屏蔽一部分中斷事件。但是PMU的事件也是一个中斷,如果系統中有大量的中斷屏蔽,那麼會有PMU的中斷被屏蔽掉一部分,導致統計出來的數據不準確。表現出來的效果就是,統計出來的處於空閑狀態的時間比實際的要高。

總結

這幾個就是比較常見的性能分析方法。我們知道了性能分析的原理,可以更好地理解性能分析器給出的結果,也可以在出現明顯異常的結果時,分析判斷可能的原因,針對性的解決。

參考

https://en.wikipedia.org/wiki/Profiling_(computer_programming)
https://software.intel.com/content/www/us/en/develop/articles/understanding-how-general-exploration-works-in-intel-vtune-amplifier-xe.html
https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/analyze-performance/user-mode-sampling-and-tracing-collection.html
https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/analyze-performance/hardware-event-based-sampling-collection.html

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

【其他文章推薦】

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

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

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

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

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

循序漸進VUE+Element 前端應用開發(10)— 基於vue-echarts處理各種圖表展示,循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理,循序漸進VUE+Element 前端應用開發(9)— 界面語言國際化的處理

在我們做應用系統的時候,往往都會涉及圖表的展示,綜合的圖表展示能夠給客戶帶來視覺的享受和數據直觀體驗,同時也是增強客戶認同感的舉措之一。基於圖表的處理,我們一般往往都是利用對應第三方的圖表組件,然後在這個基礎上為它的數據模型提供符合要求的圖表數據即可,VUE+Element 前端應用也不例外,我們這裏使用了基於vue-echarts組件模塊來處理各種圖表vue-echarts是對echarts圖表組件的封裝。

1、圖表組件的安裝使用

首先使用npm 安裝vue-echarts組件。

git地址:https://github.com/ecomfe/vue-echarts

NPM安裝命令

npm install echarts vue-echarts

然後在對應模塊頁面裏面引入對應的組件對象,如下代碼所示。

<script>
import ECharts from 'vue-echarts' // 主圖表對象
import 'echarts/lib/chart/line' // 曲線圖表
import 'echarts/lib/chart/bar' // 柱狀圖
import 'echarts/lib/chart/pie' // 餅狀圖
import 'echarts/lib/component/tooltip' // 提示信息

接着在Vue組件裏面對象中加入對象即可。

export default {
  components: {
    'v-chart': ECharts
  },

如果是全局註冊使用,那麼可以在main.js裏面進行加載

// 註冊組件后即可使用
Vue.component('v-chart', VueECharts)

我們來看看圖表展示的效果圖

柱狀圖效果

 

餅狀圖

  

 曲線圖

  

 其他類型,極坐標和散點圖形

  

 或者曲線和柱狀圖組合的圖形

 更多的案例可以參考官網的展示介紹:https://echarts.apache.org/examples/zh/index.html

  

2、各種圖表的展示處理

對於我們需要的各種常規的柱狀圖、餅狀圖、折線圖(曲線圖)等,我下來介紹幾個案例代碼,其他的一般我們根據官方案例提供的data數據模型,構造對應的數據即可生成,就不再一一贅述。

另外,我們也可以參考Vue-echarts封裝的處理demo:https://github.com/ecomfe/vue-echarts/tree/master/src/demo 

對於柱狀圖,效果如下

在Vue模塊頁面的Template 裏面,我們定義界面代碼如下即可。

<v-chart
  ref="simplebar"
  :options="simplebar"
  autoresize
/>

然後在data裏面為它準備好數據即可,如下代碼所示。

 data() {
    return {
      simplebar: {
        title: { text: '柱形圖Demo' },
        tooltip: {},
        xAxis: {
          data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']
        },
        yAxis: {},
        series: [{
          name: '銷量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20]
        }]
      }
    }
  }

當然我們也可以把這些構造對應數據的邏輯放在單獨的JS文件裏面,然後導入即可。

例如對於餅圖,它的界面效果如下所示。

它的vue視圖下,Template裏面的代碼如下所示。

<v-chart
  ref="pie"
  :options="pie"
  autoresize />

一般對於圖表的數據,由於處理代碼可能不少,建議是獨立放在一個JS文件裏面,然後我們通過import導入即可使用。

  然後在data裏面引入對應的對象即可,如下所示。

<script>
import ECharts from 'vue-echarts' // 主圖表對象
import 'echarts/lib/chart/line' // 曲線圖表
import 'echarts/lib/chart/bar' // 柱狀圖
import 'echarts/lib/chart/pie' // 餅狀圖
import 'echarts/lib/component/tooltip' // 提示信息

// 導入報表數據
import getBar from './chartdata/bar' import pie from './chartdata/pie'
import scatter from './chartdata/scatter'
import lineChart from './chartdata/lineChart'
import incomePay from './chartdata/incomePay'

export default {
  components: {
    'v-chart': ECharts
  },
   return {
      pie,
      scatter,,
      lineChart,
      incomePay,
      simplebar: {
        title: { text: '柱形圖Demo' },
        tooltip: {},
        xAxis: {
          data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']
        },
        yAxis: {},
        series: [{
          name: '銷量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20]
        }]
      }
    }
  },

其中pie.js裏面放置的是處理餅圖數據的邏輯,如下代碼所示。

export default {
  title: {
    text: '餅圖程序調用高亮示例',
    x: 'center'
  },
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c} ({d}%)'
  },
  legend: {
    orient: 'vertical',
    left: 'left',
    data: ['直接訪問', '郵件營銷', '聯盟廣告', '視頻廣告', '搜索引擎']
  },
  series: [
    {
      name: '訪問來源',
      type: 'pie',
      radius: '55%',
      center: ['50%', '60%'],
      data: [
        { value: 335, name: '直接訪問' },
        { value: 310, name: '郵件營銷' },
        { value: 234, name: '聯盟廣告' },
        { value: 135, name: '視頻廣告' },
        { value: 1548, name: '搜索引擎' }
      ],
      itemStyle: {
        emphasis: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: 'rgba(0, 0, 0, 0.5)'
        }
      }
    }
  ]
}

在界面處理的時候,值得注意的時候,有時候Vue頁面處理正常,但是圖表就是沒有出來,可能是因為高度或者寬度為0的原因,需要對對應的樣式進行處理設置,以便能夠正常显示出來。

如下是我 對圖表的設置的樣式處理,使得圖表在一個卡片的位置能夠显示正常。

<style scoped>
  .echarts { width: 100%; height: 400px;}

  .el-row {
    margin-bottom: 20px;
  }
  .el-col {
    border-radius: 4px;
    margin-bottom: 20px;
  }
</style>

最後幾個界面組合一起的效果如下所示。

 以上就是基於vue-echarts處理各種圖表展示,其中常規的引入組件很容易的,主要就是需要根據對應的圖表案例,參考數據組成的規則,從而根據我們實際情況構建對應的數據,賦值給對應的模型變量即可。

列出以下前面幾篇隨筆的連接,供參考:

循序漸進VUE+Element 前端應用開發(1)— 開發環境的準備工作

循序漸進VUE+Element 前端應用開發(2)— Vuex中的API、Store和View的使用

循序漸進VUE+Element 前端應用開發(3)— 動態菜單和路由的關聯處理

循序漸進VUE+Element 前端應用開發(4)— 獲取後端數據及產品信息頁面的處理

循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理

循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用

循序漸進VUE+Element 前端應用開發(7)— 介紹一些常規的JS處理函數

循序漸進VUE+Element 前端應用開發(8)— 樹列表組件的使用

循序漸進VUE+Element 前端應用開發(9)— 界面語言國際化的處理

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

因為知道了30+款在線工具,我的工作效率提升500%!

GitHub 15.2k Star 的Java工程師成神之路,不來了解一下嗎!

GitHub 15.2k Star 的Java工程師成神之路,不來了解一下嗎!

Perl 之父 Larry Wall 曾經在自己的《Programming Perl》一書中提到過:”程序員有3種美德: 懶惰、急躁和傲慢” 。懶惰,作為程序員美德的第一個要素。

Larry Wall 所說程序員應該具備的懶惰,並不是安於現狀、不思進取。而是一種為了達到同樣甚至更好的目標,而付出最少的時間或者精力的行為。一個懶惰的程序員會盡量使自己的代碼即實用又有很好的可讀性,這樣可以節省很多後面的維護的成本。一個懶惰的程序員會儘力完善代碼中的註釋及文檔,以免別人問自己太過問題。一個懶惰的程序員會擅長使用各種工具,從方方面面提升自己的效率。

懶惰是科技發展、人類進步的最大動力。從原始社會、農業時代、工業時代一直到如今的信息時代。因為懶惰,人們才會有動力去發明各種高效、便捷的工具,這些當初的工具,漸漸的就形成了如今的科技。所謂工欲善其事、必先利其器,說的就是這個道理。

在一篇文章中,作者將介紹多種實用的工具,全方位的武裝你,使我們的讀者都可以當一個“懶惰”的程序員。

搜索類在線工具

1、SearchCode(https://searchcode.com/ )是一個源碼搜索引擎,目前支持從 Github、Bitbucket、Google Code、CodePlex、SourceForge 和 Fedora Project 平台搜索公開的源碼。

2、mvnrepository(http://mvnrepository.com )這個不用詳細解釋了,就是查詢maven的gav等信息。

3、Iconfont(https://www.iconfont.cn )國內功能很強大且圖標內容很豐富的矢量圖標庫,提供矢量圖標下載、在線存儲、格式轉換等功能。阿里巴巴體驗團隊傾力打造,設計和前端開發的便捷工具。

4、BinaryDoc for OpenJDK(https://openjdk.binarydoc.org/net.java/openjdk/)直接從OpenJDK二進制文件生成文檔,二進制代碼是最好的文檔。

5、Unsplash(https://unsplash.com )是一個免費的圖片分享網站,可以在上面搜索無版權圖片

6、鳩摩搜書(https://www.jiumodiary.com/ )國內一款強大的电子書搜索引擎,整合了大部分电子書平台的資源,最重要的是他無需註冊登錄,可以直接下載。並且網站頁面清新、且資源免費。

7、MySlide(https://myslide.cn/ )是一個提供PPT分享服務的平台,在這裏你可以找到你想要的PPT。專註技術領域的PPT共享,各種技術大會的演講PPT這裏都有。

8、IT大咖說(https://www.itdks.com/ )是IT垂直領域的大咖知識分享平台,分享行業TOP大咖乾貨,技術大會在線直播錄播,在線直播知識分享平台。

生成類在線工具

1、BeJSON(http://www.bejson.com/json2javapojo )是一個比較好用將Json轉成Java對象的工具。json是目前JavaWeb中數據傳輸的主要格式,很多時候會有把json轉成Java對象的需求。有時候合作方會提供一個json的樣例,需要我們自己定義Java類,這時候這個工具就派上用場了。

2、在線corn生成工具(https://cron.qqe2.com/ ),Cron 一般用於配置定時任務的執行。但是要想一次性的把一個corn表達式配置好確實很難的,需要程序員記住他的語法。有一些在線工具可以提供圖形化的界面,只要輸入想要定時執行的周期等,就可以自動生成corn表達式。

3、正則表達式的生成工具(http://tool.chinaz.com/tools/regexgenerate )正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE),計算機科學的一個概念。正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本。在使用正則表達式進行字符轉過濾的時候,需要用事先定義好的一些特定字符、及這些特定字符的組合,組成一個“規則字符串”,這個“規則字符串”用來表達對字符串的一種過濾邏輯。通常,這個規則字符串的定義是比較麻煩和複雜的。也需要經過大量的測試和驗證才能被採用。

4、 ASCII藝術生成工具(http://patorjk.com/software/taag/ )可以將輸入的字符快速轉換成ASCII藝術文字的形式。

5、ProcessOn(https://www.processon.com/ )是一個在線協作繪圖平台,為用戶提供最強大、易用的作圖工具!支持在線創作流程圖、BPMN、UML圖、UI界面原型設計、iOS界面原型設計。

6、MarkDown編輯器,Markdown 是一種輕量級標記語言,它允許人們使用易讀易寫的純文本格式編寫文檔,深受廣大程序員們的喜愛,推薦幾款在線md編輯器:MaHua(https://mahua.jser.me/ ) 馬克飛象(https://maxiang.io/ ) Cmd(https://www.zybuluo.com/mdeditor )

轉換類在線工具

1、站長工具的編碼轉換(http://tool.chinaz.com/tools/unicode.aspx )比較全面,提供了Unicode編碼、UFT8編碼、URL編碼/解碼等功能。編碼問題一直困擾着開發人員,尤其在Java 中更加明顯,因為Java 是跨平台語言,不同平台之間編碼之間的切換較多。計算中提拱了多種編碼方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。有些時候開發人員需要通過編碼轉換的方式來查看不同編碼下面的文件內容。

2、時間戳轉換工具(http://tool.chinaz.com/Tools/unixtime.aspx),時間戳(英語:Timestamp)是指在一連串的資料中加入辨識文字,如時間或日期,用以保障本地端(local)資料更新順序與遠端(remote)一致。

3、Timebie(http://www.timebie.com/cn/easternbeijing.php )提供了世界時間相互轉換的功能。世界各地時間轉換在做國際業務的時候會經常用到,比如北京時間轉紐約時間,北京時間轉洛杉磯時間。

4、加密解密也是JavaWeb可能會經常遇到的,有的時候我們需要驗證加密算法是否正確,或者要解密等場景,就需要一個在線工具(http://tool.chinaz.com/tools/textencrypt.aspx )來快速驗證。

5、convertworld(https://www.convertworld.com/zh-hans/ )是一個比較全的單位換算的網站。我經常用它進行時間單位和貨幣單位的換算。

6、Convertio(https://convertio.co/zh/flv-mp4/ )是一個在線視頻格式轉換工具,支持多種常見視頻格式,如 FLV、MOV 和 AVI 等。上傳的視頻文件不能超過 100 MB。

7、Docsmall(https://docsmall.com/image-compress )是一個在線圖片壓縮工具,可以批量壓縮圖片、Gif 圖,一次最多上傳 30 張圖片,每張圖片最大為 25 MB。

檢查類在線工具

1、JSON格式化工具(https://www.json.cn/ )是我嘗試過很多同類工具之後最經常使用的一個,不僅支持json格式的驗證及格式化,還可以將json格式壓縮成普通文本等好用功能。有時候我們不確定這個文本是否完全符合JSON格式,有時候我們也想可以更清晰的查看這個JSON文本的格式關係。就可以使用這個工具來進行JSON格式的驗證和格式化。

2、正則驗證(http://tool.chinaz.com/regex ),Java開發對正則表達式肯定不陌生。站長工具提供的這個正則驗真工具還不錯。

3、Diffchecker(https://www.diffchecker.com/ )是一個使用很不錯代碼差異對比工具。使過svn或者git的人對diffcheck肯定不陌生,但有時候我們修改的文本內容並沒有被版本控制,那麼就可以使用在線的網站查看文件的修改情況。

對照類工具

1、ASCII對照表 : http://tool.oschina.net/commons?type=4 2、HTTP狀態碼 : http://tool.oschina.net/commons?type=5 3、HTTP Content-type : http://tool.oschina.net/commons 4、TCP/UDP常見端口參考 : http://tool.oschina.net/commons?type=7 5、HTML轉義字符 : http://tool.oschina.net/commons?type=2 6、RGB顏色參考 : http://tool.oschina.net/commons?type=3 7、網頁字體參考 : http://tool.oschina.net/commons?type=8

在線代碼運行

1、CodeRunner(https://tool.lu/coderunner/ )可以在線運行php、c、c++、go、python、java、groovy等代碼。當我們在外面,沒有IDE又想執行個小程序的時候是個不錯的選擇。

一個實用小插件

最後,再給大家推薦一個chrome插件,這個插件中囊括了很多上面介紹的在線工具的功能,如JSON格式化、時間戳轉換、Markdown工具、編碼解碼、加密解密、正則驗證等。

FeHelper ,可以關注我的公眾號,後台回復”在線工具”,我已經把安裝包給大家準備好了。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

一起玩轉微服務(1)——概念

一、什麼是微服務

隨着各行各業公司的快速發展,業務規模的不斷擴大,不可避免的造成原有架構不能夠適應快速的增長和變化。這時,微服務就進入大家的視野,其實在微服務之前,很多的公司已經做過服務化的改造,並且取得了一定的成果,但是對於整體流程的標準化還有一定有差距。那麼,什麼是微服務呢?
準確的說,微服務是一種軟件架構模式,將大型系統或者複雜的應用分割成多個服務的架構,服務之間互相協調、互相配合,為用戶提供最終價值。每個服務都有獨立的生命周期,可以單獨的維護和部署,各個業務模塊之間是松耦合的,比傳統的應用程序更有效地利用計算資源,應用的擴展更加靈活,能夠通過擴展組件來處理功能瓶頸問題。這樣一來,開發人員只需要為額外的組件部署計算資源,而不需要部署一個完整的應用程序的全新迭代。
一個微服務的架構如圖所示,單體應用被拆分成多個微小的服務:

也有人將微服務的開發比喻成搭積木,每個服務都是一個零件,使用這些不同的服務可以搭建出不同的形狀。簡單的說,微服務架構就是把一個大系統按業務功能分解成多個職責單一的小系統,並利用簡單的方法使多個小系統相互協作,組合成一個大系統。
其實這裏蘊含着自古以來的真理,就是分而治之,當一件事情大到不能處理的時候,就使用一定的切分方法,將其變成很多微小的類,然後分門別類的進行處理,以達到最好的效果,最終實現1+1>2的功效。
微服務的優點和缺點(或者說挑戰)一樣明顯。
優點

  • 開發簡單,每個服務完成獨立的功能

  • 技術棧靈活,可以選擇不同的語言完成不同的服務,發揮各種語言的最大優勢

  • 服務獨立無依賴,每個服務都可以單獨部署,一個服務出現問題不會導致整個系統癱瘓

  • 獨立按需擴展,以應對高併發以及大流量

  • 可用性高,當其中一個點出現問題時,能夠及時切換,不影響業務正常運行

  • 複雜應用解耦為小而眾的服務,拆分可以基於一定的原則,將耗時的應用解耦

  • 各服務精而專,也就是我們常說的專人干專事,映射到微服務中就是專服務干專事

  • 服務間通信通過API完成,選擇輕量的API通信

    缺點(挑戰)

  • 多服務運維難度,服務的增加意味着運維的困難,如何有效的管理是一個挑戰

  • 系統部署依賴,當業務複雜是,系統之間的耦合關係高度耦合,如何高效部署是一個挑戰

  • 服務間通信成本,包括網絡延遲,接口不可用性等,保證服務的高可用性是一個挑戰

  • 數據一致性,再各個服務間如何有效的共享數據,確保相應服務的數據需求一致性是一個挑戰

  • 系統集成測試,拆分后,原本需要測試的內容成倍的增加,如何高效的降低測試成本是一個挑戰

  • 重複工作,服務拆分之後,由於信息的不對稱導致的重複性工作,如何有效抽象是一個挑戰

  • 性能監控,原本只需要一個監控的部分,現在需要分開監控,如何快速定位問題是一個挑戰

  • 溝通成本的成倍增加,服務拆分后,各個服務由單獨的人來維護,如何高效的溝通是一個挑戰

二、為什麼微服務

從一般的平台遇到的問題說起,平台的問題包括:

  • 服務配置複雜。基礎服務多,服務的資源配置複雜。傳統方式管理服務複雜。
  • 服務之間調用複雜。檢索服務、用戶中心服務等,服務之間的調用複雜,依賴多。
  • 服務監控難度大。服務比較多,機器部署複雜,服務存活監控、業務是否正常監控尤為重要。
  • 服務化測試問題。服務依賴性比較大,測試一個小的功能,周邊服務也需要啟動。

那麼微服務的架構有什麼優勢,大家為什麼都懷着極高的熱情來應對微服務呢。

1. 區別

首先看一下兩者的區別,單體應用和分佈式應用都有哪些優點和缺點呢,通過下面的表格來做一下分析和對比?

 

 

從上面的表格我們可以看到,分佈式系統雖然有一些優勢,但也存在一些問題。

  • 架構設計變得複雜(尤其是其中的分佈式事務)。
  • 部署單個服務會比較快,但是如果一次部署需要多個服務,部署會變得複雜。
  • 系統的吞吐量會變大,但是響應時間會變長。
  • 運維複雜度會因為服務變多而變得很複雜。
  • 架構複雜導致學習曲線變大。
  • 測試和查錯的複雜度增大。
  • 技術可以很多樣,這會帶來維護和運維的複雜度。
  • 管理分佈式系統中的服務和調度變得困難和複雜。

也就是說,分佈式系統架構的難點在於系統設計,以及管理和運維。

接下來我們就一起來看一下架構的演變過程。

2. 從單體應用說起

下圖是我們非常熟悉的單體應用,或者說是非常傳統的應用架構。
從圖中可以看到,應用通過瀏覽器進行訪問,當訪問到訂單服務的時候,分單服務提供相應的功能,然後去訪問應用的數據層,數據層負責對數據的解析,這是一個極其典型的單體應用結構,這種結構所帶來的問題顯而易見,數據庫存在單點,所有服務都耦合在一個應用中,當其中一個服務出現問題的時候,整個工程都需要重新發布,從而導致整體業務不能提供響應。這種結構在小項目中是沒有什麼問題的,而且操作起來非常靈活,但當業務量爆增的時候,就無法靈活應對了。

 

 

3. 第一步切分

為了應對單體應對無法滿足業務增漲,需要對數據庫進行進一步的切分,以提高擴展性,下圖就是一個切分后的架構圖。

業務之間通過進程間的服務進行相互調用,數據庫之間沒有耦合性,不會存在單點故障。前端只需要調用相應的服務,返回自身需要的數據,然後與用戶進行交互。可以看到,這種方式比傳統的單體應用已經前進了一步,但是數據庫層面仍然存在着問題,根據數據量需要評估是否使用讀寫分離的設計,服務層面也增加了相應的複雜性。前端調用隨着調用接口數量的增加也急需治理。

4. 服務化所帶來的問題

隨着服務的拆分,新的問題應運而生。客戶端如何訪問這些服務?這些服務的調用情況,切分是否合理,安全問題,如果受到攻擊應該如何應對,是否可以使用限流或者降級的方式來及時解決。
應對這些問題,API網關是一個不錯的解決方案。當有新的設備需要調用這些接口時,可以復用原有接口,不需要進行二次開發。接口的維護也會更有條理性,對於訪問次數,安全等問題,都可以在這一層進行解決。

解決了應用前端訪問的問題后,服務之間的相互調用也是一個問題,如果整個系統內部調用關係混亂,就會帶來非常多的不必要的問題。所以約定好服務之間的通信方式是非常有必要的。不管是開源還是公司內部研發,都有非常多的解決方案。最簡單的方式,是通過rpc的調用,傳輸json或者XML,雙方定義好協議格式,通過報文的方式進行數據傳輸。
當多種服務需要互相調用的時候,服務的數量會急劇的增加。服務的治理就成為新的問題。不同的服務的版本問題。如果不能通過一個單獨的註冊地址,像書的目錄一樣來管理整個服務結構,服務的調用就侍显示非常混亂。
一般的分佈式服務,都有一個註冊中心,例如dubbo是基於zookeeper進行的二次開發,自身提供管理控制台,可以對服務進行註冊和查找,Spring Cloud有Eureka,還有etcd,consul等可供選擇。
經過上面的處理,整體上來看應用已經達到了一個非常好的狀態,但是每個應用服務仍然存在着單點問題,當一個服務出現問題的時候,有可能導致連鎖的反應,使用整個系統癱瘓。這時候需要除了監控告警之外的一種容錯機制保障整體服務的可運行性。
通過網關層的反向代理來實現高可用是一個不錯的解決方案,訪問層無需要了解整個體系有多少應用提供,只需要關心服務是否能夠提供服務,並且對必要的接口進行監測,當發現接口無法正常提供服務時,提供相應的告警機制,以微信、短信或者郵件的方式通知相關人及時進行處理。
隨着服務的切分,業務的擴展,數據量的激增也是一個非常大的問題,如果採用傳統的方案來應對,各種關係型數據庫都有瓶頸,把運算量比較大,比較耗資源的運算,跨庫統計查詢的需求。
再下一步隨着數據量的不斷增漲,需要業務和統計分離。
統計一般都是比較耗時的應用,比如計算用戶的留存情況,需要分析一周甚至是更長周期內的用戶數據,如果使用在線的方式分析顯然不太現實,所以,對於大數據量的分析和數據挖掘,需要從業務中抽取數據進行離線分析,然後將分析的結果進行展現。

5. 微服務的可擴展性

針對以上的分析,可以看到,微服務需要具備極強的可擴展性,這些擴展性包含以下幾個方面:
• 性能可擴展:性能無法完全實現線性擴展,但要盡量使用具有併發性和異步性的組件。具備完成通知功能的工作隊列要優於同步連接到數據庫。
• 可用性可擴展:CAP理論表明,分佈式系統無法同時提供一致性、可用性和分區容錯性保證。許多大規模Web應用程序都為了可用性和分區容錯性而犧牲了強一致性,而後者則有賴於最終一致性來保證。
• 維護可擴展:軟件和服務器都需要維護。在使用平台的工具監控和更新應用程序時,要盡可能地自動化。
• 成本可擴展:總成本包括開發、維護和運營支出。在設計一個系統時,要在重用現有組件和完全新開發組件之間進行權衡。現有組件很少能完全滿足需求,但修改現有組件的成本還是可能低於開發一個完全不同的方案。另外,使用符合行業標準的技術使組織更容易聘到專家,而發布獨有的開源方案則可能幫助組織從社區中挖掘人才。

6. 微服務與SOA的區別

面向服務的架構(SOA)是一個組件模型,它將應用程序的不同功能單元(稱為服務)通過這些服務之間定義良好的接口和契約聯繫起來。接口是採用中立的方式進行定義的,它應該獨立於實現服務的硬件平台、操作系統和編程語言。這使得構建在各種各樣的系統中的服務可以以一種統一和通用的方式進行交互。
都是做服務化,那麼微服務與SOA的異同有哪些呢?
相同點:

  • 需要Registry,實現動態的服務註冊發現機制;
  • 需要考慮分佈式下面的事務一致性,CAP原則下,兩段式提交不能保證性能,事務補償機制需要考慮;
  • 同步調用還是異步消息傳遞,如何保證消息可靠性?SOA由ESB來集成所有的消息;
  • 都需要統一的Gateway來匯聚、編排接口,實現統一認證機制,對外提供APP使用的RESTful接口;
  • 同樣的要關注如何再分佈式下定位系統問題,如何做日誌跟蹤,就像我們電信領域做了十幾年的信令跟蹤的功能;

差異點:

  • 是持續集成、持續部署?對於CI、CD(持續集成、持續部署),這本身和敏捷、DevOps是交織在一起的,所以這更傾向於軟件工程的領域而不是微服務技術本身;
  • 使用不同的通訊協議是不是區別?微服務的標杆通訊協議是RESTful,而傳統的SOA一般是SOAP,不過目前來說採用輕量級的RPC框架Dubbo、Thrift、gRPC非常多,在Spring Cloud中也有Feign框架將標準RESTful轉為代碼的API這種仿RPC的行為,這些通訊協議不應該是區分微服務架構和SOA的核心差別;
  • 是流行的基於容器框架還是虛擬機為主?Docker和虛擬機還是物理機都是架構實現的一種方式,不是核心區別;

SOA和微服務的一個主要不同點就是自動化程度上的不同。大部分的SOA實現只達到服務級別的抽象,而微服務走的更遠,它達到了對實現和運行環境的抽象級別。
而在一個規範的微服務中,每個微服務應該被構建成胖jar(fat Jar)其中內置了所有的依賴,然後作為一個單獨的java進程存在。

三、常見的微服務組件

既然談到了微服務架構,就說一下通用的微服務都包括哪些組件:

  • 服務註冊

服務註冊是一個記錄當前可用的微服務實例的網絡信息數據庫,是服務發現機制的主要核心,服務註冊表查詢api、管理api,使用查詢api獲得可用服務的實例,使用管理api實現註冊、註銷。

  • 服務發現

服務調用方從服務註冊中心找到自己需要調用的服務的地址。可以選擇客戶端服務發現,也可以選擇服務端服務發現。

  • 負載均衡

服務提供方一般以多實例的形式提供服務,負載均衡功能能夠讓服務調用方連接到合適的服務節點。並且,節點選擇的工作對服務調用方來說是透明的。可以選擇服務端的負載均衡也可以選擇客戶端的負載均衡。

  • 服務網關

服務網關是服務調用的唯一入口,可以在這個組件是實現用戶鑒權、動態路由、灰度發布、A/B測試、負載限流等功能。根據公司流量規模的大小網關可以是一個,也可以是多個。

  • 配置中心

將本地化的配置信息(properties, XML, yaml等)註冊到配置中心,實現程序包在開發、測試、生產環境的無差別性,方便程序包的遷移。配置部分可以單獨使用高可用的分佈式配置中心,確保一個配置服務出現問題是,其它服務也能夠提供配置服務。

  • API管理

以方便的形式編寫及更新API文檔,並以方便的形式供調用者查看和測試。通常需要加入版本控制的概念,以確保服務的不同版本在升級過程中都能夠提供服務。

  • 集成框架

微服務組件都以職責單一的程序包對外提供服務,集成框架以配置的形式將所有微服務組件集成到統一的界面框架下,讓用戶能夠在統一的界面中使用系統。

  • 分佈式事務

對於重要的業務,需要通過分佈式事務技術(TCC、高可用消息服務、最大努力通知)保證數據的一致性。根據業務的不同適當的需要犧牲一些數據的一致性要求,確保數據的最終一致性。

  • 調用鏈

記錄完成一個業務邏輯時調用到的微服務,並將這種串行或并行的調用關係展示出來。在系統出錯時,可以方便地找到出錯點。同時統計各個服務的調用次數,確保比較熱的服務能夠分配更多的資源。

  • 支撐平台

系統微服務化后,系統變得更加碎片化,系統的部署、運維、監控等都比單體架構更加複雜,那麼,就需要將大部分的工作自動化。現在,可以通過Docker等工具來中和這些微服務架構帶來的弊端。 例如持續集成、藍綠髮布、健康檢查、性能健康等等。可以這麼說,如果沒有合適的支撐平台或工具,就不要使用微服務架構。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

.Net 對於PDF生成以及各種轉換的操作

前段時間公司的產品,要做一個新功能,簽章(就是把需要的數據整理成PDF很標準的文件,然後在蓋上我們在服務器上面的章)

然後我就在百度上找了找,發現搞PDF的類庫很少,要麼就要錢,要麼就有水印,破解版的沒找到。

先講一講我是怎麼生成PDF的

1、生成PDF

  這裏用到了 Spire.Pdf    這個類庫可以在NuGet裏面搜索到,上面帶個小紅標的就是免費版本。  

  當然也可以去他們的官網,上面還有文檔(https://www.e-iceblue.cn/Introduce/Spire-PDF-NET.html)。

  代碼(這是我自己寫的一個測試的表格)

  

        public static void abc()
        {
            //創建PDF文檔
            Spire.Pdf.PdfDocument doc = new Spire.Pdf.PdfDocument();

            //添加一頁
            PdfPageBase page = doc.Pages.Add();
    //設置字體
            PdfTrueTypeFont font = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 20f), true);
            PdfTrueTypeFont font1 = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 11f), true);

            //創建一個PdfGrid對象
            PdfGrid grid = new PdfGrid();

        //這一段的內容是在表格只玩显示一些數據 根據坐標定位 第一個是內容,第二個是字體,第三個是顏色,第四第五是坐標
            page.Canvas.DrawString("XXXXXXXX管理中心回單",
                    font,
                    new PdfSolidBrush(System.Drawing.Color.Black), 130, 10);
            page.Canvas.DrawString("編號:31231",
                    font1,
                    new PdfSolidBrush(System.Drawing.Color.Black), 380, 60);
            page.Canvas.DrawString("經辦人:XXXX",
                    font1,
                    new PdfSolidBrush(System.Drawing.Color.Black), 60, 250);
            page.Canvas.DrawString("打印日期:2020/06/15",
                    font1,
                    new PdfSolidBrush(System.Drawing.Color.Black), 380, 250);
            //設置單元格邊距
            grid.Style.CellPadding = new PdfPaddings(1, 1, 4, 4);

            //設置表格默認字體
            grid.Style.Font = new PdfTrueTypeFont(new System.Drawing.Font("Microsoft Yahei", 12f), true);

            //添加4行4列
            PdfGridRow row1 = grid.Rows.Add();
            PdfGridRow row2 = grid.Rows.Add();
            PdfGridRow row3 = grid.Rows.Add();
            PdfGridRow row4 = grid.Rows.Add();
            PdfGridRow row5 = grid.Rows.Add();
            PdfGridRow row6 = grid.Rows.Add();
            grid.Columns.Add(4);

            //設置列寬
            foreach (PdfGridColumn col in grid.Columns)
            {
                col.Width = 120f;
            }

            //寫入數據 第一行第一個格式的值,第一行第二個格子的值 
            row1.Cells[0].Value = "收款單位";
            row1.Cells[1].Value = "{DW}";
            row2.Cells[0].Value = "收款單位";
            row2.Cells[1].Value = "{DW}";
            row3.Cells[0].Value = "匯款時間";
            row3.Cells[1].Value = "2016/06/02";
            row3.Cells[2].Value = "金額小寫";
            row3.Cells[3].Value = "¥231";
            row4.Cells[0].Value = "金額合計大寫";
            row4.Cells[1].Value = "大蘇打實打實";
            row5.Cells[0].Value = "用途:" +
                "付XXXX2020年04月至2020年04月";
            row6.Cells[0].Value = "提示:回單可重複打印,請勿重複XXX";

            //row5.Cells[0].Height = (float)20;
            //水平和垂直合併單元格 從那個格子開始合併幾個(包含當前格子)
            row1.Cells[1].ColumnSpan = 3;
            row2.Cells[1].ColumnSpan = 3;
            row4.Cells[1].ColumnSpan = 3;
            row5.Cells[0].ColumnSpan = 2;
            row5.Cells[2].ColumnSpan = 2;
            row6.Cells[0].ColumnSpan = 2;
            row6.Cells[2].ColumnSpan = 2;
        //這個是垂直合併,但是我之前合併的沒有效果
            row5.Cells[1].RowSpan = 2;

            //設置單元格內文字對齊方式
            row1.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row1.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row2.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row2.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row3.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row3.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row3.Cells[2].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row3.Cells[3].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row4.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row4.Cells[1].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row5.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            row6.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
            //設置單元格背景顏色
            //row1.Cells[0].Style.BackgroundBrush = PdfBrushes.Gray;
            //row3.Cells[3].Style.BackgroundBrush = PdfBrushes.Green;
            //row4.Cells[3].Style.BackgroundBrush = PdfBrushes.MediumVioletRed;

            //設置邊框顏色、粗細
            PdfBorders borders = new PdfBorders();
            borders.All = new PdfPen(System.Drawing.Color.Black, 0.1f);
            foreach (PdfGridRow pgr in grid.Rows)
            {
                foreach (PdfGridCell pgc in pgr.Cells)
                {
                    pgc.Style.Borders = borders;
                }
            }
            //保存到文檔
            //在指定為繪入表格
            grid.Draw(page, new PointF(30, 80));
            doc.SaveToFile(@"路徑");
       //這句是在瀏覽器重打開這個PDF  
            System.Diagnostics.Process.Start(@"路徑");
        }

 

  保存我們看一下

  

 

 

2、之後就是關於PDF一些轉換的操作了(PDF轉base64,轉圖片之類的,我把代碼貼到下面)

 

 

 

  • 這個用到了iTextSharp類庫,也可以在Nuget下載到

 

/// <summary>
        /// 圖片轉pdf
        /// </summary>
        /// <param name="jpgfile"></param>
        /// <param name="pdf"></param>
        public static bool ConvertJPG2PDF(string jpgfile, string pdf)
        {
            try
            {
                var document = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 25, 25, 25, 25);
                using (var stream = new FileStream(pdf, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    PdfWriter.GetInstance(document, stream);
                    document.Open();
                    using (var imageStream = new FileStream(jpgfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        var image = iTextSharp.text.Image.GetInstance(imageStream);
                        if (image.Height > iTextSharp.text.PageSize.A4.Height - 25)
                        {
                            image.ScaleToFit(iTextSharp.text.PageSize.A4.Width - 25, iTextSharp.text.PageSize.A4.Height - 25);
                        }
                        else if (image.Width > iTextSharp.text.PageSize.A4.Width - 25)
                        {
                            image.ScaleToFit(iTextSharp.text.PageSize.A4.Width - 25, iTextSharp.text.PageSize.A4.Height - 25);
                        }
                        image.Alignment = iTextSharp.text.Image.ALIGN_MIDDLE;
                        document.NewPage();
                        document.Add(image);
                    }
                    document.Close();
                }

                return true;

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
  • 這個用的是(O2S.Components.PDFRender4NET)
/// <summary>
        /// pdf轉img
        /// </summary>
        /// <param name="path">pdf位置</param>
        /// <param name="path2">img位置</param>
        public static void Pdf2Img(string path, string path2)
        {
            PDFFile pdfFile = PDFFile.Open(path);
            //實例化一個PdfDocument類對象,並加載PDF文檔
            using (Spire.Pdf.PdfDocument doc = new Spire.Pdf.PdfDocument())
            {
                doc.LoadFromFile(path);
                //調用方法SaveAsImage()將PDF第一頁保存為Bmp格式
                System.Drawing.Image bmp = doc.SaveAsImage(0);
                // //調用另一個SaveAsImage()方法,並將指定頁面保存保存為Emf、Png            
                System.Drawing.Image emf = doc.SaveAsImage(0, Spire.Pdf.Graphics.PdfImageType.Bitmap);
                System.Drawing.Image zoomImg = new Bitmap((int)(emf.Size.Width * 2), (int)(emf.Size.Height * 2));
                using (Graphics g = Graphics.FromImage(zoomImg))
                {
                    g.ScaleTransform(2.0f, 2.0f);
                    g.DrawImage(emf, new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), emf.Size), new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), emf.Size), GraphicsUnit.Pixel);
                    zoomImg.Save(path2, ImageFormat.Jpeg);
                    
                    zoomImg.Dispose();
                    emf.Dispose();
                    bmp.Dispose();
                }
                doc.Close();
                doc.Dispose();
            }
            
           
        }
  • 這個和上面用的也是同一個(我覺得這個比較好用一些)

 

/// <summary>
                /// 將PDF文檔轉換為圖片的方法
                /// </summary>
                /// <param name="pdfInputPath">PDF文件路徑</param>
                /// <param name="imageOutputPath">圖片輸出路徑</param>
                /// <param name="imageName">生成圖片的名字</param>
                /// <param name="startPageNum">從PDF文檔的第幾頁開始轉換</param>
                /// <param name="endPageNum">從PDF文檔的第幾頁開始停止轉換</param>
                /// <param name="imageFormat">設置所需圖片格式</param>
                /// <param name="definition">設置圖片的清晰度,数字越大越清晰</param>
        public static void ConvertPDF2Image(string pdfInputPath, string imageOutputPath,
      string imageName, int startPageNum, int endPageNum, ImageFormat imageFormat, Definition definition)
        {
            PDFFile pdfFile = null;
            FileStream fs = null;
            try
            {
                fs = new FileStream(pdfInputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
                pdfFile = PDFFile.Open(fs);

                if (startPageNum <= 0)
                {
                    startPageNum = 1;
                }
                if (endPageNum > pdfFile.PageCount)
                {
                    endPageNum = pdfFile.PageCount;
                }
                if (startPageNum > endPageNum)
                {
                    int tempPageNum = startPageNum;
                    startPageNum = endPageNum;
                    endPageNum = startPageNum;
                }
                string path = imageOutputPath + imageName + "1" + "." + imageFormat.ToString();
                Logger.WriteLogs("PDFIMG:" + path);
                using (Bitmap pageImage = pdfFile.GetPageImage(0, 56 * (int)definition))
                {
                    if (!File.Exists(path))
                    {
                        using (FileStream f = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            pageImage.Save(f, ImageFormat.Jpeg);
                            pageImage.Dispose();
                        }
                    }
                }
                fs.Flush();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                if (pdfFile != null)
                {
                    pdfFile.Dispose();
                }
                else if (fs != null)
                {
                    fs.Close();
                    fs.Dispose();
                }
            }
        }

    public enum Definition
    {
        One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6, Seven = 7, Eight = 8, Nine = 9, Ten = 10
    }
  • PDF轉Base64
  • /// <summary>
            /// pdf轉base64
            /// </summary>
            /// <param name="filePath"></param>
            /// <returns></returns>
            public static string PdfWord_To_Base64(string filePath)
            {
                try
                {
                    if (!string.IsNullOrWhiteSpace(filePath.Trim()))
                    {
                        
                            FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
                            byte[] bt = new byte[fileStream.Length];
                            fileStream.Read(bt, 0, bt.Length);
                            fileStream.Close();
                            fileStream.Dispose();
                            return Convert.ToBase64String(bt);
                    }
                    else
                    {
                        return "請輸入正確的路徑";
                    }
    
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

    我主要也就用到這些,之後在發現,我在往上加  

  鏈接: https://pan.baidu.com/s/16xEpLBJ3-8fGjNPyvHUSmA

  提取碼: p9qi

  這個是O2S.Components.PDFRender4NET的文件,我看評論里有問的,之前忘記發了,網上有的是收費的,Nuget裏面帶後綴的我沒用過。

 

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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