REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践

主要内容:
一、什么是分布式锁?
二、分布式锁的业务应用场景
三、REDIS分布式锁的构建
四、SpringBoot+Redis集群的"减库存"粗糙实践
五、遇到的问题与解决
六、待学习的文章

一、什么是分布式锁?
    我最初的理解:例如在减库存的场景下,多个线程并发访问DB或redis集群时,锁是用来同步多线程操作集群中的一个共享变量标记,使该共享变量不会发生"超卖"现象。
    推荐学习这篇文章 “分布式锁的一点理解” 这样理解分布式锁:
1.分布式与单机情况下最大的不同在于其不是多线程而是多进程。
2.多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。
3.当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
4.与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。
5.分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。
    再推荐这篇文章 “分布式锁的作用及实现(Redis)

二、分布式锁的业务应用场景
秒杀减库存、全局increase、楼层生成、分布式调度等

三、REDIS分布式锁的构建
这里直接提供传送门,不再COPY
1.《Redis官方文档》用Redis构建分布式锁
2. redis分布式锁安全性探讨(一):基于单个redis节点的分布式锁
3. redis分布式锁的安全性探讨(二):分布式锁Redlock
4. Redis学习笔记(六)redis实现分布式锁
    
四、SpringBoot+Redis集群的"减库存"简单实践
1.参见 REDIS学习总结(一)单机集群搭建
2.SpringBoot项目搭建
REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践
pom.xml文件依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jupiter.redis.test</groupId>
    <artifactId>redis-learn</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis-learn</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.10.0</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project

FastJson2RedisJsonSerializer、RedisLearnApplication、application.properties、redis.properties REDIS学习总结(二)Java操作redis集群
RedisConfig内容(--2019-01-31 update--)

package com.jupiter.redis.commons;

import java.time.Duration;
import java.util.HashSet;
import java.util.Set;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.parser.ParserConfig;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

/*
 * @author Jupiter
 * @date 2019/1/20-23:41
 * @description redis配置
 */
@Component
@PropertySource("classpath:redis.properties")
public class RedisConfig {

    @Value("${spring.redis.cluster.nodes}")
    private String clusterNodes;
    @Value("${redis.cache.timeout}")
    private int timeout;
    @Value("${redis.pool.max-idle}")
    private int maxIdle;
    @Value("${redis.pool.max-wait}")
    private long maxWaitMillis;
    @Value("${redis.cache.commandTimeout}")
    private int commandTimeout;

    @Bean
    public RedisSerializer fastJson2JsonRedisSerializer(){
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        return new FastJson2RedisJsonSerializer<Object>(Object.class);
    }

    @Bean
    public Redisson redisson() {
        //支持单机,主从,哨兵,集群等模式
        String[] cNodes = clusterNodes.split(",");
        Config config = new Config();
        for (String node:cNodes) {
            config.useClusterServers().addNodeAddress("redis://"+node);
        }
        return (Redisson)Redisson.create(config);
    }

    @Bean
    public JedisCluster getJedisCluster() {
        String[] cNodes = clusterNodes.split(",");
        Set<HostAndPort> nodes =new HashSet<>();
        //分割出集群节点
        for(String node : cNodes) {
            String[] hp = node.split(":");
            nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
        }
        JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        //创建集群对象
        return new JedisCluster(nodes,timeout,jedisPoolConfig);
    }

    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //使用fastjson做序列化
        FastJson2RedisJsonSerializer fastJson2RedisJsonSerializer = new FastJson2RedisJsonSerializer(Object.class);
        redisTemplate.setValueSerializer(fastJson2RedisJsonSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    public RedisConnectionFactory connectionFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(false);
        poolConfig.setTestWhileIdle(true);
        JedisClientConfiguration clientConfig = JedisClientConfiguration.builder()
                .usePooling().poolConfig(poolConfig).and().readTimeout(Duration.ofMillis(timeout)).build();

        // 单点redis
        //RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        // 哨兵redis
        // RedisSentinelConfiguration redisConfig = new RedisSentinelConfiguration();
        // 集群redis
        RedisClusterConfiguration redisConfig = new RedisClusterConfiguration();
        String[] cNodes = clusterNodes.split(",");
        Set<RedisNode> nodes =new HashSet<>();
        //分割出集群节点
        for(String node : cNodes) {
            String[] hp = node.split(":");
            nodes.add(new RedisNode(hp[0], Integer.parseInt(hp[1])));
        }
        redisConfig.setClusterNodes(nodes);
        //redisConfig.setPassword(RedisPassword.of(redisAuth));

        return new JedisConnectionFactory(redisConfig, clientConfig);
    }
}

