高并发高可用之Redis

为什么Redis是单线程还这么快?

一台Redis每秒可以支持10W+的并发。

  • redis是内存型数据库
  • redis特殊的数据结构
  • 单线程避免锁的竞争
  • 使用多路复用NIO

二进制安全

Redis保存的数据是二进制安全的,只关心二进制化的字符串,不关心具体格式。只会严格的按照二进制的数据存取。不会妄图已某种特殊格式(UTF8,GBK等)解析数据。

类型与数据结构

  1. string:一个key对应一个字符串。
  2. list:数组、链表、栈、队列、阻塞单播队列
  3. hash:一个key下对应多个键值对。(详情页点赞收藏数)
  4. set:去重的数组列表。(交集并集差集 随机抽奖等)
  5. sorted set:带排序的set。(实时排行榜)

发布/订阅

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。
这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

注意:需要先有订阅才能发布成功。

Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:

序号 命令及描述
1 PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道。
2 PUBSUB subcommand [argument [argument …]]
查看订阅与发布系统状态。
3 PUBLISH channel message
将信息发送到指定的频道。
4 PUNSUBSCRIBE [pattern [pattern …]]
退订所有给定模式的频道。
5 SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息。
6 UNSUBSCRIBE [channel [channel …]]
指退订给定的频道。

布隆过滤器RedisBloom(扩展库)

Redis支持引入第三方扩展库模块

为了避免恶意多次查询Redis没有且数据库里也没有的数据造成穿透

首先会把数据库有的元素按照Bloom算法映射关系映射到bitmap二进制位上并导入的Redis中,由于是二进制的所以能降低大量数据对内存空间的损耗。这时如果来了一条在Redis没查到的数据会进行同样的映射并对比,如果指向的都为数值1位表示数据库中可能存在,则会进行放行到数据库。

并非百分百的元素都能进行成功过滤,如图所示元素3则会放行过滤失败(如果想进一步优化,可以把数据库中查到的null也在Redis中进行缓存)
在这里插入图片描述

回收策略

Redis配置文件中可以配置maxmemory,如果达到最大内存数则自动触发过期回收机制。
回收算法略过,可查阅redis.cn

注意:在操作Redis时,如果进行修改操作需要重新设置过期时间,原过期时间作废。

持久化

RDB(Redis DataBase):全量的形式,fork实现了写时复制(copy-on-write)需要持久化时创建子进程

AOF(Append Only File):增量命令日志的形式,每条命令都会被记录

4.0之后:AOF支持混合使用RDB为增量基础,使用非阻塞重写命令BGREWRITEAOF转化成RDB的文件(文件后缀名.aof不变),此命令相当于剔除像开始增加一个key后来又被删除了这个key之类的无效命令集。且在Redis配置文件中可以配置到达多大内存时自动触发该命令,默认64MB。

在这里插入图片描述

Redis高可用集群

因为选主要过半的概念3个集群节点和4个集群节点最多都只能挂一台且4台发生挂一台的风险更高,集群一般设置奇数节点性价比最高。

Redis的集群部署还可以带来额外的收益:

  • 负载(性能),Redis本身的QPS已经很高了,但是如果在一些并发量非常高的情况下,性能还是会受到影响。这个时候我们希望有更多的Redis服务来完成工作
  • 扩容(水平扩展),第二个是出于存储的考虑。因为Redis所有的数据都放在内存中,如果数据量大,很容易受到硬件的限制。升级硬件收效和成本比太低,所以我们需要有一种横向扩展的方法

在Redis中,目前可行的高可用方案包含以下1和2两种:
1. 哨兵的主从复制(主从读写分离,哨兵选主)
2. Cluster分片集群(这里分片在Redis端自身实现,客户端实现分片和中间件代理层端实现分片不常用)
3. 原始的主从复制(已淘汰,需要运维手动切换主节点)

1,哨兵的主从复制

哨兵本质上也是一台Redis 而且它可以自己自动修改自己的监控配置文件。

哨兵的主要工作:

  • 监控master和slave是否正常运行
  • master出现故障时自动将slave数据库升级为master

同时为了保证哨兵的高可用,我们会对Sentinel做集群部署,因此Sentinel不仅仅监控Redis所有的主从节点,Sentinel也会实现相互监控。哨兵通过发布订阅功能可以看到其他哨兵。
如果有多个Sentinel且master挂了,多个Sentinel通过Raft算法选出一个Sentinel Leader,再由他来从slave节点中选出新的master节点。

哨兵并没有清除已停止的服务的实例,这是因为已经停止的服务器有可能会在某个时间进行恢复,恢复以后会以slave角色加入到整个集群中。

master节点提供读和写操作,slave只提供读操作。
在这里插入图片描述

