1.自定义注解,编辑AOP切面:
1.自定义注解:
/**
* 该注解主要实现查询操作.
* 有缓存查询缓存,没缓存查询数据库
* 操作规范:
* key:
* 1.用户没有赋值
* 如果key为"",表示用户使用自动生成的key
* key:包名.类名.方法名.拼接第一个参数
* 2.如果用户赋值
* key:使用用户的数据
* seconds:
* 如果时间不为0,表示用户需要设定超时时间
*/
@Target({ElementType.METHOD}) //对方法生效
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache_Find {
String key() default "";
int seconds() default 0;
}
2.编辑AOP切面:
//切面 = 切入点 + 通知
@Aspect //标识切面
@Component //交给spring容器管理
public class CacheAspect {
@Autowired
private Jedis jedis;
/**
* 利用AOP规则:动态获取注解对象
* 步骤:
* 1.根据key查询redis.
* 2.没有数据,需要让目标方法执行.查询的结果保存redis
* 3.将json串转化为返回值对象 返回.
* @param joinPoint
* @param cacheFind
* @return
*/
@SuppressWarnings("unchecked")//除去警告
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,
Cache_Find cacheFind) {
Object data = null;
String key = getKey(joinPoint,cacheFind);
//1.从redis中获取数据
String result = jedis.get(key);
//2.判断缓存中是否有数据
try {
if(StringUtils.isEmpty(result)) {
//2.1缓存中没有数据
data = joinPoint.proceed();//目标方法执行,就是执行下面的findItemCatCache方法
//2.2将返回值结果,转化为JSON
String json = ObjectMapperUtil.toJSON(data);//相互之间的转换在下面
//2.3判断用户是否编辑时间
//如果有时间,必须设定超时时间.
if(cacheFind.seconds()>0) {
int seconds = cacheFind.seconds();
jedis.setex(key,seconds, json);//ex是用来设计时间的,超出时间redis会自动清除
}else {
jedis.set(key,json);
}
System.out.println("AOP查询数据库!!!!!");
}else {
//表示缓存数据不为空,将缓存数据转化为对象
Class returnClass = getReturnClass(joinPoint);
data = ObjectMapperUtil.toObject(result,returnClass);
System.out.println("AOP查询缓存!!!!");
}
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return data;
}
/**
* 获取目标方法的返回值类型
* @param joinPoint
* @return
*/
private Class getReturnClass(ProceedingJoinPoint joinPoint) {
MethodSignature signature =
(MethodSignature) .getSignature();
return signature.getRetujoinPointrnType();
}
/**
* 动态获取key
* @param joinPoint
* @param cacheFind
* @return
*/
private String getKey(ProceedingJoinPoint joinPoint, Cache_Find cacheFind) {
String key = cacheFind.key();
if(StringUtils.isEmpty(key)) {
//key自动生成
String className =
joinPoint.getSignature().getDeclaringTypeName();
String methodName =
joinPoint.getSignature().getName();
if(joinPoint.getArgs().length>0)
//拼接第一个参数
key = className+"."+methodName+"::"
+ joinPoint.getArgs()[0];
else
key = className+"."+methodName;
}
return key;
}
}
----------------------------------------------
@Bean//方法的返回值,交给容器管理
public Jedis jedis() {
return new Jedis(host,port);
}
对象和json直接的相互转换:
public class ObejctMapperUtil {
//常量对象,可以调用对象的方法 线程是安全的
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String toJSON(Object data) {
String json = null;
try {
json=MAPPER.writeValueAsString(data);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}
return json;
}
/**
* 根据JSON转化成对象
* 参数:JSON数据,Class
* 返回值由用户决定
*/
public static <T> T toObject(String json,Class<T> target) {
T obj = null;
try {
obj = MAPPER.readValue(json, target);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return obj;
}
}
3.Redis速度提示:
原始访问数据:
有redis之后的访问数据
2. Redis中持久化规则:
说明:redis运行时会根据配置文件中的配置指定持久化方式。都会按照特定的规则定期将内存中的数据保存到持久化文件中。
当redis服务器重启时,首先根据配置文件中指定的持久化文件读取其中的数据,之后恢复到内存中,继续为用户提供服务。
1. RDB模式:
1.1特点:
- RDB模式是redis中默认的持久化策略
- RDB模式定期持久化(每个多久持久化一次)
- 风险:可能会数据丢失
- RDB模式工作的效率高,RDB模式定期做内存数据的快照,持久化文件较小。数据恢复时间短。
- RDB模式操作是同步的。
2.持久化命令:
- save:阻塞式持久化,当redis持久化操作时,不允许用户更新数据。
- bgsave:非阻塞式。在后台单独执行持久化,但是不能准确了解什么时候执行完成,类似于GC。
3.持久化策略:
save 900 1 在900秒内如果执行1次更新操作,持久化一次
save 300 10 在300秒内如果执行10次更新操作,持久化一次
save 60 10000 在60秒内如果执行10000次更新操作,持久化一次
save 1 1 效率很低. 基本实现不丢数据.
4. AOF模式:
特点:
- AOF模式默认条件下是关闭状态,需要手动开启
- AOF模式可以实现实时持久化(不丢失数据)
- AOF模式记录用户的操作过程。持久文件大,回复时间长。
- AOF模式持久化操作是异步进行。
AOF配置:(redis.conf文件)
持久化策略:
always:只要操作redis,就会持久化
everyesc:每秒持久化一次
no:持久化操作由当前操作系统决定(时钟),一般不用。
5. RDB模式与AOF模式优先级:
- 两种持久化方式可以同时存在。
- 如果同时进行,以AOF模式为准,AOF模式优先级高。
3.内存优化策略:
redis运行在内存中,如果redis执行大量的set(添加)操作。最终导致内存溢出,为了保证redis正常运行,必须通过某种机制,实现内存的控制。
1. LRU算法:
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
(简单理解:长时间没有用到的将会被淘汰)
2. LFU算法:
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数
(简单理解:就是使用的次数)
3.Redis(5.0)内存策略:
- volatile-lru 将设定超时时间的数据采用lru算法删除
- allkeys-lru 所有数据采用lru算法删除
- volatile-lfu 将设定超时时间的数据采用lfu算法删除
- allkeys-lfu 所有数据采用lfu算法删除
- volatile-random 将设定超时时间的数据随机删除
- allkeys-random 所有数据随机删除
- volatile-ttl 将设定超时时间的数据采用TTL方式排序,将马上要过期的数据删除。
- noeciction(默认策略) 如果内存溢出们不会删除数据只会报错返回。
4. Redis分片机制:
单台redis内存使用有上限,一般不超过1024 M。但是如果业务有特殊要求,单台redis不能提供内存的支持,redis分片机制,实现内存的扩容。
备注:一下几种机制,都是放在单独的文件夹下面,通过修改对应的conf文件来实现的。
1. redis分片搭建:
1.1准备分片文件的目录:
[root@localhost redis]# mkdir shards
1.2复制配置文件:
[root@localhost redis]# cp redis.conf shards/redis-6379.conf
[root@localhost redis]# cp redis.conf shards/redis-6380.conf
[root@localhost redis]# cp redis.conf shards/redis-6381.conf
1.3修改端口号:分别修改为6380/6381
vim redis.conf shards/redis-6379.conf #修改文件
1.4启动服务:
redis-server -p 端口号 redis-6379.conf
1.5查看已经启动的服务
1.6代码演示:
/**
* 实现redis分片测试 6379/6380/6381
* 使用的类是ShardedJedis
*/
@Test
public void testShards() {
String host = "192.168.80.134";
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
shards.add(new JedisShardInfo(host, 6379));
shards.add(new JedisShardInfo(host, 6380));
shards.add(new JedisShardInfo(host, 6381));
ShardedJedis jedis = new ShardedJedis(shards);
jedis.set("100", "迅哥升本加油");
System.out.println(jedis.get("100"));
}
1.7 SpringBoot整合:
@Configuration //标识配置类
@PropertySource("classpath:/properties/redis.properties")//此处的redis.properties需要自己去编写
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes;
@Bean
public ShardedJedis shardedJedis() {
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
String[] arrayNodes = nodes.split(",");
//node={ip:port}
for (String node : arrayNodes) {
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
JedisShardInfo info = new JedisShardInfo(host, port);
shards.add(info);
}
return new ShardedJedis(shards);
}
}
1.8修改AOP的引用:
//切面 = 切入点 + 通知
@Aspect //标识切面
@Component //交给spring容器管理
public class CacheAspect {
@Autowired(required = false)
private ShardedJedis jedis;
//(此处代码同上,就是类型更改了)
--------------------------------
@Bean
public ShardedJedis shardedJedis() {
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
String[] arrayNodes = nodes.split(",");
for (String node : arrayNodes) {
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
JedisShardInfo info = new JedisShardInfo(host, port);
shards.add(info);
}
return new ShardedJedis(shards);
}
2.小结:
- Redis分片效率是最高的
- Redis分片主要目的实现内存扩展
- Redis分片没有实现高可用技术,如果一天Redis宕机。则整个分片不能正常运行。
5. 哨兵机制
原理:
- 哨兵主要根据IP地址和端口监听主机Master,并且记录当前主机全部的丛机的信息。
- 哨兵利用PING-PONG机制检查是否存活,如果联系3次主机没有反应,表示主机宕机,。之后由哨兵开始选举
- 哨兵根据内部的推选,选举新的主机,之后修改其他redis的配置文件,实现新的主从关系。(主要是为了防止出现多主的现象)
1.哨兵主从搭建:
把上面的分片机制的文件,复制到新的文件夹之后,为哨兵机制
1.2主从挂载:
执行下面的命令,是将6380端口的挂载到6379下面。
slaveof 192.168.80.134:6379
1.3编辑哨兵配置文件:
1.关闭保护模式:
2.开启后台启动
3.修改哨兵监控
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster: 代表当前主机的变量名称
IP:端口: 当前主机信息
总的台数一般都是设置成奇数。
4.修改推选时间:
5.修改哨兵推选失效时间:
1.4测试:
@Test
public void testSentinel() {
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.80.1348:26379");
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels);
Jedis jedis = pool.getResource();
jedis.set("测试", "哨兵搭建完成!!!!!");
System.out.println(jedis.get("测试"));
}
1.5SpringBoot整合:
@Bean
public JedisSentinelPool jedisSentinelPool() {
Set<String> sentinels = new HashSet<>();
sentinels.add(nodes);
return new JedisSentinelPool("mymaster", sentinels);
}
-----------------------------
@Bean//实例化对象是如果方法中添加了参数,首先会先到Spring容器中查找该参数,如果有则直接注入
@Scope("prototype") //多例对象 用户使用时创建
public Jedis jedis(JedisSentinelPool pool) {
return pool.getResource();
}
1.6小结:
- Redis哨兵主要实现了Redis高可用机制,自动的实现故障迁移。
- Redis哨兵没有实现内容扩容。
- Redis没有实现高可用。
6.Redis集群:
1.Redis集群的准备:
1.准备文件夹:
Mkdir cluster
#并创建文件夹
mkdir 7000 7001 7002 7003 7004 7005
2复制文件:
将redis根目录下的redis.cong文件复制到cluster/7000,并保存
3.编辑配置文件。
1)注释本地绑定的IP地址:
2)关闭保护模式:
3)修改端口号:
4)启动后台启动:
5)修改pid文件:
6)修改持久化文件路径:
7)设定内存优化策略:
8)关闭AOF模式:
9)开启集群配置:
10)开启集群配置文件:
11)修改集群超时时间:
4.将修改好的文件,复制到不同的文件夹下,然后批量修改。
:%s/7000/7001/g
5.可用通过脚本开启或者关闭
6.创建集群:
#5.0版本执行 使用C语言内部管理集群
redis-cli --cluster create --cluster-replicas 1 192.168.80.134:7000 192.168.80.134:7001 192.168.80.134:7002 192.168.80.134:7003 192.168.80.134:7004 192.168.80.134:7005
2.测试:
@Test
public void testCluster() {
String host = "192.168.80.134";
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort(host,7000));
nodes.add(new HostAndPort(host,7001));
nodes.add(new HostAndPort(host,7002));
nodes.add(new HostAndPort(host,7003));
nodes.add(new HostAndPort(host,7004));
nodes.add(new HostAndPort(host,7005));
JedisCluster jedisCluster = new JedisCluster(nodes);
jedisCluster.set("测试", "你好redis集群");
System.out.println(jedisCluster.get("测试"));
}
3.整合SpringBoot:
@Configuration //标识配置类
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes;
@Bean
public JedisCluster jedisCluster() {
Set<HostAndPort> setNodes = new HashSet<>();
String[] arrayNode = nodes.split(",");
//node{IP:PORT}
for (String node : arrayNode) {
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
setNodes.add(new HostAndPort(host, port));
}
return new JedisCluster(setNodes);
}
}