分布式系统中,session共享方案长借助于数据库,将session存放于数据库中,多个应用客户端共连一个数据库,以此达到session共享的方案。数据库中,redis在session共享中通常作为第一选择,spring框架提供了一个集成httpsession和redis的解决方案,这篇文章,就展示spring-session和redis共享session的示例。
工程组件说明
- servicecenter 服务注册中心
- zuul 网关
- UserManagerA 用户服务A
- UserManagerB 用户服务B
- BuyManager 购买服务
其中,servicecenter单纯做注册中心,zuul做网关路由功能。UserManagerA 和 UserManagerB作为用户服务两台应用服务,BuyManager作为一个购买服务。
用户登录session处理在UserManagerA 和 UserManagerB 服务中,在任意一台服务创建session会话后,在另外一台服务中都可以看到。BuyManager作为另外的一台服务,将展示同一个redis服务中的不同应用可以共享到session信息。
工程代码示例
pom文件
使用spring-session redis方案时,引用的jar支持
<!--redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
其余各项目依赖,根据项目须要引入。
servicecenter
注册中心无其它代码内容,仅一个注册中心配置项文件,设置注册中心地址 localhost:8761
server:
port: 8761 #服务注册中心端口号
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false #是否向服务注册中心注册自己
fetchRegistry: false #是否检索服务
serviceUrl: #服务注册中心的配置内容,指定服务注册中心位置
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
zuul
网关做路由分发功能
设置网关端口号5000,设置注册中心地址。设置路由分发策略,将/um开头服务分发到um服务实例;将/bm开头服务分发到bm服务实例。
# 服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
# 设置程序端口号为5000,服务名为zuul-service
server.port=5000
spring.application.name=zuul
# 将以"/um"开头的url路由到um服务
zuul.routes.um.path=/um/**
zuul.routes.um.serviceId=um
zuul.routes.um.sensitiveHeaders=*
# 将以"/bm"开头的url路由到bm服务
zuul.routes.bm.path=/bm/**
zuul.routes.bm.serviceId=bm
zuul.routes.bm.sensitiveHeaders=*
zuul.host.socket-timeout-millis=60000
zuul.host.connect-timeout-millis=60000
springboot主启动类添加注解,@EnableZuulProxy注解,开启Zuul功能
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* 添加@EnableEurekaClient注解,开启EurakaClient功能
* 添加@EnableZuulProxy注解,开启Zuul功能
* @author PC
*/
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class Zuul {
public static void main(String[] args) {
SpringApplication.run(Zuul.class, args);
}
}
UserManagerA工程
UserManagerB和UserManagerA工程类似,这里只展示一个工程,简要说明差异处。
配置文件,说明工程名称为um,配置redis服务器信息和工程信息,其中UserManagerA端口为8766,UserManagerB工程端口为8769
spring.application.name=um
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
server.port=8766
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=0
spring.redis.timeout=5000
spring.session.store-type=redis
UserManagerA 工程访问入口类 SpringSessionRedisSessionShared
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
@EnableEurekaClient
@RestController
public class SpringSessionRedisSessionShared {
@Value("${server.port}")
private String port;
@ResponseBody
@RequestMapping("/loginIn")
public String login(HttpServletRequest request, String token) {
HttpSession session = request.getSession();
System.out.println("session=" + JSONObject.toJSONString(session));
String name = (String) session.getAttribute("name");
if (null != name) {
return port + " success.name=" + name;
}
session.setAttribute("name", token);
return port + " " + token;
}
@ResponseBody
@RequestMapping("/go")
public String test(HttpServletRequest request, String token) {
HttpSession session = request.getSession();
System.out.println("session=" + JSONObject.toJSONString(session));
String name = (String) session.getAttribute("name");
System.out.println("spring-session name=" + name);
if (null != name) {
return port + " success.";
}
return port + " test failed";
}
}
其中,token为前端传递的一个参数信息,该信息会被放进session中,同步的spring-session框架会将session同步到redis中。
UserManagerA启动类,这里一定添加注解@EnableRedisHttpSession,以示启动redis作为spring-session共享方案。括号中maxInactiveIntervalInSeconds时间为秒数,若不设置,则默认不过期,永久有效。这里设置为120秒,即两分钟。
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 120)
@SpringBootApplication
public class UserA {
public static void main(String[] args) {
SpringApplication.run(UserA.class, args);
}
}
BuyManager
配置文件
spring.application.name=bm
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
server.port=8767
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=0
spring.redis.timeout=3000
spring.session.store-type=redis
代码类
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 120)
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@ResponseBody
@RequestMapping("/hello")
public String hello(String name) {
return "Hi, I am " + name + ". This service was supplied by instance " + port;
}
@ResponseBody
@RequestMapping("/come")
public String come(HttpServletRequest request) {
HttpSession session = request.getSession();
String loginUserId = (String) session.getAttribute("name");
if (null != loginUserId) {
System.out.println("session=" + JSONObject.toJSONString(session));
return "come get session success";
}
System.out.println("session=" + JSONObject.toJSONString(session));
return "come get session failed";
}
}
项目测试
依次启动 servicecenter、zuul、UserManagerA、UserManagerB 和 BuyManager工程,启动完毕后,打开注册中心 http://localhost:8761/ 可以看到各实例注册成功
在浏览器输入 http://localhost:5000/um/loginIn?token=spring-session 访问UserManager工程创建session
可以看到,在8769端口对应的实例(UserManagerA工程),创建session成功
此时,在redis客户端查看存在于redis中的session信息
session信息在redis中使用hash列表存储,其中spring:session:sessions:sessionId 为hash key值。
在浏览器中输入http://localhost:5000/um/go 测试session共享情况,由于zuul默认轮询策略,很快可以看到服务请求到8766实例,session请求成功
继而到 http://localhost:5000/bm/come ,这时请求BuyManager工程,可以看到session请求成功
代码中,设置的maxInactiveIntervalInSeconds 为120秒,两分钟后,再去请求服务,各请求均失败
测试过程中,可以看后台应用打印的日志信息,多次打印的session创建时间和sessionId 均相同,表示为同一个session信息。
session={"attributeNames":["name"],"creationTime":1609893405410,"id":"e47ef9eb-041b-497b-a6c8-0d77672cc08b","lastAccessedTime":1609893409057,"maxInactiveInterval":120,"new":false,"servletContext":{},"session":{"attributeNames":["name"],"creationTime":"2021-01-06T00:36:45.410Z","expired":false,"id":"e47ef9eb-041b-497b-a6c8-0d77672cc08b","lastAccessedTime":"2021-01-06T00:36:49.057Z","maxInactiveInterval":"PT2M","new":false},"sessionContext":{"ids":[]},"valueNames":["name"]}
简单示例就到这里,写的仓促,有细节不清楚的还望留言沟通讨论。