


Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson
1. Kami menggunakan Redis untuk melaksanakan kunci yang diedarkan pada mulanya gunakan
Kunci dan gunakan skrip Lua untuk memastikan atomicity melepaskan kunci. Pelaksanaan manual ini menyusahkan laman web rasmi Redis juga dengan jelas menyatakan bahawa versi Java menggunakan untuk melaksanakannya. Editor juga melihat laman web rasmi dan perlahan-lahan memikirkannya, dan mengambil jarak dekat untuk merakamnya. Daripada tapak web rasmi untuk menyepadukan Springboot kepada tafsiran kod sumber, ambil satu nod sebagai contoh. SET resource-name anystring NX EX max-lock-time
Redisson
2. Mengapa menggunakan Redisson
1 Kami membuka laman web rasmi
laman web rasmi redis Cina
2 untuk menggunakan lain
3 Buka syor rasmi
4 Alamat Redisson
1 dependencies
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--redis分布式锁--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
2 Ambil tapak web rasmi sebagai contoh untuk melihat cara mengkonfigurasi
3. Tulis kelas konfigurasiimport org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author wangzhenjun * @date 2022/2/9 9:57 */ @Configuration public class MyRedissonConfig { /** * 所有对redisson的使用都是通过RedissonClient来操作的 * @return */ @Bean(destroyMethod="shutdown") public RedissonClient redisson(){ // 1. 创建配置 Config config = new Config(); // 一定要加redis:// config.useSingleServer().setAddress("redis://192.168.17.130:6379"); // 2. 根据config创建出redissonClient实例 RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }

@ResponseBody @GetMapping("/hello") public String hello(){ // 1.获取一把锁,只要锁名字一样,就是同一把锁 RLock lock = redisson.getLock("my-lock"); // 2. 加锁 lock.lock();// 阻塞试等待 默认加的都是30s // 带参数情况 // lock.lock(10, TimeUnit.SECONDS);// 10s自动解锁,自动解锁时间一定要大于业务的执行时间。 try { System.out.println("加锁成功" + Thread.currentThread().getId()); Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 3. 解锁 System.out.println("解锁成功:" + Thread.currentThread().getId()); lock.unlock(); } return "hello"; }

1 Buka kelas pelaksanaan RedissonLock
@Override public void lock() { try { // 我们发现不穿过期时间源码默认过期时间为-1 lock(-1, null, false); } catch (InterruptedException e) { throw new IllegalStateException(); } }

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { // 获取线程的id,占有锁的时候field的值为UUID:线程号id long threadId = Thread.currentThread().getId(); // 尝试获得锁 Long ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired 获得锁,返回 if (ttl == null) { return; } // 这里说明获取锁失败,就通过线程id订阅这个锁 RFuture<RedissonLockEntry> future = subscribe(threadId); if (interruptibly) { commandExecutor.syncSubscriptionInterrupted(future); } else { commandExecutor.syncSubscription(future); } try { // 这里进行自旋,不断尝试获取锁 while (true) { // 继续尝试获取锁 ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired 获取成功 if (ttl == null) { // 直接返回,挑出自旋 break; } // waiting for message 继续等待获得锁 if (ttl >= 0) { try { future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { if (interruptibly) { throw e; } future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } } else { if (interruptibly) { future.getNow().getLatch().acquire(); } else { future.getNow().getLatch().acquireUninterruptibly(); } } } } finally { // 取消订阅 unsubscribe(future, threadId); } // get(lockAsync(leaseTime, unit)); }
4 Masuk dan cuba dapatkan kaedah kunci
<🎜. >private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { // 直接进入异步方法 return get(tryAcquireAsync(leaseTime, unit, threadId)); } private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { // 这里进行判断如果没有设置参数leaseTime = -1 if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } // 此方法进行获得锁,过期时间为看门狗的默认时间 // private long lockWatchdogTimeout = 30 * 1000;看门狗默认过期时间为30s // 加锁和过期时间要保证原子性,这个方法后面肯定调用执行了Lua脚本,我们下面在看 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); // 开启一个定时任务进行不断刷新过期时间 ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e != null) { return; } // lock acquired 获得锁 if (ttlRemaining == null) { // 刷新过期时间方法,我们下一步详细说一下 scheduleExpirationRenewal(threadId); }); return ttlRemainingFuture;
5. Semak kaedah tryLockInnerAsync()
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, // 首先判断锁是否存在 "if (redis.call('exists', KEYS[1]) == 0) then " + // 存在则获取锁 "redis.call('hset', KEYS[1], ARGV[2], 1); " + // 然后设置过期时间 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // hincrby自增一 "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 锁的值大于1,说明是可重入锁,重置过期时间 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // 锁已存在,且不是本线程,则返回过期时间ttl "return redis.call('pttl', KEYS[1]);", Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
6 Pergi ke apa yang tinggal dalam 4 kaedah ScheduleExpirationRenewal() tugas berjadual
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getName() + " expiration", e);
return;
}
if (res) {
// reschedule itself
renewExpiration();
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
Salin selepas log masuk
5. Analisis kod sumber Lock.lock(10, TimeUnit.SECONDS) 1 Buka kelas pelaksanaan private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } RFuture<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error("Can't update lock " + getName() + " expiration", e); return; } if (res) { // reschedule itself renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }
@Override public void lock(long leaseTime, TimeUnit unit) { try { // 这里的过期时间为我们输入的10 lock(leaseTime, unit, false); } catch (InterruptedException e) { throw new IllegalStateException(); } }
kaedah
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { // 这里进行判断如果没有设置参数leaseTime = -1,此时我们为10 if (leaseTime != -1) { // 来到此方法 return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } // 此处省略后面内容,前面以详细说明。。。。 }
4. Buka kaedah lock()
Tidak sukar untuk kita mencari bahawa ia adalah sama dengan kaedah tanpa melepasi masa tamat, kecuali nilai leaseTime telah berubah. tryAcquireAsync()
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, // 首先判断锁是否存在 "if (redis.call('exists', KEYS[1]) == 0) then " + // 存在则获取锁 "redis.call('hset', KEYS[1], ARGV[2], 1); " + // 然后设置过期时间 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // hincrby自增一 "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 锁的值大于1,说明是可重入锁,重置过期时间 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // 锁已存在,且不是本线程,则返回过期时间ttl "return redis.call('pttl', KEYS[1]);", Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
tryLockInnerAsync()
1 Pelaksanaan kaedah terbuka
@Override public void unlock() { try { // 点击进入释放锁方法 get(unlockAsync(Thread.currentThread().getId())); } catch (RedisException e) { if (e.getCause() instanceof IllegalMonitorStateException) { throw (IllegalMonitorStateException) e.getCause(); } else { throw e; } } // Future<Void> future = unlockAsync(); // future.awaitUninterruptibly(); // if (future.isSuccess()) { // return; // } // if (future.cause() instanceof IllegalMonitorStateException) { // throw (IllegalMonitorStateException)future.cause(); // } // throw commandExecutor.convertException(future); }
kaedah
@Override public RFuture<Void> unlockAsync(long threadId) { RPromise<Void> result = new RedissonPromise<Void>(); // 解锁方法,后面展开说 RFuture<Boolean> future = unlockInnerAsync(threadId); // 完成 future.onComplete((opStatus, e) -> { if (e != null) { // 取消到期续订 cancelExpirationRenewal(threadId); // 将这个未来标记为失败并通知所有人 result.tryFailure(e); return; } // 状态为空,说明解锁的线程和当前锁不是同一个线程 if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); result.tryFailure(cause); return; } cancelExpirationRenewal(threadId); result.trySuccess(null); }); return result; }
3 . Buka
Kaedahprotected RFuture<Boolean> unlockInnerAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, // 判断释放锁的线程和已存在锁的线程是不是同一个线程,不是返回空 "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + // 释放锁后,加锁次数减一 "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + // 判断剩余数量是否大于0 "if (counter > 0) then " + // 大于0 ,则刷新过期时间 "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + // 释放锁,删除key并发布锁释放的消息 "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
Atas ialah kandungan terperinci Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Mod Redis cluster menyebarkan contoh Redis ke pelbagai pelayan melalui sharding, meningkatkan skalabilitas dan ketersediaan. Langkah -langkah pembinaan adalah seperti berikut: Buat contoh Redis ganjil dengan pelabuhan yang berbeza; Buat 3 contoh sentinel, memantau contoh redis dan failover; Konfigurasi fail konfigurasi sentinel, tambahkan pemantauan maklumat contoh dan tetapan failover; Konfigurasi fail konfigurasi contoh Redis, aktifkan mod kluster dan tentukan laluan fail maklumat kluster; Buat fail nodes.conf, yang mengandungi maklumat setiap contoh Redis; Mulakan kluster, laksanakan perintah Buat untuk membuat kluster dan tentukan bilangan replika; Log masuk ke kluster untuk melaksanakan perintah maklumat kluster untuk mengesahkan status kluster; buat

Cara Mengosongkan Data Redis: Gunakan perintah Flushall untuk membersihkan semua nilai utama. Gunakan perintah flushdb untuk membersihkan nilai utama pangkalan data yang dipilih sekarang. Gunakan Pilih untuk menukar pangkalan data, dan kemudian gunakan FlushDB untuk membersihkan pelbagai pangkalan data. Gunakan perintah DEL untuk memadam kunci tertentu. Gunakan alat REDIS-CLI untuk membersihkan data.

Untuk membaca giliran dari Redis, anda perlu mendapatkan nama giliran, membaca unsur -unsur menggunakan arahan LPOP, dan memproses barisan kosong. Langkah-langkah khusus adalah seperti berikut: Dapatkan nama giliran: Namakannya dengan awalan "giliran:" seperti "giliran: my-queue". Gunakan arahan LPOP: Keluarkan elemen dari kepala barisan dan kembalikan nilainya, seperti LPOP Queue: My-Queue. Memproses Baris kosong: Jika barisan kosong, LPOP mengembalikan nihil, dan anda boleh menyemak sama ada barisan wujud sebelum membaca elemen.

Menggunakan Arahan Redis memerlukan langkah -langkah berikut: Buka klien Redis. Masukkan arahan (nilai kunci kata kerja). Menyediakan parameter yang diperlukan (berbeza dari arahan ke arahan). Tekan Enter untuk melaksanakan arahan. Redis mengembalikan tindak balas yang menunjukkan hasil operasi (biasanya OK atau -r).

Menggunakan REDIS untuk mengunci operasi memerlukan mendapatkan kunci melalui arahan SETNX, dan kemudian menggunakan perintah luput untuk menetapkan masa tamat tempoh. Langkah-langkah khusus adalah: (1) Gunakan arahan SETNX untuk cuba menetapkan pasangan nilai utama; (2) Gunakan perintah luput untuk menetapkan masa tamat tempoh untuk kunci; (3) Gunakan perintah DEL untuk memadam kunci apabila kunci tidak lagi diperlukan.

Cara terbaik untuk memahami kod sumber REDIS adalah dengan langkah demi langkah: Dapatkan akrab dengan asas -asas Redis. Pilih modul atau fungsi tertentu sebagai titik permulaan. Mulakan dengan titik masuk modul atau fungsi dan lihat baris kod mengikut baris. Lihat kod melalui rantaian panggilan fungsi. Berhati -hati dengan struktur data asas yang digunakan oleh REDIS. Kenal pasti algoritma yang digunakan oleh Redis.

Gunakan alat baris perintah redis (redis-cli) untuk mengurus dan mengendalikan redis melalui langkah-langkah berikut: Sambungkan ke pelayan, tentukan alamat dan port. Hantar arahan ke pelayan menggunakan nama arahan dan parameter. Gunakan arahan bantuan untuk melihat maklumat bantuan untuk arahan tertentu. Gunakan perintah berhenti untuk keluar dari alat baris arahan.

Pada sistem CentOS, anda boleh mengehadkan masa pelaksanaan skrip LUA dengan mengubah fail konfigurasi REDIS atau menggunakan arahan REDIS untuk mengelakkan skrip jahat daripada memakan terlalu banyak sumber. Kaedah 1: Ubah suai fail konfigurasi Redis dan cari fail konfigurasi Redis: Fail konfigurasi Redis biasanya terletak di /etc/redis/redis.conf. Edit Fail Konfigurasi: Buka fail konfigurasi menggunakan editor teks (seperti Vi atau nano): sudovi/etc/redis/redis.conf Tetapkan had masa pelaksanaan skrip lua: Tambah atau ubah suai baris berikut dalam fail konfigurasi untuk menetapkan masa pelaksanaan maksimum skrip lua (unit: milidor)
