[b]需求:[/b]
策略类游戏中,后台自动实时结算战斗等延时事件。
特征:用户提交延时事件,后台需要在延时事件时间到时立刻执行事件。
分析:
[b]一:扫描策略[/b]
(1) 时间步长,查找范围(lastTime, lastTime + stepTime], 通过控制stepTime大小来限制数据量。
优点: 用时间戳当边界,简单易实现
缺点: 当延时事件稀疏时,经常会出现空数据,必须一步一步的轮询,性能低。
(2) limit限制,查找范围(lastTime, stopTime] limit size,通过控制limit大小来限制数据量。
优点: 每次查询有效率高,只要有数据就可以立即获取。
缺点: 用limit之后,就会出现时间戳分页边界问题。
[b]二:问题总结(选择扫描策略:limit限制):[/b]
[b](1)轮询事件和注册延时事件线程不同步问题(多线程顺序不确定性、网络不稳定)[/b]
a、在now时刻,程序准备注册延时事件发生时间为now + delay
b、轮询线程轮询(tiem0, now + advance], advance为提前获取事件的时间,如果advance为0,表示只查询已经发生的事件。
c、将发生时间为now + delay的事件写入数据库
如果now + advance >= now + delay,即事件延时的时间小于轮询提前的时间,则会出现此事件丢失的问题。
解决方法:
a、保证advance <= 事件最小delay,同时写延时事件到数据库时,不用“set time=时间戳”的形式,而是用“set time=now() + delay”的形式,就可以忽略从java程序到写入数据库的时间误差。
b、增加自增字段id,多查询一次丢失事件:time <= now + advance && id > 已轮询的最大id
[b](2)limit时间戳分页边界问题[/b]
例如:有如下延时事件:
时间戳 事件
1 1
2 2
3 3
4 4
5 5
5 6
5 7
5 8
5 9
5 10
当查询时间戳在范围(0,5]内limit 8时,会检索得到事件1-8,但是事件9-10的时间戳也是5,故如果下次查找范围为(5,10]时,就会丢失事件9-10.
解决方法:
a、用java实现边界去重
查询(0,5]后,保留第5毫秒的所有事件id,接下来继续查询[5, 10]的事件,将两个查询中第5毫秒的事件做去重。
这样做有一个问题,如果第5毫秒的事件总数>= limit, 则取到第5毫秒时,每次获得的都是第5毫秒的事件,然后去重,出现死循环。
解决这个问题的方法的是主查询继续查询(5,10],另起一条sql查询第5毫秒的事件然后去重。
b、用自增字段id做边界
执行查找(5, 10] or (time=5 && id > 第5毫秒中上次查询的最大id) order by time, id
也可以将边界查找用另一个sql实现,变成两条sql:
select * from table where time > lastTime && time <= stopTime
select * from table where time = lastTime && id > 上次查询结果中时间戳为lastTime的事件最大id
c、将时间戳变成唯一id,即将时间戳 + 其他信息 -> 组合(各占多少位)到一个long型字段中做唯一时间有序id
策略类游戏中,后台自动实时结算战斗等延时事件。
特征:用户提交延时事件,后台需要在延时事件时间到时立刻执行事件。
分析:
[b]一:扫描策略[/b]
(1) 时间步长,查找范围(lastTime, lastTime + stepTime], 通过控制stepTime大小来限制数据量。
优点: 用时间戳当边界,简单易实现
缺点: 当延时事件稀疏时,经常会出现空数据,必须一步一步的轮询,性能低。
(2) limit限制,查找范围(lastTime, stopTime] limit size,通过控制limit大小来限制数据量。
优点: 每次查询有效率高,只要有数据就可以立即获取。
缺点: 用limit之后,就会出现时间戳分页边界问题。
[b]二:问题总结(选择扫描策略:limit限制):[/b]
[b](1)轮询事件和注册延时事件线程不同步问题(多线程顺序不确定性、网络不稳定)[/b]
a、在now时刻,程序准备注册延时事件发生时间为now + delay
b、轮询线程轮询(tiem0, now + advance], advance为提前获取事件的时间,如果advance为0,表示只查询已经发生的事件。
c、将发生时间为now + delay的事件写入数据库
如果now + advance >= now + delay,即事件延时的时间小于轮询提前的时间,则会出现此事件丢失的问题。
解决方法:
a、保证advance <= 事件最小delay,同时写延时事件到数据库时,不用“set time=时间戳”的形式,而是用“set time=now() + delay”的形式,就可以忽略从java程序到写入数据库的时间误差。
b、增加自增字段id,多查询一次丢失事件:time <= now + advance && id > 已轮询的最大id
[b](2)limit时间戳分页边界问题[/b]
例如:有如下延时事件:
时间戳 事件
1 1
2 2
3 3
4 4
5 5
5 6
5 7
5 8
5 9
5 10
当查询时间戳在范围(0,5]内limit 8时,会检索得到事件1-8,但是事件9-10的时间戳也是5,故如果下次查找范围为(5,10]时,就会丢失事件9-10.
解决方法:
a、用java实现边界去重
查询(0,5]后,保留第5毫秒的所有事件id,接下来继续查询[5, 10]的事件,将两个查询中第5毫秒的事件做去重。
这样做有一个问题,如果第5毫秒的事件总数>= limit, 则取到第5毫秒时,每次获得的都是第5毫秒的事件,然后去重,出现死循环。
解决这个问题的方法的是主查询继续查询(5,10],另起一条sql查询第5毫秒的事件然后去重。
b、用自增字段id做边界
执行查找(5, 10] or (time=5 && id > 第5毫秒中上次查询的最大id) order by time, id
也可以将边界查找用另一个sql实现,变成两条sql:
select * from table where time > lastTime && time <= stopTime
select * from table where time = lastTime && id > 上次查询结果中时间戳为lastTime的事件最大id
c、将时间戳变成唯一id,即将时间戳 + 其他信息 -> 组合(各占多少位)到一个long型字段中做唯一时间有序id