SpringBoot之使用Redis实现分布式锁(秒杀系统)

1、Redis分布式锁概念篇html

1.一、为何要使用分布式锁java

    咱们在开发应用的时候,若是须要对某一个共享变量进行多线程同步访问的时候,可使用咱们学到的Java多线程的18般武艺进行处理,而且能够完美的运行,毫无Bug!redis

    注意这是单机应用,也就是全部的请求都会分配到当前服务器的JVM内部,而后映射为操做系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间!spring

    后来业务发展,须要作集群,一个应用须要部署到几台机器上而后作负载均衡,大体以下图:数据库

 

    上图能够看到,变量A存在JVM一、JVM二、JVM3三个JVM内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象,例如:UserController控制器中的一个整形类型的成员变量),若是不加任何控制的话,变量A同时都会在JVM分配一块内存,三个请求发过来同时对这个变量操做,显然结果是不对的!即便不是同时发过来,三个请求分别操做三个不一样JVM内存区域的数据,变量A之间不存在共享,也不具备可见性,处理的结果也是不对的!缓存

    若是咱们业务中确实存在这个场景的话,咱们就须要一种方法解决这个问题!安全

    为了保证一个方法或属性在高并发状况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的状况下,可使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了不少并发处理相关的API。可是,随着业务发展的须要,原单体单机部署的系统被演化成分布式集群系统后,因为分布式系统多线程、多进程而且分布在不一样机器上,这将使原单机部署状况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就须要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!服务器

1.二、分布式锁应具有哪些条件多线程

    在分析分布式锁的三种实现方式以前,先了解一下分布式锁应该具有哪些条件:并发

    一、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;

    二、高可用的获取锁与释放锁;

    三、高性能的获取锁与释放锁;

    四、具有可重入特性;

    五、具有锁失效机制,防止死锁;

    六、具有非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

1.三、分布式锁的三种实现方式

    目前几乎不少大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉咱们“任何一个分布式系统都没法同时知足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时知足两项。”因此,不少系统在设计之初就要对这三者作出取舍。在互联网领域的绝大多数的场景中,都须要牺牲强一致性来换取系统的高可用性,系统每每只须要保证“最终一致性”,只要这个最终时间是在用户能够接受的范围内便可。

    在不少场景中,咱们为了保证数据的最终一致性,须要不少的技术方案来支持,好比分布式事务、分布式锁等。有的时候,咱们须要保证一个方法在同一时间内只能被同一个线程执行。

    一、基于数据库实现分布式锁;

    二、基于缓存(Redis等)实现分布式锁;

    三、基于Zookeeper实现分布式锁;

    尽管有这三种方案,可是不一样的业务也要根据本身的状况进行选型,他们之间没有最好只有更适合!

2、Redis分布式锁实战篇

2.一、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
2.二、配置Redis配置信息




spring
    redis:
      port: 6379
      host: 127.0.0.1
      password:
      database: 0
2.三、配置RedisConfig属性、若是须要使用FastJSON来序列化你的对象能够看看我前面写的一篇文章





@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate initRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws Exception {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
2.四、写一个RedisLock工具类














/**
* @Description //直接使用Redis进行分布式锁
* @Date $ $
* @Author huangwb
**/

@Component
public class RedisLockCommon {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * Redis加锁的操做
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean tryLock(String key, String value) {
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(currentValue) && Long.valueOf(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间 若是高并发的状况可能会出现已经被修改的问题  因此多一次判断保证线程的安全
            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
            if (StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }


    /**
     * Redis解锁的操做
     *
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        try {
            if (StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)) {
                stringRedisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
        }
    }
}
2.五、减库存操做

















































@Override
public boolean decrementProductStore(Long productId, Integer productQuantity) {
String key = "dec_store_lock_" + productId;
long time = System.currentTimeMillis();
try {
//若是加锁失败
if (!redisLock.tryLock(key, String.valueOf(time))) {
return false;
}
ProductInfo productInfo = productInfoMapper.selectByPrimaryKey(productId);
//若是库存为空
if (productInfo.getProductStock() == 0) {
return false;
}
//减库存操做
productInfo.setProductStock(productInfo.getProductStock() - 1);
productInfoMapper.updateByPrimaryKey(productInfo);
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
//解锁
redisLock.unlock(key, String.valueOf(time));
}
return true;

}
2.六、测试接口


























@GetMapping("test")
public String createOrderTest() {
    if (!productInfoService.decrementProductStore(1L, 1)) {
        return "库存不足";
    }
    OrderMaster orderMaster = new OrderMaster();
    //未支付
    orderMaster.setOrderStatus(0);
    //未支付
    orderMaster.setPayStatus(0);
    orderMaster.setBuyerName("张三");
    orderMaster.setBuyerAddress("湖南长沙");
    orderMaster.setBuyerPhone("18692794847");
    orderMaster.setOrderAmount(BigDecimal.ZERO);
    orderMaster.setCreateTime(DateUtils.getCurrentDate());
    orderMaster.setOrderId(UUID.randomUUID().toString().replaceAll("-", ""));
    orderMasterService.insert(orderMaster);
}
2.七、可使用使用Apache ab进行http接口测试

















    具体文章能够看看这篇文章 https://www.cnblogs.com/greatluoluo/p/5725978.html

3、结尾

    文章概览篇来源于 https://blog.csdn.net/hxpjava1/article/details/81068355————————————————版权声明:本文为CSDN博主「水能载舟,亦能覆舟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处连接及本声明。原文连接:https://blog.csdn.net/qq_37892957/article/details/89322334

相关文章
相关标签/搜索