项目框架搭建:
1.SpringBoot环境搭建
2.集成Thymeleaf,RespBean
3.MyBatisPlus
分布式会话:
1.用户登录
a.设计数据库
b.明文密码二次MD5加密
c.参数校验+全局异常处理
2.共享Session
a.SpringSession
b.Redis
功能开发:
1.商品列表
2.商品详情
3.秒杀
4.订单详情
系统压测:
1.JMeter
2.自定义变量模拟用户
3.JMeter命令行的使用
4.正式压测
a.商品列表
b.秒杀
页面优化:
1.页面缓存+URL缓存+对象缓存
2.页面静态化,前后端分离
3.静态资源优化
4.CDN优化
接口优化:
1.Redis预减库存减少对数据库的访问
2.内存标记减少Redis的访问
3.RabbitMQ异步下单
a.SpringBoot整合RabbitMQ
b.交换机
安全优化:
1.秒杀接口地址隐藏
2.算术验证码
3.接口防刷 计数器
使⽤分布式Session,可以实现让多台服务器同时可以响应--共享Session
使⽤redis做缓存提⾼访问速度和并发量,减少数据库压⼒,利⽤内存标记减少redis的访问
使⽤页⾯静态化,加快⽤户访问速度,提⾼QPS,缓存页⾯⾄浏览器,前后端分离降低服务器压⼒
使⽤消息队列完成异步下单,提升⽤户体验,削峰和降流
安全性优化:双重md5密码校验,秒杀接⼝地址的隐藏,接⼝限流防刷,数学公式验证码
成本低、系统复杂度低、维护成本低、快速定位问题。符合架构设计的三大原则。
稳定性差、并发量低、扩展性弱
CDN加速:静态资源隔离
在单体系统中,我们的静态资源(Html,JS,CSS 和 IMG
)可能都是通过我们服务端进行返回,存在的问题是:
代码维护
成本比较高(全栈开发成本也高)带宽
,请求资源
占用等前后端分离的好处:
低耦合
),发布成本低(高效率
)动态数据
CDN资源
访问加速,减少后端服务压力(高性能
)Nginx:反向代理器
反向代理的作用比较明显, 由于我们服务拆分成多个,那么我们和前端进行交互时,需要提供一个通用的入口。而这个入口,就是我们的反向代理服务器(Nginx)。 例如: 服务域名:https://www.jiuling.com ,根据restful规范,我们可以通过 https://www.jiuling.com/user/1.0/login 将请求转发到 用户服务的登录接口中。
进程间通信
随着服务的拆分,在部分功能的实现上,就会涉及到服务间相互调用
的情况,例如:订单系统调用商品服务、用户服务等。
在常见的实现方案上,我们会采用 注册中心
和 RPC框架
,来实现这一能力。而我们比较常用的实现方案就是 zookeeper & dubbo
。
为什么要使用RPC框架?每个服务提供 RESTful
接口,不是也能够完成服务间通信吗?
这里就需要进行对比 RPC 和 RESTful 的区别了:
说完优点后,再来分析一下,RPC的缺点
:
耦合性强
: 相较于 RESTful
而言,RPC
框架在跨语言的场景下实现比较困难。并且版本依赖
比较强。服务脱离了当前内网环境
后,无法正常提供服务,迁移成本高。内网调用
: RPC
更适合内网传输,在公网环境下,显得没那么安全。分布式微服务
在上一个版本的服务拆分中, 我们根据不同的业务边界,功能职责,划分出了多个子系统,而针对不同的系统,他所承受的负载压力是不一样的,例如: 订单服务的每个请求处理耗时较长(其他服务压力不大),为了挺升我们的下单量,我们可以只扩容订单服务即可,这就是我们在服务拆分所带来的收益,性能使用率提升!
从上面的图我们可以看到,有些服务出现了不同的重影,每一个方块
,可以理解为一台机器
,在这个架构中, 为了保证我们的下单成功率,以及下单量,我们主要将服务器集中在了订单服务
中间件集群部署
除此之前,再来看看我们的中间件
集群部署:
mysql 主从架构
: 读写分离,减轻主库压力,确保数据能正常写入,保障订单数据落库.zookeeper 主从架构
: 保障注册中心可用,避免导致全链路雪崩。redis 哨兵集群
: 避免redis
宕机导致大流量直接打到数据库中。资源预热——CDN
资源预热: 通过预先将资源加载到CDN
回源: CDN找不到资源后,会触发源站(商品服务)调用,进行查询对应资源,如果源站存在该资源,则会返回到CDN中进行缓存。
OSS: 实际存储静态资源的服务(可参考阿里云OSS)
CDN的风险
成本 : 比较直接,就是得多花钱! 带宽 : 在大流量的访问下, CDN 是否能支撑那么多的带宽,每个服务器能支撑的流量是有限的,需要考虑CDN是否能支撑业务的访问量。 CDN命中率: 在CDN命中率低的情况下,比如活动图片,每一个小时都会发生改变,那么每次图片的替换,都会触发回源操作,这时候的资源访问效率反而有所下降。
缓存预热——redis
与上面的静态资源加速
相对比,动态数据
则需要通过缓存
进行性能上的优化,老生常谈,为什么redis
那么快?
单线程(redis的性能瓶颈并不在这,所以这个不算优势)
多路I/O复用模型
(Epoll)
数据结构简单
基于内存操作
引入 redis 带来的风险主要有:
reids 宕机: 单机部署的情况下,会导致大量的服务调用超时,最终引起服务雪崩。可通过哨兵集群优化。 缓存击穿:大流量下,缓存MISS和缓存过期等情况,会导致请求穿透到数据库,如果数据库扛不住压力,会造成服务雪崩。可以通过 布隆过滤器进行优化(解决穿透问题)。 数据一致性: 缓存数据与DB 的数据一致性问题,需要通过更新策略进行保障
异步调用——MQ
主要原因在于,通过异步调用的方式,我们将消息投递过去了,就完成了这一次的请求处理,那么性能的瓶颈,由订单服务,转移到了秒杀服务这里。通过减少调用依赖,从而提升了整体服务的吞吐量。
MQ
带来的常见问题:
数据一致性
重复消费
:由于生产者重复投递消息,或者消费缓慢导致重复推送消息。需要通过加锁,消费幂等来保证消费正常。(消费幂:多次请求的结果一样)消息堆积
: 生产能力远大于消费能力情况下,会导致消息堆积。MQ可用性
:MQ宕机的情况下,需要支持同步调用切换。登录进⼊商品列表页⾯,静态资源缓存
点击进⼊商品详情页⾯,静态资源缓存,Ajax获取验证码等动态信息
商品列表
商品详情
点击秒杀, 将验证码结果和商品ID传给后端,如果结果正确。动态⽣成随机串UUID,结合⽤户ID和商品ID存⼊redis,并将path传给前端。前端获取path后,再根据path地址调⽤秒杀服务
服务端获取请求的path参数,去查缓存是否在
如果存在,并且Redis还有库存,预减redis库存,看是否已经⽣成订单,没有的话就将请求⼊消息队列
还有库存,预减库存,预减库存后去判断库存是否<0,若<0,则++;若>0,则进入消息队列
预减库存时,使用分布式锁
从消息队列中取消息:获取商品ID和⽤户ID,判断数据库库存,然后下单
下单:减库存,⽣成订单
前端轮询订单⽣成结果。50ms继续轮询或者秒杀是否成功和失败
本项目旨在开发一个基于SpringBoot的商品秒杀系统,实现用户登录、商品列表查看、商品详情查看、秒送、订单详情查看等基本功能,使用JMeter工具对系统接口进行压力测试;并对系统进行页面优化、接口优化和安全优化。
分布式会话:
1.用户登录
a.设计数据库
b.明文密码二次MD5加密
c.参数校验+全局异常处理
2.共享Session
a.SpringSession
b.Redis
明文密码二次MD5加密 :需要在POM中引入依赖:commons-codec
inputPass---FormPass--DBPass
用户端到后端加密:防止密码在网络中进行明文传输
存入数据库时再次加密:防止数据库被盗用后 被解密
数据库存储的是两次加密的结果
前端传过来的是一次加密的
参数校验
a.简单的校验 validator组件 通过添加依赖
@NotNull @Length(min = 6)
b.对手机号校验:自定义校验组件 hibernate-validator依赖
IsMobileValidator.java:定义校验规则和组件名结合起来 ValidatorUtil.java 具体的校验
@interface IsMobile 组件
校验时需要在变量名前加上@Value
原理
(26条消息) 4种分布式session解决方案_断橋殘雪的博客-CSDN博客_分布式session
有哪几种共享Session
依赖:spring-session-data-redis
Redis实现分布式Session的两种方式
SpringSession
依赖:
spring-boot-starter-data-redis
commons-pool2
spring-session-data-redis
添加对Redis的配置
不用动代码 Session会自动保存到Redis上
将Cookie和用户信息存入Redis
将Session存到Redis
取消Redis Session依赖
配置RedisConfig配置类 注意注释@Configuation @Bean 将key value HashKey HashValue 序列化
将ticket(UUID)存到Redis 根据Redis中的ticket获取用户信息
Redis的使用
安装 启动 可视化管理器 默认16个DataBase
Redis分布式锁
setnx key 会出现释放别人的锁 setIfAbsent 只设置了key 加了失效时间
setnx key value value标记锁看(UUID)但是不是原子性
Lua脚本呢可以在Redis服务端写 也可以在Java写 Redis的话网络传输快 但是修改麻烦 Java端相反 将一系列操作变成原子性的
功能开发:
1.商品列表
2.商品详情
3.秒杀
4.订单详情
系统压测:
1.JMeter
2.自定义变量模拟用户
3.JMeter命令行的使用:./jmeter.sh -n -t first.jmx -l result.jtl
4.正式压测
a.商品列表
b.秒杀
衡量标准与区别https://www.cnblogs.com/uncleyong/p/11059556.html
QPS(每秒的查询率 处理流量多少的标准)
TPS:Transactions Per Second,意思是每秒事务数,具体事务的定义,都是人为的,可以一个接口、多个接口、一个业务流程等等。一个事务是指事务内第一个请求发送到接收到最后一个请求的响应的过程,以此来计算使用的时间和完成的事务个数
远程测试
安装JDK
安装MySql
安装https://blog.csdn.net/Iversonx/article/details/80341596
修改密码https://blog.csdn.net/u013277209/article/details/108237466
远程连接 https://blog.csdn.net/xdmdth/article/details/52588785
https://blog.csdn.net/weixin_47984545/article/details/120620560
打包 把Jar包放在Linux服务器上 JMeter在Linux系统进行压测 Windows不是真正的服务器 效果不太好
JMeter也可以在Windows进行压测
页面优化:
1.页面缓存+URL缓存+对象缓存
2.页面静态化,前后端分离
3.静态资源优化
4.CDN优化
Redis做缓存
(我们想把整个商品列表页作为页面缓存起来:
步骤:A 从 Redis中读取缓存,若有页面直接返回给浏览器,若没有则手动渲染模板,之前使用的Thymeleaf,然后存到Redis中,并将结果输出到浏览器端:前端 代码中有设置失效时间)
将不同的id对应的页面也做缓存@RequestMapping("/toDetail/{goodsId}")
缓存用户 (用户是永不失效的,但是若用户信息更改时,若不更新Redis 则会产生数据一致性问题) 解决方法:对数据库有新的操作(更新密码)则删除Redis
延迟双删
即前后端分离
因为使用了Thmeleaf模板,每次浏览器请求的时候 都要从服务器端获取数据然后拼接成模板,渲染返回给浏览器,即使加了缓存,中间传输的是整个模板引擎。我们想前端就是HTML 后端动态的数据才通过服务端传递到前端,前端有很多框架:React、JQuery、zepto... 前端基本不会变动的页面就是静态的,所以要做页面静态化)
把页面和数据拆分出来 将页面给浏览器缓存起来 没有用前端框架 只是用了Ajax来模拟了一下
静态资源的优化(css..js,image.)
商品详情、秒杀、订单详情页面静态化
(css..js,image.)
1、用户Id+商品Id的唯一索引 解决同一用户秒杀多商品 唯一索引BTree 防止重复购买 事务的原子性:将库存减1和判断库存放在一条语句中 借助了数据库引擎Innonb的行锁 2、修改SQL语句(加库存的判断) 购买商品:判断用户是否登录 库存是否够 是否重复抢购(至此 满足抢购的条件) 开始抢购:事务 判断库存>1 库存-1 利用了数据库本身的行锁
再看看网上的讲解
接口优化:
1.Redis预减库存减少对数据库的访问
2.内存标记减少Redis的访问
3.RabbitMQ异步下单
a.SpringBoot整合RabbitMQ
b.交换机
页面优化还是要频繁的和数据库交互
比如获取库存
———————所以通过Redis预减缓存,减少和数据库的交互
——————和Redis(在其他服务器上)频繁交互————内存标记
【还可以增强数据库 分库分表】
Ubuntu里面安装 并且设置远程访问
SpringBoot整合 添加依赖 配置 RabbitMQ 、准备一个队列、准备消息队列的生产者消费者
什么是交换机 交换机接受生产者消息并把生产者的消息推向消息队列
交换机的几种模式
Fanout 广播模式 所有消息队列都接受
Direct 直接交换模式 有了RountKey
先配置 交换机、消息队列和 RouteKey
然后发送消息 使用Direct交换机 指定发送的RouteKey
只有匹配的消息队列才能接受
Topic主题模式
RouteKey使用通配符描述 *表示一个字段 #表示多个字段 (使用最多)
Headers模式
通过不依赖routingkey,使用发送消息时basicProperties对象中的headers匹配队列,headers是一个键值对类型,键值对的值可以是任何类型在队列绑定交换机时用x-match来指定,all代表定义的多个键值对都要满足,any则代表只要满足一个可以了
优化前:四次访问数据库(查库存 查是否重复抢购 生成秒杀订单 生成订单)
Redis优化:将判断重复抢购放在Redis中
预减库存
数据库初始化时把库存存入Redis,秒杀时获取库存,预减库存,
若不足则返回失败,
若足够,将请求加入RabbitMQ消息队列,(《--异步--》)并立即返回客户端”排队中“
异步操作真正的减库存修改数据库,抢单成功后页面如何显示:客户端页面做一个轮询。
预减内存代码
//预减库存 redisTemplate.opsForValue()decrement:如果value是一个数值的话 每调用一次就会递减——————原子性的
//Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
Long stock =
(Long) redisTemplate.execute(redisScript,
Collections.singletonList("seckillGoods:" + goodsId), Collections.EMPTY_LIST);
if (stock < 0){//0-1
EmptyStockMap.put(goodsId,true);/////////////////////////内存标记
valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
why
加入消息队列时(用到了将用户和商品ID 转为JSON )然后发送给交换机 (Topic模式)
消息队列接收时(真正开始下单 数据库操作),(将JSON转回来:jackson依赖)再次查询库存: 是否重复抢购等
会出现问题 若库存为10个,用户100000个,那第11个之后的都会访问Redis,所以“”“”“内存标记”“”“
设置了一个Map
Map<Long,Boolean> EmptyStockMap = new HashMap<>();//商品ID 是否有库存
系统初始化时 EmptyStockMap.put(goodsVo.getId(),false);false表示还有库存 当没有库存时 置为true if (stock < 0){//0-1 EmptyStockMap.put(goodsId,true);
请求加入消息队列和真正修改数据库存在时间差 所以在客户端显示正在排队中 排队结果(成功则返回订单ID 失败返回-1L 继续等待返回0)轮询查看排队结果
后端 获取秒杀结果
//这里仍然是用查询数据库 是因虽然Redis中有订单信息 但是若别人退货则无法及时更新到Redis 数据有不一致的情况把?
SeckillOrder seckillOrder = seckillOrderMapper.selectOne(new QueryWrapper<SeckillOrder>()
.eq("user_id",user.getId()).eq("goods_id",goodsId));
if (!Objects.equals(seckillOrder,null)){//有订单
return seckillOrder.getOrderId();
}else if(redisTemplate.hasKey("isStockEmpty:"+goodsId)){//有这个key说明 已经没有库存了-OrderServiceImpl.java
return -1L;
}else {//没有对应订单 还有库存 则排队
return 0L;
}
前端 轮询
安全优化:
1.秒杀接口地址隐藏
2.算术验证码
3.接口防刷 计数器
获取秒杀地址 并存入Redis
public String createPath(User user, Long goodsId) {
String str = MD5Util.md5(UUIDUtil.uuid()+"123456");
redisTemplate.opsForValue().set("seckillPath:"+user.getId()+":"+goodsId,str,60, TimeUnit.SECONDS);
return str;
}
校验秒杀地址
public Boolean checkPath(User user, Long goodsId,String path) {
if (user == null || StringUtils.isEmpty(path)){
return false;
}
String redisPath = (String) redisTemplate.opsForValue().get("seckillPath:"+user.getId()+":"+goodsId);
return path.equals(redisPath);
}
防止黄牛脚本(会计算 之类的)
减缓 短时间内大量的请求
从Gitee上下载的 注意看使用方法
三种算法
计数器算法 (redis的自增 原子性的)
这里限制了规定时间内访问次数 但是会出现 在规定时间的某段时间频繁访问 另一段时间空闲 临界问题和资源浪费
漏桶算法(保护他人)
水龙头的 水表示请求 放在水箱中 水箱有一个漏斗来过滤请求 水太大: 可能会造成水箱装满 (把服务器撑爆) 使用队列
洞太大:出水量过的大 水太少 资源浪费
令牌桶算法(保护自己)
有以恒定速度专门生成令牌的机制,生成时会判断令牌桶是否满了,没有满则生成放进桶, 桶里面装着令牌
请求过来会请求令牌 请求成功 则执行业务逻辑,如果令牌桶里面的令牌用完了 还是以恒定的速度生成令牌
+++++++++++++能够应对突然的大量请求
本项目旨在开发一个基于 SSM 框架的水果商城系统, 系统支持两种角色:管理员和普通用户。管理员拥有查询水果购买统计表信息、水果类目管理、用户管理、商品管理、订单管理、公告管理和留言管理等权限;普通用户拥有注册登录并修改个人信息的权限,可以查询并收藏商品、加入购物车、结算、查看物流状态等
@Valid:搭配校验组件一起使用 表示对这个变量进行校验
@RestController注解相当于@ResponseBody + @Controller合在一起的作用
超卖
下载一些软件时 用到外国的源 更换国内源
怎么自定义变量?
选择文件;设置文件编码;设置变量名(变量名使用逗号间隔);设置分隔符等
压力测试结果怎么样?
压力测试结果首先是与计算机性能有很大关系的;其实就是跟系统本身的性能有关,因此我也做了一些性能优化:页面优化、接口优化等,主要是减少对数据库和Redis的访问,因为发现涉及到数据库的修改时,QPS就比较小。
衡量标准与区别https://www.cnblogs.com/uncleyong/p/11059556.html
QPS(每秒的查询率 处理流量多少的标准)每秒查询率,是一台服务器每秒能够响应的查询次数(数据库中的每秒执行查询sql的次数),显然,这个不够全面,不能描述增删改,所以,不建议用qps来作为系统性能指标。
TPS:Transactions Per Second,意思是每秒事务数,具体事务的定义,都是人为的,可以一个接口、多个接口、一个业务流程等等。一个事务是指事务内第一个请求发送到接收到最后一个请求的响应的过程,以此来计算使用的时间和完成的事务个数
以单接口定义为事务为例,每个事务包括了如下3个过程:
a.向服务器发请求
b.服务器自己的内部处理(包含应用服务器、数据库服务器等)
c.服务器返回结果给客户端
如果每秒能够完成N次这三个过程,tps就是N;
如果多个接口定义为一个事务,那么,会重复执行abc,完成一次这几个请求,算做一个tps。
如果是对一个查询接口(单场景)压测,且这个接口内部不会再去请求其它接口,那么tps=qps,否则,tps≠qps
如果是容量场景,假设n个接口都是查询接口,且这个接口内部不会再去请求其它接口,qps=n*tps
为什么要在两个系统做压力测试?
Windows系统和 真正的Linux服务器还是有很大区别的
在Linux系统测试需要什么?
项目可以在Linux系统运行:MySql+JDK+Redis(在其他系统上)+JMeter
JMeter命令行的使用:./jmeter.sh -n -t first.jmx -l result.jtl first.jmx是JMeter的一些配置
还有哪些解决分布式会话的方案?
在Redis还是Java端编写Lua脚本,各有什么优缺点
分布式锁有哪几种?
为什么要使用Lua脚本
通过页面缓存、URL缓存、对象缓存和页面静态化进行页面优化,其中缓存使用Redis实现
页面缓存指什么?
URL缓存指什么?
对象缓存 指什么?
如何进行页面静态化?
为什么要进行页面优化?
通过预减内存、内存标记等进行接口优化,通过RabbitMQ消息队列实现异步下单功能
如何预减内存?为什么要
如何进行内存标记?为什么要
减少访问Redis
RabbitMQ有哪几种模式?有哪几种引擎?
如何进行异步下单?
通过隐藏秒杀地址、设置计算验证码进行安全优化,通过计时器算法实现接口防刷功能
如何隐藏秒杀地址?为什么要隐藏
为什么要设置验证码?
接口防刷具体指什么?为什么要接口防刷?
除了计时器算法,还有什么算法?优缺点 【高并发】
这个项目遇到了什么问题 怎么解决的 收获了什么
搭建Spring+SpringMVC+Mybatis框架,前端集成Themeleaf模板引擎
关于SSM框架的基本知识
用户登录时对密码进行二次MD5加密;并通过自定义validator组件对用户手机号进行校验
为什么要进行二次加密,还有什么加密算法
如何自定义组件
这个项目遇到了什么问题 怎么解决的 收获了什么
1、简单描述一下你的科研项目
2、科研中遇到的困难 如何解决的