本文主要知识点:
1.使用Django的signals来获取Model的新建/删除操作更新
2.使用数据库的select for update来正确处理并发的数据库操作
3.使用redis的sorted set来缓存计数器的修改操作
一,起始
我们要实现消息未读记录,常规做法:
定义一个类,当用户有一条消息未读,就生成一条记录。你可以通过以下方式获取指定用户未读消息数目:
当你的Notification表比较小的时候,这样的方式没有任何问题。但是随着业务的增大,表中的数据可能有上亿条记录。那么随之用户的未读消息数,也会有所提高。
这时候,你就需要实现一个计数器,让这个计数器来统计每个用户的未读消息数,这样 比起之前的 count() ,我们只需要执行一条简单的主键查询(或者更优)就可以拿到实时的未读消息数了。
二,优化
首先 ,我们得建立一个新表来存储每个用户的未读消息数。
我们为每个注册的用户提供一条对应的记录,来保存用户的未读消息数。
那么重点就是,我们如何知道什么时候应该更新unread_count呢?
这就要说到Django给我提供的强大的信号机制,从而方便我们实时检测计数器。
为了实时更新我们的计数器,我们必须做到以下几点:
1.当有新的未读消息过来的时候,计数器+1
2.当消息被异常删除时,如果关联的消息未读,为计数器-1
3.当阅读完一个新消息的时候,计数器-1
django.db.models.signals.pre_save & django.db.models.signals.post_save 表示的是 某个Model调用save方法之前和之后会触发的事件
现在,我们利用Django提供的两个信号机制来实现我们的实时更新计数器。
1.当有新的未读消息过来的时候,计数器+1
这样,每当你使用Notification.create或者.save()之类的方法创建新通知时,我们的 NotificationController 便会得到通知,为计数器 +1。
2.当消息被异常删除时,如果关联的消息为未读,为计数器 -1
3. 当阅读一个新消息的时候,为计数器 -1
接下来,当用户阅读某条未读消息的时候,我们也需要更新我们的未读消息计数器。 你可能会说,这有什么难的?我只要在我的阅读消息的方法里面,手动更新我的计数器不就好了?
但是,这样的实现方式有一个 非常致命的问题, 这个方式没有办法正常处理并发的请求。
例如,你拥有一个id为100的未读消息对象,这个时候同时有了两个请求过来,都要标记这个通知为已读
显而易见的,这两次方法都会成功的标记这条通知为已读,因为在并发的情况下, if notification.has_readed 这样的检查无法正常工作,所以我们的计数器将会被错误的 -1 两次 ,但其实我们只读了一条请求。
那么,这样的问题应该怎么解决呢?
基本上,解决并发请求产生的数据冲突只有一个办法: 加锁 ,介绍两种比较简单的解决方案:
使用 select for update 数据库查询
select ... for update 是数据库层面上专门用来解决并发取数据后再修改的场景的,主流的关系数据库 比如mysql、postgresql都支持这个功能, 新版的Django ORM甚至直接提供了这个功能的shortcut 。 关于它的更多介绍,你可以搜索你使用的数据库的介绍文档。
使用 select for update 后,我们的代码可能会变成这样:
除了使用``select for update``这样的功能,还有一个比较简单的办法来解决这个问题。
使用update来实现原子性修改
其实,更简单的办法,只要把我们的数据库改成单条的update就可以解决并发情况下的问题了:
这样,并发的标记已读操作也可以正确的影响到我们的计数器了。
三,高性能
我们可能会直接使用UPDATE 语句来修改我们的计数器,就像这样:
但是在生产环境中,这样的处理方式很有可能造成严重的性能问题,因为如果我们的计数器在频繁 更新的话,海量的Update会给数据库造成不小的压力。所以为了实现一个高性能的计数器,我们 需要把改动暂存起来,然后批量写入到数据库。
使用 redis 的 sorted set ,我们可以非常轻松的做到这一点。
使用sorted set来缓存计数器改动
redis是一个非常好用的内存数据库,其中的sorted set是它提供的一种数据类型:有序集合, 使用它,我们可以非常简单的缓存所有的计数器改动,然后批量回写到数据库。
通过以上的代码,我们把计数器的更新缓冲在了redis里面,我们还需要一个脚本来把这个缓冲区 里面的数据定时回写到数据库中。
通过自定义django的command,我们可以非常轻松的做到这一点:
之后,通过 python manage.py notification_update_counter 这样的命令就可以把缓冲区 里面的改动批量回写到数据库了。我们还可以把这个命令配置到crontab中来定义执行。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。