2009-07-17 16 views
8

में मैं एक जावा प्रोग्रामर जो हास्केल सीखता हूँ।
मैं एक छोटे से वेब एप्लिकेशन HDBC के माध्यम से एक डेटाबेस के लिए Happstack और वार्ता का उपयोग करता है पर काम करते हैं।समवर्ती डीबी कनेक्शन पूल हास्केल

मैं और कार्यकारी चयन कार्यों लिखा है और मैं उन्हें इस तरह का उपयोग करें:

module Main where 

import Control.Exception (throw) 

import Database.HDBC 
import Database.HDBC.Sqlite3 -- just for this example, I use MySQL in production 

main = do 
    exec "CREATE TABLE IF NOT EXISTS users (name VARCHAR(80) NOT NULL)" [] 

    exec "INSERT INTO users VALUES ('John')" [] 
    exec "INSERT INTO users VALUES ('Rick')" [] 

    rows <- select "SELECT name FROM users" [] 

    let toS x = (fromSql x)::String 
    let names = map (toS . head) rows 

    print names 

बहुत ही सरल रूप में आप देखते हैं। वहाँ क्वेरी, पैरामीटर और परिणाम है।
कनेक्शन निर्माण और प्रतिबद्ध/रोलबैक सामान चयन और निष्पादन के अंदर छिपा हुआ है।
यह अच्छा है, मैं इसके बारे में "तर्क" कोड में इसकी परवाह नहीं करना चाहता हूं।

exec :: String -> [SqlValue] -> IO Integer 
exec query params = withDb $ \c -> run c query params 

select :: String -> [SqlValue] -> IO [[SqlValue]] 
select query params = withDb $ \c -> quickQuery' c query params 

withDb :: (Connection -> IO a) -> IO a 
withDb f = do 
    conn <- handleSqlError $ connectSqlite3 "users.db" 
    catchSql 
     (do r <- f conn 
      commit conn 
      disconnect conn 
      return r) 
     (\[email protected](SqlError _ _ m) -> do 
      rollback conn 
      disconnect conn 
      throw e) 

बुरा अंक:

  • एक नया कनेक्शन हमेशा हर कॉल के लिए बनाया जाता है - यह भारी बोझ पर प्रदर्शन को मारता है
  • डीबी यूआरएल "users.db" hardcoded है - मैं नहीं कर सकता अन्य परियोजनाओं भर में इन कार्यों का पुन: उपयोग/संपादन

प्रश्न 1 ओ डब्ल्यू: कैसे कनेक्शन वाई का एक पूल शुरू करने की समवर्ती कनेक्शन की कुछ परिभाषित (न्यूनतम, अधिकतम) संख्या, तो चयन/निष्पादन कॉल के बीच कनेक्शन का पुन: उपयोग किया जाएगा?

प्रश्न 2: "users.db" स्ट्रिंग कॉन्फ़िगर करने योग्य कैसे करें? (कैसे ग्राहक कोड के लिए ले जाने के लिए?)

यह एक पारदर्शी सुविधा होना चाहिए: उपयोगकर्ता कोड स्पष्ट कनेक्शन हैंडलिंग/रिलीज आवश्यकता नहीं होनी चाहिए।

+0

मेरे पास आपके लिए पूर्ण उत्तर नहीं है, लेकिन आपकी समस्या यह है कि आपने कनेक्शन को गलत तरीके से समझाया है। आप शायद इसे रीडर-जैसी संरचना में रखना चाहते हैं, ताकि यह प्रत्येक क्वेरी को पारित किया जा सके। – jrockway

+0

हम्म, एसक्यूएल ऑपरेशंस सभी 'आईओ' मोनैड में फंस गए हैं, तो शायद 'रीडर टीओ'? उचित लगता है। – ephemient

उत्तर

8

प्रश्न 2: मैंने कभी भी एचडीबीसी का उपयोग नहीं किया है, लेकिन शायद मैं ऐसा कुछ लिखूंगा।

trySql :: Connection -> (Connection -> IO a) -> IO a 
trySql conn f = handleSql catcher $ do 
    r <- f conn 
    commit conn 
    return r 
    where catcher e = rollback conn >> throw e 

ओपन Connection कहीं समारोह के बाहर, और समारोह के भीतर यह डिस्कनेक्ट नहीं है।

प्रश्न 1: हम्म, एक कनेक्शन पूल कि लागू करने के लिए कठिन प्रतीत नहीं होता ...

import Control.Concurrent 
import Control.Exception 

data Pool a = 
    Pool { poolMin :: Int, poolMax :: Int, poolUsed :: Int, poolFree :: [a] } 

newConnPool low high newConn delConn = do 
    cs <- handleSqlError . sequence . replicate low newConn 
    mPool <- newMVar $ Pool low high 0 cs 
    return (mPool, newConn, delConn) 

delConnPool (mPool, newConn, delConn) = do 
    pool <- takeMVar mPool 
    if length (poolFree pool) /= poolUsed pool 
     then putMVar mPool pool >> fail "pool in use" 
     else mapM_ delConn $ poolFree pool 

takeConn (mPool, newConn, delConn) = modifyMVar mPool $ \pool -> 
    case poolFree pool of 
     conn:cs -> 
      return (pool { poolUsed = poolUsed pool + 1, poolFree = cs }, conn) 
     _ | poolUsed pool < poolMax pool -> do 
      conn <- handleSqlError newConn 
      return (pool { poolUsed = poolUsed pool + 1 }, conn) 
     _ -> fail "pool is exhausted" 

