项目总结

1.项目整体架构

  • 用户通过客户端向服务器发送请求,请求来到Nginx,再由Nginx将请求转发给Gateway网关
  • 在网关中:
    • 根据请求,动态路由到指定的服务;

    • 在网关这个级别通过Sentinel做熔断降级和限流
      sentinel提供了springcloud gateway的适配模块,并且支持网关流控的专属控制台,可以在控制台中设置网关流控的规则:资源的名就可以写配置的路由id,可以针对请求属性,比如说客户端ip、请求头、URL参数、cookie等进行限流;还可以针对QPS进行限流,流控方式有快速失败匀速排队两种。burst size是限流时允许额外通过的请求数

    • TODO还可以对请求进行认证授权, 请求过来以后,看是否合法,合法了以后再放行

  • Feign:实现了微服务之间的调用
  • OAuth2.0认证中心:实现了登录之后才能处理的请求、以及社交登录
  • 缓存:使用Redis实现了分片集群以及哨兵集群
  • 持久化:使用MySQL集群实现了读写分离、分库分表
  • 消息队列:使用Rabbit MQ集群,实现微服务之间的异步解耦,包括完成分布式事务的最终一致性
  • 全文检索:使用Elastic Search实现
  • 对象存储:使用阿里云的OOS存储服务
  • 日志存储:使用ELK对日志进行相关处理,使用LogStash收集业务里面的各种日志,然后把它存储到ES里,然后ELK使用Kibana可视化插件从ES中检索出相关的日志信息,帮我们快速定位线上问题的所在。
  • Nacos注册中心:实现服务之间的注册与发现
  • Nacos配置中心:实现统一管理配置,实现改一处服务的配置,其它服务都自动修改,所有的服务都可以通过配置中心,动态获取它的配置
  • 分布式事务:使用Seata

2.项目中的微服务是如何划分的?

        按照单一职责原则,将整个项目分为了13个微服务。
  • 后台管理系统是使用人人开源自动生成的前后端代码,对应的是renren-fast模块,实现商品的三级分类、品牌管理、商品属性管理、库存管理、发布与上架、秒杀活动上架等功能;
  • 公共模块——gulimall-common,放了一些其他模块都需要的公共依赖,比如nacos服务发现中心、nacos配置中心、sentinel等,让其余模块都依赖该模块;
  • 网关模块——gulimall-gateway,网关可以根据当前请求动态路由到指定的服务;如果从路由过来的请求很多,还可以借助Nginx进行负载均衡;网关还可以在某个服务器出问题时,对服务进行熔断降级,以及在网关进行限流
  • 商品服务——gulimall-product,商品的分类信息、品牌信息等;
  • 库存服务——gulimall-ware,查询商品库存,进行库存的锁定和释放等;
  • 检索服务——gulimall-search;
  • 认证服务——gulimall-auth-server,使用阿里云实现短信验证码注册,使用微博(oAuth2.0)社交登录;
  • 第三方服务——gulimall-third-party,使用阿里云对象存储图片,使用阿里云短信服务实现短信验证码注册;
  • 购物车服务——gulimall-cart,添加购物车、实现两种登陆状态的购物车;
  • 订单服务——gulimall-order,实现订单确认、订单提交等功能;
  • 会员服务——gulimall-member,使用MD5盐值加密保存用户信息;
  • 优惠券服务——gulimall-coupon;
  • 秒杀服务——gulimall-seckill,秒杀商品的上架、进行秒杀。

3.项目中怎么用的Redis?各种Redis数据类型的在项目中的使用场景?

(1)项目中Redis的使用场景

        在项目中用Redis+ThreadLocal实现了购物车功能;通过令牌机制,将token存在Redis中保证接口的幂等性;在实现社交登录时,为了解决session共享问题,将session同一存在了Redis中;在实现秒杀时,将秒杀商品信息缓存到Redis中。

