C# Redis分布式锁(RedLock)
Redis單節(jié)點(diǎn)的分布式鎖只需要注意三點(diǎn)就可以了:
1.加鎖并設(shè)置鎖的過期時(shí)間必須是原子操作;
2.鎖的value值必須要有唯一性;
3.釋放鎖的時(shí)候要驗(yàn)證其value值,不是自己加的鎖不能釋放.
但是單節(jié)點(diǎn)分布式鎖最大的缺點(diǎn)就是,它只作用在一個(gè)Redis節(jié)點(diǎn)上,如果該節(jié)點(diǎn)掛了,那就掛了.
那可不可以通過哨兵機(jī)制來保證高可用呢?
答案是不行.
因?yàn)镽edis在進(jìn)行主從復(fù)制的時(shí)候是異步的.
假設(shè) clientA 拿到鎖后,在 master 還沒同步到 slave 時(shí),master 發(fā)生了故障,這時(shí)候 salve 升級(jí)為 master,導(dǎo)致鎖丟失.
RedLock 的思想是:假設(shè)有5個(gè)Redis節(jié)點(diǎn).這些節(jié)點(diǎn)完全相互獨(dú)立,不存在主從或者集群機(jī)制,都是 master.并且這5個(gè)Redis實(shí)例運(yùn)行在5臺(tái)機(jī)器上,這樣保證他們不會(huì)同時(shí)宕掉.
客戶端應(yīng)該按照以下操作來獲取鎖:
1.獲取當(dāng)前時(shí)間戳,假設(shè)是T1.
2.依次嘗試從這5個(gè)Redis實(shí)例獲取鎖.當(dāng)客戶端向Redis請(qǐng)求獲取鎖時(shí),客戶端應(yīng)該設(shè)置超時(shí)時(shí)間,并且這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間.比如你的鎖自動(dòng)失效時(shí)間為10秒,則超時(shí)時(shí)間應(yīng)該在5-50毫秒之間.這樣可以避免Redis已經(jīng)掛掉的情況下,客戶端還在等待響應(yīng)結(jié)果.如果Redis沒有在規(guī)定時(shí)間內(nèi)響應(yīng),客戶端應(yīng)該盡快嘗試去另外一個(gè)Redis實(shí)例請(qǐng)求獲取鎖.
3.請(qǐng)求完所有的Redis節(jié)點(diǎn)后,只有滿足如下兩點(diǎn),才算真正的獲取到鎖:
1)當(dāng)前時(shí)間 - T1 的時(shí)間差小于鎖的過期時(shí)間.比如T1=00:00:00,然后從5個(gè)Redis節(jié)點(diǎn)都拿到了鎖,當(dāng)前時(shí)間是 00:00:05,也就是說獲取鎖一共用了5秒鐘.假設(shè)鎖的過期時(shí)間是3秒,那么這次獲取鎖的操作就算失敗了.
2)從(N/2+1)個(gè)Redis節(jié)點(diǎn)都獲取到鎖.這個(gè)很好理解,5個(gè)節(jié)點(diǎn),你拿2個(gè),我拿2個(gè),到底算誰的?
總結(jié)一句話就是:從開始獲取鎖計(jì)時(shí),只要在鎖的過期時(shí)間內(nèi)成功獲取到一半以上的鎖便算成功,否則算失敗.
4.當(dāng)客戶端獲取到了鎖,鎖的真正有效時(shí)間 = 鎖的過期時(shí)間 - 獲取鎖所使用的時(shí)間(也就是第3步計(jì)算出來的時(shí)間).
5.如果客戶端由于某些原因(比如獲取鎖的實(shí)例個(gè)數(shù)小于N/2+1,或者已經(jīng)超過了有效時(shí)間),沒有獲取到鎖,客戶端便會(huì)在所有的Redis實(shí)例上進(jìn)行解鎖(即使某些Redis實(shí)例根本就沒有加鎖成功),因?yàn)榭赡芤呀?jīng)獲取了小于 N/2+1個(gè)鎖,必須釋放掉,否則會(huì)影響其他客戶端獲取鎖.
關(guān)于是否啟動(dòng)AOF永久存儲(chǔ),需要有所取舍.
1.永久啟動(dòng),由于Redis的過期機(jī)制是按照unix時(shí)間戳走的,所以當(dāng)我們重啟Redis后,依然會(huì)按照規(guī)定的時(shí)間過期.但是永久啟動(dòng)對(duì)性能有一定影響;
2.采用默認(rèn)的1秒1次.如果在1秒內(nèi)斷電,會(huì)導(dǎo)致數(shù)據(jù)丟失,這時(shí)候如果立刻重啟會(huì)導(dǎo)致鎖的互斥性實(shí)效.
所以有效的解決方案是,采用AOF,1秒1次,不管什么原因宕機(jī)后,等待一定時(shí)間再重啟.這個(gè)時(shí)間就是鎖的過期時(shí)間.
Demo:
安裝官方提供的 RedLock.net
Startup:
public class Startup
{
private RedLockFactory _redLockFactory;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var endPoints = new List<RedLockEndPoint>
{
new DnsEndPoint("127.0.0.1", 6379),
new DnsEndPoint("127.0.0.1", 6380),
new DnsEndPoint("127.0.0.1", 6381)
};
_redLockFactory = RedLockFactory.Create(endPoints);
services.AddSingleton(typeof(IDistributedLockFactory), _redLockFactory);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//應(yīng)用程序結(jié)束時(shí)釋放,因?yàn)椴皇侨萜鲃?chuàng)建的對(duì)象
applicationLifetime.ApplicationStopping.Register(() =>
{
_redLockFactory.Dispose();
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
測(cè)試api:
[ApiController]
public class ValuesController : ControllerBase
{
private static int _stock = 10;
private readonly IDistributedLockFactory _distributedLockFactory;
public ValuesController(IDistributedLockFactory distributedLockFactory)
{
_distributedLockFactory = distributedLockFactory;
}
[Route("lockTest")]
[HttpGet]
public async Task<int> DistributedLockTest()
{
// resource 鎖定的資源
var resource = "the-thing-we-are-locking-on";
// expiryTime 鎖的過期時(shí)間
var expiry = TimeSpan.FromSeconds(5);
// waitTime 等待時(shí)間
var wait = TimeSpan.FromSeconds(1);
// retryTime 等待時(shí)間內(nèi),多久重試一次
var retry = TimeSpan.FromMilliseconds(250);
using (var redLock = await _distributedLockFactory.CreateLockAsync(resource, expiry, wait, retry))
{
if (redLock.IsAcquired)
{
// 模擬執(zhí)行業(yè)務(wù)邏輯
await Task.Delay(new Random().Next(100, 500));
if (stock > 0)
{
stock--;
return stock;
}
return stock;
}
Console.WriteLine($"{DateTime.Now} : 獲取鎖失敗");
}
return -99;
}
}
測(cè)試控制臺(tái):
static void Main(string[] args)
{
HttpClient client = new HttpClient();
var result = Parallel.For(0, 20, (i) =>
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var response = client.GetAsync($"http://localhost:5000/locktest").Result;
stopwatch.Stop();
var data = response.Content.ReadAsStringAsync().Result;
Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
});
client.Dispose();
Console.ReadKey();
}
測(cè)試結(jié)果:
總結(jié)
以上是生活随笔為你收集整理的C# Redis分布式锁(RedLock)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 孤独的英文网名72个
- 下一篇: 图像处理之生成ColorBar