深入浅出:Redis 分布式锁原理与实现 (一)

本文探讨在分布式环境下,如何使用Redis实现分布式锁以解决并发问题。通过场景分析,逐步展示从简单的Redis同步锁到原子性同步锁、设置超时时间以及守护线程防止死锁的解决方案。同时指出,为了确保原子性操作,可以利用Lua脚本。
摘要由CSDN通过智能技术生成

Redis 分布式锁原理与实现

Java 应用在多线程环境下,我们可以通过 Java 内存模型实现同步,比如 Lock,synchronized 等, 但是在分布式环境下,特别是现在微服务盛行的时代,服务为了高可用会做集群。在这样的情况下每个服务都有自己独立进程,当高并发的情况下,会存在同步问题,本文主要记录自己学习Redis分布式锁的过程。从浅到深一步步通过代码去分析。

1. 什么场景下用分布式锁

​ 比如,有一个账户服务对用户账户进行金额操作并且做了集群。此时有A,B两个请求同时对账户金额进行操作,操作步骤如下:

  1. 从数据库读取账户金额
  2. 对账户金额进行操作
  3. 保存账户金额到数据库

假如不做互斥,A得到账户金额,还没等A先修改完保存,B也从数据库中得到账户金额,那么等A处理完金额保存后,B随后的保存就将覆盖A的保存。

2. 场景分析和解决方案

​ 我们通过代码演示不同的场景下,我们会出现什么样的问题,并且怎么样去解决。

2.1 不做任何同步

可以通过代码来解释这种情况,由于本地学习,用 Redis 模拟数据库存储账户金额。用多线程来模拟分布式环境,同时对账户进行操作。

UserAccount.java

//用户账户Model
public class UserAccount {
   

    //用户ID
    private String userId;
    //账户内金额
    private int amount;

    public UserAccount(String userId, int amount) {
   
        this.userId = userId;
        this.amount = amount;
    }

    public UserAccount() {
   
    }

    //添加账户金额
    public void addAmount(int amount) {
   
        this.amount = this.amount + amount;
    }

    public String getUserId() {
   
        return userId;
    }

    public void setUserId(String userId) {
   
        this.userId = userId;
    }

    public int getAmount() {
   
        return amount;
    }

    public void setAmount(int amount) {
   
        this.amount = amount;
    }
}

AccountOperationThread.java 线程,模拟分布式环境

/**
 * 账户操作
 * 多线程方式模拟分布式环境
 */
public class AccountOperationThread implements Runnable {
   

    private final static Logger logger = LoggerFactory.getLogger(AccountOperationThread.class);

    //操作金额
    private int amount;

    private String userId;


    private RedisTemplate redisTemplate;

    public AccountOperationThread(String userId, int amount) {
   
        this.amount = amount;
        this.userId = userId;
        redisTemplate = (RedisTemplate) SpringBeanUtil.getBeanByName("redisTemplate");
    }

    @Override
    public void run() {
   
        noLock();
    }


    /**
     * 不做任何同步(锁)
     */
    private void noLock() {
   
      	try {
   
          	Random random = new Random();
            // 为了更好测试,模拟线程先后进入,每个线程随机休眠 1-100毫秒再进行业务操作
            TimeUnit.MILLISECONDS.sleep(random.nextInt(100) + 1);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        //模拟数据库中获取用户账号
        UserAccount userAccount = (UserAccount) redisTemplate.opsForValue().get(userId);
        //设置金额
        userAccount.setAmount(userAccount.getAmount() + amount);
        logger.info(Thread.currentThread().getName() + " : user id : " + userId + " amount : " + userAccount.getAmount());
        //模拟存回数据库
        redisTemplate.opsForValue().set(userId, userAccount);
    }
}

为了方便测试,我用的是SpringBootRedisTemplate配置了自定义序列化方式,TestController来进行测试。

@Configuration
public class RedisConfig {
   

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
   
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}
@RestController
public class TestController {
   

    private final static Logger logger = LoggerFactory.getLogger(TestController.class)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值