標籤 : Java與NoSQLjavascript
Redis事務(
transaction
)是一組命令的集合,同命令同樣也是Redis的最小執行單位, Redis保證一個事務內的命令執行不被其餘命令影響.css
`MULTI`
SADD user:1:following 2
SADD user:2:follower 1
`EXEC`
事務操做 | MySQL | Redis |
---|---|---|
開啓 | start transaction |
MULTI |
語句 | DML | 普通命令 |
取消 | rollback |
DISCARD |
執行 | commit |
EXEC |
rollback
與Redis的DISCARD
有必定的區別. rollback
後,前2條的語句影響消失.ZADD
操做List
), EXEC
會執行前2條語句, 並跳過第3條語句. key
的設計.WATCH
悲觀鎖(Pessimistic Lock): 很悲觀,每次讀寫數據都認爲別人會修改,因此每次讀數據都會上鎖,這樣若是別人也想讀寫這條數據就會阻塞, 直到加鎖的人把鎖釋放. 傳統的RDBMS中用到了不少這種鎖機制, 如行鎖、表鎖、讀鎖、寫鎖等.
樂觀鎖(Optimistic Lock): 顧名思義很是樂觀, 每次讀寫數據時候都認爲別人不會修改,因此再也不上鎖,但在更新數據時會判斷一下在此期間有沒有人更新了這條數據, 這個判斷過程可使用版本號
等機制實現, 而Redis默認就對樂觀鎖提供了支持 –WATCH
命令.html
WATCH
命令能夠監控一個/多個key
, 一旦其中有一個被修改/刪除, 則以後的事務就不會執行,如用WATCH
命令來模擬搶票場景:java
SET ticket 1 # 如今假設只有一張票了
`WATCH` ticket # 監控票數變化
`MULTI`
DECRBY username 400
DECR ticket
[DECR ticket] # 如今假設有另一個用戶直接把這張票買走了
`EXEC` -> `(nil)` # 則這條事務執行就不會成功
WATCH
命令的做用只是當被監控的key
值修改後阻止事務執行,並不能阻止其餘Client修改. 因此一旦EXEC
執行失敗, 能夠從新執行整個方法或使用UNWATCH
命令取消監控.mysql
樂觀鎖適用於讀多寫少情景,即衝突真的不多發生,這樣能夠省去大量鎖的開銷. 但若是常常產生衝突,上層應用須要不斷的retry,反卻是下降了性能,因此這種狀況悲觀鎖比較適用.nginx
Redis可使用
EXPIRE
命令設置key
的過時時間, 到期後Redis會自動刪除它.git
命令 | 做用 |
---|---|
EXPIRE key seconds |
Set a timeout on key. |
TTL key |
Get the time to live for a key |
PERSIST key |
Remove the expiration for a key |
除了
PERSIST
命令以外,SET
/GETSET
爲key
賦值的同時也會清除key
的過時時間.另外若是WATCH
監控了一個擁有過時時間的key
,key
到期自動刪除並不會被WATCH
認爲該key
被修改.github
/** * @author jifang. * @since 2016/6/13 20:08. */
public class RedisDAO {
private static final int _1M = 60 * 1000;
private static final DataSource dataSource;
private static final Jedis redis;
static {
Properties properties = new Properties();
try {
properties.load(ClassLoader.getSystemResourceAsStream("db.properties"));
} catch (IOException ignored) {
}
/** 初始化鏈接池 **/
HikariConfig config = new HikariConfig();
config.setDriverClassName(properties.getProperty("mysql.driver.class"));
config.setJdbcUrl(properties.getProperty("mysql.url"));
config.setUsername(properties.getProperty("mysql.user"));
config.setPassword(properties.getProperty("mysql.password"));
dataSource = new HikariDataSource(config);
/** 初始化Redis **/
redis = new Jedis(properties.getProperty("redis.host"), Integer.valueOf(properties.getProperty("redis.port")));
}
public List<Map<String, Object>> executeQuery(String sql) {
List<Map<String, Object>> result;
try {
/** 首先請求Redis **/
String key = sql.replace(' ', '-');
String string = redis.get(key);
// 若是key未命中, 再請求DB
if (string == null || string.trim().isEmpty()) {
ResultSet resultSet = dataSource.getConnection().createStatement().executeQuery(sql);
/** 得到列數/列名 **/
ResultSetMetaData meta = resultSet.getMetaData();
int columnCount = meta.getColumnCount();
List<String> columnName = new ArrayList<>();
for (int i = 1; i <= columnCount; ++i) {
columnName.add(meta.getColumnName(i));
}
/** 填充實體 **/
result = new ArrayList<>();
while (resultSet.next()) {
Map<String, Object> entity = new HashMap<>(columnCount);
for (String name : columnName) {
entity.put(name, resultSet.getObject(name));
}
result.add(entity);
}
/**寫入Redis**/
String value = JSON.toJSONString(result);
redis.set(key, value, "NX", "PX", _1M);
} else {
result = JSON.parseObject(string, List.class);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return result;
}
public static void main(String[] args) {
RedisDAO dao = new RedisDAO();
List<Map<String, Object>> execute = dao.executeQuery("select * from user");
System.out.println(execute);
}
}
當服務器內存有限時,若是大量使用緩存並且過時時間較長會致使Redis佔滿內存; 另外一方面爲了防止佔用內存過大而設置過時時間太短, 則有可能致使緩存命中率太低而使系統總體性能降低.所以爲緩存設計一個合理的過時時間是很糾結的, 在Redis中能夠限制可以使用的最大內存,並讓Redis按照必定規則的淘汰再也不須要的key
: 修改maxmemory
參數,當超過限制會依據maxmemory-policy
參數指定的策略來刪除不須要的key
:redis
maxmemory-policy |
規則說明 |
---|---|
volatile-lru |
只對設置了過時時間的key使用LRU算法刪除 |
allkey-lru |
使用LRU刪除一個key |
volatile-random |
只對設置了過時時間的key隨機刪除一個key |
allkey-random |
隨機刪除一個key |
volatile-ttl |
刪除過時時間最近的一個key |
noevication |
不刪除key, 只返回錯誤(默認) |
Redis的
SORT
命令能夠對List
、Set
、Sorted-Set
類型排序, 而且能夠完成與RDBMS 鏈接查詢 相似的任務:算法
SORT key [BY pattern]
[LIMIT offset count]
[GET pattern [GET pattern ...]]
[ASC|DESC]
[ALPHA]
[STORE destination]
參數 | 描述 |
---|---|
ALPHA |
SORT 默認會將全部元素轉換成雙精度浮點數比較,沒法轉換則會提示錯誤,而使用ALPHA 參數可實現按字典序比較. |
DESC |
降序排序(SORT 默認升序排序). |
LIMIT |
指定返回結果範圍. |
STORE |
SORT 默認直接返回排序結果, STORE 可將排序後結果保存爲List . |
注:
SORT
在對Sorted-Set
排序時會忽略元素分數,只針對元素自身值排序.
不少狀況下key
實際存儲的是對象ID, 有時單純對ID自身排序意義不大,這就用到了BY
參數, 對ID關聯的對象的某個屬性進行排序:
[BY pattern]
pattern
能夠是字符串類型key
或Hash
類型key
的某個字段(表示爲鍵名 -> 字段名).若是提供了BY
參數, SORT
將使用ID值替換參考key
中的第一個*
並獲取其值,而後根據該值對元素排序.
SORT mi.blog:1:my BY mi.blog:*:data->time DESC
pattern
不包含*
時, SORT
將不會執行排序操做;key
不存在時,默認設置爲0;pattern
值相同,則會再比較元素自己值排序.GET
參數不影響排序過程,它的做用是使SORT
返回結果再也不是元素自身的值,而是GET
參數指定的鍵值:
[GET pattern [GET pattern ...]]
同BY
同樣, GET
參數也支持String
類型和Hash
類型, 並使用*
做爲佔位符.
SORT mi.blog:1:my BY mi.blog:*:data->time GET mi.blog:*:data->content GET mi.blog:*:data->time
注:
GET
參數獲取自身值須要使用#
:GET #
SORT
的時間複雜度爲O(N+M*log(M))
:
where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is currently O(N) as there is a copy step that will be avoided in next releases.
SORT
須要注意: key
中元素數量(減少N
);LIMIT
參數限制結果集大小(減少M
);STORE
將結果緩存.消息隊列就是」傳遞消息的隊列」,與消息隊列進行交互的實體有兩類, 一是生產者: 將須要處理的消息放入隊列; 一是消費者: 不斷從消息隊列中讀出消息並處理.
Redis提供了BRPOP
/BLPOP
命令來實現消息隊列:
命令 | 描述 |
---|---|
BRPOP key [key ...] timeout |
Remove and get the last element in a list, or block until one is available |
BLPOP key [key ...] timeout |
Remove and get the first element in a list, or block until one is available |
BRPOPLPUSH source destination timeout |
Pop a value from a list, push it to another list and return it; or block until one is available |
注: 若Redis同時監聽多個
key
, 且每一個key
均有元素可取,則Redis按照從左到右的順序去挨個讀取key
的第一個元素.
前面的BRPOP
/BLPOP
實現的消息隊列有一個限制: 若是一個隊列被多個消費者監聽, 生產者發佈一條消息只會被其中一個消費者獲取. 所以Redis還提供了一組命令實現「發佈/訂閱」模式, 一樣可用於進程間通訊:
「發佈/訂閱」模式也包含兩種角色: 發佈者與訂閱者. 訂閱者能夠訂閱一個/多個頻道, 而發佈者可向指定頻道發送消息, 全部訂閱此頻道的訂閱者都會收到此消息.
命令 | 描述 |
---|---|
PUBLISH channel message |
Post a message to a channel |
SUBSCRIBE channel [channel ...] |
Listen for messages published to the given channels |
UNSUBSCRIBE [channel [channel ...]] |
Stop listening for messages posted to the given channels |
PSUBSCRIBE pattern [pattern ...] |
Listen for messages published to channels matching the given patterns |
PUNSUBSCRIBE [pattern [pattern ...]] |
Stop listening for messages posted to channels matching the given patterns |
/** * @author jifang * @since 16/7/11 下午2:36. */
public class MessageQueue<T> {
private Jedis redis;
private String chanel;
public MessageQueue(Jedis redis, String chanel) {
this.redis = redis;
this.chanel = chanel;
}
public Long publish(T message) {
String json = JSON.toJSONString(message);
return redis.publish(chanel, json);
}
public void subscribe(final MessageHandler<T> handler) {
redis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
for (Type type : handler.getClass().getGenericInterfaces()) {
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type handlerClass = pType.getActualTypeArguments()[0];
T result = JSONObject.parseObject(message, handlerClass);
handler.handle(result);
}
}
}
}, chanel);
}
}
public interface MessageHandler<T> {
void handle(T object);
}
注: 發送的消息不會持久化,一個訂閱者只能接收到後續發佈的消息,以前發送的消息就接收不到了.
Redis支持兩種持久化方式: RDB與AOF. RDB: Redis根據指定的規則「定時」將內存數據快照到硬盤; AOF:Redis在每次執行命令後將命令自己記錄下來存放到硬盤.兩種持久化方式可結合使用.
- 快照執行過程:
- Redis使用
fork()
函數複製一份當前進程副本;- 父進程繼續接收並處理客戶端請求, 而子進程將全部內存數據寫入磁盤臨時文件;
- 當子進程將全部數據寫完會用該臨時文件替換舊的RDB文件, 至此一次快照完成(能夠看到自始至終RDB文件都是完整的).
Redis會在如下幾種狀況下對數據進行快照:
M
和改動key
個數N
; 當時間M
內被改動的key
的個數大於N
時, 即符合自動快照條件:save 900 1 save 300 10 save 60 10000
SAVE
/BGSAVE
/FLUSHALL
命令: 命令 | 描述 |
---|---|
SAVE |
SAVE 命令會使Redis同步地執行快照操做(過程當中會阻塞全部來自客戶端的請求, 所以儘可能避免線上使用) |
BGSAVE |
在後臺異步執行快照操做,Redis還可繼續響應請求 |
FLUSHALL |
FLUSHALL 會清空全部數據,不管是否觸發了自動快照條件(只要有配置了),Redis都會執行一次快照 |
LASTSAVE |
獲取最近一次成功執行快照時間 |
經過RDB方式實現持久化, Redis在啓動後會讀取RDB快照文件, 將數據從硬盤導入內存, 但若是在持久化過程當中Redis異常退出, 就會丟失最後一次快照之後更改的全部數據.
dir ./ # 設置工做目錄,RDB文件(以及後面的AOF文件)會寫入該目錄
dbfilename dump.rdb # 設置RDB文件名
rdbcompression yes # 導出RDB是否壓縮
rdbchecksum yes # 存儲和加載RDB校驗完整性
stop-writes-on-bgsave-error yes # 後臺備份進程出錯時,主進程中止寫入.
AOF將Redis執行的每一條命令追加到硬盤文件中.而後在啓動Redis時逐條執行AOF文件中的命令將數據載入內存.
Redis默認沒有開啓AOF, 須要以以下參數啓用:
appendonly yes
no-appendfsync-on-rewrite yes: # 正在導出RDB快照的過程當中,中止同步AOF.
開啓AOF後, Redis會將每一條有可能更改數據的命令寫入AOF文件,這樣就致使AOF文件愈來愈大,即便有可能內存中實際存儲的數據並沒多少. 所以Redis每當達到必定條件就自動重寫AOF文件,這個條件能夠在配置文件中設置:
auto-aof-rewrite-percentage 100 # 比起上次重寫時的大小,AOF增加率100%時重寫
auto-aof-rewrite-min-size 64mb # AOF大小超過64M時重寫
此外, 咱們還可使用BGREWRITEAOF
命令手動執行AOF重寫.
執行AOF持久化時, 因爲操做系統緩存機制, 數據並無真正寫入磁盤,而是進入了磁盤緩存, 默認狀況下系統每30S執行一次同步操做, 將緩存內容真正寫入磁盤, 若是在這30S的系統異常退出則會致使磁盤緩存數據丟失, 若是應用沒法忍受這樣的損失, 可經過appendfsync
參數設置同步機制:
# appendfsync always # 每次執行寫入都執行同步
appendfsync everyse # 每秒執行一次同步操做
# appendfsync no # 不主動進行同步, 而是徹底由操做系統執行.
複製(replication)中,Redis的角色能夠分爲兩類, Master:能夠執行讀/寫操做,當寫操做致使數據修改時會自動將數據同步給Slave; Slave:通常是隻讀的,並接受Master同步過來的數據(Slave自身也能夠做爲Master存在, 如圖):
SYNC
命令;Master收到後在後臺保存RDB快照, 並將快照期間接收到的全部命令緩存.SLAVEOF NO ONE
命令將Slave提高成Master繼續服務;SLAVEOF
將其設置爲新Master的Slave, 便可將數據同步回來.注意: 當開啓複製且Master關閉持久化時, Master崩潰後必定不能直接重啓Master, 這是由於當Master重啓後, 由於沒有開啓持久化, 因此Redis內的全部數據都會被清空, 這時Salve從Master接受數據, 全部的Slave也會被清空, 致使Slave持久化失去意義.
關於Redis複製的詳細介紹以及配置方式可參考博客:Redis研究 -主從複製.
當Master遭遇異常中斷服務後, 須要手動選擇一個Slave升級爲Master, 以使系統可以繼續提供服務. 然而整個過程相對麻煩且須要人工介入, 難以實現自動化. 爲此Redis提供了哨兵Sentinel.
Sentinel哨兵是Redis高可用性解決方案之一: 由一個/多個Sentinel實例組成的Sentinel系統能夠監視任意多個Master以及下屬Slave, 並在監控到Master進入下線狀態時, 自動將其某個Slave提高爲新的Master, 而後由新的Master代替已下線的Master繼續處理命令請求.
- 如圖: 若此時Master:server1進入下線狀態, 那麼Slave: server2,server3,server4對Master的複製將被迫停止,而且Sentinel系統也會察覺到server1已下線, 當下線時長超過用戶設定的下線時長時, Sentinel系統就會對server1執行故障轉移操做:
- Sentinel會挑選server1下屬的其中一臺Slave, 將其提高爲新Master;
- 而後Sentinel向server1下屬的全部Slave發送新的複製指令,讓他們成爲新Master的Salve, 當全部Salve都開始複製新Master時, 故障轉移操做完成.
- 另外, Sentinel還會繼續監視已下線的server1, 並在他從新上線時, 將其設置爲新Master的Slave.
關於Redis哨兵的詳細介紹以及配置方式可參考博客:Redis Sentinel(哨兵):集羣解決方案.
Cluster是Redis提供的另外一高可用性解決方案:Redis集羣經過分片(sharding)來進行數據共享, 並提供複製與故障轉移功能.
一個 Redis 集羣一般由多個節點組成, 最初每一個節點都是相互獨立的,要組建一個真正可工做的集羣, 必須將各個獨立的節點鏈接起來.鏈接各個節點的工做可使用CLUSTER MEET
命令完成:
CLUSTER MEET <ip> <port>
向一個節點發送CLUSTER MEET
命令,可使其與ip
+port
所指定的節點進行握手,當握手成功時, 就會將目標節點添加到當前節點所在的集羣中.
CLUSTER MEET 127.0.0.1 7001
命令,可將節點7001添加到節點7000所在的集羣中: CLUSTER MEET 127.0.0.1 7002
命令,一樣也可將節點7002也拉進來: 關於Redis-Cluster的詳細介紹以及更多配置方式可參考博客:redis-cluster研究和使用.
經過在配置文件中使用requirepass
參數可爲Redis設置密碼:
requirepass œ∑´®†¥¨ˆøπ
這樣客戶端每次鏈接都須要發送密碼,不然Redis拒絕執行客戶端命令:
AUTH œ∑´®†¥¨ˆøπ
Redis支持在配置文件中將命令重命名, 以保證只有本身的應用可使用該命令:
rename-command FLUSHALL qwertyuiop
若是但願禁用某個命令,可將命令重命名爲空字符串.
SLOWLOG
slowlog-log-slower-than 10000 # 超時限制(單位微秒)
slowlog-max-len 128 # 記錄條數限制
MONITOR
: 監控Redis執行的全部命令
注意:
MONITOR
命令很是影響Redis性能, 一個客戶端使用MONITOR
會下降Redis將近一半的負載能力. Instagram團隊開發了一個基於MONITOR
命令的Redis查詢分析工具redis-faina, 可根據MONITOR
的監控結果分析出最經常使用的命令/訪問最頻繁的key
等信息, 詳細可參考博客:關於 Redis 的性能分析工具 Redis Faina.
其餘經常使用管理工具
TIME # 系統時間戳與微秒數
DBSIZE # 當前數據庫的key數量
INFO # Redis服務器信息
CONFIG GET # 獲取配置信息
CONFIG SET # 設置配置信息
CONFIG REWRITE # 把值寫到配置文件
CONFIG RESTART # 更新INFO命令信息
CLIENT LIST # 客戶端列表
CLIENT KILL # 關閉某個客戶端
CLIENT SETNAME # 爲客戶端設置名字
CLIENT GETNAME # 獲取客戶端名字
DEBUG OBJECT key # 調試選項,查看一個key的信息
DEBUG SEGFAULT # 模擬段錯誤,使服務器崩潰
OBJECT (refcount|encoding|idletime) key