以传统的数据库作为存储介质、以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 {@Autowiredprivate 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);@AutowiredUserRedPacketDao userRedPacketDao;@AutowiredRedPacketDao redPacketDao;@Autowiredprivate RedisUtils redisUtils;private ForkJoinPool joinPool = new ForkJoinPool();//默认内核为服务器的内核数private static final int FAILED = 0;@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED)@Overridepublic int grapRedPacket(long redPacketId, long userId) {LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);//获取红包信息RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);//当前小红包库存大于0if(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)@Overridepublic int grapRedPacketForUpdate(long redPacketId, long userId) {LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);//获取红包信息RedPacket redPacket = redPacketDao.getRedPacketForUpdate(redPacketId);//当前小红包库存大于0if(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)@Overridepublic int grapRedPacketForVersion(long redPacketId, long userId) {LOGGER.info("线程:"+Thread.currentThread().getName() + "  事务开始。。。userId->"+userId);//获取红包信息RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);//当前小红包库存大于0if(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)@Overridepublic 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);//当前小红包库存大于0if(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)@Overridepublic 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);//当前小红包库存大于0if(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]--当前被抢红包keylocal redPacket = 'red_packet_'..KEYS[1]--获取当前红包库存信息local stock = tonumber(redis.call('hget',redPacket,'stock'))--没有库存呢,返回0if stock <= 0 then return 0 end--库存j减1stock = 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";@Overridepublic 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_1LOGGER.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;}@Overrideprotected 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>

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

  1. 计算机性能过低配色方案,系统之家windows7提示更改配色方案提高性能的方法

    有很多win7旗舰版用户在运行一些游戏的时候弹出了是否要更改配色方案来提高性能的提示,原因是检测到计算机性能过低的问题,其实可以选择使用更低内存的windows7 basic主题或者提高虚拟内存,下面 ...

  2. java redis 商品秒杀_使用redis秒杀出现产品超发现象求解?

    亲测,用ab 压测并发500 请求4000 无超卖! header("content-type:text/html;charset=utf-8"); $redis = new re ...

  3. 高并发-【抢红包案例】之一:SSM环境搭建及复现红包超发问题

    文章目录 概述 抢红包案例 案例关注点 工程结构 库表设计 Domain Dao层实现 Service层实现 使用全注解搭建SSM 开发环境 Controller层 View层 运行测试 超量发送的B ...

  4. 高并发-【抢红包案例】之三:使用乐观锁方式修复红包超发的bug

    文章目录 导读 乐观锁 CAS 原理 ABA问题 库表改造 代码改造 RedPacketDao新增接口方法及Mapper映射文件 UserRedPacketServic接口及实现类的改造 Contro ...

  5. 高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug

    文章目录 概述 超发问题分析 使用数据库锁的解决方案 使用悲观锁(排它锁 for update) 使用乐观锁(依靠表的设计和代码) 总结 悲观锁(抽象的描述,不真实存在这个锁) 共享锁(S锁) 排他锁 ...

  6. 基于SpringBoot2 + Redis + MySQL实现一个抢红包系统(至尊典藏版)

    一.需求分析 SpringBoot2 + Redis 实现一个抢红包系统.本文分析一个具体的实现方案,不喜轻喷! 常见的红包系统,由用户指定金额.红包总数来完成红包的创建,然后通过某个入口将红包下发至 ...

  7. 整体大于部分_Redis典型应用场景实战之抢红包系统整体业务流程分析赠书

    在2014年春节,微信上线了红包功能,在短短一个月内,微信支付的用户便从3000万激增到1亿,为微信在移动支付领域争得了一席之地.微信红包成功的原因在本章中不会细谈,因为若是从其产品定位.组织架构.用 ...

  8. 高并发引起的库存超发解决方案

    库存设计:设置锁定库存和总库存,当用户下单未支付时锁定库存,支付成功时释放锁定库存并扣减总库存,当30分钟用户还未支付,此时释放锁定库存不扣减总库存. 一.库存超发原因: 下单流程: 当库存为1时,两 ...

  9. 中国货币超发严重 去年新增货币占全球近一半

    [提要] 2009年以来,中国已成为目前全球最大的"印钞机".2012年,全球新增货币供应量中国占近一半.21世纪网评估发现,均衡人均收入差异后,中国的经济货币化程度高居全球前列. ...

最新文章

  1. 一个最简单的通过WireShark破解SSL加密网络数据包的方法
  2. python 异常处理中try else语句的使用
  3. Codeforces Round #395 (Div. 2)(未完)
  4. idea设置默认maven路径(2020版idea)
  5. why FOR ALL ENTRIES is not considered at all in one order search
  6. Matlab数理统计工具箱应用简介
  7. vue.js 入门,简介
  8. 汇编:汇编语言实现冒泡排序(loop指令实现)
  9. MariaDB学习记录
  10. 如何检查计算机是否超频了,如何判断电脑是否支持超频?知识点get
  11. 设计模式 - 建造者模式
  12. 英语学习网站超级大全(转载自豆瓣网)
  13. https://blog.csdn.net/zxp_cpinfo/article/details/53692922
  14. Java并发 ReentrantLock(重入锁)之非公平锁源码解析 超详细!!
  15. 分享写SQL的21个好习惯!
  16. 文件上传和OSS上传至阿里云
  17. PyCharm许可证过期解决方案
  18. 【第11天】SQL进阶-索引的创建、删除(SQL 小虚竹)
  19. 产品防伪码查询系统_学历学籍查询系统_证书查询系统_录取成绩查询系统_工资查询系统_信息查询系统
  20. android t时间工具,Android Market:正點工具箱,6大好用工具集一身

热门文章

  1. autojs-读写ini
  2. Lua的一些常用函数
  3. SpringBoot打成jar包部署,Excel模板下载文件遇到的问题
  4. 2018,互联网套餐还会继续辉煌吗?
  5. 【华为OD机试】1030 - 图片整理
  6. USG6000V测试
  7. PHP 根据身份证号识别 - 星座,生肖,性别
  8. Golang 下载文件
  9. Ubuntu16.04环境下文件下载
  10. 上班摸鱼在群里吹牛B,逮到一个华为10年老Java开发,聊过之后收益良多...