Resis入门
- Redis简介
- Redis基本数据类型
- String
- List
- Hash
- set
- zset
- Go操作redis
- Redis当中的pipline
Redis简介
Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。与MySQL数据库不同的是Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
Redis基本数据类型
即以下五种基本数据类型:
- String(字符串)
- List (列表)
- Hash(哈西散列)
- Set(集合)
- Zset(有序集合)
下面我们详细介绍一下Redis的这五种数据类型
String
String 是 Redis 最基本的数据类型。字符串是一组字节,在 Redis 数据库中,字符串具有二进制安全(binary safe)特性,这意味着它的长度是已知的,不由任何其他终止字符决定的,一个字符串类型的值最多能够存储 512 MB 的内容。下面解释一下什么叫做这个二进制安全特性:
所谓的二进制安全特性是指:在C语言当中字符串遇到**'\0’就会截断而在Redis当中的String是安全字符串可以包含这个特殊字符**不会被特殊字符给隔断。这一点是需要注意的。
在Redis当中这个String的数据结构大概是这样的:
struct sdshdr{//记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度int len;//记录 buf 数组中未使用的字符数量int free;//字符数组,用于保存字符串char buf[];}
其大概是怎么存储的如上图所示,当然其内部实际存储根据string的数据特征可以采用如下三种方式进行存储:
- int
- embstr
- raw
下面我们来演示一下这个string在数据特征的不同的情况下其存储方式的不同,在这里博主选择在Centos7的云服务器上做这个实验各位铁子可以自行选择。
[root@VM-4-17-centos redis]# redis-cli
127.0.0.1:6379> set key 123
OK
127.0.0.1:6379> object encoding key
"int"
127.0.0.1:6379>
在这里解释一下我们可以使用object encoding 查看具体的存储类型。
下面我们将value换成不是整数我们再次使用这个object encoding 再次查看这个其存储类型。
127.0.0.1:6379> set ksy jjjdkla
OK
127.0.0.1:6379> object encoding ksy
"embstr"
127.0.0.1:6379>
此时我们发现他的存储类型变成了这个embstr也就是当字符串长度小于44个字节时存储类型采用这个embstr.当这个字符串的长度比较长是存储类型将会变为raw.下面我们来演示一下。天上飞的理论总要有落地的实现。
127.0.0.1:6379> set ksy jddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
OK
127.0.0.1:6379> objecct encoding ksy
(error) ERR unknown command 'objecct', with args beginning with: 'encoding' 'ksy'
127.0.0.1:6379> object encoding ksy;
(nil)
127.0.0.1:6379> get ksy
"jddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
127.0.0.1:6379> object encoding ksy
"raw"
我们发现这个他的存储类型变成了这个raw.
为什么redis当中的String要这样来设计了,个人认为有一下几个原因:
- 字符串长度处理:Redis获取字符串长度,时间复杂度为O(1),而C语言中,需要从头开始遍历,复杂度为O(n).
- 空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。
- 惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。
- 二进制安全:Redis可以存储一些二进制数据,在C语言中字符串遇到’\0’会结束,而 SDS中标志字符串结束的是len属性。
下面我们一起来学习一下这个String一共有那些常用的基本命令,下面我们一个一个的来介绍一下。
String常用的命令主要有上面这些下面我们一起来介绍一下如何使用这些命令。
命令 | 功能 | 用法 |
---|---|---|
set | 设置指定 key 的值。 | set key val |
get | 获取指定 key 的值。 | get key val |
del | 删除指定的key | del key |
strlen | 获取字符串的长度 | strlen key |
append | 在指定字符串后面追加指定字符串 | append key val |
Incr | 将key 中储存的数字值增一注意一定要是数字 | incr key |
decr | 将key当中的数字减一同样的也只能是数字 | decr key |
incrby | 将 key 所储存的值加上给定的增量值 | incrby key increment |
decrby | 将 key 所储存的值减去给定的减量值 | decrby key decrement |
getrange | 返回 key 中字符串值的子字符 | getrange key start end |
setrange | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 | setrange key offset val |
setnx | 只有在 key 不存在时设置 key 的值否则什么也不做。 | setnx key val |
setex | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 | set key seconds value |
mset | 同时设置一个或多个 key-value 对 | mset key value [key value …] |
mget | 获取所有(一个或多个)给定 key 的值。 | mget key1 [key2…] |
msetnx | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 | msetnx key value [key value …] |
铁子们注意了上面表格当中的这个[…]代表的是可以有也可以没有的意思。下面我们一起来联系一下这些命令吧我们首先从set和这个get系列联系开始
首先是最基本的set 和get
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379>
如果我们想要一次性设置多个我们可以使用mset,来进行设置这样就方便一点
127.0.0.1:6379> mset k2 v2 k3 v3
OK
127.0.0.1:6379> mget k2 k3
1) "v2"
2) "v3"
127.0.0.1:6379> strlen k2
(integer) 2
127.0.0.1:6379>
在mget的时候我们顺便把这个字符串的长度也给拿到了,下面我们来看看这个整数的增加和减少
127.0.0.1:6379> set int 1
OK
127.0.0.1:6379> incr int
(integer) 2
127.0.0.1:6379> get int
"2"
127.0.0.1:6379> incrby int -2
(integer) 0
127.0.0.1:6379> get int
"0"
注意这个incrby的这个增量可以为负数也就变成了这个decrby的功能了。decrby也是类似的在这里就不一个一个演示了,铁子们可以自行下来进行实验。
下面我们再来看看这个setnx,和这个setex这两个的功能如何
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> setnx k1 v2
(integer) 0
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> setnx k10 v10
(integer) 1
127.0.0.1:6379> get k10
"v10"
其功能和这个描述的是一致的,如果key已经存在那么就什么都不做如果不存在那么就设置。
127.0.0.1:6379> setex k11 6 v11
OK
127.0.0.1:6379> get k11
"v11"
127.0.0.1:6379> get k11
(nil)
这个我们发现刚刚设置的时候我们是能够取到的但是这个6秒过后我们再去取的时候此时他已经不存在了。
我们在看一下这个setrange 和这个getrange
127.0.0.1:6379> set ksy "ksy nihao"
OK
127.0.0.1:6379> getrange ksy 0 1
"ks"
127.0.0.1:6379> getrange ksy 0 -1
"ksy nihao"
127.0.0.1:6379> setrange ksy 0 1
(integer) 9
127.0.0.1:6379> get ksy
"1sy nihao"
我们发现这个getrange有点像我们语言当中的substr的意思,setrange 是从字符串的莫个小标开始将val值依次覆盖掉原来的。如果现在的比原来的要长那么就全部覆盖掉。
最后一个msetnx我们继续看一下哈。有老铁说这个太容易了存在就什么都不干,不存在就设置so easy
但是如果一部分存在一部分又不存在了会是什么情况
127.0.0.1:6379> msetnx k1 v1 k20 v20
(integer) 0
127.0.0.1:6379> get k29
(nil)
127.0.0.1:6379> get k20
(nil)
我们发现这个都失败了,并没有设置成功这点是需要注意的地方哈哈哈。最后我们在这个redis当中的这个String有那些应用了首先主要有一下几个用途。
对象存储
set role:1010 'json串'get role:1010
由于这个json串太长了,所以在这里博主直接用这个json串来代替了。
累加器
#统计阅读数量1
incr reads
#累加100
分布式锁
#加锁
setnx lock 1
del lock
#释放锁
List
简介:
- List 列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。
- 简单实用举例:lpush key value [value …] 、lrange key start end
- 内部编码:ziplist(压缩列表)、linkedlist(链表)
- 应用场景:消息队列,文章列表,
在redis当中的列表每一个节点都有指向前一个节点和后一个节点的指针。
头节点和尾节点的prev和next指针指向为null,所以链表是无环的。链表有自己长度的信息,获取长度的时间复杂度为O(1)。也就是我们在学习数据结构时的这个双向循环列表
在这里需要注意的是列表当中的元素可能会被压缩:
- 当元素长度小于48字节不会压缩
- 元素压缩前后长度差不超过8,不压缩
如果有对双向循环列表有兴趣的同学可以这个看看博主之前的博客。下面我们一起看看他一共有那些常用命令了。
命令 | 功能 | 用法 |
---|---|---|
lpush | 将一个或多个值插入到列表头部 | lpush key value1 [value2] |
rpush | 将一个或多个值插入到列表尾部 | rpush key value1 [value2] |
lpop | 移除列表的头部元素,返回值为移除的元素 | lpop key |
rpop | 移除列表的最后一个元素,返回值为移除的元素 | rpop key |
brpop | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 | brpop key1 [key2 ] timeout |
blpop | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 | blpop key1[key2] timeout |
lrem | 移除列表元素 | lrem count val |
lindex | 通过索引获取列表中的元素 | lindex key val |
ltrim | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 | ltrim key start stop |
llen | 获取列表长度 | llen key |
linsert | 在指定元素前面后面插入一个元素 | linsert key before or after val |
lset | 通过索引设置列表元素的值 | lset key index val |
下面我们一个一个的来操作这些命令,首先从这个lpush这些开始
127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> lpop list
"3"
我们发现这个lpush有点类似于这个头插的意思。lpop就是取双向循环列表这个头部的元素,在这里解释一下这个lrange list 0 -1.在这里这个0到-1代表是全部获取的意思。rpush和rpop和这个lpush类似在这里就不演示了。
下面我们来看看这个lrem和这个llen
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "1"
3) "2"
4) "1"
127.0.0.1:6379> lrem list 2 1
(integer) 2
127.0.0.1:6379> llen list
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
llen是获取这个列表当中有几元素,而lrem是指将删除指定个数的元素已上面的为列就是删除列表当中 2 个1.随便说一下这个linde就是获取这个链表当中指定下标的元素
127.0.0.1:6379> lindex list 0
"2"
最后我们来看看这个lset,linsert吧
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> linsert list before 2 1
(integer) 3
127.0.0.1:6379> linsert list after 2 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "4"
4) "1"
127.0.0.1:6379> lset list 0 9
OK
127.0.0.1:6379> lrange list 0 -1
1) "9"
2) "2"
3) "4"
4) "1"
linsert 可以指定在某个元素前面或者后面插入元素。lset可以将列表当中这个指定下标的元素设置为你想要的值。
下面我们重点介绍一下这个brpop这个应该是这里面最难的一个命令Redis Brpop 命令移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。现在我们使用del将list这个key删除掉。我们使用brpop命令
直到我们往这个list当中写入数据才没有立即阻塞住这是我们需要注意的。
List的数据编码
如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码.
List基本应用我们谈一下:
第一个可以用来做栈
lpush+lpop
#或者
rpush+lpop
第二个用来做完队列
lpush+rpop#或者rpush+lpop
第二个用来做这个阻塞队列
lpush+brpop
#或者
rpush+brpop
Hash
其实哈西和字符串是有点像的。
Hash(哈希)
- 简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
- 简单使用举例:hset key field value 、hget key field
- 内部编码:ziplist(压缩列表) 、hashtable(哈希表)
- 应用场景:缓存用户信息等。
- 注意点:如果开发使用hgetall,哈希元素比较多的话,可能导致Redis阻 塞,可以使用hscan。而如果只是获取部分field,建议使用hmget。
同样的如果对哈西刚兴趣的铁子可以看看博主的博客,在这里把博客连接放到下面
哈西博客
下面我们来看看Hash一共有那些命令吧
命令 | 功能 | 用法 |
---|---|---|
hset | 将哈希表 key 中的字段 field 的值设为 value | hset key field value |
hget | 获取存储在哈希表中指定字段的值。 | hget key field |
hmset | 同时将多个 field-value (域-值)对设置到哈希表 key 中 | hmset key field1 value1 [field2 value2 ] |
hmget | 获取一个或者多个给定字段的值 | hmege key fileld1[fileld2] |
hlen | 获取哈希表中字段的数量 | hlen key |
hexists | 查看哈希表 key 中,指定的字段是否存在 | hexists key filed |
hincrby | 为哈希表 key 中的指定字段的整数值加上增量 increment | hincrby key field increment |
hsetnx | 只有在字段 field 不存在时,设置哈希表字段的值 | hsetnx key field value |
我们会发现和这个string,list非常的像。下面我们简单的演示一下吧
127.0.0.1:6379> del user
(integer) 1
127.0.0.1:6379> clear
127.0.0.1:6379> hset user id 11
(integer) 1
127.0.0.1:6379> hset user name ksy
(integer) 1
127.0.0.1:6379> hget user id
"11"
非常的好理解redis的KV模式不变,只不过这个V是一个键值对。如果我们想要一次插入多个键值对我们可以使用这个mset,获取就用mget。
127.0.0.1:6379> hmset user id 22 name lyz age 10
OK
127.0.0.1:6379> hmget user id name age
1) "22"
2) "lyz"
3) "10"
127.0.0.1:6379> hgetall user
1) "id"
2) "22"
3) "name"
4) "lyz"
5) "age"
6) "10"
和这个String 和List非常的相似。如果我们想要这个将这个user当中的id字段删除掉我们就可以使用这个hdel
127.0.0.1:6379> hdel user id
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "lyz"
3) "age"
4) "10"
下面再演示一下这个hlen和这个hexist这两个的用法
127.0.0.1:6379> hlen user
(integer) 2
127.0.0.1:6379> hgetall user
1) "name"
2) "lyz"
3) "age"
4) "10"
127.0.0.1:6379> hexists user name
(integer) 1
hexists 是看的哈西当中是否有这个key,hlen是判断哈西当中有几个元素。
然后就是hkeys,hvals
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
127.0.0.1:6379> hvals user
1) "lyz"
2) "10"
非常的简单就是获取这个哈西当中的key和val的值。这个hincrby就是当哈西的val是整数时可以将其自增非常的简单再这里就不演示了。
hsetnx就是当哈西当中某个key存在就什么都不做否则就设置
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
127.0.0.1:6379> hsetnx user name denghonglin
(integer) 0
127.0.0.1:6379> hget user name
"lyz"
最后再补充一点
哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
其应用主要可以用来这个存储对象,购物车之类的,再这里暂时跳过。
set
- 简介:集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素
- 简单使用举例:sadd key element [element …]、smembers key
- 内部编码:intset(整数集合)、hashtable(哈希表)
- 注意点:smembers和lrange、hgetall都属于比较重的命令,如果元素
- 存在阻塞Redis的可能性,可以使用sscan来完成。
- 应用场景:用户标签,生成随机数抽奖、社交需求。
命令 | 功能 | 用法 |
---|---|---|
sadd | 向集合添加一个或多个成员 | sadd key member1 [member2] |
smembers | 返回集合中的所有成员 | smembers key |
sismember | 判断 member 元素是否是集合 key 的成员 | sismember key member |
scard | 获取集合当中元素的个数 | scard key |
srem | 移除集合中一个或多个成员 | srem key1 [key2] |
spop | 移除并返回集合中的一个随机元素 | spop key |
srandmember | 返回集合中一个或多个随机数 | srandmember key [count] |
下面我们一起来使用一下这个集合当中的命令吧其实很简单和这个List的命令差不多
127.0.0.1:6379> sadd sete01 1 2 3 2 1 4
(integer) 4
127.0.0.1:6379> smembers sete01
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sismember sete01 1
(integer) 1
我们发现这个集合是去重的,重复的元素只会出现一次因此也可以用来进行这个去重。sadd往这个集合当中添加元素,smembers 获取集合当中的元素。sismember 查看某个元素是否再这个集合当中。
127.0.0.1:6379> scard sete01
(integer) 4
127.0.0.1:6379> srem sete01 1
(integer) 1
127.0.0.1:6379> smembers sete01
1) "2"
2) "3"
3) "4"
scard获取集合当中元素的个数,srem 删除这个集合当中的某个元素。
127.0.0.1:6379> spop sete01
"2"
127.0.0.1:6379> srandmember sete01
"3"
127.0.0.1:6379> srandmember sete01 2
1) "3"
2) "4"
spop 从集合当中随机弹出一个元素,而这个srandmember 可以指定随机弹出几个如果不指定默认弹出一个。
最后补充一下这个交并集,非常的简单在这里就直接给出
sdiff #差集
sinter #交集
sunion #并集
zset
- 简介:已排序的字符串集合,同时元素不能重复
- 简单格式举例:zadd key score member [score member …],zrank key member
- 底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
- 应用场景:排行榜,社交需求(如用户点赞)。
如果铁子对这个跳表有兴趣的话可以看看我的博客,在这里把链接给出有兴趣的可以看看博主跳表的博客
跳表详解
下面我们看看他有那些命令
命令 | 功能 | 用法 |
---|---|---|
zadd | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 | zadd score1 member1 [score2 member2] |
zrange | 通过索引区间返回有序集合指定区间内的成员 | zrange key start stop [withscores] |
zrangbyscore | 通过分数返回有序集合指定区间内的成员 | zrangbyscore key min max [withscores] [limit] |
zcount | 计算在有序集合中指定区间分数的成员数 | zcount key min max |
zrank | 返回有序集合中指定成员的索引 | zrank key member |
zrem | 移除有序集合中的一个或多个成员 | zrem key member [member …] |
zrevrank | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 | zrevrank key member |
zrevrange | 返回有序集中指定区间内的成员,通过索引,分数从高到低 | zrevrange key start stop [withscores] |
zrevrangebyscore | 返回有序集中指定分数区间内的成员,分数从高到低排序 | zrevrangebyscore key max min [withscores] |
下面我们从这个zadd开始吧
127.0.0.1:6379> zadd zset 70 v1 60 v2 67 v3
(integer) 3
127.0.0.1:6379> zrange zset 0 -1
1) "v2"
2) "v3"
3) "v1"
我们发现这个zset是按照这个分数来进行排序的。或者我们用这样看的更加清除一点:
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "v2"
2) "60"
3) "v3"
4) "67"
5) "v1"
6) "70"
现在假设我们需要这个范围查找我们就可以使用这个zrangebyscore.下面我们想要查这个 60到68分之间的
127.0.0.1:6379> zrangebyscore zset 60 68 withscores
1) "v2"
2) "60"
3) "v3"
4) "67"
羡慕我们看看这个删除和统计zset当中的元素
127.0.0.1:6379> zrangebyscore zset 60 68 withscores
1) "v2"
2) "60"
3) "v3"
4) "67"
127.0.0.1:6379> zadd zset 90 v4 80 v5
(integer) 2
127.0.0.1:6379> zrem zset v5
(integer) 1
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "v2"
2) "60"
3) "v3"
4) "67"
5) "v1"
6) "70"
7) "v4"
8) "90"
127.0.0.1:6379> zcount zset 0 100
(integer) 4
127.0.0.1:6379> zcard zset
(integer) 4
127.0.0.1:6379> zrank zset v4
(integer) 3
127.0.0.1:6379> zscore zset v4
"90"
这个zrank 可以计算这个某个元素的排名,zcard ,zcount 统计个数。而上面这些这个 zrevrank,zrevrange等是将其反转,zset默认是升序的有时候了我们想要降序那么此时我们就可以使用这个zrevrange,zrevrank等,在这里就不演示了。
Go操作redis
首先我们要操作redis,在这里我们使用这个go语言来操作redis.首先安装好这个redisgo
go get -u github.com/garyburd/redigo/redis
package mainimport ("fmt""github.com/garyburd/redigo/redis""reflect"
)func main() {conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", "101.35.98.26", 6379))//注意tcp是面向字节流在tcp当中对应[]byte切片if err != nil {panic(err)}defer func() {fmt.Println(" redis connection close ")conn.Close()}()if false {conn.Do("set", "ksy", "hello world") //执行命令rpy, err := redis.String(conn.Do("get", "ksy")) //将redis.DO返回的interface转化为stringif err != nil {panic(err)}fmt.Println(rpy)}if false {conn.Do("set", "ksy", 1)rpy, err := redis.Int(conn.Do("get", "ksy"))if err != nil {panic(err)}fmt.Println(reflect.TypeOf(rpy))}if false {// conn.Do("lpush", "list", "mark", "darren", "king")//如果参数需要程序计算出来我们可以使用redis go 封装的接口args := redis.Args{}args = args.Add("list", "5", "6", "7", "8") //切片conn.Do("lpush", args...)rpy, _ := redis.Strings(conn.Do("lrange", "list", 0, -1))fmt.Println(rpy, reflect.TypeOf(rpy))}if false {conn.Do("del", "list")args := redis.Args{}args = args.Add("list", 23)value, _ := redis.Strings(conn.Do("brpop", args...))fmt.Println(value)}if false {conn.Do("del", "list")conn.Do("lpush", "list", "ksy", 200)values, _ := redis.Values(conn.Do("lrange", "list", 0, -1))var name stringvar score intredis.Scan(values, &score, &name) //注意list当中的lpush是头插注意顺序fmt.Println(name, score)}if true {var p1, p2 struct {Name string `redis :"name"`Age int `redis:"age"`Sex string `redis:"sex"`}p1.Age = 18p1.Name = "ksy"p1.Sex = "male"args1 := redis.Args{}.Add("role:10001").AddFlat(p1) //AddFlat是将结构体展开if _, err := conn.Do("hmset", args1...); err != nil {fmt.Println(err)return}m := map[string]string{"name": "lyz","age": "20","sex": "female",}args2 := redis.Args{}.Add("role:10002").AddFlat(m)if _, err := conn.Do("hmset", args2...); err != nil {fmt.Println(err)return}for _, id := range []string{"role:10001", "role:1002"} {v, err := redis.Values(conn.Do("hgetall", id))if err != nil {fmt.Println(err)return}if err := redis.ScanStruct(v, &p2); err != nil {fmt.Println(err)return}fmt.Printf("%#v\n", p2)}}}
在这里我们说一下这个redis.Args{}.我们可以使用他这个将多个参数放入到这个其中。还有就是如果我们需要将结构体传入进去我们可以使用这个AddFlat方法将结构体展开,使用这个scan方法将数据读取出来。
Redis当中的pipline
redis客户端执行一条命令分4个过程:
发送命令-〉命令排队-〉命令执行-〉返回结果
这个过程称为Round trip time(简称RTT, 往返时间),mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,这个时候需要pipeline来解决这个问题.下面我们来讨论一下这个
1、未使用pipeline执行N条命令
2、使用了pipeline执行N条命令
显然这个使用这个pipline的方式效率要更高.一次发送多条命令,减少这个和redis-server网络交互。
下面我们来介绍一下这个管道的使用技巧下面是一些伪代码的形式
//批量发送,批量接收c.Send(cmd1,...)c.Send(cmd2,...)c.Send(cmd3,...)c.Flush()//上面的Send只是将命令写入到这个缓存区当中并没有真正的发送c.Receive()//cmd1的返回值c.Receive()//cmd2的返回值c.Receive()//cmd3的返回值-----------------------------------//如果不关心这个返回值c.Send(cmd1,...)c.Send(cmd2,...)c.Send(cmd3,...)c.Do("")------------------------------//如果只关心这个最后一个命令的返回值c.Send(cmd1,...)c.Send(cmd2,...)c.Send(cmd3,...)c.Do(cmd3,...)
对应上面这三种情况我们分别使用go语言来进行操作一下
package mainimport ("fmt""github.com/garyburd/redigo/redis""math/rand""time"
)func main() {conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", "101.35.98.26", 6379))//注意tcp是面向字节流在tcp当中对应[]byte切片if err != nil {panic(err)}defer func() {fmt.Println(" redis connection close ")conn.Close()}()if false {conn.Send("del", "set", "list", "zset")conn.Send("sadd", "set", "mark", "darren", "king")conn.Send("lpush", "list", 1001, 1002, 1003)conn.Send("smembers", "set")conn.Send("lrange", "list", 0, -1)conn.Flush()conn.Receive() //delconn.Receive() //saddconn.Receive() //lpush//前面三个命令的结果我们不想要mbrs, err := redis.Strings(conn.Receive())if err != redis.ErrNil {fmt.Println(err, mbrs)}lsts, err := redis.Ints(conn.Receive())if err != redis.ErrNil {fmt.Println(err, lsts)}}if false {//并不需要知道他的返回值conn.Send("del", "set", "list", "zset")conn.Send("sadd", "set", "mark", "darren", "king")conn.Send("lpush", "list", 1001, 1002, 1003)conn.Do("")}if true {rand.Seed(time.Now().Unix())conn.Send("del", "set", "list", "zset")conn.Send("sadd", "set", "mark", "darren", "king"){args := redis.Args{}.Add("zset")args = args.Add(rand.Int()%100, "mark", rand.Int()%100, "darren", rand.Int()%100, "king")fmt.Println(args)conn.Send("zadd", args...)}{args := redis.Args{}.Add("zset")args = args.Add(0, -1, "withscores")fmt.Println(args)vals, err := redis.Values(conn.Do("zrange", args...))if err != nil {panic(err)}var rets []struct {Name stringScore int}if err = redis.ScanSlice(vals, &rets); err != nil {panic(err)}fmt.Println(rets)}}
}
非常的简单,基础篇就到这里为止,后面更新这个进阶篇