PHPer的月工做總結之構建抽獎工具

前言

目標是每月寫一篇文章,對從事編程開發的基礎知識作一個學習總結。這個月的計劃原本是對基礎的數據結構作一個沉澱,可是,可是,可是......這個月的的狀態就是工做工做...既然這樣就總結下這個月的工做吧。php

工做內容

促銷活動的抽獎工具,具有以下功能:前端

  • 根據不一樣的訂單金額抽獎,可設置最高訂單金額限制
  • 根據不一樣的抽獎次數抽獎,可設置積分消耗限制
  • 根據不一樣的時間段抽獎,可設置積分消耗限制

建模

一看到上面的需求,很顯然的咱們會想到策略模式,制定三種不一樣的策略實體類:mysql

  • 按訂單抽獎策略:LotteryOrderStrategy
  • 按次數抽獎策略:LotteryTimesStrategy
  • 時間段抽獎策略:LotteryTimeScopeStrategy

創建了具體的三個策略實體類以後,因爲不一樣的抽獎策略其實有不少的類似行爲,咱們開始進行抽象,最後整個的抽獎行爲以下:linux

  • 活動參與條件驗證: check[抽象方法]
  • 讀取規則信息: getRule[具體方法]
  • 匹配符合的規則區間: getNodeByRule[抽象方法]
  • 活動參與次數驗證: checkTimes[具體方法]
  • 活動規則限制驗證: checkJoinLimit[抽象方法]
  • 消費積分: consumePoints[抽象方法]
  • 讀取該規則對應的全部獎品: getPrize[具體方法]
  • 抽獎: draw[具體方法]
  • 組裝獎品信息: packagePrizeInfo[具體方法]

接着,創建抽象類:LotteryAbstract。抽象完成之後:nginx

  • 相同的邏輯: 不一樣抽獎實體類直接繼承使用便可
  • 不一樣的邏輯: 不一樣抽獎實體類具體實現便可

具體抽象類以下:git

abstract class LotteryAbstract
{
    abstract protected function check();

    protected function getRule()
    {
        # code...
    }

    abstract protected function getNodeByRule();

    protected function checkTimes()
    {
        # code...
    }

    abstract protected function checkJoinLimit();

    abstract protected function consumePoints();

    protected function getPrize()
    {
        # code...
    }

    protected function draw()
    {
        # code...
    }

    protected function packagePrizeInfo()
    {
        # code...
    }
}

接着咱們發現其實不一樣的抽獎策略的抽獎流程基本一致,這樣咱們就聯想到了設計模式的「模板模式」,咱們對抽象類作些小的調整,咱們把抽獎的算法調用流程實如今抽象類中,最後抽象類就構成了一個抽獎類的模板。之後咱們增長新的抽象方式,只須要實現抽獎模板的抽象方法便可,變動後的抽象類以下:github

abstract class LotteryAbstract
{
    /**
     * 抽獎算法
     */
    public function run ()
    {
        $this->check();
        $this->getRule();
        $this->getNodeByRule();
        $this->checkTimes();
        $this->checkJoinLimit();
        $this->consumePoints();
        $this->getPrize();
        $this->draw();
        $this->packagePrizeInfo();
    }

    abstract protected function check();

    protected function getRule()
    {
        # code...
    }

    abstract protected function getNodeByRule();

    protected function checkTimes()
    {
        # code...
    }

    abstract protected function checkJoinLimit();

    abstract protected function consumePoints();

    protected function getPrize()
    {
        # code...
    }

    protected function draw()
    {
        # code...
    }

    protected function packagePrizeInfo()
    {
        # code...
    }
}

併發

建模完成後,還存一個併發的問題:併發下對獎品領取數量的變動問題。固然可能都會想到加鎖,讓併發的過程變成串行的過程,這樣就不會存在問題了。一是使用mysql的悲觀鎖(for update),可是考慮到這個去抽獎的過程有在相似秒殺的場景中使用,因此我就考慮用redis的悲觀鎖實現,畢竟內存的io性能比磁盤要高的多,因此開始的方案一以下:redis

