對於一個互聯網平台來說,高並發是經常會遇到的場景。最有代表性的比如秒殺和搶購。高並發會出現三個特點:
1、高並發讀取
2、高並發寫入(一致性)
3、出現超賣問題
前端如何應對?
1、緩存靜態數據,例如圖片,html頁面,js等
2、搭建負載均衡集群,目前采用較多的為nginx
3、進行ip限制,限制同一個ip單位時間內發起的請求數量。或者建立ip黑名單,避免惡意攻擊
4、考慮系統降級。比如當達到系統負載的時候返回一個靜態處理頁面
后端如何應對?
1、采用mysql讀寫分離,但是當高並發的時候mysql性能會降低。 一般來說,MySQL的處理性能會隨着並發thread上升而上升,但是到了一定的並發度之后會出現明顯的拐點,之后一路下降,最終甚至會比單thread的性能還要差。比如加減庫存的操作,通常並發量不高的做法為:update xxx set count=count-xx where curcount>xx;這樣可以充分利用mysql的事務鎖來避免出現超賣的情況。但是並發量上了后,會因為排他鎖等待而大大降低性能。
2、采用redis數據庫,前置到mysql。思路如下:
2.1系統啟動后,初始化sku信息到redis數據庫,記錄其可用量和鎖定量
2.2使用樂觀鎖,采用redis的watch機制。邏輯為:
1.定義門票號變量,設置初始值為0。watchkey
2.watch該變量,watch(watchkey);
3.使用redis事務加減庫存。首先獲取可用量和搶購量比較,如果curcount>buycount,那么正常執行減庫存和加鎖定量操作:
Redis用法詳細說明
1、Pipeline
利用pipeline的方式從client打包多條命令一起發出,不需要等待單條命令的響應返回,而Redis服務端會處理完多條命令后會將多條命令的處理結果打包到一起返回給客戶端。所以pipeline適合批處理作業可以提升效率如:
- public static void testMget() {
- Jedis jedis = RedisCacheClient.getInstrance().getClient();
- Set<String> keys = jedis.keys("cvfeedBackHandl_*");
- List<String> result = Lists.newArrayList();
- long t1 = System.currentTimeMillis();
- for (String key : keys) {
- result.add(jedis.get(key));
- }
- for (String src : result) {
- System.out.println(src);
- }
- System.out.println(System.currentTimeMillis() - t1);
- }
- public static void testPipline() {
- Jedis jedis = RedisCacheClient.getInstrance().getClient();
- Set<String> keys = jedis.keys("cvfeedBackHandl_*");
- List<Object> result = Lists.newArrayList();
- Pipeline pipelined = jedis.pipelined();
- long t1 = System.currentTimeMillis();
- for (String key : keys) {
- pipelined.<span style="font-family: Arial;">get</span>("testabcd");
- }
- result = pipelined.syncAndReturnAll();
- for (Object src : result) {
- System.out.println(src);
- }
- System.out.println(System.currentTimeMillis() - t1);
- }
如第一個方法執行的時間是82ms
第二個方法執行的時間是9ms
注意:pipeline和事務都是異步調用返回結果的,即並不是等待每條命令執行完立馬返回結果而是等待所有命令執行完之后再返回結果。pipelined.syncAndReturnAll()返回的是參與打包執行的每條命令的結果。如果上面改成:
- for (String key : keys) {//keys長度為5
- pipelined.get(key);
- pipelined.del("testabcd");
- }
返回結果將是
- "test1"
- 1
- "test2"
- 0
- "test2"
- 0
- "test4"
- 0
- "test5"
- 0
2、事務
事務是保證事務內的所有命令是原子操作,一般配合watch使用,事務的執行結果和pipeline一樣都是采用異步的方式獲取結果,multi.exec()提交事務,如果執行成功,其返回的結果和pipeline一樣是所有命令的返回值,如果事務里面有兩個命令那么事務的exec返回值會把兩個命令的返回值組合在一起返回。如果事務被取消返回null。
3、watch
一般是和事務一起使用,當對某個key進行watch后如果其他的客戶端對這個key進行了更改,那么本次事務會被取消,事務的exec會返回null。jedis.watch(key)都會返回OK
eg:
- public static void testWach(){
- Jedis jedis = RedisCacheClient.getInstrance().getClient();
- String watch = jedis.watch("testabcd");
- System.out.println(Thread.currentThread().getName()+"--"+watch);
- Transaction multi = jedis.multi();
- multi.set("testabcd", "23432");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- List<Object> exec = multi.exec();
- System.out.println("---"+exec);
- jedis.unwatch();
- }
- public static void testWatch2(){
- Jedis jedis = RedisCacheClient.getInstrance().getClient();
- String watch = jedis.watch("testabcd2");
- System.out.println(Thread.currentThread().getName()+"--"+watch);
- Transaction multi = jedis.multi();
- multi.set("testabcd", "125");
- List<Object> exec = multi.exec();
- System.out.println("--->>"+exec);
- }
Thread-2--OK
Thread-0--OK
--->>[OK]
---null//事務取消
4、事務與管道
當對某個key進行watch時,如果其他的客戶端對key進行了更改事務可以做到取消事務操作但是管道不可以