(2)Redis数据类型的使用场景

        Redis常用的数据类型有String、List、Set、哈希表、ZSet。
  • String用来存token、三级分类的信息。
    将三级分类的信息转成json字符串,然后存到redis里。

  • List:在上架秒杀商品时,采用定时任务提前查好每场秒杀需要哪些商品,以场次的起止时间作为大key,用List来保存value,也就是当前场次涉及到的skuId。

  • 保存购物车数据时,采用的哈希表。购物车中每一件商品包括自己的基本信息,比如商品的skuId、商品的价格、数量等,而且购物车里有很多件商品,所以购物车数据结构应该是一个大数组,然后每个元素都是一件商品哈希表的key对应skuId(整个大Key是用户id),value对应商品的其他信息,这样就可以根据skuId快速获取对应的商品

为什么不用List存购物车数据?

        因为后续还要往购物车里添加和删除商品,包括修改商品数量,如果用List存的话,我们就要取出整个List,然后找到要操作的商品数据,最后再把整个List写回Redis。相比之下,用Hash存就方便多了,可以通过商品的skuId,直接获取到对应的商品数据。
  • 保存session时,也采用的hash表,因为session本身也是个map,它的key有我们存Attribute时给的名字,还有创建时间、上次访问时间等信息

  • 缓存秒杀商品时,也用Hash:

  • Set的话没有用到,但是Set主要的特点就是元素不唯一嘛,所以可以用来去重,也可以用来实现接口的幂等性。
  • ZSet也没有用到,如果让我优化一下的话,我能想到的就是利用ZSet实现排行榜这样一个功能。

4.★秒杀系统怎么用Redis进行的优化?最终具体怎么实现的?

        (在上架秒杀商品时,需要操作数据库来查询商品信息,如果等到秒杀活动开始时才上架要秒杀的商品,肯定是不合理的。
        我是通过异步执行定时任务(加分布式锁来保证同一场只能上架一次),在服务器压力小的时候将最近三天的秒杀场次以及需要的商品信息缓存到Redis。先查询数据库得到最近三天所有的场次信息以及关联的商品id ,如果最近三天有秒杀活动的话,就需要把场次信息商品信息都保存到Redis中。缓存场次信息是以场次的起止时间作为大key,场次涉及到的skuId作为value存到Redis中,value用list存;缓存商品信息是以skuId作为大key,用hash保存value,hash的key是场次信息+skuId,value是商品的秒杀信息。
        
        然后在扣减库存时,不是通过秒杀时的实时扣减库存,而是通过分布式信号量,在上架商品时把商品的秒杀库存作为一个信号量。所以还需要将信号量缓存到Redis。这样起到了一个限流的作用,因为假如说只有100件商品,有100万个秒杀请求,如果实时扣减库存的话,数据库压力太大了;将这100个库存缓存到Redis后,可以保证最多只有100条秒杀请求去访问数据库扣减库存。
  • 分布式信号量要用Redisson的getSemaphore()方法获取,然后通过trySetPermits(库存数量)设置库存。
【tips】每天凌晨3点定时:0 0 3 * * ?。

5.Redis SortedSet(ZSet)的实现原理?为什么用跳表不用红黑树、平衡二叉树等平衡树?

        Zset 的底层实现用到了跳表,使用跳表进行查找的平均时间复杂度为 O(logN)
        跳表是在对链表做了改进,实现了多层有序,不像链表每次都要从头查找。跳表可以有多个层,每一层的多个节点间用指针连接,节点保存了数据和对应的权重,权重越小离头结点越近,当查找一个数据时,会先从头结点最高层逐层遍历节点,如果当前节点权重比要找的节点的权重小或者权重相等但值小,就继续向后遍历;否则就会跳到下一层继续遍历。

为什么 Zset 的实现用跳表不用平衡树(如 AVL树、红黑树等)

  • 跳表比平衡树内存占用更少。平衡树每个节点包含指向左右子树的2 个指针,而跳表每个节点包含的指针数目平均为 1/(1-p),具体取决于参数 p 的大小。如果像 Redis里的实现一样,取 p=1/4,那么平均每个节点包含 1.33 个指针,比平衡树更有优势。
  • 在做范围查找的时候,跳表比平衡树操作要简单。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。而在跳表上进行范围查找就非常简单,只需要在找到小值之后,对第 1 层链表进行若干步的遍历就可以找到其他不超过大值的节点。
  • 更新操作时,跳表比平衡树要简单得多。平衡树的插入和删除操作可能导致子树的调整,逻辑复杂;而跳表的插入和删除只需要修改相邻节点的指针,操作简单快速。

6.如何保证接口的幂等性?

(1)如何保证?

        通过令牌机制,在服务器生成一个token,保存到Redis中,同时让客户端在发送请求时带上这个token,并与缓存的token进行比较,二者相同才能请求通过,而且只要这次请求通过了,就把Redis中的token删除,这样客户端再发起两次三次请求时,就在Redis查不到对应的token了,以此来实现接口的幂等性。

(2)★删除token的时机

        我选择的是先删除token,再执行业务。因为如果先执行业务,再删除token的话,如果业务执行时间比较长,还没执行完又来了一个请求,此时token还没删,就会再执行一遍业务,无法保证幂等了
        同时使用lua脚本(script),来保证获取token、判断token是否存在、删除token这三个操作的原子性,防止高并发下重复获取token的情况。

7.★下单的流程?如何防止重复提交订单?

(1)下单流程
        从订单页点击提交订单后,会先验token,通过了就创建订单,然后锁定对应商品的库(远程调用库存服务,不是真正扣减),如果锁库存成功,就跳转到支付页面;如果库存不足锁失败了,就回滚创建的订单。
        选中商品添加到购物车,点击去结算,来到结算详情页,点击去支付,这样的话库存就会锁定,订单如果出现异常或者不支付就会通过消息队列进行通知,让库存解锁。
2)防止重复提交订单(还是幂等性的问题。)
        通过令牌机制+唯一索引结合Redis的来实现的。
        首先是令牌机制:在提交订单之前(渲染订单页时),先由服务器生成一个uuid作为token,并把用户id作为key,token作为value用存到Redis中。提交订单时带上token,根据当前用户id查询Redis,如果能查到对应的token,说明是第一次提交订单,把Redis中的token删除继续执行提交订单的业务;如果查不到token,就说明是重复提交订单。
        
        然后在数据库中把订单号字段设置为唯一索引,这样如果重复下单,由于订单号重复了所以就会创建失败。
