第三方数据延迟取不出来 消息队列mq在项目中是怎么用的?
1.项目整体架构
Feign:实现了微服务之间的调用 .0认证中心:实现了登录之后才能处理的请求、以及社交登录 缓存:使用Redis实现了分片集群以及哨兵集群 持久化:使用MySQL集群实现了读写分离、分库分表 消息队列:使用 MQ集群,实现微服务之间的异步解耦,包括完成分布式事务的最终一致性 全文检索:使用 实现 对象存储:使用阿里云的OOS存储服务 日志存储:使用ELK对日志进行相关处理,使用收集业务里面的各种日志,然后把它存储到ES里,然后ELK使用可视化插件从ES中检索出相关的日志信息,帮我们快速定位线上问题的所在。 Nacos注册中心:实现服务之间的注册与发现 Nacos配置中心:实现统一管理配置,实现改一处服务的配置,其它服务都自动修改,所有的服务都可以通过配置中心,动态获取它的配置 分布式事务:使用Seata 2.项目中的微服务是如何划分的?
按照单一职责原则,将整个项目分为了13个微服务。
3.项目中怎么用的Redis?各种Redis数据类型的在项目中的使用场景? (1)项目中Redis的使用场景
在项目中用Redis+实现了购物车功能;通过令牌机制,将token存在Redis中保证接口的幂等性;在实现社交登录时,为了解决共享问题,将同一存在了Redis中;在实现秒杀时,将秒杀商品信息缓存到Redis中。
(2)Redis数据类型的使用场景
Redis常用的数据类型有、List、Set、哈希表、ZSet。
为什么不用List存购物车数据?
因为后续还要往购物车里添加和删除商品,包括修改商品数量,如果用List存的话,我们就要取出整个List,然后找到要操作的商品数据,最后再把整个List写回Redis。相比之下,用Hash存就方便多了,可以通过商品的skuId,直接获取到对应的商品数据。
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条秒杀请求去访问数据库扣减库存。
【tips】每天凌晨3点定时:0 0 3 * * ?。
5.Redis (ZSet)的实现原理?为什么用跳表不用红黑树、平衡二叉树等平衡树?
Zset 的底层实现用到了跳表,使用跳表进行查找的平均时间复杂度为O(logN)。
跳表是在对链表做了改进,实现了多层有序,不像链表每次都要从头查找。跳表可以有多个层,每一层的多个节点间用指针连接,节点保存了数据和对应的权重,权重越小离头结点越近,当查找一个数据时,会先从头结点最高层逐层遍历节点,如果当前节点权重比要找的节点的权重小或者权重相等但值小,就继续向后遍历;否则就会跳到下一层继续遍历。
为什么 Zset 的实现用跳表不用平衡树(如AVL树、红黑树等)?
6.如何保证接口的幂等性? (1)如何保证?
通过令牌机制,在服务器生成一个token,保存到Redis中,同时让客户端在发送请求时带上这个token,并与缓存的token进行比较,二者相同才能请求通过,而且只要这次请求通过了,就把Redis中的token删除,这样客户端再发起两次三次请求时,就在Redis查不到对应的token了,以此来实现接口的幂等性。
(2)★删除token的时机
我选择的是先删除token,再执行业务。因为如果先执行业务,再删除token的话,如果业务执行时间比较长,还没执行完又来了一个请求,此时token还没删,就会再执行一遍业务,无法保证幂等了。
同时使用lua脚本(),来保证获取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等。
锁库存的流程?
将订单中的每个商品进行锁库存,只要有一个商品没锁成功,就回滚订单和所有的商品库存。只要锁库存失败就抛一个异常,通过@()来捕获,然后进行回滚。
锁库存的sql语句是个更新操作(扣减库存也一样):
8.如果库存扣减成功了(优惠服务查询失败导致)订单创建失败怎么办?
通过来实现订单和库存的最终一致性。
订单创建完后无论成功还是失败都给交换机发送一个消息,然后通过交换机转发给库存mq,库存服务监听到这个消息以后,就根据消息进行判断需不需要回滚库存,如果订单状态是创建失败,就进行库存回滚的操作,根据订单的商品数再把库存加回去。
9.哪里用到了拦截器()?拦截器怎么实现的?多个拦截器的执行顺序? (1)使用场景
主要是使用拦截器实现登录拦截功能,在执行一些业务之前,比如进入购物车、操作订单、秒杀等,要求用户要先登录。
在执行业务前,用拦截器中的()根据用户的不同登录状态进行不同操作,先从共享中获取到的当前用户的登录信息,判断当前用户是否登录,如果用户登录了,可以对用户信息进行封装,保存在中,方便后续使用,然后放行;如果用户未登录,就跳转(.())到登录页面。
(2)拦截器的实现(3)执行顺序
当有多个拦截器时,会按照在配置类中的添加顺序运行拦截器,比如说按照拦截器1、2、3的顺序添加,假设所有的pre都返回成功了,那么会按照pre1、pre2、pre3、目标方法、post3、post2、post1、、、的顺序执行;但是只要pre发生拦截了,不会执行后续的pre、目标方法和,直接倒序执行所有执行过的拦截器的after。
10.为什么要用md5加密?为什么需要盐值?
md5是一种不可逆的加密算法,它的压缩性比较好,对于任意长度的数据,它的md5长度都是固定的。
md5虽然说是不可逆的,但是在网上有一种暴力解法,一些网站通过提前收集大量数据的md5值,来进行暴力解密,所以单纯的md5加密也是不安全的。所以我用到了盐值加密,盐值加密的原理就是说给明文拼上一段随机字符,然后再用md5加密。提供了一个基于盐值MD5加密的编码类——r,可以直接实现利用md5+盐值对密码加密(())和验证(()):
11.第三方登录(社交登录)涉及到哪些设计模式?
(1)适配器()模式。
登录涉及到用户名密码登陆以及第三方登录——微博、qq、微信登陆,这个时候就需要适配器模式了,可以在不改变原来登陆逻辑的基础上,与新的第三方登陆做一个适配。可以创建一个第三方登录的适配器,在适配器里创建不同第三方登录的方法,然后调用原来的登录逻辑。
(2)策略模式
如果单纯为每种第三方登录创建一个方法调用原来的登录逻辑的话,耦合度太高了,如果想添加新的三方登录的话就不利于扩展。因此可以利用策略模式,把不同的第三方登录抽成不同的策略,但是都是实现登录这一个功能。
(3)工厂模式
工厂模式就是创建一个工厂类,根据传入参数的不同,返回不同的实例。
12.工厂模式有几种实现?
工厂模式分为简单工厂模型、工厂方法、抽象工厂模式。
(1)简单工厂模型
又叫静态工厂模式,它属于类创建型模式。简单工厂模式专门定义一个工厂类来负责创建其他类的实例,可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
在Java中的类就用了简单工厂模式:
简单工厂模式代码演示:
(2)工厂方法模式
在这种模式中,工厂类不再负责所有产品的创建,而是将创建工作交给子类去做,由各自产品的工厂类创建对应的实例。
Java中的就是用了工厂方法模式。
工厂方法模式代码演示:
(3)抽象工厂模式
抽象工厂模式简单来说就是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来生产具体产品。
13.对MySQL的慢查询()如何排查和优化?
(1)如何排查?
可以通过开启慢查询日志()来查看有哪些sql执行时间超过了我们设定的值()。
(2)如何优化?
可以给sql语句加上索引,以及避免造成索引失效。
14. 的路由规则?
可以使用断言实现路由。常见的断言有:
15.项目中的配置文件如何被其他模块复用?
创建了一个公共模块,在公共模块中把配置文件按照 -服务名命名,然后在这个配置文件中配置: 服务名。要让其他模块能读取公共模块中的配置文件,首先要依赖这个公共模块,然后创建自己的文件,然后再配置:: 服务名,就可以引入公共模块中对应的配置文件中的内容了。
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。
19.为什么采用前后端分离?有什么优势?
在实际开发中,采用前后端分离,可以使前端和后端的工作同时进行,提高了工作效率,分工更加明确,前端只需要关注前端的工作,后端只需要关注具体的业务逻辑。
把静态资源放在前端,动态资源放在后台,这样一来一旦服务器挂了,前端也能显示页面,只不过加载不出来数据罢了,同时还能减轻服务器的负担。
20.项目的整个流程介绍一下?
用户通过浏览器发送请求,请求先来到Nginx,然后由Nginx将请求转发给网关,网关就可以根据当前请求路由到对应的服务,比如商品服务、购物车服务、订单服务等。假如这个服务挂了,还用了在网关对服务做了简单的熔断降级。有的服务还需要远程调用其他服务,远程调用是用Feign来实现的。像下订单这样的请求必须登录才能继续操作,我是用了拦截器实现登录拦截功能,登录除了正常的用户名+密码登录,还使用实现了社交登录。
21.项目数据存储的解决方案?
我是使用MySQL实现数据的持久化存储,用Redis做了缓存,使用来实现事务的最终一致性,像商品的检索使用的是es,图片使用阿里的OSS对象存储服务。
22.消息队列mq在项目中是怎么用的?
作为异步下单的中间件,实现解锁库存和定时关单。
(1)解锁库存
(超时未支付、用户自己取消订单、锁完库存但是其他远程调用失败了导致的订单回滚都要解锁库存)
当需要解锁库存的时候,比如说订单回滚了,就给交换机发送一个消息,然后交换机根据路由键转发给解锁mq,库存解锁服务监听到这个消息以后,就根据消息进行库存解锁的操作。——可以涉及到方法重载
【库存工作单表】记录了库存锁了多少,是哪个订单锁的,方便回滚。
(2)定时关单
当订单创建成功了,就给交换机发送(.())一个消息(订单实体对象),然后交换机根据路由键将消息转发到延迟队列,延迟队列设置了30min的过期时间,当30分钟一过,延迟队列会把过期的消息,再发给交换机,由交换机转发给关单的消息队列,由关单服务进行监听,关单服务拿到消息以后要先判断订单状态,如果是待支付状态,就进行关单,把订单状态设置为已取消;如果是其他状态就不用操作了。
23.如何保证消息的可靠性(如何解决消息丢失问题)?
消息确认机制+失败重试机制。
消息丢失可能发生在从生产者到交换机、从交换机到mq以及消费者消费这三个地方,如果说没收到,说明消息没有到交换机,如果收到了说明没有到mq,这是从生产者角度来说的一种确认机制。然后消费者的话可以进行手动确认,只有消息消费成功了才回发Ack。
如果通过消息确认机制发现消息丢失了,就要进行失败重试,我是通过把每个消息的信息,消息的路由键、发到哪个交换机、消息的状态等信息保存在mysql中,如果发现消息丢失了,就查询数据库中消息状态为失败的消息,然后重发该消息。
24.如果出现消息积压怎么办?
出现消息积压说明消费者消费能力比不过生产者的生产能力,可以增加更多的消费者,提高消费速度;
可以通过惰性队列,提高mq中消息存储上限。但是惰性队列是基于磁盘的,所以它的时效性可能差一点,性能受磁盘io的影响。
25.如果没来得及回发ack消费者就挂了,这时候消息重复消费怎么办?
可以给消息一个唯一id,通过Redis的setnx命令,在消费消息时,以消息id为key用setnx把消息存入Redis,如果消息没被消费过,就会成功保存到Redis中,然后进行消费;如果保存失败返回0了,说明Redis中已经有这条消息了,也就是消息被消费过了。
26.社交登录的流程?
用户点了weibo登录后,会跳转到一个授权登录页,然后用户输入自己的账号密码,发给weibo服务器进行验证,如果验证成功就会给项目服务器返回token(访问令牌)和用户的uid,然后保存在Redis中(通过配置文件设置的存储类型为Redis会自动保存到Redis)。然后根据uid来判断当前用户是不是第一次社交登陆,如果是第一次的话就把uid、token和用户的其他信息注册到会员服务中,然后跳转到登陆成功页面,如果不是第一次登陆就直接拿到用户信息回到登陆成功页面。
27.是怎么获取的?是怎么识别的?这个流程是怎么样的?
通过 来获取。本身就是k-v结构,以用户的uid作为的key,以用户其他信息作为value(())。
在中是以map的形式保存的,key就是,通过就能找到对应的。
用户第一次发送请求时,服务器发现请求数据里的请求头没有,说明该用户是第一次访问,就为该用户创建一个,并生成一个对应的,通过返回给用户,用户就把存到里。这样以后该用户再发送请求时,服务器就可以拿到里的,根据找到对应的。
(1)和的区别?
安全性方面:不是很安全,别人可以从本地中获取、用户名和密码等信息;保存在服务器上,理论上是可靠的,但是如果攻击者获取到了,就能伪装成已登录用户访问服务器。
(2)的生命周期?
的生命周期分为创建和销毁。
当客户端第一次访问服务器时,服务器会为该客户端创建一个,并生成一个返回给客户端,保存在中。
如果设置了过期时间,同时在这段时间内没有再次访问,那么到了过期时间后就会销毁;也可以调用()来手动销毁。
(3)的工作原理?
是基于实现的。
当客户端第一次访问服务器时,服务器会为该客户端创建一个,并生成一个,在响应时存储在客户端的中;这样以后客户端访问服务器,都会带上这个,服务器就会根据中的来获取对应的数据。
(4)如果客户端禁用了,还能生效吗?
不能。因为是基于实现在客户端和服务器之间传输的,所以如果客户端禁用了,服务器就获取不到了。
如果禁用了,可以通过url重写来实现。url重写就是把拼接在url的后面,通过url在客户端和服务器之间传输这个。
url重写使用.()。
28.项目里怎么用es的?es怎么支持搜索的?
主要用es实现商城首页的三级分类的查找,以及在搜索框输入要搜索商品的名字,通过查es返回数据。
es是通过倒排索引进行查询。es在保存数据时,先将数据进行分词得到若干个词条,然后把词条和词条对应的文档id等信息记录下来。当我们要查询一条数据时,es会先把我们输入的条件进行分词,然后拿着分词后的词条在倒排索引中查找,然后得到对应的文档id,再根据文档id进行正向索引的查找。
为什么不直接使用正向查询?倒排索引比正向索引好在哪里?
像我们在搜一个商品的时候,肯定不是拿着商品id去查,而是输入商品的名字,这就相当于模糊查询。如果是正向索引的话,模糊查询需要全表扫描,查询效率比较低。而倒排索引是先分词,然后根据分词后的词条进行查询,再得到文档id,这时候再用文档id查询,就像正向索引那样,根据索引直接拿到数据,效率就很快了。
29.项目中有用到AOP吗?
项目中用到AOP的主要有统一异常处理:
我使用的是@注解。先抽取一个统一异常处理类,然后加上@注解,在这个类里可以针对不同的异常的处理方法,用@来捕获对应的异常。
30.项目中哪里用到动态代理了?
AOP就是基于动态代理实现的。
31.项目用的什么框架?32.用的什么数据库?框架和数据库连接用的什么?
(1)用的关系型数据库MySQL。
(2)框架和数据库连接用的Druid+JDBC,通过进行连接参数的配置。
连接数据库最基本的信息有:连接数据库所需的驱动、URL、用户名和密码。