本地ab -c 100 -n 1000 壓測正常。算法

而後上線就出問題了,順時redis大量的操做,遠遠的超過了之前的峯值。而後方案二出來了,搶不到鎖,睡5毫秒,下降搶鎖的頻率,方案以下:sql

僞代碼:

do {
    搶鎖...
    if (! 失敗) {
        usleep(5000);
    }
} while (! 失敗);

上面的方案有效的下降了峯值,可是又形成了499的請求,接着方案三出來了,具體方案以下:

  • 因爲redis是單線程的利用redis的decr自減,保證獎品庫存的準確性
  • 活動開始前注入獎品庫存到redis
  • 定時同步庫存到mysql(減小了直接從mysql中減小庫存的主庫壓力)

經過這個方案,redis,mysql主庫的壓力基本減輕。

問題

接着來講說這段時間工做中遇到的一些問題:

  • 我的問題:

    • 錯誤的使用redis的悲觀鎖,搶鎖失敗沒有進行睡眠,致使線上redis瞬時大量的操做(本地壓測未發現問題),後期會對這塊進行深度的研究
    • 沒有從頭至尾認真的進行code review(項目開發時間過於緊急)
  • 項目排期混亂:每一年按期搞的活動,卻只預留了5天開發時間
  • 接口文檔:老式的wiki文檔,沒有返回值的示例,沒有返回值的類型說明。增長了先後端開發成本,低效率。
  • 前端依賴:前端重度的依賴後端數據進行調試
  • 測試低效:純手工的測試,也缺少對一些基礎工具的使用
  • 低效的後臺項目項目代碼:基本不具有代碼複用能力的代碼,組織混亂
  • 各個環境的使用:目前咱們開發測試灰度環境,每次使用前都靠「吼」,常常會出現代碼被別人覆蓋的問題
  • svn問題

    • 同事本地代碼丟失
    • 代碼發佈的分支,發佈經過合併trunk,致使線上緊急修復分支被阻塞
    • 代碼發佈的分支,常常致使忘記合併回trunk
    • 每次發佈前須要到專門的線上代碼diff機器進行代碼diff

解決方案

提出了問題,固然得給出對應的解決方案:

  • 我的問題:

    • 繼續對基礎知識進行深度學習,目前對於nginx,linux,redis,mysql,mongo我都還須要大力的去學習。
    • 有質量的code review是必須的
  • 項目排期混亂:對產品和上級反饋但願咱們能從中挖掘出緣由,避免和減小一樣的事情的發生
  • 接口文檔:內部推進試行api blueprint,和對snowboard,swagger等這樣工具的使用,目前從我作起。
  • 前端依賴:推進前端同事自行打斷點調試和api mock
  • 測試低效:推進至少目前對簡單代理工具的使用
  • 低效的後臺項目項目代碼:有可能推進內部使用yii2開發後臺,我的覺着開發後臺gii仍是蠻有效率
  • 各個環境的使用: 有空寫一個簡單的頁面,使用對應環境的機器checkbox選中便可,一目瞭然,徹底避免之前的問題
  • svn問題

    • 推進內部轉向git
    • git stash 本地暫存未提交的代碼,從而避免丟代碼問題
    • 緊急的修補分支,採用git workflow的熱補丁分之隨時上線便可
    • git workflow的工做流能夠避免咱們目前的使用svn代碼常常忘記合併到trunk問題
    • git 本地diff分支代碼便可 提升了效率

經驗

  • 寫代碼前必定要儘量的弄清楚要作什麼
  • 寫代碼前進行必要的抽象過程,這樣一般能夠寫出易於閱讀和擴展的代碼(不過,脫離業務的代碼是耍流氓哦,哈哈~)
  • code review 必不可少,慢慢養成習慣吧,騷年們
  • 壓測,對咱們寫完的代碼進行壓測,簡單的可使用ab,siege
  • 項目完成後的總結和沉澱

圖片描述

相關文章
相關標籤/搜索