SpringBoot+nginx+redis實現session分佈式共享和單點登錄

最近搭建了一個小項目測試了一下session的分佈式共享

  1. 項目是基於SpringBoot的這裏只講一個注意事項
    SpringBoot內嵌tomcat,直接run Application即可,那麼我們如何去除內嵌的tomcat,使用自己的呢?
    一、POM(去除內嵌tomcat後,需要添加servlet依賴)
<packaging>war</packaging>
     <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <!-- 去除內嵌tomcat -->
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <!--添加servlet的依賴-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

二、繼承SpringBootServletInitializer重寫configure方法

package com.zyu.springandmybatis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;


@SpringBootApplication
@EnableCaching  // 開啓緩存註解
public class SpringandmybatisApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringandmybatisApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }


}

  1. 我主要來講一下里面的nginx部分
    什麼是nginx?
    Nginx是一個http服務器。是一個使用c語言開發的高性能的http服務器及反向代理服務器,nginx常用做靜態內容服務和代理服務器。
    爲什麼使用nginx?
    在傳統的Web項目中,併發量小,用戶使用的少。所以在低併發的情況下,用戶可以直接訪問tomcat服務器,然後tomcat服務器返回消息給用戶,互聯網飛速發展的今天,大用戶量高併發已經成爲互聯網的主體.怎樣能讓一個網站能夠承載幾萬個或幾十萬個用戶的持續訪問呢,nginx能夠支持高達 50000 個併發連接數的響應,用單機tomcat搭建的網站,在比較理想的狀態下能夠承受的併發訪問量在150到200左右。按照併發訪問量佔總用戶數量的5%到10%這樣計算,單點tomcat網站的用戶人數在1500到4000左右。對於一個爲全國範圍提供服務的網站顯然是不夠用的,爲了解決這個問題引入了負載均衡方法。負載均衡就是一個web服務器解決不了的問題可以通過多個web服務器來平均分擔壓力來解決,併發過來的請求被平均分配到多個後臺web服務器來處理,這樣壓力就被分解開來。
    Nginx安裝:

1、去官網下載最新的穩定版本的nginx包解壓tar -zxvf nginx-1.16.1.tar.gz
2、切換到解壓後的目錄
3、安裝一些基礎工具
yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
4、編譯和安裝 make && make install
5、配置所需的模塊,不配置使用默認的配置
./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_stub_status_module --with-http_ssl_module
6、 啓動、重啓、停止
啓動:/usr/local/nginx/sbin/nginx
重啓:/usr/local/nginx/sbin/nginx -s reload
停止:方法1:第一個是完整有序的停止
/usr/local/nginx/sbin/nginx -s quit
方法2:快速停止
/usr/local/nginx/sbin/nginx -s stop
方法3: kill

啓動多個tomcat時,端口會發生衝突,需要修改server.xml中的以下地方

1<Server port="8005" shutdown="SHUTDOWN">是tomcat 監聽的關閉端口
2<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"  useBodyEncodingForURI="true"/> 訪問地址端口
3<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>這個是接受其他服務轉發過來的請求。
4、第四處修改 Engine元素增加jvmRoute屬性:<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">

配置nginx.conf文件

user  root; 
worker_processes  1; #nginx進程數,建議設置爲等於CPU總核心數
events {
    worker_connections  1024; #單個進程最大連接數。那麼該服務器的最大連接數=連接數*進程數
}
    #服務器的集羣,test.com爲集羣名字,必須與proxy_pass的值一樣。
    upstream test.com{
    server 192.168.214.135:8081 weight=1; #weight權重,權值越高被分配的機率越大
    server 192.168.214.135:8082 weight=2;
    }
    server {
        listen       80; #監聽端口一般都爲http端口:80
        server_name  localhost; #當前服務的域名,可以有多個,用空格隔開
        location / {
           proxy_pass http://test.com; #請求轉向自定義的服務器列表
           proxy_redirect default;
        }

nginx的四種調度算法
A)輪詢(默認):每個請求按時間順序逐一分配到不同的後端服務器;
B)ip_hash:每個請求按訪問IP的hash結果分配,同一個IP客戶端固定訪問一個後端服務器,可以解決session會話保持問題,適用業務場景:適用於需要賬號登錄的系統,會話連接保持的業務,同時這種方式也有弊端,使用ip_hash可能會導致某一臺服務器負載較大。如果某段時間內服務器進入了很多固定IP代理的請求 [翻牆,代理],如果代理IP的負載過高就會導致ip_hash對應的服務器負載壓力過大,這樣ip_hash就失去了負載均衡的作用了。
C)url_hash:按訪問url的hash結果來分配請求,使每個url定向到同一個後端服務器,需編譯安裝第三方模塊 ngx_http_upstream_hash_module,適用業務場景:適用於後端服務器爲緩存服務器時比較有效。
upstream backendserver {
server 192.168.0.14:80 max_fails=2 fail_timeout=10s;
server 192.168.0.15:80 max_fails=2 fail_timeout=10s;
hash $request_uri;
}
D)fair:這是比上面兩個更加智能的負載均衡算法。此種算法可以依據頁面大小和加載時間長短智能地進 行負載均衡,也就是根據後端服務器的響應時間來分配請求,響應時間短的優先分配。Nginx本身是不支持 fair的,如果需要使用這種調度算法,必須下載Nginx的upstream_fair模塊。需編譯安裝第三方模塊 ngx_http_upstream_fair_module,適用業務場景:對訪問響應速度有一定要求的業務。
upstream backendserver {
fair;
server 192.168.0.14:80;
server 192.168.0.15:80 ;
}
設置後端負載均衡服務器的狀態:
down,表示當前的server暫時不參與負載均衡。 backup,預留的備份機器。當其他所有的非backup機器出現故障或者忙的時候,纔會請求backup機器,因 此這臺機器的壓力最輕。
注意:backup不能和ip_hash同時配置。因爲ip_hash只能訪問同一臺服務器,而backup是在只有所有參與
負載均衡的服務器出現故障時,纔會請求備份機。當所有負載均衡的服務器出現故障了,ip_hash的將無法 請求了。
這裏就是nginx實現負載均衡部分,上面我弄了兩個tomcat服務器測試負載均衡。

  1. 寫一個項目來測試session分佈式共享
    中間涉及到一些redis部分的知識點,可以看看我寫的redis教程redis教程,以下是關鍵部分的代碼。
    攔截器:
