Redis
Redis 基本指令
- keys * :查询当前库的所有键
- exists < key> : 判断某个键是否存在
- type < key>: 判断键的类型
- del< key>: 删除某个键
- expire < key> < seconds>: 为一个已经存在的键设置过期时间,单位为秒
- ttl < key>: 查看键还有多久过期,-1是永不过期,-2是已过期
- dbsize :查看当前数据库的key数量
- Flushdb:清空当前库
- Flushall:通杀全部库!!!!
五大数据类型
-
String
- 是最基本的数据类型
- 是二进制安全的,意味着String可以包含任何数据,如图片或者序列化的对象
- 一个字符串的value最多可以是512M
- get < key>: 查询对应键值
- set < key>: 添加键值对
- append < key> < value>: 将给定的value添加到键为key的值的末尾
- strlen < key>: 获得值的长度
- setnx ,只有在key不存在时,设置key的值
- incr < key>: 将key中存储的数字值增1;只能对数值进行操作
- decr < key>: 将key中存储的数值减1;只能对数值进行操作
- incrby / decrby : 进行一定步长的加减
- mset < key1> < value1> < key2> < value2>……< key n> < value n> :同时设置一个或者多个键值对
- mget:可以获得多个键的值
- msetnx:同时设置一个或者多个键值对,当且仅当所给定的key都不存在
- getrange < key> <开始位置> <结束位置> 获得值的指定范围,是一个闭区间
- setrange < key> <开始位置> < value> 从某个位置开始设置值
- setex < key> < 过期时间> < value>
- getset < key> < value>: 给key赋新的value的时候,获得旧值
-
List
- 单键多值
- 是简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头或者尾
- 底层是一个双向链表,对两端的操作性能高
- lpush/rpush < key1> < value1> < value2>……< value n>: 从左边/右边加入值
- rpoplpush < key1> < key2>: 从key1的右边弹出一个值,添加到key2的左边
- lrange < key> < start> < stop>: 按照索引下标获得一定范围的元素,从左到右
- lindex < key> < index>: 按照下标
- llen < key>: 获得列表长度
- linsert < key> before/after < value> < newvalue>: 在value之前/后加入值 、
- lrem < key> < n> < value> :从删除n个value,如果n是正数 表示从左往右,为负数表示从右往左,为0则是删除所有。
-
set
- 对外提供的功能和list相似,特殊之处是set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,可以选择使用set, 且set提供了判断某个成员是否在一个set集合内的接口
- 底层是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)
- sadd < key> < value1> < value2>……< value n>: 添加值
- smembers < key>: 取出该集合所有的值
- sismember < key> < value>: 判断集合中是否有这个value
- scard < key>: 这个集合中有多少元素
- srem < key> < value1> < value2>……< value n>:删除这些value
- spop < key> : 随机输出一个值,删除
- srandmember < key> < n>: 随机取出n个value,不会删除
- sinter < key1> < key2>: 返回两个集合的交集
- sunion < key1> < key2>: 返回两个集合的并集
- sdiff < key1> < key2>: 返回两个集合的差集
-
hash
- 键值对集合,是一个string类型的field和value的映射表,类似于java里面的Map< String,String>,适合存储对象
- hset < key> < field> < value>: 给key集合中的field键赋值value
- hmset: 设置多个
- hget < key> < field>: 取出value
- hexists < key> < field>: 查看hash表中是否存在field
- hkeys < key>: 列出hash集合的所有field
- hvals < key>: 列出该hash集合的所有value
- hincrby < key> < field> < increment>: 为key中的域field的value加上增量increment
- hsetnx < key> < field> < value>: 将key中的域field的值设置为value,如果没有的话
-
zset
- 与普通set相似,每个成员都会关联一个评分score,这个评分被用来按照从最低分到最高分的方式排序集合中的成员,评分可重复,因为元素是有序的,所以可以很快的根据评分score或者次序position来获一个范围的元素。访问有序集合的中间元素也是非常快的,因此能够使用有序集合作为一个没有重复成员的智能列表
- zadd < key> < score1> < value1> < score2> < value2> ……< score n> < value n> : 为集合中添加元素,并设置分数
- zrange < key> < start> < stop> [WITHSCORES]: 返回有序集合key中,下标在[start, stop]中的元素,添加了WITHSCORES 返回值会带有分数
- zrangebyscore key min max:返回有序集合中分数介于[min, max]之间的成员,按照从小到大排列
- zrevrangbyscore key max min:同上,从大到小排列
- zincrby < key> < increment> < value>: 为元素的score加上增量
- zrem < key> < value> : 删除值为value的元素
- zcount < key> < value> < min> < max>: 返回分数在[min, max]区间的元素个数
- zrank < key> < value>: 返回该值在集合中的排名,从0开始
Redis 相关配置
- 计量单位 :
1k => 1000 bytes
1kb => 1024 bytes
1m => 10001000 bytes
1mb => 10241024 bytes 以此类推 - 大小写不敏感
- 可以把多个配置文件里面共通的部分抽取出来,然后用include 引入,避免冗余
- 将第61行的 bind=127.0.0.1注释掉,同时把 第80行的 protected-mode 设置为 no,这样可以任意的地方去访问
- tcp-backlog:可以理解为一个请求到达后直到接受进程处理的队列,包括未完成3次握手队列和已经完成握手的。高并发环境下它的设置值跟超时时限内的Redis吞吐量决定。
- timeout:一个空闲的客户端维持多长时间会关闭,0是永不关闭
- Tcp keepalive:对访问客户端大大一种心跳检测,每隔n秒检测一次,推荐为60s
- daemonize: 是否设置后台开启
- pidfile:存放pid文件的位置,每个实例会产生一个不同的pid文件
- log level:debug、verbose、notice、warning,4个逐渐级别逐渐降低,同时级别越低,需要存储的信息也就越少,生产环境下选择notice和warning
- logfile:日志文件名称
- syslog:是否将Redis日志输送到linux系统的日志服务中
- syslog-ident:日志的标志
- syslog-facility:输出日志的设备
- database:设定库的数量,默认16
- security
- 在命令行中设置密码:config set requirepass “****” 登陆使用 auth ****,该方法是临时的,重启后无效
- 在配置文件中 修改 requirepass 是永久的
- maxclient:最大客户端连接数
- maxmemory:设置Redis可以使用的内存量。一旦达到内存使用上限,Redis会试图去移除数据,移除规则可以通过maxmemory-plicy来指定。如果无法移除,那么redis会针对哪些需要申请内存的指令返回错误信息
- maxmemory-policy:
- volatile-lru:使用最近最少使用算法移除key,只对设置了过期时间键
- allkeys-lru: 对所有的键
- volatile-random:在过期集合中随机删除,只对设置了过期时间的键
- allkeys-random:所有
- volatile-ttl:移除哪些TTL值最小的key,即哪些快要过期的
- noeviction:不进行移除,只返回错误
- maxmemory-samples:设置样本数量,LRU算法和最小ttl算法都不是精确的算法,而是估算值,所以可以设置样本的大小,一般设置3-7的数字,数值越小样本越不精确,但是性能消耗越小
java 连接Redis
导入包,然后对象即可
Redis 事务
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化,按顺序的执行。事务在执行的过程中不会被其他客户端发送来的命令请求打断。因此主要作用是串联多个命令防止别的命令插队。
Multi、Exec、discard
-
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行
-
组队的过程中可以通过discard来放弃组队
错误处理
- 组队中某个命令出现了报告错误(编译时异常),执行时整个的所有队列都会被取消。
- 如果执行阶段出现错误,并不会中止事务,该成功的成功,失败的失败,一直执行玩所有的任务
锁
- 悲观锁(Pessimistic Lock): 每次拿数据的时候,都会认为别人会去修改数据,因此拿到数据后就会去上锁。传统的关系型数据库都是这种的,比如 行锁、表锁等
- 乐观锁(Optimistic Lock): 每次拿数据的时候,都认为别人不会去修改,所以不会上锁,但是在更新的时候会判断一下在此期间有没有人修改这个数据,常用的方法是给数据添加一个版本号。乐观锁适用于多读的应用类型,这样可以提高吞吐量,redis使用的是check-and-set(CAS)机制实现事务。
- Watch key [key……] : 在执multi之前,先执行watch key,可以监视一个或者多个key,如果在事务执行之前这个key被其他命令所改动,那么事务就会被打断
redis事务特性
- 单独的隔离操作:事务中所有的命令都会被序列化,按顺序的执行。事务在执行的过程中不会被其他客户端发送来的命令请求给打断
- 没有隔离级别的概念:队列中的命令没有提交之前都不会被实际的执行,也就不会存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人头痛的问题
- 不保证原子性:redis同一个事物中如果有一条命令失败,其他的命令仍然会被执行,没有回滚
Redis持久化
RDB
- 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是snapshot快照,它恢复时是将快照文件直接读入内存
- rdb的保存的文件:在redis.conf中配置文件名称,默认为dump.rdb
- 修改rdb的保存路径:默认是在redis启动的那个目录
- rdb的保存策略:默认是(900,1);(300 10)(60 1000),如果不满足保存策略的话,那么正常关闭redis的情况下也是能够保存的,但是非正常关闭就不行了
- 手动保存快照:save:只管保存,其他不管,全部阻塞,一般不用
- stop-writes-on-bgsave-error yes:当redis无法写入磁盘的时候,直接关闭Redis的写操作
- rdbcompression yes: 进行rdb保存时,将文件压缩
- rdbchecksum yes:在存储快照后,还可以让redis使用CRC64算法进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能,可以关闭此功能。
- rdb的备份
- 先通过config get dir 查询rdb文件的目录
- 将*.rdb的文件拷贝到别的地方
- rdb的恢复
- 关闭redis
- 先把备份的文件拷贝到工作目录下
- 启动redis,备份数据会直接加载
优缺点
优点:节省磁盘空间、恢复数据快
缺点:1. 虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能的
2. 在备份周期在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照的所有修改
备份数据是如何执行的
Redis会单独创建(fork)一个子进程来执行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,RDB的缺点是最后一次持久化后的数据可能会丢失
关于fork:在linux系统中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了"写时复制技术", 一般情况下父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
AOF
以日志的形式来记录每个写操作,将redis执行过的所有写指令记录下来,只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
- AOF默认不启动,需要手动在配置文件中配置:默认是appendonly no
- 也可以在appendfilename处修改文件名称
- AOF文件的保存路径和RDB的路径一致
AOF文件故障备份
- AOF 的备份机制和性能虽然和RDB不同,但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载
- AOF和RDB同时开启,系统默认取AOF的数据
AOF文件故障恢复
- AOF文件的保存路径与RDB的路径一样
- 如遇到AOF文件损坏,可通过 redis-check-aof – fix appendonly.aof 进行恢复
AOF同步频率设置
- 始终同步,每次redis的写入都会立刻计入日志 appendsync always
- 每秒同步,每秒记入一次日志,如果宕机,本秒的数据就会丢失 appendsync everysec
- 把同步的进行同步,把同步时机交给操作系统。appendsync no
Rewirte
AOF采用文件追加方式,文件会越来越大,为了避免出现此种情况,新增了重写机制,当AOF文件的大小超过了所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。 可以使用bgrewriteaof
redis如何实现重写
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件然后再rename),遍历新进程的内存中的数据,每条记录有一条的set语句。然后重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,和快照类似
何时重写
- 重写虽然可以节约大量的磁盘空间,减少恢复时间。但是每次重写还是有一定负担的,因此设定redis要满足一定条件才会进行重写:
- auto-aof-rewrite-percentage 100
- auto-aof-rewrite-min-size 64mb
- 系统载入时或者上次重写完毕时,redis会记录此时aof大小,设为base_size, 如果redis的aof当前大小大于base_size+base_size*100%(默认)且大于64mb(默认)的情况下,Redis会对AOF进行重写
###AOF的优缺点
优点: - 备份机制更稳健,丢失数据概率更低
- 可读的日志文本,通过操作AOF文件,可以处理误操作
缺点: - 比起RDB要占用更多的磁盘空间
- 恢复备份速度慢
- 每次读写都同步的话,会有一定的性能压力
- 存在个别bug,造成不能恢复
Redis主从复制
- 概念:主机数据更新后根据配置和策略,自动同步到备份机的master/slave机制,master以写为主,slave以读为主
- 用处:读写分离,性能扩展;容灾快速恢复
搭建
- 创建三个配置文件,因为已经存在一个6379端口的了,所以接下去配置6380和6381
- 编写配置文件
- 启动服务器
- 启动客户端
- 选择从服务器,输入命令:
slaveof ip port
也可以直接修改主配置文件里面的slaveof ,这样就是永久的 - 查看信息:info replication
可以看到我把6379设置为master,其他两个设置为slave了
复制原理
- 每次从机联通后,都会给主机发送sync指令
- 主机立刻进行存盘操作,并把RDB文件发送给从机
- 从机收到RDB文件后,进行全盘加载
- 之后每次主机的写操作都会立刻发给从机,从机执行相同的命令
薪火相传
- 上一个slave可以是下一个slave的Master,slave同样可以接收其他slave的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力,去中心化降低风险
- 用slaveof ip port
- 中途变更转向:会清除之前的数据,立刻建立拷贝最新的
- 风险是一旦某个slave宕机,后面的slvae都没法备份
如果主服务器宕机了,通过设置 slaveof no one 可以把从服务器的身份去掉 (反客为主)
哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票自动将从库转换为主库
- 在myredis目录(我自己的)下新疆sentinel.conf文件
- 在配置文件中填写内容:
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster 为监控对象起的服务器名称,1 为至少有1个哨兵同意迁移的数量 - 启动哨兵:redis-sentinel /myredis/sentinel.conf
故障恢复
- 新主登基:从下线的主服务的所有的从服务里面挑选一个从服务
选择条件为:- 选择优先级靠前的,redis.conf中 slave-priority
- 选择偏移量最大的:指的是获得原主服务的数据最多的
- 选择runid最小的从服务: 每个redis实例启动后都会随机生成一个40位的runid
- 群仆俯首
挑选出了主服务之后sentinel向原主服务的从服务发送(slaveof 新ip 新端口)命令,复制新的master - 旧主俯首
当已经下线的主服务再次登陆时,sentinel会发送命令,变成从
redis集群
解决问题:1. 容量不够,redis扩容;2.并发写操作,redis如何分摊
Redis集群实现了对Redis的水平扩容,即启动N个节点,每个节点保存1/N的数据
Redis集群通过分区(Patition)来提供了一定程度的可用性;(availability)即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
集群配置
- 安装ruby
- yum install ruby
- yum install rubygems
- 制作6个实例,6379、6380、6381、6389、6390、6391
- 拷贝多个redis.conf文件
- 开启daemonize yes
- Pid文件名
- 指定端口
- Log文件名字
- Dump.rdb名称
- Appendonly 关闭或者换名称
- 安装redis cluster配置修改
- cluster-enabled yes 打开集群模式
- cluster-config-file nodes-6379.conf 设置节点配置文件名
- cluster-node-timeout 设置节点失效时间,超过该时间ms,集群自动进行主从切换
- 启动各个实例,确保nodes-xxx.conf正确生成
- 将各个节点合并
./redis-trib.rb create --replicas 1 ip1:port1 ip2:port2 ……
–replicas 1 表示 集群中每个主节点有1个从节点
ip 要用真实地址 不能用127.0.0.1
分配原则:- 一个集群至少有3个主节点
- –replicas 1 表示 集群中每个主节点有1个从节点
- 尽量保证每个主数据库运行在不同的ip地址,每个从库和主库不在一个ip地址上
什么是slots
- 一个redis集群包含了16384个插槽(hash slot),数据库中的每个键都属于这16384个插槽的其中一个,集群使用公式CRC16(key)%16384来计算键属于哪个槽。
- 集群中的每个节点负责处理一部分插槽。
在集群中录入值
- redis-cli每次录入,查询键值,redis都会计算key应该送往哪个插槽,如果不是客户端对应的插槽,redis会报错,并告知因前往的redis实例地址和端口
- redis-cli客户端提供了 -c 参数实现自动重定向
reids-cli -c -p 6379 - 不在一个slot下的键值,是不能使用mget,mset等多建操作。
- 可以通过{}来自定义组的概念,从而使key中{} 内相同的键值放到一个slot中
set a{user} 2
set aa{user} 3
set aaa{user} 4
把上面的数据都放在user这个组中 - cluster keyslot < key> 计算key应该被放在哪个槽上
- cluster countkeysinslot < slot> 返回槽slot目前包含的键值对数量
- cluster getkeysinslot < slot> < count> 返回count个slot槽中的键
故障恢复
- 如果主节点下线,从节点会自动升级为主节点
- 原来的主节点再次上线后,就会变为从节点
- 如果某一个部分的slot机器都宕机了,那么集群就不好使了
优缺点
优点:
- 实现扩容
- 分摊压力
- 无中心配置相对简单
缺点: - 多键操作是不被支持的
- 多键的Redis事务是不被支持的。lua脚本不被支持
- 由于集群方案出现较晚,很多公司采用了其他的方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是过渡迁移,复杂度较大