springboot-分布式实例开发 (七)-Redis


之前的课程一直是在搭项目,也是为接下来的技术讲解做准备。今天就是开始第一个缓存技术-Redis的教程

1.Redis

在电商网站中,用户的查询的需求量远远高于更新的数据量,所以为了提高服务器响应的能力.需要添加缓存服务器.
在这里插入图片描述
缓存服务器特点

  1. 缓存数据结构采用k-v格式 key是唯一标识. value一般存储对象JSON数据.
  2. 缓存数据应该存储到内存中,速度是最快的.
  3. 定期将缓存数据持久化到硬盘中.
  4. 为了维护内存大小,定期将缓存数据删除. lru算法 lfu算法
  5. 编程语言 首选C语言.

1.1 简单介绍

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
速度:

  1. 读:11.2万/秒
  2. 写:8.6万/秒
    平均10万/秒

1.2 下载和安装

官网下载:官网地址
在这里插入图片描述
打开虚拟机,xshell连接上.cd到一个目录(随意)

cd  /usr/local/src

将下载的redis压缩包上传。推荐一款ftp软件filezilla。可以百度直接下载即可。
上传后查看
在这里插入图片描述解压文件

tar -xvf redis-5.0.4.tar.gz

删除压缩包文件/修改文件名称

删除压缩包命令 rm

rm redis-5.0.4.tar.gz

输入命令后,要在后面再次输入y,表示确定删除
使用mv命令,将解压后的文件移动到新的文件中,这操作就相当于重命名。

mv redis-5.0.4 redis

在这里插入图片描述

1.3 编译和安装

进入到redis目录下 cd redis
进行编译 make
在这里插入图片描述稍等片刻,编译成功
在这里插入图片描述
安装redis 输入命令 make install
在这里插入图片描述

1.4 修改Redis配置文件

执行编辑指令 vim redis.conf 切记进入编辑时需要注意,在刚进入时还未处于编辑状态,先点击a
,当左下角出现insert时才是真正的编辑状态。重要的是千万千万千万不要用数字键盘输入数字。请自学Linux文件编辑命令

在这里插入图片描述

  1. 去除IP绑定
    在这里插入图片描述
    2.关闭保护模式 将yes改为no
    在这里插入图片描述
  2. 开启后台启动
    在这里插入图片描述
    编辑好后,先点击ESC。退出编辑状态。左下角的insert标识消失,才能进行下一步
    输入命令:wq。回车即可
    在这里插入图片描述
    切记每一步操作很重要

1.5 Redis服务器命令

配置好后,我们启动一下试试

redis-server redis.conf

在这里插入图片描述输入命令

ps -ef |grep redis

在这里插入图片描述
可以看到启动成功

1.5.1 redis客户端

redis-cli -p 6379

在这里插入图片描述
退出就输入exit
在这里插入图片描述

1.5.2 关闭redis

方式1:

ps -ef |grep redis  查找redis的PID
kill -9 PID号

方式2:使用命令关闭

redis-cli -p  6379 shutdown

1.5.3 redis 命令

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
相关基本命令请看 有道云笔记
我们先写个测试试试,看看具体的效果:
1.引入jar包

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
</dependency>

创建TestRedis.java

1.5.3.1 string类型测试

string 是 redis 最基本的类型,一个 key 对应一个 value。
string 类型是二进制安全的。 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

public class TestRedis {
    /**
     * 测试String类型操作
     * 服务器前提:1.防火墙关闭  2.IP绑定注释  3.保护模式关闭
     */
    @Test
    public void test01() {
     	//new Jedis() 第一个参数是服务器ip,第二个是端口号
        Jedis jedis = new Jedis("192.168.180.160", 6379);
        jedis.set("name","olderSheep");
        System.out.println(jedis.get("name"));
    }
}

