跨雲廠商部署 k3s 集群

原文鏈接:https://fuckcloudnative.io/posts/deploy-k3s-cross-public-cloud/

最近一兩年各大雲服務商都出了各種福利活動,很多小夥伴薅了一波又一波羊毛,比如騰訊雲 1C2G 95/年 真香系列,華為雲和阿里雲也都有類似的活動,薅個兩三台就能搭建一個 Kubernetes 集群。但是跨雲服務商搭建 Kubernetes 集群並不像我們想象中的那麼容易,首先就是原生的 Kubernetes 組件本身對資源的消耗量很大,而雲服務器的資源非常有限,經不起這麼大傢伙的折騰,對此我們可以選擇使用輕量級 Kubernetes 發行版:k3s

k3s 將安裝 Kubernetes 所需的一切打包進僅有 60MB 大小的二進制文件中,並且完全實現了 Kubernetes API。為了減少運行 Kubernetes 所需的內存,k3s 刪除了很多不必要的驅動程序,並用附加組件對其進行替換。由於它只需要極低的資源就可以運行,因此它能夠在任何 512MB 內存以上的設備上運行集群。

其實 k3s 的安裝非常簡單,分分鐘就能搞定,但對於公有雲來說,還是有很多坑的,比如內網不通、公網 IP 不在服務器上該咋辦?本文就為你一一解決這些難題,讓天下的雲羊毛都成為 k3s 的後宮!

1. 下載二進制文件

首先來解決第一個難題:k3s 二進制文件的下載。國內下載 GitHub 速度基本都是以幾個 kb 為單位,不忍直視,如果下載內容都是代碼,有很多辦法可以解決,比如通過碼雲中轉啊、直接通過 CDN 下載啊,什麼?你不知道可以通過 CDN 下載?好吧沒關係,現在我告訴你了:https://cdn.con.sh/。

但是上面的 CDN 並不能下載 release 里的內容,要想下載 release 里的內容,可以使用這個網站:https://toolwa.com/github/。打開網站,輸入 release 裏面的文件下載鏈接,點擊起飛即可加速下載。

當然,如果你會魔法上網的話,上面的所有花里胡哨的方法都可以無視,直接下載就好啦(本文選擇使用版本 v1.17.6+k3s1):

$ wget https://github.com/rancher/k3s/releases/download/v1.17.6+k3s1/k3s -O /usr/local/bin/k3s
$ chmod +x /usr/local/bin/k3s

需要在所有節點中下載上述二進制文件。

2. 升級內核

k3s 的默認網絡插件是 flannel,默認模式是 vxlan 模式,建議使用 wireguard 模式,原因不解釋了,不知道 wireguard 是啥的自己去搜一下。

wireguard 對內核的要求比較高,而 CentOS 7.x 的默認內核是不滿足要求的,需要升級內核(如果你的操作系統是 CentOS 7.x 的話)。步驟如下:

① 載入公鑰

$ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

② 升級安裝 elrepo

$ rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

③ 載入 elrepo-kernel 元數據

$ yum --disablerepo=\* --enablerepo=elrepo-kernel repolist

④ 安裝最新版本的內核

$ yum --disablerepo=\* --enablerepo=elrepo-kernel install  kernel-ml.x86_64  -y

⑤ 刪除舊版本工具包

$ yum remove kernel-tools-libs.x86_64 kernel-tools.x86_64  -y

⑥ 安裝新版本工具包

$ yum --disablerepo=\* --enablerepo=elrepo-kernel install kernel-ml-tools kernel-ml-devel kernel-ml-headers -y

⑦ 查看內核插入順序

$ grep "^menuentry" /boot/grub2/grub.cfg | cut -d "'" -f2

CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core)
CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core)
CentOS Linux (0-rescue-96820b9851c24560b5f942f2496b9aeb) 7 (Core)

默認新內核是從頭插入,默認啟動順序也是從 0 開始。

⑧ 查看當前實際啟動順序

$ grub2-editenv list

saved_entry=CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core)

⑨ 設置默認啟動

$ grub2-set-default 'CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core)'

最後重啟檢查:

$ reboot
$ uname -r

注意:集群中的所有節點都需要升級內核。

3. 安裝 wireguard

內核升級了之後,就可以安裝 wireguard 了,也很簡單,步驟如下:

$ yum install epel-release https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
$ yum install yum-plugin-elrepo
$ yum install kmod-wireguard wireguard-tools

注意:集群中的所有節點都需要安裝。

4. 部署控制平面

下面就可以在控制節點上啟動控制平面的組件了,這裏我們選擇手動部署,這樣比較方便修改參數。先創建一個 Service Unit 文件:

$ cat > /etc/systemd/system/k3s.service <<EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=notify
EnvironmentFile=/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
    server \
    --tls-san <public_ip> \
    --node-ip <public_ip> \
    --node-external-ip <public_ip> \
    --no-deploy servicelb \
    --flannel-backend wireguard \
    --kube-proxy-arg "proxy-mode=ipvs" "masquerade-all=true" \
    --kube-proxy-arg "metrics-bind-address=0.0.0.0"
EOF
  • <public_ip> 替換成控制節點的公網 IP。
  • flannel 使用 wireguard 協議來跨主機通信。
  • kube-proxy 使用 ipvs 模式。

啟動 k3s 控制平面並設置開機自啟:

$ systemctl enable k3s --now

查看集群組件健康狀況:

$ kubectl get cs

NAME                 STATUS    MESSAGE   ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok

這裏的輸出沒有 etcd,因為 k3s 的默認數據存儲是 Sqlite,對於小型數據庫十分友好。Kubernetes 控制平面中發生的更改更多是與頻繁更新部署、調度 Pod 等有關,因此對於幾個節點的小型集群而言,數據庫不會造成太大負載,能省下不少資源,真香!

5. 加入計算節點

部署好控制平面之後,就可以加入計算節點了。首先在計算節點上創建 Service Unit 文件:

$ cat > /etc/systemd/system/k3s-agent.service <<EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=exec
EnvironmentFile=/etc/systemd/system/k3s-agent.service.env
KillMode=process
Delegate=yes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s agent \
    --node-external-ip <public_ip> \
    --node-ip <public_ip> \
    --kube-proxy-arg "proxy-mode=ipvs" "masquerade-all=true" \
    --kube-proxy-arg "metrics-bind-address=0.0.0.0"
EOF

環境變量文件 /etc/systemd/system/k3s-agent.service.env 中需要加入兩個環境變量:

  • K3S_URL : API Server 的 URL,一般格式為:https://<master_ip>:6443。其中 <master_ip> 是控制節點的公網 IP。
  • K3S_TOKEN : 加入集群所需的 token,可以在控制節點上查看 /var/lib/rancher/k3s/server/node-token 文件。

/etc/systemd/system/k3s-agent.service.env 內容如下:

K3S_URL=https://<master_ip>:6443
K3S_TOKEN=xxxxxxxx

啟動 k3s-agent 並設置開啟自啟:

$ systemctl enable k3s-agent --now

查看節點狀態:

$ kubectl get node

NAME         STATUS   ROLES    AGE     VERSION
blog-k3s01   Ready    master   3d6h    v1.17.6+k3s1
blog-k3s02   Ready    <none>   3d3h    v1.17.6+k3s1

