RPC调用,session传递解决方案一:(Dubbo+Spring Redis Session)

背景

在分布式开发成为主流之后,现在我们经常采用spring redis session作为分布式共享session的解决防范,采用Dubbo+Zookeeper作为RPC调用的解决方案;但是在RPC调用链路中获取不到HttpSession了,这就导致在一些业务场景下,加大RPC代码编写的难度
在这里插入图片描述
例如:上图中服务1可以获取到session,但是服务2,和服务3获取不到session,如果业务中需要获取当前登录用户的信息,这时就存在问题了

解决方案

解决方案就是在RPC调用时,在消费端隐式传递session参数,在生产端调用之前根据session参数获取相应的用户信息并绑定到当前线程。
在这里插入图片描述

Session 信息统一获取

为了保证RPC方式和Http方式使用一套代码来获取登录用户信息,故业务代码中不应该直接通过request等对象来获取信息,需要单独封装,这里封装为两个类HttpContext和UserManager;其中HttpContext主要用来获取session信息,UserManager用来获取用户信息
HttpContext类

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class HttpContext {
	
	public static final String SESSION_KEY_CURRENT_USER_ID="SESSION_KEY_CURRENT_USER_ID";
	public static final String SESSION_KEY_CURRENT_USER_NAME="SESSION_KEY_CURRENT_USER_NAME";
	
	public static final String SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT="SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT";
	//用來存储RPC调用时的session相关信息
	public static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
	public static HttpServletRequest getRequest(){
		ServletRequestAttributes servletRequestAttributes=	(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if(servletRequestAttributes==null)
			return null;
		HttpServletRequest request =servletRequestAttributes.getRequest();
		return request;
	}
	
	public  static HttpSession getSession(){
		if( getRequest()==null) {
			return null;
		}
		HttpSession session = getRequest().getSession();
		return session;
		
	}
	/**
	 * 获取sessionId
	 * @return
	 */
	public static String getSessionId() {
		HttpSession session = getSession();
		if(null != session) {
			//从http session中获取
			return session.getId();
		} else {
			//从线程本地存储中获取
			//判断当前线程是否有数据
			Map<String, Object> data = HttpContext.threadLocal.get();
			return null != data ? (String)data.get("sessionId") : null;
		}
	}
}

UserManager类

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.uiot.util.SpringContextUtil;
import com.uiot.web.HttpContext;
//UserInfo类根据需要自行替换
public class UserManager extends HttpContext{

	public static final String SESSION_KEY_CURRENT_USER="SESSION_KEY_CURRENT_USER";
	public static final String SESSION_ID="SESSION_ID";
	
	public static Object getCurrentUser() {
		HttpSession session = getSession();
		Object userInfo = null;
		if(null != session) {
			userInfo = (UserInfo) session.getAttribute(SESSION_KEY_CURRENT_USER);
		} else {
			//判断当前线程是否有数据
			Map<String, Object> data = HttpContext.threadLocal.get();
			userInfo = null != data ? (UserInfo)data.get(SESSION_KEY_CURRENT_USER) : null;
		}
		return userInfo;
	}
	public static String getUserName(){
		UserInfo userInfo = getCurrentUser();
		if(null != userInfo) {
			return userInfo.getUserName();
		} else {
			return null;
		}
	}
	public static Long getUserID(){
		UserInfo userInfo = getCurrentUser();
		if(null != userInfo) {
			return userInfo.getUserID();
		} else {
			return null;
		}
	}
	public static String getLoginAccount(){
		String account = (String) getSessionAttribute(SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT);
		if (null == account) {
			//判断当前线程是否有数据
			Map<String, Object> data = HttpContext.threadLocal.get();
			account = null != data ? (String)data.get(SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT) : null;
		}
		return account;
	}
}

Dubbo RPC调用隐式传参

创建两个类ConsumerTraceFilter 和ProviderTraceFilter 分别用于RPC调用的消费端处理和生产端处理
消费端Filter

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcInvocation;
import com.uiot.platform.user.UserManager;
import com.uiot.web.HttpContext;

@Activate(group = {Constants.CONSUMER})
public class ConsumerTraceFilter implements Filter {

    private static Logger logger = LoggerFactory.getLogger(ConsumerTraceFilter.class);


    // 调用过程拦截
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
        	RpcInvocation invocation1 = (RpcInvocation) invocation;
        	//设置session信息
        	setSessionAttachment(invocation1);
            Result result = invoker.invoke(invocation);
            return result;
        }catch (RpcException e) {
            throw e;
        }finally {
        }
    }
    private void setSessionAttachment(RpcInvocation invocation) {
    	if (null != UserManager.getCurrentUser()) {
    		String sessionId = HttpContext.getSessionId();
    		invocation.setAttachment("sessionId", HttpContext.getSessionId());
    	}
   }
}

生产端Filter

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
import com.uiot.platform.redis.session.RedisSessionManager;
import com.uiot.platform.user.IUserInfo;
import com.uiot.platform.user.UserManager;
import com.uiot.web.HttpContext;

@Activate(group = {Constants.PROVIDER})
public class ProviderTraceFilter implements Filter{
	private Logger logger  = LoggerFactory.getLogger(this.getClass()); 
    // 调用过程拦截
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
        	//获取session信息 
            initSession(invocation);
            Result result = invoker.invoke(invocation);
            return result;
        }catch (RpcException e) {
            throw e;
        }finally {
        }
    }
    private void initSession(Invocation invocation) {
    	String sessionId = invocation.getAttachment("sessionId");
    	if (null != sessionId) {
    		//加载session
    		String sessionKey ="spring:session:sessions:" + sessionId;
    		logger.info("sessionKey=>" + sessionKey);
    		if (RedisSessionManager.redisTemplate != null) {
    			Map<String, Object> data = new HashMap<String, Object>();
    			IUserInfo userInfo = (IUserInfo)RedisSessionManager.redisTemplate.opsForHash().get(sessionKey, "sessionAttr:" + UserManager.SESSION_KEY_CURRENT_USER);
    			if(null != userInfo) {
    				String account = (String)RedisSessionManager.redisTemplate.opsForHash().get(sessionKey, "sessionAttr:" + UserManager.SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT);
        			//用户
        			data.put(UserManager.SESSION_KEY_CURRENT_USER, userInfo);
        			//登录账户
        			data.put(UserManager.SESSION_KEY_CURRENT_USER_LOGIN_ACCOUNT, account);
        			//sessionId往下传递
        			data.put("sessionId", sessionId);
        			HttpContext.threadLocal.set(data);
        			logger.warn("sessionKey=>" + sessionKey + ",获取到用户信息=>" + userInfo.getUserName());
    			} else {
    				logger.warn("没有获取到用户信息sessionKey=>" + sessionKey);
    			}
    		}
    	}
    }
}

Dubbo Filter配置

在classpath目录下创建目录"META-INF/dubbo",
然后在该目录下创建文件com.alibaba.dubbo.rpc.Filter
文件内容如下

consumerTrace=com.***.ConsumerTraceFilter
providerTrace=com.***.ProviderTraceFilter
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值