近半个月过得很痛苦,主要是产品上线后,引来无数机器用户恶意攻击,不停的刷新产品各个服务入口,制造垃圾数据,消耗资源。他们的最好成绩,1秒钟可以并发6次,赶在Database入库前,Cache进行Missing Loading前,强占这其中十几毫秒的时间,进行恶意攻击。
为了应对上述情况,做了如下调整:
- 更新数据时,先写Cache,然后写Database,如果可以,写操作交给队列后续完成。
- 限制统一帐号,同一动作,同一秒钟并发次数,超过1次不做做动作,返回操作失败。
- 限制统一用户,每日动作次数,超限返回操作失败。
要完成上述操作,同事给我支招。用Memcached的add方法,就可以很快速的解决问题。不需要很繁琐的开发,也不需要依赖数据库记录,完全内存操作。
以下实现一个判定冲突的方法:
- /**
- *冲突延时1秒
- */
- publicstaticfinalintMUTEX_EXP=1;
- /**
- *冲突键
- */
- publicstaticfinalStringMUTEX_KEY_PREFIX="MUTEX_";
- /**
- *冲突判定
- *
- *@paramkey
- */
- publicbooleanisMutex(Stringkey){
- returnisMutex(key,MUTEX_EXP);
- }
- /**
- *冲突判定
- *
- *@paramkey
- *@paramexp
- *@returntrue冲突
- */
- publicbooleanisMutex(Stringkey,intexp){
- booleanstatus=true;
- try{
- if(memcachedClient.add(MUTEX_KEY_PREFIX+key,exp,"true")){
- status=false;
- }
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- returnstatus;
- }
做个说明:
选项 | 说明 |
add | 仅当存储空间中不存在键相同的数据时才保存 |
replace | 仅当存储空间中存在键相同的数据时才保存 |
set | 与add和replace不同,无论何时都保存 |
也就是说,如果add操作返回为true,则认为当前不冲突!
回归场景,恶意用户1秒钟操作6次,遇到上述这个方法,只有乖乖地1秒后再来。别小看这1秒钟,一个数据库操作不过几毫秒。1秒延迟,足以降低系统负载,增加恶意用户成本。
附我用到的基于XMemcached实现:
- importnet.rubyeye.xmemcached.MemcachedClient;
- importorg.apache.log4j.Logger;
- importorg.springframework.beans.factory.annotation.Autowired;
- importorg.springframework.stereotype.Component;
- /**
- *
- *@authorSnowolf
- *@version1.0
- *@since1.0
- */
- @Component
- publicclassMemcachedManager{
- /**
- *缓存时效1天
- */
- publicstaticfinalintCACHE_EXP_DAY=3600*24;
- /**
- *缓存时效1周
- */
- publicstaticfinalintCACHE_EXP_WEEK=3600*24*7;
- /**
- *缓存时效1月
- */
- publicstaticfinalintCACHE_EXP_MONTH=3600*24*30;
- /**
- *缓存时效永久
- */
- publicstaticfinalintCACHE_EXP_FOREVER=0;
- /**
- *冲突延时1秒
- */
- publicstaticfinalintMUTEX_EXP=1;
- /**
- *冲突键
- */
- publicstaticfinalStringMUTEX_KEY_PREFIX="MUTEX_";
- /**
- *Loggerforthisclass
- */
- privatestaticfinalLoggerlogger=Logger
- .getLogger(MemcachedManager.class);
- /**
- *MemcachedClient
- */
- @Autowired
- privateMemcachedClientmemcachedClient;
- /**
- *缓存
- *
- *@paramkey
- *@paramvalue
- *@paramexp
- *失效时间
- */
- publicvoidcacheObject(Stringkey,Objectvalue,intexp){
- try{
- memcachedClient.set(key,exp,value);
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- logger.info("CacheObject:["+key+"]");
- }
- /**
- *ShutdowntheMemcachedCilent.
- */
- publicvoidfinalize(){
- if(memcachedClient!=null){
- try{
- if(!memcachedClient.isShutdown()){
- memcachedClient.shutdown();
- logger.debug("ShutdownMemcachedManager...");
- }
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- }
- }
- /**
- *清理对象
- *
- *@paramkey
- */
- publicvoidflushObject(Stringkey){
- try{
- memcachedClient.deleteWithNoReply(key);
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- logger.info("FlushObject:["+key+"]");
- }
- /**
- *冲突判定
- *
- *@paramkey
- */
- publicbooleanisMutex(Stringkey){
- returnisMutex(key,MUTEX_EXP);
- }
- /**
- *冲突判定
- *
- *@paramkey
- *@paramexp
- *@returntrue冲突
- */
- publicbooleanisMutex(Stringkey,intexp){
- booleanstatus=true;
- try{
- if(memcachedClient.add(MUTEX_KEY_PREFIX+key,exp,"true")){
- status=false;
- }
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- returnstatus;
- }
- /**
- *加载缓存对象
- *
- *@paramkey
- *@return
- */
- public<T>TloadObject(Stringkey){
- Tobject=null;
- try{
- object=memcachedClient.<T>get(key);
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- logger.info("LoadObject:["+key+"]");
- returnobject;
- }
- }