高性能/并发的保证-Netty在Redisson的应用

前言
 Redisson Github: https://github.com/redisson/redisson
 Redisson 官网:https://redisson.pro/
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
以下是Redisson的结构:
- Redisson作为独立节点 可以用于独立执行其他节点发布到分布式执行服务 和 分布式调度任务服务 里的远程任务。  
Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。
客户端初始化
createBootstrap
org.redisson.client.RedisClient#createBootstrap
private Bootstrap createBootstrap(RedisClientConfig config, Type type) {
        Bootstrap bootstrap = new Bootstrap()
                        .resolver(config.getResolverGroup())
          							//1.指定配置中的IO类型
                        .channel(config.getSocketChannelClass())
          							//2.指定配置中的线程模型
                        .group(config.getGroup());
  			//3.IO处理逻辑
        bootstrap.handler(new RedisChannelInitializer(bootstrap, config, this, channels, type));
  			//4. 指定bootstrap配置选项
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout());
        bootstrap.option(ChannelOption.SO_KEEPALIVE, config.isKeepAlive());
        bootstrap.option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay());
        config.getNettyHook().afterBoostrapInitialization(bootstrap);
        return bootstrap;
    }
从上面的代码可以看到,客户端启动的引导类是 Bootstrap,负责启动客户端以及连接服务端,引导类创建完成之后,下面我们描述一下客户端启动的流程。
一. 首先,我们需要给它指定线程模型,驱动着连接的数据读写。然后,redisson默认指定 IO 模型为 NioSocketChannel
二. 接着,给引导类指定一系列处理链路,这里主要就是定义连接的业务处理逻辑,不理解没关系,在后面我们会详细分析
RedisChannelInitializer
org.redisson.client.handler.RedisChannelInitializer

 @Override
    protected void initChannel(Channel ch) throws Exception {
      	// 开启SSL终端识别能力
        initSsl(config, ch);
        if (type == Type.PLAIN) {
          	//Redis正常连接处理类
            ch.pipeline().addLast(new RedisConnectionHandler(redisClient));
        } else {
          	//Redis订阅发布处理类
            ch.pipeline().addLast(new RedisPubSubConnectionHandler(redisClient));
        }
        ch.pipeline().addLast(
          	//链路检测狗
            connectionWatchdog,
          	//Redis协议命令编码器
            CommandEncoder.INSTANCE,
          	//Redis协议命令批量编码器
            CommandBatchEncoder.INSTANCE,
          	//Redis命令队列
            new CommandsQueue());
        if (pingConnectionHandler != null) {
           //心跳包连接处理类
            ch.pipeline().addLast(pingConnectionHandler);
        }
        if (type == Type.PLAIN) {
          	//Redis协议命令解码器
            ch.pipeline().addLast(new CommandDecoder(config.getExecutor(), config.isDecodeInExecutor()));
        } else {
          	//Redis订阅发布解码器
            ch.pipeline().addLast(new CommandPubSubDecoder(config.getExecutor(), config.isKeepPubSubOrder(), config.isDecodeInExecutor()));
        }
        config.getNettyHook().afterChannelInitialization(ch);
    }
图1 Redisson 链路处理图

Redisson的处理链
Redisson的Pipeline里面的ChannelHandler比较多,我挑选其中
CommandEncoder和CommandDecoder进行源码剖析。

