分布式Session一致性问题 跨域问题

分布式Session一致性问题

什么是Session

 

Session 是客户端与服务器通讯会话技术, 比如浏览器登陆、记录整个浏览会话信息

Session实现原理

客户对向服务器端发送请求后,Session 创建在服务器端,返回Sessionid给客户端浏览器保存在本地,当下次发送请求的时候,在请求头中传递sessionId获取对应的从服务器上获取对应的Sesison

 

 

Session常见问题

Session 保证在那里?

答案:存放在服务器上

关闭浏览器Session会失效吗

答案:不会消失

 

相关代码

@SpringBootApplication

@RestController

public class TestSessionController {

 

// 创建session 会话

@RequestMapping("/createSession")

public String createSession(HttpServletRequest request, String nameValue) {

HttpSession session = request.getSession();

System.out.println("存入Session  sessionid:信息" + session.getId() + ",nameValue:" + nameValue);

session.setAttribute("name", nameValue);

return "success";

}

 

// 获取session 会话

@RequestMapping("/getSession")

public Object getSession(HttpServletRequest request) {

HttpSession session = request.getSession();

System.out.println("获取Session sessionid:信息" + session.getId());

Object value = session.getAttribute("name");

return value;

}

 

public static void main(String[] args) {

SpringApplication.run(TestSessionController.class, args);

}

}

 

 

服务集群会产生那些问题

如果服务器产生了集群后,因为session是存放在服务器上,客户端会使用同一个Sessionid在多个不同的服务器上获取对应的Session,从而会导致Session不一致问题。

 

Nginx配置负载均衡

Nginx负载均衡提供上游服务器(真实业务逻辑访问的服务器),负载均衡、故障转移、失败重试、容错、健康检查等。

当上游服务器(真实业务逻辑访问的服务器)发生故障时,可以转移到其他上游服务器(真实业务逻辑访问的服务器)。

Upstream Server配置

upstream 主要配置如下:

IP地址和端口号:配置上游服务器的IP地址和端口

###定义上游服务器(需要被nginx真实代理访问的服务器) 默认是轮训机制

    upstream  backServer{

    server 127.0.0.1:8080;

    server 127.0.0.1:8081;

}

 

server {

        listen       80;

        server_name  www.itmayiedu.com;

        location / {

    ### 指定上游服务器负载均衡服务器

    proxy_pass http://backServer;

            index  index.html index.htm;

        }

    }

 

负载均衡算法

1、轮询(默认)

 每个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。

2、weight(轮询权值)

weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。

 3、ip_hash

        每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。俗称IP绑定。

 

  4、fair(第三方)

       比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。

 

  5、url_hash(第三方)

       按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身不支持url_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包。

 

@SpringBootApplication

@RestController

public class TestSessionController {

@Value("${server.port}")

private String serverPort;

 

@RequestMapping("/")

public String index() {

return serverPort;

}

 

// 创建session 会话

@RequestMapping("/createSession")

public String createSession(HttpServletRequest request, String nameValue) {

HttpSession session = request.getSession();

System.out.println(

"存入Session  sessionid:信息" + session.getId() + ",nameValue:" + nameValue + ",serverPort:" + serverPort);

session.setAttribute("name", nameValue);

return "success-" + serverPort;

}

 

// 获取session 会话

@RequestMapping("/getSession")

public Object getSession(HttpServletRequest request) {

HttpSession session = request.getSession(false);

if (session == null) {

return serverPort + "-" + "没有找到对应的session值";

}

System.out.println("获取Session sessionid:信息" + session.getId() + "serverPort:" + serverPort);

Object value = session.getAttribute("name");

return serverPort + "-" + value;

}

 

public static void main(String[] args) {

SpringApplication.run(TestSessionController.class, args);

}

}

 

分布式Session一致性解决方案

nginx或者haproxy实现IP绑定

用Nginx 做的负载均衡可以添加ip_hash这个配置,

用haproxy做的负载均衡可以用 balance source这个配置。

从而使同一个ip的请求发到同一台服务器。

利用数据库同步session

使用Session集群存放Redis

使用spring-session框架,底层实现原理是重写httpsession

引入maven依赖

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.0.0.RELEASE</version>

<relativePath /> <!-- lookup parent from repository -->

</parent>

<properties>

