场景模拟:随着项目的业务场景复杂,业务越来越繁琐,所以就需要我们进行对项目的重构。项目要进行模块化,系统化。现在一般程序们的解决方案是使用分布式SOA,或者微服务架构去使项目结构更清晰,业务更加简单。我这里是用的微服务架构,springboot项目是通过http去进行相互的交互,比如转账操作:
系统A要给系统B进行转账,由于springboot机制是有重试机制。当系统A对系统B进行一笔转账操作时,假如系统B服务卡, 但是默认的系统A没有接受到相应,它会进行重试,继续发起转账请求。当系统B网络情况进行好转,那么系统B就会接受到多笔转账操作,这是就需要分布式的幂等。(当然这只是一个模拟场景。再比如Form表单的重复提交也是场景之一)
下面是我的代码:系统A--------转账到---------->系统B,下面只有核心代码。
目前写了一个方式一,当然还可以用redis去做
1.在系统A做了一个全局的拦截器(使用ThreadLoacl线程去保证一次请求,只能被一次消费,避免重复请求)
package com.socket.cn.configs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.socket.cn.util.IdempotentTool;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@Configuration
public class IdempotentConfiguration {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**为解决幂等问题,在发请求时在header中加入requestId
* @return
*/
@Bean
public RequestInterceptor headerInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
int reqTepmlateId = requestTemplate.hashCode();
String requestId = IdempotentTool.createRequestId(reqTepmlateId);
if(requestId==null){
requestId = IdempotentTool.createRequestId(reqTepmlateId);
}else{
logger.error("出现重复请求ID:"+requestId+","+requestTemplate.url());
}
requestTemplate.header("requestId", requestId);
}
};
}
}
创建一个工具类:保证生产requestId
package com.socket.cn.util;
import java.util.UUID;
public class IdempotentTool {
private static ThreadLocal<String> thread = new ThreadLocal<String>();
public static String createRequestId(int reqTepmlateId) {
String replace = UUID.randomUUID().toString().replace("-", "").toUpperCase();
String value = reqTepmlateId+"_"+replace;
thread.set(value);
return value;
}
/**
* 根据请求的id获取当前的线程uuid
*
* @param reqTepmlateId
* @return
*/
public static String getRequestId(int reqTepmlateId) {
String requestId = thread.get();
if (requestId==null) return null;
String[] split = requestId.split("_");
if (split.length!=2) {
clearRequestId();
return null;
}
if (!split[0].equals(reqTepmlateId+"")) {
clearRequestId();
return null;
}
return split[1];
}
public static void clearRequestId() {
thread.remove();
}
}
2.在系统B做一个过滤器。在请求的时候,就会做一个过滤。
package com.bitstar.assets.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(urlPatterns = "/*", filterName = "IdempotentFilter")
public class IdempotentFilter implements Filter{
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)throws IOException,ServletException {
HttpServletRequest req = (HttpServletRequest)arg0;
String requestId = req.getHeader("requestId");
if(requestId==null){
requestId = req.getParameter("requestId");
}
if(requestId!=null){
IdempotentTool.setRequestId(requestId);
}else{
IdempotentTool.clearRequestId();
}
try{
arg2.doFilter(arg0, arg1);
}finally{
if(requestId!=null){
IdempotentTool.clearRequestId();
}
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
线程的工具类,只是做保存。
package com.bitstar.assets.web.filter;
//用于保存Request的ThreadLocal
public class IdempotentTool {
private static ThreadLocal<String> threadlocal = new ThreadLocal<>();
public static void setRequestId(String value){
threadlocal.set(value);
}
public static String getRequestId(){
return threadlocal.get();
}
public static void clearRequestId(){
threadlocal.remove();
}
}
我使用的是springboot项目 所以大家要做好系统A,系统B的包扫描路径(只需要在启动类上面配置一下)
@ServletComponentScan(basePackages="。。。。。")