Redis-入门

1. 简介

Redis属于NoSQL,是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库。

Redis读写性能高,读的速度是110000次/s,写的速度是81000次/s。

Redis属于内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串、散列、 列表、 集合、 有序集合等。

官网地址:https://redis.io/

中文地址:http://www.redis.cn/

2. 部署

查看当前redis稳定版本https://redis.io/download/

下载源码如果官方网站打不开,使用国内网站:http://download.redis.io/releases/

2.1 docker部署单节点

1
2
3
docker run -itd --name redis -p 6379:6379 \
-v /home/dockerdata/redis/data:/data redis:6.2.6 \
--requirepass aacopy.cn

2.2 源码部署单节点

1
2
3
4
5
6
7
8
9
10
11
#安装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
  • 编译redis
1
2
3
4
5
6
7
8
9
10
11
#进入redis目录
cd /aacopy
tar -zxvf redis-6.2.6.tar.gz
cd redis-6.2.6

#编译redis
make

#安装到指定目录
mkdir -p /usr/local/redis
make PREFIX=/usr/local/redis install
  • 配置redis
1
2
3
4
5
6
7
8
#创建目录
mkdir -p /usr/local/redis/conf
mkdir -p /usr/local/redis/log
mkdir -p /usr/local/redis/data

#添加配置文件
cd /usr/local/redis/conf
touch redis.conf
  • 添加redis.conf配置文件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#任何ip可以访问,0.0.0.0是不限制,配置多个ip例子 12.13.432.12 31.12.43.13 用空格隔开
bind 0.0.0.0

#守护进程
daemonize yes

#密码
requirepass aacopy.cn

#日志文件
logfile "/usr/local/redis/log/redis.log"

#持久化文件存储路径
dir /usr/local/redis/data

#持久化策略, 10秒内有个1个key改动,执行快照
save 10 1
  • 启动redis
1
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf

如果客户端无法连接到redis,记得把防火墙关掉,重启系统

使用redis可视化工具查看链接:

mac版本:https://pan.baidu.com/s/1PlD6U-D-hgWnNAWMnZS0NA 密码: 3iqa

3. 核心配置

配置文件可以在/aacopy/redis-6.2.6/redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#任何ip可以访问,0.0.0.0是不限制,配置多个ip例子 12.13.432.12 31.12.43.13 用空格隔开
bind 0.0.0.0

#守护进程
daemonize yes

#密码
requirepass aacopy.cn

#日志文件
logfile "/usr/local/redis/log/redis.log"

#持久化文件存储路径
dir /usr/local/redis/data

#持久化策略, 10秒内有个1个key改动,执行快照
save 10 1

4. 命令

4.1 通用命令

  • exists [key]
    • 判断key是否存在
  • del [key]
    • 删除key
  • type [key]
    • 判断key类型
  • ttl [key]
    • 查看key的存活时间

4.2 String

存储字符串类型

值的长度不能超过512 MB

key命名规范,不要过长,冒号分割,业务名:表名:ID

使用场景:验证码,计数器、发号器,订单重复提交令牌,热点商品卡片(序列化json对象存储),分布式锁

  • set/get
    • 设置和获取 key-value
  • mget/mset
    • 批量设置或获取多个key的值
    • MGET key [key …]
    • MSET key value [key value …]
  • incr [key]
    • incr对key对应的值进行加1操作,并返回新的值;
  • incrby [key] [increment]
    • 将key对应的数字加increment。如果key不存在,操作之前,key就会被置为0
  • setex [key] [seconds] [value]
    • 设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期,原子操作
  • setnx [key] [value]
    • 将key设置值为value,如果key不存在等同SET命令。 当key存在时什么也不做, 是set if not exists的简写。
  • getset [key] [value]
    • 设置key的值,并返回key旧的值

4.3 List

字符串列表,按照插入顺序排序

双向链表,插入删除时间复杂度O(1)快,查找为O(n)慢

