偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

業(yè)務(wù)腳本:為什么說可編程訂閱式緩存服務(wù)更有用?

存儲 數(shù)據(jù)管理
在那些讀多寫多的服務(wù)當(dāng)中,實(shí)時交互類的服務(wù)數(shù)量相當(dāng)多,并且這類服務(wù)對于數(shù)據(jù)的實(shí)時性有著很高的要求。若是采用集中型緩存的方式,往往很難滿足此類服務(wù)所提出的各項(xiàng)需求。

我們已經(jīng)習(xí)慣了使用緩存集群對數(shù)據(jù)做緩存。然而,這種常見的內(nèi)存緩存服務(wù)存在諸多不便之處。首先,集群會獨(dú)占大量的內(nèi)存。這意味著在資源有限的情況下,可能會對其他系統(tǒng)資源的分配造成壓力,影響整體系統(tǒng)的性能和穩(wěn)定性。

其次,不能原子修改緩存的某一個字段。在一些對數(shù)據(jù)一致性要求較高的場景中,這可能會引發(fā)數(shù)據(jù)不一致的問題,從而影響業(yè)務(wù)的正常運(yùn)行。

再者,多次通訊有網(wǎng)絡(luò)損耗。尤其是在頻繁進(jìn)行數(shù)據(jù)交互的情況下,網(wǎng)絡(luò)損耗可能會導(dǎo)致數(shù)據(jù)傳輸延遲增加,降低系統(tǒng)的響應(yīng)速度。

另外,很多時候我們獲取數(shù)據(jù)并不需要全部字段,但因?yàn)榫彺娌恢С趾Y選,在批量獲取數(shù)據(jù)的場景下性能就會下降很多。這種情況在數(shù)據(jù)量較大時尤為明顯,會嚴(yán)重影響系統(tǒng)的效率。

而這些問題在讀多寫多的場景下,會更加突出。那么,有什么方式能夠解決這些問題呢?

緩存即服務(wù)

可編程訂閱式緩存服務(wù)意味著我們能夠自行實(shí)現(xiàn)一個數(shù)據(jù)緩存服務(wù),進(jìn)而直接提供給業(yè)務(wù)服務(wù)使用。這種實(shí)現(xiàn)方式具有獨(dú)特的優(yōu)勢,它可以依據(jù)業(yè)務(wù)的具體需求,主動地對數(shù)據(jù)進(jìn)行緩存,并且能夠提供一些數(shù)據(jù)整理以及計(jì)算的服務(wù)。

雖然自行實(shí)現(xiàn)這樣的數(shù)據(jù)緩存服務(wù)過程較為繁瑣,然而其帶來的優(yōu)勢卻是顯著的。除了吞吐能力能夠得到提升之外,我們還可以實(shí)現(xiàn)眾多有趣的定制功能。比如,我們可以根據(jù)業(yè)務(wù)的特定邏輯對數(shù)據(jù)進(jìn)行個性化的整理和優(yōu)化,使其更符合業(yè)務(wù)的使用場景。

同時,它還具備更好的計(jì)算能力。這使得我們在處理數(shù)據(jù)時,能夠更加高效地進(jìn)行各種復(fù)雜的計(jì)算操作,為業(yè)務(wù)提供更強(qiáng)大的數(shù)據(jù)支持。

甚至,它可以讓我們的緩存直接對外提供基礎(chǔ)數(shù)據(jù)的查詢服務(wù)。這樣一來,業(yè)務(wù)在獲取基礎(chǔ)數(shù)據(jù)時無需再通過繁瑣的流程從其他數(shù)據(jù)源獲取,大大提高了數(shù)據(jù)獲取的效率和便捷性,進(jìn)一步提升了整個業(yè)務(wù)系統(tǒng)的性能和靈活性。

圖片圖片

上圖展示了一個自實(shí)現(xiàn)的緩存功能結(jié)構(gòu)。不得不說,這種緩存的性能和效果更為出色,究其原因在于它對數(shù)據(jù)的處理方式與傳統(tǒng)模式大相徑庭。

