项目

秒杀项目

1. 项目做了什么

  1. redis实现分布式Session
  2. redis做缓存减轻数据库压力
  3. 服务器内存标记减少对redis的访问
  4. rabbitmq进行异步处理,提高了系统的响应速度,减少了用户的无响应等待时间
  5. 秒杀URL隐藏、对恶意请求进行限流防刷
  6. 超买超卖问题

2. 用户的登录状态如何保持:分布式session

  1. 用户登录后,将user和token存入redis,将token存入cookie,redis和cookie设置相同的存活时间,将cookie返回给前端
  2. 客户端下一次请求时,从request请求头中拿出token,根据token从redis中查询user,判断用户的状态,进行同步,添加到Model中

3. 缓存是如何预热的

  1. 实现InitilizingBean接口,重写afterPropertiesSet()方法,从数据库中查询goodsid和库存,存入redis,使用HashMap标记goodsid为false

4. 如何进行限流:AccessLimit注解、拦截器进行计数校验

  1. 使用AccessLimit注解,设置两个属性,maxcount和second
  2. 在获取URL的模块上使用这个注解,初始化两个属性maxcount和second为5
  3. web拦截器拦截请求
    1. 反射判断当前模块是否使用了AccessLimit注解,没有就放行,有就进行属性校验
    2. 从request中获取token,根据token从redis中获取user
    3. 反射获取注解的两个属性,second和maxcount,根据userid从redis中获取当前对象的count计数,如果count为null,将count设置为1,过期时间设置为second。如果count小于maxcount,incr+1,如果count大于maxcount,提示警告

5. 数据库补充库存之后如何反馈到内存标记:单机和集群

  1. 单机:将hashMap设置为public,定期检测数据库库存,如果有库存,就重置内存标记
  2. 集群:将hashMap设置为public,定期检测数据库库存,如果有库存,rabbitmq通知服务器重置本地的内存标记

7. Redis预减库存成功但数据库库存没有减少出现什么情况:rabbitmq消息丢失

  1. 问题可能出现在rabbitmq,rabbitmq消息丢失可以分为三种情况:生产者丢失了消息、rabbitmq丢失了消息、消费者丢失了消息
  2. 生产者丢失了消息
    1. 开启事务模式,rabbitmq成功接收消息就提交事务,失败就回滚事务重发消息。事务模式是同步的,吞吐量低
    2. 开启confirm模式,rabbitmq成功接收消息返回一个ack消息,失败回调一个nack接口,重发消息。confirm模式是异步的,吞吐量大
  3. rabbitmq丢失了消息
    1. 队列持久化:durable=true
    2. 消息持久化:发送消息时,deliverMode(2)
  4. 消费者丢失了消息:消费过程中消息被删除
    1. 关闭自动ack机制,消费者消费完之后手动进行ack

8. 缓存数据一致性:延时双删

  1. 对于这个系统来说,并没有强制要求缓存和数据库的库存一致。不一致的情况又两种
    1. 缓存的库存比数据库的小,这种情况会导致商品卖不完,不行
    2. 缓存的库存比数据库的大,这种情况其实是可以的,这样不会发生超卖问题的,因为超卖问题是在数据库的SQL语句层面解决的,SQL语句限制只有当数据库的库存足够时才能减库存下订单
    3. 所以可以在预热数据时将数据库的库存稍微设置大一点,这样既能保证商品能够卖完又不会发生超卖问题
  2. 对于严格要求缓存数据一致的项目,为缓存的key设置过期时间是解决这个问题的最终方案
    1. 采用延时双删:先删除缓存,再更新数据库,休眠一段时间,然后删除缓存。这样就可以将回填到缓存的脏数据删除,后面的读会回填正确的数据到缓存

