Redis(REmote DIctionary Server)

Redius 為非關聯式的鍵值資料庫(Map)


支援的資料型別:

  • 字串型態(String)
    包含字串(JSON,BASE64,二進位資料),整數,浮點數
  • 雜湊(Hash)型態
    巢狀 key-value,無順序性
  • 列表(List)型態
  • 集合(Set)型態
  • 有序集合(Sorted Set)型態
    Zset

指令和 key 型別要對,不然會噴 Exception

功能特性

  • Redis 將所有資料都儲存在記憶體
    讀寫速度比硬碟快上許多

  • Redis 提供了持久化的支援
    記憶體的資料以非同步的方式輸出到硬碟裡

  • 可以幫每個 key 設定存活時間 (Time To Live, TTL),常用於快取和佇列

  • 提供了十幾種不同程式語言的 API

安裝


Redis官方並不對Windows進行安裝支援的開發,但微軟的Open Technologies Inc.還是發佈了一個可以在Windows上執行Redis的分支,目前最新版只到3.2版,請參考:
https://github.com/MicrosoftArchive/redis/releases

根據次版本編號(也就是第一個小數點數字)為偶數的版本是穩定版(Stable),奇數版本為非穩定版

變成手動後要下指令開,關閉視窗即關閉

1
redis -server C:\redis.windows.conf

也可以寫腳本,先開一文字文件,再輸入指令後把檔名改成批次檔(.bat)

1
2
@echo off
redis-server C:\Redis\redis.windows.conf --appendonly yes
1
2
@echo off
redis-cli shutdown

開啟執行批次檔後可以使用 CMD 來下指令

1
2
3
4
5
6
7
8
redis-cli

IP:6379> ping
PONG
IP:6379> set Hello REdis
OK
IP:6379> get Hello
"REdis"

使用 set , get 方式設定 key,value


輸入資料後可以搭配使用 redis 的圖形化介面來看存取的資料
https://github.com/qishibo/AnotherRedisDesktopManager

預設為 16 個儲存空間如需要切換的話需下指令,不切換預設為 db0

1
2
// 切換到第15個儲存空間
jedis.select(15);

與 MySQL 不同,不開放,所以無帳號密碼設定上的需要(扮演暫存,快取角色)

操作資料

  • Redis 會根據新增資料使用的指令,自動推斷型別
  • 預設初始值為 null,key 為唯一,如同名會覆蓋

字串操作指令 :

1
2
3
4
5
6
GET key :

SET key newValue :

DEL key :

在 Java 中操作指令 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Jedis jedis = new Jedis("localhost", 6379);
jedis.set("key","value");
jedis.get("key");//value

jedis.append("key","value2"); //value被替換成value2

// 回傳value的'byte'長度
jedis.strlen("key");

// 回傳提供索引值(單位為byte)的value,有包含start~end
jedis.getrange("key",2,5);//lue2

// 也可用負數表示,從右至左(-1為最後一個byte,-2為倒數第二個)
jedis.getrange("key",-3,-1);//ue2

多筆資料處理

1
2
3
jedis.mset("key1", "value1", "key2", "value2", "key3", "value3");
List<String> data = jedis.mget("key1", "key2", "key3");
for (String str : data)

處理整數資料

1
2
3
4
5
6
for (int i = 1; i <= 100; i++) {
jedis.incr("num");
}
//incr(); 會直接做加總,在圖形化介面可以看到value為100
jedis.decr("num");
// 會減一

你的 value 必須為數字格式才能使用以上方法

處理位元資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 位元
// b a r
// 01100010 01100001 01110010
//
// OR運算
//
// a a r
// 01100001 01100001 01110010
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//
// c a r
// 01100011 01100001 01110010
jedis.set("foo", "bar");
System.out.println(jedis.getbit("foo", 6));//回傳為true

jedis.setbit("foo", 6, false);
jedis.setbit("foo", 7, true);
System.out.println(jedis.get("foo"));//bar -> aar

