Subscribe / Unsubscribe Enewsletters | Login | Register

Pencil Banner

Lightning fast NoSQL with Spring Data Redis

Dr. Xinyu Liu | May 25, 2016
6 uses cases for Redis in server-side Java applications

Using Redis with Elasticache

Amazon Elasticache is an in-memory cache service that can be combined with either Memcached or Redis as a cache server. While Elasticache is out the scope of this article, I'd like to offer a tip to developers using Elasticache with Redis. While we can live with the default values of most Redis parameters, the default Redis settings for tcp-keepalive and timeout don't remove dead client connections, and could eventually exhaust the sockets on the cache server. Always set these two values explicitly when using Redis with Elasticache.

Use cases for Redis as a database

Now let's look at a variety of ways that you can use Redis as a database in server-side Java EE systems. Whether the use case is simple or complex, Redis can help you achieve performance, throughput, and latency that would be formidable to a normal Java EE technology stack.

1. Globally unique incremental counter

This is a relatively simple use case to start with: an incremental counter that displays how many hits a website receives. Spring Data Redis offers two classes that you can use for this utility: RedisAtomicInteger and RedisAtomicLong. Unlike AtomicInteger and AtomicLong in the Java concurrency package, these Spring classes work across multiple JVMs.

Listing 3. Globally unique increment counter


RedisAtomicLong counter = 
	new RedisAtomicLong("UNIQUE_COUNTER_NAME", redisTemplate.getConnectionFactory()); 
Long myCounter = counter.incrementAndGet();    // return the incremented value 

Watch out for integer overflow and remember that operations on these two classes are relatively expensive.

2. Global pessimistic lock

From time to time you will need to deal with contention in a server cluster. Say you're running a scheduled job from a server cluster. Without a global lock, nodes in the cluster will launch redundant job instances. In the case of a chat room partition, you might have a capacity of 50. When that chat room is full, you need to create a new chat room instance to accommodate the next 50.

Detecting a full chat room without a global lock could lead each node in the cluster to create its own chat-room instance, making the whole system unpredictable. Listing 4 shows how to leverage the SETNX (SET if Not eXists) Redis command to implement a global pessimistic lock.

Listing 4. Global pessimistic locking


public String aquirePessimisticLockWithTimeout(String lockName,
			int acquireTimeout, int lockTimeout) {
		if (StringUtils.isBlank(lockName) || lockTimeout <= 0)
			return null;
		final String lockKey = lockName;
		String identifier = UUID.randomUUID().toString(); 
		Calendar atoCal = Calendar.getInstance();
		atoCal.add(Calendar.SECOND, acquireTimeout);
		Date atoTime = atoCal.getTime();

		while (true) {
			// try to acquire the lock
			if (redisTemplate.execute(new RedisCallback<Boolean>() {
				@Override
				public Boolean doInRedis(RedisConnection connection)
						throws DataAccessException {
					return connection.setNX(
redisTemplate.getStringSerializer().serialize(lockKey), redisTemplate.getStringSerializer().serialize(identifier));
				}
			})) { 	// successfully acquired the lock, set expiration of the lock
				redisTemplate.execute(new RedisCallback<Boolean>() {
					@Override
					public Boolean doInRedis(RedisConnection connection)
							throws DataAccessException {
						return connection.expire(redisTemplate
								.getStringSerializer().serialize(lockKey),
								lockTimeout);
					}
				});
				return identifier;
			} else { // fail to acquire the lock
				// set expiration of the lock in case ttl is not set yet.
				if (null == redisTemplate.execute(new RedisCallback<Long>() {
					@Override
					public Long doInRedis(RedisConnection connection)
							throws DataAccessException {
						return connection.ttl(redisTemplate
								.getStringSerializer().serialize(lockKey));
					}
				})) {
					// set expiration of the lock
					redisTemplate.execute(new RedisCallback<Boolean>() {
						@Override
						public Boolean doInRedis(RedisConnection connection)
								throws DataAccessException {
							return connection.expire(redisTemplate
								.getStringSerializer().serialize(lockKey),
									lockTimeout);
						}
					}); 
}
				if (acquireTimeout < 0) // no wait
					return null;
				else {
					try {
						Thread.sleep(100l); // wait 100 milliseconds before retry
					} catch (InterruptedException ex) {
					}
				}
				if (new Date().after(atoTime))
					break;
			}
		}
		return null;
	}

	public void releasePessimisticLockWithTimeout(String lockName, String identifier) {
		if (StringUtils.isBlank(lockName) || StringUtils.isBlank(identifier))
			return;
		final String lockKey = lockName;

		redisTemplate.execute(new RedisCallback<Void>() {
					@Override
					public Void doInRedis(RedisConnection connection)
							throws DataAccessException {
						byte[] ctn = connection.get(redisTemplate
								.getStringSerializer().serialize(lockKey));
						if(ctn!=null && identifier.equals(redisTemplate.getStringSerializer().deserialize(ctn)))
							connection.del(redisTemplate.getStringSerializer().serialize(lockKey));
						return null;
					}
				});
	}	 

 

Previous Page  1  2  3  4  5  6  7  Next Page 

Sign up for CIO Asia eNewsletters.