以传统的数据库作为存储介质、以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>