面试资料
2025-08-07 19:46:31 0 举报
AI智能生成
JAVA核心面试宝典。准确率90%以上
作者其他创作
大纲/内容
JVM(主流虚拟机)
JVM整体结构及内存模型
JVM整体结构及内存模型
什么是堆内存?
堆内存是指由程序代码自由分配的内存,与栈内存作区分。
在Java中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,
所有线程都可以访问堆内存。
在Java中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,
所有线程都可以访问堆内存。
堆内存包括哪些部分?
老年代
年轻代
Eden S0 S1 8:1:1
年轻代
Eden S0 S1 8:1:1
Spring
Spring之Bean生命周期
Spring之Bean生命周期
Spring中到底有几种依赖注入的方式?
1. 手动注入
1. set方法注入 2. 构造方法注入
2. 自动注入
1. XML的autowire自动注入
1. byType 2. byName 3. constructor 4. default 5. no
那么XML的自动注入底层其实也就是: 1. set方法注入 2. 构造方法注入
2. @Autowired注解的自动注入
@Autowired注解可以写在: 1. 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个 2. 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个 3. set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
而这种底层到了: 1. 属性注入 2. set方法注入 3. 构造方法注入
@Resource注解底层工作流程图
@Resource注解底层工作流程图
Spring启动过程
Spring启动过程
Spring在启动过程中就需要做两件事
Mybatis
MyBatis介绍
MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手 写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有 时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半 自动ORM框架
传统JDBC的问题如下
1.数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使 用数据库连接池可以解决问题。
2.sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可 能较大,sql代码和java代码没有分离开来维护不方便。
3.使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中 的where子句的条件不确定,同样是修改不方便
4.对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维 护不方便
mybatis对传统的JDBC的解决方案
1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果 使用数据库连接池可解决此问题。 解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链 接。
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大, sql变动需要改变java代码。 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可 能少,占位符需要和参数一一对应。 解决:Mybatis自动将java对象映射至sql语句,通过statement中的 parameterType定义输入参数的类型。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如 果能将数据库记录封装成pojo对象解析比较方便。 解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的 resultType定义输出结果的类型。
一个Mybatis最简单的使用列子如下
从配置文件(通常是XML文件)得到SessionFactory;
从SessionFactory得到SqlSession;
通过SqlSession进行CRUD和事务的操作;
执行完相关操作之后关闭Session。
Mybatis 和 Mybatis Plus 的区别
MyBatis:
所有SQL语句全部自己写
手动解析实体关系映射转换为MyBatis内部对象注入容器
不支持Lambda形式调用
Mybatis Plus
内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
支持Lambda形式调用
提供了基本的CRUD功能,连SQL语句都不需要编写
自动解析实体关系映射转换为MyBatis内部对象注入容器
MyBatis-Plus 优点
依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring 。
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 。
预防Sql注入:内置 Sql 注入剥离器,有效预防Sql注入攻击 。
多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题
支持热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。
内置分页插件:基于 Mybatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询。
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作。
内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询 。
默认将实体类的类名查找数据库中的表,使用@TableName(value=“table1”)注解指定表名,@TableId指定表主键,若字段与表中字段名保持一致可不加注解。
网络
MQ
浅谈Rabbitmq如何实现延时队列
1)使用TTL+死信队列组合实现延迟队列的效果。
1、TTL
TTL 全称 Time To Live(存活时间/过期时间)。当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。可以类比redis的TTL。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。可以类比redis的TTL。
2、死信队列 DLX
(消息成为死信的三种情况:)
(消息成为死信的三种情况:)
队列消息长度到达限制;
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
原队列存在消息过期设置,消息到达超时时间未被消费;
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
原队列存在消息过期设置,消息到达超时时间未被消费;
1)使用TTL+死信队列组合实现延迟队列的效果。
2)使用RabbitMQ官方延迟插件,实现延时队列效果
如果消息大量堆积在broker里面,
应该怎么处理
应该怎么处理
修复Consumer不消费问题,使其恢复正常消费,根据业务需要看是否要暂停
临时topic队列扩容,并提高消费者能力,但是如果增加Consumer数量,但是堆积的topic里
面的message queue数量固定,过多的consumer不能分配到message queue
编写临时处理分发程序,从旧topic快速读取到临时新topic中,新topic的queue数量扩容多倍,
然后再启动更多consumer进行在临时新的topic里消费
直到堆积的消息处理完成,再还原到正常的机器数量
临时topic队列扩容,并提高消费者能力,但是如果增加Consumer数量,但是堆积的topic里
面的message queue数量固定,过多的consumer不能分配到message queue
编写临时处理分发程序,从旧topic快速读取到临时新topic中,新topic的queue数量扩容多倍,
然后再启动更多consumer进行在临时新的topic里消费
直到堆积的消息处理完成,再还原到正常的机器数量
如何保证消息队列的消息生成
和消费的顺序性
和消费的顺序性
如何保证消息队列的消息生成和消费的顺序性
如何保证消息可靠性-消息重复
如何保证消息可靠性-消息重复
如何保证消息可靠性消息丢失
如何保证消息可靠性消息丢失
如何保证消息可靠性-消息积压
如何保证消息可靠性-消息积压
RabbitMQ 工作队列模型
简单队列
工作队列模式
通配符模式
路由、主题模式
交换机和发布订阅模型
Direct Exchange 定向
Fanout Exchange 广播
Topic Exchange 通配符
Headers Exchanges(少用)
工作队列模式
通配符模式
路由、主题模式
交换机和发布订阅模型
Direct Exchange 定向
Fanout Exchange 广播
Topic Exchange 通配符
Headers Exchanges(少用)
SpringMVC
说说你对Spring MVC的理解
MVC:MVC是一种设计模式:
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
工作原理(流程):
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器
拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器
拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
处理器映射器(HandlerMapping):根据URL去查找处理器。
处理器(Handler):需要程序员去写代码处理逻辑的。
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理
器,类比笔记本的适配器(适配器模式的应用)。
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面。
处理器映射器(HandlerMapping):根据URL去查找处理器。
处理器(Handler):需要程序员去写代码处理逻辑的。
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理
器,类比笔记本的适配器(适配器模式的应用)。
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面。
Spring-MVC的优缺点
优点
(1)强大的 JSP 标签库,使 JSP 编写更容易。支持各种视图技术,而不仅仅局限于JSP;
(2)和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
(3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping),
处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
(4) 支持各种请求资源的映射策略。并且支持RESTful 编程风格的请求
(5) 封装代码,维护成本低,耦合性低
(6) 有利于开发中的分工,提高开发效率
(7) 组件重用,有利于代码复用,重用性高
缺点
(1) Spring与MVC 的Servlet API 耦合,难以脱离容器独立运行
(2) 太过于细分,开发效率低
(3) 原理不容易理解
优点
(1)强大的 JSP 标签库,使 JSP 编写更容易。支持各种视图技术,而不仅仅局限于JSP;
(2)和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
(3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping),
处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
(4) 支持各种请求资源的映射策略。并且支持RESTful 编程风格的请求
(5) 封装代码,维护成本低,耦合性低
(6) 有利于开发中的分工,提高开发效率
(7) 组件重用,有利于代码复用,重用性高
缺点
(1) Spring与MVC 的Servlet API 耦合,难以脱离容器独立运行
(2) 太过于细分,开发效率低
(3) 原理不容易理解
JMM
JAVA内存模型简称 JMM
JMM规定所有的变量存在在主内存,每个线程有自己的工作内存,线程对变量的操作都在工作内存中进行,
不能直接对主内存就行操作,所有的共享变量都存在主内存中
每个线程都保存了一份该线程使用到的共享变量的副本。
线程A是无法直接访问到线程B的本地内存的,只能访问主内存
线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取
并发的三要素:可见性、原子性、有序性,而JMM就主要体现在这三方面
不能直接对主内存就行操作,所有的共享变量都存在主内存中
每个线程都保存了一份该线程使用到的共享变量的副本。
线程A是无法直接访问到线程B的本地内存的,只能访问主内存
线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取
并发的三要素:可见性、原子性、有序性,而JMM就主要体现在这三方面
操作系统的处理办法
只要超出立即报错,比如抛出内存溢出错误
什么是内存溢出
什么是内存泄漏?
两者有什么关系?
什么是内存泄漏?
两者有什么关系?
内存溢出(OOM)是指可用内存不足。
程序运行需要使用的内存超出最大可用值,如果不进行处理就会影响到其他进程
内存泄漏(Memory Leak)是指本来无用的对象却继续占用内存,没有再恰当的时机
释放占用的内存。
如果存在严重的内存泄漏问题,随着时间的推移,则必然会引起内存溢出。
内存泄漏一般是资源管理问题和程序BUG,内存溢出则是内存空间不足和内存泄漏的
最终结果。
程序运行需要使用的内存超出最大可用值,如果不进行处理就会影响到其他进程
内存泄漏(Memory Leak)是指本来无用的对象却继续占用内存,没有再恰当的时机
释放占用的内存。
如果存在严重的内存泄漏问题,随着时间的推移,则必然会引起内存溢出。
内存泄漏一般是资源管理问题和程序BUG,内存溢出则是内存空间不足和内存泄漏的
最终结果。
内存泄漏比较典型的场景
每一个请求进来,或者每一次操作处理,都分配了内存,却有一部分不能回收(或未释放),
那么随着处理的请求越来越多,内存泄漏也就越来越严重。
那么随着处理的请求越来越多,内存泄漏也就越来越严重。
什么是指令重排
指令重排序分两类 编译器重排序和运行时重排序
JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,
主要目的是优化运行效率(不改变程序结果的前提)
Java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况 的结果相等,
那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序
JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,
主要目的是优化运行效率(不改变程序结果的前提)
Java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况 的结果相等,
那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序
指令重排解决办法
内存屏障是屏障指令,使CPU对屏障指令之前和之后的内存操作执行结果的一种约束
JMM的内存可见性保证
单线程程序
正确同步的多线程程序
未同步/未正确同步的多线程程序
正确同步的多线程程序
未同步/未正确同步的多线程程序
SpringCloud
JAVA基础
hashmap
hashmap在链表长度大于8,且数组长度大于64时将链表转换为红黑树
说一说HashMap的扩容机制
1.如果你没有指定初始长度的话,默认会为null,这时候往里面添加元素他就会进行扩容,初始数组大小为16
2.当元素添加到链表上,链表的长度大于8的时候,数组长度小于64时,会将链表转换成红黑树
3.当添加元素达到了他的阈值,默认的加载因子是0.75,比如16的容量,你加到12的时候他就会进行扩容
扩容是resize方法,每次扩容都是两倍,他是用一个新的数组代替了原来那个小的数组,将原数组的数据迁移到新数组里面
2.当元素添加到链表上,链表的长度大于8的时候,数组长度小于64时,会将链表转换成红黑树
3.当添加元素达到了他的阈值,默认的加载因子是0.75,比如16的容量,你加到12的时候他就会进行扩容
扩容是resize方法,每次扩容都是两倍,他是用一个新的数组代替了原来那个小的数组,将原数组的数据迁移到新数组里面
Serializable(可序列化)
序列化(将对象状态转换为可保持或传输的格式的过程)
反序列化(它将流转换为对象)
反序列化(它将流转换为对象)
接口和抽象类的区别
接口和抽象类的区别
Arrays的常用方法
一、Arrays的复制方法
1.Arrays.copyOfRange(int[] original, int from, int to)
2.Arrays.copyOf(src, length)
3.拓展:另一个复制方法:System.arraycopy(Object src,
int srcPos, Object dest, int destPos, int length)
原文链接:https://blog.csdn.net/C_LIN_XIE/article/details/105145301
1.Arrays.copyOfRange(int[] original, int from, int to)
2.Arrays.copyOf(src, length)
3.拓展:另一个复制方法:System.arraycopy(Object src,
int srcPos, Object dest, int destPos, int length)
原文链接:https://blog.csdn.net/C_LIN_XIE/article/details/105145301
二、将数组转换为字符串
1.Arrays.toString(src)
三、将无序数组进行有序的排列
(该排列是从小到大的排列)1.Arrays.sort(src)
四、在数组中搜索某个具体元素(该方法将会显示元素的具体位置,
即该元素的下标)1.Arrays.binarySearch(src, key)
五、对两个数组进行判断两者是否完全相同
1.Arrays.equals(src1,src2)
六、对未初始化数组进行填充
1.Arrays.fill(src, value)
七 ,Arrays的asList方法
Arrays中有一个方法asList方法。是将任何类型的数组转换为list形式。
将任何类型的数组转换成List集合
1.Arrays.toString(src)
三、将无序数组进行有序的排列
(该排列是从小到大的排列)1.Arrays.sort(src)
四、在数组中搜索某个具体元素(该方法将会显示元素的具体位置,
即该元素的下标)1.Arrays.binarySearch(src, key)
五、对两个数组进行判断两者是否完全相同
1.Arrays.equals(src1,src2)
六、对未初始化数组进行填充
1.Arrays.fill(src, value)
七 ,Arrays的asList方法
Arrays中有一个方法asList方法。是将任何类型的数组转换为list形式。
将任何类型的数组转换成List集合
system常用方法
//srcPos 从第几位开始复制0开始 destPos目标数组从第几位开始放 length放的长度
System.arraycopy(arr,4,dest,4,4);
//currentTimeMillis计算1970到现在的时间毫秒 可以用来计时
long start = System.currentTimeMillis();
System.arraycopy(arr,4,dest,4,4);
//currentTimeMillis计算1970到现在的时间毫秒 可以用来计时
long start = System.currentTimeMillis();
System.gc();//告诉垃圾回收期回收垃圾 可以重写finalize();方法 obj里面
System.exit(0);//退出JVM 0正常退出 非0非正常退出
//获取系统属性 Properties ps = Sys tem. getP roperties() ;
//获取操作系统名称 String OS_ name = Sys tem. getP roperty("os . name”) ;
System.exit(0);//退出JVM 0正常退出 非0非正常退出
//获取系统属性 Properties ps = Sys tem. getP roperties() ;
//获取操作系统名称 String OS_ name = Sys tem. getP roperty("os . name”) ;
ObjectMapper的常用方法
字符串转实体类
mapper.readValue(jsonString, Student.class);
实体类转字符串
jsonString = mapper.writeValueAsString(student);
集合和json字符串之间转换
List<Student> studentList2 = mapper.readValue(jsonStr, List.class);
字符串转 List<实体类>
Student[] stu = mapper.readValue(jsonString, Student[].class);
String ss = mapper.writeValueAsString(stu);
mapper.readValue(jsonString, Student.class);
实体类转字符串
jsonString = mapper.writeValueAsString(student);
集合和json字符串之间转换
List<Student> studentList2 = mapper.readValue(jsonStr, List.class);
字符串转 List<实体类>
Student[] stu = mapper.readValue(jsonString, Student[].class);
String ss = mapper.writeValueAsString(stu);
//对象转为byte数组
byte[] byteArr = mapper.writeValueAsBytes(student);
System.out.println(byteArr);
//byte数组转为对象
Student student= mapper.readValue(byteArr, Student.class);
System.out.println(student);
map和json字符串之间转换
Map<String, Object> testMap = new HashMap<>();
testMap.put("name", "22");
String jsonStr = mapper.writeValueAsString(testMap);
Map<String, Object> testMapDes = mapper.readValue(jsonStr, Map.class);
byte[] byteArr = mapper.writeValueAsBytes(student);
System.out.println(byteArr);
//byte数组转为对象
Student student= mapper.readValue(byteArr, Student.class);
System.out.println(student);
map和json字符串之间转换
Map<String, Object> testMap = new HashMap<>();
testMap.put("name", "22");
String jsonStr = mapper.writeValueAsString(testMap);
Map<String, Object> testMapDes = mapper.readValue(jsonStr, Map.class);
日期转json字符串
// 修改时间格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
Student student = new Student ("hyl",21, new Date());
student.setIntList(Arrays.asList(1, 2, 3));
String jsonStr = mapper.writeValueAsString(student);
// 修改时间格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
Student student = new Student ("hyl",21, new Date());
student.setIntList(Arrays.asList(1, 2, 3));
String jsonStr = mapper.writeValueAsString(student);
IO流
IO流
多线程
协程
协程
又称为微线程,是一种用户态的轻量级线程,协程不像线程和进程需要进行系统内核上的上下文切换,
协程的上下文切换是由用户自己决定的,有自己的上下文,所以说是轻量级的线程,也称之为用户级别
的线程就叫协程,一个线程可以多个协程,线程进程都是同步机制,而协程则是异步
协程的上下文切换是由用户自己决定的,有自己的上下文,所以说是轻量级的线程,也称之为用户级别
的线程就叫协程,一个线程可以多个协程,线程进程都是同步机制,而协程则是异步
并发三大特性
可见性
当一个线程修改了共享变量的值,其他线程能够看到修改的值
有序性
即程序执行的顺序按照代码的先后顺序执行。JVM 存在指令重排,所以存在有序性问题
原子性
一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行
当一个线程修改了共享变量的值,其他线程能够看到修改的值
有序性
即程序执行的顺序按照代码的先后顺序执行。JVM 存在指令重排,所以存在有序性问题
原子性
一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行
如何保证可见性
如何保证有序性
如何保证原子性
如何保证有序性
如何保证原子性
通过 volatile 关键字保证可见性
通过 内存屏障保证可见性。
通过 synchronized 关键字保证可见性
通过 Lock保证可见性
通过 final 关键字保证可见性
通过 内存屏障保证可见性。
通过 synchronized 关键字保证可见性
通过 Lock保证可见性
通过 final 关键字保证可见性
通过 synchronized关键字保证有序性。
通过 Lock保证有序性
通过 Lock保证有序性
通过 synchronized 关键字保证原子性。
通过 Lock保证原子性
通过 CAS保证原子性。
通过 Lock保证原子性
通过 CAS保证原子性。
volatile写-读的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来
将从主内存中 读取共享变量。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来
将从主内存中 读取共享变量。
JDK8~13新特性
JDK8~13新特性
进程间通信的方式
1. 管道(pipe)及有名管道(named pipe)
2. 信号(signal)
3. 消息队列(message queue)
4. 共享内存(shared memory)
5. 信号量(semaphore)
6. 套接字(socket
2. 信号(signal)
3. 消息队列(message queue)
4. 共享内存(shared memory)
5. 信号量(semaphore)
6. 套接字(socket
Mysql
什么是表锁
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低; 一般用在整表数据迁移的场景
结论Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更 高一下,但是在整体并发处理能力方面要远远优于MYISAM的表级锁定的。当系统并发量高的时候,Innodb 的整体性能和MYISAM相比就会有比较明显的优势了。 但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现 不仅不能比MYISAM高,甚至可能会更差。
什么是行锁
每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最 高。
总结: MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自 动给涉及的表加写锁。 InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行 锁。
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为 表锁。
什么是共享锁
也叫S锁/读锁,能查看但无法修改和删除的一种数据锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享
什么是互斥锁
也叫X锁/排它锁/写锁/独占锁/独享锁/ 该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。
什么是死锁
两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去
间隙锁(Gap Lock)
间隙锁,锁的就是两个值之间的空隙。Mysql默认级别是repeatable-read,有办法解决幻读问题吗?间隙锁 在某些情况下可以解决幻读问题。
临键锁(Next-key Locks)
Next-Key Locks是行锁与间隙锁的组合。像上面那个例子里的这个(3,20]的整个区间可以叫做临键锁。
乐观锁
每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步
小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多
悲观锁
当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized
为什么范围查找Mysql没有用索引下推优化?
估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。
公平锁
指多个线程按照申请锁的顺序来获取锁,简单来说 如果一个线程组里,能保证每个线程都能拿到锁 比如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)
非公平锁
获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock
小结:非公平锁性能高于公平锁,更能重复利用CPU的时间
可重入锁
也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
不可重入锁
若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
小结:可重入锁能一定程度的避免死锁 synchronized、ReentrantLock 重入锁
自旋锁
一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有一个执行单元获得锁.
小结:不会发生线程状态的切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU
常见的自旋锁:TicketLock,CLHLock,MSCLock
常见的自旋锁:TicketLock,CLHLock,MSCLock
偏向锁
一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,获取锁的代价更低,
轻量级锁
当锁是偏向锁的时候,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,但不会阻塞,且性能会高点
重量级锁
当锁为轻量级锁的时候,其他线程虽然是自旋,但自旋不会一直循环下去,当自旋一定次数的时候且还没有获取到锁,就会进入阻塞,该锁升级为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能也会降低
分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作
锁优化建议
1尽可能让所有数据检索都通过索引来完成 ,避免无索引行锁升级为表锁
2 合理设计索引 ,尽量缩小锁的范围
3 尽可能减少检索条件范围,避免间隙锁
4 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
5 尽可能低级别事务隔离
数据库优化
数据库优化
回表
简单来说,回表就是 MySQL 要先查询到主键索引,然后再用主键索引定位到数据
如何避免回表?
使用覆盖索引,所谓覆盖索引就是指索引中包含了查询中的所有字段,这种情况下就不需要再进行回表查询了。
执行计划explain
explain中的列
1. id列
explain select * from film where id = 2;
2. select_type列
1)simple:简单查询。查询不包含子查询和union
explain select * from film where id = 2;
2)primary:复杂查询中最外层的 select
explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
3)subquery:包含在 select 中的子查询(不在 from 子句中)
4)derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含 义)
5)union:在 union 中的第二个和随后的 select
explain select 1 union all select 1;
3. table列
这一列表示 explain 的一行正在访问哪个表。
当 from 子句中有子查询时,table列是 <derivenN> 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查 询。
当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。
4. type列
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
一般来说,得保证查询达到range级别,最好达到ref
5. possible_keys列
这一列显示查询可能使用哪些索引来查找。
explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引 对此查询帮助不大,选择了全表查询。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提 高查询性能,然后用 explain 查看效果。
6. key列
这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。
如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index
7. key_len列
这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。
8. ref列
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)
9. rows列
这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
10. Extra列
1)Using index:使用覆盖索引
2 Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖
3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
4)Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索 引来优化。
5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一 般也是要考虑使用索引来优化的。
6)Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段是
数据库连表方式
左外连接(左连接)outer join
结果集几包括连接表的匹配行,也包括左连接表的所有行 left outer join left join
左连接
右外连接(右连接)
结果集既包括连接表的匹配连接行,也包括右连接表的所有行 right outer join right join
右外连接(右连接)
全连接(全外连接)
不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录 full join 、union
全外连接
内连接
内连接查询的是两张表的并集,也就是A表和B表都必须有数据才能查询出来 inner 、inner join
内连接
笛卡尔积 (join)
两表关联,把左表的列和右表的列通过笛卡尔积的形式表达出来。
select * from t1 join t2
联合查询(Union 和Union all)
Union
当使用union 时,mysql 会把结果集中重复的记录删掉
当使用union 时,mysql 会把结果集中重复的记录删掉
Union all
而使用union all ,mysql 会把所有的记录返回,且效率高于union 。
而使用union all ,mysql 会把所有的记录返回,且效率高于union 。
主键索引和唯一索引有什么区别
主键索引一定包含唯一索 引,唯一索引并不一定就是主键
唯一索引列允许空值,而主键列不允许为空值
主键可以被其他表引用为外键,而唯一索引不能
一个表最多只能 创建一个主键,但可以创建多个唯一索引
⼤⼚⾯试题,⾼并发库存扣减超卖问题解决,多种sql
适合场景
适合场景
⾼并发库存扣减超卖问题解决
⾼并发库存扣减超卖问题解决
MySQL的索引数据结构(B/B+树)
B树
叶节点具有相同的深度,叶节点的指针为空
所有索引元素不重复
节点中的数据索引从左到在递增排列
B+树
叶子节点包含所有索引字段
叶子节点用指针连接,提高区间访问的性能
非叶子节点存储Key,只存储索引(冗余),可以放更多的索引,叶子节点存储Key和数据
Redis
Redis的持久化机制
AOF持久化
优点:
数据更加安全
当Redis AOF文件太大时,Redis能够在后台自动重写AOF
AOF以易于理解和解析的格式,一个接一个地包含所有操作的日志
缺点:
AOF文件通常比同一数据集的等效RDB文件大
根据确切的fsync策略,恢复的时候AOF可能比RDB慢
优点:
数据更加安全
当Redis AOF文件太大时,Redis能够在后台自动重写AOF
AOF以易于理解和解析的格式,一个接一个地包含所有操作的日志
缺点:
AOF文件通常比同一数据集的等效RDB文件大
根据确切的fsync策略,恢复的时候AOF可能比RDB慢
RDB持久化
优点:
RDB最大限度地提高了Redis的性能,父进程不需要参与磁盘I/O
RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复
在恢复大数据集时的速度比 AOF 的恢复速度要快
生成的是一个紧凑压缩的二进制文件
缺点:
每次快照是一次全量备份,fork子进程进行后台操作,子进程存在开销
在快照持久化期间修改的数据不会被保存,可能丢失数据
优点:
RDB最大限度地提高了Redis的性能,父进程不需要参与磁盘I/O
RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复
在恢复大数据集时的速度比 AOF 的恢复速度要快
生成的是一个紧凑压缩的二进制文件
缺点:
每次快照是一次全量备份,fork子进程进行后台操作,子进程存在开销
在快照持久化期间修改的数据不会被保存,可能丢失数据
redis集群为什么不用一致性哈希算法
redis是处理高并发用到的分布式缓存,高并发场景下,
一致性哈西会导致一个key去频道打到单一结点的redis上导致redis的集群失去负载均衡能力。
Redis6.X主从架构-复制读写分离原理解析
背景
单机部署简单,但是可靠性低,且不能很好利用CPU多核处理能力
生产环境-必须要保证高可用-一般不可能单机部署
读写分离是可用性要求不高、性能要求较高、数据规模小的情况;
目标
读写分离,扩展主节点的读能力,分担主节点读压力
容灾恢复,一旦主节点宕机,从节点作为主节点的备份可以随时顶上来
单机部署简单,但是可靠性低,且不能很好利用CPU多核处理能力
生产环境-必须要保证高可用-一般不可能单机部署
读写分离是可用性要求不高、性能要求较高、数据规模小的情况;
目标
读写分离,扩展主节点的读能力,分担主节点读压力
容灾恢复,一旦主节点宕机,从节点作为主节点的备份可以随时顶上来
主从复制分两种(主从刚连接的时候,进行全量同步;全同步结束后,进行增量同步)
全量复制
master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件
主服务器会缓存所有接收到的来自客户端的写命令,当后台保存进程 处理完毕后,会将该rdb文件传递给slave服务器
slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存
在此之后master服务器会将在此期间缓存的命令通过redis传输协议发送给slave服务器
然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性
增量复制
Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程
服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令
全量复制
master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件
主服务器会缓存所有接收到的来自客户端的写命令,当后台保存进程 处理完毕后,会将该rdb文件传递给slave服务器
slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存
在此之后master服务器会将在此期间缓存的命令通过redis传输协议发送给slave服务器
然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性
增量复制
Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程
服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令
特点
主从复制对于 主/从 redis服务器来说是非阻塞的,所以同步期间都可以正常处理外界请求
一个主redis可以含有多个从redis,每个从redis可以接收来自其他从redis服务器的连接
从节点不会让key过期,而是主节点的key过期删除后,成为del命令传输到从节点进行删除
从节点开启 sync 看日志
主从复制对于 主/从 redis服务器来说是非阻塞的,所以同步期间都可以正常处理外界请求
一个主redis可以含有多个从redis,每个从redis可以接收来自其他从redis服务器的连接
从节点不会让key过期,而是主节点的key过期删除后,成为del命令传输到从节点进行删除
从节点开启 sync 看日志
加速复制
完全重新同步需要在磁盘上创建一个RDB文件,然后加载这个文件以便为从服务器发送数据
在比较低速的磁盘,这种操作会给主服务器带来较大的压力
新版支持无磁盘的复制,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储
repl-diskless-sync yes (默认是no)
完全重新同步需要在磁盘上创建一个RDB文件,然后加载这个文件以便为从服务器发送数据
在比较低速的磁盘,这种操作会给主服务器带来较大的压力
新版支持无磁盘的复制,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储
repl-diskless-sync yes (默认是no)
主从断开重连
如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步
2.8版本后 部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令
如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步
2.8版本后 部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令
Redis6.X节点高可用监控之Sentinel
背景
前面搭建了主从,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,人工干预费事费力,还会造成一段时间内服务不可用
前面搭建了主从,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,人工干预费事费力,还会造成一段时间内服务不可用
哨兵模式介绍
Redis提供了哨兵的命令,是一个独立的进程
原理 哨兵通过发送命令给多个节点,等待Redis服务器响应,从而监控运行的多个Redis实例的运行情况
当哨兵监测到master宕机,会自动将slave切换成master,通过通知其他的从服务器,修改配置文件切换主机
Redis提供了哨兵的命令,是一个独立的进程
原理 哨兵通过发送命令给多个节点,等待Redis服务器响应,从而监控运行的多个Redis实例的运行情况
当哨兵监测到master宕机,会自动将slave切换成master,通过通知其他的从服务器,修改配置文件切换主机
Sentinel三大工作任务
监控(Monitoring)
Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
提醒(Notification)
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知
自动故障迁移(Automatic failover)
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器
当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
监控(Monitoring)
Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
提醒(Notification)
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知
自动故障迁移(Automatic failover)
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器
当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
问题
一个哨兵进程对Redis服务器进行监控,可能会出现问题
一般是使用多个哨兵进行监控,各个哨兵之间还会进行监控,形成多哨兵模式
一个哨兵进程对Redis服务器进行监控,可能会出现问题
一般是使用多个哨兵进行监控,各个哨兵之间还会进行监控,形成多哨兵模式
多哨兵模式下线名称介绍
主观下线(Subjectively Down, 简称 SDOWN)
是单个 Sentinel 实例对服务器做出的下线判断,比如网络问题接收不到通知等
一个服务器没有在 down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel 返回一个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线
客观下线(Objectively Down, 简称 ODOWN)
指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断
一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线
客观下线条件只适用于主服务器
仲裁 qurum
Sentinel 在给定的时间范围内, 从其他 Sentinel 那里接收到了【足够数量】的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线
这个【足够数量】就是配置文件里面的值,一般是Sentinel个数的一半加1,比如3个Sentinel则就设置为2
down-after-milliseconds 是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用
当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover
主观下线(Subjectively Down, 简称 SDOWN)
是单个 Sentinel 实例对服务器做出的下线判断,比如网络问题接收不到通知等
一个服务器没有在 down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel 返回一个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线
客观下线(Objectively Down, 简称 ODOWN)
指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断
一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线
客观下线条件只适用于主服务器
仲裁 qurum
Sentinel 在给定的时间范围内, 从其他 Sentinel 那里接收到了【足够数量】的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线
这个【足够数量】就是配置文件里面的值,一般是Sentinel个数的一半加1,比如3个Sentinel则就设置为2
down-after-milliseconds 是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用
当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover
核心流程
每秒ping,超过时间不响应 则认为主观下线
满足多个,则认为是客观下线
投票选择主节点
如果没有足够的节点同意master下线,则状态会被移除
每秒ping,超过时间不响应 则认为主观下线
满足多个,则认为是客观下线
投票选择主节点
如果没有足够的节点同意master下线,则状态会被移除
Redis6.X节点高可用之Cluster集群和分片
背景
Sentinel解决了主从架构故障自动迁移的问题
但是Master主节点的写能力和存储能力依旧受限
使用Redis的集群cluster就是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器
Sentinel解决了主从架构故障自动迁移的问题
但是Master主节点的写能力和存储能力依旧受限
使用Redis的集群cluster就是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器
什么是集群Cluster
是一组相互独立的、通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理
容易和分布式弄混,分布式系统简单的可以认为就一个庞大的系统,进行拆分度多个小系统
是一组相互独立的、通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理
容易和分布式弄混,分布式系统简单的可以认为就一个庞大的系统,进行拆分度多个小系统
Redis集群模式介绍
Cluster模式是Redis3.0开始推出
采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接
官方要求:至少6个节点才可以保证高可用,即3主3从;扩展性强、更好做到高可用
各个节点会互相通信,采用gossip协议交换节点元数据信息
数据分散存储到各个节点上
Cluster模式是Redis3.0开始推出
采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接
官方要求:至少6个节点才可以保证高可用,即3主3从;扩展性强、更好做到高可用
各个节点会互相通信,采用gossip协议交换节点元数据信息
数据分散存储到各个节点上
Redis6.X节点高可用之Cluster数据分片和虚拟哈希槽
常见的数据分区算法
哈希取模
对选择的 partitioning key 计算其哈希值,得到的哈希值就是对应的分区
范围分片
通过确定分区键是否在某个范围内来选择分区
一致性Hash分区
redis cluster集群没有采用一致性哈希方案,而是采用【数据分片】中的哈希槽来进行数据存储与读取的
哈希取模
对选择的 partitioning key 计算其哈希值,得到的哈希值就是对应的分区
范围分片
通过确定分区键是否在某个范围内来选择分区
一致性Hash分区
redis cluster集群没有采用一致性哈希方案,而是采用【数据分片】中的哈希槽来进行数据存储与读取的
什么是Redis的哈希槽 slot
Redis集群预分好16384个槽,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中
Redis集群预分好16384个槽,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中
存储查找
对要存储查找的键进行crc16哈希运算,得到一个值,并取模16384,判断这个值在哪个节点的范围区间
假设crc16("test_key")%16384=3000,就是节点一
crc16算法不是简单的hash算法,是一种校验算法
对要存储查找的键进行crc16哈希运算,得到一个值,并取模16384,判断这个值在哪个节点的范围区间
假设crc16("test_key")%16384=3000,就是节点一
crc16算法不是简单的hash算法,是一种校验算法
使用哈希槽的好处就在于可以方便的添加或移除节点。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了
分布式
SpringBoot
SpringBoot中常⽤注解及其底层实现
@SpringBootApplication
@SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个 配置类
@EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean
@ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫 描的路径是启动类所在的当前⽬录
@Bean注解:⽤来定义Bean,类似于XML中的<bean>标签,Spring在启动时,会对加了@Bean注 解的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象
@Controller、@Service、@ResponseBody、@Autowired
SpringBoot是如何启动Tomcat的
1. ⾸先,SpringBoot在启动时会先创建⼀个Spring容器
在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在 Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean
Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后 启动Tomcat
分布式事务
Seata事务处理过程
Seata事务处理过程
Seata的AT模式
Seata的AT模式
TCC
补偿性分布式事务
XA协议 2PC
XA协议 2PC
商品服务
商品服务
分布式事务的解决⽅案之⼀事务消息
分布式事务的解决⽅案之⼀事务消息
优惠劵服务
优惠劵服务
seata事务
Saga 模式
适用场景:
业务流程长、业务流程多
参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
优势: 一阶段提交本地事务,无锁,高性能
事件驱动架构,参与者可异步执行,高吞吐
补偿服务易于实现
缺点:不保证隔离性
适用场景:
业务流程长、业务流程多
参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
优势: 一阶段提交本地事务,无锁,高性能
事件驱动架构,参与者可异步执行,高吞吐
补偿服务易于实现
缺点:不保证隔离性
并发
什么是ABA问题
当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马 上将其修改为A,此时其他线程不感知,还是会修改成功
ABA问题的解决方案
数据库有个锁称为乐观锁,是一种基于数据版本实现数据同步的机制,每次修改一次数据,版本 就会进行累加。
同样,Java也提供了相应的原子引用类AtomicStampedReference<V>,reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性
AQS
AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。它是一个Java提高的底层同步工具类,比如CountDownLatch、ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的
简单来说:是用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态对象
一个是 state(用于计数器,类似gc的回收计数器)
一个是线程标记(当前线程是谁加锁的),
一个是阻塞队列(用于存放其他未拿到锁的线程)
acquire(int arg) 源码讲解,好比加锁lock操作
tryAcquire()尝试直接去获取资源,如果成功则直接返回,AQS里面未实现但没有定义成abstract,因为独占模式下只用实现tryAcquire-tryRelease,而共享模式下只用实现tryAcquireShared-tryReleaseShared,类似设计模式里面的适配器模式
addWaiter() 根据不同模式将线程加入等待队列的尾部,有Node.EXCLUSIVE互斥模式、Node.SHARED共享模式;如果队列不为空,则以通过compareAndSetTail方法以CAS将当前线程节点加入到等待队列的末尾。否则通过enq(node)方法初始化一个等待队列
acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回,如果在等待过程中被中断,则返回true,否则返回false
release(int arg)源码讲解 好比解锁unlock
独占模式下线程释放指定量的资源,里面是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了;在自义定同步器在实现时,如果已经彻底释放资源(state=0),要返回true,否则返回false
unparkSuccessor方法用于唤醒等待队列中下一个线程
并发编程里面解决生产消费者模型你知道哪几种方式?
1、wait() / notify()方法
2、await() / signal()方法 用ReentrantLock和Condition实现等待/通知模型
3、Semaphore信号量
4、BlockingQueue阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
put方法用来向队尾存入元素,如果队列满,则阻塞
take方法用来从队首取元素,如果队列为空,则阻塞
LinkedBlockingQueue
put方法用来向队尾存入元素,如果队列满,则阻塞
take方法用来从队首取元素,如果队列为空,则阻塞
你知道的AQS有几种同步方式
独占式: 比如ReentrantLock
共享式:比如Semaphore
存在组合:组合式的如ReentrantReadWriteLock,AQS为使用提供了底层支撑,使用者可以自由组装实现
注意:线程获取锁成功后直接返回,不会进入等待队列里面,只有失败的时候才会
ReentrantLock和synchronized使用的场景是什么,实现机制有什么不同
synchronized 1、是悲观锁会引起其他线程阻塞,java内置关键字,
2、无法判断是否获取锁的状态,锁可重入、不可中断、只能是非公平
3、加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单但显得不够灵活
4、一般并发场景使用足够、可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁
5、synchronized操作的应该是对象头中mark word,参考原先原理图片
2、无法判断是否获取锁的状态,锁可重入、不可中断、只能是非公平
3、加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单但显得不够灵活
4、一般并发场景使用足够、可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁
5、synchronized操作的应该是对象头中mark word,参考原先原理图片
ReentrantLock 1、是个Lock接口的实现类,是悲观锁,
2、可以判断是否获取到锁,可重入、可判断、可公平可不公平
3、需要手动加锁和解锁,且 解锁的操作尽量要放在finally代码块中,保证线程正确释放锁
4、在复杂的并发场景中使用在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致 其他线程无法获得该锁。
5、创建的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁
6、底层不同是AQS的state和FIFO队列来控制加锁
2、可以判断是否获取到锁,可重入、可判断、可公平可不公平
3、需要手动加锁和解锁,且 解锁的操作尽量要放在finally代码块中,保证线程正确释放锁
4、在复杂的并发场景中使用在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致 其他线程无法获得该锁。
5、创建的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁
6、底层不同是AQS的state和FIFO队列来控制加锁
知道ReentrantReadWriteLock吗?和ReentrantLock有啥不同?
ReentrantReadWriteLock
1、读写锁接口ReadWriteLock接口的一个具体实现,实现了读写锁的分离,
2、支持公平和非公平,底层也是基于AQS实现
3、允许从写锁降级为读锁
流程:先获取写锁,然后获取读锁,最后释放写锁;但不能从读锁升级到写锁
4、重入:读锁后还可以获取读锁;获取了写锁之后既可以再次获取写锁又可以获取读锁
1、读写锁接口ReadWriteLock接口的一个具体实现,实现了读写锁的分离,
2、支持公平和非公平,底层也是基于AQS实现
3、允许从写锁降级为读锁
流程:先获取写锁,然后获取读锁,最后释放写锁;但不能从读锁升级到写锁
4、重入:读锁后还可以获取读锁;获取了写锁之后既可以再次获取写锁又可以获取读锁
核心:读锁是共享的,写锁是独占的。 读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,主要是提升了读写的性能
ReentrantLock是独占锁且可重入的,相比synchronized而言功能更加丰富也更适合复杂的并发场景,但是也有弊端,假如有两个线程A/B访问数据,加锁是为了防止线程A在写数据, 线程B在读数据造成的数据不一致; 但线程A在读数据,线程C也在读数据,读数据是不会改变数据没有必要加锁,但是还是加锁了,降低了程序的性能,所以就有了ReadWriteLock读写锁接口
场景:读多写少,比如设计一个缓存组件 或 提高Collection的并发性
你知道阻塞队列BlockingQueue
常见的阻塞队列
ArrayBlockingQueue:
基于数组实现的一个阻塞队列,需要指定容量大小,FIFO先进先出顺序
基于数组实现的一个阻塞队列,需要指定容量大小,FIFO先进先出顺序
LinkedBlockingQueue:
基于链表实现的一个阻塞队列,如果不指定容量大小,默认 Integer.MAX_VALUE, FIFO先进先出顺序
基于链表实现的一个阻塞队列,如果不指定容量大小,默认 Integer.MAX_VALUE, FIFO先进先出顺序
PriorityBlockingQueue:
一个支持优先级的无界阻塞队列,默认情况下元素采用自然顺序升序排序,也可以自定义排序实现 java.lang.Comparable接口
一个支持优先级的无界阻塞队列,默认情况下元素采用自然顺序升序排序,也可以自定义排序实现 java.lang.Comparable接口
DelayQueue:
延迟队列,在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素,里面的对象必须实现 java.util.concurrent.Delayed 接口并实现CompareTo和getDelay方法
延迟队列,在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素,里面的对象必须实现 java.util.concurrent.Delayed 接口并实现CompareTo和getDelay方法
你知道非阻塞队列ConcurrentLinkedQueue不,它怎么实现线程安全的?
线程安全原因:
ConcurrentLinkedQueue是基于链表实现的无界线程安全队列,采用FIFO进行排序
保证线程安全的三要素:原子、有序、可见性
1、底层结构是Node,链表头部和尾部节点是head和tail,使用节点变量和内部类属性使用volatile声明保证了有序和可见性
2、插入、移除、更新操作使用CAS无锁操作,保证了原子性
3、假如多线程并发修改导致 CAS 更新失败,采用for循环插入保证更新操作成功
ConcurrentLinkedQueue是基于链表实现的无界线程安全队列,采用FIFO进行排序
保证线程安全的三要素:原子、有序、可见性
1、底层结构是Node,链表头部和尾部节点是head和tail,使用节点变量和内部类属性使用volatile声明保证了有序和可见性
2、插入、移除、更新操作使用CAS无锁操作,保证了原子性
3、假如多线程并发修改导致 CAS 更新失败,采用for循环插入保证更新操作成功
平时多线程用的挺多的,写出3条你遵循的多线程最佳实践
给不同模块的线程起名称,方便后续排查问题
使用同步代码块或者同步的方法的时候,尽量减小同步范围
多用并发集合少用同步集合
支持线程安全
同步集合:Hashtable/Vector/同步工具类包装Collections.synXXX
并发集合:ConcurrentHashMap、CopyOnWriteArrayList
线上业务需要使用多线程,优先考虑线程池是否更加合适,然后判断哪种线程池比较好,最后才是自己创建单一线程
使用同步代码块或者同步的方法的时候,尽量减小同步范围
多用并发集合少用同步集合
支持线程安全
同步集合:Hashtable/Vector/同步工具类包装Collections.synXXX
并发集合:ConcurrentHashMap、CopyOnWriteArrayList
线上业务需要使用多线程,优先考虑线程池是否更加合适,然后判断哪种线程池比较好,最后才是自己创建单一线程
用过线程池不? 有什么好处
好处:重用存在的线程,减少对象创建销毁的开销,有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,且可以定时定期执行、单线程、并发数控制,配置任务过多任务后的拒绝策略等功能
0 条评论
下一页