redis缓存

Redis缓存服务器

网址: http://www.redis.cn/

2.2.1 Redis介绍

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
nginx: 3-5万/秒
redis: 读: 11.2万/秒 写: 8.6万/秒 平均10万/秒
吞吐量: 50万/秒

2.2.2 Redis安装

1).解压redis文件
在这里插入图片描述

2.2.3 修改redis.conf配置文件

1.修改IP绑定
在这里插入图片描述

2.2.4 Redis命令

1.1 Redis命令

1.1.1 String类型

命令 说明 案例
set 添加key-value set username admin
get 根据key获取数据 get username
strlen 根据key获取值的长度 strlen key
exists 判断key是否存在 exists name
返回1存在 0不存在
del 删除redis中的key del key
Keys 用于查询符合条件的key keys * 查询redis中全部的key
keys n?me 使用占位符获取数据
keys nam* 获取nam开头的数据
mset 赋值多个key-value mset key1 value1 key2 value2 key3 value3
mget 获取多个key的值 mget key1 key2
append 对某个key的值进行追加 append key value
type 检查某个key的类型 type key
select 切换redis数据库 select 0-15 redis中共有16个数据库
flushdb 清空单个数据库 flushdb
flushall 清空全部数据库 flushall
incr 自动加1 incr key
decr 自动减1 decr key
incrby 指定数值添加 incrby 10
decrby 指定数值减 decrby 10
expire 指定key的生效时间 单位秒 expire key 20
key20秒后失效
pexpire 指定key的失效时间 单位毫秒 pexpire key 2000
key 2000毫秒后失效
ttl 检查key的剩余存活时间 ttl key -2数据不存在 -1该数据永不超时
persist 撤销key的失效时间 persist key

1.1.1 Hash类型

说明:可以用散列类型保存对象和属性值

例子:User对象{id:2,name:小明,age:19}
命令 说明 案例
hset 为对象添加数据 hset key field value
hget 获取对象的属性值 hget key field
hexists 判断对象的属性是否存在 HEXISTS key field
1表示存在 0表示不存在
hdel 删除hash中的属性 hdel user field [field ...]
hgetall 获取hash全部元素和值 HGETALL key
hkyes 获取hash中的所有字段 HKEYS key
hlen 获取hash中所有属性的数量 hlen key
hmget 获取hash里面指定字段的值 hmget key field [field ...]
hmset 为hash的多个字段设定值 hmset key field value [field value ...]
hsetnx 设置hash的一个字段,只有当这个字段不存在时有效 HSETNX key field value
hstrlen 获取hash中指定key的值的长度 HSTRLEN key field
hvals 获取hash的所有值 HVALS user

1.1.1 List类型

说明:Redis中的List集合是双端循环列表,分别可以从左右两个方向插入数据.

List集合可以当做队列使用,也可以当做栈使用

队列:存入数据的方向和获取数据的方向相反

栈:存入数据的方向和获取数据的方向相同
命令 说明 案例
lpush 从队列的左边入队一个或多个元素 LPUSH key value [value ...]
rpush 从队列的右边入队一个或多个元素 RPUSH key value [value ...]
lpop 从队列的左端出队一个元素 LPOP key
rpop 从队列的右端出队一个元素 RPOP key
lpushx 当队列存在时从队列的左侧入队一个元素 LPUSHX key value
rpushx 当队列存在时从队列的右侧入队一个元素 RPUSHx key value
lrange 从列表中获取指定返回的元素 LRANGE key start stop
Lrange key 0 -1 获取全部队列的数据
lrem 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:
count > 0: 从头往尾移除值为 value 的元素。
count < 0: 从尾往头移除值为 value 的元素。
count = 0: 移除所有值为 value 的元素。  LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。
需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。
Lset 设置 index 位置的list元素的值为 value LSET key index value

1.1.1 Redis事务命令

说明:redis中操作可以添加事务的支持.一项任务可以由多个redis命令完成,如果有一个命令失败导致入库失败时.需要实现事务回滚.
命令 说明 案例
multi 标记一个事务开始 127.0.0.1:6379> MULTI
OK
exec 执行所有multi之后发的命令 127.0.0.1:6379> EXEC
OK
discard 丢弃所有multi之后发的命令

2 Redis高级应用

2.1 Redis入门案例

2.1.1 添加jar包文件

说明:在JT-PARENT项目中添加jar包文件





redis.clients

jedis

${jedis.version}







org.springframework.data

spring-data-redis

1.4.1.RELEASE



### 2.1.2 **入门案例-String**

/**

 * 连接单台redis 

 * 参数介绍:

 * redisIP地址.

 * redis:6379

 */

@Test

