带你读《Netty、Redis、ZooKeeper高并发实战》之一:高并发时代的必备技能

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本书从操作系统底层的IO原理入手,同时提供高性能开发的实战案例,是一本高并发Java编程应用基础图书。

Netty、Redis、ZooKeeper高并发实战
点击查看第二章
点击查看第三章
image.png
尼恩 编著

第1章

高并发时代的必备技能

高并发时代已然到来,Netty、Redis、ZooKeeper是高并发时代的必备工具。

1.1 Netty为何这么火

Netty是JBOSS提供的一个Java开源框架,是基于NIO的客户端/服务器编程框架,它既能快速开发高并发、高可用、高可靠性的网络服务器程序,也能开发高可用、高可靠的客户端程序。
注:NOI是指非阻塞输入输出(Non-Blocking IO),也称非阻塞IO。另外,本书为了行文上的一致性,把输入输出的英文缩写统一为IO,而不用I/O。

1.1.1 Netty火热的程度

Netty已经有了成百上千的分布式中间件、各种开源项目以及各种商业项目的应用。例如火爆的Kafka、RocketMQ等消息中间件、火热的ElasticSearch开源搜索引擎、大数据处理Hadoop的RPC框架Avro、主流的分布式通信框架Dubbo,它们都使用了Netty。总之,使用Netty开发的项目,已经有点数不过来了……
Netty之所以受青睐,是因为Netty提供异步的、事件驱动的网络应用程序框架和工具。作为一个异步框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便地主动获取或者通过通知机制获得IO操作结果。
与JDK原生NIO相比,Netty提供了相对十分简单易用的API,因而非常适合网络编程。Netty主要是基于NIO来实现的,在Netty中也可以提供阻塞IO的服务。
Netty之所以这么火,与它的巨大优点是密不可分的,大致可以总结如下:

  • API使用简单,开发门槛低。
  • 功能强大,预置了多种编解码功能,支持多种主流协议。
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活扩展。
  • 性能高,与其他业界主流的NIO框架对比,Netty的综合性能最优。
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO中的BUG,业务开发人员不需要再为NIO的BUG而烦恼。
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复。

1.1.2 Netty是面试的必杀器

Netty是互联网中间件领域使用最广泛、最核心的网络通信框架之一。几乎所有互联网中间件或者大数据领域均离不开Netty,掌握Netty是作为一名初中级工程师迈向高级工程师重要的技能之一。
目前来说,主要的互联网公司,例如阿里、腾讯、美团、新浪、淘宝等,在高级工程师的面试过程中,就经常会问一些高性能通信框架方面的问题,还会问一些“你有没有读过什么著名框架的源代码?”等类似的问题。
如果掌握了Netty相关的技术问题,更进一步说,如果你能全面地阅读和掌握Netty源代码,相信面试大公司时,一定底气十足,成功在握。

1.2 高并发利器Redis

任何高并发的系统,不可或缺的就是缓存。Redis缓存目前已经成为缓存的事实标准。

1.2.1 什么是Redis

Redis是Remote Dictionary Server(远程字典服务器)的缩写,最初是作为数据库的工具来使用的。是目前使用广泛、高效的一款开源缓存。Redis使用C语言开发,将数据保存在内存中,可以看成是一款纯内存的数据库,所以它的数据存取速度非常快。一些经常用并且创建时间较长的内容,可以缓存到Redis中,而应用程序能以极快的速度存取这些内容。举例来说,如果某个页面经常会被访问到,而创建页面时需要多次访问数据库、造成网页内容的生成时间较长,那么就可以使用Redis将这个页面缓存起来,从而减轻了网站的负担,降低了网站的延迟。
Redis通过键-值对(Key-Value Pair)的形式来存储数据,类似于Java中的Map映射。Redis的Key键,只能是string字符串类型。Redis的Value值类型包括:string字符类型、map映射类型、list列表类型、set集合类型、sortedset有序集合类型。
Redis的主要应用场景:缓存(数据查询、短连接、新闻内容、商品内容等)、分布式会话(Session)、聊天室的在线好友列表、任务队列(秒杀、抢购、12306等)、应用排行榜、访问统计、数据过期处理(可以精确到毫秒)。

1.2.2 Redis成为缓存事实标准的原因

