分類: 3C資訊

  • 高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

    高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

    目錄

    示例代碼託管在:

    博客園地址:

    華為雲社區地址:

    附件PPT來自開發文檔。術語里的cc指的是Chromium Compositor

    一直以來都想了解瀏覽器合成層的運作機制,但是相關的中文資料大多比較關注框架和開發技術,這方面的資料實在是太少了,後來在chromium官方網站的文檔里找到了項目組成員malaykeshav在 2019年4月的一份關於瀏覽器合成流水線的演講PPT,個人感覺裏面講的非常清楚了,由於沒有找到視頻,有些部分只能自行理解,本文僅對關鍵信息做一些筆記,對此感興趣的讀者可以在文章開頭的github倉庫或附件中拿到這個PPT自行學習。

    摘要

    1.合成流水線

    合成流水線,就是指瀏覽器處理合成層的工作流程,其基本步驟如下:

    大致的流程就是說Paint環節會生成一個列表,列表裡登記了頁面元素的繪製指令,接着這個列表需要經過Raster光柵化處理,並在合成幀中處理紋理,最後的Draw環節才是將這些紋理圖展示在瀏覽器內容區。

    2. 預定義UI層

    chromium中預定義了一些指定類型的UI層,大致分為:

    • Not Drawn – 為了處理透明度或濾鏡效果、transform變形或者clip剪裁的非繪製層
    • Solid color layer – 固有顏色層
    • Painted texture layer – Texture紋理會在這個層執行paint渲染和後續的rasterized光柵化任務
    • Transferable resource layer – 共享資源層,可能是GPU裏面的Texture紋理也可能未來會發給GPU的位圖
    • Surface layer – 臨時佔位層,因為自頂向下遍歷layer樹時子樹都還沒處理,需要先佔位最後再填充
    • Nine patch layer – 用於實現陰影的層

    3. paint是什麼意思

    每個層layer是由若干個views組成的,所謂paint,就是每個views將自己對應圖形的繪製指令添加到層的可展示元素列表Display Item List里,這個列表會被添加到一個延遲執行的光柵化任務中,並最終生成當前層的texture紋理(可以理解為當前層的繪製結果),考慮到傳輸性能以及未來增量更新的需求,光柵化的結果會以tiles瓦片形式保存。在chrome中也可以看到頁面瓦片化拆分的結果:

    4. 分層的優勢和劣勢

    分層的優勢和劣勢也在此進行了說明,和之前我們主動思考的答案基本一致(暗爽一下)。

    5. 視圖屬性及其處理方式

    views中支持的屬性包含Clip剪裁,transform變換,effect效果(如半透明或濾鏡等),mask遮罩,通常按照後序遍歷的方式自底向上進行遍歷處理。

    clip剪裁的處理方式是在父節點和子節點之間插入一個剪裁層,用來將其子樹的渲染結果剪裁到限定的範圍內,然後再向上與父級進行合併;

    transform變換直接作用於父節點,處理到這個節點時其子樹都已經處理完畢,直接將整體應用變形即可;

    effect效果一般直接作用於當前處理的節點,有時也會產生交叉依賴的場景;

    PPT第40頁中在介紹effect效果處理時描述了兩種不同的透明度處理需求,從而引出了一個Render Surface的概念,它相當於一個臨時的層,它的子樹需要先繪製在這個層上,然後再向上與父節點進行合併,屏幕就是是根級的Render Surface

    6. Quads

    Layer遍歷處理輸出的結果被稱為Quads(從意思上理解好像就是指輸出了很多個矩形方塊),每個quad都持有它被繪製到目標緩衝區所需要的資源,根據它持有的資源不同可以分為:

    • Solid Color-固定顏色型
    • Texture– 紋理型
    • Tile– 瓦片型
    • Surface– 臨時繪圖表面型
    • Video – 視頻幀型
    • Render PassRender Surface類型的佔位區,Render Surface子樹處理完后填充到關聯的Render Pass

    7. Compositor Frame

    合成層真正的工作要開始了,主角概念Compositor Frame(合成幀)登場,它負責將quads合併繪製在一起,膠片里59-62頁非常清楚地展示了合成的過程,最終輸出的結果就是根節點的紋理。

    chromium是多進程架構,Browser Process瀏覽器進程會對菜單欄等等容器部分的畫面生成合成幀來輸出,每個網頁的Render Process渲染進程會對頁面內容生成合成幀來輸出,最終的結果都被共享給GPU ProcessGPU進程進行聚合併生成最終完整的合成表面,接着在Display Compositor環節將最後的位圖展示在屏幕上。

    8. 關於光柵化以及渲染方式

    膠片里並沒有描述具體的光柵化的處理過程,但是layer輸出的quads看起來應該是光柵化以後的結果,推測應該是處理Display Item List中的繪圖指令時也和WebGL類似,經過頂點着色器片元着色器的遍歷式處理機制,並在過程中自動完成像素插值。

    9.【重要】軟件渲染和硬件渲染的區別

    聲明:本節內容是個人理解,僅用作技術交流,不保證對!

    軟件渲染和硬件渲染的區別對筆者而言一直非常抽象,只是知道基本概念。後來在(國內可能無法訪問)中《Compositor Thread Architecture》這篇合成器線程架構的文章中找到了一些相關描述,也解開了筆者心中一直以來的疑惑,相關部分摘抄如下:

    Texture Upload

    One challenge with all these textures is that we rasterize them on the main thread of the renderer process, but need to actually get them into the GPU memory. This requires handing information about these textures (and their contents) to the impl thread, then to the GPU process, and once there, into the GL/D3D driver. Done naively, this causes us to copy a single texture over and over again, something we definitely don’t want to do.

    We have two tricks that we use right now to make this a bit faster. To understand them, an aside on “painting” versus “rasterization.”

    • Painting is the word we use for telling webkit to dump a part of its RenderObject tree to a GraphicsContext. We can pass the painting routine a GraphicsContext implementation that executes the commands as it receives them, or we can pass it a recording context that simply writes down the commands as it receives them.
    • Rasterization is the word we use for actually executing graphics context commands. We typically execute the rasterization commands with the CPU (software rendering) but could also execute them directly with the GPU using Ganesh.
    • Upload: this is us actually taking the contents of a rasterized bitmap in main memory and sending it to the GPU as a texture.With these definitions in mind, we deal with texture upload with the following tricks:
    • Per-tile painting: we pass WebKit paint a recording context that simply records the GraphicsContext operations into an SkPicture data structure. We can then rasterize several texture tiles from that one picture.
    • SHM upload: instead of rasterizing into a void* from the renderer heap, we allocate a shared memory buffer and upload into that instead. The GPU process then issues its glTex* operations using that shared memory, avoiding one texture copy.The holy grail of texture upload is “zero copy” upload. With such a scheme, we manage to get a raw pointer inside the renderer process’ sandbox to GPU memory, which we software-rasterize directly into. We can’t yet do this anywhere, but it is something we fantasize about.

    大概翻譯一下,方便英語水平一般的小夥伴理解,GPU處理圖片的方式是按照Texture進行貼圖的,對此不熟悉的小夥伴可以查看筆者以前發的有關Three.js相關的博文。

    紋理上傳:
    處理紋理的挑戰之一就是它是在渲染進程(可以理解為單個Tab網頁的進程)的主線程里進行的,但是最終需要將其放入GPU內存。這就需要將紋理數據遞交給合成器線程,然後再交給GPU進程(Chromium架構里有專門的GPU進程用來專門處理和GPU之間的協作任務),最後再傳遞給底層的Direct3DOpenGL(也就是圖形學的底層技術),如果只是按照常規流程來處理,就會需要一次又一次來複制生成的紋理數據,這顯然不是我們想要的。
    我們現在使用了兩個小方法來使這個流程變得快一點。它們分別作用於painting(繪製)和rasterization(光柵化)兩個階段。

    • 1號知識點!!!Painting我們用來告訴webkit為RenderObject Tree的來生成對應的GraphicsContext。通過給painting routine(繪製流程)傳遞一個GraphicsContext的具體實現來執行這些已經編排好的繪製命令,也可以傳遞一個record context(記錄上下文)只是簡單地把繪圖命令都記錄下來。
    • 2號知識點!!!Rasterization(光柵化)是指Graphics context關聯的繪圖命令實際被執行的過程。通常我們使用CPU(也就是軟件渲染的方式)來執行光柵化任務,也可以直接使用GPU來渲染(也就是硬件渲染的方式)。
    • 上傳:指在主線程存儲區獲取到光柵化以後的位圖內容然後將它作為紋理上傳給GPU的過程,考慮到上述已經提及的定義,上傳過程是如下來處理的:
      • 瓦片繪製:我們在webkit中使用recording context來簡單地記錄Graphics Context的操作指令,將它存儲為SkPicture類型(直接使用軟件光柵化時生成的是SkBitmap類型),隨後可以從一張picture裏面光柵化處理得到多個紋理瓦片
      • 共享內存:在軟件渲染的方式中,光柵化的結果會被存儲在renderer進程的堆內存里,現在不這樣搞了,我們重新分配了一塊共享緩衝區,然後通過它來傳遞相關對象,GPU進程隨後在獲取紋理時直接從共享內存中獲取就行了,這樣就避免了數據的拷貝。
        總的來說,紋理上傳的過程幾乎是零拷貝的。利用這樣的結構,我們在renderer進程(也就是網頁的渲染進程)的沙箱環境內也可以獲取到指向GPU 內存的指針,而在軟件光柵化的過程中,是直接將位圖結果放在這裏的。
    • Painting: this is the process of asking Layers for their content. This is where we ask webkit to tell us what is on a layer. We might then rasterize that content into a bitmap using software, or we might do something fancier. Painting is a main thread operation.
    • Drawing: this is the process of taking the layer tree and smashing it together with OpenGL onto the screen. Drawing is an impl-thread operation.
    • painting:表示的過程是向Layers對象查詢層內容,也就是讓webkit告訴我們每一層上面到底有什麼。接下來我們就可以使用軟件光柵化的方式將這些內容處理為位圖,也可以做一些更牛的事情,painting是一個主線程行為。
    • drawing:是指將Layer中的內容用OpenGL繪製在屏幕上的過程,它是另一個線程中的操作。

    概念比較多沒有基礎的讀者可能理解起來有難度,我嘗試用自己的話複述一下:

    【軟件渲染】的模式下,在paint時會直接利用Graphics Context繪圖上下文將結果繪製出來,在一個SkBitmap實例中保存為位圖信息;【硬件渲染】的模式下,在paint時傳入一個SkPicture實例,將需要執行的繪圖命令保存在裏面先不執行,然後通過共享內存將它傳給GPU進程,藉助GPU來最終去執行繪圖命令,生成多個瓦片化的位圖紋理結果(OpenGL中頂點着色器向片元着色器傳遞數據時可以自動進行數據插值,完成光柵化的任務)。 純軟件渲染里嚴格說是沒有合成層概念的,因為最終輸出的只有一張位圖,按照順序從下往上畫,和畫到一個新層上再把新層貼到已有結果上其實是一樣的。

    不管使用哪種途徑,paint動作都是得到位圖數據,而最終的draw這個動作是藉助OpenGL和位圖數據最終把圖形显示在显示器上。

    所以【硬件渲染】就是渲染進程把要做的事情和需要的數據都寫好,然後打包遞給GPU讓它去幹活。

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

    【其他文章推薦】

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

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

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

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

  • EFK教程 – ElasticSearch高性能高可用架構

    EFK教程 – ElasticSearch高性能高可用架構

    通過將elasticsearch的data、ingest、master角色進行分離,搭建起高性能+高可用的ES架構

    作者:“發顛的小狼”,歡迎轉載與投稿

    目錄

    ▪ 用途
    ▪ 架構
    ▪ 步驟說明
    ▪ elasticsearch-data部署
    ▪ elasticsearch-ingest部署
    ▪ elasticsearch-master部署

    用途

    在第一篇《EFK教程 – 快速入門指南》中,闡述了EFK的安裝部署,其中ES的架構為三節點,即master、ingest、data角色同時部署在三台服務器上。

    在本文中,將進行角色分離部署,並且每個角色分別部署三節點,在實現性能最大化的同時保障高可用。

    ▷ elasticsearch的master節點:用於調度,採用普通性能服務器來部署
    ▷ elasticsearch的ingest節點:用於數據預處理,採用性能好的服務器來部署
    ▷ elasticsearch的data節點:用於數據落地存儲,採用存儲性能好的服務器來部署

    若不知道去哪找《EFK教程 - 快速入門指南》,可在主流搜索引擎里搜索:
    小慢哥 EFK教程 快速入門指南
    或者
    小慢哥 EFK教程 基於多節點ES的EFK安裝部署配置

    架構

    服務器配置

    注意:此處的架構是之前的文章《EFK教程 – 快速入門指南》的拓展,因此請先按照《EFK教程 – 快速入門指南》完成部署

    步驟說明

    1️⃣ 部署3台data節點,加入原集群
    2️⃣ 部署3台ingest節點,加入原集群
    3️⃣ 將原有的es索引遷移到data節點
    4️⃣ 將原有的es節點改造成master節點

    elasticsearch-data部署

    之前已完成了基礎的elasticsearch架構,現需要新增三台存儲節點加入集群,同時關閉master和ingest功能

    elasticsearch-data安裝:3台均執行相同的安裝步驟

    tar -zxvf elasticsearch-7.3.2-linux-x86_64.tar.gz
    mv elasticsearch-7.3.2 /opt/elasticsearch
    useradd elasticsearch -d /opt/elasticsearch -s /sbin/nologin
    mkdir -p /opt/logs/elasticsearch
    chown elasticsearch.elasticsearch /opt/elasticsearch -R
    chown elasticsearch.elasticsearch /opt/logs/elasticsearch -R
    # 數據盤需要elasticsearch寫權限
    chown elasticsearch.elasticsearch /data/SAS -R
    
    # 限制一個進程可以擁有的VMA(虛擬內存區域)的數量要超過262144,不然elasticsearch會報max virtual memory areas vm.max_map_count [65535] is too low, increase to at least [262144]
    echo "vm.max_map_count = 655350" >> /etc/sysctl.conf
    sysctl -p

    elasticsearch-data配置

    ▷ 192.168.1.51 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.51
    # 數據盤位置,如果有多個硬盤位置,用","隔開
    path.data: /data/SAS
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.51
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 關閉ingest功能
    node.ingest: false
    # 開啟data功能
    node.data: true

    ▷ 192.168.1.52 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.52
    # 數據盤位置,如果有多個硬盤位置,用","隔開
    path.data: /data/SAS
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.52
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 關閉ingest功能
    node.ingest: false
    # 開啟data功能
    node.data: true

    ▷ 192.168.1.53 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.53
    # 數據盤位置,如果有多個硬盤位置,用","隔開
    path.data: /data/SAS
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.53
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 關閉ingest功能
    node.ingest: false
    # 開啟data功能
    node.data: true

    elasticsearch-data啟動

    sudo -u elasticsearch /opt/elasticsearch/bin/elasticsearch

    elasticsearch集群狀態

    curl "http://192.168.1.31:9200/_cat/health?v"

    elasticsearch-data狀態

    curl "http://192.168.1.31:9200/_cat/nodes?v"

    elasticsearch-data參數說明

    status: green  # 集群健康狀態
    node.total: 6  # 有6台機子組成集群
    node.data: 6  # 有6個節點的存儲
    node.role: d  # 只擁有data角色
    node.role: i  # 只擁有ingest角色
    node.role: m  # 只擁有master角色
    node.role: mid  # 擁master、ingest、data角色

    elasticsearch-ingest部署

    現需要新增三台ingest節點加入集群,同時關閉master和data功能

    elasticsearch-ingest安裝:3台es均執行相同的安裝步驟

    tar -zxvf elasticsearch-7.3.2-linux-x86_64.tar.gz
    mv elasticsearch-7.3.2 /opt/elasticsearch
    useradd elasticsearch -d /opt/elasticsearch -s /sbin/nologin
    mkdir -p /opt/logs/elasticsearch
    chown elasticsearch.elasticsearch /opt/elasticsearch -R
    chown elasticsearch.elasticsearch /opt/logs/elasticsearch -R
    
    # 限制一個進程可以擁有的VMA(虛擬內存區域)的數量要超過262144,不然elasticsearch會報max virtual memory areas vm.max_map_count [65535] is too low, increase to at least [262144]
    echo "vm.max_map_count = 655350" >> /etc/sysctl.conf
    sysctl -p

    elasticsearch-ingest配置

    ▷ 192.168.1.41 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.41
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.41
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 開啟ingest功能
    node.ingest: true
    # 關閉data功能
    node.data: false

    ▷ 192.168.1.42 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.42
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.42
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 開啟ingest功能
    node.ingest: true
    # 關閉data功能
    node.data: false

    ▷ 192.168.1.43 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.43
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.43
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    # 關閉master功能
    node.master: false
    # 開啟ingest功能
    node.ingest: true
    # 關閉data功能
    node.data: false

    elasticsearch-ingest啟動

    sudo -u elasticsearch /opt/elasticsearch/bin/elasticsearch

    elasticsearch集群狀態

    curl "http://192.168.1.31:9200/_cat/health?v"

    elasticsearch-ingest狀態

    curl "http://192.168.1.31:9200/_cat/nodes?v"

    elasticsearch-ingest參數說明

    status: green  # 集群健康狀態
    node.total: 9  # 有9台機子組成集群
    node.data: 6  # 有6個節點的存儲
    node.role: d  # 只擁有data角色
    node.role: i  # 只擁有ingest角色
    node.role: m  # 只擁有master角色
    node.role: mid  # 擁master、ingest、data角色

    elasticsearch-master部署

    首先,將上一篇《EFK教程 – 快速入門指南》中部署的3台es(192.168.1.31、192.168.1.32、192.168.1.33)改成只有master的功能, 因此需要先將這3台上的索引數據遷移到本次所做的data節點中

    1️⃣ 索引遷移:一定要做這步,將之前的索引放到data節點上

    curl -X PUT "192.168.1.31:9200/*/_settings?pretty" -H 'Content-Type: application/json' -d'
    {
      "index.routing.allocation.include._ip": "192.168.1.51,192.168.1.52,192.168.1.53"
    }'

    2️⃣ 確認當前索引存儲位置:確認所有索引不在192.168.1.31、192.168.1.32、192.168.1.33節點上

    curl "http://192.168.1.31:9200/_cat/shards?h=n"

    elasticsearch-master配置

    注意事項:修改配置,重啟進程,需要一台一台執行,要確保第一台成功后,再執行下一台。重啟進程的方法:由於上一篇文章《EFK教程 – 快速入門指南》里,是執行命令跑在前台,因此直接ctrl – c退出再啟動即可,啟動命令如下

    sudo -u elasticsearch /opt/elasticsearch/bin/elasticsearch

    ▷ 192.168.1.31 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.31
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.31
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    #開啟master功能
    node.master: true
    #關閉ingest功能
    node.ingest: false
    #關閉data功能
    node.data: false

    ▷ 192.168.1.32 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.32
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.32
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    #開啟master功能
    node.master: true
    #關閉ingest功能
    node.ingest: false
    #關閉data功能
    node.data: false

    ▷ 192.168.1.33 /opt/elasticsearch/config/elasticsearch.yml

    cluster.name: my-application
    node.name: 192.168.1.33
    path.logs: /opt/logs/elasticsearch
    network.host: 192.168.1.33
    
    discovery.seed_hosts: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    cluster.initial_master_nodes: ["192.168.1.31","192.168.1.32","192.168.1.33"]
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
    #開啟master功能
    node.master: true
    #關閉ingest功能
    node.ingest: false
    #關閉data功能
    node.data: false

    elasticsearch集群狀態

    curl "http://192.168.1.31:9200/_cat/health?v"

    elasticsearch-master狀態

    curl "http://192.168.1.31:9200/_cat/nodes?v"

    至此,當node.role里所有服務器都不再出現“mid”,則表示一切順利完成。

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

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

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

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

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

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

  • canvas繪製工作流之繪製節點

    canvas繪製工作流之繪製節點

       上一篇我們介紹了canvas繪製工作流的大概步驟,接下來會有系列文章細緻的介紹怎麼用canvas繪製工作流;這篇文章主要介紹用canvas繪製流程節點。

      繪製前我們需要先準備一張節點圖片,例如:;好了,正題開始:

    1. html中添加canvas標籤:
    <canvas id="canvasId" width = "800" height="600" style="border:1px solid black;  margin-left: 1px;"></canvas>

    這裏要注意設置canvas標籤的寬度跟高度,也就是要設置畫布的寬度跟高度。

    1. 獲取畫布對象並初始化畫布參數
    var _canvas= document.getElementById(“canvasId”);
    
    var _height = _canvas.height;//獲取畫布高度
    
    var _width = _canvas.width;//獲取畫布寬度
    
    Var ctx =_canvas.getContext('2d');
    
    //畫個畫布大小的長方形,目的是為了有個好看的小邊框框
    ctx.clearRect(0, 0, _width, _height);
    
    /*繪製畫布的背景線*/
    //設置線寬
    ctx.lineWidth  = 0.1;
    //繪製縱向背景線
    for(var i = 1; i < _width / 15; i++) {
      ctx.beginPath();
      ctx.moveTo(i * 15, 0);
      ctx.lineTo(i * 15, _height);
      ctx.stroke();
    }
    //繪製橫向背景線
    for(var i = 1; i < _ height / 15; i++) {
      ctx.beginPath();
      ctx.moveTo(0, i * 15);
      ctx.lineTo(_width, i * 15);
      ctx.stroke();
    }

     

    繪製完效果如圖:

    1. 獲取節點圖片對象
       //創建新的圖片對象
      
       var _img = new Image();
      
        //指定圖片的URL
      
       _img.src="node.png";

                我這裏為了舉個例子直接創建圖片對象,實際繪製過程中可以直接獲取圖片對象,因為動態創建圖片對象是有個圖片加載的時間。

    1. 繪製節點圖片
    ctx.drawImage(_img,_x,_y,_imgWidth, _imgHeight);

        這裏_img是上面獲取到的圖片對象,_x是圖片要繪製在畫布中的x坐標,_y是圖片要繪製在畫布中的_y坐標,_imgWidth是要將圖片繪製的寬度,_imgHeight是要將 圖片繪製的寬度。

        實際應用過程中,一般都會當去鼠標的位置當做x坐標跟y坐標,具體的後面文章會介紹到。

           繪製的效果圖:

       節點下面的文字後面文章會詳細講到怎麼繪製。

      每天get一點點,每天成長一點點,好了,今天就到這裏。

            

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

    【其他文章推薦】

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

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

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

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

  • 敲開通往架構師的門

    最近學習了一些關於架構設計的知識想分享給大家。俗話說得好,不想當架構師的程序員不是好廚子。那麼如何成為一名架構師呢?接下來就聊一聊我的一些想法。

    什麼是架構師

    之前有同學問我,做了幾年技術,應該轉管理還是轉架構師?對於這位同學,我給他的答案是,你要先踏踏實實做好現在的工作。因為就他提的問題來看,應該是剛入行不久或者是在校學生。

    專心做技術的,都想做架構師。但架構師並不是說技術做時間長了可以轉的。隨着你的知識深度和廣度的增加,在工作中會扮演更重要的角色,承擔更大的責任,最終自然而然就會接觸到架構設計的工作。

    而架構師的主要工作,其實是利用架構設計知識以及豐富的工作經驗,在設計架構時,結合實際情況,在不同的選項中做出取捨。

    架構設計的真正目的?

    為什麼要進行架構設計?因為架構設計很重要?可是為什麼重要呢?似乎說不清楚。

    因為可以提升開發效率嗎?也不一定,因為只有簡單的設計才會使開發效率更高。而架構設計出於多方面考慮,不得已會引入一些複雜度,因此架構設計並不一定能提升開發效率。

    是為了大多數口中的“高可用”、“高性能”、“可擴展”嗎?其實也不是。我們的系統可能並不一定需要這些。

    那架構設計的真正目的是什麼呢?我認為架構設計的真正目的是與系統複雜度做鬥爭。

    系統複雜度的來源有:高性能、高可用、可擴展性、低成本、安全、規模

    前面我們聊到有些系統可能不需要高可用、高性能。有些同學可能不理解,這些難道不是軟件開發最基本的要求嗎?這樣的說法是存在一定偏差的。我們舉一個簡單的例子說明一下。

    如果讓你為一所學校設計一個學生信息管理系統。針對上述幾個複雜度的來源,你會做出怎樣的取捨?我們來逐條分析一下。

    首先是高性能,學校的學生最多也就幾萬人,而且平時也不可能幾萬人同時用系統。因此我們並不需要考慮高性能。數據的CRUD直接用關係型數據庫就足夠了。

    然後是高可用,對於學生系統而言,即使宕機幾個小時,影響也不會太大。不過數據的可靠性還是要保證的,如果大量數據丟失而又沒有備份的話,數據修復將會是一項繁重的工作。所以這裏需要做一些數據高可靠的設計。

    接下來是可擴展性,學生管理系統一般比較穩定,不會出現需要擴展的情況。因此我們也不太需要考慮可擴展性。

    至此,我們在設計系統時習慣考慮的高可用、高性能和可擴展,在這個系統中都不需要過多關注了。我們再來看看剩下的幾個複雜度來源。

    關於低成本,由於我們並不需要高可用和高性能的設計,所以幾台服務器的成本對於學校來說也不足為慮。

    安全性而言,學生信息需要一定的安全保證,但也不必做到金融級安全。所以只需要做好數據庫權限管理,登錄密碼管理就足夠了。

    最後是系統規模,學生管理系統往往不會很複雜。也不會迭代出許多功能。因此規模是比較固定且比較小的,不會帶來很多的複雜度。

    從我們的分析中可以看出,學生管理系統是一個並不複雜的系統,我們真正需要着重考慮的就只有數據高可靠和數據安全兩方面。面對複雜的系統,我們也應該按照這個步驟來思考並設計出合理的架構。在合理的情況下,盡量減少系統的複雜度。

    架構設計原則

    前面我們提到,架構師的工作其實就是在多種選項中做出合理的取捨,取捨沒有對錯之分,只有是否合適一說。為了更好的做出選擇,架構設計應該遵循三個原則:合適原則、簡單原則、演化原則。下面我來一一介紹這三個原則。

    合適原則

    我們一直在說,架構設計中架構師要做出取捨,選擇合適的架構。之所以一直強調合適,是因為我們在架構設計過程中需要結合實際情況來考慮。

    那麼脫離實際情況的設計通常是怎樣發生的呢?不知道大家在開發時有沒有遇到過這樣的需求:“我們決定做一個電商網站,就按照淘寶做一個一模一樣的吧。“這時作為開發的你一定是黑人問號臉,心裏也會萬馬奔騰。

    在架構設計時也是一樣,最忌諱的就是不顧實際情況,盲目的使用業界最優的架構設計。有同學可能不太理解,使用最優設計有什麼錯呢?

    這裏我們所說的實際情況就是你的業務。試想如果你的業務剛剛起步,QPS剛過百,這時,你設計的架構是能支持1000QPS還是3000QPS對於系統來說沒什麼區別。但對於開發成本來說就提升了不止3倍。而對於這樣的業務體量來說,開發團隊一般只有十幾人或幾十人這樣的規模。要讓這樣的團隊來開發的話,大概率是無法完成的。

    演化原則

    聊完了合適原則,我們再來聊一聊演化原則。就像北京的城市規劃一樣,它一定是先有二環,慢慢向外擴建,才逐漸有了三四五六環。而我們現在所使用的大多數軟件,也都是經過了許多版本的迭代才有了現在的功能。

    對於一名合格的架構師來說,我們首先要遵循合適原則,然後再逐步演化。切不可想着一步到位,從而引起過度設計。當業務發展到一定階段時,我們不可避免的會需要對架構進行擴展、重構甚至重寫。在這一過程中,我們應該保留下好的設計,對不好的設計進行完善。就像淘寶的架構一樣,它是經歷了多次“雙十一”之後,才有了現在這樣能支撐每天上千億成交額的架構。

    因此,我們在設計架構時要遵循的第二個原則就是循序漸進的演化原則,而不是追求一步到位。

    簡單原則

    最後再來說簡單原則。前面我們也說了,架構設計其實是在和系統的複雜度做鬥爭。為什麼要有簡單原則?我認為原因主要有兩點。

    第一,複雜的架構開發成本更高。在開發資源有限的情況下,如果我們的架構設計很複雜,勢必會提升開發成本。而對於當今飛速發展的市場來說,時間就是生命。如果你設計的架構開發周期非常長,那麼公司也許就會放棄這個項目,那麼架構也就沒有存在的意義了。

    第二,複雜的架構往往會帶來更多的故障。舉個栗子,電動牙刷和普通牙刷相比,壞的概率一定會高一點,電動牙刷可能出現刷頭磨損,電路問題,充電故障等等,而普通牙刷只會出現刷頭磨損的情況。也就是說,系統的組件越多,系統出現故障的概率也就越大。在此基礎上還有一個問題就是,一旦出了故障,定位問題的速度而言,簡單系統相較於複雜系統也有着很大的優勢。

    至此,架構設計的三個原則我們都已經聊完了。細心的同學可能注意到了,我在詳細介紹時的順序和最開始提到的順序並不一致。這不是我不注意細節。而是我在詳細介紹時,對這三個原則的重要程度排了一個順序。這也是作為架構師的一種取捨,當三種原則無法同時滿足時,應該以哪個為重?這裏我的答案是合適>演化>簡單

    關於架構設計,我已經有了一個大體的認識,不知道在讀完本文以後你是否也有同樣的感覺。如果有任何困惑,歡迎和我一起討論交流。

    最後,架構師是需要有很深的技術積累的,而我在這方面做得還不夠。所以後面還是要以技術積累為主,同時也會嘗試將架構設計的知識引入到日常工作中。後續有什麼新的體會我會繼續和大家分享。

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

    【其他文章推薦】

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

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

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

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

  • 【故障公告】新版博客後台部署時的配置問題引發故障

    【故障公告】新版博客後台部署時的配置問題引發故障

    最近,我們對新版博客後台(Angular 8.2.7 + .NET Core 3.0)進行了灰度發布,如果您訪問博客後台時跳轉到 ,說明使用的就是新版博客後台。

    今天我們在一次基於 gitlab-ci 的自動化發布過程中,由於操作問題在發布前沒有對 appsettings.Production.json 的修改進行保存,造成容器在啟動時使用了舊版的配置文件,再加上容器的健康檢查不能檢查出這種不正常情況(這個地方的改進還沒完成),最不該的是在發布后沒有對關鍵功能進行測試驗證以及值班人員沒有及時處理用戶反饋,從而造成 18:22~19:27 期間使用新版博客后的用戶無法正常發布博文,非常抱歉由此給您帶來了麻煩,請您諒解。

    我們會吸取教訓,並採取以下改進措施:

    • 更高優先級改進健康檢查。一是容器的健康檢查,二是阿里云云監控的健康檢查。當關鍵功能不可用時,讓健康檢查失敗(之前的健康檢查沒有對業務功能進行檢查)。這樣發布時如果出現問題,容器健康檢查失敗,docker swarm 就不會部署新容器。當正在運行的容器出現問題影響關鍵功能的使用時及時報警。
    • 盡可能實現在生產環境發布後用“機器人”對關鍵功能進行測試驗證。
    • 每次自動化發布時在值班群發消息通知值班人員留意用戶反饋。

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

    【其他文章推薦】

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

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

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

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

  • MySQL系列:一句SQL,MySQL是怎麼工作的?

    MySQL系列:一句SQL,MySQL是怎麼工作的?

    對於MySQL而言,其實分為客戶端與服務端。

    • 服務端,就是MySQL應用,當我們使用net start mysql命令啟動的服務,其實就是啟動了MySQL的服務端。

    • 客戶端,負責發送請求到服務端並從服務端獲取數據,客戶端可以有多種形式,可以是我們通過mysql -uroot -p1234打開的黑窗口,也可以是我們使用的Nativecat、SQLyog等數據庫連接工具,甚至我們的程序,也可以稱作MySQL的客戶端。

    而當我們在mysql窗口或者數據庫連接工具中輸入一句sql后,我們就可以獲取到想要的數據,這中間MySQL到底是怎麼工作的呢?

    在我們執行SQL后,例如一句簡單的select * from user where name = ‘yanger’,客戶端發送請求到服務端,請求到達Server層,會經過連接器、查詢緩存、分析器、優化器、執行器等,最終通過存儲引擎從文件系統獲取數據或者插入數據到文件系統。

    連接器

    在客戶端程序發起連接的時候,需要攜帶主機信息、用戶名、密碼,服務器程序會對客戶端程序提供的這些信息進行認證,如果認證失敗,服務器程序會拒絕連接。

    連接命令大家都比較熟悉。

    mysql -h$ip -P$port -u$user -p

    輸完命令之後,需要繼續輸入密碼,密碼也可以直接跟在 -p 後面,但這樣可能會導致你的密碼泄露,如果你連的是生產服務器,強烈建議你不要這麼做。

    MySQL採用TCP作為服務器和客戶端之間的網絡通信協議,完成 TCP 握手后,連接器主要做密碼校驗和權限獲取。

    • 如果用戶名或密碼不對,你就會收到一個”Access denied for user”的錯誤

    • 如果用戶名密碼認證通過,連接器會到權限表裡面查出你擁有的權限。之後,這個連接裏面的權限判斷邏輯,都將依賴於此時讀到的權限

    MySQL的默認連接是8小時,由參數 wait_timeout 控制的,如果超過這個時間不使用,會自動斷開,並在之後的操作中,拋出Lost connection to MySQL server during query的錯誤。

    查詢緩存

    針對於查詢語句,MySQL 拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執行過這條語句,之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。如果命中緩存,將直接返回結果。如果不在查詢緩存中,就會繼續後面的執行階段。執行完成后,執行結果會被存入查詢緩存中。

    針對於更新語句,包含插入刪除語句,MySQL 收到更新請求時,會把查詢緩存中該表相關的緩存數據全部清空。

    我們可以看到,只要有更新,緩存就會失效,而對於正常的業務,更新其實是比較頻繁的,也就是說,其實MySQL的查詢緩存命中率並不會很高,所以建議一般不到開啟。

    可以通過設置 query_cache_type 為 DEMAND 來關閉查詢緩存功能。而事實上,在 MySQL 8.0 版本,更是直接移除了查詢緩存這一個功能。

    分析器

    MySQL 首先需要對SQL語句進行分析,分析過程本質上算是一個編譯過程,涉及詞法解析、語法分析、語義分析等階段,通過分析MySQL知道自己要做什麼。

    如果語句不對,就會收到“You have an error in your SQL syntax”的錯誤提醒,一般語法錯誤會提示第一個出現錯誤的位置,所以你要關注的是緊接“use near”的內容。

    優化器

    面對分析器拿到的結果,MySQL會做一些優化處理,例如在表裡面有多個索引的時候,決定使用哪個索引,或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序。

    優化的結果就是生成一個執行計劃,這個執行計劃表明了應該使用哪些索引進行查詢,表之間的連接順序是啥樣的。我們可以使用EXPLAIN語句來查看某個語句的執行計劃。

    這裏\G在命令窗口無法一行時,可以豎著展示結果,方便查看。

    執行器

    經過了分析器和優化器,就正式進行執行階段了,不過執行之前,需要做權限驗證,如果權限不足,就會拋出權限的錯誤。其實在查詢緩存的時候,一樣也會進行權限校驗。

    如果通過驗證,執行器就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。

    存儲引擎

    MySQL支持非常多種存儲引擎,常用的是InnoDB和MyISAM,MySQL的默認存儲引擎是InnoDB。

    假如我們選擇是InnoDB引擎,對於查詢,那InnoDB 會取這個表的第一行來進行判斷是不是符合要求,符合則存在結果集中,否則繼續進行下一行,直到該表的最後一行。

    然後存儲引擎將結果返回給執行器, 執行器拿着結果返回給客戶端,這樣一句SQL就執行完成了。

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

    【其他文章推薦】

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

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

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

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

  • Abp vNext 自定義 Ef Core 倉儲引發異常

    Abp vNext 自定義 Ef Core 倉儲引發異常

    問題

    在使用自定義 Ef Core 倉儲和 ABP vNext 注入的默認倉儲時,通過兩個 Repository 進行 Join 操作,提示 Cannot use multiple DbContext instances within a single query execution. Ensure the query uses a single context instance. 。這個異常信息翻譯成中文的大概意思就是,你不能使用兩個 DbContext 裏面的 DbSet 進行 Join 查詢。

    如果將自定義倉儲改為 IRepository<TEntity,TKey> 進行注入,是可以與 _courseRepostory 進行關聯查詢的。

    我在 XXXEntityFrameworkCoreModule 的配置,以及自定義倉儲 EfCoreStudentRepository 代碼如下。

    XXXEntityFrameworkCoreModule 代碼:

    public class XXXEntityFrameworkCoreModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddAbpDbContext<XXXDbContext>(op =>
            {
                op.AddDefaultRepositories();
            });
            
            Configure<AbpDbContextOptions>(op => op.UsePostgreSql());
        }
    }

    EfCoreStudentRepository 代碼:

    public class EfCoreStudentRepository : EfCoreRepository<IXXXDbContext, Student, long>, IStudentRepository
    {
        public EfCoreStudentRepository(IDbContextProvider<IXXXDbContext> dbContextProvider) : base(dbContextProvider)
        {
        }
    
        public Task<int> GetCountWithStudentlIdAsync(long studentId)
        {
            return DbSet.CountAsync(x=>x.studentId == studentId);
        }
    }

    原因

    原因在異常信息已經說得十分清楚了,這裏我們需要了解兩個問題。

    1. 什麼原因導致兩個倉儲內部的 DbContext 不一致?
    2. 為什麼 ABP vNext 自己實現的倉儲能夠進行關聯查詢呢?

    首先我們得知道,倉儲內部的 DbContext是怎麼獲取的。我們的自定義倉儲都會繼承 EfCoreRepository ,而這個倉儲是實現了 IQuerable<T> 接口的,最終它會通過一個 IDbContextProvider<TDbContext> 獲得一個可用的 DbContext

    public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity>
        where TDbContext : IEfCoreDbContext
        where TEntity : class, IEntity
    {
        public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>();
    
        DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>();
    
        // 這裏可以看到,是通過 IDbContextProvider 來獲得 DbContext 的。
        protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext();
    
        protected virtual AbpEntityOptions<TEntity> AbpEntityOptions => _entityOptionsLazy.Value;
    
        private readonly IDbContextProvider<TDbContext> _dbContextProvider;
        private readonly Lazy<AbpEntityOptions<TEntity>> _entityOptionsLazy;
    
        // ... 其他代碼。
    }

    下面就是 IDbContextProvider<TDbContext> 內部的核心代碼:

    public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : IEfCoreDbContext
    {
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IConnectionStringResolver _connectionStringResolver;
    
        // ... 其他代碼。
    
        public TDbContext GetDbContext()
        {
            var unitOfWork = _unitOfWorkManager.Current;
            if (unitOfWork == null)
            {
                throw new AbpException("A DbContext can only be created inside a unit of work!");
            }
    
            var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
            var connectionString = _connectionStringResolver.Resolve(connectionStringName);
    
            // 會構造一個 Key,而這個 Key 剛好是泛型類型的 FullName。
            var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";
    
            // 內部是從一個字典當中,根據 dbContextKey 獲取 DbContext。如果不存在的話則調用工廠方法創建一個新的 DbContext。
            var databaseApi = unitOfWork.GetOrAddDatabaseApi(
                dbContextKey,
                () => new EfCoreDatabaseApi<TDbContext>(
                    CreateDbContext(unitOfWork, connectionStringName, connectionString)
                ));
    
            return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext;
        }
    
        // ... 其他代碼。
    }

    通過以上代碼我們就可以知道,ABP vNext 在倉儲的內部是通過 IDbContextProvider<TDbContext> 中的 TDbContext 泛型,來確定是否構建一個新的 DbContext 對象。

    不論是 ABP vNext 針對 IRepository<TEntity,TKey> ,還是我們自己實現的自定義倉儲,它們最終的實現都是基於 EfCoreRepository<TDbContext,TEntity,TKey> 的。而我們 IDbContextProvider<TDbContext> 的泛型,也是這個倉儲基類提供的,後者的 TDbContext 就是前者的泛型參數。

    所以當我們在模塊添加 DbContext 的過城中,只要調用了 AddDefaultRepositories() 方法,ABP vNext 就會遍歷你提供的 TDbContext 所定義的實體,然後為這些實體建立默認的倉儲。

    在注入倉儲的時候,找到了獲得默認倉儲實現類型的方法,可以看到這裏它使用的是 DefaultRepositoryDbContextType 作為默認的 TDbContext 類型。

    protected virtual Type GetDefaultRepositoryImplementationType(Type entityType)
    {
        var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType);
    
        // 重點在於構造倉儲類型時,傳遞的 Options.DefaultRepositoryDbContextType 參數,這個參數就是後面 EfCoreRepository 的 TDbContext 泛型。
        if (primaryKeyType == null)
        {
            return Options.SpecifiedDefaultRepositoryTypes
                ? Options.DefaultRepositoryImplementationTypeWithoutKey.MakeGenericType(entityType)
                : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType);
        }
    
        return Options.SpecifiedDefaultRepositoryTypes
            ? Options.DefaultRepositoryImplementationType.MakeGenericType(entityType, primaryKeyType)
            : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType, primaryKeyType);
    }

    最後我發現這個就是在模塊調用 AddAbpContext<TDbContext> 所提供的泛型參數。

    public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContextRegistrationOptionsBuilder
    {
        // ... 其他代碼
    
        protected AbpCommonDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
        {
            OriginalDbContextType = originalDbContextType;
            Services = services;
            DefaultRepositoryDbContextType = originalDbContextType;
            CustomRepositories = new Dictionary<Type, Type>();
            ReplacedDbContextTypes = new List<Type>();
        }
    
        // ... 其他代碼
    }
    
    public class AbpDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions, IAbpDbContextRegistrationOptionsBuilder
    {
        public Dictionary<Type, object> AbpEntityOptions { get; }
    
        public AbpDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
            : base(originalDbContextType, services) // 之類調用的就是上面的構造方法。
        {
            AbpEntityOptions = new Dictionary<Type, object>();
        }
    }
    
    public static class AbpEfCoreServiceCollectionExtensions
    {
        public static IServiceCollection AddAbpDbContext<TDbContext>(
            this IServiceCollection services, 
            Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null)
            where TDbContext : AbpDbContext<TDbContext>
        {
            // ... 其他代碼。
            
            var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);
    
            // ... 其他代碼。
    
            return services;
        }
    }

    所以,我們的默認倉儲的 dbContextKeyXXXDbContext,我們的自定義倉儲繼承 EfCoreRepository<IXXXDbContext,TEntity,TKey> ,所以它的 dbContextKey 就是 IXXXDbContext 。所以自定義倉儲獲取到的 DbContext 就與自定義倉儲的不一致了,從而提示上述異常。

    解決

    找到自定自定義倉儲的定義,修改它 EfCoreReposiotry<TDbContext,TEntity,TKey>TDbContext 泛型參數,變更為 XXXDbContext 即可。

    public class EfCoreStudentRepository : EfCoreRepository<XXXDbContext, Student, long>, IStudentRepository
    {
        public EfCoreStudentRepository(IDbContextProvider<XXXDbContext> dbContextProvider) : base(dbContextProvider)
        {
        }
    
        public Task<int> GetCountWithStudentlIdAsync(long studentId)
        {
            return DbSet.CountAsync(x=>x.studentId == studentId);
        }
    }

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

    【其他文章推薦】

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

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

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

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

  • 003.Kubernetes二進制部署準備

    003.Kubernetes二進制部署準備

    一 前置準備

    1.1 前置條件

    相應的充足資源的Linux服務器;

    設置相應的主機名,參考命令:

     1 hostnamectl set-hostname k8smaster

    Mac及UUID唯一;

    若未關閉防火牆則建議放通相應端口,如下:

    Master節點——






    規則

    方向

    端口範圍

    作用

    使用者

    TCP

    Inbound

    6443*

    Kubernetes API server

    All

    TCP

    Inbound

    2379-2380

    etcd server client API

    kube-apiserver, etcd

    TCP

    Inbound

    10250

    Kubelet API

    Self, Control plane

    TCP

    Inbound

    10251

    kube-scheduler

    Self

    TCP

    Inbound

    10252

    kube-controller-manager

    Self

    Worker 節點——

    規則

    方向

    端口範圍

    作用

    使用者

    TCP

    Inbound

    10250

    Kubelet API

    Self, Control plane

    TCP

    Inbound

    30000-32767

    NodePort Services**

    All


    其他更多前置準備見:

    二 主要組件

    2.1 核心組件

    • etcd:保存了整個集群的狀態;
    • apiserver:提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API註冊和發現等機制;
    • controller manager:負責維護集群的狀態,比如故障檢測、自動擴展、滾動更新等;
    • scheduler:負責資源的調度,按照預定的調度策略將Pod調度到相應的機器上;
    • kubelet:負責維護容器的生命周期,同時也負責Volume(CVI)和網絡(CNI)的管理;
    • Container runtime:負責鏡像管理以及Pod和容器的真正運行(CRI);
    • kube-proxy:負責為Service提供cluster內部的服務發現和負載均衡。

    2.2 非核心組件

    • kube-dns:負責為整個集群提供DNS服務;
    • Ingress Controller:為服務提供外網入口;
    • Heapster:提供資源監控;
    • Dashboard:提供GUI;
    • Federation:集群聯邦提供跨可用區的集群;
    • Fluentd-elasticsearch:提供集群日誌採集、存儲與查詢。

    延伸1:對master節點服務組件的理解:

    Master節點上面主要由四個模塊組成:APIServer,schedule,controller-manager,etcd。

    APIServer: APIServer負責對外提供RESTful的kubernetes API的服務,它是系統管理指令的統一接口,任何對資源的增刪該查都要交給APIServer處理后再交給etcd,如架構圖中所示,kubectl(Kubernetes提供的客戶端工具,該工具內部就是對Kubernetes API的調用)是直接和APIServer交互的。

    schedule: schedule負責調度Pod到合適的Node上,如果把scheduler看成一個黑匣子,那麼它的輸入是pod和由多個Node組成的列表,輸出是Pod和一個Node的綁定,即將這個pod部署到這個Node上。Kubernetes目前提供了調度算法,但是同樣也保留了接口,用戶可以根據自己的需求定義自己的調度算法。

    controller manager: 如果APIServer做的是前台的工作的話,那麼controller manager就是負責後台的。每一個資源都對應一個控制器。而control manager就是負責管理這些控制器的,比如我們通過APIServer創建了一個Pod,當這個Pod創建成功后,APIServer的任務就算完成了。而後面保證Pod的狀態始終和我們預期的一樣的重任就由controller manager去保證了。

    etcd:etcd是一個高可用的鍵值存儲系統,kubernetes使用它來存儲各個資源的狀態,從而實現了Restful的API。

    延伸2:對master節點服務組件的理解:

    每個Node節點主要由三個模板組成:kubelet、kube-proxy、runtime。

    runtime:runtime指的是容器運行環境,目前Kubernetes支持docker和rkt兩種容器。

    kube-proxy: 該模塊實現了kubernetes中的服務發現和反向代理功能。kube-proxy支持TCP和UDP連接轉發,默認基於Round Robin算法將客戶端流量轉發到與service對應的一組後端pod。服務發現方面,kube-proxy使用etcd的watch機制,監控集群中service和endpoint對象數據的動態變化,並且維護一個service到endpoint的映射關係,從而保證了後端pod的IP變化不會對訪問者造成影響。另外,kube-proxy還支持session affinity。

    kublet:kublet是Master在每個Node節點上面的agent,是Node節點上面最重要的模塊,它負責維護和管理該Node上的所有容器,但是如果容器不是通過kubernetes創建的,它並不會管理。本質上,它負責使Pod的運行狀態與期望的狀態一致。

    三 部署規劃

    3.1 節點規劃

    節點

    IP

    類型

    運行服務

    k8smaster01 172.24.8.71 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、kubelet、kube-nginx、flannel
    k8smaster02 172.24.8.72 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、 kubelet、kube-nginx、flannel
    k8smaster03 172.24.8.73 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、 kubelet、kube-nginx、flannel
    k8snode01 172.24.8.74 Kubernetes node節點1 docker、etcd、kubelet、proxy、flannel
    k8snode03 172.24.8.75 Kubernetes node節點2 docker、etcd、kubelet、proxy、flannel

    提示:本實驗使用三節點master部署,從而實現master的高可用。

    3.2 組件及版本

    • Kubernetes 1.14.2
    • Docker 18.09.6-ce
    • Etcd 3.3.13
    • Flanneld 0.11.0
    • 插件:
      • Coredns
      • Dashboard
      • Metrics-server
      • EFK (elasticsearch、fluentd、kibana)
    • 鏡像倉庫:
      • docker registry
      • harbor

    3.3 組件策略

    kube-apiserver:

    • 使用節點本地 nginx 4 層透明代理實現高可用;
    • 關閉非安全端口 8080 和匿名訪問;
    • 在安全端口 6443 接收 https 請求;
    • 嚴格的認證和授權策略 (x509、token、RBAC);
    • 開啟 bootstrap token 認證,支持 kubelet TLS bootstrapping;
    • 使用 https 訪問 kubelet、etcd,加密通信;

    kube-controller-manager:

    • 3 節點高可用;
    • 關閉非安全端口,在安全端口 10252 接收 https 請求;
    • 使用 kubeconfig 訪問 apiserver 的安全端口;
    • 自動 approve kubelet 證書籤名請求 (CSR),證書過期后自動輪轉;
    • 各 controller 使用自己的 ServiceAccount 訪問 apiserver;

    kube-scheduler:

    • 3 節點高可用;
    • 使用 kubeconfig 訪問 apiserver 的安全端口;

    kubelet:

    • 使用 kubeadm 動態創建 bootstrap token,而不是在 apiserver 中靜態配置;
    • 使用 TLS bootstrap 機制自動生成 client 和 server 證書,過期后自動輪轉;
    • 在 KubeletConfiguration 類型的 JSON 文件配置主要參數;
    • 關閉只讀端口,在安全端口 10250 接收 https 請求,對請求進行認證和授權,拒絕匿名訪問和非授權訪問;
    • 使用 kubeconfig 訪問 apiserver 的安全端口;

    kube-proxy:

    • 使用 kubeconfig 訪問 apiserver 的安全端口;
    • 在 KubeProxyConfiguration 類型的 JSON 文件配置主要參數;
    • 使用 ipvs 代理模式;

    集群插件:

    • DNS:使用功能、性能更好的 coredns;
    • Dashboard:支持登錄認證;
    • Metric:metrics-server,使用 https 訪問 kubelet 安全端口;
    • Log:Elasticsearch、Fluend、Kibana;
    • Registry 鏡像庫:docker-registry、harbor。

    四 其他準備

    4.1 手動添加解析

    注意:以下4.1至4.7步驟可通過如下腳本快速實現:

      1 [root@k8smaster01 ~]# vi k8sinit.sh
      2 # Modify Author: xhy
      3 # Modify Date: 2019-06-23 22:19
      4 # Version:
      5 #***************************************************************#
      6 # Initialize the machine. This needs to be executed on every machine.
      7 
      8 # Add host domain name.
      9 cat >> /etc/hosts << EOF
     10 172.24.8.71 k8smaster01
     11 172.24.8.72 k8smaster02
     12 172.24.8.73 k8smaster03
     13 172.24.8.74 k8snode01
     14 172.24.8.75 k8snode02
     15 EOF
     16 
     17 # Add docker user
     18 useradd -m docker
     19 
     20 # Disable the SELinux.
     21 sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
     22 
     23 # Turn off and disable the firewalld.
     24 systemctl stop firewalld
     25 systemctl disable firewalld
     26 
     27 # Modify related kernel parameters & Disable the swap.
     28 cat > /etc/sysctl.d/k8s.conf << EOF
     29 net.ipv4.ip_forward = 1
     30 net.bridge.bridge-nf-call-ip6tables = 1
     31 net.bridge.bridge-nf-call-iptables = 1
     32 net.ipv4.tcp_tw_recycle = 0
     33 vm.swappiness = 0
     34 vm.overcommit_memory = 1
     35 vm.panic_on_oom = 0
     36 net.ipv6.conf.all.disable_ipv6 = 1
     37 EOF
     38 sysctl -p /etc/sysctl.d/k8s.conf >&/dev/null
     39 swapoff -a
     40 sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
     41 modprobe br_netfilter
     42 
     43 # Add ipvs modules
     44 cat > /etc/sysconfig/modules/ipvs.modules <<EOF
     45 #!/bin/bash
     46 modprobe -- ip_vs
     47 modprobe -- ip_vs_rr
     48 modprobe -- ip_vs_wrr
     49 modprobe -- ip_vs_sh
     50 modprobe -- nf_conntrack_ipv4
     51 EOF
     52 chmod 755 /etc/sysconfig/modules/ipvs.modules
     53 bash /etc/sysconfig/modules/ipvs.modules
     54 
     55 # Install rpm
     56 yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget
     57 
     58 # Create k8s directory $$ Add system PATH
     59 mkdir -p  /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert
     60 echo 'PATH=/opt/k8s/bin:$PATH' >>/root/.bashrc
     61 source /root/.bashrc
     62 
     63 # Reboot the machine.
     64 reboot
      1 [root@k8smaster01 ~]# cat <<EOF >> /etc/hosts
      2 172.24.8.71 k8smaster01
      3 172.24.8.72 k8smaster02
      4 172.24.8.73 k8smaster03
      5 172.24.8.74 k8snode01
      6 172.24.8.75 k8snode02
      7 EOF

    提示:所有節點均建議如上操作。

    4.2 添加docker賬戶

      1 [root@k8smaster01 ~]# useradd -m docker

    提示:所有節點均建議如上操作。

    4.3 關閉SELinux

      1 [root@k8smaster01 ~]# setenforce 0
      2 [root@k8smaster01 ~]# sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

    4.4 修正iptables

      1 [root@k8smaster01 ~]# systemctl stop firewalld
      2 [root@k8smaster01 ~]# systemctl disable firewalld			#關閉防火牆
      3 [root@k8smaster01 ~]# cat <<EOF >> /etc/sysctl.d/k8s.conf
      4 net.bridge.bridge-nf-call-ip6tables = 1
      5 net.bridge.bridge-nf-call-iptables = 1
      6 net.ipv4.ip_forward = 1
      7 EOF
      8 [root@k8smaster01 ~]# modprobe br_netfilter
      9 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf

    提示:所有節點均建議如上操作。

    4.5 關閉swap

      1 [root@k8smaster01 ~]# sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
      2 [root@k8smaster01 ~]# echo "vm.swappiness = 0" >> /etc/sysctl.d/k8s.conf	#禁止使用 swap 空間,只有當系統 OOM 時才允許使用它
      3 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf

    4.6 其他調整

      1 [root@k8smaster01 ~]# cat <<EOF >> /etc/sysctl.d/k8s.conf
      2 vm.overcommit_memory = 1						# 不檢查物理內存是否夠用
      3 vm.panic_on_oom = 0							# 開啟 OOM
      4 net.ipv6.conf.all.disable_ipv6 = 1					# 關閉 IPV6
      5 EOF
      6 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf
      7 [root@k8smaster01 ~]# mkdir -p  /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert	#創建相應目錄
      8 [root@k8smaster01 ~]# yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget

    提示:必須關閉 tcp_tw_recycle,否則和 NAT 衝突,會導致服務不通;

    關閉 IPV6,防止觸發 docker BUG。

    4.7 加載IPVS

    pod的負載均衡是用kube-proxy來實現的,實現方式有兩種,一種是默認的iptables,一種是ipvs,相對iptables,ipvs有更好的性能。且當前ipvs已經加入到了內核的主幹。

    為kube-proxy開啟ipvs的前提需要加載以下的內核模塊:

    • ip_vs
    • ip_vs_rr
    • ip_vs_wrr
    • ip_vs_sh
    • nf_conntrack_ipv4
      1 [root@k8smaster01 ~]# cat > /etc/sysconfig/modules/ipvs.modules <<EOF
      2 #!/bin/bash
      3 modprobe -- ip_vs
      4 modprobe -- ip_vs_rr
      5 modprobe -- ip_vs_wrr
      6 modprobe -- ip_vs_sh
      7 modprobe -- nf_conntrack_ipv4
      8 EOF
      9 [root@k8smaster01 ~]# chmod 755 /etc/sysconfig/modules/ipvs.modules
     10 [root@k8smaster01 ~]# bash /etc/sysconfig/modules/ipvs.modules
     11 [root@k8smaster01 ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
     12 [root@k8smaster01 ~]# yum -y install ipvsadm

    提示:所有節點均建議如上操作。

    為了更好的管理和查看ipvs,可安裝相應的管理工具《002.LVS管理工具的安裝與使用》。

    五 環境準備

    5.1 配置免秘鑰

    為了更方便遠程分發文件和執行命令,本實驗配置master節點到其它節點的 ssh 信任關係。

      1 [root@k8smaster01 ~]# ssh-keygen -f ~/.ssh/id_rsa -N ''
      2 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster01
      3 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster02
      4 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster03
      5 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8snode01
      6 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8snode02

    提示:此操作僅需要在master節點操作。

    5.2 分發集群配置參數腳本

    後續使用的環境變量都定義在文件 environment.sh 中,同時拷貝到所有節點的 /opt/k8s/bin 目錄:

      1 #!/usr/bin/bash
      2 
      3 # 生成 EncryptionConfig 所需的加密 key
      4 export ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
      5 
      6 # 集群 MASTER 機器 IP 數組
      7 export MASTER_IPS=(172.24.8.71 172.24.8.72 172.24.8.73)
      8 
      9 # 集群 MASTER IP 對應的主機名數組
     10 export MASTER_NAMES=(k8smaster01 k8smaster02 k8smaster03)
     11 
     12 # 集群 NODE 機器 IP 數組
     13 export NODE_IPS=(172.24.8.74 172.24.8.75)
     14 
     15 # 集群 NODE IP 對應的主機名數組
     16 export NODE_NAMES=(k8snode01 k8snode02)
     17 
     18 # 集群所有機器 IP 數組
     19 export ALL_IPS=(172.24.8.71 172.24.8.72 172.24.8.73 172.24.8.74 172.24.8.75)
     20 
     21 # 集群所有IP 對應的主機名數組
     22 export ALL_NAMES=(k8smaster01 k8smaster02 k8smaster03 k8snode01 k8snode02)
     23 
     24 # etcd 集群服務地址列表
     25 export ETCD_ENDPOINTS="https://172.24.8.71:2379,https://172.24.8.72:2379,https://172.24.8.73:2379"
     26 
     27 # etcd 集群間通信的 IP 和端口
     28 export ETCD_NODES="k8smaster01=https://172.24.8.71:2380,k8smaster02=https://172.24.8.72:2380,k8smaster03=https://172.24.8.73:2380"
     29 
     30 # kube-apiserver 的反向代理(kube-nginx)地址端口
     31 export KUBE_APISERVER="https://127.0.0.1:8443"
     32 
     33 # 節點間互聯網絡接口名稱
     34 export IFACE="eth0"
     35 
     36 # etcd 數據目錄
     37 export ETCD_DATA_DIR="/data/k8s/etcd/data"
     38 
     39 # etcd WAL 目錄,建議是 SSD 磁盤分區,或者和 ETCD_DATA_DIR 不同的磁盤分區
     40 export ETCD_WAL_DIR="/data/k8s/etcd/wal"
     41 
     42 # k8s 各組件數據目錄
     43 export K8S_DIR="/data/k8s/k8s"
     44 
     45 # docker 數據目錄
     46 export DOCKER_DIR="/data/k8s/docker"
     47 
     48 ## 以下參數一般不需要修改
     49 
     50 # TLS Bootstrapping 使用的 Token,可以使用命令 head -c 16 /dev/urandom | od -An -t x | tr -d ' ' 生成
     51 BOOTSTRAP_TOKEN="41f7e4ba8b7be874fcff18bf5cf41a7c"
     52 
     53 # 最好使用 當前未用的網段 來定義服務網段和 Pod 網段
     54 
     55 # 服務網段,部署前路由不可達,部署後集群內路由可達(kube-proxy 保證)
     56 SERVICE_CIDR="10.254.0.0/16"
     57 
     58 # Pod 網段,建議 /16 段地址,部署前路由不可達,部署後集群內路由可達(flanneld 保證)
     59 CLUSTER_CIDR="172.30.0.0/16"
     60 
     61 # 服務端口範圍 (NodePort Range)
     62 export NODE_PORT_RANGE="30000-32767"
     63 
     64 # flanneld 網絡配置前綴
     65 export FLANNEL_ETCD_PREFIX="/kubernetes/network"
     66 
     67 # kubernetes 服務 IP (一般是 SERVICE_CIDR 中第一個IP)
     68 export CLUSTER_KUBERNETES_SVC_IP="10.254.0.1"
     69 
     70 # 集群 DNS 服務 IP (從 SERVICE_CIDR 中預分配)
     71 export CLUSTER_DNS_SVC_IP="10.254.0.2"
     72 
     73 # 集群 DNS 域名(末尾不帶點號)
     74 export CLUSTER_DNS_DOMAIN="cluster.local"
     75 
     76 # 將二進制目錄 /opt/k8s/bin 加到 PATH 中
     77 export PATH=/opt/k8s/bin:$PATH
      1 [root@k8smaster01 ~]# source environment.sh
      2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
      3   do
      4     echo ">>> ${all_ip}"
      5     scp environment.sh root@${all_ip}:/opt/k8s/bin/
      6     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
      7   done

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

    【其他文章推薦】

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

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

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

  • 科學家發現新型態鈾 恐影響核廢料處理計畫

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

    【其他文章推薦】

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

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

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

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

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

  • 澳洲野火不止 維州熱浪祭禁火令 擬照辦跨年煙火秀

    摘錄自2019年12月30日中央通訊社報導

    澳洲野火肆虐,重創當地生態環境及空氣品質,已逾26萬人連署,建議取消知名的雪梨跨年煙火秀,以免助長空氣污染,把錢省下來幫助受災農民及消防員等,不過,雪梨市長克洛弗.摩爾(Clover Moore)已煙火秀照舊,且據報導,高達10萬支煙火都已裝船開運,其中有8成來自中國。

    綜合媒體報導,雪梨是全球最早迎接新年的大城市之一,跨年煙火秀更是全球慶祝跨年的重點戲之一,去(2018)年就花了580萬澳元(約1.2億元台幣),今年打算花650萬澳元(約1.36億台幣)來辦跨年煙火秀,但今年正值澳洲發生數十年來最嚴重的野火災難,致農損嚴重,截至目前並未撲滅,致使澳洲民眾在網上連署,建議當局取消今年跨年煙火秀。

    不過當局並不打算取消,女市長摩爾並表示,若是取消,可能重創雪梨商家們,也會毀了數以萬計前來雪梨參加跨年活動人們的計劃。

    即使如此,這場煙火秀是否在最後喊停,還在未定之天,因為,澳洲野火未停,仍有接近100個火場火勢仍未撲滅,雪梨所屬的新南威爾斯省的災情最嚴重,且位於雪梨西南面的巴爾莫勒爾鎮幾乎全毀,許多公路仍然封閉;且產生的空氣污染已經形成有毒煙霧,籠罩雪梨及其他主要城市。

    由於East Gippsland的森林火災不斷升級,加上熱浪來襲,預計今天氣溫可能升高到44度,維多利亞州當局已在昨天(29日)下令,全面撤離Goongerah和Martins Creek地區居民,今天更將全州實行禁火令,嚴禁人們在戶外用火。

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

    【其他文章推薦】

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

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

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

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