抢红包系统搭建和超发现象,以及解决问题提高性能

以传统的数据库作为存储介质、以Redis+Lua来作为库存存储介质最后落到数据库

本次案例介绍抢红包的场景,模拟20万红包,分为400个小红包,每个红包500元,有1000千人并发同时抢夺,并讲解会出现超发和保证如何数据一致性问题,在高并发场景下还需要保证性能的问题。

在这里,首先查看红包库存,是否有,如果有,则更新库存减一,然后插入一条记录到抢红包信息表中,这3个操作作为一个事务,原子性来操作。下面也讲解高并发下事务的问题

第一种:在没有任何锁的情况下,就是仅仅对事务操作操作,数据库也没有使用任何悲观锁。在1000个高并发下,数据出现超发

现象总共400个,出现了405个。

 

第二种:在代码层上使用synchronized,因为synchronized是在jvm语义层面的,后来jdk版本中对其作了优化,偏向锁、轻量级锁、重量级锁,因为高并发下众多线程访问synchronized的方法,所以会转为重量级锁(悲观锁,只有一个线程去执行抢红包)性能降低,,可采用ReentrantLock可重入锁。

(使用Lock接口 ReenTrant来显示加锁)

(解决了数据不一致问题,但是在互联网下,其性能有待提高。)

第三种:悲观锁是利用数据库的内部加锁机制,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有数据库的记录,其他线程将不能在对数据进行更新操作了,

注意这里使用的SQL中加入了for update 语句,意味着将持有对数据库记录的行更新操作(因为这里使用主键查询id为主键,所以是行级锁。如果使用的非主键查询,则会对整个表进行加锁,可能引发其他查询的阻塞)。

mysql中的行级锁:1、只有通过索引检索条件,innodb才会使用行级锁,否则使用表锁。

                               2、即使查询不同行的记录,如果查询的索引建是相同的,则也会产生锁冲突。

                               3、 如果数据表建有多个索引时,可以通过不同的索引锁定不同的行。

(解决了数据不一致问题,但是在互联网下,其性能有待提高。使用悲观锁就会造成大量的线程被挂起和恢复,这将十分消耗资源,从用户态切换到内核态,cpu频繁切换上下文,所以导致性能降低,悲观锁也称为独占锁。)

为了克服悲观锁带来的性能问题,提高并发处理的能力,避免大量线程因为阻塞导致CPU频繁切换上下文,故提出了乐观锁机制,并且已经在企业中被大量的应用了。

第四种:乐观锁是一种不阻塞其他线程的并发机制,称为非阻塞锁,不使用数据库的锁机制,因为不阻塞其他线程,所以并不会引发线程频繁挂起和恢复,便能够提高并发能力,乐观锁使用的是CAS原理。

CAS原理阐述:compareAndSwap 比较并交换的底层原理机制,对于多个线程竞争相同的资源,先保存一个旧值(old value)当扣库存的时候,先比较当前数据库的值与旧值是否一致,如果一致则进行扣减,否则就认为已经被其他线程修改过了,就不再进行操作了,可以考虑重试或者放弃,多数采用重试的机制,这样就是一个可重入锁了,流程如下:

CAS不排斥并发,也不独占资源,但是会出现一个问题,ABA的问题,可以使用一个自增的version版本号来解决问题。

         第一阶段采用乐观锁实现

版本号自增避免ABA的问题,对于查询也不使用显示的for update语句,避免锁发生的冲突,即没有线程阻塞的问题。

性能是提高了不少,但是成功率降低了,失败率高,有时候会容忍这个失败,这取决于业务的需求,因为允许用户再次发起抢夺红包,比如微信抢红包也常常会发生错误返回,然后用户再次去抢。因为库存不变,不会导致超发。

