lettuce客户端底层bug(-READONLY You can‘t write against a read only replica.)

简介: lettuce客户端底层、READONLY You can't write against a read only replica.

出现的现象:

这个问题只在 AWS elastic cache版本会重现

org.springframework.data.redis.RedisSystemException:
Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException:
ERR Error running script (call to f_e9f69f2beb755be68b5e456ee2ce9aadfbc4ebf4):
@user_script:1: @user_script: 1: -READONLY You can't write against a read only replica.  
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:275)
        at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.convertLettuceAccessException(LettuceScriptingCommands.java:236)
        at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.evalSha(LettuceScriptingCommands.java:195)
        at org.springframework.data.redis.connection.DefaultedRedisConnection.evalSha(DefaultedRedisConnection.java:1502)
        at jdk.internal.reflect.GeneratedMethodAccessor225.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:61)
        at com.sun.proxy.$Proxy248.evalSha(Unknown Source)
        at org.springframework.data.redis.core.script.DefaultScriptExecutor.eval(DefaultScriptExecutor.java:77)
        at org.springframework.data.redis.core.script.DefaultScriptExecutor.lambda$execute$0(DefaultScriptExecutor.java:68)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:175)
        at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:58)
        at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:52)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:350)


官方关于这个bug的描述:

image.png

image.png

environment

  • Lettuce version(s): [5.0.3.RELEASE, 5.0.4.RELEASE,5.1.0.M1]
  • Redis version: AWS 6.0.5

官方case:

https://stackoverflow.com/questions/62992129/lettuce-readonly-you-cant-write-against-a-read-only-slave

https://github.com/lettuce-io/lettuce-core/issues/804


问题原因:

ReadFrom类详情配置:https://lettuce.io/core/6.1.4.RELEASE/api/index.html

Modifier and Type   Field and Description
static ReadFrom ANY                     Setting to read from any node.
static ReadFrom ANY_REPLICA             Setting to read from any replica node.
static ReadFrom MASTER                  Setting to read from the upstream only.
static ReadFrom MASTER_PREFERRED        Setting to read preferred from the upstream and fall back to a replica if the master is not available.
static ReadFrom NEAREST                 Setting to read from the nearest node.
static ReadFrom REPLICA                 Setting to read from the replica only.
static ReadFrom REPLICA_PREFERRED       Setting to read preferred from replica and fall back to upstream if no replica is not available.
static ReadFrom SLAV                    EDeprecated. renamed to REPLICA.
static ReadFrom SLAVE_PREFERRED         Deprecated. Renamed to REPLICA_PREFERRED.
static ReadFrom UPSTREAM                Setting to read from the upstream only.
static ReadFrom UPSTREAM_PREFERRED      Setting to read preferred from the upstream and fall back to a replica if the upstream is not available.

查看读写分离的读取节点

io.lettuce.core.masterslave.MasterSlaveConnectionProvider
 public CompletableFuture<StatefulRedisConnection<K, V>> getConnectionAsync(MasterSlaveConnectionProvider.Intent intent) {
        if (this.debugEnabled) {
            logger.debug("getConnectionAsync(" + intent + ")");
        }
        if (this.readFrom != null && intent == MasterSlaveConnectionProvider.Intent.READ) {
            List<RedisNodeDescription> selection = this.readFrom.select(new Nodes() {
                public List<RedisNodeDescription> getNodes() {
                    return MasterSlaveConnectionProvider.this.knownNodes;
                }
                public Iterator<RedisNodeDescription> iterator() {
                    return MasterSlaveConnectionProvider.this.knownNodes.iterator();
                }
            });
            if (selection.isEmpty()) {
                throw new RedisException(String.format("Cannot determine a node to read (Known nodes: %s) with setting %s", this.knownNodes, this.readFrom));
            } else {
                try {
                    Flux<StatefulRedisConnection<K, V>> connections = Flux.empty();
                    RedisNodeDescription node;
                    for(Iterator var4 = selection.iterator(); var4.hasNext(); connections = connections.concatWith(Mono.fromFuture(this.getConnection(node)))) {
                        node = (RedisNodeDescription)var4.next();
                    }
                    return !OrderingReadFromAccessor.isOrderSensitive(this.readFrom) && selection.size() != 1 ? connections.filter(StatefulConnection::isOpen).collectList().map((it) -> {
                        int index = ThreadLocalRandom.current().nextInt(it.size());
                        return (StatefulRedisConnection)it.get(index);
                    }).switchIfEmpty(connections.next()).toFuture() : connections.filter(StatefulConnection::isOpen).next().switchIfEmpty(connections.next()).toFuture();
                } catch (RuntimeException var6) {
                    throw Exceptions.bubble(var6);
                }
            }
        } else {
            return this.getConnection(this.getMaster());
        }
    }


