2018年10月31日 星期三

《面試官別再問》PHP-高並發和大流量的解決方案

高並發架構相關概念

1、QPS (每秒查詢率) : 每秒鐘請求或者查詢的數量,在互聯網領域,指每秒響應請求數(指HTTP請求)

2、PV(Page View):綜合瀏覽量,即頁面瀏覽量或者點擊量,一個訪客在24小時內訪問的頁面數量

--注:同一個人瀏覽你的網站的同一頁面,只記做一次pv

3、吞吐量(fetches/sec) :單位時間內處理的請求數量(通常由QPS和並發數決定)

4、響應時間:從請求發出到收到響應花費的時間

5、獨立訪客(UV):一定時間範圍內,相同訪客多次訪問網站,只計算為1個獨立訪客

6、帶寬:計算帶寬需關注兩個指標,峰值流量和頁面的平均大小

7、日網站帶寬: PV/統計時間(換算到秒) * 平均頁面大小(kb)* 8

高並發解決方案

1、前端優化

(1) 減少HTTP請求[將css,js等合併]

(2) 添加異步請求(先不將所有數據都展示給用戶,用戶觸發某個事件,才會異步請求數據)

(3) 啟用瀏覽器緩存和文件壓縮

(4) CDN加速

(5) 建立獨立的圖片服務器(減少I/O)

2、服務端優化

(1) 頁面靜態化

(2) 並發處理

(3) 隊列處理 

3、數據庫優化


(1) 數據庫緩存

(2) 分庫分錶,分區

(3) 讀寫分離

(4) 負載均衡

4、web服務器優化


(1) nginx反向代理實現負載均衡

(2) lvs實現負載均衡



涉及搶購、秒殺、抽獎、搶票等活動時,為了避免超賣,那麼庫存數量是有限的,但是如果同時下單人數超過了庫存數量,就會導致商品超賣問題。

那麼我們怎麼來解決這個問題呢

高並發的情況下,正常邏輯寫的話數據庫的庫存會出現負數,對付這類問題有很多解決方案,我就不一一贅述,我這次用的是redis的隊列機制。


採用ab壓力測試:

-r 指定接收到錯誤信息時不退出程序

-t 等待響應的最大時間

-n 指定壓力測試總共的執行次數

-c 用於指定壓力測試的並發數

進入apache的bin目錄 cmd輸入下列分代碼

E:\phpstudy\Apache\bin>ab -r -t 60 -n 3000 -c 600 http://127.0.0.1/api/kill/index/id/1

結果

2018年10月30日 星期二

《面試官別再問》PHP實作CI3 + JQuery前後端分離

前後端分離的核心概念

在傳統的 CI3 開發中,通常由 CI3 的視圖 (View) 直接輸出 HTML,並包含 JavaScript 程式碼。但在前後端分離的模式下:

  • 後端 (CI3):主要負責提供 API 服務(如 RESTful API),處理業務邏輯、資料庫操作、身份驗證等。它只返回資料(通常是 JSON 格式),而不是完整的 HTML 頁面。

  • 前端 (jQuery):負責 使用者介面 (UI) 的呈現和互動。它透過 AJAX 請求從後端取得資料,然後動態地將資料渲染到頁面上。


CI3 後端實作

1. 設定 CI3 專案

確保你已經安裝並設定好 CI3 專案。

2. 建立 API 控制器

建立一個新的控制器來處理 API 請求。例如,我們建立一個 Api.php 控制器:

PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Api extends CI_Controller {

    public function __construct() {
        parent::__construct();
        // 載入模型或輔助函數
        $this->load->model('your_data_model'); // 假設你有一個處理資料的模型
        $this->load->helper('url');
    }

    // 範例:取得所有產品的 API
    public function products() {
        $products = $this->your_data_model->get_all_products(); // 從模型取得資料

        // 設定回應頭為 JSON
        header('Content-Type: application/json');
        echo json_encode(['status' => 'success', 'data' => $products]);
    }

    // 範例:新增產品的 API (POST 請求)
    public function add_product() {
        if ($this->input->method() === 'post') {
            $product_name = $this->input->post('name');
            $product_price = $this->input->post('price');

            if ($product_name && $product_price) {
                $data = [
                    'name' => $product_name,
                    'price' => $product_price
                ];
                $insert_id = $this->your_data_model->insert_product($data); // 插入資料

                header('Content-Type: application/json');
                echo json_encode(['status' => 'success', 'message' => '產品新增成功', 'id' => $insert_id]);
            } else {
                header('Content-Type: application/json');
                echo json_encode(['status' => 'error', 'message' => '名稱和價格為必填']);
            }
        } else {
            header('Content-Type: application/json');
            echo json_encode(['status' => 'error', 'message' => '只允許 POST 請求']);
        }
    }

    // 範例:處理找不到方法或錯誤的預設回應
    public function _remap($method, $params = array()) {
        if (method_exists($this, $method)) {
            return call_user_func_array(array($this, $method), $params);
        } else {
            header('Content-Type: application/json');
            echo json_encode(['status' => 'error', 'message' => 'API 方法不存在']);
        }
    }
}

