部署方式改变引起的用户计数问题

本文探讨了在从Django的manage.pyrunserver切换到Apache部署时,因多进程模型导致的单例模式数据错乱问题。通过对比prefork与worker模式,分析了问题原因,并提出了解决方案,即使用悲观锁确保数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结论先行

前几天在公司用户反馈系统,统计某项活动反馈用户数时,发现原始的用户请求数据表与汇总的表结果不一致。问题原因是:不久之前项目改用了apache(之前是用Django 方式 manage.py runserver 启动服务)进行部署。

分析

先讲述下apache的2种工作模式

  1. prefork 多进程模型

预先生成进程,一个请求用一个进程处理。优点:稳定可靠,任何一个进程崩溃都不会影响其他进程;缺点:性能差,特别是并发量非常大时,非常消耗内存资源,会有大量的进程切换。

  1. worker 多线程模型

基于线程,启动多个进程,每个进程中生成多个线程。进程负责响应请求,而线程负责处理请求。因为linux不是原生支持线程,线程同步模型复杂,必须处理锁争用的问题,所以在Linux上不建议。测试发现,在linux系统上,worker 的效率并没有 prefork 高。这是默认不是worker的原因。

采用Django manage.py runserver 启动服务实际测试是1个进程处理服务

定位

apache多进程处理请求和Django单个进程处理请求,本质上没有啥区别啊,只是最大处理请求量上会存在一些差别。为什么会影响用户计数了。仔细看了下计数业务的代码,发现采用的是单例模式。很明显在apache多进程模式下,单例模式是存在问题的。每个独立的进程对数据库同一行进行读写操作在没有加锁的情况下,会出现数据错乱问题。

class _SuiteCurrentGroup:
    _class_lock = threading.Lock()
    _instances = {}
    _index = None

    def __init__(self, id):
        self.id = id
        self._lock = threading.Lock()

    @classmethod
    def get_instance(cls, id):
        if id not in cls._instances:
            with cls._class_lock:
                _SuiteCurrentGroup._do_init(id)
                value = _SuiteCurrentGroup.get_and_increase(id)
                cls._instances[id] = value
                return value
        else:
            with cls._class_lock:
                value = _SuiteCurrentGroup.get_and_increase(id)
                cls._instances[id] = value
                return cls._instances[id]

    @classmethod
    def _do_init(cls, id):
        cur_group_index_list = Suite.objects.filter(id=id).values_list('cur_group_index', flat=True)
        cls._index = cur_group_index_list[0]

    @classmethod
    def get_and_increase(cls, id):
        old_value = cls._index
        cls._index += 1
        Suite.objects.filter(id=id).update(cur_group_index=cls._index)
        return old_value

解决

采用悲观锁的方式进行数据更新

 with transaction.atomic():
            suite = Suite.objects.select_for_update().get(id=suite_id)
            cur_group_index = suite.cur_group_index
            suite.cur_group_index = suite.cur_group_index + 1
            suite.save()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值