<weixin-java-mp.version>2.8.0</weixin-java-mp.version>

<maven.compiler.source>1.8</maven.compiler.source>

<maven.compiler.target>1.8</maven.compiler.target>

<maven.compiler.encoding>UTF-8</maven.compiler.encoding>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.build.locales>zh_CN</project.build.locales>

</properties>

 

<dependencies>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

<!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId> </exclusion> </exclusions> -->

</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.47</version>

</dependency>

<!-- Testing Dependencies -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<!--spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错 -->

<dependency>

<groupId>org.springframework.session</groupId>

<artifactId>spring-session-data-redis</artifactId>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-pool2</artifactId>

</dependency>

<dependency>

<groupId>redis.clients</groupId>

<artifactId>jedis</artifactId>

</dependency>

 

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.8</source>

<target>1.8</target>

</configuration>

</plugin>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<maimClass>com.meiteedu.WxMpApplication</maimClass>

</configuration>

<executions>

<execution>

<goals>

<goal>repackage</goal>

</goals>

</execution>

</executions>

 

</plugin>

</plugins>

</build>

YML配置信息

 

server:

  port: 8080

redis:

 hostname: 192.168.212.151

 port: 6379

 password: 123456

 

启动redis /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

 

创建SessionConfig

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

 

//这个类用配置redis服务器的连接

//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)

public class SessionConfig {

 

// 冒号后的值为没有配置文件时,制动装载的默认值

@Value("${redis.hostname:localhost}")

String HostName;

@Value("${redis.port:6379}")

int Port;

 

@Bean

public JedisConnectionFactory connectionFactory() {

JedisConnectionFactory connection = new JedisConnectionFactory();

connection.setPort(Port);

connection.setHostName(HostName);

return connection;

}

}

 

 

初始化Session

//初始化Session配置

public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{

    public SessionInitializer() {

        super(SessionConfig.class);

    }

}

 

最靠谱的分布式Session解决方案

基于令牌(Token)方式实现Session解决方案,因为Session本身就是分布式共享连接。

 

@Service

public class TokenService {

@Autowired

private RedisService redisService;

 

// 新增 返回token

public String put(Object object) {

String token = getToken();

redisService.setString(token, object);

return token;

}

 

// 获取信息

public String get(String token) {

String reuslt = redisService.getString(token);

return reuslt;

}

 

public String getToken() {

return UUID.randomUUID().toString();

}

 

}

 

TokenController

 

@RestController

public class TokenController {

@Autowired

private TokenService tokenService;

@Value("${server.port}")

private String serverPort;

 

@RequestMapping("/put")

public String put(String nameValue) {

String token = tokenService.put(nameValue);

return token + "-" + serverPort;

}

 

@RequestMapping("/get")

public String get(String token) {

String value = tokenService.get(token);

return value + "-" + serverPort;

}

}

 

 

 

网站跨域解决方案

 

 

什么是网站跨域

跨域原因产生:在当前域名请求网站中,默认不允许通过ajax请求发送其他域名。

 

网站跨域报错案例

 

jquery-1.7.2.min.js?t=2017-07-27:4 Failed to load http://b.itmayiedu.com:8081/ajaxB: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.itmayiedu.com:8080' is therefore not allowed access.

 

五种网站跨域解决方案

 

  1. 使用jsonp解决网站跨域 
     2.使用HttpClient内部转发
     3.使用设置响应头允许跨域
     4.基于Nginx搭建企业级API接口网关
     5.使用Zuul搭建微服务API接口网关

 

跨域项目环境搭建

 

使用JSONP解决网站跨域

 

前端代码

<script type="text/javascript"

src="http://www.itmayiedu.com/static/common/jquery-1.7.2.min.js?t=2017-07-27"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "GET",

async : false,

url : "http://b.itmayiedu.com:8081/ajaxB",

dataType : "jsonp",

jsonp : "jsonpCallback",//服务端用于接收callback调用的function名的参数

success : function(data) {

alert(data["errorCode"]);

},

error : function() {

alert('fail');

}

});

 

});

</script>

 

后端代码

@RequestMapping(value = "/ajaxB", method = RequestMethod.GET)

