V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
leochan32767
V2EX  ›  Python

求教大神一个 flask 技术问题,百思不得其解

  •  
  •   leochan32767 · 2019-01-29 18:37:46 +08:00 · 3737 次点击
    这是一个创建于 2126 天前的主题,其中的信息可能已经有所发展或是发生改变。

    使用 python 的 flask 框架搭建了一个服务,用于公司内部的管理系统,公司比较大,员工比较多,有时候访问比较频发,最近发生了如下的一个问题,苦思冥想各种解决方法都试过了,还是无法解决,江湖告急 1 用的数据库是 mysql
    2 表的引擎是 innodb 3 orm 用的是 SQLAlchemy

    问题:

    添加数据的时候,使用 load.reqMoney = 100000 db.session.add(load) db.session.commit() 并且在添加数据前使用了 db.session.query(func.sum(Load.reqMoney)).filter(Load.uid == xxx).scalar() 的语句,进行查询 load 的 reqMoney 的总和 ,然后判断是否允许添加数据 [总和大于某一值时,不允许添加]

    但是生产环境的时候,经常会出现 !!!判断失效的情况!!!,用户连续点击添加数据(请求时间非常短), 判断失效,用户可以添加总和大于某一值的情况,请问这个是为什么,怎么解决 [个人对数据库的知识比较薄弱,是否是数据库锁的问题?代码层面怎么解决] ?

    能帮小弟解决的大神送小蓝杯咖啡十杯,以表敬意

    16 条回复    2019-01-29 23:06:00 +08:00
    pabupa
        1
    pabupa  
       2019-01-29 19:45:13 +08:00 via Android
    https://www.v2ex.com/t/531596

    看一下这个帖子吧,,,应该有帮助。
    kaneg
        2
    kaneg  
       2019-01-29 19:53:53 +08:00 via iPhone
    你的 flask server 应该是运行在多线程或者多进程模式,所以出现了并发问题。

    如果是多线程模式,不考虑太复杂的情况下,可以加个锁
    leochan32767
        3
    leochan32767  
    OP
       2019-01-29 20:17:34 +08:00
    @kaneg 的确,开了 16 个 gunicorn 请问下加锁有什么教程吗?
    wwqgtxx
        4
    wwqgtxx  
       2019-01-29 20:34:23 +08:00
    借助 redis 或者是 multiprocessing.Manager 很容易实现跨进程锁,我自己还造过这方面的轮子
    https://github.com/wwqgtxx/RedisTools
    kaneg
        5
    kaneg  
       2019-01-29 20:36:34 +08:00
    可以看看这个链接最后的 Lock 部分: http://blog.jobbole.com/52060/
    Trim21
        6
    Trim21  
       2019-01-29 20:38:25 +08:00 via Android
    你需要一个分布式锁
    okwork
        7
    okwork  
       2019-01-29 21:09:19 +08:00
    多个 worker 不加锁,出现超卖的情况不可避免,严谨的做法就是加锁。也有粗暴的做法,很多秒杀排队也是简单粗暴的抛弃请求,比如你可以把相邻时间小于 1 秒钟的请求丢弃,具体返回个什么值或提升,根据具体场景设计。
    cz5424
        8
    cz5424  
       2019-01-29 21:15:08 +08:00 via iPhone
    看看能否把高频的判断值扔在 redis,或者函数上 redis 锁,保证高并发只有单个在处理
    guog
        9
    guog  
       2019-01-29 21:22:26 +08:00 via Android
    最简单的就是使用 redlock,一个基于 Redis 的简单锁。加几行代码的事。
    ziding
        10
    ziding  
       2019-01-29 21:23:58 +08:00
    pg 的话可以意向锁或者 for updte,mysql 看看有没有 for update 的等价物。
    dagger
        11
    dagger  
       2019-01-29 21:47:25 +08:00   ❤️ 1
    查询语句把用户表一起 join 出来,用 for update 锁住,记得建好索引,不然 mysql 的 for update 会锁全表
    zeraba
        12
    zeraba  
       2019-01-29 21:53:24 +08:00 via Android   ❤️ 1
    锁 MySQL 已经自己做了,判断大于总和这个逻辑可以在执行插入的时候判断 比如 update price where amount > 10000 而不是前端拿这个作为条件去判断能否执行插入
    ericls
        13
    ericls  
       2019-01-29 22:32:35 +08:00 via iPhone
    在数据库那边应该加个锁
    MySQL 有 select for update. Isolation 等级可能要改一下
    zsen
        14
    zsen  
       2019-01-29 22:40:02 +08:00
    如果基于以下的条件:
    1、用于公司内部的管理系统
    2、用户连续点击添加数据(请求时间非常短)

    前端加一个点击后禁掉点击事件,根据后台返回结果再处理是否可行呢?
    tomczhen
        15
    tomczhen  
       2019-01-29 23:03:20 +08:00
    数据库上根据 select 结果来 update,多线程下要么使用幂等逻辑,类似:update status = 1 where id =1 and status =0; update sum = 19 where id= 1 and sum = 10,要么 select 就要上锁。

    加锁是为了保证一致性,会影响并发数。而且还得注意索引,确保锁的粒度,当然,内部系统没那么讲究的话其实也没所谓。

    redis 也行,毕竟单线程。另外如果取 key,重新写 value 不是原子( redis lua )操作的话,还是会出现并发问题。

    至于前端限制,为了并发最好也是要做的,减少无谓的请求,web 页面可以考虑 Redirect After Post。
    alvin666
        16
    alvin666  
       2019-01-29 23:06:00 +08:00 via Android
    并发大的话用乐观锁,改一下表结构和 update 语句就行了,并发小的话用悲观锁
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   969 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 19:25 · PVG 03:25 · LAX 11:25 · JFK 14:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.