Netty 基本概念以及核心组件
Netty 概述Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发高性能、高可靠的网络 IO 程序。Netty 是基于 NIO 的,它封装了 jdk 的 NIO,让我们使用起来更加方法灵活。
Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络 IO 程序。
Netty 本质是一个基于 Java NIO 的框架,适用于服务器通讯相关的多种应用场景。
Netty 主要针对在 TCP 协议下,面向客户端的高并发应用,或者 Peer-to-Peer 场景下的大量数据持续传输的应用。
NIO 的存在的问题(缺点)
Java NIO 的类库和 API 繁杂,使用麻烦,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
需要熟悉 Java 多线程编程。这是因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网络编程非常熟悉,才能写出高质量的 NIO 程序。
开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等 ...
Netty入门实战——TCP服务、群聊系统的实现
TCP 服务Netty 服务器在 6667 端口监听,客户端上线后发送消息给服务器,服务器接收并回复消息给客户端。总的来说就是一来一回。
NettyServer 服务器启动类12345678910111213141516171819202122232425262728293031323334353637public class NettyServer { private static final int PORT = 6667; public static void main(String[] args) { //1.创建两个线程组 BossGroup、WorkerGroup EventLoopGroup bossGroup = new NioEventLoopGroup(1); //只处理连接请求 EventLoopGroup workerGroup = new NioEventLoopGroup(); //处理与客户端的IO传输以及业务处理 try { //2.创 ...
NIO 网络编程——群聊系统实现
在使用 NIO 之前,我们先来看一下传统 BIO 阻塞 IO 的实现。
Java BIO 工作机制Java BIO 就是传统的 Java I/O 编程,其相关的类和接口在 java.io。
BIO(BlockingI/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。Java BIO 工作流程:
服务器端启动一个 ServerSocket。
客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯。
客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。
如果有响应,客户端线程会等待请求结束后,再继续执行。
Java BIO 应用实例
使用 BIO 模型编写一个服务器端,监听 6666 端口,当有客户端连接时,就启动一个线程与之通讯。
要求使用线程池机制改善,可以连接多个客户端。
服务器端可以接收客户端发送的数据(telnet 方式即可)。
1234567891011 ...
Java NIO 基本原理以及三大核心组件
I/O 模型Java 共支持 3 种网络编程 I/O 模型:BIO、NIO、AIO。
Java BIO:同步阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
Java NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理。
Java AIO(NIO.2):异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
需要注意的是,Java 的 NIO 并不等同于操作系统层面上的 NIO,Java NIO 实际上是基于 IO 多路复用模型的,同时所用的 NIO 组件在 Linux 系统上是使用 epoll 系统调用实现的。这一点我一开始也弄混了,看了书才搞清楚。
BIO、NIO、AIO 使用场景分析
BIO 方式适用于连接 ...
回顾我的第一次实习——少年的逐梦之旅
前记四月底了,暑期实习招聘也渐入尾声,这次春招获得的机会其实挺少的,但至少最心仪的公司都给了面试机会。可惜的是,虽然能够走到流程的最后,却还是因为各种原因与 offer 失之交臂。最终接了之前从未考虑过的公司的 offer。虽然挺可惜的,但人生还是要向前看,坚定地向未来奔跑,我相信付出终究能获得回报。
其实这次拿到的 offer 是我的第二份实习了,在去年 7 月开始,也就是大二暑假,我拿到了虎牙 Java 开发实习生的日常实习 offer,也开启了我的第一次实习之旅。现在也过去有半年了,之前一直没有写篇博客总结一番,所以这次有空便来回顾总结一下。
仓促的前期准备以及意外顺利的面试大二暑假开始了,因为工作室目前接不到外面的项目,所以师兄建议我去找份实习增加实践经历。这时的我才匆匆开始准备写简历,好在有师兄的指导和修改,一天之内做出了我的第一份简历。现在我面临的问题是,我该如何去找实习,该投哪些公司,该如何去做相应的准备。我用了几天的时间大致复习了 Java 技术栈的相关知识,同时在刷牛客的无意间,发现了一条虎牙公司的日常实习招聘,于是我便投出了我的第一份简历。
一面二面都是电话面试,出 ...
Redis高可用的基石——主从复制
Redis 支持主从同步,提供 Cluster 集群部署模式,通过 Sentinel 哨兵来监控 Redis 主节点的状态,以此来保证 Redis 的高可用,而主从复制正是高可用的基石。
一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。从节点的数据来自主节点,实现原理就是主从复制机制。
主从复制过程一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。从节点的数据来自主节点,实现原理就是主从复制机制。
主从复制过程大体可以分为3个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段。
连接建立阶段
保存主节点信息
建立socket连接:slave 将根据指定的 IP 地址和端口,向 master 发起套接字(socket)连接,master 在接受(accept) slave 的套接字连接之后,为该套接字创建相应的客户端状态,此时连接建立完成。
发送ping命令:slave 向 master ...
Redis设计与实现——对象
Redis 的底层数据结构主要包括简单动态字符串(SDS)、双端链表、字典、跳跃表、整数集合、压缩列表。Redis 并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每一种对象类型都至少采用了两种编码,不同的编码使用的底层数据结构也不同。通过这五种不同类型的对象,Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。
除此之外,Redis 的对象系统还实现了基于引用计数技术的内存回收机制:当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,Redis 还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。
对象的类型与编码Redis 使用对象来表示数据库中的键和值,每次当我们在 Redis 的数据库中新创建一个键值对时,我们至少会创建两个对象, ...
基于Redis3.0与6.0版本源码看SDS内存优化
实践中引发的思考最近在看《Redis 设计与实现》这本书,不由得赞叹 Redis 底层数据结构设计的精妙。在看到 Redis 对象章节时,我们知道 Redis 是使用对象来表示数据库中的键和值的,其中键总是字符串对象,而字符串对象的编码又可以是 int、raw 或者 embstr 。
关键点来了,书中对字符串对象是这么写的:
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于 39 字节,那么字符串对象将使用 embstr 编码的方式来保存这个字符串值。
看到这,我马上实践了一波,结果直接懵了。。。
当我创建了一个长度为 39 的字符串,编码为 embstr,这没有什么问题,但长度变为 41 的时候,此时编码应该转换为 raw 才对,然而并没有!
我的第一直觉便觉得应该就是版本问题,因为《Redis 设计与实现》是基于 Redis 3.0 版本的,而我之前专门看过 Redis 3.0 和 6.0 版本的 SDS 源码,已经知道 Redis 在 3.2 版本的时候对 SDS 进行了内存优化,很可能是因为这个原因导致编码转换的边界值发生改变。
上网一搜,看到这位博主 ...
Redis设计与实现——跳跃表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均 O(log N) 、最坏 O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
Redis 使用跳跃表作为有序集合键的底层实现之一:如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis 就会使用跳跃表来作为有序集合键的底层实现。
跳跃表的实现Redis 的跳跃表由 zskiplistNode 和 zskiplist 两个结构定义,zskiplistNode 结构用于表示跳跃表节点, zskiplist 结构用于保存跳跃表节点相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等。
如图展示了一个跳跃表示例, 位于图片最左边的是 zskiplist 结构, 该结构包含以下属性:
header :指向跳跃表的表头节点。
tail :指向跳跃表的表尾节点。
level :记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。
length :记录跳跃表的长度,也 ...
Redis设计与实现——字典(渐进式rehash细节)
字典是一种用于保存键值对的数据结构。在字典中,一个键(key)可以和一个值(value)进行关联(或者说将键映射为值),这些关联的键和值就称为键值对。
字典的实现Redis 的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行 rehash 过程中使用,一个哈希表里面可以有多个哈希表节点,每个哈希表节点就保存了字典中的一个键值对。
哈希表 hashtableRedis 字典所使用的哈希表由 dict.h/dictht 结构定义:
1234567891011121314typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used;} dictht;
table 属性是一个数组,数组中的每个元素都是一个 ...