redis必會基礎命令、資料結構、lua指令碼和分散式鎖等
導讀
本文介紹日常工作中redis的使用,涉及到redis的資料結構、對應的命令、持久化配置和Lua指令碼,以及基於redis的分散式鎖實現方案,使用redis時這些都是必會的基礎知識,建議儲存以下命令
通用命令
# 檢視當前庫中key的數量
dbsize
# 清空當前庫
flushdb
# 清空所有庫
flushall
# 檢視當前庫下所有key
keys *
# 當前庫下是否有指定key
exists key1
# 檢視key的值型別
type key1
# 刪除key
del key1
# 設定指定key的過期時間,單位秒
expire key1 10
# 檢視key的剩餘過期時間,-1表示永不過期,-2表示已過期
ttl key1
# 監視key
watch key1
# 取消監視key
unwatch key1
資料結構和命令
string
是key/value的資料結構,一個key對應一個string型別的value,單個value最大512M
這個是最常用的資料結構了
命令
# 設定key/value
set key1 value1
# 設定key/value的同時設定過期時間
setex key1 10 value1
# 獲取key的值
get key1
# 設定新值,同時返回舊值
getset key1 value1
# 一次設定多個key/value,後面的....表示還可以寫key3 value3等
mset key1 value1 key2 value2 ...
# 一次獲取多個key的值
mget key1 key2
# 追加內容到指定key的值後面
append key1 xxx
# 獲取值的長度
strlen key1
# 只有在key不存在時才成功
setnx key1 value1
# 只有在所有key不存在時才成功
msetnx key1 value1 key2 value2 ...
# 給指定key的值加1
incr key1
# 減1
decr key1
# 給指定key的值加指定數值,本例是加2
incrby key1 2
# 給指定key的值減指定數值,本例是減2
decrby key1 2
# 獲取指定key的值中指定範圍的字元,如值為abcdefg,取1至2返回bc,即包含1和2兩個位置的字元
getrange key1 1 2
# 設定指定位置的值,指定開始位置,然後直接覆蓋,如下例中值為abcdefg,從第1個位置開始覆蓋為cb,則結果為acbdefg
setrange key1 1 cb
list
雙向連結串列,無序可重複的集合,一般用來做佇列
命令
# 從表頭新增元素,value2是新的表頭
lpush key1 value1 value2 ...
# 從表尾新增元素,value2是新的表尾
rpush key1 value1 value2 ...
# 從表頭彈出元素
lpop key1
# 從表尾彈出元素
rpop key1
# 從key1表尾彈出一個元素,再加到key2表頭
rpoplpush key1 key2
# 從表中檢視指定索引的範圍的元素
lrange key1 0 2
# 檢視整個連結串列
lrange key1 1 0 -1
# 獲取連結串列中從左向右指定索引的元素
lindex key1 1
# 獲取連結串列中最後一個元素
lindex key1 -1
# 獲取連結串列長度
llen key1
# 向連結串列中的value1前面插入value2
linsert key1 before value1 value2
# 向連結串列中value1後面插入value2
linsert key1 after value1 value2
# 從連結串列中刪除一個值為value1的元素,從左向右
lrem key1 1 value1
# 從連結串列中刪除一個值為value1的元素,從右向左
lrem key1 -1 value1
# 刪除連結串列中所有值為value1的元素
lrem key1 0 value1
set
無序不可重複的集合,常用來排除重複資料和隨機抽獎功能
命令
# 向集合中新增元素,重複元素會自動跳過
sadd key1 value1 value2 ...
# 取出集合所有元素
smembers key1
# 判斷集合中是否存在某個元素
sismember key1 value1
# 獲取集合中的元素個數
scard key1
# 從集合中刪除指定元素
srem key1 value1 value2 ...
# 隨機從集合中彈出一個元素並刪除該元素
spop key1
# 隨機從集合中取出元素,但不會刪除元素,後面的1表示取出元素的個數
srandmember key1 1
# 求兩個集合交集
sinter key1 key2
# 求兩個集合並集
sunion key1 key2
# 求兩個集合差集
sdiff key1 key2
zset
有序不可重複的集合,常用來做排行榜
命令
# 新增元素,相同value不同score會覆蓋score
zadd key1 score1 value1 score2 value2
# 獲取元素數量
zcard key1
# 取出全部元素,從小到大
zrange key1 0 -1
# 取出部分元素,從小到大
zrange key1 0 4
# 取出全部元素,從大到小
zrevrange key1 0 -1
# 取出部分元素,從大到小
zrevrange key1 0 4
# 取出score在指定範圍內的元素,從小到大,其中min和max是score的範圍
zrangebyscore key1 min max withscores
# 取出score在指定範圍內的元素,從大到小
zrevrangebyscore key1 max min withscores
# 為指定value的元素的score遞增,其中1是每次遞增多少,可以為負數
zincrby key1 1 value1
# 刪除指定元素
zrem key1 value1
# 統計集合中score在範圍內的元素個數
zcount key1 min max
# 返回指定值在集合中的排名,從小到大,排名從0開始
zrank key1 value1
# 返回指定值在集合中的排名,從大到小
zrevrank key1 value1
# 新增一個鍵值對
hset key1 field1 value1
# 獲取鍵值
hget key1 field1
# 批次設定鍵值對
hmset key1 field1 value1 field2 value2 ...
# 檢查鍵是否存在
hexists key1 field1
# 獲取所有鍵
hkeys key1
# 獲取所有值
hvals key1
# 鍵值遞增,後面的1表示每次遞增多少,可以為負數,當是負數時表示遞減
hincrby key1 field1 1
# 鍵不存在時成功
hsetnx key1 field1 value1
# 獲取所有鍵值對,奇數為鍵,偶數為值
hgetall key1
bitmap
bitmap以bit為單位設定各個位的值(要麼是0,要麼是1),根據實際應用場景可以設計出節省空間的演算法,如布隆過濾器,本文以記錄使用者簽到為例,假設使用者ID為1,每年一個key,並且key=使用者ID_年份,如1_2021
ID=1的使用者在2021-01-01這一天簽到,這一天是2021年第1天(也就是第0天),可以執行以下命令,儲存簽到記錄
# 設定1_2021這個key的第0個bit值為1,以此表示第0天簽到成功
setbit 1_2021 0 1
這種情況要麼按月來設定key值,要麼單獨查詢2021-02-01這一天是否簽到,如果簽到則總次數就減1
透過上面的例子,可以看到以bit為單位儲存非常節省空間,用8個bit就可以表示8天內的簽到情況。也可以用bitmap來儲存所有使用者一天內的簽到情況,這種就以使用者ID作為bit的偏移量,如果使用者ID很大,超過了bitmap的最大範圍,可以透過使用者ID分片到不同的bitmap上
地理位置
在同一個key內新增多個位置(經緯度),計算位置各個位置之間的距離,也可指定圓心按半徑查詢符合條件的位置,可實現附近的xxx功能
命令
# 向key1新增一個叫company的位置,經緯度為116.404844 39.915378
geoadd key1 116.404844 39.915378 company
# 向key1新增一個叫home的位置,經緯度為116.370924 39.930871
geoadd key1 116.370924 39.930871 home
# 查詢指定位置的經緯度
geopos key1 company
# 查詢多個位置的經緯度
geopos key1 company home
# 計算兩個位置的距離,單位是m
geodist key1 company home
# 計算兩個位置的距離,指定單位為km
geodist key1 company home km
# 以指定經緯度為圓心,查詢指定半徑內的所有位置,其中116.370924 39.930871是圓心點的經緯度,2000 m是半徑大小,單位m,withdist表示輸出符合條件的位置與圓心的距離,withcoord表示輸出符合條件的位置的經緯度,asc表示按距離從小到大排序
georadius key1 116.370924 39.930871 2000 m withdist withcoord asc
# 以指定位置為圓心,查詢指定半徑內的所有位置,返回結果中包含圓心自身,其他可選引數與上一條georadius相同
georadiusbymember key1 home 4000 m
持久化
redis大部分時間用來做快取,通常資料丟失也可以恢復,但是有時候也會用來儲存熱門的資料,或者nginx直接連線redis做一些重要資料的儲存(丟失後很難恢復),所以redis中的資料需要持久化
redis提供了RDB和AOF兩種持久化方式,RDB是對當前資料的全量備份(理解成快照),AOF採用追加的方式記錄所有寫入的命令,所以一般AOF檔案更大,可能導致硬碟被佔滿,這一點需要注意,需要及時的對AOF檔案進行瘦身
RDB
執行save
和bgsave
命令會生成一份當前記憶體資料的快照到.rdb
檔案內,其中bgsave
命令是另起一個執行緒去執行,因此不會阻塞主執行緒
redis預設開啟了RDB,redis會自動進行RDB儲存,RDB常用配置引數:
save 300 1000 # 每隔300秒,如果有1000個key發生了變化,則備份一次
save 30 10000 # 每隔30秒,如果有10000個key發生了變化,則備份一次
上面的引數不能亂改,要根據redis的寫入資料情況來設定,不能太頻繁的生成RDB快照,這會影響redis的效能
AOF
一般做持久化要同時開啟RDB和AOF,下面介紹工作中如何設定redis的AOF,編輯redis.conf配置檔案,修改以下配置項
# 開啟AOF
appendonly yes
# AOF檔名
appendfilename "xxx.aof"
# 將命令刷入磁碟的間隔,everysec是每秒一次
appendfsync everysec
# 在執行bgrewrite的時候不向磁碟重新整理資料
no-appendfsync-on-rewrite no
關於appendfsync的意思:在計算機組成原理中,我們知道相對於記憶體而言對磁碟的讀寫是很慢的,所以CPU將資料寫入記憶體緩衝區,定時或存滿後再寫入磁碟,redis的AOF中appendfsync
這個配置就是設定多久寫入磁碟一次,設為一秒是比較保險的,如果發生故障只會丟失最近1秒內的資料
RDB和AOF對比
RDB定期對記憶體中的資料進行快照,會影響redis的效能,所以不能太頻繁
RDB在快照之間如何發生錯誤會丟失此段時間內的資料
RDB在重啟redis時恢復速度更快,不像AOF那樣需要一條一條命令執行
AOF可設定每秒追加一次寫入命令到aof檔案中,所以發生故障時丟失資料最少
AOF檔案中儲存的是redis的寫入命令,所以可以開啟檔案進行修改,刪除不需要的命令
AOF的缺點是要記錄redis做的每一步寫入命令,所以檔案很大,需要及時進行瘦身
lua指令碼
介紹
redis中預設就支援lua指令碼,我們通常會使用lua指令碼來代替redis事務,可解決超賣和少賣的情況
以下是redis的lua指令碼特性:
原子操作,lua指令碼會作為一個整體執行,不會被其他連線的命令中斷,因此可替代事務
lua指令碼載入後可重複使用
減少網路請求的開銷,一次性發出一個lua指令碼到redis,redis執行完後返回結果,不用多次請求
用法
直接執行
eval 指令碼 引數數量 引數名1 引數名2 值1 值2
示例:
eval "return KEYS[1]..ARGV[1]" 1 key1 val1
# 輸出
key1val1
lua指令碼中KEYS
和ARGV
兩個陣列名是固定的,且索引從1開始,上面例子中引數數量為1,引數名1為key1,值1為val1,而指令碼是拼接key1和val1,所以結果就是key1val1了
注意:如果指令碼有多個引數,則引數名寫在一起,引數值寫在一起,如key1 key2 val1 val2
,而不是key1 val1 key2 val2
載入指令碼
載入指令碼的目的是重複使用,透過script load命令實現,返回一個sha1的hash值,之後透過此值可呼叫已載入的指令碼
script load "return KEYS[1]..ARGV[1]"
# 假設返回3783a90bf1f43b15a1e06c4e7664da956ed959d9
呼叫指令碼
# 3783a90bf1f43b15a1e06c4e7664da956ed959d9 是script load返回的結果
# 1 是引數數量
# key1 是引數名1
# val1 是引數值1
evalsha 3783a90bf1f43b15a1e06c4e7664da956ed959d9 1 key1 val1
指定載入指令碼返回的sha1 hash值來呼叫指令碼,並指定引數數量和引數名以及引數值
判斷指令碼是否載入
script exists 3783a90bf1f43b15a1e06c4e7664da956ed959d9
Java中使用redis lua指令碼
本例使用spring提供的RedisTemplate,首先引入依賴
定義一個RedisClient,對常用命令進行封裝
@Component
public class RedisClient {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
public
DefaultRedisScript
redisScript.setResultType(clazz);
redisScript.setScriptText(script);
return (T) redisTemplate.execute(redisScript, keys, args);
}
}
測試使用,先在redis中set兩個key出來,分別是test_key1和test_key2,以下lua指令碼是對這兩個key進行遞增操作,分別遞增1和2
String script = "redis.call("INCRBY", KEYS[1], ARGV[1])nredis.call("INCRBY", KEYS[2], ARGV[2])";
ArrayList
Object execute = redisClient.execute(Object.class, script, keys, 1, 2);
System.out.println(execute);
分散式鎖
redis有個setnx命令,在key不存在時才能設定成功,因此也經常用來實現分散式鎖,但是隻透過setnx命令來做分散式鎖是不安全的,假設執行緒1執行setnx成功並設定10秒後過期,執行緒2再執行setnx命令肯定是失敗的,如果執行緒1執行過程中發生故障沒有及時清除鎖,則其他執行緒只能等待10秒後才能獲取鎖;如果執行緒1在10秒內還沒有執行完,由於鎖已經過期,導致其他執行緒執行setnx成功,這就存在兩個執行緒同時拿到鎖,這樣的使用方式肯定是不行的,今天介紹第三方庫redisson,redisson幫我們搞定了以上兩種情況需要解決的問題
先引入依賴
定義一個工具類,方便使用
@Component
public class RedissonUtils {
@Autowired
private RedissonClient redissonClient;
private static RedissonClient client;
@PostConstruct
private void init() {
client = redissonClient;
}
public static RLock lock(String key) {
RLock lock = client.getLock(key);
lock.lock();
return lock;
}
public static RLock lock(String key, long expire) {
RLock lock = client.getLock(key);
lock.lock(expire, TimeUnit.MILLISECONDS);
return lock;
}
public static RLock lock(String key, long expire, TimeUnit unit) {
RLock lock = client.getLock(key);
lock.lock(expire, unit);
return lock;
}
public static RLock tryLock(String key, long wait, long expire, TimeUnit unit) {
RLock lock = client.getLock(key);
try {
if (lock.tryLock(wait, expire, unit)) {
return lock;
} else {
return null;
}
} catch (InterruptedException e) {
return null;
}
}
public static void unlock(String key) {
RLock lock = client.getLock(key);
lock.unlock();
}
public static void unlock(RLock lock) {
if (lock != null) {
lock.unlock();
}
}
}
獲取鎖
RLock lock = RedissonUtils.tryLock(LockKey.XXX, 5L, 10L, TimeUnit.SECONDS);
Assert.isNull(lock, "獲取鎖失敗");
// 取到鎖
try {
// 拿到鎖後執行的操作
...
} finally {
RedissonUtils.unlock(lock);
}
需要注意的是釋放鎖一定要放到finally中,防止發生異常而未釋放鎖
最後說明一下分散式鎖也不是非要使用redisson,使用redis實現分散式鎖有個致命的問題,當拿到鎖後主redis還未同步到從redis時,主redis掛掉,系統切換到從redis,此時其他執行緒仍然可以拿到鎖,這樣系統中就有兩個執行緒拿到了鎖
作者:飛翔的程式碼
連結:
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4550/viewspace-2797508/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Redis學習筆記(三)使用Lua指令碼實現分散式鎖Redis筆記指令碼分散式
- Redis基礎資料結構Redis資料結構
- 【Redis的那些事 · 上篇】Redis的介紹、五種資料結構演示和分散式鎖Redis資料結構分散式
- 一種redis命令搞定基於redis的分散式鎖Redis分散式
- 基於資料庫、redis和zookeeper實現的分散式鎖資料庫Redis分散式
- 基於 Redis 分散式鎖Redis分散式
- Redis - Lua 指令碼Redis指令碼
- Redis基礎——剖析基礎資料結構及其用法Redis資料結構
- Redis基礎資料結構之字串Redis資料結構字串
- Redis基礎資料結構之MapRedis資料結構
- Redis基礎資料結構之SkipListRedis資料結構
- 淺析Redis基礎資料結構Redis資料結構
- 用Redis構建分散式鎖Redis分散式
- 基於 Redis 的分散式鎖Redis分散式
- 基於redis的分散式鎖Redis分散式
- 基於redis做分散式鎖Redis分散式
- Redis原始碼學習——基礎資料結構之SDSRedis原始碼資料結構
- Redis基礎資料結構之連結串列Redis資料結構
- Redis使用Lua指令碼Redis指令碼
- Redis 的基礎資料結構(三)物件Redis資料結構物件
- Java分散式鎖方案和區別 - Redis,Zookeeper,資料庫Java分散式Redis資料庫
- 十九、Redis分散式鎖、Zookeeper分散式鎖Redis分散式
- 基於redis實現分散式鎖Redis分散式
- OceanBase分散式儲存引擎公共模組——基礎資料結構分散式儲存引擎資料結構
- Redis基礎(一)資料結構與資料型別Redis資料結構資料型別
- 分散式鎖-Redis分散式Redis
- Redis分散式鎖Redis分散式
- Redis 分散式鎖Redis分散式
- Redis Redisson 分散式鎖的應用和原始碼Redis分散式原始碼
- 基於redis和zookeeper的分散式鎖實現方式Redis分散式
- 分散式必備理論基礎:CAP和BASE分散式
- Redis基礎知識(學習筆記2--分散式鎖)Redis筆記分散式
- [翻譯]基於redis的分散式鎖Redis分散式
- 基於redis的分散式鎖實現Redis分散式
- Redis篇:事務和lua指令碼的使用Redis指令碼
- Redis基礎知識(學習筆記21--Lua 指令碼語言)Redis筆記指令碼
- 通俗易懂的Redis資料結構基礎教程Redis資料結構
- Redis 分散式鎖(一)Redis分散式