Disruptor高性能的另一个实现机制为 “缓存行填充”,它解决了CPU访问内存变量的“伪共享”问题。

什么是伪共享?

在解释什么是伪共享之前,先了解下数据在缓存中是如何存储的。

我们都知道,计算机为了解决CPU与主存之间速度差的问题,引入了多级缓存机制。

cache-1.png

事实上,数据在CPU缓存(多级cache)中并非是单独存储的,而是按行存储的。其中每一行成为一个缓存行。

cache-2.png

缓存行是CPU的Cache与主内存进行数据交换的基本单位,每个缓存行的大小一般为2的N次方字节。(在32位计算机中为32字节,64位计算机中为64字节。)可以想到,如果计算机为128位,则缓存行大小就是128字节。

在Java中,一个long型变量为8字节,也就是说在64位计算机中,每行可存放8个long型变量。

当CPU访问某个变量的时,如果CPU Cache中存在该变量,则直接获取。若不存在则去主内存获取该变量。由于缓存行机制的存在,因此会将该变量所在内存区域为一个缓存行大小的内存复制到CPU Cache中。

此时有可能会在一行缓存行中加载多个变量,如图中不同的颜色对应不同的long型变量。

cache-3.png

试想,如果多个内核的线程都操作了同一缓存行的数据,如图所示。CPU1读取并修改了缓存行中的变量D,了解volatile的同学都知道,当CPU Cache中的变量发生变更,会通过缓存一致性协议通知其他CPU失效当前缓存行,重新从主内存中加载当前行的值。

expire.png

图中,CPU1修改了缓存行中的变量D,CPU2也在读取该缓存行的值。根据缓存一致性协议,CPU2中的缓存行会失效,因为它操作的缓存行中的变量D的值已经不是最新值了。

这是因为CPU是以缓存行为单位进行数据的读写操作的。

这就是伪共享。

为什么是“伪”共享呢?

看起来CPU1 与 CPU2 共享了同一个缓存行,但是由于CPU以缓存行为单位进行读写操作,无论CPU1 与 CPU2中的任何一位修改了缓存行中的值,都需要通知其他CPU对失效该缓存行。也就是说当线程对缓存进行了写操作,则当前线程所在内核就需要失效其他内核的缓存行,并重新加载主内存。

这是一种缓存未命中的情况,当发生这样的情况,缓存本身的意义就被削弱了,因为CPU始终需要从主内存加载数据,而根本命中不了CPU Cache中的缓存。

所谓的“伪”共享,就可以理解成是一种 “错误”的共享,这种共享如果不发生,则多核CPU操作缓存行互不影响,每个核心都只关心自己操作的变量,而不会因为读写自己关心的变量而影响到其他CPU对变量的读写。

Disruptor是如何进行缓存行填充的?

Disruptor解决伪共享的方式为:使用缓存行填充。

上文我们提到,由于多核CPU同时读写统一缓存行中的数据,导致了CPU Cache命中失败的伪共享问题。

那么只需要避免多核CPU同时操作统一缓存行,不就可以解决这个问题了么?

事实上,Disruptor正是这么做的。

Disruptor为Sequence中的value(volatile修饰)进行了缓存行填充,保证每个sequence只在一个缓存行中存在,避免了其他的变量对sequence的干扰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class LhsPadding
{
protected long p1, p2, p3, p4, p5, p6, p7; // 缓存行填充
}
class Value extends LhsPadding
{
protected volatile long value;
}
class RhsPadding extends Value
{
protected long p9, p10, p11, p12, p13, p14, p15; // 缓存行填充
}
/**
* <p>Concurrent sequence class used for tracking the progress of
* the ring buffer and event processors. Support a number
* of concurrent operations including CAS and order writes.
*
* <p>Also attempts to be more efficient with regards to false
* sharing by adding padding around the volatile field.
*/
public class Sequence extends RhsPadding
{
static final long INITIAL_VALUE = -1L;
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
static
{
UNSAFE = Util.getUnsafe();
try
{
VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
}
catch (final Exception e)
{
throw new RuntimeException(e);
}
}

其他的缓存行填充机制

JDK1.8 提供了注解 @Contended 用于解决伪共享问题,需要注意的是,如果业务代码需要使用该注解,要添加JVM参数

-XX:-RestrictContended。

默认填充宽度为128,若需要自定义填充宽度,则设置

-XX:ContendedPaddingWidth

具体的使用方式为:

1
2
3
4
@sun.misc.Contended
public final static class Value {
public volatile long value = 0L;
}

参考资料

