Redis(补充二)

1. SpringDataRedis配置RedisTemplate介绍

  • RedisTemplate 介绍(推荐使用序列化、性能更好、业界都默认json序列化的)

    • ValueOperations:简单K-V操作
    • SetOperations:set类型数据操作
    • ZSetOperations:zset类型数据操作
    • HashOperations:针对map类型的数据操作
    • ListOperations:list类型的数据操作
  • RedisTemplate和StringRedisTemplate的区别

    • RedisTemplate和StringRedisTemplate的区别
    • 两者的数据是不共通的(默认的序列化机制导致key不 ⼀样)
    • StringRedisTemplate默认采⽤的是String的序列化策 略
    • RedisTemplate默认采⽤的是JDK的序列化策略,会将 数据先序列化成字节数组然后在存⼊Redis数据库
  • 总结

    • 当redis数据库⾥⾯本来操作的是字符串数据的时 候,那使⽤StringRedisTemplate即可
    • 数据是复杂的对象类型,那么使⽤RedisTemplate是 更好的选择
    • 两者都是 String 结构
    • 存储对象

2. RedisTemplate的序列化和反序列化机制

问题:同个key为啥获取不到值,核⼼就是序列化机制导致key 不⼀样

  • 什么是序列化

    • 把对象转换为字节序列的过程称为对象的序列化
    • 把字节序列恢复为对象的过程称为对象的反序列化
    • 对象的序列化主要有两种⽤途
      • 把对象的字节序列永久地保存到硬盘上,通常存放 在⼀个⽂件中
      • 在⽹络上传送对象的字节序列
  • Redis 为什么要序列化

    • 性能可以提⾼,不同的序列化⽅式性能不⼀样
    • 可视化⼯具更好查看
    • 采⽤默认的jdk⽅式会乱码(POJO类需要实现 Serializable接⼝)
    • 采⽤JSON⽅式则不⽤,且可视化⼯具更好查看
  • 自定义Redis序列化方式,提供了多种可选择策略

    • jdkSerializationRedisSerializer
      • POJO对象的存取场景,使⽤JDK本身序列化机制
      • 默认机制 ObjectInputStream/ObjectOutputStream进⾏序 列化操作
    • StringRedisSerializer
      • Key或者value为字符串
    • Jackson2JsonRedisSerializer
      • 利⽤jackson-json⼯具,将pojo实例序列化成json格 式存储
    • GenericFastJsonRedisSerializer(阿里)
      • 另一种javabean与json之间的转换,同时也需要指定Class类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1JbDj26F-1623767260757)(C:\Users\heng\AppData\Roaming\Typora\typora-user-images\image-20210608085549793.png)]

起 RedisTemplate配置类

