Serverseitige Transaktionen
Benutzerdefinierte Transaktionen ermöglichen das Hinzufügen einer neuen Transaktion und deren Registrierung bei Garnet. Diese registrierte Transaktion kann dann von jedem Garnet-Client aufgerufen werden, um eine Transaktion auf dem Garnet-Server durchzuführen.
Entwicklung benutzerdefinierter serverseitiger Transaktionen
CustomTransactionProcedure ist die Basisklasse für alle benutzerdefinierten Transaktionen. Um eine neue zu entwickeln, muss diese Klasse erweitert und dann die benutzerdefinierte Logik integriert werden. Es gibt drei Methoden, die in einer neuen benutzerdefinierten Transaktion implementiert werden müssen:
Prepare<TGarnetReadApi>(TGarnetReadApi api, ref CustomProcedureInput procInput)Main<TGarnetApi>(TGarnetApi api, ref CustomProcedureInput procInput, ref MemoryResult<byte> output)Finalize<TGarnetApi>(TGarnetApi api, ref CustomProcedureInput procInput, ref MemoryResult<byte> output)
Die Implementierung der Prepare-Methode muss die Schlüssel einrichten, die an der Transaktion beteiligt sind, und dabei die unten beschriebenen Hilfsmethoden verwenden. Die Main-Methode ist der Ort, an dem die eigentliche Operation durchgeführt wird, da die in der Prepare-Methode eingerichteten Sperren für die Schlüssel bereits erworben wurden. Die Main-Methode generiert dann auch die Ausgabe der Transaktion. Nach der Entsperrung der Schlüssel folgt die Finalize-Phase, die beliebige nicht-transaktionale Lese- und Schreibvorgänge im Speicher enthalten kann und auch Ausgaben schreiben kann. Finalize ermöglicht es Benutzern auch, komplexe nicht-transaktionale Skripte zu erstellen: Prepare sollte einfach false zurückgeben, während Main nicht implementiert bleibt.
Dies sind die Hilfsmethoden für die Entwicklung benutzerdefinierter Transaktionen.
AddKey(ArgSlice key, LockType type, bool isObject)Diese Methode wird verwendet, um einen angegebenen Schlüssel zum Sperrset hinzuzufügen. Sie nimmt drei Parameter entgegen: key (der hinzuzufügende Schlüssel), type (die Art der anzuwendenden Sperre) und isObject (ein boolescher Wert, der angibt, ob der Schlüssel ein Objekt darstellt).RewindScratchBuffer(ref ArgSlice slice)Diese Methode ist dafür verantwortlich, den letzten Eintrag des Scratch-Buffers zurückzuspulen (poppen), wenn er die angegebene ArgSlice enthält. Sie nimmt einen Verweis auf einen ArgSlice-Parameter entgegen und gibt einen booleschen Wert zurück, der angibt, ob die Rückspuloperation erfolgreich war.CreateArgSlice(ReadOnlySpan<byte> bytes)Diese Methode wird verwendet, um eine ArgSlice im Scratch-Buffer aus einem gegebenen ReadOnlySpan<byte> zu erstellen. Sie nimmt einen ReadOnlySpan<byte>-Parameter entgegen, der das Argument darstellt, und gibt ein ArgSlice-Objekt zurück.CreateArgSlice(string str)Diese Methode ist ähnlich wie die vorherige, erstellt jedoch eine ArgSlice im UTF8-Format aus einer gegebenen Zeichenkette. Sie nimmt einen String-Parameter entgegen und gibt ein ArgSlice-Objekt zurück.GetNextArg(ref CustomProcedureInput procInput, ref int offset)Diese Methode wird verwendet, um das nächste Argument aus der Eingabe am angegebenen Offset abzurufen. Sie nimmt einen ArgSlice-Parameter entgegen, der die Eingabe darstellt, und einen Verweis auf einen int offset. Sie gibt ein ArgSlice-Objekt zurück, das das Argument als Span darstellt. Die Methode liest intern einen Zeiger mit einem Längen-Header, um das Argument zu extrahieren. Diese Memberfunktionen bieten Hilfs- und Komfortfunktionen für die Manipulation und Arbeit mit den Transaktionsdaten, dem Scratch-Buffer und den Eingabeargumenten innerhalb der CustomTransactionProcedure-Klasse.
HINWEIS Beim mehrfachen Aufrufen von APIs auf IGarnetApi mit großen Ausgaben kann die Kapazität des internen Puffers erschöpft werden. Wenn solche Nutzungsszenarien erwartet werden, kann der Puffer wie unten beschrieben zurückgesetzt werden.
- Rufen Sie den anfänglichen Puffer-Offset mit
IGarnetApi.GetScratchBufferOffsetab - Rufen Sie die notwendigen APIs auf
IGarnetApiauf - Setzen Sie den Puffer mit
IGarnetApi.ResetScratchBuffer(offset)auf den ursprünglichen Stand zurück
Die Registrierung der benutzerdefinierten Transaktion erfolgt serverseitig durch Aufruf der Methode NewTransactionProc(string name, int numParams, Func<CustomTransactionProcedure> proc) im RegisterAPI-Objekt des Garnet-Serverobjekts mit seinem Namen, der Anzahl der Parameter und einer Methode, die eine Instanz der benutzerdefinierten Transaktionsklasse zurückgibt.
Es ist auch möglich, die benutzerdefinierte Transaktion von der Clientseite aus zu registrieren (als Admin-Befehl, vorausgesetzt, der Code befindet sich bereits auf dem Server) mit dem Befehl REGISTERCS (siehe Benutzerdefinierte Befehle).
Ausführung
Benutzerdefinierte Transaktionen werden von der Methode RunTransactionProc in der Klasse TransactionManager ausgeführt. Diese Methode kann entweder durch Aufrufen des Befehls RUNTXP mit den Details der benutzerdefinierten Transaktion oder durch Verwendung des benutzerdefinierten Transaktionsnamens, der bei der Registrierung verwendet wurde, aufgerufen werden.
Die anfängliche Phase wird durch den Aufruf der Methode Prepare der benutzerdefinierten Transaktion durchgeführt, die die zu sperrenden Schlüssel mit der Methode AddKey hinzufügt. Wenn die Prepare-Methode fehlschlägt und false zurückgibt, wird der Reset(false) des Transaktionsmanagers aufgerufen, um ihn zurückzusetzen. Andernfalls wird mit dem nächsten Schritt fortgefahren, indem die Methode Run der Klasse TransactionManager aufgerufen wird. Siehe die obige Beschreibung für diese Methode. Wenn die Run-Methode fehlschlägt, wird der Transaktionsmanager ebenfalls zurückgesetzt.
Als nächstes wird die Main-Methode der benutzerdefinierten Transaktion aufgerufen, die die Kernlogik der Transaktion durchführt. Bei erfolgreichem Abschluss werden die Transaktionsinformationen protokolliert. Wenn die Main-Methode fehlschlägt und eine Ausnahme auslöst, wird Reset(true) aufgerufen, um alle gesperrten Schlüssel zu entsperren und sich zurückzusetzen.
Die Finalize-Phase wird am Ende aufgerufen, unabhängig vom Erfolg oder Misserfolg der eigentlichen Transaktion. Wie bereits erwähnt, kann sie nicht-transaktionale Logik über den Speicher enthalten und Ausgaben generieren.