摘要:數(shù)據(jù)交互層可選如果你習(xí)慣了層結(jié)構(gòu),你可以加載層,作為與數(shù)據(jù)庫交互的層,而層作為業(yè)務(wù)層。
YCRoute
github: https://github.com/caohao-php...
目錄框架介紹
運(yùn)行環(huán)境
代碼結(jié)構(gòu)
路由配置
過濾驗(yàn)簽
控制層
加載器
模型層
數(shù)據(jù)交互dao層(可選)
Redis緩存操作
數(shù)據(jù)庫操作
配置加載
公共類加載
公共函數(shù)
日志模塊
視圖層
RPC 介紹 - 像調(diào)用本地函數(shù)一樣調(diào)用遠(yuǎn)程函數(shù)
RPC Server
RPC Client
RPC 并行調(diào)用
附錄 - Core_Model 中的輔助極速開發(fā)函數(shù)
框架介紹框架由3層架構(gòu)構(gòu)成,Controller、Model、View 以及1個(gè)可選的Dao層,支持PHP7,優(yōu)點(diǎn)如下:
1、框架層次分明,靈活可擴(kuò)展至4層架構(gòu)、使用簡潔(開箱即用)、功能強(qiáng)大。
2、基于 yaf 路由和 ycdatabase 框架,兩者都是C語言擴(kuò)展,保證了性能。
3、ycdatabase 是強(qiáng)大的數(shù)據(jù)庫 ORM 框架,功能強(qiáng)大,安全可靠,支持便捷的主從配置,支持穩(wěn)定、強(qiáng)大的數(shù)據(jù)庫連接池。具體參考 https://blog.csdn.net/caohao0...
4、支持Redis代理,簡便的主從配置,支持穩(wěn)定的redis連接池。具體參考:https://blog.csdn.net/caohao0...
5、強(qiáng)大的日志模塊、異常捕獲模塊,便捷高效的類庫、共用函數(shù)加載模塊
6、基于PHP7,代碼緩存opcache。
運(yùn)行環(huán)境運(yùn)行環(huán)境: PHP 7
依賴擴(kuò)展: yaf 、 ycdatabase 擴(kuò)展
創(chuàng)建日志目錄:/data/app/logs ,目錄權(quán)限為 php 項(xiàng)目可寫。
yaf 介紹以及安裝: https://github.com/laruence/yaf
ycdatabase 介紹以及安裝: https://github.com/caohao-php...
代碼結(jié)構(gòu)———————————————— |--- system //框架系統(tǒng)代碼 |--- conf //yaf配置路徑 |--- application //業(yè)務(wù)代碼 |----- config //配置目錄 |----- controller //控制器目錄 |------ User.php //User控制器 |----- core //框架基類目錄 |----- daos //DAO層目錄(可選) |----- errors //錯(cuò)誤頁目錄 |----- helpers //公共函數(shù)目錄 |----- library //公共類庫目錄 |----- models //模型層目錄 |----- plugins //yaf路由插件目錄,路由前后鉤子,(接口驗(yàn)簽在這里) |----- third //第三方類庫 |----- views //視圖層路由配置
路由配置位于: framework/conf/application.ini
示例: http://localhost/index.php?c=...
詳細(xì)參考文檔: http://php.net/manual/zh/book...
參數(shù) | 方式 | 描述 |
---|---|---|
c | GET | 控制器,路由到 /application/controller/User.php 文件 |
m | GET | 入口方法, User.php 里面的 getUserInfoAction 方法 |
程序?qū)⒈宦酚傻?framework/application/controllers/User.php文件的 UserController::getUserInfoAction方法,其它路由細(xì)節(jié)參考Yaf框架
class UserController extends Core_Controller { public function getUserInfoAction() { } }過濾驗(yàn)簽
framework/application/plugins/Filter.php , 在 _auth 中寫入驗(yàn)簽方法,所有接口都會(huì)在這里校驗(yàn), 所有GET、POST等參數(shù)放在 $this->params 里。
class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由之前調(diào)用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->_auth(); } //驗(yàn)簽過程 protected function _auth() { //在這里寫你的驗(yàn)簽邏輯 } ... }控制層
所有控制器位于:framework/application/controllers 目錄,所有控制器繼承自Core_Controller方法,里面主要獲取GET/POST參數(shù),以及返回?cái)?shù)據(jù)的處理,Core_Controller繼承自 Yaf_Controller_Abstract, init方法會(huì)被自動(dòng)調(diào)用,更多細(xì)節(jié)參考 Yaf 框架控制器。
class UserController extends Core_Controller { public function init() { parent::init(); //必須 $this->user_model = Loader::model("UserinfoModel"); //模型層 $this->util_log = Logger::get_instance("user_log"); //日志 Loader::helper("common_helper"); //公共函數(shù) $this->sample = Loader::library("Sample"); //加載類庫,加載的就是 framework/library/Sample.php 里的Sample類 } //獲取用戶信息接口 public function getUserInfoAction() { $userId = $this->params["userid"]; $token = $this->params["token"]; if (empty($userId)) { $this->response_error(10000017, "user_id is empty"); } if (empty($token)) { $this->response_error(10000016, "token is empty"); } $userInfo = $this->user_model->getUserinfoByUserid($userId); if (empty($userInfo)) { $this->response_error(10000023, "未找到該用戶"); } if (empty($token) || $token != $userInfo["token"]) { $this->response_error(10000024, "token 校驗(yàn)失敗"); } $this->response_success($userInfo); } }
通過 $this->response_error(10000017, "user_id is empty"); 返回錯(cuò)誤結(jié)果
{ "errno":10000017, "errmsg":"user_id is empty" }
通過 $this->response_success($result); 返回JSON格式成功結(jié)果,格式如下:
{ "errno":0, "union":"", "amount":0, "session_key":"ZqwsC+Spy4C31ThvqkhOPg==", "open_id":"oXtwn4_mrS4zIxtSeV0yVT2sAuRo", "nickname":"涼之渡", "last_login_time":"2018-09-04 18:53:06", "regist_time":"2018-06-29 22:03:38", "user_id":6842811, "token":"c9bea5dee1f49488e2b4b4645ff3717e", "updatetime":"2018-09-04 18:53:06", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/xfxHib91BictV8T4ibRQAibD10DfoNpzpB1LBqZvRrz0icPkN0gdibZg62EPJL3KE1Y5wkPDRAhibibymnQCFgBM2nuiavA/132", "city":"Guangzhou", "province":"Guangdong", "country":"China", "appid":"wx385863ba15f573b6", "gender":1, "form_id":"" }加載器
通過 Loader 加載器可以加載模型層,公共類庫,公共函數(shù),數(shù)據(jù)庫,緩存等對象, Logger 為日志類。
模型層framework/application/models/Userinfo.php ,模型層,你可以繼承自Core_Model, 也可以不用,Core_Model 中封裝了許多常用SQL操作。最后一章會(huì)介紹各個(gè)函數(shù)用法。
通過 $this->user_model = Loader::model("UserinfoModel") 加載模型層,模型層與數(shù)據(jù)庫打交道。
class UserinfoModel extends Core_Model { public function __construct() { $this->db = Loader::database("default"); $this->util_log = Logger::get_instance("userinfo_log"); } function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data["appid"] = $appid; $data["user_id"] = $userid; $data["open_id"] = $open_id; $data["session_key"] = $session_key; $data["last_login_time"] = $data["regist_time"] = date("Y-m-d H:i:s", time()); $data["token"] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert("user_info", $data); if ($ret != -1) { return $data["token"]; } else { $this->util_log->LogError("error to register_user, DATA=[".json_encode($data)."]"); return false; } } ... }數(shù)據(jù)交互Dao層(可選)
如果你習(xí)慣了4層結(jié)構(gòu),你可以加載Dao層,作為與數(shù)據(jù)庫交互的層,而model層作為業(yè)務(wù)層。這個(gè)時(shí)候 Model 最好不要繼承 Core_Model,而由 Dao 層來繼承。
framework/application/daos/UserinfoDao.php ,數(shù)據(jù)庫交互層,你可以繼承自Core_Model, 也可以不用,Core_Model 中封裝了許多常用SQL操作。最后一章會(huì)介紹各個(gè)函數(shù)用法。
通過 $this->user_dao = Loader::dao("UserinfoDao") 加載dao層,我們建議一個(gè)數(shù)據(jù)庫對應(yīng)一個(gè)Dao層。
redis 緩存操作加載 redis 緩存: Loader::redis("default_master"); 參數(shù)為framework/application/config/redis.php 配置鍵值,如下:
$redis_conf["default_master"]["host"] = "127.0.0.1"; $redis_conf["default_master"]["port"] = 6379; $redis_conf["default_slave"]["host"] = "/tmp/redis_pool.sock"; //unix socket redis連接池,需要配置 openresty-pool/conf/nginx.conf,并開啟代理,具體參考 https://blog.csdn.net/caohao0591/article/details/85679702 $redis_conf["userinfo"]["host"] = "127.0.0.1"; $redis_conf["userinfo"]["port"] = 6379; return $redis_conf;
使用例子:
$redis = Loader::redis("default_master"); //主寫 $redis->set("pre_redis_user_${userid}", serialize($result)); $redis->expire("pre_redis_user_${userid}", 3600); $redis = Loader::redis("default_slave"); //從讀 $data = $redis->get("pre_redis_user_${userid}");
連接池配置 openresty-pool/conf/nginx.conf :
worker_processes 1; #nginx worker 數(shù)量 error_log logs/error.log; #指定錯(cuò)誤日志文件路徑 events { worker_connections 1024; } stream { lua_code_cache on; lua_check_client_abort on; server { listen unix:/tmp/redis_pool.sock; content_by_lua_block { local redis_pool = require "redis_pool" pool = redis_pool:new({ip = "127.0.0.1", port = 6380, auth = "password"}) pool:run() } } server { listen unix:/var/run/mysql_sock/mysql_user_pool.sock; content_by_lua_block { local mysql_pool = require "mysql_pool" local config = {host = "127.0.0.1", user = "root", password = "test123123", database = "userinfo", timeout = 2000, max_idle_timeout = 10000, pool_size = 200} pool = mysql_pool:new(config) pool:run() } } }數(shù)據(jù)庫操作
數(shù)據(jù)庫加載: Loader::database("default"); 參數(shù)為 framework/application/config/database.php 里配置鍵值,如下:
$db["default"]["unix_socket"] = "/var/run/mysql_sock/mysql_user_pool.sock"; //unix socket 數(shù)據(jù)庫連接池,具體使用參考 https://blog.csdn.net/caohao0591/article/details/85255704 $db["default"]["pconnect"] = FALSE; $db["default"]["db_debug"] = TRUE; $db["default"]["char_set"] = "utf8"; $db["default"]["dbcollat"] = "utf8_general_ci"; $db["default"]["autoinit"] = FALSE; $db["payinfo_master"]["host"] = "127.0.0.1"; //地址 $db["payinfo_master"]["username"] = "root"; //用戶名 $db["payinfo_master"]["password"] = "test123123"; //密碼 $db["payinfo_master"]["dbname"] = "payinfo"; //數(shù)據(jù)庫名 $db["payinfo_master"]["pconnect"] = FALSE; //是否連接池 $db["payinfo_master"]["db_debug"] = TRUE; //debug標(biāo)志,線上關(guān)閉,打開后,異常SQL會(huì)顯示到頁面,不安全,僅在測試時(shí)打開,(注意,上線一定得將 db_debug 置為 FALSE,否則一定概率可能暴露數(shù)據(jù)庫配置) $db["payinfo_master"]["char_set"] = "utf8"; $db["payinfo_master"]["dbcollat"] = "utf8_general_ci"; $db["payinfo_master"]["autoinit"] = FALSE; //自動(dòng)初始化,Loader的時(shí)候就連接,建議關(guān)閉 $db["payinfo_master"]["port"] = 3306; $db["payinfo_slave"]["host"] = "192.168.0.7"; $db["payinfo_slave"]["username"] = "root"; $db["payinfo_slave"]["password"] = "test123123"; $db["payinfo_slave"]["dbname"] = "payinfo"; $db["payinfo_slave"]["pconnect"] = FALSE; $db["payinfo_slave"]["db_debug"] = TRUE; $db["payinfo_slave"]["char_set"] = "utf8"; $db["payinfo_slave"]["dbcollat"] = "utf8_general_ci"; $db["payinfo_slave"]["autoinit"] = FALSE; $db["payinfo_slave"]["port"] = 3306;原生SQL:
$data = $this->db->query("select * from user_info where country="China" limit 3");查詢多條記錄:
$data = $this->db->get("user_info", ["regist_time[<]" => "2018-06-30 15:48:39", "gender" => 1, "country" => "China", "city[!]" => null, "ORDER" => [ "user_id", "regist_time" => "DESC", "amount" => "ASC" ], "LIMIT" => 10], "user_id,nickname,city"); echo json_encode($data);exit;
[ { "nickname":"芒果", "user_id":6818810, "city":"Yichun" }, { "nickname":"Smile、格調(diào)", "user_id":6860814, "city":"Guangzhou" }, { "nickname":"Yang", "user_id":6870818, "city":"Hengyang" }, { "nickname":"涼之渡", "user_id":7481824, "city":"Guangzhou" } ]查詢單列
$data = $this->db->get("user_info", ["regist_time[<]" => "2018-06-30 15:48:39", "gender" => 1, "country" => "China", "city[!]" => null, "ORDER" => [ "user_id", "regist_time" => "DESC", "amount" => "ASC" ], "LIMIT" => 10], "nickname"); echo json_encode($data);exit;
[ "芒果", "Smile、格調(diào)", "Yang", "涼之渡" ]查詢單條記錄
$data = $this->db->get_one("user_info", ["user_id" => 6818810]);
{ "union":null, "amount":0, "session_key":"Et1yjxbEfRqVmCVsYf5qzA==", "open_id":"oXtwn4wkPO4FhHmkan097DpFobvA", "nickname":"芒果", "last_login_time":"2018-10-04 16:01:27", "regist_time":"2018-06-29 21:24:45", "user_id":6818810, "token":"5a350bc05bbbd9556f719a0b8cf2a5ed", "updatetime":"2018-10-04 16:01:27", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", "city":"Yichun", "province":"Jiangxi", "country":"China", "appid":"wx385863ba15f573b6", "gender":1, "form_id":"" }插入數(shù)據(jù)
function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data["appid"] = $appid; $data["user_id"] = $userid; $data["open_id"] = $open_id; $data["session_key"] = $session_key; $data["last_login_time"] = $data["regist_time"] = date("Y-m-d H:i:s", time()); $data["token"] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert("user_info", $data); if ($ret != -1) { return $data["token"]; } else { $this->util_log->LogError("error to register_user, DATA=[".json_encode($data)."]"); return false; } }更新數(shù)據(jù)
function update_user($userid, $update_data) { $redis = Loader::redis("userinfo"); $redis->del("pre_redis_user_info_" . $userid); $ret = $this->db->update("user_info", ["user_id" => $userid], $update_data); if ($ret != -1) { return true; } else { $this->util_log->LogError("error to update_user, DATA=[".json_encode($update_data)."]"); return false; } }刪除操作
$ret = $this->db->delete("user_info", ["user_id" => 7339820]);更多操作參考
通過 $this->db->get_ycdb(); 可以獲取ycdb句柄進(jìn)行更多數(shù)據(jù)庫操作, ycdb 的使用教程如下:
英文: https://github.com/caohao-php...
中文: https://blog.csdn.net/caohao0...
通過 Loader::config("xxxxx"); 加載 /application/config/xxxxx.php 的配置。例如:
$config = Loader::config("config"); var_dump($config);公共類加載
所有的公共類庫位于superci/application/library目錄,但是注意的是, 如果你的類位于library子目錄下面,你的類必須用下劃線"_"分隔;
$this->sample = Loader::library("Sample");
加載的就是 framework/application/library/Sample.php 中的 Sample類。
$this->ip_location = Loader::library("Ip_Location");
加載的是 framework/application/library/Ip/Location.php 中的Ip_Location類
公共函數(shù)所有的公共類庫位于superci/application/helpers目錄,通過 Loader::helper("common_helper"); 方法包含進(jìn)來。
日志日志使用方法如下:
$this->util_log = Logger::get_instance("userinfo"); $this->util_log->LogInfo("register success"); $this->util_log->LogError("not find userinfo");
日志級別:
const DEBUG = "DEBUG"; /* 級別為 1 , 調(diào)試日志, 當(dāng) DEBUG = 1 的時(shí)候才會(huì)打印調(diào)試 */ const INFO = "INFO"; /* 級別為 2 , 應(yīng)用信息記錄, 與業(yè)務(wù)相關(guān), 這里可以添加統(tǒng)計(jì)信息 */ const NOTICE = "NOTICE"; /* 級別為 3 , 提示日志, 用戶不當(dāng)操作,或者惡意刷頻等行為,比INFO級別高,但是不需要報(bào)告*/ const WARN = "WARN"; /* 級別為 4 , 警告, 應(yīng)該在這個(gè)時(shí)候進(jìn)行一些修復(fù)性的工作,系統(tǒng)可以繼續(xù)運(yùn)行下去 */ const ERROR = "ERROR"; /* 級別為 5 , 錯(cuò)誤, 可以進(jìn)行一些修復(fù)性的工作,但無法確定系統(tǒng)會(huì)正常的工作下去,系統(tǒng)在以后的某個(gè)階段, 很可能因?yàn)楫?dāng)前的這個(gè)問題,導(dǎo)致一個(gè)無法修復(fù)的錯(cuò)誤(例如宕機(jī)),但也可能一直工作到停止有不出現(xiàn)嚴(yán)重問題 */ const FATAL = "FATAL"; /* 級別為 6 , 嚴(yán)重錯(cuò)誤, 這種錯(cuò)誤已經(jīng)無法修復(fù),并且如果系統(tǒng)繼續(xù)運(yùn)行下去的話,可以肯定必然會(huì)越來越亂, 這時(shí)候采取的最好的措施不是試圖將系統(tǒng)狀態(tài)恢復(fù)到正常,而是盡可能的保留有效數(shù)據(jù)并停止運(yùn)行 */
FATAL和ERROR級別日志文件以 .wf 結(jié)尾, DEBUG級別日志文件以.debug結(jié)尾,日志目錄存放于 /data/app/localhost 下面,localhost為你的項(xiàng)目域名,比如:
[root@gzapi: /data/app/logs/localhost]# ls userinfo.20190211.log userinfo.20190211.log.wf
日志格式: [日志級別] [時(shí)間] [錯(cuò)誤代碼] [文件|行數(shù)] [ip] [uri] [referer] [cookie] [統(tǒng)計(jì)信息] "內(nèi)容"
[INFO] [2019-02-11 18:57:01] - - [218.30.116.8] - - - [] "register success" [ERROR] [2019-02-11 18:57:01] [0] [index.php|23 => | => User.php|35 => Userinfo.php|93] [218.30.116.8] [/index.php?c=user&m=getUserInfo&userid=6842811&token=c9bea5dee1f49488e2b4b4645ff3717e] [] [] - "not find userinfo"VIEW層
視圖層參考yaf視圖渲染那部分, 我沒有寫案例。
RPC 介紹 - 像調(diào)用本地函數(shù)一樣調(diào)用遠(yuǎn)程函數(shù) 傳統(tǒng)web應(yīng)用弊端傳統(tǒng)的Web應(yīng)用, 一個(gè)應(yīng)用隨著業(yè)務(wù)快速增長, 開發(fā)人員的流轉(zhuǎn), 就會(huì)慢慢的進(jìn)入一個(gè)惡性循環(huán), 代碼量上只有加法沒有了減法. 因?yàn)殡S著系統(tǒng)變復(fù)雜, 牽一發(fā)就會(huì)動(dòng)全局, 而新來的維護(hù)者, 對原有的體系并沒有那么多時(shí)間給他讓他全面掌握. 即使有這么多時(shí)間, 要想掌握以前那么多的維護(hù)者的思維的結(jié)合, 也不是一件容易的事情…
那么, 長次以往, 這個(gè)系統(tǒng)將會(huì)越來越不可維護(hù)…. 到一個(gè)大型應(yīng)用進(jìn)入這個(gè)惡性循環(huán), 那么等待他的只有重構(gòu)了.
那么, 能不能對這個(gè)系統(tǒng)做解耦呢? 我們已經(jīng)做了很多解耦了, 數(shù)據(jù), 中間件, 業(yè)務(wù), 邏輯, 等等, 各種分層. 但到Web應(yīng)用這塊, 還能怎么分呢, MVC我們已經(jīng)做過了….
解決利器---微服務(wù)目前比較流行的解決方案是微服務(wù),它可以讓我們的系統(tǒng)盡可能快地響應(yīng)變化,微服務(wù)是指開發(fā)一個(gè)單個(gè)小型的但有業(yè)務(wù)功能的服務(wù),每個(gè)服務(wù)都有自己的處理和輕量通訊機(jī)制,可以部署在單個(gè)或多個(gè)服務(wù)器上。微服務(wù)也指一種種松耦合的、有一定的有界上下文的面向服務(wù)架構(gòu)。也就是說,如果每個(gè)服務(wù)都要同時(shí)修改,那么它們就不是微服務(wù),因?yàn)樗鼈兙o耦合在一起;如果你需要掌握一個(gè)服務(wù)太多的上下文場景使用條件,那么它就是一個(gè)有上下文邊界的服務(wù),這個(gè)定義來自DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。
相對于單體架構(gòu)和SOA,它的主要特點(diǎn)是組件化、松耦合、自治、去中心化,體現(xiàn)在以下幾個(gè)方面:
一組小的服務(wù)
服務(wù)粒度要小,而每個(gè)服務(wù)是針對一個(gè)單一職責(zé)的業(yè)務(wù)能力的封裝,專注做好一件事情。
獨(dú)立部署運(yùn)行和擴(kuò)展
每個(gè)服務(wù)能夠獨(dú)立被部署并運(yùn)行在一個(gè)進(jìn)程內(nèi)。這種運(yùn)行和部署方式能夠賦予系統(tǒng)靈活的代碼組織方式和發(fā)布節(jié)奏,使得快速交付和應(yīng)對變化成為可能。
獨(dú)立開發(fā)和演化
技術(shù)選型靈活,不受遺留系統(tǒng)技術(shù)約束。合適的業(yè)務(wù)問題選擇合適的技術(shù)可以獨(dú)立演化。服務(wù)與服務(wù)之間采取與語言無關(guān)的API進(jìn)行集成。相對單體架構(gòu),微服務(wù)架構(gòu)是更面向業(yè)務(wù)創(chuàng)新的一種架構(gòu)模式。
獨(dú)立團(tuán)隊(duì)和自治
團(tuán)隊(duì)對服務(wù)的整個(gè)生命周期負(fù)責(zé),工作在獨(dú)立的上下文中,自己決策自己治理,而不需要統(tǒng)一的指揮中心。團(tuán)隊(duì)和團(tuán)隊(duì)之間通過松散的社區(qū)部落進(jìn)行銜接。
我們可以看到整個(gè)微服務(wù)的思想就如我們現(xiàn)在面對信息爆炸、知識爆炸是一樣的:通過解耦我們所做的事情,分而治之以減少不必要的損耗,使得整個(gè)復(fù)雜的系統(tǒng)和組織能夠快速的應(yīng)對變化。
微服務(wù)的基石---RPC服務(wù)框架微服務(wù)包含的東西非常多,這里我們只討論RPC服務(wù)框架,ycroute框架基于Yar擴(kuò)展為我們提供了RPC跨網(wǎng)絡(luò)的服務(wù)調(diào)用基礎(chǔ),Yar是一個(gè)非常輕量級的RPC框架, 使用非常簡單, 對于Server端和Soap使用方法很像,而對于客戶端,你可以像調(diào)用本地對象的函數(shù)一樣,調(diào)用遠(yuǎn)程的函數(shù)。
RPC Server 安裝環(huán)境 (客戶端服務(wù)端都需要安裝)擴(kuò)展: yar.so
擴(kuò)展: msgpack.so 可選,一個(gè)高效的二進(jìn)制打包協(xié)議,用于客戶端和服務(wù)端之間包傳輸,還可以選php、json, 如果要使用Msgpack做為打包協(xié)議, 就需要安裝這個(gè)擴(kuò)展。
我們在 framework/application/controllers/Rpcserver.php 中將 Model 層作為服務(wù),提供給遠(yuǎn)程的其它程序調(diào)用,RPC Client 便可以像調(diào)用本地函數(shù)一樣,調(diào)用遠(yuǎn)程的服務(wù),如下我們將 UserinfoModel 和 TradeModel 兩個(gè)模型層提供給遠(yuǎn)程程序調(diào)用。
class RpcserverController extends Core_Controller { public function init() { parent::init(); //必須 } //用戶信息服務(wù) public function userinfoModelAction() { $user_model = Loader::model("UserinfoModel"); //模型層 $yar_server = new Yar_server($user_model); $yar_server->handle(); exit; } //支付服務(wù) public function tradeModelAction() { $trade_model = Loader::model("TradeModel"); //模型層 $yar_server = new Yar_server($trade_model); $yar_server->handle(); exit; } }
上面一共提供了2個(gè)服務(wù),UserinfoModel 和 TradeModel 分別通過http://localhost/index.php?c=... 和 http://localhost/index.php?c=... 來訪問,我們來看看 UserinfoModel 一共有哪些服務(wù):
從上圖可以看到,UserinfoModel 類的所有 public 方法都會(huì)被當(dāng)做服務(wù)提供,包括他繼承的父類 public 方法。
服務(wù)校驗(yàn)為了安全,我們最好對客戶端發(fā)起的RPC服務(wù)請求做校驗(yàn)。在 framework/application/plugins/Filter.php 中做校驗(yàn):
class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由之前調(diào)用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->_auth(); if(!empty($this->params["rpc"])) { $this->_rpc_auth(); //rpc 調(diào)用校驗(yàn) } } //rpc調(diào)用校驗(yàn) protected function _rpc_auth() { $signature = $this->get_rpc_signature($this->params); if($signature != $this->params["signature"]) { $this->response_error(1, "check failed"); } } //rpc簽名計(jì)算,不要改函數(shù)名,在RPC客戶端中 system/YarClientProxy.php 我們也會(huì)用到這個(gè)函數(shù),做簽名。 public function get_rpc_signature($params) { $secret = "MJCISDYFYHHNKBCOVIUHFUIHCQWE"; unset($params["signature"]); ksort($params); reset($params); unset($auth_params["callback"]); unset($auth_params["_"]); $str = $secret; foreach ($params as $value) { $str = $str . trim($value); } return md5($str); } ... }
切記不要修改簽名生成函數(shù) get_rpc_signature 的名字和參數(shù),因?yàn)樵?RPC Client 我們也會(huì)利用這個(gè)函數(shù)做簽名,如果需要修改,請?jiān)?system/YarClientProxy.php 中做相應(yīng)修改,以保證客戶端和服務(wù)器之間的調(diào)用正常。
RPC Clientyar 除了支持 http 之外,還支持tcp, unix domain socket傳輸協(xié)議,不過ycroute中只用了 http ,當(dāng)然 http 也可以開啟 keepalive 以獲得更高的傳輸性能,只不過相比 socket, http 協(xié)議還是多了不少的協(xié)議頭部的開銷。
安裝環(huán)境擴(kuò)展: yar.so
擴(kuò)展: msgpack.so 可選,一個(gè)高效的二進(jìn)制打包協(xié)議,用于客戶端和服務(wù)端之間包傳輸,還可以選php、json, 如果要使用Msgpack做為打包協(xié)議, 就需要安裝這個(gè)擴(kuò)展。
例子:
class UserController extends Core_Controller { ... //獲取用戶信息(從遠(yuǎn)程) public function getUserInfoByRemoteAction() { $userId = $this->params["userid"]; if (empty($userId)) { $this->response_error(10000017, "user_id is empty"); } $model = Loader::remote_model("UserinfoModel"); $userInfo = $model->getUserinfoByUserid($userId); $this->response_success($userInfo); } ... }
通過 $model = Loader::remote_model("UserinfoModel"); 可以獲取遠(yuǎn)程 UserinfoModel,參數(shù)是framework/application/config/rpc.php配置里的鍵值:
$remote_config["UserinfoModel"]["url"] = "http://localhost/index.php?c=rpcserver&m=userinfoModel&rpc=true"; //服務(wù)地址 $remote_config["UserinfoModel"]["packager"] = FALSE; //RPC包類型,F(xiàn)ALSE則選擇默認(rèn),可以為 "json", "msgpack", "php", msgpack 需要安裝擴(kuò)展 $remote_config["UserinfoModel"]["persitent"] = FALSE; //是否長鏈接,需要服務(wù)端支持keepalive $remote_config["UserinfoModel"]["connect_timeout"] = 1000; //連接超時(shí)(毫秒),默認(rèn) 1秒 $remote_config["UserinfoModel"]["timeout"] = 5000; //調(diào)用超時(shí)(毫秒), 默認(rèn) 5 秒 $remote_config["UserinfoModel"]["debug"] = TRUE; //DEBUG模式,調(diào)用異常是否會(huì)打印到屏幕,線上關(guān)閉 $remote_config["TradeModel"]["url"] = "http://localhost/index.php?c=rpcserver&m=tradeModel&rpc=true"; $remote_config["TradeModel"]["packager"] = FALSE; $remote_config["TradeModel"]["persitent"] = FALSE; $remote_config["TradeModel"]["connect_timeout"] = 1000; $remote_config["TradeModel"]["timeout"] = 5000; $remote_config["TradeModel"]["debug"] = TRUE;
這樣,我們就可以把 model 當(dāng)成本地對象一樣調(diào)用遠(yuǎn)程 UserinfoModel 的成員方法。
url簽名調(diào)用遠(yuǎn)程服務(wù)的時(shí)候,system/YarClientProxy.php 會(huì)從配置中獲取服務(wù)的 url, 然后調(diào)用 FilterPlugin::get_rpc_signature 方法對 URL 做簽名,并將簽名參數(shù)拼接到 url 結(jié)尾,發(fā)起調(diào)用。
class YarClientProxy { ... public static function get_signatured_url($url) { $get = array(); $t = parse_url($url, PHP_URL_QUERY); parse_str($t, $get); $get["timestamp"] = time(); $get["auth"] = rand(11111111, 9999999999); $signature = FilterPlugin::get_rpc_signature($get); return $url . "×tamp=" . $get["timestamp"] . "&auth=" . $get["auth"] . "&signature=" . $signature; } ... }調(diào)用異常日志
日志位于 /data/app/logs/localhost 下,localhost 為項(xiàng)目域名。
[root@gzapi: /data/app/logs/localhost]# ls yar_client_proxy.20190214.log.wf
[ERROR] [2019-02-14 18:57:13] [0] [index.php|23 => | => User.php|61 => YarClientProxy.php|46] [218.30.116.3] [/index.php?c=user&m=getUserInfoByRemote&userid=6818810&token=c9bea5dee1f49488e2b4b4645ff3717e1] [] [] - "yar_client_call_error URL=[http://tr.gaoqu.site/index.ph...] , Remote_model=[UserinfoModel] Func=[getUserinfoByUserid] Exception=[server responsed non-200 code "500"]"
RPC 并行調(diào)用yar框架支持并行調(diào)用,可以同時(shí)調(diào)用多個(gè)服務(wù),這樣可以充分利用CPU性能,避免IO等待,提升系統(tǒng)性能,按照yar的流程,你首先得一個(gè)個(gè)注冊服務(wù),然后發(fā)送注冊的調(diào)用,然后reset 重置調(diào)用。在ycroute 中,一個(gè)函數(shù)就可以了。
用 Loader::concurrent_call($call_params); 來并行調(diào)用RPC服務(wù), 其中 call_params是調(diào)用參數(shù)數(shù)組。
如下數(shù)組包含4個(gè)元素,每個(gè)調(diào)用都包含 model, method 兩個(gè)必輸參數(shù),以及 parameters, callback , error_callback 三個(gè)可選參數(shù)。
model : 服務(wù)名,是framework/application/config/rpc.php配置里的鍵值。
method : 調(diào)用函數(shù)
parameters : 函數(shù)的參數(shù),是一個(gè)數(shù)組,數(shù)組的個(gè)數(shù)為參數(shù)的個(gè)數(shù)
callback : 回調(diào)函數(shù),調(diào)用成功之后回調(diào),針對的是各自的回調(diào)。
error_callback : 調(diào)用失敗之后會(huì)回調(diào)這個(gè)函數(shù),其中調(diào)用超時(shí)不會(huì)回調(diào)該方法, 針對的也是各自的回調(diào)。
class UserController extends Core_Controller { //獲取用戶信息(并行遠(yuǎn)程調(diào)用) public function multipleGetUsersInfoByRemoteAction() { $userId = $this->params["userid"]; $call_params = array(); $call_params[] = ["model" => "UserinfoModel", "method" => "getUserinfoByUserid", "parameters" => array($userId), "callback" => array($this, "callback1")]; $call_params[] = ["model" => "UserinfoModel", "method" => "getUserInUserids", "parameters" => array(array(6860814, 6870818)), "callback" => array($this, "callback2"), "error_callback" => array($this, "error_callback")]; $call_params[] = ["model" => "UserinfoModel", "method" => "getUserByName", "parameters" => array("CH.smallhow")]; //不存在的方法 $call_params[] = ["model" => "UserinfoModel", "method" => "unknownMethod", "parameters" => array(), "error_callback" => array($this, "error_callback")]; Loader::concurrent_call($call_params); echo json_encode($this->retval); exit; } //回調(diào)函數(shù)1 public function callback1($retval, $callinfo) { $this->retval["callback1"]["retval"] = $retval; $this->retval["callback1"]["callinfo"] = $callinfo; } //回調(diào)函數(shù)2 public function callback2($retval, $callinfo) { $this->retval["callback2"]["retval"] = $retval; $this->retval["callback2"]["callinfo"] = $callinfo; } //錯(cuò)誤回調(diào) public function error_callback($type, $error, $callinfo) { $tmp["type"] = $type; $tmp["error"] = $error; $tmp["callinfo"] = $callinfo; $this->retval["error_callback"][] = $tmp; } }
我特意將第4個(gè)調(diào)用的method設(shè)置一個(gè)不存在的函數(shù),大家可以看下上面的并行調(diào)用的結(jié)果:
{ "error_callback":[ { "type":4, "error":"call to undefined api ::unknownMethod()", "callinfo":{ "sequence":4, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=5930400101&signature=fc0ed911c624d9176523544421a0248d", "method":"unknownMethod" } } ], "callback1":{ "retval":{ "user_id":"6818810", "appid":"wx385863ba15f573b6", "open_id":"oXtwn4wkPO4FhHmkan097DpFobvA", "union":null, "session_key":"Et1yjxbEfRqVmCVsYf5qzA==", "nickname":"芒果", "city":"Yichun", "province":"Jiangxi", "country":"China", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", "gender":"1", "form_id":"", "token":"5a350bc05bbbd9556f719a0b8cf2a5ed", "amount":"0", "last_login_time":"2018-10-04 16:01:27", "regist_time":"2018-06-29 21:24:45", "updatetime":"2018-10-04 16:01:27" }, "callinfo":{ "sequence":1, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=8384256613&signature=c0f9c944ae070d2eb38c8e9638723a2e", "method":"getUserinfoByUserid" } }, "callback2":{ "retval":{ "6860814":{ "user_id":"6860814", "nickname":"Smile、格調(diào)", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKNE5mFLk33q690Xl1N6mrehQr0ggasgk8Y4cuaUJt4CNHORwq8rVjwET7H06F3aDjU5UiczjpD4nw/132", "city":"Guangzhou" }, "6870818":{ "user_id":"6870818", "nickname":"Yang", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLTKBoU1tdRicImnUHyr43FdMulSHRhAlsQwuYgAyOlrwQaLGRoFEHbgfVuyEV1K1VU2NMmm0slS4w/132", "city":"Hengyang" } }, "callinfo":{ "sequence":2, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=7249482640&signature=26c419450bb4747ac166fbaa4a242b77", "method":"getUserInUserids" } } }附錄 - Core_Model 中的輔助極速開發(fā)函數(shù)(不關(guān)心可以跳過)
$this->redis_conf_path = "default_master"; //用到快速緩存時(shí),需要在 __construct 構(gòu)造函數(shù)中加上 redis 緩存配置
/** * 插入表記錄 * @param string table 表名 * @param array data 表數(shù)據(jù) * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 */ public function insert_table($table, $data, $redis_key = ""); /** * 更新表記錄 * @param string table 表名 * @param array where 查詢條件 * @param array data 更新數(shù)據(jù) * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 */ public function update_table($table, $where, $data, $redis_key = ""); /** * 替換表記錄 * @param string table 表名 * @param array data 替換數(shù)據(jù) * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 */ public function replace_table($table, $data, $redis_key = ""); /** * 刪除表記錄 * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 */ public function delete_table($table, $where, $redis_key = ""); /** * 獲取表數(shù)據(jù) * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 * @param int redis_expire redis 緩存到期時(shí)長(秒) * @param boolean set_empty_flag 是否標(biāo)注空值,如果標(biāo)注空值,在表記錄更新之后,一定記得清理空值標(biāo)記緩存 */ public function get_table_data($table, $where = array(), $redis_key = "", $redis_expire = 600, $set_empty_flag = true); /** * 根據(jù)key獲取表記錄 * @param string table 表名 * @param string key 鍵名 * @param string value 鍵值 * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 * @param int redis_expire redis 緩存到期時(shí)長(秒) * @param boolean set_empty_flag 是否標(biāo)注空值,如果標(biāo)注空值,在表記錄更新之后,一定記得清理空值標(biāo)記緩存 */ public function get_table_data_by_key($table, $key, $value, $redis_key = "", $redis_expire = 300, $set_empty_flag = true); /** * 獲取一條表數(shù)據(jù) * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis 緩存鍵值, 可空, 非空時(shí)清理鍵值緩存 * @param int redis_expire redis 緩存到期時(shí)長(秒) * @param boolean set_empty_flag 是否標(biāo)注空值,如果標(biāo)注空值,在表記錄更新之后,一定記得清理空值標(biāo)記緩存 */ public function get_one_table_data($table, $where, $redis_key = "", $redis_expire = 600, $set_empty_flag = true);
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/30976.html
摘要:一是什么全稱用語言編寫的輕量級擴(kuò)展框架專注于開發(fā)。這里只是給出了一種測試方法通過多次不同并發(fā)數(shù)測試結(jié)果得知與原生性能消耗是。業(yè)務(wù)開發(fā)速度結(jié)論采用框架開發(fā)業(yè)務(wù)代碼量能節(jié)約。 showImg(https://segmentfault.com/img/bVbamHp?w=320&h=320); 一、Asf 是什么? 全稱 API Services Framework, 用C語言編寫的輕量級P...
摘要:每一個(gè)開發(fā)者都知道,擁有一個(gè)強(qiáng)大的框架可以讓開發(fā)工作變得更加快捷安全和有效。官方網(wǎng)站是一款老牌的框架,現(xiàn)在穩(wěn)定版本已經(jīng)是了。官方網(wǎng)站是由最大的社區(qū)之一的管理開發(fā)的,也是一個(gè)開源的框架。 對于Web開發(fā)者來說,PHP是一款非常強(qiáng)大而又受歡迎的編程語言。世界上很多頂級的網(wǎng)站都是基于PHP開發(fā)的。 每一個(gè)開發(fā)者都知道,擁有一個(gè)強(qiáng)大的框架可以讓開發(fā)工作變得更加快捷、安全和有效。在開發(fā)項(xiàng)目之前選...
摘要:是一個(gè)基于擴(kuò)展實(shí)現(xiàn)的輕量級高性能的常駐內(nèi)存型的和應(yīng)用服務(wù)框架高度封裝了,,服務(wù)器,以及基于實(shí)現(xiàn)可擴(kuò)展的服務(wù),同時(shí)支持包方式安裝部署項(xiàng)目?;趯?shí)用,抽象事件處理類,實(shí)現(xiàn)與底層的回調(diào)的解耦,支持同步異步調(diào)用,內(nèi)置等常用組件等。 swoolefy swoolefy是一個(gè)基于swoole擴(kuò)展實(shí)現(xiàn)的輕量級高性能的常駐內(nèi)存型的API和Web應(yīng)用服務(wù)框架,高度封裝了http,websocket,ud...
摘要:是一個(gè)用語言編寫的輕量級的開源高性能框架,支持多種多路復(fù)用技術(shù)和等支持,定時(shí)器和信號等事件注冊事件優(yōu)先級。定時(shí)器提供了系列函數(shù),實(shí)現(xiàn)一次性定時(shí)器,精度微秒。 Libevent 是一個(gè)用C語言編寫的、輕量級的開源高性能I/O框架,支持多種 I/O 多路復(fù)用技術(shù): epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定時(shí)器和信號等事件;注冊事件優(yōu)...
摘要:今天,就為開發(fā)者介紹個(gè)方便的工具。對開發(fā)者來說,是一個(gè)非常有用的工具,它提供了超過個(gè)有用的函數(shù)。該工具檢查輸入源代碼和報(bào)告任何違反給定的標(biāo)準(zhǔn)??蚣苁且粋€(gè)開發(fā)的工具。它側(cè)重于安全性和性能,絕對是最安全的開發(fā)框架之一。 PHP是為Web開發(fā)設(shè)計(jì)的服務(wù)器腳本語言,但也是一種通用的編程語言。超過2.4億個(gè)索引域使用PHP,包括很多重要的網(wǎng)站,例如Facebook、Digg和WordPress。...
閱讀 1772·2021-10-18 13:34
閱讀 3970·2021-09-08 10:42
閱讀 1600·2021-09-02 09:56
閱讀 1647·2019-08-30 15:54
閱讀 3181·2019-08-29 18:44
閱讀 3340·2019-08-26 18:37
閱讀 2261·2019-08-26 12:13
閱讀 507·2019-08-26 10:20