最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

就創(chuàng)新試錯(cuò)聊聊Serverless + Faas架構(gòu),贈(zèng)送Groovy高性能規(guī)則引擎實(shí)踐

2023-07-30 23:51 作者:remix_hello  | 我要投稿

原文鏈接

更多可關(guān)注zouzhiquan.com

1. 前言

始前先看下整個(gè)文章的梗概:我會(huì)對(duì)于多變業(yè)務(wù),比如“小步快跑”、“創(chuàng)新試錯(cuò)”下的業(yè)務(wù)開發(fā)模式和架構(gòu)方案做出分析設(shè)計(jì),最終推導(dǎo)出Faas + ServerLess架構(gòu),并就Java技術(shù)棧下,對(duì)于這種架構(gòu)進(jìn)行落地,實(shí)現(xiàn)基于自定義Groovy引擎的Faas+ServerLess架構(gòu)下的“低代碼”服務(wù)。

1.1 大體背景

我由于是做營(yíng)銷活動(dòng)相關(guān)的,日常經(jīng)常會(huì)碰到一個(gè)場(chǎng)景:各種頻出的創(chuàng)新的idea需要嘗試,創(chuàng)新這件事兒意義很大。但是每個(gè)idea都去實(shí)踐落地,成本又太高了,如果完全靠直覺(jué)和經(jīng)驗(yàn),很容易扼殺一些優(yōu)秀的idea。

所以就開始嘗試降低創(chuàng)新的成本,比如說(shuō)之前一個(gè)活動(dòng)30人日,我們能不能降低到3人日,看上去這是一個(gè)很夸張的成本節(jié)省數(shù)量,但是理論上是可行的。

1.2 成本分析 – 需要提效

首先我們對(duì)于成本進(jìn)行初步分析,一場(chǎng)活動(dòng)的成本,大致分為:人力成本 + 活動(dòng)預(yù)算 + 流量成本。

對(duì)于idea的創(chuàng)新實(shí)踐,我們初期并不需要過(guò)大的活動(dòng)預(yù)算和大規(guī)模的資源流量,通常在前幾天就能把效果論證出來(lái),定向推送特征用戶就好了,有傳播傾向的idea,可以用延遲發(fā)獎(jiǎng)來(lái)解決,效果好了就增加預(yù)算,效果差,錢也花不出去。

所以剩下的,最核心的成本在于人力成本,人力成本可比大家想象的要重要的多,尤其是在這么個(gè)降本增效的大環(huán)境下,幾十人日、幾百人日的投入創(chuàng)新嘗試是不現(xiàn)實(shí)的,所以我們要解決的關(guān)鍵性問(wèn)題就是活動(dòng)生產(chǎn)的效率問(wèn)題。

不僅是活動(dòng)方面,其實(shí)每個(gè)具有創(chuàng)新屬性、小步試錯(cuò)屬性的場(chǎng)景,都有著類似的問(wèn)題。

1.2 其他推薦

前面有幾篇文章已經(jīng)提到過(guò)大致的方法論和初步的實(shí)踐思路了,有興趣也可以前置的讀一下,不過(guò)不讀也不耽誤本篇的閱讀,部分內(nèi)容是有一些重復(fù)的。

營(yíng)銷活動(dòng)提效之道

業(yè)務(wù)開發(fā)“銀彈” – 低代碼平臺(tái)建設(shè)

接下來(lái)進(jìn)入正題,如何解決這種創(chuàng)新類、小步試錯(cuò)的“輕業(yè)務(wù)”。

2. 可以有的思路,一通分析

要想節(jié)省成本,有兩個(gè)思路:減少工作量、提升落地效率。

事情得做,還得做作,需求不能砍,所以剩下的方法只有復(fù)用;提升落地效率,這個(gè)prd效率、前后端研發(fā)效率、測(cè)試效率每一個(gè)方面其實(shí)都可以去節(jié)省。

我們就idea的落地流程,進(jìn)行細(xì)節(jié)上的分析,看看如何來(lái)提升效率。

2.1 落地分析

首先就整個(gè)流程進(jìn)行分析,減少繁瑣的流程,少一點(diǎn)大公司病,多一些創(chuàng)業(yè)狀態(tài),高生產(chǎn)效率的節(jié)奏下,快速idea落地體驗(yàn),溝通對(duì)齊,是整體的目標(biāo)。

做事前,先看最佳實(shí)踐,一種好的、理想的方式是“我只關(guān)心本次創(chuàng)新的部分,也只需要執(zhí)行本次創(chuàng)新的部分”

具體舉措:
1: 要有機(jī)制能夠保障只需要開發(fā)創(chuàng)新點(diǎn),并且高效的開發(fā)創(chuàng)新點(diǎn)。
2: 影響能夠隔離,只影響當(dāng)前的創(chuàng)新嘗試(測(cè)試范圍),不影響其他任意功能或業(yè)務(wù)。
3: 線上無(wú)運(yùn)維成本,即寫即跑,不用申請(qǐng)存儲(chǔ)、不用配nginx、不用起機(jī)群、不用部署上線,就小成本快速出個(gè)看板,關(guān)注具體效果就好。
4: 開發(fā)體驗(yàn)上,熱更熱部,0等待成本,大腦無(wú)線程間切換,快速心流模式。
5: 很輕松的就能復(fù)用,少量的復(fù)用適配成本。

2.1 落地分析,我能想到的方案

