日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

文件服务器冷热数据划分,游戏服务器冷热数据分离方案

發布時間:2024/8/5 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 文件服务器冷热数据划分,游戏服务器冷热数据分离方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

冷熱數據分離

當前場景:

gamserver啟動時,會將所有數據加載到內存中,提高讀取數據的性能。但是有很多數據很可能是不常用甚至再也用不到的數據,將這些數據加載到內存中需要占用更多的內存,極大的浪費了內存的使用。

目標:

對冷熱數據進行分離,減少非必要數據對內存的占用,節約內存資源。

主要工作:

數據監控

冷熱數據識別

數據遷移

1.數據監控:

監控與統計數據的使用,為冷熱數據識別服務

監控數據讀取的命中率和數據存儲的百分比

統計每次數據庫讀取和寫入的命中,定期收集數據讀取的命中率和數據存儲比例,以便更加直觀了解使用冷熱數據分離后,節約多少內存,包括百分比和大小,數據讀取的命中率,為以后優化冷熱數據識別算法提供對比數據。

2.冷數據與熱數據:

定義冷數據與熱數據

按玩家區分/按數據的引用區分

目前主要用的還是LRU算法來識別冷熱數據,知網上有看到基于溫度模型的緩存替換策略TCR(Temperature Cache Replacement)論文,可能會有更高的緩存命中率。

或者我們也可根據玩家活躍行為來定義冷熱數據,但是非活躍玩家的數據也可能會被讀取,所以效果不能保證。

3.數據遷移:

主要是對冷熱數據的數據遷移處理。

冷數據處理:

冷數據壓縮/冷數據逐出

大部分都是采用冷數據逐出的方法,把冷數據放到存儲系統中的低性能層級,節約高性能存儲空間。

也有少部分采用冷數據壓縮的方法,把冷數據壓縮存儲,還是放在內存中,等需要用的時候解壓就行,可以節約一點內存,并且讀取冷數據時不需要等待IO。具體壓縮效率需要試驗,相關文檔也比較少,網上看到的基本上是基于gzip壓縮數據。但是我覺得對于我們的架構模型還蠻適用,因為我們的全局唯一鎖的架構,當讀取冷數據時,需要等待IO,這時候釋放鎖和不釋放鎖都比較尷尬。

LRU:

LRU全稱是Least Recently Used,即最近最久未使用的意思。

LRU算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那么在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。

LRU的變形算法:

LRU-K

LRU-K中的K代表最近使用的次數,因此LRU可以認為是LRU-1。LRU-K的主要目的是為了解決LRU算法“緩存污染”的問題,其核心思想是將“最近使用過1次”的判斷標準擴展為“最近使用過K次”。

相比LRU,LRU-K需要多維護一個隊列,用于記錄所有緩存數據被訪問的歷史。只有當數據的訪問次數達到K次的時候,才將數據放入緩存。當需要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據。

two queue

Two queues(以下使用2Q代替)算法類似于LRU-2,不同點在于2Q將LRU-2算法中的訪問歷史隊列(注意這不是緩存數據的)改為一個FIFO緩存隊列,即:2Q算法有兩個緩存隊列,一個是FIFO隊列,一個是LRU隊列。當數據第一次訪問時,2Q算法將數據緩存在FIFO隊列里面,當數據第二次被訪問時,則將數據從FIFO隊列移到LRU隊列里面,兩個隊列各自按照自己的方法淘汰數據。

Multi Queue(MQ)

MQ算法根據訪問頻率將數據劃分為多個隊列,不同的隊列具有不同的訪問優先級,其核心思想是:優先緩存訪問次數多的數據。詳細的算法結構圖如下,Q0,Q1....Qk代表不同的優先級隊列,Q-history代表從緩存中淘汰數據,但記錄了數據的索引和引用次數的隊列。

其他

LRU算法有很多變種,innoDB也是采用LRU算法來提高緩存命中率,innoDB的LRU把表分為兩個部分,old和yount,中間用modpoint隔開,modpoint值可以自己設置,默認值為37,距離表尾端37%的位置。數據第一次讀區的時候會被放在midpoint的位置,這個位置是old表的頭部,但是如果再次被讀取,下次就會直接放在young表的頭部。另外一個數據頁里面可能會有多條記錄,在做全表掃描的時候,一個數據頁可能會一瞬間被訪問多次,這時候可能剛放入列表的時候就再次被訪問,導致直接挪到young區頭部但是其實數據只訪問過一次,這時候有個innodb_old_blocks_time的值來控制,innodb_old_blocks_time設置一個時間,當數據第一次放入列表中后,只有經過一段時間后再次讀取才能觸發把數據移動到young表頭部的行為。

go語言Demo實現

LRUCache.go

package main

import (

"container/list"

"sync"

"time"

)

type LRUCache struct {

list *list.List

cacheMap map[NodeKey]*list.Element

}

var RWLock sync.RWMutex

