JBoss.orgCommunity Documentation
A Cache
consists of a collection of Node
instances, organised in a tree
structure. Each Node
contains a Map
which holds the data
objects to be cached. It is important to note that the structure is a mathematical tree, and not a graph; each
Node
has one and only one parent, and the root node is denoted by the constant fully qualified
name, Fqn.ROOT
.
In the diagram above, each box represents a JVM. You see 2 caches in separate JVMs, replicating data to each
other.
Any modifications (see API chapter) in one cache instance will be replicated to the other cache. Naturally, you can have more than 2 caches in a cluster. Depending on the transactional settings, this replication will occur either after each modification or at the end of a transaction, at commit time. When a new cache is created, it can optionally acquire the contents from one of the existing caches on startup.
In addition to Cache
and Node
interfaces, JBoss Cache exposes more
powerful CacheSPI
and NodeSPI
interfaces, which offer more control over
the internals of JBoss Cache. These interfaces are not intended for general use, but are designed for people
who wish to extend and enhance JBoss Cache, or write custom Interceptor
or
CacheLoader
instances.
The CacheSPI
interface cannot be created, but is injected into Interceptor
and CacheLoader
implementations by the setCache(CacheSPI cache)
methods on these interfaces. CacheSPI
extends Cache
so all the functionality of the basic API is also available.
Similarly, a NodeSPI
interface cannot be created. Instead, one is obtained by performing
operations on CacheSPI
, obtained as above. For example, Cache.getRoot() : Node
is overridden as CacheSPI.getRoot() : NodeSPI
.
It is important to note that directly casting a Cache
or Node
to its SPI counterpart is not recommended and is bad practice, since the inheritace of interfaces it is not a
contract that is guaranteed to be upheld moving forward. The exposed public APIs, on the other hand, is
guaranteed to be upheld.
Since the cache is essentially a collection of nodes, aspects such as clustering, persistence, eviction, etc. need to be applied to these nodes when operations are invoked on the cache as a whole or on individual nodes. To achieve this in a clean, modular and extensible manner, an interceptor chain is used. The chain is built up of a series of interceptors, each one adding an aspect or particular functionality. The chain is built when the cache is created, based on the configuration used.
It is important to note that the NodeSPI
offers some methods (such as the xxxDirect()
method family) that operate on a node directly without passing through the interceptor stack. Plugin authors
should note that using such methods will affect the aspects of the cache that may need to be applied, such as
locking, replication, etc. To put it simply, don't use such methods unless you really
know what you're doing!
JBoss Cache essentially is a core data structure - an implementation of DataContainer
- and
aspects and features are implemented using interceptors in front of this data structure. A
CommandInterceptor
is an abstract class, interceptor implementations extend this.
CommandInterceptor
implements the Visitor
interface so it is able to
alter commands in a strongly typed manner as the command makes its way to the data structure. More on
visitors and commands in the next section.
Interceptor implementations are chained together in the InterceptorChain
class, which
dispatches a command across the chain of interceptors. A special interceptor, the CallInterceptor
,
always sits at the end of this chain to invoke the command being passed up the chain by calling the
command's process()
method.
JBoss Cache ships with several interceptors, representing different behavioral aspects, some of which are:
TxInterceptor
- looks for ongoing transactions and registers with transaction managers to participate in
synchronization events
ReplicationInterceptor
- replicates state across a cluster using the RpcManager class
CacheLoaderInterceptor
- loads data from a persistent store if the data requested is not available in memory
The interceptor chain configured for your cache instance can be obtained and inspected by calling
CacheSPI.getInterceptorChain()
, which returns an ordered List
of interceptors in the order in which they would be encountered by a command.
Custom interceptors to add specific aspects or features can be written by extending
CommandInterceptor
and overriding the relevant
visitXXX()
methods based on the commands you are interested in intercepting. There
are other abstract interceptors you could extend instead, such as the PrePostProcessingCommandInterceptor
and the SkipCheckChainedInterceptor
. Please see their respective javadocs for details
on the extra features provided.
The custom interceptor will need to be added to the interceptor chain by using the
Cache.addInterceptor()
methods. See the javadocs on these methods for details.
Adding custom interceptors via XML is also supported, please see the XML configuration reference for details.
Internally, JBoss Cache uses a command/visitor pattern to execute API calls. Whenever a method is called
on the cache interface, the CacheInvocationDelegate
, which implements the Cache
interface, creates an instance of VisitableCommand
and dispatches this command up a chain of
interceptors. Interceptors, which implement the Visitor
interface, are able to handle
VisitableCommand
s they are interested in, and add behavior to the command.
Each command contains all knowledge of the command being executed such as parameters used and processing
behavior, encapsulated in a process()
method. For example, the RemoveNodeCommand
is created and passed up the interceptor chain when Cache.removeNode()
is called, and
RemoveNodeCommand.process()
has the necessary knowledge of how to remove a node from
the data structure.
In addition to being visitable, commands are also replicable. The JBoss Cache marshallers know how to efficiently marshall commands and invoke them on remote cache instances using an internal RPC mechanism based on JGroups.
InvocationContext
holds intermediate state for the duration of a single invocation, and is set up and
destroyed by the
InvocationContextInterceptor
which sits at the start of the interceptor chain.
InvocationContext
, as its name implies, holds contextual information associated with a single cache
method invocation. Contextual information includes associated
javax.transaction.Transaction
or
org.jboss.cache.transaction.GlobalTransaction
,
method invocation origin (
InvocationContext.isOriginLocal()
) as well as
Option
overrides
, and information around which nodes have been locked, etc.
The
InvocationContext
can be obtained by calling
Cache.getInvocationContext()
.
Some aspects and functionality is shared by more than a single interceptor. Some of these have been
encapsulated
into managers, for use by various interceptors, and are made available by the
CacheSPI
interface.
This class is responsible for calls made via the JGroups channel for all RPC calls to remote caches, and encapsulates the JGroups channel used.
This class manages buddy groups and invokes group organization remote calls to organize a cluster of caches into smaller sub-groups.
Early versions of JBoss Cache simply wrote cached data to the network by writing to an
ObjectOutputStream
during replication. Over various releases in the JBoss Cache 1.x.x series this approach was gradually
deprecated
in favor of a more mature marshalling framework. In the JBoss Cache 2.x.x series, this is the only officially
supported and recommended mechanism for writing objects to datastreams.
The
Marshaller
interface extends
RpcDispatcher.Marshaller
from JGroups.
This interface has two main implementations - a delegating
VersionAwareMarshaller
and a
concrete
CacheMarshaller300
.
The marshaller can be obtained by calling
CacheSPI.getMarshaller()
, and defaults to the
VersionAwareMarshaller
.
Users may also write their own marshallers by implementing the
Marshaller
interface or extending the AbstractMarshaller
class, and adding it to their configuration
by using the Configuration.setMarshallerClass()
setter.
As the name suggests, this marshaller adds a version
short
to the start of any stream when
writing, enabling similar
VersionAwareMarshaller
instances to read the version short and
know which specific marshaller implementation to delegate the call to.
For example,
CacheMarshaller200
is the marshaller for JBoss Cache 2.0.x.
JBoss Cache 3.0.x ships with
CacheMarshaller300
with an improved wire protocol. Using a
VersionAwareMarshaller
helps achieve wire protocol compatibility between minor
releases but still affords us the flexibility to tweak and improve the wire protocol between minor or micro
releases.
When used to cluster state of application servers, applications deployed in the application tend to put
instances
of objects specific to their application in the cache (or in an
HttpSession
object) which
would require replication. It is common for application servers to assign separate
ClassLoader
instances to each application deployed, but have JBoss Cache libraries referenced by the application server's
ClassLoader
.
To enable us to successfully marshall and unmarshall objects from such class loaders, we use a concept called regions. A region is a portion of the cache which share a common class loader (a region also has other uses - see eviction policies).
A region is created by using the
Cache.getRegion(Fqn fqn, boolean createIfNotExists)
method,
and returns an implementation of the
Region
interface. Once a region is obtained, a
class loader for the region can be set or unset, and the region can be activated/deactivated. By default,
regions
are active unless the
InactiveOnStartup
configuration attribute is set to
true
.