Redis之分布式锁

1.环境

        <!-- RedisTemplate -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring:
  redis:
    host: 192.168.8.128
    port: 6379
    password: 1234
    database: 0
    timeout: 10000
    jedis:
      pool:
        max-wait: -1
        max-active: -1
        max-idle: 20
        min-idle: 10

在这里插入图片描述

2.Redis配置

package com.yzm.redis10.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class ObjectMapperConfig {

    public static final ObjectMapper objectMapper;
    private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";

    static {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        objectMapper = new ObjectMapper()
                // 转换为格式化的json(控制台打印时,自动格式化规范)
                //.enable(SerializationFeature.INDENT_OUTPUT)
                // Include.ALWAYS  是序列化对像所有属性(默认)
                // Include.NON_NULL 只有不为null的字段才被序列化,属性为NULL 不序列化
                // Include.NON_EMPTY 如果为null或者 空字符串和空集合都不会被序列化
                // Include.NON_DEFAULT 属性为默认值不序列化
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                // 如果是空对象的时候,不抛异常
                .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
                // 反序列化的时候如果多了其他属性,不抛出异常
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                // 取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
                .setDateFormat(new SimpleDateFormat(PATTERN))
                // 对LocalDateTime序列化跟反序列化
                .registerModule(javaTimeModule)

                .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
                // 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
                .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
        ;
    }

    static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(DateTimeFormatter.ofPattern(PATTERN)));
        }
    }

    static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
            return LocalDateTime.parse(p.getValueAsString(), DateTimeFormatter.ofPattern(PATTERN));
        }
    }
}
package com.yzm.redis10.config;

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 {

    /**
     * redisTemplate配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jacksonSerializer.setObjectMapper(ObjectMapperConfig.objectMapper);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 使用StringRedisSerializer来序列化和反序列化redis的key,value采用json序列化
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jacksonSerializer);

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

3.模拟商品

package com.yzm.redis10.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Accessors(chain = true)
public class Product implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;

    /**
     * 商品名
     */
    private String name;

    /**
     * 库存
     */
    private Integer leftNum;
}
package com.yzm.redis10.service;

import com.yzm.redis10.entity.Product;

import java.util.List;

public interface ProductService {

    Product getById(Integer id);

    void updateById(Product product);

    List<Product> list();

}
package com.yzm.redis10.service.impl;

import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ProductServiceImpl implements ProductService {

    private static final Map<Integer, Product> productMap;

    static {
        productMap = new HashMap<>();
        productMap.put(productMap.size() + 1, Product.builder().id(productMap.size() + 1).name("苹果").leftNum(10).build());
    }

    @Override
    public Product getById(Integer id) {
        return productMap.get(id);
    }

    @Override
    public void updateById(Product product) {
        Product update = productMap.get(product.getId());
        update.setLeftNum(product.getLeftNum());
        productMap.put(product.getId(), update);
    }

    @Override
    public List<Product> list() {
        return new ArrayList<>(productMap.values());
    }
}

4.锁实现(推荐)

package com.yzm.redis10.redis;

import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class RedisLock {

    private final StringRedisTemplate redisTemplate;
    private final ProductService productService;

    public RedisLock(StringRedisTemplate redisTemplate, ProductService productService) {
        this.redisTemplate = redisTemplate;
        this.productService = productService;
    }

    private static final int TIMEOUT = 4000;
    private static final String LOCK_PREFIX = "secKill:";
    private final AtomicInteger i = new AtomicInteger(1);

    public void secKill(int productId) {
        // 查询库存
        Product product = productService.getById(1);
        if (product.getLeftNum() < 1) {
            log.error("库存不足");
            return;
        }

        // 加锁
        String uuid = UUID.randomUUID().toString();
        if (!lock(LOCK_PREFIX + productId, uuid)) {
            log.info("活动太火爆了,请稍后再操作");
            return;
        }

        //秒杀逻辑
        try {
            product.setLeftNum(product.getLeftNum() - 1);
            productService.updateById(product);
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 解锁
        unlock(LOCK_PREFIX + productId, uuid);
        log.info("秒杀成功" + i.getAndIncrement());
    }

    /**
     * 加锁
     */
    public boolean lock(String key, String value) {
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofMillis(TIMEOUT));
        if (ifAbsent != null && ifAbsent) {
            log.info("加锁成功");
            return true;
        }
        return false;
    }

    /**
     * 解锁(lua脚本原子性)
     */
    public void unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then return redis.call('del', KEYS[1]) " +
                "else return 0 " +
                "end";

        List<String> keys = new ArrayList<>();
        keys.add(key);
        Long execute = redisTemplate.execute(RedisScript.of(script, Long.class), keys, value);
        log.info("解锁成功 = " + execute);
    }
}
package com.yzm.redis10.controller;