通常添加一个元素到列表的头部(左边)或者尾部(右边)

存储的都是string字符串类型

支持分页操作,高并发项目中,第一页数据都是来源list,第二页和更多信息则是通过数据库加载

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表不超过40亿个元素)

应用场景:简单队列,最新评论列表,非实时排行榜:定时计算榜单,如手机日销榜单

  • lpush [key] [value1 value2 …]
    • 将一个或多个值插入到列表头部
  • rpop [key]
    • 移除并获取列表最后一个元素
  • llen [key]
    • 获取列表长度
  • lindex [key] [index]
    • 通过索引获取列表中的元素
  • lrange [key] [start] [stop]
    • 获取key对应的list的指定下标范围的元素, 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素, -1表示获取所有元素( lrange key 0 -1);
  • rpush [key] [value1 value2 …]
    • 在key对应的list的尾部添加一个元素
  • lpop [key]
    • 从key对应的list的尾部删除一个元素,并返回该元素
  • brpop [key] [timeout]
    • 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
  • lrem [key] [count] [value]
    • 移除元素,可以指定移除个数

4.3 Hash

是一个string类型的field和value的映射表,hash特别适合用于存储对象

每个 hash 可以存储 232 - 1 键值对(40多亿)

应用场景:购物车,用户个人信息,商品详情

  • hset [key] [field] [value]
    • 设置 key 指定的哈希集中指定字段的值
  • hget [key] [field]
    • 返回 key 指定的哈希集中该字段所关联的值
  • hgetall [key]
    • 返回 key 指定的哈希集中所有的字段和值
  • hdel [key] [field1 field2 …]
    • 从 key 指定的哈希集中移除指定的字段
  • hexists [key] [field]
    • 返回hash里面field是否存在
  • hincrby [key] [field] [increment]
    • 增加 key 指定的哈希集中指定字段的数值, 如果是-1 则是递减
  • hmset [key] [field1 value1 field2 value2…]
    • 批量设置 key 指定的哈希集中指定字段的值
  • hmget [key] [field1 field2 …]
    • 返回 key 指定的哈希集中指定字段的值

4.4 Set

将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略

集合是通过哈希表实现的

应用场景:去重,社交应用关注、粉丝、共同好友,统计网站的PV、UV、IP,大数据里面的用户画像标签集合

  • sadd [key] [member1 member2 …]
    • 添加一个或多个指定的member元素到集合的 key中.指定的一个或者多个元素member 如果已经在集合key中存在则忽略
  • scard [key]
    • 返回集合存储的key的基数 (集合元素的数量).
  • sdiff [key1 key2 …]
    • 返回的集合元素是第一个key的集合与后面所有key的集合的差集
  • sinter [key1 key2 …]
    • 返回指定所有的集合的成员的交集
  • sunion [key1 key2 …]
    • 返回给定的多个集合的并集中的所有成员
  • sismember [key] [member]
    • 返回成员 member 是否是存储的集合 key的成员
  • srem [key] [member1 member2 …]
    • 在key集合中移除指定的元素. 如果指定的元素不是key集合中的元素则忽略
  • smembers [key]
    • 返回key集合所有的元素

4.5 Sorted Set

用于将一个或多个成员元素及其分数值加入到有序集当中

如果某个成员已经是有序集的成员,那么更新这个成员的分数值,分数值可以是整数值或双精度浮点数。

有序集合可以看做是在Set集合的的基础上为集合中的每个元素维护了一个顺序值: score,它允许集合中的元素可以按照score进行排序

底层使用到了Ziplist压缩列表和“跳跃表”两种存储结构

如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果