putConn (mPool, newConn, delConn) conn = modifyMVar_ mPool $ \pool -> 
    let used = poolUsed pool in 
    if used > poolMin conn 
     then handleSqlError (delConn conn) >> return (pool { poolUsed = used - 1 }) 
     else return $ pool { poolUsed = used - 1, poolFree = conn : poolFree pool } 

withConn connPool = bracket (takeConn connPool) (putConn conPool) 

आप शायद इस शब्दशः नहीं लेना चाहिए के रूप में मैं नहीं भी यह संकलन का परीक्षण किया है (और fail वहाँ बहुत अमित्र है), लेकिन विचार की तरह

connPool <- newConnPool 0 50 (connectSqlite3 "user.db") disconnect 

कुछ करना और आस-पास के रूप में की जरूरत connPool पारित करने के लिए है।

+0

कूल! क्या यह धागा सुरक्षित है? क्या एक सिंगल "कॉनपूल" बनाना ठीक है और इसे सभी हैप्स्टैक हैंडलर में उपयोग करना ठीक है? – oshyshko

+0

यह थ्रेड-सुरक्षित होना चाहिए, सभी काम 'संशोधित एमवर' (जो 'टेकएमवर' + 'पुटएमवर' है) के भीतर किया जाता है, जो प्रभावी रूप से सभी 'टेक'/'put' संचालन को अनुक्रमित करता है। लेकिन आपको यह देखने के लिए वास्तव में इस कोड का ऑडिट करना चाहिए कि यह आपकी आवश्यकताओं के अनुरूप है या नहीं। – ephemient

+2

पूल परीक्षण का उपयोग करने से पहले आपके डेटाबेस ड्राइवर डिस्कनेक्ट के साथ कैसे copes। मैंने एमएस एसक्यूएल सर्वर के खिलाफ hdbc-odbc ड्राइवर के साथ इस पूल कार्यान्वयन का उपयोग करने की कोशिश की। यह बढ़िया काम करता है। लेकिन फिर मैं एसक्यूएल सर्वर को रोकता हूं, एप्लिकेशन को आज़माता हूं, जो मुझे स्पष्ट रूप से त्रुटि देता है, फिर एसक्यूएल सर्वर वापस शुरू करें, और फिर से एप्लिकेशन को आजमाएं। यह अभी भी एक त्रुटि देता है। दुर्भाग्य से नेटवर्क पर डिस्कनेक्ट होता है। तो सुनिश्चित करें कि आप दोषपूर्ण कनेक्शन से निपटें और नए स्पॉन करें। मौजूदा पैकेज को इंगित करने के लिए –

1

मैं ऊपर कोड को संशोधित किया है, अब यह कम से कम संकलन करने में सक्षम है।

module ConnPool (newConnPool, withConn, delConnPool) where 

import Control.Concurrent 
import Control.Exception 
import Control.Monad (replicateM) 
import Database.HDBC 

data Pool a = 
    Pool { poolMin :: Int, poolMax :: Int, poolUsed :: Int, poolFree :: [a] } 

newConnPool :: Int -> Int -> IO a -> (a -> IO()) -> IO (MVar (Pool a), IO a, (a -> IO())) 
newConnPool low high newConn delConn = do 
-- cs <- handleSqlError . sequence . replicate low newConn 
    cs <- replicateM low newConn 
    mPool <- newMVar $ Pool low high 0 cs 
    return (mPool, newConn, delConn) 

delConnPool (mPool, newConn, delConn) = do 
    pool <- takeMVar mPool 
    if length (poolFree pool) /= poolUsed pool 
     then putMVar mPool pool >> fail "pool in use" 
     else mapM_ delConn $ poolFree pool 

takeConn (mPool, newConn, delConn) = modifyMVar mPool $ \pool -> 
    case poolFree pool of 
     conn:cs -> 
      return (pool { poolUsed = poolUsed pool + 1, poolFree = cs }, conn) 
     _ | poolUsed pool < poolMax pool -> do 
      conn <- handleSqlError newConn 
      return (pool { poolUsed = poolUsed pool + 1 }, conn) 
     _ -> fail "pool is exhausted" 

putConn :: (MVar (Pool a), IO a, (a -> IO b)) -> a -> IO() 
putConn (mPool, newConn, delConn) conn = modifyMVar_ mPool $ \pool -> 
    let used = poolUsed pool in 
    if used > poolMin pool 
    then handleSqlError (delConn conn) >> return (pool { poolUsed = used - 1 }) 
    else return $ pool { poolUsed = used - 1, poolFree = conn : (poolFree pool) } 

withConn connPool = bracket (takeConn connPool) (putConn connPool) 
16

resource-pool पैकेज एक उच्च प्रदर्शन संसाधन पूल जो डेटाबेस कनेक्शन पूलिंग के लिए इस्तेमाल किया जा सकता प्रदान करता है।उदाहरण के लिए:

import Data.Pool (createPool, withResource) 

main = do 
    pool <- createPool newConn delConn 1 10 5 
    withResource pool $ \conn -> doSomething conn 

1 उप-पूल और 5 कनेक्शन तक डेटाबेस कनेक्शन पूल बनाता है। प्रत्येक कनेक्शन को नष्ट होने से पहले 10 सेकंड के लिए निष्क्रिय होने की अनुमति है।

+0

+1 –

+0

मैंने अभी डेटा का उपयोग किया है (और मैं प्यार कर रहा हूं) Data.Conduit.Pool (पूल-कंड्यूट पैकेज)। यह Data.Pool के आसपास एक रैपर है (yesod और अन्य द्वारा उपयोग किया जाता है) http://hackage.haskell.org/package/pool-conduit-0.1.1 –

संबंधित मुद्दे