相对于其他的键-值对(Key-Value)内存数据库(如Memcached)而言,Redis具有如下特点:
(1)速度快 不需要等待磁盘的IO,在内存之间进行的数据存储和查询,速度非常快。当然,缓存的数据总量不能太大,因为受到物理内存空间大小的限制。
(2)丰富的数据结构 除了string之外,还有list、hash、set、sortedset,一共五种类型。
(3)单线程,避免了线程切换和锁机制的性能消耗。
(4)可持久化 支持RDB与AOF两种方式,将内存中的数据写入外部的物理存储设备。
(5)支持发布/订阅。
(6)支持Lua脚本。
(7)支持分布式锁 在分布式系统中,如果不同的节点需要访同到一个资源,往往需要通过互斥机制来防止彼此干扰,并且保证数据的一致性。在这种情况下,需要使用到分布式锁。分布式锁和Java的锁用于实现不同线程之间的同步访问,原理上是类似的。
(8)支持原子操作和事务 Redis事务是一组命令的集合。一个事务中的命令要么都执行,要么都不执行。如果命令在运行期间出现错误,不会自动回滚。
(9)支持主-从(Master-Slave)复制与高可用(Redis Sentinel)集群(3.0 版本以上)
(10)支持管道 Redis管道是指客户端可以将多个命令一次性发送到服务器,然后由服务器一次性返回所有结果。管道技术的优点是:在批量执行命令的应用场景中,可以大大减少网络传输的开销,提高性能。

1.3 分布式利器ZooKeeper

突破了单体瓶颈之后的高并发,就必须靠集群了,而集群的分布式架构和协调,一定少不了可靠的分布式协调工具,ZooKeeper就是目前极为重要的分布式协调工具。

1.3.1 什么是ZooKeeper

ZooKeeper最早起源于雅虎公司研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型的系统需要依赖一个类似的系统进行分布式协调,但是这些系统往往存在分布式单点问题。所以雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架。在项目初期给这个项目命名的时候,准备和很多项目一样,按照雅虎公司的惯例要用动物的名字来命名的(例如著名的Pig项目)。在进行探讨取什么名字的时候,研究院的首席科学家Raghu Ramakrishnan开玩笑说:再这样下去,我们这儿就变成动物园了。此话一出,大家纷纷表示就叫动物园管理员吧,于是,ZooKeeper的名字由此诞生了。当然,取名ZooKeeper,也绝非没有一点儿道理。ZooKeeper的功能,正好是用来协调分布式环境的,协调各个以动物命名的分布式组件,所以,ZooKeeper这个名字也就“名副其实”了。

1.3.2 ZooKeeper的优势

ZooKeeper对不同系统环境的支持都很好,在绝大多数主流的操作系统上都能够正常运行,如:GNU/Linux、Sun Solaris、Win32以及MacOS等。需要注意的是,ZooKeeper官方文档中特别强调,由于FreeBSD系统的JVM(Java Virtual Machine,即Java虚拟机)对Java的NIO Selector选择器支持得不是很好,因此不建议在FreeBSD系统上部署生产环境的ZooKeeper服务器。
ZooKeeper的核心优势是,实现了分布式环境的数据一致性,简单地说:每时每刻我们访问ZooKeeper的树结构时,不同的节点返回的数据都是一致的。也就是说,对ZooKeeper进行数据访问时,无论是什么时间,都不会引起脏读、重复读。注:脏读是指在数据库存取中无效数据的读出。
ZooKeeper提供的功能都是分布式系统中非常底层且必不可少的基本功能,如果开发者自己来实现这些功能而且要达到高吞吐、低延迟同时的还要保持一致性和可用性,实际上是非常困难的。因此,借助ZooKeeper提供的这些功能,开发者就可以轻松在ZooKeeper之上构建自己的各种分布式系统。

1.4 高并发IM的综合实践

为了方便交流和学习,笔者组织了一帮高性能发烧友,成立了一个高性能社群,叫作“疯狂创客圈”。同时,牵头组织社群的小伙伴们,应用Netty、Redis、ZooKeeper持续迭代一个高并发学习项目,叫作“CrazyIM”。

1.4.1 高并发IM的学习价值