在傳統(tǒng)模式下,緩存服務(wù)并不會對數(shù)據(jù)進(jìn)行任何加工處理,所保存的是系列化的字符串。在這種情況下,大部分的數(shù)據(jù)無法直接進(jìn)行修改。當(dāng)我們利用這種緩存對外提供服務(wù)時,業(yè)務(wù)服務(wù)不得不將所有數(shù)據(jù)取出至本地內(nèi)存,隨后進(jìn)行遍歷加工才能投入使用。

然而,可編程緩存卻能夠?qū)?shù)據(jù)結(jié)構(gòu)化地存儲在 map 中。相較于傳統(tǒng)模式下序列化的字符串,它更為節(jié)省內(nèi)存。更為便捷的是,我們的服務(wù)無需再從其他服務(wù)獲取數(shù)據(jù)來進(jìn)行計(jì)算,如此便能夠節(jié)省大量網(wǎng)絡(luò)交互所耗費(fèi)的時間,因而非常適合應(yīng)用于對實(shí)時要求極高的場景之中。

倘若我們的熱數(shù)據(jù)量頗為龐大,那么可以結(jié)合 RocksDB 等嵌入式引擎,憑借有限的內(nèi)存來為大量數(shù)據(jù)提供服務(wù)。除了常規(guī)的數(shù)據(jù)緩存服務(wù)之外,可編程緩存還具備諸多強(qiáng)大的功能,比如支持對緩存數(shù)據(jù)的篩選過濾、統(tǒng)計(jì)計(jì)算、查詢、分片以及數(shù)據(jù)拼合。

在此,關(guān)于查詢服務(wù),我要補(bǔ)充說明一下。對于對外的服務(wù),建議通過類似 Redis 的簡單文本協(xié)議來提供服務(wù),因?yàn)檫@樣相較于 HTTP 協(xié)議,其性能會更為優(yōu)越。

Lua 腳本引擎

雖然緩存提供業(yè)務(wù)服務(wù)能夠提升業(yè)務(wù)的靈活度,然而這種方式也存在諸多缺點(diǎn)。其中最大的缺點(diǎn)便是業(yè)務(wù)修改后,我們需要重啟服務(wù)才能夠更新我們的邏輯。由于內(nèi)存中存儲了大量的數(shù)據(jù),每重啟一次,數(shù)據(jù)就需要經(jīng)歷繁瑣的預(yù)熱過程,同步代價極為高昂。

為此,我們需要對設(shè)計(jì)進(jìn)行再次升級。在這種情況下,lua 腳本引擎不失為一個上佳的選擇。lua 是一種小巧的嵌入式腳本語言,借助它能夠?qū)崿F(xiàn)一個高性能、可熱更新的腳本服務(wù),進(jìn)而與嵌入的服務(wù)進(jìn)行高效靈活的互動。

我繪制了一張示意圖,用以描述如何通過 lua 腳本來具體實(shí)現(xiàn)可編程緩存服務(wù):

圖片圖片

上圖所示,可以看到我們提供了 Kafka 消費(fèi)、周期任務(wù)管理、內(nèi)存緩存、多種數(shù)據(jù)格式支持、多種數(shù)據(jù)驅(qū)動適配這些服務(wù)。不僅僅如此,為了減少由于邏輯變更導(dǎo)致的服務(wù)經(jīng)常重啟的情況,我們還以性能損耗為代價,在緩存服務(wù)里嵌入了 lua 腳本引擎,借此實(shí)現(xiàn)動態(tài)更新業(yè)務(wù)的邏輯。lua 引擎使用起來很方便,我們結(jié)合后面這個實(shí)現(xiàn)例子看一看,這是一個 Go 語言寫的嵌入 lua 實(shí)現(xiàn),代碼如下所示:

package main


import "github.com/yuin/gopher-lua"


// VarChange 用于被lua調(diào)用的函數(shù)
func VarChange(L *lua.LState) int {
   lv := L.ToInt(1)            //獲取調(diào)用函數(shù)的第一個參數(shù),并且轉(zhuǎn)成int
   L.Push(lua.LNumber(lv * 2)) //將參數(shù)內(nèi)容直接x2,并返回結(jié)果給lua
return 1                    //返回結(jié)果參數(shù)個數(shù)
}


