redis6.0分布式缓存+高可用集群整合springboot

redis6.0分布式缓存+高可用集群整合springboot,第1张

解决问题思路经验:
项目代码:看日志、找核心定位
中间件框架:进官网、github->issue
其他:论坛如csdn、stackvoerflow

redis详细配置文件详解:请点击此处跳转

文章目录
    • 本地缓存和分布式缓存
    • 源码安装redis
    • 配置文件
    • redis结构
      • zSet跳跃表
    • springboot 整合redis
      • redisTemplate
      • redisTemplate序列化与反序列化
    • 自定义序列化和反序列化机制配置实战
    • SpringBoot整合连接池配置
        • Lettuce客户端(推荐)
        • Jedis客户端(不推荐)
    • redis验证图形验证码
    • Jmeter5.x压力测试工具
    • SpringCache缓存框架
    • SpringCache框架自定义CacheManager配置和过期时间
      • SpringCache框架自定义缓存KeyGenerator
      • CachePut修改
      • CacheEvict 删除缓存
      • 多注解组合Caching
      • 缓存击穿 (某个热点key缓存失效了)
      • 分布式缓存必考题之缓存雪崩+解决方案
      • 缓存穿透(查询不存在数据)
    • Redis持久化介绍
      • RDB
      • AOF
      • 持久化配置AOF重新rewrite配置
      • Redis4.0后的rewrite支持混合模式
    • Redis6.x服务端配置 info命令-生产监控知识
    • Redis6.x服务端配置config命令
    • Redis6的key过期时间删除策略
    • Redis6.X主从复制+读写分离
      • 一主二从模式:
      • 创建主节点配置文件redis.conf
      • (一)从节点配置文件redis.conf
      • (二)从节点配置文件redis.conf
      • 启动一主二从
    • 主从复制-读写分离原理
    • 高可用监控之Sentinel哨兵模式
      • 新版SpringBoot/微服务cloud整合Redis主从+Sentinel哨兵
    • Redis6.X之Cluster集群
      • Cluster集群环境
      • cluster启动6个节点
      • Cluster集群读写命令
    • Redis6核心特性-多线程
    • Redis6核心特性-acl 权限控制
    • Redis6核心特性-Client-Side-Caching 客户端缓存

本地缓存和分布式缓存
  • 和业务数据结合去选择 高并发项目里面一般都是有本地缓存和分布式缓存共同 存在的

  • 热点key的解决方案之一:避免带宽或者传输影响,本地缓存热点key数据,对于每次读请求,将首先检查key是否存在于本地缓存中,如果存在则直接返回,如果不存在再去访问分布式缓存的机器

    • 缓存中的某些Key对应的value存储在集群中一台机器,使得所有流量涌向同一机器,成为系统的瓶颈,无法通过增加机器容量来解决

    • 热卖商品、热点新闻、热点评论、大V明星结婚

源码安装redis
  • 源码安装Redis-上传到Linux服务(先安装升级gcc再编译,不然会有问题)
#安装gcc
yum install -y gcc-c++ autoconf automake
​
#centos7 默认的 gcc 默认是4.8.5,版本小于 5.3 无法编译,需要先安装gcc新版才能编译
gcc -v
​
#升级新版gcc,配置永久生效
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
​
scl enable devtoolset-9 bash  
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile 

# 解压
tar -zxvf redis-6.2.1.tar.gz​
mv redis-6.2.1 redis

#编译redis
cd redis
make
​
#安装到指定目录
mkdir -p /usr/local/redis
​
make PREFIX=/usr/local/redis install


配置文件
  • 配置

    • daemonize yes 配置后台运行,默认no

    • bind 绑定指定ip访问,0.0.0.0是不限制,配置多个ip例子 12.13.432.12 31.12.43.13 用空格隔开

    • port 端口号 默认6379

    • requirepass 密码配置

    • dbfilename 配置redis持久化文件名称

    • dir 配置redis持久化文件存储地址

    • save 配置redis持久化机制

  • 创建目录

    • 日志 /usr/local/redis/log

    • 数据 /usr/local/redis/data

    • 配置文件 /usr/local/redis/conf

  • 创建自定义配置文件 (使用自带的也行)

#任何ip可以访问
bind 0.0.0.0
​
#守护进程
daemonize yes
​
#密码
requirepass 123456
​
#日志文件
logfile "/usr/local/redis/log/redis.log"
​
#持久化文件名称
dbfilename xdclass.rdb
​
#持久化文件存储路径
dir /usr/local/redis/data
​
#持久化策略, 10秒内有个1个key改动,执行快照
save 10 1

启动redis指定配置文件

./redis-server ../conf/redis.conf
  • key命名规范

    • 方便管理+易读

    • 不要过长,本身key也占据空间

    • 冒号分割,不要有特殊字符(空格-引号-转义符)

    • 例子:业务名:表名:ID

      • product-service:produdct:1

      • user:sign:1

  • 单机默认16个数据库,集群的话则没有这个概念,而是solt槽位

  • 在线工具学习: http://try.redis.io/

redis结构

  • 使用HashMap+跳表skipList保证数据存储和有序

  • 应用场景:商品日销榜、积分榜等

  • 什么是跳跃表:性能堪比红黑树,而且实现起来比红黑树简单很多