2.1.1 開發(fā)過(guò)程

根據(jù)上面的最小實(shí)現(xiàn)原則,我們不妨對(duì)于開發(fā)一個(gè)功能所涉及的工作進(jìn)行分析,就拿營(yíng)銷活動(dòng)來(lái)看,我們能想象這么幾種方案:

  • 基于已有的邏輯做if-else變更
    我們使用現(xiàn)有的活動(dòng)做變更,支持給活動(dòng)增加活動(dòng)id之類的概念,用于區(qū)分不同的邏輯,在原有的邏輯上做新增,然后適配兼容。

這種模式就本次開發(fā)和之后的維護(hù)成本來(lái)說(shuō)都是最高的,可能改起來(lái)最不費(fèi)勁,加一點(diǎn)代碼就解決了,但兼容邏輯、廢棄邏輯會(huì)越來(lái)越多。我們業(yè)務(wù)上的屎山通常就是這樣出現(xiàn)的。

而且對(duì)于單次活動(dòng)的開發(fā),影響面也沒(méi)有控制住,可能會(huì)對(duì)既有邏輯產(chǎn)生一定的影響。

這個(gè)方案基本上可以否了。

  • 完全純新寫
    新起一組邏輯,完全用來(lái)支持當(dāng)前的創(chuàng)新活動(dòng),與現(xiàn)有邏輯完全隔離,最小原則實(shí)現(xiàn),和原有的邏輯完全做了隔離,這是業(yè)務(wù)發(fā)展初期、系統(tǒng)重構(gòu)常用的思路,在成本不高的時(shí)候,是可行的。

但是如果邏輯相對(duì)復(fù)雜,創(chuàng)新idea的成本是沒(méi)有減少的,所以這個(gè)方案也暫時(shí)先否了。

  • 基于復(fù)制邏輯
    copyOnWrite,把當(dāng)前的代碼做一份復(fù)制,在復(fù)制出來(lái)的代碼上做修改,與現(xiàn)有邏輯變相隔離,不用適配老邏輯,直接做新增修改即可。

這樣是不是就做到了,即跟既有邏輯隔離,又減少了重復(fù)開發(fā)。但是現(xiàn)實(shí)是我們的活動(dòng)開發(fā)并沒(méi)有那么輕松就可以復(fù)制,整個(gè)的復(fù)制成本也會(huì)很高,除此之外,老邏輯修改至邏輯的成本還是太高,需要對(duì)老邏輯非常熟悉才行。

要是有一種方式,能夠一鍵復(fù)制就好了,比如就一個(gè)腳本,直接copy?這個(gè)思路沒(méi)準(zhǔn)能行。

  • 基于沉淀復(fù)用 + 純新寫組裝邏輯
    我們?nèi)コ恋泶罅康墓ぞ撸趧?chuàng)新idea到來(lái)時(shí),利用工具集去構(gòu)建新的活動(dòng),由于沉淀的這部分是穩(wěn)定的,變的通常是組合邏輯,是可行的。

但是組裝邏輯也相對(duì)龐大,能不能再省省。

小朋友才做選擇,大朋友選擇都要,我們想要的已經(jīng)清晰了:快捷復(fù)制 + 純新部分新寫 + 沉淀功能集

2.1.2 調(diào)試過(guò)程

還記得寫php和python的爽感嘛,語(yǔ)法精煉,熱更熱部。那能不能對(duì)于這種創(chuàng)新idea我們也采用這個(gè)技術(shù)棧,當(dāng)然是可行的。想要提效,熱更熱部是必須的。

但是通常這個(gè)方式會(huì)收到各方面的制約,比如說(shuō)技術(shù)棧不統(tǒng)一的問(wèn)題,由于生態(tài)的原因,不得不否認(rèn)Java是最主流的業(yè)務(wù)工程開發(fā)語(yǔ)言。并且對(duì)于活動(dòng)場(chǎng)景來(lái)看性能要求是比較高的,php、python的性能說(shuō)實(shí)話,還是差了一些的。

我們需要一種模式提供熱更熱部,并且執(zhí)行效率至少跟Java差不多,能兼容現(xiàn)有的功能集合語(yǔ)言上無(wú)縫兼容(如果調(diào)一個(gè)功能就來(lái)次rpc,成本太高了),順道還能有Java的巨大生態(tài)便利。

肯定有人杠,Java也能熱更熱部,我是覺(jué)著真的夠難用,而且跟這種天然熱的腳本類語(yǔ)言完全不是一個(gè)概念。

2.1.3 運(yùn)維成本

開發(fā)、調(diào)試完成、測(cè)試完成之后,下一步就是線上運(yùn)維了,傳統(tǒng)的開發(fā)模式,研發(fā)對(duì)于線上的機(jī)器資源、存儲(chǔ)資源、中間件資源等感知太多了,但就一個(gè)上線過(guò)程就要耗費(fèi)掉一天的時(shí)間,并且為了安全還要使用各種手段,一臺(tái)臺(tái)機(jī)器去灰,還要感知nginx、DB等中間件服務(wù),需要時(shí)還得有各種前置工作。

那能不能無(wú)需感知這些對(duì)于線上應(yīng)用即寫即跑,背后的資源毫不關(guān)心,讓研發(fā)看不到“機(jī)器”,管控該做做,灰度就用流量灰。

2.2 Faas + ServerLess