@Configuration
public class RedisTemplateConfiguration {
    /**
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object>
    redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使⽤Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // 对象转json的包
        ObjectMapper objectMapper = new ObjectMapper();
        // 配置基本属性 all 转换 json
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 设置key和value的序列化规则
        // 1. key改为String类型
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 2. value改为String类型
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        // 设置hashKey 和 hashValue的序列化规则
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);

        // 设置支持事务
        redisTemplate.setEnableTransactionSupport(true);
        // 配置
        redisTemplate.afterPropertiesSet();
        // 返回 加入IOP容器
        return redisTemplate;
    }

}

3. SpringBoot 整合Jedis + Lettuce 客户端连接池配置

<!-- Spring2.X 集成redis 所需common-pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
    		<!--切换jedis 移除lettuce-->
<!--		   <exclusions>-->
<!--                <exclusion>-->
<!--                    <groupId>io.lettuce</groupId>-->
<!--                    <artifactId>lettuce-core</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

application.yml 配置

spring:
  redis:
    #Redis 服务器地址
    host: x.x.x.x
    #Redis 服务器连接端口
    port: 3366
    password: 密码
      max-redirects: 2 #获取失败 最大重定向次数
    # 密码
    #Redis 数据库索引(默认为0)
    database: 0
    #连接超时时间(毫秒)
    timeout: 1800000
    #连接池最大连接数(使用负值表示没有限制)
    client-tyep: jedis
    lettuce:
      pool:
        max-active: 20
        #最大阻塞等待时间(负数表示没有限制)
        max-wait: 1
        #连接池中的最大空闲连接
        max-idle: 5
        #连接池中的最小空闲连接
        min-idle: 0
    jedis:
      pool:
        max-active: 20
        #最大阻塞等待时间(负数表示没有限制)
        max-wait: 1
        #连接池中的最大空闲连接
        max-idle: 5
        #连接池中的最小空闲连接
        min-idle: 0

分布式缓存实战-String数据结构案例

1.图形验证码 + 谷歌开源 Kaptcha

简介:

  • 注册-登录-修改密码⼀般需要发送验证码,但是容易被 攻击恶意调⽤

  • 公司带来的损失

    • 短信⼀条5分钱,被⼤盗刷,带宽、连接等都被占 ⽤, 导致⽆法正常使⽤
  • 如何避免⾃⼰的⽹站成为”⾁鸡“或者被刷呢

    • 增加图形验证码(开发⼈员)
    • 单IP请求次数限制(开发⼈员)
    • 限制号码发送(⼀般短信提供商会做)
  • Kaptcha 框架介绍

    • ⾕歌开源的⼀个可⾼度配置的实⽤验证码⽣成⼯具
    • 验证码的字体/⼤⼩/颜⾊
    • 验证码内容的范围(数字,字⺟,中⽂汉字!)
    • 验证码图⽚的⼤⼩,边框,边框粗细,边框颜⾊ 验证码的⼲扰线
    • 验证码的样式(⻥眼样式、3D、普通 模糊)

栗子:

<!--kaptcha依赖包-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>kaptcha-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

配置类:

@Configuration
public class CaptchaConfig {
    /**
     * 验证码配置
     * Kaptcha配置类名
     * * @return
     */
    @Bean
    @Qualifier("captchaProducer")
    public DefaultKaptcha kaptcha() {
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        //验证码个数
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "5");
        //字体间隔
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,"8");
        //⼲扰线颜⾊
        //⼲扰实现类
        properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
        //图⽚样式

        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple"); //⽂字来源
        String Idcode = getCode();
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, Idcode);
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }

    public static String getCode(){
        Random random = new Random();
        String code = "";

        for(int i=0;i<5;i++){
            int rand = random.nextInt(10);
            code += rand;
        }
        return code;
    }
}

起 CaptchaController :

获取二维码图形

@Controller
public class CaptchaController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 和 CaptchaConfig 配置类的 名称一样
    @Autowired
    private Producer captchaProducer;

    @GetMapping("/get_captchar")
    public void getCaptchar(HttpServletRequest request, HttpServletResponse response){
        // 生成验证码
        String text = captchaProducer.createText();
        // 获取 统一key
        String key  = getCaptcharKey(request);
        System.out.println(key);
        // 设置10分钟过期
        redisTemplate.opsForValue().set(key,text,10, TimeUnit.MINUTES);
        // 拿到图片
        BufferedImage image = captchaProducer.createImage(text);

        ServletOutputStream outputStream = null;
        try {
            // 输出流
            outputStream = response.getOutputStream();
            // 参 1.图片  2. 图片格式  3.输出流
            ImageIO.write(image,"jpg",outputStream);
            // 关闭
            outputStream.flush();
            outputStream.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    // 把key避免过长 统一转MD5字符串
    private String getCaptcharKey(HttpServletRequest request){
        String ipAddr = CommonUtil.getIpAddr(request);
        String userAgent = request.getHeader("User-Agent");
        String key = "user-service:captchar:"+CommonUtil.MD5(ipAddr+userAgent);
        return key;
    }

}

验证二维码:

起 验证码工具类

public class JsonData {
    /**
     * 状态码 0 表示成功
     */
    private Integer code;

    /**
     * 数据
     */
    private Object data;

    /**
     * 描述
     */
    private String msg;


    public JsonData(int code,Object data,String msg){
        this.code = code;
        this.msg = msg;
        this.code = code;
    }

    /**
     * 成功,不传入数据
     * @return
     */
    public static JsonData buildSuccess() {
        return new JsonData(0, null, null);
    }

    /**
     *  成功,传入数据
     * @param data
     * @return
     */
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, null);
    }

    /**
     * 失败,传入描述信息
     * @param msg
     * @return
     */
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

