Cassandra:每个密钥空间的客户数据

时间:2017-10-26 22:14:27

标签: cassandra spring-data-cassandra

问题:我们的一位新客户希望将数据存储在他自己的国家/地区(法律法规)。但是,我们使用现有客户的数据分布在不同国家/地区的少数数据中心。

问题:如何在不改变现有Cassandra架构的情况下将新客户的数据分离到自己的国家?

潜在解决方案#1 :为此客户使用单独的密钥空间。关键空间之间的模式将是相同的,这增加了数据迁移的复杂性等等。 DataStax支持确认可以为每个区域配置密钥空间。 但是我们使用的Spring Data Cassandra不允许动态选择键空间。 唯一的方法是使用CqlTemplate并在调用之前每次运行use keyspace blabla或在表select * from blabla.mytable之前添加键空间,但这对我来说听起来像是黑客。

潜在解决方案#2 为新客户使用单独的环境,但管理层拒绝执行此操作。

实现这一目标的其他方法是什么?

3 个答案:

答案 0 :(得分:3)

更新3

下面的示例和说明与GitHub相同

更新2

GitHub中的示例现在正在运行。最具前瞻性的解决方案似乎是使用存储库扩展。将很快更新下面的例子。

更新

请注意,我最初发布的解决方案存在一些我在JMeter测试期间发现的缺陷。 Datastax Java驱动程序参考建议避免通过Session对象设置键空间。您必须在每个查询中明确设置键空间。

我已更新GitHub存储库并更改了解决方案的说明。

  

但要非常小心:如果会话由多个线程共享,   在运行时切换键空间很容易导致意外的查询失败。

     

通常,推荐的方法是使用没有的单个会话   密钥空间,并为所有查询添加前缀。

解决方案描述

我会为此特定客户设置一个单独的键空间,并为更改应用程序中的键空间提供支持。我们以前使用这种方法在生产中使用RDBMS和JPA。所以,我想说它也适用于Cassandra。解决方案如下所示。

我将简要介绍如何准备和设置Spring Data Cassandra以在每个请求上配置目标密钥空间。

第1步:准备服务

我首先要定义如何在每个请求上设置租户ID。 REST API的一个很好的例子就是使用定义它的特定HTTP头:

Tenant-Id: ACME

同样,在每个远程协议上,您都可以在每条消息上转发租户ID。让我们说如果你正在使用AMQP或JMS,你可以转发这个内部邮件标题或属性。

第2步:在应用程序中获取租户ID

接下来,您应该在控制器内的每个请求上存储传入的标头。您可以使用ThreadLocal,也可以尝试使用请求范围的bean。

@Component
@Scope(scopeName = "request", proxyMode= ScopedProxyMode.TARGET_CLASS)
public class TenantId {

    private String tenantId;

    public void set(String id) {
        this.tenantId = id;
    }

    public String get() {
        return tenantId;
    }
}
@RestController
public class UserController {

    @Autowired
    private UserRepository userRepo;
    @Autowired
    private TenantId tenantId;

    @RequestMapping(value = "/userByName")
    public ResponseEntity<String> getUserByUsername(
            @RequestHeader("Tenant-ID") String tenantId,
            @RequestParam String username) {
        // Setting the tenant ID
        this.tenantId.set(tenantId);
        // Finding user
        User user = userRepo.findOne(username);
        return new ResponseEntity<>(user.getUsername(), HttpStatus.OK);
    }
}

步骤3:在数据访问层中设置租户ID

最后,您应该根据租户ID扩展Repository实施和设置密钥空间

public class KeyspaceAwareCassandraRepository<T, ID extends Serializable>
        extends SimpleCassandraRepository<T, ID>  {

    private final CassandraEntityInformation<T, ID> metadata;
    private final CassandraOperations operations;

    @Autowired
    private TenantId tenantId;

    public KeyspaceAwareCassandraRepository(
            CassandraEntityInformation<T, ID> metadata,
            CassandraOperations operations) {
        super(metadata, operations);
        this.metadata = metadata;
        this.operations = operations;
    }

    private void injectDependencies() {
        SpringBeanAutowiringSupport
                .processInjectionBasedOnServletContext(this,
                getServletContext());
    }

    private ServletContext getServletContext() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest().getServletContext();
    }

    @Override
    public T findOne(ID id) {
        injectDependencies();
        CqlIdentifier primaryKey = operations.getConverter()
                .getMappingContext()
                .getPersistentEntity(metadata.getJavaType())
                .getIdProperty().getColumnName();

        Select select = QueryBuilder.select().all()
                .from(tenantId.get(),
                        metadata.getTableName().toCql())
                .where(QueryBuilder.eq(primaryKey.toString(), id))
                .limit(1);

        return operations.selectOne(select, metadata.getJavaType());
    }

    // All other overrides should be similar
}
@SpringBootApplication
@EnableCassandraRepositories(repositoryBaseClass = KeyspaceAwareCassandraRepository.class)
public class DemoApplication {
...
}

如果上述代码存在任何问题,请与我们联系。

GitHub中的示例代码

https://github.com/gitaroktato/spring-boot-cassandra-multitenant-example

参考

答案 1 :(得分:0)

有2个键空间的建议是正确的。 如果问题是只有2个键空间,为什么不配置2个键空间。 对于Region Dependent客户端 - 写入两者 对于其他人 - 仅写入一个(主)键空间。 不需要数据迁移。 下面是如何配置Spring存储库以命中不同的键空间的示例: http://valchkou.com/spring-boot-cassandra.html#multikeyspace

如果没有

,选择存储库可以很简单
if (org in (1,2,3)) { 
   repoA.save(entity)
   repoB.save(entity)
} else {
   repoA.save(entity)
}

答案 2 :(得分:0)

经过多次来回,我们决定不在同一个JVM中进行动态密钥空间解析。

决定在每个密钥空间和nginx路由器级别上设置专用的Jetty / Tomcat,以定义应该将请求重定向到哪个服务器(基于来自请求URL的companyId)。

例如,我们所有的端点都有/companyId/<value>所以根据该值,我们可以将请求重定向到使用正确密钥空间的正确服务器。