查看运行结果:
在这里插入图片描述为了验证服务器中是否真正的存在,我们去服务器验证一下:
连接服务器:直接进入redis客户端
在这里插入图片描述
我们使用get命令去找key为name的值,可以发现已存在。验证成功。
在这里插入图片描述
(可跳过)推荐一款连接redis的客户端软件RedisDesktopManager。百度可直接找到,直接下载即可
在这里插入图片描述
打开软件,连接
在这里插入图片描述连接名称随便写,Host是服务器ip, port为端口号。直接确认即可
在这里插入图片描述
双击,并依次打开,可以看到刚才存储的key:name。右边显示了它的value。这里也可以进行删除等操作。这个客户端特点就是更直观。
在这里插入图片描述

1.5.3.2 hash 类型测试

Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

@Test
public void testHash() {
   Jedis jedis = new Jedis("192.168.180.160", 6379);
   jedis.hset("person", "id", "100");
   jedis.hset("person", "name", "人");
   jedis.hset("person", "age", "18");
   System.out.println(jedis.hgetAll("person"));
}

运行结果:
在这里插入图片描述使用客户端时,先进行右击鼠标先刷新就能看到新添加的数据。
在这里插入图片描述

1.5.3.3 List类型测试

list是一个字符串链表,既然是链表,那就可以左,右都可以进行插入数据。(链表也是数据结构的相关知识,不是很明白的朋友,也可以与我一起讨论数据结构与算法。
若key不存在,创建链表
若key存在,链表添加内容
若链表值全部移除,key也自动消失
效率比较

  1. 链表的头尾元素操作,效率都非常高
  2. 链表中间元素操作,效率比较低

操作命令
Lpush:先进后出,在列表头部插入元素
Rpush:先进先出,在列表的尾部插入元素
Lrange:出栈,根据索引,获取列表元素
Lpop:左边出栈,获取列表的第一个元素
Rpop:右边出栈,获取列表的最后一个元素
Lindex:根据索引,取出元素
Llen:链表长度,元素个数
Lrem:根据key,删除n个value
Ltrim:根据索引,删除指定元素
Rpoplpush:出栈,入栈
Lset:根据index,设置value
Linsert before:根据value,在之前插入值
Linsert after:根据value,在之后插入值

注意
出栈,该元素在链表中,就不存在了
左边,默认为列表的头部,索引小的一方
右边,默认为列表的尾部,索引大的一方

我们测试一下

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

使用lpush插入数据,即为从左边依次插入数据。那顺序即为4,3,2,1。
使用rpop就是获取最右边的数据并删除。也可称右边出栈。
因此结果获取的是1.剩下的数据是4 3 2.
在这里插入图片描述在这里插入图片描述
其他命令,朋友可自行测试。

1.6 redis 业务实现

数据添加缓存的条件

  1. 变化范围小的数据
    a.层级菜单. b.部门的组织关系 c.省市县乡 d.邮编等
  2. 用户频繁访问的数据.
    为了将其数据统一成json格式,我们先写一个jsonapi工具。也可以使用阿里提供的api。
    我们将其放在jt-common中,创建utils包,将此api放在其中即可。朋友也可随意。
/**
 * json转化工具
 */
@Configuration
public class JsonUtil {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    /**
     * 根据API将对象转化为JSON.同时将JSON转化为对象.
     *
     * @param obj 转化的对象
     * @return 转化后的json串
     */
    public static String toJSON(Object obj) {
        String result = null;
        try {
            result = MAPPER.writeValueAsString(obj);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return result;
    }

    /**
     * json转化为对象
     *
     * @param json        json串
     * @param targetClass 要转化的实体class
     * @param <T>         转化后的实体对象
     * @return
     */
    public static <T> T toObject(String json, Class<T> targetClass) {
        T obj = null;
        try {
            obj = MAPPER.readValue(json, targetClass);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return obj;
    }
}

编辑pro配置文件。ip地址需要添加自己服务器的ip。
在这里插入图片描述

#配制单台redis
redis.host=192.168.180.160
redis.port=6379

在这里插入图片描述
创建配置类。在jt-common中创建config包,在里创建工具类

@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);
	}
}

修改ItemCatController

