首页 > 数据库 >并发业务使用redis分布式锁

并发业务使用redis分布式锁

时间:2024-06-21 14:29:34浏览次数:23  
标签:return String redis User 并发 user key 分布式

伴随着业务体量的上升,我们的qps与并发问题越来越明显,这时候就需要用到让代码一定情况下进行串行执行的工具:锁

1.业务场景代码

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Object testBatch(User user) {
        LambdaQueryWrapper<User> eq = Wrappers.<User>lambdaQuery()
                .eq(User::getBatch, user.getBatch());
        List<User> userList = list(eq);
        if (CollUtil.isEmpty(userList)) {
            save(user);
        } else {
            this.lambdaUpdate().eq(User::getBatch, user.getBatch())
                    .set(User::getUsername, user.getUsername())
                    .set(User::getUpdateTime, LocalDateTime.now())
                    .update();
        }
        redisUtil.delete(key);
        return user;
    }

备注:上述的代码逻辑在串行执行的时候是没有任何问题的,但是假如同时有两个线程进来:两个线程同时读取到当前batch对应的user为null,那么此时当前两个线程就会同时执行insert语句,导致当前batch本该只有1个user的但是此刻数据库有2个user记录。这个就是并发问题

2.解决方案
此刻我能想到的解决方案有以下三种,此处只讲redis锁
2.1 代码同步执行
2.1.1 redis分布式锁

    private int maxCostSeconds = 5;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Object testBatch(User user) {
        String key = "userBatch::" + user.getBatch();
        boolean lock = redisUtil.setNxEx(key, key, maxCostSeconds);
        LocalDateTime startNow = LocalDateTime.now();
        LocalDateTime endNow = LocalDateTime.now();
        // 自选等待获取锁,超过5s就放弃
        int count = 0;
        while (!lock) {
            lock = redisUtil.setNxEx(key, key, maxCostSeconds);
            if (lock) {
                break;
            }
            endNow = LocalDateTime.now();
            int costSeconds = endNow.getSecond() - startNow.getSecond();
            if (costSeconds >= maxCostSeconds) {
                break;
            }
            Thread.sleep(500);
            System.out.println("获取次数:" + count++);
        }
        System.out.println("当前线程获取到了 redis锁,线程名" + Thread.currentThread().getName());
        if (!lock) {
            throw new RunTimeException("系统繁忙,请稍后重试");
        }
        LambdaQueryWrapper<User> eq = Wrappers.<User>lambdaQuery()
                .eq(User::getBatch, user.getBatch());
        List<User> userList = list(eq);
        if (CollUtil.isEmpty(userList)) {
            save(user);
        } else {
            this.lambdaUpdate().eq(User::getBatch, user.getBatch())
                    .set(User::getUsername, user.getUsername())
                    .set(User::getUpdateTime, LocalDateTime.now())
                    .update();
        }
        redisUtil.delete(key);
        return user;
    }

redis工具类

package com.lzq.learn.utils;

import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;


@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private final RedisScript<String> lockScript = new DefaultRedisScript<>("if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then return ARGV[1] else return nil end", String.class);
    private final RedisScript<Long> unlockScript = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class);

    public boolean acquireLock(String lockKey, String lockValue, long expireTime) {
        String result = redisTemplate.execute(lockScript, Collections.singletonList(lockKey), lockValue, expireTime);
        return "OK".equals(result);
    }

    public void releaseLock(String lockKey, String lockValue) {
        redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), lockValue);
    }
    /**
     * 删除key
     *
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 批量删除key
     *
     * @param keys
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * set NX  PX
     * @param key key
     * @param value value
     * @param seconds 过期时间  单位:seconds
     * @return boolean
     */
    public boolean setNxEx(String key , String value , int seconds){
        Boolean result = false;
        try {
            result = redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                    RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                    try {
                        Object set = redisConnection.execute("set", keySerializer.serialize(key), value.getBytes("UTF-8")
                                , "NX".getBytes("UTF-8"), "EX".getBytes("UTF-8"),
                                String.valueOf(seconds).getBytes("UTF-8"));
                        return "OK".equals(String.valueOf(set));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        return false;
                    }
                }
            });
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return result;
    }

}

2.1.2 java锁(synchronized、lock)
2.2 数据库唯一索引校验
2.3 数据库锁(select for update行锁,version乐观锁)

标签:return,String,redis,User,并发,user,key,分布式
From: https://blog.csdn.net/lzq2357639195/article/details/139848413

相关文章

  • [转帖]Redis如何绑定CPU
    https://wenfh2020.com/2023/10/08/https/ 发布时间:2022-03-0809:44:39 阅读:649 作者:小新 栏目:开发技术开发者测试专用服务器限时活动,0元免费领,库存有限,领完即止!点击查看>>这篇文章主要介绍了Redis如何绑定CPU,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这......
  • Golang并发
    Sync.MutexMutex结构typeMutexstruct{ stateint32 semauint32}Sync.Mutex由两个字段构成,state用来表示当前互斥锁处于的状态,sema用于控制锁状态的信号量互斥锁state(32bit)主要记录了如下四种状态:waiter_num(29bit):记录了当前等待这个锁的goroutine数量starving(......
  • RedisDesktopManager的使用
    简介        RedisDesktopManager(RDM)是一个开源的图形化Redis数据库管理工具,是Redis可视化工具,支持Windows、macOS和Linux平台        它提供了一系列的功能,如连接管理、数据浏览、编辑和调试等,帮助用户管理和操作Redis数据库;适用于多种操作系......
  • Redis集群搭建及原理详解
    1.Redis集群方案比较1.1哨兵模式在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是在主从切换的瞬间存在访问瞬断......
  • CompletableFuture多线程并发处理
    CompletableFuture多线程并发处理   概要  一个接口可能需要调用N个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,  如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速......
  • Redis 缓存应用、淘汰机制
    (四)Redis缓存应用、淘汰机制 合集-Redis(4) 1.(一)LinuxCentOSRedis安装05-082.(二)Redis数据类型与结构05-173.(三)Redis线程与IO模型06-054.(四)Redis缓存应用、淘汰机制06-20收起 1、缓存应用一个系统中不同层面数据访问速度不一样,以计算机为例,CPU、内存......
  • MVCC多版本并发控制
    MVCC(MultiVersionConcurrencyControl)多版本并发控制,是指在使用READCOMMITTED、REPEATABLEREAD这两种隔离级别的事务执行SELECT操作时访问记录的版本链的过程,使不同事务的读写操作能够并发执行,提升系统性能。MVCC机制的核心是在做SELECT操作前会生产一个ReadView,READCO......
  • Redis漏洞原理
    Redis漏洞原理Redis简单介绍Redis是一款内存高速缓存的数据库,是一款K-V型数据库,它的所有键值都是用字典来存储的。其中它的value支持多种数据类型,包括String、List、Set、Zset和Hash。‍Redis未授权访问漏洞介绍利用条件Redis默认情况下绑定在127.0.0.1:6379,在没有进......
  • 【异常】nested exception is java.lang.NoClassDefFoundError: redis/clients/jedis/
    原因是版本冲突。以下我原本使用的版本信息<!--SpringBootRedis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.1.8.RELEASE</version><......
  • 基于AUTBUS总线的分布式储能监控系统
     ......