希望可以帮助到大家
注意:三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的
所谓三次握手,即建立TCP连接,需要客户端和服务端总共发送至少三个包确认连接的建立。流程如下
第一次握手:
Client什么都不能确认
Server确认:自己接收正常 ,对方发送正常
第二次握手:
Client确认:自己发送/接收正常,对方发送/接收正常
Server确认:自己接收正常 ,对方发送正常
第三次握手:
Client确认:自己发送/接收正常, 对方发送/接收正常
Server确认:自己发送/接收正常,对方发送/接收正常
有这样一种情况,当A发送一个消息给B,但是由于网络原因,消息被阻塞在了某个节点,然后阻塞的时间超出设定的时间,A会认为这个消息丢失了,然后重新发送消息。
当A和B通信完成后,这个被A认为失效的消息,到达了B
对于B而言,以为这是一个新的请求链接消息,就向A发送确认,
对于A而言,它认为没有给B再次发送消息(因为上次的通话已经结束)所有A不会理睬B的这个确认,但是B则会一直等待A的消息
这就导致了B的时间被浪费(对于服务器而言,CPU等资源是一种浪费),这样是不可行的,这就是为什么不能两次握手的原因了。
第一次挥手:
Clien发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:
Server收到FIN后,发送一个ACK给Client,Server进入CLOSE_WAIT状态。
第三次挥手:
Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:
Client收到FIN后,Client进入TIME_WAIT状态,发送ACK给Server,Server进入CLOSED状态,完成四次握手。
四次挥手的本质原因是tcp是全双公的,通信是双向的, A到B是一个通道,B到A又是另一个通道。
A端确认没有数据发送后,发出结束报文,此时B端返回确认后,B端也不会接收A端数据。
但是此时B端可能还有数据没有传输完,A端还是可以接收数据。
只有当B端数据发送完之后,才能发出结束报文,并且确认A端接收到的时候,两边才会真正的断开连接,双方的读写分开。
MSL是TCP报文的最大生命周期,因为TIME_WAIT持续在2MSL就可以保证在两个传输方向上的尚未接收到或者迟到的报文段已经消失,否则服务器立即重启,可能会收到来自上一个进程迟到的数据,但是这种数据很可能是错误的,同时也是在理论上保证最后一个报文可靠到达,假设最后一个ACK丢失,那么服务器会再重发一个FIN,这是虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK。
1)首先当 Producer 向 Broker 发送数据后,会进行 commit,如果commit成功,由于 Replica 副本机制的存在,则意味着消息不会丢失,但是 Producer 发送数据给 Broker 后,遇到网络问题而造成通信中断,那么 Producer 就无法准确判断该消息是否已经被提交(commit),这就可能造成 at least once 语义。
2)在 Kafka 0.11.0.0 之前, 如果 Producer 没有收到消息 commit 的响应结果,它只能重新发送消息,确保消息已经被正确的传输到 Broker,重新发送的时候会将消息再次写入日志中;而在 0.11.0.0 版本之后, Producer 支持幂等传递选项,保证重新发送不会导致消息在日志出现重复。为了实现这个, Broker 为 Producer 分配了一个ID,并通过每条消息的序列号进行去重。也支持了类似事务语义来保证将消息发送到多个 Topic 分区中,保证所有消息要么都写入成功,要么都失败,这个主要用在 Topic 之间的
exactly once 语义。
3)从 Consumer 角度来剖析, 我们知道 Offset 是由 Consumer 自己来维护的, 如果 Consumer 收到消息后更新 Offset, 这时
Consumer 异常 crash 掉, 那么新的 Consumer 接管后再次重启消费,就会造成 at most once 语义(消息会丢,但不重复)。
4) 如果 Consumer 消费消息完成后, 再更新 Offset,如果这时 Consumer crash 掉,那么新的 Consumer 接管后重新用这个
Offset 拉取消息, 这时就会造成 at least once 语义(消息不丢,但被多次重复处理)。
静态单例模式是指单例对象在程序运行时就已经创建好,而不是在使用时动态创建。下面是一个实现静态单例模式的示例:静态单例模式是指单例对象在程序运行时就已经创建好,而不是在使用时动态创建。下面是一个实现静态单例模式的示例:
public class MySingleton { // 静态成员变量,存储单例对象 private static MySingleton instance = new MySingleton(); // 私有构造方法,防止其他类创建该类的实例 private MySingleton() { // 初始化操作 } // 静态方法,获取单例对象 public static MySingleton getInstance() { return instance; } }
Master
将数据改变记录到二进制日志(binary log
)中,也就是配置文件log-bin
指定的文件,这些记录叫做二进制日志事件(binary log events
);Slave
通过 I/O
线程读取 Master
中的 binary log events
并写入到它的中继日志(relay log
);Slave
重做中继日志中的事件,把中继日志中的事件信息一条一条的在本地执行一次,完成数据在本地的存储,从而实现将改变反映到它自己的数据。把一张表的数据分成多个区块,在逻辑上看最终只是一张表,但底层是由多个物理区块组成的。
MySQL的物理数据,存储在表空间文件(.ibdata1和.ibd)中,这里讲的分区的意思是指将同一表中不同行的记录分配到不同的物理文件中,几个分区就有几个.idb文件。
分表分为水平分表和垂直分表。
水平分表和分区很像,或者说分区就是水平分表的数据库实现版本,它们分的都是行记录,就像用一把刀,水平的将一个表切成多张表一样。
水平分表分的是行记录,而垂直分表,分的是列字段,它就像用一把刀,垂直的将一个表切成多张表一样。
垂直分表是基于列字段进行的。一般是表中的字段较多,或者有数据较大长度较长(比如text,blob,varchar(1000)以上的字段)的字段时,我们将不常用的,或者数据量大的字段拆分到“扩展表”上。这样避免查询时,数据量太大造成的“跨页”问题。
分库同样分为水平分库和垂直分库。
用join方法来保证线程顺序,其实就是让main这个主线程等待子线程结束,然后主线程再执行接下来的其他线程任务
这种方式的原理其实就是将线程用排队的方式扔进一个线程池里,让所有的任务以单线程的模式,按照FIFO先进先出、LIFO后进先出、优先级等特定顺序执行,但是这种方式也是存在缺点的,就是当一个线程被阻塞时,其它的线程都会受到影响被阻塞,不过依然都会按照自身调度来执行,只是会存在阻塞延迟。
在Java中,volatile
是一种关键字,用于告诉编译器和JVM,该变量的值可能被其他线程修改,因此每次访问该变量时都需要从内存中读取最新的值,而不是使用缓存的值。这可以确保多线程环境下该变量的值的一致性。
//使用了volatile public class VolatileDemo { public static volatile boolean stop = false;//任务是否停止,volatile变量 public static void main(String[] args) throws Exception { Thread thread1 = new Thread(() -> { while (!stop) { //stop=false,不满足停止条件,继续执行 //do someting } System.out.println("stop=true,满足停止条件。" + "停止时间:" + System.currentTimeMillis()); }); thread1.start(); Thread.sleep(100);//保证主线程修改stop=true,在子线程启动后执行。 stop = true; //true System.out.println("主线程设置停止标识 stop=true。" + "设置时间:" + System.currentTimeMillis()); } }
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果实现每个线程都有自己的专属变量该如何设置?ThreadLocal类主要就是解决让每个线程绑定自己的专属变量。
ThreadLocal主要用来存储当前上下文的变量信息,他可以保障存储进去的信息只能被当前的线程读取到,并且线程之间不会受到影响。ThreadLocal为变量在每个线程都创建了一个副本,那么每个线程可以访问自己的内部的副本变量。
1.ThreadLocal内存泄漏是指ThreadLocal对象中持有的变量副本没有被及时清理而导致的内存泄漏问题。当一个线程结束时,如果ThreadLocal对象中持有的变量副本没有被清理,这些变量副本会一直存在于内存中,无法被回收,从而导致内存泄漏。
解决方案:
ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法时会处理掉key为null的记录。使用ThreadLocal时,最好手动调用remove()方法。
2.上下文切换开销:每个线程都有自己的变量副本,当线程切换时,需要保存和恢复这些变量,这可能会带来一些上下文切换的开销。