多数据库支持的应用程序设计(来自深空老大)
2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
以前做PHP應(yīng)用,多數(shù)是單數(shù)據(jù)庫數(shù)據(jù)查詢和更新,頂多也是主從數(shù)據(jù)庫的支持,實現(xiàn)起來相對簡單。主從數(shù)據(jù)庫的問題在于,當(dāng)會話存儲在數(shù)據(jù)庫的時候,同步將可能出現(xiàn)問題,也就是說有可能出現(xiàn)會話的中斷。所以我想在主從數(shù)據(jù)庫設(shè)計上,應(yīng)該將所有會話相關(guān)表進(jìn)行特殊對待。即:所有的會話數(shù)據(jù)表都可以更新和查詢,當(dāng)一個用戶訪問站點的時候,即將此用戶綁定到指定數(shù)據(jù)庫,所有會話訪問和查詢操作都對此數(shù)據(jù)庫進(jìn)行。會話表不做同步,其他非會話類更新也從主數(shù)據(jù)庫更新。這樣做其實也逃脫不了會話更新時候的數(shù)據(jù)庫切換,所以如果不想麻煩,還是將會話存放在文本中進(jìn)行的好。分?jǐn)?shù)據(jù)庫設(shè)計,將可能從壓力性能上會提升幾個檔次,當(dāng)然單次執(zhí)行效率不會比單數(shù)據(jù)庫來的高的,畢竟存在著數(shù)據(jù)庫切換的效率問題。分庫以及主從數(shù)據(jù)庫搭配是可以比較好改善數(shù)據(jù)庫并發(fā)瓶頸的方案。原則:大數(shù)據(jù)量,分庫;大訪問量,主從。很多時候,都是這兩者并行(本文不討論cache)。
我想,如果要實現(xiàn)分庫以及主從關(guān)系,那么數(shù)據(jù)庫服務(wù)器數(shù)量將是非??捎^,在應(yīng)用程序中隨時切換到某一臺服務(wù)器,將是非常頭痛的問題,配置更換,變量名稱,是不是會有一大堆呢?如何尋找更好的解決方案將是本文談?wù)摰脑掝}。
首先是分庫使得數(shù)據(jù)庫頗多的問題。什么情況下分庫?或許有些人還搞不明白為什么要分庫,我就簡要說一下自己的經(jīng)驗猜測。比如一個博客程序,一般設(shè)計是將日志存放在一張日志表中。假設(shè)是一個多用戶博客,那么將會關(guān)聯(lián)一個uid,如果數(shù)據(jù)量不大,這樣設(shè)計是沒有問題的,但是當(dāng)日志量巨大,一天有幾十萬條日志記錄錄入的時候,而且訪問量也比較可觀的時候,我想不可能每個用戶來訪問日志列表,都去從這包含幾千萬條日志記錄的數(shù)據(jù)表中去找那么幾條,效率可見一斑。這個時候就該考慮到分庫的問題。如何分?有一個很簡單的分表方法,即,根據(jù)uid段,將日志記錄在各個數(shù)據(jù)庫中,當(dāng)然,這個分布還是需要根據(jù)以往統(tǒng)計結(jié)果做出調(diào)整的,因為用戶日志分布肯定不是均勻的。設(shè)置好uid段,然后根據(jù)uid索引到指定數(shù)據(jù)庫配置,創(chuàng)建一個數(shù)據(jù)庫對象即可。配置信息可能如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $configs [ 'db_info' ][ 'blog' ][0] = array ( ???? 'db_host' => '192.168.0.1' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][1] = array ( ???? 'db_host' => '192.168.0.2' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][2] = array ( ???? 'db_host' => '192.168.0.2' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); //...還有很多 |
至于選擇哪一臺服務(wù)器,只需要根據(jù)uid做一個簡單的匹配就可以了。
再談到的就是主從數(shù)據(jù)庫了。什么情況下使用主從數(shù)據(jù)庫?比如某個名人博客,訪問量相當(dāng)?shù)拇?#xff0c;已經(jīng)沒有辦法把他的數(shù)據(jù)再進(jìn)行拆分了,這個時候就得考慮主從數(shù)據(jù)庫服務(wù)器了,使用多臺數(shù)據(jù)庫來分流。這樣要適用主從和分庫,可能上面配置信息得稍微改動一下。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | $configs [ 'db_info' ][ 'blog' ][0][ 'master' ] = array ( ???? 'db_host' => '192.168.0.1' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][0][ 'slave' ][0] = array ( ???? 'db_host' => '192.168.0.2' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][0][ 'slave' ][1] = array ( ???? 'db_host' => '192.168.0.3' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][1][ 'master' ] = array ( ???? 'db_host' => '192.168.0.4' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][1][ 'slave' ][0] = array ( ???? 'db_host' => '192.168.0.5' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'blog' ][1][ 'slave' ][1] = array ( ???? 'db_host' => '192.168.0.6' , ???? 'db_name' => 'blog' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'session' ][0][ 'master' ] = array ( ???? 'db_host' => '192.168.0.7' , ???? 'db_name' => 'session' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); $configs [ 'db_info' ][ 'session' ][1][ 'master' ] = array ( ???? 'db_host' => '192.168.0.8' , ???? 'db_name' => 'session' , ???? 'db_user' => 'root' , ???? 'db_pass' => '' , ); |
寫到這里,我想都應(yīng)該知道如何分表和分配你的數(shù)據(jù)庫了吧,接下去我就來說一下如何輕松的讀取這樣的配置信息,如何將這些配置融入你的數(shù)據(jù)庫驅(qū)動中。首先以單例摸式創(chuàng)建DB類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php if (!defined( "DB_FETCH_ASSOC" )) { ???? define( "DB_FETCH_ASSOC" , 1); } ?? if (!defined( "DB_FETCH_ROW" )) { ???? define( "DB_FETCH_ROW" , 2); } ?? if (!defined( "DB_FETCH_ARRAY" )) { ???? define( "DB_FETCH_ARRAY" , 3); } ?? if (!defined( "DB_FETCH_DEFAULT" )) { ???? define( "DB_FETCH_DEFAULT" , DB_FETCH_ASSOC); } class DB { ???? function DB( $dsn , $db_key , $p_conn , $fetch_mode ) { ???????? $this ->dsn??????? = $dsn ; ???????? $this ->db_key???? = $db_key ; ???????? $this ->sql??????? = '' ; ???????? $this ->sqls?????? = array (); ???????? $this ->u_sqls???? = array (); ???????? $this ->q_sqls???? = array (); ???????? $this ->u_conn???? = null; ???????? $this ->q_conn???? = null; ???????? $this ->p_conn???? = $p_conn ; ???????? $this ->fecth_mode = $fetch_mode ; ???????? $this ->query_num? = 0; ???????? $this ->update_num = 0; ???? } ?? ???? function &init(& $dsn , $db_key , $p_conn = false, $fetch_mode = DB_FETCH_DEFAULT) { ???????? static $db ; ???????? $db_key = explode ( '.' , $db_key ); ???????? $db_key = "['" . implode( "']['" , $db_key ) . "']" ; ???????? eval ( '$flag = isset($db' . $db_key . ');' ); ???????? eval ( "$db_info = $dsn['db_info']" . $db_key . ";" ); ???????? if (! $flag ) { ???????????? $obj = new DB( $db_info , $db_key , $p_conn , $fetch_mode ); ???????????? eval ( '$db' . $db_key . ' = $obj;' ); ???????????? unset( $obj ); ???????? } ???????? return $db ; ???? } } $db = &DB::init( $configs , 'blog.0' ); print_r( $db ); ?> |
從上面打印結(jié)果可以看出,blog數(shù)據(jù)庫集群的第一組數(shù)據(jù)庫服務(wù)器被載入到$this->dsn中了。這個下面就是簡單的數(shù)據(jù)COPY的主從服務(wù)器,所以可以使用隨機(jī)數(shù)來指定到某一臺服務(wù)器。以下是一個簡單的隨機(jī)數(shù)實現(xiàn):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | class DB { ???? //.... ?? ???? function connectDB( $type = "master" ) { ???????? if ( $type == "master" ) { ???????????? $db_host = $this ->dsn[ "master" ][ "db_host" ]; ???????????? $db_name = $this ->dsn[ "master" ][ "db_name" ]; ???????????? $db_user = $this ->dsn[ "master" ][ "db_user" ]; ???????????? $db_pass = $this ->dsn[ "master" ][ "db_pass" ]; ???????????? $this ->u_conn = mysqli_connect( $db_host , $db_user , $db_pass ); ???????????? $this ->selectDB( $db_name , $this ->conn); ???????????? if (! $this ->u_conn) { ???????????????? $message = "Update DataBase Connect False : ($db_host, $db_user, ******) !" ; ???????????????? $this ->error( $message , 0); ???????????? } ???????? } else { ???????????? if ( empty ( $_COOKIE [ $_configs [ 'cookie_prefix' ] . 'db_no' ])) { ???????????????? $db_no = array_rand ( $this ->dsn[ "db_info" ][ "slave" ]); ???????????? } else { ???????????????? $db_no = $_COOKIE [ $_configs [ 'cookie_prefix' ] . 'db_no' ]; ???????????? } ???????????? $db_info = $this ->dsn[ "slave" ][ $db_no ]; ???????????? $db_host = $db_info [ "db_host" ]; ???????????? $db_name = $db_info [ "db_name" ]; ???????????? $db_user = $db_info [ "db_user" ]; ???????????? $db_pass = $db_info [ "db_pass" ]; ???????????? $this ->q_conn = mysqli_connect( $db_host , $db_user , $db_pass ); ?? ???????????? if (! $this ->q_conn) { ???????????????? if (! $this ->u_conn) { ???????????????????? $this ->connectDB(); ???????????????? } ???????????????? $this ->q_conn = $this ->u_conn; ???????????????? if (! $this ->q_conn) { ???????????????????? $message = "Query DataBase Connect False : ($db_host, $db_user, ******) !" ; ???????????????????? $this ->error( $message , 0); ???????????????? } ???????????? } else { ???????????????? $this ->selectDB( $db_name , $this ->q_conn); ???????????? } ???????? } ???? } ?? ???? function selectDB( $db_name , $conn ) { ???????? if ( $db_name != null) { ???????????? if (! mysqli_select_db( $conn , $db_name )) { ???????????????? $code = mysqli_errno( $conn ); ???????????????? $message = mysqli_error( $conn ); ???????????????? $this ->error( $message , $code ); ???????????? } ???????????? return true; ???????? } ???????? return false; ???? } ?? ???? function query( $sql , $limit = 1, $quick = false) { ???????? if ( $limit != null) { ???????????? $sql = $sql . " LIMIT " . $limit ; ???????? } ???????? $this ->sqls[] = $sql ; ???????? $this ->q_sqls[] = $sql ; ???????? $this ->sql = $sql ; ?? ???????? if ( empty ( $this ->q_conn)) { ???????????? $this ->connectDB( "slave" ); ???????? } ???????? $this ->qrs = mysqli_query( $this ->q_conn, $sql , $quick ? MYSQLI_USE_RESULT : MYSQLI_STORE_RESULT); ???????? if (! $this ->qrs) { ???????????? $code = mysqli_errno( $this ->q_conn); ???????????? $message = mysqli_error( $this ->q_conn); ???????????? $this ->error( $message , $code ); ???????? } ???????? $this ->query_num++; ???????? return $this ->qrs; ???? } ?? ???? function update( $sql ) { ???????? $this ->sql = $sql ; ???????? $this ->sqls[] = $this ->sql; ???????? $this ->u_sqls[] = $this ->sql; ???????? if ( $this ->u_conn == null) { ???????????? $this ->connectDB( "master" ); ???????? } ?? ???????? $this ->urs = mysqli_query( $this ->u_conn, $sql , MYSQLI_USE_RESULT); ???????? $this ->update_num++; ?? ???????? if (! $this ->urs) { ???????????? return false; ???????? } else { ???????????? return true; ???????? } ???? } } |
至此,基本框架已經(jīng)出來了,來看看調(diào)用方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php // 連接到第一組會話服務(wù)器 $db = &DB::init( $configs , 'session.0' ); //? 執(zhí)行一次查詢 $db [ 'session' ][0]->query( 'SELECT ...' ); ?? //? 再次連接BLOG服務(wù)器 $db = &DB::init( $configs , 'blog.1' ); //? 執(zhí)行一次更新 $db [ 'blog' ][1]->update( 'UPDATE ...' ); ?? //? 再次調(diào)用會話更新 $db [ 'session' ][0]->update( 'INSERT ...' ); ?> |
轉(zhuǎn)載于:https://my.oschina.net/tenking/blog/28574
總結(jié)
以上是生活随笔為你收集整理的多数据库支持的应用程序设计(来自深空老大)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计篇-之一文言文翻译
- 下一篇: C#基础(Day08)