一、為什么需要session共享
HttpSession是由servelet容器進(jìn)行管理的。而我們常用的應(yīng)用容器有 Tomcat/Jetty等, 這些容器的HttpSession都是存放在對(duì)應(yīng)的應(yīng)用容器的內(nèi)存中,在分布式集群的環(huán)境下,通常我們使用Nginx或者LVS、Zuul等進(jìn)行反向代理和負(fù)載均衡,因此用戶(hù)請(qǐng)求是由一組提供相同服務(wù)的應(yīng)用來(lái)進(jìn)行處理,而用戶(hù)最終請(qǐng)求到的服務(wù)由Nginx和LVS、Zuul進(jìn)行確定。
那么問(wèn)題就來(lái)了,我們?cè)鯓颖WC多個(gè)相同的應(yīng)用共享同一份session數(shù)據(jù)?對(duì)于這種問(wèn)題Spring為我們提供了Spring Session進(jìn)行管理我們的HttpSession。
二、基礎(chǔ)Spring Boot配置Spring Session
1.添加Spring session的包,而Spring session 是將HttpSession存放在Redis中,因此需要添加Redis的包。我們這里是用了Spring boot進(jìn)行配置Redis。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.2.RELEASE</version>
<type>pom</type>
</dependency>
2、啟動(dòng)類(lèi)使用@EnableRedisHttpSession注解進(jìn)行配置啟用使用Spring session
@SpringBootApplication
@MapperScan(basePackages = "com.engine56.container.common.mapper")
@EnableTransactionManagement
public class ContainerApplication {
public static void main( String[] args ){
new SpringApplicationBuilder(ContainerApplication.class).web(true).run(args);
}
3、配置我們的Redis鏈接,我們這里使用的是Spring Boot作為基礎(chǔ)進(jìn)行配置,因此我們只需要在YML或者Properties配置文件添加Redis的配置即可。此處在application.properties中配置
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=123456
# 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.pool.min-idle=0
# 連接超時(shí)時(shí)間(毫秒)
spring.redis.timeout=0
4、在controller編寫(xiě)代碼
@GetMapping("/session")
public String test(HttpServletRequest request){
HttpSession session = request.getSession();
UUID uid = (UUID) session.getAttribute("uid");
String msg = "拿到了session!";
if (uid == null) {
uid = UUID.randomUUID();
session.setAttribute("uid", uid);
session.setAttribute("userinfo","張三,男,12歲");
msg="沒(méi)拿到session";
}else{
return msg+" ::: "+session.getAttribute("userinfo");
}
return msg;
}
5、測(cè)試
將項(xiàng)目用兩個(gè)不同端口啟動(dòng),用第一個(gè)端口訪問(wèn)后,用第二個(gè)端口再訪問(wèn),看是否拿到session。
測(cè)試結(jié)果:第一次訪問(wèn)輸出:沒(méi)拿到session;第二次訪問(wèn)輸出:拿到了session!張三,男,12歲。
三、SpringSession與shiro集成
1、首先要了解springSession實(shí)現(xiàn)原理
- 通過(guò)@EnableRedisHttpSession可以知道,Spring Session是通過(guò)RedisHttpSessionConfiguration類(lèi)進(jìn)行配置,該類(lèi)是用于創(chuàng)建一個(gè)過(guò)濾SessionRepositoryFilter
擴(kuò)展知識(shí):Spring Session提供了3種方式存儲(chǔ)session的方式。
@EnableRedisHttpSession-存放在緩存redis
@EnableMongoHttpSession-存放在Nosql的MongoDB
@EnableJdbcHttpSession-存放數(shù)據(jù)庫(kù) - 此filter放在所有filter之前,接管session管理。
- 如何獲取getSession:
先檢查是不是已經(jīng)有session了。如果有的話,就將其返回,
否則的話,它會(huì)檢查當(dāng)前的請(qǐng)求中是否有session id。
如果有的話,將會(huì)根據(jù)這個(gè)session id,從它的SessionRepository中加載session。
如果session repository中沒(méi)有session,或者在當(dāng)前請(qǐng)求中,沒(méi)有當(dāng)前
session id與請(qǐng)求關(guān)聯(lián)的話,那么它會(huì)創(chuàng)建一個(gè)新的session,并將其
持久化到session repository中 - 如何存儲(chǔ)session
請(qǐng)求時(shí),先獲取當(dāng)前session,不為空時(shí)即保存session。保存后,判斷
當(dāng)前請(qǐng)求中的sessionId是否與當(dāng)前sessionId一致,若不一致,則將當(dāng)
前sessionId保存至cookie。
2、shiro配置
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(EgRealm myShiroRealm) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm);
//<!-- 用戶(hù)授權(quán)/認(rèn)證信息Cache, 采用EhCache 緩存 -->
dwsm.setCacheManager(getEhCacheManager());
return dwsm;
}
ServletContainerSessionManager:DefaultWebSecurityManager使用的默認(rèn)實(shí)現(xiàn),用于Web環(huán)境,其直接使用Servlet 容器的會(huì)話;
故,不需要再額外配置,spring-session直接為shiro所用。
三、nginx實(shí)現(xiàn)負(fù)載均衡
以上實(shí)現(xiàn)了session共享后,如何做到負(fù)載均衡就要靠nginx了,配置如下:
(具體需要如何配置看項(xiàng)目業(yè)務(wù)需要了)
upstream blank {
server 127.0.0.1:3000 weight=10;
server 127.0.0.1:3001 weight=1;
}
server {
listen 8000;
server_name localhost;
location ~^/engine56{
proxy_pass http://blank;//注意:blank要和上面upstream后的名稱(chēng)一致。
}
location / {
root D:\xxxx\xxxxx;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}