不支持事务,不支持ACID,不适用处理复杂关系的查询,但是redis每个都是原子操作

常见的nosql

1 Memcached

不支持持久化,仅支持key-value

2 Redis

支持持久化,更多数据结构

3 MongoDB

文档型数据库

–row 不转码,直接展示原参数

多实例关闭指定端口的redis服务

redis-cli -p 6379 shutdown

Redis是单线程+多路复用,redis7以后在异步io那加了多线程概念

Redis五大数据类型

  • String

  • List

  • Hash (key-value键值对)

  • Set (无序集合)

  • Zset

help命令 help @数据类型

1 key操作的相关命令

语法 功能
keys * 查看当前库所有key (匹配:keys *1)
exists key 判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
unlink key 非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
expire key 10 10秒钟:为给定的key设置过期时间
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
select 命令切换数据库
dbsize 查看当前数据库的key的数量
flushdb 清空当前库
flushall 清空全部库

2 String

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。

语法 解释
set 添加键值对
NX:当数据库中key不存在时,可以将key-value添加数据库
XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥
EX:key的超时秒数
PX:key的超时毫秒数,与EX互斥
get 查询对应键值
append 将给定的 追加到原值的末尾
strlen 获得值的长度
setnx 只有在 key 不存在时 设置 key 的值
incr 将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
decr 将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
incrby / decrby <步长> 将 key 中储存的数字值增减。自定义步长
mset ….. 同时设置一个或多个 key-value对
mget ….. 同时获取一个或多个 value
msetnx ….. 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。有一个失败则都失败(原子性)
getrange <起始位置><结束位置> 获得值的范围,类似java中的substring,前包,后包
setrange <起始位置> 覆写所储存的字符串值,从<起始位置>开始(索引从0开始)。
setex <过期时间> 设置键值的同时,设置过期时间,单位秒。
getset 以新换旧,设置了新值同时获得旧值。

mset,mget在集群下不好用,一般不用

set k1 v1 get

取出老的值,设置新的值v1覆盖他

1
2
3
4
5
6
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> set name lisi get
"zhangsan"
127.0.0.1:6379> get name
"lisi"

保留原来的过期时间

keepttl

数据结构

内部结构实现上类似于Java的ArrayList

3 list

语法 功能
lpush/rpush …. 从左边/右边插入一个或多个值。
lpop/rpop 从左边/右边吐出一个值。值在键在,值光键亡。
rpoplpush 列表右边吐出一个值,插到列表左边
lrange 按照索引下标获得元素(从左到右)
0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex 按照索引下标获得元素(从左到右)
llen 获得列表长度
linsert before 的前面插入插入值
linsert after 的后面插入插入值
lrem 从左边删除n个value(从左到右)
lset 将列表key下标为index的值替换成value

ltrim key 开始index 结束index 截取指定范围的值后再赋值给key

rpoplpush list1 list2左边弹出一个,放到右边:图:

屏幕截图_20240328_234058

lest key 下标 元素

linsert key before/after

数据结构

在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。

4 set

java里面的hashset

语法 功能
sadd ….. 将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers 取出该集合的所有值。
sismember 判断集合是否为含有该值,有1,没有0
scard 返回该集合的元素个数。
srem …. 删除集合中的某个元素。
spop 随机从该集合中吐出一个值
spop 随机从该集合中吐出N个值。
srandmember 随机从该集合中取出n个值。不会从集合中删除 。
smove 把集合中一个值从一个集合移动到另一个集合
sinter 返回两个集合的交集元素。
sunion 返回两个集合的并集元素。
sdiff 返回两个集合的差集元素(key1中的,不包含key2中的)

添加

sadd

遍历

smembers key

是否存在,有返回1,没有返回0

sismember set1 1

删除

srem

统计

scard

随机取数

127.0.0.1:6379> SRANDMEMBER set1 3

迁移

smove

集合运算

1 sdiff set1 set2 属于set1 不属于set2的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> SMEMBERS set1
1) "1"
2) "2"
3) "a"
4) "b"
5) "c"
127.0.0.1:6379> SMEMBERS set2
1) "1"
2) "2"
3) "3"
4) "a"
5) "x"
127.0.0.1:6379> sdiff set1 set2
1) "b"
2) "c"
127.0.0.1:6379>

2 合并:SUNION set1 set2

1
2
3
4
5
6
7
8
127.0.0.1:6379> SUNION set1 set2 
1) "1"
2) "2"
3) "a"
4) "b"
5) "c"
6) "3"
7) "x"

3 交集

sinter set1 set2

4 sintercard redis7新出的,返回交集的个数

