源碼函數所在文件
爲了避免代碼影響閱讀,以及代碼順序不符合個人的讀碼習慣,就不貼代碼了,都可以通過函數名在下面幾個c文件中找到
- redis.c
- library.c
- common.h
幾個核心函數
-
redis_connect:用於創建redis對象並建立sock連接。第一個參數INTERNAL_FUNCTION_PARAM_PASSTHRU是宏定義,用來獲取函數傳入的參數,第二個參數persistent用於區分是否長鏈接。
-
redis_sock_get:獲取sock,檢活,第一個參數如果爲空的sock對象,則代表用新的連接,並且不需要檢活;如果爲當前sock對象(getThis函數獲取),則需要在hash符號表裏查找是否有舊的sock對象。
-
redis_sock_get_instance:根據redis實例的id,返回redis實例中的sock
-
redis_sock_server_open:用於判斷socket活躍狀態,如果連接已斷開調用redis_sock_connect重新連接。
-
redis_sock_create:爲redis實例對象創建新的sock連接,申請空間,存儲到zend全局空間中
-
redis_sock_disconnect:釋放sock連接,如果是長鏈接從連接池中獲取釋放,短連接直接釋放
-
redis_sock_get_connection_pool:從連接池中獲取連接
-
php_stream_pclose:關閉長連接數據流
-
php_stream_close:關閉短連接數據流
-
redis_free_socket:釋放sock佔用的zend全局空間
幾個核心宏定義
-
PHPREDIS_GET_OBJECT:獲取redis實例
-
REDIS_THROW_EXCEPTION:拋出異常
-
INTERNAL_FUNCTION_PARAM_PASSTHRU獲取參數
pconnect長鏈接邏輯
調用redis_connect創建長鏈接經歷下面幾個步驟(參數persistent爲1)
- 初始化變量和參數
- 條件編譯,檢測ZTS宏定義,將persistent改爲0(我猜是在某些環境下不允許長鏈接)
-
調用zend_parse_method_parameters獲取參數,獲取失敗則返回錯誤
-
如果是短連接,persistent_id(長鏈接id)定義爲NULL,這裏由於是長鏈接,跳過
-
檢測timeout、read_timeout、retry_interval、port
-
調用PHPREDIS_GET_OBJECT宏定義獲取redis對象
-
如果該redis對象的sock已經被創建則調用redis_sock_disconnect斷開sock連接,並調用redis_free_socket關閉sock(避免同一個redis實例創建多個sock造成端口占用)
-
redis_sock_create爲sock創建持久化連接,這裏通過保存在RedisSock裏的持久化標識persistent_id來使用同一個sock
-
爲了保險,調用redis_sock_server_open檢查連接狀態,如果連接失敗調用redis_free_socket釋放sock,並返回錯誤
-
返回成功
connect短連接邏輯
和上面創建長鏈接步驟一致,只不過persistent參數爲0
redis命令操作
-
REDIS_PROCESS_CMD進入執行redis命令
-
調用redis_sock_get獲取當前連接,第一個參數用getThis()函數獲取當前redis對象,參數no_throw傳0代表redis_sock_get_instance不跳過異常拋出
-
調用redis_sock_get_instance獲取redis實例中的sock連接
-
調用redis_sock_server_open檢測連接狀態,斷開會自動重連
-
返回活躍的redis_sock連接
-
用前面返回的sock連接執行redis命令
-
返回命令結果
結論
- redis客戶端對象存儲在Zend的全局EG哈希表中,所以redis對象和sock長連接的在子進程重啓後纔會被釋放
- redis長鏈接通過複用同一個sock實現,通過RedisSock結構體中的persistent標識是否爲長鏈接
- 重複pconnect會造成多次斷開和連接
- 每次redis命令之前會自動檢活
- 如果使用pconnect連接,調用close之後會關閉並釋放sock連接,但是隻要子進程不重啓,就會複用同一個sock連接,重新連接