天天看點

java遊戲伺服器業務之每日重置邏輯

實作背景

該系列打了這麼久的地基,還沒寫過業務代碼。實際上我是這麼考慮的,因為不同遊戲項目的業務代碼差異性非常大,業務邏輯隻有你想不到,沒有策劃大大想不到的。要想做一個拓展性強的架構,按理說業務代碼越少越好。但再三考慮,還是要盡量構造出一些遊戲公共的業務,也算是這套架構的示例代碼吧。

玩過手機遊戲的人應該知道,不管是什麼遊戲,都會經常出現每日重置的邏輯。比如,玩家打副本每日有一個上限次數,玩家購買某種商品也會有每日購買最大次數……這些業務有個特點,就是每天有上限,隔天次數會被重置。至于隔天是0點重置,還是5點重置,就要看項目的營運更新時間了。

每日重置業務的注意事項

每日重置的邏輯看似簡單,要完美實作還是要花點功夫的。因為每日重置有這樣的特點:

1. 在家玩家和離線玩家的處理方式是不同的;

2. 每日重置的時候要考慮線程安全,避免出現并發問題。

針對第一個問題,我們的處理方式是,處理在家玩家,我們通過周遊線上玩家清單,依次執行重置業務。而離線玩家就比較麻煩,我們不可能把所有不線上的玩家從資料庫撈出來對其處理。實際上,我們可以在玩家登入的時候處理,玩家每天登入的時候,我們将玩家身上的重置辨別與系統公共的重置時間進行比較,若不相同,則可以執行重置業務。

針對第二個問題,考慮線程并發問題,就會跟系統所使用的異步線程模型相結合。由于我們的線程模型是根據玩家的分發id作負載均衡的。是以,每日重置也應該與其相适應。

quartz在遊戲中的應用

quartz是一個非常優秀的作業排程架構。在遊戲開發中,我們經常用quartz來實作定點任務和頻率任務。

使用quartz隻需要引入相關的jar包,然後使用配置檔案對任務觸發進行配置。

每日重置業務實作

1. 首先,我們在目前線程模型的基礎上,定義一種timer事件。該事件需要滿足,對玩家業務的執行來說是線程安全的。同時,為了拓展性,該事件可以設定timer的執行次數。要達到線程安全,隻要讓timer事件繼承自AbstractDistributeTask就可以了。具體原因,可參考該系列關于線程模型的文章 手遊服務端架構之消息線程模型

/**
 * timer任務
 * @author kingston
 */
public abstract class TimerTask extends AbstractDistributeTask {
	
	private int currLoop;
	/** 小于0表示無限任務 */
	private int maxLoop;
	
	public TimerTask(int distributeKey) {
		this(distributeKey, 1);
	}

	public TimerTask(int distributeKey, int maxLoop) {
		this.distributeKey = distributeKey;
		this.maxLoop = maxLoop;
	}
	
	public void updateLoopTimes() {
		this.currLoop += 1;
	}
	
	public boolean canRunAgain() {
		if (this.maxLoop <= 0) {
			return true;
		}
		return this.currLoop < this.maxLoop;
	}

}
           

2. 玩家每日重置timer事件(DailyResetTask),隻要繼承上面的TimerTask就可以了

public class DailyResetTask extends TimerTask {

	private Player player;

	public DailyResetTask(int distributeKey, Player player) {
		super(distributeKey);
		this.player = player;
	}

	@Override
	public void action() {
		System.err.println("玩家"+player.getName()+"進行每日重置");
		PlayerManager.getInstance().checkDailyReset(player);
	}

}
           

3.編寫quartz作業。假設我們每日重置的時間發生在每天5點,那麼我們可以在jobs.xml裡加上這樣的配置

<!-- 每日重置 -->
<job>
	<name>DailyResetJob</name>
	<group>DEFAULT</group>
	<job-class>com.kingston.game.cronjob.DailyResetJob</job-class>
</job>
<trigger>
	<cron>
		<name>DailyResetJobTrigger</name>
		<group>DEFAULT</group>
		<job-name>DailyResetJob</job-name>
		<job-group>DEFAULT</job-group>
		<cron-expression>0 0 5 * * ?</cron-expression>
		<!-- 每天05:00運作 -->
	</cron>
</trigger>
           

4. quartz在調試的時候,肯定是在自己的線程上跑的。重置job觸發的時候,我們周遊所有玩家,對每個玩家執行每日重置業務。這個過程中,我們需要将觸發點封裝成timer事件,丢到主業務線程裡處理。這些操作在DailyResetJob上實作。

/**
 * 每日5點定時job
 * @author kingston
 */
@DisallowConcurrentExecution
public class DailyResetJob implements Job {

	private Logger logger = LoggerSystem.CRON_JOB.getLogger();

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		logger.info("每日5點定時任務開始");

		long now = System.currentTimeMillis();

		SystemParameters.update("dailyResetTimestamp", now);
		Collection<Player> onlines = PlayerManager.getInstance().getOnlinePlayers().values();
		for (Player player:onlines) {
			int distributeKey = player.distributeKey();
			//将事件封裝成timer任務,丢回業務線程處理
			TaskHandlerContext.INSTANCE.acceptTask(new DailyResetTask(distributeKey, player));
		}

	}

}
           

5. 對于離線玩家的處理,我們需要将當次重置的時刻記錄在一張公共的系統表(systemrecord)。在DailyResetJob裡的 

SystemParameters.update("dailyResetTimestamp", now);
           

玩家登入的時候檢查一下,在LoginManager.handlSelectPlayer()方法。登入邏輯發生在玩家發出登入消息,本來就在架構裡的線程模型,本身就是線程安全的了。

/**
	 * 選角登入
	 * @param session
	 * @param playerId
	 */
	public void handleSelectPlayer(IoSession session, long playerId) {
		Player player = PlayerManager.getInstance().get(playerId);
		if (player != null) {
			//綁定session與玩家id
			session.setAttribute(SessionProperties.PLAYER_ID, playerId);
			//加入線上清單
			PlayerManager.getInstance().add2Online(player);
			SessionManager.INSTANCE.registerNewPlayer(playerId, session);
			//推送進入場景
			ResPlayerEnterSceneMessage response = new ResPlayerEnterSceneMessage();
			response.setMapId(1001);
			MessagePusher.pushMessage(session, response);
			
			
			//檢查日重置
			PlayerManager.getInstance().checkDailyReset(player);

		}
	}
           

至此,遊戲的每日重置業務的介紹到這裡就結束啦。

java遊戲伺服器架構系列完整的代碼請移步github ->>  jforgame