Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

Redis学习笔记

简介

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

安装工具推荐

windows工具-Redis Desktop Manager 0.9.3.817

AnotherRedisDesktopManager

通过docker来安装。

1
docker run --name redis -d -p 6379:6379 redis:6.0

安装完成之后一共有这些工具:

1
2
3
4
5
6
./redis-benchmark //用于进行redis性能测试的工具
./redis-check-dump //用于修复出问题的dump.rdb文件
./redis-cli //redis的客户端
./redis-server //redis的服务端
./redis-check-aof //用于修复出问题的AOF文件
./redis-sentinel //用于集群管理

切换数据库:

1
2
select 5
127.0.0.1:6379[5]> key *

基本数据结构

官网数据结构简介

字符串(strings)

如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set mystr "hello world!" //设置字符串类型
get mystr //读取字符串类型
APPED mystr " I'm Franky!!" // 字符串追加
STRLEN mystr // 获取字符串长度
127.0.0.1:6379> set mynum "2"
OK
127.0.0.1:6379> get mynum
"2"
127.0.0.1:6379> incr mynum
(integer) 3
127.0.0.1:6379> get mynum
"3"
EXIST key // 用于检查数值是否存在
SETNX key value

在遇到数值操作时,redis会将字符串类型转换成数值。由于INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果,假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。不少网站都利用redis的这个特性来实现业务上的统计计数需求

使用了SDS(simple dynamic string)动态字符串来保存。在保存的时候,按照大小分为

查找的时候算法复杂度是多少?O(1)

字符串列表(lists)

有链表的特点,可以从头尾来操作这个数据结构。内部实现双端链表压缩链表。如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码

list应用场景参考以下:

  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpsh+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

哈希(hashes)

在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构。有些类似table的数据结构。

  • 简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
  • 简单使用举例:hset key field value 、hget key field
  • 内部编码:ziplist(压缩列表) 、hashtable(哈希表)
  • 应用场景:缓存用户信息等。
  • 注意点:如果开发使用hgetall,哈希元素比较多的话,可能导致Redis阻塞,可以使用hscan。而如果只是获取部分field,建议使用hmget。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379[15]> get 36d9d4c5-2f13-4f9c-921e-ee6472c4fa65
(error) WRONGTYPE Operation against a key holding the wrong kind of value
### 查询某个key的类型
127.0.0.1:6379[15]> type 36d9d4c5-2f13-4f9c-921e-ee6472c4fa65
hash
### 直接查询key下全部的内容
127.0.0.1:6379[15]> hgetall 36d9d4c5-2f13-4f9c-921e-ee6472c4fa65
1) "KgParty1EcKeypair"
2) "{\"public_share\":{\"x\":\"4861006ae0100caa16d1a3acf0a99d0f39cdae7a7629fa9d192b272a7e703800\",\"y\":\"a2c2d08ac462b095e18d4cb2214db57323c4024192be07e37e6aebee9be9665a\"},\"secret_share\":\"bece3f6951c9a70cd353fb5a04707b70d1e3230d13eaa6019f48060316ec2925\"}"
3) "KgParty1CommWitness"
4) "{\"d_log_proof\":{\"challenge_response\":\"f137758c977468eb23540fb9c7fdb4ae4e15d7897133491c17f6257f8d1c4d82\",\"pk\":{\"x\":\"4861006ae0100caa16d1a3acf0a99d0f39cdae7a7629fa9d192b272a7e703800\",\"y\":\"a2c2d08ac462b095e18d4cb2214db57323c4024192be07e37e6aebee9be9665a\"},\"pk_t_rand_commitment\":{\"x\":\"acccf69013c1ba2bfe473470fa403f976c9c0ad291929386a85e577d144e96b\",\"y\":\"ff8bac688b173bfde35104678cc4281c1d305174be6704f2e7d754960dec467d\"}},\"pk_commitment_blind_factor\":\"418ddf7e0ee4f119aa51da5eb83fc73698260d3a9a1dff71f7b0b921fef1db9b\",\"public_share\":{\"x\":\"4861006ae0100caa16d1a3acf0a99d0f39cdae7a7629fa9d192b272a7e703800\",\"y\":\"a2c2d08ac462b095e18d4cb2214db57323c4024192be07e37e6aebee9be9665a\"},\"zk_pok_blind_factor\":\"f56c32a72b19fa9d912d49b81cb4932c9cf1e351e28edf8c44c238e638f0f1e3\"}"
127.0.0.1:6379[15]>
## 检查某个key的存活时间
expire key seconds

Set(集合)

  • 简介:集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素
  • 简单使用举例:sadd key element [element …]、smembers key
  • 内部编码:intset(整数集合)、hashtable(哈希表)
  • 注意点:smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,可以使用sscan来完成。
  • 应用场景: 用户标签,生成随机数抽奖、社交需求。

有序字符串集合(sorted sets)

  • 简介:已排序的字符串集合,同时元素不能重复
  • 简单格式举例:zadd key score member [score member …],zrank key member
  • 底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
  • 应用场景:排行榜,社交需求(如用户点赞)。

特殊数据结构

  • Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
  • HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
  • Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组

基本操作

为啥Redis这么快

什么叫做击穿

什么是集群

消息队列示例

ReplyError: NOGROUP No such key 'MQKey' or consumer group 'MQGroup' in XREADGROUP with GROUP option

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 创建,0或者是$方式,0的意思是从最久远的一条读起;
XGROUP CREATE MQKey MQGroup 0 MKSTREAM
XINFO STREAM MQKey
127.0.0.1:6379[6]> xinfo STREAM MQKey
1) "length"
2) (integer) 3
3) "radix-tree-keys"
4) (integer) 1
5) "radix-tree-nodes"
6) (integer) 2
7) "last-generated-id"
8) "1651139273795-0"
9) "groups"
10) (integer) 1
11) "first-entry"
12) 1) "1651138102720-0"
2) 1) "txSN"
2) "5"
13) "last-entry"
14) 1) "1651139273795-0"
2) 1) "txSN"
2) "7"

