macOS 如何隐藏文件或文件夹

修改文件隐藏属性
在终端中输入

chflags hidden +空格
然后拖入文件,然后按回车键(return)执行也可以隐藏该文件。

新建隐藏文件
打开终端(Terminal),在终端中输入 mkdir 文件位置/.文件名称,如

mkdir desktop/.AiMe
按回车键(return)执行命令,即可创建一个隐藏文件夹(.AiMe)。

我们我们通过按下键盘快捷键 Shift+command+. 就可以将这个隐藏文件夹显示出来。

通过Mac软件安装包藏匿文件/文件夹
Mac应用程序都是一些 .app 的文件夹,在应用程序的图标上右键会有一个显示包内容的选项,在这个包里面装的都是程序文件和资源文件,一般人是不会访问这里的。

把秘密文件拖进这些包里面,也是一种很不错的方式,只是要记得放哪个应用程序,不然就要大海捞针了。或者 将秘密文件命名为.app 的文件夹,这样就不怕忘啦~

(0068)iOS开发之AutoLayout框架Masonry使用心得

苹果官方给出了一些有关自动布局的建议

不论用interfaceBuilder还是用代码来实现自动布局,这些建议都是适用的。
(1)不用view的frame、bounds、center来指定view的形状
(2)尽可能地使用stackView来布局
(3)约束尽量建立在view和其相邻view之间
(4)避免给view指定固定的长和宽
(5)自动更新view的frame时要留心,尤其是对于约束条件不足的view。
(6)view的命名要有意义,方便布局时认得它们。
(7)使用leading和trailing约束,不要用left和right。
(8)在写相对于view边界的约束的时候,分两种情况:
水平方向上的约束:对于大多数的控件,约束应该相对于根视图的内边界
对于像小说阅读器这样文字布满屏幕的情况,约束应该相对于文本边界。
对于需要铺满根视图宽度的视图,约束可以相对于根视图的边界。
垂直方向上的约束:如果根视图有被导航栏、tabBar等部分遮挡了,那么约束应该相对于top margin和bottom margin。
(9)在使用autolayout来布局那些用代码创建的view的时候,要把他们的translatesAutoresizingMaskIntoConstraints属性设置为NO。这个属性如果设为YES,系统会自动为这些view生成一些约束,这些约束可能会和我们设置的约束产生冲突。

AutoLayout框架Masonry使用心得

我们组分享会上分享了页面布局的一些写法,中途提到了AutoLayout,会后我决定将很久前挖的一个坑给填起来(还有好多坑就不说了,说了不填更毁形象了)。

可使用的框架首推Masonry,关于为啥选择Masonry看看官方文档就明白了https://github.com/SnapKit/Masonry,官方称AutoLayout所有功能Masonry都支持。这次项目界面方面我就全部使用了Masonry。

AutoLayout的一些基本概念

  • 利用约束来控制视图的大小和位置,系统会在运行时通过设置的约束计算得到frame再绘制屏幕
  • 两个属性Content Compression Resistance(排挤,值越高越固定)和Content Hugging(拥抱),Masonry代码如下
  1. //content hugging 为1000
  2. [view setContentHuggingPriority:UILayoutPriorityRequired
  3. forAxis:UILayoutConstraintAxisHorizontal];
  4. //content compression 为250
  5. [view setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
  6. forAxis:UILayoutConstraintAxisHorizontal];
  • multipler属性表示约束值为约束对象的百分比,在Masonry里有对应的multipliedBy函数
  1. //宽度为superView宽度的20%
  2. make.width.equalTo(superView.mas_width).multipliedBy(0.2);
  • AutoLayout下UILabel设置多行计算需要设置preferredMaxLayoutWidth
label.preferredMaxWidth = [UIScreen mainScreen].bounds.size.width - margin - padding;
  • preferredMaxLayoutWidth用来制定*大的宽,一般用在多行的UILabel中
  • systemLayoutSizeFittingSize方法能够获得view的高度
  • iOS7有两个很有用的属性,topLayoutGuide和bottomLayoutGuide,这个两个主要是方便获取UINavigationController和UITabBarController的头部视图区域和底部视图区域。
  1. //Masonry直接支持这个属性
  2. make.top.equalTo(self.mas_topLayoutGuide);

AutoLayout关于更新的几个方法的区别

  • setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
  • layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
  • layoutSubviews:系统重写布局
  • setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
  • updateConstraintsIfNeeded:告知立刻更新约束
  • updateConstraints:系统更新约束

Masonry使用注意事项

  • 用mas_makeConstraints的那个view需要在addSubview之后才能用这个方法
  • mas_equalTo适用数值元素,equalTo适合多属性的比如make.left.and.right.equalTo(self.view)
  • 方法and和with只是为了可读性,返回自身,比如make.left.and.right.equalTo(self.view)和make.left.right.equalTo(self.view)是一样的。
  • 因为iOS中原点在左上角所以注意使用offset时注意right和bottom用负数。

Masonry适配iOS6和iOS7时需要注意的问题

开发项目时是先在iOS8上调试完成的,测试时发现低版本的系统会发生奔溃的现象,修复后总结问题主要是在equalTo的对象指到了父视图的父视图或者父视图同级的子视图上造成的,所以做约束时如果出现了奔溃问题百分之九十都是因为这个。

Masonry使用范例

基本写法

  1. //相对于父视图边距为10
  2. UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
  3. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  4. make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
  5. make.left.equalTo(superview.mas_left).with.offset(padding.left);
  6. make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
  7. make.right.equalTo(superview.mas_right).with.offset(-padding.right);
  8. }];
  9. //相对于父视图边距为10简洁写法
  10. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  11. make.edges.equalTo(superview).with.insets(padding);
  12. }];
  13. //这两个作用完全一样
  14. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  15. make.left.greaterThanOrEqualTo(self.view);
  16. make.left.greaterThanOrEqualTo(self.view.mas_left);
  17. }];
  18. //.equalTo .lessThanOrEqualTo .greaterThanOrEqualTo使用NSNumber
  19. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  20. make.width.greaterThanOrEqualTo(@200);
  21. make.width.lessThanOrEqualTo(@400);
  22. make.left.lessThanOrEqualTo(@10);
  23. }];
  24. //如果不用NSNumber可以用以前的数据结构,只需用mas_equalTo就行
  25. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  26. make.top.mas_equalTo(42);
  27. make.height.mas_equalTo(20);
  28. make.size.mas_equalTo(CGSizeMake(50, 100));
  29. make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
  30. make.left.mas_equalTo(self.view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
  31. }];
  32. //也可以使用数组
  33. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  34. make.height.equalTo(@[self.view.mas_height, superview.mas_height]);
  35. make.height.equalTo(@[self.view, superview]);
  36. make.left.equalTo(@[self.view, @100, superview.mas_right]);
  37. }];
  38. // priority的使用
  39. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  40. make.left.greaterThanOrEqualTo(self.view.mas_left).with.priorityLow();
  41. make.top.equalTo(self.view.mas_top).with.priority(600);
  42. }];
  43. //同时创建多个约束
  44. [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
  45. //让top,left,bottom,right都和self.view一样
  46. make.edges.equalTo(self.view);
  47. //edges
  48. make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(5, 10, 15, 20));
  49. //size
  50. make.size.greaterThanOrEqualTo(self.view);
  51. make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50));
  52. //center
  53. make.center.equalTo(self.view);
  54. make.center.equalTo(self.view).centerOffset(CGPointMake(-5, 10));
  55. //chain
  56. make.left.right.and.bottom.equalTo(self.view);
  57. make.top.equalTo(self.view);
  58. }];

AutoLayout情况如何计算UITableView的变高高度

