目录
1.1使用 Nginx 实现反向代理和动态资源、静态资源的分离;
1.2.1 SpringCloud-Gateway: (API网关)实现服务的路由,
结合 SpringCloud - Ribbon :实现负载均衡,
1.2.2 SpringCloud-Feign :声明式HTTP客户端,调用远程服务(实现服务间的远程调用)
1.3 注册功能前端使用 JS 表单校验,后端使用 JSR303 进行校验,并对存储的密码进行加密;
1.4.使用 OAuth2.0 + Spring Session 实现用户社交登录功能,并模拟单点登录的实现;
1.4.1 使用 OAuth2.0 + Spring Session 实现用户社交登录功能
1.5 Elastic Search + Redis + Redisson 商品分类展示和全文检索功能
1.5.1 ElastiacSearch——商城上架的商品要存储在ES中,供检索
1.5.2 redis + Redisson,缓存 SpringCache
1.6 使用 Redis + 本地 Cookie 实现购物车功能;
1.7使用 Rabbit MQ 的延时队列+本地事务,解决分布式事务,实现订单系统。
1.7.1Rabbit MQ 的延时队列 + 本地事务---> 解决分布式事务
2.1.使用 Freemarker 对待秒杀的商品详情页进行页面静态化处理;
2.1.1应用场景: 什么情况下才使用Freemarker?
2.1.5 具体的后端生成静态页面,前端的超链接地址就是生成静态页面的地址
2.2.利用 Bean 的生命周期初始化将商品库存数量加载到 Redis,并使用内存标记减少对 Redis 访问;
2.2.1 利用Bean的生命周期初始化商品信息到Redis中
2.3使用 RabbitMQ,异步化下单逻辑,减轻数据库压力;
2.4 负责基于 Redis 和 Lua 脚本构建分布式锁优化抢单逻辑;
2.5使用 RabbitMQ 实现死信队列,解决订单超时问题;
1.项目一: 薰衣草商城平台
1.1使用 Nginx 实现反向代理和动态资源、静态资源的分离;
答:使用Nginx实现反向代理,其主要的原因有:(1)可以对我们的服务器起到保护的作用,不暴露服务的地址,通过访问Nginx,再通过Ngnix将我们想要访问的服务反向代理给我们(2)为了解决常见的跨域问题。
解决跨域问题: 项目中做法是 :配置当次请求允许跨域
主要流程就是:1.客户端向服务器发送非简单请求(如put 、 delete)时,要先向服务器发送一个预检请求(Options)的操作,预检请求不是真正的请求;
2.服务器响应给客户端 —— 允许跨域;(允许跨域的配置,添加响应头:如支持哪些来源的请求跨域,支持哪些方法跨域,跨域请求默认不包含cookie——可以设置包含cookie,跨域请求暴露的手段等)
3.客户端向服务器发送真实的请求;
4.服务器处理完请求后,向客户端响应数据。
(7条消息) 谷粒商城(1)商品服务-三级分类(跨域问题)_时光、相遇的博客-CSDN博客_谷粒商城商品服务
如何指定我们访问的网址(也就是我们项目的地址 gulimall.com),访问到我们的虚拟机上?
答:域名的解析规则:①先解析本机是否有相应的映射规则
②DNS域名解析获取相应的ip地址
由于每次修改hosts文件太麻烦,因此安装SwitchHosts,将待访问的地址 gulimall.com 映射到虚拟机地址上。
配置完之后,访问以下的域名,就会访问到我们的虚拟机上了。
总结: 设置Nginx自动启动,1.Nginx 各自一一配置服务的地址,2.Nginx只配置网关,也就是Nginx只反向代理网关。 配置上流服务器的名称为 gulimall, 然后将其映射到 网关的地址,这样就可以反向代理网关(但是这样会出现丢失请求的HOST信息,解决方法:配置Nginx,不要丢掉HOST信息:proxy_set_header Host $host
)。然后网关服务有断言 路由功能,实现服务的负载均衡。
参考:
(8条消息) 谷粒商城:17.商城业务 — Nginx搭建域名访问_KaiSarH的博客-CSDN博客
(8条消息) 谷粒商城-nginx_兰交余文乐的博客-CSDN博客_谷粒商城nginx
Nginx将server配置到网关gateWay,然后请求的路径根据gateWay去路由到不同的服务下边,以此可以实现负载均衡。
1.2使用 Nacos 作为服务的注册、配置中心
答:其实Spring Cloud 和 SpringCloud Alibaba是两个微服务集合,其中
SpringCloud Alibaba - Nacos: 既是微服务的注册中心(服务的发现/注册);
又是微服务的配置中心(动态配置管理)。
如何使用? ——作为服务的注册中心
答:1. 先下载nacos-server 2.启动nacos-server
3.将微服务注册到nacos中:
3.1先修改微服务(准备注册到Nacos中的微服务)的pom.xml文件,引入 Nacos Discovery Starter;
3.2在微服务的 appliacation.properties 配置文件中配置 Nacos Server 的地址
3.3 使用 @EnableDiscoveryClient 开启服务注册发现功能
3.4 启动微服务,观察nacos服务列表(网页形式)是否已经注册上服务
3.5 注册更多的微服务到Nacos中,测试使用 feign 进行远程调用
(Feign的使用步骤:)
3.5.1 在微服务中导入 openFeign的依赖
3.5.2 在微服务上开启 @EnableFeignClients 功能
3.5.3 编写接口,进行远程调用:
@FeignClient("stores")
Public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
....
}
如何使用? ——作为服务的配置中心
答: 1. 微服务的 pom.xml中引入 Nacos Config Starter。
2.在微服务的 bootstrap.properties配置文件中配置 Nacos Config 元数据: 主要配置应用名(微服务名)和配置中心地址
3.在nacos中添加配置
....后续就是在写项目的时候, 在 Nacos页面进行对应各种微服务配置的操作,写dataId 和 配置分组等,加载多个微服务对应的配置文件。
每个微服务创建自己的 namespace进行隔离, group 来区分 dev, beta, prod等环境。
穿插一下: 云服务存储的时候,就是将前端需要展示的图片资源等存储到云服务器上,步骤如下:
1.本质也是通过后端存储的, 项目中是有后台管理模块的前端页面,先在微服务pom.xml文件中引入 阿里云OSS的依赖
2.前端页面根据 Element Ui 进行开发,图形化上传界面,其实就是 提交之后 通过后端存储到了 云服务器上,
3.当前端访问的时候,后端就将这些资源从服务器中取出来,返回给前端。
1.2.1 SpringCloud-Gateway: (API网关)实现服务的路由,
结合 SpringCloud - Ribbon :实现负载均衡,
网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。
Filter分为: GateWayFliter, 和 GlobalFilter
1.2.2 SpringCloud-Feign :声明式HTTP客户端,调用远程服务(实现服务间的远程调用)
答: Feign是一个声明式的HTTP客户端,它的目的就是让远程调用更加简单。 Feign整合了 Ribbon(负载均衡) 和 Hystrix(服务熔断),可以让我们不需要显式地使用这两个组件。
SpringCloudFeign 在 NetFlixFeign的基础上扩展了 对 SpringMVC注解的支持,在其实现下,我们只需要创建一个接口并用注解的方式来配置它,就可以完成对服务提供方的接口绑定。简化了 SpringCloud Ribbon 自行封装服务调用客户端的开发量。
如何使用?
1.在微服务中pom.xml中引入 Feign的依赖
2.在希望被调用的微服务上 开启 feign功能: @EnableFeignClients(basePackages = "com.atguigu.gulimall.pms.feign")
3.声明远程接口--在 feign包下,有该微服务想要调用的微服务的接口,@FeignClient该微服务想要调用哪些微服务的哪些方法,
@FeignClient ("gulimall - ware")
public interface WareFeignService {
@PostMapping("/ware/waresku/skus")
}
1.2.3 Seata 2PC 协议实现分布式事务的管理
这些组件的功能以及特点,可以参考具体的开发文档——SpringCloud组件
SpringCloud Alibaba - Sentinel: 服务容错(限流、降级、熔断)
SpringCloud - Sleuth: 调用链监控
SpringCloud Alibaba - Seata: 原Fescar, 即 分布式事务解决方案。
访问量过大, 服务器会有自动扩容机制。
1)2PC协议 又叫做 XA Transactions;
注意,在说分布式事务的时候,可以这样说: 2PC是最开始的时候所用的方法,但是发现该方法存在一定的问题:(缺点)
1.3 注册功能前端使用 JS 表单校验,后端使用 JSR303 进行校验,并对存储的密码进行加密;
服务的注册功能——验证码,发送验证码,可以整合第三方短信验证服务,就是在前端绑定事件,到后端的controller。
验证码和手机号是存在 Redis里边的。
也可以直接用httpUtils来完成。
可以参考别人的博客中写的:
(13条消息) 谷粒商城-认证服务_兰交余文乐的博客-CSDN博客_认证服务 谷粒商城
项目中在这个模块,用到了校验。
(10条消息) 谷粒商城(2) 商品服务-品牌管理_时光、相遇的博客-CSDN博客_谷粒商城商品管理
1.3.1 注册功能的前端表单校验具体的内容
(9条消息) JavaWeb前端开发注册表单验证_我不读研的博客-CSDN博客_前端开发表单验证
注意: 注册的时候验证码存在 Redis中。
1.3.2 后端的JSR303校验具体的内容
使用步骤:
1.标注校验注解
2.使用校验功能
3.提取校验错误信息
4.分组校验与自定义校验
![](https://i-blog.csdnimg.cn/blog_migrate/b01b066f2d98b669f093e50ed3da399b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6edc28de47552aab1c67e4079bdee1ec.png)
在商品服务中,只要涉及到提交和前端数据保存的时候,都需要进行后端和前端的双重校验:
1.4.使用 OAuth2.0 + Spring Session 实现用户社交登录功能,并模拟单点登录的实现;
1.4.1 使用 OAuth2.0 + Spring Session 实现用户社交登录功能
社交登录:
![](https://i-blog.csdnimg.cn/blog_migrate/bfcda128759425e388dbe0d899e31855.png)
1.4.2 模拟单点登录的实现
在自己写的博客:有大概的实现流程
(9条消息) 单点登录(项目)_时光、相遇的博客-CSDN博客_单点登录项目
单点登录: 一处登录,处处可用。
单点登录的流程:
1.4.3Spring Session
SpringSession解决的是 不同服务(域名)下,无法实现如登录信息的共享,通过springSession来解决不同域的session共享问题。
简单的例子: 当在登录服务下登录好了之后,其登录的信息既可以在(放大到父域)gulimall.com下能用,在order.gulimall.com下也能用(子域的共享)。
要实现session的子域共享,就是把cookie的域名给放大到了父域中:
实现域名的放大效果,就是借助了springsession中的 cookieSerializer.setDomianName(),等 进行自定义的session配置。
自己理解的就是,登录服务把自己的cookie存到redis,想让大家共享,那就需要登陆服务把自己的cookie给自定义配置一下,共享到父域,只有这样,大家才能一起使用。
自己的博客中:
(10条消息) 分布式Session_时光、相遇的博客-CSDN博客
1.5 Elastic Search + Redis + Redisson 商品分类展示和全文检索功能
1.5.1 ElastiacSearch——商城上架的商品要存储在ES中,供检索
先从ES的概念原理上入手:
如 什么是倒排索引?等一系列的问题:
自己的博客中:
(12条消息) ElasticSearch相关_时光、相遇的博客-CSDN博客
项目中是如何做的?
1)在Linux中安装ES,使用docker进行安装:
s1:下载镜像文件
具体的操作步骤在 高级篇的 es文档中可以找到。
ES的操作分类:
1.保存文档
POST新增文档,Put可以新增也可以修改,但是PUT必须指定id,因此一般用作修改文档。
2.查询文档
3.更新文档
4.删除文档
5.bulk 批量API
6.样本测试
提前把样本数据,导入到ES中,post data/....
7.使用ES,进行检索功能
如何进行检索,Query DSL,其基本的语法格式,其实就是 MySQL语言一样的,那种要写SQL语句,只不过写的规范不一样,如 聚合,匹配,结果过滤,或者是分词——分词需要分词器,可以自己定义分词器(比如我们当然是需要汉语的分词器,安装ik分词器(标准的中文分词器),还可以自定义词库)。
8.在项目中整合操作 ES的 API, Elasticsearch - Rest - Client
项目中有单独的搜索服务, gulimall - search:
8.1先在项目中导入 Elasticsearch - Rest - Client的 api
8.2 封装好查询商品的 vo数据模型,以及返回结果的vo数据模型
8.3基于 rest-high-level-client的api进行对商品的搜索, 也的也是搜索的接口
具体的见,开发文档高级篇——商城业务中11页
分类查询,就涉及到了商品的三级分类问题,以及商品的分页查询问题
商品三级分类:先创建好数据库表,数据表中的表格属性包含 parentCid 、catLevel,用于三级分类的(递归)查询和返回。
商品的分页查询: 使用PageUtils工具类,将查到的数据信息,利用PageUtils工具类,进行封装即可。
1.5.2 redis + Redisson,缓存 SpringCache
缓存的使用规则:
该部分是位于 商品服务中(秒杀服务也存在)。
在商品服务中,首先配置springCache,引入其相关的依赖,相当于开启缓存功能。
启用缓存之后,就要给缓存 注册一个 cacheManager——缓存管理器,项目中用的就是Redis,整合redis 作为缓存。
项目中,就是商品的查询信息保存在缓存中,而缓存是位于Redis中的,第一次查询服务,将商品信息保存在缓存里边,而缓存是存在内存中的,可以设置将这样的缓存持久化到OS内存中去。
@EnableCaching:标记注解@EnableCaching,开启缓存,并配置Redis缓存管理器,@EnableCaching注解触发后置处理器,检查每一个Spring bean的public方法是否存在缓存注解,如果找到这样的一个注释,自动创建一个代理拦截方法调用和处理相应的缓存行为。
springCache : 对 存在redis里边的商品信息等缓存进行操作——读或者写。
缓存@CachePut(添加一个数据后,将会把数据同步到缓存中);
缓存@CacheEvict(使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上)
缓存标签@Cacheable(根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回,如果缓存不存在,则执行方法,并把返回的结果存入缓存中,一般用在查询方法上)
springCache的总结,自己的项目中也有:
(13条消息) 谷粒商城--后端笔记(混乱)_时光、相遇的博客-CSDN博客_谷粒商城seata
1.5.3既然存在了缓存,就存在缓存的一系列问题
缓存的失效问题:
1)缓存穿透
2)缓存击穿
3)缓存雪崩
缓存数据的一致性问题(Redis——缓存 和 数据库——真实数据)
具体见高级篇文档——缓存和分布式锁
1)双写模式
2)失效模式——涉及到了分布式读写锁
分布式锁—— 基于 Redisson完成:自己的博客中,有详细写了Redisson相关的使用场景。
(13条消息) 谷粒商城--后端笔记(混乱)_时光、相遇的博客-CSDN博客_谷粒商城seata
1)配置Redisson
2)使用分布式锁
其中也涉及到信号量
1.6 使用 Redis + 本地 Cookie 实现购物车功能;
具体见,开发文档高级篇——商城业务的23页
本地cookie就是保存在浏览器中的:
注意:
对于用户的临时购物车,我们也希望传递给后端,这样通过大数据就能得到用户经常浏览的商品,并进行推送、因此有价值的数据不能存到前端,而是统统放到后端。
1.主要就是,分为本地购物车和登录购物车,主要问题是在登录购物车的时候:也就是在登录的状态下,对购物车中的商品信息进行CURD...
2.购物车中的商品是存储在 Redis中的
1.7使用 Rabbit MQ 的延时队列+本地事务,解决分布式事务,实现订单系统。
1.7.1Rabbit MQ 的延时队列 + 本地事务---> 解决分布式事务
多个MQ的属性对比:
提到分布式事务,想到 CAP理论 的延伸——BASE理论(最终一致性)。
(13条消息) 分布式事务(项目)_时光、相遇的博客-CSDN博客
1.7.2 订单系统
2.订单系统涉及到的问题就是:
项目的难点是 订单的创建和订单的解锁功能。
当一个订单创建成功后,立刻锁定库存,且订单创建成功的消息在30mins如果不支付,订单将关闭,然后40mins后,库存将检查订单的状态,如果订单状态为:未支付或者取消状态,则库存将进行解锁的逻辑。
整个流程大致如下:
订单创建成功的消息--->延时队列(一个普通的30min消息存活队列+死信路由器和死信队列)--->服务监听到stock.release.stock.queue队列,实现库存的解锁;
但是 解锁库存之前要先判断订单是否解锁了, 即 订单的解锁(关单) 要在 库存的解锁之前,当库存解锁服务看到订单已经关闭了,就要自动解锁库存。
2.1手动解锁库存是什么:因为网络延迟问题,库存解锁先于订单的解锁。
手动解锁库存就是,在这样的场景下:如果订单创建成功后由于机器卡顿,消息延迟等原因, 订单信息还没有解锁(订单消息的解锁就是关闭订单,当订单创建成功,就会进入延时队列,当过了30mins,则该订单信息就会路由到 order.release.order.queuqe,从而实现关闭订单功能(释放订单服务),这时候因为过了30mins,库存已经解锁过了,但是此时延迟的订单信息才到达,因此设置一个 当订单自动解锁的时候,就给死信交换机发送一个消息,并根据路由键order.release.other 路由到 Order.release.stock.queue队列,从而实现手动解锁库存。)
2.2 消费端要保证消息的可靠性到达消费端,但是消费者在消费过程中出现宕机,则此时应该开启 手动ACK机制,即消费成功才移除MQ中的消息,如果没有成功,则将该消息重新入MQ。
总: 只要订单创建之后超过自定义的时间,如30mins,则订单都是要被释放的,然后至于需不需要解锁库存,则是根据订单的状态去进行判断的,如果订单没有支付或者被取消,则此时才需要对库存的解锁。
订单的确认流程:
![](https://i-blog.csdnimg.cn/blog_migrate/40c972ca00313ab4479540edb1c69f56.png)
消息队列的流程,也就是订单的创建和取消,以及库存的解锁全部的流程:
下订单还要有:
幂等性处理:详细的见,高级篇开发文档 接口幂等性。里边涉及到了一系列的 解决接口幂等性的问题。
订单生成后,要引入订单生成的页面,Nginx 配置动静分离,上传静态资源到nginx, 并编写controller的 跳转逻辑。
防止超卖的逻辑: 就是在下订单的时候,就先扣减库存,进行对比,如果发现库存容量不够,则就不创建订单。
说该项目的时候,要注意说,客户的体验——在进行秒杀时候的体验
2.项目二: 薰衣草商城抢购秒杀系统
2.1.使用 Freemarker 对待秒杀的商品详情页进行页面静态化处理;
2.1.1应用场景: 什么情况下才使用Freemarker?
1.对于经常要访问的页面,每次用户访问这些页面都需要查询数据库获取动态数据进行展示,而且这些页面的访问量是比较大的,这就对数据库造成了很大的访问压力;
2.并且数据库中的数据变化频率并不高。
那我们需要通过什么方法为数据库减压并提高系统运行性能呢?答案就是页面静态化。
页面静态化其实就是将原来的动态网页(例如通过ajax请求动态获取数据库中的数据并展示的网页)改为通过静态化技术生成的静态网页,这样用户在访问网页时,服务器直接给用户响应静态html页面,没有了动态查询数据库的过程。
那么这些静态HTML页面还需要我们自己去编写吗?其实并不需要,我们可以通过专门的页面静态化技术帮我们生成所需的静态HTML页面,例如:Freemarker、thymeleaf等。
2.1.2FreeMarker是什么?
FreeMarker 是一个用 Java 语言编写的模板引擎,它基于模板来生成文本输出。FreeMarker与 Web 容器无关,即在 Web 运行时,它并不知道 Servlet 或 HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成 XML,JSP 或 Java 等。
2.1.3FreeMarker的具体使用流程:
1.添加 freeMarker 的依赖
2.创建模板文件
模板文件中有四种元素:
1、文本,直接输出的部分
2、注释,即<#--...-->格式不会输出
3、插值(Interpolation):即${..}部分,将使用数据模型中的部分替代输出
4、FTL指令:FreeMarker指令,和HTML标记类似,注释用 #
Freemarker的模板文件后缀可以任意,一般建议为ftl。
在D盘创建ftl目录,在 ftl 目录中创建名称为test.ftl的模板文件
3.生成文件:具体分为以下几个步骤
第一步:创建一个 Configuration 对象,直接 new 一个对象。构造方法的参数就是 freemarker的版本号。
第二步:设置模板文件所在的路径。
第三步:设置模板文件使用的字符集。一般就是 utf-8。
第四步:加载一个模板,创建一个模板对象。
第五步:创建一个模板使用的数据集,可以是 pojo 也可以是 map。一般是 Map。
第六步:创建一个 Writer 对象,一般创建 File Writer 对象,指定生成的文件名。
第七步:调用模板对象的 process 方法输出文件。
第八步:关闭流。
注意:
上面的入门案例中Configuration配置对象是自己创建的,字符集和模板文件所在目录也是在Java代码中指定的。在项目中应用时可以将Configuration对象的创建交由Spring框架来完成,并通过 依赖注入 方式 将 字符集 和 模板所在目录 注入进去。
2.1.4freeMarker还有一些常用的指令
2.1.5 具体的后端生成静态页面,前端的超链接地址就是生成静态页面的地址
项目中使用: 场景: 主页面 的模板文件是一致的,但是要包含每一个商品的页面,不同的商品都要生成对应的模板文件,如在点击主页面的某一个商品的时候,就要链接到该商品对应的静态页面中,假设3个商品,就要有3个商品的详情页。
当然,可以不要这么详细的页面,可以在主页面的下边,再总体地生成多个次级的静态页面(非每一个商品的详情页)。
先按照 2.1.3中写的流程来一遍:
1.先创建模板文件,注意模板文件中的每一个次级页面的链接地址,要是动态的,如
2.配置生成静态页面的路径,以及在 spring文件中配置 freemarker的相关属性
3.在前端的链接地址,也要进行修改,虽然每一个不同的页面的名称是根据id来的,但是最终生成的页面对象是一个
注意:
在进行秒杀活动的时候,要生成静态页面的内容应该包括,所有要展示的商品的信息,如名称,图片等。 然后下级页面,应该对应生成手机类,衣服类,电脑类。。。。
2.2.利用 Bean 的生命周期初始化将商品库存数量加载到 Redis,并使用内存标记减少对 Redis 访问;
2.2.1 利用Bean的生命周期初始化商品信息到Redis中
借助 spring 中 Bean对象的初始化过程来理解:
1)首先在这样的业务场景下,我们需要一个controller来完成 seckill的功能。 在spring的生命周期中,这样的@Controller 整个其实就是一个 Bean
2)商品信息等,在自定义属性注入 和 容器属性注入的时候,作为一个json 注入到了这个Bean里边
3) 然后我们要将商品存到 Redis里边,是在 执行完前置的处理方法后,执行初始化方法——检测initializingBean接口——调用afterPropertiesSet()完成属性设置后的工作,这一步,引入了Redis,如 调用 redisTemplate.put(goods)
这样,就把我们的商品信息 以 Bean的生命周期初始化过程,给缓存到了Redis里边。
2.2.2其中的内存标记的意思:可以参考如下的博客:
(13条消息) 接口优化----Redis预减库存,内存标记_Dandy1awcoder的博客-CSDN博客_redis内存标记
内存标记: 我们是将商品信息缓存到Redis中,但是进行查询的时候也是要访问Redis的,为了减少Redis的压力,可以加一个内存map,标记对应商品的库存量是否还有,在访问Redis之前,在map中拿到对应商品的库存量标记,就可以不需要访问Redis 就可以判断没有库存了。
2.3使用 RabbitMQ,异步化下单逻辑,减轻数据库压力;
在添加购物车的时候,有使用异步化的概念。
用的是 CompletableFuture 异步编排原理,
应用场景是: 查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,会花费很多的时间,采用异步化编排可以提高效率。
1.获取sku的基本信息
2.获取sku的图片信息
3.获取sku的促销信息
4.获取spu的所有销售属性
5.获取规格参数组及组下的规格参数
6.spu详情
其中: 1,2,3可以异步执行,4,5必须等待1执行结束才能执行,4,5,6可以异步执行
参考:(13条消息) Java中CompletableFuture多线程任务异步编排_嗑嗑磕嗑瓜子的猫的博客-CSDN博客_java 任务编排
适合自己项目的参考:(以及还有其它的问题)
(14条消息) 秒杀系统实战(五)| 如何优雅的实现订单异步处理_小赞很优秀的博客-CSDN博客_异步下单实现
三点:
1.我们将每一条秒杀的请求(包括用户ID和订单ID)存入消息队列(例如RabbitMQ)中;
2.放入消息队列后,给用户返回类似“抢购请求发送成功”的结果;
3.而在消息队列中,我们将收到的下订单请求一个个的写入数据库中。
如下,项目中下订单的时候要拉取很多的服务,因此采用异步化CompletalbeFuture,进行异步化编排:
2.4 负责基于 Redis 和 Lua 脚本构建分布式锁优化抢单逻辑;
高级篇开发文档——缓存与分布式锁(7/13)
最low的回答就是,要保证每个客户购买时,其它客户不能购买,必须等到商品数量减完之后,才能被其它客户下单。
Lua脚本解锁,保证原子性,主要嵌入到Redis中,优化扣库存的操作;
在Redis里边使用lua脚本,其实是为了保证Redis里边的某些操作是原子性的。(如在保证提交订单的时候——幂等性,就是Redis内嵌lua 脚本实现获取令牌等一系列原子操作的)
还有是防重令牌,是创建订单的时候,来一个uuid,放到redis中缓存起来
注意:扣库存是订单服务远程调用库存服务实现的(Feign)
参考这篇文章:
2.5使用 RabbitMQ 实现死信队列,解决订单超时问题;
订单超时,就是要让库存解锁;
场景:当一个订单创建成功,发送给普通的MQ(设置MQ,消息队列的存活时间为30mins,即如果消息在里边30mins没有被消费,则消息国过期)进入延迟队列, 如果消息过了30mins,
场景1: 订单创建了,但是没有支付或者订单取消,要解锁库存:
只要订单创建之后超过自定义的时间,如30mins,则订单都是要被释放的,然后至于需不需要解锁库存,则是根据订单的状态去进行判断的,如果订单没有支付或者被取消,则此时才需要对库存的解锁。
订单表和库存表是两个。订单创建成功之后,是同时发给两个服务的。然后根据MQ的过期时间,去判断是否要进行库存的回滚。
场景2: 订单没创建,但是库存锁定了,要解锁库存;
订单创建好之后,UUID,进行扣库存,库存存储UUID,但是网络异常(i / 0),订单没有创建成功,订单表就没有UUID,等这个UUID超过指定时间后,库存服务要根据这个UUID订单状态进行是否解锁库存的操作,因为 订单表中不存在这个UUID,因此进行回滚,库存解锁。
以下适合的场景是: 订单取消的问题:
1.释放订单服务(其实就是一个消费者,redis是在这边吗?): 是为了给 order- event-exchange发送订单状态信息,然后订单状态的信息发送到stock.release.stock.queue,
2.stock.delay.queue——50mins,的队列是使用场景2,因为订单没有创建成功,但是库存锁了,也要保证库存可靠性解锁
流程:订单创建成功后,都会被发送到 delay-30mins的queue,那么在这个过程中,用户对订单进行支付或者未支付——支付成功,会更改订单的状态(在订单表中),不管用户是否支付,我们都是等到30mins之后判断是否要解锁库存。
即30mins已到,delay-30mins将订单信息发送给order-event-exchange(也就是死信交换机),发送给死信队列:order- release.order.queue,被释放订单服务监听到,并消费;
在消费的过程中,会出现两种情况,判断订单的状态,是否或未支付,若支付,则ack消费掉;
如果未支付,则将消息通过 死信交换机发送给 stock.release.stock.queue,进行库存的解锁服务。
以上就是库存的回滚逻辑。
还有另外一个相对的场景是 超卖问题:库存数量不够,但是还是多下单了
秒杀的时候,前端很多用户进行下单,我们将 待秒杀的商品信息存放在redis中,包括商品信息和对应的数量。
首先就是下单: 两种逻辑:
1.设置一个信号量,100个——信号量相当于停车位,可以允许一次有100个用户同时进来进行秒杀活动,每一个用户下单的数量是不一样的,每个用户下一次单,都要查一次库存,如果某一个客户购买物品较多,超过库存量了,就不允许他购买,直接返回秒杀失败;
2.设置商品的数量信息, 在mysql之前,使用 redis + lua,直接判断是否可以进行秒杀,客户买一次,就将商品数量信息减一次对应的值,直到不能购买,返回失败。
lua脚本保证的是: 一个客户在购买某种商品,其它用户不能购买,只有等到对应商品的数量减完之后,其它用户才能购买。
但是这种逻辑,会存在问题,如果用户取消订单,数据库要回滚,redis也要回滚,这种时候就是延时双删来解决。