失败重连
org.redisson.client.handler.ConnectionWatchdog#reconnect 重连机制
private void reconnect(final RedisConnection connection, final int attempts){
		//重试时间越来越久
    int timeout = 2 << attempts;
    if (bootstrap.config().group().isShuttingDown()) {
        return;
    }
    try {
        timer.newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                tryReconnect(connection, Math.min(BACKOFF_CAP, attempts + 1));
            }
        }, timeout, TimeUnit.MILLISECONDS);
    } catch (IllegalStateException e) {
        // skip
    }
}
netty中的Timer管理,使用了的Hashed time Wheel的模式,Time Wheel翻译为时间轮,是用于实现定时器timer的经典算法。
这个方法的声明是这样的:
 /**
     * Schedules the specified {@link TimerTask} for one-time execution after
     * the specified delay.
     *
     * @return a handle which is associated with the specified task
     *
     * @throws IllegalStateException       if this timer has been {@linkplain #stop() stopped} already
     * @throws RejectedExecutionException if the pending timeouts are too many and creating new timeout
     *                                    can cause instability in the system.
     */
    Timeout newTimeout(TimerTask task, long delay, TimeUnit unit);
这个方法需要一个TimerTask对象以知道当时间到时要执行什么逻辑,然后需要delay时间数值和TimeUnit时间的单位。
Redis协议命令编码器
 Redis 的作者认为数据库系统的瓶颈一般不在于网络流量,而是数据库自身内部逻辑处理上。所以即使 Redis 使用了浪费流量的文本协议,依然可以取得极高的访问性能。Redis 将所有数据都放在内存,用一个单线程对外提供服务,单个节点在跑满一个 CPU 核心的情况下可以达到了 10w/s 的超高 QPS。
RESP 是 Redis 序列化协议的简写。它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。
Redis 协议将传输的结构数据分为 5 种最小单元类型,单元结束时统一加上回车换行符号\r\n。
- 单行字符串 以 +符号开头。
- 多行字符串 以 $符号开头,后跟字符串长度。
- 整数值 以 :符号开头,后跟整数的字符串形式。
- 错误消息 以 -符号开头。
- 数组 以 *号开头,后跟数组的长度。
单行字符串 hello world
+hello world\r\n
多行字符串 hello world
$11\r\nhello world\r\n
多行字符串当然也可以表示单行字符串。
整数 1024
:1024\r\n
错误 参数类型错误
-WRONGTYPE Operation against a key holding the wrong kind of value\r\n
数组 [1,2,3]
*3\r\n:1\r\n:2\r\n:3\r\n
NULL 用多行字符串表示,不过长度要写成-1。
$-1\r\n
空串 用多行字符串表示,长度填 0。
$0\r\n\r\n
注意这里有两个\r\n。为什么是两个?因为两个\r\n之间,隔的是空串。
org.redisson.client.handler.CommandEncoder#encode()
private static final char ARGS_PREFIX = '*';
private static final char BYTES_PREFIX = '$';
private static final byte[] CRLF = "\r\n".getBytes();
@Override
    protected void encode(ChannelHandlerContext ctx, CommandData<?, ?> msg, ByteBuf out) throws Exception {
        try {
          	//redis命令前缀
            out.writeByte(ARGS_PREFIX);
            int len = 1 + msg.getParams().length;
            if (msg.getCommand().getSubName() != null) {
                len++;
            }
            out.writeCharSequence(Long.toString(len), CharsetUtil.US_ASCII);
            out.writeBytes(CRLF);
            writeArgument(out, msg.getCommand().getName().getBytes(CharsetUtil.UTF_8));
            if (msg.getCommand().getSubName() != null) {
                writeArgument(out, msg.getCommand().getSubName().getBytes(CharsetUtil.UTF_8));
            }
          	......
        } catch (Exception e) {
            msg.tryFailure(e);
            throw e;
        }
    }
private void writeArgument(ByteBuf out, ByteBuf arg) {
    out.writeByte(BYTES_PREFIX);
    out.writeCharSequence(Long.toString(arg.readableBytes()), CharsetUtil.US_ASCII);
    out.writeBytes(CRLF);
    out.writeBytes(arg, arg.readerIndex(), arg.readableBytes());
    out.writeBytes(CRLF);
}
Redis协议命令解码器
org.redisson.client.handler.CommandDecoder#readBytes
 private static final char CR = '\r';
 private static final char LF = '\n';
 private static final char ZERO = '0';
