开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Redis篇:事务和lua脚本的使用 前言 参考文章

发表于 2021-11-21

现在多数秒杀,抽奖,抢红包等大并发高流量的功能一般都是基于 redis 实现,然而在选择 redis 的时候,我们也要了解 redis 如何保证服务正确运行的原理

前言

  • redis 如何实现高性能和高并发
  • reids 事务的 ACID 原理
  • WATCH、EXEC 命令实现 redis 事务
  • lua 实现 redis事务
  • 抢红包方案

关注公众号,一起交流,微信搜一搜: 潜行前行

redis 如何实现高性能和高并发

  • redis 是一个内存数据库,读写非常高效。除了开启 AOF,RDB 异步线程去持久化数据,基本没有磁盘I/O消耗,性能方面是比 mysql,oracle 快很多
  • redis 自己实现一套简单高效的基础数据结构:动态字符串(SDS),链表,字典,跳跃链表,整数集合和压缩列表。然后在这个基础上去实现用户能操作的对象:字符串,列表,哈希,集合,有序集合等对象
  • reactor 模式的网络事件处理器。它使用了 I/O 多路复用去同时监控多个套接字,这是一种高效的I/O模型。reactor 相关知识可以看下这篇文章框架篇:见识一下linux高性能网络IO+Reactor模型
  • 事件处理器是单线执行的,这大大减少CPU的上下文切换,和对资源锁的竞争问题,极大提高redis服务处理速度(至于为啥使用单线程,因为CPU够用了,它的性能瓶颈在内存而不是CPU)
  • Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

reids 事务的 ACID 原理

  • redis 的事务需要先划分出三个阶段
    • 事务开启,使用 MULTI 可以标志着执行该命令的客户端从非事务状态切换至事务状态redis> MULTI
    • 命令入队,MULTI开启事务之后,非 WATCH、EXEC、DISCARD、MULTI 等特殊命令;客户端的命令不会被立即执行,而是放入一个事务队列
    • 执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行。如果是 DISCARD 则事务被丢弃
  • 命令入队过程如果出错(如使用了不存在的命令),则事务队列会被拒接执行
  • 执行事务期间出现了异常(如命令和操作的数据类型不匹配),事务队列的里的命令还是继续执行下去,直到全部命令执行完。不会回滚
  • WATCH 可用于监控 redis 变量值,在命令 EXEC 之前;redis 里的数据是有机会被其他客户端的命令修改的。使用 WATCH,监控的变量被修改后,执行 EXEC 时则会返回执行失败的 nil 回复
1
2
3
4
5
6
7
8
lua复制代码redis> WATCH "name"
OK
redis> MULTI ### 此时name已被其他客户端的命令修改
OK
redis> SET "name" "lwl"
QUEUED
redis> EXEC
(nil)
  • 从严格意义上来说,redis 是没有事务的。因为事务必须具备四个特点:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。然后 redis 是做不到这四点,只是具备其中一些特征,redis的事务是个伪事务,而且不支持回滚。下面将为各位同学一一道来

原子性

从上面可以,事务的异常会发生在EXEC命令执行前、后

  • EXEC命令执行前:在命令入队时就报错,(如内存不足,命令名称错误),redis 就会报错并且记录下这个错误。此时,客户还能继续提交命令操作;等到执行EXEC时,redis 就会拒绝执行所有提交的命令操作,返回事务失败的结果 nil
  • EXEC命令执行后:命令和操作的数据类型不匹配,但 redis 实例没有检查出错误。在执行完 EXEC 命令以后,redis 实际执行这些指令,就会报错。此时事务是不会回滚的,但事务队列的命令还是继续被执行。事务的原子性无法保证
  • EXEC执行时,发生故障:如果 redis 开启了 AOF 日志,那么,只会有部分的事务操作被记录到 AOF 日志中。需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。事务的原子性得到保证

一致性

  • EXEC命令执行前:入队报错事务会被放弃执行,具有一致性
  • EXEC命令执行后:实际执行时报错,错误的执行不会执行,正确的指令可以正常执行,一致性可以保证
  • EXEC执行时,发生故障:RDB 模式,RDB 快照不会在事务执行时执行,事务结果不会保存在RDB;AOF 模式,可以使用 redis-check-aof 工具检查 AOF 日志文件,把未完成的事务操作从 AOF 文件中去除。可以保证一致性

隔离性

  • EXEC 命令前执行,隔离性需要通过 WATCH 机制保证。因为 EXEC 命令执行前,其他客户端命令可以被执行,相关变量会被修改;但可以使用 WATCH 机制监控相关变量。一旦相关变量被修改,则 EXEC 后则事务失败返回;具有隔离性
  • EXEC 命令之后,隔离性可以保证。因为 redis 是单线程执行,事务队列里的命令和其他客户端的命令只能二选一被顺序执行,因此具有隔离性

持久性

  • 如果 redis 没有使用 RDB 或 AOF,事务的持久化是不存在的
  • 使用 RDB 模式,那么在一个事务执行后,而下一次的 RDB 快照还未执行前,如果发生了实例宕机,数据丢失,这种情况下,事务修改的数据也是不能保证持久化
  • AOF 模式,因为 AOF 模式的三种配置选项 no、everysec 和 always 都会存在数据丢失的情况。所以,事务的持久性属性也还是得不到保证

总结

  • redis 的事务机制可以保证一致性和隔离性;但是无法保证持久性;具备了一定的原子性,但不支持回滚

WATCH、EXEC 命令实现 redis 事务

1
2
3
4
5
6
7
8
9
10
11
lua复制代码redis> WATCH "map"
OK
redis> MULTI
OK
redis> HSET map "csc" "lwl"
QUEUED
redis> HGET map "csc"
QUEUED
redis> EXEC
1) OK
2) "lwl"

lua 实现 redis 事务

