商城项目技术(不含秒杀)

本文详细介绍了谷粒商城项目中商品上架的流程,包括为何使用ES而非MySQL进行商品信息存储,以及商品检索的步骤。同时,讨论了商品更新与删除的处理方式。此外,还阐述了项目中的登录功能实现,利用OAuth2.0、Redis存储Token、SpringSession和ThreadLocal确保分布式环境下的登录状态一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

谷粒商城项目总结



一、商品上架

1、为什么上架的商品信息不存到数据库?

答:商品上架是将后台的商品放在ES中,为用户提供全文检索功能,因此这个问题就围绕MySQL与ES在全文检索中的优缺点去进行阐述。

MySQLLucene
磁盘IO层面Mysql只有term dictionary这一层,以B+树的数据结构存储在磁盘中,由于数据存储在B+树的叶子节点中,因此检索一个term词项需要若干次的随机磁盘IO,代价昂贵Lucene底层基于倒排索引技术,在term dictionary的基础上添加了term index来加速检索,term index以树的形式(FST)缓存在内存中。首先在内存term index中查到对应的term dictionary的磁盘块block位置之后,再去磁盘上找term,大大减少了磁盘随机IO的次数;
数据结构层面B+树(存储在磁盘)FST(存储在内存),一种变种字典树,不但能共享前缀还能共享后缀;不但能判断查找的key是否存在,还能给出响应的输出(posting list);他在时间复杂度与空间复杂度上都做到了最大程度的优化,使得Luence能够将term dictionary的信息完全加载到内存,以根据读内存快速的定位到term所在的磁盘block位置;
磁盘存储层面磁盘页(16KB)以磁盘块分block的形式进行存储,每个block内部利用公共前缀压缩,可以比b+tree更加节约磁盘空间
读写层面多次随机磁盘IO,而且为了保证数据安全,需要加锁先读内存定位后,再去磁盘找,IO次数少。同时ES底层存储数据的结构是Segment段对象,不可变,不需要加锁且并发性能好。
应用层面① 使用MySQL进行全文检索时,很容易因为左前缀原则等造成索引失效;

②使用MySQL进行全文检索的查询结果相关性差,并且无法与其它属性相关联;
倒排索引技术,更快的查询,且能够通过倒排表posting list进行排序打分,能够给出相关性;

2、上架细节

  • (1)将商品sku字段信息封装成mapping信息,在ES中建立product索引。

    步骤描述
    Step1客户端发送写数据的请求,ES为数据分配一个文档ID;请求可以随机发往任意节点,该节点成为协调节点;
    Step2根据hash算法或者轮询法,计算文档要写入哪个分片(sharding);
    Step3协调节点进行路由,将写数据请求转发给shard_ID所在的ES节点;
    Step4(先写内存)在内存中建立一个索引 (Index),该Index在内存中会形成一个分段对象 (Segment);
    Step5(再写日志) 然后将索引数据写到日志 (Translog) 与 sharding-备份分片
    Step6协调节点确保上述步骤都完成后,返回客户端写入成功的响应;
  • (2)点击上架,首先从数据库中查询到上架商品的全部信息,然后将数据写入product索引,保存到ES中。

  • (3)ES保存成功后,在数据库更新商品的上架状态信息。


2、商品检索过程

  • (4)商品上架成功后。用户可以通过【点击商品分类】、【输入搜索关键字】、【选择筛选条件】三个部分进行检索。

  • (5)用户点击搜索后,将检索条件&&排序条件等信息放在url请求上,传到后台服务器进行检索;

  • (6)服务端根据url传递的参数构建DSL查询语句BoolQueryBuilder类,在ES中进行检索。在ES中的搜索行为是一个【查询后取回 (Query then Fetch)】的过程;

    步骤描述
    Step1首先,后台服务器向ES集群的某一个节点,发送查询请求(关键词key),该节点成为协调节点;
    Step2协调节点将查询请求的key,广播到所有ES节点,这些节点的分片sharding就会各自处理查询请求;
    Step3每个分片根据key进行数据查询,具体步骤如下:

    ① 首先,根据ES指定的分词器,将查询关键词key进行分词 (规范化、去重),分成多个词项term;

    ② 对于每一个词项term,分别根据内存中的term index (FST) 获取包含该term文档所在磁盘块block中的位置,同时输出倒排表Posting List;

    ③ 再对每个词项的倒排表进行合并或者取交集等操作,获得查询关键字最终的倒排表,进行打分排序;
    Step4① Query阶段 (轻量级),每个分片通过查询倒排索引,返回的是文档ID集合,并不是数据】待到全部分片的查询全部结束后,将所有分片的查询结果放入一个队列中,并将这些数据的文档ID、节点信息、分片信息全部返回给协调节点;
    Step5② Scroll分析 (经过轻量级的Query阶段后,免去了繁重的全局排序过程)】,由协调节点将所有的查询结果进行筛选 + 汇总 + 排序;
    Step6③ Fetch阶段 (重量级),将筛选后的数据 (所有信息) 全部取回】,协调节点处理完毕后,向包含筛选后文档ID的分片发送 get() 请求,待取回全部分片的数据后,协调节点将查询结果数据整合起来,返回给客户端;

