前言
這是一篇大長文,是對我上份工作的總結,對主要工作內容的總結、也是對 “2020技術驛站” 補交的作業(yè),望前東家前團隊能更好,也祝愿自己在新的環(huán)境中能繼續(xù)滿心歡喜的前行。
18年參加工作,到今年3月底離職,一共歷經(jīng)了1年8個月的時間,開始的半年做廣告相關的,之后到離職一直是在做營銷活動。第一段工作經(jīng)歷的飛速成長期就發(fā)生在這段做營銷的時間。運氣比較好,遇見了一個很棒的團隊,很棒的leader和師父,也有幸一開始就參與了千萬峰值的項目,雖然到我們的模塊沒有那么高,但也是整個項目的規(guī)模和量級也是業(yè)內很難經(jīng)歷的,也正是這些東西讓我發(fā)現(xiàn)了把一件事做好很難,想做好一件事對自己技術底蘊、技術深度&廣度的要求都非常的高。
然后請大家原諒我本文對營銷活動類系統(tǒng)的以偏概全,內容僅僅是我對營銷及營銷系統(tǒng)的認知及技術方面的相關積累。

何為營銷
入職的第一天,老板就跟我說,我們是C端的營銷,但是還挺懵懂的,對營銷基本是一無所知。在我印象中營銷就是銷售人員的各種推銷說辭和各種app 上的push,直到現(xiàn)在才有了一點基礎的認知,當然啦,一年多的經(jīng)歷,我的見解也大概率是片面的,大家就這么一看。
指企業(yè)發(fā)現(xiàn)或發(fā)掘準消費者需求,讓消費者了解該產品進而購買該產品的過程。
營銷學關于企業(yè)如何發(fā)現(xiàn)、創(chuàng)造和交付價值以滿足一定目標市場的需求,同時獲取利潤的學科。
上面是對營銷常見的兩個定義,個人而言比較認可的是第二種。以市場需求為目標,發(fā)現(xiàn)、創(chuàng)造和交付價值,從而獲取利潤。市場需求實際上是公司的業(yè)務范疇,發(fā)現(xiàn)、創(chuàng)造和交付價值通常是挖掘用戶需求并刺激使用的過程。而作為營銷人員的我們要做的就是根據(jù)公司業(yè)務范疇,通過某些形式進行獲客&經(jīng)營,刺激用戶使其發(fā)生交易行為,從而產生利潤。
這里的某些形式,也就是我們核心的關注點了。
無法吃透所有領域的營銷
營銷的業(yè)務場景十分的多,某兩大手機廠商門前兩個卡通人物的互動、電視上插播的洗發(fā)水廣告、春晚的搖一搖、朋友圈的明星推廣、購物支付時的返現(xiàn)和優(yōu)惠券,這些都是在不同領域下的不同形式的營銷手段。
而每個領域都有自己專屬的特點,我們無法照搬或者復制方案,需要具體業(yè)務具體分析。
至于本文要講的線上營銷,毫不夸張的說互聯(lián)網(wǎng)目前涉及到哪些領域,就有哪些領域的營銷,電商、醫(yī)療、教育、金融、游戲&文娛等等。每個領域都有自己特有的盈利方式,面向人群的行為屬性差異也非常大,這就導致營銷的方式都差異較大。
我們需要做的實際上是:掌握營銷的核心思想&本質,洞察業(yè)務特點以此構建營銷模型,對營銷模型進行多樣化的落地實現(xiàn),從而最大力度刺激用戶。
對我個人而言,接觸也相對較窄,除了自己親自實踐的信貸領域,其他的也都是來自間接經(jīng)驗,但不妨礙對營銷有一個較深刻的理解。
線上營銷的落地

上面提到了“通過某些形式進行獲客&經(jīng)營”、“構建營銷模型,對營銷模型進行多樣化的落地實現(xiàn)”,對于互聯(lián)網(wǎng)行業(yè)來說落地的方式有很多,常見的有廣告營銷、營銷活動等,通常來說這些都是柔和在一起進行的,比如投放一個營銷活動的廣告。
廣告方面就是在信息流中根據(jù)不同的人群屬性投放對應的物料,或者通過有影響力的事或人代言產品并在高曝光的場合出現(xiàn),對于信息流來說就是買流量,通過自己的DSP系統(tǒng)或者第三方流量買賣系統(tǒng)進行競價投放。
營銷活動通常是結合產品流程,以“小游戲”、“優(yōu)惠刺激”等方式,利用人們的貪心/好奇心等若干心理來構成的營銷方式。大家感觸最深的應該是“幫我砍一刀”吧。而這些營銷活動通常是基于背后的權益系統(tǒng)(優(yōu)惠券、紅包等)、觸達系統(tǒng)(push、短信等)、活動流程系統(tǒng)(規(guī)則引擎、基礎活動單元等)、支撐性組件、算法平臺等來共同構建出來的。
而面向活動的營銷系統(tǒng),就是本文要闡述的重點了
詳細說說活動
這里的活動具體指的就是上面所提到的營銷活動
活動實質上就是 在時間、場景、成本限制下,對不同用戶根據(jù)不同規(guī)則進行權益投放,以達到獲客、經(jīng)營、品牌傳播等目的最終構成交易的行為。
雖然最終目的都是賺錢,但中間階段的目標是不同的(對于打工仔來說就是KPI不同),也就促成了各種各樣的活動形式:
通常有離線觸達類刺激復購和獲客、常規(guī)流程內補貼提高交易欲望、周期性“游戲”類提升用戶活躍度的經(jīng)營or獲客目的、大型營銷活動的品牌宣傳獲客。
常規(guī)離線觸達活動

對于常規(guī)的離線觸達類活動,目的基本是刺激復購或獲客。
這種活動通常分為兩類:
1、干巴巴的文字游戲
這種活動形式較為簡單,一般通過“誘惑性“、”誤導性“,利用人們的”貪心“心理玩文字游戲,進行獲客和經(jīng)營行為,說實話就是一種合法的”短信詐騙“,通常是廣撒網(wǎng)的形式。
隨著各種營銷短信的爆炸式增長,這種方式基本沒有太大的效果,但礙于成本低基數(shù)大,也是一種常規(guī)存在的形式。
2、濕漉漉的文字游戲
這部分通常是真正的結合一些優(yōu)惠活動,直接給予用戶一些優(yōu)惠券,或者引導用戶參與活動。這種形式通常為流程內補貼或者游戲類活動的附屬子活動,作為宣傳的推銷的作用。
這種活動其中涉及的到的技術:(后面會詳細的進行介紹)
觸達平臺:通常包含文案模版的配置管理、內容發(fā)送控制等(主要為對接各大電信運營商平臺、內部push平臺、公眾號平臺等)
算法平臺:稍微高級一點的觸達平臺定義好模版后,有一些關鍵字和語句是可以通用戶喜好進行填充的,效果更佳。
短鏈服務:一個1k的鏈接毫無疑問是不適合進行投放的,大多是通過短鏈服務進行鏈接的精簡來進行投放。
常規(guī)產品流程內補貼

我們的產品,從用戶遇見,到開始使用,再到發(fā)生交易通常是擁有一個漫長的流程的。
拿購物來講:
看到某app的廣告,不小心點擊去,下載、注冊登陸、點擊產品、加入購物車、提交訂單、綁定銀行卡、喚起收銀臺、完成支付、點擊評價、后期查看。


