【问题标题】:Invalid lambda deserialization with Infinispan Cache and computeIfAbsentInfinispan Cache 和 computeIfAbsent 的 lambda 反序列化无效
【发布时间】:2019-08-22 18:12:46
【问题描述】:

我在玩基本的 infinispan 集群,遇到了一个令人费解的错误。

我基本上是在实现一个共享地图,只保存一个整数

这是我的服务代码

package sandbox.infinispan.test.service;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.infinispan.Cache;

@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService {

    private static final String KEY = "key";

    @Inject
    private Cache<String, Integer> cache;

    @Override
    public void inc(final int amount) {
        this.cache.put(KEY, Integer.valueOf(this.get() + amount));
    }

    @Override
    public int get() {
        return this.cache.computeIfAbsent(KEY, k -> Integer.valueOf(0)).intValue();
    }
}

缓存由以下内容产生:

package sandbox.infinispan.test.config;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;

import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;

@Dependent
class CacheProvider {

    @Produces
    @ApplicationScoped
    private EmbeddedCacheManager defaultClusteredCacheManager() {
        final GlobalConfiguration g = new GlobalConfigurationBuilder() //
                .clusteredDefault() //
                .transport() //
                .nodeName(this.getNodeName()) //
                .clusterName("infinispanTestCluster") //
                .build();
        final Configuration cfg = new ConfigurationBuilder() //
                .clustering() //
                .cacheMode(CacheMode.REPL_SYNC) ///
                .build();
        return new DefaultCacheManager(g, cfg);
    }
}

当集群中至少有两台服务器时,computeIfAbsent 失败并显示

15:48:50,253 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (jgroups-7,myhostname-14393) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [key]: org.infinispan.remoting.RemoteException: ISPN000217: Received exception from otherhostname-44445, see cause for remote stack trace

深入到:

Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda)

最后是:

Caused by: java.lang.IllegalArgumentException: Invalid lambda deserialization
        at sandbox.infinispan.test.service.CounterService.$deserializeLambda$(CounterService.java:10)
            ... 68 more
Caused by: an exception which occurred:
        in object of type java.lang.invoke.SerializedLambda

如果我将我漂亮的时尚代码重写为丑陋的追随者,它会起作用。

@Override
public int get() {
    Integer value = this.cache.get(KEY);
    if (value == null) {
        value = Integer.valueOf(0);
        this.cache.put(KEY, value);
    }
    return value.intValue();
}

我现在如何使用漂亮的 computeIfAbsent 做事方式?


Eclipse 2018-12,WildFly 14,java 10 on 集群的 dev 成员,CentOs 7,OpenJdk 10,WildFly 14 在远程集群成员上。

感谢您的帮助


解决了(有点)

感谢我在这里得到的帮助,我将 lambda 转换为一个内部类:

static class OhWell implements Serializable {

    static Integer zero(final String t) {
        return Integer.valueOf(0);
    }
}

@Override
public int get() {
    return this.cache.computeIfAbsent(KEY, OhWell::zero).intValue();
}

它现在可以工作了,但它不如整洁的 lambda 好。所以我会坚持老式的方法——除非有人能想到更好的方法。


进一步的结果:

以下带有 static 方法的 static 内部类可以工作

static class StaticOhWell implements Serializable {

    static Integer apply(final String t) {
        return Integer.valueOf(0);
    }
}

@Override
public int get() {
    return this.cache.computeIfAbsent(KEY, StaticOhWell::apply).intValue();
}

以下具有非静态方法的非静态内部类失败:

class NotStaticOhWell implements SerializableFunction<String, Integer> {

    @Override
    public Integer apply(final String t) {
            return Integer.valueOf(0);
    }
}

@Override
public int get() {
    return this.cache.computeIfAbsent(KEY, new NotStaticOhWell()::apply).intValue();
}

失败并显示此错误消息 NotSerializableException: org.infinispan.cache.impl.EncoderCache:

13:41:29,221 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (default task-1) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [value]: org.infinispan.commons.marshall.NotSerializableException: org.infinispan.cache.impl.EncoderCache
    Caused by: an exception which occurred:
    in field sandbox.infinispan.test.service.CounterService.cache
    in object sandbox.infinispan.test.service.CounterService@4612a6c3
    in field sandbox.infinispan.test.service.CounterService$NotStaticOhWell.this$0
    in object sandbox.infinispan.test.service.CounterService$NotStaticOhWell@4effd362
    in field java.lang.invoke.SerializedLambda.capturedArgs
    in object java.lang.invoke.SerializedLambda@e62f08a
    in object sandbox.infinispan.test.service.CounterService$$Lambda$1195/1060417313@174a143b

结束语 (?)

使用“静态 lambda”(实现 SerializableFunction 接口的 static 内部类)也很有效

static class StaticSerializableFunction implements SerializableFunction<String, Integer> {

    @Override
    public Integer apply(final String t) {
        return Integer.valueOf(0);
    }
}

@Override
public int get() {
    return this.cache.computeIfAbsent(KEY, new StaticSerializableFunction()::apply).intValue();
}


获胜者是……

通过“transienting”使类真正可序列化,Cache 允许简单地使用此类的方法。无需创建内部类!

package sandbox.infinispan.test.service;

import java.io.Serializable;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.infinispan.Cache;

@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService, Serializable {

    private static final String KEY = "value";

    @SuppressWarnings("cdi-ambiguous-dependency")
    @Inject
    private transient Cache<String, Integer> cache;

    @Override
    public void inc(final int amount) {
        this.cache.put(KEY, Integer.valueOf(this.get() + amount));
    }

    @Override
    public int get() {
        return this.cache.computeIfAbsent(KEY, this::zero).intValue();
    }

    private Integer zero(@SuppressWarnings("unused") final String unused) {
        return Integer.valueOf(0);
    }

    @Override
    public void reset() {
        this.cache.clear();
    }
}

谢谢大家!

【问题讨论】:

  • 远程节点上是否存在CounterService 类?您是否尝试过将 lambda 更改为匿名内部类?
  • @DanBerindei 谢谢,将 lambda 移动到内部类(虽然不是匿名)成功了。
  • 恐怕您只是将实现移动到嵌套类中,OhWell::zero 仍然是一个 lambda。但是因为它只引用嵌套类中的静态方法,所以它不会捕获任何东西,而且它肯定是可序列化的。
  • 重新阅读描述,我现在很确定Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda) 表示部署在远程节点上的CounterService 类是在您添加 lambda 之前编译的,如果您添加 lambda,您的初始 lambda 将起作用在所有节点上部署相同的CounterService 类。
  • @DanBerindei 不。我非常确定在 Wildfly(苍蝇?)中运行完全相同的代码,并且仍然无效 lambda 反序列化

标签: wildfly infinispan jgroups


【解决方案1】:

根据Unable to deserialize lambda,解串器需要实际代码可用。您确定您的应用程序已在集群中的所有其他节点上启动(完全相同的版本,包括您的 lambda)?

computeIfAbsent() 将 lambda 直接发送到数据(因此使用一个 RPC 处理操作,而不是先获取值然后将其写为“丑陋的代码”)。在 WF 中,您的应用程序位于与 Infinispan 不同的类加载器(模块)中,这可能会导致问题。

您能否尝试将您的 lambda 重构为一个类,看看是否会遇到类似的问题?我对 WF 不太了解,因此对于 lambdas 缺少的常规类可能会有缓解。

【讨论】:

  • 谢谢。我将我的 lambda 重构为使用内部类,实现 SerializableFunction,而且,它成功了!不知道从一个简洁的 lambda 到一个庞大的显式内部类如何让我的代码更好......它确实有效,但我认为我会坚持明显的前一种做事方式。
  • 我并不是说这是一种更好的方法(它显然不太方便)——只是想知道这是否有效......
  • 正如您在我编辑的帖子中看到的那样,实现 SerializableFunction 实际上并没有奏效。我不得不写一个静态方法。你能对此发表评论吗?
  • 实现Serializable接口还不足以使类真正可序列化;如果它是一个内部类,它承载对外部类 (CounterService) 的引用,并且它是不可序列化的,因为它具有不可序列化的 Cache 字段。
  • 我认为你不需要使用StaticOhWell 内部类; CounterService 中的静态方法也应该可以工作。如您所见,这会导致不相关(且可以理解)的 NotSerializableException。不过,我并不完全理解 IllegalArgumentException 的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-11
  • 1970-01-01
  • 1970-01-01
  • 2016-04-03
  • 2018-03-06
  • 1970-01-01
相关资源
最近更新 更多