SpringBoot+redis 实现redis 库存|名额 扣减

目录

1.业务流程说明

2.结构设计

3.实现思路

4.开整

   1.引入依赖

  2.配置redisTemplate

3.编写测试类


1.业务流程说明

        业务流程要求实现报名是对选项的名额进行限定,如 举办活动的为A, 报名字段中有  性别 (男,女),职业 (老师,学生),且对性别 男限定可报名额为10,女为20,  老师为10 学生为100。该文针对该业务流程进行分析设计,库存方案类似。各位看官一定会举一反三。

2.结构设计

使用redis的hash 结构来保存.Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表;

使用以上场景说明,初始化活动A  性别字段 的选项名额.   ACTIVITY:1  下的选项 为男(OPTION:M)的库存为10

127.0.0.1:6379> HSET ACTIVITY:A OPTION:SEXY:M 10
(integer) 1
127.0.0.1:6379> 

使用HGETALL(获取在哈希表中指定 key 的所有字段和值) 查看是否设置成功;

127.0.0.1:6379> HGETALL ACTIVITY:A
1) "OPTION:SEXY:M"
2) "10"
127.0.0.1:6379> 

相同的方式设置性别选项=女 和职业字段的名额.如下

127.0.0.1:6379> HSET ACTIVITY:A OPTION:SEXY:W 20
(integer) 1
127.0.0.1:6379> HSET ACTIVITY:A OPTION:JOB:T 10
(integer) 1
127.0.0.1:6379> HSET ACTIVITY:A OPTION:JOB:S 100
(integer) 1
127.0.0.1:6379> HGETALL ACTIVITY:A
1) "OPTION:SEXY:M"
2) "10"
3) "OPTION:SEXY:W"
4) "20"
5) "OPTION:JOB:T"
6) "10"
7) "OPTION:JOB:S"
8) "100"
127.0.0.1:6379> 

3.实现思路

使用redis 命令HINCRBY(为哈希表 key 中的指定字段的整数值加上增量 increment ) 来实现名额/库存的扣减操作;具体增减示例

127.0.0.1:6379> HINCRBY ACTIVITY:A OPTION:SEXY:M -5
(integer) 5
127.0.0.1:6379> HGET ACTIVITY:A OPTION:SEXY:M
"5"
127.0.0.1:6379> HINCRBY ACTIVITY:A OPTION:SEXY:M 5
(integer) 10
127.0.0.1:6379> HGET ACTIVITY:A OPTION:SEXY:M
"10"
127.0.0.1:6379> 

4.开整

   1.引入依赖

    具体版本依赖项目的版本管理器;

     
            org.springframework.boot
            spring-boot-starter-data-redis
     

  2.配置redisTemplate

 设置RedisSerializer 序列化方式是为了方便在redis终端和桌面工具中调试,不设置则使用默认序列化方式,在工具和终端中查询到的是序列化后 不方便阅读; 

@ComponentScan("com.evolu.*")
@EnableAutoConfiguration
public class AppConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplateBuilder().build();
    }


    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return  redisTemplate;
    };


}

3.编写测试类

import com.evolu.OrderApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description:
 * @Title: RedisTest
 * @Package PACKAGE_NAME
 * @Author: XX_Dog
 * @CreateTime: 2022/10/25 15:50
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class RedisTest {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 测试并发场景下扣减库存是否存在问题
     * @throws InterruptedException
     */
    @Test
    public void test() throws InterruptedException {
        HashOperations ops = redisTemplate.opsForHash();
        ops.put("form:1","option:1",0);
        Object result =  ops.get("form:1","option:1");
        System.out.println(result);


        for (int i = 0; i < 10; i++) {
         Thread  thread = new Thread(()->{
              Long incrresult = ops.increment("form:1","option:1",1);
                System.out.println("threadA执行结果"+incrresult);
            },"threadA"+i);
         thread.start();
//         thread.join();
        }

        for (int i = 0; i < 10; i++) {
            Thread  thread =  new Thread(()->{
                Long incrresult = ops.increment("form:1","option:1",1);
                System.out.println("threadB执行结果"+incrresult);
            },"threadB"+i);
            thread.start();
//            thread.join();
        }

        Object result1 =  ops.get("form:1","option:1");
        System.out.println(result1);
    }

    /**
     * 扣减库存实际操作
     * @throws InterruptedException
     */

    @Test
    public void testLua() throws InterruptedException {

        String resultScript = "if(redis.call('hexists',KEYS[1],KEYS[2]) == 1) \n" +
                "\tthen\n" +
                "\t\tlocal stock = tonumber(redis.call('hget',KEYS[1],KEYS[2]));\n" +
                "\t\tlocal num = tonumber(ARGV[1]);\n" +
                "\n" +
                "\t\tif(stock >=num) \n" +
                "\t\t\tthen\n" +
                "\t\t\t\treturn redis.call(\"hincrby\",KEYS[1],KEYS[2],-num);\n" +
                "\t\t\tend;\n" +
                "\t\treturn 0;\n" +
                "\tend;\n" +
                " return 1;\n";
        List keys = new ArrayList<>();
        keys.add("form:1");
        keys.add("option:1");
        Object result =  redisTemplate.execute(new DefaultRedisScript(resultScript,Long.class),keys,"2");
        System.out.println(result);
    }


}

这里使用lua 脚本的原因是 HINCRBY  虽然是线程安全的,但是无法判断是否超卖,所以我们必须先读到剩余库存在去扣减。在读和扣减这两部操作 并不具备原子性。所以使用Lua脚本来进行读取库存,若充足则进行扣减,不充足则返回;


                                                                                                                                                                                                                                         不喜轻喷   诗书幸有先人业,贫贱初非学者羞

你可能感兴趣的:(redis,数据库,缓存)