[英]Setting Hazelcast Cache for Multi-tenancy
我目前正在使用 JHipster 生成器生成真正的样板代码,其中涉及 HazelCast 作为二级缓存。 我能够使用基于 header 的租户上下文来获得多租户(每个租户的架构)。 我现在遇到的问题是 @Cacheable 注释都共享一个上下文。 如果缓存很热,我最终会得到跨模式数据。 例如,tenant1 从缓存的表中提取所有记录。 租户 2 从他们的表中提取相同的项目,读取缓存,并且它永远不会进入实际的租户数据库。 一个简单的解决方法是一起禁用缓存,但我不想这样做。 我一生都无法弄清楚如何让 hazelcast 了解租户上下文 - 缺少文档。 其他一些人已经使用自定义名称解析器解决了这个问题,但它似乎不像我希望的那样动态(即您必须提前了解所有租户)。 想法?
当前缓存配置:
@Configuration
@EnableCaching
public class CacheConfiguration implements DisposableBean {
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
private final Environment env;
private final ServerProperties serverProperties;
private final DiscoveryClient discoveryClient;
private Registration registration;
public CacheConfiguration(Environment env, ServerProperties serverProperties, DiscoveryClient discoveryClient) {
this.env = env;
this.serverProperties = serverProperties;
this.discoveryClient = discoveryClient;
}
@Autowired(required = false)
public void setRegistration(Registration registration) {
this.registration = registration;
}
@Override
public void destroy() throws Exception {
log.info("Closing Cache Manager");
Hazelcast.shutdownAll();
}
@Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
log.debug("Starting HazelcastCacheManager");
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance);
}
@Bean
public HazelcastInstance hazelcastInstance(JHipsterProperties jHipsterProperties) {
log.debug("Configuring Hazelcast");
HazelcastInstance hazelCastInstance = Hazelcast.getHazelcastInstanceByName("SampleApp");
if (hazelCastInstance != null) {
log.debug("Hazelcast already initialized");
return hazelCastInstance;
}
Config config = new Config();
config.setInstanceName("SampleApp");
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
if (this.registration == null) {
log.warn("No discovery service is set up, Hazelcast cannot create a cluster.");
} else {
// The serviceId is by default the application's name,
// see the "spring.application.name" standard Spring property
String serviceId = registration.getServiceId();
log.debug("Configuring Hazelcast clustering for instanceId: {}", serviceId);
// In development, everything goes through 127.0.0.1, with a different port
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
log.debug("Application is running with the \"dev\" profile, Hazelcast " +
"cluster will only work with localhost instances");
System.setProperty("hazelcast.local.localAddress", "127.0.0.1");
config.getNetworkConfig().setPort(serverProperties.getPort() + 5701);
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
String clusterMember = "127.0.0.1:" + (instance.getPort() + 5701);
log.debug("Adding Hazelcast (dev) cluster member {}", clusterMember);
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
}
} else { // Production configuration, one host per instance all using port 5701
config.getNetworkConfig().setPort(5701);
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
String clusterMember = instance.getHost() + ":5701";
log.debug("Adding Hazelcast (prod) cluster member {}", clusterMember);
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
}
}
}
config.getMapConfigs().put("default", initializeDefaultMapConfig(jHipsterProperties));
// Full reference is available at: http://docs.hazelcast.org/docs/management-center/3.9/manual/html/Deploying_and_Starting.html
config.setManagementCenterConfig(initializeDefaultManagementCenterConfig(jHipsterProperties));
config.getMapConfigs().put("com.test.sampleapp.domain.*", initializeDomainMapConfig(jHipsterProperties));
return Hazelcast.newHazelcastInstance(config);
}
private ManagementCenterConfig initializeDefaultManagementCenterConfig(JHipsterProperties jHipsterProperties) {
ManagementCenterConfig managementCenterConfig = new ManagementCenterConfig();
managementCenterConfig.setEnabled(jHipsterProperties.getCache().getHazelcast().getManagementCenter().isEnabled());
managementCenterConfig.setUrl(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUrl());
managementCenterConfig.setUpdateInterval(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUpdateInterval());
return managementCenterConfig;
}
private MapConfig initializeDefaultMapConfig(JHipsterProperties jHipsterProperties) {
MapConfig mapConfig = new MapConfig();
/*
Number of backups. If 1 is set as the backup-count for example,
then all entries of the map will be copied to another JVM for
fail-safety. Valid numbers are 0 (no backup), 1, 2, 3.
*/
mapConfig.setBackupCount(jHipsterProperties.getCache().getHazelcast().getBackupCount());
/*
Valid values are:
NONE (no eviction),
LRU (Least Recently Used),
LFU (Least Frequently Used).
NONE is the default.
*/
mapConfig.setEvictionPolicy(EvictionPolicy.LRU);
/*
Maximum size of the map. When max size is reached,
map is evicted based on the policy defined.
Any integer between 0 and Integer.MAX_VALUE. 0 means
Integer.MAX_VALUE. Default is 0.
*/
mapConfig.setMaxSizeConfig(new MaxSizeConfig(0, MaxSizeConfig.MaxSizePolicy.USED_HEAP_SIZE));
return mapConfig;
}
private MapConfig initializeDomainMapConfig(JHipsterProperties jHipsterProperties) {
MapConfig mapConfig = new MapConfig();
mapConfig.setTimeToLiveSeconds(jHipsterProperties.getCache().getHazelcast().getTimeToLiveSeconds());
return mapConfig;
}
}
使用 cacheNames 的示例存储库...
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
String USERS_BY_LOGIN_CACHE = "usersByLogin";
String USERS_BY_EMAIL_CACHE = "usersByEmail";
String USERS_BY_ID_CACHE = "usersById";
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmailIgnoreCase(String email);
Optional<User> findOneByLogin(String login);
@EntityGraph(attributePaths = "roles")
@Cacheable(cacheNames = USERS_BY_ID_CACHE)
Optional<User> findOneWithRolesById(Long id);
@EntityGraph(attributePaths = "roles")
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithRolesByLogin(String login);
@EntityGraph(attributePaths = { "roles", "roles.permissions" })
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithRolesAndPermissionsByLogin(String login);
@EntityGraph(attributePaths = "roles")
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithRolesByEmail(String email);
Page<User> findAllByLoginNot(Pageable pageable, String login);
}
我正在使用每个数据库的租户(MySQL),但是只要您在上面设置线程上下文就是我正在做的事情 - 我正在使用 Spring 引导。 我创建了一个自定义缓存密钥生成器,它结合了租户名称 + class + 和方法。 你真的可以选择任何组合。 每当我将该租户传回时,它都会提取正确的条目。 在我的 AppointmentType map 类型的 Hazelcast 命令中心中,我看到每个租户增加的条目数。
其他一些可能有用的参考资料:
在您要缓存的 class 中(我的是服务类):
@Service
public class AppointmentTypeService {
private static final Logger LOGGER = LoggerFactory.getLogger(AppointmentTypeService.class);
private final AppointmentTypeRepository appointmentTypeRepository;
@Autowired
AppointmentTypeService(AppointmentTypeRepository appointmentTypeRepository) {
this.appointmentTypeRepository = appointmentTypeRepository;
}
//ADD keyGenerator value. Name is the name of the bean of the class
@Cacheable(value="appointmentType", keyGenerator = "multiTenantCacheKeyGenerator")
public List<AppointmentType> list() {
return this.appointmentTypeRepository.findAll();
}
@CacheEvict(value="appointmentType", allEntries=true)
public Long create(AppointmentType request) {
this.appointmentTypeRepository.saveAndFlush(request);
return request.getAppointmentTypeId();
}
@CacheEvict(value="appointmentType", allEntries=true)
public void delete(Long id) {
this.appointmentTypeRepository.deleteById(id);
}
public Optional<AppointmentType> findById(Long id) {
return this.appointmentTypeRepository.findById(id);
}
}
创建密钥生成器 class
//setting the bean name here
@Component("multiTenantCacheKeyGenerator")
public class MultiTenantCacheKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object o, Method method, Object... os) {
StringBuilder sb = new StringBuilder();
sb.append(TenantContext.getCurrentTenantInstanceName()) //my tenant context class which is using local thread. I set the value in the Spring filter.
.append("_")
.append(o.getClass().getSimpleName())
.append("-")
.append(method.getName());
}
return sb.toString();
}
}
为租户定义不同缓存键的一种方法是覆盖org.springframework.cache.CacheManager
中的getCache
方法,如下所示: Extended spring cache...
从 Jhipster 7.0.1
开始,Hazelcast 的CacheManager
在 class CacheConfiguration
中定义,如下所述:
@Configuration
@EnableCaching
public class CacheConfiguration {
//...
@Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance);
}
//...
}
要让缓存键以租户 ID 为前缀,可以使用以下代码作为起点:
@Configuration
@EnableCaching
public class CacheConfiguration {
@Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance){
@Override
public Cache getCache(String name) {
String tenantId = TenantStorage.getTenantId();
if (StringUtils.isNotBlank(tenantId)){
return super.getCache(String.format("%s:%s", tenantId, name));
}
return super.getCache(name);
}
};
}
}
注意:在上面的代码中, TenantStorage.getTenantId()
是一个 static function 应该实现并返回当前租户 ID。
考虑 OP 发布的 class :
@Cacheable(cacheNames = "usersByLogin")
Optional<User> findOneWithRolesByLogin(String login);
HazelCast 将使用以下缓存值:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.