func main() {
   L := lua.NewState() //新lua線程
defer L.Close() //程序執(zhí)行完畢自動回收


// 注冊lua腳本可調(diào)用函數(shù)
// 在lua內(nèi)調(diào)用varChange函數(shù)會調(diào)用這里注冊的Go函數(shù) VarChange
   L.SetGlobal("varChange", L.NewFunction(VarChange))


//直接加載lua腳本
//腳本內(nèi)容為:
// print "hello world"
// print(varChange(20)) # lua中調(diào)用go聲明的函數(shù)
if err := L.DoFile("hello.lua"); err != nil {
panic(err)
   }


// 或者直接執(zhí)行string內(nèi)容
if err := L.DoString(`print("hello")`); err != nil {
panic(err)
   }
}


// 執(zhí)行后輸出結(jié)果:
//hello world
//40
//hello

從這個例子里我們可以看出,lua 引擎是可以直接執(zhí)行 lua 腳本的,而 lua 腳本可以和 Golang 所有注冊的函數(shù)相互調(diào)用,并且可以相互傳遞交換變量。回想一下,我們做的是數(shù)據(jù)緩存服務(wù),所以需要讓 lua 能夠獲取修改服務(wù)內(nèi)的緩存數(shù)據(jù),那么,lua 是如何和嵌入的語言交換數(shù)據(jù)的呢?我們來看看兩者相互調(diào)用交換的例子:

package main


import (
"fmt"
"github.com/yuin/gopher-lua"
)


func main() {
   L := lua.NewState()
defer L.Close()
//加載腳本
   err := L.DoFile("vardouble.lua")
if err != nil {
panic(err)
   }
// 調(diào)用lua腳本內(nèi)函數(shù)
   err = L.CallByParam(lua.P{
      Fn:      L.GetGlobal("varDouble"), //指定要調(diào)用的函數(shù)名
      NRet:    1,                        // 指定返回值數(shù)量
      Protect: true,                     // 錯誤返回error
   }, lua.LNumber(15)) //支持多個參數(shù)
if err != nil {
panic(err)
   }
//獲取返回結(jié)果
   ret := L.Get(-1)
//清理下,等待下次用
   L.Pop(1)


//結(jié)果轉(zhuǎn)下類型,方便輸出
   res, ok := ret.(lua.LNumber)
if !ok {
panic("unexpected result")
   }
   fmt.Println(res.String())
}


// 輸出結(jié)果:
// 30

其中 vardouble.lua 內(nèi)容為:

function varDouble(n)
return n * 2
end

過這個方式,lua 和 Golang 就可以相互交換數(shù)據(jù)和相互調(diào)用。對于這種緩存服務(wù)普遍要求性能很好,這時我們可以統(tǒng)一管理加載過 lua 的腳本及 LState 腳本對象的實(shí)例對象池,這樣會更加方便,不用每調(diào)用一次 lua 就加載一次腳本,方便獲取和使用多線程、多協(xié)程。

Lua 腳本統(tǒng)一管理

從前面所做的講解當(dāng)中,我們能夠察覺到,在實(shí)際進(jìn)行使用的時候,lua 會有許多實(shí)例在內(nèi)存之中運(yùn)行著。

為了能夠?qū)@些實(shí)例實(shí)現(xiàn)更為妥善的管理,并且進(jìn)一步提升效率,我們最為理想的做法便是運(yùn)用一個專門的腳本管理系統(tǒng),來對所有 lua 的運(yùn)行實(shí)例展開管理。通過這樣的操作,便可以達(dá)成對腳本進(jìn)行統(tǒng)一更新、實(shí)現(xiàn)編譯緩存、完成資源調(diào)度以及對單例加以控制等一系列目標(biāo)。

lua 腳本其自身特性是單線程的,不過它的體量非常輕,單個實(shí)例所造成的內(nèi)存損耗大概僅為 144kb 左右。在一些服務(wù)當(dāng)中,平常運(yùn)行的時候甚至能夠同時開啟成百上千個 lua 實(shí)例。

倘若要提高服務(wù)的并行處理能力,我們可以選擇啟動多個協(xié)程,讓每一個協(xié)程都能夠獨(dú)立去運(yùn)行一個 lua 線程。

