当前位置:   article > 正文

Redis 缓存服务器_redis服务

redis服务

1.Redis介绍

官网:http://redis.cn/

  • Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
  • 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)。
  • 原子性说明: Redis的操作是单进程单线程操作,所以没有线程并发性的安全问题. 采用队列的方式一个一个操作.
  • Redis常见用法:
    • 1.Redis可以当做缓存使用;
    • 2.Redis可以当做数据库使用 验证码;
    • 3.Redis可以消息中间件使用 银行转账等。

2.Redis 安装

  • 1.解压 Redis文件
    执行命令:[root@localhost src]# tar -zxvf redis-5.0.4.tar.gz
  • 2.移动文件/修改文件
  • 3.安装Redis
    命令1: make
    命令2: make install
    • 修改Redis.conf配置文件
      (1)修改IP绑定
      展现行号 :set nu
      (2)关闭保护模式
      (3)开启后台启动
  • Redis服务器命令
    (1)启动redis:redis-server redis.conf(千万不能直接执行redis-server)
    (2)检查redis服务项:ps -ef | grep redis
    (3)进入客户端: redis-cli -p 6379(默认情况下端口6379可以省略)
    (4)关闭redis: kill -9 PID号 / redis-cli -p 6379 shutdown
  • Redis客户端命令:
    • String类型:

在这里插入图片描述

  • Hash类型
    说明:可以用散列类型保存对象和属性值
    例子:User对象{id:2,name:小明,age:19}

在这里插入图片描述

  • List类型
    说明:Redis中的List集合是双端循环列表,分别可以从左右两个方向插入数据;
    List集合可以当做队列使用,也可以当做栈使用;
    队列:存入数据的方向和获取数据的方向相反;
    栈:存入数据的方向和获取数据的方向相同。

在这里插入图片描述

  • Set类型
    说明:不重复且无序的字符串元素的集合。

在这里插入图片描述

3.Redis入门案例

(1)在pom.xml引入jar包文件