为什么要开始一个高并发IM(即时通信)的实践呢?
首先,通过实践完成一个分布式、高并发的IM系统,具有相当的技术挑战性。这一点,对于从事传统的企业级Web开发者来说,相当于进入了一片全新的天地。企业级Web,QPS(Query Per Second,每秒查询率)峰值可能在1000以内,甚至在100以内,没有多少技术挑战性和含金量,属于重复性的CRUD的体力活。注:CRUD是指Create(创建)、Retrieve(查询)、Update(更新)和Delete(删除)。而一个分布式、高并发的IM系统,面临的QPS峰值可能在十万、百万、千万,甚至上亿级别。对于此纵深层次化的、递进的高并发需求,将无极限地考验着系统的性能。需要不断地从通信协议、到系统的架构进行优化,对技术能力是一种非常极致的考验和训练。
其次,就具有不同QPS峰值规模的IM系统而言,它们所处的用户需求环境是不一样的。这就造成了不同用户规模的IM系统,各自具有一定的市场需求和实际需要,因而它们不一定都需要上亿级的高并发。但是,作为一个顶级的架构师,就应该具备全栈式的架构能力,对不同用户规模的、差异化的应用场景,提供和架构出与对应的应用场景相匹配的高并发IM系统。也就是说,IM系统综合性相对较强,相关的技术需要覆盖到满足各种不同应用场景的网络传输、分布式协调、分布式缓存、服务化架构等。
接下来具体看看高并发IM的应用场景吧。

1.4.2 庞大的应用场景

一切高实时性通信、消息推送的应用场景,都需要高并发IM。随着移动互联网、AI的飞速发展,高性能、高并发IM,有着非常广泛的应用场景。
高并发IM典型的应用场景如下:私信、聊天、大规模推送、视频会议、弹幕、抽奖、互动游戏、基于位置的应用(Uber、滴滴司机位置)、在线教育、智能家居等,如图1-1所示。
image.png

图1-1 高并发IM典型的应用场景

尤其是对于APP开发的小伙伴们来说,IM已经成为大多数APP的标配。在移动互联网时代,推送(Push)服务成为APP应用不可或缺的重要组成部分,推送服务可以提升用户的活跃度和留存率。我们的手机每天接收到各种各样的广告和提示消息等,它们大多数都是通过推送服务实现的。
随着5G时代物联网的发展,未来所有接入物联网的智能设备,都将是IM系统的客户端,这就意味着推送服务会在未来面临海量的设备和终端接入。为了支持这些千万级、上亿级的终端,一定是需要强悍的后台系统。
有这么多的应用场景,对于想成长为Java高手的小伙伴们,高并发IM都是绕不开的一个话题。
对于想在后台有所成就的小伙伴们来说,高并发IM实践,更是在与终极BOSS PK之前的一场不可避免的打怪练手。

1.5 Netty、Redis、ZooKeeper实践计划

下面从最基础的NIO入手,列出一个大致12天的实践计划,帮助大家深入掌握Netty、Redis、ZooKeeper。

1.5.1 第1天:Java NIO实践

实践一:使用FileChannel复制文件
通过使用Channel通道,完成复制文件。
本环节的目标是掌握以下知识:Java NIO中ByteBuffer、Channel两个重要组件的使用。
接着是升级实战的案例,使用文件Channel通道的transferFrom方法,完成高效率的文件复制。
实践二:使用SocketChannel传输文件
本环节的目标是掌握以下知识:

  • 非阻塞客户端在发起连接后,需要不断的自旋,检测连接是否完成的。
  • SocketChannel传输通道的read读取方法、write写入方法。
  • 在SocketChannel传输通道关闭前,尽量发送一个输出结束标志到对方端。

实践三:使用DatagramChannel传输数据
客户端使用DatagramChannel发送数据,服务器端使用DatagramChannel接收数据。
本环节的目标是掌握以下知识:

  • 使用接收数据方法receive,使用发送数据方法send。
  • DatagramChannel和SocketChannel两种通道,在发送、接收数据上的不同。

实践四:使用NIO实现Discard服务器
客户端功能:发送一个数据包到服务器端,然后关闭连接。服务器端也很简单,收到客户端的数据,直接丢弃。
本环节的目标是掌握以下知识:

  • Selector 选择器的注册,以及选择器的查询。
  • SelectionKey选择键方法的使用。
  • 根据SelectionKey方法的四种IO事件类型,完成对应的IO处理。

1.5.2 第2天:Reactor反应器模式实践

实践一:单线程Reactor反应器模式的实现
使用单线程Reactor反应器模式,设计和实现一个EchoServer回显服务器。功能很简单:服务器端读取客户端的输入,然后回显到客户端。
本环节的目标是掌握以下知识:

  • 单线程Reactor反应器模式两个重要角色——Reactor反应器、Handler处理器的编写。
  • SelectionKey选择键两个重要的方法——attach和attachment方法的使用。

