网站 报价单,做科技申报看什么网站,自己做网站自己买服务器,网站推广百度优化1 分布式锁
Java锁能保证一个JVM进程里多个线程交替使用资源。而分布式锁保证多个JVM进程有序交替使用资源#xff0c;保证数据的完整性和一致性。 分布式锁要求
互斥。一个资源在某个时刻只能被一个线程访问。避免死锁。避免某个线程异常情况不释放资源#xff0c;造成死锁…1 分布式锁
Java锁能保证一个JVM进程里多个线程交替使用资源。而分布式锁保证多个JVM进程有序交替使用资源保证数据的完整性和一致性。 分布式锁要求
互斥。一个资源在某个时刻只能被一个线程访问。避免死锁。避免某个线程异常情况不释放资源造成死锁。可重入。高可用。高性能。非阻塞没获取到锁直接返回失败。
2 实现
1 lua脚本
为了实现redis操作的原子性使用lua脚本。为了方便改脚本将脚本单独写在文件里。
-- 加锁脚本
if redis.call(setnx, KEYS[1], ARGV[1]) 1 thenredis.call(pexpire, KEYS[1], ARGV[2]);return true;
elsereturn false;
end-- 解锁脚本
if redis.call(get, KEYS[1]) ARGV[1] thenredis.call(del, KEYS[1]);return true;
elsereturn false;
end-- 更新锁脚本
if redis.call(get, KEYS[1]) ARGV[1] thenredis.call(pexpire, KEYS[1], ARGV[2]);-- pexpire与expire的区别是pexpire毫秒级,expire秒级return true;
elsereturn false;
end将脚本装在Springboot容器管理的bean里。
Configuration
public class RedisConfig {Bean(lock)public RedisScriptBoolean lockRedisScript() {DefaultRedisScript redisScript new DefaultRedisScript();redisScript.setResultType(Boolean.class);redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(/ratelimit/lock.lua)));return redisScript;}Bean(unlock)public RedisScriptBoolean unlockRedisScript() {DefaultRedisScript redisScript new DefaultRedisScript();redisScript.setResultType(Boolean.class);redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(/ratelimit/unlock.lua)));return redisScript;}Bean(refresh)public RedisScriptBoolean refreshRedisScript() {DefaultRedisScript redisScript new DefaultRedisScript();redisScript.setResultType(Boolean.class);redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(/ratelimit/refresh.lua)));return redisScript;}
}redis分布式锁业务类
Service
public class LockService {private static final long LOCK_EXPIRE 30_000;private static final Logger LOGGER LoggerFactory.getLogger(LockService.class);Autowiredprivate RedisTemplateString, Object redisTemplate;AutowiredQualifier(lock)private RedisScriptBoolean lockScript;AutowiredQualifier(unlock)private RedisScriptBoolean unlockScript;AutowiredQualifier(refresh)private RedisScriptBoolean refreshScript;public boolean lock(String key, String value) {boolean res redisTemplate.execute(lockScript, List.of(key), value, LOCK_EXPIRE);if (res false) {return false;}refresh(key, value);LOGGER.info(lock, key: {}, value: {}, res: {}, key, value, res);return res;}public boolean unlock(String key, String value) {Boolean res redisTemplate.execute(unlockScript, List.of(key), value);LOGGER.info(unlock, key: {}, value: {}, res: {}, key, value, res);return res ! null Boolean.TRUE.equals(res);}private void refresh(String key, String value) {Thread t new Thread(() - {while (true) {redisTemplate.execute(refreshScript, List.of(key), value, LOCK_EXPIRE);try {Thread.sleep(LOCK_EXPIRE / 2);} catch (InterruptedException e) {e.printStackTrace();}LOGGER.info(refresh, current time: {}, key: {}, value: {}, System.currentTimeMillis(), key, value);}});t.setDaemon(true); // 守护线程t.start();}
}测试类
SpringBootTest(classes DemoApplication.class)
public class LockServiceTest {Autowiredprivate LockService service;private int count 0;Testpublic void test() throws Exception {ListCompletableFutureVoid taskList new ArrayList();for (int threadIndex 0; threadIndex 10; threadIndex) {CompletableFutureVoid task CompletableFuture.runAsync(() - addCount());taskList.add(task);}CompletableFuture.allOf(taskList.toArray(new CompletableFuture[0])).join();}public void addCount() {String id UUID.randomUUID().toString().replace(-, );boolean tryLock service.lock(account, id);while (!tryLock) {tryLock service.lock(account, id);}for (int i 0; i 10_000; i) {count;}try {Thread.sleep(100_000);} catch (Exception e) {System.out.println(e);}for (int i 0; i 3; i) {boolean releaseLock service.unlock(account, id);if (releaseLock) {break;}}}
}3 存在的问题
这个分布式锁实现了互斥redis键映射资源如果存在键则资源正被某个线程持有。如果不存在键则资源空闲。 避免死锁靠的是设置reds键的过期时间同时开启守护线程动态延长redis键的过期时间直到该线程任务完结。 高性能。redis是内存数据库性能很高。同时lua脚本使得redis以原子性更新锁状态避免多次spirngboot与redis的网络IO。 非阻塞。lock()方法没有获取到锁立即返回false不会阻塞当前线程。
没有实现可重入和高可用。高可用需要redis集群支持。