沧海一粟

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

0%

The Structure of the Java Virtual Machine

  1. The class File Format
  2. Data Types
  3. Primitive Types and Values
  4. Reference Types and Values
  5. Run-Time Data Areas
  6. Frames
  7. Representation of Objects
  8. Floating-Point Arithmetic
  9. Special Methods
  10. Exceptions
  11. Instruction Set Summary
  12. Class Libraries
  13. Public Design, Private Implementation

  本文档指定了一台抽象机器。它没有描述Java虚拟机的任何特定实现。

  要正确实现Java虚拟机,您只需要能够读取类文件格式并正确执行其中指定的操作。不属于Java虚拟机规范的实现细节将不必要地限制实现者的创造力。例如,运行时数据区域的内存布局、使用的垃圾收集算法以及Java虚拟机指令的任何内部优化(例如,将它们转换为机器代码)都由实现者自行决定。

  本规范中对Unicode的所有引用都是针对Unicode标准6.0.0版提供的,可从http://www.unicode.org/。

阅读全文 »

redo log(重做日志)
binlog(归档日志)
undolog (回滚日志)

在电视剧里经常看到酒店掌柜有一个粉板,专门用来记录客人的赊账记录。
如果赊账的人不多,那么他可以把顾客名和账目写在板上。
但如果赊账的人多了,粉板记不下的时候,这个时候掌柜会用专门记录赊账的账本记录。
如果记错了,划掉就行

MySQL 里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。

 而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。

(1) 重要的日志模块:redo log (重做日志)

 1. 具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。

  1. 同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。

 3. 如果今天赊账的不多,掌柜可以等打烊后再整理。

  1. 但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。

(1.1) MySQL redo log 具体实现

 与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

redo log 重做日志

 write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

 write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

(1.2) redo log 相关配置

show variables like 'innodb_log%' 查看innodb_log相关配置

innodb_flush_log_at_trx_commit

innodb_flush_log_at_trx_commit InnoDB 引擎特有的,ib_logfile的刷新方式,取值 0 1 2

innodb_flush_log_at_trx_commit=0,表示延迟写
每次事务提交的时候写入 log buffer,然后每秒把log buffer刷到文件系统中(os buffer)去,并且调用文件系统的“flush”操作fsync()将缓存刷新到磁盘上去。
也就是说一秒之前的日志都保存在日志缓冲区,也就是内存上,如果机器宕掉,可能丢失1秒的事务数据。

innodb_flush_log_at_trx_commit=1,表示实时写,实时刷
在每次事务提交的时候,都把log buffer刷到文件系统中(os buffer)去,并且调用文件系统的“flush”操作fsync()将缓存刷新到redo log file中。
这种方式即使系统崩溃也不会丢失任何数据。 但是因为每次提交都写入磁盘,对IO的要求非常高,MySQL数据库的并发很快就会由于硬件IO问题而无法提升。

innodb_flush_log_at_trx_commit=2,表示实时写,延迟刷,
在每次事务提交的时候会把log buffer刷到文件系统os buffer,然后每秒调用系统的flush操作fsync()将os buffer中的日志写入redo log file
如果只是MySQL数据库挂掉了,由于文件系统没有问题,那么对应的事务数据并没有丢失。只有在数据库所在的主机操作系统损坏或者突然掉电的情况下,数据库的事务数据可能丢失1秒之类的事务数据。
这样的好处,减少了事务数据丢失的概率,而对底层硬件的IO要求也没有那么高(log buffer写到文件系统中,一般只是从log buffer的内存转移的文件系统的内存缓存中,对底层IO没有压力)。

innodb_log_file_size

innodb_log_file_size=1G
一般建议1G,可以根据压测结果或实际使用情况调整大小


(2) 重要的日志模块:binlog (归档日志)

 MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。

粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。

 最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。

而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。

这两种日志有以下三点不同。
 1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
 2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,把id=2的这一行c字段改成2;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 id=2 这一行的 c 字段加 1 ”。
 3. redo log 是循环写的,空间固定会用完; binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

 Tips
 Redo log不是记录数据页“更新之后的状态”,而是记录这个页 “做了什么改动”。
 Binlog有三种种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。
redo log 记录 做了什么改动(比如把某个字段从0改成了1)
binlog 记录 是怎么修改的(记录sql语句 或者 记录更新前后的行)

 有了对这两个日志的概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。

 1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
 2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
 3.引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
 4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
 5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。  

(2.1) binlog日志格式

