共计 7310 个字符,预计需要花费 19 分钟才能阅读完成。
一、Cookie 机制和 Session 机制回顾
1)定义:Session 成为“会话”,具体是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间。Session 实际上是一个特定的时间概念。
2)HTTP 协议与状态保持:HTTP 协议本身是无状态的,这与 HTTP 协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。
cookie 的作用就是为了解决 HTTP 协议无状态的缺陷。至于后来出现的 session 机制则是又一种在客户端与服务器之间保持状态的解决方案。
3)Cookie 和 Session 机制的区别和联系(几个有趣的例子):
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。(Cookie 原理)
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。(Session 原理)
由于 HTTP 协议是无状态的,cookie 机制采用的是在客户端保持状态的方案,而 session 机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以 session 机制可能需要借助于 cookie 机制来达到保存标识的目的,但实际上它还有其他选择。
二、集群下实现 Session 共享的几种方案
1. 请求精确定位:基于 IP 地址的 Hash 策略,将同一用户的请求都集中在一台服务器上,这台服务器上保存了该用户的 Session 信息。缺点:单点部署发生宕机时,Session 丢失。
2.Session 复制共享:比如可以用 Tomcat 自带的插件进行 Session 同步,使得多台应用服务器之间自动同步 Session,保持一致。如果一台发生故障,负载均衡会遍历寻找可用节点,Session 也不会丢失。缺点:必须是 Tomcat 和 Tomcat 之间,Session 的复制也会消耗系统 的性能,使得同步给成员时容易造成内网流量瓶颈。
3. 基于 cache DB 缓存的 Session 共享(推荐,Spring-Session 也是同样的原理,同自定义的 JRedis 一起配置可以实现目的): 使用 Redis 存取 Session 信息,应用服务器发生故障时,当 Session 不在内存中时就会去 CacheDB 中查找(要求 Redis 支持持久化),找到则复制到本机,实现 Session 共享和高可用。
分布式 Session 配置原理图
其他方式:利用公共的 NFS 服务器做共享服务器、完全利用 Cookie(将 Session 数据全放在 Cookie 中) 等。
三、基于 Redis 的 Session 共享实现(核心代码)
1)原理:写一个 Session 过滤器拦截每一次请求,在这里检查由 Cookie 生成的 SessionID,进行创建或获取。核心是实现使用装饰类,实现 Session 在 Redis 中的存取操作。
2)此处存取方式为 sessionID+sessionKey 作为 Redis 的 key ==== sessionValue 作为 Redis 的 value,这样保存了每次存取都从 Redis 中操作,效率更高。
3)注意:序列化方式推荐使用 Apache 下 Commons 组件——SerializationUtils 或 org.springframework.util.SerializationUtils
1 // 定义请求经过的 Session 过滤器
2 public class SessionFilter extends OncePerRequestFilter implements Filter { 3 @Override
4 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
5 throws ServletException, IOException { 6 // 从 cookie 中获取 sessionId,如果此次请求没有 sessionId,重写为这次请求设置一个 sessionId
7 String sid = CookieUtil.getCookieValue(request, GlobalConstant.JSESSIONID);
8 if (StringUtils.isEmpty(sid) || sid.length() != 36) { 9 sid = UUID.randomUUID().toString();
10 CookieUtil.setCookie(request, response, GlobalConstant.JSESSIONID, sid, 60 * 60);
11 }
12 // 交给自定义的 HttpServletRequestWrapper 处理
13 filterChain.doFilter(new HttpServletRequestWrapper(sid, request, response), response);
14 }
15 }
1 //Cookie
2 public static void setCookie(HttpServletRequest request,
3 HttpServletResponse response, String name, String value, int seconds) { 4 if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value))
5 return;
6 Cookie cookie = new Cookie(name, value);
7 //cookie.setDomain(domain);
8 cookie.setMaxAge(seconds);
9 cookie.setPath("/");
10 response.setHeader("P3P",
11 "CP='IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT'");
12 response.addCookie(cookie);
13 }
14
15 public String getCookieValue(String name)
16 throws UnsupportedEncodingException {17 Cookie cookies[] = request.getCookies();
18 if (cookies != null) {19 for (int i = 0; i < cookies.length; i++) {20 if (name.equalsIgnoreCase(cookies[i].getName())) {21 return cookies[i].getValue();
22 }
23 }
24 }
25 return "";
26 }
1 //SessionService 实现 sidKey == sessionID+SessionKey
2 public Object getSession(String sidKey) { 3 Object realValue = null;
4 try { 5 String key =“SESSION_DISTRIBUTED_SESSIONID”+ sidKey;
6 realValue = SerializeUtil.unserialize(RedisUtils.getInstance().get(key.getBytes()));
7 } catch (Exception e) { 8 LOG.error("Redis 获取 session 异常" + e.getMessage(), e.getCause());
9 }
10 return realValue;
11 }
12
13 public void saveSession(String sidKey, Object value) {14 try {15 String key =“SESSION_DISTRIBUTED_SESSIONID”+ sidKey;
16 boolean isSetSuccess = RedisUtils.getInstance().set(key.getBytes(), SerializeUtil.serialize(value));
17 if (!isSetSuccess) {18 LOG.error("Redis 保存 session 异常");
19 }
20 } catch (Exception e) {21 LOG.error("Redis 保存 session 异常" + e.getMessage(), e.getCause());
22 }
23 }
24
25 public void removeSession(String sidKey) {26 try {27 String key =“SESSION_DISTRIBUTED_SESSIONID”+ sidKey;
28 RedisUtils.getInstance().del(key.getBytes());
29 } catch (Exception e) {30 LOG.error("Redis 删除 session 的 attribute 异常" + e.getMessage(), e.getCause());
31 }
32 }
33
34 public void removeAllSession(String sid) {35 try {36 String keyPattern =“SESSION_DISTRIBUTED_SESSIONID”+ sid + "*";
37 Set<byte[]> keys = RedisUtils.getInstance().keys(keyPattern.getBytes());
38 for (byte[] key : keys) {39 RedisUtils.getInstance().del(key);
40 }
41 } catch (Exception e) {42 LOG.error("Redis 删除 session 异常" + e.getMessage(), e.getCause());
43 }
44 }
45
46 public Set<String> getAllKeys(String sid) {47 try {48 Set<String> keysResult = new HashSet<String>();
49 String keyPattern =“SESSION_DISTRIBUTED_SESSIONID”+ sid + "*";
50 Set<byte[]> keys = RedisUtils.getInstance().keys(keyPattern.getBytes());
51
52 for (byte[] key : keys) {53 keysResult.add(new String(key));
54 }
55 return keysResult;
56 } catch (Exception e) {57 LOG.error("Redis 删除 session 异常" + e.getMessage(), e.getCause());
58 return null;
59 }
60 }
1 HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper
2 private HttpSession session;
3
4 private HttpServletRequest request;
5
6 private HttpServletResponse response;
7
8 private String sid = "";
9
10 public HttpServletRequestWrapper(HttpServletRequest request) {11 super(request);
12 }
13
14 public HttpServletRequestWrapper(String sid, HttpServletRequest request) {15 super(request);
16 this.sid = sid;
17 }
18
19 public HttpServletRequestWrapper(String sid, HttpServletRequest request, HttpServletResponse response) {20 super(request);
21 this.request = request;
22 this.response = response;
23 this.sid = sid;
24 if (this.session == null) {25 this.session = new HttpSessionWrapper(sid, super.getSession(false), request, response);
26 }
27 }
28
29 @Override
30 public HttpSession getSession(boolean create) {31 if (this.session == null) {32 if (create) {33 this.session = new HttpSessionWrapper(this.sid, super.getSession(create), this.request, this.response);
34 return this.session;
35 } else {36 return null;
37 }
38 }
39 return this.session;
40 }
41
42 @Override
43 public HttpSession getSession() {44 if (this.session == null) {45 this.session = new HttpSessionWrapper(this.sid, super.getSession(), this.request, this.response);
46 }
47 return this.session;
48 }
1 HttpSessionWrapper implements HttpSession{ 2
3 private String sid = "";
4
5 private HttpSession session;
6
7 private HttpServletRequest request;
8
9 private HttpServletResponse response;
10
11 private SessionService sessionService = (SessionService) SpringContextHolder.getBean("sessionService");
12
13 public HttpSessionWrapper() {14 }
15
16 public HttpSessionWrapper(HttpSession session) {17 this.session = session;
18 }
19
20 public HttpSessionWrapper(String sid, HttpSession session) {21 this(session);
22 this.sid = sid;
23 }
24
25 public HttpSessionWrapper(String sid, HttpSession session,
26 HttpServletRequest request, HttpServletResponse response) {27 this(sid, session);
28 this.request = request;
29 this.response = response;
30 }
31
32
33 @Override
34 public Object getAttribute(String name) {35 return sessionService.getSession(this.sid+"#"+name);
36 }
37
38 @Override
39 public void setAttribute(String name, Object value) {40 sessionService.saveSession(this.sid+"#"+name, value);
41 }
42
43 @Override
44 public void invalidate() {45 sessionService.removeAllSession(this.sid);
46 CookieUtil.removeCookieValue(this.request,this.response, GlobalConstant.JSESSIONID);
47 }
48
49 @Override
50 public void removeAttribute(String name) {51 sessionService.removeSession(this.sid+"#"+name);
52 }
53
54 @Override
55 public Object getValue(String name) {56 return this.session.getValue(name);
57 }
58
59 @SuppressWarnings("unchecked")
60 @Override
61 public Enumeration getAttributeNames() {62 return (new Enumerator(sessionService.getAllKeys(this.sid), true));
63 }
64
65 @Override
66 public String getId() {67 return this.sid;
68 }
69 }
本文永久更新链接地址 :http://www.linuxidc.com/Linux/2017-01/139867.htm
正文完
星哥说事-微信公众号