Redis系列之(一)——開發(fā)基礎(chǔ)

<meta charset="utf-8">

一、安裝與配置

#將Redis的相關(guān)運行文件放到/usr/local/bin/下,這樣就可以在任意目錄下執(zhí)行Redis的命令
#啟動
redis-server /opt/redis/redis.conf
#命令行客戶端
redis-cli -h 127.0.0.1 -p 6379
#停止服務(wù),nosave|save參數(shù)表示是否關(guān)閉前生成持久化文件
redis-cli shutdown nosave|save

image

在配置文件redis.conf中,默認的bind 接口是127.0.0.1,也就是本地回環(huán)地址。
這樣的話,訪問redis服務(wù)只能通過本機的客戶端連接,而無法通過遠程連接,
這樣可以避免將redis服務(wù)暴露于危險的網(wǎng)絡(luò)環(huán)境中,防止一些不安全的人隨隨便便通過遠程
連接到redis服務(wù)。如果bind選項為空的話,那會接受所有來自于可用網(wǎng)絡(luò)接口的連接。


#bind
#bind 127.0.0.1
#protected-mode
protected-mode no

處理異常
Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 
3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 
4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

二、理解redis

1、redis數(shù)據(jù)結(jié)構(gòu)

redis數(shù)據(jù)結(jié)構(gòu)有:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。
每種數(shù)據(jù)結(jié)構(gòu)內(nèi)部都有多種實現(xiàn)編碼,這樣好處是:
不同使用場景下redis可以采用不同內(nèi)部實現(xiàn)編碼,達到節(jié)省內(nèi)存和提高性能的目的;
另外,對外的數(shù)據(jù)結(jié)構(gòu)屏蔽了內(nèi)部編碼的不同實現(xiàn),引入新的內(nèi)部實現(xiàn)編碼不影響開發(fā)人員的使用。

image
image

redis3.2關(guān)于list的內(nèi)部編碼增加了quicklist

127.0.0.1:6379> object encoding mylist
"quicklist"

2、redis架構(gòu):內(nèi)存-單線程-I/O多路復用

reids性能之道:
第一,純內(nèi)存訪問
第二,非阻塞I/O
第三,單線程避免了線程切換和競態(tài)產(chǎn)生的消耗
注意:如果某個命令執(zhí)行過長,會造成其他命令的阻塞,對于Redis這種高性能的服務(wù)來說是致命的,所以Redis是面向快速執(zhí)行場景的數(shù)據(jù)庫。
另外,為了性能(對key做hash)和節(jié)省空間,redis的key設(shè)計盡量簡短。

I/O多路復用詳解:

image

blocking I/O:如果當前文件不可讀或不可寫,整個服務(wù)處于阻塞狀態(tài),不會對其它的操作作出響應。

image

I/O 多路復用模型中,最重要的函數(shù)調(diào)用就是 select,該方法的能夠同時監(jiān)控多個文件描述符的可讀可寫情況,當其中的某些文件描述符可讀或者可寫時,select 方法就會返回可讀以及可寫的文件描述符個數(shù)。

image

Reactor 設(shè)計模式是事件驅(qū)動的,I/O 多路復用模塊同時監(jiān)聽多個文件描述FD,當 accept、read、write 、close 文件事件產(chǎn)生時,文件事件處理器就會回調(diào)文件描述FD綁定的事件處理器handler。
Reactor設(shè)計模式實現(xiàn)了代碼的解耦、模塊化、提供復用性、控制并發(fā)粒度等,在性能上減少每個client創(chuàng)建線程查詢select返回,提高性能。

image

三、數(shù)據(jù)結(jié)構(gòu)和內(nèi)部編碼

1、字符串String

image

(1)、set:
ex seconds:為鍵設(shè)置秒級過期時間。
px milliseconds:為鍵設(shè)置毫秒級過期時間。
nx:鍵必須不存在,才可以設(shè)置成功,用于添加。使用場景有分布式鎖。
xx:與nx相反,鍵必須存在,才可以設(shè)置成功,用于更新。
setex和setnx兩個命令等同于ex、nx選項

127.0.0.1:6379> set test1 100 ex 300
"ok"
127.0.0.1:6379> set test1 200 nx
"nil"

