小谈mmall架构演进

上回书和上上回书说到redis的用法还有在代码里怎么操作Redis数据库,学完了得用啊。怎么用啊?这得从项目架构说起了。
mmall是一个简单的用SSM搭建起来的基本只能本地玩耍的电商DEMO,最简单的架构版本V1.0是这样婶的:
user123访问网址发送请求,nginx把请求发送到Tomcat,Tomcat再去访问数据库或者ftpserver;session保存在Tomcat里;
如果是访问人少,一台服务器当然可以顶得住,请求服务器的多了,我们可以给这个服务器升级,提高它的纵向扩展能力:升级机器的内存,CPU,硬盘机械改固态但带来的还有成本指数级升高。
升级一台服务器成本高,那用几个普通的服务器做成集群不就可以了吗?于是,就有了下面的机构:
这个架构版本,称为V1.1版本吧。
这个版本看上去没毛病,实际是不能使用的。想象下这个业务场景:userA登录请求,nginx发送给TomcatA;用户再进行下单请求,Nginx发送给TomcatB;B里没有session,A里有session,这里就会校验到用户未登录,实际上用户登录了,session保存在A里,请求的却是B。
既然这个版本不能满足业务需求,就继续升级吧,于是就有了下面的版本V2.0:

user无论请求到哪台服务器,我们都会把session信息放到redis session server上。tomcat服务器都会从Redis session server上读取session信息。
这样一来,Session登录信息存储及读取的问题就解决了。但是还有一个问题:服务器定时任务并发的问题。这个问题怎么产生的?怎么解决?请看下回分解。

  • todo 服务器定时任务并发问题的解决
    左下角的Token是个小彩蛋,下面会说到。
    Tomcat集群能提高服务的性能,并发能力,以及高可用性;
    实际中一台服务器只部署一个Tomcat,因为机器硬件有瓶颈(内存,硬盘IO等);
    一台Tomcat的HTTP线程池是有限的多个Tomcat,多个线程池,并发能力提高;
    活多了一个人做不完怎么办?摇人就是了。
    请求多了服务器要挂掉了怎么办?做成集群就是了。
空谈误国,代码兴邦。

代码解析

util,util还是Util。util是什么?是你写完永远不会去看源码拿来就用的东西。
cookie的path,domain属性没有深入研究,其实要是不做这个DEMO,cookie都碰不到。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
public class CookieUtil {private final static String COOKIE_DOMAIN = ".happymmall.com";private final static String COOKIE_NAME = "mmall_login_token";// 把request对象里的所有cookie保存到cookie数组里,遍历数组拿到指定的cookie后返回其值public static String readLoginToken (HttpServletRequest request) {Cookie[] cks = request.getCookies();if (cks != null) {for (Cookie ck : cks) {log.info("read cookieName:{}, cookieValue:{}", ck.getName(), ck.getValue());if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {log.info("return cookieName:{}, cookieValue:{}", ck.getName(), ck.getValue());return ck.getValue();}}}return null;}// 根据传入的tooken新建cookie对象,将其保存到response对象里public static void writeLoginToken (HttpServletResponse response, String token) {Cookie ck = new Cookie(COOKIE_NAME, token);ck.setDomain(COOKIE_DOMAIN);ck.setPath("/"); // 代表设置在根目录ck.setHttpOnly(true); // 不能通过脚本访问cookie// 如果maxage不设置,cookie就不会写入硬盘而是写在内存里,只在当前页面有效ck.setMaxAge(60 * 60 * 24 * 265);// -1 永久 单位是秒log.info("write cookieName:{}, cookieValue: {}", ck.getName(), ck.getValue());response.addCookie(ck);}// 从request里拿到cookie数组,找到指定的cookie,设置删除后添加到response里public static void delLoginToken (HttpServletRequest request, HttpServletResponse response) {Cookie[] cks = request.getCookies();if (cks != null) {for (Cookie ck : cks) {if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {ck.setDomain(COOKIE_DOMAIN);ck.setPath("/");ck.setMaxAge(0);// 0为删除cookielog.info("del cookieName:{}, cookieValue:{}", ck.getName(), ck.getValue());response.addCookie(ck);return;}}}}
}

接下来看个登录接口,看下是怎么实现单点登录的。
小白千万不要怕,心理千万不要这么想:(唉,单点登录啊,这是什么流弊的技术啊这个该怎么用啊等等心中一万个曹尼玛。)我只想说,娃儿,莫慌,莫慌。莫慌。
知道什么是服务器吧?服务器是什么?说白了就是一个电脑,不过性能高些而已。
知道什么是部署吧?部署是什么?说白了就是复制粘贴,把自己的本地代码放到服务器上运行跑起来。
有些词看起来高大上,其实并没有。有些听上去高大上的东西是骗投资人和消费者的,IT行业的高大上的东西,是老鸟们装逼用的。装逼是人类的刚需。
userA在登录操作的时候,nginx把请求分发到了tomcatA,在A里把cookie保存到客户端,用户信息保存到redis里。A服务器的cookie只能在A里使用,为了让集群服务器都能使用,cookie的域名(domain属性)在util里写死掉,这样所有以写死的域名结尾的域名都能访问该域名。多个服务器共享一个cookie,DEMO里单点登录用的还是共享cookie。

