中國動力電池技術突破,2025 年電動車成本效益比料勝燃油車

 

21 世紀經濟報導,中國電動汽車百人會理事長陳清泰表示,過去一年,中國電動汽車產業正在向高品質發輾轉型,發展形勢良好;而電動汽車再往前發展要跨越一個臨界點,就是電動車的成本效益比達到和超過燃油車,他預期這個臨界點會可能會在 2025 年前後出現。   中國 2017 年新能源汽車銷量目標 70 萬輛,去年 1 到 11 月累計銷售 60.9 萬輛、年增 51.4%。專家預測,中國去年新能源汽車總銷量可能超過 80 萬輛。中國汽車工業協會秘書長助理許海東日前表示,中國去年新能源汽車 70 萬輛銷量目標應可達成,2018 年新能源車銷量增速仍可保持 40% 至 50%,預期銷量將超過 100 萬輛。   陳清泰指出,電動汽車再往前發展要跨越一個臨界點,就是電動車的成本效益比達到和超過燃油車,如果跨過了這個門檻,電動車就可依託市場力量自主發展了,目前仍需靠政策、靠政府補貼。他亦預期,當財政補貼完全取消後,雙積分政策作為替代政策,新能源汽車積分比例將在 2020 年 12% 的基礎上繼續上調。   對於上述臨界點會出現在什麼時候?陳清泰的判斷是,大約在 2025 年前後。對此,他建議中國車企要在以下幾個方面做好準備,首先是在財政補貼退坡之後,做好可持續發展的保障工作,另外電動車自身要透過輕量化、節能化提高成本效益比;其次,產品技術要雙線作戰,其中一條戰線是完善汽車的行駛功能,另一條戰線則是將車聯網和共享化應用到新能源汽車上;第三,自動駕駛是爭奪未來的一個重點;第四,在有限的時間要加緊做品牌建設。   中國電動汽車百人會執行副理事長歐陽明高表示,2017 年中國動力電池技術已取得實質性進展,動力電池系統能量密度已達到 150 瓦時/公斤甚至以上,鋰離子動力電池單體比能量有望於 2020 年前實現 300 瓦時/公斤目標。   他進一步指出,2025 年,具備一般成本效益比的純電動轎車合理的里程是 300 到 400 公里;但 2030 年,最大的技術突破將體現在電解質上,固態電池會規模產業化,電池單體比能量可望觸及 500 瓦時/公斤;2030 年常規的成本效益比車型,續航里程應該可達到 500 公里以上。   (本文內容由授權使用。首圖來源:)

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

【其他文章推薦】

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

※平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

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

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

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

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

一文帶你深入了解 redis 複製技術及主從架構

主從架構可以說是互聯網必備的架構了,第一是為了保證服務的高可用,第二是為了實現讀寫分離,你可能熟悉我們常用的 MySQL 數據庫的主從架構,對於我們 redis 來說也不意外,redis 數據庫也有各種各樣的主從架構方式,在主從架構中會涉及到主節點與從節點之間的數據同步,這個數據同步的過程在 redis 中叫做複製,這在篇文章中,我們詳細的聊一聊 redis 的複製技術和主從架構 ,本文主要有以下內容:

  • 主從架構環境搭建
    • 主從架構的建立方式
    • 主從架構的斷開
  • 複製技術的原理
    • 數據同步過程
    • 心跳檢測
  • 主從拓撲架構
    • 一主一從
    • 一主多從
    • 樹狀結構

主從環境搭建

redis 的實例在默認的情況下都是主節點,所以我們需要修改一些配置來搭建主從架構,redis 的主從架構搭建還是比較簡單的,redis 提供了三種方式來搭建主從架構,在後面我們將就介紹,在介紹之前我們要先了解主從架構的特性:在主從架構中有一個主節點(master)和最少一個從節點(slave),並且數據複製是單向的,只能從主節點複製到從節點,不能由從節點到主節點。

主從架構的建立方式

主從架構的建立有以下三種方式:

  • 在 Redis.conf 配置文件中加入 slaveof {masterHost} {masterPort} 命令,隨 Redis 實例的啟動生效
  • 在 redis-server 啟動命令后加入 –slaveof {masterHost} {masterPort} 參數
  • 在 redis-cli 交互窗口下直接使用命令:slaveof {masterHost} {masterPort}

上面三種方式都可以搭建 Redis 主從架構,我們以第一種方式來演示,其他兩種方式自行嘗試,由於是演示,所以就在本地啟動兩個 Redis 實例,並不在多台機器上啟動 redis 的實例了,我們準備一個端口 6379 的主節點實例,準備一個端口 6480 從節點的實例,端口 6480 的 redis 實例配置文件取名為 6480.conf 並且在裏面添加 slaveof 語句,在配置文件最後加入如下一條語句

slaveof 127.0.0.1 6379

分別啟動兩個 redis 實例,啟動之後他們會自動建立主從關係,關於這背後的原理,我們後面在詳細的聊一聊,先來驗證一下我們的主從架構是否搭建成功,我們先在 6379 master 節點上新增一條數據:

然後再 6480 slave 節點上獲取該數據:

可以看出我們在 slave 節點上已經成功的獲取到了在 master 節點新增的值,說明主從架構已經搭建成功了,我們使用 info replication 命令來查看兩個節點的信息,先來看看主節點的信息

可以看出 6379 端口的實例 role 為 master,有一個正在連接的實例,還有其他運行的信息,我們再來看看 6480 端口的 redis 實例信息

可以看出兩個節點之間相互記錄著對象的信息,這些信息在數據複製時候將會用到。在這裡有一點需要說明一下,默認情況下 slave 節點是只讀的,並不支持寫入,也不建議開啟寫入,我們可以驗證一下,在 6480 實例上寫入一條數據

127.0.0.1:6480> set x 3
(error) READONLY You can't write against a read only replica.
127.0.0.1:6480> 

提示只讀,並不支持寫入操作,當然我們也可以修改該配置,在配置文件中 replica-read-only yes 配置項就是用來控制從服務器只讀的,為什麼只能只讀?因為我們知道複製是單向的,數據只能由 master 到 slave 節點,如果在 salve 節點上開啟寫入的話,那麼修改了 slave 節點的數據, master 節點是感知不到的,slave 節點的數據並不能複製到 master 節點上,這樣就會造成數據不一致的情況,所以建議 slave 節點只讀。

主從架構的斷開

主從架構的斷開同樣是 slaveof 命令,在從節點上執行 slaveof no one 命令就可以與主節點斷開追隨關係,我們在 6480 節點上執行 slaveof no one 命令

127.0.0.1:6480> slaveof no one
OK
127.0.0.1:6480> info replication
# Replication
role:master
connected_slaves:0
master_replid:a54f3ba841c67762d6c1e33456c97b94c62f6ac0
master_replid2:e5c1ab2a68064690aebef4bd2bd4f3ddfba9cc27
master_repl_offset:4367
second_repl_offset:4368
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4367
127.0.0.1:6480> 

執行完 slaveof no one 命令之後,6480 節點的角色立馬恢復成了 master ,我們再來看看時候還和 6379 實例連接在一起,我們在 6379 節點上新增一個 key-value

127.0.0.1:6379> set y 3
OK

在 6480 節點上 get y

127.0.0.1:6480> get y
(nil)
127.0.0.1:6480> 

在 6480 節點上獲取不到 y ,因為 6480 節點已經跟 6379 節點斷開的聯繫,不存在主從關係了,slaveof 命令不僅能夠斷開連接,還能切換主服務器,使用命令為 slaveof {newMasterIp} {newMasterPort},我們讓 6379 成為 6480 的從節點, 在 6379 節點上執行 slaveof 127.0.0.1 6480 命令,我們在來看看 6379 的 info replication

127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6480
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:4367
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:99624d4b402b5091552b9cb3dd9a793a3005e2ea
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4367
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4368
repl_backlog_histlen:0
127.0.0.1:6379> 

6379 節點的角色已經是 slave 了,並且主節點的是 6480 ,我們可以再看看 6480 節點的 info replication

127.0.0.1:6480> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=4479,lag=1
master_replid:99624d4b402b5091552b9cb3dd9a793a3005e2ea
master_replid2:a54f3ba841c67762d6c1e33456c97b94c62f6ac0
master_repl_offset:4479
second_repl_offset:4368
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4479
127.0.0.1:6480> 

在 6480 節點上有 6379 從節點的信息,可以看出 slaveof 命令已經幫我們完成了主服務器的切換。

複製技術的原理

redis 的主從架構好像很簡單一樣,我們就執行了一條命令就成功搭建了主從架構,並且數據複製也沒有問題,使用起來確實簡單,但是這背後 redis 還是幫我們做了很多的事情,比如主從服務器之間的數據同步、主從服務器的狀態檢測等,這背後 redis 是如何實現的呢?接下來我們就一起看看

數據複製原理

我們執行完 slaveof 命令之後,我們的主從關係就建立好了,在這個過程中, master 服務器與 slave 服務器之間需要經歷多個步驟,如下圖所示:

slaveof 命令背後,主從服務器大致經歷了七步,其中權限驗證這一步不是必須的,為了能夠更好的理解這些步驟,就以我們上面搭建的 redis 實例為例來詳細聊一聊各步驟。

1、保存主節點信息

在 6480 的客戶端向 6480 節點服務器發送 slaveof 127.0.0.1 6379 命令時,我們會立馬得到一個 OK

127.0.0.1:6480> slaveof 127.0.0.1 6379
OK
127.0.0.1:6480> 

這時候數據複製工作並沒有開始,數據複製工作是在返回 OK 之後才開始執行的,這時候 6480 從節點做的事情是將給定的主服務器 IP 地址 127.0.0.1 以及端口 6379 保存到服務器狀態的 masterhost 屬性和 masterport 屬性裏面

2、建立 socket 連接

在 slaveof 命令執行完之後,從服務器會根據命令設置的 IP 地址和端口,跟主服務器創建套接字連接, 如果從服務器能夠跟主服務器成功的建立 socket 連接,那麼從服務器將會為這個 socket 關聯一個專門用於處理複製工作的文件事件處理器,這個處理器將負責後續的複製工作,比如接受全量複製的 RDB 文件以及服務器傳來的寫命令。同樣主服務器在接受從服務器的 socket 連接之後,將為該 socket 創建一個客戶端狀態,這時候的從服務器同時具有服務器和客戶端兩個身份,從服務器可以向主服務器發送命令請求而主服務器則會向從服務器返回命令回復。

