代码细节带来的极致体验,shardingsphere 5.1.0 性能提升密钥-ag真人官方网

代码细节带来的极致体验,shardingsphere 5.1.0 性能提升密钥-ag真人官方网

来源:csdn博客 | 2022-03-10 10:53:14 |

前言

在 apache shardingsphere 被广泛采用的同时,我们接到了部分用户的反馈,shardingsphere 5.x 相比过去版本性能略有下降。在 5.0.0 正式版发布后,shardingsphere 团队对 shardingsphere 的内核、接入端等方面进行了大量性能优化。目前 5.1.0 正式版已发布,本文将介绍 shardingsphere 5.1.0 在代码层面所进行的部分性能优化,并对 shardingsphere-proxy 进行 tpc-c 基准测试验证优化成果。

吴伟杰

sphereex 基础设施研发工程师,apache shardingsphere committer。目前专注于 apache shardingsphere 及其子项目 elasticjob 的研发。

优化内容

更正 optional 的使用方式

java 8 引入的 java.util.optional能够让代码更加优雅,例如避免方法直接返回 null。其中 optional有两个比较常用的方法:

public t orelse(t other) {    return value != null ? value : other;}public t orelseget(supplier other) {    return value != null ? value : other.get();}

在 shardingsphere 的类 org.apache.shardingsphere.infra.binder.segment.select.orderby.engine.orderbycontextengine中有这么一段使用了 optional的代码:

optional result = // 省略代码...return result.orelse(getdefaultorderbycontextwithoutorderby(groupbycontext));

以上这种使用 orelse的写法,即使 result 的结果不为空,orelse里面的方法也会被调用,尤其是 orelse里面的方法涉及修改操作时,可能会发生意料之外的事情。涉及方法调用的情况下应调整为下面的写法:

optional result = // 省略代码...return result.orelseget(() -> getdefaultorderbycontextwithoutorderby(groupbycontext));

使用 lambda 提供一个 supplier给 orelseget,这样只有 result 为空的时候才会调用 orelseget里面的方法。

相关 pr:https://github.com/apache/shardingsphere/pull/11459/files

避免高频并发调用 java 8 concurrenthashmap 的 computeifabsent

java.util.concurrent.concurrenthashmap是我们在并发场景下比较常用的一种 map,相比对所有操作以 synchronized修饰的 java.util.hashtableconcurrenthashmap在保证线程安全的情况下提供了更好的性能。但在 java 8 的实现中,concurrenthashmap的 computeifabsent在 key 存在的情况下,仍然会在 synchronized代码块中获取 value,在对同一个 key 高频调用 computeifabsent的情况下非常影响并发性能。

参考:https://bugs.openjdk.java.net/browse/jdk-8161372

这个问题在 java 9 解决了,但为了在 java 8 上也能保证并发性能,我们在 shardingsphere 的代码中调整写法规避这一问题。

以 shardingsphere 的一个高频调用的类 org.apache.shardingsphere.infra.executor.sql.prepare.driver.driverexecutionprepareengine为例:

// 省略部分代码...    private static final map type_to_builder_map = new concurrenthashmap<>(8, 1);    // 省略部分代码...    public driverexecutionprepareengine(final string type, final int maxconnectionssizeperquery, final executordrivermanager executordrivermanager,                                         final storageresourceoption option, final collection rules) {        super(maxconnectionssizeperquery, rules);        this.executordrivermanager = executordrivermanager;        this.option = option;        sqlexecutionunitbuilder = type_to_builder_map.computeifabsent(type,                 key -> typedspiregistry.getregisteredservice(sqlexecutionunitbuilder.class, key, new properties()));    }

以上代码传入 computeifabsent的 type只有 2 种,而且这段代码是大部分 sql 执行的必经之路,也就是说会并发高频地对相同 key 调用 computeifabsent方法,导致并发性能受限。我们采用如下方式规避这一问题:

sqlexecutionunitbuilder result;if (null == (result = type_to_builder_map.get(type))) {    result = type_to_builder_map.computeifabsent(type, key -> typedspiregistry.getregisteredservice(sqlexecutionunitbuilder.class, key, new properties()));}return result;

相关 pr:https://github.com/apache/shardingsphere/pull/13275/files

避免高频调用 java.util.properties

java.util.properties是 shardingsphere 在配置方面比较常用的一个类,properties继承了 java.util.hashtable,因此要避免在并发情况下高频调用 properties的方法。

我们排查到 shardingsphere 与数据分片算法有关的类 org.apache.shardingsphere.sharding.algorithm.sharding.inline.inlineshardingalgorithm中存在高频调用 getproperty的逻辑,导致并发性能受限。我们的处理方式为:将涉及 properties方法调用的逻辑放在 inlineshardingalgorithm的 init方法内完成,避免在分片算法计算逻辑的并发性能。

相关 pr:https://github.com/apache/shardingsphere/pull/13282/files

避免使用 collections.synchronizedmap

在排查 shardingsphere 的 monitor blocked 过程中,发现在 org.apache.shardingsphere.infra.metadata.schema.model.tablemetadata这个类中使用了 collections.synchronizedmap修饰会被高频读取的 map,影响并发性能。经过分析,被修饰的 map 只会在初始化阶段有修改操作,后续都是读取操作,我们直接移除 collections.synchronizedmap修饰方法即可。

相关 pr: https://github.com/apache/shardingsphere/pull/13264/files

字符串拼接代替不必要的 string.format

在 shardingsphere 的类 org.apache.shardingsphere.sql.parser.sql.common.constant.quotecharacter有这么一段逻辑:

public string wrap(final string value) {        return string.format("%s%s%s", startdelimiter, value, enddelimiter);    }

显然上面的逻辑就是做一个字符串拼接,但使用 string.format的方式相比直接字符串拼接的开销会更大。我们修改成以下方式:

public string wrap(final string value) {        return startdelimiter   value   enddelimiter;    }

我们用 jmh 做一个简单的测试,测试结果:

# jmh version: 1.33# vm version: jdk 17.0.1, java hotspot(tm) 64-bit server vm, 17.0.1 12-lts-39# blackhole mode: full   dont-inline hint (default, use -djmh.blackhole.autodetect=true to auto-detect)# warmup: 3 iterations, 5 s each# measurement: 3 iterations, 5 s each# timeout: 10 min per iteration# threads: 16 threads, will synchronize iterations# benchmark mode: throughput, ops/timebenchmark                           mode  cnt          score         error  unitsstringconcatbenchmark.benchformat  thrpt    9   28490416.644 ± 1377409.528  ops/sstringconcatbenchmark.benchplus    thrpt    9  163475708.153 ± 1748461.858  ops/s

可以看出,使用 string.format相比使用  拼接字符串的开销会更大,且自 java 9 起优化了直接拼接字符串的性能。由此可见选择合适的字符串拼接方式的重要性。

相关 pr:https://github.com/apache/shardingsphere/pull/11291/files

使用 for-each 代替高频 stream

shadingsphere 5.x 代码中使用了较多的 java.util.stream.stream

在我们之前做的一次 benchmarksql(tpc-c 测试的 java 实现) 压测 shardingsphere-jdbc opengauss 的性能测试中,我们发现将压测过程中发现的所有高频 stream 替换为 for-each 后,shardingsphere-jdbc 的性能提升明显。

*注:shardingsphere-jdbc 与 opengauss 分别在 2 台 128 核 aarch64 的机器上,使用毕昇 jdk 8。

以上测试结果也可能和 aarch64 平台及 jdk 有关。不过 stream 本身存在一定开销,性能在不同场景下差异较大,对于高频调用且不确定 stream 能够优化性能的逻辑,我们考虑优先使用 for-each 循环。

相关 pr:https://github.com/apache/shardingsphere/pull/13845/files

避免不必要的逻辑(重复)调用

避免不必要的逻辑重复调用有很多案例:

shardingsphere 有个类 org.apache.shardingsphere.sharding.route.engine.condition.column实现了 equals和 hashcode方法:

@requiredargsconstructor@getter@tostringpublic final class column {    private final string name;    private final string tablename;    @override    public boolean equals(final object obj) {...}    @override    public int hashcode() {        return objects.hashcode(name.touppercase(), tablename.touppercase());     } }

显而易见,上面这个类是不可变的,但是却在hashcode方法的实现中每次都调用方法计算 hashcode。如果这个对象频繁在 map 或者 set 中存取,就会多出很多不必要的计算开销。

调整后:

@getter@tostringpublic final class column {    private final string name;    private final string tablename;    private final int hashcode;    public column(final string name, final string tablename) {        this.name = name;        this.tablename = tablename;        hashcode = objects.hash(name.touppercase(), tablename.touppercase());    }    @override    public boolean equals(final object obj) {...}    @override    public int hashcode() {        return hashcode;    } }

相关 pr:https://github.com/apache/shardingsphere/pull/11760/files

使用 lambda 代替反射调用方法

在 shardingsphere 源码中,有以下场景需要记录方法及参数调用,并在需要的时候对指定对象重放方法调用:

1. 向 shardingsphere-proxy 发送 begin 等语句;

2. 使用 shardingspherepreparedstatement 为指定位置的占位符设置参数。