jedis.set("foo1", "bar");
jedis.set("foo2", "aar");
jedis.bitop(BitOP.OR, "result", "foo1", "foo2");
System.out.println(jedis.get("result"));//car

List資料

  • List 如同 LinkedList,或是可視為佇列或堆疊進行操作,因為可以進行 List 的左右兩端的加入與移除,或是取得某一部份內容,但搭配索引值存取的效能表現就較不理想
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
// 從左邊放入
jedis.lpush("customers", "David", "James", "Vincent", "Ben", "Ron", "George", "Howard");
// List內容:"Howard", "George", "Ron", "Ben", "Vincent", "James", "David"

jedis.lpop("customers");//Howard,拿到最左邊的value且移除

// 從右邊放入
jedis.rpush("customers", "Jerry", "Joe", "Smith");
// List內容:"George", "Ron", "Ben", "Vincent", "James", "David", "Jerry", "Joe", "Smith"

// 拿出但不移除
List<String> range1 = jedis.lrange("customers", 3, 6);
for (String customer : range1)
System.out.println(customer);

// 回傳元素個數,key不存在就回傳0
jedis.llen("customers");

//去頭去尾,等於你要保留的索引值
jedis.ltrim("customers", 3, 6);
List<String> range2 = jedis.lrange("customers", 0, jedis.llen("customers") - 1);
//List<String> range2 = jedis.lrange("customers", 0, - 1); 最右邊為-1
for (String customer : range2)
System.out.println(customer);

// 插入元素
jedis.linsert("customers", LIST_POSITION.BEFORE, "David", "Jedis");//插入在David前面
//也可LIST_POSITION.AFTER
List<String> range3 = jedis.lrange("customers", 0, jedis.llen("customers") - 1);
for (String customer : range3)
System.out.println(customer);

Hash資料

  • 目的是讓 Redis 可以如同關聯式資料庫(表格-欄位)的存取對應關係
  • Hash 型態也是一種 key-value 結構,儲存欄位與對應的值,但值只能是字串資料,不支援其它型態,因此不能做巢狀結構 (Redis 所有資料型態都不支援巢狀資料結構)
  • Hash 型態很適合用來儲存物件,利用物件所屬類別與 ID 做為 key,而實體變數名稱做為欄位,而欄位值存著實體變數的值
  • 可在購物車使用
1
2
3
4
jedis.hset("pen:1", "brand", "SKB");
jedis.hset("pen:1", "price", "10");
jedis.hget("pen:1", "brand");//SKB
jedis.hget("pen:1", "price");//10

Set

  • 重複資料無法放入,資料為唯一
  • key 之間還能進行聯集、交集與差集等運算 (如文章分類標籤)
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
jedis.sadd("post:1:tags", "Java");
jedis.sadd("post:2:tags", "Java", "Servlet");
jedis.sadd("post:3:tags", "Java", "Servlet", "JSP");
jedis.sadd("tag:Java:posts", "2", "1", "3");
jedis.sadd("tag:Servlet:posts", "3", "2");
jedis.sadd("tag:JSP:posts", "3");

for (String str : jedis.smembers("post:3:tags")) {
System.out.println(str);
}
jedis.sismember("post:2:tags", "JSP");//false


// 差集運算
jedis.sdiff("A集合","B集合"); //A-B剩下的東西,A比B多了什麼

// 交集運算
jedis.sinter("A集合","B集合");//屬於A也屬於B的元素

// 聯集運算
jedis.sunion("A集合","B集合");//A+B,重複的不放

// 放入資料
jedis.sadd("candidate", "David", "James", "Vincent", "Peter", "Ron", "George");

//隨機拿取不重複
System.out.println(jedis.srandmember("candidate"));

// 回傳List<String>,拿3個,如為負數就可以重複
System.out.println(jedis.srandmember("candidate", 3));
// System.out.println(jedis.srandmember("candidate", -3));