3、發送 ping 命令

從服務器與主服務器連接成功后,做的第一件事情就是向主服務器發送一個 ping 命令,發送 ping 命令主要有以下目的:

  • 檢測主從之間網絡套接字是否可用
  • 檢測主節點當前是否可接受處理命令

在發送 ping 命令之後,正常情況下主服務器會返回 pong 命令,接受到主服務器返回的 pong 回復之後就會進行下一步工作,如果沒有收到主節點的 pong 回復或者超時,比如網絡超時或者主節點正在阻塞無法響應命令,從服務器會斷開複製連接,等待下一次定時任務的調度。

4、身份驗證

從服務器在接收到主服務器返回的 pong 回復之後,下一步要做的事情就是根據配置信息決定是否需要身份驗證:

  • 如果從服務器設置了 masterauth 參數,則進行身份驗證
  • 如果從服務器沒有設置 masterauth 參數,則不進行身份驗證

在需要身份驗證的情況下,從服務器將就向主服務器發送一條 auth 命令,命令參數為從服務器 masterauth 選項的值,舉個例子,如果從服務器的配置里將 masterauth 參數設置為:123456,那麼從服務器將向主服務器發送 auth 123456 命令,身份驗證的過程也不是一帆風順的,可能會遇到以下幾種情況:

  • 從服務器通過 auth 命令發送的密碼與主服務器的 requirepass 參數值一致,那麼將繼續進行後續操作,如果密碼不一致,主服務將返回一個 invalid password 錯誤
  • 如果主服務器沒有設置 requirepass 參數,那麼主服務器將返回一個 no password is set 錯誤

所有的錯誤情況都會令從服務器中止當前的複製工作,並且要從建立 socket 開始重新發起複制流程,直到身份驗證通過或者從服務器放棄執行複製為止

5、發送端口信息

在身份驗證通過後,從服務器將執行 REPLCONF listening 命令,向主服務器發送從服務器的監聽端口號,例如在我們的例子中從服務器監聽的端口為 6480,那麼從服務器將向主服務器發送 REPLCONF listening 6480 命令,主服務器接收到這個命令之後,會將端口號記錄在從服務器所對應的客戶端狀態的 slave_listening_port 屬性了,也就是我們在 master 服務器的 info replication 裏面看到的 port 值。

6、數據複製

數據複製是最複雜的一塊了,由 psync 命令來完成,從服務器會向主服務器發送一個 psync 命令來進行數據同步,在 redis 2.8 版本以前使用的是 sync 命令,除了命令不同之外,在複製的方式上也有很大的不同,在 redis 2.8 版本以前使用的都是全量複製,這對主節點和網絡會造成很大的開銷,在 redis 2.8 版本以後,數據同步將分為全量同步和部分同步。

  • 全量複製:一般用於初次複製場景,不管是新舊版本的 redis 在從服務器第一次與主服務連接時都將進行一次全量複製,它會把主節點的全部數據一次性發給從節點,當數據較大時,會對主節點和網絡造成很大的開銷,redis 的早期版本只支持全量複製,這不是一種高效的數據複製方式

  • 部分複製:用於處理在主從複製中因網絡閃斷等原因造成的數據丟失 場景,當從節點再次連上主節點后,如果條件允許,主節點會補發丟失數據 給從節點。因為補發的數據遠遠小於全量數據,可以有效避免全量複製的過高開銷,部分複製是對老版複製的重大優化,有效避免了不必要的全量複製操作

redis 之所以能夠支持全量複製和部分複製,主要是對 sync 命令的優化,在 redis 2.8 版本以後使用的是一個全新的 psync 命令,命令格式為:psync {runId} {offset},這兩個參數的意義:

  • runId:主節點運行的id
  • offset:當前從節點複製的數據偏移量

也許你對上面的 runid、offset 比較陌生,沒關係,我們先來看看下面三個概念:

1、複製偏移量

參与複製的主從節點都會分別維護自身複製偏移量:主服務器每次向從服務器傳播 N 個字節的數據時,就將自己的偏移量的值加上 N,從服務器每次接收到主服務器傳播的 N個字節的數據時,將自己的偏移量值加上 N。通過對比主從服務器的複製偏移量,就可以知道主從服務器的數據是否一致,如果主從服務器的偏移量總是相同,那麼主從數據一致,相反,如果主從服務器兩個的偏移量並不相同,那麼說明主從服務器並未處於數據一致的狀態,比如在有多個從服務器時,在傳輸的過程中某一個服務器離線了,如下圖所示:

由於從服務器A 在數據傳輸時,由於網絡原因掉線了,導致偏移量與主服務器不一致,那麼當從服務器A 重啟並且與主服務器連接成功后,重新向主服務器發送 psync 命令,這時候數據複製應該執行全量複製還是部分複製呢?如果執行部分複製,主服務器又如何補償從服務器A 在斷線期間丟失的那部分數據呢?這些問題的答案都在複製積壓緩衝區裏面

2、複製積壓緩衝區

複製積壓緩衝區是保存在主節點上的一個固定長度的隊列,默認大小為 1MB,當主節點有連接的從節點(slave)時被創建,這時主節點(master) 響應寫命令時,不但會把命令發送給從節點,還會寫入複製積壓緩衝區,如下圖所示:

因此,主服務器的複製積壓緩衝區裏面會保存着一部分最近傳播的寫命令,並且複製積壓緩衝區會為隊列中的每個字節記錄相應的複製偏移量。所以當從服務器重新連上主服務器時,從服務器通過 psync 命令將自己的複製偏移量 offset 發送給主服務器,主服務器會根據這個複製偏移量來決定對從服務器執行何種數據同步操作:

  • 如果從服務器的複製偏移量之後的數據仍然存在於複製積壓緩衝區裏面,那麼主服務器將對從服務器執行部分複製操作
  • 如果從服務器的複製偏移量之後的數據不存在於複製積壓緩衝區裏面,那麼主服務器將對從服務器執行全量複製操作

3、服務器運行ID

每個 Redis 節點啟動后都會動態分配一個 40 位的十六進制字符串作為運行 ID,運行 ID 的主要作用是用來唯一識別 Redis 節點,我們可以使用 info server 命令來查看

127.0.0.1:6379> info server
# Server
redis_version:5.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:2ef1d58592147923
redis_mode:standalone
os:Linux 3.10.0-957.27.2.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:4.8.5
process_id:25214
run_id:7b987673dfb4dfc10dd8d65b9a198e239d20d2b1
tcp_port:6379
uptime_in_seconds:14382
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:14554933
executable:/usr/local/redis-5.0.5/src/./redis-server
config_file:/usr/local/redis-5.0.5/redis.conf
127.0.0.1:6379> 

這裏面有一個run_id 字段就是服務器運行的ID

了解這幾個概念之後,我們一起來看看 psync 命令的運行流程,psync 命令運行流程如下圖所示:

psync 命令的邏輯比較簡單,整個流程分為兩步:

1、從節點發送 psync 命令給主節點,參數 runId 是當前從節點保存的主節點運行ID,參數offset是當前從節點保存的複製偏移量,如果是第一次參与複製則默認值為 -1。

2、主節點接收到 psync 命令之後,會向從服務器返回以下三種回復中的一種:

  • 回復 +FULLRESYNC {runId} {offset}:表示主服務器將與從服務器執行一次全量複製操作,其中 runid 是這個主服務器的運行 id,從服務器會保存這個id,在下一次發送 psync 命令時使用,而 offset 則是主服務器當前的複製偏移量,從服務器會將這個值作為自己的初始化偏移量
  • 回復 +CONTINUE:那麼表示主服務器與從服務器將執行部分複製操作,從服務器只要等着主服務器將自己缺少的那部分數據發送過來就可以了
  • 回復 +ERR:那麼表示主服務器的版本低於 redis 2.8,它識別不了 psync 命令,從服務器將向主服務器發送 sync 命令,並與主服務器執行全量複製

7、命令持續複製

當主節點把當前的數據同步給從節點后,便完成了複製的建立流程。但是主從服務器並不會斷開連接,因為接下來主節點會持續地把寫命令發送給從節點,保證主從數據一致性。

經過上面 7 步就完成了主從服務器之間的數據同步,由於這篇文章的篇幅比較長,關於全量複製和部分複製的細節就不介紹了,全量複製就是將主節點的當前的數據生產 RDB 文件,發送給從服務器,從服務器再從本地磁盤加載,這樣當文件過大時就需要特別大的網絡開銷,不然由於數據傳輸比較慢會導致主從數據延時較大,部分複製就是主服務器將複製積壓緩衝區的寫命令直接發送給從服務器。

心跳檢測

心跳檢測是發生在主從節點在建立複製后,它們之間維護着長連接並彼此發送心跳命令,便以後續持續發送寫命令,主從心跳檢測如下圖所示:

主從節點彼此都有心跳檢測機制,各自模擬成對方的客戶端進行通信,主從心跳檢測的規則如下:

  • 主節點默認每隔 10 秒對從節點發送 ping 命令,判斷從節點的存活性和連接狀態。可通過修改 redis.conf 配置文件裏面的 repl-ping-replica-period 參數來控制發送頻率
  • 從節點在主線程中每隔 1 秒發送 replconf ack {offset} 命令,給主節點 上報自身當前的複製偏移量,這條命令除了檢測主從節點網絡之外,還通過發送複製偏移量來保證主從的數據一致

主節點根據 replconf 命令判斷從節點超時時間,體現在 info replication 統 計中的 lag 信息中,我們在主服務器上執行 info replication 命令:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6480,state=online,offset=25774,lag=0
master_replid:c62b6621e3acac55d122556a94f92d8679d93ea0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:25774
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:25774
127.0.0.1:6379> 

可以看出 slave0 字段的值最後面有一個 lag,lag 表示與從節點最後一次通信延遲的秒數,正常延遲應該在 0 和 1 之間。如果超過 repl-timeout 配置的值(默認60秒),則判定從節點下線並斷開複製客戶端連接,如果從節點重新恢復,心跳檢測會繼續進行。

主從拓撲架構

Redis的主從拓撲結構可以支持單層或多層複製關係,根據拓撲複雜性可以分為以下三種:一主一從、一主多從、樹狀主從架構

