簡介 Intro 相信在這個年代(2021)大多數的後端開發者對於分散式系統開發及加速都有不少心得
其中非常知名的快取應用,就包含了Memory Storage類型的NoSQL,比如Redis 或Memcached
今天寫的文章主要介紹在GCP上開發分散式系統,Redis的簡單應用
準備階段 既然是要介紹應用,那肯定是有實作的階段,要完成這個實作需要準備的東西有:
GCP Computer Engine 兩台Instance 或 兩台電腦 或 你有其他手段,比如容器/VM,能產生兩個概念實體的環境
Ubuntu 20.04 或其他支援Redis 6.0以上版本的OS(可能Redis 5.0應該也能完成這個小測驗)
基礎Shell技能
基礎網路技能
Node.js v14.x.x以上
NPM 套件管理工具
ioredis(nodejs對redis讀寫的套件之一)
介紹 在安裝完畢所有的東西後,首先要來說明原理,知行合一是非常重要的事情。
文中會提到Redis的設定配置,設計理念以及好處…等相關內容
記憶體資料庫 首先我打算介紹Redis ,它是一個記憶體資料庫,相似的產品有memcached 。
這兩者都屬於NoSQL也都是記憶體資料庫,這兩者取決差異在於
Redis更適合
複雜的資料型態,像是 string、hash、list 和 set
排序 in-memory 內的資料
持久性的 key 儲存
將你的資料做多個點的讀寫分離
自動化錯誤修復
使用/訂閱模式
備份和恢復資料
Memcached更適合
簡單的模型
使用多執行續來執行多節點程序
擴展/收縮功能
根據需求增加/減少節點
將資料分至不同區儲存
快取物件 (圖片、音檔等等)
兩者之間的選擇需要讀者自行選擇,雖然實作手段不同,但設計理念是相似的。
以筆者目前處理的業務IoT系統平台來說,會面臨更複雜的資料型態。
且由於採用MQTT的方式進行機台與系統的資料傳遞,MQTT傳遞方式是屬於pub/sub的模式(也就是使用/訂閱模式)
因此在筆者經手的案子上,Redis更加適合。因此這次實作以Redis Cluster為主
Redis 基本上Google搜尋能力是必備的,關於Redis的介紹隨便查一下就有很多比這篇文章更詳細的介紹。
主要幫讀者整理了Redis的幾個特性,避免讀者在未來的設計及開發上踩雷
長話短說,基本上整理了這三點提供參考
Redis在6.0版本以前是單執行續的,準確地說
其網路I/O和key-value讀寫是由一個執行序完成的
而其他部分,e.g. 持久化模組、叢集模組等都還是多執行序的
詳細的說明請點我
在試圖使用多執行序前,請先試著優化自己的查詢方式,改採用pipe。
如果不跳脫直覺設計觀念,維持著one command In,one response Out,就算用上多執行序
我可以肯定效能還是一樣糟糕,因為每一次下達命令對Redis server來說都是一個很大的資源消耗
如果情況允許,請多沉澱心情,設計適合pipe查詢模式的系統
由於Redis資料結構的特性,Key的長度不建議太長,太長的key會導致redis下降
長度為10 : 寫->平均消耗時間0.053ms,讀->0.040ms
長度為20000:寫->平均消耗時間0.352ms,讀->0.084ms
Redis Cluster 先不要直接被叢集這個名詞嚇到了,其實官方很親民的提供了很強大的cluster設置工具及組件。
基本上只要跟著官方的文件操作,很快也能搞出一個屬於自己的Redis Cluster
而Redis Cluster的特性自行搜尋也有很多結果,這邊也不多提。
但相對少見的資料大概是對於Redis Cluster的效能描述。
雖然對這類資料庫做基準測試是一件很沒有意義的事情,因為基本上只是在考驗網路I/O速度
不過Cluster的效能可以簡單理解為N*M(Master Node 數量)
e.g.
使用Redis官方文件配置Cluster,應該會有3個主節點以及3個從結點。
那這個Cluster的效能,可以直接將在部屬的機器上測得單個Redis server效能 乘上 3個主節點數量
假設我在我的電腦測得Redis IOPS 分別為 SET 200000/s GET 400000/s
那Cluster的效能直接*3就好,但實務上受限於網路及socket分配速度等因素,實際效能會有瓶頸
取決於系統負荷如何,事實上不應該將所有的資料只交由一台機器負責
Redis安全策略 先假設讀者已經裝好的Redis,並且也很順利的利用cli連上Redis。
在預設情況下可以試著,從另外一台電腦,透過網路連線到Redis Server。
預設情況下應該會回報一些關於security的問題。(金魚腦)
這是因為Redis server為了效能考量其實並沒有真正的考慮到Security。
因此如果直接將Redis server對外開放IP,很可能會受到反序列化這類的攻擊。
(mongoDB存在類似問題)
所以我們需要一個大使對外開放,代替我們對Redis server進行操作。
而大使模式本身真正的用途不僅僅如此,也不僅僅用於Redis server,這後面會再度提到。
Redis安裝及配置 雖然網路上已經有很多資源了,但這裡除了安裝,也會提到一些需要注意的小細節。
安裝Redis 1 2 3 4 5 6 7 8 wget https://download.redis.io/releases/redis-6.2.5.tar.gz -P /usr/local/src # 版本可以自行更換 cd /usr/local/src/ #移動到剛剛下載的資料夾 tar -zxvf redis-6.2.5.tar.gz #解壓縮Redis安裝包 cd redis-6.2.5 #移動到解壓縮後的資料夾 install GCC #安裝GCC yum install -y gcc-c++ #利用yum安裝C++ 如果沒有安裝yum自己想辦法 make MALLOC=libc install #正式安裝Redis server
配置Redis Cluster 安裝完畢之後,接下來要進行Cluster配置
1 2 3 4 5 cd ~ #或是移動到你想配置從集的資料夾 mkdir redis-cluster && cd redis-cluster mkdir 7000 7001 7002 7003 7004 7005 # 這邊要從7000重覆到7005 可以透過寫shell script解放雙手 vim ./7000/7000.redis.conf
要撰寫的內容為
1 2 3 4 5 6 7 8 9 port 7000 bind 0.0.0.0 dir ~/redis-cluster/7000/data cluster-config-file nodes-7000.conf cluster-enabled yes cluster-node-timeout 5000 cluster-announce-ip 127.0.0.1 appendonly yes daemonize yes
從7000~7005都配置完畢之後
1 redis-server 127.0.0.1:7000 #這個也要從7000~7005
基本上這樣應該已經先架設完畢cluster,測試連線可以輸入
1 redis-cli -c -p 7000 -h 127.0.0.1 #只有在本機可以輸入127.0.0.1,如果是另一台要連線需要輸入對外的IP
如果可以正常使用redis那基本就是安裝且配置成功了
Ambassador 大使模式在分散式系統中,其實主要是提供不同應用程式之間的溝通,大使的功能跟應用程式分離。
可以使在撰寫應用程式時,減少與其他服務的耦合。
因此大使不只能夠使用在Redis,也能應用在SQL或其他服務之上
能夠使整體架構更加彈性且增加解耦合性,但要注意的是透過大使處理服務,會產生延遲…等常見問題
而本篇文章要說的,正是由於Redis server極度不建議對外開放,所以透過大使操作在遠端機器的Redis server
大使也能夠過濾惡意字串,避免Redis server直接遭受攻擊
總結的說,大使大致上做的事情有;
防止惡意請求直接破壞應用程式
增加系統架構整體解耦合性
監控性能指標以及機器環境…等
接下來要實作的內容的架構圖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 10.0.14.15 |---------------------------| |------------- |-----------------| | |-----------------| | | | | | | |-------------| | | | | | | | |Redis server| | | | | | | | |------|------| | | | | Instance A | ====|===>| | |<===|=X=X=X=|===> | | | | |------|------| | | Don't | | | | | | Ambassador | | |Provide| | | | | |------|------| | |Service| |-----------------| | |--------|--------| | | | | | | |-----------------| | |--------|--------| | | INTERNET | | | | |------|------| | | | | | | | | | | | | | | | | | | | | | | Instance B | ====|===>| |WebSiteServer| |<===|=======|===> | | | | | | | |Service| | | | | | | | | | | | | | |-------------| | | | |-----------------| | |-----------------| | | 10.0.14.16 | VPC NetWork [Same Region] | | 35.x.x.x |---------------------------| |-------------
Ambassador撰寫 使用Node.js搭配Express撰寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const ioredis = require ("ioredis" );const express = require ("express" );const router = express.router();const config = [ {port :"7000" ,host :"127.0.0.1" },{port :"7001" ,host :"127.0.0.1" }, {port :"7002" ,host :"127.0.0.1" },{port :"7003" ,host :"127.0.0.1" }, {port :"7004" ,host :"127.0.0.1" },{port :"7005" ,host :"127.0.0.1" } ] let cluster = ioredis.Cluster(config);function getKeyAndValue ({data} ) { return { key :data.key, value :data.value } } router.post('/set' ,(req,res,next )=> { let payload = getKeyAndValue({data :req.body}); cluster.set(payload.key,payload.value); res.status(200 ).send(); }); router.post('/get' ,(req,res,next )=> { let payload = getKeyAndValue({data :req.body}); cluster.get(payload.key,payload.value,(error,apiResponse )=> { if (error){ console .log(error) res.staus(404 ).send(`${error} ` ); }else { console .log(apiResponse); res.status(200 ).send(apiResponse); } }); res.status(504 ).send(); });
順便附上Redis Cluster自動安裝架設的Shell Script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #!/bin/bash BASE_DIR=/usr/local /redis-cluster PORTS=`seq 7000 7005` START_UP=$BASE_DIR /startup.sh SERVICE=redis-cluster.service function remove_cluster (){ ps -ef | grep redis-server | grep cluster | awk '{print $2}' | xargs kill -9 systemctl disable redis-cluster.serivce if [ -d $BASE_DIR ];then rm -rf $BASE_DIR fi } if [ "$1 " = "--remove" ];then remove_cluster exit 0 fi if [ ! -f "/usr/local/bin/redis-server" ];then echo "Redis not ready,Please install redis firstly!" echo "" echo "===== Install redis as follows =====" wget https://download.redis.io/releases/redis-6.2.5.tar.gz -P /usr/local /src cd /usr/local /src/ tar -zxvf redis-6.2.5.tar.gz cd redis-6.2.5 install GCC if not exists yum install -y gcc-c++ make MALLOC=libc install echo "" fi echo -n "Enter your host's public address(default 127.0.0.1):" read cluster_addressmkdir -p $BASE_DIR cd $BASE_DIR function generate_instance_conf (){ echo "configuring server $1 " echo "" > $1 /redis.conf echo "port $1 " >> $1 /redis.conf echo "bind 0.0.0.0" >> $1 /redis.conf echo "dir $BASE_DIR /$1 /data" >> $1 /redis.conf echo "cluster-config-file nodes-$1 .conf" >> $1 /redis.conf echo "cluster-enabled yes" >> $1 /redis.conf echo "cluster-node-timeout 5000" >> $1 /redis.conf if [ -n "$cluster_address " ];then echo "cluster-announce-ip $cluster_address " >> $1 /redis.conf else echo "cluster-announce-ip 127.0.0.1" >> $1 /redis.conf fi echo "appendonly yes" >> $1 /redis.conf echo "daemonize yes" >> $1 /redis.conf } echo "#!/bin/bash" > $START_UP servers= for port in $PORTS ;do mkdir -p $BASE_DIR /$port /data generate_instance_conf $port echo "/usr/local/bin/redis-server $BASE_DIR /$port /redis.conf" >> $START_UP servers="$servers 127.0.0.1:$port " done chmod +x $START_UP echo "starting servers..." $START_UP sleep 5s echo "servers ready!" echo "configuring cluster..." /usr/local /bin/redis-cli --cluster create $servers --cluster-replicas 1 echo "configured!" cat << EOT > $BASE_DIR/redis-cluster.service [Unit] Description=Redis 6.2.5 Cluster Service After=network.target [Service] WantedBy=default.target EOT echo "Creating redis cluster serivce..." ln -s $BASE_DIR /$SERVICE /etc/systemd/system/$SERVICE sudo systemctl daemon-reload && sudo systemctl enable $SERVICE && sudo systemctl start $SERVICe echo "" echo "Completed!" echo "" echo "Test cluster with: /usr/local/bin/redis-cluster -c -h 127.0.0.1 -p 7000" echo "" echo "127.0.0.1:7000>cluster nodes"
撰寫中
參考資料