赞
踩
分布式Session共享是分布式系统中常见的问题,主要解决在多个服务器之间共享用户会话信息的需求。以下是五种常见的分布式Session共享技术方案及其优劣势比较:
设计思路:
多个Web服务器之间相互同步Session,每个Web服务器包含全部Session数据。
优点:
缺点:
Session复制是一种在服务器集群中同步Session数据的机制,使得每个服务器都拥有所有用户的Session副本。以下是使用Tomcat和Spring Boot实现Session复制的基本步骤和代码示例。
在Spring Boot项目的pom.xml
文件中添加必要的依赖。
<dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Session --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <!-- Redis Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
在application.properties
或application.yml
中配置Redis连接信息。
spring.redis.host=localhost
spring.redis.port=6379
在Spring Boot启动类上添加@EnableRedisHttpSession
注解,启用Spring Session。
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession
@SpringBootApplication
public class SessionReplicationApplication {
public static void main(String[] args) {
SpringApplication.run(SessionReplicationApplication.class, args);
}
}
在server.xml
中配置Tomcat集群,启用Session复制功能。
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"> <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true" /> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" dropTime="3000" /> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autoBind="100" selectorTimeout="5000" maxThreads="6" /> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" /> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" /> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor" /> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter="" /> <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" /> <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/" deployDir="/tmp/war-deploy/" watchDir="/tmp/war-listen/" watchEnabled="false" /> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" /> </Cluster>
在web.xml
中添加<distributable/>
标签,启用分布式部署。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<distributable/>
</web-app>
@EnableRedisHttpSession
注解启用Spring Session,并将Session数据存储在Redis中。server.xml
中配置Tomcat集群,启用Session复制功能。这包括配置Cluster、Manager、Channel等组件。web.xml
中添加<distributable/>
标签,确保Web应用在分布式环境下可以正确运行。通过这种方式,每个Tomcat实例都会将Session数据同步到Redis中,从而实现在多个服务器之间共享Session数据。这样,即使用户的请求被分发到不同的服务器,也能访问到一致的Session数据。
设计思路:
将Session数据加密后存储在Cookie中。
优点:
缺点:
将Session信息存储在Cookie中是一种简单且常见的分布式Session共享方法。这种方式将用户的会话信息加密后存储在客户端的Cookie中,每次请求时携带这些信息,服务器通过解析Cookie来恢复会话状态。
在Spring Boot项目的pom.xml
文件中添加必要的依赖。
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
在Spring Boot应用中创建一个控制器,处理用户的登录请求,并在成功登录后将用户信息存储在Cookie中。
import org.springframework.web.bind.annotation.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @RestController public class LoginController { @PostMapping("/login") public String login(@RequestParam String username, HttpServletResponse response) { String sessionId = UUID.randomUUID().toString(); String encryptedSessionData = encryptSessionData(username); // 创建Cookie并设置加密的Session数据 Cookie cookie = new Cookie("JSESSIONID", encryptedSessionData); cookie.setHttpOnly(true); // 增加安全性,禁止JavaScript访问Cookie cookie.setPath("/"); // Cookie对整个应用有效 response.addCookie(cookie); return "Logged in with session ID: " + sessionId; } private String encryptSessionData(String username) { // 这里使用简单的Base64编码作为示例,实际应用中应使用更安全的加密算法 return Base64.getEncoder().encodeToString(username.getBytes()); } }
处理用户的登出请求,并清除Cookie中的Session信息。
import org.springframework.web.bind.annotation.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @RestController public class LogoutController { @GetMapping("/logout") public String logout(HttpServletRequest request, HttpServletResponse response) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("JSESSIONID".equals(cookie.getName())) { cookie.setMaxAge(0); // 清除Cookie cookie.setPath("/"); // 确保Cookie被正确清除 response.addCookie(cookie); } } } return "Logged out"; } }
使用Thymeleaf创建首页和登录页面。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login Page</title>
</head>
<body>
<h2>Login</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<button type="submit">Login</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<a href="/logout">Logout</a>
</body>
</html>
pom.xml
中添加Spring Web和Thymeleaf的依赖。注意:
通过这种方式,用户的会话信息在客户端和服务器之间传递,实现了分布式环境下的Session共享。
设计思路:
利用负载均衡器的分发能力,将同一浏览器上同一用户的请求定向发送到固定服务器上。
优点:
缺点:
Session粘性(也称为会话粘滞性或会话亲和性)是一种通过负载均衡器确保同一用户的请求始终被路由到同一服务器的技术。这可以通过负载均衡器的配置实现,而不是在应用代码中处理。以下是使用Nginx实现Session粘性的步骤和代码示例。
在Nginx的配置文件中(通常是nginx.conf
或default.conf
),配置负载均衡器并启用Session粘性。
http { upstream backend { server tomcat1.example.com; server tomcat2.example.com; server tomcat3.example.com; # 启用Session粘性 sticky; sticky cookie cookie_name; } server { listen 80; location / { proxy_pass http://backend; # 配置负载均衡器的其他参数 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } }
参数说明:
upstream backend
:定义了一个服务器组。server
:定义了服务器组中的各个服务器。sticky
:启用Session粘性。sticky cookie cookie_name
:指定用于Session粘性的Cookie名称。确保Tomcat服务器能够处理来自Nginx的请求。在server.xml
中配置Tomcat的端口和应用路径。
<Server port="8005" shutdown="SHUTDOWN">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Host name="localhost" appBase="webapps"
unpackWAR="true" autoDeploy="true">
<Context path="" docBase="app" reloadable="true" />
</Host>
</Server>
在Spring Boot应用中,创建一个控制器来处理用户的登录和登出请求,并在成功登录后设置一个Cookie。
import org.springframework.web.bind.annotation.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @RestController public class SessionController { @PostMapping("/login") public String login(@RequestParam String username, HttpServletResponse response) { // 创建Cookie并设置用户名 Cookie cookie = new Cookie("JSESSIONID", username); cookie.setPath("/"); // Cookie对整个应用有效 cookie.setMaxAge(60 * 60 * 24); // 设置Cookie有效期为1天 response.addCookie(cookie); return "Logged in with session ID: " + username; } @GetMapping("/logout") public String logout(HttpServletRequest request, HttpServletResponse response) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("JSESSIONID".equals(cookie.getName())) { cookie.setMaxAge(0); // 清除Cookie cookie.setPath("/"); // 确保Cookie被正确清除 response.addCookie(cookie); } } } return "Logged out"; } }
使用Thymeleaf创建首页和登录页面。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login Page</title>
</head>
<body>
<h2>Login</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<button type="submit">Login</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<a href="/logout">Logout</a>
</body>
</html>
sticky
指令和sticky cookie
指令指定用于粘性的Cookie。注意:
设计思路:
Session存储在数据库或缓存(如Redis、Memcached)中。
优点:
缺点:
集中管理Session在后端是一种常见的分布式Session共享方法,通常使用缓存系统如Redis来存储Session数据。这种方式将所有用户的Session信息存储在集中的缓存系统中,而不是分散在各个应用服务器上。以下是使用Spring Boot和Redis实现Session集中管理的步骤和代码示例。
在Spring Boot项目的pom.xml
文件中添加必要的依赖。
<dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Session Data Redis --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <!-- Spring Boot Starter Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
在application.properties
或application.yml
中配置Redis连接信息。
spring.redis.host=localhost
spring.redis.port=6379
在Spring Boot启动类上添加@EnableRedisHttpSession
注解,启用Spring Session。
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession
@SpringBootApplication
public class SessionManagementApplication {
public static void main(String[] args) {
SpringApplication.run(SessionManagementApplication.class, args);
}
}
在Spring Boot应用中创建一个控制器,处理用户的登录请求,并在成功登录后将用户信息存储在Session中。
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@RestController
public class LoginController {
@PostMapping("/login")
public String login(@RequestParam String username, HttpSession session) {
session.setAttribute("username", username);
return "Logged in with username: " + username;
}
}
处理用户的登出请求,并清除Session中的信息。
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@RestController
public class LogoutController {
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "Logged out";
}
}
使用Thymeleaf创建首页和登录页面。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login Page</title>
</head>
<body>
<h2>Login</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<button type="submit">Login</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<form action="/logout" method="get">
<button type="submit">Logout</button>
</form>
</body>
</html>
pom.xml
中添加Spring Web、Spring Session Data Redis和Spring Data Redis的依赖。@EnableRedisHttpSession
注解启用Spring Session,并将Session数据存储在Redis中。注意:
设计思路:
使用JSON Web Token(JWT)代替传统的Session。
优点:
缺点:
通过以上方案的比较,可以根据不同业务需求和技术背景选择合适的分布式Session共享方案。
使用Token代替Session是一种现代的身份验证方法,其中JSON Web Tokens (JWT) 是最常用的实现方式。Token通常在用户登录后生成,并在用户进行后续请求时发送到服务器。以下是使用Spring Boot和JWT实现Token认证的步骤和代码示例。
jjwt
,用于生成和验证JWT。在Spring Boot项目的pom.xml
文件中添加JWT库的依赖。
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JWT library -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- 请使用最新版本 -->
</dependency>
</dependencies>
创建一个工具类来生成和解析JWT。
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.function.Function; public class JwtUtils { private static final String SECRET_KEY = "your-secret-key"; // 应从配置文件或环境变量中安全地获取 public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时后过期 .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static String getUserNameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { return false; } } }
在Spring Boot应用中创建一个控制器,处理用户的登录请求,并在成功登录后生成JWT。
import org.springframework.web.bind.annotation.*;
import io.jsonwebtoken.Jwts;
import javax.servlet.http.HttpServletResponse;
@RestController
public class AuthController {
@PostMapping("/login")
public String login(@RequestParam String username, HttpServletResponse response) {
String token = JwtUtils.generateToken(username);
response.addHeader("Authorization", "Bearer " + token);
return "Logged in with token";
}
}
创建一个过滤器,用于验证传入的Token。
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); if (JwtUtils.validateToken(token)) { filterChain.doFilter(request, response); } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } }
注册JwtFilter
以确保所有受保护的请求都经过Token验证。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JwtConfig {
@Bean
public FilterRegistrationBean jwtFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*"); // 所有/api下的请求都需要验证Token
return registrationBean;
}
}
创建一个受保护的API,只有验证Token后才可访问。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SecureController {
@GetMapping("/secure/data")
public String secureData() {
return "Secure data for user: " + JwtUtils.getUserNameFromToken("from-request");
}
}
pom.xml
中添加JWT库的依赖。注意:
5种分布式Session共享实现方案该如何选择呢?
选择适合的分布式Session共享方案时,我们需要考虑以下因素:
系统规模:
安全性要求:
开发和维护成本:
性能考虑:
可扩展性:
跨域支持:
技术栈和偏好:
通过综合考虑这些因素,可以为特定业务场景选择最合适的分布式Session共享方案。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。