Spring Data Redis实战之电商广告缓存案例

Spring Data Redis实战之品优购项目广告缓存案例

技术使用背景

     品优购前台首页轮播图广告

项目常见问题及思考

      目前的系统已经实现了广告后台管理和广告前台展示,但是对于首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。

Redis

    redis 是一款开源的 Key-Value  数据库,运行在内存中,由 ANSI C 编写。企业开发通常采用 Redis 来实现缓存。同类的产品还有 memcache 、memcached 、MongoDB 等。

 Redis的高并发和快速原因很多,总结一下几点:

        1. Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。

        2. 再说一下IO,Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。

        3. Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。

        4. 另外,数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。

        5. 还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
 

Jedis

    Jedis 是 Redis 官方推出的一款面向 Java 的客户端,提供了很多接口供 Java 语言调用。可以在 Redis 官网下载,当然还有一些开源爱好者提供的客户端,如 Jredis、SRP 等等,推荐使用 Jedis。

 

Spring Data Redis

    Spring-data-redis 是 spring 大家族的一部分,提供了在 srping 应用中通过简单的配置访问 redis 服务,对 reids 底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate 提供了 redis 各种操作、异常处理及序列化,支持发布订阅,并对 spring 3.1 cache 进行了实现。

spring-data-redis 针对 jedis 提供了如下功能:
1.连接池自动管理,提供了一个高度封装的“RedisTemplate”类
2.针对 jedis 客户端中大量 api 进行了归类封装,将同一类型操作封装为 operation 接口
    ValueOperations:简单 K-V 操作
    SetOperations:set 类型数据操作
    ZSetOperations:zset 类型数据操作
    HashOperations:针对 map 类型的数据操作
    ListOperations:针对 list 类型的数据操作

 

实战开始:

添加依赖:

(注:若配置了依赖版本控制,则不用再写版本号)



    redis.clients
    jedis
    2.8.1


    org.springframework.data
    spring-data-redis
    1.7.2.RELEASE
 

 

新建redis-config.properties

maxIdle:最大空闲数

maxWaitMillis:连接时的最大等待毫秒数

testOnBorrow:在提取一个 jedis 实例时,是否提前进行验证操作;如果为 true,则得到的 jedis实例均是可用的;

# Redis settings 
# server IP 
redis.host=127.0.0.1
# server port 
redis.port=6379
# server pass 
redis.pass=
# use dbIndex 
redis.database=0
# \u63A7\u5236\u4E00\u4E2Apool\u6700\u591A\u6709\u591A\u5C11\u4E2A\u72B6\u6001\u4E3Aidle(\u7A7A\u95F2\u7684)\u7684jedis\u5B9E\u4F8B 
redis.maxIdle=300
# \u8868\u793A\u5F53borrow(\u5F15\u5165)\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u6700\u5927\u7684\u7B49\u5F85\u65F6\u95F4\uFF0C\u5982\u679C\u8D85\u8FC7\u7B49\u5F85\u65F6\u95F4(\u6BEB\u79D2)\uFF0C\u5219\u76F4\u63A5\u629B\u51FAJedisConnectionException\uFF1B  
redis.maxWait=3000
# \u5728borrow\u4E00\u4E2Ajedis\u5B9E\u4F8B\u65F6\uFF0C\u662F\u5426\u63D0\u524D\u8FDB\u884Cvalidate\u64CD\u4F5C\uFF1B\u5982\u679C\u4E3Atrue\uFF0C\u5219\u5F97\u5230\u7684jedis\u5B9E\u4F8B\u5747\u662F\u53EF\u7528\u7684  
redis.testOnBorrow=true

 

 

新建 applicationContext-redis.xml,添加如下配置

   
  
    
     
        
       
       
     
  
     
   
     
    	  
   

 

 

后端服务实现层

    在对数据库中的广告进行CRUD操作之后,都应该更新缓存数据!!!

package com.pinyougou.content.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.dubbo.config.annotation.Service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.pinyougou.mapper.TbContentMapper;
import com.pinyougou.pojo.TbContent;
import com.pinyougou.pojo.TbContentExample;
import com.pinyougou.pojo.TbContentExample.Criteria;
import com.pinyougou.content.service.ContentService;

