案例设计基于时间的事件响应管理器
Posted 野奔在山外的猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了案例设计基于时间的事件响应管理器相关的知识,希望对你有一定的参考价值。
开发平台:Unity 2021
编程语言:CSharp
编程平台:Visual Studio 2022
一、前言
程序运行的特性是 逐行关联即执行。在执行单行代码语句的效率上基于物理设备基础与底层架构逻辑。对一般程序员来看,只需要学会应用即可。但有时候,并不期望于其立即执行,甚至是不断的自我检测、等待若干时间、等待若干帧后执行。于是有了计时器的说法。本文重点记录计时器的构想与设计。
二、思考:计时器类型表现
类型 | 描述 |
---|---|
顺序 | 从单位时间 0 开始计数或计次。每次值累加固定单位值。默认情况下,无上限值。即启用即不停止,直至应用程序结束。 |
逆序 | 从固定时间 任意值 开始计数或计次。每次值累减固定值。默认情况下,存在下限值 0。当应用程序强制关闭或到达下限值时,停止当前逻辑。 |
三、思考:目前能设想的计时器实现方式(从 Unity 角度构想)
3.1 实现一:Unity 生命周期 Update/FixedUpdate
public void Updaete() while(timer <= 0) timer -= Time.deltaTime;
public void FixedUpdate() while(timer <= 0) timer -= Time.FixedTime;
使用 Update
更新,在一定程度上解决计时所带来的问题。但在其他方面上,Update
中计算计时选项不是明举的实现方法,于是较于 Update
有了 FixedUpdate
的更新方案。因为计时器,记录的是事件发生的时间节点。一般的,对于个人开发者而言,时间的计算精确并不需要精确至 毫秒。所以 FixedUpdate
在整体刷新上并不会较 Update
次数多且耗能大。也是不错的优化方案。但最终不是最佳。
3.2 实现二:IEnumerator 协程(常用)
协程是目前 Unity 开发者中最为常用的计时器实现方式。其特点用时则用,无用时无需调用(即 与 Update
相比,不需要持续更新,或在 Update 中添加 bool
判断环节是否执行等特点),每次使用 yield return new WaitForSecond()\\WaitForEndOfFrame()
等来延迟执行。
public IEnumerator DoTimeCount()
yield return new WaitForSeconds(1f);
timer += 1f;
TimerEvent?.Invoke(timer);
StartCoroutine(DoTimeCount());
- 协程特点:基于主线程上,单开一分支独立运行。不影响原主分支执行。
- 依赖于 MonoBahaviour 实现 IEnumerator 操作(对 非
MonoBehaviour
的类对象无法调用)
四、思考:基于 IEnumerator 的计时器类封装
4.1 解决依赖问题
IEnumerator
的启用基于 MonoBehaviour
基础上进行。非 MonoBehaviour
继承的子项无法使用 IEnumerator
进行协程操作。故其实现上应基于该对象上进行。但从以下两个角度进行思考,选择 继承 MonoBahviour
的 Mono单例 将更具备优势。
- 单例模式下,调用方法更加直观、快速。无需实例化对象。
- 计时器作为辅助功能,无需准备额外的 Prefab 对象用于加载时程序添加对象,或 持续存在于场景内上。从调用与项目维护上,这是不可取的行为。但在
MonoBehaviour
环境下的单例模式,建立调用时,若场景无该对象,则程序创建该对象并完成引用的行为。减少了资产中需存储特定对象的 Prefab 并管理的麻烦性。
4.2 考虑计时器的基础属性
计时器作为统筹时间变化的重要,必要的参数包括如下:
参数 | 数据类型 | 说明 |
---|---|---|
StartTime | decimal | 计时器 起始时间 |
EndTime | decimal | 计时器 终止时间 |
LerpTime | decimal | 计时器 插值时间(限制时间精度) |
IsPositiveTime | bool | 判断 计时器为 正序 or 逆序, |
OnTimeEvent | Dictionary<decimal, Action> | 记录 时间戳事件 |
- 从精度角度考虑,
float
的精确度存在较小的误差,但为了保证计时器在确切的时间。例如0.1m
,而非0.1000002231f
。使用十进制decimal
数据类型作为标准,后续设计提供基础奠定。 - 从程序设计角度上考虑,参数变量过多的情况,并不建议使用复数的局部变量作为配置。相比较可参考 事件 的设计思想,将变量集中于 类 中以供调用实现。
4.3 考虑计时器的拓展设计
实际上,计时器并非仅用于计时,在应用层面上,涉及程序逻辑响应 + 时间显示。例如 0 -10s 的计时器,以单位1s作为过渡。在每个时间戳结点下,调用对应的方法与内容,以强化时间事件效果。具体如下:
- 加入 第2s时,执行方法体
Debug.Log("Hello 2s, taking fire! Now!")
- 加入 第2s时,执行方法体
Debug.Log("Hello 2s, Where are u from ?")
- 加入 第4s时,执行方法体
Debug.Log("4s, R U Hungry?")
- 加入 第9s时,执行方法体
Debug.Log("The Time will over soon")
那么 Action
将毫无疑问的成为程序开发的首选选项。为什么?每一秒时间响应的方法内容并不局限于一种方法的内容执行。例如 在第 2s 时,添加新的方法体内容以执行。从解耦度角度考虑,新添方法体 + 原方法体 之间互不影响干涉,即两者之一的有无均不会影响 第2s 时刻的时间事件执行。则 委托、事件 是最直接可用的方法。在时间事件的注册上也更便捷快捷。
五、程序设计
5.1 关于 GameTime 的说明
public class GameTime : MonoSington<GameTime>
public static Coroutine DoCoroutine(TimeData data)
return Instance.StartCoroutine(DoPositiveTime(data));
private static IEnumerator DoPositiveTime(TimeData data)
yield return new WaitForSeconds(data.LerpTime);
// 更新当前时间值
data.CurrentTime += data.LerpTime;
data.TimeAction[data.CurrentTime]?.Invoke();
if(data.CurrentTime <= data.EndTIime)
// 迭代继续
Instance.StartCoroutine(DoPositiveTime(data));
yield break;
Debug.Log($"[Time Coroutinue] The Time Event Is Finish");
- 从实现角度上:
- 使用
MonoSingleton
的 Mono 单例模式实现 Mono 环境下调用 Coroutinue 的前置条件(重要)。 - 选择静态方法。直接同通过
GameTime.DoCoroutine(new Time());
快速启用协程,降低程序上调用的复杂性。
- 使用
- 从方法命名角度上,
- 使用 Positive / Nagetive 以区分计时器的正反顺序。
- 更多可优化与补充内容:
- TimeData 中注册的
TimeAction
未提供便捷的初始化方法。
可构建public TimeAction(decimal startTime, decimal endTime, decimal lerpTime)
用于初始化 TimeData 类。 - 在使用习惯角度上,集成 事件注册、注销、启用、禁用 方法于 GameTime 类是最佳的选择。
无需在new
之后,引用新建对象中的事件注册进行。(记住多的API倒不如集合在一起,自己找)。 - 在判断时间事件属于 顺序计时/逆序计时 上可进行数据判断,执行对应的数据内容。
扩展DoCoroutine
,增加判断计时器应用类型方法。 - 考虑多类型的计时器可能同时运行,未区别其计时器具体属于何种应用的计时器,可添加
Name
属性用于Debug
识别。
- TimeData 中注册的
5.2 关于 TimeData 的说明
public class TimeData
private decimal currentTime;
public decimal CurrentTime get return currentTime;
public decimal StartTime get; set;
public decimal EndTime get; set;
public decimal LerpTime get; set;
public Dictionary<decimal, Action> TimeAction get; set;
public TimeData(decimal startTime, decimal endTime, decimal lerpTime)
this.currenTime = startTime;
this.StartTime = startTime;
this.EndTime = endTime;
this.LerpTime = lerpTime;
this.OnTimeEvent = new Dictionary<decimal, Action>();
- TimeData:配置事件计时器相关属性与事件的配置文件。
- 一般情况下,一个时间触发计时器需要 开始时间、结束时间、时间插值。随着需求变化,增加展示当前数据、区别计时器类型等内容。(视实际情况而定)或许
Action
也会因为实际需求改用Actoin<int>
或其他也说不定。
六、后记
关于时间管理的事件管理器是作者在 BILIBILI 上偶然看见同开发者自己开发作品时萌发的设想。虽然他的视频内容我并没有太多关于他讲解时的记忆。但基于时间驱动事件这一需求设计,深深的给我留下了印象。以思考 —— 如果是我,我会怎么去实现这个需求?这篇文章也并非完整的内容。仅提供实现方式的思路参考。后续仍不定期更新完善内容。
服务案例|基于IT事件管理,提升业务连续性
数字化经济时代,IT架构复杂性越来越高,业务连续性成为很多行业或企业最核心的任务。业务连续性管理是一个不断提升的过程,围绕事件“发现-响应-定位处理-降低发生”的事件处理思路,结合平台化运维,助力业务快速提升。
我们将具体事件从监控、调查、上报和响应几个环节来处理。即当平台监控发现异常,进行事件优先级分类,判断事件处理的紧迫性,分析事件影响造成破坏程度,然后进行事故调查与诊断,快速定位识别问题,联系现场工程师最终解决问题,事件流程结束。
围绕事件提升业务连续性的优势在于:主动快速处理使业务恢复正常,将影响降至最低。流程闭环提高用户满意度,最大程度降低事件处理成本。下面我们来看案例的处理过程。
一、问题发现
夜间服务器在飞速运转,主要进行流程审批、数据库备份、报表统计这类定时、耗时的工作。夜间无人值守的机房,加上高速飞转的服务器,很容易触发故障。
2月1日凌晨4:40分,平台接到某服务器ping不通告警,检测到此服务器发生死机现象。这台服务器已经连续发生几次夜间死机故障。
二、问题分析定位
根据时序图,协助服务器管理工程师查找故障发生的具体原因。查看4:40分前后,CPU,内存和虚拟内存,磁盘使用等运维参数如下
1 CPU没有异常,空闲率达到70%以上
2、可用内存22.28G,充足
3、虚拟内存使用率只有0.49%
4、硬盘剩余可用空间充足、
5、网络输出输入数据也是正常
6、在4:40-6:48左右,ping不通,服务器已发生死机。4:40-6:48之间的服务器运行指标参数没有上传
7、从服务器发生死机前和重启后的运维参数看,服务器的资源配置充足,并非是资源争用导致死机。
8、查看服务器运行日志,服务器并没有自动进行打补丁更新,但是组策略配置有告警生成
平台未接到安全攻击告警,也未接收到硬件故障告警,首先排除病毒攻击;硬件故障,服务器资源匮乏,且近期应用软件未升级、变更,其次则排除软件问题。最后锁定操作系统问题。服务器管理工程师对操作系统进行了打补丁升级处理。后期持续对这台服务器进行重点监控,未产生故障,问题得到解决。
服务器宕机,可能导致客户无法访问,业务中断造成巨大的经济损失;也可能影响数据备份,导致数据丢失;夜间无人值守,故障重启等问题不易察觉,LinkSLA智能运维管家不仅能够及时监测到服务器故障,第一时间进行反馈,可以根据历史运维指标数据,进行分析,协助用户查找出故障的根本原因,从根本上解决问题。
四、总结
除了实时发现告警,及时处理,流程闭环外,还需加强问题管理以及自动巡检服务出发,从源头上降低故障事件发生。
基于业务系统的多样性,还可为业务发展提供依据,通过一段时间的监控数据累积,利用监控系统提供的报表功能对数据进行统计处理,帮助用户做系统升级决策,如是否需要采购新硬件、是否需要新增系统节点等。另外,还可以利用系统的监控大屏功能,对系统的整体健康状况做到一目了然,做到资源、业务的可视化。
以上是关于案例设计基于时间的事件响应管理器的主要内容,如果未能解决你的问题,请参考以下文章
Java语言程序设计 上机实验6 掌握Java的图形用户界面编程,掌握布局管理器和事件的响应方法