todo 研究下真正的单点登录:
https://blog.csdn.net/qq_40241957/article/details/88371061

对照着V1.0架构图和V2.0架构图,我们可以清楚的看到,原来的session是保存在tomcat服务器里的,现在放在了redis里面,redis成了"session server"。
V1.0的登录接口:

    @RequestMapping(value = "login.do",method = RequestMethod.POST)@ResponseBodypublic ServerResponse<User> login (String username, String password, HttpSession session){ServerResponse<User> response = iUserService.login(username,password);if (response.isSuccess()){session.setAttribute(Const.CURRENT_USER,response.getData());}return response;}

V2.0的登录接口:

    @RequestMapping(value = "login.do",method = RequestMethod.POST)@ResponseBodypublic ServerResponse<User> login (String username, String password, HttpSession session,HttpServletResponse httpServletResponse){//HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest){//service-->mybatis-->daoServerResponse<User> response = iUserService.login(username,password);if (response.isSuccess()){// 重构后的对业务代码仍然有侵入// todo 使用spring session 进行解耦CookieUtil.writeLoginToken(httpServletResponse, session.getId());// 本来保存在session里的用户信息 放到数据库里 有效期 30minRedisPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);}return response;}

用户登录后,在页面上点点点,买买买,和后台交互的不亦乐乎,像这样:

    /*** 获取用户信息* @return*/@RequestMapping(value = "get_user_info.do",method = RequestMethod.POST)@ResponseBodypublic ServerResponse<User> getUserInfo(HttpServletRequest httpServletRequest){//User user = (User) session.getAttribute(Const.CURRENT_USER);String loginToken = CookieUtil.readLoginToken(httpServletRequest);if (StringUtils.isEmpty(loginToken)) {return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");}String userJsonStr = RedisPoolUtil.get(loginToken);User user = JsonUtil.string2Obj(userJsonStr, User.class);if (user != null){return ServerResponse.createBySuccess(user);}return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");}

但是别忘了在登录的时候,我们redis保存的用户信息是有有效期的:

RedisPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);

时间半小时。半小时啊半小时,也就是用户最多只能玩耍半小时过了半小时就得重新登录。怎么办?能不能改下代码,加个东西让用户每请求一次后台就重置下用户的有效期?
当然可以。那个东西叫拦截器:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;public class SessionExporeFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Override// 拦截器,用户登录后每次请求后台都会重置token有效期为30minpublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;String loginToken = CookieUtil.readLoginToken(httpServletRequest);if (StringUtils.isNotEmpty(loginToken)) {String userJsoinStr = RedisPoolUtil.get(loginToken);User user = JsonUtil.string2Obj(userJsoinStr, User.class);if (user != null) {RedisPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);}}filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {}
}

web.xml里也要配置下:

    <filter><filter-name>sessionExpireFilter</filter-name><filter-class>com.mmall.controller.common.SessionExporeFilter</filter-class></filter><filter-mapping><filter-name>sessionExpireFilter</filter-name><url-pattern>*.do</url-pattern></filter-mapping>

好了,功能实现了,接下来该做什么呢?
奥,还有一个彩蛋。
这个彩蛋涉及到用户重置密码这个业务。
最简单的前台页面是,用户点击忘记密码,输入用户名,问题答案,点击提交后输入新的密码就能重置密码;在后台涉及到两个接口:忘记密码&重置密码。简单地回顾下逻辑:(详细的请移步mallV1.0)
mmallV1.0
1 忘记密码checkAnswer
传入用户名 问题 答案 校验
设置有有效期的 forgetToken token放在服务器的 GuavaCache 里
返回forgetToken
token的作用是 防止其他人拿到这个forgetToken去恶意请求接口修改他人的密码
2 重置密码 forgetResetPassword
传入用户名 新密码 forgetToken 校验
forgetToken超过了有效期就会被清除 以此校验前端传来的forgetToken
校验成功后 修改密码
现在集群来了,原来的不能再放到服务器上了:
1 用户点击忘记密码,请求服务器1,它将token保存在本服务器的 GuacaCache 里
2 用户提交新密码 请求服务器2,这个服务器里的 GuavaCache 里没有保存 第一步的 token 提交就会报错 token无效或者过期
怎么办?当然还是把将生成的token放到redis里:

            String forgetToken = UUID.randomUUID().toString();// 原来的 token 保存在服务器上//TokenCache.setKey(TokenCache.TOKEN_PREFIX+username,forgetToken);// 现在放到redis里面RedisPoolUtil.setEx(Const.TOKEN_PREFIX+username, forgetToken, 60*30);

