Memcached筆記——(四)應對高併發攻擊

y_keven發表於2013-10-29

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

相關連結:
Memcached筆記——(一)安裝&常規錯誤&監控
Memcached筆記——(二)XMemcached&Spring整合
Memcached筆記——(三)Memcached使用總結

Memcached筆記——(四)應對高併發攻擊

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

  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. }
	/**
	 * 衝突延時 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程式碼 複製程式碼 收藏程式碼
  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;
  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. }
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;

	/**
	 * 快取時效 永久
	 */
	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;
	}

}

相關連結:
Memcached筆記——(一)安裝&常規錯誤&監控
Memcached筆記——(二)XMemcached&Spring整合
Memcached筆記——(三)Memcached使用總結

Memcached筆記——(四)應對高併發攻擊

相關文章