package com.zyu.springandmybatis.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 編寫登錄狀態攔截器RedisSessionInterceptor
 */
public class RedisSessionInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        HttpSession session = request.getSession();
        if (session.getAttribute("loginUserId") != null) {
            try {
                //驗證當前請求的session是否是已登錄的session
                String loginSessionId = (String)redisTemplate.opsForValue().get("loginUser:" + session.getAttribute("loginUserId").toString());
                if (loginSessionId != null && loginSessionId.equals(session.getId())) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        response401(response);
        return false;
    }

    private void response401(HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");

        try {
            response.getWriter().print("用戶未登錄,或登錄過期");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

    }
}

配置攔截器

package com.zyu.springandmybatis.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * 配置攔截器
 */
@Configuration
public class WebSecurityConfig extends WebMvcConfigurationSupport {
    @Bean
    public RedisSessionInterceptor getSessionInterceptor() {
        return new RedisSessionInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/redis/**").excludePathPatterns("/redis/login");
        registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/user/**");
        super.addInterceptors(registry);
    }
}

對請求進行攔截和放過。
登錄login部分的代碼

package com.zyu.springandmybatis.controller;

import com.zyu.springandmybatis.entity.User;

import com.zyu.springandmybatis.service.UserService;
import com.zyu.springandmybatis.service.myException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@RestController
@RequestMapping(value = "redis")
public class RedisController {
    @Autowired
    UserService userService;
    @Autowired
    RedisTemplate redisTemplate;
    @RequestMapping(value = "/test")
    public String  test(){
        String test= (String) redisTemplate.opsForValue().get("loginUser:2");
        System.out.println(test);
        return test;
    }

    
   //登錄過濾
    @RequestMapping(value = "/login")
    //登錄
    public void login(HttpServletRequest request, HttpServletResponse response, String userName, String userPassword) throws IOException,NullPointerException {
        HttpSession session = request.getSession();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        Object sss = session.getAttribute("loginUserId");
        if(sss!=null){
            response.getWriter().print("已登錄");
        }
        else{
        User user = userService.getUser(userName,userPassword);
        if(user!=null){
            session.setAttribute("loginUserId", user.getUserId());
            session.setMaxInactiveInterval(60);
            redisTemplate.opsForValue().set("loginUser:" + user.getUserId(), session.getId());
            response.getWriter().print("登錄成功");
        }
        else{
            throw new myException("登錄失敗");
        }}

    }
}

測試頁面代碼

package com.zyu.springandmybatis.controller;

import com.zyu.springandmybatis.entity.User;
import com.zyu.springandmybatis.service.UserService;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping(value="/user")
public class UserController {
    @Autowired
    private UserService userService;
    
    @RequestMapping(value = "/select")
    public String getUser(Model model){
       User user = userService.selectUserById(7,"test");
        model.addAttribute("user",user);
        return "test";
    }
}

test頁面

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>tomcat2</h2>
<p th:text="${user.userId}"></p>
<p th:text="${user.userName}"></p>
<p th:text="${user.userPassword}"></p>
<p th:text="${user.userEmail}"></p>
</body>
</html>

把項目打成war包放到linux上的tomcat1的webapps中,再重複一次這個操作把test.html 中的h2標籤的內容修改成tomcat2,放到tomcat2的webapps中.

最後啓動nginx和兩個tomcat服務器。
登錄:
在這裏插入圖片描述
訪問資源:

在這裏插入圖片描述
在這裏插入圖片描述
會發現請求會在兩臺tomcat服務器之間輪流進行訪問,因爲這裏我們的nginx負載均衡選擇的是輪詢的模式,同時在一臺服務器登錄後其他服務器就不用再次登錄了,除非session失效了,就需要從新進行登錄,同時不同的請求地址IP來訪問這個請求,會自動把其他的登錄踢下線,實現了session分佈式的共享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章