为了克服这个问题,提高成功率,还会考虑可重入机制(重试机制)也就是因为版本号不一致而失败,可以重试尝试去请求抢红包,但是过多的查询会造成大量的sql执行,目前比较流行方案:一种是按照时间的重入。(比如在100毫秒,不成功的会循环成功为止,直到时间超时退出,返回失败)。另一种是按照次数,比如限定3次,失败后重试,知道达到次数退出。这两种都有助于提高抢红包的成功率。

            二阶段的乐观锁(优化)按照时间、按照次数来进行限定的重试机制,目的提高成功率,对于使用乐观锁,操作业务(先查询红包信息库存以及版本号,然后根据当前版本号与库中的版本号匹配,如一致,则添加抢红包的用户信息到库中),涉及查询-更新-插入,因为使用了乐观锁,所以业务操作可以不使用事务,将三种操作作为一个原子性的,因为如果在方法上加了事务的话,在更新语句的时候,其他线程在操作的时候,就会阻塞,知道前一个线程退出事务,才会抢到锁。如果不加事务,就会出现更新失败,不会回滚,则用户信息表多了或者少了,不够正确,这边可以使用异步化,比如消息队列。

按照时间重试机制,就要设定好时间,过长会导致查询次数增多,也不宜过短,根据业务时间定。

 

以下为Redis来实现抢红包

        reidis事先预备好数据 库存信息

性能远远超过乐观锁的20多秒。在这个普通请求中,并没有去操作任何数据库,而只是将数据存放在redis缓存中,且库存放在内存中比放入数据库存取效率更快,以下就是抢红包的流程。

各类方式的优缺点:

           悲观锁使用了数据库的内部锁机制,可以消除数据一致性的问题,处理十分简单,但是使用悲观锁后,数据库的性能有所下降,因为大量的线程被阻塞,而且需要大量的恢复过程,需要进一步的算法来改善以提高系统的并发性能。

           通过CAS 原理和ABA的问题讨论,更加对乐观锁有了清晰的认识,有助于提高并发处理性能,但是由于版本号的冲突,乐观锁导致多次请求服务失败的概率提高,而通过乐观锁的可重入(重试)机制l来提高请求成功率,实现比较复杂了,其性能也会随着版本号冲突的概率提高而提升,并不稳定。其弊端在于,导致大量的sql语句被执行,容易引起数据库性能的瓶颈。

           使用Redis去实现高并发,通过redis提供的Lua脚本的原子性,消除了了数据不一致的问题,消除了数据不一致的问题,且插入用户信息可使用队列方式异步去操作,这样使用的风险在于redis的不稳定性,因为其事务和存储都存在不稳定的因素,所以更多的时候,建议使用独立的redis服务器来作高并发业务,一方面提高redis 的性能,另一方面即使在高并发的场合,redis服务器宕机也不会影响其他现有的业务,同时可以使用备机来提高系统的高可用,保证网站的安全稳定。

以下是代码:

Controller层

package com.webTest.HAConcurrent.GrabRedPacket.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.webTest.HAConcurrent.GrabRedPacket.service.UserRedPacketService;

@Controller
@RequestMapping("/userRedpacket")
public class UserRedPacketController {

	
	@Autowired
	private UserRedPacketService userRedPacketService;
	
	private Lock lock = new ReentrantLock();
	
