不支持事务,不支持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 |
用 |
| setex |
设置键值的同时,设置过期时间,单位秒。 |
| getset |
以新换旧,设置了新值同时获得旧值。 |
mset,mget在集群下不好用,一般不用
set k1 v1 get
取出老的值,设置新的值v1覆盖他
1 | 127.0.0.1:6379> get name |
保留原来的过期时间
keepttl
数据结构
内部结构实现上类似于Java的ArrayList
3 list
| 语法 | 功能 |
|---|---|
| lpush/rpush |
从左边/右边插入一个或多个值。 |
| lpop/rpop |
从左边/右边吐出一个值。值在键在,值光键亡。 |
| rpoplpush |
从 |
| lrange |
按照索引下标获得元素(从左到右) |
| 0左边第一个,-1右边第一个,(0-1表示获取所有) | |
| lindex |
按照索引下标获得元素(从左到右) |
| llen |
获得列表长度 |
| linsert |
在 |
| linsert |
在 |
| lrem |
从左边删除n个value(从左到右) |
| lset |
将列表key下标为index的值替换成value |
ltrim key 开始index 结束index 截取指定范围的值后再赋值给key
rpoplpush list1 list2左边弹出一个,放到右边:图:

lest key 下标 元素
linsert key before/after
数据结构
在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。
4 set
java里面的hashset
| 语法 | 功能 |
|---|---|
| sadd |
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略 |
| smembers |
取出该集合的所有值。 |
| sismember |
判断集合 |
| 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 | 127.0.0.1:6379> SMEMBERS set1 |
2 合并:SUNION set1 set2
1 | 127.0.0.1:6379> SUNION set1 set2 |
3 交集
sinter set1 set2
4 sintercard redis7新出的,返回交集的个数
1 | 127.0.0.1:6379> sintercard 2 set1 set2 |
数据结构
Set数据结构是dict字典,字典是用哈希表实现的。Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它内部也使用hash结构,所有value都指向同一个内部值。
5 zset
| 语法 | 功能 |
|---|---|
| zadd |
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。 |
| zrange |
升序返回有序集 key 中,下标在 |
| zrevrange |
降序返回有序集 key 中,下标在 |
| zrangebyscore |
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 |
| zrevrangebyscore |
同上,改为从大到小排列。 |
| zincrby |
为元素的score加上增量 |
| zrem |
删除该集合下,指定值的元素 |
| zcount |
统计该集合,分数区间内的元素个数 |
| zrank |
返回该值在集合中的排名,从0开始。 |
/TODO,我理解zmpop 后面的可选参数count 是执行几次,
逆序
127.0.0.1:6379> ZrevRANGE myzset 0 -1
- “three”
- “two”
- “one”
127.0.0.1:6379> ZRANGE myzset 0 -1
- “one”
- “two”
- “three”
数据结构
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
6 hash
方式1 单key+序列化.问题:每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大。

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

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

- **通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题
| 语法 | 功能 |
|---|---|
| hset |
给 |
| hget |
从 |
| hmset |
批量设置hash的值 |
| hexists |
查看哈希表 key 中,给定域 field 是否存在。 |
| hkeys |
列出该hash集合的所有field |
| hvals |
列出该hash集合的所有value |
| hincrby |
为哈希表 key 中的域 field 的值加上增量 1 -1 |
| hsetnx |
将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 . |
kv模式不变,但V是一个键值对
1 | 127.0.0.1:6379> hset user id 1 name zhangsan age 25 |
要hget key key进行取值
数据结构
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
jedis
不好用,类似jdbc,了解即可
springboot 整合redis
1 | spring.data.redis.host=192.168.6.131 |
序列化定制
redistemplate存储值,经过了序列化器,默认是jdk序列化器,存的值直接用redis取,取出来的带地址值。不好用。所以要换序列化器
1 |
|
配置文件
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个文件,位置也由位置+文件名,分开组成

开启
修改默认的appendonly no,改为yes,开启AOP方式
同步频率
appendfsync always
- 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec
- 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync no
- redis不主动进行同步,把同步时机交给操作系统。
重写

何时触发重写
默认配置是当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 | 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。 |
主从复制
实现思路
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
7include /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/目标端口 来批量替换每个配置文件中的端口号

(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,需要整体迁移而不是逐步过渡,复杂度较大。



