在做游戏开发的时候遇到过这样的需求:
- 设计一个家园功能。家园包含了建筑。
- 建筑可以产出和升级(都是玩家手动触发,会消耗一定的道具),随着建筑等级的提高。物品产出的质量越稀有。产出低端物品的时间会缩短
- 在产出过程中可以花费钻石进行加速
- 升级过程中不可产出。
分析需要完成的主要功能:
- 玩家点击升级和产出过后更新完成的时间戳(即当前时间+产出/升级时间),同时给标记为升级/产出状态,并入库。
- 服务器端定时扫描在线玩家的家园信息。选出符合状态的建筑
- 用建筑升级/产出完成的时间戳和当前时间戳进行比较。如果小于当前时间戳,更新建筑状态。推送给客户端。
技术选型
**
1、基于DelayQueue或定时任务,存在内存中,数据不可靠。实现简单
2、基于定时任务加mysql的形式,数据可靠。实现简单,但是实现任务的延迟很高。
定时任务每隔一段时间就去扫描表里面满足状态的任务,然后拿出来执行。
因为是对线上的数据量很大,并且只执行查询,所以一般是操作从库。
每个定时任务都会扫描整张表,效率低下。
主从同步存在延迟。
因此对于游戏来说。不满足效率
3、基于定时任务结合redis的方式,数据较可靠,高吞吐量高效率。
**
生产者代码
package com.hoolai.timer.task.delay.tasks;
import com.hoolai.timer.task.delay.core.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:
* @author: Ksssss(chenlin @ hoolai.com)
* @time: 2020-03-31 11:47
*/
@Component
public class DelayTaskProducer {
@Autowired
private JedisProvider client;
@Autowired
private RedisConfig config;
@PostConstruct
public void start() {
ExecutorService executor = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
executor.execute(new TaskProducer());
}
}
class TaskProducer implements Runnable {
private final Jedis jedis;
private final String[] keys;
private final long timeStamp = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(111);
private final AtomicInteger i = new AtomicInteger(0);
public TaskProducer() {
this.jedis = client.provider();
keys = config.keys();
}
@Override
public void run() {
int value = i.get();
try {
while (value < 3000) {
int pos = value % 2;
String key = keys[pos];
jedis.zadd(key, System.currentTimeMillis() + value, "" + i);
value = i.incrementAndGet();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
}
}
消费者代码
package com.hoolai.timer.task.delay.tasks;
import com.hoolai.timer.task.delay.core.ConsumerThreadPoolExecutor;
import com.hoolai.timer.task.delay.core.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:
* @author: Ksssss(chenlin @ hoolai.com)
* @time: 2020-03-28 17:57
*/
@Component
public class DelayTaskConsumer {
@Autowired
private JedisProvider provider;
@Autowired
private RedisConfig config;
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
private String[] keys;
private ConsumerThreadPoolExecutor executorEvent;
private ConsumerThreadPoolExecutor executor;
private AtomicInteger pos = new AtomicInteger(-1);
@PostConstruct
public void start() {
this.keys = config.keys();
executorEvent = new ConsumerThreadPoolExecutor("handler", keys.length);
executor = new ConsumerThreadPoolExecutor("consumer", keys.length);
scheduledExecutorService.scheduleWithFixedDelay(new DelayTaskTrigger(), 1, 1, TimeUnit.SECONDS);
}
class DelayTaskTrigger implements Runnable {
private final Jedis client;
public DelayTaskTrigger() {
client = provider.provider();
}
@Override
public void run() {
CountDownLatch countDownLatch = new CountDownLatch(keys.length);
for (int i = 0; i < keys.length; i++) {
executorEvent.submit(new DelayTaskEvent(keys[i], countDownLatch));
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class DelayTaskEvent implements Runnable {
private final String key;
private final Jedis client;
private final CountDownLatch countDownLatch;
public DelayTaskEvent(String key, CountDownLatch countDownLatch) {
this.key = key;
client = provider.provider();
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
int retry = 0;
Set<String> ids;
while (retry < 3) {
try {
while (true) {
ids = client.zrangeByScore(key, 0, System.currentTimeMillis(), 0, 100);
if (CollectionUtils.isEmpty(ids)) {
break;
}
client.zrem(key, ids.toArray(new String[ids.size()]));
executor.submit(new DelayTaskHandler(ids));
}
break;
} catch (Exception e) {
e.printStackTrace();
retry++;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException subE) {
subE.printStackTrace();
}
}
}
client.close();
if (client.isConnected()) {
client.disconnect();
}
countDownLatch.countDown();
}
}
class DelayTaskHandler implements Runnable {
private final Set<String> udids;
public DelayTaskHandler(Set<String> ids) {
this.udids = ids;
}
@Override
public void run() {
for (String id : udids) {
//todo
System.out.println(id + "升级完成");
}
}
}
private String next() {
if (pos.incrementAndGet() == keys.length) {
pos.set(0);
}
return keys[pos.get()];
}
}