发表于: 2021-03-17 22:44:33

1 1237


今天完成的事情:
ssm框架整合redis
了解Redis几个概念

缓存过期 && 缓存淘汰,缓存穿透 && 布隆过滤器,缓存击穿 && 缓存雪崩




明天计划的事情:

压测,压测,压测




遇到的问题:


是用Redis接口测试,刷新不出来


原因:本地Redis没有启动




Error creating bean with name 'excellentStudentMapper' defined in file


Error creating bean with name 'sqlSessionFactory' defined in class path resource [spring/spring-mybatis.xml]


Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'studentController':


翻译:

创建文件中定义了名为“excellentStudentMapper”的bean时出错


创建在类路径资源[spring/spring]中定义的名为“sqlSessionFactory”的bean时出错-mybatis.xml文件]


上下文初始化期间遇到异常-取消刷新尝试:org.springframework.beans网站.factory.UnsatifiedDependencyException异常:创建名为“studentController”的bean时出错:


原因:redis和mecached冲突,注销mecached




收获:

导入依赖



<!-- redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>

配置redis.properties



#访问地址
redis.host=127.0.0.1
#访问端口
redis.port=6379
#注意,如果没有password,此处不设置值,但这一项要保留
redis.password=
#最大空闲数(默认:8)
redis.maxIdle=300
#当连接池资源耗尽时,调用者最大阻塞时间,超时将抛出异常.单位:毫秒,默认:-1,表示永不超时.
redis.maxWaitMillis=1000
#最大连接数(默认:8)
redis.maxTotal=500
#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 (默认:false)
redis.testOnBorrow=true
redis.testOnReturn=true
redis.testWhileIdle=true
redis.blockWhenExhausted=false
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.minEvictableIdleTimeMillis=1800000


配置redis.xml



<?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="
<!--1)如果你有多个数据源需要通过<context:property-placeholder管理,且不愿意放在一个配置文件里,那么一定要加上ignore-unresolvable=“true"-->
<!--2)注意新版的(具体从哪个版本开始不清楚,有兴趣可以查一下)JedisPoolConfigproperty name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码。-->
<!--扫描redis配置文件-->
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true" />
<!--设置连接池-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!-- 最大连接数 -->
<property name="maxTotal" value="${redis.maxTotal}" />
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="${redis.testOnReturn}" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="${redis.testWhileIdle}" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="${redis.blockWhenExhausted}" />
</bean>
<!--Spring整合Jedis,设置连接属性-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:hostName="${redis.host}"
p:port="${redis.port}"
p:password="${redis.password}"
p:pool-config-ref="poolConfig"
p:timeout="100000"/>
<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">
<!--key的默认序列化器。默认值是StringSerializer-->
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<!--是对value的默认序列化器,默认值是取自DefaultSerializerJdkSerializationRedisSerializer-->
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<!--存储Mapkey需要的序列化配置-->
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<!--存储Mapvalue需要的序列化配置-->
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
</bean>
<!--配置redis工具类bean-->
<bean id="redisUtils" class="com.kbk.util.RedisUtil"></bean>
</beans>


在spring-mybaits.xml导入redis.xml,并注释mecached.xml不然运行过程会报错



<!-- 加载memcached.xml -->
<!-- <import resource="classpath:memcached.xml"/>-->
<!-- 加载redis.xml -->
<import resource="classpath:redis.xml"/>


覆写一个RedisUtil工具类



package com.kbk.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
*
* @Description
* @Author 况博凯
* @Date 2021/03/17 10:27
* @Version 1.0
*
*/
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
*
* @param key
* @param time 时间()
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间() 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存获取
*
* @param key
* @return
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key
* @param value
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key
* @param value
* @param time 时间() time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key
* @param map 对应多个键值
* @param time 时间()
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key
* @param item
* @param value
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key
* @param item
* @param value
* @param time 时间() 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key
* @param item
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key
* @param item
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key
* @param value
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* set数据放入缓存
*
* @param key
* @param time 时间()
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value
*
* @param key
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key
* @param start 开始
* @param end 结束 0 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key
* @param index 索引 index>0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* list放入缓存
*
* @param key
* @param value
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* list放入缓存
*
* @param key
* @param value
* @param time 时间()
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* list放入缓存
*
* @param key
* @param value
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* list放入缓存
*
* @param key
* @param value
* @param time 时间()
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key
* @param index 索引
* @param value
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key
* @param count 移除多少个
* @param value
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}



测试类:


import com.kbk.util.RedisUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
*
* @Description
* @Author 况博凯
* @Date 2021/03/17 14:14
* @Version 1.0
*
*/
// 让测试运行于spring测试环境
@RunWith(SpringJUnit4ClassRunner.class)
// 指定 Spring 配置文件所在的位置
@ContextConfiguration(locations = "classpath:redis.xml")
public class TestRedis {
@Resource
RedisUtil redisUtil;
@Test
public void test1(){
if(!redisUtil.hasKey("test:01")){
redisUtil.set("test:01","这是一个测试redis缓存");
}
System.out.println(redisUtil.get("test:01"));
}
}