【tips】在购物车里点击去结算会给后台发送请求,服务器需要查询出地址信息、计算订单总金额等订单信息进行订单详情的展示,还会生成一个token返回给客户端

提交订单都提交了哪些数据?(一个订单包含哪些信息?)

        用户的收货地址、支付方式、要买的商品信息、优惠信息、订单总金额、token等。

锁库存的流程?

        将订单中的每个商品进行锁库存,只要有一个商品没锁成功,就回滚订单和所有的商品库存。只要锁库存失败就抛一个异常,通过@Transactional()来捕获,然后进行回滚。
        锁库存的sql语句是个更新操作(扣减库存也一样):
  • 锁库存:update 库存表 set stock_locked(商品已经锁了几件) = stock_locked+购买数量 where sku_id = ? and stock(总库存)- stock_locked>=购买数量;
  • 扣库存:update 库存表 set stock = stock-1 where sku_id = ? and stock > 1;

8.如果库存扣减成功了(优惠服务查询失败导致)订单创建失败怎么办?

        通过RabbitMQ来实现订单和库存的最终一致性。
        订单创建完后无论成功还是失败都给交换机发送一个消息,然后通过交换机转发给库存mq库存服务监听到这个消息以后,就根据消息进行判断需不需要回滚库存,如果订单状态是创建失败,就进行库存回滚的操作,根据订单的商品数再把库存加回去。

9.哪里用到了拦截器(Interceptor)?拦截器怎么实现的?多个拦截器的执行顺序?

(1)使用场景

        主要是使用拦截器实现登录拦截功能,在执行一些业务之前,比如进入购物车、操作订单、秒杀等,要求用户要先登录。
        在执行业务前,用拦截器中的preHandler()根据用户的不同登录状态进行不同操作,先从共享Session中获取到的当前用户的登录信息,判断当前用户是否登录,如果用户登录了,可以对用户信息进行封装,保存在ThreadLocal中,方便后续使用,然后放行;如果用户未登录,就跳转(response.sendRedirect())到登录页面。

