[精選] 用Swoole實現四種高性能靜態API方案

什麼是靜態化API?php

靜態化API能夠理解成把一些接口的數據存儲在服務器本地。經常使用的是存成json文件,也能夠是放在swoole的table中,總之是用戶不從數據庫直接讀取數據,而是從本地加載的方式來大幅提升性能,由於不少系統的性能瓶頸是在數據庫的位置。前端

解決方案mysql

方案1 easySwoole + crontab
方案2 easySwoole定時器
方案3 Swoole table
方案4 Redisweb

實現redis

這裏作的分頁的場景,不包含分頁的源碼,只從拿到了分頁的數據看看定時生成json和獲取json的部分sql

原始的方法 – 讀取mysql數據庫

這是原始的方法,每一個用戶訪問都會去數據庫裏面讀取一次,每一次分頁也會訪問數據庫,會形成大量的資源開銷。json

public function lists0(){
    $condition = [];
    if(!empty($this->params['cat_id'])){
        $condition['cat_id'] = intval($this->params['cat_id']);
    }

    try {
        $videoModel = new VideoModel();
        $data = $videoModel->getVideoData($condition, $this->params['page'], $this->params['size']);
    } catch (Exception $e) {
        //$e->getMessage();
        return $this->writeJson(Status::CODE_BAD_REQUEST,"服務異常");
    }

    if(!empty($data['lists'])){
        foreach ($data['lists'] as &$list){
            $list['create_time'] = date("Ymd H:i:s",$list['create_time']);
            $list['video_duration'] = gmstrftime("%H:%M:%S",$list["video_duration"]);
        }
    }

    return $this->writeJson(Status::CODE_OK,'OK',$data);
}

知識點:數組

1 獲取以秒飛單位的時間長度使用gmstrftime(「%H:%M:%S」,$list[「video_duration」])緩存

2 在作模型的時候寫一個基類,把鏈接數據庫的工做放在這個基類的構造方法當中,這樣每次實例化的時候就自動鏈接了,提升代碼的複用性

<?php
/**
 * Created by bingxiong.
 * Date: 12/23/19
 * Time: 1:20 AM
 * Description:
 */

namespace AppModel;

use EasySwooleCoreComponentDi;

class Base
{
    public $db = "";

    public function __construct()
    {
        if(empty($this->tableName)){
            throw new Exception("table error");
        }
        $db = Di::getInstance()->get("MYSQL");
        if($db instanceof MysqliDb){
            $this->db = $db;
        }else{
            throw new Exception("db error");
        }
    }

    public function add($data){
        if(empty($data) || !is_array($data)){
            return false;
        }
        return $this->db->insert($this->tableName,$data);
    }

}

方案1、方案二 使用easySwoole定時器以及contab的生成靜態化API

這是直接讀取靜態化API的方法,其實就是讀文件而後返回給前端

/**
 * 第二套方法 - 直接讀取靜態化json數據
 * @return bool
 */
public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoFile = EASYSWOOLE_ROOT."/webroot/video/json/".$catId.".json";
    $videoData = is_file($videoFile) ? file_get_contents($videoFile) : [];
    var_dump($videoData);
    $videoData = !empty($videoData) ? json_decode($videoData,true) : [];

    $count = count($videoData);
    // var_dump($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

這裏的getPagingData使用了array_slice來切割數組來作分頁。

/**
 * 獲取分頁信息
 * @param $count
 * @param $data
 * @return array
 */
public function getPagingData($count, $data){
    $totalPage = ceil($count / $this->params['size']);
    $data = $data ?? [];
    $data = array_slice($data, $this->params['from'], $this->params['size']);
    return [
        'total_page' => $totalPage,
        'page_size' => $this->params['page'],
        'count' => intval($count),
        'list' => $data
    ];

}

定時生成Json文件代碼 – 核心部分 全局事件easySwooleEvent->mainServiceCreate中執行,這樣easySwoole啓動了以後就會執行,這裏使用的原生的crontab,只可以精確到分