主要是UILabel的高度会有变化,所以这里主要是说说label变化时如何处理,设置UILabel的时候注意要设置preferredMaxLayoutWidth这个宽度,还有ContentHuggingPriority为UILayoutPriorityRequried

  1. CGFloat maxWidth = [UIScreen mainScreen].bounds.size.width – 10 * 2;
  2. textLabel = [UILabel new];
  3. textLabel.numberOfLines = 0;
  4. textLabel.preferredMaxLayoutWidth = maxWidth;
  5. [self.contentView addSubview:textLabel];
  6. [textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  7. make.top.equalTo(statusView.mas_bottom).with.offset(10);
  8. make.left.equalTo(self.contentView).with.offset(10);
  9. make.right.equalTo(self.contentView).with.offset(-10);
  10. make.bottom.equalTo(self.contentView).with.offset(-10);
  11. }];
  12. [_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

如果版本支持*低版本为iOS 8以上的话可以直接利用UITableViewAutomaticDimension在tableview的heightForRowAtIndexPath直接返回即可。

  1. tableView.rowHeight = UITableViewAutomaticDimension;
  2. tableView.estimatedRowHeight = 80; //减少*次计算量,iOS7后支持
  3. – (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  4. // 只用返回这个!
  5. return UITableViewAutomaticDimension;
  6. }

但如果需要兼容iOS 8之前版本的话,就要回到老路子上了,主要是用systemLayoutSizeFittingSize来取高。步骤是先在数据model中添加一个height的属性用来缓存高,然后在table view的heightForRowAtIndexPath代理里static一个只初始化一次的Cell实例,然后根据model内容填充数据,*后根据cell的contentView的systemLayoutSizeFittingSize的方法获取到cell的高。具体代码如下

  1. //在model中添加属性缓存高度
  2. @interface DataModel : NSObject
  3. @property (copy, nonatomic) NSString *text;
  4. @property (assign, nonatomic) CGFloat cellHeight; //缓存高度
  5. @end
  6. – (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  7. static CustomCell *cell;
  8. //只初始化一次cell
  9. static dispatch_once_t onceToken;
  10. dispatch_once(&onceToken, ^{
  11. cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CustomCell class])];
  12. });
  13. DataModel *model = self.dataArray[(NSUInteger) indexPath.row];
  14. [cell makeupData:model];
  15. if (model.cellHeight <= 0) {
  16. //使用systemLayoutSizeFittingSize获取高度
  17. model.cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1;
  18. }
  19. return model.cellHeight;
  20. }

动画

因为布局约束就是要脱离frame这种表达方式的,可是动画是需要根据这个来执行,这里面就会有些矛盾,不过根据前面说到的布局约束的原理,在某个时刻约束也是会被还原成frame使视图显示,这个时刻可以通过layoutIfNeeded这个方法来进行控制。具体代码如下

  1. [aniView mas_makeConstraints:^(MASConstraintMaker *make) {
  2. make.top.bottom.left.right.equalTo(self.view).offset(10);
  3. }];
  4. [aniView mas_updateConstraints:^(MASConstraintMaker *make) {
  5. make.top.equalTo(self.view).offset(30);
  6. }];
  7. [UIView animateWithDuration:3 animations:^{
  8. [self.view layoutIfNeeded];
  9. }];

ios开发中masonry的使用心得

去年做项目时用到一个第三方自动布局的框架——Masonry,期间碰到过一些问题,现在在此总结一下:

1.添加约束后APP崩溃的问题:(所有问题原因都能归根结底到子view没有成功加到父view上就设置约束)

①如果是ios7及ios7以下的系统,崩溃的大部分之前在网络上查过,主要元原因说“主要是在equalTo的对象指到了父视图的父视图或者父视图同级的子视图上造成的”,后来将约束equalto全都指向了父view或者同级别的view上,之后的确奏效;

②如果是ios7以上的系统,系统崩溃的可能原因:a)还没有将子view添加到父view上就开始添加约束,这样view完全找不到自己的约束根基在哪里,必然会崩;b)之前遇到过一个设置navigationbar的leftbuttonitem和rightbuttonitem,但是我在viewdidload()方法直接设置约束就会引发崩溃,原因可能是在跳转该界面时(页面push进来时)navigationbar还没有加载完,此时就开始添加buttonitem并设置约束,所以解决方案是延时设置navigationbar的样式:

dispatch_after(time, dispatch_get_main_queue(), ^{

[XXX];

});

2.添加约束不起作用的问题:

①设置的约束不能确定控件的唯一位置,除非有需要自适应高度的控件,需要其他地方重新控制高度;

②取消掉所有控件的use autolayout 和autoresize subviews属性,因为这是系统自带的autolayout所以需要去掉才能发挥masonry的用处;

3)在一些跳转的界面,有的控件还没有加载完就设置约束肯定不会起作用的,例如之前我用MJRefresh进行下拉上拉刷新时,页面刚跳转进来时tableview还没有加载完就设置了header,此时header的约束肯定不能成功,所以解决方案也是采用延时处理;

*后放一下从网络上摘取的几个刷新布局的方法,给自己mark一下:

setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
layoutSubviews:系统重写布局
setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
updateConstraintsIfNeeded:告知立刻更新约束
updateConstraints:系统更新约束
这只是目前刚用masonry不久的一点小心得,如果不对,请各位大神指正啊(*^__^*)

Python 简化函数调用的3种技巧

Python 简化函数调用的3种技巧
亚马逊又要放大招?7月15日亚马逊大咖在线直播带你一探究竟
全能型容器服务的技术架构是什么样?亚马逊容器专家在线深度剖析并演示,马上预约!
假设有一个函数,这个函数需要接收4个参数,并返回这4个参数的和:
def sum_four(a, b, c, d):
  return a + b + c + d
如果需要固定*后前三个参数,仅改变*后一个参数的值,这时候可能需要这么调用:
>>> a, b, c = 1, 2, 3
>>> sum_four(a=a, b=b, c=c, d=1)
7
>>> sum_four(a=a, b=b, c=c, d=2)
8
>>> sum_four(a=a, b=b, c=c, d=3)
9
>>> sum_four(a=a, b=b, c=c, d=4)
10
这样写实在是太丑了,如果用 Map 函数,是否能简化代码?
答案是肯定的,但是Map函数【一般】只能接受单一元素,如果你强行使用的话,它会报这样的错:
>>> list(map(sum_four, [(1, 2, 3, 4)]))
Traceback (most recent call last):
  File “<stdin>”, line 1, in <module>
TypeError: sum_four() missing 3 required positional arguments: ‘b’, ‘c’, and ‘d’
怎么解决?
方案1: itertools.starmap
可以使用 itertools 的函数 starmap 替换Map.
它与Map不同,允许接受一个元组作为传入sum_four的参数。
>>> import itertools
>>> list(itertools.starmap(sum_four, [(1, 2, 3, 4)]))
[10]
非常棒,这样的话,上述问题就可以使用starmap函数解决:
>>> import itertools
>>> ds = [1, 2, 3, 4]
>>> items = ((a, b, c, d) for d in ds)
>>> list(items)
 [(1, 2, 3, 1), (1, 2, 3, 2), (1, 2, 3, 3), (1, 2, 3, 4)]
>>> list(itertools.starmap(sum_four, items))
 [7, 8, 9, 10]
请注意 items 是一个生成器,这是为了避免 items 过大导致内存消耗量过大。平时开发的时候注意这些细节,能够使你和普通的开发者拉开差距。
方案2: functools.partial
第二种解决方案是使用 partial 函数固定前三个参数。
根据文档,partial将“冻结”函数的参数的某些部分,从而生成简化版的函数。
因此上述问题的解决方案就是:
>>> import functools
>>> partial_sum_four = functools.partial(sum_four, a, b, c)
>>> partial_sum_four(3)
9
>>> # 这样就可以使用map函数了:
>>> list(map(partial_sum_four, ds))
[7, 8, 9, 10]
方案3: itertools.repeat()
事实上,Map 函数是允许传递可迭代参数的,但是有一个有趣的特点,他会用每个可迭代对象里的项作为传入函数的不同参数。这样说可能太过于抽象了,来看看实际的例子:
>>> list(map(sum_four, [1,1,1,1], [2,2,2,2], [3,3,3,3], [1,2,3,4]))
 [7, 8, 9, 10]
明显,每次都使用了不同数组中对应下标的项传入函数进行计算。
这样,我们可以使用这个特点进行优化。
itertools.repeat() 函数能够根据参数产生一个迭代器,该迭代器一次又一次返回对象。不指定times参数,它将无限期运行。
而 Map 函数会在*短的可迭代对象被迭代完后,就会自动停止运行。
结合这两个特点,上述问题的解决方案就出来了:
>>> import itertools
>>> list(map(sum_four, itertools.repeat(a), itertools.repeat(b), itertools.repeat(c), ds))
 [7, 8, 9, 10]
这招非常巧妙,缺点是能读懂的人不多。不过没关系,计算机世界中某些东西知道就好,你并不一定需要去使用它

字典合并与更新运算符

1.字典合并与更新运算符
在Python3.9以前,你可能需要这样合并字典:
d1 = {‘name’: ‘revotu’, ‘age’: 99}
d2 = {‘age’: 24, ‘sex’: ‘male’}
# 方法1,使用两次update方法向字典中添加元素
d = {}
d.update(d1)
d.update(d2)
print(d)
# 方法2,先复制,后更新,缺点是增加了一个临时变量
d = d1.copy()
d.update(d2)
print(d)
# 方法3,字典构造器
d = dict(d1)
d.update(d2)
print(d)
# 方法4,关键字参数hack
# 只有一行代码,看上去很酷,缺点是这种hack技巧只有在字典的键是字符串时才有效。
d = dict(d1, **d2)
print(d)
# 方法5,字典拆分,在Python3.5+中,可以使用一种全新的字典合并方式,这行代码很pythonic
d = {**d1, **d2}
print(d)
现在,Python 3.9之后,合并 ( | ) 与更新 ( |= ) 运算符已被加入内置的 dict 类。它们为现有的 dict.update 和 {**d1, **d2} 字典合并方法提供了补充。
>>> x = {“key1”: “value1 from x”, “key2”: “value2 from x”}
>>> y = {“key2”: “value2 from y”, “key3”: “value3 from y”}
>>> x | y
{‘key1’: ‘value1 from x’, ‘key2’: ‘value2 from y’, ‘key3’: ‘value3 from y’}
>>> y | x
{‘key2’: ‘value2 from x’, ‘key3’: ‘value3 from y’, ‘key1’: ‘value1 from x’}
2.新增用于移除前缀和后缀的字符串方法
增加了  和  用于方便地从字符串移除不需要的前缀或后缀。也增加了 bytes, bytearray  以及 collections.UserString 的对应方法。
内置的str类将获得两个新方法,它们的源代码如下:
def removeprefix(self: str, prefix: str, /) -> str:
    if self.startswith(prefix):
        return self[len(prefix):]
    else:
        return self[:]
def removesuffix(self: str, suffix: str, /) -> str:
    # suffix=” should not call self[:-0].
    if suffix and self.endswith(suffix):
        return self[:-len(suffix)]
    else:
        return self[:]
它的使用方法如下:
>> “HelloWorld”.removesuffix(“World”)
‘Hello’
>> “HelloWorld”.removeprefix(“Hello”)
‘World’
3.类型标注支持更多通用类型
在类型标注中现在你可以使用内置多项集类型例如 list 和 dict 作为通用类型而不必从 typing 导入对应的大写形式类型名 (例如 List 和 Dict)。
比如:
def greet_all(names: list[str]) -> None:
    for name in names:
        print(“Hello”, name)
4.标准库新增 zoneinfo 模块
zoneinfo 模块为标准库引入了 IANA 时区数据库。它添加了 zoneinfo.ZoneInfo,这是一个基于系统时区数据的实体 datetime.tzinfo 实现。
>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime, timedelta
>>> # 夏时制 – 07:00
>>> dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo(“America/Los_Angeles”))
>>> print(dt)
2020-10-31 12:00:00-07:00
>>> dt.tzname()
‘PDT’
>>> # 加7天,夏时制结束 – 08:00
>>> dt += timedelta(days=7)
>>> print(dt)
2020-11-07 12:00:00-08:00
>>> print(dt.tzname())
PST
5.标准库新增 graphlib 模块
支持针对图的操作,比如获取拓扑排序:
>>> from graphlib import TopologicalSorter
>>> graph = {“D”: {“B”, “C”}, “C”: {“A”}, “B”: {“A”}}
>>> ts = TopologicalSorter(graph)
>>> tuple(ts.static_order())
(‘A’, ‘C’, ‘B’, ‘D’)
关于graphlib模块更多信息可见:
https://docs.python.org/zh-cn/3/library/graphlib.html#module-graphlib
6.Math模块迎来调整
以前gcd计算*大公因数的函数只能应用于2个数字,这就很蛋疼,我们必须使用 math.gcd(80, math.gcd(64, 152))来处理大于2个数字的情况。而现在 gcd 允许计算任意数量的数字:
import math
# Greatest common divisor
math.gcd(80, 64, 152)
# 8
并新增了一个函数,计算*小公倍数:
# *小公倍数
math.lcm(4, 8, 5)
# 40
用法与gcd一样,它允许可变数量的参数。
7.os模块
以前Windows系统下是无法使用 os.unsetenv 的,这个问题在Python3.9已被修复。现在Windows系统也支持 os.unsetenv 了:
def __init__(self, *args, **kw):
    if hasattr(os, ‘unsetenv’):
        os.unsetenv(‘_MEIPASS2’)
    else:
        os.putenv(‘_MEIPASS2’, ”)
8.random模块: 新增随机字节串
现在你可以使用random下的randbytes函数随机生成字节:
>> from random import randbytes
>> randbytes(4)
b’\xf3\xf5\xf8\x98′
9.性能优化
重点优化:
1.多个 Python 内置类型 (range, tuple, set, frozenset, list, dict) 现在通过使用 PEP 590 向量调用协议得到加速。
2.优化了在推导式中为临时变量赋值的惯用方式。现在推导式中的 for y in [expr] 会与简单赋值语句 y = expr 一样快速。例如:
sums = [s for s in [0] for x in data for s in [s + x]]
不同于 := 运算符,这个惯用方式不会使变量泄露到外部作用域中。
3.优化了多线程应用的信号处理。如果一个线程不是获得信号的主线程,字节码求值循环不会在每条字节码指令上被打断以检查无法被处理的挂起信号。只有主解释器的主线程能够处理信号。
以下是对从 Python 3.4 到 Python 3.9 的性能提升情况的总结:
Python version                       3.4     3.5     3.6     3.7     3.8    3.9
————–                       —     —     —     —     —    —
Variable and attribute read access:
    read_local                       7.1     7.1     5.4     5.1     3.9    3.9
    read_nonlocal                    7.1     8.1     5.8     5.4     4.4    4.5
    read_global                     15.5    19.0    14.3    13.6     7.6    7.8
    read_builtin                    21.1    21.6    18.5    19.0     7.5    7.8
    read_classvar_from_class        25.6    26.5    20.7    19.5    18.4   17.9
    read_classvar_from_instance     22.8    23.5    18.8    17.1    16.4   16.9
    read_instancevar                32.4    33.1    28.0    26.3    25.4   25.3
    read_instancevar_slots          27.8    31.3    20.8    20.8    20.2   20.5
    read_namedtuple                 73.8    57.5    45.0    46.8    18.4   18.7
    read_boundmethod                37.6    37.9    29.6    26.9    27.7   41.1
Variable and attribute write access:
    write_local                      8.7     9.3     5.5     5.3     4.3    4.3
    write_nonlocal                  10.5    11.1     5.6     5.5     4.7    4.8
    write_global                    19.7    21.2    18.0    18.0    15.8   16.7
    write_classvar                  92.9    96.0   104.6   102.1    39.2   39.8
    write_instancevar               44.6    45.8    40.0    38.9    35.5   37.4
    write_instancevar_slots         35.6    36.1    27.3    26.6    25.7   25.8
Data structure read access:
    read_list                       24.2    24.5    20.8    20.8    19.0   19.5
    read_deque                      24.7    25.5    20.2    20.6    19.8   20.2
    read_dict                       24.3    25.7    22.3    23.0    21.0   22.4
    read_strdict                    22.6    24.3    19.5    21.2    18.9   21.5
Data structure write access:
    write_list                      27.1    28.5    22.5    21.6    20.0   20.0
    write_deque                     28.7    30.1    22.7    21.8    23.5   21.7
    write_dict                      31.4    33.3    29.3    29.2    24.7   25.4
    write_strdict                   28.4    29.9    27.5    25.2    23.1   24.5
Stack (or queue) operations:
    list_append_pop                 93.4   112.7    75.4    74.2    50.8   50.6
    deque_append_pop                43.5    57.0    49.4    49.2    42.5   44.2
    deque_append_popleft            43.7    57.3    49.7    49.7    42.8   46.4
Timing loop:
    loop_overhead                    0.5     0.6     0.4     0.3     0.3    0.3
可以看到,Python 3.9 相比于 Python 3.6 提升还是非常明显的,比如在列表的append 和 pop 操作上,性能提升了30%、在全局变量的读取上,性能提高了45%。
如果你的项目比较小型,升级到Python3.9的成本问题和依赖问题不多,还是非常推荐升级的。
10.未来可期
这一个更改你可能看不见、摸不着,但它可能改变Python的未来。

永远不要使用print进行调试

永远不要使用print进行调试
PySnooper 是一个非常方便的调试器。如果您正在试图弄清楚为什么您的Python代码没有按照您的预期去做,您会希望使用具有断点和监视功能的成熟Debug工具,但是许多Debug工具配置起来非常麻烦。
现在,有了PySnooper,您并不需要配置那么复杂的Debug工具,就能够完成对整个代码的分析。它能告诉您哪些代码正在运行,以及局部变量的值是什么。
其实,PySnooper 就是替代了一行一行print的重复性工作,给你的代码一个pysnooper装饰器,它能自动识别到语句和变量并将其值print出来:
import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
    if number:
        bits = []
        while number:
            number, remainder = divmod(number, 2)
            bits.insert(0, remainder)
        return bits
    else:
        return [0]
number_to_bits(6)
效果如下:
Source path:… 1.py
Starting var:.. number = 6
23:03:35.990701 call         4 def number_to_bits(number):
23:03:35.991699 line 5     if number:
23:03:35.991699 line 6         bits = []
New var:……. bits = []
23:03:35.991699 line 7         while number:
23:03:35.991699 line 8             number, remainder = divmod(number, 2)
Modified var:.. number = 3
New var:……. remainder = 0
23:03:35.991699 line 9             bits.insert(0, remainder)
Modified var:.. bits = [0]
23:03:36.004664 line 7         while number:
23:03:36.005661 line 8             number, remainder = divmod(number, 2)
Modified var:.. number = 1
Modified var:.. remainder = 1
23:03:36.005661 line 9             bits.insert(0, remainder)
Modified var:.. bits = [1, 0]
23:03:36.007657 line 7         while number:
23:03:36.007657 line 8             number, remainder = divmod(number, 2)
Modified var:.. number = 0
23:03:36.008655 line 9             bits.insert(0, remainder)
Modified var:.. bits = [1, 1, 0]
23:03:36.008655 line 7         while number:
23:03:36.009651 line 10         return bits
23:03:36.009651 return      10         return bits
Return value:.. [1, 1, 0]
Elapsed time: 00:00:00.020945
可以看到,它将每一行变量的值都输出到屏幕上,方便你调试代码。
仅仅需要写一行代码—使用装饰器就可以实现这个方便的调试功能,比起一行行写print,这可方便多了。
0.安装模块
使用这个模块,你只需要使用Pip安装PySnooper:
pip install pysnooper
接下来讲讲这个模块其他好用的功能:
1.支持日志文件
如果你觉得print到屏幕上不方便,还可以将其输出到log文件中,你只需要将装饰器那一行改为:
@pysnooper.snoop(‘/my/log/file.log’)
2.读取局外变量或其他表达式
如果你想读取在装饰器作用范围以外的变量或者表达式的值,还可以使用watch参数:
@pysnooper.snoop(watch=(‘foo.bar’, ‘self.x[“whatever”]’))
3.如果你不想用装饰器,也可以用上下文的形式调试
没错,装饰器有限定的使用条件,使用起来比较局限,因此pysnooper还支持使用 with 的上下文形式:
import pysnooper
import random
def foo():
    lst = []
    for i in range(10):
        lst.append(random.randrange(1, 1000))
    with pysnooper.snoop():
        lower = min(lst)
        upper = max(lst)
        mid = (lower + upper) / 2
        print(lower, mid, upper)
foo()
效果如下,只有上下文里的代码才会被调试出来:
New var:……. i = 9
New var:……. lst = [681, 267, 74, 832, 284, 678, …]
09:37:35.881721 line 10         lower = min(lst)
New var:……. lower = 74
09:37:35.882137 line 11         upper = max(lst)
New var:……. upper = 832
09:37:35.882304 line 12         mid = (lower + upper) / 2
74 453.0 832
New var:……. mid = 453.0
09:37:35.882486 line 13         print(lower, mid, upper)
Elapsed time: 00:00:00.000344
当我们只需要调试部分代码的时候,这个上下文形式的调试方法非常方便。

超简单一键美化你的文章—使其更具可读性

超简单一键美化你的文章—使其更具可读性
亚马逊又要放大招?7月15日亚马逊大咖在线直播带你一探究竟
全能型容器服务的技术架构是什么样?亚马逊容器专家在线深度剖析并演示,马上预约!
在平时写文章的时候,我都会注意在中文和英文单词之间保留一个空格的习惯,这样能使文本具有良好的可读性。
但是我经常忽略某些半角字符(数字和符号)与中文之间的空格,导致可读性比较差,在阅读别人的文章或者修改别人的文章时候,也经常为烦恼他人没有这种优化可读性的细节。
现在,有一个很棒的工具,叫做 pangu , 它可以在中文、日文、韩文和半角字符(字母,数字和符号)之间自动插入空格。
有了它,你可以在每次写完文章后利用 pangu 一键美化文章。也可以用 pangu 美化别人的文章,比如:
import pangu
new_text = pangu.spacing_text(‘你可以在每次写完文章后利用pangu一键美化文章。也可以用pangu 美化别人的文章:’)
print(new_text)
# new_text = ‘你可以在每次写完文章后利用 pangu 一键美化文章。也可以用 pangu 美化别人的文章:’
1.准备
开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,可以访问这篇文章:超详细Python安装指南 进行安装。
如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.
此外,推荐大家用VSCode编辑器,它有许多的优点:Python 编程的*好搭档—VSCode 详细指南。
请选择以下任一种方式输入命令安装依赖:
1. Windows 环境 打开 Cmd (开始-运行-CMD)。
2. MacOS 环境 打开 Terminal (command+空格输入Terminal)。
3. 如果你用的是 VSCode编辑器 或 Pycharm,可以直接使用界面下方的Terminal.
pip install -U pangu
2.使用
安装完成之后,你可以尝试写一些简单的句子并美化它们:
import pangu
new_text = pangu.spacing_text(‘Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal)’)
print(new_text)
# new_text = ‘Windows 环境下打开 Cmd (开始 — 运行 —CMD),苹果系统环境下请打开 Terminal (command + 空格输入 Terminal)’
一键执行
你也可以不写 python 文件,直接通过 -m 参数执行命令美化文本:
python -m pangu “為什麼小明有問題都不Google?因為他有Bing”
# 為什麼小明有問題都不 Google?因為他有 Bing
此外,pangu 也支持 pangu 命令直接格式化文本:
pangu “請使用uname -m指令來檢查你的Linux作業系統是32位元或是[敏感词已被屏蔽]位元”
# 請使用 uname -m 指令來檢查你的 Linux 作業系統是 32 位元或是 [敏感词已被屏蔽] 位元
此外,pangu 也支持 pangu 命令直接格式化文本:
文件支持
通过 -f 参数,pangu 支持把指定的文件内容进行美化,然后输出到另一个文件中:
echo “未來的某一天,Gmail配備的AI可能會得出一個結論:想要消滅垃圾郵件*好的辦法就是消滅人類” >> path/to/file.txt
pangu -f path/to/file.txt >> pangu_file.txt
cat pangu_file.txt
# 未來的某一天,Gmail 配備的 AI 可能會得出一個結論:想要消滅垃圾郵件*好的辦法就是消滅人類
管道支持 (UNIX)
在 UNIX 系统中,比如 Linux 和 MacOS,pangu还支持使用管道 ( | ) 命令美化文本:
echo “心裡想的是Microservice,手裡做的是Distributed Monolith” | pangu
# 心裡想的是 Microservice,手裡做的是 Distributed Monolith
echo “你從什麼時候開始產生了我沒使用Monkey Patch的錯覺?” | python -m pangu
# 你從什麼時候開始產生了我沒使用 Monkey Patch 的錯覺?
两句命令的效果一样,如果你无法直接使用 pangu 命令,可以尝试 python -m pangu,他们能达到一样的效果。

硬核源码剖析 Celery Beat 调度原理

硬核源码剖析 Celery Beat 调度原理
有*调研:参与调研问卷,赢*品!
Intel 联手 CSDN 回馈拜百万开发者,限量*品,参与调研问卷得好*!
Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,它是一个专注于实时处理的任务队列,同时也支持任务调度。
为了讲解 Celery Beat 的周期调度机制及实现原理,我们会基于Django从制作一个简单的周期任务开始,然后一步一步拆解 Celery Beat 的源代码。
相关前置应用知识,可以阅读以下文章:
1. 实战教程!Django Celery 异步与定时任务
2. Python Celery异步快速下载股票数据
1.Celery 简单周期任务示例
在 celery_app.tasks.py 中添加如下任务:
@shared_task
def pythondict_task():
    print(“pythondict_task”)
在 django.celery.py 文件中添加如下配置:
from celery_django import settings
from datetime import timedelta
app.autodiscover_tasks(lambda : settings.INSTALLED_APPS)
CELERYBEAT_SCHEDULE = {
    ‘pythondict_task’: {
        ‘task’: ‘celery_app.tasks.pythondict_task’,
        ‘schedule’: timedelta(seconds=3),
    },
}
app.conf.update(CELERYBEAT_SCHEDULE=CELERYBEAT_SCHEDULE)
至此,配置完成,此时,先启动 Celery Beat 定时任务命令:
celery beat -A celery_django -S django
然后打开第二个终端进程启动消费者:
celery -A celery_django worker
此时在worker的终端上就会输出类似如下的信息:
[2021-07-11 16:34:11,546: WARNING/PoolWorker-3] pythondict_task
[2021-07-11 16:34:11,550: WARNING/PoolWorker-4] pythondict_task
[2021-07-11 16:34:11,551: WARNING/PoolWorker-2] pythondict_task
[2021-07-11 16:34:11,560: WARNING/PoolWorker-1] pythondict_task
看到结果正常输出,说明任务成功定时执行。
2.源码剖析
为了明白 Celery Beat 是如何实现周期任务调度的,我们需要从 Celery 源码入手。
当你执行 Celery Beat 启动命令的时候,到底发生了什么?
celery beat -A celery_django -S django
当你执行这个命令的时候,Celery/bin/celery.py 中的 CeleryCommand 类接收到命令后,会选择 beat 对应的类执行如下代码:
# Python 实用宝典
# https://pythondict.com
from celery.bin.beat import beat
class CeleryCommand(Command):
    commands = {
        # …
        ‘beat’: beat,
        # …
    }
    # …
    def execute(self, command, argv=None):
        try:
            cls = self.commands[command]
        except KeyError:
            cls, argv = self.commands[‘help’], [‘help’]
        cls = self.commands.get(command) or self.commands[‘help’]
        try:
            return cls(
                app=self.app, on_error=self.on_error,
                no_color=self.no_color, quiet=self.quiet,
                on_usage_error=partial(self.on_usage_error, command=command),
            ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
        except self.UsageError as exc:
            self.on_usage_error(exc)
            return exc.status
        except self.Error as exc:
            self.on_error(exc)
            return exc.status
此时cls对应的是beat类,通过查看位于bin/beat.py中的 beat 类可知,该类只重写了run方法和add_arguments方法。
所以此时执行的 run_from_argv 方法是 beat 继承的 Command 的 run_from_argv 方法:
# Python 实用宝典
# https://pythondict.com
def run_from_argv(self, prog_name, argv=None, command=None):
    return self.handle_argv(prog_name, sys.argv if argv is None else argv, command)
该方法中会调用 Command 的 handle_argv 方法,而该方法在经过相关参数处理后会调用 self(*args, **options) 到 __call__ 函数:
     # Python 实用宝典
    # https://pythondict.com
    def handle_argv(self, prog_name, argv, command=None):
        “””Parse command-line arguments from “argv“ and dispatch
        to :meth:`run`.
        :param prog_name: The program name (“argv[0]“).
        :param argv: Command arguments.
        Exits with an error message if :attr:`supports_args` is disabled
        and “argv“ contains positional arguments.
        “””
        options, args = self.prepare_args(
            *self.parse_options(prog_name, argv, command))
        return self(*args, **options)
Command 类的 __call__函数:
    # Python 实用宝典
    # https://pythondict.com
    def __call__(self, *args, **kwargs):
        random.seed() # maybe we were forked.
        self.verify_args(args)
        try:
            ret = self.run(*args, **kwargs)
            return ret if ret is not None else EX_OK
        except self.UsageError as exc:
            self.on_usage_error(exc)
            return exc.status
        except self.Error as exc:
            self.on_error(exc)
            return exc.status
可见,在该函数中会调用到run方法,此时调用的run方法就是beat类中重写的run方法,查看该方法:
# Python 实用宝典
# https://pythondict.com
class beat(Command):
    “””Start the beat periodic task scheduler.
    Examples::
        celery beat -l info
        celery beat -s /var/run/celery/beat-schedule –detach
        celery beat -S djcelery.schedulers.DatabaseScheduler
    “””
    doc = __doc__
    enable_config_from_cmdline = True
    supports_args = False
    def run(self, detach=False, logfile=None, pidfile=None, uid=None,
            gid=None, umask=None, working_directory=None, **kwargs):
        # 是否开启后台运行
        if not detach:
            maybe_drop_privileges(uid=uid, gid=gid)
        workdir = working_directory
        kwargs.pop(‘app’, None)
        # 设定偏函数
        beat = partial(self.app.Beat,
                       logfile=logfile, pidfile=pidfile, **kwargs)
        if detach:
            with detached(logfile, pidfile, uid, gid, umask, workdir):
                return beat().run() # 后台运行
        else:
            return beat().run() # 立即运行
这里引用了偏函数的知识,偏函数就是从基函数创建一个新的带默认参数的函数,详细可见廖雪峰老师的介绍:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017454145929440
可见,此时创建了app的Beat方法的偏函数,并通过 .run 函数执行启动 beat 进程,首先看看这个 beat 方法:
    # Python 实用宝典
    # https://pythondict.com
    @cached_property
    def Beat(self, **kwargs):
        # 导入celery.apps.beat:Beat类
        return self.subclass_with_self(‘celery.apps.beat:Beat’)
可以看到此时就实例化了 celery.apps.beat 中的 Beat 类,并调用了该实例的 run 方法:
    # Python 实用宝典
    # https://pythondict.com
    def run(self):
        print(str(self.colored.cyan(
            ‘celery beat v{0} is starting.’.format(VERSION_BANNER))))
        # 初始化loader
        self.init_loader()
        # 设置进程
        self.set_process_title()
        # 开启任务调度
        self.start_scheduler()
init_loader 中,会导入默认的modules,此时会引入相关的定时任务,这些不是本文重点。我们重点看 start_scheduler 是如何开启任务调度的:
    # Python 实用宝典
    # https://pythondict.com
    def start_scheduler(self):
        c = self.colored
        if self.pidfile: # 是否设定了pid文件
            platforms.create_pidlock(self.pidfile) # 创建pid文件
        # 初始化service
        beat = self.Service(app=self.app,
                            max_interval=self.max_interval,
                            scheduler_cls=self.scheduler_cls,
                            schedule_filename=self.schedule)
        # 打印启动信息
        print(str(c.blue(‘__ ‘, c.magenta(‘-‘),
                  c.blue(‘ … __ ‘), c.magenta(‘-‘),
                  c.blue(‘ _\n’),
                  c.reset(self.startup_info(beat)))))
        # 开启日志
        self.setup_logging()
        if self.socket_timeout:
            logger.debug(‘Setting default socket timeout to %r’,
                         self.socket_timeout)
            # 设置超时
            socket.setdefaulttimeout(self.socket_timeout)
        try:
            # 注册handler
            self.install_sync_handler(beat)
            # 开启beat
            beat.start()
        except Exception as exc:
            logger.critical(‘beat raised exception %s: %r’,
                            exc.__class__, exc,
                            exc_info=True)
我们看下beat是如何开启的:
    # Python 实用宝典
    # https://pythondict.com
    def start(self, embedded_process=False, drift=-0.010):
        info(‘beat: Starting…’)
        # 打印*大间隔时间
        debug(‘beat: Ticking with max interval->%s’,
              humanize_seconds(self.scheduler.max_interval))
        # 通知注册该signal的函数
        signals.beat_init.send(sender=self)
        if embedded_process:
            signals.beat_embedded_init.send(sender=self)
            platforms.set_process_title(‘celery beat’)
        try:
            while not self._is_shutdown.is_set():
                # 调用scheduler.tick()函数检查还剩多余时间
                interval = self.scheduler.tick()
                interval = interval + drift if interval else interval
                # 如果大于0
                if interval and interval > 0:
                    debug(‘beat: Waking up %s.’,
                          humanize_seconds(interval, prefix=’in ‘))
                    # 休眠
                    time.sleep(interval)
                    if self.scheduler.should_sync():
                        self.scheduler._do_sync()
        except (KeyboardInterrupt, SystemExit):
            self._is_shutdown.set()
        finally:
            self.sync()
这里重点看 self.scheduler.tick() 方法:
    # Python 实用宝典
    # https://pythondict.com
    def tick(self):
        “””Run a tick, that is one iteration of the scheduler.
        Executes all due tasks.
        “””
        remaining_times = []
        try:
            # 遍历每个周期任务设定
            for entry in values(self.schedule):
                # 下次运行时间
                next_time_to_run = self.maybe_due(entry, self.publisher)
                if next_time_to_run:
                    remaining_times.append(next_time_to_run)
        except RuntimeError:
            pass
        return min(remaining_times + [self.max_interval])
这里通过 self.schedule 拿到了所有存放在用 shelve 写入的 celerybeat-schedule 文件的定时任务,遍历所有定时任务,调用 self.maybe_due 方法:
    # Python 实用宝典
    # https://pythondict.com
    def maybe_due(self, entry, publisher=None):
        # 是否到达运行时间
        is_due, next_time_to_run = entry.is_due()
        if is_due:
            # 打印任务发送日志
            info(‘Scheduler: Sending due task %s (%s)’, entry.name, entry.task)
            try:
                # 执行任务
                result = self.apply_async(entry, publisher=publisher)
            except Exception as exc:
                error(‘Message Error: %s\n%s’,
                      exc, traceback.format_stack(), exc_info=True)
            else:
                debug(‘%s sent. id->%s’, entry.task, result.id)
        return next_time_to_run
可以看到,此处会判断任务是否到达定时时间,如果是的话,会调用 apply_async 调用Worker执行任务。如果不是,则返回下次运行时间,让 Beat 进程进行 Sleep,减少进程资源消耗。
到此,我们就讲解完了 Celery Beat 在周期定时任务的检测调度机制,怎么样,小伙伴们有没有什么疑惑?可以在下方留言区留言一起讨论哦。

疫情中的2021,云原生会走向哪里

近日,CNCF发布了2020年云原生领域所有工作的年度总结[1],在疫情流行的形势下,我们度过了坚实的一年,希望读者朋友们阅读该报告。本文将分享我对云原生在2021年及以后发展方向及趋势的想法。

%title插图%num

云原生IDE

未来,开发生命周期(代码、构建、调试)将主要发生在云上,而不是本地Emacs或VSCode。每个PR都会有完整的开发和部署环境,以满足开发和调试的需求。

目前,GitHub代码空间和GitPod都是这一技术的具体示例。虽然GitHub代码空间还在测试阶段,但您可以尝试使用GitPod。以Prometheus为例,在大约一分钟左右的时间里,您就拥有了一个完整的在线开发环境,包括编辑器和预览环境。*疯狂的是,这个开发环境(工作区)是用代码来描述的,并且像其他代码中间件一样可以与团队中的其他开发人员共享。

我期望未来能看到云原生IDE继续不可思议的创新,特别是GitHub代码空间结束测试阶段被广泛使用,这样开发者就可以体验到这个新概念并爱上它。

%title插图%num

边缘Kubernetes

Kubernetes诞生于海量数据中心,但Kubernetes会像Linux一样演进到新的环境。Linux的*终结果是,*终用户扩展了内核,以支持不同领域下各种新的部署方案,包括移动端、嵌入式等。

我坚信Kubernetes会经历类似的演进,我们已经看到运行商(和初创企业)将Kubernetes作为边缘平台,通过KubeEdge、k3s、k0s、LFEdge、Eclipse ioFog等开源项目将VNF改造成CNFs(Cloud Native Network Function) 。超大规模云对运营商和边缘场景的支持,对云原生软件的复用能力,以及基于目前庞大的云原生生态系统的构建能力,这些推动力都在巩固着Kubernetes在未来几年作为边缘计算的主导平台的地位。

%title插图%num

云原生+WASM

Web汇编(WASM)是一项刚刚起步的技术,但我希望它能成为云原生生态系统中日益重要的角色,特别是随着WASI的成熟,以及Kubernetes更多被用作上文描述的边缘协同器。一个用例是为扩展机制提供动力,就像LuaJIT和Envoy所做的那样。与其直接处理Lua,您可以使用支持多种编程语言的小型优化运行时。

Envoy项目目前正处于采用WASM的征程中,我期望任何环境都遵循类似的模式,而作为流行扩展机制的脚本语言,将来会被WASM完全取代。

在Kubernetes方面,有一些像微软的Krustlet这样的项目正在探索如何在Kubernetes中支持基于WASI的运行时。这应该不会太令人惊讶,因为Kubernetes已经通过CRD和其他机制来扩展,以运行不同类型的工作负载,比如VM (KubeVirt)等。另外,如果您是WASM的新手,我推荐这门来自Linux基金会的入门课程:

https://www.edx.org/course/introduction-to-webassembly-runtime。

%title插图%num

FinOps 的崛起(CFM)

新型冠状病毒的爆发加速了向云原生的转变,至少有一半的公司正在危机中加速他们的云计划……近60%的受访者表示,由于COVID-19大流行(2020年云状况报告https://info.flexera.com/SLO-CM-REPORT-State-of-the-Cloud-2020),云使用率将超过先前的计划。*重要的是,云财务管理(FinOps)是一个日益增长的问题,也是许多公司所关心的问题。

老实说,在过去六个月中,我和一些公司在云原生旅程中进行了大约一半的讨论。你也可以说,云提供商并没有被激励去简化云财务管理,因为那样会让客户更轻松地减少开支。真正痛苦的是云财务管理缺乏开源创新和标准化(所有云的成本管理方式都不一样),CNCF中没有多少开源项目试图让FinOps更简单,目前的KubeCost项目还很年轻。

此外,Linux基金会*近推出了FinOps基金会,推动该领域的创新,他们在这个领域有许多介绍材料值得一读(https://www.edx.org/course/introduction-to-finops)。我希望在未来几年里,在FinOps领域看到更多的开源项目和规范。

%title插图%num

更多的云原生Rust项目

Rust仍然是一种年轻而小众的编程语言,特别是如果你以Redmonk的编程语言排名为例。然而,我的感觉是,在未来一年,你会看到Rust在更多的云原生项目中,因为已经有少数CNCF项目开始利用Rust的优势,如microvm Firecracker。虽然CNCF目前*大部分的项目是用Golang编写的,但我期望在Rust社区成熟后的几年内,基于Rust的项目将与基于Go的项目持平。

%title插图%num

GitOps + CD/PD大幅增长

GitOps是云原生技术的运营模型,提供一套统一应用部署、管理和监控的*佳实践(*初由来自Weaveworks的Alexis Richardson提出)。

GitOps*重要的方面是描述通过声明方式在Git中进行版本化的期望系统状态,这实际上使一组复杂的系统更改能够正确应用,然后进行验证(通过Git和其他工具启用的审计日志)。

从实用的角度来看, GitOps提升了开发者体验,随着Argo、GitLab、Flux等项目的不断增长,我预计GitOps工具今年将会对企业产生更多冲击。如果你看看GitLab的数据,GitOps仍然是一个新兴的实践,大多数公司尚未探索它,但随着越来越多的公司开始大规模采用云原生软件,GitOps的发展自然会如我所言。

%title插图%num

服务目录2.0:云原生开发者看板

服务目录的概念并不是什么新东西,对于我们这些在ITIL时代长大的老人们来说,你可能会记得CMDB之类的东西,但是随着微服务的兴起和云原生的发展,对服务和各种实时服务元数据进行编目和索引的能力对于推动开发人员自动化至关重要。这可能包括使用服务目录来了解处理事件管理、管理SLO等的所有权。

将来,您将看到开发人员仪表板不仅仅是一个服务目录,而且能够通过各种自动化功能来扩展。典型的开源例子是Lyft的Backstage和Clutch,但是,任何应用云原生的公司往往都有一个试图构建类似的东西的平台基础架构团队。随着拥有大型插件生态系统的开源仪表盘的成熟,您将会看到越来越多平台工程团队加速使用他们。

%title插图%num

跨云不再是梦想

Kubernetes和云原生运动已经证明,在生产环境中,云原生和多云方法是可能的。数据表明,93%的企业有使用微软、亚马逊和谷歌等多个云厂商的服务(2020年云状况报告https://info.flexera.com/SLO-CM-REPORT-State-of-the-Cloud-2020)。

随着云市场的不断成熟,Kubernetes有望开启编程式的跨云管理服务。一个具体例子体现在Crossplane项目中,它利用Kubernetes API的扩展性,提供一个开源的跨云控制平面,实现跨云工作负载管理(参见https://thenewstack.io/gitlab-deploys-the-crossplane-control-plane-to-offer-multicloud-deployments)。

%title插图%num

eBPF成为主流

eBPF允许您在Linux内核中运行程序,而无需更改内核代码或加载模块,您可以将其视为沙箱扩展机制。eBPF允许新一代软件扩展Linux内核的行为,以支持各种不同的功能,包括改进的网络、监控和安全。eBPF的缺点是,它需要一个高版本的内核版本来利用它,而且在很长一段时间里,这对许多公司来说并不是一个现实的选择。

然而,情况正在改变,甚至较新版本的RHEL开始支持eBPF ,您将看到更多的项目使用它。如果你看看Sysdig的*新容器报告(https://sysdig.com/blog/sysdig-2021-container-security-usage-report/),你可以看到利用eBPF进行容器安全检查的Falco使用率正在大幅增长。因此,请继续关注并寻找更多基于eBPF的项目!

相关链接:

[1] CNCF 2020年度报告原文:

https://www.cncf.io/blog/2020/12/29/2020-cncf-annual-report

看穿这些套路,你的kubernetes会更香

1. kubernetes架构设计

要了解Kubernetes的架构, 首先要看懂它的架构图。

%title插图%num

其实Kubernetes他就是一个分布式的王者解决方案,那他本身也是一个分布式系统的,是由master和多台node节点组成,master并不处理消息, 主要负责转发消息到node节点上,node才是我们真正处理业务能力的主要节点,这个我们可以借鉴nginx的master进程与process进程之间的关系,还有jenkins的主从也是主并不处理消息,分发给从来处理的。

master接收各种api,把请求接收进来,不论你是通过kubectl还是通过http的api接口,都由master接收,并把各种消息全部存储到etcd中,然后把任务分发出去,让node小弟开始干活,这是它一贯的“地主作风”。

2. kubernetes创建pod的时序图

我们再来看看kubernetes创建pod的时序图:

%title插图%num

kubernetes所有的交互过程,其实是把所有的数据存储在etcd中。

api server在处理请求的时候,都把所有的数据记录在etcd中,流程就结束了。

定时任务Scheduler也是出发api接口,把数据写入到etcd中,它的使命也完成了。

kubelet是它的客户端命令行,也是通过api接口把数据写入到etcd。

*终pod的调用过程也是要和docker容器进行挂钩的。

%title插图%num

kubernetes的核心概念

系统地梳理kubernetes的核心概念,可以让我们更加清晰、深刻地理解它们。

1. master

kubernetes集群中有一个节点,就是master节点。master节点包含kube-controller-manager、kube-apiserver、kube-scheduler、cloud-controller-manager等组成部分。它们主要是用来派发任务的,自己并不干活。像地主老财,核心机密都掌握在自己手中。

2. Node

kubernetes集群中除了master节点,就是node节点,node节点就是那些干活的,*底层的工作者。

3. kube-apiserver

Kubernetes API Server: Kubernetes API,集群的统一入口,各组件协调者,以HTTP API提供接口服务,所有对象资源的增删改查和监听操作都交给APIServer处理后再提交给Etcd存储。

所有对于kubernetes操作的所有命令,都是从这里发出去的,这里就像是中央首长的办公室,只有他发了话,kubernetes内部才会干活儿,正常运作起来。

4. kube-scheduler

从scheduler这个单词的意思,其实我们也可以猜出来其大概意思。如果要创建一个pod,人家接收到请求以后,直接写入到etcd中了,因为是分布式的,所有人家的事情就完成了。

但是真正创建pod的事情还没有做。比如, 在yu哪个node上创建pod、不同服务的负载情况、使用情况、负载均衡的算法等。其他一些后续的工作,都听从scheduler的调度,它发挥的是统一指挥的作用,进行资源安排。

5. kube-controller-manager

controller-manager主要是用来做什么的呢?从controller这个我们可以知道, 他这个是提供接口的, 但是他还有一个manager, 那种重点其实就是在这个manager上面。

我们使用docker,肯定是希望对docker容器进行动态的扩缩容。那接下来我们需要一个接口、需要给一个容器(比如nginx容器)、需要几个备份的副本、需要对容器有一个统一的manager入口。而controller-manager发挥的就是这个作用。

但是controller-manager并不仅仅包含副本个数的manager,还包括其他很多controller。现将contoller Manager包含的controller列举如下:

  1. Replication Controller
  2. Node Controller
  3. Namespace Controller
  4. Service Controller
  5. EndPoints Controller
  6. Service Account Controller
  7. Persistent Volume Controller
  8. Daemon Set Controller
  9. Deployment Controller
  10. Job Controller
  11. Pod Autoscaler Controller

它们的主要作用是进行副本的控制,只不过我们把副本拆分出了很多的模块, 对各种副本的控制。

6. etcd

这个不用说了,就是一个KV的nosql数据库,用来存储所有kubernetes中的所有数据。

7. kubelet

在学习kubelet之前,我们先来看一张图。

%title插图%num

kubelet主要是干什么的呢?其实说白了它就是一个kubernetes封装的客户端,类似于redis的redis-client,zookeeper的zookeeper-cli,可以在服务器上直接运行一些命令,操作kubernetes。其实kubelet*终也是把命令交给“中央首长”帮公司apiserver进行畸形处理。

8. kube-proxy

kube-proxy只是存在于node节点上,主要维护网络规则和四层负载均衡的工作。本质上,kube-proxy 类似一个反向代理, 我们可以把每个节点上运行的kube-proxy 看作service的透明代理兼LB。

kube-proxy 监听 apiserver 中service 与Endpoint 的信息,配置iptables 规则,请求通过iptables 直接转发给 pod。

9. pod

Pod是*小部署单元,一个Pod由一个或多个容器组成,Pod中容器共享存储和网络,在同一台Docker主机上运行。

为什么要设计这个pod的,直接用docker不香吗?这个是kubernetes设计中比较睿智的点,docker目前是*火的容器虚拟化技术,那如果来年不火了呢,或者是换成了其他呢?kubernetes是不是就跪了……这就是设计pod的巧妙之处,可以根据不同虚拟化容器技术,进行快速切换,减少开发的成本。

%title插图%num

kubernetes核心组件原理

1. RC[控制器]

ReplicationController

用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的pod来替代,而如果发生异常,多出来的容器也会自动回收。

在新版本的kubernetes中,建议使用ReplicaSet来取代ReplicationController。

2. RS[控制器]

ReplicaSet

ReplicaSet跟Repliation Controler没有本质的不同,只是名字不一样。并且ReplicaSet支持集合式的selector,虽然ReplicaSet可以独立使用,但一般还是建议使用Deployment来自动管理ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如ReplicaSet不支持rolling-update 但Deployment支持)。

Rolling-update的滚动更新:先创建新版本的pod容器,再删除老版本的pod容器。

3. Deployment

Deployment为pod和ReplicaSet提供了一个声明式定义方法,用来替代以前的ReplicationController来方便的管理应用。

deployment不仅仅可以滚动更新,而且可以进行回滚。如果发现升级到的新版本后服务不可用,可以回滚到上一个版本。

4. HAP

HPA是Horizontal Pod AutoScale的缩写,Horizontal Pod Autoscaling 仅适用于 Deployment 和 ReplicaSet,在V1版本中仅支持根据Pod的CPU利用率扩容,在vlalpha版本中,支持根据内存和用户自定义的metric扩缩容。

5. statefullSet

StatefullSet是为了解决有状态服务的问题(对应Deployments 和 ReplicaSets 是为无状态服务而设计)。其应用场景包括:

1)稳定的持久化存储,即Pod重新调度后还是能访问的相同持久化数据,基于PVC来实现。

2)稳定的网络标志,及Pod重新调度后其 PodName 和 HostName 不变,基于Headlesss Service(即没有 Cluster IP 的 Service)来实现。

3)有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行 (即从 0 到 N-1,在下一个Pod运行之前所有之前的Pod必须都是Running 和 Ready 状态),基于 init containers 来实现。

4)有序收缩,有序删除(即从N-1 到 0)。

6. DaemonSet

DaemonSet确保全部或者一些node打上污点,pod如果不定义容忍这个污点,那么pod就不会被调度器分配到这个node。

Node上运行一个Pod的副本。当有Node加入集群时,也会为他们新增一个Pod。当有Node从集群移 除时,这些Pod也会被回收。删除DaemonSet将会删除他创建的所有Pod,使用DaemonSet 的一些典型 用法:

1)运行集群存储daemon,例如在每个Node上运行glustered,ceph。