mset、mget用于批量操作。
(2)、incr key:
incr命令用于對值做自增操作,返回結(jié)果分為三種情況:
?值不是整數(shù),返回錯誤。
?值是整數(shù),返回自增后的結(jié)果。
?鍵不存在,按照值為0自增,返回結(jié)果為1。
可用于分布式全局ID,如時間戳+redis自增ID。
redis單線程架構(gòu),完全避免使用CAS、同步解決并發(fā)問題。

Long id = jedis.incr(key);
 if (id > max) {
      jedis.set(key, "0");
}
return System.currentTimeMillis() +  String.format("%0" + length + "d", id);

其他計數(shù)命令
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
(3)、getset key value命令:設(shè)置并返回原值
getset和set一樣會設(shè)置值,但是不同的是,它同時會返回鍵原來的值。
(4)、內(nèi)部編碼
?int:8個字節(jié)的長整型。
?embstr:小于等于39個字節(jié)的字符串。
?raw:大于39個字節(jié)的字符串。
(5)
字符串最大大小為512M。

2、哈希hash

image
set user:1:name   tom
set user:1:age   23
set user:1:city   beijing

set user:1   serialize(userInfo)

hmset user:1   name tom age 23 city beijing

一般使用哈希存儲對象,比使用多個key(占用過多的鍵,內(nèi)存占用量較大,同時用戶信息內(nèi)聚性比較差)或者存儲對象序列化后字符串要更好(序列化和反序列化有一定的開銷,同時每次更新屬性都需要把全部數(shù)據(jù)取出進行反序列化,更新后再序列化到Redis中)
注意:
在使用hgetall時,如果哈希元素個數(shù)比較多,會存在阻塞Redis的可能。如果開發(fā)人員只需要獲取部分field,可以使用hmget,如果一定要獲取全部field-value,可以使用hscan命令,該命令會漸進式遍歷哈希類型。

//HSCAN 命令用于迭代哈希鍵中的鍵值對。  
        Map<String,String> data = new HashMap<>();  
        for(int i=0;i<1000;i++){  
            data.put("key"+i,String.valueOf(i));  
        }  
        jedis.hmset("hash",data);  
        ScanResult<Map.Entry<String, String>> result;// =  jedis.hscan("hash",DATASOURCE_SELECT);  
        int count = 0;  
        int cursor = 0;  
        do {  
            result = jedis.hscan("hash",cursor);  
            cursor = Integer.valueOf(result.getStringCursor());  
            for (Map.Entry<String, String> map : result.getResult()) {  
                System.out.println(map.getKey() + ":" + map.getValue());  
                count++;  
            }  
        }  
        while(cursor!=0);  

Redis為解決諸如keys、hgetall、smembers、zrange可能產(chǎn)生的阻塞問題。對應的命令分別是scan、hscan(哈希)、sscan(集合)、zscan(有序集合),它們的用法基本類似。不過如果在scan的過程中如果有鍵的變化(增加、刪除、修改),那么遍歷效果可能會碰到如下問題:新增的鍵可能沒有遍歷到,遍歷出了重復的鍵等情況。

3、列表list

(1)、命令
list以對列表兩端插入(push)和彈出(pop),還可以獲取指定范圍的元素列表、獲取指定索引下標的元素等。列表是一種比較靈活的數(shù)據(jù)結(jié)構(gòu),可以充當棧和隊列的角色。

image
image
image
linsert key before|after pivot value

//列出所有
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"

lrem key count valuelrem命令會從列表中找到等于value的元素進行刪除,
根據(jù)count的不同分為三種情況:   ?count>0,從左到右,刪除最多count個元素。
?count<0,從右到左,刪除最多count絕對值個元素。   
?count=0,刪除所有。
下面操作將從列表左邊開始刪除4個為a的元素:
127.0.0.1:6379> lrem listkey 4 a
(integer) 4

blpop和brpop是lpop和rpop的阻塞版本,如果timeout=3,那么客戶端要等到3秒后返回,如果timeout=0,那么客戶端一直阻塞等下去:
127.0.0.1:6379> brpop list:test 3
(nil)

(2)、使用場景
?lpush+lpop=Stack(棧)
?lpush+rpop=Queue(隊列)
?lpsh+ltrim=Capped Collection(有限集合)
?lpush+brpop=Message Queue(消息隊列,生產(chǎn)者客戶端使用lrpush從列表左側(cè)插入元素,多個消費者客戶端使用brpop命令阻塞式的“搶”列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。)