9. 下单后如何减库存:下单就减库存、付款减库存、临时订单

  1. 三种策略
    1. 下单就减库存。最理想但是不可能实现,用户不可能在抢到订单的同时就付款减库存
    2. 付款减库存。用户可能因为网络问题没来得及付款导致购买失败
    3. 生成临时订单,用户在订单有效期内付款,才生成真实订单和减数据库库存
  2. 可以采用第三种策略
    1. 用户下订单后,预减redis的库,然后在redis中生成一个临时订单,设置过期时间,前端开启倒计时
    2. 如果用户在期限内付款,触发检查,检查数据库的库存是否大于0,在redis和数据库生成真实订单,减数据库库存
    3. 如果临时订单过期,redis库存回填+1,通知用户购买失败

10. 为什么要进行异步处理、使用MQ和没使用MQ的流程

  1. 如果不使用MQ进行异步处理,用户从抢购开始到获得抢购的结果,系统需要执行的操作量多,抢购和获取结果的操作要全部完成才能返回响应给前端,用户等待的时间久
    1. 查询redis,判断用户是否超买
    2. 查询内存标记,判断库存是否不足
    3. redis预减库存,如果预减后库存小于0,将内存标记置为false
    4. 查询数据库,判断数据库库存是否不足,判断用户是否超买
    5. 下订单,减库存
    6. 将抢购结果返回给前端,如果是抢购成功,显示订单详情页面。如果抢购失败,通知用户
  2. 使用MQ进程异步处理后,将请求入mq后,先返回状态码让前端页面显示正在获取结果,减少了用户的无响应等待时间,然后再去获取抢购结果,将抢购和获取抢购结果分开执行
    1. 查询redis,判断用户是否超买
    2. 查询内存标记,判断库存是否不足
    3. redis预减库存,如果预减后库存小于0,将内存标记置为false
    4. 将请求入MQ,返回一个状态码给前端,前端显示用户等待,前端再请求到result模块获取结果
    5. 与此同时,rabbitmq会进行下订单减库存,在redis和数据库中生成订单
    6. 前端请求到result模块,从redis和数据库中查询是否存在当前用户的订单,有久显示订单详情页面,否则提示用户购买失败

11. rabbitmq使用了哪一种工作模式、一共有哪几种工作模式:simple、work、发布订阅、routing、topic

  1. 使用了routing路由模式:为queue添加了一个字符串标识,接收者监听标识了这个字符串的queue
  2. 一共5种
    1. simple:一个发送者一个接收者
    2. work:一个发送者多个接收者,谁先拿到就消费
    3. 订阅发布:发送者将消息发送给交换机,交换机将消费转发给订阅者
    4. routing:使用标识标注queue,接收者监听标识的queue
    5. topic:routing的一种,标识可以使用通配符

12. rabbitmq如何保证消息有序:为什么要有序、项目中如何实现有序、乱序的情况、有序的情况

  1. 为什么要有序:如果多个消息之间存在上下文的关系,消息乱序执行可能会导致数据错误
  2. 项目如何实现有序:一个消息队列一个消费者,消费者没有使用多线程处理
  3. 乱序:消费者使用多线程,多个消费者读取,执行时机不定
  4. 有序:读取消息后存入队列,有序执行

13. 如何保证消息不会被重复消费:保证消息的幂等性

  1. 保证消息的幂等性,如果消息已经被消费过,丢弃不消费
  2. 发送消息时,生成一个唯一的id,将消息存入redis,消费时根据id从redis中查询,如果有,消费,消费完之后删除,没有就丢弃

14. 如何解决超买超卖:唯一索引和SQL限制

  1. 超买:为订单表的userid和goodsid添加唯一索引,确保一个用户对一个商品不会生成两张订单
    1. 为了减少对数据库的访问,下订单时在redis中生成订单,下一次判断用户超卖就可以直接查询redis来判断,不用请求到数据库
  2. 超卖:SQL语句限制只有当库存大于0时才能减数据库库存
    1. 为了减少对数据库的访问,使用redis和内存标记进行数据预热,用户抢购时,先判断超买,然后查询内存标记,最后再减redis库存

