摘自:http://snowolf.iteye.com/blog/1677495, 感謝作者
近半個月過得很痛苦,主要是產品上線後,引來無數機器用戶惡意攻擊,不停的刷新產品各個服務入口,製造垃圾數據,消耗資源。他們的最好成績,1秒鐘可以併發6次,趕在Database入庫前,Cache進行Missing Loading前,強佔這其中十幾毫秒的時間,進行惡意攻擊。
爲了應對上述情況,做了如下調整:
- 更新數據時,先寫Cache,然後寫Database(雙寫),如果可以,寫操作交給隊列後續完成。
- 限制統一帳號,同一動作,同一秒鐘併發次數,超過1次不做做動作,返回操作失敗。
- 限制統一用戶,每日動作次數,超限返回操作失敗。
要完成上述操作,同事給我支招。用Memcached的add方法,就可以很快速的解決問題。不需要很繁瑣的開發,也不需要依賴數據庫記錄,完全內存操作。
以下實現一個判定衝突的方法:
- /**
- * 衝突延時 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實現:
- 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;
- }
- }