前言:
在实际的开发项目中,一个暴露于外部世界的接口经常会面临大量即时提交的重复请求。要想过滤掉重复请求对业务造成的危害,就需要实现幂等性!
让我们解释幂等性的概念:
任何多次执行的影响与一次执行的影响相同。按照这个意思,最后的意思就是对数据库的影响只能是一次性的,不能重复处理。
如何保证其幂等性,通常有以下几种手段:
1.数据库建立一个唯一的索引,这可以确保最终只有一条数据被插入到数据库中。2.令牌机制在每个接口请求之前获得一个令牌,然后在下一个请求中将这个令牌添加到请求的头体中,并在后台验证它。如果验证通过,删除令牌,然后在下一个请求中再次判断令牌3、悲观锁还是乐观锁。悲观锁可以保证其他sql每次更新都不能更新数据(当数据库引擎为innodb时,select的条件必须是唯一的索引,以防止锁定整个表)。4.先质疑再判断。首先,查询数据库中是否有数据。如果存在,则证明已经被请求,直接拒绝请求。如果不存在,证明是第一次进来直接放出来。
redis实现自动幂等的示意图;
Spring Boot+Redis坚持住,瞬间提交上千次(例子)
首先,构建redis的服务Api
1.首先,建立redis服务器。
2.在Spring封装的springboot或jedis中引入redis的stater是可以的。后来主要使用的api是它的set方法和exists方法。这里我们使用封装的回弹模板。
/** * redis工具类 */ @Componentpublic classRedisService{@ autowiredprivateredestemplate redis template;
/* * *写缓存* @ param key * @ param value * @ return */public boolean set(final string key,object value){ boolean result = false;
尝试{ ValueOperations & lt可序列化,对象>;operations = redistemplate . opsforvalue;运营。set(键、值);result = true} catch(Exception e){ e . printstacktrace;} returnresult}
/* * *写缓存集老化时间* @ param key * @ param value * @ return */public boolean setex(final string key,object value,long expire time){ boolean result = false;
尝试{ ValueOperations & lt可序列化,对象>;operations = redistemplate . opsforvalue;运营。set(键、值);redisTemplate.expire(key,expireTime,TimeUnit。秒);result = true} catch(Exception e){ e . printstacktrace;} returnresult}
/* * *判断缓存中是否存在对应的值* @ param key * @ return */public boolean exists(final string key){ returnredistemplate . haskey(key);}
/* * *
public Object get(FinaliString键){ Object result = nullValueOperations<。可序列化,对象>;operations = redistemplate . opsforvalue;结果=操作。get(key);returnresult}
/* * *删除相应的值* @ param key */public boolean remove(final string key){ if(exists(key)){ boolean delete = redis template . delete(key);returndelete} returnfalse}}
第二,自定义注释自动增强
自定义注释。定义这个注释的主要目的是将其添加到需要实现幂等性的方法中。任何方法注释它都会实现自动幂等。后台用反射。如果扫描这个标注,就会对这个方法进行处理,实现自动幂等。元注释元素类型。方法表示只能放在方法上,而扩展策略。RUNTIME表示它正在运行。
@ Target({ ElementType.METHOD}) @ Retention( RetentionPolicy. RUNTIME) public @interface AutoIdempotent {}
三.令牌创建和检查
1.令牌服务接口
我们创建了一个新的接口和一个令牌服务。主要有两种方法,一种用于创建令牌,另一种用于验证令牌。创建令牌主要产生一个字符串。检查令牌时,主要传递请求对象。为什么要传递请求对象?主要功能是获取头中的令牌,然后进行检查,通过抛出的Exception获取具体的错误信息,返回给前端。
publicinterfaceTokenService{ /*** 创建token * @return*/publicString createToken;/* * *验证令牌* @ param request * @ return */public boolenchecktoken(http servlet请求请求)引发异常;
}
2.令牌的服务实现类
Token指redis服务。创建令牌并使用random算法工具类生成random uuid字符串,然后放入redis(为了防止冗余数据保留,到期时间设置为10000秒,具体取决于业务)。如果令牌成功放入,令牌值将最终返回。checkToken的方法是从header获取Token到value(如果header中没有,从paramter获取),如果不存在,直接抛出异常。这种异常信息可以被拦截器捕获,然后返回到前端。
@ServicepublicclassTokenServiceImplimplementsTokenService{@ autowiredprivateredeservice redis service;
/* * *
@ OverridePublicString create token { String str = randomutil . randomuuid;StrBuilder token = newStrBuilder
尝试{ token.append(常量。Redis.TOKEN_PREFIX)。append(str);redis service . setex(token.toString,token . tostring,10000 l);booleannotEmpty = strutil . isnotempty(token . tostring);
if(notEmpty){ return token . ToString;} } catch(Exception ex){ ex . printstacktrace;} returnnull}
/* * *检查令牌* * @ param request * @ return */@ override public boolenchecktoken(http servlet请求请求)引发异常{
字符串标记= request.getHeader(常量。TOKEN _ NAME);token token = request . getparameter(constant . token _ name)在if(struil . is blank(token)){//header中不存在;if(strutil . is blank(token)){//参数中也没有token row newserviceexception(constant . response code . invalid _ argument,100);}}
if(!redis service . exists(token)){ throwNewServiceException(常量)。response code . REPLICATIONAL _ OPERATION,200);}
booleandremove = rediservice . remove(token);if(!移除){ thrownewServiceException(常量。response code . REPLICATIONAL _ OPERATION,200);}
returntrue}}
四.拦截器的配置
1.实现WebMvcConfigurerAdapter的web配置类,主要是用来给配置类添加autoIdempotentInterceptor,这样只有到了拦截器的时候才能生效。请注意使用@Configuration注释,以便在容器启动时可以将其添加到上下文中。
@ConfigurationpublicclassWebConfigurationextendsWebMvcConfigurerAdapter{@ ResourcePrivateAutoDemmendeinterceptor AutoDemmendeinterceptor;
/* * *添加拦截器* @ param registry */@ override public viddinterceptors(拦截器注册表){注册表。addinterceptor(自动拦截器);super.addInterceptors(注册表);}}
2.拦截处理器:主要功能是拦截并扫描AutoIdempotent to annotation to方法,然后调用TokenService的checkToken方法验证token是否正确,如果捕获到异常,则将异常信息渲染为json返回给前端
/*** 拦截器*/@ComponentpublicclassAutoIdempotentInterceptorimplementsHandlerInterceptor{@ AutowiredprivateTokenService令牌服务;
/* * *预处理* * @ param request * @ param response * @ param handler * @ return * @ throwsException */@ overrideepublibooreanphandle(http servlet request,HttpServletResponse response,Object handler)throwsException { if(!(处理程序实例方法(){ returntrue}
handler method handler method =(handler method)处理程序;method method = HandlerMethod . GetMethod;//扫描由ApiIdempotment auto幂等方法批注= method.getannotation (auto幂等. class)标记;
if(methodAnnotation!= null){ try { returntokenservice . check token(请求);//幂等验证,通过则释放,失败则抛出异常,通过统一异常处理返回友好提示} catch(exception ex ){
ResultVo failed result = ResultVo . getfailed result(101,ex . getmessage);writeReturnJson(response,jsonutil . tojsonstr(failedResult));throwex} }//必须返回true,否则所有请求都会被拦截
returntrue}
@ OverridePublicavityPostHandle(HttpServletrequest请求,HttpServletResponse响应,对象处理程序,模型和视图模型和视图)抛出异常{
}
@ override publicationaftercompletion(HttpServletrequest请求,HttpServletResponse响应,对象处理程序,异常ex)throwsException {
}
/* * *返回json值* @ param response * @ param JSON * @ throwsexception */private voitwriterreturnjson(http servlet response,string JSON)throwsexception { print writer = null;response . setcharacterencoding(" UTF-8 ");response . SetContentType(" text/html;charset = utf-8 ");
请尝试{ writer = response.getWriterwriter . print(JSON);} catch(IOException e) {
}最后{ if(writer!= null)writer . close;}}}
动词 (verb的缩写)测试案例
1.模拟业务请求类
首先我们需要通过/get/token路径通过getToken方法获取具体的令牌,然后我们调用testIdempotence方法,用@ AutoIdempotent注释。拦截器将拦截所有请求。当判断标注在处理方法上时,会调用TokenService中的checkToken方法。如果捕获到异常,该异常将被引发给调用方。让我们模拟下面的请求:
@RestControllerpublicclassBusinessController{ @ResourceprivateTokenService tokenService; @ResourceprivateTestService testService;@ post mapping("/get/token ")public StrIng GetToken { StrIng token = TokenServiCe . CreateToken;if(Strutil . is notempty(token)){ ResultVo ResultVo = new ResultVo;resultVo.setCode(常量. code _ success);结果设置消息(常量。SUCCESS);resultVo.setData(令牌);returnjsonutil . tojsonstr(resultVo);}
returnStrUtil。EMPTY}
@ AutoIdemPoint @ post mapping("/test/幂等")publicString TestidemPointS { String business result = TestServiCe . TestidemPointS;if(StratIl . is notempty(business result)){ ResultVo successResult = ResultVo . getsuccessresult(business result);returnjsonutil . tojsonstr(successResult);}
returnStrUtil。EMPTY}}
2.使用邮递员请求
首先,访问get/token路径以获取特定的令牌:
Spring Boot+Redis坚持住,瞬间提交上千次(例子)
通过使用获得的令牌并将其放入特定的请求头,我们可以看到第一个请求成功了,然后我们请求了第二次:
Spring Boot+Redis坚持住,瞬间提交上千次(例子)
对于第二个请求,是重复操作,说明重复验证已经通过。当我们多次要求时,只会让它第一次成功,第二次失败:
Spring Boot+Redis坚持住,瞬间提交上千次(例子)
不及物动词总结
本博客介绍了如何使用springboot、interceptor和redis优雅地实现接口的幂等性,这在实际开发过程中非常重要,因为一个接口可能会被无数的客户端调用。如何保证不影响后台的业务处理,如何保证只影响数据一次,非常重要。可以防止产生脏数据或者混乱数据,还可以降低并发性,这是一件非常有益的事情。但是传统的方法是每次判断数据,不够智能和自动化,比较麻烦。今天的自动化处理也可以提高程序的可扩展性。
来源:https://www.jianshu.com/p/9aef0c40b1b9
1.《淘宝购物车代码 淘宝购物车是如何使用Spring Boot+Redis 扛住十万qps重复提交的(含代码实例)》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《淘宝购物车代码 淘宝购物车是如何使用Spring Boot+Redis 扛住十万qps重复提交的(含代码实例)》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/fangchan/1612083.html