$cacheVideoObj = new VideoCache();
// 使用cronTab處理定時任務
// 這裏是閉包 要use $cacheVideoObj以後才能獲取,實例化不放在function中是爲了防止每次都實例化浪費資源
CronTab::getInstance()
    ->addRule("test_bing_crontab", '*/1 * * * *', function () use($cacheVideoObj) {
        $cacheVideoObj->setIndexVideo();
    });

也可使用easySwoole的定時器來實現也是放在mainServiceCreate中執行,這裏的代碼必定要注意要註冊一個onWorkerStart。

而後指定一個進程去執行這個定時任務注意一下這裏閉包裏面又有一個閉包,外面的變量要use兩次。

必定要注意easwoole定時器的使用,這裏的坑比較多必定要注意,優點是swoole的定時器能夠到毫秒級而contab只能到分級

// easySwoole自帶定時器
$register->add(EventRegister::onWorkerStart,function (swoole_server $server,$workerId)use($cacheVideoObj){
    //讓第一個進程去執行,不然每一個進程都會執行
    if($workerId == 0){
        Timer::loop(1000*2,function ()use ($cacheVideoObj){
            $cacheVideoObj->setIndexVideo();
        });
    }
});

方案三 Swoole table的解決方案

swoole_table一個基於共享內存和鎖實現的超高性能,併發數據結構。用於解決多進程/多線程數據共享和同步加鎖問題。性能十分強悍。使用起來有一點像是緩存。

這裏再也不是生成一個json文件去讀取了,

讀取table中數據給前端的方法,注意要use cache這個類,這裏直接使用get就能夠根據key獲取到table中的數據,注意這個是一個單例模式,所以須要getInstance

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoFile = EASYSWOOLE_ROOT."/webroot/video/json/".$catId.".json";

    //  第三套方案 table
    $videoData = Cache::getInstance()->get("index_video_data_cat_id".$catId);
    $videoData = !empty($videoData) ? $videoData : [];

    $count = count($videoData);;
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

設置直接set(key,data)就能夠了,注意這裏的代碼不是很嚴謹,好比要判斷一下是否是設置成功了,沒有設置成功發短信之類的,也要處理一下空值的場景,這裏只是作一個演示。

<?php
/**
 * Created by bingxiong.
 * Date: 12/23/19
 * Time: 10:13 PM
 * Description:
 */

namespace AppLibCache;

use AppModelVideo as VideoModel;
use EasySwooleConfig;
use EasySwooleCoreComponentCacheCache;

class Video
{
    public function setIndexVideo()
    {
        $catIds = array_keys(Config::getInstance()->getConf("category"));
        array_unshift($catIds, 0);

        $modelObj = new VideoModel();

        foreach ($catIds as $catId) {
            $condition = [];
            if (!empty($catId)) {
                $condition['cat_id'] = $catId;
            }

            try {
                $data = $modelObj->getVideoCacheData($condition);
            } catch (Exception $e) {
                // 短信報警
                $data = [];
            }
            if (empty($data)) {

            }
            foreach ($data as &$list) {
                $list['create_time'] = date("Ymd H:i:s", $list["create_time"]);
                $list["video_duration"] = gmstrftime("%H:%M:%s", $list["video_duration"]);
            }
            // 因爲table存在內存因此重啓服務器時數據會丟失,要將配置中的PERSISTENT_TIME設置爲1進行落盤操做
            Cache::getInstance()->set("index_video_data_cat_id".$catId, $data);
        }
    }
}

注意,必定要去config裏面開啓

'PERSISTENT_TIME'=>1//若是須要定時數據落地,請設置對應的時間週期,單位爲秒

不然會在重啓服務的時候沒有數據,由於每次啓動的時候swoole table會清空,要等到定時去set table的時候纔會有數據,所以要開啓數據落盤,這樣會生成兩個文件:
image
每次啓動的時候因爲尚未執行定時任務,就會先讀取這兩個落盤的文件中的數據,防止服務啓動時等待table生成形成業務中斷。

方案四 Redis

用redis來作數據緩存,每次從緩存裏面度讀

先重寫一下set方法,更加嚴謹一點

/**
 * 重寫set方法 處理一下失效時間以及數組轉json
 * @param $key
 * @param $value
 * @param $time
 * @return bool|string
 */