2)在每个Node上运行日志收集Daemon,例如:fluentd、logstash。

3)在每个Node上运行监控Daemon,例如:Prometheus Node Exporter Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束 Cron Job管理基于时间Job,即:在给定时间点只运行一次,周期性地在给定时间点运行。

7. Volume

数据卷,共享pod中容器使用的数据。

8. Label

标签用于区分对象(比如Pod、Service),键/值对存在;每个对象可以有多个标签,通过标签关联对象。

Kubernetes中任意API对象都是通过Label进行标识,Label的实质是一系列的Key/Value键值对,其中key与value由用户自己指定。

Label可以附加在各种资源对象上,如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上去。

Label是Replication Controller和Service运行的基础,二者通过Label来进行关联Node上运行的 Pod。

我们可以通过给指定的资源对象捆绑一个或者多个不同的Label来实现多维度的资源分组管理功能,以便于灵活方便的进行资源分配、调度、配置等管理工作。一些常用的Label如下:

1)版本标签:

“release”:”stable”,”release”:”canary”……

2)环境标签:

“environment”:”dev”,”environment”:”qa”,”environment”:”production”

3)架构标签:

“tier”:”frontend”,”tier”:”backend”,”tier”:”middleware”

4)分区标签:

“partition”:”customerA”,”partition”:”customerB”

