Ansible之playbook拓展

  一、handlers和notify結合使用觸發條件

  handlers同tasks是屬同級,相當於一個特殊任務列表,這些任務同前文說的tasks里的任務沒有本質的不同,用於當關注的資源發生變化時,才會採取一定的操作。notify此action可用於在每一個play的最後被觸發,這樣可避免多次有改變發生時都執行指定的操作,僅在所有的變化發生完成后一次性地執行指定操作,在notify中列出的操作稱為handler,換句話說當所關注的資源發生變化時notify將調用handlers中定義的操作。其中notify所在任務就是被監控的任務資源變化的任務,notify可以調用多個handlers定義的操作,一個handlers里可以定義很多任務。

---
- hosts: websers
  remote_user: root

  tasks:
    - name: create apache group
      group: name=apache gid=80 system=yes
    - name: create apache user
      user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
    - name: install httpd
      yum: name=httpd
    - name: copy config file
      copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
      notify: restart httpd service

    - name: start httpd service
      service: name=httpd state=started enabled=yes

  handlers:
    - name: restart httpd service
      service: name=httpd state=restarted   

  說明:notify后指定的名稱必須要和handlers里的任務名稱相同,如不同handlers所定義的任務將不會執行,相當於沒有notify調用handlers里的任務。

  在某些情況下,我們可能同時需要調用多個handlers,或者需要使用handlers其他handlers,ansible可以很簡單的實現這些功能,如下所示

  1)調用多個handlers

---
- hosts: websers
  remote_user: root

  tasks:
    - name: create apache group
      group: name=apache gid=80 system=yes
    - name: create apache user
      user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
    - name: install httpd
      yum: name=httpd
    - name: copy config file
      copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
      notify: 
        - restart httpd service
        - check httpd process

    - name: start httpd service
      service: name=httpd state=started enabled=yes

  handlers:
    - name: restart httpd service
      service: name=httpd state=restarted
    - name: check httpd process                                                                                      
      shell: /usr/bin/killall -0 httpd &> /tmp/httpd.log

  說明:調用多個handlers我們需要在notify中寫成列表的形式,同樣我們被觸發的任務名稱需要同handlers里的被調用的任務名稱完全相同

  2)handlers調用handlers

---
- hosts: websers
  remote_user: root

  tasks:
    - name: create apache group
      group: name=apache gid=80 system=yes
    - name: create apache user
      user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
    - name: install httpd
      yum: name=httpd
    - name: copy config file
      copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
      notify: restart httpd service

    - name: start httpd service
      service: name=httpd state=started enabled=yes

  handlers:
    - name: restart httpd service
      service: name=httpd state=restarted
      notify: check httpd process                                                                                    
    - name: check httpd process
      shell: /usr/bin/killall -0 httpd &> /tmp/httpd.log

  說明:handlers調用handlers,則直接在handlers中使用notify選項就可以。

在使用handlers我們需要注意一下幾點:

  1)handlers只有在其所在任務被執行時才會被運行,handlers定義的任務它不會像task任務那樣,自動會從上至下依次執行,它只會被notify所在的任務發生狀態改變時才會觸發handlers 的任務執行,如果一個任務中定義了notify調用handlers,但由於條件的判斷等原因,該任務尚未執行,那麼notify調用的handlers同樣也不會執行。

  2)handlers只會在play的末尾運行一次;如果想要在一個playbook的中間運行handlers,則需要使用meta模塊來實現,如:-mate: flush_handlers

  二、playbook中變量的使用

ansible中變量的命名規範同其他語言或系統中變量命名規則非常類似。變量名以英文大小寫字母開頭,中間可以包含下劃線和数字,ansible變量的來源有很多,具體有以下幾點:

  1)ansible setup模塊,這個模塊可以從遠程主機上獲取很多遠程主機的基本信息,它所返回的所有變量都可以直接調用,有關setup說明請參考本人博客

  2)在/etc/ansible/hosts中定義,此文件是ansible執行名時默認加載的主機清單文件,在裏面除了可定義我們要管理的主機外,我們還可以定義針對單個主機定義單獨的變量,我們把針對單獨某一台主機定義的變量叫做普通變量(也可叫做主機變量);還有一種變量它不是針對單獨一個主機,它針對某一個組裡的所有主機,我們把這種變量叫做公共組變量。主機清單中定義的變量優先級是普通變量高於公共變量。

    2.1)主機變量,可以在主機清單中定義主機時為其添加主機變量以便於在playbook中使用,如下所示

[websers]
192.168.0.128 http_port=80 maxRequestsPerChild=808
192.168.0.218 http_port=81 maxRequestsPerChild=909

    2.2)主機組變量,組變量是指定賦予給指定組內所有主機上的在playbook中可使用的變量,如下所示

[websers]
192.168.0.128 http_port=80 
192.168.0.218 http_port=81 
[websers:vars]
maxRequestsPerChild=909

  3)通過命令行指定變量(-e指定變量賦值,可以說多個但需要用引號引起或者一個變量用一個-e指定賦值),這種在命令行指定的優先級最高。如下所示

ansible-playbook -e 'package_name1=httpd package_name2=nginx' test_vars.yml

  4)在playbook中定義變量,最常見的定義變量的方法是使用vars代碼塊,如下所示

---
- hosts: websers
  remote_user: root
  vars:
    - abc: xxx 
    - bcd: aaa  

  5)在獨立的變量yml文件中定義,在playbook中使用vars_files代碼塊引用其變量文件,如下所示

[root@test ~]#cat vars.yml 
---
package_name1: vsftpd
package_name2: nginx
[root@test ~]#cat test_vars.yml 
---
- hosts: websers
  remote_user: root
  vars_files:
    - vars.yml
  tasks:
    - name: install package1
      yum: name={{ package_name1 }}
    - name: install package2
      yum: name={{ package_name2 }}
[root@test ~]#

  6)在role中定義,這個後續說到角色在做解釋

  變量的調用方式:第一種在playbook中使用變量需要用“{{}}”將變量括起來,表示括號里的內容是一個變量,有時用“{{  variable_name }}”才生效;第二種是ansible-playbook -e 選項指定其變量,ansible-playbook -e “hosts=www user=xxxx” test.yml

  在主機清單中定義變量的方法雖然簡單直觀,但是當所需要定義的變量有很多時,並且被多台主機使用時,這種方法顯得非常麻煩,事實上ansible的官方手冊中也不建議我們把變量直接定義到hosts文件中;在執行ansible命令時,ansible會默認會從/etc/ansible/host_vars/和/etc/ansible/group_vars/兩個目錄下讀取變量定義文件,如果/etc/ansible/下沒有以上這兩個目錄,我們可以手動創建,並且可以在這兩個目錄下創建與hosts文件中的主機名或主機組同名的文件來定義變量。比如我們要給192.168.0.218 這個主機定義個變量文件,我們可以在/etc/ansible/host_vars/目錄下創建一個192.168.0.218的空白文件,然後在文件中以ymal語法來定義所需變量即可。如下所示

[root@test ~]#tail -6 /etc/ansible/hosts 
## db-[99:101]-node.example.com
[websers]
192.168.0.128 
192.168.0.218 
[appsers]
192.168.0.217
[root@test ~]#cat /etc/ansible/host_vars/192.168.0.218 
---
file1: abc
file2: bcd
[root@test ~]#cat test.yml 
---
- hosts: 192.168.0.218
  remote_user: root
  
  tasks:
    - name: touch file1
      file: name={{ file1 }} state=touch
    - name: toch file2
      file: name={{ file2 }} state=touch
[root@test ~]#ansible-playbook test.yml 

PLAY [192.168.0.218] ************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [192.168.0.218]

TASK [touch file1] **************************************************************************************************
changed: [192.168.0.218]

TASK [toch file2] ***************************************************************************************************
changed: [192.168.0.218]

PLAY RECAP **********************************************************************************************************
192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   

[root@test ~]#ansible 192.168.0.218 -m shell -a 'ls -l /root'
192.168.0.218 | SUCCESS | rc=0 >>
總用量 12
-rw-r--r--. 1 root   root    0 11月 17 16:49 abc
-rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
-rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file

[root@test ~]#

  說明:可看到我們定義在/etc/ansible/host_vars/下的主機變量文件中的變量生效了。

同理,我們要想針對某個組的主機定義一些變量,我們只需要在/etc/ansible/group_vars/目錄下創建與主機清單中的主機組同名的文件即可。

  三、使用高階變量

  對於普通變量,例如由ansible命令行設定的,hosts文件中定義的以及playbook中定義的和變量文件中定義的,這些變量都被稱為普通變量或者叫簡單變量,我們可以在playbook中直接用雙大括號加變量名來讀取變量內容;除此以外ansible還有數組變量或者叫做列表變量,如下所示:

[root@test ~]#cat vars.yml 
---
packages_list:
  - vsftpd
  - nginx
[root@test ~]#

  列表定義完成后我們要使用其中的變量可以列表名加下標的方式去訪問,有點類似shell腳本里的數組的使用,如下所示

[root@test ~]#cat test.yml 
---
- hosts: 192.168.0.218
  remote_user: root
  
  vars_files:
    - vars.yml
  tasks:
    - name: touch file
      file: name={{ packages_list[0] }} state=touch
    - name: mkdir dir
      file: name={{ packages_list[1] }} state=directory
[root@test ~]#

  說明:我們要使用列表中的第一個元素變量,我們可以寫成vars_list[0],使用第二個變量則下標就是1,依此類推

[root@test ~]#ansible *218 -m shell -a 'ls -l /root'
192.168.0.218 | SUCCESS | rc=0 >>
總用量 12
-rw-r--r--. 1 root   root    0 11月 17 16:49 abc
-rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
-rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file

[root@test ~]#ansible-playbook test.yml 

PLAY [192.168.0.218] ************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [192.168.0.218]

TASK [touch file] ***************************************************************************************************
changed: [192.168.0.218]

TASK [mkdir dir] ****************************************************************************************************
changed: [192.168.0.218]

PLAY RECAP **********************************************************************************************************
192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   

[root@test ~]#ansible *218 -m shell -a 'ls -l /root'
192.168.0.218 | SUCCESS | rc=0 >>
總用量 16
-rw-r--r--. 1 root   root    0 11月 17 16:49 abc
-rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
drwxr-xr-x. 2 root   root 4096 11月 17 17:23 nginx
drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
-rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file
-rw-r--r--. 1 root   root    0 11月 17 17:23 vsftpd

[root@test ~]#

  說明:可看到我們創建的文件和目錄在目標主機已經生成

