分类 技术 下的文章

Java 工程中注解配合切面往往能带来事半功倍的效果,但是注解有的时候并不是太灵活,例如方法注解无法指定一些自定义的参数,很多时候还是需要到方法里面去做一些侵入性的逻辑来完成想要的操作;为了解决这个痛点,我写了个 demo 通过反射+递归的方式来给方法注解提供解析动态参数的能力,例如对于 void doSomething(Entity entity) 这个方法,可以通过 @RecordEvent(code = "TEST", keyWord = "{entity.id}", content = "content is {entity.nested.foo}") 注解中的花括号来取到入参中 entity 参数的某些值,且支持无限递归获取子属性的值,demo 代码如下:

有这么两个参数类型,其中Entity类中包含类型为Nested的属性

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Entity {
    private int id;
    private String name;
    private String otherField;
    private Nested nested;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Nested {
    private String foo;
    private int bar;
}

有一个方法,其第一个入参名为entity,可以使用“{entity.nested.foo}”这样的模板字符串,切面处理时会把模板字符串自动替换为对应入参属性的getter值,支持无限级往下查属性,查到最后的属性(本例中为foo)将其toString替换到模板。

@Override
@RecordEvent(code = "TEST", keyWord = "{entity.id}", content = "content is {entity.nested.foo}")
public void doSomething(Entity entity) {
    int a = 2*2;
    // some other logic
    log.info("done");
}
public static void main() {
    Entity entity = new Entity(1, "hello", "world", new Nested("bar", 111));
    xxxService.doSomething(entity);
}

调用时生成事件:EventPublishRequestDto(source=OPS, code=TEST, keyWord=1, content=content is bar, dateTime=2021-07-27T17:06:19.593)

具体方法是将模板参数用分隔符分割成队列,队列的第一个值是入参名,第二个值往后依次出队递归拿到文本对应的属性,以此类推直到队列为空,递归结束返回最深层的值

demo 源码如下:

- 阅读剩余部分 -

本系列博客将介绍如何在家里搭建一个高可用的私有云。

公网IP篇

现在家庭带宽运营商基本上都是不会给公网IP,都是在一个大局域网里通过类NAT协议映射出去,已节约运营商的IPV4资源。但是我们至少需要一个公网IP,你可以通过打运营商电话索要公网IP或买一台有公网IP的低配服务器(买IP送机器),两者优劣如下:

-家宽申请购买云服务
静态
带宽
时间成本
金钱成本

我已经有了一个腾讯云服务器的公网IP,但迫于服务器性能过低,带宽太小,还是和电信申请了个公网IP。上海电信打电话人工客服说明家里需要安装摄像头监控设施后第二天比较痛快地通知我已经帮忙开通了。

如果你的运营商不幸难以沟通,那只能考虑使用frp等穿透方案,走小水管公有云了。

拿到公网IP后,需要设置电信网关DMZ到内网路由器:

IMG_2566.jpg

上海电信现在都是SDN网关了,封装了一些DMZ、UPNP、端口映射等功能到手机APP中;如果你的网关没有限制那么死,可以谷歌搜一下网关的型号,通过超管密码进到网关里面自己进行对应的设置,或干脆设置为桥接模式,桥接到二级路由上去,让路由器拨号。

我的路由器是192.168.1.2,直接把流量完整地DMZ到路由器上。至此,你的路由器已经能handle所有外部流量了(注意,因为不是桥接,现在路由器还是二级路由,没办法直接拿到公网IP)。

- 阅读剩余部分 -

最近写代码时发现将枚举类暴露到 dict 接口时总需要做一些重复的装配工作,于是写了这么个工具类来提供枚举类的自动暴露功能。通过在想要暴露的枚举类属性上引用@DictApi注解,不需要再写额外的逻辑即可将对应枚举类和字段暴露出来。

期望

  1. 添加枚举类时可以低成本或无成本地添加到已有的枚举字典接口,不需要写额外的逻辑
  2. 可以指定枚举类哪些字段是需要暴露的,且可以指定字典接口中的属性别名
  3. 支持私有属性通过公有的Getter()方法获取属性值

实现

- 阅读剩余部分 -

从现有的告警记录中提取故障告警,并找到造成故障的原因。

思路整理

目前我们的告警类型众多,但所有告警都有以下几个特点:

  1. 告警有层级和优先级之分。有些告警表示底层故障(网络带宽、磁盘IO等),有些表示上层故障(接口慢查等);慢查故障的优先级肯定不如宕机来的严重。
  2. 不同服务、告警之间存在依赖关系,某次故障告警可能由其他故障告警造成,也可能引发其他告警。所以对于一个告警,需要向下分析其根因,也要向上分析其可能造成的影响。
  3. 上述依赖关系包括纵向的基础设施依赖关系(顶层应用依赖Redis、MQ,Redis依赖K8S。K8S依赖网络、磁盘等),也包括横向的业务依赖关系(PUB依赖AIM,AIM依赖PMS)。
  4. 依赖关系可以抽象成一个图,但是图中可能有环,有环的情况是比较危险的,可能造成循环故障,例如上次DCC和UAS互相依赖;所以理想情况下最细粒度的依赖关系应该是个有向无环图(DAG),对于DAG可以用拓扑排序得到横向的业务依赖关系。
  5. 除了故障会引发故障,事件也可能会引发故障。例如某次服务启动失败可能因为触发了配置变更事件引起。

故障发生与Metrics、Tracing、Logging三个维度的关系

饿了么分层

吴晟-三者关系

故障其实就是Metrics维度的数据阈值。Metrics维度的数据变化与Tracing和Logging这两个维度有关。Logging可以理解为一系列事件的触发,事件中心的作用可以理解为从海量Logging数据中提取出真正有意义的事件。Tracing则将离散的Logging或事件以一次调用链路的粒度聚合了起来,或者说很多Logging或事件的触发都是在一次Tracing追踪的链路中进行的。

一次故障的发生表现为Metrics维度的数据符合某个PromQL查询,则认为发生了对应的故障。Metrics维度数据的变化可以由其他Metrics引起(例:接口慢查故障可能由网络带宽故障引起),也可能由Tracing维度数据引起(例:接口A慢查故障可能因为调用了接口B造成),也可能由Logging维度数据引起(例:配置中心变更事件导致某个服务启动失败或接口异常熔断)。

一般发生故障后,常见的排查步骤首先是去Grafana上去看直接或间接相关的指标曲线(Metrics),然后找到Metrics维度的根因,这一步最方便的操作就是把异常时刻的Metrics值和正常时刻进行对比,找到哪些数值相差比较大的,那么很有可能就是造成故障的原因。找到Metrics的根因后再去找触发这次阈值的操作(Logging & Tracing)。分析Logs的目的就是为了找到最根本的事件,而这个事件往往也是Logs的一部分。在这个过程中可能需要Tracing的数据来进行横向的分析,把不同的Logs聚合起来,根据服务和接口间的依赖找到业务上的根因。

相关资料

[1]. Beginner's Guide to Observability(8、9两页比较有价值,指明了造成故障的三个方面和主流的Event-Handling技术)

[2]. Monitoring vs. Observability(详细介绍了Logs、Metrics和Traces三者在服务可观察性方面的特点和联系,比较有参考价值,可以细读)

[3]. 饿了么监控体系:从架构的减法中演进而来(监控+观察系统体系结构、组织方式值得参考)

- 阅读剩余部分 -

许多云服务厂商都有事件中心/事件总线之类的产品,例如阿里云的事件总线、Azure的事件中心,Shopee的数据事件中心等产品,分别了解下各家的事件中心类产品设计理念和使用场景,看看能借鉴哪些思路。

阿里云-事件总线

文档地址:https://help.aliyun.com/document_detail/163239.html

据官方文档简介,阿里云的事件总线以标准化的CloudEvents 1.0规范在这些应用之间路由事件,用来帮助构建松耦合、分布式的事件驱动架构。其涉及以下几个概念:

  • 事件:状态变化的数据记录。
  • 事件源:事件的来源,负责生产事件。
  • 事件目标:事件的处理终端,负责消费事件。
  • 事件总线:事件的接收者,负责存储事件。
  • 事件规则:用于监控特定类型的事件。当发生匹配事件时,事件会被路由到与事件规则关联的事件目标。

img

特点:阿里云的事件总线这个产品主打事件路由功能,例如应用发布事件,可以通过配置路由规则实现某个步骤成功或失败(事件发生)后钉钉、邮件通知或webhook、发MQ进行后处理等(事件目标)。审计功能不强,提供了按id和按时间检索的功能。

Azure-事件中心

文档地址:https://docs.microsoft.com/zh-cn/azure/event-hubs/

Azure的事件中心和Kafka很像,甚至Api都是兼容的,可以认为是解耦的、PaaS平台版的Kafka。对比一下消息队列和事件中心的使用差异,不难看出消息队列是要求Consumer接入的,订阅的消息/事件的消费逻辑是在分布在各个服务的消费者里面实现;而事件中心则更灵活一些,将事件生成者与事件使用者分离开来。

关键组件:

  • 事件生成者:向事件中心发送数据的所有实体。 事件发布者可以使用 HTTPS、AMQP 1.0 或 Apache Kafka(1.0 和更高版本)发布事件。
  • 分区:每个使用者只读取消息流的特定子集或分区。
  • 使用者组:整个事件中心的视图(状态、位置或偏移量)。 通过使用者组来使用应用程序时,每个应用程序都有事件流的单独视图。 使用者根据自身的步调和情况独立读取流。
  • 吞吐量单位:预先购买的容量单位,控制事件中心的吞吐量容量。
  • 事件接收者:从事件中心读取事件数据的所有实体。 所有事件中心使用者通过 AMQP 1.0 会话进行连接。 事件中心服务在事件变得可用时通过会话来提供事件。 所有 Kafka 使用者都通过 Kafka 协议 1.0 及更高版本进行连接。

特点:基于Kafka,兼容Kafka Producer Api,已经接入MQ的系统再接入事件中心会比较方便。完全的PaaS平台和服务解耦,支持实时捕获、处理数据。对比阿里云的事件总线,更偏向于数据流式处理、收集。

腾讯云-事件中心

文档地址:https://cloud.tencent.com/document/product/248/14361

img

腾讯云的事件中心是云监控产品里的一个功能,从结构图上来看和上面那些产品类似,都是有生产、消费的概念。但是腾讯云的事件中心仅限于云监控产品的一些事件,可复用性不高。

Shopee-数据事件中心

Shopee的这个事件中心主要用于数据同步,DEC (Data Event Center) 是 Shopee 的数据库事件订阅和任务执行平台,负责监听 MySQL 数据库数据变更事件,并根据用户配置对数据事件进行处理,执行数据同步、缓存同步、事件回调等不同类型的任务。

img

从系统架构图可以看出,其核心部分也是由Kafka完成的,生产者是MySQL,消费者是其他各个需要同步的数据源。大体设计思路和上面的事件中心类似,但是仅仅针对数据同步功能有所应用

总结

调研了上面几款事件中心类的产品,发现其大体结构和MQ还是比较像的,甚至完全兼容MQ的Api。但是其扩展能力更好,消费终端更多样化,同时PaaS的模式也一定程度上避免了业务方接入复杂的问题。事件中心的功能还是阿里云抽象的比较好,主要通过不同的规则来进行一个路由的操作。但是阿里云的审计/数据处理功能不强。基于对上面这些产品的调研,感觉我们的事件中心可以从通过规则的事件路由和审计这两方面去考虑如何设计,同时根据Azure的事件中心和Kafka的区别联系考虑与我们现有MQ、MSG等服务的关系,以及消费终端的需求收集和设计。