Memcached笔记——(四)应对高并发攻击

http://snowolf.iteye.com/blog/1677495

近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源。他们的最好成绩,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.  *  
  13.  * @param key 
  14.  */  
  15. public boolean isMutex(String key) {  
  16.     return isMutex(key, MUTEX_EXP);  
  17. }  
  18.   
  19. /** 
  20.  * 冲突判定 
  21.  *  
  22.  * @param key 
  23.  * @param exp 
  24.  * @return true 冲突 
  25.  */  
  26. public boolean isMutex(String key, int exp) {  
  27.     boolean status = true;  
  28.     try {  
  29.         if (memcachedClient.add(MUTEX_KEY_PREFIX + key, exp, "true")) {  
  30.             status = false;  
  31.         }  
  32.     } catch (Exception e) {  
  33.         logger.error(e.getMessage(), e);  
  34.     }  
  35.     return status;  
  36. }  

 

做个说明:

 

选项 说明
add 仅当存储空间中不存在键相同的数据时才保存
replace 仅当存储空间中存在键相同的数据时才保存
set 与add和replace不同,无论何时都保存

也就是说,如果add操作返回为true,则认为当前不冲突!

 

回归场景,恶意用户1秒钟操作6次,遇到上述这个方法,只有乖乖地1秒后再来。别小看这1秒钟,一个数据库操作不过几毫秒。1秒延迟,足以降低系统负载,增加恶意用户成本。

 

附我用到的基于XMemcached实现:

 

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

 

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

 

相关链接: 
Memcached笔记——(一)安装&常规错误&监控 
Memcached笔记——(二)XMemcached&Spring集成 
Memcached笔记——(三)Memcached使用总结 

Memcached笔记——(四)应对高并发攻击

4 
2 
分享到:  
评论
8 楼 风吟想飞 2014-04-16  
[size=medium]您好,我初次使用XMemcached ,现在有个项目的业务和您在“Memcached笔记——(四)应对高并发攻击”中的业务描述一样,我看了您的文章,但是因为基础薄落,没有很好的明白怎么进行冲突判定。想麻烦您讲解一下。谢谢。[/size]
7 楼 di1984HIT 2014-04-14  
说的非常好啊
6 楼 snowolf 2014-02-26  
richardor 写道
那个超时一个月的常量,少乘7了

好眼力
5 楼 richardor 2014-02-25  
那个超时一个月的常量,少乘7了
4 楼 snowolf 2012-12-11  
zym820910 写道
snowolf 写道
CurrentJ 写道
先写Cache,然后写Database,断电或者故障会导致用户数据丢失。

各有利弊,需要根据业务需求权衡。


写得非常好!应对高并发的时候,我们通常的思维是泄洪模式,通过一道又一道的防洪大堤将洪水分流,尤其是在应对数据要求不严厉的SNS这类产品,异步的保存数据值得提倡!

不过,更好的方式是:通过旁路式架构,解决代码层面的大部分压力。现在很多商城的商品展示和搜索都采用NOSQL技术来应对处理,异步增加或更新,并不显得那么重要了,更多的是通过产品和技术架构来调整,比如通过分析用户喜好,事先静态化搜索结果。

赞同,感谢分享! 最核心的优化,还是应当在产品层面多下工夫。找到用户-产品-技术,三方都能满足的平衡点。
3 楼 zym820910 2012-12-11  
snowolf 写道
CurrentJ 写道
先写Cache,然后写Database,断电或者故障会导致用户数据丢失。

各有利弊,需要根据业务需求权衡。


写得非常好!应对高并发的时候,我们通常的思维是泄洪模式,通过一道又一道的防洪大堤将洪水分流,尤其是在应对数据要求不严厉的SNS这类产品,异步的保存数据值得提倡! 

不过,更好的方式是:通过旁路式架构,解决代码层面的大部分压力。现在很多商城的商品展示和搜索都采用NOSQL技术来应对处理,异步增加或更新,并不显得那么重要了,更多的是通过产品和技术架构来调整,比如通过分析用户喜好,事先静态化搜索结果。
2 楼 snowolf 2012-11-07  
CurrentJ 写道
先写Cache,然后写Database,断电或者故障会导致用户数据丢失。

各有利弊,需要根据业务需求权衡。
1 楼 CurrentJ 2012-11-07  
先写Cache,然后写Database,断电或者故障会导致用户数据丢失。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。