6. 內網不互通的解決辦法

這裡會遇到一個問題,不同節點的 flannel 使用的是內網 IP 來進行通信,而我們的雲服務器是內網不互通的,而且公網 IP 也不在服務器上。可以看一下 node 的 annotations

$ kubectl get node blog-k3s02 -o yaml

apiVersion: v1
kind: Node
metadata:
  annotations:
    flannel.alpha.coreos.com/backend-data: '"xxxxx"'
    flannel.alpha.coreos.com/backend-type: extension
    flannel.alpha.coreos.com/kube-subnet-manager: "true"
    flannel.alpha.coreos.com/public-ip: 192.168.0.11
    ...

可以看到 flannel 給節點打的註解中的節點 IP 是內網 IP。要想讓 flannel 使用公網 IP 進行通信,需要額外添加一個註解 public-ip-overwrite,然後 flannel 會基於這個 IP 配置網絡。按照官方文檔的說法,如果你的 node 設置了 ExternalIP,flannel 會自動給 node 添加一個註解 public-ip-overwrite,但我不知道該如何給 node 設置 ExternalIP,乾脆就直接手動加註解吧:

$ kubectl annotate nodes <master> flannel.alpha.coreos.com/public-ip-overwrite=<master_pub_ip>
$ kubectl annotate nodes <node> flannel.alpha.coreos.com/public-ip-overwrite=<node_pub_ip>

加了註解之後,flannel 的 public-ip 就會被修改為公網 IP。然後在各個節點上重啟各自的 k3s 服務,查看 wireguard 連接狀況:

$ wg show flannel.1

interface: flannel.1
  public key: ONDgJCwxxxxxxxJvdWpoOKTxQA=
  private key: (hidden)
  listening port: 51820
  
peer: MKKaanTxxxxxxxV8VpcHq4CSRISshw=
  endpoint: <pub_ip>:51820
  allowed ips: 10.42.4.0/24
  latest handshake: 26 seconds ago
  transfer: 133.17 KiB received, 387.44 KiB sent
  persistent keepalive: every 25 seconds

可以看到通信端點被改成了公網 IP,大功告成!

7. metrics-server 問題解決

還有一個問題就是 metrics-server 無法獲取 cpu、內存等利用率核心指標。需要修改 metrics-server 的 manifests,使用以下命令在線編輯 metrics-server 的 manifests:

$ kubectl -n kube-system edit deploy metrics-server

然後加入以下執行參數后保存退出:

      -command:
        - /metrics-server
        - --kubelet-preferred-address-types=ExternalIP
        - --kubelet-insecure-tls

這樣就可以讓 metrics-server 使用公網 IP 來和 node 通信了。修改成功后就可以看到核心指標了:

$ kubectl top nodes
NAME         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
blog-k3s01   193m         9%     886Mi           22%
blog-k3s02   41m          2%     1292Mi          32%

$ kubectl top pod -n kube-system
NAME                                      CPU(cores)   MEMORY(bytes)
coredns-848b6cc76f-zq576                  8m           14Mi
local-path-provisioner-58fb86bdfd-bzdfl   2m           9Mi
metrics-server-bdfc79c97-djmzk            1m           12Mi

到這裏跨雲服務商部署 k3s 基本上就大功告成了,下一篇文章將會教你如何打通家裡到雲上 k3s 的網絡,讓你家中所有設備都可以直接訪問 Pod IP、svc IP,甚至可以直接訪問 svc 域名,敬請期待。

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的動態。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

Flutter學習筆記(35)–通知Notification,Flutter學習筆記(35)–通知Notification

如需轉載,請註明出處:Flutter學習筆記(35)–通知Notification

通知的NotificationListener和我們之前寫的事件的Listener一樣,都是功能性的組件,而且也都是從子節點順着widget樹向上冒泡,不同的是,事件的Listener不可以被終止,但是通知的NotificationListener是可以被終止的。

是否終止根據NotificationListener的返回值來決定。

說一下我個人的理解:

通知Notification的發送是通過disPatch進行分發的,就好像Android裏面的事件分發,當NotificationListener監聽到了通知事件,這時候會走到其onNotification回調中,根據回調中的返回值類型(true還是false)來決定是否還繼續向父親節點發送通知。

返回true就是繼續分發,返回false就是終止分發,返回false就意味着上層節點的NotificationListener就不會接收到通知事件了。

舉個例子就是:

兩層NotificationListener嵌套,子節點的NotificationListener返回true,那麼父親節點的NotificationListener可以接收到通知事件,反之如果返回false,那麼父親節點的NotificationListener就不會接收到通知事件了。

下面看一下demo示例:

demo就是簡單的發送通知,監聽到通知事件后改變text的內容。

1.創建一個事件通知類,要繼承Notification,它其實就是一個數據載體,在裏面定義通知數據的類型和內容。

import 'package:flutter/material.dart';

class MyNotification extends Notification{
  String notificationStr;

  MyNotification(this.notificationStr);
}

2.NotificationListener的使用和通知事件的分發

import 'package:flutter/material.dart';
import 'package:study_app/util/MyNotification.dart';

class NotificationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _NotificationDemoState();
  }
}

class _NotificationDemoState extends State {
  String _notificationData = 'default_data';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NotificationDemo',
      home: new Scaffold(
          appBar: AppBar(
            title: Text('NotificationDemo'),
          ),
          body: NotificationListener<MyNotification>(
            onNotification: (notification) {
              setState(() {
                _notificationData = notification.notificationStr;
              });
              return true;
            },
            child: Column(
              children: <Widget>[
                Text(_notificationData),
                Builder(
                  builder: (context) {
                    return Container(
                      width: double.infinity,
                      child: RaisedButton(
                          child: Text('發送通知'),
                          onPressed: () {
                            MyNotification('notification_data')
                                .dispatch(context);
                          }),
                    );
                  },
                )
              ],
            ),
          )),
    );
  }
}

在看書的時候,作者強調了一種錯誤的寫法,如下圖註釋的部分:

原因是通知在分發的時候,需要一個context參數,這個參數指的是Notification監聽的子widget的context,如果按照註釋部分的寫法的話,context是根widget的,這樣會導致監聽不到子widget了。

所以需要我們通過Builder構建出我們子widget的context,這裏需要特別注意一下。

最後看一下效果截圖:

   

以上!有任何疑問歡迎留言!

 

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

聚甘新

二叉樹的創建與遍歷(遞歸實現)

在樹的基本概念和術語總結一文中介紹了二叉樹的基本結構。

在不知道怎樣用遞歸?按步驟來!一文中介紹了如何使用遞歸。

二叉樹的結構是遞歸的,所以創建、遍歷也可以通過遞歸實現。

下面是一顆二叉樹:

結點的定義:

public class Node {
    Integer value;
    Node leftChild;
    Node rightChild;

    public Node(Integer value) {
        this.value = value;
    }
}

創建

各個結點的值用一個ArrayList集合來保存,根據該集合來創建二叉樹。

按照不知道怎樣用遞歸?按步驟來!中的方法分析如何遞歸地創建一顆二叉樹。

第一步:找到大問題是什麼?

創建一顆二叉樹。

private Node createBinaryTree(ArrayList<Integer> inputList) {
        
}