正是出于這樣的需求,gopher-lua 庫為我們提供了一種類似于線程池的實(shí)現(xiàn)方式。借助于這種方式,我們就不再需要頻繁地去創(chuàng)建以及關(guān)閉 lua 了,其官方所給出的具體例子如下:

//保存lua的LState的池子
type lStatePool struct {
    m     sync.Mutex
    saved []*lua.LState
}
// 獲取一個LState
func (pl *lStatePool) Get() *lua.LState {
    pl.m.Lock()
defer pl.m.Unlock()
    n := len(pl.saved)
if n == 0 {
return pl.New()
    }
    x := pl.saved[n-1]
    pl.saved = pl.saved[0 : n-1]
return x
}


//新建一個LState
func (pl *lStatePool) New() *lua.LState {
    L := lua.NewState()
// setting the L up here.
// load scripts, set global variables, share channels, etc...
//在這里我們可以做一些初始化
return L
}


//把Lstate對象放回到池中,方便下次使用
func (pl *lStatePool) Put(L *lua.LState) {
    pl.m.Lock()
defer pl.m.Unlock()
    pl.saved = append(pl.saved, L)
}


//釋放所有句柄
func (pl *lStatePool) Shutdown() {
for _, L := range pl.saved {
        L.Close()
    }
}
// Global LState pool
var luaPool = &lStatePool{
    saved: make([]*lua.LState, 0, 4),
}


//協(xié)程內(nèi)運(yùn)行的任務(wù)
func MyWorker() {
//通過pool獲取一個LState
   L := luaPool.Get()
//任務(wù)執(zhí)行完畢后,將LState放回pool
defer luaPool.Put(L)
// 這里可以用LState變量運(yùn)行各種lua腳本任務(wù)
//例如 調(diào)用之前例子中的的varDouble函數(shù)
   err = L.CallByParam(lua.P{
      Fn:      L.GetGlobal("varDouble"), //指定要調(diào)用的函數(shù)名
      NRet:    1,                        // 指定返回值數(shù)量
      Protect: true,                     // 錯誤返回error
   }, lua.LNumber(15)) //這里支持多個參數(shù)
if err != nil {
panic(err) //僅供演示用,實(shí)際生產(chǎn)不推薦用panic
   }
}
func main() {
defer luaPool.Shutdown()
go MyWorker() // 啟動一個協(xié)程
go MyWorker() // 啟動另外一個協(xié)程
/* etc... */
}

通過這個方式我們可以預(yù)先創(chuàng)建一批 LState,讓它們加載好所有需要的 lua 腳本,當(dāng)我們執(zhí)行 lua 腳本時直接調(diào)用它們,即可對外服務(wù),提高我們的資源復(fù)用率。

變量的交互

實(shí)際上,我們的數(shù)據(jù)既能夠存儲在 lua 當(dāng)中,也可以存放在 Go 里面,然后通過相互調(diào)用的方式來獲取對方所存儲的數(shù)據(jù)。就我個人而言,更習(xí)慣把數(shù)據(jù)放在 Go 中進(jìn)行封裝處理,之后供 lua 來調(diào)用。之所以會這樣選擇,主要是因?yàn)檫@種做法相對更加規(guī)范,在管理方面也會比較便捷,畢竟腳本在運(yùn)行過程中是會存在一定損耗的。

前面也曾提到過,我們會采用 struct 和 map 相互組合的方式來對一些數(shù)據(jù)進(jìn)行處理,進(jìn)而對外提供數(shù)據(jù)服務(wù)。那么,lua 和 Golang 之間究竟是如何實(shí)現(xiàn)像 struct 這一類數(shù)據(jù)的交換呢?

在這里,我選取了官方所提供的例子,并且還額外添加了大量的注釋內(nèi)容,其目的就是為了能夠更好地幫助大家去理解這兩者之間的數(shù)據(jù)交互過程。

// go用于交換的 struct
type Person struct {
    Name string
}


//為這個類型定義個類型名稱
const luaPersonTypeName = "person"


