【问题标题】:concurrent and background task in elixir?长生不老药中的并发和后台任务?
【发布时间】:2018-10-08 22:51:05
【问题描述】:

我在控制器中有一个发送大量文本消息的功能。(1000 条消息) 控制器功能看起来像这样..

def send_sms(conn, _params) do
  // code is abbreviated
  # Spawn new process for sending message and saving to database
  spawn(fn -> send_message_and_save(user, recipients, phone_numbers) end)
  // code is abbreviated
end

还有这个功能

def send_message_and_save(user, recipients, phone_numbers) do
    // code is abbreviated 
    results = Sms.send_sms_with_messaging_service_async(phone_numbers, recipients.message, msg_sid, status_callback, account, token)

    case Sales.confirm_order(recipients, attrs) do
      {:ok, %{id: order_id, user_id: user_id}} ->
        Messenger.create_message_status(results, order_id, user_id)
        # Update Bitly status as Saved
        if is_nil(recipients.bitly_id) do
        else
          bitly = Texting.Bitly.get_bitly_by_id(recipients.bitly_id)
          Bitly.confirm_changeset(bitly) |> Bitly.update()
        end
        {:ok, "Message sent successfully. Your analytics data will be updated shortly."}
      {:error, _changeset} ->
        {:error, "Can't send message!"}
    end
end

在 send_message_and_save 函数中的作业过程是这样的 1.使用外部api请求发送1000条消息。 - 我使用 Task.Supervisor.async_stream/6 做到了这一点 2. confrim_order(将订单模式标记为“已确认”状态并更新(update) 3. create_message_status(创建操作) 4.get_bitly_by_id(获取操作) 5. Bitly.confirm_changeset 和 Bitly.update(更新操作)

在此过程中,总共将发生 5000 次数据库操作。 并且在向外部 api 请求发出请求后,对于每个外部 api 请求,我的网络服务器都会有三个状态回调请求。 这意味着,发送 1000 条消息将对 message_status 架构进行 300 万次更新操作(接受、已交付或未交付)。

所以发送 1000 条消息将结束 8000 次数据库操作,我在此尝试过,我的网站变得缓慢,并且由于超时而丢失了对我的网站的一些状态回调请求(外部 api 说“连接超时的原因有很多发生;常见原因是长时间运行的数据库查询或外部进程以及对外部系统的调用需要很长时间才能返回")

那么我该如何改善这种情况呢?我怎样才能正确设计这个? 请帮忙:(

【问题讨论】:

  • 你使用poolboy创建db连接池了吗?
  • 使用像rabbitmq这样的mq来做这个工作怎么样?
  • 一些数据库支持批量插入,这非常有用。您可以使用 poolboy 或管理您自己的数据库连接池,这样您就不会只使用一个连接。如果它是一个 http api,您仍然可以使用一个池或为每次写入/读取生成一个新进程。

标签: concurrency elixir phoenix-framework ecto


【解决方案1】:

我会开始使用ETS as a cache - 你知道所有需要从数据库中提取的信息,所以继续将它加载到内存中。然后,您可以启动一个负责 ETS 表的进程,并且您可以针对内存表中的该表进行所有未来的查询,这比为您需要向其发送消息的每个用户进行多次数据库调用要快得多。请注意,ETS 表只能在本地访问**,但如果您将其包装在一个进程中,那么该进程将像任何其他 OTP 进程一样在全球范围内可用。

许多应用程序的数据库存在瓶颈,因此您可以为数据库提供的工作越少越好(对于许多系统而言)。

** local 表示节点本地。如果您只在一台服务器上运行您的应用程序,那么一切都是本地的,您不必考虑太多。如果您计划对您的应用程序进行集群化,那么您需要使用 API 将 ETS 表包装在一个进程中。进程在所有集群 BEAM 节点中具有唯一地址,因此您可以使用该 API 访问内存表。在大多数情况下,您实际上可以将 ETS 视为更快的 redis。在高层次上它们是相同的,但 ETS 使用 erlang/elixir 术语,因此它没有 redis 所具有的字符串序列化开销,因此速度更快。

【讨论】:

  • 您好,感谢您的建议。这个解决方案怎么样?使用 GenServer 中的 ets 进行回调(为发送的每条消息触发 3 次更新操作)。
  • 所以改为更新 3 次(排队、发送、交付)到数据库,将每个状态保存在 ets 中,这样当它完成时(交付或未交付)只需更新数据库 1 次。
  • 我想我之前误读了你的问题,因为这意味着你也在从数据库中进行大量读取,但如果你只是在进行写入,那么是的,同样的原则也会起作用。写入 ETS 比写入数据库便宜得多。实际上,您甚至可以通过将 delivered 数据库写入批量化为每分钟仅写入一次左右来节省更多工作。
  • 好的,我会试试这个.. 但有更多问题。 ets表数据能保存多久?以及ets表是如何清理内存数据的?
  • ETS 是内存表,但 DETS 可以持久保存到磁盘。我建议阅读所有广泛的文档以获取任何其他信息:)
猜你喜欢
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
  • 2016-01-08
相关资源
最近更新 更多