应用场景:实时排行榜:商品热销榜、体育类应用热门球队、积分榜,优先级任务、队列,朋友圈 文章点赞-取消,逻辑:用户只能点赞或取消,统计一篇文章被点赞了多少次,可以直接取里面有多少个成员

  • zadd [key] [score1 member1 score2 member2 …]
    • 向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • zcard [key]
    • 获取有序集合的成员数
  • zcount [key] [min] [max]
    • 计算在有序集合中指定区间分数的成员数
  • zincrby [key] [increment] [member]
    • 有序集合中对指定成员的分数加上增量 increment
  • zrange [key] [start] [stop] [withscores]
    • 通过索引区间返回有序集合指定区间内的成员, 成员的位置按分数值递增(从小到大)来排序
  • zrevrange [key] [start] [stop] [withscores]
    • 通过索引区间返回有序集合指定区间内的成员, 成员的位置按分数值递增(从大到小)来排序
  • zrank [key] [member]
    • 返回有序集key中成员member的排名。其中有序集成员按score值递增(从小到大)顺序排列
  • zrevrank [key] [member]
    • 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
  • zrem [key] [member1 member2 …]
    • 移除有序集合中的一个或多个成员
  • zscore [key] [member]
    • 返回有序集中,成员的分数值

5. redis Java客户端

5.1 jedis

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

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

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

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

5.2 lettuce

  • 高级Redis客户端,用于线程安全同步,异步响应
  • 基于Netty的的事件驱动,可以在多个线程间并发访问, 通过异步的方式可以更好的利用系统资源
  • 在springboot中默认使用Lettuce客户端,旧版本lettuce存在堆外内存溢出的bug, 5.3版本修复了这个bug

6. Springboot整合redis

6.1 整合测试

  • springboot版本:2.7.7

  • 引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 添加配置文件
1
2
3
4
5
spring:
redis:
host: 192.168.80.128
port: 6379
password: aacopy.cn
  • 编写测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
public class RedisTest {

@Resource
private RedisTemplate<String, Object> redisTemplate;

@Test
void test1() {
redisTemplate.opsForValue().set("name", "aacopy.cn");
System.out.println("缓存数据成功...");
Object name = redisTemplate.opsForValue().get("name");
System.out.println("获取缓存数据成功:name = " + name);
}
}
  • 测试结果
1
2
缓存数据成功...
获取缓存数据成功:name = aacopy.cn

6.2 RedisTemplate

6.2.1 操作方法

  • opsForValue(),返回ValueOperations<K, V>,简单K-V操作
  • opsForList(),返回ListOperations<K, V>,list类型的数据操作
  • opsForSet(),返回SetOperations<K, V>,set类型数据操作
  • opsForZSet(),返回ZSetOperations<K, V>,zset类型数据操作
  • opsForHash(),返回HashOperations<K, HK, HV>,map类型的数据操作

6.2.2 StringRedisTemplate

  • StringRedisTemplate继承RedisTemplate(StringRedisTemplate extends RedisTemplate<String, String>
  • StringRedisTemplate默认采用的是String的序列化策略
  • RedisTemplate默认采用的是JDK的序列化策略,会将数据先序列化成字节数组然后在存入Redis数据库

6.2.3 序列化方式

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

6.2.4 自定义序列化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, 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.afterPropertiesSet();
return redisTemplate;
}
}

6.2.5 Lettuce连接池

  • 添加连接池依赖,只需要添加依赖默认开启连接池
1
2
3
4
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
  • debug RedisTemplate Bean的创建过程,可以看到初始化的连接池配置

  • 自定义连接池配置
1
2
3
4
5
6
7
8
9
10
11
12
spring:
redis:
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制
max-active: 10
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms

6.2.6 Jedis连接池

  • 添加配置文件
1
2
3
4
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

jedis已经依赖了commons-pool2,不需要单独引入,Lettuce的包也不需要排除,直接在配置文件里指定使用jedis即可

  • 添加配置文件
1
2
3
4
5
6
7
8
9
spring:
redis:
client-type: jedis
jedis:
pool:
max-active: 10
max-idle: 10
min-idle: 0
max-wait: -1ms