上面的用法是典型的python列表的用法,在python中讀取列表中的元素就是用下標的表示來讀取相應的元素的值。接下我們將介紹另外一種更為複雜的變量,它類似python中的字典概念,但比字典的維度要高,更像是二維字典。ansible內置變量ansible_eth0就是這樣一種,它用來保存遠端主機上面eth0接口的信息,包括ip地址和子網掩碼等。如下所示

[root@test ~]#cat test.yml     
---
- hosts: 192.168.0.218
  remote_user: root
  
  tasks:
    - debug: var=ansible_eth0 
[root@test ~]#ansible-playbook test.yml 

PLAY [192.168.0.218] ************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [192.168.0.218]

TASK [debug] ********************************************************************************************************
ok: [192.168.0.218] => {
    "ansible_eth0": {
        "active": true, 
        "device": "eth0", 
        "features": {
            "fcoe_mtu": "off [fixed]", 
            "generic_receive_offload": "on", 
            "generic_segmentation_offload": "on", 
            "highdma": "off [fixed]", 
            "large_receive_offload": "off [fixed]", 
            "loopback": "off [fixed]", 
            "netns_local": "off [fixed]", 
            "ntuple_filters": "off [fixed]", 
            "receive_hashing": "off [fixed]", 
            "rx_checksumming": "on", 
            "rx_vlan_filter": "on [fixed]", 
            "rx_vlan_offload": "on [fixed]", 
            "scatter_gather": "on", 
            "tcp_segmentation_offload": "on", 
            "tx_checksum_fcoe_crc": "off [fixed]", 
            "tx_checksum_ip_generic": "on", 
            "tx_checksum_ipv4": "off", 
            "tx_checksum_ipv6": "off", 
            "tx_checksum_sctp": "off [fixed]", 
            "tx_checksum_unneeded": "off", 
            "tx_checksumming": "on", 
            "tx_fcoe_segmentation": "off [fixed]", 
            "tx_gre_segmentation": "off [fixed]", 
            "tx_gso_robust": "off [fixed]", 
            "tx_lockless": "off [fixed]", 
            "tx_scatter_gather": "on", 
            "tx_scatter_gather_fraglist": "off [fixed]", 
            "tx_tcp6_segmentation": "off", 
            "tx_tcp_ecn_segmentation": "off", 
            "tx_tcp_segmentation": "on", 
            "tx_udp_tnl_segmentation": "off [fixed]", 
            "tx_vlan_offload": "on [fixed]", 
            "udp_fragmentation_offload": "off [fixed]", 
            "vlan_challenged": "off [fixed]"
        }, 
        "hw_timestamp_filters": [], 
        "ipv4": {
            "address": "192.168.0.218", 
            "broadcast": "192.168.0.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.0.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::20c:29ff:fee8:f67b", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "00:0c:29:e8:f6:7b", 
        "module": "e1000", 
        "mtu": 1500, 
        "pciid": "0000:02:01.0", 
        "promisc": false, 
        "speed": 1000, 
        "timestamping": [
            "rx_software", 
            "software"
        ], 
        "type": "ether"
    }
}

PLAY RECAP **********************************************************************************************************
192.168.0.218              : ok=2    changed=0    unreachable=0    failed=0   

[root@test ~]#

  說明:以上playbook就實現了對ansible_eth0這個變量進行調試並打印,可以看到ansible_eth0是一個相對比較複雜的變量,裡面包含了字典,列表混合一起的一個大字典。

我們可以看到ansible_eht0裡面包含了很多內容,我們要想讀取其中的IPV4地址,我們可以採用“.”或者下標的方式去訪問,如下所示

[root@test ~]#cat test.yml 
---
- hosts: 192.168.0.218
  remote_user: root
  
  tasks:
    - name: print ipv4  
      shell: echo {{ ansible_eth0["ipv4"]["address"] }} 
    - name: print mac
      shell: echo  {{ ansible_eth0.macaddress }}
[root@test ~]#ansible-playbook test.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [192.168.0.218] ************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [192.168.0.218]

TASK [print ipv4] ***************************************************************************************************
changed: [192.168.0.218] => {"changed": true, "cmd": "echo 192.168.0.218", "delta": "0:00:00.001680", "end": "2019-11-17 18:30:21.926368", "rc": 0, "start": "2019-11-17 18:30:21.924688", "stderr": "", "stderr_lines": [], "stdout": "192.168.0.218", "stdout_lines": ["192.168.0.218"]}

TASK [print mac] ****************************************************************************************************
changed: [192.168.0.218] => {"changed": true, "cmd": "echo 00:0c:29:e8:f6:7b", "delta": "0:00:00.001746", "end": "2019-11-17 18:30:22.650541", "rc": 0, "start": "2019-11-17 18:30:22.648795", "stderr": "", "stderr_lines": [], "stdout": "00:0c:29:e8:f6:7b", "stdout_lines": ["00:0c:29:e8:f6:7b"]}

PLAY RECAP **********************************************************************************************************
192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   

[root@test ~]#

  說明:由此可以看出ansible多級變量的調用,使用中括號和點號都是可以的

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

【其他文章推薦】

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

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

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

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

讀《MySQL必知必會》我學到了什麼?

前言

最近在寫項目的時候發現自己的SQL基本功有些薄弱,遂上知乎查詢MYSQL關鍵字,期望得到某些高贊答案的指點,於是乎發現了

https://www.zhihu.com/question/34840297/answer/272185020 這位老兄的建議的書單,根據他的建議首先拜讀了《MYSQL必知必會》這本書,整體講的很基礎,頁數也不多一共 253 頁,適合基礎比較薄弱的同學進行食用。然後循序漸進,閱讀更深層次的書籍進行自我提升。這裏記載了自己在閱讀的過程中記錄的一些關鍵內容,分享給大家。書本 PDF 可以在上面的知乎鏈接獲取,或者點擊 http://www.notedeep.com/note/38/page/282 前往老哥的深度筆記進行下載。

閱讀心得

SQL語句和大小寫

SQL語句不區分大小寫,並且在 Windows 環境下,4.1.1版本之後(現在常用的都是 5.6/5.7/8.0+),MYSQL表名,字段名也是不區分大小寫的,因此我們在命名的時候建議使用單個單詞_單個單詞的形式命名,如:mysql_crash_course user_role

這裏附上阿里代碼規範的一條強制要求:

【強制】表名、字段名必須使用小寫字母或数字,禁止出現数字開頭,禁止兩個下劃線中間只出現数字。數據庫字段名的修改代價很大,因為無法進行預發布,所以字段名稱需要慎重考慮。
說明: MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。

不能部分使用DISTINCT

  • DISTINCT 關鍵字應用於所有列而不僅是前置它的列。如果給出 SELECT DISTINCT vend_id,prod_price ,除非指定的兩個列都不同,否則所有行都將被檢索出來。
  • DISTINCT 不會返回其後面跟的所有字段都相同的列。即兩行中SELECT查詢的任何一個字段都相同才會被去重。不能通過DISTINCT加括號 () 等方式來對單個字段進行去重。

比如項目中有這麼一個需求:

需要分頁查詢綁了某收費標準的房屋,因為是房屋列表查詢,我們默認相同ID的房屋只出現一次,錯誤的SQL如下:

房屋表

收費標準表

SELECT DISTINCT h.id, h.num, s.name 
FROM house h 
LEFT JOIN standard s 
ON h.id = s.room_id 
WHERE s.name = '物業費' OR s.name = '暖氣費';

這條語句並不能返回想要的結果,即每套房屋只出現一次,因為不同的收費標準名稱不一樣,DISTINCT 不能對部分查詢條件去重。可以看到房號為1-001的記錄出現了兩次。

不過其實按照需求的描述我這裏僅查詢房屋的信息,對於查詢結果來說同一條記錄的房屋信息肯定完全相同,因此 DISTINCT 在我的業務中滿足要求。而有其他業務需要此關鍵字的時候,請大家慎重使用,切記不能部分使用該關鍵字

區分大小寫和排序順序

在對文本性的數據進行排序時,A與a相同嗎?a位於B之前還是位於Z之後?

在創建字段時可以指定字符集,一般使用 utf8mb4, 此時可以選擇相應的排序規則。

  1. utf8mb4_general_ci ci即大小寫不敏感,排序時忽略大小寫,A a 視作相同
  2. utf8mb4_bin / 帶 cs 的即大小寫敏感,相應的升序排列的話, A~Z 在前,小a~z在後
    相應的,在設置大小寫敏感后,查詢條件 where cs = ‘a’ 只能查找到表中該字段為小寫 a 的行。而不敏感,即ci時,A,a都可以被查詢出來。

BETWEEN關鍵字的注意事項

在區間查詢時,我們最關注的不應該是區間內的能否被匹配到,因為這是肯定的。而區間的邊界能否被匹配才是我們應該注意的知識點,BETWEEN AND 關鍵字匹配區間時,包含左右邊界條件。如下面的 SQL 執行結果如下:

SELECT prod_name, prod_price 
FROM products p
WHERE p.`prod_price` BETWEEN 5.99 AND 10

NULL 與不匹配

在通過過濾選擇出不具有特定值的行時,你可能希望返回具有 NULL 值的行。但是,不行。常見的錯誤會發生在is_xxx 字段上,我經常有這個毛病,

對於 is_delete 字段,我認為為 null 或者 0 都是未刪除的房屋,所以當我使用

SELECT * FROM house WHERE is_delete = '0';

查詢未刪除的房屋時,我只能查到 id 為 3 的房屋,這顯然與我的預期是不符的,解決辦法是where後面加 or is_delete is null ,或者給 is_delete 列默認值 0;建議使用後者。

這裏附上阿里代碼手冊的一條強制項目:

【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint(1 表示是,0 表示否)。說明:任何字段如果為非負數,必須是 unsigned 。

解釋:tinyint 相當於 Java 中的 byte,取值範圍 -128 ~ 127 ,用來表達是否長度已經足夠,也可以用來表示人的年齡。而 unsigned 表示無符號的,對於確定為非負數的字段,使用 unsigned 可以將取值範圍擴大一倍。

AND 和 OR 的計算次序

舉個例子:假如需要列出價格為10美元(含)以上且由 1002 或 1003 製造的所有產品。下面的 SELECT 語句使用 AND 和 OR 操作符的組合建立了一個WHERE 子句:

SELECT *
FROM products
WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10;

查詢結果如下:

而被我用紅框標註的行很顯然不是我們需要的行,為什麼會這樣呢?原因在於計算的次序。SQL(像多數語言一樣)在處理 OR 操作符前,優先處理 AND 操作符。當SQL看到上述 WHERE 子句時,它理解為由供應商 1003 製造的任何價格為10美元(含)以上的產品,或者由供應商 1002 製造的任何產品,而不管其價格如何。換句話說,由於 AND 在計算次序中優先級更高,操作符被錯誤地組合了。解決方法就是加 ();正確的 SQL 如下:

SELECT *
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;

建議在任何時候使用具有 AND 和 OR 操作符的 WHERE 子句,都應該使用圓括號明確地分組操作符。不要過分依賴默認計算次序,即使它確實是你想要的東西也是如此。使用圓括號沒有什麼壞處,它能消除歧義。

UNION 組合查詢

  • 利用 UNION ,可給出多條SELECT 語句,將它們的結果組合成單個結果集。
  • UNION 中的每個查詢必須包含相同的列、表達式或聚集函數。
  • 在使用UNION 時,重複的行被自動取消。這是 UNION 的默認行為,但是如果需要,可以改變它。事實上,如果想返回所有匹配行,可使用 UNION ALL 而不是 UNION 。
  • 在用 UNION 組合查詢時,只能使用一條 ORDER BY 子句,它必須出現在最後一條 SELECT 語句之後

INSERT語句總是使用列的列表

一般不要使用沒有明確給出列的列表的 INSERT 語句。使用列的列表能使SQL代碼繼續發揮作用,即使表結構發生了變化。實際開發中有可能由於業務的需要,對錶結構進行修改,添加/刪除某一列。這時如果代碼中使用的SQL語句是沒有明確列表的插入語句就會報錯。當然一般我們使用逆向工程生成的 insertSelective(POJO) 並不存在這個問題,因為它對應生成的 SQL 會為我們生成列的列表。

小心使用更新和刪除語句

MySQL 沒有撤銷按鈕,因此在使用 UPDATE / DELETE 時一定要加上 WHERE 條件,並且在執行更新/刪除操作之前先進行 SELECT 操作,開啟事務。在執行結束后核對影響的行數和 SELECT 查詢出來的行數一致后再 COMMIT;

另外,使用 ALTER TABLE 要極為小心,應該在進行改動前做一個完整的備份(模式和數據的備份)。數據庫表的更改不能撤銷,如果增加了不需要的列,可能不能刪除它們。類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有數據。

視圖的規則和限制

  • 與表一樣,視圖必須唯一命名(不能給視圖取與別的視圖或表相同的名字)。
  • 對於可以創建的視圖數目沒有限制。
  • 為了創建視圖,必須具有足夠的訪問權限。這些限制通常由數據庫管理人員授予。
  • 視圖可以嵌套,即可以利用從其他視圖中檢索數據的查詢來構造一個視圖。
  • ORDER BY 可以用在視圖中,但如果從該視圖檢索數據 SELECT 中也含有 ORDER BY ,那麼該視圖中的 ORDER BY 將被覆蓋。
  • 視圖不能索引,也不能有關聯的觸發器或默認值。
  • 視圖可以和表一起使用。例如,編寫一條聯結表和視圖的 SELECT語句。

其中視圖不能索引這一點要格外注意,在我開發過程中遇到過這樣一個視圖:使用 UNION 連結了好幾張表進行查詢,對查詢的結果使用 WHERE 條件再過濾,這裏雖然對於被連接的表對於 WHERE 條件后的字段都建立了索引,但是使用 UNION 連結生成視圖的臨時表並不能擁有索引。因此查詢的效率會很慢!所以建議在 UNION 查詢中將 WHERE 條件放在每個 SELECT 語句中。

改善性能的一些建議

  • 首先,MySQL(與所有DBMS一樣)具有特定的硬件建議。在學習和研究MySQL時,使用任何舊的計算機作為服務器都可以。但
    對用於生產的服務器來說,應該堅持遵循這些硬件建議。

  • 一般來說,關鍵的生產DBMS應該運行在自己的專用服務器上。

  • MySQL是用一系列的默認設置預先配置的,從這些設置開始通常是很好的。但過一段時間后你可能需要調整內存分配、緩衝區大小等。(為查看當前設置,可使用 SHOW VARIABLES; 和 SHOWSTATUS; )

  • MySQL一個多用戶多線程的DBMS,換言之,它經常同時執行多個任務。如果這些任務中的某一個執行緩慢,則所有請求都會執
    行緩慢。如果你遇到顯著的性能不良,可使用 SHOW PROCESSLIST显示所有活動進程(以及它們的線程ID和執行時間)。你還可以用KILL 命令終結某個特定的進程(使用這個命令需要作為管理員登錄)。

  • 總是有不止一種方法編寫同一條 SELECT 語句。應該試驗聯結、並、子查詢等,找出最佳的方法。

  • 使用 EXPLAIN 語句讓MySQL解釋它將如何執行一條 SELECT 語句。

  • 一般來說,存儲過程執行得比一條一條地執行其中的各條MySQL語句快。但存儲過程一般難以調試和擴展,並且沒有移植性,因此阿里代碼規約裏面強制禁止使用存儲過程

  • 應該總是使用正確的數據類型。

  • 決不要檢索比需求還要多的數據。換言之,不要用 SELECT * (除非你真正需要每個列)。

  • 有的操作(包括 INSERT )支持一個可選的 DELAYED 關鍵字,如果使用它,將把控制立即返回給調用程序,並且一旦有可能就實際執行該操作。

    ​ 延遲插入,當插入和查詢併發執行時,插入被放入等待隊列中。直至所有查詢執行完畢后執行插入。並且MYSQL會在收到插入請求后直接返回給客戶端狀態信息,既是INSERT語句還在隊列中

  • 在導入數據時,應該關閉自動提交。你可能還想刪除索引(包括FULLTEXT 索引),然後在導入完成后再重建它們。

  • 必須索引數據庫表以改善數據檢索的性能。確定索引什麼不是一件微不足道的任務,需要分析使用的 SELECT 語句以找出重複的WHERE 和 ORDER BY 子句。如果一個簡單的 WHERE 子句返回結果所花的時間太長,則可以斷定其中使用的列(或幾個列)就是需要索引的對象。

  • 你的 SELECT 語句中有一系列複雜的 OR 條件嗎?通過使用多條SELECT 語句和連接它們的 UNION 語句,你能看到極大的性能改進。

  • 索引改善數據檢索的性能,但損害數據插入、刪除和更新的性能。如果你有一些表,它們收集數據且不經常被搜索,則在有必要之前不要索引它們。(索引可根據需要添加和刪除。)

  • LIKE 很慢。一般來說,最好是使用 FULLTEXT 而不是 LIKE 。但是 MYSQL FULLTEXT 對漢字並不友好,如果需要使用全文索引,建議使用搜索引擎 ES,可以參考我的另一篇博客: https://www.cnblogs.com/keatsCoder/p/11341835.html

  • 數據庫是不斷變化的實體。一組優化良好的表一會兒后可能就面目全非了。由於表的使用和內容的更改,理想的優化和配置也會改變。

  • 最重要的規則就是,每條規則在某些條件下都會被打破。

實際開發過程中,我們需要根據業務的需要,開啟慢查詢日誌,然後針對慢SQL,不斷地進行 EXPLAIN 與修改SQL和索引,以求達到 ref 級別,至少達到 range 級別。這就需要強大的內功支持而不是每次都通過百度來解決,希望閱讀此篇文章的你和我一起不斷修鍊。加油!

常用的函數

文本處理函數

日期和時間處理函數

數值處理函數

聚合函數

  • 雖然 MAX() 一般用來找出最大的數值或日期值,但MySQL允許將它用來返回任意列中的最大值,包括返迴文本列中的最大值。在用於文本數據時,如果數據按相應的列排序,則 MAX() 返回最後一行。MIN() 函數類似
  • MAX() 函數忽略列值為 NULL 的行。MIN() 函數類似,SUM() 也是

附錄

由於數中所附的附件內容(建表語句即數據插入語句)需要外網才能訪問,為了方便大家使用。這裏我已經下載下來附在了這篇博客裏面。如果不需要這些語句,可以直接通過網頁右邊的目錄跳躍到下一章進行瀏覽

建表語句

########################################
# MySQL Crash Course MYSQL必知必會建表語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


########################
# Create customers table
########################
CREATE TABLE customers
(
  cust_id      int       NOT NULL AUTO_INCREMENT,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL ,
  PRIMARY KEY (cust_id)
) ENGINE=InnoDB;

#########################
# Create orderitems table
#########################
CREATE TABLE orderitems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL ,
  item_price decimal(8,2) NOT NULL ,
  PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;


#####################
# Create orders table
#####################
CREATE TABLE orders
(
  order_num  int      NOT NULL AUTO_INCREMENT,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

#######################
# Create products table
#######################
CREATE TABLE products
(
  prod_id    char(10)      NOT NULL,
  vend_id    int           NOT NULL ,
  prod_name  char(255)     NOT NULL ,
  prod_price decimal(8,2)  NOT NULL ,
  prod_desc  text          NULL ,
  PRIMARY KEY(prod_id)
) ENGINE=InnoDB;

######################
# Create vendors table
######################
CREATE TABLE vendors
(
  vend_id      int      NOT NULL AUTO_INCREMENT,
  vend_name    char(50) NOT NULL ,
  vend_address char(50) NULL ,
  vend_city    char(50) NULL ,
  vend_state   char(5)  NULL ,
  vend_zip     char(10) NULL ,
  vend_country char(50) NULL ,
  PRIMARY KEY (vend_id)
) ENGINE=InnoDB;

###########################
# Create productnotes table
###########################
CREATE TABLE productnotes
(
  note_id    int           NOT NULL AUTO_INCREMENT,
  prod_id    char(10)      NOT NULL,
  note_date datetime       NOT NULL,
  note_text  text          NULL ,
  PRIMARY KEY(note_id),
  FULLTEXT(note_text)
) ENGINE=MyISAM;


#####################
# Define foreign keys
#####################
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);

數據語句

########################################
# MySQL Crash Course MYSQL必知必會數據語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


##########################
# Populate customers table
##########################
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');


########################
# Populate vendors table
########################
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');


#########################
# Populate products table
#########################
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');



#######################
# Populate orders table
#######################
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20005, '2005-09-01', 10001);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20006, '2005-09-12', 10003);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20007, '2005-09-30', 10004);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20008, '2005-10-03', 10005);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20009, '2005-10-08', 10001);


