sofastack / sofa-dashboard

Dashboard of SOFAStack.
Apache License 2.0
94 stars 51 forks source link

开源之夏-SOFADashboard 整体功能细分 #59

Open glmapper opened 3 years ago

glmapper commented 3 years ago

https://summer.iscas.ac.cn/#/org/prodetail/210170198

ashlee618 commented 3 years ago

计划

实时性

现在的版本是检测节点变化,然后读取数据。但是消费者消费了之后是不能同步到dashboard上面,要重新部署dashboard才能获取,我们可以设置一个定时任务,进行同步数据。

可视化

可以增加一个服务状态(正常使用、已停止、已过期等)、服务调用次数用来进行负载调优等。服务新增时间(gmt_create)、服务修改时间(gmt_modified),给服务备别名或前端新增备注行。

兼容性:

我们可以制作一个下拉框来让用户选择使用哪个注册中心,增加兼容性。

高性能:

使用Nosql数据库进行缓存,比如redis。

易管理

对服务的增删改查。

增:现在都是用sofa-rpc进行服务的发布,以后是否可以考虑把这一步骤,也合并到sofaDashboard上?不过这样要解决一个问题,就是对java代码的检查,相当于要写一个类似头歌(educoder)那样的编译器。

删:移除服务或停用服务?

改:修改服务信息(备注、分组、权限、状态)。

查:通过多个维度(比如应用名、服务名、作者、分组名...)

权限设置:

可以对服务进行权限限制,比如A这个服务提供给甲客人和乙客人,对于甲可以调用A中的get方法和set方法,但是对于乙客人只能调用get方法。

细节问题:

需要修复的问题:

1、如果先点上面的应用维度进行搜索,然后再切回服务维度,会出现额外多处几行空白的。 image-20210718171655610

2、当生产者停止提供某一服务时,应当给予用户明显的提醒,告知该服务已停用。

image-20210718171730026

架构图:

功能模块

glmapper commented 3 years ago

1、可以暂时不考虑对于 ark 部分的支持,如果在实际的开发过程中,有影响,可以将 ark 部分能力关闭掉(功能要支持插件化) 2、权限部分不需要具体实现,可以基于 Oauth2、spring security 搭一个框架出来,后续托管社区来实现对接具体平台 3、注册中心部分是重点需要关注的,由于 zk 和 sofaregistry 在模型上是不同的,所以在定义模型时,可以适当考虑冗余,由一个模型来呈现 4、重点是对 sofaregistry 的集成,现在的注册信息是通过 rest api 获取的,当数据量变大时,同步获取会有一定延迟,异步去拉则存在一定的时效性问题;这块需要和 sofaregistry 同学一块看下,基于事件方式,由服务端直接推过来是不是更合理。

ashlee618 commented 3 years ago

对于缓存的探讨

目前的服务信息数据是从SOFARegistry中通过rest api获取数据,然后存入缓存中。

当运行时和检测到数据变化时,都会再通过rest api再获取一次然后存入缓存中。

这个缓存的大概实现是用3个ConcurrentHashMap实现

    private Map<String, RpcService>        serviceMap  = new ConcurrentHashMap<>();
    private Map<String, List<RpcConsumer>> consumerMap = new ConcurrentHashMap<>();
    private Map<String, List<RpcProvider>> providerMap = new ConcurrentHashMap<>();

也就是说当前的实现方法是直接把 数据存储到项目内存里面。

好处是:存取速度快 吞吐量大

坏处是:

解决办法

原先的数据流向示意图: image

现在的数据流向示意图: image

保留通过 rest api 从SOFARegistry获取数据,在此基础上,抛弃了之前存入ConcurrentHashMap的方法。我们可以直接把数据封装后存入redis里面。

image

接口压力测试

采用jemeter 对/api/service/all-service?query接口进行测试,该接口是获取所有的服务列表。

测试分别采用了1个服务1个生产者1个消费者和1个服务1000个生产者1000个消费者的模式。

image

image-20210724103849350

image-20210724103903528

1个服务1个生产者1个消费者

未使用redis:

Label | # 样本 | 平均值 | 中位数 | 90% 百分位 | 95% 百分位 | 99% 百分位 | 最小值 | 最大值 | 异常 % | 吞吐量 | 接收 KB/sec | 发送 KB/sec -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- HTTP请求 | 10000 | 521 | 426 | 1476 | 1521 | 1602 | 1 | 1847 | 0.00% | 1968.5039370078741 | 667.0613927165355 | 547.7135365403543 HTTP请求 | 50000 | 541 | 463 | 1186 | 1367 | 1721 | 1 | 1935 | 0.0008% | 2256.215874734895 | 764.9731651324398 | 627.8688313647849 HTTP请求 | 100000 | 504 | 365 | 1115 | 1347 | 1725 | 1 | 2062 | 1.40% | 1858.6669640533808 | 689.7317435341623 | 510.0682573661295 HTTP请求 | 100000 | 508 | 383 | 1181 | 1372 | 1604 | 1 | 2118 | 0.32% | 1780.6901955197834 | 616.7849666343175 | 493.98359733920364 HTTP请求 | 100000 | 566 | 331 | 1350 | 1572 | 2147 | 1 | 2918 | 0.382% | 1487.8074182077871 | 517.2565319395057 | 412.59512087505396

使用了redis:

Label | # 样本 | 平均值 | 中位数 | 90% 百分位 | 95% 百分位 | 99% 百分位 | 最小值 | 最大值 | 异常 % | 吞吐量 | 接收 KB/sec | 发送 KB/sec -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- HTTP请求 | 10000 | 529 | 365 | 1283 | 1570 | 2024 | 1 | 2425 | 0.00% | 1967.7292404565133 | 666.7988734750098 | 547.5394929407713 HTTP请求 | 50000 | 667 | 537 | 1480 | 1731 | 2061 | 2 | 2474 | 0.49% | 2034.4224274728404 | 712.5415944022866 | 563.3703910795663 HTTP请求 | 100000 | 551 | 337 | 1268 | 1704 | 2354 | 1 | 2881 | 0.39% | 1669.1425614661748 | 580.6460898770677 | 462.7642167913655 HTTP请求 | 100000 | 676 | 581 | 1481 | 1679 | 2094 | 2 | 2901 | 1.521 | 1399.5605379910708 | 523.2841770496565 | 383.6158122130901 HTTP请求 | 100000 | 660 | 466 | 1482 | 1802 | 2143 | 2 | 2622 | 0.08% | 1555.185766939861 | 529.9021824602261 | 432.5503327368548

1个服务1000个消费者1000个提供者

未使用redis:

Label | # 样本 | 平均值 | 中位数 | 90% 百分位 | 95% 百分位 | 99% 百分位 | 最小值 | 最大值 | 异常 % | 吞吐量 | 接收 KB/sec | 发送 KB/sec -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- HTTP请求 | 10000 | 512 | 515 | 1029 | 1163 | 1199 | 1 | 1434 | 1.37% | 1974.7235387045814 | 742.8786532385466 | 542.1462699323657 HTTP请求 | 50000 | 484 | 352 | 1203 | 1456 | 1792 | 1 | 2449 | 1.89% | 2131.0147892426376 | 826.7616916128585 | 581.8186895990495 HTTP请求 | 100000 | 490 | 349 | 1197 | 1367 | 1665 | 1 | 2268 | 0.80% | 2164.7364433380235 | 786.4666765883753 | 597.6206290927048 HTTP请求 | 100000 | 416 | 254 | 956 | 1188 | 1639 | 1 | 2524 | 0.15% | 1924.001924001924 | 670.2368927368927 | 534.6023854617605 HTTP请求 | 100000 | 11 | 2 | 7 | 24 | 513 | 1 | 2461 | 0.00% | 1898.97455374098 | 654.6269701860996 | 528.5554218690658

使用了redis:

Label | # 样本 | 平均值 | 中位数 | 90% 百分位 | 95% 百分位 | 99% 百分位 | 最小值 | 最大值 | 异常 % | 吞吐量 | 接收 KB/sec | 发送 KB/sec -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- HTTP请求 | 10000 | 11110 | 11409 | 17174 | 18399 | 19594 | 37 | 20467 | 0.00% | 315.52709 | 108.77 | 87.8 HTTP请求 | 50000 | 20354 | 19321 | 32624 | 35311 | 38081 | 20 | 46476 | 0.30% | 271.22322 | 95.37 | 75.26 HTTP请求 | 100000 | 12064 | 10912 | 23051 | 31654 | 37782 | 20 | 46235 | 0.78% | 257.6755092 | 93.46176579 | 71.15649046 HTTP请求 | 100000 | 22046 | 22044 | 35130 | 38246 | 41616 | 20 | 44853 | 1.22% | 255.7132738 | 95.34609034 | 70.29910252 HTTP请求 | 100000 | 19110 | 18760 | 31679 | 36184 | 45397 | 20 | 49494 | 3.86% | 267.8143404 | 116.0746346 | 71.66292504

分析并总结

在第一个1个服务1个消费者1个生产者模型中,可以看到两者的异常率和吞吐量是差不多的,原先内存缓存级别的会比用了redis的速度稍微更快一点

在第二个模型中,两者的差别明显变大了。在吞吐量中,原先的缓存比采用redis的高了10倍左右。我的测试环境用的是redis单机测试的,如果采用redis集群可能会更好,而且redis跟项目是一起运行的,电脑带不动2个。但我们也非常明显看得出原先的缓存方法确实在速度上是远超redis。

总结: 本次提出的将数据迁移到redis上,想法的初衷是想着当数据量越来越大的时候,使用ConcurrentHashMap存储数据,无疑是在项目里面装了个小型数据库一样,迁移出来能给系统做到一个瘦身的效果,但这同时也意味着失去了原先的高吞吐量,高响应的性能,但是能得到减轻系统、对于数据易管理、易分析等好处。

glmapper commented 3 years ago

@ashlee618 我的意见是不绑定到具体的实现上去,可以在接口层面提供好扩展,redis 作为存储的一种实现,基本 map 的 mem 存储也是一种实现,当然也可以扩展其他存储

ashlee618 commented 3 years ago

基于Websocket+Netty+Mq实现消息实时推送

业务需求

实现高并发消息实时性地从SofaDashboard Client中把应用和实例消息推送到SoDashboard,进行展示。

1627796706

需克服难点

技术选型

这套技术体系参考与京东的京东到家中打印小票流程的实现,和抖音上用户关注的作者视频消息推送的实现。

1、采用WebSocket长连接的特点,实现消息实时性推送。 2、采用Netty作为WebSocket的容器,比较主流的有netty、tomcat、socketIO 三个框架。 1627796956(1) 3、 采用RabbitMq作为消息队列,也可以用其他优秀的MQ框架比如Kafuka、RocketMq等。

消息交互架构图

image

消息丢失

image

生产者、消费者、消息队列这三个部分任何一个部分出问题都会导致消息丢失。

1、消息队列发生故障

在RabbitMq中,消息默认是以内存的方式存储,当Mq出现宕机时,消息就会全部丢失,这是我们不想看到的。以RabbitMq为例子,具体操作:对交换机(Exchange)、队列(Queue)、消息(Message)进行可持久化存储。

2、消费者发生故障

当消费者消费逻辑复杂时间太长的消息、超时、消费者被停机或网络问题,会导致消息无法传递到消费者手中。

具体操作:设置手动Ack模式。

3、生产者发生故障

生产者会遇到一个问题,就是不知道消息是否到达Mq中。

具体操作:设置Confirm机制和Return机制。如果没有到达可以做重试和异常处理。

消息补偿机制

当生产者正在把消息发送给Mq途中宕机,或者是Mq正在把消息进行可持久化存储时宕机,都会导致消息”胎死腹中“。

因此我们可以设置一个消息补偿机制,对这些异常的消息进行重新发送或者判定为放弃该消息的发送。

image

具体操作:需要把消息不断地都存储在db中,然后设置一个定时任务一直去读取db里面的消息,判断是否需要进行补偿重发,如果补偿次数过多,就把该消息判定为异常消息放弃发送。

总结

这套技术体系不仅可以应用在这里,也可以为后面SofaRegistry和SofaDashboard服务信息的传递。