Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享

当我们通过nginx实现负载均衡,将用户的请求按照一定的规则分发到到不同的服务器上,就带来了,一个用户进行登陆,这个请求分发到服务器A上,当用户再次请求这个请求被分发到服务器B上,就会出现用户未登录的情况,虽然我们可以使用nginx的iphash策略一定程度上规避这种情况,但是ip_hash有些缺陷使它不能随便使用(如多台pc使用同一个外网ip)。
使用spring 提供的Spring session模块就可以很简单的实现多台服务器的session共享

spring session实现机制

当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建。将sessionID写入cookie—>将sessionID写入redis中并设置失效时间。当用户登陆时,读取cookie中的sessionID,根据sessionID在redis进行判断时候存在。从而实现session共享。

在Maven中引入Spring session所需jar

    <!-- spring session 单点登录 -->
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
      <version>1.2.0.RELEASE</version>
    </dependency>

配置web.xml

使用了Spring Web提供的代理过滤器,将拦截到的请求全部交给一个名为springSessionRepositoryFilter的过滤器进行处理

    <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

配置Spring session

<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>

    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="domainName" value=".happymmall.com" />
        <property name="useHttpOnlyCookie" value="true" />
        <property name="cookiePath" value="/" />
        <property name="cookieMaxAge" value="31536000" />
    </bean>
    <-- Jedis连接池时 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="20"/>
    </bean>
    <-- 配置redis 参数-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="127.0.0.1" />
        <property name="port" value="6379" />
        <property name="poolConfig" ref="jedisPoolConfig" />
    </bean>

到这整个 Spring session就配置完了,是不是很简单。

下面再介绍一种原生的实现,原理都是一样的,简单的画了一个草图
实现的思路就是通过配置Cookie的domain为一级域名,不管用户登陆被请求到A,B,C中的任何一台服务器上,都会在用户登陆之后在本地存储Cookie,我们定义一个自定义的字符为key,sessionid为value,同时将用户对象序列化之后缓存在redis中并设置有效期。之后我们只需要在cookie中取到自定义字符串对应的sessionid,然后根据sessionid去缓存中去查询对应的数据是否存在
这里写图片描述
好草的图

Redis+Cookie+Jackson+Filter实现单点登录

首先通过maven导入所需jar包

<dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-avro</artifactId>
      <version>2.9.0</version>
    </dependency>

创建redis连接池

package com.mmall.common;

import com.mmall.util.PropertiesUtil;
import redis.clients.jedis.*;
import redis.clients.util.Hashing;
import redis.clients.util.ShardInfo;
import redis.clients.util.Sharded;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by king on 2018/5/24.
 */
public class RedisShardedPool {
    private static ShardedJedisPool pool; // Sharded jedis连接池

    private static  Integer maxTotal= Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20"));// 最大连接数
    private static  Integer maxIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态的jedis连接空闲实例
    private static  Integer minIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态的jedis连接空闲实例
    private static  Boolean testOnBorrow= Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));
    private static  Boolean testOnReturn= Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));

    private static  String ip1 = PropertiesUtil.getProperty("redis1.ip");
    private static  Integer port1 = Integer.valueOf(PropertiesUtil.getProperty("redis1.port"));
    private static  String ip2 = PropertiesUtil.getProperty("redis2.ip");
    private static  Integer port2 = Integer.valueOf(PropertiesUtil.getProperty("redis2.port"));

    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(true); // 当连接耗尽时,是否阻塞等待,fasle会抛出异常
        JedisShardInfo info = new JedisShardInfo(ip1,port1,1000*2);
        JedisShardInfo info2= new JedisShardInfo(ip2,port2,1000*2);
        List<JedisShardInfo> shardedJedisList = new ArrayList<>();
        shardedJedisList.add(info);
        shardedJedisList.add(info2);
        pool= new ShardedJedisPool(config,shardedJedisList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
    }


    static {

        initPool();

    }

    public   static ShardedJedis getJedis(){

        return  pool.getResource();

    }
    public static  void returnJedis(ShardedJedis jedis){

        pool.returnResource(jedis);
    }
    public static  void  returnBrokenJedis(ShardedJedis jedis){
        pool.returnBrokenResource(jedis);
    }


}