第二步:找到最簡單問題是什麼?滿足最簡單問題時應該做什麼?

「創建一個空二叉樹」是最簡單的問題,當滿足時,直接返回null

private Node createBinaryTree(ArrayList<Integer> inputList) {   
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
}

第三步:找到重複邏輯是什麼?

因為我們把每個結點的值都放在ArrayList集合中了,所以,每創建一個二叉樹結點,都需要從集合中拿值。

對於每個結點而言,它一定有左孩子和右孩子(上圖中結點3的左孩子和右孩子可以看成「值為null的結點」),

所以要確定每個結點的左孩子和右孩子是誰。

所以重複邏輯是:

  1. 從集合中拿值,創建結點。
  2. 確定該結點的左孩子和右孩子。
//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = ?;//重複邏輯
        node.rightChild = ?;//重複邏輯
    }

}

第四步:自己調用自己

先解釋一下上個代碼片段中的問號。

要確定一個結點的左孩子和右孩子是誰,其實就是一個賦值操作,那麼就一定要先有一些可選的結點

比如說,如果我們要確定結點1的左右孩子,那麼結點2、結點5就必須已經被創建出來了,這樣才能進行賦值操作。

那麼如何在進行賦值操作之前創建結點2、結點5呢?答案是自己調用自己。

我們可以把結點2、結點5看成另一顆二叉樹的根結點,只要我們創建好以結點2或結點5為根結點的二叉樹,那麼結點2和結點5自然就被創建出來了。

確定結點2和結點5的左右孩子同理,這樣一直分解下去,直到分解成最簡單問題,或者從集合中拿到null為止。

注意:自己調用自己時參數的變小是通過inputList.remove(0)實現的。

//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
        node.rightChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
    }

}

第五步:返回

返回的是根結點,該根結點被確定為左孩子或右孩子,從而構成一顆更大的二叉樹,直到滿足最大問題的那顆二叉樹被創建成功,此時返回的根結點是真正的解。

//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
        node.rightChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
    }
	return node;//返回
}

遍歷

先序遍歷

第一步:找到大問題是什麼?

先序遍歷一顆二叉樹,打印出每個結點的值。

public void preOrderTraveral(Node node) {
    
}

第二步:找到最簡單問題是什麼?滿足最簡單問題時應該做什麼?

「遍歷一顆空二叉樹」是最簡單問題,此時任何操作都不用做。

public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
}

第三步:找到重複邏輯是什麼?

打印每個結點的值

public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
    System.out.print(node.value);//重複邏輯
}

第四步:自己調用自己

先序遍歷的過程:

  1. 遍歷根結點
  2. 先序遍歷左子樹
  3. 先序遍歷右子樹
public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
    System.out.print(node.value);//重複邏輯
    preOrderTraversal(node.leftChild);//自己調用自己
    preOrderTraversal(node.rightChild);//自己調用自己
}

自己調用自己時參數通過node.leftChildnode.rightChild不斷變小

第五步:返回

不需要返回值。

中序遍歷和後序遍歷同理

完整代碼

//二叉樹結點
public class Node {
    Integer value;
    Node leftChild;
    Node rightChild;

    public Node(Integer value) {
        this.value = value;
    }
}
//二叉樹
public class BinaryTree {

    private Node root;

    public Node getRoot() {
        return root;
    }

    public BinaryTree(ArrayList<Integer> inputList) {
        Node root = createBinaryTree(inputList);
        this.root = root;
    }

	//創建二叉樹
    private Node createBinaryTree(ArrayList<Integer> inputList) {
        if (inputList == null || inputList.isEmpty()) {
            return null;
        }
        Node node = null;
        Integer value = inputList.remove(0);
        if (value != null) {
            node = new Node(value);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    //先序遍歷
    public void preOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        System.out.print(node.value);
        preOrderTraversal(node.leftChild);
        preOrderTraversal(node.rightChild);
    }
	
    //中序遍歷
    public void inOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        inOrderTraversal(node.leftChild);
        System.out.print(node.value);
        inOrderTraversal(node.rightChild);
    }

    //後序遍歷
    public void postOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        postOrderTraversal(node.leftChild);
        postOrderTraversal(node.rightChild);
        System.out.print(node.value);
    }
}
//測試
public static void main(String[] args) {
    List<Integer> list = Arrays.asList(new Integer[]{1, 2, 3, null, null, 4, null, null, 5, null, 6});
    ArrayList inputList = new ArrayList(list);

    BinaryTree binaryTree = new BinaryTree(inputList);
    Node root = binaryTree.getRoot();
    System.out.print("先序遍歷:");
    binaryTree.preOrderTraversal(root);
    System.out.print("\n中序遍歷:");
    binaryTree.inOrderTraversal(root);
    System.out.print("\n後序遍歷:");
    binaryTree.postOrderTraversal(root);
}

如有錯誤,還請指正。

文章首發於微信公眾號『行人觀學』

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

這樣用技術,程序猿更幸福

作為久經職場而又富有責任心的猿外,每天上班的第一件事,便是連上生產服務器,top free ps 一頓命令熱身猛如虎,然後匯總出業務服務的狀態、系統指標等,看到一切正常,心裏才算踏實。

不知道有多少盡職盡責的朋友們,每天都重複的做着如此机械而又簡單的事情。說句實話,其實和机械狗沒啥區別。本着做事認真、追求極致的態度,那我們為什麼不能打造一款這樣的机械狗呢?

關注過猿外的朋友們都應該知道,在之前的文章中,猿外曾經提到過,守護服務的監控應用——看門狗。不錯,要打造的机械狗,就是看門狗。

接下來就給大家簡單聊一聊,看門狗的背景來源以及實現思路,希望能給正在尋找守護服務監控解決方案的朋友們,一點實現思路。下面的內容十分燒腦,請大家坐穩扶好。

1. 什麼是看門狗?

在由單片機構成的微型計算機系統中,由於單片機的工作,常常會受到來自外界電磁場的干擾,造成程序的跑飛,而陷入死循環,程序的正常運行被打斷,由單片機控制的系統無法繼續工作,會造成整個系統的陷入停滯狀態,發生不可預料的後果,所以出於對單片機運行狀態進行實時監測的考慮,便產生了一種專門用於監測單片機程序運行狀態的芯片,稱“看門狗”——摘自“百度百科”。

相信有不少朋友,看到上面一段非人話的描述,就想直接刪了此篇熊文,溜之大吉。大家,心莫慌,猿外再舉個貼近生活的栗子解釋一下。

猿外家養了一條 dog ,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
如果孩子睡着,長時間未撒狗糧,dog 就主動把孩子叫醒;
繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。

剝開栗子,內行看門道,外行看熱鬧,把上面內容翻譯一遍:

原話:猿外家養了一條 dog,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
翻譯:存在兩個進程:子進程(應用服務)、父進程(守護進程),子進程和父進程兩個進程間一直保持心跳通訊;

原話:如果孩子睡着,長時間未撒狗糧, dog 就主動把孩子叫醒;
翻譯:子進程與父進程心跳通訊中斷,父進程則負責啟動子進程,完成子進程的服務守護;

原話:繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
翻譯:子進程與父進程實時通訊,父進程實時監控子進程的狀態,並實時進行報警通知;

