<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>zzpp-个人档案</title>
    <link>http://www.yooyaa.cn</link>
    <description>zzpp,博客系统,个人文档</description>
    <language>zh-CN</language>
    <item>
      <title>Linux学习笔记-1</title>
      <link>http://www.yooyaa.cn/article/21</link>
      <content:encoded>&lt;h1&gt;1.查看端口的命令  &lt;/h1&gt; &lt;p&gt;netstat -nlpt 》用于显示网络信息&lt;/p&gt; &lt;p&gt;lsof -i:端口号   》用于列出当前系统打开的文件的工具&lt;/p&gt; &lt;p&gt;ss -tuln 》用于显示套字节信息，是netstat的替代品&lt;/p&gt; &lt;p&gt;fuser -v -n tcp &amp;lt;端口号&amp;gt;  ：用于查找使用指定文件或文件系统的进程&lt;/p&gt; &lt;h1&gt;2.linux开机启动设置&lt;/h1&gt; &lt;p&gt;1.将启动脚本天骄到/etc/init.d/&lt;/p&gt; &lt;p&gt;2.编辑/etc/rc.local&lt;/p&gt; &lt;p&gt;3.在/etc/systemd/system下创建xxxx.service服务 ：systemctl start xxxx.service&lt;/p&gt; &lt;h1&gt;3.linux查看日志&lt;/h1&gt; &lt;p&gt;1.tail -n 2000 -f 可以查看就近2000行日志&lt;/p&gt; &lt;p&gt;2.使用less&lt;/p&gt; &lt;p&gt;3.使用cat&lt;/p&gt; &lt;h1&gt;4.linux文件搜索&lt;/h1&gt; &lt;p&gt;1.find命令：file &amp;lt;path&amp;gt; -name &amp;quot;关键字&amp;quot;  （速度慢）&lt;/p&gt; &lt;p&gt;2.locate命令：是一个基于数据库的文件查找工具&lt;/p&gt; &lt;p&gt;3.grep命令： 可以搜索文件文本中搜索模式或关键字  ：grep -r “关键字” &amp;lt;path&amp;gt;&lt;/p&gt; &lt;p&gt;4.which命令：用于在系统路劲中可执行文件的位置&lt;/p&gt; &lt;p&gt;5.whereis命令：用于查找二进制、源代码和手册页文件的位置  ：whereis 文件名&lt;/p&gt; &lt;h1&gt;5.查看磁盘信息&lt;/h1&gt; &lt;p&gt;df -h&lt;/p&gt; &lt;h1&gt;6.进行软链接&lt;/h1&gt; &lt;p&gt;ln -s &amp;lt;源&amp;gt; &amp;lt;目标&amp;gt;&lt;/p&gt; &lt;h1&gt;7.nginx 如何只绑定域名&lt;/h1&gt; &lt;p&gt;配置白名单&lt;/p&gt; &lt;h1&gt;8.nginx只允许内外ip访问，禁止外网访问&lt;/h1&gt; &lt;p&gt;配置location 访问清单&lt;/p&gt; &lt;h1&gt;9.docker push到新的仓库&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;改名docker tag push &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 09:13:00 GMT</pubDate>
    </item>
    <item>
      <title>Kubernetes笔记</title>
      <link>http://www.yooyaa.cn/article/20</link>
      <content:encoded>&lt;h1&gt;1.各大组件&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;控制层面&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;apiserver:&lt;/strong&gt;(rest http)是最核心的组件，是k8s的入口&lt;/p&gt; &lt;p&gt;**etcd:**是k8s资源的DB&lt;/p&gt; &lt;p&gt;k**ube-scheduler:**是k8s默认的调度器（可以自己实现）{发现集群中新创建且尚未被调度到Node上的pod,保证节点上有足够的资源供其上的所有Pod使用}&lt;/p&gt; &lt;p&gt;**kube-controller-manager:**控制器（replicaset controller,node controller,namespace controller和serviceAccount Controller）&lt;/p&gt; &lt;p&gt;&lt;strong&gt;kube-proxy:&lt;/strong&gt; 用于网络代理运行在node上&lt;/p&gt; &lt;p&gt;**kubelet:**是运行在每个worker节点的代理组件，他会监视已分配给节点的pod&lt;/p&gt; &lt;p&gt;**kubectl:**客户端管理工具&lt;/p&gt; &lt;pre&gt;&lt;code&gt;打印日志 kubectl   logs  -n  jx-staging  --tail=1000   -f  单个删除命令 kubectl delete pods -n  打印容器详细信息 kubectl describe pods -n jx-staging  content-center-content-center-64ccb69bb9-6hdnf 查看cpu内存资源 kubectl top pods -n jx-staging 查看容器ip kubectl get pods -o wide -n jx-staging &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Ingress:是反向代理机制，用于跪地http/s请求应该被转发到哪个service上，比如根据不同的host和url&lt;/p&gt; &lt;p&gt;Pod: k8s中应用和服务的最小单元，一个pod中可以部署多个容器&lt;/p&gt; &lt;p&gt;Deployment: 提供&lt;/p&gt; &lt;p&gt;Service:是一个部署的多服务&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 09:12:00 GMT</pubDate>
    </item>
    <item>
      <title>Mq消息队列面试笔记</title>
      <link>http://www.yooyaa.cn/article/19</link>
      <content:encoded>&lt;h1&gt;&lt;strong&gt;为什么使用消息队列？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;解耦，异步，削峰&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;缺点：降低系统的可用性（外部依赖越多，越容易挂掉）&lt;/p&gt; &lt;p&gt;   系统复杂度提高&lt;/p&gt; &lt;p&gt;   一致性问题&lt;/p&gt; &lt;p&gt;Kafka,RabbitMq,RocketMq的优缺点&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th align="left"&gt;特性&lt;/th&gt; &lt;th align="center"&gt;RabbitMq&lt;/th&gt; &lt;th align="center"&gt;RocketMq&lt;/th&gt; &lt;th align="center"&gt;Kafka&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td align="left"&gt;单机吞吐率&lt;/td&gt; &lt;td align="center"&gt;万级&lt;/td&gt; &lt;td align="center"&gt;10万级&lt;/td&gt; &lt;td align="center"&gt;10万（用于大数据实时计算，日志采集）&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;topic数量对应吞吐量&lt;/td&gt; &lt;td align="center"&gt;&lt;/td&gt; &lt;td align="center"&gt;topic达到几千吞吐量下降幅度小&lt;/td&gt; &lt;td align="center"&gt;topic从几十到几百的时候吞吐量大幅度下降（增加更多机器资源）&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;时效性&lt;/td&gt; &lt;td align="center"&gt;微秒级，延迟最低&lt;/td&gt; &lt;td align="center"&gt;ms级&lt;/td&gt; &lt;td align="center"&gt;延迟在ms级以内&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;可用性&lt;/td&gt; &lt;td align="center"&gt;高，基于主从架构实现高可用&lt;/td&gt; &lt;td align="center"&gt;非常高，分布式架构&lt;/td&gt; &lt;td align="center"&gt;非常高，分布式，一个数据多个副本，少数宕机不会丢失数据，不可用&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;消息可靠性&lt;/td&gt; &lt;td align="center"&gt;基本不丢&lt;/td&gt; &lt;td align="center"&gt;经过优化配置，可以做到0丢失&lt;/td&gt; &lt;td align="center"&gt;经过优化配置，可以做到0丢失&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;功能支持&lt;/td&gt; &lt;td align="center"&gt;基于erlang开发，并发能力腔，性能好，延迟低&lt;/td&gt; &lt;td align="center"&gt;mq功能较为完善，分布式的，扩展性好&lt;/td&gt; &lt;td align="center"&gt;功能较简单，主要应用与大数据实时计算以及日志采集被大规模使用&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;1.如何选择消息队列&lt;/p&gt; &lt;p&gt; 1.普遍使用rabbitMq，延迟低，高可用，性能好。但是因为erlang开发的，java开发人员接触不深。重在社区完善。不会黄&lt;/p&gt; &lt;p&gt; 2.rocketMq目前已经捐献给了apache,但是目前活跃度不算高&lt;/p&gt; &lt;p&gt; 3.kafka 主要还是应用于大数据领域&lt;/p&gt; &lt;h1&gt;如何保证消息队列的高可用？&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.RabbitMq的高可用：主要基于主从实现系统高可用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   不是分布式消息队列，是传统的消息队列，提供了一些高可用机制&lt;/p&gt; &lt;p&gt; 单机模式，普通集群模式，镜像集群模式&lt;/p&gt; &lt;p&gt; &lt;strong&gt;单机模式：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; **普通集群模式（无高可用性）：**多个rabbitMq实例，每个实例都同步queue的元数据（配置信息，用于找对应实例）。每次随机拉去指定实例的数据&lt;/p&gt; &lt;p&gt;  如果开启消息持久化，消息不一定会丢&lt;/p&gt; &lt;p&gt;&lt;strong&gt;镜像集群模式（高可用性）：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  后台管理系统里面开启， 会将queue元数据完整镜像到每台实例上面。缺点开销大&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.kafka的高可用性：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; kafka：是由多个broker组成，每个broker是一个节点，创建一个topic这个topic会划分为多个partiton,每个partition存在与不同的broker上面，每个partition放一部分数据,天然的分布式消息队列。&lt;/p&gt; &lt;p&gt; kafka 0.8以后提供了Ha机制，replice(复制品)副本机制，每个protiton都会同步到其他机器上，形成自己的多个replice副本，所以replica会选举一个leader出来，与生产者和消费者打交道，其他replice就是follower，写的时候leader会负责吧数据写道folloer上面。读的时候直接的leader就行，&lt;strong&gt;只能读leader吗？要是随机读写任意follower，需要处理数据一致性问题。复杂度太高，容易出问题&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;ack机制，&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  写数据：生产者写leader,然后leader将数据逻辑写本地磁盘，然后follower 主动从leader来pull数据，一旦folloer写好数据会发送ack给leader,leader收到所有的follower的ack消息，就会返回成功给生产者（可以调整）&lt;/p&gt; &lt;p&gt;  消费的时候，从leader去读取，只有当一个消息被所有follower都同步成功返回ack才能被读取到&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;如何保证消息的幂等性（如何保证消息不被重复消费）？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt; kafka：有个offset的概念，就是每个消息写进去，都有一个offset代表消费序号，然后consumer消费了数据之后，每隔一段时间会吧自己消费过的offset提交一下。但是如果遇到kill等宕机问题，没有提交offset就会重复销毁&lt;/p&gt; &lt;p&gt;  token机制，version乐观锁机制，唯一键约束机制，基于redis校验&lt;/p&gt; &lt;h1&gt;如何保证消息可靠性传说，或者说，如何处理消息的丢失问题&lt;/h1&gt; &lt;p&gt; rabbitMq：&lt;/p&gt; &lt;p&gt;    生产者弄丢数据 （网络问题）&lt;/p&gt; &lt;p&gt;      1.（开启rabbitMq的事务，提过回滚提交），缺点：吞吐量不高&lt;/p&gt; &lt;p&gt;      2.开启confirm模式，每次写消息都会分配一个唯一的id,如果写入rabbitMq成功后，会回传一个ack消息，如果没处理成功会回调nack接口&lt;/p&gt; &lt;p&gt;      区别：事务机制是同步的，confirm模式是异步的 &lt;/p&gt; &lt;p&gt;    RabbitMq弄丢了数据&lt;/p&gt; &lt;p&gt;       开启rabbitMq的持久化（设置queue元数据，和发送消息deliverMode设置为2）&lt;/p&gt; &lt;p&gt;    消费端弄丢数据&lt;/p&gt; &lt;p&gt;       提供ack机制，关闭自动ack。如果处理完毕后手动提交ack&lt;/p&gt; &lt;p&gt;kafka弄丢数据：&lt;/p&gt; &lt;p&gt; kafka的某个broker待机，然后重新选举partiton的leader,这个时候可能会丢失一条消息（上一个leader挂掉之前未能全部同步folloer）&lt;/p&gt; &lt;p&gt; 避免：给topic设置多个副本。在服务端要求leader至少感知到一个follower跟自己保持联系，没有掉队。这样能确保leader挂了后还有一个follower还或者&lt;/p&gt; &lt;p&gt; 将producer端设置成ack=all模式，这样保证每条数据写入到所有replica后才能算写成功&lt;/p&gt; &lt;p&gt; 将producer端设置成retries=Max 这样一旦写入失败，就可以无限充实&lt;/p&gt; &lt;p&gt;&lt;strong&gt;生产者会不会弄丢数据？设置成ack=all 一定不会丢&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;如何保证消息的顺序性？  &lt;/h1&gt; &lt;p&gt; rabbitmq拆分成多个queue，每个queue对应一个consumer&lt;/p&gt; &lt;p&gt; kafka：使用单个partition&lt;/p&gt; &lt;p&gt; 分组group概念&lt;/p&gt; &lt;h1&gt;如何解决消息队列延时以及过期失效问题？消息队列满了以后该怎么处理？有几百万消息积压几小时，说说怎么解决？&lt;/h1&gt; &lt;p&gt; 产生这种情况一般都是消费端出现了问题，不消费或者消费极慢&lt;/p&gt; &lt;p&gt;1：如果是系统并发不高的情况下，恢复消费端让他慢慢消费&lt;/p&gt; &lt;p&gt;2.是临时扩容，使用一个新的topic和partition 是原来的10倍，写一个分发消费者的程序去消费挤压的数据，&lt;/p&gt; &lt;h1&gt;如果过期失效？&lt;/h1&gt; &lt;p&gt;那么就批量重导数据，写一个临时程序在晚上批量灌入&lt;/p&gt; &lt;p&gt;  &lt;/p&gt; &lt;h1&gt;一、KAFKA&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;是一个分布式，支持分区，多副本，基于zookeeper协调的分布式消息系统（通讯协议是Tcp&lt;/strong&gt;）&lt;/p&gt; &lt;h3&gt;基本概念&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Broker: 处理节点，一个kafka节点就是一个broker Topic: kafka是根据Topic对消息分类的，发布到集群的消息都需要指定该tOpic Producer：消息生产 Consumer:小肥猪 ConsumerGroup:消费者分组 Partition:一个topic可以分为多个分区，每个分区的内部消息是有序的     Partition  - 是一个有序的message序列，这些message按顺序添加到commit log文件中，每个partition中的消息都有一个唯一的编号，称为offset,  - 每个partition对应一个commit log文件。每个partition中的offset是唯一的，不同的partition的offset可能相同  - 消费offset是由consumer自己来维护的，所以kafka集群是无状态的。性能不会因为consumer的过多而影响，kafka还将很多关键信息存储到zookeeper中，保证自己的无状态，从而水平扩展的时候方便 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;为什么对Topic下进行分区存储？&lt;/h3&gt; &lt;p&gt;1.commit log文件系统大小的约束，理论一个topic可以处理任意数量的数据&lt;/p&gt; &lt;p&gt;2.为了提高并行度&lt;/p&gt; &lt;h3&gt;分布式Distribution&lt;/h3&gt; &lt;p&gt; commit log的partitions分布在kafka集群中不同的broker上，每个broker可以请求备份其他broker上partition上的数据。kafka 集群支持配置一个partition备份的数量。&lt;/p&gt; &lt;p&gt; 针对每个partition，都有一个broker起到“leader”的作用，0个或多个其他的broker作为“follwers”的作用。&lt;/p&gt; &lt;p&gt;leader处理所有的针对这个partition的读写请求，而followers被动复制leader的结果。如果这个leader失效了，其中 的一个follower将会自动的变成新的leader。&lt;/p&gt; &lt;p&gt;   Producers 生产者将消息发送到topic中去，同时负责选择将message发送到topic的哪一个partition中。通过round­robin做简单的 负载均衡。也可以根据消息中的某一个关键字来进行区分。通常第二种方式使用的更多。&lt;/p&gt; &lt;p&gt;    Consumers 传统的消息传递模式有2种：队列( queue) 和（publish-subscribe）&lt;/p&gt; &lt;p&gt;        queue模式：多个consumer从服务器中读取数据，消息只会到达一个consumer。所有的consumer都位于同一个consumer group 下。&lt;/p&gt; &lt;p&gt;        publish-subscribe模式：消息会被广播给所有的consumer。所有的consumer都有着自己唯一的consumer group。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;消费顺序&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   Kafka比传统的消息系统有着更强的顺序保证。 一个partition同一个时刻在一个consumer group中只有一个consumer instance在消费，从而保证顺序。 consumer group中的consumer instance的数量不能比一个Topic中的partition的数量多，否则，多出来的 consumer消费不到消息。&lt;/p&gt; &lt;h3&gt;kafka的ack机制&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;acks=1 (默认,至少等待leader成功将数据写入到log中)&lt;/li&gt; &lt;li&gt;acks = -1/all（需要等待数据将leader和follower全部写入成功后，返回确认数据,同步副本数量还受到min,insync,.replicas（备份副本数量）限制）&lt;/li&gt; &lt;li&gt;acks=0（性能最高，表示不需要等待broker确认收到消息）&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;producers 本地缓冲区（buffer-&amp;gt;batch），默认32mb，默认每16kb批量发送一次，以及超时必须发送&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;Kafka核心总控制器Controller&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;在Kafka集群中会有一个或者多个broker，其中有一个broker会被选举为控制器（Kafka Controller），它负责管理整个 集群中所有分区和副本的状态。&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;当某个分区的leader副本出现故障时，由控制器负责为该分区选举新的leader副本。&lt;/li&gt; &lt;li&gt;当检测到某个分区的ISR集合发生变化时，由控制器负责通知所有broker更新其元数据信息。&lt;/li&gt; &lt;li&gt;当使用kafka-topics.sh脚本为某个topic增加分区数量时，同样还是由控制器负责分区的重新分配。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;Controller选举机制&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   在kafka集群启动的时候，会自动选举一台broker作为controller来管理整个集群，选举的过程是集群中每个broker都会 尝试在zookeeper上创建一个 /controller 临时节点，zookeeper会保证有且仅有一个broker能创建成功，这个broker 就会成为集群的总控器controller。 当这个controller角色的broker宕机了，此时zookeeper临时节点会消失，集群里其他broker会一直监听这个临时节 点，发现临时节点消失了，就竞争再次创建临时节点，就是我们上面说的选举机制，zookeeper又会保证有一个broker 成为新的controller。 具备控制器身份的broker需要比其他普通的broker多一份职责，&lt;/p&gt; &lt;h3&gt;内容精炼&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;消息顺序消费：     1. 指定同一个分区     2. 使用相同的key(变相的使用同一个分区) 消息丢失：     分为生产者、服务端、客户端三个方面     生产者：         设置异步发送，发送失败使用回调进行处理再发送         失败重试，设置重试次数     kafka服务端：         发送机制设置ack，设置all，只有当所有副本保存成功之后才会认为成功     消费者：         分区的消息都有一个分区内的唯一偏移量 0 开始自增，各个分区的消息消费各有不同，所以再重平衡消费者之后，有可能导致消息丢失         关闭自提交偏移量，开启手动提交偏移量，避免提交比较靠后的偏移量导致中间部分消息未被消费到         提交方式改为 同步 + 异步方式 消息重复消费：     唯一、幂等 高可用机制： 数据清理机制：     1. 默认保存7天(168小时)     2. topic存储数据的大小，如果大小超过一定阈值，就会删除从最早的数据开始删除 高性能设计：     1. 消息分区，不受单节点限制，处理更多数据     2. 顺序读写，磁盘的顺序读写，提升读写效率     3. 页缓存，在内存中进行数据的操作，变磁盘访问为内存访问     4. 零拷贝，减少中间态的IO复制，即上下文切换和数据拷贝     5. 消息压缩，较少磁盘IO和网络IO     6. 分批发送，将消息批量发送，减少网络IO的开销  在实际生产应用中，通常会使用kafka作为消息传输的数据管道，rabbitmq作为交易数据作为数据传输管道，主要的取舍因素则是是否存在丢数据的可能；     rabbitmq在金融场景中经常使用，具有较高的严谨性，数据丢失的可能性更小，同事具备更高的实时性；     而kafka优势主要体现在吞吐量上，虽然可以通过策略实现数据不丢失，但从严谨性角度来讲，大不如rabbitmq；而且由于kafka保证每条消息最少送达一次，有较小的概率会出现数据重复发送的情况；  https://blog.csdn.net/wanghaiping1993/article/details/125346010  kafka如何实现数据的高效读取？（顺序读写、分段命令、二分查找）     Kafka为每个分段后的数据文件建立了索引文件，文件名与数据文件的名字是一样的，只是文件扩展名为index。     index文件中并没有为数据文件中的每条Message建立索引，而是采用了稀疏存储的方式，每隔一定字节的数据建立一条索引。     这样避免了索引文件占用过多的空间，从而可以将索引文件保留在内存中。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;二、Rabbit MQ&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;elang语言开发，能做到微秒级别延迟&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;基本组件概念&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;Broker：消息队列服务进程，此进程包括两个部分：Exchange和Queue &lt;/li&gt; &lt;li&gt;Exchange：消息队列交换机，按一定的规则将消息路由转发到某个队列，对消息进行过虑。 &lt;/li&gt; &lt;li&gt;Queue：消息队列，存储消息的队列，消息到达队列并转发给指定的 &lt;/li&gt; &lt;li&gt;Producer：消息生产者，即生产方客户端，生产方客户端将消息发送 &lt;/li&gt; &lt;li&gt;Consumer：消息消费者，即消费方客户端，接收MQ转发的消息。 &lt;/li&gt; &lt;li&gt;Connection：生产者，消费者和 broker 之间的 TCP 连接 &lt;/li&gt; &lt;li&gt;Channel：信道、Channel 作为轻量级的 'Connection 极大减少了操作系统建立 TCP connection 的开销 &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;基本概念&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;生产者（Producer）：发送消息的应用。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;消费者（Consumer）：接收消息的应用。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;队列（Queue）：存储消息的缓存。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;消息（Message）：由生产者通过RabbitMQ发送给消费者的信息。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;连接（Connection）：连接RabbitMQ和应用服务器的TCP连接。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;信道（Channel）：连接里的一个虚拟通道，通过消息队列发送或者接收消息时，都是通过信道进行的。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;交换机（Exchange）：交换机负责从生产者那里接收消息，并根据交换类型分发到对应的消息队列里。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;绑定（Binding）：绑定是队列和交换机的一个关联连接。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;路由键（Routing Key）：路由键是供交换机查看并根据键来决定如何分发消息到队列的一个键，路由键可以说是消息的目的地址。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;代理（Broker）：接收和分发消息的应用，RabbitMQ Server就是Message Broker。 &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;虚拟主机（Virtual host）：出于多租户和安全因素设计的，把AMQP的基本组件划分到一个虚拟的分组中，类似于网络中的namespace概念。 &lt;/p&gt; &lt;p&gt;   &lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;当多个不同的用户使用同一个RabbitMQ server提供的服务时，可以划分出多个vhost，每个用户在自己的vhost创建exchange/queue 等。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;死信队列&lt;/h3&gt; &lt;p&gt; 死信队列（DLX，Dead-Letter-Exchange），利用DLX，当消息在一个队列中变成无法被消费的消息（dead message）之后，它能被重新publish到另一个Exchange，这个Exchange就是DLX。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1、 消息被拒绝（channel.basicReject/channel.basicNack）并且request=false； 2、 消息在队列的存活时间超过设置的生存时间（TTL）时间； 3、 队列达到最大长度（队列满了，无法再添加数据到队列中）。 DLX也是一个正常的Exchange，和一般的Exchange没有区别，它能在任何的队列上被指定，实际上就是设置某个队列的属性。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;延迟插件实现原理&lt;/h3&gt; &lt;p&gt; rabbitmq_delayed_message_exchange插件，实现延迟队列效果。 它是一种新的交换类型，该类型消息支持延迟投递机制消息传递后并不会立即投递到目标队列中，而是存储在mnesia（一个分布式数据系统）表中， 当达到投递时间时，才投递到目标队列中。使用延迟队列，可以有效解决定时任务带来的系统压力以及业务处理时效性等问题。&lt;/p&gt; &lt;h3&gt;五大消息模型&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;基本消息模型：   生产者将消息发送到队列，消费者从队列中获取消息，队列是存储消息的缓冲区。  work消息模型：   让多个消费者绑定到一个队列，共同消费队列中的消息。队列中的消息一旦消费，就会消失，因此任务是不会被重复执行的  订阅模型：   1、1个生产者，多个消费者   2、每一个消费者都有自己的一个队列   3、生产者没有将消息直接发送到队列，而是发送到了交换机   4、每个队列都要绑定到交换机   5、生产者发送的消息，经过交换机到达队列，实现一个消息被多个消费者获取的目的  订阅模型-Fanout 广播   1） 可以有多个消费者   2） 每个消费者有自己的queue（队列）   3） 每个队列都要绑定到Exchange（交换机）   4） 生产者发送的消息，只能发送到交换机，交换机来决定要发给哪个队列，生产者无法决定。   5） 交换机把消息发送给绑定过的所有队列   6） 队列的消费者都能拿到消息。实现一条消息被多个消费者消费  订阅模型-Direct   在 Direct 模型下，队列与交换机的绑定，不能是任意绑定了，而是要指定一个 RoutingKey（路由 key），                 消息的发送方在向 Exchange 发送消息时，也必须指定消息的 routing key。   P：生产者，向 Exchange 发送消息，发送消息时，会指定一个 routing key。   X：Exchange（交换机），接收生产者的消息，然后把消息递交给 与 routing key 完全匹配的队列   C1：消费者，其所在队列指定了需要 routing key 为 error 的消息   C2：消费者，其所在队列指定了需要 routing key 为 info、error、warning 的消息  订阅模型-Topic   Topic 类型的 Exchange 与 Direct 相比，都是可以根据 RoutingKey 把消息路由到不同的队列。   只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符！   通配符规则：#：匹配一个或多个词*：匹配不多不少恰好 1 个词 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;5种消息模式&lt;/h3&gt; &lt;p&gt;1.简单模式：一个生产者，默认交换机，一个队列，一个消费者&lt;/p&gt; &lt;p&gt;2.工作模式：一个生产者，默认交换机，一个队列，多个消费者&lt;/p&gt; &lt;p&gt;3.发布订阅模式：一个生产者，一个交换机，多个队列，多个消费者&lt;/p&gt; &lt;p&gt;4.路由模式：一个生成者，一个交换机，多个队列，多个消费者，消息发到交换机，然后进行路由给对象的队列消费者&lt;/p&gt; &lt;p&gt;5.主题topic模式：&lt;/p&gt; &lt;h1&gt;三、rocketMQ&lt;/h1&gt; &lt;h1&gt;其他问题&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;在实际生产应用中，通常会使用kafka作为消息传输的数据管道，rabbitmq作为交易数据作为数据传输管道，主要的取舍因素则是是否存在丢数据的可能；&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;rabbitmq在金融场景中经常使用，具有较高的严谨性，数据丢失的可能性更小，同事具备更高的实时性；&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;而kafka优势主要体现在吞吐量上，虽然可以通过策略实现数据不丢失，但从严谨性角度来讲，大不如rabbitmq；而且由于kafka保证每条消息最少送达一次，有较小的概率会出现数据重复发送的情况；&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;1.mq介绍及使用场景&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;     消息队列 messageQueue 先进先出队列     优点：     异步(减少响应时间)、解耦(分支)、削峰填谷、数据分发     缺点：     可用性减低(mq宕机，影响业务)、系统复杂度提高(数据链路变长，消息丢失、重复调用、 序性)、数据一致性 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;2.保证mq消息不丢失&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;造成消息丢失的原因：     生产者丢失：         消息发送成功确认回调机制         rocketmq的事务消息:Producer端消息发送事件和本地事务事件，同时成功或同时失败                 正常事务的发送及提交、事务信息的补偿流程(都是针对生产者 因为事务只出现在DataBase中 有些情况需要将消息存储在数据库中 如果发生事务问题…)         整体流程为:             正常事务发送与提交阶段             生产者发送一个半消息给broker(半消息是指的暂时不能消费的消息)             服务端响应             开始执行本地事务             根据本地事务的执行情况执行Commit或者Rollback             事务信息的补偿流程                          如果broker长时间没有收到本地事务的执行状态,会向生产者发起一个确认会查的操作请求             生产者收到确认会查请求后,检查本地事务的执行状态             根据检查后的结果执行Commit或者Rollback操作 补偿阶段主要是用于解决生产者在发送Commit或者Rollback操作时发生超时或失败的情况     mq服务端丢失：         主从节点同步数据丢失             刷盘丢失(内存写入磁盘)         消费者端丢失：          如何避免消息丢失：             生产者丢失：  保证消息顺序消费：     全局有序和局部有序：mq 只需要保证局部有序，并不需要保证全局有序(无业务意义)     rocketmq：有完整的设计(生产顺序性和消费顺序性)  保证消息消费的幂等性：     mq 产品并没有提供主动解决幂等性的机制，需要由消费者自行控制     rocketmq: 生产者给消息分配了一个 messageId，这个 messageId就是作为消费者判断幂等的依据，但是有可能全局不唯一         在消息中手动设置一个 全局唯一标识，来进行幂等判断。 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 09:01:00 GMT</pubDate>
    </item>
    <item>
      <title>ElasticSearch</title>
      <link>http://www.yooyaa.cn/article/17</link>
      <content:encoded>&lt;p&gt;&lt;strong&gt;ElasticSearch：是一个基于Lucene的搜索服务器，天然支持分布式，近实时查询&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;Elasticsearch 的 master 选举流程？&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;Elasticsearch在满足如下时间点的时候会触发选举 集群启动初始化 集群的Master崩溃的时候 任何一个节点发现当前集群中的Master节点没有得到n/2 + 1节点认可的时候，触发选举 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Elasticsearch使用Zookeeper或者内置的Zen Discovery机制来实现主节点(master)选举。在Elasticsearch中，一个集群中的节点被分为两类：主节点(master-eligible nodes)和数据节点(data nodes)。主节点负责集群级别的管理任务，例如索引创建、节点加入/退出等，而数据节点则负责存储和处理数据。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;精炼&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;倒排索引：属性值来确定记录的位置，因而称为倒排索引 倒排表 posting list 压缩算法：   数据表示越大，所占的空间也就越大，以 int 为例  **FOR算法(Frame Of Reference)：**核心思想是用减法来削减数值大小，从而达到降低空间存储，存储的是后一位减去前一位的差值，所以稀疏的数组，不适合使用FOR算法   &lt;/p&gt; &lt;p&gt;** RBM算法(RoaringBitMap)**：当稀疏的数组 每一项的值都除以 16bit的最大值 65535 得到的余和商作为表示此数据的一个形式，将得到的K-V存入一个数组中，并且源数组为有序数组，高低位16位转换后  商和同商的余都是从小到大排列的&lt;/p&gt; &lt;h1&gt;问题收集&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;1.设计理念&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ElasticSearch 设计的理念就是分布式搜索引擎，底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 es 进程实例，组成了一个 es 集群&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;3.什么是ES？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES是Elasticsearch的缩写，是一款开源的分布式搜索引擎。它可以快速地存储、搜索和分析大量的数据，支持全文检索、结构化查询等多种查询方式。ES的主要特点是速度快、可扩展、高可用和易于使用。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;4.ES的主要用途是什么？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES主要用于建立搜索引擎、日志分析、监控等场景。在搜索引擎领域，ES可以快速地检索海量数据，支持复杂的查询语句和聚合操作。在日志分析领域，ES可以实时地收集、分析和可视化大量的日志数据。在监控领域，ES可以实时地监控系统、网络、服务器等各种指标数据。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;5.ES的数据存储方式是什么？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES使用的是倒排索引的方式来存储数据。倒排索引是一种将文档中的单词映射到包含这些单词的文档中的数据结构。它可以快速地定位文档中包含某个单词的位置，从而实现快速的全文检索。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;6.ES的数据分片是如何实现的？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES的数据分片是通过将数据分成多个分片来实现的。每个分片都是一个独立的索引，包含部分数据。分片可以在多个节点上分布式存储，提高了数据的可用性和可扩展性。当进行查询时，ES会自动将查询请求分发到所有相关的分片上，并将结果进行合并返回。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;7.ES的查询语句有哪些？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES的查询语句主要有以下几种：&lt;/p&gt; &lt;p&gt;（1）match查询：用于执行全文搜索。&lt;/p&gt; &lt;p&gt;（2）term查询：用于匹配精确值。&lt;/p&gt; &lt;p&gt;（3）range查询：用于匹配指定范围内的值。&lt;/p&gt; &lt;p&gt;（4）bool查询：用于组合多个查询语句。&lt;/p&gt; &lt;p&gt;（5）match_phrase查询：用于匹配短语。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;8.ES的聚合操作有哪些？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES的聚合操作主要有以下几种：&lt;/p&gt; &lt;p&gt;（1）count聚合：用于计算文档数量。&lt;/p&gt; &lt;p&gt;（2）sum聚合：用于计算指定字段的总和。&lt;/p&gt; &lt;p&gt;（3）avg聚合：用于计算指定字段的平均值。&lt;/p&gt; &lt;p&gt;（4）max聚合：用于计算指定字段的最大值。&lt;/p&gt; &lt;p&gt;（5）min聚合：用于计算指定字段的最小值。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;9.ES的集群是如何工作的？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES的集群是由多个节点组成的，每个节点都是独立的进程。当启动一个节点时，它会自动加入到集群中，参与数据的存储和查询。ES的集群通过Master节点进行协调和管理，Master节点负责维护集群状态、节点状态和分片状态等信息。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;10.ES的数据备份和恢复如何实现？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;答：ES的数据备份和恢复可以通过快照和恢复功能来实现。快照是对索引和分片的一份拷贝，可以保存在本地或远程存储库中。当需要恢复数据时，可以从快照中恢复索引和分片。此外，ES还提供了基于日志的复制机制，可以在多个节点之间复制数据，提供数据冗余和高可用性。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;11.es的分区与分片？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;12.es的分布式架构原理&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;index-&amp;gt;type-&amp;gt;mapping-&amp;gt;document-&amp;gt;field&lt;/p&gt; &lt;p&gt;index: 索引，相当于mysql的表 type:es7.0已经移除 mapping:是表结构 document：是一行文档数据 field:是字段值&lt;/p&gt; &lt;p&gt;es一个index可以拆分成多个shard（分片）,每个shard（分片）存储部分数据。拆分的好处就是可以&lt;strong&gt;支持横向扩展..提高吞吐量&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;如何新增shard呢？ 新建一个index为4个分片索引，批量导入护具&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;shard:有多个备份。或者说每个shard都有一个主shard,负责写入数据，再同步到其他的副本shard，这样实现高可用&lt;/p&gt; &lt;p&gt;es集群有多个节点，会自动选举一个节点当master,这个master节点其实就是干一些管理工作的，（维护元数据，复制切换主分片与副分片），如果master宕机会重新选举maser节点 ，如果非master宕机会将该节点上面的主shard转移到其他节点上面&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;13.es写入数据的工作原理是什么，查询数据的工作原理是什么，倒排索引了解吗？&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;es写数据过程：客户端会选择一个node发送请求过去，这个node被称为（协调节点）&lt;/p&gt; &lt;p&gt;        协调节点对document进行**路由，**将请求转发到对应的node上面（主分片所在节点）&lt;/p&gt; &lt;p&gt;        实际的node节点来处理请求。，然后将数据同步到replica node(副本)&lt;/p&gt; &lt;p&gt;        协调节点如果发现主节点和所有副本节点都处理完成后，会返回响应结果给客户端。&lt;/p&gt; &lt;p&gt;es读数据过程：客户端发送请求到任意node,成为协调节点&lt;/p&gt; &lt;p&gt;        协调节点对doc的id进行hash路由，将请求转发到对应的node节点上面，此时会用随机轮询算法再主分片和副分片中随机选择一个，以达到负载均衡&lt;/p&gt; &lt;p&gt;es的搜索数据过程：es最强大的是做全文检索&lt;/p&gt; &lt;p&gt;         客户端发送请求到协调node,&lt;/p&gt; &lt;p&gt;        协调节点将请求转发到对应sheard节点上面&lt;/p&gt; &lt;p&gt;        query phase:每个shard将自己的搜索结果返回给协调节点，由协调节点对数据进行合并、排序、分页等操作，产出结果&lt;/p&gt; &lt;p&gt;        fetch phase:接着由协调节点根据doc id去各个节点拉取实际的docment数据最终返回给客户端&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;写数据底层原理&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt; 当数据发送到主shard的时候，会先写入内存buffer（在buffer里的时候数据是搜索不到的），同时写入translog日志文件。&lt;/p&gt; &lt;p&gt; 如果buffer快满了，或者到一定时间。就会将buffer中的数据refresh到新的segment file中。但此时数据不是直接写入segment file磁盘文件。而是进入os cache(操作系统缓存)。这个过程就是refresh&lt;/p&gt; &lt;p&gt;    大约每隔1秒，es就是将buffer中的数据写入到一个新的segment file，每1s产生一个新的磁盘文件。如果buffer没有数据就不会产生segment file&lt;/p&gt; &lt;p&gt; 在操作系统里面。磁盘文件都有一个东西叫os cache(操作系统缓存)。数据写入磁盘都是写入到os cache里面。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;为什么es叫准实时？即1秒刷新一次buffer到磁盘才能被搜索。可以调用api手动执行一次&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;随着不断的buffer被刷入os cache中，translog不断的增大，当达到一定长度后，会触发一次commit操作。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;commit操作第一步就是将现有buffer刷入osCache,清空buffer.&lt;/p&gt; &lt;p&gt;然后将一个commit point 写入磁盘。里面标识这个commit point对应的所有segmint file，同时强行将os cache 进行fsync到磁盘中。最后清空translog日志文件，此时commit操作完成&lt;/p&gt; &lt;p&gt;commit操作叫做flush,默认30分钟执行一次。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;为什么存在translog?&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;无论buffer与os cache都是缓存，一旦宕机就会丢失数据，translog就是防止宕机重启丢失数据（间隔5秒刷新到os cache）&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;删除/更新数据底层原理&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;如果是删除操作，commit的时候会生成一个.del文件，里面是某个doc标识为delete状态，那么搜索的时候会根据.del文件就知道这个doc是否被删除。&lt;/p&gt; &lt;p&gt;如果更新操作，将原来的数据标识为delete状态，新写入一条数据&lt;/p&gt; &lt;p&gt;buffer每refresh一次到os cache就会产生一个segment file,每1s都会产生一个.此时会定期执行merge.每次merge的时候，会将多个segment file合并。同时会将标识为deleted的doc给物理删除掉。然后将新的segment file写入磁盘，再写一个新的commit point.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;底层：lucene是一个jar包，里面包含了各种建立倒排索引的算法代码&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;倒排索引&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;  在搜索引擎中，每个文档对应一个文档id，文档内容表示一系列关键词的集合&lt;/p&gt; &lt;p&gt;  正常索引，是文档id对应document文档关键字&lt;/p&gt; &lt;p&gt;  倒排索引，是dockument对应文档ids集合&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;es在数据量很大的情况下如何提高查询效率&lt;/strong&gt;   （为什么第一次慢，后面快）    &lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;es搜索引擎严重依赖底层的filesystem cache如果给更多的filesystem cache尽量让内存可以容纳所有的ide segment file索引数据文件。那么搜索的时候基本都是走内存，性能会非常高&lt;/p&gt; &lt;p&gt; 最佳情况，fileSystem cache 是数据量的一半，可以1秒以内查询&lt;/p&gt; &lt;p&gt;&lt;strong&gt;数据预热&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 就是写一个程序 每隔一段时间定期访问一下热点数据&lt;/p&gt; &lt;p&gt;&lt;strong&gt;冷热分离&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 将热数据和冷数据分开存到不同的索引中，热数据预热，不会被冷数据冲刷掉&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;分页性能优化&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;不允许深分页，越翻越慢。（查询原理和mysql类似）&lt;/p&gt; &lt;p&gt;&lt;strong&gt;使用无限翻页替代&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 使用scroll api （游标）。对所有数据生成快照，然后通过游标移动&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 09:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Redis</title>
      <link>http://www.yooyaa.cn/article/18</link>
      <content:encoded>&lt;h1&gt;一、Redis数据结构&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;string（字符串）：整型 + 字符串     key: value     整数或者字符串，存入数据时会尝试强转 int，如果成功则为 int     还可以构建分布式锁  setNx     setNx：『SET if Not exists』(如果不存在，则 SET)的简写         因为加锁和过期时间设置非原子，存在设置超时时间失败情况，导致死锁 hash（哈希）：哈希表 + 压缩表     key: {sKey1:value1,sKey2:value2...}     应用场景：         电商购物车： list（列表）：压缩表 + 双向链表     实现栈和队列结构 set（集合）：哈希表 + 整数数组     收藏、点赞等基础功能 微博微信关注模型 zSet(sorted set：有序集合)：压缩链表 + 跳表 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;二、Redis跳表&lt;/h1&gt; &lt;p&gt;​   可以理解为一个可以进行二分查找的有序双向(redis改进)链表，因为是折半，所以中间空出一个元素&lt;/p&gt; &lt;p&gt;在原有的有序链表上面增加了多级索引链表(指向下一节点和下一级的原数据位置)，通过索引来实现快速查找。&lt;/p&gt; &lt;p&gt;跳表不仅能提高搜索性能，同时也可以提高插入和删除操作的性能。有效降低了查询查询次数&lt;/p&gt; &lt;p&gt;​   redis是内存存储，不存在io的瓶颈，所以跳表的层数的耗时可以忽略不计，而且插入数据时不需要开销以平衡数据结构（写多）。而mysql数据库是持久化数据库，即是存储到磁盘上的，因此查询时要求更少磁盘IO，且mysql是读多写少的场景较多，显然B+树更加适合mysql。&lt;/p&gt; &lt;h1&gt;三、Redis压缩链表&lt;/h1&gt; &lt;p&gt;​  Ziplist 是一种内存紧凑型，经过特殊编码的双向链表，用来取代常规的双向链表&lt;/p&gt; &lt;p&gt;设计成了内存紧凑型，可以充分利用 CPU 高速缓存，并且可以给不同的数据类型进行编码，节省内存。&lt;/p&gt; &lt;p&gt;但是压缩链表也有缺点：&lt;/p&gt; &lt;p&gt;​ 1. 保存的元素过多，查询会变慢。&lt;/p&gt; &lt;p&gt;​ 2.插入或者删除一个元素时，需要重新分配内存。而且有可能会引发连锁更新。&lt;/p&gt; &lt;h1&gt;四、Redis哈希冲突过多解决&lt;/h1&gt; &lt;p&gt;redis 是维护一个全局哈希表进行键值的管理(其实就是一个数组)，数组的每个元素称为一个哈希桶，哈希表是由多个哈希桶组成的，每个哈希桶中保存了键值对数据。&lt;/p&gt; &lt;p&gt;redis解决哈希冲突的方式，就是使用拉链法的链式哈希。链式哈希也很容易理解，就是指发生哈希冲突时，同一个哈希桶中的多个元素用一个链表来保存，它们之间依次用指针连接。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;产生原因：hash冲突时，会在冲突的hash上拉出来一个链表，哈希冲突链上的元素只能通过指针逐一查找再操作，如果哈希表里写入的数据越来越多，     哈希冲突可能也会越来越多，这就会导致某些哈希冲突链过长，进而导致这个链上的元素查找耗时长，效率降低。 解决方式：     1.0 redis解决哈希冲突过多的方式——rehash         rehash 也就是增加现有的哈希桶数量，让逐渐增多的 entry 元素能在更多的桶之间分散保存，减少单个桶中的元素数量，从而减少单个桶中的冲突。         为了使 rehash 操作更高效，Redis 默认使用了两个全局哈希表：哈希表 1 和哈希表 2。一开始，当你刚插入数据时，默认使用哈希表 1，此时的哈希表 2 并没有被分配空间。         随着数据逐步增多，Redis 开始执行 rehash，这个过程分为三步：             1.0 给哈希表 2 分配更大的空间，例如是当前哈希表 1 大小的两倍；             2.0 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中；             3.0 释放哈希表 1 的空间。         到此，我们就可以从哈希表 1 切换到哈希表 2，用增大的哈希表 2 保存更多数据，而原来的哈希表 1 留作下一次 rehash 扩容备用。         这个过程看似简单，但是第二步涉及大量的数据拷贝，如果一次性把哈希表 1 中的数据都迁移完，会造成 Redis 线程阻塞，无法服务其他请求。此时，Redis 就无法快速访问数据了。          渐进式rehash：             为了避免以上问题，Redis 采用了渐进式 rehash。             简单来说就是在第二步拷贝数据时，Redis 仍然正常处理客户端请求，每处理一个请求时，从哈希表 1 中的第一个索引位置开始，             顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中；等处理下一个请求时，             再顺带拷贝哈希表 1 中的下一个索引位置的 entries。             这样就巧妙地把一次性大量拷贝的开销，分摊到了多次处理请求的过程中，避免了耗时操作，保证了数据的快速访问。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;五、Redis&lt;strong&gt;持久化&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.rdb 快照模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;保存策略：n秒内数据集至少有m个改动，则自动保存一次&lt;/p&gt; &lt;p&gt;优点：恢复数据快&lt;/p&gt; &lt;p&gt;缺点：当redis宕机可能会丢失临界数据&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.aof 日志模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;保存策略：&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;每次新加命令都会保存一次，非常慢，但非常安全&lt;/li&gt; &lt;li&gt;每秒保存一次，足够快，仅丢失1秒数据&lt;/li&gt; &lt;li&gt;将数据交给操作系统自行管理，更快也更不安全&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;**优点：**数据损失小&lt;/p&gt; &lt;p&gt;**缺点：**恢复数据很慢&lt;/p&gt; &lt;p&gt;&lt;strong&gt;aof重写&lt;/strong&gt;：因为aof文件可能有 太多没用的指令，所以aof会定期根据内存的最新数据生成aof文件&lt;/p&gt; &lt;p&gt;​                注意，AOF重写redis会fork出一个子进程去做，不会对redis正常命令处理有太多影响&lt;/p&gt; &lt;p&gt;&lt;strong&gt;redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据，因为aof一般来说数据更全一 点。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3.Redis4.0的混合持久化&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;重启 Redis 时，我们很少使用 RDB来恢复内存状态，因为会丢失大量数据。我们通常使用 AOF 日志重 放，但是重放 AOF 日志性能相对 RDB来说要慢很多，这样在 Redis 实例很大的情况下，启动需要花费很 长的时间。 Redis 4.0 为了解决这个问题，带来了一个新的持久化选项——混合持久化。&lt;/p&gt; &lt;p&gt;如果开启了混合持久化，AOF在重写时，不再是单纯将内存数据转换为RESP命令写入AOF文件，而是将 重写这一刻之前的内存做RDB快照处理，并且将RDB快照内容和增量的AOF修改内存数据的命令存在一 起，都写入新的AOF文件，新的文件一开始不叫appendonly.aof，等到重写完新的AOF文件才会进行改 名，原子的覆盖原有的AOF文件，完成新旧两个AOF文件的替换。 于是在 Redis 重启的时候，可以先加载 RDB 的内容，然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放，因此重启效率大幅得到提升。&lt;/p&gt; &lt;h4&gt;1.为什么创建一个子进程而不是在主进程中创建一个线程呢？&lt;/h4&gt; &lt;p&gt;​  （fork主进程，开启一个子进程执行RDB，子进程共享主进程的内存数据 + 页表，避免主线程收到影响）&lt;/p&gt; &lt;h1&gt;六、Reidis管道与lua脚本&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;管道（Pipeline）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;​        客户端可以一次性发送多个请求而不用等待服务器的响应，待所有命令都发送完后再一次性读取服务的响 应，这样可以极大的降低多条命令执行的网络传输开销，管道执行多条命令的网络开销实际上只相当于一 次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送，**redis必须在处理完所有命令前先缓 存起所有命令的处理结果。**打包的命令越多，缓存消耗内存也越多。所以并不是打包的命令越多越好。 pipeline中发送的每个command都会被server立即执行，如果执行失败，将会在此后的响应中得到信 息；也就是pipeline并不是表达“所有command都一起成功”的语义，管道中前面命令失败，后面命令 不会有影响，继续执行。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Redis Lua脚本&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Redis在2.6推出了脚本功能，允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:&lt;/p&gt; &lt;p&gt;​      1、减少网络开销：本来5次网络请求的操作，可以用一个请求完成，原先5次请求的逻辑放在redis服务器 上完成。使用脚本，减少了网络往返时延。这点跟管道类似。&lt;/p&gt; &lt;p&gt;​      2、原子操作：Redis会将整个脚本作为一个整体执行，中间不会被其他命令插入。管道不是原子的，不过 redis的批量操作命令(类似mset)是原子的。&lt;/p&gt; &lt;p&gt;​      3、替代redis的事务功能：redis自带的事务功能很鸡肋，报错不支持回滚，而redis的lua脚本几乎实现了 常规的事务功能，支持报错回滚操作，官方推荐如果要使用redis的事务功能可以用redis lua替代。&lt;/p&gt; &lt;p&gt;​      &lt;strong&gt;注意，不要在Lua脚本中出现死循环和耗时的运算，否则redis会阻塞，将不接受其他的命令， 所以使用 时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;七、&lt;em&gt;Redis对于过期键有三种清除策略&lt;/em&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;被动删除:读、写一个过期的key时触发 主动删除：定期清理已过期key 当前内存超过maxmemory先定个时，触发主动清除测率 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;八、redis 数据过期策略&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;惰性删除：过期后只有在查询的时候才会进行过期时间的校验，如果过期，则删除，占内存 定期删除：配置固定时间段进行定时一定量的清除（SLOW + FAST）        SLOW模式：定时任务，执行频率默认 10 hz， 每次执行时间不超过 25 ms，通过配置文件指定执行频率        FAST模式：执行频率不固定，但是执行间隔一般低于 2 ms，执行耗时不超过 1 ms        可以通过限制执行频率和执行时长减缓对CPU的影响 一般 惰性删除 + 定期删除 结合使用 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;九、Redis 数据淘汰策略（8种策略）&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;全体key 与 设置了定时的key     base random lru lfu     noevication（base）: 不淘汰任何key，内存满了则不允许数据的写入新数据了，这是默认执行的策略     volatile-ttl（base）: 对设置了 TTL 的 key，比较其 key 的剩余 TTL 值，TTL 越小优先被淘汰     allkeys-random（random）：对全体 key 进行随机淘汰     volatile-random（random）：对设置了 TTL 的 key，进行随机淘汰     allkeys-lru（lru）：对全体 key，进行基于 LRU 算法（least Recently Used 最近最少使用，当前时间减去最后一次访问时间，值越大淘汰优先级越高）淘汰     volatile-lru（lru）：对设置了 TTL 的 key，进行基于 LRU 算法（least Recently Used 最近最少使用，当前时间减去最后一次访问时间，值越大淘汰优先级越高）淘汰     allkeys-lfu（lfu）：对全体 key，进行基于 LFU 算法（least Frequently Used 最少频率使用，统计每个key的访问频率，频率越低淘汰优先级越高）淘汰     volatile-lfu（lfu）：对设置了 TTL 的 key，进行基于 LFU 算法（least Frequently Used 最少频率使用，统计每个key的访问频率，频率越低淘汰优先级越高）淘汰     使用建议：         一般使用 allkeys-lru 的数据淘汰策略；         如果有明显的冷热数据的区分，建议使用 allkeys-lru；         如果没有明显的冷热数据的区分，建议使用 allkeys-random，进行随机选择淘汰；         如果有置顶的需求，也可以通过 使用 volatile-lru ，同时不进行置顶 key 的过期时间 TTL 设置的方式来实现         如果有短时的高频访问区分，可以通过 allkeys-lfu 或者 volatile-lfu 来进行实现 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十、Redis的主从复制&lt;/h1&gt; &lt;p&gt;如果你为master配置了一个slave，不管这个slave是否是第一次连接上Master，它都会发送一个SYNC命 令(redis2.8版本之前的命令)给master请求复制数据。&lt;/p&gt; &lt;p&gt;​   master收到SYNC命令后，会在后台进行数据持久化通过bgsave生成最新的rdb快照文件，持久化期间， master会继续接收客户端的请求，它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以 后，master会把这份rdb文件数据集发送给slave，slave会把接收到的数据进行持久化生成rdb，然后再加 载到内存中。然后，master再将之前缓存在内存中的命令发送给slave。&lt;/p&gt; &lt;p&gt;​   当master与slave之间的连接由于某些原因而断开时，slave能够自动重连Master，如果master收到了多 个slave并发连接请求，它只会进行一次持久化，而不是一个连接一次，然后再把这一份持久化的数据发送 给多个并发连接的slave。 当master和slave断开重连后，一般都会对整份数据进行复制。但从redis2.8版本开始，master和slave断 开重连后支持部分复制。&lt;/p&gt; &lt;p&gt;​   &lt;strong&gt;部分复制就是使用psync增加了offset,如果此offset在master的repl_back_buffer中则将部分复制，否则全量&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;单节点redis的并发能力是有上限的，要进一步提高redis的并发能力就需要搭建主从集群，实现读写分离，一般来说是一主多从，主节点负责写，从节点负责读     全量同步：         1.0 从节点请求主节点同步数据(replication id, offset)         2.0 主节点负责判断是否是第一次请求，是第一次就会与从节点同步版本信息(replication id, offset)         3.0 主节点执行 bgsave ,生成一个RDB快照文件后，发送给从节点去执行         4.0 在RDB生成期间，主节点会以命令的方式记录到缓冲区(一个日志文件)         5.0 把上述生成之后的命令日志文件发送给从节点进行同步     增量同步：         1.0 从节点请求主节点同步数据，主节点负责判断是否是第一次请求，判断不是第一次请求就获取从节点的 offset 值         2.0 主节点从命令日志中获取 offset 值之后的数据，发送给从节点进行数据同步   replication id：简称 replid，是数据集的标记，id 一致说明是同一个数据集。每一个 master 都有一个唯一的 replid ，slave 则会继承 master 节点的 replid offset：偏移量，随着记录在 repl_baklog 中的数据增多而主键增大。从节点完成同步时也会记录当前同步的 offset 。     如果从节点的 offset 小于主节点的 offset ，说明 slave 数据落后于主节点，需要进行更新 从节点执行 replicaof 命令 或者 从节点重启后，向主节点发送一个数据同步的请求（携带数据集标记 id ，数据偏移量），主节点先判断是否是首次同步，replid 是否一致 全量同步：基于 RDB 模式的全量同步     若为首次同步，则发送主节点的数据版本信息给从节点，从节点把主节点的数据版本信息保存至本地，     主节点开始执行 bgsave 命令，生成 RDB 文件，然后将其 RDB 文件发送给从节点，     从节点获取到主节点发送过来的 RDB 文件后，会清空本地的数据，然后加载主节点发送过来的 RDB 文件，     若在从节点同步过程中，主节点又有增量修改的话，主节点会将这一块增量修改打包成一个 repl_baklog 文件，发送给从节点，从节点同步完 repl_baklog 后。 增量同步：     若不为首次同步，则为增量同步，主节点会将这一块增量修改打包成一个 repl_baklog 文件（由 replid 和 offset 决定），发送给从节点，从节点同步完 repl_baklog 后。 全量同步：         1.0 从节点请求主节点同步数据(replication id, offset)         2.0 主节点负责判断是否是第一次请求，是第一次就会与从节点同步版本信息(replication id, offset)         3.0 主节点执行 bgsave ,生成一个RDB快照文件后，发送给从节点去执行         4.0 在RDB生成期间，主节点会以命令的方式记录到缓冲区(一个日志文件)         5.0 把上述生成之后的命令日志文件发送给从节点进行同步 增量同步：     1.0 从节点请求主节点同步数据，主节点负责判断是否是第一次请求，判断不是第一次请求就获取从节点的 offset 值     2.0 主节点从命令日志中获取 offset 值之后的数据，发送给从节点进行数据同步 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十一、&lt;strong&gt;Redis哨兵高可用架构&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;sentinel哨兵是特殊的redis服务，不提供读写服务，主要用来监控redis实例节点。是一个单独服务&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;哨兵架构下client端第一次从哨兵找出redis的主节点，后续就直接访问redis的主节点，不会每次都通过 sentinel代理访问redis的主节点，当redis的主节点发生变化，哨兵会第一时间感知到，并且将新的redis 主节点通知给client端(&lt;strong&gt;这里面redis的client端一般都实现了订阅功能，订阅sentinel发布的节点变动消息&lt;/strong&gt;)&lt;/p&gt; &lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态，如果master节点异常，则会 做主从切换，将某一台slave作为master，哨兵的配置略微复杂，并且性能和高可用性等各方面表现一般，特别是在主 从切换的瞬间存在访问瞬断的情况，而且哨兵模式只有一个主节点对外提供服务，没法支持很高的并发，且单个主节点 内存也不宜设置得过大，否则会导致持久化文件过大，影响数据恢复或主从同步的效率&lt;/p&gt; &lt;p&gt;&lt;strong&gt;哨兵leader选举流程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;​        当一个master服务器被某sentinel视为客观下线状态后，该sentinel会与其他sentinel协商选出sentinel的leader进行故 障转移工作。每个发现master服务器进入客观下线的sentinel都可以要求其他sentinel选自己为sentinel的leader，选举 是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期)，每个纪元中只会选择一个sentinel的leader。如 果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作，从存活的slave中选举出新 的master，这个选举过程跟集群的master选举很类似。 哨兵集群只有一个哨兵节点，redis的主从也能正常运行以及选举master，如果master挂了，那唯一的那个哨兵节点就 是哨兵leader了，可以正常选举新master。 不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;主从模式保证不了集群的高可用。 哨兵基于心跳机制来监测服务状态，每隔1秒发送一个 ping 命令     主观下线：某个哨兵发现某个 redis 服务未在固定时间内响应，则认为是主观下线     客观下线：多个哨兵（一个数据阈值quorum，可以设置也有默认值，最好超过一半）         发现某个 redis 服务未在固定时间内响应，则认为是客观下线 哨兵机制会自动实现主从集群的自动故障恢复 监控：全程检查主从节点的是否正常工作 自动故障恢复：当主节点故障之后，会自动选举一个从节点为主节点，即使原主节点故障恢复之后会降级为从节点 通知：哨兵充当 redis 客户端的服务发现，当集群故障转移时，会通知 redis 客户端最新的集群信息 redis 主节点选举规则：     1 首先判断主从节点断开时间长短，如超过指定值就排除该从节点     2 判断一个自定义的优先值配置 slave-prority 值，值越小优先级越高     3 如果 slave-prority 一样，则判断从节点的 offset 值，值越大则优先级越高，代表着丢失的数据越小     4 如果 offset 一样，则判断从节点的 运行id 大小，越小优先级越高 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;&lt;strong&gt;十二、Redis集群&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;一般公司标配，1主1从 + 哨兵，单节点的内存尽量不超过10G&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;redis集群是一个由多个主从节点群组成的分布式服务器群，它具有复制、高可用和分片特性。Redis集群不需要 sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式，这种集群模式没有中心节点，可 水平扩展，据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于 之前版本的哨兵模式，且集群配置非常简单。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;redis集群需要至少要三个master节点，&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Redis Cluster 将所有数据划分为 16384 个 slots(槽位)，每个节点负责其中一部分槽位。槽位的信息存储于每个节点 中。&lt;/p&gt; &lt;p&gt;当 Redis Cluster 的客户端来连接集群时，它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户 端要查找某个 key 时，可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况，还需 要纠正机制来实现槽位信息的校验调整。&lt;/p&gt; &lt;p&gt;槽位定位算法&lt;/p&gt; &lt;p&gt;Cluster 默认会对 key 值使用&lt;strong&gt;CRC16&lt;/strong&gt; 算法进行 hash 得到一个整数值，然后用这个整数值对 16384 进行取模来得到具体 槽位。 HASH_SLOT = CRC16(key) mod 16384&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Redis集群节点间的通信机制&lt;/strong&gt;：redis cluster节点间采取gossip协议进行通信 维护集群的元数据有两种方式：集中式和gossip&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;1)主从模式（读写分离 一主多从）&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;解决高并发问题&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;单节点redis的并发能力是有上限的，要进一步提高redis的并发能力就需要搭建主从集群，实现读写分离，一般来说是一主多从，主节点负责写，从节点负责读&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;replication id：简称 replid，是数据集的标记，id 一致说明是同一个数据集。每一个 master 都有一个唯一的 replid ，slave 则会继承 master 节点的 replid offset：偏移量，随着记录在 repl_baklog 中的数据增多而主键增大。从节点完成同步时也会记录当前同步的 offset 。     如果从节点的 offset 小于主节点的 offset ，说明 slave 数据落后于主节点，需要进行更新 从节点执行 replicaof 命令 或者 从节点重启后，向主节点发送一个数据同步的请求（携带数据集标记 id ，数据偏移量），主节点先判断是否是首次同步，replid 是否一致 全量同步：基于 RDB 模式的全量同步     若为首次同步，则发送主节点的数据版本信息给从节点，从节点把主节点的数据版本信息保存至本地，     主节点开始执行 bgsave 命令，生成 RDB 文件，然后将其 RDB 文件发送给从节点，     从节点获取到主节点发送过来的 RDB 文件后，会清空本地的数据，然后加载主节点发送过来的 RDB 文件，     若在从节点同步过程中，主节点又有增量修改的话，主节点会将这一块增量修改打包成一个 repl_baklog 文件，发送给从节点，从节点同步完 repl_baklog 后。 增量同步：     若不为首次同步，则为增量同步，主节点会将这一块增量修改打包成一个 repl_baklog 文件（由 replid 和 offset 决定），发送给从节点，从节点同步完 repl_baklog 后。 全量同步：         1.0 从节点请求主节点同步数据(replication id, offset)         2.0 主节点负责判断是否是第一次请求，是第一次就会与从节点同步版本信息(replication id, offset)         3.0 主节点执行 bgsave ,生成一个RDB快照文件后，发送给从节点去执行         4.0 在RDB生成期间，主节点会以命令的方式记录到缓冲区(一个日志文件)         5.0 把上述生成之后的命令日志文件发送给从节点进行同步 增量同步：     1.0 从节点请求主节点同步数据，主节点负责判断是否是第一次请求，判断不是第一次请求就获取从节点的 offset 值     2.0 主节点从命令日志中获取 offset 值之后的数据，发送给从节点进行数据同步 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;2)哨兵模式（sentinel）&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;解决高并发、高可用问题&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;主从模式保证不了集群的高可用。 哨兵基于心跳机制来监测服务状态，每隔1秒发送一个 ping 命令     主观下线：某个哨兵发现某个 redis 服务未在固定时间内响应，则认为是主观下线     客观下线：多个哨兵（一个数据阈值quorum，可以设置也有默认值，最好超过一半）         发现某个 redis 服务未在固定时间内响应，则认为是客观下线 哨兵机制会自动实现主从集群的自动故障恢复 监控：全程检查主从节点的是否正常工作 自动故障恢复：当主节点故障之后，会自动选举一个从节点为主节点，即使原主节点故障恢复之后会降级为从节点 通知：哨兵充当 redis 客户端的服务发现，当集群故障转移时，会通知 redis 客户端最新的集群信息 redis 主节点选举规则：     1 首先判断主从节点断开时间长短，如超过指定值就排除该从节点     2 判断一个自定义的优先值配置 slave-prority 值，值越小优先级越高     3 如果 slave-prority 一样，则判断从节点的 offset 值，值越大则优先级越高，代表着丢失的数据越小     4 如果 offset 一样，则判断从节点的 运行id 大小，越小优先级越高 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;3)&lt;strong&gt;分片模式&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;高并发写、海量数据存储、高并发、高可用。各个主节点含有哨兵的功能&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;集群中有多个主节点（主节点可下行多个从节点），每个主节点存不同的内容，主节点之间通过心跳 ping 进行监控服务的监控状态 客户端可以访问集群中的任意节点，主节点会自动转发到正确的节点上去 redis 引入了 hash 槽（16384 - 2kb左右）会把每个 key 进行哈希计算，然后进行 16384 取余，得到得到余值匹配到节点上的槽值，然后寻找插槽所在的实例 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;集中式：&lt;/strong&gt; 优点在于元数据的更新和读取，时效性非常好，一旦元数据出现变更立即就会更新到集中式的存储中，其他节点读取的 时候立即就可以立即感知到；不足在于所有的元数据的更新压力全部集中在一个地方，可能导致元数据的存储压力。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;网络抖动&lt;/strong&gt; ：真实世界的机房网络往往并不是风平浪静的，它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现 象，突然之间部分连接变得不可访问，然后很快又恢复正常。 为解决这种问题，Redis Cluster 提供了一种选项cluster­node­timeout，表示当某个节点持续 timeout 的时间失 联时，才可以认定该节点出现故障，需要进行主从切换。如果没有这个选项，网络抖动会导致主从频繁切换 (数据的重 新复制)。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Redis集群选举原理分析&lt;/strong&gt;：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;当slave发现自己的master变为FAIL状态时，便尝试进行Failover，以期成为新的master。由于挂掉的master可能会有 多个slave，从而存在多个slave竞争成为master节点的过程， 其过程如下：   1.slave发现自己的master变为FAIL   2.将自己记录的集群currentEpoch加1，并广播FAILOVER_AUTH_REQUEST 信息      3.其他节点收到该信息，只有master响应，判断请求者的合法性，并发送FAILOVER_AUTH_ACK，对每一个epoch只发 送一次ack      4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK      5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点，如果只有两个，当其 中一个挂了，只剩一个主节点是不能选举成功的)      6.广播Pong消息通知其他集群节点。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举，而是有一定延迟，一定的延迟确保我们等待FAIL状态在 集群中传播，slave如果立即尝试选举，其它masters或许尚未意识到FAIL状态，可能会拒绝投票 •延迟计算公式：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下，持 有最新数据的slave将会首先发起选举（理论上）。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Redis集群为什么至少需要三个master节点，并且推荐节点数为奇数？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;reidis的高并发分布式锁&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;锁续命，子线程定时器，&lt;/p&gt; &lt;p&gt;Redisson实现分布式锁&lt;/p&gt; &lt;h3&gt;4)集群脑裂问题&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;当主节点被哨兵进行客观下线之后，此时主节点与新的主节点转化有个时间差（此时哨兵还未发送新集群信息的通知），   -由于此时客户端连接的还是旧主节点，此时进行数据的写入修改时是不会同步到集群上去的，   -并且此时旧主节点恢复了心跳通讯之后会被强制降级为从节点（客观下线之后会进行重新选举主节点），   -旧主节点会清空数据，重新同步新主节点的数据，此时就会造成客户端连接未切换时修改的数据得不到更新造成丢失   -大概率规避方式：      redis的配置参数：        min-replicas-to-write X # 与主节点通信的从节点数量 X（可以为1个从节点） 必须大于等于该值主节点，否则主节点拒绝写入。        min-replicas-max-lag Y # 主节点与从节点通信的 ACK 消息延迟必须小于 Y（可以为5秒） ，主从同步延迟时间   这两个配置项必须同时满足，不然主节点拒绝写入。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;5)Redis集群数据倾斜&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;以数据行为的角度划分：         数据增删改倾斜，数据访问倾斜         数据增删改倾斜：             根本原因是：数据在各个redis实例上分布不均匀。                 1.0 存在bigkey                     对于bigkey的操作通常会造成实例IO线程阻塞，使该实例对数据请求处理效率严重下降。                     常用的做法是对bigkey进行&amp;quot;化整为零&amp;quot;,在业务层面生成数据的时候，尽量避免把过多的数据保存到同一个键中                 2.0 HashTag使用不当                     HashTag：使用方式是 在 key中添加一对花括号 {},这个 {} 可以将key的一部分内容包裹起来，而redis server会对于加上{}的key进行识别，并进行特殊处理。                 3.0 slot(槽点)分配不均匀                     如果slot在实例上分配不均匀的话，使某个或者某些实例上分配过多的slot，那么这些实例上被分配数据量也会更多一些，也就会产生类似HashTag一样的数据倾斜问题。                     对于由于slot分配不均导致的数据倾斜问题，可以在slot分配前，通过人工分配的方式，来避免多个slot分配到同一个实例的问题，如果一个集群中slot已经分配完毕，                     可以通过redis提供的运维工具和相应的运维命令，来查看redis集群中，slot的分配情况，如果存在某些实例上slot分配过多的情况，                     那么可以将这些过多的slot转移到其他实例上。         数据访问倾斜：             热点数据问题，通常采用的解决方案是多副本冗余。             我们把热点数据复制多份，在每一个数据副本的 key 中增加一个随机前缀，让它和其它副本数据不会被映射到同一个 Slot 中。             这样一来，热点数据既有多个副本可以同时服务请求，同时，这些副本数据的 key 又不一样，会被映射到不同的 Slot 中。             在给这些 Slot 分配实例时，我们也要注意把它们分配到不同的实例上，那么，热点数据的访问压力就被分散到不同的实例上了。             这样可以有效解决数据访问倾斜问题。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十三、Redisson&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;是一个在Redis的基础上实现的Java驻内存数据网格   主要是保证AP 核心是通过 lua 脚本，设置一个 哈希类型的 key1 - 线程idkey - value，以及设置一个过期时间，因为一个 lua 脚本就是一个原子操作 加锁过程：     加锁请求 ---&amp;gt; 查询Key是否存在 ---&amp;gt; 若不存在，创建锁并设置过期时间 ---&amp;gt; 返回空值 nil ---&amp;gt; 加锁流程结束                                   ---&amp;gt; 若存在 ---&amp;gt; 查看 value 是否匹配 ---&amp;gt; 若匹配 ---&amp;gt; 执行重入 +1 并重置锁过期时间 ---&amp;gt; 加锁流程结束                                                                        ---&amp;gt; 若不匹配 ---&amp;gt; 表示锁被其它线程占用 ---&amp;gt; 返回锁的过期时间 ---&amp;gt; 加锁流程结束  解锁过程：     解锁请求 ---&amp;gt; 查询Key是否存在 ---&amp;gt; 若不存在，取消刷新锁续期任务                                   ---&amp;gt; 若存在 ---&amp;gt; 修改重入次数减 1 ---&amp;gt; 判断重入次数为0 ---&amp;gt; 删除锁 ---&amp;gt; 解锁流程结束                                                                     ---&amp;gt; 判断重入次数不为0 ---&amp;gt; 重置过期时间  锁续期：     在获取锁的时候不能传 leaseTime 过期时间，否则 不会创建看门狗     Redisson默认加锁30秒，每隔10秒刷新加锁时间     watchdog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间，但是不要设置太小     Redisson是通过Future和Timeout功能来实现异步延时  分布式锁主节点加锁后锁丢失问题：     如果一定得要确保锁加成功，则需要强一致性，这样还不如用zk的分布式锁，同步的间隔得要足够断或者甚至所有节点同步成功之后，才返回加锁成功才行 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十四、Redis分布式锁的一种自定义实现&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;主要是利用 setNx（set if no exists 如果不存在 则set）命令来实现，可重入锁     获取锁         set key value NX EX 10     释放锁         del key         或者到达过期时间 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十五、缓存穿透&amp;amp;缓存击穿&amp;amp;缓存雪崩&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;缓存穿透：请求不存在的 redis key，然后直接查 DB，导致每次此场景的请求都会查询 DB 缓存击穿：短时间大量请求一个已过期的 redis key 缓存雪崩：短时间内大量redis key过期，或者redis宕机，导致大量请求查询DB    缓存穿透:高并发时缓存无数据，数据库无数据，所有请求全部落到db上面，导致db压力过大     1.做接口校验，将没有的不规范的请求过滤掉     2.缓存值为null的请求，时间设置短一点 缓存击穿：缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多，同时读缓存没读到数据，又同时去数据库去取数据，引起数据库压力瞬间增大，造成过大压力     1.设置热点数据永不过期     2.加互斥锁，或redis的incr     3.设置热点数据过期时间，加一个随机数 缓存雪崩：  缓存雪崩是指缓存中数据大批量到过期时间，redis宕机，而查询数据量巨大，引起数据库压力过大甚至down机。和缓存击穿不同的是，       缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。     1.限流降级，     2.高可用集群 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十六、Redis执行快的原因&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1 redis 是基于内存操作的，执行速度非常快     2 采用了单线程，避免了不必要的线程上下文切换可竞争条件，多线程还需要考虑线程安全问题     3 使用了 I/O 多路复用模型         3.1 一般 IO ：用户缓冲区 ---&amp;gt; 内核缓冲区 ---&amp;gt; 硬件设备         linux 中有三种实现方式：             select: 遍历去处理消息，只会通知用户进程有就绪状态，不会告知是哪一个             poll: 遍历去处理消息，只会通知用户进程有就绪状态，不会告知是哪一个             epoll（linux独有）: 通知用户进程有就绪状态，并且告知是哪一个消息          redis 网络模型：                                                   ·---&amp;gt; 连接应答处理器（tcpAcceptHandler）             事件 ---&amp;gt; IO多路复用 + 事件派发机制 -&amp;gt;·---&amp;gt; 命令请求处理器（readQueryFromClient）  ---------------|                                                   ·---&amp;gt; 命令回复处理器（sendReplyToClient）（多线程）      |         |-------------------------------------------------|                |         |                               V     缓冲区 &amp;lt;--- 选择并执行命令，把结果写入缓冲队列（串行执行） &amp;lt;--- 把数据转换为redis命令（多线程） &amp;lt;--- 接收请求数据（多线程） &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十七、Redis冷热备份&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Redis冷备用：&lt;/strong&gt; Redis冷备用是指在系统出现故障时，使用备份数据来恢复系统，这种备份数据称为“冷备份”。冷备份可以保证系统恢复后的数据一致性，但是需要一定的时间来恢复数据，因此不适合用于高可用性的系统。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Redis热备用：&lt;/strong&gt; Redis热备用是指在系统出现故障时，使用另一台服务器上的数据来恢复系统，这种备份数据称为“热备份”。热备份可以在不停止系统的情况下进行，因此可以很快恢复数据，而且可以更好地保证系统的高可用性。 启用Rsync，配置Rsync，使用crontab定时任务定时备份，以及设定数据存储策略。有了这个过程，可以有效保护Redis数据，以避免灾难发生或丢失重要数据。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;优势和不足：&lt;/strong&gt; Redis冷备用和热备用都有它们的优势和不足，冷备用可以更好地保证数据的一致性，但是恢复数据需要花费更多的时间；而热备用可以更快地恢复数据，但是数据的一致性可能无法得到保证。&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;十八、双写一致性(视业务情况而定，强一致性和弱一致性)&lt;/h1&gt; &lt;p&gt;1 强一致性：性能较差 .使用redisson读写锁进行同步更新，锁 + 事务的范围得要含DB的写入操作以及redis的更新操作，保证其原子性&lt;/p&gt; &lt;p&gt;2.最终一致性（允许延时）：性能较好.&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 异步更新： 1. 使用mq异步更新：DB库更新成功之后，写入mq，进行异步更新 2. 使用线程池的异步线程更新：DB库更新成功之后，从线程池中取出线程，进行异步更新 3. 使用mysql的binlog进行更新：DB库更新成功之后，使用Canal或者flink cdc进行异步更新   1 强一致性：性能较差         使用redisson读写锁进行同步更新，锁 + 事务的范围得要含DB的写入操作以及redis的更新操作，保证其原子性         1. 共享锁：读锁readLock，加锁之后，其它线程可以共享读操作，代码示例如下：             RReadWriteLock rrwLock = redissonClient.getReadWriteLock(&amp;quot;key&amp;quot;);             RLock readLock = rrwLock.readLock();         2. 排他锁：独占锁writeLock，加锁之后，阻塞其它线程的读写操作，代码示例如下：             RReadWriteLock rrwLock = redissonClient.getReadWriteLock(&amp;quot;key&amp;quot;);             RLock writeLock = rrwLock.writeLock();  2 最终一致性（允许延时）：性能较好         异步更新：             1. 使用mq异步更新：DB库更新成功之后，写入mq，进行异步更新             2. 使用线程池的异步线程更新：DB库更新成功之后，从线程池中取出线程，进行异步更新             3. 使用mysql的binlog进行更新：DB库更新成功之后，使用Canal或者flink cdc进行异步更新 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;其他问题&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.redis和mysql如何保证数据一致性  答：1.采用事务+锁的机，保证原子性 2.采用最终一致性：mq 2.redis为什么那么快？  答：1.网络请求采用多路复用设计         2.数据操作采用单线程，避免了上下文切换         3.基于内存 3.redis存在线程安全问题吗？为什么？  答：redis操作数据是单线程的，线程是安全的。虽然6.0增加了多线曾机制，但是他的多线程是用来处理网络io事件的 4.redis的内存淘汰算法和原理  1.采用随机random算法（随机移除），LRU算法(移除最近很少使用的key)，LFU算法（移除最近很少使用的key），TTL算法（移除更早过期时间key有限移除） 5.分布式锁的理解，以及分布式锁定实现  分布式锁是垮进程跨机器节点的互斥锁，可以保证多机器节点对于共享资源访问的排他性。他和线程锁的本质都是一样的。但是生命周期不同，线程锁是单进程多线程使用，分布式锁是多进程多机器节点使用  1.redis锁超时怎么办？ 看门狗续期  2.redis主从切换导致锁失效怎么办 ？redis提供了一个redlock的解决办法。   &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 09:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Mybatis</title>
      <link>http://www.yooyaa.cn/article/15</link>
      <content:encoded>&lt;p&gt;&lt;strong&gt;mybatis是一个半Orm(对象关系映射)框架，它内部封装了JDBC,加载驱动，创建链接，创建statement等繁杂的过程。&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;一、mybatis的执行流程&lt;/strong&gt;&lt;/h1&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;构造会话工厂SqlSessionFactory&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;操作数据库的接口，Executor执行器，同时负责查询缓存的维护&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Executor接口的执行方法中有一个MappedStatement类型的参数，封装了映射信息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;输入参数映射&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;输出结果映射&lt;/p&gt; &lt;p&gt;1、 创建SqlSessionFactory 2、 通过SqlSessionFactory创建SqlSession 3、 通过sqlsession执行数据库操作 4、 调用session.commit()提交事务 5、 调用session.close()关闭会话&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;SqlSessionFactory 是单例的 由 SqlSessionFactoryBuilder 构建；sqlsession 是会话&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;二、mybatis时怎么绑定xml&lt;/strong&gt;&lt;/h1&gt; &lt;h1&gt;&lt;strong&gt;三、mybatis的拦截器&lt;/strong&gt;&lt;/h1&gt; &lt;h1&gt;&lt;strong&gt;四、mybatis的优化&lt;/strong&gt;&lt;/h1&gt; &lt;h1&gt;&lt;strong&gt;五、mybatis的缓存&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;一级缓存是基于PerpetualCache的hashmap本地缓存，其存储作用域为Session，当Session flush后，默认打开一级缓存！ 二级缓存和一级缓存的机制是相同的，默认也是采用PerpetualCache的hashmap本地缓存，不过他的储存作用于在Mapper，而且可自定义存储源，要开启二级缓存，需要使用二级缓存属性类实现Serializable序列化的接口，可在它的映射文件中配置&amp;lt;cache/&amp;gt;  缓存数据的更新机制，当某一个作用域（一级缓存session/二级缓存namespace）的进行了c/u/d操作后，默认该作用域下所有select中的缓存将被clear    一级缓存：基于 PerpetualCache 的 hashmap 本地缓存，其存储作用域为 Session，当 Session 进行 flush 或者 close 之后，该 Session 中的所有的 Cache 就将清空，默认打开一级缓存 二级缓存：基于 namespaces 和 mapper 的作用域起作用的，不是依赖于 SQL Session，默认也是采用 PerpetualCache ，hashmap 存储，但是需要单独开启，一个是核心配置，一个是 mapper 文件  二级缓存什么时候会清空缓存中的数据：     当某一个作用域(一级缓存 Session /二级缓存 namespaces )的进行了新增、修改、删除操作后，默认该作用域下所有 select 中的缓存将被 clear &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;六、延迟加载&lt;/h1&gt; &lt;p&gt;延迟加载的意思是：就是在需要用到数据时才进行加载，不需要用到数据时就不进行加载数据。 Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载 在Mybatis配置文件中，可以配置是否启用延迟加载lazyLoadingEnabled=truelfalse，默认是关闭的&lt;/p&gt; &lt;pre&gt;&lt;code&gt;底层原理：      1.使用CGLIB创建目标对象的代理对象      2.当调用目标方法时，进入拦截器invoke方法，发现目标方法是null值，执行sql查询      3.获取数据以后，调用set方法设置属性值，再继续查询目标方法，就有值了 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;&lt;strong&gt;其他问题&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.${}是字符串替换，#{}是预处理；   Mybatis在处理${}时，就是把${}直接替换成变量的值。而Mybatis在处理#{}时，会对sql语句进行预处理，将sql中的#{}替换为?号，调用PreparedStatement的set方法来赋值； 2.Mybatis是如何进行分页的？分页插件的原理是什么？  3.说一下Mybaits的优缺点和使用场合  优点：基于SQL语句编译，相当灵活，与JDBC相比，减少了50%的代码，很好的与各种数据库兼容，能够与Spring很好的集成，提供映射标签，支持对象关系组件维护！  缺点：SQL语句的编写工作量较大，尤其字段多，关联表多时，对开发人员编写SQL语句的功底有一定要求！ SQL语句依赖于数据库，导致数据库移植性差，不能随意更换数据库！ 适用场合：MyBatis专注于SQL本身，是一个足够灵活的DAO层解决方案，对性能要求很高，或者需求变化较多的项目，如互联网项目！ &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 08:59:00 GMT</pubDate>
    </item>
    <item>
      <title>Spring cloud</title>
      <link>http://www.yooyaa.cn/article/14</link>
      <content:encoded>&lt;p&gt;什么是Spring cloud &lt;/p&gt; &lt;p&gt;   spring cloud流应用程序是基于spring boot的，用于快速构建执行有限数据处理的应用程序&lt;/p&gt; &lt;h1&gt;一、CAP &amp;amp; BASE理论详解&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;CAP&lt;/strong&gt; 也就是 &lt;strong&gt;Consistency（一致性）&lt;/strong&gt;、&lt;strong&gt;Availability（可用性）&lt;/strong&gt;、&lt;strong&gt;Partition Tolerance（分区容错性）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;常见的可以作为注册中心的组件有：ZooKeeper、Eureka、Nacos...。&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;ZooKeeper 保证的是 CP&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Eureka 保证的则是 AP。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Nacos 不仅支持 CP 也支持 AP，默认AP模式。&lt;/p&gt; &lt;p&gt;在进行分布式系统设计和开发时，我们不应该仅仅局限在 CAP 问题上，还要关注系统的扩展性、可用性等等在系统发生“分区”的情况下，CAP 理论只能满足 CP 或者 AP。要注意的是，这里的前提是系统发生了“分区”如果系统没有发生“分区”的话，节点间的网络连接通信正常的话，也就不存在 P 了。这个时候，我们就可以同时保证 C 和 A 了。总结：如果系统发生“分区”，我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话，我们要思考如何保证 CA 。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;二、Gateway&lt;/h1&gt; &lt;p&gt;响应式编程&lt;/p&gt; &lt;h1&gt;三、Hystrix&lt;/h1&gt; &lt;p&gt;  他是让我们在服务间调用时，加入了一些&lt;strong&gt;调用延迟&lt;/strong&gt;或者&lt;strong&gt;依赖故障&lt;/strong&gt;的容错机制&lt;/p&gt; &lt;p&gt;&lt;strong&gt;设计原则：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;对依赖服务调用出现的调用延迟和调用失败进行控制和容错保护&lt;/li&gt; &lt;li&gt;对复杂的分布式系统中，组织故障的进一步蔓延&lt;/li&gt; &lt;li&gt;提供fail-fast快速失败机制&lt;/li&gt; &lt;li&gt;提供fallback优雅降级机制&lt;/li&gt; &lt;li&gt;支持近实时的监控，报警，以及运维只是  &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;隔离策略：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 线程池隔离（默认）：就是调用下次服务使用的时hystrix自己的线程池，这样避免过多的线程请求打到下级服务（控制访问的线程数量），满了后就降级&lt;/p&gt; &lt;p&gt; 信号量隔离：是上级线程调用下级依赖服务，只是设置了一道关卡，当信号量满的时候就降级&lt;/p&gt; &lt;p&gt;&lt;strong&gt;request Cache：需要command开启请求缓存&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 如果多个command，参数一样，调用接口一样，而结果可以认为一样，这个时候我们可以让第一个command执行缓存，下一次从内存获取&lt;/p&gt; &lt;h1&gt;四、Ribbon&lt;/h1&gt; &lt;p&gt; 客户端轮询算法&lt;/p&gt; &lt;p&gt; 原理就是将拦截带有@loadBloan注解的restTempte请求，然后从主从中心获取服务的实例，根据轮询算法进行请求，达到负载均很&lt;/p&gt; &lt;h1&gt;五、OpenFeign&lt;/h1&gt; &lt;h1&gt;六、分布式事务&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.0 seata: 支持 AT(自动事务Auto Transaction) TCC SAGA XA  TM: Transaction Manager 事务管理器     全局事务的管理者，或者说是全局事务的发起方，再通俗一点就是标注了@GlobalTransactional的方法所在的服务 RM: Resources Manager 资源管理     负责分支(本地)事务注册、提交和回滚。每个服务都是一个RM，负责本地事务的管理 TC：Transaction Coordinate 事务协调器     全局事务的协调者，TM,RM启动的时候要向TC注册，TM创建的时候要向TC申请一个全局事务ID，所以整个事务的把控是在TC中的，但是各自事务的管理是在RM中   AT(自动事务Auto Transaction): 两阶段     一阶段：拦截并且解析sql，生成还原操作sql经过处理之后，保存在每个数据库的undo_log表中，对对应数据添加全局行锁，然后再执行业务sql     二阶段：第一阶段成功执行后会通知各个库删除一阶段生成的undo_log记录和全局行锁，但是如果失败了就会通过各个库的undo_log表中的记录来进行回滚，         此时会校验 当前数据 和 after image 是否一致，一致则表明可以还原，否则表示出现了脏写，需要转人工处理     全局行锁保证数据正常回滚，局部行锁的话会导致回滚失败     无代码侵入 TCC: Try, Confirm, Cancel需要业务层面支持第一阶段Try和第二阶段的Confirm, Cancel.     用户接入 TCC 模式，最重要的事情就是考虑如何将业务模型拆成 2 阶段，实现成 TCC 的 3 个方法，并且保证 Try 成功 Confirm 一定能成功。     相对于 AT 模式，TCC 模式对业务代码有一定的侵入性，但是 TCC 模式无 AT 模式的全局行锁，TCC 性能会比 AT 模式高很多。  SAGA:     手动实现数据回滚，适合与第三方接入时操作  XA:     整个事务提交分为 prepare 和 commit 两个阶段     第一阶段，事务协调者向事务参与者发送 prepare 请求，事务参与者收到请求后，如果可以提交事务，回复 yes，否则回复 no。开启XA，执行sql,     第二阶段，如果所有事务参与者都回复了 yes，事务协调者向所有事务参与者发送 commit 请求，否则发送 rollback 请求。  seata 与 oauth2 冲突：     seata进行bean扫描的时候会对全局bean进行读取并解析其方法上面的注解，     ClientDetailsService 默认创建的方法上是有 @Lazy 注解，只要不主动使用是不会进行创建的，但是seata打破了这个状态，导致提前创建了一个虚假bean     这个操作将导致 ClientDetailsService 会被默认创建一个不可操作的bean      解决方案：         1.0 再虚假 ClientDetailsService 的 bean 创建之后进行真实 bean 创建操作，并且设置可以覆盖 bean 的配置，并         2.0 不引入 seata 依赖，涉及到全局事务的场景使用类似于 SAGA 原理手动调用补偿接口 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;七、分布式锁&lt;/h1&gt; &lt;h1&gt;八、限流熔断降级&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;出现场景：     服务雪崩：         因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程         服务雪崩的出现原因：激增流量，不稳定服务依赖  解决方案：     稳定性、恢复性      常见的容错机制：         超时机制：             在不做任何处理的情况下，服务提供者不可用会导致消费请求线程强制等待，而造成系统资源耗尽。加入超时机制，一旦超时，就释放资源。             由于释放资源速度较快，一定程度上可以抑制资源耗尽的问题。          服务限流：          服务隔离：             用户的请求将不再直接访问服务，而是通过线程池中的空闲线程来进行访问服务，如果线程池已满，则会进行降级处理，用户的请求不会被阻塞，             至少可以看到一个执行结果(例如返回友好的提示信息)，而不是无休止的等待或者看到系统奔溃。          服务熔断：             远程服务不稳定或网络抖动时暂时关闭，就叫服务熔断             现实世界的断路器大家肯定都很了解，断路器实时监控电路的情况，如果发现电路电流异常，就会跳闸，从而防止电路被烧毁。             软件世界的断路器可以这样理解:实时监测应用，如果发现在一定时间内失败次数/失败率达到一定阈值，就&amp;quot;跳闸&amp;quot;，断路器打开，此时，请求直接返回，而不去调用原本调用的逻辑。             跳间一段时间后(10秒 ，断路器会进入半开状态，这是一个瞬间态，此时允许一次请求调用该调的逻辑，如果成功，则断路器关闭，应用正常调用；如果调用依然不成功，断路器继续回到打开状态，             过段时间再进入半开状态尝试一一通过&amp;quot;跳闸&amp;quot;，应用可以保护自己，而且避免浪费资源，而通过半开的设计，可实现应用的&amp;quot;自我修复&amp;quot;。             所以，同样的道理，当依赖的服务有大量超时时，在让新的请求去访问根本没有意义，只会无畏的消耗现有资源。比如我们设置了超时时间为1s，如果短时间内有大量请求在1s内都得不到响应，             就意味着这个服务出现了异常，此时就没有必要再让其他的请求去访问这个依赖了，这个时候就应该使用断路器避免资源浪费。          服务降级(弱依赖-不那么重要服务的依赖)：             有服务熔断，必然要有服务降级。             所谓降级，就是当某个服务熔断之后，服务将不再被调用，此时客户端可以自已准备个本地的 fallback 回退)回调，返回一个缺省值。例如:(备用接口/缓存/mock数据)。这样做，虽然服务水平             下降，但好歹可用，比直接挂掉要强，当然这也要看适合的业务场景。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;九、RPC与HTTP协议有什么区别？&lt;/h1&gt; &lt;p&gt;rpc是一种针对跨进程或者跨网络节点之间的远程过程调用协议，核心是让开发人员在调用远程方法与调用本地方法一样，不需要额外的编码交互（底层的数据传输可以使用TCP协议也可以使用http协议）&lt;/p&gt; &lt;p&gt;http:是为了web浏览器与web服务器之间通信而设计的远程通信协议。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;十、NACOS 的注册机制&lt;/strong&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 08:58:00 GMT</pubDate>
    </item>
    <item>
      <title>Spring</title>
      <link>http://www.yooyaa.cn/article/13</link>
      <content:encoded>&lt;hr /&gt; &lt;h1&gt;一、spring的启动流程&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;加载应用程序上下文 扫描应用程序中的所有组件 自动配置应用程序环境 启动嵌入式Web服务器 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;首先从main找到run()方法，在执行run()方法之前new一个SpringApplication对象 进入run()方法，创建应用监听器SpringApplicationRunListeners开始监听 然后加载SpringBoot配置环境(ConfigurableEnvironment)，然后把配置环境(Environment)加入监听对象中 然后加载应用上下文(ConfigurableApplicationContext)，当做run方法的返回对象 最后创建Spring容器，refreshContext(context)，实现starter自动化配置和bean的实例化等工作。 ————————————————&lt;/p&gt; &lt;h1&gt;二、spring的bean的生命周期&lt;/h1&gt; &lt;p&gt;创建 -&amp;gt; 初始化 -&amp;gt; 使用 -&amp;gt; 销毁&lt;/p&gt; &lt;p&gt;创建前准-》创建实例阶段-》依赖注入阶段-》容器缓存阶段-》销毁实例阶段&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1. 通过BeanDefinition获取bean的定义信息 2. 调用构造函数实例化bean 3. bean的依赖注入 4. 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware) 5. Bean的后置处理器BeanPostProcessor-前置 6. 初始化方法(InitializingBean、init-method) 7. Bean的后置处理器BeanPostProcessor-后置 8. 销毁bean &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;&lt;strong&gt;三、SpringBoot 自配装配的流程&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;通过&lt;code&gt;@EnableAutoConfiguration&lt;/code&gt;注解在类路径的META-INF/spring.factories文件中找到所有的对应配置类，然后将这些自动配置类加载到spring容器中。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1.0 SpringBoot 启动时，会扫描 META-INF/spring.factories 文件，获取所有自动配置类的全限定名 2.0 Spring Boot 根据项目的依赖关系和配置信息，选择并加载相应的自动配置类     启动时，会扫描 META-INF/spring.factories 文件，获取所有自动配置类的全限定名 3.0 Spring Boot 根据项目的依赖关系和配置信息，选择并加载相应的自动配置类 4.0 自动配置类使用 @ConditionalOnXXX 注解来进行条件装配，通过判断特定条件是否满足来确定是否进行自动装配  @Import 注解导入了一个 AutoConfigurationImportSelector 类，这个 AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口， 关于 @Import 注解，我们这里不展开，以后有机会单独讲，这边只需要知道，AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口， 并且其内部类 AutoConfigurationGroup 实现了 DeferredImportSelector.Group 接口， AutoConfigurationGroup 的 process 方法会在 SpringBoot 启动时被调用 里面包含了一个 loadSpringFactories 来加载资源文件，运用一些缓存机制，防止重复加载和读取文件，减少磁盘IO操作 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;四、&lt;strong&gt;SpringBoot2为什么默认使用CGLib不再使用JDK动态代理&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.0 不需要实现接口 2.0 性能：CGlib动态代理比JDK动态代理更快,JDK动态代理是通过反射来实现的,而CGLib动态代理是使用字节码生成技术,直接操作字节码,因此CGLib性能更高. 3.0 代理对象的创建：JDK动态代理只能代理实现了接口的类,它是通过Proxy类和InvocationHandler接口来创建代理对象.     而CGLib动态代理可以代理任意类(final修饰的类和fina修饰的方法除外),它是通过Enhancer类来创建代理对象,无需接口. 4.0 调用方法：JDK动态代理对代理方法的调用是通过InvocationHandler来转发的,     而CGLib动态代理是对代理方法通过FastClass机制来直接调用目标方法的,避免了一些额外的方法调用,从而提高了执行效率,这也是CGLib性能较高的原因之一. &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;五、&lt;strong&gt;如何优化SpringBoot启动速度？&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.0 延迟初始化bean springboot 2.2版本后 全局懒加载 2.0 创建扫描索引 3.0 关闭JMX 4.0 关闭分层编译 【使用的不多...】 5.0 AOP 切面尽量不用注解方式 6.0 排除项目多余的依赖 jar 7.0 swagger 扫描接口时，指定只扫描某个路径下的类 8.0 缩小 feign 客户端接口的扫描范围 9.0 关闭 endpoint 的一些监控功能    1.0 延迟初始化bean springboot 2.2版本后 全局懒加载         配置文件：spring.main.lazy-initialization=true         或者注入属性上打 @Lazy 注解         大概优化 1 ~ 2 秒 2.0 创建扫描索引         添加 spring-context-indexer 依赖包         spring 5.0 之后提供 spring-context-indexer 功能，可以通过在编译时创建一个静态候选列表来提高大型应用程序的启动性能         在项目的启动类上打上 @Indexed 注解之后，编译打包的时候就会在项目种自动生成 META-INF/spring-components 文件。         当 Spring 应用上下文执行 ComponentScan 扫描时， 会读取索引文件，提高扫描速度。         META-INF/spring-components 将会被 CandidateComponentsIndexLoader 读取并且加载，将其转换为 CandidateComponentsIndex 对象。         大概优化 1 秒左右     其它非重要： 3.0 关闭JMX         Spring Boot 2.2.X 版本以下默认会开启 JMX，可以使用 jconsole 查看，对于我们无需这些监控的话可以手动关闭它。         spring.jmx.enabled=false 4.0 关闭分层编译 【使用的不多...】         Java8 之后的版本，默认打开多层编译，使用命令java -XX:+PrintFlagsFinal -version | grep CompileThreshold查看。 5.0 AOP 切面尽量不用注解方式 6.0 排除项目多余的依赖 jar 7.0 swagger 扫描接口时，指定只扫描某个路径下的类 8.0 缩小 feign 客户端接口的扫描范围 9.0 关闭 endpoint 的一些监控功能 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;六、&lt;strong&gt;SpringBoot 解决跨域问题的操作&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;跨域问题指的是不同站点之间，使用 ajax 无法相互调用的问题。跨域问题本质上是浏览器的一种保护机制，初衷是为了保证用户数据的安全，防止恶意网站窃取数据。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;浏览器同源，就是域名、端口号、ip、采用的协议都相同，那么我们就是同源的 1.0 返回新的CorsFilter 2.0 重写 WebMvcConfigurer 3.0 使用注解 @CrossOrigin 4.0 手动设置响应头 (HttpServletResponse) 5.0 自定web filter 实现跨域 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;七、spring的扩展点&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;BeanPostProcessor：在Bean实例化后、初始化前后进行扩展操作。 BeanFactoryPostProcessor：在BeanFactory标准初始化后，所有Bean定义已经被加载但是还没有实例化的时候进行扩展操作。 ApplicationListener：监听Spring事件，进行相应的扩展操作。 InitializingBean：在Bean初始化后进行扩展操作。 FactoryBean：用于创建复杂的Bean实例。 BeanFactory：Bean工厂，用于管理Bean实例。 ConfigurableBeanFactory：可配置的Bean工厂，可以配置Bean的各种属性。 HandlerInterceptor：在请求处理前、后进行扩展操作。 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;SpringBoot扩展点(按执行先后顺序)：     1.0 org.springframework.context.ApplicationContextInitializer：         使用场景：             在最开始激活一些配置，或者利用这时候class还没被类加载器加载的时机，进行动态字节码注入等操作。             这是整个spring容器在刷新之前初始化ConfigurableApplicationContext的回调接口。             简单来说，就是在容器刷新之前调用此类的initialize方法。这个点允许被用户自己扩展。用户可以在整个spring容器还没被初始化之前做一些事情。         使用方式(因为这时候spring容器还没被初始化，所以想要自己的扩展的生效)：             1.0 在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())语句加入             2.0 配置文件配置context.initializer.classes=com.example.demo.TestApplicationContextInitializer             3.0 Spring SPI扩展，在spring.factories 中加入                 org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer     2.0 org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor：         使用场景：             可以在这里动态注册自己的beanDefinition，可以加载classpath之外的bean，这个接口在读取项目中的beanDefinition之后执行，提供一个补充的扩展点。     3.0 org.springframework.beans.factory.config.BeanFactoryPostProcessor：         使用场景：             使用场景：修改已经注册的beanDefinition的元信息，这个接口是beanFactory的扩展接口，调用时机在spring在读取beanDefinition信息之后，实例化bean之前。     4.0 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor：         使用场景：             比如对实现了某一类接口的bean在各个生命期间进行收集，或者对某个类型的bean进行统一的设值等等。这个扩展点非常有用 ，无论是写中间件和业务中，都能利用这个特性。          BeanPostProcess只在bean的初始化阶段进行扩展（注入spring上下文前后），而改接口把可扩展的范围增加了实例化阶段和属性注入阶段，具体说明看下列代码示例方法注释。     5.0 org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor：         使用场景：             1.0 predictBeanType：该触发点发生在postProcessBeforeInstantiation之前(在图上并没有标明，因为一般不太需要扩展这个点)，                 这个方法用于预测Bean的类型，返回第一个预测成功的Class类型，如果不能预测返回null；                 当你调用BeanFactory.getType(name)时当通过bean的名字无法得到bean类型信息时就调用该回调方法来决定类型信息。             2.0 determineCandidateConstructors：该触发点发生在postProcessBeforeInstantiation之后，用于确定该bean的构造函数之用，                 返回的是该bean的所有构造函数列表。用户可以扩展这个点，来自定义选择相应的构造器来实例化这个bean。             3.0 getEarlyBeanReference：该触发点发生在postProcessAfterInstantiation之后，当有循环依赖的场景，当bean实例化好之后，                 为了防止有循环依赖，会提前暴露回调方法，用于bean实例化的后置处理。这个方法就是在提前暴露的回调方法中触发。     6.0 org.springframework.beans.factory.BeanFactoryAware：         使用场景：             可以对每个bean作特殊化的定制，也可以把BeanFactory拿到进行缓存，日后使用 。             这个类只有一个触发点，发生在bean的实例化之后，但还未初始化之前，注入属性之前，也就是Setter之前。     7.0 org.springframework.context.support.ApplicationContextAwareProcessor(6个扩展点)：         该类本身并没有扩展点，但是该类内部却有6个扩展点可供实现 ，这些类触发的时机在bean实例化之后，初始化之前。         可以看到，该类用于执行各种驱动接口，在bean实例化之后，属性填充之后，通过执行以上红框标出的扩展接口，来获取对应容器的变量。         使用场景：             1.0 EnvironmentAware：用于获取EnvironmentAware的一个扩展类，这个变量非常有用， 可以获得系统内的所有参数。             2.0 EmbeddedValueResolverAware：用于获取StringValueResolver的一个扩展类， StringValueResolver用于获取基于String类型的properties的变量，                 一般我们都用@Value的方式去获取，如果实现了这个Aware接口，把StringValueResolver缓存起来，通过这个类去获取String类型的变量，效果是一样的。             3.0 ResourceLoaderAware：用于获取ResourceLoader的一个扩展类，ResourceLoader可以用于获取classpath内所有的资源对象，可以扩展此类来拿到ResourceLoader对象。             4.0 ApplicationEventPublisherAware：用于获取ApplicationEventPublisher的一个扩展类，ApplicationEventPublisher可以用来发布事件，                 结合ApplicationListener来共同使用，下文在介绍ApplicationListener时会详细提到。这个对象也可以通过spring注入的方式来获得。             5.0 MessageSourceAware：用于获取MessageSource的一个扩展类，MessageSource主要用来做国际化。             6.0 ApplicationContextAware：用来获取ApplicationContext的一个扩展类，ApplicationContext应该是很多人非常熟悉的一个类了，                 就是spring上下文管理器，可以手动的获取任何在spring上下文注册的bean，我们经常扩展这个接口来缓存spring上下文，包装成静态方法。                 同时ApplicationContext也实现了BeanFactory，MessageSource，ApplicationEventPublisher等接口，也可以用来做相关接口的事情。     8.0 org.springframework.beans.factory.BeanNameAware：         使用场景：             用户可以扩展这个点，在初始化bean之前拿到spring容器中注册的的beanName，来自行修改这个beanName的值。             可以看到，这个类也是Aware扩展的一种，触发点在bean的初始化之前，也就是postProcessBeforeInitialization之前，             这个类的触发点方法只有一个：setBeanName。     9.0 javax.annotation.PostConstruct：         使用场景：             bean执行初始化逻辑。其作用是在bean的初始化阶段，如果对一个方法标注了@PostConstruct，会先调用这个方法。             这里重点是要关注下这个标准的触发点，这个触发点是在postProcessBeforeInitialization之后，InitializingBean.afterPropertiesSet之前。     10.0 org.springframework.beans.factory.InitializingBean：         使用场景：             用户实现此接口，来进行系统启动的时候一些业务指标的初始化工作。顾名思义，也是用来初始化bean的。             InitializingBean接口为bean提供了初始化方法的方式，它只包括afterPropertiesSet方法，凡是继承该接口的类，在初始化bean的时候都会执行该方法。             这个扩展点的触发时机在postProcessAfterInitialization之前。     11.0 org.springframework.beans.factory.FactoryBean：         使用场景：             为要实例化的bean作一个代理，比如为该对象的所有的方法作一个拦截，在调用前后输出一行log，模仿ProxyFactoryBean的功能。         FactoryBean接口对于Spring框架来说占用重要的地位，隐藏了实例化一些复杂bean的细节，给上层应用带来了便利，用户可以通过实现该接口定制实例化Bean的逻辑。     12.0 org.springframework.beans.factory.SmartInitializingSingleton：         使用场景：             用户可以扩展此接口在对所有单例对象初始化完毕后，做一些后置的业务处理。这个接口中只有一个方法afterSingletonsInstantiated，             其作用是在spring容器管理的所有单例对象（非懒加载对象）初始化完成之后调用的回调接口。其触发时机为postProcessAfterInitialization之后。     13.0 org.springframework.boot.CommandLineRunner：         使用场景：             用户扩展此接口，进行启动项目之后一些业务的预处理。触发时机为整个项目启动完毕后，自动执行run(String... args)。             如果有多个CommandLineRunner，可以利用@Order来进行排序。     14.0 org.springframework.beans.factory.DisposableBean：         使用场景：             这个扩展点也只有一个方法：destroy()，其触发时机为当此对象销毁时，会自动执行这个方法。             比如说运行applicationContext.registerShutdownHook时，就会触发这个方法。     15.0 org.springframework.context.ApplicationListener：         使用场景：             ApplicationListener可以监听某个事件的event，穿插在启动调用中，我们可以自定义某个业务事件，来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。         Spring主要的内置事件包括ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent等。  &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;八、循环依赖&lt;/h1&gt; &lt;p&gt;一级缓存：单例池 二级缓存：半成品池 三级缓存：打破循环依赖 -&amp;gt; ObjectFactory创建实例&lt;/p&gt; &lt;pre&gt;&lt;code&gt;循环依赖其实就是循环引用，也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。 比如 A 依赖于 B , B 依赖于A循环依赖在 spring 中是允许存在，spring 框架依据三级缓存已经解决了大部分的循环依赖 一级缓存：单例池，作用：缓存已经经历了完整的生命周期(生命周期还没走完)，已经初始化完成的 bean 对象，属性名为：singletonObjects 二级缓存：半成品池，作用：存放已经创建好的单例对象，但是还没有进行属性的填充，属性名为：earlySingletonObjects 三级缓存：ObjectFactory，作用：表示对象工厂用来创建某个对象的工厂，这个对象还没有创建完成，只是一个半成品，         当二级缓存中没有的时候，从三级缓存中获取，属性名为：singletonFactories  spring 循环依赖的解决：     1.0 通过三级缓存解决循环依赖     2.0 通过构造函数的循环依赖 构造函数的循环依赖：     出现原因：由于bean的生命周期中构造函数是第一个执行的，spring框架并不能解决构造函数的的依赖注入     解决方案：使用@Lazy进行懒加载，什么时候需要对象再进行bean对象的创建 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;九、spring aop 如何实现的 与 AspectJ 有什么区别&lt;/h1&gt; &lt;p&gt;spring aop 基于动态代理 (继承类使用：cglib；实现接口使用：jdk动态代理) AspectJ 是编译期进行的操作，在生成字节码的时候 插入自定义的逻辑 需要单独使用额外的maven插件&lt;/p&gt; &lt;p&gt;在不修改源码的情况下，给程序统一添加额外的功能，&lt;/p&gt; &lt;h1&gt;十、spring 事务传播机制&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;PROPAGATION_REQUIRED / REQUIRED (默认)    支持当前事务，如果当前没有事务，则新建事务；如果当前存在事务，则加入当前事务，合并成一个事务。    如果上下文中已经存在事务，那么就加入到事务中执行，如果当前上下文中不存在事务，则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。 REQUIRES_NEW：    新建事务，如果当前存在事务，则把当前事务挂起，这个方法会独立提交事务，不受调用者的事务影响，父级异常，它也是正常提交。    如果不起作用，可能的原因如下：调用方与被调用方写在了同一个service类里面，需要用Spring的上下文获取对象。 NESTED    如果当前存在事务，它将会成为父级事务的一个子事务，方法结束后并没有提交，只有等父事务结束才提交，如果当前没有事务，则新建事务    如果它异常，父级可以捕获它的异常而不进行回滚，正常提交，但如果父级异常，它必然回滚，这就是和 REQUIRES_NEW 的区别SUPPORTS    如果当前存在事务，则加入事务；如果当前不存在事务，则以非事务方式运行，这个和不写没区别 NOT_SUPPORTED    以非事务方式运行；如果当前存在事务，则把当前事务挂起 MANDATORY    如果当前存在事务，则运行在当前事务中；如果当前无事务，则抛出异常，也即父级方法必须有事务 NEVER    以非事务方式运行，如果当前存在事务，则抛出异常，即父级方法必须无事务  一般用得比较多的是 PROPAGATION_REQUIRED ， REQUIRES_NEW； &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十一、Spring框架中用到了哪些设计模式？&lt;/h1&gt; &lt;p&gt;Spring框架是一个非常重要的开源框架，它涉及到许多设计模式，以下是Spring框架中使用的一些设计模式： &lt;strong&gt;单例模式&lt;/strong&gt;：Spring框架中的bean默认是单例的，可以通过配置更改其作用域。单例模式保证了在整个应用程序中只有一个实例。 &lt;strong&gt;工厂模式&lt;/strong&gt;：Spring框架中的BeanFactory是一个工厂模式的典型实现。BeanFactory负责实例化并管理应用程序中的对象。 &lt;strong&gt;**代理模式：&lt;/strong&gt;**Spring框架中的AOP（面向切面编程）机制是通过代理模式来实现的。Spring中使用代理对象对目标对象进行包装，从而实现对目标对象的增强。 **观察者模式：**Spring框架中的事件驱动机制就是一个观察者模式的实现。事件源产生事件后，会通知已经注册的监听器进行处理。 **模板方法模式：**Spring框架中的JdbcTemplate是一个典型的模板方法模式的实现。JdbcTemplate定义了一系列操作数据库的基本方法，而具体的实现则由其子类完成。 &lt;strong&gt;适配器模式&lt;/strong&gt;：Spring框架中的HandlerAdapter就是一个适配器模式的典型实现。HandlerAdapter负责将请求发送给处理器进行处理，从而使得不同类型的处理器可以被统一处理。 除了以上这些设计模式，Spring框架还使用了许多其他的设计模式，如策略模式、装饰器模式、命令模式等。这些设计模式的使用，使得Spring框架具有了更好的灵活性、可扩展性和可维护性。&lt;/p&gt; &lt;h1&gt;十二、spring的三级缓存&lt;/h1&gt; &lt;h1&gt;十三、spring的核心注解&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;@SpringBootApplication 标记 springboot 应用 &lt;/li&gt; &lt;li&gt;@SpringBootConfiguration 标记 springboot 配置 &lt;/li&gt; &lt;li&gt;@EnableAutoConfiguration 开启自动配置&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;十四、SpringBoot 读取配置文件原理&lt;/h1&gt; &lt;h1&gt;十五、什么是Spring WebFlux，以及它与Spring MVC的主要区别是什么？&lt;/h1&gt; &lt;p&gt;Spring WebFlux 是Spring 5引入的一个新的响应式框架，旨在为在Spring框架上构建响应式Web应用提供支持。它使用非阻塞I/O模型，能够处理长时间运行的异步任务和大量的并发请求，这使得它非常适合处理事件驱动和实时更新的应用，例如实时聊天应用或大规模的实时数据处理。&lt;/p&gt; &lt;p&gt;与Spring MVC 相比，Spring MVC是一个基于Servlet API构建的传统同步框架，它使用阻塞I/O模型处理HTTP请求。虽然Spring MVC也可以异步处理请求，但其核心仍然是为同步处理而设计的。 ————————————————&lt;/p&gt; &lt;p&gt;Spring MVC&lt;/p&gt; &lt;ul&gt; &lt;li&gt;构建于 Servlet API 之上&lt;/li&gt; &lt;li&gt;同步阻塞 I/O 模型, 认为应用汇阻塞当前线程，所以一个 Request 对应一个 Thread，需要有一个含有大量线程的线程池&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Spring WebFlux&lt;/p&gt; &lt;ul&gt; &lt;li&gt;构建于 Reactive Streams Adapters 之上&lt;/li&gt; &lt;li&gt;异步非阻塞 I/O 模型，认为应用不会阻塞当前线程，所以只是需要一个包含少数固定线程数的线程池 (event loop workers) 来处理请求&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;十六、spring boot 约定优于配置&lt;/h1&gt; &lt;p&gt; 核心思想就是让我们少些很多配置，只需要关注业务代码的编写就行&lt;/p&gt; &lt;h1&gt;十七、spring boot中的starter是什么&lt;/h1&gt; &lt;p&gt;帮我们整合其他组件注册到spring的组件。避免开发者自己去引入依赖&lt;/p&gt; &lt;h1&gt;十八、spring bean的作用域有哪几种&lt;/h1&gt; &lt;p&gt;五种：&lt;/p&gt; &lt;p&gt;1、singleton： bean在每个Spring ioc 容器只有一个实例&lt;/p&gt; &lt;p&gt;2、prorotype：一个bean的定义可以有多个实例&lt;/p&gt; &lt;p&gt;3、request：每次http请求都会创建一个，该bean作用域仅在基于web的Spring ApplicationContext情形下有效&lt;/p&gt; &lt;p&gt;4、session：在一个http session中 一个bean定义对应一个实例。该作用域仅在基于web 的spring applicationContext情形下有效&lt;/p&gt; &lt;p&gt;5、global-session：在一个全局的http session中，一个bean定义对应一个实例&lt;/p&gt; &lt;p&gt;单例，原型（多例），请求，会话，全局会话&lt;/p&gt; &lt;h1&gt;其他问题&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;SpringBoot 默认实现的日志框架：logback SpringBoot 中默认的日志级别是：INFO     常见的日志级别由低到高分为：TRACE &amp;lt; DEBUG &amp;lt; INFO &amp;lt; WARN &amp;lt; ERROR &amp;lt; FATAL &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 08:57:00 GMT</pubDate>
    </item>
    <item>
      <title>Mysql</title>
      <link>http://www.yooyaa.cn/article/12</link>
      <content:encoded>&lt;h1&gt;&lt;strong&gt;一、索引数据结构&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;索引的本质，帮助mysql高效获取数据的&lt;strong&gt;排好序&lt;/strong&gt;的&lt;strong&gt;数据结构&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;1）二叉树&lt;/strong&gt;&lt;/h3&gt; &lt;h3&gt;&lt;strong&gt;2）红黑树&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;​  （本质二叉树，二叉平衡树）&lt;/p&gt; &lt;p&gt;​         缺点：百万数据后，树的高度变的不可控，查找磁盘io次数变的不可控&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;3）Hash表&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;​ 查找性能很高，&lt;/p&gt; &lt;p&gt;​ 缺点：无法模糊范围查询&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;4）B-Tree&lt;/strong&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;叶子节点具有相同的深度，叶节点指针为空&lt;/li&gt; &lt;li&gt;所有的索引元素不重复&lt;/li&gt; &lt;li&gt;节点中的数据索引从左到右递增排列&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;**缺点：**data的存储空间比较大,mysql每列默认大小16k,导致存储数据过少&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;5）B+Tree(mysql的数据结构)(B-Tree变种)&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;- 非叶子节点不存储data，只存储索引(冗余)，可以放更多的索引 - 叶子节点包含所有索引字段 - 叶子节点用指针连接，提高区间访问的性能 每次一行一行的load进ram进行比对 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;二、&lt;strong&gt;mysql索引原理&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;MySQL官方对索引定义：是存储引擎用于快速查找记录的一种数据结构。需要额外开辟空间和数据维护工作。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;索引是物理数据页存储，在数据文件中（InnoDB，ibd文件），利用数据页(page)存储。&lt;/li&gt; &lt;li&gt;索引可以加快检索速度，但是同时也会降低增删改操作速度，索引维护需要代价。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;索引涉及的理论知识：二分查找法、Hash和B+Tree。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;mysql中每节点默认大小16k,一个磁盘指针6byte，一个索引8byte，每个节点存储1170个元素&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mysql中每节点默认大小16k,一个磁盘指针6byte，一个索引8byte，每个节点存储1170个元素，最大数据2000w左右 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;三、mysql的执行过程&lt;/h1&gt; &lt;p&gt;连接器,（缓存）题词分析器，优化器，执行器&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;四、存储引擎原理&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;不同的表有不同的存储引擎&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;1）innrdb&lt;/strong&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;磁盘文件二个，&lt;/li&gt; &lt;li&gt;frm :  表结构文件&lt;/li&gt; &lt;li&gt;idb ： 索引+数据&lt;/li&gt; &lt;li&gt;索引叶子节点存的是所有数据&lt;/li&gt; &lt;li&gt;非主键索引（辅助索引），他的叶子节点存储的是主键id,(为什么如此设计，如果存储的是数据，那么会出现一致性问题，性能原因，空间浪费)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;InnoDB索引实现(聚集索引)&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;•表数据文件本身就是按B+Tree组织的一个索引结构文件&lt;/p&gt; &lt;p&gt;&lt;strong&gt;•聚集索引-叶节点包含了完整的数据记录&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;•为什么InnoDB表必须有主键，并且推荐使用整型的自增主键？&lt;/p&gt; &lt;p&gt;是因为mysql的设计b+tree必须要用主键作为索引，如果没有设置主键，就会在表中找一个唯一一列作为主键，如果找不到则自动生成RowId主键（隐藏主键），主键自增（如果不自增，会为了满足B+tree的特性，则会导致元素的分裂，以及影响整个树的平衡，这样效率会很低）&lt;/p&gt; &lt;p&gt;•为什么非主键索引结构叶子节点存储的是主键值？(一致性和节省存储空间)&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;2）myIsam&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;磁盘文件共三个，&lt;/p&gt; &lt;p&gt;MYD ：数据存储在MYD文件中&lt;/p&gt; &lt;p&gt;MYI ： 主键，索引&lt;/p&gt; &lt;p&gt;frm ： 表结构文件&lt;/p&gt; &lt;p&gt;主键索引叶子节点存储的是：索引所在行的磁盘指针&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;五、索引&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;聚集索引（同一个概念聚簇索引）&lt;/strong&gt;：（就是索引和和表的数据列聚集在一个文件） innerdb的主键索引就是，MyISaM非聚集索引，稀疏索引就是非聚集索引&lt;/p&gt; &lt;p&gt;**联合索引：**能用联合索引，就用联合（反之浪费空间）&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;聚簇索引：     数据存储与索引放到了一块，索引结构的叶子节点保存了行数据  非聚簇索引：     数据与索引分开存储，索引结构的叶子节点指向了数据对应的位置 联合索引中最左前缀原则 与 B+ 树的存储结构有关 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;&lt;strong&gt;六、Explain工具&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;​          使用EXPLAIN关键字可以模拟优化器执行SQL语句，分析你的查询语句或是结构的性能瓶颈 在 select 语句之前增加 explain 关键字，MySQL 会在查询上设置一个标记，执行查询会返 回执行计划的信息，而不是执行这条SQL 注意：如果 from 中包含子查询，仍会执行该子查询，将结果放入临时表中&lt;/p&gt; &lt;p&gt;&lt;strong&gt;explain 两个变种&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;**explain extended：**会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通 过 show warnings 命令可以得到优化后的查询语句，从而看出优化器优化了什么。额外还有 filtered 列，是一个半分比的值，rows * filtered/100 可以估算出将要和 explain 中前一个表 进行连接的行数（前一个表指 explain 中的id值比当前表id值小的表）。 mysql&amp;gt;&amp;gt; explain extended select * from film where id = 1;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-reStructuredText"&gt;1. id列  id列越大执行优先级越高，id相同则从上往下执行，id为NULL最后执行。 2. select_type列        1）simple：简单查询      2）primary：复杂查询中最外层的 select      3）subquery：包含在 select 中的子查询（不在 from 子句中）      4）derived：包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中，也称为 派生表（derived的英文含义） 用这个例子来了解 primary、subquery 和 derived 类型 3. table列 这一列表示 explain 的一行正在访问哪个表。 4. partitions 列  查询将匹配记录的分区。 对于非分区表，该值为 NULL。 5. type列 依次从最优到最差分别为：system &amp;gt; const &amp;gt; eq_ref &amp;gt; ref &amp;gt; range &amp;gt; index &amp;gt; ALL  const, system：mysql能对查询的某部分进行优化并将其转化成一个常量（可以看show warnings 的结果）。用于 primary key 或 unique key 的所有列与常数比较时，所以表最多 有一个匹配行，读取1次，速度比较快。system是const的特例，表里只有一条元组匹配时为 system  eq_ref：primary key 或 unique key 索引的所有部分被连接使用 ，最多只会返回一条符合 条件的记录。这可能是在 const 之外最好的联接类型了，简单的 select 查询不会出现这种 type。  ref：相比 eq_ref，不使用唯一索引，而是使用普通索引或者唯一性索引的部分前缀，索引要 和某个值相比较，可能会找到多个符合条件的行。  - NULL：MySQL能在优化阶段分解查询语句，在执行阶段不用再去访问表或者索引。     - system、const：MySQL对查询的某部分进行优化并把其转化成一个常量（可以通过show warnings命令查看结果）。     - system是const的一个特例，表示表里只有一条元组匹配时为system。     - eq_ref：主键或唯一键索引被连接使用，最多只会返回一条符合条件的记录。简单的select查询不会出现这种type。     - ref：相比eq_ref，不使用唯一索引，而是使用普通索引或者唯一索引的部分前缀，索引和某个值比较，会找到多个符合条件的行。     - range：通常出现在范围查询中，比如in、between、大于、小于等。使用索引来检索给定范围的行。     - index：扫描全索引拿到结果，一般是扫描某个二级索引，二级索引一般比较少，所以通常比ALL快一点。     - ALL：全表扫描，扫描聚簇索引的所有叶子节点。 6. possible_keys列   此列显示在查询中可能用到的索引。         如果该列为NULL，则表示没有相关索引，可以通过检查where子句看是否可以添加一个适当的索引来提高性能。 7. key 列   - 此列显示MySQL在查询时实际用到的索引。     - 在执行计划中可能出现possible_keys列有值，而key列为null，这种情况可能是表中数据不多，MySQL认为索引对当前查询帮助不大而选择了全表查询。     - 如果想强制MySQL使用或忽视possible_keys列中的索引，在查询时可使用force index、ignore index。 8. key_len 列  此列显示MySQL在索引里使用的字节数，通过此列可以算出具体使用了索引中的那些列。     索引最大长度为768字节，当长度过大时，MySQL会做一个类似最左前缀处理，将前半部分字符提取出做索引。     当字段可以为null时，还需要1个字节去记录。     key_len计算规则：             字符串：               char(n)：n个数字或者字母占n个字节，汉字占3n个字节               varchar(n)：  n个数字或者字母占n个字节，汉字占3n+2个字节。+2字节用来存储字符串长度。             数字类型：               tinyint：1字节      smallint：2字节               int：4字节             bigint：8字节             时间类型               date：3字节        timestamp：4字节          datetime：8字节 9. ref 列   此列显示key列记录的索引中，表查找值时使用到的列或常量。常见的有const、字段名 10.rows 列  此列是MySQL在查询中估计要读取的行数。注意这里不是结果集的行数。 11.Extra 列  1）Using index：使用覆盖索引（如果select后面查询的字段都可以从这个索引的树中获取，不需要通过辅助索引树找到主键，再通过主键去主键索引树里获取其它字段值，这种情况一般可以说是用到了覆盖索引）。     2）Using where：使用 where 语句来处理结果，并且查询的列未被索引覆盖。     3）Using index condition：查询的列不完全被索引覆盖，where条件中是一个查询的范围。     4）Using temporary：MySQL需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的。     5）Using filesort：将使用外部排序而不是索引排序，数据较小时从内存排序，否则需要在磁盘完成排序。     6）Select tables optimized away：使用某些聚合函数（比如 max、min）来访问存在索引的某个字段时。 &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;索引数据结构红黑树，Hash，B+树详解&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;索引是怎么支撑千万级表的快速查找&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;面试常问B+树索引面试题解析&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;联合索引底层数据结构又是怎样的&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;其他&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;•索引数据结构红黑树，Hash，B+树详解 •索引是怎么支撑千万级表的快速查找 •面试常问B+树索引面试题解析 •联合索引底层数据结构又是怎样的 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;七、buffer pool&lt;/h1&gt; &lt;pre&gt;&lt;code&gt; buffer pool 内存区域 一个数组， 最小单位为一页 默认为 128M         free 链表：free链表是一个双向链表，是把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中，这个链表便称之为free链表。所有空闲区域引用的链表         flush 链表：创建一个存储脏页的链表，凡是被修改过的缓冲页对应的控制块都会作为节点加入到这个链表中。该链表也被称为flush链表。             修改了Buffer Pool中某个缓冲页的数据，那么它就与磁盘上的页不一致了，这样的缓冲页也被称之为脏页（dirty page）。         lru 链表 的优化策略 避免全表扫描情况把 buffer pool 的热点数据全被刷出内存 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;八、锁&lt;/h1&gt; &lt;h3&gt;&lt;strong&gt;锁定分离&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;1.从性能上分为乐观锁，悲观锁&lt;/p&gt; &lt;p&gt;2.从都数据库操作类型分，读锁（共享锁），写锁（排他锁），都属于悲观锁&lt;/p&gt; &lt;p&gt;3.从对数据操作的粒度分，分为表锁和行锁&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;1).表锁&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;每次操作锁住整张表。开销小，加锁快；不会出现死锁；锁定粒度大，发生锁冲 突的概率最高，并发度最低；&lt;/p&gt; &lt;p&gt;手动增加表锁 lock table 表名称 read(write),表名称2 read(write);&lt;/p&gt; &lt;p&gt;查看表上加过的锁 show open tables;&lt;/p&gt; &lt;p&gt;删除表锁 unlock tables;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;读锁&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当前session和其他session都可以读该表 当前session中插入或者更新锁定的表都会报错，其他session插入或更新则会等 待&lt;/p&gt; &lt;p&gt;&lt;strong&gt;写锁&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当前session对该表的增删改查都没有问题，其他session对该表的所有操作被阻塞&lt;/p&gt; &lt;p&gt;MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改 操作前,会自动给涉及的表加写锁。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;总结： 简而言之，就是****读锁会阻塞写，但是不会阻塞读。而写锁则会把读和写都阻塞。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;2).行锁&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;每次操作锁住一行数据。开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁 冲突的概率最低，并发度最高。&lt;/p&gt; &lt;p&gt;InnoDB与MYISAM的最大不同有两点： 支持事务（TRANSACTION） 支持行级锁&lt;/p&gt; &lt;h1&gt;九、事务&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;行锁支持事务 事务（Transaction）及其ACID属性：原子性，一致性，隔离性，持久性&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;**原子性：**语句要么全执行，要么全不执行，是事务最核心的特性，事务本身就是以原子性来定义的；实现主要基于undo log&lt;/p&gt; &lt;p&gt;&lt;strong&gt;一致性&lt;/strong&gt;：事务追求的最终目标，一致性的实现既需要数据库层面的保障，也需要应用层面的保障（前面提到的原子性、持久性和隔离性，都是为了保证数据库状态的一致性。此外，除了数据库层面的保障，一致性的实现也需要应用层面进行保障。）&lt;/p&gt; &lt;p&gt;**隔离性：**保证事务执行尽可能不受其他事务影响；InnoDB默认的隔离级别是RR，RR的实现主要基于锁机制（包含next-key lock）、MVCC（包括数据的隐藏列、基于undo log的版本链、ReadView）&lt;/p&gt; &lt;p&gt;&lt;strong&gt;持久性&lt;/strong&gt;：保证事务提交后不会因为宕机等原因导致数据丢失；实现主要基于redo log。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;redo log 和 undo log 都属于 InnoDB 的事务日志。下面先聊一下 redo log 存在的背景。 InnoDB 作为 MySQL 的存储引擎，数据是存放在磁盘中的，但如果每次读写数据都需要磁盘 IO，效率会很低。 为此，InnoDB 提供了缓存(Buffer Pool)，Buffer Pool 中包含了磁盘中部分数据页的映射，作为访问数据库的缓冲： 当从数据库读取数据时，会首先从 Buffer Pool 中读取，如果 Buffer Pool 中没有，则从磁盘读取后放入 Buffer Pool。 当向数据库写入数据时，会首先写入 Buffer Pool，Buffer Pool 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。 Buffer Pool 的使用大大提高了读写数据的效率，但是也带来了新的问题：如果 MySQL 宕机，而此时 Buffer Pool 中修改的数据还没有刷新到磁盘，就会导致数据的丢失，事务的持久性无法保证。 于是，redo log 被引入来解决这个问题：当数据修改时，除了修改 Buffer Pool 中的数据，还会在 redo log 记录这次操作;当事务提交时，会调用 fsync 接口对 redo log 进行刷盘。 如果 MySQL 宕机，重启时可以读取 redo log 中的数据，对数据库进行恢复。 redo log 采用的是 WAL(Write-ahead logging，预写式日志)，所有修改先写入日志，再更新到 Buffer Pool，保证了数据不会因 MySQL 宕机而丢失，从而满足了持久性要求。 既然 redo log 也需要在事务提交时将日志写入磁盘，为什么它比直接将 Buffer Pool 中修改的数据写入磁盘(即刷脏)要快呢? 主要有以下两方面的原因： 刷脏是随机 IO，因为每次修改的数据位置随机，但写 redo log 是追加操作，属于顺序 IO。 刷脏是以数据页(Page)为单位的，MySQL 默认页大小是 16KB，一个 Page 上一个小修改都要整页写入;而 redo log 中只包含真正需要写入的部分，无效 IO 大大减少。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;并发事务处理带来的问题&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;更新丢失（Lost Update） 脏读（Dirty Reads） 不可重读（Non-Repeatable Reads） 幻读（Phantom Reads） &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;事务隔离级别&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;**注：**脏读，不可重复读，幻读，都是数据库读一致性问题，必须由数据库提供一定的事务隔离机制来结局&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th align="left"&gt;隔离级别&lt;/th&gt; &lt;th align="left"&gt;脏读（Dirty Read）&lt;/th&gt; &lt;th align="left"&gt;不可重复读（NonRepoatable Read）&lt;/th&gt; &lt;th align="left"&gt;幻读（Phantom Read）&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td align="left"&gt;读未提交（Read uncommitted）&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;读已提交（Read committed）&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;可重复读（Repeatable read）&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;td align="left"&gt;可能&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="left"&gt;可串行化（Serializable）&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;td align="left"&gt;不可能&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;strong&gt;Mysql默认级别是repeatable-read，有办法解决幻读问题吗？ 间隙锁在某些情况下可以解决幻读问题&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;update account set name = 'zhuge' where id &amp;gt; 10 and id &amp;lt;=20;， &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;InnoDB的行锁是针对索引加的锁，不是针对记录加的锁。并且该索引不能失 效，否则都会从行锁升级为表锁。&lt;/p&gt; &lt;h1&gt;十、日志&lt;/h1&gt; &lt;h3&gt;1）日志种类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;binlog：         归属于mysql server层，binlog 是逻辑日志，它记录的是操作语句涉及的每一行修改前后的值，在任何存储引擎下都可以使用。         二进制日志，用于主从复制、崩溃恢复，默认不开启。  undolog：     归属于innodb引擎，是Innodb MVCC的重要组成部分，主要用于记录历史版本数据，用于事务回滚。  redolog：     归属于innodb引擎，redolog 是物理日志，它记录的是数据页修改逻辑以及 change buffer 的变更，只能在innodb引擎下使用。  redolog 是搭配缓冲池、change buffer 使用的，缓冲池的作用是缓存磁盘上的数据页，减少磁盘的IO；change buffer 的作用是将写操作先存在内存中， 等到下次需要读取这些操作涉及到的数据页时，就把数据页加载到缓冲池中，然后在缓冲池中更新；  relaylog 主从同步数据时的中继日志，用于存从主库传来的 binlog 日志文件的内容  事务的持久性是通过redolog实现的（write ahead log（WAL）），即先写日志再写数据；而因为binlog和redolog两种日志属于不同的组件， 所以为了保证数据的一致性，要保证binlog和redolog的一致，所以有了二阶段提交的概念。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;2）redo log与bin log&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;作用不同： redo log 是用于 crash recovery 的，保证 MySQL 宕机也不会影响持久性; binlog 是用于 point-in-time recovery 的，保证服务器可以基于时间点恢复数据，此外 binlog 还用于主从复制。 层次不同： redo log 是 InnoDB 存储引擎实现的， 而 binlog 是 MySQL 的服务器层(可以参考文章前面对 MySQL 逻辑架构的介绍)实现的，同时支持 InnoDB 和其他存储引擎。 内容不同： redo log 是物理日志，内容基于磁盘的 Page。 binlog 是逻辑日志，内容是一条条 sql。 写入时机不同： redo log 的写入时机相对多元。前面曾提到，当事务提交时会调用 fsync 对 redo log 进行刷盘;这是默认情况下的策略，修改 innodb_flush_log_at_trx_commit 参数可以改变该策略，但事务的持久性将无法保证。 除了事务提交时，还有其他刷盘时机：如 master thread 每秒刷盘一次 redo log 等，这样的好处是不一定要等到 commit 时刷盘，commit 速度大大加快。 binlog 在事务提交时写入。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;3) redo log与undo log&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;redo log 和zz会生成一个read view的视图，在后续的操作中会基于一些机制从read view和undo日志版本链中获取对应的数据的一个机制，保证在并发情况下获取正确的数据    1.是mysql中的多版本并发控制，指维护一个数据的多个版本，使得读写操作没有冲突，由三个部分组成：隐藏字段、undolog、readview 2.我们有一个undo日志版本链，在一个事务中第一次查询会生成一个read view的视图，在后续的操作中会基于一些机制从read view和undo日志版本链中获取对应的数据的一个机制，保证在并发情况下获取正确的数据 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;更新，新增，删除才会自动添加事务id,生成事务id,就会生成read-view.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;read-viem:生成的时全库，针对的时session&lt;/p&gt; &lt;p&gt;mysql会默认给每一张表增加一个事务id字段，回滚指针两个字段&lt;/p&gt; &lt;p&gt;修改：插入一条记录，插入之前的记录放到undo日志里面，并回滚指针指向他&lt;/p&gt; &lt;p&gt;当 执行查询sql时，会身材read-viem,包含所有未提交事务数组，和已创建的最大事务id&lt;/p&gt; &lt;p&gt;版本链比对规则,&lt;/p&gt; &lt;p&gt;​            &lt;strong&gt;最大事务id表示当前事务之前的最大提交id(max_id),最小事务id表示所有未提交事务id&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;​   1.trx_id&lt;/p&gt; &lt;p&gt;​   2.trx_id&amp;gt;max_id 不可见&lt;/p&gt; &lt;p&gt;​   3.min_id &amp;lt;= trx_id &amp;lt;= maxi_id&lt;/p&gt; &lt;p&gt;创建了查询快照，记录执 行sql这一刻最大的已提交事务id(快照点已提交最大事务id)&lt;/p&gt; &lt;p&gt;创建事务id &amp;lt;= max(当前事务id(12)，快照点已提交最大事务id），&lt;/p&gt; &lt;p&gt;删除事务id&amp;gt; max(当前事 务id(12)，快照点已提交最大事务id）&lt;/p&gt; &lt;p&gt;Innodb存储引擎由于实现了行级锁定，虽然在锁定机制的实现方面所带来的 性能损耗可能比表级锁定会要更高一下，但是在整体并发处理能力方面要远远优 于MYISAM的表级锁定的。当系统并发量高的时候，Innodb的整体性能和 MYISAM相比就会有比较明显的优势了。 但是，Innodb的行级锁定同样也有其脆弱的一面，当我们使用不当的时候， 可能会让Innodb的整体性能表现不仅不能比MYISAM高，甚至可能会更差。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优化建议&lt;/strong&gt; 尽可能让所有数据检索都通过索引来完成，避免无索引行锁升级为表锁 合理设计索引，尽量缩小锁的范围 尽可能减少检索条件范围，避免间隙锁 尽量控制事务大小，减少锁定资源量和时间长度，涉及事务加锁的sql 尽量放在事务最后执行 尽可能低级别事务隔离&lt;/p&gt; &lt;h1&gt;十二、mysql主从&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;主从同步原理：     mysql主从赋值的核心就是二进制日志binlog(记录了DDL数据定义语言语句和DML数据操作语言语句)     1.0 主库在事务提交时，会把数据变更记录在二进制日志文件 binlog 中     2.0 从库读取主库的二进制日志文件 binlog，写入到从库的中继日志 relay log 中     3.0 从库从中继日志中读取数据，然后写入到从库中  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;1.为什么要主从&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;如果主服务器出现问题，可以快速切换到从服务&lt;/li&gt; &lt;li&gt;从服务器提供查询，实现读写分离&lt;/li&gt; &lt;li&gt;从服务器备份，避免主服务器受到影响&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;2.同步方式&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;基于GTiD,事务(底层基于bin-log)&lt;/li&gt; &lt;li&gt;基于bin-log&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;3.主从复制：延时问题？&lt;/h2&gt; &lt;p&gt;1、优化网络环境&lt;/p&gt; &lt;p&gt;2、增加从库数量：增加从库数量可以增加数据同步的速度和可靠性，同时也能减少每个从库的负担，提高从库响应速度。&lt;/p&gt; &lt;p&gt;3、调整数据库相关参数：可以调整一些&lt;a href="https://cloud.tencent.com/product/cdb?from_column=20065&amp;amp;from=20065" target="_blank"&gt;MySQL数据库&lt;/a&gt;中的相关参数，比如调整binlog格式、binlog缓冲区大小、innodb_flush_log_at_trx_commit等参数，采用半同步模式，以加快数据的同步速度。&lt;/p&gt; &lt;p&gt;4、分区数据库：将数据库分成多个区，每个从库只复制自己所需要的数据区，可以有效的减少排队堵塞、网络传输等方面的延迟问题。&lt;/p&gt; &lt;p&gt;综上所述，优化网络环境、增加从库数量、调整数据库相关参数、分区数据库等方法可以有效的降低MySQL主从复制模式的延迟。&lt;/p&gt; &lt;h2&gt;4.主从复制方式&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;同步复制（Fully Syncharonized）&lt;/li&gt; &lt;li&gt;异步复制(Async） ，默认主从复制方式&lt;/li&gt; &lt;li&gt;半同步复制 （如果超时没有ask，则降级为异步）&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;5.高可用&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;**MM方案，已经废弃**  MMM(Master-Master replication managerfor Mysql，Mysql主主复制管理器)是一 套灵活的脚本程序，基于perl实现，用来对mysql replication进行监控和故障迁移，并能管 理mysql Master-Master复制的配置(同一时间只有一个节点是可写的)  1.1 优点  （1）高可用性，扩展性好，出现故障自动转移，对于主主同步，在同一时间只提供一台数 据库写操作，保证数据的一致性。  （2）配置简单，容易操作。  1.2 缺点  （1）需要一台备份服务器，浪费资源  （2）需要多个虚拟IP  （3）agent可能意外终止，引起裂脑。（当网络抖动，mater1切换master2，但是抖动消失后master1恢复，就会形成）  MHA方案  MHA服务，有两种角色， MHA Manager(管理节点)和 MHA Node(数据节点)。在 MySQL故障切换过程中，MHA能做到在0\~30秒之内自动完成数据库的故障切换操作，目 前MHA主要支持一主多从的架构，要搭建MHA,要求一个复制集群中必须最少有三台数据 库服务器。  2.1 优点  （1）不需要备份服务器  （2）不改变现有环境  （3）操作非常简单  （4）可以进行日志的差异修复  （5）可以将任意slave提升为master  2.2 缺点  （1）需要全部节点做ssh秘钥  （2）MHA出现故障后配置文件会被修改，如果再次故障转移需要重新修改配置文件。  （3）自带的脚本还需要进一步补充完善，且用perl开发，二次开发困难。  &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十三、分表分库维度&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;水平切分：比如按照时间 优点： 1. 单库单表的数据保持一定的量级，有助于性能的提高 2. 切分的表的结构相同，应用层改造较少，只需要增加路由规则即可 3. 提高了系统的稳定性和负载能力 缺点： 1. 切分后数据是分散的，很难利用数据库的关联查询，跨库查询性能较差 2. 拆分规则难以抽象 3. 分片数据的一致性难以解决 4. 数据扩容的难度和维护量极大 垂直切分：按照字段，比如把大字段单独裂成一张表 优点： 1. 拆分后业务清晰，拆分规则明确 2. 系统之间进行整合或扩展容易 3. 按照成本、应用等级、应用的类型等将表放到不同的机器上，便于管理 4. 便于实现动静分离、冷热分离的数据库表的设计模式 5. 数据维护简单 缺点： 1. 部分业务表无法进行关联、只能通过接口的方式来解决，提高了系统的复杂度 2. 受每种业务不同的限制，存在单库性能瓶颈，对数据扩展和性能提升不友好 3. 事务处理复杂 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十四、mysql 调优&lt;/h1&gt; &lt;p&gt;Mysql 慢查询：聚合查询、多表查询、表数据过大查询、深度分页查询 表象：页面加载过慢、接口压测响应时间过长（超过了1s）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1.参数调优       调整 MYSQL 配置，如 最大连接数，线程池缓存线程数，缓存大小 等，部分数据存入redis、mongodb等其它数据库 2.硬件升级 加配置..... 3.定期清理无用数据 4.对于不同的业务场景使用不同的存储引擎       读多写少：MyISAM       读少写多，事务型：InnoDB 5.设计优化：       分库分表(水平拆分、垂直拆分)、使用冗余字段(但是不宜太多)、数据类型优化(适配实际值的存储长度)、索引优化(覆盖索引、联合索引、前缀索引、索引下推)、       索引下推：在 like '%xxx%' 时，如果前面的字段是索引，后面的字段不是索引，那么就会导致全表扫描，索引下推就是解决这个问题的       覆盖索引：索引中包含了查询的字段，不需要回表       联合索引：多个字段组成的索引，可以减少索引的数量，但是也会增加索引的长度，所以需要权衡       前缀索引：索引的字段不是全部，而是前面的一部分，可以减少索引的长度，但是会增加查询的次数，需要权衡    监控报警、 排查慢SQL、MySQL调优     监控报警(搭建 Prometheus  + Grafana)         监控MYSQL查询性能，     排查慢SQL         通过慢查询日志，找出慢查询SQL，然后通过explain分析SQL执行计划，找出问题所在         show status like 'slow_queries';         show variables like 'long_query_time';         show variables like 'slow_query_log';         开启慢查询日志，修改慢查询阈值：         set slow_query_log='ON';    #开启慢查询日志         set long_query_time = 1;     #设置慢查询阈值         分析查询计划 explain：             explain分析sql执行计划（访问类型、记录条数、索引长度等）；主要关注字段：             possible_keys：查询可能用到的索引             key：实际使用的索引             key_len：实际使用的索引的字节数长度。             type：访问类型，看有没有走索引。all（全表扫描），ref（命中非唯一索引），const（命中主键/唯一索引）、range(范围索引查询)、index_merge(使用多个索引)、 system(一行记录时,快速查询)。             Extra：额外信息。看有没有走索引。             using index：覆盖索引，不回表。             using filesort：需要额外的排序。排序分为索引排序和filesort排序，索引排序一般更快，深分页等查询数据量大时filesort更快。             using index condition：索引下推。MySQL5.6开始支持。联合索引某字段是模糊查询（非左模糊）时，该字段进行条件判断后，后面几个字段可以直接条件判断，判断过滤后再回表对不包含在联合索引内的字段条件进行判断。             using where：不走索引，全表扫描 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十五、&lt;strong&gt;三范式&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1NF(第一范式)：属性不可再分。 2NF(第二范式)：1NF 的基础之上，消除了非主属性对于码的部分函数依赖。 3NF(第三范式)：3NF 在 2NF 的基础之上，消除了非主属性对于码的传递函数依赖 。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;十六、页数据&lt;/h1&gt; &lt;p&gt;页数据：每一页最大默认是16KB 包含 文件头 38字节， 页头 56字节， 两个虚拟行记录 26字节， 行数据 大小不确定， 页中尚未使用的空间 大小不确定， 页字典 记录某条数据的相对位置 大小不确定， 叶尾 8字节 校验页是否完整&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;相关面试问题&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.100万数据的表或1亿数据的表如何优化？     1.优化数据库配置     2，增加硬件资源     3.分库分表     4.冷热数据拆分     5.sql索引优化 2.为什么sql语句不要过多使用join     1.性能问题，（会产生临时表。join是2个或多个表连接查询，需要大量的计算资源和内存，join操作过多会导致sql的执行效率降低）     2.可读性和维护性问题 3.limit 500000和limit 50的速度一样快吗？优化方案     1.速度：前者查询一般会查询500000数据然后丢弃，后者直接获取  （数据量很小的情况下，相差不大，如果相差恨到会跳过大量的数据行）     2.优化：使用合适的索引，         从业务层面优化：如果涉及大量数据查询，做冷热数据分离         从sql层面优化：假设存在主键id，可以使用子查询跳过前面的数据集 4、高度为3的b+树可以存放多少数据？  大约2千万左右。 5.Mysql夺命三连问：什么是索引下推？什么是索引覆盖？什么是回表  索引下推：是mysql5.6推出的查询方案，主要目的是减少数据活查询中不必要的数据读取和计算。将查询条件尽可能的推送到索引层面进行一个过滤，减少从磁盘读取的数据量，和后续的计算开销  索引覆盖:就是查询条件与返回值都在索引上面，不需要回表  回表：就是通过索引查询后的数据，根据主键获取完整表数据 6.sql组内排序 7.数据量多大的时候要开始分表分库？      需要结合业务场景和系统架构来考虑？     1.单表数据量超过百万级别，考虑分表     2.单个数据库的性能无法满足需求的时候考虑分库     3.数据库的访问频率，单个库的某些表的并发非常高，又无法满足并发需求，需要把这些表分到不同的数据库节点上，去提高整个数据库的io能力     4.业务拆分：当系统业务逻辑越来越复杂，不同的业务之间的数据耦合度越来底，需要考虑对系统的拆分，方便管理和扩展 8.timestamp和datetime的区别     1.datatime范围更大到9999年，timestamp到2038年，timestamp只需要4个字节，datatime需要8个字节，timestamp存储时以秒为单位，datetime存储的时具体日期和时间     2.timestamp没有默认值，默认值时当前时间，datatime也没有默认值，默认为NULL     3.timestamp受时区影响，datatime不受时区影响 9. limit 1000000 加载很慢的话，你是怎么解决的呢？     - 方案一：如果id是连续的，可以这样，返回上次查询的最大记录(偏移量)，再往下limit     - 方案二：在业务允许的情况下限制页数：     - 方案三：order by + 索引（id为索引）     - 方案四：利用延迟关联或者子查询优化超多分页场景。（先快速定位需要获取的id段，然后再关联） 10.DROP和TRUNCATE和DELETE的区别是什么？  - DROP是删除表和数据不记录日志  - DELETE命令从一个表中删除某一行，或多行，TRUNCATE命令永久地从表中删除每一行。  - delete支持条件删除，truncate只能删除整个表  - delete时DML语句，delete 语句每次删除一行，并在事务日志中为所删除的每行记录一项，固然会慢  - truncate是DDL语句，truncate不需要支持回滚，会保留聚集索引，重置自增索引 11.索引合并：可以让一条SQL使用多个索引。然后对这些索引取交集、并集、交集的并集，从而减少读表次数，提高查询效率。     执行计划的type列会显示index_merge， 12.MySQL为什么使用B+树，而不是B树？     1.当数据量大的时候，树的高度会比较高，数据量大的时候，查询会比较慢；     2.当数据线性增大时，二叉搜索树会呈现单边倒的情况，时间复杂度退化 O(n)，效率更低；     3.b+树的数据只存储在叶子节点，而非叶子节点存储的时索引和指针     4.B树没有内部节点和叶子结点的区分，它的每个节点都是即存了key又存了data     5.因为B树不管叶子节点还是非叶子节点，都会保存数据，这样导致在非叶子节点中能保存的指针数量变少（有些资料也称为扇出），指针少的情况下要保存大量数据，只能增加树的高度，导致IO 操作变多，查询性能变低。     6.进行范围查询时，由于缺乏叶子结点的连接，因此只能通过树的遍历来完成范围查询，这会涉及多个节点的IO问题，效率不如B+树。 13.MySQL中叶子节点超过16k的问题  当一个数据行的大小超过16KB时，MySQL无法存储这行数据，会抛出&amp;quot;Row size too large&amp;quot;的异常。这会导致数据被丢失，严重影响系统稳定运行  增加叶子节点大小：可以通过修改参数innodb_page_size来实现。 14.复合(组合)索引失效的几种情况总结      -复合索引绑定的第一个列,没有出现在查询条件中;（全部失效，第2-7项的情况是部分失效）     -复合索引绑定的多个列是有顺序的,某一个列没有出现在查询条件中,存储引擎不能使用索引中该列及其后的所有列。     -查询条件中出现某个列是范围查询的，存储引擎不能使用复合索引中该列其后的所有列。     -查询条件中某列使用否定条件的（!= &amp;lt;&amp;gt; IS NOT NULL），存储引擎不能使用索引中该列其后的所有列。     -查询条件中某列使用LIKE条件后的字段是以%开头的（如：’%ABC’），存储引擎不能使用索引中该列及其后的所有列。     -查询条件中某列使用函数的，存储引擎不能使用索引中该列及其后的所有列。     -查询条件中某列使用类型转换的（包括显示的和隐示的），存储引擎不能使用索引中该列及其后的所有列。如：字符串类型的列NAME=3,就是隐示的类型转换，将INT型转换为字符串类型。如果写为NAME=’3’,就不是类型转换。 15.DISTINCT和GROUP BY的区别?     -有索引的情况下：group by和distinct都能使用索引，效率相同。     -无索引的情况下：distinct效率高于group by。原因是distinct 和 group by都会进行分组操作，但group by可能会进行排序，触发filesort，导致sql执行效率低下。     -DISTINCT子句将所有NULL值视为相同的值。 16.亿级数据中如何查询 uid为4(非主键id)的那一条数据：  1.0 分库分表         垂直分库、水平分库     2.0 ES存储 17.mysql怎么优化？  Mysql 慢查询：聚合查询、多表查询、表数据过大查询、深度分页查询  表象：页面加载过慢、接口压测响应时间过长（超过了1s） 18.mysql中b+树在插入时怎么变化的？   1.如果节点不存在，则新增节点，并作为B+树的根节点  2.如果节点存在，则查找当前数值所对应的位置，然后插入到叶子节点（如果插入的节点未达到最大数量）  3.如果已经达到最大数量，则将当前叶子节点进行对半分裂，将（m/2）个放入左边节点，剩余放入右边节点。  4.将分列后的右节点的第一个值上升到父节点中。如果未达到最大数则结束，如果达到则继续分裂 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 08:56:00 GMT</pubDate>
    </item>
    <item>
      <title>mysql学习笔记</title>
      <link>http://www.yooyaa.cn/article/11</link>
      <content:encoded>&lt;h1&gt;1.Buff Pool&lt;/h1&gt; &lt;p&gt;  &lt;strong&gt;1.他是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   他是存放索引数据和表数据的缓冲区，默认大小128M,我们的page默认16k，控制块（对缓存数据描述的&lt;strong&gt;控制块，表空间，数据页编号，还有地址信息&lt;/strong&gt;）5%约800字节，所以在buff pool申请内存是 会多申请6m空间 用于存放控制块。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2.如何判断一个页是否在缓冲区&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   mysql有一个hash表数据结构，他的key:表空间号+数据页号,vaue:对应的控制块&lt;/p&gt; &lt;p&gt;   当访问某个page页的时候 ，会先从这个hash表中去查找有没有对应的缓存页，没有就从free链表中找一个空闲页来加载&lt;/p&gt; &lt;h1&gt;2.Page页&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.page页是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   page是mysql存储数据的最小单位，他将mysql的的数据以页为单位存储到磁盘上，减少磁盘io操作，提升效率&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.page页有哪些类型&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  free page（链表） ：空闲页&lt;/p&gt; &lt;p&gt;  clean page:被使用的page，但是没有修改过&lt;/p&gt; &lt;p&gt;  dirty page：脏页，被使用的page，并且数据被修改过，与磁盘数不一致&lt;/p&gt; &lt;h1&gt;3.为什么写缓冲区，仅适用于非唯一普通索引页？&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.写缓冲区是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  change buffer:写缓冲区，是针对二级索引（辅助索引）页的更新优化措施&lt;/p&gt; &lt;p&gt;  他是存储insert,update,delete等变更操作&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.有什么用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  在进行DML（insert,update,delete）操作的时候 ，&lt;/p&gt; &lt;ul&gt; &lt;li&gt;如果数据是非唯一索引（二级索引）会检查数据是否在buffer pool中，&lt;strong&gt;如果不在那么会进行chang buffer 记录，然后再某个时刻merge到buffer pool中&lt;/strong&gt;，然后将记录更新到redo log中 再刷脏&lt;/li&gt; &lt;li&gt;不是就会到磁盘进行唯一校验 ，然后加载数据到buffer pool中，然后将记录更新到redo log中 再刷脏&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;4.使用索引一定可以提升效率吗？  &lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.索引是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 索引是一个排好序的数据结构，为了方便我们检索数据，加速数据检索，提升服务器性能&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.他的优点缺点&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;··优点:提升数据检索效率，降低io成本。他是一组排行序的数据，降低排序成本&lt;/p&gt; &lt;p&gt; 缺点：创建维护索引都会消耗时间与磁盘空间，这个时间还会随着数据量的增大而增减&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3.创建索引的原则是什么&lt;/strong&gt;    &lt;/p&gt; &lt;p&gt;  在经常搜索的列上面创建索引&lt;/p&gt; &lt;p&gt;  在主键上创建索引，强制唯一性，排序结构&lt;/p&gt; &lt;p&gt; 在经常连接的列上面建立索引，加快连接速度（join）&lt;/p&gt; &lt;p&gt; 在经常需要范围查询的列上添加索引（因为是排好序的便于范围搜索）&lt;/p&gt; &lt;p&gt; 在经常需要排序的列上面添加索引（因为索引是排好顺序的）&lt;/p&gt; &lt;p&gt; 在经常使用where子句的列上面创建索引，加快搜索&lt;/p&gt; &lt;h1&gt;5.什么是聚簇索引和非聚簇索引？&lt;br /&gt; &lt;strong&gt;1.他是什么&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt; 聚簇索引：就是索引与数据在一起的索引，比如innerdb的主键索引&lt;/p&gt; &lt;p&gt; 非聚簇索引：就是数据不在一起的索引，也称为二级索引。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.缺点是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  聚簇索引：插入速度严重依赖插入顺序，&lt;/p&gt; &lt;p&gt;  非聚簇索引：需要进行2次查询&lt;/p&gt; &lt;h1&gt;6.索引的种类&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;普通索引&lt;/li&gt; &lt;li&gt;唯一索引&lt;/li&gt; &lt;li&gt;主键索引&lt;/li&gt; &lt;li&gt;复合索引（联合索引）&lt;/li&gt; &lt;li&gt;全文索引：比like速度快&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;7.介绍一下最佳左前缀法则？&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.它是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 他是在聚合索引（联合索引）中匹配原则，根据b+tree索引数据结构&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;8.什么是索引下推？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;1.他是什么：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 在查询条件中涉及like的时候，如果匹配上聚合索引匹配，会优先匹配like，然后再匹配剩余条件中的条件，减少回表次数&lt;/p&gt; &lt;h1&gt;9.什么是自适应hash索引？&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;·1.他是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  他是innerdb的三大特性之一，一个是buffer pool,另一个是双写缓冲区&lt;/p&gt; &lt;p&gt;  1.自适应即不需要我们处理，当innerDb引擎根据查询统计发现某一条件满足hash索引的索引结构，然后就会自动建立一个hash索引&lt;/p&gt; &lt;p&gt;······2.自适应hash索引存在与内存中，不存在与缓存中。&lt;/p&gt; &lt;p&gt;  3.只适合等值查询&lt;/p&gt; &lt;h1&gt;10.为什么LIKE以%开头的索引会失效？&lt;/h1&gt; &lt;p&gt; &lt;strong&gt;1.它是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;   由于是B+tree结构，索引顺序是按字母&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.怎么解决他？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 使用覆盖索引&lt;/p&gt; &lt;h1&gt;11.InnoDb与MyIsam的区别？&lt;/h1&gt; &lt;p&gt;事务和外键&lt;/p&gt; &lt;p&gt; InnoDb支持事务和外键&lt;/p&gt; &lt;p&gt; MyIsam不支持&lt;/p&gt; &lt;p&gt;锁机制&lt;/p&gt; &lt;p&gt; InnoDb支持表锁行锁&lt;/p&gt; &lt;p&gt; MyIsam只支持表锁&lt;/p&gt; &lt;p&gt;索引结构&lt;/p&gt; &lt;p&gt; InnoDb是聚簇索引&lt;/p&gt; &lt;p&gt; MyIsom是非聚簇索引&lt;/p&gt; &lt;p&gt;并非处理能力&lt;/p&gt; &lt;p&gt; MyIsam使用表锁写效率低，读不阻塞&lt;/p&gt; &lt;p&gt; InnoDb读写阻塞与隔离级别有关&lt;/p&gt; &lt;p&gt;存储文件&lt;/p&gt; &lt;p&gt; MyIsam是三个文件&lt;/p&gt; &lt;p&gt; InnoDb是2个文件&lt;/p&gt; &lt;h1&gt;12.一个b+树中大概能存放多少索引记录&lt;/h1&gt; &lt;p&gt; 一页大小是16k,一个指针是6字节，一个主键用int是4字节 换算下来，三层节点大约4千多万数据&lt;/p&gt; &lt;h1&gt;13.explain主要那些字段&lt;/h1&gt; &lt;p&gt; 1.id,select_t*ype,table,type,possible_*key,key,key_len,ref,row,extra&lt;/p&gt; &lt;h1&gt;14.type字段中常见的值？&lt;/h1&gt; &lt;p&gt;system,const,eq&lt;em&gt;ref,ref,rang,index,all&lt;/em&gt;&lt;/p&gt; &lt;h1&gt;15.extra有哪些主要的指标&lt;/h1&gt; &lt;p&gt;using where：全表扫描&lt;/p&gt; &lt;p&gt;using tmpporary :临时表，常见于排序和分组查询 &lt;/p&gt; &lt;p&gt;using index： 不需要回表，直接从索引获取&lt;/p&gt; &lt;p&gt;using filesort  :无法利用索引进行排序&lt;/p&gt; &lt;p&gt;using index condition： 有索引列，但是部分字段没有走索引&lt;/p&gt; &lt;p&gt;using join buffer  ：链接表&lt;/p&gt; &lt;h1&gt;16.如何进行分页查询优化?&lt;/h1&gt; &lt;p&gt;1.索引优化（id递增，取id &amp;gt;= ）&lt;/p&gt; &lt;p&gt;2.利用子查询优化&lt;/p&gt; &lt;h1&gt;17.如何对慢查询优化？&lt;/h1&gt; &lt;p&gt;1.等待时间长&lt;/p&gt; &lt;p&gt;2.执行时间长&lt;/p&gt; &lt;p&gt;3.从explain入手&lt;/p&gt; &lt;p&gt;4.尽可能的从索引中排好序&lt;/p&gt; &lt;p&gt;5.尽量不适用select *&lt;/p&gt; &lt;p&gt;6.只过滤有效的数据&lt;/p&gt; &lt;p&gt;7.尽可能的避免复杂join与子查询&lt;/p&gt; &lt;p&gt;8.合理设计利用索引&lt;/p&gt; &lt;p&gt; 优化思路：&lt;/p&gt; &lt;p&gt;   Io问题，cpu，网络带宽&lt;/p&gt; &lt;h1&gt;18.InnoDB日志相关的参数优化&lt;/h1&gt; &lt;p&gt;1.修改日志缓存去大小&lt;/p&gt; &lt;p&gt;2.修改日志组文件个数&lt;/p&gt; &lt;h1&gt;19.InnoDB Io线程相关参数优化&lt;/h1&gt; &lt;p&gt;1.开启查询缓存&lt;/p&gt; &lt;h1&gt;20.什么是写失效？&lt;/h1&gt; &lt;p&gt;1.由于操作系统页大小是4k，mysql的页大小是16k。innodb的页写入到磁盘需要写4次&lt;/p&gt; &lt;p&gt;2.部分写失效，导致数据丢失&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 05:59:00 GMT</pubDate>
    </item>
    <item>
      <title>JVM虚拟机</title>
      <link>http://www.yooyaa.cn/article/10</link>
      <content:encoded>&lt;h1&gt;&lt;strong&gt;&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域?id=java-内存区域详解" target="_blank"&gt;1.JAVA内存区域&lt;/a&gt;&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;**线程私有：**虚拟机栈，本地方法栈（native方法 ），程序计数器&lt;/p&gt; &lt;p&gt;**线程公有：**堆，1.7之前方法区常量池，1.8之后元空间&lt;/p&gt; &lt;h1&gt;2.JAVA堆&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;minor gc  年轻代gc&lt;/li&gt; &lt;li&gt;full gc 整个堆的gc  ,暂停应用程序，并发问题，各种空指针，清理不干净&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;strong&gt;1).进入老年代的几种方式&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;1.长期存活的对象，当对象gc15次之后，可以修改但是最大15.取决于对象头种只用4bit表示年代信息 2.动态年龄判断，当该块的servivor区大于50%,会从低年龄累加到超过servivoer区的一半之后的对象，会一次性放到老年代 3.大对象，直接放入 4.to servivor区被占满后 5.老年代担保机制？ 6.当minor gc时 servivor区放不下的对象会直接放入老年代 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;*对象   Eden -&amp;gt; form servivor  -&amp;gt; to servivor =&amp;gt; old&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;直接内存并不是虚拟机运行时数据区的一部分，也不是虚拟机规范中定义的内存区域，但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域?id=_253-为什么要将永久代-permgen-替换为元空间-metaspace-呢" target="_blank"&gt;为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;整个永久代有一个 JVM 本身设置固定大小上限，无法进行调整，而元空间使用的是直接内存，受本机可用内存的限制，虽然元空间仍旧可能溢出，但是比原来出现的几率会更小。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;直接内存&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;直接内存并不是虚拟机运行时数据区的一部分，也不是虚拟机规范中定义的内存区域，但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;strong&gt;2).对象的创建&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;类的加载检查，分配内存空间 ，设置对象头，执行init方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1.类加载检查：虚拟机遇到一条 new 指令时，首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用，并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有，那必须先执行相应的类加载过程。 2.分配空间 3.类的初始化 4.设置对象头 5.执行init方法 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;3.类的加载过程&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;加载，验证，准备，解析，初始化，使用，卸载&lt;/p&gt; &lt;pre&gt;&lt;code&gt;加载，验证，准备，解析，初始化，使用，卸载 加载：在硬盘上查找并通过IO读入字节码文件，使用到类时才会加载，例如调用 类的main()方法，new对象等等 验证：校验字节码文件的正确性  准备：给类的静态变量分配内存，并赋予默认值  解析：将符号引用替换为直接引用，该阶段会把一些静态方法(符号引用，比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用)，这是所谓的静态链 接过程(类加载期间完成)，动态链接是在程序运行期间完成的将符号引用替换为直接 引用，下节课会讲到动态链接  初始化：对类的静态变量初始化为指定的值，执行静态代码块 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;4.对象的内存布局&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;1.对象头（锁信息，gc年代，hashcode,类的元数据，锁的标志位） 2.对象的实例数据 3.对齐占位（如果时数组对象，那么会是数组信息） &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;5).如何判断一个对象是否死亡&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;1.引用计数器法：当对象存在引用，则引用计数+1，不存在引用，引用计数为0.不能避免循环引用&lt;/p&gt; &lt;p&gt;2.可达性分析：&lt;strong&gt;gc root根&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;可作为 GC Roots 的对象包括下面几种: 1.虚拟机栈(栈帧中的本地变量表)中引用的对象 2.本地方法栈(Native 方法)中引用的对象 3.方法区中类静态属性引用的对象 4.方法区中常量引用的对象 5.所有被同步锁持有的对象 6.jni的引用对象  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;6).String 和常量池&lt;/h3&gt; &lt;p&gt;只要使用 new 方法，便需要创建新的对象。&lt;/p&gt; &lt;h3&gt;7).如何判断一个类是无用的类&lt;/h3&gt; &lt;p&gt;** 方法区主要回收的是无用的类**&lt;/p&gt; &lt;ul&gt; &lt;li&gt;该类所有的实例都已经被回收，也就是 Java 堆中不存在该类的任何实例。&lt;/li&gt; &lt;li&gt;加载该类的 ClassLoader 已经被回收。&lt;/li&gt; &lt;li&gt;该类对应的 java.lang.Class 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;8).HotSpot 为什么要分为新生代和老年代？&lt;/h3&gt; &lt;p&gt;  比如在新生代中，每次收集都会有大量对象死去，所以可以选择”标记-复制“算法，只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的，而且没有额外的空间对它进行分配担保，所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。&lt;/p&gt; &lt;h1&gt;3.&lt;strong&gt;双亲委派模式&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;上面的类加载过程主要是通过类加载器来实现的，Java里有如下几种类加载器&lt;/strong&gt; **启动类加载器：**负责加载支撑JVM运行的位于JRE的lib目录下的核心类库，比如 rt.jar、charsets.jar等 扩展类加载器：负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中 的JAR类包 **应用程序类加载器：**负责加载ClassPath路径下的类包，主要就是加载你自己写 的那些类 **自定义加载器：**负责加载用户自定义路径下的类包&lt;/p&gt; &lt;p&gt;   加载某个类时会先委托父加载器寻找目标类，找不 到再委托上层父加载器加载，如果所有父加载器在自己的加载类路径下都找不到目标类，    &lt;/p&gt; &lt;p&gt;则 在自己的类加载路径中查找并载入目标类。比如我们的Math类，最先会找应用程序类加载器加载，应用程序类加载器会先委托扩展类加载器加载，扩展类加载器再委托启动类加载器，顶层启动类加载器在自己的类加载路径 找了半天没找到Math类，则向下退回加载Math类的请求，扩展类加载器收到回复就自己 载，在自己的类加载路径里找了半天也没找到Math类，又向下退回Math类的加载请求给 用程序类加载器，应用程序类加载器于是在自己的类加载路径里找Math类，结果找到了 自己加载了。&lt;/p&gt; &lt;p&gt; &lt;strong&gt;双亲委派机制说简单点就是，先找父亲加载，不行再由儿子自己加载&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;为什么要设计双亲委派机制？&lt;/strong&gt;  **沙箱安全机制：**自己写的java.lang.String.class类不会被加载，这样便可以防止 核心API库被随意篡改  **避免类的重复加载：**当父亲已经加载了该类时，就没有必要子ClassLoader再加 载一次，保证被加载类的唯一性&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Tomcat打破双亲委派模式&lt;/strong&gt;：webappClassLoader加载自己的目录下的class文件，不会传递给父类加载器，打破了双亲委派机制。&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;4.Gc Roots&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;可作为 GC Roots 的对象包括下面几种:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;虚拟机栈(栈帧中的本地变量表)中引用的对象&lt;/li&gt; &lt;li&gt;本地方法栈(Native 方法)中引用的对象&lt;/li&gt; &lt;li&gt;方法区中类静态属性引用的对象&lt;/li&gt; &lt;li&gt;方法区中常量引用的对象&lt;/li&gt; &lt;li&gt;所有被同步锁持有的对象&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;&lt;strong&gt;5.三色标记&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;要找出存活对象，根据可达性分析，从GC Roots开始进行遍历访问，可达的则为存活对象：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;白色：&lt;/strong&gt;*尚未被GC访问过的对象，如果全部标记已完成依旧为白色的，称为不可达对象，既垃圾对象。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;灰色：&lt;/strong&gt;*本对象已访问过，但是本对象的子引用对象还没有被访问过，全部访问完会变成黑色，属于中间态（本对象的孩子节点还没有访问）。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;黑色：&lt;/strong&gt;*本对象已经被GC访问过，且本对象的子引用对象也已经被访问过了（本对象的孩子节点也都被访问过）。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;标记过程：&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;初始时，所有对象都在 【白色集合】中；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;将GC Roots 直接引用到的对象 挪到 【灰色集合】中；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;从灰色集合中获取对象：&lt;/p&gt; &lt;p&gt;3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中.&lt;/p&gt; &lt;p&gt;3.2. 将本对象 挪到 【黑色集合】里面。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;重复步骤3，直至【灰色集合】为空时结束。&lt;/p&gt; &lt;p&gt;结束后，仍在【白色集合】的对象即为GC Roots 不可达，可以进行回收。&lt;/p&gt; &lt;h1&gt;6.垃圾回收算法&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.标记-清除  （1.效率问题，缺点产生不连续的内存空间） 2.标记-复制   （常用于eden区）（将存活的对象复制到空闲区，然后变为运行区，将旧运行区的内存清空即可变为空闲区，既保证了内存连贯性，效率也得到保证，无效对象较多的情况下，效率较高，清理后无内存碎片，但是占用内存，某一时刻只能使用其中一块内存区，内存的使用率较低） 3.标记-整理   （常用于老年代）（对已标记可被清除的对象进行垃圾回收，然后对存活的对象进行内存整理 多了一步，对象需要移动，影响整体GC的效率） &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;7.分代收集算法&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;新生代用标记-复制算法，老年代用标记-整理、标记-清除算法&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Minor GC(young GC)：新生代的垃圾回收，暂停时间短(STW stop the world 暂停所有引用程序线程，等待垃圾回收完成) Mixed GC：新生代 + 老年代 部分 区域的垃圾回收， G1 收集器特有的 Full GC：新生代 + 老年代 完整 垃圾回收，暂停时间(STW)长，应该尽量避免 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;8.JVM 垃圾回收器&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;串行垃圾收集器：     使用单线程进行垃圾回收，堆内存较小，适合个人电脑     Serial：作用于新生代，采用复制算法     Serial Old：作用于老年代，采用标记-整理算法     垃圾回收时，只有一个线程在工作，并且java应用中的所有线程都要暂停(STW)，等待垃圾回收的完成 并行垃圾收集器：     是一个并行垃圾回收器，jdk8 默认使用此垃圾回收器     Parallel New：作用于新生代，采用复制算法     Parallel Old：作用于老年代，采用标记-整理算法     垃圾回收时，多个线程在工作，并且java应用中的所有线程都要暂停(STW)，等待垃圾回收的完成    1.Serial收集器  （串行收集器，单线程收集器 新生代采用标记-复制算法，老年代采用标记-整理算法。） 2.ParNew收集器  （Serial收集器的多线程版本 新生代采用标记-复制算法，老年代采用标记-整理算法。） 3.Parallel Scavenge收集器（高效利用cpu的收集器 标记-复制） 4.Serial Old收集器（老年代版本的收集器）、 5.Paralled Old收集器（老年代版本的多线程收集器，标记-整理） 6.CMS收集器 （以获取最短停顿时间为目标的收集器，注重用户体验应用） 7.G1收集器 8.ZGC收集器 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;1）cms垃圾收集器：cms是真正意义上的第一款，并发收集器  是一种标记-清除 算法&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1.初始标记  暂停所有其他线程，记录直接与gc roots 相连的对象，速度快 2.并发标记  同时gc线程和用户线程同时执行 3.重新标记  修正并发标记期间产生变动的标记记录，停顿时间比初始标记长，比并发标记快 4.并发清除  开启用户线程，同时gc线程清扫 缺点： 1.对 CPU 资源敏感； 2.无法处理浮动垃圾； 3.它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。 注： 如果cms收集器执行过程中，又触发了cms垃圾回收，则会走Serail串行化垃圾回收机制 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;2）G1垃圾收集器：是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.（作用在新生代和老年代 jdk9及其之后默认使用此垃圾收集器）&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;划分为多个区域，每个区域都可以充当eden，survivor，old，humongous，其中 humongous 是专门为大对象准备的 采用标记-复制算法 响应时间与吞吐量兼顾 分成三个阶段：新生代回收、并发标记、混合收集 G1 收集器的运作大致分为以下几个步骤： 初始标记： 并发标记 最终标记 筛选回收 年轻代垃圾回收(Young Collection) =&amp;gt; 年轻代垃圾回收 + 并发标记(Young Collection + ConCurrent Mark) =&amp;gt; 混合垃圾回收(Mixed Collection) =&amp;gt; 年轻代垃圾回收(Young Collection) 年轻代垃圾回收(Young Collection)：     初始时，所有区域都处于空闲状态     创建了一些对象，挑出一些空闲区域作为伊甸园Eden区存储这些对象     当伊甸园Eden区需要进行垃圾回收的时候，挑出一个空闲的区域作为幸存者Survivor区，用复制算法从伊甸园Eden区复制存活的对象到幸存者Survivor区，需要STW     多次幸存者Survivor区 GC之后(最多15次)，还存活的就会晋升至老年代 年轻代垃圾回收 + 并发标记(Young Collection + ConCurrent Mark)：     当老年代的占用内存超过了45%的阈值之后，就会触发并发标记，这时不会STW，但是重新标记的时候，还是会需要进行STW的     并发标记之后，会有重新标记阶段解决漏标问题，此时需要暂停用户线程STW     等上述都完成之后就知道老年代有哪些对象存活，然后进入混合收集阶段。     此时不会对所有的老年代区域进行回收，而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是Gabarge First名称的由来) 混合垃圾回收(Mixed Collection)：     参与复制的有eden区、survivor区、old区，可能会执行多次，一次不一定能全部收集的完......     新生代的存活对象汇集到幸存者survivor区，或者满足进入老年代old区的就进入老年代old区     老年代的存活对象汇集到一个新的老年代old区     复制完成，内存得到释放。     如果一个对象太大了就会将此对象存入多个连续的humongous区     进入下一轮的新生代回收、并发标记、混合收集  如果并发失败(即垃圾回收的速度赶不上创建新对象的速度)，触发Full GC &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;3）ZGC垃圾回收器&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jdk11实验性引入，jdk15正式投入使用，使用方法启动参数添加：–XX:+UseZGC 优点：     1. 低延时，亚毫秒的最大暂停时间     2. 暂停时间不会随着堆、live-set 或 root-set 的大小而增加     3. 处理 TB 量级的堆(可以处理 8MB-16TB 的堆)； 特点：     1.0 内存多重映射：         就是使用 mmap 把不同的虚拟内存地址映射到同一个物理内存地址上。         ZGC 为了更灵活高效地管理内存，使用了内存多重映射，把同一块儿物理内存映射为 Marked0、Marked1 和 Remapped 三个虚拟内存。         当应用程序创建对象时，会在堆上申请一个虚拟地址，这时 ZGC 会为这个对象在 Marked0、Marked1 和 Remapped 这三个视图空间分别申请一个虚拟地址，         这三个虚拟地址映射到同一个物理地址。         Marked0、Marked1 和 Remapped 这三个虚拟内存作为 ZGC 的三个视图空间，在同一个时间点内只能有一个有效。         ZGC 就是通过这三个视图空间的切换，来完成并发的垃圾回收。     2.0 染色指针：         总共有三种颜色，说明如下：              白色：本对象还没有被标记线程访问过。              灰色：本对象已经被访问过，但是本对象引用的其他对象还没有被全部访问。              黑色：本对象已经被访问过，并且本对象引用的其他对象也都被访问过了。  三色标记的过程如下：     初始阶段，所有对象都是白色。     将 GC Roots 直接引用的对象标记为灰色。     处理灰色对象，把当前灰色对象引用的所有对象都变成灰色，之后将当前灰色对象变成黑色。     重复步骤 3，直到不存在灰色对象为止。     三色标记结束后，白色对象就是没有被引用的对象(比如上图中的 H  和 G)，可以被回收了。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;9.&lt;strong&gt;JVM运行的三个模式&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;解释模式：&lt;/strong&gt;*执行一行JVM字节码就编译一行为机器码&lt;/p&gt; &lt;p&gt;&lt;strong&gt;编译模式：&lt;/strong&gt;*先将所有的JVM字节码一次编译为机器码，然后一次性执行所有机器码&lt;/p&gt; &lt;p&gt;&lt;strong&gt;混合模式：&lt;/strong&gt;*依然使用解释模式执行代码，但是对于一些“热点”代码采取编译器模式执行，这些热点代码对应的机器码会被缓存起来，下次执行无需再编译。JVM一般采用混合模式执行代码&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;10.类的加载过程&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;加载，验证，准备，解析，初始化，使用，卸载&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;**加载：**在硬盘上查找并通过IO读入字节码文件，使用到类时才会加载，例如调用 类的main()方法，new对象等等&lt;/p&gt; &lt;p&gt;**验证：**校验字节码文件的正确性&lt;/p&gt; &lt;p&gt;**准备：**给类的静态变量分配内存，并赋予默认值&lt;/p&gt; &lt;p&gt;**解析：**将符号引用替换为直接引用，该阶段会把一些静态方法(符号引用，比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用)，这是所谓的静态链 接过程(类加载期间完成)，动态链接是在程序运行期间完成的将符号引用替换为直接 引用，下节课会讲到动态链接&lt;/p&gt; &lt;p&gt;**初始化：**对类的静态变量初始化为指定的值，执行静态代码块&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;11.JVM调优&lt;/strong&gt;&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;jsp 查看经常id  1.jmp查看整个jvm对象的数量及内存大小:jmp -histo     2.查看堆的情况:jmp -heap   3.jvisualVm    4.jstack  找出死锁  打印线程栈状态  5.jinfo -flags   jvm运行的参数  6.jstat 可以看堆的各部分使用量，以及加载类的数量  垃圾回收统计 jstat -gc  time&amp;lt;间隔事件 毫秒&amp;gt;  年轻代对象增长的速率  young gc的触发频率和每次耗时  每次young gc后由多少堆进入老年代 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;jvm调优主要:减少full gc ,垃圾再年轻代尽量的减少&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;垃圾收集器相关问题&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.什么是内存泄漏？  持有对象移植不释放，直到堆撑爆 2.如何排查线上内存泄漏，该如何排查?  答：在程序运行过程中，因为某些原因导致不需要使用的对象仍然占用jvm的空间，并且无法被回收，最终导致程序越来越来越大，从而出现oom。或者影响程序的性能 表现为：频繁full gc，内存不释放,老年代越来越大，年轻代居高不下，full gc卡顿 排查：使用jstat命令获取虚拟机内存区域的使用情况和gc情况，使用jmap来dump内存使用mat分析，定位有问题的类 3.jvm调优是什么？  答：减少full gc,垃圾在年轻代尽量的减少 3.如何排查线上cpu标高 4.堆为什么要分代？ 提高效率。对象的寿命有长有短，寿命长的放在一个区，寿命短的放在另一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点，寿命长的区清理频次低一点。 5.如何判断一个类是无用的类     1)方法区主要回收的是无用的类     2)该类所有的实例都已经被回收，也就是 Java 堆中不存在该类的任何实例。     3)加载该类的 ClassLoader 已经被回收。     4)该类对应的 java.lang.Class 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。 6.HotSpot 为什么要分为新生代和老年代？   比如在新生代中，每次收集都会有大量对象死去，所以可以选择”标记-复制“算法，只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的，而且没有额外的空间对它进行分配担保，所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。 7.常见的垃圾回收器有哪些？ 8.介绍一下 CMS,G1 收集器。 9.Minor Gc 和 Full GC 有什么不同呢？答：minor gc 主要清除的eden区，频率高，full Gc 整个堆，频率低 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;&lt;/h1&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 05:58:00 GMT</pubDate>
    </item>
    <item>
      <title>JAVA并发</title>
      <link>http://www.yooyaa.cn/article/9</link>
      <content:encoded>&lt;h1&gt;&lt;strong&gt;并发的三个重要特性：原子性，可见性，有序性&lt;/strong&gt;&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;原子性 :&lt;/strong&gt; 一个的操作或者多次操作，要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断，要么所有的操作都执行，要么都不执行。synchronized 可以保证代码片段的原子性。&lt;/li&gt; &lt;li&gt;**可见性 ：**当一个变量对共享变量进行了修改，那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。&lt;/li&gt; &lt;li&gt;**有序性 ：**代码在执行的过程中的先后顺序，Java 在编译器以及运行期间的优化，代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;1.什么是线程和进程?&lt;/h1&gt; &lt;p&gt;线程与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源，但每个线程有自己的程序计数器、虚拟机栈和本地方法栈，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;**总结：&lt;/strong&gt;**线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程&lt;/p&gt; &lt;p&gt;则不一定，因为同一进程中的线程极有可能会相互影响。线程执行开销小，但不利于资源的管理和保护；而进程正相反。&lt;/p&gt; &lt;h1&gt;2.线程的上下文切换&lt;/h1&gt; &lt;p&gt;CPU通过时间片分配算法来循环执行任务，当前任务执行一个时间片后会切换到下一个 任务。但是，在切换前会保存上一个任务的状态，以便下次切换回这个任务时，所以任务从保存到再加载的过程就是一次上下文可以再加载这 个任务的状态。切换。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;主动让出 CPU，比如调用了 &lt;code&gt;sleep()&lt;/code&gt;, &lt;code&gt;wait()&lt;/code&gt; 等。&lt;/li&gt; &lt;li&gt;时间片用完，因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死。&lt;/li&gt; &lt;li&gt;调用了阻塞类型的系统中断，比如请求 IO，线程被阻塞。&lt;/li&gt; &lt;li&gt;被终止或结束运行&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;p&gt;著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接：&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html" target="_blank"&gt;https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html&lt;/a&gt;&lt;/p&gt; &lt;h1&gt;3.&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B" target="_blank"&gt;如何创建线程？&lt;/a&gt; &lt;/h1&gt; &lt;p&gt;一般来说，创建线程有很多种方式，例如继承&lt;code&gt;Thread&lt;/code&gt;类、实现&lt;code&gt;Runnable&lt;/code&gt;接口、实现&lt;code&gt;Callable&lt;/code&gt;接口、使用线程池、使用&lt;code&gt;CompletableFuture&lt;/code&gt;类等等。&lt;/p&gt; &lt;p&gt;不过，这些方式其实并没有真正创建出线程。准确点来说，这些都属于是在 Java 代码中使用多线程的方法。&lt;/p&gt; &lt;p&gt;严格来说，Java 就只有一种方式可以创建线程，那就是通过&lt;code&gt;new Thread().start()&lt;/code&gt;创建。不管是哪种方式，最终还是依赖于&lt;code&gt;new Thread().start()&lt;/code&gt;。&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;4.&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#%E8%AF%B4%E8%AF%B4%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E7%8A%B6%E6%80%81" target="_blank"&gt;说说线程的生命周期和状态?&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;NEW: 初始状态，线程被创建出来但没有被调用 &lt;code&gt;start()&lt;/code&gt; 。&lt;/li&gt; &lt;li&gt;RUNNABLE: 运行状态，线程被调用了 &lt;code&gt;start()&lt;/code&gt;等待运行的状态。&lt;/li&gt; &lt;li&gt;BLOCKED：阻塞状态，需要等待锁释放。&lt;/li&gt; &lt;li&gt;WAITING：等待状态，表示该线程需要等待其他线程做出一些特定动作（通知或中断）。&lt;/li&gt; &lt;li&gt;TIME_WAITING：超时等待状态，可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。&lt;/li&gt; &lt;li&gt;TERMINATED：终止状态，表示该线程已经运行完毕。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。&lt;/p&gt; &lt;h1&gt;5.指令重排&lt;/h1&gt; &lt;p&gt;为了保证处理器的运算单元被充分利用，处理器可能会对输入的代码进行乱序执行优化&lt;/p&gt; &lt;h1&gt;6.&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_22-%e7%a8%8b%e5%ba%8f%e8%ae%a1%e6%95%b0%e5%99%a8%e4%b8%ba%e4%bb%80%e4%b9%88%e6%98%af%e7%a7%81%e6%9c%89%e7%9a%84" target="_blank"&gt;程序计数器为什么是私有的?&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;如果执行的是 native 方法，那么程序计数器记录的是 undefined 地址，只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。&lt;/p&gt; &lt;h1&gt;7.&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_8-%e4%bb%80%e4%b9%88%e6%98%af%e7%ba%bf%e7%a8%8b%e6%ad%bb%e9%94%81%e5%a6%82%e4%bd%95%e9%81%bf%e5%85%8d%e6%ad%bb%e9%94%81" target="_blank"&gt;死锁&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;线程死锁描述的是这样一种情况：多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞，因此程序不可能正常终止。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;死锁必须具备以下四个条件：&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;互斥条件：该资源任意一个时刻只由一个线程占用。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺，只有自己使用完毕后才释放资源。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;导致死锁的条件有四个，也就是这四个条件同时满足就会产生死锁。&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;互斥条件，共享资源 X 和 Y 只能被一个线程占用；&lt;/li&gt; &lt;li&gt;请求和保持条件，线程 T1 已经取得共享资源 X，在等待共享资源Y 的时候，不释放共享资源 X；&lt;/li&gt; &lt;li&gt;不可抢占条件，其他线程不能强行抢占线程 T1 占有的资源；&lt;/li&gt; &lt;li&gt;循环等待条件，线程 T1 等待线程 T2 占有的资源，线程T2 等待线程T1占有的资源，就是循环等待。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;按照死锁发生的四个条件，只需要破坏其中的任何一个，就可以解决，但是，互斥条件是没办法破坏的，因为这是互斥锁的基本约束，&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;其他三方条件都有办法来破坏：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;对于“请求和保持”这个条件，我们可以一次性申请所有的资源，这样就不存在等待了。&lt;/li&gt; &lt;li&gt;对于“不可抢占”这个条件，占用部分资源的线程进一步申请其他资源时，如果申请不到，可以主动释放它占有的资源，这样不可抢占这个条件就破坏掉了。&lt;/li&gt; &lt;li&gt;对于“循环等待”这个条件，可以靠按序申请资源来预防。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;所谓按序申请，是指资源是有线性顺序的，申请的时候可以先申请资源序号小的，再申请资源序号大的，这样线性化后自然就不存在循环了。&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;8.&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#thread-sleep-方法和-object-wait-方法对比" target="_blank"&gt;Thread#sleep() 方法和 Object#wait() 方法对比&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;共同点&lt;/strong&gt;：两者都可以暂停线程的执行。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;区别&lt;/strong&gt;：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;sleep()&lt;/code&gt; 方法没有释放锁，而 &lt;code&gt;wait()&lt;/code&gt; 方法释放了锁&lt;/strong&gt; 。&lt;/li&gt; &lt;li&gt;&lt;code&gt;wait()&lt;/code&gt; 通常被用于线程间交互/通信，&lt;code&gt;sleep()&lt;/code&gt;通常被用于暂停执行。&lt;/li&gt; &lt;li&gt;&lt;code&gt;wait()&lt;/code&gt; 方法被调用后，线程不会自动苏醒，需要别的线程调用同一个对象上的 &lt;code&gt;notify()&lt;/code&gt;或者 &lt;code&gt;notifyAll()&lt;/code&gt; 方法。&lt;code&gt;sleep()&lt;/code&gt;方法执行完成后，线程会自动苏醒，或者也可以使用 &lt;code&gt;wait(long timeout)&lt;/code&gt; 超时后线程会自动苏醒。&lt;/li&gt; &lt;li&gt;&lt;code&gt;sleep()&lt;/code&gt; 是 &lt;code&gt;Thread&lt;/code&gt; 类的静态本地方法，&lt;code&gt;wait()&lt;/code&gt; 则是 &lt;code&gt;Object&lt;/code&gt; 类的本地方法。为什么这样设计呢？下一个问题就会聊到。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h1&gt;&lt;a href="#为什么-wait-方法不定义在-thread-中" target="_blank"&gt;9.为什么 wait() 方法不定义在 Thread 中？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;&lt;code&gt;wait()&lt;/code&gt; 是让获得对象锁的线程实现等待，会自动释放当前线程占有的对象锁。每个对象（&lt;code&gt;Object&lt;/code&gt;）都拥有对象锁，既然要释放当前线程占有的对象锁并让其进入 WAITING 状态，自然是要操作对应的对象（&lt;code&gt;Object&lt;/code&gt;）而非当前的线程（&lt;code&gt;Thread&lt;/code&gt;）。&lt;/p&gt; &lt;p&gt;类似的问题：**为什么 &lt;code&gt;sleep()&lt;/code&gt; 方法定义在 &lt;code&gt;Thread&lt;/code&gt; 中？**因为 &lt;code&gt;sleep()&lt;/code&gt; 是让当前线程暂停执行，不涉及到对象类，也不需要获得对象锁。&lt;/p&gt; &lt;h1&gt;10.&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#可以直接调用-thread-类的-run-方法吗" target="_blank"&gt;可以直接调用 Thread 类的 run 方法吗？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;new 一个 &lt;code&gt;Thread&lt;/code&gt;，线程进入了新建状态。调用 &lt;code&gt;start()&lt;/code&gt;方法，会启动一个线程并使线程进入了就绪状态，当分配到时间片后就可以开始运行了。 &lt;code&gt;start()&lt;/code&gt; 会执行线程的相应准备工作，然后自动执行 &lt;code&gt;run()&lt;/code&gt; 方法的内容，这是真正的多线程工作。 但是，直接执行 &lt;code&gt;run()&lt;/code&gt; 方法，会把 &lt;code&gt;run()&lt;/code&gt; 方法当成一个 main 线程下的普通方法去执行，并不会在某个线程中执行它，所以这并不是多线程工作。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;总结：调用 &lt;code&gt;start()&lt;/code&gt; 方法方可启动线程并使线程进入就绪状态，直接执行 &lt;code&gt;run()&lt;/code&gt; 方法的话不会以多线程的方式执行。&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;11.mesi缓存一致性协议&lt;/h1&gt; &lt;p&gt;解决多核cpu之间操作变量的一致性问题&lt;/p&gt; &lt;p&gt;m:修改&lt;/p&gt; &lt;p&gt;e:独占，互斥&lt;/p&gt; &lt;p&gt;s:共享&lt;/p&gt; &lt;p&gt;i:无效&lt;/p&gt; &lt;p&gt;通过总线嗅探机制，监听其他cpu是否再操作同一个变量&lt;/p&gt; &lt;h1&gt;12.JMM模型&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;概念&lt;/strong&gt;：Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步（解决硬件底层不同的抽象协议）&lt;/p&gt; &lt;p&gt;是围绕原子性，有序性，可见性展开 【&lt;a href="https://blog.csdn.net/lxm55913153/article/details/79208126" target="_blank"&gt;https://blog.csdn.net/lxm55913153/article/details/79208126&lt;/a&gt;】&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8种状态:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;lock ：加锁 锁定操作变量&lt;/li&gt; &lt;li&gt;unlock：解锁&lt;/li&gt; &lt;li&gt;load：加载&lt;/li&gt; &lt;li&gt;read：读取&lt;/li&gt; &lt;li&gt;write：写入&lt;/li&gt; &lt;li&gt;store：存储&lt;/li&gt; &lt;li&gt;use：使用&lt;/li&gt; &lt;li&gt;assign：赋值&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;13.volatile&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;保证可见性&lt;/li&gt; &lt;li&gt;防止指令重排&lt;/li&gt; &lt;li&gt;不能保证原子性&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;主要时使用了内存屏障（读屏障和写屏障），cpu的指令：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;lock,unlock 【volatile的有序性】 lock,store &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;同样的,JVM在volatile变量写操作之后插入存储屏障,在读操作之前插入加载屏障,==保证volatile变量的可见性==&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;总线风暴&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;由于volatile的mesi缓存一致性协议需要不断的从主内存嗅探和cas不断循环无效交互导致总线带宽达到峰值&lt;/p&gt; &lt;h1&gt;14.CAS&lt;/h1&gt; &lt;pre&gt;&lt;code&gt; CAS 的原理是拿期望的值和原本的一个值作比较，如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法，这个方法是用来拿到“原来的值”的内存地址，返回值是 valueOffset。另外 value 是一个 volatile 变量，在内存中可见，因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。  cas是高效的的原子操作  缺点：无法避免aba问题，自旋开销大，只能操作一个共享变量 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt; &lt;strong&gt;是一种乐观锁思想，在无锁情况下保证线程操作共享数据原子性，底层使用Unsafe类的方法来实现，（拿预期值与现有值比较，如果相同就修改，无法避免aba问题，自旋）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;JUC中用到了CAS的操作的类：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;AbstractQueueSynchronizer(AQS框架)&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;**AtomicXXX类 原子类:**AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;15.synchronized&lt;/h1&gt; &lt;p&gt; &lt;strong&gt;synchronized 关键字解决的是多个线程之间访问资源的同步性，synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 因为监视器锁（monitor）是依赖于底层的操作系统的 Mutex Lock 来实现的。&lt;/p&gt; &lt;p&gt;庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优化，所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化，如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。&lt;/p&gt; &lt;p&gt;synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令，其中 monitorenter 指令指向同步代码块的开始位置，monitorexit 指令则指明同步代码块的结束位置。&lt;/p&gt; &lt;p&gt;synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令，取得代之的确实是 ACC_SYNCHRONIZED 标识，该标识指明了该方法是一个同步方法。&lt;/p&gt; &lt;p&gt;不过两者的本质都是对对象监视器 monitor 的获取。&lt;/p&gt; &lt;p&gt;在 Java 虚拟机(HotSpot)中，Monitor 是基于 C++实现的，由&lt;a href="https://github.com/openjdk-mirror/jdk7u-hotspot/blob/50bdefc3afe944ca74c3093e7448d6b889cd20d1/src/share/vm/runtime/objectMonitor.cpp" target="_blank"&gt;ObjectMonitor&lt;/a&gt;实现的。每个对象中都内置了一个 ObjectMonitor对象。&lt;/p&gt; &lt;h3&gt;1).synchronized 关键字最主要的三种使用方式：&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;修饰实例方法，锁的是实例对象。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;修饰静态方法，锁的是类class。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;修饰代码块，锁定的类class。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_13-%e8%ae%b2%e4%b8%80%e4%b8%8b-synchronized-%e5%85%b3%e9%94%ae%e5%ad%97%e7%9a%84%e5%ba%95%e5%b1%82%e5%8e%9f%e7%90%86" target="_blank"&gt;2).讲一下 synchronized 关键字的底层原理&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;synchronized 关键字底层原理属于 JVM 层面。&lt;/p&gt; &lt;h3&gt;&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_14-%e8%af%b4%e8%af%b4-jdk16-%e4%b9%8b%e5%90%8e%e7%9a%84-synchronized-%e5%85%b3%e9%94%ae%e5%ad%97%e5%ba%95%e5%b1%82%e5%81%9a%e4%ba%86%e5%93%aa%e4%ba%9b%e4%bc%98%e5%8c%96%ef%bc%8c%e5%8f%af%e4%bb%a5%e8%af%a6%e7%bb%86%e4%bb%8b%e7%bb%8d%e4%b8%80%e4%b8%8b%e8%bf%99%e4%ba%9b%e4%bc%98%e5%8c%96%e5%90%97" target="_blank"&gt;3).说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化，可以详细介绍一下这些优化吗&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;JDK1.6 对锁的实现引入了大量的优化，如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;锁主要存在四种状态，依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态&lt;/strong&gt;，他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级，这种策略是为了提高获得锁和释放锁的效率。&lt;/p&gt; &lt;h3&gt;4).锁主要存在四种状态&lt;/h3&gt; &lt;p&gt;依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态&lt;/p&gt; &lt;h3&gt;5).锁升级&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;synchronized再jdk1.6之后进行了大量的优化，增加了偏向锁，自旋锁，轻量级锁，锁的粗化，锁消除，重量级锁&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1.偏向锁：&lt;/strong&gt; 一段很长时间都只被一个线程使用的锁，可以使用偏向锁，第一次获取锁时，会有一次CAS操作，编程偏向锁结构，之后线程获取在获取锁，只要判断 mark word 中是否有自己的线程id即可，而不是开销相对较大的CAS命令。(只有一个线程使用)&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.轻量级锁（自旋锁）&lt;/strong&gt;：线程加锁时间是错开的(即无竞争)，可以使用轻量级锁来优化。轻量级锁修改了锁对象头的锁标志，相较于重量级锁性能提升了很多，每次修改都是CAS操作，保证了原子性(不同线程交替使用)&lt;/p&gt; &lt;p&gt;**3.重量级锁(多线程争抢)：**底层使用monitor实现，涉及到用户态和内核态的切换、线程上下文切换，成本较高，性能相对较低&lt;/p&gt; &lt;pre&gt;&lt;code&gt;偏向锁:的核心思想是，如果一个线程获得了锁，那么锁就进入偏向模 式，此时Mark Word 的结构也变为偏向锁结构，当这个线程再次请求锁时，无需 再做任何同步操作，即获取锁的过程，这样就省去了大量有关锁申请的操作，从 而也就提供程序的性能。 所以，对于没有锁竞争的场合，偏向锁有很好的优化效 果，毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激 烈的场合，偏向锁就失效了，因为这样场合极有可能每次申请锁的线程都是不相 同的，因此这种场合下不应该使用偏向锁，否则会得不偿失，需要注意的是，偏 向锁失败后，并不会立即膨胀为重量级锁，而是先升级为轻量级锁。  轻量级锁：倘若偏向锁失败，虚拟机并不会立即升级为重量级锁，它还会尝试使用一种 称为轻量级锁的优化手段(1.6之后加入的)，此时Mark Word 的结构也变为轻量 级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁，在整个同 步周期内都不存在竞争”，注意这是经验数据。需要了解的是，轻量级锁所适应 的场景是线程交替执行同步块的场合，如果存在同一时间访问同一锁的场合，就 会导致轻量级锁膨胀为重量级锁。  自旋锁：  偏向锁：刚执行到Synchronized关键字时的锁对象为偏向锁（偏向第一个申请到它的线程）（通过CAS操作修改对象头里的锁标志位），当该线程执行完之后，锁不会被释放；当第二次执行到同步代码块时，线程会判断当前持有锁的线程是否就是自己（对象头里有持有锁的线程ID），若是则继续往下执行，不需要重新加锁；若不是，则会把偏向锁升级为轻量级锁。  轻量级锁: 当有第二个线程加入锁竞争时，偏向锁就会升级为轻量级锁。轻量级锁是自旋锁，即当一个线程申请锁而不得时，该线程就会进入自旋（为什么是自旋而不是挂起呢？因为挂起和恢复需要在用户态和内核态之间切换，会造成较大的开销，而短时间的自旋开销更小，不需要切换状态）。  重量级锁：若某线程忙等次数过多大于设置的阈值，说明锁竞争情况严重（长时间的自旋会造成CPU资源的浪费，开销变大），因此这个达到最大自旋次数的线程就会将轻量级锁升级为重量级锁（CAS操作修改锁标志位），将自己挂起，放弃CPU,等待未来被唤醒。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;6).锁的粗化和锁消除&lt;/h3&gt; &lt;p&gt;1.锁消除：是在程序编译阶段的优化手段（编译器和 JVM 会检测当前代码是否是多线程执行或是否有必要加锁。如果无必要，但又把锁给写了，那么在编译的过程中就会自动把锁去掉。）&lt;/p&gt; &lt;p&gt;2.锁粗化：锁的粒度指的是 synchronized 代码块中包含代码的多少。代码越多，粒度越大；代码越少，粒度越小。（锁的粒度小就意味着串行执行的代码更少，并发执行的代码更多）&lt;/p&gt; &lt;p&gt;&lt;strong&gt;如果某个场景需要频繁地加锁解锁，此时编译器就可能把这个操作优化成个粒度更粗的锁，即锁的粗化。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;7).volatile和synchronized&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;synchronized 关键字和 volatile 关键字是两个互补的存在，而不是对立的存在！&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;volatile 关键字是线程同步的轻量级实现，所以**volatile 性能肯定比synchronized关键字要好。但是volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块**。&lt;/li&gt; &lt;li&gt;volatile 关键字能保证数据的可见性，但不能保证数据的原子性。synchronized 关键字两者都能保证。&lt;/li&gt; &lt;li&gt;volatile关键字主要用于解决变量在多个线程之间的可见性，而 synchronized 关键字解决的是多个线程之间访问资源的同步性。&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;16.ReentrantLock&lt;/h1&gt; &lt;p&gt;&lt;code&gt;ReentrantLock&lt;/code&gt; 实现了 &lt;code&gt;Lock&lt;/code&gt; 接口，是一个可重入且独占式的锁，和 &lt;code&gt;synchronized&lt;/code&gt; 关键字类似。不过，&lt;code&gt;ReentrantLock&lt;/code&gt; 更灵活、更强大，增加了轮询、超时、中断、公平锁和非公平锁等高级功能。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;code&gt;ReentrantLock&lt;/code&gt; 里面有一个内部类 &lt;code&gt;Sync&lt;/code&gt;，&lt;code&gt;Sync&lt;/code&gt; 继承 AQS（&lt;code&gt;AbstractQueuedSynchronizer&lt;/code&gt;），添加锁和释放锁的大部分操作实际上都是在 &lt;code&gt;Sync&lt;/code&gt; 中实现的。&lt;code&gt;Sync&lt;/code&gt; 有公平锁 &lt;code&gt;FairSync&lt;/code&gt; 和非公平锁 &lt;code&gt;NonfairSync&lt;/code&gt; 两个子类。&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;17.&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_15-%e8%b0%88%e8%b0%88-synchronized-%e5%92%8c-reentrantlock-%e7%9a%84%e5%8c%ba%e5%88%ab" target="_blank"&gt;synchronized&amp;amp;ReentrantLock&lt;/a&gt;&lt;/h1&gt; &lt;h3&gt;1).&lt;a href="https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93?id=_151-%e4%b8%a4%e8%80%85%e9%83%bd%e6%98%af%e5%8f%af%e9%87%8d%e5%85%a5%e9%94%81" target="_blank"&gt;两者都是可重入锁&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;“可重入锁” 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增 1，所以要等到锁的计数器下降为 0 时才能释放锁。&lt;/p&gt; &lt;h3&gt;2).synchronized是基于jvm实现的&lt;/h3&gt; &lt;h3&gt;3).reentrantLock是基于cpu的特殊指令实现的&lt;/h3&gt; &lt;h3&gt;4).相比synchronized，ReentrantLock增加了一些高级功能。主要来说主要有三点：&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;等待可中断 :&lt;/strong&gt; ReentrantLock提供了一种能够中断等待锁的线程的机制，通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;可实现公平锁 :&lt;/strong&gt; ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的，可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;可实现选择性通知（锁可以绑定多个条件）:&lt;/strong&gt; synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition()方法。&lt;/p&gt; &lt;p&gt;Condition是 JDK1.5 之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知” ，这个功能非常重要，而且是 Condition 接口默认提供的。而synchronized关键字就相当于整个 Lock 对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;strong&gt;5).锁synchronized和ReentrantLock的区别&lt;/strong&gt; &lt;/h3&gt; &lt;pre&gt;&lt;code&gt; *   1.syncchronized是隐式锁      2.syncchronized只能修饰静态方法，实例方法，代码块。而ReentrantLock 只能用在代码块      3.syncchronized是jvm层面的锁，是java的关键字，通过monitor对象来完成，ReentrantLock是java的api层面实现的底层是基于cas+aqs多线程同步器实现      4.两者都是可重入锁      5.syncchronized只能是非公平锁，ReentrantLock是公平锁和非公平锁      6.synchronized 不需要用户去手动释放锁，ReentrantLock使用lock加锁，unlock释放锁，需要手动释放      7.synchronized是不可中断的锁，ReentrantLock则可以中断，可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中，调用interrupt方法进行中断。      8.synchronized不能绑定Condition； ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒，而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。      9.synchronzied锁的是对象，锁是保存在对象头里面的，根据对象头数据来标识是否有线程获得锁/争抢锁；ReentrantLock锁的是线程，根据进入的线程和int类型的state标识锁的获得/争抢。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;18.AQS:多线程同步器&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;AQS 是一个用来构建锁和同步器的框架，使用 AQS 能简单且高效地构造出应用广泛的大量的同步器，比如我们提到的 ReentrantLock，Semaphore，其他的诸如 ReentrantReadWriteLock，SynchronousQueue，FutureTask 等等皆是基于 AQS 的。当然，我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。  AQS 核心思想是，如果被请求的共享资源空闲，则将当前请求资源的线程设置为有效的工作线程，并且将共享资源设置为锁定状态。如果被请求的共享资源被占用，那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制，这个机制 AQS 是用 CLH 队列锁实现的，即将暂时获取不到锁的线程加入到队列中。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当线程申请共享资源时，共享资源空闲则将线程设置为有效工作线程，并将共享资源锁定，当共享资源被占用时，需要一个能处理线程的等待和唤醒时锁定分配机制。&lt;/p&gt; &lt;p&gt;内部是一个&lt;strong&gt;CLH&lt;/strong&gt;虚拟队列。他是一个双向链表，遵循**fifo（先进先出）**原则，他的节点就是我们的等待的线程。&lt;/p&gt; &lt;p&gt;1.aqs为什么使用双向链表：&lt;/p&gt; &lt;p&gt;1）没有竞争到锁的线程加入到阻塞队列，并且阻塞等待的前提是，当前线程所在节点的前置节点是正常状态。这样设计是为了避免链表中存在异常线程导致无法唤醒后续线程的问题。&lt;/p&gt; &lt;p&gt;2）第二个方面，在 Lock 接口里面有一个，lockInterruptibly()方法，这个方法表示处于锁阻塞的线程允许被中断。也就是说，没有竞争到锁的线程加入到同步队列等待以后，是允许外部线程通过interrupt()方法触发唤醒并中断的。这个时候，被中断的线程的状态会修改成 CANCELLED。（如图）被标记为 CANCELLED 状态的线程，是不需要去竞争锁的，但是它仍然存在于双向链表里面。意味着在后续的锁竞争中，需要把这个节点从链表里面移除，否则会导致锁阻塞的线程无法被正常唤醒。&lt;/p&gt; &lt;p&gt;3)第三个方面，为了避免线程阻塞和唤醒的开销，所以刚加入到链表的线程，首先会通过自旋的方式尝试去竞争锁。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;羊群效应，公平锁，也就是大量的线程在阻塞之前尝试去竞争锁带来比较大的性能开销。所以，（如图）为了避免这个问题，加入到链表中的节点在尝试竞争锁之前，需要判断前置节点是不是头节点，如果不是头节点，就没必要再去触发锁竞争的动作。&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;aqs的其他实现: CountDownLatch(减法计数器):     只有数到0了才会运行 await 后面的代码(无参的时候)     CountDownLatch#getCount() 获取当前剩余数     CountDownLatch#countDown() 当前剩余数减一 - 原子操作     CountDownLatch#await() 等待计数器归0，再向下执行 每次有线程调用 countDown() 数量-1，当计数器变为0，countDownLatch.await()就会被唤醒，继续往下执行！     CountDownLatch#await(long, TimeUnit) 等待计数器归0，或者已过指定时间，再向下执行      使用场景:         将DB的1000W条数据导入到es中，使用 CountDownLatch 记录总执行次数，进行次数控制  CyclicBarrier(加法计数器):     到达指定等待数时，第一个线程 会运行构造后的方法体内容     CyclicBarrier#CyclicBarrier(int parties, Runnable barrierAction) CyclicBarrier构造函数         parties 指定等待数         barrierAction 当满足执行条件(到达等待时间或者达到指定等待数时)后待执行的逻辑     CyclicBarrier#await() 阻塞获取当前等待数 - 会导致被阻塞的线程一直阻塞     CyclicBarrier#await(long, TimeUnit) 超时阻塞获取当前等待数 - 推荐此方法，  Semaphore(信号量(默认为非公平的) 底层使用AQS):     可用于限流，多个共享资源互斥的使用！并发限流，控制最大的线程数！     Semaphore#acquire() 阻塞获取访问权限 - 会导致被阻塞的线程一致阻塞     Semaphore#tryAcquire(int) 阻塞获取指定数量的访问权限 - 会导致被阻塞的线程一致阻塞     Semaphore#tryAcquire(long, TimeUnit) - 超时返回false     Semaphore#tryAcquire(int, long, TimeUnit)  - 超时返回false     Semaphore#tryAcquire() 阻塞获取当前等待数 - 会导致被阻塞的线程一致阻塞     Semaphore#release() 释放一个访问权限     Semaphore#release(int) 释放指定数量的访问权限  ForkJoin: 并行执行任务，【只能将任务1个切分为两个，不能切分为3个或其他数量】     实现 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;19.线程的6种状态&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;new创建&lt;/li&gt; &lt;li&gt;runnable 可运行状态&lt;/li&gt; &lt;li&gt;blocked 状态，获取锁失败的状态&lt;/li&gt; &lt;li&gt;waiting状态 调用sleep或wait的状态&lt;/li&gt; &lt;li&gt;time waiting 设置超时时间的wait&lt;/li&gt; &lt;li&gt;terminated 退出或结束&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;20.线程间通信的定义&lt;/h1&gt; &lt;p&gt;线程的通信可以被定义为：当多个线程共同操作共享的资源时，线程间通过某种方式互相告知自己的状态，以避免无效的资源争夺。&lt;/p&gt; &lt;p&gt;对象的wait()方法：当前线程就进入阻塞状态，并释放同步监视器&lt;/p&gt; &lt;p&gt;对象的notify()方法：一旦执行此方法，就会唤醒被阻塞的进程，如果有多个被wait()，就唤醒优先级最高的&lt;/p&gt; &lt;p&gt;==sleep()不会释放锁，wait()会释放锁==&lt;/p&gt; &lt;h1&gt;21.线程池&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。&lt;/li&gt; &lt;li&gt;提高响应速度。当任务到达时，任务可以不需要的等到线程创建就能立即执行。&lt;/li&gt; &lt;li&gt;提高线程的可管理性。线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;1).ThreadPoolExecutor&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;corePoolSize: 核心线程数，核心线程数目 CPU密集型(多于计算，减少线程上下文的切换): 线程数 = CPU核数 + 1 IO密集型: 线程数 = CPU核数 * 2 + 1 maximumPoolSize: 最大线程数(核心线程 + 救济线程的最大数目) keepAliveTime: 生存时间，救急线程的生存时间，生存时间内没有新任务，此线程的资源会被释放 unit: 时间单位 救急线程的生存时间单位 workQueue: 当无空闲核心线程时，新来任务加入到此队列排队，队列满了就会创建救急线程执行任务     ThreadPoolExecutor线程池推荐了三种等待队列，它们是：SynchronousQueue 、LinkedBlockingQueue 和 ArrayBlockingQueue。     1)有界队列：         SynchronousQueue ：一个不存储元素的阻塞队列，每个插入操作必须等到另一个线程调用移除操作，否则插入操作一直处于 阻塞状态，                             吞吐量通常要高于LinkedBlockingQueue，静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。         ArrayBlockingQueue：一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。                             一旦创建了这样的缓存区，就不能再增加其容量。                             试图向已满队列中放入元素会导致操作受阻塞；试图从空队列中提取元素将导致类似阻塞。     2)无界队列：         LinkedBlockingQueue：【指定容量的场景使用的多】，基于链表结构的无界阻塞队列，                                 它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的，这个容量就是Integer.MAX_VALUE)         PriorityBlockingQueue：是一个按照优先级进行内部元素排序的无界阻塞队列。队列中的元素必须实现 Comparable 接口，这样才能通过实现compareTo()方法进行排序。                                 优先级最高的元素将始终排在队列的头部；PriorityBlockingQueue 不会保证优先级一样的元素的排序。                                 注意：keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。                                 如果BlockingQueue是无界的，那么永远不会触发maximumPoolSize，自然keepAliveTime也就没有了意义。  threadFactory: 线程工厂 定制线程实例的创建，如设置线程名、是否是守护线程等  handler: 拒绝策略，当所有线程都在繁忙，并且工作队列也放满了的时候，就会触发拒绝策略     拒绝策略         - AbortPolicy  直接抛异常阻止系统正常运行         - CallerRunsPolicy  由调用线程处理该任务 (采用)         - DiscardOldestPolicy 丢弃队列最前面的任务，然后重新尝试执行任务(重复此过程)         - DiscardPolicy  也是丢弃任务，但是不抛出异常。          当调用线程池 execute() 方法添加一个任务时，线程池会做如下判断：          corePoolSize -&amp;gt; 阻塞队列 -&amp;gt; maximumPoolSize -&amp;gt; 拒绝策略          如果有空闲线程，则直接执行该任务；         如果没有空闲线程，且当前运行的线程数少于 corePoolSize，则创建新的线程执行该任务(无视其他工作线程处于空闲状态)；         如果没有空闲线程，且当前的线程数等于 corePoolSize，同时阻塞队列未满，则将任务入队列，而不添加新的线程；         如果没有空闲线程，且阻塞队列已满，同时池中的线程数小于 maximumPoolSize ，则创建新的线程执行任务；         如果没有空闲线程，且阻塞队列已满，同时池中的线程数大于等于 maximumPoolSize ，则根据构造函数中的 handler 指定的策略来拒绝新的任务。      线程池 五 个状态：         RUNNING：该状态下，线程池可以接受新任务，并能够处理阻塞队列中的任务         SHUTDOWN：该状态下，线程池不再可以接受新任务，但能够继续处理阻塞队列中的任务         STOP：该状态下，线程池不再可以接受新任务，也不会继续处理阻塞队列中的任务。同时会中断正在处理的任务         TIDYING：该状态下，线程池中的工作线程数量为0。并且会调用terminated()钩子方法(hook method)         TERMINATED：当terminated()钩子方法(hook method)执行完毕后，线程池进入该状态     线程池的shutdown() 方法，将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态     线程池的shutdownNow()方法，将线程池由RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。      状态生命周期：RUNNING ---&amp;gt; SHUTDOWN/STOP ---&amp;gt; TIDYING ---&amp;gt; TERMINATED      不建议使用 Executors 创建的线程池：         1.0 newCachedThreadPool 可以创建一个最大线程数为 Integer.MAX_VALUE 的线程池，创建大量线程，可能会导致 OOM         2.0 newSingleThreadExecutor 和 newFixedThreadPool 可以创建一个阻塞队列为 Integer.MAX_VALUE 的线程池，堆积大量请求，可能会导致 OOM &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;2).&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-03.html#future-类有什么用" target="_blank"&gt;Future 类有什么用？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;&lt;code&gt;Future&lt;/code&gt; 类是异步思想的典型运用，主要用在一些需要执行耗时任务的场景，避免程序一直原地等待耗时任务执行完成，执行效率太低。具体来说是这样的：当我们执行某一耗时的任务时，可以将这个耗时任务交给一个子线程去异步执行，同时我们可以干点其他事情，不用傻傻等待耗时任务执行完成。等我们的事情干完后，我们再通过 &lt;code&gt;Future&lt;/code&gt; 类获取到耗时任务的执行结果。这样一来，程序的执行效率就明显提高了。&lt;/p&gt; &lt;hr /&gt; &lt;h3&gt;3).&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-03.html#callable-和-future-有什么关系" target="_blank"&gt;Callable 和 Future 有什么关系？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;&lt;code&gt;FutureTask&lt;/code&gt; 提供了 &lt;code&gt;Future&lt;/code&gt; 接口的基本实现，常用来封装 &lt;code&gt;Callable&lt;/code&gt; 和 &lt;code&gt;Runnable&lt;/code&gt;，具有取消任务、查看任务是否执行完成以及获取任务执行结果的方法。&lt;code&gt;ExecutorService.submit()&lt;/code&gt; 方法返回的其实就是 &lt;code&gt;Future&lt;/code&gt; 的实现类 &lt;code&gt;FutureTask&lt;/code&gt; 。&lt;/p&gt; &lt;h3&gt;4).&lt;a href="https://javaguide.cn/java/concurrent/java-concurrent-questions-03.html#completablefuture-类有什么用" target="_blank"&gt;CompletableFuture 类有什么用？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;&lt;code&gt;Future&lt;/code&gt; 在实际使用过程中存在一些局限性比如不支持异步任务的编排组合、获取计算结果的 &lt;code&gt;get()&lt;/code&gt; 方法为阻塞调用。&lt;/p&gt; &lt;p&gt;Java 8 才被引入&lt;code&gt;CompletableFuture&lt;/code&gt; 类可以解决&lt;code&gt;Future&lt;/code&gt; 的这些缺陷。&lt;code&gt;CompletableFuture&lt;/code&gt; 除了提供了更为好用和强大的 &lt;code&gt;Future&lt;/code&gt; 特性之外，还提供了函数式编程、异步任务编排组合（可以将多个异步任务串联起来，组成一个完整的链式调用）等能力。&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;22.java的四种引用&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;ThreadLocal 资源对象的线程隔离，各自线程的线程变量，存储在 Thread.threadLocals(ThreadLocal.ThreadLocalMap 类型但未实现Map接口)     set get remove操作     内存溢出原因：         ThreadLocalMap 中 Entry 的 key 是弱引用，所以遇到 GC 时会被回收，但是 Entry 存储的 value 是强引用，还会留在内存中，         所以积累起来会导致线程本地数据越存越多，从而导致OOM         建议 使用完之后 调用 remove 删除对应的value  引用：     强引用：         对象处于有用且必须的状态         只有所有GC Roots对象都不通过【强引用】引用该对象，该对象才能被垃圾回收         即便出现OOM，GC在对象的使用期间也不会回收的实例 最为普遍的方式 new 关键字创建的对象(非弱引用 软引用 虚引用类的实例)      弱引用：         对象处于可能有用但非必须的状态         无论内存是否足够，GC时，都会被回收         也可以使用引用队列         WeakReference reference = new WeakReference(obj);      软引用：         仅有软引用引用该对象时，在垃圾回收后，内存仍不足时会再次触发垃圾回收         内存不够时，GC是会被回收，如果内存足够，则不会被回收         也可以使用引用队列         SoftReference reference = new SoftReference(obj);      虚引用：         必须配合引用队列使用，被引用对象回收时，会将虚引用入队，由 Reference Handler线程 调用与引用相关方法释放直接内存，释放外部资源         ReferenceQueue referenceQueue = new ReferenceQueue();         PhantomReference referenceQueue = new PhantomReference(obj, referenceQueue); &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;23.如何知道线程池中的任务是否已经执行完成？&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.通过submit提交的返回值future.get()方法，阻塞 2.通过线程池的isTemnated()方法来循环判断，但是需要调用shutdown()方法.(一般不会采用这种方法) 3.通过coundownLatch &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;24.Thread和Runnable区别是什么？&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;thread是一个类型，runable是一个接口。runable是一个任务。而thread是一个真正处理线程的实现。 &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;25.ThreadLocal&lt;/h1&gt; &lt;p&gt;  通常情况下，我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢？ JDK 中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值，可以将ThreadLocal类形象的比喻成存放数据的盒子，盒子中可以存储每个线程的私有数据。&lt;/p&gt; &lt;h1&gt;26.逃逸分析&lt;/h1&gt; &lt;p&gt; 就是即时编译时（jit），编译器分析当前代码不会被线程外应用的，则会自动进行逃逸分析&lt;/p&gt; &lt;h1&gt;27.线程的生命周期状态?&lt;/h1&gt; &lt;p&gt;new 初始&lt;/p&gt; &lt;p&gt;runable 运行&lt;/p&gt; &lt;p&gt;blocked 阻塞&lt;/p&gt; &lt;p&gt;wating 等待&lt;/p&gt; &lt;p&gt;time_wating 超时等待&lt;/p&gt; &lt;p&gt;terminated 终止&lt;/p&gt; &lt;h1&gt;其他问题&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;1.如何知道线程池中的任务是否已经执行完成？ 答：1.通过submit提交的返回值future.get()方法，阻塞   2.通过线程池的isTemnated()方法来循环判断，但是需要调用shutdown()方法.(一般不会采用这种方法)   3.通过coundownLatch 2.Thread和Runnable区别是什么？ 答：thread是一个类型，runable是一个接口。runable是一个任务。而thread是一个真正处理线程的实现。 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 05:57:00 GMT</pubDate>
    </item>
    <item>
      <title>JAVA集合</title>
      <link>http://www.yooyaa.cn/article/8</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://oss.javaguide.cn/github/javaguide/java/collection/java-collection-hierarchy.png" alt="Java 集合框架概览" title="Java 集合框架概览" /&gt;&lt;/p&gt; &lt;h1&gt;1.list&lt;/h1&gt; &lt;pre&gt;&lt;code&gt;arraylist：动态数组、由于实现了RandomAccess标志性接口所以支持快速随机访问  vertor：动态数组，线程安全  linkedList:双向链表结构（1.6之前时循环链表） &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;(1).ArrayList&lt;/h2&gt; &lt;p&gt;**默认初始容量是10：new ArrayList()**为0.初始化为10，也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10&lt;/p&gt; &lt;p&gt;&lt;code&gt;ArrayList&lt;/code&gt; 的底层是数组队列，相当于动态数组。与 Java 中的数组相比，它的容量能动态增长。&lt;/p&gt; &lt;h3&gt;ArrayList扩容机制：每次1.5倍（&lt;strong&gt;oldCapacity 为偶数就是 1.5 倍，否则是 1.5 倍左右&lt;/strong&gt;）&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;*扩容发生在add()方法  将oldCapacity 右移一位，其效果相当于oldCapacity /2， // 我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍， int newCapacity = oldCapacity + (oldCapacity &amp;gt;&amp;gt; 1);  然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;这里补充一点比较重要，但是容易被忽视掉的知识点：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Java 中的 &lt;code&gt;length&lt;/code&gt;属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.&lt;/li&gt; &lt;li&gt;Java 中的 &lt;code&gt;length()&lt;/code&gt; 方法是针对字符串说的,如果想看这个字符串的长度则用到 &lt;code&gt;length()&lt;/code&gt; 这个方法.&lt;/li&gt; &lt;li&gt;Java 中的 &lt;code&gt;size()&lt;/code&gt; 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h3&gt;&lt;a href="#system-arraycopy-和-arrays-copyof-方法" target="_blank"&gt;&lt;code&gt;System.arraycopy()&lt;/code&gt; 和 &lt;code&gt;Arrays.copyOf()&lt;/code&gt;方法&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;阅读源码的话，我们就会发现 &lt;code&gt;ArrayList&lt;/code&gt; 中大量调用了这两个方法。比如：我们上面讲的扩容操作以及&lt;code&gt;add(int index, E element)&lt;/code&gt;、&lt;code&gt;toArray()&lt;/code&gt; 等方法中都用到了该方法！&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;strong&gt;联系：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;看两者源代码可以发现 &lt;code&gt;copyOf()&lt;/code&gt;内部实际调用了 &lt;code&gt;System.arraycopy()&lt;/code&gt; 方法&lt;/p&gt; &lt;p&gt;&lt;strong&gt;区别：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;arraycopy()&lt;/code&gt; 需要目标数组，将原数组拷贝到你自己定义的数组里或者原数组，而且可以选择拷贝的起点和长度以及放入新数组中的位置 &lt;code&gt;copyOf()&lt;/code&gt; 是系统自动在内部新建一个数组，并返回该数组。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;code&gt;ArrayList&lt;/code&gt; 继承于 &lt;code&gt;AbstractList&lt;/code&gt; ，实现了 &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;RandomAccess&lt;/code&gt;, &lt;code&gt;Cloneable&lt;/code&gt;, &lt;code&gt;java.io.Serializable&lt;/code&gt; 这些接口。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;List&lt;/code&gt; : 表明它是一个列表，支持添加、删除、查找等操作，并且可以通过下标进行访问。&lt;/li&gt; &lt;li&gt;&lt;code&gt;RandomAccess&lt;/code&gt; ：这是一个标志接口，表明实现这个接口的 &lt;code&gt;List&lt;/code&gt; 集合是支持 &lt;strong&gt;快速随机访问&lt;/strong&gt; 的。在 &lt;code&gt;ArrayList&lt;/code&gt; 中，我们即可以通过元素的序号快速获取元素对象，这就是快速随机访问。&lt;/li&gt; &lt;li&gt;&lt;code&gt;Cloneable&lt;/code&gt; ：表明它具有拷贝能力，可以进行深拷贝或浅拷贝操作。&lt;/li&gt; &lt;li&gt;&lt;code&gt;Serializable&lt;/code&gt; : 表明它可以进行序列化操作，也就是可以将对象转换为字节流进行持久化存储或网络传输，非常方便。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h3&gt;&lt;a href="#arraylist-和-vector-的区别-了解即可" target="_blank"&gt;ArrayList 和 Vector 的区别?（了解即可）&lt;/a&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;ArrayList&lt;/code&gt; 是 &lt;code&gt;List&lt;/code&gt; 的主要实现类，底层使用 &lt;code&gt;Object[]&lt;/code&gt;存储，适用于频繁的查找工作，线程不安全 。&lt;/li&gt; &lt;li&gt;&lt;code&gt;Vector&lt;/code&gt; 是 &lt;code&gt;List&lt;/code&gt; 的古老实现类，底层使用&lt;code&gt;Object[]&lt;/code&gt; 存储，线程安全。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;插入和删除是否受元素位置的影响：&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;是否支持快速随机访问：&lt;/strong&gt; &lt;code&gt;LinkedList&lt;/code&gt; 不支持高效的随机元素访问，而 &lt;code&gt;ArrayList&lt;/code&gt;（实现了 &lt;code&gt;RandomAccess&lt;/code&gt; 接口） 支持。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;内存空间占用：&lt;/strong&gt; &lt;code&gt;ArrayList&lt;/code&gt; 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间，而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间（因为要存放直接后继和直接前驱以及数据）。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h3&gt;&lt;a href="https://javaguide.cn/java/collection/arraylist-source-code.html#arraylist-%E4%B8%8E-linkedlist-%E5%8C%BA%E5%88%AB" target="_blank"&gt;Arraylist 与 LinkedList 区别?&lt;/a&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;是否保证线程安全：&lt;/strong&gt; &lt;code&gt;ArrayList&lt;/code&gt; 和 &lt;code&gt;LinkedList&lt;/code&gt; 都是不同步的，也就是不保证线程安全；&lt;/li&gt; &lt;li&gt;&lt;strong&gt;底层数据结构：&lt;/strong&gt; &lt;code&gt;ArrayList&lt;/code&gt; 底层使用的是 &lt;strong&gt;&lt;code&gt;Object&lt;/code&gt; 数组&lt;/strong&gt;；&lt;code&gt;LinkedList&lt;/code&gt; 底层使用的是 &lt;strong&gt;双向链表&lt;/strong&gt; 数据结构&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;2.map&lt;/h1&gt; &lt;p&gt;&lt;code&gt;HashMap&lt;/code&gt;：JDK1.8 之前 &lt;code&gt;HashMap&lt;/code&gt; 由数组+链表组成的，数组是 &lt;code&gt;HashMap&lt;/code&gt; 的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）。JDK1.8 以后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为 8）（将链表转换成红黑树前会判断，如果当前数组的长度小于 64，那么会选择先进行数组扩容，而不是转换为红黑树）时，将链表转化为红黑树，以减少搜索时间&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;code&gt;LinkedHashMap&lt;/code&gt;：&lt;code&gt;LinkedHashMap&lt;/code&gt; 继承自 &lt;code&gt;HashMap&lt;/code&gt;，所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外，&lt;code&gt;LinkedHashMap&lt;/code&gt; 在上面结构的基础上，增加了一条双向链表，使得上面的结构==可以保持键值对的插入顺序==。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;treeMap：红黑树&lt;/p&gt; &lt;hr /&gt; &lt;h2&gt;(1).hashmap&lt;/h2&gt; &lt;p&gt;1.数据结构:数组+链表+红黑树（当链表长度大于8并且数组长度大于64是，链表会转化为红黑树，主要是为了提升检索效率）&lt;/p&gt; &lt;p&gt;2.其中数组小于64时会进行扩容不会转化为红黑树。在移除数据时，当红黑树的节点移除到剩6个时，将红黑树转换成链表。(选择6个而不是8个，是为了避免频繁进行红黑树和链表的转换，造成性能的损耗。)&lt;/p&gt; &lt;p&gt;3.数组长度是2的n次幂：计算索引时效率更高&lt;/p&gt; &lt;p&gt;4.hashmap的寻址方法：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;计算对象的hashcode()&lt;/li&gt; &lt;li&gt;调用hash()函数进行二次hash，然后右移动16位进行异或运算（让hash分布的更均匀）&lt;/li&gt; &lt;li&gt;最后（capocity-1）&amp;amp;hash 得到索引（hashmap的大小-1再&amp;amp;对象的hash）&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;5.HashMap的put操作过程：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 1. map.put(key, value)，首先计算key的hash，得到一个int值。     2.如果Node数组为空则初始化Node数组。这里注意，Node数组的长度length始终应该是2的n次方，比如默认的16, 还有32,64等     3.用 hash&amp;amp;(length-1) 运算得到数组下标，这里要提一句，其实正常我们最容易想到的，而且也是我之前很长一段时间以为的，这一步应该进行的是求模运算: hash % length ,这样得到的正好是0~length-1之间的值，可以作为数组的下标， 那么为何此处是位与运算呢？     先说结论： 上面提到数组的长度length始终是2^n,在这个前提下，hash &amp;amp; (length-1) 与hash % length是等价的。 而位与运算更快。这里后面会另开一遍进行详解。     4.  如果Node[hash&amp;amp;(length-1)]处为空，用传入的的key, value创建Node对象，直接放入该下标；如果该下标处不为空，且对象为TreeNode类型，证明此下标处的元素们是按照红黑树的结构存储的，将传入的key，value作为新的红黑树的节点插入到红黑树；否则，此处为链表，用next找到链表的末尾，将新的元素插入。如果在遍历链表的过程中发现链表的长度超过了8，此时如果数组长度&amp;lt;64则进行扩容，否则转红黑树。     5. 如果key的hash和key本身都相等则将该key对应的value更新为新的value     6. 需要扩容的话则进行扩容。 注意：     1. 如果key是null则返回的hash为0，也就是key为null的元素一直被放在数组下标为0的位置。     2. 在JDK 1.8以前，链表是采用的头部插入的方式，从1.8改成了在链表尾部插入新元素的方式。 这么做是为了防止在扩容的时候，多线程时出现循环链表死循环。具体会新开一遍进行详细演绎。  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;6.HashMap的get操作过程：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1. map.get(key). 首先计算key的hash。 2. 根据hash&amp;amp;(length-1)定位到Node数组中的一个下标。如果该下标的元素(也就是链表/红黑树的第一个元素)中 key的hash的key本身 都和传入的key相同，则证明找到了元素，直接返回即可。 3.如果第一个元素不是要找的，如果第一个元素的类型是TreeNode，则按照红黑树的查找方法查找元素，如果不是则证明是链表，按照next指针找下去，直到找到或者到达队尾。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;7.hashmap 数组长度为2的n次幂： &lt;/p&gt; &lt;p&gt; 7.1 计算索引时效率更高，%运算的速度并没有&amp;amp;的操作速度快。而&amp;amp;操作能代替%运算，必须满足一定的条件，也就是a%b=a&amp;amp;(b-1)仅当b是2的n次方的时候方能成立 &lt;/p&gt; &lt;p&gt;   7.2 扩容时重新计算索引的效率更高，hash &amp;amp; oldCap(旧容量) == 0的元素保留在原位置，否则新位置 = 旧位置 + oldCap(旧容量)&lt;/p&gt; &lt;p&gt;8.hashmap寻址方法： &lt;/p&gt; &lt;p&gt;   8.1 计算对象的hashCode() &lt;/p&gt; &lt;p&gt;   8.2 然后调用hash()进行二次哈希，然后右移16位进行异或运算，让哈希分布的均匀 3.0 最后 (capacity - 1) &amp;amp; hash 得到索引&lt;/p&gt; &lt;p&gt;9.&lt;a href="https://javaguide.cn/java/collection/java-collection-questions-02.html#hashmap-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%93%8D%E4%BD%9C%E5%AF%BC%E8%87%B4%E6%AD%BB%E5%BE%AA%E7%8E%AF%E9%97%AE%E9%A2%98" target="_blank"&gt;HashMap 多线程操作导致死循环问题&lt;/a&gt;&lt;/p&gt; &lt;p&gt;JDK1.7 及之前版本的 &lt;code&gt;HashMap&lt;/code&gt; 在多线程环境下扩容操作可能存在死循环问题，这是由于当一个桶位中有多个元素需要进行扩容时，多个线程同时对链表进行操作，头插法可能会导致链表中的节点指向错误的位置，从而形成一个环形链表&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;JDK1.8 版本的 HashMap 采用了尾插法而不是头插法来避免链表倒置，使得插入的节点永远都是放在链表的末尾，避免了链表中的环形结构。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;但是还是不建议在多线程下使用 &lt;code&gt;HashMap&lt;/code&gt;，因为多线程下使用 &lt;code&gt;HashMap&lt;/code&gt; 还是会存在数据覆盖的问题。并发环境下，推荐使用 &lt;code&gt;ConcurrentHashMap&lt;/code&gt;&lt;/strong&gt; &lt;/p&gt; &lt;p&gt;10.&lt;a href="https://javaguide.cn/java/collection/java-collection-questions-02.html#hashmap-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8" target="_blank"&gt;HashMap 为什么线程不安全？&lt;/a&gt;&lt;/p&gt; &lt;p&gt;JDK1.7 及之前版本，在多线程环境下，&lt;code&gt;HashMap&lt;/code&gt; 扩容时会造成死循环和数据丢失的问题。&lt;/p&gt; &lt;p&gt;JDK 1.8 后，在 &lt;code&gt;HashMap&lt;/code&gt; 中，多个键值对可能会被分配到同一个桶（bucket），并以链表或红黑树的形式存储。多个线程对 &lt;code&gt;HashMap&lt;/code&gt; 的 &lt;code&gt;put&lt;/code&gt; 操作会导致线程不安全，具体来说会有数据覆盖的风险。&lt;/p&gt; &lt;hr /&gt; &lt;h2&gt;(2).ConcurrentHashMap&lt;/h2&gt; &lt;p&gt;JDK1.7&lt;/p&gt; &lt;p&gt;&lt;img src="https://oss.javaguide.cn/github/javaguide/java/collection/java7_concurrenthashmap.png" alt="Java 7 ConcurrentHashMap 存储结构" title="Java 7 ConcurrentHashMap 存储结构" /&gt;&lt;/p&gt; &lt;p&gt;JDK1.8&lt;/p&gt; &lt;p&gt;&lt;img src="https://oss.javaguide.cn/github/javaguide/java/collection/java8_concurrenthashmap.png" alt="Java8 ConcurrentHashMap 存储结构（图片来自 javadoop）" title="Java8 ConcurrentHashMap 存储结构（图片来自 javadoop）" /&gt;&lt;/p&gt; &lt;p&gt;数据结构：数组+链表+红黑树&lt;/p&gt; &lt;p&gt;1.8之后采用cas控制数组节点的添加，synchronized锁定链表或者红黑树的首个节点。size在并发激烈的时候，采用数组形式.&lt;/p&gt; &lt;p&gt;怎么保证线程安全：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;在jdk1.7版本前使用分段锁（segment）实现的，每次访问资源在不同分段&lt;/li&gt; &lt;li&gt;在jdk1.8版本后使用的是node+数组加红黑树,采用的是cas+synchronized,cas负责主要创建node节点，synchronized主要负责插入值。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;a href="https://javaguide.cn/java/collection/java-collection-questions-02.html#concurrenthashmap-%E4%B8%BA%E4%BB%80%E4%B9%88-key-%E5%92%8C-value-%E4%B8%8D%E8%83%BD%E4%B8%BA-null" target="_blank"&gt;ConcurrentHashMap 为什么 key 和 value 不能为 null？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt; 的 key 和 value 不能为 null 主要是为了避免二义性。null 是一个特殊的值，表示没有对象或没有引用。如果你用 null 作为键，那么你就无法区分这个键是否存在于 &lt;code&gt;ConcurrentHashMap&lt;/code&gt; 中，还是根本没有这个键。同样，如果你用 null 作为值，那么你就无法区分这个值是否是真正存储在 &lt;code&gt;ConcurrentHashMap&lt;/code&gt; 中的，还是因为找不到对应的键而返回的。&lt;/p&gt; &lt;h3&gt;&lt;a href="https://javaguide.cn/java/collection/java-collection-questions-02.html#concurrenthashmap-%E8%83%BD%E4%BF%9D%E8%AF%81%E5%A4%8D%E5%90%88%E6%93%8D%E4%BD%9C%E7%9A%84%E5%8E%9F%E5%AD%90%E6%80%A7%E5%90%97" target="_blank"&gt;ConcurrentHashMap 能保证复合操作的原子性吗？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;复合操作是指由多个基本操作(如&lt;code&gt;put&lt;/code&gt;、&lt;code&gt;get&lt;/code&gt;、&lt;code&gt;remove&lt;/code&gt;、&lt;code&gt;containsKey&lt;/code&gt;等)组成的操作，例如先判断某个键是否存在&lt;code&gt;containsKey(key)&lt;/code&gt;，然后根据结果进行插入或更新&lt;code&gt;put(key, value)&lt;/code&gt;。这种操作在执行过程中可能会被其他线程打断，导致结果不符合预期。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;如 &lt;code&gt;putIfAbsent&lt;/code&gt;、&lt;code&gt;compute&lt;/code&gt;、&lt;code&gt;computeIfAbsent&lt;/code&gt; 、&lt;code&gt;computeIfPresent&lt;/code&gt;、&lt;code&gt;merge&lt;/code&gt;等&lt;/p&gt; &lt;h2&gt;(3).other&lt;/h2&gt; &lt;p&gt;HashMap与HashTable有什么区别？hashmap是线程不安全的，数组+链表+红黑树;hasttable是线程安全的，数组+链表，采用sync实现&lt;/p&gt; &lt;p&gt;&lt;strong&gt;fail-fast 机制&lt;/strong&gt;：多个线程对 fail-fast 集合进行修改的时候，可能会抛出:&lt;/p&gt; &lt;p&gt; &lt;strong&gt;fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时，就可能会产生fail-fast事件。其实fail-fast机制并不是Java集合特有的机制，它是一个通用的系统设计思想。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt; 通过反编译你会发现 foreach 语法底层其实还是依赖 &lt;code&gt;Iterator&lt;/code&gt; 。不过， &lt;code&gt;remove/add&lt;/code&gt; 操作直接调用的是集合自己的方法，而不是 &lt;code&gt;Iterator&lt;/code&gt; 的 &lt;code&gt;remove/add&lt;/code&gt;方法&lt;/p&gt; &lt;p&gt;这就导致 &lt;code&gt;Iterator&lt;/code&gt; 莫名其妙地发现自己有元素被 &lt;code&gt;remove/add&lt;/code&gt; ，然后，它就会抛出一个 &lt;code&gt;ConcurrentModificationException&lt;/code&gt; 来提示用户发生了并发修改异常。这就是单线程状态下产生的 &lt;strong&gt;fail-fast&lt;/strong&gt;&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;Java8 开始，可以使用 &lt;code&gt;Collection#removeIf()&lt;/code&gt;方法删除满足特定条件的元素,如&lt;/p&gt; &lt;h1&gt;(4).hashtable&lt;/h1&gt; &lt;p&gt;hash表，线程安全的,&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 05:56:00 GMT</pubDate>
    </item>
    <item>
      <title>JAVA基础</title>
      <link>http://www.yooyaa.cn/article/7</link>
      <content:encoded>&lt;p&gt;&lt;strong&gt;什么是jvm：J&lt;/strong&gt;ava 虚拟机（JVM）是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现（Windows，Linux，macOS），目的是使用相同的字节码，它们都会给出相同的结果&lt;/p&gt; &lt;h1&gt;1.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-01.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AF%B4-java-%E8%AF%AD%E8%A8%80-%E7%BC%96%E8%AF%91%E4%B8%8E%E8%A7%A3%E9%87%8A%E5%B9%B6%E5%AD%98" target="_blank"&gt;为什么说 Java 语言“编译与解释并存”？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt; 编译型：&lt;a href="https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E8%AA%9E%E8%A8%80" target="_blank"&gt;编译型语言&lt;/a&gt;会通过&lt;a href="https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8" target="_blank"&gt;编译器&lt;/a&gt;将源代码一次性翻译成可被该平台执行的机器码。一般情况下，编译语言的执行速度比较快，开发效率比较低。&lt;/p&gt; &lt;p&gt; 解释型：&lt;a href="https://zh.wikipedia.org/wiki/%E7%9B%B4%E8%AD%AF%E8%AA%9E%E8%A8%80" target="_blank"&gt;解释型语言open in new window&lt;/a&gt;会通过&lt;a href="https://zh.wikipedia.org/wiki/%E7%9B%B4%E8%AD%AF%E5%99%A8" target="_blank"&gt;解释器open in new window&lt;/a&gt;一句一句的将代码解释（interpret）为机器代码后再执行。解释型语言开发效率比较快，执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。&lt;/p&gt; &lt;p&gt; 这是因为 Java 语言既具有编译型语言的特征，也具有解释型语言的特征。因为 Java 程序要经过先编译，后解释两个步骤，由 Java 编写的程序需要先经过编译步骤，生成字节码（&lt;code&gt;.class&lt;/code&gt; 文件），这种字节码必须由 Java 解释器来解释执行&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;2.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-01.html#aot-%E6%9C%89%E4%BB%80%E4%B9%88%E4%BC%98%E7%82%B9-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%85%A8%E9%83%A8%E4%BD%BF%E7%94%A8-aot-%E5%91%A2" target="_blank"&gt;AOT 有什么优点？为什么不全部使用 AOT 呢？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;JDK 9 引入了一种新的编译模式 &lt;strong&gt;AOT(Ahead of Time Compilation)&lt;/strong&gt; 。和 JIT 不同的是，这种编译模式会在程序被执行前就将其编译成机器码，属于静态编译（C、 C++，Rust，Go 等语言就是静态编译）。AOT 避免了 JIT 预热等各方面的开销，可以提高 Java 程序的启动速度，避免预热时间长。并且，AOT 还能减少内存占用和增强 Java 程序的安全性（AOT 编译后的代码不容易被反编译和修改），特别适合云原生场景。&lt;/p&gt; &lt;p&gt;缺点：AOT 编译无法支持 Java 的一些动态特性，如反射、动态代理、动态加载、JNI（Java Native Interface）等。然而，很多框架和库（如 Spring、CGLIB）都用到了这些特性。如果只使用 AOT 编译，那就没办法使用这些框架和库了，或者说需要针对性地去做适配和优化&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;CGLIB 动态代理使用的是 ASM 技术，而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 &lt;code&gt;.class&lt;/code&gt; 文件，如果全部使用 AOT 提前编译，也就不能使用 ASM 技术了。为了支持类似的动态特性，所以选择使用 JIT 即时编译器。&lt;/p&gt; &lt;h1&gt;3.基本数据类型大小&lt;/h1&gt; &lt;p&gt;byte:1字节&lt;/p&gt; &lt;p&gt;short:2字节&lt;/p&gt; &lt;p&gt;int:4字节&lt;/p&gt; &lt;p&gt;long:8字节&lt;/p&gt; &lt;p&gt;char:2字节&lt;/p&gt; &lt;p&gt;float:4字节&lt;/p&gt; &lt;p&gt;double:8字节 &lt;/p&gt; &lt;p&gt;&lt;code&gt;Byte&lt;/code&gt;,&lt;code&gt;Short&lt;/code&gt;,&lt;code&gt;Integer&lt;/code&gt;,&lt;code&gt;Long&lt;/code&gt; 这 4 种包装类默认创建了数值 &lt;strong&gt;[-128，127]&lt;/strong&gt; 的相应类型的缓存数据，&lt;code&gt;Character&lt;/code&gt; 创建了数值在 &lt;strong&gt;[0,127]&lt;/strong&gt; 范围的缓存数据，&lt;code&gt;Boolean&lt;/code&gt; 直接返回 &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt;。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;strong&gt;为什么说是几乎所有对象实例都存在于堆中呢？&lt;/strong&gt; 这是因为 HotSpot 虚拟机引入了 JIT 优化之后，会对对象进行逃逸分析，如果发现某一个对象并没有逃逸到方法外部，那么就可能通过标量替换来实现栈上分配，而避免堆上分配内存&lt;/p&gt; &lt;h1&gt;4.字符型常量和字符串常量的区别?&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的 0 个或若干个字符&lt;/li&gt; &lt;li&gt;含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)&lt;/li&gt; &lt;li&gt;占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意： char 在 Java 中占两个字节)&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;5.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-02.html#%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%9B%B8%E7%AD%89%E5%92%8C%E5%BC%95%E7%94%A8%E7%9B%B8%E7%AD%89%E7%9A%84%E5%8C%BA%E5%88%AB" target="_blank"&gt;对象的相等和引用相等的区别&lt;/a&gt;&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;对象的相等一般比较的是内存中存放的内容是否相等。&lt;/li&gt; &lt;li&gt;引用相等一般比较的是他们指向的内存地址是否相等。&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;6.深拷贝 vs 浅拷贝vs引用拷贝&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;浅拷贝：对基本数据类型进行值传递，对引用数据类型进行引用传递般的拷贝，此为浅拷贝。&lt;/li&gt; &lt;li&gt;深拷贝：对基本数据类型进行值传递，对引用数据类型，创建一个新的对象，并复制其内容，此为深拷贝。&lt;/li&gt; &lt;li&gt;引用拷贝：简单来说，引用拷贝就是两个不同的引用指向同一个对象。&lt;/li&gt; &lt;li&gt;==clone接口实现：浅克隆：创建新的引用地址，基本数据类型值传递，引用数据类型引用传递==&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;7.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-02.html#object-%E7%B1%BB%E7%9A%84%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95%E6%9C%89%E5%93%AA%E4%BA%9B" target="_blank"&gt;Object 类的常见方法有哪些？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;getClass,clone,equals,hashCode,wait,join,toString,notify,notifyAll,finalize(实例被垃圾回收器回收的时候触发的操作)&lt;/p&gt; &lt;h1&gt;8.Java 序列化中如果有些字段不想进行序列化，怎么办？&lt;/h1&gt; &lt;p&gt;用关键字transient:阻止实例中那些用此关键字修饰的的变量序列化；当对象被反序列化时，被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量，不能修饰类和方法。&lt;/p&gt; &lt;h1&gt;9.s&lt;code&gt;tring&lt;/code&gt; 的底层实现由 &lt;code&gt;char[]&lt;/code&gt; 改成了 &lt;code&gt;byte[]&lt;/code&gt; ?&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;Java 9 为何要将 &lt;code&gt;String&lt;/code&gt; 的底层实现由 &lt;code&gt;char[]&lt;/code&gt; 改成了 &lt;code&gt;byte[]&lt;/code&gt; ?  编码占位问题&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;新版的 String 其实支持两个编码方案：Latin-1 和 UTF-16。&lt;/p&gt; &lt;h1&gt;10.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-02.html#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B8%B8%E9%87%8F%E6%B1%A0%E7%9A%84%E4%BD%9C%E7%94%A8%E4%BA%86%E8%A7%A3%E5%90%97" target="_blank"&gt;字符串常量池的作用了解吗？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;字符串常量池&lt;/strong&gt; 是 JVM 为了提升性能和减少内存消耗针对字符串（String 类）专门开辟的一块区域，主要目的是为了避免字符串的重复创建。&lt;/p&gt; &lt;h1&gt;11.字符型常量和字符串常量的区别?&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的 0 个或若干个字符&lt;/li&gt; &lt;li&gt;含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)&lt;/li&gt; &lt;li&gt;占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意： char 在 Java 中占两个字节),&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;12.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-02.html#string-intern-%E6%96%B9%E6%B3%95%E6%9C%89%E4%BB%80%E4%B9%88%E4%BD%9C%E7%94%A8" target="_blank"&gt;String#intern 方法有什么作用?&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;&lt;code&gt;String.intern()&lt;/code&gt; 是一个 native（本地）方法，其作用是将指定的字符串对象的引用保存在字符串常量池中，可以简单分为两种情况：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;如果字符串常量池中保存了对应的字符串对象的引用，就直接返回该引用。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果字符串常量池中没有保存了对应的字符串对象的引用，那就在常量池中创建一个指向该字符串对象的引用并返回。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;*   // 在堆中创建字符串对象”Java“     // 将字符串对象”Java“的引用保存在字符串常量池中     String s1 = &amp;quot;Java&amp;quot;;     // 直接返回字符串常量池中字符串对象”Java“对应的引用     String s2 = s1.intern();     // 会在堆中在单独创建一个字符串对象     String s3 = new String(&amp;quot;Java&amp;quot;);     // 直接返回字符串常量池中字符串对象”Java“对应的引用     String s4 = s3.intern();     // s1 和 s2 指向的是堆中的同一个对象     System.out.println(s1 == s2); // true     // s3 和 s4 指向的是堆中不同的对象     System.out.println(s3 == s4); // false     // s1 和 s4 指向的是堆中的同一个对象     System.out.println(s1 == s4); //true &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;13.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-03.html#exception-%E5%92%8C-error-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB" target="_blank"&gt;Exception 和 Error 有什么区别？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;所有的异常都有一个共同的祖先 &lt;code&gt;java.lang&lt;/code&gt; 包中的 &lt;code&gt;Throwable&lt;/code&gt; 类。&lt;code&gt;Throwable&lt;/code&gt; 类有两个重要的子类:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;Exception&lt;/code&gt;&lt;/strong&gt; :程序本身可以处理的异常，可以通过 &lt;code&gt;catch&lt;/code&gt; 来进行捕获。&lt;code&gt;Exception&lt;/code&gt; 又可以分为 Checked Exception (受检查异常，必须处理) 和 Unchecked Exception (不受检查异常，可以不处理)。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;Error&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;Error&lt;/code&gt; 属于程序无法处理的错误 ，我们没办法通过 &lt;code&gt;catch&lt;/code&gt; 来进行捕获不建议通过&lt;code&gt;catch&lt;/code&gt;捕获 。例如 Java 虚拟机运行错误（&lt;code&gt;Virtual MachineError&lt;/code&gt;）、虚拟机内存不够错误(&lt;code&gt;OutOfMemoryError&lt;/code&gt;)、类定义错误（&lt;code&gt;NoClassDefFoundError&lt;/code&gt;）等 。这些异常发生时，Java 虚拟机（JVM）一般会选择线程终止。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h3&gt;&lt;a href="#checked-exception-和-unchecked-exception-有什么区别" target="_blank"&gt;Checked Exception 和 Unchecked Exception 有什么区别？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;Checked Exception&lt;/strong&gt; 即 受检查异常 ，Java 代码在编译过程中，如果受检查异常没有被 &lt;code&gt;catch&lt;/code&gt;或者&lt;code&gt;throws&lt;/code&gt; 关键字处理的话，就没办法通过编译。&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;14.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-03.html#finally-%E4%B8%AD%E7%9A%84%E4%BB%A3%E7%A0%81%E4%B8%80%E5%AE%9A%E4%BC%9A%E6%89%A7%E8%A1%8C%E5%90%97" target="_blank"&gt;finally 中的代码一定会执行吗？&lt;/a&gt;&lt;/h1&gt; &lt;p&gt;不一定的！在某些情况下，finally 中的代码不会被执行。&lt;/p&gt; &lt;p&gt;就比如说 finally 之前虚拟机被终止运行的话，finally 中的代码就不会被执行。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;程序所在的线程死亡。&lt;/li&gt; &lt;li&gt;关闭 CPU。&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;15.泛型&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;Java 泛型（generics）是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制，该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型，也就是说所操作的数据类型被指定为一个参数。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;什么是泛型：泛型的本质就是参数化类型。也就是，将一个数据类型指定为参数。&lt;/p&gt; &lt;p&gt;泛型擦除： 泛型信息只存在于代码编译阶段，在进入 JVM 之前，与泛型相关的信息会被擦除掉，专业术语叫做类型擦除。&lt;/p&gt; &lt;p&gt;引入泛型有什么好处呢？&lt;/p&gt; &lt;h1&gt;16.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-03.html#spi" target="_blank"&gt;SPI&lt;/a&gt;机制&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;很多框架都使用了 Java 的 SPI 机制，比如：Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。&lt;/p&gt; &lt;h3&gt;&lt;a href="#spi-的优缺点" target="_blank"&gt;SPI 的优缺点？&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;通过 SPI 机制能够大大地提高接口设计的灵活性，但是 SPI 机制也存在一些缺点，比如：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;需要遍历加载所有的实现类，不能做到按需加载，这样效率还是相对较低的。&lt;/li&gt; &lt;li&gt;当多个 &lt;code&gt;ServiceLoader&lt;/code&gt; 同时 &lt;code&gt;load&lt;/code&gt; 时，会有并发问题。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;h1&gt;17.序列号&amp;amp;反序列化&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;（1）&lt;code&gt;serialVersionUID&lt;/code&gt;&lt;/h2&gt; &lt;p&gt;序列化号 &lt;code&gt;serialVersionUID&lt;/code&gt; 属于版本控制的作用。反序列化时，会检查 &lt;code&gt;serialVersionUID&lt;/code&gt; 是否和当前类的 &lt;code&gt;serialVersionUID&lt;/code&gt; 一致。如果 &lt;code&gt;serialVersionUID&lt;/code&gt; 不一致则会抛出 &lt;code&gt;InvalidClassException&lt;/code&gt; 异常。&lt;/p&gt; &lt;p&gt;推荐每个序列化类都手动指定其 &lt;code&gt;serialVersionUID&lt;/code&gt;，如果不手动指定，那么编译器会动态生成默认的 &lt;code&gt;serialVersionUID&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;serialVersionUID 不是被 static 变量修饰了吗？为什么还会被“序列化”？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;static&lt;/code&gt; 修饰的变量是静态变量，属于类而非类的实例，本身是不会被序列化的。然而，&lt;code&gt;serialVersionUID&lt;/code&gt; 是一个特例，&lt;code&gt;serialVersionUID&lt;/code&gt; 的序列化做了特殊处理。当一个对象被序列化时，&lt;code&gt;serialVersionUID&lt;/code&gt; 会被写入到序列化的二进制流中；在反序列化时，也会解析它并做一致性判断，以此来验证序列化对象的版本一致性。如果两者不匹配，反序列化过程将抛出 &lt;code&gt;InvalidClassException&lt;/code&gt;，因为这通常意味着序列化的类的定义已经发生了更改，可能不再兼容。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;也就是说，&lt;code&gt;serialVersionUID&lt;/code&gt; 只是用来被 JVM 识别，实际并没有被序列化。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;（2）如果有些字段不想进行序列化怎么办？&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;对于不想进行序列化的变量，可以使用 &lt;code&gt;transient&lt;/code&gt; 关键字修饰。&lt;/p&gt; &lt;p&gt;&lt;code&gt;transient&lt;/code&gt; 关键字的作用是：阻止实例中那些用此关键字修饰的的变量序列化；当对象被反序列化时，被 &lt;code&gt;transient&lt;/code&gt; 修饰的变量值不会被持久化和恢复。&lt;/p&gt; &lt;p&gt;关于 &lt;code&gt;transient&lt;/code&gt; 还有几点注意：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;transient&lt;/code&gt; 只能修饰变量，不能修饰类和方法。&lt;/li&gt; &lt;li&gt;&lt;code&gt;transient&lt;/code&gt; 修饰的变量，在反序列化后变量值将会被置成类型的默认值。例如，如果是修饰 &lt;code&gt;int&lt;/code&gt; 类型，那么反序列后结果就是 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt; &lt;li&gt;&lt;code&gt;static&lt;/code&gt; 变量因为不属于任何对象(Object)，所以无论有没有 &lt;code&gt;transient&lt;/code&gt; 关键字修饰，均不会被序列化。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;p&gt;&lt;strong&gt;为什么不推荐使用 JDK 自带的序列化？不支持跨语言调用；性能差；存在安全问题；&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;18.IO流&lt;/h1&gt; &lt;p&gt;IO 即 &lt;code&gt;Input/Output&lt;/code&gt;，输入和输出。数据输入到计算机内存的过程即输入，反之输出到外部存储（比如数据库，文件，远程主机）的过程即输出&lt;/p&gt; &lt;h2&gt;1.&lt;a href="https://javaguide.cn/java/basis/java-basic-questions-03.html#i-o-%E6%B5%81%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%88%86%E4%B8%BA%E5%AD%97%E8%8A%82%E6%B5%81%E5%92%8C%E5%AD%97%E7%AC%A6%E6%B5%81%E5%91%A2" target="_blank"&gt;I/O 流为什么要分为字节流和字符流呢?&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;问题本质想问：&lt;strong&gt;不管是文件读写还是网络发送接收，信息的最小存储单元都是字节，那为什么 I/O 流操作要分为字节流操作和字符流操作呢？&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;字符流是由 Java 虚拟机将字节转换得到的，这个过程还算是比较耗时；&lt;/li&gt; &lt;li&gt;如果我们不知道编码类型的话，使用字节流的过程中很容易出现乱码问题&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;19.java的Unsafe类&lt;/h1&gt; &lt;h1&gt;20.创建对象的三个步骤&lt;/h1&gt; &lt;p&gt; 1.类加载检查 &lt;/p&gt; &lt;p&gt; 2.分配内存空间&lt;/p&gt; &lt;p&gt; 3.类的初始化&lt;/p&gt; &lt;p&gt; 4.设置对象头&lt;/p&gt; &lt;p&gt; 5.执行init方式  &lt;/p&gt; &lt;h1&gt;21.成员变量与局部变量的区别有哪些？&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;从语法形式上看:成员变量是属于类的，而局部变量是在代码块或方法中定义的变量或是方法的参数；成员变量可以被 public,private,static 等修饰符所修饰，而局部变量不能被访问控制修饰符及 static 所修饰；但是，成员变量和局部变量都能被 final 所修饰。&lt;/li&gt; &lt;li&gt;从变量在内存中的存储方式来看:如果成员变量是使用static修饰的，那么这个成员变量是属于类的，如果没有使用static修饰，这个成员变量是属于实例的。而对象存在于堆内存，局部变量则存在于栈内存。&lt;/li&gt; &lt;li&gt;从变量在内存中的生存时间上看:成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动消失。&lt;/li&gt; &lt;li&gt;成员变量如果没有被赋初值:则会自动以类型的默认值而赋值（一种情况例外:被 final 修饰的成员变量也必须显式地赋值），而局部变量则不会自动赋值。&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;22.面向对象和面向过程的区别&lt;/h1&gt; &lt;p&gt;面向过程性能比面向对象高，是因为java属于半编译型语言，不是cpu可以直接运行的语言&lt;/p&gt; &lt;p&gt;面向对象 ：面向对象易维护、易复用、易扩展。&lt;/p&gt; &lt;h1&gt;23.==和 equals 的区别&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;==比较的是内存地址，基本数据则是值，equals比较的是引用对象的值&lt;/li&gt; &lt;li&gt;equals 没有被重写时等价于“==”，比较对象。&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;24、为什么 Java 中只有值传递？&lt;/h1&gt; &lt;p&gt;Java 程序设计语言对对象采用的不是引用调用，实际上，对象引用是按 值传递的。&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 11 Sep 2024 08:38:00 GMT</pubDate>
    </item>
  </channel>
</rss>