在控制层 加入:

/**
     *
     * 发送验证码
     * @param request
     * @return
     *
     *
     */
    @GetMapping("/getCode")
    public JsonData sendCode(@RequestParam(value = "to",required = true) String to,
                             @RequestParam(value = "captchar",required = true) String captchar,
                             HttpServletRequest request){
        String key = getCaptcharKey(request);
        // 获取key
        String cacheCaptchar = redisTemplate.opsForValue().get(key);
        // 判断 一样删除
        if(captchar != null && cacheCaptchar != null &&  cacheCaptchar.equals(captchar)){
            redisTemplate.delete(key);
            //TODO 发送验证码
            return JsonData.buildSuccess();
        } else {
            return JsonData.buildError("验证码错误");
        }
    }

2. Jmeter5.x

热点数据接口压测

  • QPS: (Query Per Second): 每秒请求数,就是说服务器在 ⼀秒的时间内处理了多少个请求

  • 新增聚合报告:线程组->添加->监听器->聚合报告 (Aggregate Report)

lable: sampler的名称

Samples: ⼀共发出去多少请求,例如10个⽤户,循环10 次,则是 100

Average: 平均响应时间

Median: 中位数,也就是 50% ⽤户的响应时间 90%

Line : 90% ⽤户的响应不会超过该时间 (90% of the samples took no more than this time. The remaining samples at least as long as this) 95%

Line : 95% ⽤户的响应不会超过该时间 99%

Line : 99% ⽤户的响应不会超过该时间

min : 最⼩响应时间

max : 最⼤响应时间

Error%:错误的请求的数量/请求的总数

Throughput: 吞吐量——默认情况下表示每秒完成的请求数 (Request per Second) 可类⽐为qps、tps KB/Sec: 每秒接收数据量

3. Hash 类型案例

  • 背景

    • 电商购物⻋实现,⽀持买多件商品,每个商品可以买不 同数量
    • ⽀持⾼性能处理
  • 购物⻋常⻅实现⽅式

    • 实现⽅式⼀:存储到数据库
      • 性能存在瓶颈
    • 实现⽅式⼆:前端本地存储-localstorage-sessionstorag
      • localstorage在浏览器中存储 key/value 对,没有过期时间
      • sessionstorage在浏览器中存储 key/value 对,在关闭会话窗⼝后将会删除这些数据
    • 实现⽅式三:后端存储到缓存如redis
      • 可以开启AOF持久化防⽌重启丢失(推荐)
  • 购物车数据结构介绍

    • ⼀个购物⻋⾥⾯,存在多个购物项
    • 所以 购物⻋结构是⼀个双层Map:
      • Map<String,Map<String,String>>
      • 第一层Map, Key是用户id
      • 第二层Map,Key是购物车中商品id,值是购物车数据
  • 对应redis⾥⾯的存储

    • redis⾥⾯有多种数据结构,应该使⽤哪种?
    • 答案是 hash结构

4. SpringCache框架常⽤之Cacheable

Cacheable注解

  • 标记在⼀个⽅法上,也可以标记在⼀个类上

  • 缓存标注对象的返回结果,标注在⽅法上缓存该⽅法的 返回值,标注在类上缓存该类所有的⽅法返回值

  • value 缓存名称,可以有多个

  • key 缓存的key规则,可以⽤springEL表达式,默认是 ⽅法参数组合

  • condition 缓存条件,使⽤springEL编写,返回true才 缓存