原話:如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。
翻譯:父進程down了,子進程與父進程的通訊也就無法保持了,子進程也一併退出。

不知道朋友們通過上面的栗子剖析,對看門狗了解了多少呢?為了更清晰的給你們說清楚,猿外再給大家上個一目瞭然的圖吧。

如上圖所示,猿外主要把看門狗定位為:集服務守護、指標採集、日誌歸集、自動化報警於一體的監控系統。說白了,有了看門狗,媽媽再也不用擔心我的應用服務出問題了。

洋洋洒洒鋪墊這麼多,那到底該如何實現呢?

2. 如何實現?

猿外先給大家畫個腦圖,主要分五步走,希望朋友們跟着猿外的腳步,莫掉隊。

相信絕大部分朋友通過猿外的腦圖,已經對看門狗應用了解個八九不離十。猿外再稍微闡述一下:

實現方式:看門狗應用在實現過程中也走了一些彎路。剛開始技術選型的時候,考慮到看門狗應該對應用零侵入、低耦合,於是採用javaagent植入方式進行實現,但是萬萬沒想到的是開發、調試過程比較麻煩,一遇到問題,小夥伴就排查半天,在第一版上線后,猿外迅速帶領小夥伴,採用插件化方式進行第二版迭代。

服務守護:採用 J2SE 1.5 java.lang 包中新添加的 ProcessBuilder 類來完成子進程的啟動、停止、重啟(完成子服務的守護功能)。

指標採集:通過 JMX 方式連接子服務,獲取子服務的內存佔用、CPU 使用、線程數等指標。

日誌歸集:主要站在 flume 的肩膀上,集成到項目中並進行二次開發,存儲採用 elasticsearch。

報警通知:主要提供郵箱通知、QQ 通知、微信實時通知。

其中每個實現細節猿外就不再進行深入展開了,感興趣的朋友歡迎關注微信公眾號四猿外(si-yuanwai),後台留言進行交流。

羅里吧嗦,寫了這麼多。羅馬並非一日建成的,實現途中踩過的坑、吃過的苦頭,剎那間全部湧上心頭。不過看門狗應用,已經在生產上推廣使用,自上線以來還廣受好評,猿外現在每天來的第一件事,由原來的系統巡檢變成了沖咖啡,幸福指數陡然上升。

最後,猿外想說的是:簡單的事重複做,你就是專家;重複的事用心做,你就是贏家。沒錯,這話說的沒毛病,不過成為專家之餘,大家不妨稍做創新,看看有沒有可以用的技術輪子,說不定會改變机械的工作方式,提升工作效率,提高幸福指數呢。

歡迎大家關注我的公眾號:四猿外。
我準備了一些純手打的高質量PDF,有好友贊助的也有我自己的,大家可以免費領取:
深入淺出Java多線程、HTTP超全匯總、Java基礎核心總結、程序員必知的硬核知識大全、簡歷面試談薪的超全乾貨。
別看數量不多,但篇篇都是乾貨,看完的都說很肝。

領取方式:掃碼關注后,在公眾號後台回復:666

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

【其他文章推薦】

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

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

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

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

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

聚甘新

Oracle SQL調優系列之SQL Monitor Report

@

目錄

  • 1、SQL Monitor簡介
  • 2、捕捉sql的前提
  • 3、SQL Monitor 參數設置
  • 4、SQL Monitor Report
    • 4.1、SQL_ID獲取
    • 4.2、Text文本格式
    • 4.3、Html格式
    • 4.4、ACTIVE格式
    • 4.5 SQL Monitoring list
  • 5、SQL Monitor Report查詢
    • 5.1、查看所有的sql monitor report
    • 5.2、查看某個sql的sql monitor report
    • 5.3、查看某個sql的整體性能
    • 5.4、查看整個系統的性能

1、SQL Monitor簡介

sql monitor是oracle官方提供的自動監控符合特定條件的SQL,用於收集執行時的細節信息的監控工具,常用於sql調優和系統性能監控

2、捕捉sql的前提

sql monitor 捕捉sql的前提:

  • 并行執行的sql語句
  • 單次執行消耗的CPU或IO超過5秒
  • statistics_level級別必須是TYPICAL 或者ALL
  • 使用/* +MONITOR*/ HINT的SQL語句

3、SQL Monitor 參數設置

  • STATISTICS_LEVEL必須設置為:’TYPICAL’(缺省)或者 ‘ALL’
  • CONTROL_MANAGEMENT_PACK_ACCESS設置為:’DIAGNOSTIC+TUNING’

查看statistics_level參數

show parameter statistics_level;

建議還是改變Session就可以

alter session set statistics_level=ALL;

查看參數CONTROL_MANAGEMENT_PACK_ACCESS

show parameter CONTROL_MANAGEMENT_PACK_ACCESS;

4、SQL Monitor Report

本博客採用DBMS_SQLTUNE包DBMS_SQLTUNE.report_sql_monitor的方式獲取,報告格式有:’TEXT’,’HTML’,’XML’ ,’ACTIVE’,其中’ACTIVE’只在11g R2以後才支持

4.1、SQL_ID獲取

sql monitor使用,必須在sql中使用/* +MONITOR*/ Hint,然後數據會存在v$sql_monitor表裡

隨意找條sql,注意要加/*+ moniotr*/


select /*+ moniotr*/ a.user_code, a.full_name, a.user_pwd, c.unit_code, c.unit_name
  from base_user a
  left join (select ur.user_code, ur.unit_code
               from t_user_role ur
              where ur.user_role < 10) b
    on a.user_code = b.user_code
  left join t_unit_info c
    on b.unit_code = c.unit_code
 where c.unit_code in
       (select uinfo.unit_code
          from t_unit_info uinfo
         start with uinfo.unit_code = '15803'
        connect by prior uinfo.unit_code = uinfo.para_unit_code);
     

提供sql查詢,獲取sql_id

select sql_id,sql_text from v$sql_monitor where sql_text like '%t_unit_info%

4.2、Text文本格式

將上面查詢到的sql_id改下,然後執行如下SQL:

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_text.txt
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => 'g9rtj389t0g66',
  TYPE => 'TEXT',
  REPORT_LEVEL => 'ALL') AS REPORT
FROM dual;
spool off

獲取到text格式的sql monitor

4.3、Html格式

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_html.html
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => 'g9rtj389t0g66',
  TYPE => 'HTML',
  REPORT_LEVEL => 'ALL') AS REPORT
FROM dual;
spool off

獲取到對應報告,可以看到執行計劃、Buffer Gets 等等信息

4.4、ACTIVE格式

ACTIVE格式需要下載相應的flash組件、腳本,詳細見SQL Monitor Report 使用詳解

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_active.html
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => '2rjh5d5k2yujz',
  TYPE => 'ACTIVE',
  REPORT_LEVEL => 'ALL',
  BASE_PATH => 'http://ip/script') AS REPORT
FROM dual;
spool off

4.5 SQL Monitoring list

如果要獲取所有sql monitor,就可以使用如下SQL:

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
SPOOL report_sql_monitor_list.html
SELECT dbms_sqltune.report_sql_monitor_list(
  type         => 'HTML',
  report_level => 'ALL') AS report
FROM dual;
SPOOL OFF

