Rumah  >  Artikel  >  pangkalan data  >  Memberi anda pemahaman yang mendalam tentang kunci yang diedarkan dalam Redis

Memberi anda pemahaman yang mendalam tentang kunci yang diedarkan dalam Redis

青灯夜游
青灯夜游ke hadapan
2022-03-08 10:13:502017semak imbas

Adakah anda benar-benar memahami kunci yang diedarkan Redis? Artikel berikut akan memberi anda pengenalan yang mendalam kepada kunci yang diedarkan dalam Redis, dan bercakap tentang cara melaksanakan kunci, melepaskan kunci, kecacatan kunci yang diedarkan, dll. Saya harap ia akan membantu anda!

Memberi anda pemahaman yang mendalam tentang kunci yang diedarkan dalam Redis

Apakah itu kunci teragih

Bercakap tentang Redis, fungsi pertama yang kami fikirkan ialah keupayaan untuk cache data. Selain itu, Redis adalah satu -proses, ciri prestasi tinggi, ia juga sering digunakan untuk kunci yang diedarkan. [Cadangan berkaitan: Tutorial video Redis]

Kita semua tahu bahawa kunci berfungsi dalam program sebagai alat penyegerakan untuk memastikan sumber yang dikongsi hanya boleh diakses oleh satu utas pada masa yang sama Kita semua biasa dengan kunci, seperti disegerakkan dan Kunci, yang sering kita gunakan, bagaimanapun, kunci Java hanya boleh dijamin berkesan dalam satu mesin, dan tidak berkuasa dalam persekitaran kluster yang diedarkan gunakan kunci yang diedarkan.

Kunci teragih, seperti namanya, adalah kunci yang digunakan dalam pembangunan projek yang diedarkan Ia boleh digunakan untuk mengawal akses segerak kepada sumber yang dikongsi antara sistem yang diedarkan Secara umumnya, kunci yang diedarkan perlu memenuhi ciri-ciri berikut :

1. Eksklusiviti bersama: pada bila-bila masa, untuk sekeping data yang sama, hanya satu aplikasi boleh mendapatkan kunci yang diedarkan; , masa henti sebilangan kecil pelayan tidak menjejaskan penggunaan biasa Dalam kes ini, perkhidmatan yang menyediakan kunci teragih perlu digunakan dalam kelompok

3. Cegah tamat masa kunci: Jika pelanggan tidak melepaskan kunci secara aktif, pelayan akan melepaskan kunci secara automatik selepas satu tempoh masa untuk mengelakkan kebuntuan apabila pelanggan terputus atau rangkaian tidak dapat dicapai

4: penguncian dan buka kunci mesti dilakukan oleh yang sama pelayan, iaitu kunci Hanya pemegang boleh melepaskan kunci Kunci yang anda tambah tidak boleh dibuka kunci oleh orang lain

Terdapat banyak alat dalam industri yang boleh mencapai kesan kunci yang diedarkan. adalah seperti berikut: mengunci, membuka kunci , Cegah tamat masa kunci.

Memandangkan artikel ini bercakap tentang kunci yang diedarkan Redis, sudah tentu kami akan memanjangkannya dengan pengetahuan Redis.

Arahan untuk melaksanakan kunci

Mula-mula perkenalkan beberapa arahan Redis,

1 SETNX, penggunaannya ialah

SETNX key valueSETNX ialah 『 SET. "jika Tidak wujud" (jika ia tidak wujud, maka SET), ia mengembalikan 1 jika tetapan berjaya, jika tidak ia mengembalikan 0.

penggunaan setnx

Ia boleh dilihat apabila menukar

kunci

kepada kunci Selepas nilai ditetapkan kepada "Java", ia akan gagal jika ia ditetapkan kepada nilai lain Ia kelihatan sangat mudah, dan nampaknya kunci itu eksklusif, tetapi terdapat masalah maut, iaitu kunci. tidak mempunyai masa tamat tempoh, jadi Pertama, melainkan kunci dipadamkan secara manual atau masa tamat tempoh ditetapkan selepas memperoleh kunci, benang lain tidak akan mendapat kunci. Dalam kes ini, kami sentiasa boleh menambah masa tamat tempoh pada kunci dan terus membiarkan benang melakukan operasi dua langkah apabila memperoleh kunci:

Penyelesaian ini juga mempunyai masalah, kerana memperoleh kunci dan Menetapkan masa tamat tempoh terbahagi kepada dua langkah Ia bukan operasi atom Ia mungkin
`SETNX Key 1`
`EXPIRE Key Seconds`
berjaya memperoleh kunci tetapi gagal menetapkan masa