// 在LState對象中,聲明這種類型,這個只會在初始化LState時執(zhí)行一次
// Registers my person type to given L.
func registerPersonType(L *lua.LState) {
//在LState中聲明這個類型
    mt := L.NewTypeMetatable(luaPersonTypeName)
//指定 person 對應(yīng) 類型type 標(biāo)識
//這樣 person在lua內(nèi)就像一個 類聲明
    L.SetGlobal("person", mt)
// static attributes
// 在lua中定義person的靜態(tài)方法
// 這句聲明后 lua中調(diào)用person.new即可調(diào)用go的newPerson方法
    L.SetField(mt, "new", L.NewFunction(newPerson))
// person new后創(chuàng)建的實(shí)例,在lua中是table類型,你可以把table理解為lua內(nèi)的對象
// 下面這句主要是給 table定義一組methods方法,可以在lua中調(diào)用
// personMethods是個map[string]LGFunction 
// 用來告訴lua,method和go函數(shù)的對應(yīng)關(guān)系
    L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
}
// person 實(shí)例對象的所有method
var personMethods = map[string]lua.LGFunction{
"name": personGetSetName,
}
// Constructor
// lua內(nèi)調(diào)用person.new時,會觸發(fā)這個go函數(shù)
func newPerson(L *lua.LState) int {
//初始化go struct 對象 并設(shè)置name為 1
    person := &Person{L.CheckString(1)}
// 創(chuàng)建一個lua userdata對象用于傳遞數(shù)據(jù)
// 一般 userdata包裝的都是go的struct,table是lua自己的對象
    ud := L.NewUserData() 
    ud.Value = person //將 go struct 放入對象中
// 設(shè)置這個lua對象類型為 person type
    L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
// 將創(chuàng)建對象返回給lua
    L.Push(ud)
//告訴lua腳本,返回了數(shù)據(jù)個數(shù)
return 1
}
// Checks whether the first lua argument is a *LUserData 
// with *Person and returns this *Person.
func checkPerson(L *lua.LState) *Person {
//檢測第一個參數(shù)是否為其他語言傳遞的userdata
    ud := L.CheckUserData(1)
// 檢測是否轉(zhuǎn)換成功
if v, ok := ud.Value.(*Person); ok {
return v
    }
    L.ArgError(1, "person expected")
return nil
}
// Getter and setter for the Person#Name
func personGetSetName(L *lua.LState) int {
// 檢測第一個棧,如果就只有一個那么就只有修改值參數(shù)
    p := checkPerson(L)
if L.GetTop() == 2 {
//如果棧里面是兩個,那么第二個是修改值參數(shù)
        p.Name = L.CheckString(2)
//代表什么數(shù)據(jù)不返回,只是修改數(shù)據(jù)
return 0
    }
//如果只有一個在棧,那么是獲取name值操作,返回結(jié)果
    L.Push(lua.LString(p.Name))


//告訴會返回一個參數(shù)
return 1
}
func main() {
// 創(chuàng)建一個lua LState
    L := lua.NewState()
defer L.Close()


//初始化 注冊
    registerPersonType(L)
// 執(zhí)行l(wèi)ua腳本
if err := L.DoString(`
        //創(chuàng)建person,并設(shè)置他的名字
        p = person.new("Steven")
        print(p:name()) -- "Steven"
        //修改他的名字
        p:name("Nico")
        print(p:name()) -- "Nico"
    `); err != nil {
panic(err)
    }
}

可以看到,我們通過 lua 腳本引擎就能很方便地完成相互調(diào)用和交換數(shù)據(jù),從而實(shí)現(xiàn)很多實(shí)用的功能,甚至可以用少量數(shù)據(jù)直接寫成 lua 腳本的方式來加載服務(wù)。

緩存預(yù)熱與數(shù)據(jù)來源

在對 lua 有了一定了解之后,接下來我們一起探討一下服務(wù)是如何加載數(shù)據(jù)的。

當(dāng)服務(wù)啟動之時,我們首先要做的就是將數(shù)據(jù)緩存加載到緩存當(dāng)中,以此來完成緩存預(yù)熱的操作。只有在數(shù)據(jù)全部加載完成之后,才會開放對外的 API 端口,從而正式對外提供服務(wù)。