每一個環(huán)節(jié)都可能面臨用戶的流失,尤其是前幾步代價較高的&用戶不熟悉的階段,毫無疑問在獲客之后,如何推進流程、如何促使復購,每一個環(huán)節(jié)都是不可忽略的,所以我們通常需要對每個節(jié)點進行刺激,刺激的大了成本不夠、刺激的小沒有用、不對口味沒有用。
這就要求我們需要建立在每一個操作環(huán)節(jié)建立營銷行為,并且根據(jù)環(huán)節(jié)特點和不同人群定制營銷手段和激勵措施。比如說“綁卡返現(xiàn)”、“購物給券”、“登陸有禮”、“再購更便宜”等很多惡心的措施。
對于我們系統(tǒng)而言,產品流程高度業(yè)務可配變的十分的重要,對應背后的流程編排引擎要求較高。
對營銷系統(tǒng)而言,需要能簡單配置可上線的簡單激勵活動,背后需要更多的活動單元、更靈活的規(guī)則引擎。
周期性“游戲”類活動
這類活動較前幾種而言,活動的存在感更強了,有用戶感知更深刻的活動形式,比如說邀請得獎勵、購物得抽獎機會、集卡贏大獎等等。
這類活動一般有一整套的活動規(guī)則,也會有大部分用戶樂在其中“薅羊毛”(這類活動有羊毛,但是實際上不多),通常是小恩小惠用于提升產品活躍度的,像是分享、邀請等功能通常也會有一定的獲客效果。
這類活動的活動形式:
闖關小游戲、搖一搖、紅包雨抽獎、集卡、戰(zhàn)隊比拼等,對于我們的系統(tǒng)而言對應的通常是:任務系統(tǒng)、簽到系統(tǒng)、抽獎系統(tǒng)、邀請關系系統(tǒng)、用戶代幣系統(tǒng)、價值交換系統(tǒng)等基礎活動單元。偶爾會加上活動編排引擎涉及1-2個基礎單元等。
大型營銷活動
這種活動重在品牌推廣和新業(yè)務獲客,最常見的有央視春晚歷年的活動、某熱播節(jié)日活動、自造節(jié)大促活動等,這個大家應該都有所感知,也都沒少剁手也沒少轉發(fā)。當然啦,這類活動是最適合薅羊毛的,通常來說是真有東西的,有一點技術基礎的同學現(xiàn)在就可以動手啦,馬甲號用起來積少成多~
從技術角度來看,這類活動都較為復雜,針對預熱期、白熱期、冷卻器都有不同的活動形式存在,但這些形式是作為一個活動整體存在的。通常能把我們現(xiàn)有組件的能力都給用上,所以就不一一列舉可能用到的系統(tǒng)啦。
在技術特點上具有:活動邏輯復雜、性能要求較高、資損風險較大、數(shù)據(jù)分析困難等特點。
這類活動是對于營銷系統(tǒng)最大的挑戰(zhàn),很多的降級措施、應急預案、性能調優(yōu)其實都是對這類活動特殊準備的。
營銷系統(tǒng)設計的問題域

需求特點
上面著重說了常見的營銷活動類型,粗略介紹了面向的場景。如果大家經(jīng)常使用一個產品或者關注一個產品,就會發(fā)現(xiàn)各類營銷活動層出不窮,每個細節(jié)都是營銷的樣子,幾乎每天看都會有新的不同。
這種直觀感受側面反映出營銷活動相關需求的特點:
1、需求量無比巨大,且多變
2、時間緊急,倒排需求通常占80%以上
面對這樣的情況,增加人力&臨時借調,是一種解決方案,但也只能算是下策,不可能為營銷投入50%以上的人力(但是我感覺pdd可能是50%以上,也可能是營銷系統(tǒng)已經(jīng)非常成熟),臨時借調研發(fā)效率、質量等都有不小的風險。
作為一個技術人員,我們要做的就是“以技術手段解決工程效率問題”,這就要求我們能需要做一種功能豐富且更加靈活易用營銷系統(tǒng),來達到小需求簡單配置可上線,大活動釋放降低80%以上的人力成本的目的。
技術特點
1、性能要求較高(成敗往往就在峰值)
營銷活動的訪問量通常較大,而且峰值突出。拿春晚活動、娛樂節(jié)目來說,通常是伴隨口播等引導動作帶領用戶參與的,80%的用戶都會集中在那一分鐘左右涌入,而只是依賴于加機器是不現(xiàn)實的,降級限流等只會白白浪費流量并且造成負面營銷,營銷大促場景下,我們需要的就是抗。
安全&風險
上面頻頻提到了薅羊毛,這其實就是營銷系統(tǒng)面臨的非常重要的一個問題:資損
資損的來源通常有活動設計、代碼實現(xiàn)、羊毛黨的大量存在等多方面的原因,尤其是在這樣的需求特點及大環(huán)境因素下,營銷系統(tǒng)是一個極易發(fā)生資損的點。
下面就來看一下是如何用技術的手段解決上述問題,及其中核心系統(tǒng)&組件的實現(xiàn)原理。
我認為營銷系統(tǒng)的樣子
先看一下上面提到的兩個概念或者說實體:
營銷:營銷學關于企業(yè)如何發(fā)現(xiàn)、創(chuàng)造和交付價值以滿足一定目標市場的需求,同時獲取利潤的學科。
活動:在時間、場景、成本限制下,對不同用戶根據(jù)不同規(guī)則進行權益投放,以達到獲客、經(jīng)營、品牌傳播等目的最終構成交易的行為。
然后結合業(yè)務領域,根據(jù)營銷場景下的問題域(現(xiàn)在&將來),通過技術手段解決效率&質量問題,以此最終確定系統(tǒng)的職責、定位及內部架構。
這一板塊的內容十分龐大。
聊一下方案選擇

對于方案的選擇通常有這么幾個標準:人力成本、研發(fā)質量成本、研發(fā)效率成本、機器成本
一個系統(tǒng)方案的實現(xiàn),最優(yōu)的是人力、機器投入較少的情況下,能夠確保質量的最快支持需求。
對于營銷類系統(tǒng)來說就是:
建設非常通用且高性能的基礎設施組件,實現(xiàn)相對通用的系統(tǒng)支持常規(guī)迭代,緊急且重大的需求依賴基礎設施進行特化實現(xiàn)。
像支付域、廣告域的這些系統(tǒng),面對的需求雖然也不少但是主要邏輯是確定的,一般都是對鏈路、功能進行豐富和優(yōu)化。而營銷場景下的需求講真的,千奇百怪,除了上述所說的一些固定類型的活動形式及基礎的活動單元,很多時候伴隨運營的臨時且全新的想法,我們需要面臨決策,全新臨時方案 or 通用解決方案的抉擇。
通用的系統(tǒng)這個大家都能接受,但對于當前所要實現(xiàn)的邏輯顯然不能復用的情況,我的建議是采用全新的臨時方案僅依賴基礎組件,新的數(shù)據(jù)存儲、新的server,一切只為解決當前臨時需求。因為就風險和成本來說,實現(xiàn)一個臨時方案相對于改一個相對復雜的系統(tǒng)要簡單的多,并且風險完全收斂,我們也能集中精力解決當前問題,而不是竭盡所能把系統(tǒng)建設的啥都可配制化,看似系統(tǒng)牛逼,但為了暫時的需求,投入產出比太低。
營銷場景下永遠都有臨時且重大的需求,我們要做的是成本投入較小的情況下快速迭代。
營銷系統(tǒng)架構
系統(tǒng)功能:
1、對業(yè)務系統(tǒng)或直接對用戶提供營銷能力輸出
2、向業(yè)務方提供營銷業(yè)務的配置化能力
3、提供效果洞察、成本、資損監(jiān)控能力

理想目標:
常規(guī)活動配置化可上線,大型活動節(jié)省80%以上成本。
活動效果可洞察,資損&故障易發(fā)現(xiàn)
系統(tǒng)架構:

活動流程層主要為構建實現(xiàn)營銷活動流程實現(xiàn)能力的標準化輸出,以活動單元能力或活動組件基礎能力為原子單位,進行活動流程編排。
活動單元層:提供常用營銷活動的能力(比如抽獎、秒殺、邀請),每個單元面向一類營銷形式,對外暴露標準接口。內部依賴于活動組件。
活動組件:提供標準的單一的營銷功能(簽到、分享)或業(yè)務能力(價值交換、標簽輸出)或系統(tǒng)能力(調度組件、權益高效發(fā)放)等。
權益系統(tǒng):主要包含業(yè)務場景下內部權益、現(xiàn)金權益、外部權益,提供權益的實際發(fā)放、核銷能力。
數(shù)據(jù)處理:主要針對數(shù)據(jù)庫(mysql、redis)、日志數(shù)據(jù)等數(shù)據(jù)進行標準化處理,為監(jiān)控、數(shù)據(jù)一致性提供數(shù)據(jù)源
監(jiān)控方面:對業(yè)務方提供可視化、標準化的效果展示,及資損監(jiān)控&系統(tǒng)監(jiān)控等能力。
對賬補單:提供數(shù)據(jù)一致性的最后一層保障。
常用組件
要開始深入技術細節(jié)啦,這塊內容背后的知識體系基本就是我這1年8個月的基礎技術能力的成長了,選幾個具有代表性的組件來說。
唯一單號服務
這個組件非常非常重要,所以會說的相對仔細。
在繁多的業(yè)務場景下n多系統(tǒng)的交互場景下,我們通常需要一個唯一id來作冪等處理,同時海量的數(shù)據(jù)(DB、log等)我們也需要一個唯一單號作為線索完成高效的數(shù)據(jù)分析工作,所以說每個請求、每個環(huán)節(jié)我們都是需要唯一id的。
看到上述背景,不難想到唯一單號服務所面臨的核心問題
功能性要求
1、唯一性保障
2、業(yè)務相關性且安全
系統(tǒng)要求
3、高性能
4、高可用
這四點即是難點,也是唯一單號的特征。
唯一單號服務的實現(xiàn)方案有很多種,最常見的有自增鍵、UUID、雪花算法、Leaf-segment三種方案。
自增鍵
利用自增鍵是最容易想到的一種方式,實現(xiàn)方式通常有mysql主鍵自增、redis incr。但這兩種方式很顯然都會存在單點的問題,而且自增主鍵性能受限、redis 易發(fā)生數(shù)據(jù)丟失(性能只是相對mysql更好),但實現(xiàn)大學作業(yè)的時候還是可以用的,真實生產環(huán)境是肯定不會選用的。
uuid
UUID(Universally Unique Identifier)的標準型式包含32個16進制數(shù)字,以連字號分為五段,形式為xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx的36個字符,具體標準可見:http://www.ietf.org/rfc/rfc4122.txt(墻),目前業(yè)界有5種常用的生成uuid的方式。
這種方式優(yōu)點是性能高,純本地生成,無網(wǎng)絡IO發(fā)生。
缺點也很突出:
存儲方面:
1、不易存儲(實在太長了)
2、這類id由于通常要落庫并掛索引,對于mysql來講,UUID的無序性會使數(shù)據(jù)位置頻繁變動,嚴重影響性能,尤其是作為主鍵的情況下(主鍵越短越好)
業(yè)務相關&安全性:
1、最常用的基于mac地址生成的方式會暴露mac。
2、無任何業(yè)務標示,排查問題和統(tǒng)計時十分不便。
雪花算法
雪花算法最初是推特用于標記消息的,跟uuid結構類似,通過劃分命名空間實現(xiàn),基于時間戳+機器碼+業(yè)務標示實現(xiàn),這種方式極易橫向擴展,由于是本地生成,且每臺機器碼值都不同,可用性和性能都得到了保證。并且靈活的結構添加業(yè)務標示也變的輕松了許多。

實現(xiàn)方式與uuid相似,雖然相對提及會小一些,存儲方面的問題稍微得到緩解。
但是有一個很嚴重的風險點,一旦發(fā)生時鐘回撥,那就很慘了
mongoDB使用的就是這樣方式。
Leaf-segment
這種方式是我除雪花算法外最看好的實現(xiàn)方式
依賴于mysql存儲保證數(shù)據(jù)不丟,并且利用行鎖保證單調遞增,一張表維護多個活動的id,通過異步化的方式批量批發(fā)單號對外提供服務保證性能,多個單號批發(fā)商對外提供服務并容忍單號浪費以此保證可用性。
這段話可能說不明白直接看代碼和圖:
`activity_id` bigint(20) NOT NULL AUTO_INCREMENT,
`activity_name` varchar(512) NOT NULL,
`sn` bigint(20) NOT NULL DEFAULT '0',
`step` bigint(20) NOT NULL DEFAULT '10000',
/**
* 每次批發(fā)時:
* 1、返回sn ~ maxSn(sn + step) (就是所批發(fā)走的號段)
* 2、sn = sn + step + 1
* 提供服務時:
* 內存中輸出小于maxSn的sn,并對sn++即可
**/

mysql可以只用一個,通常來說內存中大量的號段足以支撐mysql恢復,如果想用多個mysql同樣需要進行劃分命名空間。
規(guī)則引擎
在任何配置化的場景下規(guī)則引擎都是核心的存在,通常來說表達式在可視化的配置化后臺,結合配置模版+配置元數(shù)據(jù)編譯成“規(guī)則”,規(guī)則在執(zhí)行時被翻譯成表達式或者執(zhí)行從而達到業(yè)務邏輯可編排,決策邏輯可配制的作用。

規(guī)則引擎一抓一大把,大家自行百度即可。
基礎的規(guī)則引擎中表達式的生成與解釋也是營銷場景非常常用的,比如波蘭表達式等,涉及標簽的地方就大概率涉及波蘭表達式。
價值交換組件
這里的價值交換組件指的是營銷場景下,營銷活動系統(tǒng)中各種“積分”、“代幣”、“機會”等價值載體的交換體系,不同的活動單元中所使用的價值載體是不同的,要串聯(lián)幾個組件完成整個“大活動”的正常運作,出上層的規(guī)則引擎外,更重要的就是不同價值載體之間的轉換,比如說“抽獎機會”<——>"任務積分"<——>"權益"。
價值交換組件的場景更像是一種存在匯率的轉賬系統(tǒng)(但是相對轉賬來說,要簡單的多,通常是單向并不可退的,僅僅作為系統(tǒng)邏輯轉換的內部存在).
可以簡單看一下價值交換組件的內部實現(xiàn):

cache、異步記賬等存粹為了性能,其中最核心的點就是數(shù)據(jù)一致性問題,這個點會在后面單獨介紹。
用戶標簽體系
用戶標簽通常分為用戶實時業(yè)務狀態(tài)標簽、用戶畫像特征標簽兩種。實時業(yè)務特征通常來源于實時業(yè)務系統(tǒng)的接口或者DB,而用戶畫像特征標簽這個通常是算法平臺提供,用戶標簽所要承受的qps數(shù)是除唯一單號服務外最大的,所以通常性能要求高(承載qps量大、耗時短),但可用性要求倒是沒那么高(一個合理的系統(tǒng),在不知道用戶信息的情況下也應該能正常作出兜底反應的)
通常來說,我認為一個合理的在線標簽系統(tǒng)是這樣的:

單獨具有流量控制功能,保證大部分流量可用,降級掉一部分請求,對常用的標簽進行緩存控制<標簽key,用戶id,統(tǒng)一有效時間>,根據(jù)需要獲取可接受時間范圍內用戶標簽。
未命中緩存的標簽,交由標簽任務管理器進行處理,決定單個標簽處理最大時長、標簽并行&串行處理機制及處理順序、處理任務調度等,底層維護并發(fā)模型(線程數(shù)、任務存放機制等)
再下一層,維護對外部接口、業(yè)務數(shù)據(jù)或接口、用戶畫像數(shù)據(jù)的處理單元。
短鏈服務
上文在觸達單元中就提到了短鏈服務,首先業(yè)務常見、技術特點鮮明,所以單獨拿出來說了。
短鏈服務的應用場景非常多,除了短信中的鏈接,平日里的鏈接分享、網(wǎng)頁分享、二維碼分享其實斗志這種方式,只是表現(xiàn)形式不同而已。
短鏈接實現(xiàn)思路很簡單:

找一個短鏈接試試看:3.cn/j/10nd-pE9


至于這里為什么用的是302而不是301,是因為臨時重定向時能夠統(tǒng)計到短鏈接點擊次數(shù),通常也會被用作效果分析。用301永久重定向也是可以的。
至于后面“10nd-pE9”這個碼,通常被稱為短碼,生成方式有很多種,比如上面提到的唯一單號,還有就是摘要法(存在小概率重復的可能性,但能夠保證鏈接夠短)。
如果是小型企業(yè)大可以利用現(xiàn)成的短鏈服務提供商,比如百度的:https://dwz.cn/
基礎活動單元
抽獎活動單元將常見的活動形式進行了一定程度上的抽象,做了通用化處理,以應對其高頻的需求。這里拿幾個經(jīng)典的活動形式進行闡述。
活動單元的建立,可以根據(jù)具體的業(yè)務場景下的各種運營的需求來進行拆分抽象。
談談抽獎
拿抽獎來說,搖一搖是抽獎,九宮格轉盤是抽獎,n選一砸金蛋也是抽獎,大家點的火熱朝天的紅包雨其實也是抽獎,這類抽獎活動對用戶活躍度非常有幫助,用戶分享意愿也很強,基本是大型活動每次必選的組成部分。所以就完全可以對其進行抽獎建立一個通用的抽獎模型。
整體看下來一個抽獎系統(tǒng)是這樣的:

其中的核心組件有:
概率模型
用戶控制用戶中獎的概率,實現(xiàn)方式很簡單:
1、確定可抽獎總區(qū)間,確定獎品區(qū)間段,一次性進行抽獎。

2、Alias Method
可以直接看這一篇實現(xiàn):http://www.keithschwarz.com/darts-dice-coins/
大致意思:把N種可能性拼裝成一個方形(整體),分成N列,每列高度為1且最多兩種可能性,可能性抽象為某種顏色,即每列最多有兩種顏色,且第n列中必有第n種可能性,這里將第n種可能性稱為原色。 想象拋出一個硬幣,會落在其中一列,并且是落在列上的一種顏色。這樣就得到兩個數(shù)組:一個記錄落在原色的概率是多少,記為Prob數(shù)組,另一個記錄列上非原色的顏色名稱,記為Alias數(shù)組,若該列只有原色則記為null。

抽獎機會控制
提供用戶可抽獎機會的控制,是一種價值的載體。抽獎系統(tǒng)對外暴露次數(shù)增加接口,內部抽獎行為消耗抽獎次數(shù)。
獎池控制
每個用戶群面向自己的獎品集合,高價值的用戶或者潛在用戶面向價值較高的獎品,風險用戶或者下沉用戶面向低價值的優(yōu)惠券等,就是這么黑。
這里通常依賴于表達式引擎,根據(jù)標簽計算用戶所選獎池。
中獎限制
曾經(jīng)的我也以為獎可以隨便中,除了面向獎品集合之外,通常對用戶會額外加n多限制條件。
比如說,a獎品最多中2次,b獎品最價值不能超過2元,命中iphone大獎后其他不能再中大獎并且其他獎品概率下降。
庫存控制
所有的獎品都是有成本預算的,不能無限制發(fā)放,這就要求對獎品進行庫存控制。
這里有一個設計的思路,分別設置庫存供應量、庫存消耗量,剩余庫存=庫存供應量-庫存消耗量,這樣相對于拿一個數(shù)值表示庫存進行增減操作要靈活的多并且安全很多,尤其適合在庫存增加時。
另外,庫存的消耗操作很顯然是一個存在競態(tài)條件的復合操作,我們需要保證其原子性,可以利用上鎖等措施串行化,也可以利用現(xiàn)有工具的原子能力(比如reids 操作的原子性:incrby、lua等)
裂變營銷
這里還有一個值得提的是列表營銷,當下正火的營銷方式,最經(jīng)典的莫過于“幫我砍一刀”。
分開來看,營銷上面說過了,裂變概括來說,按生物學來說是細胞核的裂變,由一個裂變成2個,2個裂變成4個。換作人來說,就是當前用戶是否能夠幫忙獲客。
對于裂變營銷而言,通常包含三個環(huán)節(jié),設計裂變流程,裂變流程變?yōu)檗D化漏斗,漏斗分析。

裂變營銷重在邏輯,性能等要求一般不會很高。
對于系統(tǒng)設計而言有幾塊格外的重要:
1、底層邀請關系組件
裂變營銷往往是從人出發(fā),以人結束,造出一顆顆龐大的樹,所以我們需要維護整個樹的結構,不僅僅是為了存儲活動的邀請記錄供激勵使用,對后期判斷“散點”也是尤為重要的。
2、標簽組件
活動的重心在于拉人,什么樣的人能拉人,是相當重要的,要建立與裂變營銷關聯(lián)性很強的人群畫像標簽十分重要。
這塊了解的不多,暫時就這些~
數(shù)據(jù)的一致性處理

數(shù)據(jù)一致性一直是各種系統(tǒng)很頭痛的點,尤其是分布式系統(tǒng)越來越昌盛的今天。數(shù)據(jù)一致性也存在cap、base這樣的著名的理論,實際上平衡一致性與性能、可用性等特性是需要根據(jù)產品特點來做的,像跟錢相關的一致性要求會非常的高(不能出錯),積分數(shù)量這種虛擬權益類其次,簽到記錄等用戶就不太關心了。
營銷場景下的一致性要求
營銷場景的數(shù)據(jù)一致性要求其實是相對寬松的,只需要在較短的時間內保證最終一致就可以了。
拿抽獎活動舉例,一次抽獎行為的發(fā)生,涉及到次數(shù)減扣、庫存減扣、抽獎記錄更新、獎品發(fā)放等一系列的操作,很有可能會發(fā)生部分失敗,按照常理來說像獎品發(fā)放失敗這樣的情況,短時間未到賬用戶大概率也是無感知的,通常需要繞道卡券包等,所以這種情況我們只需要一定時間段內給用戶補上即可,需要保證的主要是和成本密切相關的次數(shù)、庫存等。
當然啦,一致性要求雖然沒有那么高,但是還是要盡可能照顧到的,沒有數(shù)據(jù)一致性的保證,性能、可能性再高的系統(tǒng)可能都是一堆垃圾。
常見的一致性實現(xiàn)方案
先來看一下什么是數(shù)據(jù)一致性:數(shù)據(jù)的變更,與現(xiàn)實世界變更的預期結果保持一致。
后來在這個基礎上延伸出:
各種一致性
強一致性(也稱線性一致性): 任何一次讀都能讀到某個數(shù)據(jù)的最近一次寫的數(shù)據(jù),系統(tǒng)中的所有進程,看到的操作順序,都和全局時鐘下的順序一致。
弱一致性:用戶讀到某一操作對系統(tǒng)特定數(shù)據(jù)的更新需要一段時間,我們稱這段時間為“不一致性窗口”。
最終一致性:是弱一致性的一種特例,保證用戶最終能夠讀取到某操作對系統(tǒng)特定數(shù)據(jù)的更新。
順序一致性:任何一次讀都能讀到某個數(shù)據(jù)的最近一次寫的數(shù)據(jù),系統(tǒng)的所有進程的順序一致,而且是合理的。(相對于強一致性少了系統(tǒng)時鐘)
事務的誕生,很大程度上就是為了保證數(shù)據(jù)的一致性。
基礎理論
cap:指的是在一個分布式系統(tǒng)中,一致性(Consistency)、可用性(Availability)、分區(qū)容錯性(Partition tolerance)只能滿足兩其中兩點。
base:BASE 是 Basically Available(基本可用)、Soft state(軟狀態(tài))和 Eventually consistent (最終一致性)三個短語的縮 寫。BASE理論是對CAP中AP的一個擴展,保證可用性和分區(qū)容錯的情況下,允許存在軟狀態(tài),但會保證最終一致性。
兩階段提交
這部分理論知識蠻陳舊的就不展開說了,大體能懂就好:二階段提交的算法思路可以概括為: 參與者將操作成敗通知協(xié)調者,再由協(xié)調者根據(jù)所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。

兩階段的缺點:
1、同步阻塞問題。
2、協(xié)調者的單點故障問題。
3、第二階段執(zhí)行中,協(xié)調者與參與者共同掛掉的場景導致不一致。
三階段提交
三階段提交協(xié)議在協(xié)調者和參與者中都引入超時機制,并且把兩階段提交協(xié)議的第一個階段分成了兩步,引入了中間狀態(tài): 詢問,然后再鎖資源,最后真正提交。
解決了“同步阻塞”、”單點故障“等小缺點,及二階段無法解決的“協(xié)調者與參與者共同掛掉的場景導致不一致”這個很重要的問題。

說實話不管是兩階段或者三階段,都無法徹底解決一致性問題。
剛性&柔性事務
剛性事務:具備我們熟知的的ACID的特性的事務,通常的實現(xiàn)有WAL(white ahead log)(這是目前市面上幾乎所有數(shù)據(jù)庫的實現(xiàn)方式),還有一種是影子分頁。
柔性事務:基于base理論實現(xiàn)的追求最終一致性的事務模型,通常實現(xiàn)方式包括TCC補償性事務、異步確保型、最大努力型。
tcc分布式事務是一種二階段的變種,將操作分為嘗試、提交、撤銷,是一種實時鏈路上的的操作行為。大致模型是這樣的:事務發(fā)起者發(fā)起一個事務,參與者進行事務的提交操作,如果其中幾個參與者本地事務失敗,會告知失敗,其他已提交的參與者事務進行補償操作進行回滾。
異步確保型事務則為將同步阻塞的事務操作變?yōu)橐徊讲僮?,避免對?shù)據(jù)庫事務的爭用,對操作進行匯總處理,通常對于熱點資源的操作采用這種方式。
最大努力型事務,依賴于補單消息的補償機制或者重試機制,對事務進行最大程度的推進,以此保證最終一致性。
營銷場景下的數(shù)據(jù)一致性設計