redis工具类

package com.mmall.common;

import com.mmall.util.PropertiesUtil;
import redis.clients.jedis.*;
import redis.clients.util.Hashing;
import redis.clients.util.ShardInfo;
import redis.clients.util.Sharded;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by king on 2018/5/24.
 */
public class RedisShardedPool {
    private static ShardedJedisPool pool; // Sharded jedis连接池

    private static  Integer maxTotal= Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20"));// 最大连接数
    private static  Integer maxIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态的jedis连接空闲实例
    private static  Integer minIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态的jedis连接空闲实例
    private static  Boolean testOnBorrow= Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));
    private static  Boolean testOnReturn= Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));

    private static  String ip1 = PropertiesUtil.getProperty("redis1.ip");
    private static  Integer port1 = Integer.valueOf(PropertiesUtil.getProperty("redis1.port"));
    private static  String ip2 = PropertiesUtil.getProperty("redis2.ip");
    private static  Integer port2 = Integer.valueOf(PropertiesUtil.getProperty("redis2.port"));

    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(true); // 当连接耗尽时,是否阻塞等待,fasle会抛出异常
        JedisShardInfo info = new JedisShardInfo(ip1,port1,1000*2);
        JedisShardInfo info2= new JedisShardInfo(ip2,port2,1000*2);
        List<JedisShardInfo> shardedJedisList = new ArrayList<>();
        shardedJedisList.add(info);
        shardedJedisList.add(info2);
        pool= new ShardedJedisPool(config,shardedJedisList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
    }


    static {

        initPool();

    }

    public   static ShardedJedis getJedis(){

        return  pool.getResource();

    }
    public static  void returnJedis(ShardedJedis jedis){

        pool.returnResource(jedis);
    }
    public static  void  returnBrokenJedis(ShardedJedis jedis){
        pool.returnBrokenResource(jedis);
    }


}

Cookie工具类

package com.mmall.util;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by king on 2018/5/20.
 */
@Slf4j
public class CookieUtil {

    private  final  static  String COOKIE_DOMAIN=".imooc1.com";

    private final  static  String COOKIE_NAME = "mmall_login_token";



    //写入cookie
    public static void  writeLoginToken(HttpServletResponse response ,String token){

        Cookie cookie = new Cookie(COOKIE_NAME,token);
        cookie.setDomain(COOKIE_DOMAIN);
        cookie.setPath("/");
        cookie.setHttpOnly(true);// 不许通过脚本访问cookie
        //单位是秒
        cookie.setMaxAge(60*60*24*365); // 设置有效期,-1 是永久,如果不设置 cookie不会写入硬盘,只写在内存
        log.info("write cookieName{},cookieValue{}",cookie.getName(),cookie.getValue());
        response.addCookie(cookie);


    }


    public static  String getCookie(HttpServletRequest request){
        Cookie [] cks = request.getCookies(); // 从request中读取cookie

        if (cks!=null){
            for (Cookie cookie: cks){
                log.info("read  cookieName{},CookieValue{}",cookie.getName(),cookie.getValue());
                if (cookie.getName().equals(COOKIE_NAME)){

                    log.info("return   cookieName{},CookieValue{}",cookie.getName(),cookie.getValue());
                    return cookie.getValue();
                }
            }
        }


        return  null;
    }

    public static  void  delLoginToken(HttpServletRequest request,HttpServletResponse response){
        Cookie [] cks =request.getCookies();
        if (cks!=null){
            for (Cookie cookie: cks) {
                if (cookie.getName().equals(COOKIE_NAME)){
                    cookie.setDomain(COOKIE_DOMAIN);
                    cookie.setPath("/");
                    cookie.setMaxAge(0);// 如果设置时间为0 ,代表删除cookie

                    log.info("del cookie  cookieName{},cookieValue" ,cookie.getName(),cookie.getValue());
                    response.addCookie(cookie);

                    return;

                }

            }
        }
    }


}

