對於一個互聯網平臺來講,高併發是常常會遇到的場景。最有表明性的好比秒殺和搶購。高併發會出現三個特色:html
一、高併發讀取前端
二、高併發寫入(一致性)java
三、出現超賣問題mysql
前端如何應對?nginx
一、緩存靜態數據,例如圖片,html頁面,js等redis
二、搭建負載均衡集羣,目前採用較多的爲nginxsql
三、進行ip限制,限制同一個ip單位時間內發起的請求數量。或者創建ip黑名單,避免惡意攻擊數據庫
四、考慮系統降級。好比當達到系統負載的時候返回一個靜態處理頁面後端
後端如何應對?緩存
一、採用mysql讀寫分離,可是當高併發的時候mysql性能會下降。 通常來講,MySQL的處理性能會隨着併發thread上升而上升,可是到了必定的併發度以後會出現明顯的拐點,以後一路降低,最終甚至會比單thread的性能還要差。好比加減庫存的操做,一般併發量不高的作法爲:update xxx set count=count-xx where curcount>xx;這樣能夠充分利用mysql的事務鎖來避免出現超賣的狀況。可是併發量上了後,會由於排他鎖等待而大大下降性能。
二、採用redis數據庫,前置到mysql。思路以下:
2.1系統啓動後,初始化sku信息到redis數據庫,記錄其可用量和鎖定量
2.2使用樂觀鎖,採用redis的watch機制。邏輯爲:
1.定義門票號變量,設置初始值爲0。watchkey
2.watch該變量,watch(watchkey);
3.使用redis事務加減庫存。首先獲取可用量和搶購量比較,若是curcount>buycount,那麼正常執行減庫存和加鎖定量操做:
Redis用法詳細說明
一、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) {
- pipelined.get(key);
- pipelined.del("testabcd");
- }
返回結果將是
- "test1"
- 1
- "test2"
- 0
- "test2"
- 0
- "test4"
- 0
- "test5"
- 0
二、事務
事務是保證事務內的全部命令是原子操做,通常配合watch使用,事務的執行結果和pipeline同樣都是採用異步的方式獲取結果,multi.exec()提交事務,若是執行成功,其返回的結果和pipeline同樣是全部命令的返回值,若是事務裏面有兩個命令那麼事務的exec返回值會把兩個命令的返回值組合在一塊兒返回。若是事務被取消返回null。
三、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//事務取消
四、事務與管道
當對某個key進行watch時,若是其餘的客戶端對key進行了更改事務能夠作到取消事務操做可是管道不能夠