分布式锁

在分布式环境中,为了保证业务数据的正常访问,防止出现重复请求的问题,会使用分布式锁来阻拦后续请求。具体伪代码如下:

  public void doSomething(String userId){
        User user=getUser(userId);
        if(user==null){
            user.setUserName("xxxxx");
            user.setUserId(userId);
            insert(user);
            return;
        }
        update(user);
    }

上面的代码很简单,查询db中有没有对应的user数据,如果有的话,执行更新操作,如果没有则插入。

我们知道,上面的代码是线程不安全的,在多线程的环境中,就会出现问题。为了能够保证数据的正确性,在单机环境下,我们可以使用synchronized的方法,来保证线程安全,具体修改:

  public synchronized void doSomething(String userId){
        User user=getUser(userId);
        if(user==null){
            user.setUserName("xxxxx");
            user.setUserId(userId);
            insert(user);
            return;
        }
        update(user);
    }

在单机器的环境下,能够解决线程安全的问题,那在分布式环境下呢? 这个时候需要用到分布式锁.

分布式锁需要借助其他组件来实现,常用的有rediszookeeper。下面我们就用redis的实现,来说明下问题,分布式锁具体的实现方法如下

    public  void doSomething(String userId){
        String lock=RedisUtils.get("xxxx"+userId);
        if(StringUtils.isNotEmpty(lock)){//说明当前userId已经被锁定
            return;
        }
        RedisUtils.set("xxxx"+userId,userId,1000);//锁定10s
        User user=getUser(userId);
        if(user==null){
            insert(user);
            RedisUtils.delete("xxxx"+userId);
            return;
        }
        update(user);
        RedisUtils.delete("xxxx"+userId);

    }

上面的代码解决了在分布式环境中的并发的问题。但同样需要考虑一个问题,如果insert操作和update操作异常了,分布式锁不会释放,后续的请求还会被拦截。

所以我们再优化,增加对异常的捕获。

 public  void doSomething(String userId){
        try {
                String lock=RedisUtils.get("xxxx"+userId);
                if(StringUtils.isNotEmpty(lock)){//说明当前userId已经被锁定
                    return;
                }
                RedisUtils.set("xxxx"+userId,userId,1000);//锁定1s
                User user=getUser(userId);
                if(user==null){
                    insert(user);
                    return;
                }
                update(user);
        }
        catch(Exception ex){

        }
        finally{
            RedisUtils.delete("xxxx"+userId);
        }
    }

现在即使是程序异常了,锁会自动释放。但redis的get和set也会存在并发问题,我们再继续优化,使用redis中的setnx方法

    public  void doSomething(String userId){
        try {
                boolean lock=RedisUtils.setnx("xxxx"+userId,userId,1000);//锁定1s
                if(!lock){//说明当前userId已经被锁定
                    return;
                }
                User user=getUser(userId);
                if(user==null){
                    insert(user);
                    return;
                }
                update(user);
        }
        catch(Exception ex){

        }
        finally{
            RedisUtils.delete("xxxx"+userId);
        }
    }

上面的代码好像没有什么问题了,但也存在很大的隐患。 我们分析下,假设第一个请求过来,执行锁定成功,程序开始运行,但是insert和update操作阻塞了1s,第二个请求过来,锁的缓存已经过期,第二个执行锁定成功,这个时候第一个请求完成了锁被释放,第二个请求的锁就被第一次请求释放了,第三次的请求就会造成线程不安全问题。

怎么再去优化呢?问题主要是出现在第一次请求误删锁的问题,所以我们在移除锁的时候要判断能否移除。

思路:我们在锁定的时候,value使用当前的时间戳,删除时判断是否过期如果不过期就不要删除,具体代码如下:

public  void doSomething(String userId){
        try {
                boolean lock=RedisUtils.setnx("xxxx"+userId,LocalDateTime.now(),1000);//锁定10s
                if(!lock){//说明当前userId已经被锁定
                    return;
                }
                User user=getUser(userId);
                if(user==null){
                    insert(user);
                    return;
                }
                update(user);
        }
        catch(Exception ex){

        }
        finally{
            LocalDateTime lockTIme=    RedisUtils.get("xxxx"+userId);
            if(lockTIme.compare(LocalDateTime.now())<0){
                //说明已经过期,可以删除key
                RedisUtils.delete("xxxx"+userId);
            }
        }
    }

