NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netstandard,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。
整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。
开源地址:https://github.com/NewLifeX/X (求star, 729+)
累加的需求背景
一个网站,部署了两台应用服务器,共用数据库,其中文章表有个访问次数的字段。
现在需要记录访问次数,需要怎么做?
var entity = Article.FindByID(9527); entity.Views++; entity.Update();
如果两台服务器都有用户访问了9527这篇文章,访问前Views是1000,访问后是多少?1002?大部分情况下是1002,少数情况下可能是1001。
如果每台服务器都有100个用户同时访问这篇文章呢?那可就精彩了,最后访问数可能是1001到1200之间的某个数。
按照教科书做法,我们似乎应该开个事务加个锁,确保同时只能有一个用户(线程)修改这一行数据。
且不说加锁和事务成本有多高,光是为了一个字段就锁住这一行导致用户无法更新这一行其它字段,就让人觉得挺不地道的。再者,访问次数对于其它字段来说,也许并没有那么重要。
聪明如你,可以想到这么一个办法:
update article set views=views+1 where id=9527
哈,这就是XCode增量累加的出发点,每个用户(线程)执行自己的那一次,不管排队先后,最终结果都将会是1200。
设置增量累加
在实体类静态构造函数中,可以设置需要增量累加的字段
向 Meta.Factory.AdditionalFields 添加需要采用增量累加的字段,执行update时才生成 x=x+123 样子的语句。
测试代码:
Update VisitStat Set Times=Times+123,Users=Users+1,IPs=IPs+1,UpdateTime='2019-03-26 22:36:14' Where ID=1
从输出效果看到,产生了累加效果。并且,这段代码不管执行多少次,都是这样的累加效果,而不管实际值是多少。
累加原理
从数据库查出来一个对象时,如果发现有设置累加字段,XCode会把此时的数据“备份” 下来。
在执行update保存的时候,拿累加字段的最后值减去原始备份值,得到差值(可能是负数),生成 x=x+123 或 x=x-456 的语句。
不光整数,小数也可以设置累加字段。
需要注意的是,如果字段x允许空,并且要更新行的x字段刚好为NULL,x=x+123 将会得不到预期效果。
高级用法
再看开头的例子,即使使用了累加,不需要加锁以及开事务,仍然需要update数据库200次。
借助累加以及异步保存功能,可以把这个次数大大降低。
var entity = Article.FindByID(9527); entity.Views++; entity.SaveAsync(5_000);
先把Views设为累加字段。
Article.FindByID内部可以用对象缓存,然后每台应用服务器在10秒(默认缓存时间)内多线程查到的都是同一个entity对象。
SaveAsync将把对象entity放入实体队列,5秒后延迟保存。如果200用户访问集中在5秒内,最后每台服务器只会执行一次update操作。
Update Article Set Viewss=Views+100 Where ID=9527
数据库写入次数由200次下降到2次,提升100倍。
由此,你能想到什么更有意思的用法了吗?