关于Redis入门及透明(透明即使用AOP实现低耦合)缓存层实现

emm...博主在前些天学习了一下Redis,以及在网上查询了部分资料、将看到的大牛们写的相关文章整合了一下。。(如有侵权多见谅...)分享一些经验,可能写的或者了解的不是很透彻,哈哈...大牛们请无视

那么在讲解之前我们先来了解一下Redis

一、什么是Redis

  • Redis是一个缓存 ( 缓存的优势在于它的速度远远高于硬盘运行速度) 服务器   ——高性能系统必备技术之一
  • Redis是一个开源的、使用ANSI C语言编写的、支持网络交互的、遵循BSD协议、可基于内存也可持久化的Key-Value数据库

二、Redis的优点:

  • Redis的速度非常快,每秒可执行大约 110000 次的设置(SET)操作,每秒大约可执行 81000 次的读取/获取(GET)操作。
  • Redis可以支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)     由于开发人员常用的大多数数据类型,例如列表,集合,排序集和散列等等。这使得Redis很容易被用来解决各种问题,因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
  • 操作具有原子性所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新的值,避免接收异常数据。
  • 支持数据持久化,支持AOF和RDB两种持久化方式
  • Redis是一个多实用工具,可用于多种用例,如:缓存,消息队列(Redis本地支持发布/订阅),应用程序中的任何短期数据,例如,web应用程序中的会话,网页命中计数等

缺点:

Redis比较难在线扩容,在集群的容量上限时,在线扩容会变得很复杂。

 三、Redis与其他键值存储系统相比:

  • Redis是键值数据库系统的不同进化路线,它的值可以包含更复杂的数据类型,可在这些数据类型上定义原子操作。
  • Redis是一个内存数据库,但在磁盘数据库上是持久的,因此它代表了一个不同的权衡,在这种情况下,在不能大于存储器(内存)的数据集的限制下实现非
  • 常高的写和读速度。
  • 内存数据库的另一个优点是,它与磁盘上的相同数据结构相比,复杂数据结构在内存中存储表示更容易操作。 因此,Redis可以做很少的内部复杂性。

四、Redis执行流程 

  • 业务层所有指定的查询方法,都要先去缓存去查询,如果有,就直接返回缓存的数据(对象);如果没有,就去数据库查询,并且把数据库中查到的数据,放入缓存中;
  • 删除、修改:判断Redis缓存中key是否存在,如果有,则删除缓存中的数据(对象);如果没有,则直接执行。这里修改语句的话博主认为不需要在缓存中去修改,而选择直接删除的原因是因为,在用户修改完之后可能并不需要立即使用,并且多了许多代码所产生的收益微乎其微(还可以偷懒..)。
  • 增:不必要提前查出来放到缓存中或者进行key的非空验证,所以在下面的代码中博主没有做增加的环绕增强

 

一些实现细节大家可以看图来理解

下面来看一下相关的实现代码。


实体类以及Dao层、Service层操作省略


Bean配置 

Spring-Beans.xml内代码

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context-4.1.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

       <aop:aspectj-autoproxy/> //声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面

       <context:annotation-config/>// 是用于激活那些已经在spring容器里注册过的bean(无论是通过xml的方式还是通过package sanning的方式)上面的注解。

       <context:component-scan base-package="com.landun.lichenming"/>//  在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean

       

<!-- 配置Jedis -->

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">

        <property name="maxActive" value="20"/>//连接池最大的连接数

        <property name="maxIdle" value="10"/> //连接池中最多可空闲maxIdle个连接 ,这里取值为10,表示即使没有数据库连接时依然可以保持10空闲的连接,而不被清除,随时处于待命状态。设 0 为没有限制。minIdle 表示 连接池中最少空闲maxIdle个连接

        <property name="maxWait" value="1000"/>// maxWait 连接池中连接用完时,新的请求等待时间,毫秒,取值-1,表示无限等待,直到超时为止,也可取值9000,表示9秒后超时。超过时间会出错误信息一般把maxActive设置成可能的并发量就行了

        <property name="testOnBorrow" value="true"/>// 从连接池获取一个连接时,验证有效性

    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">

        <constructor-arg index="0" ref="jedisPoolConfig" />

        <constructor-arg index="1" value="127.0.0.1" />

        <constructor-arg index="2" value="6379" />

        <!-- timeout -->

        <constructor-arg index="3" value="2000" />

        <!-- password -->

        <constructor-arg index="4" value="62416038626259878353394562797" />

    </bean>

<!--导入spring-mybatis.xml-->

<import resource="spring-mybatis.xml"/>

</bean>

配置mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

       "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

       <settings>

             <setting name="lazyLoadingEnabled" value="false"/>//延迟加载的开关,即懒加载

为什么使用懒加载:因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,

       </settings>

       

       <typeAliases>

             <package name="com.landun.lichenming.entity"/>//映射实体类

       </typeAliases>

       

       <mappers>

             <mapper resource="com/landun/lichenming/dao/UserMapper.xml"/>//映射dao内的映射文件

       </mappers>

</configuration>

配置RedisCache(进行jedis的查询,删除等操作)

package com.landun.lichenming.cache;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

@Component

public class RedisCache {

    @Resource

    private JedisPool jedisPool;

