https://player.vimeo.com/video/201989439
La file d'attente Chronicle est un cadre de messagerie à faible latence persistant pour les applications haute performance.
Ce projet couvre la version Java de la file d'attente Chronicle. Une version C ++ de ce projet est également disponible et prend en charge l'interopérabilité Java / C ++ plus les liaisons linguistiques supplémentaires, par exemple Python. Si vous souhaitez évaluer la version C ++, veuillez contacter [email protected].
À première vue, la file d'attente Chronicle peut être considérée comme une autre implémentation de file d'attente . Cependant, il a des choix de conception majeurs qui devraient être soulignés. En utilisant le stockage hors trépail , la file d'attente Chronicle fournit un environnement lorsque les applications ne souffrent pas de la collecte des ordures (GC). Lors de la mise en œuvre d'applications haute performance et à forte intensité de mémoire (vous avez entendu le terme fantaisie "BigData"?) En Java, l'un des plus gros problèmes est la collection Garbage.
La file d'attente Chronicle permet d'ajouter des messages à la fin d'une file d'attente ("annexe"), à lire dans la file d'attente ("tailled") et prend également en charge la recherche d'accès aléatoire.
Vous pouvez considérer qu'une file d'attente Chronicle est similaire à un sujet durable / persistant sans courtier à faible latence qui peut contenir des messages de différents types et tailles. La file d'attente de Chronicle est une file d'attente persistante non liée à la répartition qui:
Prend en charge les interfaces Asynchrones RMI et Publish / Abonnez-vous avec les latences microsecondes.
transmet des messages entre JVM
transmet des messages entre JVM sur différentes machines via la réplication en moins de 10 microsecondes (fonctionnalité d'entreprise)
Fournit des latences en temps réel stables et douces dans les millions de messages par seconde pour un seul fil à une file d'attente; avec la commande totale de chaque événement.
Lors de la publication de messages de 40 octets, un pourcentage élevé du temps, nous atteignons les latences en moins d'une microseconde. La latence du 99e centile est la pire 1 sur 100, et le 99,9e centile est la pire latence 1 sur 1000.
Taille de lot | 10 millions d'événements par minute | 60 millions d'événements par minute | 100 millions d'événements par minute |
---|---|---|---|
99% ile | 0,78 µs | 0,78 µs | 1,2 µs |
99,9% ile | 1,2 µs | 1,3 µs | 1,5 µs |
Taille de lot | 10 millions d'événements par minute | 60 millions d'événements par minute | 100 millions d'événements par minute |
---|---|---|---|
99% ile | 20 µs | 28 µs | 176 µs |
99,9% ile | 901 µs | 705 µs | 5 370 µs |
Note | 100 millions d'événements par minute envoient un événement toutes les 660 nanosecondes; reproduit et persisté. |
Important | Cette performance n'est pas réalisée à l'aide d'un grand groupe de machines . Il s'agit d'un thread pour publier et d'un fil à consommer. |
La file d'attente Chronicle est conçue pour:
Soyez un "Record Everything Store" qui peut lire avec la latence en temps réel microseconde. Cela prend en charge même les systèmes de trading à haute fréquence les plus exigeants. Cependant, il peut être utilisé dans n'importe quelle application où l'enregistrement des informations est une préoccupation.
Prise en charge de la réplication fiable avec notification à l'appender (écrivain de message) ou à un tailleur (lecteur de message), lorsqu'un message a été reproduit avec succès.
La file d'attente Chronicle suppose que l'espace disque est bon marché par rapport à la mémoire. La file d'attente Chronicle utilise pleinement l'espace disque que vous avez, et vous n'êtes donc pas limité par la mémoire principale de votre machine. Si vous utilisez un disque dur de rotation, vous pouvez stocker plusieurs TBS d'espace disque pour peu de coût.
Le seul logiciel supplémentaire dont la file d'attente chronique doit fonctionner est le système d'exploitation. Il n'a pas de courtier; Au lieu de cela, il utilise votre système d'exploitation pour faire tout le travail. Si votre application décède, le système d'exploitation continue de fonctionner pendant des secondes plus longtemps, donc aucune donnée n'est perdue; même sans réplication.
Comme Chronicle Fidue stocke toutes les données enregistrées dans des fichiers mappés par la mémoire, cela a une surcharge triviale sur la tête, même si vous avez plus de 100 To de données.
Chronicle a mis des efforts importants à la réalisation d'une très faible latence. Dans d'autres produits qui se concentrent sur le support des applications Web, les latences de moins de 40 millisecondes sont bien car elles sont plus rapides que vous ne pouvez le voir; Par exemple, la fréquence d'images du cinéma est de 24 Hz, soit environ 40 ms.
La file d'attente Chronicle vise à atteindre des latences de moins de 40 microsecondes pendant 99% à 99,99% du temps. En utilisant la file d'attente Chronicle sans réplication, nous prenons en charge les applications avec des latences inférieures à 40 microsecondes de bout en bout sur plusieurs services. Souvent, la latence à 99% de la file d'attente Chronicle dépend entièrement du choix du système d'exploitation et du sous-système de disque dur.
La réplication de la file d'attente Chronicle prend en charge l'entreprise Chronicle Wire. Cela prend en charge une compression en temps réel qui calcule les deltas pour les objets individuels, tels qu'ils sont écrits. Cela peut réduire la taille des messages d'un facteur de 10, ou mieux, sans avoir besoin de lot; c'est-à-dire sans introduire une latence significative.
La file d'attente Chronicle prend également en charge la compression LZW, Snappy et GZIP. Ces formats ajoutent cependant une latence significative. Ceux-ci ne sont utiles que si vous avez des limitations strictes sur la bande passante du réseau.
La file d'attente Chronicle prend en charge un certain nombre de sémantiques:
Chaque message est rejoué sur le redémarrage.
Seuls de nouveaux messages sont joués sur le redémarrage.
Redémarrez à partir de tout point connu en utilisant l'index de l'entrée.
Rejouer uniquement les messages que vous avez manqués. Ceci est pris en charge directement à l'aide des constructeurs MethodReader / MethodWriter.
Sur la plupart des systèmes System.nanoTime()
est à peu près le nombre de nanosecondes depuis le dernier redémarrage du système (bien que différents JVM peuvent se comporter différemment). C'est la même chose à travers les JVM sur la même machine, mais extrêmement différente entre les machines. La différence absolue en ce qui concerne les machines est dénuée de sens. Cependant, les informations peuvent être utilisées pour détecter les valeurs aberrantes; Vous ne pouvez pas déterminer quelle est la meilleure latence, mais vous pouvez déterminer à quelle distance vous êtes les meilleures latences. Ceci est utile si vous vous concentrez sur les latences du 99e centile. Nous avons une classe appelée RunningMinimum
pour obtenir des horaires de différentes machines, tout en compensant une dérive dans la nanoTime
entre les machines. Plus vous prenez souvent des mesures, plus ce minimum est précis.
La file d'attente Chronicle gère le stockage par cycle. Vous pouvez ajouter un StoreFileListener
qui vous informera lorsqu'un fichier est ajouté et lorsqu'il n'est plus conservé. Vous pouvez déplacer, compresser ou supprimer tous les messages d'une journée, à la fois. Remarque: Malheureusement sur Windows, si une opération IO est interrompue, elle peut fermer le FileChannel sous-jacent.
Pour des raisons de performances, nous avons supprimé la vérification des interruptions du code de la file d'attente Chronicle. Pour cette raison, nous vous recommandons d'éviter d'utiliser la file d'attente Chronicle avec du code qui génère des interruptions. Si vous ne pouvez pas éviter de générer des interruptions, nous vous suggérons de créer une instance distincte de la file d'attente de chronique par fil.
La file d'attente Chronicle est le plus souvent utilisée pour les systèmes centrés sur les producteurs où vous devez conserver beaucoup de données pendant des jours ou des années. Pour les statistiques, voir l'utilisation de Chronicle-Quye
Important | La file d'attente Chronicle ne prend pas en charge le fonctionnement du système de fichiers réseau, que ce soit NFS, AFS, stockage basé sur SAN ou autre chose. La raison en est que ces systèmes de fichiers ne fournissent pas toutes les primitives requises pour les fichiers à mémoire de mémoire Les utilisations de la file d'attente. Si un réseautage est nécessaire (par exemple pour rendre les données accessibles à plusieurs hôtes), la seule manière prise en charge est la réplication de la file d'attente de Chronicle (fonctionnalité d'entreprise). |
La plupart des systèmes de messagerie sont centrés sur le consommateur. Le contrôle de flux est mis en œuvre pour éviter que le consommateur ne soit surchargé; même momentanément. Un exemple courant est un serveur prenant en charge plusieurs utilisateurs de GUI. Ces utilisateurs peuvent être sur différentes machines (système d'exploitation et matériel), différentes qualités de réseau (latence et bande passante), faisant une variété d'autres choses à différents moments. Pour cette raison, il est logique que le consommateur client dise au producteur quand reculer, en retardant toutes les données jusqu'à ce que le consommateur soit prêt à prendre plus de données.
La file d'attente Chronicle est une solution centrée sur le producteur et fait tout ce qui est possible pour ne jamais repousser le producteur, ou lui dire de ralentir. Cela en fait un outil puissant, offrant un grand tampon entre votre système et un producteur en amont sur lequel vous avez peu ou pas de contrôle.
Les éditeurs de données du marché ne vous donnent pas la possibilité de repousser le producteur pendant longtemps; le cas échéant. Quelques-uns de nos utilisateurs consomment des données de CME OPRA. Cela produit des pics de 10 millions d'événements par minute, envoyés sous forme de paquets UDP sans réessayer. Si vous manquez ou déposez un paquet, il est perdu. Vous devez consommer et enregistrer ces paquets aussi vite qu'ils viennent à vous, avec très peu de tampon dans l'adaptateur réseau. Pour les données du marché en particulier, le temps réel signifie en quelques microsecondes ; Cela ne signifie pas intra-jour (pendant la journée).
La file d'attente de Chronicle est rapide et efficace, et a été utilisée pour augmenter la vitesse que les données sont passées entre les threads. En outre, il conserve également un enregistrement de chaque message passé vous permettant de réduire considérablement la quantité de journalisation que vous devez faire.
Les systèmes de conformité sont requis par de plus en plus de systèmes de nos jours. Tout le monde doit les avoir, mais personne ne veut être ralenti par eux. En utilisant la file d'attente Chronicle pour tamponner les données entre les systèmes surveillés et le système de conformité, vous n'avez pas à vous soucier de l'impact de l'enregistrement de conformité pour vos systèmes surveillés. Encore une fois, la file d'attente Chronicle peut soutenir des millions d'événements par seconde, par serveur et des données d'accès qui sont conservées depuis des années.
La file d'attente Chronicle prend en charge la faible latence IPC (communication inter-processus) entre les JVM sur la même machine dans l'ordre de grandeur de 1 microseconde; ainsi qu'entre les machines avec une latence typique de 10 microsecondes pour des débits modestes de quelques centaines de milliers. La file d'attente Chronicle prend en charge les débits de millions d'événements par seconde, avec des latences en microseconde stables.
Voir des articles sur l'utilisation de la file d'attente Chronicle dans les microservices
Une file d'attente Chronicle peut être utilisée pour construire des machines d'État. Toutes les informations sur l'état de ces composants peuvent être reproduites à l'extérieur, sans accès direct aux composants ou à leur état. Cela réduit considérablement le besoin de journalisation supplémentaire. Cependant, toute journalisation dont vous avez besoin peut être enregistrée en détail. Cela rend la connexion DEBUG
à la production pratique. En effet, le coût de l'exploitation forestière est très faible; Moins de 10 microsecondes. Les journaux peuvent être répliqués au centre de la consolidation des log. La file d'attente Chronicle est utilisée pour stocker plus de 100 TB de données, qui peuvent être rejouées à partir de n'importe quel point dans le temps.
Les composants de streaming non par lots sont très performants, déterministes et reproductibles. Vous pouvez reproduire des bogues qui n'apparaissent qu'après un million d'événements joués dans un ordre particulier, avec des timings réalistes accélérés. Cela rend le traitement de flux attrayant pour les systèmes qui ont besoin d'un degré élevé de résultats de qualité.
Les versions sont disponibles sur Maven Central AS:
< dependency >
< groupId >net.openhft</ groupId >
< artifactId >chronicle-queue</ artifactId >
< version > <!-- replace with the latest version, see below --> </ version >
</ dependency >
Voir les notes de version de la file d'attente Chronicle et obtenez le dernier numéro de version. Des instantanés sont disponibles sur https://oss.sonatype.org
Note | Les classes qui résident dans l'un ou l'autre des packages «internes», «impl» et «main» (ces derniers contenant diverses méthodes principales coulissables) et tous les sous-packages ne font pas partie de l'API publique et peuvent devenir sujets à changer à tout temps pour quelque raison que ce soit . Voir les fichiers package-info.java respectifs pour plus de détails. |
Dans Chronicle Queue V5 Tailers, sont désormais en lecture seule, dans Chronicle Queue V4, nous avions le concept d'indexation paresseuse, où les annexes n'écriraient pas d'index, mais à la place, l'indexation pourrait être effectuée par le caraileur. Nous avons décidé de supprimer l'indexation paresseuse dans la V5; La création de Tailers en lecture seule simplifie non seulement la file d'attente Chronicle, mais nous permet également d'ajouter des optimisations ailleurs dans le code.
Le modèle de verrouillage de la file d'attente Chronicle a été modifié en V5, dans la file d'attente de Chronique V4 Le verrouillage d'écriture (pour éviter les écritures simultanées dans la file d'attente) existe dans le fichier .CQ4. En V5, il a été déplacé vers un seul fichier appelé un magasin de table (metadata.cq4t). Cela simplifie le code de verrouillage en interne car seul le fichier de magasin de table doit être inspecté.
Vous pouvez utiliser Chronicle Queue V5 pour lire les messages écrits avec Chronicle Queue V4, mais cela ne fonctionne pas pour toujours fonctionner - si, par exemple, vous avez créé votre file d'attente V4 avec wireType(WireType.FIELDLESS_BINARY)
puis Chronicle Queue V5 ne sera pas en mesure de Lisez l'en-tête de la file d'attente. Nous avons quelques tests pour les files d'attente V5 V5, mais celles-ci sont limitées et tous les scénarios peuvent ne pas être pris en charge.
Vous ne pouvez pas utiliser Chronicle Queue V5 pour écrire sur Chronicle Fitre v4 files d'attente.
Chronicle Queue V4 est une réécriture complète de la file d'attente Chronicle qui résout les problèmes suivants qui existaient dans la V3.
Sans messages auto-décrits, les utilisateurs ont dû créer leurs propres fonctionnalités pour le vidage des messages et le stockage à long terme des données. Avec V4, vous n'avez pas à le faire, mais vous le pouvez si vous le souhaitez.
La file d'attente de vanille chronique créerait un fichier par thread. C'est bien si le nombre de threads est contrôlé, cependant, de nombreuses applications ont peu ou pas de contrôle sur le nombre de threads utilisés et cela a causé des problèmes d'utilisation.
La configuration de l'indexé et de la chronique Vanilla était entièrement dans le code, le lecteur devait donc avoir la même configuration que les scénaristes et il n'était pas toujours clair ce que c'était.
Il n'y avait aucun moyen pour le producteur de savoir combien de données avaient été reproduites à la deuxième machine. La seule solution de contournement était de reproduire les données aux producteurs.
Vous deviez spécifier la taille des données à réserver avant de commencer à écrire votre message.
Vous deviez faire votre propre verrouillage pour l'appender lorsque vous utilisez une chronique indexée.
Dans la file d'attente Chronicle V3, tout était en termes d'octets, pas de fil. Il existe deux façons d'utiliser l'octet dans Chronicle Queue V4. Vous pouvez utiliser les méthodes writeBytes
et readBytes
, ou vous pouvez obtenir les bytes()
à partir du fil. Par exemple:
appender . writeBytes ( b -> b . writeInt ( 1234 ). writeDouble ( 1.111 ));
boolean present = tailer . readBytes ( b -> process ( b . readInt (), b . readDouble ()));
try ( DocumentContext dc = appender . writingDocument ()) {
Bytes <?> bytes = dc . wire (). bytes ();
// write to bytes
}
try ( DocumentContext dc = tailer . readingDocument ()) {
if ( dc . isPresent ()) {
Bytes <?> bytes = dc . wire (). bytes ();
// read from bytes
}
}
Chronicle Queue Enterprise Edition est une version commercialement prise en charge de notre file d'attente à succès Chronicle Open Source. La documentation open source est prolongée par les documents suivants pour décrire les fonctionnalités supplémentaires disponibles lorsque vous êtes sous licence pour Enterprise Edition. Ce sont:
Cryptage des files d'attente de messages et des messages. Pour plus d'informations, voir la documentation de chiffrement.
Réplication TCP / IP (et éventuellement UDP) entre les hôtes pour assurer une sauvegarde en temps réel de toutes vos données de file d'attente. Pour plus d'informations, consultez la documentation de réplication, le protocole de réplication de la file d'attente est couvert dans le protocole de réplication.
Prise en charge du fuseau horaire pour la planification quotidienne du roulement des files d'attente. Pour plus d'informations, consultez le support du fuseau horaire.
Prise en charge du mode asynchrone pour donner des performances améliorées à un débit élevé sur les systèmes de fichiers plus lents. Pour plus d'informations, voir le mode asynchrone et également les performances.
Pré-Touch pour les valeurs aberrantes améliorées, voir Pre-Toucher et sa configuration
De plus, vous serez pleinement soutenu par nos experts techniques.
Pour plus d'informations sur Chronicle Queue Enterprise Edition, veuillez contacter [email protected].
Une file d'attente Chronicle est définie par SingleChronicleQueue.class
conçue pour soutenir:
des fichiers roulant sur une base quotidienne, hebdomadaire ou horaire,
écrivains simultanés sur la même machine,
lecteurs simultanés sur la même machine ou sur plusieurs machines via la réplication TCP (avec Chronicle Fitre Enterprise),
lecteurs et écrivains simultanés entre Docker ou d'autres charges de travail conteneurisées
sérialisation et désérialisation zéro,
Des millions d'écrits / lectures par seconde sur le matériel des produits de base.
Environ 5 millions de messages / seconde pour les messages de 96 octets sur un processeur i7-4790. Une structure de répertoire de file d'attente est la suivante:
base-directory /
{cycle-name}.cq4 - The default format is yyyyMMdd for daily rolling.
Le format se compose d'octets préfixés en taille qui sont formatés à l'aide BinaryWire
ou TextWire
. La file d'attente Chronicle est conçue pour être chassée du code. Vous pouvez facilement ajouter une interface qui convient à vos besoins.
Note | En raison de l'exploitation assez à bas niveau, les opérations de lecture / écriture de la file d'attente de Chronicle peuvent lancer des exceptions incontrôlées. Afin d'empêcher la mort du thread, il pourrait être pratique de capter RuntimeExceptions et de les enregistrer / analyser le cas échéant. |
Note | Pour des démonstrations sur la façon dont la file d'attente chronique peut être utilisée, voir la démo de la file d'attente Chronicle et pour la documentation Java, voir Chronicle Fitre Javadocs |
Dans les sections suivantes, nous introduisons d'abord une terminologie et une référence rapide pour utiliser la file d'attente Chronicle. Ensuite, nous fournissons un guide plus détaillé.
Chronicle Queue est un journal persistant de messages qui prend en charge les écrivains et lecteurs simultanés, même dans plusieurs JVM sur la même machine. Chaque lecteur voit chaque message, et un lecteur peut se joindre à tout moment et voir chaque message.
Note | Nous évitons délibérément le terme consommateur et utilisons plutôt le lecteur car les messages ne sont pas consommés / détruits en lisant. |
La file d'attente Chronicle a les principaux concepts suivants:
Extrait
L'extrait est le principal conteneur de données dans une file d'attente Chronicle. En d'autres termes, chaque file d'attente de chronique est composée d'extraits. Écrire un message à une file d'attente Chronicle signifie démarrer un nouvel extrait, y écrire un message et terminer l'extrait à la fin.
Appender
Un appender est la source des messages; Quelque chose comme un itérateur dans Chronicle Environment. Vous ajoutez des données en ajoutant la file d'attente de chronique actuelle. Il peut effectuer des écritures séquentielles en ajoutant jusqu'à la fin de la file d'attente uniquement. Il n'y a aucun moyen d'insérer ou de supprimer des extraits.
Caraileur
Un tailleur est un lecteur extrait optimisé pour les lectures séquentielles. Il peut effectuer des lectures séquentielles et aléatoires, à la fois en avant et en arrière. Les Tailers lisent le prochain message disponible à chaque fois qu'ils sont appelés. Les suivants sont garantis dans la file d'attente de Chronicle:
Pour chaque appender , les messages sont écrits dans l'ordre que l'appenner les a écrits. Les messages de différents annexes sont entrelacés,
Pour chaque tailleur , il verra chaque message pour un sujet dans le même ordre que tous les autres tailleurs,
Lorsqu'il est reproduit, chaque réplique a une copie de chaque message.
La file d'attente Chronicle est sans courtier. Si vous avez besoin d'une architecture avec un courtier, veuillez contacter [email protected].
Fichiers de fichiers et de fichiers de file d'attente
La file d'attente Chronicle est conçue pour rouler ses fichiers en fonction du cycle de roulement choisi lorsque la file d'attente est créée (voir RollCycles). En d'autres termes, un fichier de file d'attente est créé pour chaque cycle de roulement qui a une extension cq4
. Lorsque le cycle de roulement atteint le point qu'il doit rouler, l'appender écrira atomiquement la marque EOF
à la fin du fichier actuel pour indiquer qu'aucun autre appender ne devrait écrire dans ce fichier et aucun tailleur ne doit lire plus loin, et à la place, tout le monde devrait utiliser un nouveau fichier.
Si le processus a été arrêté et redémarré plus tard lorsque le cycle de roulement devrait utiliser un nouveau fichier, un appender essaiera de localiser les anciens fichiers et de rédiger une marque EOF
pour aider les tailleurs à les lire.
Sujets
Chaque sujet est un répertoire de fichiers de file d'attente. Si vous avez un sujet appelé mytopic
, la mise en page pourrait ressembler à ceci:
mytopic/
20160710.cq4
20160711.cq4
20160712.cq4
20160713.cq4
Pour copier toutes les données pour une seule journée (ou cycle), vous pouvez copier le fichier pour cette journée sur votre machine de développement pour les tests de relecture.
Restrictions sur les sujets et les messages
Les sujets se limitent à être des chaînes qui peuvent être utilisées comme noms de répertoires. Dans un sujet, vous pouvez avoir des sous-thèmes qui peuvent être n'importe quel type de données pouvant être sérialisé. Les messages peuvent être des données sérialisables.
Supports de file d'attente de chronique:
Objets Serializable
, bien que cela soit à éviter car il n'est pas efficace
Les objets Externalizable
sont préférés si vous souhaitez utiliser des API Java standard.
byte[]
et String
Marshallable
; Un message décrivant auto-décrivant qui peut être écrit comme Yaml, Binary Yaml ou JSON.
BytesMarshallable
qui est binaire de bas niveau ou codage de texte.
Cette section fournit une référence rapide pour utiliser la file d'attente Chronicle pour montrer brièvement comment créer, écrire / lire dans / depuis une file d'attente.
Construction de la file d'attente Chronicle
La création d'une instance de la file d'attente Chronicle est différente de l'appel d'un constructeur. Pour créer une instance, vous devez utiliser le ChronicleQueueBuilder
.
String basePath = OS . getTarget () + "/getting-started"
ChronicleQueue queue = SingleChronicleQueueBuilder . single ( basePath ). build ();
Dans cet exemple, nous avons créé uneChronicle IndexedChronicle
qui crée deux RandomAccessFiles
; Un pour les index et un pour les données ayant des noms relativement:
${java.io.tmpdir}/getting-started/{today}.cq4
Écrire dans une file d'attente
// Obtains an ExcerptAppender
ExcerptAppender appender = queue . acquireAppender ();
// Writes: {msg: TestMessage}
appender . writeDocument ( w -> w . write ( "msg" ). text ( "TestMessage" ));
// Writes: TestMessage
appender . writeText ( "TestMessage" );
Lire une file d'attente
// Creates a tailer
ExcerptTailer tailer = queue . createTailer ();
tailer . readDocument ( w -> System . out . println ( "msg: " + w . read (()-> "msg" ). text ()));
assertEquals ( "TestMessage" , tailer . readText ());
De plus, la méthode ChronicleQueue.dump()
peut être utilisée pour vider le contenu brut sous forme de chaîne.
queue . dump ();
Nettoyage
La file d'attente de Chronicle stocke ses données hors-heap, et il est recommandé d'appeler close()
une fois que vous avez fini de travailler avec Chronicle Fitre, aux ressources gratuites.
Note | Aucune donnée ne sera perdue si vous faites cela. Il s'agit uniquement de nettoyer les ressources utilisées. |
queue . close ();
Mettre tout cela ensemble
try ( ChronicleQueue queue = SingleChronicleQueueBuilder . single ( "queue-dir" ). build ()) {
// Obtain an ExcerptAppender
ExcerptAppender appender = queue . acquireAppender ();
// Writes: {msg: TestMessage}
appender . writeDocument ( w -> w . write ( "msg" ). text ( "TestMessage" ));
// Writes: TestMessage
appender . writeText ( "TestMessage" );
ExcerptTailer tailer = queue . createTailer ();
tailer . readDocument ( w -> System . out . println ( "msg: " + w . read (()-> "msg" ). text ()));
assertEquals ( "TestMessage" , tailer . readText ());
}
Vous pouvez configurer une file d'attente Chronicle en utilisant ses paramètres de configuration ou ses propriétés système. De plus, il existe différentes façons d'écrire / lire dans / depuis une file d'attente telle que l'utilisation de proxys et l'utilisation de MethodReader
et MethodWriter
.
La file d'attente Chronicle (CQ) peut être configurée via un certain nombre de méthodes sur la classe SingleChronicleQueueBuilder
. Quelques-uns des paramètres les plus interrogés par nos clients sont expliqués ci-dessous.
Cycle
Le paramètre RollCycle
configure la vitesse à laquelle CQ lancera les fichiers de file d'attente sous-jacents. Par exemple, l'utilisation de l'extrait de code suivant entraînera le lancement des fichiers de file d'attente (c'est-à-dire un nouveau fichier créé) toutes les heures:
ChronicleQueue . singleBuilder ( queuePath ). rollCycle ( RollCycles . HOURLY ). build ()
Une fois le cycle de rouleau d'une file d'attente réglé, il ne peut pas être modifié à une date ultérieure. Tous les autres cas de SingleChronicleQueue
configurés pour utiliser le même chemin doivent être configurés pour utiliser le même cycle de rouleau, et s'ils ne le sont pas, le cycle de roulement sera mis à jour pour correspondre au cycle de rouleau persistant. Dans ce cas, un message de journal d'avertissement sera imprimé afin d'informer l'utilisateur de la bibliothèque de la situation:
// Creates a queue with roll-cycle MINUTELY
try ( ChronicleQueue minuteRollCycleQueue = ChronicleQueue . singleBuilder ( queueDir ). rollCycle ( MINUTELY ). build ()) {
// Creates a queue with roll-cycle HOURLY
try ( ChronicleQueue hourlyRollCycleQueue = ChronicleQueue . singleBuilder ( queueDir ). rollCycle ( HOURLY ). build ()) {
try ( DocumentContext documentContext = hourlyRollCycleQueue . acquireAppender (). writingDocument ()) {
documentContext . wire (). write ( "somekey" ). text ( "somevalue" );
}
}
// Now try to append using the queue configured with roll-cycle MINUTELY
try ( DocumentContext documentContext2 = minuteRollCycleQueue . acquireAppender (). writingDocument ()) {
documentContext2 . wire (). write ( "otherkey" ). text ( "othervalue" );
}
}
Sortie de la console:
[main] WARN SingleChronicleQueueBuilder - Overriding roll cycle from HOURLY to MINUTELY.
Le nombre maximum de messages qui peuvent être stockés dans un fichier de file d'attente dépend du cycle de roulement. Voir FAQ pour plus d'informations à ce sujet.
Dans la file d'attente Chronicle, le temps de retournement est basé sur UTC. La fonction d'entreprise de rabouglement de fuseau horaire étend la capacité de Chronicle Queue à spécifier l'heure et la périodicité des rouleaux de file d'attente, plutôt que UTC. Pour plus d'informations, voir le renversement de la file d'attente du fuseau horaire.
La classe FileUtil
d'attente Chronicle fournit des méthodes utiles pour gérer les fichiers de file d'attente. Voir la gestion des fichiers de rouleau directement.
type de transport électronique
Il est possible de configurer comment Chronicle Fidue stockera les données en définissant explicitement le WireType
:
// Creates a queue at "queuePath" and sets the WireType
SingleChronicleQueueBuilder . builder ( queuePath , wireType )
Par exemple:
// Creates a queue with default WireType: BINARY_LIGHT
ChronicleQueue . singleBuilder ( queuePath )
// Creates a queue and sets the WireType as FIELDLESS_BINARY
SingleChronicleQueueBuilder . fieldlessBinary ( queuePath )
// Creates a queue and sets the WireType as DEFAULT_ZERO_BINARY
SingleChronicleQueueBuilder . defaultZeroBinary ( queuePath )
// Creates a queue and sets the WireType as DELTA_BINARY
SingleChronicleQueueBuilder . deltaBinary ( queuePath )
Bien qu'il soit possible de fournir explicitement les types de câbles lors de la création d'un constructeur, il est découragé car tous les types de fils ne sont pas encore pris en charge par Chronicle Fitre. En particulier, les types de fils suivants ne sont pas pris en charge:
Texte (et essentiellement tous basés sur le texte, y compris JSON et CSV)
BRUT
Lire_any
bloquer
Lorsqu'une file d'attente est lue / écrite, une partie du fichier en cours de lecture / écrit est mappée à un segment de mémoire. Ce paramètre contrôle la taille du bloc de mappage de mémoire. Vous pouvez modifier ce paramètre en utilisant la méthode SingleChronicleQueueBuilder.blockSize(long blockSize)
si cela est nécessaire.
Note | Vous devriez éviter de changer blockSize inutilement. |
Si vous envoyez de gros messages, vous devez définir une grande blockSize
, c'est-à-dire que la blockSize
doit être au moins quatre fois la taille du message.
Avertissement | Si vous utilisez une petite blockSize pour les grands messages, vous recevez un IllegalStateException et que l'écriture est interrompue. |
Nous vous recommandons d'utiliser le même blockSize
pour chaque instance de file d'attente lors de la réplication des files d'attente, le blockSize
n'est pas écrit dans les métadonnées de la file Pour fonctionner avec une blocksize
différente, vous pouvez).
Conseil | Utilisez le même blockSize pour chaque instance de files d'attente répliquées. |
espance index
Ce paramètre montre l'espace entre des extraits qui sont explicitement indexés. Un nombre plus élevé signifie des performances d'écriture séquentielles plus élevées mais une lecture d'accès aléatoire plus lent. La performance de lecture séquentielle n'est pas affectée par cette propriété. Par exemple, l'espacement d'index par défaut suivant peut être renvoyé:
16 (minutieusement)
64 (quotidiennement)
Vous pouvez modifier ce paramètre à l'aide de la méthode SingleChronicleQueueBuilder.indexSpacing(int indexSpacing)
.
index
La taille de chaque tableau d'index, ainsi que le nombre total de tableaux d'index par fichier de file d'attente.
Note | IndexCount 2 est le nombre maximum d'entrées de file d'attente indexées. |
Note | Voir l'indexation de l'extrait de la section dans la file d'attente Chronicle de ce guide de l'utilisateur pour plus d'informations et des exemples d'utilisation des index. |
ReadBuffermode, WriteBuffermode
Ces paramètres définissent Buffermode pour les lectures ou les écritures qui ont les options suivantes:
None
- la valeur par défaut (et la seule disponible pour les utilisateurs open source), pas de mise en mémoire tampon;
Copy
- utilisée en conjonction avec le cryptage;
Asynchronous
- Utilisez un tampon asynchrone lors de la lecture et / ou de l'écriture, fourni par le mode Asyncle Asyncle.
buffcapacité
Capacité de ringbuffer en octets lors de l'utilisation bufferMode: Asynchronous
Dans la file d'attente Chronicle, nous nous référons à l'acte d'écrire vos données dans la file d'attente Chronicle, comme stockant un extrait. Ces données peuvent être composées à partir de tout type de données, y compris du texte, des nombres ou des blobs sérialisés. En fin de compte, toutes vos données, quelle que soit ce qu'elles sont, sont stockées comme une série d'octets.
Juste avant de stocker votre extrait, Chronicle Fidue réserve un en-tête de 4 octets. Chronicle Queue écrit la longueur de vos données dans cet en-tête. De cette façon, lorsque la file d'attente Chronicle vient de lire votre extrait, il sait combien de temps chaque goutte de données. Nous nous référons à cet en-tête de 4 octets, ainsi que votre extrait, en tant que document. STRICTHY SPEAUX CHRONICLE La file d'attente peut être utilisée pour lire et rédiger des documents.
Note | Dans cet en-tête de 4 octets, nous réservons également quelques bits pour un certain nombre d'opérations internes, telles que le verrouillage, pour faire de la file d'attente de la file d'attente sur la file d'attente sur les processeurs et les threads. La chose importante à noter est que à cause de cela, vous ne pouvez pas convertir strictement les 4 octets en un entier pour trouver la longueur de votre goutte de données. |
Comme indiqué précédemment, la file d'attente Chronicle utilise un appender pour écrire dans la file d'attente et un tailleur pour lire dans la file d'attente. Contrairement à d'autres solutions de file d'attente Java, les messages ne sont pas perdus lorsqu'ils sont lus avec un tailleur. Ceci est couvert plus en détail dans la section ci-dessous sur "la lecture d'une file d'attente à l'aide d'un tailleur". Pour écrire des données dans une file d'attente Chronicle, vous devez d'abord créer un appender:
try ( ChronicleQueue queue = ChronicleQueue . singleBuilder ( path + "/trades" ). build ()) {
final ExcerptAppender appender = queue . acquireAppender ();
}
Chronicle Queue utilise l'interface de bas niveau suivante pour écrire les données:
try ( final DocumentContext dc = appender . writingDocument ()) {
dc . wire (). write (). text (“ your text data “);
}
La fermeture sur le TRY-With-Resources, est le point où la longueur des données est écrite à l'en-tête. Vous pouvez également utiliser le DocumentContext
pour découvrir l'index qui vient d'être attribué (voir ci-dessous). Vous pouvez ensuite utiliser cet index pour déplacer / rechercher cet extrait. Chaque extrait de file d'attente Chronicle a un index unique.
try ( final DocumentContext dc = appender . writingDocument ()) {
dc . wire (). write (). text (“ your text data “);
System . out . println ( "your data was store to index=" + dc . index ());
}
Les méthodes de haut niveau ci-dessous telles que writeText()
sont des méthodes de commodité pour appeler appender.writingDocument()
, mais les deux approches font essentiellement la même chose. Le code réel de writeText(CharSequence text)
ressemble à ceci:
/**
* @param text the message to write
*/
void writeText ( CharSequence text ) {
try ( DocumentContext dc = writingDocument ()) {
dc . wire (). bytes (). append8bit ( text );
}
}
Vous avez donc le choix d'un certain nombre d'interfaces de haut niveau, jusqu'à une API de bas niveau, à la mémoire brute.
Il s'agit de l'API le plus haut niveau qui masque le fait que vous écrivez dans la messagerie. L'avantage est que vous pouvez échanger des appels vers l'interface avec un composant réel ou une interface à un protocole différent.
// using the method writer interface.
RiskMonitor riskMonitor = appender . methodWriter ( RiskMonitor . class );
final LocalDateTime now = LocalDateTime . now ( Clock . systemUTC ());
riskMonitor . trade ( new TradeDetails ( now , "GBPUSD" , 1.3095 , 10e6 , Side . Buy , "peter" ));
Vous pouvez écrire un "message auto-décrivant". Ces messages peuvent prendre en charge les changements de schéma. Ils sont également plus faciles à comprendre lors du débogage ou du diagnostic de problèmes.
// writing a self describing message
appender . writeDocument ( w -> w . write ( "trade" ). marshallable (
m -> m . write ( "timestamp" ). dateTime ( now )
. write ( "symbol" ). text ( "EURUSD" )
. write ( "price" ). float64 ( 1.1101 )
. write ( "quantity" ). float64 ( 15e6 )
. write ( "side" ). object ( Side . class , Side . Sell )
. write ( "trader" ). text ( "peter" )));
Vous pouvez écrire des "données brutes" qui se décrivent. Les types seront toujours corrects; La position est la seule indication quant à la signification de ces valeurs.
// writing just data
appender . writeDocument ( w -> w
. getValueOut (). int32 ( 0x123456 )
. getValueOut (). int64 ( 0x999000999000L )
. getValueOut (). text ( "Hello World" ));
Vous pouvez écrire des "données brutes" qui ne se décrivent pas. Votre lecteur doit savoir ce que signifie ces données et les types utilisés.
// writing raw data
appender . writeBytes ( b -> b
. writeByte (( byte ) 0x12 )
. writeInt ( 0x345678 )
. writeLong ( 0x999000999000L )
. writeUtf8 ( "Hello World" ));
Ci-dessous, la façon la plus basse pour écrire des données est illustrée. Vous obtenez une adresse à la mémoire brute et vous pouvez écrire ce que vous voulez.
// Unsafe low level
appender . writeBytes ( b -> {
long address = b . address ( b . writePosition ());
Unsafe unsafe = UnsafeMemory . UNSAFE ;
unsafe . putByte ( address , ( byte ) 0x12 );
address += 1 ;
unsafe . putInt ( address , 0x345678 );
address += 4 ;
unsafe . putLong ( address , 0x999000999000L );
address += 8 ;
byte [] bytes = "Hello World" . getBytes ( StandardCharsets . ISO_8859_1 );
unsafe . copyMemory ( bytes , Jvm . arrayByteBaseOffset (), null , address , bytes . length );
b . writeSkip ( 1 + 4 + 8 + bytes . length );
});
Vous pouvez imprimer le contenu de la file d'attente. Vous pouvez voir les deux premiers et les deux derniers messages stockent les mêmes données.
// dump the content of the queue
System . out . println ( queue . dump ());
Impressions:
# position: 262568, header: 0
--- !!data # binary
trade : {
timestamp : 2016-07-17T15:18:41.141,
symbol : GBPUSD,
price : 1.3095,
quantity : 10000000.0,
side : Buy,
trader : peter
}
# position: 262684, header: 1
--- !!data # binary
trade : {
timestamp : 2016-07-17T15:18:41.141,
symbol : EURUSD,
price : 1.1101,
quantity : 15000000.0,
side : Sell,
trader : peter
}
# position: 262800, header: 2
--- !!data # binary
!int 1193046
168843764404224
Hello World
# position: 262830, header: 3
--- !!data # binary
000402b0 12 78 56 34 00 00 90 99 00 90 99 00 00 0B ·xV4·· ········
000402c0 48 65 6C 6C 6F 20 57 6F 72 6C 64 Hello Wo rld
# position: 262859, header: 4
--- !!data # binary
000402c0 12 ·
000402d0 78 56 34 00 00 90 99 00 90 99 00 00 0B 48 65 6C xV4····· ·····Hel
000402e0 6C 6F 20 57 6F 72 6C 64 lo World
La lecture de la file d'attente suit le même modèle que l'écriture, sauf qu'il n'y a pas de possibilité qu'il n'y ait pas de message lorsque vous essayez de le lire.
try ( ChronicleQueue queue = ChronicleQueue . singleBuilder ( path + "/trades" ). build ()) {
final ExcerptTailer tailer = queue . createTailer ();
}
Vous pouvez transformer chaque message en un appel de méthode basé sur le contenu du message et avoir une file d'attente de chronique désérialiser automatiquement les arguments de la méthode. L'appel reader.readOne()
sautera automatiquement (filtrer) tous les messages qui ne correspondent pas à votre lecteur de méthode.
// reading using method calls
RiskMonitor monitor = System . out :: println ;
MethodReader reader = tailer . methodReader ( monitor );
// read one message
assertTrue ( reader . readOne ());
Vous pouvez décoder le message vous-même.
Note | Les noms, le type et l'ordre des champs n'ont pas à correspondre. |
assertTrue ( tailer . readDocument ( w -> w . read ( "trade" ). marshallable (
m -> {
LocalDateTime timestamp = m . read ( "timestamp" ). dateTime ();
String symbol = m . read ( "symbol" ). text ();
double price = m . read ( "price" ). float64 ();
double quantity = m . read ( "quantity" ). float64 ();
Side side = m . read ( "side" ). object ( Side . class );
String trader = m . read ( "trader" ). text ();
// do something with values.
})));
Vous pouvez lire les valeurs de données auto-décrites. Cela vérifiera que les types sont corrects et convertissent au besoin.
assertTrue ( tailer . readDocument ( w -> {
ValueIn in = w . getValueIn ();
int num = in . int32 ();
long num2 = in . int64 ();
String text = in . text ();
// do something with values
}));
Vous pouvez lire les données brutes sous forme de primitives et de chaînes.
assertTrue ( tailer . readBytes ( in -> {
int code = in . readByte ();
int num = in . readInt ();
long num2 = in . readLong ();
String text = in . readUtf8 ();
assertEquals ( "Hello World" , text );
// do something with values
}));
Ou, vous pouvez obtenir l'adresse de mémoire sous-jacente et accéder à la mémoire native.
assertTrue ( tailer . readBytes ( b -> {
long address = b . address ( b . readPosition ());
Unsafe unsafe = UnsafeMemory . UNSAFE ;
int code = unsafe . getByte ( address );
address ++;
int num = unsafe . getInt ( address );
address += 4 ;
long num2 = unsafe . getLong ( address );
address += 8 ;
int length = unsafe . getByte ( address );
address ++;
byte [] bytes = new byte [ length ];
unsafe . copyMemory ( null , address , bytes , Jvm . arrayByteBaseOffset (), bytes . length );
String text = new String ( bytes , StandardCharsets . UTF_8 );
assertEquals ( "Hello World" , text );
// do something with values
}));
Note | Chaque caraileur voit chaque message. |
Une abstraction peut être ajoutée aux messages de filtre ou attribuer des messages à un seul processeur de messages. Cependant, en général, vous n'avez besoin que d'un seuldeur principal pour un sujet, avec éventuellement, certains tailleurs de support pour la surveillance, etc.
Comme la file d'attente Chronicle ne partage pas ses sujets, vous obtenez une commande totale de tous les messages dans ce sujet. Dans tous les sujets, il n'y a aucune garantie de commande; Si vous souhaitez rejouer de manière déterministe à partir d'un système qui consomme de plusieurs sujets, nous vous suggérons de rejouer à partir de la sortie de ce système.
Les tailleurs de file d'attente Chronicle peuvent créer des gestionnaires de fichiers, les gestionnaires de fichiers sont nettoyés chaque fois que la méthode close()
de la file d'attente de Chronicle associée est invoquée ou chaque fois que le JVM exécute une collection d'ordures. Si vous écrivez votre code, pas de pauses GC et que vous souhaitez explicitement nettoyer les gestionnaires de fichiers, vous pouvez appeler ce qui suit:
(( StoreTailer ) tailer ). releaseResources ()
ExcerptTailer.toEnd()
Dans certaines applications, il peut être nécessaire de commencer à lire depuis la fin de la file d'attente (par exemple dans un scénario de redémarrage). Pour ce cas d'utilisation, ExcerptTailer
fournit la méthode toEnd()
. Lorsque la direction du tailleur est FORWARD
(par défaut, ou tel que définie par la méthode ExcerptTailer.direction
), l'appel toEnd()
placera le tailleur juste après le dernier enregistrement existant dans la file d'attente. Dans ce cas, le tailleur est maintenant prêt à lire les nouveaux enregistrements annexés à la file d'attente. Jusqu'à ce que les nouveaux messages soient ajoutés à la file d'attente, il n'y aura pas de nouveau DocumentContext
disponible pour la lecture:
// this will be false until new messages are appended to the queue
boolean messageAvailable = tailer . toEnd (). readingDocument (). isPresent ();
S'il est nécessaire de lire à l'envers à travers la file d'attente depuis la fin, alors le tailleur peut être défini pour lire à l'envers:
ExcerptTailer tailer = queue . createTailer ();
tailer . direction ( TailerDirection . BACKWARD ). toEnd ();
Lors de la lecture en arrière, la méthode toEnd()
déplacera le tailleur vers le dernier enregistrement dans la file d'attente. Si la file d'attente n'est pas vide, il y aura un DocumentContext
disponible pour la lecture:
// this will be true if there is at least one message in the queue
boolean messageAvailable = tailer . toEnd (). direction ( TailerDirection . BACKWARD ).
readingDocument (). isPresent ();
AKA nommé Tailers.
Il peut être utile d'avoir un caraileur qui continue d'où il allait redémarrer de l'application.
try ( ChronicleQueue cq = SingleChronicleQueueBuilder . binary ( tmp ). build ()) {
ExcerptTailer atailer = cq . createTailer ( "a" );
assertEquals ( "test 0" , atailer . readText ());
assertEquals ( "test 1" , atailer . readText ());
assertEquals ( "test 2" , atailer . readText ()); // (1)
ExcerptTailer btailer = cq . createTailer ( "b" );
assertEquals ( "test 0" , btailer . readText ()); // (3)
}
try ( ChronicleQueue cq = SingleChronicleQueueBuilder . binary ( tmp ). build ()) {
ExcerptTailer atailer = cq . createTailer ( "a" );
assertEquals ( "test 3" , atailer . readText ()); // (2)
assertEquals ( "test 4" , atailer . readText ());
assertEquals ( "test 5" , atailer . readText ());
ExcerptTailer btailer = cq . createTailer ( "b" );
assertEquals ( "test 1" , btailer . readText ()); // (4)
}
Taireur "A" Last lit Message 2
Taireur "A" Le message indique le message 3
Taireur "B" Last lit Message 0
Taireur "B" Suivant Message 1
This is from the RestartableTailerTest
where there are two tailers, each with a unique name. These tailers store their index within the Queue itself and this index is maintained as the tailer uses toStart()
, toEnd()
, moveToIndex()
or reads a message.
Note | The direction() is not preserved across restarts, only the next index to be read. |
Note | The index of a tailer is only progressed when the DocumentContext.close() is called. If this is prevented by an error, the same message will be read on each restart. |
Chronicle Queue stores its data in binary format, with a file extension of cq4
:
��@π�header∂�SCQStoreÇE���»wireType∂�WireTypeÊBINARYÕwritePositionèèèèß��������ƒroll∂�SCQSRollÇ*���∆length¶ÄÓ6�∆format
ÎyyyyMMdd-HH≈epoch¶ÄÓ6�»indexing∂SCQSIndexingÇN��� indexCount•��ÃindexSpacing�Àindex2Indexé����ß��������…lastIndexé�
���ß��������fllastAcknowledgedIndexReplicatedé������ߡˇˇˇˇˇˇˇ»recovery∂�TimedStoreRecoveryÇ����…timeStampèèèß����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
This can often be a bit difficult to read, so it is better to dump the cq4
files as text. This can also help you fix your production issues, as it gives you the visibility as to what has been stored in the queue, and in what order.
You can dump the queue to the terminal using net.openhft.chronicle.queue.main.DumpMain
or net.openhft.chronicle.queue.ChronicleReaderMain
. DumpMain
performs a simple dump to the terminal while ChronicleReaderMain
handles more complex operations, eg tailing a queue. They can both be run from the command line in a number of ways described below.
If you have a project pom file that includes the Chronicle-Queue artifact, you can read a cq4
file with the following command:
$ mvn exec:java -Dexec.mainClass="net.openhft.chronicle.queue.main.DumpMain" -Dexec.args="myqueue"
In the above command myqueue is the directory containing your .cq4 files
You can also set up any dependent files manually. This requires the chronicle-queue.jar
, from any version 4.5.3 or later, and that all dependent files are present on the class path. The dependent jars are listed below:
$ ls -ltr
total 9920
-rw-r--r-- 1 robaustin staff 112557 28 Jul 14:52 chronicle-queue-5.20.108.jar
-rw-r--r-- 1 robaustin staff 209268 28 Jul 14:53 chronicle-bytes-2.20.104.jar
-rw-r--r-- 1 robaustin staff 136434 28 Jul 14:56 chronicle-core-2.20.114.jar
-rw-r--r-- 1 robaustin staff 33562 28 Jul 15:03 slf4j-api-1.7.30.jar
-rw-r--r-- 1 robaustin staff 33562 28 Jul 15:03 slf4j-simple-1.7.30.jar
-rw-r--r-- 1 robaustin staff 324302 28 Jul 15:04 chronicle-wire-2.20.105.jar
-rw-r--r-- 1 robaustin staff 35112 28 Jul 15:05 chronicle-threads-2.20.101.jar
-rw-r--r-- 1 robaustin staff 344235 28 Jul 15:05 affinity-3.20.0.jar
-rw-r--r-- 1 robaustin staff 124332 28 Jul 15:05 commons-cli-1.4.jar
-rw-r--r-- 1 robaustin staff 4198400 28 Jul 15:06 19700101-02.cq4
Conseil | To find out which version of jars to include please, refer to the chronicle-bom . |
Once the dependencies are present on the class path, you can run:
$ java -cp chronicle-queue-5.20.108.jar net.openhft.chronicle.queue.main.DumpMain 19700101-02.cq4
This will dump the 19700101-02.cq4
file out as text, as shown below:
!!meta-data # binary
header : !SCQStore {
wireType : !WireType BINARY,
writePosition : 0,
roll : !SCQSRoll {
length : !int 3600000,
format : yyyyMMdd-HH,
epoch : !int 3600000
},
indexing : !SCQSIndexing {
indexCount : !short 4096,
indexSpacing : 4,
index2Index : 0,
lastIndex : 0
},
lastAcknowledgedIndexReplicated : -1,
recovery : !TimedStoreRecovery {
timeStamp : 0
}
}
...
# 4198044 bytes remaining
Note | The example above does not show any user data, because no user data was written to this example file. |
There is also a script named dump_queue.sh
located in the Chonicle-Queue/bin
-folder that gathers the needed dependencies in a shaded jar and uses it to dump the queue with DumpMain
. The script can be run from the Chronicle-Queue
root folder like this:
$ ./bin/dump_queue.sh <file path>
ChronicleReaderMain
The second tool for logging the contents of the chronicle queue is the ChronicleReaderMain
(in the Chronicle Queue project). As mentioned above, it is able to perform several operations beyond printing the file content to the console. For example, it can be used to tail a queue to detect whenever new messages are added (rather like $tail -f).
Below is the command line interface used to configure ChronicleReaderMain
:
usage: ChronicleReaderMain -a <binary-arg> Argument to pass to binary search class -b <binary-search> Use this class as a comparator to binary search -cbl <content-based-limiter> Specify a content-based limiter -cblArg <content-based-limiter-argument> Specify an argument for use by the content-based limiter -d <directory> Directory containing chronicle queue files -e <exclude-regex> Do not display records containing this regular expression -f Tail behaviour - wait for new records to arrive -g Show message history (when using method reader) -h Print this help and exit -i <include-regex> Display records containing this regular expression -k Read the queue in reverse -l Squash each output message into a single line -m <max-history> Show this many records from the end of the data set -n <from-index> Start reading from this index (eg 0x123ABE) -named <named> Named tailer ID -r <as-method-reader> Use when reading from a queue generated using a MethodWriter -s Display index -w <wire-type> Control output ie JSON -x <max-results> Limit the number of results to output -z Print timestamps using the local timezone
Just as with DumpQueue
you need the classes in the example above present on the class path. This can again be achieved by manually adding them and then run:
$ java -cp chronicle-queue-5.20.108.jar net.openhft.chronicle.queue.ChronicleReaderMain -d <directory>
Another option is to create an Uber Jar using the Maven shade plugin. It is configured as follows:
< build >
< plugins >
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-shade-plugin</ artifactId >
< executions >
< execution >
< phase >package</ phase >
< goals >
< goal >shade</ goal >
</ goals >
< configuration >
< filters >
< filter >
< artifact >*:*</ artifact >
< includes >
< include >net/openhft/**</ include >
< include >software/chronicle/**</ include >
</ includes >
</ filter >
</ filters >
</ configuration >
</ execution >
</ executions >
</ plugin >
</ plugins >
</ build >
Once the Uber jar is present, you can run ChronicleReaderMain
from the command line via:
java -cp "$UBER_JAR" net.openhft.chronicle.queue.ChronicleReaderMain "19700101-02.cq4"
Lastly, there is a script for running the reader named queue_reader.sh
which again is located in the Chonicle-Queue/bin
-folder. It automatically gathers the needed dependencies in a shaded jar and uses it to run ChronicleReaderMain
. The script can be run from the Chronicle-Queue
root folder like this:
$ ./bin/queue_reader.sh <options>
ChronicleWriter
If using MethodReader
and MethodWriter
then you can write single-argument method calls to a queue using net.openhft.chronicle.queue.ChronicleWriterMain
or the shell script queue_writer.sh
eg
usage: ChronicleWriterMain files.. -d < directory > [-i < interface > ] -m < method >
Missing required options: m, d
-d < directory > Directory containing chronicle queue to write to
-i < interface > Interface to write via
-m < method > Method name
If you want to write to the below "doit" method
public interface MyInterface {
void doit ( DTO dto );
}
public class DTO extends SelfDescribingMarshallable { private int age; nom de chaîne privé; }
Then you can call ChronicleWriterMain -d queue doit x.yaml
with either (or both) of the below Yamls:
{
age : 19,
name : Henry
}
ou
!x.y.z.DTO {
age : 42,
name : Percy
}
If DTO
makes use of custom serialisation then you should specify the interface to write to with -i
Chronicle v4.4+ supports the use of proxies to write and read messages. You start by defining an asynchronous interface
, where all methods have:
arguments which are only inputs
no return value or exceptions expected.
import net . openhft . chronicle . wire . SelfDescribingMarshallable ;
interface MessageListener {
void method1 ( Message1 message );
void method2 ( Message2 message );
}
static class Message1 extends SelfDescribingMarshallable {
String text ;
public Message1 ( String text ) {
this . text = text ;
}
}
static class Message2 extends SelfDescribingMarshallable {
long number ;
public Message2 ( long number ) {
this . number = number ;
}
}
To write to the queue you can call a proxy which implements this interface.
SingleChronicleQueue queue1 = ChronicleQueue . singleBuilder ( path ). build ();
MessageListener writer1 = queue1 . acquireAppender (). methodWriter ( MessageListener . class );
// call method on the interface to send messages
writer1 . method1 ( new Message1 ( "hello" ));
writer1 . method2 ( new Message2 ( 234 ));
These calls produce messages which can be dumped as follows.
# position: 262568, header: 0
--- !!data # binary
method1 : {
text : hello
}
# position: 262597, header: 1
--- !!data # binary
method2 : {
number : !int 234
}
To read the messages, you can provide a reader which calls your implementation with the same calls that you made.
// a proxy which print each method called on it
MessageListener processor = ObjectUtils . printAll ( MessageListener . class )
// a queue reader which turns messages into method calls.
MethodReader reader1 = queue1 . createTailer (). methodReader ( processor );
assertTrue ( reader1 . readOne ());
assertTrue ( reader1 . readOne ());
assertFalse ( reader1 . readOne ());
Running this example prints:
method1 [!Message1 {
text: hello
}
]
method2 [!Message2 {
number: 234
}
]
For more details see, Using Method Reader/Writers and MessageReaderWriterTest
Chronicle Queue supports explicit, or implicit, nanosecond resolution timing for messages as they pass end-to-end over across your system. We support using nano-time across machines, without the need for specialist hardware. To enable this, set the sourceId
of the queue.
ChronicleQueue out = ChronicleQueue . singleBuilder ( queuePath )
...
. sourceId ( 1 )
. build ();
SidedMarketDataListener combiner = out . acquireAppender ()
. methodWriterBuilder ( SidedMarketDataListener . class )
. get ();
combiner . onSidedPrice ( new SidedPrice ( "EURUSD1" , 123456789000L , Side . Sell , 1.1172 , 2e6 ));
A timestamp is added for each read and write as it passes from service to service.
--- !!data # binary
history : {
sources : [
1,
0x426700000000 # (4)
]
timings : [
1394278797664704, # (1)
1394278822632044, # (2)
1394278824073475 # (3)
]
}
onTopOfBookPrice : {
symbol : EURUSD1,
timestamp : 123456789000,
buyPrice : NaN,
buyQuantity : 0,
sellPrice : 1.1172,
sellQuantity : 2000000.0
}
First write
First read
Write of the result of the read.
What triggered this event.
In the following section you will find how to work with the excerpt index.
Finding the index at the end of a Chronicle Queue
Chronicle Queue appenders are thread-local. In fact when you ask for:
final ExcerptAppender appender = queue.acquireAppender();
the acquireAppender()
uses a thread-local pool to give you an appender which will be reused to reduce object creation. As such, the method call to:
long index = appender.lastIndexAppended();
will only give you the last index appended by this appender; not the last index appended by any appender. If you wish to find the index of the last record written to the queue, then you have to call:
queue.lastIndex()
Which will return the index of the last excerpt present in the queue (or -1 for an empty queue). Note that if the queue is being written to concurrently it's possible the value may be an under-estimate, as subsequent entries may have been written even before it was returned.
The number of messages between two indexes
To count the number of messages between two indexes you can use:
((SingleChronicleQueue)queue).countExcerpts(<firstIndex>,<lastIndex>);
Note | You should avoid calling this method on latency sensitive code, because if the indexes are in different cycles this method may have to access the .cq4 files from the file system. |
for more information on this see :
net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.countExcerpts
Move to a specific message and read it
The following example shows how to write 10 messages, then move to the 5th message to read it
@ Test
public void read5thMessageTest () {
try ( final ChronicleQueue queue = singleBuilder ( getTmpDir ()). build ()) {
final ExcerptAppender appender = queue . acquireAppender ();
int i = 0 ;
for ( int j = 0 ; j < 10 ; j ++) {
try ( DocumentContext dc = appender . writingDocument ()) {
dc . wire (). write ( "hello" ). text ( "world " + ( i ++));
long indexWritten = dc . index ();
}
}
// Get the current cycle
int cycle ;
final ExcerptTailer tailer = queue . createTailer ();
try ( DocumentContext documentContext = tailer . readingDocument ()) {
long index = documentContext . index ();
cycle = queue . rollCycle (). toCycle ( index );
}
long index = queue . rollCycle (). toIndex ( cycle , 5 );
tailer . moveToIndex ( index );
try ( DocumentContext dc = tailer . readingDocument ()) {
System . out . println ( dc . wire (). read ( "hello" ). text ());
}
}
}
You can add a StoreFileListener
to notify you when a file is added, or no longer used. This can be used to delete files after a period of time. However, by default, files are retained forever. Our largest users have over 100 TB of data stored in queues.
Appenders and tailers are cheap as they don't even require a TCP connection; they are just a few Java objects. The only thing each tailer retains is an index which is composed from:
a cycle number. For example, days since epoch, and
a sequence number within that cycle.
In the case of a DAILY
cycle, the sequence number is 32 bits, and the index = ((long) cycle << 32) | sequenceNumber
providing up to 4 billion entries per day. if more messages per day are anticipated, the XLARGE_DAILY
cycle, for example, provides up 4 trillion entries per day using a 48-bit sequence number. Printing the index in hexadecimal is common in our libraries, to make it easier to see these two components.
Rather than partition the queue files across servers, we support each server, storing as much data as you have disk space. This is much more scalable than being limited to the amount of memory space that you have. You can buy a redundant pair of 6TB of enterprise disks very much more cheaply than 6TB of memory.
Chronicle Queue runs a background thread to watch for low disk space (see net.openhft.chronicle.threads.DiskSpaceMonitor
class) as the JVM can crash when allocating a new memory mapped file if disk space becomes low enough. The disk space monitor checks (for each FileStore you are using Chronicle Queues on): that there is less than 200MB free. If so you will see:
Jvm . warn (). on ( getClass (), "your disk " + fileStore + " is almost full, " +
"warning: chronicle-queue may crash if it runs out of space." );
otherwise it will check for the threshold percentage and log out this message:
Jvm . warn (). on ( getClass (), "your disk " + fileStore
+ " is " + diskSpaceFull + "% full, " +
"warning: chronicle-queue may crash if it runs out of space." );
The threshold percentage is controlled by the chronicle.disk.monitor.threshold.percent system property. The default value is 0.
As mentioned previously Chronicle Queue stores its data off-heap in a '.cq4' file. So whenever you wish to append data to this file or read data into this file, chronicle queue will create a file handle . Typically, Chronicle Queue will create a new '.cq4' file every day. However, this could be changed so that you can create a new file every hour, every minute or even every second.
If we create a queue file every second, we would refer to this as SECONDLY rolling. Of course, creating a new file every second is a little extreme, but it's a good way to illustrate the following point. When using secondly rolling, If you had written 10 seconds worth of data and then you wish to read this data, chronicle would have to scan across 10 files. To reduce the creation of the file handles, chronicle queue cashes them lazily and when it comes to writing data to the queue files, care-full consideration must be taken when closing the files, because on most OS's a close of the file, will force any data that has been appended to the file, to be flushed to disk, and if we are not careful this could stall your application.
Pretoucher
is a class designed to be called from a long-lived thread. The purpose of the Pretoucher is to accelerate writing in a queue. Upon invocation of the execute()
method, this object will pre-touch pages in the queue's underlying store file, so that they are resident in the page-cache (ie loaded from storage) before they are required by appenders to the queue. Resources held by this object will be released when the underlying queue is closed. Alternatively, the shutdown()
method can be called to close the supplied queue and release any other resources. Invocation of the execute()
method after shutdown()
has been called will cause an IllegalStateException
to be thrown.
The Pretoucher's configuration parameters (set via the system properties) are as follows:
SingleChronicleQueueExcerpts.earlyAcquireNextCycle
(defaults to false): Causes the Pretoucher to create the next cycle file while the queue is still writing to the current one in order to mitigate the impact of stalls in the OS when creating new files.
Avertissement | earlyAcquireNextCycle is off by default and if it is going to be turned on, you should very carefully stress test before and after turning it on. Basically what you experience is related to your system. |
SingleChronicleQueueExcerpts.pretoucherPrerollTimeMs
(defaults to 2,000 milliseconds) The pretoucher will create new cycle files this amount of time in advanced of them being written to. Effectively moves the Pretoucher's notion of which cycle is "current" into the future by pretoucherPrerollTimeMs
.
SingleChronicleQueueExcerpts.dontWrite
(defaults to false): Tells the Pretoucher to never create cycle files that do not already exist. As opposed to the default behaviour where if the Pretoucher runs inside a cycle where no excerpts have been written, it will create the "current" cycle file. Obviously enabling this will prevent earlyAcquireNextCycle
from working.
Pretoucher usage example
The configuration parameters of Pretoucher that were described above should be set via system properties. For example, in the following excerpt earlyAcquireNextCycle
is set to true
and pretoucherPrerollTimeMs
to 100ms.
System . setProperty ( "SingleChronicleQueueExcerpts.earlyAcquireNextCycle" , "true" );
System . setProperty ( "SingleChronicleQueueExcerpts.pretoucherPrerollTimeMs" , "100" );
The constructor of Pretoucher takes the name of the queue that this Pretoucher is assigned to and creates a new Pretoucher. Then, by invoking the execute()
method the Pretoucher starts.
// Creates the queue q1 (or q1 is a queue that already exists)
try ( final SingleChronicleQueue q1 = SingleChronicleQueueBuilder . binary ( "queue-storage-path" ). build ();
final Pretoucher pretouch = PretouchUtil . INSTANCE . createPretoucher ( q1 )){
try {
pretouch . execute ();
} catch ( InvalidEventHandlerException e ) {
throw Jvm . rethrow ( e );
}
}
The method close()
, closes the Pretoucher and releases its resources.
pretouch . close ();
Note | The Pretoucher is an Enterprise feature |
Chronicle Queue can be monitored to obtain latency, throughput, and activity metrics, in real time (that is, within microseconds of the event triggering it).
The following charts show how long it takes to:
write a 40 byte message to a Chronicle Queue
have the write replicated over TCP
have the second copy acknowledge receipt of the message
have a thread read the acknowledged message
The test was run for ten minutes, and the distribution of latencies plotted.
Note | There is a step in latency at around 10 million message per second; it jumps as the messages start to batch. At rates below this, each message can be sent individually. |
The 99.99 percentile and above are believed to be delays in passing the message over TCP. Further research is needed to prove this. These delays are similar, regardless of the throughput. The 99.9 percentile and 99.93 percentile are a function of how quickly the system can recover after a delay. The higher the throughput, the less headroom the system has to recover from a delay.
When double-buffering is disabled, all writes to the queue will be serialized based on the write lock acquisition. Each time ExcerptAppender.writingDocument()
is called, appender tries to acquire the write lock on the queue, and if it fails to do so it blocks until write lock is unlocked, and in turn locks the queue for itself.
When double-buffering is enabled, if appender sees that the write lock is acquired upon call to ExcerptAppender.writingDocument()
call, it returns immediately with a context pointing to the secondary buffer, and essentially defers lock acquisition until the context.close()
is called (normally with try-with-resources pattern it is at the end of the try block), allowing user to go ahead writing data, and then essentially doing memcpy on the serialized data (thus reducing cost of serialization). By default, double-buffering is disabled. You can enable double-buffering by calling
SingleChronicleQueueBuilder.doubleBuffer(true);
Note | During a write that is buffered, DocumentContext.index() will throw an IndexNotAvailableException . This is because it is impossible to know the index until the buffer is written back to the queue, which only happens when the DocumentContext is closed. |
This is only useful if (majority of) the objects being written to the queue are big enough AND their marshalling is not straight-forward (eg BytesMarshallable's marshalling is very efficient and quick and hence double-buffering will only slow things down), and if there's a heavy contention on writes (eg 2 or more threads writing a lot of data to the queue at a very high rate).
Résultats:
Below are the benchmark results for various data sizes at the frequency of 10 KHz for a cumbersome message (see net.openhft.chronicle.queue.bench.QueueContendedWritesJLBHBenchmark
), YMMV - always do your own benchmarks:
1 KB
Double-buffer disabled:
-------------------------------- SUMMARY (Concurrent) -------------- ---------------------------------------------- Percentile run1 run2 run3 % Variation 50: 90.40 90.59 91.17 0.42 90: 179.52 180.29 97.50 36.14 99: 187.33 186.69 186.82 0.05 99.7: 213.57 198.72 217.28 5.86 -------------------------------------------------- -------------------------------------------------- -------------- -------------------------------- SUMMARY (Concurrent2) ----------------------------------------------------------- Percentile run1 run2 run3 % Variation 50: 179.14 179.26 180.93 0.62 90: 183.49 183.36 185.92 0.92 99: 192.19 190.02 215.49 8.20 99.7: 240.70 228.16 258.88 8.24 -------------------------------------------------- -------------------------------------------------- --------------
Double-buffer enabled:
-------------------------------- SUMMARY (Concurrent) -------------- ---------------------------------------------- Percentile run1 run2 run3 % Variation 50: 86.05 85.60 86.24 0.50 90: 170.18 169.79 170.30 0.20 99: 176.83 176.58 177.09 0.19 99.7: 183.36 185.92 183.49 0.88 -------------------------------------------------- -------------------------------------------------- -------------- -------------------------------- SUMMARY (Concurrent2) ----------------------------------------------------------- Percentile run1 run2 run3 % Variation 50: 86.24 85.98 86.11 0.10 90: 89.89 89.44 89.63 0.14 99: 169.66 169.79 170.05 0.10 99.7: 175.42 176.32 176.45 0.05 -------------------------------------------------- -------------------------------------------------- --------------
4 KB
Double-buffer disabled:
-------------------------------- SUMMARY (Concurrent) -------------- ---------------------------------------------- Percentile run1 run2 run3 % Variation 50: 691.46 699.65 701.18 0.15 90: 717.57 722.69 721.15 0.14 99: 752.90 748.29 748.29 0.00 99.7: 1872.38 1743.36 1780.22 1.39 -------------------------------------------------- -------------------------------------------------- -------------- -------------------------------- SUMMARY (Concurrent2) ----------------------------------------------------------- Percentile run1 run2 run3 % Variation 50: 350.59 353.66 353.41 0.05 90: 691.46 701.18 697.60 0.34 99: 732.42 733.95 729.34 0.42 99.7: 1377.79 1279.49 1302.02 1.16 -------------------------------------------------- -------------------------------------------------- --------------
Double-buffer enabled:
-------------------------------- SUMMARY (Concurrent) -------------- ---------------------------------------------- Percentile run1 run2 run3 % Variation 50: 342.40 344.96 344.45 0.10 90: 357.25 360.32 359.04 0.24 99: 688.38 691.97 691.46 0.05 99.7: 1376.77 1480.19 1383.94 4.43 -------------------------------------------------- -------------------------------------------------- -------------- -------------------------------- SUMMARY (Concurrent2) ----------------------------------------------------------- Percentile run1 run2 run3 % Variation 50: 343.68 345.47 346.24 0.15 90: 360.06 362.11 363.14 0.19 99: 694.02 698.62 699.14 0.05 99.7: 1400.32 1510.91 1435.14 3.40 -------------------------------------------------- -------------------------------------------------- --------------
If you wish to tune your code for ultra-low latency, you could take a similar approach to our QueueReadJitterMain
net . openhft . chronicle . queue . jitter . QueueReadJitterMain
This code can be considered as a basic stack sampler profiler. This is assuming you base your code on the net.openhft.chronicle.core.threads.EventLoop
, you can periodically sample the stacks to find a stall. It is recommended to not reduce the sample rate below 50 microseconds as this will produce too much noise
It is likely to give you finer granularity than a typical profiler. As it is based on a statistical approach of where the stalls are, it takes many samples, to see which code has the highest grouping ( in other words the highest stalls ) and will output a trace that looks like the following :
28 at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1012) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at net.openhft.chronicle.core.util.WeakReferenceCleaner.newCleaner(WeakReferenceCleaner.java:43) at net.openhft.chronicle.bytes.NativeBytesStore.<init>(NativeBytesStore.java:90) at net.openhft.chronicle.bytes.MappedBytesStore.<init>(MappedBytesStore.java:31) at net.openhft.chronicle.bytes.MappedFile$$Lambda$4/1732398722.create(Unknown Source) at net.openhft.chronicle.bytes.MappedFile.acquireByteStore(MappedFile.java:297) at net.openhft.chronicle.bytes.MappedFile.acquireByteStore(MappedFile.java:246) 25 at net.openhft.chronicle.queue.jitter.QueueWriteJitterMain.lambda$main$1(QueueWriteJitterMain.java:58) at net.openhft.chronicle.queue.jitter.QueueWriteJitterMain$$Lambda$11/967627249.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) 21 at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1027) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at net.openhft.chronicle.core.util.WeakReferenceCleaner.newCleaner(WeakReferenceCleaner.java:43) at net.openhft.chronicle.bytes.NativeBytesStore.<init>(NativeBytesStore.java:90) at net.openhft.chronicle.bytes.MappedBytesStore.<init>(MappedBytesStore.java:31) at net.openhft.chronicle.bytes.MappedFile$$Lambda$4/1732398722.create(Unknown Source) at net.openhft.chronicle.bytes.MappedFile.acquireByteStore(MappedFile.java:297) at net.openhft.chronicle.bytes.MappedFile.acquireByteStore(MappedFile.java:246) 14 at net.openhft.chronicle.queue.jitter.QueueWriteJitterMain.lambda$main$1(QueueWriteJitterMain.java:54) at net.openhft.chronicle.queue.jitter.QueueWriteJitterMain$$Lambda$11/967627249.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)
from this, we can see that most of the samples (on this occasion 28 of them ) were captured in ConcurrentHashMap.putVal()
if we wish to get finer grain granularity, we will often add a net.openhft.chronicle.core.Jvm.safepoint
into the code because thread dumps are only reported at safe-points.
Résultats:
In the test described above, the typical latency varied between 14 and 40 microseconds. The 99 percentile varied between 17 and 56 microseconds depending on the throughput being tested. Notably, the 99.93% latency varied between 21 microseconds and 41 milliseconds, a factor of 2000.
Acceptable Latency | Throughput |
< 30 microseconds 99.3% of the time | 7 million message per second |
< 20 microseconds 99.9% of the time | 20 million messages per second |
< 1 milliseconds 99.9% of the time | 50 million messages per second |
< 60 microseconds 99.3% of the time | 80 million message per second |
Batching and Queue Latency
End-to-End latency plots for various message sizes
Chronicle Queue is designed to out-perform its rivals such as Kafka. Chronicle Queue supports over an order-of-magnitude of greater throughput, together with an order-of-magnitude of lower latency, than Apache Kafka. While Kafka is faster than many of the alternatives, it doesn't match Chronicle Queue's ability to support throughputs of over a million events per second, while simultaneously achieving latencies of 1 to 20 microseconds.
Chronicle Queue handles more volume from a single thread to a single partition. This avoids the need for the complexity, and the downsides, of having partitions.
Kafka uses an intermediate broker to use the operating system's file system and cache, while Chronicle Queue directly uses the operating system's file system and cache. For comparison see Kafka Documentation
Big Data and Chronicle Queue - a detailed description of some techniques utilised by Chronicle Queue
FAQ - questions asked by customers
How it works - more depth on how Chronicle Queue is implemented
Utilities - lists some useful utilities for working with queue files
Chronicle support on StackOverflow
Chronicle support on Google Groups
Leave your e-mail to get information about the latest releases and patches to stay up-to-date.