  • 《Java并发编程之美》
  • 并发编程网:剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

在之前的文章中,我们讨论了Disruptor高性能实现机制中的:

  • RingBuffer环形队列及内存预加载
  • 缓存行填充避免伪共享

本文开始之前先对之前没有讲到的细节进行补充。

对于数组元素预加载的补充解释

private void fill(EventFactory<E> eventFactory)
{
    for (int i = 0; i < bufferSize; i++)
    {
        entries[BUFFER_PAD + i] = eventFactory.newInstance();
    }
}

一次性填充慢整个数组,这样做是一个比较有技巧的做法,Disruptor通过填充慢数组,在运行时改变对象的值来达到防止Java垃圾回收(GC)产生的系统开销。

换句话说就是它不需要垃圾回收。

Read More

Disruptor,一款超高性能、超低延迟的并发编程框架。这里用了两个“超”来突出它在性能上的优越性。

它的性能远远超过了传统并发编程基于锁同步,阻塞队列的实现方案,在高性能后端编程中,disruptor是一个不错的选择。

Disruptor从何而来?

Disruptor的爆火起源于软件开发大师 martin fowler(马丁富勒)在自己网站上一篇文章,原文链接 文章介绍了外汇交易平台LMAX使用并开源的一种架构方案。

LMAX使用该方案实现了难以置信的 “单线程每秒处理600w订单” 的惊人能力。业务处理逻辑基于 完全运行内存运行 + 事件溯源 方式驱动。

Disruptor目前已经被LMAX开源,github地址 https://github.com/LMAX-Exchange/disruptor

Disruptor有何特点?

Disruptor性能优越,必然有其设计上的独到之处,一般来说,我们认为Disruptor有以下特点:

  • Disruptor是面向并发编程的高性能框架,它在开发上简化了并发程序编码难度,性能上也是JUC并发包的数倍乃至十几倍;
  • Disruptor是CPU友好的、无锁的,基于单线程方式对任务进行调度,减少了上下文切换对系统资源的开销;
  • Disruptor底层数据结构基于数组,通过预加载方式提前加载对象到内存;Disruptor不会清理缓存中的数据,而是通过覆盖对象属性方式实现数据的读写,这降低了GC频率,使得系统资源的使用趋于平稳;
  • Disruptor能够避免“伪共享”,通过缓存行填充机制,Disruptor避免了伪共享对并发读写变量的消耗,消除了不必要缓存未命中。



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

金融是这个世界运转不可或缺的部分,学习一点儿金融交易知识也是我们了解世界运行规律的一种方式。本系列“金融交易101”主要目的为普及一些金融交易相关的基础概念,不作为任何的投资建议与参考。希望我们在了解了这些金融证券交易的知识之后,能够明辨风险,谨慎理财。

股市,顾名思义,股票市场。核心在于股票,市场的运转围绕着股票这一核心概念展开。

Read More

本来想起个题目叫 “什么?你还没用过这个工具?” 或者 “再见,火焰图”。但是想了想,己所不欲,勿施于人。

正常写个题目就好了,非搞这么多噱头,就是为了所谓的阅读量和点击量。

如果内容是干货,对人有帮助还好,要是满怀期待打开,进去划拉划拉,越看越不对劲,最后拉到末尾才近乎“上当了”,又是可恶的推广软文,这种感觉就如同吃了翔味的巧克力一样,让人反胃。

言归正传,今天来聊聊性能调优利器,火焰图的安装、使用及分析方法。

火焰图(Flame Graph)是由 Linux 性能优化大师 Brendan Gregg 发明的,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能导致性能瓶颈的调用栈。

先看一个火焰图的样例,看不懂不要紧,后文会解释怎么去理解,稍安勿躁。

fire.PNG

安装

要获得火焰图,需要安装一套组件,核心的组件主要有

  • async-profiler : 性能数据采集
  • FlameGraph : 性能数据分析,并生成火焰图

接下来按顺序讲解安装过程,读者朋友如果严格按照这个步骤来操作,一般都没有问题。

原料准备

至少需要有一套linux机器,笔者用的是centos-7。

可以搞个阿里云的ecs,也可以搞个虚拟机,当然直接在公司机器上操作也未尝不可,但是要注意安全,root权限下操作要小心(你懂的)。

环境准备

在正式安装之前,先确保环境已经准备好。否则环境搞了半天搞不定,直接放弃,打出GG。

至少保证jdk、perl、c++编译器已经安装完毕。

