1.redis數(shù)據(jù)結(jié)構(gòu)
-
string
string可以存儲(chǔ)字符串、整數(shù)或者浮點(diǎn)數(shù)。
當(dāng)存儲(chǔ)整數(shù)時(shí),可以進(jìn)行自增或者自減操作

一個(gè)鍵對(duì)應(yīng)一個(gè)值

-
list列表
一個(gè)列表結(jié)構(gòu)可以有序的存儲(chǔ)多個(gè)字符串,且可以重復(fù)。
列表可以從序列的兩端推入(RPUSH、LPUSH)或者彈出元素(RPOP、LPOP),也可以返回某下標(biāo)的元素(lindex(key,index))還可以存儲(chǔ)任務(wù)信息、最近瀏覽過(guò)的文章信息或常用聯(lián)系人信息。
還可以使用阻塞彈出和推入命令實(shí)現(xiàn)消息傳遞和任務(wù)隊(duì)列。
set集合
集合和列表都可以存儲(chǔ)多個(gè)字符串,可以刪除集合中的某個(gè)或多個(gè)元素(srem),可以返回集合的所有元素(smembers(key)),不同在于set集合存儲(chǔ)的字符串是不可重復(fù)且無(wú)序的。
集合真正厲害的地方是組合和處理多個(gè)集合
sdiff:兩個(gè)集合的差集
sinter:交集
sunion:并集



-
hash散列
redis的散列可以存儲(chǔ)多個(gè)鍵和值得映射,各個(gè)鍵不相同,且無(wú)序
-
zset有序集合
zset有序集合和散列一樣,都是存儲(chǔ)多個(gè)鍵值對(duì),有序集合的鍵被稱為成員(member),值被稱為分?jǐn)?shù)(score),既可以根據(jù)鍵來(lái)訪問(wèn)元素,也可以根據(jù)值和值得排列順序來(lái)訪問(wèn)元素。
可以根據(jù)鍵獲取排名(zrank),根據(jù)鍵獲取分值(zscore)
也可以獲取范圍內(nèi)的排名(zrange)
取并集(zunionstore),兩個(gè)集合相同元素的score取最小值
取交集(zinterstore),兩個(gè)集合相同元素的score取和
2.redis的發(fā)布和訂閱
發(fā)送者負(fù)責(zé)向頻道發(fā)送消息,頻道內(nèi)的所有訂閱者都會(huì)獲得消息
subscribe channel --訂閱一個(gè)頻道
unsubscribe channel --退訂給定頻道
public channel msg --向給定頻道發(fā)送消息
3.基本的redis事務(wù)
redis的基本事務(wù)需要用到multi命令和exec命令,這種事務(wù)可以讓一個(gè)客戶端在不被其他客戶端打斷的情況下執(zhí)行多個(gè)命令,在redis中,被multi命令和exec命令包圍的命令會(huì)依次執(zhí)行,直到所用的命令執(zhí)行完畢為止。當(dāng)一個(gè)事務(wù)執(zhí)行完畢之后,redis才會(huì)執(zhí)行其他客戶端的命令。
在客戶端中使用piepline()方法創(chuàng)建一個(gè)事物,在一切正常的情況下,客戶端會(huì)自動(dòng)的使用multi和exec命令包裹用戶輸入的多個(gè)命令,為了提升性能,會(huì)在事務(wù)執(zhí)行時(shí)一次性地將所有命令都發(fā)送給redis,減少連接次數(shù),提高性能。
當(dāng)使用piepline(false)時(shí),將使用非事務(wù)型流水線,被包裹的命令一次連接執(zhí)行全部命令,但不以事務(wù)方式運(yùn)行。