一主一從結構

一主一從結構是最簡單的複製拓撲結構,我們前面搭建的就是一主一從的架構,架構如圖所示:

一主一從架構用於主節點出現宕機時從節點 提供故障轉移支持,當應用寫命令併發量較高且需要持久化時,可以只在從節點上開啟 AOF,這樣既保證數據安全性同時也避免了持久化對主節點的性能干擾。但是這裡有一個坑,需要你注意,就是當主節點關閉持久化功能時, 如果主節點脫機要避免自動重啟操作。因為主節點之前沒有開啟持久化功能自動重啟后數據集為空,這時從節點如果繼續複製主節點會導致從節點數據也被清空的情況,喪失了持久化的意義。安全的做法是在從節點上執行 slaveof no one 斷開與主節點的複製關係,再重啟主節點從而避免這一問題

一主多從架構

一主多從架構又稱為星形拓撲結構,一主多從架構如下圖所示:

一主多從架構可以實現讀寫分離來減輕主服務器的壓力,對於讀佔比較大的場景,可以把讀命令發送到 從節點來分擔主節點壓力。同時在日常開發中如果需要執行一些比較耗時的讀命令,如:keys、sort等,可以在其中一台從節點上執行,防止慢查詢對主節點造成阻塞從而影響線上服務的穩定性。對於寫併發量較高的場景,多個從節點會導致主節點寫命令的多次發送從而過度消耗網絡帶寬,同時也加重了主節點的負載影響服務穩定性。

樹狀主從架構

樹狀主從架構又稱為樹狀拓撲架構,樹狀主從架構如下圖所示:

樹狀主從架構使得從節點不但可以複製主節 數據,同時可以作為其他從節點的主節點繼續向下層複製。解決了一主多從架構中的不足,通過引入複製中 間層,可以有效降低主節點負載和需要傳送給從節點的數據量。如架構圖中,數據寫入節點A 後會同步到 B 和 C節點,B節點再把數據同步到 D 和 E節點,數據實現了一層一層的向下複製。當主節點需要掛載多個從節點時為了避免對主節點的性能干擾,可以採用樹狀主從結構降低主節點壓力。

最後

目前互聯網上很多大佬都有 Redis 系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支持。若文中有所錯誤之處,還望提出,謝謝。

歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。

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

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

※收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

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

物聯網架構成長之路(47)-利用GitLab實現CI持續集成

0.前言
  前段時間,考慮到要練習部署一套CI/CD的系統。一開始考慮到Jenkins,隨着這两天的了解,發現最新版的GitLab已經提供有CI/CD集成了。所以本次博客,乾脆一步到位,直接用GitLab裏面的CI/CD模塊。Jenkins可能需要更高級的應用場合。經過測試GitLab自帶的功能完全符合我的需求。

1. 安裝GitLab和GitLab-CI(gitlab-runner)
  英語比較好的,可以直接看官方文檔。https://docs.gitlab.com/omnibus/docker/#install-gitlab-using-docker-compose https://docs.gitlab.com/ee/ci/quick_start/README.html
  下面提供我使用的 docker-compose.yml

 1 version: '3'
 2 services:
 3     gitlab:
 4         image: twang2218/gitlab-ce-zh:latest
 5         #image: gitlab/gitlab-ce:rc
 6         restart: always
 7         hostname: '172.16.23.203'
 8         environment:
 9             GITLAB_OMNIBUS_CONFIG: |
10                 external_url 'http://172.16.23.203:8929'
11                 gitlab_rails["time_zone"] = "Asia/Shanghai"
12         ports:
13             - 8929:8929
14             - 1080:80
15             - 1443:443
16             - 1022:22
17         volumes:
18             - /root/workspace/docker/gitlab/1/config:/etc/gitlab
19             - /root/workspace/docker/gitlab/1/logs:/var/log/gitlab
20             - /root/workspace/docker/gitlab/1/data:/var/opt/gitlab
21     gitlab-runner:
22         image: gitlab/gitlab-runner:latest
23         restart: always
24         volumes:
25             - /root/workspace/docker/gitlab/2/config:/etc/gitlab-runner
26             - /var/run/docker.sock:/var/run/docker.sock

  執行docker-compose up -d 就運行起來,幾點需要說明的
    1. gitlab的image,可以選擇中文版或者英文版
    2. hostname 這裏指定本機IP地址
    3. gitlab環境變量,external_url表示提供訪問的IP和端口,時區配置上海
    4. 端口映射,默認是80端口,由於我上面配置了8929,所以映射8929到Host主機
    5. volumes 配置持久化數據
    6. 這裏的/var/run/docker.sock 要映射到主機,因為會用到主機的一些資源,同時還會在docker裏面安裝docker
  下面是運行效果,第一次運行會比較久,因為要拉取鏡像和初始化GitLab

2. 登錄使用GitLab
  首次登錄,設置密碼。 登錄默認用戶名是root
  利用模版,新建一個Spring項目

  利用IDE,或者其他工具,或者直接在GitLab修改代碼

3. 配置CI/CD,把機器(gitlab-runner)註冊到GitLab中
  可以在項目配置CI/CD機器,也可以在個人所有項目下配置,也可以由管理員配置所有項目下CI/CD機器。原理和流程都是一樣的,只是比Jenkins更加細粒度控制而已。

  進入gitlab-runner的Docker,執行初始化命令 gitlab-ci-multi-runner register,完整命令如下:

1 sudo docker exec -it gitlab-runner gitlab-ci-multi-runner register

  需要錄入的信息,安裝上圖進行,填寫,後續還可以修改。

  如果需要修改,可以修改之前volumes配置的路徑下, config/config.toml

 

 1 concurrent = 1
 2 check_interval = 0
 3 
 4 [session_server]
 5   session_timeout = 1800
 6 
 7 [[runners]]
 8   name = "myRunner"
 9   url = "http://172.16.23.203:8929/"
10   token = "96beefdaa54832b0c8369ffa3811c9"
11   executor = "docker"
12   [runners.custom_build_dir]
13   [runners.docker]
14     tls_verify = false
15     image = "docker:latest"
16     privileged = true
17     disable_entrypoint_overwrite = false
18     oom_kill_disable = false
19     disable_cache = false
20     volumes = ["/cache", "/root/.m2:/root/.m2", "/var/run/docker.sock:/var/run/docker.sock"]
21     shm_size = 0
22   [runners.cache]
23     [runners.cache.s3]
24     [runners.cache.gcs]

 

  上面這個是配置文件,裏面有幾個注意點
    1. privileged 這裏要配置 true,因為要在docker裏面安裝docker
    2. /root/.m2 這個是配置maven的倉庫使用宿主主機的緩存,這樣就不用每次CI都要下載依賴
    3. /var/run/docker.sock 這個也要配置,在構建dockerfile的時候會用到
  還有一個需要配置的就是,這個Runner需要設置tag,這個是標識Runner的名稱。在.gitlab-ci.yml中會用到

4. 配置CI/CD
  默認GitLab是啟用該功能的,根目錄配置新增 .gitlab-ci.yml 文件,然後每次git push,都會觸發CI持續集成。當然可以在yml配置,在主線master觸發。
  來個簡單的配置,測試一下

 1 image: maven:3-jdk-8
 2 cache:
 3     paths:
 4         - .m2/repository
 5 test:
 6     stage: test
 7     script:
 8         - mvn package
 9     tags:
10         - tag

  上面這個配置,寫到.gitlab-ci.yml然後提交到repo,我們提交該文件到gitlab對應項目上去。

1 git add .gitlab-ci.yml
2 git commit -m "Add .gitlab-ci.yml"
3 git push origin master

  如果嫌慢,pom.xml 可以換個阿里源

 1         <repository>
 2             <id>maven-ali</id>
 3             <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
 4             <releases>
 5                 <enabled>true</enabled>
 6             </releases>
 7             <snapshots>
 8                 <enabled>true</enabled>
 9                 <updatePolicy>always</updatePolicy>
10                 <checksumPolicy>fail</checksumPolicy>
11             </snapshots>
12         </repository>

  一提交,就會觸發自動構建

  可以看到整個構建過程,如果出現錯誤,也是到這個日誌裏面排查問題。

 

 

5. 測試、打包、發布
  這一步,我們實現一個簡單的測試、打包、發布
5.1 增加 Dockerfile

1 FROM openjdk:8-jdk-alpine
2 VOLUME /tmp
3 COPY  target/demo-0.0.1-SNAPSHOT.jar app.jar
4 ENV PORT 5000
5 EXPOSE $PORT
6 ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dserver.port=${PORT}","-jar","/app.jar"]

5.2 修改 .gitlab-ci.yml

 1 image: maven:3-jdk-8
 2 
 3 variables:
 4     DOCKER_TAG: test/demo-spring:0.1
 5 
 6 cache:
 7     paths:
 8         - .m2/repository
 9 