  1. 安装Java,不用多说了哇,网上教程一大堆。

yum -y install git

  1. 安装GCC编译器

yum install gcc gcc-c++

如果去搜索一下火焰图的其他教程,你可以会发现,有些教程让你安装 perf,我也试过了,装了perf,然后用perf-map-agent去搜集性能数据,直接失败了。

换成async-profiler,直接一次成功。不想踩坑的同学,直接用async-profiler吧,省心。

获取源码

很多教程都说,要安装git,然后使用git clone方式下载源码,但是他们没有交代的是,除了git安装外,你还需要经过配置才可以实现源码下载。

其实源码获取有更简便的方式,或许也不太简便吧,管他呢。

安装lrzsz,用于上传文件

1
yum install -y lrzsz

安装完成之后,获取async-profiler源码包。

进入async-profiler源码地址,https://github.com/jvm-profiling-tools/async-profiler, 微信禁止外链,读者可以自行复制粘贴至浏览器访问。

download-async.PNG

点击图中红线圈住的 “download zip” ,待下载完成之后,通过 sz -y 方式上传源码包。

upload-async.PNG

使用相同的方式,将FlameGraph的源码包也上传至服务器。

进入FlameGraph源码地址,https://github.com/brendangregg/FlameGraph 下载源码压缩包,也一并上传至服务器。

具体的图不贴了,和上面一模一样。

安装async-profiler

接着就到重头戏了,首先介绍如何安装async-profiler。

进入async-profiler源码上传路径,解压源码(直接unzip async-profiler.zip),进入解压后的文件目录。

1
2
cd async-profiler
make

make完成之后,等待安装完成即可。

安装FlameGraph

实际上FlameGraph也无需安装,将代码上传并解压,就可以直接使用。

【实战】分析Java性能,生成火焰图

接着讲一个实战案例。

首先,要有一个分析目标程序,我在服务器上部署了一个基于netty的im聊天demo,同时启动服务端与客户端。

启动服务端:

nohup java -jar im-server-1.0.0-jar-with-dependencies.jar > Log.log 2>&1 &

启动客户端,指定服务端地址(客户端与服务端在同一个机器)

java -jar -Dremote_ip=127.0.0.1 im-client-1.0.0-jar-with-dependencies.jar

启动性能分析

启动性能分析,持续收集一分钟服务端性能指标。

先获取服务端的PID:

1
2
3
[root@snowalker ~]# ps -ef | grep java
root 9349 1 0 Feb11 ? 00:09:40 java -jar im-server-1.0.0-jar-with-dependencies.jar

PID为9349,接着启动async-profiler收集java进程的性能指标。

./profiler.sh -d 60 -o collapsed -f /tmp/test_01.txt ${pid}

简单解释下,

  • -d表示的是持续时长,60代表持续采集时间60s;
  • -o表示的是采集规范,这里用的是collapsed;
  • -f后面的路径,表示的是数据采集后生成的数据存放的文件路径(这里放在了/tmp/test_01.txt)
  • ${pid} 表示的是采集目标进程的pid,也就是上面提到的30937

pid为进程实际的进程id,这里就是9349,那么只需要执行命令:

1
2
./profiler.sh -d 60 -o collapsed -f /tmp/test_02.txt 9349

运行期间,处于阻塞状态,直到约定时间完成。

运行期间,接着模拟用户聊天,连续发送消息至服务端:

send-message.PNG

性能数据收集结束之后,到/tmp/ 查看输出的性能指标文件:

1
2
3
[root@snowalker tmp]# ll
-rw-r--r-- 1 root root 6008 Feb 22 12:45 test_02.txt

可以看到,性能指标已经收集完成,接着就到火焰图生成工具-FlameGraph的出场时间了。

生成火焰图

执行命令

perl flamegraph.pl –colors=java /tmp/test_02.txt > /tmp/test_02.svg

查看tmp路径下文件:

1
2
3
4
[root@snowalker tmp]# ll
-rw-r--r-- 1 root root 32452 Feb 22 12:46 test_02.svg
-rw-r--r-- 1 root root 6008 Feb 22 12:45 test_02.txt

可以看到已经生成一张svg格式的图片,下载图片到本地:

1
sz test_02.svg

使用浏览器打开图片,一张美观的火焰图展现在面前:

fire.png

如何阅读火焰图?

有了火焰图,我们得读懂它才能利用它进行性能优化。

一眼看过去,红红火火的,密密麻麻,可能你觉得案例中 不够密密麻麻,如果分析的是线上的程序,那复杂程度足够让人眼花缭乱。

那么我们应该如何理解火焰图的内容呢?

简单的说:

  1. 火焰图,每一列代表一个调用栈,每一个格子代表一个函数
  2. 纵轴,即垂直方向的y轴,展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。
  3. 横轴,即水平方向的x轴,表示:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。【横轴没有特殊的含义,不代表调用关系!】
  4. 横轴格子的 宽度 代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
  5. 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
  6. 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。
  7. 采样方式可以是单线程、多线程、多进程甚至是多 host,进阶用法参考文档:https://www.brendangregg.com/flamegraphs.html

另外,多说两句,火焰图的栈深度与y轴高度成正比,可以这么认为:造成性能问题的基本都处于调用栈的栈顶位置。

因为栈顶位置的性能问题会间接拖慢整个调用栈,简单的举个例子:方法A调用方法B,方法B调用方法C。

如果方法C执行的慢会则间接导致方法B慢,从而导致方法A慢。符合我们说的通过分析栈顶从而达到分析瓶颈的目的。

如果A方法本身就慢呢?通过火焰图也是可以看出来的,这种底层栈的宽度很宽,但是建立在其撒花姑娘的调用链线条都很窄,火焰图呈现“┻”型,那么我们基本可以确定,栈底方法本身就存在性能问题。

我们的一个分析火焰图的基本原则就是,从栈顶看起,往栈底分析。



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

作为一名Java后端开发者,尤其是国内开发者,从刚参加工作开始就与Mybatis打交道了。

用了这么久的Mybatis难免会心生疑问:

  • 我只是写了个Mapper接口,再配合xml或者注解,把SQL一写,就可以执行数据库操作,这是为何?
  • 都说Mybatis是对JDBC的封装,可是我却看不到JDBC相关的接口和对象,它们到哪里去了?
  • 为什么在Spring中使用Mybatis,不用加@Repository/@Component之类的注解,就可以随用随注入(如:@Autowired)?

硬核万字长文,点个再看,转发,多谢啦~

Read More

题诗:

不要温和地走进那良夜,
老年应当在日暮时燃烧咆哮;
怒斥,怒斥光明的消逝。
虽然智慧的人临终时懂得黑暗有理,
因为他们的话没有迸发出闪电,他们
也并不温和地走进那个良夜。
善良的人,当最后一浪过去,高呼他们脆弱的善行
可能曾会多么光辉地在绿色的海湾里舞蹈,
怒斥,怒斥光明的消逝。
狂暴的人抓住并歌唱过翱翔的太阳,
懂得,但为时太晚,他们使太阳在途中悲伤,
也并不温和地走进那个良夜。
严肃的人,接近死亡,用炫目的视觉看出
失明的眼睛可以像流星一样闪耀欢欣,
怒斥,怒斥光明的消逝。
您啊,我的父亲.在那悲哀的高处.
现在用您的热泪诅咒我,祝福我吧.我求您
不要温和地走进那个良夜。
怒斥,怒斥光明的消逝。

平常的日子,地球又一次完成了它伟大的公转,于是,一年又过去。

往年每逢公历年的末尾,总是会像写故事一样,洋洋洒洒的罗列自己一年来达成的目标,嗟叹一番未完成的事情,然后立下当时自信能够在接下来一年
能够完成的flag,最后再下一次所谓年终总结中,继续这个循环。

这次,我承认我又不能免俗的继续写这个所谓的“年终总结”。只是觉得,如果不写的话,我将又丢掉一个本就少的可怜的习惯。

仅仅是出于习惯,我写给自己。

总得找个主题,这一年,复杂,我想不出别的词来概括我的一年,下意识的想到了复杂。

这是一个男孩变成所谓男人的故事,经历很多,结婚,生子,跳槽,网暴,写书…..太多的事情,让你变得急躁。

做加法容易,做减法难。

着急地想要做完这件事,奔赴下一件事;

从一个孩子变为一个父亲,只觉得,脚步都变得不同往常。

从此不是为了自己而活,一切的美好语言比不上陪伴二字。

感谢妻的辛苦,感谢父母的照顾,感谢陪伴。

经历的事情确实很多,但此时的心态却不同往常,往日肯定会大篇幅的去逐个罗列,然后写上一段所谓心得。这都是过去式了。

要向前看。

活着,生活着,努力生活着,就很满足了。

于是我想到了八个字,归于平静,甘于平凡。

事情太多,加法做太多,心情变得急躁,于是什么都想要,于是总会计较得失。

来年,接下来的日子里,要学会做减法,断舍离,关注重要的,忽略次要的,要接受自己是一个普通人的事实,不虚妄,不浮夸。

甘于平凡,修身修心,过好平常的每天,陪伴身边的人。

不炫耀,不盲从,负责任,扮演好每个需要扮演的角色,不逾矩。

不再立虚无的目标,学会衡量承诺的重量。

感谢过去的自己,做好现在的自己,迎接更好的自己。

迎接更好的2022。



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

项目中需要实现高效的IO操作,不仅支持查询、写入数据,还需要实现数据的持久化。选型最终选择了RocksDB,那么本文就来一睹RocksDB的芳容。

RocksDB是什么?

RocksDB是Facebook开发的一款高效的数据库软件,是采用C++编写而成的。

RocksDB是一款key-value型数据存储设施,具备四个特点,其具有四大特点。

高性能:RocksDB使用一套C++编写而成的高性能日志结构的数据库引擎。 它的Key和value支持任意大小的字节流。

可适配性:RocksDB适合于多种不同工作场景。从像MyRocks这样的数据存储引擎到应用数据缓存, 甚至是嵌入式工作场景,RocksDB都可以从容面对这些不同的数据工作需求。

为快速存储而优化:RocksDB为快速而又低延迟的存储设备(例如闪存或者高速硬盘)进行了特殊优化处理,将最大限度发挥闪存和RAM的高度率读写性能。

基础和高级的数据库操作:RocksDB提供了一些基础的操作,例如打开和关闭数据库。它对于合并、压缩、过滤等高级操作,也提供了支持。

如果对RocksDB感兴趣,可以去读它的源码,地址为:https://github.com/facebook/rocksdb

在Java工程中使用RocksDB

如何在Java工程中使用RocksDB呢?

首先建立一个maven工程,在pom.xml中引入RocksDB依赖:

<!-- https://mvnrepository.com/artifact/org.rocksdb/rocksdbjni -->
<dependency>
    <groupId>org.rocksdb</groupId>
    <artifactId>rocksdbjni</artifactId>
    <version>6.20.3</version>
</dependency>

接着我们通过一个demo尝试在Java中使用RocksDB。

初始化RocksDB

private RocksDB rocksDB;

private String path = "D:/rocksdb";

public RocksDBDemo() {
    RocksDB.loadLibrary();
    Options options = new Options();
    options.setCreateIfMissing(true);
    try {
        rocksDB = RocksDB.open(options, path);
    } catch (RocksDBException e) {
        e.printStackTrace();
    }
}

public RocksDB rocksDB() {
    return this.rocksDB;
}

首先指定一个路径用于创建RocksDB的log文件:rocksdb写入时,直接以append方式写到log文件以及memtable,随即返回,因此非常快速。

接着通过RocksDB.loadLibrary();导入库,然后设置Options,并打开rocksDB,此时成员变量rocksDB就可以拿来进行操作了。

RocksDB读写

RocksDBDemo rocksDBDemo = new RocksDBDemo();
RocksDB rocksDB = rocksDBDemo.rocksDB();
// 写入
rocksDB.put("name".getBytes(), "snowalker".getBytes());
// 读取
byte[] bytes = rocksDB.get(("name".getBytes()));
System.out.println("读取结果:" + new String(bytes));

// 遍历
RocksIterator iter = rocksDB.newIterator();
for (iter.seekToFirst();iter.isValid();iter.next()) {
    System.out.println("iter key: " + new String(iter.key()) + ",iter value: " +
            new String(iter.value()));
}

RocksDBDemo是我们的测试类名,首先获取rocksDB引用,然后调用put,get方法进行读写操作。

其中put方法签名为:

public void put(final byte[] key, final byte[] value)
    throws RocksDBException {
    put(nativeHandle_, key, 0, key.length, value, 0, value.length);
}

get方法签名为:

public byte[] get(final byte[] key) throws RocksDBException {
    return get(nativeHandle_, key, 0, key.length);
}

通过rocksDB.newIterator()可以获取迭代器,借助迭代器能够执行迭代操作,这里是读取了一下key与value。

rocksDB的write操作

rocksdb的一个WriteBatch是原子操作,要么全部成功,要么全部失败,

具体的实现原理是在整个log的写的过程中只会调用Write操作,最后会调用一次flush,所以如果中间发生机器crash,所有的都会失败,否则所有的都会成功。

看一段实际代码:

// write batch test
try (final WriteOptions writeOpt = new WriteOptions()) {
    for (int i = 0; i <= 10; ++i) {
        try (final WriteBatch batch = new WriteBatch()) {
            for (int j = 0; j <= 10; ++j) {
                batch.put(String.format("%d * %d%s", i, j, "--batch").getBytes(),
                        String.format("%d", i * j).getBytes());
            }
            rocksDB.write(writeOpt, batch);
        }
    }
}

这里实际上将乘法表写入了rocksDB。

运行代码,观察目录D:/rocksdb中出现了以下文件:

1.PNG

简单对这几种文件进行讲解:

我们可以从后缀看出:主要有这几种类型

