redis是单进程单线程的,用C写的。 全程其实是:REmote Dictionary Server,远程数据服务。
①一种NoSQL(Not Only SQL)数据库。 ②C/S通讯模型、单进程单线程模型、丰富数据类型、操作原子性。 ③持久化 可缓存也可持久化。缓存到内存中,持久化有两种方式:RDB和AOF。 ④速度快 机器性能优良情况下,读写最快可达到10W/s。 ⑤基于键值对的数据结构,半结构化数据。 ⑥高并发读写 ⑦主从复制 实现了多个相同数据的Redis副本 ⑧支持Lua ⑨不需要依赖于操作系统中的类库,它自己实现了事件处理的相关功能。 提供了简单的TCP通信协议,很多编程语言可以很方便地接入到Redis。 ⑩高可用和分布式 从2.8版本正式提供了高可用实现Redis Sentinel,它能够保证Redis节点的故障发现和故障自动转移。3.0版本正式提供了分布式实现Redis Cluster,它是Redis真正的分布式实现,提供了高可用、读写和容量的扩展性。
● 速度为什么快? 数据放在内存; C语言实现; 单线程,无多线程竞争问题。
● 半结构化、结构化、非结构化数据 结构化:可以用关系型数据库表示、存储,表现为二维形式的数据。一般数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。其存储、排序都是有规律的。 半结构化:是结构化数据的一种形式,有相关标记,但不像关系型数据库那样的数据模型,同属一类的实体可能有不同属性,属性个数不一定相同,也无关顺序。 常见的半结构数据有XML和JSON。 非结构化:没有固定结构的数据,一般存储为二进制的数据格式。所有格式的办公文档、文本、图片、XML、HTML、各类报表、图像和音频/视频信息等等。
可用于缓存,事件发布或订阅,高速队列等场景。 缓存系统(热点数据:多读少写)、消息队列系统、社交网络、实时系统。消息队列推送。 (1)会话缓存(最常用) (2)消息队列(支付) (3)排行榜 跟某个条件有关,毕竟是按照XX来排序,排序的值设置成sorted set的score,数据作为value,每次只需执行一条ZADD命令。 (4)发布,订阅消息(消息通知) (5)商品列表 (6)计数器 命令都是原子性,可以用INCR、DECR来计数。 (7)好友关注、粉丝 (8)实时消息系统 Pub/Sub。 (9)评论列表 比如获取最近1000条评论,可以把这1000条评论的ID放在List中,超出部分直接从数据库取。 LPUSH latest.comments:插入数据 LTRIM latest.comments 0 1000:永远只保存最近1000个ID 当获取某页评论时,可以通过如下代码(伪代码):
function getLatestComments(start, num_items): id_list = redis.lrange("latest.comments", start, start + num_items -1) if id_list.length < num_items id_list = SQL_DB("select ... order by time limit ...") return id_list如果要通过其他维度,可以按照这个维度再建一个list。
分为内存存储、磁盘存储和log文件。
同一对象类型,不同场景下用到的编码不同。这种方式更加灵活,可以帮助redis获取更高性能以及占用更少内存。 比如字符串存储的内容比较小,就用embstr格式,内容较大就用raw格式。
键和值都是对象,对象由对象结构和数据结构组成。键总是字符串对象,值可以是字符串、列表、哈希、集合或者有序集合中的一种。
供5个属性:type、encoding、ptr、refcount、lru。
属性描述type表示该对象的类型是什么encoding表示该对象使用的底层数据结构是什么ptr一个指向底层数据结构的指针refcount一个引用计数属性,可以用来内存回收和对象共享lru记录对象最后一次被命令程序访问的时间,可计算某个键的空转时长前三个跟保存数据有关。
①内存回收 用于记录对象的引用计数信息,redis利用引用计数(reference counting)技术实现内存回收机制(程序可以跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收)。 策略: a.创建对象时,引用计数的值会初始化为1; b.当对象被一个新程序使用时,引用计数值加1; c.当对象不再被一个程序使用,引用计数值减1; d.当对象的引用计数值变为0时,对象所占用内存会被释放。 ②对象共享 redis在初始化服务器时,服务器会创建1W个字符串对象,包含0~9999的所有整数值,当服务器、新创建的键需要用到值在这个范围内的字符串对象时,服务器会使用这些共享对象,而不会新建对象了。 对象结构中,refcount是引用指针属性,若有n个键共享一个值,refcount=n。 创建多少个共享字符串对象,可以通过redis.h/redis_share_intengers常量来修改。 object refcount命令可查看某个键对应值被引用了多少次。
如何让一个值被多个键共享: a.将键的值指针指向被共享的值对象; b.被共享的值对象的引用计数加1(refcount+1)。
两种实现方式:C处理字符串、SDS。
(1)缓冲区溢出,若未分配足够空间,可能溢出; (2)获取字符串长度需要遍历,复杂度O(N); (3)内存重分配,每次变化都要重新分配,缩短的话处理多余空间时可能内存泄漏。
一般用在不需要对字符串进行修改的地方,比如输出静态字符。
简单动态字符串。 预分配free字段,默认留够一定空间防止多次重分配内存。 key-value键值对含有字符串值的都是由SDS实现的。 比如:
127.0.0.1:6379>set name "Helen" 127.0.0.1:6379>lpush name "Helen" "Lee"set那个,其中key和value都是字符串对象,对象底层实现分别是,两个保存了字符串name、Helen的SDS结构。 lpush那个,键值对的值是一个包含了两个字符串对象的列表对象,这两个对象底层也是SDS。
struct sdshdr{ int len; //数组已使用字节数,sds保存字符串的长度 int free; //数组未使用字节数 char buf[]; //字节数组,存放字符串 }(1)SET key value 设置指定 key 的值 (2)GET key 获取指定 key 的值。 (3)GETRANGE key start end 返回 key 中字符串值的子字符 (4)GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 (5)GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 (6)MGET key1 [key2…] 获取所有(一个或多个)给定 key 的值。 (7)SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 (8)SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 (9)SETNX key value 只有在 key 不存在时设置 key 的值。 (10)SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 (11)STRLEN key 返回 key 所储存的字符串值的长度。 (12)MSET key value [key value …] 同时设置一个或多个 key-value 对。 (13)MSETNX key value [key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 (14)PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 (15)INCR key 将 key 中储存的数字值增一。 (16)INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。 (17)INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。 (18)DECR key 将 key 中储存的数字值减一。 (19)DECRBY key decrement key所储存的值减去给定的减量值(decrement) 。 (20)APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。
就是个简单的字符串列表,按插入顺序排序,可以添加元素到头部或者尾部。 一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 40亿)。 元素少的话会使用一块连续的内存存储,ziplist,即压缩列表,元素紧挨存储,分配的内存是连续的。 数据量多了会改用quicklist。普通链表附加指针空间消耗大,而且会加重内存碎片化。
链表+ziplist = quicklist 即多个ziplist使用双向指针串起来。双向链表之上增加了头、尾节点、元素数等属性。
typeof struct listNode{ struct listNode *prev; //前置节点 struct listNode *next; //后置节点 void *value; //节点值 }listNode typeof struct list{ listNode *head; //表头节点 listNode *tail; //表尾节点 void *value; //节点值 }listNode(1)LPOP key 移出并获取列表的第一个元素。 (2)LPUSH key value1 [value2] 将一个或多个值插入到列表头部。 (3)LPUSHX key value 将一个或多个值插入到已存在的列表头部。 (4)RPOP key 移除并获取列表最后一个元素。 (5)RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。 (6)RPUSH key value1 [value2] 在列表中添加一个或多个值。 (7)RPUSHX key value 为已存在的列表添加值。 (8)LLEN key 获取列表长度。 (9)RANGE key start stop 获取列表指定范围内的元素。 (10)BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 (11)BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 (12)BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 (13)LINDEX key index 通过索引获取列表中的元素。 (14)LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素。 (15)LREM key count value 移除列表元素。 (16)LSET key index value 通过索引设置列表元素的值。 (17)LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
string类型的field和value的映射表,适合用于存储对象; 每个 hash 可以存储 232 - 1 键值对(40多亿)。
(1)HDEL key field2 [field2] 删除一个或多个哈希表字段 (2)HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 (3)HGET key field 获取存储在哈希表中指定字段的值/td> (4)HGETALL key 获取在哈希表中指定 key 的所有字段和值 (5)HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 (6)HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 (7)HKEYS key 获取所有哈希表中的字段 (8)HLEN key 获取哈希表中字段的数量 (9)HMGET key field1 [field2] 获取所有给定字段的值 (10)HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 (11)HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 (12)HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 (13)HVALS key 获取哈希表中所有值 (14)HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。
string类型的无序集合,无重复数据; 通过hash表实现,所以添加、删除、查询复杂度都是O(1)。 集合最大存储成员232-1个,40多亿。
(1)不允许重复的string类型元素有序集合。每个元素都有个double类型的score,以完成集合的升序排序,score可相同。 (2)通过hash表实现,但是当items内容大于64的时候同时使用了hash和skiplist两种设计实现。 添加和删除都需要修改skiplist,所以复杂度为O(log(n))。 但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1) 其他的range操作复杂度一般为O(log(n)) 当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)。 集合最大存储成员232-1个,40多亿。
(1)zadd key score1 member1 [score2 member2] 添加一个或多个成员,或者更新已存在成员的分数。 (2)zrange key start stop [WITHSCORES] 通过索引区间返回有序集合成指定区间内的成员。
不太精确的set结构,使用contains方法判断对象是否存在时可能误判。 只要参数设置合理,精确度就相对足够精确。 只会误判存在,不会误判不存在。
1、爬虫:判断某个url是否已经爬过了; 2、垃圾邮箱的过滤; 3、判断新增的10W个号码,是否已经存在于10亿个号码池里了。 一种数据结构,由一串很长的二进制向量组成,可以看成一个二进制数组,当做一个容器,初始默认值都是0。
bf.add bf.exists 添加: 通过多个hash函数计算出多个index,将对应位置都置为1。 判断存在: ①把数据用这些hash函数都计算一次,只要有一个为0,就表示数据不存在; ②那么能不能说,如果一个数据的这些hash函数计算结果都是1,是不是就说明数据是存在的?答案是不能,因为多个数据可能这些hash函数计算结果一样。 所以说,布隆过滤器只能判断数据的不存在,不能判断存在。
①二进制数组,占用内存小,插入、查询都快; ②数据越多,误判率越高,无法判断数据的存在,无法删除数据。
过期时间是时间戳。 生存时间是毫秒 TTL和PTTL接受一个带有生存时间或过期时间的键,返回其剩余生存时间(秒/毫秒),也就是知道它还有多久会被服务器自动删除。
关键词命令含义EXPIREEXPIRE 设置键key的生存时间为ttl秒PEXPIREPEXPIRE 设置键key的生存时间为ttl毫秒EXPIREATEXPIREAT 设置键key的过期时间为指定秒数时间戳timestampPEXPIREATPEXPIREAT 设置键key的过期时间为指定毫秒数时间戳timestamp其实这四个都是使用PEXPIREAT命令实现的,都会转换成PEXPIREAT执行。
expires字典存放了所有带有过期时间的键的过期时间,又称过期字典。
typedef struct redisDb{ …… dict *expires; …… }redisDb;其key是一个指针,指向某个键。 value是个long long型整数,保存键的过期时间,一个毫秒级的UNIX时间戳。
在实际中,键空间的键和过期字典的键都指向同一个键对象,所以不会出现任何重复对象,也不会浪费任何空间。
设置了过期时间的key会被放到一个独立字典(expires字典)中。
策略含义优点缺点定时删除设置过期时间时,创建一个定时器, 到点删除保证内存被尽快释放若过期key很多,删除会占用很多的CPU时间,且创建定时器耗时,严重影响性能惰性删除客户端访问key时,redis检查其过期时间,过期则删除对CPU时间占用较少若大量的key在超出超时时间,而一直没有访问,可能会内存泄漏,大量无用垃圾占用内存定期删除每隔一段时间执行一次删除限制删除操作的时长和频率,减少删除操作对CPU时间的占用–处理内存友好方面,不如"定时删除",CPU时间友好方面,不如"惰性删除"redis中哪个版本开始可以对分布式使用;
其内容就是普通字符串,即byte数组。 可以用get/set直接操作整个位图的内容(即字符串),也就是把它当成一个字符串。 也可以用getbit/setbit操作每个位,也就是把它当做数组看。 位数组是自动扩容的,当设置的偏移量超出了当前数组大小,会自动进行0扩容。 每个数据存储只占用1位(bit,8位等于一个字节)。
签了是1,没签是0;
指定字符串偏移的位的值。 当key不存在时,会自动生成一个新的字符串值,字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内) 。对于大偏移量的setbit 操作申请内存会花费一定的时间。
获取指定偏移量上的位 ,当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
应用:字符串大小写转换
127.0.0.1:6379> set character A # 65 0100 0001 OK 127.0.0.1:6379> get character "A" 127.0.0.1:6379> setbit character 2 1 (integer) 0 127.0.0.1:6379> getbit character 2 (integer) 1 127.0.0.1:6379> get character # 97 0110 0001 "a"start、end都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。 统计指定位置范围内 1 的个数。
127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitcount w (integer) 21 127.0.0.1:6379> bitcount w 0 0 # 第一个字符中 1 的位数 (integer) 3 127.0.0.1:6379> bitcount w 0 1 # 前两个字符中 1 的位数 (integer) 7有符号数:指获取的位数组中第一个位是符号位,剩下的才是值。如果第一位是1,那就是负数。 无符号数:表示非负数,没有符号位,获取的位数组全部都是值。 有符号数最多可以获取 64 位,无符号数只能获取 63 位 (因为 Redis 协议中的 integer 是有符号数,最大 64 位,不能传递 64 位无符号值)。如果超出位数限制,Redis 就会告诉你参数错误。
应用:用户上线次数统计 用户名作为key,上线日期作为offset,置为1,表示那天上线了,bitcount 用户名,就可以得到总的上线天数。 性能优化 1)如果bitmap数据量非常大,可以把大的bitmap分散到不同的key中,作为小的bitmap来处理。可以使用Lua来完成这个操作; 2) 使用 BITCOUNT key start end,每次只对所需的部分进行统计。
查找指定范围内出现的第一个 0 或 1。
127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitcount w (integer) 21 127.0.0.1:6379> bitpos w 0 # 第一个 0 位 (integer) 0 127.0.0.1:6379> bitpos w 1 # 第一个 1 位 (integer) 1 127.0.0.1:6379> bitpos w 1 1 1 # 从第二个字符算起,第一个 1 位 (integer) 9 127.0.0.1:6379> bitpos w 1 2 2 # 从第三个字符算起,第一个 1 位 (integer) 17对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 支持 AND 、 OR 、 NOT 、 XOR 这四种操作,除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
127.0.0.1:6379> set str1 "foobar" OK 127.0.0.1:6379> set str2 "abcdef" OK 127.0.0.1:6379> bitop and str1 str1 str2 (integer) 6 127.0.0.1:6379> get str1 "`bc`ab" 127.0.0.1:6379> 操作012345值A0110 0110 (f 102)0110 1111 (o 111)0110 1111 (o 111)0110 0010(b 98)0110 0001 (a 97)0111 0010 (r 114)值B0110 0001 (a 97)0110 0010(b 98)0110 0011(c 99)0110 0100(d 100)0110 0101(e 101)0110 0110(f 102)and0110 0000( ` 96)0110 0010(b 98)0110 0011(c 99)0110 0000( ` 96)0110 0001 (a 97)0110 0010(b 98)应用 统计近一周连续活跃用户数 key为周几,offset为用户唯一偏移量,上线的话值为1
sitop and res mon tues ... sun获得的结果res中,第几位为1,就表示该用户唯一偏移量所对应的用户,连续一周活跃。
有三个子指令,分别是get、set、incrby。都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令。 ①get
127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w get u4 0 # 从第一个位开始取 4 个位,结果是无符号数 (u) (integer) 6 127.0.0.1:6379> bitfield w get u3 2 # 从第三个位开始取 3 个位,结果是无符号数 (u) (integer) 5 127.0.0.1:6379> bitfield w get i4 0 # 从第一个位开始取 4 个位,结果是有符号数 (i) 1) (integer) 6 127.0.0.1:6379> bitfield w get i3 2 # 从第三个位开始取 3 个位,结果是有符号数 (i) 1) (integer) -3 127.0.0.1:6379> 127.0.0.1:6379> bitfield w get u4 0 get u3 2 get i4 0 get i3 2 1) (integer) 6 2) (integer) 5 3) (integer) 6 4) (integer) -3②set
127.0.0.1:6379> bitfield w set u8 8 97 # 从第 8 个位开始,将接下来的 8 个位用无符号数 97 替换 1) (integer) 101 127.0.0.1:6379> get w "hallo"③incrby 对指定范围的位进行自增操作。
127.0.0.1:6379> set w hello OK 127.0.0.1:6379> bitfield w incrby u4 2 1 # 从第三个位开始,对接下来的 4 位无符号数 +1 1) (integer) 11 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 12 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 13 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 14 127.0.0.1:6379> bitfield w incrby u4 2 1 1) (integer) 15 127.0.0.1:6379> bitfield w incrby u4 2 1 # 溢出折返了 1) (integer) 0redis数据库底层实现,CRUD也是构建在对字典的操作之上。 字典底层是hash表。
系统在10:05 设置一个值,并给出5分钟的过期时间,系统刚刚set完之后redis集群崩溃,10:11分系统重启成功,那么redis中set的值是否还存在? 无论是设置了过期时间还是生存时间,都会被转化成一个毫秒级的时间戳,当前时刻晚于该时间戳,所以服务器会主动删除,就不存在了
2.8之前是非原子操作,可能造成死锁 setnx expire 原子操作: set name liu ex 5 nx
brpop、blpop阻塞 注意空闲链表的问题:长时间闲置,服务器会主动断开连接,抛出异常。
Redis v2.8.9 是种结构,用来做基数统计的算法,当输入元素数量或体积很大时,计算基数所需空间总是固定、且很小的。每个HyperLogLog键计算将近264个不同元素的基数需要12KB内存,不像集合计算基数的元素越多越耗费内存。 因为只根据输入元素来计算基数,不存储元素本身。 不精确的技术方案,标准误差0.81%。 基数估计就是在误差可接受的范围内,快速计算基数。
(1)PFADD key element [element …] 添加指定元素到 HyperLogLog 中。 (2)PFCOUNT key [key …] 返回给定 HyperLogLog 的基数估算值。 (3)PFMERGE destkey sourcekey [sourcekey …] 将多个 HyperLogLog 合并为一个 HyperLogLog。
一次执行多个命令。 三个保证: (1)批量操作在EXEC命令前放入队列缓存; (2)收到EXEC命令后开始执行事务,事务是原子操作,要么全部执行,要么全部不执行; (3)单独的隔离操作,一个事务中的所有命令都会被序列化的按顺序执行,事务执行过程中,其他客户端提交命令不会插入事务执行命令序列中。 开始事务→命令入列→执行事务。
取消事务,放弃执行事务块内的所有命令。
执行所有事务块内的命令。
标记一个事务块的开始。
取消 WATCH 命令对所有 key 的监视。
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
客户端发送 MULTI 命令,服务器执行 MULTI 命令逻辑。 服务器会在客户端状态(redisClient)的 flags 属性打开 REDIS_MULTI 标识,将客户端从非事务状态切换到事务状态。
用户在客户端输入多条此事务要执行的命令,客户端切换到事务状态,服务器端根据输入的命令执行动作。若收到EXEC、DISCARD、WATCH、MULTI中任一一个命令,服务器都会立即执行,其他命令都不会被立刻执行。 首先检查命令格式是否正确,若不正确,服务器会在客户端状态(redisClient)的flags属性中打开REDIS_DIRTY_EXEC 标识,并返回错误信息给客户端。如果正确就把命令放入事务队列中,返回给客户端QUEUED
事务队列是如何执行的? 每个 Redis 客户端都有自己的事务状态,对应的是客户端状态(redisClient)的 mstate 属性。 事务状态(mstate)包含一个事务队列(FIFO 队列),以及一个已入队命令的计数器。 事务队列是一个 multiCmd 类型数组,数组中每个 multiCmd 结构都保存了一个如入队命令的相关信息:指向命令实现函数的指针,命令的参数,以及参数的数量。
客户端发送 EXEC 命令,服务器执行 EXEC 命令逻辑。
如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识,或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识,那么就直接取消事务的执行。
否则客户端处于事务状态(flags 有 REDIS_MULTI 标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端;
隔离性的串行化——当前执行事务不被其他事务打断。
redis的事务到底支不支持原子性? 不支持回滚,在事务运行期间,Redis命令可能会执行失败,但Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作。虽然不支持回滚,但是在执行前会检查事务中的所有命令有没有错误,但是不会去检查书写中的逻辑错误,例如对 String 类型的数据库键执行对 HashMap 类型的操作。 只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。 Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。 对于Redis事务的这种行为,有人认为程序有可能会有bug。但是我们要知道:事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。 请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。 我们可以认为redis是支持原子性的。
执行事务通常跟pipeline配合使用,将多次IO压缩为单次IO操作。 pipe = redis.pipeline(transaction = true) pipe.multi() pipe.incr(“books”) pipe.incr(“books”) values = pipe.execute()
watch 用于监视任意数量的数据库键,并在EXEC命令执行时,检测被监视的键是否被修改,如果被修改了,服务器将拒绝执行事务,并向客户端返回空(nil)回复。 1)监视数据库键 redis数据库有个字典——watched_keys,其键是某个被WATCH命令监视的数据库键,其值是一个链表,记录了所有监视相关数据库键的客户端。 2)监视机制的触发 上面已经说了监视的存储,那要怎么触发监视?所有对数据库修改的命令都可以触发,比如set、lpush、sadd等,执行后都会对watched_keys字典进行检查,如果有客户端在监视这个在修改的键,那么这个客户端的REDIS_DIRTY_CAS就会被打开,说明事务安全性被破坏了。 3)判断事务是否安全 当服务器收到客户端的EXEC时,会根据客户端的REDIS_DIRTY_CAS是否打开了,来决定要不要执行事务。
Redis DataBase,快照,全量 在指定时间间隔(保存点)内将内存数据集快照写入磁盘。 保存快照时,调用OS的cow(copy on write),fork一个子进程来copy,主线程不受影响。 当主线程有修改,会复制一个4k的分页,该操作对子进程透明,也就是说子进程复制的还是修改前的内容,所以说数据可能丢失。 写入时是先写入临时文件,成功后再替换旧文件,二进制压缩存储。 方便移植,恢复比AOF快。
Append Only File,增量,仅附加文件 日志,增删改都记录,查询不记录,以文本方式记录,是一点点持久化的,不是一下子记录所有数据。 保存指令流,收到指令都先保存再执行。但随着时间增长,AOF越来越大,而redis单线程,就会导致恢复时特别慢。 耐久 设置fsync策略,每秒一次fsync,或每次执行写入命令时fsync。 fsync在后台线程执行,主线程不受影响。
4.0开始支持该模式,RDB开启时,进行AOF操作。
一种消息通信模式,客户端可订阅任意数量的频道(channel)
当有消息通过PUBLISH命令发给频道(channel)时,消息会发给订阅它的所有客户端(client)。
创建一个订阅频道redisChat
redis 127.0.0.1:6379> SUBSCRIBE redisChat Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "redisChat" 3) (integer) 1在同一个频道 redisChat 发布两次消息
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique" (integer) 1 redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by w3cschool.cc" (integer) 1 # 订阅者的客户端会显示如下消息 1) "message" 2) "redisChat" 3) "Redis is a great caching technique" 1) "message" 2) "redisChat" 3) "Learn redis by w3cschool.cc"(1)PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道。 (2)PUBSUB subcommand [argument [argument …]] 查看订阅与发布系统状态。 (3)PUBLISH channel message 将信息发送到指定的频道。 (4)PUNSUBSCRIBE [pattern [pattern …]] 退订所有给定模式的频道。 (5)SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。 (6)UNSUBSCRIBE [channel [channel …]] 指退订给定的频道。
用Lua解释器执行脚本。
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...] redis 127.0.0.1:6379> redis 127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"(1)EVAL script numkeys key [key …] arg [arg …] 执行 Lua 脚本。 (2)EVALSHA sha1 numkeys key [key …] arg [arg …] 执行 Lua 脚本。 (3)SCRIPT EXISTS script [script …] 查看指定的脚本是否已经被保存在缓存当中。 (4)SCRIPT FLUSH 从脚本缓存中移除所有脚本。 (5)SCRIPT KILL 杀死当前正在运行的 Lua 脚本。 (6)SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
用于连接redis服务。
验证密码是否正确
打印字符串
查看服务是否运行
关闭当前连接
切换到指定的数据库
将一批命令一次性传到Redis,减少了网络的开销。
redis-benchmark
(1)BGREWRITEAOF 异步执行一个 AOF(AppendOnly File) 文件重写操作 (2)BGSAVE 在后台异步保存当前数据库的数据到磁盘 (3)CLIENT KILL [ip:port] [ID client-id] 关闭客户端连接 (4)CLIENT LIST 获取连接到服务器的客户端连接列表 (5)CLIENT GETNAME 获取连接的名称 (6)CLIENT PAUSE timeout 在指定时间内终止运行来自客户端的命令 (7)CLIENT SETNAME connection-name 设置当前连接的名称 (8)CLUSTER SLOTS 获取集群节点的映射数组 (9)COMMAND 获取 Redis 命令详情数组 (10)COMMAND COUNT 获取 Redis 命令总数 (11)COMMAND GETKEYS 获取给定命令的所有键 (12)TIME 返回当前服务器时间 (13)COMMAND INFO command-name [command-name …] 获取指定 Redis 命令描述的数组 (14)CONFIG GET parameter 获取指定配置参数的值 (15)CONFIG REWRITE 对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写 (16)CONFIG SET parameter value 修改 redis 配置参数,无需重启 (17)CONFIG RESETSTAT 重置 INFO 命令中的某些统计数据 (18)DBSIZE 返回当前数据库的 key 的数量 (19)DEBUG OBJECT key 获取 key 的调试信息 (20)DEBUG SEGFAULT 让 Redis 服务崩溃 (21)FLUSHALL 删除所有数据库的所有key (22)FLUSHDB 删除当前数据库的所有key (23)INFO [section] 获取 Redis 服务器的各种信息和统计数值 (24)LASTSAVE 返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示 (25)MONITOR 实时打印出 Redis 服务器接收到的命令,调试用 (26)ROLE 返回主从实例所属的角色 (27)SAVE 异步保存数据到硬盘 (28)SHUTDOWN [NOSAVE] [SAVE] 异步保存数据到硬盘,并关闭服务器 (29)SLAVEOF host port 将当前服务器转变为指定服务器的从属服务器(slave server) (30)SLOWLOG subcommand [argument] 管理 redis 的慢日志 (31)SYNC 用于复制功能(replication)的内部命令。
1、备份(save/bgsave)
redis 127.0.0.1:6379> SAVE OK redis 127.0.0.1:6379> redis 127.0.0.1:6379> BGSAVE Background saving started将在 redis 安装目录中创建dump.rdb文件。 2、恢复 把备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。
获取redis安装目录
redis 127.0.0.1:6379> CONFIG GET dir 1) "dir" 2) "/usr/local/redis/bin"服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。
Jedis、Redisson、RedisTemplate
Jedis jedis = new Jedis("localhost"); System.out.println("Server is running: "+jedis.ping()); jedis.set("w3ckey", "Redis tutorial"); jedis.lpush("tutorial-list", "Redis"); jedis.lpush("tutorial-list", "Mongodb"); List<String> list = jedis.lrange("tutorial-list", 0 ,5);