Redis分布式锁(一)并发问题引入

一、背景demo:

1、代码:

product商品表:

Redis分布式锁(一)并发问题引入_第1张图片

userorder订单表:

Redis分布式锁(一)并发问题引入_第2张图片

现有抢购活动:

@RequestMapping("/product")
@RestController
public class ProductController {
	
	@Autowired
	private OrderService orderService;
	
	@Autowired
	private ProductService productService;
	
	
    //抢购
	@RequestMapping("/order")
	public void order(@RequestParam String userId,@RequestParam String productId){
		orderService.order(productId,userId);
	}
	
   
    //库存
	@RequestMapping("/select")
	public Integer select(@RequestParam String  productId){
		Integer count = productService.getCountByProductId(productId);
	    return count;
	}
	
}

service实现,主要是订单service:

@Service("orderService")
public class OrderServiceImpl implements OrderService{
	
	@Autowired
	private ProductService productService;
	
	@Autowired
private UserOrderMapper userOrderMapper;

	//用户下订单,返回订单id
	@Override
	public Integer order(String productId, String userId) {
		//先判断productId是否存在
		Product product = productService.getByProductId(productId);
		if(product == null){
			return null;
		}
		//是否有库存
		Integer id = product.getId();
		Integer total = product.getTotal();
		System.out.println("下单前库存"+total);
		if(total <= 0){
			return null;
		}
		
		UserOrder order = new UserOrder();
		order.setCreatetime(new Date());
		order.setProductid(productId);
		order.setUserid(userId);
		int add = userOrderMapper.addOrder(order);
		if(add > 0){
			//创建订单成功,库存--
			total--;
			System.out.println("下单后库存"+total);
			productService.updateTotal(id,total);
		}
		return order.getId();
	}

	@Override
	public Integer getCountByProductId(String productId) {
		return userOrderMapper.getCountByProductId(productId);
	}

}

Redis分布式锁(一)并发问题引入_第3张图片

启动这个工程,运行在本地8080端口。

2、测试:先在数据库中预设有productid为abcd的商品,库存为5。再另建一个工程模拟多线程并发:

public class HttpRequestUtil {
    /**
     * 向指定URL发送GET方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }
 
    /**
     * 向指定 URL 发送POST方法的请求
     * 
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!"+e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }    
}
package com.test.test;

import com.test.util.HttpRequestUtil;

public class ProductTest implements Runnable{
	public static void main(String[] args) {
		ProductTest productTest = new ProductTest();
      for(int i=0;i<50;i++){
    	 Thread thread = new Thread(productTest);
    	  thread.start();
      }
//      String url = "http://localhost:8080/product/select";
//	  String productId = "abcd";
//	  String param = "productId="+productId;
//	  String count = HttpRequestUtil.sendPost(url, param);
//	  System.out.println("总订单数"+count);
	}

	@Override
	public void run() {
		 String url = "http://localhost:8080/product/order";
   	  String productId = "abcd";
   	  String userId = "userid";
   	  String param = "userId="+userId+"&productId="+productId;
   	  HttpRequestUtil.sendPost(url, param);
	}
}

随机一次,看下数据库的变化:

在并发量为50的情况下,商品库存是变为0了,

Redis分布式锁(一)并发问题引入_第4张图片

但实际下单的订单数却不只5次。 原因是多个用户进入到下单逻辑代码后,同时执行了int add = userOrderMapper.addOrder(order);

二、分布式锁:锁一般考虑分布式锁,解决分布式部署架构中的并发问题。

(1)以上是单节点下的高并发问题;

(2)多节点下的并发问题:部署多个节点,单个节点存在并发问题,多个节点自然也存在并发问题;即使单个节点不存在并发问题,某些场景下,多节点访问同一个数据库也存在并发问题。如定时job,两个节点同时去读写数据库,任务可能重复执行。

三、分布式锁的实现方式:分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种:

Redis分布式锁(一)并发问题引入_第5张图片

四、redis锁解决demo中的问题:为达到这样的效果:

Redis分布式锁(一)并发问题引入_第6张图片      Redis分布式锁(一)并发问题引入_第7张图片

1、方法1:使用synchronized修饰:

修饰方法使之成为同步方法:

@Override
	public synchronized Integer order(String productId, String userId) {
		// 先判断productId是否存在
		Product product = productService.getByProductId(productId);

		
			if (product == null) {
				return null;
			}
			// 是否有库存
			Integer id = product.getId();
			Integer total = product.getTotal();
			System.out.println("下单前库存" + total);

			UserOrder order = new UserOrder();
			if (total <= 0) {
				return null;
			}

			order.setCreatetime(new Date());
			order.setProductid(productId);
			order.setUserid(userId);
			int add = userOrderMapper.addOrder(order);
			if (add > 0) {
				// 创建订单成功,库存--
				total--;
				System.out.println("下单后库存" + total);
				productService.updateTotal(id, total);
				return order.getId();
			}
		return null;	
	}

2、方法二:使用redis:原理,redis的SETNX 操作set only if not exist。

@RequestMapping("/product")
@RestController
public class ProductController {

	@Autowired
	private OrderService orderService;

	@Autowired
	private ProductService productService;

	@Autowired
	private RedisUtil redisUtil;

	//private String GROUP_LOCK = "produce:lock:{productId}";

	private String KEY = "productId";

	@RequestMapping("/order")
	public void order(@RequestParam String userId,
			@RequestParam String productId) {

		
		boolean lock = redisUtil.exists(KEY);
		if (!lock) {
			//
			
			Long result = redisUtil.addValue(KEY, productId);
			redisUtil.disableTime(KEY, 60);
	
			if (result == 1) {
				System.err.println("不存在key,执行逻辑操作");
				orderService.order(productId, userId);
				redisUtil.delKey(KEY);
			}
		}else{
			System.out.println("存在该key,不允许执行");
		}
		
	}

	@RequestMapping("/select")
	public Integer select(@RequestParam String productId) {
		Integer count = productService.getCountByProductId(productId);
		return count;
	}

}

其中:

/**
 * @Description:redis工具类
 */
@SuppressWarnings("unused")
@Component
public class RedisUtil {
	private static final String IP = "127.0.0.1"; // ip
	private static final int PORT = 6379; // 端口
	// private static final String AUTH=""; // 密码(原始默认是没有密码)
	private static int MAX_ACTIVE = 1024; // 最大连接数
	private static int MAX_IDLE = 200; // 设置最大空闲数
	private static int MAX_WAIT = 10000; // 最大连接时间
	private static int TIMEOUT = 10000; // 超时时间
	private static boolean BORROW = true; // 在borrow一个事例时是否提前进行validate操作
	private static JedisPool pool = null;
	private static Logger logger = Logger.getLogger(RedisUtil.class);
	/**
	 * 初始化线程池
	 */
	static {
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(MAX_ACTIVE);
		config.setMaxIdle(MAX_IDLE);
		config.setMaxWaitMillis(MAX_WAIT);
		config.setTestOnBorrow(BORROW);
		pool = new JedisPool(config, IP, PORT, TIMEOUT);
	}

