终极版——面试

目录

1.项目一: 薰衣草商城平台

1.1使用 Nginx 实现反向代理和动态资源、静态资源的分离;

1.2使用 Nacos 作为服务的注册、配置中心

如何使用? ——作为服务的注册中心

如何使用? ——作为服务的配置中心

1.2.1 SpringCloud-Gateway: (API网关)实现服务的路由,

结合 SpringCloud - Ribbon :实现负载均衡,

1.2.2 SpringCloud-Feign :声明式HTTP客户端,调用远程服务(实现服务间的远程调用)

1.2.3 Seata 2PC 协议实现分布式事务的管理

1.3 注册功能前端使用 JS 表单校验,后端使用 JSR303 进行校验,并对存储的密码进行加密;

1.3.1 注册功能的前端表单校验具体的内容

1.3.2 后端的JSR303校验具体的内容

1.4.使用 OAuth2.0 + Spring Session 实现用户社交登录功能,并模拟单点登录的实现;

1.4.1 使用 OAuth2.0 + Spring Session 实现用户社交登录功能

1.4.2 模拟单点登录的实现

1.4.3Spring Session 

1.5 Elastic Search  + Redis  +  Redisson 商品分类展示和全文检索功能

1.5.1 ElastiacSearch——商城上架的商品要存储在ES中,供检索

1.5.2 redis + Redisson,缓存 SpringCache

1.5.3既然存在了缓存,就存在缓存的一系列问题

1.6 使用 Redis + 本地 Cookie 实现购物车功能;

1.7使用 Rabbit MQ 的延时队列+本地事务,解决分布式事务,实现订单系统。

1.7.1Rabbit MQ 的延时队列 + 本地事务---> 解决分布式事务

1.7.2 订单系统

2.项目二: 薰衣草商城抢购秒杀系统

2.1.使用 Freemarker 对待秒杀的商品详情页进行页面静态化处理;

2.1.1应用场景: 什么情况下才使用Freemarker?

2.1.2FreeMarker是什么?

2.1.3FreeMarker的具体使用流程:

2.1.4freeMarker还有一些常用的指令

2.1.5 具体的后端生成静态页面,前端的超链接地址就是生成静态页面的地址

2.2.利用 Bean 的生命周期初始化将商品库存数量加载到 Redis,并使用内存标记减少对 Redis 访问;

2.2.1 利用Bean的生命周期初始化商品信息到Redis中

2.2.2其中的内存标记的意思:可以参考如下的博客:

2.3使用 RabbitMQ,异步化下单逻辑,减轻数据库压力;

2.4 负责基于 Redis 和 Lua 脚本构建分布式锁优化抢单逻辑;

2.5使用 RabbitMQ 实现死信队列,解决订单超时问题;

2.6使用 JMeter 进行高并发压力测试。


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 :实现负载均衡,

网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。

网关提供 API 全托管服务,丰富的 API 管理功能,辅助企业管理大规模的 API ,以降低管理
成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。
常用的负载均衡算法:
轮询: 为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直
到最后一个,然后循环。
最小连接: 优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下
可以考虑采取这种方式。
散列: 根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程
度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到 和之前相同的服务器,可以考虑采取这种方式。

Spring Cloud Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,并为他们提供切
面,例如:安全性,监控 / 指标 和弹性等。、
总结: GateWay的使用:
       如果满足某些断言( predicates )就路由到指定的地址( uri ),使用指定的过滤器( filter,当目标的请求到达服务器被处理之后,返回过程中通过 依次执行过滤器(链中,多个过滤器)的Post方法,将结果返回去

         客户端发送请求给网关,弯管 HandlerMapping 判断是否请求满足某个路由,满足就发给网
关的 WebHandler 。这个 WebHandler 将请求交给一个过滤器链,请求到达目标服务之前,会
执行所有过滤器的 preHandle  方法。请求到达目标服务处理之后再依次执行所有过滤器的 postHandle  方法。

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.  XA 协议比较简单,而且一旦商业数据库实现了 XA 协议,使用分布式事务的成本也比较
低。
2.  XA 性能不理想 ,特别是在交易下单链路,往往并发量很高, XA 无法满足高并发场景
3. XA 目前在商业数据库支持的比较理想, mysql 数据库中支持的不太理想 mysql
XA 实现,没有记录 prepare 阶段日志,主备切换会导致主库与备库数据不一致。
4. 许多 nosql 也没有支持 XA ,这让 XA 的应用场景变得非常狭隘。
5. 也有 3PC ,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间
未收到回应则做出相应处理)
2)还有柔性事务-TCC事务补偿性方案
3)柔性事务-最大努力通知型方案
4) 柔性事务 - 可靠消息 + 最终一致性方案(异步确保型)
具体的内容,在 高级篇: 本地事务 和 分布式事务的  PDF开发手册中。

1.3 注册功能前端使用 JS 表单校验,后端使用 JSR303 进行校验,并对存储的密码进行加密;

服务的注册功能——验证码,发送验证码,可以整合第三方短信验证服务,就是在前端绑定事件,到后端的controller。

验证码和手机号是存在 Redis里边的。

也可以直接用httpUtils来完成。

可以参考别人的博客中写的:

(13条消息) 谷粒商城-认证服务_兰交余文乐的博客-CSDN博客_认证服务 谷粒商城

项目中在这个模块,用到了校验。

(10条消息) 谷粒商城(2) 商品服务-品牌管理_时光、相遇的博客-CSDN博客_谷粒商城商品管理

1.3.1 注册功能的前端表单校验具体的内容

