目录
1.Service中缓存一致性分析
第一步:修改TagService接口,添加相关方法,例如:
第二步:修改TagServiceImpl类,在类中重写TagService接口方法,例如:
第三步:将Redis中数据key设置为一致状态
第四步:修改单元测试类,测试缓存数据一致性.
5.在Controller中添加一个本地缓存,减少对远程redis缓存的访问,例如:
Controller中本地缓存一致性分析
Redis集群链接配置实践
当我们从数据库查询数据以后,假如将数据存入到了缓存,后续更新了数据库的数据,但假如没有更新缓存就会出现缓存数据与数据库数据不一致的这样的现象,对于这样问题,有时允许在一定时间范围之内存在。假如我们希望在更新了数据库数据以后要更新缓存,如何实现呢?接下来通过一个案例,来演示和解决一下这个问题.
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import java.util.List;
public interface TagService {
/**
* 查询所有的标签
* @return
*/
List selectTags();
/**
* 创建一个新的tag对象
* @param tag
*/
void insertTag(Tag tag);
/**
* 更新tag对象
* @param tag
* @return
*/
Tag updateTag(Tag tag);
/**
* 基于id查询tag信息
* @param id
* @return
*/
Tag selectById(Long id);
}
package com.jt.blog.service.impl;
import com.jt.blog.dao.TagMapper;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
/*@Cacheable注解描述的方法为缓存切入点方法
*访问此方法时系统底层会先从缓存查找数据,假如缓存缓存没有,
*会查询mysql数据库,这个注解假如想生效需要在启动类或者配置
*类上添加@EnableCaching注解.
*其中,这里的value用于指定一个key前缀,
*没有指定key属性,则默认会使用 KeyGenerator对象创建key
*/
@Cacheable(value = "tagCache")
@Override
public List selectTags() {
return tagMapper.selectList(null);
}
/**
* @CacheEvict注解的作用是定义缓存切入点方法,执行此注解描述的方法
* 时,底层通过AOP方式执行缓存数据的清除操作.
* 其中,allEntries表示清除指定key所有数据,beforeInvocation用于定义
* 在何时清除缓存数据,是更新数据库之后还是之前,false表示之后
*/
@CacheEvict(value = "tagCache",allEntries = true,beforeInvocation = false)
@Override
public void insertTag(Tag tag) {
tagMapper.insert(tag);
}
/**
* 缓存数据时,可以自己指定key,key的值为spring中的el表达式,语法可以打开@Cacheable注解源码进行查看,
* 这里的#id表示基于id的值作为key
*/
@Cacheable(value="tagCache",key="#id")
@Override
public Tag selectById(Long id){
return tagMapper.selectById(id);
}
/** @CachePut注解描述的方法为缓存切入点方法,系统底层会在执行此方法后,更新缓存数据,
* 这里更新完数据以后,key为tag对象的id值,值为方法的返回值.
*/
@CachePut(value = "tagCache",key="#tag.id")
@Override
public Tag updateTag(Tag tag){
tagMapper.updateById(tag);
return tag;
}
}
package com.jt.blog.damain.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.time.Duration;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
@Override
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target,
Method method,
Object... params) {
StringBuilder sb=new StringBuilder();
sb.append(target.getClass().getName());
sb.append("::");
sb.append(method.getName());
for(Object param:params){//方法没有参数就没有这个循环了
sb.append(param);
}
return sb.toString();
}
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60*60))
.serializeKeysWith(RedisSerializationContext.SerializationPair.
fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.
fromSerializer(new Jackson2JsonRedisSerializer
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
@Autowired
private TagService tagService;
@Test
void testSelectTags(){
List tags=
tagService.selectTags();
System.out.println(tags);
}
@Test
void testInsertTag(){
Tag tag=new Tag();
tag.setName("Oracle1");
tagService.insertTag(tag);
}
@Test
void testSelectById(){
Tag tag = tagService.selectById(1L);
System.out.println(tag);
}
@Test
void testUpdateTag(){
Tag tag=new Tag();
tag.setId(1L);
tag.setName("mysql8.0");
tagService.updateTag(tag);
}
}
package com.jt.blog.controller;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@RestController
@RequestMapping("/tag")
public class TagController {
@Autowired
private TagService tagService;
//此对象存哪了?(JVM)
private List tags=new CopyOnWriteArrayList<>();//本地 cache
@GetMapping
public List doSelectTags(){
if(tags.isEmpty()) {
synchronized (tags) {
if(tags.isEmpty()) {
tags.addAll(tagService.selectTags());//1.redis,2.mysql
}
}
}
return tags;
}
}
此次项目案例中,我们在Controller层添加了本地缓存,这个缓存我们也需要考虑其缓存一致性,其相关代码实现如下:
package com.jt.blog.controller;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
@RestController
@RequestMapping("/tag")
public class TagController {
@Autowired
private TagService tagService;
//private List tags=new ArrayList<>();
private List tags=new CopyOnWriteArrayList<>();//本地 cache
@GetMapping("/{id}")
public Tag doSelectById(@PathVariable("id") Long id){
//查询本地缓存
for(Tag t:tags){
if(t.getId().equals(id)) return t;
}
//查询redis,mysql
return tagService.selectById(id);
}
@PostMapping
public String doInsertTag(Tag tag){
//向数据库写入数据
tagService.insertTag(tag);//A
//更新本地缓存
tags.add(tag);
return "insert ok";
}
@PutMapping
public String doUpdateTag(Tag tag){//id=1,name=mysql 8.9
//向数据库写入数据
tagService.updateTag(tag);
//更新本地缓存
for(Tag t:tags){
if(t.getId().equals(tag.getId())){
t.setName(tag.getName());
}
}
return "update ok";
}
@GetMapping
public List doSelectTags(){//B
if(tags.isEmpty()) {
synchronized (tags) {
if(tags.isEmpty()) {
tags.addAll(tagService.selectTags());//1.redis,2.mysql
}
}
}
return tags;
}
/**Spring中Bean对象的生命周期方法,对象初始化时执行此方法*/
@PostConstruct
public void doInit(){
doTimerRefreshTask();
}
/**Spring中Bean对象的生命周期方法,Bean对象初始化时执行此方法*/
@PreDestroy
public void doDestory(){
//退出定时任务
timer.cancel();
}
private Timer timer;
//定义刷新任务
private void doTimerRefreshTask(){
//构建一个定时任务调度对象
timer=new Timer();
//构建一个任务对象
TimerTask task=new TimerTask() {
@Override
public void run() {
System.out.println("refresh cache");
tags.clear();
}
};
//执行任务对象(每隔5秒执行一次)
timer.schedule(task, 5000, 5000);
}
}
修改项目中的application.yml配置文件,修改redis配置,采用集群方式进行实现,例如:
spring:
datasource: #默认配置的是HikariDataSource,应用的是HikariCP链接池(HikariPool)
url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
#redis 集群配置
redis:
cluster: #redis 集群配置
nodes: 192.168.126.129:8010,192.168.126.129:8011,192.168.126.129:8012,192.168.126.129:8013,192.168.126.129:8014,192.168.126.129:8015
max-redirects: 3 #最大跳转次数
timeout: 5000 #超时时间
database: 0
jedis: #连接池
pool:
max-idle: 8
max-wait: 0
#日志配置
logging:
level:
com.jt: debug