binlog日志有三种格式,分别为 STATMENT、ROW 和 MIXED。
在 MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。

日志格式通过binlog-format指定。

(2.1.1) STATMENT

 基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。

  优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能;
  缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。

(2.1.2) ROW

 基于行的复制(row-based replication, RBR),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。

  优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题;
  缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨

(2.1.3) MIXED

 基于STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog

(2.2) binlog相关配置

(2.2.1) sync_binlog

sync_binlog:是MySQL 的二进制日志(binary log)同步到磁盘的频率。
取值:0-N

sync_binlog=0,当事务提交之后,MySQL不做fsync之类的磁盘同步指令刷新binlog_cache中的信息到磁盘,而让Filesystem自行决定什么时候来做同步,或者cache满了之后才同步到磁盘。这个是性能最好的。

sync_binlog=1,当每进行1次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。

sync_binlog=n,当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。

(3) 两阶段提交

更新数据时把对应的行数据读入内存
然后将对应行进行更新操作
更新完成后写入新行
新行更新到内存
写入 redo log,处于 prepare阶段
写binglog
提交事务,处于commit状态

 为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。要说明这个问题,我们得从文章开头的那个问题说起:怎样让数据库恢复到半个月内任意一秒的状态?

 前面我们说过了,binlog 会记录所有的逻辑操作,并且是采用“追加写”的形式。如果你的 DBA 承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有 binlog,同时系统会定期做整库备份。这里的“定期”取决于系统的重要性,可以是一天一备,也可以是一周一备。

 当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:
 1. 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
 2. 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
 这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。

 好了,说完了数据恢复过程,我们回来说说,为什么日志需要“两阶段提交”。这里不妨用反证法来进行解释。

 由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。

 仍然用前面的 update 语句 update T set c=c+1 where ID=2; 来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?

 先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。 但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。 然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。

 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

 可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

 不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用 binlog 来实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。

 简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

 sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;
binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。

Redo log不是记录数据页“更新之后的状态”,而是记录这个页 “做了什么改动”。
Binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。(row格式会导致日志变大)

binlog是可以关的,你如果有权限,可以set sql_log_bin=0关掉本线程的binlog日志。 所以只依赖binlog来恢复就靠不住。

innodb B+树主键索引的叶子节点存的是什么,存的是页
B+树的叶子节点是page (页),一个页里面可以存多个行

References

[1] 02 | 日志系统:一条SQL更新语句是如何执行的?
[2] 详细分析MySQL事务日志(redo log和undo log)
[3] 详细分析MySQL的日志(一)
[4] dev.mysql.com/doc/refman/5.7/en/server-logs.html
[5] dev.mysql.com/doc/refman/5.7/en/innodb-redo-log.html

什么是分布式ID

在我们系统数据量不大的时候,单库单表完全可以支撑现有系统,数据再大一点搞个MySQL主从同步读写分离也能对付。
但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;特别一点的如订单、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID。

分布式ID需要满足那些条件
全局唯一:必须保证ID是全局性唯一的,基本要求
高性能:高可用低延时,ID生成响应要块,否则反倒会成为瓶颈
高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性
好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
趋势递增:最好趋势递增,这个要求就得看具体场景了,一般不严格要求

有哪些生成方式

程序自增ID
关系型数据库自增ID
Redis

UUID

数据库多主模式
号段模式

雪花算法(SnowFlake)
滴滴出品(TinyID)
百度 (Uidgenerator)
美团(Leaf)

阅读全文 »

Java虚拟机是Java平台的基石。它是技术的组成部分,负责硬件和操作系统的独立性,编译代码的小巧大小以及保护用户免受恶意程序侵害的能力。

Java虚拟机是抽象的计算机。像真正的计算机一样,它具有指令集,并在运行时操作各种内存区域。使用虚拟机实现编程语言是相当普遍的。最知名的虚拟机可能是UCSD Pascal的P代码计算机。

Java虚拟机对Java编程语言一无所知,仅对特定的二进制格式(class文件格式)一无所知。一个class文件包含的Java虚拟机指令(或字节码)和符号表,以及其它辅助信息。

为了安全起见,Java虚拟机对class文件中的代码施加了严格的语法和结构约束。但是,classJava虚拟机可以托管任何可以用有效文件表示的功能的语言。受通用的,独立于机器的平台的吸引,其他语言的实现者可以将Java虚拟机用作其语言的交付工具。

References