除了 MULTI、WATCH、EXEC 命令,还有其他的方式可做到 redis 原子性和隔离性吗?有的,lua 脚本;redis 内置了lua的执行环境,并自带了一些 lua 函数库。redis 执行 lua 时,会启动一个伪客户端去执行脚本里的 redis 命令

  • 一致性,原子性,持久性 和 MULTI,EXEC 过程相似:如果 lua 存在错误的命令名称,事务会执行失败。如果在执行 redis 命令过程出现异常,之前正常执行的命令也不会回滚
  • lua 脚本被当做一命令集合一起被执行,且 redis 是单线处理机制,因此不需要 WATCH 保证隔离性,天然具备隔离性
  • Lua调用Redis指令: redis.call("命令名称",参数1,参数2)

优点

  • 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延
  • 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。在脚本运行过程中无需担心会出现竞态条件
  • 可重复使用:客户端发送的脚本会永久存在 redis 中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑

抢红包方案

  • 问题关键点
    • 一:用户是否参与过活动,不可重复参与
    • 二:红包数量有限;而且一个可抢的红包,保证不能让多个人同时抢到
    • 三:持久化存储红包与用户的关系
    • 四:如何保证 步骤一到步骤三的原子性和隔离性

关键点一

  • redis 的集合对象 set 是无序且唯一的。set 集合由整数集合或字典实现的,添加,删除,查找的复杂度基本视为 O(1),存放的最大对象个数是2^32 - 1 (4294967295)
  • 使用 set 集合保存参加过的用户,每次用户参与活动时先判断是否在 set 里。不在则可以抢红包
  • 如果是用户可以重复参与多次的场景,则使用哈希对象,key存用户对象,value 存放参与次数。使用 INCR 原子操作增加 value,如果返回数值 > 上限,说明抢的次数用完

关键点二

使用 list 或者 set 存放事先创建好的有限个红包; 因为 redis 是单线程操作,同一时间,多人抢红包,只会有一个人成功。而红包是事先生成的,消费用完即止,不存在超发的可能

  • 使用 list 列表存放红包
    • 因为红包金额大小不一,为增加抢到红包大小的随机性,需要先shuffle一次,再 LPUSH 入队列
    • RPOP 出队列一个红包,如果返回不为nil,则代表获取成功,继续下一步,反之则说明已抢完,返回
  • set 集合中有两个指令非常适合在抢红包、抽奖的场景使用
    • SPOP key [count] 移除并返回集合中的一个随机元素
    • SRANDMEMBER key [count] 返回集合中一个或多个随机数;需要再调 SREM 移除一遍
    • 将所有的红包通过 SADD 添加到 set 中,然后通过随机命令获取对应的红包即可
  • 如果有谢谢惠顾之类的落空选项,生成对应的无效红包、奖品放入 set 或 list 即可
  • 抢红包一般是有时效性,正好可以配合 redis 的 key 的失效时间使用。使得抢红包功能很完美的解决

关键点三

  • 使用额为的 list 列表保存用户与红包的关系,用户抢到红包后,将对应的关系 LPUSH 入队列,然后服务去消费拉取数据批量保存到数据库即可

关键点四

使用 lua 脚本实现即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
lua复制代码-- 参数:KEYS[1]-红包list,KEYS[2]-用户和红包的消费list,KEYS[3]-去重的哈希对象,KEYS[4]-用户ID
-- 函数:尝试获得红包,如果成功,则返回json字符串,如果不成功,则返回nil
-- 返回值:nil 或者 json字符串,{"userId":"用户ID","id":"红包ID"}
-- 如果用户已抢过红包,则返回nil

-- 步骤一,拦截重复参与
if redis.call('hexists', KEYS[3], KEYS[4]) == 1 then
return nil
else
-- 步骤二,先取出一个红包
local lunkMoney = redis.call('rpop', KEYS[1]);
if luckMoney then
local data = cjson.decode(luckMoney);
data['userId'] = KEYS[4]; -- 加入用户ID信息
local re = cjson.encode(data);
-- 把用户ID放到去重的哈希,value设置为 1
redis.call('hset', KEYS[3], KEYS[4], 1);
-- 步骤三: 用户和红包放到已消费队列里
redis.call('lpush', KEYS[2], re);
return re;
end
end
return nil

欢迎指正文中错误

参考文章

  • redis事务一致性问题?
  • Redis的ACID属性
  • 抢红包设计
  • 腾讯二面:Redis 事务支持 ACID 么?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mac中git的使用和初始化

发表于 2021-11-21

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言

Git是我们在进行多人合作中经常使用的工具下面让我们来简单的说明一下在Mac端的安装和使用。

安装Git

1、浏览器搜索git,进入官网

image-20211121120009926

2、点击下载

image-20211121120147507

3、官方提供了几种下载方式(我们使用第一种)

image-20211121121416414

4、如果没安装Homebrew先安装Homebrew

1
arduino复制代码https://brew.sh/index_zh-cn

image-20211121121715427

5、然后安装Git

1
ruby复制代码$ brew install git

6、检查是否安装成功

1
ruby复制代码$ git

安装成功后会显示

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
sql复制代码These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
add Add file contents to the index
mv Move or rename a file, a directory, or a symlink
restore Restore working tree files
rm Remove files from the working tree and from the index
sparse-checkout Initialize and modify the sparse-checkout

examine the history and state (see also: git help revisions)
bisect Use binary search to find the commit that introduced a bug
diff Show changes between commits, commit and working tree, etc
grep Print lines matching a pattern
log Show commit logs
show Show various types of objects
status Show the working tree status

grow, mark and tweak your common history
branch List, create, or delete branches
commit Record changes to the repository
merge Join two or more development histories together
rebase Reapply commits on top of another base tip
reset Reset current HEAD to the specified state
switch Switch branches
tag Create, list, delete or verify a tag object signed with GPG