把上面的方式總結(jié)下來(lái)看:

  • 開發(fā)過(guò)程

    • 快速?gòu)?fù)制

    • 穩(wěn)定的功能集合


  • 調(diào)試過(guò)程

    • 即寫即跑,熱更熱部


  • 運(yùn)維過(guò)程

    • 不感知服務(wù)器


總結(jié)下來(lái)這不就是Faas和 ServerLess架構(gòu)嘛(面向的沉淀好的穩(wěn)定功能集合,進(jìn)行腳本化編程)。我們只需要給我們的系統(tǒng),增加能執(zhí)行創(chuàng)新idea的容器就好了。

用腳步寫幾個(gè)基礎(chǔ)活動(dòng)模版,然后實(shí)例化一個(gè)模版(復(fù)制一個(gè)出來(lái)),把創(chuàng)新的玩法在里面加進(jìn)去,編寫創(chuàng)新玩法時(shí),使用我們沉淀好的功能集合,發(fā)布時(shí)就點(diǎn)一下,然后自動(dòng)構(gòu)建接口,自動(dòng)識(shí)別存儲(chǔ)。需要灰度就拿流量整個(gè)白名單推全,然后在整個(gè)腳本的版本系統(tǒng),是不是問(wèn)題就都解了。

整體的方案就是這么簡(jiǎn)單,但是我們需要解決落地過(guò)程中的 Java兼容性問(wèn)題腳本對(duì)于功能庫(kù)的識(shí)別、高效復(fù)用問(wèn)題、腳本執(zhí)行性能問(wèn)題、服務(wù)穩(wěn)定性問(wèn)題,下面就具體來(lái)看,是怎么落地的。

業(yè)界并沒(méi)有一種完美的解決方案能把上面的幾個(gè)問(wèn)題妥善解決掉,在整個(gè)落地過(guò)程中做了較多的自主優(yōu)化。

3. 基于Groovy引擎的優(yōu)化落地

直接看下我們要落地的應(yīng)用:

3.1 Java 兼容性問(wèn)題

這里就拿業(yè)界最常用的Java技術(shù)棧來(lái)看落地,其他場(chǎng)景可能技術(shù)選型不同,但是思路是一致的。

Groovy算是Java生態(tài)中最常用的語(yǔ)言了,純粹的groovy語(yǔ)法十分精煉。并且與Java環(huán)境高度兼容,甚至可以直接在groovy腳本中寫Java代碼。

在國(guó)內(nèi)Groovy 最常用的場(chǎng)景是寫單測(cè)腳本、寫離線數(shù)據(jù)腳本等等,很大的原因就是跟Java兼容性極高,并且編寫效率極高,對(duì)于Java開發(fā)同學(xué)沒(méi)有成本。

但是Groovy腳本性能是比較差的,測(cè)試來(lái)看比Java性能得低一個(gè)數(shù)量級(jí),無(wú)論是用哪種執(zhí)行方式,這個(gè)是腳本執(zhí)行的通病,但是如果把性能問(wèn)題解決了,Groovy腳本是不是就很香了。

3.2 腳本執(zhí)行性能問(wèn)題

Java環(huán)境中使用groovy腳本有這么幾種模式:GroovyShell執(zhí)行、GroovyClassLoader、GroovyScriptEngine 三種方式去執(zhí)行腳本。這三種模式都是類似的,性能都比較差。

3.2.1 執(zhí)行原理

  • GroovyShell
    GroovyShell 適用于執(zhí)行片段腳本,具體執(zhí)行過(guò)程,可以理解為把代碼片段放到一個(gè)靜態(tài)方法里,然后invokeStatic,然后完成處理。

每次都要進(jìn)行新groovy 腳本的拼接,然后編譯成對(duì)應(yīng)的class文件,然后再newInstance構(gòu)建一個(gè)對(duì)象,然后向下轉(zhuǎn)為GroovyObject,然后調(diào)用invokeStatic。

整個(gè)拼接、編譯、反射調(diào)用都是非常耗時(shí)的,然后次次編譯產(chǎn)生大量的類,短時(shí)的方法區(qū)占用比較多。

  • GroovyClassloader
    GroovyClassloader 可以選擇groovy文件進(jìn)行編譯,然后夾在對(duì)應(yīng)的class文件,使用的方式也是newInstance對(duì)象,用GroovyObject中的invokeMethod方法,調(diào)用的是MetaClass中的invokeMethod方法,最底層就是lang.reflect,性能也比較差。

相對(duì)于Groovy少了拼接、編譯的過(guò)程,我們可以根據(jù)需要緩存對(duì)應(yīng)的實(shí)例,每次的主要成本就是反射調(diào)用成本。

  • GroovyScriptEngine
    這東西就是在groovyClass、GroovyShell上封裝了一下,可以指定url去進(jìn)行當(dāng)前目錄的腳本訪問(wèn),并且提供了緩存機(jī)制,能自主發(fā)現(xiàn)文件更新(檢查md5),提供了使用上的便利性。

但是這些便利性還不如不提供,反而引入了OOM風(fēng)險(xiǎn),方法區(qū)不會(huì)進(jìn)行卸載,然后每次變更會(huì)生成一個(gè)全新的class。并且性能方面雖然提供了緩存,但是反射調(diào)用的問(wèn)題依舊無(wú)解,性能還是比較差

3.2.2 基礎(chǔ)信息供給

回憶點(diǎn)基礎(chǔ),設(shè)計(jì)過(guò)程需要大量的類加載、反射等相關(guān)知識(shí)。

一個(gè)class對(duì)象的唯一標(biāo)示是:類加載器 + 類全限定名稱。

一個(gè)class文件被裝載后在方法區(qū)生成class對(duì)象(啟動(dòng)時(shí)),這個(gè)對(duì)象在整個(gè)運(yùn)行過(guò)程中是無(wú)法被替換的,像重新加載要么換名字,要么換類加載器,要么改JVM(這個(gè)侵入性太大)。

Java的類加載委派模型,最上層是bootstrap、然后是Extension、在是application,然后加載時(shí)會(huì)層層向上委派,主要是解決同一個(gè)類不同加載器問(wèn)題。

由于上面的實(shí)現(xiàn),父類加載器加載的類,是沒(méi)辦法訪問(wèn)子類加載器加載的類的,這個(gè)隔離是在class文件裝載時(shí)進(jìn)行校驗(yàn)的。

反射定義 是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意方法和屬性;這種動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象方法的功能稱為java語(yǔ)言的反射機(jī)制。

有了前置的這部分信息,我們開始進(jìn)行分析設(shè)計(jì)。

3.2.3 自定義引擎

針對(duì)性能差的問(wèn)題,我是做了對(duì)應(yīng)的自定義實(shí)現(xiàn),反正就是個(gè)編譯 + 裝載的過(guò)程。并且除性能以外,我需要對(duì)于整個(gè)過(guò)程都完全可控。

首先使用CompilerConfiguration進(jìn)行腳本編譯控制,編譯成對(duì)應(yīng)的class文件。(這里可以讓編譯過(guò)程可控)

然后自定義classloader進(jìn)行裝載(只需要一個(gè)單純的class文件,只需要一個(gè)裝載功能,并且需要整個(gè)過(guò)程完全可控)

然后ASM 進(jìn)行對(duì)應(yīng)目標(biāo)class的代理類生成(ReflectASM),生成新的class:*****MethodAccess,然后再用代理類完成Groovy 腳本內(nèi)方法反射調(diào)用(由于直接定位到方法調(diào)用,本質(zhì)就是直接調(diào)用)。

過(guò)程按照一定的策略緩存對(duì)應(yīng)的代理對(duì)象,來(lái)避免次次編譯、創(chuàng)建代理。

這樣使用grovvy的時(shí)候,就跟使用本地方法一致了。

ps: 為什么ReflectASM比Java原生的反射快,而ASMReflect比直接調(diào)用性能差在哪,為什么能得到JIT的優(yōu)化,下面嘗試分析。

  • 直接調(diào)用
    前置動(dòng)作:java 代碼 -> 字節(jié)碼 -> class load

執(zhí)行時(shí):根據(jù)加載好的類信息找到對(duì)應(yīng)的方法引用,去執(zhí)行就好

  • Java 原生反射
    前置動(dòng)作:java 代碼 -> 字節(jié)碼 -> class load

提前緩存:獲取class信息 -> 獲取對(duì)應(yīng)Method

執(zhí)行過(guò)程:檢查系統(tǒng)狀態(tài)和參數(shù)可以執(zhí)行這個(gè)方法 -> 和正常JVM執(zhí)行方法一樣執(zhí)行這個(gè)方法

  • ReflectASM 反射
    前置動(dòng)作:java 代碼 -> 字節(jié)碼 -> class load

提前緩存:獲取class信息 -> 生成XXXXXXXMethodAccess類 -> 完成新類load

執(zhí)行過(guò)程:使用向上轉(zhuǎn)型后的代理對(duì)象(MethodAccess容器加載可識(shí)別)-> 調(diào)用invoke方法 -> 然后會(huì)走到具體的實(shí)現(xiàn)類(繼承自MethodAccess)-> 然后代理內(nèi)部將腳本對(duì)象強(qiáng)轉(zhuǎn)為對(duì)應(yīng)的腳本對(duì)象 -> 根據(jù)名稱(for-each)或者方法序號(hào)(switch-case) 對(duì)于腳本對(duì)象的方法進(jìn)行直接調(diào)用。

ReflectASM進(jìn)行調(diào)用比直接調(diào)用就多了兩層棧、一層for-each或者switch-case,性能近似相同。

ReflectASM相對(duì)于原生JDK的反射,基本相當(dāng)于直接調(diào)用和反射調(diào)用的差異,少了參數(shù)和狀態(tài)檢查的過(guò)程,并且能得到JIT的優(yōu)化。

ReflectASM能被JIT優(yōu)化到,完全是因?yàn)檫@種模式本質(zhì)上就是直接調(diào)用。

看上去父類加載器訪問(wèn)了子類加載器加載的類,其實(shí)沒(méi)有,裝載過(guò)程都是沒(méi)有任何問(wèn)題的,這里就是利用了向上轉(zhuǎn)型,編寫時(shí)使用父類進(jìn)行子類訪問(wèn),然后再層層向上使用。

3.2.3 編譯優(yōu)化 – 內(nèi)部函數(shù)鏈路優(yōu)化

重寫完引擎之后,發(fā)現(xiàn)性能還是差一些,然后一邊壓測(cè)一遍看了下對(duì)應(yīng)火焰圖,發(fā)現(xiàn)調(diào)用棧里多了一些我不認(rèn)識(shí)的東西。并不是常規(guī)的函數(shù)調(diào)用棧。