5、SQL Monitor Report查詢

提供sql monitor常用的查詢腳本

5.1、查看所有的sql monitor report

  • 查看所有的sql monitor report
   select dbms_sqltune.report_sql_monitor from dual;

5.2、查看某個sql的sql monitor report

  • 查看某個sql的sql monitor report
  SELECT DBMS_SQLTUNE.report_sql_monitor(sql_id => '2rjh5d5k2yujz', type => 'TEXT') from dual;

5.3、查看某個sql的整體性能

  • 查看某個sql的整體性能
   SELECT DBMS_SQLTUNE.report_sql_monitor_list(sql_id=>'2rjh5d5k2yujz',type =>'TEXT',report_level => 'ALL') AS report FROM dual;

5.4、查看整個系統的性能

  • 查看整個系統的性能
   SELECT DBMS_SQLTUNE.report_sql_monitor_list(type =>'TEXT',report_level => 'ALL') AS report FROM dual;

相關SQL腳本下載:sql download

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

【其他文章推薦】

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

基礎拾遺—-多線程,基礎拾遺—-多線程

基礎拾遺

基礎拾遺——特性詳解

基礎拾遺——webservice詳解

基礎拾遺——redis詳解

基礎拾遺——反射詳解

基礎拾遺——委託、事件詳解

基礎拾遺——接口詳解

基礎拾遺——泛型詳解

基礎拾遺—–依賴注入

基礎拾遺—–數據註解與驗證

基礎拾遺—–mongoDB操作

基礎拾遺—-RabbitMQ

基礎拾遺—CLR

基礎拾遺—-多線程

前言

我們知道c# 程序是自上而下的,但有的時候部分程序使用時間較長比如下載文檔什麼的。這是就可以用到線程。線程可以理解為是程序的執行路徑,每個線程都定義了一個獨特的控制流。如果您的應用程序涉及到複雜的和耗時的操作,那麼設置不同的線程執行路徑往往是有益的,每個線程執行特定的工作。

1.線程的生命周期

線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。

下面列出了線程生命周期中的各種狀態:

未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況。

就緒狀態:當線程準備好運行並等待 CPU 周期時的狀況。

不可運行狀態:下面的幾種情況下線程是不可運行的:

  已經調用 Sleep 方法

  已經調用 Wait 方法

  通過 I/O 操作阻塞

死亡狀態:當線程已完成執行或已中止時的狀況。

2.多線程的優缺點

2.1.優點

  1. 可以使用線程將代碼同其他代碼隔離,提高應用程序的可靠性。
  2. 可以使用線程來簡化編碼。
  3. 可以使用線程來實現併發執行。
  4. 可以提高CPU的利用率

2.2.缺點

  1. 線程開的越多,內存佔用越大。
  2. 協調和管理代碼的難度加大,需要CPU時間跟蹤線程。
  3. 線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題。
  4. 銷毀線程需要了解可能發生的問題並對那些問題進行處理。

3.線程的實現

3.1.異步委託

關於委託基礎拾遺——委託、事件詳解這有詳細介紹,我們都知道調用委託Delegate()或者Delegate?.Invoke()。進行執行,但是主線程的代碼是從上至下進行執行的,那麼我們想要委託方法進行一個新的線程只需BeginInvoke生成異步方法調用即可。

3.3.1.實現

        public delegate void ThreadDelegate();
        static void MethodDelegata()
        {
            Console.WriteLine("MethodDelegata");
        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果,如委託有參數載氣前方添加即可。
            d.BeginInvoke(null,null);
            Console.WriteLine("Main");
            Console.ReadKey();
        } 

執行結果如下

 3.1.1.獲取線程返回值

線程執行時有可能執行時間過長,如果我們要獲取線程的返回值,這是就需要不回線程的狀態和利用線程的回調方法。

  • 檢測等待線程狀態
       public delegate int ThreadDelegate(int i);
        static int MethodDelegata(int i)
        {
            Console.WriteLine("MethodDelegata" + i);
            Thread.Sleep(1000);
            return 100;

        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            IAsyncResult ar = d?.BeginInvoke(1, null, null);//獲取線程執行狀態
            Console.WriteLine("Main");
            while (!ar.IsCompleted) {//線程是否已執行完成,未完成執行
                Console.WriteLine(".");
                Thread.Sleep(10);//減少線程監測頻率
            }
            int res = d.EndInvoke(ar);//獲取線程的返回值
            Console.WriteLine(res);
            Console.ReadKey();
        }

結果如下

·  

我們如果不用while 的方式去等待方法執行結束,可以  ar.AsyncWaitHandle.WaitOne(1000); 但我們預估執行時間如果小於實際執行時間的化,返回值就獲取不到了。

 bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
                if (isEnd)
            {
                int res = d.EndInvoke(ar);//獲取線程的返回值
                Console.WriteLine(res);

            }

View Code

  • 利用 d?.BeginInvoke(1, callBackobject) 回調方法
       public delegate int ThreadDelegate(int i);
        static int MethodDelegata(int i)
        {
            Console.WriteLine("MethodDelegata" + i);
            Thread.Sleep(1000);
            return 100;

        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
            Console.WriteLine("Main");
         
            Console.ReadKey();
        }
        /// <summary>
        /// 結束回調方法
        /// </summary>
        /// <param name="ar"></param>
        private static void CallBack(IAsyncResult ar)
        {
            var obj=ar.AsyncState as ThreadDelegate;
            int res = obj.EndInvoke(ar);
            Console.WriteLine("線程結束,結果為:"+res);
        }

 我們通過lamda表達式優化一下上面的代碼

     static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            //IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
            d?.BeginInvoke(1, ar => {
                int res = d.EndInvoke(ar);
                Console.WriteLine("線程結束,結果為:" + res);
            }, null);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.2.Thread 類

3.2.1.不帶參數

        static void MethodThread()
        {
            Console.WriteLine("MethodDelegata");//第二個參數最多執行時間
            Thread.Sleep(1000);
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(MethodThread);//創建了thread 對象單位啟動
        //Thread t = new Thread(()=> { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000);});//可直接用lamda表達式 
            t.Start();
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.2.2.帶參數 

Start(obj) 傳參:定義方法如果有參數必須object
        static void MethodThread(object s) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } static void Main(string[] args) { //創建了thread 對象單位啟動
            Thread t = new Thread(MethodThread); t.Start("wokao");//傳遞參數
            Console.WriteLine("Main"); Console.ReadKey(); }
對象傳參:定義存放數據和線程方法的類
   class Program
    {
       
        static void Main(string[] args)
        {
            //創建了thread 對象單位啟動
            ClassThead c = new ClassThead("1");
            Thread t = new Thread(c.MethodThread);
            t.Start();//傳遞參數
            Console.WriteLine("Main");
            Console.ReadKey();
        }

    }
    public class ClassThead
    {
        private string wr;
        public ClassThead(string s)
        {
            this.wr = s;
        }
        public  void MethodThread()
        {
            Console.WriteLine("MethodDelegata");
            Thread.Sleep(1000);

        }
    }