	/**
	 * 无任何处理,导致超发,  400个红包,,1000个并发,,31秒结束
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket.do")
	public Map<String,Object> grapRedPacket(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacket(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	
	/**
	 * 仅仅 在代码层加锁  (synchronized  独占模式)  数据一致,性能降低
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket2.do")
	public Map<String,Object> grapRedPacket2(long redPacketId,long userId){
		int grapRedPacket = 0;
		/*synchronized (this) {
			grapRedPacket = userRedPacketService.grapRedPacket(redPacketId, userId);
		}*/
		lock.lock();
		try{
			grapRedPacket = userRedPacketService.grapRedPacket(redPacketId, userId);
		}finally{
			lock.unlock();
		}
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	/**
	 * 利用悲观锁,是一种利用数据库的n内部机制提供的锁,对数据更新加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,
	 * 其他线程将不能在对数据进行更新
	 * 在sql 语句中 加for update  前提是该列上是索引,保证行级锁,不然会是表锁,导致性能降低
	 * 等线程1事务提交后,其他线程的事务开始竞争资源
	 * 悲观锁导致性能会降低,cpu上下文频繁切换与竞争资源  ,并出现阻塞
	 *
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket3.do")
	public Map<String,Object> grapRedPacket3(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacketForUpdate(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	/**
	 * 为了提高效率,高并发性能
	 * 采用乐观锁,CAS原理并不排斥并发,也不独占资源。  会出现ABA问题, 添加一个version版本号来解决,version一直增加
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket4.do")
	public Map<String,Object> grapRedPacket4(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacketForVersion(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	/**
	 * 因为乐观锁,版本号不一致就直接抢红包失败,导致成功率不高
	 * 所以需要重试机制,
	 * 目前流行的方案,1、根据时间可重入  2、根据次数可重入
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket4_1.do")
	public  Map<String,Object>  grapRedPacket4_1(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacketForVersionByTime(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	/**
	 * 根据次数来解决成功率低的问题
	 * @param redPacketId
	 * @param userId
	 * @return
	 * Map<String,Object>
	 * @author 88397658
	 * @since
	 */
	@ResponseBody
	@RequestMapping("/grapRedPacket4_2.do")
	public  Map<String,Object>  grapRedPacket4_2(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacketForVersionByTimes(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
	
	@ResponseBody
	@RequestMapping("/grapRedPacketByReds.do")
	public  Map<String,Object>  grapRedPacketByReds(long redPacketId,long userId){
		int grapRedPacket = userRedPacketService.grapRedPacketByRedis(redPacketId, userId);
		Map<String , Object> resMap = new HashMap<String, Object>();
		boolean flag = grapRedPacket > 0;
		resMap.put("success", flag);
		resMap.put("message", flag?"抢红包成功":"抢红包失败");
		return resMap;
	}
}

 

service层

package com.webTest.HAConcurrent.GrabRedPacket.service.impl;

import java.sql.Timestamp;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import redis.clients.jedis.Jedis;

import com.base.common.RedisUtils;
import com.webTest.HAConcurrent.GrabRedPacket.bean.RedPacket;
import com.webTest.HAConcurrent.GrabRedPacket.bean.UserRedPacket;
import com.webTest.HAConcurrent.GrabRedPacket.dao.RedPacketDao;
import com.webTest.HAConcurrent.GrabRedPacket.dao.UserRedPacketDao;
import com.webTest.HAConcurrent.GrabRedPacket.service.UserRedPacketService;

@Service
public class UserRedPacketServiceImpl implements UserRedPacketService {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(UserRedPacketServiceImpl.class);
	
	@Autowired
	UserRedPacketDao userRedPacketDao;
	
	@Autowired
	RedPacketDao redPacketDao;
	
	@Autowired
	private RedisUtils redisUtils;
	
	private ForkJoinPool joinPool = new ForkJoinPool();//默认内核为服务器的内核数
	
	private static final int FAILED = 0;

	@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)
	@Override
	public int grapRedPacket(long redPacketId, long userId) {
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);
		//获取红包信息
		RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
		//当前小红包库存大于0
		if(redPacket.getStock() > 0){
			redPacketDao.decreaseRedPacket(redPacketId);
			//生成抢红包信息
			UserRedPacket packet = new UserRedPacket();
			packet.setUserId(userId);
			packet.setRedPacketId(redPacketId);
			packet.setAmount(redPacket.getUnitAmount());
			packet.setGrabTime(new Timestamp(System.currentTimeMillis()));
			packet.setNote("grap success!!!"+redPacketId);
			//插入抢红包信息
			int grapRedPacket = userRedPacketDao.grapRedPacket(packet);
			LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。userId->"+userId+"插入成功" + grapRedPacket );
			return grapRedPacket;
		}
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。。userId->"+userId);
		return FAILED;
	}
	
