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
Redis支持的客戶端操做語言很是豐富,達到40多種。就Java來講,也有不少訪問驅動實現,咱們最經常使用的仍是Jedis。 Jedis活躍度很高,可以跟上Redis服務端發佈的最新功能,並且使用簡單,基本和Redis命令行語法類似。數據庫
名稱數組 |
類型緩存 |
數據存儲選項安全 |
查詢類型服務器 |
附加功能數據結構 |
---|---|---|---|---|
Redis多線程 |
使用內存存儲(in-memory)的非關係數據庫併發 |
字符串、列表、集合、散列表、有序集合app |
每種數據類型都有本身的專屬命令,另外還有批量操做(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擁有兩種不一樣形式的持久化方法,它們均可以用小而緊湊的格式將存儲在內存中的數據寫入硬盤:
第一種持久化方法爲RDB,即時間點轉儲(point-in-time dump)。有一份數據,就把這一份數據總體保存一份,每隔必定的時間就保存一下數據,保存的是最終的結果。轉儲操做既能夠在「指定時間段內有指定數量的寫操做執行」這一條件被知足時執行,又能夠經過調用兩條轉儲到硬盤(dump-to-disk)命令中的任何一條來執行;
第二種持久化方法是AOF,將全部修改了數據庫的命令都寫入一個只追加(append-only)文件裏面,保存的是命令操做。用戶能夠根據數據的重要程度,將只追加寫入設置爲從不一樣步(sync)、每秒同步一次或者每寫入一個命令就同步一次。
Redis實現了主從複製特性:執行復制的從服務器會鏈接上主服務器,接收主服務器發送的整個數據庫的初始副本(copy);以後主服務器執行的寫命令,都會被髮送給全部鏈接着的從服務器去執行,從而實時地更新從服務器的數據集。由於從服務器包含的數據會不斷地進行更新,因此客戶端能夠向任意一個從服務器發送讀請求,以此來避免對主服務器進行集中式的訪問。
string是redis最基本的類型,一個key對應一個value。
string類型是二進制安全的。意思是redis的string能夠包含任何數據。好比jpg圖片或者序列化的對象 。
string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。
除了get、set、incr、decr mget等操做外,Redis還提供了下面一些操做:
String是最經常使用的一種數據類型,普通的key/value存儲均可以歸爲此類,value其實不只是String, 也能夠是數字:好比想知道何時封鎖一個IP地址(訪問超過幾回)。INCRBY命令讓這些變得很容易,經過原子遞增保持計數。
m,decr等操做時會轉成數值型進行計算,此時redisObject的encoding字段爲int。
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("*"));
Redis 列表是簡單的字符串列表,按照插入順序排序。能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)。列表最多可存儲 2^32 - 1 元素 (4294967295, 每一個列表可存儲40多億)。
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
Redis list的應用場景很是多,也是Redis最重要的數據結構之一。
咱們能夠輕鬆地實現最新消息、排行榜等功能(好比新浪微博的 TimeLine )。
Lists的另外一個應用就是消息隊列,能夠利用List的PUSH操做,將任務存在Lists中,而後工做線程再用POP操做將任務取出進行執行。
Redis list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。
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));
Redis的Set是string類型的無序集合。集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。集合中最大的成員數爲 2^32 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
sadd,srem,spop,sdiff ,smembers,sunion 等。
Redis set對外提供的功能與list相似是一個列表的功能,特殊之處在於set是能夠自動排重的,當你須要存儲一個列表數據,又不但願出現重複數據時,set是一個很好的選擇,而且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
在微博應用中,能夠將一個用戶全部的關注人存在一個集合中,將其全部粉絲存在一個集合。由於 Redis 很是人性化的爲集合提供了求交集、並集、差集等操做,那麼就能夠很是方便的實現如共同關注、共同喜愛、二度好友等功能,對上面的全部集合操做,你還可使用不一樣的命令選擇將結果返回給客戶端仍是存集到一個新的集合中。
set 的內部實現是一個 value永遠爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的緣由。
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));
Redis hash 是一個鍵值對集合。它是一個string類型的field和value的映射表,hash特別適合用於存儲對象。集合中最大的成員數爲 2^32 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
hget,hset,hgetall 等。
存儲、讀取、修改用戶屬性。
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 一個屬性同樣只修改某一項屬性值。
Redis Hash對應Value內部實際就是一個HashMap,實際這裏會有2種不一樣實現,這個Hash的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。
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));
Redis zset 和 set 一tring類型元素的集合,且不容許重複的成員。不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。zset的成員是惟一的,但分數(score)卻能夠重複。
zadd,zrange,zrem,zcard等
以某個條件爲權重,好比按頂的次數排序。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的倒序來獲取工做任務。讓重要的任務優先執行。
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是全部的成員,排序依據是HashMap裏存的score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。
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