1
2
127.0.0.1:6379> sintercard 2 set1 set2 
(integer) 3
数据结构

Set数据结构是dict字典,字典是用哈希表实现的。Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它内部也使用hash结构,所有value都指向同一个内部值。

5 zset

语法 功能
zadd 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange [WITHSCORES] 升序返回有序集 key 中,下标在之间的元素,0代表第一个元素索引,-1代表最后一个元素索引.带WITHSCORES,可以让分数一起和值返回到结果集。
zrevrange [WITHSCORES] 降序返回有序集 key 中,下标在之间的元素,0代表第一个元素索引,-1代表最后一个元素索引.带WITHSCORES,可以让分数一起和值返回到结果集
zrangebyscore [withscores] [limit offset count] 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore [withscores] [limit offset count] 同上,改为从大到小排列。
zincrby 为元素的score加上增量
zrem 删除该集合下,指定值的元素
zcount 统计该集合,分数区间内的元素个数
zrank 返回该值在集合中的排名,从0开始。

/TODO,我理解zmpop 后面的可选参数count 是执行几次,

逆序

127.0.0.1:6379> ZrevRANGE myzset 0 -1

  1. “three”
  2. “two”
  3. “one”

127.0.0.1:6379> ZRANGE myzset 0 -1

  1. “one”
  2. “two”
  3. “three”
数据结构

SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

6 hash

  • 方式1 单key+序列化.问题:每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大。

    image-20230706110321231

  • 方式2 多key-value.问题:用户ID数据冗余

    image-20230706110341117

  • 方式3 单key + 多(field+value)

image-20230706110256007

  • **通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题
语法 功能
hset 集合中的 键赋值
hget 集合取出 value
hmset 批量设置hash的值
hexists 查看哈希表 key 中,给定域 field 是否存在。
hkeys 列出该hash集合的所有field
hvals 列出该hash集合的所有value
hincrby 为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

kv模式不变,但V是一个键值对

1
2
3
4
5
6
7
8
127.0.0.1:6379> hset user id 1 name zhangsan age 25
(integer) 3
127.0.0.1:6379> hget user id
"1"
127.0.0.1:6379> hget user name
"zhangsan"
127.0.0.1:6379> hget user age
"25"

要hget key key进行取值

数据结构

Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

jedis

不好用,类似jdbc,了解即可

springboot 整合redis

1
2
3
4
5
6
7
8
9
10
11
12
spring.data.redis.host=192.168.6.131
spring.data.redis.port=6379
#spring.data.redis.client-type=lettuce

#设置lettuce的底层参数
#spring.data.redis.lettuce.pool.enabled=true
#spring.data.redis.lettuce.pool.max-active=8

#切换jedis
spring.data.redis.client-type=jedis
spring.data.redis.jedis.pool.enabled=true
spring.data.redis.jedis.pool.max-active=8

序列化定制

