谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
反应式编程在客户端编程当中的应用相当广泛,而当前在服务端中的应用相对被提及较少。本篇将介绍如何在服务端编程中应用响应时编程来改进数据库操作的性能。
开篇就是结论
接续上一篇《谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒》之后,这次,我们带来了关于利用反应式编程进行 upsert 优化的案例说明。建议读者可以先阅读一下前一篇,这样更容易理解本篇介绍的方法。
同样还是利用批量化的思路,将单个 upsert 操作批量进行合并。已达到减少数据库链接消耗从而大幅提升性能的目的。
业务场景
在最近的一篇文章《十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验》中。我们通过激活多个常驻于内存当中的 Claptrap 来实现快速验证 JWT 正确性的目的。
但,当时有一个技术问题没有得到解决:
Newbe.Claptrap 框架设计了一个特性:当 Claptrap Deactive 时,可以选择将快照立即保存到数据库。因此,当尝试从集群中关闭一个节点时,如果节点上存在大量的 Claptrap ,那么将产生大量的数据库 upsert 操作。瞬间推高数据库消耗,甚至导致部分错误而保存失败。
一点点代码
有了前篇的 IBatchOperator,那么留给这篇的代码内容就非常少了。
首先,按照使用上一篇的 IBatchOperator 编写一个支持操作的 Repository,形如以下代码:
public class BatchUpsert : IUpsertRepository |
然后,只要实现对应数据库的 UpsertMany 方法,便可以很好地完成这项优化。
各种数据库的操作
结合 Newbe.Claptrap 现在项目的实际。目前,被支持的数据库分别有 SQLite、PostgreSQL、MySql 和 MongoDB。以下,分别对不同类型的数据库的批量 Upsert 操作进行说明。
由于在 Newbe.Claptrap 项目中的 Upsert 需求都是以主键作为对比键,因此以下也只讨论这种情况。
SQLite
根据官方文档,使用 INSERT OR REPLACE INTO 便可以实现主键冲突时替换数据的需求。
具体的语句格式形如以下:
INSERT OR REPLACE INTO TestTable (id, value) |
因此只要直接拼接语句和参数调用即可。需要注意的是,SQLite 的可传入参数默认为 999,因此拼接的变量也不应大于该数量。
PostgreSQL
众所周知,PostgreSQL 在进行批量写入时,可以使用高效的 COPY 语句来完成数据的高速导入,这远远快于 INSERT 语句。但可惜的是 COPY 并不能支持 ON CONFLICT DO UPDATE 子句。因此,无法使用 COPY 来完成 upsert 需求。
因此,我们还是回归使用 INSERT 配合 ON CONFLICT DO UPDATE 子句,以及 unnest 函数来完成批量 upsert 的需求。
具体的语句格式形如以下:
INSERT INTO TestTable (id, value) |
其中的 ids 和 values 分别为两个等长的数组对象,unnest 函数可以将数组对象转换为行数据的形式。
注意,可能会出现 ON CONFLICT DO UPDATE command cannot affect row a second time 错误。
因此如果尝试使用上述方案,需要在传入数据库之前,先在程序中去重一遍。而且,通常来说,在程序中进行一次去重可以减少向数据库中传入的数据,这本身也很有意义。
MySql
MySql 与 SQLite 类似,支持 REPLACE 语法。具体语句形式如下:
REPLACE INTO TestTable (id, value) |
MongoDB
MongoDB 原生支持 bulkWrite 的批量传输模式,也支持 replace 的 upsert 语法。因此操作非常简单。
那么这里展示一下 C# 操作方法:
private async Task SaveManyCoreMany( |
这是从 Newbe.Claptrap 项目业务场景中给出的代码,读者可以结合自身需求进行修改。
通用型解法
优化的本质是减少数据库链接的使用,尽可能在一个链接内完成更多的工作。因此如果特定的数据库不支持以上数据库类似的操作。那么还是存在一种通用型的解法:
- 以尽可能快地方式将数据写入一临时表
- 将临时表的数据已连表 update 的方式更新的目标表
- 删除临时表
性能测试
以 SQLite 为例,尝试对 12345 条数据进行 2 次 upsert 操作。
单条并发:1 分 6 秒
批量处理:2.9 秒
样例中不包含有 MySql、PostgreSQL 和 MongoDB 的样例,因为没有优化之前,在不提高连接池的情况下,一并发基本就爆炸了。所有优化的结果是直接解决了可用性的问题。
所有的示例代码均可以在代码库中找到。如果 Github Clone 存在困难,也可以点击此处从 Gitee 进行 Clone
常见问题解答
此处对一些常见的问题进行解答。
客户端是等待批量操作的结果吗?
这是一个很多网友提出的问题。答案是:是的。
假设我们公开了一个 WebApi 作为接口,由浏览器调用。如果同时有 100 个浏览器同时发出请求。
那么这 100 个请求会被合并,然后写入数据库。而在写入数据库之前,这些客户端都不会得到服务端的响应,会一直等待。
这也是该合并方案区别于普通的 “写队列,后写库” 方案的地方。
原理上讲,这种和 bulkcopy 有啥不一样?
两者是不相关,必须同时才有作用的功能。
首先,代码中的 database.InsertMany 就是你提到的 bulkcopy。
这个代码的关键不是 InsertMany ,而是如何将单次的插入请求合并。
试想一下,你可以在 webapi 上公开一个 bulkcopy 的 API。
但是,你无法将来自不同客户端的请求合并在同一个 API 里面来调用 bulkcopy。
例如,有一万个客户端都在调用你的 API,那怎么合并这些 API 请求呢?
如果如果通过上面这种方式,虽然你只是对外公开了一个单次插入的 API。你却实现了来自不同客户端请求的合并,变得可以使用 bulkcopy 了。这在高并发下很有意义。
另外,这符合开闭的原理,因为你没有修改 Repository 的 InsertOne 接口,却实现了 bulkcopy
的效果。
如果批量操作中一个操作异常失败是否会导致被合并的其他操作全部失败?
如果业务场景是合并会有影响,那当然不应该合并。
批量操作一个失败,当然是一起失败,因为底层的数据库事务肯定也是一起失败。
除非批量接口也支持对每个传入的 ID 做区别对待。典型的,比如 mongodb 的 bulkcopy 可以返回哪些成功哪些失败,那么我们就有能力设置不同的 Tcs 状态。
哪些该合并,哪些不该合并,完全取决于业务。样例给出的是如果要合并,应该怎么合并。不会要求所有都要合并。
Insert 和 Upsert 都说了,那 Delete 和 Select 呢?
笔者笼统地将该模式称为 “反应式批量处理”。要确认业务场景是否应用该模式,需要具备以下这两个基本的要求:
- 业务下游的批量处理是否会比累积的单条处理要快,如果会,那可以用
- 业务上游是否会出现短时间的突增频率的请求,如果会,那可以用
当然,还需要考量,比如:下游的批量操作能否却分每个请求的结果等等问题。但以上两点是一定需要考量的。
那么以 Delete 为例:
- Delete Where In 的速度会比 Delete = 的速度快吗?试一下
- 会有突增的 Delete 需求吗?想一下
小小工具 Zeal
笔者是一个完整存储过程都写不出来的人。能够查阅到这些数据库的文档,全靠一款名为 Zeal 的离线文档查看免费软件。推荐给您,您也值得拥有。

Zeal 官网地址:https://zealdocs.org/
最后但是最重要!
最近作者正在构建以反应式、Actor模式和事件溯源为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。
如果你对该项目感兴趣,你可以通过 github issues 提交您的看法。
如果您无法正常访问 github issue,您也可以发送邮件到 newbe-claptrap@googlegroups.com 来参与我们的讨论。
点击链接 QQ 交流【Newbe.Claptrap】:https://jq.qq.com/?_wv=1027&k=5uJGXf5。
您还可以查阅本系列的其他选文:
- Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作为基本理论的服务端开发框架
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- Newbe.Claptrap 项目周报 1 - 还没轮影,先用轮跑
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap

- 本文作者: newbe36524
- 本文链接: https://www.newbe.pro/Newbe.Claptrap/Reactive-In-Server-2/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert的更多相关文章
- [转帖]浅谈响应式编程(Reactive Programming)
浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...
- atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系
atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系 1. 服务器控件是可被服务器理解的标签.有三种类型的服务器控件: 1 1.1. HTML 服务器控件 ...
- Java服务端对Cookie的简单操作
Java服务端对Cookie的简单操作 时间 2016-04-07 10:39:44 极客头条 原文 http://www.cuiyongzhi.com/index.php/post/15.html ...
- EF5.0中的跨数据库操作
以前在用MVC + EF 的项目中,都是一个数据库,一个DbContext,因此一直没有考虑过在MVC+EF的环境下对于多个数据库的操作问题.等到要使用时,才发现这个问题也不小(关键是有个坑).直接说 ...
- 【网络编程】服务端产生大量的close_wait状态的进程分析
首先要明白close_wait状态是在tcp通信四次握手时的一个中间状态: 即当被动关闭方发送完ACK后进入的状态.这个状态的结束,即要达到下一个状态LASK_ACK需要在发无端发送完剩余的数据后(s ...
- Java网络编程(TCP服务端)
/* * TCP服务端: * 1.创建服务端socket服务,并监听一个端口 * 2.服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象 * 3.可以通过获 ...
- 游戏服务端中使用Servlet和Java注解的一个好设计
SNS类游戏基本都是使用HTTP短连接,用Java来开发服务端时能够使用Servlet+Tomcat非常轻松的架构起服务端来.在这里介绍一种使用Servlet比較好的一种设计,我也见过非常多基于HTT ...
- java网络编程-单线程服务端与客户端通信
该服务器一次只能处理一个客户端请求;p/** * 利用Socket进行简单服务端与客户端连接 * 这是服务端 */public class EchoServer { private ServerSoc ...
- 关于调试php的socket服务端中遇到的问题及解决办法
今天终于把socket的服务端解决了,期间遇到了很多问题呢~ 1.用cmd运行php的问题: 2.socket_create()函数未定义问题: 3.查看端口的问题. 以下逐一说说解决办法: 1.在c ...
随机推荐
- 【Mybatis plus 3.2】怎么操作?看看我!(update、limit、between)
必须是springboot工程 在pom.xml中添加 <dependency> <groupId>com.baomidou</groupId> <artif ...
- 使用turtle库画同切圆
import turtle as t t.setup(600,600,None,None) t.pensize(5) t.penup() t.pendown() t.pencolor("re ...
- Java实现 LeetCode 409 最长回文串
409. 最长回文串 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串. 在构造过程中,请注意区分大小写.比如 "Aa" 不能当做一个回文字符串. 注意 ...
- Java实现 蓝桥杯VIP 算法提高 企业奖金发放
算法提高 企业奖金发放 时间限制:1.0s 内存限制:512.0MB 企业发放的奖金根据利润提成.利润低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10% ...
- 面试三轮我倒在了一道sql题上——sql性能优化
一.前言 最近小农在找工作,因为今年疫情的特殊原因,导致工作不是特别好找,所以一旦有面试电话,如果可以,都会去试一试,刚好接到一个面试邀请,感觉公司还不错,于是就确定了面试时间,准备了一下就去面试了. ...
- HashMap解析(主要JDK1.8,附带1.7出现的问题以及区别)
按问题的形式来吧,这些大多是我自己总结的,如有错误请及时指正谢谢 1.你了解HashMap么,可以说说么? 首先,HashMap是一种数据结构,可以快速的帮我们存取数据.它的底层数据结构在1.7和1. ...
- SSM框架处理跨域问题
什么是跨域 跨域是指从一个域名的网页去请求另一个域名的资源.比如从www.baidu.com 页面去请求 www.google.com 的资源.跨域的严格一点的定义是:只要 协议,域名,端口有任何一个 ...
- 附020.Nginx-ingress部署及使用
一 手动部署-官网版 1.1 获取资源 [root@master01 ~]# mkdir ingress [root@master01 ~]# cd ingress/ [root@master01 i ...
- 【loj - 3055】「HNOI2019」JOJO
目录 description solution accepted code details description JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或 ...
- Swift Core Data 图片存储与读取
1.首先推出选择拍照还是相册的alert,代码如下: UIAlertController *alert = [UIAlertController alertControllerWithTitle:ni ...