首先看完上述的上面各種的一致性定義和分布式事務實現(xiàn)可以發(fā)現(xiàn),數(shù)據(jù)的一致性實現(xiàn)是比較困難的,也是業(yè)界一直在尋求最優(yōu)解的問題之一,但就目前而言,實時鏈路上的各種分布式事務能保證一定程度上的數(shù)據(jù)一致性,而且通常來說只是局部某個環(huán)節(jié)(不可能把所有事情都扔進事務里面),并且還會有一定的瑕疵。
對于整個業(yè)務邏輯推進中數(shù)據(jù)一致性的根本保證,還是最原始的:“建立業(yè)務流轉的狀態(tài)機,依賴于唯一單號,異步對賬補單,達到最終一致性”
對于營銷場景下,性能要求較高,但一致性要求沒有那么強。
建議的實現(xiàn)方式有兩種:
1、核心操作采用TCC事務模型實現(xiàn),保證核心鏈路上核心數(shù)據(jù)的一致性,對于附屬操作可通過異步對賬補單保證最終的一致性。
2、實時鏈路不做分布式事務處理,完全依賴于對賬補單實現(xiàn)。
每種方式都有自己的優(yōu)缺點,第一種方式所需的對賬補單量級較小,常見的掉單失敗等在實時主鏈路上就給干掉了,對于數(shù)據(jù)不一致引發(fā)的問題的影響時長會減少,但是引入了TCC事務,增加了三方協(xié)調和更多的網(wǎng)絡IO,數(shù)據(jù)操作邏輯的復雜度會直線上升,性能會有一定程度的下降,可能會產生新的風險和問題。
而第二種方案,主鏈路代碼精煉,性能會很好,但對于數(shù)據(jù)不一致引發(fā)的問題的影響時長會較長,并且對賬補單系統(tǒng)規(guī)模會變的龐大,要處理的任務也會變多。
既然各有優(yōu)缺點,我們就需要根據(jù)不同營銷場景來選擇,像是營銷組件完全是組件內操作本地事務或者不需要事務即可解決,對于常規(guī)活動依賴于活動單元組件這些建議使用第一種方案,直接建立較為完備的措施,一勞永逸。而像是性能要求非常高并且邏輯相對簡單的臨時活動,第二種方案就足夠了,釋放出人力處理性能優(yōu)化。像是非常大型的營銷活動,就需要根據(jù)具體場景來衡量了,數(shù)據(jù)一致性可能產生的影響面,性能、可用性、一致性的折中。
這塊內容沒有標準答案,包括了解的各種業(yè)務場景都是這樣的,數(shù)據(jù)一致性通常需要根據(jù)場景定制化開發(fā),并不存在通用解決方案。而我們需要做的就是積累一致性處理經(jīng)驗和數(shù)據(jù)一致性保證的可選方案(除了TCC這些,業(yè)界存在很多解決方案,除了TCC這種還有事務型消息、本地消息表等,但是都逃不出上面的那一堆概念的基礎理論知識)。
我所接觸過的性能優(yōu)化

營銷場景下性能要求通常是比較高的,尤其是非常規(guī)的面對大促的營銷活動,而且性能優(yōu)化中涉及到的技術相關的點會非常之多,常用的軟件研發(fā)工具的選擇、高性能系統(tǒng)的架構模型、語言的選擇、底層的網(wǎng)絡原理、代碼編寫上比如并發(fā)模型的優(yōu)化、語言底層的優(yōu)化等等。
除了優(yōu)化的具體行為,我們還需要有科學的性能衡量標準,高效的檢驗能力。
總結來說性能優(yōu)化的策略:
在最開始的時候先說結論,給出一個性能優(yōu)化的順序:
1、確定合理的架構方案(最大程度上決定了我們的系統(tǒng)能扛多少)
2、確定技術選型(包括語言、工具,對內存、epoll的高效利用幾乎是現(xiàn)在性能工具選型的根本)
3、代碼邏輯優(yōu)化(很多時候性能優(yōu)化就是砍IO,少計算,上緩存,異步化,減少鎖,還有工具的正確使用)
4、最后才是工具、語言的底層調優(yōu)(這一步很多時候根本不需要,但是咱們得知道)
***技術不是炫技,最小成本把事做成就好。
什么是高性能
衡量性的性能的標準通常有很多,比如常見的平均響應時間、最大并發(fā)數(shù)等,其實最核心的就是吞吐量,單位時間、單位資源內,系統(tǒng)所能支持的請求處理數(shù)或者事務處理數(shù)。優(yōu)化最大并發(fā)數(shù)、縮短響應時間最終目標都是吞吐量。
高性能mysql一書在第二、三章一開始的位置,就先說的衡量標準和測試方式,這點是非常贊同的,因為只有明確了什么是高性能,才能進行合理的設計系統(tǒng),進行調優(yōu)及壓測,非常推薦大家去看一下那兩章。
如何合理的壓測

除了評定標準,在優(yōu)化的具體行為之前最應該熟悉的就是如何壓測,如何高效的壓測。
對于壓測而言重要的有這么幾點:
0、重中之重,要有合理的壓測方案和周知下游、qa、op,也就是下面的說的幾點,切勿啥都不準備上來就開干。
1、數(shù)據(jù)完全隔離
如果想切實的了解系統(tǒng)在生產環(huán)境的抗壓能力,那就必須要在生產環(huán)境進行壓測,那么數(shù)據(jù)隔離是首要的,不能影響線上不能因為壓測產生故障或者資損,隔離的方式很簡單:用戶id區(qū)分
我們可以使用現(xiàn)在沒有在用及近一段時間也不會有的用戶賬號進行壓測,但是這樣需要面臨清理數(shù)據(jù)(一個相當頭疼的活)
打壓測表示,這需要我們在系統(tǒng)設計時就需要做到的,每個系統(tǒng)都要天然的支持壓測。
第二種方式無疑是日常壓測中最合適的,也是系統(tǒng)設計時需要考慮的。
2、靈活高效的壓測系統(tǒng),需要qps實時可控,要有紅線觸發(fā)機制,比如說壓測前確定最大值,壓測qps調節(jié)過程無論如何都不能超過預值。
對于流量控制,也就是qps的調控所依賴的算法其實很簡單,就是限流算法的變相使用,常見的有計數(shù)器控制法、令牌通控制法、漏桶控制,并且基于滑動窗口對于流量進行整形,保證均勻,不出現(xiàn)過高的瞬發(fā)峰值,這部分內容之前單獨寫過,可以看一下Go系列文章中限流算法實戰(zhàn),還有高性能系統(tǒng)中的限流算法原理。
3、壓測的核心關注點
壓測過程中需要有合理的指標:
關于你的服務:cpu(cpu idel、user、cpu.load)、內存(這個通常來說問題不大),
剩下的你需要看下游系統(tǒng)的壓力,切勿把下游打掛。
除此之外,壓測的目的是發(fā)現(xiàn)問題解決問題,而不是僅僅是測試系統(tǒng)能扛多少,所以一定要關注核心依賴,比如說mysql、redis等,發(fā)現(xiàn)各種問題才能避免線上掛的慘,大多時候通常是代碼寫的有問題,但是在三方應用上暴露出來,比如說hgetall。但是有時候線上機器也是可能存在問題的,需要壓測來檢驗。
4、合理的壓測過程
壓測需要循序漸進的來,有問題能及時發(fā)現(xiàn),并且一上來流量太大高估了自己的系統(tǒng),可能瞬間打掛。
壓測的流量規(guī)律需要根據(jù)線上真實流量的變化趨勢,或者預測趨勢來進行壓測,不能想當然。建議對線上流量進行復制,以供壓測使用,這樣壓測的鏈路才是真實的。
5、完整的留存
壓測數(shù)據(jù)完整留存、壓測日志完整留存、火焰圖記錄完整留存。
這一塊兒是關鍵也是我們壓測的最終產出,發(fā)現(xiàn)那些地方存在問題或風險之后,我們需要根據(jù)這些來具體分析,以此得出優(yōu)化結論。
高性能技術選型
曾經(jīng)見到這樣一句話,大概是這樣的 don’t do it,cache it,asyn it,pipline it 其實性能優(yōu)化就是這樣,尤其是對于高性能業(yè)務系統(tǒng)的設計而言。
1、don’t do it
對于主鏈路而言,是否存在可以不做的的事情,如果有那就砍掉,額外的IO和計算只會導致性能的下降。
2、cache it & asyn it
如果沒辦法不做是否可以直接緩存結果及異步處理。但是如果使用緩存和異步處理,會面臨非常的大的數(shù)據(jù)一致性問題,尤其是緩存。
3、pipline it
對于無法異步化和緩存的IO處理,直接建議同一個鏈接pipline批量處理。
通常來說,高性能系統(tǒng)其實就是基于上面的幾個思想,結合業(yè)務場景給出解決方案。
高性能工具的最大化利用
我們開發(fā)時毫無疑問會依賴于一些開源的或者內部的工具,比如說redis、nginx、mysql、kafka等,想要提升性能要做的第一件事兒就是結合業(yè)務場景對這些工具充分利用,下面詳細來看。(由于篇幅過長,就僅僅只說一下nginx和redis吧)
nginx + lua

