分布式幂等,第一篇

场景模拟:随着项目的业务场景复杂,业务越来越繁琐,所以就需要我们进行对项目的重构。项目要进行模块化,系统化。现在一般程序们的解决方案是使用分布式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="。。。。。")

猜你喜欢

转载自blog.csdn.net/qq_37228713/article/details/83514141