collaborate (see also: git help workflows)
fetch Download objects and refs from another repository
pull Fetch from and integrate with another repository or a local branch
push Update remote refs along with associated objects

'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.
See 'git help git' for an overview of the system.

初始化Git

1、打开终端,创建一个文件夹(如:testGit)作为Git本地仓库,并使用cd 命令进入该文件夹

1
bash复制代码cd /Users/username/Desktop/testGit

2、使用命令,创建 .git目录

1
csharp复制代码git init

image-20211121124307723

3、使用命令设置用户名和邮箱

1
arduino复制代码git config --global user.name "username"
1
css复制代码git config --global user.email useremail@qq.com

4、查看Git配置

1
lua复制代码git config --l

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis 5种基本数据类型详解

发表于 2021-11-21

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战。

Redis数据结构简介

对redis来说,所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。

r501.png

结构类型 结构存储的值 结构的读写能力
String字符串 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;
List列表 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;
Set集合 包含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等
Hash散列 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素
Zset有序集合 和散列一样,用于存储键值对 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

基础数据结构详解

String字符串

String是redis中最基本的数据类型,一个key对应一个value。

String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。

  • 命令使用
命令 简述 使用
GET 获取存储在给定键中的值 GET name
SET 设置存储在给定键中的值 SET name value
DEL 删除存储在给定键中的值 DEL name
INCR 将键存储的值加1 INCR key
DECR 将键存储的值减1 DECR key
INCRBY 将键存储的值加上整数 INCRBY key amount
DECRBY 将键存储的值减去整数 DECRBY key amount
  • 命令执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bash复制代码127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> get counter
"2"
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> get counter
"3"
127.0.0.1:6379> incrby counter 100
(integer) 103
127.0.0.1:6379> get counter
"103"
127.0.0.1:6379> decr counter
(integer) 102
127.0.0.1:6379> get counter
"102"
  • 实战场景
    • 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
    • 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
    • session:常见方案spring session + redis实现session共享,

List列表

Redis中的List其实就是链表(Redis用双端链表实现List)。

使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。

  • 命令使用
命令 简述 使用
RPUSH 将给定值推入到列表右端 RPUSH key value
LPUSH 将给定值推入到列表左端 LPUSH key value
RPOP 从列表的右端弹出一个值,并返回被弹出的值 RPOP key
LPOP 从列表的左端弹出一个值,并返回被弹出的值 LPOP key
LRANGE 获取列表在给定范围上的所有值 LRANGE key 0 -1
LINDEX 通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。 LINEX key index
  • 使用列表的技巧
    • lpush+lpop=Stack(栈)
    • lpush+rpop=Queue(队列)
    • lpush+ltrim=Capped Collection(有限集合)
    • lpush+brpop=Message Queue(消息队列)
  • 命令执行
1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码127.0.0.1:6379> lpush mylist 1 2 ll ls mem
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "mem"
2) "ls"
3) "ll"
4) "2"
5) "1"
127.0.0.1:6379> lindex mylist -1
"1"
127.0.0.1:6379> lindex mylist 10 # index不在 mylist 的区间范围内
(nil)
  • 实战场景
    • 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
    • 消息队列

Set集合

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

  • 命令使用
命令 简述 使用
SADD 向集合添加一个或多个成员 SADD key value
SCARD 获取集合的成员数 SCARD key
SMEMBER 返回集合中的所有成员 SMEMBER key member
SISMEMBER 判断 member 元素是否是集合 key 的成员 SISMEMBER key member

其它一些集合操作,请参考这里www.runoob.com/redis/redis…

  • 命令执行
1
2
3
4
5
6
7
8
bash复制代码127.0.0.1:6379> sadd myset ycf ycf1 xiao ycf
(integer) 3
127.0.0.1:6379> smember myset
1) "xiao"
2) "ycf1"
3) "ycf"
127.0.0.1:6379> sismember myset ycf
(integer) 1
  • 实战场景
    • 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
    • 点赞,或点踩,收藏等,可以放到set中实现

Hash散列

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

  • 命令使用
命令 简述 使用
HSET 添加键值对 HSET hash-key sub-key1 value1
HGET 获取指定散列键的值 HGET hash-key key1
HGETALL 获取散列中包含的所有键值对 HGETALL hash-key
HDEL 如果给定键存在于散列中,那么就移除这个键 HDEL hash-key sub-key1
  • 命令执行
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
bash复制代码127.0.0.1:6379> hset user name1 ycf
(integer) 1
127.0.0.1:6379> hset user email1 ycf@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "ycf"
3) "email1"
4) "ycf@163.com"
127.0.0.1:6379> hget user user
(nil)
127.0.0.1:6379> hget user name1
"ycf"
127.0.0.1:6379> hset user name2 xiaoycf
(integer) 1
127.0.0.1:6379> hset user email2 xiaoycf@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "ycf"
3) "email1"
4) "ycf@163.com"
5) "name2"
6) "xiaoycf"
7) "email2"
8) "xiaoycf@163.com"
  • 实战场景
    • 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。

Zset有序集合

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

  • 命令使用
命令 简述 使用
ZADD 将一个带有给定分值的成员添加到哦有序集合里面 ZADD zset-key 178 member1
ZRANGE 根据元素在有序集合中所处的位置,从有序集合中获取多个元素 ZRANGE zset-key 0-1 withccores
ZREM 如果给定元素成员存在于有序集合中,那么就移除这个元素 ZREM zset-key member1

更多命令请参考这里 www.runoob.com/redis/redis…

  • 命令执行
1
2
3
4
5
6
7
bash复制代码127.0.0.1:6379> zadd myscoreset 100 ycf 90 xiaoycf
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1) "xiaoycf"
2) "ycf"
127.0.0.1:6379> ZSCORE myscoreset ycf
"100"
  • 实战场景
    • 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