3.2.3 前台線程和後台線程

  1.     前台線程:只要存在有一個前台線程在運行,應用程序就在運行。
  2.     後台線程:應用程序關閉時,如果後台線程沒有執行完,會被強制性的關閉
  3.     默認情況下,用Thread類創建的線程是前台線程,線程池中的線程總是後台線程。
  4. thread.IsBackground = true; 設置為後台程序
   static void MethodThread()
        {
            Console.WriteLine("MethodDelegata");
            Thread.Sleep(10000);

            Console.ReadKey();

        }
        static void Main(string[] args)
        {
            Thread t = new Thread(MethodThread);
            t.IsBackground = true;//當main執行結束后,不管t是否執行結束程序都關閉
            t.Start();//傳遞參數
            Console.WriteLine("Main");
        }

thread.Abort() 終止線程的執行。調用這個方法,會拋出一個ThreadAbortException類型的異常。

thread.Join() 將當前線程睡眠,等待thread線程執行完,然後再繼續執行當前線程。

3.3.線程池threadPool

上面已經說了線程是為後台線程,在這多線程的操作推薦使用線程池線程而非新建線程。因為就算只是單純的新建一個線程,這個線程什麼事情也不做,都大約需要1M的內存空間來存儲執行上下文數據結構,並且線程的創建與回收也需要消耗資源,耗費時間。而線程池的優勢在於線程池中的線程是根據需要創建與銷毀,是最優的存在。但是這也有個問題,那就是線程池線程都是後台線程,主線程執行完畢后,不會等待後台線程而直接結束程序。

 //如果帶參數必須為object
        static void MethodThreadPool(object obj)
        {
            Console.WriteLine("MethodDelegata"+Thread.CurrentThread.ManagedThreadId);//當前線程id
            Thread.Sleep(1000);
         }
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(MethodThreadPool);// 必須帶參數
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.4. Task

  1. Task是架構在Thread之上的,也就是說任務最終還是要拋給線程去執行。
  2. Task跟Thread不是一對一的關係,比如開10個任務並不是說會開10個線程,這一點任務有點類似線程池,但是任務相比線程池有很小的開銷和精確的控制
  3. 可以將任務入隊到線程池中異步執行。
  4. 線程池入隊的任務無法取消
  5. 沒有回調方法,可以使用委託實現回調

3.4.1.任務的定義

方法1

var t1 = new Task(() => TaskMethod("Task 1"));
t1.Start();
Task.WaitAll(t1);//等待所有任務結束 

方法2

Task.Run(() => TaskMethod("Task 2"));

方法3

  Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接異步的方法 
  //或者
  var t3=Task.Factory.StartNew(() => TaskMethod("Task 3"));
  Task.WaitAll(t3);//等待所有任務結束

案列

 static void Main(string[] args)
        {
            var t1 = new Task(() => TaskMethod("Task 1"));
            var t2 = new Task(() => TaskMethod("Task 2"));
            t2.Start();
            t1.Start();
            Task.WaitAll(t1, t2);
            Task.Run(() => TaskMethod("Task 3"));
            Task.Factory.StartNew(() => TaskMethod("Task 4"));
            //標記為長時間運行任務,則任務不會使用線程池,而在單獨的線程中運行。
            Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);

            Console.WriteLine("主線程執行業務處理.");
            //創建任務
            Task task = new Task(() =>
            {
                Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine(i);
                }
            });
            //啟動任務,並安排到當前任務隊列線程中執行任務(System.Threading.Tasks.TaskScheduler)
            task.Start();
            Console.WriteLine("主線程執行其他處理");
            task.Wait();
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.ReadLine();
        }

        static void TaskMethod(string name)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        }

View Code

3.4.2.async/await

async是contextual關鍵字,await是運算符關鍵字。

async/await 結構可分成三部分:

  1. 調用方法:該方法調用異步方法,然後在異步方法執行其任務的時候繼續執行;
  2. 異步方法:該方法異步執行工作,然後立刻返回到調用方法;
  3. await 表達式:用於異步方法內部,指出需要異步執行的任務。一個異步方法可以包含多個 await 表達式(不存在 await 表達式的話 IDE 會發出警告)。
class Program
    {
        async static void AsyncFunction()
        {
            await Task.Delay(1);
            Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(string.Format("AsyncFunction:i={0}", i));
            }
        }
        public static void Main()
        {
            Console.WriteLine("主線程執行業務處理.");
            AsyncFunction();
            Console.WriteLine("主線程執行其他處理");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(string.Format("Main:i={0}", i));
            }
            Console.ReadLine();
        }
    }

