kafka场景
- 缓存双删
- 短信异步
- 基于AP的分布式事务
jkd8新特性
- lambda表达式、Strean流、方法引用
- 函数式接口:如果一个接口中只有一个抽象方法,就是函数式接口,当一个方法的参数类型是函数式接口时,可以使用lambda表达式进行简化,lambda表达式右侧的函数体会自动匹配到函数式接口的抽象方法中
- 元空间
怎么解决OOM
- 定位cpiu占用最高的进程,记录下pid
- 再根据进程pid,找到进程中占用cpu最高的线程,记录线程的pid
- 打印该线程的堆栈日志,根据堆栈日志可以定位具体原因
java数组和集合自带的数组的排序算法是什么?如何自定义排序算法?
- 数组使用Array.sort
- 集合使用Collection.sort
- sort方法底层使用归并排序,适合大型数组排序,若数组元素不多或者已经部分有序的话,执行效率不高
- 自定义排序算法时实现一个Comparator类对象,其中定义数组元素具体排序规则,然后将Comparator对象传给sort方法
循环依赖问题的情景?怎么解决?
- A引用B,B引用A,A初始化时发现其中引用了B,又去初始化B,B初始化时又发现引用了A,又去初始化A,出现死循环
- Spring针对Sington对象解决了循环依赖问题,Spring设计了三级缓存结构,不仅可以解决普通对象循环依赖,还可以解决aop代理对象的循环依赖。
- 普通对象循环利用Spring二级缓存就可以解决,A对象生成过程中Spring需要发现注入B对象时,会将当前A对象暂时放入二级缓存半成品池中,然后去生成B对象,在发现B对象需要注入A对象时就把二级缓存半成品池中的A对象取出来注入到B中,B就能正常完成后续流程,创建完成后放入单例池中,B创建完后再去创建A,从单例池取出B注入A,A也能正常完成注入
- AOP对象的循环依赖需要使用三级缓存的工厂对象池才能解决。正常来说,AOP动态代理对象的生成是在被代理对象在完全完成IOC和DI后的BeanFactoryPostProcessor后置处理器中完成的,而二级缓存中所存放的是还没有完成IOC和DI的对象,所以AOP循环依赖的问题使用二级缓存就不能解决。假设A和B是两个AOP对象,这两个AOP对象在初始化之前spring会首先在三级缓存中存放两个AOP对象的工厂对象,工厂对象的方法可以提前返回AOP代理对象;假设A对象先创建,首先会针对A对象的被代理对象首先进行创建,A被代理对象在注入到B对象时,发现B也是AOP对象,就会去三级缓存中直接调用A的工厂对象的方法,提前创建A被代理对象的AOP代理对象A,并将其放入二级缓存半成品池中,然后去创建B对象,B对象会正常创建,首先B的被代理对象在IOC和DI时就从半成品池中取出A进行注入,然后B的被代理对象在Bean后置处理器中正常创建B的AOP代理对象,最后放入单例池中。之后A对象继续进行初始化,从单例池取出B对象注入,在生成AOP对象这一步会检测到AOP代理对象A已经提前创建,所以就不会在Bean后置处理方法中继续创建,直接将当前A对象放入单例池即可
怎么保证代码质量?
- 组长单元测试检查
- 同事代码审查
- 代码编写规范
技术困难?业务困难?
技术困难:
- jpa中findById和getOne
- feign远程调用要指明参数
业务困难:
- 异步发送短信
- mybatis分页插件远程调用问题
- 分布式job问题
java的深拷贝和浅拷贝
微服务和单体架构区别?
过滤器拦截器区别?分别在什么场景下使用?在项目中分别怎么用的?
过滤器基于servlet容器运行,拦截进入servlet前的请求,可以在其中获取request,一般用于对整个项目的请求做统一处理,比如字符集过滤器、跨域过滤器、布隆过滤器等,而拦截器是基于spring运行的,直接被spring容器管理,其中可以获取spring容器中的对象,可以直接注入容器中对象,拦截器一般用于拦截访问某个具体接口的请求,如登录认证拦截器、权限鉴定拦截器
多个定时器Spring Task同时运行为什么会出现分布式job问题?
定时方法用Scheduled注解标记,将这个类交给spring管理即可
因为定时任务中有段逻辑:查询数据库中所有已过期的订单,然后查询出来后直接更改这些订单的状态为超时未支付,并且要解锁库存。如果两个定时器同时执行,第一个定时任务查询到需要删除的订单列表,但还没执行删除之前,第二个定时任务也查询到需要删除的内容,这时就会同时出现两个线程在执行同一订单的解锁方法,所以解锁库存的方法就会执行两次,这样就会出现库存问题,导致超卖
Object对象的equals和hashcode方法有什么区别?重写的时候有什么讲究?hashcode相等的对象一定equals吗?equals的对象一定hashcode相等吗?
- 默认情况下,equals的对象的hashcode一定相等,但hashcode相等的对象不一定equals
- 如果当前的对象需要作为Key存放到Map当中,重写equals后一定要重写hashcode
说一下java的集合体系
HashTable和HashMap
Hashtable 线程安全,不支持key和value为空,key不能重复,但value可以重复,不支持key和value为null。
Hashmap 非线程安全,支持key和value为空,key不能重复,但value可以重复,支持key和value为null。
HashMap中如果put的两条数据的key完全相等怎么办?
HashMap中不允许出现重复的key,如果相等直接覆盖
equals和hashcode的关系?重写时有什么规范?
- equals定义:默认底层是通过==比较两个对象的地址
- hashcode定义:默认是调用native方法,根据地址进行hash运算得到的int值
- 所以默认情况下,equals的对象一定hashcode相等,而hashcode相等的对象不一定equals,因为可能将不同的地址经过hash运算得到的结果是一样的
- 重写时要同时重写两个方法,保证两者一致性,否则如果该类对象作为key存放到hashmap中时,就会出现异常。
- 如果只重写equals,当以该类的两个不同的但equals为true的对象作为key到map中存放或查询数据时,因为对象不同,所以地址也不同,所以hashcode也不同,就会出现两个对象明明equals,但却放入或查询到了不同的map下标;
- 如果只重写hashcode,当插入发生hash冲突时,或者查询到当前map下标的位置是个链表时,就会调用equals进行比较,但equals默认比较的就是地址,如果使用的是两个不同内存地址的对象,就根本查不出对应的结果
HashMap数据写入和查询的流程?
-
数据写入时:先根据key的hashcode进行运算。确定当前元素具体存放位置,如果这个位置没有元素就直接写入,如果有元素就进行一个判断,判断当前key值是否和链表中已有的某个元素的key相同,如果相同就覆盖,如果没有就挂在链表后面
-
数据查询时:先得到查询的key的hashcode,利用hashcode定位key应该去hashmap中哪个位置查找,如果这里只有一个元素就直接返回,如果有一个链表就通过key的equals方法挨个去链表中比较每个元素,当找到后就返回
抽象类和接口的区别
- 接口的变量只能是常量,抽象类可以有普通成员变量
- 多个接口可以被同一个类同时实现,而类只能继承一个抽象类
- 抽象类可以有构造方法,接口不能有构造方法
父类和子类的构造方法顺序?
父类静态代码块——子类静态代码块——父类非静态代码块——父类构造——子类非静态代码块——子类构造
mysql的数据类型
- int、bigint、decimal、double:int4字节,bigint和double8字节,decimal长度不固定,一般就用int存储整数、如果长度不够换bigint,decimal和double处理小数,decimal更适合对高精度小数进行运算
- char、varchar、text:char定长字符串,如char(10)的字段只能存放长度为10的字符串,如果不够也会用空格填充到10,char一般存放用户手机号;varchar是不定长字符串,如varchar(10)的字段不一定长度是10,存放用户名等,text存放超长文本数据,比如评论可以用text,varchar在长度到达一定范围后会自动转成text,查询效率char最高
- date、datetime、timestamp:date是年月日,datetime是年月日时分秒,timestamp是年月日时分秒,但范围只支持70-38年,并且timestamp默认值是当前时间,一般记录当前数据的最后操作时间和创建时间
项目中怎么用线程池的
使用Configuration注解配置一个专门针对线程池对象的java配置类,业务中会直接注入这个线程池对象,用于异步编排,可以用到的场景包括
- 提交订单时需要为用户展示用户所有地址信息、显示所有订单项,同时生成一个订单唯一编号用于订单重复提交的校验,这里这三步就可以同时执行,我们利用CompletableFuture对象的runAsync方法先后开启了一三个个任务,并将这三个任务绑定到线程池对象上,最后直接调用CompletableFuture对象的alllOf静态方法方法,同时传入这三个CompletableFuture对象,再执行join方法,就能实现异步代码执行。
- 订单确认接口中,在为用户锁库存成功需要去调用购物车微服务的方法删除购物车中用户下单的商品,这里也会用异步编排,专门开一个线程远程调用
redis集群怎么做的?
使用redis的哨兵sentinel做高可用,主从复制读写分离,每种微服务都有自己的redis sentinel,集群种master和slave分散在不同的服务器中,同一台服务器根据性能会有多台redis,这样可以避免单点故障提高数据安全性,
redis sentinel选举机制:基于raft协议,根据每个slave的健康状态作为判断依据,分数最高的slave作为新master
nacos集群怎么做的?
- 创建nacos集群的mysql表,nacos集群是在mysql基础上进行的
- 修改集群中nacos的配置文件,将ip修改为本机的ip地址
- 启动所有nacos,即可完成集群,启动时nacos会相互注册,实现高可用高并发
zk集群怎么做的?
选择一台zk作为主节点,主节点用来协调所有从节点,其他作为从节点,配置集群配置文件,包括ip、端口号等,同时启动后,就会统一注册到zk主节点中,同时建立一个quorum集合,客户端配置zk集群的ip和端口,就能找到zk集群,zk集群后,客户端可以通过任何从节点和zk集群联系,主节点挂掉或出现网络分区后,会进行zk选举产生新的主节点
zk的选举机制?
zk集群中每个节点都有一个唯一的id,每个节点会有三种不同状态:
- Looking:正在寻找leader
- Leading:当前节点是主节点
- Following:当前节点是从节点
处于Looking状态的节点会向其他从节点以自己的id为内容发送一条proposal请求投票,其他从节点收到后就会进行对比,如果我们规定id最大的节点当leader,就会对比自己的id和接收到的proposal,将更大的作为消息发送给其他节点,当某个节点得到了半数以上的集群的从节点投票后,就会自动升级为主节点,并将信息广播出去,所有从节点再恢复Following状态
分页查询时,页数越大查询效率有影响吗?
页数越大,查询效率越慢,因为需要跳过的记录就越多
分页查询超大分页怎么优化
利用覆盖查询和子查询进行优化,先利用一个子查询,select id from 表名 order by id limit m,n,查询出分页数据的id,再利用id直接通过聚簇索引查询分页结果
分主表的原则是什么?
分表时按照常用的查询字段进行划分,比如订单表分表时按照订单编号划分,我们订单编号是通过雪花算法生成的,我们根据编号的范围进行划分,总共划分了3部分,采用客户端分表原则,查询和插入时查询当前订单应该操作那张表,使用spring的动态数据源切换表。每一部分有两张主表,双向复制,正常情况读写分离,使用keepalived做高可用
mybatisplus和jpa区别
- my使用自定义sql或wrapper操作数据库,jpa底层使用jpql操作数据库
- my的性能更好,my可以手动优化查询语句,而jpa需要将jpql转化为数据库语言才能执行
- my可以有多种主键生成方式,而jpa只有自增
- 项目的数据库操作复杂时用my,只有简单crud时使用jpa
微服务设计模式
- 集成模式:网关统一入口
- 分支模式:同时调用多个其他微服务,适用于多个调用间没有逻辑顺序,如确认订单
- 链式模式:按先后顺序调用多个微服务,适用于多个调用间有逻辑顺序,如支付回调方法中,需要先调用订单微服务修改订单状态,得到成功返回后再调用商品微服务减库存
创建对象的方式
- clone:使用时需要实现cloneable接口,克隆时要保证每个成员属性也实现了cloneable接口,默认情况下,clone是使用的浅拷贝,成员属性的拷贝只是拷贝的地址,如果要实现深拷贝需要自己重写
- Class.newInstance,利用类的字节码对象以反射的方式创建对象
- Constructor.newInstance,利用反射获取构造方法,通过构造方法对象创建对象
为什么#可以防止sql注入
使用preparedStatement的setParameter进行参数设置,当用户尝试进行sql注入时,#可以将sql注入时的特殊语句进行转义,使其无法破坏sql的结构
$相比#有哪些优势?
- #的占位符需要单独的变量进行存放,而$直接进行替换,效率更高
- $指定的参数可以直接传递给jdbc驱动进行处理,而#需要进行解析和处理,会影响性能
- 性能要求高的场景用$合适,但要做好校验,不能直接将用户输入的数据拼接到sql中
微服务项目上线后,如果要针对数据库中某张表新增字段时,应该怎样操作才能不影响用户体验,让用户感觉不到项目更新带来的体验中断?
- 新增字段或表时,先备份相关数据,然后进行新增,保证若出现问题后也能快速恢复
- 使用数据库迁移工具如FlyWay,用于管理数据库版本和变更脚本,编写好对应的新增脚本,设置在发布新版本时自动执行
常用设计模式
- 单例模式:保证全局只有一个实例对象,避免对资源的浪费,实现时的重点:私有构造方法和单例对象,只能通过get方法获取单例对象,比如数据库连接池对象、线程池对象就可以用单例模式
- 工厂模式:spring的FactoryBean就是工厂类接口,实现其中getObject方法就能自动创建并返回对象
- 观察者模式:实现对象间的一对多依赖关系时,观察者接口中有通知方法,不同的观察者实现类会自定义通知方法,在被观察者对象中会保存一个观察者对象集合,当被观察者被改变时,就调用每个观察者中的通知方法进行通知
http请求响应报文?
- 请求行:请求方式,post、get
- 请求头:键值对参数,如content-type,cookie
- 请求体:请求内容,get无请求体
- 响应行:响应状态码,403权限不够
- 响应头:键值对参数,如content-type,set-cookie
- 响应体:响应的结果
JDK和JRE的区别
- JDK:开发工具包
- JRE:运行环境
- jdk中包含jre,jre中包含bin和lib,bin就是jvm,lib中是jvm运行需要的类库,包含lang、util、io
Math的取整方法
- cell:向上取整(向更大的方向取整)
- floor:向下取整(向更小的方向取整)
- round:加0.5后向下取整
Integer比较
- Integer类中有静态内部类,专门用于-128-127之间值的初始化
Integer a1 = 127;
Integer b1 = 127;
System.out.println(a1 == b1); //true
Integer a2 = 128;
Integer b2 = 128;
System.out.println(a1 == b2); //false
Integer a3 = 127;
Integer b3 = new Integer(127); //false
- integer和String类似,对其赋值后,再进行修改会创建一个新的Integer
String
比较
String a = "123";
String b = "123";
System.out.println(a == b); //true
String a1 = "123";
String b2 = new String("123");
System.out.println(a1 == b2); //false
反转字符串
StringBuilder s = new StringBuilder("123");
s.reverse();
ASCII和Unicode的区别
ascii是针对英文字母、标点符号的编码,一共128取值,而unicode中一共2的16次方个值,包含绝大部分语言的文字,char的字符就是以unicode形式存放的,utf8是unicode的一种实现形式
八大数据类型
- byte:1字节,-128-127,用在
- short:2字节,-32768-3276
- int:4字节
- float:4字节
- double:8字节
- long:8字节
- char:2字节,0-65535
- boolean:1字节
char和short区别
范围不同,short是有符号整数
注解底层实现
注解就是java中的一种元数据,可以为类、属性、方法进行额外标记,相当于作为程序运行时给编译器看的注释,底层会配合反射,通过反射就能获取对应的类、属性、方法上标记的注解内容,根据注解内容对代码进行额外处理
运行异常:
- 空指针
- 类型转换
- 栈溢出
- 数组越界异常
springboot的容器中包含tomcat,当需要换版本时更换tomcat的自动装配jar包就可以
redis怎么使用的?
- 用的spring data redis中的redistemplate,引入redis的starter
- 自己手写的redistemplate(包括redissonClient)自动装配工程
- 需要用到redis的地方引入
mybatisplus怎么用的?
- 引入mybatisplus的stater
- 自己手写mybatisplus的自动装配,主要包括分页插件MybatisPlusInterceptor
mysql除了索引外还有哪些地方可以进行优化
- 避免使用select*,避免返回不需要的字段,避免出现非覆盖查询,减少查询数据量(非覆盖查询是说查询的字段中有的字段没有索引,所以需要进行回表查询,回表查询会影响查询速度)
- 少使用子查询,子查询会使每个查询执行时产生多条子查询语句,使用多表连接查询来提高性能
- 常用的查询结果做缓存
mysql用in做查询时,怎么才能使用索引
- 为in的列建立索引
- 在5.7中in中的字段超过1500个时就会出现索引失效
值传递和引用传递
- 值传递指传参时,方法内的参数和实参是独立的,方法内修改参数不会影响方法外的实参
- 引用传递指传参时,传递的参数就是实参的引用,方法内修改会直接影响方法外的实参
String s1 = new String(“abc”) 在内存中创建了几个对象
一个或两个,若abc在常量池中已经存在,就只在堆空间中开辟空间指向常量池的abc,若abbc在常量池中不存在,就再去常量池中创建一个abc
String为什么会被final修饰
String经常会被作为参数、返回值进行传递,用final修饰可以保证安全,同时使用final后编译器会进行一定优化,提高程序执行速度
static方法内只能使用static的变量、调用static方法
final修饰基本数据类型时值不能改变,修饰引用数据类型时引用的对象地址不能改变,但是引用内的属性可以改变
重写时子类的访问修饰符权限不能小于父类的,如父类default,子类只能是default、protected、public中的,父类为private的方法不能被重写
HashMap中允许key和value为空,为空的key只能出现一次,HashTable中不允许空key和value,HashMap默认大小为16,扩容时为2倍
HashSet底层和HashMap结构类似,只不过Map是以键值对形式保存数据,而set是单个数据
HashMap的put过程
- 检查Map是否是新创建的,如果是执行resize,分配空间
- 根据key的hashcode找到应该存放的位置,判断位置上有没有元素,如果没有就直接放入,并判断是否需要扩容
- 如果有其他元素,说明发生了hash冲突,就根据key的equals方法比较冲突位置上的链表或者红黑树中的每个元素的大小,若找到一样的就进行替换,若没有找到就插入其中,若插入的时候是个链表再判断是否需要转换成红黑树
ascii、unicode、utf8的关系
- ascii:全称美国标准信息交换码,主要用于传输英文字母及符号,只有128个字符
- unicode:能够表示绝大部分语言的绝大部分字母的一种编码格式,目前已经有十万多个不同语言的字母,java的char采用的就是unicode编码,可以表示0-65535之间的字符
- utf-8:是unicode的一种编码和解码实现,可以把字符编码转换成能够进行传输的字节序列
Arrays.asList的注意
- 得到的List集合不能使用add、remove等方法,因为得到的List集合是Array的一个内部类,其中重写的add、remove方法会之间抛异常
- 不能对基本数据类型使用asList方法
Collection和Collections的区别
- Collection是集合类接口,包含List、Set
- Collections是处理集合的工具类,里面有sort、reverse等方法用于对集合进行加工
int和Integer区别
- int默认4字节,值为0;Integer默认为null
- int是在栈中,Integer是在堆中
- int没有方法可以调用,Integer有很多方法可以调用
io流有哪些
- PrintStream
- ObjectStream
- FileStream
反射
通过类的字节码对象直接操作类对象,获取类字节码对象的方式
- 类对象实例.getClass;
- Class.forName
- 类名.class
重写和重载
- 重写只能针对父类的实例方法,静态方法不能重载,重写的访问修饰符不能小于父类的访问修饰符
- 重载:编写多个同名方法,方法的参数列表不同,和返回值无关
多态
- 编译时多态:指重载
- 运行时多态:指重写
synchronized锁的升级机制
会根据尝试获取sync锁的线程的状态进行升级
- 偏向锁:当锁资源第一次被某个线程获取后,进入偏向锁状态,锁对象的对象头的MarkWord字段变为偏向锁状态,这时锁只能被持有的线程进行访问,当这个线程后续想要重复获取锁时,也不需要进行额外申请,因为没有其他线程进行竞争,所以这种状态下的锁占用资源最小,执行效率最高
- 轻量级锁:当其他线程尝试获取偏向锁时,升级为轻量级锁,锁对象的对象头的MarkWord字段变为轻量级锁状态,其他线程会通过cas自旋,重复检查锁对象是否被释放,如果被释放则获取到锁资源,否则一直自旋,直到达到一定的自旋阈值,这种状态下其他尝试获取锁的线程没有释放cpu资源
- 重量级锁:当自旋次数达到阈值后,升级为重量级锁,锁升级为重量级锁,重量级锁中的monitor采用了操作系统提供的互斥量计数器和等待队列实现锁的同步,所有尝试获取重量级锁的线程都会被放入等待队列,获取到锁对象后,调用monitorenter方法,将计数器+1,若出现了同一线程的重入,则将计数器继续递增,当同步代码执行完成,计数器再依次递减,当递减到0时锁被释放,然后由操作系统唤醒处于等待队列的阻塞线程执行同步代码,重量级锁为了避免过多的线程同时自旋导致cpu资源浪费,所以直接将线程阻塞,但同时由于需要由操作系统唤醒,也就是会有用户态——内核态的切换,所以会时整个任务执行时间增加
mysql查询条件顺序
这些关键字的使用顺序如下:
SELECT:选择需要查询的列名。
FROM:指定查询的表名。
WHERE:指定查询的条件表达式,用于过滤数据。
GROUP BY:对查询结果进行分组。
HAVING:用于过滤分组后的数据。
ORDER BY:指定查询结果的排序方式。
ASC/DESC:指定升序或降序排序。
LIMIT:指定查询结果的返回行数。
怎么做限流的?
用过hystrix做熔断器,针对上游请求设置信号量或线程池,针对下游调用做熔断降级
sentinel对上级进行流量控制,对下级实现熔断降级,保护整个微服务运行时的稳定性。
sentienal有一个基于springboot编写的控制台,可以让我们更直观观察流量状态,可以直接针对每个接口做限流设置,sentienal可以基于并发线程数或响应时间做限流
-
并发线程数:当请求某个资源的线程达到阈值后,不再接收新的请求,直到把所有堆积请求处理完成再恢复
-
响应时间:当请求资源响应时间超过阈值,所有新请求直接被拒绝,直到经过指定的时间窗口后再恢复
-
针对上级的限流
- 资源名:默认是要限流的接口的请求路径
- 来源:可以指定针对哪个具体来源的请求进行限制
- 阈值类型:QPS(每秒请求数)或线程数,当调用接口的qps或者线程数达到阈值时限流
- 限流模式:
- 直接模式:默认模式
- 关联模式:当前资源超过阈值后限制零一资源
- 链路模式:可以更细粒度的编写controller到service层的限流
- 限流效果:按照哪种处理方式进行限流
- 快速失败:限流时再调用直接抛异常
- warm up:在warm up时间内进行更严格限流,warm up时间后恢复普通限流
- 排队等待:让请求匀速通过,每个等待的请求会有超时时间,超过超时时间还没响应则直接抛弃
- 针对下级的熔断降级策略
- 慢调用比例:当一定时间内,调用下游请求的响应时间超过设置的最大响应时间比例达到一定阈值,触发熔断,经过指定时间后,再尝试调用下游,若调用仍然超时,继续熔断
- 异常比例
- 异常数量
- sentienel使用的是滑动窗口进行时间的统计,比如在某一秒最后100ms时请求失败,下一秒前100ms时也请求失败,这样也会当作1s内的失败
搭建框架
- 顶级父工程引入lombok,引入版本管理
- 搭建common工程,里面存放core核心依赖、mybatis分页插件自动装配工程、redis工具类和redisson自动装配工程;core包括全局状态码和异常信息、分页查询默认分页参数、存放到redis中常量或kafka的主题
- 搭建各个业务微服务,其中分为boot子工程和api子工程
kafka基于zk做集群
- 配置每个kafka的监听地址和端口号
- 配置zk的地址
- 启动kafka
kafka客户端?
- 引入kafka依赖,配置yml文件,主要包括地址、生产者消费者的缓存大小、序列化方式等信息
- 使用java配置类,配置新建一个主题,比如我们发短信需要用到,就使用java配置类写一个NewTopic,主题名叫sms,配置3个分区1个副本
- 生产者注入kafkatemplate,发送消息
- 消费者消费方法上面使用kafkalistener注解,监听主题,就能在参数中获取到其中的内容
kafka消息的数量级大概每分钟3-400,单机最大并发10w
redis如何保证缓存一致性的?
- 全量复制和增量复制:全量复制是当redis集群启动时,使用全量复制将信息复制到slave中,一般只会在启动时使用一次;增量复制是redis启动后,当redis主节点信息改变,从节点只需要复制变化后的数据而不是全部数据,通过增量复制保证复制的高效性
- 项目中使用延迟双删来实现缓存一致性,是指实际修改数据库信息前,先删除缓存数据,更新完成后利用kafka再删除一次缓存
进程和线程的区别
- 进程是操作系统分配资源的最小粒度,线程是cpu进行调度的最小粒度
- 进程中包含多个线程,每个线程都可以共享进程的资源,当进程出现问题时,所有线程都要退出
- 进程的切换相比线程需要消耗更大的资源,需要从内存、文件、网络等部分进行操作,而线程相比会更加简单
- 进程之间的通信需要依赖消息队列、共享内存等方式,而线程之间的通信可以直接通过进程内的共享地址
myspring
三个重点
- 解析xml
使用dom4j解析xml文件
- 根据解析的xml进行ioc和di
- ioc:读取xml的bean标签中配置的全限定类名,根据类名获取字节码对象,利用反射创建对象实例,再放入map中保存
- di:读取xml的bean标签的id,根据id从map中取出对象,读取bean标签下所有property标签,再根据property标签中的name、value得到注入的属性名、属性值,如果需要注入对象,就将value替换成ref,根据ref中的id去容器中找对应的对象,再利用BeanUtils.copyProperty方法,将属性注入到对象中
- 将对象放入容器中进行管理
为了保证线程安全和高并发,使用concurrentHashMap,封装一个根据对象名获取对象的方法,如果要和servlet整合,就实现ServletContextListener,监听servletContext域对象的创建,监听到创建后就调用上面的ioc和di方法,并且将map容器放入servletcontext域对象中进行保存。使用时直接通过servletcontext的getAttribute方法获取map容器
mysql死锁?怎么解决?
- mysql死锁是说多个事务互相等待其他事务所持有的锁资源,最后谁都不能正常提交事务的状态
- 解决方法
- 首先通过命令查看所有正在等待资源的事务列表,如果发生了死锁,在这里就能看到死锁的事务的id
- 根据事务id获取事务的详情信息,再获取对应的mysql连接id
- 根据连接id查询连接信息,分析连接中的sql语句,找到死锁的资源位置
mvc三层结构
- controller负责接收调用请求,解析参数,调用下游service方法
- service负责具体业务流程处理,如订单确认、订单提交都在service中进行处理,同时处理完成后调用mapper层
- mapper负责与数据库进行连接,主要包括数据库连接对象和映射文件,通过连接对象操作数据库,映射文件中的内容会映射到对象上
实现ioc的方式
- xml配置
- FactoryBean对象
- 注解
- java配置类,configuration和bean注解
ES实现查询
- 根据iid精确查询:get / 索引名 / _doc / 记录
- 根据text模糊匹配:get / 索引名 /_search {query:match}
- 根据keyword精确匹配:get / 索引名 /_search {query:term}
MyBatis 的 XML 中动态查询标签
if:用于指定一个条件语句,只有当满足该条件时,才会执行该语句。
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM user WHERE 1 = 1
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</select>
choose、when、otherwise:类似于 Java 中的 switch 语句,根据不同的条件选择执行不同的语句。
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="name != null">
AND name = #{name}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
</select>
trim、where、set:用于去除或者添加 SQL 语句中的某些部分,常用于动态生成 WHERE 或 SET 子句。
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</trim>
</select>
foreach:用于循环执行一段 SQL 语句,常用于 IN 查询,可以指定集合、数组等类型的参数。
<update id="batchUpdateUser" parameterType="List">
UPDATE user
<set>
<foreach collection="list" item="user" separator=",">
name = #{user.name},
age = #{user.age}
</foreach>
</set>
WHERE id IN
<foreach collection="list" item="user" open="(" close=")" separator=",">
#{user.id}
</foreach>
</update>
bind:用于绑定一个变量,可以在其他标签中使用该变量。
sql:用于定义一个可重用的 SQL 片段。
布隆过滤器
就是一组二进制向量+一组hash函数组成的结构,优点是查询元素是否存在集合中时速度很快,缺点是可能会出现误判,就是如果查询出来有结果,实际可能集合中没有,如果查询出来没有结果,实际就不可能会有
存入数据时:根据数据的值计算多个hash函数,将对应位置的值设置为1
查询数据时:根据查询条件计算多个hash值,判断对应位置上的值是不是全为1,若全1说明可能其中会有查询的元素,若不是全1说明集合中没有
springboot热部署
Spring Boot的热部署依赖于Spring Boot DevTools模块。它使用两种方式实现热部署:
-
ClassLoader加载器
DevTools通过使用两个ClassLoader实现热部署。第一个ClassLoader是基本的应用程序ClassLoader,第二个ClassLoader是重启ClassLoader,它包含类的应用程序代码。DevTools在启动过程中将应用程序的原始classpath中的所有类都加载到重启ClassLoader中,并且只在需要时重启ClassLoader,这样可以更快地重新加载更改。 -
文件系统监控
DevTools监控classpath和资源文件的更改。当检测到更改时,它会触发应用程序重新加载。这是通过使用FileSystemWatcher来实现的。
当上述两种方式中的一个发现应用程序已更改时,DevTools会重启应用程序。重启将重新加载所有更改并启动应用程序。这样,您可以获得最新的更改,而无需重新启动应用程序。
kafka为什么这么快?
- 顺序存储方式接近内存
- 有批量处理和压缩数据的技术
- 零拷贝
正常情况如果要将应用程序中的数据通过网络传输,需要先将数据从应用程序缓冲中拷到内核缓冲,再将数据从内核缓冲
开启多线程方式
- 继承Thread,重写run方法,调用start方法开启多线程
- 实现Runable接口(函数式接口),重写run方法(可以用lambda表达式),创建Thread对象传入Runable对象,开启start方法
- 实现Callable接口(函数式接口),重写call方法(可以用lambda表达式),调用线程池对象的submit方法,传入Callable对象,可以用Future对象接收返回的结果
回表查询(非覆盖查询)
当查询的字段不是索引字段时,需要先查询非聚簇索引得到主键,在利用主键到聚簇索引中进行查询,也就是回表查询;若查询的字段都有索引,则只需要查询非聚簇索引就能返回结果
索引优化(创建原则)
- 字段要常用
- 字段不会经常更改
- 字段值的区分度很高
- 尽量建立联合索引,这样可以使用覆盖索引进行查询,避免出现从非聚簇索引到聚簇索引的回表查询
- 控制索引的数量,索引越多,底层的索引表就越多,增删改时需要维护的索引表也越频繁
索引失效
- 联合索引失效(假设A、B、C是三个联合索引)
- 若用A、C查询,则只会使用A的索引
- 若使用A=?,B>?,C=?会导致范围查询右边的索引失效,这里只会使用A和B的索引
- 若索引字段是字符串类型,查询时若没有加引号也会失效,因为mysql底层会做类型转换
- 若模糊查询,则%只能出现在查询条件结尾,否则索引失效
- 若索引列使用了函数运算,索引失效