1.Redis缓存
1.1为什么需要使用缓存
说明:使用缓存可以有效的降低用户访问物理设备的频次,有效的减少并发的压力。保护后端真实的服务器。
1.2 缓存应该如何设计
1.缓存应该使用什么样的数据结构. K-V KEY必须唯一
2.如果需要缓存进行快速的读取,首先开发语言如何选择? C语言 软件的运行环境如何选择. 内存中
3.如何防止内存数据丢失! 缺点: 断电即擦除 内存数据的持久化操作(内存数据保存到磁盘中)
4.缓存的空间大小如何维护. 不能一直存,有时也会删除数据 如何操作? 内存优化算法: LRU算法/LFU算法
5.缓存使用如何防止宕机带来的影响 (HA)高可用 部署redis集群.
1.3 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)
nosql: 非关系型数据库 redis ,mongdb,hbase;
消息中间件: 主要的作用是为了实现数据的平滑过渡,让程序调用更加流畅(润滑剂)
性能问题: 读速度 11.2万次/秒 写 8.6万次/秒 平均 10万次/秒
1.4 Redis安装
1.4.1上传redis安装包
上传安装包,之后解压
1.4.2 安装redis
说明:在redis的根目录中执行
1). make
2). make install
1.5修改配置文件
1.5.1 知识补充
1).补充知识
如果修改配置文件时,出现了.swp文件,则表示上一次改文件没有正确的保存,生成了保护性文件.所以一般删除改文件即可
方式1: 如果提示按D删除,则按D
方式2: 如果没有按D提示,则 采用 rm -rf .xxxxx.swp
1.5.2 编辑redis.conf
1).命令 vim redis.conf
2).修改ip绑定
3).修改保护模式
4).开启后台启动
1.5.3 关于redis服务器命令
1.启动redis redis-server redis.conf
2.进入客户端 redis-cli -p 6379
3.关闭redis redis-cli -p 6379 shutdown
简化操作: 如果操作的redis是默认的端口 则可以省略不写.
redis-cli & redis-cli shutdown
1.6 Redis入门案例
1.6.1 导入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.6.2 编辑测试类
package com.jt.test;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;
public class TestRedis {
/**
* 1.spring整合redis
* 报错说明:
* 1).如果测试过程中报错 则检查redis配置文件 改3处
* 2).检查redis启动方式 redis-server redis.conf
* 3).检查Linux的防火墙
* 做完的 测试其他命令.
*/
@Test
public void testString01() {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
//2.操作redis
jedis.set("a", "redis入门案例");
String value = jedis.get("a");
System.out.println(value);
}
@Test
public void testString02() {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
//2.判断当前数据是否存在
if(jedis.exists("a")) {
System.out.println(jedis.get("a"));
}else {
jedis.set("a", "测试是否存在的方法");
}
}
/**
* 1.能否简化是否存在的判断
* 2.如果该数据不存在时修改数据,否则不修改
* setnx方法: 只有当数据不存在时赋值.
*/
@Test
public void testString03() {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
jedis.setnx("a", "测试setnx方法1");
jedis.setnx("a", "测试setnx方法2");
System.out.println(jedis.get("a"));
}
/**
* 为数据添加超时时间
* @throws InterruptedException
* setex方法 保证赋值操作和添加超时时间的操作的原子性
* 原子性: 要么同时成功,要么同时失败(类似事务)
*/
@Test
public void testString04() throws InterruptedException {
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
jedis.set("a", "aaaa"); //如果程序报错,则超时方法将不会执行,改数据将永不超时
//程序报错,意外终止!!!!!!!
jedis.expire("a", 20); //添加超时时间 不是原子性操作
Thread.sleep(2000);
System.out.println("剩余存活时间:"+jedis.ttl("a"));
//2.实现原子性操作
jedis.setex("b", 20, "原子性测试");
System.out.println(jedis.get("b"));
}
/**
*
* 1.只有数据不存在时允许修改
* 2.要求实现添加超时时间,并且是原子性操作
* SetParams 参数说明:
* 1.NX 只有key不存在时才能修改
* 2.XX 只有key存在时,才能修改
* 3.PX 添加的时间单位是毫秒
* 4.EX 添加的时间单位是秒
*/
@Test
public void testString05(){
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
SetParams params = new SetParams();
params.xx().ex(20);
jedis.set("aa", "测试A", params);
jedis.set("aa", "测试B", params);
System.out.println(jedis.get("aa"));
}
/**
* 存储一类数据时,可以使用hash.
*/
@Test
public void testHASH(){
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
jedis.hset("user", "name", "tomcat");
jedis.hset("user", "id", "100");
System.out.println(jedis.hgetAll("user"));
}
@Test
public void testList(){
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
jedis.lpush("list", "1","2","3","4");
System.out.println(jedis.rpop("list"));
}
//控制事务
@Test
public void testTx(){
//1.创建jedis对象
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.flushAll(); //清空所有的redis缓存
Transaction transaction = jedis.multi(); //2.开启事务
try {
transaction.set("aaa", "aaa");
transaction.set("bbb", "bbbbb");
transaction.set("ccc", "cccccc");
transaction.exec(); //事务提交
} catch (Exception e) {
e.printStackTrace();
transaction.discard(); //事务回滚
}
}
}
1.7 Spring整合Redis
1.7.1 整合思路
说明:将jedis对象交给spring容器进行管理.之后哪里需要直接注入即可.
步骤:
1.编辑redis.properties文件,指定redis节点的ip:port
2.由于redis比较重要,很多业务系统都需要调用,所以将redis整合写入common
3.通过配置类(配置文件)形式整合redis.
1.7.2 编辑redis.properties文件
1.配置文件位置
2.编辑配置文件
redis.host=192.168.126.129
redis.port=6379
1.7.3 编辑配置类
@Configuration //我是一个配置类 一般都会与@Bean联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private Integer port;
//将返回值的结果交给spring容器进行管理,如果以后想要使用该对象则可以直接注入.
@Bean
public Jedis jedis() {
return new Jedis(host, port);
}
}
1.8.json格式转化问题
1.8.1 json转化API
package com.jt.test;
import java.util.Date;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
public class TestObjectMapper {
//改对象就是工具api 有且只有一份即可.并且不允许别人修改.
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 目的: 实现对象与json串之间的转化
* 步骤1: 将对象转化为json
* 步骤2: 将json转化为对象
* 利用ObjectMapper 工具API实现
* @throws JsonProcessingException
*/
@Test
public void test01() throws JsonProcessingException {
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(101L).setItemDesc("json转化测试")
.setCreated(new Date()).setUpdated(itemDesc.getCreated());
//1.将对象转化为JSON 调用的是对象的get方法
String json = MAPPER.writeValueAsString(itemDesc);
System.out.println(json);
//2.将json转化为对象 传递需要转化之后的class类型 调用是对象的set方法
ItemDesc itemDesc2 = MAPPER.readValue(json, ItemDesc.class);
System.out.println(itemDesc2.getItemDesc());
}
}
1.8.2 封装ObjectMapperUtil
说明:改API主要负责将对象转化为JSON,将JSON转化为对象,同时优化异常处理.
package com.jt.util;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjectMapperUtil {
//json与对象的转化 优化异常处理
private static final ObjectMapper MAPPER = new ObjectMapper();
//1.将对象转化为JSON
public static String toJSON(Object target) {
if(target == null) {
throw new NullPointerException("taget数据为null");
}
try {
return MAPPER.writeValueAsString(target);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e); //如果转化过程中有问题则直接抛出异常
}
}
//2. 将json串转化为对象 用户传递什么样的类型,就返回什么样的对象!!!
// <T> 定义了一个泛型对象 代表任意类型
public static <T> T toObject(String json,Class<T> targetClass) {
if(StringUtils.isEmpty(json) || targetClass == null) {
throw new NullPointerException("参数不能为null");
}
try {
return MAPPER.readValue(json, targetClass);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
1.9 商品分类缓存实现
1.9.1 实现策略
业务说明:商品分类信息可以使用redis缓存实现该功能.
步骤:
1.当用户点击商品分类按钮时开始获取服务端数据.
2.先查询redis缓存是否有数据
3.如果redis中没有数据,则查询数据库.之后将查询的结果保存到redis中
4.如果redis中有数据,则直接返回缓存记录.
思路:
1).准备key=“ITEM_CAT::”+parentID
2).准备value="JSON"串
3).首先查询redis缓存
有: 直接获取缓存数据返回给用户.
没有: 直接查询数据库,之后将返回值结果保存到redis中,方便下次使用.
1.9.2编辑ItemCatController
/**
* 业务:查询商品分类信息,返回VO对象
* url地址: /item/cat/list
* 参数: id:一级分类id值
* 返回值: EasyUITree对象
* json格式:
* [{"id":"2","text":"王者荣耀","state":"closed"},{"id":"3","text":"王者荣耀","state":"closed"}]`
* sql语句:
* 一级商品分类信息 parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatByParentId
(@RequestParam(value = "id",defaultValue = "0") Long parentId){
//初始化时应该设定默认值.
//1.查询一级商品分类信息
//Long parentId = id==null?0L:id;
//return itemCatService.findItemCatByParentId(parentId);
//通过缓存的方式获取数据.
return itemCatService.findItemCatByCache(parentId);
}
1.9.3 编辑ItemCatService
/**
* 通过缓存的方式查询数据库.
* 1).定义key
* 2).根据key查询redis.
*/
@SuppressWarnings("unchecked")
@Override
public List<EasyUITree> findItemCatByCache(Long parentId) {
//1.定义key
String key = "ITEM_CAT_LIST::"+parentId;
List<EasyUITree> treeList = new ArrayList<EasyUITree>();
Long startTime = System.currentTimeMillis();
//2.判断redis中是否有值
if(jedis.exists(key)) {
//不是第一次查询,则获取缓存数据之后直接返回
String json = jedis.get(key);
Long endTime = System.currentTimeMillis();
treeList =
ObjectMapperUtil.toObject(json, treeList.getClass());
System.out.println("redis查询缓存的时间为:"+(endTime-startTime)+"毫秒");
}else {
//redis中没有这个key,表示用户第一次查询.
treeList = findItemCatByParentId(parentId);
Long endTime = System.currentTimeMillis();
//需要将list集合转化为json
String json = ObjectMapperUtil.toJSON(treeList);
//将数据保存到redis中
jedis.set(key, json);
System.out.println("查询数据库的时间为:"+(endTime-startTime)+"毫秒");
}
return treeList;
}
1.9.4 速度测试
1.10 利用AOP实现redis缓存
1.10.1 传统项目弊端
说明:
1).由于将redis的操作写到service层中,必须导致业务的耦合性高
2).如果采用上述的方式完成缓存,则改缓存不通用,并且代码冗余.效率低.
1.10.2 AOP的核心理念
公式: AOP = 切入点表达式 + 通知方法
1.10.3 切入点表达式
1). bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
2).within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
3).execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
4).@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.
1.10.4 通知方法
1.前置通知: 主要在 目标方法执行之前执行
2.后置通知: 在目标方法执行之后执行
3.异常通知: 在目标方法执行的过程中报了异常之后执行.
4.最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
5.环绕通知: 一般采用环绕通知 实现对业务的控制.
1.10.5 AOP入门案例
//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
//公式: 切面 = 切入点表达式 + 通知方法.
/**
* 业务需求: 要求拦截ItemCatServiceImpl类中的业务
* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
*/
//@Pointcut("bean(itemCatServiceImpl)") //按类匹配,控制的粒度较粗 单个bean
//@Pointcut("within(com.jt.service..*)") //按类匹配,控制的粒度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
public void pointCut() {
}
//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!!!!");
String typeName =
joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Object[] objs = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
System.out.println("获取方法参数:"+objs);
System.out.println("获取目标对象:"+target);
}
//添加环绕通知 可以控制目标方法执行 要求添加参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
//Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
} //指定目标方法
}
}
1.10.6 实现AOP 缓存处理
1.10.6.1 自定义注解
@Target(ElementType.METHOD) //标识注解 对谁生效
@Retention(RetentionPolicy.RUNTIME) //注解使用的有效期
public @interface CacheFind {
public String key(); //标识存入redis的key的前缀
public int seconds() default 0; //标识保存的时间 单位是秒
}
1.10.6.2 注解标识
1.10.6.3 AOP实现缓存业务处理
package com.jt.aop;
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 com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.Jedis;
//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
@Autowired(required = false)
private Jedis jedis;
/**
* 实现思路: 拦截被@CacheFind标识的方法 之后利用aop进行缓存的控制
* 通知方法: 环绕通知
* 实现步骤:
* 1.准备查询redis的key ITEM_CAT_LIST::第一个参数
* 2.@annotation(cacheFind) 动态获取注解的语法.
* 拦截指定注解类型的注解并且将注解对象当做参数进行传递.
*/
@SuppressWarnings("unchecked")
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
//1.获取用户注解中的key ITEM_CAT_LIST::0
String key = cacheFind.key();
//2.动态获取第一个参数当做key
String firstArg = joinPoint.getArgs()[0].toString();
key += "::"+firstArg;
Object result = null;
//3.根据key查询redis.
if(jedis.exists(key)) {
//根据redis获取数据信息
String json = jedis.get(key);
//如何获取返回值类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
result = ObjectMapperUtil.toObject(json, methodSignature.getReturnType());
System.out.println("aop查询redis缓存");
}else {
//如果key不存在,则证明是第一次查询. 应该查询数据库
try {
result = joinPoint.proceed(); //目标方法返回值
System.out.println("AOP查询数据库获取返回值结果");
//将数据保存到redis中
String json = ObjectMapperUtil.toJSON(result);
int seconds = cacheFind.seconds();
if(seconds>0)
jedis.setex(key, seconds, json);
else
jedis.set(key, json);
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
return result;
}
//公式: 切面 = 切入点表达式 + 通知方法.
/**
* 业务需求: 要求拦截ItemCatServiceImpl类中的业务
* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
*/
/**
//@Pointcut("bean(itemCatServiceImpl)") //按类匹配,控制的粒度较粗 单个bean
//@Pointcut("within(com.jt.service..*)") //按类匹配,控制的粒度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
public void pointCut() {
}
//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!!!!");
String typeName =
joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Object[] objs = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
System.out.println("获取方法参数:"+objs);
System.out.println("获取目标对象:"+target);
}
//添加环绕通知 可以控制目标方法执行 要求添加参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
//Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
} //指定目标方法
}
**/
}
1.10.6.4 AOP和代理的关系
1.10.6.5 完成商品分类的缓存实现
2.Redis分片实现
2.1 为什么使用分片
1).说明: 虽然redis可以扩展内存空间的大小.但是如果需要存储海量的数据一味的扩大内存,其实效率不高.
2).分片介绍: 准备多台redis,共同为用户提供缓存服务.在保证效率的前提下,实现了内存的扩容.
用户在使用分片机制时,将多台redis当做1台使用.
2.2 分片搭建
2.2.1 分片规划
由3台redis构成 端口号分别为6379/6380/6381, 如果需要准备多台redis则准备多个配置文件即可,注意其中的端口号.
2.2.2 准备多台redis
2.2.3 修改redis端口
说明:修改redis的配置文件的端口号
vim 6381.conf
2.2.4 启动多台redis
命令: [root@localhost shards]# redis-server 6379.conf & redis-server 6380.conf & redis-server 6381.conf &
检查服务是否启动:
2.2.5 spring整合Redis入门案例
ublic class TestRedisShards {
//思考:key=shards 存储到了哪台redis中? 如何存储的?
@Test
public void test01() {
//1.准备list集合 之后添加节点信息
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
shards.add(new JedisShardInfo("192.168.126.129", 6379));
shards.add(new JedisShardInfo("192.168.126.129", 6380));
shards.add(new JedisShardInfo("192.168.126.129", 6381));
//2.创建分片对象
ShardedJedis shardedJedis = new ShardedJedis(shards);
shardedJedis.set("shards", "准备分片操作!!!!!");
System.out.println(shardedJedis.get("shards"));
}
}
2.3 一致性hash算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
2.3.1 一致性hash原理说明
目的:解决数据如何在分布式环境下进行存储!!!
hash取值区间: 8位16进制数 共有 2^32种可能性!!! (24)8=2^32次方
1).数据如何存储
2).当节点发生变化带来哪些影响
当节点的数量发生了变化时,则节点中的对应的数据可以动态的迁移.
原则: 当发生了节点变化时,应该尽可能小的影响其他节点.
2.3.2 一致性hash特性
一致性哈希算法是在哈希算法基础上提出的,在动态变化的分布式环境中,哈希算法应该满足的几个条件:平衡性、单调性和分散性 [4] 。
①平衡性(均衡性)是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。 利用虚拟节点实现数据平衡 (平衡数据不能做到绝对平均,只能是相对的)
②单调性是指在新增或者删减节点时,不影响系统正常运行 . 可以实现动态的数据迁移.
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4]
鸡蛋不要放到一个篮子里。
2.4 SpringBoot整合Redis分片
2.4.1 编辑分片配置文件
#redis.host=192.168.126.129
#redis.port=6379
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
2.4.2 编辑配置类实现redis整合
@Configuration //我是一个配置类 一般都会与@Bean联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String redisNodes; //node,node,node
/*整合分片实现Redis内存扩容*/
@Bean
public ShardedJedis shardedJedis() {
String[] nodes = redisNodes.split(","); //节点数组
//动态获取Redis节点信息.
List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
for (String node : nodes) { //node= host:port ---->[host,port]
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
list.add(new JedisShardInfo(host, port));
}
//返回分片对象
return new ShardedJedis(list);
}
/**
* 单台测试
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private Integer port;
//将返回值的结果交给spring容器进行管理,如果以后想要使用该对象则可以直接注入.
@Bean
public Jedis jedis() {
return new Jedis(host, port);
}
*/
}
2.4.3 修改RedisAOP中的注入
2.4.4 关于redis分片总结
1.当redis节点宕机之后,用户访问必然受到影响.
2.当redis服务宕机之后,该节点中的数据可能丢失
3.Redis分片可以实现内存数据的扩容.
4.Redis分片机制中hash运算发生在业务服务器中.redis只负责存取.不负责计算. 所以效率更高.
3 Redis属性说明
3.1 Redis持久化策略
3.1.1 Redis持久化策略说明
说明: Redis的数据都保存在内存中,如果断电或者宕机,则内存数据将擦除,导致数据的丢失.为了防止数据丢失,Redis内部有持久化机制.
当第一次Redis服务启动时,根据配置文件中的持久化要求.进行持久化操作.如果不是第一次启动,则在服务启动时会根据持久化文件的配置,读取指定的持久化文件.实现内存数据的恢复.
3.1.2 RDB模式
特点:
1.rdb模式是redis中默认的持久化策略.
2.rdb模式定期持久化.保存的是Redis中的内存数据快照.持久化文件占用空间较小.
3.rdb模式可能导致内存数据丢失
命令:
前提:需要在redis的客户端中执行.
- save 命令 立即持久化 会导致其他操作陷入阻塞.
- bgsave 命令 开启后台运行. 以异步的方式进行持久化. 不会造成其他操作的阻塞.
save 900 1 900秒内,如果用户执行的1次更新操作,则持久化一次
save 300 10 300秒内,如果用户执行的10次更新操作,则持久化一次
save 60 10000 60秒内,如果用户执行的10000次更新操作,则持久化一次
save 1 1 1秒内,如果用户执行的1次更新操作,则持久化一次 set 阻塞!!!!
持久化文件:
持久化文件路径:
3.1.3 AOF模式
特点:
1). AOF模式默认条件下是关闭状态. 如果需要开启则需要修改配置文件.
2). AOF模式可以实现数据的实时持久化操作,AOF模式记录的是用户的操作过程.
3). 只要开启了AOF模式,则持久化方式以AOF模式为主.
配置:
开启AOF持久化方式
持久化文件格式:
持久化文件名称配置:
持久化文件策略说明
appendfsync always 只要用户执行一次操作,则持久化一次.
**appendfsync everysec 每秒持久化一次 默认策略** 效率略低于RDB
appendfsync no 不主动持久化.
3.1.4 面试题
公司新入职一个员工,对于业务不熟,出于好奇在生产环境下执行了flushAll命令,问:如果你是项目经理,如何解决 ??
A: 暴打一顿 驱逐出公司
B: 冷嘲热讽 让其主动离职 ,并且承担后果
C: 予以安慰,告诉他你是最棒的 之后去甲方负荆请罪
D: 相视一笑,告诉他 你别管了 交给我处理吧 .
处理方式:修改AOF文件中的flushAll命令,之后重启即可.
3.1.5 持久化总结
1.如果用户允许少量的数据丢失,则可以选用RDB模式. 效率更高
2.如果用户不允许数据丢失,则选用AOF模式.
3.可以2种方式都选, 需要搭建组从结构 , 主机选用RDB模式, 从机选用AOF模式,可以保证业务允许.
3.1.6 配置多种持久化方式
1).设计 : 6379 当主机 7380当从机.
2).修改主机的配置文件:
要求: 主机使用RDB模式
从机使用AOF模式
3).检查默认模式的状态
命令: info replication
4).实现主从挂载
编辑从服务器向主机进行挂载
4).主从测试
1.主机中添加测试数据.
2.检查从机中是否有数据
3.检查持久化文件是否有数据.
总结: 一般条件下主机采用RDB模式,从机采用AOF模式,效率更高.
3.2.Redis内存策略
3.2.1 内存策略说明
redis服务器运行在内存中,数据也在内存中保存. 如果一直往里存,总有一天内存资源不够用,所以需要研究如何优化内存.
3.2.2 LRU算法
维度:T 时间
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面(数据)置换算法,选择最近最久未使用的页面(数据)予以淘汰。该算法赋予每个页面(数据)一个访问字段,用来记录一个页面(数据)自上次被访问以来所经历的时间 t,当须淘汰一个页面(数据)时,选择现有页面(数据)中其 t 值最大的,即最近最少使用的页面(数据)予以淘汰。
3.2.3 LFU算法
维度:引用次数
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
3.2.4 随机算法
3.2.5 TTL算法
说明: 将设定了超时时间的数据提前删除.
3.2.6 Redis中内存优化策略
volatile-lru 设定超时时间的数据采用lru算法
allkeys-lru .所有的数据采用lru算法
volatile-lfu 设定超时时间的数据采用LFU算法
.allkeys-lfu 所有的数据才能lfu算法
volatile-random 设定了超时时间的数据采用随机算法
allkeys-random 所有数据采用随机算法
volatile-ttl 设定超时时间的数据采用TTL算法
noeviction 该配置为模式配置 表示内存满时 只报错,不删除数据.
修改配置文件之后,重启服务器即可.
3.3 Redis集群
3.3.1 redis集群搭建问题解决方案
1.检查防火墙
2.检查配置文件
3.关闭所有的redis服务器 sh stop.sh
4.删除多余的文件 nodes.conf dump.rdb
[root@localhost cluster]# rm -rf 700*/dump.rdb
[root@localhost cluster]# rm -rf 700*/nodes.conf
5.重新启动服务器,之后执行挂载命令
3.3.2 redis入门案例
public class TestCluster {
/**
* 通过程序操作redis 3主机 读写 3从机 数据备份
*/
@Test
public void test01() {
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.126.129", 7000));
nodes.add(new HostAndPort("192.168.126.129", 7001));
nodes.add(new HostAndPort("192.168.126.129", 7002));
nodes.add(new HostAndPort("192.168.126.129", 7003));
nodes.add(new HostAndPort("192.168.126.129", 7004));
nodes.add(new HostAndPort("192.168.126.129", 7005));
//利用程序操作redis集群
JedisCluster jedisCluster = new JedisCluster(nodes);
jedisCluster.set("AAAAA", "redis集群测试");
System.out.println(jedisCluster.get("AAAAA"));
}
}
3.3.3 SpringBoot整合Redis集群
编辑redis.properties配置文件
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
编辑配置类
@Configuration //我是一个配置类 一般都会与@Bean联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
/**
* spring整合Redis集群
*/
@Value("${redis.nodes}")
private String redisNodes;
@Bean
public JedisCluster jedisCluster() {
Set<HostAndPort> nodeSet = new HashSet<HostAndPort>();
String[] clusters = redisNodes.split(",");
for (String cluster : clusters) { //host:port
String host = cluster.split(":")[0];
int port = Integer.parseInt(cluster.split(":")[1]);
nodeSet.add(new HostAndPort(host, port));
}
return new JedisCluster(nodeSet);
}
}
编辑AOP注入