参考文章

  • www.cnblogs.com/haoprogramm…
  • www.pianshen.com/article/647…
  • www.runoob.com/redis/redis…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【python】 列表、元组、数组、双向队列 方法对比 数组

发表于 2021-11-21

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

数组 array

如果需要一个只包含数字的列表, array.array 比 list 更高效,因为数组在背后存的并不是数字对象(如 float ),而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。

数组支持所有跟可变序列有关的操作,包括 .pop 、.insert 和 .extend 。另外,数组还提供从文件读取和存入文件的更快的方法,如 .frombytes 和 .tofile 。

array.array 第一个参数指明数组元素的数据类型。Python 不会允许你在数组里存放除指定类型之外的数据。

队列 deque

collections.deque 类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型( append 和 popleft 都是原子操作)。

如果想要有一种数据类型来存放“最近用到的几个元素”,deque 也是一个很好的选择。这是因为在新建一个双向队列的时候,你可以指定这个队列的大小,如果这个队列满员了,还可以从反向端删除过期的元素,然后在尾端添加新的元素。

方法比较

方法 列表 元组 数组 双向队列 说明
s__add__(s2) 有 有 有 s + s2 ,拼接
s__iadd__(s2) 有 有 有 s += s2 ,就地拼接
s.append(e) 有 有 有 尾部添加一个新元素
s.appendleft(e) 有 尾部添加一个新元素
s.clear() 有 有 删除所有元素
s.__contains__(e) 有 有 有 s 是否包含 e
s.copy() 有 浅复制
s.__copy__() 有 有 对 copy.copy 的支持
s.__deepcopy__() 有 对 copy.deepcopy 的支持
s.count(e) 有 有 有 有 e 在 s 中出现次数
s.__delitem__(p) 有 有 有 删除位于 p 的元素
s.extend(it) 有 有 有 把可迭代对象 it 追加到 s 尾部
s.extendleft(it) 有 把可迭代对象 it 追加到 s 头部
s.__getitem__(p) 有 有 有 有 获取位于 p 的元素
s.__getnewargs__() 有 在 pickle 中支持更加优化的序列化
s.index(e) 有 有 有 找到 s 中 e 第一次出现的位置
s.insert(p, e) 有 有 在位置 p 之前插入元素 e
s.__iter__() 有 有 有 有 获取 s 的迭代器
s.__len__() 有 有 有 有 len(s) 元素个数
s.__mul__(n) 有 有 有 s * n , 重复拼接
s.__imul__(n) 有 有 s *= n , 就地重复拼接
s.__rmul__(n) 有 有 有 n * s , 反向拼接
s.pop([p]) 有 有 有(无参) 删除最后/位于 p 的元素并返回其值
s.popleft() 有 删除最前元素并返回其值
s.remove(e) 有 有 有 删除 s 中第一次出现的 e
s.reverse() 有 有 有 就地倒序
s.__reversed__() 有 有 有 返回倒序迭代器
s.rotate(n) 有 有 把 n 个元素从队列的一端移到另一端
s.__setitem__(p, e) 有 有 有 将位置 p 替换为元素 e
s.sort([key], [reverse]) 有 排序
s.byteswap 有 翻转数组内每个元素的字节序列
s.frombytes(b) 有 将压缩成机器值的字节序列读出来添加到尾部
s.fromfile(f,n) 有 将二进制文件 f 内含有机器值读出来添加到尾部,最多添加 n 项
s.fromlist(l) 有 将列表里的元素添加到尾部,如果其中任何一个元素导致了 TypeError 异常,那么所有的添加都会取消
s.tobytes() 有 把所有元素的机器值用 bytes 对象的形式返回
s.tofile(f) 有 把所有元素以机器值的形式写入一个文件
s.tolist() 有 把数组转换成列表,列表里的元素类型是数字对象
s.typecode 有 返回只有一个字符的字符串,代表数组元素在 C 语言中的类型

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

python爬虫-场内ETF基金获取

发表于 2021-11-21

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

光明总是短暂,黑暗才是永恒,你这浮夸的光,怎么能理解黑暗的深邃呢?

1 前言

之前已经介绍了基金的变动信息,但是这些基金都是属于场外的,今天我们要介绍的是一个带门槛的投资产品-ETF。只有开立证券账户的玩家才能入局,ETF 是一种场内交易型基金,可以在盘中进行交易,交易性比场外基金强一点,那么闲言少叙,马上开始介绍正题。

2 ETF列表和简称

ETF基金变动情况和基本情况的获取方式和场外基金是一样的,怎么获取比较全面的ETF基金列表呢?

1
2
bash复制代码# 获取基金信息的列表
http://fund.eastmoney.com/data/fbsfundranking.html

以下是ETF信息列表所展示的信息。

ETF 在场内进行交易时,一般都有一个简称,获取简称的方式比较麻烦一点,需要访问一个页面,然后通过 bs4 去解析元素的方式去获取。

1
2
3
bash复制代码#经过分析,我们可以发现基金代码前缀就代表这其市场,5上海市场 1-深圳市场,以地产ETF和光伏ETF为例
http://quote.eastmoney.com/sz159707.html
http://quote.eastmoney.com/sh515790.html

3 ETF 信息获取

3.1 ETF列表信获取

ETF 列表信息我们通过访问列表发现在访问列表数据时,是请求了一个api接口到了后台,然后返回给前端一个响应报文。
获取基金列表信息

1
ini复制代码http://fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=fb&ft=ct&rs=&gs=0&sc=zzf&st=desc&pi=1&pn=50

看到这里就觉得很开心,不用解析 html 文件了,当通过request使用get 方式获取数据时,发现竟然没有返回无访问权限,我想可能是没有携带cookie的原因,但是我也没有登录呀,可能是请求头需要携带一些页面信息,于是,经过尝试,最终确定了需要携带的信息为:

1
2
3
4
rust复制代码headers = {
'Host': 'fund.eastmoney.com',
'Referer': 'http://fund.eastmoney.com/data/fbsfundranking.html'
}

最终我们获取基金列表的代码应该这样写:

调试后获取到的结果如下图所示:

3.2 获取基金的简称

获取基金的简称相对比较简单,通过分析发现,简称所在的位置在
<span class="quote_title_0 wryh">光伏ETF</span> 中,通过访问页面获取元素即可拿到简称的描述。具体的代码如下图所示:

4 最终结果展示

经过获取基金列表和获取基金简称两个步骤,我们获取到了最终的结果如下图所示,已经达成了需要获取信息的目的:

基金最终结果

后续我们会把基金信息和ETF 信息进行合并存入数据库中,方便后续的数据分析。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言搬砖 cobra命令行工具包

发表于 2021-11-21

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

简介

cobra是大佬 spf13 的一个现代化的CLI工具包,业界有名的kubernetes和docker都有使用它作为客户端命令行工具

另外大佬spf13还有其他比较流行的项目,比如配置工具viper,静态网站生成器hugo等

cobra官网: github.com/spf13/cobra

cobra特点

  • 子命令编写简单
  • 支持嵌套子命令
  • 智能建议(语义化识别)
  • 自带帮助标识 -h、–help等
  • 命令和标识等自动生成(需先定义结构)
  • 完全符合POSIX的标志,包括短版和长版
  • 可以自定义帮助,用法等
  • 自动生成命令补全功能

概念

cobra建立在命令、参数和标志的结构上,命令代表动作,参数是事物,标志是这些动作的修饰符

最好的cobra命令行工具在使用时读起来像句子,非常便于理解(模式: appname command arg –flag)

安装

基于go model的方式安装

1
js复制代码go get -u github.com/spf13/cobra

在代码中引入

1
js复制代码import "github.com/spf13/cobra"

使用

编写第一个demo

此demo中定义了一个子命令print, 将接收到的参数打印出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
js复制代码package main

import (
"fmt"
"github.com/spf13/cobra"
"strings"
)

func main() {

//定义命令结构
var cmdPrint = &cobra.Command{
Use: "print",
Short: "该命令的简短描述",
Long: "该命令的详细描述",
//定义最小参数个数
Args: cobra.MinimumNArgs(1),
//命令执行的入口
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("print: "+ strings.Join(args,""))
},
}
cmdPrint.Execute()
}

运行会报错,因为结构中声明了最小参数个数
image.png

如果想在代码编辑器中运行,需要编辑配置,设置程序参数 那一行

image.png

image.png

仿docker部分命令的demo

使用cobra编写命令行工具,调用系统上的docker命令进行操作,也可以伪装其他命令

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
js复制代码package main

import (
"fmt"
"github.com/spf13/cobra"
"os"
"os/exec"
"strings"
)

func main() {

var cmdPs = &cobra.Command{
Use: "ps",
Short: "列出容器",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("--------容器列表--------")
args = []string{"ps"}
dockerPrint("docker",args)
},
}

var cmdImages = &cobra.Command{
Use: "images",
Short: "列出镜像",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("--------镜像列表--------")
args = []string{"images"}
dockerPrint("docker",args)
},
}

var cmdLogs = &cobra.Command{
Use: "logs",
Short: "查看容器日志",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("--------查看日志--------")
args = []string{"logs",strings.Join(args,"")}
dockerPrint("docker",args)
},
}

var rootCmd = &cobra.Command{Use: "docker"}
rootCmd.AddCommand(cmdPs, cmdImages,cmdLogs)
rootCmd.Execute()

}

