一. Redis簡介
Redis是一個速度非常快的高性能的key-value存儲系統。redis的出現,很大程度補償了memcached這類key/value存儲的不足。Redis支持存儲五種value數據類型,包括string(字符串)、list(鏈表)、set(集合)、hash(哈希類型)和zset(sorted set --有序集合)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。為了保證效率,數據都是緩存在內存中。redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文 件,並且在此基礎上實現了master-slave(主從)同步。Redis是基於內存的數據結構存儲開源系統,采用C語言編寫,數據存在內存里面,運行效率極高。可作為內存數據庫、緩存或消息代理中間件,前兩種情況實際當中使用更多些。
Redis支持的客戶端操作語言非常豐富,達到40多種。就Java來說,也有很多訪問驅動實現,我們最常用的還是Jedis。 Jedis活躍度很高,能夠跟上Redis服務端發布的最新功能,而且使用簡單,基本和Redis命令行語法相似。
二. Redis的優點
- 速度快,因為數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1)
- 支持豐富數據類型,支持string,list,set,sorted set,hash
- 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要么全部執行,要么全部不執行
- 豐富的特性:可用於緩存,消息,按key設置過期時間,過期后將會自動刪除
三. Redis與其他一些數據庫和緩存服務器的特性與功能的對比如下:
名稱 |
類型 |
數據存儲選項 |
查詢類型 |
附加功能 |
---|---|---|---|---|
Redis |
使用內存存儲(in-memory)的非關系數據庫 |
字符串、列表、集合、散列表、有序集合 |
每種數據類型都有自己的專屬命令,另外還有批量操作(bulk operation)和不完全(partial)的事務支持 |
發布與訂閱,主從復制(master/slave replication),持久化,腳本(存儲過程,stored procedure) |
memcached |
使用內存存儲的鍵值緩存 |
鍵值之間的映射 |
創建命令、讀取命令、更新命令、刪除命令以及其他幾個命令 |
為提升性能而設的多線程服務器 |
MySQL |
關系數據庫 |
每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖(view);支持空間(spatial)和第三方擴展 |
|
支持ACID性質(需要使用InnoDB),主從復制和主主復制 (master/master replication) |
PostgreSQL |
關系數據庫 |
每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖;支持空間和第三方擴展;支持可定制類型 |
|
支持ACID性質,主從復制,由第三方支持的多主復制(multi-master replication) |
MongoDB |
使用硬盤存儲(on-disk)的非關系文檔存儲 |
每個數據庫可以包含多個表,每個表可以包含多個無schema(schema-less)的BSON文檔 |
創建命令、讀取命令、更新命令、刪除命令、條件查詢命令等 |
支持map-reduce操作,主從復制,分片,空間索引(spatial index) |
四. Redis的持久化方式
在使用類似Redis這樣的內存數據庫時,一個首先要考慮的問題就是“當服務器被關閉時,服務器存儲的數據將何去何從呢?”
Redis擁有兩種不同形式的持久化方法,它們都可以用小而緊湊的格式將存儲在內存中的數據寫入硬盤:
第一種持久化方法為RDB,即時間點轉儲(point-in-time dump)。有一份數據,就把這一份數據整體保存一份,每隔一定的時間就保存一下數據,保存的是最終的結果。轉儲操作既可以在“指定時間段內有指定數量的寫操作執行”這一條件被滿足時執行,又可以通過調用兩條轉儲到硬盤(dump-to-disk)命令中的任何一條來執行;
第二種持久化方法是AOF,將所有修改了數據庫的命令都寫入一個只追加(append-only)文件里面,保存的是命令操作。用戶可以根據數據的重要程度,將只追加寫入設置為從不同步(sync)、每秒同步一次或者每寫入一個命令就同步一次。
五. Redis的主從同步
Redis實現了主從復制特性:執行復制的從服務器會連接上主服務器,接收主服務器發送的整個數據庫的初始副本(copy);之后主服務器執行的寫命令,都會被發送給所有連接着的從服務器去執行,從而實時地更新從服務器的數據集。因為從服務器包含的數據會不斷地進行更新,所以客戶端可以向任意一個從服務器發送讀請求,以此來避免對主服務器進行集中式的訪問。
六. Redis的value的數據類型
1. String(字符串)
string是redis最基本的類型,一個key對應一個value。
string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。
string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。
1.1 常用命令
除了get、set、incr、decr mget等操作外,Redis還提供了下面一些操作:
- 獲取字符串長度
- 往字符串append內容
- 設置和獲取字符串的某一段內容
- 設置及獲取字符串的某一位(bit)
- 批量設置一系列字符串的內容
1.2 應用場景
String是最常用的一種數據類型,普通的key/value存儲都可以歸為此類,value其實不僅是String, 也可以是數字:比如想知道什么時候封鎖一個IP地址(訪問超過幾次)。INCRBY命令讓這些變得很容易,通過原子遞增保持計數。
1.3 實現方式
m,decr等操作時會轉成數值型進行計算,此時redisObject的encoding字段為int。
1.4 實例
1 // get,set 2 jedis.set("hello", "world"); 3 print(1, jedis.get("hello")); 4 jedis.rename("hello", "newhello"); 5 print(1, jedis.get("newhello")); 6 jedis.setex("hello2", 15, "world"); 7 // 數值操作 8 jedis.set("pv", "100"); 9 jedis.incr("pv"); 10 jedis.decrBy("pv", 5); 11 print(2, jedis.get("pv")); 12 print(3, jedis.keys("*"));
2. List(列表)
Redis 列表是簡單的字符串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。列表最多可存儲 2^32 - 1 元素 (4294967295, 每個列表可存儲40多億)。
2.1 常用命令
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
2.2 應用場景
Redis list的應用場景非常多,也是Redis最重要的數據結構之一。
我們可以輕松地實現最新消息、排行榜等功能(比如新浪微博的 TimeLine )。
Lists的另一個應用就是消息隊列,可以利用List的PUSH操作,將任務存在Lists中,然后工作線程再用POP操作將任務取出進行執行。
2.3 實現方式
Redis list的實現為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷,Redis內部的很多實現,包括發送緩沖隊列等也都是用的這個數據結構。
2.4 實例
1 // 列表操作, 最近來訪, 粉絲列表,消息隊列 2 String listName = "list"; 3 jedis.del(listName); 4 for (int i = 0; i < 10; ++i) { 5 jedis.lpush(listName, "a" + String.valueOf(i)); 6 } 7 print(4, jedis.lrange(listName, 0, 12)); // 最近來訪10個id 8 print(5, jedis.llen(listName)); 9 print(6, jedis.lpop(listName)); 10 print(7, jedis.llen(listName)); 11 print(8, jedis.lrange(listName, 2, 6)); // 最近來訪10個id 12 print(9, jedis.lindex(listName, 3)); 13 print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx")); 14 print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb")); 15 print(11, jedis.lrange(listName, 0, 12));
3.Set(集合)
Redis的Set是string類型的無序集合。集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。集合中最大的成員數為 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。
3.1 常用命令
sadd,srem,spop,sdiff ,smembers,sunion 等。
3.2 應用場景
Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
- 共同好友、二度好友
- 利用唯一性,可以統計訪問網站的所有獨立 IP
- 好友推薦的時候,根據 tag 求交集,大於某個 threshold 就可以推薦
在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。因為 Redis 非常人性化的為集合提供了求交集、並集、差集等操作,那么就可以非常方便的實現如共同關注、共同喜好、二度好友等功能,對上面的所有集合操作,你還可以使用不同的命令選擇將結果返回給客戶端還是存集到一個新的集合中。
3.3 實現方式
set 的內部實現是一個 value永遠為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。
3.4 實例
1 String likeKey1 = "newsLike1"; 2 String likeKey2 = "newsLike2"; 3 for (int i = 0; i < 10; ++i) { 4 jedis.sadd(likeKey1, String.valueOf(i)); 5 jedis.sadd(likeKey2, String.valueOf(i * 2)); 6 } 7 print(20, jedis.smembers(likeKey1)); 8 print(21, jedis.smembers(likeKey2)); 9 print(22, jedis.sunion(likeKey1, likeKey2)); 10 print(23, jedis.sdiff(likeKey1, likeKey2)); 11 print(24, jedis.sinter(likeKey1, likeKey2)); 12 print(25, jedis.sismember(likeKey1, "12")); 13 print(26, jedis.sismember(likeKey2, "12")); 14 jedis.srem(likeKey1, "5"); 15 print(27, jedis.smembers(likeKey1)); 16 // 從1移動到2 17 jedis.smove(likeKey2, likeKey1, "14"); 18 print(28, jedis.smembers(likeKey1)); 19 print(29, jedis.scard(likeKey1));
4.Hash(哈希)
Redis hash 是一個鍵值對集合。它是一個string類型的field和value的映射表,hash特別適合用於存儲對象。集合中最大的成員數為 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。
4.1 常用命令
hget,hset,hgetall 等。
4.2 應用場景
存儲、讀取、修改用戶屬性。
Redis的Hash實際是內部存儲的Value為一個HashMap, 並提供了直接存取這個Map成員的接口, 如:hmset user:001 name "李三" age 18 birthday "20010101" ,也就是說,Key仍然是用戶ID,value是一個Map,這個Map的key是成員的屬性名,value是屬性值, 這樣對數據的修改和存取都可以直接通過其內部Map的Key(Redis里稱內部Map的key為field), 也就是通過 key(用戶ID) + field(屬性標簽) 操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和並發修改控制的問題。Redis 的 Hash 結構可以像在數據庫中 Update 一個屬性一樣只修改某一項屬性值。
4.3 實現方式
Redis Hash對應Value內部實際就是一個HashMap,實際這里會有2種不同實現,這個Hash的成員比較少時Redis為了節省內存會采用類似一維數組的方式來緊湊存儲,而不會采用真正的HashMap結構,對應的value redisObject的encoding為zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding為ht。
4.4 實例
1 // hash, 可變字段 2 String userKey = "userxx"; 3 jedis.hset(userKey, "name", "jim"); 4 jedis.hset(userKey, "age", "12"); 5 jedis.hset(userKey, "phone", "18666666666"); 6 print(12, jedis.hget(userKey, "name")); 7 print(13, jedis.hgetAll(userKey)); 8 jedis.hdel(userKey, "phone"); 9 print(14, jedis.hgetAll(userKey)); 10 print(15, jedis.hexists(userKey, "email")); 11 print(16, jedis.hexists(userKey, "age")); 12 print(17, jedis.hkeys(userKey)); 13 print(18, jedis.hvals(userKey)); 14 jedis.hsetnx(userKey, "school", "zju");//這個方法是先判斷有沒有這個字段,沒有的話才進行設置 15 jedis.hsetnx(userKey, "name", "yxy"); 16 print(19, jedis.hgetAll(userKey));
5.zset(sorted set:有序集合)
Redis zset 和 set 一tring類型元素的集合,且不允許重復的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重復。
5.1 常用命令
zadd,zrange,zrem,zcard等
5.2 使用場景
- 帶有權重的元素,比如一個游戲的用戶得分排行榜
- 比較復雜的數據結構,一般用到的場景不算太多
以某個條件為權重,比如按頂的次數排序。ZREVRANGE命令可以用來按照得分來獲取前100名的用戶,ZRANK可以用來獲取用戶排名,非常直接而且操作容易。
Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過用戶額外提供一個優先級(score)的參數來為成員排序,並且是插入有序的,即自動排序。比如:twitter 的public timeline可以以發表時間作為score來存儲,這樣獲取時就是自動按時間排好序的。比如:全班同學成績的SortedSets,value可以是同學的學號,而score就可以是其考試得分,這樣數據插入集合的,就已經進行了天然的排序。
另外還可以用Sorted Sets來做帶權重的隊列,比如普通消息的score為1,重要消息的score為2,然后工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優先執行。
5.3 實現方式
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據是HashMap里存的score,使用跳躍表的結構可以獲得比較高的查找效率,並且在實現上比較簡單。
5.4 實例
1 // 排序集合,有限隊列,排行榜 2 String rankKey = "rankKey"; 3 jedis.zadd(rankKey, 15, "Jim"); 4 jedis.zadd(rankKey, 60, "Ben"); 5 jedis.zadd(rankKey, 90, "Lee"); 6 jedis.zadd(rankKey, 75, "Lucy"); 7 jedis.zadd(rankKey, 80, "Mei"); 8 print(30, jedis.zcard(rankKey)); 9 print(31, jedis.zcount(rankKey, 61, 100)); 10 // 改錯卷了 11 print(32, jedis.zscore(rankKey, "Lucy")); 12 jedis.zincrby(rankKey, 2, "Lucy"); 13 print(33, jedis.zscore(rankKey, "Lucy")); 14 jedis.zincrby(rankKey, 2, "Luc"); 15 print(34, jedis.zscore(rankKey, "Luc")); 16 print(35, jedis.zcount(rankKey, 0, 100)); 17 // 1-4 名 Luc 18 print(36, jedis.zrange(rankKey, 0, 10)); 19 print(36, jedis.zrange(rankKey, 1, 3)); 20 print(36, jedis.zrevrange(rankKey, 1, 3)); 21 for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) { 22 print(37, tuple.getElement() + ":" + String.valueOf(tuple.getScore())); 23 } 24 print(38, jedis.zrank(rankKey, "Ben")); 25 print(39, jedis.zrevrank(rankKey, "Ben")); 26 String setKey = "zset"; 27 jedis.zadd(setKey, 1, "a"); 28 jedis.zadd(setKey, 1, "b"); 29 jedis.zadd(setKey, 1, "c"); 30 jedis.zadd(setKey, 1, "d"); 31 jedis.zadd(setKey, 1, "e"); 32 print(40, jedis.zlexcount(setKey, "-", "+")); 33 print(41, jedis.zlexcount(setKey, "(b", "[d")); 34 print(42, jedis.zlexcount(setKey, "[b", "[d")); 35 jedis.zrem(setKey, "b"); 36 print(43, jedis.zrange(setKey, 0, 10)); 37 jedis.zremrangeByLex(setKey, "(c", "+"); 38 print(44, jedis.zrange(setKey, 0, 2));
參考:http://www.epubit.com.cn/article/200
http://www.jb51.net/article/54774.htm
http://blog.csdn.net/gaogaoshan/article/details/41039581/