  • sst文件
  • CURRENT文件
  • manifest文件
  • log文件
  • LOG文件和LOCK文件

其中

  1. sst文件存储的是落地的数据
  2. CURRENT文件存储的是当前最新的是哪个manifest文件
  3. manifest文件存储的是Version的变化
  4. log文件是rocksdb的write ahead log,就是在写db之前写的数据日志文件,类似binlog
  5. LOG文件是一些日志信息,是供调试用的
  6. LOCK是打开db锁,只允许同时有一个进程打开db

这里我们重点看一下log文件中的内容,由于写入的byte,可能有乱码。

2.PNG

从图中可以看到,实际上是顺序写入了我们在代码中设置的key-value。

解释一下rocksdb的flush操作

Flush是指将memtable的数据导入到sst中,变成持久化存储,从而不必担心数据丢失了。

1.首先在memtable的add的时候,
会检测是否memtable的大小达到了max write buffer,
如果是就将should_flush_置为true,
并会在WriteBatch的Handler里面调用CheckMemtableFull,
将当前column family加入flush_scheduler。

2.在Write的时候,调用ScheduleFlushes,
将需要flush的column family的memtable切换一个新的,
同时将原来的memtable加入cfd的imm中,
如果这个column family data的imm数量大于min_write_buffer_number_to_merge,
并启动一个新的线程调用BGWorkFlush

由于真正的Flush过程是在另一个线程完成的,所以这个地方并不会block写过程

rocksDB的WAL(write ahead log)

rocksdb的write ahead log(WAL)是指:
每次写操作,rocksdb会先写write ahead log,然后才会写db
write ahead log可以配置到单独的空间,并且可以配置WAL文件的单独的删除机制。

这种原因是为了保存WAL文件,达到特殊的目的,比如,其他sst文件放在不可靠存储里面,而WAL放到可靠存储里面。

对RocksDB 的写操作而言,每次都必写到两个地方:

  1. 基于内存的数据结构memtable(达到quota 后会flush 至SST file)。
  2. 预写日志-Write Ahead Log(WAL)。
    如果出现异常情况,WAL 可以用来完整恢复memtable 中的数据,恢复db 的原有的状态。

默认配置下,RocksDB 通过每次用户写之后flush WAL,来保证进程crash 后的一致性。

小结

本文我们主要对rocksDB做了一个了解和学习,一般来说,使用它的目的在于高性能的写入,实现数据的快速持久化。

业界不乏优秀的中间件底层依赖了rocksDB,如TiDB。

###



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

复杂软件开发之道系列进行到现在,主要讲的还是理论和思考,但是如何针对DDD编写代码这一问题想必是大家一直关心的问题。

本文我们就小试牛刀,展示一下通过DDD方式编写代码。

1.只有新项⽬才能考虑⽤DDD吗?

当然不是,DDD这套⽅法论不仅适合从零开始的新项⽬的建模,⽽且适 合复杂业务系统的重构。 当然如果能在新模型的建⽴的过程中就使⽤DDD作为指导,是最好不过的事情,原因在于你会省略对原 有业务系统代码逻辑的梳理和适配过程,众所周知,这不是⼀件容易的事情。

Read More

Fork me on GitHub