func dockerPrint(docker string,args []string) {
cmd := exec.Command(docker, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

cmd.Run()
cmd.Wait()
}

运行后截图

image.png

总结

本文只是简单的实践了一下cobra创建命令行工具,可以发现编写命令行工具非常简单和快速。。

官网比较推荐的方法是使用cobra init直接生成骨架,然后直接写入相关逻辑,感兴趣的伙伴可以看看,传送门: github.com/spf13/cobra…

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何通过抓包来查看Kubernetes API流量 从kub

发表于 2021-11-21

当我们通过kubectl来查看、修改Kubernetes资源时,有没有想过后面的接口到底是怎样的?有没有办法探查这些交互数据呢?

Kuberenetes客户端和服务端交互的接口,是基于http协议的。所以只需要能够捕捉并解析https流量,我们就能看到kubernetes的API流量。

但是由于kubenetes使用了客户端私钥来实现对客户端的认证,所以抓包配置要复杂一点。具体是如下的结构:

capture-architecture.png

如果想了解更多Kubernetes证书的知识,可以看下这篇Kubernetes证书解析的文章

从kubeconfig中提取出客户端证书和私钥

kubeconfig中包含了客户端的证书和私钥,我们首先要把它们提取出来:

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码# 提取出客户端证书
grep client-certificate-data ~/.kube/config | \
awk '{ print $2 }' | \
base64 --decode > client-cert.pem
# 提取出客户端私钥
grep client-key-data ~/.kube/config | \
awk '{ print $2 }' | \
base64 --decode > client-key.pem
# 提取出服务端CA证书
grep certificate-authority-data ~/.kube/config | \
awk '{ print $2 }' | \
base64 --decode > cluster-ca-cert.pem

参考自Reddit

配置Charles代理软件

从第一张图可以看出,代理软件的作用有两个:一是接收https流量并转发,二是转发到kubernetes apiserver的时候,使用指定的客户端私钥。

首先配置Charles,让他拦截所有的https流量:

ssl-proxy-settings.png

然后配置客户端私钥,即对于发送到apiserver的请求,统一使用指定的客户端私钥进行认证:

client-cert-config.png

配置kubectl

需要抓包kubectl的流量,需要两个条件:1. kubectl使用Charles作为代理,2. kubectl需要信任Charles的证书。

1
2
3
4
5
6
bash复制代码# Charles的代理端口是8888,设置https_proxy环境变量,让kubectl使用Charles代理
$ export https_proxy=http://127.0.0.1:8888/
# insecure-skip-tls-verify表示不校验服务端证书
$ kubectl --insecure-skip-tls-verify get pod
NAME READY STATUS RESTARTS AGE
sc-b-7f5dfb694b-xtfrz 2/2 Running 0 2d20h

我们就可以看到get pod的网络请求了:

kubectl-get-pod.png

可以看到,get pod的endpoint是GET /api/v1/namespaces/<namespace>/pods。

让我们再尝试下创建pod的请求:

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码$ cat <<EOF >pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-robberphex
spec:
containers:
- name: nginx
image: nginx:1.14.2
EOF
$ kubectl --insecure-skip-tls-verify apply -f pod.yaml
pod/nginx-robberphex created

也同样可以抓到包:

kubectl-apply-pod.png

创建pod的endpoint是POST /api/v1/namespaces/<namespace>/pods

配置kubenetes client

我们先从写一个用kubernetes go client来获取pod的例子(注意,代码中已经信任所有的证书,所以可以抓到包):

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
golang复制代码package main

/*
require (
k8s.io/api v0.18.19
k8s.io/apimachinery v0.18.19
k8s.io/client-go v0.18.19
)
*/
import (
"context"
"flag"
"fmt"
"path/filepath"

apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)

func main() {
ctx := context.Background()
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()

config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
// 让clientset信任所有证书
config.TLSClientConfig.CAData = nil
config.TLSClientConfig.Insecure = true
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
podClient := clientset.CoreV1().Pods(apiv1.NamespaceDefault)
podList, err := podClient.List(ctx, metav1.ListOptions{})
if err != nil {
panic(err)
}

for _, pod := range podList.Items {
fmt.Printf("podName: %s\n", pod.Name)
}

fmt.Println("done!")
}

然后编译执行:

1
2
3
4
5
6
bash复制代码$ go build -o kube-client
$ export https_proxy=http://127.0.0.1:8888/
$ ./kube-client
podName: nginx-robberphex
podName: sc-b-7f5dfb694b-xtfrz
done!

这时也可以抓到同样的结果:

go-client-get-pod.png

基于此,我们就可以分析一个Kubernetes到底干了什么,也是我们分析Kubernetes​实现的入口。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Flask 入门系列之 视图!

发表于 2021-11-21

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战。

视图函数

之前的文章说过,在 Flask 中路由是请求的 url 与处理函数之间的映射,使用app.route装饰器将处理函数和 url 绑定,路由绑定的处理函数就被成为视图函数。

1
2
3
python复制代码@app.route('/user/<name>')
def hello_user(name):
return 'Hello {}!'.format(name)

上面的hello_user()函数就是一个简单的视图函数。

当然我们也可以不使用app.route装饰器,使用app.add_url_rule()方法也可将视图函数和 url 进行绑定,实际上装饰器app.route也是调用的app.add_url_rule()方法。

1
2
3
4
5
python复制代码def hello():
return 'hello Flask!'


app.add_url_rule('/hello', view_func=hello)

通过app.add_url_rule()方法,可以将路由同视图分开,方便将路由进行统一管理。

类视图

我们之前包括上面使用的,都是基于函数的视图,这虽然是最简单便捷的用法,但是不容易扩展,其实视图函数也可以基于类来实现,好处就是类支持继承,可以把一些共性的代码放在父类中,其他子类可以继承,在某些情况下,使用类更合理,更易于扩展。

类视图分为标准类视图和基于调度方法的类视图,下面分别介绍一下。

标准类视图

标准类视图的写法:

  • 父类必须继承 flask.views.View 类
  • 子类实现dispatch_request()方法,完成自身的业务逻辑并返回结果
  • 子类使用app.add_url_rule()进行注册,其中view_func参数使用as_view()方法做类方法转换
  • 如果注册时指定了endpoint参数,endpoint的值会覆盖指定的视图名称,使用url_for时就必须使用endpoint指定的值

具体使用方式如下:

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
python复制代码from flask.views import View


class ParentView(View):
def __init__(self):
super().__init__()
# 公共部分信息
self.public_data = 'Flask Web App'


class Index(ParentView):
methods = ['GET']

def dispatch_request(self):
return self.public_data + " index"


class User(ParentView):
methods = ['POST']

def dispatch_request(self):
return self.public_data + " user"


app.add_url_rule('/index', endpoint='index', view_func=Index.as_view('index'))
app.add_url_rule('/user', endpoint='user', view_func=User.as_view('user'))

上述代码中创建了一个 ParentView 类,继承自 flask.views.View 类,然后创建了 Index 和 User 两个类继承自 ParentView 类,并分别重写实现了dispatch_request()函数,使用了父类 ParentView 的属性public_data, 实现自己的业务逻辑。然后我们通过as_view()方法把类转换为实际的视图函数,as_view()必须传入一个唯一不重复的视图名。此后,这个视图由app.add_url_rule()方法和指定路由绑定。

类视图支持的 HTTP 请求方法由视图类变量methods指定,默认只支持 GET 请求。

基于方法的视图

如果视图支持多种 HTTP 请求方法的话,之前我们都是在视图函数中进行判断,根据不同的请求方法执行不同的业务逻辑,那有没有更简单的方法呢?是有的,Flask 中的方法类视图 flask.views.MethodView 就可以做到,它是 flask.views.View 的子类,通过定义和请求方式同名的小写方法来完成了逻辑处理,不必提供methods属性,每个 HTTP 方法都映射到一个具有相同名称(小写)的函数。下面看下详细使用方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python复制代码from flask.views import MethodView


class UserView(MethodView):
def get(self):
user_id = request.args.get("user_id")
return "Hello user:{}".format(user_id)

def post(self):
name = request.form.get("name")
password = request.form.get("password")
if name == "admin" and password == "123456":
return "hello admin!"
else:
return "not allow!"

app.add_url_rule('/user/get_info', view_func=UserView.as_view('get'))
app.add_url_rule('/user/login', view_func=UserView.as_view('post'))

代码中定义的get()函数用于处理 GET 请求,post()函数用于处理 POST 请求,代码中省去了 HTTP 请求方法的判断语句,而且是不是更加RESTFul一些了。

请求测试:
image.png

image.png

原创不易,如果小伙伴们觉得有帮助,麻烦点个赞再走呗~

最后,感谢女朋友在工作和生活中的包容、理解与支持 !

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python 操作腾讯对象存储(COS)详细教程 1 腾讯

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

  1. 腾讯对象存储

1.1 开通服务

  • 腾讯COS
  • 开通后会赠送免费额度

1.2 后台

在这里插入图片描述

1.3 创建桶

在这里插入图片描述

1.4 上传文件及查看

  • 上传文件

在这里插入图片描述

  • 上传后,点击详情,进入后会看到一个对象地址,复制在浏览器打开即可查看文件

在这里插入图片描述

在这里插入图片描述

“桶” 的概念可以理解为一块区域,或者是一个文件夹,能够进行数据存取

  1. python实现上传文件

点击概览,我们可以看到SDK文档,打开查找python SDK文档
在这里插入图片描述

1
arduino复制代码pip install -U cos-python-sdk-v5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码# -*- coding=utf-8
# appid 已在配置中移除,请在参数 Bucket 中带上 appid。Bucket 由 BucketName-APPID 组成
# 1. 设置用户配置, 包括 secretId,secretKey 以及 Region
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import sys

secret_id = 'COS_SECRETID' # 替换为用户的 secretId
secret_key = 'COS_SECRETKEY' # 替换为用户的 secretKey
region = 'ap-nanjing' # 替换为用户的 Region

token = None # 使用临时密钥需要传入 Token,默认为空,可不填
scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)
# 2. 获取客户端对象
client = CosS3Client(config)