對于營銷系統(tǒng)而言,很多時候性能要求苛刻,但是邏輯相對簡單,這種時候就完全可以采用nginx+lua構建openresty應用,迅速解決問題。還有邏輯不太變的接入層,也非常適合這種方式。
nginx作為一個高性能的http和反向代理web服務器,不同于 Apache的一點就是,Nginx 采用單線程,非阻塞,異步IO的工作模型。整體來說就是對epoll的充分使用,用它來作為第一層承接流量再合適不過。
這里要說的不僅是nginnx,更多的是配合lua的使用模式,Lua是一種輕量級、可嵌入式的腳本語言,在nginx之上運行l(wèi)ua腳本來實現(xiàn)相對簡單的接入層邏輯或者部分業(yè)務邏輯。
市面上常見的方式是由章亦春將Lua和Nginx粘合的ngx_lua模塊,并且將Nginx核心、LuaJIT、ngx_lua模塊、許多有用的Lua庫和常用的第三方Nginx模塊組合在一起成為OpenResty,使用Lua編寫腳本,然后部署到Nginx Web容器中運行。從而非常輕松就能開發(fā)出高性能的Web服務。
其中ngx_lua是Nginx的一個模塊,將Lua嵌入到Nginx中,從而可以使用Lua來編寫腳本,部署到Nginx中運行,即Nginx變成了一個Web容器。
常見的lua工具庫有:
lua-resty-memcached
lua-resty-mysql
lua-resty-redis
lua-resty-dns
lua-resty-limit-traffic
lua-resty-template
對于nginx+lua的使用方式通常有:
1、最基礎的負載均衡
2、單機閉環(huán),利用lua實現(xiàn)nginx-lua-static-merger,直接在接入層進行本機靜態(tài)資源讀取完成靜態(tài)資源合并。
3、分布式閉環(huán),解決單機閉環(huán)中數(shù)據(jù)不一致問題(分布式集中存儲)、存儲瓶頸(實現(xiàn)分片邏輯)
4、作為接入網(wǎng)關使用,比如說過濾請求、防DDOS、Nginx Proxy Cache完成緩存、更細粒度的限流、降級預案等
在極端的性能場景下,nginx+lua完全可以實現(xiàn)業(yè)務邏輯,插入數(shù)據(jù)到redis中后續(xù)異步處理,性能非常之高。
至于其中的原理可以看下,我之前的一篇文章《高并發(fā) Nginx + lua是如何抗住的》,其核心就是基于epoll實現(xiàn)的單線程處理多鏈接的思路。
Redis + lua

上面說的是nginx+lua,這一篇幅要講的是redis+lua,但是需要注意的一點是,redis+lua的性能其實一般,這一點要格外謹慎,我們要利用的是其操作的原子性。
我們想要進行高性能的數(shù)據(jù)操作時,很多時候都會直接選用redis做存儲或者緩存,但是依賴于redis數(shù)據(jù)做操作但涉及具有競態(tài)條件的復合操作時就會比較麻煩,大概率會涉及分布式鎖,其實redis+lua就是一種變通的方式,將密集的redis操作更裝成lua腳本,這樣既解決了原子性問題,又把這部分邏輯變的可插拔(據(jù)說游戲領域經(jīng)常會存在這樣的動態(tài)邏輯替換)
但是切記不要在lua中實現(xiàn)過多復雜的邏輯,尤其像是各種json處理,會導致redis性能急劇下降。
redis+pipline
對于redis性能的充分利用,正確的打開方式是redis+pipline,一次性灌命令的方式實現(xiàn)redis若干邏輯處理,其他工具也是一樣的。
在redis+pipline編碼時需要注意,pipeline 期間將“獨占”鏈接,此期間將不能進行非“管道”類型的其他操作,直到 pipeline 關閉;如果你的 pipeline的指令集通常比較龐大,為了不干擾鏈接中的其他操作,可以為pipeline操作新建鏈接,讓pipeline和其他正常操作分離。還要注意pipeline所能容忍的操作個數(shù),也就是socket-output緩沖區(qū)大小/返回結果的數(shù)據(jù)尺寸,這些是受限于server的物理內存或網(wǎng)絡接口的緩沖能力。
這一塊說白了就是網(wǎng)絡連接的高效利用,最基礎的手段是使用短鏈接一個請求一個鏈接干到底或者使用連接池+長鏈接,而pipline就是在這基礎上,進一步利用緩沖隊列,對命令進行一次性傳輸和結果的一次性返回,kafka所謂的微批處理其實也是這種實現(xiàn)思路。如果仔細看各種工具對于網(wǎng)絡IO的優(yōu)化思路,幾乎都是這樣的策略--減少網(wǎng)絡IO的產生并且充分利用epoll,我們業(yè)務系統(tǒng)優(yōu)化的思路毫無疑問也是這樣的。
redis的集群方案
對于redis的充分利用還有很重要的一點是其集群方案,一般就是兩種類codis方案、官方的cluster方案,這些都是從單點到主從再到主動+哨兵一步步演變過來的,主要一步步解決了單點、可用性、吞吐量等問題。
簡單的看一下這兩種方案的實現(xiàn)思路:
codis方案(中心化配置存儲)

cluster方案(去中心化實現(xiàn)思路)

推薦這一篇博客,說的很清楚:https://www.cnblogs.com/pingyeaa/p/11294773.html
看完集群方案,其中一點非常明顯,為了提高系統(tǒng)的吞吐量,數(shù)據(jù)是分片存儲的,而分片的核心就是hashtag,hashtag是一種在我們知道redis主庫選擇的hash規(guī)則后,支持自主定義所存儲的redis服務器的能力。
關于hashtag的使用有兩點需要注意:
1、保證分片的均勻
通常來說后我們需要讓每個服務器承擔基本一致的流量壓力,最合適的方式就是根據(jù)用戶id求余(針對分片數(shù)量求余)來做分片處理,這個結論是在假設用戶id是均勻的基礎上成立的,如果用戶id不均勻那就在id的基礎上加一層hash值再做處理。
2、使用lua時的坑
在使用lua時,是根據(jù)第一個key值進行分片選擇的,所以說獨立原子操作時根據(jù)key值hash后選擇的分片,使用lua操作會找不到,或者lua操作的key值獨立的原子操作會找不到,這一點一定要注意。
分布式鎖實現(xiàn)

