你们项目中没有做过限流?怎么做的?
介绍项目业务,什么情况下用到了限流
我们当时有一个活动,到了假期回抢购优惠券,QPS最高可以达到2000,平时10-50之间,为了应对突发流量,需要做限流
常规限流,为了防止恶意攻击,保护系统正常运行,我们当时系统能够承受最大的QPS是多少(压测的结果,例如:1000)
Nginx限流
控制速率(突发流量),使用漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量
控制并发数,限制单个IP的链接数和并发链接的总数
网关限流
在Spring Cloud Gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌算法
可以根据IP或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量
限流常见的算法有哪些呢?
漏桶算法
令牌算法
两者区别:https://serve.ulucky.cn/notes/%E9%99%90%E6%B5%81%E7%AE%97%E6%B3%95.html
解释一下CAP和BASE
CAP定理(一致性、可用性、分区容错性)
分布式系统节点通过网络连接,一定会出现分区问题(P)
当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
BASE理论
基本可用
软状态
最终一致
解决分布式事务思想和模型
最终一致性思想:各分支事务分别执行和提交,如果有不一致的情况,再想办法恢复数据(AP)
强一致思想:各分支事务执行完业务不要提交,等待彼此结果,而后统一提交或回滚(CP)
你们采用哪种分布式事务的解决方案?
使用Seata框架
Seata的XA模式,CP,需要互相等待各个分布式事务提交,可以保证强一致性,性能差
Seata的AT模式,AP,底层使用的是undo log实现,性能好
Seata的TCC模式,AP,性能较好,不过需要人工编码实现
MQ模式实现分布式事务,在A服务写数据的时候,需要在同一个事务内发送消息到另外一个事务,异步,性能最好
分布式服务的接口幂等性如何设计呢?
幂等:多次调用接口或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致
如果是新增数据,可以试用数据库的唯一索引
如果是新增或修改数据呢?
分布式锁,性能较低
Token+Reids来实现,性能好
第一次请求,生成一个唯一的token存入Redis,返回给前端
第二次请求,业务处理,携带之前的Token,到Redis进行验证,如果存在,可以执行业务,删除token;,如果不存在,则直接返回,不处理业务
你们项目中用了什么分布式任务调度?
xxl-job路由策略有哪些?
xxl-job提供了很多的路由策略,我们平时用的较多的是:轮询、故障转移、分片广播....
xxl-job任务执行失败怎么解决?
路由策略选择故障转移,使用健康的实例来执行任务
设置重试次数
查看日志+邮件告警来通知相关负责人解决
如果有大数据量的任务同时都要执行,怎么解决?
让多个实例一块去执行(部署集群),路由策略分片广播
在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行
RabbitMQ-如何保证消息不丢失?
开启生产者确认机制,确保生产者的消息能到达队列
开启持久化功能,确保消息未消费前在队列中不会丢失
开启消费者确认机制为auto,由spring确认消息成功后完成ack
开启消费者失败重试机制,多次重试失败后将消息投递到异常交换机,交由人工处理
RabbitMQ消息的重复消费的问题如何解决?
原因:
网络抖动
消费者挂了
解决方案:
每条消息设置一个唯一的标识ID
幂等方案:【分布式锁、数据库锁(悲观锁、乐观锁)】
RabbitMQ死信交换机?(RabbitMQ延迟队列了解过嘛)
我们当时一个什么业务用到了延时队列(超时订单、现时优惠、定时发布)
其中延迟队列就用到了死信交换机和TTL(消息存活时间)实现的
消息超时未被消费就会变成死信(私信的其他情况:拒绝被消费,队列满了)
延迟队列插件实现延迟队列DelayExchange
声明一个交换机,添加delayed属性为true
发送消息时,添加x-delay头,值为超时时间
RabbitMQ如果有100万消息堆积在MQ,如何解决?(消息堆积怎么解决)
解决消息堆积有三种思路:
增加更多消费者,提高消费速度
在消息者内开启线程池加快消息处理速度
扩大队列容积,提高堆积上限,采用惰性队列
在声明队列的时候可以设置x-queue-mode为lazy,即为惰性队列
基于磁盘存储,消息上限高
性能比较稳定,但基于磁盘存储,受限于磁盘IO,时效性会降低
RabbitMQ的高可用有了解过吗?
在生产环境下,我们当时采用的镜像模式搭建的集群,共有3个节点
镜像队列结构是一主多从(从就是镜像),所有操作都是主节点完成,然后同步给镜像节点
主宕机后,镜像节点会替代成新的主(如果主从同步完成前,主就已经宕机,可能出现数据丢失)
那出现丢失数据怎么办呢?
我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致,并且使用起来非常简单,不需要额外的配置,在声明队列的时候只需要指定这个是仲裁队列即可
设计模式-工厂模式
简单工厂模式
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
具体产品:实现或继承抽象产品的子类
具体工厂:提供创建了产品的方法,调用者通过改方法来获取产品
工厂方法模式
抽象工厂(Abstract Factory):提供创建了产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂(ConcreteFactory):主要实现抽象工厂中的抽象方法,完成具体产品创建。
抽象产品(Prudoct):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
优点:
用户只需要知道具体的工厂的名称就可以得到索要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式
产品族:一个品牌下面的所有产品,例如华为下面的电脑、手机称为华为的产品族;
产品等级:多个品牌下面的同种产品;例如华为和小米都有手机电脑为一个产品等级;
策略模式
哪些场景使用策略模式?
订单的支付策略(支付宝、微信、银行卡)
解析不同类型的Excel(xls格式、xlsx格式)
打折促销(满300元打9折、满500打8折、满1000打7折)
物流运费计算等等...
什么是策略模式呢?
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
案例(工厂方法+策略)
介绍业务(登录、支付、解析Excel、优惠等级..)
提供了很多种策略,都让Spring容器管理
提供一个工厂:准备策略对象,根据参数提供对象
什么是程序计数器?
线程私有的,每个线程都有一份,内部保存的字节码的行号,主要是用于记录正在执行的字节码指令的地址。
你能详细介绍一下Java堆吗?
Java堆是一个线程共享的区域,主要用来保存对象实例、数组等,内存不够抛出OutOfMemoryError异常。
组成:年轻代+老年代
年轻代本划分为三部分,Eden区和两个大小严格相同的Survivor区
老年代主要保存生命周期长的对象,一般是一些老的对象
JDK1.7和JDK1.8的区别
1.7中拥有一个永久代,存储的类信息、静态变量、常量,编译后的代码
1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出
什么是虚拟机栈?
Java Virtual machine Stacks(Java 虚拟机栈)
每个线程运行时所需要的内存,称为虚拟机栈,先进后出
每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
垃圾回收是否涉及栈内存呢?
垃圾回收主要指的是堆内存,当栈帧弹栈以后,内存就回释放
栈内存分配的越大越好吗?
未必,默认的栈内存为1024k
栈帧过大会导致线程数变少,例如,机器总内存为512M,目前能活动的线程则为512个,如果把栈内存改为2048k,那么能活动的栈帧就回减半
方法内从局部变量是否线程安全?
如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
如果局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
栈内存溢出情况
栈帧过多导致栈内存溢出,典型问题:递归调用
栈帧过大导致栈内存溢出
堆栈的区别是什么?
栈内存一般回用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的,堆会GC垃圾回收,而栈不会。
栈内存是线程私有的,而堆内存是线程共享的。
两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常
栈内存不足:java.lang.StackOverFlowError
堆内存不足:java.lang.OutOfMemoryError
能不能解释一下方法区?
方法区(Method Area)是各个线程共享的内存区域
主要存储类的信息、运行时常量池
虚拟机启动的时候创建,关闭虚拟机时释放
如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError:MetaSpace
介绍一下运行时常量池
常量池:可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
你听过直接内存吗?
并不属于JVM中的内存结构,不由JVM进行管理,是虚拟机的系统内存
常见于NIO操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受JVM内存回收管理
什么是类加载器呢?
JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来
类加载器有哪些呢?
启动类加载器(BootStrap ClassLoder):加载JAVA_HOME/jre/lib目录下的库
扩展类加载器(ExtClassLoder):主要加载JAVA_HOME/jre/lib/ext目录中的类
应用加载器(ApplClassLoder):用户加载ClassPath下的类
自定义类加载器(CustomizeClassLoder):自定义继承ClassLoder,实现自定义类加载规则
什么双亲委派模型?
加载某一个类,先委托上一个加载器进行加载,如果上级加载器也有上级,则会继续委托上级,如果该委托上级没有被加载,子类加载器尝试加载该类
JVM为什么采用双亲委派极致?
通过双亲委派机制可与避免一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性
为了安全保证API类库不会被修改