月份: 2020 年 5 月

  • Ansibile之playbook初識

    Ansibile之playbook初識

      一、playbook簡介

      ansiblie的任務配置文件被稱為playbook,俗稱“劇本”,每一個劇本(playbook)中都包含了一系列的任務,這每個任務在ansible中又被稱為“戲劇”(play),一個劇本中包含多齣戲劇。。

      前文我們了解了ansible有兩種執行方式ad-hoc和ansible-playbook,ad-hoc主要用於臨時命令的執行,而playbook我們可以理解為ad-hoc的集合,有點類似shell腳本,ad-hoc就相當於shell腳本里的某條任務語句,playbook就相當於整個shell腳本。playbook是由一個或多個“play”組成的列表,play的主要功能在於將預定義的一組主機,裝扮成事先通過ansible中的task定義好的角色。task實際是調用ansible的一個模塊,將多個play組織在一個playbook中,即可以讓他們聯合起來,按事先編排的機制執行預定義的動作。

     如以上圖示,用戶可以把多條任務(ad-hoc任務)寫到playbook中,用戶用ansible-playbook命令調用執行編排好的playbook,ansible會讀取playbook中的每一條play和task,並按照playbook中的順序從上至下依次執行,ansible會調用每個task中定義的模塊去依次執行相應的任務,並按照playbook中指定的主機去主機清單里匹配對應的主機,然後通過ssh認證,把編譯好的相應的任務文件發送到對應的主機或網絡設備上執行,最後返回執行的狀態。

      二、YAML簡介

      playbook採用yaml語言編寫,yaml是一個可讀性高的用來表達資料序列格式的語言,它參考了其他很多種語言,包括:XML、C語言、python、perl以及电子郵箱格式RFC2822等。Clark Evans在2001年首次發表了這種語言,另外Ingy döt Net與Oren Ben-Kiki也是這語言的共同設計者。YAML( YAML Ain’t Markup Language),即yaml不是標記語言。不過在開發這種語言時,yaml的意思其實是:”Yet Another Markup Language”(仍是一種標記語言)

      ymal特性

      1)YAML的可讀性好

      2)YAML和腳本語言的交互性好

      3)YAML使用實現語言的數據類型

      4)YAML有一個一致的信息模板

      5)YAML易於實現,可以基於流程處理,表達能力強,擴展性好

    更多的內容及規範請參考官方文檔

      三、playbook語法簡介

      1)需要以“—”(3個減號)開始,且需頂行首寫。另外還有選擇性的連續三個點號(…)用來表示文件的結尾。

      2)次行開始正常寫playbook的內容,建議次行寫該playbook的功能,當然不寫也是可以的。

      3)使用“#”號註釋代碼。

      4)縮進必須統一,不能空格tab混用。

      5)縮進的級別必須是一致的,同樣的縮進代表同樣級別,程序判別配置的級別是通過縮進結合換行來實現的。

      6)YAML文件內容和Linux系統大小寫判斷方式一直,區分大小寫(大小寫敏感),k/v的值均大小寫敏感。

      7)k/v的值可同行寫也可換行寫。同行使用“:”分隔,換行寫需要以“-”分隔。

      8)v可以是字符串,也可以另外一個列表,當然也可以是字典。

      9)一個完整的代碼塊功能最少需要有name:xxx(對任務的描述)。

      10)一個name只能包括一個task

      11)yaml文件擴展名通常為yml或yaml

    list:列表,其所有元素均使用“-”開頭

    示例:

    ---
    # A list of tasty fruits
    
    - apple
    - orange
    - strawberry
    - mango
    ~           

    dictionary:字典,通常由多個key與value構成

    示例:

    ---
    #An employee record
    name: example developer
    job: developer
    skill: elite  

    當然也可以將key:value放置於{}中進行表示,用“,”分隔多個key:value

    示例:

    ---
    #An employee record
    {name: example developer,job: developer, skill: elite}
    ~                                                         

      YAML的語法和其他高階語言類似,並且可以簡單表達清單、散列表、標量等數據結構。其結構(Structure)通過空格來展示,序列(Sequence)里的項用”-“來代表,Map里的鍵值對用”:”分隔。

    示例:

    ---
    name: John Smith
    age: 41
    gender: Male
    spouse:
      name: Jane Smith
      age: 37
      gender: Female
    children:
      - name: Jimmy Smith
        age: 17
        gender: Male
      - name: Jenny Smith
        age: 13
        gender: Female
    ~                    
    

      四、playbook核心元素

      1)hosts  :指定執行任務的遠程主機列表(主機清單定義的主機組或單個主機,支持前面的說的主機模式匹配)

      2)tasks  :任務集

      3)varniables  :內置變量或自定義變量在playbook中調用

      4)templates  :模板,可替換模板文件中的變量並實現一些簡單邏輯的文件

      5)handlers  和  notity結合使用,由特定條件出發的操作,滿足條件方才執行,否則不執行

      6)tags標籤  :給指定的任務貼上標籤,我們在執行playbook的時候可以根據標籤選擇性的挑選部分代碼執行,如 ansible-playbook -t tagsname useradd.yml  ,這條命令的意思就是在useradd.yml中挑選標籤名為tagsname的任務執行

       五、playbook基礎組件

      1)hosts:

        playbook中的每一個play的目的都是為了讓特定主機以某個指定的用戶身份執行任務。hosts用於指定要執行任務的主機,須事先定義在主機清單中。hosts指定主機的形式同樣支持像主機清單中定義的那樣,支持通配,支持主機模式匹配與或非,支持IP地址,當然也支持混合匹配與或非。

    示例:在websers組,但不再dbsers組,可以這樣定義hosts

    ---
    - hosts: websers:!dbsers
    

      2)remote_user:可用於host和task中,也可以通過指定其通過sudo的方式在遠程執行任務,其可用於play全局或某個任務;此外,甚至可以在sudo時使用用sudo_user指定sudo時切換的用戶,如下所示

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping
    

      說明:默認sudo 為root,上例指定了sudo_user 為qiuping,上述任務同sudo -u qiuping ping xxxx(代表某主機)命令一樣的意思,當然在使用sudo 時 我們還需要在目標主機上對qiuhom授權,要讓qiuhom這個用戶具有代表qiuping的權限去執行ping命令。

      3)task列表和action:play的主體部分是task list,task list 中的各任務按次序逐個在hosts中指定的所有主機上執行,即在所有主機上完成第一個任務后,再開始第二個任務;task的目的是使用指定的參數執行模塊,而在模塊參數中可以使用變量,模塊執行時是冪等的,這意味着多次執行時是安全的,其結果均一致;每個task都應該有其name,用於playbook的執行結果輸出,建議其內容能清晰地描述任務步驟,如未提供name,則action的結果將用於輸出。

      tasks:任務列表,它有兩種格式如下

        (1)action: module arguments

        (2)module: arguments        ##建議使用

       注意:shell模塊和command模塊後面跟的是命令,而非key=value

    如果某項任務的狀態在運行後為changed時,可通過“notify”通知給相應的handlers;當然任務可以通過“tags”打標籤,可以在ansible-playbook命令上使用-t指定進行指定其標籤名調用。

    示例:

    [qiuhom@test ~]$cat test.yml 
    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping 
          tags: test
        - name: test command
          shell: /bin/ls /home/qiuhom/
    [qiuhom@test ~]$ansible-playbook -t test test.yml 
    

      說明:用-t 指定標籤名,表示只運行所指定標籤所在的任務,當然同名的標籤可以在多條任務中,一個任務也可以有多個標籤。

    如果命令或腳本的退出碼不為零,可用使用如下方式忽略或跳過繼續執行以下代碼

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0 || /bin/true
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0
          ignore_errors: True
    

      說明:兩種方式都可以跳過出錯的命令而不打斷playbook,繼續執行以下的代碼,前者使用的短路或的特性,後者使用ignore_errors參數來控制

      六、playbook運行的方式

    ansible-playbook <filename.yml> ... [options]
    

    常用選項:

      -C , –check  : 只檢查可能會發生的改變,但不真正執行操作,相當於空跑一遍playbook,測試下是否和自己預想的結果一樣,但它不會真正的去遠端主機上執行。常用於測試寫的playbook語法是否有誤。

      –list-hosts  :列出playbook指定運行任務所匹配的主機

      –list-tags    :列出playbook中所有標籤名稱列表

      –list-tasks  :列出playbook中所有任務名稱及標籤名稱

      –limit 主機列表   :只針對指定主機列表中的主機執行當前playbook(指定主機列表必須是在playbook里定義的主機列表範圍內)

      -v,-vv,-vvv       :  显示執行playbook的過程,-v,显示較簡單,-vv显示較詳細,-vvv显示整個過程(非常詳細)

    [root@test ~]#cat test.yml 
    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: run this command 
          shell: hostname
          tags: hostname
          ignore_errors: True
        - name: show ip addr
          shell: /sbin/ip addr show
          tags: showip
    [root@test ~]#ansible-playbook test.yml --list-hosts
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        pattern: [u'websers']
        hosts (2):
          192.168.0.128
          192.168.0.218
    [root@test ~]#ansible-playbook test.yml --list-tags
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
          TASK TAGS: [hostname, showip]
    [root@test ~]#ansible-playbook test.yml --list-tasks
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        tasks:
          run this command  TAGS: [hostname]
          show ip addr      TAGS: [showip]
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218]
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218]
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218 -v
    Using /etc/ansible/ansible.cfg as config file
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.002139", "end": "2019-11-16 23:11:02.996962", "rc": 0, "start": "2019-11-16 23:11:02.994823", "stderr": "", "stderr_lines": [], "stdout": "localhost.localdomain", "stdout_lines": ["localhost.localdomain"]}
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "/sbin/ip addr show", "delta": "0:00:00.002604", "end": "2019-11-16 23:11:03.733004", "rc": 0, "start": "2019-11-16 23:11:03.730400", "stderr": "", "stderr_lines": [], "stdout": "1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n    inet6 ::1/128 scope host \n       valid_lft forever preferred_lft forever\n2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\n    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff\n    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0\n    inet6 fe80::20c:29ff:fee8:f67b/64 scope link \n       valid_lft forever preferred_lft forever\n3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \n    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff", "stdout_lines": ["1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN ", "    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00", "    inet 127.0.0.1/8 scope host lo", "    inet6 ::1/128 scope host ", "       valid_lft forever preferred_lft forever", "2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000", "    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff", "    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0", "    inet6 fe80::20c:29ff:fee8:f67b/64 scope link ", "       valid_lft forever preferred_lft forever", "3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN ", "    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff"]}
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#
    

      說明:–limit 所指定的主機必須是在playbook中所指定的主機範圍內。

       七、playbook vs shell scripts

      1)shell腳本如下:

    #!/bin/bash
    # 安裝Apache
    yum install --quiet -y httpd
    # 複製配置文件
    cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
    cp/tmp/vhosts.conf /etc/httpd/conf.d/
    # 啟動Apache,並設置開機啟動
    service httpd start
    chkconfig httpd on
    

      2)playbook

    ---
    - 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/
        - name: copy config 2 file
          copy: src=/tmp/vhosts.conf dest=/etc/httpd/conf.d/
        - name: start httpd service
          service: name=httpd state=started enabled=yes     
    

      說明:兩者都是實現同樣的目的,很明顯playbook的優勢要比腳本的優勢多,playbook 可以針對很多台主機進行任務執行,而腳本只可以在某一台主機上執行;腳本重複執行沒有冪等性,很有可能帶來很多錯誤,而playbook卻不會有這樣的苦惱。

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

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

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

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

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

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

  • 快速搭建Jenkins集群

    快速搭建Jenkins集群

    關於Jenkins集群

    在Jenkins上同時執行多個任務時,單機性能可能達到瓶頸,使用Jenkins集群可以有效的解決此問題,讓多台機器同時處理這些任務可以將壓力分散,對單機版Jenkins的單點故障的隱患也有分散作用,今天就來實戰快速搭建Jenkins集群,Jenkins版本是2.190.2;

    如何做到快速搭建集群

    通過Docker可以省去大部分準備工作,您只需在Linux電腦上安裝docker,在輔以少量命令和操作即可完成集群搭建;

    環境信息

    本次實戰的環境一共要用三台電腦,它們的設置都是一樣的,如下:

    1. 操作系統:CentOS Linux release 7.6.1810
    2. 防火牆關閉
    3. docker:1.13.1

    三台電腦的信息如下:
    | 主機名 | IP地址 | 作用 |
    |–|–|–|
    | master | 192.168.133.131 | Jenkins集群的master節點,提供web服務 |
    | agent1 | 192.168.133.132 | Jenkins集群的一號工作接節點,標籤是maven |
    | agent2 | 192.168.133.133 | Jenkins集群的二號工作接節點,標籤是gradle |

    建議agent2節點的內存大於4G,因為下一篇的實戰操作會用agent2編譯構建spring-framework,對內存的需求略大;

    準備工作

    1. 後面的所有操作都是root賬號;
    2. 在每台電腦上創建文件夾/usr/local/jenkins

      創建Jenkins的master

    3. 登錄master機器,執行以下命令:
    docker run \
      -u root \
      -idt \
      --name master \
      -p 8080:8080 \
      -p 50000:50000 \
      -v /usr/local/jenkins:/var/jenkins_home \
      -v /var/run/docker.sock:/var/run/docker.sock \
      jenkinsci/blueocean:1.19.0
    1. 執行docker logs master,會在控制台显示jenkins的登錄秘鑰,如下圖紅框所示:
    2. 瀏覽器輸入地址: ,显示Jenkins登錄頁面,如下圖所示,在紅框位置輸入剛才複製的登錄秘鑰即可登錄:
    3. 選擇安裝推薦的插件
    4. 靜候插件在線安裝完成:
    5. 接下來是創建管理員和使用實例url的操作,這裏就不多說了,您按實際情況自行斟酌;

      至此,Jenkins的master已經搭建好,接下來將agent1和agent2作為工作節點加入集群;

      加入agent1

    6. 在Jenkins網頁上新增節點,操作如下圖,先進入節點管理頁面:
    7. 如下圖,新增一個節點,名為agent1
    8. 接下來的節點詳情信息如下圖,注意四個紅框中的內容要和圖中保持一致:
    9. 保存成功後會显示機器列表,如下圖,圖標上的紅叉表示機器不在線(此時agent1還沒有接入),點擊紅框:
    10. 如下圖所示,紅框中的命令就是agent1的啟動命令,執行該命令的機器會以agent1的身份加入集群:
    11. 注意上圖紅框中的agent.jar是個名為agent.jar的文件的下載鏈接,將此文件下載到agent1電腦的/usr/local/jenkins目錄下;
    12. ssh登錄agent1電腦,執行以下命令,即可將agent1加入Jenkins集群:
    docker run \
      -u root \
      -idt \
      --name agent \
      -v /usr/local/jenkins:/usr/local/jenkins \
      bolingcavalry/openjdk-with-sshpass:8u232 \
      java -jar /usr/local/jenkins/agent.jar \
      -jnlpUrl http://192.168.133.131:8080/computer/agent1/slave-agent.jnlp \
      -secret 44c3e8d1531754b8655b53294bbde6dd99b3aaa91a250092d0d3425534ae1058 \
      -workDir "/usr/local/jenkins"

    上述命令中的後半部分,即java -jar ……就是前面圖片紅框中的agent1啟動命令,唯一要改變的是將agent.jar改成絕對路徑/usr/local/jenkins/agent.jar

    1. 上述命令的鏡像是bolingcavalry/openjdk-with-sshpass:8u232,其Dockerfile內容如下,可見非常簡單,就是OpenJDK鏡像裏面安裝了sshpass,這樣的容器可以在執行ssh命令時帶上遠程機器的密碼,而不用等待用戶輸入密碼,這樣便於shell腳本執行ssh命令:
    FROM openjdk:8u232
    
    ARG DEBIAN_FRONTEND=noninteractive
    RUN apt-get update && apt-get install --assume-yes sshpass
    1. 去Jenkins的網頁上查看節點列表,如下圖,可見agent1已經成功加入:

      加入agent2

      agent2加入集群的方式和agent1大部分是一樣的,只有以下兩點要注意:

    2. 在Jenkins頁面上創建節點,名稱是agent2
    3. agent2的標籤是gradle,如下圖紅框所示:
    4. 此時agent2也加入成功:

      至此,Jenkins集群搭建完成,這兩個節點帶有不同的標籤,下一篇文章中,我們在這個集群環境創建pipeline任務,並通過標籤被分配到不同的節點上,實現多節點并行執行;

      歡迎關注公眾號:程序員欣宸

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

    【其他文章推薦】

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

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

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

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

  • 數據結構之隊列and棧總結分析

    一、前言:

      數據結構中隊列和棧也是常見的兩個數據結構,隊列和棧在實際使用場景上也是相輔相成的,下面簡單總結一下,如有不對之處,多多指點交流,謝謝。

    二、隊列簡介

      隊列顧名思義就是排隊的意思,根據我們的實際生活不難理解,排隊就是有先後順序,先到先得,其實在程序數據結構中的隊列其效果也是一樣,及先進先出。

         隊列大概有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為32)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,隊列在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進先出原則

           

       c#中有關隊列的幾個常用方法:

      • Count:Count屬性返回隊列中元素個數。
      • Enqueue:Enqueue()方法在隊列一端添加一個元素。
      • Dequeue:Dequeue()方法在隊列的頭部讀取和刪除元素。如果在調用Dequeue()方法時,隊列中不再有元素,就拋出一個InvalidOperationException類型的異常。
      • Peek:Peek()方法從隊列的頭部讀取一個元素,但不刪除它。
      • TrimExcess:TrimExcess()方法重新設置隊列的容量。Dequeue()方法從隊列中刪除元素,但它不會重新設置隊列的容量。要從隊列的頭部去除空元素,應使用TrimExcess()方法。
      • Clear:Clear()方法從隊列中移除所有的元素。
      • ToArray:ToArray()複製隊列到一個新的數組中。

      下面通過隊列來實例模擬消息隊列的實現流程:

     

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace dataStructureQueueTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("通過Queue來模擬消息隊列的實現");
                QueueTest queueTest = new QueueTest();
    
                while (true)
                {
                    Console.WriteLine("請輸入你操作的類型:1:代表生成一條消息,2:代表消費一條消息");
                    string type = Console.ReadLine();
                    if (type == "1")
                    {
                        Console.WriteLine("請輸入具體消息:");
                        string inforValue = Console.ReadLine();
                        queueTest.InformationProducer(inforValue);
                    }
                    else if (type == "2")
                    {
                        //// 在消費消息的時候,模擬一下,消費成功與消費失敗下次繼續消費的場景
    
                        object inforValue = queueTest.InformationConsumerGet();
                        if (inforValue == null)
                        {
                            Console.WriteLine("當前無可消息可消費");
                        }
                        else
                        {
                            Console.WriteLine("獲取到的消息為:" + inforValue);
    
                            Console.WriteLine("請輸入消息消費結果:1:成功消費消息,2:消息消費失敗");
                            string consumerState = Console.ReadLine();
    
                            ///// 備註:該操作方式線程不安全,在多線程不要直接使用
                            if (consumerState == "1")
                            {
                                queueTest.InformationConsumerDel();
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("操作有誤,請重新選擇");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class QueueTest
        {
            /// <summary>
            /// 定義一個隊列
            /// </summary>
            public Queue<string> queue = new Queue<string>();
    
            /// <summary>
            /// 生成消息--入隊列
            /// </summary>
            /// <param name="inforValue"></param>
            public void InformationProducer(string inforValue)
            {
                queue.Enqueue(inforValue);
            }
    
            /// <summary>
            /// 消費消息---出隊列--只獲取數據,不刪除數據
            /// </summary>
            /// <returns></returns>
            public object InformationConsumerGet()
            {
                if (queue.Count > 0)
                {
                    return queue.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 消費消息---出隊列---獲取數據的同時刪除數據
            /// </summary>
            /// <returns></returns>
            public string InformationConsumerDel()
            {
                if (queue.Count > 0)
                {
                    return queue.Dequeue();
                }
    
                return null;
            }
        }
    }

     

     

    三、棧簡介

      棧和隊列在使用上很相似,只是棧的數據存儲滿足先進后出原則,棧有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為10)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,棧在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進后出原則

        c#中有關棧的幾個常用方法:

    • Count:Count屬性返回棧中的元素個數。
    • Push:Push()方法在棧頂添加一個元素。
    • Pop:Pop()方法從棧頂刪除一個元素,並返回該元素。如果棧是空的,就拋出一個InvalidOperationException類型的異常。
    • Peek:Peek()方法返回棧頂的元素,但不刪除它。
    • Contains:Contains()方法確定某個元素是否在棧中,如果是,就返回true。

         下面通過一個棧來模擬瀏覽器的回退前進操作的實現

     

    using System;
    using System.Collections.Generic;
    
    namespace dataStructureStackTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                //// 通過棧來模擬瀏覽器回退前進操作
                ////   1、定義兩個棧,分別記錄回退的地址集合,和前進地址集合
                ////   2、在操作具體的回退或者前進操作時
                ////      如果和前一次操作相同,那麼就取出對應隊列的一條數據存儲到另外一個隊列
                Console.WriteLine("本練習模擬瀏覽器的回退前進操作:");
    
                /// 假設瀏覽器已瀏覽了20個網站記錄
                StackTest stackTestBack = new StackTest(20);
                StackTest stackTestGo = new StackTest(20);
                for (int i = 0; i < 20; i++)
                {
                    stackTestBack.PushStack("網站" + (i + 1).ToString());
                }
    
                //// 記錄上一次操作
                string beforOpert = "";
                while (true)
                {
                    Console.WriteLine("");
                    Console.WriteLine("請輸入你操作的類型:1:回退,2:前進");
                    string type = Console.ReadLine();
    
                    if (type == "1")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestGo.PushStack(stackTestBack.GetAndDelStack());
                        }
                        string wbeSit = stackTestBack.GetStack();
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else if (type == "2")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestBack.PushStack(stackTestGo.GetAndDelStack());
                        }
                        string wbeSit = stackTestGo.GetStack();
    
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else
                    {
                        Console.WriteLine("請輸入正確的操作方式!!");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class StackTest
        {
            /// <summary>
            /// 定義一個棧
            /// </summary>
            public Stack<string> stack;
    
            /// <summary>
            ///無參數構造函數,棧初始化為默認長度
            /// </summary>
            public StackTest()
            {
                stack = new Stack<string>();
            }
    
            /// <summary>
            ///有參數構造函數,棧初始化為指定長度
            ///如果在定義隊列時,如果知道需要存儲的數據長度,那麼最好預估一個長度,並初始化指定的長度
            /// </summary>
            public StackTest(int stackLen)
            {
                stack = stackLen > 0 ? new Stack<string>(stackLen) : new Stack<string>();
            }
    
            /// <summary>
            /// 入棧
            /// </summary>
            /// <param name="inforValue"></param>
            public void PushStack(string inforValue)
            {
                stack.Push(inforValue);
            }
    
            /// <summary>
            /// 出棧(但不刪除)
            /// </summary>
            /// <returns></returns>
            public string GetStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 出棧(並刪除)
            /// </summary>
            /// <returns></returns>
            public string GetAndDelStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Pop();
                }
    
                return null;
            }
        }
    }

     

    四、使用場景總結

      根據隊列和棧的特點,下面簡單總結一下隊列和棧的一些實際使用場景

       隊列:

        1、異步記錄日誌,此處會涉及到單例模式的使用

        2、消息隊列

        3、業務排隊,比如12306車票購買排隊等候

        4、其他符合先進先出原則的業務操作

       棧:

        1、可回退的操作記錄,比如:瀏覽器的回退操作

        2、計算表達式匹配,比如:計算器表達式計算

        3、其他符合先進后出原則的業務操作

     

    附件:

    關於這一些練習的代碼,上傳到github,有興趣的可以看一下:

     

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

    【其他文章推薦】

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

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

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

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

  • 深入理解java繼承從“我爸是李剛”講起

    深入理解java繼承從“我爸是李剛”講起

    目錄

    前言
    本文主要多方面講解java繼承,旨在讓初學者通俗易懂,至於“我爸是李剛”,反正樓主也不知道誰爸是李剛。
    @

    1、繼承的概述

    1.1、繼承的由來

    至於由來簡單一句話:多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為。

    繼承描述的是事物之間的所屬關係,這種關係是 is-a 的關係。

    1.2、繼承的定義

    繼承:就是子類繼承父類的屬性行為,使得子類對象具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。

    這裏再聲明一點,父類又稱為超類或者基類。而子類又稱為派生類這點很基礎!

    1.3、繼承的優點

    1. 提高代碼的復用性
    2. 類與類之間產生關係,為多態做了完美的鋪墊(不理解沒關係,之後我會再寫一篇多態的文章)

    雖然繼承的優點很多但是Java只支持單繼承,不支持多繼承

    1.4、繼承的格式

    通過 extends 關鍵字,可以聲明一個子類繼承另外一個父類,定義格式如下:

      class 父類 {
       ... 
       }
       class 子類 extends 父類 { 
       ... 
       } 

    2、關於繼承之後的成員變量

    當類之間產生了關係后,其中各類中的成員變量,產生了哪些影響呢? 關於繼承之後的成員變量要從兩方面下手,一是成員變量不重名方面,二是成員變量重名方面。

    2.1、成員變量不重名

    如果子類父類中出現不重名的成員變量,這時的訪問是沒有影響的。代碼如下:

      class liGang {
            // 父類中的成員變量。
           String name ="李剛";//------------------------------父類成員變量是name
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name2 ="李小剛";//--------------------------子類成員變量是name2
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+name2);
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    2.2、 成員變量重名

    如果子類父類中出現重名的成員變量,這時的訪問是有影響的。代碼如下:

    class liGang {
            // 父類中的成員變量。
           String name ="李剛";//------------------------------父類成員變量是name
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";//---------------------------子類成員變量也是name
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+name);
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李小剛   我是李小剛
    
    

    子父類中出現了同名的成員變量時,在子類中需要訪問父類中非私有成員變量時,需要使用 super 關鍵字,至於修飾父類成員變量,類似於之前學過的 this 。 使用格式 super.父類成員變量名

    this表示當前對象,super則表示父類對象,用法類似!

    class liGang {
            // 父類中的成員變量。
           String name ="李剛";
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    2.3、關於繼承中成員變量值得思考的一個問題

    同學你有沒有想過這樣一個問題。如果父類中的成員變量
    非私有:子類中可以直接訪問。
    私有:子類是不能直接訪問的。如下:

    當然,同學你要自己體驗體驗編譯報錯過程,看圖沒體驗感不得勁,~嘔,你這無處安放的魅力,無理的要求,我佛了,行吧~

      class liGang2 {
            // 父類中的成員變量。
            private String name ="李剛";
    
        }
        class LiXiaoGang2 extends liGang2 {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.name);//------編譯失敗不能直接訪問父類私有屬性(成員變量)
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class PrivateVariable {
            public static void main(String[] args) {
                // 創建子類對象
                ExtendDemo.LiXiaoGang z = new ExtendDemo.LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }

    通常開發中編碼時,我們遵循封裝的原則,使用private修飾成員變量,那麼如何訪問父類的私有成員變量呢?其實這個時候在父類中提供公共的getXxx方法和setXxx方法就可以了。代碼如下:

    class liGang {
            // 父類中的成員變量。
          private String name ="李剛";
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.getName());
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    分析如下:

    3、關於繼承之後的成員方法

    分析完了成員變量,現在我們一起來分析分析成員方法。
    想一想,當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢? 同樣我們依舊從兩方面分析。
    #### 3.1、成員方法不重名
    如果子類父類中出現不重名的成員方法,這時的調用是沒有影響的。對象調用方法時,會先在子類中查找有沒有對 應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。代碼如下:

     class liGang3 {
            // 父類中的成員方法。
           public void zhuangRen1(){//--------------------------父類方法名zhuangRen1
               System.out.println("我叫李剛,人不是我撞的,別抓我,我不認識李小剛");
           }
        }
        class LiXiaoGang3 extends liGang3 {
    
            // 子類中的成員方法
            public void zhuangRen() {//--------------------------子類方法名zhuangRen
                System.out.println("有本事你們告去,我爸是李剛");  
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的show方法
                liXiaoGang.zhuangRen();
                liXiaoGang.zhuangRen1();
            }
        }
        
    打印結果:有本事你們告去,我爸是李剛
            我叫李剛,人不是我撞的,別抓我,我不認識李小剛
    

    #### 3.2、成員方法重名 【方法重寫】
    成員方法重名大體也可以分兩種情況:

    1、方法名相同返回值類型、參數列表卻不相同(優先在子類查找,沒找到就去父類)
    2、方法名、返回值類型、參數列表都相同,沒錯這就是重寫(Override)

    這裏主要講方法重寫 :子類中出現與父類一模一樣的方法時(返回值類型,方法名和參數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。聲明不變,重新實現。 代碼如下:

        class liGang3 {
            // 父類中的成員方法。
           public void zhuangRen(int a){
               System.out.println("我叫李剛,人不是我撞的,別抓我");
           }
        }
        class LiXiaoGang3 extends liGang3 {
    
            // 子類中的成員方法
            public void zhuangRen(int a) {
                System.out.println("有本事你們告去,我爸是李剛");
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的zhuangRen方法
                liXiaoGang.zhuangRen(1);
    
            }
        }
        結果打印:有本事你們告去,我爸是李剛

    #### 3.3、繼承中重寫方法的意義
    子類可以根據需要,定義特定於自己的行為。既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴展增強。比如李剛會開車,李小剛就牛了,在父類中進行擴展增強還會開車撞人,代碼如下:

     class liGang3 {
            // 父類中的成員方法。
           public void kaiChe(){
               System.out.println("我會開車");
           }
        }
        class LiXiaoGang3 extends liGang3 {
            // 子類中的成員方法
            public void kaiChe(){
                super.kaiChe();
                System.out.println("我還會撞人");
                System.out.println("我還能一撞撞倆婆娘");
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的zhuangRen方法
                liXiaoGang.kaiChe();
    
    打印結果:   我會開車
               我還會撞人
               我還能一撞撞倆婆娘
            }
        }
    

    不知道同學們發現了沒有,以上代碼中在子類中使用了 super.kaiChe();super.父類成員方法,表示調用父類的成員方法。

    最後重寫必須注意這幾點:

    1、方法重寫時, 方法名與形參列表必須一致。
    2、子類方法覆蓋父類方法時,必須要保證子類權限 >= 父類權限。
    3、方法重寫時,子類的返回值類型必須要 <= 父類的返回值類型。
    4、方法重寫時,子類拋出的異常類型要 <= 父類拋出的異常類型。

    粗心的同學看黑板,look 這裏【注意:只有訪問權限是>=,返回值、異常類型都是<=

    下面以修飾權限為例,如下:

    4、關於繼承之後的構造方法

    為了讓你更好的體會,首先我先編寫一個程序

       class liGang4 {
            // 父類的無參構造方法。
            public liGang4(){
                System.out.println("父類構造方法執行了。。。");
            }
        }
        class LiXiaoGang4 extends liGang4 {
            // 子類的無參構造方法。
           public LiXiaoGang4(){
               System.out.println("子類構造方法執行了====");
           }
        }
        public class ConstructionDemo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang4 z = new LiXiaoGang4();
    
            }
        }

    用一分鐘猜想一下結果是什麼,猜好了再看下面結果:

    父類構造方法執行了。。。
    子類構造方法執行了====

    好了,看了結果之後,你可能有疑惑。父類構造器方法怎麼執行了?我們先來分析分析,首先在main方法中實例化了子類對象,接着會去執行子類的默認構造器初始化,這個時候在構造方法中默認會在第一句代碼中添加super();沒錯,他就是開掛般的存在,不寫也存在的!有的調~讀四聲“跳”~皮的同學就會說,你說存在就存在啊,無憑無據 ~呀,你這個該死的靚仔~ 如下:

    構造方法的名字是與類名一致的,所以子類是無法繼承父類構造方法的。 構造方法的作用是初始化成員變量的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中默認會在第一句代碼中添加super(),表示調用父類的構造方法,父類成員變量初始化后,才可以給子類使用。

    當然我已經強調很多遍了 super() 不寫也默認存在,而且只能是在第一句代碼中,不在第一句代碼中行不行,答案是當然不行,這樣會編譯失敗,如下:

    5、關於繼承的多態性支持的例子

    直接上代碼了喔

    class A{
        public String show(C obj) {
            return ("A and C");
        }
    
        public String show(A obj) {
            return ("A and A");
        }
    
    }
    class B extends A{
        public String show(B obj) {
            return ("B and B");
        }
    }
    class C extends B{
        public String show(A obj) {
            return ("A and B");
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            A a=new A();
            B b=new B();
            C c=new C();
            System.out.println("第一題 " + a.show(a));
            System.out.println("第二題 " + a.show(b));
            System.out.println("第三題 " + a.show(c));
        }
    }
    運行結果:
            第一題 A and A
            第二題 A and A
            第三題 A and C

    其實吧,第一題和第三題都好理解,第二題就有點意思了,會發現A類中沒有B類型這個參數,這個時候,你就應該知道子類繼承就是父類,換句話說就是子類天然就是父類,比如中國人肯定是人,但是人不一定是中國人(可能是火星人也可能是非洲人),所以父類做為參數類型,直接傳子類的參數進去是可以的,反過來,子類做為參數類型,傳父類的參數進去,就需要強制類型轉換。

    6、super與this的用法

    了解他們的用法之前必須明確一點的是父類空間優先於子類對象產生

    在每次創建子類對象時,先初始化父類空間,再創建其子類對象本身。目的在於子類對象中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。代碼體現在子類的構 造方法調用時,一定先調用父類的構造方法。理解圖解如下:

    #### 5.1、 super和this的含義:

    super :代表父類的存儲空間標識(可以理解為父親的引用)。

     

    this :代表當前對象的引用(誰調用就代表誰)。

    #### 5.2、 super和this訪問成員

    this.成員變量 ‐‐ 本類的
    super.成員變量 ‐‐ 父類的
    this.成員方法名() ‐‐ 本類的
    super.成員方法名() ‐‐ 父類的

    #### 5.3、super和this訪問構造方法

    this(...) ‐‐ 本類的構造方法
    super(...) ‐‐ 父類的構造方法

    #### 5.4、super()和this()能不能同時使用?

    不能同時使用,thissuper不能同時出現在一個構造函數裏面,因為this必然會調用其它的構造函數,其它的構造函數必然也會有super語句的存在,所以在同一個構造函數裏面有相同的語句,就失去了語句的意義,編譯器也不會通過。
    #### 5.5、總結一下super與this

    子類的每個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()super()this() 都必須是在構造方法的第一行,所以不能同時出現

    到這裏,java繼承你get到了咩,get到了請咩一聲,隨便隨手~點個讚唄~

    推薦閱讀本專欄的下一篇java文章

    歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

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

    【其他文章推薦】

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

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

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

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

  • 徹底搞懂 netty 線程模型

    徹底搞懂 netty 線程模型

    編者注:Netty是Java領域有名的開源網絡庫,特點是高性能和高擴展性,因此很多流行的框架都是基於它來構建的,比如我們熟知的Dubbo、Rocketmq、Hadoop等。本文就netty線程模型展開分析討論下 : )

    IO模型

    • BIO:同步阻塞IO模型;
    • NIO:基於IO多路復用技術的“非阻塞同步”IO模型。簡單來說,內核將可讀可寫事件通知應用,由應用主動發起讀寫操作;
    • AIO:非阻塞異步IO模型。簡單來說,內核將讀完成事件通知應用,讀操作由內核完成,應用只需操作數據即可;應用做異步寫操作時立即返回,內核會進行寫操作排隊並執行寫操作。

    NIO和AIO不同之處在於應用是否進行真正的讀寫操作。

    reactor和proactor模型

    • reactor:基於NIO技術,可讀可寫時通知應用;
    • proactor:基於AIO技術,讀完成時通知應用,寫操作應用通知內核。

    netty線程模型

    netty的線程模型是基於Reactor模型的。

    netty單線程模型

    Reactor 單線程模型,是指所有的 I/O 操作都在同一個 NIO 線程上面完成的,此時NIO線程職責包括:接收新建連接請求、讀寫操作等。

    在一些小容量應用場景下,可以使用單線程模型(注意,Redis的請求處理也是單線程模型,為什麼Redis的性能會如此之高呢?因為Redis的讀寫操作基本都是內存操作,並且Redis協議比較簡潔,序列化/反序列化耗費性能更低)。但是對於高負載、大併發的應用場景卻不合適,主要原因如下:

    • 一個NIO線程同時處理成百上千的連接,性能上無法支撐,即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的編碼、解碼、讀取和發送。
    • 當NIO線程負載過重之後,處理速度將變慢,這會導致大量客戶端連接超時,超時之後往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時,成為系統的性能瓶頸。
    • 可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障。

    Reactor多線程模型

    Rector 多線程模型與單線程模型最大的區別就是有一組 NIO 線程來處理連接讀寫操作,一個NIO線程處理Accept。一個NIO線程可以處理多個連接事件,一個連接的事件只能屬於一個NIO線程。

    在絕大多數場景下,Reactor 多線程模型可以滿足性能需求。但是,在個別特殊場景中,一個 NIO 線程負責監聽和處理所有的客戶端連接可能會存在性能問題。例如併發百萬客戶端連接,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗性能。在這類場景下,單獨一個 Acceptor 線程可能會存在性能不足的問題,為了解決性能問題,產生了第三種 Reactor 線程模型——主從Reactor 多線程模型。

    Reactor主從多線程模型

    主從 Reactor 線程模型的特點是:服務端用於接收客戶端連接的不再是一個單獨的 NIO 線程,而是一個獨立的 NIO 線程池。Acceptor 接收到客戶端 TCP連接請求並處理完成后(可能包含接入認證等),將新創建的 SocketChannel注 冊 到 I/O 線 程 池(sub reactor 線 程 池)的某個I/O線程上, 由它負責SocketChannel 的讀寫和編解碼工作。Acceptor 線程池僅僅用於客戶端的登錄、握手和安全認證,一旦鏈路建立成功,就將鏈路註冊到後端 subReactor 線程池的 I/O 線程上,由 I/O 線程負責後續的 I/O 操作。

    netty線程模型思考

    netty 的線程模型並不是一成不變的,它實際取決於用戶的啟動參數配置。通過設置不同的啟動參數,Netty 可以同時支持 Reactor 單線程模型、多線程模型。

    為了盡可能地提升性能,Netty 在很多地方進行了無鎖化的設計,例如在 I/O 線程內部進行串行操作,避免多線程競爭導致的性能下降問題。表面上看,串行化設計似乎 CPU 利用率不高,併發程度不夠。但是,通過調整 NIO 線程池的線程參數,可以同時啟動多個串行化的線程并行運行,這種局部無鎖化的串行線程設計相比一個隊列多個工作線程的模型性能更優。(小夥伴們後續多線程併發流程可參考該類實現方案

    Netty 的 NioEventLoop 讀取到消息之後,直接調用 ChannelPipeline 的fireChannelRead (Object msg)。 只要用戶不主動切換線程, 一直都是由NioEventLoop 調用用戶的 ChannelHandler,期間不進行線程切換。這種串行化處理方式避免了多線程操作導致的鎖的競爭,從性能角度看是最優的。

    Netty擁有兩個NIO線程池,分別是bossGroupworkerGroup,前者處理新建連接請求,然後將新建立的連接輪詢交給workerGroup中的其中一個NioEventLoop來處理,後續該連接上的讀寫操作都是由同一個NioEventLoop來處理。注意,雖然bossGroup也能指定多個NioEventLoop(一個NioEventLoop對應一個線程),但是默認情況下只會有一個線程,因為一般情況下應用程序只會使用一個對外監聽端口。

    這裏試想一下,難道不能使用多線程來監聽同一個對外端口么,即多線程epoll_wait到同一個epoll實例上?

    epoll相關的主要兩個方法是epoll_wait和epoll_ctl,多線程同時操作同一個epoll實例,那麼首先需要確認epoll相關方法是否線程安全:簡單來說,epoll是通過鎖來保證線程安全的, epoll中粒度最小的自旋鎖ep->lock(spinlock)用來保護就緒的隊列, 互斥鎖ep->mtx用來保護epoll的重要數據結構紅黑樹

    看到這裏,可能有的小夥伴想到了Nginx多進程針對監聽端口的處理策略,Nginx是通過accept_mutex機制來保證的。accept_mutex是nginx的(新建連接)負載均衡鎖,讓多個worker進程輪流處理與client的新連接。當某個worker進程的連接數達到worker_connections配置(單個worker進程的最大處理連接數)的最大連接數的7/8時,會大大減小獲取該worker獲取accept鎖的概率,以此實現各worker進程間的連接數的負載均衡。accept鎖默認打開,關閉它時nginx處理新建連接耗時會更短,但是worker進程之間可能連接不均衡,並且存在“驚群”問題。只有在使能accept_mutex並且當前系統不支持原子鎖時,才會用文件實現accept鎖。注意,accept_mutex加鎖失敗時不會阻塞當前線程,類似tryLock。

    現代linux中,多個socker同時監聽同一個端口也是可行的,nginx 1.9.1也支持這一行為。linux 3.9以上內核支持SO_REUSEPORT選項,允許多個socker bind/listen在同一端口上。這樣,多個進程可以各自申請socker監聽同一端口,當連接事件來臨時,內核做負載均衡,喚醒監聽的其中一個進程來處理,reuseport機制有效的解決了epoll驚群問題。

    再回到剛才提出的問題,java中多線程來監聽同一個對外端口,epoll方法是線程安全的,這樣就可以使用使用多線程監聽epoll_wait了么,當然是不建議這樣乾的,除了epoll的驚群問題之外,還有一個就是,一般開發中我們使用epoll設置的是LT模式(水平觸發方式,與之相對的是ET默認,前者只要連接事件未被處理就會在epoll_wait時始終觸發,後者只會在真正有事件來時在epoll_wait觸發一次),這樣的話,多線程epoll_wait時就會導致第一個線程epoll_wait之後還未處理完畢已發生的事件時,第二個線程也會epoll_wait返回,顯然這不是我們想要的,關於java nio的測試demo如下:

    public class NioDemo {
        private static AtomicBoolean flag = new AtomicBoolean(true);
        public static void main(String[] args) throws Exception {
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.socket().bind(new InetSocketAddress(8080));
            // non-block io
            serverChannel.configureBlocking(false);
            Selector selector = Selector.open();
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            // 多線程執行
            Runnable task = () -> {
                try {
                    while (true) {
                        if (selector.select(0) == 0) {
                            System.out.println("selector.select loop... " + Thread.currentThread().getName());
                            Thread.sleep(1);
                            continue;
                        }
    
                        if (flag.compareAndSet(true, false)) {
                            System.out.println(Thread.currentThread().getName() + " over");
                            return;
                        }
    
                        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                        while (iter.hasNext()) {
                            SelectionKey key = iter.next();
    
                            // accept event
                            if (key.isAcceptable()) {
                                handlerAccept(selector, key);
                            }
    
                            // socket event
                            if (key.isReadable()) {
                                handlerRead(key);
                            }
    
                            /**
                             * Selector不會自己從已選擇鍵集中移除SelectionKey實例,必須在處理完通道時手動移除。
                             * 下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。
                             */
                            iter.remove();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
    
            List<Thread> threadList = new ArrayList<>();
            for (int i = 0; i < 2; i++) {
                Thread thread = new Thread(task);
                threadList.add(thread);
                thread.start();
            }
            for (Thread thread : threadList) {
                thread.join();
            }
            System.out.println("main end");
        }
    
        static void handlerAccept(Selector selector, SelectionKey key) throws Exception {
            System.out.println("coming a new client... " + Thread.currentThread().getName());
            Thread.sleep(10000);
            SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        }
    
        static void handlerRead(SelectionKey key) throws Exception {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
    
            int num = channel.read(buffer);
            if (num <= 0) {
                // error or fin
                System.out.println("close " + channel.getRemoteAddress());
                channel.close();
            } else {
                buffer.flip();
                String recv = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
                System.out.println("recv: " + recv);
    
                buffer = ByteBuffer.wrap(("server: " + recv).getBytes());
                channel.write(buffer);
            }
        }
    }

    netty線程模型實踐

    (1) 時間可控的簡單業務直接在 I/O 線程上處理

    時間可控的簡單業務直接在 I/O 線程上處理,如果業務非常簡單,執行時間非常短,不需要與外部網絡交互、訪問數據庫和磁盤,不需要等待其它資源,則建議直接在業務 ChannelHandler 中執行,不需要再啟業務的線程或者線程池。避免線程上下文切換,也不存在線程併發問題。

    (2) 複雜和時間不可控業務建議投遞到後端業務線程池統一處理

    複雜度較高或者時間不可控業務建議投遞到後端業務線程池統一處理,對於此類業務,不建議直接在業務 ChannelHandler 中啟動線程或者線程池處理,建議將不同的業務統一封裝成 Task,統一投遞到後端的業務線程池中進行處理。過多的業務ChannelHandler 會帶來開發效率和可維護性問題,不要把 Netty 當作業務容器,對於大多數複雜的業務產品,仍然需要集成或者開發自己的業務容器,做好和Netty 的架構分層。

    (3) 業務線程避免直接操作 ChannelHandler

    業務線程避免直接操作 ChannelHandler,對於 ChannelHandler,IO 線程和業務線程都可能會操作,因為業務通常是多線程模型,這樣就會存在多線程操作ChannelHandler。為了盡量避免多線程併發問題,建議按照 Netty 自身的做法,通過將操作封裝成獨立的 Task 由 NioEventLoop 統一執行,而不是業務線程直接操作,相關代碼如下所示:

    如果你確認併發訪問的數據或者併發操作是安全的,則無需多此一舉,這個需要根據具體的業務場景進行判斷,靈活處理。

    推薦閱讀

    歡迎小夥伴關注【TopCoder】閱讀更多精彩好文。

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

    【其他文章推薦】

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

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

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

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

  • 前端Leader你應該知道的NPM包管理機制

    前端Leader你應該知道的NPM包管理機制

    npm install 命令

    首先總結下npm 安裝一個模塊包的常用命令。

    /* 模塊依賴會寫入 dependencies 節點 */
    
    npm install moduleName
    
    npm install -save moduleName
    
    npm install -S moduleName 
    
    /* 模塊依賴會寫入 devDependencies 節點 */
    
    npm install -save-dev moduleName
    
    npm install -D moduleName
    
    /* 全局安裝模塊包 */
    
    npm install -g moduleName
    
    /* 安裝特定版本的包 */
    
    npm install 包名@版本號
    
    /* 通過地址安裝git倉庫 */
    
    npm install git+https://github.com/itwmike/axios.git
    
    npm install git+ssh://git@github.com:itwmike/axios.git
     
    /* 安裝特定分支或Tag的git倉庫 */
    
    npm install git+https://github.com/itwmike/axios.git#tag
    
    /* 通過用戶名安裝git倉庫 */ 
    
    npm install github:帳號/倉庫名 # npm install github:itwmike/axios
    
    npm install github:帳號/倉庫名
    

      

    npm 依賴包版本號

    npm 所有node包都使用語義化版本號,規則要求如下:  

    • 每個版本號都形如1.2.3,由三個部分組成,依次叫做“主版本號(major)”、“次版本號(minor)”和“修訂號(patch)” 。

    • 當新版本無法兼容基於前一版本的代碼時,則提高主版本號 。

    • 當新版本新增了功能與特性,但仍兼容前一版本的代碼時,則提高次版本號 。

    • 當新版本僅僅修正漏洞或者增強效率,仍然兼容前一版本代碼,則提高修訂號。

    默認使用 npm install -save 下載的都是最新版本,同時會在package.json 文件中登記一個最優版本號,如下形式:

    "dependencies": {
      "axios": "^0.19.0"
    },
    

      

    最優版本號前面會多出一個“標記”,這個標記有啥意義?它的寫法又有哪些?

     

    npm install 都做了哪些事?

    拿到一個node項目時首要做法是運行 npm install 命令,這個命令將 package.json 文件中的依賴包自動解析並安裝,這也是項目能夠本地運行的前置條件。那如此簡單的一條命令,npm 背後又做了哪些不為人知的事呢?

    Number One

    自 npm 5.0后,項目中如果沒有 package-lock.json 文件的時候,npm 會自動幫我們生成。該文件的主要作用是記錄依賴包之間的具體版本號,對包版本有一個鎖定的意義,項目開發中應該將此文件上傳到git等版本控制工具(博主為此經歷了血淋淋的慘痛教育)。

    Number Two

    檢測本地包是否已經下載。如果本地 node_modules 下已經存在和 package-lock.json 中版本一致的包,則不會重新下載。

    Number Three

    下載依賴節點中對應的模板包。下載規則是:如果 package-lock.json 文件存在,則按照該文件中記錄的版本號下載對應的模塊包;如果文件不存在或文件中沒有該包的記錄,此時會按照版本號的標記(上面已講)規範下載並同時更新到 package-lock.json。

    了解了 package-lock.json 的作用后,筆者有個疑問:手動修改 package.json 中的包版本號后運行 npm install 命令會下載新包么?

    帶着這個疑問,筆者做了實驗,得出如下結論:

    • 如果新舊版本號差距較大,比如從 ^2.5.2 變為 2.6.0 ,那麼會下載最新包並且更新 package-lock.json 。

    • 如果新舊版本號差距較小,比如從 ^2.5.2 變為 2.5.4,那麼不會更新。

    總之是否更新要看特定情況,取決於 package.json中版本號的標記和 package-lock.json 是否一致。

    cnpm install 探索

    cnpm 是淘寶 npm 鏡像,在國內很受歡迎,雖然筆者並不喜歡使用。那 cnpm 和 npm 對包的管理是否一樣呢?

    • cnpm install 並不會生成 package-lock.json

    • cnpm install 並不受 package-lock.json 的約束,它會按照版本號標記規則下載依賴包

     

    由此可見,我們在項目中使用 cnpm 的時候一定要慎重,因為很可能團隊成員每個人使用的依賴包版本都不相同,造成打包后的結果也不同。

    如果團隊要使用 cnpm,請使用固定版本號的方式安裝依賴包如:cnpm install -E moduleName

     

    本文轉載自:

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

    【其他文章推薦】

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

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

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

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

  • 微信作弊,為3款小遊戲引擎開掛增速,將給小遊戲帶來怎樣的利好?

    微信作弊,為3款小遊戲引擎開掛增速,將給小遊戲帶來怎樣的利好?

    11月14日小遊戲開發圈子,有一條重磅新聞:“微信小遊戲聯合遊戲引擎廠商,推出引擎插件功能,可為小遊戲提升0.5~2秒的啟動時間”。

    引擎插件是個什麼東西?

    昨天有不少人在問曉衡:“引擎插件到底是個什麼東西?”、“又要讓我學習新東西嗎?”、“引擎插件是怎麼加速的,不太明白?” …

    曉衡也在第一時間,將文檔通讀了一遍,並用自己的小遊戲工程做了測試,對微信小遊戲引擎插件算是有了一個簡單的認識,看下圖:

    普通模式,每一個使用遊戲引擎開發的小遊戲,都需要下載遊戲引擎代碼模塊。

    引擎插件模式,僅第一個遊戲需要下載引擎代碼,其它使用同類引擎的遊戲,可共享之前 A 遊戲下載過的遊戲引擎代碼,從而加速遊戲的啟動時間。

    從事小遊戲開發和運營的夥伴應該都很了解,H5、小遊戲注重啟動加載速度,它對新用戶的體驗和流失都至關重要。

    啟動概況分析

    估計有人會覺得引擎插件就加快了0.5~2秒有什麼用?眨個眼的時間而已。

    其實對使用 Cocos Creator 開發的休閑類的小遊戲來說,目前的微信小遊戲啟動速度已經很不錯了,首包含引擎的話,iOS 在4 ~ 6秒啟動,Android 大多可以在6~8秒左右打開首屏,並不像微信吹噓的1秒啟動,估計只有 引擎和資源全放子包的遊戲可以做到!

    下面我將自己個人開發的一款微信小遊戲,在微信公測的前後两天做了一個數據統計,想窺視一下啟動性能對留存的影響,下圖是曉衡的遊戲在8月7日 ~ 8月9日時的活躍情況:

    遊戲是在8月7日的晚上10:00點打開的微信公測,微信平台在24小時內持續導入5184的用戶,當天遊戲啟動8121次。不過圖片上的數據比較尷尬,公測一過就沒幾個玩家了,但它不是我們要講的重點,我們是用這個時間節點、用戶數量,來看微信小遊戲的啟動性能表現。

    iOS啟動概況

    Android啟動概況

    從圖中看,8月7日這天 iOS 的總啟動時間比 Android 快 3.88 秒,Android 的用戶流失比 iOS 要多 12.55%,這裏重點也不是說 iOS 和 Android 系統那個好,而是看遊戲的啟動時間對首屏打開留存的影響。

    這是另一個朋友的遊戲《周車勞盾》在9月14日微信公測4800+用戶,遊戲啟動7000+次,下面是它在9月13~9月15日的啟動概況:

    《周車勞盾》的 iOS 首屏打開留存率由於用戶數太少,不太好與 Android 對比,並且朋友說當時遊戲沒有做分包優化。在9月14日公測當天,由於新用戶多,iOS、Android 的啟動速度都不快,在 10 秒左右。從中也可以看出微信小遊戲用戶,以及微信導量用戶,以 Android 屬性為主。

    啟動流失分析

    下圖是曉衡的遊戲在8月8日公測時的 Android 手機用戶流失分佈情況,統計一共有 893 名流失用戶:

    從前面的啟動概況看到,小遊戲啟動進入首屏是在8.38s,我們以9秒為分界線,將上圖分成左右兩部分:

    • 右邊標註綠色線框,是已經進入遊戲后流失的人數,這部分的優化需要美術和策劃同學的幫助。
    • 左邊紅色線框中的用戶,是在遊戲啟動過程中流失掉的共計679人佔76%,而且前4秒流失的最多共543占 60%,如果不計算已經打開首屏的更是高達80%,因此前幾秒它才是我們關心的重點。

    曉衡根據平常使用微信的習慣,模擬分析一下前8秒的用戶是大概會是什麼情況走失的,需要注意的一個前題是,這些用戶都是微信導量進入的,絕大多是手滑不小心點到廣告,並不是目標用戶。

    • 第1秒:1秒流失用戶,手滑的機率最大,似乎經過專業訓練,眼、腦、手的速度都非常的快,遊戲是什麼都沒看清就閃人了;
    • 第2秒:2秒流失用戶,與1秒戶大概差不多,只是動作稍慢而已,此時遊戲圖標已經進入視覺系統,但估計比較模糊,瞬間閃人;
    • 第3秒:3秒流失用戶,不僅遊戲圖標已經從視覺系統進入大腦,遊戲名字估計也是能看清楚,但是沒有任何感覺,同樣是條件反射,快速點擊關閉;
    • 第4秒:4秒流失用戶,已經是把遊戲圖標、名字已經完全進入大腦神經迴路,給他反饋的信號是沒有愛,甚至是反感,迅速閃人了。第4秒很關鍵,因為用戶已經有了思考!
    • 第5~6秒:5~6秒流失用戶,認真看完遊戲圖標、名字,以及加載進度,經過大腦綜合反饋,這個遊戲不值得等待,88了!
    • 第7~8秒:7~8秒流失用戶,估計是盯到了遊戲的加載進度,在100%或某個数字上停止下一瞬間,實在是不耐煩了,什麼個鬼遊戲,半天進不去,走了!

    以上分析是曉衡的個人YY,僅供參考,這裏要說的是前 3 秒流失的用戶大多是條件反射,很難轉化。當用戶將遊戲圖標、遊戲名稱看清了后,大腦產生了思考,再離開的這對我們來說還有機會爭取,讓他們早點看到遊戲首屏,已經花了這5、6秒了,體驗一下再走!

    提升遊戲0.5 ~ 2秒的啟動速度是非常具有價值的,而且小遊戲絕大多數又是 Android 用戶,特別是對需要買量的遊戲來說,時間就是金錢,毫秒必爭。

    引擎插件帶來的好處

    下面我們再來看看,引擎插件具體在那些場景下會帶來比較明顯的性能提供,盡可能充分利用這個機制呢?

    微信公測

    對於個人開發者,使用微信公測功能免費送5000流量,一定要利用好這個機會。將首包資源做到盡量小巧,引擎裁剪、圖片壓縮一定要做足,同時盡量選擇使用量較多的引擎版本號(目前曉衡了解到的,使用較多 Cocos Creator 引擎版本號分佈在:2.0.8 ~ 2.0.10、2.1.3、2.2.0,不過還是要以微信或 Cocos 官方統計為準),這樣容易蹭上已經下載過的遊戲引擎,這對大多數遊戲來說都是適用的。

    中重度遊戲

    中重度遊戲,通常會依賴較多的引擎模塊,比如 RPG 遊戲中的:地圖、角色動畫,會使用TileMap、Spine、DragonBones、Animation 等模塊,還有一些遊戲會使用到物理引擎模塊、碰撞模塊等,完整引擎模塊高達 1.6M。

    隨着微信引擎插件的廣泛普及,以後構建遊戲完全時可以將引擎裁剪到最精簡狀態,大概在550K左右。甚至可以想像到,以後小遊戲平台完全不用上傳引擎代碼,構建時只用配置上使用的什麼引擎,引擎版本號即可。

    中重度遊戲利用引擎插件同樣可以快速進入首屏,首包僅保留炫麗的動效和初始界面,用分治的方式動態下載遊戲當前必要的內容,儘快讓用戶參与到遊戲中去。還有隨着 5G 的到來,中重度遊戲的遊戲資源下載劣勢也會得到改善,對小遊戲更是一件好事。

    遊戲矩陣

    單款小遊戲一般是很難有收益的,甚至是虧本買賣。微信平台,一個小遊戲可以支持10個遊戲的跳轉,目前絕大多數遊戲商廠,都會在小遊戲中集成其它遊戲的入口加大流量,優質的遊戲還會出售遊戲跳轉坑位,有的還價格不菲。個人開發者也意識到了遊戲間跳轉帶來的爆光機會,不少開發者會在微信公眾時,組織邀請好友建立鏈接。

    如果是自家開發的休閑小遊戲,利用引擎插件的啟動增速,再配合上自定義的啟動背景(頭條支持),讓玩家感受不出是在不同遊戲中切換,在矩陣中瞬間穿梭,這也將極大增加遊戲的曝光率,降低流失。

    小結

    劉潤老師說的好:“一切的商業價值,要看是否讓用戶獲益”。

    微信引擎插件不僅讓普通用戶能獲得更好的遊戲體驗,也能讓遊戲開發商能中從獲益。點開即玩的小遊戲,縮短了遊戲產品呈現在用戶手中的時間,極大優化了產品的傳遞價值。

    曉衡是一個搬運工,傳遞有價值的遊戲開發技術,如果覺得本文對你有用,感謝來看個再看或傳遞給朋友。感謝您的閱讀,願我們在前進的道路上“砥礪前行,共同成長!”

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

    【其他文章推薦】

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

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

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

  • 盤點2016年起實施的新能源汽車新政策

    盤點2016年起實施的新能源汽車新政策

    電動汽車充電介面及通信協定5項新國標  

    第一版電動汽車充電介面等4項國家標準曾在2011年發佈,並於2012年3月1日起實施,包括《電動汽車傳導充電用連接裝置第1部分:通用要求》、《電動汽車傳導充電用連接裝置第2部分:交流充電介面》、《電動汽車傳導充電用連接裝置第3部分:直流充電介面》《電動汽車非車載傳導式充電機與電池管理系統之間的通信協定》等。此次發佈的5項國家標準在4項標準的修訂基礎上新增《電動汽車傳導充電系統 第1部分:一般要求》,於2016年1月1日起正式實施。

    標準內容:新修訂的5項國家標準主要在提高電動汽車充電設施安全性及相容性方面做出更進一步的要求。

    交流充電部分,更新禁止採用存在安全隱患的直通電纜加普通家用插頭的連接方式,大於16安培的充電方式要求在車輛插座和供電插座安裝電子鎖和溫度感測器等規範。

     

    直流充電部分,更新在直流充電槍內要求安裝電子鎖,同時預留車輛插座加裝電子鎖的機械結構,要求車輛和設施必須具備檢測和告警功能等規範。

     

    新能源車補貼政策  

    2015年4月29日,新一輪新能源汽車補貼政策正式出臺,其中指出在2016-2020年,對消費者購買的進入國家新能源車目錄的純電動汽車、插電式混合動力汽車和燃料電池汽車繼續給予購車補貼。新標準規定新能源車的補貼將分階段退坡,到2020年補貼標準將在2016年基礎上下降40%。

    政策內容:新的補貼標準將依據節能減排效果,並綜合考慮生產成本、規模效應、技術進步等因素逐步下調。具體下調辦法是,2017-2018年補貼標準將在2016年基礎上下降20%,2019-2020年補貼標準在2016年基礎上下降40%。而燃料電池汽車的補貼將從2015年的18萬元/輛提升至20萬元/輛。

     對比2013—2015年新能源車補貼標準,2016年起執行的新補貼標準針對續駛里程大於等於80km小於150km的純電動車和增程式在內的插電式混合動力乘用車的補貼金額分別降低了6500元和1500元,而續駛里程大於等於250km的電動車和燃料電池乘用車補貼金額分別提高了1000元和2萬元,因而您在購買新能源車的時候,續駛里程將是非常關鍵的價格影響因素。

    新補貼標準對補助範圍內的新能源汽車產品技術的要求也有所提高,其中純電動乘用車的最低續駛里程由大於等於80km提升至100km,同時在行駛速度方面,純電動乘用車30分鐘最高車速應不低於100km/h。

    車船稅管理新規  

    規程內容:規程中指出,已經繳納車船稅的車船,因品質原因,車船被退回生產企業或者經銷商的,納稅人可以向納稅所在地的主管稅務機關申請退還自退貨月份起至該納稅年度終了期間的稅款,退貨月份以退貨發票所載日期的當月為准。

    此外,已完稅車輛被盜搶、報廢、滅失而申請車船稅退稅的,由納稅人納稅所在地的主管稅務機關按照有關規定辦理。

    而對不屬於車船稅徵稅範圍的純電動乘用車和燃料電池乘用車,應當積極獲取車輛的相關資訊予以判斷,對其徵收了車船稅的應當及時予以退稅。

    第四階段乘用車燃料消耗量限值  

    政策內容:2012年6月28日,國務院發佈《節能與新能源汽車產業發展規劃(2012-2020年)》,明確了我國汽車節能標準的整體目標,要求2020年當年乘用車新車平均燃料消耗量達到5.0 L/100km。目前,第四階段的GB 19578-2014《乘用車燃料消耗量限值》和GB 27999-2014《乘用車燃料消耗量評價方法及指標》已於2014年12月22日正式發佈,於2016年1月1日起實施,要求汽車生產企業2016年平均燃料消耗標準需滿足6.7L/100km。

    除了以上提到的自2016年1月1日起實施的政策外,還有一些和我們密切相關的政策和措施已在2015年的最後一天終止或完成,讓我們也來關注一下。

    節能惠民補貼政策到期取消

    政策內容:2013年,國家財政部、發展改革委、工業和資訊化部發佈《關於開展1.6升及以下節能環保汽車推廣工作的通知》,決定從2013年10月1日起至2015年12月31日,繼續實施1.6升及以下節能環保汽車(乘用車)推廣政策,對購買符合條件節能汽車的消費者給予3000元補貼。截止2015年末,國家共實施了3個階段的汽車節能補貼,從每一輪政策的推出可以發現,國家對享受節能補貼的車型要求在逐年提高,推廣車輛要達到產品綜合燃料消耗量標準也由2011年的6.9L調整到2013年的5.9L,且需符合“國V”排放標準。

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

    【其他文章推薦】

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

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

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

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

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

  • 賓士傳將開發四款純電動車

    世界級車商賓士(Mercedes-Benz)被報導正在開發全新的電動車平台,並將以此平台為基礎,推出四款純電動汽車。

    ETToday引述外媒《Car Magazine》的報導,表示賓士正以MRA平台為基礎開發一款名為「EVA」的電動車平台,可供後續開發C-Class到S-Class尺碼的各式車款,最高能搭載400公斤的電池組。而在動力方面,後驅車型可能搭載一具402匹馬力的電動馬達,四驅車行可能還會再加上一具120匹馬力或者201匹馬力的馬達。主動懸載、扭力分配、動能回收等功能也將面面俱到。

    該報導表示,賓士官方並未證實這項消息,但將在2018年推出C-Class與E-Class之間的電動轎車,並以此為基礎再行打造一款休旅車。此後還有S-Class和GLS等級的電動車款會問世。《Car Magazine》表示,賓士目標為每年銷售2萬輛電動車,入門車款售價為7萬英鎊起跳。

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

    【其他文章推薦】

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

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

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

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

  • 吉利:到2020年新能源汽車銷量將實現90%以上的占比

    日前,吉利控股集團董事長李書福在接受《中國經營報》記者採訪時表示,以後我們開發的新產品基本是新能源汽車和智慧互聯汽車,傳統汽車就逐漸不生產了。

    此話背後,實際就是吉利於近日提出的“藍色吉利行動”中的重要一環,到2020年,其新能源汽車銷量將實現90%以上的銷量占比。

    具體來說,根據“藍色吉利行動”,吉利新能源將在技術上主打純電動、油電混動、插電式混合動力三種路線,並通過兩大平臺——FE(中高端)與PE(緊湊級)平臺來發展純電動車板塊。

    從以上路徑上看,吉利與其他車企並無二致,但其公佈的“五大承諾”卻足以讓業內熱議——第一,提前全面實現2020年國家第四階段每百公里5.0L的企業平均燃油消耗限值;第二,實現消費者用傳統汽車的購買成本購買插電式混動汽車的夢想;第三,實現到2020年新能源汽車銷量占吉利整體銷量的90%以上;第四,在氫燃料及金屬燃料電池汽車研發方面取得實質性成果;第五,實現新能源技術,智慧化、輕量化技術在行業的領先地位。

    雖然言之鑿鑿,但不得不說的是,90%以上的新能源銷量加上此前吉利發佈的“2020年實現120萬輛”的銷量目標,同年吉利新能源汽車銷量目標竟逾100萬輛。

    對此,吉利控股集團總裁CEO安聰慧表示:“吉利制定這樣的目標並不是為了和其他企業進行對比,而是結合自身發展提出來的。”

    據其介紹,在技術領域,吉利汽車將以與沃爾沃合作打造的CMA中高級車基礎模組架構為核心打造新能源車型,該方面的設計研發工作主要由吉利汽車歐洲研發中心承擔,該中心在瑞典哥德堡已有1200名工程師,中國杭州也有300名工程師,負責架構開發、上車體開發、核心部件開發開發,整車設計、工程製造及新技術的研發。CMA基礎模組架構可以實現電機+發動機等核心部件的批量生產。

    此外,2015年初,吉利與新大洋機電集團成立合資公司並推出知豆電動車,加之此前其子公司上海華普國潤與康迪車業成立的合資公司,署名吉利旗下的新能源車型並不止吉利品牌。雖然,吉利在新能源領域的開疆破土頗有“借力而為”之感,但不論如何,2015年1~11月,在乘聯會統計的自主品牌新能源車銷量占比情況中,吉利節節攀升,的確實現了在自主品牌領域的市占率穩增,也正因如此,加之政策的多重鼓勵,吉利才許下到2020年實現新能源汽車逾百萬的戰略目標。

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

    【其他文章推薦】

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

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

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

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