内容简介:之前写过一篇幂等的文章,但是仅仅只是思路,在生产环境肯定还是不能够用的。恰巧同事最近在讨论如何做幂等。那么如何设计幂等能够达到生产可用呢?请看下面代码:那么大家看到如上接口设计,会发现当执行process方法时执行幂等且还需要 confim一次,同时可以调用cancel取消幂等。为什么,因为如果在方法执行之前幂等了后续方法执行失败则出大事了。所以需要confirm、cancel两个方法保证最终成功和失败(画外音:此类设计有点TCC事务有木有?)第一次请求幂等失败手动取消幂等,第二次幂等成功
之前写过一篇幂等的文章,但是仅仅只是思路,在生产环境肯定还是不能够用的。恰巧同事最近在讨论如何做幂等。那么如何设计幂等能够达到生产可用呢?请看下面代码:
幂等处理接口:
public interface IdempotentProcessor {
/**
* 处理幂等执行代码块
*
* @param optType the opt type
* @param optId the opt id
* @param initStatus the init status
* @param expireMs the expire ms
* @return the status enum
*/
StatusEnum process(String optType, String optId, StatusEnum initStatus, long expireMs);
/**
* 提交幂等
* @param optType the opt type
* @param optId the opt id
* @return the boolean
*/
boolean confirm(String optType, String optId);
/**
*
* 取消幂等
* @param optType the opt type
* @param optId the opt id
* @return the boolean
*/
boolean cancel(String optType, String optId);
}
复制代码
那么大家看到如上接口设计,会发现当执行process方法时执行幂等且还需要 confim一次,同时可以调用cancel取消幂等。为什么,因为如果在方法执行之前幂等了后续方法执行失败则出大事了。所以需要confirm、cancel两个方法保证最终成功和失败(画外音:此类设计有点TCC事务有木有?)
幂等实现类
public class ItemProcessor implements IdempotentProcessor {
private static final String CACHE_KEY = "dew:idempotent:item:";
@Override
public StatusEnum process(String optType, String optId, StatusEnum initStatus, long expireMs) {
if (Dew.cluster.cache.setnx(CACHE_KEY + optType + ":" + optId, initStatus.toString(), expireMs / 1000)) {
// 设置成功,表示之前不存在
return StatusEnum.NOT_EXIST;
} else {
// 设置不成功,表示之前存在,返回存在的值
String status = Dew.cluster.cache.get(CACHE_KEY + optType + ":" + optId);
if (status == null || status.isEmpty()) {
// 设置成功,表示之前不存在
return StatusEnum.NOT_EXIST;
} else {
return StatusEnum.valueOf(status);
}
}
}
@Override
public boolean confirm(String optType, String optId) {
long ttl = Dew.cluster.cache.ttl(CACHE_KEY + optType + ":" + optId);
if (ttl > 0) {
Dew.cluster.cache.setex(CACHE_KEY + optType + ":" + optId, StatusEnum.CONFIRMED.toString(), ttl);
}
return true;
}
@Override
public boolean cancel(String optType, String optId) {
Dew.cluster.cache.del(CACHE_KEY + optType + ":" + optId);
return true;
}
}
复制代码
幂等拦截器
public class IdempotentHandlerInterceptor extends HandlerInterceptorAdapter {
private DewIdempotentConfig dewIdempotentConfig;
/**
* Instantiates a new Idempotent handler interceptor.
*
* @param dewIdempotentConfig the dew idempotent config
*/
public IdempotentHandlerInterceptor(DewIdempotentConfig dewIdempotentConfig) {
this.dewIdempotentConfig = dewIdempotentConfig;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Idempotent idempotent = ((HandlerMethod) handler).getMethod().getAnnotation(Idempotent.class);
if (idempotent == null) {
return super.preHandle(request, response, handler);
}
// 参数设置
String optType = "[" + request.getMethod() + "]" + Dew.Info.name + "/" + request.getRequestURI();
String optIdFlag = StringUtils.isEmpty(idempotent.optIdFlag()) ? dewIdempotentConfig.getDefaultOptIdFlag() : idempotent.optIdFlag();
String optId = request.getHeader(optIdFlag);
if (StringUtils.isEmpty(optId)) {
optId = request.getParameter(optIdFlag);
}
if (StringUtils.isEmpty(optId)) {
// optId不存在,表示忽略幂等检查,强制执行
return super.preHandle(request, response, handler);
}
if (!DewIdempotent.existOptTypeInfo(optType)) {
long expireMs = idempotent.expireMs() == -1 ? dewIdempotentConfig.getDefaultExpireMs() : idempotent.expireMs();
boolean needConfirm = idempotent.needConfirm();
StrategyEnum strategy = idempotent.strategy() == StrategyEnum.AUTO ? dewIdempotentConfig.getDefaultStrategy() : idempotent.strategy();
DewIdempotent.initOptTypeInfo(optType, needConfirm, expireMs, strategy);
}
switch (DewIdempotent.process(optType, optId)) {
case NOT_EXIST:
return super.preHandle(request, response, handler);
case UN_CONFIRM:
ErrorController.error(request, response, 409,
"The last operation was still going on, please wait.", IdempotentException.class.getName());
return false;
case CONFIRMED:
ErrorController.error(request, response, 423,
"Resources have been processed, can't repeat the request.", IdempotentException.class.getName());
return false;
default:
return false;
}
}
}
复制代码
测试用例
测试场景一
第一次请求幂等失败手动取消幂等,第二次幂等成功
代码:
@GetMapping(value = "manual-confirm")
@Idempotent(expireMs = 5000)
public Resp<String> testManualConfirm(@RequestParam("str") String str) {
try {
if ("dew-test1".equals(str)){
throw new RuntimeException("处理幂等失败");
}
DewIdempotent.confirm();
} catch (Exception e) {
DewIdempotent.cancel();
return Resp.serverError(str + "处理幂等失败");
}
return Resp.success(str);
}
复制代码
@Test
public void testConfirm() throws IOException, InterruptedException {
//幂等唯一键值
//
HashMap<String, String> header = new HashMap<String, String>() {
{
put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001");
}
};
//字符串等于dew-test1幂等失败
Resp<String> error = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test1", header), String.class);
System.out.println("幂等失败 [errorCode=" + error.getCode() + "-errorMsg=" + error.getMessage() + "]");
//字符串等于dew-test2幂等成功
Resp<String> success = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test2", header), String.class);
System.out.println("幂等成功 [code=" + success.getCode() + "-body=" + success.getBody() + "]");
}
复制代码
结果:
幂等失败 [errorCode=500-errorMsg=dew-test1处理幂等失败] 幂等成功 [code=200-body=dew-test2] 复制代码
测试场景二
第一次请求幂等成功手动提交confim,第二次幂等失败
代码:
@GetMapping(value = "manual-confirm")
@Idempotent(expireMs = 5000)
public Resp<String> testManualConfirm(@RequestParam("str") String str) {
try {
DewIdempotent.confirm();
} catch (Exception e) {
DewIdempotent.cancel();
return Resp.serverError(str + "处理幂等失败");
}
return Resp.success(str);
}
复制代码
@Test
public void testConfirm() throws IOException, InterruptedException {
//幂等唯一键值
//
HashMap<String, String> header = new HashMap<String, String>() {
{
put(DewIdempotentConfig.DEFAULT_OPT_ID_FLAG, "0001");
}
};
//字符串等于dew-test1幂等成功
Resp<String> success = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test1", header), String.class);
System.out.println("幂等成功 [code=" + success.getCode() + "-body=" + success.getBody() + "]");
Thread.sleep(500);
//字符串等于dew-test2幂等失败
Resp<String> error = Resp.generic($.http.get(urlPre + "manual-confirm?str=dew-test2", header), String.class);
System.out.println("幂等失败 [errorCode=" + error.getCode() + "-errorMsg=" + error.getMessage() + "]");
}
复制代码
结果:
幂等成功 [code=200-body=dew-test1] 2019-04-23 00:14:17.123 ERROR 8600 --- [nio-8080-exec-2] ms.dew.core.web.error.ErrorController : Request [GET-/idempotent/manual-confirm] 169.254.156.20 , error 423 : Resources have been processed, can't repeat the request. 幂等失败 [errorCode=423-errorMsg=[]Resources have been processed, can't repeat the request.] 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Hadoop小文件解决方案-基于文件整合的解决方案
- Hadoop小文件解决方案-基于NameNode内存和MapReduce性能解决方案
- 跨域解决方案
- 异步解决方案---promise
- 前端一键打印解决方案
- MySQL 压缩解决方案(一)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mastering Regular Expressions, Second Edition
Jeffrey E F Friedl / O'Reilly Media / 2002-07-15 / USD 39.95
Regular expressions are an extremely powerful tool for manipulating text and data. They have spread like wildfire in recent years, now offered as standard features in Perl, Java, VB.NET and C# (and an......一起来看看 《Mastering Regular Expressions, Second Edition》 这本书的介绍吧!