请问 Django 并发条件下,生成雪花 ID 为什么会重复? - V2EX
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
Phishion
V2EX    Python

请问 Django 并发条件下,生成雪花 ID 为什么会重复?

  •  
  •   Phishion 2021-07-30 13:31:32 +08:00 4148 次点击
    这是一个创建于 1535 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我用的是别人写好的模块,如下

    https://github.com/tarzanjw/pysnowflake/blob/master/snowflake/server/generator.py

    我首先自己跑单线程测了一下,完全不会有重复,多线程在加线程锁的情况下也完全没有发生重复。

    但是我在实际项目中生成的时候,大概 10 条线提交总共 1600 条数据,每次都会产生大概几十条重复

    我打印过 ID,这个雪花生成器实例并没有被初始化多个,请问如何排查?

    大致代码如下:

    from newsnow import Generator logger = logging.getLogger('django-production') get_flake_id = Generator(dc=0, worker=0) def create_product_meta(prepare_product_meta): new_product = models.productMeta( own_store=prepare_product_meta.get("own_store").upper(), product_name=prepare_product_meta.get("product_name"), ) logger.warning(id(get_flake_id)) new_product.flake_id = get_flake_id.get_next_id() return new_product 
    第 1 条附言    2021-07-31 00:20:16 +08:00

    目前问题已经修复,确定是多进程间 “无法共享实例状态” 导致的 SnowFlake ID 重复问题。

    我个人的解决方案是在生成 SnowFlake ID 前,直接动态获取 PID 的后 2 位作为 Worker ID,即如下写法

    flake_worker = int(str(os.getpid())[-2:])

    弄那么“不专业”的理由是,因为我观察到在 Docker 环境中,这个 ID 一般都是“连续生成”,大多是 100~200 范围内,而且我个人也就开了 4 个进程,再者我的程序本身也有重试逻辑,所以就这样简单的修改了。

    另外,本帖中尚未获得其他同学关于映射 PID 的解决方案,我也只是提供一个思路,因为标准的雪花 ID 的 worker 位严格较真的话只有 0~255 可选,直接放 PID 是绝对可能溢出的,所以必然存在一种转换方案,或者想办法找其他类似 PID 的东西作为物理隔离方案。

    希望帮助到同样受到这个问题困扰的人。

    26 条回复    2021-08-01 20:29:07 +08:00
    todd7zhang
        1
    todd7zhang  
       2021-07-30 13:42:15 +08:00   1
    如果生产是多进程跑的话,应该是初始化 Generator(dc=0, worker=0)这个时候,所有进程的 10bit 的 node_id 都是一样的,然后就重复了。可以考虑 worker=os.getpid()
    find456789
        2
    find456789  
       2021-07-30 13:43:04 +08:00
    有考虑过 hashid 吗, 这个支持很多语言,Python 、django 也有对应的 包
    LeeReamond
        3
    LeeReamond  
       2021-07-30 13:44:06 +08:00
    一个个人猜测是,因为 sleep 间隔固定的原因,线程数量一多了之后系统挨个唤醒,可能搞不好获取的时间戳也一样。
    cszchen
        4
    cszchen  
       2021-07-30 13:47:40 +08:00 via iPhone
    你有没有用到多进程
    cszchen
        5
    cszchen  
       2021-07-30 13:48:12 +08:00 via iPhone
    1 楼的方法可以解决
    Phishion
        6
    Phishion  
    OP
       2021-07-30 13:55:41 +08:00
    @todd7zhang
    @cszchen
    没有多进程,我就一台机器单跑了一个 Django 服务没有啥特别的设置
    Phishion
        7
    Phishion  
    OP
       2021-07-30 13:58:08 +08:00
    @cszchen
    @todd7zhang

    我使用了 uwsgi,请问下面的 processes 算多进程么?

    [uwsgi]
    chdir=/www/project/
    module=project.wsgi
    master=True
    processes=4
    chenqh
        8
    chenqh  
       2021-07-30 14:05:05 +08:00
    这种东西 py 最简单应该是用 redis 写一个把

    ```

    def util_redis_get_next_id_str(redis_client, name="id_sequence", mod_base=1000):
    """
    1 秒并发最多 mod_base 这么多,也就是 10W, 所以我并不怕
    """
    value = redis_client.incr(name)
    value = value % mod_base
    utcnow = datetime.datetime.utcnow()
    now = util_time_utc_to_local(utcnow)
    now = now.strftime("%Y%m%d%H%M%S")
    secOnd= int(time.time() * 1000) % 1000
    return '{}{:03d}{:03d}'.format(now, second, value)
    ```
    chenqh
        9
    chenqh  
       2021-07-30 14:05:45 +08:00
    @Phishion 算的应该
    Phishion
        10
    Phishion  
    OP
       2021-07-30 14:05:54 +08:00
    @cszchen
    @todd7zhang
    好像确实是这个问题,我把 processes 配置减少到 1,就没有发现重复了
    LemonK
        11
    LemonK  
       2021-07-30 14:10:43 +08:00
    snowflake 多进程需要不同的 worker 值,我是另外写了个方法分配的。如果是 docker 部署的话 pid 也有可能重复。
    Phishion
        12
    Phishion  
    OP
       2021-07-30 14:12:47 +08:00
    @LemonK 请问您有什么方法解决这个问题?
    Phishion
        13
    Phishion  
    OP
       2021-07-30 14:15:25 +08:00
    @find456789 我主要就想要一连串数字,实际业务上也需要显示出来
    cszchen
        14
    cszchen  
       2021-07-30 14:34:08 +08:00 via iPhone
    如果多台机器,可以用 redis 的原子特性来分配 workerid
    Phishion
        15
    Phishion  
    OP
       2021-07-30 14:41:42 +08:00
    @chenqh

    请问 pid 在整个程序运行周期会不断变化么?
    另外 pid 范围是 几十到上万不等,这个数字是远超出 work id 范围的,是要写一个映射么?
    Phishion
        16
    Phishion  
    OP
       2021-07-30 14:43:42 +08:00
    @cszchen 目前没有多台,我不想跑 redis 是因为目前没有其他地方用到这个,不想就为了生成 ID 单跑一个数据库
    chenqh
        17
    chenqh  
       2021-07-30 15:12:12 +08:00
    @Phishion 你没有用 celery 或者 rq 吗?
    est
        18
    est  
       2021-07-30 15:16:05 +08:00
    @Phishion 看源码

    self.node_id = ((dc & 0x03)<< 8) | (worker & 0xff)
    Phishion
        19
    Phishion  
    OP
       2021-07-30 15:32:09 +08:00
    @chenqh 有的,这个能获取到 pid 么
    Phishion
        20
    Phishion  
    OP
       2021-07-30 15:42:31 +08:00
    @est 这个不是一共支持 10 位,共 1024 个节点么,直接填 pid 号肯定有几率溢出啊,关键这一块儿我觉得也不是填 PID 的地方,只是 PID 确实能解决这个问题,最理想情况下,这个 worker 应该就是从 0 开始,多一个进程就加 1
    todd7zhang
        21
    todd7zhang  
       2021-07-30 15:44:36 +08:00
    差不多啦,uwsgi 启动的子进程的 pid 基本都是连续递增的,这边还有 worker & 0xff, 一台机器支持 256 个 worker 呢。
    实际启动的时候就那么几个进程,没那么巧就重复了吧,哈哈。
    caviar
        22
    caviar  
       2021-07-30 23:53:03 +08:00
    既然是用 uwsgi,直接拿 uwsgi 的 worker id 咯 https://uwsgi-docs.readthedocs.io/en/latest/API.html#uwsgi-worker-id
    Phishion
        23
    Phishion  
    OP
       2021-07-31 00:05:46 +08:00
    @caviar 实际上 os.getpid() 打印的就是 uwsgi 的 PID,通过观察我发现这个 ID 一般是连续生成,所以我直接取后 2 位数字,作为 worker ID
    caviar
        24
    caviar  
       2021-08-01 12:29:57 +08:00
    @Phishion os.getpid() 拿的是系统的 pid,个人并不觉得有连续的保证。uwsgi 提供的 worker id 是从 1 开始连续递增的。就像你前面说的,使用 pid 在大部分情况下不会有问题,但是既然有提供更好的 worker id,为什么不用呢。
    Phishion
        25
    Phishion  
    OP
       2021-08-01 13:32:32 +08:00
    @caviar 连续不连续实际上无所谓,后面 2 位不重复我觉得就可以了,总不能跳 100 个 PID 再生成第二个,uwsgi 的 worker ID 我还不知道怎么拿,配合重试逻辑,应该已经解决了,所以就没进一步修这个问题。
    ysw
        26
    ysw  
       2021-08-01 20:29:07 +08:00
    可用 redis 的分布式锁
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2629 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 06:08 PVG 14:08 LAX 23:08 JFK 02:08
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86