分布式锁(二)基于Redis的分布式锁

分布式锁的核心思想:

用一个状态值表示锁,对锁的占用和释放通过状态值来表示。例如在商品秒杀活动中通过锁定goodsId来解决商品超卖等问题。

分布式锁的实现步骤:

1.判断googsId在当前线程是否处于锁定状态,如果是则新线程需要等待或根据业务需求做其他处理
2.锁定goodsId,当拿到资源后先对其加锁,然后做核心业务处理
3.设置超时时间,当线程出现阻塞或死锁时需要用到超时时间来确保服务的可用性。
4.解锁

一、Redis实现分布式锁

Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。

1.配置pom.xml文件

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.配置application.properties文件

spring.redis.host=123.207.123.159
spring.redis.port=6379
spring.redis.password=root
spring.redis.database=1

3.配置Redis Bean

@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer()); //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setHashKeySerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

4.RedisTemplate分布式锁工具类

import com.zzx.provider.dao.UserDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class TestRedisTemplate {

    private static final long TIMEOUT = 30 * 1000;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Autowired
    UserDao userDao;

    @GetMapping("/redis/template")
    public void order() {
        log.info("starting lock!");
        for (int i = 0; i < 50; i++) {
            String goodsId = "1";
            long time = System.currentTimeMillis() + TIMEOUT;
            //加锁
            if (!lock(goodsId, String.valueOf(time))) {
                System.out.println("The current number or people is too large!");
            } else {
                //一大坨逻辑代码这里用到了往数据库修改数据,处理一次age减一
                int age = userDao.findAge(1);
                age=age - 1;
                System.out.println("age:" + age);
                userDao.update(1,age );
                //解锁
                unLock(goodsId, String.valueOf(time));
            }
        }
    }

     /**
     * 加锁
     */
    public boolean lock(String key, String value) {
        String currentValue=String.valueOf(redisTemplate.opsForValue().get(key));
        //setIfAbsent 如果key存在false, 如果不存在返回true
        if (!redisTemplate.opsForValue().setIfAbsent(key, value)) {
            if (currentValue.equals("null") || ((Long.parseLong(currentValue) > System.currentTimeMillis()))) {
                return false;
            }
            System.out.println("time:" + currentValue);
            //setIfAbsent 如果key存在false, 如果不存在返回true
             System.out.println("add lock true");
             redisTemplate.opsForValue().set(key, value);
             return true;
        } else {
                System.out.println("add lock true");
                redisTemplate.opsForValue().set(key, value);
                return true;
        }
    }

    /**
     * 解锁
     */
    public void unLock(String key, String value) {
        try {
            System.out.println("unlock success");
            redisTemplate.delete(key);
        } catch (Throwable e) {
            log.error("解锁异常, {}", e.getMessage());
        }
    }
}

测试结果:
在这里插入图片描述

二、Redisson分布式框架实现分布式锁

Redisson基于Redis实现分布式锁的加锁与释放锁。此外Redisson还支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构。

1.pom.xml配置

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--start redis distributed lock-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.5</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!--end redis distributed lock-->

2.application.properties配置

spring.redis.host=123.207.123.159
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=1

3.配置Redisson

@SpringBootApplication
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class InsertApplication {

    private String host;
    private String port;
    private String password;

    @Bean
    public RedissonClient getRedisson(){

        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
        //config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }
    public static void main(String[] args) {
        SpringApplication.run(InsertApplication.class, args);
    }
}

4.Redis配置

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer()); //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setHashKeySerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }

}

5.测试

这里我就没有使用jemeter压力测试工具类,我将测试类拷贝到了我另一个模块中:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class SellController {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redisson;

    @GetMapping("/save")
    public void add(){
        redisTemplate.delete("zzx");
        redisTemplate.opsForValue().set("zzx","100");
    }

    private final String LOCKKEY = "locks";

    @GetMapping("/sell")
    public void sell(){
        for(int i=0; i < 55; i++){
            RLock lock = redisson.getLock(LOCKKEY);
            lock.lock(60, TimeUnit.SECONDS); //加锁,60秒后自动释放锁 (默认30秒)
            int stock = Integer.parseInt(String.valueOf(redisTemplate.opsForValue().get("zzx")));
            if(stock > 0){
                redisTemplate.opsForValue().set("zzx",(stock-1)+"");
                System.out.println(LOCKKEY+":"+(stock-1)+"");
            }
            lock.unlock(); //释放锁
        }
    }
}

测试结果如下:
在这里插入图片描述
可以看到在并发情况下,各个线程得到了自己的锁,互不干扰,处理完毕之后释了放锁。

发布了55 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Z_Vivian/article/details/101775633