第四阶段笔记 Jingtao_day12

day 12 Redis分片

此文档是根据上课流程整理。更多细节及图片请参见刘老师的专栏

江哥的专栏

cgb2008-京淘day12

一. AOP方式实现商品分类缓存
  1. 业务需求

    ​ 通过自定义注解的形式,动态实现缓存操作。拦截的目标是带有指定注解的方法。通过注解获取其中的key和超时时间。

  2. 编辑@CacheFind注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CacheFind {
        public String key();
        public int seconds() default 0; //不需要设置超时时间
    }
  3. 自定义注解的用法

    == ItemCatServiceImpl ==
    @CacheFind(key = "ITEM_CAT_PARENT",seconds = 7*24*60*60) //具体的key自动补齐
    public List findItemCatCache(long pid) {}
  4. 编辑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;
    }
  5. 为查询商品类目的方法加上注解

    @Override
    @CacheFind(key = "ITEM_CAT")
    public String findCatNameByCid(int cid) {
        return itemCatMapper.findCatNameByCid(cid);
    }
二. Redis基本特性
  1. 持久化策略

    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命令,之后重启即可。

  2. 内存优化策略

    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.
  3. Redis常见面试题

    业务场景:高并发环境下用户长时间对服务器进行操作,可能产生如下的问题:

    i. 什么是缓存穿透?

    ​ 高并发环境下,访问数据库和缓存中都不存在的数据。导致大量的用户直接访问数据库,而导致数据库连接异常。

    ​ 解决方案:禁用IP,限制IP的访问/限流 每秒最多访问3次,布隆过滤器(用于检索一个元素是否存在某一个集合中)

    ​ 二进制向量 -> 由于哈希碰撞,可能有多个key有相同的位置,所以,布隆过滤器认为数据存在,那么数据可能存在;如果布隆过滤器认为数据不存在,则数据一定不存在。

    ​ 解决方案:扩容二维向量的长度,使用多个哈希算法进行计算

    ​ 1G内存,如何判断200亿是否有效?

    ii. 什么是缓存击穿?

    ​ 某些热点数据在缓存中突然失效,导致大量用户直接访问数据库,导致并发压力过大,数据库异常。

    ​ 解决方案:将热点数据的超时时间设置长一点,设置多级缓存(超时时间采用随机算法)

    iii. 什么是缓存雪崩?

    ​ 在缓存服务器中,由于大量的缓存数据失效,导致用户访问的命中率过低,直接访问数据库。flushAll会导致缓存雪崩,设定超时时间,应采用随机算法。

    ​ 解决方法:设置多级缓存

三. Redis分片机制
  1. Redis性能优化

    ​ 单台redis内存容量是有限的,但是如果有海量的数据要求实现缓存存储,要使用多个redis节点。这种结构称为Redis的分片机制。

  2. 分片机制配置

    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 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"));
    }
  3. 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 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;

你可能感兴趣的:(java,intellij-idea)