10 stages:
11     - test
12     - package
13     - deploy
14 
15 test:
16     stage: test
17     tags:
18         - tag
19     script:
20         - mvn test
21 
22 package:
23     stage: package
24     tags:
25         - tag
26     script:
27         - mvn clean package -Dmaven.test.skip=true
28     artifacts:
29         paths:
30             - target/*.jar
31 
32 deploy:
33     image: docker:latest
34     stage: deploy
35     services:
36         - docker:dind
37     tags:
38         - tag
39     script:
40         - docker version 
41         - docker build -t $DOCKER_TAG .
42         - docker rm -f test || true
43         - docker run -d --name test -p 5000:5000 $DOCKER_TAG

  那個artifacts.paths 配置,提交target目錄下的文件到下一個流水線,因為不同流水線,由於是基於Docker,所以每一步都是隔離的。同時,上傳的附件還可以在構建成功后,在流水線pipelines界面進行下載。每一步的image都是可以指定的,那個tags也是可以指定的。可以提交到不同的機器進行構建。
  上面一共就三步流程,先test(測試),然後package(打包編譯),最後deploy(發布測試)。前兩個比較好理解,就是maven的基本命令。最後那個deploy就是利用docker裏面的docker來進行打包成docker,然後運行起來,作為測試發布。
  更新代碼.gitlab-ci.yml,然後提交,觸發持續集成。

  查看構建日誌

  查看宿主機鏡像和運行狀態

  查看瀏覽器,已經發布到測試環境了

5.3 釘釘通知

 1 image: maven:3-jdk-8
 2 
 3 variables:
 4     DOCKER_TAG: test/demo-spring:0.1
 5 
 6 cache:
 7     paths:
 8         - .m2/repository
 9 
10 stages:
11     - test
12     - package
13     - deploy
14     - notify
15 
16 test:
17     stage: test
18     tags:
19         - tag
20     script:
21         - mvn test
22 
23 package:
24     stage: package
25     tags:
26         - tag
27     script:
28         - mvn clean package -Dmaven.test.skip=true
29     artifacts:
30         paths:
31             - target/*.jar
32 
33 deploy:
34     image: docker:latest
35     stage: deploy
36     services:
37         - docker:dind
38     tags:
39         - tag
40     script:
41         - docker version 
42         - docker build -t $DOCKER_TAG .
43         - docker rm -f test || true
44         - docker run -d --name test -p 5000:5000 $DOCKER_TAG
45 
46 notify:
47     image: appropriate/curl:latest
48     stage: notify
49     tags:
50         - tag
51     script: "curl 'https://oapi.dingtalk.com/robot/send?access_token=d6c15304c1***************************************' -H 'Content-Type: application/json' -d '{\"msgtype\": \"text\", \"text\": {\"content\": \"功能已更新部署至測試環境\"}}' "

  有了這個通知,就可以做很多事情了,寫個腳本,封裝成一個Docker 鏡像,可以發送釘釘,發送郵件,可以對接到第三方系統等。

  更多高級應用,如集成之前了解的Harbor,Rancher。使整個系統更加強大,更加智能化。

 

參考資料
  
  
  
  
  

本文地址:
本系列目錄:
個人主頁:

volumes

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

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

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

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

※收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

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

JavaScript 是否應該重命名

  在誕生 25 年之後,JavaScript 語言仍然讓很多人困惑不已。所以一個老生常談的問題是:它是否應該重命名?呼籲改名的支持者列舉了一系列理由,包括:

  • JavaScript 本意指的是 ECMAScript 的子集,但使用中它經常被指代多種不同的 ECMAScript 超集
  • JavaScript 是甲骨文公司的商標,這與 JavaScript 作為 Web 平台核心組件的身份不相符合,Web 平台是建立在開放技術和標準基礎上的
  • JavaScript 連官方 logo 都沒有
  • JavaScript 與 Java 沒有一點關係,幾十年來它給非技術人員造成了混淆。

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

※3c收購,鏡頭 收購有可能以全新價回收嗎?

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

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

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

※賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

雅虎日本在其餐廳徵收“油炸食品稅”

  為了推廣健康生活方式,減少僱員中間的肥胖率,雅虎日本在其總部餐廳開始徵收“油炸食品稅”。從 10 月 8 日開始,炸豬排之類的油炸食品價格上漲,而水煮魚或烤魚之類的魚類食品則價格下降。

  它在 2017 年的體檢显示,45% 的僱員 LDL 膽固醇含量較高。它的自助餐廳每天有 1000 名僱員吃飯,油炸食品要比水煮魚或烤魚受歡迎得多。除了炸豬排漲價外,炸雞排也漲了 100 日元至 691 日元。如今到了午餐點,魚類食品一售而空,官員表示效果顯著。

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

※公開收購3c價格,不怕被賤賣!

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

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

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

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

傳谷歌母公司欲收購Fitbit 擬推自有品牌可穿戴設備

  原標題:Google is reportedly trying to buy Fitbit

  網易科技訊,10 月 29 日消息,據外媒報道,據知情人士透露,谷歌母公司 Alphabet 正在就收購美國可穿戴設備公司 Fitbit 與後者進行談判。不過,目前兩家公司還沒有確認這筆交易,也不清楚 Alphabet 的報價。上個月,有報道稱 Fitbit 正在探索出售的可能。

  多年來,谷歌憑藉其 Wear OS 操作系統在可穿戴市場上佔據了重要地位,但它始終難以與 Apple Watch 競爭,儘管其得到了包括 LG、Fossil 和 TicWatch 在內的眾多公司支持。就連主要的安卓製造商三星也未使用 Wear OS,而是使用自己的 Tizen 操作系統。

  今年 1 月,谷歌斥資 4000 萬美元從 Fossil 手中收購了某種智能手錶技術,但目前尚不清楚該技術到底是什麼,Fossil 高管將其描述為“尚未投放市場的新產品創新”,不過迄今仍然沒有上市。

  多年來,始終有傳言稱谷歌希望推出自家品牌的 Pixel 智能手錶。在 2016 年的某個時候,這些計劃幾乎促使谷歌品牌的智能手錶上市,但該公司最終擱置了計劃,因為其擔心這些手錶可能“傷及谷歌硬件品牌的聲譽”。這些 LG 製造的智能手錶後來在 2017 年以 LG Watch Sports 和 LG Watch Style 的形式發布,評價一般。

  從那時起,谷歌打造自家硬件野心開始膨脹。2017 年,谷歌收購了 HTC 智能手機工程團隊以開發 Pixel 手機,而 Fitbit 的收購可能會幫助其在可穿戴設備領域取得類似的進展。

  與此同時,蘋果在 Apple Watch 上的經驗表明,健康正迅速成為智能手錶的殺手級應用,這也是 Fitbit 自身健身追蹤器越來越關注的一個領域。然而,儘管 Fitbit 在 2016 年收購了智能手錶製造商 Pebble,但其在智能手錶方面的表現卻不盡如人意。例如,今年早些時候發布的健身跟蹤器 Fitbit Versa 2,其實本質上就是一款普通的智能手錶。

  谷歌和 Fitbit 拒絕就上述報道置評。(小小)

  

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

※公開收購3c價格,不怕被賤賣!

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

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

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

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

Three.js – 走進3D的奇妙世界

本文將通過Three.js的介紹及示例帶我們走進3D的奇妙世界。

文章來源:宜信技術學院 & 宜信支付結算團隊技術分享第6期-支付結算部支付研發團隊前端研發高級工程師-劉琳《three.js – 走進3D的奇妙世界》

分享者:宜信支付結算部支付研發團隊前端研發高級工程師-劉琳

原文首發於支付結算團隊公號-“野指針”

隨着人們對用戶體驗越來越重視,Web開發已經不滿足於2D效果的實現,而把目標放到了更加炫酷的3D效果上。Three.js是用於實現web端3D效果的JS庫,它的出現讓3D應用開發更簡單,本文將通過Three.js的介紹及示例帶我們走進3D的奇妙世界。

一、Three.js相關概念

1.1 Three.JS

Three.JS是基於WebGL的Javascript開源框架,簡言之,就是能夠實現3D效果的JS庫。

1.2 WebGL

WebGL是一種Javascript的3D圖形接口,把JavaScript和OpenGL ES 2.0結合在一起。

1.3 OpenGL

OpenGL是開放式圖形標準,跨編程語言、跨平台,Javascript、Java 、C、C++ 、 python 等都能支持OpenG ,OpenGL的Javascript實現就是WebGL,另外很多CAD製圖軟件都採用這種標準。OpenGL ES 2.0是OpenGL的子集,針對手機、遊戲主機等嵌入式設備而設計。

1.4 Canvas

Canvas是HTML5的畫布元素,在使用Canvas時,需要用到Canvas的上下文,可以用2D上下文繪製二維的圖像,也可以使用3D上下文繪製三維的圖像,其中3D上下文就是指WebGL。

二、Three.js應用場景

利用Three.JS可以製作出很多酷炫的3D動畫,並且Three.js還可以通過鼠標、鍵盤、拖拽等事件形成交互,在頁面上增加一些3D動畫和3D交互可以產生更好的用戶體驗。

通過Three.JS可以實現全景視圖,這些全景視圖應用在房產、家裝行業能夠帶來更直觀的視覺體驗。在電商行業利用Three.JS可以實現產品的3D效果,這樣用戶就可以360度全方位地觀察商品了,給用戶帶來更好的購物體驗。另外,使用Three.JS還可以製作類似微信跳一跳那樣的小遊戲。隨着技術的發展、基礎網絡的建設,web3D技術還能得到更廣泛的應用。

三、主要組件

在Three.js中,有了場景(scene)、相機(camera)和渲染器(renderer) 這3個組建才能將物體渲染到網頁中去。

1)場景

場景是一個容器,可以看做攝影的房間,在房間中可以布置背景、擺放拍攝的物品、添加燈光設備等。

2)相機

相機是用來拍攝的工具,通過控制相機的位置和方向可以獲取不同角度的圖像。

3)渲染器

渲染器利用場景和相機進行渲染,渲染過程好比攝影師拍攝圖像,如果只渲染一次就是靜態的圖像,如果連續渲染就能得到動態的畫面。在JS中可以使用requestAnimationFrame實現高效的連續渲染。

3.1 常用相機

1)透視相機

透視相機模擬的效果與人眼看到的景象最接近,在3D場景中也使用得最普遍,這種相機最大的特點就是近大遠小,同樣大小的物體離相機近的在畫面上顯得大,離相機遠的物體在畫面上顯得小。透視相機的視錐體如上圖左側所示,從近端面到遠端面構成的區域內的物體才能显示在圖像上。

透視相機構造器

PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )

  • fov — 攝像機視錐體垂直視野角度
  • aspect — 攝像機視錐體長寬比
  • near — 攝像機視錐體近端面
  • far — 攝像機視錐體遠端面

2)正交相機

使用正交相機時無論物體距離相機遠或者近,在最終渲染的圖片中物體的大小都保持不變。正交相機的視錐體如上圖右側所示,和透視相機一樣,從近端面到遠端面構成的區域內的物體才能显示在圖像上。

正交相機構造器

OrthographicCamera( left : Number, right : Number, top : Number, bottom : Number, near : Number, far : Number )

  • left — 攝像機視錐體左側面
  • right — 攝像機視錐體右側面
  • top — 攝像機視錐體上側面
  • bottom — 攝像機視錐體下側面
  • near — 攝像機視錐體近端面
  • far — 攝像機視錐體遠端面

3.2 坐標系

在場景中,可以放物品、相機、燈光,這些東西放置到什麼位置就需要使用坐標系。Three.JS使用右手坐標系,這源於OpenGL默認情況下,也是右手坐標系。從初中、高中到大學的課堂上,教材中所涉及的幾何基本都是右手坐標系。

上圖右側就是右手坐標系,五指併攏手指放平,指尖指向x軸的正方向,然後把四個手指垂直彎曲大拇指分開,併攏的四指指向y軸的正方向,大拇指指向的就是Z軸的正方向。

在Three.JS中提供了坐標軸工具(THREE.AxesHelper),在場景中添加坐標軸后,畫面會出現3條垂直相交的直線,紅色表示x軸,綠色表示y軸,藍色表示z軸(如下圖所示)。

3.3 示例代碼

/* 場景 */
var scene = new THREE.Scene();
scene.add(new THREE.AxesHelper(10)); // 添加坐標軸輔助線