redistemplate存储值,经过了序列化器,默认是jdk序列化器,存的值直接用redis取,取出来的带地址值。不好用。所以要换序列化器

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
@Configuration
public class AppRedisConfiguration {

/**
* 允许Object类型的key-value,都可以被转为json进行存储。
* @param redisConnectionFactory 自动配置好了连接工厂
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();

//指定链接的服务器信息
template.setConnectionFactory(redisConnectionFactory);

//String 的序列化 对象
StringRedisSerializer stringRedisSerializer = StringRedisSerializer.UTF_8;

// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash 的key采用String类型的序列化方式
template.setHashKeySerializer(stringRedisSerializer);

// value 序列化方式使用Jackson
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// hash 的value序列化采用Jackson
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

//RedisTemplate 的 afterPropertiesSet() 方法主要检查 ConnectionFactory 是否设置,可以安全省略
template.afterPropertiesSet();
return template;
}
}

配置文件

maxmemory-policy 置换策略

1 noeviction:不进行移除。

2 allkeys-lru:按照间隔时间淘汰

3 allkeys-lfu:按照访问次数淘汰

4 allkeys-random:随机淘汰

5 volatile-lru:只对设置了过期时间的键(最近最少使用)淘汰;

6 volatile-lfu:和上个类似,按访问频率淘汰

7 allkeys-random:在所有集合key中,移除随机的key

事务和锁

事务

redis中的事务,保证事务内的命令会连续不被打断的执行,并不保证事务的原子性?可能会失败,不会影响其他的任务执行。

ps:

原子性的核心是 “要么全部成功,要么全部失败,失败后全部回滚”

redis事务是延迟执行

情况2,组队中某个命令出现了错误,执行时整个的所有队列都会被取消,回滚

情况3, 执行阶段某个命令报出了错误,则只有报错的命令不会被执行,其他的命令都会执行,不会回滚。

悲观锁
乐观锁

用版本号,或者一些算法控制。

持久化

rdb是将数据集整体存下来存在磁盘,

aof是存操作命令,后面全部执行一次

RDB

rdb还可以手动保存,触发情况

  • save :使用主进行进行持久化指令,只管保存,其它不管,全部阻塞。不建议。

  • bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。

  • flushall命令

    • 但里面是空的,无意义
  • shutdown命令

RDB禁用操作

AOF

文件位置

3个文件,位置也由位置+文件名,分开组成

image-20251113034350958

开启

修改默认的appendonly no,改为yes,开启AOP方式

同步频率
  • appendfsync always

    • 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
  • appendfsync everysec

    • 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
  • appendfsync no

    • redis不主动进行同步,把同步时机交给操作系统。
重写

image-20251113040543632

何时触发重写

默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

  • auto-aof-rewrite-percentage 设置重写基准值
    • 文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
  • auto-aof-rewrite-min-size 设置重写基准值
    • 最小文件64MB。达到这个值开始重写。
重写的流程是

没看

AOF的优势

备份机制更稳健,丢失数据概率更低。

劣势

  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成无法恢复。

持久化方案选择

RDB和AOP用哪个好?

  • 官方推荐两个都启用。
  • 如果对数据不敏感,可以选单独用RDB。
  • 不建议单独用 AOF,因为可能会出现Bug。
  • 如果只是做纯内存缓存,可以都不用。
  • AOF和RDB如果同时开启,系统默认取AOF中的持久化数据

  • 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
  • 同时开启两种持久化方式
  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
  • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?
  • 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。

性能建议

1
2
3
4
5
6
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。

主从复制

实现思路

  • 1 一个redis服务作为主机,主要负责写操作

  • 2 两个redis服务作为从机,主要负责读操作

  • 3 从机自动从主机同步数据下来

  • 4 从机主动找主机,而主机不会找从机

  • 5 正常来说主机和从机应该在不同的IP上开启redis服务,我们为了快速模拟,可以在一台机器上模拟出三个redis服务即可

配置主从机器

  • 配从不配主,是让从机主动去找主机

  • 在6380 和6381的机器上执行如下命令

    1
    slaveof 127.0.0.1 6379

一主二仆?多仆呢?

哨兵模式

反客为主的自动版

使用

新建哨兵的配置文件sentinel.conf ,放入如下内容

1
sentinel monitor mymaster 127.0.0.1 6379 1

其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。 &#

启动哨兵

redis-sentinel /root/myredis/sentinel.conf

哨兵选择有个优先级问题

集群

解决并发写,扩容问题

主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

解决方法:

无中心化集群配置。

集群搭建

太长

(1)第一步,搭建前的准备
  • 之前操作产生的rdb和aof文件删除
  • appendonly 修改回 no
  • 清空主从复制和哨兵模式留下的一些文件
  • 开启daemonize yes
  • protected-mode no
  • 注释掉bind
(2)第二步,制作六个实例的配置文件
  • 配置文件的内容解释

    1
    2
    3
    4
    5
    6
    7
    include /root/myredis/redis.conf #引用公共的配置文件
    port 6379 # 设置端口号
    pidfile "/var/run/redis_6379.pid" # 设置pid进程文件
    dbfilename "dump6379.rdb" # 设置rdb持久化问价名
    cluster-enabled yes # 开启集群
    cluster-config-file nodes-6379.conf # 设置集群使用的结点文件名
    cluster-node-timeout 15000 # 设置结点失联时间
  • 创建6379 6380 6381 6389 6390 6391 六个结点的配置文件

    1
    创建一个配置文件后,进行复制即可,然后再vim下,通过 :%s/6379/目标端口 来批量替换每个配置文件中的端口号

    image-20230706200218897

(3)第三步,启动六个服务
(4)第四步 ,将六个服务合并为一个集群

//todo

-c 代表以集群方式登录

一个集群至少要有三个主节点。选项 –cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。

集群的slots

  • 一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
  • 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
  • 集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
    • 节点 A 负责处理 0 号至 5460 号插槽。
    • 节点 B 负责处理 5461 号至 10922 号插槽。
    • 节点 C 负责处理 10923 号至 16383 号插槽。

不在一个slot下的键值,是不能使用mget,mset等多键操作。

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。

集群中找值

  • cluster keyslot key 计算key应该保存在那个插槽
  • cluster countkeysinslot slot的值 计算某个插槽中保存的key的数量
  • CLUSTER GETKEYSINSLOT <slot><count> 返回 count 个 slot 槽中的键。

集群的不足

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持
  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。