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

article/2025/10/31 1:14:48

小谈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

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


http://chatgpt.dhexx.cn/article/jv2oUYwF.shtml

相关文章

自学实践前后端项目4 MMall商城 2

一。搭建静态页面 1)UserController里面实现登录操作 Autowired private UserService userService;PostMapping("/login") public String login(String loginName, String password, HttpSession session){QueryWrapper wrapper new QueryWrapper();wrapper.eq(&q…

mmall用户模块

mmall用户模块 user数据表设计用户模块接口文档服务端响应对象&#xff08;ServerResponse< T>&#xff09;响应对象封装以下3个属性判断响应是否成功私有化构造函数&#xff0c;对外暴露静态方法返回所需要的响应对象&#xff0c;例如&#xff1a;响应成功响应失败 Resp…

自学实践前后端项目4 MMall商城 7

一。地址管理 1.前端改为 userAddress 2. OrderController增加两个需要的元素 3.接口 服务也加上去 4. 在OrderServiceImpl实现层判断是否为新地址再进行保存 //先判断新老地址 if (orders.getUserAddress().equals("newAddress")){//存入数据库UserAddress use…

自学实前后端践项目4 MMall商城 1

一.开发环境 1.JDK8以上Spring Boot 2.3.0ThymeleafMyBatis Plus3.3.1MySQL8.0 2.部署&#xff1a;Linux,&#xff0c;&#xff08;阿里云 腾讯云&#xff09;JDK8&#xff0c;MySQL8.0 3.部署方式&#xff1a;jar包部署&#xff0c;不需要Tomcat 二.新建工程 1&#xff0…

mmall电商项目学习笔记之mybatis三剑客

一.Mybatis plugin IDEA 2017.3版本下Mybatis plugin 3.53安装使用 插件下载地址 http://www.awei.org/download/iMybatis-3.21.jar 二.MyBatis-Generate 反向生成 【转】mybatis自动生成实体代码的插件 【method2】逆向生成 2.1 在pom.xml中做两处配置 2.1.1配置depen…

自学实践前后端项目4 MMall商城 4

一。实现商品详情展示 1.测试获取后台当个商品的信息 2.实现通过点击商品名称和商品图片进入商品详情页面 1&#xff09;查找出商品信息 2&#xff09;在前端进行映射 3&#xff09;设置库存选择限制 判断逻辑 $(function(){//给type绑定点击事件$(".type").click…

mmall 项目实战(一)项目初始化

1.创建 数据库 及 表 数据脚本&#xff1a; /* Navicat Premium Data Transfer Source Server : 182.92.82.103 Source Server Type : MySQL Source Server Version : 50173 Source Host : 182.92.82.103 Source Database : mmall Target Se…

B2C购物商城---MMALL商城概览

注意&#xff1a; 商品小图原图缺失后续上传到图片服务器。不影响使用支付宝二维码是支付宝沙箱开发环境生成&#xff0c;不会产生真实交易&#xff0c;若需体验&#xff0c;请下载支付宝沙箱版扫描体验项目仍需优化 本项目的完成参考了慕课网happymmall的设计 项目源码在&…

MMall项目完整分析总结

Linux服务器 线上环境&#xff1a; 1.jdk 2.vsftpd 3.nginx 4.mysql 5.tomcat 6.git 7.maven 8.Redis 项目采用Tomcat集群方式: 在此架构图中&#xff0c;nginx使用的是轮询的负载均衡策略。session不交给tomcat自己管理&#xff0c;已经交由左侧的redis分布式…

python flask-sqlalchemy flask-marshmallow基本使用

首先安装 pip install marshmallow-sqlalchemy pip install flask-sqlalchemy pip install flask-marshmallow 参考 sqlalchemy query 官网 app.py文件内容 from flask import Flask,jsonify import config from flask_sqlalchemy import SQLAlchemy from flask_marshmallow i…

Flask_使用flask_marshmallow序列化数据

代码如下&#xff1a; from flask import Flask from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from marshmallow import fieldsapp Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] "mysqlpymysql://root:12…

【Python】Marshmallow:Python中的“棉花糖”

博主&#xff1a;&#x1f44d;不许代码码上红 欢迎&#xff1a;&#x1f40b;点赞、收藏、关注、评论。 文章目录 一、Marshmallow简介1.1、基础概念 二、序列化2.1、User类2.2、UserSchema类2.3、Serializing(序列化)2.4、运行2.5、过滤输出 三、反序列化四、验证数据4.1、V…

marshmallow——简介

一、marshmallow简介 在marshmallow诞生之前,已经有很多优秀的模块来用于数据的格式化和数据校验中。 因此书写mashmallow这个库的作者受这些库的启发,例如Django REST Framework, Flask-RESTful, 和colander这些。他同样从这些库中大量借用了设计和实现序列化、反序列化以及…

flask---》Marshmallow介绍及基础使用

0. Marshmallow背景介绍 介绍 Marshmallow&#xff0c;中文译作&#xff1a;棉花糖。是一个轻量级的数据格式转换的模块&#xff0c;也叫序列化和反序列化模块&#xff0c;常用于将复杂的orm模型对象与python原生数据类型之间相互转换。一般用于flaskmarshmallow提供了丰富的…

YApi接口平台-接口挡板

YApi是一个开源的平台&#xff0c;官方平台链接&#xff0c;如下https://hellosean1025.github.io/yapi/index.html,目前很多大公司在使用&#xff0c;主要优势我认为有2个&#xff0c;第一该平台开源&#xff0c;搭建方便&#xff1b;第二该平台解决了前后端分离开发的痛点&am…

open source HTML 5移动应用 -Exlive 人员定位客户端(BlackBerry 10, Android, iPhone)

下图是exlive人员定位系统的宣传页&#xff0c;其官方主站在这里 www.exlive.cn 下图是BlackBerry OS 7.0上面的运行效果&#xff0c;更多截图见这里 http://blog.csdn.net/berryreload/article/details/8099674 Update: Remove BlackBerry OS 7.0支持 升级到PhoneGap 3.0&…

My BlackBerry

什么是黑莓 RIM公司成立于1984年。 黑莓手机&#xff08;Blackberry&#xff09;&#xff0c;是指由加拿大Reserach In Motion&#xff08;RIM&#xff09;公司推出的一种无线手持邮件解决终端设备&#xff0c;也就是我们平时称的手机。 2013年1月30日&#xff0c;RIM公司今…

解决联网下载服务端返回405问题

最近遇到了一个问题&#xff0c;在项目中有一个启动页广告图片下载的功能&#xff0c;之前能够正常下载&#xff0c;由于这个版本遇到了运营商DNS劫持的问题&#xff0c;服务端要调整图片的下载路径&#xff0c;也就是改变了图片的链接地址。修改地址之后下载就出现异常了&…

IIS 405 Method Not Allowed

今天将项目发布到IIS上后&#xff0c;发现Delete方法用不了&#xff0c;可进行如下设置&#xff1a; 打开处理程序映射 找到WebDAV点击请求限制&#xff0c;勾选全部谓词 这个方法如果无效的话 方法二&#xff1a;因为是WebDAVModule限制的请求&#xff0c;所以直接将WebDAV…