最近遇到比較多數據不一致的問題,大多數都是因為並發請求時,沒及時處理的原因,故用一個比較有代表性的業務場景【活動秒殺】來模擬一下這個這種高並發所產生的問題。
首先搭建一個springboot項目在這里我做演示了,不會的可以自行百度,搭建過程很簡單。
1:搭建好的項目目錄結構
2:商品表(記錄商品名稱,本次可以秒殺的庫存量)
加了一條記錄(后面每次測試都先手動把庫存恢復成100才進行測試)
3:實體類(這里不用實體類也可以,根據自己的需求來)
一、不做任何處理的高並發秒殺實現(錯誤演示):
1.Controller層,模擬500個並發調用:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
miaoshaService.miaoshaGoods(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,每個請求進來就去數據庫里查剩余的庫存量,並且搶購成功后,就減1個庫存:
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
}
3.dao層(mybatis的xml文件):
<select id="getGoods" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from miao_sha_goods
where goods_name = #{goodsName,jdbcType=VARCHAR}
</select>
<update id="updateMsGoods" parameterType="com.mybatis.model.MiaoShaGoods">
update miao_sha_goods
set goods_sum = #{goodsSum,jdbcType=INTEGER}
where goods_name = #{goodsName,jdbcType=VARCHAR}
</update>
4.測試結果:
截圖表明,居然有500個人搶購成功,而且庫存量卻只減少了12個,這是明顯是錯誤的。
二、數據庫樂觀鎖處理的高並發秒殺實現:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
//miaoshaService.miaoshaGoods(request,response);
//數據庫樂觀鎖秒殺
miaoshaService.miaoshaGoods_sql_optimistic_lock(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,每個請求進來就去數據庫里查剩余的庫存量,並且搶購成功后,就減1個庫存:
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* 數據庫樂觀鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_sql_optimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_lgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_lgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
//做出相應的邏輯(記錄搶購成功的用戶名什么的....)
}else{
System.out.println("搶到iphoneX,失敗!");
//重試或者返回友好的提示什么的....
}
return response;
}
}
3.dao層(mybatis的xml文件)[在SQL層面改為數據庫樂觀鎖]:
<select id="getGoods_lgs" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from miao_sha_goods
where goods_name = #{goodsName,jdbcType=VARCHAR}
</select>
<update id="updateMsGoods_lgs" parameterType="com.mybatis.model.MiaoShaGoods">
update miao_sha_goods
set goods_sum = #{goodsSum,jdbcType=INTEGER},version=version+1
where goods_name = #{goodsName,jdbcType=VARCHAR} and version = #{version,jdbcType=VARCHAR}
</update>
4.測試結果:
截圖表明,總共有500個人搶,有29個人搶購成功,而且庫存量減少了29個,這保證了庫存的正確性。但卻會有搶購不成功的請求,需要我們后續去處理。
三、數據庫悲觀鎖處理的高並發秒殺實現:
1.Controller層,模擬500個並發調用:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
//miaoshaService.miaoshaGoods(request,response);
//數據庫樂觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_optimistic_lock(request,response);
//數據庫悲觀鎖秒殺
miaoshaService.miaoshaGoods_sql_pessimistic_lock(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,每個請求進來就去數據庫里查剩余的庫存量,並且搶購成功后,就減1個庫存(查詢和更新必須在同一個事務):
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* 數據庫樂觀鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_sql_optimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_lgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_lgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
//做出相應的邏輯(記錄搶購成功的用戶名什么的....)
}else{
System.out.println("搶到iphoneX,失敗!");
//重試或者返回友好的提示什么的....
}
return response;
}
/**
* 數據庫悲觀鎖實現秒殺
* @param request
* @return
*/
@Override
@Transactional
public BaseResponse miaoshaGoods_sql_pessimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_bgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_bgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
}
3.dao層(mybatis的xml文件)[在SQL層面改為數據庫悲觀鎖]:
<select id="getGoods_bgs" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from miao_sha_goods
where goods_name = #{goodsName,jdbcType=VARCHAR} FOR UPDATE
</select>
<update id="updateMsGoods_bgs" parameterType="com.mybatis.model.MiaoShaGoods">
update miao_sha_goods
set goods_sum = #{goodsSum,jdbcType=INTEGER}
where goods_name = #{goodsName,jdbcType=VARCHAR}
</update>
4.測試結果:
截圖表明,總共有500個人搶,有100個人搶購成功,而且庫存量減少了100個,這保證了庫存的正確性。
四、java線程同步鎖處理的高並發秒殺實現:
1.Controller層,模擬500個並發調用:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
//miaoshaService.miaoshaGoods(request,response);
//數據庫樂觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_optimistic_lock(request,response);
//數據庫悲觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_pessimistic_lock(request,response);
//java線程同步鎖秒殺
miaoshaService.miaoshaGoods_java_synchronized_lock(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,每個請求進來就去數據庫里查剩余的庫存量,並且搶購成功后,就減1個庫存:
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* 數據庫樂觀鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_sql_optimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_lgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_lgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
//做出相應的邏輯(記錄搶購成功的用戶名什么的....)
}else{
System.out.println("搶到iphoneX,失敗!");
//重試或者返回友好的提示什么的....
}
return response;
}
/**
* 數據庫悲觀鎖實現秒殺
* @param request
* @return
*/
@Override
@Transactional
public BaseResponse miaoshaGoods_sql_pessimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_bgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_bgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* java同步鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_java_synchronized_lock(MiaoshaRequest request,BaseResponse response) {
synchronized(this){
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
}
return response;
}
}
3.dao層(mybatis的xml文件):
<select id="getGoods" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from miao_sha_goods
where goods_name = #{goodsName,jdbcType=VARCHAR}
</select>
<update id="updateMsGoods" parameterType="com.mybatis.model.MiaoShaGoods">
update miao_sha_goods
set goods_sum = #{goodsSum,jdbcType=INTEGER}
where goods_name = #{goodsName,jdbcType=VARCHAR}
</update>
4.測試結果:
截圖表明,總共有500個人搶,有100個人搶購成功,而且庫存量減少了100個,這保證了庫存的正確性。
五、java線程可重入鎖處理的高並發秒殺實現:
1.Controller層,模擬500個並發調用:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
//miaoshaService.miaoshaGoods(request,response);
//數據庫樂觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_optimistic_lock(request,response);
//數據庫悲觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_pessimistic_lock(request,response);
//java線程同步鎖秒殺
//miaoshaService.miaoshaGoods_java_synchronized_lock(request,response);
//java線程可重入鎖秒殺
miaoshaService.miaoshaGoods_java_reentrant_lock(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,每個請求進來就去數據庫里查剩余的庫存量,並且搶購成功后,就減1個庫存:
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* 數據庫樂觀鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_sql_optimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_lgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_lgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
//做出相應的邏輯(記錄搶購成功的用戶名什么的....)
}else{
System.out.println("搶到iphoneX,失敗!");
//重試或者返回友好的提示什么的....
}
return response;
}
/**
* 數據庫悲觀鎖實現秒殺
* @param request
* @return
*/
@Override
@Transactional
public BaseResponse miaoshaGoods_sql_pessimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_bgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_bgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* java同步鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_java_synchronized_lock(MiaoshaRequest request,BaseResponse response) {
synchronized(this){
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
}
return response;
}
/**
* java可重入鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_java_reentrant_lock(MiaoshaRequest request,BaseResponse response) {
lock.lock();
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println(request.getGoodNames()+"搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
lock.unlock();
return response;
}
}
3.dao層(mybatis的xml文件):
<select id="getGoods" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from miao_sha_goods
where goods_name = #{goodsName,jdbcType=VARCHAR}
</select>
<update id="updateMsGoods" parameterType="com.mybatis.model.MiaoShaGoods">
update miao_sha_goods
set goods_sum = #{goodsSum,jdbcType=INTEGER}
where goods_name = #{goodsName,jdbcType=VARCHAR}
</update>
4.測試結果:
截圖表明,總共有500個人搶,有100個人搶購成功,而且庫存量減少了100個,這保證了庫存的正確性。
六、redis單線程處理的高並發秒殺實現(推薦):
1.Controller層,模擬500個並發調用:
package com.mybatis.controller;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value="/miaoshagoods")
public class MiaoshaController {
@Autowired
public MiaoshaService miaoshaService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@PostMapping("/miaosha_java_sql_lock")
public @ResponseBody BaseResponse miaoshaJavaSqlLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//不做任何處理的秒殺實現
//miaoshaService.miaoshaGoods(request,response);
//數據庫樂觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_optimistic_lock(request,response);
//數據庫悲觀鎖秒殺
//miaoshaService.miaoshaGoods_sql_pessimistic_lock(request,response);
//java線程同步鎖秒殺
//miaoshaService.miaoshaGoods_java_synchronized_lock(request,response);
//java線程可重入鎖秒殺
miaoshaService.miaoshaGoods_java_reentrant_lock(request,response);
}
});
thread.start();
}
return response;
}
// redis 防止超賣
@PostMapping("/miaosha_redis_lock")
public @ResponseBody BaseResponse miaoshaRedisLock(@RequestBody MiaoshaRequest request){
BaseResponse response=new BaseResponse();
//初始化商品數量
Integer goodsSum=miaoshaService.getGoodsSum(request);
redisTemplate.opsForValue().set(request.getGoodNames()+":goodsSum",goodsSum+"");
System.out.println("總共的庫存量:"+goodsSum);
for(int i=0;i<500;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
miaoshaService.miaoshaGoods_redis(request,response);
}
});
thread.start();
}
return response;
}
}
2.Service層,先把參與秒殺的總庫存量寫入redis里,然后再利用redis的增量方法去遞減:
package com.mybatis.service.Impl;
import com.mybatis.dao.MiaoShaGoodsDao;
import com.mybatis.domain.BaseResponse;
import com.mybatis.domain.MiaoshaRequest;
import com.mybatis.model.MiaoShaGoods;
import com.mybatis.service.MiaoshaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class MiashaServiceImpl implements MiaoshaService{
@Autowired
MiaoShaGoodsDao miaoShaGoodsDao;
private Lock lock = new ReentrantLock();
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 不做任何處理的秒殺實現
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods(MiaoshaRequest request, BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* 數據庫樂觀鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_sql_optimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_lgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_lgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
//做出相應的邏輯(記錄搶購成功的用戶名什么的....)
}else{
System.out.println("搶到iphoneX,失敗!");
//重試或者返回友好的提示什么的....
}
return response;
}
/**
* 數據庫悲觀鎖實現秒殺
* @param request
* @return
*/
@Override
@Transactional
public BaseResponse miaoshaGoods_sql_pessimistic_lock(MiaoshaRequest request,BaseResponse response) {
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods_bgs(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods_bgs(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
/**
* java同步鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_java_synchronized_lock(MiaoshaRequest request,BaseResponse response) {
synchronized(this){
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println("搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
}
return response;
}
/**
* java可重入鎖實現秒殺
* @param request
* @return
*/
@Override
public BaseResponse miaoshaGoods_java_reentrant_lock(MiaoshaRequest request,BaseResponse response) {
lock.lock();
int countSuc=0;
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
if(miaoShaGoods.getGoodsSum()>0){
miaoShaGoods.setGoodsSum(miaoShaGoods.getGoodsSum()-1);
countSuc= miaoShaGoodsDao.updateMsGoods(miaoShaGoods);
}
if(countSuc==1){
System.out.println(request.getGoodNames()+"搶到iphoneX,成功!");
}else{
System.out.println("搶到iphoneX,失敗!");
}
lock.unlock();
return response;
}
@Override
public Integer getGoodsSum(MiaoshaRequest request) {
MiaoShaGoods miaoShaGoods=miaoShaGoodsDao.getGoods(request.getGoodNames());
return miaoShaGoods.getGoodsSum();
}
/**
* redis實現秒殺
* @param response
* @return
*/
@Override
public BaseResponse miaoshaGoods_redis(MiaoshaRequest request,BaseResponse response) {
//增量計算剩余庫存(利用redis的單線程特性)
double goodsSurplusSum=redisTemplate.opsForValue().increment(request.getGoodNames()+":goodsSum",-1);
if(goodsSurplusSum>=0){
System.out.println("搶到iphoneX,成功!還剩下:"+goodsSurplusSum);
}else {
System.out.println("搶到iphoneX,失敗!");
}
return response;
}
}
3.測試結果:
截圖表明,總共有500個人搶,有100個人搶購成功,而且庫存量減少了100個,這保證了庫存的正確性,搶后redis顯示是-400個,說明有500個人進來了,有400個人搶不到。
綜上所述,要控制庫存量,一般要用鎖機制。但是一般加鎖的話會比較影響性能(只能用於單服務),推薦大家使用redis自帶的線程安全屬性來實現(可實現分布式鎖)。