实践二:多线程Reactor反应器模式
使用多线程Reactor反应器模式,设计一个EchoServer回显服务器,主要的升级方式为:

  • 引入ThreadPool线程池,将负责IOHandler输入输出处理器的执行,放入独立的线程池中,与负责服务监听和IO事件查询的反应器线程相隔离。
  • 将反应器线程拆分为多个SubReactor子反应器线程,同时,引入多个Selector选择器,每一个子反应器线程负责一个选择器读取客户端的输入,回显到客户端。

本环节的目标是掌握以下知识:

  • 线程池的使用。
  • 多线程反应器模式的实现。

1.5.3 第3天:异步回调模式实践

实践一:使用线程join方式,通过阻塞式异步调用的方式,实现泡茶喝的实例
为了在计算机中,实现华罗庚的课文《统筹方法》泡茶喝的流程,可以设计三条线程:主线程、清洗线程、烧水线程,主要介绍如下:

  • 主线程(MainThread)的工作是:启动清洗线程、启动烧水线程,等清洗、烧水的工作完成后,泡茶喝。
  • 清洗线程(WashThread)的工作是:洗茶壶、洗茶杯。
  • 烧水线程(HotWarterThread)的工作是:洗好水壶,灌上凉水,放在火上,一直等水烧开。

本环节的目标是掌握以下知识:
不同的版本的join方法的使用。
实践二:使用FutureTask类和Callable接口,启动阻塞式的异步调用,并且获取异步线程的结果。
还是实现华罗庚的课文《统筹方法》泡茶喝的流程,可以设计三条线程:主线程、清洗线程、烧水线程,主要改进如下:

  • 主线程(MainThread)的工作是:启动清洗线程、启动烧水线程,然后阻塞,等待异步线程的返回值,根据异步线程的返回值,决定后续的动作。
  • 清洗线程(WashThread)在异步执行完成之后,有返回值。
  • 烧水线程(HotWarterThread)在异步执行完成之后,有返回值。
    本环节的目标是掌握以下知识:
  • Callable(可调用)接口的使用;Callable接口和Runnable(可执行)接口的不同。
  • FutureTask异步任务类的使用。

实践三:使用ListenableFuture类和FutureCallback接口,启动非阻塞异步调用,并且完成异步回调。
还是实现华罗庚的课文《统筹方法》泡茶喝的流程,可以设计三条线程:主线程、清洗线程、烧水线程,主要改进如下:

  • 主线程(MainThread)的工作是:启动清洗线程、启动烧水线程,并且设置异步完成后的回调方法,这里主线程不阻塞等待,而是去干其他事情,例如读报纸。
  • 清洗线程(WashThread)在异步执行完成之后,执行回调方法。
  • 烧水线程(HotWarterThread)在异步执行完成之后,执行回调方法。

本环节的目标是掌握以下知识:

  • FutureCallback接口的使用;FutureCallback接口和Callable接口的区别和联系。
  • ListenableFuture异步任务类的使用,以及为异步任务设置回调方法。

1.5.4 第4天:Netty基础实践

**实践一:Netty中Handler处理器的生命周期
操作步骤如下:**
定义一个非常简单的入站处理器—InHandlerDemo。这个类继承于ChannelInboundHandlerAdapter适配器,它实现了基类的所有的入站处理方法,并在每一个方法的实现中,都加上了必要的输出信息。
编写一个单元测试代码:将这个处理器加入到一个EmbeddedChannel嵌入式通道的流水线中。
通过writeInbound方法,向EmbeddedChannel写一个模拟的入站ByteBuf数据包。InHandlerDemo作为一个入站处理器,就会处理到该ByteBuf数据包。
通过输出,可以观测到处理器的生命周期。
本环节的目标是掌握以下知识:

  • Netty中Handler处理器的生命周期。
  • EmbeddedChannel嵌入式通道的使用。

实践二:ByteBuf的基本使用
操作步骤如下:
使用Netty的默认分配器,分配了一个初始容量为9个字节,最大上限为100个字节的ByteBuf缓冲区。
向ByteBuf写数据,观测ByteBuf的属性变化。
从ByteBuf读数据,观测ByteBuf的属性变化。
本环节的目标是掌握以下知识:

  • ByteBuf三个重要属性:readerIndex(读指针)、writerIndex(写指针)、maxCapacity(最大容量)。
  • ByteBuf读写过程中,以上三个重要属性的变化规律。

