1.Redis key值操作以及RedisTemplate對應的API
本文默認使用RedisTemplate,關於RedisTemplate和StringRedisTemplate的區別如下
RedisTemplate和StringRedisTemplate
二者主要區別是他們使用的序列化類不一樣,RedisTemplate使用的是JdkSerializationRedisSerializer,
StringRedisTemplate使用的是StringRedisSerializer,兩者的數據是不共通的。
1.RedisTemplate: RedisTemplate使用的是JDK的序列化策略,向Redis存入數據會將數據先序列化成字節數組然后在存入Redis數據庫,這個時候打開Redis查看的時候,
你會看到你的數據不是以可讀的形式展現的,而是以字節數組顯示,類似下面:\xAC\xED\x00\x05t\x05sr\x00。
所以使用RedisTemplate可以直接把一個java對象直接存儲在redis里面。 2.StringRedisTemplate: StringRedisTemplate默認采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
StringRedisTemplate是繼承RedisTemplate的,這種對redis的操方式更優雅,
因為RedisTemplate以字節數組的形式存儲不利於管理,也不通用。
如果存入的是簡單的字符串使用StringRedisTemplate;
如果存入的是復雜的對象用RedisTemplate;
1.1 Key操作
1.1.1 常用的key操作
keys *查看當前庫所有key (匹配:keys *1) public Set<String> keys(String pattern) { return redisTemplate.keys(pattern); }
exists key判斷某個key是否存在 public Boolean hasKey(String key) { return redisTemplate.hasKey(key); }
type key 查看你的key是什么類型 public DataType type(String key) { return redisTemplate.type(key); }
del key 刪除指定的key數據 public void delete(String key) { redisTemplate.delete(key); }
unlink key 根據value選擇非阻塞刪除,僅將keys從keyspace元數據中刪除,真正的刪除會在后續異步操作。 public void unlink(String key) { redisTemplate.unlink(key); }
expire key 10 10秒鍾:為給定的key設置過期時間 public Boolean expire(String key, long timeout, TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); }
ttl key 查看還有多少秒過期,-1表示永不過期,-2表示已過期 public Long getExpire(String key) { return redisTemplate.getExpire(key); }
flushdb 清空當前庫 public void flushdb() { Set<String> keys = redisTemplate.keys("*"); redisTemplate.delete(keys); }
dbsize 查看當前數據庫的key的數量 // 令pattern = "*" public Set<String> keys(String pattern) { return redisTemplate.keys(pattern); }
2.Redis 五大數據類型操作以及RedisTemplate對應的API
2.1 String類型
1.緩存結構體信息: 將結構體json序列化成字符串,然后將字符串保存在redis的value中,將結構體的業務唯一標示作為key;這種保存json的用法用的最多的場景就是緩存用戶信息,
將用戶bean信息轉成json再序列化為字符串作為value保存在redis中,將用戶id作為key。從代碼中獲取用戶緩存信息就是一個逆過程,
根據userid作為key獲取到結構體json,然后將json轉成java bean。 2.計數功能: 我們都知道redis是單線程模式,並且redis將很多常用的事務操作進行了封裝,這里我們最常用的就是數值自增或自減,redis的作者封裝了incr可以進行自增,
沒調用一次自增1,因為redis是單線程運行,所以就算client是多線程調用那么也是正確自增,因為incr命令中將read和write做了事務封裝。
同樣可以設置incr的step,每次根據step進行自增,當然如果要達到自減的效果,那么只要將step設置為負數就可以了。 計數功能使用的場景很多,我們之前經常用在實時計數統計場景,也用在過庫存場景、限流計數場景等等,而且redis的性能也是非常高的,
對於一般的並發量沒那么高的系統都是適用的。
2.1.1 String的數據結構
String的數據結構為簡單動態字符串(Simple Dynamic String,縮寫SDS)。是可以修改的字符串,內部結構實現上類似於Java的ArrayList,采用預分配冗余空間的方式來減少內存的頻繁分配.
如上圖中所示,內部為當前字符串實際分配的空間capacity一般要高於實際字符串長度len。當字符串長度小於1M時,擴容都是加倍現有的空間,如果超過1M,擴容時一次只會多擴1M的空間。需要注意的是字符串最大長度為512M。
2.1.2 String常用指令
set <key><value>添加鍵值對 public void set(String key, String value) { redisTemplate.opsForValue().set(key, value); }
set ... nx:當數據庫中key不存在時,可以將key-value添加數據庫 public boolean setIfAbsent(String key, String value) { return redisTemplate.opsForValue().setIfAbsent(key, value); }
set ... xx:當數據庫中key存在時,可以將key-value添加數據庫,與nx參數互斥 public boolean setIfPresent(String key, String value) { return redisTemplate.opsForValue().setIfPresent(key, value); }
set ... ex:設置key的超時秒數 set ... px:設置key的超時毫秒數 public void setEx(String key, String value, long timeout, TimeUnit unit) { redisTemplate.opsForValue().set(key, value, timeout, unit); }
get <key>查詢對應鍵值 public Object get(String key) { return redisTemplate.opsForValue().get(key); }
append <key><value>將給定的<value> 追加到原值的末尾 public Integer append(String key, String value) { return redisTemplate.opsForValue().append(key, value); }
strlen <key>獲得值的長度 public Long size(String key) { return redisTemplate.opsForValue().size(key); }
incr <key> 將 key 中儲存的數字值增1,只能對數字值操作,如果為空,新增值為1 decr <key> 將 key 中儲存的數字值減1,只能對數字值操作,如果為空,新增值為-1 incrby / decrby <key><步長>將 key 中儲存的數字值增減。自定義步長。 public Long incrBy(String key, long increment) { return redisTemplate.opsForValue().increment(key, increment); }
mset <key1><value1><key2><value2> ..... 同時設置一個或多個 key-value對 public void multiSet(Map<String, String> maps) { redisTemplate.opsForValue().multiSet(maps); }
mget <key1><key2><key3> ..... 同時獲取一個或多個 value public List<Object> multiSet(Collection<String> keys) { return redisTemplate.opsForValue().multiGet(keys); }
msetnx <key1><value1><key2><value2> ..... 同時設置一個或多個 key-value 對,當且僅當所有給定 key 都不存在。原子性,有一個失敗則都失敗 public boolean multiSetIfAbsent(Map<String, String> maps) { return redisTemplate.opsForValue().multiSetIfAbsent(maps); }
getrange <key><起始位置><結束位置>獲得值的范圍,類似java中的substring,前包,后包 public Object getRange(String key, long start, long end) { return redisTemplate.opsForValue().get(key, start, end); }
setrange <key><起始位置><value> 用 <value> 覆寫<key>所儲存的字符串值,從<起始位置>開始(索引從0開始)。 public void setRange(String key, String value, long offset) { redisTemplate.opsForValue().set(key, value, offset); }
getset <key><value> 以新換舊,設置了新值同時獲得舊值。 public Object getAndSet(String key, String value) { return redisTemplate.opsForValue().getAndSet(key, value); }
2.2 List類型
Redis 列表List類型是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。它的底層實際是個雙向鏈表,對兩端的操作性能很高,通過索引下標的操作中間的節點性能會較差。
應用場景
1.list列表結構常用來做異步隊列使用 將需要延后處理的任務結構體序列化成字符串塞進 Redis 的列表,另一個線程從這個列表中輪詢數據進行處理。 2.list可用於秒殺搶購場景 在商品秒殺場景最怕的就是商品超賣,為了解決超賣問題,我們經常會將庫存商品緩存到類似MQ的隊列中,多線程的購買請求都是從隊列中取,
取完了就賣完了,但是用MQ處理的化有點重,這里就可以使用redis的list數據類型來實現,在秒殺前將本場秒殺的商品放到list中,
因為list的pop操作是原子性的,所以即使有多個用戶同時請求,也是依次pop,list空了pop拋出異常就代表商品賣完了。
2.2.1 List的數據結構
List的數據結構為快速鏈表quickList。首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是ziplist,也即是壓縮列表。它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存。當數據量比較多的時候才會改成quicklist。因為普通的鏈表需要的附加指針空間太大,會比較浪費空間。比如這個列表里存的只是int類型的數據,結構上還需要兩個額外的指針prev和next。
Redis將鏈表和ziplist結合起來組成了quicklist。也就是將多個ziplist使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗余。
2.2.2 List常用的指令
lpush/rpush <key><value1><value2><value3> .... 從左邊/右邊插入一個或多個值。 public Long lLeftPush(String key, String value) { return redisTemplate.opsForList().leftPush(key, value); } public Long lLeftPushAll(String key, String... value) { return redisTemplate.opsForList().leftPushAll(key, value); } public Long lLeftPushAll(String key, Collection<String> value) { return redisTemplate.opsForList().leftPushAll(key, value); }
lpop/rpop <key>從左邊/右邊吐出一個值。值在鍵在,值光鍵亡。 public Object lLeftPop(String key) { return redisTemplate.opsForList().leftPop(key); }
rpoplpush <key1><key2> 從<key1>列表右邊吐出一個值,插到<key2>列表左邊。 public Object lRightPopAndLeftPush(String sourceKey, String destinationKey) { return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey); }
lrange <key><start><stop>按照索引下標獲得元素(從左到右) lrange <key> 0 -1 0左邊第一個,-1右邊第一個,(0-1表示獲取所有) public List<Object> lRange(String key, long start, long end) { return redisTemplate.opsForList().range(key, start, end); }
lindex <key><index>按照索引下標獲得元素(從左到右) public Object lIndex(String key, long index) { return redisTemplate.opsForList().index(key, index); }
llen <key>獲得列表長度 public Long lLen(String key) { return redisTemplate.opsForList().size(key); }
lset<key><index><value>將列表key下標為index的值替換成value public void lSet(String key, long index, String value) { redisTemplate.opsForList().set(key, index, value); }
linsert <key> before <value><newvalue> 在<value>的前面插入<newvalue>插入值 public Long lLeftPush(String key, String pivot, String value) { return redisTemplate.opsForList().leftPush(key, pivot, value); }
lrem <key><n><value> n>0 從左邊到右刪除n個value, n<0從右到左刪除n個value public Long lRemove(String key, long index, String value) { return redisTemplate.opsForList().remove(key, index, value); }
2.3 Set類型
Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
Redis的Set是string類型的無序集合。它底層其實是一個value為null的hash表,所以添加,刪除,查找的復雜度都是O(1)。一個算法,隨着數據的增加,執行時間的長短,如果是O(1),數據增加,查找數據的時間不變.使用場景也是比較單一的,就是用在一些去重的場景里,例如每個用戶只能參與一次活動、一個用戶只能中獎一次等等去重場景。
2.3.1 Set的數據結構
Set數據結構是dict字典,字典是用哈希表實現的。
Java中HashSet的內部實現使用的是HashMap,只不過所有的value都指向同一個對象。Redis的set結構也是一樣,它的內部也使用hash結構,所有的value都指向同一個內部值。
2.3.2 Set常用的指令
sadd <key><value1><value2> ..... 將一個或多個 member 元素加入到集合 key 中,已經存在的 member 元素將被忽略 public Long sAdd(String key, String... values) { return redisTemplate.opsForSet().add(key, values); }
smembers <key> 取出該集合的所有值。 public Set<Object> setMembers(String key) { return redisTemplate.opsForSet().members(key); }
sismember <key><value> 判斷集合<key>是否為含有該<value>值,有1,沒有0 public Boolean sIsMember(String key, Object value) { return redisTemplate.opsForSet().isMember(key, value); }
scard<key> 返回該集合的元素個數。 public Long sSize(String key) { return redisTemplate.opsForSet().size(key); }
srem <key><value1><value2> .... 刪除集合中的某個元素。 public Long sRemove(String key, Object... values) { return redisTemplate.opsForSet().remove(key, values); }
spop <key> 隨機從該集合中吐出一個值。 public Object sPop(String key) { return redisTemplate.opsForSet().pop(key); }
srandmember <key><n> 隨機從該集合中取出n個值。不會從集合中刪除 。 public List<Object>sRandomMembers(String key, long count) { return redisTemplate.opsForSet().randomMembers(key, count); }
smove <source><destination>value 把集合中一個值從一個集合移動到另一個集合 public Boolean sMove(String key, String value, String destKey) { return redisTemplate.opsForSet().move(key, value, destKey); }
sinter <key1><key2> 返回兩個集合的交集元素。 public Set<Object> sIntersect(String key, String otherKey) { return redisTemplate.opsForSet().intersect(key, otherKey); }
sunion <key1><key2> 返回兩個集合的並集元素。 public Set<Object> sUnion(String key, String otherKeys) { return redisTemplate.opsForSet().union(key, otherKeys); }
sdiff <key1><key2> 返回兩個集合的差集元素(key1中的,不包含key2中的) public Set<Object> sDifference(String key, String otherKey) { return redisTemplate.opsForSet().difference(key, otherKey); }
2.4 Hash類型
Redis hash 是一個鍵值對集合。Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。類似Java里面的Map<String,Object>用戶ID為查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,如果用普通的key/value結構來存儲
主要有以下2種存儲方式:
第一種方式將用戶ID作為查找key,把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化/反序列化的開銷,並且在需要修改其中一項信息時,需要把整個對象取回,並且修改操作需要對並發進行保護,引入CAS等復雜問題。
第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應屬性的名稱作為唯一標識來取得對應屬性的值,雖然省去了序列化開銷和並發問題,但是用戶ID為重復存儲,如果存在大量這樣的數據,內存浪費還是非常嚇人的。
那么Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部存儲的Value為一個HashMap,並提供了直接存取這個Map成員的接口,通過key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和並發修改控制的問題,如下圖:
常用使用場景
(1)保存結構體信息
hash字典類型也是比較適合保存結構體信息的,不同於字符串一次序列化整個對象,hash可以對用戶結構中的每個字段單獨存儲。這樣當我們需要獲取結構體信息時可以進行部分獲取,而不用序列化所有字段,而將整個字符串保存的結構體信息只能一次性全部讀取。
2.4.1 Hash的數據結構
Hash類型對應的數據結構是兩種:ziplist(壓縮列表),hashtable(哈希表)。當field-value長度較短且個數較少時,使用ziplist,否則使用hashtable。
2.4.2 Hash常用的指令
hset <key><field><value> 給<key>集合中的 <field>鍵賦值<value> public void hPut(String key, String hashKey, String value) { redisTemplate.opsForHash().put(key, hashKey, value); }
hget <key1><field> 從<key1>集合<field>取出 value public Object hGet(String key, String field) { return redisTemplate.opsForHash().get(key, field); }
hmset <key1><field1><value1><field2><value2>... 批量設置hash的值 public void hPutAll(String key, Map<String, String> maps) { redisTemplate.opsForHash().putAll(key, maps); }
hexists<key1><field> 查看哈希表 key 中,給定域 field 是否存在。 public boolean hExists(String key, String field) { return redisTemplate.opsForHash().hasKey(key, field); }
hkeys <key> 列出該hash集合的所有field public Set<Object> hKeys(String key) { return redisTemplate.opsForHash().keys(key); }
hvals <key> 列出該hash集合的所有value public List<Object> hValues(String key) { return redisTemplate.opsForHash().values(key); }
hincrby <key><field><increment> 為哈希表 key 中的域 field 的值加上增量 1 -1 public Double hIncrByFloat(String key, Object field, double delta) { return redisTemplate.opsForHash().increment(key, field, delta); }
hsetnx <key><field><value> 將哈希表 key 中的域 field 的值設置為 value ,當且僅當域 field 不存在 . public Boolean hPutIfAbsent(String key, String hashKey, String value) { return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); }
2.5 Zset類型
Redis有序集合zset與普通集合set非常相似,是一個沒有重復元素的字符串集合。不同之處是有序集合的每個成員都關聯了一個評分(score),這個評分(score)被用來按照從最低分到最高分的方式排序集合中的成員。集合的成員是唯一的,但是評分可以是重復了 。
因為元素是有序的, 所以你也可以很快的根據評分(score)或者次序(position)來獲取一個范圍的元素。訪問有序集合的中間元素也是非常快的,因此你能夠使用有序集合作為一個沒有重復成員的智能列表。
常用使用場景
(1)各類熱門排序場景
例如熱門歌曲榜單列表,value值是歌曲ID,score是播放次數,這樣就可以對歌曲列表按播放次數進行排序。
當然還有類似微博粉絲列表、評論列表等等,可以將value定義為用戶ID、評論ID,score定義為關注時間、評論點贊次數等等。
2.5.1 Zset的數據結構
SortedSet(zset)是Redis提供的一個非常特別的數據結構,一方面它等價於Java的數據結構Map<String, Double>,可以給每一個元素value賦予一個權重score,另一方面它又類似於TreeSet,內部的元素會按照權重score進行排序,可以得到每個元素的名次,還可以通過score的范圍來獲取元素的列表。
zset底層使用了兩個數據結構
(1)hash,hash的作用就是關聯元素value和權重score,保障元素value的唯一性,可以通過元素value找到相應的score值。
(2)跳躍表,跳躍表的目的在於給元素value排序,根據score的范圍獲取元素列表。
2.5.2 Zset常用的指令
zadd <key><score1><value1><score2><value2>… 將一個或多個 member 元素及其 score 值加入到有序集 key 當中。 public Boolean zAdd(String key, String value, double score) { return redisTemplate.opsForZSet().add(key, value, score); } public Long zAdd(String key, Set<TypedTuple<Object>> values) { return redisTemplate.opsForZSet().add(key, values); }
zrange <key><start><stop> [WITHSCORES] 返回有序集 key 中,下標在<start><stop>之間的元素 帶WITHSCORES,可以讓分數一起和值返回到結果集。 public Set<TypedTuple<Object>> zRangeWithScores(String key, long start,long end) { return redisTemplate.opsForZSet().rangeWithScores(key, start, end); }
zrangebyscore key min max [withscores] [limit offset count] 返回有序集 key 中,所有 score 值介於 min 和 max 之間(包括等於 min 或 max )的成員。有序集成員按 score 值遞增(從小到大)次序排列。 public Set<TypedTuple<Object>> zRangeByScoreWithScores(String key,double min, double max, long start, long end) { return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,start, end); }
zrevrangebyscore key max min [withscores] [limit offset count] 同上,改為從大到小排列。 public Set<TypedTuple<Object>> zReverseRangeWithScores(String key,long start, long end) { return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end); }
zincrby <key><increment><value> 為元素的score加上增量 public Double zIncrementScore(String key, String value, double delta) { return redisTemplate.opsForZSet().incrementScore(key, value, delta); }
zrem <key><value> 刪除該集合下,指定值的元素 public Long zRemove(String key, Object... values) { return redisTemplate.opsForZSet().remove(key, values); }
zcount <key><min><max> 統計該集合,分數區間內的元素個數 public Long zCount(String key, double min, double max) { return redisTemplate.opsForZSet().count(key, min, max); }
zrank <key><value> 返回該值在集合中的排名,從0開始。 public Long zReverseRank(String key, Object value) { return redisTemplate.opsForZSet().reverseRank(key, value); }
全部redis指令可查看:http://www.redis.cn/commands.html