使用流水線性能提高多倍。
public void benchmarkUpdateToken(Jedis conn, int duration) {
try{
@SuppressWarnings("rawtypes")
Class[] args = new Class[]{
Jedis.class, String.class, String.class, String.class};
Method[] methods = new Method[]{
this.getClass().getDeclaredMethod("updateToken", args),
this.getClass().getDeclaredMethod("updateTokenPipeline", args),
};
for (Method method : methods){
int count = 0;
long start = System.currentTimeMillis();
long end = start + (duration * 1000);
while (System.currentTimeMillis() < end){
count++;
method.invoke(this, conn, "token", "user", "item");
}
long delta = System.currentTimeMillis() - start;
System.out.println(
method.getName() + ' ' +
count + ' ' +
(delta / 1000) + ' ' +
(count / (delta / 1000)));
}
}catch(Exception e){
throw new RuntimeException(e);
}
}
public void updateToken(Jedis conn, String token, String user, String item) {
long timestamp = System.currentTimeMillis() / 1000;
conn.hset("login:", token, user);
conn.zadd("recent:", timestamp, token);
if (item != null) {
conn.zadd("viewed:" + token, timestamp, item);
conn.zremrangeByRank("viewed:" + token, 0, -26);
conn.zincrby("viewed:", -1, item);
}
}
public void updateTokenPipeline(Jedis conn, String token, String user, String item) {
long timestamp = System.currentTimeMillis() / 1000;
Pipeline pipe = conn.pipelined();
pipe.multi();
pipe.hset("login:", token, user);
pipe.zadd("recent:", timestamp, token);
if (item != null){
pipe.zadd("viewed:" + token, timestamp, item);
pipe.zremrangeByRank("viewed:" + token, 0, -26);
pipe.zincrby("viewed:", -1, item);
}
pipe.exec();
}
//updateToken 52558 5 10511
//updateTokenPipeline 476595 5 95319
watch命令:
在使用watch命令對(duì)鍵進(jìn)行監(jiān)視之后,直到執(zhí)行exec命令的這段時(shí)間里,如果其他客戶端對(duì)被監(jiān)視的鍵進(jìn)行了修改,那么在執(zhí)行exec時(shí),事務(wù)將失敗。(樂(lè)觀鎖)
比如在執(zhí)行購(gòu)買商品事務(wù)時(shí)監(jiān)視商品,當(dāng)商品發(fā)生變化時(shí),事務(wù)執(zhí)行失敗。
public boolean purchaseItem(
Jedis conn, String buyerId, String itemId, String sellerId, double lprice) {
String buyer = "users:" + buyerId;
String seller = "users:" + sellerId;
String item = itemId + '.' + sellerId;
String inventory = "inventory:" + buyerId;
long end = System.currentTimeMillis() + 10000;
while (System.currentTimeMillis() < end){
conn.watch("market:", buyer); //對(duì)市場(chǎng)中買家的商品進(jìn)行監(jiān)視
double price = conn.zscore("market:", item);
double funds = Double.parseDouble(conn.hget(buyer, "funds"));
if (price != lprice || price > funds){
conn.unwatch();
return false;
}
Transaction trans = conn.multi(); // 事務(wù)中轉(zhuǎn)移商品和錢
trans.hincrBy(seller, "funds", (int)price);
trans.hincrBy(buyer, "funds", (int)-price);
trans.sadd(inventory, itemId);
trans.zrem("market:", item);
List<Object> results = trans.exec(); //如果exec方法沒(méi)有引發(fā)watchError錯(cuò)誤,說(shuō)明該鍵沒(méi)有被更改,事務(wù)執(zhí)行成功,并且watch執(zhí)行結(jié)束
// null response indicates that the transaction was aborted due to
// the watched key changing.
if (results == null){
continue;
}
return true;
}
return false;
}
4.鍵的過(guò)期時(shí)間
// 設(shè)置過(guò)期時(shí)間,對(duì)于list,set,hash來(lái)說(shuō),只能為鍵設(shè)置過(guò)期時(shí)間,而無(wú)法為鍵里的單個(gè)元素設(shè)置過(guò)期時(shí)間
conn.expire("tran", 2);
// 查看鍵距離過(guò)期還有多長(zhǎng)時(shí)間
Long tran = conn.ttl("tran");
5.持久化策略
- 快照持久化
redis可以通過(guò)創(chuàng)建快照來(lái)獲得存儲(chǔ)在內(nèi)存的數(shù)據(jù)的某個(gè)時(shí)間點(diǎn)上的副本。
save 60 1000 //60秒內(nèi)有1000次寫入則執(zhí)行快照
stop-writes-on-bgsave-error no //創(chuàng)建快照失敗后是否執(zhí)行寫命令
rdbcompression yes //是否對(duì)快照文件進(jìn)行壓縮
dbfilename dump.rdb //快照創(chuàng)建的副本文件名
創(chuàng)建快照的幾種方式
- BGSAVE,會(huì)創(chuàng)建子線程
- SAVE
- 設(shè)置save配置選項(xiàng),觸發(fā)條件后會(huì)執(zhí)行BGSAVE命令
快照的缺陷
- 在只使用快照持久化保存數(shù)據(jù)時(shí),如果系統(tǒng)真的發(fā)生崩潰,用戶將丟失最近一次生成快照之后更改的所有數(shù)據(jù)。
- 當(dāng)數(shù)據(jù)量太大時(shí),BGSAVE創(chuàng)建的子線程要將數(shù)據(jù)保存到硬盤中耗費(fèi)的時(shí)間越來(lái)越多。
-
AOF持久化
簡(jiǎn)單來(lái)說(shuō),AOF持久化會(huì)將被執(zhí)行的寫命令寫到AOF文件的末尾,以此來(lái)記錄文件的變化。因此,redis只要從頭到尾重新執(zhí)行一次AOF文件的寫命令,就可以恢復(fù)AOF文件所記錄的數(shù)據(jù)集,AOF文件可能會(huì)很大,可以使用bgrewriteaof命令消除aof文件中的冗余命令。
image.png
appendonly no //是否使用AOF持久化
appendfsync everysec //多久才將寫入的內(nèi)容同步到硬盤,可選always、everysec、no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100 //AOF文件體積上次重寫大100%時(shí)重新執(zhí)行bgrewriteaof
auto-aof-rewrite-min-size 64mb //只有在AOF文件體積大于64M時(shí)才執(zhí)行bgrewriteaof,這個(gè)命令會(huì)移除AOF文件中的冗余命令來(lái)重寫AOF文件,使AOF文件盡可能的小