	@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)
	@Override
	public int grapRedPacketForUpdate(long redPacketId, long userId) {
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);
		//获取红包信息
		RedPacket redPacket = redPacketDao.getRedPacketForUpdate(redPacketId);
		//当前小红包库存大于0
		if(redPacket.getStock() > 0){
			redPacketDao.decreaseRedPacket(redPacketId);
			//生成抢红包信息
			UserRedPacket packet = new UserRedPacket();
			packet.setUserId(userId);
			packet.setRedPacketId(redPacketId);
			packet.setAmount(redPacket.getUnitAmount());
			packet.setGrabTime(new Timestamp(System.currentTimeMillis()));
			packet.setNote("grap success!!!"+redPacketId);
			//插入抢红包信息
			int grapRedPacket = userRedPacketDao.grapRedPacket(packet);
			LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。userId->"+userId+"插入成功" + grapRedPacket );
			return grapRedPacket;
		}
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。。userId->"+userId);
		return FAILED;
	}

	/**
	 * 这里可以不使用事务,事务只是保证失败了,就回滚,会影响效率
	 */
//	@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)
	@Override
	public int grapRedPacketForVersion(long redPacketId, long userId) {
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);
		//获取红包信息
		RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
		//当前小红包库存大于0
		if(redPacket.getStock() > 0){
			int updateForV = redPacketDao.decreaseRedPacketForVersion(redPacketId,redPacket.getVersion());
			//如果没有数据要更新,则说明其他线程已经修改过了数据,本次抢红包失败
			if(updateForV == 0){
				return FAILED;
			}
			//生成抢红包信息
			UserRedPacket packet = new UserRedPacket();
			packet.setUserId(userId);
			packet.setRedPacketId(redPacketId);
			packet.setAmount(redPacket.getUnitAmount());
			packet.setGrabTime(new Timestamp(System.currentTimeMillis()));
			packet.setNote("grap success!!!"+redPacketId);
			//插入抢红包信息
			int grapRedPacket = userRedPacketDao.grapRedPacket(packet);
			LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。userId->"+userId+"插入成功" + grapRedPacket );
			return grapRedPacket;
		}
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。。userId->"+userId);
		return FAILED;
	}
	
	/**
	 * 这里可以不使用事务,事务只是保证失败了,就回滚,会影响效率
	 */
//	@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)
	@Override
	public int grapRedPacketForVersionByTime(long redPacketId, long userId) {
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);
		//记录开始时间
		long start = System.currentTimeMillis();
		//无限循环,等待成功或者超过指定的时间退出
		while(true){
			long end = System.currentTimeMillis();
			//当时间超过100毫秒  则退出
			if(end - start > 100){
				LOGGER.info("超过指定重试时间,退出");
				return FAILED;
			}
			//获取红包信息
			RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
			//当前小红包库存大于0
			if(redPacket.getStock() > 0){
				int updateForV = redPacketDao.decreaseRedPacketForVersion(redPacketId,redPacket.getVersion());
				
				//如果没有数据要更新,则说明其他线程已经修改过了数据,则重试再次去抢
				if(updateForV == 0){
					continue;
				}
				//生成抢红包信息
				UserRedPacket packet = new UserRedPacket();
				packet.setUserId(userId);
				packet.setRedPacketId(redPacketId);
				packet.setGrabTime(new Timestamp(System.currentTimeMillis()));
				packet.setAmount(redPacket.getUnitAmount());
				packet.setNote("grap success!!!"+redPacketId);
				//插入抢红包信息
				int grapRedPacket = userRedPacketDao.grapRedPacket(packet);
				LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。userId->"+userId+"插入成功" + grapRedPacket );
				return grapRedPacket;
			}else{
				LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。。userId->"+userId);
				return FAILED;
			}
		}
	}
	
	/**
	 * 这里可以不使用事务,事务只是保证失败了,就回滚,会影响效率
	 */
