概述
当我们通过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("用户未登录,无法获取当前用户的信息");
}
最后
以上就是无语河马为你收集整理的Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享的全部内容,希望文章能够帮你解决Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复