You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Record作为Apache Ignite Cache键的序列化异常问题

Java Record作为Apache Ignite Cache键时序列化报错的解决方案

问题描述

我尝试将Java Record类型作为Apache Ignite Cache的键使用时触发了错误,当前环境是Java 17.0.12(openjdk version "17.0.12" 2024-07-16)和GridGain Ignite 8.8.39。想知道有没有其他开发者遇到过同样的问题,以及是否必须自定义序列化处理才能使用Record类型?

错误日志

java.lang.UnsupportedOperationException: can't get field offset on a
record class: private final long
***.TestIgniteCache$TestKey.ttlInMillis at
jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unknown Source) at
org.apache.ignite.internal.util.GridUnsafe.objectFieldOffset(GridUnsafe.java:1193)
at
org.apache.ignite.internal.binary.BinaryFieldAccessor$AbstractPrimitiveAccessor.(BinaryFieldAccessor.java:223)
at
org.apache.ignite.internal.binary.BinaryFieldAccessor$LongPrimitiveAccessor.(BinaryFieldAccessor.java:387)
at
org.apache.ignite.internal.binary.BinaryFieldAccessor.create(BinaryFieldAccessor.java:75)
at
org.apache.ignite.internal.binary.BinaryClassDescriptor.(BinaryClassDescriptor.java:368)
at
org.apache.ignite.internal.binary.BinaryClassDescriptor.(BinaryClassDescriptor.java:155)
at
org.apache.ignite.internal.binary.BinaryContext.createDescriptorForClass(BinaryContext.java:648)
at
org.apache.ignite.internal.binary.BinaryContext.descriptorForClass(BinaryContext.java:606)
at
org.apache.ignite.internal.binary.BinaryWriterExImpl.marshal0(BinaryWriterExImpl.java:181)
at
org.apache.ignite.internal.binary.BinaryWriterExImpl.marshal(BinaryWriterExImpl.java:164)
at
org.apache.ignite.internal.binary.BinaryWriterExImpl.marshal(BinaryWriterExImpl.java:151)
at
org.apache.ignite.internal.binary.GridBinaryMarshaller.marshal(GridBinaryMarshaller.java:250)
at
org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.marshalToBinary(CacheObjectBinaryProcessorImpl.java:506)
at
org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.toBinary(CacheObjectBinaryProcessorImpl.java:1360)
at
org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.toCacheKeyObject(CacheObjectBinaryProcessorImpl.java:1137)
at
org.apache.ignite.internal.processors.cache.GridCacheContext.toCacheKeyObject(GridCacheContext.java:1841)
at
org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache$12.apply(GridDhtAtomicCache.java:476)
at
org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache$12.apply(GridDhtAtomicCache.java:474)
at
org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.asyncOp(GridDhtAtomicCache.java:766)
at
org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.getAsync(GridDhtAtomicCache.java:474)
at
org.apache.ignite.internal.processors.cache.GridCacheAdapter.get0(GridCacheAdapter.java:4376)
at
org.apache.ignite.internal.processors.cache.GridCacheAdapter.get(GridCacheAdapter.java:4357)
at
org.apache.ignite.internal.processors.cache.GridCacheAdapter.get(GridCacheAdapter.java:1432)
at
org.apache.ignite.internal.processors.cache.IgniteCacheProxyImpl.get(IgniteCacheProxyImpl.java:1116)
at
org.apache.ignite.internal.processors.cache.GatewayProtectedCacheProxy.get(GatewayProtectedCacheProxy.java:637)
at ***.cache.TestIgniteCache.get(TestIgniteCache.java:123)

Record类定义

public static record TestKey<K>(K k, long ttlInMillis) {

    // 默认TTL为5分钟
    private static final long defaultTTLInMillis = 300000L;

    /**
     * 指定TTL的构造方法
     * @param k 键值
     * @param ttlInMillis 毫秒级TTL
     */
    public TestKey {
        if (Objects.isNull(k)) { 
            throw new IllegalArgumentException("Key must not be null!");
        }
        if (0 >= ttlInMillis) { 
            throw new IllegalArgumentException("ttlInMillis must not be zero or negative!");
        }
    }

    /**
     * 使用默认TTL的构造方法
     * @param k 键值
     */
    public TestKey (K k) {
        this(k, defaultTTLInMillis);
    }

    /**
     * 获取默认TTL值
     * @return 默认毫秒级TTL
     */
    public static final long defaultTTLInMillis() {
        return defaultTTLInMillis;
    }

}

问题原因

Ignite的默认二进制序列化器依赖sun.misc.Unsafe.objectFieldOffset()方法获取字段偏移量,但Java Record的字段是隐式定义的,Unsafe API不支持直接获取Record字段的偏移量,这直接导致了报错。

解决方案

有两种可行的处理方式:

1. 自定义序列化器

为Record类实现BinarySerializer接口,手动控制序列化和反序列化逻辑,绕过Unsafe的字段偏移量获取逻辑。示例代码如下:

public class TestKeyBinarySerializer implements BinarySerializer {
    @Override
    public void writeBinary(Object obj, BinaryWriter writer) throws BinaryObjectException {
        TestKey<?> key = (TestKey<?>) obj;
        writer.writeObject("k", key.k());
        writer.writeLong("ttlInMillis", key.ttlInMillis());
    }

    @Override
    public void readBinary(Object obj, BinaryReader reader) throws BinaryObjectException {
        // Record是不可变类,反序列化时通过构造方法初始化,只需按写入顺序读取所有字段
        reader.readObject("k");
        reader.readLong("ttlInMillis");
    }
}

然后在Ignite配置中注册这个序列化器:

IgniteConfiguration cfg = new IgniteConfiguration();
BinaryConfiguration binaryCfg = new BinaryConfiguration();
BinaryTypeConfiguration typeCfg = new BinaryTypeConfiguration(TestKey.class.getName());
typeCfg.setSerializer(new TestKeyBinarySerializer());
binaryCfg.setTypeConfigurations(List.of(typeCfg));
cfg.setBinaryConfiguration(binaryCfg);

2. 改用普通不可变类替代Record

将TestKey改为普通类,手动实现equals()和hashCode()方法(Ignite缓存键必须实现这两个方法保证正确性):

public static class TestKey<K> {
    private final K k;
    private final long ttlInMillis;
    private static final long defaultTTLInMillis = 300000L;

    public TestKey(K k, long ttlInMillis) {
        if (Objects.isNull(k)) {
            throw new IllegalArgumentException("Key must not be null!");
        }
        if (0 >= ttlInMillis) {
            throw new IllegalArgumentException("ttlInMillis must not be zero or negative!");
        }
        this.k = k;
        this.ttlInMillis = ttlInMillis;
    }

    public TestKey(K k) {
        this(k, defaultTTLInMillis);
    }

    public static long defaultTTLInMillis() {
        return defaultTTLInMillis;
    }

    public K k() {
        return k;
    }

    public long ttlInMillis() {
        return ttlInMillis;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestKey<?> testKey = (TestKey<?>) o;
        return ttlInMillis == testKey.ttlInMillis && Objects.equals(k, testKey.k);
    }

    @Override
    public int hashCode() {
        return Objects.hash(k, ttlInMillis);
    }
}

补充说明

GridGain Ignite 8.8.x版本确实存在对Java Record的支持缺陷,后续版本可能会修复该问题。如果坚持使用Record,自定义序列化器是最直接的解决方式。

内容的提问来源于stack exchange,提问作者lmk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 02:08:12