首页 文章 精选 留言 我的

精选列表

搜索[分布式调度],共10000篇文章
优秀的个人博客,低调大师

io_uring技术在分布式云原生数据库中的应用

Part 1 - 背景 1.1异步I/O 异步I/O是计算机操作系统对输入输出的一种处理方式:发起I/O请求的线程不等待I/O操作完成,就继续执行随后的代码,I/O结果用其他方式通知发起I/O请求的程序。 与异步I/O相对的是更为常见的“同步(阻塞)I/O”:发起I/O请求的线程不从正在调用的I/O操作函数返回(即被阻塞),直至I/O操作完成。 同步IO机制存在着一定的弊端,例如:IO的实现都是在当前进程上下文的系统调用中完成的,会阻塞当前进程,降低系统的实时性,同时导致性能较低。 对于I/O密集型应用,同步I/O的弊端就会被放大,而异步I/O的优势便体现出来:(1)由内核来操作I/O,应用不再阻塞在I/O,可以执行其他逻辑,此时应用运行和I/O执行变成了并行的关系;(2)可以批量地进行I/O操作,让设备的能力得到最大发挥。 Linux内核提供的I/O机制大都是同步实现的,如常规的read/write/send/recv等系统调用。在引入io_uring之前,Linux系统下的异步I/O机制的实现主要为两种: (1)POSIX AIO。这种方案是用户态实现的异步I/O机制,其核心思想为:创建一个专门用来处理IO的线程,用户程序将I/O操作交给该线程来进行。这种方式实现的异步I/O效率和扩展性都比较差。 (2)LINUX AIO。Linux内核里也实现了一套异步I/O机制,被称为AIO。该机制的使用限制比较大,比如只支持direct IO而无法使用cache,且扩展性比较差。 1.2io_uring优势 随着Linux 5.1的发布,Linux终于有了自己好用的异步I/O实现,并且支持大多数文件类型(磁盘文件、socket,管道等),彻底解决了长期以来Linux AIO的各种不足,这就是io_uring。 相比于Linux传统的异步I/O机制,io_uring的优势主要体现在以下几个方面: (1)高效。一方面,io_uring采用了共享内存的方式来传递参数,减少了数据拷贝;另一方面,采用ring buffer的方式来实现批量的I/O请求,减少了系统调用的次数。 (2)可扩展性强。io_uring具有超强的可扩展性,具体表现在: •支持的I/O设备类型多样化,不仅支持块设备的IO,还支持任何基于文件的IO,例如套接口、字符设备等; •支持的I/O操作多样化,不仅支持常规的read/write,还支持send/recv/sendmsg/recvmsg/close/sync等大量的操作,而且能够很灵活地进行扩充。 (3)易用。io_uring提供了配套的liburing库,其对io_uring的系统调用进行了大量的封装,使得接口变得简单易用。 (4)可伸缩。io_uring提供了poll模式,对于I/O性能要求较高的场景,允许用户牺牲一定的CPU来获得更高的IO性能:低延迟、高IOPS。 经测试,相比于libaio,在poll模式下io_uring性能提升将近150%,堪比SPDK。在高QD的情况下,更是有赶超SPDK的趋势。 io_uring就是:一套全新的syscall,一套全新的async API,更高的性能,更好的兼容性,来迎接高IOPS,高吞吐量的未来。 Part 2 - io_uring基本实现 2.1基本原理 io_uring实现异步I/O的方式其实是一个生产者-消费者模型: (1)用户进程生产I/O请求,放入提交队列(Submission Queue,简称SQ)。 (2)内核消费SQ中的I/O请求,完成后将结果放入完成队列(Completion Queue,简称CQ)。 (3)用户进程从CQ中收割I/O结果。 SQ和CQ是内核初始化io_uring实例的时候创建的。为了减少系统调用和减少用户进程与内核之间的数据拷贝,io_uring使用mmap的方式让用户进程和内核共享SQ和CQ的内存空间。 另外,由于先提交的I/O请求不一定先完成,SQ保存的其实是一个数组索引(数据类型 uint32),真正的SQE(Submission Queue Entry)保存在一个独立的数组(SQ Array)。所以要提交一个I/O请求,得先在SQ Array中找到一个空闲的SQE,设置好之后,将其数组索引放到SQ中。 用户进程、内核、SQ、CQ和SQ Array之间的基本关系如下: 图1 io_uring机制的基本原理 2.2 io_uring的用户态 io_uring的实现仅仅使用了三个syscall:io_uring_setup, io_uring_enter和io_uring_register。它们分别用于设置io_uring上下文,提交并获取完成任务,以及注册内核用户共享的缓冲区。使用前两个syscall已经足够使用io_uring接口了。 2.2.1初始化 int io_uring_setup(int entries, struct io_uring_params *params); 内核提供了io_uring_setup系统调用来初始化一个io_uring实例,创建SQ、CQ和SQ Array,entries参数表示的是SQ和SQArray的大小,CQ的大小默认是2 * entries。params参数既是输入参数,也是输出参数。 该函数返回一个file descriptor,并将io_uring支持的功能、以及各个数据结构在fd中的偏移量存入params。用户根据偏移量将fd通过mmap内存映射得到一块内核用户共享的内存区域。这块内存区域中,有io_uring的上下文信息:SQ信息、CQ信息和SQ Array信息。 图2 fd映射到用户态 io_uring_setup设计的巧妙之处在于,内核通过一块和用户共享的内存区域进行消息的传递。在创建上下文后,任务提交、任务收割等操作都通过这块共享的内存区域进行,可以完全绕过Linux的syscall机制去完成需要内核介入的操作,大大减少了syscall切换上下文、刷TLB的开销。 2.2.2 I/O提交与收割 初始化完成之后,我们需要向io_uring提交I/O请求。io_uring可以处理多种I/O相关的请求。比如: 文件相关:read, write, open, fsync,fallocate, fadvise, close 网络相关:connect, accept, send, recv,epoll_ctl 默认情况下,使用 io_uring 提交 I/O 请求需要: (1)从SQ Arrary中找到一个空闲的SQE; (2)根据具体的I/O请求设置该SQE; (3)将SQE的数组索引放到SQ中; (4)调用系统调用io_uring_enter提交SQ中的I/O请求。 图3 io_uring请求的提交与收割 在我们提交I/O请求的同时,使用同一个io_uring_enter系统调用就可以回收完成状态,这样的好处就是一次系统调用接口就完成了原本需要两次系统调用的工作,大大的减少了系统调用的次数,也就是减少了内核核外的切换,这是一个很明显的优化,内核与核外的切换极其耗时。 当I/O完成时,内核负责将完成I/O在SQ Array中的index放到CQ中。由于I/O在提交的时候可以顺便返回完成的I/O,所以收割I/O不需要额外系统调用。 如果使用了IORING_SETUP_SQPOLL参数,I/O收割也不需要系统调用的参与。由于内核和用户态共享内存,所以收割的时候,用户态遍历已经完成的I/O队列,然后找到相应的CQE并进行处理,最后移动head指针到tail,IO收割至此而终。 所以,在最理想的情况下,IO提交和收割都不需要使用系统调用。 2.3 io_uring的内核态 io_uring在创建时有两个选项,对应着io_uring处理任务的不同方式: (1)开启IORING_SETUP_IOPOLL,io_uring会使用轮询的方式执行所有的操作; (2)开启IORING_SETUP_SQPOLL,io_uring会创建一个内核线程专门用来收割用户提交的任务。 这些选项的设定会影响用户与io_uring交互的方式: (1)都不开启,通过io_uring_enter提交任务,收割任务无需syscall; (2)开启IORING_SETUP_IOPOLL,通过io_uring_enter提交和收割任务; (3)开启IORING_SETUP_SQPOLL,无需任何syscall即可提交、收割任务。内核线程在一段时间无操作后会休眠,可以通过io_uring_enter唤醒。 2.3.1基于轮询的任务执行 创建io_uring时指定IORING_SETUP_SQPOLL选项即可开启I/O轮询模式。在轮询模式下,io_uring_enter只负责把操作提交到内核的文件读写队列中。之后,用户需要调用io_uring_enter获取完成事件,内核会使用轮询方式不断检查I/O设备是否已经完成请求,而非等待设备通知。通过这种方式,能够尽可能快地获取设备I/O完成情况,开始后续的I/O操作。 2.3.2基于内核线程的任务执行 同时,在内核中还支持了一个内核I/O模式,通过IORING_SETUP_SQPOLL标志设置。在这个模式下,io_uring会启动一个内核线程,循环访问和处理请求队列。内核线程与用户态线程不同,不能在没有工作时无条件的无限循环等待,因此当内核线程持续运行一段时间没有发现I/O请求时,就会进入睡眠。如果内核线程进入睡眠,会通过I/O请求队列的flag字段IORING_SQ_NEED_WAKEUP通知用户态程序,用户态程序需要在有新的I/O请求时通过带IORING_ENTER_SQ_WAKEUP标识的io_uring_enter调用来唤醒内核线程继续工作。 如果IORING_SETUP_IOPOLL和IORING_SETUP_SQPOLL同时设置,内核线程会同时对io_uring的队列和设备驱动队列做轮询。在这种情况下,用户态程序又不需要调用io_uring_enter来触发内核的设备轮询了,只需要在用户态轮询完成事件队列即可,这样就可以做到对请求队列、完成事件队列、设备驱动队列全部使用轮询模式,达到最优的I/O性能。当然,这种模式会产生更多的CPU开销。 通过对上述对io_uring实现原理的介绍,可以总结出,io_uring之所以拥有如此出众的性能,主要来源于以下几个方面: (1)用户态和内核态共享提交队列和完成队列; (2)I/O提交和收割可以offload给Kernel,且提交和完成不需要经过系统调用; (3)支持Block层的Polling模式 (4)通过提前注册用户态内存地址,减少地址映射的开销 (5)相比libaio,完美支持buffered I/O Part 3 -云溪数据库中的应用 云溪数据库使用Pebble作为默认存储引擎,Pebble底层使用同步I/O机制。为了提升Pebble引擎的性能,可以考虑从改变I/O模型的角度入手,使用支持异步I/O的io_uring机制。 Pebble涉及磁盘I/O的主要文件类型和操作包括:WAL文件的读写、SST文件在flush和compaction时的读写以及MANIFEST文件的读写。在Pebble原有的虚拟文件系统中,文件系统类通过调用os接口打开文件,文件操作类通过调用传统io接口实现文件的读、写、同步、关闭等操作。要想实现将io_uring机制集成到Pebble中,需要新增一个虚拟文件系统类,对其中的文件系统类和文件操作类进行重写,同时需要对io_uring进行封装,负责处理各个文件生成的io_uring读写任务。因此,实现方案主要分为三个部分:1、使用io_uring机制的文件系统实现类;2、使用io_uring机制的文件操作实现类;3、处理io_uring任务的实现类。 图4 Pebble集成io_uring的实现方案 3.1文件系统类 基于Pebble的vfs.defaultFS实现,需要改写Create、Open以及ReuseForWrite三个函数,即对应创建文件、打开文件以及再利用重写文件三种操作。改造点可以概括为两处:1、调用os.OpenFile打开文件时使用direct_io模式;2、函数返回的File对象具体为使用io_uring机制实现的文件操作类。 3.2文件操作类 基于Pebble的vfs.File类进行实现,由于文件是以direct_io模式打开的,只支持大小为512字节整数倍的读写操作。因此,需要对文件操作类的结构进行改造,以及对文件操作类中涉及读写操作的函数进行改造,包括:Read、ReadAt、Write以及Sync。 3.2.1文件操作类结构改造 主要改造点有:1、定义两个数组对象,一个用于保存文件的写入内容,另一个用于读写操作时不满512字节的缓存处理,大小为512字节;2、定义三个整型对象,分别用于记录读、写以及落盘的偏移量;3、定义waitgroup对象,用于确保每次文件Sync时,所有的io_uring写任务已经处理完成。 3.2.2文件操作类函数改造 (1)Read。Read函数逻辑为:记录文件读取的偏移量,每次调用Read函数时,以保存的偏移量为起始位置对文件进行读取,直至文件结束或者传入的保存读取内容的数组已经读满。由于direct_io只支持512字节大小的读取,因此需要对读文件的起始位置和结束位置做一些特殊处理。首先文件读取的起始偏移量必须为512字节的整数倍,当记录的文件读取偏移量不满足此条件时,需要将起始偏移量前移至最大的满足512字节整数倍的位置。其次,由于读取文件长度必须为512字节的整数倍,需要根据传入数组的长度和文件大小,将读文件的结束位置后移至最小的满足512字节整数倍的位置。确定文件读取位置后,提交io_uring读任务,并创建对应的一个waitgroup,等待读操作完成。当读操作完成后,需要对读取内容进行截取处理,可以理解为“掐头去尾”,最后将真正需要的读取内容返回。 (2)ReadAt。改造思想与Read函数同理。 (3)Write。由于文件是以direct_io模式打开的,即文件读写不会经过操作系统缓存,写操作先将内容写到内存,即文件操作类中定义的数组对象中,当写入内容达到一定数量后,会提交一次io_uring写任务,将文件内容写入磁盘,这样做可以达到减少I/O次数的目的。提交io_uring写任务时,会传入文件操作类中的waitgroup对象,但与读操作不同的是,写操作不需要等待任务处理完成,提交任务后可以直接返回。 (4)Sync。根据记录的落盘偏移量,将当前内存中未落盘的全部文件内容提交io_uring写任务,并传入文件操作类中的waitgroup对象,等待I/O任务完成。针对不满512字节的数据,需要进行特殊处理:将这部分文件内容写入文件操作类中大小为512字节的数组对象,不足512字节的部分补0,并创建对应的一个waitgroup,将这部分文件内容再提交一次io_uring写任务,并等待任务完成。不足512字节的提交,不会更新当前的落盘偏移量,这部分文件内容会跟随后续写入的内容再次落盘。因此落盘的起始位置永远为512字节的整数倍,使用时不需要进行特殊处理。 3.2.3处理io_uring任务的实现类 基本思想是实现一个io_uring封装类,使用单例模式创建唯一对象,所有文件的io_uring读写请求都通过该对象提交到内核中。实现方式是定义一个队列,该队列负责接收从多个线程发送过来的io_uring任务。该对象在初始化时,需要开启一个协程,在Pebble运行的生命周期中,不断地循环从队列头部获取io_uring任务提交到内核,同时不断地循环从io_uring的CQ队列中获取已经写入磁盘结束的io_uring任务,调用waitgroup.Done来唤醒等待的线程继续处理,通知发起请求的函数I/O操作已经完成。 3.2.4 WAL场景优化 Pebble中对文件系统Create的文件进一步封装成LogWriter类,导致在写WAL时两次将数据写入内存,影响性能。因此,需要针对WAL场景定制化一个文件操作类,用于提升WAL文件的写入性能,仅需要重写Write逻辑。 参考: 1.Getting Hands on with io_uring using Go 2.io_uring技术的分析与思考 3.高性能异步IO机制:IO_URING 4.一篇文章带你读懂 io_uring 的接口与实现 5.Linux 文件 I/O 进化史(四):io_uring—— 全新的异步 I/O 6.《操作系统与存储:解析Linux内核全新异步IO引擎——io_uring设计与实现》(一)