這里的問(wèn)題是因?yàn)镚roovy的動(dòng)態(tài)性,grovvy編譯后的class對(duì)象都默認(rèn)實(shí)現(xiàn)了groovy.lang.GroovyObject(可以嘗試反編譯上面的class文件看看哈),里面有一個(gè)MetaClass持有了整個(gè)類的元信息,方法路由就是通過(guò)MetaClass來(lái)進(jìn)行方法尋找的,可以用這個(gè)機(jī)制完成在運(yùn)行時(shí)完成屬性和方法的添加。(底層調(diào)用其實(shí)就是反射,只不過(guò)讓反射的使用變簡(jiǎn)單了),具體可以去看下官方文檔哈。

這個(gè)動(dòng)態(tài)性使性能大幅度下降,這時(shí)候我們就需要把這個(gè)特性給關(guān)掉:@CompileStatic 就行。

ps-1: 外層依然有必要進(jìn)行動(dòng)態(tài)代理,沒(méi)有其他的入口,防止JIT中斷。

ps-2: 很多時(shí)候JIT 去優(yōu)化就是因?yàn)榉瓷洌唧w可以看下對(duì)應(yīng)的JIT 日志,-XX:+PrintCompilation -XX:+CITime

3.2.4 服務(wù)預(yù)熱優(yōu)化

我們?nèi)粘5姆?wù)在啟動(dòng)時(shí)都會(huì)有預(yù)熱動(dòng)作,讓服務(wù)的剛開始的請(qǐng)求都能以正常的性能來(lái)提供。但是容器化之后,新腳本的預(yù)熱是缺失的。

預(yù)熱缺失在除了會(huì)導(dǎo)致部分請(qǐng)求的平響增加,在流量極大的情況下是非常危險(xiǎn)的,預(yù)熱產(chǎn)生的開銷和鏈接夯死有極大的概率導(dǎo)致服務(wù)奔潰,這里可以參照《架構(gòu)視角的性能優(yōu)化》

一個(gè)腳本中需要預(yù)熱的是:client(初始化)、函數(shù)(實(shí)例化)、自身代碼,核心重開銷的是client、函數(shù),自身代碼的JIT熱度其實(shí)還好。

這里可以給腳本的下發(fā)過(guò)程做一點(diǎn)處理,提供預(yù)熱入口,編寫腳本時(shí)就能保證腳本是熱的。這樣可以根據(jù)腳本的內(nèi)部邏輯進(jìn)行針對(duì)性的預(yù)熱,但是這樣相對(duì)麻煩,我們需要感知發(fā)布邏輯,并且每次需要新增代碼,不夠優(yōu)雅。

最佳的方式是,服務(wù)啟動(dòng)時(shí)保證腳本中用到的函數(shù)、client這些都是熱的,剩下的只有腳本邏輯了,這部分本質(zhì)上的性能損耗非常少了,基本可以忽略不計(jì)。如果想要極致的優(yōu)化方式,可以使用AOT編譯,強(qiáng)制靜態(tài)編譯,這部分內(nèi)容未曾實(shí)踐落地,就不說(shuō)了。

3.3 如何識(shí)別功能函數(shù)庫(kù)

我們基于Faas落地組裝邏輯,如果在開發(fā)過(guò)程中、運(yùn)行過(guò)程中識(shí)別函數(shù)庫(kù)呢。

首先功能函數(shù)庫(kù),可以有兩種落地形式,標(biāo)準(zhǔn)的服務(wù)(有狀態(tài)函數(shù))或工具服務(wù)(無(wú)狀態(tài)函數(shù)),就使用場(chǎng)景來(lái)看有狀態(tài)函數(shù)是我們最常用的,比如說(shuō)庫(kù)存、機(jī)會(huì)、計(jì)數(shù)、榜單這些,工具函數(shù)比如概率、規(guī)則引擎、決策服務(wù)等等。

3.3.1 編譯時(shí) – SDK引入

3.3.1.1 服務(wù)引入

對(duì)于開發(fā)過(guò)程,由于是腳本 + 面向接口編程,可以直接利用類似于SPI的實(shí)現(xiàn)機(jī)制,引入一個(gè)SDK,只感知具體的接口,這個(gè)接口怎么實(shí)現(xiàn)、有哪些實(shí)現(xiàn)就由容器和運(yùn)行環(huán)境來(lái)決定。

3.3.1.2 自定義函數(shù)庫(kù)

除了上面的這種方式,可以直接跳過(guò)接口,直接自己導(dǎo)入沉淀好的工具庫(kù),比如一個(gè)Jar包,里面包含了自身的業(yè)務(wù)功能函數(shù)等等。

3.3.1.3 RPC接入

可以向在Java開發(fā)一樣,直接在腳本里使用其他的RPC服務(wù),只需要包裝個(gè)類似于ClientFactory就可以啦。

3.3.2 運(yùn)行時(shí) – 類加載模型擴(kuò)展

上面提到的都是各種開發(fā)時(shí)的引用方式,但是運(yùn)行時(shí)怎么保證你的腳本們正常加載到對(duì)應(yīng)的函數(shù)、容器又是怎么執(zhí)行你的腳本呢。

我們spring中的包是在application這一層所加載的,相當(dāng)于容器的信息是application加載的。

3.3.2.1 腳本識(shí)別

而腳本是子類加載器所加載的,我們想要訪問(wèn),只能通過(guò)反射。(容器不感知腳本也非常的合理),所以就直接反射調(diào)用了。

