一、字符串對象概述
字符串類型是Redis最基礎的數據結構。首先鍵都是字符串類型,而且其他幾種數據結構都是在字符串類型基礎上構建的,所以字符串類型能為其他四種數據結構的學習奠定基礎
字符串就是一個由字節組成的序列
如下圖所示,字符串類型的值實際可以是:
字符串(簡單的字符串、復雜的字符串(例如JSON、XML))
數字?(整數、浮點數)
二進制(圖片、音頻、視頻),但是值最大不能超過512MB
二、命令
設置值(SET、SETEX、SETNX)
set:設置值。參數如下:
ex seconds:為鍵設置秒級過期時間
px milliseconds:為鍵設置毫秒級過期時間
nx:鍵必須不存在,才可以設置成功,用于添加
xx:與nx相反,鍵必須存在,才可以設置成功,用于更新
set key value [ex seconds] [px milliseconds] [nx|xx]
setex、setnx:它們的作用和set命令的ex和nx選項是一樣的
setex:設置鍵的時候同時設置過期時間
setnx:鍵必須不存在,才可以設置成功,否則出錯
setex key seconds value
setnx key value
setnx和setxx在實際使用中有什么應用場景嗎?
以setnx命令為例子,由于 Redis的單線程命令處理機制,如果有多個客戶端同時執行setnx key value, 根據setnx的特性只有一個客戶端能設置成功,setnx可以作為分布式鎖的一種實現方案,Redis官方給出了使用setnx實現分布式鎖的方法:http://redis.io/topics/distlock
獲取值(GET)
get key
如果要獲取的鍵不存在,則返回nil(空)
批量設置值(MSET)
mset key value [key value ...]
例如下面依次設置4個鍵值對:
批量獲取值(MGET)
如果有些鍵不存在,那么它的值為nil(空)
mget key [key ...]
例如下面獲取鍵為a、b、c、d、e的值,其中e鍵不存在
批量操作的好處
批量操作命令可以有效提高開發效率,假如沒有mget這樣的命令,要執行n次get命令需要耗時如下:
n次get時間 = n次網絡時間 + n次命令時間
使用mget命令后,要執行n次get命令操作只需要耗時:
n次get時間 = 1次網絡時間 + n次命令時間
Redis可以支撐每秒數萬的讀寫操作,但是這指的是Redis服務端的處理能力,對于客戶端來說,一次命令除了命令時間還是有網絡時間,假設網絡時間為1毫秒,命令時間為0.1毫秒(按照每秒處理1萬條命令算),那么執行1000次get命令和1次mget命令的區別如下圖所示,因為Redis的處理能力已經足夠高,對于開發人員來說,網絡可能會成為性能的瓶頸
學會使用批量操作,有助于提高業務處理效率,但是要注意的是每次批 量操作所發送的命令數不是無節制的,如果數量過多可能造成Redis阻塞或 者網絡擁塞。
字符串的自增命令和自減命令:
命令 | 用例和描述 |
INCR | INCR key-name 將鍵存儲的值加上l |
DECR | DECR key-name 將鍵存儲的值減去Ⅰ |
INCRBY | INCRBY key-name amount 將鍵存儲的值加上整數amount |
DECRBY | DECRBY key-name amount 將鍵存儲的值減去整數amount |
INCRBYELOAT | 工NCRBYFLOAT key-name amount 將鍵存儲的值加上浮點數amount,這個命令在Redis 2.6或以上的版本可用 |
概念:
用戶可以通過給定一個任意的數值,對存儲著整數或者浮點數的字符串執行自增(increment)、自減操作(decrement)
在需要的時候,Redis還會將整數轉換為浮點數
整數的取值范圍和系統的長整數(long integer)的取值范圍相同(32位系統中就是32位有符號整數;64位系統中就是64位有符號整數)
而浮點數的取值范圍和精度則與IEEE 754標準的雙精度浮點數(double)相同
返回值:
INCR:返回增加后鍵的值。返回值分為三種:
DECR:返回刪除后鍵的值
INCRBY:返回增加后鍵的值
DECRBY:返回刪除后鍵的值
INCRBYFLOAT:返回增加后鍵的值
注意事項:
如果對一個不存在的鍵或者一個保存了空串的鍵執行自增或者自減操作,那么Redis在執行操作時會將這個鍵的值作為0來處理
如果所操作的字符串不是一個能被解釋為整數或者浮點數的字符串,那么這些命令的操作將返回一個錯誤
很多存儲系統和編程語言內部使用CAS機制實現計數功能,會有一定的CPU開銷,但在Redis中完全不存在這個問題,因為Redis是單線程架構,任 何命令到了Redis服務端都要順序執行
演示案例如下:
? ? ?STRLEN:獲取字符串長度(備注:中文占3個字節)
strlen key
?
GETSET:設置并返回原值。
getset和set一樣會設置值,但是不同的是,它同時會返回鍵原來的值
getset key value
下面給出了其他一些演示案例:
注意SETRANGE的用法:
下圖是字符串類型命令的時間復雜度:
命令
時間復雜度
set key value
o(l)
get key
o(1)
del key [key ...]
o(k),k是鍵的個數
mset key value [ key value ...]
O(k),t是鍵的個致
mget key [key ...]
o(),I是鍵的個致
incr key
o1)
decr key
o(1)
incrby key increment
o(1)
decrby key decrement
o(1)
incrbyfloat key increment
o(1)
append key value
o(1)
strlen key
o(1)
setrange key offset value
o(1)
getrange key start end
oo),n是寧符串長度,由于獲取字符串非常快,所以
如果字符非不是很長,可以視阿為O(1)
三、內部編碼
字符串類型的內部編碼有3種:
int:8個字節的長整型
embstr:小于等于39個字節的字符串
raw:大于39個字節的字符串
演示說明
Redis會根據當前值的類型和長度決定使用哪種內部編碼實現
整數類型示例如下:
短字符串示例如下:
長字符串示例如下:
四、典型使用場景
①緩存功能
下圖是比較典型的緩存使用場景,其中Redis作為緩存層,MySQL作為存儲層,絕大部分請求的數據都是從Redis中獲取。由于Redis具有支撐高并發的特性,所以緩存通常能起到加速讀寫和降低后端壓力的作用
下面偽代碼模擬了上圖的訪問過程:
//1.該函數用于獲取用戶的基礎信息UserInfo getUserInfo(long id){...}//2.首先從Redis獲取用戶信息:// 定義鍵userRedisKey = "user:info:" + id;// 從Redis獲取值value = redis.get(userRedisKey);if (value != null) {// 將值進行反序列化為UserInfo并返回結果userInfo = deserialize(value);return userInfo;}//3.如果沒有從Redis獲取到用戶信息,需要從MySQL中進行獲取,并將結果回寫到Redis,添加1小時(3600秒)過期時間://從MySQL獲取用戶信息userInfo = mysql.get(id);// 將userInfo序列化,并存入Redisredis.setex(userRedisKey, 3600, serialize(userInfo));// 返回結果return userInfo
整個功能的偽代碼如下:
UserInfo getUserInfo(long id){userRedisKey = "user:info:" + idvalue = redis.get(userRedisKey);UserInfo userInfo;if (value != null) {userInfo = deserialize(value);} else {userInfo = mysql.get(id);if (userInfo != null)redis.setex(userRedisKey, 3600, serialize(userInfo));}return userInfo;}
應用場景1:緩存熱門圖片
set redis-log.jpg redis-log-data
應用場景2:存儲文章。當用戶想在博客中撰寫一篇新文章的時候,程序就需要把文章的標題、內容、作者、發表時間等多 項信息存儲起來,并在用戶閱讀文章的時候取出來這些信息。可以使用 mset mget msetnx 命令來進行
開發提示:
與MySQL等關系型數據庫不同的是,Redis沒有命令空間,而且也沒有對鍵名有強制要求(除了不能使用一些特殊字符)
但設計合理的鍵名,有利于防止鍵沖突和項目的可維護性,比較推薦的方式是使用“業務名:對象 名:id:[屬性]”作為鍵名(也可以不是分號)
例如MySQL的數據庫名為 vs,用戶表名為user,那么對應的鍵可以用"vs:user:1","vs:user:1:name"來表示,如果當前Redis只被一個業務使用,甚至可以去掉“vs:”。如 果鍵名比較長,例如“user:{uid}:friends:messages:{mid}”,可以在能描 述鍵含義的前提下適當減少鍵的長度,例如變為“u:{uid}:fr:m:{mid}”,從而減少由于鍵過長的內存浪費
②計數
許多應用都會使用Redis作為計數的基礎工具,它可以實現快速計數、 查詢緩存的功能,同時數據可以異步落地到其他數據源
應用場景1:文章長度計數功能、文章摘要、文章計數
文章長度:STRLEN article:10086:content
文章摘要:GETRANGE article:10086:content 0 5
文章閱讀計數:INCRBY article:10086:count
應用場景2:例如筆者所在團隊的視頻播放數系統就是使用Redis作為視頻播放數計數的基礎組件,用戶每播放一次視頻,相應的視頻播放數就會自增1。代碼如下所示
long incrVideoCounter(long id) {
key = "video:playCount:" + id;
return redis.incr(key);
}
開發提示:實際上一個真實的計數系統要考慮的問題會很多:防作弊、按照不同維度計數,數據持久化到底層數據源等
③共享Session
如下圖所示,一個分布式Web服務將用戶的Session信息(例如用戶登錄信息)保存在各自服務器中,這樣會造成一個問題,出于負載均衡的考慮,分布式服務會將用戶的訪問均衡到不同服務器上,用戶刷新一次訪問可能會發現需要重新登錄,這個問題是用戶無法容忍的
為了解決這個問題,可以使用Redis將用戶的Session進行集中管理,如下圖所示,在這種模式下只要保證Redis是高可用和擴展性的,每次用戶更新或者查詢登錄信息都直接從Redis中集中獲取
④限速器
應用場景1:很多應用出于安全的考慮,會在每次進行登錄時,讓用戶輸入手機驗證 碼,從而確定是否是用戶本人。但是為了短信接口不被頻繁訪問,會限制用 戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次,如下圖所示
此功能可以使用Redis來實現,下面的偽代碼給出了基本實現思路:
phoneNum?=?"138xxxxxxxx";key?=?"shortMsg:limit:"?+?phoneNum;//SET?key?value?EX?60?NXisExists?=?redis.set(key,1,"EX?60","NX");if(isExists?!=?null?||?redis.incr(key)?<=5){//?通過}else{//?限速}
應用場景2:防止用戶的賬號遭到暴力破解,如果同個賬號連續好幾次輸入錯誤的密碼,則限制賬號的登錄,只 能等 30 分鐘后再次登錄,比如設置 3 次
1)SET max:execute:times 3
2)密碼出錯時 DECR max:execute:times
3)當 max:execute:times 的值小于 0 時則禁止登錄,并可以設置 SETEX login:error:darren 1800 "Incorrect password",然后使用 TTL login:error:darren 1800 檢測對應剩余的時間
應用場景3:例如一些網站為了防止網頁內容被網絡爬蟲瘋狂抓取,限制一個IP地址在固定的時間段內能夠訪問的頁面數量.