当前位置:网站首页>Distributed session solution
Distributed session solution
2022-06-27 15:45:00 【·wangweijun】
Consider a scenario , The background needs to verify whether the user logs in before placing an order , If you are not logged in, you are not allowed to submit an order , This is very easy to implement in traditional monomer applications , Just judge before submitting the order Session Whether the user information in is logged in , But in distributed applications , This is obviously a problem to be solved .
Under distributed application Session The problem is
In a distributed architecture , An application is often divided into several sub modules , such as : Login registration module and order module , When the application is split , Then comes the problem of data sharing :

Generally, in the login registration module, we save the login status of the user to Session in , However, when the user places an order , Since the order module is independent , It cannot get the stored in the login registration module Session, So the order module cannot judge whether the user logs in .
In order to ensure the high availability of the system , A module is often deployed in multiple copies to form a cluster , Data sharing between these modules is also a problem :

After the user successfully logs in a module , It is likely that the request will be load balanced to other cluster modules during the next access , This will result in failure to read Session, Make the user have to log in to the system again .
Session Case demonstration of shared problems
Here is a case to demonstrate , First create a SpringBoot application , Realize the login module :
@RestController
public class LoginController {
@Autowired
private ServiceOrderClient serviceOrderClient;
@GetMapping("/login")
public Result login(User user, HttpSession session) {
String username = user.getUsername();
String password = user.getPassword();
Result result = new Result();
if ("admin".equals(username) && "admin".equals(password)) {
result.setCode(200);
result.setMessage(" Login successful ");
session.setAttribute("user", user);
} else {
result.setCode(-1);
result.setMessage(" Login failed ");
}
}
}
Create another SpringBoot application , Implement the order module :
@RestController
public class OrderController {
@GetMapping("/order/test")
public String order(@CookieValue("JSESSIONID") String jSessionId) {
return "success";
}
}
The code is very simple , We mainly observe Session The problem of , Write the remote call interface in the login module :
@FeignClient("service-order")
public interface ServiceOrderClient {
@GetMapping("/order/test")
String order();
}
Register both applications to Nacos in , I will not post other codes , It's all pretty simple .
Start the two projects separately , And access http://localhost:8080/test , You will find that the visit was unsuccessful :

The result of the console output :
2021-09-21 16:51:43.155 WARN 20908 --- [nio-9000-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestCookieException: Missing cookie 'JSESSIONID' for method parameter of type String]
Can't find the name JSESSIONID Of Cookie, We know , The server is through JSESSIONID To find the corresponding Session The information of , since JSESSIONID You can't get , Not to mention user information , This is it. Session The problem of not sharing .
Redis solve Session Sharing issues
For distributed applications Session problem , In fact, it's very simple , It's just that you can't share it Session, therefore , We can analogize the idea of caching , take Session Put in cache , Other services want to get Session Also get from the cache , That's it Session The share of .
Improve the login module :
@GetMapping("/login")
public Result login(User user, HttpSession session) {
String username = user.getUsername();
String password = user.getPassword();
Result result = new Result();
if ("admin".equals(username) && "admin".equals(password)) {
result.setCode(200);
result.setMessage(" Login successful ");
String json = JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("session", json);
} else {
result.setCode(-1);
result.setMessage(" Login failed ");
}
return result;
}
When we access the login interface http://localhost:8080/login?username=admin&password=admin when , Will go to Redis Keep a journal of Session Value :

At this time, if other services are required Session, As long as Redis It can be read from the , Modify the order module :
@RestController
public class OrderController {
@GetMapping("/order/test")
public String order() {
return "success";
}
}
Add a login interceptor in the order module :
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Manual access StringRedisTemplate object
StringRedisTemplate redisTemplate = SpringBeanOperator.getBean(StringRedisTemplate.class);
String json = redisTemplate.opsForValue().get("session");
User user = JSONObject.parseObject(json, User.class);
System.out.println(user);
if (user == null) {
System.out.println(" The user is not logged in ......");
return false;
} else {
System.out.println(" User logged in ......");
return true;
}
}
}
Register the interceptor :
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**");
}
}
Restart project , visit http://localhost:8080/test , Output results :
User(username=admin, password=admin)
User logged in ......
SpringSession solve Session Sharing issues
We used it ourselves just now Redis Tried to solve it Session The problem of sharing , However, this method has many defects , First , What we keep is just a User object , Not at all Session, So we can't identify the user , This will cause the user to access the information of other users , Make the system chaotic . Of course we can use JSESSIONID To identify different users , But in fact ,Spring A component has been provided for us to solve this problem , That's it SpringSession.
\
In both modules SpringSession Dependence :
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
stay application.yml Middle configuration Session The storage method of is Redis:
spring: session:
store-type: redis
Finally, add... To the startup class @EnableRedisHttpSession annotation , such SpringSession The integration of .
We modify the code of the login module :
@GetMapping("/login")
public Result login(User user, HttpSession session) {
String username = user.getUsername();
String password = user.getPassword();
Result result = new Result();
if ("admin".equals(username) && "admin".equals(password)) {
result.setCode(200);
result.setMessage(" Login successful ");
session.setAttribute("user",user);
} else {
result.setCode(-1);
result.setMessage(" Login failed ");
}
return result;
}
Follow the normal process to User Objects in Session, Restart the project and access the login interface , Let's see Redis What's the change in :

