天天看點

叢集環境下實作Session共享

文章内容輸出來源:拉勾教育Java高薪訓練營

文章目錄

      • 為什麼要實作Session共享
      • 實作方案
        • 1. Nginx的IP_Hash政策
        • 2. Tomcat的Session複制
        • 3. Session集中存儲
      • SpringSession使用示例
        • 資料庫腳本
        • 項目實作步驟
        • Tomcat配置步驟
        • Nginx配置步驟
        • 示範效果
      • 項目代碼

為什麼要實作Session共享

Http是無狀态的,為了保持使用者的資訊,就需要通過Cookie或者Session來存儲會話資訊。如使用者登入成功後,在Session中存儲使用者資訊,此使用者後續的操作就不用再重複登入。

但當在叢集環境中,同一個站點部署在多台伺服器上,就需要實作Session共享。不然使用者登入時,請求指向A伺服器執行個體,在Session中存儲了會話資訊。但下一次請求可能指向了B伺服器執行個體,B伺服器拿不到使用者之前登入的資訊,又要再登入一次。

實作方案

1. Nginx的IP_Hash政策

  • 方案說明

    同一IP的請求都路由到同一台伺服器上,即會話粘滞

  • 優點
    • 配置簡單
    • 無代碼侵入
  • 缺點
    • 伺服器重新開機會導緻Session丢失
    • 存在單點故障風險
推薦使用

2. Tomcat的Session複制

  • 方案說明

    多個Tomcat之間修改配置檔案達到Session複制的目的。這是早期處理的一種方案

  • 優點
    • 伺服器重新開機不會導緻Session丢失
    • 無代碼侵入
    • 友善伺服器的水準擴充
  • 缺點
    • 性能低。存儲的資料越多,性能越低下
    • 消耗記憶體
    • 複制存在延遲性
不推薦使用的方案

3. Session集中存儲

  • 方案說明

    Session的本質是緩存,将它交給專業的緩存中間件(如Redis來處理)

  • 優點
    • 伺服器重新開機不會導緻Session丢失
    • 擴充能力強
    • 适合大叢集環境的使用
  • 缺點
    • 代碼侵入性,需要在代碼中實作與Redis的互動
推薦使用。Spring Session使得基于Redis的Session共享應⽤起來⾮常之簡單

SpringSession使用示例

開發一個簡單的SpringBoot項目,實作了登入以及履歷增、删、改、查的功能。頁面直接使用JSP進行實作。

在本機上配置Nginx以及Tomcat模仿叢集環境。配置兩個Tomcat執行個體,将上述的項目打包為war包,部署在兩個Tomcat執行個體中。

資料庫腳本

