1. Redis的簡介
Redis是一個基於內存的高性能key-value數據庫,與memcached類似,整個數據庫統統加載在內存當中進行操作,定期通過異步操作把數據庫數據flush到硬盤上進行保存。因爲是純內存操作,Redis的性能非常出色,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。
1.1. Redis特點
- Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啓的時候可以再次加載進行使用。
- Redis不僅僅支持簡單的key-value類型的數據,同時還提供了list、set、zset、hash等數據結構的存儲。
- Redis支持數據的備份,即master-slave模式的數據備份
1.2. Redis優勢
- 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1),Redis能讀的速度是110000次/s,寫的速度是81000次/s
- 支持豐富數據類型,支持string,list,set,sorted set,hash
- 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行
- 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除
2. Redis持久化方式
Redis擁有兩種不同形式的持久化方法,它們都可以用小而緊湊的格式將存儲在內存中的數據寫入硬盤
- RDB持久化:即時間點轉儲(point-in-time dump)。有一份數據,就把這一份數據整體保存一份,每隔一定的時間就保存一下數據,保存的是最終的結果。轉儲操作既可以在“指定時間段內有指定數量的寫操作執行”這一條件被滿足時執行,又可以通過調用兩條轉儲到硬盤(dump-to-disk)命令中的任何一條來執行
- AOF持久化:將所有修改了數據庫的命令都寫入一個只追加(append-only)文件裏面,保存的是命令操作。用戶可以根據數據的重要程度,將只追加寫入設置爲從不同步(sync)、每秒同步一次或者每寫入一個命令就同步一次。
3. Redis的value數據類型
3.1. string字符串
- string是redis最基本的類型,一個key對應一個value。
- string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。
- string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB
3.1.1 常用命令
除了get、set、incr、decr mget等操作外,Redis還提供了下面一些操作:
- 獲取字符串長度
- 往字符串append內容
- 設置和獲取字符串的某一段內容
- 設置及獲取字符串的某一位(bit)
- 批量設置一系列字符串的內容
3.1.2 應用場景
String是最常用的一種數據類型,普通的key/value存儲都可以歸爲此類,value其實不僅是String, 也可以是數字:比如想知道什麼時候封鎖一個IP地址(訪問超過幾次)。INCRBY命令讓這些變得很容易,通過原子遞增保持計數。
3.1.3 實現方式
incr,decr等操作時會轉成數值型進行計算,此時redisObject的encoding字段爲int。
3.1.4 實例
//連接redis Jedis jedis = new Jedis("localhost",6379); //驗證密碼 jedis.auth("123"); System.out.println("連接成功"); System.out.println("-------------get/set操作---------------"); //set get jedis.set("Hello", "world1"); System.out.println(jedis.get("Hello")); //重命名key jedis.rename("Hello", "newHello"); System.out.println(jedis.get("newHello")); jedis.setex("Hello2", 3, "world2"); System.out.println(jedis.get("Hello2")); //獲取一個或多個key的value List<String> mget = jedis.mget("newHello","Hello2"); System.out.println(mget); System.out.println("-------------數值操作---------------"); //數值操作 jedis.set("pv", "100"); jedis.incr("pv"); System.out.println(jedis.get("pv")); jedis.decrBy("pv",5); System.out.println(jedis.get("pv")); System.out.println(jedis.keys("*")); //斷開連接 jedis.close(); |
輸出結果:
3.2 List列表
Redis 列表是簡單的字符串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。列表最多可存儲 2^32 - 1 元素 (4294967295, 每個列表可存儲40多億)。
3.2.1 常用命令
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
3.2.2 應用場景
- Redis list的應用場景非常多,也是Redis最重要的數據結構之一。
- 我們可以輕鬆地實現最新消息、排行榜等功能(比如新浪微博的TimeLine )。
- Lists的另一個應用就是消息隊列,可以利用List的PUSH操作,將任務存在Lists中,然後工作線程再用POP操作將任務取出進行執行。
3.2.3 實現方式
Redis list的實現爲一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括髮送緩衝隊列等也都是用的這個數據結構。
3.2.4 實例
//連接redis Jedisjedis = new Jedis("localhost",6379); //驗證密碼 jedis.auth("123"); System.out.println("連接成功"); System.out.println("-------------列表操作---------------"); // 列表操作, 最近來訪, 粉絲列表,消息隊列 StringlistName = "list"; jedis.del(listName); for (inti = 0; i < 10; ++i) { jedis.lpush(listName, "a" + String.valueOf(i)); } System.out.println(jedis.lrange(listName, 0, 12)); System.out.println("listName的size爲:"+jedis.llen(listName)); System.out.println(jedis.lpop(listName)); System.out.println("listName的size爲:"+jedis.llen(listName)); System.out.println(jedis.lrange(listName, 2, 6)); System.out.println(jedis.lindex(listName, 3)); //在a4值之後插入 System.out.println(jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx")); //在a4值之前插入 System.out.println(jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb")); System.out.println(jedis.lrange(listName, 0, 12)); //斷開連接 jedis.close(); |
輸出結果:
3.3 Set集合
Redis的Set是string類型的無序集合。集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。集合中最大的成員數爲 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。
3.3.1 常用命令
sadd,srem,spop,sdiff,smembers,sunion 等。
3.3.2 應用場景
Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重複數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
- 共同好友、二度好友
- 利用唯一性,可以統計訪問網站的所有獨立 IP
- 好友推薦的時候,根據 tag 求交集,大於某個 threshold 就可以推薦
在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。因爲 Redis 非常人性化的爲集合提供了求交集、並集、差集等操作,那麼就可以非常方便的實現如共同關注、共同喜好、二度好友等功能,對上面的所有集合操作,你還可以使用不同的命令選擇將結果返回給客戶端還是存集到一個新的集合中。
3.3.3 實現方式
set 的內部實現是一個 value永遠爲null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。
3.3.4 實例
//連接redis Jedis jedis = new Jedis("localhost",6379); //驗證密碼 jedis.auth("123"); System.out.println("連接成功"); System.out.println("-------------set集合---------------"); String likeKey1 = "newsLike1"; String likeKey2 = "newsLike2"; for (inti = 0; i < 10; ++i) { jedis.sadd(likeKey1, String.valueOf(i)); jedis.sadd(likeKey2, String.valueOf(i * 2)); } System.out.println("likekey1:"+jedis.smembers(likeKey1)); System.out.println("likekey2:"+jedis.smembers(likeKey2)); //並集 System.out.println("並集:"+jedis.sunion(likeKey1, likeKey2)); //差集 System.out.println("差集:"+jedis.sdiff(likeKey1, likeKey2)); //交集 System.out.println("交集:"+jedis.sinter(likeKey1, likeKey2)); //判斷是否有該值 System.out.println(jedis.sismember(likeKey1, "12")); System.out.println(jedis.sismember(likeKey2, "12")); //刪除某個值 jedis.srem(likeKey1, "5"); System.out.println("likekey1:"+jedis.smembers(likeKey1)); //將"14"從2移動到1 jedis.smove(likeKey2, likeKey1, "14"); System.out.println("likekey1:"+jedis.smembers(likeKey1)); System.out.println("likekey2:"+jedis.smembers(likeKey2)); System.out.println(jedis.scard(likeKey1)); //斷開連接 jedis.close(); |
3.4 Hash哈希
Redis hash 是一個鍵值對集合。它是一個string類型的field和value的映射表,hash特別適合用於存儲對象。集合中最大的成員數爲 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。
3.4.1 常用命令
hget,hset,hgetall 等。
3.4.2 應用場景
存儲、讀取、修改用戶屬性。
Redis的Hash實際是內部存儲的Value爲一個HashMap,並提供了直接存取這個Map成員的接口,如:hmset user:001 name:"李三" age:18birthday:"20010101" ,也就是說,Key仍然是用戶ID,value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取都可以直接通過其內部Map的Key(Redis裏稱內部Map的key爲field), 也就是通過 key(用戶ID) + field(屬性標籤) 操作對應屬性數據了,既不需要重複存儲數據,也不會帶來序列化和併發修改控制的問題。Redis 的 Hash 結構可以像在數據庫中 Update 一個屬性一樣只修改某一項屬性值。
3.4.3 實現方式
Redis Hash對應Value內部實際就是一個HashMap,實際這裏會有2種不同實現,這個Hash的成員比較少時Redis爲了節省內存會採用類似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht
3.4.4 實例
//連接redis Jedis jedis = new Jedis("localhost",6379); //驗證密碼 jedis.auth("123"); System.out.println("連接成功"); System.out.println("-------------hash---------------"); // hash, 可變字段 String userKey = "userxx"; jedis.hset(userKey, "name", "jim"); jedis.hset(userKey, "age", "12"); jedis.hset(userKey, "phone", "18666666666"); System.out.println(jedis.hget(userKey, "name")); System.out.println(jedis.hgetAll(userKey)); jedis.hdel(userKey, "phone"); System.out.println(jedis.hgetAll(userKey)); System.out.println(jedis.hexists(userKey, "email")); System.out.println(jedis.hexists(userKey, "age")); System.out.println(jedis.hkeys(userKey)); System.out.println(jedis.hvals(userKey)); jedis.hsetnx(userKey, "school", "zju");//這個方法是先判斷有沒有這個字段,沒有的話才進行設置 jedis.hsetnx(userKey, "name", "yxy"); System.out.println(jedis.hgetAll(userKey)); //斷開連接 jedis.close(); |
輸出結果:
3.5 Zset(sorted set:有序集合)
Redis zset 和 set 一tring類型元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來爲集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。
3.5.1 常用命令
zadd,zrange,zrem,zcard等。
3.5.2 應用場景
- 帶有權重的元素,比如一個遊戲的用戶得分排行榜
- 比較複雜的數據結構,一般用到的場景不算太多
以某個條件爲權重,比如按頂的次數排序。ZREVRANGE命令可以用來按照得分來獲取前100名的用戶,ZRANK可以用來獲取用戶排名,非常直接而且操作容易。
Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先級(score)的參數來爲成員排序,並且是插入有序的,即自動排序。比如:twitter 的public timeline可以以發表時間作爲score來存儲,這樣獲取時就是自動按時間排好序的。比如:全班同學成績的SortedSets,value可以是同學的學號,而score就可以是其考試得分,這樣數據插入集合的,就已經進行了天然的排序。
另外還可以用SortedSets來做帶權重的隊列,比如普通消息的score爲1,重要消息的score爲2,然後工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優先執行。
3.5.3 實現方式
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是所有的成員,排序依據是HashMap裏存的score,使用跳躍表的結構可以獲得比較高的查找效率,並且在實現上比較簡單。
3.5.4 實例
//連接redis Jedis jedis = new Jedis("localhost",6379); //驗證密碼 jedis.auth("123"); System.out.println("連接成功"); System.out.println("-------------zset---------------"); // 排序集合,有限隊列,排行榜 String rankKey = "rankKey"; jedis.zadd(rankKey, 15, "Jim"); jedis.zadd(rankKey, 60, "Ben"); jedis.zadd(rankKey, 90, "Lee"); jedis.zadd(rankKey, 75, "Lucy"); jedis.zadd(rankKey, 80, "Mei"); System.out.println("key的數量:"+jedis.zcard(rankKey)); System.out.println(jedis.zcount(rankKey, 61, 100)); // 改錯捲了 System.out.println(jedis.zscore(rankKey, "Lucy")); jedis.zincrby(rankKey, 2, "Lucy"); System.out.println(jedis.zscore(rankKey, "Lucy")); jedis.zincrby(rankKey, 2, "Luc"); System.out.println(jedis.zscore(rankKey, "Luc")); System.out.println(jedis.zcount(rankKey, 0, 100)); // 1-4 名 Luc System.out.println(jedis.zrange(rankKey, 0, 10)); System.out.println(jedis.zrange(rankKey, 1, 3)); System.out.println(jedis.zrevrange(rankKey, 1, 3)); for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) { System.out.println(tuple.getElement() + ":" + String.valueOf(tuple.getScore())); } System.out.println(jedis.zrank(rankKey, "Ben")); System.out.println(jedis.zrevrank(rankKey, "Ben")); String setKey = "zset"; jedis.zadd(setKey, 1, "a"); jedis.zadd(setKey, 1, "b"); jedis.zadd(setKey, 1, "c"); jedis.zadd(setKey, 1, "d"); jedis.zadd(setKey, 1, "e"); //"-" "+"分別表示的得分最小值成員和得分最大值成員 System.out.println(jedis.zlexcount(setKey, "-", "+")); //"(b" 表示不包括b System.out.println(jedis.zlexcount(setKey, "(b", "[d")); //成員名稱必須以"["開頭 System.out.println(jedis.zlexcount(setKey, "[b", "[d")); jedis.zrem(setKey, "b"); System.out.println(jedis.zrange(setKey, 0, 10)); jedis.zremrangeByLex(setKey, "(c", "+"); System.out.println(jedis.zrange(setKey, 0, 2)); //斷開連接 jedis.close(); |
輸出結果: