Masalah dengan kunci teragih struktur tuan-hamba
Cara paling mudah untuk melaksanakan kunci yang diedarkan Redis ialah menciptanya dalam kunci Redis A , kunci ini mempunyai masa tamat tempoh (TTL) untuk memastikan bahawa kunci akhirnya akan dilepaskan secara automatik. Apabila pelanggan melepaskan sumber (membuka kuncinya), kunci akan dipadamkan.
Di permukaan nampaknya berfungsi dengan baik, tetapi terdapat satu titik masalah kegagalan yang serius: bagaimana jika Redis digantung? Anda mungkin mengatakan bahawa masalah ini boleh diselesaikan dengan menambah nod hamba. Tetapi ini biasanya tidak berfungsi. Penyegerakan tuan-hamba Redis biasanya tidak segerak, jadi ini tidak dapat mencapai penggunaan sumber secara eksklusif.
Terdapat keadaan perlumbaan yang jelas dalam senario ini (struktur tuan-hamba):
Klien A memperoleh kunci daripada tuan
Tuan gagal sebelum tuan menyegerakkan kunci kepada hamba.
Nod hamba dinaikkan pangkat kepada nod induk
Klien B memperoleh kunci daripada induk baharu
Sumber yang sepadan dengan kunci ini telah diperoleh oleh klien A sebelum ini. Kegagalan keselamatan!
Program ini kadangkala begitu bijak Contohnya, apabila nod ditutup, berbilang pelanggan berlaku untuk mendapatkan kunci pada masa yang sama. Selagi anda boleh bertolak ansur dengan kebarangkalian ralat yang rendah ini, tiada masalah untuk menggunakan penyelesaian berasaskan replikasi ini. Jika tidak, kami mengesyorkan agar anda melaksanakan penyelesaian yang diterangkan di bawah.
Pengenalan
Redis memperkenalkan konsep kunci merah untuk situasi ini. Dalam sistem kunci merah, tanda kejayaan memperoleh atau melepaskan kunci ialah operasi berjaya pada lebih separuh daripada nod.
Prinsip
Dengan mengandaikan terdapat N Redis master dalam persekitaran teragih Redis.. Nod ini bebas sepenuhnya antara satu sama lain, dan tiada replikasi tuan-hamba atau mekanisme penyelarasan kelompok lain. Kami sebelum ini telah menerangkan cara memperoleh dan melepaskan kunci dengan selamat dalam satu contoh Redis. Kami memastikan bahawa kunci akan diperoleh dan dilepaskan menggunakan kaedah ini pada setiap contoh (N). Dalam contoh ini, kami mengandaikan bahawa terdapat 5 nod induk Redis Ini adalah tetapan yang munasabah, jadi kami perlu menjalankan kejadian ini pada 5 mesin atau 5 mesin maya untuk memastikan bahawa mereka tidak akan turun pada masa yang sama.
Untuk mendapatkan kunci, pelanggan hendaklah melakukan operasi berikut:
Dapatkan masa Unix semasa dalam milisaat.
Cuba dapatkan kunci daripada N kejadian dalam turutan, menggunakan kunci dan nilai rawak yang sama.
Apabila menetapkan kunci kepada Redis, pelanggan harus menetapkan sambungan rangkaian dan tamat masa tindak balas, yang sepatutnya kurang daripada masa tamat tempoh kunci.
Jika kunci anda tamat tempoh secara automatik dalam masa 10 saat, tamat masa hendaklah ditetapkan antara 5-50 milisaat. Ini menghalang pelanggan daripada menunggu tanpa henti untuk hasil respons walaupun pelayan Redis tidak berfungsi. Apabila tiada respons daripada pelayan diterima dalam masa yang ditetapkan, pelanggan harus cuba menyambung ke contoh Redis lain secepat mungkin.
Pelanggan menggunakan masa semasa tolak masa mula untuk memperoleh kunci (masa yang direkodkan dalam langkah 1) untuk mendapatkan masa yang digunakan untuk memperoleh kunci.
Syarat untuk berjaya memperoleh kunci ialah kunci mesti diperoleh daripada majoriti nod Redis (3 nod), dan masa penggunaan tidak boleh melebihi masa tamat tempoh kunci .
Jika kunci diperoleh, masa sah sebenar kunci adalah sama dengan masa sah tolak masa yang digunakan untuk memperoleh kunci (hasil dikira dalam langkah 3) .
Jika atas sebab tertentu, pemerolehan kunci gagal (kunci tidak diperoleh dalam sekurang-kurangnya N/2+1 Redis kejadian atau masa pemerolehan kunci telah melebihi masa efektif), klien harus Buka Kunci pada semua kejadian Redis (walaupun beberapa kejadian Redis tidak berjaya dikunci sama sekali).
Tapak web rasmi
Github rasmi: 8. Kunci dan penyegerak yang diedarkan· redisson/redisson Wik
>RLock lock1 = redissonInstance1.getLock("lock1"); RLock lock2 = redissonInstance2.getLock("lock2"); RLock lock3 = redissonInstance3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); // 同时加锁:lock1 lock2 lock3 // 红锁在大部分节点上加锁成功就算成功。 lock.lock(); ... lock.unlock();
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); // 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开 lock.lock(10, TimeUnit.SECONDS); // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); ... lock.unlock();
org.redisson.RedissonMultiLock.java#tryLock(long waitTime, long leaseTime, TimeUnit unit)源码如下:
final List<RLock> locks = new ArrayList<>(); /** * Creates instance with multiple {@link RLock} objects. * Each RLock object could be created by own Redisson instance. * * @param locks - array of locks */ public RedissonMultiLock(RLock... locks) { if (locks.length == 0) { throw new IllegalArgumentException("Lock objects are not defined"); } this.locks.addAll(Arrays.asList(locks)); } public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long newLeaseTime = -1; if (leaseTime != -1) { newLeaseTime = unit.toMillis(waitTime)*2; } long time = System.currentTimeMillis(); long remainTime = -1; if (waitTime != -1) { remainTime = unit.toMillis(waitTime); } long lockWaitTime = calcLockWaitTime(remainTime); /** * 1. 允许加锁失败节点个数限制(N-(N/2+1)) */ int failedLocksLimit = failedLocksLimit(); /** * 2. 遍历所有节点通过EVAL命令执行lua加锁 */ List<RLock> acquiredLocks = new ArrayList<>(locks.size()); for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) { RLock lock = iterator.next(); boolean lockAcquired; /** * 3.对节点尝试加锁 */ try { if (waitTime == -1 && leaseTime == -1) { lockAcquired = lock.tryLock(); } else { long awaitTime = Math.min(lockWaitTime, remainTime); lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS); } } catch (RedisResponseTimeoutException e) { // 如果抛出这类异常,为了防止加锁成功,但是响应失败,需要解锁所有节点 unlockInner(Arrays.asList(lock)); lockAcquired = false; } catch (Exception e) { // 抛出异常表示获取锁失败 lockAcquired = false; } if (lockAcquired) { /** *4. 如果获取到锁则添加到已获取锁集合中 */ acquiredLocks.add(lock); } else { /** * 5. 计算已经申请锁失败的节点是否已经到达 允许加锁失败节点个数限制 (N-(N/2+1)) * 如果已经到达, 就认定最终申请锁失败,则没有必要继续从后面的节点申请了 * 因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功 */ if (locks.size() - acquiredLocks.size() == failedLocksLimit()) { break; } if (failedLocksLimit == 0) { unlockInner(acquiredLocks); if (waitTime == -1 && leaseTime == -1) { return false; } failedLocksLimit = failedLocksLimit(); acquiredLocks.clear(); // reset iterator while (iterator.hasPrevious()) { iterator.previous(); } } else { failedLocksLimit--; } } /** * 6.计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回false */ if (remainTime != -1) { remainTime -= System.currentTimeMillis() - time; time = System.currentTimeMillis(); if (remainTime <= 0) { unlockInner(acquiredLocks); return false; } } } if (leaseTime != -1) { List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size()); for (RLock rLock : acquiredLocks) { RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); futures.add(future); } for (RFuture<Boolean> rFuture : futures) { rFuture.syncUninterruptibly(); } } /** * 7.如果逻辑正常执行完则认为最终申请锁成功,返回true */ return true; }
Atas ialah kandungan terperinci Apakah prinsip menggunakan kunci merah Redisson dalam Redis?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!