here Redis User information has been saved in , And there is also the creation time 、 Lifetime and other configurations , Other modules want to get Session User information in , You just need to write code according to the normal process :
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
System.out.println(user);
if (user == null) {
System.out.println(" The user is not logged in ......");
return false;
} else {
System.out.println(" User logged in ......");
return true;
}
}
}
Note that the login module stores User Objects need to be read out with other modules User Object package names are consistent , So it's better to User Classes are extracted into common modules , Available to all modules .
Come here SpringSession It's settled. Session The problem of sharing , You can run the project to test , visit http://localhost:8080/test :

The results were unexpected , The result of the console is :
null
The user is not logged in ......
This is strange , Is it SpringSession Didn't work ? Let's write a test method to test :
@GetMapping("/test")
public String test(HttpSession session) {
User user = (User) session.getAttribute("user");
System.out.println(user);
return "test";
}
visit http://localhost:9000/test , Get the results :
User(username=admin, password=admin)
obviously SpringSession There is no problem , So what's the problem ?
OpenFeign Remote call pit
Just now we had a test , Find direct access to... In the order module Session Can get User object , However, through remote calls ,User You can't get it , We can guess that this is OpenFeign There is a problem ,Debug Debug the project , This is the code for remote calls :

Let's go in and have a look :
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
Some judgments are made in this method , Will eventually call dispatch.get() Method :
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
This method will call executeAndDecode():

This method encapsulates a request template as the target request for remote invocation , However, we observe that there are no parameters or request headers in the request template , And we know that ,Session Depend on JSESSIONID For identification , stay SpringSession in ,Session Depend on SESSIONID Identification of the :

From this we come to the conclusion that , because OpenFeign The remote call lost the request header , Lead to SESSIONID The loss of , As a result, the order module cannot obtain User object . After knowing the problem , The solution is very simple , We can create a request filter , It will process the request before the request template is generated :
@Configuration
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
System.out.println(" Call this method before remote call -->requestInterceptor......");
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String cookie = request.getHeader("Cookie");
requestTemplate.header("Cookie", cookie);
};
}
}
The original Request Object Cookie Set the request header information to the request template , such OpenFeign The created request has Cookie Content , Restart the project test , The problem is solved with ease .
边栏推荐
- Why can't the start method be called repeatedly? But the run method can?
- LeetCode每日一练(无重复字符的最长子串)
- 28 object method extension
- Use of abortcontroller
- Interview question: rendering 100000 data solutions
- CNN convolutional neural network (the easiest to understand version in History)
- [170] the PostgreSQL 10 field type is changed from string to integer, and the error column cannot be cast automatically to type integer is reported
- Synchronized and lock escalation
- Vscode uses yapf auto format to set the maximum number of characters per line
- Basic configuration and usage of Jupiter notebook
猜你喜欢

Synchronized and lock escalation

CentOS8-postgresql初始化时报错:initdb: error: invalid locale settings; check LANG and LC_* environment

Problems encountered in vs compilation

洛谷入门1【顺序结构】题单题解

Interview question: rendering 100000 data solutions

一场分销裂变活动,不止是发发朋友圈这么简单!

Centos8 PostgreSQL initialization error: initdb: error: invalid locale settings; check LANG and LC_* environment

E ModuleNotFoundError: No module named ‘psycopg2‘(已解决)

CAS之比较并交换

Let's talk about the process of ES Indexing Documents
随机推荐
Does polardb-x currently not support self-made database service Das?
[interview questions] common interview questions (I)
About sitemap XML problems
ReentrantLock、ReentrantReadWriteLock、StampedLock
Redis CacheClient
PSS:你距离NMS-free+提点只有两个卷积层 | 2021论文
利用Redis实现订单30分钟自动取消
洛谷入门2【分支结构】题单题解
Luogu_ P1003 [noip2011 improvement group] carpet laying_ Violence enumeration
volatile与JMM
关于 Spartacus 的 sitemap.xml 问题
[high concurrency] deeply analyze the callable interface
PolarDB-X现在版本的开源兼容什么?mysql8?
Talk about redis transactions
创建数据库并使用
Admixture usage document Cookbook
Eolink launched a support program for small and medium-sized enterprises and start-ups to empower enterprises!
Cesium 使用MediaStreamRecorder 或者MediaRecorder录屏并下载视频,以及开启摄像头录像。【转】
Does polardb-x open source support mysql5.7?
固收+产品有什么特点?