Zset

  • Zset(Sorted Set)與 Set 不同之處就在於是有序性的。每個元素都會連結一個分數,讓我們可以輕鬆完成最高(或最低)的前 N 個元素

  • Zset 與 List 也有些相似,兩者皆為有序性,也都能取得某一個範圍的元素,但使用的情境卻是不同的:

  1. List 是 Linked 結構,所以對兩端資料存取效率極高,但對中間元素處理較慢,適合用在新的資料或最舊的資料(兩端)存取
  2. Zset 非 Linked 結構,即使是取中間元素資料速度也很快
  3. List 要調整某個元素位置效能表現不佳,但 Zset 表現極佳(透過調整元素連結的分數即可)
  4. Zset 比起 List 更耗費記憶體的使用
  • 分數資料可以使用整數之外也可以使用浮點數
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
Map<String, Double> scores1 = new HashMap<>();
scores1.put("David", new Double(100));
scores1.put("James", new Double(85));
scores1.put("Ron", new Double(67));
scores1.put("Vincent", new Double(72));
scores1.put("George", new Double(87));
scores1.put("Howard", new Double(75));
scores1.put("Peter", new Double(80));

Map<String, Double> scores2 = new HashMap<>();
scores2.put("David", new Double(90));
scores2.put("James", new Double(77));
scores2.put("Ron", new Double(78));
scores2.put("Vincent", new Double(68));
scores2.put("George", new Double(95));
scores2.put("Howard", new Double(81));
scores2.put("Peter", new Double(72));

jedis.zadd("scores1", scores1);
jedis.zadd("scores2", scores2);

jedis.zadd("scores1", 92, "Vincent");

// 叫出資料
jedis.zscore("scores1", "Vincent"); //92

// 預設為由小到大(分數)
jedis.zrange("scores1", 1, -1);

// '('代表不包含,也可以用+inf或-inf來代表正無窮或負無窮
jedis.zrangeByScore("scores1", "80", "(100");

// 小於等於100分的前3人
jedis.zrevrangeByScore("scores1", "100", "0", 0, 3);

// 加8分
jedis.zincrby("scores1", 8, "Peter");

// 他的位置(索引值)-小到大
jedis.zrank("scores1", "David");

// 大到小
jedis.zrevrank("scores1", "David");

// 加總(預設)後排序
ZParams zp = new ZParams();
// Aggregate.SUM(預設) | Aggregate.MAX | Aggregate.MIN
jedis.zinterstore("scoresinter", zp.aggregate(Aggregate.SUM), "scores1", "scores2");
System.out.println(jedis.zrevrange("scoresinter", 0, -1));

JSON 資料格式

  • JSON(JavaScript Object Notation)為一種輕量級的資料交換語言,以文字為基礎而讓人容易閱讀理解,在 WEB 的資料傳輸上,佔有相當重要的地位

可以讓不同的程式語言可以互相進行資料傳輸的交換使用,也實現了以文字形式對物件進行序列化

  • XML 也是類似的概念,但因為 XML 是一種完整標記語言,因此在程式撰寫判讀上比起 JSON 過於笨重與較多心力,故現今瀏覽器解析、手機資料交換都採用較輕量的 JSON 以取得最佳效能

JSON 是一種以鍵值(key-value)的方式存取資料:

1
2
3
4
// JSON Object
{“myKey”:myValue}
// JSON Array
[{“myKey”:myValue},{“myKey2”:myValue2}]

Java SE 沒有包含 JSON API,要 import jar 檔

Base64 資料格式

  • Base64 是一種編碼格式,可以將二進制位元資料編成可顯示字元,用來進行儲存或是傳輸資料使用,例如圖片轉成 Base64 字串後,可以用在 HTML 標籤上顯示

  • 在 HTTP 協定下,由於是傳輸文字內容,所以其中一種方式就是將二進制位元資料轉成 Base64 格式來進行編碼與反編碼的動作,如電子郵件圖片

Java 8 在 java.util 套件新增了 Base64 類別,即可使用標準 API 進行操作:

1
2
3
4
5
6
// 編碼
String encode = Base64.getEncoder().encodeToString("Hello World!".getBytes("UTF-8"));
System.out.println(encode);
// 解碼
byte[] decode = Base64.getDecoder().decode(encode);
System.out.println(new String(decode, "UTF-8"));