(3)、內(nèi)部編碼
ziplist(壓縮列表):當列表的元素個數(shù)小于list-max-ziplist-entries配置(默認512個),同時列表中每個元素的值都小于list-max-ziplist-value配置時(默認64字節(jié)),Redis會選用ziplist來作為列表的內(nèi)部實現(xiàn)來減少內(nèi)存的使用。
linkedlist(鏈表):當列表類型無法滿足ziplist的條件時,Redis會使用linkedlist作為列表的內(nèi)部實現(xiàn)。
quicklist:簡單地說它是以一個ziplist為節(jié)點的linkedlist,它結(jié)合了ziplist和linkedlist兩者的優(yōu)勢。
http://zhangtielei.com/posts/blog-redis-quicklist.html

image

quicklistLZF結(jié)構(gòu)表示一個被壓縮過的ziplist

4、集合(set)

(1)、集合(set)類型也是用來保存多個的字符串元素,但和列表類型不一樣的是,集合中不允許有重復元素,并且集合中的元素是無序的,不能通過索引下標獲取元素。

image
image
獲取所有元素smembers key

(2)、內(nèi)部編碼
?intset(整數(shù)集合):當集合中的元素都是整數(shù)且元素個數(shù)小于set-max-intset-entries配置(默認512個)時,Re-dis會選用intset來作為集合的內(nèi)部實現(xiàn),從而減少內(nèi)存的使用。
?hashtable(哈希表):當集合類型無法滿足intset的條件時,Redis會使用hashtable作為集合的內(nèi)部實現(xiàn)。
(3)、使用場景
?sadd=Tagging(標簽)
?spop/srandmember=Random item(生成隨機數(shù),比如抽獎)
?sadd+sinter=Social Graph(社交需求)

5、有序集合zset

(1)、有序集合保留了集合不能有重復成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標作為排序依據(jù)不同的是,它給每個元素設(shè)置一個分數(shù)(score)作為排序的依據(jù)。

image
image
返回指定排名范圍的成員
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"

返回指定分數(shù)范圍成員個數(shù)
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"

(2)、內(nèi)部編碼
ziplist(壓縮列表):當有序集合的元素個數(shù)小于zset-max-ziplist-entries配置(默認128個),同時每個元素的值都小于zset-max-ziplist-value配置(默認64字節(jié))時,Redis會用ziplist來作為有序集合的內(nèi)部實現(xiàn),zi-plist可以有效減少內(nèi)存的使用。
skiplist(跳躍表):當ziplist條件不滿足時,有序集合會使用skiplist作為內(nèi)部實現(xiàn),因為此時ziplist的讀寫效率會下降。
(3)、有序集合比較典型的使用場景就是排行榜系統(tǒng)。

6、其他命令

rename key newkey
//為了防止被強行rename,Redis提供了renamenx命令,確保只有newKey不存在時候才被覆蓋,renamex返回0表示newkey存在未完成重命名。
keys \* 輸出所有key(注意,keys命令會遍歷所有key,算法復雜度O(n),生成環(huán)境禁止使用)。
dbsize 獲取key的總數(shù)(算法復雜度O(1))
exists key檢查鍵是否存在,存在返回1否則返回0

?expire key seconds:鍵在seconds秒后過期。   
?expireat key timestamp:鍵在秒級時間戳timestamp后過期。
?pexpire key milliseconds:鍵在milliseconds毫秒后過期。   
?pexpireat key milliseconds-timestamp鍵在毫秒級時間戳timestamp后過期。

對于字符串類型鍵,執(zhí)行set命令會去掉過期時間,
127.0.0.1:6379> expire hello 50
(integer) 1
127.0.0.1:6379> ttl hello(integer) 
46
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> ttl hello(integer) 
-1

flushdb/flushall
命令用于清除數(shù)據(jù)庫,兩者的區(qū)別的是flushdb只清除當前數(shù)據(jù)庫,flushall會清除所有數(shù)據(jù)庫。

dump+restore命令在Redis實例之間遷移數(shù)據(jù)

image

migrate命令在Redis實例之間原子性的遷移數(shù)據(jù)

image

