《消息队列高手课》笔记(2)

第九课 09 | 学习开源代码该如何入手?

总结

一、通过文档来了解开源项目

最佳的方式就是先看它的文档。

  quick start -> Introduction -> Basic Concepts -> Use cases -> EcoSystem -> feature -> 论文(项目产生的背景)

    • 这个项目是干什么的?
    • 能解决哪些问题?
    • 适合在哪些场景使用?
    • 有哪些功能?
    • 如何使用?

二、用以点带面的方式来阅读源码

1、带着问题去读源码,最好是带着问题的答案去读源码

RocketMQ 的消息是怎么写到文件里的?

Kafka 的 Coordinator 是怎么维护消费位置的?

2、提出问题

  看文档 : DESIGN -> IMPLEMENTATION -> Improvement Proposal

 

 

第十课 10 | 如何使用异步设计提升系统性能?

总结

一、同步实现的性能瓶颈

  采用同步实现的方式,整个服务器的所有线程大部分时间都没有在工作,而是都在等待。

二、采用异步实现解决等待问题

  在线程模型上由同步顺序调用改为了异步调用和回调的机制。

三、简单实用的异步框架: CompletableFuture

四、异步思想

  当我们要执行一项比较耗时的操作时,不去等待操作结束,而是给这个操作一个命令:“当操作完成后,接下来去执行什么。”

思考

异步的本质是为了不占用过多的线程对象。 比如一个响应时间是1秒的http1.1请求,并且不考虑http pipeline:

同步模式下,一个请求在未返回前,需要独占一个线程和一个httpconnection。

异步模式下,一个请求在未返回前,只需要独占一个httpconnection,那个线程在提交完io任务后就回到线程池了。

也就是说一秒并发5000的话,同步需要5000个connection和5000个线程,而异步可以省下5000个线程的内存以及操作系统对这些线程的管理能耗。

 

 

第十一课 11 | 如何实现高性能的异步网络传输?

总结

一、理想的异步网络框架应该是什么样的?

1、发送数据的时候同步发送就可以了,没有必要异步。

2、只用少量的线程就能处理大量的连接,有数据到来的时候能第一时间处理就可以了。

二、使用 Netty 来实现异步网络通信

三、使用 NIO 来实现异步网络通信

 

思考

关于JAVA的网络,之前有个比喻形式的总结,分享给大家: 例子:有一个养鸡的农场,里面养着来自各个农户(Thread)的鸡(Socket),每家农户都在农场中建立了自己的鸡舍(SocketChannel)
1、BIO:Block IO,每个农户盯着自己的鸡舍,一旦有鸡下蛋,就去做捡蛋处理;
2、NIO:No-Block IO-单Selector,农户们花钱请了一个饲养员(Selector),并告诉饲养员(register)如果哪家的鸡有任何情况(下蛋)均要向这家农户报告(select keys);
3、NIO:No-Block IO-多Selector,当农场中的鸡舍逐渐增多时,一个饲养员巡视(轮询)一次所需时间就会不断地加长,这样农户知道自己家的鸡有下蛋的情况就会发生较大的延迟。怎么解决呢?没错,多请几个饲养员(多Selector),每个饲养员分配管理鸡舍,这样就可以减轻一个饲养员的工作量,同时农户们可以更快的知晓自己家的鸡是否下蛋了;
4、Epoll模式:如果采用Epoll方式,农场问题应该如何改进呢?其实就是饲养员不需要再巡视鸡舍,而是听到哪间鸡舍的鸡打鸣了(活跃连接),就知道哪家农户的鸡下蛋了;
5、AIO:Asynchronous I/O, 鸡下蛋后,以前的NIO方式要求饲养员通知农户去取蛋,AIO模式出现以后,事情变得更加简单了,取蛋工作由饲养员自己负责,然后取完后,直接通知农户来拿即可,而不需要农户自己到鸡舍去取蛋。

 

第十二课 12 | 序列化与反序列化:如何通过网络传输结构化的数据?

总结

一、网络传递的是二进制流啊(等价于字节流)

1、要想使用网络框架的 API 来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。

二、选择哪种序列化实现

  • 序列化后的数据最好是易于人类阅读的;
  • 实现的复杂度是否足够低;
  • 序列化和反序列化的速度越快越好;
  • 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好;