**实践三:使用Netty,实现EchoServer回显服务器
前面实现过Java NIO版本的EchoServer回显服务器,学习了Netty后,设计和实现一个Netty版本的EchoServer回显服务器。功能很简单:服务器端读取客户端的输入,然后将数据包直接回显到客户端。**
本环节的目标是掌握以下知识:

  • 服务器端ServerBootstrap的装配和使用。
  • 服务器端NettyEchoServerHandler入站处理器的channelRead入站处理方法的编写。
  • 服务器端实现Netty的ByteBuf缓冲区的读取、回显。
  • 客户端Bootstrap的装配和使用。
  • 客户端NettyEchoClientHandler入站处理器中接受回显的数据,并且释放内存。
  • 客户端实现多种方式释放ByteBuf,包括:自动释放、手动释放。

1.5.5 第5天:解码器(Decoder)与编码器(Encoder)实践

实践一:整数解码实践
具体步骤如下:
定义一个非常简单的整数解码器—Byte2IntegerDecoder。这个类继承于ByteToMessageDecoder字节码解码抽象类,并实现基类的decode抽象方法,将ByteBuf缓冲区中的数据,解码成以一个一个的Integer对象。
定义一个非常简单的整数处理器—IntegerProcessHandler。读取上一站的入站数据,把它转换成整数,并且显示在Console控制台。
编写一个整数解码实战的测试用例。在测试用例中,新建了一个EmbeddedChannel嵌入式的通道实例,将两个自己的入站处理器Byte2IntegerDecoder、IntegerProcessHandler加入到通道的流水线上。通过writeInbound方法,向EmbeddedChannel写入一个模拟的入站ByteBuf数据包。
通过输出,可以观察整数解码器的解码结果。
本环节的目标是掌握以下知识:

  • 如何基于Netty的ByteToMessageDecoder字节码解码抽象类,实现自己的ByteBuf二进制字节到POJO对象的解码。
  • 使用ByteToMessageDecoder,如何管理ByteBuf的应用计数。

实践二:整数相加的解码器实践
具体步骤如下:
继承ReplayingDecoder基础解码器,编写一个整数相加的解码器:一次解码两个整数,并把这两个数据相加之和,作为解码的结果。
使用前面定义的整数处理器——IntegerProcessHandler。读取上一站的入站数据,把它转换成整数,并且显示在Console控制台。
使用前面定义的测试类,测试整数相加的解码器,并且查看结果是否正确。
本环节的目标是掌握以下知识:

  • 如何基于ReplayingDecoder解码器抽象类,实现自己的ByteBuf二进制字节到POJO对象的解码。
  • ReplayingDecoder的成员属性——state阶段属性的使用。
  • ReplayingDecoder的重要方法——checkpoint(IntegerAddDecoder.Status)方法的使用。

实践三:基于Head-Content协议的字符串分包解码器
具体步骤如下:
继承ReplayingDecoder基础解码器,编写一个字符串分包解码器StringReplayDecoder。
在StringReplayDecoder的decode方法中,分两步:第1步,解码出字符串的长度;第2步,按照第一个阶段的字符串长度,解码出字符串的内容。
编写一个简单的业务处理器StringProcessHandler。其功能是:读取上一站的入站数据,把它转换成字符串,并且显示在Console控制台。
新建了一个EmbeddedChannel嵌入式的通道实例,将两个自己的入站处理器StringReplayDecoder、StringProcessHandler加入到通道的流水线上。为了测试入站处理器,使用writeInbound方法,向嵌入式通道EmbeddedChannel写入了100个ByteBuf入站缓冲;每一个ByteBuf缓冲,仅仅包含一个字符串。EmbeddedChannel通道接收到入站数据后,pipeline流水线上的两个入站处理器,就能不断地处理这些入站数据:将接收到的二进制字节,解码成一个一个的字符串,然后逐个地显示在Console控制台上。
本环节的目标是掌握以下知识:

  • 如何基于ReplayingDecoder解码器抽象类,实现自己的ByteBuf二进制字节到字符串的解码。
  • 巩固ReplayingDecoder的成员属性——state阶段属性的使用。
  • 巩固ReplayingDecoder的重要方法——checkpoint(IntegerAddDecoder.Status)方法的使用。

