1. 程式人生 > >Memcached應對高併發攻擊

Memcached應對高併發攻擊

產品上線後,引來無數機器使用者惡意攻擊,不停的重新整理產品各個服務入口,製造垃圾資料,消耗資源。他們的最好成績,1秒鐘可以併發6次,趕在Database入庫前,Cache進行Missing Loading前,強佔這其中十幾毫秒的時間,進行惡意攻擊。

為了應對上述情況,做了如下調整:

  1. 更新資料時,先寫Cache,然後寫Database(雙寫),如果可以,寫操作交給佇列後續完成。
  2. 限制統一帳號,同一動作,同一秒鐘併發次數,超過1次不做做動作,返回操作失敗。
  3. 限制統一使用者,每日動作次數,超限返回操作失敗。

用Memcached的add方法,就可以很快速的解決問題。不需要很繁瑣的開發,也不需要依賴資料庫記錄,完全記憶體操作。

以下實現一個判定衝突的方法:

Java程式碼  收藏程式碼
  1. /** 
  2.  * 衝突延時 1秒 
  3.  */  
  4. public static final int MUTEX_EXP = 1;  
  5. /** 
  6.  * 衝突鍵 
  7.  */  
  8. public static final String MUTEX_KEY_PREFIX = "MUTEX_";  
  9. /** 
  10.  * 衝突判定 
  11.  *  
  12.  * @param key 
  13.  */  
  14. public boolean isMutex(String key) {  
  15.     return isMutex(key, MUTEX_EXP);  
  16. }  
  17. /** 
  18.  * 衝突判定 
  19.  *  
  20.  * @param key 
  21.  * @param exp 
  22.  * @return true 衝突 
  23.  */  
  24. public boolean isMutex(String key, int exp) {  
  25.     boolean status = true;  
  26.     try {  
  27.         if (memcachedClient.add(MUTEX_KEY_PREFIX + key, exp, "true")) {  
  28.             status = false;  
  29.         }  
  30.     } catch (Exception e) {  
  31.         logger.error(e.getMessage(), e);  
  32.     }  
  33.     return status;  
  34. }  

做個說明:

選項 說明
add 僅當儲存空間中不存在鍵相同的資料時才儲存
replace 僅當儲存空間中存在鍵相同的資料時才儲存
set 與add和replace不同,無論何時都儲存

也就是說,如果add操作返回為true,則認為當前不衝突!

迴歸場景,惡意使用者1秒鐘操作6次,遇到上述這個方法,只有乖乖地1秒後再來。別小看這1秒鐘,一個數據庫操作不過幾毫秒。1秒延遲,足以降低系統負載,增加惡意使用者成本。

附我用到的基於XMemcached實現:

Java程式碼  收藏程式碼
  1. import net.rubyeye.xmemcached.MemcachedClient;  
  2. import org.apache.log4j.Logger;  
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.stereotype.Component;  
  5. /** 
  6.  *  
  7.  * @author Snowolf 
  8.  * @version 1.0 
  9.  * @since 1.0 
  10.  */  
  11. @Component  
  12. public class MemcachedManager {  
  13.     /** 
  14.      * 快取時效 1天 
  15.      */  
  16.     public static final int CACHE_EXP_DAY = 3600 * 24;  
  17.     /** 
  18.      * 快取時效 1周 
  19.      */  
  20.     public static final int CACHE_EXP_WEEK = 3600 * 24 * 7;  
  21.     /** 
  22.      * 快取時效 1月 
  23.      */  
  24.     public static final int CACHE_EXP_MONTH = 3600 * 24 * 30 * 7;  
  25.     /** 
  26.      * 快取時效 永久 
  27.      */  
  28.     public static final int CACHE_EXP_FOREVER = 0;  
  29.     /** 
  30.      * 衝突延時 1秒 
  31.      */  
  32.     public static final int MUTEX_EXP = 1;  
  33.     /** 
  34.      * 衝突鍵 
  35.      */  
  36.     public static final String MUTEX_KEY_PREFIX = "MUTEX_";  
  37.     /** 
  38.      * Logger for this class 
  39.      */  
  40.     private static final Logger logger = Logger  
  41.             .getLogger(MemcachedManager.class);  
  42.     /** 
  43.      * Memcached Client 
  44.      */  
  45.     @Autowired  
  46.     private MemcachedClient memcachedClient;  
  47.     /** 
  48.      * 快取 
  49.      *  
  50.      * @param key 
  51.      * @param value 
  52.      * @param exp 
  53.      *            失效時間 
  54.      */  
  55.     public void cacheObject(String key, Object value, int exp) {  
  56.         try {  
  57.             memcachedClient.set(key, exp, value);  
  58.         } catch (Exception e) {  
  59.             logger.error(e.getMessage(), e);  
  60.         }  
  61.         logger.info("Cache Object: [" + key + "]");  
  62.     }  
  63.     /** 
  64.      * Shut down the Memcached Cilent. 
  65.      */  
  66.     public void finalize() {  
  67.         if (memcachedClient != null) {  
  68.             try {  
  69.                 if (!memcachedClient.isShutdown()) {  
  70.                     memcachedClient.shutdown();  
  71.                     logger.debug("Shutdown MemcachedManager...");  
  72.                 }  
  73.             } catch (Exception e) {  
  74.                 logger.error(e.getMessage(), e);  
  75.             }  
  76.         }  
  77.     }  
  78.     /** 
  79.      * 清理物件 
  80.      *  
  81.      * @param key 
  82.      */  
  83.     public void flushObject(String key) {  
  84.         try {  
  85.             memcachedClient.deleteWithNoReply(key);  
  86.         } catch (Exception e) {  
  87.             logger.error(e.getMessage(), e);  
  88.         }  
  89.         logger.info("Flush Object: [" + key + "]");  
  90.     }  
  91.     /** 
  92.      * 衝突判定 
  93.      *  
  94.      * @param key 
  95.      */  
  96.     public boolean isMutex(String key) {  
  97.         return isMutex(key, MUTEX_EXP);  
  98.     }  
  99.     /** 
  100.      * 衝突判定 
  101.      *  
  102.      * @param key 
  103.      * @param exp 
  104.      * @return true 衝突 
  105.      */  
  106.     public boolean isMutex(String key, int exp) {  
  107.         boolean status = true;  
  108.         try {  
  109.             if (memcachedClient.add(MUTEX_KEY_PREFIX + key, exp, "true")) {  
  110.                 status = false;  
  111.             }  
  112.         } catch (Exception e) {  
  113.             logger.error(e.getMessage(), e);  
  114.         }  
  115.         return status;  
  116.     }  
  117.     /** 
  118.      * 載入快取物件 
  119.      *  
  120.      * @param key 
  121.      * @return 
  122.      */  
  123.     public <T> T loadObject(String key) {  
  124.         T object = null;  
  125.         try {  
  126.             object = memcachedClient.<T> get(key);  
  127.         } catch (Exception e) {  
  128.             logger.error(e.getMessage(), e);  
  129.         }  
  130.         logger.info("Load Object: [" + key + "]");  
  131.         return object;  
  132.     }  
  133. }  

PS:Redis的SETNX(即SET if Not eXists,類似於memcache的add)