Apache Ignite .NET多主键场景下KeyNotFoundException问题排查
问题描述
使用GridGain Ignite社区版8.8.31(与Apache Ignite 2.15版本一致),通过DDL创建两张仅主键名称不同的表并插入数据。在C#/.NET中调用cache.get()方法时,Person4表可正常运行,但Person3表抛出KeyNotFoundException。
DDL代码
CREATE TABLE IF NOT EXISTS Person3 ( ID int, CITY_ID int, NAME varchar, AGE int, COMPANY varchar, PRIMARY KEY (ID, CITY_ID) ) WITH "template=REPLICATED,CACHE_NAME=Person3,KEY_TYPE=test.PersonKey3,VALUE_TYPE=test.PersonValue3"; INSERT INTO Person3 VALUES (1,1,'1',1,'1'); CREATE TABLE IF NOT EXISTS Person4 ( CITY_ID int, CITY_ID_2 int, NAME varchar, AGE int, COMPANY varchar, PRIMARY KEY (CITY_ID, CITY_ID_2) ) WITH "template=REPLICATED,CACHE_NAME=Person4,KEY_TYPE=test.PersonKey4,VALUE_TYPE=test.PersonValue4"; INSERT INTO Person4 VALUES (1,1,'1',1,'1');
.NET代码
namespace test { class PersonKey3 { [QuerySqlField] public int ID, CITY_ID; } class PersonValue3 : PersonKey3 { [QuerySqlField] public string NAME, COMPANY; [QuerySqlField] public int AGE; } class PersonKey4 { [QuerySqlField] public int CITY_ID, CITY_ID_2; } class PersonValue4 : PersonKey4 { [QuerySqlField] public string NAME, COMPANY; [QuerySqlField] public int AGE; } class IgniteTest { public static void Main() { IgniteClientConfiguration cfg = new IgniteClientConfiguration { Endpoints = new[] { "127.0.0.1:10800" } }; using (var client = Ignition.StartClient(cfg)) { client.GetBinary().GetBinaryType(typeof(PersonKey3)); client.GetBinary().GetBinaryType(typeof(PersonValue3)); client.GetBinary().GetBinaryType(typeof(PersonKey4)); client.GetBinary().GetBinaryType(typeof(PersonValue4)); /*runs well*/ var cache4 = client.GetCache<PersonKey4, PersonValue4>("Person4"); PersonKey4 tableKey4 = new PersonKey4(); tableKey4.CITY_ID = 1; tableKey4.CITY_ID_2 = 1; Console.WriteLine(cache4.Get(tableKey4).COMPANY); /*error!*/ var cache3 = client.GetCache<PersonKey3, PersonValue3>("Person3"); PersonKey3 tableKey3 = new PersonKey3(); tableKey3.ID = 1; tableKey3.CITY_ID = 1; Console.WriteLine(cache3.Get(tableKey3).COMPANY); } } } }
错误信息
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key was not present in the cache. at Apache.Ignite.Core.Impl.Client.Cache.CacheClient`2.UnmarshalNotNull[T](ClientResponseContext ctx) at Apache.Ignite.Core.Impl.Client.Cache.CacheClient`2.<Get>b__14_0(ClientResponseContext ctx) at Apache.Ignite.Core.Impl.Client.ClientSocket.DecodeResponse[T](BinaryHeapStream stream, Func`2 readFunc, Func`3 errorFunc) at Apache.Ignite.Core.Impl.Client.ClientSocket.DoOutInOp[T](ClientOp opId, Action`1 writeAction, Func`2 readFunc, Func`3 errorFunc) at Apache.Ignite.Core.Impl.Client.ClientFailoverSocket.DoOutInOpAffinity[T,TKey](ClientOp opId, Action`1 writeAction, Func`2 readFunc, Int32 cacheId, TKey key, Func`3 errorFunc) at Apache.Ignite.Core.Impl.Client.Cache.CacheClient`2.DoOutInOpAffinity[T](ClientOp opId, TK key, Func`2 readFunc) at Apache.Ignite.Core.Impl.Client.Cache.CacheClient`2.Get(TK key) at test.IgniteTest.Main() in E:\GHLee\dev\cs\GridgainTest\Program.cs:line 119
问题原因及解决方案
核心原因
问题出在自定义键类的相等性判断和哈希码计算上:
- C#自定义类默认使用引用相等性判断,而Ignite客户端与服务器端的键对象是不同实例,无法通过引用判断相等。
- 未重写
Equals和GetHashCode方法,导致客户端生成的键对象哈希码与服务器端存储的键哈希码不匹配,缓存无法定位到对应条目。
Person4能正常运行属于偶然情况(字段组合的哈希码恰好匹配),但这种实现方式本身存在严重隐患。
解决方案
为所有自定义键类重写Equals和GetHashCode方法,确保基于字段值进行相等性判断和哈希码计算:
修改后的PersonKey3类
class PersonKey3 { [QuerySqlField] public int ID, CITY_ID; public override bool Equals(object obj) { if (obj is not PersonKey3 other) return false; return ID == other.ID && CITY_ID == other.CITY_ID; } public override int GetHashCode() { return HashCode.Combine(ID, CITY_ID); } }
修改后的PersonKey4类
class PersonKey4 { [QuerySqlField] public int CITY_ID, CITY_ID_2; public override bool Equals(object obj) { if (obj is not PersonKey4 other) return false; return CITY_ID == other.CITY_ID && CITY_ID_2 == other.CITY_ID_2; } public override int GetHashCode() { return HashCode.Combine(CITY_ID, CITY_ID_2); } }
额外注意事项
- 确保键类的字段顺序与DDL中主键的字段顺序完全一致,Ignite的二进制序列化对字段顺序敏感,顺序不一致会导致哈希码不匹配。
- 避免在一行声明多个字段(如
public int ID, CITY_ID;),显式分行声明字段可以明确序列化顺序,减少潜在问题。
内容的提问来源于stack exchange,提问作者GH Lee
相关产品推荐
相关产品推荐