//	@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)
	@Override
	public int grapRedPacketForVersionByTimes(long redPacketId, long userId) {
		LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);
		//循环3次,等待成功或者超过指定的次数退出
		for(int i =1; i<=2;i++){
			//获取红包信息
			RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
			//当前小红包库存大于0
			if(redPacket.getStock() > 0){
				int updateForV = redPacketDao.decreaseRedPacketForVersion(redPacketId,redPacket.getVersion());
				//如果没有数据要更新,则说明其他线程已经修改过了数据,则重试再次去抢
				if(updateForV == 0){
					continue;
				}
				//生成抢红包信息
				UserRedPacket packet = new UserRedPacket();
				packet.setUserId(userId);
				packet.setRedPacketId(redPacketId);
				packet.setGrabTime(new Timestamp(System.currentTimeMillis()));
				packet.setAmount(redPacket.getUnitAmount());
				packet.setNote("grap success!!!"+redPacketId);
				//插入抢红包信息
				int grapRedPacket = userRedPacketDao.grapRedPacket(packet);
				LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。userId->"+userId+"插入成功" + grapRedPacket );
				return grapRedPacket;
			}else{
				LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务结束。。。userId->"+userId);
				return FAILED;
			}
		}
		return FAILED;
	}

	//在缓存Lua脚本h后,使用该变量保存redis返回的32位的SHA1密文,使用它去执行缓存z中的lua脚本
	private String sha1 = null;
	/**
	 * lua脚本
	 * 
	 * local listKey = 'red_packet_list_'..KEYS[1]
		--当前被抢红包key
   		local redPacket = 'red_packet_'..KEYS[1]
   		--获取当前红包库存信息
   		local stock = tonumber(redis.call('hget',redPacket,'stock'))
   		--没有库存呢,返回0
   		if stock <= 0 then return 0 end
   		--库存j减1
   		stock = stock -1
   		--保存当前库存
   		redis.call('hset',redPacket,'stock',tostring(stock))
   		--往链表中加入当前红包信息
   		redis.call('rpush',listKey,ARGV[1])
   		--如果是最后一个红包则返回2,表示抢红包已经结束,需要j将列表中的数据保存到s数据库中
   		if stock == 0 then return 2 end
   		--如果并非是最后一个红包,则返回1,表示抢红包成功
   		return 1
	 */
	private String luaScriptString = "local listKey = 'red_packet_list_'..KEYS[1] \n" 
									+ " local redPacket = 'red_packet_'..KEYS[1] \n"
									+ " local stock = tonumber(redis.call('hget',redPacket,'stock')) \n"
									+" if stock <= 0 then return 0 end \n"
									+ " stock = stock -1 \n"
									+" redis.call('hset',redPacket,'stock',tostring(stock)) \n"
									+" redis.call('rpush',listKey,ARGV[1]) \n"
									+" if stock == 0 then return 2 end \n"
									+" return 1";
	@Override
	public int grapRedPacketByRedis(long redPacketId, long userId) {
		//当前抢红包的用户信息和日期信息
		String args = userId + "-"+System.currentTimeMillis();
		long result = 0;
		//获取底层Redis的操作
		Jedis jedis = (Jedis) redisUtils.getRedisTemplate().getConnectionFactory().getConnection().getNativeConnection();
		try{
			//如果脚本没有加载过,则进行加载,然后返回一个sha1密文
			if(sha1 == null){
				sha1 = jedis.scriptLoad(luaScriptString);
			}
			//执行脚本,返回结果
			Object res = jedis.evalsha(sha1,1,redPacketId+"",args);
			result = (long) res;
			//返回2 时,为最后一个红包,此时将用户抢红包的信息通过异步去存储到数据库中,可用JMS消息队列,在这里使用forkJoinpork来并行插入
			if(result == 2){
				LOGGER.info("全部抢完,将用户信息存储到数据库中。。。。");
				List<Object> list = redisUtils.getList("red_packet_list_"+redPacketId);
				BatchHandlerGrapInfo grapInfo = new BatchHandlerGrapInfo(list, 500.00, redPacketId, 0, list.size(), userRedPacketDao);
				joinPool.submit(grapInfo).get();
				//删除redis中的节点red_packet_list_1
				LOGGER.info("删除redis中的red_packet_list_1节点  。。。。");
				redisUtils.delete("red_packet_list_"+redPacketId);
			}
		}catch (Exception e) {
			LOGGER.error("出现问题!!!"+e.getMessage());
		}finally{
			//确保redis顺利关闭
			if(jedis != null && jedis.isConnected()){
				jedis.close();
			}
		}
		return (int)result;
	}

}
/**
 * 批量插入红包信息
 * @author MTW
 *
 */
