视频链接:https://www.bilibili.com/video/BV1Rv41177Af/?spm_id_from=333.999.0.0
1. redis概述
- 开源的nosql数据库,key-value存储系统
- 支持string、list、set、zset、hash数据类型。
- 所有操作都是原子性的
- 支持各种不同方式的排序
- redis数据既会存到内存,也会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
- redis能实现主从同步
- 端口6379:Alessia Merz
- 提供0-15个数据库,默认0号库
- 所有库密码相同
- 底层技术:单线程 多路io复用
- 多路复用是指使用一个线程来检查多个文件描述符(socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时,得到就绪状态后,进行真正的操作,可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
2. 应用场景
- 配合关系型数据库做高速缓存
- 高频次,热门访问的数据,降低数据库IO
- 分布式架构,做session共享
- 多样的数据结构存储持久化数据
- 最新N个数据:通过list实现按自然时间排序的数据
- 排行榜,topN:利用zset
- 时效性的数据,eg.手机验证码:expire过期时间
- 计数器,秒杀:原子性,自增方法incr,decr
- 去除大量数据中的重复数据:利用set集合
- 构建队列:利用list
- 发布订阅消息系统:pub/sub模式
3. 五大数据类型
- 常用命令:http://redis.cn/commands.html
3.1 操作
- redis键(key)
keys *:当前库所有key。O(N),其中N为数据库包含的键数量。
key pattern:查看当前库的key
scan cursor [MATCH pattern] [COUNT count] [TYPE type]:从数据库中获取一部分键,用户可以通过重复调用SCAN命令来迭代数据库包含的所有键。
SCAN命令、HSCAN命令、SSCAN命令和ZSCAN命令单次执行的复杂度为O(1)
使用这些命令进行一次完整迭代的复杂度则为O(N),其中N为被迭代的元素数量。
scan 0开始,返回下次迭代的游标 本次迭代得到的key
exists key [key ...]:判断key是否在当前库,返回存在key的个数。O(N),其中N为用户给定的键数量。
type key:查看key数据类型。O(1)。
del key [key ...]:删除key。O(N),其中N为被移除键的数量
unlink key [key ...]:非阻塞删除key。O(N),其中N为被移除键的数量
如果待移除的键非常庞大或者数量众多,那么用del命令在服务器执行移除操作的过程中就有可能被阻塞
UNLINK命令与DEL命令一样,都可以用于移除指定的键,但它与DEL命令的区别在于,当用户调用UNLINK命令去移除一个数据库键时,UNLINK只会在数据库中移除对该键的引用(reference),而对键的实际移除操作则会交给后台线程执行,因此UNLINK命令将不会造成服务器阻塞
expire key seconds:为key设置过期时间(单位:秒)。O(1)
pexpire key milliseconds:为key设置过期时间(单位:毫秒)。O(1)
expireat key timestamp:为key设置过期时间(指定过期时间戳-秒)。O(1)。
pexpireat key milliseconds-timestamp:为key设置过期时间(指定过期时间戳-毫秒)。O(1)。
ttl key:查看key的过期时间-秒。-1:永不过期, -2:已过期。O(1)
pttl key:查看key的过期时间-毫秒。-1:永不过期, -2:已过期。O(1)
randomkey:随机地返回一个键。O(1)
sort key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] :对列表元素、集合元素或者有序集合成员进行排序
平均复杂度:O(N*log(N) M),其中N为被排序元素的数量,而M则为命令返回的元素数量
ASC|DESC:升序/降序
ALPHA:字符串排序
LIMIT:只需要其中一部分排序结果。offset参数用于指定返回结果之前需要跳过的元素数量,而count参数则用于指定需要获取的元素数量。
rename key newkey:修改键的名称。如果用户指定的新键名已经被占用,那么RENAME命令会先移除占用了新键名的那个键,然后再执行改名操作。O(1)。
renamenx key newkey:只会在新键名尚未被占用的情况下进行改名,如果用户指定的新键名已经被占用,那么RENAMENX将放弃执行改名操作。O(1)。
move key db:将一个键从当前数据库移动至目标数据库。当目标数据库存在与给定键同名的键时,MOVE命令将放弃执行移动操作。O(1)
- 库操作
select index: 切换库。提供0-15个数据库,默认0号库。O(1)。
dbsize:查看当前库的key数量。O(1)。
flushdb [ASYNC]:清空当前库。O(N),其中N为被清空数据库包含的键值对数量
如果用户在调用FLUSHDB命令时使用了async选项,那么实际的数据库清空操作将放在后台线程中以异步方式进行,这样FLUSHDB命令就不会再阻塞服务器了。
flushall [ASYNC]:清空全部库。O(N),其中N为被清空的所有数据库包含的键值对总数量。
swapdb index1 index2:接受两个数据库号码作为输入,然后对指定的两个数据库进行互换,最后返回OK作为结果。O(1)。
因为互换数据库这一操作可以通过调整指向数据库的指针来实现,这个过程不需要移动数据库中的任何键值对,所以SWAPDB命令的复杂度是O(1)而不是O(N),
并且执行这个命令也不会导致服务器阻塞
- 数据结构(参考:https://zhuanlan.zhihu.com/p/372412604)
因为redis本身要求获取速度快,那么时间复杂度肯定是O(1),为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对
3.2 字符串(string)数据类型
3.2.1 简介
- string类型是二进制安全的,意味着redis的string可以包含任何数据,比如jpg图片或者序列化的对象。
- 一个字符串value最多可以是512M
- 底层数据结构(参考:https://blog.csdn.net/ymb615ymb/article/details/123370477)
- 自定义的数据结构SDS(simple dynamic string),并将 SDS 作为 Redis的默认字符串表示。
3.2.2 常用命令
代码语言:javascript复制set key value: 添加键值对。O(1)
set key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]
NX:当数据库中key不存在时,可以将key-value添加到数据库
XX:当数据库中key存在时,可以将key-value添加到数据库,和NX参数互斥。
EX:key的超时秒数
PX:key的超时毫秒数,和EX互斥
KEEPTTL: 加上后修改key,不改变key过期时间?
get key:查询键对应的值。O(1)
append key value:将给定的value追加到原值的末尾。O(N),其中N为新追加内容的长度。
strlen key:获取值的长度。O(1)
setnx key value:只有key不存在时,设置key的值
incr key:将key中储存的数字值加1。O(1)
decr key:将key中储存的数字值减1。O(1)
incrby key increment:将key中储存的值增加自定义步长。O(1)
decrby key increment:将key中储存的值减少自定义步长。O(1)
mset key value [key value ...]:同时设置多个key-value。O(N)
mget key [key ...]:同时获取一个或多个value。O(N)
msetnx key value [key value ...]:同时设置1个或多个key-value对,当前仅当所有给定的key都不存在。O(N)
getrange key start end:获得值的范围,闭区间。end可越界。O(N)
setrange key offset value:用value覆盖key所储存的字符串值,从起始位置开始(索引从0开始)。O(N)
setex key seconds value:设置键值的同时,设置过期时间(单位:秒)
getset key value:以旧换新,设置了新值的同时,获得旧值。O(1)
3.3 列表(List)数据类型
3.3.1 简介
- redis列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表头部或尾部。
- 底层是双向链表,对两端的操作性能很高,通过索引下标操作中间的节点性能会较差。
- 列表最多可存储 232 - 1元素 (4294967295, 每个列表可存储40多亿)。
- 底层数据结构
- list的数据结构为快速链表quickList。首先在列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist,也就是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续内存。当数据量比较多的时候,才会改成quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,比如列表里存的只是int类型的数据,结构上还要两个额外的指针prev和next
- Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
- quicklist是Redis 3.2中新引入的数据结构(参考:《Redis5设计与源码分析-陈雷等》)
- list-max-ziplist-size配置节点所占内存大小。实际上每个ziplist节点所占的内存会在该值上下浮动
- 考虑quicklistNode节点个数较多时,我们经常访问的是两端的数据,为了进一步节省空间,Redis允许对中间的quicklistNode节点进行压缩,通过修改参数list-compress-depth进行配置,即设置compress参数,该项的具体含义是两端各有compress个节点不压缩
3.3.2 常用命令
代码语言:javascript复制lpush/rpush key element [element ...]:从左边/右边插入一个或多个值。O(N),其中N为被推入列表的元素数量
lpushx/rpushx key element [element ...]:只会在列表已经存在的情况下,将元素推入列表左端。O(N),其中N为被推入列表的元素数量??
lpop/rpop key:从左边/右边吐出一个值。键在值在,键光值亡。O(1)
rpoplpush source destination:从source列表右边吐出一个值,插到destination的左边。O(1)
lrange key start stop:按照索引下标获得元素(从左到右, -1是最右边value索引)。O(N),其中N为给定列表的长度。
lindex key index:按照索引下标获得元素(从左到右)。O(N),其中N为给定列表的长度。
llen key:获得列表的长度。O(1)
linsert key BEFORE|AFTER pivot element: 在首次出现pivot值前/后插入element值。O(N),其中N为给定列表的长度。
lrem key count element:从左边删除count个element(从左到右),0-全部。O(N),其中N为给定列表的长度
lset key index element:将列表key下标为index的值替换成element。O(N),其中N为给定列表的长度。
ltrim key start stop:保留给定范围之内的元素,移除给定索引范围之外的所有元素。O(N),其中N为给定列表的长度
3.3.3 应用
- 参考:《redis使用手册-黄健宏》
- BLPOP、BRPOP和BRPOPLPUSH是阻塞版本的弹出和推入命令,如果用户给定的所有列表都为空,那么执行命令的客户端将被阻塞,直到给定的阻塞时限到达或者某个给定列表非空为止
3.4 集合(set)数据结构
3.4.1 简介
- set对外提供的功能和list类似,是一个列表的功能,特殊之处在于set可以自动排重。当需要存储一个列表数据,又不希望出现重复数据时,set是个很好的选择。同时,set提供了判断某个成员是否在一个set集合内的接口。
- set是string类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)
- 集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
- 底层数据结构
- 底层有两种实现方式:当value是整数值时,且数据量不大时使用inset来存储,其他情况都是用字典dict来存储
- intset(参考:https://blog.csdn.net/ymb615ymb/article/details/123371540)
typedf struct inset{
uint32_t encoding;//编码方式 默认 INSET_ENC_INT16
// 有三种,INTSET_ENC_INT16、INSET_ENC_INT32和INSET_ENC_INT64三种,分别对应不同的范围。
// Redis为了尽可能地节省内存,会根据插入数据的大小选择不一样的类型来进行存储。
uint32_t length;//集合元素个数
// 记录了保存数据的数组contents中共有多少个元素,这样获取个数的时间复杂度就是O(1)。
int8_t contents[];// 元素类型并不一定是ini8_t类型
//真正存储数据的地方,数组是按照从小到大有序排列的,并且不包含任何重复项
}
3.4.2 常用命令
代码语言:javascript复制sadd key member [member ...]: 将一个或多个member元素加入到集合key中,已经存在的member元素将被忽略。O(N),其中N为用户给定的元素数量。
smembers key:取出该集合的所有值。O(N),其中N为集合包含的元素数量。
sismember key value:判断集合key是否含有value值,1-有, 0-没有。O(1)
scard key:返回该集合的元素个数。O(1)
srem key member [member ...]:删除集合中的元素member。O(N),其中N为用户给定的元素数量。
spop key [count]:随机从集合中取出一个/count个值,并从集合中删除。O(N),其中N为被返回的元素数量。
srandmember key [count]: 随机从该集合中取出一个/count个值,不会从该集合中删除。O(N),其中N为被返回的元素数量。
smove source destination member:把member值从source集合移动到destination集合。O(1)
sinter key [key ...]:一个key时,查看集合key中的所有元素。多个key时,取多个集合的交集。O(N*M),其中N为给定集合的数量,而M则是所有给定集合当中,包含元素最少的那个集合的大小。
sinterstore destination key [key ...]:把给定集合的交集计算结果存储到指定的键里面。键已经存在的情况下自动覆盖已有的键。O(N*M),其中N为给定集合的数量,而M则是所有给定集合当中,包含元素最少的那个集合的大小。
sunion key [key ...]:一个key时,查看集合key中的所有元素。多个key时,取多个集合的并集。O(N),其中N为所有给定集合包含的元素数量总和。O(N),其中N为所有给定集合包含的元素数量总和
sunionstore destination key [key ...]:把给定集合的并集计算结果存储到指定的键中,并在键已经存在的情况下自动覆盖已有的键。O(N),其中N为所有给定集合包含的元素数量总和
sdiff key [key ...]:一个key时,查看集合key中的所有元素。多个key时,按顺序取集合的差集。O(N),其中N为所有给定集合包含的元素数量总和。
eg.sdiff key1 key2 key3:先计算key1和key2的差集,再计算key1和key2差集和key3的差集。
sdiffstore destination key [key ...]:把给定集合之间的差集计算结果存储到指定的键中,并在键已经存在的情况下自动覆盖已有的键。O(N),其中N为所有给定集合包含的元素数量总和。
3.5 哈希(hash)数据结构
3.5.1 简介
- redis的hash是一个键值对集合。redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 不需要重复存储数据,也不会带来序列化和并发修改控制的问题
- 每个 hash 可以存储 232 - 1键值对(40多亿)。
- 底层数据结构:hash类型对应的数据结构是两种,ziplist(压缩列表)和hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
3.5.2 常用命令
代码语言:javascript复制hset key field value [field value ...]:给key集合中的field键赋值value。O(N)??
hget key field:从key集合filed取出value。O(1)
hmset key field value [field value ...]:批量设置hash值。O(N),其中N为被设置的字段数量。
hmget key field [field ...]:批量获取field1,field2值。O(N),其中N为被设置的字段数量。
hexists key field:查看key中,field是否存在。O(1)
hkeys key:列出key中所有field。O(N),其中N为散列包含的字段数量
hvals key:列出key中所有value。O(N),其中N为散列包含的字段数量
hincrby key field increment:为hash表key中的field值加上增量。O(1)
hincrbyfloat key field increment:为hash表key中的field值加上浮点增量。O(1)
hsetnx key field value:将hash表key中的field设为value,当且仅当field不存在。O(1)
hgetall key:查看key所有field和value。O(N),其中N为散列包含的字段数量
hstrlen key field:获取给定字段值的字节长度。O(1)
hdel key field [field ...]:删除散列中的指定字段及其相关联的值。O(N),其中N为被设置的字段数量。??
hlen key:获取给定散列包含的字段数量。O(1)。
3.6 有序集合(zset)数据结构
3.6.1 简介
- 有序集合和set非常相似,是一个没有重复元素的字符串集合。
- 不同之处在于有序集合的每个成员都关联了一个评分(score),这个评分被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分是可以重复的。
- 因为元素是有序的,所以可以很快根据评分或者次序来获取一个范围的元素。
- 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
- 集合中最大的成员数为 2^32-1 (4294967295, 每个集合可存储40多亿个成员)。
- 底层数据结构
- SortedSet是redis提供的一个非常特别的数据结构,一方面它等价于java的数据结构Map<String, Double>,可以给每个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的排名,还可以通过score的范围来获取元素的列表。
- zset底层使用了两种数据结构
- hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值
- 跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表
- 跳跃表
- 有序集合的实现:数组、平衡树、链表等。数组不便元素的插入、删除;平衡树或红黑树虽然效率高,但结构复杂;链表查询需要遍历所有,效率低。redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。
3.6.2 常用命令
代码语言:javascript复制zadd key [NX|XX] [CH] [INCR] score member [score member ...]: 将一个或多个member元素及其score值加入到有序集key中
XX:在给定XX选项的情况下,ZADD命令只会对给定成员当中已经存在于有序集合的成员进行更新,而那些不存在于有序集合的给定成员则会被忽略。
NX:在给定NX选项的情况下,ZADD命令只会把给定成员当中不存在于有序集合的成员添加到有序集合里面,而那些已经存在于有序集合中的给定成员则会被忽略。
CH:用户可以通过给定CH选项,让ZADD命令返回被修改(changed)成员的数量作为返回值。“被修改成员”指的是新添加到有序集合的成员,以及分值被更新了的成员
O(M*log(N)),其中M为给定成员的数量,而N则为有序集合包含的成员数量。
zrange key start stop [WITHSCORES]:返回有序集key中,下标在start和stop之间的元素。带WITHSCORES,可以让分数一起和值返回到结果集。O(log (N) M),其中N为有序集合包含的成员数量,而M则为命令返回的成员数量。
zrevrange key start stop [WITHSCORES]:倒序返回有序集key中,下标在start和stop之间的元素。O(log (N) M),其中N为有序集合包含的成员数量,而M则为命令返回的成员数量。
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]:返回有序集key中,所有score介于min和max之间(闭区间)的成员。O(log (N) M),其中N为有序集合包含的成员数量,而M则为命令返回的成员数量
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]:同上,从大到小。O(log (N) M),其中N为有序集合包含的成员数量,而M则为命令返回的成员数量
zincrby key increment member:为元素member的score加上增量increment。O(log (N)),其中N为有序集合包含的成员数量。
zrem key member [member ...]:删除该集合下,指定的元素(member)。O(M*log(N)),其中M为给定成员的数量,N为有序集合包含的成员数量。
zcount key min max:统计该集合,分数区间的元素个数。O(log (N)),其中N为有序集合包含的成员数量
zrank key member:返回该值在集合中的排名,排名从0开始。O(log (N)),其中N为有序集合包含的成员数量。
zrevrank key member:返回该值在集合中的倒序排名。O(log (N)),其中N为有序集合包含的成员数量。
zscore key member:获取与给定成员相关联的分值。O(1)
zcard key:取得有序集合的基数,即有序集合包含的成员数量。O(1)
zremrangebyrank key start stop:从升序排列的有序集合中移除位于指定排名范围内的成员,然后返回被移除成员的数量。O(log (N) M),其中N为有序集合包含的成员数量,M为被移除的成员数量
zremrangebyscore key min max:从有序集合中移除位于指定分值范围内的成员,并在移除操作执行完毕返回被移除成员的数量。O(log (N) M),其中N为有序集合包含的成员数量,M为被移除成员的数量。
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]:并集。O(N*log(N)),其中N为所有给定有序集合的成员总数量。
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]:交集。O(N*log(N)*M),其中N为所有给定有序集合中,基数最小的那个有序集合的基数,而M则是给定有序集合的数量
zrangebylex key min max [LIMIT offset count]:返回指定字典序范围内的成员。O(log (N) M),其中N为有序集合包含的元素数量,而M则为命令返回的成员数量。
zrevrangebylex key max min [LIMIT offset count]:返回指定字典序范围内的成员。O(log (N) M),其中N为有序集合包含的元素数量,而M则为命令返回的成员数量。
zlexcount key min max:统计位于字典序指定范围内的成员数量。O(log (N)),其中N为有序集合包含的成员数量。
zremrangebylex key min max:移除位于字典序指定范围内的成员。O(log (N) M),其中N为有序集合包含的成员数量,M为被移除成员的数量
zpopmax key [count]:弹出分值最高的成员。O(N),其中N为命令移除的元素数量。
zpopmin key [count]:弹出分值最低的成员。O(N),其中N为命令移除的元素数量。
bzpopmax key [key ...] timeout:阻塞式最大元素弹出操作。O(N),其中N为用户给定的有序集合数量
bzpopmin key [key ...] timeout:阻塞式最小元素弹出操作。O(N),其中N为用户给定的有序集合数量
4. redis配置文件
4.1 简介
- 配置文件:redis.conf(Windows 名为 redis.windows.conf)
- 查看配置项:config get *
- 修改配置项
- 修改配置文件
- config set修改(重启服务器后,密码还原)
4.2 配置项
4.2.1 units
- 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit。
- 大小写不敏感
4.2.2 includes
- 类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
- 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
4.2.3 modules
4.2.4 network
- bind:绑定的主机地址。默认bind=127.0.0.1,只能接受本机的访问请求,不写的情况下,无限制接受任何ip地址的访问。
- protected-mode:如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应。
- port:指定 Redis 监听端口,默认端口为 6379
- tcp-backlog: 默认511。设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 已经完成三次握手队列。
- 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
- 注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
- timeout:当客户端闲置多长秒后关闭连接,如果指定为 0 ,表示关闭该功能。默认是0,以秒为单位。
- tcp-keepalive:检查心跳时间。检查是否还连接,没连接则释放连接。默认300秒
4.2.5 TLS/SSL
4.2.6 general
- daemonize:是否为后台进程,设置为yes。守护进程,后台启动。Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no )
- pidfile:当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis.pid 文件,可以通过 pidfile 指定。每个实例会产生一个不同的pid文件
- loglevel:指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice
- logfile:日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null
- database:设定库的数量,默认16,默认数据库为0,可以使用select <dbid>命令指定连接的数据库。
4.2.7 snapshotting
- save:指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合。Redis 默认配置文件中提供了三个条件
- save 900 1:900 秒(15 分钟)内有 1 个更改
- save 300 10:300 秒(5 分钟)内有 10 个更改
- save 60 10000:60 秒内有 10000 个更改。
- rdbcompression:指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大
- dbfilename:指定本地数据库文件名,默认值为 dump.rdb
- dir:指定本地数据库存放目录
4.2.8 replication
- slaveof:设置当本机为 slave 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步
- masterauth:当 master 服务设置了密码保护时,slave 服务连接 master 的密码
4.2.9 keys tracking
4.2.10 security
- requirepass:设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH命令提供密码,默认关闭
- 配置文件中修改:requirepass yourpasswd,重启redis-server服务,指定配置文件。永久设置
- 进入redis-cli客户端修改(重启服务器后,密码还原)
- config get requirepass 1) "requirepass" 2) ""
- config set requirepass yourpasswd
- auth yourpasswd
4.2.11 clients
- maxclients:设置同一时间最大客户端连接数,默认10000。Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息
4.2.12 memory management
- maxmemory:指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
- Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区
- 建议必须设置。否则将内存占满,造成服务器宕机。
- 设置redis可以使用的内存量。一旦达到上线,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定
- 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
- 如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
- maxmemory-policy
- volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
- allkeys-lru:在所有集合key中,使用LRU算法移除key
- volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
- allkeys-random:在所有集合key中,移除随机的key
- volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
- noeviction:不进行移除。针对写操作,只是返回错误信息
- maxmemory-samples:设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
- 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
4.2.13 lazy freeing
4.2.14 threaded I/O
4.2.15 kernel oom control
4.2.16 append only mode
- appendonly:指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no
- appendfilename:指定更新日志文件名,默认为 appendonly.aof
- appendfsync:指定更新日志条件,共有 3 个可选值:no:表示等操作系统进行数据缓存同步到磁盘(快)always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全)everysec:表示每秒同步一次(折中,默认值)
- vm-enabled:指定是否启用虚拟内存机制,默认值为 no。VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中
- vm-swap-file:虚拟内存文件路径,默认值为 /tmp/redis.swap,不可多个 Redis 实例共享
- vm-max-memory:将所有大于 vm-max-memory 的数据存入虚拟内存,无论 vm-max-memory 设置多小,所有索引数据都是内存存储的(Redis 的索引数据 就是 keys),也就是说,当 vm-max-memory 设置为 0 的时候,其实是所有 value 都存在于磁盘。默认值为 0
- vm-page-size:Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,vm-page-size 是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为 32 或者 64bytes;如果存储很大大对象,则可以使用更大的 page,如果不确定,就使用默认值32
- vm-pages:设置 swap 文件中的 page 数量,由于页表(一种表示页面空闲或使用的 bitmap)是在放在内存中的,,在磁盘上每 8 个 pages 将消耗 1byte 的内存。默认:134217728
- vm-max-threads:设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
- glueoutputbuf:设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
- hash-max-zipmap-entries 64、hash-max-zipmap-value 512:指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
- activerehashing:指定是否激活重置哈希,默认为yes-开启
4.2.17 lua scripting
4.2.18 redis cluster
4.2.19 cluster docker/nat support
4.2.20 slow log
4.2.21 latency monitor
4.2.22 event notification
4.2.23 gopher server
4.2.24 advanced config
4.2.25 active defragmentation
5. redis的发布和订阅
5.1 简介
- 什么是发布和订阅
- redis发布订阅(pub/sub)是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息
- redis客户端可以订阅任意数量的频道
- redis的发布和订阅
5.2 命令行实现
- 打开一个客户端订阅channel1
subscribe channel1
打开另一个客户端,给channel1发布消息publish channel1 hello
返回的结果是订阅者数量:打开第一个客户端,就可以看到发送的消息 - 注意:发布的消息没有持久化,只能收到订阅后发布的消息
5.3 命令
代码语言:javascript复制unsubscribe [channel [channel ...]]:退订给定的频道。
subscribe channel [channel ...]:订阅一个或多个频道。
pubsub subcommand [argument [argument ...]]:查看订阅与发布系统状态。
punsubscribe [pattern [pattern ...]]:退订所有给定模式的频道。
publish channel message:将信息发送到指定的频道。
psubscribe pattern [pattern ...]:订阅一个或多个符合给定模式的频道
6. Redis新数据类型
6.1 Bitmaps
6.1.1 简介
- 现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图。合理地使用操作位能够有效地提高内存使用率和开发效率
- Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
- Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value),但是它可以对字符串的位进行操作。
- Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
- string类型
- 当我们使用字符串命令获取位图的值时,命令返回的是一个字符串,而不是一个二进制形式的位图。因此我们在使用字符串命令操作位图的时候,必须先将命令返回的字符串转换回二进制形式,然后再执行具体的二进制操作。
6.1.2 命令
代码语言:javascript复制setbit key offset value:设置Bitmaps中某个偏移量的值(0或1)。offset:偏移量从0开始。返回二进制位被设置之前的旧值。O(1)
getbit key offset:获取Bitmaps中某个偏移量的值。获取键的第offset位的值(从0开始算)。访问未设置的offset也返回0。O(1)
bitcount key [start end]:统计位图中值为1的二进制位数量。start参数和end这两个参数是用来指定字节偏移量而不是二进制位偏移量的。O(N),其中N为被统计字节的数量。
eg. K1 【01000001 01000000 00000000 00100001】,对应【0,1,2,3】
bitcount K1 1 2:统计下标1,2字节组中bit=1的个数,即01000000 00000000 =》 1
bitcount K1 1 3:统计下标1,2,3字节组中bit=1的个数,即01000000 00000000 00100001 =》3
bitcount K1 0 -2:统计下标0到下标倒数第2,字节组中bit=1的个数,即01000001 01000000 00000000 =》3
bitop operation destkey key [key ...]:bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。O(N),其中N为查找涉及的字节数量。。
bitpos key bit [start] [end]:在位图中查找第一个被设置为指定值的二进制位,并返回这个二进制位的偏移量。O(N),其中N为查找涉及的字节数量
bitfield key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]:在位图中的任意区域(field)存储指定长度的整数值,并对这些整数值执行加法或减法操作。O(N),其中N为用户给定的子命令数量。
bitfield k2 set u8 0 123 set i32 20 10086:从偏移量0开始,设置一个8位长无符号整数值123。从偏移量20开始,设置一个32位长有符号整数值10086。
6.1.3 应用场景
- 访问网站的用户
- 注:很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
- 在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。
- 获取用户是否访问过网站,返回0说明没有访问过
- 统计访问网站的用户数:bitcount key
- 统计不同时刻都访问网站的用户数
- 2020-11-04 日访问网站的userid=1,2,5,9
- setbit unique:users:20201104 1 1
- setbit unique:users:20201104 2 1
- setbit unique:users:20201104 5 1
- setbit unique:users:20201104 9 1
- 2020-11-03 日访问网站的userid=0,1,4,9
- setbit unique:users:20201103 0 1
- setbit unique:users:20201103 1 1
- setbit unique:users:20201103 4 1
- setbit unique:users:20201103 9 1
- 计算出两天都访问过网站的用户数量。
- bitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104
- 计算出任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集
- bitop or unique:users:or:20201104_03 unique:users:20201103 unique:users:20201104
- 2020-11-04 日访问网站的userid=1,2,5,9
6.1.4 Bitmaps与set对比- 假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表
6.2 HyperLogLog
6.2.1 简介
- HyperLogLog是一个专门为了计算集合的基数而创建的概率算法,对于一个给定的集合,HyperLogLog可以计算出这个集合的近似基数。近似基数并非集合的实际基数,它可能会比实际的基数小一点或者大一点,但是估算基数和实际基数之间的误差会处于一个合理的范围之内,因此那些不需要知道实际基数或者因为条件限制而无法计算出实际基数的程序就可以把这个近似基数当作集合的基数来使用
- 基数问题:求集合中不重复元素个数的问题。
- 解决基数问题有很多种方案
- 数据存储在MySQL表中,使用distinct count计算不重复个数
- 使用Redis提供的hash、set、bitmaps等数据结构来处理
- 以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
- 解决基数问题有很多种方案
- Redis推出了HyperLogLog,通过降低一定的精度来平衡存储空间
- Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
- 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- 基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
- sring类型
6.2.2 命令
代码语言:javascript复制pfadd key element [element ...]:添加指定元素到 HyperLogLog 中。如果执行命令后key估计的近似基数发生变化,则返回1,否则返回0。O(N),其中N为用户给定的元素数量
pfcount key [key ...]:计算key的近似基数,可以计算多个key。O(N),其中N为用户给定的HyperLogLog数量
pfmerge destkey sourcekey [sourcekey ...]:将一个或多个key合并后的结果存储在另一个key中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得。O(N),其中N为用户给定的HyperLogLog数量
6.3 Geospatial
6.3.1 简介
- Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
- zset类型
6.3.2 命令
代码语言:txt复制geoadd key longitude latitude member [longitude latitude member ...]:添加地理位置(经度,纬度,名称)
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
两极无法直接添加,一般会下载城市数据,直接通过程序一次性导入。
有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。
当坐标位置超出指定范围时,该命令将会返回一个错误。已经添加的数据,是无法再次往里面添加的。
geopos key member [member ...]:获得指定地区的坐标值。O(log (N)*M),其中N为位置集合目前包含的位置数量,而M则为用户给定的位置数量
geopos china:city beijing
geodist key member1 member2 [m|km|ft|mi]: 获取两个位置之间的直线距离。O(log (N)),其中N为位置集合目前包含的位置数量
geodist china:city beijing shanghai km
单位:m 表示单位为米[默认值]。km 表示单位为千米。mi 表示单位为英里。ft 表示单位为英尺。
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]:以给定的经纬度为中心,找出某一半径内的元素。经度 纬度 距离 单位
O(N),其中N为命令实施范围查找时检查的位置数量。
WITHDIST:这些位置与中心点之间的距离
WITHHCOORD:返回被匹配位置的坐标
ASC|DESC:排序查找结果
COUNT:限制命令获取的位置数量
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]:查找指定位置半径范围内的其他位置
O(N),其中N为命令实施范围查找时检查的位置数量。
geohash key member [member ...]:获取指定位置的Geohash值。O(N),其中N为用户给定的位置数量
- georadius命令和georadiusbymember命令返回的是被解释为数字的Geohash值(WITHHASH)。而GEOHASH命令返回的则是被解释为字符串的Geohash值。比如GEOHASH命令在获取清远市的Geohash值时返回的是字符串"ws0w0phgp70",而GEORADIUS命令获取的Geohash值却是数字4046597933543051,不过这两个值底层的二进制位是完全相同的。
- 能用zset命令操作geo类型
- ZADD china:city 4046597933543051 Qingyuan
6.4 Stream
6.4.1 简介
- 流(stream)是Redis 5.0版本中新增加的数据结构
- 流(stream)是使用Redis实现消息队列应用的最佳选择
- 流是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个ID和任意多个键值对,这些元素会根据ID的大小在流中有序地进行排列。
- 一条消费者组消息从出现到处理完毕,需要经历以下阶段:不存在;未递送;待处理;已确认。
- Redis Stream的底层主要使用了listpack(紧凑列表)和Rax树(基数树)。 (参考:https://blog.csdn.net/weixin_52851967/article/details/127705350)
- redis配置文件
- stream-node-max-bytes 4096
- stream-node-max-entries 100:100*100个节点。或maxlen裁剪
6.4.2 命令
代码语言:javascript复制xadd key ID field value [field value ...]:追加新元素到流的末尾。O(log (N)),其中N为流目前包含的元素数量。
如果给定的流不存在,那么Redis会先创建一个空白的流,然后将给定的元素追加到流中
ID:流元素的ID由毫秒时间(millisecond)和顺序编号(sequcen number)两部分组成
使用UNIX时间戳表示的毫秒时间用于标识与元素相关联的时间
以0为起始值的顺序编号则用于区分同一时间内产生的多个不同元素
XADD s1 1100000000000-12345 k1 v1。元素ID的毫秒时间部分为1100000000000,而顺序编号部分则为12345。Redis将把包含键值对k1和v1的新元素追加到流s1的末尾
XADD s1 1000000000000 k1 v1。用户在输入流ID的时候,除了可以给出带有毫秒时间和顺序编号的完整流ID之外,还可以给出只包含毫秒时间的不完整流ID:在这种情况下,Redis会自动将ID的顺序编号部分设置为0
不允许使用相同的ID,同时要求新元素的ID必须比流中所有已有元素的ID都要大
*:当用户将符号*用作id参数的值时,Redis将自动为新添加的元素生成一个可用的新ID。
maxlen:XADD命令还提供了MAXLEN选项,让用户可以在添加新元素的同时删除旧元素,以此来限制流的长度。xadd key * maxlen 3 1 a 2 b 3 c
xtrim key MAXLEN [~] count:对流进行修剪。返回被移除元素的数量作为结果。O(log (N) M),其中N为执行修剪操作前流包含的元素数量,而M则为被移除元素的数量。
stream最开头的count个元素将被移除
参数~的意思是,用户不是真的需要精确的count个项目。它可以多几十个条目,但决不能少于count个。通过使用这个参数,仅当我们移除整个节点的时候才执行修整。这使得命令更高效,而且这也是我们通常想要的。
xdel key ID [ID ...]:移除指定元素。O(log (N)*M),其中N为流包含的元素数量,而M则为被移除元素的数量。
xlen key:获取流包含的元素数量。O(1)
xrange key start end [COUNT count]:用于读取给定ID范围内的消息数据,并可以设置返回数据的条数。O(log (N) M),其中N为流包含的元素数量,而M则为命令返回的元素数量。
start:开始消息ID,指定具体值或通过“-”特殊符号来表示最小ID。
end:结束消息ID,指定具体值或通过“ ”特殊符号来表示最大ID。
xrevrange key end start [COUNT count]:访问流中元素。O(log (N) M),其中N为流包含的元素数量,而M则为命令返回的元素数量。
xrevrange命令与xrange用法完全一致,唯一区别是返回数据的顺序为消息ID的递减序,正好与xrange返回的数据顺序相反。
xgroup [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername] [HELP]:用于队列的消费组管理,包含对消费组的创建、删除、修改等操作
O(N M),其中N为消费者组被删除时,仍处于“待处理”状态的消息数量,而M则是该组属下消费者的数量。
xreadgroup GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]:用于从消费组中可靠地消费n条消息,如果指定的消费者不存在,则创建之。
对于用户给定的每个流,从流中获取消息的复杂度为O(log (N) M),其中N为流包含的消息数量,而M则为被获取消息的数量。因此对于用户给定的I个流,获取这些流消息的总复杂度为O((log (N) M)*I)。
xread [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]:用于从Stream队列中读取N条消息,一般用作遍历队列中的消息。
对于用户给定的每个流,获取流元素的复杂度为O(log (N) M),其中N为流包含的元素数量,M为被获取的元素数量。因此对于用户给定的I个流,获取流元素的总复杂度为O((log (N) M)*I)
xpending key group [start end count] [consumer]:显示待处理消息的相关信息
执行XPENDING stream group格式的XPENDING的复杂度为O(N),其中N为消费者组目前拥有的消费者数量;执行带有start、stop和count参数的XPENDING命令的复杂度为O(log (N) M),其中N为消费者组目前拥有的待处理消息总数量,而M则是命令返回的消息数量;执行带有consumer参数的XPENDING命令的复杂度为O(log(N) M),其中N为该消费者目前拥有的待处理消息数量,而M则为命令返回的消息数量。
xack key group ID [ID ...]:将消息标记为“已处理”。O(N),其中N为用户给定的消息ID数量
xclaim key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] [JUSTID]:转移消息的归属权
O(N),其中N为用户给定的消息ID数量。
xinfo [CONSUMERS key groupname] [GROUPS key] [STREAM key] [HELP]:查看流和消费者组的相关信息
XINFO CONSUMERS命令的复杂度为O(N),其中N为给定消费者组的消费者数量;XINFO GROUPS命令的复杂度为O(M),其中M为给定流属下的消费者组数量;XINFO STREAM命令的复杂度为O(1)
- 修剪流
- 执行XADD命令的同时使用MAXLEN命令对流进行修剪
- 通过执行XTRIM命令直接将流修剪至指定长度
都是根据先进先出规则来淘汰旧元素的
6.4.3 消息队列
- https://zhuanlan.zhihu.com/p/572350546
7. 实现
7.1 Golang Redis
- https://github.com/go-redis/redis
If you are using Redis 6, install go-redis/v8:
go get github.com/go-redis/redis/v8
If you are using Redis 7, install go-redis/v9:
go get github.com/go-redis/redis/v9
7.1.1 连接redis
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
func PingRedis(rdb *redis.Client) {
cmd := rdb.Ping(ctx)
err := cmd.Err()
if err != nil {
panic(err)
}
fmt.Println(cmd.Val())
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
PingRedis(rdb)
}
7.1.2 String操作
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"time"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
// set
func SetString(rdb *redis.Client, key string, value string, ex time.Duration) bool {
cmd := rdb.Set(ctx, key, value, ex)
if cmd.Err() != nil {
fmt.Println("设置失败:", cmd.Err())
return false
}
return true
}
// get
func GetString(rdb *redis.Client, key string) string {
cmd := rdb.Get(ctx, key)
if cmd.Err() != nil {
fmt.Println("获取string值失败:", cmd.Err())
return ""
}
return cmd.Val()
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
setFlag := SetString(rdb, "k1", "value1", 120*time.Second)
fmt.Println(setFlag) // true
value := GetString(rdb, "k1")
fmt.Println(value) // value1
}
7.1.3 List操作
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
// rpush
func RpushList(rdb *redis.Client, key string, values ...interface{}) bool {
cmd := rdb.RPush(ctx, key, values)
if cmd.Err() != nil {
fmt.Println("Lpush失败:", cmd.Err())
return false
}
return true
}
// lrange
func LrangeList(rdb *redis.Client, key string, start int64, end int64) []string {
cmd := rdb.LRange(ctx, key, start, end)
if cmd.Err() != nil {
fmt.Println("Lrange失败:", cmd.Err())
return []string{}
}
return cmd.Val()
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
LpushFlag := RpushList(rdb, "k2", "golang", "php", "python", 10001, 10086)
fmt.Println(LpushFlag)
ListValue := LrangeList(rdb, "k2", 0, -1)
fmt.Println(ListValue)
}
7.1.4 Set操作
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
// sadd
func SAddSet(rdb *redis.Client, key string, members ...interface{}) bool {
cmd := rdb.SAdd(ctx, key, members)
if cmd.Err() != nil {
fmt.Println("SAdd失败:", cmd.Err())
return false
}
return true
}
// smembers
func SMembersSet(rdb *redis.Client, key string) []string {
cmd := rdb.SMembers(ctx, key)
if cmd.Err() != nil {
fmt.Println("获取集合成员失败:", cmd.Err())
return []string{}
}
return cmd.Val()
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
SaddFlag := SAddSet(rdb, "k3", "golang", "php", "python", 10001, 10086, "golang", 10001, "python")
fmt.Println(SaddFlag)
Smembers := SMembersSet(rdb, "k3")
fmt.Println(Smembers)
}
7.1.5 Hash操作
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
// hset
func HSetHash(rdb *redis.Client, key string, values ...interface{}) bool {
cmd := rdb.HSet(ctx, key, values)
if cmd.Err() != nil {
fmt.Println("hset失败:", cmd.Err())
return false
}
return true
}
// hkeys
func HKeysHash(rdb *redis.Client, key string) []string {
cmd := rdb.HKeys(ctx, key)
if cmd.Err() != nil {
fmt.Println("获取hash key失败:", cmd.Err())
return []string{}
}
return cmd.Val()
}
// hvals
func HValsHash(rdb *redis.Client, key string) []string {
cmd := rdb.HVals(ctx, key)
if cmd.Err() != nil {
fmt.Println("获取hash value失败:", cmd.Err())
return []string{}
}
return cmd.Val()
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
HSetFlag := HSetHash(rdb, "k4", "golang", 1, "php", 2, "python", 3, "python", 4)
fmt.Println(HSetFlag)
HashKeys := HKeysHash(rdb, "k4")
fmt.Println(HashKeys)
HashValues := HValsHash(rdb, "k4")
fmt.Println(HashValues)
}
7.1.6 Zset操作
代码语言:javascript复制package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func ConnectClient() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "172.29.170.39:6379",
Password: "123456",
DB: 0,
})
return rdb
}
// zadd
func ZAddZset(rdb *redis.Client, key string, members ...*redis.Z) bool {
cmd := rdb.ZAdd(ctx, key, members...)
if cmd.Err() != nil {
fmt.Println("zadd失败:", cmd.Err())
return false
}
return true
}
// zrange
func ZrangeWithScoreZset(rdb *redis.Client, key string, start interface{}, stop interface{}) []redis.Z {
z := redis.ZRangeArgs{
Key: key,
Start: start,
Stop: stop,
}
cmd := rdb.ZRangeArgsWithScores(ctx, z)
if cmd.Err() != nil {
fmt.Println("获取zset数据失败:", cmd.Err())
return []redis.Z{}
}
return cmd.Val()
}
// do
func ZsetDo(rdb *redis.Client, args ...interface{}) interface{} {
cmd := rdb.Do(ctx, args...)
if cmd.Err() != nil {
fmt.Println("do err:", cmd.Err())
return false
}
return cmd.Val()
}
func main() {
rdb := ConnectClient()
defer rdb.Close()
members1 := redis.Z{Score: 100, Member: "php"}
members2 := redis.Z{Score: 200, Member: "golang"}
members3 := redis.Z{Score: 300, Member: "python"}
ZaddFlag := ZAddZset(rdb, "k5", &members1, &members2, &members3)
fmt.Println(ZaddFlag)
ZsetVals := ZrangeWithScoreZset(rdb, "k5", 0, -1)
fmt.Println(ZsetVals)
ZRes := ZsetDo(rdb, "zcount", "k5", 100, 200)
fmt.Println(ZRes)
}
7.2 Python Redis
代码语言:javascript复制from redis import StrictRedis
# 连接
client = StrictRedis("172.26.108.198", password="123456")
resp = client.ping()
if resp == True:
print("连接成功")
# string操作
resp = client.set("k1", "2023-01-12", 120)
if resp == True:
print("k1设置成功")
resp = client.get("k1")
if resp==None:
print("key不存在")
else:
print(resp.decode())
# hash操作
res = client.delete("k2")
resp = client.hset("k2", "name", "lily", {"age":18, "gender":"女"})
if resp == True:
print("k2设置成功")
resp = client.hkeys("k2")
if resp==None:
print("key不存在")
else:
print([i.decode() for i in resp])
resp = client.hvals("k2")
if resp==None:
print("key不存在")
else:
print([i.decode() for i in resp])
7.3 Php Redis
7.4. Java Redis
7.4.1 Jedis
代码语言:javascript复制import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JedisTest {
public static void main(String[] args) {
// 连接到redis
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("redis");
String pong = jedis.ping();
System.out.println("连接成功:" pong);
// set
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
// keys *
Set<String> keys = jedis.keys("*");
System.out.println(keys.size());
System.out.println(keys);
// exists
System.out.println(jedis.exists("k1"));
// ttl
System.out.println(jedis.ttl("k1"));
// get key
System.out.println(jedis.get("k1"));
// mset
jedis.mset("str1","v1","str2","v2","str3","v3");
// mget
System.out.println(jedis.mget("str1","str2","str3"));
// lset
jedis.lpush("mylist", "hello", "world", "!");
jedis.rpush("mylist", "hello", "world", "~");
// lrange
List<String> list = jedis.lrange("mylist",0,-1);
System.out.println(list);
// del
jedis.del("mylist");
// sadd
jedis.sadd("orders", "order01");
jedis.sadd("orders", "order02");
jedis.sadd("orders", "order03");
jedis.sadd("orders", "order04");
// smembers
Set<String> smembers = jedis.smembers("orders");
System.out.println(smembers);
// srem
jedis.srem("orders", "order02");
Set<String> smembers2 = jedis.smembers("orders");
System.out.println(smembers2);
// hset
jedis.hset("hash1","userName","lisi");
// hget
System.out.println(jedis.hget("hash1","userName"));
// hmset
Map<String,String> map = new HashMap<String,String>();
map.put("tel","123456");
map.put("addr","example");
map.put("email","abc@163.com");
jedis.hmset("hash2",map);
// hmget
List<String> result = jedis.hmget("hash2", "tel","email");
System.out.println(result);
// hgetAll
Map<String, String>mp = jedis.hgetAll("hash2");
System.out.println(mp);
// zadd
jedis.zadd("zset01", 100d, "z3");
jedis.zadd("zset01", 90d, "l4");
jedis.zadd("zset01", 80d, "w5");
jedis.zadd("zset01", 70d, "z6");
// zrange
Set<String> zrange = jedis.zrange("zset01", 0, -1);
System.out.println(zrange);
// 断开连接
jedis.close();
}
}
7.4.2 Springboot
pom.xml
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springboot-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>springboot-test-redis</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
代码语言:javascript复制server.port=8080
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis数据库索引(默认为0)
spring.redis.database= 0
# 连接超时时间(毫秒)
spring.redis.timeout=1800000
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
# 最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接密码
spring.redis.password=redis
RedisConfig
代码语言:javascript复制package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
MyConfig
代码语言:javascript复制package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({RedisConfig.class})
public class MyConfig {
}
MainApplication
代码语言:javascript复制package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication()
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
RedisController
代码语言:javascript复制package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redisTest")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucky");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
System.out.println(name);
return name;
}
}
页面访问
8. Redis事务
8.1 简介
- 事务定义
- Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- Redis事务的主要作用就是串联多个命令防止别的命令插队。
8.2 multi、exec、discard
- 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行
- 输入Exec后,Redis会将之前的命令队列中的命令依次执行。
- 组队的过程中可以通过discard来放弃组队。
8.3 事务的错误处理
- 组队成功,提交成功(都成功)
- 组队阶段报错,提交失败(都失败)
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
- 组队成功,提交有成功有失败情况(部分失败)
- 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
8.4 watch、unwatch
- watch:在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
// client-1
watch k1
multi
set k1 value1
set k2 value2
get k1
get k2
// client-2
set k1 v1
set k2 v2
// client-1
exec
// 结果:nil 事务内命令没执行
// client-1或client2
get k1 // v1
get k2 // v2
- unwatch:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
8.5 锁
- 悲观锁(Pessimistic Lock):每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
- 乐观锁(Optimistic Lock):每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis watch就是利用这种check-and-set机制实现事务的。
8.6 事务特性
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