Ehcache是纯java的开源缓存框架,其缓存的数据可以是存放在内存里面的,也可以是存放在硬盘上的。
让ehcache作为二级缓存,当redis服务器宕机后,可以查询ehcache缓存。
启动类加上:@EnableCachin,开启ehcache缓存模式
@CacheConfig(cacheNames = "userCache")
public interface UserMapper {
@Select("SELECT ID ,NAME,AGE FROM users where id=#{id}")
@Cacheable
List getUser(@Param("id") Long id);
}
@Cacheable 加了该注解的方法表示可以缓存
@CacheConfig 表示创建缓存配置,Key为userCache
一级(EhCache),二级(Redis):本地没有再走网络
spring boot中集成了spring cache,并有多种缓存方式的实现,如:Redis、Caffeine、JCache、EhCache等等。但如果只用一种缓存,要么会有较大的网络消耗(如Redis),要么就是内存占用太大(如Caffeine这种应用内存缓存)
二级缓存原理:redis网络消耗大,当应用内缓存有符合条件的数据时,就可以直接使用,而不用通过网络到redis中去获取,这样就形成了两级缓存,目的是为了减轻访问redis压力也可以提高访问速度(并且不会产生EhCache内存溢出,EhCache不走网络)
流程说明:
(1)查一级缓存Ehcache一级缓存是否存在值,存在则直接返回,不用走网络,很快;
(2)一级缓存没有,查二级缓存Redis:二级缓存有值——>更新一级缓存——>返回值;
(3)一级缓存没有,查二级缓存Redis:二级缓存无值——>查数据库——更新二级缓存Redis——>更新一级缓存——>返回值。
几个问题:
(1)过期时间怎么控制(一级和二级的如何同步)?
一级缓存过期时间 要比二级缓存过期时间要短
......
(2)redis和ehcache缓存值不同步,怎么解决?
定时Job、MQ通知
.......
(3)其他问题
.....待整理
注意:可以把以下代码封装以下,封装成一个注解,一个注解搞定!
Ehcache工具类:
@Component
public class EhCacheUtils {
// @Autowired
// private CacheManager cacheManager;
@Autowired
private EhCacheCacheManager ehCacheCacheManager;
// 添加本地缓存 (相同的key 会直接覆盖)
public void put(String cacheName, String key, Object value) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = new Element(key, value);
cache.put(element);
}
// 获取本地缓存
public Object get(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = cache.get(key);
return element == null ? null : element.getObjectValue();
}
public void remove(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
cache.remove(key);
}
}
项目整合Ehcache环境:
@Service
public class UserService {
@Autowired
private EhCacheUtils ehCacheUtils;
private static final String CACHENAME_USERCACHE = "userCache";
@Autowired
private RedisService redisService;
@Autowired
private UserMapper userMapper;//DB
public Users getUser(Long id) {
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
+ "-id:" + id;
// 1.先查找一级缓存(本地缓存),如果本地缓存有数据直接返回
Users ehUser = (Users) ehCacheUtils.get(CACHENAME_USERCACHE, key);
if (ehUser != null) {
System.out.println("使用key:" + key + ",查询一级缓存 ehCache 获取到ehUser:" + JSONObject.toJSONString(ehUser));
return ehUser;
}
// 2. 如果本地缓存没有该数据,直接查询二级缓存(redis)
String redisUserJson = redisService.getString(key);
if (!StringUtils.isEmpty(redisUserJson)) {
// 将json 转换为对象(如果二级缓存redis中有数据直接返回二级缓存)
JSONObject jsonObject = new JSONObject();
Users user = jsonObject.parseObject(redisUserJson, Users.class);
// 更新一级缓存
ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
System.out.println("使用key:" + key + ",查询二级缓存 redis 获取到ehUser:" + JSONObject.toJSONString(user));
return user;
}
// 3. 如果二级缓存redis中也没有数据,查询数据库
Users user = userMapper.getUser(id);
if (user == null) {//如果DB查不到
return null;
}
//如何保证 两级缓存有效期相同?————一级缓存有效期时间 减去 二级缓存执行代码时间,保证二级缓存时间大于一级缓存时间
//.....略
//存放在二级缓存
String userJson = JSONObject.toJSONString(user);
redisService.setString(key, userJson);
//存放在一级缓存
ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
System.out.println("使用key:" + key + ",一级缓存和二级都没有数据,直接查询db" + userJson);
return user;
}
}
redis有16个库,默认连接第一个库
(1)令牌生成(临时 有效期)
(2)短信验证(短信也有有效期)
(3)缓存,热点数据(减轻查询数据库压力)
(4)分布式锁(使用ZK或者Redis实现)
(5)网站计数器(因为redis单线程,在高并发时刻保证全局count唯一性)
(6)实现消息队列(不推荐)——发布-订阅
(1)String(字符串)——常用
应用场景:普通key——value存储都可以
redis 127.0.0.1:6379> SET mykey "redis"
OK
redis 127.0.0.1:6379> GET mykey
"redis"
(2)List(列表)
应用场景:比如关注列表、粉丝列表
//LPUSH :存
redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql
(integer) 3
//LRANGE :查
redis 127.0.0.1:6379> LRANGE runoobkey 0 10
(1)"mysql"
(2)"mongodb"
(3) "redis"
(3)Hash(字典)
对应的Value内部是一个HashMap 或者数组
应用场景:当Hash成员少时为了节省空间Value采用数组存储;成员数大时Value自动转成HashMap
127.0.0.1:6379> HMSET runoobkey name "redis tutorial"
127.0.0.1:6379> HGETALL runoobkey
(1)"name"
(2) "redis tutorial"
(3)"description"
(4) "redis basic commands for caching"
(5)"likes"
(6)"20"
(7)"visitors"
(8) "23000"
(4)Set(集合)——无序集合
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
redis 127.0.0.1:6379> SADD runoobkey redis
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mongodb
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 0
redis 127.0.0.1:6379> SMEMBERS runoobkey
(1)"mysql"
(2)"mongodb"
(3)"redis"
(5)Sorted Set(有序集合)——有序集合
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。每个元素都会关联一个double类型的分数,正是通过分数来为集合中的成员进行从小到大的排序(有序集合成员唯一,但分数(score)却可以重复)
redis 127.0.0.1:6379> ZADD runoobkey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD runoobkey 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES
(1) "redis"
(2) "1"
(3) "mongodb"
(4)"2"
(5) "mysql"
(6) "4"
(1)Maven依赖
略
(2)配置文件
spring:
redis:
//database代表存在哪个库,默认第一个库
database: 0
host: 132.232.44.194
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
(3)代码整合
@RestController
public class IndexControler {
@Autowired
private RedisService redisService;
@RequestMapping("/setString")
public String setString(String key, String value) {
redisService.set(key, value, 60l);
return "success";
}
@RequestMapping("/getString")
public String getString(String key) {
return redisService.getString(key);
}
@RequestMapping("/setSet")
public String setSet() {
Set set = new HashSet();
set.add("yushengjun");
set.add("lisi");
redisService.setSet("setTest", set);
return "success";
}
}
@Component
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void set(String key, Object object, Long time) {
// 存放String 类型
if (object instanceof String) {
setString(key, object);
}
// 存放 set类型
if (object instanceof Set) {
setSet(key, object);
}
// 设置有效期 以秒为单位
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
}
public void setString(String key, Object object) {
// 如果是String 类型
String value = (String) object;
stringRedisTemplate.opsForValue().set(key, value);
}
public void setSet(String key, Object object) {
Set value = (Set) object;
for (String oj : value) {
stringRedisTemplate.opsForSet().add(key, oj);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
}