2017-10-26 1 views
2

Problem: Einer unserer neuen Kunden möchte, dass die Daten in seinem eigenen Land gespeichert werden (gesetzliche Bestimmungen). Wir verwenden jedoch Daten von bestehenden Kunden, die auf wenige Rechenzentren in verschiedenen Ländern verteilt sind.Cassandra: Kundendaten pro Schlüsselbereich

Frage: Wie können wir neue Kundendaten trennen, um in ihrem eigenen Land zu wohnen, ohne die bestehende Cassandra-Architektur zu verändern?

Mögliche Lösung # 1: separaten Schlüsselbereich für diesen Kunden verwenden. Schemata sind zwischen den Schlüsselbereichen identisch, was die Komplexität der Datenmigration erhöht und so weiter. Die DataStax-Unterstützung hat bestätigt, dass es möglich ist, den Schlüsselbereich pro Region zu konfigurieren. Spring Data Cassandra, die wir verwenden, erlaubt es jedoch nicht, den Schlüsselraum dynamisch zu wählen. Die einzige Möglichkeit ist, CqlTemplate zu verwenden und use keyspace blabla jedes Mal vor dem Anruf oder um Schlüsselbereich vor der Tabelle select * from blabla.mytable hinzufügen, aber es klingt wie ein Hack für mich.

Mögliche Lösung # 2 separate Umgebung für neue Client verwenden, aber das Management lehnt es ab.

Andere Möglichkeiten, dieses Ziel zu erreichen?

+2

Ich sehe nicht, wie Sie es erreichen können, ohne einen neuen Schlüsselraum zu erstellen, wenn alle Kunden denselben Schlüsselraum teilen und seine Daten in Rechenzentren in mehreren Ländern verteilt sind. Im Schlüsselbereich geben Sie an, dass die Daten nur in bestimmten Datencentern (im Kundenland) abgelegt werden sollen. – Edu

+0

@Edu, ja, wir denken auf die gleiche Weise (potentielle Lösung # 1), aber mit Spring Data Cassandra ist es nicht möglich, dynamisches Umschalten von Schlüsselraum zu verwenden (zumindest meine paar Stunden Forschung hat nicht geholfen). – walv

+0

@walv: warum sagst du das Hinzufügen von "keyspace vor der Tabelle select * von blabla.mytable" klingt wie ein hack? Es ist eine normale Art, eine Tabelle zu referenzieren und wird ziemlich verwendet. Es ist wie ein vollständig qualifizierter Name der Tabelle. – Horia

Antwort

3

Update 3

Beispiel und unter Erklärung ist die gleiche wie in GitHub

Update 2

Das Beispiel in GitHub arbeitet nun. Die zukunftssicherste Lösung schien die Verwendung von Repository-Erweiterungen zu sein. Wird das folgende Beispiel bald aktualisieren.

aktualisieren

Beachten Sie, dass die Lösung, die ich ursprünglich hatte einige Mängel geschrieben, die ich während JMeter Tests entdeckt. Die Datastax-Java-Treiberreferenz rät, das Festlegen des Schlüsselbereichs durch Session Objekt zu vermeiden. Sie müssen den Schlüsselbereich in jeder Abfrage explizit festlegen.

Ich habe das GitHub-Repository aktualisiert und auch die Beschreibung der Lösung geändert.

sehr vorsichtig sein, aber: wenn die Sitzung von mehreren Threads gemeinsam genutzt wird, den Schlüsselraum zur Laufzeit Schalt leicht unerwartete Abfrage Fehler verursachen könnten.

Im Allgemeinen wird empfohlen, eine einzelne Sitzung ohne Schlüsselbereich zu verwenden und alle Ihre Abfragen mit einem Präfix zu versehen.

Lösung Beschreibung

Ich würde einen separaten Schlüsselraum für diesen speziellen Kunden Set-up und Unterstützung Schlüsselraum bei der Anwendung zu ändern. Wir haben diesen Ansatz zuvor mit RDBMS und JPA in der Produktion verwendet. Also würde ich sagen, dass es auch mit Cassandra funktionieren kann. Lösung war ähnlich wie unten.

Ich werde kurz beschreiben, wie Sie Spring Data Cassandra vorbereiten und einrichten, um den Ziel-Keypace bei jeder Anfrage zu konfigurieren.

Schritt 1: Vorbereiten der Dienste

definiere ich würde zunächst, wie die Mieter ID auf jede Anfrage zu setzen. Ein gutes Beispiel wäre in-case-of seinen REST API ist einen bestimmten HTTP-Header zu verwenden, die es definiert:

Tenant-Id: ACME 

Ebenso auf jedem Remote-Protokoll Sie Mieter ID bei jeder Nachricht weiterleiten können. Angenommen, Sie verwenden AMQP oder JMS, können Sie diese interne Nachrichtenkopfzeile oder Eigenschaften weiterleiten.

Schritt 2: Erste Mieter ID in Anwendung

Als nächstes sollten Sie den eingehenden Header gespeichert auf jede Anforderung in Ihrem Controller. Sie können ThreadLocal verwenden oder Sie können versuchen, eine beanspruchte Bean zu verwenden.

@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); 
    } 
} 

Schritt 3: Einstellen Mieter ID in Datenzugriffsschicht