15. 项目压测:JMeter

  1. 5W并发对秒杀接口进行压测,创建5000个HTTP请求,循环10次
    1. 直接访问数据库时,QPS1300+
    2. 使用了redis、内存标记、MQ后,QPS2100+
  2. 因为秒杀URL要动态生成,所以先使用工具类生成5000个用户,保存到数据库中,然后将这5000个用户登陆到系统,生成token令牌,将token保存到txt文件,JMeter导入txt文件,提取token,进行压测,查看聚合报告,查看QPS

16. Nginx负载均衡和反向代理有什么区别

  1. 负载均衡是将并发的请求分发给后端的服务器集群,更注重对请求的分发减轻并发压力
  2. 反向代理是Nginx作为代理服务器,将前端请求转发给指定的后端服务器,将后端服务器的响应转发给前端

17. Nginx负载均衡策略:轮询、权重、iphash、urlhash、fair、least_conn

  1. 轮询:默认,顺序访问服务器
  2. weight权重:在轮询的基础上,对服务器节点配置了权重,权重越大,越容易被击中
  3. iphash:对客户端的ip进行hash计算,再对服务器数量进行取模,每个客户端ip都有一个固定的服务器节点
  4. urlhash:对url进行hash计算,每个url都有对应的服务器节点
  5. least_conn:请求到最少连接的服务器节点
  6. fair:请求到响应速度最快的服务器节点

18. 一致性hash

  1. iphash需要对服务器数量进行取模运算,如果进行扩容和缩容影响范围大
  2. 一致性hash:设置一个hash环,先对服务器ip进行hash计算,放置到hash环上,客户端请求时,对客户端ip进行hash计算,对应到hash环上,顺时针查找最近的服务器节点

19. 如何设计一个秒杀系统(设计的思路)

  1. 秒杀系统可能出现的问题
    1. 高并发
    2. 超买超卖,超买不一定要处理但是超卖一定要处理
    3. 安全性问题
  2. 针对上述问题展开进行考虑解决
    1. 高并发:系统响应慢、防止数据库被打崩
      1. 防止数据库被打崩:使用redis作为缓存减轻数据库压力,使用内存标记减轻redis压力
      2. 系统响应慢:使用mq进行异步处理,将抢购和通知抢购结果分成两部分,减少用户的等待时间
    2. 超买超卖
      1. 超买:订单表唯一索引限制
      2. 超卖:SQL语句限制库存大于0时才能减
    3. 安全问题
      1. 接口动态生成:根据当前user使用MD5加密生成URL,存入到redis进行校验
      2. 接口限流:使用注解对获取URL的模块进行限流,限制5秒内只能请求5次

RPC项目

1. 微服务和分布式

  1. 微服务:是一种设计架构方式,简单来说一个微服务就是一个功能,可以单独部署和运行。服务粒度小、拓展方便、团队开发时分工明确
  2. 分布式:是一种系统部署方式,将大的系统拆分成多个小的模块,分别部署到不同的服务器上。分布式可以实现高并发和高可用
  3. 微服务和分布式的区别:微服务是分布式部署,但是分布式不一定是微服务,比如分布式集群,就是将多个功能相同的应用部署到多台服务器上,实现高并发和高可用,整个集群还是单一功能的应用

2. 微服务框架有哪些

  1. SpringCloud:基于HTTP请求,自带一个Eureka注册中心
  2. 阿里的Dubbo:基于RPC请求,只支持Java,需要第三方注册中心
  3. 谷歌的GRPC:基于HTTP2.0请求,使用protobuf序列的IDL文件生成服务器和客户端通信的代码
  4. Dubbo支持Java,可以自定义协议和序列化方式

