天天看點

遊戲伺服器架構-資料持久化之資料複制

為了提高遊戲伺服器的吞吐量,玩家資料會在登陸的時候加載到伺服器記憶體之中,在玩家玩遊戲的過程中,操作的都是記憶體中的資料。但是這樣子設計,如果伺服器重新開機時,沒有及時将資料更新到資料庫,資料就會有丢失的風險。在設計伺服器架構的時候,這個問題是架構師必須要解決的。因為在業務開發的時候,開發人員不應該關心資料的持久化。

想要保證資料百分百不丢失,就需要考慮極端的情況,但是這樣所付的時間和精力是很龐大的,而且在遊戲中,隻要保證資料的完整性,短時間的資料丢失是可以接受的(這種情況出現的機率也是非常低的,比如伺服器意外當機,伺服器斷點,記憶體溢出等),營運商會以發送獎勵作為補償。

是以在目前的大多數遊戲伺服器架構設計中,都采用定時持久化資料的方式。即每隔一段時間,如果玩家資料發生了變化,就更新到資料庫一次。比如五分鐘或10分鐘,這個頻率可以根據自己遊戲的類型調整。

但是,這樣又會引起另外一個問題,就是更新資料庫的時候,會産生資料序列化和網絡IO的操作,這一步相對是比較慢的,如果放在業務線程裡面,很明顯會阻塞其它的業務執行,如果用戶端請求并發量比較大的話,可能會導緻消息處理延遲,導緻用戶端卡頓,或一直轉菊花(loading)。是以一來都是采用異步方式持久化資料。

如果是使用異步持久化資料,那麼這又會引出另外一個問題,即資料對象的線程安全。比如玩家的資料存在一個Player對象中,如下面代碼所示:

public class Player  {
    private long playerId;
    private String nickName;
    private int level;
    private Map<String, String> map = new HashMap<>();
    private List<String> list = new ArrayList<>();
    private List<Weapon> weapons = new ArrayList<>();
    private Weapon weapon = new Weapon();
    //省略get set方法
}
           

我們都知道,為了防止玩家操作自己的資料時産生并發操作,處理同一個玩家資料操作都是有序的,一般都是在同一個線程中執行。比如A玩家,登陸的時候配置設定給它一個a線程,那麼,A玩家的所有操作都是在a線程中執行,以保證執行的順序性。如果使用異步持久化,就會導緻多個線程(業務線程和持久化線程)操作Player對象,而Player對象中的集合List,Map,都不是線程安全的。

為了解決這個問題,一個思路就是把資料複制出來一份,或者是持久化的時候,将Player對象序列化為不可變資料,比如将Player對象變成Json串,然後持久化Json串或變成byte[]。如果是複制一個新的Player對象,這就會涉及到對象的淺拷貝和深拷貝問題了,毫無疑問,必須是深拷貝,這樣的話,就需要我們每次在Player添加集合或對象時,需要手動實作深拷貝。是一種比較笨的方法,而且容易在添加新資料對象時遺忘修改深拷貝代碼。架構的設計就是幫助開發人員在開發業務的時候減少需要關心的問題,是以隻能尋找其它的方式。

  1. 使用Json将對象序列化與反序列化
  2. 使用Serializable将對象序列化與反序列化
  3. 使用Orika複制對象

    Orika是近期在github活躍的項目,底層采用了javassist類庫生成Bean映射的位元組碼,之後直接加載執行生成的位元組碼檔案,是以在速度上比使用反射進行指派會快很多。

    添加maven依賴

<dependency>
			<groupId>ma.glasnost.orika</groupId>
			<artifactId>orika-core</artifactId>
			<version>1.5.0</version>
		</dependency>
           

性能測試

資料大小:2.8M,執行深拷貝1000條,各自消耗的時間如下所示:

orikaClone測試用時:7748 ms  ,平均每次7.7ms
serializableClone測試用時:23164 ms  平均每次23ms
jsonClone測試用時:17761 ms     平均每次17.7ms

           

由此可以看到使用Orika是最快的,json次之,對象serializable是最慢的。

是以,可以選擇在持久的時候,使用Orika在業務線程複制出一份新的Player,放到持久化線程更新到資料庫。也可以做為大多線程間互動資料的時候,将互動的資料轉複制出來,防止多線程并發操作。

另外

網上有人說使用apache的coomons-beanutils插件實作對象的複制,但是這個插件隻是實作了淺拷貝,在它的注釋中已說明了:

* <p>
     * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
     * In other words, any objects referred to by the bean are shared with the clone
     * rather than being cloned in turn.
     * </p>
           

包括網上說的cglib方式,我也沒有找到實作深拷貝的例子。如果有見過這種方式的同學,希望留言交流一下。

遊戲伺服器架構-資料持久化之資料複制

關注公衆号,發送“深拷貝”,擷取測試源碼。