CREATE DATABASE db_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE `tb_resume`(
 `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '使用者ID',
 `name` varchar(200) NOT NULL COMMENT '名稱',
 `address` varchar(200)  COMMENT '位址',
 `phone` varchar(200) COMMENT '手機',
  PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
           

項目實作步驟

  1. 建立SpringBoot項目

    cluster_demo

  2. 實作了履歷表

    t_resume

    的資料通路,并實作了履歷的增、删、改、查功能(具體詳見項目代碼)
  3. 實作簡單的登入功能
  • 登入後端請求,實作登入頁面跳轉以及登入請求,登入請求中直接判斷使用者名和密碼為admin即登入成功
@Controller
public class LoginController {

    @GetMapping("/login")
    public String loginPage(HttpServletRequest request) {
        Object loginUser = request.getSession().getAttribute("loginUser");

        if(null != loginUser) {
            return "redirect:resumes/list";
        }

        return "login";
    }


    @PostMapping("/login")
    @ResponseBody
    public Res login(HttpServletRequest request, @RequestBody LoginVO loginVO) {
        if(null == loginVO.getName() || loginVO.getName().trim().length() == 0) {
            return Res.fail("使用者名不能為空");
        }

        if(null == loginVO.getPassword() || loginVO.getPassword().trim().length() == 0) {
            return Res.fail("密碼不能為空");
        }

       
       if(!loginVO.getName().equals("admin") || !loginVO.getPassword().equals("admin")) {
            return Res.fail("使用者名或密碼錯誤");
        }


 //登入成功後,在session中存儲使用者資訊       
 request.getSession().setAttribute("loginUser", "admin");
        return Res.ok("登入成功");

    }
}
           
  • 登入攔截器的配置,判斷非登入頁面的請求,判斷使用者是否登入,沒有登入則跳轉到登入頁面
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("loginUser");

        if(null != loginUser) {
            return true;
        }

        response.sendRedirect("/login");
        return false;
    }
}


@Configuration
public class LoginAppConfigurer extends WebMvcConfigurerAdapter{
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login/**","/error");
        super.addInterceptors(registry);
    }
}
           
  • 登入表單的頁面以及登入請求的互動實作
<html>
    <body>
        <h2>使用者登入</h2>
        <div>
            <div>
                <b>使用者名:</b><input id="name" type="text" placeholder="請輸入使用者名"/>
            </div>
            <div>
                <b>密碼:</b><input id="password" type="password" placeholder="請輸入使用者密碼"/>
            </div>
            <div>
                <input id="btnLogin" type="button" value="登入"/>
            </div>
        </div>
    </body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
    $('#btnLogin').on('click', function() {
       var name =  $('#name').val().trim();
       var password =  $('#password').val().trim();
       if(name.length == 0) {
           alert('請輸入使用者名');
           return;
       }
       if(password.length == 0) {
           alert('請輸入密碼');
           return;
       }
       var params = {
           'name': name,
           'password': password
       };
        $.ajax({
            type: 'post',
            url: 'login',
            data: JSON.stringify(params),
            contentType: 'application/json;charset=utf-8',
            dataType: 'json',
            success: function(result) {
                if(result.success) {
                    window.location.href = 'resumes/list';
                }else {
                    alert(result.data);
                }
            }
        });
    });
</script>
</html>

           
  1. 開始實作Session共享功能。引入Redis以及SpringSession的依賴
<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>
           
  1. 項目配置檔案

    application.properties

    中配置下redis的配置資訊
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
           
  1. ClusterApplication

    中添加上

    EnableRedisHttpSession

    注解,實作Session共享的配置
@SpringBootApplication
@EnableCaching
@EnableRedisHttpSession
public class ClusterApplication extends SpringBootServletInitializer {}
           
  1. login.jsp

    中增加輸出端口号以及SessionID
<div>
    <div>
        <p>伺服器端口:${pageContext.request.localPort}</p>
        <p>目前SessionID:${pageContext.session.id}</p>
    </div>
</div>
           
  1. 使用

    mvn clean package

    打包項目為

    demo.war

Tomcat配置步驟

  1. 建立兩個Tomcat執行個體,分别配置為8080和8081端口
    叢集環境下實作Session共享
  2. 将上述打包的

    demo.war

    分别放到兩個tomcat的webapps目錄下
  3. 修改每個Tomcat執行個體的

    server.xml

    配置
  • Server

    Connector

    修改通路端口為8080或8081(預設為8080端口的話則不用再修改)
<Server port="8006" shutdown="SHUTDOWN">

    <Connector port="8081"/>
</Server>
           
  • Host

    标簽上增加

    Context

    子标簽,指定相應項目的路徑以及通路路徑。這裡配置如下:
<Host name="localhost"  appBase="webapps"
            deployOnStartup="false" unpackWARs="true" autoDeploy="true">
<Context docBase="demo.war" path="/" />
</Host>
           
  1. 啟動tomcat,确認項目通路成功
sh bin/startup.sh
           

Nginx配置步驟

在Nginx上配置負載均衡政策

  1. 修改

    nginx.conf

    配置檔案,配置反向代理。此處是監聽80端口,域名是www.yyh.com。配置内容如下:
http{
    upstream cluster-server {
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }

    server {
        listen       80;
        server_name  www.yyh.com;
        access_log  /logs/nginx/cluster.access.log;
        location / {
            proxy_pass  http://cluster-server;
        }
    }
}

           
  1. 修改電腦的hosts檔案,配置上

    www.yyh.com

    與本機的映射關系
127.0.0.1 www.yyh.com
           
  1. Nginx執行熱加載
nginx -s reload
           

示範效果

在浏覽器上通路

http://www.yyh.com

檢視實作效果

  • Nginx轉發請求給了

    tomcat-8080

    叢集環境下實作Session共享
  • Nginx轉發請求給了

    tomcat-8081

    叢集環境下實作Session共享
從上面可以看到伺服器端口在8080和8081順序變化着,但是目前SessionID的值沒有變化,則實作了Session的共享
  • 輸入

    admin/admin

    進行登入,登入成功後跳轉到履歷清單頁面
    叢集環境下實作Session共享

項目代碼

  • cluster_demo項目代碼

繼續閱讀