3. RPC是什么、架构、RPC和HTTP有什么区别

  1. RPC是remote procedure call,远程过程调用,客户端通过网络调用远程服务器的服务进行处理的一种请求方式,
  2. 架构:
    1. 客户端:服务发现,提供接口服务名请求到注册中心,得到服务所在的服务器的IP和端口进行远程调用
    2. 服务器:服务暴露,服务器将接口服务和自身IP地址和端口注册到注册中心
    3. 注册中心
      1. 服务发现:根据客户端提供的接口名查询对应服务器的IP和端口
      2. 服务管理:将服务器接口注册成永久节点和临时节点
      3. 负载均衡:对客户端的请求进行负载均衡
    4. 核心功能
      1. 序列化和反序列化:实现跨网络传输
      2. 编码和解码:选用TCP协议,通过收发双方约定数据包的长度解决TCP粘包问题
      3. 网络传输:大部分RPC框架都是使用TCP协议的,也可以使用HTTP协议,比如GRPC选用的就是HTTP2.0协议
  3. RPC和HTTP
    1. HTTP:
      1. 只能使用HTTP协议,如果是HTTP1.1版本,请求报文存在大量的无用信息,报文体积大,传输效率低,也可以使用HTTP2.0,封装成RPC来使用。
      2. 传输的是JSON信息
      3. 负载均衡需要借助Nginx等外部组件实现
      4. 一般用于对外的请求调用
    2. RPC:
      1. 可以使用HTTP或者是TCP,自定义TCP报文格式,使请求报文的体积减小,解决TCP粘包问题。也可使用HTTP2.0,有效减小请求报文的体积,提高传输效率。
      2. 传输的是byte字节
      3. 可以自定义负载均衡策略
      4. 公司内部的服务调用,传输效率高

4. 拆分Dubbo功能:基于RPC请求、传输byte、TCP粘包、第三方注册中心功能

  1. 基于RPC请求方式:使用Netty实现高效的TCP传输
  2. 传输byte:序列化和反序列化
  3. TCP粘包:编码和解码,编码时将byte数组的长度传入buffer,解码时提取byte数组的长度,创建相同大小的byte数组接收
  4. 第三方注册中心:zookeeper实现服务管理、负载均衡

5. Netty:为什么要用Netty、Netty模型、解决TCP粘包、序列化与反序列化

1. Netty是什么

  1. Netty是一个网络通信框架,它对Java的NIO非阻塞IO进行了封装,简化了使用的难度

2. 为什么用Netty:并发高、传输快、封装好

  1. 并发高:Netty是基于Java中的NIO开发的,对应操作系统中的多路复用IO,将监听和处理分开进行,由selector多路复用器同时监听多个客户端,有事件就将请求交给线程进行处理,可以让单个线程多次处理请求。而BIO是由线程独立完成监听和处理,这样单个线程能够处理的请求就变少了
  2. 传输快:NIO的零拷贝特性,bytebuf
  3. 封装好:Netty对Nio进行了封装,简化了Nio的使用难度,比如Nio在解决TCP粘包问题时,需要调用的API包多,处理起来繁琐。而Netty提供了两个类,MessageToByte和ByteToMessage,继承这两个类来自定义编码器和解码器,编码时先将序列化之后的byte数组的长度发送到buffer,解码时先从buffer中提取byte数组的长度,创建相同长度的byte数组进行接收。

2. Netty模型:单Reactor单线程、单Reactor多线程、主从Reactor、Netty的IO模型

  1. 单Reactor单线程:使用一个Reactor处理器同时监听多个客户端,有事件发生就读取分发给服务器的Eventhandler处理
  2. 单Reactor多线程:使用一个Reactor处理器同时监听多个客户端,如果是连接请求,分发给Acceptor处理器进行连接。如果是读写请求,分发给handler,handler调用send方法将请求发送给线程池处理,调用read读取结果
  3. 主从Reactor多线程:有父子Reactor,父Reactor负责监听客户端,如果是连接请求,分发给Acceptor处理器进行连接。如果是读写请求,则分发给子Reactor,子Reactor将请求分发给handler,handler调用send将请求分发给线程池,调用read读取结果
  4. Netty的IO模型:Bossgroup负责监听客户端,Bossgroup的Nioeventloop循环调用accept读取事件,将事件通过channel注册到Workergroup,Workergroup的Nioeventloop循环调用accept读取事件,分发给channelhandler进行处理

