目录
一、jedis对5种java数据类型的存储方式
一个缓存信息包含三个,name缓存名称,key缓存的key值,value缓存的value值。jedis常用操作如下:
public class JedisTest {
private static final Jedis jedis = new Jedis("127.0.0.1", 6379);
@Test
public void test_string() {
jedis.set("name", "zhangsan");
}
@Test
public void test_hash() {
//存放的类似map类型的
jedis.hset("url", "google", "www.google.com");
jedis.hset("url", "taobao", "www.taobao.com");
jedis.hset("url", "baidu", "www.baidu.com");
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "zhangsan");
userInfo.put("age", "35");
userInfo.put("sex", "man");
jedis.hmset("userInfo", userInfo);
String name = jedis.hget("userInfo", "name");
//取多个返回值
List<String> urlList = jedis.hmget("url", "google", "taobao", "baidu");
//取hash所有key值
Map<String, String> userInfoMap = jedis.hgetAll("userInfo");
}
@Test
public void test_list() {
jedis.lpush("charList", "abc");//lpush,在list首部添加元素
jedis.rpush("charList", "def");//rpush,在listw尾部添加元素
//截取list
List<String> charList = jedis.lrange("charList", 0, 1);
jedis.lpop("charList");//在list首部删除元素
jedis.rpop("charList");//在list尾部删除元素
}
@Test
public void test_set() {
jedis.sadd("setMem", "s1");
jedis.sadd("setMem", "s2");
Set<String> sets = jedis.smembers("setMem");
}
@Test
public void test_sort_set() {
jedis.zadd("sortSetMem", 1, "s1");
jedis.zadd("sortSetMem", 2, "s1");
Set<String> sets = jedis.zrange("sortSetMem", 0, 1);
Set<String> revesortSet = jedis.zrevrange("sortSetMem", 0, 1);//反向取
}
}
二、关于redis的一点介绍
具体见redis简介
三、相关代码
1、CacheDefinition
在@Cacheable中没有属性描述缓存有效时间,以及当有个多个相关缓存(比如产品的缓存)的关联性属性,所以提供两个属性,duration和tag。tag属性的作用是将多个缓存关联,可以一起删除这些缓存(利用redis的hash数据类型)。
public class CacheDefinition {
private String name;//缓存名称
private int duration;//缓存有效时间
private String tag;//缓存标签
public String getName() {
return name;
}
public CacheDefinition setName(String name) {
this.name = name;
return this;
}
public int getDuration() {
return duration;
}
public CacheDefinition setDuration(int duration) {
this.duration = duration;
return this;
}
public String getTag() {
return tag;
}
public CacheDefinition setTag(String tag) {
this.tag = tag;
return this;
}
}
2、CacheMapping
定义该注解,用于表示缓存时间和标签。
/**
* 定义一个缓存,支持类级别和方法级别,如果同时存在,类级别覆盖方法级别配置
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CacheMapping {
// 缓存时间(秒)
public int duration() default 60;
// 缓存标签,用于清理缓存
public String tag() default "";
}
3、Storage
定义该接口,提供一些常用的缓存方法。
public interface Storage {
Object get(Object key);
void put(Object key, Object value, int seconds);//seconds,过期时间,单位秒
void evict(Object key);
void replace(Object key, Object value, int seconds);
void destroy();
boolean available();
}
4、RedisStorage
定义了关于Cache的一些信息,比如连接信息(redisStorage)、缓存名称、tag、有效时间等信息;
该类实现了上面的Storage接口,提供了基本的缓存增删改查操作。这里注意可以key、value等值都进行序列化了,方便存储和传输。然后对于redis的hash类型,采用hget/hset方法,第一个参数为缓存名称,第二个参数为缓存key;
另外Jedis只提供了保存string类型的方法,所以我们可以使用可以保存字节数组的BinaryJedisCommands方法。
public class RedisStorage<ResourceType extends BinaryJedisCommands> implements Storage {
//序列化默认为jdk序列化,也可以选择为hessian2/kryo
private Serializer<Object> serializer = new DefaultSerializer();
private Deserializer<Object> deserializer = new DefaultDeserializer();
private Logger log = Logger.getLogger(RedisStorage.class);
private final Pool<ResourceType> pool;
private final List<HostAndPort> hostAndPorts;
public RedisStorage(Pool<ResourceType> pool, List<HostAndPort> hostAndPorts) {
this.pool = checkNotNull(pool);
this.hostAndPorts = checkNotNull(hostAndPorts);
}
//下面方法为Storage接口的方法实现
@Override
public Object get(Object key) {
ResourceType resource = getResource();
byte[] bytes = resource.get(key.toString().getBytes());
if (bytes == null || bytes.length == 0) {
return null;
}
DeserializingConverter dc = new DeserializingConverter(deserializer);
return dc.convert(bytes);//反序列化成对象
}
@Override
public void put(Object key, Object value, int seconds) {
ResourceType resource = getResource();
SerializingConverter sc = new SerializingConverter(serializer);
byte[] bytes = sc.convert(value);
resource.setex(key.toString().getBytes(), seconds, bytes);
}
@Override
public void evict(Object key) {
ResourceType resource = getResource();
if (resource.exists(key.toString().getBytes())) {
resource.del(key.toString().getBytes());
}
}
@Override
public void replace(Object key, Object value, int seconds) {
put(key, value, seconds);
}
@Override
public void destroy() {
if (!pool.isClosed()) {
pool.close();
}
}
//返回增加的条数(如果key存在update,update返回0)
public Long hset(final Object key, final Object field, final Object value, int seconds) {
ResourceType resource = getResource();
SerializingConverter sc = new SerializingConverter(serializer);
byte[] bytes = sc.convert(value);
long result = resource.hset(key.toString().getBytes(), field.toString().getBytes(), bytes);
resource.expire(key.toString().getBytes(), seconds);
return result;
}
public Set<Object> hkeys(String tag) {
ResourceType resource = getResource();
Set<byte[]> bytes = resource.hkeys(tag.getBytes());
Set<Object> stringSet = new HashSet<>();
for (byte[] bytes1 : bytes) {
stringSet.add(new String(bytes1));
}
return stringSet;
}
public Object hget(Object tag, Object key) {
ResourceType resource = getResource();
byte[] bytes = resource.hget(tag.toString().getBytes(), key.toString().getBytes());
if (bytes == null || bytes.length == 0) {
return null;
}
DeserializingConverter dc = new DeserializingConverter(deserializer);
return dc.convert(bytes);//反序列化成对象
}
//标签类缓存,利用hash数据类型(类似map),可以保存某一类的缓存
//hash类型: key(field,value)
public void putCacheToTag(Object tag, Object key, Object value, int duration) {
if (StringUtils.isEmpty(tag)) {
return;
}
Long resultCode = this.hset(tag, key, value, duration);
if (null == resultCode) {
throw new IllegalArgumentException("Fail to put cache to tag");
}
}
//根据标签删除相关缓存
//如果可以通过key删除,该方法可以不要
public void evictTag(String tag) {
if (StringUtils.isEmpty(tag)) {
return;
}
this.evict(tag);
Set<Object> keys = this.hkeys(tag);
log.info("Removed cache tag " + tag);
for (Object k : keys) {
log.info("Removed cache,tag is " + tag + ",key is " + k.toString());
}
}
//检测是否可用
@Override
public boolean available() {
ResourceType resource = getResource();
byte[] bytes = resource.echo("test".getBytes());
if (bytes == null || bytes.length == 0) {
return false;
}
return Arrays.equals(bytes, "test".getBytes());
}
private static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
public Pool<ResourceType> getPool() {
return pool;
}
private ResourceType getResource() {
return pool.getResource();
}
}
5、RedisStorageFactory
创建redisStoage,redis集群信息可以使用类JedisShardInfo。
/**
* 构建jedis连接
* 单个连接 JedisPool
* 集群连接 ShardedJedisPool
*/
public class RedisStorageFactory {
public RedisStorage<? extends BinaryJedisCommands> newStorage(JedisPoolConfig poolConfig, List<JedisShardInfo> shardInfos) {
List<HostAndPort> hostAndPortList = getHostAndPortByShardInfos(shardInfos);
if (shardInfos.size() == 1) {
return new RedisStorage<>(new JedisPool(poolConfig, shardInfos.get(0).getHost(), shardInfos.get(0).getPort(), shardInfos.get(0).getSoTimeout(), shardInfos.get(0).getPassword()), hostAndPortList);
}
ShardedJedisPool shardedJedisPool = new ShardedJedisPool(poolConfig, shardInfos);
return new RedisStorage<>(shardedJedisPool, hostAndPortList);
}
private List<HostAndPort> getHostAndPortByShardInfos(List<JedisShardInfo> shardInfos) {
List<HostAndPort> hostAndPorts = new ArrayList<>();
for (JedisShardInfo shardInfo : shardInfos) {
hostAndPorts.add(new HostAndPort(shardInfo.getHost(), shardInfo.getPort()));
}
return hostAndPorts;
}
}
6、RedisCache
spring Cache接口的实现类,里面的实现方法可以调用storage的方法。
public class RedisCache<ResourceType extends BinaryJedisCommands> implements Cache {
private String name;
private String tag;
private int duration;
private TimeUnit timeUnit;
private RedisStorage<ResourceType> redisStorage;
public RedisCache() {
}
public RedisCache(RedisStorage<ResourceType> redisStorage, String name, int duration, TimeUnit timeUnit) {
this.redisStorage = redisStorage;
this.name = name;
this.duration = duration;
this.timeUnit = timeUnit;
}
public RedisCache(RedisStorage<ResourceType> redisStorage, String name, String tag, int duration, TimeUnit timeUnit) {
this.redisStorage = redisStorage;
this.name = name;
this.duration = duration;
this.timeUnit = timeUnit;
this.tag = tag;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return redisStorage.getPool();
}
@Override
public ValueWrapper get(Object key) {
Object actObject = null;
if (StringUtils.isEmpty(tag)) {
actObject = redisStorage.get(key);
} else {
actObject = redisStorage.hget(tag, key);
}
return actObject == null ? null : new SimpleValueWrapper(actObject);
}
@Override
public <T> T get(Object key, Class<T> aClass) {
ValueWrapper valueWrapper = this.get(key);
if (valueWrapper == null) {
return null;
}
return aClass.cast(valueWrapper.get());
}
@Override
public void put(Object key, Object value) {
if (StringUtils.isEmpty(tag)) {
redisStorage.put(key, value, (int) timeUnit.toSeconds(duration));
return;
}
redisStorage.putCacheToTag(tag, key, value, duration);
}
@Override
public void evict(Object key) {
redisStorage.evict(key);//如果该方法不行,试试evictTag,key为tag
}
@Override
public void clear() {
redisStorage.destroy();
}
}
7、RedisServersManager
该类主要是对redis的服务进行管理。
public class RedisServersManager {
private Logger log = Logger.getLogger(RedisServersManager.class);
//jedis连接分片信息
private List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
protected volatile RedisStorage<? extends BinaryJedisCommands> redisStorage;
protected final static Object REDIS_STORAGE_LOCK = new Object();
protected RedisStorageFactory factory = new RedisStorageFactory();
protected JedisPoolConfig config = new JedisPoolConfig();
/**
* 根据servers生成jedis分片连接池
*/
public void setServers(String servers) {
String[] serversArrays = servers.trim().split(",");
for (String server : serversArrays) {
JedisShardInfo jedisShardInfo = shardInfo(server);
this.shards.add(jedisShardInfo);
log.info("Add a node to redis shard info,node info:" + jedisShardInfo);
}
}
private JedisShardInfo shardInfo(String server) {
String[] texts = server.split("\\^");
JedisShardInfo shard = new JedisShardInfo(texts[0], Integer.parseInt(texts[1]), 1000);
if (texts.length == 3) {
shard.setPassword(texts[2]);
}
return shard;
}
public RedisStorage<? extends BinaryJedisCommands> newStorage() {
if (null == redisStorage) {
synchronized (REDIS_STORAGE_LOCK) {
if (null == redisStorage) {
redisStorage = factory.newStorage(config, shards);
log.info("Successfully created a redis storage.");
}
}
}
return redisStorage;
}
}
8、SpringDataCacheManager
实现CacheManager接口,对cache进行管理。通过ApplicationContextAware接口获取到applicationContext,通过InitializingBean接口在初始化该类的时候把所有有缓存注解信息的放入到集合中。
public class SpringDataCacheManager extends RedisServersManager implements ApplicationContextAware, InitializingBean, CacheManager {
private ApplicationContext applicationContext;
private final Map<String, CacheDefinition> caches = new ConcurrentReferenceHashMap<>();
private int defaultDuration = 60;//默认缓存60秒
private String servers = "127.0.0.1^7100^password,127.0.0.1^6379^password";
//InitializingBean的实现方法
@Override
public void afterPropertiesSet() throws Exception {
setServers(servers);
parseFromContext(applicationContext);
}
private void parseFromContext(final ApplicationContext context) {
String[] beanNames = context.getBeanNamesForType(Object.class);
for (final String beanName : beanNames) {
final Class<?> classType = context.getType(beanName);
//查询该类是否有service和repository注解
Service service = findAnnotation(classType, Service.class);
Repository repository = findAnnotation(classType, Repository.class);
if (service == null && repository == null) {
continue;
}
ReflectionUtils.doWithMethods(classType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
ReflectionUtils.makeAccessible(method);
CacheMapping cacheMapping = findCacheMapping(method, classType);//先找类级别,没有再找当前方法级别
Cacheable cacheable = findAnnotation(method, Cacheable.class);
String[] cacheNames = getCacheNamesWithCacheable(cacheable);
for (String cacheName : cacheNames) {
CacheDefinition cacheDefinition = new CacheDefinition()
.setName(cacheName)
.setDuration(cacheMapping.duration())
.setTag(cacheMapping.tag());
caches.put(cacheName, cacheDefinition);
}
}
}, new ReflectionUtils.MethodFilter() {
public boolean matches(Method method) {
return !method.isSynthetic() && findAnnotation(method, Cacheable.class) != null;
}
});
if (context.getParent() != null) {
parseFromContext(context.getParent());
}
}
}
private CacheMapping findCacheMapping(Method method, Class<?> handlerType) {
CacheMapping cacheMapping = findAnnotation(handlerType, CacheMapping.class);
if (cacheMapping == null) {//如果类注解没有,再从方法注解查询
cacheMapping = findAnnotation(method, CacheMapping.class);
}
if (cacheMapping == null) {
throw new IllegalStateException("No cache mapping (@CacheMapping) could be detected on '" +
method.toString() + "'. Make sure to set the value parameter on the annotation or " +
"declare a @CacheMapping at the class-level with the default cache cacheMapping to use.");
}
return cacheMapping;
}
private String[] getCacheNamesWithCacheable(Cacheable cacheable) {
return cacheable.value();
}
//CacheManager的实现方法,该方法每次调用cache的get方法之前会调用一次
@Override
public Cache getCache(String name) {
CacheDefinition cacheDefinition = caches.get(name);
if (null != cacheDefinition) {
return new RedisCache<>(newStorage(), name, cacheDefinition.getTag(), cacheDefinition.getDuration(), TimeUnit.SECONDS);
}
return new RedisCache<>(newStorage(), name, defaultDuration, TimeUnit.SECONDS);
}
@Override
public Collection<String> getCacheNames() {
return caches.keySet();
}
//ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
9、Service类
@Service
public class PersonService2 {
@Cacheable(value = "getPersonByName", key = "'PersonService.getPersonByName'+#name")
@CacheMapping(duration = 20, tag = "PERSON")
public Person getPersonByName(String name) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
System.out.println("调用了Service方法");
return getFromDB(name);
}
private Person getFromDB(String name) {
System.out.println("从数据库查询了");
return new Person();
}
}
10、测试类
public class PersonCacheTest {
private PersonService2 personService2;
@Before
public void setUp() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans/application_redis_cache.xml");
personService2 = context.getBean("personService2", PersonService2.class);
}
@Test
public void testGetPersonByName() {
System.out.println("第一次查询………………");
personService2.getPersonByName("张三");
System.out.println("第二次查询………………");
personService2.getPersonByName("李四");
System.out.println("第三次查询………………");
personService2.getPersonByName("张三");
}
}
输出
第一次查询………………
调用了Service方法
从数据库查询了
第二次查询………………
调用了Service方法
从数据库查询了
第三次查询………………
第三次没有从数据库查询了。
四、总结
1、BinaryJedisCommands接口可以存储字节数组,存储的key/value建议先转化成字节数组然后再序列化;
2、单服务jedis使用到连接池接口有JedisPoolConfig/JedisPool,集群的对应有JedisPoolConfig/JedisShardInfo/ShardedJedisPool;
3、实现spring Cache需要实现Cache接口和CacheManage接口(cache接口提供增删改查,cacheManage接口对cache进行管理)。