不過(guò)這里繞了一層,直接通過(guò)ASM生成字節(jié)碼文件,做了層代理,生成代理類之后,使用被代理類的加載器(腳本類加載器)作為父類加載器構(gòu)建一個(gè)子類加載器,然后代理類通過(guò)子類加載器進(jìn)行裝載,最終會(huì)委派至父類加載器裝載,也就是腳本類加載器裝載。

具體可以看看,com.esotericsoftware.reflectasm.AccessClassLoader#get,邏輯不復(fù)雜。

ps: 調(diào)用過(guò)程可以認(rèn)定是反射調(diào)用,但性能是直接調(diào)用

3.3.2.2 腳本訪問(wèn)容器

而腳本中想要訪問(wèn)存放于容器中的接口暢通無(wú)阻。

3.3.2.3 腳本訪問(wèn)函數(shù)庫(kù)

然后再把自定義函數(shù)庫(kù)加進(jìn)去,只需要指定函數(shù)庫(kù)Jar類加載器的父類加載器是application就好,然后再指定腳本的父類加載器是函數(shù)庫(kù)Jar類加載器即可。

這樣就做到了,腳本中能夠正常訪問(wèn)容器中的能力,又能訪問(wèn)函數(shù)庫(kù)中的資源,同時(shí)容器能夠正常訪問(wèn)對(duì)應(yīng)的腳本資源。

3.3.3 整體來(lái)看

整體盤下來(lái),類加載視圖長(zhǎng)這樣:

整體的調(diào)用鏈路長(zhǎng)這樣:

3.4 服務(wù)穩(wěn)定性問(wèn)題

解決完性能、兼容性、函數(shù)庫(kù)識(shí)別之后,下一個(gè)問(wèn)題是穩(wěn)定性問(wèn)題,要求其實(shí)很清晰,隔離性 + 故障屏蔽

3.4.1 隔離問(wèn)題

腳本之間不能互相影響、不同業(yè)務(wù)的腳本之間不應(yīng)該出現(xiàn)資源搶占的情況。

首先腳本之間在上面說(shuō)的那種模式落地之后,隔離邏輯非常好做,只要把整個(gè)腳本try掉就好了,無(wú)論任何錯(cuò)誤不能導(dǎo)致影響容器運(yùn)行。

然后業(yè)務(wù)之間的腳本是互相隔離的,在一個(gè)資源上只能加載一個(gè)業(yè)務(wù)的腳本,或者只能加載其中一個(gè)腳本,必要時(shí)一個(gè)集群僅運(yùn)行一個(gè)腳本也是十分有必要的,選擇不同的套餐就好了。

并且應(yīng)該對(duì)于單個(gè)腳本進(jìn)行邏輯隔離的治理,比如說(shuō)這個(gè)腳本能使用多少存儲(chǔ)、能用多少機(jī)器、最大多少流量,只需要在存儲(chǔ)選擇層面做合適的集群轉(zhuǎn)發(fā)、流量入口處做限流、分發(fā)至不同的集群即可。

3.4.2 故障屏蔽

當(dāng)一個(gè)腳本存在技術(shù)上的問(wèn)題時(shí),是不應(yīng)該被發(fā)布成功的,在新腳本錯(cuò)誤期應(yīng)該保持當(dāng)前可用腳本對(duì)外提供服務(wù)。并且在錯(cuò)誤期間應(yīng)該持續(xù)性報(bào)警,并且腳本發(fā)布流程夯住的。實(shí)現(xiàn)模式就是經(jīng)典的上線單發(fā)布流程。

對(duì)于業(yè)務(wù)性問(wèn)題,我們是無(wú)法立即感知的,我們需要提供灰度過(guò)程,實(shí)現(xiàn)白名單用戶、特征用戶訪問(wèn)新的腳本,實(shí)現(xiàn)腳本的影響范圍控制,如果小流量存在問(wèn)題時(shí),應(yīng)該要有故障恢復(fù)能力。

3.4.3 故障恢復(fù)

提供腳本的快速回滾機(jī)制、腳本下線機(jī)制,在出現(xiàn)問(wèn)題時(shí)能夠快速的恢復(fù)。

3.5 復(fù)用的效率分析

可以參照我之前的文章?tīng)I(yíng)銷活動(dòng)提效之道,這部分內(nèi)容為節(jié)選篇章,原理都是一致的。

3.5.1 顆粒度問(wèn)題

通常來(lái)說(shuō)復(fù)用機(jī)會(huì)和顆粒度大致呈負(fù)相關(guān)的關(guān)系,顆粒度越大,能夠復(fù)用的機(jī)會(huì)就越小,顆粒度越小,能夠復(fù)用的機(jī)會(huì)往往越大。

拿一個(gè)活動(dòng)來(lái)說(shuō),從大顆粒到小顆粒,可以大致分為活動(dòng)、玩法、有狀態(tài)函數(shù)、無(wú)狀態(tài)函數(shù)、庫(kù)函數(shù)。

顆粒度越大成本節(jié)省越多,但是復(fù)用機(jī)會(huì)較小,顆粒度越小能夠節(jié)省的成本越小,但是復(fù)用機(jī)會(huì)較大。就實(shí)踐而言,單就成本節(jié)省帶來(lái)的提效是有一個(gè)最優(yōu)點(diǎn)的,這個(gè)點(diǎn)往往是有狀態(tài)函數(shù)和玩法。那么我們復(fù)用的粒度基本就是圍繞這個(gè)展開的。