5)质量管控标签:

“track”:”daily”,”track”:”weekly”

Label相当于我们熟悉的标签,给某个资源对象定义一个Label就相当于给它贴上了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制。

Label Selector在Kubernetes中重要使用场景如下:-> kube-Controller进程通过资源对象RC上定义Label Selector来筛选要监控的Pod副本的数量,从而实现副本数量始终符合预期设定的全自动控制流程;-> kube-proxy进程通过Service的Label Selector来选择对应的Pod,自动建立起每个Service到对应Pod 的请求转发路由表,从而实现Service的智能负载均衡;-> 通过对某些Node定义特定的Label,并且在Pod定义文件中使用Nodeselector这种标签调度策略,kuber-scheduler进程可以实现Pod“定向调度”的特性。

9. service

Service是一个抽象的概念。它通过一个虚拟的IP的形式(VIPs),映射出来指定的端口,通过代理客户端发来的请求转发到后端一组Pods中的一台(也就是endpoint)。

Service定义了Pod逻辑集合和访问该集合的策略,是真实服务的抽象。Service提供了统一的服务访 问入口以及服务代理和发现机制,关联多个相同Label的Pod,用户不需要了解后台Pod是如何运行。外部系统访问Service的问题:

首先需要弄明白Kubernetes的三种IP这个问题

  1. Node IP:Node节点的IP地址
  2. Pod IP: Pod的IP地址
  3. Cluster IP:Service的IP地址

首先,Node IP是Kubernetes集群中节点的物理网卡IP地址,所有属于这个网络的服务器之间都能通过这个网络直接通信。这也表明Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者 TCP/IP服务的时候,必须通过Node IP进行通信。

其次,Pod IP是每个Pod的IP地址,他是Docker Engine根据docker0网桥的IP地址段进行分配的,通 常是一个虚拟的二层网络。

*后,Cluster IP是一个虚拟的IP,但更像是一个伪造的IP网络。

原因有以下几点:

Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配P地址-> Cluster IP无法被ping,他没有一个“实体网络对象”来响应-> Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备通信的基础,并 且他们属于Kubernetes集群这样一个封闭的空间。-> Kubernetes集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊路由规则。