const result = await this.redis.xreadgroup(
'GROUP',
this.groupName,
this.consumerName,
'COUNT',
this.MaxMsgNumPerBatch,
'COUNT',
this.MaxMsgNumPerBatch,
'STREAMS',
this.key,
0,
);

const result = await this.redis.xreadgroup(
'GROUP',
this.groupName,
this.consumerName,
'COUNT',
this.MaxMsgNumPerBatch,
'BLOCK',
1000,
'STREAMS',
this.key,
'>',
);

this.redis.xack(this.key, this.groupName, id);

const id = await this.redis.xadd(this.key, '*', ...msg);

const result = await this.redis.xreadgroup(
'GROUP',
this.groupName,
this.consumerName,
'COUNT',
this.MaxMsgNumPerBatch,
'BLOCK',
500,
'STREAMS',
this.key,
'>',
);
if (!result) {
return [];
}

使用lua脚本

lua脚本输入参数的时候,key需要单独从外面输入,否则会存在一些问题。

  1. 先将lua脚本加载起来
1
2
3
private readonly redisScriptPath = path.join(__dirname, '../../redis-script/expire-hset.lua');
private readonly redisScript: string;
this.redisScript = fs.readFileSync(this.redisScriptPath).toString('ascii');
  1. 使用redis调用将函数驱动起来
1
return this.redisClient.eval(this.redisScript, 1, key, filed, value, RedisConstants.ExpireSeconds) as Promise<number>;
  1. 示例lua代码
1
2
3
4
5
6
7
if redis.pcall("exist", KEYS[1]) ~= 0 then
return redis.pcall("hset", KEYS[1], ARGV[1], ARGV[2])
else
local ret = redis.pcall("hset", KEYS[1], ARGV[1], ARGV[2])
redis.pcall("expire", KEYS[1], ARGV[3])
return ret
end

检查某个key是否存在,否则将这个hash的field设置上去,并且设置超时时间。否则直接修改其中的数值。

设置密码

1
2
3
4
5
redis-cli
127.0.0.1:6379> CONFIG SET requirepass "123456"
auth 输入密码
在redis解压目录中找到 redis.windows.conf 文件
requirepass foobared

你在使用 Homebrew 启动 Redis 服务时遇到的错误信息说明了 Homebrew 的服务管理部分没有正确初始化。这可能是由于几个原因引起的,以下是一些解决方法:

  1. 确保 Homebrew 是最新的
    你可以通过以下命令更新 Homebrew 和已安装的软件包:

    1
    2
    brew update
    brew upgrade
  2. 重新安装 Homebrew services
    你可能需要重新安装 homebrew-services 插件。可以用以下命令安装或重新安装它:

    1
    brew tap homebrew/services
  3. 检查 Redis 是否安装正确
    确保 Redis 已经成功安装并且没有损坏。你可以通过以下命令检查:

    1
    brew list | grep redis

    如果没有输出,说明 Redis 没有安装,或者安装不正确。可以尝试重新安装:

    1
    2
    brew uninstall redis
    brew install redis
  4. 使用直接命令启动 Redis
    如果问题依旧存在,你可以通过直接运行 Redis 来避免使用 brew services。首先,你可以手动启动 Redis 服务器:

    1
    redis-server /usr/local/etc/redis.conf

    注意,请根据实际的配置文件路径来调整 /usr/local/etc/redis.conf

  5. 查看日志文件
    如果 Redis 启动时仍然有问题,检查日志文件可以帮助你找到更多的线索。默认情况下,Redis 的日志文件在 /usr/local/var/log/redis.log(或其他路径,取决于你的安装设置)。

  6. 重启你的终端或电脑
    有时候,简单的重启可以解决一些不明确的问题。

如果以上方法都无法解决问题,请考虑在 Homebrew 的 GitHub 仓库或相关社区论坛上查找或提出问题,可能会有更多用户遇到相同的问题并找到了解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ sudo brew services list
Password:
Error: uninitialized constant Homebrew::Service
/opt/homebrew/Library/Homebrew/formula.rb:1267:in `service'
/opt/homebrew/Library/Homebrew/formula.rb:1238:in `plist_name'
/opt/homebrew/Library/Homebrew/formula.rb:1250:in `launchd_service_path'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/formula_wrapper.rb:58:in `service_file'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/formula_wrapper.rb:92:in `plist?'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/formulae.rb:12:in `select'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/formulae.rb:12:in `available_services'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/formulae.rb:17:in `services_list'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/lib/service/commands/list.rb:13:in `run'
/opt/homebrew/Library/Taps/homebrew/homebrew-services/cmd/services.rb:102:in `services'
/opt/homebrew/Library/Homebrew/brew.rb:98:in `public_send'
/opt/homebrew/Library/Homebrew/brew.rb:98:in `<main>'
If reporting this issue please do so at (not Homebrew/brew or Homebrew/homebrew-core):
https://github.com/homebrew/homebrew-services/issues/new
❯ git -C $(brew --repo homebrew/services) remote get-url origin
https://gitee.com/imirror/homebrew-services.git
❯ git -C $(brew --repo homebrew/services) show HEAD
❯ git -C "$(brew --repo homebrew/services)" remote set-url origin https://github.com/Homebrew/homebrew-services.git
brew update

国内的mac使用的brew有可能是gitee,这个山寨货。

参考: https://github.com/Homebrew/homebrew-core/issues/127329

引用