zSet跳跃表

springboot 整合redis
  • java语言客户端:

    • jedis

    Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,需要使用连接池

    其API提供了比较全面的Redis命令的支持,相比于其他Redis 封装框架更加原生

    Jedis中的方法调用是比较底层的暴露的Redis的API,Java方法基本和Redis的API保持着一致

    使用阻塞的I/O,方法调用同步,程序流需要等到socket处理完I/O才能执行,不支持异步 *** 作

    • lettuce

    高级Redis客户端,用于线程安全同步,异步响应

    基于Netty的的事件驱动,可以在多个线程间并发访问, 通过异步的方式可以更好的利用系统资源

redisTemplate
  • RedisTemplate介绍

    • ValueOperations:简单K-V *** 作

    • SetOperations:set类型数据 *** 作

    • ZSetOperations:zset类型数据 *** 作

    • HashOperations:针对map类型的数据 *** 作

    • ListOperations:list类型的数据 *** 作

  • RedisTemplate和StringRedisTemplate的区别

    • StringRedisTemplate继承RedisTemplate

    • 两者的数据是不共通的(默认的序列化机制导致key不一样)

    • StringRedisTemplate默认采用的是String的序列化策略

    • RedisTemplate默认采用的是JDK的序列化策略,会将数据先序列化成字节数组然后在存入Redis数据库

    • 总结

      • 当redis数据库里面本来 *** 作的是字符串数据的时候,那使用StringRedisTemplate即可

      • 数据是复杂的对象类型,那么使用RedisTemplate是更好的选择

  • *** 作

    • String结构

      • 存储字符串

      • 存储对象

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类型
自定义序列化和反序列化机制配置实战
@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);ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// 设置支持事物
        //redisTemplate.setEnableTransactionSupport(true);
​
        redisTemplate.afterPropertiesSet();return redisTemplate;
    }}
SpringBoot整合连接池配置 Lettuce客户端(推荐)
<dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active = 100
​
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle = 10
​
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle = 5
​
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait= -1ms
​
#指定客户端
spring.redis.client-type = lettuce
  • 断点调试 redisTemplate的connectionFactory实现

Jedis客户端(不推荐)
  • Jedis连接池介绍(可以不排除lettuce依赖包)
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.lettuce</groupId>
          <artifactId>lettuce-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    
    <!--不用指定版本号,本身spring-data-redis里面有-->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>
    
<dependency>
    <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.6.1</version>
  </dependency>
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active = 100
​
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle = 10
​
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle = 0
​
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait= -1ms
​
#指定客户端
spring.redis.client-type = jedis
redis验证图形验证码
 <!--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, "4");
        //字体间隔
        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");
        //文字来源
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }
}