class BatchHandlerGrapInfo extends RecursiveAction{
	
	private static final Logger LOGGER = LoggerFactory.getLogger(BatchHandlerGrapInfo.class);
	
	UserRedPacketDao userRedPacketDao;
	
	private List<Object> list = null;
	
	private Double unitAmountDouble = null;
	
	private Long redPacketId;
	
	private int start,end=0;
	
	private int middle = 150;
	public BatchHandlerGrapInfo(List<Object> list,Double unDouble,Long redPacketId,int start,int end,UserRedPacketDao userRedPacketDao) {
		this.list = list;
		this.unitAmountDouble = unDouble;
		this.start = start;
		this.end = end;
		this.userRedPacketDao = userRedPacketDao;
		this.redPacketId = redPacketId;
	}

	@Override
	protected void compute() {
		
		if(end - start <= middle){
			for(int i = start ; i< end ;i++){
				String args = list.get(i).toString();
				String[] split = args.split("-");
				Long userIdLong = Long.parseLong(split[0]);
				Long time = Long.parseLong(split[1]);
				//抢包信息
				//生成抢红包信息
				UserRedPacket packet = new UserRedPacket();
				packet.setUserId(userIdLong);
				packet.setRedPacketId(redPacketId);
				packet.setAmount(unitAmountDouble);
				packet.setGrabTime(new Timestamp(time));
				packet.setNote("grap success!!!"+redPacketId);
				//插入抢红包信息
				userRedPacketDao.grapRedPacket(packet);
				LOGGER.info("抢红包用户信息  插入成功!!!"+i);
			}
			
		}else{
			middle = (start + end)/2;
			BatchHandlerGrapInfo handlerGrapInfo = new BatchHandlerGrapInfo(list, unitAmountDouble,redPacketId, start, middle, userRedPacketDao);
			BatchHandlerGrapInfo handlerGrapInfo2 = new BatchHandlerGrapInfo(list, unitAmountDouble,redPacketId, middle, end, userRedPacketDao);
			invokeAll(handlerGrapInfo,handlerGrapInfo2);
		}
		
	}
	
}

mybatis xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.webTest.HAConcurrent.GrabRedPacket.dao.RedPacketDao">
	
	<!-- 查询红包信息 -->
	<select id="getRedPacket" parameterType="long" resultType="com.webTest.HAConcurrent.GrabRedPacket.bean.RedPacket">
		select id,user_id,amount,send_date as sendDate,total,unit_amount as unitAmount,stock,version,note 
		from t_red_packet where id = #{id}
	</select>
	
	<select id="getRedPacketForUpdate" parameterType="long" resultType="com.webTest.HAConcurrent.GrabRedPacket.bean.RedPacket">
		select id,user_id,amount,send_date as sendDate,total,unit_amount as unitAmount,stock,note 
		from t_red_packet where id = #{id} for update
	</select>
	
	<!-- 扣减抢红包库存 -->
	<update id="decreaseRedPacket">
		update t_red_packet set stock =stock-1 where id = #{id}
	</update>
	<!-- 通过版本号扣减没更新一次,版本增加1,其次是对版本号的比较 -->
	<update id="decreaseRedPacketForVersion">
		update t_red_packet set stock =stock-1,version=version+1 where id = #{0} and version =#{1} 
	</update>
	
	
</mapper>

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值