2019独角兽企业重金招聘Python工程师标准>>>
假如有个直播间,在数据有更新的时候,能及时反映在客户端上。通信方式来说,有两种:
1、拉取模式。
2、推送+拉取模式(或者纯推送)
拉取模式,技术简单。但轮询间隔设置比较难;如果设置得太大,数据更新不及时;如果设置得太小,那么服务端可能会多余很多无用请求(数据本来并无更新)。
推送模式,可以在数据有更新的情况下,才选择通知客户端。但如果在同一秒内,数据更新得太频繁,可能会造成推送风暴。
这时候做一个简单优化,如果距离上一次推送不超过一秒,当前数据更新不推送。那么也会有个问题,最后更新的数据,可能在很长一段时间内都更新不到客户端。
下面介绍一个设计方法,如下是一个时间轴:
0 0.2 1 1.3 2 3 4
|------------------------|------------------------|------------------------|------------------------|
0 0.2 1 1.3
采用ThreadPoolExecutor来处理时间轴上面0/0.2/1/1.3的请求Task,而BlockingQueue则采用DelayedQueue来实现,进入线程池的任务的Task都实现Delayd接口并且设置延迟1秒。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
那么Task真正执行的时间点就如时间轴下面的数字。而由于设置了规则,距离上次推送间隔小于1秒的时候,不再继续推送。所以 红色的数字 0 和 1 表示会执行推送,而 蓝色的数字 0.2 和 1.3 就不会推送。那么反馈到客户端层面,当 红色的数字 0 执行推送,会把当时 绿色的0/0.2/1状态推送给客户端(因为是全量更新,所以只需要查询当前最新数据);而当 红色的数字 1 执行推送,会把当时 绿色的1.3状态推送给客户端。那么这个过程中,最晚的更新数据也不会超过1秒的延迟到达客户端。同时,只发生了两次推送。
上面的任务执行过程中,涉及到上一次推送的时间变量的比较。那么这个时间变量存储在哪里呢?
简单的单进程,当然是在进程内定义一个变量就完事了。
假如是多进程,那么为了最大化优化性能逻辑,我们可以分别设置本地变量缓存以及全局的redis变量缓存。方法有很多,下面只介绍一种同步方式:
当进程内的任务执行,先对比本地变量;如果系统时间减去本地变量小于一秒,那么不继续执行任务;如果系统时间减去本地变量大于一秒,那么通过redis的get获取到上一次推送的时间戳;如果系统时间减去该全局时间戳小于一秒,那么不继续执行任务并更新本地缓存,如果系统时间减去该全局时间戳大于一秒,通过redis的getset(value是系统时间)返回的全局时间戳,这时的时间戳如果还大于1秒那么就推送并且更新本地缓存为当前系统时间,否则不继续执行(其它进程执行了推送)。