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倍。

 

由此,你能想到什么更有意思的用法了吗?