案例:

//@Cacheable(value = {"product_page"},key = "#root.methodName+'_'+#page+'_'+#size") key麻烦   自定义规则 二选一
  • spEL表达式
    • methodName 当前被调⽤的⽅法名
      • root.methodname
    • args 当前被调⽤的⽅法的参数列表
      • root.args[0]
    • result ⽅法执⾏后的返回值
      • result

⾃定义CacheManager配置和过期 时间

  • 修改redis缓存序列化器和配置manager过期时间
/**
     * 配置过期时间 1分钟
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(60L);
        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build();
    }

    /**
     * 配置过期时间 1小时
     * @param connectionFactory
     * @return
     */
    @Bean
    @Primary
    public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(3600L);
        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build();
    }

    /**
     * 过期数据1天
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(3600 * 24L);
        return RedisCacheManager.builder(connectionFactory)
                        .cacheDefaults(config)
                        .transactionAware()
                        .build();
    }

    private RedisCacheConfiguration instanceConfig(Long ttl) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        // 去掉各种@JsonSerialize注解的解析

        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        // 只针对⾮空的值进⾏序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 将类型序列化到属性json字符串中

        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //ofSeconds 是秒 disableCachingNullValues 禁止缓存null值
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(ttl))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    }

SpringCache框架⾃定义缓存KeyGenerator

/**
     * Cache框架⾃定义缓存KeyGenerator
     * @return
     */
    @Bean
    public KeyGenerator springCacheDefaultKeyGenerator(){
       return new KeyGenerator() {
           @Override
           public Object generate(Object o, Method method, Object... objects) {
               String key = o.getClass().getSimpleName()+"_"+method.getName()+"_"+ StringUtils.arrayToDelimitedString(objects, "_");
               System.out.println(key);
               return key;
           }
       };
    }

使用:

  • key 属性和keyGenerator属性只能⼆选⼀
@Cacheable(value = {"product_page"},keyGenerator = "springCacheDefaultKeyGenerator")

常⽤注解CachePut

  • CachePut
    • 根据⽅法的请求参数对其结果进⾏缓存,每次都会触发 真实⽅法的调⽤
    • value 缓存名称,可以有多个
    • key 缓存的key规则,可以⽤springEL表达式,默认是 ⽅法参数组合
    • condition 缓存条件,使⽤springEL编写,返回true才 缓存

案例:

@CachePut(value = {"product"}, key="#productDO.id", cacheManager = "cacheManager1Minute")

常⽤之CacheEvict

  • CacheEvict
    • 从缓存中移除相应数据, 触发缓存删除的操作
    • value 缓存名称,可以有多个
    • key 缓存的key规则,可以⽤springEL表达式,默认是 ⽅法参数组合
    • beforeInvocation = false
      • 缓存的清除是否在⽅法之前执⾏ ,默认代表缓存清除 操作是在⽅法执⾏之后执⾏;
      • 如果出现异常缓存就不会清除
    • beforeInvocation = true
      • 代表清除缓存操作是在⽅法运⾏之前执⾏,⽆论⽅ 法是否出现异常,缓存都清除

案例:

@CacheEvict(value = {"product"},key = "#root.args[0]")

多注解组合Caching

  • Caching
    • 组合多个Cache注解使⽤
    • 允许在同⼀⽅法上使⽤多个嵌套的@Cacheable、 @CachePut和@CacheEvict注释
@Caching(
            cacheable = {
                    @Cacheable(value = {"product"},key = "#root.args[0]"),
                    @Cacheable(value = {"product"},key = "'heng'+#root.args[0]"),
            },
            put = {
                    @CachePut(value = {"product_test"}, key="#id", cacheManager = "cacheManager1Minute")
            }
    )

