本地缓存Ehcache与分布式缓存Redis介绍

一、本地缓存Ehcache介绍

1.1、什么是Ehcache(JVM内置的缓存)

Ehcache是纯java的开源缓存框架其缓存的数据可以是存放在内存里面的,也可以是存放在硬盘上的。

让ehcache作为二级缓存,当redis服务器宕机后,可以查询ehcache缓存。

1.2、项目使用

启动类加上:@EnableCachin,开启ehcache缓存模式

@CacheConfig(cacheNames = "userCache")
public interface UserMapper {
	@Select("SELECT ID ,NAME,AGE FROM users where id=#{id}")
	@Cacheable
	List<Users> getUser(@Param("id") Long id);
}

@Cacheable  加了该注解的方法表示可以缓存

@CacheConfig 表示创建缓存配置,Key为userCache

 1.3、Redis和Ehcache缓存的区别

  • 如果是单个应用或者对缓存访问要求很高的应用,用ehcache;
  • 如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

 

二、使用Redis+Ehcache实现分布式缓存(二级缓存)

2.1、原理图:

 

2.2、二级缓存使用

一级(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)其他问题

.....待整理

2.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

redis有16个库,默认连接第一个库

 3.1、Redis的一些应用场景

(1)令牌生成(临时 有效期)

(2)短信验证(短信也有有效期)

(3)缓存,热点数据(减轻查询数据库压力)

(4)分布式锁(使用ZK或者Redis实现)

(5)网站计数器(因为redis单线程,在高并发时刻保证全局count唯一性)

(6)实现消息队列(不推荐)——发布-订阅

3.2、Redis基本数据类型(5种)

(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(集合)——无序集合

RedisSetstring类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据

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"

 

3.3、SpringBoot整合redis

(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<String> set = new HashSet<String>();
		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<String> value = (Set<String>) object;
		for (String oj : value) {
			stringRedisTemplate.opsForSet().add(key, oj);
		}
	}

	public String getString(String key) {
		return stringRedisTemplate.opsForValue().get(key);
	}

}
发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103649938