商品减库在Redis中的运用

一.商品减库中存在问题

1.传统的代码

1.1引入jar包

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

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

        <dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.6.5</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

1.2配置application.yml

server:
  port: 8090

spring:
  redis:
    host: 192.168.2.66
    port: 6379

1.3原始测试代码

package com.redisson;

import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestDeductController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }
}

1.apache-jmeter测试工具的使用

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.配置nginx代理,修改nginx-1.10.2/conf/nginx.conf,并启动
 upstream redislock{
         server 192.168.2.100:8080 weight=1;
         server 192.168.2.100:8090 weight=1;
    }

server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://redislock;
        }
  }      

在这里插入图片描述

3创建8080,8090spring boot后端客户端

在这里插入图片描述

4.测试结果

在这里插入图片描述

说明:当多个客户端同时访问这个减库存的逻辑的时候,会出现多个客户端获取的库存数据是一样的,这样导致库存的实际的扣减值和库存值不一致。

1.4使用synchronized进行代码优化

package com.redisson;

import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestDeductController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        synchronized (this){
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
            return "end";
        }
    }
}

在这里插入图片描述
在这里插入图片描述
说明:当存在多台后端服务器时,synchronized也会失效,因为synchronized针对的是单台服务器加锁,出现多台服务器,就不能够很好的实现库存加锁。

1.5设置一个锁,程序解锁后或超时的后删除锁

package com.redisson;

import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/test")
public class TestDeductController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "lock:product_101";
        String clientId = UUID.randomUUID().toString();
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
        if (!result) {
            return "error_code";
        }

            try {
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
                if (stock > 0) {
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                    System.out.println("扣减成功,剩余库存:" + realStock);
                } else {
                    System.out.println("扣减失败,库存不足");
                }
                return "end";
            } finally {
                if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                    stringRedisTemplate.delete(lockKey);
                }
            }
 }

}

说明:以上情况依然会出现一个问题,当在执行在finally行是,刚刚判断完成,此时的超时的时间到了,此后删除锁不是自身加的锁而是,后面线程加的锁,此后的过程都会删除后面线程的加的锁,依然存在bug

1.6最终优化方案

1.导入jar包
 <dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.6.5</version>
		</dependency>

2.在启动项中配置redisson
 @Bean
    public Redisson redisson() {
        // 此为单机模式
        Config config = new Config(); //config.setLockWatchdogTimeout(1000);config.useSingleServer().setAddress("redis://192.168.2.66:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

在这里插入图片描述

3.优化代码程序
package com.redisson;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/test")
public class TestDeductController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "lock:product_101";
        //获取锁对象
        RLock redissonLock = redisson.getLock(lockKey);
        //加分布式锁
        redissonLock.lock();
        try {
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
                if (stock > 0) {
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                    System.out.println("扣减成功,剩余库存:" + realStock);
                } else {
                    System.out.println("扣减失败,库存不足");
                }
                return "end";
            } finally {
               redissonLock.unlock();
            }
 }

}

说明:为啥这种方式可以避免多线程引发的数据问题,由于其中采用LUA脚本的形式,这样能够把一个事务的执行放在LUA脚本中,使得整个事务具备了原子性,同时节约了管道开销

1.7Redis中Lua脚本的使用

1.Redis中的使用

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

在这里插入图片描述

2.java中Redis Lua脚本的使用

package com.redisson;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.Arrays;

public class RedisLuaController {

    public static void main(String[] args) {
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(5);

        JedisPool jedisPool=new JedisPool(jedisPoolConfig,"192.168.1.61",6379,3000,null);
        Jedis jedis=null;
        try{
          jedis=jedisPool.getResource();
            jedis.set("product_stock_10016", "15");  //初始化商品10016的库存
            String script = " local count = redis.call('get', KEYS[1]) " +
                    " local a = tonumber(count) " +
                    " local b = tonumber(ARGV[1]) " +
                    " if a >= b then " +
                    "   redis.call('set', KEYS[1], a-b) " +
                    "   return 1 " +
                    " end " +
                    " return 0 ";
            Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
            System.out.println(obj);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
          if(jedis!=null){
              jedis.close();
          }
        }
    }
}

在这里插入图片描述

二.布隆过滤器

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
在这里插入图片描述
通过一种内置的hash函数进行一个hash运算取整数值,对位数进行取模得到一个值,然后找到对应的位置,把位置的值置为1,当比较的时候就通过同样的算法查看对应的位置是否为1,不为一就直接返回,这样就能够防止内存穿透。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值