(2)拦截器的实现

  • 首先要创建一个拦截器类,实现HandlerInterceptor接口,并重写三个方法,可以分别在业务执行前preHandler(真正起着"拦截"功能)、业务执行后postHandler以及postHandler执行后afterCompletion进行处理,比如说要实现登录拦截,就是重写了preHandler方法。

  • 然后要定义一个配置类实现WebMvcConfigurer接口,添加之前创建的拦截器,并配置要拦截的路径

(3)执行顺序

        当有多个拦截器时,会按照在配置类中的添加顺序运行拦截器,比如说按照拦截器1、2、3的顺序添加,假设所有的pre都返回成功了,那么会按照pre1、pre2、pre3、目标方法post3、post2、post1after3、after2after1的顺序执行;但是只要pre发生拦截了,不会执行后续的pre、目标方法和postHandler,直接倒序执行所有执行过的拦截器的after
        

10.为什么要用md5加密?为什么需要盐值?

        md5是一种不可逆的加密算法,它的压缩性比较好,对于任意长度的数据,它的md5长度都是固定的。
        
        md5虽然说是不可逆的,但是在网上有一种暴力解法,一些网站通过提前收集大量数据的md5值,来进行暴力解密,所以单纯的md5加密也是不安全的。所以我用到了盐值加密,盐值加密的原理就是说给明文拼上一段随机字符,然后再用md5加密。Spring提供了一个基于盐值MD5加密的编码类——BCryptPasswordEncoder,可以直接实现利用md5+盐值对密码加密(encode())和验证(matches())
        

11.第三方登录(社交登录)涉及到哪些设计模式?

(1)适配器(Adapter)模式。
        登录涉及到用户名密码登陆以及第三方登录——微博、qq、微信登陆,这个时候就需要适配器模式了,可以在不改变原来登陆逻辑的基础上,与新的第三方登陆做一个适配。可以创建一个第三方登录的适配器,在适配器里创建不同第三方登录的方法,然后调用原来的登录逻辑。
        
(2)策略模式
        如果单纯为每种第三方登录创建一个方法调用原来的登录逻辑的话,耦合度太高了,如果想添加新的三方登录的话就不利于扩展。因此可以利用策略模式,把不同的第三方登录抽成不同的策略,但是都是实现登录这一个功能。
        
(3)工厂模式
        工厂模式就是创建一个工厂类,根据传入参数的不同,返回不同的实例。

12.工厂模式有几种实现?

        工厂模式分为简单工厂模型、工厂方法、抽象工厂模式。
(1)简单工厂模型
        又叫静态工厂模式,它属于类创建型模式简单工厂模式专门定义一个工厂类来负责创建其他类的实例,可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
        在Java中的DateFormat类就用了简单工厂模式:
        

        简单工厂模式代码演示:
  • 创建共同父类Car
  • 创建产品类(工厂能返回的实例)


  • 创建工厂类,根据传入参数的不同(车的牌子),决定返回哪种实例

(2)工厂方法模式
        在这种模式中工厂类不再负责所有产品的创建,而是将创建工作交给子类去做,由各自产品的工厂类创建对应的实例
        Java中的Collection就是用了工厂方法模式。
        工厂方法模式代码演示:
  • 创建Car
  • 创建工厂方法

  • 创建产品类
  • 创建产品的工厂类(工厂子类)


  • 通过不同的产品工厂创建实例

(3)抽象工厂模式
        抽象工厂模式简单来说就是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来生产具体产品。
  • 创建第一个产品类

  • 创建第二个产品力类

  • 创建一个总工厂,及实现类(由总工厂的实现类决定调用那个工厂的那个实例)
  • 运行测试

13.对MySQL的慢查询(slowQuery)如何排查和优化?

(1)如何排查?
        可以通过开启慢查询日志(slow_query_log)来查看有哪些sql执行时间超过了我们设定的值(long_query_time)
  • 开启慢查询日志(默认关闭)
    set global slow_query_log=on;
