点一下关注吧!!!非常感谢!!持续更新!!!
目前已经更新到了:
Hadoop(已更完)
HDFS(已更完)
MapReduce(已更完)
Hive(已更完)
Flume(已更完)
Sqoop(已更完)
Zookeeper(已更完)
HBase(已更完)
Redis (正在更新…)
章节内容
上节我们完成了:
Redis缓存相关的概念
缓存穿透、缓存击穿、数据不一致性等
HotKey、BigKey等问题
针对上述问题提出一些解决方案
分乐观锁介绍
乐观锁基于CAS(Compare And Swap)思想,比较和替换,是不具有互斥性,不会产生锁等待而消耗资源,但需要反复的重试,能比较快的响应。
Watch实现
watch介绍
我们可以使用 Redis 来实现乐观锁:
利用 Redis 的 watch 功能,监控 Redis-Key的状态值
获取 RedisKey 的值
创建 Redis 事务
给这个Key的值+1
然后去执行这个事务,如果 key 的值被修改过则修改,key不加1
wacth实现
暂时就先忽略编码规范的内容,就先实现即可。
具体编写逻辑如下:
public class Test02 { public static void main(String[] args) { String redisKey = "lock"; ExecutorService executor = Executors.newFixedThreadPool(20); try { Jedis jedis = new Jedis("h121.wzk.icu", 6379); jedis.del(redisKey); jedis.set(redisKey, "0"); jedis.close(); } catch (Exception e) { e.printStackTrace(); } for (int i = 0; i < 300; i ++) { executor.execute(() -> { Jedis jedis = null; try { jedis = new Jedis("h121.wzk.icu", 6379); jedis.watch(redisKey); String redisValue = jedis.get(redisKey); int value = Integer.valueOf(redisValue); String userInfo = UUID.randomUUID().toString(); if (value < 20) { Transaction tx = jedis.multi(); tx.incr(redisKey); List<Object> list = tx.exec(); if (list != null && !list.isEmpty()) { System.out.println("获取锁成功, 用户信息: " + userInfo + " 成功人数: " + (value + 1)); } } else { System.out.println("秒杀结束!"); } } catch (Exception e) { e.printStackTrace(); } finally { if (null != jedis) { jedis.close(); } } }); } executor.shutdown(); } }
运行之后,会看到已经在进行争抢了:
获取锁成功, 用户信息: e6e06770-f274-4d89-8369-65babc2e3073 成功人数: 1 获取锁成功, 用户信息: 2cc2803b-085e-47ee-9fe6-4bbe1f694fd5 成功人数: 2 获取锁成功, 用户信息: 525ad22c-abb2-4f94-868a-cca981f9d768 成功人数: 3 获取锁成功, 用户信息: 9af67396-798e-4e09-b524-6ddc5e1673ec 成功人数: 4 获取锁成功, 用户信息: d5aa82f4-7d25-42c1-b8db-01ff7cfaf6c6 成功人数: 5 获取锁成功, 用户信息: 7dcc0646-e7a0-4cc0-bdcc-b96c7e8ba98b 成功人数: 6 获取锁成功, 用户信息: 7c9276d0-eec9-462a-8a8b-87711406375b 成功人数: 8 获取锁成功, 用户信息: c43b0158-b211-4a91-b430-51eb6ef74ded 成功人数: 9 获取锁成功, 用户信息: 9ab9418f-5e52-4d28-9ea5-92bc6b8b7742 成功人数: 7 获取锁成功, 用户信息: 7692d829-f7ef-4e28-90a4-2222a14c45d4 成功人数: 11 获取锁成功, 用户信息: 52695f97-49bf-4a06-bc45-a8ee1abb4524 成功人数: 10 获取锁成功, 用户信息: 196e29cc-b2fe-4356-841c-1f4376e3d5ae 成功人数: 12 获取锁成功, 用户信息: 8bb39e3c-c751-4468-b948-50ccb6aeb533 成功人数: 13 获取锁成功, 用户信息: d9691236-13f0-452b-b765-bc15b094866b 成功人数: 14 获取锁成功, 用户信息: cb1b0291-de78-4779-b4e6-294121393e9f 成功人数: 15 获取锁成功, 用户信息: dc368684-533f-47b0-9847-3fbfbf8fee78 成功人数: 16 获取锁成功, 用户信息: 361d2d66-cb9d-4e79-9c85-19f1b83c136d 成功人数: 17 获取锁成功, 用户信息: bd6fe63f-e48a-48f1-b751-e091d19886a2 成功人数: 19 秒杀结束! 秒杀结束! 秒杀结束! 秒杀结束! 获取锁成功, 用户信息: dba287f8-65f0-4da8-a131-05304164b3aa 成功人数: 18 秒杀结束! 获取锁成功, 用户信息: 05c5c5f9-f9cd-48b3-a266-c4ff3f256814 成功人数: 20 秒杀结束! 秒杀结束! 秒杀结束!
SETNX
setnx介绍
共享资源互斥
共享资源串行化
单应用中使用锁:单进程但是多线程
synchronized、ReentrantLock
分布式应用中的锁:多进程多线程
分布式锁是控制分布式系统之间同步访问共享资源的一种方式
利用Redis的单线程特性对共享资源进行串行化处理
SETNX实现
获取锁方式1 SET
public boolean getLock(String lockKey,String requestId,int expireTime) { // NX:保证互斥性 // hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁 String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime); if("OK".equals(result)) { return true; } return false; }
获取锁方式2 SETNX
public boolean getLock(String lockKey,String requestId,int expireTime) { Long result = jedis.setnx(lockKey, requestId); if(result == 1) { // 成功设置 进程down 永久有效 别的进程就无法获得锁 jedis.expire(lockKey, expireTime); return true; } return false; }
释放锁方式1 del
注意,当调用del方法时候,如果这把锁已经不属于当前客户端了,比如已经过期了,而别的人拿到了这把锁,此时删除就会导致释放掉了别人的锁。
public static void releaseLock(String lockKey,String requestId) { if (requestId.equals(jedis.get(lockKey))) { jedis.del(lockKey); } }
释放锁方式2 lua
public static boolean releaseLock(String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (result.equals(1L)) { return true; } return false; }
Redisson分布式锁
Redisson介绍
- Redisson是假设在Redis基础上的Java驻内存数据网格(In-Memory Data Grid)
- Redisson是基于NIO的Netty框架上,生产环境使用分布式锁。
添加依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>2.7.0</version> </dependency>
配置Redisson
public class RedissonManager { private static final Config CONFIG = new Config(); private static Redisson redisson = null; static { CONFIG .useClusterServers() .setScanInterval(2000) .addNodeAddress("redis://h121.wzk.icu:6379") .addNodeAddress("redis://h122.wzk.icu:6379") .addNodeAddress("redis://h123.wzk.icu:6379"); redisson = (Redisson) Redisson.create(CONFIG); } public static Redisson getRedisson() { return redisson; } }
获取与释放锁
public class DistributedRedisLock { private static Redisson redisson = RedissonManager.getRedisson(); private static final String LOCK_TITLE = "redisLock_"; public static boolean acquire(String lockName) { String key = LOCK_TITLE + lockName; RLock rLock = redisson.getLock(key); rLock.lock(3, TimeUnit.SECONDS); return true; } public static void release(String lockName) { String key = LOCK_TITLE + lockName; RLock rLock = redisson.getLock(key); rLock.unlock(); } }
业务使用
public String discount() throws IOException{ String key = "lock001"; // 加锁 DistributedRedisLock.acquire(key); // 执行具体业务逻辑 dosoming // 释放锁 DistributedRedisLock.release(key); // 返回结果 return soming; }
实现原理
分布式锁特性
- 互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
- 同一性:锁只能被持有该锁客户端删除,不能由其他客户端删除
- 可重入性:持有某个客户端可持续对该锁加锁 实现锁的续租
- 容错性:超过生命周期会自动进行释放,其他客户端可以获取到锁
常见分布式锁对比