day 12 Redis分片
此文档是根据上课流程整理。更多细节及图片请参见刘老师的专栏
江哥的专栏
一. AOP方式实现商品分类缓存
- 业务需求
通过自定义注解的形式,动态实现缓存操作。拦截的目标是带有指定注解的方法。通过注解获取其中的key和超时时间。
编辑@CacheFind注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CacheFind { public String key(); public int seconds() default 0; //不需要设置超时时间 }
自定义注解的用法
== ItemCatServiceImpl == @CacheFind(key = "ITEM_CAT_PARENT",seconds = 7*24*60*60) //具体的key自动补齐 public List<EasyUITree> findItemCatCache(long pid) {}
编辑CacheAop
@Autowired private Jedis jedis; /* 实现思路: 找到类,方法名字,参数列表的类型 key = 用户自定义的前缀+用户的参数 [0,xx] 判断key是否存在,如果存在,获取其中的数据JSON 转化为具体的对象(返回值类型) 反之执行目标方法,获得返回值,将返回值转化为JSON格式保存到缓存中 */ @Around("@annotation(cacheFind)") public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){ System.out.println("==环绕通知开始=="); Object result = null; //1.获取key的前缀 String key = cacheFind.key(); System.out.println("key = "+key); //2.获取方法参数 Object[] args = joinPoint.getArgs(); String argString = Arrays.toString(args); key = key+"::"+argString; System.out.println("key = "+key); try { //3.判断缓存中是否有数据 if(jedis.exists(key)){ String json = jedis.get(key); //5.获取返回值类型 Class clazz = ((MethodSignature)joinPoint.getSignature()).getReturnType(); result = ObjectMapperUtil.toObj(json,clazz); System.out.println("==执行数据库=="); }else{ //缓存中没有数据 result = joinPoint.proceed(); String resultJSON = ObjectMapperUtil.toJSON(result); //4.判断数据中是否有超时时间 if(cacheFind.seconds()>0){ jedis.setex(key, cacheFind.seconds(), resultJSON); }else { jedis.set(key, resultJSON); } System.out.println("==执行数据库=="); } } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable); } System.out.println("==环绕通知结束=="); return result; }
为查询商品类目的方法加上注解
@Override @CacheFind(key = "ITEM_CAT") public String findCatNameByCid(int cid) { return itemCatMapper.findCatNameByCid(cid); }
二. Redis基本特性
持久化策略
i. 为什么要持久化?
Redis中的记录都保存在内存中,如果内存断电或者服务器宕机,则内存数据直接丢失。为了防止这一现象,需要将数据定期进行维护。
ii. RDB模式
1) RDB模式是Redis的默认的持久化策略,无需手动开启;
2) Redis会定期执行RDB持久化操作。缺点:可能导致内存数据丢失;
3) RDB记录的是内存数据的快照,后续的快照会覆盖之前的快照,每次只保留最新的数据,效率更高;
4) 如果数据允许少量丢失,可以使用RDB模式。
命令:
# 立即执行持久化操作 save => 会造成线程阻塞 # 后台执行持久化操作 bgsave => 不会造成阻塞,不清楚他何时完成,不能保证立即执行
iii. AOF模式
1) 默认的条件下是关闭的,需要手动的开启,开启之后RDB模式失效。但是如果手动执行save命令,也会生成RDB文件;
2) 记录的是程序运行的过程,所以数据不丢失;
3) 由于记录的是程序运行的过程,持久化文件会很大,需要定期维护。
开启AOF
1) 编辑配置文件
appendonly yes # The name of the append only file (default: "appendonly.aof") appendfilename "appendonly.aof"
2) 重启redis服务器
redis-cli shutdown redis-server redis.conf
iv. RDB和AOF持久化策略的对比
1) RDB模式
save 900 1 => 如果在900秒内,执行了一次更新操作,则持久化一次 save 300 10 save 60 1000 操作越快,则持久化的周期越短
2) AOF模式
appendfsync always 用户执行一次更新操作,则持久化一次 异步操作 appendfsync everysec 每秒操作一次 appendfsync no 不主动操作,一般不用
v. 总结
如果数据允许少量丢失,首选RDB模式;如果数据不允许丢失,首选AOF。
企业策略:既满足效率,又要满足数据不丢失。配置多个Redis服务器,主机使用RDB模式,读写效率快;从机使用AOF模式,备份数据。
vi. 面试题
小李误操作将redis服务器执行了flushAll的命令,作为项目经理你会如何解决?
需要将从库中的AOF文件进行编辑,删除多余的flushAll命令,之后重启即可。
内存优化策略
i. LRU算法
1) 最近最少使用置换算法 - 淘汰最久未被使用的数据;
2) 判断维度:时间t
ii. LFU算法
1) 最不经常使用置换算法
2) 判断维度:使用次数
iii. 随机算法:随机删除
iv. ttl算法:将剩余时间短的删除
# volatile-lru -> Evict using approximated LRU among the keys with an expire set. # allkeys-lru -> Evict any key using approximated LRU. # volatile-lfu -> Evict using approximated LFU among the keys with an expire set. # allkeys-lfu -> Evict any key using approximated LFU. # volatile-random -> Remove a random key among the ones with an expire set. # allkeys-random -> Remove a random key, any key. # volatile-ttl -> Remove the key with the nearest expire time (minor TTL) # noeviction -> Don't evict anything, just return an error on write operations.
- Redis常见面试题
业务场景:高并发环境下用户长时间对服务器进行操作,可能产生如下的问题:
i. 什么是缓存穿透?
高并发环境下,访问数据库和缓存中都不存在的数据。导致大量的用户直接访问数据库,而导致数据库连接异常。
解决方案:禁用IP,限制IP的访问/限流 每秒最多访问3次,布隆过滤器(用于检索一个元素是否存在某一个集合中)
二进制向量 -> 由于哈希碰撞,可能有多个key有相同的位置,所以,布隆过滤器认为数据存在,那么数据可能存在;如果布隆过滤器认为数据不存在,则数据一定不存在。
解决方案:扩容二维向量的长度,使用多个哈希算法进行计算
1G内存,如何判断200亿是否有效?
ii. 什么是缓存击穿?
某些热点数据在缓存中突然失效,导致大量用户直接访问数据库,导致并发压力过大,数据库异常。
解决方案:将热点数据的超时时间设置长一点,设置多级缓存(超时时间采用随机算法)
iii. 什么是缓存雪崩?
在缓存服务器中,由于大量的缓存数据失效,导致用户访问的命中率过低,直接访问数据库。flushAll会导致缓存雪崩,设定超时时间,应采用随机算法。
解决方法:设置多级缓存
三. Redis分片机制
- Redis性能优化
单台redis内存容量是有限的,但是如果有海量的数据要求实现缓存存储,要使用多个redis节点。这种结构称为Redis的分片机制。
分片机制配置
i. 配置规划
准备三台redis服务器,端口号分别是6379, 6380, 6381
# 关闭redis服务器 redis-cli shutdown # 创建文件夹 mkdir shards # 将三台服务器配置文件移动到文件夹中 cp redis.conf shards/6379.conf cp redis.conf shards/6380.conf cp redis.conf shards/6381.conf # 修改端口号 cd /shards vim 6380.conf # 开启服务 redis-server 6379.conf redis-server 6380.conf redis-server 6381.conf # 检查服务器状态 ps -ef | grep redis
ii. 编写测试方法
/* 用户通过客户端程序,动态链接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("shards","redis分片操作"); System.out.println(shardedJedis.get("shards")); }
SpringBoot整合Redis分片
i. 修改redis.porperties配置文件
# 配置redis分片机制 redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
ii. 修改RedisConfig配置类
@Value("${redis.nodes}") private String nodes; //node,node,node @Bean public ShardedJedis shardedJedis(){ String[] nodeArr = nodes.split(","); List<JedisShardInfo> list = new ArrayList<>(); for (String node:nodeArr){ //node = host:port String host = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); list.add(new JedisShardInfo(host,port)); } ShardedJedis shardedJedis = new ShardedJedis(list); return shardedJedis; }
iii. 修改CacheAop
@Autowired private ShardedJedis jedis;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。