這一塊按理說是一致性部分的內容,但是因為密切和性能相關,所以就在這里說吧。
分布式鎖是用來保證分布式環(huán)境下數(shù)據(jù)操作的原子性的,和我們常用的鎖的使用方式一樣,加鎖+邏輯處理+解鎖(主動解鎖、超時釋放)
常見的分布式鎖的實現(xiàn)方式有三種:
1、基于redis的紅鎖實現(xiàn)及其簡化版本
2、利用 Zookeeper 的順序臨時節(jié)點,來實現(xiàn)分布式鎖和等待隊列。
3、Google 公司實現(xiàn)的粗粒度分布式鎖服務,底層利用了 Paxos 一致性算法。
后面兩種實現(xiàn)方式通常適用于較粗的鎖粒度,并且具有等待隊列,看起來更像是一把鎖,但不太適用于高性能場景。而對于營銷這樣的場景來看,基于redis的分布式鎖更適合。
分布式鎖通常會存在以下幾個問題:
1、鎖超時后,其他請求加鎖后的誤釋放問題。通??梢岳梦ㄒ粏翁杹龛b別加鎖者解決。
2、鎖的可用性問題,這個問題通常發(fā)生在單點的redis服務上,紅鎖已經(jīng)給了完整的解決方案(超過半數(shù)加鎖成功),但是這種方式一定程度上損耗了性能。
需要注意的是分布式鎖的實現(xiàn),像分布式事務一樣,不管哪種方式都存在一定的問題,比如redis的紅鎖,redis本人也是說存在瑕疵的,我們最需要的是根據(jù)場景來進行鑒別選擇,解決痛點問題,容忍帶來的其他問題。
關于存儲
存儲的選擇

數(shù)據(jù)的存儲對所有的系統(tǒng)都是非常的重要,而且是重中之重,在寫這塊內容的十分鐘之前恰巧看到了剛看了oceanbase的發(fā)展之路,頗為震撼。
對于營銷場景下的存儲,很顯然沒有金融領域那么的苛刻,但是數(shù)據(jù)的可靠存儲依然十分重要,但是相對多了一些選擇。
對于常規(guī)場景而言,使用基于磁盤的關系型數(shù)據(jù)庫mysql就足夠了,特性十分豐富,也經(jīng)過了大環(huán)境的檢驗,但不要覺著所有的存儲只能拿mysql來做。
在極端的性能場景下,我們可以稍微激進點,拿redis做存儲,雖然有過redis掛掉瘋狂補數(shù)據(jù)的經(jīng)歷,但是依然覺著在某些場景下是可行的,至少比mysql提升了近一個數(shù)量級,如果想用redis做存儲,首先必須要要有預案措施也就是redis掛了怎么辦,其次要有數(shù)據(jù)的落盤行為必須保證可恢復,這里比較好用的有日志留存用戶相關數(shù)據(jù)快照、異步落庫,在建立恢復機制后,我們就可以依賴于redis快照數(shù)據(jù)+掛掉這部分時間的日志或者數(shù)據(jù)庫數(shù)據(jù)來推演出當前redis中的數(shù)據(jù)了。切記需要根據(jù)具體的業(yè)務場景來定制化選擇,最痛的點及其他方面的容忍度,就算是激進也得有個度。
數(shù)據(jù)拆分

在性能場景下,單mysql或者redis存儲完全滿足不了(查詢性能(cpu瓶頸)+IO瓶頸),這適合就要面對數(shù)據(jù)的拆分,來應對CPU+IO兩大問題。
而數(shù)據(jù)拆分無外乎垂直拆分、水平拆分,對于redis字典型非關系數(shù)據(jù)庫而言,使用hashtag+散列的key值很顯然同時做到了。對于mysql而言其實就是分庫分表(水平+垂直)
但是在拆分過程中非常重要的幾點:
1、分庫分表,需要了解瓶頸在哪,然后才能合理地拆分,不能為了分庫分表而拆分。
2、選key很重要,既要考慮到拆分均勻,也要考慮到非partition key的查詢,可以利用上面提到的用戶id或者訂單id進行拆分
另外,分褲分表會帶來大量的問題,為了問題盡可能的少、系統(tǒng)易維護拆分規(guī)則越簡單越好。
相關工具:
redis:codis、cluster等方式自帶hashtag功能。
mysql:sharding-sphere、或者自定義實現(xiàn)。
若干的語言

確定完架構方案、工具選擇,剩下的就是語言和相關的優(yōu)化了,在選擇語言時依然是上面提到的幾個標準:人力成本、研發(fā)質量成本、研發(fā)效率成本、機器成本,具體來說:
1、從語言的特性出發(fā),衡量各種成本的變化情況
2、從當前人員構成和公司內基礎設施出發(fā),衡量各種成本的變化情況
兩者結合考慮,并根據(jù)當前項目的緊急程度確定方案的選擇。如果是基礎設施的建設,直接從第一點考慮就OK了。
然后下面仔細的說一下各種語言及其擅長的領域,大家準備好,可以開噴了,本章不涉及語言的開發(fā)細節(jié),只說特性和適用程度。
Java
這是我的第一門編程語言,大概是大二的時候開始接觸,對于語言的熟悉發(fā)生在大三下學期和實習之后。
Java的優(yōu)勢很明顯,生態(tài)十分龐大,性能相對優(yōu)良,但極端性能場景下還是需要c系,研發(fā)效率處于中等位置,研發(fā)質量較高,并且Java相關人才比較好招。由于它的生態(tài)Java看起來適合非常多的場景:Hadoop、spring等,語言特性很多,工具庫異常豐富。
對于營銷系統(tǒng)而言,Java非常適合開發(fā)重業(yè)務的系統(tǒng),比如活動單元及活動流程層,在Java的基礎上我們能夠把業(yè)務邏輯盡可能的抽象,對于底層細節(jié)而言,像垃圾回收(適合各種場景的垃圾回收器:G1、cms、ZGC)網(wǎng)絡IO的處理(官方庫中基于水平觸發(fā)epoll的IO處理,netty中給予邊緣觸發(fā))等等一切都是非常完備的,對于中規(guī)中矩的系統(tǒng)非常適合。
Go
這是我的第二門編程語言,19年幾乎一年都在寫go,go是一門非常年輕的語言,看現(xiàn)在的發(fā)展也迅猛無比,
優(yōu)點:(網(wǎng)上通常有各種各樣的介紹,對于營銷而言主要是以下兩點)
1、天然的并發(fā)性
2、相對較高的開發(fā)效率
缺點也十分的明顯:
1、生態(tài)較差,你用Java可輕松找到的工具,如果用go恐怕要自己實現(xiàn)了。
2、相對Java較為垃圾的垃圾回收,以至于性能沒有那么極致。
3、if err != nil
對于營銷場景而言:
go由于天然的并發(fā)性和對epoll的高效支持,對于網(wǎng)絡IO處理非常的擅長,最經(jīng)典的應用莫過于前東家開源的bfe。落地到營銷場景而言,go很適合做流量調度型基礎組件。
其他的話,go適合做系統(tǒng)級應用開發(fā),docker、k8s這些都是基于go來實現(xiàn)的,如果做基礎架構,go是一個非常不錯的選擇。
下面要說的PHP和C++ 沒有真正的實戰(zhàn)過,沒有太多的發(fā)言權,只是簡單的說幾句,像python這種只寫過兩個時間的,就不拿出來獻丑了。
PHP
世界上最好的語言的PHP,簡直就是為web場景而生的,極致的開發(fā)效率,對于最原始的營銷場景更是如此。由于前東家的PHP做了很大程度上的優(yōu)化,所以已經(jīng)不僅僅是原生的PHP了,這點需要考慮進去,如果是原生的PHP性能場景可以排除掉了。
C++
c++比較適合極端的場景,比如說需要內存精確控制的場景像是搜索領域,對于營銷場景而言,C++使用程度其實一般。如果存在老的C++營銷系統(tǒng),拿go或者Java來重構掉是一個不錯的選擇。
對于語言和工具方面,工作一年多的時間里也做了一些積累,大家可以參考一下之前的博客:《redis系列》《kafka系列》《go語言開發(fā)系列》《Java concurrent 系列》《高性能系統(tǒng)構建系列》,后面這些內容也都會持續(xù)更新的~
http://m.itdecent.cn/u/636e09dd8775
單獨提一下epoll
在重IO的業(yè)務系統(tǒng)中,一個工具或者語言對于epoll的支持程度大概率決定了是否用它,它是IO中性能的基礎。
Java對epoll的支持相對較好,原生網(wǎng)絡包中提供了基于水平觸發(fā)的epoll,netty補充實現(xiàn)了基于邊緣觸發(fā)的epoll(Dubbo就是基于它實現(xiàn)的)
Go,采用協(xié)程+epoll的方式進行網(wǎng)絡編程,效率十分之高,這也是go適合做流量調度的根本原因
nginx最大的性能來源其實就是epoll,同時支持邊緣觸發(fā)和水平觸發(fā)兩種模式,再加上精確的內存操作所以性能炸裂。
redis的單線程處理多鏈接的模式底層也是基于epoll實現(xiàn)的,可以看一下redis ae庫中的實現(xiàn)。
如果想看一下互聯(lián)網(wǎng)時代網(wǎng)絡的發(fā)展史,可以從C10k問題起再到
如何高效的進行并發(fā)編程