CommonUtil

  /**
     * 获取ip
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) {
                // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        return ipAddress;
    }
​
​
​
    public static String MD5(String data)  {
        try {
            java.security.MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
            return sb.toString().toUpperCase();
        } catch (Exception exception) {
        }
        return null;}
Jmeter5.x压力测试工具
  • LoadRunner

    • 性能稳定,压测结果及细粒度大,可以自定义脚本进行压测,但是太过于重大,功能比较繁多
  • Apache AB(单接口压测最方便)

    • 模拟多线程并发请求,ab命令对发出负载的计算机要求很低,既不会占用很多CPU,也不会占用太多的内存,但却会给目标服务器造成巨大的负载, 简单DDOS攻击等
  • Webbench

    • webbench首先fork出多个子进程,每个子进程都循环做web访问测试。子进程把访问的结果通过pipe告诉父进程,父进程做最终的统计结果。
  • Jmeter

    • 开源免费,功能强大,在互联网公司普遍使用
  • 压测工具本地快速安装Jmeter5.x

    • 需要安装JDK8 以上

    • 建议安装JDK环境,虽然JRE也可以,但是压测https需要JDK里面的 keytool工具

    • 快速下载 https://jmeter.apache.org/download_jmeter.cgi

    • 文档地址:http://jmeter.apache.org/usermanual/get-started.html

  • 目录

bin:核心可执行文件,包含配置

jmeter.bat: windows启动文件(window系统一定要配置显示文件拓展名)

jmeter: mac或者linux启动文件

jmeter-server:mac或者Liunx分布式压测使用的启动文件

jmeter-server.bat:window分布式压测使用的启动文件

jmeter.properties: 核心配置文件 

extras:插件拓展的包

lib:核心的依赖包

  • Jmeter语言版本中英文切换

    • 控制台修改 menu -> options -> choose language
  • 配置文件修改

    • bin目录 -> jmeter.properties

    • 默认 #language=en

    • 改为 language=zh_CN

  • 添加->threads->线程组(控制总体并发)

线程数:虚拟用户数。一个虚拟用户占用一个进程或线程

准备时长(Ramp-Up Period(in seconds)):全部线程启动的时长,比如100个线程,20秒,则表示20秒内 100个线程都要启动完成,每秒启动5个线程

循环次数:每个线程发送的次数,假如值为5100个线程,则会发送500次请求,可以勾选永远循环
  • 线程组->添加-> Sampler(采样器) -> Http (一个线程组下面可以增加几个Sampler)
名称:采样器名称

注释:对这个采样器的描述

web服务器:

  默认协议是http

  默认端口是80

  服务器名称或IP :请求的目标服务器名称或IP地址

路径:服务器URL
  • 查看测试结果
线程组->添加->监听器->察看结果树

线程组->添加->监听器->聚合报告

SpringCache缓存框架
<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 配置文件指定缓存类型
spring:
  cache:
    type: redis
  • 启动类开启缓存注解
@EnableCaching
  • Cacheable注解

    • 标记在一个方法上,也可以标记在一个类上

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

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

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

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

  • 案例

//对象
@Cacheable(value = {"product"}, key="#root.methodName")//分页
@Cacheable(value = {"product_page"},key="#root.methodName + #page+'_'+#size")
  • spEL表达式

    • methodName 当前被调用的方法名

      • root.methodname
    • args 当前被调用的方法的参数列表

      • root.args[0]
    • result 方法执行后的返回值

      • result
SpringCache框架自定义CacheManager配置和过期时间
  • 修改redis缓存序列化器和配置manager过期时间
 @Bean
    @Primary
    public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(3600L);
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }@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);
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(ttl))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    }
SpringCache框架自定义缓存KeyGenerator

自定规则

    @Bean
    public KeyGenerator springCacheDefaultKeyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                // 类名 + 方法名 + 参数
                return o.getClass().getSimpleName() + "_"
                        + method.getName() + "_"
                        + StringUtils.arrayToDelimitedString(objects, "_");
            }
        };}

key 属性和keyGenerator属性只能二选一

@Cacheable(value = {"product"},keyGenerator = "springCacheCustomKeyGenerator", cacheManager = "cacheManager1Minute")
CachePut修改
  • 根据方法的请求参数对其结果进行缓存,每次都会触发真实方法的调用

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

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

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

  • 案例实战

@CachePut(value = {“product”},key = “#productDO.id”)

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",keyGenerator = "xdclassKeyGenerator")
            },
            put = {
                    @CachePut(value = "product",key = "#id"),
                    @CachePut(value = "product",key = "'stock:'+#id")
            }
    )
缓存击穿 (某个热点key缓存失效了)
  • 缓存中没有但数据库中有的数据,假如是热点数据,那key在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力增大。

  • 和缓存雪崩的区别在于这里针对某一key缓存,后者则是很多key。

  • 预防

    • 设置热点数据不过期

    • 定时任务定时更新缓存

    • 设置互斥锁

  • SpringCache解决方案

    • 缓存的同步 sync

    • sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中

@Cacheable(value = {"product"},key = "#root.args[0]", 
cacheManager = "customCacheManager", 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就行

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

查询一个不存在的数据,由于缓存是不命中的,并且出于容错考虑,如发起为id为“-1”不存在的数据

如果从存储层查不到数据则不写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。存在大量查询不存在的数据,可能DB就挂掉了,这也是黑客利用不存在的key频繁攻击应用的一种方式。

预防

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

空结果也缓存,默认不配置condition或者unless就行

Redis持久化介绍 RDB
  • Redis是一个内存数据库,如果没有配置持久化,redis重启后数据就全丢失

  • 因此开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。

  • 两种持久化方式

    • RDB (Redis DataBase)

    • AOF (append only file)

  • RDB持久化介绍

    • 在指定的时间间隔内将内存中的数据集快照写入磁盘

    • 默认的文件名为dump.rdb

    • 产生快照的情况

      • save

        • 会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止
      • bgsave

        • fork创建子进程,RDB持久化过程由子进程负责,会在后台异步进行快照 *** 作,快照同时还可以响应客户端请求
      • 自动化

        • 配置文件来完成,配置触发 Redis的 RDB 持久化条件

        • 比如 “save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave

      • 主从架构

        • 从服务器同步数据的时候,会发送sync执行同步 *** 作,master主服务器就会执行bgsave
  • 优点

    • RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复

    • 在恢复大数据集时的速度比 AOF 的恢复速度要快

    • 生成的是一个紧凑压缩的二进制文件

  • 缺点

    • 每次快照是一次全量备份,fork子进程进行后台 *** 作,子进程存在开销

    • 在快照持久化期间修改的数据不会被保存,可能丢失数据

  • 核心配置

    • dir 持久化文件的路径

    • dbfilename 文件名

#任何ip可以访问
bind 0.0.0.0
​
#守护进程
daemonize yes
​
#密码
requirepass 123456
​
#日志文件
logfile "/usr/local/redis/log/redis.log"
​
#持久化文件名称
dbfilename xdclass.rdb
​
#持久化文件存储路径
dir /usr/local/redis/data
​
#持久化策略, 10秒内有个1个key改动,执行快照
save 10 1
​
######之前配置######
​
#导出rdb数据库文件压缩字符串和对象,默认是yes,会浪费CPU但是节省空间
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
  • 备注: linux内存分配策略
0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;
  否则,内存申请失败,并把错误返回给应用进程
​
1 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
2 表示内核允许分配超过所有物理内存和交换空间总和的内存
​
解决方式
echo 1 > /proc/sys/vm/overcommit_memory
​
​
持久化配置
vim /etc/sysctl.conf
​
改为
vm.overcommit_memory=1
修改sysctl.conf后,需要执行 sysctl -p 以使生效。

AOF
  • AOF持久化介绍

    • append only file,追加文件的方式,文件容易被人读懂

    • 以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的

    • 写入过程宕机,也不影响之前的数据,可以通过 redis-check-aof检查修复问题

  • 配置实战

    • appendonly yes,默认不开启

    • AOF文件名 通过 appendfilename 配置设置,默认文件名是appendonly.aof

    • 存储路径同 RDB持久化方式一致,使用dir配置

  • 核心原理

    • Redis每次写入命令会追加到aof_buf(缓冲区)

    • AOF缓冲区根据对应的策略向硬盘做同步 *** 作

    • 高频AOF会带来影响,特别是每次刷盘

  • 提供了3种同步方式,在性能和安全性方面做出平衡

    • appendfsync always

      • 每次有数据修改发生时都会写入AOF文件,消耗性能多
    • appendfsync everysec

      • 每秒钟同步一次,该策略为AOF的缺省策略。
    • appendfsync no

      • 不主从同步,由 *** 作系统自动调度刷磁盘,性能是最好的,但是最不安全
bind 0.0.0.0
​
daemonize yes
​
requirepass 123456Xdclass
​
logfile "/usr/local/redis/log/redis.log"
​
dbfilename xdclass.rdb
​
dir /usr/local/redis/data
​
#save 10 2
#save 100 5
save ""
rdbcompression yes
#对rdb数据进行校验,耗费CPU资源,默认为yes
rdbchecksum yes
​
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
持久化配置AOF重新rewrite配置
  • rewrite 重写介绍

    • AOF文件越来越大,需要定期对AOF文件进行重写达到压缩

    • 旧的AOF文件含有无效命令会被忽略,保留最新的数据命令

    • 多条写命令可以合并为一个

    • AOF重写降低了文件占用空间

    • 更小的AOF 文件可以更快地被Redis加载

  • 重写触发配置

    • 手动触发

      • 直接调用bgrewriteaof命令
    • 自动触发

      • auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数

      • auto-aof-rewrite-min-size

        • 表示运行AOF重写时文件最小体积,默认 为64MB。
      • auto-aof-rewrite-percentage

        • 代表当前AOF文件空间和上一次重写后AOF文件空间(aof_base_size)的比值。
  • 常用配置

# 是否开启aof
appendonly yes
​
# 文件名称
appendfilename "appendonly.aof"
​
# 同步方式
appendfsync everysec
​
# aof重写期间是否同步
no-appendfsync-on-rewrite no
​
# 重写触发配置
# 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写
auto-aof-rewrite-percentage 100

# AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,6.x默认配置64mb。
auto-aof-rewrite-min-size 64mb
​
# 加载aof时如果有错如何处理
# yes表示如果aof尾部文件出问题,写log记录并继续执行。no表示提示写入等待修复后写入
​
aof-load-truncated yes 
  • Redis提供了不同的持久性选项:

    • RDB持久化以指定的时间间隔执行数据集的时间点快照。

    • AOF持久化记录服务器接收的每个写入 *** 作,将在服务器启动时再次读取,重建原始数据集。使用与Redis协议本身相同的格式以仅追加方式记录命令,当文件太大时,Redis能够重写

  • RDB的优缺点

    • 优点:

      • RDB最大限度地提高了Redis的性能,父进程不需要参与磁盘I/O

      • RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复

      • 在恢复大数据集时的速度比 AOF 的恢复速度要快

      • 生成的是一个紧凑压缩的二进制文件

    • 缺点:

      • 如果您需要在Redis停止工作时(例如断电后)将数据丢失的可能性降至最低,则RDB并不好

      • RDB经常需要fork才能使用子进程持久存储在磁盘上。如果数据集很大,Fork可能会非常耗时

  • AOF的优缺点

    • 优点:

      • 数据更加安全

      • 当Redis AOF文件太大时,Redis能够在后台自动重写AOF

      • AOF以易于理解和解析的格式,一个接一个地包含所有 *** 作的日志

    • 缺点:

      • AOF文件通常比同一数据集的等效RDB文件大

      • 根据确切的fsync策略,恢复的时候AOF可能比RDB慢

  • 在线上我们到底该怎么做?

    • 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.x服务端配置 info命令-生产监控知识

info命令介绍

  • 服务器的各种信息和统计数值
Server:有关redis服务器的常规信息
  redis_mode:standalone                         # 运行模式,单机或者集群
  multiplexing_api:epoll                        # redis所使用的事件处理机制
  run_id:3abd26c33dfd059e87a0279defc4c96c13962ede # redis服务器的随机标识符(用于sentinel和集群)
  config_file:/usr/local/redis/conf/redis.conf       # 配置文件路径
​
Clients:客户端连接部分
  connected_clients:10           # 已连接客户端的数量(不包括通过slave连接的客户端)
  
Memory:内存消耗相关信息
  used_memory:874152  # 使用内存,以字节(byteB为单位
  used_memory_human:853.66K # 以人类可读的格式返回 Redis 分配的内存总量
  used_memory_rss:2834432 # 系统给redis分配的内存即常驻内存,和top 、 ps 等命令的输出一致
  used_memory_rss_human:2.70M  # 以人类可读的格式返回系统redis分配的常驻内存top、ps等命令的输出一致
  used_memory_peak:934040  # 内存使用的峰值大小
  used_memory_peak_human:912.15K
​
  total_system_memory:1039048704  #  *** 作系统的总内存 ,以字节(byte)为单位
  total_system_memory_human:990.91M 
  used_memory_lua:37888  # lua引擎使用的内存
  used_memory_lua_human:37.00K
​
  maxmemory:0  # 最大内存的配置值,0是不限制
  maxmemory_human:0B  
  maxmemory_policy:noeviction  # 达到最大内存配置值后的策略
  
Persistence:RDB和AOF相关信息
  rdb_bgsave_in_progress:0   # 标识rdb save是否进行中
  rdb_last_bgsave_status:ok        # 上次的save *** 作状态
  rdb_last_bgsave_status:ok        # 上次的save *** 作状态
  rdb_last_bgsave_time_sec:-1        # 上次rdb save *** 作使用的时间(单位s)
  rdb_current_bgsave_time_sec:-1    # 如果rdb save *** 作正在进行,则是所使用的时间
​
  aof_enabled:1                    # 是否开启aof,默认没开启
  aof_rewrite_in_progress:0        # 标识aof的rewrite *** 作是否在进行中
  aof_last_rewrite_time_sec:-1    # 上次rewrite *** 作使用的时间(单位s)
  aof_current_rewrite_time_sec:-1 # 如果rewrite *** 作正在进行,则记录所使用的时间
  aof_last_bgrewrite_status:ok    # 上次rewrite *** 作的状态
  aof_current_size:0                # aof当前大小
​
​
Stats:一般统计
  evicted_keys:0                    # 因为内存大小限制,而被驱逐出去的键的个数
​
​
Replication:主从同步信息
  role:master                        # 角色
  connected_slaves:1                # 连接的从库数
  master_sync_in_progress:0        # 标识主redis正在同步到从redis
​
​
CPU:CPU消耗统计
​
Cluster:集群部分
  cluster_enabled:0  # 实例是否启用集群模式
​
Keyspace:数据库相关统计
  db0:keys=4,expires=0,avg_ttl=0  # db0的key的数量,带有生存期的key的数,平均存活时间
Redis6.x服务端配置config命令
  • config命令介绍(都有默认值)

    • 可以动态地调整 Redis 服务器的配置(configuration)而无须重启

    • config get xxx、config set xxx

daemonize  #后端运行
bind #ip绑定
timeout  #客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接
​
databases #设置数据库的个数,可以使用 SELECT 命令来切换数据库。默认使用的数据库是 0
save  #设置 Redis 进行rdb持久化数据库镜像的频率。
rdbcompression #在进行镜像备份时,是否进行压缩
​
slaveof #设置该数据库为其他数据库的从数据库
masterauth  #当主数据库连接需要密码验证时,在这里配置
​
maxclients  #限制同时连接的客户数量,当连接数超过这个值时,redis 将不再接收其他连接请求,返回error
​
maxmemory  #设置 redis 能够使用的最大内存,
  • 备注

    • 防止所用内存超过服务器物理内存, maxmemory限制的是Redis实际使用的内存量, 也就是used_memory统计项对应的内存

    • 由于内存碎片率的存在, 实际消耗的内存可能会比maxmemory设置的更大, 实际使用时要小心这部分内存溢出

    • 默认无限使用服务器内存, 为防止极端情况下导致系统内存耗尽, 建议所有的Redis进程都要配置maxmemory

    • 在64bit系统下,maxmemory设置为0表示不限制Redis内存使用,在32bit系统下,maxmemory不能超过3GB

redis在占用的内存超过指定的maxmemory之后,通过maxmemory_policy确定redis是否释放内存以及如何释放内存

Redis6的key过期时间删除策略
  • redis的key配置了过期时间,这个是怎么被删除的

  • redis数据明明过期了,怎么还占用着内存?

  • Redis 就只能用 10G,你要是往里面写了 20G 的数据,会发生什么?淘汰哪些数据

redis key过期策略

  • 定期删除+惰性删除。

  • Redis如何淘汰过期的keys: set name xdclass 3600

    • 定期删除:

      • 隔一段时间,就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,

      • 定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢,所以就是惰性删除

    • 惰性删除 :

      • 概念:当一些客户端尝试访问它时,key会被发现并主动的过期

      • 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键

  • Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。

  • 问题

    • 如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?

    • 如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,就需要走内存淘汰机制

  • 设计缓存中间件:可以参考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
OK
Redis6.X主从复制+读写分离

一主二从模式:
mkdir -p /data/redis/master/data 
mkdir -p /data/redis/slave1/data 
mkdir -p /data/redis/slave2/data
​

# 从节点开启只读模式(默认)
replica-read-only yes

# 从节点访问主节点的密码,和requirepass一样
masterauth 123456

# 哪个主节点进行复制(在从节点里面配置 主节点的ip和端口号)
replicaof 8.129.113.233 6379
创建主节点配置文件redis.conf
bind 0.0.0.0
port 6379
# 守护进程,后台方式运行
daemonize yes
​
requirepass "123456"
​
logfile "/usr/local/redis/log/redis1.log"
dbfilename "xdclass1.rdb"

# 数据存放目录
dir "/usr/local/redis/data"

# 开启AOF
appendonly yes
appendfilename "appendonly1.aof"
# 主节点密码
masterauth "123456"
(一)从节点配置文件redis.conf
bind 0.0.0.0
port 6380
daemonize yes

requirepass "123456"

logfile "/usr/local/redis/log/redis2.log"
dbfilename "xdclass2.rdb"

dir "/usr/local/redis/data"

appendonly yes
appendfilename "appendonly2.aof"

# 指定主节点的地址以及端口
replicaof 8.129.113.233 6379

masterauth "123456"
(二)从节点配置文件redis.conf
bind 0.0.0.0
port 6381
daemonize yes

requirepass "123456"

logfile "/usr/local/redis/log/redis3.log"
dbfilename "xdclass3.rdb"

dir "/usr/local/redis/data"

appendonly yes
appendfilename "appendonly3.aof"

# 指定主节点的地址以及端口
replicaof 8.129.113.233 6379

masterauth "123456"
启动一主二从

先进入bin目录

cd /usr/local/redis/bin

#启动主
./redis-server /data/redis/master/data/redis.conf
​
#启动从
./redis-server /data/redis/slave1/data/redis.conf
​
#启动从
./redis-server /data/redis/slave2/data/redis.conf

bin目录下

./redis-cli -p 6379 -a 密码

info replication 命令查看状态

主从复制-读写分离原理
  • 主从复制分两种(主从刚连接的时候,进行全量同步;全同步结束后,进行增量同步)

    • 全量复制

      • master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件

      • 主服务器会缓存所有接收到的来自客户端的写命令,当后台保存进程 处理完毕后,会将该rdb文件传递给slave服务器

      • slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存

      • 在此之后master服务器会将在此期间缓存的命令通过redis传输协议发送给slave服务器

      • 然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性

    • 增量复制

      • Slave初始化后开始正常工作时主服务器发生的写 *** 作同步到从服务器的过程

      • 服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令

特点

  • 主从复制对于 主/从 redis服务器来说是非阻塞的,所以同步期间都可以正常处理外界请求

  • 一个主redis可以含有多个从redis,每个从redis可以接收来自其他从redis服务器的连接

  • 从节点不会让key过期,而是主节点的key过期删除后,成为del命令传输到从节点进行删除

    • 从节点开启 sync 看日志
  • 加速复制

    • 完全重新同步需要在磁盘上创建一个RDB文件,然后加载这个文件以便为从服务器发送数据

    • 在比较低速的磁盘,这种 *** 作会给主服务器带来较大的压力

    • 新版支持无磁盘的复制,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储

    • repl-diskless-sync yes (默认是no)

  • 主从断开重连

    • 如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步

    • 2.8版本后 部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令

高可用监控之Sentinel哨兵模式
  • 哨兵模式介绍

    • Redis提供了哨兵的命令,是一个独立的进程

    • 原理 哨兵通过发送命令给多个节点,等待Redis服务器响应,从而监控运行的多个Redis实例的运行情况

    • 当哨兵监测到master宕机,会自动将slave切换成master,通过通知其他的从服务器,修改配置文件切换主机

  • 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-master-down-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

  • 核心流程

    • 每秒ping,超过时间不响应 则认为主观下线

    • 满足多个,则认为是客观下线

    • 投票选择主节点

    • 如果没有足够的节点同意master下线,则状态会被移除

  • 环境准备

    • 配置3个哨兵,每个哨兵的配置都是一样的

    • 启动顺序 先启动主再启动从,最后启动3个哨兵

    • 哨兵端口是 【26379】记得开放

说明:

#不限制ip
bind 0.0.0.0
​
# 让sentinel服务后台运行
daemonize yes
​
# 配置监听的主服务器,mymaster代表服务器的名称,自定义,172.18.172.109 代表监控的主服务器,6379代表端口,
#2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover *** 作。
sentinel monitor mymaster 172.18.172.109 6379 2
​
# sentinel auth-pass定义服务的密码,mymaster是服务名称,123456Redis服务器密码
sentinel auth-pass mymaster 123456
​
#超过5秒master还没有连接上,则认为master已经停止
sentinel down-after-milliseconds mymaster 5000
​
#如果该时间内没完成failover *** 作,则认为本次failover失败
sentinel failover-timeout mymaster 30000

在目录下创建3个文件sentinel-1.conf、sentinel-2.conf、sentinel-3.conf

cd /usr/local/redis/conf

port 26379
bind 0.0.0.0
daemonize yes
pidfile "/var/run/redis-sentinel-1.pid"
logfile "/var/log/redis/sentinel_26379.log"
dir "/tmp"
sentinel monitor mymaster 8.129.113.233 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
port 26380
bind 0.0.0.0
daemonize yes
pidfile "/var/run/redis-sentinel-2.pid"
logfile "/var/log/redis/sentinel_26380.log"
dir "/tmp"
sentinel monitor mymaster 8.129.113.233 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
port 26381
bind 0.0.0.0
daemonize yes
pidfile "/var/run/redis-sentinel-3.pid"
logfile "/var/log/redis/sentinel_26381.log"
dir "/tmp"
sentinel monitor mymaster 8.129.113.233 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000


  • 记得创建 /var/log/redis 文件夹

启动哨兵集群(哨兵模式是基于 主从模式的)

启动顺序 先启动主再启动从,最后启动3个哨兵

cd /usr/local/redis/bin

./redis-server /usr/local/redis/conf/sentinel-1.conf --sentinel
​
./redis-server /usr/local/redis/conf/sentinel-2.conf --sentinel
​
./redis-server /usr/local/redis/conf/sentinel-3.conf --sentinel
  • 网络安全组需要开放端口

  • 优点

    • 主从可以自动切换,可用性更高
  • 缺点

    • 主从切换会丢失短暂数据

    • 主节点的写能力和存储能力受限

新版SpringBoot/微服务cloud整合Redis主从+Sentinel哨兵
  • 注释掉yml配置文件中的 spring.redis.host和 spring.redis.port

  • 新增配置

sentinel:
  # 配置文件的 主节点的名称
  master: mymaster
  # 多个 sentinel 的地址以及端口
  nodes: 8.129.113.233:26379,8.129.113.233:26380,8.129.113.233:26381
Redis6.X之Cluster集群
  • 背景

    • Sentinel解决了主从架构故障自动迁移的问题

    • 但是Master主节点的写能力和存储能力依旧受限

    • 使用Redis的集群cluster就是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器

  • 什么是集群Cluster

    • 是一组相互独立的、通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理

    • 容易和分布式弄混,分布式系统简单的可以认为就一个庞大的系统,进行拆分度多个小系统

  • Redis集群模式介绍

    • Cluster模式是Redis3.0开始推出

    • 采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接

    • 官方要求:至少6个节点才可以保证高可用,即3主3从;扩展性强、更好做到高可用

    • 各个节点会互相通信,采用gossip协议交换节点元数据信息

    • 数据分散存储到各个节点上

  • 主节点的写能力和存储能力受限

  • 单台机器无法满足需求,因此把数据分散存储到多个机器

  • 常见的数据分区算法

    • 哈希取模

      • 对选择的 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算法,是一种校验算法

  • 使用哈希槽的好处就在于可以方便的添加或移除节点。

    • 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;

    • 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了

Cluster集群环境
  • 说明

    • 旧版本的需要使用ruby语言进行构建,新版5之后直接用redis-cli即可

    • 6个节点,三主双从,主从节点会自动分配,不是人工指定

    • 主节点故障后,从节点会替换主节点

  • 注意点:

    • 把之前的rdb、aof文件删除
  • 节点(网络安全组开放端口)

    • 6381、6382

    • 6383、6384

    • 6385、6386

  • 配置

cd /usr/loacl/redis/conf

mkdir cluster

touch redis1.conf

不要直接复制,会有乱码

bind 0.0.0.0
port 6381
# 守护线程的方式,后台运行
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis1.log"
dbfilename "xdclass1.rdb"
dir "/usr/local/redis/data"

appendonly yes
appendfilename "appendonly1.aof"
masterauth "123456"
​
#是否开启集群
cluster-enabled yes
​
# 生成的node文件,记录集群节点信息,默认为nodes.conf,防止冲突,改为nodes-6381.conf
cluster-config-file nodes-6381.conf
​
#节点连接超时时间
cluster-node-timeout 20000
​
#集群节点的ip,当前节点的ip
cluster-announce-ip 172.18.172.109
​
#集群节点映射端口
cluster-announce-port 6381
​
#集群节点总线端口,节点之间互相通信,常规端口+1万
cluster-announce-bus-port 16381

去除注释的配置

bind 0.0.0.0
port 6386
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis6.log"
dbfilename "xdclass6.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly6.aof"
masterauth "123456"
​
cluster-enabled yes
cluster-config-file nodes-6386.conf
cluster-node-timeout 20000
cluster-announce-ip 172.18.172.109
cluster-announce-port 6386
cluster-announce-bus-port 16386
cluster启动6个节点

cd /usr/local/redis/bin

./redis-server ../conf/cluster/redis1.conf
./redis-server ../conf/cluster/redis2.conf
./redis-server ../conf/cluster/redis3.conf
./redis-server ../conf/cluster/redis4.conf
./redis-server ../conf/cluster/redis5.conf
./redis-server ../conf/cluster/redis6.conf
  • 将6个节点 加入集群(其中一个节点执行以下命令即可)

    • –cluster 构建集群全部节点信息

    • –cluster-replicas 1 主从节点的比例,1表示1主1从的方式

./redis-cli -a 123456 --cluster create 172.18.172.109:6381 172.18.172.109:6382 \
172.18.172.109:6383 172.18.172.109:6384 172.18.172.109:6385 172.18.172.109:6386 \
--cluster-replicas 1


  • 检查状态信息(其中一个节点执行即可)
./redis-cli -a 123456 --cluster check 172.18.172.109:6381

Cluster集群读写命令
  • 集群状态

cd /usr/local/redis/bin

# -c 的话就是集群的模式
./redis-cli -c -a 123456 -p 6381
​
#集群信息
cluster info
​
#节点信息
cluster nodes


  • 测试集群读写命令set/get

    • key哈希运算计算槽位置

    • 槽在当前节点的话直接插入/读取,否则自动转向到对应的节点

  • *** 作都是主节点 *** 作,从节点只是备份


  • 启动应用

  • 加入集群

  • 从节点请求复制主节点(主从复制一样)

    • 先全量

    • 再增量

不在同个网络,所以集群改为阿里云公网ip地址才可以访问

  • 公司开发部署都会用同个网络

  • 开发的时候修改集群的地址为外网地址,配置文件修改(需要删除集群中以前的数据文件)

#对外的ip,集群中某个端口的ip
cluster-announce-ip 8.129.113.233   
#对外端口,集群节点中某个端口的对外端口
cluster-announce-port   
#集群桥接端口,集群节点内部通信端口
cluster-announce-bus-por 

  • 动态修改临时集群配置(则不需要删除集群中以前的数据文件)

    • config set cluster-announce-ip 8.129.113.233

  • 配置文件(注释Sentinel相关配置 以及 spring.redis.host和spring.redis.port)
spring:
  redis:
    cluster:
      #命名的最多转发次数
      max-redirects: 3
      # 所有集群节点
      nodes: 8.129.113.233:6381,8.129.113.233:6382,8.129.113.233:6383,8.129.113.233:6384,8.129.113.233:6385,8.129.113.233:6386

Cluster集群故障自动转移

# -c 是查看集群进入
./redis-cli -c -a 123456 -p 6381
​
#集群信息
cluster info
​
#节点信息
cluster nodes

1.关闭主节点后,从节点尝试重新连接主节点,发现连接不上,从节点则作为主节点

  • 高可用架构总结

    • 主从模式:读写分离,备份,一个Master可以有多个Slaves

    • 哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器

    • 集群: 为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,提高并发量。

Redis6核心特性-多线程

支持多线程

  • redis6多线程只是用来处理网络数据的读写和协议解析上,底层数据 *** 作还是单线程

  • 执行命令仍然是单线程,之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题

  • 默认不开启

io-threads-do-reads yes
io-threads 线程数
  • 官方建议 ( 线程数小于机器核数 )

    • 4 核的机器建议设置为 2 或 3 个线程

    • 8 核的建议设置为 4或6个线程,

  • 开启多线程后,是否会存在线程并发安全问题?

  • 不会有安全问题,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。

Redis6核心特性-acl 权限控制

引入了 ACL(Access Control List)

  • 之前的redis没有用户的概念,redis6引入了acl

  • 可以给每个用户分配不同的权限来控制权限

  • 通过限制对命令和密钥的访问来提高安全性,以使不受信任的客户端无法访问

  • 提高 *** 作安全性,以防止由于软件错误或人为错误而导致进程或人员访问 Redis,从而损坏数据或配置

  • 文档:https://redis.io/topics/acl

  • 常用命令

    • acl list 当前启用的 ACL 规则

    • acl cat 支持的权限分类列表

    • acl cat hash 返回指定类别中的命令

    • acl setuser 创建和修改用户命令

    • acl deluser 删除用户命令

+<command> 将命令添加到用户可以调用的命令列表中,如+@hash
-<command> 将命令从用户可以调用的命令列表中移除
​
#切换默认用户
auth default 123456
​
​
#例子 密码 123 ,全部key,全部权限
acl setuser jack on >123 ~* +@all
​
#例子 密码 123 ,全部key,get权限
acl setuser jack on >123 ~* +get
参 数说明
user用户
default表示默认用户名,或则自己定义的用户名
on表示是否启用该用户,默认为off(禁用)
#…表示用户密码,nopass表示不需要密码
~*表示可以访问的Key(正则匹配)
+@表示用户的权限,“+”表示授权权限,有权限 *** 作或访问,“-”表示还是没有权限; @为权限分类,可以通过 ACL CAT 查询支持的分类。+@all 表示所有权限,nocommands 表示不给与任何命令的 *** 作权限

Redis6核心特性-Client-Side-Caching 客户端缓存
  • client side caching客户端缓存

    • 类似浏览器缓存一样

      • 在服务器端更新了静态文件(如css、js、图片),能够在客户端得到及时的更新,但又不想让浏览器每次请求都从服务器端获取静态资源

      • 类似前端的-Expires、Last-Modified、Etag缓存控制

  • 文档:https://redis.io/topics/client-side-caching

分为两种模式

redis在服务端记录访问的连接和相关的key, 当key有变化时通知相应的应用
应用收到请求后自行处理有变化的key, 进而实现client cache与redis的一致
这需要客户端实现,目前lettuce对其进行了支持
  • 默认模式

    • Server 端全局唯一的表(Invalidation Table)记录每个Client访问的Key,当发生变更时,向client推送数据过期消息。

      • 优点:只对Client发送其访问过的被修改的数据

      • 缺点:Server端需要额外存储较大的数据量。

  • 广播模式

    • 客户端订阅key前缀的广播,服务端记录key前缀与client的对应关系。当相匹配的key发生变化时通知client。

    • 优点:服务端记录信息比较少

    • 缺点:client会收到自己未访问过的key的失效通知

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/793465.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存