三、实现高性能的序列化和反序列化

1、很多的消息队列都选择自己实现高性能的专用序列化和反序列化。

 

思考

1、在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢

答: 

  内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用, 指针,并不是直接数据块。

  序列化, 反序列化, 其实是约定一种标准吧, 大家都按这个标准去弄, 就能跨平台 , 跨语言。

 

 

第十三课 13 | 传输协议:应用程序之间对话的语言

总结

一、传输协议就是应用程序之间对话的语言。

二、如何“断句”?

1、定义一个分隔符

  在数据传输的过程中,无论你定义什么字符作为分隔符,理论上,它都有可能会在传输的数据中出现。为了区分“数据内的分隔符”和真正的分隔符,你必须得在发送数据阶段,加上分隔符之前,把数据内的分隔符做转义,收到数据之后再转义回来。这是个比较麻烦的过程,还要损失一些性能。

2、预置长度

  • 给每句话前面加一个表示这句话长度的数字,收到数据的时候,我们按照长度来读取就可以了
  • 实现起来要比分隔符的方法简单很多,性能也更好,是目前普遍采用的一种分隔数据的方法。

3、用双工收发协议提升吞吐量

  • TCP 连接它是一个全双工的通道,你可以同时进行数据的双向收发,互相是不会受到任何影响的。要提高吞吐量,应用层的协议也必须支持双工通信。
  • “使用 ID 来标识请求与响应对应关系”的方法,是一种比较通用的实现双工通信的方法,可以有效提升数据传输的吞吐量。

 

第十四课 14 | 内存管理:如何避免内存溢出和频繁的垃圾回收

总结

一、自动内存管理机制的实现原理

二、为什么在高并发下程序会卡死?

1、短时间内就会创建大量的对象,这些对象将会迅速占满内存,这时候,由于没有内存可以使用了,垃圾回收被迫开始启动,并且,这次被迫执行的垃圾回收面临的是占满整个内存的海量对象,它执行的时间也会比较长,相应的,这个回收过程会导致进程长时间暂停。

2、进程长时间暂停,又会导致大量的请求积压等待处理,垃圾回收刚刚结束,更多的请求立刻涌进来,迅速占满内存,再次被迫执行垃圾回收,进入了一个恶性循环。如果垃圾回收的速度跟不上创建对象的速度,还可能会产生内存溢出的现象。

三、高并发下的内存管理技巧

1、优化你的代码中处理请求的业务逻辑,尽量少的创建一次性对象

2、对于需要频繁使用,占用内存较大的一次性对象,我们可以考虑自行回收并重用这些对象。实现的方法是这样的:我们可以为这些对象建立一个对象池。收到请求后,在对象池内申请一个对象,使用完后再放回到对象池中,这样就可以反复地重用这些对象,非常有效地避免频繁触发垃圾回收。

3、如果可能的话,使用更大内存的服务器,也可以非常有效地缓解这个问题。

4、当然,要从根本上来解决这个问题,办法只有一个,那就是绕开自动垃圾回收机制,自己来实现内存管理。  

  自行管理内存将会带来非常多的问题,比如说极大增加了程序的复杂度,可能会引起内存泄漏等等。

 

思考

1、如果我们的微服务的需求是处理大量的文本,比如说,每次请求会传入一个 10KB 左右的文本,在高并发的情况下,你会如何来优化这个程序,来尽量避免由于垃圾回收导致的进程卡死问题?

答: 

  如果有一个微服务是处理大量的文本,感觉这种一般不会要求时延,大部分都会进行异步处理,更加注重服务的吞吐率,服务可以在更大的内存服务器进行部署,然后把新生代的eden设置的更大些,因为这些文本处理完不会再拿来复用,朝生夕灭,可以在新生代Minor GC,防止对象晋升到老年代,防止频繁的Major GC,如果晋升的对象过多大于老年代的连续内存空间也会有触发Full Gc,然后在这些处理文本的业务流程中,防止频繁的创建一次性的大对象,把文本对象做为业务流程直接传递下去,如果这些文本需要复用可以将他保存起来,防止频繁的创建。也为了保证服务的高可用,也需对服务做限流、负载、兜底的一些策略。

posted @ 2023-03-01 11:39  r1-12king  阅读(24)  评论(0编辑  收藏  举报