V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
klgd
V2EX  ›  程序员

求助,支付系统的异步通知实现

  •  
  •   klgd · 2016-09-28 13:37:13 +08:00 · 11085 次点击
    这是一个创建于 2738 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前的实现方式:
    参考支付宝的异步通知,每个订单的异步通知实行分频率发送:15s 3m 10m 30m 30m 1h 2h 6h 15h
    脚本每秒请求数据库获取到时间需要发送的通知,返回成功则结束,失败则按下一个时间写入数据库,等待发送
    目前的方式虽然实现了但是 1 、每秒请求有点儿浪费资源; 2 、通知方式不稳定; 3 、无法承受大数据量等等

    现在想改为使用队列的方式实现,但是不知道如何实现通知返回失败,往队列里写下一次通知?
    方案 1 :到下一次发送通知时间时,写入队列,队列实时处理,但不知道该如何实现到指定时间写队列
    方案 2 :队列里延迟,也找到了可以实现这个延迟的队列 beanstalk,但是无法实现分布式,单台的方式公司运维不给弄,他建议用 kafka,但是 kafka 好像不可以延迟

    想请教大家,异步通知是实现方案,或者上面的问题怎么解决?

    27 条回复    2019-08-29 16:06:16 +08:00
    pubby
        1
    pubby  
       2016-09-28 13:52:52 +08:00
    benstalk 支持分布式的 类似 memcache ,客户端可以同时连几个 beanstalk 服务器
    beanstalkd 的队列数据也可以写入磁盘 log 的

    但是,如果一个挂掉,磁盘又出故障无法恢复的话,会丢失一部分队列数据
    klgd
        2
    klgd  
    OP
       2016-09-28 14:17:56 +08:00
    @pubby 看介绍说支持分布式,我安装了 php 扩展 https://github.com/nil-zhang/php-beanstalk/, phpinfo 里也看到有 beanstalk 扩展了,但是按照示例执行,始终返回 false ,不知道哪里的问题,而且扩展都是好几年前的了
    moro
        3
    moro  
       2016-09-28 14:20:19 +08:00
    可以用 golang 写服务来处理,自身包含队列,异步发送通知。
    klgd
        4
    klgd  
    OP
       2016-09-28 14:21:50 +08:00
    @moro golang 不会啊
    pubby
        5
    pubby  
       2016-09-28 14:22:56 +08:00
    @klgd 不要 so 扩展,不稳定。

    我用的是 composer require mrpoundsign/pheanstalk-5.3
    xss
        6
    xss  
       2016-09-28 14:28:46 +08:00
    rabbitmq 不可以?
    pubby
        7
    pubby  
       2016-09-28 14:30:07 +08:00
    @klgd 不过我用的不支持连多个 server ,你可以其他找找看
    moro
        8
    moro  
       2016-09-28 14:38:05 +08:00
    用 redis 的有序集合,按时间排序,每次把需要处理的事务插入进去。
    处理部分每秒读取一次有序集合,只取出当前时间的进行处理就可以了。
    moro
        9
    moro  
       2016-09-28 14:39:19 +08:00
    分布式的话可以在 redis 使用 lua 脚本来保证一致性。
    pubby
        10
    pubby  
       2016-09-28 14:54:06 +08:00
    klgd
        11
    klgd  
    OP
       2016-09-28 15:22:10 +08:00
    @xss 不知道是否可以 没用过 rabbitmq
    @moro 谢谢,我去尝试一下,每秒请求有没有好的方案?
    @pubby 谢谢你的提供,这个包应该是可以用, so 扩展确实不好用,读队列就用死循环?有没有其他好的方案?
    TangMonk
        12
    TangMonk  
       2016-09-28 15:28:04 +08:00
    rabbitmq 有个插件可以延迟发送
    moro
        13
    moro  
       2016-09-28 15:31:45 +08:00
    redis 每秒请求无压力,都是内存的。
    fansgentle
        14
    fansgentle  
       2016-09-28 15:36:33 +08:00
    Python 平台的话 Celery 就很赞 ...
    pubby
        15
    pubby  
       2016-09-28 16:20:57 +08:00
    @klgd 看了一下,这个库在 pool 上 reserve() job 的时候也有问题
    它是随机找一个 server 获取的,如果队列很空的话,可能会长时间饿死状态。

    put 也是随机找一个 server 写入


    可以改变一下应用方案:
    1. 生产者 put 的时候可以向所有 pool 里面随机挑选一个写入(内部已经实现)
    2. 每个消费者脚本只连一台 beanstalkd 进行任务处理。任务忙,可以多启动几个消费者连接同一台 beanstalkd 。
    accacc
        16
    accacc  
       2016-09-28 18:24:42 +08:00
    使用 redis 的 zset 结构做队列 score 写入时间戳 按照小于当前时间的出队
    hankwh
        17
    hankwh  
       2016-09-28 18:58:12 +08:00
    异步通知不好实现的话,你提供查询订单结果的接口就可以了,让接入方主动查询
    klgd
        18
    klgd  
    OP
       2016-09-28 19:35:28 +08:00
    @pubby 嗯,今天在试用时发现这个问题了,不过还没来得及思考如何和业务需求结合
    @hankwh 查询接口有提供
    @accacc redis 的排序功能?这个思路也不错,回头我想想怎么具体实现
    @fansgentle python 不会啊
    youxiaer
        19
    youxiaer  
       2016-09-28 19:41:03 +08:00
    beanstalk 做这个很合适
    sherlocktheplant
        20
    sherlocktheplant  
       2016-09-28 19:50:29 +08:00
    rabbitmq 好像可以实现你的功能 也支持分布式部署
    cszchen
        21
    cszchen  
       2016-09-28 20:32:36 +08:00
    可以用 php-resque ,本身是一个队列,支持定时执行。
    pubby
        22
    pubby  
       2016-09-28 20:49:40 +08:00
    印象中 rabbitmq 貌似没有 message 的优先级,几年前用过,至少 php 的 amqp 扩展没有支持优先级设置。

    rabbitmq 比较强大,能做消息系统
    beanstalk 只能做队列
    julyclyde
        23
    julyclyde  
       2016-09-29 11:50:14 +08:00
    用什么队列其实是无所谓的

    轻负载的情况下,队列长度基本保持在 0 ,也就是收到之后立刻就能处理
    当队列积压的情况下,虽然没及时确认,导致支付网关假确认,但你也没更好的办法了
    所以其实没啥需要担心的
    jerray
        24
    jerray  
       2016-09-29 12:08:33 +08:00
    延迟发送用 RabbitMQ 可以实现,并不需要任何 RabbitMQ 扩展。

    方案:

    比如说你有一个 Exchange EA ,一个队列 QA ,通过 EA 进来的消息会被分发到 QA 上, Consumer 监听着队列 QA ,一旦有消息就会被消费。
    然后创建一个 15s 消息超时的延时队列 QA_deferred_15s ,设置参数 x-message-ttl 为 15000 , x-dead-letter-exchange 为 EA 。

    QA 的 Consumer 消费队列消息时,如果认为需要延时重试,则把这条消息发送到 QA_deferred_15s 中。由于设置了 x-message-ttl 参数, 15 秒后, QA_deferred_15s 中的这条消息会超时。由于 x-dead-letter-exchange 设置为了 EA ,超时的消息会被发送到 EA ,再由 EA 分发给 QA 。

    依次类推,创建其他延时队列。大致流程就是这样:

    Publisher -> EA -> QA -> Consumer
    Consumer -> QA_deferred_15s -> message timeout -> EA
    Consumer -> QA_deferred_3m -> message timeout -> EA
    Consumer -> QA_deferred_10m -> message timeout -> EA
    ...

    需要在 Consumer 中改写消息,以便下次需要重试时能知道把消息丢进哪个延时队列。
    klgd
        25
    klgd  
    OP
       2016-09-30 09:00:11 +08:00
    @jerray 感谢回复,写的很详细,我让我们运维看看, RabbitMQ 没用过,不懂这块的东西
    inputnames
        26
    inputnames  
       2017-12-13 09:21:55 +08:00
    楼主,请问你支付解决了吗,我也遇到同样的问题。求帮助呀
    Evilk
        27
    Evilk  
       2019-08-29 16:06:16 +08:00
    @jerray 赞! 目前正打算用 RabbitMQ 来做此功能,跟你描述的完全一样,如果延迟时间相同,则可只创建一个死信队列,如果延迟时间不同,则需要为每种延迟时间创建对应的死信队列
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5311 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 09:25 · PVG 17:25 · LAX 02:25 · JFK 05:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.