redis缓存实践

Redis 是一个由Salvatore Sanfilippo写的key-value存储系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

我们将redis作为mysql的缓存服务器,每次请求数据都会去redis里查一遍,若没有则请求mysql走真正的查询再把数据存到redis。当有新增,修改,删除操作的时候更新缓存中的数据以防止脏读的发生。

引入redis的依赖

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

配置文件

#redis
spring.redis.database=0
spring.redis.host=172.16.10.11
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1

redis缓存配置类

@Configuration
@EnableCaching  //开启缓存
public class RedisCacheConfig {

    /**
     * 自定义key的生成规则,保证key的唯一性
     * @return
     */
    @Bean  
    public KeyGenerator wiselyKeyGenerator(){  
        return new KeyGenerator() {  
            @Override  
            public Object generate(Object target, Method method, Object... params) {  
                StringBuilder sb = new StringBuilder();  
                sb.append(target.getClass().getName());  
                sb.append(method.getName());  
                for (Object obj : params) {  
                    sb.append(obj.toString());  
                }  
                return sb.toString();  
            }  
        };  
  
    }  
    
    @Bean
    public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        //设置key的超时时间
        cacheManager.setDefaultExpiration(60*30);
        return cacheManager;
    }

    @Bean  
    public RedisTemplate<String, String> redisTemplate(  
            RedisConnectionFactory factory) {  
        StringRedisTemplate template = new StringRedisTemplate(factory);  
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);  
        ObjectMapper om = new ObjectMapper();  
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
        jackson2JsonRedisSerializer.setObjectMapper(om);  
        template.setValueSerializer(jackson2JsonRedisSerializer);  
        template.afterPropertiesSet();  
        return template;  
    }  
}

封装一个redis的操作类

@Component  
public class RedisClient {  
  
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 获取key对应list的长度
     * @param key
     * @return
     */
    public long getListSize(String key) {  
        Long size = stringRedisTemplate.opsForList().size(key);
        return size;
    }  
    
    /**
     * 根据key获取list
     * @param key
     * @param i
     * @return
     */
    public String getListData(String key,int i) {  
        String json = stringRedisTemplate.opsForList().index(key,i);
        return json;
    }    
   
    /**
     * 逐条加入数据到key所在的list
     * @param key
     * @param json
     */
    public void setListData(String key,String json) {  
        stringRedisTemplate.opsForList().rightPush(key, json);
    }    
    
    /**
     * 修改index的数据
     * @param key
     * @param index
     * @param json
     */
    public void updateListData(String key, int index, String json) {  
        stringRedisTemplate.opsForList().set(key, index, json);
    }   
   
    /**
     * 获取key的值
     * @param key
     * @return
     */
    public String getValue(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }
    
    /**
     * 设置key的值
     * @param key
     * @param json
     */
    public void setValue(String key,String json){
        stringRedisTemplate.opsForValue().set(key, json);
    }
}  

写一个简单的controller

@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    private UserDao userDao;
         
    @RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
    @Cacheable(value="userCache",key= "'user' + #id" )
    @ResponseBody
        public User user(@PathVariable long id) {
        User u =  userDao.findOne(id);
        log.info("走db查询");
        return u;
        }
}

关于Spring缓存的三个注解:
1.@Cacheable
@Cacheable是用来声明方法是可缓存的。将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法。直接从缓存中取值。最简单的格式需要制定缓存名称。
默认key生成:
默认key的生成按照以下规则:

  • 如果没有参数,则使用0作为key
  • 如果只有一个参数,使用该参数作为key
  • 如果又多个参数,使用包含所有参数的hashCode作为key

也可以通过keyGenerator="wiselyKeyGenerator"自定义key的生成策略。

2.@CachePut
如果缓存需要更新,且不干扰方法的执行,可以使用注解@CachePut。@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

3.@CacheEvict
@CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,当指定了allEntries为true时,将会清除缓存中所有的元素。

首次访问http://localhost:8080/user/1时会打日志走了db查询,之后的每次访问都没有再打印db的日志。通过redis客户端可以看到我们缓存的数据

缓存.png

更新缓存
controller里新建一个更新数据的方法,配合@CachePut更新缓存

@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    private UserDao userDao;
         
    @RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
    @Cacheable(value="userCache",key= "'user' + #id" )
    @ResponseBody
    public User user(@PathVariable long id) {
        User u =  userDao.findOne(id);
        log.info("走db查询");
        return u;
        }

    @RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
    @CachePut(value= "userCache",key= "'user' + #id")
    @ResponseBody
    public User update(@PathVariable long id) {
        User user = userDao.findOne(id);
        user.setAge(10086);
        userDao.save(user);
        return user;
    }
}

访问http://localhost:8080/user/update/1 更新数据同时更新了缓存

上述是针对单一一条数据的缓存,实际中也会有缓存列表数据的场景,我们看下列表数据要如何缓存和更新
在原来的controller上新增查询列表的接口

    @RequestMapping(value = {""}, method = RequestMethod.GET)
    @ResponseBody
    public List<User> list() {
        List<User> dataList = new ArrayList<>();
        Long size = redisClient.getListSize("userList");

        if(0==size){
            dataList= userDao.findAll();
            log.info("FindAll  走db查询");
            if (null != dataList) {
                for (int i = 0; i < dataList.size(); i++) {
                    redisClient.setListData("userList", JSON.toJSONString(dataList.get(i)));
                }
            }
        }else{
            log.info("FindAll  从redis中获取");
            for (int i = 0; i < size; i++) {
                String json = redisClient.getListData("userList",i);
                User u = JSON.parseObject(json, User.class);  
                dataList.add(u);
            }
        }
        return dataList;
    }


    @RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
    @CachePut(value= "userCache",key= "'user' + #id")
    @ResponseBody
    public User update(@PathVariable long id) {
        User user = userDao.findOne(id);
        user.setAge(10086);
        userDao.save(user);
        Long size = redisClient.getListSize("userList");
        
        for (int j = 0; j < size; j++) {
            User u = JSON.parseObject(redisClient.getListData("userList", j), User.class);  
            if(id==u.getId()){
                redisClient.updateListData("userList", j, JSON.toJSONString(user));
                log.info("已更新缓存");
            }
        }
        
        return user;
    }

对于列表数据缓存的更新我还没有想好一个更好的方式,先实现一种。

缓存的删除

    @RequestMapping(value = {"/delete/{id}"})
    @CacheEvict(value = "userCache", allEntries = true)  
    @ResponseBody
    public String delete(@PathVariable long id) {
        userDao.delete(id);
        return "success";
    }

这样实现了一个简单的读写分离,实际场景中还会更加严谨,本文仅提供一个想法。

代码gti地址:http://git.oschina.net/gpy1994/redisdemo

福利


雏田
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容