3. 設定路由 (routes.php)

application/config/routes.php 中設定 API 的路由,讓 URL 更簡潔。

PHP
$route['api/products'] = 'api/products';
$route['api/add_product'] = 'api/add_product';

4. 建立資料模型 (Model)

建立一個模型來處理資料庫操作。例如 Your_data_model.php

PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Your_data_model extends CI_Model {

    public function __construct() {
        parent::__construct();
        $this->load->database();
    }

    public function get_all_products() {
        $query = $this->db->get('products'); // 假設你的產品資料表名為 'products'
        return $query->result_array();
    }

    public function insert_product($data) {
        $this->db->insert('products', $data);
        return $this->db->insert_id();
    }
}

jQuery 前端實作

前端部分會使用 HTML、CSS 和 jQuery 來構建使用者介面,並透過 AJAX 請求與 CI3 後端進行互動。

1. HTML 結構

建立一個 HTML 檔案(例如 index.html 或在 CI3 的 views 資料夾下建立一個簡單的視圖 frontend_view.php,但不渲染資料):

HTML
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>產品列表 - 前後端分離</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #product-list { margin-top: 20px; border: 1px solid #ccc; padding: 10px; }
        .product-item { margin-bottom: 5px; padding: 5px; background-color: #f9f9f9; }
        form { margin-top: 20px; padding: 10px; border: 1px solid #eee; }
        input[type="text"], input[type="number"] { padding: 8px; margin-right: 10px; }
        button { padding: 8px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        .message { margin-top: 10px; padding: 10px; border-radius: 5px; }
        .message.success { background-color: #d4edda; color: #155724; border-color: #c3e6cb; }
        .message.error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
    </style>
</head>
<body>
    <h1>產品管理系統 (前後端分離)</h1>

    <h2>新增產品</h2>
    <form id="add-product-form">
        <label for="product-name">產品名稱:</label>
        <input type="text" id="product-name" name="name" required>
        <label for="product-price">產品價格:</label>
        <input type="number" id="product-price" name="price" step="0.01" required>
        <button type="submit">新增產品</button>
        <div id="add-product-message" class="message" style="display: none;"></div>
    </form>

    <h2>產品列表</h2>
    <div id="product-list">
        載入中...
    </div>

    <script>
        $(document).ready(function() {
            const baseUrl = '<?php echo base_url(); ?>'; // 假設前端頁面是由 CI3 載入

            // 函式:載入產品列表
            function loadProducts() {
                $.ajax({
                    url: baseUrl + 'api/products', // CI3 後端 API 網址
                    method: 'GET',
                    dataType: 'json', // 預期回應為 JSON 格式
                    success: function(response) {
                        $('#product-list').empty(); // 清空現有列表
                        if (response.status === 'success' && response.data.length > 0) {
                            $.each(response.data, function(index, product) {
                                $('#product-list').append(
                                    '<div class="product-item">' +
                                    'ID: ' + product.id + ', ' +
                                    '名稱: ' + product.name + ', ' +
                                    '價格: $' + product.price +
                                    '</div>'
                                );
                            });
                        } else if (response.status === 'success' && response.data.length === 0) {
                            $('#product-list').append('<p>目前沒有產品。</p>');
                        } else {
                            $('#product-list').append('<p>載入產品失敗: ' + response.message + '</p>');
                            console.error('API Error:', response.message);
                        }
                    },
                    error: function(xhr, status, error) {
                        $('#product-list').empty().append('<p>與後端通訊失敗,請檢查網路或伺服器。</p>');
                        console.error('AJAX Error:', status, error, xhr.responseText);
                    }
                });
            }

            // 初始載入產品
            loadProducts();

            // 表單提交事件:新增產品
            $('#add-product-form').submit(function(e) {
                e.preventDefault(); // 阻止表單預設提交行為

                const productName = $('#product-name').val();
                const productPrice = $('#product-price').val();
                const messageDiv = $('#add-product-message');

                messageDiv.hide().removeClass('success error').text(''); // 清空並隱藏訊息

                $.ajax({
                    url: baseUrl + 'api/add_product', // 新增產品 API 網址
                    method: 'POST',
                    dataType: 'json',
                    data: {
                        name: productName,
                        price: productPrice
                    },
                    success: function(response) {
                        if (response.status === 'success') {
                            messageDiv.addClass('success').text(response.message).fadeIn();
                            $('#add-product-form')[0].reset(); // 清空表單
                            loadProducts(); // 重新載入產品列表
                        } else {
                            messageDiv.addClass('error').text(response.message).fadeIn();
                        }
                    },
                    error: function(xhr, status, error) {
                        messageDiv.addClass('error').text('新增產品失敗,請檢查網路或伺服器。').fadeIn();
                        console.error('AJAX Error:', status, error, xhr.responseText);
                    }
                });
            });
        });
    </script>
</body>
</html>

注意事項:

  • 如果你的前端頁面不是由 CI3 控制器載入,則 const baseUrl = '<?php echo base_url(); ?>'; 這行需要替換為你的 CI3 專案的實際根 URL,例如 const baseUrl = 'http://localhost/your_ci3_project/';

  • 你需要確保 CI3 專案能夠正確處理 AJAX 請求。如果遇到 CORS (Cross-Origin Resource Sharing) 問題,你需要在 CI3 後端配置允許跨域請求。


部署與測試

  1. 資料庫設定:在 CI3 的 application/config/database.php 中設定你的資料庫連接。

  2. 建立資料表:在你的資料庫中建立一個 products 表格:

    SQL
    CREATE TABLE products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        price DECIMAL(10, 2) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
  3. 運行 CI3 專案:透過 Apache 或 Nginx 配置你的 CI3 專案。

  4. 訪問前端頁面

    • 如果你把 HTML 頁面放在 CI3 的 views 資料夾下,並透過 CI3 控制器載入(例如 application/controllers/Welcome.php 中的 index() 方法載入 frontend_view),那麼訪問 http://localhost/your_ci3_project/ 或對應的 URL。

    • 如果你把 HTML 頁面獨立放置,則直接打開該 HTML 檔案,但需注意 baseUrl 的設定。


優點與考量

優點:

  • 職責分離:前後端開發人員可以獨立工作,互不影響。

  • 提高開發效率:前端和後端可以並行開發。

  • 更好的擴展性:後端 API 可以為多個前端應用(如 Web、Mobile App)提供服務。

  • 彈性:前端可以使用更現代的框架 (如 React, Vue, Angular) 而不只是 jQuery。

  • 效能優化:前端可以實現更細粒度的頁面更新,減少完整頁面載入。

考量:

  • CORS 問題:如果前後端部署在不同的域名或埠,可能會遇到跨域問題,需要在後端進行配置。

  • 路由管理:需要為後端 API 設計清晰的路由。

  • 狀態管理:在純前端應用中,需要考慮前端的狀態管理(例如使用瀏覽器儲存或更複雜的狀態管理庫)。

  • SEO 挑戰:對於爬蟲不支援 JavaScript 的情況,動態載入的內容可能對 SEO 不利。可以考慮 SSR (Server-Side Rendering) 或預渲染。

  • 安全性:需要確保 API 的安全性,例如身份驗證、授權、防止 XSS 和 CSRF 攻擊。


進一步的優化與學習

  • API 版本控制:為 API 引入版本控制(例如 /api/v1/products)。

  • 身份驗證與授權:可以使用 JWT (JSON Web Tokens) 或其他基於 Token 的驗證機制。

  • 錯誤處理:定義統一的 API 錯誤回應格式。

  • 表單驗證:在前端和後端都進行資料驗證。

  • 安全性:實作輸入過濾、輸出編碼等防止常見 Web 攻擊的措施。

  • 前端框架:考慮使用更現代的前端框架(React, Vue, Angular)來取代 jQuery,它們提供了更強大的組件化、狀態管理和開發工具,更適合大型專案。

透過上述步驟,你就可以在 PHP CI3 專案中成功實現前後端分離,並透過 jQuery 進行前端互動。

熱門文章