以如下代码为例,重构前,使用反射的方式记录方法调用及重放,反射调用方法本身存在一定的性能开销,且代码可读性欠佳:

@overridepublic void begin() {    recordmethodinvocation(connection.class, "setautocommit", new class[]{boolean.class}, new object[]{false});}

重构后,避免了使用反射调用方法的开销:

@overridepublic void begin() {    connection.getconnectionpostprocessors().add(target -> {        try {            target.setautocommit(false);        } catch (final sqlexception ex) {            throw new runtimeexception(ex);        }    });}

相关 pr:

https://github.com/apache/shardingsphere/pull/10466/files

https://github.com/apache/shardingsphere/pull/11415/files

netty epoll 对 aarch64 的支持

netty 的 epoll 实现自 4.1.50.final支持 aarch64 架构的 linux 环境。在 aarch64 linux 环境下,使用 netty epoll api 相比 netty nio api 能够提升性能。

参考:https://stackoverflow.com/a/23465481/7913731

5.1.0 与 5.0.0 shardingsphere-proxy tpc-c 性能测试对比

我们使用 tpc-c 对 shardingsphere-proxy 进行基准测试,以验证性能优化的成果。由于更早期版本的 shardingsphere-proxy 对 postgresql 的支持有限,无法进行 tpc-c 测试,因此使用 5.0.0 与 5.1.0 版本对比。

为了突出 shardingsphere-proxy 本身的性能损耗,本次测试将使用数据分片(1 分片)的 shardingsphere-proxy 对比 postgresql 14.2。

测试按照官方文档中的《benchmarksql 性能测试(https://shardingsphere.apache.org/document/current/cn/reference/test/performance-test/benchmarksql-test/)》进行,配置由 4 分片缩减为 1 分片。

shardingsphere-proxy

postgresql

benchmarksql

cpu

2 * intel(r) xeon(r) cpu e5-2650 v4 @ 2.20ghz

2 * intel(r) xeon(r) gold 6146 cpu @ 3.20ghz

2 * intel(r) xeon(r) cpu e5-2650 v4 @ 2.20ghz

ram

3 * 32gb 2400mhz

16 * 32gb 2666mhz

3 * 32gb 2400mhz

硬盘

/

avago scsi 240gb

/

网卡

intel corporation 82599es 10-gigabit sfi/sfp network connection

intel corporation ethernet connection x722 for 10gbe sfp

intel corporation 82599es 10-gigabit sfi/sfp network connection

操作系统

centos 7.9

centos 7.9

centos 7.9

软件环境

java 17.0.1

shardingsphere-proxy 5.0.0 / 5.1.0

postgresql 14.2

benchmarksql 5.0

其他配置

网卡队列绑核 0-1,24-25

shardingsphere-proxy 绑核 2-23,26-47

fsync=off

full_page_writes=off

shared_buffers=128gb

benchmarksql 参数:

warehouses=192 (数据量)

terminals=192 (并发数)

terminalwarehousefixed=false

运行时间 30 mins

postgresql jdbc 参数:

defaultrowfetchsize=50

rewritebatchedinserts=true

shardingsphere-proxy jvm 部分参数:

-xmx16g

-xms16g

-xmn12g

-xx:autoboxcachemax=4096

-xx: usenuma

-xx: disableexplicitgc

-xx:largepagesizeinbytes=128m

-xx: segmentedcodecache

-xx: aggressiveheap

tpmc

相比直连损耗

postgresql 14.2

413,821

/

shardingsphere-proxy 5.0.0

237,079

42.7%

shardingsphere-proxy 5.1.0

300,558

27.4%

在本文的环境与场景中所得到的结论:

以 shardingsphere-proxy 5.0.0 postgresql 为基准,5.1.0 性能提升约 26.8%

以直连 postgresql 为基准,shardingsphere-proxy 5.1.0 相比 5.0.0 损耗减少了约 15%,由 42.7% 降低至 27.4%。

由于代码细节优化遍布 shardingsphere 各模块,以上测试结果并未覆盖所有优化点。

如何看待性能问题

可能不时会有人问,“shardingsphere 性能怎么样?损耗多少?”

在我看来,性能能够满足需求即可。性能是一个比较复杂的问题,受非常多的因素影响。在不同的环境、场景下,shardingsphere 的性能损耗有可能不到 1%,也有可能高达 50%,我们无法在脱离环境和场景的情况下给出答案。此外,shardingsphere 作为基础设施,其性能是研发过程中重点考虑的因素之一,shardingsphere 社区中的团队、个人也会持续发挥工匠精神,不断地将 shardingsphere 的性能推向极致。

关键词:

ag真人官方网 ag真人官方网的版权所有.

联系网站:920 891 263@qq.com
网站地图