1 AOP实现Redis缓存服务
1.1 现有代码的分析
说明:
1.虽然在业务层service中完成了代码的实现.但是该代码不具有复用性.如果换了其他的业务则需要重新编辑.
2.由于缓存的代码写在业务层service中,所以代码的耦合性高,不方便以后的扩展.
需求:
1.能否实现代码的复用.
2.能否降低代码的耦合性.
1.2 AOP
1.2.1 AOP作用
名称:面向切面编程.
一句话总结: 在不改变原有代码的条件下,对功能进行扩展.
公式: AOP = 切入点表达式 + 通知方法.
专业术语:
1.连接点: 在执行正常的业务过程中满足了切入点表达式时进入切面的点.(织入) 多个
2.通知: 在切面中执行的具体的业务(扩展) 方法
3.切入点: 能够进入切面的一个判断 if判断 一个
4.目标方法: 将要执行的真实的业务逻辑.
1.2.2 关于通知说明
1.前置通知: 目标方法执行之前执行
2.后置通知: 目标方法执行之后执行
3.异常通知: 目标方法执行之后抛出异常时执行
4.最终通知: 不管什么时候都要执行的方法.
说明:上述的四大通知类型不能控制目标方法是否执行.一般使用上述的四大通知类型,都是用来记录程序的执行状态.
5.环绕通知: 在目标方法执行前后都要执行的通知方法. 控制目标方法是否执行.并且环绕通知的功能最为强大.
1.2.3 切入点表达式说明
1). bean(bean的id) 类名首字母小写 匹配1个类
2). within(包名.类名) 按包路径匹配类 匹配多个类
上述表达式是粗粒度的控制,按类匹配.
3).execution(返回值类型 包名.类名.方法名(参数列表))
4).@annotation(包名.注解名) 按注解进行拦截.
1.2.4 AOPDemo复习
package com.jt.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //将对象交给spring容器管理
@Aspect //标识我是一个切面
public class CacheAOP {
//@Pointcut(value = "bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service..*)")
//拦截com.jt.service下的所有类的所有方法的任意参数类型
//@Pointcut("execution(int com.jt.service..*.add*(String))")
@Pointcut("execution(* com.jt.service..*.*(..))")
public void pointcut(){
}
//定义前置通知
@Before("pointcut()")
public void before(){
System.out.println("我是前置通知");
}
}
1.3 实现Redis缓存
1.3.1 需求分析
1.自定义注解CacheFind 主要被注解标识的方法,则开启缓存的实现.
2.为了将来区分业务,需要在注解中标识key属性,由使用者自行填写.
3.为了用户提供数据超时功能.
1.3.2 自定义注解
@Retention(RetentionPolicy.RUNTIME) //该注解什么时候有效
@Target({ ElementType.METHOD}) //对方法有效
public @interface CacheFind {
String key(); //该属性为必须添加
int seconds() default 0; //设定超时时间 默认不超时
}
1.3.3 编辑CacheAOP
package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.pojo.ItemDesc;
import com.jt.util.ObjectMapperUtil;
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 redis.clients.jedis.Jedis;
import java.lang.reflect.Method;
import java.util.Arrays;
@Component //将对象交给spring容器管理
@Aspect //标识我是一个切面
public class CacheAOP {
//1.注入缓存redis对象
@Autowired
private Jedis jedis;
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
//1.如何获取用户在注解中填写的内容呢??? 如何获取注解对象....
String key = cacheFind.key(); //前缀 ITEM_CAT_PARENTID
//2.如何获取目标对象的参数呢???
Object[] array = joinPoint.getArgs();
key += "::"+Arrays.toString(array); // "ITEM_CAT_PARENTID::[0]"
//3.从redis中获取数据
Object result = null;
if(jedis.exists(key)){
//需要获取json数据之后,直接转化为对象返回!!
String json = jedis.get(key);
//如何获取返回值类型
MethodSignature methodSignature =
(MethodSignature) joinPoint.getSignature();
Class targetClass = methodSignature.getReturnType();
result = ObjectMapperUtil.toObject(json,targetClass);
System.out.println("AOP实现缓存的查询!!!");
}else{
//key不存在,应该查询数据库
try {
result = joinPoint.proceed(); //执行目标方法,获取返回值结果
String json = ObjectMapperUtil.toJSON(result);
if(cacheFind.seconds()>0){ //判断是否需要超时时间
jedis.setex(key, cacheFind.seconds(), json);
}else{
jedis.set(key,json);
}
System.out.println("AOP执行数据库操作!!!");
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
}
return result;
}
//@Pointcut(value = "bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service..*)")
//拦截com.jt.service下的所有类的所有方法的任意参数类型
//@Pointcut("execution(int com.jt.service..*.add*(String))")
}
1.3.4 关于环绕通知参数的说明
问题一:连接点必须位于通知的参数的第一位.
否则报错信息如下:
问题二: 其他四大通知了类型是否可以添加ProceedingJoinPoint对象
答案: ProceedingJoinPoint 只能添加到环绕通知中.
报错如下:
1.3.5 关于JoinPoint方法说明
@Before("@annotation(com.jt.anno.CacheFind)")
public void before(JoinPoint joinPoint){
Object target = joinPoint.getTarget(); //获取目标对象
Object[] args = joinPoint.getArgs(); //获取方法参数的
String targetName =
joinPoint.getSignature().getDeclaringTypeName(); //获取目标对象的名称
//获取目标对象的类型
Class targetClass = joinPoint.getSignature().getDeclaringType();
//获取目标方法的名称
String methodName = joinPoint.getSignature().getName();
System.out.println(target);
System.out.println(args);
System.out.println(targetName);
System.out.println(targetClass);
System.out.println(methodName);
}
1.4 商品列表分类实现缓存处理
说明:在业务方法中添加缓存的注解.
@RequestMapping("/queryItemName")
@CacheFind(key="ITEM_CAT_NAME")
public String findItemCatName(Long itemCatId){
return itemCatService.findItemCatNameById(itemCatId);
}
2.Redis分片机制
2.1 需求数据
如果需要在redis中进行海量的数据存储,如果只有一台redis显然不能实现该功能.如果通过扩大内存的方式也不能达到要求.因为时间都浪费在寻址中. 如何有效的存储海量的数据呢???
2.2 Redis分片说明
说明:一般采用多台redis,分别保存用户的数据,从而实现内存数据的扩容.
对于用户而言:将redis分片当做一个整体,用户不在乎数据到底存储到哪里,只在乎能不能存.
分片主要的作用: 实现内存扩容.
2.3 Redis分片准备
2.3.1 创建目录
说明:在redis根目录中创建一个shards目录
2.3.2 分片搭建策略
说明:由于Redis启动是根据配置文件运行的,所以如果需要准备3台redis,则需要复制3份配置文件redis.conf. 端口号依次为6379/6380/6381
复制配置文件:
修改端口号:
根据配置文件名称,动态修改对应的端口即可.
启动redis:
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf
2.3.3 Redis分片入门案例
@Test
public void testShards(){
List<JedisShardInfo> list = new ArrayList<>();
list.add(new JedisShardInfo("192.168.126.129", 6379));
list.add(new JedisShardInfo("192.168.126.129", 6380));
list.add(new JedisShardInfo("192.168.126.129", 6381));
ShardedJedis shardedJedis = new ShardedJedis(list);
shardedJedis.set("2005", "redis分片学习");
System.out.println(shardedJedis.get("2005"));
}
2.4 一致性hash算法
2.4.1 算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
2.4.2 常识介绍
1.常见hash多少位16进制数? 8位16进制数
2.16进制数取值有哪些 0-9 A-F 共16个数
3. hash的取值范围从 00000000 ~ FFFFFFFF (24)8
4. 上述取值的个数共有多少个??? 要求以2为底 2^32 幂
5. 对相同的数据进行hash值一致
2.4.3 一致性hash说明
步骤:
1.首先计算node节点
2.将用户的key进行hash计算,之后按照顺时针的方向找到最近的node节点之后链接,执行set操作.
2.4.3 特性一 平衡性
①平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。
说明: 如果发现节点中存储的数据负载不均,则采用虚拟节点的方式实现数据的平衡(相对平衡)
2.4.3 特性一 单调性
②单调性是指在新增或者删减节点时,不影响系统正常运行 [4] 因为可以实现自动的数据迁移.。
原则: 在进行数据迁移时 应该尽可能少的改变原有的数据.
2.4.4 特性一 分散性
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4] 。
鸡蛋不要放在一个篮子里