乐趣区

关于java:第四阶段笔记-Jingtaoday12

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<EasyUITree> 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<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"));
    }
  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<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;
退出移动版