Memcached應對高併發攻擊
阿新 • • 發佈:2019-01-29
產品上線後,引來無數機器使用者惡意攻擊,不停的重新整理產品各個服務入口,製造垃圾資料,消耗資源。他們的最好成績,1秒鐘可以併發6次,趕在Database入庫前,Cache進行Missing Loading前,強佔這其中十幾毫秒的時間,進行惡意攻擊。
為了應對上述情況,做了如下調整:
- 更新資料時,先寫Cache,然後寫Database(雙寫),如果可以,寫操作交給佇列後續完成。
- 限制統一帳號,同一動作,同一秒鐘併發次數,超過1次不做做動作,返回操作失敗。
- 限制統一使用者,每日動作次數,超限返回操作失敗。
用Memcached的add方法,就可以很快速的解決問題。不需要很繁瑣的開發,也不需要依賴資料庫記錄,完全記憶體操作。
以下實現一個判定衝突的方法:
Java程式碼- /**
- * 衝突延時 1秒
- */
- public static final int MUTEX_EXP = 1;
- /**
- * 衝突鍵
- */
- public static final String MUTEX_KEY_PREFIX = "MUTEX_";
- /**
- * 衝突判定
- *
- * @param key
- */
- public boolean isMutex(String key) {
- return isMutex(key, MUTEX_EXP);
- }
- /**
-
* 衝突判定
- *
- * @param key
- * @param exp
- * @return true 衝突
- */
- public boolean isMutex(String key, int exp) {
- boolean status = true;
- try {
- if (memcachedClient.add(MUTEX_KEY_PREFIX + key, exp, "true")) {
- status = false;
- }
- } catch (Exception e) {
-
logger.error(e.getMessage(), e);
- }
- return status;
- }
做個說明:
選項 | 說明 |
add | 僅當儲存空間中不存在鍵相同的資料時才儲存 |
replace | 僅當儲存空間中存在鍵相同的資料時才儲存 |
set | 與add和replace不同,無論何時都儲存 |
也就是說,如果add操作返回為true,則認為當前不衝突!
迴歸場景,惡意使用者1秒鐘操作6次,遇到上述這個方法,只有乖乖地1秒後再來。別小看這1秒鐘,一個數據庫操作不過幾毫秒。1秒延遲,足以降低系統負載,增加惡意使用者成本。
附我用到的基於XMemcached實現:
Java程式碼- import net.rubyeye.xmemcached.MemcachedClient;
- import org.apache.log4j.Logger;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- /**
- *
- * @author Snowolf
- * @version 1.0
- * @since 1.0
- */
- @Component
- public class MemcachedManager {
- /**
- * 快取時效 1天
- */
- public static final int CACHE_EXP_DAY = 3600 * 24;
- /**
- * 快取時效 1周
- */
- public static final int CACHE_EXP_WEEK = 3600 * 24 * 7;
- /**
- * 快取時效 1月
- */
- public static final int CACHE_EXP_MONTH = 3600 * 24 * 30 * 7;
- /**
- * 快取時效 永久
- */
- public static final int CACHE_EXP_FOREVER = 0;
- /**
- * 衝突延時 1秒
- */
- public static final int MUTEX_EXP = 1;
- /**
- * 衝突鍵
- */
- public static final String MUTEX_KEY_PREFIX = "MUTEX_";
- /**
- * Logger for this class
- */
- private static final Logger logger = Logger
- .getLogger(MemcachedManager.class);
- /**
- * Memcached Client
- */
- @Autowired
- private MemcachedClient memcachedClient;
- /**
- * 快取
- *
- * @param key
- * @param value
- * @param exp
- * 失效時間
- */
- public void cacheObject(String key, Object value, int exp) {
- try {
- memcachedClient.set(key, exp, value);
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- logger.info("Cache Object: [" + key + "]");
- }
- /**
- * Shut down the Memcached Cilent.
- */
- public void finalize() {
- if (memcachedClient != null) {
- try {
- if (!memcachedClient.isShutdown()) {
- memcachedClient.shutdown();
- logger.debug("Shutdown MemcachedManager...");
- }
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- }
- }
- /**
- * 清理物件
- *
- * @param key
- */
- public void flushObject(String key) {
- try {
- memcachedClient.deleteWithNoReply(key);
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- logger.info("Flush Object: [" + key + "]");
- }
- /**
- * 衝突判定
- *
- * @param key
- */
- public boolean isMutex(String key) {
- return isMutex(key, MUTEX_EXP);
- }
- /**
- * 衝突判定
- *
- * @param key
- * @param exp
- * @return true 衝突
- */
- public boolean isMutex(String key, int exp) {
- boolean status = true;
- try {
- if (memcachedClient.add(MUTEX_KEY_PREFIX + key, exp, "true")) {
- status = false;
- }
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- return status;
- }
- /**
- * 載入快取物件
- *
- * @param key
- * @return
- */
- public <T> T loadObject(String key) {
- T object = null;
- try {
- object = memcachedClient.<T> get(key);
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- logger.info("Load Object: [" + key + "]");
- return object;
- }
- }
PS:Redis的SETNX(即SET if Not eXists,類似於memcache的add)