(四)Java高併發秒殺API之高併發優化
下載Redis
下載完後解壓壓縮包
進入解壓後的資料夾裡面 ,執行命令
make
然後再執行
sudo make install
最後再啟動
REdis
,啟動命令為redis-server
執行命令'redis-cli -p 6379'檢視執行情況
使用Java
操作Redis
匯入操作
Redis
的jedis
的 jar包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
新增
protostuff-core
以及protostuff-runtime
序列化jar包
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.1.1</version>
</dependency>
在com.suny.dao
下建包cache
然後建立類
RedisDao
/**
* 操作Redis的dao類
* Created by 孫
*/
public class RedisDao {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final JedisPool jedisPool;
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
public RedisDao(String ip, int port) {
jedisPool = new JedisPool(ip, port);
}
public Seckill getSeckill(long seckillId) {
// redis操作業務邏輯
try (Jedis jedis = jedisPool.getResource()) {
String key = "seckill:" + seckillId;
// 並沒有實現內部序列化操作
//get->byte[]位元組陣列->反序列化>Object(Seckill)
// 採用自定義的方式序列化
// 快取獲取到
byte[] bytes = jedis.get(key.getBytes());
if (bytes != null) {
// 空物件
Seckill seckill = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
// seckill被反序列化
return seckill;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
public String putSeckill(Seckill seckill) {
// set Object(Seckill) -> 序列化 -> byte[]
try (Jedis jedis = jedisPool.getResource()) {
String key = "seckill:" + seckill.getSeckillId();
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
// 超時快取
int timeout=60*60;
return jedis.setex(key.getBytes(), timeout, bytes);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
}
下一步是在在
applicationContext-dao.xml
中注入redisDao
<!--注入redisDao-->
<bean id="redisDao" class="com.suny.dao.cache.RedisDao">
<!--構造方法注入值-->
<constructor-arg index="0" value="localhost"/>
<constructor-arg index="1" value="6379"/>
</bean>
改造
exportSeckillUrl
方法,一定要先注入redisDao
@Autowired
private RedisDao redisDao;
@Override
public Exposer exportSeckillUrl(long seckillId) {
// 根據秒殺的ID去查詢是否存在這個商品
/* Seckill seckill = seckillMapper.queryById(seckillId);
if (seckill == null) {
logger.warn("查詢不到這個秒殺產品的記錄");
return new Exposer(false, seckillId);
}*/
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
// 訪問資料庫讀取資料
seckill = seckillMapper.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
} else {
// 放入redis
redisDao.putSeckill(seckill);
}
}
// 判斷是否還沒到秒殺時間或者是過了秒殺時間
LocalDateTime startTime = seckill.getStartTime();
LocalDateTime endTime = seckill.getEndTime();
LocalDateTime nowTime = LocalDateTime.now();
// 開始時間大於現在的時候說明沒有開始秒殺活動 秒殺活動結束時間小於現在的時間說明秒殺已經結束了
if (nowTime.isAfter(startTime) && nowTime.isBefore(endTime)) {
//秒殺開啟,返回秒殺商品的id,用給介面加密的md5
String md5 = getMd5(seckillId);
return new Exposer(true, md5, seckillId);
}
return new Exposer(false, seckillId, nowTime, startTime, endTime);
}
寫儲存過程,然後去
Mysql
控制檯執行儲存過程
-- 秒殺執行儲存過程
DELIMITER $$ -- console ; 轉換為
$$
-- 定義儲存過程
-- 引數: in 引數 out輸出引數
-- row_count() 返回上一條修改型別sql(delete,insert,update)的影響行數
-- row_count:0:未修改資料 ; >0:表示修改的行數; <0:sql錯誤
CREATE PROCEDURE `seckill`.`execute_seckill`
(IN v_seckill_id BIGINT, IN v_phone BIGINT,
IN v_kill_time TIMESTAMP, OUT r_result INT)
BEGIN
DECLARE insert_count INT DEFAULT 0;
START TRANSACTION;
INSERT IGNORE INTO success_killed
(seckill_id, user_phone, create_time)
VALUES (v_seckill_id, v_phone, v_kill_time);
SELECT row_count()
INTO insert_count;
IF (insert_count = 0)
THEN
ROLLBACK;
SET r_result = -1;
ELSEIF (insert_count < 0)
THEN
ROLLBACK;
SET r_result = -2;
ELSE
UPDATE seckill
SET number = number - 1
WHERE seckill_id = v_seckill_id
AND end_time > v_kill_time
AND start_time < v_kill_time
AND number > 0;
SELECT row_count()
INTO insert_count;
IF (insert_count = 0)
THEN
ROLLBACK;
SET r_result = 0;
ELSEIF (insert_count < 0)
THEN
ROLLBACK;
SET r_result = -2;
ELSE
COMMIT;
SET r_result = 1;
END IF;
END IF;
END;
$$
-- 儲存過程定義結束
DELIMITER ;
SET @r_result = -3;
-- 執行儲存過程
CALL execute_seckill(1003, 13502178891, now(), @r_result);
-- 獲取結果
SELECT @r_result;
在
SeckillMapper
中編寫killProduce()
方法
/**
* 使用儲存過程執行秒殺
* @param paramMap
*/
void killByProcedure(Map<String,Object> paramMap);
然後在
SeckillMapper.xml
中寫sql
語句
<!--呼叫儲存過程-->
<select id="killByProcedure" statementType="CALLABLE">
CALL execute_seckill(
#{seckillId,jdbcType=BIGINT,mode=IN},
#{phone,jdbcType=BIGINT,mode=IN},
#{killTime,jdbcType=TIMESTAMP,mode=IN},
#{result,jdbcType=INTEGER,mode=OUT}
)
</select>
下一步在
SeckillService
介面中中編寫killProduce()
方法
SeckillExecution executeSeckillProcedure(long seckillId,long userPhone,String md5);
匯入
commons-collections
工具類
<!--匯入apache工具類-->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
然後
SeckillServiceImpl
實現killProduce()
方法
@Override
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(getMd5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStatEnum.DATE_REWRITE);
}
LocalDateTime killTime = LocalDateTime.now();
Map<String, Object> map = new HashMap<>();
map.put("seckillId", seckillId);
map.put("phone", userPhone);
map.put("killTime", killTime);
map.put("result", null);
// 執行儲存過程,result被複制
try {
seckillMapper.killByProcedure(map);
// 獲取result
int result = MapUtils.getInteger(map, "result", -2);
if (result == 1) {
SuccessKilled successKilled = successKilledMapper.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
} else {
return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
}
}
改造執行秒殺
executeSeckill
方法,減少一道虛擬機器GC
程式,優化效能
@Transactional
@Override
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException {
if (md5 == null || !md5.equals(getMd5(seckillId))) {
logger.error("秒殺資料被篡改");
throw new SeckillException("seckill data rewrite");
}
// 執行秒殺業務邏輯
LocalDateTime nowTIme = LocalDateTime.now();
try {
// 記錄購買行為
int insertCount = successKilledMapper.insertSuccessKilled(seckillId, userPhone);
if (insertCount <= 0) {
// 重複秒殺
throw new RepeatKillException("seckill repeated");
} else {
// 減庫存 ,熱點商品的競爭
int reduceNumber = seckillMapper.reduceNumber(seckillId, nowTIme);
if (reduceNumber <= 0) {
logger.warn("沒有更新資料庫記錄,說明秒殺結束");
throw new SeckillCloseException("seckill is closed");
} else {
// 秒殺成功了,返回那條插入成功秒殺的資訊 進行commit
SuccessKilled successKilled = successKilledMapper.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
}
}
} catch (SeckillCloseException | RepeatKillException e1) {
throw e1;
}
}
編寫
SeckillServiceImpl
中的killProduce()
方法的測試方法
@Test
public void executeSeckillProcedureTest() {
long seckillId = 1001;
long phone = 1368011101;
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
if (exposer.isExposed()) {
String md5 = exposer.getMd5();
SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5);
System.out.println(execution.getStateInfo());
}
}
改造
SeckillController
中的execute
方法呼叫,把一開始呼叫普通方法的改成呼叫儲存過程的那個方法
@RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST)
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") long seckillId,
@PathVariable("md5") String md5,
@CookieValue(value = "userPhone", required = false) Long userPhone) {
// 如果使用者的手機號碼為空的說明沒有填寫手機號碼進行秒殺
if (userPhone == null) {
return new SeckillResult<>(false, "沒有註冊");
}
// 根據使用者的手機號碼,``秒殺商品的id跟md5進行秒殺商品,沒異常就是秒殺成功
try {
// 這裡換成儲存過程
// SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, userPhone, md5);
return new SeckillResult<>(true, execution);
} catch (RepeatKillException e1) {
// 重複秒殺
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<>(false, execution);
} catch (SeckillCloseException e2) {
// 秒殺關閉
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<>(false, execution);
} catch (SeckillException e) {
// 不能判斷的異常
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<>(false, execution);
}
// 如果有異常就是秒殺失敗
}
程式碼
https://github.com/Sunybyjava/seckill
(完)
相關文章
- (二)Java高併發秒殺API之Service層JavaAPI
- (三)Java高併發秒殺系統API之Web層開發JavaAPIWeb
- 前端優化之高併發處理前端優化
- Java高併發秒殺系統【觀後總結】Java
- SSM框架實現高併發秒殺API學習筆記SSM框架API筆記
- RocketMQ實戰--高併發秒殺場景MQ
- 高併發秒殺系統架構詳解,不是所有的秒殺都是秒殺!架構
- 高併發秒殺專案隨手筆記筆記
- 高併發優化方向優化
- 【高併發】Redis如何助力高併發秒殺系統,看完這篇我徹底懂了!!Redis
- SpringBoot實現Java高併發秒殺系統之Web層開發(三)Spring BootJavaWeb
- Java高併發實戰,鎖的優化Java優化
- 【高併發】秒殺系統架構解密,不是所有的秒殺都是秒殺(升級版)!!架構解密
- 如何設計一個高可用、高併發秒殺系統
- Redis 實現高併發下的搶購 / 秒殺功能Redis
- PHP高併發商品秒殺問題的解決方案PHP
- 淺談高併發-前端優化前端優化
- 【高併發】高併發環境下如何優化Tomcat效能?看完我懂了!優化Tomcat
- 並行化-你的高併發大殺器並行
- [php]如何做到高併發優化PHP優化
- Nginx+php-fpm高併發優化NginxPHP優化
- Java 高併發思路Java
- 高併發業務場景下的秒殺解決方案 (初探)
- 高併發下秒殺商品,必須知道的9個細節
- 使用Redis構建高併發高可靠的秒殺拍賣系統 - LuisRedisUI
- 非同步化,你的高併發大殺器非同步
- 6步帶你用Spring Boot開發出商城高併發秒殺系統Spring Boot
- 分享一個整合SSM框架的高併發和商品秒殺專案SSM框架
- 短影片直播系統,實現高併發秒殺的多種方式
- Redis+Lua解決高併發場景搶購秒殺問題Redis
- 【高併發】面試官:講講高併發場景下如何優化加鎖方式?面試優化
- 高併發&效能優化(一)------總體介紹優化
- [分散式][高併發]高併發架構分散式架構
- Java併發---併發理論Java
- Laravel 高併發調優筆記Laravel筆記
- 絕了!雙11千億流量「高併發秒殺架構設計」先睹為快架構
- PHP高併發 商品秒殺 問題的 2大種(MySQL or Redis) 解決方案PHPMySqlRedis
- 高併發IM系統架構優化實踐架構優化