赞
踩
redis-server redis.conf
(千万不能直接执行redis-server)ps -ef | grep redis
redis-cli -p 6379
(默认情况下端口6379可以省略)redis: kill -9 PID号
/ redis-cli -p 6379 shutdown
String
类型:Hash
类型List
类型Set
类型(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>
(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(); } } }
由于redis的IP地址和端口都是动态变化的,所以通过配置文件标识数据. 由于redis是公共部分,所以写到common中.
redis.host=192.168.126.129
redis.port=6379
说明: 编辑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); } }
简单对象转化:
/** * 测试简单对象的转化 */ @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()); }
集合对象转化:
/** * 测试集合对象的转化 */ @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); }
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.定义Redis中的key,key必须唯一不能重复,存取key=“ITEM_CAT_PARENTID::70”;
2.根据key 去redis中进行查询:有数据/没有数据;
3.没有数据:则查询数据库获取记录,并且将查询的结果转化为JSON之后保存到redis中 方便后续使用;
4.有数据:表示用户不是第一次查询,可以将缓存数据转化为对象直接返回即可.
/**
* 业务: 实现商品分类的查询
* 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);
}
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; } }
before
通知(前置通知):在执行目标方法之前执行afterReturning
通知(后置通知):在目标方法执行之后执行afterThrowing
通知(异常通知):在目标方法执行之后报错时执行after
通知(最终通知):无论什么时候程序执行完成都要执行的通知around
通知(功能最为强大的) 在目标方法执行前后执行.因为环绕通知可以控制目标方法是否执行.joinPoint.proceed();控制程序的执行的轨迹.理解: 切入点表达式就是一个程序是否进入通知的一个判断(IF)
作用: 当程序运行过程中,满足了切入点表达式时才会去执行通知方法,实现业务的扩展.
种类(写法):
bean(“bean的ID”)
粒度: 粗粒度,按bean匹配,当前bean中的方法都会执行通知(只能拦截具体的某个bean对象 只能匹配一个对象).within(“包名.类名”)
粒度: 粗粒度,可以匹配多个类.execution(“返回值类型 包名.类名.方法名(参数列表)”)
粒度: 细粒度,方法参数级别.@annotation(“包名.类名”)
粒度:细粒度,按照注解匹配.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).需要自定义注解CacheFind
2).设定注解的参数 key的前缀,数据的超时时间.
3).在方法中标识注解.
4).利用AOP 拦截指定的注解.
5).应该使用Around通知实现缓存业务.
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface CacheFind {
public String preKey(); //定义key的前缀
public int seconds() default 0; //定义数据的超时时间.
}
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; } }
说明:Redis运行环境在内存中,如果redis服务器关闭,则内存数据将会丢失.
需求: 如何保存内存数据呢?
解决方案: 可以定期将内存数据持久化到磁盘中.
持久化策略规则:
当redis正常运行时,定期的将数据保存到磁盘中,当redis服务器重启时,则根据配置文件中指定的持久化的方式,实现数据的恢复.(读取数据,之后恢复数据.)
save
命令: 将内存数据持久化到磁盘中,主动的操作,会造成线程阻塞.bgsave
命令:将内存数据采用后台运行的方式,持久化到文件中,异步的操作,由于是异步操作,不会造成阻塞.1.当内存数据允许少量丢失时,采用RDB模式 (快)
2.当内存数据不允许丢失时,采用AOF模式(定期维护持久化文件)
3.一般在工作中采用 RDB+AOF模式共同作用,保证数据的有效性.
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
计算维度: 时间T
注意事项: LRU算法是迄今为止内存中最好用的数据置换算法.
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 使用次数
随机删除数据。
根据剩余的存活时间,将马上要超时的数据提前删除.
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内存优化策略:
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
@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"));
}
说明:将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
@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); } }
在AOP中注入Redis缓存对象:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。