手游服务端框架之每日重置逻辑

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/littleschemer/article/details/78012630

实现背景

该系列打了这么久的地基,还没写过业务代码。实际上我是这么考虑的,因为不同游戏项目的业务代码差异性非常大,业务逻辑只有你想不到,没有策划大大想不到的。要想做一个拓展性强的框架,按理说业务代码越少越好。但再三考虑,还是要尽量构造出一些游戏公共的业务,也算是这套框架的示例代码吧。

玩过手机游戏的人应该知道,不管是什么游戏,都会经常出现每日重置的逻辑。比如,玩家打副本每日有一个上限次数,玩家购买某种商品也会有每日购买最大次数……这些业务有个特点,就是每天有上限,隔天次数会被重置。至于隔天是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);

		}
	}

至此,游戏的每日重置业务的介绍到这里就结束啦。



手游服务端开源框架系列完整的代码请移步github ->> jforgame




猜你喜欢

转载自blog.csdn.net/littleschemer/article/details/78012630