1.Redis分布式鎖
1.1鎖的處理
-
單應用中使用鎖:單進程多線程
synchronize、Lock
-
分布式應用中使用鎖:多進程
1.2分布式鎖的實現
-
基於數據庫的樂觀鎖實現分布式鎖
-
基於zookeeper臨時節點的分布式鎖
-
基於redis的分布式鎖
1.3分布式鎖注意事項
-
互斥性:在任意時刻,只有一個客戶端能持有鎖
-
同一性:加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
-
可重入性:即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖。
1.4實現分布式鎖
1.4.1獲取鎖
- (推薦)方式1(使用set命令實現):
/**
* 使用redis的set命令實現獲取分布式鎖
* @param lockKey 可以就是鎖
* @param requestId 請求ID,保證同一性
* @param expireTime 過期時間,避免死鎖
* @return
*/
public static boolean getLock(String lockKey,String requestId,int expireTime) {
//NX:保證互斥性
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
- 方式2(使用setnx命令實現):
不推薦是因為方法代碼塊不是一個原子操作
public static boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if(result == 1) {
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}
1.4.2釋放鎖
- 方式1(del命令實現):
/**
* 釋放分布式鎖
* @param lockKey
* @param requestId
*/
public static void releaseLock(String lockKey,String requestId) {
if (requestId.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
}
}
- (推薦)方式2(redis+lua腳本實現)
public static boolean releaseLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (result.equals(1L)) {
return true;
}
return false;
}
2.Redis的主從復制
2.1什么是主從復制
持久化保證了即使redis服務重啟也不會丟失數據,因為redis服務重啟后會將硬盤上持久化的數據恢復到內存中,但是當redis服務器的硬盤損壞了可能會導致數據丟失,不過通過redis的主從復制機制就可以避免這種單點故障。同時在分布式系統中為了解決單點問題, 通常會把數據復制多個副本部署到其他機器, 滿足故障恢復和負載均衡等需求。
說明:
-
主redis中的數據有兩個副本(replication)即從redis1和從redis2,即使一台redis服務器宕機其它兩台redis服務也可以繼續提供服務。
-
主redis中的數據和從redis上的數據保持實時同步,當主redis寫入數據時通過主從復制機制會復制到兩個從redis服務上。
-
只有一個主redis,可以有多個從redis。
-
主從復制不會阻塞master,在同步數據時,master 可以繼續處理client 請求
-
一個redis可以即是主又是從,如下圖:
2.2主從配置
前提准備:復制兩份redis.conf修改為redis-6379.conf(主配置),redis-6380.conf(從配置)
修改:
dbfilename dump-6379.rdb
dbfilename dump-6380.rdb
總體配置結構如下:
2.2.1主redis配置
無需特殊配置。
2.2.2從redis配置
修改redis-6380.conf
slaveof
slaveof 127.0.0.1 6379
上邊的配置說明當前【從服務器】對應的【主服務器】的IP是127.0.0.1,端口是6379。
默認情況下,從節點使用slave-read-only=yes 配置為只讀模式,由於復制只能從主節點到從節點,對於從節點的任何修改主節點都無法感知,修改從節點會導致數據不一致。因此建議線上不要修改從節點的只讀模式
主節點:
[root@localhost bin]# ./redis-cli -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set s1 111
OK
127.0.0.1:6379>
從節點:
[root@localhost bin]# ./redis-cli -p 6380
127.0.0.1:6380> keys *
(empty list or set)
127.0.0.1:6380> keys *
1) "s1"
127.0.0.1:6380> set s1 22222
(error) READONLY You can't write against a read only slave.
從運行結果中看到復制已經工作了, 針對主節點6379的任何修改都可以,同步到從節點6380中。
slaveof本身是異步命令, 執行slaveof命令時, 節點只保存主節點信息后返回, 后續復制流程在節點內部異步執行。 主從節點復制成功建立后, 可以使用info replication命令查看復制相關狀態。
在
127.0.0.1:6379> info replication
# Replication
role:master ###表示當前節點是主節點
connected_slaves:1 ###從節點的連接個數為1個
slave0:ip=127.0.0.1,port=6380,state=online,offset=15033,lag=0 ####從節點的具體鏈接信息
master_replid:3a86ebe866b3a5c2c996d175c87b311bd9e15fdc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:15033
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:15033
在從節點6380上執行 info replication
127.0.0.1:6380> info replication
# Replication
role:slave ###表示當前節點是從節點
master_host:127.0.0.1 ##主節點連接主機
master_port:6379 ##主節點連接端口
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:15047
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:3a86ebe866b3a5c2c996d175c87b311bd9e15fdc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:15047
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:15047
2.3其他配置主從方式
- 在redis-server啟動命令后加入--slaveof {masterHost} {masterPort}生效
- 直接使用命令: slaveof {masterHost} {masterPort} 生效。
2.4實現原理
-
Redis的主從同步,分為全量同步和增量同步。
-
只有從機第一次連接上主機是全量同步
-
斷線重連有可能觸發全量同步也有可能是增量同步(master判斷runid是否一致)
-
除此之外的情況都是增量同步
2.4.1全量同步
Redis的全量同步過程主要分三個階段:
-
同步快照階段:Master創建並發送快照給Slave,Slave載入並解析快照。Master同時將此階段所產生的新的寫命令存儲到緩沖區。
-
同步寫緩沖階段:Master向Slave同步存儲在緩沖區的寫操作命令。
-
同步增量階段:Master向Slave同步寫操作命令。
2.4.2增量同步
-
Redis增量同步主要指Slave完成初始化后開始正常工作時,Master發生的寫操作同步到Slave的過程。
-
通常情況下,Master每執行一個寫命令就會向Slave發送相同的寫命令,然后Slave接收並執行。
2.5安全性
對於數據比較重要的節點, 主節點會通過設置requirepass參數進行密碼驗證, 這時所有的客戶端訪問必須使用auth命令實行校驗。 從節點與主節點的復制連接是通過一個特殊標識的客戶端來完成, 因此需要配置從節點的
masterauth參數與主節點密碼保持一致, 這樣從節點才可以正確地連接到主節點並發起復制流程。
# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.(如果主服務器受密碼保護(使用下面的“requirepass”配置指令),則可以在啟動復制同步進程之前告訴從服務器進行身份驗證,否則主服務器將拒絕從服務器的請求。)
#
# masterauth <master-password>
################################## SECURITY ###################################
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#(要求客戶端在處理任何其他命令之前發出AUTH 。在您不信任其他人訪問運行redis-server的主機的環境中,這可能很有用。
為了向后兼容性,這應該保留注釋,因為大多數人們不需要auth(例如,他們運行自己的服務器)。
警告:由於Redis速度相當快,外部用戶可以嘗試高達每秒150k密碼對一個好的框。這意味着您應該使用一個非常強大的密碼,否則它將非常容易被打破。
)
# requirepass foobared
微信公眾號