3、检索商品的更新与删除

  • (7)当商品上架的信息发生变化时,需要对ES中的数据进行更新。但是由于ES底层的文档是通过【不可变的Segment段】存储数据的,因此不能向MySQL那样进行修改后再展示。为此,ES针对删除操作与更新操作是按照如下进行处理的。

    操作描述
    更新将旧的文档标记为deleted状态,然后创建一个新的文档;
    删除文档其实并没有真的被删除,而是在 .del 文件中被标记为 deleted 状态。该文档依然能匹配查询,但是会在结果中被过滤掉;
  • (8)在ES的底层,当Segment段文件大小达到合并阈值时,会对Segment文件进行合并操作,在合并的过程中就会将这写标识了deleted状态的文档给物理删除掉;



二、说一下项目中登录功能是怎么实现的?

关键描述
登录项目中的登录,是通过【OAuth2.0】+【redis存储Token】+【SpringSession】+【ThreadLocal】实现的
OAuth2.0
协议
现在很多网站的登录功能基本都提供了社交登录的操作,比如可以通过微信、支付宝等账号直接进行登录,而不需要注册再登录。我的商城项目中就是通过OAuth2.0协议实现了这种社交登录功能。以微信登录为例,具体的流程如下:

(1)当用户点击微信登录按钮,浏览器会携带者client_id与回调地址访问微信的【认证服务器】。

(2)微信的【认证服务器】验证客户端的合法性之后,生成并返回给浏览器一个用户授权页面。

(3)用户同意授权后(登录微信),此时微信【认证服务器】就给【商城服务器】发放一个code码(此时商城就有资格访问微信的资源服务器了);

(4)【商城服务器】拿着code码与回调地址,发送POST请求访问微信的【资源服务器】,换取access_token(访问令牌);

(5)【商城服务器】拿到access_token后,就可以访问【资源服务器】,从而获取用户到的开放信息。至此,完成社交登录。在这里插入图片描述
ThreadLocal
存储用户登录状态
用户登陆后,登录状态存储在ThreadLocal中,以实现多线程环境下不同用户登录信息的数据隔离。而且通过ThreadLocal,使得我们可以在同一线程中很方便的获取用户信息,而不需要频繁的传递session对象。

ThreadLocal面试题:那你说一下ThreadLocal是如何实现线程的数据隔离的?

①ThreadLocal类只是一个外壳,真正存储数据的是ThreadLocal内部的 ThreadLocalMap<ThreadLocal,value> 结构。ThreadLocalMap的引用是定义在Thread类中的。因此,实际上ThreadLocal本身并不存储数据,它只是作为key来让线程从ThreadLocalMap获取对应的Value。

② 因此,每个线程创建ThreadLocal时,实际上数据是存储在自己线程Thread类内部的threadLocals变量中的,其它线程无法拿到,从而实现了数据隔离。在这里插入图片描述
分布式会话为了保证分布式场景下的登录状态一致性。用户登录之后,通过【SpringSession】 + 【redis】+【token】+【设置cookie跨域】机制来实现。

面试题:那你说一下是怎么实现分布式会话的?

分布式会话实现的原理

难点描述
问题在单机环境下时,由于HTTP协议是无状态的,导致服务器无法记录我们的登录状态,此时我们是可以用【cookies】+【session】解决的。

但是进行分布式扩展后,会发现我们在已经登陆后,分别访问不同的微服务时,系统依然提示我们去登录。
原因针对这个问题,我查阅了相关资料。发现session的本质实际上时tomcat为我们创建的一个对象,它使用ConcurrentHashMap保存属性值。 但是tomcat本质也是一个Java程序,由于每个微服务监听的是不同的端口。那么从一个tomcat容器中(8084)创建的session,另外一个tomcat容器(8085)是肯定获取不到的,因此也就出现了分布式扩展后,提示我们登录的问题。
解决针对分析的原因后,我们可以通过【设置cookie跨域分享】+ 【redis存储token】+ 【SpringSession】的方式解决。解决原理如下:

(1)首先针对tomcat这个问题,我通过定义一个CookieSerializer方法,放大Cookies的作用域,以方便进行跨域。

(2)用户在某个微服务中登录后,将session中的用户登录信息以token的形式存储在redis中。

(3)然后开始引入SpringSession。具体的:

  ① 通过@EnableRedisHttpSession注解,导入RedisHttpSessionConfiguration配置;

  ② 该注解给SpringBoot容器中添加了一个组件SessionRepository,这个类是session的增删改查封装类;

  ③ 配置完成后,每个request请求都会经过一个SessionRepositoryFilter类,这个类是一个增强器,它的内部将原始的request、response进行了包装增强,形成了新的类( SessionRepositoryRequestWrapper、SessionRepositoryResponseWrapper)。

  ④ 经过包装的request,在调用request.getSession()方法时,实际上调用的是SessionRepositoryRequestWrapper的getSession()方法,相当于从redis中获取session。从而实现了分布式session会话。

至此,解决了分布场景下提示登录的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值