	/**
	 * 获取连接
	 */
	public static synchronized Jedis getJedis() {
		try {
			if (pool != null) {
				return pool.getResource();
			} else {
				return null;
			}
		} catch (Exception e) {
			logger.info("连接池连接异常");
			return null;
		}

	}

	// 加锁
	public boolean tryLock(String key){
		Jedis jedis = null;
		jedis = getJedis();

	    String result = jedis.setex(key,1000,"1");


	    if("OK".equals(result)){

	        return true;
	    }
	 
	    return false;
	}

	// 释放锁
	public void  releaseLock(String key){
		Jedis jedis = null;
		jedis = getJedis();
	    jedis.del(key);
	    
	}
	
	/**
	 * @Description:设置失效时间
	 * @param @param key
	 * @param @param seconds
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static void disableTime(String key, int seconds) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			jedis.expire(key, seconds);

		} catch (Exception e) {
			logger.debug("设置失效失败.");
		} finally {
			getColse(jedis);
		}
	}

	public static boolean exists(String key) {
		boolean flag = false;
		Jedis jedis = null;
		try {
			jedis = getJedis();
			flag = jedis.exists(key);
		} catch (Exception e) {
			logger.debug("设置失效失败.");
		} finally {
			getColse(jedis);
		}
		return flag;
	}

	/**
	 * @Description:插入对象
	 * @param @param key
	 * @param @param obj
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static boolean addObject(String key, Object obj) {

		Jedis jedis = null;
		String value = JSONObject.toJSONString(obj);
		try {
			jedis = getJedis();
			jedis.set(key, value);
			return true;
		} catch (Exception e) {
			logger.debug("插入数据有异常.");
			return false;
		} finally {
			getColse(jedis);
		}
	}

	/**
	 * @Description:存储key~value
	 * @param @param key
	 * @param @param value
	 * @return void 返回类型
	 */

	public static Long addValue(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			//String code = jedis.set(key, value);
			return jedis.setnx(key, value);
		
		} catch (Exception e) {
			logger.debug("插入数据有异常.");
			return null;
		} finally {
			getColse(jedis);
		}
		
	}

	/**
	 * @Description:删除key
	 * @param @param key
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static boolean delKey(String key) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			Long code = jedis.del(key);
			if (code > 1) {
				return true;
			}
		} catch (Exception e) {
			logger.debug("删除key异常.");
			return false;
		} finally {
			getColse(jedis);
		}
		return false;
	}

	/**
	 * @Description: 关闭连接
	 * @param @param jedis
	 * @return void 返回类型
	 */

	public static void getColse(Jedis jedis) {
		if (jedis != null) {
			jedis.close();
		}
	}

}

项目目录结构:

Redis分布式锁(一)并发问题引入_第8张图片

现在是这样子的:

Redis分布式锁(一)并发问题引入_第9张图片

Redis分布式锁(一)并发问题引入_第10张图片

随机测试,并发量越大时,就是数据库中库存为0 ,订单表有5条记录。

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