在這個加載數(shù)據(jù)的過程中,如果引入了 lua 腳本的話,那么就能夠在服務(wù)啟動之際,針對不同格式的數(shù)據(jù)開展適配加工的工作。如此一來,數(shù)據(jù)的來源也會變得更加豐富多樣。

通常情況下,常見的數(shù)據(jù)來源是由大數(shù)據(jù)挖掘周期所生成的全量數(shù)據(jù)離線文件。這些文件會通過 NFS 或者 HDFS 進(jìn)行掛載操作,并且會定期進(jìn)行刷新,以便加載最新的文件。這種通過掛載離線文件來獲取數(shù)據(jù)的方式,比較適合那些數(shù)據(jù)量龐大且更新速度較為緩慢的數(shù)據(jù)。不過,它也存在一定的缺點(diǎn),那就是在加載數(shù)據(jù)的時候需要對數(shù)據(jù)進(jìn)行整理工作。要是情況較為復(fù)雜的話,比如對于 800M 大小的數(shù)據(jù),可能就需要花費(fèi) 1 至 10 分鐘的時間才能夠完成加載操作。

除了采用上述這種利用文件獲取數(shù)據(jù)的方式之外,我們還可以在程序啟動之后,通過掃描數(shù)據(jù)表的方式來恢復(fù)數(shù)據(jù)。但是這樣做的話,數(shù)據(jù)庫將會承受一定的壓力,所以建議使用專門的從庫來進(jìn)行此項(xiàng)操作。而且需要注意的是,相較于通過磁盤離線文件獲取數(shù)據(jù)的方式,這種掃描數(shù)據(jù)表的方式其加載速度會更慢一些。

前面所提及的那兩種數(shù)據(jù)加載方式,在速度方面都存在一定的不足。接下來,我們還可以考慮另外一種做法,那就是將 RocksDB 嵌入到進(jìn)程之中。通過這樣的操作,能夠極大幅度地提升我們的數(shù)據(jù)存儲容量,進(jìn)而實(shí)現(xiàn)內(nèi)存與磁盤之間高性能的讀取和寫入操作。不過,這么做也是有代價的,那就是相對而言會使得查詢性能出現(xiàn)一定程度的降低。

關(guān)于 RocksDB 的數(shù)據(jù)獲取,我們可以借助大數(shù)據(jù)來生成符合 RocksDB 格式的數(shù)據(jù)庫文件,然后將其拷貝給我們的服務(wù),以便服務(wù)能夠直接進(jìn)行加載。采用這種方式的好處在于,它可以大幅減少系統(tǒng)在啟動過程中整理以及加載數(shù)據(jù)所耗費(fèi)的時間,從而能夠?qū)崿F(xiàn)更多的數(shù)據(jù)查詢操作。

另外,倘若我們存在對于本地有關(guān)系數(shù)據(jù)進(jìn)行查詢的需求,那么還可以選擇嵌入 SQLite 引擎。通過這個引擎,我們就能夠開展各種各樣的關(guān)系數(shù)據(jù)查詢工作。對于 SQLite 的數(shù)據(jù)生成,同樣也可以利用相關(guān)工具提前完成,生成之后便可以直接提供給我們的服務(wù)使用。但在這里需要特別注意的是,這個數(shù)據(jù)庫的數(shù)據(jù)量最好不要超過 10 萬條,不然的話,很有可能會導(dǎo)致服務(wù)出現(xiàn)卡頓的現(xiàn)象。

最后,在涉及離線文件加載的情況時,我們最好能夠制作一個類似于 CheckSum 的文件。制作這個文件的目的在于,在加載文件之前,可以利用它來檢查文件的完整性。因?yàn)槲覀兯褂玫氖蔷W(wǎng)絡(luò)磁盤,所以不太能確定這個文件是否正在處于拷貝的過程當(dāng)中,這就需要運(yùn)用一些小技巧來確保我們的數(shù)據(jù)完整性。其中,最為簡單粗暴的方式就是,在每次拷貝完畢之后,生成一個與原文件同名的文件,并且在這個新生成的文件內(nèi)部記錄一下它的 CheckSum 值,這樣一來,便方便我們在加載文件之前進(jìn)行校驗(yàn)操作。