分布式缓存必考题之缓存击穿 +解决⽅案

  • 缓存击穿 (某个热点key缓存失效了)

    • 缓存中没有但数据库中有的数据,假如是热点数据,那 key在缓存过期的⼀刻,同时有⼤量的请求,这些请求 都会击穿到DB,造成瞬时DB请求量⼤、压⼒增⼤。
    • 和缓存雪崩的区别在于这⾥针对某⼀key缓存,后者则 是很多key。
  • 预防

    • 设置热点数据不过期
    • 定时任务定时更新缓存
    • 设置互斥锁
  • SpringCache解决⽅案

    • 缓存的同步 sync
    • sync 可以指示底层将缓存锁住,使只有⼀个线程可以 进⼊计算,⽽其他线程堵塞,直到返回结果更新到缓存 中
@Cacheable(value = {"product"},keyGenerator = "springCacheDefaultKeyGenerator",cacheManager = "cacheManager1Minute",sync = true)

分布式缓存必考题之缓存雪崩 +解决⽅案

  • 缓存雪崩 (多个热点key都过期)

    • ⼤量的key设置了相同的过期时间,导致在缓存在同⼀ 时刻全部失效,造成瞬时DB请求量⼤、压⼒骤增,引 起雪崩
  • 预防

    • 存数据的过期时间设置随机,防⽌同⼀时间⼤量数 据过期现象发⽣
    • 设置热点数据永远不过期,定时任务定时更新
  • SpringCache解决⽅案

    • 设置差别的过时时间
    • ⽐如CacheManager配置多个过期时间维度
    • 配置⽂件 time-to-live 配置
 cache:
     #使⽤的缓存类型
     type: redis
     #过时时间
     redis:
     time-to-live: 3600000
     # 开启前缀,默以为true
     use-key-prefix: true
     # 键的前缀,默认就是缓存名cacheNames
     key-prefix: XD_CACHE
     # 是否缓存空结果,防⽌缓存穿透,默以为true
     cache-null-values: true

分布式缓存必考题之缓存穿透 +解决⽅案

  • 缓存穿透(查询不存在数据)

    • 查询⼀个不存在的数据,由于缓存是不命中的,并且出 于容错考虑,如发起为id为“-1”不存在的数据
    • 如果从存储层查不到数据则不写⼊缓存这将导致这个不 存在的数据每次请求都要到存储层去查询,失去了缓存 的意义。存在⼤量查询不存在的数据,可能DB就挂掉 了,这也是⿊客利⽤不存在的key频繁攻击应⽤的⼀种 ⽅式
  • 预防

    • 接⼝层增加校验,数据合理性校验
    • 缓存取不到的数据,在数据库中也没有取到,这时 也可以将key-value对写为key-null,设置短点的过 期时间,防⽌同个key被⼀直攻击
  • SpringCache解决⽅案

    • 空结果也缓存,默认不配置condition或者unless就 ⾏
 cache:
     #使⽤的缓存类型
     type: redis
     #过时时间
     redis:
     time-to-live: 3600000
     # 开启前缀,默以为true
     use-key-prefix: true
     # 键的前缀,默认就是缓存名cacheNames
     key-prefix: XD_CACHE
     # 是否缓存空结果,防⽌缓存穿透,默以为true
     cache-null-values: true

RDB 和 AOF 使用

  • RDB持久化与AOF持久化⼀起使⽤
  • 如果Redis中的数据并不是特别敏感或者可以通过其它 ⽅式重写⽣成数据
  • 集群中可以关闭AOF持久化,靠集群的备份⽅式保证可 ⽤性
  • ⾃⼰制定策略定期检查Redis的情况,然后可以⼿动触 发备份、重写数据;
  • 采⽤集群和主从同步

