金融是这个世界运转不可或缺的部分,学习一点儿金融交易知识也是我们了解世界运行规律的一种方式。本系列“金融交易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

DDD领域驱动设计今年真的是大火了,这是好事,表明我们的软件开发领域是一直在向前发展的。

但是很多的文章或者培训,都是在说DDD如何如何优秀,简单的举一些例子就行了,有些甚至是完全错误的。

因此笔者决定将自己的实践心得以及与他人讨论的关于DDD的问题整理为一篇文章,通过问题驱动的方式,将领域驱动设计中的一些注意点进行总结,希望能够对读者有所帮助。

主要的问题如下:

  • 到底什么才是统⼀语⾔,它有那么重要么?
  • 领域驱动设计仅仅需要开发者参与吗?
  • 领域驱动设计的那些个概念到底是在说什么?实体和值对象有啥区别?
  • DDD四层架构的好处有哪些?
  • 限界上下文如何划分比较好呢?有没有工具推荐?
  • 聚合粒度如何控制呢?一个聚合根就够了,为什么还要细分各种子域?
  • ACL(防腐层)应该怎么用?它的作用和优势有哪些?它在DDD分层中处于哪个位置?

Read More

在笔者的公众号上发布了关于 对账 业务分析的一篇文章,, 该文也是笔者新书中的一节内容。

本文作为补充,我们从实战角度,从代码角度呈现一个对账框架的实现。

注意:本文中提供的对账框架为广义上的对账,也就是说不局限于支付、交易领域的对账场景,凡是需要通过数据比对进行数据校准、比对、核准的场景,均能够采用本文提供的思路进行实现。

Read More

事情的经过是这样的,群友四哥发来一个问题,问大家有什么看法,我看了下,刚好之前接触过类似的业务场景,因此斗胆就问题谈谈自己的看法,抛砖引玉。

问题如下:

A系统联机同步调用B系统(A和B不是同一公司系统,不能用分布式事务),

如何保证系统间数据准实时一致性(聊聊设计思路即可)?

提醒:需要考虑调用超时、并发、幂等、反交易先到等。

各种异常场景怎么处理要考虑更完善些,如事务隔离、并发、反交易先到调用方和服务方约定(前端客户不可能一直等着)

Read More

Fork me on GitHub