redis-server会直接加载redis默认配置文件。想要指定加载一个配置文件可使用redis-server /usr/local/redis.conf
通用命令
keys * 查询匹配模式的key(通配符 * 多个,?一个,[])
randomkey 随机返回key
type key 返回该key的类型
exists key 判断该key是否存在
del key 删掉该key
rename key newkey重命名key
renamenx key newkey 重命名key,防止名字重复
move key dbnum 移动key到redis其他服务器
ttl key 查询key过期时间(s),永久的返回-1,pttl为ms
expire key time 设置key过期时间,pexpire为ms
persist key 将key设置为永久有效
dbsize查看当前数据库下的键个数
bgrewriteaof手动重写aof日志文件
save在当前进程手动导出rdb文件
bgsave在后台进程导出rdb文件
config get configname获取某个配置参数的值
config set configname value设置某个配置参数的值
slowlog get [num]获取指定数量的慢日志记录,无参数表示获取所有
字符型(string)操作命令
set key value设置键值
mset key value key1 value1…设置多个键值
get key获取该键的值
mget key key1…获取多个键的值
setrange key num value设置该键第num个字符之后用value填充
append key value把value增加到key的原值上
getrange key start stop获取该key start到stop范围的字符
getset key newvalue 获取该key的值并设置新值
incr key递增该key
decr key递减该key
incrby key num递增该key num
decrby key num 递减该key num
incrbyfloat key num增加浮点数
setbit key offset value设置offset对应二进制上的值
链表(list)操作命令
lpush key value value1…往链表左边添加值
rpush key value往链表右边添加一个value
lrange key start end获取链表start到end范围的数据0 -1获取所有
lpop key弹出链表左边的值
rpop key弹出链表右边的值
lrem key count value删除链表中count个值为value的元素count<0从表尾开始找
ltrim key start end截取该范围的值重新赋给key
lindex key index获取index索引对应的值
llen key获取该链表的长度
linsert key after|before value newvalue在value前或后插入newvalue
rpoplpush key key1从key右边弹出往key1左边插入
brpop,blpop key timeout等待timeout时间弹出key右边的值
集合(set)操作命令(无序性,唯一性)
sadd key value value1…往集合添加元素
smembers key查看key的集合
srem key value value1…删除集合的元素
spop key随机返回key集合中的元素,并弹出
srandmember key随机返回key集合中的一个元素
sismember key value判断value是否在集合key中
scard key返回集合有多少个元素
smove key key1 value将key集合中的value移动到key1中
sinter key key1…求key,key1…集合的交集
sunion key key1…求key,key1…集合的并集
sdiff key key1…求key-key1-…集合的差集
sinterstore newkey key key1…将key,key1…集合的交集返回值存入newkey集合中
有序集合(zset)操作命令(在集合基础上增加score用来排序)
zadd key score value score1 value1…向有序集合中添加元素
zrange key start end [withscores]取出start名次到end名次之间的元素,名次从0开始, withscores是否把score一起取出
zrangebyscore key start end [withscores] limit offset num根据分数的范围取出元素,limit从offset开始取出num个元素,withscores是否把score一起取出
zrank key value取出key有序集合中值为value的排名值,名次从0开始
zrevrank key value倒序取出value的排名,名次从0开始
zremrangebyscore key start end根据分数值删除[start,end]之间的值
zremrangebyrank key start end根据排名删除[start,end]之间的值
zrem key value value1…删除值为value,value1…的元素
zcard key返回有序集合元素个数
zcount key start end统计分数值在[start,end]之间的元素个数
zinterstore newkey numkeys key key1… weight w1 w2 [aggregate sum|min|max]将key,key1…交集可求和,最大,最小.可加权重进行计算,最后把结果存到newkey中.numkey是所求key的个数
zunionstore用法与上相同,这个是求并集
哈希(hash)操作命令(与PHP数组类似)
hset key field value设置为key的哈希的键值
hmset key field value field1 value…设置为key的哈希的多个键值
hget key field获取key哈希中键为field的值
hmget key field field1获取key哈希中键为field,field1…的值
hgetall key获取key哈希中的所有键值
hdel key field删除key哈希中键为field的值
hlen key获取key哈希中的元素数量
hexists key field判断key哈希中是否存在field
hincrby key field num .key哈希中filed对应值增长num
hincrbyfloat key field num .key哈希中filed对应值增长浮点型num
hkeys key与php array_keys类似
hvals key与php array_values类似
redis事务
mysql | redis | |
---|---|---|
开启 | start transaction | multi |
语句 | 普通sql | 普通命令 |
失败 | rollback 回滚 | discard取消 |
成功 | commit提交 | exec |
redis是将事务中的语句放入一个队列中,只有exce后才执行,如遇到语法错误,事务取消,如遇到语句使用错误但语法无错(如:sadd list),语句都会先放入队列,exec执行时该语句会报错,但其他任何语句会依次执行
redis中使用的是乐观锁,可用watch监控某个key值,如果在事务提交前监控的值发生改变过,那么事务队列中语句都不执行。如果不监控,事务中语句照常执行。unwatch取消监控
频道发布与消息订阅
publish key value发布一个频道key,并推送内容value
subscribe key订阅一个频道key,并可以接收该频道推送的消息
psubscribe key pattr订阅匹配pattr模式的所有频道,并可以接收该频道推送的消息
redis持久化(作为store,可持久化存储)
rdb快照持久化
没隔N分钟或N次写操作后,从内存dump数据形式rdb文件,压缩,放在备份目录。加粗字体在配置文件(redis.conf)通过参数进行配置。
工作方式:redis-server进程监视,如果达到save参数设置条件,调用rdbdump进程进行导出一大块二级制文件,恢复速度快。
N分钟或N次写:save 时间 key改变的个数 进行dump
压缩:rdbcompression yes导出来是都需要压缩
dbfilename dump.rdb导出的文件名配置,dir ./导出文件目录配置
rdb的缺陷:因为save参数需要key改变次数的限制才会dump数据,如果刚好不在这个次数内,在上一个备份点到下一个备份点之间的数据将会丢失,所以增加了aof持久化方式。
aof日志持久化
当达到aof配置条件时,aof写下日志(记录键值的修改命令)到磁盘上,aof写日志条件取决于配置文件appendfsync参数值。
appendonly yes该配置参数配置是否打开aof
appendfsync always每一个命令都立即同步到aof,安全但速度慢
appendfsync everysec折衷方案,每秒写一次
appendfsync no写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof,同步频率低但速度快
在dump rdb过程中,aof如果停止同步,所有操作系统会缓存在内存的队列里,dump完成后,统一操作
重写aof日志文件:配置文件参数设置值为条件(文件大小达到多大时),可以重写。重写是指把内存中的数据逆化成命令,可简化记录(可减少内存使用,快速恢复)
如果rdb文件和aof文件都存在时,优先使用aof文件进行恢复
恢复时,rdb恢复比较快,直接是内存映射,载入内存。而aof是执行一条条命令。
redis主从复制
1.主从备份
2.读写分离
3.任务分离
主从复制过程:slave自动请求master同步,master dump出rdb给slave,在这个过程中的key变化会有aof缓冲,再把aof缓冲给slave.完成主从复制。
redis集群配置:在从redis服务配置文件中配置slave-of参数,如果master有密码需要配置密码(masterauth)
缺点:每次slave断开后(无论怎么断开),再连接master,都要把master全部dump出来rdb,再aof,即同步过程都要重新执行一遍。(不能多个slave同时启动起来,master IO会剧增)
字符串实现数据结构
需要改变的字符串都是由SDS(简单动态字符串)实现的,无须改变的(比如打印日志时)使用字符串字面量
SDS结构体包含:
其中字符数组buf[]遵循C语言的字符数组规则,以\0结尾,但并不计算入len和free中,都由SDS自己完成对用户来说是透明的,这样做的好处在于可以直接使用stdio.h里面的部分函数,避免了不必要的代码重复
与C字符相比:
C字符获取字符长度需要遍历计数复杂度为O(N),而SDS直接获取free复杂度为O(1)
C字符需要手动扩展空间,SDS在进行字符串拼接的时候(sdscat)会先检查给定的空间是否足够,不够则扩展,再拼接,这样不会导致内容溢出
C字符的字符空间长度为N+1,导致每次更改字符后需要重新分配空间(没有多余的空间存下),SDS通过free实行预分配空间策略(修改之后len属性的值<1MB则free值和len值相同,如>=1MB则给free分配1MB空间),减少内存重分配次数
SDS惰性空间释放策略,在移除字符时(sdstrim),会将移除的字符长度存入free值(并未释放),在下次增加该字符时,直接使用free中的空间,可减少内存分配次数
C字符通过判断\0是否结尾,不能储存很多特殊数据,而SDS通过len值判断是否结尾可以存储多种特殊格式的数据
链表实现数据结构
链表节点:
链表数据结构:
表头节点的prev和表尾节点的next指针都指向NULL,双向链表的访问以NULL为终点
字典(数据库整体键值对的实现和哈希底层的实现)
字典也被称为符号表、关联数组或映射,是用于保存键值对的抽象数据结构
字典在redis中主要用于数据库整体键值对的实现和哈希底层的实现
哈希表节点:
next属性是指向另一个哈希表节点的指针,将多个哈希值相同的键值对连接在一起,以此来解决键冲突的问题
哈希表结构:
字典结构:
type属性和private属性是针对不同类型的键值对,为创建多态字典而设置的
ht一般包含2个dictht哈希表数组,字典只使用ht[0],ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用
rehashindx进入rehash目前的进度,如果没有正在进行rehash那么它的值为-1
哈希算法:
如果需要添加一个新的键值对,步骤是:首先根据键值对的键计算出哈希值,再根据ht[0] (根据情况不同也可能是h[1])的sizemask(总是为size-1)根哈希值进行&(按位与运算)计算出索引值,再根据索引值,将新键值对的哈希节点放到哈希表数组的指定索引上面
如上图这样计算出k0的索引值为0,则应该将该哈希节点放到数组的索引0位置
如果计算出的索引值相同时将出现键冲突,将用哈希节点中的next指针把索引相同的连接成单链表,新节点排在表头
rehash:为了让负载因子(通过某种计算出来的负载程度,进行某些操作时会有变化)维持在一个合理的范围内,当哈希表保存的键值对数量太多或太少,需要对哈希表进行rehash(可扩容或收缩)
rehash过程:先为ht[1]分配空间(分配策略有点多);再将ht[0]中的键值对重新计算键的索引值,根据索引值分配到ht[1]的相应索引上;然后都迁移好之后,把ht[0]释放,ht[1]改为ht[0]并再创建一个新的空白哈希表ht[1]位下一次rehash使用
渐进式哈希:在进行rehash的时候,如果只是4个那可以一瞬间完成,如果是4亿个呢,将会分多次渐进式的进行rehash(通过维持字典结构里的rehashindx值)
在rehash期间执行增删改查操作时,除了完成指定的操作以外(增会在ht[1],删改查会先查ht[0]再ht[1]),还会顺带将为rehashindx值的索引rehash到ht[1]中,完成后rehashindx增一,这样直到所有数据转移完成ht[1]变为ht[0],再新创建一个空的ht[1]并且rehashindx值变为-1
跳跃表(skiplist)有序集合键和集群节点底层实现
跳跃表节点结构:
通过level来加快访问其他节点的速度,一般来说,level越多,访问其他节点越快
创建新的跳跃表节点时,根绝幂次定律(越大的数出现的几率越小)随机生成一个介于1-32之间的值作为level数组的大小,被称为层的高度
前进指针用于从表头向表尾方向访问节点,跨度用于记录两个节点之间的距离(遍历只需要前进指针即可完成,沿途经过的跨度相加起来用来计算排位(rank 在表中是第几个))
后退指针用于从表尾向表头方向访问节点,跟前进指针不同,每次只能后退一个节点
跳跃表都按score分值从小到大排序,obj成员对象是一个SDS保存集合对象名
跳跃表结构:
length记录跳跃表长度,不将表头计算在内
level跳跃表中层数最大的节点层数,不将表头计算在内
整数集合(都是整数集合的底层实现)
整数集合结构:
编码方式:在向集合数组contents中添加新元素时,如果编码不足以容纳该值,会进行升级操作(将所有数组编码格式变为最大数得编码格式,并为数组中每个元素重新分配内存空间)
升级的好处:1.提升整数集合的灵活性2.尽可能的节约内存
不支持降级操作,如果升级之后编码将一直保持升级后的状态
压缩列表(ziplist 少量列表项列表、少量键值对哈希底层实现)并且整数小字符短
压缩列表是redis为了节约内存而开发的
压缩列表是被用作列表键和哈希键的底层实现之一
压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
添加新节点到压缩列表,或者从压缩列表中删除一个节点,可能会引发连锁更新操作,但这种操作出现的几率不高
对象
基于上面介绍的这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象、有序集合对象这五种类型的对象,每种对象都用到了至少一种数据结构
对象的结构:
type可选用的值:
一般对数据库保存的键值对来说,键总是一个字符串对象,值可以是其中任何一种(TYPE命令返回的就是值的对象类型)
encoding属性决定这个对象使用了哪种数据结构作为对象底层的实现
对象的引用计数:(与php变量引用计数机制类似)
再创建一个新对象时,引用计数的初始值为1,对象被一个新程序使用时refcount增一,不再被一个程序使用时refcount减一,refcount为0时,对象所占内存会被释放
redis在初始化时,自动创建值为0-9999的对象,当要使用到这其中的对象时,直接对应对象的refcount加一(这样就可以直接共享对象,而不是新创建对象)
尽管共享越复杂的对象可以节约更多的内存,但受到CPU时间的限制,redis只对包含整数值的字符串对象进行共享(越复杂验证共享对象和目标对象是否相同所需的复杂度会越高)
lru属性:
该属性记录该对象最后一次被命令程序访问的时间
ONJECT IDLETIME命令查看某键的空转时长,用当前时间-lru(该值越小越活跃,该命令不会改变lru属性值)
该属性还可以用于回收内存,在服务器打开了maxmemory选项,并且回收内存算法为volatile-lru或allkeys-lru时,当占用内存数超过maxmemory所设置的上限值时,空转时长较高的部分键会被优先释放回收内存
redis采用单线程,epoll多路复用I/O模型
单线程是指网络IO和键值对的读写是由一个线程来完成(多线程面临共享资源并发访问控制问题,在线程数达到一定值时,吞吐率不会再增加,还可能出现降低情况)多线程存在同时访问的共享资源,为了保证共享资源的正确性需要额外机制
socket网络模型采用非阻塞模式,采用epoll多路复用IO模型
epoll机制创建多个fd监听套接字,当fd有请求到达时,会触发相应的事件,然后将事件放入事件处理队列,redis一直对事件队列调用相应的处理函数进行处理
Redis单线程处理IO请求性能瓶颈主要包括2个方面:
1、任意一个请求在server中一旦发生耗时,都会影响整个server的性能,也就是说后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。耗时的操作包括以下几种:
a、操作bigkey:写入一个bigkey在分配内存时需要消耗更多的时间,同样,删除bigkey释放内存同样会产生耗时;
b、使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很大,例如lrange key 0 -1一次查询全量数据;
c、大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长;
d、淘汰策略:淘汰策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会造成耗时变长;
e、AOF刷盘开启always机制:每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能;
f、主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久;
2、并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但是读写客户端数据依旧是同步IO,只能单线程依次读取客户端的数据,无法利用到CPU多核。
针对问题1,一方面需要业务人员去规避,一方面Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操作放在了异步线程中执行,降低对主线程的影响。
针对问题2,Redis在6.0推出了多线程,可以在高并发场景下利用CPU多核多线程读写客户端数据,进一步提升server性能,当然,只是针对客户端的读写是并行的,每个命令的真正操作依旧是单线程的。
AOF写后日志,先执行命令将数据写入到内存再写入日志,不用在写前去进行语法检查,防止在AOF中记录错误的语句,在之后写AOF可防止阻塞当前的写操作
缺点:1.在执行命令后还未记录就宕机,可能出现丢失数据2.记录日志是写磁盘而且是在主线程完成的,写盘很慢会导致对下一个操作有阻塞风险
三种写会策略,除了always策略,其他策略会将数据暂时放到内存缓冲区,然后根据策略入盘
防止AOF日志文件过大,出现重写机制,由主线程fork出后台的bgrewriteaof子进程
每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。
RDB(内存快照)
执行全量快照,将内存中的数据全部记录到磁盘中
在生成RDB文件时:save命令在主线程中执行,会导致阻塞bgsave命令创建一个子进程写入RDB文件,避免阻塞主线程,也是redis RDB的默认配置
如果在进行RDB时,如果可以修改键值内容,会导致所记录的值和内存中真实值不一致,如果不可以修改的话,将会导致主线程在RDB写文件这段时都阻塞,大大影响业务
redis采用写时复制技术(Copy-OnpWrite,COW),在执行快照的同时正常处理写操作
在进行RDB时,若果主线程上是读操作,那么主线程和bgsave进程互不影响,如果主线程上是要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本,bgsave子进程把这个副本数据写入RDB文件
关于 AOF 和 RDB 的选择问题,我想再给你提三点建议:
1.数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
2.如果允许分钟级别的数据丢失,可以只使用 RDB;
3.如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
redis具有高可靠性一是数据尽量少丢失(AOF,RDB保证)二是服务尽量少中断(增加副本冗余量使用主从库模式,主从库 采用读写分离读操作:主从库都可接收写操作:先到主库执行然后主库将写操作同步到从库)
主从库之间进行第一次同步
1.从实例执行replicaof ip port命令,从和主请求建立连接,主确认回复从之后,主从就可以开始同步了
2.主库将当前所有数据同步给从库,从库收到数据在本地完成数据加载(主库执行bgsave生成rdb文件,再将文件发给从库,从库先清空当前数据库,然后加在rdb文件)
3.在主库生成rdb文件过程中,主库不会被阻塞,仍然可以接收请求,将这些请求中的写操作写入replication buffer中,再将缓存发给从库,从库再执行这些操作完成同步
主从库同步时网络断开时,在redis2.8之前进行全量复制,开销大,在redis2.8之后进行增量复制
增量复制:在网络断开之后主库会将写操作写入eplication buffer和repl_backlog_buffer环形缓冲区(满了会覆盖以前的),主库记录自己写到的位置,从库记录自己已经读到的位置,该初始位置应该相同,主库记录离初始位置的偏移量(master_repl_offset)从库记录已复制的偏移量(slave_repl_offset),在恢复网络连接之后,从库把自己的slave_repl_offset发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距把之间的命令同步给从库就行