Cassandra:每个键空间的客户数据

wal*_*alv 3 cassandra spring-data-cassandra

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

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

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

潜在的解决方案#2为新客户使用单独的环境,但管理层拒绝这样做。

还有其他方法可以实现这个目标吗?

Ore*_*esz 5

更新 3

下面的示例和解释与 GitHub 中的相同

更新 2

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

更新

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

我更新了 GitHub 存储库并更改了解决方案的描述。

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

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

解决方案说明

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

我将简要描述如何准备和设置 Spring Data Cassandra 以在每个请求上配置目标键空间。

第 1 步:准备服务

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

Tenant-Id: ACME
Run Code Online (Sandbox Code Playgroud)

同样,在每个远程协议上,您可以在每条消息上转发租户 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;
    }
}
Run Code Online (Sandbox Code Playgroud)
@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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
}
Run Code Online (Sandbox Code Playgroud)
@SpringBootApplication
@EnableCassandraRepositories(repositoryBaseClass = KeyspaceAwareCassandraRepository.class)
public class DemoApplication {
...
}
Run Code Online (Sandbox Code Playgroud)

如果上面的代码有任何问题,请告诉我。

GitHub 中的示例代码

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

参考