一拦焚、redis的優(yōu)勢
1蜡坊、redis簡介
redis是速度非常快的非關(guān)系型數(shù)據(jù)庫赎败,是內(nèi)存數(shù)據(jù)庫秕衙,可以以key-value的形式存儲String、set僵刮、zset据忘、list鹦牛、hash這5種數(shù)據(jù)結(jié)構(gòu),還能作為輕量級的消息隊列(功能和rabbitmq等消息隊列差不多勇吊,不過當數(shù)據(jù)很大時入隊很慢曼追,不推薦使用)。
2汉规、redis和其他數(shù)據(jù)庫的對比
名稱 | 類型 | 數(shù)據(jù)存儲選項 | 查詢類型 | 附加功能 |
---|---|---|---|---|
redis | 使用內(nèi)存存儲的非關(guān)系型數(shù)據(jù)庫 | 字符串礼殊、列表、集合针史、散列表晶伦、有序集合 | 各種數(shù)據(jù)類型都有自己的專屬命令,還有批量操作和不完全的事務支持 | 發(fā)布訂閱悟民、主從復制坝辫、持久化 |
memcached | 使用內(nèi)存存儲的鍵值緩存 | 鍵值之間的映射 | 創(chuàng)建命令、讀取命令射亏、更新命令近忙、刪除命令和其他幾個命令 | 為提升性能而設的多線程服務器 |
mysql | 關(guān)系數(shù)據(jù)庫 | 表 | select、update智润、insert及舍、delete、函數(shù)窟绷、存儲過程 | 支持ACID锯玛、主從復制和主主復制 |
3、使用redis的理由
- redis的數(shù)據(jù)類型相比于memcached等內(nèi)存緩存更加豐富兼蜈,能解決遇到的大多數(shù)問題攘残。
- redis的效率和簡單比關(guān)系型數(shù)據(jù)庫要好得多
- 對于大多數(shù)關(guān)系數(shù)據(jù)庫來說,插入行是很快的(順序?qū)懹脖P比隨機寫內(nèi)存要快)为狸,但是更新很慢(隨機寫硬盤)歼郭,而對redis來說更新很快
二、redis命令
1辐棒、字符串病曾、列表、集合命令
1.1 字符串
incr key ## key的值++
decr key ## key的值--
incrby key incremental ## key的值加上整數(shù)incremental
decrby key decrease ## key的值減去整數(shù)decrease
incrbyfloat key float ## key的值加上浮點數(shù)float
append key value ## key的值某位加上字符串value
getrange key start end ## 截取key對應的字符串的子字符串(從start到end漾根,含頭含尾)
setrange key offset value ## key對應的字符串從offset開始設置為給定的值
getbit key offset ## 將key的值看成一個二進制位串泰涂,并返回串中偏移量為offset的二進制位值
setbit key offset value ## 字符串中偏移量為offset的二進制位的值設置成value
bitcount key [start,end] ##統(tǒng)計二進制字符串中值為1的二進制位的數(shù)量
bitop operation dest-key key [key2...] ## 對一個或者多個二進制位串執(zhí)行按位運算辐怕,并把的出來的結(jié)果放在dest-key中
1.2 列表
rpush key value ## 右邊入列表
lpush key value ## 左邊入列表
rpop key ## 移除并返回最右邊的元素
lpop key ## 移除并返回最左邊的元素
lindex key offset ## 返回列表中偏移量為offset的元素
lrange key start end ## 返回列表中偏移量start到end范圍內(nèi)的所有元素
ltrim key start end ## 只保留列表中偏移量從start到end范圍內(nèi)的元素(頭尾都保留)
blpop key [key2...] timeout ## 從第一個非空隊列中彈出位于最左端的元素逼蒙,或者在timeout秒之內(nèi)阻塞并等待可彈出元素出現(xiàn)
brpop key [key2...] timeout ## 從第一個非空隊列中彈出位于最右端的元素,或者在timeout秒之內(nèi)阻塞并等待可彈出元素出現(xiàn)
rpoplpush source-key dest-key ## 從列表source-key最右端彈出寄疏,從列表dest-key最左端進入
brpoplpush source-key dest-key timeout## 從列表source-key最右端彈出是牢,從列表dest-key最左端進入顶考,如果source-key為空,那么阻塞等timeout秒
1.3 集合
sadd key value [value2...] ## 向集合中加入一個或多個元素妖泄,返回被添加的個數(shù)
srem key value [value2...] ## 刪除集合中的一個或多個元素驹沿,返回被刪除的個數(shù)
sismember key value ## 返回該value是夠存在于集合key中(布爾值)
scard key ## 返回集合key中元素個數(shù)
smembers key ## 返回集合中所有元素
srandmember key [count] ## 從集合中隨機返回一個或多個元素,當count為正數(shù)時蹈胡,返回的元素不會重復渊季;當count為負數(shù)時,返回的元素可能會重復
spop key ## 隨機返回并移除集合key中的一個元素
smove source-key dest-key value ## 如果source-key中含有value罚渐,那么把value從source-key移到dest-key中并返回1却汉;否則返回0
sdiff key [key2...] ## 返回那些存在于第一個集合并不存在于其他集合中的元素
sdiffstore dest-key key [key2] ## 將那些那些存在于第一個集合并不存在于其他集合中的元素放入集合des-key中
sinter key [key2...] ## 返回那些同時存在于所有集合中的元素
sinterstore dest-key key [key2...] 將那些同時存在于所有集合中的元素放入集合dest-key中
sunion key [key2...] ## 返回那些至少存在于一個集合中的元素
sunionstore dest-key key [key2...] ## 將那些至少存在于一個集合中的元素放入集合dest-key中
2、散列荷并、有序集合命令
2.1 散列表
hget field key ## 取得field下key對應的值
hset field key value ## 將field下key的值設為value
hdel field key ## 刪除field下key對應的映射
hlen field ## 返回散列表field下的鍵值對的個數(shù)
hexists field key ## 返回field下key是否存在(布爾值)
hkeys field ## 返回field下所有的鍵值對
hvals field ## 返回散列field中所有的值
hgetall field ## 返回散列field中所有的鍵值對
hincrby field key increment ## 將鍵key存儲的值加上整數(shù)increment
hincrbyfloat field key increment ## 將鍵key存儲的值加上浮點數(shù)increment
2.2 有序集合
zadd key score member [score2 member2...] ## 在有序集合key中加入member和它的分值
zrem key member [member2...] ## 有序集合key中刪除一個或多個成員
zcard key ## 返回有序集合中成員個數(shù)
zincrby key increment member ## 將有序集合key中成員member的分值加上increment
zcount key min max ## 返回有序集合key中分值介于min和max之間的成員數(shù)量
zrank key member ## 返回有序集合key中成員member的排名
zscore key member ## 返回member的分值
zrange key start end [withscore] ## 返回排名在start和end之間的member合砂,有了withscore就分值一并返回
zrevrank key member ## 返回有序集合中成員member的排名,從分值從大到小排序
zrevrange key start end [withscore] ## 返回有序集合中給定排名start到end之間的元素源织,排名從分值從大到小排序
zrangebyscore key min max [withscore] ## 返回有序集合中分值介于min和max的元素
zrevrangescore key min max [withscore] ## 返回有序集合中分值介于min和max的元素翩伪,返回順序是分值從大到小
zremrangebyrank key start end ## 移除有序集合中排名從start到end的元素
zremrangebyscore key min max ## 移除有序集合中分值從min到max的元素
zinterstore dest-key key-count key [key2...] [weights weight weight2...] [aggregate sum|min|max] ## 取key,key2...之間的交集并放入到有序集合dest-key中谈息,其中weight選項是給key缘屹、key2...的權(quán)重(可填可不填),
zunionstore dest-key key-count key [key2...] ## 取key,key2...之間的并集并放入到有序集合dest-key中
3侠仇、發(fā)布訂閱命令
3.1 介紹
訂閱者負責訂閱頻道轻姿,發(fā)送者負責向頻道發(fā)送二進制字符串消息。每當有消息發(fā)送至給定頻道時逻炊,頻道的所有訂閱者都會收到消息互亮。
3.2 命令
subscribe channel [channel2...] ## 訂閱一個或多個模式
unsubscribe [channel...] ## 退訂給定的一個或者多個頻道
publish channel message ## 向給定頻道發(fā)送消息
psubscribe pattern [pattern2...] ## 訂閱與給定模式相匹配的頻道
punsubscribe [pattern...] ## 退訂給定的模式,要是沒給定模式則退訂所有模式
3.3 流程
新消息發(fā)布(publish)到頻道(channel):
消息發(fā)送給訂閱該頻道的客戶端:
3.4 java實現(xiàn)發(fā)布訂閱
3.4.1 用Jedis發(fā)布訂閱
初始化bean:
<!-- 連接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}"></property>
<property name="maxIdle" value="${redis.maxIdle}"></property>
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"></property>
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"></property>
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"></property>
<property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}"></property>
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"></property>
<property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
<property name="testWhileIdle" value="${redis.testWhileIdle}"></property>
<property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"></property>
</bean>
<!-- 初始化連接池 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1" value="${redis.host}" />
<constructor-arg index="2" value="${redis.port}" />
<constructor-arg index="3" value="3000" />
<constructor-arg index="4" value="${redis.password}" />
</bean>
發(fā)布:
/**
* jedis池
*/
@Autowired
private JedisPool jedisPool;
public String pub(GlobalDTO globalDTO, String channel, String message) throws BusiException {
Jedis jedis = null;
try {
jedis = this.jedisPool.getResource();
jedis.publish(channel, message);
} catch (Exception e) {
LOGGER.error("{}publish error 發(fā)布數(shù)據(jù)channel = {}, message = {}",
new Object[] { JSON.toJSONString(globalDTO), channel, message});
} finally {
this.jedisPool.returnResourceObject(jedis);
}
return null;
}
訂閱:
JedisPubSub類的重寫:
package com.awifi.vp.assist.utils.redis;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import com.awifi.vp.assist.config.PropertiesUtil;
import com.awifi.vp.assist.constant.Prefix;
import com.awifi.vp.assist.http.HttpClient;
import com.awifi.vp.assist.utils.EncryptUtil;
import redis.clients.jedis.JedisPubSub;
/**
* @Description: redis訂閱到信息后執(zhí)行操作
* @Title: Subscriber.java
* @Package: com.awifi.vp.assist.utils.redis
* @author ffn
* @date 2017年6月20日
*/
public class Subscriber extends JedisPubSub {
public Subscriber() {}
/**
* redis訂閱到信息后執(zhí)行
* @param channel 主題
* @param message 接收到的訂閱信息
* @author ffn
* @date 2017年6月20日
*/
public void onMessage(String channel, String message) {
//對接收到的消息進行處理
}
/**
* 繼承自JedisPubSub
* @param channel 主題
* @param subscribedChannels ..
*/
public void onSubscribe(String channel, int subscribedChannels) {
}
/**
* 繼承自JedisPubSub
* @param channel 主題
* @param subscribedChannels ..
*/
public void onUnsubscribe(String channel, int subscribedChannels) {
}
}
訂閱線程:
/**
* @Description: redis訂閱線程
* @Title: SubThread.java
* @Package: com.awifi.vp.assist.utils.redis
* @author ffn
* @date 2017年6月20日
*/
public class SubThread extends Thread {
/**
* redis連接池
*/
private JedisPool jedisPool;
/**
* redis訂閱操作類
*/
private final Subscriber subscriber = new Subscriber();
/**
* 訂閱主題
*/
private String channel;
public SubThread(String channel,JedisPool jedisPool) {
this.jedisPool = jedisPool;
this.channel = channel;
}
@Override
public void run() {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.subscribe(subscriber, channel);
} catch (Exception e) {
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
啟動訂閱線程:
new SubThread(channel,jedisPool).start();
3.4.2 使用監(jiān)聽器
bean初始化:
<!-- spring data redis -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="usePool" value="true"></property>
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.pass}" />
<property name="timeout" value="${redis.timeout}" />
<property name="database" value="${redis.default.db}"></property>
<constructor-arg index="0" ref="jedisPoolConfig" />
</bean>
<!-- jedis pool配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<!--
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
-->
</bean>
<!-- Bean Configuration -->
<bean id="messageListener"
class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="com.redis.MyMessageListener" />
</constructor-arg>
</bean>
<bean id="redisContainer"
class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="messageListeners">
<map>
<entry key-ref="messageListener">
<list>
<bean class="org.springframework.data.redis.listener.ChannelTopic">
<constructor-arg value="springtv" />
</bean>
<bean class="org.springframework.data.redis.listener.PatternTopic">
<constructor-arg value="hello*" />
</bean>
<bean class="org.springframework.data.redis.listener.PatternTopic">
<constructor-arg value="tv*" />
</bean>
</list>
</entry>
</map>
</property>
</bean>
package com.redis;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println("接收到的消息主題:" + message.getChannel()
+ ",接收到的消息為:" + message.getBody());
}
}
3.5 小結(jié)
- redis的發(fā)布訂閱多用于實時性較高的消息推送余素,并不保證可靠豹休。
- 要是redis某個客戶端訂閱了某個頻道,但是它讀取的速度不夠快溺森,那么積壓的消息可能會使緩存區(qū)越來越大慕爬,甚至把內(nèi)存占滿窑眯。
- 傳輸?shù)臅r候萬一斷線屏积,那么客戶端將會丟失斷線期間所有的數(shù)據(jù)。
4磅甩、其他命令
4.1 排序
sort source-key [by pattern] [limit offset count] [get pattern...] [asc|desc] [store dest-key] ## 根據(jù)給定的選項炊林,對輸入的列表、集合或者有序集合進行排序
4.2 基本的redis事務
4.2.1 介紹
redis事務可以讓redis客戶端在不被其他客戶端打斷的情況下執(zhí)行多個命令卷要。和關(guān)系數(shù)據(jù)庫中可以回滾的事務不同渣聚,redis事務會用multi和exec包圍要執(zhí)行的命令独榴,當事務執(zhí)行完之后,redis才會處理其他客戶端的命令奕枝。
4.2.2 示例
/**
* redis連接池
*/
private JedisPool jedisPool;
public void test(){
Jedis jedis = this.jedisPool.getResource();
Transaction tx = jedis.multi();
tx.zadd("market:", "1");
tx.exec();
}
4.3 鍵的過期時間
persist key ## 移除鍵的過期時間
ttl key ## 查看鍵的過期時間棺榔,單位是秒
expire key seconds ## 讓給定的鍵在指定秒數(shù)后過期
expireat key timestamp ## 將給定的key的過期時間設置為unix時間戳,單位是秒
pttl key ## 查看鍵的過期時間隘道,單位是毫秒
pexpire key millisseconds ## 讓給定的鍵在指定毫秒數(shù)后過期
pexpireat key timestamp ## 將給定的key的過期時間設置為unix時間戳症歇,單位是毫秒
三、數(shù)據(jù)安全和性能保障
1.持久化
1.1 介紹
將redis數(shù)據(jù)存儲到硬盤里谭梗。目的是為了在之后重用數(shù)據(jù)忘晤,或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個遠程位置。
在redis-list寫緩存隊列時激捏,持久化能保存隊列中的信息设塔,防止系統(tǒng)崩潰隊列中的信息丟失(pub/sub會丟失部分數(shù)據(jù)),不過這種方式違背了redis持久化的初衷远舅,所以不推薦使用闰蛔。
1.2 持久化選項(redis.conf中配置)
快照持久化:
save 60 1000 ## 60s內(nèi)至少有10000個key有變化(可以配置多個,滿足任何一個就會觸發(fā))
stop-writes-on-bgsave-error yes/no ## 指定redis在后臺dump磁盤出錯時的行為图柏,默認為yes钞护,表示若后臺dump出錯,則RedisServer拒絕新的寫入請求爆办,通過這種方式來引起用戶警覺难咕,避免因用戶未發(fā)現(xiàn)異常而引起更大的事故。
rdbcompression yes/no ## RDB文件是否壓縮存儲距辆,若為yes余佃,會在壓縮時消耗一點CPU,但省磁盤空間跨算。
dbfilename dump.rdb ## 指定RDB文件名爆土,默認為dump.rdb
AOF持久化:
appendonly yes/no ## 配置是否啟用AOF持久化,默認為no
appendfsync no/always/everysec
## 配置aof文件的同步方式诸蚕,Redis支持3種方式:
## a. no => redis不主動調(diào)用fsync步势,何時刷盤由OS來調(diào)度;
## b. always => redis針對每個寫入命令均會主動調(diào)用fsync刷磁盤背犯;
## c. everysec => 每秒調(diào)一次fsync刷盤坏瘩。
no-appendfsync-on-rewrite yes/no ##指定是否在后臺aof文件rewrite期間調(diào)用fsync,默認為no漠魏,表示要調(diào)用fsync(無論后臺是否有子進程在刷盤)
auto-aof-rewrite-percentage 100 ## 指定Redis重寫aof文件的條件倔矾,默認為100,表示與上次rewrite的aof文件大小相比,當前aof文件增長量超過上次afo文件大小的100%時哪自,就會觸發(fā)background rewrite丰包。若配置為0,則會禁用自動rewrite壤巷。
auto-aof-rewrite-min-size 64mb ## 指定觸發(fā)rewrite的aof文件大小邑彪。若aof文件小于該值,即使當前文件的增量比例達到auto-aof-rewrite-percentage的配置值胧华,也不會觸發(fā)自動rewrite锌蓄。即這兩個配置項同時滿足時,才會觸發(fā)rewrite撑柔。
共享選項:
dir ./ ## 指定快照文件和aof文件的存放路徑
1.3 快照持久化
redis通過創(chuàng)建快照來獲取存儲在內(nèi)存里的數(shù)據(jù)在某個時間點上的副本瘸爽。以便數(shù)據(jù)丟失時恢復數(shù)據(jù)。
如果新的快照文件創(chuàng)建完畢之前铅忿,Redis剪决、系統(tǒng)或硬件之間任意一個崩潰,那么redis將丟失最近一次創(chuàng)建快照之后寫入的所有數(shù)據(jù)檀训。
創(chuàng)建快照的方式:
- 客戶端向redis發(fā)送bgsave命令(windows不支持柑潦,因為windows不支持fork)來主動創(chuàng)建一個快照。redis會調(diào)用fork來創(chuàng)建一個子進程峻凫,然后子進程負責將快照寫入磁盤渗鬼,父進程繼續(xù)執(zhí)行別的請求。
- 客戶端向redis發(fā)送save命令來主動創(chuàng)建快照荧琼。這種方式服務器會阻塞譬胎,不再響應別的請求。
- 用戶配置了save選項命锄,滿足了會自動創(chuàng)建快照堰乔。(比如前面的save 60 1000)
- 當redis通過shutdown命令接收到關(guān)閉服務器請求時,或者接收到標準term信號時脐恩,會執(zhí)行一個save命令镐侯,阻塞所有客戶端。
- 當一個redis服務器連接到另一個redis服務器時驶冒,并向?qū)Ψ桨l(fā)送sync命令來開始一次復制操作的時候苟翻,如果主服務器沒有執(zhí)行bgsave命令或者并非剛剛執(zhí)行bgsave命令,那么主服務器會調(diào)用一次bgsave骗污。
從上面的介紹中可以看出崇猫,快照持久化因為要將所有的數(shù)據(jù)都保存到硬盤,所以執(zhí)行時間會比較慢身堡,這就無法保證數(shù)據(jù)的實時性邓尤。(過于頻繁會浪費資源拍鲤,過于稀少會丟失大量數(shù)據(jù))贴谎。
1.4 AOF持久化
將被執(zhí)行的寫命令寫到aof文件的末尾汞扎,以此來記錄數(shù)據(jù)發(fā)生的變化。并不是像快照持久化一樣每次都完整的執(zhí)行一次持久化擅这。
重寫/亞索AOF文件:
aof文件既可以將損失數(shù)據(jù)的時間降至1秒(甚至不丟失任何數(shù)據(jù))澈魄,又可以在極短的時間內(nèi)完成持久化。但是aof文件會越來越大(甚至會占滿整個硬盤)仲翎,因為aof文件太大痹扇,還原操作也會消耗大量的時間。所以溯香,重寫/壓縮aof文件刻不容緩鲫构。
重寫/壓縮aof文件方式:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
(要同時滿足這兩個條件才會重寫/壓縮aof文件,具體含義參考前面的持久化配置選項)
2.主從復制
2.1 對redis的復制相關(guān)選項進行配置
slaveof host post ## 從服務器連接主服務器
slaveof no one ## 讓服務器終止復制操作玫坛,不再接受主服務器的數(shù)據(jù)更新
從服務器連接主服務器時的步驟:
步驟 | 主服務器操作 | 從服務器操作 |
---|---|---|
1 | (等待命令接入) | 連接主服務器结笨,發(fā)送sync命令 |
2 | 開始執(zhí)行bgsave,并使用緩沖區(qū)記錄bgsave之后執(zhí)行的所有寫命令 | 根據(jù)配置選項來決定是繼續(xù)使用現(xiàn)有的數(shù)據(jù)來處理客戶端的請求湿镀,還是向發(fā)送請求的客戶端返回錯誤 |
3 | bgsave執(zhí)行完畢炕吸,向從服務器發(fā)送快照文件,并在發(fā)送期間繼續(xù)使用緩沖區(qū)記錄被執(zhí)行寫命令 | 丟棄所有舊數(shù)據(jù)勉痴,開始載入主服務器發(fā)來的快照文件 |
4 | 快照文件發(fā)送完畢赫模,開始向從服務器發(fā)送緩沖區(qū)里面的寫命令 | 完成對快照文件的解釋操作,想往常一樣開始接受命令請求 |
5 | 緩沖區(qū)存儲的寫命令發(fā)送完畢蒸矛;從現(xiàn)在開始瀑罗,每執(zhí)行一個寫命令,就向從服務器發(fā)送相同的寫命令 | 執(zhí)行從服務器發(fā)來的所有存儲在緩沖區(qū)里面的寫命令雏掠;從現(xiàn)在開始廓脆,接受并執(zhí)行主服務器傳來的每個寫命令。 |
2.2 主從鏈
從服務器擁有自己的從服務器磁玉,并由此形成了主從鏈停忿。
當讀請求的重要性明顯高于寫請求的重要性,并且讀請求的數(shù)量遠遠超出一臺redis服務器可以處理的范圍時蚊伞,用戶就需要添加新的從服務器來處理讀請求席赂。
隨著負載不斷上升,主服務器可能會無法快速地更新所有的從服務器时迫,為了緩解這個問題颅停,用戶可以創(chuàng)建一個redis主從節(jié)點組成的中間層來分擔主服務器的復制工作。
3.redis事務
在多個客戶端同時處理相同的數(shù)據(jù)時掠拳,不謹慎的操作將會導致數(shù)據(jù)出錯癞揉。redis事務和關(guān)系數(shù)據(jù)庫的事務不一樣,redis事務提供了一種“將多個命令打包, 然后一次性喊熟、按順序地執(zhí)行”的機制柏肪, 并且事務在執(zhí)行的期間不會主動中斷 —— 服務器在執(zhí)行完事務中的所有命令之后, 才會繼續(xù)處理其他客戶端的其他命令芥牌。
悲觀鎖:
在訪問寫入為目的的數(shù)據(jù)的時候烦味,關(guān)系數(shù)據(jù)庫會對被訪問的數(shù)據(jù)行進行加鎖,直到事務被提交為止壁拉。如果有其他客戶端試圖對被加鎖的數(shù)據(jù)行進行寫入谬俄,那么該客戶端將被阻塞,直到第一個事務執(zhí)行完為止弃理。缺點是溃论,持有鎖的客戶端運行越慢,等待解鎖的客戶端被阻塞的時間就越長痘昌。
樂觀鎖:
redis為了減少客戶端的等待時間蔬芥,并不會在執(zhí)行watch命令時對數(shù)據(jù)進行加鎖。redis只會在數(shù)據(jù)已經(jīng)被其他客戶端修改的情況下控汉,通知執(zhí)行了watch命令的客戶端笔诵。
4.非事務型流水線
在需要執(zhí)行大量命令的情況下,即使命令實際上并不需要放在事務里面執(zhí)行姑子,但是為了通過一次發(fā)送所有命令來減少通信次數(shù)并降低延遲值乎婿,會用到非事務型流水線。(Jediscluster不支持管道模式)
Jedis jedis = this.jedisPool.getResource();
Pipeline pipeline = jedis.pipelined();
pipeline.zremrangeByScore(semname, 0, now-timeout);
pipeline.zadd(semname,now, identifier);
Long id = pipeline.zrank(semname, identifier).get();
pipeline.zrem(semname, identifier);
pipeline.exec();
5.分布式鎖
5.1 介紹
不同機器上的不同Redis客戶端先獲取鎖來得到對數(shù)據(jù)行排他性訪問的能力街佑,然后對數(shù)據(jù)行進行操作谢翎,最后釋放鎖。
5.2 使用Redis構(gòu)建鎖
構(gòu)建鎖之前沐旨,首先介紹一個redis命令:
SETNX key value
將 key 的值設為 value 森逮,當且僅當 key 不存在。
若給定的 key 已經(jīng)存在磁携,則 SETNX 不做任何動作褒侧。
SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫谊迄。
5.2.1 獲取鎖(帶有超時時間)
Jedis jedis = this.jedisPool.getResource();
//設置鎖的名字
String lockName = "lock:";
//鎖的值
String identifier = "identifier";
//設置鎖的過期時間是當前時間過后60s
Long currentTime = System.currentTimeMillis()/1000闷供;
Long timeOut = currentTime + 60;
Long end = currentTime + 10;
//10s內(nèi)一直獲取,直到獲取到了鎖或者超時了才罷休
while(System.currentTimeMillis()/1000 < end) {
//獲取到了鎖
if(jedis.setnx(lockName,identifier) == 1){
jedis.exprie(lockTime,timeOut);
return identifier;
//沒有設置超時時間统诺,則為其設置超時時間
}else if(-1 == jedis.ttl(lockName)){
jedis.exprie(lockTime,timeOut);
}
}
return null;
5.2.2 釋放鎖(帶有超時時間)
Jedis jedis = this.jedisPool.getResource();
Pipeline pipeline = jedis.pipelined();
//鎖的名字
String lockName = "lock:";
//鎖的值
String identifier = "identifier";
while(true){
pipeline.watch(lockName);
if(identifier == pipeline.get(lockName)){
pipeline.multi();
pipeline.del(lockName);
pipeline.exec();
return true;
}
}
四歪脏、分片
1、概念
分片(partitioning)就是將你的數(shù)據(jù)拆分到多個 Redis 實例的過程粮呢,這樣每個實例將只包含所有鍵的子集婿失。
2钞艇、作用
- 允許使用很多電腦的內(nèi)存總和來支持更大的數(shù)據(jù)庫。沒有分片豪硅,你就被局限于單機能支持的內(nèi)存容量哩照。
- 允許伸縮計算能力到多核或多服務器,伸縮網(wǎng)絡帶寬到多服務器或多網(wǎng)絡適配器舟误。
3葡秒、分片基礎
3.1. 范圍分片
先進入實例的用戶在前面的片姻乓,前面的片滿了再進入后面的片
缺點:需要一個映射范圍到實例的表格嵌溢。這張表需要管理,不同類型的對象都需要一個表蹋岩, 所以范圍分片在 Redis 中常常并不可取赖草,因為這要比替他分片可選方案低效得多。
3.2 哈希分片
- 使用一個哈希函數(shù)(例如剪个,crc32 哈希函數(shù)) 將鍵名轉(zhuǎn)換為一個數(shù)字秧骑。例如,如果鍵是 foobar扣囊, crc32(foobar)將會輸出類似于 93024922 的東西乎折。
- 對這個數(shù)據(jù)進行取模運算,以將其轉(zhuǎn)換為一個 0 到 3 之間的數(shù)字侵歇,這樣這個數(shù)字就可以映射到我的 4 臺 Redis 實例之一骂澄。93024922 模 4 等于 2,所以我知道我的鍵 foobar 應當存儲到 R2 實例惕虑。注意:取模操作返回除法操作的余數(shù)坟冲,在許多編程語言總實現(xiàn)為%操作符。
4溃蔫、分片的不同實現(xiàn)
- 客戶端分片(Client side partitioning)意味著健提,客戶端直接選擇正確的節(jié)點來寫入和讀取指定鍵。許多 Redis 客戶端實現(xiàn)了客戶端分片伟叛。
- 代理協(xié)助分片(Proxy assisted partitioning)意味著私痹,我們的客戶端發(fā)送請求到一個可以理解 Redis 協(xié)議的代理上,而不是直接發(fā)送請求到 Redis 實例上统刮。代理會根據(jù)配置好的分片模式侄榴,來保證轉(zhuǎn)發(fā)我們的請求到正確的 Redis 實例,并返回響應給客戶端网沾。Redis 和 Memcached 的代理 Twemproxy 實現(xiàn)了代理協(xié)助的分片癞蚕。
- 查詢路由(Query routing)意味著,你可以發(fā)送你的查詢到一個隨機實例辉哥,這個實例會保證轉(zhuǎn)發(fā)你的查詢到正確的節(jié)點桦山。Redis 集群在客戶端的幫助下攒射,實現(xiàn)了查詢路由的一種混合形式 (請求不是直接從 Redis 實例轉(zhuǎn)發(fā)到另一個,而是客戶端收到重定向到正確的節(jié)點)恒水。
5会放、分片的缺點
- 涉及多個鍵的操作通常不支持。例如钉凌,你不能對映射在兩個不同 Redis 實例上的鍵執(zhí)行交集(事實上有辦法做到咧最,但不是直接這么干)。
- 涉及多個鍵的事務不能使用御雕。
- 分片的粒度(granularity)是鍵矢沿,所以不能使用一個很大的鍵來分片數(shù)據(jù)集,例如一個很大的有序集合酸纲。
- 當使用了分片捣鲸,數(shù)據(jù)處理變得更復雜,例如闽坡,你需要處理多個 RDB/AOF 文件栽惶,備份數(shù)據(jù)時你需要聚合多個實例和主機的持久化文件。
- 添加和刪除容量也很復雜疾嗅。例如外厂,Redis 集群具有運行時動態(tài)添加和刪除節(jié)點的能力來支持透明地再均衡數(shù)據(jù),但是其他方式代承,像客戶端分片和代理都不支持這個特性汁蝶。但是,有一種稱為預分片(Presharding)的技術(shù)在這一點上能幫上忙次泽。