运行成功:



RedisController类:

package com.kbk.controller;
import com.kbk.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class RedisController {
@Autowired
RedisUtil redisUtil;
@RequestMapping("/test")
@ResponseBody
public String testRedis(){
if(!redisUtil.hasKey("test:01")){
redisUtil.set("test:01","这是一个测试redis缓存");
}
System.out.println(redisUtil.get("test:01"));
return "success!";
}
}

运行报错:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisController' defined in file [D:\JAVASourceCode\SpringMVC-Task4\target\SpringMVC-Task4\WEB-INF\classes\com\kbk\controller\RedisController.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Unexpected AOP exception; nested exception is java.lang.IllegalStateException: Unable to load cache item


原因

spring-aop版本与 aspectjweaver 包版本冲突



解决办法


我的maven项目的spring-aop版本是5.1.9,其他的spring版本是5.2.1,aspectjweaver 版本是1.8.3,将spring-aop的版本改成5.2.1,运行正常

项目中并没有用到spring-aop包,用的是aspectjweaver ,直接将spring-aop删掉,运行正常


改成同一版本
<!-- spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>

运行成功:


===============
===============

了解Redis几个概念


缓存过期 && 缓存淘汰,缓存穿透 && 布隆过滤器,缓存击穿 && 缓存雪崩


缓存过期 && 缓存淘汰


设置缓存时间的目的是:因为缓存都存在内存中,而内存的大小是有限制的,不能无限的存储下去,所以需要设置缓存时间。具体设置多长一般交给应用程序去设置

那应该怎么清理的呢?


定期清理的话,一次性删除所有缓存的话,那么重新添加回来的话,会花太多时间,严重影响新的请求。


那么只能随机一部分来清理了,能缓解内存压力就行了


就这样过了一段日子,我发现有些个键值运气比较好,每次都没有被我的随机算法选中,每次都能幸免于难,这可不行,这些长时间过期的数据一直霸占着不少的内存空间!


于是在原来定期删除的基础上,又加了一招:


那些原来逃脱我随机选择算法的键值,一旦遇到查询请求,被我发现已经超期了,那我就绝不客气,立即删除。

这种方式因为是被动式触发的,不查询就不会发生,所以也叫惰性删除


可是,还是有部分键值,既逃脱了我的随机选择算法,又一直没有被查询,导致它们一直逍遥法外!而于此同时,可以使用的内存空间却越来越少。


而且就算退一步讲,我能够把过期的数据都删除掉,那万一过期时间设置的很长,还没等到我去清理,内存就吃满了,一样要吃枣药丸,所以我还得想个办法。


我苦思良久,终于憋出了个大招:内存淘汰策略,这一次我要彻底解决问题!

我提供了8种策略供应用程序选择,用于我遇到内存不足时该如何决策:



  • noeviction:返回错误,不会删除任何键值
  • allkeys-lru:使用LRU算法删除最近最少使用的键值
  • volatile-lru:使用LRU算法从设置了过期时间的键集合中删除最近最少使用的键值
  • allkeys-random:从所有key随机删除
  • volatile-random:从设置了过期时间的键的集合中随机删除
  • volatile-ttl:从设置了过期时间的键中删除剩余时间最短的键
  • volatile-lfu:从配置了过期时间的键中删除使用频率最少的键
  • allkeys-lfu:从所有键中删除使用频率最少的键
=================

缓存穿透 && 布隆过滤器


一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透


所以很容易被别人恶意大量访问数据库,造成服务器的压力很大。


如何避免?
1.对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2.或者使用:布隆过滤器

布隆过滤器就擅长从超大的数据集中快速告诉你查找的数据存不存在(但它有一点不靠谱,它告诉你存在的话不能全信,其实有可能是不存在的,不过它他要是告诉你不存在的话,那就一定不存在)。

==============

缓存击穿 && 缓存雪崩


缓存雪崩


什么是缓存雪崩?

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。


如何避免?

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。


2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。


3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)


==============
==============
Redis缓存代码
@Autowired
RedisUtil redisUtil;
private static final String KEY = "ExcellentStudentList";
public static final Logger log = LoggerFactory.getLogger(ExcellentStudentServiceImpl.class);
@Override
public List<ExcellentStudent> selectAll(){
List<ExcellentStudent> excellentStudentList = null;
if (redisUtil.hasKey(KEY)) {
excellentStudentList = (List<ExcellentStudent>) redisUtil.get(KEY);
log.info("\n"+"Redis缓存中查询所有");
} else {
log.info("\n"+"从数据库中查询所有");
excellentStudentList = excellentStudentMapper.selectAll();
boolean list = redisUtil.set(KEY, excellentStudentList);
log.info("\n"+"新增所有数据缓存:"+list);
}
return excellentStudentList;
}


运行报错:


没有开启Redis服务器的原因....


开启,重新运行:

=========
=========



返回列表 返回列表
评论

    分享到