**public** **void** test01(){

Jedis jedis = **new** Jedis("192.168.126.166",6379);

jedis.set("redis", "redis入门案例");

System.**_out_**.println

("获取redis中的数据:"+jedis.get("redis"));

//为数据设定超时时间  单位秒

jedis.setex("1804", 100, "1804班");

}

2.1.3 入门案例-hash

@Test

**public** **void** test01(){

Jedis jedis = **new** Jedis("192.168.126.148", 6379);

jedis.hset("user", "id", "1");

jedis.hset("user", "name", "tomcat");

jedis.hset("user", "age", "18");

System.**_out_**.println("操作完成!!!"+jedis.hget("user", "id"));

Map map = jedis.hgetAll("user");

System.**_out_**.println(map);

}

结果展现:

操作完成!!!1

{name=tomcat, age=18, id=1}

2.1.4 入门案例-List

@Test

**public** **void** test02(){

Jedis jedis = **new** Jedis("192.168.126.148", 6379);

Long number = jedis.lpush("list", "a","b","c","d","e");

System.**_out_**.println("获取数据"+number);

List list= jedis.lrange("list", 0, -1);

System.**_out_**.println("获取参数:"+list);

}

结果展现:

获取数据5

获取参数:[e, d, c, b, a]

1.启动redis

redis-server redis.conf
在这里插入图片描述

  1. 进入redis客户端
`redis-cli  -p 6379` 

*   1

4.关闭redis
1).命令 redis-cli -p 6379 shutdown
2).kill命令 kill -9 pid号
在这里插入图片描述

2.3 SpringBoot整合Redis

2.3.1 导入jar包

 `
        
            redis.clients
            jedis
        
        
            org.springframework.data
            spring-data-redis
        ` 


2.3.2 入门案例

`package com.jt;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

public class TestRedis {
    /**
     * 1.实现redis测试
     * 报错检查:
     *      1.检查redis.conf配置文件  1.ip绑定问题   2.保护模式问题  3.后台启动问题
     *      2.检查redis启动方式       redis-server redis.conf
     *      3.检查防火墙
     *      */
    @Test
    public void test01(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.set("2007", "redis入门案例");
        System.out.println(jedis.get("2007"));
    }

    /**
     * 我想判断是否有key数据,如果没有则新增数据,如果有则放弃新增     */
    @Test
    public void test02(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
//        if(!jedis.exists("2007")){  //判断数据是否存在.
//            jedis.set("2007", "测试案例2222");
//        }
        //setnx作用: 如果有数据,则不做处理.
        jedis.setnx("2007", "测试高级用法");
        System.out.println(jedis.get("2007"));

    }

    /**
     * 需求:
     *      向redis中添加一个数据.set-key-value,要求添加超时时间 100秒.
     * 隐藏bug: 代码执行过程中,如果报错,则可能删除失败.
     * 原子性:  要么同时成功,要不同时失败.
     * 解决方法:  将入库操作/超时时间一齐设定. setex
     */
    @Test
    public void test03() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129",6379);
        //jedis.set("2007", "测试时间");
        //隐藏含义: 业务需要 到期删除数据
        //jedis.expire("2007", 100);
        jedis.setex("2007", 100, "测试时间");
        System.out.println(jedis.ttl("2007")+"秒");

    }

    /**
     * 1.如果数据存在,则不操作数据  setnx
     * 2.同时设定超时时间,注意原子性 setex
     * 参数说明:
     *   1.  XX = "xx";  只有key存在,则进行操作
     *   2.  NX = "nx";  没有key,进行写操作
     *   3.  PX = "px";  毫秒
     *   4.  EX = "ex";  秒
     */
    @Test
    public void test04() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129",6379);
        SetParams setParams = new SetParams();
        setParams.xx().ex(100);
        jedis.set("2007", "bbbbb",setParams);
        System.out.println(jedis.get("2007"));
    }
}` 

2.3.3 构建项目结构

redis缓存_第1张图片

AOP实现Redis缓存

5.1 现有代码存在的问题

1.如果直接将缓存业务,写到业务层中,如果将来的缓存代码发生变化,则代码耦合高,必然重写编辑代码.
2.如果其他的业务也需要缓存,则代码的重复率高,开发效率低.
解决方案: 采用AOP方式实现缓存.

5.2 AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

5.3 AOP实现步骤

公式: AOP(切面) = 通知方法(5种) + 切入点表达式(4种)

5.3.1 通知复习

1.before通知 在执行目标方法之前执行
2.afterReturning通知 在目标方法执行之后执行
3.afterThrowing通知 在目标方法执行之后报错时执行
4.after通知 无论什么时候程序执行完成都要执行的通知

上述的4大通知类型,不能控制目标方法是否执行.一般用来记录程序的执行的状态.
一般应用与监控的操作.