HyperLogLog
完成數(shù)據(jù)統(tǒng)計,占用空間很少,不過有80%左右誤差。

127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
(integer) 1
//uuid-90新增
127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-90"
(integer) 1
127.0.0.1:6379> pfcount 2016_03_06:unique:ids
(integer) 5

7、redis運維命令

(1)、慢查詢
redis提供以下兩種命令記錄慢查詢?nèi)罩尽?/p>

config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite

slowlog-log-slower-than為預設(shè)閥值,它的單位是微秒(1秒=1000毫秒=1000000微秒),默認值是10000,即10毫秒。(如果slowlog-log-slower-than=0會記錄所有的命令,slowlog-log-slower-than<0對于任何命令都不會進行記錄。)在高并發(fā)情況下,建議設(shè)置1毫秒
slowlog-max-len慢查詢?nèi)罩咀疃鄺l數(shù),以列表方式存儲內(nèi)存。線上建議1000以上,并不會占用過多內(nèi)存。
慢查詢只記錄命令執(zhí)行時間,并不包括命令排隊和網(wǎng)絡(luò)傳輸時間。

image
#獲取慢查詢?nèi)罩?,可選n條
slowlog get [n]
#慢查詢條數(shù)
slowlog len
#重置慢查詢
slowlog rset

CacheCloud提供更強大的redis運維功能:http://www.ywnds.com/?p=10610
(2)、redis-cli --bigkeys統(tǒng)計redis數(shù)據(jù)大小

[root@localhost ~]# redis-cli --bigkeys

# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).

[00.00%] Biggest zset   found so far 'myrank' with 3 members
[00.00%] Biggest set    found so far 'user:test' with 2 members
[00.00%] Biggest string found so far 'test' with 5 bytes
[00.00%] Biggest hash   found so far 'testmap' with 3 fields
[00.00%] Biggest list   found so far 'mylist' with 5 items
[00.00%] Biggest set    found so far 'myset' with 3 members

-------- summary -------

Sampled 8 keys in the keyspace!
Total key length in bytes is 61 (avg len 7.62)

Biggest string found 'test' has 5 bytes
Biggest   list found 'mylist' has 5 items
Biggest    set found 'myset' has 3 members
Biggest   hash found 'testmap' has 3 fields
Biggest   zset found 'myrank' has 3 members

1 strings with 5 bytes (12.50% of keys, avg size 5.00)
1 lists with 5 items (12.50% of keys, avg size 5.00)
4 sets with 7 members (50.00% of keys, avg size 1.75)
1 hashs with 3 fields (12.50% of keys, avg size 3.00)
1 zsets with 3 members (12.50% of keys, avg size 3.00)

(3)、stat
--stat選項可以實時獲取Redis的重要統(tǒng)計信息,雖然info命令中的統(tǒng)計信息更全,但是能實時看到一些增量的數(shù)據(jù)。

[root@localhost ~]# redis-cli --stat
------- data ------ --------------------- load -------------------- - child -
keys       mem      clients blocked requests            connections
8          901.15K  3       0       282 (+0)            18
8          901.15K  3       0       284 (+2)            18
8          901.15K  3       0       286 (+2)            18
8          901.15K  3       0       288 (+2)            18
8          901.15K  3       0       290 (+2)            18
8          901.15K  3       0       291 (+1)            18
8          901.15K  3       0       292 (+1)            18

(4)、info Commandstats
命令平均耗時使用info Commandstats命令獲取,包含每個命令調(diào)用次數(shù)、總耗時、平均耗時,單位為微秒。

127.0.0.1:6379> info Commandstats
# Commandstats
cmdstat_hmset:calls=1,usec=12,usec_per_call=12.00
cmdstat_hincrby:calls=2,usec=30,usec_per_call=15.00
cmdstat_scan:calls=3,usec=46,usec_per_call=15.33
cmdstat_srem:calls=1,usec=16,usec_per_call=16.00
cmdstat_dbsize:calls=4,usec=3,usec_per_call=0.75
cmdstat_publish:calls=5,usec=15,usec_per_call=3.00
cmdstat_llen:calls=4,usec=9,usec_per_call=2.25
cmdstat_lpush:calls=2,usec=25,usec_per_call=12.50
cmdstat_hlen:calls=3,usec=4,usec_per_call=1.33
cmdstat_scard:calls=12,usec=6,usec_per_call=0.50
cmdstat_command:calls=11,usec=71675,usec_per_call=6515.91
cmdstat_hkeys:calls=2,usec=23,usec_per_call=11.50
cmdstat_subscribe:calls=2,usec=7,usec_per_call=3.50
cmdstat_hdel:calls=2,usec=11,usec_per_call=5.50

