redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
本文介绍在Spring中使用Jedis做缓存,将部分db中的数据缓存在redis中,优先从redis中获取,以提高性能。
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<exclusions>
<exclusion>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
exclusion>
exclusions>
<version>1.7.1.RELEASEversion>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
dependency>
配置在setting.properties文件中,在spring.xml中通过
引入。
##redis连接配置
redis.host=127.0.0.1
redis.port=6379
redis.pass=admineap
redis.database=0
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true
#当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
redis.timeout=10000
这部分配置了redis的基本配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.pass}"/>
<property name="database" value="${redis.database}"/>
<property name="timeout" value="${redis.timeout}"/>
<property name="poolConfig" ref="poolConfig"/>
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
bean>
beans>
package com.cnpc.framework.base.dao;
import java.util.List;
import java.util.Set;
/**
* Created by billJiang on 2017/4/10.
* e-mail:[email protected] qq:475572229
*/
public interface RedisDao {
boolean add(final String key, final T obj);
/**
* setNx
*
* @param key
* @param value
* @return
*/
boolean add(final String key, final String value);
boolean add(final String key, final List list);
void delete(final String key);
void delete(final List keys);
boolean update(final String key, final T obj);
boolean update(final String key, final String value);
/**
* 保存 不存在则新建,存在则更新
*
* @param key
* @param value
* @return
*/
boolean save(final String key, final String value);
boolean save(final String key, final T obj);
T get(final String key, final Class clazz);
List getList(final String key, final Class clazz);
byte[] getByte(final String key);
String get(final String key);
void add(final String key, final long timeout, final T obj);
void add(final String key, final long timeout, final byte[] object);
Set keys(String pattern);
boolean exist(final String key);
boolean set(final String key,final byte[] value);
boolean flushDB();
long dbSize();
}
package com.cnpc.framework.base.dao.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.BooleanCodec;
import com.cnpc.framework.base.dao.RedisDao;
import com.cnpc.framework.utils.StrUtil;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Created by billJiang on 2017/4/10.
* e-mail:[email protected] qq:475572229
* redis操作实体类
*/
@Service("redisDao")
public class RedisDaoImpl implements RedisDao {
@Resource
protected RedisTemplate redisTemplate;
/**
* 设置redisTemplate
*
* @param redisTemplate the redisTemplate to set
*/
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
/**
* 获取 RedisSerializer
*/
private RedisSerializer getRedisSerializer() {
return redisTemplate.getStringSerializer();
}
@Override
public boolean add(final String key, final T obj) {
return add(key, JSON.toJSONString(obj));
}
@Override
public void add(final String key, final long timeout, final T obj) {
redisTemplate.execute(new RedisCallback() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
final byte[] object = serializer.serialize(JSON.toJSONString(obj));
add(key,timeout,object);
return null;
}
});
}
@Override
public void add(final String key, final long timeout,final byte[] object) {
redisTemplate.execute(new RedisCallback() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
connection.setEx(keyStr, timeout, object);
return null;
}
});
}
@Override
public boolean add(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] object = serializer.serialize(value);
return connection.setNX(keyStr, object);
}
});
return result;
}
@Override
public boolean add(final String key, final List list) {
return this.add(key, JSON.toJSONString(list));
}
@Override
public void delete(final String key) {
List list = new ArrayList<>();
list.add(key);
this.delete(list);
}
@Override
public void delete(final List keys) {
redisTemplate.delete(keys);
}
@Override
public boolean update(final String key, final T obj) {
return this.update(key, JSON.toJSONString(obj));
}
@Override
public boolean update(final String key, final String value) {
if (get(key) == null) {
throw new NullPointerException("数据行不存在, key = " + key);
}
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] valueStr = serializer.serialize(value);
connection.set(keyStr, valueStr);
return true;
}
});
return result;
}
@Override
public boolean save(String key, String value) {
if (StrUtil.isEmpty(get(key))) {
return this.add(key, value);
} else {
return this.update(key, value);
}
}
@Override
public boolean save(String key, T obj) {
if (get(key, obj.getClass()) == null) {
return this.add(key, obj);
} else {
return this.update(key, obj);
}
}
@Override
public T get(final String key, final Class clazz) {
T result = redisTemplate.execute(new RedisCallback() {
public T doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return (T) JSON.parseObject(valueStr, clazz);
}
});
return result;
}
@Override
public List getList(final String key, final Class clazz) {
List result = redisTemplate.execute(new RedisCallback>() {
public List doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return JSON.parseArray(valueStr, clazz);
}
});
return result;
}
@Override
public String get(final String key) {
String result = redisTemplate.execute(new RedisCallback() {
public String doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
if (value == null) {
return null;
}
String valueStr = serializer.deserialize(value);
return valueStr;
}
});
return result;
}
@Override
public byte[] getByte(final String key) {
byte[] result = redisTemplate.execute(new RedisCallback<byte[]>() {
public byte[] doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
byte[] value = connection.get(keyStr);
return value;
}
});
return result;
}
@Override
public Set keys(final String pattern) {
return redisTemplate.keys(pattern);
}
@Override
public boolean exist(final String key){
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
return connection.exists(keyStr);
}
});
return result;
}
@Override
public boolean set(final String key,final byte[] value){
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = getRedisSerializer();
byte[] keyStr = serializer.serialize(key);
connection.set(keyStr, value);
return true;
}
});
return result;
}
public boolean flushDB(){
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
connection.flushDb();
return true;
}
});
return result;
}
public long dbSize(){
long result = redisTemplate.execute(new RedisCallback() {
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.dbSize();
}
});
return result;
}
}
比如,在如下场景中,我的文件夹每个栏目右侧显示的是当前类目的数量,可以将这些数据存放在redis缓存中,只要缓存中存在就从缓存中取,否则从数据库中读取。
在相关业务导致数据变化的时候,则可强制缓存失效(设置缓存过期或删除缓存),则下次再读取数据的时候因为缓存无法命中而会从数据库中读取,从而拿到最新的数据。
相关代码
@Override
public Map getMessageCount() {
//优先从redis中读取,不存在,则从数据库中读取,因为本数据与个人用户相关,缓存名加上用户标识
Map retMap =redisDao.get(RedisConstant.MESSAGE_PRE+"count:"+SecurityUtil.getUserId(),Map.class);
if(retMap==null) {
retMap = new HashMap<>();
//发件箱
String hql_sent = "select count(id) from Message where sendUserID='" + SecurityUtil.getUserId() + "'";
Long count_sent = this.get(hql_sent);
retMap.put("sent", count_sent.intValue());
//草稿箱
String hql_draft = "select count(id) from Message where sendUserID='" + SecurityUtil.getUserId() + "' and messageStatus='0'";
Long count_draft = this.get(hql_draft);
retMap.put("draft", count_draft.intValue());
//收件箱
String hql_inbox = "select count(id) from MessageReceiver where receiveUserID='" + SecurityUtil.getUserId() + "' and deleted=0";
Long count_inbox = this.get(hql_inbox);
retMap.put("inbox", count_inbox.intValue());
//回收站
String hql_trash = "select count(id) from MessageReceiver where receiveUserID='" + SecurityUtil.getUserId() + "' and deleted=1 ";
Long count_trash = this.get(hql_trash);
retMap.put("trash", count_trash.intValue());
//将结果存入缓存
redisDao.add(RedisConstant.MESSAGE_PRE+"count:"+SecurityUtil.getUserId(), retMap);
return retMap;
}else {
return retMap;
}
}
@Override
public void deleteCacheForMsgCount(){
redisDao.delete(RedisConstant.MESSAGE_PRE+"count:"+SecurityUtil.getUserId());
}
数据变化将缓存删除或设置为过期
@RequestMapping(value = "/save")
@ResponseBody
public Result save(String message) {
Message msgobj = JSON.parseObject(message, Message.class);
//保存为草稿或保存并直接发送
messageService.deleteCacheForMsgCount();
return messageService.saveMessage(msgobj);
}
@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
@ResponseBody
public Result delete(@PathVariable("id") String id) {
Message message = this.get(id);
if (!MessageConstant.SEND_STATUS_DRAFT.equals(message.getMessageStatus())) {
return new Result(false, "该消息不是草稿状态,不能删除");
}
try {
messageService.delete(message);
messageService.deleteCacheForMsgCount();
return new Result();
} catch (Exception e) {
return new Result(false, "该数据已经被引用,不可删除");
}
}
使用缓存后,性能对比
下面对比了请求,上面是通过redis缓存读取,下面是通过数据库读取,两次用时差别很多,说明使用redis缓存可以显著提高性能。