业务使用redis分布式锁

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

1.业务场景代码

java 复制代码
    @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分布式锁

java 复制代码
    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工具类

java 复制代码
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乐观锁)

相关推荐
小小霸王龙!11 分钟前
互联网大厂Java面试实录:Spring Boot与微服务在电商场景中的应用
java·spring boot·redis·微服务·电商
草履虫建模15 分钟前
Postman - API 调试与开发工具 - 标准使用流程
java·测试工具·spring·json·测试用例·postman·集成学习
混乱意志16 分钟前
dgraph example数据导入
数据库·后端
Web极客码28 分钟前
WordPress 站点漏洞利用:数据库恶意注入与多重感染的案例分析
数据库·wordpress·网站安全·数据库注入·wordpress漏洞·wordpress安全插件
刺客xs32 分钟前
MySQL数据库----DML语句
数据库·mysql
都叫我大帅哥38 分钟前
Redis BitMap 深度解剖:比特世界的精密引擎
redis
嘉讯科技HIS系统42 分钟前
嘉讯科技:医疗信息化、数字化、智能化三者之间的关系和区别
大数据·数据库·人工智能·科技·智慧医疗
火车叨位去19491 小时前
映射阿里云OSS(对象存储服务)
java·spring
一只程序汪1 小时前
【如何实现分布式压测中间件】
分布式·中间件
大只鹅2 小时前
Springboot3.3.4使用spring-data-elasticsearch整合Elasticsearch7.12.1
spring boot·elasticsearch