對於運行良好的游戲來說,停服一分就會損失很多收益。因為有些小bug就停服就划不來了。在使用Java開游戲服務器時,JVM給我們提供了一些接口,可以簡單做一些熱更新。修復一些小Bug而不用重啟服務。
JVM可以給運行中的服務器綁定一個代理,在這個代理中可以拿到Instrumentation 這個類的實例,它可以讓用戶手動修改jvm中的class類,對它進行熱更新,但是有一點,用於熱更新的新類和老的類方法簽名必須一樣,即不能修改方法的名字,參數類型,還有修改聲明的字段。只能修改方法體里面的代碼。一般的小bug都是方法體內的邏輯漏洞,不會做很多大的修改,所以這種方式還是能滿足我們的需要求的。
現在我們分三個項目:
1,GameServer 即我們正常的游戲服務器。
2,LoadAgent 這個是熱更新的代理項目,熱更新的操作就在這里面執行。
3,GameServerHotBoot 這個項目是用來把LoadAget代理和GameServer進行綁定的。
JDK代理的兩種方式:
1.premain方式是Java SE5開始就提供的代理方式,但其必須在命令行指定代理jar,並且代理類必須在main方法前啟動,它要求開發者在應用啟動前就必須確認代理的處理邏輯和參數內容等等
2.agentmain方式是JavaSE6開始提供,它可以在應用程序的VM啟動后再動態添加代理的方式
應用場景:
premain這種方式必須在jar包啟動的時候進行指定,它是運行在項目的main方法之前的,即項目啟動時:
java – javaagent:LoadAgent.jar -jar GameServer.Jar
但是正常的生產環境下,一般不會開啟代理功能,但是在發生問題時,我們不希望停止應用就能夠動態的去修改一些類的行為,以幫助排查問題,這在應用啟動前是無法確定的。這時agentmain就可以做到了。所以我們采用agentmain這種方式。
1,LoadAgent實現
這個實現也比較簡單,就像我們的程序入口有main方法一樣,它需要一個agemtmain方法

public class GameServerAgent { public static void agentmain(String args, Instrumentation inst) throws Exception { System.out.println("agent 啟動成功,開發重定義對象...."); Class<?>[] allClass = inst.getAllLoadedClasses(); for (Class<?> c : allClass) { if (c.getName().endsWith("TestHot")) { String pathname = "config\\TestHot.class"; File file = new File(pathname); try { byte[] bytes = fileToBytes(file); System.out.println("文件大小:" + bytes.length); ClassDefinition classDefinition = new ClassDefinition(c, bytes); inst.redefineClasses(classDefinition); } catch (IOException e) { e.printStackTrace(); } System.out.println("轉換代碼。。。"); } } System.out.println("熱更新成功...."); } public static byte[] fileToBytes(File file) throws IOException { FileInputStream in = new FileInputStream(file); byte[] bytes = new byte[in.available()]; in.read(bytes); in.close(); return bytes; } }
對某個class的替換有兩種方式
1,使用ClassFileTransformer
2,使用ClassDefinition
由於ClassDefinition比較方便,所以我們使用ClassDefinition對類進行更新。
項目源碼地址:https://github.com/youxijishu/game-hot-update
熱更新步驟:
1,打包LoadAgent
在使用LoadAgent的時候,需要在MANNIFEST.MF添加一些屬性
Agent-Class: com.xinyue.hot.agent.GameServerAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
這個在可以在打包的pom.xml中配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Agent-Class> com.xinyue.hot.agent.GameServerAgent </Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
然后使用mvn install命令即可,在target中找到生成的jar,LoadAgent-0.0.1-SNAPSHOT.jar,把它放到GameServer下的config目錄下面,這里是測試,用的相對路徑。
2,在GameServer中創建一個測試的類,叫TestHop.java,使用里面有一個輸出方法,打印1,然后使用mvn install進行編譯,在target/classes中找到這個類,把它也復制到GameServer的config路徑下面。
3,把TestHop類中的輸出修改為2,運行GameServer,這時會輸出pid和2
4,把pid復制到GameServerHotUpdate的HotUpdateMain類中,當然,在實際應用中也可以通過args傳進來。然后運行HotUpdateMain,等會兒就會輸出結果
這時我們發現類的輸出變化了,沒有熱更之前輸出是的2,熱更新之后,輸出的是1.說明,熱更新成功了。
項目源碼地址:https://github.com/youxijishu/game-hot-update
QQ群交流:66728073,更多文章:http://www.coc88.com;公眾號: