You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决WSGI多进程下Flask应用写入同一CSV文件的数据丢失问题

多进程环境下CSV文件写入冲突的解决方案

针对mod_wsgi多进程导致的同一CSV文件被频繁覆盖、数据丢失问题,以下是几个可行的解决办法:

1. 原子文件写入(最简便的跨平台方案)

核心思路是先将内容写入临时文件,确认写入完成后,再通过原子重命名操作替换目标CSV文件。文件系统的重命名操作在大多数操作系统中是原子性的,不会出现多个进程同时覆盖导致的文件损坏或数据丢失,且能保证下游读到的要么是完整的旧文件,要么是完整的新文件。

修改后的代码示例:

import os
import tempfile
from uuid import uuid4

def write_csv_files(self, destination: str) -> None:
    for filename in self.dataDict:
        filepath = os.path.join(destination, f"{filename}.csv")
        # 在目标目录创建临时文件,保证重命名的原子性
        temp_file = tempfile.NamedTemporaryFile(
            mode='w', 
            delete=False, 
            dir=destination,
            prefix=f"{filename}_tmp_{uuid4().hex}_",
            suffix=".csv"
        )
        try:
            # 写入内容到临时文件并强制刷入磁盘
            temp_file.write(gen_row_helper(self.dataDict[filename]))
            temp_file.flush()
            os.fsync(temp_file.fileno())
        finally:
            temp_file.close()
        
        # 原子重命名替换目标文件,失败则清理临时文件
        try:
            os.replace(temp_file.name, filepath)
        except OSError:
            os.remove(temp_file.name)
            raise

2. 文件锁机制(控制并发写入)

通过给目标文件加排他锁,确保同一时间只有一个进程能执行写入操作,其他进程需等待锁释放后再尝试。可以使用跨平台的portalocker库实现:

先安装依赖:

pip install portalocker

修改后的代码:

import os
import portalocker

def write_csv_files(self, destination: str) -> None:
    for filename in self.dataDict:
        filepath = os.path.join(destination, f"{filename}.csv")
        # 打开文件并加排他锁,写入完成后自动释放
        with open(filepath, "w") as fobj:
            try:
                portalocker.lock(fobj, portalocker.LOCK_EX)
                fobj.write(gen_row_helper(self.dataDict[filename]))
                fobj.flush()
                os.fsync(fobj.fileno())
            finally:
                portalocker.unlock(fobj)

该方案会让多进程排队等待锁,有一定性能损耗,但能保证每个进程的写入都不会被覆盖,适合对数据完整性要求极高的场景。

3. 剥离写入任务到独立进程(彻底避免竞争)

把CSV定时写入任务从Flask应用进程中分离出来,单独用一个定时进程负责写入操作,Flask进程仅负责收集数据并存入中间存储(如Redis、数据库)。

示例架构:

  1. Flask端数据收集逻辑:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)

def update_csv_data():
    # 每2分钟触发的数据更新逻辑
    data_dict = get_new_data()
    # 将CSV内容存入Redis
    r.hmset("csv_data", {k: gen_row_helper(v) for k, v in data_dict.items()})
  1. 独立定时写入进程:
from apscheduler.schedulers.blocking import BlockingScheduler
import os
import redis

r = redis.Redis(host='localhost', port=6379, db=0)
destination = "/path/to/csv/directory"

def write_csv_files():
    csv_data = r.hgetall("csv_data")
    for filename, content in csv_data.items():
        filename = filename.decode('utf-8')
        content = content.decode('utf-8')
        filepath = os.path.join(destination, f"{filename}.csv")
        with open(filepath, "w") as fobj:
            fobj.write(content)

if __name__ == "__main__":
    scheduler = BlockingScheduler()
    scheduler.add_job(write_csv_files, 'interval', minutes=2)
    scheduler.start()

这种方案从根源上消除了多进程竞争,架构更清晰,Flask进程可专注处理Web请求。


内容的提问来源于stack exchange,提问作者Kevin123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:17:35