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项目搭建
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进行压力测试,设置如下:
③ 运行测试(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分布式锁