Schließlich sollten Sie Repository Implementierungen erweitern und Set-up Schlüsselraum entsprechend der Mieter 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 
} 

@SpringBootApplication 
@EnableCassandraRepositories(repositoryBaseClass = KeyspaceAwareCassandraRepository.class) 
public class DemoApplication { 
... 
} 

Lassen Sie mich wissen, wenn es Probleme mit dem obigen Code gibt.

Beispielcode in GitHub

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

Referenzen

+0

für Schritt 1 und 2, wir behandeln es auf Nginx-Ebene, weil jede API Firmen-ID enthält, z. api/v1/companies/123/... Schritt # 3 wird wahrscheinlich für JPA arbeiten, aber definitiv nicht für Spring Data Cassandra. Und das ist, was ich suche - wie man Schlüsselraum in Frühlings-Daten Cassandra in der Laufzeit setzt. – walv

+0

Für Schritt 1 und 2 können Sie den Kundenkontext von nginx an die Anwendungen weiterleiten, indem Sie die HTTP-Anforderungen neu schreiben. Die Anwendung muss wissen, welche Kundenanfrage auf Anfrage bearbeitet werden muss. Was ist das Problem mit Schritt # 3 genau? Spring-Daten JPA kann auch mit Cassandra zusammenarbeiten. Möchten Sie nur JPA in Ihrer Codebasis vermeiden und Spring-Data-Cassandra mit einer anderen Schnittstelle verwenden? – Oresztesz

+0

für die Schritte 1 und 2, Sie haben Recht, nach Firmen-ID von URL, definieren wir, welche Schlüsselraum verwendet werden soll. Bezüglich Schritt # 3 bin ich nicht sicher, ob JPA mit Cassandra arbeiten kann. Vielleicht meinen Sie, dass Spring Data mit Cassandra arbeiten kann, aber nicht mit JPA? Mit der Idee des Abfangens von Methoden klingt es gut. Das einzige, was ich bezweifle, ist, dass wenn ich EntityManager durch CassandraTemplate (Singleton) ersetze und ich "USE KEYSPACE" abfrage, dann unvorhersehbares Lesen/Schreiben in Multithread-Umgebung bekommen kann, weil Cassandra keine Transaktionen hat. Aber vielleicht verstehe ich etwas falsch? – walv

0

Hinweis mit 2 Schlüsselräumen ist korrekt. Wenn Frage nur zwei Schlüsselbereiche hat, warum nicht 2 Schlüsselräume konfigurieren. für Regionsabhängiger Client - Schreiben Sie in beide
für andere - Schreiben Sie nur in einen (Haupt-) Schlüsselbereich. Es ist keine Datenmigration erforderlich. Hier ist Probe, wie Spring Repositorys konfigurieren verschiedene keyspaces treffen: http://valchkou.com/spring-boot-cassandra.html#multikeyspace

die Wahl des Repository kann einfach sein, wenn sonst

if (org in (1,2,3)) { 
    repoA.save(entity) 
    repoB.save(entity) 
} else { 
    repoA.save(entity) 
} 
+0

Ich fürchte, dass es in unserem Fall schwer vorhersehbar ist, dass wir nur zwei Schlüsselräume brauchen und nicht mehr. Morgen kann der dritte und der übernächste Tag sein. Deshalb haben wir nach der dynamischen Lösung gesucht, um PITA in Zukunft zu vermeiden. – walv

+0

Wenn Sie mehrere Sitzungen für jeden Schlüsselbereich haben, muss die Anwendung einen separaten Verbindungspool für jeden Mandanten verwalten. Das kann unnötigen Overhead verursachen, wenn die Anzahl der Mieter steigt. – Oresztesz

0

Nach vielen hin und her, haben wir beschlossen, nicht die Dynamik zu tun Schlüsselraumauflösung innerhalb derselben JVM.

Es wurde die Entscheidung getroffen, dedizierten Jetty/Tomcat pro Schlüsselbereich und auf Nginx-Router-Ebene zu definieren, auf welchen Server die Anfrage umgeleitet werden sollte (basierend auf CompanyId von der Anfrage-URL).

Zum Beispiel haben alle unsere Endpunkte /companyId/<value>, also können wir basierend auf dem Wert die Anfrage an den richtigen Server umleiten, der den richtigen Schlüsselraum verwendet.

+1

Ich glaube wirklich, du kannst es in der gleichen Instanz machen, aber ohne die 'spring-data-cassandra'-Bibliothek zu erweitern, ist es nicht möglich. Schade, dass sie Multi-Tenancy nicht als Option einführen. Es gibt ein geschlossenes JIRA-Ticket, aber nichts mehr zu dieser Funktion: https://jira.spring.io/browse/DATACASS-330?jql=labels%20%3D%20cassandra%20AND%20labels%20%3D%20multi Tenant – Oresztesz

+1

Irgendwie werde ich versuchen, dies zu schieben und die Bibliothek zu erweitern, um Multi-Tenant-Option zu haben. Nur um ein Beispiel zu haben, wie es geht. – Oresztesz

+1

Ich aktualisierte meine Antwort mit einer alternativen Lösung. Sie können es sich ansehen – Oresztesz

Verwandte Themen