6.3 SpringCache

支持使用注解操作缓存

6.3.1 添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

6.3.2 指定缓存类型

1
2
3
spring:
cache:
type: redis

6.3.3 在启动类上添加启动缓存注解

1
@EnableCaching

6.3.4 缓存配置

springCache 通过RedisCacheManager来配置缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@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));
}

6.3.5 自定义缓存key生产策略

1
2
3
4
5
6
@Bean
public KeyGenerator customKeyGenerator(){
return (o, method, objects) -> o.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ StringUtils.arrayToDelimitedString(objects, "_");
}

6.3.6 缓存注解

  • @Cacheable

    • 标记在一个方法上,也可以标记在一个类上,缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上缓存该类所有的方法返回值
    • value 缓存名称,可以有多个
    • key 缓存的key规则,可以用springEL表达式,默认是方法参数组合
    • condition 缓存条件,使用springEL编写,返回true才缓存
    1
    2
    3
    4
    5
    @Cacheable(value = "user", key = "#root.methodName+'_'+#userId")
    public UserInfoDTO getUserInfo(Integer userId) {
    System.out.println("查询用户信息...");
    return new UserInfoDTO(userId, "aacopy.cn", 18);
    }

    缓存的key为:user::getUserInfo_1,value为:{"@class":"cn.aacopy.learn.redis.dto.UserInfoDTO","userId":1,"name":"aacopy.cn","age":18}

    • 使用自定义key生成策略和缓存配置,key 属性和keyGenerator属性只能二选一
    1
    @Cacheable(value = "user", keyGenerator = "customKeyGenerator", cacheManager = "cacheManager1Day")
  • @CachePut

    • 根据方法的请求参数对其结果进行缓存,每次都会触发真实方法的调用
    • 用于更新缓存
    1
    @CachePut(value = "user", key = "#userInfoDTO.id")
  • @CacheEvict

    • 从缓存中移除相应数据, 触发缓存删除的操作
    • beforeInvocation参数可以设置在方法执行前或后执行删除缓存操作
    1
    @CacheEvict(value = "user", key = "#root.args[0]")
  • @Caching

    • 组合多个Cache注解使用
    • 允许在同一方法上使用多个嵌套的@Cacheable、@CachePut和@CacheEvict注释
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Caching(
    cacheable = {
    @Cacheable(value = "user",keyGenerator = "customKeyGenerator")
    },
    put = {
    @CachePut(value = "user",key = "#id"),
    @CachePut(value = "user",key = "'userInfo:'+#id")
    }
    )

6.4 缓存相关问题解决方案

6.4.1 缓存击穿

缓存击穿:即某个热点key缓存失效了

  • 造成的问题

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

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

    • 缓存的同步 sync
    • sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中
    1
    @Cacheable(value = "user",key = "#root.args[0]", cacheManager = "customCacheManager", sync=true)

6.4.2 缓存雪崩

缓存雪崩:即多个热点key同一时间过期

  • 造成的问题

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

    • 存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
    • 设置热点数据永远不过期,定时任务定时更新
  • SpringCache解决方案

    • 设置差别的过时时间
    • 比如CacheManager配置多个过期时间维度
    • 配置文件 time-to-live 配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring:
    cache:
    type: redis
    redis:
    time-to-live: 3600000
    #开启前缀,默以为true
    use-key-prefix: true
    #键的前缀,默认就是缓存名cacheNames
    key-prefix: aacopy

6.4.3 缓存穿透

缓存穿透:即查询不存在数据

  • 造成的问题

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

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

    • 空结果也缓存,默认不配置condition或者unless就行
    1
    2
    3
    4
    5
    spring:
    cache:
    redis:
    #是否缓存空结果,防止缓存穿透,默以为true
    cache-null-values: true

7. 持久化

7.1 RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,默认的文件名为dump.rdb