@RequestMapping("/list")
	public List<EasyUITree> findItemCatList(@RequestParam(defaultValue="0",name ="id") Long parentId){
		//查询一级商品分类信息
//		return itemCatService.findEasyUITreeList(parentId);
		return itemCatService.findEasyUITreeCache(parentId);
	}

ItemCatServiceImpl
引入jedis。我们在配置中使用@Bean注解。注入了一个实例对象。所以在这直接引入,就可以使用

 @Autowired
 private Jedis jedis;
@Override
public List<EasyUITree> findEasyUITreeCache(Long parentId) {
   List<EasyUITree> treeList = new ArrayList<EasyUITree>();
   String key = "ITEM_CAT_"+parentId;
   //1.根据key查询redis服务器
   String result = jedis.get(key);
   if(StringUtils.isEmpty(result)) {
   		//表示缓存没有数据,需要查询数据库
   		treeList = findEasyUITreeList(parentId);
   		//将数据保存到缓存中
  	 	String value = JsonUtil.toJSON(treeList);
   		jedis.set(key, value);
   		System.out.println("查询后台数据库!!!!!");
   }else {
      	//缓存中有数据
      	treeList = JsonUtil.toObject(result, treeList.getClass());
      	System.out.println("查询Redis缓存");
    }
    return treeList;
}

开启项目,我们查看一下
点击新增商品,选择类目。第一次首先会先查数据库。
看后台日志打印
在这里插入图片描述
当我们再次访问时:
在这里插入图片描述
再看时间对比:
第一次:
在这里插入图片描述
第二次:
在这里插入图片描述
可以看出时间缩短了。

1.7 AOP缓存实现

1.7.1 AOP(面向切面编程)

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要实现的功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
底层原理
使用的动态代理模式实现
关于面向切面编程,朋友可自行进行查阅资料,目前本人还没书写相关博客内容,后期会专门加上

1.7.2 业务实现

  1. 需要自定义注解, Cache_Find
  2. redis中存储需要 key:value
    Key:如果用户自己指定了key,使用用户的.
    Key:如果用户自己没有指定,需要自动生成. 类名+方法名+第一个参数
    Value:是用户查询的java对象结果的json串

自定义注解
在这里插入图片描述

//在运行期生效
@Retention(RetentionPolicy.RUNTIME)
//修饰方法 该注解对谁有效
@Target(ElementType.METHOD)
public @interface CacheFind {
    /**
     * //用户可以不写,如果为空串表示自动生成key
     * @return
     */
    String key() default "";

    /**
     * 0表示用户设置该数据不需要超时时间,如果不等于0则说明用户自己定义了超时时间
     * @return
     */
    int seconds() default 0;  					  

}

编辑Controller添加注解。那我们就不需要在serviceImpl中自己去写相关查询缓存的方法。

@RequestMapping("/list")
	@CacheFind
	public List<EasyUITree> findItemCatList(@RequestParam(defaultValue="0",name ="id") Long parentId){
		//恢复到一开始调用的方法
		return itemCatService.findEasyUITreeList(parentId);
		//使用aop,则把这儿注释掉
//		return itemCatService.findEasyUITreeCache(parentId);
	}

既然不需要在serviceImp中写,那就需要专门处理缓存的api。
创建CacheAspect。并交给spring管理
在这里插入图片描述

@Component	//将对象交给spring容器管理
@Aspect		//表示标识切面  切面=切入点+通知
public class CacheAspect {
	