(2)如何优化?
        可以给sql语句加上索引,以及避免造成索引失效

14.spring gateway的路由规则?

        gateway可以使用断言实现路由。常见的断言有:
        

15.项目中的配置文件如何被其他模块复用?

        创建了一个公共模块common,在公共模块中把配置文件按照 application-服务名 命名,然后在这个配置文件中配置profiles: 服务名。要让其他模块能读取公共模块中的配置文件,首先要依赖这个公共模块,然后创建自己的application文件,然后再配置profiles:active: 服务名,就可以引入公共模块中对应的配置文件中的内容了。
                     

16.XSS攻击了解吗?

        XSS攻击是利用html标签的特点,在页面中插入恶意的html代码,当用户访问该页面时,恶意代码就会执行,从而达到恶意攻击用户的目的。

17.Redis集群通过key获取value的过程了解吗?(获取数据的过程)

        在Redis集群中,每个节点都会保存槽信息在每个节点中,都会保存自己处理哪些槽数据,其他节点处理哪些槽数据。比如Redis集群默认有16384个槽,假设node0保存了0-4000槽数据,node1保存了4001-8000槽数据,node2保存了8001-16383槽数据,node0保存了0-4000由node0处理,4001-8000由node1处理,8001-16383由node2处理。当客户端请求过来,如果首先到达node0,根据这个key计算出槽节点为10086,所在的槽并不在node0 节点上,node0会查出10086该由哪个节点(node1)处理,然后给客户端返回一个MOVED错误以及那个节点的地址(如下),这样客户端就知道该再去请求node1了。
        

18.购物车实现的流程?

        购物车的功能主要包括进入购物车添加购物车以及删除商品等功能。将购物车数据保存在Redis中(可以加快购物车的读写性能,提升用户体验)。购物车模块主要封装了两个vo,一个是购物车vo,一个是购物车中的商品vo。
  • 当我们点击进入购物车的时候,首先要保证用户是登录状态,所以在进入购物车之前,先通过拦截器做登录拦截(通过session获取用户信息,用户信息为空则未登录),如果登录了就把用户信息放到ThreadLocal里,然后放行,如果没登录就跳转到登陆页面。
  • 登录放行了后来到controller,先从ThreadLocal中拿到当前登录的用户信息,然后根据用户信息去Redis中查询对应的购物车信息,在页面中展示。
  • 添加购物车时,先判断如果Redis中已经存储了该商品,就直接修改数量,如果没有的话,就远程调用商品服务,查出商品信息保存到Redis中。
  • 删除商品,就是根据skuId从Redis中删除对应的商品信息。

19.为什么采用前后端分离?有什么优势?

        在实际开发中,采用前后端分离,可以使前端和后端的工作同时进行,提高了工作效率,分工更加明确,前端只需要关注前端的工作,后端只需要关注具体的业务逻辑。
        把静态资源放在前端,动态资源放在后台,这样一来一旦服务器挂了,前端也能显示页面,只不过加载不出来数据罢了,同时还能减轻服务器的负担。

20.项目的整个流程介绍一下?

        用户通过浏览器发送请求,请求先来到Nginx,然后由Nginx将请求转发给网关,网关就可以根据当前请求路由到对应的服务,比如商品服务、购物车服务、订单服务等。假如这个服务挂了,还用了sentinel在网关对服务做了简单的熔断降级。有的服务还需要远程调用其他服务,远程调用是用Feign来实现的。像下订单这样的请求必须登录才能继续操作,我是用了拦截器实现登录拦截功能,登录除了正常的用户名+密码登录,还使用OAuth2实现了社交登录

21.项目数据存储的解决方案?

        我是使用MySQL实现数据的持久化存储,用Redis做了缓存,使用rabbitmq来实现事务的最终一致性,像商品的检索使用的是es,图片使用阿里的OSS对象存储服务。

22.消息队列mq在项目中是怎么用的?

        作为异步下单的中间件,实现解锁库存定时关单
