一台Redis每秒可以支持10W+的并发。
Redis保存的数据是二进制安全的,只关心二进制化的字符串,不关心具体格式。只会严格的按照二进制的数据存取。不会妄图已某种特殊格式(UTF8,GBK等)解析数据。
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 …]]
指退订给定的频道。
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。
因为选主要过半的概念3个集群节点和4个集群节点最多都只能挂一台且4台发生挂一台的风险更高,集群一般设置奇数节点性价比最高。
Redis的集群部署还可以带来额外的收益:
在Redis中,目前可行的高可用方案包含以下1和2两种:
1. 哨兵的主从复制(主从读写分离,哨兵选主)
2. Cluster分片集群(这里分片在Redis端自身实现,客户端实现分片和中间件代理层端实现分片不常用)
3. 原始的主从复制(已淘汰,需要运维手动切换主节点)
哨兵本质上也是一台Redis 而且它可以自己自动修改自己的监控配置文件。
哨兵的主要工作:
同时为了保证哨兵的高可用,我们会对Sentinel做集群部署,因此Sentinel不仅仅监控Redis所有的主从节点,Sentinel也会实现相互监控。哨兵通过发布订阅功能可以看到其他哨兵。
如果有多个Sentinel且master挂了,多个Sentinel通过Raft算法选出一个Sentinel Leader,再由他来从slave节点中选出新的master节点。
哨兵并没有清除已停止的服务的实例,这是因为已经停止的服务器有可能会在某个时间进行恢复,恢复以后会以slave角色加入到整个集群中。
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
下载:
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
配置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代码实现:
引入maven依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
加入springboot配置文件,配置哨兵集群的IP和端口号
spring:
redis:
sentinel:
master: mymaster
nodes:
- ip:端口号
- ip:端口号
- ip:端口号
编写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;
}
}
使用
@SpringBootTest
public class RedisTest1 {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void test1(){
stringRedisTemplate.opsForValue().set("test1","123");
System.out.println(stringRedisTemplate.opsForValue().get("test1"));
}
}
① 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代码实现:
引入maven依赖(同上哨兵模式)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
加入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
编写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;
}
}
使用(同上哨兵模式)
@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
Value最大存10k
生产环境不允许使用keys *命令,会阻塞线程造成服务不可用