/* 幾何體 */
// 這是自定義的創建幾何體方法,如果創建幾何體後續會介紹
var kleinGeom = createKleinGeom(); 
scene.add(kleinGeom); // 場景中添加幾何體

/* 相機 */
var camera = new THREE.PerspectiveCamera(45, width/height, 1, 100);
camera.position.set(5,10,25); // 設置相機的位置
camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相機看向原點

/* 渲染器 */
var renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(width, height);
// 將canvas元素添加到body
document.body.appendChild(renderer.domElement);
// 進行渲染
renderer.render(scene, camera);

 

四、幾何體

計算機內的3D世界是由點組成,兩個點能夠組成一條直線,三個不在一條直線上的點就能夠組成一個三角形面,無數三角形面就能夠組成各種形狀的幾何體。

以創建一個簡單的立方體為例,創建簡單的立方體需要添加8個頂點和12個三角形的面,創建頂點時需要指定頂點在坐標系中的位置,添加面的時候需要指定構成面的三個頂點的序號,第一個添加的頂點序號為0,第二個添加的頂點序號為1…

創建立方體的代碼如下:

var geometry = new THREE.Geometry();

// 添加8個頂點
geometry.vertices.push(new THREE.Vector3(1, 1, 1));
geometry.vertices.push(new THREE.Vector3(1, 1, -1));
geometry.vertices.push(new THREE.Vector3(1, -1, 1));
geometry.vertices.push(new THREE.Vector3(1, -1, -1));
geometry.vertices.push(new THREE.Vector3(-1, 1, -1));
geometry.vertices.push(new THREE.Vector3(-1, 1, 1));
geometry.vertices.push(new THREE.Vector3(-1, -1, -1));
geometry.vertices.push(new THREE.Vector3(-1, -1, 1));

// 添加12個三角形的面
geometry.faces.push(new THREE.Face3(0, 2, 1));
geometry.faces.push(new THREE.Face3(2, 3, 1));
geometry.faces.push(new THREE.Face3(4, 6, 5));
geometry.faces.push(new THREE.Face3(6, 7, 5));
geometry.faces.push(new THREE.Face3(4, 5, 1));
geometry.faces.push(new THREE.Face3(5, 0, 1));
geometry.faces.push(new THREE.Face3(7, 6, 2));
geometry.faces.push(new THREE.Face3(6, 3, 2));
geometry.faces.push(new THREE.Face3(5, 7, 0));
geometry.faces.push(new THREE.Face3(7, 2, 0));
geometry.faces.push(new THREE.Face3(1, 3, 4));
geometry.faces.push(new THREE.Face3(3, 6, 4));

 

4.1 正面和反面

創建幾何體的三角形面時,指定了構成面的三個頂點,如: new THREE.Face3(0, 2, 1),如果把頂點的順序改成0,1,2會有區別嗎?

通過下圖可以看到,按照0,2,1添加頂點是順時針方向的,而按0,1,2添加頂點則是逆時針方向的,通過添加頂點的方向就可以判斷當前看到的面是正面還是反面,如果頂點是逆時針方向添加,當前看到的面是正面,如果頂點是順時針方向添加,則當前面為反面。

下圖所看到的面就是反面。如果不好記,可以使用右手沿頂點添加的方向握住,大拇指所在的面就是正面,很像我們上學時學的電磁感應定律。

五、材質

創建幾何體時通過指定幾何體的頂點和三角形的面確定了幾何體的形狀,另外還需要給幾何體添加皮膚才能實現物體的效果,材質就像物體的皮膚,決定了物體的質感。常見的材質有如下幾種:

  • 基礎材質:以簡單着色方式來繪製幾何體的材質,不受光照影響。
  • 深度材質:按深度繪製幾何體的材質。深度基於相機遠近端面,離近端面越近就越白,離遠端面越近就越黑。
  • 法向量材質:把法向量映射到RGB顏色的材質。
  • Lambert材質:是一種需要光源的材質,非光澤表面的材質,沒有鏡面高光,適用於石膏等表面粗糙的物體。
  • Phong材質:也是一種需要光源的材質,具有鏡面高光的光澤表面的材質,適用於金屬、漆面等反光的物體。
  • 材質捕獲:使用存儲了光照和反射等信息的貼圖,然後利用法線方向進行採樣。優點是可以用很低的消耗來實現很多特殊風格的效果;缺點是僅對於固定相機視角的情況較好。

下圖是使用不同貼圖實現的效果:

六、光源

前面提到的光敏材質(Lambert材質和Phong材質)需要使用光源來渲染出3D效果,在使用時需要將創建的光源添加到場景中,否則無法產生光照效果。下面介紹一下常用的光源及特點。

6.1 點光源

點光源類似蠟燭放出的光,不同的是蠟燭有底座,點光源沒有底座,可以把點光源想象成懸浮在空中的火苗,點光源放出的光線來自同一點,且方向輻射向四面八方,點光源在傳播過程中有衰弱,如下圖所示,點光源在接近地面的位置,物體底部離點光源近,物體頂部離光源遠,照到物體頂部的光就弱些,所以頂部會比底部暗些。

6.2 平行光

平行光模擬的是太陽光,光源發出的所有光線都是相互平行的,平行光沒有衰減,被平行光照亮的整個區域接受到的光強是一樣的。

6.3 聚光燈

類似舞台上的聚光燈效果,光源的光線從一個錐體中射出,在被照射的物體上產生聚光的效果。聚光燈在傳播過程也是有衰弱的。

6.4 環境光

環境光是經過多次反射而來的光,環境光源放出的光線被認為來自任何方向,物體無論法向量如何,都將表現為同樣的明暗程度。

環境光通常不會單獨使用,通過使用多種光源能夠實現更真實的光效,下圖是將環境光與點光源混合后實現的效果,物體的背光面不像點光源那樣是黑色的,而是呈現出深褐色,更自然。

七、紋理

在生活中純色的物體還是比較少的,更多的是有凹凸不平的紋路或圖案的物體,要用Three.JS實現這些物體的效果,就需要使用到紋理貼圖。3D世界的紋理是由圖片組成的,將紋理添加在材質上以一定的規則映射到幾何體上,幾何體就有了帶紋理的皮膚。

7.1 普通紋理貼圖

在這個示例中使用上圖左側的地球紋理,在球形幾何體上進行貼圖就能製作出一個地球。

代碼如下:

/* 創建地球 */
function createGeom() {
    // 球體
    var geom = new THREE.SphereGeometry(1, 64, 64);
    // 紋理
    var loader = new THREE.TextureLoader();
    var texture = loader.load('./earth.jpg');
    // 材質
    var material = new THREE.MeshLambertMaterial({
        map: texture
    });
    var earth = new THREE.Mesh(geom, material);
    return earth;
}

 

7.2 反面貼圖實現全景視圖

這個例子是通過在球形幾何體的反面進行紋理貼圖實現的全景視圖,實現原理是這樣的:創建一個球體構成一個球形的空間,把相機放在球體的中心,相機就像在一個球形的房間中,在球體的裏面(也就是反面)貼上圖片,通過改變相機拍攝的方向,就能看到全景視圖了。

材質默認是在幾何體的正面進行貼圖的,如果想要在反面貼圖,需要在創建材質的時候設置side參數的值為THREE.BackSide,代碼如下:

/* 創建反面貼圖的球形 */
// 球體
var geom = new THREE.SphereGeometry(500, 64, 64);
// 紋理
var loader = new THREE.TextureLoader();
var texture = loader.load('./panorama.jpg');
// 材質
var material = new THREE.MeshBasicMaterial({
    map: texture,
    side: THREE.BackSide
});
var panorama = new THREE.Mesh(geom, material);

 

7.3 凹凸紋理貼圖

凹凸紋理利用黑色和白色值映射到與光照相關的感知深度,不會影響對象的幾何形狀,隻影響光照,用於光敏材質(Lambert材質和Phong材質)。

如果只用上圖左上角的磚牆圖片進行貼圖的話,就像一張牆紙貼在上面,視覺效果很差,為了增強立體感,可以使用上圖左下角的凹凸紋理,給物體增加凹凸不平的效果。

凹凸紋理貼圖使用方式的代碼如下:

// 紋理加載器
var loader = new THREE.TextureLoader();
// 紋理
var texture = loader.load( './stone.jpg');
// 凹凸紋理
var bumpTexture = loader.load( './stone-bump.jpg');
// 材質
var material =  new THREE.MeshPhongMaterial( {
    map: texture,
    bumpMap: bumpTexture
} );

 

7.4 法線紋理貼圖

法線紋理也是通過影響光照實現凹凸不平視覺效果的,並不會影響物體的幾何形狀,用於光敏材質(Lambert材質和Phong材質)。上圖左下角的法線紋理圖片的RGB值會影響每個像素片段的曲面法線,從而改變物體的光照效果。

使用方式的代碼如下:

// 紋理
var texture = loader.load( './metal.jpg');
// 法線紋理
var normalTexture = loader.load( './metal-normal.jpg');
var material =  new THREE.MeshPhongMaterial( {
    map: texture,
    normalMap: normalTexture
} );

 

7.5 環境貼圖

環境貼圖是將當前環境作為紋理進行貼圖,能夠模擬鏡面的反光效果。在進行環境貼圖時需要使用立方相機在當前場景中進行拍攝,從而獲得當前環境的紋理。立方相機在拍攝環境紋理時,為避免反光效果的小球出現在環境紋理的畫面上,需要將小球設為不可見。

環境貼圖的主要代碼如下:

/* 立方相機 */
var cubeCamera = new THREE.CubeCamera( 1, 10000, 128 );
/* 材質 */
var material = new THREE.MeshBasicMaterial( {
    envMap: cubeCamera.renderTarget.texture
});
/* 鏡面反光的球體 */
var geom = new THREE.SphereBufferGeometry( 10, 32, 16 );
var ball = new THREE.Mesh( geom, material );
// 將立方相機添加到球體
ball.add( cubeCamera );
scene.add( ball );

// 立方相機生成環境紋理前將反光小球隱藏
ball.visible = false;
// 更新立方相機,生成環境紋理
cubeCamera.update( renderer, scene );
balls.visible = true;

// 渲染
renderer.render(scene, camera);

 

八、加載外部3D模型

Three.JS已經內置了很多常用的幾何體,如:球體、立方體、圓柱體等等,但是在實際使用中往往需要用到一些特殊形狀的幾何體,這時可以使用3D建模軟件製作出3D模型,導出obj、json、gltf等格式的文件,然後再加載到Three.JS渲染出效果。

上圖的椅子是在3D製圖軟件繪製出來的,chair.mtl是導出的材質文件,chair.obj是導出的幾何體文件,使用材質加載器加載材質文件,加載完成后得到材質對象,給幾何體加載器設置材質,加載后得到幾何體對象,然後再創建場景、光源、攝像機、渲染器等進行渲染,這樣就等得到如圖的效果。主要的代碼如下:

// .mtl材質文件加載器
var mtlLoader = new THREE.MTLLoader();
// .obj幾何體文件加載器
var objLoader = new THREE.OBJLoader();

mtlLoader.load('./chair.mtl', function (materials) {
    objLoader.setMaterials(materials)
        .load('./chair.obj', function (obj) {
            scene.add(obj);
            …
        });
});

 

九、說明

以上內容對Three.JS的基本使用進行了介紹,文中涉及到的示例源碼已上傳到github,感興趣的同學可以下載查看,下載地址:https://github.com/liulinsp/three-demo。使用時如果有不清楚的地方可以查看Three.JS的官方文檔:https://threejs.org/docs/index.html。

作者:劉琳

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

【精選推薦文章】

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

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

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

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

《面試官之你說我聽》:簡明的圖解Redis RDB持久化、AOF持久化

歡迎關注文章這一系列,一起學習

《提升能力,漲薪可待篇》

《面試知識,工作可待篇》

《實戰演練,拒絕996篇》

如果此文對你有幫助、喜歡的話,那就點個讚唄,點個關注唄!

1.持久化

1.1 持久化簡介

持久化(Persistence),持久化是將程序數據在持久狀態和瞬時狀態間轉換的機制,即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。

1.2 redis持久化

redis為內存數據庫,為了防止服務器宕機以及服務器進程退出后,服務器數據丟失,Redis提供了持久化功能,即將Redis中內存數據持久化到磁盤中。Redis 提供了不同級別的持久化方式:

  • RDB持久化方式:可以在指定的時間間隔能對數據進行快照存儲.

  • AOF持久化方式:記錄每次對服務器寫的操作,當服務器重啟的時候會重新執行這些命令來恢復原始的數據,AOF命令以redis協議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進行後台重寫,使得AOF文件的體積不至於過大.

如果服務器開啟了AOF持久化功能。服務器會優先使用AOF文件還原數據。只有關閉了AOF持久化功能,服務器才會使用RDB文件還原數據

2. RDB持久化

2.1 RDB文件格式

RDB文件是一個經過壓縮的二進制文件(默認的文件名:dump.rdb),由多個部分組成,RDB格式:

2.2 RDB文件持久化創建與載入

在 Redis持久化時, RDB 程序將當前內存中的數據庫狀態保存到磁盤文件中, 在 Redis 重啟動時, RDB 程序可以通過載入 RDB 文件來還原數據庫的狀態。

2.3 工作方式

當 Redis 需要保存 dump.rdb 文件時, 服務器執行以下操作:

  • Redis 調用forks。同時擁有父進程和子進程。

  • 子進程將數據集寫入到一個臨時 RDB 文件中。

  • 當子進程完成對新 RDB 文件的寫入時,Redis 用新 RDB 文件替換原來的 RDB 文件,並刪除舊的 RDB 文件。

這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益。

2.4 創建方式

SAVE

同步操作,在執行該命令時,服務器會被阻塞,拒絕客戶端發送的命令請求

    redis> save

BGSAVE

異步操作,在執行該命令時,子進程執行保存工作,服務器還可以繼續讓主線程處理客戶端發送的命令請求

 redis>bgsave

自動創建

由於BGSAVE命令可不阻塞服務器進程下執行,可以讓用戶自定義save屬性,讓服務器每個一段時間自動執行一次BGSAVE命令(即通過配置文件對 Redis 進行設置, 讓它在“ N 秒內數據集至少有 M 個改動”這一條件被滿足時, 自動進行數據集保存操作)。

比如:
/*服務器在900秒之內,對數據庫進行了至少1次修改*/
Save 900   1
/*服務器在300秒之內,對數據庫進行了至少10次修改*/
Save 300   10
/*服務器在60秒之內,對數據庫進行了至少10000次修改*/
Save 60     10000

只要滿足其中一個條件就會執行BGSAVE命令

 

 

2.5 RDB 默認配置

################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#在給定的秒數和給定的對數據庫的寫操作數下,自動持久化操作。
#   save <seconds> <changes>
#
save 900 1
save 300 10
save 60 10000
​
#bgsave發生錯誤時是否停止寫入,一般為yes
stop-writes-on-bgsave-error yes
​
#持久化時是否使用LZF壓縮字符串對象?
rdbcompression yes
​
#是否對rdb文件進行校驗和檢驗,通常為yes
rdbchecksum yes
​
# RDB持久化文件名
dbfilename dump.rdb
​
#持久化文件存儲目錄
dir ./

 

3. AOF持久化

3.1 AOF持久化簡介

AOF持久化是通過保存Redis服務器所執行的寫命令來記錄數據庫狀態

 

 

AOF持久化功能實現:

  1. append命令追加:當AOF持久化功能處於打開狀態時,服務器執行完一個寫命令會協議格式被執行的命令追加服務器狀態的aof_buf緩衝區的末尾。

    reids>SET KET VAULE //協議格式 \r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVAULE\r\n

  2. 文件寫入和同步sync:Redis的服務器進程是一個事件循環,這個文件事件負責接收客戶端的命令請求以及向客戶端發送命令回復。當執行了append命令追加后,服務器會調用flushAppendOnlyFile函數是否需要將AOF緩衝區的內容寫入和保存到AOF文件

    redis> SET msg "Ccww"
  redis> SADD persistence "rdb" "aof"
  redis> RPUSH size 128 256 512

 

3.2 AOF持久化策略

AOF持久化策略(即緩衝區內容寫入和同步sync到AOF中),可以通過配置appendfsync屬性來選擇AOF持久化策略:

  • always:將aof_buf緩衝區中的所有內容寫入並同步到AOF文件,每次有新命令追加到 AOF 文件時就執行一次 fsync。

  • everysec(默認):如果上次同步AOF的時間距離現在超過一秒,先將aof_buf緩衝區中的所有內容寫入到AOF文件,再次對AOF文件進行同步,且同步操作由一個專門線程負責執行。

  • no:將aof_buf緩衝區中的所有內容寫入到AOF文件,但並不對AOF文件進行同步,何時同步由操作系統(OS)決定。

AOF持久化策略的效率與安全性:

  • Always:效率最慢的,但安全性是最安全的,即使出現故障宕機,持久化也只會丟失一個事件 循環的命令數據

  • everysec:兼顧速度和安全性,出現宕機也只是丟失一秒鐘的命令數據

  • No:寫入最快,但綜合起來單次同步是時間是最長的,且出現宕機時會丟失上傳同步AOF文件之後的所有命令數據。

 

3.3 AOF重寫

由於AOF持久化會把執行的寫命令追加到AOF文件中,所以隨着時間寫入命令會不斷增加, AOF文件的體積也會變得越來越大。AOF文件體積大對Reids服務器,甚至宿主服務器造成影響。

為了解決AOF文件體積膨脹的問題,Redis提供了AOF文件重寫(rewrite)功能:

  • 生成一個不保存任何浪費空間的冗餘命令新的AOF文件,且新舊AOF文件保存數據庫狀態一樣的

  • 新的AOF文件是通過讀取數據庫中的鍵值對來實現的,程序無須對現有的AOF文件進行讀入,分析,或者寫入操作。

  • 為防止緩衝區溢出,重寫處理list,hash,set以及Zset時,超過設置常量數量時會多條相同命令記錄一個集合。

  • Redis 2.4 可以通過配置自動觸發 AOF 重寫,觸發參數 auto-aof-rewrite-percentage(觸發AOF文件執行重寫的增長率) 以及 auto-aof-rewrite-min-size(觸發AOF文件執行重寫的最小尺寸)

AOF重寫的作用:

  • 減少磁盤佔用量

  • 加速數據恢復

 

Redis服務器使用單個線程來處理命令請求,服務器大量調用aof_rewrite函數,在AOF重寫期間,則無法處理client發來的命令請求,所以AOF重寫程序放在子進程執行,好處:

  1. 子進程進行AOF重寫期間,服務器進程可以繼續處理命令請求

  2. 子進程帶有服務器進程的數據副本,保證了數據的安全性。

AOF重寫使用子進程會造成數據庫與重寫后的AOF保存的數據不一致,為了解決這種數據不一致,redis使用了AOF重寫緩衝區 實現:

BGREWRITEAOF命令實現原理(只有信號處理函數執行時才對服務器進程造成阻塞):

  • 執行命令,同時將命令追加到AOF緩衝區和AOF重寫緩衝區

  • 當AOF子進程重寫完成后,發送一個信號給父進程,父進程將執行AOF重寫緩衝區中的所有內容寫入到新AOF文件中,新AOF文件保存的數據庫狀態將和服務器當前的數據庫狀態一致。

  • 對新的AOF文件進行改名,原子性地覆蓋現有AOF文件,完成新舊兩個AOF文件替換處理完成。

 

 

3.4 AOF持久化默認參數