private ByteBuf readBytes(ByteBuf is) throws IOException {
    long l = readLong(is);
    if (l > Integer.MAX_VALUE) {
        throw new IllegalArgumentException(
                "Java only supports arrays up to " + Integer.MAX_VALUE + " in size");
    }
    int size = (int) l;
    if (size == -1) {
        return null;
    }
    ByteBuf buffer = is.readSlice(size);
    int cr = is.readByte();
    int lf = is.readByte();
  	//判断是否以\r\n开头
    if (cr != CR || lf != LF) {
        throw new IOException("Improper line ending: " + cr + ", " + lf);
    }
    return buffer;
}
数据序列化
Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。Redisson提供了以下几种的对象编码应用,以供大家选择:
| 编码类名称 | 说明 | 
|---|---|
| org.redisson.codec.JsonJacksonCodec | Jackson JSON 编码 默认编码 | 
| org.redisson.codec.AvroJacksonCodec | Avro 一个二进制的JSON编码 | 
| org.redisson.codec.SmileJacksonCodec | Smile 另一个二进制的JSON编码 | 
| org.redisson.codec.CborJacksonCodec | CBOR 又一个二进制的JSON编码 | 
| org.redisson.codec.MsgPackJacksonCodec | MsgPack 再来一个二进制的JSON编码 | 
| org.redisson.codec.IonJacksonCodec | Amazon Ion 亚马逊的Ion编码,格式与JSON类似 | 
| org.redisson.codec.KryoCodec | Kryo 二进制对象序列化编码 | 
| org.redisson.codec.SerializationCodec | JDK序列化编码 | 
| org.redisson.codec.FstCodec | FST 10倍于JDK序列化性能而且100%兼容的编码 | 
| org.redisson.codec.LZ4Codec | LZ4 压缩型序列化对象编码 | 
| org.redisson.codec.SnappyCodec | Snappy 另一个压缩型序列化对象编码 | 
| org.redisson.client.codec.JsonJacksonMapCodec | 基于Jackson的映射类使用的编码。可用于避免序列化类的信息,以及用于解决使用 byte[]遇到的问题。 | 
| org.redisson.client.codec.StringCodec | 纯字符串编码(无转换) | 
| org.redisson.client.codec.LongCodec | 纯整长型数字编码(无转换) | 
| org.redisson.client.codec.ByteArrayCodec | 字节数组编码 | 
| org.redisson.codec.CompositeCodec | 用来组合多种不同编码在一起 | 

Codec
public interface Codec {
  	//返回用于HMAP Redis结构中哈希映射值的对象解码器
    Decoder<Object> getMapValueDecoder();
  	//返回用于HMAP Redis结构中哈希映射值的对象编码器
    Encoder getMapValueEncoder();
  	//返回用于HMAP Redis结构中哈希映射键的对象解码器
    Decoder<Object> getMapKeyDecoder();
  	//返回用于HMAP Redis结构中哈希映射键的对象编码器
    Encoder getMapKeyEncoder();
    //返回用于除HMAP之外的任何存储Redis结构的对象解码器
    Decoder<Object> getValueDecoder();
    //返回用于除HMAP之外的任何存储Redis结构的对象编码器
    Encoder getValueEncoder();
    //返回用于加载解码过程中使用的类的类加载器对象
    ClassLoader getClassLoader();
}
BaseCodec
org.redisson.client.codec.BaseCodec

- HashMap的键值对的编解码的处理类使用普通的对象编解码处理类进行分解。 - //返回用于除HMAP之外的任何存储Redis结构的对象解码器
 Decoder<Object> getValueDecoder(); //返回用于除HMAP之外的任何存储Redis结构的对象编码器
 Encoder getValueEncoder();
 
SerializationCodec
org.redisson.codec.SerializationCodec
Decoder

Encoder