3. TCP粘包问题:为什么会出现TCP粘包、如何解决TCP粘包

  1. 发送方在发送时为了提高发送效率,将发送间隔短的几个数据包打包成一个大的包发送,接收方无法拆分数据包
  2. 编码:继承MessageToByte
    1. 调用序列化方法,将数据序列化为byte数组
    2. 将byte数组的长度写入buffer
    3. 将byte数组写入buffer
  3. 解码:继承ByteToMessage
    1. 提取编码的序列化方式,读取byte数组长度
    2. 创建相同大小的byte数组,接收byte数组

4. 序列化和反序列化:序列化原理、主流的序列化方式

  1. 序列化原理:跨网络传输,序列化把java对象序列化为byte数组(MessageToByte),反序列化把byte数组恢复为java对象(ByteToMessage)
  2. 主流的序列化方式
    1. Serialized:Java自带的序列化方式,无法跨语言,效率低
    2. Protobuf:json和xml,跨语言,拓展性好,支持c++,java,pyhton
    3. fastjson:json,效率高,但是可能出现类型转换错误
    4. jackson:和fastjson类似
    5. gson:json,功能强大,但是性能比fastjson稍微差
  3. JSON格式:使用fastjson,可能出现类型丢失
    1. 序列化:object.toJsonObject()
    2. 反序列化
      1. request请求(客户端 to 服务器),遍历byte数组,request.setParam()存入参数
      2. respons回应(服务器 to 客户端),遍历byte数组,response.setData()存入数据
  4. Serialized格式
    1. 序列化(对象 to byte):ObjectOutputStream写入ByteArrayOutputStream
    2. 反序列化(byte to 对象):ByteArrayOutputStream写入ObjectOutputStream

5. Netty实现长连接的心跳检测:以写事件检测为例

  1. 在客户端中加入IdleStateHandler,设置写事件触发时间为5秒
  2. 如果客户端超过5秒未写入数据,触发心跳检测,向服务器发送心跳包
  3. 服务器收到心跳包后做出回应
  4. 如果服务器连续收到三次客户端的心跳包,说明客户端可能已经挂了,就断开连接
  5. 之后,如果客户端重新上线,发现连接已经断开了,客户端重新发起连接请求建立连接

6.zookeeper注册中心:注册中心是什么、有哪些注册中心、zookeeper心跳检测

1. 注册中心是什么

  1. 注册中心在RPC模型中相当于一个中间代理,客户端如果想要调用远程服务器的服务,必须要先连接到远程服务器,但是如果每一次都要事先知道服务器的IP和端口,很不方便。所以出现了注册中心,服务器可以将业务挂载到注册中心,客户端请求到注册中心,提供服务名,查找目标服务器进行连接。

2. 服务暴露:永久节点和临时节点

  1. 永久节点:服务器下线后,不删除节点
  2. 临时节点:服务器下线后,节点被删除,临时节点没有子节点

3. 服务发现

  1. 客户端提供接口名,请求到注册中心,注册中心调用getChildren.getPath()查询目标服务器的IP和端口,客户端连接到远程服务器

4. 负载均衡:轮询和随机

  1. 轮询:从-1开始递增,ipList.size()取余
  2. 随机:random.nextInt(ipList.size())随机获取节点

5. zookeeper的心跳检测

  1. 目的:维持客户端和服务器之间的长连接
  2. 原理:
    1. 客户端:如果60s内没有收到服务器的消息,发送心跳包,如果3次(180s)都没有收到服务器的消息,客户端主动断开这个连接
    2. 服务器:如果60s内没有收到客户端的消息,发送心跳包,如果3次(180s)都没有收到客户端的消息,服务器主动断开这个连接
全部评论

相关推荐

点赞 6 评论
分享
牛客网
牛客企业服务