【工具应用杂记】(一)Git&&Docker&&Redis

188 阅读13分钟

背景

笔者最近在使用一些工具的过程中随手记录了一些使用经验,但由于知识体系过于杂碎,不好每个点都单独成文;但是又希望把它发布出来,供自己将来遗忘时查阅,所以起了这么个标题。

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 新建一个目录用来放Redis
  • cd Redis 进入Redis目录
  • wget https://download.redis.io/redis-stable.tar.gz (2022.4.27发布的7.0版本,是截止笔者写这篇文章时的最新版)
  • tar -zxvf redis-stable.tar.gz 解压
  • cd redis-stable
  • make
  • cd src
  • make 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类型。

image.png

我很好奇redis是怎么做到的。后面看文档时发现了linenoise这个开源库。 仓库地址: github.com/antirez/lin…

这是一个久经考验的库。代码用c写成,短小精悍,无需额外配置。最近的提交时间可追溯到三年前,比较前的甚至可以追溯到12年前。MongoDB和Android也有用它。

简单使用

参考文章:linenoise的使用

上述文章构造了一个简易的项目结构,src下的main.c放执行代码,CMakeLists.txt放编译配置,将编译结果放到build目录下。

和上述文章不同的是,我的main.c直接复制源代码中的example.c的代码,所以我的CMakeLists.txt和文章中最初的那版相同即可。

库效果测试如下:

得到打印结果:

image.png

tab补全

输入h后按tab键一次,可得hello world,再按一次可得hello there image.png 对这样的效果的形成,官方也有代码说明:

void completion(const char *buf, linenoiseCompletions *lc) {
    if (buf[0] == 'h') {
        linenoiseAddCompletion(lc,"hello");
        linenoiseAddCompletion(lc,"hello there");
    }
}

目前由于代码编写时只写了这一处,所以也暂时仅支持这一处的补全,其他的可自行配置。

笔者认为:
有两种方式:

  • 以曾经输入过的内容作为词库来源。比如原生的vim采取的就是这种方式
  • 使用特定的词库。比如有些vim插件就是利用这一点,但是提示范围广,不够精确。 (至于idea、vscode之类的是怎么做的我还没探究过,不过我猜它本质上也是从词库上找,只是这个词库是对应语言的api库,并且能动态缩小搜索范围,所以结果比较精确)
是否显示输入字符

image.png 以上图片展示的是,笔者先是输入/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 //只能设值为01
(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