大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁

点一下关注吧!!!非常感谢!!持续更新!!!

目前已经更新到了:

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;
}

实现原理

分布式锁特性

  • 互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
  • 同一性:锁只能被持有该锁客户端删除,不能由其他客户端删除
  • 可重入性:持有某个客户端可持续对该锁加锁 实现锁的续租
  • 容错性:超过生命周期会自动进行释放,其他客户端可以获取到锁

常见分布式锁对比

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
目录
相关文章
|
1月前
|
数据采集 存储 NoSQL
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
193 67
|
25天前
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
93 3
|
1月前
|
缓存 监控 NoSQL
Redis设计与实现——分布式Redis
Redis Sentinel 和 Cluster 是 Redis 高可用与分布式架构的核心组件。Sentinel 提供主从故障检测与自动切换,通过主观/客观下线判断及 Raft 算法选举领导者完成故障转移,但存在数据一致性和复杂度问题。Cluster 支持数据分片和水平扩展,基于哈希槽分配数据,具备自动故障转移和节点发现机制,适合大规模高并发场景。复制机制包括全量同步和部分同步,通过复制积压缓冲区优化同步效率,但仍面临延迟和资源消耗挑战。两者各有优劣,需根据业务需求选择合适方案。
|
26天前
|
存储 NoSQL Java
从扣减库存场景来讲讲redis分布式锁中的那些“坑”
本文从一个简单的库存扣减场景出发,深入分析了高并发下的超卖问题,并逐步优化解决方案。首先通过本地锁解决单机并发问题,但集群环境下失效;接着引入Redis分布式锁,利用SETNX命令实现加锁,但仍存在死锁、锁过期等隐患。文章详细探讨了通过设置唯一标识、续命机制等方法完善锁的可靠性,并最终引出Redisson工具,其内置的锁续命和原子性操作极大简化了分布式锁的实现。最后,作者剖析了Redisson源码,揭示其实现原理,并预告后续关于主从架构下分布式锁的应用与性能优化内容。
74 0
|
4月前
|
缓存 NoSQL 搜索推荐
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
241 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
|
4月前
|
NoSQL Redis 数据库
Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
通过本文的介绍,我们详细讲解了 Lua 脚本在 Redis 中的作用、`eval` 命令的使用方法以及 `redis.call` 和 `redis.pcall` 的区别和用法。通过合理使用 Lua 脚本,可以实现复杂的业务逻辑,确保操作的原子性,并减少网络开销,从而提高系统的性能和可靠性。
165 13
|
6月前
|
监控 安全
公司用什么软件监控电脑:Lua 脚本在监控软件扩展功能的应用
在企业环境中,电脑监控软件对保障信息安全、提升效率至关重要。Lua 脚本在此类软件中用于扩展功能,如收集系统信息、监控软件使用时长及文件操作,向指定服务器发送数据,支持企业管理和运营。
103 6
|
12月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
357 0
|
8月前
|
缓存 分布式计算 NoSQL
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
99 2
|
9月前
|
存储 JSON Ubuntu
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?

热门文章

最新文章