5.around通知(功能最为强大的) 在目标方法执行前后执行.
因为环绕通知可以控制目标方法是否执行.控制程序的执行的轨迹.

5.3.2 切入点表达式

1.bean(“bean的ID”) 粒度: 粗粒度 按bean匹配 当前bean中的方法都会执行通知.
2.within(“包名.类名”) 粒度: 粗粒度 可以匹配多个类
3.execution(“返回值类型 包名.类名.方法名(参数列表)”) 粒度: 细粒度 方法参数级别
4.@annotation(“包名.类名”) 粒度:细粒度 按照注解匹配

5.3.3 AOP入门案例

`package com.jt.aop;

import lombok.extern.apachecommons.CommonsLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Aspect //标识我是一个切面
@Component  //交给Spring容器管理
public class CacheAOP {

    //切面 = 切入点表达式 + 通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.ItemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")   // .* 一级包路径   ..* 所有子孙后代包
    //@Pointcut("execution(返回值类型 包名.类名.方法名(参数列表))")
    @Pointcut("execution(* com.jt.service..*.*(..))")
    //注释: 返回值类型任意类型   在com.jt.service下的所有子孙类   以add开头的方法,任意参数类型
    public void pointCut(){

    }

    /**
     * 需求:
     *      1.获取目标方法的路径
     *      2.获取目标方法的参数.
     *      3.获取目标方法的名称
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        String classNamePath = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("方法路径:"+classNamePath);
        System.out.println("方法参数:"+ Arrays.toString(args));
        System.out.println("方法名称:"+methodName);
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("环绕通知开始");
            Object obj = joinPoint.proceed();
            //如果有下一个通知,就执行下一个通知,如果没有就执行目标方法(业务方法)
            System.out.println("环绕通知结束");
            return null;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }

    }
}` 

5.4 AOP实现Redis缓存

5.4.1 业务实现策略

1).需要自定义注解CacheFind
2).设定注解的参数 key的前缀,数据的超时时间.
3).在方法中标识注解.
4).利用AOP 拦截指定的注解.
5).应该使用Around通知实现缓存业务.

5.4.2 编辑自定义注解

`@Target(ElementType.METHOD)         //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface CacheFind {

    public String preKey(); //定义key的前缀
    public int seconds() default 0;   //定义数据的超时时间.

}` 

5.4.3 方法中标识注解

redis缓存_第2张图片

5.4.4 编辑CacheAOP

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import lombok.extern.apachecommons.CommonsLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect //标识我是一个切面
@Component  //交给Spring容器管理
public class CacheAOP {

    @Autowired
    private Jedis jedis;

    /**
     * 注意事项:  当有多个参数时,joinPoint必须位于第一位.
     * 需求:
     *      1.准备key= 注解的前缀 + 用户的参数
     *      2.从redis中获取数据
     *         有: 从缓存中获取数据之后,直接返回值
     *         没有: 查询数据库之后再次保存到缓存中即可.
     *
     * 方法:
     *      动态获取注解的类型,看上去是注解的名称,但是实质是注解的类型. 只要切入点表达式满足条件
     *      则会传递注解对象类型.
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws Throwable {
        Object result = null;   //定义返回值对象
        String preKey = cacheFind.preKey();
        String key = preKey + "::" + Arrays.toString(joinPoint.getArgs());

        //1.校验redis中是否有数据
        if(jedis.exists(key)){
            //如果数据存在,需要从redis中获取json数据,之后直接返回
            String json = jedis.get(key);
            //1.获取方法对象,   2.获取方法的返回值类型
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            //2.获取返回值类型
            Class returnType = methodSignature.getReturnType();
            result = ObjectMapperUtil.toObject(json,returnType);
            System.out.println("AOP查询redis缓存!!!");
        }else{
            //代表没有数据,需要查询数据库
            result = joinPoint.proceed();
            //将数据转化为JSON
            String json = ObjectMapperUtil.toJSON(result);
            if(cacheFind.seconds() > 0){
                jedis.setex(key, cacheFind.seconds(), json);
            }else{
                jedis.set(key,json);
            }
            System.out.println("AOP查询数据库!!!");
        }
        return result;
    }

   /* @Around("@annotation(com.jt.anno.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取目标对象的Class类型
        Class targetClass = joinPoint.getTarget().getClass();
        //2.获取目标方法名称
        String methodName = joinPoint.getSignature().getName();
        //3.获取参数类型
        Object[] argsObj = joinPoint.getArgs();
        Class[]  argsClass = null;
        //4.对象转化为class类型
        if(argsObj.length>0){
           argsClass = new Class[argsObj.length];
            for(int i=0;i

你可能感兴趣的:(java)