(1)解锁库存
        (超时未支付、用户自己取消订单、锁完库存但是其他远程调用失败了导致的订单回滚都要解锁库存)
        当需要解锁库存的时候,比如说订单回滚了,就给交换机发送一个消息,然后交换机根据路由键转发给解锁mq库存解锁服务监听到这个消息以后,就根据消息进行库存解锁的操作。——可以涉及到方法重载
【库存工作单表】记录了库存锁了多少,是哪个订单锁的,方便回滚。
(2)定时关单
        当订单创建成功了,就给交换机发送(rabbitTemplate.convertAndSend())一个消息(订单实体对象),然后交换机根据路由键将消息转发到延迟队列,延迟队列设置了30min的过期时间,当30分钟一过,延迟队列会把过期的消息,再发给交换机,由交换机转发给关单的消息队列,由关单服务进行监听,关单服务拿到消息以后要先判断订单状态,如果是待支付状态,就进行关单,把订单状态设置为已取消;如果是其他状态就不用操作了。

23.如何保证消息的可靠性(如何解决消息丢失问题)?

        消息确认机制+失败重试机制。
        消息丢失可能发生在从生产者到交换机、从交换机到mq以及消费者消费这三个地方,如果说没收到ConfirmCallback,说明消息没有到交换机,如果收到了returnCallBack说明没有到mq,这是从生产者角度来说的一种确认机制。然后消费者的话可以进行手动确认,只有消息消费成功了才回发Ack。
        如果通过消息确认机制发现消息丢失了,就要进行失败重试,我是通过把每个消息的信息,消息的路由键、发到哪个交换机、消息的状态等信息保存在mysql中,如果发现消息丢失了,就查询数据库中消息状态为失败的消息,然后重发该消息。

24.如果出现消息积压怎么办?

        出现消息积压说明消费者消费能力比不过生产者的生产能力,可以增加更多的消费者,提高消费速度;
        可以通过惰性队列,提高mq中消息存储上限。但是惰性队列是基于磁盘的,所以它的时效性可能差一点,性能受磁盘io的影响。

25.如果没来得及回发ack消费者就挂了,这时候消息重复消费怎么办?

        可以给消息一个唯一id,通过Redis的setnx命令,在消费消息时,以消息id为key用setnx把消息存入Redis,如果消息没被消费过,就会成功保存到Redis中,然后进行消费;如果保存失败返回0了,说明Redis中已经有这条消息了,也就是消息被消费过了。

26.社交登录的流程?

        用户点了weibo登录后,会跳转到一个授权登录页,然后用户输入自己的账号密码,发给weibo服务器进行验证,如果验证成功就会给项目服务器返回token(访问令牌)和用户的uid,然后保存在Redis中(通过配置文件设置session的存储类型为Redis会自动保存到Redis)。然后根据uid来判断当前用户是不是第一次社交登陆,如果是第一次的话就把uid、token和用户的其他信息注册到会员服务中,然后跳转到登陆成功页面,如果不是第一次登陆就直接拿到用户信息回到登陆成功页面。

27.session是怎么获取的?sessionId是怎么识别的?这个流程是怎么样的?

        通过 HttpSession 来获取session。session本身就是k-v结构,以用户的uid作为session的key,以用户其他信息作为value(setAttribute())。
        Tomcat中session是以map的形式保存的,key就是sessionId,通过sessionId就能找到对应的session。
        用户第一次发送请求时,服务器发现请求数据里的Cookie请求头没有Session_id,说明该用户是第一次访问,就为该用户创建一个Session,并生成一个对应的Session_id,通过Response返回给用户,用户就把sessionId存到Cookie里。这样以后该用户再发送请求时,服务器就可以拿到cookie里的sessionId,根据sessionId找到对应的session。