public void ajaxB(HttpServletResponse response, String jsonpCallback) throws IOException {

JSONObject root = new JSONObject();

root.put("errorCode", "200");

root.put("errorMsg", "登陆成功");

response.setHeader("Content-type", "text/html;charset=UTF-8");

PrintWriter writer = response.getWriter();

writer.print(jsonpCallback + "(" + root.toString() + ")");

writer.close();

}

 

缺点:不支持post请求,代码书写比较复杂

 

使用设置响应头允许跨域

 

前端代码

<script type="text/javascript"

src="http://www.itmayiedu.com/static/common/jquery-1.7.2.min.js?t=2017-07-27"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "GET",

async : false,

url : "http://b.itmayiedu.com:8081/ajaxB",

dataType : "json",

success : function(data) {

alert(data["errorCode"]);

},

error : function() {

alert('fail');

}

});

 

});

</script>

 

后端代码

@RequestMapping("/ajaxB")

public Map<String, Object> ajaxB(HttpServletResponse response) {

response.setHeader("Access-Control-Allow-Origin", "*");

Map<String, Object> result = new HashMap<String, Object>();

result.put("errorCode", "200");

result.put("errorMsg", "登陆成功");

return result;

}

response.setHeader("Access-Control-Allow-Origin", "*"); 设置响应头允许跨域

如果在实际项目中,该代码建议放在过滤器中。

 

 

使用HttpClient进行内部转发

前端代码

<script type="text/javascript"

src="http://www.itmayiedu.com/static/common/jquery-1.7.2.min.js?t=2017-07-27"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "POST",

async : false,

url : "http://a.itmayiedu.com:8080/forwardB",

dataType : "json",

success : function(data) {

alert(data["errorCode"]);

},

error : function() {

alert('fail');

}

});

 

});

</script>

 

 

后端代码

 

A项目进行转发到B项目

 

@RequestMapping("/forwardB")

@ResponseBody

public JSONObject forwardB() {

JSONObject result = HttpClientUtils.httpGet("http://b.itmayiedu.com:8081/ajaxB");

System.out.println("result:" + result);

return result;

}

 

B项目代码

 

@RequestMapping("/ajaxB")

public Map<String, Object> ajaxB(HttpServletResponse response) {

response.setHeader("Access-Control-Allow-Origin", "*");

Map<String, Object> result = new HashMap<String, Object>();

result.put("errorCode", "200");

result.put("errorMsg", "登陆成功");

return result;

}

 

搭建企业级API接口网关

使用Nginx搭建API接口网关

 Nginx相关配置

   server {

        listen       80;

        server_name  www.itmayiedu.com;

 

###A项目

        location /a {

            proxy_pass   http://a.itmayiedu.com:8080/;

            index  index.html index.htm;

        }

###B项目

 location /b {

            proxy_pass   http://b.itmayiedu.com:8081/;

            index  index.html index.htm;

        }

    }

 

 前端代码

<script type="text/javascript"

src="http://code.jquery.com/jquery-1.8.0.min.js"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "POST",

async : false,

url : "http://www.itmayiedu.com/b/ajaxB",

dataType : "json",

success : function(data) {

alert(data["errorCode"]);

},

error : function() {

alert('fail');

}

});

 

});

</script>

 

后端代码

@RequestMapping("/ajaxB")

public Map<String, Object> ajaxB(HttpServletResponse response) {

response.setHeader("Access-Control-Allow-Origin", "*");

Map<String, Object> result = new HashMap<String, Object>();

result.put("errorCode", "200");

result.put("errorMsg", "登陆成功");

return result;

}

 

 

SpringCloud搭建API接口网关

 

使用SpringCloud Zuul搭建API接口网关

 

 

 

 

 

Maven初始化依赖参数

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.0.0.RELEASE</version>

</parent>

<dependencies>

<!-- SpringBoot 对lombok 支持 -->

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

</dependency>

 

<!-- SpringBoot web 核心组件 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-tomcat</artifactId>

</dependency>

<!-- SpringBoot 外部tomcat支持 -->

<dependency>

<groupId>org.apache.tomcat.embed</groupId>

<artifactId>tomcat-embed-jasper</artifactId>

</dependency>

 

<!-- springboot-log4j -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-log4j</artifactId>

<version>1.3.8.RELEASE</version>

</dependency>

<!-- springboot-aop 技术 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-aop</artifactId>

</dependency>

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpclient</artifactId>

</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.47</version>

</dependency>

</dependencies>

 

application.yml

