1. 主要問題
1.1 什麼是redis?
答:redis 是一個遠端記憶體的非關系型資料庫。
1.2 redis能夠存儲什麼資料?存儲量如何?
答:string 、list、hash、set、zset;
redis官網(https://redis.io/topics/data-types),一個String類型的value最大可以存儲512M;
list、hash、set、zset元素個數最多為2^32-1個,也就是4294967295個。
注意: jedis 的源碼,支援String 類型的value 存儲1G。
1.3 redis的主要優點?redis為什麼快?
答:速度快;因為他是記憶體資料庫,直接在記憶體中處理請求。
1.4 redis的缺點?如何解決?
答:1)記憶體存儲,不在硬碟中;
解決辦法:
采用兩種方式進行持久化(同步資料SNAPSHOT、同步指令AOF);
2)redis伺服器單片處理資料量有限;
解決辦法:
采用伺服器叢集、資料分片、資料主從複制,可以解決,些技術此處隻做該要介紹,具體實作方式參考《redis-in-action》一書;
1.5 redis 能不能用作一般網絡系統的主資料庫?存在有那些問題?如何解決?
答:可以,需要考慮資料持久化、伺服器叢集、資料分片等問題,具體操作見下;
2. 應用場景和實作
2.1 分頁查詢
/**
* 代碼清單1-8
* 1。 擷取分頁文章的ids;
* 2。 擷取分頁文章的所有資訊;
*
* @param conn redis 連結
* @param page 查詢文章的頁碼
* @param order 文章分組資料zset 的key
* @return 分頁文章的查詢結果
*/
public List<Map<String, String>> getArticles(Jedis conn, int page, String order) {
int start = (page - 1) * ARTICLES_PER_PAGE;
int end = start + ARTICLES_PER_PAGE - 1;
// 擷取分頁文章的ID;
Set<String> ids = conn.zrevrange(order, start, end);
List<Map<String, String>> articles = new ArrayList<Map<String, String>>();
for (String id : ids) {
// 根據文章ID,擷取文章的所有資訊;
Map<String, String> articleData = conn.hgetAll(id);
articleData.put("id", id);
articles.add(articleData);
}
return articles;
}
/**
* 代碼清單1-10
* 1。 将文章加入分組的zset中;
* 2。 擷取zset中的分頁資料;
*
* @param conn redis 連結
* @param group 指定分組名稱
* @param page 頁碼數
* @param order 文章zset
* @return 分組、分頁後的所有文章
*/
public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page, String order) {
// 建構文章分組的鍵名
String key = order + group;
if (!conn.exists(key)) {
ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
// 将group:group、order中的文章,取最大值,加入key中
conn.zinterstore(key, params, "group:" + group, order);
conn.expire(key, 60);
}
return getArticles(conn, page, key);
}
2.2 分布式鎖
參考連結:https://blog.csdn.net/leinminna/article/details/101155733
2.3 資料持久化
redis将資料持久化到硬碟有兩種方法:
方法一: 快照(snapshotting),将某一時刻的所有資料都寫入硬碟(dump.rdb快照檔案);
save 60 1000 // 60s内發生1000次寫入觸發bgsave,建立快照,
stop-writes-on-bgsave-error no // 發生錯誤是否停止寫入
rdbcompression yes // 是否壓縮
dbfilename dump.rdb // 快照檔案名稱
快照的其他指令:
SAVE:建立快照。接到save指令的redis伺服器在快照建立完成之前不會相應任何其他指令。
BGSAVE:建立快照。redis伺服器會建立一個子程序,在快照建立完成之前父程序繼續處理其他指令。
SHUTDOWN:關閉伺服器。redis會馬上執行SAVE阻塞其他指令,在SAVE完成後關閉伺服器。
SYNC:一個redis伺服器向另一個redis伺服器發送SNYC,如果主伺服器沒有執行BGSAVE,那麼,主伺服器就會執行BGSAVE指令。
方法二: 隻追加檔案(append-only-file,AOF),将被執行的指令複制到硬碟裡;
appendonly yes // 是否開啟AOF
appendfsync-no-rewrite everysec // 每秒執行一次同步,将寫指令同步到硬碟
auto-aof-rewrite-percentage 100 //
auto-aof-rewrite-min-size 64mb // 當AOF檔案大于64MB,并且體積至少是上次重寫後體積的2倍,Redis 将觸發BGREWRITEAOF
兩個方法可以單獨使用,也可以同時使用;
BGREWRITEAOF:優化AOF檔案。移出備援指令,縮小檔案體積,類似于BGSAVE名命,子程序執行。
2.4 資料分片
分片: 就是基于某些簡單的規則,将資料劃分為更小的部分,然後根據資料所屬的部分來決定将資料發送到那個位置上;
/**
* 一個簡單的分片規則,通過分片數量和的鍵計算新的分片的ID,進而,将資料存儲到不同的hset中
*
* @param base 原來存儲的hash的基礎鍵
* @param key 該條資料的key
* @param totalElements 存儲資料的總量
* @param shardSize 設計的分片數量
* @return 生成的分片編号:新hash的鍵,用于存儲該條資料的分片的鍵
*/
public String shardKey(String base, String key, long totalElements, int shardSize) {
long shardId = 0;
if (isDigit(key)) {
shardId = Integer.parseInt(key, 10) / shardSize;
} else {
CRC32 crc = new CRC32();
crc.update(key.getBytes());
long shards = 2 * totalElements / shardSize;
shardId = Math.abs(((int) crc.getValue()) % shards);
}
return base + ':' + shardId;
}
/**
* 儲存該條資料到分片中
*
* @param conn jedis連結
* @param base 原來資料存儲的鍵
* @param key 該條資料的key
* @param value 該條資料的值
* @param totalElements 存儲資料的總量
* @param shardSize 設計的分片數量
* @return 儲存是否成功
*/
public Long shardHset(
Jedis conn, String base, String key, String value, long totalElements, int shardSize) {
String shard = shardKey(base, key, totalElements, shardSize);
return conn.hset(shard, key, value);
}
/**
* 從分片中擷取該條資料
*
* @param conn jedis連結
* @param base 原來資料存儲的鍵
* @param key 該條資料的key
* @param totalElements 存儲資料的總量
* @param shardSize 設計的分片數量
* @return 擷取的資料
*/
public String shardHget(
Jedis conn, String base, String key, int totalElements, int shardSize) {
String shard = shardKey(base, key, totalElements, shardSize);
return conn.hget(shard, key);
}
2.5 叢集
單台redis 伺服器的讀寫性能和單片容量有限,然後就有了叢集;
擴充讀性能: 将主伺服器的資料複制到重伺服器,進而可以同時通路多台從伺服器;
擴充寫性能: 不建議将資料寫入不同伺服器,後續資料彙總會出現諸多問題;
主從鍊: 使用樹狀結構,在減少資料傳輸次數,同時能降低主伺服器的傳輸負載;
處理系統故障: 詳細資訊參考《redis-in-action》page73-76;
2.6 降低記憶體占用
redis 可以存儲string 、list、set、hash、zset。
但是list、set、hash、zset他們的結構被稱為體積較大的結構,使用會産生許多備援空間,這四種資料結構可以被分割成為多個體積較小的結構。
list: 它的資料結構實際上是一個雙向連結清單結構,每個節點包含了三個指針占用的三個空間、一個字元串長度占用空間、一個剩餘長度占用空間、字元串本身和一個額外位元組占消耗用空間。
壓縮效率:以’'ten"占用的空間為例,采用list,該節點将消耗21位元組的記憶體,采用壓縮清單,隻會消耗2個位元組記憶體,很令人心動。
但,萬物是公平的,當使用壓縮清單這種資料結構時,當存儲的元素的大小過大和數量長時,就會對redis的性能産生影響。其他三種結構類似。
開啟壓縮清單的配置檔案:
// 《redis-in-action》介紹:list壓縮清單長度限制在500-2000,單個元素體積小于128位元組,list壓縮清單的性能會處于合理的範圍之内。
list-max-ziplist-entries 1024 // list壓縮清單,最大元素數量;
list-max-ziplist-value 64 // list壓縮清單, 每個節點最大體積64位元組
hash-max-ziplist-entries 512 // hash壓縮清單,最大元素數量
hash-max-ziplist-value 64 // hash壓縮清單, 每個節點最大體積64位元組
zset-max-ziplist-entries 128 // zset壓縮清單,最大元素數量
zset-max-ziplist-value 64 // zset壓縮清單, 每個節點最大體積64位元組
set-max-intset-entries 512 // set限制使用十進制整數,
當壓縮清單中的元素超過了這個配置,那麼,壓縮清單将将會失效,資料結構轉化為普通的資料結構,即使之後資料再次滿足配置。
推薦書籍:《redis-in-action》