###########################
# Populate orderitems table
###########################
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'ANV01', 10, 5.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'ANV02', 3, 9.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 3, 'TNT2', 5, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 4, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'JP2000', 1, 55);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'TNT2', 100, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'FC', 50, 2.50);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'OL1', 1, 8.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'SLING', 1, 4.49);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 4, 'ANV03', 1, 14.99);

#############################
# Populate productnotes table
#############################
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(101, 'TNT2', '2005-08-17',
'Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(102, 'OL1', '2005-08-18',
'Can shipped full, refills not available.
Need to order new can if refill needed.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(103, 'SAFE', '2005-08-18',
'Safe is combination locked, combination not provided with safe.
This is rarely a problem as safes are typically blown up or dropped by customers.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(104, 'FC', '2005-08-19',
'Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(105, 'TNT2', '2005-08-20',
'Included fuses are short and have been known to detonate too quickly for some customers.
Longer fuses are available (item FU1) and should be recommended.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(106, 'TNT2', '2005-08-22',
'Matches not included, recommend purchase of matches or detonator (item DTNTR).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(107, 'SAFE', '2005-08-23',
'Please note that no returns will be accepted if safe opened using explosives.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(108, 'ANV01', '2005-08-25',
'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(109, 'ANV03', '2005-09-01',
'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(110, 'FC', '2005-09-01',
'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(111, 'SLING', '2005-09-02',
'Shipped unassembled, requires common tools (including oversized hammer).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(112, 'SAFE', '2005-09-02',
'Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(113, 'ANV01', '2005-09-05',
'Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(114, 'SAFE', '2005-09-07',
'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.
Comment forwarded to vendor.'
);

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

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

Hazel,自動整理文件,讓你的 Mac 井井有條

原文地址

讓我們從實際需求出發,看看問題出在哪裡,並在此基礎上認識和學習使用 Hazel。

電腦隨着使用時間的增長,其中的文件也在瘋狂的增長,時間長了也就會出現各種混亂:大量文件堆放在一起,舊文件很少清理,分不清哪些文件還有用,找不到需要的文件等等。

今天我們就以「下載」和「桌面」為例,聊一聊如何整理我們的電腦。

Downloads:下載的文件很少處理,時間一長就各種堆積…… 

Desktop:經常把臨時文件存放在此,方便拖拽使用,但時間一長,就是各種凌亂……

既然知道了問題所在,那麼我們就來着手整理吧。

理清整理思路

首先是確定整理思路,比如如何界定一個文件是否還有用,如何界定它屬於什麼分類等,對應的操作一般是刪除(比如不再需要的或重複的文件)或存檔(學習資料或工作材料等分類存儲),知道如何處理一個文件就很好辦了,剩下的就都是體力活兒。

雖然這不是一件特別麻煩的事,但是我們也經常忘記或「懶得整理」。這有點類似於打掃房間,當我們沒有時間或者經常忘記時,可以買一台掃地機器人幫助我們打掃,同樣的,在 Mac 上也有這樣一台「機器人」,它就是 Hazel。

Hazel 是什麼?

Hazel 是一款可以自動監控並整理文件夾的工具,其官網的介紹就是簡單的一句話:Automated Organization for Your Mac。

它的使用有點類似於網絡服務 IFTTT,你可以設定一個 if 條件,如果被監控的文件夾出現符合條件的項,那麼對其執行 then 的操作(也可以通過郵箱的收件過濾規則來理解)。

Hazel 不是一款新工具,它已經有了很長的歷史,其第一個版本在 2006 年底就已經發布,在今年 5 月 4 號,Hazel 發布了 4.0 版本,新增了規則同步(文末會有介紹)、規則搜索等一系列實用功能。

Hazel 具體能做什麼?

先為大家簡單羅列一些 Hazel 能做到的事情:

  • 根據文件創建的時間,自動將文件進行顏色標記(比如將最近的文件標記為藍色)
  • 自動的用特定軟件打開某個特定文件(比如下載 BT 種子后,自動用迅雷打開下載)
  • 自動刪除已下載過的 BT 種子文件
  • 根據文件的類型,自動轉移到相應的文件夾中(比如圖片移動到照片文件夾,電影移動到視頻文件夾等)
  • 自動刪除某些特定文件(比如標題中含有固定內容且創建日期在很早以前的)
  • 自動將壓縮文件解壓
  • 自動幫你清理文件的緩存
  • 自動幫你整理照片,可以按照「年 – 月」來分類存儲到相應文件夾
  • 自動把文件夾中的內容上傳到 FTP 等網絡服務中
  • 自動將照片導入 Photos,自動將音樂導入 iTunes 
  • ……

以上只是列舉的一些場景能夠實現的功能,再加上 Hazel 支持 AppleScript、JavaScript、Automator workflow 等代碼指令,令其擴展性更上一層樓,可以做到的事情也可以說只剩下想象力這道門檻了。

介紹了不少,下面我們就從 Hazel 的安裝和實際設置來為大家做一個簡單的入門指南。

Hazel 的安裝

前往下載最新版本,按照提示安裝,完成后 Hazel 會出現在系統設置中(在應用程序中可找不到哦)。
Hazel 是一款收費軟件,初次安裝后可以免費試用 14 天,此時可以選擇加載一些簡單的默認規則以幫助你快速上手(當然看完這篇文章也就可以不用加載了)。

操作后 Hazel 會給我們彈出警告信息:在激活這些規則之前,一定要先檢查它們。具體的方法下面會提及。

Hazel 的界面和基礎應用

注:文末提供了文中所有 Hazel 規則的打包下載地址,如果你對文中介紹的規則感興趣,可以直接下載使用。

Hazel 的主界面包含三部分,分別是設置文件夾規則的 Folders 頁面,設置垃圾箱規則的 Trash 頁面和其他信息頁(Info),今天主要給大家講解文件夾規則設置頁面。

在 Folders 中包含三部份:設置監控的文件夾(圖中 1),設置該文件夾下的具體規則(圖中 2),設置該文件夾的重複文件處理(圖中 3),圖 1 部分右側的 icon 分別表示「暫停規則執行」和「同步」,建議嘗試新規則的時候先暫停執行再進行調試。

以整理「下載」文件夾為例,我個人的需求有如下幾條:

  • 最近的下載文件用顏色標籤提醒
  • 超過 3 天的文件不再是新文件,去掉顏色標籤
  • 對存放超過 3 周的文件需進行處理,將滿足此條件的文件用紅色標記提醒
  • 自動刪除已使用的 .torrent 文件
  • 將手機截屏的圖片單獨存放

上面幾條是梳理自己的整理需求后,選擇的可以被 Hazel 自動執行的。此時回到 Hazel,我們點擊左下角的加號新增「下載」文件夾,隨後在右側 Rules 區域點擊加號新增規則。

標記最新下載文件

下圖是規則設置界面,圖 1 部分設置規則名稱和註釋;圖 2 部分設置監控條件,此時設置的是文件添加時間在最後匹配時間之前(新文件添加后暫未被匹配,所以一定是早於匹配時間);圖 3 部分設置執行的動作,此時是將匹配出來的文件標記藍色標籤,並且同時可以被其他規則匹配。

標記舊文件

超過 3 天的文件,不再是我需要關注的內容,將其中的藍色標籤去掉:

標記待處理文件

對「下載」文件夾,我需要對超過 3 周未處理的文件進行處理,要麼歸檔要麼刪除,需要進行人工判斷的時候我使用紅色標記來提醒自己:

刪除 .torrent 文件

在使用 BT 下載之後,留在文件夾的種子文件也就沒有什麼用了,為了防止誤刪設置了 5 天的期限,注意圖中綠色符號,那是點擊了 Preview 后的效果,建議設置規則的時候多使用 Preview 功能來檢查條件設置是否正確,特別是那些複雜的符合條件。

自動移動手機截屏文件

工作關係,經常需要在手機上截屏上傳到電腦使用(使用 AirDrop 上傳到「下載」中),這類圖片的處理一般是超過一周后移動到桌面文件夾中再進行集中處理:

上面介紹了「下載」文件夾的整理思路和執行;對於「桌面」文件夾的整理,我的思路一般是不輕易自動刪除(防誤刪),而是統一到分類文件夾中集中處理。將文檔存放於「文檔」中,將圖片存放於「圖片」中等等,都是非常簡單和基礎的設置,就不做過多介紹;

下面說一下我對源文件的處理,這裏涉及到條件的嵌套使用:

圖中使用了嵌套條件,具體的操作是鼠標長按右側加號(也可按住 Option 後點擊),即可增加嵌套條件組。

附上桌面整理后截圖:

Hazel 中級應用

除了以上的基礎使用,Hazel 還可作用於更加廣泛的場景,下面以自動解壓自動清理緩存為例。

自動解壓

下載壓縮包后不用手動解壓,Hazel 會自動創建文件夾(按照壓縮包的名稱命名),並將壓縮包和解壓后的文件存放於此:

有三點需要為大家說明:

  • 設置標籤是為了防止壓縮文件有損壞而導致 Hazel 陷入循環執行中;
  • 不能設置自動刪除,因為 Hazel 會自動選中解壓后的文件,此時的刪除也只是把解壓后的文件刪掉;
  • 使用默認的「Unarchive」操作也可解壓,不過在解壓 .zip 文件後會自動將壓縮包刪掉,所以我這裏使用了第三方的免費解壓軟件  代替(注意:在第一次執行時需要權限設置);不介意刪除壓縮包的同學使用默認的解壓操作即可。

此規則參考了  的博客,特此感謝。

自動清理緩存

以 QQ 為例,QQ 會把群消息中的圖片自動保存到本地,時間一長這個文件夾就很容易達到幾個 G 的大小,這時候 Hazel 又可以派上用處了。

首先找到你的 QQ 文件夾,可嘗試如下路徑(本人 Mac 系統 10.11)

/Users/用戶名/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ

將路徑中的「用戶名」換成自己的,然後在 Finder 中按住「⌘ + Shift + G」,把路徑粘貼到輸入框中點擊「前往」即可。

如果路徑沒問題,就可以在 Hazel 中添加此文件夾了,點擊添加按鈕彈出選擇文件夾界面后,使用上述快捷鍵和路徑同樣可以快速選定,添加後設置如下兩條規則,第一條規則的作用是讓所有子文件夾都可以適配規則並執行操作;第二條規則是把超過 500M 的子文件夾進行刪除操作,且不會直接刪除父文件夾。

至此,QQ 緩存文件的自動清理就設置完成了,其他軟件緩存也可以進行類似的規則設計,不過一定要注意確保這裏面沒有你需要的文件,否則一旦刪除要找回也是頗為麻煩的。

更多用法

如前文所說,Hazel 能做到的不止這些場景,還有用戶用它來整理照片,利用 AppleScript 執行更加複雜的工作流程等等,這裏僅當作拋磚引玉,歡迎大家分享自己的用法,並且以後也會有更多關於 Hazel 使用技巧的文章。 

其他功能

管理垃圾箱

在 Hazel 的 Trash 頁面,可以進行一些垃圾箱的設置,比如將其中超過一周的文件刪除,保持垃圾箱大小控制在 2GB 左右,選擇刪除時是否使用安全刪除功能,以及卸載應用時檢測其附屬文件夾等等;這方面的功能筆者並不常用,在此不做過多介紹。

刪除應用時檢測相關文件,並可選擇一併刪除。作用類似於 。

同步規則

同步功能在 4.0 終於推出,現在也可以方便的使用在多台電腦上了。點擊左側面板中的齒輪圖標,選擇 Rule Sync Options 即可打開同步界面(也可在文件夾上右鍵選擇 Rule Sync Options)。

同步需要配合第三方同步網盤使用,當前文件夾若是第一次使用同步,需要設置同步文件存放路徑,點擊 Set up new sync file 即可。如果要使用同步的文件,在界面中點擊 Use existing sync file 即可。

Hazel 的下載

Hazel 是一款收費軟件(),五月初的時候發布了 4.0 版本,單獨購買是 $32,Family Pack $49,從 3.0 版本升級需要 $10。初次下載可以免費試用 14 天,建議大家先試用再購買。

最後給大家提供我自己的 Hazel 設置,你可以導入后調整為適合自己的規則再使用:。

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

【其他文章推薦】

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

『嗨威說』算法設計與分析 – PTA 程序存儲問題 / 刪數問題 / 最優合併問題(第四章上機實踐報告)

本文索引目錄:

一、PTA實驗報告題1 : 程序存儲問題

  1.1  實踐題目

  1.2  問題描述

  1.3  算法描述

  1.4  算法時間及空間複雜度分析

二、PTA實驗報告題2 : 刪數問題

  2.1  實踐題目

  2.2  問題描述

  2.3  算法描述

  2.4  算法時間及空間複雜度分析

三、PTA實驗報告題3 : 最優合併問題

  3.1  實踐題目

  3.2  問題描述

  3.3  算法描述

  3.4  算法時間及空間複雜度分析

四、實驗心得體會(實踐收穫及疑惑)

 

 

一、PTA實驗報告題1 : 程序存儲問題

  1.1  實踐題目:

 

  1.2  問題描述:

      題意是,題干給定磁盤總容量和各個文件的佔用空間,詢問該磁盤最多能裝幾個文件。

 

  1.3  算法描述:

      簽到題,只需要將各個文件從小到大排序,並拿一個變量存儲已佔用的容量總和,進行對比即可得到結果。

#include<bits/stdc++.h>
#include<algorithm>
using namespace std;
#define MAXLENGTH 1000
int interger[MAXLENGTH];
int main()
{
    int num,length;
    int sum = 0;
    int counter = 0;
    int m = 0;
    cin>>num>>length;
    for(int i=0;i<num;i++){
        cin>>interger[i];
    }
    sort(interger,interger+num);
    while(true){
        if(sum+interger[m]>length||counter==num)
            break;
        sum+=interger[m];
        counter++;
        m++;
    }
    cout<<counter<<endl;
    return 0;
 } 

 

  1.4  算法時間及空間複雜度分析:

     整體算法上看,輸入需要O(n)的時間進行輸入,最快用O(nlogn)的時間複雜度進行排序,使用O(n)的時間進行結果疊加,總時間複雜度為O(nlogn),時間複雜度花費在排序上。

    空間上,只需要一個臨時變量存儲當前佔用容量總和即可。

 

 

二、PTA實驗報告題2 : 刪數問題

  2.1  實踐題目:

 

  2.2  問題描述:

    第二題題意是指,在給定的数字串以及可刪數個數的條件下,刪數指定k個數,得到的數是最小的。

 

  2.3  算法描述:

    首先,分析題目,刪數問題,可以用一個比較方便的函數,String類的erase函數,這個函數可以刪除字符串內的單個或多個字符,可以比較方便的處理刪數問題。

    第二,我們注意到這道題有個坑點,那就是前導零,我們需要注意100000,刪除1后結果應為0

    第三,確定我們的貪心策略:噹噹前的數,比后一位數大時,刪去當前的數。

    如:樣例178543

    用一個index時刻從頭往後掃,不滿足就后移。

 

     當滿足之後,刪除當前的值。

 

    得到17543,這時將index重新置0,並記錄已刪數+1,直到滿足最大刪數。以此類推,直接輸出string便是結果。

    AC代碼:

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
#define MAXLENGTH 1005
int main(){
    int k;
    string a;
    cin>>a>>k;
    int len = a.size();
    while(k>0){
        for(int i = 0;(i<a.size()-1);i++){
            if(a[i]>a[i+1])
            {
                a.erase(i,1);
                break;
            }
        }
        k--;
    }
    while(a.size()>1&&a[0]=='0'){
        a.erase(0,1);
    }
    cout<<a<<endl;
    return 0;
}

 

  2.4  算法時間及空間複雜度分析:

    時間複雜度為O(n^2),即開銷在不斷的刪數和回溯到字符串頭的過程。

    空間複雜度需要一個String字符串長度,因此空間複雜度是O(n)

 

 

三、PTA實驗報告題3 : 最優合併問題

  3.1  實踐題目:

 

  3.2  問題描述:

    該題目為:題目用 2 路合併算法將這k 個序列合併成一個序列,並且合併 2 個長度分別為m和n的序列需要m+n-1 次比較,輸出某段合併的最大比較次數和最小比較次數。

 

  3.3  算法描述:

    這道題算是哈夫曼算法的一道裸題,很容易解決,只需要使用優秀隊列不斷維護最小值或最大值即可。

    哈夫曼樹:是一顆最優二叉樹。給定n個權值作為n個恭弘=叶 恭弘子的結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,這棵樹則被稱為哈夫曼樹。

    因此本題根據哈夫曼算法,我們以最小比較次數為例:

 

 

     首先從隊列中選出兩個最小的數進行合併,並在隊列中刪除這兩個數,並將新合成數加入隊列中。

 

 

     再取最小的兩個數再進行合併,以此類推,得到最終的大數如下

    因此最小比較次數為:( 7 – 1 ) + ( 18 – 1 ) + ( 30 – 1 ) =  52,即為所得。最大比較次數也是同理。

   AC代碼如下:

#include<bits/stdc++.h>
using namespace std;
priority_queue<int> Haff;
priority_queue<int, vector<int>, greater<int> > Haff2;
int n,ans1,ans2;

int main()
{
    cin>>n;
    for(int i = 0;i<n;i++)
    {
        int temp;
        cin>>temp;
        Haff.push(temp);
        Haff2.push(temp);
    }

    while(1)
    {
        if(Haff.size() == 1)
            break;
        int temp1 = Haff.top();
        Haff.pop();
        int temp2 = Haff.top();
        Haff.pop();
        Haff.push(temp1+temp2);
        ans1 += temp1+temp2-1;
    }
    
    while(1)
    {
        if(Haff2.size() == 1)
            break;
        int temp1 = Haff2.top();
        Haff2.pop();
        int temp2 = Haff2.top();
        Haff2.pop();
        Haff2.push(temp1+temp2);
        ans2 += temp1+temp2-1;
    }
    cout<<ans1<<" "<<ans2;
    return 0;
 } 

 

  3.4  算法時間及空間複雜度分析:

    由分析易知,雖然進行了兩次優先隊列維護,但是總的時間複雜度數量級是不變的,用O(n/2)的時間pop和push合成樹。在優先隊列裏面用紅黑樹對順序進行維護,時間複雜度為O(nlogn),最後將統計的結果輸出,總的時間複雜度為O(nlogn)。

   空間複雜度為兩棵紅黑樹,即T(2n) = O(n)。

 

 

四、實驗心得體會(實踐收穫及疑惑):

    經過動態規劃的肆虐之後,貪心算法變得稍微容易很多,和三木小哥哥的合作很愉快,能夠很好較快及時的解決三道實踐問題,暫無太多的問題,繼續加油。

 

 

如有錯誤不當之處,煩請指正。

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

【其他文章推薦】

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

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

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

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

北汽新能源全新戰略:十三五末期產能達80萬輛以上

在產業佈局方面,北汽新能源將以“1(北京采育基地)+2(常州基地、青島基地)+I(北汽集團內部傳統乘用車生產基地)+P(社會合作夥伴生產資源)>80”為基礎,在十三五末期形成80萬輛以上生產能力,年產銷50萬輛規模,打造年營業收入600億元、上市市值1000億元的企業。

在研發方面,北汽新能源將推進“1496”戰略,1:打造1個世界級科技創新中心,具備正向開發能力;4:構建4層次研發體系;9:在研發總部建成9大研發中心;5:整合全球資源,組建5大海外研發中心。此外,在北汽新能源的目標中,其還將爭創新能源汽車行業最佳雇主品牌,價值鏈研發團隊人數達到5000以上,專業領域國際級人才占比達到10%以上,經營團隊中具有國際視野的複合型人才占比60%以上。

產品方面,北汽新能源將構建3大技術平臺(共用平臺、協同平臺、全新平臺),同樣將實現3大維度(大中小、高中低、234)全面發展,打造三款年銷量突破10萬輛的明星車型。基於國內當前新能源汽車市場發展特點,北汽新能源短期內聚焦A級以下車型,並根據市場發展適時啟動B、C級高端車型儲備開發。

今年,北汽新能源將推出首款純電動6萬元高性價比國民純電動車也將在2016年迎來投放,實現不靠地方補貼國內市場全覆蓋。2017年,公司將推出採用全鋁框架超輕量化車身設計的EX系列純電動精品微型小車。

根據規劃,北汽新能源未來每年將研發4-6款新車型,形成“2、3、4”(即續航里程200公里、300公里、400公里)、“高、中、低”(即高檔、中檔、標配三個級別)、“大、中、小”(即車體大小)完整新能源汽車產品組合,為消費者提供更加豐富的選擇。

密集的產品投放加上車型續航能力不斷升級,這些利好都將為北汽新能源持續保持國內純電動市場第一、全球前四的發展目標奠定堅實基礎。而在擴張產能的同時,有效利用起北汽集團以及合作夥伴的資源,則可以大幅縮減開支,獲得更高的利潤。

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

【其他文章推薦】

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

BYD:電動車銷量目標連三年翻倍

比亞迪(BYD)董事長兼總裁王傳福在接受中國媒體《財新網》訪談時表示,BYD看好電動車市場的發展,未來三年的銷售目標是逐年倍增,且到2020年時,電動車將取代傳統汽車成為汽車銷售的主力。

去年全球電動車市場火熱,中國市場的銷量提升了三倍,躍升為全球最大電動車市場。BYD的電動車也跟著成長了三倍,產值來到人民幣(下同)220億元。BYD同時發展私家車、電動巴士、電動卡車與電動計程車等產品,屬多角化經營;目前,全球已有160餘座城市看得到BYD電動公車的身影,包含倫敦、阿姆斯特丹、洛杉磯、京都與50多個中國城市。

王傳福指出,特斯拉(Tesla)專注於生產高端私家電動汽車,此一路線與BYD的多角化經營有所不同,因此不會出現直接競爭。此外,BYD會逐步減少傳統汽車的投資比例,未來將逐漸轉為以電動車為主。

王傳福認為,北京政府對電動車的補助確實有助於銷售成長,但中國電動車市場已具一定的規模效應,生產成本會繼續下降。加上民眾對電動車的認同度持續上升,因此即使未來補貼逐漸減少,電動車的銷量也不會受到太大的衝擊。

據統計,BYD的電動車銷售量在2015年12月來到1.09萬輛,月增41.4%。全年銷量6.17萬輛,已超越Nissan、Tesla成為全球電動車銷售冠軍。其中以插電式油電混和車「秦」與「唐」的銷量貢獻最多,達86%;其餘銷量則來自純電動車。

(照片來源:BYD)

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

【其他文章推薦】

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

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

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

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

上海推出新能源汽車充電樁綜合保險

近期,上海保險業與國家電網積極合作,推出充電樁綜合保險產品。該產品包括充電樁財產保險和充電樁用電安全責任保險。充電樁財產保險保額最高1萬元,充電樁用電安全責任保險保額3萬元。

合作初期,上海相關險企採取贈送方式,建立專項資金池,向新能源車主贈送推廣充電樁保險。同時也在國家電網營業廳設點,向預報樁客戶宣傳介紹充電樁綜合保險的相關產品和服務。

據瞭解,2015年7月1日起,上海正式實施《上海市電動汽車充電基礎設施建設管理暫行規定》。7月1日後,上海市民必須提供政府備案的充電服務機構出具的“充電設施已裝證明”,才能在銷售企業辦理購買新能源汽車手續,必須安裝好充電樁後才能獲得國家和地方政府補貼、並申請到新能源車免費牌照。此外,《規定》也明確充電設施所有權人應當承擔充電設施維修更新養護及侵害第三者權益責任。

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

【其他文章推薦】

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

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

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

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

Jetpack Compse 實戰 —— 全新的開發體驗

公眾號回復 Compose 獲取安裝包

項目地址:

經過前段時間的 Android Dev Summit ,相信你已經大概了解了 Jetpack Compose 。如果你還沒有聽說過,可以閱讀這篇文章 。總而言之,Compose 是一個 顛覆性聲明式 UI 框架 ,它的口號就是 消滅 xml 文件 !

儘管 Jetpack Compose 還只是預覽版,API 可能發生變化,缺乏足夠的控件支持,甚至不是那麼穩定,但這阻止不了我這顆好奇的心。我在第一時間就上手擼了一款 Compose 版本 Wanandroid 應用,功能也比較簡單,僅僅包括首頁,廣告和最新項目,類似於 Android 原生頁面的 Viewpager + TabLayout 。下面的 gif 展示了應用的基本頁面:

可以看出來頁面並不是那麼流暢,View 的復用應該是個問題,甚至我也沒發現應該怎麼做下拉刷新。那麼,Compose 給我們帶來了什麼呢?在解答這個問題之前,我想先來說說 Android 應用架構問題。

荒蕪年代 —— MVC

在我剛入行的時候,可以說是 Android 開發的黃金時代,也可以說是開發者的荒蕪時代。一方面,毫不誇張的說,基本會寫 xml 都能謀得一份工作。另一方面,對於開發者來說,遠遠沒有現在的規範的開發架構。沒記錯的話,我當年的主力開發框架是 xUtils 2 ,一個類庫大包干,從 布局和 ID 綁定,網絡請求,到圖片展示,ORM 操作,一應俱全。當時的 布局和 ID 綁定還是運行時反射,而不是編譯期註解。很長一段時間以來,Android 連一個官方的網絡庫都沒有。

在架構方面,很多人都是一個 Activity 擼到死,我真的見過上千行zouguolai的 MainActivity 。並且覺得這就是 MVC 架構,實體類 Entity 就是 Model 層,Activity/Fragment 就是 Controller 層,布局文件就是 View 層。

但這當真是 MVC 嗎?其實並不是。不管是 MVCMVP,還是 MVVM,都應該遵循一個最起碼的原則,表現層和業務層分離 ,也就是 Android 官網給出的 中強調的 分離關注點Activity/Fragment 既要承擔視圖層的任務,展示和更新 UI,又要處理業務層邏輯,獲取數據等。這並不符合架構設計的基本原則。

正確的 MVC 模式中,Model 層不僅包含實體類 Entity,更重要的作用是處理業務邏輯。View 層負責處理視圖邏輯。而 Controller 就是 Model 和 View 之間的橋樑。橋怎麼建,其實並沒有標準,根據你自己的需求就可以了。

引用一張阮一峰老師的圖,大致是這麼個意思,但是也不一定就完全都是單向依賴。

從荒蕪時代走過來,MVC 總算有點分層的味道在裏面了,分離了視圖層和業務層。但是 View 層和 Model 層的依賴關係,造成代碼耦合,終將導致 Activity 日益臃腫。那麼有沒有辦法將 View 層和 Model 層徹底分離,做到視圖層和模型層完全分離呢? MVP 就應運而生了。

青銅年代 —— MVP

依舊是阮一峰老師的圖片:

相較於 MVC ,MVP 用 Presenter 層 代替了 Controller 層 ,且 View 層Model 層 完全分離,依靠 Presenter 進行通信 。

想象一個獲取用戶信息的場景。IView 接口中定義了一系列視圖層接口 ,View 層(Activity)實現 IView 接口中相應視圖邏輯。 View 層通過持有的 Presenter 處理業務邏輯,即請求用戶信息。一般情況下,Presenter 也不直接處理業務邏輯,而是通過 Model 層,例如數據倉庫 Repository, 來獲取數據,避免 Presenter 重蹈覆轍,日漸臃腫。同時,Presenter 層也是持有 VIew 的,獲取用戶信息之後再轉發給 View 。

總結一下,MVP 中 View 和 Model 完全解耦,通過 Presenter 通信。View 和 Presenter 共同處理視圖層邏輯,Model 層負責業務邏輯。

在 Github 上 Android 官方的架構示例 中 MVP 作為主分支堅挺了很久。我最初也是根據這個官方示例改造了自己的 MVP 架構,並且使用了很長時間。但是 MVP 作為一款面向接口編程的架構,隨着業務的複雜程度不斷加大,有種遍地都是接口的既視感,實在顯得有點繁瑣。

另外一點,Presenter 的職責邊界不夠清晰,它除了承擔調用 Model 層獲取業務邏輯之外,還要控制 View 層處理 UI。用下面一段代碼錶示一下:

class LoginPresenter(private val mView: LoginContract.View) : LoginContract.Presenter {

    ......

    override fun login(userName: String, passWord: String) {
        CoroutineScope(Dispatchers.Main).launch {
            val result = WanRetrofitClient.service.login(userName, passWord).await()
            with(result) {
                if (errorCode == -1)  mView.loginError(errorMsg) else mView.login(data)
            }
        }
    }
}

一旦 View 層發生任何變化,Presenter 層也要做出相應改動。雖然 View 和 Model 之間解耦了,但是 View 和 Presenter 卻耦合了。理想情況下,Presenter 層應該僅負責數據的獲取,View 層自動觀察數據的變化。於是,MVVM 來了。

黃金時代 —— MVVM

Google 官圖鎮樓 。

MVP 風光早已不在, Android 官方的架構示例 的主分支已經切換到 MVVM 。在 Android 的 MVVM 架構中,ViewModel 是重中之重,它一方面通過數據倉庫 Repository 獲取數據,另一方面根據獲取的數據更新 View 層的 Activity/Fragment。等等,這句話怎麼聽着這麼耳熟,Presenter 不也是幹了這些事嗎?的確,它們乾的事情都差不多,但是實現上完全不一樣。

以我的開源項目 中的 LoginViewModel 為例:

class LoginViewModel(val repository: LoginRepository) : BaseViewModel() {

    private val _uiState = MutableLiveData<LoginUiModel>()
    val uiState: LiveData<LoginUiModel>
        get() = _uiState


    fun loginDataChanged(userName: String, passWord: String) {
        emitUiState(enableLoginButton = isInputValid(userName, passWord))
    }

    // ViewModel 只處理視圖邏輯,數據倉庫 Repository 負責業務邏輯
    fun login(userName: String, passWord: String) {
        viewModelScope.launch(Dispatchers.Default) {
            if (userName.isBlank() || passWord.isBlank()) return@launch

            withContext(Dispatchers.Main) { showLoading() }

            val result = repository.login(userName, passWord)

            withContext(Dispatchers.Main) {
                if (result is Result.Success) {
                    emitUiState(showSuccess = result.data,enableLoginButton = true)
                } else if (result is Result.Error) {
                    emitUiState(showError = result.exception.message,enableLoginButton = true)
                }
            }
        }
    }

    private fun showLoading() {
        emitUiState(true)
    }

    private fun emitUiState(
            showProgress: Boolean = false,
            showError: String? = null,
            showSuccess: User? = null,
            enableLoginButton: Boolean = false,
            needLogin: Boolean = false
    ) {
        val uiModel = LoginUiModel(showProgress, showError, showSuccess, enableLoginButton,needLogin)
        _uiState.value = uiModel
    }

    data class LoginUiModel(
            val showProgress: Boolean,
            val showError: String?,
            val showSuccess: User?,
            val enableLoginButton: Boolean,
            val needLogin:Boolean
    )
}

可以看到,ViewModel 中是沒有 View 的引用的,View 通過可觀察的 LIveData 來觀察數據變化,基於觀察者模式做到和 ViewModel 完全解耦。

數據驅動視圖 ,這是 Jetpack MVVM 推崇的一個重要原則。其基本數據流如下所示 :

  • 數據層 Repository 負責從不同數據源獲取和整合數據,基本負責所有的業務邏輯
  • ViewModel 持有 Repository,獲取數據並驅動 View 層更新
  • View 持有 ViewModel,觀察 LiveData 攜帶的數據,數據驅動 UI

曾經和一些開發者討論過這樣一個問題,** 不使用 DataBinding 還算是 MVVM 嗎 ?** 我認為 MVVM 的核心從來不在於 DataBinding 。DataBinding 只是可以幫助我們將 數據驅動視圖 做到極致,順便還可以雙向綁定。

要說到對 Jetpack MVVM 中最不滿意的一塊,那非 DataBinding 莫屬了。在我狹隘的認為 DataBinding 就是一個在 xml 裏面寫邏輯代碼的反人類的庫時,我是堅決反對在任何項目中引入它的。固執己見的時候就容易走進誤區,在閱讀 KunminX 的 之後,正如這篇文章名字一樣,真香。

香的確是香,一切能讓我早下班的都是好東西。在我的某次提交日誌上,我寫下了 消滅 Adapter 幾個字,那時我剛用 DataBinding 消滅了大部分 RecyclerView 的 Adapter 。可是在提交之後,我的良心惴惴不安,我追究還是在 xml 文件里寫邏輯代碼了,難道這真的不反人類嗎?

未來可期 —— Jetpack Compose

現在你應該可以理解我對 Jetpack Compose 的執念了。拋去其他特性,在我看來,它完美的解決了 數據驅動視圖 的問題,我再也不需要使用 DataBinding 了。

簡單代碼展示一下 Compose 的用法。下面的代碼描繪的是首頁 Tab 下的文章列表。

@Composable
fun MainTab(articleUiModel: ArticleViewModel.ArticleUiModel?) {

    VerticalScroller {
        FlexColumn {
            inflexible {
                HeightSpacer(height = 16.dp)
            }
            flexible(1f) {
                articleUiModel?.showSuccess?.datas?.forEach {
                    ArticleItem(article = it)
                }

                articleUiModel?.showError?.let { toast(App.CONTEXT, it) }
wenjian
                articleUiModel?.showLoading?.let { Progress() }
            }
        }
    }
}

這種寫法叫做 聲明式編程 ,會用 Flutter 的同學應該很熟悉。方法參數 ArticleUiModel 就是數據實體類,直接根據數據 ArticleUiModel 構建 UI 。說的大白話一點,就是給你長方形的長和寬了,讓你畫個長方形出來。最後加上 @Compose 註解,就是一個可用的 UI 組件了。仔細看代碼,裏面還用了兩個 UI 組件 ,ArticleItemProgress ,代碼就不貼出來了。分別是文章列表的 item 項目 和加載進度條。

那麼,數據如何更新呢?最簡單的方式是使用 @Model 註解。

@Model
data class ArticleUiModel(){
  ......
}

對,就是這麼簡單。@Model 註解會自動把你的數據類變成可觀察對象,只要 ArticleUIModel 發生變化,UI 就會自動更新。

但是我在實際開發中結合 LiveData 使用時,好像表現的不是那麼正常。後來在 Medium 上無意中看到了解決方案,針對 LiveData 做了特殊處理 :

// general purpose observe effect. this will likely be provided by LiveData. effect API for
// compose will also simplify soon.
fun <T> observe(data: LiveData<T>) = effectOf<T?> {
    val result = +state<T?> { data.value }
    val observer = +memo { Observer<T> { result.value = it } }

    +onCommit(data) {
        data.observeForever(observer)
        onDispose { data.removeObserver(observer) }
    }

    result.value
}wenjian

在 Activity/Fragment 中觀測 LiveData 即可:

class MainActivity : BaseVMActivity<ArticleViewModel>() {

    override fun initView() {
        setContent {
           +observe(mViewModel.uiState)
            WanandroidApp(mViewModel)
        }
    }

    override fun initData() {
       mViewModel.getHomeArticleList()
    }
}

這樣 View 層就可以自動觀察 LiveData 所包含的值了。

沒有 xml,沒有 DataBinding,一切看起來稱心如意多了。但就是 UI 體驗有那麼一點糟心,你可以在公眾號後台回復 Compose 安裝體驗一下。由於還是早期的預覽版,這也是可以理解的。我相信,等到發布 Release 版本的時候,一定足以完全代替原聲的 View 體系。

本文並沒有詳細介紹 Jetpack Compose 的詳細使用過程和其他特性,更多信息我推薦下面兩篇文章:

最後

正如 Android 官網 Jetpack 介紹頁所說,Jetpack 可以幫助開發者更輕鬆的編寫優質應用。的確,隨着應用架構的規範,我們只需要把精力放在需要的代碼上,加速開發,消除樣板代碼,減少崩潰和內存泄露,構建高質量的強大應用。我想不出來有任何理由不使用 Jetpack 來構建你的應用。而 Compose 必將稱為 Jetpack 中極其重要的一塊拼圖。

Jetpack Compse ,未來可期 !

添加我的微信,加入技術交流群。

公眾號後台回復 “compose”, 獲取最新安裝包。

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

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

必知必會-存儲器層次結構

相信大家一定都用過各種存儲技術,比如mysql,mongodb,redis,mq等,這些存儲服務性能有非常大的區別,其中之一就是底層使用的存儲設備不同。作為一個程序員,你需要理解存儲器的層次結構,這樣才能對程序的性能差別瞭然於心。今天帶大家了解下計算機系統存儲器的層次結構。

存儲技術

首先了解下什麼是存儲器系統?

實質上就是一個具有不同容量、成本和訪問時間的存儲設備的層次結構。從快到慢依次為:CPU寄存器、高速緩存、主存、磁盤;

這裏給大家介紹一組數據,讓大家有一個更清晰的認識:

如果數據存儲在CPU寄存器,需要0個時鐘周期就能訪問到,存儲在高速緩存中需要4~75個時鐘周期。如果存儲在主存需要上百個周期,而如果存儲在磁盤上,大約需要幾千萬個周期! — 出自 CSAPP

接下來一起深入了解下計算機系統涉及的幾個存儲設備:

隨機訪問存儲器

隨機訪問存儲器(RAM)分為靜態RAM (SRAM) 和動態RAM(DRAM)。SRAM的速度更快,但也貴很多,一般不會超過幾兆字節,通常用來做告訴緩存存儲器。DRAM就是就是我們常說的主存。

訪問主存

數據流是通過操作系統中的總線的共享电子電路在處理器和DRAM之間來來回回。每次CPU和主存之間的數據傳送都是通過一系列複雜的步驟完成,這些步驟成為總線事務。讀事務是將主存傳送數據到CPU。寫事務從CPU傳送數據到主存。

總線是一組并行的導線,能攜帶地址、數據和控制信號。下圖展示了CPU芯片是如何與主存DRAM連接的。

那麼我們在加載數據和存儲數據時,CPU和主存到底是怎樣交互實現的呢?

首先來看一個基本指令,加載內存數據到CPU寄存器中:

movq A,%rax

將地址A的內容加載到寄存器%rax中,這個命令會使CPU芯片上稱為總線接口(bus interface)的電路在總線上發起讀事務,具體分為三個步驟:

  1. CPU將地址A放到系統總線上,I/O橋將信號傳遞到內存總線。詳情看下下圖a
  2. 主存感覺到內存總線上的地址信號,從內存總線讀地址,從DRAM取出數據字,將其寫到內存總線。I/O橋將內存總線信號翻譯成系統總線信號,沿着系統總線傳遞到CPU總線接口。下圖b
  3. CPU感覺到系統總線上的數據,從總線上讀數據,並將數據複製到寄存器%rax。下圖c

隨機訪問存儲器,有個缺點是當斷電后,DRAM和SRAM會丟失它們的信息,因此為易失性存儲。

磁盤存儲

磁盤是廣為使用的保存大量數據的存儲設備,目前我們家用電腦,動輒也都是1T的。它相比於基於RAM的只有幾百或幾千兆字節的存儲器來說,雖然大但是讀寫性能差。時間為毫秒級,比DRAM讀慢了10萬倍,比SRAM慢了100萬倍。

磁盤構造

磁盤是由盤片構成的。每個盤片有兩面。表面覆蓋著磁性記錄材料。盤片中央是一個可以旋轉的主軸(spindle),它使盤片可以以固定的速率旋轉,通常是5400~15000轉每分鐘,磁盤通常包含多個盤片,密封在一個容器內。

如上圖,我們可以看到,表面被劃分為很多同心圓,稱為磁道。磁道又被劃分為很多扇區,每個扇區具有相同的數據位(通常512字節)。扇區之間有間隙隔開,用來存儲標識扇區的格式化位。

多個盤片封裝在一起到一個容器中,就是我們平時用的硬盤,稱為磁盤驅動器。

磁盤容量

容量很好理解,就是磁盤一共可以存儲的數據位。根據磁盤的構造,我們得出磁盤的容量由下面因素決定:

  • 記錄密度(recording density,位/英寸):磁道一英寸可以放入的位數。
  • 磁道密度(track density,道/英寸):從中心主軸向外的半徑上,一英寸可以有多少磁道。
  • 面密度(areal density,位/平方英寸):記錄密度與磁道密度的乘積。

通過上面的了解,增加磁盤容量其實就是增加面密度,近些年面密度每隔幾年就會翻倍。下面大家可以看一下這個磁盤容量的計算公式:

磁盤容量=字節數/扇區 * 平均扇區數/磁道 * 磁道數/表面 * 表面數/盤片 * 盤片數/磁盤

結合一個例子方便各位理解:

假如我們有一個磁盤,有5個盤片,每個扇區512字節,沒個面20000條磁道,每條磁道 300 個扇區,那麼容量計算為:

磁盤容量 = 512 * 300 * 20000 * 2 * 5 = 30720000000字節=30.72G

磁盤操作

磁盤讀寫操作靠的是讀寫頭來讀寫存儲在磁性表面的位,它在傳動臂的一端,通過這個傳動臂沿着半徑前後移動,從而讀取不同的磁盤上數據,這個過程就成為尋道(seek)

通過上圖可以清晰的了解到,在讀取數據的時候,首先通過傳動臂沿着半徑將讀寫頭移動到對應表面的磁道上,而表面一直在以固定的速率旋轉,讀取指定扇區的數據(磁盤是以扇區大小來讀寫數據)。因為對於數據訪問來說,消耗時間主要集中在:尋道時間、旋轉時間和傳送時間。

  • 尋道時間:即移動傳動臂到包含目標扇區的磁道上所需的時間;
  • 旋轉時間:即尋道完成后,等待目標扇區的第一個位旋轉到讀寫頭下的時間;
  • 傳送時間:即扇區第一個位開始位於讀寫頭下,到最後一個位所需的時間;

這裏給出一個書上寫的結論,訪問一個磁盤扇區中512字節的時間主要是尋道時間和旋轉延遲。也就是訪問扇區中第一個字節花費很長時間,剩下的幾乎不用時間。

這裏大家可能有疑問,CPU是如何讀取磁盤的數據到主存的,這就需要了解I/O總線。他們通過多種適配器連接到總線,而I/O總線連接了內存和CPU。如下圖所示:

也就是I/O總線連接各種I/O設備、主存等。

固態硬盤

固態硬盤也就是俗稱的SSD(Solid State Disk),是一種基於閃存的存儲技術,目前常用的日常PC都用它來代替了磁盤,獲取更快的速度。

SSD是內部由閃存構成,一個閃存由B個塊的序列組成,每個塊由P頁組成。通常頁的大小是512字節~4KB,塊由32~128頁組成,塊的大小為16KB~512KB。

SSD的隨機讀比寫快很多,是因為:

  1. 在寫的時候,只有一頁所屬的整個塊被擦除之後才能寫。而擦除塊需要較長時間,1ms級的,比讀取高一個數量級。
  2. 如果寫的頁P已經有數據,那麼這個塊中所有帶數據的頁都必須被複制到一個新的已經擦除過的塊,然後才能對頁P寫操作。

在大約進行100000次重複寫之後,塊會被磨損,不能在使用,所以這也是網上建議保存固態磁盤不要頻繁格式化,作為系統盤的原因。

局部性

現在計算機頻繁的使用基於SRAM的告訴緩存,為了彌補處理器-內存之間的差距,這種方法行之有效是因為局部性這個基本屬性。

程序的局部性原理是指程序在執行時呈現出局部性規律,即在一段時間內,整個程序的執行僅限於程序中的某一部分。相應地,執行所訪問的存儲空間也局限於某個內存區域。局部性原理又表現為:時間局部性和空間局部性。時間局部性是指如果程序中的某條指令一旦執行,則不久之後該指令可能再次被執行;如果某數據被訪問,則不久之後該數據可能再次被訪問。空間局部性是指一旦程序訪問了某個存儲單元,則不久之後。其附近的存儲單元也將被訪問。

上面我們介紹了內存和磁盤的讀取邏輯,因此一旦某個數據被訪問過,很快的時間內再次被訪問,則會有緩存等手段,提高訪問效率。

因此我們程序中應該尊村下列普遍方法:

  1. 重複引用相同變量的程序有良好的時間局部性;
  2. 總是順序訪問數據,跨越的步長越小,則程序的空間局部性越好。
  3. 對於取指令來說,循環有好的時間和空間局部性。循環體越小,循環迭代次數越多,局部性越好。

比如一個for循環,這是平時經常使用到的場景。假設它訪問一個同一個數組元素,那麼這個數組就是當前階段的訪問工作集,在緩存夠大的情況下,它是可以直接命中緩存的。

存儲器層次結構

上面主要介紹了存儲技術和計算機軟件一些基本的和持久的屬性:

  • 存儲技術:不同的存儲技術的訪問時間差異很大。速度較快的技術每字節的成本要比速度慢技術高,而且容量越小。CPU和主存之間的速度差距在增大;
  • 計算機軟件:一個便攜良好的程序傾向於展示出良好的局部性。

而現在計算機系統中,硬件和軟件這些基本屬性互相補充的很完美,即高層從底層走,存儲設備變得更慢、更便宜和更大,頂層的是CPU寄存器,CPU可以在一個時鐘周期內訪問他們,接下來是高速緩存SRAM、主存等 。

看上圖所示,其中心思想就是:對於每個k,位於k層的更快更小的存儲設備是作為位於k+1層更大更慢設備的緩存。

概括來說,基於緩存的存儲器層次結構行之有效,因為較慢的存儲設備比較快的設備更便宜,還因為程序傾向於展示局部性。

  • 利用時間局部性:由於時間局部性,同一數據可能會被多次使用,在第一次使用緩存不命中后就被複制到緩存中,後面在訪問時性能就比第一次快很多。
  • 利用空間局部性:存儲設備底層都有塊的概念,作為基本的讀取單位。通常塊包含多個數據,由於空間局部性,後面對該塊中其他對象的訪問即命中緩存,彌補首次訪問塊複製的消耗;

總結

今天,這篇文章主要學習了計算機存儲器的相關知識。

  1. 常用的存儲技術,以及計算機是如何操作這些存儲設備中的數據的。
  2. 講解了程序中的局部性原理,時間局部性和空間局部性。方便大家寫出更快的程序。
  3. 最後學習了整個計算機系統的存儲器層次結構。存儲系統其實就是一個多級緩存系統,上層的存儲設備昂貴,容量小,價格貴,但是速度快,作為下一層設備的緩存。

閱讀更多內容,請瀏覽我的個人小站:

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

【其他文章推薦】

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

012.Kubernetes二進制部署worker節點Flannel

一 部署flannel

1.1 安裝flannel


kubernetes 要求集群內各節點(包括 master 節點)能通過 Pod 網段互聯互通。flannel 使用 vxlan 技術為各節點創建一個可以互通的 Pod 網絡,使用的端口為 UDP 8472。




flanneld 第一次啟動時,從 etcd 獲取配置的 Pod 網段信息,為本節點分配一個未使用的地址段,然後創建 flannedl.1 網絡接口(也可能是其它名稱,如 flannel1 等)。




flannel 將分配給自己的 Pod 網段信息寫入 /run/flannel/docker 文件,docker 後續使用這個文件中的環境變量設置 docker0 網橋,從而從這個地址段為本節點的所有 Pod 容器分配 IP。

更多flannel參考:《008.Docker Flannel+Etcd分佈式網絡部署》。

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

1.2 分發flannel

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for node_ip in ${NODE_IPS[@]}
  4   do
  5     echo ">>> ${node_ip}"
  6     scp flannel/{flanneld,mk-docker-opts.sh} root@${node_ip}:/opt/k8s/bin/
  7     ssh root@${node_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建flannel證書和密鑰


提示:k8smaster01節點已創建flanneld的CA證書請求文件,可直接分發至node節點。

1.4 分發證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for node_ip in ${NODE_IPS[@]}
  4   do
  5     echo ">>> ${node_ip}"
  6     ssh root@${node_ip} "mkdir -p /etc/flanneld/cert"
  7     scp flanneld*.pem root@${node_ip}:/etc/flanneld/cert
  8   done


1.5 創建flanneld的systemd


提示:k8smaster01節點已創建創建flanneld的systemd,可直接分發至node節點。

1.6 分發flannel systemd

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


二 啟動並驗證

2.1 啟動flannel

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for node_ip in ${NODE_IPS[@]}
  3   do
  4     echo ">>> ${node_ip}"
  5     ssh root@${node_ip} "systemctl daemon-reload && systemctl enable flanneld && systemctl restart flanneld"
  6   done


2.2 檢查flannel啟動

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



2.3 檢查pod網段信息

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# etcdctl \
  3   --endpoints=${ETCD_ENDPOINTS} \
  4   --ca-file=/etc/kubernetes/cert/ca.pem \
  5   --cert-file=/etc/flanneld/cert/flanneld.pem \
  6   --key-file=/etc/flanneld/cert/flanneld-key.pem \
  7   get ${FLANNEL_ETCD_PREFIX}/config			#查看集群 Pod 網段(/16)



  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# etcdctl \
  3   --endpoints=${ETCD_ENDPOINTS} \
  4   --ca-file=/etc/kubernetes/cert/ca.pem \
  5   --cert-file=/etc/flanneld/cert/flanneld.pem \
  6   --key-file=/etc/flanneld/cert/flanneld-key.pem \
  7   ls ${FLANNEL_ETCD_PREFIX}/subnets			#查看已分配的 Pod 子網段列表(/24)
  8 [root@k8smaster01 ~]# etcdctl \
  9   --endpoints=${ETCD_ENDPOINTS} \
 10   --ca-file=/etc/kubernetes/cert/ca.pem \
 11   --cert-file=/etc/flanneld/cert/flanneld.pem \
 12   --key-file=/etc/flanneld/cert/flanneld-key.pem \
 13   get ${FLANNEL_ETCD_PREFIX}/subnets/172.30.8.0-21	#查看某一 Pod 網段對應的節點 IP 和 flannel 接口地址




解釋:

172.30.8.0/21 被分配給節點 k8snode02 (172.24.8.75);

VtepMAC 為 k8snode02 節點的 flannel.1 網卡 MAC 地址。

2.4 檢查flannel網絡信息

  1 [root@k8snode02 ~]# ip addr show



解釋:flannel.1 網卡的地址為分配的 Pod 子網段的第一個 IP(.0),且是 /32 的地址。

[root@k8smaster01 ~]# ip route show |grep flannel.1

172.30.8.0/21 via 172.30.8.0 dev flannel.1 onlink

172.30.128.0/21 via 172.30.128.0 dev flannel.1 onlink

172.30.208.0/21 via 172.30.208.0 dev flannel.1 onlink

172.30.216.0/21 via 172.30.216.0 dev flannel.1 onlink

解釋:

到其它節點 Pod 網段請求都被轉發到 flannel.1 網卡;

flanneld 根據 etcd 中子網段的信息,如 ${FLANNEL_ETCD_PREFIX}/subnets/172.30.32.0-21 ,來決定進請求發送給哪個節點的互聯 IP。

2.5 驗證各節點flannel


在各節點上部署 flannel 后,檢查是否創建了 flannel 接口(名稱可能為 flannel0、flannel.0、flannel.1 等):

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     ssh ${all_ip} "/usr/sbin/ip addr show flannel.1|grep -w inet"
  6   done



輸出:








在各節點上 ping 所有 flannel 接口 IP,確保能通:

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     ssh ${all_ip} "ping -c 1 172.30.8.0"
  6     ssh ${all_ip} "ping -c 1 172.30.32.0"
  7     ssh ${all_ip} "ping -c 1 172.30.128.0"
  8     ssh ${all_ip} "ping -c 1 172.30.208.0"
  9     ssh ${all_ip} "ping -c 1 172.30.216.0"
 10   done


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

【其他文章推薦】

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

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

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

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