buang masa? Tetapi jangan risau, pegawai Redis telah mempertimbangkan ini untuk kami, jadi arahan berikut diperkenalkan

2. SETEX, penggunaan

SETEX key seconds value akan The nilai

dikaitkan dengan

dan menetapkan masa untuk hidup value kepada key (dalam saat). Jika key sudah wujud, arahan SETEX akan menimpa nilai lama. secondskeyArahan ini serupa dengan dua arahan berikut:

Dua langkah ini adalah atom dan akan diselesaikan pada masa yang sama.
`SET key value`
`EXPIRE key seconds  # 设置生存时间`

penggunaan setex

3 PSETEX, penggunaan

PSETEX key milliseconds valueArahan ini serupa dengan SETEX. arahan. Tetapi ia menetapkan jangka hayat

dalam milisaat, bukan saat seperti arahan SETEX.

keyWalau bagaimanapun, bermula dari Redis versi 2.6.12, arahan SET boleh menggunakan parameter untuk mencapai kesan yang sama seperti tiga arahan SETNX, SETEX dan PSETEX.

Sebagai contoh, selepas menambah parameter NX dan EX pada arahan ini

, kesannya adalah bersamaan dengan SETEX, yang juga merupakan cara paling biasa untuk mendapatkan kunci dalam Redis.
`SET key value NX EX seconds`

Cara melepaskan kunci

Perintah untuk melepaskan kunci adalah mudah, hanya padam kunci terus, tetapi seperti yang kami katakan sebelum ini, kerana kunci yang diedarkan mesti dilepaskan oleh pemegang kunci itu sendiri , jadi kami Anda mesti terlebih dahulu memastikan bahawa benang yang sedang melepaskan kunci adalah pemegangnya, dan kemudian memadamnya jika tiada masalah Dengan cara ini, ia menjadi proses dua langkah, yang nampaknya melanggar atomicity sekali lagi buat?

Jangan panik, kita boleh menggunakan skrip lua untuk memasang operasi dua langkah, seperti ini:

`if redis.call("get",KEYS[1]) == ARGV[1]`
`then`
 `return redis.call("del",KEYS[1])`
`else`
 `return 0`
`end`

KEYS[1]是当前key的名称,ARGV[1]可以是当前线程的ID(或者其他不固定的值,能识别所属线程即可),这样就可以防止持有过期锁的线程,或者其他线程误删现有锁的情况出现。

代码实现

知道了原理后,我们就可以手写代码来实现Redis分布式锁的功能了,因为本文的目的主要是为了讲解原理,不是为了教大家怎么写分布式锁,所以我就用伪代码实现了。

首先是redis锁的工具类,包含了加锁和解锁的基础方法:

`public class RedisLockUtil {`
 `private String LOCK_KEY = "redis_lock";`
 `// key的持有时间,5ms`
 `private long EXPIRE_TIME = 5;`
 `// 等待超时时间,1s`
 `private long TIME_OUT = 1000;`
 `// redis命令参数,相当于nx和px的命令合集`
 `private SetParams params = SetParams.setParams().nx().px(EXPIRE_TIME);`
 `// redis连接池,连的是本地的redis客户端`
 `JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);`
 `/**`
 `* 加锁`
 `*`
 `* @param id`
 `*            线程的id,或者其他可识别当前线程且不重复的字段`
 `* @return`
 `*/`
 `public boolean lock(String id) {`
 `Long start = System.currentTimeMillis();`
 `Jedis jedis = jedisPool.getResource();`
 `try {`
 `for (;;) {`
 `// SET命令返回OK ,则证明获取锁成功`
 `String lock = jedis.set(LOCK_KEY, id, params);`
 `if ("OK".equals(lock)) {`
 `return true;`
 `}`
 `// 否则循环等待,在TIME_OUT时间内仍未获取到锁,则获取失败`
 `long l = System.currentTimeMillis() - start;`
 `if (l >= TIME_OUT) {`
 `return false;`
 `}`
 `try {`
 `// 休眠一会,不然反复执行循环会一直失败`
 `Thread.sleep(100);`
 `} catch (InterruptedException e) {`
 `e.printStackTrace();`
 `}`
 `}`
 `} finally {`
 `jedis.close();`
 `}`
 `}`
 `/**`
 `* 解锁`
 `*`
 `* @param id`
 `*            线程的id,或者其他可识别当前线程且不重复的字段`
 `* @return`
 `*/`
 `public boolean unlock(String id) {`
 `Jedis jedis = jedisPool.getResource();`
 `// 删除key的lua脚本`
 `String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" + "   return redis.call('del',KEYS[1]) " + "else"`
 `+ "   return 0 " + "end";`
 `try {`
 `String result =`
 `jedis.eval(script, Collections.singletonList(LOCK_KEY), Collections.singletonList(id)).toString();`
 `return "1".equals(result);`
 `} finally {`
 `jedis.close();`
 `}`
 `}`