2,Cluster分片集群

Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽。对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分。
在这里插入图片描述
根据官方推荐这种部署方式至少要 3 台以上的 master 节点,最好使用 3 主 3 从模式,至少要6个节点才能保证完整的高可用(slave在集群中充当“冷备”,不能缓解读压力,在从节点slave上操作会自动重定向到主节点master上)。
其中三个master会分配不同的slot(表示数据分片区间),当master出现故障时,slave会自动选举成为master顶替主节点继续提供服务。
在这里插入图片描述
如果有三个及以上个master,此时不需要哨兵也能选出新的master,当slave收到超过半数master的ack后变成新master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点永远不能过半数是不能选举成功的)

客户端重定向
如图5-6所示,假设k这个key应该存储在node3上,而此时用户在node1或者node2上调用set k v指令,这个时候redis cluster怎么处理呢?

127.0.0.1:7291> set qs 1(error) MOVED 13724 127.0.0.1:7293

服务端返回MOVED,也就是根据key计算出来的slot不归当前节点管理,服务端返回MOVED告诉客户端去7293端口操作。

这个时候更换端口,用redis-cli –p 7293操作,才会返回OK。或者用./redis-cli -c -p port的命令。但是导致的问题是,客户端需要连接两次才能完成操作。所以大部分的redis客户端都会在本地维护一份slot和node的对应关系,在执行指令之前先计算当前key应该存储的目标节点,然后再连接到目标节点进行数据操作。

在redis集群中提供了下面的命令来计算当前key应该属于哪个slot

redis> cluster keyslot key1

参考:https://baijiahao.baidu.com/s?id=1714462075212273104&wfr=spider&for=pc
参考:https://www.cnblogs.com/ludongguoa/p/15314719.html


Redis高可用集群搭建

1,安装redis

下载:

wget http://download.redis.io/releases/redis-5.0.7.tar.gz

解压:

tar -zvxf redis-5.0.7.tar.gz

移动目录:

mv /root/redis-5.0.7 /usr/local/redis-5.0.7

编译:cd到redis目录(/usr/local/redis-5.0.7)下,输入命令make执行编译命令:

make

若make报错请参考:https://blog.csdn.net/weixin_46123028/article/details/124564683

安装:

make PREFIX=/usr/local/redis-5.0.7 install

启动:
进入目录/usr/local/redis-5.0.7下

./bin/redis-server& ./redis.conf

上面的启动方式是采取后台进程方式,下面是采取显示启动方式(如在配置文件设置了daemonize属性为yes则跟后台进程方式启动其实一样)。

./bin/redis-server ./redis.conf

两种方式区别无非是有无带符号&的区别。 redis-server 后面是配置文件,目的是根据该配置文件的配置启动redis服务。redis.conf配置文件允许自定义多个配置文件,通过启动时指定读取哪个即可。
安装参考:https://www.cnblogs.com/hunanzp/p/12304622.html

2,哨兵主从复制模式搭建:

配置哨兵配置文件sentinel.conf
在这里插入图片描述

配置redis从节点的配置文件redis.conf,指定redis主节点的IP和端口号
从节点配置文件添加该行配置:

slaveof 192.168.239.128 6379

启动
启动顺序:主节点—>从节点—>哨兵
redis主从服务端启动命令:./bin/redis-server ./redis.conf
哨兵服务端启动命令:./bin/redis-sentinel ./sentinel.conf

redis客户端启动命令 ./bin/redis-cli -p 6379
哨兵客户端启动命令 ./bin/redis-cli -p 26379
哨兵客户端使用info命令可以查看集群状态详情

注意:如果节点之间连接不通,需要把主从redis节点的配置文件改为:

bind 0.0.0.0

哨兵主从配置参考视频:https://www.bilibili.com/video/BV1VJ411B7Kr?p=2

SpringBoot代码实现:

  1. 引入maven依赖

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.73</version>
    </dependency>
    
  2. 加入springboot配置文件,配置哨兵集群的IP和端口号

    spring:
      redis:
        sentinel:
          master: mymaster
          nodes:
            - ip:端口号
            - ip:端口号
            - ip:端口号
    
  3. 编写redis配置类
    在一个配置类里注入一个bean,实现redis读写分离,配置从redis读数据时优先从从节点读取

    package com.wl.demo.config;
     
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.alibaba.fastjson.support.config.FastJsonConfig;
    import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
    import io.lettuce.core.ReadFrom;
    import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
     
    
    @Configuration
    public class RedisConfig {
     
        @Bean
        public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer() {
            return builder -> builder.readFrom(ReadFrom.REPLICA_PREFERRED);
        }
     
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(connectionFactory);
     
            FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
            FastJsonConfig fastJsonConfig = fastJsonRedisSerializer.getFastJsonConfig();
            SerializerFeature[] serializerFeatures = new SerializerFeature[] {SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteMapNullValue};
            fastJsonConfig.setSerializerFeatures(serializerFeatures);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
     
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.setHashKeySerializer(stringRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setEnableTransactionSupport(true);
     
            redisTemplate.afterPropertiesSet();
     
            return redisTemplate;
        }
    }
    
  4. 使用

    @SpringBootTest
    public class RedisTest1 {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test1(){
            stringRedisTemplate.opsForValue().set("test1","123");
            System.out.println(stringRedisTemplate.opsForValue().get("test1"));
        }
    }
    

