Netty/Redis/zk高并发实战
2020-12-21 10:10:34 0 举报
AI智能生成
Netty/Redis/Zookeeper高并发原理知识总结
作者其他创作
大纲/内容
高并发时代必备技能
Netty为何这么火
主流框架都有使用Netty作为内部网络通讯,包括Kafka、RocketMq、Haoop Avro 、Dubbo等等.....
Netty提供异步、事件驱动的网络应用,所有IO操作都是异步非阻塞的。通过Future-Listener回调机制得到操作结果。
和原生态NIO相比,Netty封装过后,API操作十分简单,降低了开发门槛。
目前跟业内其他NIO框架相比,Netty目前是使用最广泛的,综合性能也是最优的。
高并发利器Redis
Redis主要通过(Key-Value Pair)的形式存储数据,类似于Java中的Map映射,Redis的Key键 只能存储字符串。
Redis Value值类型包括:string字符串、Map映射类型、list列表类型、set集合类型、sortedset有序集合类型。
缓存使用场景:数据查询、短连接、新闻内容、商品类容等 以及分布式回话、聊天室在线好友列表、任务队列(秒杀、抢购、12306等)、应用排行榜、访问统计、数据过期处理(可以精确到毫秒)
Redis成为缓存事实标准的原因
内存查询速度快,数据量受限于物理内存。
可持久化支持RDB与AOF两种方式,将数据写入物理内存。
支持主从复制、双主
高并发IO底层原理
IO读写基础原理
基础知识:read系统调用、write调用只会涉及到进程缓冲区,和内核缓冲区之间的读写,并不达到物理内存的读写。
缓冲区的目的是为了减少频繁与设备之间的的物理交换,毕竟直接写外部设备,涉及的点比较多而且效率低。
四种重要的IO模型
同步阻塞IO
阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户的操作
非同步阻塞IO
阻塞是指用户空间(调用线程)一直在等待,而不能干别的事情;非阻塞是指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间,IO操作可以干就干,不可以干,就去干别的事情
IO多路复用
即经典的Reactor反应器设计模式,有时也称为异步阻塞IO, Java中的Selector选择器和Linux中的epoll都是这种模型。
异步IO
指的是用户控件与内核控件调用方式反过来。用户控件的线程向内核控件注册了各种IO事件的回调函数,由内核去主动调用。
通过合理配置来支持百万级并发连接
解除句柄限制,Linux一个进程系统默认值是1024,也就是说最多只能接受1024个socket连接。
Java NIO通信基础详解
Java NIO简介
NIO由三个核心类组成:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
详解NIO Buffer
应用程序与通道(Channel)主要的交互操作,就是进行数据的Read读取和Write写入。
通道的读取,就是将数据从通道读取到缓冲区中;通道的写入,就是将数据从缓冲区写入到通道中,缓冲区的使用是面向OIO、Nio非阻塞最基础组件之一。
详解NIO Channel(通道)类
所有的NIO的操作都是从通道开始的。一个通道类似于OIO中两个留的结合,既可以从通道读取,也可以向通道写入。
详解NIO Selector选择器
什么是IO多路复用?指的是一个进程/线程可以同时见识多个文件描述符(一个网络连接,操作系统底层使用的一个文件描述符来标识)一旦其中一个或者多个文件描述符可读可写,系统内核就通知该进程、线程。
java通过selector这个组件来监控多文件描述符的状态,它是一个IO事件的查询器。通过选择器,一个线程就可以查询多个通道的IO时间的就绪状态。
一个选择器只需要一个线程进行监控,我们可以很简单的通过选择器管理多个通道,非常高效,这种高效的选择器来自于Selector组件,当然根据底层的select/poll/epoll性能是有差异的。相比OIO 使用选择器的优势是系统开销小,系统不必为了每一个网络连接(文件描述符)创建进程/线程。
Reactor反应堆模式
Reactor反应堆模式为何重要
业内比较高性能的组件内核都有使用Reactor反应堆模式,包括Nginx/Redis/Netty
Reactor基本逻辑
Reactor反应器:负责查询IO事件,当检测到一个IO实践,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件。
Handler处理器:与IO事件(或者选择键)绑定,负责IO事件的处理。完成真正的连接建立,通道读取、处理业务逻辑、负责将结果写出到通道。
单线程反应堆模式
单线程Reactor相对传统的OIO模型,反应器模式不需要启动成千万的线程来提高效率,一个线程即可。
由于单线程处理,所有Handler都是串行处理的,如果有一个Handler处于阻塞,会导致其他Handler处理不到。一旦AcceptorHandler处理器阻塞,会导致整个服务不能接受到连接。
目前的服务器都是多核的,单线程反应器模式模型不能充分利用多核资源。总之,在高性能服务器应用场景中,单线程反应器模式实际使用的很少
多线程反应堆模式
首先多线程Reactor是考虑引进多个Selector选择器,来提升大量通道能力。
Handler处理,主要是引用线程池来提升执行效率。
多核CPU的情况下,应用多个选择器,能充分释放了系统资源能力,也能提高了反应器管理大量连接,提升选择器大量通道的能力。
并发基础Future异步回调模式
Join异步阻塞
Join主要是异步执行线程,合并线程结果,如果某个现在执行慢,在在调用JOIN线程方阻塞,等待执行完。
所以是异步调用,调用线程如果执行慢会在当前线程阻塞,还有就是拿不到直接执行结果。
FutureTask异步回调
FutureTask类的实现比join线程合并操作更加高明,能获取得到异步线程的结果。
FutureTask类的get方法,获取异步结果的同事,主线程也会被阻塞,这点,FutureTask和join也是一样的,他们两都是异步阻塞模式。
FutureTask是阻塞的异步回调,调用线程是阻塞的,在获取异步结果的过程中,一直阻塞,等待异步线程返回结果。
Guava的异步回调
实现Java的Callable接口,创建异步执行逻辑。还有一种情况,如果不需要返回值,异步执行逻辑也可以实现Java的Runnable接口。
创建Guava线程池
将第1步创建的Callable/Runnable异步执行逻辑的实例,通过submit提交到Guava线程池,从而获取ListenableFuture异步任务实例。
创建FutureCallback回调实例,通过Futures.addCallback将回调实例绑定到ListenableFuture异步任务上。
完成以上四步,当Callable/Runnable异步执行逻辑完成后,就会回调异步回调实例FutureCallback的回调方法onSuccess/onFailure。
异步执行线程、监听线程、通知异步回调结果。
Guava是非阻塞的异步调用,调用线程是非阻塞的,可以继续执行自己的业务逻辑。
Netty的异步回调
继承java的Future接口,得到了一个新的属于Netty自己的Future异步任务接口;该接口对原由的接口进行了增强,使得Netty异步任务能够以非阻塞的方式处理回调的结果。
引入了GenercFutureListener 用于表示异步执行完成的监听器,这个接口与Guava的FutureCallback的调用不同,Netty使用了监听器的模式,异步任务执行完成后的回调逻辑抽象成了Listener监听器接口。总体上来说与Guava的思路是一致的。
Netty原理与基础
解密Netty中的Reactor反应堆模式
详解Bootstrap启动类
详解Handler业务处理器
在Reactor反应器模式中,反应器查询到的IO事件后,分发到Handler业务处理器,由Handler完成IO操作和业务处理。
整个IO操作环节包括从通道读数据包、数据包接码、业务处理、目标数据编码、吧数据包写到通道、然后通道发送到对端。
入站处理(ChannelInboundHandler)
channelRegistered
当通道注册完成后,Netty会调用fireChannelRegistered,触发通道注册事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelRegistered方法,会被调用到。
channelActive
当通道激活完成后,Netty会调用fireChannelActive,触发通道激活事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelActive方法,会被调用到。
channelRead
当通道缓冲区可读,Netty会调用fireChannelRead,触发通道可读事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelRead方法,会被调用到。
channelReadComplete
当通道缓冲区读完,Netty会调用fireChannelReadComplete,触发通道读完事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelReadComplete方法,会被调用到。
channelInactive
当连接被断开或者不可用,Netty会调用fireChannelInactive,触发连接不可用事件。通道会启动对应的流水线处理,在通道注册过的入站处理器Handler的channelInactive方法,会被调用到。
exceptionCaught
当通道处理过程发生异常时,Netty会调用fireExceptionCaught,触发异常捕获事件。通道会启动异常捕获的流水线处理,在通道注册过的处理器Handler的exceptionCaught方法,会被调用到。注意,这个方法是在通道处理器中ChannelHandler定义的方法,入站处理器、出站处理器接口都继承到了该方法。
出站处理(ChannelOutboundHandler)
bind
监听地址(IP+端口)绑定:完成底层Java IO通道的IP地址绑定。如果使用TCP传输协议,这个方法用于服务器端。
connect
连接服务端:完成底层Java IO通道的服务器端的连接操作。如果使用TCP传输协议,这个方法用于客户端。
write
写数据到底层:完成Netty通道向底层Java IO通道的数据写入操作,此方法仅仅是触发操作而已,并不是完成实际的写入操作。
flush
腾空缓冲区的数据,把这些数据写到对端,将底层缓冲区的数据腾空,立即写出到对端。
read
从底层读数据:完成Netty通道从Java IO通道的数据读取。
DisConnect
断开服务器连接:断开底层Java Io通道的服务器连接。如果使用TCP传输协议,次方法要用于客户端。
close
主动关闭通道:关闭底层通道,列如服务器端的新连接监听通道。
详解Pipeline流水线
Netty特殊组件,叫做ChannelPipeline(通道流水线),它就像管道,将绑定一个通道的多个Handler处理实例,串在一起,形成一条流水线,ChannelPipeline(通道流水线)的默认实现,实际被设计成一个双向链表。所有的Handler处理器被包装成了双向链表的节点,被加入到ChannelPipeline(通道流水线)中。
每一个来自通道的IO事件,都会进入一次ChannelPipeline通道流水线。在进入第一个Handler处理器后,这个IO事件将按照既定的从前往后次序,在流水线上不断地向后流动,流向下一个Handler处理器
详解ByteBuf缓冲区
与java Nio的ByteBuffer相比 Netty ByteBuf的优势如下
Pooling(池化,这点减少了内存复制和GC,提升了效率)
复合缓冲区类型,支持零复制
不需要调用flip()方法去切换读写模式
扩展性好,列如string Buffer
可以自定义缓冲区类型
读取和写入索引分开
方法链式调用
方法可以进行引用计数,方便重复使用。
ByteBuf浅层复制的高级使用方式
slice切片浅层复制
ByteBuf的slice方法可以获取到一个ByteBuf的一个切片。一个ByteBuf可以进行多次的切片浅层复制;多次切片后的ByteBuf对象可以共享一个存储区域。
duplicate整体浅层复制
和slice切片不同,duplicate() 返回的是源ByteBuf的整个对象的一个浅层复制
Decoder与Encoder重要组件
开箱即用的Netty内置Decoder
它能将上一站Inbound入站处理器传过来的输入(Input)数据,进行数据的解码或者格式转换,然后输出(Output)到下一站Inbound入站处理器。
一个标准的解码器将输入类型为ByteBuf缓冲区的数据进行解码,输出一个一个的Java POJO对象。Netty内置了这个解码器,叫作ByteToMessageDecoder,位在Netty的io.netty.handler.codec包中。
Json和ProtoBuf序列化
详解粘包和拆包
粘包,指接受端(Receiver)接收到的ByteBuf,包含了多个发送端(Sender)的ByteBuf,多个ByteBuf“粘”在一起了。
半包,就是接收端将一个发送端的ByteBuf“拆”开了,收到多个破碎的包。换句话来说,一个接收端接收到的ByteBuf是发送端的一个ByteBuf的一部分。
Json协议通信
JSON格式是直观的序列化方式,在实际开发中,尤其是RESTful进行远程交互应用开发比较多。一般来说使用JSON开发包FastJson、谷歌的GSON比较多。
Protobuf协议通信
Protobuf格式方式非直管的二进制序列化方式,效率比较高,主要用于高性能通信开发。
Zookeeper分布式协调
ZK进行分布式存储
Zookeeper的存储模型非常简单,他和Linux的文件系统非常类似。简单的来说Zookeeper的存储模型是一颗以/为根节点的树。Zookeeper每个节点都叫做Znode,所有的ZNode节点通过树形的目录结构,安装层次关系组成在一起,构成一颗ZNode树。
ZK为了保证高吞吐低延迟,整个树形目录结构全部都放在内存中。与硬盘和其他外存设备比,计算机的内存比较有限,使得ZK的目录结构不能用于存放大量的数据。ZK官方要求的是,每个节点存放的有限负载数据(Payload)的上线是1MB
分布式命名服务实践
Curator还为ZooKeeper客户端框架提供了一些比较普遍的、开箱即用的、分布式开发用的解决方案,例如Recipe、共享锁服务、Master选举机制和分布式计算器等,帮助开发者避免了“重复造轮子”的无效开发工作
分布式事件监听的重点
第一种标准的观察者模式是通过Watcher监听器实现的;第二种缓存监听模式是通过引入了一种本地缓存视图Cache机制去实现的。
第二种Cache事件监听机制,可以理解为一个本地缓存视图与远程ZooKeeper视图的对比过程,简单来说,Cache在客户端缓存了ZNode的各种状态,当感知到Zk集群的ZNode状态变化时,会触发事件,注册的监听器会处理这些事件。
分布式锁的原理
Zlock实现的主要价值是展示一下分布式锁的原理和基础开发。在实际的开发中,如果需要使用到分布式锁,不建议去自己“重复造轮子”,而建议直接使用Curator客户端中的各种官方实现的分布式锁,例如其中的InterProcessMutex可重入锁。
ZooKeeper实现的分布式锁,性能并不太高。为什么呢?因为每次在创建锁和释放锁的过程中,都要动态创建、销毁暂时节点来实现锁功能。大家知道,Zk中创建和删除节点只能通过Leader(主)服务器来执行,然后Leader服务器还需要将数据同步到所有的Follower(从)服务器上,这样频繁的网络通信,性能的短板是非常突出的。
在高性能、高并发的应用场景下,不建议使用ZooKeeper的分布式锁。而由于ZooKeeper的高可用性,因此在并发量不是太高的应用场景中,还是推荐使用ZooKeeper的分布式锁。
基于Redis的分布式锁。适用于并发量很大、性能要求很高而可靠性问题可以通过其他方案去弥补的场景。
基于ZooKeeper的分布式锁。适用于高可靠(高可用),而并发量不是太高的场景。
分布式缓存Redis
Redis数据类型
Redis中有5种数据类型:String(字符串类型)、Hash(哈希类型)、List(列表类型)、Set(集合类型)、Zset(有序集合类型)。
Jedis基础
JedisPool连接池
spring-data-redis使用
Spring的 redis缓存注解
@CachePut作用是设置缓存。先执行方法,并将执行结果缓存起来。
@CacheEvict的作用是删除缓存。在执行方法前,删除缓存。
@Cacheable的作用更多的是查询缓存。首先检查注解中的Kye键是否在缓存中如果是,则返回Key的换乘至,不再执行方法;否则,执行方法并将方法结果缓存起来。后半部分来看@Cacheable也具备@CachePut的能力。
0 条评论
下一页