(1)session和cookie的区别?

  • 存放的位置不同:cookie存放在客户端浏览器中,session存放在服务器上。
  • 存储的数据大小不同:cookie存储的数据大小一般不超过4kb,session的存储上限主要取决于服务器的内存大小
  • 存储的类型不同:cookie和session都是k-v结构,但是value的类型不同。cookie的value只能存字符串类型,而session可以存object类型
  • 生命周期不同
    • cookie可以通过setMaxAge()来设置存活时间,而且cookie的生命周期是累计的,比如设置了20min过期,从cookie创建开始计时,经过20min后cookie就销毁了(这期间关闭了浏览器再打开cookie也还在);如果没有设置setMaxAge,浏览器关闭的时候,cookie就消失了;
    • session默认过期时间是30min,而且只要被访问了,session的过期时间就会重新计时
  • 安全性方面:cookie不是很安全,别人可以从本地cookie中获取sessionId、用户名和密码等信息;session保存在服务器上,理论上是可靠的,但是如果攻击者获取到了sessionId,就能伪装成已登录用户访问服务器。

(2)session的生命周期?

        session的生命周期分为创建和销毁session。
        当客户端第一次访问服务器时,服务器会为该客户端创建一个session,并生成一个sessionId返回给客户端,保存在cookie中。
        如果设置了过期时间,同时在这段时间内没有再次访问,那么到了过期时间后session就会销毁;也可以调用invalidate()来手动销毁session。

(3)session的工作原理?

        Session是基于Cookie实现的。
        当客户端第一次访问服务器时,服务器会为该客户端创建一个Session,并生成一个sessionId,在响应时存储在客户端的cookie中;这样以后客户端访问服务器,都会带上这个cookie,服务器就会根据cookie中的sessionId来获取对应的session数据。

(4)如果客户端禁用了cookie,session还能生效吗?

        不能。因为session是基于cookie实现在客户端和服务器之间传输sessionId的,所以如果客户端禁用了cookie,服务器就获取不到sessionId了。
        如果禁用了cookie,可以通过url重写来实现session。url重写就是把sessionId拼接在url的后面,通过url在客户端和服务器之间传输这个sessionid。
        url重写使用response.encodeURL()

28.项目里怎么用es的?es怎么支持搜索的?

        主要用es实现商城首页的三级分类的查找,以及在搜索框输入要搜索商品的名字,通过查es返回数据。
        es是通过倒排索引进行查询。es在保存数据时,先将数据进行分词得到若干个词条,然后把词条和词条对应的文档id等信息记录下来。当我们要查询一条数据时,es会先把我们输入的条件进行分词,然后拿着分词后的词条在倒排索引中查找,然后得到对应的文档id,再根据文档id进行正向索引的查找。

为什么不直接使用正向查询?倒排索引比正向索引好在哪里?

        像我们在搜一个商品的时候,肯定不是拿着商品id去查,而是输入商品的名字,这就相当于模糊查询。如果是正向索引的话,模糊查询需要全表扫描,查询效率比较低。而倒排索引是先分词,然后根据分词后的词条进行查询,再得到文档id,这时候再用文档id查询,就像正向索引那样,根据索引直接拿到数据,效率就很快了。

29.项目中有用到AOP吗?

        项目中用到AOP的主要有统一异常处理
        我使用的是@ControllerAdvice注解。先抽取一个统一异常处理类,然后加上@ControllerAdvice注解,在这个类里可以针对不同的异常的处理方法,用@ExceptionHandler来捕获对应的异常。

30.项目中哪里用到动态代理了?

        AOP就是基于动态代理实现的。

31.项目用的什么框架?

  • 业务部分用了SpringBoot,开发独立的微服务
  • 使用SpringCloud进行微服务间的协调和管理:
    nacos——服务注册发现
    openFeign——远程调用
    SpringCloudGateway——网关
    sentinel——限流、降级、保护
  • 使用mybatis操作数据库。

32.用的什么数据库?框架和数据库连接用的什么?

(1)用的关系型数据库MySQL
(2)框架和数据库连接用的Druid+JDBC,通过mybatis进行连接参数的配置。
        连接数据库最基本的信息有:连接数据库所需的驱动URL、用户名和密码。
        














        









全部评论

相关推荐

赏个offer求你了:友塔HR还专门加我告诉我初筛不通过😂
点赞 评论 收藏
分享
1 31 评论
分享
牛客网
牛客企业服务