此次說說session.php
session能夠說是當前互聯網提到的最多的名詞之一了。它的含義很寬泛,能夠指任何一次完整的事務交互(會話):如發送一次HTTP請求並接受響應,執行一條SQL語句均可以看作一次Session。如無特殊說明,本文中提到的Session單指HTTP會話。html
本文是PHP內核探索的第五篇,主要包含以下幾個方面的內容:web
1. HTTP是無狀態的redis
咱們知道,HTTP協議最初是匿名的、無狀態的請求/響應協議。這樣簡單的設計可使HTTP協議專一於資源的傳輸(HTTP是超文本傳輸協議),從而得到較好的性能。但這種無狀態的設計也驗證阻礙了交互web應用的發展,典型的如:電商網站須要獲取用戶的信息,以實現訂單、購物車、交易等功能,SNS網站須要獲取用戶信息並存檔,以創建真正的「社交網絡」,甚至電影和CD租賃網站,也須要獲取用戶信息,以提供個性化的推薦,從而帶來更好的效益。這意味着,必需要使用某種技術來識別和管理用戶信息,Cookie和Session技術即是在這種背景下誕生的。算法
2. Session與Cookie數據庫
說到Session,就不得不提Session的好基友Cookie,由於不少狀況下Session依賴於Cookie存儲其session_id。而若是要說Session和Cookie的區別,我想你們應該都不陌生,有的同窗甚至能夠輕鬆背出以下一些常見的區別:數組
(1). Cookie是客戶端保持狀態的解決方案,而Session是服務器端保持狀態的技術,所以,Cookie是存儲在客戶端的,而Session是存儲在服務器端的。瀏覽器
(2). 大多數狀況下,Session須要使用Cookie作載體,來存放session_id,因此,若是禁用了Cookie,必需要經過其餘的手段來獲取這個session_id( 例如經過get或者post的方式將session_id傳遞給服務器 )緩存
(3). Cookie過時和刪除只能保證客戶端的鏈接的失效,並不會清除服務器端的Session安全
(4). 儘管默認狀況下,Session和Cookie都是寫文件的( Session也能夠寫數據庫或者其餘內存緩存如memcached ),可是,Cookie則依賴於瀏覽器的設定:例如,IE6下限定每一個域名下最多20個Cookie,不少瀏覽器限制Cookie的大小不能超過4096字節。
關於Cookie的更多討論,已經超出了本文的範疇,須要瞭解的同窗能夠參考《HTTP權威指南》和《JavaScript高級程序設計》這兩本書,相信必定會對Cookie有更加深刻的理解。
3. php中Session的基本操做
php中,Session相關的操做是以擴展的形式提供的 ( 源碼目錄:PHPSRC/ext/session/ )。PHP提供了大量的、豐富的API來操做Session:
(1). session_start
bool session_start ( void )
session_start()用於啓動一個會話,通常而言,咱們在使用$_SESSION時,都要先調用session_start( 或者你的php.ini中配置了session.auto_start )。那麼在session.auto_start=false的狀況下, session_start是否是必定是session操做的第一個必須調用的函數呢?答案是否認的。雖然在通常狀況下,咱們在須要操做session時,基本上都是將session_start()放在腳本的第一行,但實際上在調用session_start時,Session相關的參數都已經初始化完畢,這以後是沒法經過session_name和session_set_cookie_params, session_save_path等函數更改Session的參數信息的。因此,若是須要更改session的相關參數,除了能夠在ini文件中更改(或者經過ini_set更改),還能夠經過session_name, session_save_path, session_set_cookie_params等函數修改,且這些函數必須在session_start以前調用。例如:
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123;
session_start()調用以後,除了要設置Session的基本參數以外,還會以必定的機率啓動Session的GC。
(2). session_id()
如同數據庫中每條記錄須要一個主鍵同樣,Session也須要一個id值用於惟一標識一個Client,這個標識即是session_id。函數session_id()用於設置或者更改當前會話的session_id,例如:
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123; print_r( session_id());//5rdhbe4k8k73h5g1fsii01iau5
在設置了session.save_handler=files的狀況下,服務器端是以sess_{session_id}的命名方式來儲存Session數據文件的:
正常狀況下,不一樣會話的session_id是不會重複的。在已知session_id的狀況下,咱們能夠經過傳遞session_id的方法來獲取Session數據,從而避開Cookie的限制:
session_save_path('/root/xiaoq/phpCode/session'); session_id("5rdhbe4k8k73h5g1fsii01iau5"); session_start(); print_r($_SESSION); /* Array ( [index] => this is desc [int] => 123 ) */
Session文件存儲會有不少問題和瓶頸,關於這一點,以後也會有詳細的說明和解釋。
(4). session_write_close/session_commit
默認狀況下,session數據是在當前會話結束時(通常就是指腳本執行完畢時)纔會寫入文件的,這樣會帶來一些問題。例如,若是當前腳本執行過長,那麼當其餘腳本訪問同一session_id下的session數據時便會阻塞(這實際上會涉及到文件鎖flock,以後會有說明),直到前一腳本執行完畢並寫入session文件。能夠用sleep來簡單模擬這一狀況:
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123; sleep(15);
避免這一狀況的一種方法是:在session數據使用完畢以後,調用session_commit或者session_write_close函數通知服務器寫入session數據並關閉文件(釋放flock的鎖):
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123; session_commit(); sleep(15);
注意session_commit和session_write_close只是同一函數的不一樣別名。
(5). session_destroy
不少同窗在會話結束的時候,都是經過unset($_SESSION)的方式來刪除會話數據(這與session_unset()的做用相似)。實際上這樣並非穩妥的作法,緣由是:unset($_SESSION)只是重置$_SESSION這個全局變量,並不會將session數據從服務器端刪除。較爲穩妥的作法是,在須要清除當前會話數據的時候調用session_destroy刪除服務器端Session數據(同時,最好使Cookie也過時):
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123; unset($_SESSION); session_destroy();
3. session的ini配置
因爲Session的不少操做依賴於ini中的參數配置,所以咱們有必要對此作一個比較全面的瞭解。php.ini比較重要的Session參數配置包括:
(1). session.save_handler
這個參數用於指定Session的存儲方式(其實是指定了一個處理Session的句柄)。能夠是files(文件存儲,默認), user( 用戶自定義存儲 ),或者其餘的擴展方式(如memcache)。
(2). session.save_path
在使用session.save_handler=files的狀況下,session.save_path用於指定Session文件存儲的目錄,如session.save_path= 「/tmp」;這種配置下,全部的session文件都是寫入一個目錄的。這在某些狀況下是有問題的(若有的系統單目錄下支持的文件數是有限制的,並且,同一目錄下文件過多,會形成讀取變慢)。session.save_path還支持多級目錄hash的方式:session.save_path = "N;/path"; 這種配置方式會將session文件分散到不一樣的子目錄中,避免單目錄文件文件過多。一樣,這種配置方式也有較大的問題:如Session的GC是無效的,並且,PHP並不會自動爲你建立子目錄,須要手動建立或者經過腳本建立。
(3). session.name
在使用Cookie爲載體的狀況下,session.name指定存儲session_id的Cookie的key( cookie中也是基於key=>value)。默認的狀況下,session.name= PHPSESSID
,能夠更改成任何合法的其餘名稱。一樣,也能夠經過session_name函數,在調用session_start以前設置這個key的名稱:
session_name("NEW_SESSION"); session_start(); $_SESSION['index'] = "this is desc"; $_SESSION['int'] = 123;
抓包能夠看到,如今,Cookie中是以新的session.name來傳遞session_id了,而第一次服務器端的響應中,也會發送Set-Cookie:
(4). session.auto_start
這個參數用於指定是否須要自動開啓session,在設置爲true的狀況下,不須要在腳本中顯式的調用session_start(). 若是不是特殊須要,咱們並不建議開啓session.auto_start.
(5). session.gc_*
主要用於配置session GC的相關參數。關於這點,咱們在後面會有詳細講解,這裏暫時擱置
(6). session.cookie_*
主要用於配置session的載體cookie的相關參數信息,如cookie的path, lifetime, 域domain等。
關於Session的更多配置,能夠參考:
http://cn2.php.net/manual/zh/session.configuration.php
如今,咱們對Session已經有了一個基本的認識,接下來,咱們將更深刻的去探討和挖掘Session的更多細節。這一部分的內容比較枯燥乏味,對於不須要了解Session內部細節的同窗,徹底能夠略過。接下來的部分,若是沒有特殊說明,都是指session.save_handler=files的狀況。
1. session模塊的初始化MINIT
前面咱們提到,在php中,Session是以擴展的形式加載的,所以,它也會經歷擴展的MINIT -> RINIT -> RSHUTDOWN -> MSHUTDOWN等階段。PHP_MINIT_FUNCTION和PHP_RINIT_FUNCTION是php啓動過程當中兩個關鍵點:在php啓動時,會依次調用各個擴展模塊的PHP_MINIT_FUNCTION來完成各個擴展模塊的初始化工做,而PHP_RINIT_FUNCTION則在對模塊的請求到來時做一些準備性工做。對於Session而言,PHP_MINIT_FUNCTION主要完成的初始化工做包括(注:不一樣版本的PHP具體處理過程並不徹底相同,如PHP 5.4+提供了SessionHandlerInterface,這樣能夠經過session_set_save_handler ( SessionHandlerInterface $sessionhandler )的方式自定義Session的處理機制,而沒必要像以前同樣使用冗長的bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )):
(1). 註冊$_SESSION超全局變量:
zend_register_auto_global("_SESSION", sizeof("_SESSION")-1, NULL TSRMLS_CC);
也就是說,$_SESSION超全局變量其實是在session的MINIT階段被註冊的。
(2). 讀取ini文件中的相關配置。
REGISTER_INI_ENTRIES();
REGISTER_INI_ENTRIES();其實是一個宏定義:
#define REGISTER_INI_ENTRIES() zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)
所以,其實是調用zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)。關於ini文件的解析和配置,已經超出了本文的範疇,能夠參考這篇文章:http://www.cnblogs.com/driftcloudy/p/4011954.html 。
擴展中讀取和設置ini的相關配置位於PHP_INI_BEGIN和PHP_INI_END宏之間。對於session而言,實際上包括:
PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("session.bug_compat_42", "1", PHP_INI_ALL, OnUpdateBool, bug_compat, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.bug_compat_warn", "1", PHP_INI_ALL, OnUpdateBool, bug_compat_warn, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.save_path", "", PHP_INI_ALL, OnUpdateSaveDir,save_path, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.name", "PHPSESSID", PHP_INI_ALL, OnUpdateString, session_name, php_ps_globals, ps_globals) PHP_INI_ENTRY("session.save_handler", "files", PHP_INI_ALL, OnUpdateSaveHandler) STD_PHP_INI_BOOLEAN("session.auto_start", "0", PHP_INI_ALL, OnUpdateBool, auto_start, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.gc_probability", "1", PHP_INI_ALL, OnUpdateLong, gc_probability, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.gc_divisor", "100", PHP_INI_ALL, OnUpdateLong, gc_divisor, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.gc_maxlifetime", "1440", PHP_INI_ALL, OnUpdateLong, gc_maxlifetime, php_ps_globals, ps_globals) PHP_INI_ENTRY("session.serialize_handler", "php", PHP_INI_ALL, OnUpdateSerializer) STD_PHP_INI_ENTRY("session.cookie_lifetime", "0", PHP_INI_ALL, OnUpdateLong, cookie_lifetime, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateString, cookie_path, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateString, cookie_domain, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.cookie_secure", "", PHP_INI_ALL, OnUpdateBool, cookie_secure, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.cookie_httponly", "", PHP_INI_ALL, OnUpdateBool, cookie_httponly, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.use_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_cookies, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_only_cookies, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.entropy_file", "", PHP_INI_ALL, OnUpdateString, entropy_file, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.entropy_length", "0", PHP_INI_ALL, OnUpdateLong, entropy_length, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateString, cache_limiter, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateLong, cache_expire, php_ps_globals, ps_globals) PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid) PHP_INI_ENTRY("session.hash_function", "0", PHP_INI_ALL, OnUpdateHashFunc) STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4", PHP_INI_ALL, OnUpdateLong, hash_bits_per_character, php_ps_globals, ps_globals) PHP_INI_END()
若是在ini文件中沒有配置相關的參數項,在session的MINIT階段,參數會被初始化爲默認的值。
(3). 自php 5.4起,php提供了SessionHandler和SessionHandlerInterface這兩個Class, 所以還須要對這兩個Class作相關的初始化工做。這是經過:
INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions);
和
INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions);
來實現的,有興趣的同窗能夠查看具體的實現過程,這裏再也不贅述。
2. session請求時的準備RINIT
PHP_RINIT_FUNCTION(session) 用於完成session請求之時的準備工做,主要包括:
(1). 初始化session相關的全局變量,這是經過php_rinit_session_globals來完成的:
static inline void php_rinit_session_globals(TSRMLS_D) { PS(id) = NULL;//session的id PS(session_status) = php_session_none;//初始化session_status PS(mod_data) = NULL;//session data PS(mod_user_is_open) = 0; /* Do NOT init PS(mod_user_names) here! */ PS(http_session_vars) = NULL; }
(2). 根據ini的配置查找session.save_handler,從而肯定是使用files仍是user( 或者是其餘的擴展方式)來處理session:
if (PS(mod) == NULL) { char *value; value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0); if (value) { PS(mod) = _php_find_ps_module(value TSRMLS_CC); } }
肯定是user仍是files來處理session的邏輯是由_php_find_ps_module來完成的,這個函數會依次查找ps_modules中預約義的module, 一旦查找成功,當即返回:
PHPAPI ps_module *_php_find_ps_module(char *name TSRMLS_DC) { ps_module *ret = NULL; ps_module **mod; int i; for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) { if (*mod && !strcasecmp(name, (*mod)->s_name)) { ret = *mod; break; } } return ret; }
ps_modules的定義:
#define MAX_MODULES 10 static ps_module *ps_modules[MAX_MODULES + 1] = { ps_files_ptr,// &ps_mod_files ps_user_ptr//&ps_mod_user };
而每個ps_module,其實是一個struct:
typedef struct ps_module_struct { const char *s_name; int (*s_open)(PS_OPEN_ARGS); int (*s_close)(PS_CLOSE_ARGS); int (*s_read)(PS_READ_ARGS); int (*s_write)(PS_WRITE_ARGS); int (*s_destroy)(PS_DESTROY_ARGS); int (*s_gc)(PS_GC_ARGS); char *(*s_create_sid)(PS_CREATE_SID_ARGS); } ps_module;
這意味着,每個處理session的mod,無論是files, user仍是其餘擴展的模塊,都應該包含ps_module中定義的字段,分別是:module的名稱(s_name), 打開句柄函數(s_open), 關閉句柄函數(s_close), 讀取函數(s_read) , 寫入函數(s_write), 銷燬函數(s_destroy), gc函數(s_gc),生成session_id的函數(s_create_sid)。例如,對於session.save_handler=files而言,其實是:
{ "files", ps_open_files, ps_close_files, ps_read_files, ps_write_files, ps_delete_files, ps_gc_files, php_session_create_id }
不少模塊都是以PS_MOD(module_name)的方式定義,上述files的ps_module結構,即是PS_MOD(files)宏展開後的結果:
#define PS_MOD(x) \ #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \ ps_delete_##x, ps_gc_##x, php_session_create_id
上述宏定義咱們也能夠看出,session.save_handler無論是files, user,仍是其餘的session處理的handler(如memcache, redis等) 生成session_id的算法都是使用php_session_create_id函數來實現的。
咱們花費了大量的精力來講session.save_handler, 實際上是想說明:原則上,session能夠存儲在任何可行的存儲中的(例如文件,數據庫,memcache和redis),若是你本身開發了一個存儲系統,比memcache的性能更好,那麼OK, 你只要按照session存儲的規範,設置好session.save_handler,無論是你在腳本中提供接口仍是使用擴展,能夠很方便的操做session數據,是否是很方便?
接着說RINIT的過程。
肯定完session的save_handler以後。須要肯定serializer, 這個也是必須的。Serializer用於完成session數據的序列化和反序列化,咱們在session.save_handler=files的狀況下能夠看到,session數據並非直接寫入文件的,而是經過必定的序列化機制序列化以後存儲到文件的,在讀取session數據時須要對文件的內容進行反序列化:
session_save_path('/root/xiaoq/phpCode/session'); session_start(); $_SESSION['key'] = 'value'; session_write_close();
則相應session文件的內容是:
key|s:5:"value"
查找serializer的過程與查找PS(mod)的方式相似:
if (PS(serializer) == NULL) { char *value; value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0); if (value) { PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC); } }
_php_find_ps_serializer也是在預約義的ps_serializers數組中查找:
PHPAPI const ps_serializer *_php_find_ps_serializer(char *name TSRMLS_DC) { const ps_serializer *ret = NULL; const ps_serializer *mod; for (mod = ps_serializers; mod->name; mod++) { if (!strcasecmp(name, mod->name)) { ret = mod; break; } } return ret; } static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = { PS_SERIALIZER_ENTRY(php_serialize), PS_SERIALIZER_ENTRY(php), PS_SERIALIZER_ENTRY(php_binary) };
一樣,每個serializer都是一個struct:
typedef struct ps_serializer_struct { const char *name; int (*encode)(PS_SERIALIZER_ENCODE_ARGS); int (*decode)(PS_SERIALIZER_DECODE_ARGS); } ps_serializer;
這時,若是mod不存在(設置的session.save_handler錯誤)或者serializer不存在,那麼直接標記session_status爲php_session_disabled,並返回,後面的代碼再也不執行。不然,肯定了mod和serializer,若是設置了session.auto_start,那麼就自動開啓session:
if (auto_start) { php_session_start(TSRMLS_C); }
因爲session_start()時,也是調用php_session_start開啓session,所以咱們捎帶着把session_start也一併分析。
3. session_start
session_start用於開啓或者重用現有的會話,在底層,其實現爲:
static PHP_FUNCTION(session_start) { php_session_start(TSRMLS_C); if (PS(session_status) != php_session_active) { RETURN_FALSE; } RETURN_TRUE; }
內部是調用php_session_start完成session相關上下文的設置, 其基本步驟是:
(1). 檢查當前會話的session狀態。
php_session_status用於標誌全部可能的會話狀態,它是一個enum:
typedef enum { php_session_disabled, php_session_none, php_session_active } php_session_status;
那麼可能的狀況有:
(a). session_status = php_session_active
代表已經開啓了session。那麼忽略本次的session_start(), 但同時會產生一條警告信息:
A session had already been started - ignoring session_start()
(b). session_status = php_session_ disabled
這種狀況可能發生在RINIT的過程當中,前面咱們看到:
if (PS(mod) == NULL || PS(serializer) == NULL) { /* current status is unusable */ PS(session_status) = php_session_disabled; return SUCCESS; }
若是session_status = php_session_ disabled, 沒法肯定session是否真不可用(好比咱們在腳本中設置了session_set_save_handler),還要作進一步的分析。查找mod和serializer的過程與RINIT的相似。
(c). session_status = php_session_none
在session_status= php_session_ disabled和php_session_none的狀況下,都會繼續向下執行。
(2). 若是session_id不存在,那麼內核會依次嘗試下列方法獲取session_id(爲了方便起見,咱們直接使用了$_COOKIE, $_GET, $_POST,實際上這樣是不嚴謹的,由於這些超級全局變量是php內核生成並提供給應用程序的,內核其實是在全局的symbol_table中查找)
a. $_COOKIE中
b. $_GET中
c. $_POST中
任何一此查找成功都會設置PS(id),再也不繼續查找。
(3). 執行php_session_initialize完成session的初始化工做。
注意此時PS(id)依然多是NULL,這一般發生在第一次訪問頁面的時候。php_session_initialize完成的主要工做包括:
a. 安全性檢查
正常狀況下,生成的session_id不會包含html標籤,單雙引號和空白字符的,若是session_id中包含了這些非法的字符,那麼頗有可能session_id是僞造的。對於這種狀況,處理很簡單,釋放session_id的空間,並標誌爲NULL,這樣與第一次訪問頁面時的邏輯就基本一致了:
if (PS(id) && strpbrk(PS(id), "\r\n\t <>'\"\\")) { efree(PS(id)); PS(id) = NULL; }
b. 爲了穩妥起見,這裏再次驗證PS(mod)是否存在,若是不存在則返回錯誤。
在PS(mod)存在的狀況下,嘗試打開句柄(對於session.save_handler=files而言,其實是打開文件)。
c. session_id
若是session_id不存在,那麼會調用相應模塊的s_create_sid方法建立相應的session_id。實際上,無論是user, files仍是memcache,建立session_id時都是調用的PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS);有興趣的同窗能夠看看生成session_id的算法,比較複雜,因爲篇幅問題,這裏並不跟蹤。
d. 嘗試讀取數據
若是讀取失敗,則可能緣由是session_id是無效的,那麼從新嘗試c中的步驟,直到讀取成功。
if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == SUCCESS) { php_session_decode(val, vallen TSRMLS_CC); efree(val); } else if (PS(invalid_session_id)) { /* address instances where the session read fails due to an invalid id */ PS(invalid_session_id) = 0; efree(PS(id)); PS(id) = NULL; goto new_session; }
在這以前,其實還有一個邏輯:php_session_track_init,用於清除PHP中已經存在的$_SESSION數組(多是垃圾數據):
static void php_session_track_init(TSRMLS_D) { zval *session_vars = NULL; /* Unconditionally destroy existing array -- possible dirty data */ zend_delete_global_variable("_SESSION", sizeof("_SESSION")-1 TSRMLS_CC); if (PS(http_session_vars)) { zval_ptr_dtor(&PS(http_session_vars)); } MAKE_STD_ZVAL(session_vars); array_init(session_vars); PS(http_session_vars) = session_vars; ZEND_SET_GLOBAL_VAR_WITH_LENGTH("_SESSION", sizeof("_SESSION"), PS(http_session_vars), 2, 1); }
4. session的基本流程
到這裏,session_start的流程基本走完了。咱們據此總結一下在session.save_handler=files狀況下,session的基本流程:
5. session文件存儲的問題
讓咱們回到以前提出的問題:在session.save_handler=files的狀況下,會有哪些性能問題和瓶頸?
a. 文件鎖帶來的性能問題
前面咱們已經提到,若是一個腳本的處理時間過程,且其中包含session的相關操做,那麼其餘腳本在訪問session數據時便會阻塞,直到前一腳本執行完畢,這是爲何呢?在session/mod_files.c中ps_files_open函數中追蹤到這樣一句:
flock(data->fd, LOCK_EX);
因爲是LOCK_EX(互斥鎖),於是在文件鎖按期間,即便是讀取文件的數據也是不容許的。這就形成要寫入或讀取的進程必須等待,直到前一進程釋放鎖(這一般發生在腳本執行完畢或者用戶調用session_commit/session_write_close)。
b. 分佈式服務器環境下session共享的問題
session文件存儲其實是存儲在服務器的磁盤上的,這樣在分佈式服務器環境下會形成必定的問題:假如你有a,b,c三臺服務器。則用戶的屢次請求可能按照負載均衡策略定向到不一樣的服務器,因爲服務器之間並無共享session文件,這在表象看來便發生了session丟失。這雖然能夠經過用戶粘滯會話解決,但會帶來更大的問題:沒法服務器的負載均衡,增長了服務器的複雜性
c. 高併發場景下session,大量磁盤I/O
基於以上一些緣由,在實際應用中,不少都是使用分佈式內存緩存memcache或者redis來存儲和共享session的。全內存操做使得session操做性能會有更大的提高。
session探索到這裏就基本結束了,還有不少問題亟待解決:
這些不在一一講解,有興趣的同窗,能夠追蹤一下源碼實現。
因爲時間倉促和我的水平有限,文中不免會有錯誤,歡迎指出和交流。最後,文章隨意轉載,但請尊重我的成果,標明出處。
1. http://www.tuicool.com/articles/26Rrui
2. 《HTTP權威指南》
3. http://www.cnblogs.com/shiyangxt/archive/2008/10/07/1305506.html
4. http://blog.163.com/lgh_2002/blog/static/4401752620105246517509/