############################## APPEND ONLY MODE ###############################
​
#開啟AOF持久化方式
appendonly no
​
#AOF持久化文件名
appendfilename "appendonly.aof"
#每秒把緩衝區的數據fsync到磁盤
appendfsync everysec
# appendfsync no
#是否在執行重寫時不同步數據到AOF文件
no-appendfsync-on-rewrite no
​
# 觸發AOF文件執行重寫的增長率
auto-aof-rewrite-percentage 100
#觸發AOF文件執行重寫的最小size
auto-aof-rewrite-min-size 64mb
​
#redis在恢復時,會忽略最後一條可能存在問題的指令
aof-load-truncated yes
​
#是否打開混合開關
aof-use-rdb-preamble yes

4 持久化方式總結與抉擇

4.1 RDB優缺點

RDB的優點

  • RDB是一個非常緊湊的文件,它保存了某個時間點得數據集,非常適用於數據集的備份,比如你可以在每個小時報保存一下過去24小時內的數據,同時每天保存過去30天的數據,這樣即使出了問題你也可以根據需求恢復到不同版本的數據集.

  • 基於RDB文件緊湊性,便於複製數據到一個遠端數據中心,非常適用於災難恢復.

  • RDB在保存RDB文件時父進程唯一需要做的就是fork出一個子進程,接下來的工作全部由子進程來做,父進程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.

  • 與AOF相比,在恢復大的數據集的時候,RDB方式會更快一些.

RDB的缺點

  • 如果你希望在redis意外停止工作(例如電源中斷)的情況下丟失的數據最少的話,那麼RDB不適合你.雖然你可以配置不同的save時間點(例如每隔5分鐘並且對數據集有100個寫的操作),是Redis要完整的保存整個數據集是一個比較繁重的工作,你通常會每隔5分鐘或者更久做一次完整的保存,萬一在Redis意外宕機,你可能會丟失幾分鐘的數據.

  • RDB 需要經常fork子進程來保存數據集到硬盤上,當數據集比較大的時候,fork的過程是非常耗時的,可能會導致Redis在一些毫秒級內不能響應客戶端的請求.如果數據集巨大並且CPU性能不是很好的情況下,這種情況會持續1秒,AOF也需要fork,但是你可以調節重寫日誌文件的頻率來提高數據集的耐久度.

4.2 AOF的優缺點

AOF的優點:

  • 使用AOF 會讓你的Redis更加耐久:使用不同的fsync策略:無fsync,每秒fsync,每次寫的時候fsync.使用默認的每秒fsync策略,Redis的性能依然很好(fsync是由後台線程進行處理的,主線程會儘力處理客戶端請求),一旦出現故障,你最多丟失1秒的數據.

  • AOF文件是一個只進行追加的日誌文件,所以不需要寫入seek,即使由於某些原因(磁盤空間已滿,寫的過程中宕機等等)未執行完整的寫入命令,你也可使用redis-check-aof工具修復問題.

  • Redis可以在AOF文件體積變得過大時,自動對 AOF 進行重寫: 重寫后的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。

  • AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也非常簡單(例如, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啟 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態)。

AOF 缺點:

  • 對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。

  • 根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。

4.3 如何選擇使用哪種持久化方式?

一般來說, 如果想達到足以媲美 PostgreSQL 的數據安全性, 你應該同時使用兩種持久化功能。

如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失, 那麼你可以只使用 RDB 持久化。

有很多用戶都只使用 AOF 持久化, 但我們並不推薦這種方式: 因為定時生成 RDB 快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢複數據集的速度也要比 AOF 恢復的速度要快, 除此之外, 使用 RDB 還可以避免之前提到的 AOF 程序的 bug 。

也歡迎關注公眾號【Ccww筆記】,原創技術文章第一時間推出

如果此文對你有幫助、喜歡的話,那就點個讚唄,點個關注唄!

 

 

 

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

【精選推薦文章】

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

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

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

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

看完這篇還不會用Git,那我就哭了!

你使用過 Git 嗎?也許你已經使用了一段時間,但它的許多奧秘仍然令人困惑。

Git 是一個版本控制系統,是任何軟件開發項目中的主要內容。通常有兩個主要用途:代碼備份和代碼版本控制。你可以逐步處理代碼,在需要回滾到備份副本的過程中保存每一步的進度!

常見的問題是 Git 很難使用。有時版本和分支不同步,你會花很長時間試圖推送代碼!更糟糕的是,不知道某些命令的確切工作方式很容易導致意外刪除或覆蓋部分代碼!

這就是我寫本文的原因,從而學習到如何正確使用 Git,以便在開發中共同進行編碼!

安裝和配置

Git 安裝

首先,我們必須安裝 Git 才能使用它!這裏分 Linux 和 Windows 來演示:

在 Linux 上安裝 Git

我們可以使用 yum 輕鬆快速地做到這一點:

sudo yum install git

在 Windows 上安裝 Git

直接在 https://git-scm.com/downloads 裏面,下載最新版的 Git,默認安裝就可以了。

安裝完成后,在開始菜單里找到 Git->Git Bash,點擊后出現一個類似命令行窗口的東西,就說明 Git 安裝成功。

Git 配置

可以保存 Git 用戶名和电子郵件,這樣就不必在以後的 Git 命令中再次輸入它們。

在命令行中配置本地倉庫的賬號和郵箱:

$ git config --global user.name "wupx"  
$ git config --global user.email "wupx@qq.com"  

好多人都不知道的小技巧是,你可以為 Git 啟用一些額外的顏色,這樣就可以更容易地閱讀命令的輸出!

git config --global color.ui true

Git 基本版本控制

初始化 Git

現在,我們可以開始對項目進行版本控制。使用 cd 命令導航到要在終端中設置版本控制的目錄,現在你可以像這樣初始化 Git 存儲庫:

git init

這將創建一個名為 .git 的新子目錄(Windows 下該目錄為隱藏的),其中包含所有必需的存儲庫文件(Git 存儲庫框架)。至此,你的項目中尚未跟蹤任何內容。

添加並提交

要開始對現有文件進行版本控制,你應該先跟蹤這些文件並進行初始提交。要做到這一點,你首先需要將文件添加到 Git 中,並將它們附加到 Git 項目中。

git add <file>
git commit -m 'first commit'

遠程備份

很棒!你現在已經開始在本地對項目進行版本控制。如果你想遠程保存和備份項目,則需要在 GitHub 上創建一個遠程存儲庫(它是免費的!)。因此,首先轉到 github.com 並創建一個存儲庫。然後,使用存儲庫的鏈接將其添加為本地 git 項目的來源,即該代碼的存儲位置。

# 示例
git remote add origin \
https://github.com/wupeixuan/repo.git 
# 以我的一個倉庫為例
git remote add origin \
https://github.com/wupeixuan/JDKSourceCode1.8.git

然後,你可以繼續將代碼推送到 GitHub!哇,你已經成功備份了你的代碼!

git push origin master

處理文件

狀態檢查

git status 命令用於確定哪些文件處於哪種狀態,它使你可以查看哪些文件已提交,哪些文件尚未提交。如果在所有文件都已提交並推送后運行此命令,則應該看到類似以下內容:

$ git status
# On branch master
nothing to commit (working directory clean)

如果你將新文件添加到項目中,而該文件之前不存在,則在運行 git status 時,你應該看到未跟蹤的文件,如下所示:

$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   README
nothing added to commit but untracked files present (use "git add" to track)

使用 git status 對於快速檢查你已經備份的內容和你僅在本地擁有的內容非常有用。

高級文件添加

還有一些更高級的方法可以將文件添加到 Git 中,從而使你的工作流程更高效。我們可以執行以下操作,而不是試圖查找所有有更改的文件並逐個添加它們:

# 逐個添加文件
git add filename

# 添加當前目錄中的所有文件
git add -A

# 添加當前目錄中的所有文件更改
git add .

# 選擇要添加的更改(你可以 Y 或 N 完成所有更改)
git add -p

高級提交

我們可以使用 git commit -m '提交信息' 來將文件提交到 Git。對於提交簡短消息來說,這一切都很好,但是如果你想做一些更精細的事情,你需要來學習更多的操作:

### 提交暫存文件,通常用於較短的提交消息
git commit -m 'commit message'

### 添加文件並提交一次
git commit filename -m 'commit message'

### 添加文件並提交暫存文件
git commit -am 'insert commit message'

### 更改你的最新提交消息
git commit --amend 'new commit message' 

# 將一系列提交合併為一個提交,你可能會用它來組織混亂的提交歷史記錄
git rebase -i
### 這將為你提供核心編輯器上的界面:
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell

分支與合併

GitHub存儲庫的master分支應始終包含有效且穩定的代碼。但是,你可能還希望備份一些當前正在處理的代碼,但這些代碼並不完全穩定。也許你要添加一個新功能,你正在嘗試和破壞很多代碼,但是你仍然希望保留備份以保存進度!

分支使你可以在不影響master分支的情況下處理代碼的單獨副本。首次創建分支時,將以新名稱創建master分支的完整克隆。然後,你可以獨立地在此新分支中修改代碼,包括提交文件等。一旦你的新功能已完全集成並且代碼穩定,就可以將其合併到master分支中!

分支

這是你在分支上創建和工作所需的所有東西:

### 創建一個本地分支
git checkout -b branchname

### 在2個分支之間切換
git checkout prc/dev-wupx
git checkout master

### 將新的本地分支作為備份
git push -u origin branch_2

### 刪除本地分支,這不會讓你刪除尚未合併的分支
git branch -d branch_2

### 刪除本地分支,即使尚未合併,這也會刪除該分支!
git branch -D branch_2

### Viewing all current branches for the repository, including both ### local and remote branches. Great to see if you already have a ### branch for a particular feature addition, especially on bigger ### projects
### 查看存儲庫的所有當前分支,包括本地和遠程分支。
git branch -a

### 查看已合併到您當前分支中的所有分支,包括本地和遠程。 非常適合查看所有代碼的來源!
git branch -a --merged

### 查看尚未合併到當前分支中的所有分支,包括本地和遠程
git branch -a --no-merged

### 查看所有本地分支
git branch

### 查看所有遠程分支
git branch -r

# 將主分支重新設置為本地分支
$ git rebase origin/master

# 將分支推送到遠程存儲庫源並對其進行跟蹤
$ git push origin branchname

合併

很棒!現在,你已經學習了如何創建分支並開始敲代碼!將新功能添加到分支中之後,你需要將其合併回master分支,以便您的master具有所有最新的代碼功能。

方法如下:

### 首先確保你正在查看 master 分支
git checkout master

### 現在將你的分支合併到 master 
git merge prc/dev-wupx