public function set($key, $value, $time){
    if(empty($key)){
        return '';
    }
    if(is_array($value)){
        $value = json_encode($value);
    }
    if(!$time){
        return $this->redis->set($key,$value);
    }
    return $this->redis->setex($key, $time, $value);
}

使用起來很簡單啦,在以前的代碼中

Di::getInstance()->get("REDIS")->set("index_video_data_cat_id".$catId, $data);

而後取出的數據的部分

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    //redis
    $videoData = Di::getInstance()->get("REDIS")->get("index_video_data_cat_id".$catId);
    $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
    $count = count($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

高度封裝

只須要在配置文件中進行配置便可選擇相應方法

設置靜態化API和獲取靜態化API的方法

<?php
/**
 * Created by bingxiong.
 * Date: 12/23/19
 * Time: 10:13 PM
 * Description:
 */

namespace AppLibCache;

use AppModelVideo as VideoModel;
use EasySwooleConfig;
use EasySwooleCoreComponentCacheCache;
use EasySwooleCoreComponentDi;
use EasySwooleCoreHttpMessageStatus;

class Video
{
    /**
     * 設置靜態API的方法
     * @throws Exception
     */
    public function setIndexVideo()
    {
        $catIds = array_keys(Config::getInstance()->getConf("category"));
        array_unshift($catIds, 0);

        // 獲取配置
        try {
            $cacheType = Config::getInstance()->getConf("base.indexCacheType");
        } catch (Exception $e) {
            return $this->writeJson(Status::CODE_BAD_REQUEST,"請求失敗");
        }

        $modelObj = new VideoModel();

        foreach ($catIds as $catId) {
            $condition = [];
            if (!empty($catId)) {
                $condition['cat_id'] = $catId;
            }

            try {
                $data = $modelObj->getVideoCacheData($condition);
            } catch (Exception $e) {
                // 短信報警
                $data = [];
            }
            if (empty($data)) {

            }

            foreach ($data as &$list) {
                $list['create_time'] = date("Ymd H:i:s", $list["create_time"]);
                $list["video_duration"] = gmstrftime("%H:%M:%s", $list["video_duration"]);
            }

            switch ($cacheType) {
                case 'file':
                    $res = file_put_contents($this->getVideoCatIdFile($catId), json_encode($data));
                    break;
                case 'table':
                    $res = Cache::getInstance()->set($this->getCatKey($catId), $data);
                    break;
                case 'redis':
                    $res = Di::getInstance()->get("REDIS")->set($this->getCatKey($catId), $data);
                    break;
                default:
                    throw new Exception("請求不合法");
                    break;
            }

            if(empty($res)){
            //    記錄日誌
            //    報警

            }

        }

    }

    /**
     * 獲取數據的方法
     * @param $catId
     * @return array|bool|mixed|null|string
     * @throws Exception
     */
    public function getCache($catId){
        $cacheType = Config::getInstance()->getConf("base.indexCacheType");
        switch ($cacheType){
            case 'file':
                $videoFile = $this->getVideoCatIdFile($catId);
                $videoData = is_file($videoFile) ? file_get_contents($videoFile) : [];
                $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
                break;
            case 'table':
                $videoData = Cache::getInstance()->get($this->getCatKey($catId));
                $videoData = !empty($videoData) ? $videoData : [];
                break;
            case 'redis':
                $videoData = Di::getInstance()->get("REDIS")->get($this->getCatKey($catId));
                $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
                break;
            default:
                throw new Exception("請求不合法");
                break;
        }
        return $videoData;
    }

    public function getVideoCatIdFile($catId = 0){
        return EASYSWOOLE_ROOT . "/webroot/video/json/" . $catId . ".json";
    }

    public function getCatKey($catId = 0){
        return "index_video_data_cat_id".$catId;
    }
}

只需修改配置文件

<?php
/**
 * Created by bingxiong.
 * Date: 12/24/19
 * Time: 5:12 PM
 * Description:
 */
return [
    "indexCacheType" => "redis" // redis file table
];

控制器獲取數據給前端

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoData = (new VideoCache())->getCache($catId);
    $count = count($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

image

相關文章
相關標籤/搜索