	@Autowired
	private Jedis jedis;
	/**
	 * 环绕通知:
	 * 	1.返回值 必须为object类型 
	 * 			表示执行完成业务之后返回用户数据对象
	 *  2.参数	1.必须位于第1位
	 *  		2.参数类型必须为 ProceedingJoinPoint 因为要控制目标方法执行
	 *  
	 *  3.关于注解取值规则:
	 *  	springAOP中提供了可以直接获取注解的方法,但是要求参数的名称
	 *  	必须一致.否则映射错误
	 *  
	 *  缓存操作
	 *  1.根据key 查询缓存服务器redis
	 */
	@Around("@annotation(cacheFind)")
	public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind) {
		String key = getKey(joinPoint,cacheFind);
		String resultJSON = jedis.get(key);
		Object resultData = null;
		if(StringUtils.isEmpty(resultJSON)) {
			//需要执行真实的目标方法
			try {
				resultData = joinPoint.proceed();
				String value = JsonUtil.toJSON(resultData);
				
				//判断数据是否永久保存
				if(cacheFind.seconds()>0)
					jedis.setex(key, cacheFind.seconds(), value);
				else 
					jedis.set(key, value);
				System.out.println("AOP查询数据库成功!!!");
			} catch (Throwable e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
		}else {
			//由于业务需要,要获取目标方法的返回值类型
			Class returnType = getType(joinPoint);
			//表示redis中有数据 将json转化为对象
			resultData = JsonUtil.toObject(resultJSON,returnType);
			System.out.println("AOP查询缓存成功!!!!");
		}
		return resultData;
	}
	
	private Class getType(ProceedingJoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		return signature.getReturnType();
	}

	/**
	 * 策略:
	 * 	1.如果用户有key,使用用户自己的key
	 * 	2.如果用户自己没有定义,则自动生成
	 * 		类名+方法名+第一个参数
	 * @param joinPoint
	 * @param cacheFind
	 * @return
	 */
	private String getKey(ProceedingJoinPoint joinPoint, CacheFind cacheFind) {
		String key = cacheFind.key();  //默认值""
		if(StringUtils.isEmpty(key)) {
			//用户自动生成
			String methodName = 
			joinPoint.getSignature().getName();
			String className = 
			joinPoint.getSignature().getDeclaringTypeName();
			String arg1 = String.valueOf(joinPoint.getArgs()[0]);
			//com.ly.controller.list::0
			return className+"."+methodName+"::"+arg1;
		}else {
			return key;
		}
	}
}

AOP中有五大通知

  1. 前置通知:在目标方法执行之前执行的通知
  2. 环绕通知:在目标方法执行之前和之后都可以执行额外代码的通知。
        在环绕通知中必须显式的调用目标方法,否则目标方法不会执行。
        这个显式调用时通过ProceedingJoinPoint来实现,可以在环绕通知中接收一个此类型的形 参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参位置
    意思就是说使用ProceedingJoinPoint作为第一个参数,并实现目标方法。其他的参数需要放在第二个或以后
    要注意,只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。
  3. 后置通知:在目标方法执行之后的通知。
    4.异常通知:在目标方法抛出异常时执行的通知
    5.最终通知:是在目标方法执行之后执行的通知。和后置通知不同的是,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回,比如说抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到。
//参数可以有,也可以没有。但其他是固定格式
@Before("...")
public void before(JoinPoint jt){
}
//环绕通知,调用目标方法,是必须使用ProceedingJoinPoint 显式调用,且在第一个参数位置
@Around("...")
public Object around(ProceedingJoinPoint joinPoint){
}
//后置通知
@AfterReturning("...")
public void afterReturn(JoinPoint jt){
}
/*异常通知 可以配置传入JoinPoint获取目标对象和目标方法相关信息,
*但必须处在参数列表第一位,另外,还可以配置参数,让异常通知可以接收到目标方法抛出来的异常对象
*/
@AfterThrowing("...")
public void afterThrowing(JoinPoint joinPoint) {
}
//最终通知
@After("...")
public void after(JoinPoint joinPoint) {
}

说明一下。上述的注解对应不同的通知类型,注解中还有参数。因为内容太多,这里不过多介绍。
切记注解,返回值类型是固定的格式。参数按照要求。其他的随意。

github地址

https://github.com/lmy1965673628/jingtao.git.

总结

这一节主要介绍了redis的安装以及项目的具体应用。aop的原理介绍,aop是spring的核心组成部分,所以关于aop的知识体系也是很多。我会专门去写一篇博客,朋友也可自查资料去学习相关知识。aop是很重要的,很重要的,很重要的。
下一节我们继续介绍redis缓存的持久化,内存优化,集群搭建等知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值