server:

  port: 8080

spring:

  mvc:

    view:

      prefix: /WEB-INF/jsp/

      suffix: .jsp

 

HttpClientUtils工具类

public class HttpClientUtils {

private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); // 日志记录

 

private static RequestConfig requestConfig = null;

 

static {

// 设置请求和传输超时时间

requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();

}

 

/**

 * post请求传输json参数

 *

 * @param url

 *            url地址

 * @param json

 *            参数

 * @return

 */

public static JSONObject httpPost(String url, JSONObject jsonParam) {

// post请求返回结果

CloseableHttpClient httpClient = HttpClients.createDefault();

JSONObject jsonResult = null;

HttpPost httpPost = new HttpPost(url);

// 设置请求和传输超时时间

httpPost.setConfig(requestConfig);

try {

if (null != jsonParam) {

// 解决中文乱码问题

StringEntity entity = new StringEntity(jsonParam.toString(), "utf-8");

entity.setContentEncoding("UTF-8");

entity.setContentType("application/json");

httpPost.setEntity(entity);

}

CloseableHttpResponse result = httpClient.execute(httpPost);

// 请求发送成功,并得到响应

if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

String str = "";

try {

// 读取服务器返回过来的json字符串数据

str = EntityUtils.toString(result.getEntity(), "utf-8");

// 把json字符串转换成json对象

jsonResult = JSONObject.parseObject(str);

} catch (Exception e) {

logger.error("post请求提交失败:" + url, e);

}

}

} catch (IOException e) {

logger.error("post请求提交失败:" + url, e);

} finally {

httpPost.releaseConnection();

}

return jsonResult;

}

 

/**

 * post请求传输String参数 例如:name=Jack&sex=1&type=2

 * Content-type:application/x-www-form-urlencoded

 *

 * @param url

 *            url地址

 * @param strParam

 *            参数

 * @return

 */

public static JSONObject httpPost(String url, String strParam) {

// post请求返回结果

CloseableHttpClient httpClient = HttpClients.createDefault();

JSONObject jsonResult = null;

HttpPost httpPost = new HttpPost(url);

httpPost.setConfig(requestConfig);

try {

if (null != strParam) {

// 解决中文乱码问题

StringEntity entity = new StringEntity(strParam, "utf-8");

entity.setContentEncoding("UTF-8");

entity.setContentType("application/x-www-form-urlencoded");

httpPost.setEntity(entity);

}

CloseableHttpResponse result = httpClient.execute(httpPost);

// 请求发送成功,并得到响应

if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

String str = "";

try {

// 读取服务器返回过来的json字符串数据

str = EntityUtils.toString(result.getEntity(), "utf-8");

// 把json字符串转换成json对象

jsonResult = JSONObject.parseObject(str);

} catch (Exception e) {

logger.error("post请求提交失败:" + url, e);

}

}

} catch (IOException e) {

logger.error("post请求提交失败:" + url, e);

} finally {

httpPost.releaseConnection();

}

return jsonResult;

}

 

/**

 * 发送get请求

 *

 * @param url

 *            路径

 * @return

 */

public static JSONObject httpGet(String url) {

// get请求返回结果

JSONObject jsonResult = null;

CloseableHttpClient client = HttpClients.createDefault();

// 发送get请求

HttpGet request = new HttpGet(url);

request.setConfig(requestConfig);

try {

CloseableHttpResponse response = client.execute(request);

 

// 请求发送成功,并得到响应

if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

// 读取服务器返回过来的json字符串数据

HttpEntity entity = response.getEntity();

String strResult = EntityUtils.toString(entity, "utf-8");

// 把json字符串转换成json对象

jsonResult = JSONObject.parseObject(strResult);

} else {

logger.error("get请求提交失败:" + url);

}

} catch (IOException e) {

logger.error("get请求提交失败:" + url, e);

} finally {

request.releaseConnection();

}

return jsonResult;

}

 

}

 

  1. Jsp

 

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

 

<script type="text/javascript"

src="http://code.jquery.com/jquery-1.8.0.min.js"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "POST",

async : false,

url : "http://www.itmayiedu.com/b/ajaxB",

dataType : "json",

success : function(data) {

alert(data["errorCode"]);

},

error : function() {

alert('fail');

}

});

 

});

</script>

</head>

<body>显示 ....

</body>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值