一。問題背景
為了 HTTPS 拋棄了釘子戶國內虛擬主機,趁機用 Node 重寫了之前的 PHP 服務,放到好貴的 VPS 上,搬出去後發現抓取國內 RSS 經常超時,不超時的情況也需要 loading 20s 的樣子,完全不可用。搬意已決,那就想辦法提速
之前方案是請求時現抓,拿回來解析完畢後響應請求,過程看起來很慢,但實際很快,一般 loading 不超過 3s,自用可以接受,所以只做了客戶端記憶體快取和離線快取
現在 20s 完全無法忍受,所以先上見效最快的記憶體快取:
-
定時抓取,預先存入 redis
-
redis 記憶體快取,簡單過期策略
每 2 小時去全部抓取一遍,存入 redis,請求先過快取檢查,快取裡有就不現抓,除非服務剛剛重啟過,才需要現抓。定時抓取時不影響正常響應,因為抓取場景可以不用考慮髒資料問題,新一點舊一點沒太大關係(但極端情況資料是 定時抓取間隔 + 客戶端快取過期時間 之前的,這時資料就很舊了)
二。安裝配置 redis
安裝
CentOS 環境,編譯安裝 redis stable:
# 下載
wget http://download.redis.io/releases/redis-stable.tar.gz
# 解壓
tar -axvf redis-stable.tar.gz
cd redis-stable
# 編譯安裝
make
make test
make install
預設安裝路徑在 /usr/local/bin:
$ ls /usr/local/bin | grep 'redis'
redis-benchmark
redis-check-aof
redis-check-rdb
redis-cli
redis-sentinel
redis-server
配置
預設配置檔案在安裝包的根目錄 redis.conf:
mkdir -p /etc/redis/
cp redis.conf etc/redis
# 修改配置項
vi etc/redis/redis.conf
# 後臺執行,預設不後臺
# 把 GENERAL 節的 daemonize no 改為 daemonize yes
# 改密碼,預設免密登入
# 把 SECURITY 節的 requirepass 去掉註釋,改為 requirepass <mypassword>
其它配置項埠號,日誌目錄等等無所謂,需要的話修改,然後啟動驗證:
# 啟動服務
redis-server /etc/redis/redis.conf
# 客戶端連線
redis-cli
auth <mypassword>
# 操作
set 'key' 'value'
get 'key'
P.S.redis 的更多命令,請檢視 Command reference – Redis,或者線上試玩Try Redis
新增到系統服務
redis-server /etc/redis/redis.conf 每次這樣啟動看著比較難受,新增到系統服務裡就可以通過 service redis <cmd> 管理了:
# 拷貝啟動指令碼
cp util/redis_init_script /etc/init.d/
# 改名
mv /etc/init.d/redis_init_script /etc/init.d/redis
然後修改配置項:
vi /etc/init.d/redis
# 修改第二行的 chkconfig xxx 為 chkconfig 2345 80 90
# 確認埠號正確 REDISPORT=6379
# 確認 server 可執行檔案路徑正確 EXEC=/usr/local/bin/redis-server
# 確認 cli 路徑正確 CLIEXEC=/usr/local/bin/redis-cli
# 確認 redis.conf 路徑正確 CONF="/etc/redis/${REDISPORT}.conf"
# start 改為後臺執行
# 把$EXEC $CONF 改為$EXEC $CONF &
P.S.# chkconfig 2345 80 90 中,2345 是指執行級別,80 90 分別表示啟動/關機優先順序,數值越小越優先,控制順序,更多資訊請檢視 chkconfig
預設讀取 /etc/redis/${REDISPORT}.conf,也就是 /etc/redis/6379.conf,以應對多例項情況,所以我們給配置檔案改名:
mv /etc/redis/redis.conf /etc/redis/6379.conf
最後註冊系統服務:
# 註冊
chkconfig --add redis
# 設定自啟動
chkconfig redis on
可以通過 service 命令管理了:
service redis start
三.node 接 redis
有現成的第三方模組 node_redis:
npm install redis --save
嘗試連線:
const redis = require('redis');
const PORT = 6379;
const HOST = '127.0.0.1';
const PWD = 'mypassword';
const OPTS = {auth_pass: PWD};
// connect redis
let client = redis.createClient(PORT, HOST, OPTS);
client
.on('error', (err) => {
console.log('Error ' + err);
})
.on('ready', () => console.log('redis connected'));
connect 之後就可以隨便操作了,API 與 redis 命令一致:
// 寫
client.set(key, val, callback);
// 讀
client.get(key, (error, val) => {});
// 設定有效期
client.expire(key, seconds);
// 檢查過期
client.expire(url, (error, ttl) => {
if (ttl > 0) console.log('alive');
else console.log('died');
});
特別注意:所有 callback 都是 Node 經典方式,第一個引數是 err,而不是 data
做一個簡單的快取層,結構如下:
cache
- queue
- clearQueue()
- expire()
- ttl()
+ set()
+ get()
+ checkFresh()
配合訪問抓取和定時抓取:
fetch
- onerror(error) => {
emitter.emit('error', error);
};
- onsuccess(data) => {
data && cache.set(url, data);
};
- fetchNow()
+ fetch() => {
if (noCache) {
cache.checkFresh(url, (fresh) => {
if (!fresh) console.log('schedule force fetch now'), fetchNow();
else oncancel('cache is still fresh now');
}
}
else {
cache.get(url, (data) => {
if (data) console.log('fetch from cache'), onsuccess(data);
else fetchNow();
});
}
}
訪問抓取走快取,直接從快取去,沒有才抓。定時抓取強制不走快取,但檢查過期,如果資料還很新,就取消抓取任務,不新的話現抓,抓取成功就過快取層記錄下來
P.S. 定時抓取檢查過期是為了避免不必要的重複抓取,比如服務掛了重啟了,redis 的資料不受影響,仍然是新的,這時沒必要再抓一遍
四。總結
提速效果很明顯,之前訪問國內資源 20s 的 loading 縮短到 5-6s 了,國外資源也快了 1-2s 的樣子,比起之前的 PHP 服務,5-6s 還是挺慢,但接下來的優化項就沒這麼簡單粗暴了:
todo
1. 介面拆分,有介面返回 128K 文字,考慮分頁
2.redis 資料結構優化,目前是 url key ��應一個很大的 JSON 字串,應該有更科學的方式
3. 長連線,降低路途成本
暫無評論,快來發表你的看法吧