rredisTemplate.execute方法在执行lua脚本时,使用的是redis的evalsha方法,默认算读操作(io.lettuce.core.masterslave.ReadOnlyCommands.isReadOnlyCommand方法中有体现),

所以被分配到了slave节点,而lua脚本一般读写并存操作,比如以上代码释放redis锁,而redis的slave节点不能进行write操作,所以报以下错误


// eval 和 evalsha是redis执行脚本的方法,

EVAL,EVALSHA



解决方案:


1、重写lettuce底层源码ReadOnlyCommands中CommandName枚举类,将 EVAL, EVALSHA从此类删除

  • 找到你所要重写类,查看其中的路径;io.lettuce.core.masterslave.ReadOnlyCommands
  • 在我们的 src 目录下新建一个同包名同类名的类;
  • 将 jar 包中的重写方法所在类的所有代码复制到我们新建的同包名同类名的类中;
  • 在CommandName枚举中删除EVAL, EVALSHA属性
  • 程序会优先使用我们 src 下面的类,这样就覆盖了 jar 包的方法 。

2、升级lettuce-core到最新的版本,新版本已经解决。


相关文章
|
NoSQL 网络协议 数据库
为什么 Lettuce 会带来更长的故障时间
本文详述了阿里云数据库 Tair/Redis 将使用长连接客户端在非预期故障宕机切换场景下的恢复时间从最初的 900s 降到 120s 再到 30s的优化过程,涉及产品优化,开源产品问题修复等诸多方面。
71193 11
为什么 Lettuce 会带来更长的故障时间
|
NoSQL Java Redis
redis集群拓扑结构自动更新:使用Lettuce连接Cluster集群实例时异常处理(一)
redis集群拓扑结构自动更新:使用Lettuce连接Cluster集群实例时异常处理
2576 0
|
架构师 测试技术 网络性能优化
dpdk课程学习之练习笔记七(vpp环境搭建及plugin demo测试)
dpdk课程学习之练习笔记七(vpp环境搭建及plugin demo测试)
1220 0
|
存储 NoSQL Redis
Redis 配置
10月更文挑战第14天
352 1
|
缓存 监控 供应链
1688商品数据接口深度解析:从基础调用到企业级应用
本文介绍了通过1688官方API获取商品数据的应用场景和技术实现,涵盖供应商管理、采购比价、ERP对接及跨境选品等业务赋能。技术流程包括企业认证、接口调用和关键参数说明,并解析了响应数据结构。架构设计方面,提供了高并发解决方案、数据更新策略和缓存优化方案。同时,强调了合规与风控措施,如数据使用规范和接口安全防护。最后,针对常见问题给出了解决方案,并提出了数据应用扩展和监控体系搭建的进阶建议。该方案已成功应用于多个B2B供应链平台,日均处理超2000万条商品数据。
|
网络协议 Linux Shell
在Linux中,如何通过一个端口找到程序?
在Linux中,如何通过一个端口找到程序?
|
机器学习/深度学习 算法 机器人
|
机器学习/深度学习 开发框架 自然语言处理
深度学习中的自动学习率调整方法探索与应用
传统深度学习模型中,学习率的选择对训练效果至关重要,然而其调整通常依赖于经验或静态策略。本文探讨了现代深度学习中的自动学习率调整方法,通过分析不同算法的原理与应用实例,展示了这些方法在提高模型收敛速度和精度方面的潜力。 【7月更文挑战第14天】
427 3
|
SQL 关系型数据库 数据库
Grafana实现参数查询功能
Grafana实现参数查询功能
|
SQL 安全 网络安全
与WAF的“相爱相杀”的RASP
WAF(Web Application Firewall)和新兴的RASP(Runtime Application Self-Protection)都是保护Web应用安全的工具。WAF专注于HTTP流量分析,防止恶意行为,但易受误报困扰,可被加密和混淆流量绕过,且难以防御0day漏洞。RASP则通过应用内部监控减少误报和漏报,能防御加密流量,但对宏观流量监控不足,可能影响性能和兼容性。RASP并非要替代WAF,两者结合使用,WAF提供流量预警和防御大流量攻击,RASP则擅长拦截混淆流量和优化业务场景,共同增强应用安全防护。

热门文章

最新文章