在 vertx 中可以让测试类实现 Verticle 接口,然后让 vertx 的 eventloop-thread 去直接跑 Verticle,这样做压测时就不用启动额外的线程了。lealone 其实也可以的,让测试类实现 AsyncTask 接口,然后让 lealone 的 ClientScheduler直接运行 AsyncTask,不用启动额外的线程,性能还能多好一些。
我分析了一下 vertx 的 mysql、postgresql 异步客户端为什么低效?
目前看到三个原因:
1. 在执行 sql 时每次都要去跟连接池打交道,lealone 的异步客户端是不需要的;
2. 应用的代码连续调用 SqlClient.query(sql).execute(),哪怕是异步的,vertx 也不知道去动态优化一下,每次都只发一条 sql 对应的数据包, lealone 会动态看看任务队列里还有没有更多任务要处理,如果有就先不发数据包,而是攒够了几个再通过 SocketChannel.write(ByteBuffer[] srcs) 去写;
3. vertx 的 eventloop-thread 在执行到应用代码的回调函数时,线程的堆栈深度达到了70+,而 lealone 的 eventloop-thread 堆栈深度才20+,说明它的调度器更复杂需要执行的代码更多。
当然,异步客户端只是影响整体性能的因素之一,份量也没那么重,关键还是看数据库自身的处理时间。
http://t.cn/AXURQTXx 在 techempower 这个知名排行榜上面,更新数据那一项,vertx-postgres 排名第7,每秒更新数在58300,我这个测试是每秒65000左右,所以还是靠谱的,哪怕换成 Verticle 压测也是一样。
@zhh-4096
16个并发线程,每个线程逐条执行200条 update 语句,随机更新单行记录。这是 lealone 异步 jdbc 驱动和 vertx 的 mysql、postgresql 异步客户端的测试结果,10几倍的差距。
这6年炒美股有亏有赚,但总体下来平均每年有20-30万人民币的收入,在桂林这种三线城市生活够用了。还想要赚更多钱的话,只能靠做产品了,炒股的确定性高一点,做产品能不能赚钱反而很不确定,就算技术上有很大的领先优势,也不一定能拿到多高的市场份额,确实要讲天时地利人和。
养娃和研发技术产品本质是一样的,都需要投入金钱、时间和精力,都想要做好,也希望最终能给自己一点回报。但也有一些区别,研发技术产品能做得多好,更多取决我的智商。孩子能变得有多优秀,先天因素占比更大,遗传到的优良基因多就好培养一些,否则就很吃力。
每次想起我们上屋的邻居,夫妻俩60岁的人了,还得操心两个30多岁儿子结婚的事,买车买房托人介绍对象,60了还得去打临时工。如果我的孩子也是这样,我都想不明白生孩子的意义是什么,辛苦一辈子早出晚归就是为了给孩子成家。这么对比,我妈就太幸福了,50+岁开始就不操心啥事了,就养几只鸡种点菜。
我现在管我侄女的学习,真的费钱费神。如果我要结婚生娃,自己的孩子不能继承我的智商不能像我小时候那么让父母省心,我根本就不想要养这样的孩子,我又不指望孩子给我养老,如果孩子普普通通,学习吃力,哪怕养到大学毕业,得把人愁死,太操心了。我宁愿把这些时间投入到工作上,至少还会有一点成就。
三线城市机会少,待遇低,像我这种资历的人在桂林找工作就算有合适的,月薪估计也就一万多一点。也多亏这6年炒美股赚了点钱,不然我还得往一线或准一线城市跑。在三线城市创业更难,只适合做点外包项目,想做点有技术含量的事,难找到志同道合的人,就连去本地学校挑实习生都难挑到好苗子。
就算两个人月收入8000,家里两个孩子上大学了,每个人每个月生活费1500两个人就得3000,除非5年后我很有钱,否则我也出不起侄女们的生活费了。如果我买房还房贷一个月还3000,再加两个人生活费3000,就得6000,再加其他各种支出,一个月最少一万。一个月得收入两万才踏实,桂林这种收入的工作极少。
@zhh-4096
初三学生,一个月四个周末补课,时间总计8-10小时,收费1200,说实话挺贵的,也就一天的工作时间。现在桂林的上班族月薪普遍3000-5000,补课老师这么收费,月薪得3万多了。孩子学习普通真的费钱,还好只是初三补课。
初三学生,一个月四个周末补课,时间总计8-10小时,收费1200,说实话挺贵的,也就一天的工作时间。现在桂林的上班族月薪普遍3000-5000,补课老师这么收费,月薪得3万多了。孩子学习普通真的费钱,还好只是初三补课。
最后花2-3个月,只要跑稳定了,就正式发布 lealone 8.0.0,这必将是个划时代的大版本,性能断层式领先全世界主流的开源 oltp 关系数据库和文档数据库。
lealone 从 jdbc 客户端到后端数据库的存储引擎,实现了全链路异步化,对记录和 btree page 这类共享对象的访问都使用 CAS 实现,如果应用在客户端使用异步 jdbc api 读写数据库,会获得极其炸裂的性能,甚至远超我最初的预期。任何 oltp 数据库想达到这样的并发处理能力,最后都会跟 lealone 一样。
@zhh-4096
如何证明:一个全链路异步化的系统 + CAS 实现的轻量级锁,就是最完美的并发控制系统?这个证明留给网友去思考,当你想通了这个证明,任何需要处理并发问题的系统都能迎刃而解。
之前我用 mongodb-6.0.6,最新的 mongodb-8.2.2 的性能比6.0.6快一点,但是跟 lealone-8.0.0 比读写性能实在是差得太多了。世界排名前4的4个开源关系或文档数据库 mongodb、mysql、postgresql、sqlite,都被 lealone 打败了。哈哈哈,无敌是多么寂寞,证明了 lealone 的并发控制系统确实强得可怕。
@zhh-4096
压测了最新的 mongodb-8.2.2,它的异步客户端只比同步客户端快一点点,insert 的性能跟 lealone 相比慢了20-30倍!mongodb 的写入性能其实还不如 mysql。
看不出什么问题,如果跑单行查询,vertx 的异步客户端比 mysql 的 jdbc 驱动快1.5倍,但单行查询依然比 lealone 的异步 jdbc 驱动慢8倍左右。//@zhh-4096:下午再分析一下 vertx 的代码实现,几年前还是用过 vertx 做过项目的,但是当时还没有异步客户端。印象中 vertx 的性能没那么糟糕的,10几倍的差距实在太夸张了,我的预期就是 lealone 的异步 jdbc 驱动能比 vertx 快一倍就不错了,算上 lealone 本身就比 mysql 和 postgresql 快,总共快4倍了不起了。
@zhh-4096
16个并发线程,每个线程逐条执行200条 update 语句,随机更新单行记录。这是 lealone 异步 jdbc 驱动和 vertx 的 mysql、postgresql 异步客户端的测试结果,10几倍的差距。
压测了最新的 mongodb-8.2.2,它的异步客户端只比同步客户端快一点点,insert 的性能跟 lealone 相比慢了20-30倍!mongodb 的写入性能其实还不如 mysql。
lealone 在嵌入式场景执行一条 sql 的时间是2-3微秒;在 client-server 场景,按截图里的结果算,每个线程异步执行200条 sql 要2-3毫秒,平均执行一条 sql 要10-15微秒。localhost 的网络延迟是20+微秒,异步执行不用等待前一条 sql 的结果,相当于200条 sql 可以连续发送,所以平均下来也降低了延迟。
@zhh-4096
16个并发线程,每个线程逐条执行200条 update 语句,随机更新单行记录。这是 lealone 异步 jdbc 驱动和 vertx 的 mysql、postgresql 异步客户端的测试结果,10几倍的差距。
下午再分析一下 vertx 的代码实现,几年前还是用过 vertx 做过项目的,但是当时还没有异步客户端。印象中 vertx 的性能没那么糟糕的,10几倍的差距实在太夸张了,我的预期就是 lealone 的异步 jdbc 驱动能比 vertx 快一倍就不错了,算上 lealone 本身就比 mysql 和 postgresql 快,总共快4倍了不起了。
@zhh-4096
16个并发线程,每个线程逐条执行200条 update 语句,随机更新单行记录。这是 lealone 异步 jdbc 驱动和 vertx 的 mysql、postgresql 异步客户端的测试结果,10几倍的差距。
本来比亚迪减仓到只剩100股,昨天看到比亚迪跌到93.6又补仓了500股,还好今天早上涨了。今年长线持有比亚迪确实坑爹,12月最后一天若是全年涨幅收红就不错了。在A股玩中长线就是这样,如果不做波段,哪怕中间涨了40%,不卖出的话,到年底还是杨白劳。
用异步客户端逐条执行200条sql,理论上应该跟批量执行200条sql的性能接近,因为每次异步执行一条sql时不用等待它的结果返回就能继续发送下一条sql。如果性能跟批量执行差太远,那一定是客户端的实现有问题。
avg time 是平均每个线程逐条执行完200条update语句的时间,total time 是16个线程全部结束的时间(包括线程启动和线程切换的时间)。
@zhh-4096
16个并发线程,每个线程逐条执行200条 update 语句,随机更新单行记录。这是 lealone 异步 jdbc 驱动和 vertx 的 mysql、postgresql 异步客户端的测试结果,10几倍的差距。
通过调试 java 虚拟线程调度器的代码,我发现虚拟线程有3个弱点:1. 虚拟线程出入调度器的队列有开销;2. 虚拟线程发生切换时也有开销;3. 虚拟线程遇到 synchronized 的代码会阻塞调度器中运行它的系统线程更是致命的。所以要想追求极致的低延迟和高吞吐,对系统进行全链路异步化改造才是唯一解!
lealone 的调度器是为数据库的场景专门定制的,在调度器中执行的数据库任务依然是用异步风格,并没有用虚拟线程加同步风格的任务。在 lealone 的调度器中切换不同的数据库任务开销极低,无需保存和恢复任务的状态,任务的启动和出入队列都极其高效。所以我不会用虚拟线程改写 lealone 的内核代码的。
@zhh-4096
像 h2、mysql、postgresql 的 jdbc 驱动的代码中有不少加了 synchronized 的代码,jdk 的网络 bio 的代码也有,虚拟线程遇到 synchronized 不但不会发生虚拟线程切换,还会阻塞运行它的系统线程,并且 java 的调度器中运行的系统线程数不变,一旦被阻塞就不能跑其他虚拟线程,这就解释了为啥效果不好。
像 h2、mysql、postgresql 的 jdbc 驱动的代码中有不少加了 synchronized 的代码,jdk 的网络 bio 的代码也有,虚拟线程遇到 synchronized 不但不会发生虚拟线程切换,还会阻塞运行它的系统线程,并且 java 的调度器中运行的系统线程数不变,一旦被阻塞就不能跑其他虚拟线程,这就解释了为啥效果不好。
理论上这个折中方案应该性能很好才对,但是虚拟线程的启动和切换也是有开销的,不如异步 jdbc api + nio 的组合。比如10万条 sql 用1000个虚拟线程跑,每个虚拟线程执行100条,因为每次执行 sql 调用同步 jdbc api 都要等待,就算不阻塞系统线程,也要切换虚拟线程,所以实际上发生了10万次切换操作。
@zhh-4096
异步 jdbc api + nio 性能好于 同步 jdbc api + bio,我发现在这两者之间有个折中方案: 虚拟线程 + 同步 jdbc api + nio,也就是说应用在虚拟线程中调用同步 jdbc api,然后 jdbc 驱动使用 nio 跟后端的数据库通信。如果 jdbc 驱动依然使用 bio,搭配虚拟线程可能会更差。
vertx 的 mysql 和 postgresql 异步客户端都测了,没惊喜,两个客户端的事件调度器都是 vertx,所以核心差异小,只是数据库协议和配置参数的差别。//@奇峰cc :和vertx的async pg client比过吗?
@zhh-4096
lealone 的异步 jdbc api + nio 这套组合真的太炸裂了,跑select/insert/update/delete,全都比 mysql、postgresql 快10几倍!谁能告诉我 mysql、postgresql 有哪些靠谱一点的 java 异步客户端,我要挑战它,vertx 的异步 sql 客户端太挫了,打得不过瘾。
异步 jdbc api + nio 性能好于 同步 jdbc api + bio,我发现在这两者之间有个折中方案: 虚拟线程 + 同步 jdbc api + nio,也就是说应用在虚拟线程中调用同步 jdbc api,然后 jdbc 驱动使用 nio 跟后端的数据库通信。如果 jdbc 驱动依然使用 bio,搭配虚拟线程可能会更差。
把最常用的操作都压测了一遍,lealone 的性能已经打败了世界上最流行的3个开源关系数据库:mysql、postgresql、sqlite,还挺自豪的!
@zhh-4096
linux、git 虽然是群体智慧的产物,但依然被认为是 linus 的两个作品。一个人死后能留下一两个作品就已经很成功了,我做了20多年技术,也希望最后能留下一个作品,我给自己定的目标: 在 client-server 和 嵌入式 这两个场景,在所有开源 oltp 关系数据库中,性能要做到第一。这就是目前的技术追求。
Vertx 有个 MySQL 的异步客户端,测试了一下,调了很多参数,只是比 MySQL 的 JDBC 客户端好一点,Vertx 的 EventLoopPoolSize 默认是 cpu 核数的两倍,如果用它的默认值更慢,我改成了8反而快一点。16个并发,Vertx 跟 Lealone 的异步 JDBC 客户端还是差好远。
lealone 的异步 jdbc api + nio 这个组合确实很炸裂,16个并发,随机单行更新,平均时间比 mysql 和 postgresql 快10几倍,insert、delete 也是差不多的测试数据。异步 jdbc api 没有流行真的太可惜了,那么炸裂的性能。
不管是 java 的虚拟线程还是 go 的 goroutine 或者数据库内部的任务,想在用户态用少量系统线程调度好这些任务,那就必需确保执行任务的所有代码都不会阻塞系统线程。java 的虚拟线程在实际应用中大打折扣,就是因为现有的存量代码多是同步风格的,很容易导致系统线程被阻塞。而 lealone 之所以做得好,是因为数据库这个场景的代码是可控的,并且实现了全链路异步化,执行数据库内部的任务时,至少能保证高频率运行的代码不会阻塞系统线程。
java 的虚拟线程还是挺让人失望的。
用虚拟线程跑了一下,虽然没有系统线程切换开销了,但是 ThreadPerTaskExecutor 在8核cpu跑16个虚拟线程的结果,还不如开16个Thread直接跑快,执行200条sql平均时间比虚拟线程快了一倍。
java 的虚拟线程还是没有优化好,我估计代码中运行到一些特殊的地方导致当前虚拟线程会阻塞系统线程从而变慢。比如在jdbc客户端执行sql的所有代码中一定会遇到synchronized的代码的,虚拟线程遇到synchronized就会阻塞系统线程,因为ThreadPerTaskExecutor内部只开了8个系统线程,如果其中之一被阻塞了,自然就影响跟它相关的所有虚拟线程。
@zhh-4096
当并发的线程数远超 cpu 核数时,在每个线程中连续执行200条和2000条 sql 然后计算每条 sql 的执行时间,居然会有很大差异,执行200条 sql 时算出的平均时间更小。可能是受操作系统时间分片算法的影响,执行2000条 sql 时线程被切换的次数增多,所以总时间里把线程切换的时间算在内就变多了。
@恰饭家族 postgresql 在事务中用 SET LOCAL synchronous_commit = on 开启实时 fsync 是可行的,在实现上如果支持多语句就能节省一次网络开销,也就是在 jdbc 中调用 execute 方法可以同时把 set 语句和 insert 语句传到数据库,甚至把 begin 和 commmit 也一起传过去,这样才是最高效的,但无论如何,执行 set 语句总会消耗一点时间。如果不想每次执行 set 语句,只能在 session 级设置,然后在客户端维护一个专门的 fsync jdbc 连接即可。
补充说明一下,lealone 的 jdbc 连接创建成本很低的,比在客户端单独执行一条 set 语句的成本还低,很适合在客户端单独维护一条 fsync 的专用连接,就算底层的 tcp 连接断了,也会自动重连,维护的专有 fsync jdbc 连接一直可用。
我不想再花时间做 mysql 和 postgresql 的兼容插件了,时间宝贵,要去做更有意义更有挑战的事。真正获得巨大成功的数据库从来不是靠兼容别人,而是要有自己的特色和核心竞争力。
在 postgresql 14 用 postgres 用户试了一下 SET fsync = on 和 SET LOCAL fsync = on 都不起作用了,直接报错 ERROR: parameter "fsync" cannot be changed now。用 SET 语句理论上是可以实现,但多执行一条 SET 语句,对事务的整体执行时间有一定影响。无非是参数作用域的问题,在 session 级设置就不用每次事务执行一下 SET 语句。//@恰饭家族:不用参数的!事务内用一条sql语句开启fsync同步写盘!共用此连接的其它sql依然是异步写盘!所以是最科学的方案!//@zhh-4096:这个方式的确是个折中方案,实现也不难,只是需要在客户端创建连接时配置一个参数,因为 jdbc 没有设置 fsync 的 api,需要在一个特殊的 jdbc 连接中执行这种事务。//@恰饭家族:PG默认fsync,可以关掉,然后程序可以决定对哪一个事务按需开启fsync,如转账事务,更科学!
@zhh-4096
不管开不开启实时 fsync,都不会破坏数据库的完整性,只是对用户体验不同而已。开启实时 fsync 后,如果数据写硬盘失败了,用户可以马上获得错误通知,然后让用户决定是重试还是取消;不开启实时 fsync,用户收到成功消息后,若写硬盘失败了,再让用户重试确实体验不好,但是99.99%的场景体验更好。
当对结婚不抱太多期待后,我就不为年老色衰焦虑了。我现在只希望脑力衰退慢一些,最好能用到80岁,只要脑子还够用,哪怕没有女人,生活也不会太无聊。
