Redis 简介 GitHub 地址:https://github.com/antirez/redis 。
GitHub 介绍:Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.
对于缓存
内存的速度远远大于硬盘的速度
缓存主要是在获取资源方便性能优化的关键方面
Redis 是缓存数据库
缓存未命中解决与防止缓存击穿
缓存更新策略
Cache aside :
思路:先更新数据库,在更新缓存。
问题:一个读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放到缓存,所以,会造成脏数据。
出现此问题的前提:读缓存时缓存失效,而且并发着有一个写操作。
而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
Read through
Write through
思路:有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
Write behind caching
思路:只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。
实现有点复杂,具体参考《缓存更新的套路》
Redis 实践(复杂缓存) 配置application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 spring: cache: type: REDIS redis: cache-null-values: false time-to-live: 600000ms use-key-prefix: true cache-names: userCache,allUsersCache redis: host: 127.0 .0 .1 port: 6379 database: 0 lettuce: shutdown-timeout: 200ms pool: max-active: 7 max-idle: 7 min-idle: 2 max-wait: -1ms timeout: 1000
对应的配置类:org.springframework.boot.autoconfigure.data.redis.RedisProperties
添加配置类 这里自定义RedisTemplate
的配置类,主要是想使用Jackson
替换默认的序列化机制:
自定义序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Configuration public class RedisConfig { @Bean public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate <>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer (Object.class); ObjectMapper objectMapper = new ObjectMapper (); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
使用Cache aside
策略的实例 这里只展示使用服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 @Service(value = "appUserService") public class AppUserServiceImpl implements AppUserService { @Resource(name = "appUserRepository") private AppUserRepository appUserRepository; @Resource private RedisTemplate<String, User> redisTemplate; @Override public AppUser saveOne (AppUser appUser) { return appUserRepository.save(appUser); } @Override public AppUser findByLoginName (String loginName) { ogger.info("获取用户start..." ); String key = "AppUser:" + loginName; ValueOperations<String, User> operations = redisTemplate.opsForValue(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { AppUser user = operations.get(key); logger.info("从缓存中获取了用户 AppUser = " + loginName); return user; } List<AppUser> appUserList = appUserRepository.findByLoginNameEquals(loginName); if (appUserList.size() > 0 ){ operations.set(key, appUserList.get(0 ), 10 , TimeUnit.SECONDS); } return appUserList.size() > 0 ? appUserList.get(0 ) : null ; } public void updateUser (AppUser user) { logger.info("更新用户start..." ); appUserRepository.save(user); String key = "AppUser:" + user.getLoginName(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); logger.info("更新用户时候,从缓存中删除用户 >> " + user.getLoginName()); } } public void deleteById (Long id) { logger.info("删除用户start..." ); AppUser user = appUserRepository.get(id); appUserRepository.deleteById(id); String key = "AppUser:" + user.getLoginName(); boolean hasKey = redisTemplate.hasKey(key); if (hasKey) { redisTemplate.delete(key); logger.info("删除用户时候,从缓存中删除用户 >> " + user.getLoginName()); } } }
Redis + Cache 实践(简单缓存) Spring缓存支持 Spring定义了org.springframework.cache.CacheManager
和 org.springframework.cache.Cache
接口来统一不同缓存技术。 其中CacheManager是Spring提供的各种缓存技术抽象接口,内部使用Cache接口进行缓存的增删改查操作,我们一般不会直接和Cache打交道。
针对不同的缓存技术,Spring有不同的CacheManager
实现类,定义如下表:
CacheManager
描述
SimpleCacheManager
使用简单的Collection存储缓存数据,用来做测试用
ConcurrentMapCacheManager
使用ConcurrentMap存储缓存数据
EhCacheCacheManager
使用EhCache作为缓存技术
GuavaCacheManager
使用Google Guava的GuavaCache作为缓存技术
JCacheCacheManager
使用JCache(JSR-107)标准的实现作为缓存技术,比如Apache Commons JCS
RedisCacheManager
使用Redis作为缓存技术
在我们使用任意一个实现的CacheManager的时候,需要注册实现Bean:
1 2 3 4 5 6 7 @Bean public EhCacheCacheManager cacheManager (CacheManager cacheManager) { return new EhCacheCacheManager (cacheManager); }
声明式缓存注解 Spring提供4个注解来声明缓存规则,如下表所示:
注解
说明
@Cacheable
方法执行前先看缓存中是否有数据,如果有直接返回。如果没有就调用方法,并将方法返回值放入缓存
@CachePut
无论怎样都会执行方法,并将方法返回值放入缓存
@CacheEvict
将数据从缓存中删除
@Caching
可通过此注解组合多个注解策略在一个方法上面
@Cacheable 、@CachePut 、@CacheEvict都有value属性,指定要使用的缓存名称,而key属性指定缓存中存储的键。
@EnableCaching 开启缓存。
@Cacheable 这个注解含义是方法结果会被放入缓存,并且一旦缓存后,下一次调用此方法,会通过key去查找缓存是否存在,如果存在就直接取缓存值,不再执行方法。
这个注解有几个参数值,定义如下
参数
解释
cacheNames
缓存名称
value
缓存名称的别名
condition
Spring SpEL 表达式,用来确定是否缓存
key
SpEL 表达式,用来动态计算key
keyGenerator
Bean 名字,用来自定义key生成算法,跟key不能同时用
unless
SpEL 表达式,用来否决缓存,作用跟condition相反
sync
多线程同时访问时候进行同步
在计算key、condition或者unless的值得时候,可以使用到以下的特有的SpEL表达式
表达式
解释
#result
表示方法的返回结果
#root.method
当前方法
#root.target
目标对象
#root.caches
被影响到的缓存列表
#root.methodName
方法名称简称
#root.targetClass
目标类
#root.args[x]
方法的第x个参数
@CachePut 该注解在执行完方法后会触发一次缓存put操作,参数跟@Cacheable一致
@CacheEvict 该注解在执行完方法后会触发一次缓存evict操作,参数除了@Cacheable里的外,还有个特殊的allEntries
, 表示将清空缓存中所有的值。
缓存注解使用 在service中定义增删改的几个常见方法,通过注解实现缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @Service @Transactional public class UserService { private Logger logger = LoggerFactory.getLogger(this .getClass()); @Resource private AppuserRepository appuserRepository; @Cacheable(value = "userCache", key = "#id", unless="#result == null") public AppUser getById (int id) { logger.info("获取用户start..." ); return appuserRepository.selectById(id); } @Cacheable(value = "allUsersCache", unless = "#result.size() == 0") public List<User> getAllUsers () { logger.info("获取所有用户列表" ); return appuserRepository.findByLoginNameEquals(null ); } @Caching( put = {@CachePut(value = "userCache", key = "#user.id")}, evict = {@CacheEvict(value = "allUsersCache", allEntries = true)} ) public AppUser createUser (AppUser user) { logger.info("创建用户start..., user.id=" + user.getId()); appuserRepository.save(user); return user; } @Caching( put = {@CachePut(value = "userCache", key = "#user.id")}, evict = {@CacheEvict(value = "allUsersCache", allEntries = true)} ) public AppUser updateUser (Appuser user) { logger.info("更新用户start..." ); appuserRepository.save(user); return user; } @Caching( evict = { @CacheEvict(value = "userCache", key = "#id"), @CacheEvict(value = "allUsersCache", allEntries = true) } ) public void deleteById (int id) { logger.info("删除用户start..." ); appuserRepository.deleteById(id); } }
缓存配置类 RedisCacheConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Configuration @EnableCaching public class RedisCacheConfig { private Logger logger = LoggerFactory.getLogger(this .getClass()); @Autowired private Environment env; @Bean public LettuceConnectionFactory redisConnectionFactory () { RedisStandaloneConfiguration redisConf = new RedisStandaloneConfiguration (); redisConf.setHostName(env.getProperty("spring.redis.host" )); redisConf.setPort(Integer.parseInt(env.getProperty("spring.redis.port" ))); redisConf.setPassword(RedisPassword.of(env.getProperty("spring.redis.password" ))); return new LettuceConnectionFactory (redisConf); } @Bean public RedisCacheConfiguration cacheConfiguration () { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600 )) .disableCachingNullValues(); return cacheConfig; } @Bean public RedisCacheManager cacheManager () { RedisCacheManager rcm = RedisCacheManager.builder(redisConnectionFactory()) .cacheDefaults(cacheConfiguration()) .transactionAware() .build(); return rcm; } }
keyGenerator 自定义key 一般来讲我们使用key属性就可以满足大部分要求,但是如果你还想更好的自定义key,可以实现keyGenerator。
这个属性为定义key生成的类,和key属性不能同时存在。
在RedisCacheConfig
配置类中添加我自定义的KeyGenerator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Bean(name = "myKeyGenerator") public KeyGenerator myKeyGenerator () { return new KeyGenerator () { @Override public Object generate (Object o, Method method, Object... params) { logger.info("自定义缓存,使用第一参数作为缓存key,params = " + Arrays.toString(params)); return params[0 ]; } }; }
切换缓存技术 得益于SpringBoot的自动配置机制,切换缓存技术除了替换相关maven依赖包和配置Bean外,使用方式和实例中一样, 不需要修改业务代码。如果你要切换到其他缓存技术非常简单。
EhCache 当我们需要使用EhCache作为缓存技术的时候,只需要在pom.xml中添加EhCache的依赖:
1 2 3 4 <dependency > <groupId > net.sf.ehcache</groupId > <artifactId > ehcahe</artifactId > </dependency >
EhCache的配置文件ehcache.xml只需要放到类路径下面,SpringBoot会自动扫描,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="UTF-8" ?> <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="http://ehcache.org/ehcache.xsd" updateCheck ="false" monitoring ="autodetect" dynamicConfig ="true" > <diskStore path ="java.io.tmpdir/ehcache" /> <defaultCache maxElementsInMemory ="50000" eternal ="false" timeToIdleSeconds ="3600" timeToLiveSeconds ="3600" overflowToDisk ="true" diskPersistent ="false" diskExpiryThreadIntervalSeconds ="120" /> <cache name ="authorizationCache" maxEntriesLocalHeap ="2000" eternal ="false" timeToIdleSeconds ="3600" timeToLiveSeconds ="3600" overflowToDisk ="false" statistics ="true" > </cache > </ehcache >
SpringBoot会为我们自动配置EhCacheCacheManager
这个Bean,不过你也可以自己定义。
Guava 当我们需要Guava作为缓存技术的时候,只需要在pom.xml中增加Guava的依赖即可:
1 2 3 4 5 <dependency > <groupId > com.google.guava</groupId > <artifactId > guava</artifactId > <version > 18.0</version > </dependency >
SpringBoot会为我们自动配置GuavaCacheManager
这个Bean。
Redis 最后还提一点,本篇采用Redis作为缓存技术,添加了依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
SpringBoot会为我们自动配置RedisCacheManager
这个Bean,同时还会配置RedisTemplate
这个Bean。 后面这个Bean就是下一篇要讲解的操作Redis数据库用,这个就比单纯注解缓存强大和灵活的多了。
参考文章
Spring Boot Redis Cache
SpringBoot系列 - 缓存
本文地址: SpringBoot整合Redis与Cache与实现
推荐
SpringBoot整合Redis及Redis简介和操作
本文地址: https://github.com/maxzhao-it/blog/post/3728/