实践四:多字段Head-Content协议数据包解析实践
具体步骤如下:
使用LengthFieldBasedFrameDecoder解码器,解码复杂的Head-Content协议。例如协议中包含版本号、魔数等多个其他的数据字段。
使用前面所编写那一个简单的业务处理器StringProcessHandler。其功能是:读取上一站的入站数据,把它转换成字符串,并且显示在Console控制台上。
新建一个EmbeddedChannel嵌入式的通道实例,将第一步和第二步的两个入站处理器LengthFieldBasedFrameDecoder、StringProcessHandler加入到通道的流水线上。为了测试入站处理器,使用writeInbound方法,向嵌入式通道EmbeddedChannel写入100个ByteBuf入站缓冲;每一个ByteBuf缓冲,仅仅包含一个字符串。EmbeddedChannel通道接收到入站数据后,pipeline流水线上的两个入站处理器,就能不断地处理到这些入站数据:将接到的二进制字节,解码成一个一个的字符串,然后逐个地显示在控制台上。
本环节的目标是掌握以下知识:

  • LengthFieldBasedFrameDecoder解码器的使用。
  • LengthFieldBasedFrameDecoder解码器的长度的矫正公式,计算公式为:内容字段的偏移-长度字段的偏移-长度字段的长度。

1.5.6 第6天:JSON和ProtoBuf序列化实践

实践一:JSON通信实践
客户端将POJO转成JSON字符串,编码后发送到服务器端。服务器端接收客户端的数据包,并解码成JSON,转成POJO。
具体步骤如下:
客户端的编码过程:
先通过谷歌的Gson框架,将POJO序列化成JSON字符串;然后使用Netty内置的StringEncoder编码器,将JSON字符串编码成二进制字节数组;最后,使用LengthFieldPrepender编码器(Netty内置),将二进制字节数组编码成Head-Content格式的二进制数据包。
服务器端的解码过程:
先使用LengthFieldBasedFrameDecoder(Netty内置的自定义长度数据包解码器),解码Head-Content二进制数据包,解码出Content字段的二进制内容。
然后,使用StringDecoder字符串解码器(Netty内置的解码器),将二进制内容解码成JSON字符串。
最后,使用自定义的JsonMsgDecoder解码器,将JSON字符串解码成POJO对象。
编写一个JsonMsgDecoder自定义的JSON解码器。将JSON字符串,解码成特定的POJO对象。
分别组装好服务器端、客户端的流水线,运行程序,查看两端的通信结果。
本环节的目标是掌握以下知识:

  • LengthFieldPrepender编码器的使用:在发送端使用它加上Head-Content的头部长度。
  • JsonMsgDecoder的编写。
  • JSON传输时,客户端流水线编码器的组装,服务器端流水线解码器的组装。

实践二:ProtoBuf通信实践
设计一个简单的客户端/服务器端传输程序:客户端将ProtoBuf的POJO编码成二进制数据包,发送到服务器端;服务器端接收客户端的数据包,并解码成ProtoBuf的POJO。
具体步骤如下:
设计好需要传输的ProtoBuf的“.proto”协议文件,并且生成ProtoBuf的POJO和Builder:
在“.proto”协议文件中,仅仅定义了一个消息结构体,并且该消息结构体也非常简单,只包含两个字段:消息ID、消息内容。
使用protobuf-maven-plugin插件,生成message的POJO类和Builder(构造者)类的Java代码。
客户端的编码过程:
先使用Netty内置的ProtobufEncoder,将ProtobufPOJO对象编码成二进制的字节数组。
然后,使用Netty内置的ProtobufVarint32LengthFieldPrepender编码器,加上varint32格式的可变长度。
Netty会将完成了编码后的Length+Content格式的二进制字节码,发送到服务器端。
服务器端的解码过程:
先使用Netty内置的ProtobufVarint32FrameDecoder,根据varint32格式的可变长度值,从入站数据包中,解码出二进制Protobuf字节码。
然后,可以使用Netty内置的ProtobufDecoder解码器,将Protobuf字节码解码成Protobuf POJO对象。
最后,自定义一个ProtobufBussinessDecoder解码器,处理ProtobufPOJO对象。
本环节的目标是掌握以下知识:

  • “.proto”基础协议。
  • Netty内置的ProtobufEncoder、ProtobufDecoder两个专用的传输Protobuf序列化数据的编码器/解码器的使用。
  • Netty内置的两个ProtoBuf专用的可变长度
    Head-Content协议的半包编码、解码处理器:ProtobufVarint32LengthFieldPrepender编码器、ProtobufVarint32FrameDecoderProtobufEncoder解码器的使用。