7.1.1 产生快照的情况

  • save:会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止
  • bgsave:fork创建子进程,RDB持久化过程由子进程负责,会在后台异步进行快照操作,快照同时还可以响应客户端请求
  • 自动化:配置文件来完成,配置触发 Redis的 RDB 持久化条件,比如 “save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave
  • 主从架构:从服务器同步数据的时候,会发送sync执行同步操作,master主服务器就会执行bgsave

7.1.2 优点

  • RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复
  • 在恢复大数据集时的速度比 AOF 的恢复速度要快
  • 生成的是一个紧凑压缩的二进制文件

7.1.3 缺点

  • 每次快照是一次全量备份,fork子进程进行后台操作,子进程存在开销
  • 在快照持久化期间修改的数据不会被保存,可能丢失数据

7.1.4 核心配置

  • dir: 持久化文件的路径
  • dbfilename: 文件名
1
2
3
4
5
6
7
8
9
10
11
12
13
#持久化文件名称
dbfilename dump.rdb

#持久化文件存储路径
dir /usr/local/redis/data

#持久化策略, 10秒内有个1个key改动,执行快照
save 10 1

#导出rdb数据库文件压缩字符串和对象,默认是yes,会浪费CPU但是节省空间
rdbcompression yes
# 导入时是否检查
rdbchecksum yes

7.2 AOF

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

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

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

7.2.1 配置

  • appendonly yes,默认不开启
  • AOF文件名 通过 appendfilename 配置设置,默认文件名是appendonly.aof
  • 存储路径同 RDB持久化方式一致,使用dir配置

7.2.2 核心原理

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

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

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

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

    • appendfsync always
      • 每次有数据修改发生时都会写入AOF文件,消耗性能多
    • appendfsync everysec
      • 每秒钟同步一次,该策略为AOF的缺省策略。
    • appendfsync no
      • 不主从同步,由操作系统自动调度刷磁盘,性能是最好的,但是最不安全
1
2
3
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

7.2.3 rewrite

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

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

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

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

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

  • 触发配置

    • 手动触发
      • 直接调用bgrewriteaof命令
    • 自动触发
      • auto-aof-rewrite-min-size
        • 表示运行AOF重写时文件最小体积,默认为64MB
      • auto-aof-rewrite-percentage
        • 代表当前AOF文件空间和上一次重写后AOF文件空间(aof_base_size)的比值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 是否开启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

7.3 AOF+RDB

  • 持久化选择问题

    • 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的情况,然后可以手动触发备份、重写数据
    • 采用集群和主从同步
  • AOF+RDB

    • 就是rdb和aof一起用
    • 直接将rdb持久化的方式来操作将二进制内容覆盖到aof文件中,rdb是二进制,所以很小
    • 有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite
    • 默认是开启状态
    • 优点
      • 混合持久化结合了RDB持久化 和 AOF 持久化的优点,采取了rdb的文件小易于灾难恢复
      • 同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失
    • 缺点
      • 前部分是RDB格式,是二进制,所以阅读性较差
    • 数据恢复
      • 先看是否存在aof文件,若存在则先按照aof文件恢复,aof比rdb全,且aof文件也rewrite成rdb二进制格式
      • 若aof不存在,则才会查找rdb是否存在

8. info命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 # 使用内存,以字节(byte)B为单位
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的数,平均存活时间

9. config命令

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

config get xxx、config set xxx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
daemonize  #后端运行
bind #ip绑定
timeout #客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接

databases #设置数据库的个数,可以使用 SELECT 命令来切换数据库。默认使用的数据库是 0
save #设置 Redis 进行rdb持久化数据库镜像的频率。
rdbcompression #在进行镜像备份时,是否进行压缩

slaveof #设置该数据库为其他数据库的从数据库
masterauth #当主数据库连接需要密码验证时,在这里配置

maxclients #限制同时连接的客户数量,当连接数超过这个值时,redis 将不再接收其他连接请求,返回error