`}`

具体的代码作用注释已经写得很清楚了,然后我们就可以写一个demo类来测试一下效果:

`public class RedisLockTest {`
 `private static RedisLockUtil demo = new RedisLockUtil();`
 `private static Integer NUM = 101;`
 `public static void main(String[] args) {`
 `for (int i = 0; i < 100; i++) {`
 `new Thread(() -> {`
 `String id = Thread.currentThread().getId() + "";`
 `boolean isLock = demo.lock(id);`
 `try {`
 `// 拿到锁的话,就对共享参数减一`
 `if (isLock) {`
 `NUM--;`
 `System.out.println(NUM);`
 `}`
 `} finally {`
 `// 释放锁一定要注意放在finally`
 `demo.unlock(id);`
 `}`
 `}).start();`
 `}`
 `}`
`}`

我们创建100个线程来模拟并发的情况,执行后的结果是这样的:

代码执行结果

可以看出,锁的效果达到了,线程安全是可以保证的。

当然,上面的代码只是简单的实现了效果,功能肯定是不完整的,一个健全的分布式锁要考虑的方面还有很多,实际设计起来不是那么容易的。

我们的目的只是为了学习和了解原理,手写一个工业级的分布式锁工具不现实,也没必要,类似的开源工具一大堆(Redisson),原理都差不多,而且早已经过业界同行的检验,直接拿来用就行。

虽然功能是实现了,但其实从设计上来说,这样的分布式锁存在着很大的缺陷,这也是本篇文章想重点探讨的内容。

分布式锁的缺陷

一、客户端长时间阻塞导致锁失效问题

客户端1得到了锁,因为网络问题或者GC等原因导致长时间阻塞,然后业务程序还没执行完锁就过期了,这时候客户端2也能正常拿到锁,可能会导致线程安全的问题。

客户端长时间阻塞

那么该如何防止这样的异常呢?我们先不说解决方案,介绍完其他的缺陷后再来讨论。

二、redis服务器时钟漂移问题

如果redis服务器的机器时钟发生了向前跳跃,就会导致这个key过早超时失效,比如说客户端1拿到锁后,key的过期时间是12:02分,但redis服务器本身的时钟比客户端快了2分钟,导致key在12:00的时候就失效了,这时候,如果客户端1还没有释放锁的话,就可能导致多个客户端同时持有同一把锁的问题。

三、单点实例安全问题

如果redis是单master模式的,当这台机宕机的时候,那么所有的客户端都获取不到锁了,为了提高可用性,可能就会给这个master加一个slave,但是因为redis的主从同步是异步进行的,可能会出现客户端1设置完锁后,master挂掉,slave提升为master,因为异步复制的特性,客户端1设置的锁丢失了,这时候客户端2设置锁也能够成功,导致客户端1和客户端2同时拥有锁。

为了解决Redis单点问题,redis的作者提出了RedLock算法。

RedLock算法

该算法的实现前提在于Redis必须是多节点部署的,可以有效防止单点故障,具体的实现思路是这样的:

1、获取当前时间戳(ms);

2、先设定key的有效时长(TTL),超出这个时间就会自动释放,然后client(客户端)尝试使用相同的key和value对所有redis实例进行设置,每次链接redis实例时设置一个比TTL短很多的超时时间,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

比如:TTL(也就是过期时间)为5s,那获取锁的超时时间就可以设置成50ms,所以如果50ms内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁;

3、client通过获取所有能获取的锁后的时间减去第一步的时间,还有redis服务器的时钟漂移误差,然后这个时间差要小于TTL时间并且成功设置锁的实例数>= N/2 + 1(N为Redis实例的数量),那么加锁成功

比如TTL是5s,连接redis获取所有锁用了2s,然后再减去时钟漂移(假设误差是1s左右),那么锁的真正有效时长就只有2s了;

4、如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例。

根据这样的算法,我们假设有5个Redis实例的话,那么client只要获取其中3台以上的锁就算是成功了,用流程图演示大概就像这样: 

masa sah kunci

