Memcached笔记——(四)应对高并发攻击
Posted onMemcached笔记——(四)应对高并发攻击
Snowolf的意境空间!
Memcached笔记——(四)应对高并发攻击 **
博客分类:
近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源。他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Missing Loading前,强占这其中十几毫秒的时间,进行恶意攻击。
相关链接: Memcached笔记——(一)安装&常规错误&监控 Memcached笔记——(二)XMemcached&Spring集成 Memcached笔记——(三)Memcached使用总结
为了应对上述情况,做了如下调整:
- 更新数据时,先写Cache,然后写Database,如果可以,写操作交给队列后续完成。
- 限制统一帐号,同一动作,同一秒钟并发次数,超过1次不做做动作,返回操作失败。
- 限制统一用户,每日动作次数,超限返回操作失败。
要完成上述操作,同事给我支招。用Memcached的add方法,就可以很快速的解决问题。不需要很繁琐的开发,也不需要依赖数据库记录,完全内存操作。
以下实现一个判定冲突的方法:
- ///
- /* 冲突延时 1秒
- /*/
- public static final int MUTEX_EXP = 1;
- ///
- /* 冲突键
- /*/
- public static final String MUTEXKEY_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;
- }
/// / 冲突延时 1秒 // public static final int MUTEXEXP = 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;
- ///
- /* 缓存时效 永久
- /*/
- public static final int CACHE_EXP_FOREVER = 0;
- ///
- /* 冲突延时 1秒
- /*/
- public static final int MUTEX_EXP = 1;
- ///
- /* 冲突键
- /*/
- public static final String MUTEXKEY_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 loadObject(String key) { - T object = null;
- try {
- object = memcachedClient.
get(key); - } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- logger.info("Load Object: [" + key + "]");
- return object;
- }
- }
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 CACHEEXP_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 // publicT loadObject(String key) { T object = null; try { object = memcachedClient. get(key); } catch (Exception e) { logger.error(e.getMessage(), e); } logger.info("Load Object: [" + key + "]"); return object; } }
相关链接: Memcached笔记——(一)安装&常规错误&监控 Memcached笔记——(二)XMemcached&Spring集成 Memcached笔记——(三)Memcached使用总结
2 踩
MySQL 运维笔记(一)—— 终止高负载SQL | 征服 Redis + Jedis + Spring (二)—— ...
评论
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,断电或者故障会导致用户数据丢失。
发表评论
snowolf
- 浏览: 902532 次
- 性别:
- 来自: 北京
最近访客 更多访客>>
文章分类
- 全部博客 (161)
- 职场 && 心情 (22)
- Java/Basic (17)
- Java/Compression (7)
- Java/Security (22)
- Java/Maven (3)
- Java/Cache (11)
- Eclipse (4)
- Spring (19)
- ORM/Hibernate (2)
- ORM/iBatis (3)
- DB/NoSQL (10)
- DB/MySQL (7)
- DB/MS SQL Server (4)
- OS/Linux (10)
- OS/Mac (7)
- C/C++ (4)
- Server Architecture/Basic (11)
- Server Architecture/Distributed (17)
- Moblie/Andriod (2)
- WebService (3)
- Objective-C (1)
- Html (1)
- 设计模式 (1)
- Scala (0)
-
社区版块
我的资讯 (0)
- 我的论坛 (66)
- 我的问答 (4)
存档分类
- 2013-05 (2)
- 2013-04 (1)
- 2013-03 (3)
-
评论排行榜
- MySQL 查询时强制区分大小写
- MySQL 运维笔记(一)—— 终止高负载SQL
- 征服 Redis + Jedis + Spring (一)—— ...
- 我的职场生涯(十)——又一个两年
最新评论
- dbh0512: 这样操作只是在缓存中读取吗?项目中是不是只有在海量频繁读取某一 ... 征服 Redis + Jedis + Spring (一)—— 配置&常规操作(GET SET DEL)
- 愤怒的程序猿: LZ:keytool错误:java.lang.Exceptio ... Java加密技术(九)——初探SSL
- snowolf: juwend 写道如果被加密的明文使用String ming ... Java加密技术(四)——非对称加密算法RSA
- simylau: water_lang 写道@Autowired pr ... Spring 注解学习手札(一) 构建简单Web应用
- ifox: phrmgb 写道学习了,这些东东有时感觉很偏的,但是特殊的业 ... Java关键字——transient 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。若作者同意转载,必须以超链接形式标明文章原始出处和作者。 © 2003-2012 ITeye.com. All rights reserved. [ 京ICP证110151号 京公网安备110105010620 ]