maxmemory #设置 redis 能够使用的最大内存,
  • maxmemory
    • 防止所用内存超过服务器物理内存, maxmemory限制的是Redis实际使用的内存量, 也就是used_memory统计项对应的内存
    • 由于内存碎片率的存在, 实际消耗的内存可能会比maxmemory设置的更大, 实际使用时要小心这部分内存溢出
    • 默认无限使用服务器内存, 为防止极端情况下导致系统内存耗尽, 建议所有的Redis进程都要配置maxmemory
    • 在64bit系统下,maxmemory设置为0表示不限制Redis内存使用,在32bit系统下,maxmemory不能超过3GB
    • redis在占用的内存超过指定的maxmemory之后,通过maxmemory_policy确定redis是否释放内存以及如何释放内存

10. key淘汰策略

10.1 key删除策略

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

  • 定期删除

    • 隔一段时间,就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,
    • 定期删除可能会导致很多过期 key 到了时间并没有被删除掉,需要惰性删除
  • 惰性删除

    • 当一些客户端尝试访问它时,key会被发现并主动的过期
    • 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键

10.2 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需要用中横线-

1
config set maxmemory-policy volatile-lru

11. 集群

11.1 Cluster集群

  • Cluster模式是Redis3.0开始推出
  • 采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接
  • 官方要求:至少6个节点才可以保证高可用,即3主3从;扩展性强、更好做到高可用
  • 各个节点会互相通信,采用gossip协议交换节点元数据信息
  • 数据分散存储到各个节点上

11.1.1 数据分片和虚拟哈希槽

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

常见的数据分区算法

  • 哈希取模
  • 范围分片
  • 一致性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算法,是一种校验算法
  • 使用哈希槽的好处就在于可以方便的添加或移除节点。

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

11.1.2 集群搭建

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

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

  • 端口

    • 6381、6382
    • 6383、6384
    • 6385、6386
  • 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bind 0.0.0.0
port 6381
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis-6381.log"
dbfilename "dump-6381.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly-6381.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 192.168.80.128

#集群节点映射端口
cluster-announce-port 6381

#集群节点总线端口,节点之间互相通信,常规端口+1万
cluster-announce-bus-port 16381
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bind 0.0.0.0
port 6386
daemonize yes
requirepass "123456"
logfile "/usr/local/redis/log/redis-6386.log"
dbfilename "dump-6386.rdb"
dir "/usr/local/redis/data"
appendonly yes
appendfilename "appendonly-6386.aof"
masterauth "123456"

cluster-enabled yes
cluster-config-file nodes-6386.conf
cluster-node-timeout 20000
cluster-announce-ip 192.168.80.128
cluster-announce-port 6386
cluster-announce-bus-port 16386
  • 启动6个节点
1
2
3
4
5
6
./redis-server ../conf/cluster/redis-6381.conf
./redis-server ../conf/cluster/redis-6382.conf
./redis-server ../conf/cluster/redis-6383.conf
./redis-server ../conf/cluster/redis-6384.conf
./redis-server ../conf/cluster/redis-6385.conf
./redis-server ../conf/cluster/redis-6386.conf
  • 加入集群(其中一个节点执行即可)
    • –cluster 构建集群全部节点信息
    • –cluster-replicas 1 主从节点的比例,1表示1主1从的方式
1
./redis-cli -a 123456 --cluster create 192.168.80.128:6381 192.168.80.128:6382 192.168.80.128:6383 192.168.80.128:6384 192.168.80.128:6385 192.168.80.128:6386 --cluster-replicas 1
  • 检查状态信息(其中一个节点执行即可)
1
./redis-cli -a 123456 --cluster check 192.168.80.128:6381
  • 集群状态
1
2
3
4
5
6
7
./redis-cli -c -a 123456 -p 6379

#集群信息
cluster info

#节点信息
cluster nodes