一場(chǎng)活動(dòng)原封不動(dòng)的直接復(fù)用幾乎是不可能的,至少得換個(gè)皮吧,玩法復(fù)用機(jī)會(huì)明顯大了很多,比如說(shuō)抽獎(jiǎng)、任務(wù)、簽到等玩法復(fù)用,組成玩法的有狀態(tài)函數(shù)復(fù)用機(jī)會(huì)就更大了,比如說(shuō)機(jī)會(huì)、代幣賬務(wù)、庫(kù)存、計(jì)數(shù)等等,再就是無(wú)狀態(tài)函數(shù),最后拆解到庫(kù)函數(shù),所有編程行為都是在這之上發(fā)生的,幾乎完全復(fù)用。

3.5.2 創(chuàng)新限制問(wèn)題

衡量出成本節(jié)省之后,我們不能單單按照這個(gè)進(jìn)行執(zhí)行,我們應(yīng)該加入整個(gè)復(fù)用方案對(duì)于活動(dòng)創(chuàng)新上的限制的影響。顆粒度越小對(duì)于創(chuàng)新時(shí)的限制是越小的,所以盡可能圍繞最優(yōu)的復(fù)用粒度偏向小粒度。

就實(shí)踐而言,顆粒度到達(dá)有狀態(tài)功能函數(shù)之后,就基本沒(méi)啥限制了,所以最佳的復(fù)用粒度往往就是這個(gè)。

3.5.2 善用組合拳

前面一直在提創(chuàng)新的重要性,我們要做的是對(duì)于創(chuàng)新的提效。但是一場(chǎng)活動(dòng)完完全全是創(chuàng)新玩法幾乎是不可能,甚至完全全新的玩法對(duì)于用戶并沒(méi)有那么友好,通常是一個(gè)創(chuàng)新玩法帶幾個(gè)習(xí)慣玩法,這樣才能做到讓活動(dòng)既陌生又熟悉,用戶的體驗(yàn)才是最佳的。業(yè)界也基本都是這么實(shí)踐的。

同時(shí)固有玩法的沉淀一樣重要,這就要求我們的系統(tǒng)幾個(gè)方向都得做,快速的玩法復(fù)用、快速的玩法變形、快速的創(chuàng)新玩法建設(shè)。

3.6 看下整體的邏輯視圖

4. 一點(diǎn)啟示

4.1 兩萬(wàn)個(gè)配置問(wèn)題

經(jīng)常我們的會(huì)為了邏輯上的靈活性,增加很多的配置,曾經(jīng)見(jiàn)過(guò)一些相對(duì)極端的代碼,100行代碼里10個(gè)配置項(xiàng),如果發(fā)展到這樣就沒(méi)有必要了。

零散配置維護(hù)帶來(lái)的成本遠(yuǎn)比靈活性帶來(lái)的收益要大,ROI完全打不正。

這時(shí)候不如直接用整塊代碼的“規(guī)則引擎”解決問(wèn)題,一塊代碼完成整體的決策,每次改動(dòng)時(shí)還可以兼顧到整體的決策邏輯。

4.2 關(guān)于變化的收斂

我們?cè)谶M(jìn)行代碼編寫時(shí),如果想讓代碼穩(wěn)定可維護(hù)(架構(gòu)的可擴(kuò)展性、靈活度方面),最重要的就是通過(guò)各類變化的收斂,讓影響范圍可控,讓整個(gè)的架構(gòu)變得清晰,讓整個(gè)代碼結(jié)構(gòu)變得清晰。

而變化可以大致分為這么幾類:功能繁多&耦合過(guò)重導(dǎo)致?tīng)恳话l(fā)動(dòng)全身、易變的串聯(lián)關(guān)系、易變的組合關(guān)系、易變的單元邏輯、新增的單元邏輯。

導(dǎo)致我們修改代碼的都是業(yè)務(wù)功能上的變化,可以從業(yè)務(wù)功能上進(jìn)行探索,最佳的實(shí)踐是:“化整為零,分而治之”,對(duì)于易變單元 “切分易變邏輯,使用中間件收斂和控制變化”,具體來(lái)看

  • 對(duì)于功能繁多&耦合過(guò)重導(dǎo)致?tīng)恳话l(fā)動(dòng)全身,我們可以在最上層做處理,拆分獨(dú)立的服務(wù)(比如按照領(lǐng)域拆分),將各域的變化收斂到域內(nèi),不影響整體系統(tǒng)的變更。

  • 對(duì)于易變的串聯(lián)關(guān)系,可以借鑒觀察者模式,實(shí)現(xiàn)發(fā)布訂閱等能力,完成整體的邏輯串聯(lián)(落地形式可以是 基于總線的同步觸發(fā)機(jī)制、又或者基于消息的異步觸發(fā)機(jī)制),而不是硬編碼來(lái)完成邏輯的串聯(lián)。

  • 對(duì)于易變的組合關(guān)系,可以采用本文所說(shuō)的這種模式,快速拼接現(xiàn)有的功能組成對(duì)外服務(wù),而不是次次硬編碼。

  • 對(duì)于新增單元邏輯,我們可以使用策略模式,完成單元邏輯的收斂,把新增的成本給解決掉。

  • 對(duì)于易變的單元邏輯,通常是由于業(yè)務(wù)邏輯多變引發(fā)的,最佳的就是拆分業(yè)務(wù)決策邏輯、代碼決策邏輯,然后把代碼抽象為執(zhí)行模版,復(fù)雜的業(yè)務(wù)計(jì)算交給規(guī)則引擎。比如金額計(jì)算、過(guò)濾規(guī)則等等,還比如策略模式中的計(jì)算策略。

