并发数据问题之分布式锁

3 篇文章 0 订阅

并发数据问题之分布式锁

架构不是凭空设计出来的,是长出来的

什么是分布式锁

分布式锁听起来很高大上,其实等你实际用的时候你会发现也还好,redis实现分布式锁的核心就是setnx指令;

场景示例

之前做过一个微信社群运营的项目,这个项目可以很好的诠释为啥我们会用分布式锁,大家有兴趣可以看看这个:

企业微信关于后台调用token说明:https://developer.work.weixin.qq.com/document/path/91039

使用分布式锁的前提:

  • 分布式使用同一个标识调用
  • 标识是变化的,且可能新标识获取会影响老标识的使用

架构示意

在这里插入图片描述

逻辑示意

在这里插入图片描述

这里重试设置锁也可以是直接获取失败丢弃,要看具体业务要求;

分布式锁redis实现代码示例

service

public String lockTest() {
        String value = UUID.randomUUID().toString();
        try {
            boolean result = hadoopLock.tryGetLock(key, value, expireTime * 1000, blockTime * 1000);
            if(!result){
                return "获取锁失败,放弃执行";
            }
            System.out.println(Thread.currentThread().getName()+"获取锁后需要执行的代码");
            try {
                // 修眠模拟并发获取
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            // 释放锁
            hadoopLock.releaseLockASC(key, value);
        }

        return Thread.currentThread().getName()+ "执行完毕";
    }

hadoopLock-核心代码

/**
     * 尝试获取锁
     *
     * @param key
     * @param value
     * @param expireTime 过期时间
     * @param blockTime  阻塞时间 决定是尝试还是直接返回false
     * @return 返回结果
     */
    public boolean tryGetLock(String key, String value, long expireTime, long blockTime) {
        try {
            long startTryGetLockTime = System.currentTimeMillis();
            return retryGetLock(key, value, expireTime, blockTime, startTryGetLockTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

/**
     * 重复获取锁
     *
     * @param key
     * @param value
     * @param expireTime
     * @param blockTime
     * @param startTryGetLockTime 开始获取锁的时间
     * 这里可以暴力限流   获取不到锁不执行就好
     * @return
     */
    private boolean retryGetLock(String key, String value, long expireTime, long blockTime, long startTryGetLockTime) {
        // 小于阻塞时间,可以一直重试获取
        int count = 0; // 测试获取计数次数
        while (System.currentTimeMillis() - startTryGetLockTime < blockTime) {
            System.out.println(Thread.currentThread().getName() + "第" + count + "次" + "获取锁");
            count++;
            //如果不存在则插入
            //Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
            SetParams params = new SetParams().ex(10).nx();
            String set = jedis.set(key, value, params);
            if ("OK".equals(set)) {
                System.out.println(Thread.currentThread().getName() + "获取锁成功");
                // 获取锁成功 守护线程续期
                // 守护线程开启
                Thread watchDogThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int num = 0;
                        while (true){
                            num ++;
                            // 这里设置的过期时间是该value?续期1s
                            Boolean expire = redisTemplate.expire(key, 1000, TimeUnit.MICROSECONDS);
                            System.out.println("守护线程"+Thread.currentThread().getName()+"第"+num+"次续期,key="+key+",value="+value);
                            if(!expire){
                                return;
                            }
                            try {
                                Thread.sleep(900);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
                // 设置为守护线程
                watchDogThread.setDaemon(true);
                watchDogThread.start();
                return true;
            }
            try {
                // 不成功继续获取
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return false;
            }
        }
        return false;
    }

public void releaseLockASC(String key, String value) {
        String luaString = "local v = redis.call('get',KEYS[1]);if v then if v~=ARGV[1] then return 0;end;redis.call('del',KEYS[1]);end;return 1;";
        List<String> keys = new ArrayList<>();
        keys.add(key);
        List<String> args = new ArrayList<>();
        args.add(value);
        long eval = (long) jedis.eval(luaString, keys, args);
        if (eval == 1L) {
            System.out.println(Thread.currentThread().getName() + "释放锁  key=" + key + "," + "value = " + value);
        }
    }

项目地址:https://gitee.com/a-long-time-to-name-it/yzwselfproject.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
“DRY——避免重复代码”是一个优秀的开发者在开发软件时所具备的最重要的思想之一。我们在开发企业WEB应用程序时都有一些类似的需求,例如:都需要登录页面、用户/角色管理、权限验证、数据有效性验证、多语言/本地化等等。一个高品质的大型软件都会运用一些最佳实践,例如分层体系结构、领域驱动设计、依赖注入等。我们也可能会采用ORM、数据库迁移(Database Migrations)、日志记录(Logging)等工具。 从零开始创建一个企业应用程序是一件繁琐的事,因为需要重复做很多常见的基础工作。许多公司都在开发自己的应用程序框架来重用于不同的项目,然后在框架的基础上开发一些新的功能。但并不是每个公司都有这样的实力。假如我们可以分享的更多,也许可以避免每个公司或每个项目的重复编写类似的代码。作者之所以把项目命名为“ASP.NET Boilerplate”,就是希望它能成为开发一般企业WEB应用的新起点,直接把ABP作为项目模板。 ABP的全称是Asp.net boilerplate project(asp.Net样板工程)。是github上非常活跃的一个开源项目。它并没有使用任何新的技术,只是由两名架构师将asp.net开发中常用的一些工具整合到了一起,并且部分实现了DDD的概念。是一个开箱即用的框架,可以作为asp.net分布式应用的一个良好起点。 它的功能包括: 服务器端: 基于最新的.NET技术 (目前是ASP.NET MVC 5、Web API 2、C# 5.0,在ASP.NET 5正式发布后会升级) 实现领域驱动设计(实体、仓储、领域服务、领域事件、应用服务、数据传输对象,工作单元等等) 实现分层体系结构(领域层,应用层,展现层和基础设施层) 提供了一个基础架构来开发可重用可配置的模块 集成一些最流行的开源框架/库,也许有些是你正在使用的。 提供了一个基础架构让我们很方便地使用依赖注入(使用Castle Windsor作为依赖注入的容器) 提供Repository仓储模式支持不同的ORM(已实现Entity Framework 、NHibernate、MangoDb和内存数据库) 支持并实现数据库迁移(EF 的 Code first) 模块化开发(每个模块有独立的EF DbContext,可单独指定数据库) 包括一个简单的和灵活的多语言/本地化系统 包括一个 EventBus来实现服务器端全局的领域事件 统一的异常处理(应用层几乎不需要处理自己写异常处理代码) 数据有效性验证(Asp.NET MVC只能做到Action方法的参数验证,ABP实现了Application层方法的参数有效性验证) 通过Application Services自动创建Web Api层(不需要写ApiController层了) 提供基类和帮助类让我们方便地实现一些常见的任务 使用“约定优于配置原则” 客户端: Bootstrap、Less、AngularJs、jQuery、Modernizr和其他JS库: jQuery.validate、jQuery.form、jQuery.blockUI、json2等 为单页面应用程序(AngularJs、Durandaljs)和多页面应用程序(Bootstrap+Jquery)提供了项目模板。 自动创建Javascript 的代理层来更方便使用Web Api 封装一些Javascript 函数,更方便地使用ajax、消息框、通知组件、忙状态的遮罩层等等 除ABP框架项目以外,还开发了名叫“Zero”的模块,实现了以下功能: 身份验证与授权管理(通过ASP.NET Identity实现的) 用户&角色管理 系统设置存取管理(系统级、租户级、用户级,作用范围自动管理) 审计日志(自动记录每一次接口的调用者和参数) 我共享的资料包含ABP(2.02版本)的一个Demo文件以及调试时候需要填的坑的处理方法(填了蛮久才填满。。。),还包括一本ABP中文教程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值