前言
Java
缓存实现方案有很多,最基本的自己使用Map
去构建缓存,再高级点的使用Ehcache
或者Goolge
的guava
作为内存缓存框架,Ehcache
可以满足单机缓存的需求(Ehcache
的具体使用在我过往的文章中有所介绍),如果我们是多台机子共用缓存数据的话,Ehcache
可通过rmi
,jgroup
,jms
的方式实现,但是实用性与操作性不高且复杂,现时大部分应用仅用Ehcache
作为单机缓存使用,这时候我们可以通过搭建缓存服务器解决多机使用的问题,常见的缓存服务器有Memcached
,Redis
等。
现时业界主流大多使用Redis
。所以本文主要介绍在Java
中如何使用Redis
。至于如何搭建Redis
,我在过往的文章中已有所介绍,不知道如何搭建的同学,可以参考我过往的文章,下文所用到相关的Redis
信息均为搭建教程中的信息。
PS:文章中所用到的示例代码,部分参考至开源项目iBase4J,特此声明。
Java连接Redis
Java
连接Redis
官方推荐的是使用Jedis
和Redisson
进行连接操作,Spring
对Redis
有很好的支持,所以此文我结合Spring
中的Spring Data
对Redis
进行操作。
1. maven引用
org.springframework.data
spring-data-redis
1.8.7.RELEASE
redis.clients
jedis
2.9.0
org.redisson
redisson
3.5.5
2. 建立Redis配置文件
在classpath
下建立Redis配置文件redis.properties
。
如果同学们是搭建Redis
高可用架构,是通过向外提供VIP
虚拟IP的方式连接Redis
,则只需在配置文件中将redis.host=172.16.2.185
单机IP改为VIP
虚拟IPredis.host=172.16.2.250
即可实现Redis
高可用,而不需要使用Spring
提供的RedisSentinel
方案实现Redis
高可用。
#VIP
#redis.host=172.16.2.250
redis.host=172.16.2.185
redis.port=6379
redis.password=123456
#最小空闲数
redis.minIdle=2
#最大空闲数
redis.maxIdle=10
#最大连接数
redis.maxTotal=1000
#最大建立连接等待时间
redis.maxWaitMillis=3000
#客户端超时时间单位是毫秒
redis.timeout=120000
#明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
redis.expiration=600
3. 与Spring结合
3.1 配置Jedis实现
在classpath
下建立文件夹spring
用于存放所有与spring
相关的配置文件。在spring
文件夹下建立spring-redis.xml
,主要用于注入Jedis
。
3.2 配置Redisson实现
在spring
文件夹下建立spring-redisson.xml
,主要用于注入Redisson
4. 代码实现
4.1 定义获取缓存的工具类CacheUtil
这个类主要是用于获取缓存管理器,因为Jedis
封装Redis
基本操作的接口比较友好,所以基本操作使用Jedis
实现,但是将Redis
当做分布式锁使用时,如果是自行用Jedis
中的setNX + 时间戳过程方法实现的话,会略显复杂,还可能写的不严谨,存在原子性操作或者死锁等问题。此处的分布式锁实现使用Redisson
帮我们封装好的方法实现加锁与解锁,顺便提一句,Redisson
加锁操作是使用lua
脚本一次执行加锁与设置过期的操作的,所以不存在原子性问题。这处暂时不展开讨论分布式锁的问题,日后有空再和大家一同探讨分布式锁的问题。
package com.easylink.mall.core.cache.redis;
import com.easylink.mall.core.support.util.PropertiesFileUtil;
public class CacheUtil {
/**
* 缓存管理器,主要执行缓存操作
*/
private static CacheManager cacheManager;
/**
* 锁管理器,主要执行加锁与解锁操作
*/
private static CacheManager lockManager;
public static void setCacheManager(CacheManager cacheManager) {
CacheUtil.cacheManager = cacheManager;
}
public static void setLockManager(CacheManager cacheManager) {
CacheUtil.lockManager = cacheManager;
}
public static CacheManager getCache() {
return cacheManager;
}
public static CacheManager getLockManager() {
return lockManager;
}
/** 获取锁 */
public static boolean tryLock(String key) {
int expires = 1000 * PropertiesFileUtil.getInstance("redis.properties").getInt("redis.lock.expires", 180);
return lockManager.setnx(key, expires);
}
/** 获取锁 */
public static boolean getLock(String key) {
return lockManager.lock(key);
}
/** 解锁 */
public static void unlock(String key) {
lockManager.unlock(key);
}
}
4.2 定义缓存操作管理接口CacheManager
package com.easylink.mall.core.cache.redis;
import java.io.Serializable;
import java.util.Set;
/**
* 缓存操作管理接口
*
* @author Ben.
*
*/
public interface CacheManager {
/**
* 根据key获取对象
*
* @param key
* @return
*/
Object get(final String key);
/**
* 根据正则表达式获取对象
*
* @param pattern
* 正则表达式
* @return
*/
Set
4.3 定义RedisHelper
基于Spring
的RedisTemplate
实现CacheManager
接口,主要用于对缓存的基本操作,不用于分布式锁作用,此处的分布式锁实现不严谨,不当做参考
/**
* Redis缓存辅助类
*
* @author ShenHuaJie
* @version 2016年4月2日 下午4:17:22
*/
public final class RedisHelper implements CacheManager {
private static final Logger logger = Logger.getLogger(RedisHelper.class);
private RedisSerializer keySerializer;
private RedisSerializer
4.4 定义RedissonHelper
基于Redisson
实现CacheManager
接口,主要用于实现基于Redis
的分布式锁
package com.easylink.mall.core.cache.redisson;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RBucket;
import org.redisson.api.RType;
import org.redisson.api.RedissonClient;
import com.easylink.mall.core.cache.redis.CacheManager;
import com.easylink.mall.core.cache.redis.CacheUtil;
import com.easylink.mall.core.support.util.PropertiesFileUtil;
/**
*
* Redis缓存辅助类
*
*/
public class RedissonHelper implements CacheManager {
private RedissonClient redissonClient;
private final Integer EXPIRE = PropertiesFileUtil.getInstance("redis.properties").getInt("redis.expiration");
public void setRedissonClient(Client client) {
this.redissonClient = client.getRedissonClient();
CacheUtil.setLockManager(this);
}
private RBucket getRedisBucket(String key) {
return redissonClient.getBucket(key);
}
@Override
public final Object get(final String key) {
RBucket temp = getRedisBucket(key);
expire(temp, EXPIRE);
return temp.get();
}
@Override
public final void set(final String key, final Serializable value) {
RBucket temp = getRedisBucket(key);
temp.set(value);
expire(temp, EXPIRE);
}
@Override
public final void set(final String key, final Serializable value, int seconds) {
RBucket temp = getRedisBucket(key);
temp.set(value);
expire(temp, seconds);
}
public final void multiSet(final Map temps) {
redissonClient.getBuckets().set(temps);
}
@Override
public final Boolean exists(final String key) {
RBucket temp = getRedisBucket(key);
return temp.isExists();
}
@Override
public final void del(final String key) {
redissonClient.getKeys().delete(key);
}
@Override
public final void delAll(final String pattern) {
redissonClient.getKeys().deleteByPattern(pattern);
}
@Override
public final String type(final String key) {
RType type = redissonClient.getKeys().getType(key);
if (type == null) {
return null;
}
return type.getClass().getName();
}
/**
* 在某段时间后失效
*
* @return
*/
private final void expire(final RBucket bucket, final int seconds) {
bucket.expire(seconds, TimeUnit.SECONDS);
}
/**
* 在某个时间点失效
*
* @param key
* @param unixTime
* @return
*
*/
@Override
public final Boolean expireAt(final String key, final long unixTime) {
return redissonClient.getBucket(key).expireAt(new Date(unixTime));
}
@Override
public final Long ttl(final String key) {
RBucket rBucket = getRedisBucket(key);
return rBucket.remainTimeToLive();
}
@Override
public final Object getSet(final String key, final Serializable value) {
RBucket rBucket = getRedisBucket(key);
return rBucket.getAndSet(value);
}
@Override
public Set getAll(String pattern) {
Set set = new HashSet();
Iterable keys = redissonClient.getKeys().getKeysByPattern(pattern);
for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
String key = iterator.next();
set.add(getRedisBucket(key).get());
}
return set;
}
@Override
public Boolean expire(String key, int seconds) {
RBucket bucket = getRedisBucket(key);
expire(bucket, seconds);
return true;
}
@Override
public void hset(String key, Serializable field, Serializable value) {
redissonClient.getMap(key).put(field, value);
}
@Override
public Object hget(String key, Serializable field) {
return redissonClient.getMap(key).get(field);
}
@Override
public void hdel(String key, Serializable field) {
redissonClient.getMap(key).remove(field);
}
public void sadd(String key, Serializable value) {
redissonClient.getSet(key).add(value);
}
public Set sall(String key) {
return redissonClient.getSet(key).readAll();
}
public boolean sdel(String key, Serializable value) {
return redissonClient.getSet(key).remove(value);
}
@Override
public boolean lock(String key) {
return redissonClient.getLock(key).tryLock();
}
@Override
public void unlock(String key) {
redissonClient.getLock(key).unlock();
}
@Override
public boolean setnx(String key, Serializable value) {
try {
return redissonClient.getLock(key).tryLock(Long.valueOf(value.toString()), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
@Override
public Long incr(String key) {
return null;
}
@Override
public void setrange(String key, long offset, String value) {
}
@Override
public String getrange(String key, long startOffset, long endOffset) {
return null;
}
}
4.5 定义Redisson客户端配置实现类
package com.easylink.mall.core.cache.redisson;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.config.SingleServerConfig;
/**
* Redis连接配置
*
* @author ShenHuaJie
* @since 2017年8月23日 上午9:36:53
*/
public class Client {
/**
* Redis server address
*
*/
private String address;
/**
* Password for Redis authentication. Should be null if not needed
*/
private String password;
/**
* Redis cluster node urls list
*/
private Set nodeAddresses = new HashSet();
/**
* Redis master server address
*/
private String masterAddress;
/**
* Redis slave servers addresses
*/
private Set slaveAddresses = new HashSet();
private RedissonClient redissonClient;
public void init() {
Config config = new Config();
if (StringUtils.isNotBlank(address)) {
SingleServerConfig serverConfig = config.useSingleServer().setAddress(address);
if (StringUtils.isNotBlank(password)) {
serverConfig.setPassword(password);
}
} else if (!nodeAddresses.isEmpty()) {
ClusterServersConfig serverConfig = config.useClusterServers()
.addNodeAddress(nodeAddresses.toArray(new String[] {}));
if (StringUtils.isNotBlank(password)) {
serverConfig.setPassword(password);
}
} else if (masterAddress != null && !slaveAddresses.isEmpty()) {
MasterSlaveServersConfig serverConfig = config.useMasterSlaveServers().setMasterAddress(masterAddress)
.addSlaveAddress(slaveAddresses.toArray(new String[] {}));
if (StringUtils.isNotBlank(password)) {
serverConfig.setPassword(password);
}
}
this.redissonClient = Redisson.create(config);
}
public RedissonClient getRedissonClient() {
return redissonClient;
}
public void setAddress(String address) {
this.address = address;
}
public void setPassword(String password) {
this.password = password;
}
public void setNodeAddresses(String nodeAddresse) {
if (nodeAddresse != null) {
String[] nodeAddresses = nodeAddresse.split(",");
for (int i = 0; i < nodeAddresses.length; i++) {
if (StringUtils.isNotEmpty(nodeAddresses[i])) {
this.nodeAddresses.add(nodeAddresses[i]);
}
}
}
}
public void setMasterAddress(String masterAddress) {
this.masterAddress = masterAddress;
}
public void setSlaveAddresses(String slaveAddresse) {
if (slaveAddresse != null) {
String[] slaveAddresses = slaveAddresse.split(",");
for (int i = 0; i < slaveAddresses.length; i++) {
if (StringUtils.isNotEmpty(slaveAddresses[i])) {
this.slaveAddresses.add(slaveAddresses[i]);
}
}
}
}
}
4.6 定义properties配置文件读取工具类
package com.easylink.mall.core.support.util;
import java.util.HashMap;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.log4j.Logger;
/**
* 资源文件读取工具
*
* @author Ben.
*
*/
public class PropertiesFileUtil {
private static Logger logger = Logger.getLogger(PropertiesFileUtil.class);
// 当打开多个资源文件时,缓存资源文件
private static HashMap configMap = new HashMap();
// 默认资源文件名称
private static final String NAME = "config.properties";
// 私有构造方法,创建单例
private PropertiesFileUtil() {
}
public static synchronized PropertiesConfiguration getInstance() {
return getInstance(NAME);
}
public static synchronized PropertiesConfiguration getInstance(String name) {
PropertiesConfiguration propertiesConfiguration = configMap.get(name);
if (propertiesConfiguration == null) {
Configurations configs = new Configurations();
try {
propertiesConfiguration = configs.properties(name);
} catch (ConfigurationException e) {
logger.error("can not load properties file,name : " + name);
}
configMap.put(name, propertiesConfiguration);
}
return propertiesConfiguration;
}
}
5. 测试
编写单元测试用例,测试是否搭建成功
5.1 测试基本操作
package com.easylink.mall.core.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.easylink.mall.core.cache.redis.CacheUtil;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-redis.xml")
public class JedisTest {
@Test
public void execute() {
CacheUtil.getCache().set("test-jedis", "test-value");
Object testValue = CacheUtil.getCache().get("test-jedis");
System.out.println(String.valueOf(testValue));
}
}
5.2 测试分布式锁
package com.easylink.mall.core.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.easylink.mall.core.cache.redis.CacheUtil;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-redisson.xml")
public class RedissonTest {
@Test
public void execute() {
// 加锁
CacheUtil.getLockManager().lock("test-jedis");
// 解锁
CacheUtil.getLockManager().unlock("test-jedis");
}
}
总结
至此,上述过程已经说明了如何在JAVA
中使用Redis
进行一些缓存的基本操作或者是当作分布式锁去使用。内容比较简单,基础。但是适用于初学者去学习,毕竟先学会入门的使用,然后再对其的某些功能或者特性去深入研究,这样能让自己更好的去学习一种技术。楼主由于太久没有更新文章,所以先写一篇简单的找一下感觉。迟点再和大家一同探究Redis
的一些知识和常见问题,如:基础的数据结构,缓存和数据库一致性问题,缓存雪崩问题,缓存击穿问题等。谢谢大家的支持,如果此文对你有所帮助,请点个赞,谢谢。