四、pipeline

image

pipeline批量命令節(jié)省網(wǎng)絡(luò)交互時間,相對原生批量命令(mget等)是原子的,Pipeline是非原子的。 原生批量命令是Redis服務(wù)端支持實現(xiàn)的,而Pipeline需要服務(wù)端和客戶端的共同實現(xiàn)。
Lua腳本:
?Lua腳本在Redis中是原子執(zhí)行的,執(zhí)行過程中間不會插入其他命令。
?Lua腳本可以幫助開發(fā)和運維人員創(chuàng)造出自己定制的命令,并可以將這些命令常駐在Redis內(nèi)存中,實現(xiàn)復用的效果。
?Lua腳本可以將多條命令一次性打包,有效地減少網(wǎng)絡(luò)開銷。

Pipeline pi = jedis.pipelined();
//多個操作..
pi.sync();

redis集群下pipeline:
如果集群是由客戶端做一致性hash(如shardingJedis),使用pipeline前需要對操作按hash算法做分組。
redisCluster目前不支持pipeline,解決方案參考:http://blog.csdn.net/youaremoon/article/details/51751991。

五、事務(wù)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd testTran test1
QUEUED
127.0.0.1:6379> sadd testTran test2
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember testTran  test1//exec執(zhí)行之前操作返回0
(integer) 1

redis事務(wù)并不支持回滾,同時無法實現(xiàn)命令之間的邏輯關(guān)系計算。
redis提供watch命令,如果事務(wù)執(zhí)行中key被改的過,則事務(wù)不執(zhí)行(exec結(jié)果為nil)。

#T1:客戶端1
127.0.0.1:6379> set key "java"
OK
#T2:客戶端1
127.0.0.1:6379> watch key
OK
#T3:客戶端1
127.0.0.1:6379> multi 
OK
#T4:客戶端2
127.0.0.1:6379> append key python
(integer) 11
#T5:客戶端1
127.0.0.1:6379> append key jedis
QUEUED
#T6:客戶端1
127.0.0.1:6379> exec
(nil)
#T7:客戶端1
127.0.0.1:6379> get key
"javapython"

六、發(fā)布與訂閱

image
#T1
127.0.0.1:6379> publish channel:test "hello"
#T2
127.0.0.1:6379> subscribe channel:test
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:test"
3) "hello"
#T2取消訂閱
127.0.0.1:6379> unsubscribe channel:test

#當前channel:sports頻道的訂閱數(shù)為1:
127.0.0.1:6379> pubsub numsub channel:test
1) "channel:test"
2) (integer) 1

按模式匹配訂閱
psubscribe pattern [pattern...]
punsubscribe [pattern [pattern ...]]

七、redis客戶端

作者:康康不遛貓
鏈接:http://www.itdecent.cn/p/7b5a82d9cceb
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、安裝與配置 在配置文件redis.conf中,默認的bind 接口是127.0.0.1,也就是本地回環(huán)地址。這...
    康康不遛貓閱讀 1,122評論 0 4
  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來自《Redis開發(fā)與運維》一書第八章,如轉(zhuǎn)載請聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 19,075評論 2 29
  • Redis 是一個鍵值對數(shù)據(jù)庫(key-value DB),數(shù)據(jù)庫的值可以是字符串、集合、列表等多種類型的對象,而...
    吳昂_ff2d閱讀 3,743評論 0 5
  • 聲明:本文內(nèi)容來自《Redis開發(fā)與運維》一書第八章,如轉(zhuǎn)載請聲明。Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常...
    yoqu閱讀 1,619評論 0 2
  • 我的上一位領(lǐng)導,和我年紀相仿,他剛到任時,我頗有點不服??伤枪汕趭^勁,卻又讓我膽寒。早上最早來,晚上10...
    阿甘1972閱讀 188評論 0 0

友情鏈接更多精彩內(nèi)容