import com.yzm.redis10.entity.Product;
import com.yzm.redis10.redis.RedisLock;
import com.yzm.redis10.redis.RedisLock2;
import com.yzm.redis10.redis.RedisLock3;
import com.yzm.redis10.service.ProductService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ProductController {

    private final ProductService productService;
    private final RedisLock redisLock1;
    private final RedisLock2 redisLock2;
    private final RedisLock3 redisLock3;

    public ProductController(ProductService productService, RedisLock redisLock1,RedisLock3 redisLock3, RedisLock2 redisLock2) {
        this.productService = productService;
        this.redisLock1 = redisLock1;
        this.redisLock2 = redisLock2;
        this.redisLock3 = redisLock3;
    }

    @GetMapping("/list")
    public List<Product> list() {
        return productService.list();
    }

    @GetMapping("/inr")
    public void inrLeft(Integer id) {
        productService.updateById(Product.builder().id(id).leftNum(10).build());
    }

    @GetMapping("/buy")
    public String buy(long time) {
        for (int i = 0; i < 100; i++) {
            try {
                new Thread(() -> redisLock1.secKill(1)).start();
                Thread.sleep(time);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "秒杀结束";
    }
}

http://localhost:8080/list
在这里插入图片描述
http://localhost:8080/buy?time=50
在这里插入图片描述

http://localhost:8080/list
在这里插入图片描述

5.锁实现

package com.yzm.redis10.redis;

import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class RedisLock2 {

    private final StringRedisTemplate redisTemplate;
    private final ProductService productService;

    public RedisLock2(StringRedisTemplate redisTemplate, ProductService productService) {
        this.redisTemplate = redisTemplate;
        this.productService = productService;
    }

    private static final int TIMEOUT = 4000;
    private static final String LOCK_PREFIX = "secKill:";
    private final AtomicInteger i = new AtomicInteger(1);
    private static final ThreadLocal<String> local = new ThreadLocal<>();

    public void secKill(int productId) {
        Product product = productService.getById(1);
        if (product.getLeftNum() < 1) {
            log.error("库存不足");
            return;
        }

        //加锁
        String uuid = UUID.randomUUID().toString();
        if (!lock(LOCK_PREFIX + productId, uuid)) {
            log.info("活动太火爆了,请稍后再操作");
            return;
        }

        //秒杀逻辑
        try {
            product.setLeftNum(product.getLeftNum() - 1);
            productService.updateById(product);
            Thread.sleep(80);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //解锁
        unlock(LOCK_PREFIX + productId, uuid);
        log.info("秒杀成功" + i.getAndIncrement());
    }

    /**
     * 加锁
     */
    public boolean lock(String key, String value) {
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofMillis(TIMEOUT));
        if (ifAbsent != null && ifAbsent) {
            log.info("加锁成功");
            local.set(value);
            return true;
        }
        return false;
    }

    /**
     * 解锁
     */
    public void unlock(String key, String value) {
        String localValue = local.get();
        if (localValue.equals(value) && localValue.equals(redisTemplate.opsForValue().get(key))) {
            log.info("解锁成功");
            redisTemplate.delete(key);
            local.remove();
        }
    }
}
    @GetMapping("/buy2")
    public String buy2(long time) {
        for (int i = 0; i < 100; i++) {
            try {
                new Thread(() -> redisLock2.secKill(1)).start();
                Thread.sleep(time);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "秒杀结束";
    }

http://localhost:8080/buy2?time=50
在这里插入图片描述

6.锁实现

package com.yzm.redis10.redis;

import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class RedisLock3 {

    private final StringRedisTemplate redisTemplate;
    private final ProductService productService;

    public RedisLock3(StringRedisTemplate redisTemplate, ProductService productService) {
        this.redisTemplate = redisTemplate;
        this.productService = productService;
    }

    private static final int TIMEOUT = 4000;
    private static final String LOCK_PREFIX = "secKill:";
    private final AtomicInteger ato = new AtomicInteger(1);

    public void secKill(int productId) {
        Product product = productService.getById(1);
        if (product.getLeftNum() < 1) {
            log.error("库存不足");
            return;
        }

        //加锁
        //value包含拥有者标识,具有唯一性,防止任何人都可以解锁
        String userId = UUID.randomUUID() + ":" + System.currentTimeMillis() + TIMEOUT;
        if (!lock(LOCK_PREFIX + productId, userId)) {
            log.error("活动太火爆了,请稍后再操作");
            return;
        }

        //秒杀逻辑
        try {
            product.setLeftNum(product.getLeftNum() - 1);
            productService.updateById(product);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //解锁
        unlock(LOCK_PREFIX + productId, userId);
        log.info("秒杀成功" + ato.getAndIncrement());
    }

    /**
     * 加锁
     */
    public boolean lock(String key, String value) {
        //没有锁,持有并加锁
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (ifAbsent != null && ifAbsent) {
            log.info("加锁成功");
            return true;
        }

        //锁已被持有,判断锁过期
        synchronized (RedisLock3.class) {
            String redisValue = redisTemplate.opsForValue().get(key);
            if (StringUtils.hasLength(redisValue) && Long.parseLong(redisValue.split(":")[1]) < System.currentTimeMillis()) {
                String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
                if (StringUtils.hasLength(oldValue) && oldValue.equals(redisValue)) {
                    log.info("锁过期,重新持有锁");
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 解锁
     */
    public void unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then return redis.call('del', KEYS[1]) " +
                "else return 0 " +
                "end";

        List<String> keys = new ArrayList<>();
        keys.add(key);
        Long execute = redisTemplate.execute(RedisScript.of(script, Long.class), keys, value);
        System.out.println("解锁成功 = " + execute);
    }

}
    @GetMapping("/buy3")
    public String buy3(long time) {
        for (int i = 0; i < 100; i++) {
            try {
                new Thread(() -> redisLock3.secKill(1)).start();
                Thread.sleep(time);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "秒杀结束";
    }

http://localhost:8080/buy3?time=50
在这里插入图片描述
在这里插入图片描述

相关链接

首页
上一篇:Stream消息队列
下一篇:布隆过滤器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值