3,Cluster分片集群模式搭建

① Redis端自身实现分片
修改所有redis节点的redis.conf 的配置文件并启动redis服务端:

# 下面两行配置默认为注释状态,开启即可
# 开启redis的集群模式
cluster-enabled yes
# 配置集群模式下的配置文件名称和位置,redis-cluster.conf这个文件是集群启动后自动生成的,不需要手动配置。
cluster-config-file redis-cluster.conf

在redis任意一个节点上执行槽分配命令:
(这个每个版本不太一样,redis5使用redis-cli --cluster ,redis3使用redis-trib.rb。
cluster-replicas 1表示一个主节点对应1个从节点。)

./bin/redis-cli --cluster create --cluster-replicas 1 192.168.239.128:6379 192.168.239.129:6379 192.168.239.130:6379 192.168.239.131:6379 192.168.239.132:6379 192.168.239.133:6379

连接集群客户端命令:
(IP为任意集群节点ip,-c表示集群模式)

./bin/redis-cli -c -h 192.168.239.130 -p 6379

可以看出来是分布式存储的:
在这里插入图片描述

扩展节点/重新分配槽位:
查看命令用法:
在这里插入图片描述

移动槽位配置:
(槽位对应的数据也会被移动)
(IP为任意集群节点ip)

./bin/redis-cli --cluster reshard 192.168.239.130:6379

在这里插入图片描述

查询集群节点槽位状态:
(IP为任意集群节点ip)

./bin/redis-cli --cluster check 192.168.239.130:6379

SpringBoot代码实现:

  1. 引入maven依赖(同上哨兵模式)

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.73</version>
    </dependency>
    
  2. 加入springboot配置文件

    spring:
      # Redis配置
      redis:
        # 连接超时时间(毫秒)
        timeout: 6000
        # 集群配置
        cluster:
          nodes:
            - 192.168.239.128:6379
            - 192.168.239.129:6379
            - 192.168.239.130:6379
            - 192.168.239.131:6379
            - 192.168.239.132:6379
            - 192.168.239.133:6379
        lettuce:
          pool:
            # 连接池中的最大空闲连接数
            max-idle: 8
            # 连接池中的最小空闲连接数
            min-idle: 0
            # 连接池最大连接数(使用负值表示没有限制)
            max-active: 8
            # 连接池最大阻塞等待时间(使用负值表示没有限制)
            max-wait: -1
    
  3. 编写redis配置类(同上哨兵模式)

    package com.wl.demo.config;
     
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.alibaba.fastjson.support.config.FastJsonConfig;
    import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
    import io.lettuce.core.ReadFrom;
    import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
     
    
    @Configuration
    public class RedisConfig {
     
       
     
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(connectionFactory);
     
            FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
            FastJsonConfig fastJsonConfig = fastJsonRedisSerializer.getFastJsonConfig();
            SerializerFeature[] serializerFeatures = new SerializerFeature[] {SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteMapNullValue};
            fastJsonConfig.setSerializerFeatures(serializerFeatures);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
     
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.setHashKeySerializer(stringRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setEnableTransactionSupport(true);
     
            redisTemplate.afterPropertiesSet();
     
            return redisTemplate;
        }
    }
    
  4. 使用(同上哨兵模式)

    @SpringBootTest
    public class RedisTest1 {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test1(){
            stringRedisTemplate.opsForValue().set("test1","123");
            System.out.println(stringRedisTemplate.opsForValue().get("test1"));
        }
    }
    

② 客户端实现分片
使用Jedis类可以在java代码层面实现哈希分片。
详情参考视频:https://www.bilibili.com/video/BV1Pi4y157L1?p=6

高并发下的问题

击穿:缓存击穿就是在处于集中式高并发访问的情况下,当某个热点 key 在失效的瞬间,大量的请求在缓存中获取不到。瞬间击穿了缓存,所有请求直接打到数据库,就像是在一道屏障上击穿了一个洞。

穿透:穿透主要原因是很多请求都在访问数据库一定不存在的数据,造成请求将缓存和数据库都穿透的情况。

雪崩:雪崩和击穿类似,不同的是击穿是一个热点 Key 某时刻失效,而雪崩是大量的热点 Key 在一瞬间失效。当大量缓存的过期时间相同时,缓存到达过期时间集体失效或者未加载到内存中,大量请求绕过缓存层直接访问数据库 load 数据,导致数据库频繁 IO,性能下降乃至宕机崩溃。

参考:https://zhuanlan.zhihu.com/p/348552497

Redis注意事项

Value最大存10k

生产环境不允许使用keys *命令,会阻塞线程造成服务不可用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值