这样即使出现阻塞,第二次的时间戳覆盖了第一次的锁定,这样即使第一次完成了,也不会释放锁。

redis分布式锁的问题和解决的更多相关文章

  1. 使用Redis分布式锁处理并发,解决超卖问题

    一.使用Apache ab模拟并发压测 1.压测工具介绍 $ ab -n 100 -c 100 http://www.baidu.com/ -n表示发出100个请求,-c模拟100个并发,相当是100 ...

  2. Redis分布式锁解决抢购问题

    转:https://segmentfault.com/a/1190000011421467 废话不多说,首先分享一个业务场景-抢购.一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次 ...

  3. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  4. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  5. springboot+redis分布式锁-模拟抢单

    本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如 ...

  6. Lua脚本在redis分布式锁场景的运用

    目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...

  7. Redlock(redis分布式锁)原理分析

    Redlock:全名叫做 Redis Distributed Lock;即使用redis实现的分布式锁: 使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击) ...

  8. 【分布式缓存系列】集群环境下Redis分布式锁的正确姿势

    一.前言 在上一篇文章中,已经介绍了基于Redis实现分布式锁的正确姿势,但是上篇文章存在一定的缺陷——它加锁只作用在一个Redis节点上,如果通过sentinel保证高可用,如果master节点由于 ...

  9. Redis 分布式锁的实现

    0X00 测试环境 CentOS 6.6 + Redis 3.2.10 + PHP 7.0.7(+ phpredis 4.1.0) [root@localhost ~]# cat /etc/issue ...

随机推荐

  1. WP8.1的shell:SystemTray去哪了?

    WP8.1 中的SystemTray被 StatusBar 代替了.在Windows.UI.ViewManagement 命名空间下,而且只能在后台代码中设置,XAML中不行.用法是这样的: Stat ...

  2. UWP-磁贴初识

    原文:UWP-磁贴初识 简单的磁贴内容实现,来自 Bob 的视频. 为一个按钮添加点击事件,来更新磁贴. private void ChangeTileContentButton_Click(obje ...

  3. C#的Task、async、await关键字

    Task,一个类,可以执行一个方法,构造函数需要传一个Action类型的委托,Action类型的委托是可以拥有多个参数,没有返回值的. Task<T> Task的泛型,构造函数传入一个Fu ...

  4. C语言实现常用数据结构——栈

    #include<stdio.h> #include<stdlib.h> //用链表实现栈 typedef struct Node { int data; struct Nod ...

  5. java多线程之线程安全

    线程安全和非线程安全是多线程的经典问题,非线程安全会在多个线程对同一个对象并发访问时发生. 注意1: 非线程安全的问题存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题. 实例变量是对 ...

  6. linux上java和golang环境变量的设置

    JAVA环境变量   (1).打开~/.bashrc完成环境配置( 作用类似于/etc/bashrc, 只是针对用户自己而言,不对其他用户生效.)       文件追加            expo ...

  7. 阿里云部署Redis服务器远程连接问题

    昨天在阿里云免费领了一个月的云服务器,就着最近学的SpringBoot,准备做一个SpringBoot与Redis的整合. 因为以前用的Redis都是安装在本地的,使用过程中没遇到什么大问题,可是一旦 ...

  8. __file__、__name__、__dict__方法整理

    本文主要介绍__file__.__name__.__dict__三个方法的作用. #01 __file__:打印当前文件的位置. # import os # print(__file__) # 在py ...

  9. 上手更快的网络文件系统 —— oxfs

    什么时候需要网络文件系统? 做嵌入式的同学经常会使用 NFS 讲 host 上的某个目录挂载到开发板上,方便 host 上编译构建后能直接在板子上运行,减少手工拷贝操作. 网站开发时,在 host 上 ...

  10. kali 源文件 更改和使用 更新日期:2018.04.21

    我的公众号,正在建设中,欢迎关注: 0x01 源文件格式: kali下常用的更新命令有: apt-get install update和apt-get install upgrade,update是下 ...