1.5.7 第7~10天:基于Netty的单聊实战

实践一:自定义ProtoBuf编码器/解码器
具体步骤如下:
为单聊系统,设计一套自定义的“.proto”协议文件;然后通过maven插件生成Protobuf Builder和POJO。
继承Netty提供的MessageToByteEncoder编码器,编写一个自定义的Protobuf编码器,完成Head-Content协议的复杂数据包的编码。
通过自定义编码器,最终将ProtobufPOJO编码成Head-Content协议的二进制ByteBuf帧。
继承Netty提供的ByteToMessageDecoder解码器,编写一个自定义的Protobuf解码器,完成Head-Content协议的复杂数据包的解码。
通过自定义的解码器,最终将Head-Content协议的复杂数据包,解码出Protobuf POJO。
分别组装好服务器端、客户端的流水线,运行程序,查看两端的通信结果。
本环节的目标是掌握以下知识:

  • 设计复杂的“.proto”协议文件。
  • 自定义的Protobuf编码器的编写。
  • 自定义的Protobuf编码器的编写。

实践二:登录实践
业务逻辑:

  • 客户端发送登录数据包。
  • 服务器端进行用户信息验证。
  • 服务器端创建Session会话。
  • 服务器端将登录结果信息返回给客户端,包括成功标志、Session ID等。

具体步骤如下:
从客户端到服务器端再到客户端,大致有以下几个步骤。
编写LoginConsoleCommand控制台命令类,从客户端收集用户ID和密码。
编写客户端LoginSender发送器类,组装Protobuf数据包,通过客户端通道发送到服务器端。
编写服务器端UserLoginHandler入站处理器,处理收到的登录消息,完成数据的校验后,将数据包交给业务处理器LoginMsgProcesser,进行异步的处理。
编写服务器端LoginMsgProcesser(业务处理器),将处理结果,写入用户绑定的子通道。
编写客户端业务处理器LoginResponceHandler,处理登录响应,例如设置登录的状态,保存会话的SessionID等。
本环节的目标是掌握以下知识:

  • Netty知识的综合运用。
  • Channel通道容器功能的使用。

实践三:单聊实践
单聊的业务逻辑:

  • 当用户A登录成功之后,按照单聊的消息格式,发送所要的消息。
  • 这里的消息格式设定为——userId:content。其中的userId,就是消息接收方目标用户B的userId;其中的content,表示聊天的内容。
  • 服务器端收到消息后,根据目标userId,进行消息帧的转发,发送到用户B所在的客户端。
  • 客户端用户B收到用户A发来的消息,显示在自己的控制台上。

具体步骤如下:
从客户端到服务器端再到客户端,大致有5个步骤。
当用户A登录成功之后,按照单聊的消息格式,发送所要的消息。
这里的消息格式设定为——userId:content,其中的userId,就是消息接收方目标用户B的userId,其中的content,表示聊天的内容。
CommandController在完成聊天内容和目标用户的收集后,调用chatSender发送器实例,将聊天消息组装成Protobuf消息帧,通过客户端channel通道发往服务器端。
编写服务器端的消息转发处理器ChatRedirectHandler类,其功能是,对用户登录进行判断:如果没有登录,则不能发送消息;开启异步的消息转发,由负责转发的chatRedirectProcesser实例,完成消息转发。
编写负责异步消息转发的ChatRedirectProcesser类,功能如下:根据目标用户ID,找出所有的服务器端的会话列表(Session List),然后对应每一个会话,发送一份消息。
编写客户端ChatMsgHandler处理器,主要的工作如下:对消息类型进行判断,判断是否为聊天请求Protobuf数据包。如果不是,直接将消息交给流水线的下一站;如果是聊天消息,则将聊天消息显示在控制台上。
本环节目标是掌握以下知识:

  • Netty知识的综合运用。
  • 服务器端会话(Session)的管理。

1.5.8 第11天:ZooKeeper实践计划

实践一:分布式ID生成器
具体步骤如下:
自定义一个分布式ID生成器——IDMaker类,通过创建ZK的临时顺序节点的方法,生成全局唯一的ID。
基于自定义的IDMaker,编写单元测试的用例,生成ID。
本环节的目标是掌握以下知识:

  • 分布式ID生成器原理。
  • ZooKeeper客户端的使用。

