背景
笔者最近在使用一些工具的过程中随手记录了一些使用经验,但由于知识体系过于杂碎,不好每个点都单独成文;但是又希望把它发布出来,供自己将来遗忘时查阅,所以起了这么个标题。
Git
聊聊branch、switch和checkout
switch是后来出的命令。
git branch -c<分支名>只创建分支但不切换,要切换还得手动git switch <分支名>或git branch <分支名>git checkout -b <分支名>创建新分支并切换
聊聊branch与stash
error: Your local changes to the following files would be overwritten by checkout:
<此处为项目修改的一些文件名>
Please commit your changes or stash them before you switch branches.
即使是stash,切换后在IDE/vscode之类的地方还是可以看到更改的文件,只是会显示untracked。
要注意编辑器是否保存和git版本管理的区别
stash相当于是push进去一个栈,可以有两种用法:
- 发现在一个错误的分支上进行了修改,这个时候可以stash push,然后切换到正确的内容再pop
- 想离开当前分支短暂进入另一分支,但当前内容还没想commit,这时候也可以stash push,等再次切换回来之后再pop
Docker常用命令
查看当前正在运行的容器:
docker ps
进入某个容器(以mysql为例)
docker exec -it <容器名或id> bash
之后进入的bash就如同一个抽象出的主机的终端,输入
mysql -u <用户名> -p进入
Redis
初探Redis
笔者注:这是我第一次真正接触Redis,之前只听说它的一些概念,比如缓存、内存数据库、持久化等等,因为前两天刚好在掘金看到了一篇和它相关的文章,出于好奇,下载安装来看一看,也记录一下一些学习心得。严格意义上讲,这篇文章并不是一篇教程,只是一篇随记,所以各位看官如果想对Redis有个全面、透彻的理解,还是直接看一手资料官方文档比较好。这篇文章仅是本井底之蛙一己之见,聊以消遣,后续等到我对它有更深入的认识,也会回来勘误的。
下载安装(源码方式)
mkdir Redis新建一个目录用来放Rediscd Redis进入Redis目录wget https://download.redis.io/redis-stable.tar.gz(2022.4.27发布的7.0版本,是截止笔者写这篇文章时的最新版)tar -zxvf redis-stable.tar.gz解压cd redis-stablemakecd srcmake install
编译成功后可启动服务:
redis-server启动服务端redis-cli另起一个窗口启动客户端
make install过程中报错:
/bin/sh 1 pkg-config not found解决方案:apt install pkg-config 安装pkg-config
细读Redis介绍
- 用途:数据库、缓存、消息代理、流引擎
- 数据结构:
- 范围查询:strings、hashes、lists、sets、sorted sets
- bitmaps、hyperloglog(目前先不深究,只需要知道这是一种基于概率统计基数的近似最优算法)、geospatial indexes(地理空间索引)、streams
- 机制:冗余、支持Lua脚本语言、LRU算法、事务
- 特性:可持久化到磁盘(两种方式可选,后面细说)、sentinel、通过集群实现聚合
- 其他:暂未提供对Windows版本的官方支持
redis-cli
export data
文档提到有一种方式可以通过redis-cli导出数据到外部程序:
示例代码如下:
$ redis-cli LPUSH mylist a b c d
(integer) 4
$ redis-cli --csv LRANGE mylist 0 -1
"d","c","b","a"
经试验,
- 当取出数据那行命令的0分别为1、2、3时,结果分别为:
"c","b","a"
"b","a"
"a" 我认为可以理解为这是向redis中存数据是进栈,取数据是出栈,第一个数字表示从栈顶的第几个元素开始取起(开始取的元素的下标start)
- 当第二个-1分别为0,1,-2,-3时(此时保持第一个的0不变),结果分别为:
"d"
"d","c"
"d","c","b"
"d","c" 可以理解为是结束元素的下标end。比如-1就是到倒数第一个元素;-2就是到倒数第二个元素,而0则是因为要到顺数第一个元素结束(出栈顺序)
MULTI?PING总是会第一时间得到回复PONG吗?
跟着文档的cli部分走,我输入了如下命令及得到了相应结果:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> PING
QUEUED
这让我有点意外,因为在之前的尝试中,输入PING后得到的回应总是PONG,为什么这里又是QUEUED呢?而MULTI在这里又是什么意思?为什么之后的端口后面会显示(TX)字样?
经过搜索,原来MULTI标记着一个事务块的开始,可以理解为“之后有 多条 命令”;所以输入了PING之后,系统并不着急执行,而是返回一个已查询的信息;TX也可以理解为是transaction。
结束标志:EXEC。
之后就会顺序执行命令块中的内容。
127.0.0.1:6379(TX)> EXEC
1) PONG
代码提示?聊聊强大的linenoise
在redis-cli中输入命令时,发现它似乎有代码提示操作。比如输入INCR,后面会弹出浅色小字提示我后接key类型。
我很好奇redis是怎么做到的。后面看文档时发现了linenoise这个开源库。 仓库地址: github.com/antirez/lin…
这是一个久经考验的库。代码用c写成,短小精悍,无需额外配置。最近的提交时间可追溯到三年前,比较前的甚至可以追溯到12年前。MongoDB和Android也有用它。
简单使用
参考文章:linenoise的使用
上述文章构造了一个简易的项目结构,src下的main.c放执行代码,CMakeLists.txt放编译配置,将编译结果放到build目录下。
和上述文章不同的是,我的main.c直接复制源代码中的example.c的代码,所以我的CMakeLists.txt和文章中最初的那版相同即可。
库效果测试如下:
得到打印结果:
tab补全
输入h后按tab键一次,可得hello world,再按一次可得hello there
对这样的效果的形成,官方也有代码说明:
void completion(const char *buf, linenoiseCompletions *lc) {
if (buf[0] == 'h') {
linenoiseAddCompletion(lc,"hello");
linenoiseAddCompletion(lc,"hello there");
}
}
目前由于代码编写时只写了这一处,所以也暂时仅支持这一处的补全,其他的可自行配置。
笔者认为:
有两种方式:
- 以曾经输入过的内容作为词库来源。比如原生的vim采取的就是这种方式
- 使用特定的词库。比如有些vim插件就是利用这一点,但是提示范围广,不够精确。 (至于idea、vscode之类的是怎么做的我还没探究过,不过我猜它本质上也是从词库上找,只是这个词库是对应语言的api库,并且能动态缩小搜索范围,所以结果比较精确)
是否显示输入字符
以上图片展示的是,笔者先是输入/mask,则之后输入的字符就变成了**,这时我靠着盲打输入了/unmask,则之后输入的字符又可以正常显示了。
(无论是这种形式,还是某某开发中的点一下眼睛按钮就不显示输入的内容,其实都是给使用者一个心理安慰,并没有真的起到保护密码的作用)
输入的命令记录
在引用该库的项目路径下,还会生成一个history.txt文件,里面记录的是最近一次运行demo时输入的内容。
关于这个库的其他内容详见该库的README文档。
聊聊REPL
REPL(read eval print loop)这个概念并不是Redis专有,只是我在学Redis的时候第一次了解到这个概念。
大致可以理解为是进入了应用程序里面的shell,比如Redis的127.0.0.1:6379>、前面提到的linenoise的hello>。
副本模式(Replica mode)
redis-cli --replica
当加入了--replica后,所启动的client就是主master的副本,会不断同步主master的信息。
数据存盘
在我启动redis-server一个小时后,突然留意到服务端出现了以下提示(已省略敏感信息):
29001:M 25 [时间] * 1 changes in 3600 seconds. Saving...
29001:M 25 [时间] * Background saving started by pid 29134
29134:C 25 [时间] * DB saved on disk
29134:C 25 [时间] * Fork CoW for RDB: current 0 MB, peak 0 MB, average 0 MB
29001:M 25 [时间] * Background saving terminated with success
特别是其中的"saved on disk",看来它确实不是只存在内存中的,也需要落到磁盘,也就是持久化,不过具体过程我还不太清楚,待后续回来补充。
client-side caching——tracking
文档中提到,从Redis6开始,由服务端协助、客户端实现缓存。
tracking有两种模式:
- 默认模式:服务端记住客户端曾获取哪些key(维护一张有效表),当这些key被修改时向客户端发送生效信息。这种情况下服务端会耗一点内存。
- 广播模式:服务端不存储信息,而是客户端之间相互通信。它们订阅了key的前缀,每次这其中的前缀被匹配(被查询或者修改),它们都会收到通知,从而获取最新的信息。这种情况下客户端会耗一点内存。
【Redis数据结构】
笔者跟着官方文档敲了一些命令,并在后期回顾这些命令的功能与作用。以下是一些分类:
Strings
nx:使插入无效
127.0.0.1:6379> set mykey newval
OK
127.0.0.1:6379> set mykey val nx
(nil)
127.0.0.1:6379> get mykey
"newval"
127.0.0.1:6379> set mykey val xx
OK
127.0.0.1:6379> get mykey
"val"
自增一个或多个
127.0.0.1:6379> set counter 100
OK
127.0.0.1:6379> incr counter
(integer) 101
127.0.0.1:6379> incr counter
(integer) 102
127.0.0.1:6379> incrby counter 50
(integer) 152
一次处理多个元素
127.0.0.1:6379> mset a 10 b 20 c 30
OK
127.0.0.1:6379> mget a b c
1) "10"
2) "20"
3) "30"
对元素删、查
127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> exists mykey //存在为1,不是为0
(integer) 1
127.0.0.1:6379> del mykey //删除成功返回1,不成功返回0
(integer) 1
127.0.0.1:6379> exists mykey
(integer) 0
127.0.0.1:6379> set mykey y
OK
127.0.0.1:6379> type mykey //类型
string
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> type mykey
none
设置过期时间
127.0.0.1:6379> set key some-value
OK
127.0.0.1:6379> expire key 10 //设置过期时间
(integer) 1
127.0.0.1:6379> get key
"some-value"
127.0.0.1:6379> get key
"some-value"
127.0.0.1:6379> get key //10s后过期了
(nil)
127.0.0.1:6379> set key 100 ex 5 //设值+设过期时间
OK
127.0.0.1:6379> ttl key
(integer) -2
Lists
双端队列基本操作
127.0.0.1:6379> rpush mylist A
(integer) 5
127.0.0.1:6379> rpush mylist B
(integer) 6
127.0.0.1:6379>
127.0.0.1:6379> rpush mylist first
(integer) 7
127.0.0.1:6379> lrange mylist 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "A"
6) "B"
7) "first"
127.0.0.1:6379> lpop mylist
"d"
127.0.0.1:6379> lpop mylist
"c"
127.0.0.1:6379> lpop mylist
"b"
127.0.0.1:6379> lpop mylist
"a"
127.0.0.1:6379> lpop mylist
"A"
127.0.0.1:6379> lpop mylist
"B"
127.0.0.1:6379> lpop mylist
"first"
127.0.0.1:6379> lpop mylist
(nil)
127.0.0.1:6379> lpush list 1 2 3 4 5
(integer) 5
127.0.0.1:6379> ltrim list 0 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
解决生产者-消费者问题:List上的阻断操作
produce可看作LPUSH,consume可看作RPOP,当List中已经没有元素的时候,再POP就会导致如下问题:
- 执行命令后返回空,执行命令也会耗时。
- worker接收到空值后,会等待一段时间,这段时间又浪费掉了。
所以就有了“阻断”操作:相应操作前面再加个B。 它的意思是:
- 接收POP命令后不会立即返回结果,而是在TTL内等到有新的元素来了再返回整个元素;如果超过时间还等不到,再返回空值。此处的TTL设定为5s
127.0.0.1:6379> brpop tasks 5
(nil)
(5.01s)
可能有人会问,那等待几秒之后不是更慢吗?不是更耗时间吗?笔者的理解是这样做可以节省一些操作。就像去学校接小孩放学,经典模式就是小孩还没放学,就又回家喝杯茶,过一会儿再去(u1s1这父母不太称职);而BLOCK模式就是要是孩子还没出来,就先在校门口等半个小时,接到了再走,接不到再回家等着。其实这半个小时终究都是会过去的,但是做家长的可以少做一点无用功。获取元素也是同理。
Hashes
user:1000是对象,username birthyear verified是属性
基本操作:set get getall mget hincrby
127.0.0.1:6379> hset user:1000 username antirez birthyear 1997 verified 1
(integer) 3
127.0.0.1:6379> hget user:1000 username
"antirez"
127.0.0.1:6379> hget user:1000 birthyear
"1997"
127.0.0.1:6379> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1997"
5) "verified"
6) "1"
127.0.0.1:6379> hmget user:1000 username birthyear trytry
1) "antirez"
2) "1997"
3) (nil)
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 2007
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 2017
Sets
基本操作
127.0.0.1:6379> sadd myset 1 2 3 //添加元素
(integer) 3
127.0.0.1:6379> smembers myset //查看set内成员
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> sismember myset 3 //判断元素是否属于集合
(integer) 1
127.0.0.1:6379> sismember myset 30
(integer) 0
127.0.0.1:6379> sadd news:1000:tags 1 2 5 77 //为news:1000设置1 2 5 77这几个tag
(integer) 4
127.0.0.1:6379> smembers news:1000:tags
1) "1"
2) "2"
3) "5"
4) "77"
添加元素
127.0.0.1:6379> sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK
(integer) 13
127.0.0.1:6379> sadd deck D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3
(integer) 16
127.0.0.1:6379> sadd deck H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 SJ SQ SK
(integer) 23
取并集、弹出元素、统计集合内元素个数
127.0.0.1:6379> sunionstore game:1:deck deck
(integer) 52
127.0.0.1:6379> spop game:1:deck
"HQ"
127.0.0.1:6379> scard game:1:deck
(integer) 51
排序
127.0.0.1:6379> zadd hackers 1940 "Alan Kay"
(integer) 1
127.0.0.1:6379> zadd hackers 1957 "Sophie Wilson"
(integer) 1
127.0.0.1:6379> zadd hackers 1953 "Richard Stallman"
(integer) 1
127.0.0.1:6379> zadd hackers 1949 "Anita Borg"
(integer) 1
127.0.0.1:6379> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
127.0.0.1:6379> zrange hackers 0 -1 //正序输出(按字母序)
1) "Alan Kay"
2) "Anita Borg"
3) "Richard Stallman"
4) "Sophie Wilson"
5) "Yukihiro Matsumoto"
127.0.0.1:6379> zrevrange hackers 0 -1 //逆序输出
1) "Yukihiro Matsumoto"
2) "Sophie Wilson"
3) "Richard Stallman"
4) "Anita Borg"
5) "Alan Kay"
127.0.0.1:6379> zrange hackers 0 -1 withscores //按值输出
1) "Alan Kay"
2) "1940"
3) "Anita Borg"
4) "1949"
5) "Richard Stallman"
6) "1953"
7) "Sophie Wilson"
8) "1957"
9) "Yukihiro Matsumoto"
10) "1965"
127.0.0.1:6379> zrangebyscore hackers -inf 1950 //输出值为1950以下的
1) "Alan Kay"
2) "Anita Borg"
127.0.0.1:6379> zrangebylex hackers [R [T //输出首字母在R T之间的
1) "Richard Stallman"
2) "Sophie Wilson"
Bitmaps
set
127.0.0.1:6379> setbit key 10 1
(integer) 0
127.0.0.1:6379> setbit key 0 9 //只能设值为0或1
(error) ERR bit is not an integer or out of range
127.0.0.1:6379> setbit key 1 9
(error) ERR bit is not an integer or out of range
get
127.0.0.1:6379> getbit key 10
(integer) 1
127.0.0.1:6379> getbit key 2
(integer) 0
bitcount
127.0.0.1:6379> bitcount key
(integer) 2
hyperLogLog
pfadd:add
127.0.0.1:6379> pfadd hll a b c d
(integer) 1
pfcount:get count
127.0.0.1:6379> pfcount hll
(integer) 4
127.0.0.1:6379>
Streams
命令特点:以X开头
基础命令
xadd
注意*前后要用空格隔开,不然会报错
127.0.0.1:6379> XADD temperatures:us-ny:10007 * temp_f 87.2 pressure 29.69 humidity 46
"1674825333065-0"
127.0.0.1:6379> XADD temperatures:us-ny:10007 * temp_f 83.1 pressure 29.21 humidity 46.5
"1674825344732-0"
127.0.0.1:6379> XADD temperatures:us-ny:10007 * temp_f 81.9 pressure 28.37 humidity 43.7
"1674825354057-0"
127.0.0.1:6379>
xrange
其中COUNT表示选取的符合条件的项的个数
127.0.0.1:6379> XRANGE temperatures:us-ny:10007 1658354934941-0 + COUNT 3
1) 1) "1674814597079-0"
2) 1) "temp_f"
2) "87.2"
3) "pressure"
4) "29.69"
5) "humidity"
6) "46"
2) 1) "1674814679282-0"
2) 1) "tmp_f"
2) "87.2"
3) "pressure"
4) "29.69"
5) "humidity"
6) "46"
3) 1) "1674814701493-0"
2) 1) "temp_f"
2) "87.2"
3) "pressure"
4) "29.69"
5) "humidity"
6) "46"
xread
注意这个$不能省略
XREAD COUNT 100 BLOCK 300 STREAMS temperatures:us-ny:10007 $
xlen
给定流的key,返回流的长度
127.0.0.1:6379> xlen temperatures:us-ny:10007
(integer) 6