不鼓勵存取大量資料時使用,因為效能差

3 bytes to 4 characters,資料變長,傳輸時間長

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestBase64 {

public static void main(String[] args) throws IOException {
File file = new File("FC_Bayern.png");
FileInputStream fis = new FileInputStream(file);
byte[] pic = new byte[fis.available()];
fis.read(pic);
fis.close();

// encode
String base64str = Base64.getEncoder().encodeToString(pic);
System.out.println(base64str);

// decode
byte[] fromBase64str = Base64.getDecoder().decode(base64str);
FileOutputStream fos = new FileOutputStream("fromBase64str.png");
fos.write(fromBase64str);
fos.flush();
fos.close();
}
}

Redis 交易機制

  • 交易裡的所有指令都是循序執行,而交易執行期間,Redis 就不會再接受其它用戶端的請求提供任何服務,以確保原子性(A)執行
  • 與關聯式資料庫不同,若是其中有指令執行失敗,Redis 會繼續執行後續的指令 (沒有 rollback 機制)
  • 交易開啟之前,若通訊發生故障,則所有交易指令都不會執行;但若是已經執行 EXEC 指令之後發生通訊故障,則指令都會被 Redis 執行

Redis 資料存活機制 TTL(Time To Live)

  • 在開發功能時,可能會遇到一些有時效性的資料處理,例如限時優惠、快取機制,或是驗證信件點擊處理等,過了一定時間就要做資料刪除的動作,而 Redis 就提供了這樣的功能,可以對 key 設定存活時間,只要指定的時間到期,由 Redis 自動刪除

指令 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jedis.set("test", "Hello");
//設定過期時間(單位為秒)
jedis.expire("test", 10);

//取得剩餘秒數
jedis.ttl("test");

// 取得剩餘毫秒數
jedis.pttl("test");

// 取消expire
jedis.persist("test");

// second指的是系統時間(1970-01-01 00:00:00)起始秒數
// 精準控制用
jedis.expireat("test", 10);
jedis.pexpireat("test", 10);//同上,只是單位為毫秒

Redis 持久化機制

  • Redis 提供了兩種持久化機制:
    RDB(預設)與 AOF。讓我們可以對在記憶體裡面的資料透過兩種不同機制(或是混搭),可以進行硬碟保存的永續操作

    1. DB 是透過快照機制完成,當符合設定檔裡的條件,即把所有在記憶體裡的資料進行快照並儲存在硬碟裡
    2. AOF 是滿足條件即把資料透過 append 的方式,附加在硬碟檔案裡面

RDB

官方的設定檔redis.conf裡就預置了三個RDB啟動快照的條件:
  1. save 900 1 (單位:秒/資料量)
  2. save 300 10
  3. save 60 10000

RDB 優點 :

  • 使用 RDB 對整個 Redis 資料庫來說就是一個檔案,因此在進行資料恢復時,可以做到較輕鬆的處理
  • RDB 採用子程序處理快照動作,這對 Redis 服務來說執行效率較佳

RDB 缺點 :

  • 若是在 RDB 持久化未進行的期間發生當機等中斷服務狀態時,資料都將會遺失
  • 若是一次要快照的資料較大時,會影響到 Redis 程序的運作

AOF

AOF設定條件 (2)為預設設定: 1. appendfsync always 2. appendfsync everysec 3. appendfsync no

AOF 優點:

  • 帶來更高的資料持久性與安全性,因為是採取同步機制進行儲存
  • 因為採用 append 機制,所以寫入中發生當機現象,也不會破壞原有的內容,也有 redis-check-aof 工具可以協助解決資料一致性問題
  • AOF 包含一個完整記錄檔記錄了所有的修改操作,也可以透過 AOF 完成資料重建的動作

AOF 缺點:

  • 對相同的資料量來說,AOF 的檔案會比 RDB 檔案來得大
  • 因為採用同步方式,所以 AOF 在執行的效能上慢於 RDB 機制