[WIP] 随手记
1. MySQL5.6的两种复制方式对比,这里只记录一些常见的,详细的请查阅原文
statement based replication(SBR):
- 当写操作涉及到的行数较多时,可以有效减少日志大小,从而理论上可以减少binlog写入时间,特别当写操作涉及到lob字段
row based replication(RBR):
- 对于需要用到行锁的statement,在master和slave上都有更高的并发性
- 比SBR更安全,避免SBR中不确定的行为,例如update或delete时使用了limit但未使用order by,或者使用了一些系统内置的function如UUID()
- 对于比较复杂但涉及到的行数较少的statement,RBR有更好的性能
2. MySQL slave中的几种log:relay log用作保存master binlog中获取到的events,然后由slave sql thread执行;relay info log用作保存slave中relay log的执行状态;master info log用作保存master的连接信息以及master binlog的position
3. MQ有两种常见的使用场景,一种是队列模式,即一个消息只能被一个消费者消费,另一种是pub/sub模式,即一个消息可以被一组消费者消费(订阅了同一个topic)。
Consumer group是Kafka中最小的消费单位,队列模式通过consumer N:1 consumer group实现,P/S模式通过consumer 1:1 consumer group实现;而NSQ中有相似的概念,叫channel,即topic 1:N channel,同一个channel的消费者只有一个会收到topic中某一个partition的消息。
由于kafka采用pull而NSQ采用push的消息推送方式,因此,kafka服务端需要为每一个consumer group创建一个coordinator来保存该组消息消费状态,但具体的分区分配任务则由主消费者负责;而NSQ服务端channel既要保存消息消费状态,也要负责partition的分配。
消费线程与partition(同一个topic)的关系有三种,并发数与此有关: 消费线程数 > 分区数:多余的消费线程不会收到消息,除非consumer group发生变动(consumer加入或退出) 消费线程数 = 分区数:一个消费线程对应一个分区 消费线程数 < 分区数:一个消费线程可能对应多个分区
4. 选举算法:bully算法相较于Paxos算法实现简单,但需要规则约束(只有分区节点数大于集群节点数的1/2才可以进行选举)以解决脑裂问题;同时由于bully算法常常基于约定例如选取集群中最小节点,而由于最小节点可能会出现变化,从而会导致频繁发生选举。
5. CMS重新标记阶段:由于用户线程与GC线程并行,因此在并行标记阶段可能出现用户线程修改引用关系。为了解决在接下来的并发清理阶段误删可能性,需要在该阶段将用户线程的引用修改同步到GC引用链中,因此用write barrier记录对已标记过的引用的修改信息。
6. 如何避免YoungGC扫描全堆,关键点在于被引用方(GC区域)需要知道incoming reference,对于传统分代GC来说就是如何记录oldGen对youngGen的引用,对于G1来说便是如何记录region对region的引用。cardTable便是一种特殊的rememberedSet,通过card记录引用信息,扫描时只需要找到被标记的card然后通过引用方查找outgoing reference就相当于实现了被引用方的incoming reference。
7. Kafka的exactly once的语义,可以简单区分为消息生产的实现和消息消费的实现。
生产消息的exactly once需要producer与broker共同保证,简单来说,对于发往同一个partition的一批消息,通过增加一个唯一标识,broker端发现收到重复的消息则做丢弃处理;对于发往不同partition的一批消息则由事务保证,broker可以保证消息写入操作的原子性,即该批消息如果有一个写入失败则整个回滚。(为了向后兼容,事务隔离级别有两种,一种是read comitted,一种是read uncommitted)
消费消息的exactly once只需要consumer端通过两阶段提交协议保证offset commit和message consume是原子操作。
8. Kafka服务端的故障容错由一主多从的副本机制保证,副本的单位为partition而不是topic,这么做很容易理解,毕竟Kafka的基本消息生产和消费单位是parition,topic只是一个逻辑概念。(同一个partition的副本数量并不一定等于节点数量)
9. SSTable的定义为按key排序的key-value文件,按key排序跟按写入时间排序是两回事,但是可以通过MemTable来保证按key排序也是顺序写入的,同时单个SSTable文件可以通过压缩保证key只出现一次。
LSM在基于SSTable的存储格式上提供了对多个文件的合并,但因此会带来写放大问题(读取key的时候可能会查找多个SSTable文件也会造成读放大问题)。LSM本身只能保证存在的key可以被搜索到,至于不存在的key需要通过额外的优化手段避免性能下降,比如bloomFilter。
10. 再谈DCL
synchronized保证的可见性指的是sync block中的变量对于获取到同一个monitor的线程是可见的。Java内存模型保证的是在unlock之前会把sync block中的所有变量写回到主内存中(write),但不保证何时写入,所以没有加volatile的变量可能在其他线程里读到一个未被完全初始化过的对象(虽然引用不为null).
11. redo/undo日志只要求日志(形如<T, V, o, n>
)在修改磁盘上的数据库元素之前落盘(WAL),而不要求<COMMIT T>
日志记录和数据落盘的先后顺序,当出现故障需要进行状态恢复时,需要按照从前向后redo以及从后向前undo。
事务提交(形如<COMMIT T>
)的日志如果不能立即落盘,在遇到故障时用户认为已提交的事务被数据库系统认为是需要被撤销的,因此附加规则为一旦出现提交日志要立刻FLUSH LOG。
redo/undo的check point要求在start和end期间将缓冲区的修改落盘,因此在做状态恢复时,查找到的检查点日志如果是<END CKPT>
需要找到所有之后COMMIT的事务redo以及在此之前但没有COMMIT的事务undo,如果是<START CKPT(T)>
则需要找到前一个<END CKPT>
之后的所有COMMIT的事务进行redo以及没有COMMIT的事务进行undo。
相较于undo日志,不强制要求事务提交时将数据库元素修改记录落盘,相较于redo日志,不强制要求数据库元素修改记录必须在事务提交之后再落盘,否则缓冲区在遇到长事务可能会出现不够用的情况。
12. 数据库事务的几种并发控制
- 两阶段封锁:即为事务操作的元素加锁,事务完成后解锁,根据读操作写操作区分为不同的锁模式,例如共享锁,排他锁,更新锁,增量锁
- 时间戳:为每一个事务设置一个根据时间增长的sequence,为数据库元素设置最近读时间,最近写时间以及提交位。提交位的作用在于防止读到其他事务未提交的元素值。 由于简单的时间戳实现中,每个元素都只有一个值,意味着一旦发生过晚的读就会导致该事务发生回滚,对于实际只读事务较多的情况下,增加了多版本时间戳,即为元素设置多个版本,每个版本与其被写的时间戳相关,当发生过晚的读情况时,只需要根据事务开始的时间戳查找对应的旧版本元素值就可以避免回滚。当所有活跃事务的时间戳都大于元素某个版本的写时间,那么该版本就可以删除了。
- 有效性确认:与时间戳不同,有效性确认的调度器只为每个活跃事务维护读写集合,事务执行分为三个阶段,读阶段,有效性确认阶段,写阶段。
13. Java中lambda的实现是为lambda内部表达式生成method,然后通过invokedynamic调用这个方法
Java编译器没有选择通过内部类来实现lambda语法糖,主要原因有:1. 需要生成额外的内部类,类加载需要花费时间;2. 内部类会占用meta space;3. 内部类虽然是无状态的,但是其object对象还是会占用一定的内存大小(包括对外部类的引用);4. 会限制以后对lambda的扩展和进一步优化
14. !!!在分布式环境中,基于本地时间戳的设计需要额外考虑时钟可能会被NTP server重置,意味着一旦时钟被拨回,可能会出现不可预料的冲突。
15. NWR模型:N指节点数量,W指最少写入节点数量,R指最少读取节点数量。
当W=1,R=N,对写操作要求高可用,但不保证强一致性; 当W=N,R=1,保证了强一致性,但可用性不高。