优秀的个人博客,低调大师

给学习加点实践,开发一个分布式IM(即时通信)系统!

持续坚持原创输出,点击蓝字关注我吧 作者:小傅哥博客:https://bugstack.cn ❝ 沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞ 目录 一、前言 二、演示 三、系统设计 四、UI开发 1. 整体结构定义、侧边栏 2. 对话聊天框 3. 好友栏 4. 事件定义 五、通信设计 1. 系统架构 2. 通信协议 3. 添加好友 4. 消息应答 5. 断线重连 6. 集群通信 六、源码下载🎉 七、总结 一、前言 这知识学的,根本没有忘的快呀?! 是不是感觉很多资料,点收藏起来爽、看视频时候嗨、读文章当时会,只要过了那个劲,就完了,根本不记得这里面都讲了啥。时间浪费了,东西还没学到手,这是为啥? 其实因为学习也分为上策、中策和下策: 下策:眼睛看就行,坐着、窝着、躺着,都行,反正也不累,还能一边回复下吹水的微信群 中策:看完的资料做笔记整理归纳,长期积累资料 上策:实践、上手、应用、调试、归纳、整理资料,总结经验输出文档 综上,下策学起来很快感觉自己好像会了不少,中策有点要动手了懒不想动,上策就很耗时耗力了要自己对每一个知识点都能事必躬亲到亲力亲为。就这样你在学习的时候不自觉的就选择了下策,因此其实并没有学到什么。 学习能把知识学到手,讲究的是实践,在小傅哥编写的文章中,基本都是以实践代码验证结果为核心,讲述文章内容。😁从小我就喜欢动手,就以一个即时通信的项目为例,已经基于不同技术方案实现了5、6次,仅为了实践技术,截图如下: 有些是刚学完Socket和Swing的时候,想动手试试这些技术能不能写个QQ出来。 也有的是因为实习培训需要完成的项目,不过在有了一些基础后,一周时间就能写完全部功能。 虽然这些项目在现在看上去还是丑丑的界面,以及代码逻辑可能也不是那么完善。但放在学习阶段的每一次实现中,都能为自己带来很多技术上的成长。 那么,这次IM实践的机会给你,希望你能用的上!接下来我会给你介绍一个IM的系统架构、通信协议、单聊群聊、表情发送、UI事件驱动等各项内容,以及提供全套的源码让你可以上手学习。 二、演示 在开始学习之前,先给大家演示下这套仿照PC端微信界面的IM系统运行效果。 聊天页面 添加好友 视频演示 三、系统设计 在这套IM中,服务端采用DDD领域驱动设计模式进行搭建。将 Netty 的功能交给 SpringBoot 进行启停控制,同时在服务端搭建控制台可以非常方便的操作通信系统,进行用户和通信管理。在客户端的建设上采用UI分离的方式进行搭建,以保证业务代码与UI展示分离,做到非常易于扩展的控制。 另外在功能实现上包括;完美仿照微信桌面版客户端、登录、搜索添加好友、用户通信、群组通信、表情发送等核心功能。如果有对于实际需要使用的功能,可以按照这套系统框架进行扩展。 UI开发:使用 JavaFx与 Maven搭建UI桌面工程,逐步讲解登录框体、聊天框体、对话框、好友栏等各项UI展示及操作事件。从而在这一章节中让Java 程序员学会开发桌面版应用。 架构设计:在这一章节中我们会使用DDD领域驱动设计的四层模型结构与Netty结合使用,架构出合理的分层框架。同时还有相应库表功能的设计。相信这些内容学习后,你一定也可以假设出更好的框架。 功能实现:这部分我们主要将通信中的各项功能逐步实现,包括;登录、添加好友、对话通知、消息发送、断线重连等各项功能。最终完成整个项目的开发,同时也可以让你从实践中学会技能。 四、UI开发 1. 整体结构定义、侧边栏 聊天窗体,相对于登陆窗体来说,聊天窗体的内容会比较多,同时也会相对复杂一些。因此我们会分章节的逐步来实现这些窗体以及事件和接口功能。在本篇文章中我们会主要讲解聊天框体的搭建以及侧边栏 UI 开发。 首先是我们整个聊天主窗体的定义,是一块空白面板,并去掉默认的边框按钮 (最小化、退出等) 之后是我们左侧边栏,我们称之为条形 Bar,功能区域的实现。 最后添加窗体事件,当点击按钮时变换 内容面板 中的填充信息。 2. 对话聊天框 对话框选中后的内容区域展现,也就是用户之间信息发送和展现。从整体上看这是一个联动的过程,点击左侧的对话框用户,右侧就有相应内容的填充。那么右侧被填充对话列表 ListView 需要与每一个对话用户关联,点击聊天用户的时候,是通过反复切换填充的过程。 点击左侧的每一个对话框体,右侧聊天框填充内容即随之变化。同时还有相应的对话名称也会也变化。 对话框中左侧展示好友发送的信息,右侧展示个人发送的信息。同时消息内容会随着内容的增多而增加高度和宽度。 最下面是文本输入框,在后面的实现里我们文本输入框采用公用的方式进行设计,当然你也可以设计为单独的个人使用。 3. 好友栏 大家都经常使用 PC 端的微信,可以知道在好友栏里是分了几段内容的,其中包含;新的朋友、公众号、群组和最下面的好友。 最上面的搜索框这部分内容不变,和前面的一样。我们目前使用的方式是 fxml 设计,例如这部分是通用功能,可以抽取出来放到代码中,设计成一个组件元素类。 经过我们的分析,在使用 JavaFx 组件开发为基础下,这部分是一种嵌套 ListView,也就是最底层的面板是一个 ListView,好友和群组有各是一个 ListView,这样处理后我们会很方便的进行数据填充。 另外这样的结构主要有利于在我们程序运行过程中,如果你添加了好友,那么我们需要将好友信息刷新到好友栏中,而在数据填充的时候,为了更加便捷高效,所以我们设计了嵌套的 ListView。如果还不是特别理解,可以从后续的代码中获得答案。 4. 事件定义 在桌面版 UI 开发中,为了能使 UI 与业务逻辑隔离,需要在我们把 UI 打包后提供出操作界面的展示效果的接口以及界面操作事件抽象类。那么可以按照下图理解; 序号 接口名 描述 1 void doShow() 打开窗口 2 void setUserInfo(String userId, String userNickName, String userHead) 设置登陆用户 ID、昵称、头像 3 void addTalkBox(int talkIdx, Integer talkType, String talkId, String talkName, String talkHead, String talkSketch, Date talkDate, Boolean selected) 填充对话框列表 4 void addTalkMsgUserLeft(String talkId, String msg, Date msgData, Boolean idxFirst, Boolean selected, Boolean isRemind) 填充对话框消息 - 好友 (别人的消息) 以上这些接口就是我们目前 UI 为外部提供的所有行为接口,这些接口的一个链路描述就是;打开窗口、搜索好友、添加好友、打开对话框、发送消息。 五、通信设计 1. 系统架构 在前面我们说到更适合的架构,才是符合你当下需要最好的架构。那么怎么设计这样架构呢,基本就是要找到符合点的目标。我们之所以这样设计是为什么,那么在这个系统里有如下几点; 我们系统在服务端要有 web 页面进行管理通信用户以及服务端的控制和监控。 数据库的对象类,不要被外部污染,要有隔离性。比如说;你的数据库类暴漏给外部做展示类使用了,那么现在需要增加一个字段,而这个字段又不是你数据库存在的属性。那么这个时候就已经把数据库类污染了。 因为目前我们都是在 Java 语言下实现 Netty 通信,那么服务端与客户端都会需要使用到通信过程中的协议定义和解析。那么我们需要抽离这一层对外提供 Jar 包。 接口、业务处理、底层服务、通信交互,要有明确的区分和实现,避免造成混乱难以维护。 结合我们上面这四点的目标,你头脑中有什么模型结构体现了呢?以及相应的技术栈选择上是否有计划了?接下来我们会介绍两种架构设计的模型,一种是你非常熟悉的 MVC,另外一种是你可能听说过的 DDD 领域驱动设计。 2. 通信协议 从图稿上来看,我们在传输对象的时候需要在传输包中添加一个 帧标识 以此来判断当前的业务对象是哪个对象,也就可以让我们的业务更加清晰,避免使用大量的 if 语句判断。 协议框架 agreement└──src├──main│├──java││└──org.itstack.naive.chat││├──codec│││├──ObjDecoder.java│││└──ObjEncoder.java││├──protocol│││├──demo│││├──Command.java│││└──Packet.java││└──util││└──SerializationUtil.java│├──resources││└──application.yml│└──webapp│└──chat│└──res│└──index.html└──test└──java└──org.itstack.demo.test└──ApiTest.java 协议包 publicabstractclassPacket{privatefinalstaticMap<Byte,Class<?extendsPacket>>packetType=newConcurrentHashMap<>();static{packetType.put(Command.LoginRequest,LoginRequest.class);packetType.put(Command.LoginResponse,LoginResponse.class);packetType.put(Command.MsgRequest,MsgRequest.class);packetType.put(Command.MsgResponse,MsgResponse.class);packetType.put(Command.TalkNoticeRequest,TalkNoticeRequest.class);packetType.put(Command.TalkNoticeResponse,TalkNoticeResponse.class);packetType.put(Command.SearchFriendRequest,SearchFriendRequest.class);packetType.put(Command.SearchFriendResponse,SearchFriendResponse.class);packetType.put(Command.AddFriendRequest,AddFriendRequest.class);packetType.put(Command.AddFriendResponse,AddFriendResponse.class);packetType.put(Command.DelTalkRequest,DelTalkRequest.class);packetType.put(Command.MsgGroupRequest,MsgGroupRequest.class);packetType.put(Command.MsgGroupResponse,MsgGroupResponse.class);packetType.put(Command.ReconnectRequest,ReconnectRequest.class);}publicstaticClass<?extendsPacket>get(Bytecommand){returnpacketType.get(command);}/***获取协议指令**@return返回指令值*/publicabstractBytegetCommand();} 3. 添加好友 从上面的流程中可以看到,这里包含了两部分内容;(1) 搜索好友,(2) 添加好友。当天就完成好友后,好友会出现到我们的好友栏中。 并且这里面我们采用的是单方面同意加好友,也就是你添加一个好友的时候,对方也同样有你的好友信息。 如果你的业务中是需要添加好友并同意的,那么可以在发起好友添加的时候,添加一条状态信息,请求加好友。对方同意后,两个用户才能成为好友并进行通信。 添加好友,案例代码 publicclassAddFriendHandlerextendsMyBizHandler<AddFriendRequest>{publicAddFriendHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,AddFriendRequestmsg){//1.添加好友到数据库中[A->BB->A]List<UserFriend>userFriendList=newArrayList<>();userFriendList.add(newUserFriend(msg.getUserId(),msg.getFriendId()));userFriendList.add(newUserFriend(msg.getFriendId(),msg.getUserId()));userService.addUserFriend(userFriendList);//2.推送好友添加完成AUserInfouserInfo=userService.queryUserInfo(msg.getFriendId());channel.writeAndFlush(newAddFriendResponse(userInfo.getUserId(),userInfo.getUserNickName(),userInfo.getUserHead()));//3.推送好友添加完成BChannelfriendChannel=SocketChannelUtil.getChannel(msg.getFriendId());if(null==friendChannel)return;UserInfofriendInfo=userService.queryUserInfo(msg.getUserId());friendChannel.writeAndFlush(newAddFriendResponse(friendInfo.getUserId(),friendInfo.getUserNickName(),friendInfo.getUserHead()));}} 4. 消息应答 从整体的流程可以看到,在用户发起好友、群组通信的时候,会触发一个事件行为,接下来客户端向服务端发送与好友的对话请求。 服务端收到对话请求后,如果是好友对话,那么需要保存与好友的通信信息到对话框中。同时通知好友,我与你要通信了。你在自己的对话框列表中,把我加进去。 那么如果是群组通信,是可以不用这样通知的,因为不可能把还没有在线的所有群组用户全部通知(人家还没登录呢),所以这部分只需要在用户上线收到信息后,创建出对话框到列表中即可。可以仔细理解下,同时也可以想想其他实现的方式。 消息应答,案例代码 publicclassMsgHandlerextendsMyBizHandler<MsgRequest>{publicMsgHandler(UserServiceuserService){super(userService);}@OverridepublicvoidchannelRead(Channelchannel,MsgRequestmsg){logger.info("消息信息处理:{}",JSON.toJSONString(msg));//异步写库userService.asyncAppendChatRecord(newChatRecordInfo(msg.getUserId(),msg.getFriendId(),msg.getMsgText(),msg.getMsgType(),msg.getMsgDate()));//添加对话框[如果对方没有你的对话框则添加]userService.addTalkBoxInfo(msg.getFriendId(),msg.getUserId(),Constants.TalkType.Friend.getCode());//获取好友通信管道ChannelfriendChannel=SocketChannelUtil.getChannel(msg.getFriendId());if(null==friendChannel){logger.info("用户id:{}未登录!",msg.getFriendId());return;}//发送消息friendChannel.writeAndFlush(newMsgResponse(msg.getUserId(),msg.getMsgText(),msg.getMsgType(),msg.getMsgDate()));}} 5. 断线重连 从上述流程中我们看到,当网络连接断开以后,会像服务端发送重新链接的请求。那么在这个发起链接的过程,和系统的最开始链接有所区别。断线重连是需要将用户的 ID 信息一同- - 发送给服务端,好让服务端可以去更新用户与通信管道 Channel 的绑定关系。 同时还需要更新群组内的重连信息,把用户的重连加入群组映射中。此时就可以恢复用户与好友和群组的通信功能。 消息应答,案例代码 // Channel 状态定时巡检;3 秒后每 5 秒执行一次scheduledExecutorService.scheduleAtFixedRate(()->{while(!nettyClient.isActive()){System.out.println("通信管道巡检:通信管道状态"+nettyClient.isActive());try{System.out.println("通信管道巡检:断线重连[Begin]");ChannelfreshChannel=executorService.submit(nettyClient).get();if(null==CacheUtil.userId)continue;freshChannel.writeAndFlush(newReconnectRequest(CacheUtil.userId));}catch(InterruptedException|ExecutionExceptione){System.out.println("通信管道巡检:断线重连[Error]");}}},3,5,TimeUnit.SECONDS); 6. 集群通信 跨服务之间案例采用redis的发布和订阅进行传递消息,如果你是大型服务可以使用zookeeper 用户A在发送消息给用户B时候,需要传递B的channeId,以用于服务端进行查找channeId所属是否自己的服务内 单台机器也可以启动多个Netty服务,程序内会自动寻找可用端口 六、源码下载 本项目是作者小傅哥使用JavaFx、Netty4.x、SpringBoot、Mysql等技术栈和偏向于DDD领域驱动设计方式,搭建的仿桌面版微信实现通信核心功能。 这套 IM 代码分为了三组模块;UI、客户端、服务端。之所以这样拆分,是为了将UI展示与业务逻辑隔离,使用事件和接口进行驱动,让代码层次更加干净整洁易于扩展和维护。 序号 工程 介绍 1 itstack-naive-chat-ui 使用JavaFx开发的UI端,在我们的UI端中提供了;登录框体、聊天框体,同时在聊天框体中有大量的行为交互界面以及接口和事件。最终我的UI端使用Maven打包的方式向外提供Jar包,以此来达到UI界面与业务行为流程分离。 2 itstack-naive-chat-client 客户端是我们的通信核心工程,主要使用Netty4.x作为我们的socket框架来完成通信交互。并且在此工程中负责引入UI的Jar包,完成UI定义的事件(登录验证、搜索添加好友、对话通知、发送信息等等),以及需要使用我们在服务端工程定义的通信协议来完成信息的交互操作。 3 itstack-navie-chat-server 服务端同样使用Netty4.x作为socket的通信框架,同时在服务端使用Layui作为管理后台的页面,并且我们的服务端采用偏向于DDD领域驱动设计的方式与Netty集合,以此来达到我们的框架结构整洁干净易于扩展。 4 itstack.sql 系统工程数据库表结构以及初始化数据信息,共计6张核心表;用户表、群组表、用户群组关联表、好友表、对话表以及聊天记录表。用户在实际业务开发中可以自行拓展完善,目前库表结构只以核心功能为基础。 源码获取:关注公众号:bugstack虫洞栈,回复 IM 亲,源码给我点个Star,不要 白皮袄 !!! 专栏小册:直接阅读原文即可 七、总结 此IM系统涉及到的技术栈内容较多,Netty4.x、SpringBoot、Mybatis、Mysql、JavaFx、layui等技术栈的使用,以及整个系统框架结构采用DDD四层架构+Socket模块的方式进行搭建,所有的UI都以前后端分离事件驱动方式进行设计,在这个过程中只要你能坚持学习下来,那么一定会收获非常多的内容。 足够吹牛啦!🌶 任何一个新技术栈的学习过程都会包括这样一条路线;运行HelloWorld、熟练使用API、项目实践以及最后的深度源码挖掘。那么在听到这样一个需求时候,Java程序员肯定会想到一些列的技术知识点来填充我们项目中的各个模块,例如;界面用JavaFx、Swing等,通信用Socket或者知道Netty框架、服务端控制用MVC模型加上SpringBoot等。但是怎么将这些各个技术栈合理的架设出我们的系统确是学习、实践、成长过程中最重要的部分。 - END - 下方扫码关注 bugstack虫洞栈,与小傅哥一起学习成长、共同进步,做一个码场最贵Coder! 回复【设计模式】,获取《重学Java设计模式》,这是一本互联网真实案例的实践书籍,从实际业务中抽离出,交易、营销、秒杀、中间件、源码等众多场景进行学习代码设计。 回复【Spring专栏】, 获取《手撸Spring》,这是一本通过带着读者手写简化版 Spring 框架,了解 Spring IOC、AOP、循环依赖等核心原理和设计实现的技术资料。 回复【面经手册】,获取《面经手册•拿大厂Offer》,这是一本有深度的Java核心内容,从数据结构、算法、并发编程以及JVM系8不断深入讲解,让懂了就是真的懂。 你好,我是小傅哥。一线互联网 java 工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。 2020年写了一本PDF 《重学Java设计模式》 ,全网下载量50万+,帮助很多同学成长,现已出书。同年 github 的两个项目, CodeGuide 、 itstack-demo-design ,持续霸榜 Trending,成为全球热门项目。 2021年上架一本小册 《SpringBoot 中间件设计和开发》 ,16个互联网中间件场景、30个工程,是全网唯一一次手把手教你造轮子、写中间件,因为这样的技术离P7最近、离架构师最近、离高薪资最近! 本文分享自微信公众号 - bugstack虫洞栈(bugstack)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

