使用独立键的番石榴缓存
Guava caching using independent keys
当处理来自数据库的用户对象时,通常有一个 ID 和一个用户名,通常按 ID 或用户名搜索用户。
如果我现在想要查找用户并且喜欢使用 Guava 缓存,我必须创建两个缓存。一种是按id缓存,一种是按用户名缓存。
但是都指向同一个对象。
是否可以只用一个LoadingCache?
我考虑过使用 User 对象本身作为键 LoadingCache<User, User>
并在 User 对象中实现 equals 和 hashcode。
在 equals 方法中,如果 id 或 用户名相等,则很容易说两个 User 对象相等。
但是如何生成适用于这种情况的良好 hashCode 方法?
有什么想法吗?
When working with user objects coming from a database one has usually an id and a username and it's common to search a user by id or by username.
备注:"search"对我来说意义不同,然后访问。也许 id 和 username 有不同的使用模式?也许用户名只在登录时需要?
避免在您的应用程序中使用两个不同的概念来引用/访问用户。决定一贯使用它。用户名是否唯一?能改吗?
两个缓存:您可以使用两个缓存并使用 name2user.put(user.getName(), user)
或 id2user.put(user.getId(), user)
从加载程序填充 "sister cache"。这样,相同的用户对象在两个缓存中。尽管如此,我还是不喜欢它,因为清洁度和一致性问题。
第三个问题是数据重复,如果你决定换一个解决方案。缓存可以不通过引用存储值,而是将其复制到紧凑的字节数组中并将其存储在堆外(EHCache3、Hazelcast 等)。 (干净的)代码不应该依赖于这样一个事实,即缓存通过引用在堆中存储其数据,如果没有真正需要的话。
正如上面假设的那样,这两个访问路径在使用上不会相等。我的推荐:
- 一个用于缓存用户数据的缓存:
id -> User
- 仅解析id的二级缓存:
name -> id
不介意 name
情况下的额外缓存访问。当然,第二个缓存的加载程序我已经为此目的请求了一个用户,因此您可能希望用它预填充第一个缓存。
非常感谢您的回答,尤其是来自 Guava 开发人员的回答。建议的解决方案对我来说很管用,我很懒 ;)。
所以如果我永远不会少缓存,我决定这样解决。
final LoadingCache<Serializable, Optional<ITemplate>> templatesById = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
.build(new CacheLoader<Serializable, Optional<ITemplate>>() {
@Override
public Optional<ITemplate> load(final Serializable id) {
final ITemplate template = readInternal(id);
final Optional<ITemplate> optional = Optional.ofNullable(template);
if (template != null) {
templatesByKey.put(template.getKey(), optional);
}
return optional;
}
});
final LoadingCache<String, Optional<ITemplate>> templatesByKey = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
.build(new CacheLoader<String, Optional<ITemplate>>() {
@Override
public Optional<ITemplate> load(final String key) {
final ITemplate template = byKeyInternal(key);
final Optional<ITemplate> optional = Optional.ofNullable(template);
if (template != null) {
templatesById.put(template.getId(), optional);
}
return optional;
}
});
这意味着,我不会因为在两个缓存中有两个模板实例而浪费内存。所以我只是向两个缓存添加一个模板,如果它是从数据库接收到的。
它真的很好用而且速度很快。
唯一的问题是,何时告诉缓存刷新。
在我的场景中,仅在删除或更新时才需要它。
@Override
@Transactional
public void update(final ITemplate template) {
super.update(new DBTemplate(template));
templatesById.invalidate(template.getId());
templatesByKey.invalidate(template.getKey());
}
就是这样。
对此有何评论?
当处理来自数据库的用户对象时,通常有一个 ID 和一个用户名,通常按 ID 或用户名搜索用户。
如果我现在想要查找用户并且喜欢使用 Guava 缓存,我必须创建两个缓存。一种是按id缓存,一种是按用户名缓存。
但是都指向同一个对象。
是否可以只用一个LoadingCache?
我考虑过使用 User 对象本身作为键 LoadingCache<User, User>
并在 User 对象中实现 equals 和 hashcode。
在 equals 方法中,如果 id 或 用户名相等,则很容易说两个 User 对象相等。
但是如何生成适用于这种情况的良好 hashCode 方法?
有什么想法吗?
When working with user objects coming from a database one has usually an id and a username and it's common to search a user by id or by username.
备注:"search"对我来说意义不同,然后访问。也许 id 和 username 有不同的使用模式?也许用户名只在登录时需要?
避免在您的应用程序中使用两个不同的概念来引用/访问用户。决定一贯使用它。用户名是否唯一?能改吗?
两个缓存:您可以使用两个缓存并使用 name2user.put(user.getName(), user)
或 id2user.put(user.getId(), user)
从加载程序填充 "sister cache"。这样,相同的用户对象在两个缓存中。尽管如此,我还是不喜欢它,因为清洁度和一致性问题。
第三个问题是数据重复,如果你决定换一个解决方案。缓存可以不通过引用存储值,而是将其复制到紧凑的字节数组中并将其存储在堆外(EHCache3、Hazelcast 等)。 (干净的)代码不应该依赖于这样一个事实,即缓存通过引用在堆中存储其数据,如果没有真正需要的话。
正如上面假设的那样,这两个访问路径在使用上不会相等。我的推荐:
- 一个用于缓存用户数据的缓存:
id -> User
- 仅解析id的二级缓存:
name -> id
不介意 name
情况下的额外缓存访问。当然,第二个缓存的加载程序我已经为此目的请求了一个用户,因此您可能希望用它预填充第一个缓存。
非常感谢您的回答,尤其是来自 Guava 开发人员的回答。建议的解决方案对我来说很管用,我很懒 ;)。
所以如果我永远不会少缓存,我决定这样解决。
final LoadingCache<Serializable, Optional<ITemplate>> templatesById = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
.build(new CacheLoader<Serializable, Optional<ITemplate>>() {
@Override
public Optional<ITemplate> load(final Serializable id) {
final ITemplate template = readInternal(id);
final Optional<ITemplate> optional = Optional.ofNullable(template);
if (template != null) {
templatesByKey.put(template.getKey(), optional);
}
return optional;
}
});
final LoadingCache<String, Optional<ITemplate>> templatesByKey = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
.build(new CacheLoader<String, Optional<ITemplate>>() {
@Override
public Optional<ITemplate> load(final String key) {
final ITemplate template = byKeyInternal(key);
final Optional<ITemplate> optional = Optional.ofNullable(template);
if (template != null) {
templatesById.put(template.getId(), optional);
}
return optional;
}
});
这意味着,我不会因为在两个缓存中有两个模板实例而浪费内存。所以我只是向两个缓存添加一个模板,如果它是从数据库接收到的。
它真的很好用而且速度很快。
唯一的问题是,何时告诉缓存刷新。
在我的场景中,仅在删除或更新时才需要它。
@Override
@Transactional
public void update(final ITemplate template) {
super.update(new DBTemplate(template));
templatesById.invalidate(template.getId());
templatesByKey.invalidate(template.getKey());
}
就是这样。
对此有何评论?