secret_id & secret_key

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

region: 区域

在这里插入图片描述

1
2
3
4
5
python复制代码# 创建桶
response = client.create_bucket(
# 桶的名称
Bucket='ruochen-1301954372'
)

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
python复制代码# 上传文件
response = client.upload_file(
Bucket='ruochen-1301954372',
LocalFilePath='local.txt', # 本地文件的路径
Key='picture.jpg', # 上传到桶之后的文件名
PartSize=1, # 上传分成几部分
MAXThread=10, # 支持最多的线程数
EnableMD5=False # 是否支持MD5
)
print(response['ETag'])

2.1 上传文件示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
python复制代码# -*- coding: UTF-8 -*-
'''=================================================
@Project -> File :MyDjango -> cos_upload_demo
@IDE :PyCharm
@Author :ruochen
@Date :2020/7/17 11:52
@Desc :
=================================================='''
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client

secret_id = '自己id' # 替换为用户的 secretId
secret_key = '自己key' # 替换为用户的 secretKey
region = 'ap-nanjing' # 替换为用户的 Region

config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
client = CosS3Client(config)

response = client.upload_file(
Bucket='ruochen-1301954372',
LocalFilePath='code.png', # 本地文件的路径
Key='p1.jpg', # 上传到桶之后的文件名
)
print(response['ETag'])

在这里插入图片描述

2.2 创建桶示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码# -*- coding: UTF-8 -*-
'''=================================================
@Project -> File :MyDjango -> cos_upload_demo
@IDE :PyCharm
@Author :ruochen
@Date :2020/7/17 11:52
@Desc :
=================================================='''
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client

secret_id = '自己id' # 替换为用户的 secretId
secret_key = '自己key' # 替换为用户的 secretKey
region = 'ap-nanjing' # 替换为用户的 Region

config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
client = CosS3Client(config)

response = client.create_bucket(
Bucket='test-1301954372',
ACL='public-read', # private / public-read / public-read-wirte
)

在这里插入图片描述

最后,欢迎大家关注我的个人微信公众号 『小小猿若尘』,获取更多IT技术、干货知识、热点资讯

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

理解k8s中的事件及其原理 概述 事件原理 事件管理机制 扩

发表于 2021-11-21

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

pexels-gratisography-2007
概述
==

在k8s中事件就像信鸽一样,将各个组件发生的事情发送给apiserver,然后apiserver又会将这些信心存储到etcd中,以供有需要的人来查询集群所发生的事情。

事件在k8s中也是一种资源,用于表示集群内发生的情况。由于事件的数量可能会非常庞大,为了防止打爆存储系统,k8s会强制清理一小时之前的事件。

另外一点需要说明的是事件是单纯的用于系统诊断用的,k8s控制器不会基于事件来触发任何行为,也因此k8s清理一小时之前的事件并不会有任何副作用,可以放心清理。

事件实例

Kubernetes的每一个组件都会发出事件,这些事件就是一个要告诉你组件内部发生了什么的小信息。大部分用过Kubernetes的人都已经看到一些“kubectl describe”输出尾部的事件信息。例如:

1
2
3
4
5
6
7
8
9
10
11
sh复制代码$ kubectl describe pod/podinfo-9f56d4b58-2jj8z
Name: podinfo-9f56d4b58-2jj8z
Namespace: default
Node: ...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 42s default-scheduler Successfully assigned default/podinfo-9f56d4b58-2jj8z to node1
Normal Pulled 41s kubelet Container image "ghcr.io/stefanprodan/podinfo:5.1.2" already present on machine
Normal Created 41s kubelet Created container podinfod
Normal Started 41s kubelet Started container podinfod