之前描述的,事件總線-流程編排、交互總線、規(guī)則引擎、通用獎(jiǎng)勵(lì)服務(wù)、代幣賬務(wù)等,都是按照這個(gè)思路實(shí)踐的,有興趣可以看下。

4.3 創(chuàng)新的吞吐量 – 題外話

說(shuō)句題外話,從流程上來(lái)看,從idea提出到完整性夠鍵、再到prd產(chǎn)出、研發(fā)開發(fā)、測(cè)試、線上運(yùn)行,這幾個(gè)階段是否可并性,常規(guī)來(lái)說(shuō)是不可行的,prd未產(chǎn)出細(xì)節(jié)可能會(huì)出現(xiàn)大量的變動(dòng),導(dǎo)致研發(fā)返工,研發(fā)階段測(cè)試提前介入,會(huì)導(dǎo)致測(cè)試工作重復(fù)等。但是在成本極小的情況,這種模式是有落地可能性的,尤其是創(chuàng)新場(chǎng)景,直接研發(fā)寫個(gè)demo上去也不是不行。

一定程度的并發(fā),雖然整體工作量稍微增加,但是因?yàn)榕牌谧兌?,整體的吞吐量是上升了的。

4.4 性能的優(yōu)化

整個(gè)落地過(guò)程碰到了許多問(wèn)題,允許我感慨下,代碼都是人寫的,所有的軟件層面的引發(fā)的性能問(wèn)題,都是可解的,只不過(guò)選擇性能時(shí)我們需要犧牲一些東西,比如Groovy的動(dòng)態(tài)性、比如空間換時(shí)間等等。

考量性能最關(guān)鍵的指標(biāo)就是,有效吞吐量、響應(yīng)時(shí)間,基于這兩個(gè)做綜合判斷,往往能達(dá)到性能優(yōu)化的最佳狀態(tài)。

不要一堆問(wèn)題混起來(lái)考量,分成單元問(wèn)題逐個(gè)擊破,就簡(jiǎn)單很多了。

有興趣可以讀一下架構(gòu)視角的性能優(yōu)化

4.5 易用性和靈活性

每次做中間件或者工具設(shè)計(jì)時(shí)都會(huì)發(fā)現(xiàn),易用性和靈活性天然是沖突的,易用和簡(jiǎn)單基本可以劃等號(hào),但是簡(jiǎn)單基本意味著變化可能性小,靈活性好不了。

需要注意的是,易用性提升前期,比如新增一個(gè)功能能夠讓整體更好用,同靈活性并沒(méi)有什么沖突。

4.6 架構(gòu)的廣度和深度

日常擴(kuò)大技術(shù)廣度,使用時(shí)提升技術(shù)深度,是最佳的實(shí)踐啦,有足夠的技術(shù)視野才能做出優(yōu)秀的架構(gòu),有足夠的技術(shù)深度還能保證架構(gòu)的有效落地。

5. 寫在最后

本文描述了創(chuàng)新試錯(cuò)下的多變開發(fā)模式,我們可以通過(guò)何種方式,提升面對(duì)變化時(shí)的效率,并對(duì)于理想的方式進(jìn)行了技術(shù)實(shí)踐上的落地,并且切實(shí)解決該場(chǎng)景下的問(wèn)題。

其中技術(shù)實(shí)現(xiàn),是以主流開發(fā)語(yǔ)言Java 作為基準(zhǔn)的,如果你們是其他技術(shù)棧,可能實(shí)現(xiàn)細(xì)節(jié)不同,但是整體的思路肯定是相近的。

對(duì)于這個(gè)思路都是類似的實(shí)踐模式,相信做過(guò)Faas或者Serverless的朋友都有相同的感觸吧,能根據(jù)場(chǎng)景,把思路落地才是關(guān)鍵。

最后從整體的思考、設(shè)計(jì)、落地過(guò)程中總結(jié)了一點(diǎn)經(jīng)驗(yàn),也分享給了大家,如果有更好的思路,可以一起探討。

新的思路、新的技術(shù)總能帶來(lái)一些較多的收益,帶也會(huì)帶來(lái)一些麻煩,完整考慮收益,因地制宜才好。

本文使用 文章同步助手 同步


就創(chuàng)新試錯(cuò)聊聊Serverless + Faas架構(gòu),贈(zèng)送Groovy高性能規(guī)則引擎實(shí)踐的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
定远县| 南部县| 砀山县| 桐乡市| 乌拉特后旗| 东至县| 蕉岭县| 吉安市| 赤城县| 香格里拉县| 青田县| 陆河县| 南木林县| 拜城县| 太仆寺旗| 兴业县| 滨海县| 崇文区| 开化县| 荔波县| 凤山市| 夏邑县| 宜川县| 拉孜县| 昭觉县| 赣榆县| 深泽县| 北碚区| 武穴市| 长寿区| 噶尔县| 鄱阳县| 获嘉县| 民和| 阿尔山市| 阳春市| 高陵县| 松阳县| 云和县| 涞水县| 简阳市|