记得刚来公司的时候,线上用户报了问题,需要我们查埋点日志的时候,老大给我们说用es系统查,然后丢了个链接过来,打开链接,好家伙,偌大的一个kibana的logo,彼时的我还在想,为何公司都要叫他es,而不是kibana?
此时,我大概知道原因了,es是Elasticsearch,是一个实时的分布式搜索分析引擎,我们查日志不就是一个搜索的过程,kibana只是一个负责页面展示的,故以功能为主来称呼它了。
说说公司的这套埋点系统,主要基于kafka+ELK(Elasticsearch+logstash+kibana)构建的log系统。
流程
App一条pv的数据请求先经过 Nginx转发 -> kafka集群 消息缓存队列 -> Logstash 数据过滤 -> es存储提供数据搜索 -> kibana的web展示。
简单理解下里面涉及到的框架:
Nginx
Nginx 可作为HTTP服务器,也可作为反向代理服务器。第一次用他的时候是当时在公司弄了个flutter web 的ui组件展示页面,部署在Nginx上作为服务器。
提到他肯定会想到负载均衡这个词,啥叫负载均衡,譬如现在你有3台服务器提供资源,你当然希望每台服务器被访问的概率都差不多,不至于有的服务器忙的忙死,有的是闲的闲死。那么这就需要中间有一个人来去给他们派发这个任务了,负责管理他们。一条请求先到Nginx服务器,Nginx再去给他们分任务,大致就是这个意思。 可以更加简单的理解下,我们java中经常会用到线程池,加入有3个线程在执行task,那么肯定是有一个线程来负责调度一个新来的task由谁在execute,让每一个线程都有事情做,分配均匀。
那么这个访问过程中又涉及到另一个词,反向代理。先来说说正向代理,我们使用vpn去翻墙这种就是正向代理,代理服务器代理你自己去帮你访问你要的地址,代理源头访问目标。反向代理了,就像Nginx这种,代理的是目标服务器,客户端只知道访问的Nginx服务器地址,不知道具体是哪个服务器给你返回的内容。
Kafka
kafka是一个基于发布/订阅模式的消息队列。 kafka对外使用topic的概念,生产者往topic里写消息,消费者组的消费者从topic里读消息。
- 生产者向kafka集群PUSH消息,消费者组用长链接从kafka集PULL消息。
- kafka集群具有持久化消息的能力
- 消费者以消费者组的方式存在,同一个topicA的消息最多只会被整个消费者组消费一次。
看到这个topic的概念,我才终于弄懂公司开发的一个Native和Flutte端通信的一个框架。当时看到这个组件名字叫做TopicCenter。一度难以理解,话题中心???自从看了kafka的这种设计才能理解到他的设计思想。每一种数据类型都是一个topic,譬如String是一个topic,int是一个topic,然后采用的是Stream流的形式实现监听。
Redis
说了kafka,对比下Redis。kafka是存储在硬盘上,redis queue数据主要是存储在内存。
Redis 是以 key 的 hash 方式来分散对列存储数据的。
redis中的发布订阅也依然由三部分组成,发布者(生产者)、通道(类似于topic)、订阅者(消费者)。
之前学习数据结构 跳表的时候,就有讲到Redis底层是跳表实现,采用的二分查找的思想。
CS架构
服务器-客户机,即Client-Server(C/S)结构。C/S结构通常采取两层结构。服务器负责数据的管理,客户机负责完成与用户的交互任务。
广义上来说提供的服务的那一端就可以称之为Server,并不是说一定要是远程某个ip上的主机才叫服务器。在Android里面,面试经常问到的Binder机制,那也就C/S架构,提供服务的AMS,WMS,他们都属于Server端。
再者,在我们Android组件化中,现在主流基本都是SPI(service provider interface)机制来实现模块间互相调用,moduleA暴露ServiceA来给ModuleB调用,这也可以理解为C/S架构。
流量控制/拥塞控制
流量控制防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理。
拥塞控制防止发送方发的太快,使得网络来不及处理产生拥塞,进而引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿。
这两个概念在TCP/IP网络面试中也是经常会被问到的。在Androd开发能让我想到的就是背压、buffer的概念了,先看Rxjava中,
背压:被观察者发送消息太快以至于它的操作符或者订阅者不能及时处理相关的消息,从而操作消息的阻塞现象。
都说有了kotlin协程,要放弃rxjava。协程里也是有buffer的,对应RxJava中的 BUFFER 策略。
fun main() = runBlocking {
var start = 0L
val time = measureTimeMillis {
(1..5)
.asFlow()
.onStart { start = System.currentTimeMillis() }
.onEach {
delay(100)
println("Emit $it (${System.currentTimeMillis() - start}ms) ")
}
.buffer()
.flowOn(Dispatchers.IO)
.collect {
println("Collect $it starts (${System.currentTimeMillis() - start}ms) ")
delay(500)
println("Collect $it ends (${System.currentTimeMillis() - start}ms) ")
}
}
println("Cost $time ms")
}
TCP/IP中解决流量控制主要是采用的滑动窗口,
leetcode有一道hard题就是解决这个滑动窗口问题的,有兴趣可以试试,题目