这些事件告诉了你一个小故事:首先,一个pod被分配到了一个节点,接着那个节点的kubelet开始了一系列的操作:拉取镜像、创建容器并且启动它。”kubectl describe”按照从旧到新的方式输出事件,这是因为最近发生的事情一般最让人感兴趣。

这是另外一个例子,我们将镜像地址改成”example“,一个不存在的地址。

1
2
3
4
5
6
7
8
sh复制代码Type     Reason     Age            From               Message
---- ------ ---- ---- -------
Normal Scheduled 22s default-scheduler Successfully assigned default/podinfo-5487f6dc6c-gvr69 to node1
Normal BackOff 20s kubelet Back-off pulling image "example"
Warning Failed 20s kubelet Error: ImagePullBackOff
Normal Pulling 8s (x2 over 22s) kubelet Pulling image "example"
Warning Failed 6s (x2 over 20s) kubelet Failed to pull image "example": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/example": failed to resolve reference "docker.io/library/example§": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 6s (x2 over 20s) kubelet Error: ErrImagePull

你将会看到最后三行都有”x2”,这是因为Kubernetes会把重复发生的事件合并到一起。例如“Age: 6s (x2 over 20s)”,意思是最近发生的事件发生在6s之前,而第二次发生在20s之前。

第二天再来看这个pod,我得到这样的信息

1
2
3
4
sh复制代码Type     Reason   Age                     From     Message
---- ------ ---- ---- -------
Normal BackOff 19m (x4524 over 17h) kubelet Back-off pulling image "example"
Warning Failed 4m18s (x4591 over 17h) kubelet Error: ImagePullBackOff

这表明,一方面kubelet的执行策略,17个小时之后依然在尝试执行,另一方面,Kubernetes也会关注资源的使用情况,一个小时之后的事件会被自动删除。也就是说我们不能再看的那些错误的详细信息了。

事件的用处

如果你不知道哪里去找一个具体的问题,你可能需要查看所有的事件。

1
2
3
4
5
6
7
8
9
sh复制代码$ kubectl get events -A
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
...
default 46s Normal Scheduled pod/podinfo-78bbb69b79-wfzrk Successfully assigned default/podinfo-78bbb69b79-wfzrk to kind-control-plane
default 46s Normal Pulled pod/podinfo-78bbb69b79-wfzrk Container image "ghcr.io/stefanprodan/podinfo:5.1.1" already present on machine
default 46s Normal Created pod/podinfo-78bbb69b79-wfzrk Created container podinfod
default 46s Normal Started pod/podinfo-78bbb69b79-wfzrk Started container podinfod
default 47s Normal SuccessfulCreate replicaset/podinfo-78bbb69b79 Created pod: podinfo-78bbb69b79-wfzrk
default 47s Normal ScalingReplicaSet deployment/podinfo Scaled up replica set podinfo-78bbb69b79 to 1

现在你可以看到关于Pod、ReplicaSet和Deployment的不同的事件都在滚动列出。

注意:”kubectl get events” 可能会输出大量信息,特别是集群非常繁忙时。令人头疼的是事件并不是按照时间戳排列的,因此你需要有办法来寻找或者将这些信息输出到文件中进行分析。

时间的命名方式为: “InvoledObject对象名称”+”.”+”事件创建纳秒时间戳”

1
go复制代码fmt.Sprintf("%v.%x", ref.Name, t.UnixNano())

一般事件本身的命名意义不是很大,而其产生对象的名称更有意义,通过这个名称来查询它所发生的事件,当然也可能查不到,因为事件超过一个小时就会被删除。

事件原理

事件结构

Kubernetes使用对象模型存储事件,正如Deployment和Pod那样。我们通过kubectl来看一下Event的内部信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
yaml复制代码$ kubectl get event/podinfo-84b5bccbfd-rgb42.166185b1cd1dc668 -o yaml
apiVersion: v1
kind: Event
metadata:
name: podinfo-84b5bccbfd-rgb42.166185b1cd1dc668
namespace: default
type: Normal
count: 4636
firstTimestamp: "2021-02-07T16:59:00Z"
lastTimestamp: "2021-02-08T10:39:04Z"
message: Back-off pulling image "example"
reason: BackOff
source:
component: kubelet
host: node1
involvedObject:
apiVersion: v1
kind: Pod
name: podinfo-84b5bccbfd-rgb42
namespace: default

正如你所看到的,每一个事件都是一个对象,属于一个命名空间,拥有一个唯一的名字,其他主要字段详述如下:

  • Count,firstTimestamp和lasteTimestamp 表示事件重复了多少次
  • Message 人类可读的文本信息
  • Reason 简短的编码,可用于过滤器
  • Type Normal或者Warning
  • Source 事件发出的来源
  • InvolvedObject 引用的另一个Kubernetes对象,例如Pod或者Deployment

现在我们清楚了,通过kubectl describe展示的事件列表,正是InvoledObject匹配的事件。

事件管理机制

Event事件管理主要包含三部分:

  • EventRecorder:事件生成器,k8s组件通过调用它的方法来生成事件;
  • EventBroadcaster:事件广播器,负责消费EventRecorder产生的事件,然后分发给broadcasterWatcher;
  • broadcasterWatcher:用于定义事件的处理方式,如上报apiserver;

事件管理的整体流程如下:

image-20201011221745830
扩展阅读
====

www.kubernetes.org.cn/1031.html这是一个系列文章,分为上中下三篇,从浅入深以通俗的方式讲解了k8s的事件机制,值得一看。

kubernetes.io/docs/tasks/… 官方文档,介绍了如何通过事件调试应用程序,推荐查阅,并最好实际操作一下

www.bluematador.com/blog/kubern… 详细解释了事件使用

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…254255256…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%