Java并发 -- 问题源头
CPU、内存、IO设备
核心矛盾:三者的_速度差异_
为了合理利用CPU的高性能,平衡三者的速度差异,计算机体系结构、操作系统和编译程序都做出了贡献
计算机体系结构:CPU增加了缓存、以均衡CPU与内存的速度差异
操作系统:增加了进程、线程,分时复用CPU,以平衡CPU和IO设备的速度差异
编译程序:优化指令执行次序,使得缓存能够得到更加合理地利用
CPU缓存 -> 可见性问题单核
在单核时代,所有的线程都在一颗CPU上执行,CPU缓存与内存的数据一致性很容易解决
因为所有线程操作的都是同一个CPU的缓存,一个线程对CPU缓存的写,对另外一个线程来说一定是可见的
可见性:一个线程对共享变量的修改,另一个线程能够立即看到
多核
在多核时代,每颗CPU都有自己的缓存,此时CPU缓存与内存的数据一致性就没那么容易解决了
当多个线程在不同的CPU上执行时,操作的是不同的CPU缓存
线程A操作的是CPU-1上的缓存,线程B操作的是CPU-2上的缓存,此时线程A对变量V的操作对于线程B而言不具备可见性
代码验证123456789101112131415161718192021222324252 ...
Java并发 -- 概述
核心问题分工
JUC中的Executor、Fork/Join和Future本质上都是一种分工方法
并发编程领域还总结了一些设计模式,基本上都是和分工方法相关
生产者-消费者
Thread-Per-Message
Worker Thread
同步
在并发编程领域里的同步,主要指的就是_线程间的协作_
一个线程执行完了一个任务,如何通知执行后续任务的线程开始工作
协作一般是与分工相关的
JUC中的Executor、Fork/Join和Future本质上都是一种分工方法
但同时也能解决线程协作的问题
例如,用Future可以发起一个异步调用
当主线程调用get()方法取结果时,主线程会等待
当异步执行的结果返回时,get()方法就自动返回了
Future工具类已经帮我们解决了_主线程和异步线程之间的协作_
JUC中的CountDownLatch、CyclicBarrier、Phaser和Exchanger都是用来解决线程协作问题的
但很多场景还是需要自己处理线程之间的协作,问题基本可以描述为
当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行
在Java并发编程领域,解 ...
Kafka -- 可靠性
可靠性保证
可靠性保证:确保系统在各种不同的环境下能够发生一致的行为
Kafka的保证
保证_分区消息的顺序_
如果使用同一个生产者往同一个分区写入消息,而且消息B在消息A之后写入
那么Kafka可以保证消息B的偏移量比消息A的偏移量大,而且消费者会先读取消息A再读取消息B
只有当消息被写入分区的所有同步副本时(文件系统缓存),它才被认为是已提交
生产者可以选择接收不同类型的确认,控制参数acks
只要还有一个副本是活跃的,那么已提交的消息就不会丢失
消费者只能读取已经提交的消息
复制
Kafka可靠性保证的核心:_复制机制_ + 分区的多副本架构
把消息写入多个副本,可以使Kafka在发生崩溃时仍能保证消息的持久性
Kafka的主题被分成多个分区,分区是基本的数据块,分区存储在单个磁盘上
Kafka可以保证分区里的事件总是有序的,分区可以在线(可用),也可以离线(不可用)
每个分区可以有多个副本,其中一个副本是首领副本
所有的事件都直接发送给首领副本,或者直接从首领副本读取事件
其他副本只需要与首领副本保持同步,并及时复制最新的事件即可
当首领副本不可用时,其中一个同步副本将成为新的首领
...
Kafka -- 内部原理
群组成员关系
Kakfa使用ZooKeeper来维护集群成员的信息
每个Broker都有一个唯一的ID,这个ID可以在配置文件里面指定,也可以自动生成
在Broker启动的时候,通过创建临时节点把自己的ID注册到ZooKeeper
Kakfa组件订阅ZooKeeper的/brokers/ids路径,当有Broker加入集群或者退出集群时,Kafka组件能获得通知
如果要启动另一个具有相同ID的Broker,会得到一个错误,这个Broker会尝试进行注册,但会失败
在Broker停机,出现网络分区或者长时间垃圾回收停顿时,Broker会从ZooKeeper上_断开连接_
此时,Broker在启动时创建的临时节点会从ZooKeeper上自动移除(ZooKeeper特性)
订阅Broker列表的Kafka组件会被告知该Broker已经被移除
在关闭Broker时,它对应的临时节点也会消失,不过它的ID会继续存在于其他数据结构中
例如,主题的副本列表里可能会包含这些ID
在完全关闭了一个Broker之后,如果使用相同的ID启动另一个全新的Broker
该Broker会立即加入集群,并拥有与旧Broker相同的 ...
Kafka -- Docker + Schema Registry
Avro
Avro的数据文件里包含了整个Schema
如果每条Kafka记录都嵌入了Schema,会让记录的大小成倍地增加
在读取记录时,仍然需要读到整个Schema,所以需要先找到Schema
可以采用通用的结构模式并使用Schema注册表的方案
开源的Schema注册表实现:Confluent Schema Registry
Confluent Schema Registry
把所有写入数据需要用到的Schema保存在注册表里,然后在_记录里引用Schema ID_
负责读数据的应用程序使用Schema ID从注册表拉取Schema来反序列化记录
序列化器和反序列化器分别负责处理Schema的注册和拉取
Confluent Schema Registry1234567891011121314151617181920# Start Zookeeper and expose port 2181 for use by the host machine$ docker run -d --name zookeeper -p 2181:2181 confluent/zookeeper# Start K ...
MySQL -- 自增ID耗尽
显示定义ID表定义的自增值ID达到上限后,在申请下一个ID时,得到的值保持不变
123456789101112131415161718-- (2^32-1) = 4,294,967,295-- 建议使用 BIGINT UNSIGNEDCREATE TABLE t (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=4294967295;INSERT INTO t VALUES (null);-- AUTO_INCREMENT没有改变mysql> SHOW CREATE TABLE t;+-------+------------------------------------------------------+| Table | Create Table |+-------+------------------------------------------------------+| t | CREATE TABLE `t` ( `id` int ...
MySQL -- 分区表
表初始化1234567891011121314CREATE TABLE `t` ( `ftime` DATETIME NOT NULL, `c` int(11) DEFAULT NULL, KEY (`ftime`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 PARTITION BY RANGE (YEAR(ftime)) (PARTITION p_2017 VALUES LESS THAN (2017) ENGINE = InnoDB, PARTITION p_2018 VALUES LESS THAN (2018) ENGINE = InnoDB, PARTITION p_2019 VALUES LESS THAN (2019) ENGINE = InnoDB, PARTITION p_others VALUES LESS THAN MAXVALUE ENGINE = InnoDB);INSERT INTO t VALUES ('2017-4-1',1),('2018-4-1',1);mys ...
MySQL -- 权限
创建用户1CREATE USER 'ua'@'%' IDENTIFIED BY 'pa';
用户名+地址才表示一个用户,ua@ip1和ua@ip2代表的是两个不同的用户
在磁盘上,往mysql.user表里插入一行,由于没有指定权限,所有表示权限的字段都是N
在内存里,往数组acl_users里插入一个acl_user对象,该对象的access字段的值为0
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950mysql> SELECT * FROM mysql.user WHERE user = 'ua'\G;*************************** 1. row *************************** Host: % User: ua Select_priv: N ...
MySQL -- 拷贝表
初始化123456789101112131415161718192021CREATE DATABASE db1;USE db1;CREATE TABLE t(id INT PRIMARY KEY, a INT, b INT, INDEX(a)) ENGINE=InnoDB;DELIMITER ;;CREATE PROCEDURE idata()BEGIN DECLARE i INT; SET i=1; WHILE (i <= 1000) DO INSERT INTO t VALUES (i,i,i); SET i=i+1; END WHILE;END;;DELIMITER ;CALL idata();CREATE DATABASE db2;CREATE TABLE db2.t LIKE db1.t;-- 目标:把db1.t里面a>900的数据导出来,插入到db2.t
mysqldump12345$ mysqldump -uroot --add-locks=0 --no-create-info --single-transaction -- ...
MySQL -- INSERT语句的锁
INSERT…SELECT表初始化1234567891011121314CREATE TABLE `t` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `c` INT(11) DEFAULT NULL, `d` INT(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`)) ENGINE=InnoDB;INSERT INTO t VALUES (null,1,1);INSERT INTO t VALUES (null,2,2);INSERT INTO t VALUES (null,3,3);INSERT INTO t VALUES (null,4,4);CREATE TABLE t2 LIKE t;
操作序列
时刻
session A
session B
T1
BEGIN;
T2
INSERT INTO t2(c,d) SELECT c,d FROM t;
T3
INSERT INTO t VALUES (-1,-1,-1);(Blocked)
1234567-- T3 ...