Haskell 数据库连接

Haskell database connections

请看这个scotty app(直接取自this old answer from 2014):

import Web.Scotty
import Database.MongoDB
import qualified Data.Text.Lazy as T
import Control.Monad.IO.Class

runQuery :: Pipe -> Query -> IO [Document]
runQuery pipe query = access pipe master "nutrition" (find query >>= rest) 

main = do
  pipe <- connect $ host "127.0.0.1"
  scotty 3000 $ do
    get "/" $ do
      res <- liftIO $ runQuery pipe (select [] "stock_foods")
      text $ T.pack $ show res

您会看到数据库连接 (pipe) 如何仅在 Web 应用程序启动时创建一次。随后,成千上万甚至数百万的访问者将同时点击“/”路由并使用相同的连接从数据库中读取 (pipe)。

我对如何正确使用有疑问Database.MongoDB:

  1. 这是正确的设置方式吗?而不是为每次访问“/”创建数据库连接。在后一种情况下,我们可以同时拥有数百万个连接。那是气馁吗?这种方法的优点和缺点是什么?
  2. 在上面的应用程序中,如果由于某种原因丢失了数据库连接并需要重新创建,会发生什么情况?你会如何从中恢复过来?
  3. 使用 auth function 进行身份验证怎么样? auth 函数应该只在创建 pipe 后调用一次,还是应该在每次点击“/”时调用?
  4. 有人说我应该使用游泳池 (Data.Pool)。看起来这只会有助于限制同时使用同一数据库连接的访问​​者数量。但我为什么要这样做呢? MongoDB 连接是否内置了对同时使用的支持?

你真正想要的是一个数据库连接池。看看 this other answer.

中的代码

如果您的 MongoDB 服务器处于安全模式,您可以使用 withMongoDBPool 而不是 auth

Is this the proper way of setting things up? As opposed to creating a database connection for every visit to "/". In this latter case, we could have millions of connections at once. Is that discouraged? What are the advantages and drawbacks of such an approach?

您不想打开一个连接然后使用它。您正在使用的支持 Scotty 的 HTTP 服务器称为 Warp。 Warp 在您的 Web 服务中有一个 multi-core, multi-green-thread design. You are allowed to share the same connection across all threads, since Database.MongoDB says outright that connections are thread-safe, but what will happen is that when one thread is blocked waiting for a response (the MongoDB protocol follows a simple request-response design) 所有 个线程将被阻塞。这很不幸。

我们可以改为在每个 请求上创建一个连接。这简单地解决了一个线程阻塞另一个线程的问题,但却导致了它自己的问题。建立 TCP 连接的开销虽然不大,但也不为零。回想一下,每次我们想要打开或关闭套接字时,我们都必须从用户跳转到内核,等待内核更新其内部数据结构,然后再跳回去(上下文切换)。我们还必须处理 TCP 握手和再见。我们还会在高负载下 运行 输出文件描述符或内存。

如果我们有介于两者之间的解决方案就好了。解决方案应该是

  • 线程安全
  • 让我们限制连接数,这样我们就不会耗尽操作系统的有限资源
  • 快速
  • 在正常负载下跨线程共享连接
  • 随着负载增加创建新连接
  • 允许我们在减少负载时删除连接时清理资源(如关闭句柄)
  • 希望其他生产系统已经编写并经过实战测试

resource-pool 解决的正是这个问题。

Some say that I'm supposed to use a pool (Data.Pool). It looks like that would only help limit the number of visitors using the same database connection simultaneously. But why would I want to do that? Doesn't the MongoDB connection have a built-in support for simultaneous usages?

不清楚您所说的同时使用是什么意思。我可以猜到一种解释:你的意思是像 HTTP/2 这样的东西,它在协议中内置了流水线。

standard picture of pipelining http://research.worksap.com/wp-content/uploads/2015/08/pipeline.png

上面我们看到客户端向服务器发出多个请求,没有等待响应,然后客户端可以按某种顺序接收响应。 (时间从上往下流。)这个MongoDB没有。这是一个相当复杂的协议设计,并不比仅仅要求您的客户端使用连接池好多少。 MongoDB 并不孤单:简单的请求和响应设计是 Postgres、MySQL、SQL 服务器和大多数其他数据库所采用的设计。