使用jackson序列化工具类

package com.mmall.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import sun.nio.cs.ext.IBM037;

import java.io.IOException;
import java.text.SimpleDateFormat;

/**
 * Created by king on 2018/5/18.
 */

@Slf4j
public class JsonUtil {


    private static ObjectMapper objectMapper = new ObjectMapper();


    static {

        //对象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
        // 取消默认转换timestamps形式
        objectMapper.configure(SerializationConfig.Feature.WRITE_DATE_KEYS_AS_TIMESTAMPS,false);
        //忽略空bean转json的错误
        objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
        //日期格式统一为以下格式
        objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
        //忽略在json字符串中存在,在java属性中不存在的情况。防止错误
        objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
    }


    public static <T> String obj2String(T obj){

        if (obj==null){
            return  null;
        }
        try {
            return obj instanceof String ? (String) obj :objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("Parse object to String error",e);
            return  null;
        }
    }


    // 返回格式化之后的字符串
    public static <T> String obj2StringPretty(T obj){

        if (obj==null){
            return  null;
        }
        try {
            return obj instanceof String ? (String) obj :objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("Parse object to String error",e);
            return  null;
        }
    }


    public  static  <T> T string2Obj(String str,Class <T>clazz){
        if (StringUtils.isEmpty(str)||clazz ==null){
            return  null;
        }
        try {

            return  clazz.equals(String.class)?(T)str:objectMapper.readValue(str,clazz);
        }catch (Exception e){
            log.warn("Parse str to object error",e);
            return  null;
        }

    }

    public  static  <T> T string2Obj(String str, TypeReference<T> reference){
        if (StringUtils.isEmpty(str)||reference ==null){
            return  null;
        }
        try {

            return (T) (reference.getType().equals(String.class)?(T)str:objectMapper.readValue(str,reference));
        }catch (Exception e){
            log.warn("Parse str to object error",e);
            return  null;
        }

    }


    public  static  <T> T string2Obj(String str,Class<?> collectionClass,Class<?>...elecmentClassess){
        JavaType javatype= objectMapper.getTypeFactory().constructParametricType(collectionClass,elecmentClassess);
        try {

            return  objectMapper.readValue(str,javatype);
        }catch (Exception e){
            log.warn("Parse str to object error",e);
            return  null;
        }

    }

}

用户登陆

  /**
     * 用户登陆
     *
     * @param userName
     * @param password
     * @return
     */
    @RequestMapping(value = "login.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String userName, String password, HttpSession session, HttpServletResponse response1) {
        ServerResponse<User> response = iUserService.checkUserName(userName, password);
        if (response.isSuccess()) {
            CookieUtil.writeLoginToken(response1,session.getId());
            RedisShardedPoolUtil.setex(session.getId(), JsonUtil.obj2StringPretty(response.getData()),60*30);
        }
        return response;

    }



    /**
     * 用户退出登陆
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "logout.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> loginout(HttpServletRequest request,HttpServletResponse response) {

        String loginToken =CookieUtil.getCookie(request);
        //session.removeAttribute(Const.CUREENT_USER);
        CookieUtil.delLoginToken(request,response);
        RedisShardedPoolUtil.del(loginToken);
        return ServerResponse.createBySuccess();

    }
    @RequestMapping(value = "get_user_info.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> getUserInfo(HttpServletRequest httpServletRequest){

        String loginToken = CookieUtil.readLoginToken(httpServletRequest);
        if(StringUtils.isEmpty(loginToken)){
            return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
        }
        String userJsonStr = RedisShardedPoolUtil.get(loginToken);
        User user = JsonUtil.string2Obj(userJsonStr,User.class);

        if(user != null){
            return ServerResponse.createBySuccess(user);
        }
        return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值