import entity.PageResult;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 服务实现层
 */
@Service
public class ContentServiceImpl implements ContentService {

    @Autowired
    private TbContentMapper contentMapper;

    /**
     * 查询全部
     */
    @Override
    public List findAll() {
        return contentMapper.selectByExample(null);
    }

    /**
     * 按分页查询
     */
    @Override
    public PageResult findPage(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Page page = (Page) contentMapper.selectByExample(null);
        return new PageResult(page.getTotal(), page.getResult());
    }

    /**
     * 增加
     */
    @Override
    public void add(TbContent content) {
        //新增广告后清除缓存
        redisTemplate.boundHashOps("content").delete(content.getCategoryId());
        contentMapper.insert(content);
        redisTemplate.boundHashOps("content").delete(content.getCategoryId());
    }


    /**
     * 修改
     */
    @Override
    public void update(TbContent content) {
        /**
         * 修改广告后清除缓存,考虑到用户可能会修改广告的分类,
         * 这样需要把原分类的缓存和新分类的缓存都清除掉
         */
        Long categoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
        redisTemplate.boundHashOps("content").delete(categoryId);
        contentMapper.updateByPrimaryKey(content);
        //如果分类 ID 发生了修改,清除修改后的分类 ID 的缓存
        if (categoryId.longValue() != content.getCategoryId().longValue()) {
            redisTemplate.boundHashOps("content").delete(content.getCategoryId());
        }
    }

    /**
     * 根据ID获取实体
     *
     * @param id
     * @return
     */
    @Override
    public TbContent findOne(Long id) {
        return contentMapper.selectByPrimaryKey(id);
    }

    /**
     * 批量删除
     */
    @Override
    public void delete(Long[] ids) {
        for (Long id : ids) {
            //删除广告清除缓存,根据id查询缓存需要在数据库删除数据之前
            Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
            redisTemplate.boundHashOps("content").delete(categoryId);
            contentMapper.deleteByPrimaryKey(id);
        }
    }


    @Override
    public PageResult findPage(TbContent content, int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);

        TbContentExample example = new TbContentExample();
        Criteria criteria = example.createCriteria();

        if (content != null) {
            if (content.getTitle() != null && content.getTitle().length() > 0) {
                criteria.andTitleLike("%" + content.getTitle() + "%");
            }
            if (content.getUrl() != null && content.getUrl().length() > 0) {
                criteria.andUrlLike("%" + content.getUrl() + "%");
            }
            if (content.getPic() != null && content.getPic().length() > 0) {
                criteria.andPicLike("%" + content.getPic() + "%");
            }
            if (content.getStatus() != null && content.getStatus().length() > 0) {
                criteria.andStatusLike("%" + content.getStatus() + "%");
            }

        }

        Page page = (Page) contentMapper.selectByExample(example);
        return new PageResult(page.getTotal(), page.getResult());
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List findByCategoryId(Long categoryId) {

        List list = (List) redisTemplate.boundHashOps("content").get(categoryId);
        if (list == null) {
            System.out.println("从数据库查询数据");
            TbContentExample contentExample = new TbContentExample();
            Criteria criteria = contentExample.createCriteria();
            criteria.andCategoryIdEqualTo(categoryId);//指定条件:id
            criteria.andStatusEqualTo("1");//指定开启状态
            contentExample.setOrderByClause("sort_order");//排序
            list = contentMapper.selectByExample(contentExample);
        } else {
            System.out.println("从缓存中查数据");
        }
        return list;
    }


}

 

效果如下:

管理员后台添加一条新广告,排序为1

Spring Data Redis实战之电商广告缓存案例_第1张图片

 

 

前台页面实时更新,因为排序为1,所有第一张就是新添加的广告:

Spring Data Redis实战之电商广告缓存案例_第2张图片

 

 

 

 

 

修改广告信息:

将一张广告状态改为有效

Spring Data Redis实战之电商广告缓存案例_第3张图片

 

 

 

前台出现这张广告

Spring Data Redis实战之电商广告缓存案例_第4张图片

 

 

 

 

你可能感兴趣的:(品优购项目总结)