Zadig v1.0.0 预览版发布,开源分布式持续交付产品

Zadig v1.0.0 已经发布,此版本更新内容包括: 提升 zadig 安装脚本的稳定性和成功率 以下安装环境已经过测试: Existing Cluster Vendor Kubernetes Version Test Result Ali ACK 1.16.9 pass 1.18.8 pass Tencent TKE 1.18.4 pass 1.16.3 pass 1.14.3 pass 1.12.4 pass HuaWei CCE 1.19.8 pass 1.17.17 pass Rancher 1.18 pass 1.20 pass Elastic Compute Service Vendor OS Version Test Result Ali ECS Ubuntu16.04 pass Ubuntu18.04 pass Ubuntu20.04 pass CentOS7.4 pass CentOS8.3 pass Tencent CVM Ubuntu16.04 pass Ubuntu18.04 pass Ubuntu20.04 pass CentOS8.2 pass HuaWei ECS Ubuntu16.04 pass Ubuntu18.04 pass Ubuntu20.04 pass CentOS7.6 pass CentOS8.2 pass Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery)产品,具备高可用 CI/CD 能力,提供云原生运行环境,支持开发者本地联调、微服务并行构建和部署、集成测试等。Zadig 不改变现有流程,无缝集成 Github/Gitlab、Jenkins、多家云厂商等,运维成本极低。 详情查看:https://gitee.com/koderover/zadig/releases/v1.0.0

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册