并發(fā)編程是充分利用cpu高效處理的一種手段,如果涉及并發(fā)有兩點是無法忽略的:1、并發(fā)編程模型 2、鎖的處理。
并發(fā)編程模型
對于并發(fā)編程模型通常來說是伴隨著語言的,比如go實現(xiàn)的基于協(xié)程和管道的CSP模型,Java基于內存共享的線程模型,在我們選擇語言的時候就已經(jīng)確定了。這兩者有著非常大的區(qū)別:CSP 并發(fā)模型的核心概念是:“不要通過共享內存來通信,而應該通過通信來共享內存”。兩種方式各有各的好處,根據(jù)場景選擇即可,這也是語言選擇的一個重要的點。
我們需要根據(jù)具體場景來選擇,即使語言有限制,go使用內存共享,Java來實現(xiàn)基于管道通信也未嘗不可。
鎖的使用
這是這一篇章的核心內容,對于鎖的使用。對于優(yōu)化來說通常有下面幾點:
1、避免加鎖
一些能夠犧牲空間來進行線程或者協(xié)程私有數(shù)據(jù)空間,就沒必要使用鎖了,加鎖完全是為了并發(fā)下邏輯的正確,如果有更好的解決方式,請避免使用鎖。
2、鎖選擇
拿Java舉例,就synchronized、ReentrantLock來分析比較的話,看到網(wǎng)上有好多博客都在說sychronized 在爭用頻次非常高的情況下性能會急劇下降,這種觀點是存在時效性的,就當前1.8版本使用體驗而言,sychronized在大量爭用的情況性能其實還好并不會出現(xiàn)所謂的急劇下降,倒是在激烈爭用時sychronized的性能要好一些,這個問題去官網(wǎng)確認了下,官方是建議使用sychronized的,這次的體驗也是sychronized更好。因為當前JVM是對于sychronized做出了優(yōu)化了,借鑒ReentrantLock的CAS加鎖方式,并且引入了偏向鎖、輕量級鎖等特性后,常規(guī)情況下兩者比較相似,實踐中得到的體驗是sychronized性能更好一點。
3、鎖粒度
如果非得需要鎖,粒度要盡可能的控制到小,避免不必要的加鎖。因為同步塊越長,線程持有鎖的時間就越長,其他線程等待的時間就越長,如果整個都是加鎖的,那么整個程序就變成串行處理了。
但是要主要不要頻繁加解鎖。
4、相關并發(fā)工具的選擇
最簡單的方式其實就是盡可能使用原生經(jīng)過反復驗證的官方工具(concurrenthashmap、waitgroup等),不要嘗試使用自己編寫工具,大概率出問題,但是為了學習,就很有必要了。
代碼優(yōu)化
代碼邏輯優(yōu)化
其他的代碼優(yōu)化語言層面感受到的有CPU使用減少、IO減少、語言底層優(yōu)化三方面:
對于cpu來說:
1、md5、Json序列化反序列化等這些都是非常耗性能的,如非必要,建議砍掉。
2、充分回憶起大學時的《數(shù)據(jù)結構和算法》,例如可以用Hash O(1)打表解決的,請不要幾層for一通干。
3、謹慎使用明確存在性能風險的工具,比如go中的定時器,踩坑之深,至今難忘。
4、不要覺著起協(xié)程或者線程效率高,很多時候無端的起協(xié)程和線程得到的收益是很小的。
對于IO來說:
還是上面提到的那個思想,基于epoll的基礎上,砍IO、pipline
最主要的是在IO操作評估時,要根據(jù)具體的日志來量化分析統(tǒng)計,而不是看代碼,拍腦門決定。
語言底層優(yōu)化&其他
對于語言底層的優(yōu)化,在日常開發(fā)中其實接觸的不會很多,很多的是出現(xiàn)在面試中,哈哈哈哈哈哈。實際操作過程中,也偶爾會涉及到,主要是對于GC相關的內存分配。
對于GC而言,垃圾回收器的選擇還有代碼精減(減少臨時對象等)要比瞎折騰調參數(shù)強的多,如果選擇了合適的垃圾回收器,然后才是具體參數(shù)的調優(yōu),這一塊要充分利用語言的原生工具,比如說jmap、jstack、火焰圖工具等,確定問題的根源,再動手。
再其次,才是即時編譯等參數(shù)。
可怕的資損

營銷場景下最大的問題其實是資損,營銷場景下因為其邏輯多變且臨時,rd的開發(fā)周期極短,并且方案往往很難有時間反復推敲,不管是運營邏輯還是很代碼代碼實現(xiàn)都存在較大的風險,是極易發(fā)生資損的。而面對這些問題,也是建立一個較為完善的營銷系統(tǒng)的目的所在。
經(jīng)典案例
1、某東
京東無門檻優(yōu)惠券被大量羊毛黨領取,在優(yōu)惠疊加后可以用極低價格(接近0元)購買數(shù)百元的小家電,涉及資損金額數(shù)千萬
2、某多
騰訊視頻“0.2元開通VIP”處理、東航0.4折白菜機票bug。
如何可防可控
對于資損問題,最重要的有幾個方案:
1:嚴控流程問題
保證運營方案、技術設計、代碼cr等主要環(huán)節(jié)發(fā)現(xiàn)問題,并及時解決。
2:完善資損監(jiān)控
這時候資損的發(fā)現(xiàn)環(huán)境,很多時候資損總是不經(jīng)意間發(fā)生,我們最需要的是在資損發(fā)生的第一時間能夠發(fā)現(xiàn),而不是等幾小時、幾天后鑄成大錯才意識到。資損監(jiān)控需要針對活動流程定制化開發(fā),資損監(jiān)控最好是在設計階段就要考慮進來的,根據(jù)運營邏輯、系統(tǒng)設計來確定資損點從而定制化開發(fā),這樣不光完善了資損監(jiān)控,更加后悔使rd同學方案設計及代碼開發(fā)時更謹慎。一般來說圍繞以下四點進行監(jiān)控能發(fā)現(xiàn)絕大部分的資損問題:
1、單個權益是否超標
2、單個用戶領到的權益量是否超標
3、是否存在不符合條件的用戶領取到對應權益
4、是否存在異常峰值流量進行刷單
3:監(jiān)控止損
對于止損問題,是一個比較難處理的環(huán)節(jié),通常需要在保證活動正常運行的情況下進行止損,比較重大的問題可以直接活動下線(會存在監(jiān)管合規(guī)風險)
在活動設計時應具有專門針對資損的預案處理,比如:
權益方面:
1、權益的回收機制(降低資損程度)
2、權益迅速降級機制(原本發(fā)紅包,改為優(yōu)惠券)
活動流程方案:
1、一鍵不中獎
2、活動規(guī)則范圍內增加門檻
3:接入專業(yè)風控&反欺詐能力
這里主要是針對專業(yè)羊毛黨用戶的,防止作弊流量刷單,杜絕黑名單用戶參與活動。
總結
整篇文章到這里就很急促的結束啦,想寫的業(yè)務內容還有技術內容還有很多,受限于篇幅和時間原因就先寫這么多吧,文章寫的越長,對它的整體控制能力稍微有點開始下降了。
整體來看一個營銷系統(tǒng)的核心就是:以技術能力解決營銷場景下的效率、質量、風險等問題,高效穩(wěn)定的提供營銷能力輸出。
系統(tǒng)實線時的關注點:1、性能 2、數(shù)據(jù)一致性 3、高度配置化能力 4、完備的效果洞察&監(jiān)控能力 5、系統(tǒng)的可擴展性

鄒志全記于北京、杭州
2020.03.24 - 2020.05.23