你可能必須修复分支與主服務器之間的任何代碼衝突,但是 Git 將向你展示在鍵入該 merge 命令后如何執行所有這些操作。

修復錯誤和回溯

發生錯誤……它們經常在編碼中發生!重要的是我們能夠修復它們。

不要慌!Git 提供了你所需的一切,以防你在所推送的代碼中犯錯,改寫某些內容或者只是想對所推送的內容進行更正。

### 切換到最新提交的代碼版本
git reset HEAD 
git reset HEAD -- filename # for a specific file
### 切換到最新提交之前的代碼版本
git reset HEAD^ -- filename
git reset HEAD^ -- filename # for a specific file
### 切換回3或5次提交
git reset HEAD~3 -- filename
git reset HEAD~3 -- filename # for a specific file
git reset HEAD~5 -- filename
git reset HEAD~5 -- filename # for a specific file
### 切換回特定的提交,其中 0766c053 為提交 ID
git reset 0766c053 -- filename
git reset 0766c053 -- filename # for a specific file
### 先前的命令是所謂的軟重置。 你的代碼已重置,但是git仍會保留其他代碼的副本,以備你需要時使用。 另一方面,--hard 標誌告訴Git覆蓋工作目錄中的所有更改。
git reset --hard 0766c053

對 Git 有用的提示和技巧

我們已經完成了所有細節部分!以下是一些 Git 提示和技巧,你可能會發現它們對改善工作流程非常有用!

搜索

### 搜索目錄中的字符串部分
git grep 'project'

### 在目錄中搜索部分字符串,-n 打印出 git 找到匹配項的行號
git grep -n 'project'

### git grep -C <行數> 'something' 搜索帶有某些上下文的字符串部分(某些行在我們正在尋找的字符串之前和之後)
git grep -C<number of lines> 'project'

### 搜索字符串的一部分,並在字符串之前显示行
git grep -B<number of lines> 'project'

### 搜索字符串的一部分,並在字符串之後显示行
git grep -A<number of lines> 'something'

看誰寫了什麼

### 显示帶有作者姓名的文件的更改歷史記錄
git blame 'filename'

### 显示帶有作者姓名和 git commit ID 的文件的更改歷史記錄
git blame 'filename' -l

日誌

### 显示存儲庫中所有提交的列表 該命令显示有關提交的所有信息,例如提交ID,作者,日期和提交消息
git log

### 提交列表僅显示提交消息和更改
git log -p

### 包含您要查找的特定字符串的提交列表
git log -S 'project'

### 作者提交的清單
git log --author 'wupx'

### 显示存儲庫中提交列表的摘要。显示提交ID和提交消息的較短版本。
git log --oneline

### 显示昨天以來倉庫中的提交列表
git log --since=yesterday

### 显示作者日誌,並在提交消息中搜索特定術語
git log --grep "project" --author "wupx"

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

【精選推薦文章】

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

想要讓你的商品在網路上成為最夯、最多人討論的話題?

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

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

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

堆 堆排序 優先隊列 圖文詳解(Golang實現)

引入

在實際應用中,我們經常需要從一組對象中查找最大值或最小值。當然我們可以每次都先排序,然後再進行查找,但是這種做法效率很低。哪么有沒有一種特殊的數據結構,可以高效率的實現我們的需求呢,答案就是堆(heap)

堆分為最小堆和最大堆,它們的性質相似,我們以最小堆為例子。

最小堆

舉例

如上圖所示,就為一個最小堆。

特性

  • 是一棵完全二叉樹

如果一顆二叉樹的任何結點,或者是樹恭弘=叶 恭弘,或者左右子樹均非空,則這棵二叉樹稱做滿二叉樹(full binary tree)

如果一顆二叉樹最多只有最下面的兩層結點度數可以小於2,並且最下面一層的結點都集中在該層最左邊的連續位置上,則此二叉樹稱做完全二叉樹(complete binary tree)

  • 局部有序

最小堆對應的完全二叉樹中所有結點的值均不大於其左右子結點的值,且一個結點與其兄弟之間沒有必然的聯繫

二叉搜索樹中,左子 < 父 < 右子

存儲結構

由於堆是一棵完全二叉樹,所以我們可以用順序結構來存儲它,只需要計算簡單的代數表達式,就能夠非常方便的查找某個結點的父結點和子節點,既避免了使用指針來保持結構,又能高效的執行相應操作。

結點i的左子結點為2xi+1,右子結點為2xi+2
結點i的父節點為(i-1)/2

數據結構

// 本例為最小堆
// 最大堆只需要修改less函數即可
type Heap []int

func (h Heap) swap(i, j int) {
    h[i], h[j] = h[j], h[i]
}

func (h Heap) less(i, j int) bool {
    return h[i] < h[j]
}

如上所示,我們使用slice來存儲我們的數據,為了後續方便我們在此定義了 swap 和 less 函數,分別用來交換兩個結點和比較大小。

插入-Push

如上圖所示,首先,新添加的元素加入末尾。為了保持最小堆的性質,需要沿着其祖先的路徑,自下而上依次比較和交換該結點與父結點的位置,直到重新滿足堆的性質為止。

這樣會出現兩種情況,要麼新結點升到最小堆的頂端,要麼到某一位置時發現父結點比新插入的結點關鍵值小。

上面的流程代碼如下:

func (h Heap) up(i int) {
    for {
        f := (i - 1) / 2 // 父親結點
        if i == f || h.less(f, i) {
            break
        }
        h.swap(f, i)
        i = f
    }
}

實現了最核心的 up 操作后,我們的插入操作 push 便很簡單,代碼如下:

// 注意go中所有參數轉遞都是值傳遞
// 所以要讓h的變化在函數外也起作用,此處得傳指針
func (h *Heap) Push(x int) {
    *h = append(*h, x)
    h.up(len(*h) - 1)
}

刪除-Remove

如上圖所示,首先把最末端的結點填入要刪除節點的位置,然後刪除末端元素,同理,這樣做也可能導致破壞最小堆的堆序特性。

為了保持堆的特性,末端元素需要與被刪除位置的父結點做比較,如果小於父結點,就要up(詳細代碼看插入)如果大於父結點,就要再和被刪除位置的子結點做比較,即down,直到該結點下降到小於最小子結點為止。

上面down的流程代碼如下:

func (h Heap) down(i int) {
    for {
        l := 2*i + 1 // 左孩子
        if l >= len(h) {
            break // i已經是恭弘=叶 恭弘子結點了
        }
        j := l
        if r := l + 1; r < len(h) && h.less(r, l) {
            j = r // 右孩子
        }
        if h.less(i, j) {
            break // 如果父結點比孩子結點小,則不交換
        }
        h.swap(i, j) // 交換父結點和子結點
        i = j        //繼續向下比較
    }
}

實現了核心的 down 操作后,我們的 Remove 便很簡單,代碼如下:

// 刪除堆中位置為i的元素
// 返回被刪元素的值
func (h *Heap) Remove(i int) (int, bool) {
    if i < 0 || i > len(*h)-1 {
        return 0, false
    }
    n := len(*h) - 1
    h.swap(i, n) // 用最後的元素值替換被刪除元素
    // 刪除最後的元素
    x := (*h)[n]
    *h = (*h)[0:n]
    // 如果當前元素大於父結點,向下篩選
    if (*h)[i] > (*h)[(i-1)/2] {
        h.down(i)
    } else { // 當前元素小於父結點,向上篩選
        h.up(i)
    }
    return x, true
}

彈出-Pop

當i=0時,Remove 就是 Pop

// 彈出堆頂的元素,並返回其值
func (h *Heap) Pop() int {
    n := len(*h) - 1
    h.swap(0, n)
    x := (*h)[n]
    *h = (*h)[0:n]
    h.down(0)
    return x
}

初始化-Init

在我們講完了堆的核心操作 up 和 down 后,我們來講如何根據一個數組構造一個最小堆。

其實我們可以寫個循環,然後將各個元素依次 push 進去,但是這次我們利用數學規律,直接由一個數組構造最小堆。

首先,將所有關鍵碼放到一維數組中,此時形成的完全二叉樹並不具備最小堆的特徵,但是僅包含恭弘=叶 恭弘子結點的子樹已經是堆。

即在有n個結點的完全二叉樹中,當 i>n/2-1 時,以i結點為根的子樹已經是堆。

func (h Heap) Init() {
    n := len(h)
    // i > n/2-1 的結點為恭弘=叶 恭弘子結點本身已經是堆了
    for i := n/2 - 1; i >= 0; i-- {
        h.down(i)
    }
}

測試

func main() {
    var h = heap.Heap{20, 7, 3, 10, 15, 25, 30, 17, 19}
    h.Init()
    fmt.Println(h) // [3 7 20 10 15 25 30 17 19]

    h.Push(6)
    fmt.Println(h) // [3 6 20 10 7 25 30 17 19 15]

    x, ok := h.Remove(5)
    fmt.Println(x, ok, h) // 25 true [3 6 15 10 7 20 30 17 19]

    y, ok := h.Remove(1)
    fmt.Println(y, ok, h) // 6 true [3 7 15 10 19 20 30 17]

    z := h.Pop()
    fmt.Println(z, h) // 3 [7 10 15 17 19 20 30]
}

完整代碼

堆排序

在講完堆的基礎知識后,我們再來看堆排序就變得非常簡單。利用最小堆的特性,我們每次都從堆頂彈出一個元素(這個元素就是當前堆中的最小值),即可實現升序排序。代碼如下:

// 堆排序
var res []int
for len(h) != 0 { 
    res = append(res, h.Pop())
}
fmt.Println(res)

優先隊列

優先隊列是0個或者多個元素的集合,每個元素都有一個關鍵碼,執行的操作有查找,插入和刪除等。

優先隊列的主要特點是支持從一個集合中快速地查找並移出具有最大值或最小值的元素。

堆是一種很好的優先隊列的實現方法。

參考資料

  • 《數據結構與算法》張銘 王騰蛟 趙海燕 編著
  • GO SDK 1.13.1 /src/container/heap

最後

本文是自己的學習筆記,在刷了幾道LeetCode中關於堆的題目后,感覺應該系統的學習和總結一下這一重要的數據結構了。

強烈建議看Go的源碼中關於heap的實現,仔細感受面向接口編程的思想,和他們的代碼風格以及質量。

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

【精選推薦文章】

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

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

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

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