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

2023-03-18,,

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

近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源。他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Missing Loading前,强占这其中十几毫秒的时间,进行恶意攻击。

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

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

为了应对上述情况,做了如下调整:

    更新数据时,先写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;
    }
    }

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

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

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

4 

2 

分享到:  

MySQL 运维笔记(一)—— 终止高负载SQL | 征服 Redis + Jedis + Spring (二)—— ...

2012-09-13 09:48
浏览 10171
评论(8)
分类:企业架构
相关推荐

评论

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,断电或者故障会导致用户数据丢失。

Memcached笔记——(四)应对高并发攻击【转】的相关教程结束。

《Memcached笔记——(四)应对高并发攻击【转】.doc》

下载本文的Word格式文档,以方便收藏与打印。