当前位置:网站首页>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 .
边栏推荐
- Markdown syntax
- Go error collection | when a function uses a return value with a parameter name
- 漏洞复现----34、yapi 远程命令执行漏洞
- 洛谷入门2【分支结构】题单题解
- Synchronized and lock escalation
- 28 object method extension
- VS编译遇到的问题
- 带你认识图数据库性能和场景测试利器LDBC SNB
- 洛谷_P1003 [NOIP2011 提高组] 铺地毯_暴力枚举
- Condom giants' sales have fallen by 40% in the past two years. What are the reasons for the decline?
猜你喜欢
CAS之比较并交换
What is the London Silver code
Unity3d best practices: folder structure and source control
2022-2-15 learning the imitated Niuke project - Section 5 shows comments
分布式Session解决方案
Let's talk about the process of ES Indexing Documents
洛谷入门1【顺序结构】题单题解
[high concurrency] deeply analyze the callable interface
CAS comparison and exchange
Lei Jun lost another great general, and liweixing, the founding employee of Xiaomi No. 12, left his post. He once had porridge to create Xiaomi; Intel's $5.4 billion acquisition of tower semiconductor
随机推荐
Design of CAN bus controller based on FPGA (with main codes)
Google Earth Engine(GEE)——Export. image. The difference and mixing of toasset/todrive, correctly export classification sample data to asset assets and references
Keep valid digits; Keep n digits after the decimal point;
If you want to use DMS to handle database permissions, can you only use Alibaba cloud ram accounts (Alibaba cloud RDS)
避孕套巨头过去两年销量下降40% ,下降原因是什么?
关于快速幂
Pisa-Proxy 之 SQL 解析实践
Luogu_ P1008 [noip1998 popularization group] triple strike_ enumeration
16 -- remove invalid parentheses
Lei Jun lost another great general, and liweixing, the founding employee of Xiaomi No. 12, left his post. He once had porridge to create Xiaomi; Intel's $5.4 billion acquisition of tower semiconductor
关于 Spartacus 的 sitemap.xml 问题
NFT双币质押流动性挖矿dapp合约定制
QT notes (XXVIII) using qwebengineview to display web pages
洛谷入门1【顺序结构】题单题解
开源二三事|ShardingSphere 与 Database Mesh 之间不得不说的那些事
Fundamentals of software engineering (I)
February 16, 2022 freetsdb compilation and operation
Creation and use of static library (win10+vs2022
Beginner level Luogu 1 [sequence structure] problem list solution
sql注入原理