JA-SIG
Home
About uPortal
Documentation
Getting Started
Developers
Implementors
Users
Background
Release
|
uPortal Concurrency Services
Contents
Introduction
Goals and Rationale
Package Structure
Caching API
Locking API
Using the
Caching and Locking Services
Notes on Service
Implementations
Introduction
The concurrency packages, org.jasig.portal.concurrency.*, are
offered as building blocks for portal developers who want to create
persistence support for portal entities. The locking and caching
packages are self-contained “mini-frameworks” exposed as portal
services via classes in org.jasig.portal.services.
The concurrency framework operates on entities, persistent objects
uniquely identified by a type and a key. As currently implemented,
these packages cache entities in memory for fast access and lock
entities for either reading or writing. Perhaps in the future, we
will abstract the operations of creating, finding and updating them.
Goals and Rationale
The need for these services arose because of performance
problems in the groups framework. Groups are relatively expensive
to create and are frequently reused. They needed to be cached,
and since they are updatable, they also needed to be locked.
Caching and locking objects in a single JVM is fairly simple, but it
quickly gets complicated when the caching and locking must work across
multiple JVMs and servers.
There are some decent commercial caching products available,
but they are not appropriate for an open source project.
Unfortunately, the open source solutions I looked at were too big, too
ugly, too early or just not right (I know, picky, picky.) I think
the most promising is Turbine’s Java Caching System or JCS (see http://jakarta.apache.org/turbine/jcs),
an approximate implementation of the JCache specification (see http://jcp.org/jsr/detail/107.jsp),
which in turn, describes the Oracle9i caching framework. JCS
promises to be an efficient, full-featured framework, but it is still in
development and it does not do locking. So, at least in the
interim, uPortal needs some home grown locking and caching
support. The concurrency packages are intended as a common
solution that can be used not just by groups but by any type of portal
entity and replaced when a better alternative is found. They are
not intended to replace or compete with full-featured commercial or open
source frameworks.
Package Structure
The concurrency services operate on IBasicEntities, objects
that implement org.jasig.portal.IBasicEntity , defined as
follows:
public interface IBasicEntity {
public org.jasig.portal.EntityIdentifier
getEntityIdentifier();
}
An EntityIdentifier has a key and type that uniquely identify
its underlying entity. Two EntityIdentifiers with the same key
and type refer to the same underlying entity (although they may belong
to different IBasicEntities.)
The package org.jasig.portal.concurrency defines
some basic types to support concurrency: a lock (IEntityLock), a cache
(IEntityCache), and services for locking and caching (IEntityLockService
and IEntityCachingService). It also contains the locking and
caching Exceptions:
org.jasig.portal.concurrency
IEntityCache
IEntityCachingService
IEntityCachingServiceFactory
IEntityLock
IEntityLockService
IEntityLockServiceFactory
CachingException
LockingException
The individual locking and caching packages contain reference
implementations of the basic types, plus some supporting classes.
org.jasig.portal.concurrency.caching
CachedEntityInvalidation
LRUCache
RDBMCachedEntityInvalidationStore
ReferenceEntityCache
ReferenceEntityCachingService
ReferenceEntityCachingServiceFactory
ReferenceInvalidatingEntityCache
org.jasig.portal.concurrency.locking
EntityLockImpl
IEntityLockStore
MemoryEntityLockStore
RDBMEntityLockStore
ReferenceEntityLockService
ReferenceEntityLockServiceFactory
Caching
API
IEntityCachingService defines a minimal api for caching and
retrieving IBasicEntities. An IEntityCachingService manages a
number of IEntityCaches. Each IEntityCache holds IBasicEntities of
a given type, (the type returned by IEntityCache.getEntityType() ).
This type must be known to the portal (see org.jasig.portal.EntityTypes .)
In a multi-server environment, caches must be able to synchronize
themselves with their peers on other servers, but this is part of their
implementation and not part of the api:
public interface IEntityCachingService {
void add(IBasicEntity entity) throws
CachingException;
IBasicEntity get(Class type,
String key);
void remove(Class type, String
key);
void update(IBasicEntity
entity) throws CachingException;
}
Locking
API
The interface IEntityLockService defines an api for acquiring
lock objects, IEntityLocks, that can be used to control concurrent
access to IBasicEntities. The sub-type of IBasicEntity must be
known to the portal (see org.jasig.portal.EntityTypes .)
A lock is associated with a particular entity and has an owner, a
lockType and an expirationTime. Currently supported lock types are
IEntityLockService.READ_LOCK , which is shared, and IEntityLockService.WRITE_LOCK ,
which is exclusive. To lock an entity for update, ask the
service for a write lock:
int lockType = IEntityLockService.WRITE_LOCK;
IBasicEntity entity = getEntityFromSomewhere();
IEntityLock lock = svc.newLock(entity, lockType,
lockOwner);
If there is no conflicting lock on the entity, the service
responds with the requested lock. If another lock owner holds a
conflicting lock, the service throws a LockingException. If the
service returns the lock, no other client will get be able to get a
conflicting lock. From then on, communication with the service is via
the lock:
lock.convert(int newType);
lock.isValid();
lock.release();
lock.renew();
The complete service api is as follows:
public interface IEntityLockingService {
public void convert(IEntityLock lock, int newType) throws
LockingException;
public void convert(IEntityLock lock, int newType,
int durationSecs)
throws LockingException;
public boolean existsInStore(IEntityLock lock) throws
LockingException;
public boolean isValid(IEntityLock lock) throws
LockingException;
public IEntityLock newLock (IBasicEntity
entity, int lockType, String owner)
throws LockingException;
public IEntityLock newLock (IBasicEntity
entity, int lockType, String owner, int
durationSecs) throws
LockingException;
public void release(IEntityLock lock) throws
LockingException;
public void renew(IEntityLock lock) throws
LockingException;
public void renew(IEntityLock lock, int
durationSecs)
throws LockingException;
}
Using the Caching and Locking Services
The caching and locking APIs are accessed by portal clients
via service façades located in org.jasig.portal.services ,
EntityCachingService and EntityLockService. These classes serve
as bootstraps for service implementations specified in
portal.properties. One way to plug in an external locking or
caching service would be to create an alternate implementation of the
service that adapts the external service to the portal interface.
The caching façade simply duplicates the api of the
service interface. Caching consists of asking the service to add,
retrieve, update and remove elements from the cache, e.g.:
// Retrieve the entity from its store:
String key = getEntityKey();
IBasicEntity ent = findEntity(key);
// Cache the entity:
EntityCachingService.add(ent);
...
// Retrieve the entity from the cache:
Class type = getEntityClass();
IBasicEntity anotherReferenceToTheEntity =
EntityCachingService.get(type, key);
...
// Change the entity and then notify the cache:
EntityCachingService.update(ent); //
notifies peer caches.
// Or delete the entity and notify the cache:
EntityCachingService.remove(type, key); // notifies peer
caches.
The EntityLockService façade lets clients do one single
thing, acquire a lock on an entity:
IBasicEntity entity =
getEntityFromSomewhere(type, key);
String owner = getThePortalUserId();
IEntityLock lock = EntityLockService.instance().newWriteLock(entity,
owner);
Or perhaps:
IEntityLock lock = EntityLockService.instance(). newWriteLock(entity,
owner, durationSecs);
Once the client has the lock, it communicates with the
underlying service implementation via the lock:
lock.convert(IEntityLockService.WRITE_LOCK); //
convert read to write
...
if ( lock.isValid() ) // check if lock has expired
{
entity.update();
lock.release();
}
Notes on Service Implementations
Common properties.
The portal.properties file has a section labelled "Concurrency
Services settings" with values for configurable concurrency
properties. There are 2 properties common to concurrency
services, multi-server, which
indicates if the portal will run in multiple JVMs, and clockTolerance, which can be set to
account for differences among system clocks on different hosts:
# Concurrency Services settings:
#
# multiServer (true/false) indicates if the portal will run in
multiple jvms.
#
# clockTolerance (in milliseconds) sets a fudge factor to account
for system clocks
# on different hosts. Only used when
org.jasig.portal.concurrency.multiServer=true.
#
# Defaults: multiServer=false
#
clockTolerance=5000
#
org.jasig.portal.concurrency.multiServer=false
org.jasig.portal.concurrency.clockTolerance=5000
These properties apply to both concurrency services. The full
list of properties is:
Service
|
Property
|
Description
|
concurrency
|
multiServer
|
Indicates if the
portal will run in multiple JVMs. Default=false.
|
concurrency
|
clockTolerance
|
Fudge factor in
milliseconds for system clocks. Only used when
multiServer=true. Default=5000.
|
Locking |
IEntityLockServiceFactory
|
Service
factory. Default=ReferenceEntityLockServiceFactory.
|
Locking
|
defaultLockDuration
|
Default value in
seconds, may be overridden at request time. Default=300.
|
Caching
|
IEntityCachingServiceFactory
|
Service
factory. Default=ReferenceEntityCachingServiceFactory
|
Caching
|
defaultMaxCacheSize
|
Default for maximum
number of cache entries. Can be overriden for a specific cache
type. Default=1000.
|
Caching
|
maxCacheSize
|
(Optional) Maximum
number of cache entries for a specific cache. See
portal.properties for examples.
|
Caching
|
defaultSweepInterval |
Default in seconds
for interval between sweeps. Can be overridden for a specific
cache type. Default=60.
|
Caching
|
sweepInterval
|
(Optional) Interval
in seconds between sweeps for a specific cache. See
portal.properties for examples.
|
Caching
|
defaultMaxIdleTime
|
Default in seconds
for the maximum time a cache entry can remain untouched before being
reaped. Can be overridden for a specific cache type.
Default=1800.
|
Caching
|
maxIdleTime
|
(Optional) Maximum
time in seconds a cache entry can remain untouched before being reaped,
for a specific cache. See portal.properties for examples.
|
Single-server vs. multi-server. In single-server
mode, locks are held in memory. There is no need to invalidate
entries in peer caches because there are no peer caches. In
multi-server mode, locks are stored in the portal database, which is
shared by all portal instances. Each time a cache entry is updated
or removed, the cache adds an invalidation to the invalidation store,
which is likewise held in the portal database. Caches periodically
retrieve invalidations from the store and purge invalid entries.
Multi-server mode and pooled database connections.
If the portal is running on a single server, concurrency services
should run in single-server mode, since running in multi-server mode
imposes extra database activity. However, if the portal is running
in a multi-server environment, you must run in multi-server mode for
locking and caching to function properly. In multi-server mode,
these services make frequent hits to the portal database using
connections obtained from org.jasig.portal.RDBMServices .
As a result, RDBMServices must have a source of pooled database
connections, or else the concurrency services themselves will become
performance bottlenecks.
The Cache Cleanup Cycle. Each cache instance
runs a cleanup thread that periodically wakes up and invokes a cleanup
routine to purge the cache of stale entries. The interval between
cleanups is configurable in portal.properties. In multi-server
mode, the cache first retrieves and processes its invalidations.
In either case, if the cache is larger than its maximum size, the
cleanup routine removes least recently used cache entries until the
cache no longer exceeds its maximum size.
Future enhancements. Caches are now created as
needed. Perhaps they should be created and pre-populated on
service start-up, kicked off by an entry in services.xml. The
caching properties might be better represented in xml format.
de 9/02/2003
|