//數據在內存中存活時間 單位秒

const DATA_LIVE_TIME = 5

type Node struct {

key NodeKey

time int64

}

func NewLRUCache() (*LRUCache) {

return &LRUCache{

list: list.New(),

cacheMap: make(map[NodeKey]*list.Element)}

}

//返回LRU的存儲數據長度

func (lru *LRUCache) Size() int {

return lru.list.Len()

}

func (lru *LRUCache) Set(key NodeKey) {

RWLock.Lock()

defer RWLock.Unlock()

//節點已存在

if element, ok := lru.cacheMap[key]; ok {

lru.list.MoveToFront(element)

element.Value.(*Node).time = time.Now().Unix()

} else {

// 節點不存在,生成新節點

newElement := lru.list.PushFront(&Node{key, time.Now().Unix()})

lru.cacheMap[key] = newElement

}

lru.CheckList()

}

// 獲取數據是否在LRU中,如果存在則更新時間

func (lru *LRUCache) Get(key NodeKey) bool {

/**

由于在RLock中,其他線程也能讀數據,并且WLock需要等待才能將數據移除,所以此處使用RLock就足夠

如果修改時間時用WLock,需要先釋放RLock,反而可能出現釋放RLock后被其他線程搶占WLock的情況

*/

RWLock.RLock()

defer RWLock.RUnlock()

if element, ok := lru.cacheMap[key]; ok {

lru.list.MoveToFront(element)

element.Value.(*Node).time = time.Now().Unix()

return true

} else {

return false

}

}

func (lru *LRUCache) Remove(key NodeKey) {

if element, ok := lru.cacheMap[key]; ok {

delete(lru.cacheMap, key)

lru.list.Remove(element)

//TODO:將數據在內存中移除

println("將數據從內存中移除:", key)

}

}

// 從列表尾端開始檢查數據,將過期的冷數據從內存移除

func (lru *LRUCache) CheckList() {

dList := lru.list

if dList.Len() == 0 {

return

}

node := dList.Back()

for {

if CheckData(node.Value.(*Node)) {

break

} else {

lru.Remove(node.Value.(*Node).key)

if node.Prev() != nil {

node = node.Prev()

} else {

break

}

}

}

}

// 判斷數據是否應該存留, 該存留返回true

func CheckData(node *Node) bool {

if node.time < time.Now().Unix()-DATA_LIVE_TIME {

return false

} else {

return true

}

}

DataCenter.go

package main

import "sync/atomic"

type CacheDataCenter struct {

readCount int64 //總讀取數據次數

hitCount int64 //熱數據讀取命中次數

lruCache ILRU

}

type NodeKey int64

type ILRU interface {

Set(key NodeKey)

Get(key NodeKey) (bool)

Remove(key NodeKey)

Size() int

}

func NewCacheDataCenter() *CacheDataCenter {

return &CacheDataCenter{

readCount: 0,

hitCount: 0,

lruCache: NewLRUCache(),

}

}

func (center *CacheDataCenter) AddReadNum(isHit bool) {

atomic.AddInt64(&center.readCount, 1)

if isHit {

atomic.AddInt64(&center.hitCount, 1)

}

}

// 總數據量

func (center *CacheDataCenter) GetTotalCount() int {

return 0

}

// 內存中數據量

func (center *CacheDataCenter) GetCacheCount() int {

return center.lruCache.Size()

}

func (center *CacheDataCenter) GetData(key NodeKey) {

if center.lruCache.Get(key) {

center.AddReadNum(true)

//TODO: 從內存中取數據

println("從內存讀取數據:", key)

} else {

center.AddReadNum(false)

center.lruCache.Set(key)

//TODO: 操作數據庫取數據

println("從數據庫讀取數據:", key)

}

}

main.go

package main

import (

"math/rand"

"sync"

"time"

)

func main() {

cacheDataCenter := NewCacheDataCenter()

wg := sync.WaitGroup{}

wg.Add(3)

for i := 0; i < 3; i++ {

go func() {

readData(cacheDataCenter)

wg.Done()

}()

}

wg.Wait()

println("test結束,總共讀取次數:", cacheDataCenter.readCount, "命中次數:", cacheDataCenter.hitCount, "內存中最終剩余數據:", cacheDataCenter.GetCacheCount())

}

func readData(center *CacheDataCenter) {

for i := 0; i < 10; i++ {

time.Sleep(1 * time.Second)

r := rand.Int63n(30)

center.GetData(NodeKey(r))

}

}

demo中三個線程每一秒會隨機生成一個數據,并且嘗試在LRUCache中讀取,假如未命中,則插入,假如命中,則更新時間。每次寫數據的時候,會執行一次Check()來檢查舊數據,檢查的時候從底往上檢查,如果數據為冷數據則移除,直到檢查到熱數據為止。

總結

以上是生活随笔為你收集整理的文件服务器冷热数据划分,游戏服务器冷热数据分离方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。