RushController内容:

package com.jupiter.redis.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author Jupiter
 * @date 2019/1/28-19:45
 * @description 秒杀请求Controller,当然前端要层层过滤,过滤掉
 */
@RestController
@Slf4j
public class RushController {

    private static String commodityCount = "commodityCount";//商品总数key
    private static String lockKey = "testRedisson";//分布式锁的key

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired private Redisson redisson;

    /** 初步测试计数,统计压测访问请求成功数 .*/
    private int cnt = 0;

    /**
     * 设置商品数量为100个
     * @return
     */
    @RequestMapping("/setValue")
    public String setValue(){
        //商品名称、数量设置成可配置
        redisTemplate.opsForValue().set(commodityCount , "100");
        return "success";
    }

    /**
     * 模拟抢购,使用Jmeter进行并发压测,查看是否出现超卖
     * @return
     */
    @RequestMapping("/rushGoods")
    public String spike(){
        String flag = "success";
        //获取redis分布式锁
        RLock lock = redisson.getLock(lockKey);
        try{
            //获取锁的等待时间、锁的自动释放时间设置(这里可以进行配置优化)
            Future<Boolean> res = lock.tryLockAsync(100, 5, TimeUnit.SECONDS);
            boolean result = res.get();
            //拿到锁才能进行下一步
            if(result){
                int stock = Integer.parseInt(redisTemplate.opsForValue().get(commodityCount).toString());
                if(stock > 0){
                    redisTemplate.opsForValue().set(commodityCount,(stock-1)+"");
                }else{
                    flag = "fail";
                }
                log.info("第"+(cnt++)+"个线程:"+(flag.equals("success")?"成功买到":"没买到"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        finally{
            lock.unlock(); //释放锁
        }
        //如果释放锁失败,是不会返回“success”的,因为直接跑出异常了
        return flag;
    }
}

3.秒杀测试
首先,启动运行Spring工程
① 浏览器访问localhost:8080/setValue 进行商品数量设置,DEMO中写的固定100,可在redis中get一下commodityCount
② 使用Jmeter进行压力测试,设置如下:
REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践

 REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践
REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践
REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践
REDIS学习总结(三)REDIS集群下分布式锁学习与SpringBoot秒杀场景简单实践
③ 运行测试(Ctrl+E清空测试),可以看到,Jmeter在1秒内压入1000并发请求(更大并发测试还要看机器性能或者搭建Jmeter测试集群,前端设置个代理执行),而tomcat响应4秒多,没报错,但更大并发会报错,推荐看 五、六两部分

2019-01-30 22:47:38.770  INFO 16816 --- [io-8080-exec-10] c.j.redis.controller.RushController      : 第0个线程:成功买到
2019-01-30 22:47:38.842  INFO 16816 --- [io-8080-exec-39] c.j.redis.controller.RushController      : 第1个线程:成功买到
2019-01-30 22:47:39.231  INFO 16816 --- [io-8080-exec-59] c.j.redis.controller.RushController      : 第2个线程:成功买到
2019-01-30 22:47:39.332  INFO 16816 --- [io-8080-exec-84] c.j.redis.controller.RushController      : 第3个线程:成功买到
2019-01-30 22:47:39.341  INFO 16816 --- [io-8080-exec-71] c.j.redis.controller.RushController      : 第4个线程:成功买到
2019-01-30 22:47:39.346  INFO 16816 --- [io-8080-exec-48] c.j.redis.controller.RushController      : 第5个线程:成功买到
......
2019-01-30 22:47:40.152  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第99个线程:成功买到
2019-01-30 22:47:40.156  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第100个线程:没买到
2019-01-30 22:47:40.158  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第101个线程:没买到
2019-01-30 22:47:40.161  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第102个线程:没买到
2019-01-30 22:47:40.163  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第103个线程:没买到
2019-01-30 22:47:40.166  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第104个线程:没买到
2019-01-30 22:47:40.168  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第105个线程:没买到
2019-01-30 22:47:40.172  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第106个线程:没买到
2019-01-30 22:47:40.175  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第107个线程:没买到
2019-01-30 22:47:40.179  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第108个线程:没买到
2019-01-30 22:47:40.182  INFO 16816 --- [o-8080-exec-155] c.j.redis.controller.RushController      : 第109个线程:没买到
2019-01-30 22:47:40.186  INFO 16816 --- [o-8080-exec-154] c.j.redis.controller.RushController      : 第110个线程:没买到
......
2019-01-30 22:47:42.906  INFO 16816 --- [o-8080-exec-180] c.j.redis.controller.RushController      : 第999个线程:没买到

注:以上"第XX个线程"并非实际线程访问顺序,只是计入秒杀方法的线程计数而已
    
五、遇到的问题与解决
问题一:o.r.cluster.ClusterConnectionManager:connection timed out: 使用本地可以ping同centos,但是就是出现connect timed out,原因:centos的防火墙没有关闭。 
解决:查询防火墙状态:systemctl status firewalld.service 打开方法:systemctl start firewalld.service  关闭方法:systemctl stop firewalld.service 
问题二:高并发压测异常(1秒3000并发)解决见:秒杀系统架构优化思路

2019-01-29 20:45:16.460 ERROR 25132 --- [http-nio-8080-exec-23] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: c35ec7ec-d76b-493f-b2a7-cec1c8b85668 thread-id: 2015] with root cause
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: c35ec7ec-d76b-493f-b2a7-cec1c8b85668 thread-id: 2015
    at org.redisson.RedissonLock$5.operationComplete(RedissonLock.java:521) ~[redisson-3.10.0.jar:na]
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:504) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:483) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:103) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88) ~[redisson-3.10.0.jar:na]
    at org.redisson.command.CommandAsyncService.handleReference(CommandAsyncService.java:1081) ~[redisson-3.10.0.jar:na]
    at org.redisson.command.CommandAsyncService.handleSuccess(CommandAsyncService.java:1074) ~[redisson-3.10.0.jar:na]
    at org.redisson.command.CommandAsyncService.checkAttemptFuture(CommandAsyncService.java:1056) ~[redisson-3.10.0.jar:na]
    at org.redisson.command.CommandAsyncService$12.operationComplete(CommandAsyncService.java:811) ~[redisson-3.10.0.jar:na]
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:504) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:483) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:103) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88) ~[redisson-3.10.0.jar:na]
    at org.redisson.client.handler.CommandDecoder.completeResponse(CommandDecoder.java:415) ~[redisson-3.10.0.jar:na]
    at org.redisson.client.handler.CommandDecoder.handleResult(CommandDecoder.java:410) ~[redisson-3.10.0.jar:na]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:332) ~[redisson-3.10.0.jar:na]
    at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:130) ~[redisson-3.10.0.jar:na]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:110) ~[redisson-3.10.0.jar:na]
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) ~[netty-codec-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:367) ~[netty-codec-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) ~[netty-codec-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1414) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:945) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:146) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459) ~[netty-transport-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.22.Final.jar:4.1.22.Final]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_91]

问题三:高并发压测异常(1秒10000并发)解决:秒杀系统架构优化思路
    [redisson-netty-14-13] o.r.client.handler.CommandDecoder        : Unable to decode data.
        
六、待学习的文章
    1. 电商商品秒杀系统架构分析与实战
    2. 秒杀系统架构优化思路
    3. JAVA构建高并发商城秒杀系统——架构分析
    4. 秒杀系统架构分析与实战
    5. redisson的理解和使用-调用流程
    6. Redisson源码
    其他参考文献:
    1. SpringBoot项目开发(十五):redisson实现分布式锁 (集群)
    2. 如何用Redlock实现分布式锁
    3. Redis分布式锁的正确实现方式 (单实例REDIS)
    4. Redisson分布式锁
    5. Redisson分布式锁实现
    6. SpringBoot集成redisson分布式锁