Redis4.0后开始的rewrite⽀持混合模式

  • RDB和AOF 双⽤

  • 直接将rdb持久化的⽅式来操作将⼆进制内容覆盖到aof ⽂件中,rdb是⼆进制,所以很⼩

  • 有写⼊的话还是继续append追加到⽂件原始命令,等 下次⽂件过⼤的时候再次rewrite

  • 默认是开启状态

  • 好处

    • 混合持久化结合了RDB持久化 和 AOF 持久化的优 点,采取了rdb的⽂件⼩易于灾难恢复
    • 同时结合AOF,增量的数据以AOF⽅式保存了,数据 更少的丢失
  • 坏处

    • 前部分是RDB格式,是⼆进制,所以阅读性较差
  • 数据恢复

    • 先看是否存在aof⽂件,若存在则先按照aof⽂件恢 复,aof⽐rdb全,且aof⽂件也rewrite成rdb⼆进制 格式
    • 若aof不存在,则才会查找rdb是否存在

Redis6的key过期时间删除策略

  • redis key过期策略

    • 定期删除+惰性删除
  • Redis如何淘汰过期的keys:

    • 定期删除:
      • 隔⼀段时间,就随机抽取⼀些设置了过期时间的 key,检查其是否过期,如果过期就删除
      • 定期删除可能会导致很多过期 key 到了时间并没有 被删除掉,那咋整呢,所以就是惰性删除
    • 惰性删除 :
      • 概念:当⼀些客户端尝试访问它时,key会被发现并 主动的过期
      • 放任键过期不管,但是每次从键空间中获取键时, 都检查取得的键是否过期,如果过期的话,就删除 该键

内存不⾜时-Redis的Key淘 汰策略

  • 简介

redis在占⽤的内存超过指定的maxmemory之后, 通过maxmemory_policy确定redis是否释放内存以及 如何释放内存 提供多种策略

  • 策略

    • volatile-lru(least recently used)
      • 最近最少使⽤算法,从设置了过期时间的键中选择 空转时间最⻓的键值对清除掉;
    • volatile-lfu(least frequently used)
      • 最近最不经常使⽤算法,从设置了过期时间的键中 选择某段时间之内使⽤频次最⼩的键值对清除掉;
    • volatile-ttl
      • 从设置了过期时间的键中选择过期时间最早的键值 对清除 (删除即将过期的)
    • volatile-random
      • 从设置了过期时间的键中,随机选择键进⾏清除;
    • allkeys-lru
      • 最近最少使⽤算法,从所有的键中选择空转时间最 ⻓的键值对清除;
    • allkeys-lfu
      • 最近最不经常使⽤算法,从所有的键中选择某段时 间之内使⽤频次最少的键值对清除;
    • allkeys-random
      • 所有的键中,随机选择键进⾏删除;
    • noeviction
      • 不做任何的清理⼯作,在redis的内存超过限制之 后,所有的写⼊操作都会返回错误;但是读操作都 能正常的进⾏
  • config配置的时候 下划线_的key需要⽤中横线-

127.0.0.1:6379> config set maxmemory_policy
volatile-lru
(error) ERR Unsupported CONFIG parameter:
maxmemory_policy
127.0.0.1:6379> config set maxmemory-policy
volatile-lru
OK

主从 哨兵