(9条消息) JavaWeb前端开发注册表单验证_我不读研的博客-CSDN博客_前端开发表单验证

注意: 注册的时候验证码存在 Redis中。

1.3.2 后端的JSR303校验具体的内容

使用步骤:

1.标注校验注解

javax.validation.constraints 中定义了非常多的校验注解,如:@Email、 @Future @NotBlank @Size 等。

2.使用校验功能

@Valid 开启校验功能

3.提取校验错误信息

BindingResult 获取校验结果

4.分组校验与自定义校验

Groups 定义校验分组信息;
可以编写自定义校验注解和自定义校验器。
自己的项目中用到JSR303的地方,有1)在商品服务里边,保存品牌信息的时候,在controller的地方进行了@Valid()
项目中具体的:

在商品服务中,只要涉及到提交和前端数据保存的时候,都需要进行后端和前端的双重校验:

可以参考别人的博客,较完全:
注意: 注册的时候验证码存在 Redis中。

1.4.使用 OAuth2.0 + Spring Session 实现用户社交登录功能,并模拟单点登录的实现;

1.4.1 使用 OAuth2.0 + Spring Session 实现用户社交登录功能

社交登录: 

  OAuth OAuth (开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储
在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们
数据的所有内容。
  OAuth2.0 对于用户相关的 OpenAPI (例如获取用户信息,动态同步,照片,日志,分
享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向
用户征求授权。

A )用户打开客户端(就是自己的项目)以后,客户端要求用户给予授权。
B )用户同意给予客户端授权。
C )客户端使用上一步获得的授权,向认证服务器申请令牌。
D )认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
E )客户端使用令牌,向资源服务器申请获取资源。
F )资源服务器确认令牌无误,同意向客户端开放资源。
Client: 就是客户端,客户端,就是我们自己开发的项目;
Resource Owner: 就是我们用户自己,因为我们是想把我们自己存在第三方应用(如微博,QQ,微信等) 上的信息(账号密码), 授权给 我们此时需要登录的客户端(Client——自己的项目);
Authorization Server: 是第三方应用的 授权服务器,就是专门来处理 客户端请求自己(授权服务器)授权的 地方;(注意,Client是获取不到用户的登录账号密码的)
Resource Server: 就是第三方应用存信息的服务器(数据库)。
以一个实例去解释了Oauth2.0的博客:
社交登录的流程,项目中:

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:下载镜像文件

docker pull elasticsearch:7.4.2          存储和检索数据
docker pull kibana:7.4.2          可视化检索数据

具体的操作步骤在  高级篇的 es文档中可以找到。

ES的操作分类:

1.保存文档

POST新增文档,Put可以新增也可以修改,但是PUT必须指定id,因此一般用作修改文档。

2.查询文档

3.更新文档

4.删除文档

5.bulk 批量API

    bulk  API 以此按顺序执行所有的 action (动作)。如果一个单个的动作因任何原因而失败,
它将继续处理它后面剩余的动作。当 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 作为缓存

Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache EhCacheCache ,
ConcurrentMapCache 等;
因为每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已
经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓
存结果后返回给用户。下次调用直接从缓存中获取。

项目中,就是商品的查询信息保存在缓存中,而缓存是位于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中的

user-key 是随机生成的 id,不管有没有登录都会有这个 cookie 信息。
两个功能:新增商品到购物车、查询购物车。
新增商品:判断是否登录
- 是:则添加商品到后台 Redis 中,把 user 的唯一标识符作为 key
- 否:则添加商品到后台 redis 中,使用随机生成的 user-key 作为 key
查询购物车列表:判断是否登录
- 否:直接根据 user-key 查询 redis 中数据并展示
- 是:已登录,则需要先根据 user-key 查询 redis 是否有数据。
- 有:需要提交到后台添加到 redis,合并数据,而后查询。 (先将临时购物车中的商品添加到Redis,然后将商品信息合并,查询并返回)
- 否:直接去后台查询 redis ,而后返回。

1.7使用 Rabbit MQ 的延时队列+本地事务,解决分布式事务,实现订单系统。

1.7.1Rabbit MQ 的延时队列 + 本地事务---> 解决分布式事务

多个MQ的属性对比:

提到分布式事务,想到 CAP理论 的延伸——BASE理论(最终一致性)。

(13条消息) 分布式事务(项目)_时光、相遇的博客-CSDN博客

1.7.2 订单系统

1、订单创建与支付
(1) 、订单创建前需要预览订单,选择收货信息等
(2) 、订单创建需要锁定库存,库存有才可创建,否则不能创建
(3) 、订单创建后超时未支付需要解锁库存
(4) 、支付成功后,需要进行拆单,根据商品打包方式,所在仓库,物流等进行拆单
(5) 、支付的每笔流水都需要记录,以待查账
(6) 、订单创建,支付成功等状态都需要给 MQ 发送消息,方便其他系统感知订阅

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,则订单都是要被释放的,然后至于需不需要解锁库存,则是根据订单的状态去进行判断的,如果订单没有支付或者被取消,则此时才需要对库存的解锁。

订单的确认流程:

消息队列的流程,也就是订单的创建和取消,以及库存的解锁全部的流程:

下订单还要有:

幂等性处理:详细的见,高级篇开发文档 接口幂等性。里边涉及到了一系列的 解决接口幂等性的问题。

订单生成后,要引入订单生成的页面,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也要回滚,这种时候就是延时双删来解决。

2.6使用 JMeter 进行高并发压力测试。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光、相遇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值