    public Object getFromCache(String key) {

        Jedis jedis = jedisPool.getResource();

        byte[] result = jedis.get(key.getBytes());

        // 如果查询没有为空

        if (null == result) {

            return null;

        }

        // 查询到了,反序列化

        return SerializeUtil.unSerialize(result);

    }

    // 将数据库中查询到的数据放入redis

    public void save(String redisKey, Object obj) {

        // 序列化

        byte[] bytes = SerializeUtil.serialize(obj);

        // 存入redis

        Jedis conn = jedisPool.getResource();

        String result = conn.set(redisKey.getBytes(), bytes);

        if ("OK".equals(result)) {

            System.out.println("[RedisCache]:数据成功保存到redis cache...");

        }

    }

    // 将数据库查询到的key删除

    public void del(String key) {

        Jedis jedis = jedisPool.getResource();

        if (jedis.exists(key.getBytes())) {

            jedis.del(key.getBytes());

            if (jedis.exists(key.getBytes())) {

                System.out.println(" [RedisCache] :数据删除失败!!!请联系程序员");

            } else {

                System.out.println(" [RedisCache] :数据删除成功");

            }

        } else {

            System.out.println(" [RedisCache] :内存数据不存在");

        }

    }

}

配置SerializeUtil(对传进来的数据进行序列化与反序列化操作)

package com.landun.lichenming.cache;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class SerializeUtil {

    //序列化

    public static byte[] serialize(Object obj){

        ObjectOutputStream obi=null;

        ByteArrayOutputStream bai=null;

        try {

            bai=new ByteArrayOutputStream();

            obi=new ObjectOutputStream(bai);

            obi.writeObject(obj);

            byte[] byt=bai.toByteArray();

            return byt;

        } catch (IOException e) {

            e.printStackTrace();

        }

        return null;

    }

    //反序列化

    public static Object unSerialize(byte[] byt){

        ObjectInputStream oii=null;

        ByteArrayInputStream bis=null;

        bis=new ByteArrayInputStream(byt);

        try {

            oii=new ObjectInputStream(bis);

            Object obj=oii.readObject();

            return obj;

        } catch (Exception e) {

           

            e.printStackTrace();

        }

        return null;

    }

}

定义切面

package com.landun.lichenming.cache;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 该类表示一个切面,表示: “调用find*方法根据id查某个对象时,先从缓存查询,如果没有,再查数据库”
 * 
 */
@Component
// 使用Aspect注解将RedisCacheAspect定义为切面
@Aspect
public class RedisCacheAspect {

    private static String PROJECT_NAME = "REDIS-";
    private static String MODULE_NAME = "AUTH-";

    private static final Logger log = Logger.getLogger(RedisCacheAspect.class);

    @Resource
    private RedisCache redisCache;

    // 定义切入点
    @Pointcut("execution(* com.ketai.liudun.service..*Service.find*(..))")
    public void getCachePointcut() {
    }

    @Around("getCachePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {

        // 先获取目标方法参数
        String id = null;
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            id = String.valueOf(args[0]);
        }

        // 得到一个具有唯一性的缓存key
        String redisKey = getRedisKey(joinPoint, id);
        log.debug("进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
        // 根据key获取从redis中查询到的对象
        Object data = redisCache.getFromCache(redisKey);
        // 如果查询到了
        if (null != data) {
            log.debug("[" + redisKey + "]缓存命中:" + data);
            return data;
        }

        // 没有查到,那么查询数据库
        try {
            data = joinPoint.proceed();// 执行目标方法(service中的find*方法)
        } catch (Throwable e) {
            e.printStackTrace();
        }
        log.debug("没有从redis中取出数据,改为从数据库中查询的数据..." + data);

        // 后置:将数据库中查询的数据放到redis中
        redisCache.save(redisKey, data);
        log.debug("数据已存储到redis" + data);

        // 将查询到的数据返回
        return data;

    }

    // 定义切入点
    @Pointcut("execution(* com.ketai.liudun.service..*Service.update*(..))")
    public void updateCachePointcut() {
    }

    @Pointcut("execution(* com.ketai.liudun.service..*Service.delete*(..))")
    public void deleteCachePointcut() {
    }

    @Around("updateCachePointcut() or deleteCachePointcut()")
    public Object UpdateAnddelete(ProceedingJoinPoint joinPoint) {

        // 先获取目标方法参数
        String id = null;
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            id = String.valueOf(args[0]);
        }

        // 得到一个具有唯一性的缓存key
        String redisKey = getRedisKey(joinPoint, id);
        // 根据key获取从redis中查询到的对象
        log.debug("update进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
        Object data = redisCache.getFromCache(redisKey);
        // 如果查询到了
        if (null != data) {
            log.debug("update[" + redisKey + "]缓存命中:" + data);
            try {
                data = joinPoint.proceed();// 执行目标方法(service中的find*方法)
            } catch (Throwable e) {
                e.printStackTrace();
            }
            // 执行删除操作
            redisCache.del(redisKey);
            log.debug("数据已从redis删除");
            return 1;
        } else {
            return 0;
        }
    }
    /**
     * 拼接一个独特的key
     * @param joinPoint
     * @param id
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, String id) {
        return PROJECT_NAME + MODULE_NAME + joinPoint.getTarget().getClass().getName() + "-" + id;
    }
}
 

猜你喜欢

转载自blog.csdn.net/qq_42709262/article/details/81067658
今日推荐