主从 加速复制

  • 完全重新同步需要在磁盘上创建⼀个RDB⽂件,然后加载这个⽂件以便为从服务器发送数据
  • 在⽐较低速的磁盘,这种操作会给主服务器带来较⼤的 压⼒
  • 新版⽀持⽆磁盘的复制,⼦进程直接将RDB通过⽹络发 送给从服务器,不使⽤磁盘作为中间存储
  • repl-diskless-sync yes (默认是no

Sentinel(哨兵)三⼤⼯作任务

  • 监控(Monitoring)

    • Sentinel 会不断地检查你的主服务器和从服务器是 否运作正常
  • 提醒(Notification)

    • 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应⽤程序 发送通知
  • 自动故障迁移(Automatic failover)

    • 当⼀个主服务器不能正常⼯作时, Sentinel 会开始 ⼀次⾃动故障迁移操作, 它会将失效主服务器的其 中⼀个从服务器升级为新的主服务器, 并让失效主 服务器的其他从服务器改为复制新的主服务器
    • 当客户端试图连接失效的主服务器时, 集群也会向 客户端返回新主服务器的地址, 使得集群可以使⽤ 新主服务器代替失效服务器
  • 问题

    • ⼀个哨兵进程对Redis服务器进⾏监控,可能会出现问题
    • ⼀般是使⽤多个哨兵进⾏监控,各个哨兵之间还会进⾏ 监控,形成多哨兵模式
  • 多哨兵模式下线名称介绍

    • 主观下线(Subjectively Down, 简称 SDOWN)
      • 是单个 Sentinel 实例对服务器做出的下线判断,⽐ 如⽹络问题接收不到通知等
      • ⼀个服务器没有在 down-after-milliseconds 选项所 指定的时间内, 对向它发送 PING 命令的 Sentinel 返回⼀个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线
    • 客观下线(Objectively Down, 简称 ODOWN)
      • 指的是多个 Sentinel 实例在对同⼀个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-masterdown-by-addr 命令互相交流之后, 得出的服务器 下线判断
      • ⼀个 Sentinel 可以通过向另⼀个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对 ⽅是否认为给定的服务器已下线
      • 客观下线条件只适⽤于主服务器
  • 仲裁 qurum

    • Sentinel 在给定的时间范围内, 从其他 Sentinel 那 ⾥接收到了【⾜够数量】的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变 为客观下线
    • 这个【⾜够数量】就是配置⽂件⾥⾯的值,⼀般是 Sentinel个数的⼀半加1,⽐如3个Sentinel则就设置 为2
    • down-after-milliseconds 是⼀个哨兵在超过规定时 间依旧没有得到响应后,会⾃⼰认为主机不可⽤
    • 当拥有认为主观下线的哨兵达到sentinel monitor所 配置的数量时,就会发起⼀次投票,进⾏failover

节点⾼可⽤之Cluster数据分⽚ 和虚拟哈希槽介

背景:

  • 主节点的写能⼒和存储能⼒受限
  • 单台机器⽆法满⾜需求,因此把数据分散存储到多个机 器
  • 类似案例:mysql分库分表

常⻅的数据分区算法

  • 哈希取模
    • 对选择的 partitioning key 计算其哈希值,得到的哈 希值就是对应的分区
  • 范围分⽚
    • 通过确定分区键是否在某个范围内来选择分区
  • ⼀致性Hash分区
  • redis cluster集群没有采⽤⼀致性哈希⽅案,⽽是采⽤ 【数据分⽚】中的哈希槽来进⾏数据存储与读取的

什么是Redis的哈希槽 slot

  • Redis集群预分好16384个槽,当需要在 Redis 集群中 放置⼀个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将⼀个key放到哪个桶中

  • 假设主节点的数量为3,将16384个槽位按照【⽤户⾃ ⼰的规则】去分配这3个节点,每个节点复制⼀部分槽 位

    • 节点1的槽位区间范围为0-5460
    • 节点2的槽位区间范围为5461-10922
    • 节点3的槽位区间范围为10923-16383
    • 注意:从节点是没有槽位的,只有主节点才有
  • 存储查找

    • 对要存储查找的键进⾏crc16哈希运算,得到⼀个值, 并取模16384,判断这个值在哪个节点的范围区间
    • 假设crc16(“test_key”)%16384=3000,就是节点⼀
    • crc16算法不是简单的hash算法,是⼀种校验算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RKtg7TyJ-1623767260758)(C:\Users\heng\AppData\Roaming\Typora\typora-user-images\image-20210611165627241.png)]

  • 使⽤哈希槽的好处就在于可以⽅便的添加或移除节点
    • 当需要增加节点时,只需要把其他节点的某些哈希槽挪 到新节点就可以了;
    • 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就⾏了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值