实践二:使用ZK实现SnowFlake ID算法
具体步骤如下:
编写一个实现SnowFlake ID算法的ID生成器——SnowflakeIdGenerator类,生成全局唯一的ID。
基于自定义的SnowflakeIdGenerator,编写单元测试的用例,生成ID。
本环节的目标是掌握以下知识:

  • SnowFlake ID算法原理。
  • ZooKeeper客户端的使用。

1.5.9 第12天:Redis实践计划

实践一:使用RedisTemplate模板类完成Redis的缓存CRUD操作
具体步骤如下:
将RedisTemplate模板类的大部分缓存操作,封装成一个自己的缓存操作Service服务,称之为CacheOperationService类。
编写业务类UserServiceImplWithTemplate类,使用CacheOperationService类,完成User对象的缓存CRUD。
编写测试用例,访问UserServiceImplWithTemplate类。观察在进行User对象的查询时,能优先使用缓存数据,是否省去数据库访问的时间。
本环节目标是掌握以下知识:

  • RedisTemplate模板类的使用。
  • Jedis的客户端API。
  • RedisTemplate模板API。

实践二:使用RedisTemplate模板类完成Redis的缓存CRUD操作
具体步骤如下:
使用RedisCallback的doInRedis回调方法,在doInRedis回调方法中,直接使用实参RedisConnection连接类实例,完成Redis的操作。
编写业务类UserServiceImplInTemplate类,使用RedisTemplate模板实例去执行RedisCallback回调实例,完成User对象的缓存CRUD。
编写测试用例,访问UserServiceImplInTemplate类。观察在进行User对象的查询时,优先使用缓存数据,看看是否省去了数据库访问的时间。
本环节的目标是掌握以下知识:

  • RedisCallback回调接口的使用。
  • Jedis的客户端API。
  • RedisTemplate模板API。

实践三:使用Spring缓存注解,完成Redis的缓存CRUD操作
具体步骤如下:
编写业务类UserServiceImplWithAnn类,使用缓存注解,完成User对象的缓存CRUD。
编写测试用例,访问UserServiceImplWithAnn类。观察在进行User对象的查询时,优先使用缓存数据,看看是否省去了数据库访问的时间。
本环节目标是掌握以下知识:

  • Spring缓存注解的使用。
  • Jedis的客户端API。
  • Spring缓存注解的配置。

1.6 本章小结

本章简单地给大家介绍了高并发时代,以及从业人员必须掌握的Netty、Redis、ZooKeeper等分布式高性能工具。同时,列出了一个大致12天的实践计划。
12天的实践计划不是综合性的大型实践,而是一些掌握单点知识和技能的小型实践案例。只有清楚地掌握了Netty、Redis、ZooKeeper这些工具,才能具备大型开发实践的能力。
在完成了单个功能点的实践案例之后,建议大家加入高性能社群“疯狂创客圈”,进一步参与高并发聊天项目“CrazyIM”的持续迭代,积累真正的实践经验。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
缓存 NoSQL 关系型数据库
亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证
你们有多少人是被面试官问到过Redis和MySQL的数据一致性如何保证的? 你们是否考虑过在高并发场景下,Redis与MySQL的同步会有哪些问题?该如何解决? 本篇文章会带大家详细了解,让你知其然,知其所以然,吊打面试官。
360 0
亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证
|
2月前
|
缓存 监控 NoSQL
Redis - 在电商购物车场景下的实战分析
Redis - 在电商购物车场景下的实战分析
160 0
|
2月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
53 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
2月前
|
消息中间件 NoSQL Java
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
203 1
|
27天前
|
NoSQL Redis
Netty实战:模拟Redis的客户端
Netty实战:模拟Redis的客户端
11 0
|
1月前
|
存储 NoSQL Redis
KubeSphere 核心实战之二【在kubesphere平台上部署redis】(实操篇 2/4)
KubeSphere 核心实战之二【在kubesphere平台上部署redis】(实操篇 2/4)
22 0
|
1月前
|
NoSQL Java 数据库
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
|
1月前
|
编解码 分布式计算 网络协议
一文让你深入了解 Java-Netty高性能高并发
一文让你深入了解 Java-Netty高性能高并发
56 1
|
1月前
|
消息中间件 存储 NoSQL
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
【Redis项目实战】使用Springcloud整合Redis分布式锁+RabbitMQ技术实现高并发预约管理处理系统
|
1月前
|
消息中间件 存储 缓存
【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶
【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶

热门文章

最新文章