Baiklah, algoritma telah diperkenalkan Dari sudut reka bentuk, tidak ada keraguan bahawa idea utama Algoritma RedLock adalah Untuk menghalang satu titik kegagalan Redis dengan berkesan, ralat drift jam pelayan juga diambil kira semasa mereka bentuk TTL, yang meningkatkan keselamatan kunci yang diedarkan.

Tetapi adakah ini benar-benar berlaku? Bagaimanapun, saya secara peribadi merasakan bahawa kesannya adalah purata

Pertama sekali, kita dapat melihat bahawa dalam algoritma RedLock, masa berkesan kunci akan dikurangkan mengikut masa yang diperlukan untuk menyambung ke contoh Redis. . Jika proses ini disebabkan oleh masalah rangkaian Jika ia mengambil masa terlalu lama, masa berkesan yang tinggal untuk kunci akan sangat berkurangan Masa untuk klien mengakses sumber yang dikongsi adalah sangat singkat, dan kemungkinan kunci akan tamat tempoh semasa pemprosesan program. Lebih-lebih lagi, masa kunci yang berkesan perlu ditolak daripada hanyut jam pelayan, tetapi berapa banyak yang perlu ditolak Jika nilai ini tidak ditetapkan dengan baik, masalah boleh berlaku dengan mudah.

Perkara kedua ialah walaupun algoritma ini mengambil kira penggunaan berbilang nod untuk menghalang satu titik kegagalan Redis, jika nod ranap dan dimulakan semula, berbilang pelanggan masih boleh memperoleh kunci pada masa yang sama keadaan masa.

Anggapkan terdapat 5 nod Redis secara keseluruhan: A, B, C, D, E, klien 1 dan 2 masing-masing dikunci

  • Klien 1 berjaya dikunci A , B, C diperoleh, dan kunci diperoleh dengan jayanya (tetapi D dan E tidak dikunci).

  • Induk nod C turun, dan kemudian kunci tidak disegerakkan kepada hamba Selepas hamba dinaik taraf kepada induk, ia kehilangan kunci yang ditambahkan oleh klien 1.

  • Klien 2 memperoleh kunci pada masa ini, mengunci C, D dan E, dan memperoleh kunci itu dengan jayanya.

Dengan cara ini, klien 1 dan klien 2 mendapat kunci pada masa yang sama, dan bahaya tersembunyi bagi keselamatan program masih wujud. Selain itu, jika hanyut masa berlaku dalam salah satu nod ini, ia juga boleh menyebabkan isu keselamatan kunci.

Jadi, walaupun ketersediaan dan kebolehpercayaan dipertingkatkan melalui penggunaan berbilang contoh, RedLock tidak menyelesaikan sepenuhnya bahaya tersembunyi titik kegagalan tunggal Redis, dan juga tidak menyelesaikan masalah yang disebabkan oleh drift jam dan pelanggan jangka panjang menyekat. Masalah kegagalan masa kunci dan risiko keselamatan kunci masih wujud.

Kesimpulan

Sesetengah orang mungkin ingin bertanya lebih lanjut, apakah yang perlu dilakukan untuk memastikan keselamatan mutlak kunci?

Saya hanya boleh mengatakan bahawa anda tidak boleh memiliki kek anda dan juga memakannya Sebab mengapa kami menggunakan Redis sebagai alat kunci yang diedarkan sebahagian besarnya kerana kecekapan tinggi Redis dan ciri-ciri proses tunggal boleh dijamin dengan baik di bawah keadaan konkurensi yang tinggi, tetapi dalam banyak kes, prestasi dan keselamatan tidak dapat diseimbangkan sepenuhnya Jika anda mesti memastikan keselamatan kunci, anda boleh menggunakan perisian tengah lain seperti db dan zookeeper untuk kawalan keselamatan kunci dengan sangat baik, tetapi prestasi hanya boleh dikatakan tidak memuaskan, jika tidak semua orang akan menggunakannya sejak dahulu lagi.

Secara umumnya, jika Redis digunakan untuk mengawal sumber yang dikongsi dan keperluan keselamatan data yang tinggi diperlukan, penyelesaian terakhir adalah untuk mengawal data perniagaan secara idempoten, walaupun berbilang pelanggan memperoleh kunci tidak menjejaskan ketekalan data. Sudah tentu, tidak semua adegan sesuai untuk ini Pilihan khusus perlu diputuskan oleh setiap hakim Lagipun, tidak ada teknologi yang sempurna, hanya yang sesuai adalah yang terbaik.

Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Pengenalan kepada Pengaturcaraan! !

Atas ialah kandungan terperinci Memberi anda pemahaman yang mendalam tentang kunci yang diedarkan dalam Redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:segmentfault.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam