一、redis環(huán)境搭建
1.簡介
redis是一個開源的key-value數(shù)據(jù)庫。它又經(jīng)常被認(rèn)為是一個數(shù)據(jù)結(jié)構(gòu)服務(wù)器。因為它的value不僅包括基本的string類型還有l(wèi)ist,set ,sorted set和hash類型。當(dāng)然這些類型的元素也都是string類型。也就是說list,set這些集合類型也只能包含
string 類型。你可以在這些類型上做很多原子性的操作。比如對一個字符value追加字符串(APPEND命令)。加加或者減減一個數(shù)字字符串(INCR命令,當(dāng) 然是按整數(shù)處理的)??梢詫ist類型進(jìn)行push,或者pop元素操作(可以模擬棧和隊列)。對于set類型可以進(jìn)行一些集合相關(guān)操作(intersection union difference)。memcache也有類似與++,--的命令。
不過memcache的value只包括string類型。遠(yuǎn)沒有redis的value類型豐富。和memcahe一樣為了性能。redis的數(shù)據(jù)通常都是放到內(nèi)存中的。當(dāng)然redis可以每間隔一定時間將內(nèi)存中數(shù)據(jù)寫入到磁盤以防止數(shù)據(jù)丟失。redis也支持主從復(fù)制機(jī)制(master-slave replication)。redis的其他特性包括簡單的事務(wù)支持和 發(fā)布訂閱(pub/sub)通道功能,而且redis配置管理非常簡單。還有各種語言版本的開源客戶端類庫。
2.安裝
3.0.7目前是最新穩(wěn)定版
可以在linux下運行如下命令進(jìn)行安裝
$ tar xg*** redis-3.0.7.tar.gz
$ cd redis-3.0.7
$ make
make完后 redis-3.0.7目錄下會出現(xiàn)編譯后的redis服務(wù)程序redis-server,還有用于測試的客戶端程序redis-cli
下面啟動redis服務(wù)。
$./redis-server
注意這種方式啟動redis 使用的是默認(rèn)配置。也可以通過啟動參數(shù)告訴redis使用指定配置文件使用下面命令啟動。
$ 。/redis-server redis.conf
redis.conf是一個默認(rèn)的配置文件。我們可以根據(jù)需要使用自己的配置文件。
啟動redis服務(wù)進(jìn)程后,就可以使用測試客戶端程序redis-cli和redis服務(wù)交互了。
比如
$ 。/redis-cli
redis> set foo bar
OK
redis> get foo
“bar”
這里演示了get和set命令操作簡單類型value的例子。foo是key ,bar是個string類型的value
沒linux的可以通過這個在線的來練習(xí),當(dāng)然在線版的很多管理相關(guān)的命令是不支持的。
3.java客戶端hello,world
客戶端jar包為 jedis
import redis.clients.jedis.Jedis;
public class Test1 {
public static void main(String[] args) {
// 連接本地的 Redis 服務(wù)
Jedis jedis = new Jedis(“169.254.173.100”);
System.out.println(“Connection to server sucessfully”);
// 查看服務(wù)是否運行
System.out.println(“Server is running: ” + jedis.ping());
String keys = “name”;
jedis.del(keys);
String vaule = jedis.set(keys, “tanggao”);
System.out.println(vaule);
}
}
好了redis環(huán)境已經(jīng)搭建好了。后面會寫寫redis的各種類型和類型相關(guān)的命令和一些具體的應(yīng)用場景
二、 redis學(xué)習(xí)筆記之?dāng)?shù)據(jù)類型
本文介紹下redis支持的各種數(shù)據(jù)類型包括string,list ,set ,sorted set和hash
1. keys
redis本質(zhì)上一個key-value db,所以我們首先來看看他的key.首先key也是字符串類型,但是key中不能包括邊界字符
由于key不是binary safe的字符串,所以像“my key”和“mykey ”這樣包含空格和換行的key是不允許的
順便說一下在redis內(nèi)部并不限制使用binary字符,這是redis協(xié)議限制的?!? ”在協(xié)議格式中會作為特殊字符。
redis 1.2以后的協(xié)議中部分命令已經(jīng)開始使用新的協(xié)議格式了(比如MSET)??傊壳斑€是把包含邊界字符當(dāng)成非法的key吧,
免得被bug糾纏。
另外關(guān)于key的一個格式約定介紹下,object-type:id:field。比如user:1000:password,blog:xxidxx:title
還有key的長度最好不要太長。道理很明顯占內(nèi)存啊,而且查找時候相對短key也更慢。不過也推薦過短的key,
比如u:1000:pwd,這樣的。顯然沒上面的user:1000:password可讀性好。
下面介紹下key相關(guān)的命令
exits key 測試指定key是否存在,返回1表示存在,0不存在
del key1 key2 。。。.keyN 刪除給定key,返回刪除key的數(shù)目,0表示給定key都不存在
type key 返回給定key的value類型。返回none 表示不存在key,string字符類型,list鏈表類型 set無序集合類型。。。
keys pattern 返回匹配指定模式的所有key,下面給個例子
redis> set test dsf
OK
redis> set tast dsaf
OK
redis> set tist adff
OK
redis> keys t*
1. “tist”
2. “tast”
3. “test”
redis> keys t[ia]st
1. “tist”
2. “tast”
redis> keys t?st
1. “tist”
2. “tast”
3. “test”
randomkey 返回從當(dāng)前數(shù)據(jù)庫中隨機(jī)選擇的一個key,如果當(dāng)前數(shù)據(jù)庫是空的,返回空串
rename oldkey newkey 原子的重命名一個key,如果newkey存在,將會被覆蓋,返回1表示成功,0失敗。可能是oldkey不存在或者和newkey相同
renamenx oldkey newkey 同上,但是如果newkey存在返回失敗
dbsize 返回當(dāng)前數(shù)據(jù)庫的key數(shù)量
expire key seconds 為key指定過期時間,單位是秒。返回1成功,0表示key已經(jīng)設(shè)置過過期時間或者不存在
ttl key 返回設(shè)置過過期時間的key的剩余過期秒數(shù)-1表示key不存在或者沒有設(shè)置過過期時間
select db-index 通過索引選擇數(shù)據(jù)庫,默認(rèn)連接的數(shù)據(jù)庫所有是0,默認(rèn)數(shù)據(jù)庫數(shù)是16個。返回1表示成功,0失敗
move key db-index 將key從當(dāng)前數(shù)據(jù)庫移動到指定數(shù)據(jù)庫。返回1成功。0如果key不存在,或者已經(jīng)在指定數(shù)據(jù)庫中
flushdb 刪除當(dāng)前數(shù)據(jù)庫中所有key,此方法不會失敗。慎用
flushall 刪除所有數(shù)據(jù)庫中的所有key,此方法不會失敗。更加慎用
2. string 類型
string是redis最基本的類型,而且string類型是二進(jìn)制安全的。意思是redis的string可以包含任何數(shù)據(jù)。比如jpg圖片或者序列化的對象
。從內(nèi)部實現(xiàn)來看其實string可以看作byte數(shù)組,最大上限是1G字節(jié)。下面是string類型的定義。
struct sdshdr {
long len;
long free;
char buf[];
};
buf是個char數(shù)組用于存貯實際的字符串內(nèi)容。其實char和c#中的byte是等價的,都是一個字節(jié)
len是buf數(shù)組的長度,free是數(shù)組中剩余可用字節(jié)數(shù)。由此可以理解為什么string類型是二進(jìn)制安全的了。因為它本質(zhì)上就是個byte數(shù)組。
當(dāng)然可以包含任何數(shù)據(jù)了。另外string類型可以被部分命令按int處理。比如incr等命令,下面詳細(xì)介紹。還有redis的其他類型像list,set,sorted set ,hash
它們包含的元素與都只能是string類型。
如果只用string類型,redis就可以被看作加上持久化特性的memcached.當(dāng)然redis對string類型的操作比memcached多很多啊。如下:
set key value 設(shè)置key對應(yīng)的值為string類型的value,返回1表示成功,0失敗
setnx key value 同上,如果key已經(jīng)存在,返回0。nx 是not exist的意思
get key 獲取key對應(yīng)的string值,如果key不存在返回nil
getset key value 原子的設(shè)置key的值,并返回key的舊值。如果key不存在返回nil
mget key1 key2 。。。 keyN 一次獲取多個key的值,如果對應(yīng)key不存在,則對應(yīng)返回nil。下面是個實驗,首先清空當(dāng)前數(shù)據(jù)庫,然后
設(shè)置k1,k2.獲取時k3對應(yīng)返回nil
redis> flushdb
OK
redis> dbsize
(integer) 0
redis> set k1 a
OK
redis> set k2 b
OK
redis> mget k1 k2 k3
1. “a”
2. “b”
3. (nil)
mset key1 value1 。。。 keyN valueN 一次設(shè)置多個key的值,成功返回1表示所有的值都設(shè)置了,失敗返回0表示沒有任何值被設(shè)置
msetnx key1 value1 。。。 keyN valueN 同上,但是不會覆蓋已經(jīng)存在的key
incr key 對key的值做加加操作,并返回新的值。注意incr一個不是int的value會返回錯誤,incr一個不存在的key,則設(shè)置key為1
decr key 同上,但是做的是減減操作,decr一個不存在key,則設(shè)置key為-1
incrby key integer 同incr,加指定值 ,key不存在時候會設(shè)置key,并認(rèn)為原來的value是0
decrby key integer 同decr,減指定值。decrby完全是為了可讀性,我們完全可以通過incrby一個負(fù)值來實現(xiàn)同樣效果,反之一樣。
append key value 給指定key的字符串值追加value,返回新字符串值的長度。下面給個例子
redis> set k hello
OK
redis> append k ,world
(integer) 11
redis> get k
“hello,world”
substr key start end 返回截取過的key的字符串值,注意并不修改key的值。下標(biāo)是從0開始的,接著上面例子
redis> substr k 0 8
“hello,wor”
redis> get k
“hello,world”
3. list
redis的list類型其實就是一個每個子元素都是string類型的雙向鏈表。所以[lr]push和[lr]pop命令的算法時間復(fù)雜度都是O(1)
另外list會記錄鏈表的長度。所以llen操作也是O(1)。鏈表的最大長度是(2的32次方-1)。我們可以通過push,pop操作從鏈表的頭部或者尾部添加刪除元素。這使得list既可以用作棧,也可以用作隊列。有意思的是list的pop操作還有阻塞版本的。當(dāng)我們[lr]pop一個list對象是,如果list是空,或者不存在,會立即返回nil。但是阻塞版本的b[lr]pop可以則可以阻塞,當(dāng)然可以加超時時間,超時后也會返回nil。為什么要阻塞版本的pop呢,主要是為了避免輪詢。舉個簡單的例子如果我們用list來實現(xiàn)一個工作隊列。執(zhí)行任務(wù)的thread可以調(diào)用阻塞版本的pop去
獲取任務(wù)這樣就可以避免輪詢?nèi)z查是否有任務(wù)存在。當(dāng)任務(wù)來時候工作線程可以立即返回,也可以避免輪詢帶來的延遲。ok下面介紹list相關(guān)命令
lpush key string 在key對應(yīng)list的頭部添加字符串元素,返回1表示成功,0表示key存在且不是list類型
rpush key string 同上,在尾部添加llen key 返回key對應(yīng)list的長度,key不存在返回0,如果key對應(yīng)類型不是list返回錯誤
lrange key start end 返回指定區(qū)間內(nèi)的元素,下標(biāo)從0開始,負(fù)值表示從后面計算,-1表示倒數(shù)第一個元素 ,key不存在返回空列表
ltrim key start end 截取list,保留指定區(qū)間內(nèi)元素,成功返回1,key不存在返回錯誤
lset key index value 設(shè)置list中指定下標(biāo)的元素值,成功返回1,key或者下標(biāo)不存在返回錯誤
lrem key count value 從key對應(yīng)list中刪除count個和value相同的元素。count為0時候刪除全部
lpop key 從list的頭部刪除元素,并返回刪除元素。如果key對應(yīng)list不存在或者是空返回nil,如果key對應(yīng)值不是list返回錯誤
rpop 同上,但是從尾部刪除
blpop key1.。.keyN timeout 從左到右掃描返回對第一個非空list進(jìn)行l(wèi)pop操作并返回,比如blpop list1 list2 list3 0 ,如果list不存在
list2,list3都是非空則對list2做lpop并返回從list2中刪除的元素。如果所有的list都是空或不存在,則會阻塞timeout秒,timeout為0表示一直阻塞。
當(dāng)阻塞時,如果有client對key1.。.keyN中的任意key進(jìn)行push操作,則第一在這個key上被阻塞的client會立即返回。如果超時發(fā)生,則返回nil。有點像unix的select或者poll
brpop 同blpop,一個是從頭部刪除一個是從尾部刪除
rpoplpush srckey destkey 從srckey對應(yīng)list的尾部移除元素并添加到destkey對應(yīng)list的頭部,最后返回被移除的元素值,整個操作是原子的。如果srckey是空
或者不存在返回nil
4. set
redis的set是string類型的無序集合。set元素最大可以包含(2的32次方-1)個元素。set的是通過hash table實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是O(1)。hash table會隨著添加或者刪除自動的調(diào)整大小。需要注意的是調(diào)整hash table大小時候需要同步(獲取寫鎖)會阻塞其他讀寫操作。可能不久后就會改用跳表(skip list)來實現(xiàn)
跳表已經(jīng)在sorted set中使用了。關(guān)于set集合類型除了基本的添加刪除操作,其他有用的操作還包含集合的取并集(union),交集(intersection),
差集(difference)。通過這些操作可以很容易的實現(xiàn)sns中的好友推薦和blog的tag功能。下面詳細(xì)介紹set相關(guān)命令
sadd key member 添加一個string元素到,key對應(yīng)的set集合中,成功返回1,如果元素以及在集合中返回0,key對應(yīng)的set不存在返回錯誤
srem key member 從key對應(yīng)set中移除給定元素,成功返回1,如果member在集合中不存在或者key不存在返回0,如果key對應(yīng)的不是set類型的值返回錯誤
spop key 刪除并返回key對應(yīng)set中隨機(jī)的一個元素,如果set是空或者key不存在返回nil
srandmember key 同spop,隨機(jī)取set中的一個元素,但是不刪除元素
smove srckey dstkey member 從srckey對應(yīng)set中移除member并添加到dstkey對應(yīng)set中,整個操作是原子的。成功返回1,如果member在srckey中不存在返回0,如果
key不是set類型返回錯誤
scard key 返回set的元素個數(shù),如果set是空或者key不存在返回0
sismember key member 判斷member是否在set中,存在返回1,0表示不存在或者key不存在
sinter key1 key2.。.keyN 返回所有給定key的交集
sinterstore dstkey key1.。.keyN 同sinter,但是會同時將交集存到dstkey下
sunion key1 key2.。.keyN 返回所有給定key的并集
sunionstore dstkey key1.。.keyN 同sunion,并同時保存并集到dstkey下
sdiff key1 key2.。.keyN 返回所有給定key的差集
sdiffstore dstkey key1.。.keyN 同sdiff,并同時保存差集到dstkey下
smembers key 返回key對應(yīng)set的所有元素,結(jié)果是無序的
5 sorted set
和set一樣sorted set也是string類型元素的集合,不同的是每個元素都會關(guān)聯(lián)一個double類型的score。sorted set的實現(xiàn)是skip list和hash table的混合體
當(dāng)元素被添加到集合中時,一個元素到score的映射被添加到hash table中,所以給定一個元素獲取score的開銷是O(1),另一個score到元素的映射被添加到skip list
并按照score排序,所以就可以有序的獲取集合中的元素。添加,刪除操作開銷都是O(log(N))和skip list的開銷一致,redis的skip list實現(xiàn)用的是雙向鏈表,這樣就可以逆序從尾部取元素。sorted set最經(jīng)常的使用方式應(yīng)該是作為索引來使用。我們可以把要排序的字段作為score存儲,對象的id當(dāng)元素存儲。下面是sorted set相關(guān)命令
zadd key score member 添加元素到集合,元素在集合中存在則更新對應(yīng)score
zrem key member 刪除指定元素,1表示成功,如果元素不存在返回0
zincrby key incr member 增加對應(yīng)member的score值,然后移動元素并保持skip list保持有序。返回更新后的score值
zrank key member 返回指定元素在集合中的排名(下標(biāo)),集合中元素是按score從小到大排序的
zrevrank key member 同上,但是集合中元素是按score從大到小排序
zrange key start end 類似lrange操作從集合中去指定區(qū)間的元素。返回的是有序結(jié)果
zrevrange key start end 同上,返回結(jié)果是按score逆序的
zrangebyscore key min max 返回集合中score在給定區(qū)間的元素
zcount key min max 返回集合中score在給定區(qū)間的數(shù)量
zcard key 返回集合中元素個數(shù)
zscore key element 返回給定元素對應(yīng)的score
zremrangebyrank key min max 刪除集合中排名在給定區(qū)間的元素
zremrangebyscore key min max 刪除集合中score在給定區(qū)間的元素
6. hash
redis hash是一個string類型的field和value的映射表。它的添加,刪除操作都是O(1)(平均).hash特別適合用于存儲對象。相較于將對象的每個字段存成
單個string類型。將一個對象存儲在hash類型中會占用更少的內(nèi)存,并且可以更方便的存取整個對象。省內(nèi)存的原因是新建一個hash對象時開始是用zipmap(又稱為small hash)來存儲的。這個zipmap其實并不是hash table,但是zipmap相比正常的hash實現(xiàn)可以節(jié)省不少hash本身需要的一些元數(shù)據(jù)存儲開銷。盡管zipmap的添加,刪除,查找都是O(n),但是由于一般對象的field數(shù)量都不太多。所以使用zipmap也是很快的,也就是說添加刪除平均還是O(1)。如果field或者value的大小超出一定限制后,redis會在內(nèi)部自動將zipmap替換成正常的hash實現(xiàn)。這個限制可以在配置文件中指定
hash-max-zipmap-entries 64 #配置字段最多64個
hash-max-zipmap-value 512 #配置value最大為512字節(jié)
下面介紹hash相關(guān)命令
hset key field value 設(shè)置hash field為指定值,如果key不存在,則先創(chuàng)建
hget key field 獲取指定的hash field
hmget key filed1.。..fieldN 獲取全部指定的hash filed
hmset key filed1 value1 。.. filedN valueN 同時設(shè)置hash的多個field
hincrby key field integer 將指定的hash filed 加上給定值
hexists key field 測試指定field是否存在
hdel key field 刪除指定的hash field
hlen key 返回指定hash的field數(shù)量
hkeys key 返回hash的所有field
hvals key 返回hash的所有value
hgetall 返回hash的所有filed和value
三、 redis學(xué)習(xí)筆記之排序
在了解完各種redis類型后,這次介紹下redis排序命令.redis支持對list,set和sorted set元素的排序。排序命令是sort完整的命令格式如下:
SORT key [BY pattern] [LIMIT start count] [GET pattern] [ASC|DESC] [ALPHA] [STORE dstkey]
下面我們一一說明各種命令選項
(1)sort key
這個是最簡單的情況,沒有任何選項就是簡單的對集合自身元素排序并返回排序結(jié)果。下面給個例子
redis> lpush ml 12
(integer) 1
redis> lpush ml 11
(integer) 2
redis> lpush ml 23
(integer) 3
redis> lpush ml 13
(integer) 4
redis> sort ml
1. “11”
2. “12”
3. “13”
4. “23”
(2)[ASC|DESC] [ALPHA]
sort默認(rèn)的排序方式(asc)是從小到大排的,當(dāng)然也可以按照逆序或者按字符順序排。逆序可以加上desc選項,想按字母順序排可以加alpha選項,當(dāng)然alpha可以和desc一起用。下面是個按字母順序排的例子
redis> lpush mylist baidu
(integer) 1
redis> lpush mylist hello
(integer) 2
redis> lpush mylist xhan
(integer) 3
redis> lpush mylist soso
(integer) 4
redis> sort mylist
1. “soso”
2. “xhan”
3. “hello”
4. “baidu”
redis> sort mylist alpha
1. “baidu”
2. “hello”
3. “soso”
4. “xhan”
redis> sort mylist desc alpha
1. “xhan”
2. “soso”
3. “hello”
4. “baidu”
(3)[BY pattern]
除了可以按集合元素自身值排序外,還可以將集合元素內(nèi)容按照給定pattern組合成新的key,并按照新key中對應(yīng)的內(nèi)容進(jìn)行排序。下面的例子接著使用第一個例子中的ml集合做演示:
redis> set name11 nihao
OK
redis> set name12 wo
OK
redis> set name13 shi
OK
redis> set name23 lala
OK
redis> sort ml by name*
1. “13”
2. “23”
3. “11”
4. “12”
*代表了ml中的元素值,所以這個排序是按照name12 name13 name23 name23這四個key對應(yīng)值排序的,當(dāng)然返回的還是排序后ml集合中的元素
(4)[GET pattern]
上面的例子都是返回的ml集合中的元素。我們也可以通過get選項去獲取指定pattern作為新key對應(yīng)的值??磦€組合起來的例子
redis> sort ml by name* get name* alpha
1. “l(fā)ala”
2. “nihao”
3. “shi”
4. “wo”
這 次返回的就不在是ml中的元素了,而是name12 name13 name23 name23對應(yīng)的值。當(dāng)然排序是按照name12 name13 name23 name23值并根據(jù)字母順序排的。另外get選項可以有多個??蠢樱?特殊符號引用的是原始集合也就是ml)
redis> sort ml by name* get name* get # alpha
1. “l(fā)ala”
2. “23”
3. “nihao”
4. “11”
5. “shi”
6. “13”
7. “wo”
8. “12”
最后在還有一個引用hash類型字段的特殊字符->,下面是例子
redis> hset user1 name hanjie
(integer) 1
redis> hset user11 name hanjie
(integer) 1
redis> hset user12 name 86
(integer) 1
redis> hset user13 name lxl
(integer) 1
redis> sort ml get user*->name
1. “hanjie”
2. “86”
3. “l(fā)xl”
4. (nil)
很容易理解,注意當(dāng)對應(yīng)的user23不存在時候返回的是nil
(5) [LIMIT start count]
上面例子返回結(jié)果都是全部。limit選項可以限定返回結(jié)果的數(shù)量。例子
redis> sort ml get name* limit 1 2
1. “wo”
2. “shi”
start下標(biāo)是從0開始的,這里的limit選項意思是從第二個元素開始獲取2個
(6)[STORE dstkey]
如果對集合經(jīng)常按照固定的模式去排序,那么把排序結(jié)果緩存起來會減少不少cpu開銷。使用store選項可以將排序內(nèi)容保存到指定key中。保存的類型是list
redis> sort ml get name* limit 1 2 store cl
(integer) 2
redis> type cl
list
redis> lrange cl 0 -1
1. “wo”
2. “shi”
這個例子我們將排序結(jié)果保存到了cl中
功能介紹完后,再討論下關(guān)于排序的一些問題。如果我們有多個redis server的話,不同的key可能存在于不同的server上。比如name12 name13 name23 name23,很有可能分別在四個不同的server上存貯著。這種情況會對排序性能造成很大的影響。redis作者在他的blog上提到了這個問題的解 決辦法,就是通過key tag將需要排序的key都放到同一個server上 。由于具體決定哪個key存在哪個服務(wù)器上一般都是在client端hash的辦法來做的。我們可以通過只對key的部分進(jìn)行hash.舉個例子假如我們 的client如果發(fā)現(xiàn)key中包含[]。那么只對key中[]包含的內(nèi)容進(jìn)行hash。我們將四個name相關(guān)的key,都這樣命名[name]12 [name]13 [name]23 [name]23,于是client程序就會把他們都放到同一server上。不知道jredis實現(xiàn)了沒。
還有一個問題也比較嚴(yán)重。如果要sort的集合非常大的話排序就會消耗很長時間。由于redis單線程的,所以長時間的排序操作會阻塞其他client的 請求。解決辦法是通過主從復(fù)制機(jī)制將數(shù)據(jù)復(fù)制到多個slave上。%然后我們只在slave上做排序操作。并進(jìn)可能的對排序結(jié)果緩存。另外就是一個方案是就 是采用sorted set對需要按某個順序訪問的集合建立索引。
四、 redis學(xué)習(xí)筆記之事務(wù)
redis對事務(wù)的支持目前還比較簡單。redis只能保證一個client發(fā)起的事務(wù)中的命令可以連續(xù)的執(zhí)行,而中間不會插入其他client的命令。 由于redis是單線程來處理所有client的請求的所以做到這點是很容易的。一般情況下redis在接受到一個client發(fā)來的命令后會立即處理并 返回處理結(jié)果,但是當(dāng)一個client在一個連接中發(fā)出multi命令有,這個連接會進(jìn)入一個事務(wù)上下文,該連接后續(xù)的命令并不是立即執(zhí)行,而是先放到一 個隊列中。當(dāng)從此連接受到exec命令后,redis會順序的執(zhí)行隊列中的所有命令。并將所有命令的運行結(jié)果打包到一起返回給client.然后此連接就 結(jié)束事務(wù)上下文。下面可以看一個例子
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> exec
1. (integer) 1
2. (integer) 1
從這個例子我們可以看到incr a ,incr b命令發(fā)出后并沒執(zhí)行而是被放到了隊列中。調(diào)用exec后倆個命令被連續(xù)的執(zhí)行,最后返回的是兩條命令執(zhí)行后的結(jié)果
我們可以調(diào)用discard命令來取消一個事務(wù)。接著上面例子
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> discard
OK
redis> get a
“1”
redis> get b
“1”
可以發(fā)現(xiàn)這次incr a incr b都沒被執(zhí)行。discard命令其實就是清空事務(wù)的命令隊列并退出事務(wù)上下文。
雖說redis事務(wù)在本質(zhì)上也相當(dāng)于序列化隔離級別的了。但是由于事務(wù)上下文的命令只排隊并不立即執(zhí)行,所以事務(wù)中的寫操作不能依賴事務(wù)中的讀操作結(jié)果??聪旅胬?/p>
redis> multi
OK
redis> get a
QUEUED
redis> get b
QUEUED
redis> exec
1. “1”
2. “1”
發(fā)現(xiàn)問題了吧。假如我們想用事務(wù)實現(xiàn)incr操作怎么辦?可以這樣做嗎?
redis> get a
“1”
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
“2”
結(jié) 論很明顯這樣是不行的。這樣和 get a 然后直接set a是沒區(qū)別的。很明顯由于get a和set a并不能保證兩個命令是連續(xù)執(zhí)行的(get操作不在事務(wù)上下文中)。很可能有兩個client同時做這個操作。結(jié)果我們期望是加兩次a從原來的1變成3.但是很有可能兩個client的get a,取到都是1,造成最終加兩次結(jié)果卻是2。主要問題我們沒有對共享資源a的訪問進(jìn)行任何的同步
也就是說redis沒提供任何的加鎖機(jī)制來同步對a的訪問。
還好redis 2.1后添加了watch命令,可以用來實現(xiàn)樂觀鎖??磦€正確實現(xiàn)incr命令的例子,只是在前面加了watch a
redis> watch a
OK
redis> get a
“1”
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
“2”
watch 命令會監(jiān)視給定的key,當(dāng)exec時候如果監(jiān)視的key從調(diào)用watch后發(fā)生過變化,則整個事務(wù)會失敗。也可以調(diào)用watch多次監(jiān)視多個key.這 樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連接有效的,事務(wù)也一樣。如果連接斷開,監(jiān)視和事務(wù)都會被自動清除。當(dāng)然了exec,discard,unwatch命令都會清除連接中的所有監(jiān)視。
redis的事務(wù)實現(xiàn)是如此簡單,當(dāng)然會存在一些問題。第一個問題是redis只能保證事務(wù)的每個命令連續(xù)執(zhí)行,但是如果事務(wù)中的一個命令失敗了,并不回滾其他命令,比如使用的命令類型不匹配。
redis> set a 5
OK
redis> lpush b 5
(integer) 1
redis> set c 5
OK
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> incr c
QUEUED
redis> exec
1. (integer) 6
2. (error) ERR Operation against a key holding the wrong kind of value
3. (integer) 6
可以看到雖然incr b失敗了,但是其他兩個命令還是執(zhí)行了。
最 后一個十分罕見的問題是 當(dāng)事務(wù)的執(zhí)行過程中,如果redis意外的掛了。很遺憾只有部分命令執(zhí)行了,后面的也就被丟棄了。當(dāng)然如果我們使用的append-only file方式持久化,redis會用單個write操作寫入整個事務(wù)內(nèi)容。即是是這種方式還是有可能只部分寫入了事務(wù)到磁盤。發(fā)生部分寫入事務(wù)的情況 下,redis重啟時會檢測到這種情況,然后失敗退出??梢允褂胷edis-check-aof工具進(jìn)行修復(fù),修復(fù)會刪除部分寫入的事務(wù)內(nèi)容。修復(fù)完后就 能夠重新啟動了。
五、 redis學(xué)習(xí)筆記之pipeline
redis是一個cs模式的tcp server,使用和http類似的請求響應(yīng)協(xié)議。一個client可以通過一個socket連接發(fā)起多個請求命令。每個請求命令發(fā)出后client通常 會阻塞并等待redis服務(wù)處理,redis處理完后請求命令后會將結(jié)果通過響應(yīng)報文返回給client?;镜?a target="_blank">通信過程如下
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
基 本上四個命令需要8個tcp報文才能完成。由于通信會有網(wǎng)絡(luò)延遲,假如從client和server之間的包傳輸時間需要0.125秒。那么上面的四個命 令8個報文至少會需要1秒才能完成。這樣即使redis每秒能處理100個命令,而我們的client也只能一秒鐘發(fā)出四個命令。這顯示沒有充分利用redis的處理能力。除了可以利用mget,mset之類的單條命令處理多個key的命令外
我們還可以利用pipeline的方式從client打包多條命令一起發(fā)出,不需要等待單條命令的響應(yīng)返回,而redis服務(wù)端會處理完多條命令后會將多條命令的處理結(jié)果打包到一起返回給客戶端。通信過程如下
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
假 設(shè)不會因為tcp 報文過長而被拆分??赡軆蓚€tcp報文就能完成四條命令,client可以將四個incr命令放到一個tcp報文一起發(fā)送,server則可以將四條命令 的處理結(jié)果放到一個tcp報文返回。通過pipeline方式當(dāng)有大批量的操作時候。我們可以節(jié)省很多原來浪費在網(wǎng)絡(luò)延遲的時間。需要注意到是用pipeline方式打包命令發(fā)送,redis必須在處理完所有命令前先緩存起所有命令的處理結(jié)果。打包的命令越多,緩存消耗內(nèi)存也越多。所以并是不是打 包的命令越多越好。具體多少合適需要根據(jù)具體情況測試。下面是個jredis客戶端使用pipeline的測試
package jredisStudy;
import org.jredis.JRedis;
import org.jredis.connector.ConnectionSpec;
import org.jredis.ri.alphazero.JRedisClient;
import org.jredis.ri.alphazero.JRedisPipelineService;
import org.jredis.ri.alphazero.connection.DefaultConnectionSpec;
public class PipeLineTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
usePipeline();
long end = System.currentTimeMillis();
System.out.println(end-start);
start = System.currentTimeMillis();
withoutPipeline();
end = System.currentTimeMillis();
System.out.println(end-start);
}
private static void withoutPipeline()
{
try {
JRedis jredis = new JRedisClient(“192.168.56.55”,6379);
for(int i =0 ; i 《 100000 ; i++)
{
jredis.incr(“test2”);
}
jredis.quit();
} catch (Exception e) {
}
}
private static void usePipeline() {
try {
ConnectionSpec spec = DefaultConnectionSpec.newSpec(“192.168.56.55”, 6379, 0, null);
JRedis jredis = new JRedisPipelineService(spec);
for(int i =0 ; i 《 100000 ; i++)
{
jredis.incr(“test2”);
}
jredis.quit();
} catch (Exception e) {
}
}
}
輸出
103408 //使用了pipeline
104598 //沒有使用
測試結(jié)果不是很明顯,這應(yīng)該是跟我的測試環(huán)境有關(guān)。我是在自己win連接虛擬機(jī)的linux。網(wǎng)絡(luò)延遲比較小。所以pipeline
優(yōu)勢不明顯。如果網(wǎng)絡(luò)延遲小的話,最好還是不用pipeline。除了增加復(fù)雜外,帶來的性能提升不明顯。
六、 redis學(xué)習(xí)筆記之發(fā)布訂閱
發(fā)布訂閱(pub/sub)是一種消息通信模式,主要的目的是解耦消息發(fā)布者和消息訂閱者之間的耦合,這點和設(shè)計模式中的觀察者模式比較相似。pub /sub不僅僅解決發(fā)布者和訂閱者直接代碼級別耦合也解決兩者在物理部署上的耦合。redis作為一個pub/sub server,在訂閱者和發(fā)布者之間起到了消息路由的功能。訂閱者可以通過subscribe和psubscribe命令向redis server訂閱自己感興趣的消息類型,redis將消息類型稱為通道(channel)。當(dāng)發(fā)布者通過publish命令向redis server發(fā)送特定類型的消息時。訂閱該消息類型的全部client都會收到此消息。這里消息的傳遞是多對多的。一個client可以訂閱多個channel,也可以向多個channel發(fā)送消息。
下面做個實驗。這里使用兩個不同的client一個是redis自帶的redis-cli另一個是用java寫的簡單的client。代碼如下
import java.net.*;
import java.io.*;
public class PubSubTest {
public static void main(String[] args) {
String cmd = args[0]+“ ”;
try {
Socket socket = new Socket(“192.168.56.55”,6379);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(cmd.getBytes()); //發(fā)送訂閱命令
byte[] buffer = new byte[1024];
while (true) {
int readCount = in.read(buffer);
System.out.write(buffer, 0, readCount);
System.out.println(“--------------------------------------”);
}
} catch (Exception e) {
}
}
}
代碼就是簡單的從命令行讀取傳過來的訂閱命令,然后通過一個socket連接發(fā)送給redis server,然后進(jìn)入while循環(huán)一直讀取redis server傳過來訂閱的消息。并打印到控制臺
1 首先編譯并運行此java程序(我是win7下面運行的)
D:>javac PubSubTest.java
D:>java PubSubTest “subscribe news.share news.blog”
*3
$9
subscribe
$10
news.share
:1
*3
$9
subscribe
$9
news.blog
:2
--------------------------------------
2 啟動redis-cli
redis> psubscribe news.*
Reading messages.。. (press Ctrl-c to quit)
1. “psubscribe”
2. “news.*”
3. (integer) 1
3 再啟動一個redis-cli用來發(fā)布兩條消息
redis> publish news.share “share a link http://www.google.com”
(integer) 2
redis> publish news.blog “I post a blog”
(integer) 2
4.查看兩個訂閱client的輸出
此時java client打印如下內(nèi)容
*3
$7
message
$10
news.share
$34
share a link http://www.google.com
--------------------------------------
*3
$7
message
$9
news.blog
$13
I post a blog
--------------------------------------
另一個redis-cli輸出如下
1. “pmessage”
2. “news.*”
3. “news.share”
4. “share a link http://www.google.com”
1. “pmessage”
2. “news.*”
3. “news.blog”
4. “I post a blog”
分析下
java client使用subscribe命令訂閱news.share和news.blog兩個通道,然后立即收到server返回的訂閱成功消息,可以看出redis的協(xié)議是文本類型的,這里不解釋具體協(xié)議內(nèi)容了,可以參考http://redis.io/topics/protocol或者h(yuǎn)ttp://terrylee.me/blog/post/2011/01/26/redis-internal-part3.aspx。這個報文內(nèi)容有兩部分,第一部分表示該socket連接上使用subscribe訂閱news.share成功后,此連接訂閱通道數(shù)為1,后一部分表示使用subscribe訂閱news.blog成功后,該連接訂 閱通道總數(shù)為2。
redis client使用psubscribe訂閱了一個使用通配符的通道(*表示任意字符串),此訂閱會收到所有與news.*匹配的通道消息。redis- cli打印到控制臺的訂閱成功消息表示使用psubscribe命令訂閱news.*成功后,連接訂閱通道總數(shù)為1。
當(dāng)我們在一個client使用publish向news.share和news.blog通道發(fā)出兩個消息后。redis返回的(integer) 2表示有兩個連接收到了此消息。通過觀察兩個訂閱者的輸出可以驗證。具體格式不解釋了,都比較簡單。
看 完一個小例子后應(yīng)該對pub/sub功能有了一個感性的認(rèn)識。需要注意的是當(dāng)一個連接通過subscribe或者psubscribe訂閱通道后就進(jìn)入訂 閱模式。在這種模式除了再訂閱額外的通道或者用unsubscribe或者punsubscribe命令退出訂閱模式,就不能再發(fā)送其他命令。另外使用psubscribe命令訂閱多個通配符通道,如果一個消息匹配上了多個通道模式的話,會多次收到同一個消息。
jredis目前版本沒提供pub/sub支持,不過自己實現(xiàn)一個應(yīng)該也挺簡單的。整個應(yīng)用程序可以共享同一個連接。因為redis返回的消息報文中除了消息內(nèi)容本身外還包括消息相關(guān)的通道信息,當(dāng)收到消息后可以根據(jù)不同的通道信息去調(diào)用不同的callback來處理。
另外個人覺得redis的pub/sub還是有點太單?。▽崿F(xiàn)才用150行代碼)。在安全,認(rèn)證,可靠性這方便都沒有太多支持。
七、 redis學(xué)習(xí)筆記之持久化
redis是一個支持持久化的內(nèi)存數(shù)據(jù)庫,也就是說redis需要經(jīng)常將內(nèi)存中的數(shù)據(jù)同步到磁盤來保證持久化。redis支持兩種持久化方式,一種是Snapshotting(快照)也是默認(rèn)方式,另一種是Append-only file(縮寫aof)的方式。下面分別介紹
Snapshotting
快照是默認(rèn)的持久化方式。這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb。可以通過配置設(shè)置自動做快照持久 化的方式。我們可以配置redis在n秒內(nèi)如果超過m個key被修改就自動做快照,下面是默認(rèn)的快照保存配置
save 900 1 #900秒內(nèi)如果超過1個key被修改,則發(fā)起快照保存
save 300 10 #300秒內(nèi)容如超過10個key被修改,則發(fā)起快照保存
save 60 10000
下面介紹詳細(xì)的快照保存過程
1.redis調(diào)用fork,現(xiàn)在有了子進(jìn)程和父進(jìn)程。
2. 父進(jìn)程繼續(xù)處理client請求,子進(jìn)程負(fù)責(zé)將內(nèi)存內(nèi)容寫入到臨時文件。由于os的寫時復(fù)制機(jī)制(copy on write)父子進(jìn)程會共享相同的物理頁面,當(dāng)父進(jìn)程處理寫請求時os會為父進(jìn)程要修改的頁面創(chuàng)建副本,而不是寫共享的頁面。所以子進(jìn)程的地址空間內(nèi)的數(shù) 據(jù)是fork時刻整個數(shù)據(jù)庫的一個快照。
3.當(dāng)子進(jìn)程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進(jìn)程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由于redis是用一個主線程來處理所有client的請求,這種方式會阻塞所有client請求。所以不推薦使用。另一點需要注意的是,每次快照持久化都是將內(nèi)存數(shù)據(jù)完整寫入到磁盤一次,并不 是增量的只同步臟數(shù)據(jù)。如果數(shù)據(jù)量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴(yán)重影響性能。
另外由于快照方式是在一定間隔時間做一次的,所以如果redis意外down掉的話,就會丟失最后一次快照后的所有修改。如果應(yīng)用要求不能丟失任何修改的話,可以采用aof持久化方式。下面介紹
Append-only file
aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式時,redis會將每一個收到的寫命令都通過write函數(shù)追加到文件中(默認(rèn)是appendonly.aof)。當(dāng)redis重啟時會通過重新執(zhí)行文件中保存的寫命令來在內(nèi)存中重建整個數(shù)據(jù)庫的內(nèi)容。當(dāng)然由于os會在內(nèi)核中緩存write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過配置文件告訴redis我們想要 通過fsync函數(shù)強(qiáng)制os寫入到磁盤的時機(jī)。有三種方式如下(默認(rèn)是:每秒fsync一次)
appendonly yes //啟用aof持久化方式
# appendfsync always //每次收到寫命令就立即強(qiáng)制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec //每秒鐘強(qiáng)制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
# appendfsync no //完全依賴os,性能最好,持久化沒保證
aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調(diào)用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多余的。因為要恢復(fù)數(shù)據(jù)庫的狀態(tài)其實文件中保存一條set test 100就夠了。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內(nèi)存中的數(shù)據(jù) 以命令的方式保存到臨時文件中,最后替換原來的文件。具體過程如下
1. redis調(diào)用fork,現(xiàn)在有父子兩個進(jìn)程
2. 子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫快照,往臨時文件中寫入重建數(shù)據(jù)庫狀態(tài)的命令
3.父進(jìn)程繼續(xù)處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進(jìn)程重寫失敗的話并不會出問題。
4.當(dāng)子進(jìn)程把快照內(nèi)容寫入已命令方式寫到臨時文件中后,子進(jìn)程發(fā)信號通知父進(jìn)程。然后父進(jìn)程把緩存的寫命令也寫入到臨時文件。
5.現(xiàn)在父進(jìn)程可以使用臨時文件替換老的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。
需要注意到是重寫aof文件的操作,并沒有讀取舊的aof文件,而是將整個內(nèi)存中的數(shù)據(jù)庫內(nèi)容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。
八、 redis學(xué)習(xí)筆記之主從復(fù)制
redis主從復(fù)制配置和使用都非常簡單。通過主從復(fù)制可以允許多個slave server擁有和master server相同的數(shù)據(jù)庫副本。下面是關(guān)于redis主從復(fù)制的一些特點
1.master可以有多個slave
2.除了多個slave連到相同的master外,slave也可以連接其他slave形成圖狀結(jié)構(gòu)
3.主從復(fù)制不會阻塞master。也就是說當(dāng)一個或多個slave與master進(jìn)行初次同步數(shù)據(jù)時,master可以繼續(xù)處理client發(fā)來的請求。相反slave在初次同步數(shù)據(jù)時則會阻塞不能處理client的請求。
4.主從復(fù)制可以用來提高系統(tǒng)的可伸縮性,我們可以用多個slave專門用于client的讀請求,比如sort操作可以使用slave來處理。也可以用來做簡單的數(shù)據(jù)冗余
5.可以在master禁用數(shù)據(jù)持久化,只需要注釋掉master配置文件中的所有save配置,然后只在slave上配置數(shù)據(jù)持久化。
下面介紹下主從復(fù)制的過程
當(dāng)設(shè)置好slave服務(wù)器后,slave會建立和master的連接,然后發(fā)送sync命令。無論是第一次同步建立的連接還是連接斷開后的重新連 接,master都會啟動一個后臺進(jìn)程,將數(shù)據(jù)庫快照保存到文件中,同時master主進(jìn)程會開始收集新的寫命令并緩存起來。后臺進(jìn)程完成寫文件 后,master就發(fā)送文件給slave,slave將文件保存到磁盤上,然后加載到內(nèi)存恢復(fù)數(shù)據(jù)庫快照到slave上。接著master就會把緩存的命 令轉(zhuǎn)發(fā)給slave。而且后續(xù)master收到的寫命令都會通過開始建立的連接發(fā)送給slave。從master到slave的同步數(shù)據(jù)的命令和從client發(fā)送的命令使用相同的協(xié)議格式。當(dāng)master和slave的連接斷開時slave可以自動重新建立連接。如果master同時收到多個slave發(fā)來的同步連接命令,只會使用啟動一個進(jìn)程來寫數(shù)據(jù)庫鏡像,然后發(fā)送給所有slave。
配置slave服務(wù)器很簡單,只需要在配置文件中加入如下配置
slaveof 192.168.1.1 6379 #指定master的ip和端口
九、 redis學(xué)習(xí)筆記之虛擬內(nèi)存
首先說明下redis的虛擬內(nèi)存與os的虛擬內(nèi)存不是一碼事,但是思路和目的都是相同的。就是暫時把不經(jīng)常訪問的數(shù)據(jù)從內(nèi)存交換到磁盤中,從而騰出寶貴的 內(nèi)存空間用于其他需要訪問的數(shù)據(jù)。尤其是對于redis這樣的內(nèi)存數(shù)據(jù)庫,內(nèi)存總是不夠用的。除了可以將數(shù)據(jù)分割到多個redis server外。另外的能夠提高數(shù)據(jù)庫容量的辦法就是使用vm把那些不經(jīng)常訪問的數(shù)據(jù)交換的磁盤上。如果我們的存儲的數(shù)據(jù)總是有少部分?jǐn)?shù)據(jù)被經(jīng)常訪問,大 部分?jǐn)?shù)據(jù)很少被訪問,對于網(wǎng)站來說確實總是只有少量用戶經(jīng)常活躍。當(dāng)少量數(shù)據(jù)被經(jīng)常訪問時,使用vm不但能提高單臺redis server數(shù)據(jù)庫的容量,而且也不會對性能造成太多影響。
redis沒有使用os提供的虛擬內(nèi)存機(jī)制而是自己在用戶態(tài)實現(xiàn)了自己的虛擬內(nèi)存機(jī)制,作者在自己的blog專門解釋了其中原因。http://antirez.com/post/redis-virtual-memory-story.html
主要的理由有兩點
1.os 的虛擬內(nèi)存是已4k頁面為最小單位進(jìn)行交換的。而redis的大多數(shù)對象都遠(yuǎn)小于4k,所以一個os頁面上可能有多個redis對象。另外redis的集 合對象類型如list,set可能存在與多個os頁面上。最終可能造成只有10%key被經(jīng)常訪問,但是所有os頁面都會被os認(rèn)為是活躍的,這樣只有內(nèi) 存真正耗盡時os才會交換頁面。
2.相比于os的交換方式。redis可以將被交換到磁盤的對象進(jìn)行壓縮,保存到磁盤的對象可以去除指針和對象元數(shù)據(jù)信息。一般壓縮后的對象會比內(nèi)存中的對象小10倍。這樣redis的vm會比os vm能少做很多io操作。
下面是vm相關(guān)配置
vm-enabled yes #開啟vm功能
vm-swap-file /tmp/redis.swap #交換出來的value保存的文件路徑/tmp/redis.swap
vm-max-memory 1000000 #redis使用的最大內(nèi)存上限,超過上限后redis開始交換value到磁盤文件中。
vm-page-size 32 #每個頁面的大小32個字節(jié)
vm-pages 134217728 #最多使用在文件中使用多少頁面,交換文件的大小= vm-page-size * vm-pages
vm-max-threads 4 #用于執(zhí)行value對象換入換出的工作線程數(shù)量。0表示不使用工作線程(后面介紹)
redis的vm在設(shè)計上為了保證key的查找速度,只會將value交換到swap文件中。所以如果是內(nèi)存問題是由于太多value很小的key造成 的,那么vm并不能解決。和os一樣redis也是按頁面來交換對象的。redis規(guī)定同一個頁面只能保存一個對象。但是一個對象可以保存在多個頁面中。 在redis使用的內(nèi)存沒超過vm-max-memory之前是不會交換任何value的。當(dāng)超過最大內(nèi)存限制后,redis會選擇較老的對象。如果兩個 對象一樣老會優(yōu)先交換比較大的對象,精確的公式swappability = age*log(size_in_memory)。 對于vm-page-size的設(shè)置應(yīng)該根據(jù)自己的應(yīng)用將頁面的大小設(shè)置為可以容納大多數(shù)對象的大小。太大了會浪費磁盤空間,太小了會造成交換文件出現(xiàn)碎 片。對于交換文件中的每個頁面,redis會在內(nèi)存中對應(yīng)一個1bit值來記錄頁面的空閑狀態(tài)。所以像上面配置中頁面數(shù)量(vm-pages 134217728 )會占用16M內(nèi)存用來記錄頁面空閑狀態(tài)。vm-max-threads表示用做交換任務(wù)的線程數(shù)量。如果大于0推薦設(shè)為服務(wù)器的cpu core的數(shù)量。如果是0則交換過程在主線程進(jìn)行。
參數(shù)配置討論完后,在來簡單介紹下vm是如何工作的,
當(dāng)vm-max-threads設(shè)為0時(Blocking VM)
換出
主線程定期檢查發(fā)現(xiàn)內(nèi)存超出最大上限后,會直接已阻塞的方式,將選中的對象保存到swap文件中,并釋放對象占用的內(nèi)存,此過程會一直重復(fù)直到下面條件滿足
1.內(nèi)存使用降到最大限制以下
2.swap文件滿了
3.幾乎全部的對象都被交換到磁盤了
換入
當(dāng)有client請求value被換出的key時。主線程會以阻塞的方式從文件中加載對應(yīng)的value對象,加載時此時會阻塞所以client。然后處理client的請求
當(dāng)vm-max-threads大于0(Threaded VM)
換出
當(dāng)主線程檢測到使用內(nèi)存超過最大上限,會將選中的要交換的對象信息放到一個隊列中交由工作線程后臺處理,主線程會繼續(xù)處理client請求。
換入
如果有client請求的key被換出了,主線程先阻塞發(fā)出命令的client,然后將加載對象的信息放到一個隊列中,讓工作線程去加載。加載完畢后工作線程通知主線程。主線程再執(zhí)行client的命令。這種方式只阻塞請求value被換出key的client
總的來說blocking vm的方式總的性能會好一些,因為不需要線程同步,創(chuàng)建線程和恢復(fù)被阻塞的client等開銷。但是也相應(yīng)的犧牲了響應(yīng)性。threaded vm的方式主線程不會阻塞在磁盤io上,所以響應(yīng)性更好。如果我們的應(yīng)用不太經(jīng)常發(fā)生換入換出,而且也不太在意有點延遲的話則推薦使用blocking vm的方式。關(guān)于redis vm的更詳細(xì)介紹可以參考下面鏈接
-
Redis
+關(guān)注
關(guān)注
0文章
376瀏覽量
10898
發(fā)布評論請先 登錄
相關(guān)推薦
評論