首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现数据库连接池

实现数据库连接池
EN

Code Review用户
提问于 2022-01-21 19:40:25
回答 1查看 85关注 0票数 0

我正在重新实现一个web应用程序,部分是为了学习nim以及多线程编程。作为学习练习的一部分,我想实现连接池,因为在nim中没有我知道的库或包为我实现它,同时允许我使用我选择的ORM。我连接到的数据库是sqlite。

因此,我编写了我认为是正确的代码:我创建了一个ConnectionPool类型的全局对象D1,它仅仅是与sqlite数据库的一系列连接,并且还具有一个锁。每个连接都有一个明确定义的生命周期,在此之后,它将被销毁,如果需要连接,则创建新的连接,但没有可用的连接。您可以使用borrowConnection获得连接,并通过recycleConnection返回连接。这两种方法都锁定POOL对象以检索连接或将所述连接放回。

代码语言:javascript
复制
import ../applicationSettings 
import constructor/defaults
import std/[times, locks, db_sqlite]


proc createRawDatabaseConnection(): DbConn =
    return open(applicationSettings.database, "", "", "")


type PoolConnection* {.defaults.} = object
  connection*: DbConn = createRawDatabaseConnection()
  deathTime: DateTime = now() + initTimeInterval(days = 1)
implDefaults(PoolConnection)


type ConnectionPool* = object
  connections: seq[PoolConnection]
  lock: Lock


var POOL {.global.}: ConnectionPool
proc isEmptyPool(): bool = POOL.connections.len() == 0


proc initConnectionPool*(initialPoolSize: static int) = 
  POOL.connections = @[]
  initLock(POOL.lock)

  withLock POOL.lock:
    for i in 1..initialPoolSize:
      POOL.connections.add(initPoolConnection())


proc borrowConnection*(): PoolConnection {.gcsafe.} =
  {.cast(gcsafe).}:
    withLock POOL.lock:
      if isEmptyPool():
        return initPoolConnection()
      
      result = POOL.connections.pop()


proc recycleConnection*(connection: sink PoolConnection) {.gcsafe.} =
  if connection.deathTime < now():
    return
  
  {.cast(gcsafe).}:
    withLock POOL.lock:
      POOL.connections.add(connection)


proc destroyConnectionPool*() =
  deinitLock(POOL.lock)

上面的代码有什么明显的问题吗?我认为这不是疯狂复制记忆,但我可能错了,所以请告诉我,如果我是

EN

回答 1

Code Review用户

回答已采纳

发布于 2022-01-21 22:04:16

Leorize从nim不和谐服务器上提出了一个有效的建议。上述问题有几个:

  1. 你做了很多连接的创造和破坏,不管是否需要,在一段时间之后杀死一个连接都是没有用的。
  2. 使用monoTime而不是时间对象,这样可以减少内存浪费,并且您正在使用更简单的对象来更好地捕捉持续时间

您可以采用的一个设计是给池一个固定的连接限制,它可以包含这些连接。然后也给它一个"burstMode“,它允许它超越这个限制,但前提是它的"burstModeTimer”允许它。这个burstModeTimer被修正为30分钟(设置为任何你想要的),并且每次你借用一个连接的时候,当你借用一个连接时,你的池没有溢出。这是这样的,因为“突发模式”广告了整个连接,这意味着,只有当池在正常负载下溢出时,它们才是不必要的。

当池处于突发模式时,它将接受从循环proc获得的任何连接。一旦突发模式结束,在它满的时候返回的任何连接都将被关闭和垃圾收集。

代码语言:javascript
复制
import ../applicationSettings 
import std/[times, monotimes, locks, db_sqlite]


proc createRawDatabaseConnection(): DbConn =
    return open(applicationSettings.database, "", "", "")


type ConnectionPool = object
  connections: seq[DbConn]
  lock: Lock
  defaultPoolSize: int
  burstEndTime: MonoTime
  isInBurstMode: bool

var POOL {.global.}: ConnectionPool
proc isPoolEmpty(): bool = POOL.connections.len() == 0
proc isPoolFull(): bool = POOL.connections.len() >= CONNECTION_POOL_SIZE

proc refillPoolConnections() =
  withLock POOL.lock:
    for i in 1..POOL.defaultPoolSize:
      POOL.connections.add(createRawDatabaseConnection())


proc initConnectionPool*() = 
  POOL.connections = @[]
  POOL.isInBurstMode = false
  POOL.burstEndTime = getMonoTime()
  POOL.defaultPoolSize = CONNECTION_POOL_SIZE
  initLock(POOL.lock)
  refillPoolConnections()


proc activateBurstMode() =
  POOL.isInBurstMode = true
  POOL.burstEndTime = getMonoTime() + initDuration(minutes = 30)
  refillPoolConnections()

proc updatePoolBurstModeState() =
  if not POOL.isInBurstMode:
    return

  if getMonoTime() > POOL.burstEndTime:
    POOL.isInBurstMode = false


proc extendBurstModeLifetime() =
  if POOL.isInBurstMode == false:
    raise newException(DbError, "Tried to extend pool lifetime while Pool wasn't in burst mode, there's a logic issue")

  let hasMaxLifetimeDuration: bool = POOL.burstEndTime - getMonoTime() > initDuration(minutes = 30)
  if hasMaxLifetimeDuration:
    return

  POOL.burstEndTime = POOL.burstEndTime + initDuration(seconds = 5)


proc borrowConnection(): DbConn {.gcsafe.} =
  {.cast(gcsafe).}:
    withLock POOL.lock:
      if isPoolEmpty():
        activateBurstMode()

      elif not isPoolFull() and POOL.isInBurstMode: 
        extendBurstModeLifetime()
        
      result = POOL.connections.pop()
      echo "After Borrow: POOL size: " & $POOL.connections.len()


proc recycleConnection(connection: DbConn) {.gcsafe.} =  
  {.cast(gcsafe).}:
    withLock POOL.lock:
      updatePoolBurstModeState()

      if isPoolFull() and not POOL.isInBurstMode:
        connection.close()
      else:
        POOL.connections.add(connection)

      echo "After Recycle: POOL size: " & $POOL.connections.len()


proc destroyConnectionPool*() =
  deinitLock(POOL.lock)


template withDbConn*(connection: untyped, body: untyped) =
  #Borrows a database connection, executes the body and then recycles the connection
  block: #ensures connection exists only within the scope of this block
    let connection: DbConn = borrowConnection()
    try:
      body
    finally:
      recycleConnection(connection)

小用法示例:

代码语言:javascript
复制
    withDbConn(connection): #connection is of type DbConn, which is a connection to an sqlite3 db
        connection.select(entries, "campaign_id.name = ?", campaignName)

模板非常方便,理想情况下是模块中唯一的公共部分,因为它会自动为您获取和回收您的连接。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/273234

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档