離線文件在實(shí)際應(yīng)用當(dāng)中具有一定的優(yōu)勢,它能夠幫助我們快速實(shí)現(xiàn)多個節(jié)點(diǎn)之間的數(shù)據(jù)共享以及數(shù)據(jù)的統(tǒng)一。倘若我們要求多個節(jié)點(diǎn)的數(shù)據(jù)要保持最終的一致性,那么就需要通過離線與同步訂閱相結(jié)合的方式來實(shí)現(xiàn)數(shù)據(jù)的同步操作。

訂閱式數(shù)據(jù)同步及啟動同步

那么,咱們的數(shù)據(jù)究竟是怎樣實(shí)現(xiàn)同步更新的呢?通常而言,我們的數(shù)據(jù)是從多個基礎(chǔ)數(shù)據(jù)服務(wù)獲取而來的。要是希望能夠?qū)崟r同步數(shù)據(jù)所發(fā)生的更改情況,一般會采用訂閱 binlog 的方式,先將變更的相關(guān)信息同步到 Kafka 當(dāng)中。接著,再憑借 Kafka 的分組消費(fèi)功能,去通知那些分布在不同集群里面的緩存。

當(dāng)收到消息變更的服務(wù)接收到相關(guān)通知后,就會觸發(fā) lua 腳本,進(jìn)而依據(jù)腳本對數(shù)據(jù)展開同步更新的操作。通過 lua 腳本的運(yùn)作,我們能夠以觸發(fā)式的方式同步更新其他與之相關(guān)的緩存。

比如說,當(dāng)用戶購買了一個商品的時候,我們就需要同步刷新與之相關(guān)的一些數(shù)據(jù),像是他的積分情況、訂單信息以及消息列表的個數(shù)等等,以此來確保各項(xiàng)數(shù)據(jù)的一致性和實(shí)時性。

周期任務(wù)

在談及任務(wù)管理這個話題時,就不得不著重說一說周期任務(wù)了。周期任務(wù)在通常情況下,主要是被用于對數(shù)據(jù)統(tǒng)計(jì)進(jìn)行刷新的工作。我們只要將周期任務(wù)和 lua 自定義邏輯腳本相互結(jié)合起來加以運(yùn)用,便能夠輕松實(shí)現(xiàn)定期開展統(tǒng)計(jì)的操作,這無疑是給我們的工作帶來了更多的便利條件。

在執(zhí)行定期任務(wù)或者進(jìn)行延遲刷新的這個過程當(dāng)中,較為常見的一種處理方式就是運(yùn)用時間輪來對任務(wù)加以管理。通過采用這種方式,能夠把定時任務(wù)轉(zhuǎn)化為事件觸發(fā)的形式,如此一來,就可以非常便捷地對內(nèi)存當(dāng)中那些有待觸發(fā)的任務(wù)列表進(jìn)行管理了,進(jìn)而能夠?qū)崿F(xiàn)并行處理多個周期任務(wù),也就無需再通過使用 sleep 循環(huán)這種方式去不斷地進(jìn)行查詢操作了。要是您對時間輪的具體實(shí)現(xiàn)方式感興趣的話,可以直接點(diǎn)擊此處進(jìn)行查看哦。

另外,前面也曾提到過,我們所用到的很多數(shù)據(jù)都是借助離線文件來進(jìn)行批量更新的。假如是每一小時就更新一次數(shù)據(jù)的話,那么在這一小時之內(nèi)新更新的數(shù)據(jù)就需要進(jìn)行同步處理。一般而言,處理的方式是這樣的:當(dāng)我們的服務(wù)啟動并加載離線文件的時候,要把這個離線文件生成的時間給保存下來,然后憑借這個保存下來的時間來對數(shù)據(jù)更新隊(duì)列當(dāng)中的消息進(jìn)行過濾操作。等到我們的隊(duì)列任務(wù)進(jìn)度追趕至接近當(dāng)前時間的時候,再開啟對外提供數(shù)據(jù)的服務(wù)。

總結(jié)