4.線程爭用與死鎖

  class Program
    {
        static void ChangeState(object obj)
        {
            ClassThead c = obj as ClassThead;
            while (true)
            {
                c.MethodThread();
            }
        }
        //如果帶參數必須為object

        static void Main(string[] args)
        {
            ClassThead c = new ClassThead();
            Thread t = new Thread(ChangeState);
            t.Start(c);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

    }
    public class ClassThead
    {
        private int state = 6;
        public void MethodThread()
        {
            state++;
            if (state == 6)
            {
                Console.WriteLine("MethodDelegata");
                Thread.Sleep(1000);
            }
            state = 6;
        }
    }

View Code

可以從上面的方法中看到執行結果為空,雖然他在執行但是state一直都是>6的。所以是不執行的。

但如果開啟兩個線程的結果是什麼呢?

是執行的因為多個線程有可能是在執行時另一個線程給他賦值了。所以我們就要給對象加鎖

        static void ChangeState(object obj)
        {
            ClassThead c = obj as ClassThead;
            while (true)
            {
                lock (c)
                {
                    c.MethodThread();
                }
            }
        }

注:但是有可能會出現線程爭用一直等待的情況,所以在編程過程設計好鎖的順序

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

聚甘新

測試人員遇到Android APP崩潰和無響應手足無措?,基於Python的Appium環境搭建合集

這2天,在測APP兼容性時,遇到APP奔潰閃退的情況。將問題反饋給開發后,開發自己調試后,沒有復現。由於又是遠程,base地不在一塊,我總不能把手機寄過去吧,那也太費事了。

所以就想到,提供明確的報錯日誌,讓開發定位問題,豈不是就很方便了,也解決了遠程的問題。

那如何抓取到Crash日誌呢,我又沒開發調試工具,也不可能在短時間內搭建一套開發環境。尋思答案后,最終得到了完美解決,且聽細細道來。

了解Crash

我們先來簡單了解下Crash:Crash,就是崩潰。anr(Application Not Responding — 程序無響應)是Crash的一種。程序正常運行中,可能會出現未捕獲到的異常,這就會造成崩潰。

常見Crash異常

NullPointerException  空指針

ClassCastException  類型轉換異常

IndexOutOfBoundsException  下標越界異常

ActivityNotFoundException Activity  未找到異常

IllegalStateException  非法狀態異常

ArrayIndexOutOfBoundsException  數組越界異常

SecurityException  安全異常

NoSuchMethodException  方法未找到異常

SQLException  操作數據庫異常

抓取奔潰和無響應日誌

對於開發人員來說,抓取日誌是很方便的,但對於測試人員來說,就不是太方便了。大多都是直接dos窗口下執行adb命令來抓取日誌,而每次都敲命令也是很麻煩。

所以通過adb程序與bat命令組合使用來抓取日誌,就要方便很多了,短短几秒鐘,可以輕鬆搞定日誌的抓取,期不期待。

環境準備

安裝JDK和ADB,這個安裝很簡單,可參見以前的博文,基於Python的Appium環境搭建合集。

bat文件製作

環境準備好后,就來寫bat文件了。

捕獲Crash異常的bat文件命令

命令參考如下,製作成logcat.bat文件,logcat.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將logcat.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"

SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut

@ECHO ON
adb logcat -v time > .\"%mutID%_%timeStamp%_logcat.log"

pause

上述命令實現原理:該工具的原理是bat文件調用adb工具,將手機運行日誌拉到本地,並將實時日誌也記錄到本地。

當手機需要重現Crash、或者某一段時間內已經發生過Crash,點擊我們製作的bat文件,logcat文件中的命令會將手機的logcat日誌拉下來並實時記錄,直到你關閉cmd窗口。

然後在拉下來的txt中尋找FATAL關鍵字,附近上下文即為Crash日誌。

捕獲ANR異常的bat文件命令

anr:全稱為Application Not Responding,意思為程序無響應。

命令參考如下,製作成anr.bat文件,anr.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將anr.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut
@ECHO ON
adb pull data/anr/traces.txt traces_%timeStamp%.txt

實現原理與捕獲Crash異常是一樣的,只是該命令是針對發生anr的情況。

具體實踐

捕獲Crash異常具體用法:

①將android手機連接電腦,開啟開發者模式並允許usb調試;

②運行logcat.bat文件

③如果手機程序已經發生過crash,10秒后關閉cmd窗口;如果是想重現crash,則在手機端重現后即可關閉cmd窗口;

④在logcat.bat的同級目錄下會生成一份log文件,從文件中搜查FATAL關鍵字,便可找到崩潰代碼。

查看報錯日誌,報錯如下所示:

如上所示截圖,就是測試過程中,發生奔潰的日誌了,將日誌貼在bug里,既方便開發排查問題,又節約協作時間。

使用優點

使用bat文件捕獲日誌,有如下幾個有點:手機無需root;無需開發環境支持;方便保存、查找日誌;操作簡單。

以上就是捕獲報錯日誌的操作步驟了,希望對有需要的博友有所幫助。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

JVM源碼分析之Object.wait/notify(All)完全解讀

概述

本文其實一直都想寫,因為各種原因一直拖着沒寫,直到開公眾號的第一天,有朋友再次問到這個問題,這次讓我靜心下來準備寫下這篇文章,本文有些東西是我自己的理解,比如為什麼JDK一開始要這麼設計,初衷是什麼,沒怎麼去找相關資料,所以只能談談自己的理解,所以大家看到文章之後可以談談自己的看法,對於實現部分我倒覺得說清楚問題不大,code is here,看明白了就知道怎麼回事了。

Object.wait/notify(All)大家都知道主要是協同線程處理的,大家用得也很多,大概邏輯和下面的用法差不多

看到上面代碼,你會有什麼疑惑嗎?至少我會有幾個問題會問自己: * 為什麼進入wait和notify的時候要加synchronized鎖 * 既然加了synchronized鎖,那當某個線程調用了wait的時候明明還在synchronized塊里,其他線程怎麼進入到鎖里去執行notify的 * 為什麼wait方法可能會拋出InterruptedException異常 * 如果有多個線程都進入wait狀態,那某個線程調用notify喚醒線程時是否按照順序喚起那些wait線程 * wait的線程是在某個線程執行完notify之後立馬就被喚起嗎 * notifyAll又是怎麼實現全喚起的 * wait的線程是否會影響load

如果上面這些問題也都是你想了解的,那這篇文章或許能給你一個答案。

為何要加synchronized鎖

從實現上來說,這個鎖至關重要,正因為這把鎖,才能讓整個wait/notify玩轉起來,當然我覺得其實通過其他的方式也可以實現類似的機制,不過hotspot至少是完全依賴這把鎖來實現wait/notify的。

如果要我們來實現這種機制我們會怎麼去做,我們知道wait/notify是為了線程間協作而設計的,當我們執行wait的時候讓線程掛起,當執行notify的時候喚醒其中一個掛起的線程,那需要有個地方來保存對象和線程之間的映射關係(可以想象一個map,key是對象,value是一個線程列表),當調用這個對象的wait方法時,將當前線程放到這個線程列表裡,當調用這個對象的notify方法時從這個線程列表裡取出一個來讓其繼續執行,這樣看來是可行的,也比較簡單,那現在的問題這種映射關係放到哪裡。而synchronized正好也是為線程間協作而設計的,上面碰到的問題它也要解決,或許正因為這樣wait和notify的實現就直接依賴synchronzied(monitorenter/monitorexit是jvm規範里要求要去實現的)來實現了,這隻是我的理解,可能初衷不是這個原因,這其實也是這篇文章遲遲未寫的一個原因吧,因為我無法取證自己的理解是對的,歡迎各位在這塊談談自己的見解。

wait方法執行后未退出同步塊,其他線程如何進入同步塊

這個問題其實要回答很簡單,因為在wait處理過程中會臨時釋放同步鎖,不過需要注意的是當某個線程調用notify喚起了這個線程的時候,在wait方法退出之前會重新獲取這把鎖,只有獲取了這把鎖才會繼續執行,想象一下,我們知道wait的方法是被monitorenter和monitorexit包圍起來,當我們在執行wait方法過程中如果釋放了鎖,出來的時候又不拿鎖,那在執行到monitorexit指令的時候會發生什麼?當然這可以做兼容,不過這實現起來還是很奇怪的。

為什麼wait方法可能拋出InterruptedException異常

這個異常大家應該都知道,當我們調用了某個線程的interrupt方法時,對應的線程會拋出這個異常,wait方法也不希望破壞這種規則,因此就算當前線程因為wait一直在阻塞,當某個線程希望它起來繼續執行的時候,它還是得從阻塞態恢復過來,因此wait方法被喚醒起來的時候會去檢測這個狀態,當有線程interrupt了它的時候,它就會拋出這個異常從阻塞狀態恢復過來。

這裡有兩點要注意: * 如果被interrupt的線程只是創建了,並沒有start,那等他start之後進入wait態之後也是不能會恢復的 * 如果被interrupt的線程已經start了,在進入wait之前,如果有線程調用了其interrupt方法,那這個wait等於什麼都沒做,會直接跳出來,不會阻塞

被notify(All)的線程有規律嗎

這裏要分情況: * 如果是通過notify來喚起的線程,那先進入wait的線程會先被喚起來 * 如果是通過nootifyAll喚起的線程,默認情況是最後進入的會先被喚起來,即LIFO的策略

notify執行之後立馬喚醒線程嗎

其實這個大家可以驗證一下,在notify之後寫一些邏輯,看這些邏輯是在其他線程被喚起之前還是之後執行,這個是個細節問題,可能大家並沒有關注到這個,其實hotspot里真正的實現是退出同步塊的時候才會去真正喚醒對應的線程,不過這個也是個默認策略,也可以改的,在notify之後立馬喚醒相關線程。

notifyAll是怎麼實現全喚起的

或許大家立馬想到這個簡單,一個for循環就搞定了,不過在jvm里沒實現這麼簡單,而是藉助了monitorexit,上面我提到了當某個線程從wait狀態恢復出來的時候,要先獲取鎖,然後再退出同步塊,所以notifyAll的實現是調用notify的線程在退出其同步塊的時候喚醒起最後一個進入wait狀態的線程,然後這個線程退出同步塊的時候繼續喚醒其倒數第二個進入wait狀態的線程,依次類推,同樣這這是一個策略的問題,jvm里提供了挨個直接喚醒線程的參數,不過都很罕見就不提了。

wait的線程是否會影響load

這個或許是大家比較關心的話題,因為關乎系統性能問題,wait/nofity是通過jvm里的park/unpark機制來實現的,在linux下這種機制又是通過pthread_cond_wait/pthread_cond_signal來玩的,因此當線程進入到wait狀態的時候其實是會放棄cpu的,也就是說這類線程是不會佔用cpu資源。

 

一起來學習吧:

PerfMa KO 系列課之 JVM 參數【Memory篇】

Hotspot GC研發工程師也許漏掉了一塊邏輯

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

聚甘新

昶洧將於桃園建電池包場,望2018可推首款量產車

昶洧日前舉行法說會說明近期電動車發展成果與展望,公司董事長沈瑋表示,即將在桃園觀音興建電池包廠,並預計第一台試製樣車 (Beta Car) 可在今年三月出爐,最快 2018 年底發表首款量產車。

針對電動車研發近況,沈瑋表示,公司測試路車 (Alpha Car) 進入路測階段,目前已測試 200 多次,仍持續地蒐集路測數據進行分析,最佳測試風阻係數為 0.23,比 FF 推出的新車還低,是目前風阻係數最低的電動轎車。

他也指出,公司與大陸贛州發改委合作的第一期廠房已建設完成,預計四月份設備將會安裝完畢,而贛州廠試製車間也已完工並開始運作。此外,也已與西班牙巴塞隆納區洽談合作建廠計劃,蘇格蘭政府也看中昶洧技術並邀請去當地設立研發中心,顯見公司卓越的技術已深獲大陸與歐洲政府的看重並積極提出合作的方案。

昶洧也指出,公司計劃在台灣桃園觀音鄉自有土地 2 千多坪上設置電池包廠,另投資新台幣 3 億元建廠房及購置機器設備,因技術均為自有研發技術及自有土地,預計毛利率相對較高,有助於業務開展,電池包量產銷售後可維持台灣地區的營運開支所需;電池包廠將於今年動工,年產量約計 8,000~12,000 片,這部分雖然只能供應贛州昶洧新能源公司十分之一所需,但不會全數供應自家車廠,仍會考慮部份出售給其他車廠。

關於昶洧新車展示及上市計畫,沈瑋則指出,除了一至二月在中國贛州廠展示外,三月底會在香港展出,待香港展覽完成後預計將會安排來台灣展示。昶洧也預計於今年九月時參加法蘭克福車展, 2018 年三月份參加日內瓦車展,超級跑車廠預計今年底完成建廠作業;計畫超級跑車及轎車於今年下半年即可開始預購。

在製車技術方面,沈瑋表示,昶洧輕量化技術是全球獨一無二,目前已申請專利,創新底盤及電池包等多項核心技術都是自行研發,且使用專有航太模擬器進行數據模擬與測試,故節省相當多製作模具的費用,因此領先其他廠商且在開發經費上較其他廠商減少成本約 40~60%,主因是大多數的技術都是自有的。

沈瑋並表示,昶洧一直戮力於發展新興電動車產業,尤其以自行研發的電池管理系統、熱管理系統及創新底盤設計等三大核心專業技術,期許帶動台灣電動車產業供應鏈的升級,惟受台灣保守的投資環境及相關電動車產業政策不明確與未積極推動下,對於公司有心從海外募集資金回流於台灣設廠,提升台灣電動車產業發展的計畫,屢受阻礙;為了不影響公司中、長期發展及創造股東的利益,董事會已通過出售所投資之Flash Hope Holdings Limited部份股權,以獲取資金進行後續投資計劃,且昶洧所屬企業在海外募資的成果截至目前為止,在大陸已募得人民幣 12.3 億元(合台幣56.58億元),將有助全力發展當地的電動車事業。

(本文內容由授權使用。圖片出處)

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

【其他文章推薦】

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

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

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

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

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

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

中國大陸電動車價格遭砍,影響股價

韓國同意美國佈署薩德飛彈防禦系統之後,頻遭中國報復,之前韓國電動車電池商遭到鎖定,被踢出中國補助名單之外。近來北京又出新招,據傳中國電動車商可能要求電池供應商砍價35~40%,韓商更難獲利,電池大廠三星SDI (Samsung SDI) 聞訊一度重挫 4%。

嘉實全球贏家系統報價顯示,台北時間 19 日中午 11 點 26 分,三星SDI股價大跌 4.44%、報 107,500 韓圜。另一韓國電池廠 LG Chem 也挫低 1.29%、報 268,000 韓圜。

巴倫 (Barron’s) 18日報導,摩根士丹利(大摩)分析師Shawn Kim報告稱,中國高工產業研究院宣稱,部分中國電動車商要求電池業者,今年電池價格下砍35~40%。大摩中國分析師Jack Lu認為,中國電池商大砍價格之後,仍有可觀獲利,此一方案可望繼續推動,如此一來,或許會壓縮三星 SDI 毛利。

中國處處設限,電池大砍價,北京又打算把合格電池廠的生產門檻一口氣增至8GWh,韓商生存空間大受打壓。不只如此,三星SDI供應三星Galaxy S8電池的利多,市場也反應過頭。大摩估計S8對三星SDI營收貢獻只有 4~6%。

美國、南韓去(2016)年7月宣布在韓部署「薩德」(THAAD) 反導彈系統之後,中國就開始積極抵制南韓,車用電池大廠LG Chem、三星SDI (Samsung SDI)首當其衝。中國工信部(MIIT)最新公布的第五批新能源汽車推薦目錄中,將內建LG Chem、三星SDI電池的電動車全部剔除。也就是說,所有內建韓系電池的新能源車,都已不再符合北京政府的推薦標準。

Maeil Business News Korea、Business Korea等外電2日報導,在工信部最新列出的493款新能源車推薦名單當中,沒有一款內建LG Chem、三星SDI的車用電池。

巴倫 (Barron’s)、BusinessKorea報導,中國工信部去年11月22日發布《汽車動力電池行業規範條件》修正草案,有意把合格電池廠的生產門檻,從 0.2GWh 至 8GWh,等於一口氣提高 40 倍之多。這也是向廠商喊話,若不在中國生產,就別想做中國生意。

韓媒認為,北京當局此舉是要阻止韓商跨入中國市場,原因之一是要報復南韓和美國合作,佈建薩德 (THAAD) 飛彈防禦系統;另一可能是陸電池廠缺乏補助,政府排除外商競爭,保護本國產業。8GWh 電池產能可供13萬輛純電動車使用,當前 LG Chem 南京廠產能只夠5萬輛電動車使用,三星 SDI 西安廠則可供 4 萬輛電動車使用。據稱符合 8GWh 規定的業者只有兩家,其中之一是陸廠比亞迪 (BYD)。

(本文內容由權使用。圖片出處:Pixabay CC0)

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

【其他文章推薦】

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

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

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

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

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