并且:在所有线程被阻塞并且您的用户只看到一个加载栏之前,连接池确实限制了您可以作为 Web 服务承受的负载。但是这三种场景(连接池、一个共享连接、一个请求一个连接)中的任何一种都会存在这个问题!计算机的资源是有限的,在某些时候,某些东西会在足够的负载下崩溃。连接池的优点是它可以优雅地扩展到它不能扩展的程度。处理更多流量的正确解决方案是增加计算机数量;我们不应该仅仅因为这个问题而避免池化。

In the app above, what happens if the database connection is lost for some reason and needs to be created again? How would you recover from that?

我相信这些类型的假设不在 Stack Overflow 的范围内并且不值得比 "try it and see." Buuuuuuut 考虑到服务器终止连接,我可以尝试一下可能发生的事情:假设 Warp 为每个请求分叉一个绿色线程(我认为它确实如此),每个线程在尝试写入已关闭的 TCP 连接时都会遇到未检查的 IOException。 Warp 将捕获此异常并将其作为 HTTP 500 提供,希望也为日志写入一些有用的内容。假设像现在这样的单连接模型,您可以在 "reboot" 您的 main 函数中做一些聪明的事情(但代码行数很多)并设置第二个连接。我为爱好项目做的事情:如果发生任何奇怪的事情,比如连接断开,我会要求我的主管进程(比如 systemd)查看日志并重新启动 Web 服务。虽然对于生产型、赚钱的网站来说显然不是一个很好的解决方案,但它对于小型应用程序来说已经足够好了。

What about authentication with the auth function? Should the auth function only be called once after creating the pipe, or should it be called on every hit to "/"?

创建连接后应该调用一次。 MongoDB 身份验证是针对每个连接的。可以看到an example here of how the db.auth() command mutates the MongoDB server's data structures corresponding to the current client connection.

  1. 即使您为每个客户端创建连接,您也无法创建太多连接。你会遇到 ulimit。一旦您命中该 ulimit,命中该 ulimit 的客户端将出现运行时错误。 它没有意义的原因是因为 mongodb 服务器将花费太多时间轮询所有这些连接,并且它只有与您的数据库服务器拥有的 CPU 一样多的有意义的工作人员。 一个连接不是一个坏主意,因为 mongodb 旨在发送多个请求并等待响应。因此,它将利用您的 mongodb 所能拥有的尽可能多的资源,但只有一个限制 - 您只有一个用于写入的管道,如果它意外关闭,您将需要自己重新创建该管道。 因此,拥有一个连接池更有意义。它不需要很大。我有一个应用程序可以对用户进行身份验证并为他们提供令牌。每秒 2500 个并发用户,它只有 3-4 个并发连接到数据库。

以下是连接池为您带来的好处:

  • 如果您达到池连接限制,您将等待下一个可用连接并且不会出现运行时错误。因此,您的应用将稍等片刻,而不是拒绝您的客户端。

  • Pool 将为您重新创建连接。您可以将池配置为关闭多余的连接,并根据需要创建更多连接,直到达到一定的限制。如果您在读取或写入时连接中断,那么您只需从池中获取另一个连接。如果您不这样做,return 与池 pool 断开的连接将为您创建另一个连接。

    1. 如果数据库连接关闭,则:mongodb 此连接上的侦听器将退出,在您的终端上打印错误消息,您的应用程序将收到 IO 错误。为了处理这个错误,您需要创建另一个连接并重试。当谈到处理这种情况时,您就会明白使用数据库池更容易。因为最终你对此的解决方案将非常类似于连接池。

    2. 我在打开连接时进行了一次身份验证。如果您以后需要授权另一个用户,您可以随时这样做。

    3. 是的,mongodb 处理同时使用,但正如我所说,它只提供一个管道来写入,很快就会成为瓶颈。如果您创建的连接数至少与您的 mongodb 服务器能够处理它们的线程数(CPU 计数)一样多,那么它们将全速运行。

如果我遗漏了什么,请随时要求澄清。 谢谢你的提问。