在那些讀多寫多的服務(wù)當(dāng)中,實(shí)時交互類的服務(wù)數(shù)量相當(dāng)多,并且這類服務(wù)對于數(shù)據(jù)的實(shí)時性有著很高的要求。若是采用集中型緩存的方式,往往很難滿足此類服務(wù)所提出的各項(xiàng)需求。

基于這樣的情況,在行業(yè)當(dāng)中,多數(shù)會選擇通過服務(wù)自身內(nèi)存中的數(shù)據(jù)來為實(shí)時交互服務(wù)提供支持。然而,這種做法存在一個明顯的弊端,那就是維護(hù)起來特別麻煩,一旦服務(wù)重啟,就必須要對數(shù)據(jù)進(jìn)行恢復(fù)操作。

為了能夠?qū)崿F(xiàn)業(yè)務(wù)邏輯在無需重啟的情況下就可以完成更新,在行業(yè)里通常會采用內(nèi)嵌腳本的熱更新方案。在眾多的腳本引擎當(dāng)中,較為常見且通用的就是 lua 腳本引擎了。lua 是一種非常流行且使用起來極為方便的腳本引擎,在行業(yè)領(lǐng)域中,許多知名的游戲以及各類服務(wù)都借助 lua 來實(shí)現(xiàn)高性能服務(wù)的定制化業(yè)務(wù)功能,像 Nginx、Redis 等就是如此。

當(dāng)我們把 lua 和我們所定制的緩存服務(wù)相互結(jié)合起來的時候,便能夠打造出很多強(qiáng)大的功能,以此來應(yīng)對各種各樣不同的場景需求。

由于 lua 具備十分節(jié)省內(nèi)存的特性,所以我們可以在進(jìn)程當(dāng)中開啟數(shù)量多達(dá)成千上萬的 lua 小線程。甚至可以做到為每一個用戶都配備一個 LState 線程,從而為客戶端提供類似于狀態(tài)機(jī)一樣的服務(wù)。

通過運(yùn)用上述所提到的這些方法,再將 lua 和靜態(tài)語言之間進(jìn)行數(shù)據(jù)交換以及相互調(diào)用,并且配合上我們所實(shí)施的任務(wù)管理以及各種各樣的數(shù)據(jù)驅(qū)動方式,這樣就能夠構(gòu)建出一個幾乎可以應(yīng)對所有情況的萬能緩存服務(wù)了。

在此,推薦大家在一些小型項(xiàng)目當(dāng)中親自去實(shí)踐一下上述的這些內(nèi)容。相信通過這樣的實(shí)踐,會讓大家從一個與以往不同的視角去重新審視那些已經(jīng)習(xí)慣了的服務(wù),如此一來,大家必定會從中獲得更多的收獲。

責(zé)任編輯:武曉燕 來源: 二進(jìn)制跳動
相關(guān)推薦

2015-11-04 13:56:06

SDN可編程性企業(yè)

2019-11-22 09:20:34

編程經(jīng)濟(jì)技術(shù)

2014-03-26 10:49:06

SDN軟件定義網(wǎng)絡(luò)網(wǎng)絡(luò)可編程性

2013-08-06 14:04:46

網(wǎng)絡(luò)

2009-06-19 18:51:13

ibmdwLotus

2013-08-07 09:00:57

軟件定義網(wǎng)絡(luò)SDN

2023-04-04 15:46:16

云計(jì)算

2013-11-26 10:14:15

面向?qū)ο?/a>函數(shù)式

2021-05-31 20:06:57

網(wǎng)元協(xié)議網(wǎng)關(guān)

2012-06-14 10:17:16

TecTile三星

2016-09-09 13:26:07

數(shù)據(jù)編程SDN

2013-05-03 09:49:38

ASICSDN可編程ASIC

2018-02-01 04:02:41

數(shù)據(jù)中心網(wǎng)絡(luò)編程

2014-05-09 10:03:50

編程面試

2012-05-24 10:29:54

編程程序員

2025-04-07 08:30:00

緩存Java開發(fā)

2025-05-27 10:10:00

Java緩存開發(fā)

2022-05-19 14:49:19

Nick網(wǎng)絡(luò)開源社區(qū)專有網(wǎng)絡(luò)

2010-07-07 14:42:17

SQL Server
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號