<!--spring整合redis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(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);
//setex实现了set与expire的连用
        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"));
}
 @Test
    public void testHash() throws InterruptedException {
        Jedis jedis=new Jedis("192.168.126.129",6379);
        jedis.hset("person","id","18");
        jedis.hset("person","name","hash测试");
        jedis.hset("person","age","23");
        Map<String,String > map=jedis.hgetAll("person");
        Set<String> set=jedis.hkeys("person");
        List<String> list=jedis.hvals("person");
    }

    @Test
    public void testList() throws InterruptedException {
        Jedis jedis=new Jedis("192.168.126.129",6379);
        //jedis.lpush("list", "1,2,3,4,5");
        jedis.lpush("list", "1","2","3","4","5");
        System.out.println(jedis.rpop("list"));
    }

    @Test
    public void testTx() {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //1.开启事务
        Transaction transaction=jedis.multi();
        try {
            transaction.set("a", "a");
            transaction.set("b", "b");
            transaction.set("c", "c");
            transaction.set("d", "d");
            transaction.exec();//2.提交事务
        }catch (Exception e){
            transaction.discard();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

4.SpringBoot 整合 Redis

4.1编辑pro配置文件

由于redis的IP地址和端口都是动态变化的,所以通过配置文件标识数据. 由于redis是公共部分,所以写到common中.

redis.host=192.168.126.129
redis.port=6379
  • 1
  • 2

4.2编辑配置类

说明: 编辑redis配置类.将Jedis对象交给Spring容器进行管理.

package com.jt.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;

@Configuration  //标识我是配置类
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {

    @Value("${redis.host}")
    private String  host;
    @Value("${redis.port}")
    private Integer port;

    @Bean
    public Jedis jedis(){

        return new Jedis(host, port);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

4.3对象与JSON串转化

简单对象转化:

  • 对象如何转化为JSON串的?
    步骤:
    1.获取对象的所有的getXXXX()方法.
    2.将获取的getXXX方法的前缀get去掉 形成了json的key=xxx
    3.通过getXXX方法的调用获取属性的值,形成了json的value的值.
    4.将获取到的数据 利用json格式进行拼接 {key : value,key2:value2…}
  • JSON如何转化为对象?{lyj:xxx}
    步骤:
    1.根据class参数类型,利用java的反射机制,实例化对象.
    2.解析json格式,区分 key:value
    3.进行方法的拼接 setLyj()名称.
    4.调用对象的setXXX(value) 将数据进行传递.
    5.最终将所有的json串中的key转化为对象的属性.
/**
     * 测试简单对象的转化
     */
    @Test
    public void test01() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L).setItemDesc("商品详情信息")
                .setCreated(new Date()).setUpdated(new Date());
        //对象转化为json
        String json = objectMapper.writeValueAsString(itemDesc);
        System.out.println(json);

        //json转化为对象
        ItemDesc itemDesc2 = objectMapper.readValue(json, ItemDesc.class);
        System.out.println(itemDesc2.getItemDesc());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

集合对象转化:

 /**
     * 测试集合对象的转化
     */
    @Test
    public void test02() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L).setItemDesc("商品详情信息1")
                .setCreated(new Date()).setUpdated(new Date());
        ItemDesc itemDesc2 = new ItemDesc();
        itemDesc2.setItemId(100L).setItemDesc("商品详情信息2")
                .setCreated(new Date()).setUpdated(new Date());
        List<ItemDesc> lists = new ArrayList<>();
        lists.add(itemDesc);
        lists.add(itemDesc2);
        //[{key:value},{}]
        String json = objectMapper.writeValueAsString(lists);
        System.out.println(json);

        //将json串转化为对象
        List<ItemDesc> list2 = objectMapper.readValue(json, lists.getClass());
        System.out.println(list2);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

4.4编辑 ObjectMapper 工具 API

package com.jt.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ObjectMapperUtil {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    //将对象转化为JSON
    public static String toJSON(Object target){
        try {
           return MAPPER.writeValueAsString(target);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    //将JSON转化为对象
    //需求: 如果用户传递什么类型,则返回什么对象
    public static <T> T toObject(String json,Class<T> targetClass){
        try {
            return MAPPER.readValue(json, targetClass);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

4.5实现商品分类缓存实现

实现步骤:
1.定义Redis中的key,key必须唯一不能重复,存取key=“ITEM_CAT_PARENTID::70”;
2.根据key 去redis中进行查询:有数据/没有数据;
3.没有数据:则查询数据库获取记录,并且将查询的结果转化为JSON之后保存到redis中 方便后续使用;
4.有数据:表示用户不是第一次查询,可以将缓存数据转化为对象直接返回即可.

  • 编辑ItemCatController
    说明: 切换业务调用方法,执行缓存调用
/**
     * 业务: 实现商品分类的查询
     * url地址: /item/cat/list
     * 参数:   id: 默认应该0 否则就是用户的ID
     * 返回值结果: List<EasyUITree>
     */
    @RequestMapping("/list")
    public List<EasyUITree> findItemCatList(Long id){
        Long parentId = (id==null)?0:id;
        //return itemCatService.findItemCatList(parentId);
        return itemCatService.findItemCatCache(parentId);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 编辑ItemCatServiceImpl
package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import com.jt.util.ObjectMapperUtil;
import com.jt.vo.EasyUITree;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;

@Service
public class ItemCatServiceImpl implements ItemCatService{

    @Autowired
    private ItemCatMapper itemCatMapper;
    @Autowired(required = false)    //程序启动是,如果没有改对象 暂时不加载
    private Jedis jedis;

    @Override
    public String findItemCatName(Long itemCatId) {

        return itemCatMapper.selectById(itemCatId).getName();
    }

    @Override
    public List<EasyUITree> findItemCatList(Long parentId) {
        //1.准备返回值数据
        List<EasyUITree> treeList = new ArrayList<>();
        //思路.返回值的数据从哪来? VO  转化 POJO数据

        //2.实现数据库查询
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",parentId);
        List<ItemCat> catList = itemCatMapper.selectList(queryWrapper);

        //3.实现数据的转化  catList转化为 treeList
        for (ItemCat itemCat : catList){
            long id = itemCat.getId();  //获取ID值
            String text = itemCat.getName();    //获取商品分类名称
            //判断:如果是父级 应该closed  如果不是父级 则open
            String state = itemCat.getIsParent()?"closed":"open";
            EasyUITree easyUITree = new EasyUITree(id,text,state);
            treeList.add(easyUITree);
        }
        return treeList;
    }

    /**
     * Redis:
     *      2大要素:    key: 业务标识+::+变化的参数  ITEMCAT::0
     *                  value: String  数据的JSON串
     * 实现步骤:
     *      1.应该查询Redis缓存
     *          有:  获取缓存数据之后转化为对象返回
     *          没有: 应该查询数据库,并且将查询的结果转化为JSON之后保存到redis 方便下次使用
     * @param parentId
     * @return
     */
    @Override
    public List<EasyUITree> findItemCatCache(Long parentId) {
        Long startTime = System.currentTimeMillis();
        List<EasyUITree> treeList = new ArrayList<>();
        String key = "ITEMCAT_PARENT::"+parentId;
        if(jedis.exists(key)){
            //redis中有数据
            String json = jedis.get(key);
            treeList = ObjectMapperUtil.toObject(json,treeList.getClass());
            Long endTime = System.currentTimeMillis();
            System.out.println("查询redis缓存的时间:"+(endTime-startTime)+"毫秒");
        }else{
            //redis中没有数据.应该查询数据库.
            treeList = findItemCatList(parentId);
            //需要把数据,转化为JSON
            String json = ObjectMapperUtil.toJSON(treeList);
            jedis.set(key, json);
            Long endTime = System.currentTimeMillis();
            System.out.println("查询数据库的时间:"+(endTime-startTime)+"毫秒");
        }
        return treeList;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 测试:使用Redis的效率提升了15-20倍

5.AOP实现Redis缓存

  • 现有代码存在的问题:
    1.如果直接将缓存业务,写到业务层中,如果将来的缓存代码发生变化,则代码耦合高,必然重写编辑代码.
    2.如果其他的业务也需要缓存,则代码的重复率高,开发效率低.
    解决方案: 采用AOP方式实现缓存.
  • 如何理解AOP
    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    名称: 面向切面编程
    作用: 降低系统中代码的耦合性,并且在不改变原有代码的条件下对原有的方法进行功能的扩展.
    公式: AOP(切面) = 切入点表达式(4种) + 通知方法(5种)

5.1通知类型(5种)

  • 1.before通知(前置通知):在执行目标方法之前执行
  • 2.afterReturning通知(后置通知):在目标方法执行之后执行
  • 3.afterThrowing通知(异常通知):在目标方法执行之后报错时执行
  • 4.after通知(最终通知):无论什么时候程序执行完成都要执行的通知
    上述的4大通知类型,不能控制目标方法是否执行;一般用来记录程序的执行的状态.一般应用与监控的操作.
  • 5.around通知(功能最为强大的) 在目标方法执行前后执行.因为环绕通知可以控制目标方法是否执行.joinPoint.proceed();控制程序的执行的轨迹.

5.2切入点表达式(4种)

理解: 切入点表达式就是一个程序是否进入通知的一个判断(IF)
作用: 当程序运行过程中,满足了切入点表达式时才会去执行通知方法,实现业务的扩展.
种类(写法):

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

5.3AOP入门案例

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

5.4 AOP 实现 Redis 缓存

5.4.1业务实现策略

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

5.4.2编辑自定义缓存注解

  • 问题: 如何控制 哪些方法需要使用缓存? cacheFind()
  • 解决方案: 采用自定义注解的形式 进行定义,如果 方法执行需要使用缓存,则标识注解即可.
  • 关于注解的说明:
    1.注解名称 : cacheFind
    2.属性参数 :
    2.1 key: 应该由用户自己手动添加 一般添加业务名称 之后动态拼接形成唯一的key
    2.2 seconds: 用户可以指定数据的超时的时间
@Target(ElementType.METHOD)         //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface CacheFind {

    public String preKey(); //定义key的前缀
    public int seconds() default 0;   //定义数据的超时时间.
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

5.4.3方法中标识注解

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

6. Redis 持久化策略

6.1什么是持久化

说明:Redis运行环境在内存中,如果redis服务器关闭,则内存数据将会丢失.
需求: 如何保存内存数据呢?
解决方案: 可以定期将内存数据持久化到磁盘中.
持久化策略规则:
当redis正常运行时,定期的将数据保存到磁盘中,当redis服务器重启时,则根据配置文件中指定的持久化的方式,实现数据的恢复.(读取数据,之后恢复数据.)

6.2持久化方式介绍

6.2.1 RDB 模式

  • 特点:
    1.RDB模式是redis的默认的持久化策略.
    2.RDB模式由于是定期持久化.弊端:可能导致数据的丢失.
    3.RDB模式记录的是Redis 内存数据的快照. 最新的快照会覆盖之前的内容 所有RDB持久化文件占用空间更小,持久化的效率更高.
  • 命令:
    1.save命令: 将内存数据持久化到磁盘中,主动的操作,会造成线程阻塞.
    2.bgsave命令:将内存数据采用后台运行的方式,持久化到文件中,异步的操作,由于是异步操作,不会造成阻塞.
  • 默认的持久化的机制
    save 900 1 如果在900秒内,执行了1次更新操作,则持久化一次
    save 300 10 如果在300秒内,执行了10次更新操作,则持久化一次
    save 60 10000 如果在60秒内,执行了10000次更新操作,则持久化一次
  • 配置:
    1.持久化文件名称:
    在这里插入图片描述
    2.持久化文件位置
    dir ./ 相对路径的写法
    dir /usr/local/src/redis 绝对路径写法

    3.RDB模式持久化策略

6.2.2 AOF 模式

  • 特点:
    1.AOF模式默认条件下是关闭的,需要用户手动的开启.
    2.AOF模式记录的是用户的操作过程,所以可以实现实时持久化操作.
    3.AOF模式由于记录的是实时的操作过程,所以持久化文件较大.需要定期维护.
  • 配置:
    1.启动AOF模式
    说明:如果一旦开启AOF模式,则以AOF模式为准.

    2.AOF模式持久化策略
    在这里插入图片描述

6.2.3持久化操作总结

1.当内存数据允许少量丢失时,采用RDB模式 (快)
2.当内存数据不允许丢失时,采用AOF模式(定期维护持久化文件)
3.一般在工作中采用 RDB+AOF模式共同作用,保证数据的有效性.

7. Redis 内存优化策略

7.1为什么需要内存优化

  • 说明: 由于redis在内存中保存数据.如果一直存储,则内存数据必然溢出.所以需要定期维护内存数据的大小.
  • 维护策略: 删除旧的不用的数据,保留新的常用的数据.
  • 解决方式:
    1.尽可能为保存在redis中的数据添加超时时间.
    2.利用算法优化旧的数据.

7.2 LRU 算法

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
计算维度: 时间T
注意事项: LRU算法是迄今为止内存中最好用的数据置换算法.

7.3 LFU 算法

LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 使用次数

7.4RANDOM算法

随机删除数据。

7.5TTL算法

根据剩余的存活时间,将马上要超时的数据提前删除.

7.6Redis内存数据优化

1.volatile-lru 设定了超时时间的数据采用lru算法
2.allkeys-lru 所有的数据采用LRU算法
3.volatile-lfu 设定了超时时间的数据采用lfu算法删除
4.allkeys-lfu 所有数据采用lfu算法删除
5.volatile-random 设定超时时间的数据采用随机算法
6.allkeys-random 所有数据的随机算法
7.volatile-ttl 设定超时时间的数据的TTL算法,提前删除
8.noeviction 默认规则,如果设定noeviction 则不删除数据,直接报错返回.
手动修改redis内存优化策略:

7.7关于Redis 缓存面试题

  • 问题出发点:
    由于缓存失效,导致大量的用户的请求,直接访问数据库服务器.导致负载过高,从而引发整体宕机的风险!!!
  • 运行速度:
    1.tomcat服务器 150-250 之间 JVM调优 1000/秒
    2.NGINX 3-5万/秒
    3.REDIS 读 11.2万/秒 写 8.6万/秒 平均 10万/秒

7.7.1缓存穿透

  • 说明: 用户频繁访问数据库中不存在的数据,可能出现缓存穿透的现象.如果该操作是高并发操作,则可能直接威胁数据库服务器.
  • 解决方案:
    1.采用IP限流的方式 降低用户访问服务器次数. IP动态代理(1分钟变一次)
    2.微服务的处理方式: 利用断路器返回执行的业务数据即可不执行数据库操作 从而保护了数据库.
    3.微服务处理方式: API网关设计. 不允许做非法操作。

7.7.2缓存击穿

  • 说明: 由于redis中某个热点数据由于超时/删除等操作造成数据失效.同时用户高并发访问该数据,则可能导致数据库宕机.该操作称之为缓存击穿.
  • 解决方案: 可以采用多级缓存的设计. 同时数据的超时时间采用随机数的方式.

7.7.3缓存雪崩

  • 说明: 由于redis内存数据大量失效.导致用户的访问命中率太低.大量的用户直接访问数据库,可能导致数据库服务器宕机. 这种现象称之为缓存雪崩.
  • 解决:
    1.采用多级缓存.
    2.设定不同的超时时间
    3.禁止执行 flushAll等敏感操作.
    4.提高redis缓存的命中率 调整redis内存优化策略 采用LRU等算法.

8. 实现 Redis 集群

8.1为什么需要搭建集群

  • redis分片特点:
    1.可以实现Redis内存数据的扩容.
    2.redis分片本身没有高可用效果的.如果宕机将直接影响用户的使用.
  • redis哨兵特点:
    1.Redis哨兵可以实现Redis节点的高可用.但是哨兵本身没有实现高可用机制.(最好不要引入第三方)
    2.Redis哨兵有主从的结构 实现了内存数据的备份. 但是没有实现内存扩容的效果.
  • 升级:
    需要Redis内容扩容同时需要Redis高可用性所以应该使用Redis集群.

8.2Redis集群搭建

1.首先关闭所有的Redis服务器:sh stop.sh
在这里插入图片描述
2.根据集群搭建步骤文档1.2完成配置文件的创建及修改
3.检查配置文件编辑是否正确.
4.删除多余的配置文件
在这里插入图片描述
5.创建启动脚本文件start.sh重启redis服务器
在这里插入图片描述
6.搭建redis集群
执行命令:

redis-cli --cluster create --cluster-replicas 1 192.168.126.129:7000 192.168.126.129:7001 192.168.126.129:7002 192.168.126.129:7003 192.168.126.129:7004 192.168.126.129:7005
  • 1

8.3集群入门案例

@Test
    public void testCluster(){
        Set<HostAndPort> sets = new HashSet<>();
        sets.add(new HostAndPort("192.168.126.129", 7000));
        sets.add(new HostAndPort("192.168.126.129", 7001));
        sets.add(new HostAndPort("192.168.126.129", 7002));
        sets.add(new HostAndPort("192.168.126.129", 7003));
        sets.add(new HostAndPort("192.168.126.129", 7004));
        sets.add(new HostAndPort("192.168.126.129", 7005));
        JedisCluster jedisCluster = new JedisCluster(sets);
        jedisCluster.set("jedis", "集群赋值");
        System.out.println(jedisCluster.get("jedis"));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

8.4关于选举机制-脑裂现象

  • 说明: 当集群进行选举时,如果连续3次都出现了平票的结果的则可能出现脑裂的现象.
  • 问题: 出现脑裂现象的概率是多少??? 1/8
  • 数学建模:
    抛银币连续3次出现平票的概念是多少? 1/8=12.5%
    第一次: 正正 正反 反正 反反 1/2
    第二次: 正正 正反 反正 反反 1/2
    第三次: 正正 正反 反正 反反 1/2
  • 预防: 增加主节点的数量可以有效的降低脑裂现象的发生.

8.5关于集群面试题

  • 1.redis集群中一共可以存储16384个KEY? 不对的
    答: 16384只是槽位的数量 只负责规划这个数据归谁管理的问题.至于数据如何存储,是由redis内存决定的.
    hash(key1) = 3000,
    hash(key2) = 3000;
  • 2.Redis集群中最多可以有多少台主机? 16384台主机.
  • 3.Redis中如果遇到多线程操作,是否有线程安全性问题 ? 没有
    因为:redis服务器是单进程单线程操作. 每次操作都是由一个线程执行,所以不会有线程安全性问题.
  • 4.Redis如何实现内存数据的优化? LRU/LFU/随机算法/TTL

9. SpringBoot 整合 Redis 集群

9.1编辑properties文件

说明:将redis集群的节点写入pro配置文件中

#配置单台redis服务器
#redis.host=192.168.126.129
#redis.port=6379

##配置redis分片
#redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

# redis集群配置
redis.nodes=192.168.126.129:7000,192.168.126.129:7001,192.168.126.129:7002,192.168.126.129:7003,192.168.126.129:7004,192.168.126.129:7005
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

9.2编辑RedisConfig配置类

@Configuration//标识我是配置类
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

//实现redis集群操作
    @Value("${redis.clusters}")
    private String clusters;

    @Bean
    public JedisCluster jedisCluster(){
        Set<HostAndPort> nodes = new HashSet<>();
        String[] nodesArray = clusters.split(",");
        for (String node : nodesArray){
            String host = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
          //HostAndPort hostAndPort = new HostAndPort(host,port);
            //nodes.add(hostAndPort);
nodes.add(new HostAndPort(host,port));
        }
        return new JedisCluster(nodes);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

9.3编辑CacheAOP配置

在AOP中注入Redis缓存对象:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/939481
推荐阅读
相关标签
  

闽ICP备14008679号