- 一种nosql 单线程的数据库做缓存的简单使用
- 对curd的操作,为了保证数据的一致性,除了查询,其它操作都要进行数据更新和删除
- redis的协议:Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信,了解了Redis协议的基本数据类型,就可以通过Socket与Redis Server进行通信,客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。
- 缓存击穿 指查询一个一定不存在的数据要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞;解决方法如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短
- 缓存雪崩 缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩;解决方案在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,降低几率;或则加锁,队列
- 关于缓存击穿和雪崩参考
安装服务
- http://www.redis.cn/ 下载最新redis4.01
-
#tar -zxvf redis... --解压
-
#mv file filepath --移动到你想安装的目录
-
#cd /redis
-
#make MALLOC=libc --管理内存碎片
-
#make && make install --编译并安装
-
#cd src && redies-server 如图
配置
-
#vi /home/redis/src/redis.conf --编写配置文件,写入已下配置,指定端口-父进程pid-守护进程
-
#通过src/redis-server redis.conf 启动 通过#redis-cli shutdown 来停止服务
-
会有两个端口打开未发现有什么错误,可以禁用ip6,
-
#firewall-cmd --zone=public --add-port=6379/tcp --perment
-
#vi /usr/lib/systemd/system/redis.service --编写服务单元交与systemd 管理
rdb和aof 两种持久化方案详情1详情2
-
rdb 是基于快照的,通过save seconds frequency,进行周期性的备份默认开启,当服务器出现故障可能会丢失最后一次的数据。数据恢复速度快适合数据的备份。
-
aof 是基于日志记录的,保持了高一致性。数据比较全面文件比较大恢复稍慢。默认是关闭的通过appendonly yes开启。
简单的使用
- 使用java操作jedis客户端
- 编写jedisManager
导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
redis的连接初始化类
public class JedisManager {
private static String host = "192.168.1.200";
private static int port = 6379;
private JedisPoolConfig jpc;
private static JedisPool jedisPool;
private static int timeOut;
/**
* 初始化连接池
**/
static {
if (timeOut == 0){
timeOut = Protocol.DEFAULT_TIMEOUT;
}
jedisPool = new JedisPool(host,port);
}
/**
* 从连接池中获取连接
**/
public static Jedis getJedis(){
if (jedisPool != null){
return jedisPool.getResource();
}else throw new NullPointerException("jedisPool is null");
}
/**
* 把连接返回给jedispool
**/
public static void returnResource(Jedis jedis) {
if (jedis != null){
jedisPool.close();
}
}
/**
* 关闭连接池
**/
public void shutDown(){
if (jedisPool != null){
jedisPool.destroy();
}
}
}
String类型的测试
public class JedisDao {
//存储字符串
public void cacheString(){
Jedis jedis = JedisManager.getJedis();
System.out.println(jedis);
jedis.set("name","hahaha");
System.out.println(jedis.get("name"));
//拼接
jedis.append("name","is 爸爸");
System.out.println(jedis.get("name"));
JedisManager.returnResource(jedis);
}
}
测试结果
redis.clients.jedis.Jedis@7bb11784
hahaha
hahahais 爸爸
Process finished with exit code 0
- 整合spring 分别在service层缓存,利用spirng aop 对目标方法进行缓存
导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
redis.properties
redis.host=192.168.1.200
redis.port=6379
redis.password=
#最大空闲数
redis.maxIdel=300
#连接池连接数
redis.maxActive=600
#最大等待时间
redis.maxWait=1000
#实例均用
redis.testOnBorrow=true
reids-cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入redis参数文件-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="order" value="1"/>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations">
<list>
<value>classpath:redis.properties</value>
</list>
</property>
</bean>
<!--连接池-->
<bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxActive}"/>
<property name="maxIdle" value="${redis.maxIdel}"/>
<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.password}"/>
<property name="poolConfig" ref="jedisConfig"/>
<property name="usePool" value="true"/>
</bean>
<!-- redis操作模板-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 如果不配置Serializer,那么存储的时候只能使用String,如果用对象类型存储,那么会提示错误 can't cast to String!!!-->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<!--开启事务-->
<property name="enableTransactionSupport" value="true"/>
</bean>
</beans>
放入spring的主配置文件
<!--redis-->
<import resource="redis-cfg.xml"/>
- 测试在service层进行缓存
需要的工具类
@Component
public class RedisCacheUtil {
@Autowired
private RedisTemplate<Serializable,Object> redisTemplate;
/**
* 删除对应的value
**/
public void remove(final String... args){
for (String key:args){
remove(key);
}
}
public boolean remove(String key){
boolean flag = false;
try {
if (exists(key)){
redisTemplate.delete(key);
flag = true;
}
}catch (Exception e) {
System.out.println(e.toString());
}
return flag;
}
public boolean exists(String key) {
return redisTemplate.hasKey(key);
}
/**
* 删除keys
**/
public void removePattern(final String pattern){
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size()>0){
redisTemplate.delete(keys);
}
}
/**
* 根据key读取缓存
**/
public Object get(final String key){
return redisTemplate.opsForValue().get(key);
}
/**
* 写入缓存
**/
public boolean set(final String key, Object value){
try {
redisTemplate.opsForValue().set(key,value);
return true;
}catch (Exception e){
System.out.println(e.toString());
return false;
}
}
public boolean set(final String key, Object value, Long time){ //设置过期时间
try {
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key,time, TimeUnit.SECONDS);
return true;
}catch (Exception e){
return false;
}
}
}
缓存的容器
/**
* Created by: jun on 2018/1/7.
* description: 创建redis的储存器接口
*/
public interface RedisCacheStorage<k,v> {
//key,value 普通类型
boolean set(k key, v value); //永久缓存
boolean set(k key, v value, Long time); //time内缓存
boolean remove(k key); //删除
v get(k key); //获取
}
// 实现的接口
@Component
public class RedisCacheStorageImpl<v> implements RedisCacheStorage<String,v> {
@Autowired
private RedisCacheUtil redisCacheUtil;
@Override
public boolean set(String key, v value) {
return redisCacheUtil.set(key,value);
}
@Override
public boolean set(String key, v value, Long time) {
return redisCacheUtil.set(key,value,time);
}
@Override
public boolean remove(String key) {
return redisCacheUtil.remove(key);
}
@Override
public v get(String key) {
return (v) redisCacheUtil.get(key);
}
}
实体类,一定要序列化,spirng 才能进行装配
public class User implements Serializable {
private Integer id;
private String name;
private String pwd;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
开始缓存
@Service
public class UserServiceImpl implements UserService {
private static final String cacheKey = "userEntity"; //缓存的key
private static final Long time = 10000l;
@Autowired
private UserMapper userMapper;
@Autowired
private RedisCacheStorage cacheStorage;
@Override
public List<User> select() {
//先查找缓存
List<User> userList;
userList = (List<User>) cacheStorage.get(cacheKey);
if (userList != null) {
System.out.println("读取了缓存");
return userList;
} else {
//开始查找数据库,放入缓存
userList = userMapper.selectAll();
cacheStorage.set(cacheKey, userList);
System.out.println("已添加缓存");
return userList;
}
}
测试结果
- 测试基于aop配置文件目标方法的缓存
实现MethodInterceptor接口的切面,在redis.properties中写入需要缓存的方法如果与aop拦截的方法对应则进行缓存
/**
* Created by: jun on 2018/1/3.
* description: 实现spring MethodInterceptor接口,使用aop对方法级的进行缓存。
* 根据配置文件给不同的方法加入判断是否缓存数据,第一次从缓存中读取,并将结果进行缓存
*/
public class MethodCacheInterceptor implements MethodInterceptor {
@Autowired
private RedisCacheUtil redisCacheUtil;
private List<String> cacheClassList; //缓存的serice类
private List<String> cacheMethodList; //缓存的方法
private Long timeOut = 1000l; //过期时间
private String resourcesName = "redis.properties"; //缓存的配置文件
private String fileClassValue = "cacheClass"; //配置文件设置缓存的类字段
private String fileMethodValue = "cacheMethod"; //配置文件中缓存的方法字段
/**
* 初始化读取需要缓存的类和方法
* 在reids.properties中添加缓存的方法和类
*
**/
public MethodCacheInterceptor() throws IOException {
// 在 resources 下读取配置文件
InputStream in = ConfProLoader.getClassPathFile(resourcesName);
Properties p = new Properties();
p.load(in);
//分割,获取每个类和方法
String[] cacheClass = p.getProperty(fileClassValue).split(",");
String[] cacheMethod = p.getProperty(fileMethodValue).split(",");
//将方法和类存入list
cacheClassList = new ArrayList<String>(cacheClass.length);
cacheMethodList = new ArrayList<String>(cacheMethod.length);
int maxLength = cacheClass.length > cacheMethod.length ? cacheClass.length
: cacheMethod.length;
for (int i=0; i<maxLength; i++){
if (i<cacheClass.length) cacheClassList.add(cacheClass[i]);
if (i<cacheMethod.length) cacheMethodList.add(cacheMethod[i]);
}
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//得到方法和参数列表
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] argument = invocation.getArguments();
String key = getCacheKey(targetName,methodName,argument);
System.out.println("key: "+key);
//是否缓存方法 /否则放行
System.out.println("拦截的类"+targetName);
if (isAddCache(targetName,methodName)){
//若果缓存存在,直接读取否则进行缓存
if (redisCacheUtil.exists(key)){
System.out.println("开始读取缓存");
return redisCacheUtil.get(key);
}
Object value = invocation.proceed(); //对方法的结果进行缓存
if (value != null){
System.out.println("开始缓存");
redisCacheUtil.set(key,value,timeOut);
}
}
return invocation.proceed();
}
/**
* 检查是否需要添加缓存
**/
public boolean isAddCache(String targetName,String metnodName){
boolean flag= false;
if (cacheClassList.contains(targetName) || cacheMethodList.contains(metnodName)) flag = true;
return flag;
}
/**
* 创建缓存的key
**/
public String getCacheKey(String targetName, String methodName, Object[] arguments){
StringBuffer sb = new StringBuffer();
sb.append(targetName).append("_").append(methodName);
for (int i=0; i<arguments.length; i++){
if (arguments.length>0) sb.append("_").append(arguments[i]);
}
return sb.toString();
}
}
在reids的资源文件中写入需要缓存的类和方法。如果拦截的方法和配置文件中对应,则对该方法进行缓存
host=192.168.1.200
port=6379
password=
#连接池连接数
maxTotal=400
#最大空闲数
maxIdel=300
#最大等待时间
maxWait=1000
#实例均用
testOnBorrow=true
#需要缓存的类
cacheClass=comm.tianzhuan.web.service.impl.UserServiceImpl,test1
#要缓存的方法
cacheMethod=selectt,hahaha
#设置缓存失效时间
test=60
test1=60
defaultCacheExpireTime=3600
spirng中配置aop
<!--配置拦截器实现方法的缓存-->
<bean id="methodCacheInterceptor" class="com.tianzhuan.common.cache.MethodCacheInterceptor"/>
<aop:config proxy-target-class="true">
<!-- 切入点 -->
<aop:pointcut id="redisCache" expression="execution(* com.tianzhuan.web.service.impl.*.*(..))"/>
<!--拦截器-->
<aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="redisCache"/>
</aop:config>
测试的结果
@Test
public void test(){
List<User> userList = userService.select();
System.out.println(userList);
}