深入學習Redis(二)
Redis 客戶端使用
Java 客戶端:Jedis
Jedis 是 Redis 官方首選的 Java 客戶端開發包。整合了 redis 的一些命令操作,封裝了 redis 的 java 客戶端。提供了連線池管理。
Jedis Maven 依賴包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
簡單使用
/**
* @author 又壞又迷人
* 公眾號: Java菜鳥程式設計師
* @date 2020/12/29
* @Description: Redis簡單實用
*/
public class RedisTest {
public static void main(String[] args) {
// 1.生成一個Jedis物件,這個物件負責和指定Redis節點進行通訊
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2.執行string操作
jedis.set("hello", "world");
String hello = jedis.get("hello");
System.out.println(hello); // world
jedis.set("count", "1");
// 自增
jedis.incr("count");
System.out.println(jedis.get("count")); // 2
//3.執行hash操作
jedis.hset("myHash", "f1", "v1");
jedis.hset("myHash", "f2", "v2");
System.out.println(jedis.hgetAll("myHash").toString()); // {f2=v2, f1=v1}
//4. list
jedis.rpush("myList", "1", "2", "3");
System.out.println(jedis.lrange("myList", 0, -1)); // [1, 2, 3]
//5. set
jedis.sadd("mySet", "a", "b", "c");
System.out.println(jedis.smembers("mySet")); // [a, c, b]
//6. zset
jedis.zadd("myzset", 10, "Jack");
jedis.zadd("myzset", 20, "Rose");
jedis.zadd("myzset", 30, "Michelle");
System.out.println(jedis.zrange("myzset", 0, -1)); //[Jack, Rose, Michelle]
}
}
Jedis 連線池使用
Jedis 直連
Jedis 連線池
方案 | 優點 | 缺點 |
---|---|---|
直連 | 簡單方便,適用於少量長期連線的場景。 | 存在每次新建/關閉 TCP 開銷,資源無法控制,存在洩露的可能。Jedis 物件執行緒不安全。 |
連線池 | Jedis 預先生成,減低開銷使用。連線池的形式保護和控制資源的使用 | 相對於直連,使用相對麻煩,尤其在資源的管理上需要很多引數來保證。一旦規劃不合理就會出現問題。 |
/**
* @author 又壞又迷人
* 公眾號: Java菜鳥程式設計師
* @date 2020/12/29
* @Description: Redis連線池使用
*/
public class RedisPoolTest {
// 初始化Jedis連線池,通常來講JedisPool是單例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
public static void main(String[] args) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set("hello", "world");
String hello = jedis.get("hello");
System.out.println(hello); // world
jedis.set("count", "1");
// 自增
jedis.incr("count");
System.out.println(jedis.get("count")); // 2
} catch (Exception e) {
e.printStackTrace();
} finally {
if(jedis != null){
//歸還資源
jedis.close();
}
}
}
}
Redis 其他功能
慢查詢
生命週期
生命週期兩點說明:
- 慢查詢只發生在第 3 階段。
- 客戶端超時不一定是慢查詢,但慢查詢是客戶端超時的一個可能因素。
兩個配置
slowlog-max-len
- 此參數列示慢查詢最大儲存個數,慢查詢日誌都是儲存在佇列中。
- 先進先出。
- 固定長度。
- 儲存在記憶體中(伺服器斷電會導致慢查詢資料丟失)。
slowlog-log-slower-than
- 此參數列示慢查詢閾值(單位:微妙),預設配置 10000 微妙。
slowlog-log-slower-than=0
, 表示記錄所有命令。slowlog-log-slower-than<0
,表示不記錄任何命令。
動態配置:
127.0.0.1:6379> config get slowlog-max-len #檢視預設配置
1) "slowlog-max-len"
2) "128"
127.0.0.1:6379> config set slowlog-max-len 1000 #動態修改配置
OK
127.0.0.1:6379> config get slowlog-max-len
1) "slowlog-max-len"
2) "1000"
127.0.0.1:6379> config get slowlog-log-slower-than #檢視預設配置
1) "slowlog-log-slower-than"
2) "10000"
127.0.0.1:6379> config set slowlog-log-slower-than 1200 #動態修改配置
OK
127.0.0.1:6379> config get slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "1200"
三個命令
slowlog get [n]
:獲取慢查詢佇列slowlog len
:獲取慢查詢佇列長度slowlog reset
: 清空慢查詢佇列
127.0.0.1:6379> slowlog get 10
(empty list or set)
127.0.0.1:6379> slowlog len
(integer) 0
127.0.0.1:6379> slowlog reset
OK
運維經驗
slowlog-max-len
不要設定的過大,預設 10ms,通常設定 1ms。slowlog-log-slower-than
不要設定過小,通常設定 1000 左右。- 理解命令生命週期。
- 定期持久化慢查詢。
Pipeline
什麼是流水線
1 次網路命令通訊模型
批量網路命令通訊模型
什麼是流水線
流水線的作用
命令 | N 個命令操作 | 1 次 pipeline(N 個命令) |
---|---|---|
時間 | N 次網路+N 次命令 | 1 次網路+N 次命令 |
資料量 | 1 條命令 | N 條命令 |
注意
- Redis 命令執行時間是微妙級別的。
- pipeline 每次批量命令條數需要控制(注意網路傳輸)。
Pipeline-Jedis 客戶端實現
沒有使用 Pipeline用時:29707
/**
* @author 又壞又迷人
* 公眾號: Java菜鳥程式設計師
* @date 2020/12/29
* @Description:
*/
public class PipelineRedisTest {
// 初始化Jedis連線池,通常來講JedisPool是單例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "47.110.41.15", 6379);
public static void main(String[] args) {
long start = System.currentTimeMillis();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
for (int i = 0; i < 1000; i++) {
jedis.hset("hashkey", "field_" + i, "value_" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//歸還資源
jedis.close();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start); //29707
}
}
使用 Pipeline用時:3161
/**
* @author 又壞又迷人
* 公眾號: Java菜鳥程式設計師
* @date 2020/12/29
* @Description:
*/
public class PipelineRedisTest {
// 初始化Jedis連線池,通常來講JedisPool是單例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "47.110.41.15", 6379);
public static void main(String[] args) {
long start = System.currentTimeMillis();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
for (int i = 0; i < 100; i++) {
Pipeline pipeline = jedis.pipelined();
for (int j = i * 100; j < (i + 1) * 100; j++) {
pipeline.hset("pipelinekey", "field_" + j, "value_" + j);
}
pipeline.syncAndReturnAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//歸還資源
jedis.close();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start); //3161
}
}
與原生 M 操作對比
mset、mget、hmset、hmget 等都是原子操作。
pipeline 命令是非原子操作,但是命令返回順序能夠保證。
使用建議
- 注意每次 pipeline 攜帶的資料量
- pipeline 每次只能作用在一個 Redis 節點上
- M 命令操作與 pipeline 的區別
- mset、mget、hmget、hmset 等命令是:n 次網路時間+n 次命令時間。
- pipeline 操作命令是:1 次網路時間+n 次命令時間。
釋出訂閱
- 釋出者(publisher)
- 訂閱者(subscriber)
- 頻道(channel)
模型
多個訂閱者訂閱一個頻道
釋出者 publisher 只要釋出了訊息,所有訂閱了這個頻道 channel 的訂閱者都能收到訊息。
一個訂閱者可以訂閱多個頻道
一個訂閱者可以訂閱多個頻道,當釋出者釋出不同訊息到多個頻道,訂閱者可以接受多個頻道訊息。
釋出訂閱與訊息佇列
Redis 還可以用作訊息佇列,所有訊息訂閱者是去搶佇列裡面的訊息。
相關 API
subscribe
首先訂閱頻道。
127.0.0.1:6379> subscribe baidu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "baidu"
3) (integer) 1
1) "message"
2) "baidu"
3) "hello"
1) "message"
2) "baidu"
3) "world"
1) "message"
2) "baidu"
3) "java"
1) "message"
2) "baidu"
3) "python"
1) "message"
2) "baidu"
3) "go"
publish
傳送訊息。
127.0.0.1:6379> publish baidu hello
(integer) 1
127.0.0.1:6379> publish baidu world
(integer) 1
127.0.0.1:6379> publish baidu java
(integer) 1
127.0.0.1:6379> publish baidu python
(integer) 1
127.0.0.1:6379> publish baidu go
(integer) 1
使用 unsubscribe 取消訂閱頻道
127.0.0.1:6379> unsubscribe baidu
1) "unsubscribe"
2) "baidu"
3) (integer) 0
其它 API
psubscribe [pattern...]
:訂閱指定規則的頻道。punsubscribe [pattern...]
:退訂指定的模式。pubsub channels
:列出至少有一個訂閱者的頻道。pubsub numsub [channel...]
:列出給定頻道的訂閱者數量。
Bitmap
點陣圖
點陣圖並不是一種資料結構,其實就是一種普通的字串,也可以說是 byte 陣列。
- b 的 ASCII=98 對應的二進位制為:01100010
- i 的 ASCII=105 對應的二進位制為:01101001
- g 的 ASCII=103 對應的二進位制為:01100111
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 1
(integer) 1
setbit命令
對 key
所儲存的字串值,設定或清除指定偏移量上的位(bit)。
位的設定或清除取決於 value
引數,可以是 0
也可以是 1
。
當 key
不存在時,自動生成一個新的字串值。
字串會進行伸展(grown)以確保它可以將 value
儲存在指定的偏移量上。當字串值進行伸展時,空白位置以 0
填充。
offset
引數必須大於或等於 0
,小於 2^32 (bit 對映被限制在 512 MB 之內)。
返回指定偏移量原來儲存的位。
getbit命令
對 key
所儲存的字串值,獲取指定偏移量上的位(bit)。
當 offset
比字串值的長度大,或者 key
不存在時,返回 0
。
bitcount命令
計算給定字串中,被設定為 1
的位元位的數量。
一般情況下,給定的整個字串都會被進行計數,通過指定額外的 start
或 end
引數,可以讓計數只在特定的位上進行。
不存在的 key
被當成是空字串來處理,因此對一個不存在的 key
進行 BITCOUNT
操作,結果為 0
。
bitop命令
對一個或多個儲存二進位制位的字串 key
進行位元操作,並將結果儲存到 destkey
上。
operation
可以是 AND
、 OR
、 NOT
、 XOR
這四種操作中的任意一種:
BITOP AND destkey key [key ...]
,對一個或多個key
求邏輯並,並將結果儲存到destkey
。BITOP OR destkey key [key ...]
,對一個或多個key
求邏輯或,並將結果儲存到destkey
。BITOP XOR destkey key [key ...]
,對一個或多個key
求邏輯異或,並將結果儲存到destkey
。BITOP NOT destkey key
,對給定key
求邏輯非,並將結果儲存到destkey
。
除了 NOT
操作之外,其他操作都可以接受一個或多個 key
作為輸入。
返回儲存到 destkey
的字串的長度,和輸入 key
中最長的字串長度相等。
bitpos命令
返回點陣圖中第一個值為 bit
的二進位制位的位置。
在預設情況下, 命令將檢測整個點陣圖, 但使用者也可以通過可選的 start
引數和 end
引數指定要檢測的範圍。
獨立使用者統計
- 使用 set 和 bitmap
- 1 億使用者,5 千萬獨立
資料型別 | 每個 UserId 佔用空間 | 需要儲存的使用者量 | 全部記憶體量 |
---|---|---|---|
set | 32 位 | 50,000,000 | 32 位 * 50,000,000 = 200MB |
bitmap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5MB |
但是如果只有 10 萬獨立使用者的話,結果就不一樣了。
資料型別 | 每個 UserId 佔用空間 | 需要儲存的使用者量 | 全部記憶體量 |
---|---|---|---|
set | 32 位 | 100,000 | 32 位 * 100,000 = 4MB |
bitmap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5MB |
使用經驗
- type=string 型別,最大 512MB。
- 注意 setbit 時的偏移量,可能有較大耗時。
- 點陣圖不是絕對好,合理的場景使用合理的技術。
HyperLogLog
Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、並且是很小的。
在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
PFADD命令
將任意數量的元素新增到指定的 HyperLogLog 裡面。
作為這個命令的副作用, HyperLogLog 內部可能會被更新, 以便反映一個不同的唯一元素估計數量(也即是集合的基數)。
如果 HyperLogLog 估計的近似基數(approximated cardinality)在命令執行之後出現了變化, 那麼命令返回 1
, 否則返回 0
。 如果命令執行時給定的鍵不存在, 那麼程式將先建立一個空的 HyperLogLog 結構, 然後再執行命令。
- 如果給定鍵已經是一個 HyperLogLog , 那麼這種呼叫不會產生任何效果。
- 但如果給定的鍵不存在, 那麼命令會建立一個空的 HyperLogLog , 並向客戶端返回
1
。
如果 HyperLogLog 的內部儲存被修改了, 那麼返回 1 , 否則返回 0 。
API:PFADD key element [element …]
127.0.0.1:6379> pfadd data 'java' 'python' 'go'
(integer) 1
127.0.0.1:6379> pfcount data
(integer) 3
PFCOUNT命令
當 PFCOUNT key [key …] 命令作用於單個鍵時, 返回儲存在給定鍵的 HyperLogLog 的近似基數, 如果鍵不存在, 那麼返回 0
。
當 PFCOUNT key [key …] 命令作用於多個鍵時, 返回所有給定 HyperLogLog 的並集的近似基數, 這個近似基數是通過將所有給定 HyperLogLog 合併至一個臨時 HyperLogLog 來計算得出的。
命令返回的可見集合(observed set)基數並不是精確值, 而是一個帶有 0.81% 標準錯誤(standard error)的近似值。
返回給定 HyperLogLog 包含的唯一元素的近似數量。
API:PFCOUNT key [key …]
127.0.0.1:6379> pfadd data 'java' 'python' 'go'
(integer) 1
127.0.0.1:6379> pfcount data
(integer) 3
PFCOUNT命令
將多個 HyperLogLog 合併(merge)為一個 HyperLogLog , 合併後的 HyperLogLog 的基數接近於所有輸入 HyperLogLog 的可見集合(observed set)的並集。
合併得出的 HyperLogLog 會被儲存在 destkey
鍵裡面, 如果該鍵並不存在, 那麼命令在執行之前, 會先為該鍵建立一個空的 HyperLogLog 。
字串回覆:返回 OK
。
API:PFMERGE destkey sourcekey [sourcekey …]
127.0.0.1:6379> PFADD nosql "Redis" "MongoDB" "Memcached"
(integer) 1
127.0.0.1:6379> PFADD RDBMS "MySQL" "MSSQL" "PostgreSQL"
(integer) 1
127.0.0.1:6379> PFMERGE databases nosql RDBMS
OK
127.0.0.1:6379> pfcount databases
(integer) 6
使用經驗
- 是否能容忍錯誤:HyperLogLog 錯誤率為:0.81%
- 是否需要單條資料:如果需要單條資料,就不適合使用 HyperLogLog。
GEO
GEO 主要用於儲存地理位置資訊,並對儲存的資訊進行操作。
GEOADD命令
將給定的空間元素(緯度、經度、名字)新增到指定的鍵裡面。 這些資料會以有序集合的形式被儲存在鍵裡面。
- 有效的經度介於 -180 度至 180 度之間。
- 有效的緯度介於 -85.05112878 度至 85.05112878 度之間。
當使用者嘗試輸入一個超出範圍的經度或者緯度時, GEOADD
命令將返回一個錯誤。
返回新新增到鍵裡面的空間元素數量, 不包括那些已經存在但是被更新的元素。
GEOPOS命令
從鍵裡面返回所有給定位置元素的位置(經度和緯度)。
因為 GEOPOS
命令接受可變數量的位置元素作為輸入, 所以即使使用者只給定了一個位置元素, 命令也會返回陣列回覆。
GEOPOS
命令返回一個陣列, 陣列中的每個項都由兩個元素組成: 第一個元素為給定位置元素的經度, 而第二個元素則為給定位置元素的緯度。 當給定的位置元素不存在時, 對應的陣列項為空值。
GEODIST命令
返回兩個給定位置之間的距離。
如果兩個位置之間的其中一個不存在, 那麼命令返回空值。
指定單位的引數 unit
必須是以下單位的其中一個:
m
表示單位為米。km
表示單位為千米。mi
表示單位為英里。ft
表示單位為英尺。
如果使用者沒有顯式地指定單位引數, 那麼 GEODIST
預設使用米作為單位。
GEODIST
命令在計算距離時會假設地球為完美的球形, 在極限情況下, 這一假設最大會造成 0.5% 的誤差。
計算出的距離會以雙精度浮點數的形式被返回。 如果給定的位置元素不存在, 那麼命令返回空值。
GEORADIUS命令
以給定的經緯度為中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素。
範圍可以使用以下其中一個單位:
m
表示單位為米。km
表示單位為千米。mi
表示單位為英里。ft
表示單位為英尺。
在給定以下可選項時, 命令會返回額外的資訊:
WITHDIST
: 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。 距離的單位和使用者給定的範圍單位保持一致。WITHCOORD
: 將位置元素的經度和維度也一併返回。WITHHASH
: 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者除錯, 實際中的作用並不大。
命令預設返回未排序的位置元素。 通過以下兩個引數, 使用者可以指定被返回位置元素的排序方式:
ASC
: 根據中心的位置, 按照從近到遠的方式返回位置元素。DESC
: 根據中心的位置, 按照從遠到近的方式返回位置元素。
在預設情況下, GEORADIUS
命令會返回所有匹配的位置元素。 雖然使用者可以使用 COUNT <count>
選項去獲取前 N 個匹配元素, 但是因為命令在內部可能會需要對所有被匹配的元素進行處理, 所以在對一個非常大的區域進行搜尋時, 即使只使用 COUNT
選項去獲取少量元素, 命令的執行速度也可能會非常慢。 但是從另一方面來說, 使用 COUNT
選項去減少需要返回的元素數量, 對於減少頻寬來說仍然是非常有用的。
返回值
GEORADIUS
命令返回一個陣列, 具體來說:
- 在沒有給定任何
WITH
選項的情況下, 命令只會返回一個像["New York","Milan","Paris"]
這樣的線性(linear)列表。 - 在指定了
WITHCOORD
、WITHDIST
、WITHHASH
等選項的情況下, 命令返回一個二層巢狀陣列, 內層的每個子陣列就表示一個元素。
在返回巢狀陣列時, 子陣列的第一個元素總是位置元素的名字。 至於額外的資訊, 則會作為子陣列的後續元素, 按照以下順序被返回:
- 以浮點數格式返回的中心與位置元素之間的距離, 單位與使用者指定範圍時的單位一致。
- geohash 整數。
- 由兩個元素組成的座標,分別為經度和緯度。
歡迎關注我的公眾號:Java菜鳥程式設計師
希望可以一起探討交流,一起學習!
相關文章
- [Redis 系列]redis 學習二Redis
- 【Redis 系列】redis 學習二Redis
- 深入學習 Redis(4):哨兵Redis
- 深入學習Redis(4):哨兵Redis
- 深入學習Redis(5):叢集Redis
- 深入學習 Redis(2):持久化Redis持久化
- 深入學習 Redis(1):Redis 記憶體模型Redis記憶體模型
- C語言深入學習二C語言
- 深入學習redis 的執行緒模型Redis執行緒模型
- 深入學習 Redis(3):主從複製Redis
- 深入學習Redis(3):主從複製Redis
- Redis學習筆記(二)——Redis資料型別Redis筆記資料型別
- 深入學習Spring框架(二)- 註解配置Spring框架
- 深入淺出學習決策樹(二)
- redis學習手記(二)持久化方式Redis持久化
- tomcat深入學習(二)(1) ---- tomcat初始化Tomcat
- Redis學習筆記(二)redis 底層資料結構Redis筆記資料結構
- 【Redis】Redis 學習Redis
- 深入學習二叉樹 (一) 二叉樹基礎二叉樹
- ES6深入學習(二)關於函式函式
- redis學習Redis
- Redis 學習Redis
- 深入剖析Redis系列(二) - Redis哨兵模式與高可用叢集Redis模式
- 深入學習JVM-記憶體架構圖(二)JVM記憶體架構
- golang學習筆記(二)—— 深入golang中的協程Golang筆記
- 深入學習 vueVue
- 深入學習SpringMVCSpringMVC
- 深入學習synchronizedsynchronized
- 深入學習作用域和閉包—全面(JS系列之二)JS
- Redis 學習-上Redis
- redis學習(三)Redis
- Redis 學習心得Redis
- Redis學習(一)——初識RedisRedis
- 深入學習SpringMVC以及學習總結SpringMVC
- DNS深入學習-1DNS
- Kubernetes深入學習之二:編譯和部署映象(api-server)編譯APIServer
- C#中使用Redis學習二 在.NET4.5中使用redis hash操作C#Redis
- 【Redis學習筆記】2018-07-10 Redis指令學習3Redis筆記