1.分析秒殺時大量使用者會在同一時間同時進行搶購,網站瞬時訪問流量激增。秒殺一般是訪問請求數量遠遠大於庫存數量,只有少部分使用者能夠秒殺成功。秒殺業務流程比較簡單,一般就是下訂單減庫存。
上述三點的主要問題就是在高併發的情況下保證資料的一致性。
2.使用的技術和架構2.1秒殺架構圖
2.2流程
使用 redis 快取秒殺的商品資訊,秒殺成功後使用訊息佇列傳送訂單資訊,然後將更新後資料重新寫入redis。RabbitMQ監聽器在接受到訊息後,將訂單資訊寫入資料庫。在秒殺時使用redisson對商品資訊上鎖2.3流程圖
3.準備工作3.1安裝redis cluster
csdn上教程一大堆,這裡我就不多贅述了。需要注意的點是,如果使用的是阿里雲伺服器(centos 7),在安裝完後一定要去阿里雲伺服器控制檯新增安全規則,去開放你使用的對應埠號。https://blog.csdn.net/CFrieman/article/details/83583085
3.2安裝RabbitMQ和erlang
還是直接附上鍊接。需要說明的一點是,在安裝erlang時,電腦名稱不可以是中文,erlang的版本和rabbitmq的版本一定要對應,負責會安裝失敗。https://blog.csdn.net/qq_36505948/article/details/82734133
4.具體實現4.1SeckillService
public class SeckillService { @Autowired private RedisClusterClient rt; @Autowired private SeckillMapper sm; @Autowired private RedissonClient redissonClient; // 加鎖 @Autowired private RabbitmqSendMessage rsm; @Autowired private SecorderMapper om; /** * 初始化 ,將mysql中的商品資訊快取到redis中 * @return */ public List<Seckill> querySeckill() { List<Seckill> list = (List<Seckill>) rt.get("secgoods"); if(list==null) { list = sm.selectByExample(null); rt.set("secgoods", list, 60*30); } return list; } public boolean queryStartTime(Seckill sec) { Date date = new Date();// 比較時間,是否到秒殺時間 Date startTime = sec.getStarttime(); // 秒殺活動還未開始 if (startTime.getTime() > date.getTime()) { return false; } return true; } // 減庫存redis public void decreaseStock(String id) { int goodsid = Integer.parseInt(id); List<Seckill> list = (List<Seckill>) rt.get("secgoods"); if (list!=null) { for (Seckill sec : list) { if (goodsid==sec.getId()) { sec.setCount(sec.getCount()-1); //寫回redis rt.set("secgoods", list, 60*30); return ; } } } } // public Seckill findSec(String secid) { List<Seckill> list = (List<Seckill>) rt.get("secgoods"); int id = Integer.parseInt(secid); for(Seckill sec:list) { if(sec.getId()==id) { return sec; } } return null; } // 開始秒殺 public String goSeckill(String goodsid, String username) { String key = username + ":" + goodsid; String secid = goodsid; Long value = (Long) rt.get(key); if (value != null) { return "exist"; } Seckill sec = findSec(secid); boolean flag = queryStartTime(sec); if (!flag) { return "notTime"; } RLock rLock = redissonClient.getLock("miaosha"); rLock.lock(); if (sec.getCount() > 0) { decreaseStock(goodsid); // 減少庫存 rt.set(key, System.currentTimeMillis(), 60*30); Secorder newOrder = new Secorder(); newOrder.setCreatetime(new Date()); newOrder.setGoodsid(Integer.parseInt(goodsid)); newOrder.setStatus("未付款"); newOrder.setUsername(username); String json = JSONObject.toJSONString(newOrder); rsm.send(json); // 非同步下單 rLock.unlock(); // 解鎖 return "success"; } else { rLock.unlock(); return "failed"; } } // 寫入mysql public void saveOrder(String json) { Secorder order = JSON.parseObject(json, Secorder.class); int n = sm.updateCount(order.getGoodsid()); int m = om.insert(order); } }
4.2 RabbitmqListenner
@Servicepublic class RabbitmqListenner implements MessageListener { @Autowired private SeckillService ss; @Override public void onMessage(Message msg) { byte[] data = msg.getBody(); try { String json = new String(data,"utf-8"); System.out.println(json); ss.saveOrder(json); //將監聽到的訂單寫入MySQL } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
4.3 RabbitmqSendMessage
public class RabbitmqSendMessage { @Autowired private RabbitTemplate rt; private final String QUEEN_NAME = "MIAOSHA"; /** * 傳送訊息 * @param msg */ public void send(String msg) { rt.convertAndSend(QUEEN_NAME,msg); }}
4.4以上就是整個業務流程的核心程式碼,使用redisson保證資料一致性,用rabbitmq非同步下單將下單及寫資料庫這個長操作變成兩個短操作。GitHub原始碼地址,關於資料庫建表什麼的,大家直接去原始碼裡看吧。
5.最佳化限流:使用驗證碼,請求秒殺介面需要驗證圖形驗證碼的正確性,這樣也很好的防止指令碼的不斷訪問;防刷:一個使用者對一個路徑的訪問次數在一定時間內有限制,使用redis可以解決介面地址隱藏:介面地址傳參,保證秒殺介面不是一個固定路徑,防止介面被刷,同時也可以有效隱藏秒殺地址。
最新評論