多數據庫支持的應用程序設計(來自深空老大)

之前作PHP應用,多數是單數據庫數據查詢和更新,頂多也是主從數據庫的支持,實現起來相對簡單。主從數據庫的問題在於,當會話存儲在數據庫的時候,同步將可能出現問題,也就是說有可能出現會話的中斷。因此我想在主從數據庫設計上,應該將全部會話相關表進行特殊對待。即:全部的會話數據表均可以更新和查詢,當一個用戶訪問站點的時候,即將此用戶綁定到指定數據庫,全部會話訪問和查詢操做都對此數據庫進行。會話表不作同步,其餘非會話類更新也從主數據庫更新。這樣作其實也逃脫不了會話更新時候的數據庫切換,因此若是不想麻煩,仍是將會話存放在文本中進行的好。
分數據庫設計,將可能從壓力性能上會提高几個檔次,固然單次執行效率不會比單數據庫來的高的,畢竟存在着數據庫切換的效率問題。分庫以及主從數據庫搭配是能夠比較好改善數據庫併發瓶頸的方案。原則:大數據量,分庫;大訪問量,主從。不少時候,都是這二者並行(本文不討論cache)。
我想,若是要實現分庫以及主從關係,那麼數據庫服務器數量將是很是可觀,在應用程序中隨時切換到某一臺服務器,將是很是頭痛的問題,配置更換,變量名稱,是否是會有一大堆呢?如何尋找更好的解決方案將是本文談論的話題。
首先是分庫使得數據庫頗多的問題。什麼狀況下分庫?或許有些人還搞不明白爲何要分庫,我就簡要說一下本身的經驗猜想。好比一個博客程序,通常設計是將日誌存放在一張日誌表中。假設是一個多用戶博客,那麼將會關聯一個uid,若是數據量不大,這樣設計是沒有問題的,可是當日志量巨大,一天有幾十萬條日誌記錄錄入的時候,並且訪問量也比較可觀的時候,我想不可能每一個用戶來訪問日誌列表,都去從這包含幾千萬條日誌記錄的數據表中去找那麼幾條,效率可見一斑。這個時候就該考慮到分庫的問題。如何分?有一個很簡單的分表方法,即,根據uid段,將日誌記錄在各個數據庫中,固然,這個分佈仍是須要根據以往統計結果作出調整的,由於用戶日誌分佈確定不是均勻的。設置好uid段,而後根據uid索引到指定數據庫配置,建立一個數據庫對象便可。配置信息可能以下:
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' => '' ,
);
//...還有不少

至於選擇哪一臺服務器,只須要根據uid作一個簡單的匹配就能夠了。
再談到的就是主從數據庫了。什麼狀況下使用主從數據庫?好比某個名人博客,訪問量至關的大,已經沒有辦法把他的數據再進行拆分了,這個時候就得考慮主從數據庫服務器了,使用多臺數據庫來分流。這樣要適用主從和分庫,可能上面配置信息得稍微改動一下。php

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' => '' ,
);

寫到這裏,我想都應該知道如何分表和分配你的數據庫了吧,接下去我就來講一下如何輕鬆的讀取這樣的配置信息,如何將這些配置融入你的數據庫驅動中。首先以單例摸式建立DB類:mysql

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 );
?>

從上面打印結果能夠看出,blog數據庫集羣的第一組數據庫服務器被載入到$this->dsn中了。這個下面就是簡單的數據COPY的主從服務器,因此可使用隨機數來指定到某一臺服務器。如下是一個簡單的隨機數實現:sql

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;
         }
     }
}

至此,基本框架已經出來了,來看看調用方法:數據庫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
// 鏈接到第一組會話服務器
$db = &DB::init( $configs , 'session.0' );
//  執行一次查詢
$db [ 'session' ][0]->query( 'SELECT ...' );
  
//  再次鏈接BLOG服務器
$db = &DB::init( $configs , 'blog.1' );
//  執行一次更新
$db [ 'blog' ][1]->update( 'UPDATE ...' );
  
//  再次調用會話更新
$db [ 'session' ][0]->update( 'INSERT ...' );
?>
相關文章
相關標籤/搜索