https://docs.oracle.com/javase/specs/index.html
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
https://github.com/deephacks/awesome-jvm
https://github.com/graalvm/graal-jvmci-8
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javap.html

网络通信中,最底层的就是内核中的网络 I/O 模型了。

随着技术的发展,操作系统内核的网络模型衍生出了五种 I/O 模型,《UNIX 网络编程》一书将这五种 I/O 模型分为 阻塞式 I/O非阻塞式 I/OI/O 复用信号驱动式 I/O异步 I/O。每一种 I/O 模型的出现,都是基于前一种 I/O 模型的优化升级。

阅读全文 »

代码工具:jmh JMH是一个Java工具,用于构建、运行和分析nano/micro/mili/macro基准,这些基准是用Java和其他针对JVM的语言编写的。
Code Tools: jmh JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM.

JMH解决了什么问题

JMH怎么用

  1. 添加对应jar包
  2. 根据实际情况配置参数
  3. 编写代码运行

添加依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
</dependency>
阅读全文 »

Java 性能调优不像是学一门编程语言,无法通过直线式的思维来掌握和应用,它对于工程师的技术广度和深度都有着较高的要求。
互联网时代,一个简单的系统就囊括了应用程序、数据库、容器、操作系统、网络等技术,线上一旦出现性能问题,就可能要你协调多方面组件去进行优化,这就是技术广度;而很多性能问题呢,又隐藏得很深,可能因为一个小小的代码,也可能因为线程池的类型选择错误…可归根结底考验的还是我们对这项技术的了解程度,这就是技术深度。

我们调优的对象不是单一的应用服务,而是错综复杂的系统。应用服务的性能可能与操作系统、网络、数据库等组件相关,所以我们需要储备计算机组成原理、操作系统、网络协议以及数据库等基础知识。具体的性能问题往往还与传输、计算、存储数据等相关,那我们还需要储备数据结构、算法以及数学等基础知识。

如果你们公司做的是 12306 网站,不做系统性能优化就上线,试试看会是什么情况。

一款线上产品如果没有经过性能测试,那它就好比是一颗定时炸弹,你不知道它什么时候会出现问题,你也不清楚它能承受的极限在哪儿。

好的系统性能调优不仅仅可以提高系统的性能,还能为公司节省资源

阅读全文 »

(1) Actor

Actor模型:面向对象原生的并发模型

Actor模型是高性能网络中处理并行任务的一种方法,解决并发问题的利器

Actor模型本质上是一种计算模型,基本的计算单元称为 Actor,在 Actor 模型里,一切都是 Actor,所有的计算都是在 Actor 中执行的,并且 Actor 之间是完全隔离的,不会共享任何变量。

Actor模型解决了 传统编程假设与现代多线程、多CPU架构的现实之间的不匹配问题。

  1. 消息传递的使用避免了锁和阻塞。
  2. Actor能够优化地处理错误情况。

Java 语言本身并不支持 Actor 模型,所以如果你想在 Java 语言里使用 Actor 模型,就需要借助第三方类库,目前能完备地支持 Actor 模型而且比较成熟的类库就是 Akka。

阅读全文 »

Akka is a toolkit and runtime for building highly concurrent, distributed, and fault-tolerant event-driven applications on the JVM. Akka can be used with both Java and Scala. This guide introduces Akka by describing the Java version of the Hello World example. If you prefer to use Akka with Scala, switch to the Akka Quickstart with Scala guide.

Actors are the unit of execution in Akka. The Actor model is an abstraction that makes it easier to write correct concurrent, parallel and distributed systems. The Hello World example illustrates Akka basics. Within 30 minutes, you should be able to download and run the example and use this guide to understand how the example is constructed. This will get your feet wet, and hopefully inspire you to dive deeper into the wonderful sea of Akka!

解决了什么问题

Akka是Actor模型的一种实现
Actor模型解决了 传统编程假设与现代多线程、多CPU架构的现实之间的不匹配问题。

  1. 消息传递的使用避免了锁和阻塞。
  2. Actor能够优化地处理错误情况。

Usage of message passing avoids locking and blocking.
Actors handle error situations gracefully

OOP在构建苛刻需求的分布式系统会遇到的问题

  1. 并发情况下封装导致效率问题和死锁问题。
  2. 并发时共享内存失效(CPU缓存失效导致)导致效率低的问题。
  3. 调用栈导致的后台线程在异常时的通信问题以及后续问题。
  1. The challenge of encapsulation
  2. The illusion of shared memory on modern computer architectures
  3. The illusion of a call stack
阅读全文 »