基於本地redis、protostuff序列化對於資料層的優化及java中對於泛型的使用
阿新 • • 發佈:2018-11-25
此次對於redis、protostuff的應用是在一個高併發的秒殺系統中實現的。
在高併發的秒殺系統的優化中主要有以下幾個方面:
1.對於獲取秒殺地址的介面的優化
每次獲取秒殺介面我們都要訪問資料庫,在高併發的系統中我們可以使用redis快取進行優化,不需要每次都訪問資料庫,從而減小資料庫的壓力。而同時使用Protostuff對於需要傳遞的資料進行序列化,這樣傳遞的資料量就會大大減小。從而減少併發時間。
2.對於秒殺操作的優化
秒殺操作是和資料庫事務有關的,在秒殺操作中我們主要執行insert和update兩條語句。
而對同一條資料進行update操作的時候,mysql存在行級鎖等待的情況,一條update語句必須等待前一天update執行完之後才可以拿到lock鎖。
所以這裡優化主要針對的是行級鎖事務的等待,網路的延遲和GC回收!
這次我們主要講的是對於秒殺地址的優化。
實體類Seckill,記錄了秒殺商品id,商品name,商品庫存,開始時間結束時間,建立時間。
public class Seckill
{
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Seckill{" +
"seckillId=" + seckillId +
", name='" + name + '\'' +
", number=" + number +
", startTime=" + startTime +
", endTime=" + endTime +
", createTime=" + createTime +
'}';
}
}
實體類Exposer,存放了是否秒殺,MD5加密,當前系統時間,秒殺開始時間、秒殺結束時間等資訊
/**
* Created by yliu on 18/03/27.
* 暴露秒殺地址(介面)DTO
*/
public class Exposer {
//是否開啟秒殺
private boolean exposed;
//加密措施
private String md5;
private long seckillId;
//系統當前時間(毫秒)
private long now;
//秒殺的開啟時間
private long start;
//秒殺的結束時間
private long end;
//建構函式。秒殺成功則返回true,已經經過md5加密後的地址,以及商品id,表示該商品已經開啟秒殺
public Exposer(boolean exposed, String md5, long seckillId) {
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}
//建構函式。若失敗則返回false,可以返回商品id和系統時間、開始時間、結束時間、判斷秒殺何時開啟
public Exposer(boolean exposed, long seckillId,long now, long start, long end) {
this.exposed = exposed;
this.seckillId=seckillId;
this.now = now;
this.start = start;
this.end = end;
}
//建構函式
public Exposer(boolean exposed, long seckillId) {
this.exposed = exposed;
this.seckillId = seckillId;
}
public boolean isExposed() {
return exposed;
}
public void setExposed(boolean exposed) {
this.exposed = exposed;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public long getNow() {
return now;
}
public void setNow(long now) {
this.now = now;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
@Override
public String toString() {
return "Exposer{" +
"exposed=" + exposed +
", md5='" + md5 + '\'' +
", seckillId=" + seckillId +
", now=" + now +
", start=" + start +
", end=" + end +
'}';
}
}
實體類:秒殺結果類,裡面存放了請求是否成功,成功則返回一個泛型T的資料,失敗則返回錯誤資訊:
/**
* Created by yliu on 18/03/28.
*/
//將所有的ajax請求返回型別,全部封裝成json資料
public class SeckillResult<T> {
//請求是否成功
private boolean success;
private T data;
private String error;
public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}
public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
RedisDao中定義了put、get redis中資料的方法:
/**
* Created by yliu on 18/3/27.
*/
public class RedisDao {
private final JedisPool jedisPool;//redis連線池
//redisDao的構造器,傳入redis的地址和埠,實現連線池的初始化
public RedisDao(String ip, int port) {
jedisPool = new JedisPool(ip, port);
}
//Protostuff序列化所要傳入的引數
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);
public Seckill getSeckill(long seckillId) {
//redis操作邏輯
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckillId;
//並沒有實現哪部序列化操作
//採用自定義序列化 protostuff: pojo.
//通過key得到存放在redis中經過序列化後的Seckill類,經過序列化後存放的是位元組型別的資料
byte[] bytes = jedis.get(key.getBytes());
//快取重獲取到
if (bytes != null) {
Seckill seckill=schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes,seckill,schema);
//反序列化得到Seckill 並返回
return seckill;
}
}finally {
//最後不要忘記關閉redis連線
jedis.close();
}
}catch (Exception e) {
}
return null;
}
public String putSeckill(Seckill seckill) {
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckill.getSeckillId();
//將seckill類序列化成位元組,大小為預設的大小
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
//超時快取
int timeout = 60 * 60;//1小時
//將序列化後的位元組存放在redis中,存放時間為1小時,返回string型別的result,根據返回的結果可判斷存放是否成功
String result = jedis.setex(key.getBytes(),timeout,bytes);
return result;
}finally {
jedis.close();
}
}catch (Exception e) {
}
return null;
}
}
暴露秒殺介面地址的方法,會返回一個Exposer的型別:
//加入一個混淆字串(秒殺介面)的salt,為了我避免使用者猜出我們的md5值,值任意給,越複雜越好
private final String salt="shsdssljdd'l.";
public Exposer exportSeckillUrl(long seckillId) {
//優化點:快取優化:超時的基礎上維護一致性
//1。首先訪問redis,如果redis中存放了秒殺介面的資料,則直接從redis中取出,如果沒有存放,則從資料庫中取出資料,並存放到redis中,方便下次獲取秒殺介面。
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
//2.訪問資料庫
seckill = seckillDao.queryById(seckillId);
if (seckill == null) {//說明查不到這個秒殺產品的記錄
//返回商品id 和false表示該商品未開啟秒殺
return new Exposer(false, seckillId);
}else {
//3,放入redis
redisDao.putSeckill(seckill);
}
}
//根據返回的seckill 類中存放的秒殺開啟時間判斷是否開啟秒殺
Date startTime=seckill.getStartTime();
Date endTime=seckill.getEndTime();
//系統當前時間
Date nowTime=new Date();
if (startTime.getTime()>nowTime.getTime() || endTime.getTime()<nowTime.getTime())
{
//系統當前時間比開始時間小,或者超過結束時間,說明沒有開啟秒殺,返回false及其他引數
return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
}
//秒殺開啟,返回秒殺商品的id、用給介面加密的md5
String md5=getMD5(seckillId);
return new Exposer(true,md5,seckillId);
}
//對秒殺商品的id進行加密,通過與salt鹽值的拼接,返回複雜的md5
private String getMD5(long seckillId)
{
String base=seckillId+"/"+salt;
//呼叫spring中自帶的md5工具
String md5= DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
控制器controller中
@RequestMapping(value = "/{seckillId}/exposer",
method = RequestMethod.GET,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId)
{
//返回型別是seckillResult並設定了泛型Exposer,則在實體類中seckillResult所定義的泛型變數data是Exposer型別的資料
SeckillResult<Exposer> result;
try{
//根據秒殺商品id返回商品exposer類變數,中存放了是否開啟秒殺,秒殺開啟時間,md5等資料
Exposer exposer=seckillService.exportSeckillUrl(seckillId);
//在seckillResult定義了建構函式
/* public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
} */
result=new SeckillResult<Exposer>(true,exposer);//請求成功 返回秒殺商品的資訊,即是否開啟秒殺等資訊
}catch (Exception e)
{
//請求錯誤,丟擲異常,並返回錯誤資訊
e.printStackTrace();
result=new SeckillResult<Exposer>(false,e.getMessage());
}
return result;
}
在不確定返回引數型別的時候,可以使用泛型,等返回引數確定之後,再給其繫結型別。