高性能/并发的保证-Netty在Redisson的应用的更多相关文章
- 使用 ACE 库框架在 UNIX 中开发高性能并发应用
		使用 ACE 库框架在 UNIX 中开发高性能并发应用来源:developerWorks 中国 作者:Arpan Sen ACE 开放源码工具包可以帮助开发人员创建健壮的可移植多线程应用程序.本文讨论 ... 
- Java高性能并发编程——线程池
		在通常情况下,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的 ... 
- Java之——redis并发读写锁,使用Redisson实现分布式锁
		原文:http://blog.csdn.net/l1028386804/article/details/73523810 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入 ... 
- Disruptor 高性能并发框架二次封装
		Disruptor是一款java高性能无锁并发处理框架.和JDK中的BlockingQueue有相似处,但是它的处理速度非常快!!!号称“一个线程一秒钟可以处理600W个订单”(反正渣渣电脑是没体会到 ... 
- 使用update可以防止并发问题(保证数据的准确性),如果使用select会产生并发问题 ; select * from xx for update 给查询开启事务,默认情况下是没有事物的
		update可以锁住数据防止数据被更新且导致与查询出的数据有误差,如果响应条数为0.说明更新失败 则可以回滚事务; 
- 2020年java架构师是什么-java架构师基本要求
		Java系统架构师是一个既必须掌控整体又必须洞察部分瓶颈并根据实际的业务流程情景得出解决方法的团队领导型角色.一个架构师得必须充足的创造力,可以各种各样目标要求开展不一样层面的拓展,为目标顾客出示更加 ... 
- Java异步NIO框架Netty实现高性能高并发
		原文地址:http://blog.csdn.net/opengl_es/article/details/40979371?utm_source=tuicool&utm_medium=refer ... 
- Java与Netty实现高性能高并发
		摘要: 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程 ... 
- Netty高性能之Reactor线程模型
		Netty是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用 ... 
随机推荐
- Vue项目二、vue-cli2.x脚手架搭建build文件夹及config文件夹详解
			build文件夹下 build.js 'use strict' // js的严格模式 require('./check-versions')() // node和npm的版本检查 process.en ... 
- 一起学习vue源码 - Object的变化侦测
			作者:小土豆biubiubiu 博客园:www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d 简书:h ... 
- 02 JPA
			JPA概述 JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成. JPA通过JDK ... 
- 使用ajax提交登录
			引入jquery-1.10.2.js或者jquery-1.10.2.min.js 页面 <h3>后台系统登录</h3> <form name="MyForm&q ... 
- flask 对于用户登录保持状态 flask_login
			先加载flask_login ext.py 在app下的__init__.py 进行引用把,我就不写了 login_manager = LoginManager() # 如果没有登录则重定向到该蓝图 ... 
- Simulink仿真入门到精通(十四) Simulink自定义环境
			14.1 Simulink环境自定义功能 sl_sustomization.m函数是Simulink提供给用户使用MATLAB语言自定义Simulink标准人机界面的函数机制.若sl_sustomiz ... 
- Hadoop集群搭建(五)~搭建集群
			继上篇关闭防火墙之后,因为后面我们会管理一个集群,在VMware中不断切换不同节点,为了管理方便我选择xshell这个连接工具,大家也可以选择SecureCRT等工具. 本篇记录一下3台机器集群的搭建 ... 
- 【Python】2.11学习笔记  注释,print,input,数据类型,标识符
			前面学了好多内存什么的知识,没什么用(我有眼不识泰山233 吐槽一句,这课简直就是讲给完全的编程小白听得 就从语言开始写吧(其实好多已经看过了,再来一遍 话说我已经忘了\(Markdown\)怎么写了 ... 
- go例子(二) 使用go语言实现数独游戏
			例子托管于github example.go package main import ( "./sudoku" ) func main() { //var smap ... 
- webpack资料,还需整理
			参考地址: https://github.com/ruanyf/webpack-demos#demo01-entry-file-source http://www.jianshu.com/p/4df9 ... 