用的时候也是从redis里读:

     //String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX+username);String token = RedisPoolUtil.get(Const.TOKEN_PREFIX+username);

项目地址如下,分支用dev:

https://github.com/SilentJhin/mmall/tree/dev

收工。
自己写的东西一定是有不完善的地方(内容,技术,代码等等),如果有看不惯的,来评论。我一定回。

Demo_mmall v2.0 (四) Tomcat集群演进及使用Redis进行session重构实现单点登录相关推荐

  1. Apache+Tomcat集群负载均衡的两种session处理方式

    session共享有两种方式: 1.session共享,多个服务器session拷贝保存,一台宕机不会影响用户的登录状态: 2.请求精确集中定位,即当前用户的请求都集中定位到一台服务器中,这样单台服务 ...

  2. 实践中整理出tomcat集群和负载均衡

    实践中整理出tomcat集群和负载均衡 来源:http://macrochen.blogdriver.com/macrochen/1207263.html (一)环境说明 (1)服务器有4台,一台安装 ...

  3. 《架构系列四:一键部署应用到Tomcat集群中》

    <架构系列四:一键部署应用到Tomcat集群中> 基于前面的<架构系列三:使用Keepalived+Nginx+tomcat实现集群部署>,我们配置了nginx+Tomcat集 ...

  4. Tomcat 集群中 实现session 共享的三种方法

    前两种均需要使用 memcached 或 redis 存储 session ,最后一种使用 terracotta 服务器共享.  建议使用 redis ,不仅仅因为它可以将缓存的内容持久化,还因为它支 ...

  5. Tomcat 集群问题

    参考:http://www.jfox.info/guan-yu-java-tomcat-ji-qun-de-mian-shi-wen-ti 在java工程师面试过程中,一些比较厉害的技术经理经常会问到 ...

  6. 关于 tomcat 集群中 session 共享的三种方法

    前两种均需要使用 memcached 或 redis 存储 session ,最后一种使用 terracotta 服务器共享. 建议使用 redis ,不仅仅因为它可以将缓存的内容持久化,还因为它支持 ...

  7. Apache负载均衡+Tomcat集群

    http://weijie.blog.51cto.com/340746/68195 APACHE 2.2.8+TOMCAT6.0.14配置负载均衡 目标: 使用 apache 和 tomcat 配置一 ...

  8. 通向架构师的道路(第五天)之tomcat集群-群猫乱舞

    一.为何要集群 单台App Server再强劲,也有其瓶劲,先来看一下下面这个真实的场景. 当时这个工程是这样的,tomcat这一段被称为web zone,里面用spring+ws,还装了一个jbos ...

  9. Nginx实现tomcat集群进行负载均衡

    一.背景 随着业务量和用户数量的激增,单一的tomcat部署应用已经无法满足性能需求,而且对于每次发布项目期间服务不可用的问题也凸显,既然出现了这个问题,那么我们本文就借助nginx来完美的解决这个问 ...

最新文章

  1. Python 中常见的配置文件写法
  2. 常见的神经网络求导总结!
  3. 欧拉路径 之 poj 2513 Colored Sticks
  4. 问:一行Python代码到底能干多少事情?(一)
  5. [BUUCTF-pwn]——bjdctf_2020_babystack2
  6. (七)HTML和CSS 、JavaScript 和Java到底有什么区别,今天终于明白了!!!
  7. 02-对图像进行边界填充
  8. 【MFC学习笔记-作业7-小型画图软件】【】
  9. java8 64_【java8下载】Java8最新版下载 64位-七喜软件园
  10. Fortran入门教程(六)——循环结构
  11. 何凯明新作ViTDET:目标检测领域,颠覆分层backbone理念
  12. 0805,1206等封装尺寸
  13. 桂林理工大学 就业指导 2021 创业项目计划书样本
  14. c语言小球碰壁,小球碰壁效果
  15. 工厂模式及在Spring中的应用
  16. Java DateUtil 时间工具类
  17. 普通索引 唯一索引 主键索引 组合索引 全文索引
  18. iOS上传AppStore所需各种图片尺寸
  19. Git教程 | (9) 自定义Git和使用SourceTree
  20. 发现一个名为“Douyu”的国人项目

热门文章

  1. nightwatch 自定义断言
  2. 用PHP做一个校园论坛源代码,php校园BBS论坛网站
  3. 【人在运维囧途_03】20个Linux系统内置监视工具: w 和 ps
  4. Windows dhcp server option43选项配置简介
  5. 【分享】AI绘画提示词生成器工具
  6. 达梦数据库综合监控方案
  7. 牵手新潮传媒,独角兽们彰显社区“狼群效应”
  8. Mozilla浏览器和应用程序的整合---第一步,显示
  9. HTML5标签使用的常见误区
  10. c语言神经网络基本代码大全及其含义