-
Java中的Iterator功能比较简单,并且只能单向移动:(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。(2) 使用next()获得序列中的下一个元素。(3) 使用hasNext()检查序列中是否还有元素。(4) 使用remove()将迭代器新返回的元素删除。 Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
-
在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。1.namedtuple: 生成可以使用名字来访问元素内容的tuple2.deque: 双端队列,可以快速的从另外一侧追加和推出对象3.Counter: 计数器,主要用来计数4.OrderedDict: 有序字典5.defaultdict: 带有默认值的字典1、namedtuple我们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:>>> p=(2,6)但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。这时,namedtuple就派上了用场:from collections import namedtuple point = namedtuple("point", ['x', 'y']) print(point) p_obj = point(6, 8) print(p_obj.x) print(p_obj.y)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py68Process finished with exit code 0类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:# namedtuple('名称', [属性list]):Circle = namedtuple("Circle", ['x', 'y', 'r']) cir = Circle(8, 6, 10) print(cir.x) print(cir.y) print(cir.r)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py8610Process finished with exit code 02、deque使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:from collections import deque q = deque(['a', 'b', 'c']) q.append('d') print(q) q.appendleft('e') print(q) q.pop() print(q) q.popleft() print(q)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py deque(['a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c']) deque(['a', 'b', 'c']) Process finished with exit code 0deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。3、OrderedDict使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict:from collections import OrderedDict lis = [('a', 21), ('b', 55), ('c', 86)] dic = dict(lis) # dict的Key是无序的print(dic) ord_dic = OrderedDict(lis) # OrderedDict的Key是有序的print(ord_dic)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py {'a': 21, 'b': 55, 'c': 86}OrderedDict([('a', 21), ('b', 55), ('c', 86)])Process finished with exit code 0注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:ord_dic = OrderedDict() ord_dic['z'] = 99ord_dic['y'] = 110ord_dic['z'] = 666print(ord_dic.keys()) # 按照插入的Key的顺序返回结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py odict_keys(['z', 'y']) Process finished with exit code 04、defaultdict 有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。即: {'k1': 大于66 , 'k2': 小于66}lis = [11, 22, 33, 55, 77, 66, 88, 99] dic = {}for value in lis: if value > 66: if dic.has_key('k1'): dic['k1'].append(value) else: dic['k1'] = [value] else: if dic.has_key('k2'): dic['k2'].append(value) else: dic['k2'] = [value]from collections import defaultdictlis = [11, 22, 33, 55, 77, 66, 88, 99]my_dic = defaultdict(lis)for value in lis: if value > 66: my_dic['k1'].append(value) else: my_dic['k2'].append(value)print(my_dic)使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc' # key1存在print(dd['key1']) print(dd['key2']) # key2不存在,返回默认值print(dd['key3'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py abc N/A N/A Process finished with exit code 05、CounterCounter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。from collections import Counter coun = Counter("absdsdsdadsdsa") print(coun)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py Counter({'s': 5, 'd': 5, 'a': 3, 'b': 1}) Process finished with exit code 0创建下面的代码说明了Counter类创建的四种方法:from collections import Counterc = Counter() # 创建一个空的Counter类print(c)c = Counter('g***d') # 从一个可iterable对象(list、tuple、dict、字符串等)创建print(c)c = Counter({'a': 4, 'b': 2}) # 从一个字典对象创建print(c)c = Counter(a=4, b=2) # 从一组键值对创建print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter() Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1}) Counter({'a': 4, 'b': 2}) Counter({'a': 4, 'b': 2}) Process finished with exit code 0计数值的访问与缺失的键当所访问的键不存在时,返回0,而不是KeyError;否则返回它的计数。计数值的访问from collections import Counter cou = Counter("hello world") print(cou["l"]) print(cou["o"]) print(cou["a"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py320Process finished with exit code 0计数器的更新(update和subtract)可以使用一个iterable对象或者另一个Counter对象来更新键值。计数器的更新包括增加和减少两种。其中,增加使用update()方法:计数器的更新(update)from collections import Counter cou = Counter("hello world") cou.update("which") # 使用另一个iterable对象更新print(cou["h"]) c = Counter("watch") cou.update(c) # 使用另一个Counter对象更新print(cou["h"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py34Process finished with exit code 0减少则使用subtract()方法:计数器的更新(subtract)from collections import Counter c = Counter('which') print(c["h"]) c.subtract('witch') # 使用另一个iterable对象更新print(c['h']) d = Counter('watch') c.subtract(d) # 使用另一个Counter对象更新print(c['a'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py21-1Process finished with exit code 0键的修改和删除当计数值为0时,并不意味着元素被删除,删除元素应当使用del。 键的删除from collections import Counterc = Counter("abcdcba")print(c)c["b"] = 0print(c) del c["a"]print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'c': 2, 'd': 1, 'b': 0}) Counter({'c': 2, 'd': 1, 'b': 0}) Process finished with exit code 0elements()返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。元素排列无确定顺序,个数小于1的元素不被包含。elements()方法 from collections import Counterc = Counter(a=4, b=2, c=0, d=-2)print(list(c.elements()))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py ['a', 'a', 'a', 'a', 'b', 'b'] Process finished with exit code 0most_common([n])返回一个TopN列表。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。most_common()方法from collections import Counterc = Counter('abracadabra')print(c)print(c.most_common())print(c.most_common(3))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)] [('a', 5), ('b', 2), ('r', 2)] Process finished with exit code 0浅拷贝copy浅拷贝copyfrom collections import Counterc = Counter("abcdcba")print(c) d = c.copy()print(d)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Process finished with exit code 0算术和集合操作+、-、&、|操作也可以用于Counter。其中&和|操作分别返回两个Counter对象各元素的最小值和最大值。需要注意的是,得到的Counter对象将删除小于1的元素。Counter对象的算术和集合操作from collections import Counterc = Counter(a=3, b=1) d = Counter(a=1, b=2)print(c + d) # c[x] + d[x]print(c - d) # subtract(只保留正数计数的元素)print(c & d) # 交集: min(c[x], d[x])print(c | d) # 并集: max(c[x], d[x])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 4, 'b': 3}) Counter({'a': 2}) Counter({'a': 1, 'b': 1}) Counter({'a': 3, 'b': 2}) Process finished with exit code 0其他常用操作下面是一些Counter类的常用操作,来源于Python官方文档Counter类常用操作sum(c.values()) # 所有计数的总数c.clear() # 重置Counter对象,注意不是删除list(c) # 将c中的键转为列表set(c) # 将c中的键转为setdict(c) # 将c中的键值对转为字典c.items() # 转为(elem, cnt)格式的列表Counter(dict(list_of_pairs)) # 从(elem, cnt)格式的列表转换为Counter类对象c.most_common()[:-n:-1] # 取出计数最少的n个元素c += Counter() # 移除0和负值
-
当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。来自:https://blog.csdn.net/fangchao2011/article/details/89185365
-
在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。1.namedtuple: 生成可以使用名字来访问元素内容的tuple2.deque: 双端队列,可以快速的从另外一侧追加和推出对象3.Counter: 计数器,主要用来计数4.OrderedDict: 有序字典5.defaultdict: 带有默认值的字典1、namedtuple我们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:>>> p=(2,6)但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。这时,namedtuple就派上了用场:from collections import namedtuple point = namedtuple("point", ['x', 'y']) print(point) p_obj = point(6, 8) print(p_obj.x) print(p_obj.y)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py <class '__main__.point'>68Process finished with exit code 0类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:# namedtuple('名称', [属性list]):Circle = namedtuple("Circle", ['x', 'y', 'r']) cir = Circle(8, 6, 10) print(cir.x) print(cir.y) print(cir.r)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py8610Process finished with exit code 02、deque使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:from collections import deque q = deque(['a', 'b', 'c']) q.append('d') print(q) q.appendleft('e') print(q) q.pop() print(q) q.popleft() print(q)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py deque(['a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c']) deque(['a', 'b', 'c']) Process finished with exit code 0deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。3、OrderedDict使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict:from collections import OrderedDict lis = [('a', 21), ('b', 55), ('c', 86)] dic = dict(lis) # dict的Key是无序的print(dic) ord_dic = OrderedDict(lis) # OrderedDict的Key是有序的print(ord_dic)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py {'a': 21, 'b': 55, 'c': 86}OrderedDict([('a', 21), ('b', 55), ('c', 86)])Process finished with exit code 0注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:ord_dic = OrderedDict() ord_dic['z'] = 99ord_dic['y'] = 110ord_dic['z'] = 666print(ord_dic.keys()) # 按照插入的Key的顺序返回结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py odict_keys(['z', 'y']) Process finished with exit code 04、defaultdict 有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。即: {'k1': 大于66 , 'k2': 小于66}lis = [11, 22, 33, 55, 77, 66, 88, 99] dic = {}for value in lis: if value > 66: if dic.has_key('k1'): dic['k1'].append(value) else: dic['k1'] = [value] else: if dic.has_key('k2'): dic['k2'].append(value) else: dic['k2'] = [value]from collections import defaultdictlis = [11, 22, 33, 55, 77, 66, 88, 99]my_dic = defaultdict(lis)for value in lis: if value > 66: my_dic['k1'].append(value) else: my_dic['k2'].append(value)print(my_dic)使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc' # key1存在print(dd['key1']) print(dd['key2']) # key2不存在,返回默认值print(dd['key3'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py abc N/A N/A Process finished with exit code 05、CounterCounter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。from collections import Counter coun = Counter("absdsdsdadsdsa") print(coun)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py Counter({'s': 5, 'd': 5, 'a': 3, 'b': 1}) Process finished with exit code 0创建下面的代码说明了Counter类创建的四种方法:from collections import Counterc = Counter() # 创建一个空的Counter类print(c)c = Counter('g***d') # 从一个可iterable对象(list、tuple、dict、字符串等)创建print(c)c = Counter({'a': 4, 'b': 2}) # 从一个字典对象创建print(c)c = Counter(a=4, b=2) # 从一组键值对创建print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter() Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1}) Counter({'a': 4, 'b': 2}) Counter({'a': 4, 'b': 2}) Process finished with exit code 0计数值的访问与缺失的键当所访问的键不存在时,返回0,而不是KeyError;否则返回它的计数。计数值的访问from collections import Counter cou = Counter("hello world") print(cou["l"]) print(cou["o"]) print(cou["a"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py320Process finished with exit code 0计数器的更新(update和subtract)可以使用一个iterable对象或者另一个Counter对象来更新键值。计数器的更新包括增加和减少两种。其中,增加使用update()方法:计数器的更新(update)from collections import Counter cou = Counter("hello world") cou.update("which") # 使用另一个iterable对象更新print(cou["h"]) c = Counter("watch") cou.update(c) # 使用另一个Counter对象更新print(cou["h"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py34Process finished with exit code 0减少则使用subtract()方法:计数器的更新(subtract)from collections import Counter c = Counter('which') print(c["h"]) c.subtract('witch') # 使用另一个iterable对象更新print(c['h']) d = Counter('watch') c.subtract(d) # 使用另一个Counter对象更新print(c['a'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py21-1Process finished with exit code 0键的修改和删除当计数值为0时,并不意味着元素被删除,删除元素应当使用del。 键的删除from collections import Counterc = Counter("abcdcba")print(c)c["b"] = 0print(c) del c["a"]print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'c': 2, 'd': 1, 'b': 0}) Counter({'c': 2, 'd': 1, 'b': 0}) Process finished with exit code 0elements()返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。元素排列无确定顺序,个数小于1的元素不被包含。elements()方法 from collections import Counterc = Counter(a=4, b=2, c=0, d=-2)print(list(c.elements()))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py ['a', 'a', 'a', 'a', 'b', 'b'] Process finished with exit code 0most_common([n])返回一个TopN列表。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。most_common()方法from collections import Counterc = Counter('abracadabra')print(c)print(c.most_common())print(c.most_common(3))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)] [('a', 5), ('b', 2), ('r', 2)] Process finished with exit code 0浅拷贝copy浅拷贝copyfrom collections import Counterc = Counter("abcdcba")print(c) d = c.copy()print(d)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Process finished with exit code 0算术和集合操作+、-、&、|操作也可以用于Counter。其中&和|操作分别返回两个Counter对象各元素的最小值和最大值。需要注意的是,得到的Counter对象将删除小于1的元素。Counter对象的算术和集合操作from collections import Counterc = Counter(a=3, b=1) d = Counter(a=1, b=2)print(c + d) # c[x] + d[x]print(c - d) # subtract(只保留正数计数的元素)print(c & d) # 交集: min(c[x], d[x])print(c | d) # 并集: max(c[x], d[x])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 4, 'b': 3}) Counter({'a': 2}) Counter({'a': 1, 'b': 1}) Counter({'a': 3, 'b': 2}) Process finished with exit code 0其他常用操作下面是一些Counter类的常用操作,来源于Python官方文档Counter类常用操作sum(c.values()) # 所有计数的总数c.clear() # 重置Counter对象,注意不是删除list(c) # 将c中的键转为列表set(c) # 将c中的键转为setdict(c) # 将c中的键值对转为字典c.items() # 转为(elem, cnt)格式的列表Counter(dict(list_of_pairs)) # 从(elem, cnt)格式的列表转换为Counter类对象c.most_common()[:-n:-1] # 取出计数最少的n个元素c += Counter() # 移除0和负值
-
转自:荣信er 华为云DevCloud 8月27日我们在学习研究的过程中,大家都在说成功的案例,很少有人讲失败的案例。我担心这会产生一种误导:不管什么企业,什么组织结构,什么技术能力,什么基础设施平台,都可以轻松落地DevOps。个人非常喜欢查理·芒格的逆向思维方式,既然大家都在讲成功的案例,我给大家泼泼冷水,讲讲我所理解和了解的真实情况。作为DevOps的学习和布道者,对DevOps本身没有任何诋毁质疑,只是希望从反面让大家更全面的了解DevOps,不要在错误的时间,以错误的方式,错误地尝试了一下,然后做出了DevOps无用的判断。要谈落地Devops,先来看看什么是Devops。先看下 “官方”解释:· DevOps(Development和Operations的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。· 它是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。· DevOps不能简单认为是工具、方法、技能或组织结构,DevOps的框架结合所有这一切元素,去建立一个流水线的过程,使业务更快的运营并且更快地应对变化。看完之后是不是一头雾水?这可能是刚接触到DevOps的第一反应(或者说是我当年的第一反应吧)。2009年,Patrick Debois(DevOps之父)在提出Dev和Ops概念时的主要目的是如何在运维工作中应用 Scrum 和其它敏捷实践,为的是促成开发运维合作(打破开发和运维之间的部门墙)。为什么会有部门墙?参考康威定律:“设计系统的架构受制于产生这些设计的组织的沟通结构。”通俗的来讲:产品必然是其(人员)组织沟通结构的缩影。我们来看一个图,更好的理解一下Dev和Ops部门墙产生的原因。传统的Dev 和 Ops 的关注点不同,Dev的关注点是如何开发测试交付新的功能,Ops的关注点是保证站点的稳定和高性能。导致直接的矛盾点表现在以下两方面:(一)在价值流下游的Ops 评审认为价值链上游的 Dev 软件非功能质量不满足要求,因此阻止变更。(二)在价值流上游的 Dev 无法获得价值链下游的 Ops 的真实运行环境,因此无法提升交付质量。于是,逐渐陷入了“无法提升质量”和“ 非功能质量不满足要求 ”的死循环中。这么看,问题是不是清晰了,但是有人又会说,这墙也不是一天两天垒起来的,为什么现在要砸墙了?软件工程方法论从瀑布到敏捷,到目前的DevOps,都不是凭空演进出来的。敏捷的目的是为了打破产品和开发团队之间的部门墙,但是市场变化越来越快,需要更快的交付和反馈,所以只打破产品和开发部门部门墙还不够,现在需要将开发和运维运营也打通。2010 年The Agile Admin 博客发表“ What is DevOps ”。在 DevOpsDays 之后,DevOps 被越来越多的人所熟知并迅速得到了大多数人的认可。2010年,《持续交付》的作者 Jez Humble 参加了第二届的 DevOpsDays 并做了 “持续交付”的演讲。从本质上说《持续交付》中所提到的实践给 Patrick 和 Andrew 最初所遇到的问题给出了最佳实践。理解了DevOps的产生历史和目的,再来看看DevOps具体是什么。DevOps落地由于涉及的内容非常多,所以不同的角色以不同的视角看,基本就是横看成岭侧成峰,远近高低各不同。我们从不同角度和层面来看看什么是DevOps?从上图可以看出,DevOps落地后,人员管理的组织结构会有变化。目前大多数传统IT企业的开发和运维部门的运作模式是共享的运维团队,开发完成后的交付物,交给运维团队负责部署、发布和运维。DevOps推荐的多功能团队类似于图中虚拟运维组的形式,每个开发团队都有自己的运维人员(运维人员少的也要有共享的运维联络人)。这里的运维人员指的是一种角色,有些团队也有全栈工程师,开发测试兼运维。对于中大型的公司,还经常会有基础设施运维团队,提供基础设施即代码等平台化能力。未来随着云原生服务的发展,运维的角色会有更大的转变(打破部门墙的最高境界,干掉运维部门,一切运维服务都是云原生服务)。看完组织结构的变化,再来看技术架构的演进。从技术架构层面看(以Java Web应用为例):随着IT技术的不断发展,应用系统的建设经过单体应用、SOA应用、逐步走向微服务应用。微服务的实施必然要具备需求管理、代码版本管理、质量管理、构建管理、测试管理、部署管理、环境管理等全流程自动化工具链,以及开发部门与运维部门的深度协作。因此,DevOps是微服务实施的充分必要条件。 从信息流转来看,DevOps包含了从需求管理到需求开发、代码管理、基础设施管理、持续集成、自动化测试、持续部署、持续发布和应用运维管理全流程。每个过程都需要DevOps方法和工具的支撑。比如我们需要Git来管理代码,需要根据企业的实际情况寻找合适的分支管理方法;我们需要Jenkins来做持续集成;使用selenium来做自动化测试;需要使用ansible来自动化部署;使用chef或者puppet来管理基础环境等等。理解了Dev和Ops部门墙,然后从人员管理、技术架构、信息流转多个角度看完DevOps,现在我们应该对“什么是DevOps”,“DevOps具体要做什么”有了初步的理解。(对这块感兴趣的,后续会专题介绍。)广义的DevOps涉及的内容极广,理解之后,我们才能更好的分析DevOps难落地的原因。
-
ONAP与相关标准开源组织协同ONAP一直在紧跟标准组织在SDN/NFV方面的进展,并在实施过程中尽量遵循与继承标准已有成果,比如ETSI、MEF、TMF、IETF、3 GPP、BBF等。同时,ONAP还与许多开源社区开展了协同工作,典型的有OpenStack、Kubernetes和OPNFV等。1.5.1 ONAP与ETSI NFV欧洲电信标准组织(ETSI)是电信产业具有全球影响力的电信设备和网络标准制定者。ETSI NFV是NFV领域的标准权威组织。ETSI NFV的标准定义聚焦在VNF和NS(Network Service)的生命周期管理,也就是MANO(NFVO和VNFM在一起的统称)的相关架构和接口。从功能范畴上来说,ONAP的范围远远大于MANO的范畴,ONAP要解决的是全部网络基础设施端到端的自动化运维问题,而MANO仅关注NFV的生命周期自动化。在架构和实现上,ONAP的VF-C模块相当于ETSI的NFVO功能,而VNFM模块一般由厂商来实现,VIM模块一般由云平台供应商来实现。ONAP同厂商特有的VNFM和VIM的集成接口都遵循ETSI NFV的相关标准(SOL003、SOL005等)ONAP在VNF的信息模型和SO、VF-C模块的接口上遵循ETSI NFV的相关标准。ETSI VNF的相关标准还在持续演进中,还有很多标准没有定稿,ONAP同ETSI VNF的关系是互相促进,互相影响的。ONAP的开源实现可能领先于ETSI的标准制定,并促成ETSI相关标准的成熟。1.5.2 ONAP与MEF城域以太网论坛(MEF)是2001年成立的一个专注于解决城域以太网专线业务技术标准的非营利性组织。最近几年MEF的工作重点转移到推动运营商的数字化转型和业务标准定义上来。MEF根据用户和运营商之间的消费关系提出了LSO(Lifecycle Service Orchestration,生命周期服务编排)的概念和对应的参考架构。在Casablanca版本的CCVPN用例中,在External API(北向接口项目中)支持跨运营商的企业以太专线定义中,参考了MEF的Interlude(MEF中定义的,用于Service Orche-strator间交互接口的API标准名)接口标准。1.5.3 ONAP与TMFTMF(TeleManagement Forum,电信管理论坛)是一个为电信运营和管理提供策略建议和实施方案的世界性组织,是专注于通信行业OSS和管理问题的全球性非营利性社团联盟。TMF提出的NG OSS(New Generation Operations Systems and Software,下一代运维系统)功能模型,包括TOM(Telecom Opera-tions Map,电信运营图)和eTOM(enhanced Telecom Operations Map,增强电信运营图),被国际电信运营商、设备制造商及电信运营支撑系统开发商广泛接受,成为事实上的国际标准。2018年3月,ONAP与TMF宣布正式合作。TMF开放API成为ONAP Beijing版本(2018年6月发布)的重要组成部分。在ONAP的Casablanca版本的CCVPN用例中,External API(北向接口项目中)实现对TMF接口标准的框架支持,包括为了实现跨ONAP的通信,支持了TMT 641(业务订单接口)标准,后续还会支持TMF 633(目录接口)、TMF638(业务状态变更通知)等标准接口。1.5.4 ONAP与IETFIETF负责互联网标准的开发和推动。IETF制定了TCP、IP、MPLS等关键互联网协议。IETF面向SDN各技术领域开展标准化工作,IETF的SDN参考架构已基本成为SDN的主流标准架构。IETF定义了网络配置模型语言YANG,并且定义了很多网络资源和业务模型,比如L3VPN、L2VPN、L1VPN等。SDN控制器北向接口的配置模型,一般会遵照IETF模型。ONAP同SDN厂商进行对接时,一般采用IETF定义的RestConf接口和对应的YANG模型。在Casablanca版本的CCVPN用例中,ONAP同OTN控制器的接口采用了IETF定义的ACTN(Abstraction and Control of TE networks)相关标准,包括拓扑模型、业务模型等。1.5.5 ONAP与3GPP3GPP是1998年全球各大标准组织为了协同3G标准合作建立的组织。3GPP成立后就成为无线通信领域的权威组织,并制定了WCDMA、TD-SCDMA等3G标准,以及4G标准LTE和最新的5G标准。2018年3GPP发布了首个5G标准版本R15,标志着世界正式进入5G时代。4G改变生活,5G改变社会。5G的基站网络规模会大大超过4G的;5G大量采用虚拟化技术来实现核心网业务;5G要求支持网络切片(Network Slicing);5G的基站分成DU和CU等,这类新的特点导致5G的自动化运维成为一个很大的挑战。5G是ONAP的一个核心应用场景,5G蓝图是ONAP持续多个版本的蓝图。3GPP也成立专门的工作组来研究5G同ONAP集成的运维架构。1.5.6 ONAP与BBFBBF(Broadband Forum,宽带论坛)是1994年成立的全球标准组织,聚焦在定义宽带接入的相关技术,比如DSL和PON等。BBF近年来积极推动把NFV引入电信机房,提出CloudCO(Cloud Central Office)架构。ONAP社区的vCPE和BBS两个蓝图,都是聚焦在未来的家庭宽带业务场景,相关标准参照BBF。1.5.7 ONAP与OpenStackOpenStack是当前最主流的开源云计算基础设施管理平台.电信运营商的NFVi基础设施普遍采用OpenStack作为云操作系统,提供虚拟机的编排管理和虚拟网络资源的配置管理等功能。ONAP从第一个版本开始就在MultiVIM项目中支持与OpenStack的集成,多数组件也同时支持通过OpenStack的Heat脚本进行部署管理。从Beijing版本开始ONAP支持通过OOM集成的Kubernetes实现容器化部署与管理。1.5.8 ONAP与KubernetesKubernetes(K8s)是Google开源的一个容器编排引擎,目前是CNCF(云原生云计算基金会)基金会下面的项目。K8s是集群中负责管理跨多台主机容器化应用的开源系统,支持自动化部署、大规模可伸缩、应用容器化管理,其目标是让部署容器化的应用简单且高效。K8s目前已是面向云原生的应用,是容器化部署场景的最热门的开源项目,尤其是从VNF向CNF(云原生网络功能)演进的过程,K8s被认为是默认配套。ONAP在很多项目中都在开展与K8s的集成,包括Multi-VIM项目、OOM项目等。OOM对ONAP的支持就是通过K8s实现的,使用了Rancher(容器管理平台)、Helm、Kubectl等组件。1.5.9 ONAP与OPNFVOPNFV(Open Platform for NFV)专注于加速NFV的发展,其目标是建立一个运营商级的、集成的开源参考平台。OPNFV是一个集成型的开源项目,成立多年,在NFV和NFVi的功能测试、性能测试上积累了大量的测试工具和测试用例。2018年OPNFV成立OPNFV Verified Program(OVP)认证项目,针对商用VIM产品进行认证。LFN成立以后,ONAP对NFV的认证和OPNFV对VIM的认证将统一运作,在LFN成立统一的CVC(Compliance andVerification Committee)进行管理。
-
历史在分析趋势之前,我们先看一下分布式调度系统的历史。早期分布式调度系统以批处理系统为主,例如九几年的LSF/SGE/PBS等,这些批处理系统大规划的使用在HPC领域,而且对作业级的调度进行大量的研究工作;后续由批处理系统延伸出多集群、多组织资源共享的需求,便成了网络计算。网络计算与云计算最大的不同是:网络计算强调多组织的资源共享,而云计算强调云厂商的集中式支持;这也是云计算成为主流的主要原因:多组织之间共享需要完备的协议和足够的安全支持,而云服务仅需要对用户提供相应服务和安全,并不需要在多个云厂商之间进行共享;随着开源社区的发展,再将应用接口逐步统一,e.g. Kubernetes。Hadoop出现后,不仅推动了分布式调度系统中对数据的处理,同时也推动了开源软件的生态。2012和2014是两个重要的节点,Hadoop将资源管理层与领域框架层分开,随后的领域框架也有机会构建自己的生态,e.g. Spark;同时,将资源管理层与领域框架分开也被广泛认可。在容器及Kuberentes流行后,凭借其高资源利用率与隔离,环境标准化等优势,越来越多的人希望将这些批量计算应用统一到 Kubernetes 平台上。未来的趋势多种应用统一调度随着各行各业的发展,涌现出越来越多的领域框架来支持业务的发展;这些框架都在相应的业务领域有着不可替代的作用,e.g. Spark, Tensorflow, Flink等。在业务复杂性能不断增加的情况下,单一的领域框架很难应对现在复杂的业务场景;因此现在普遍使用多种框架达成业务目标,如下图所示。但随着各个领域框架集群的不断扩大,以及单个业务的波动性,各个子集群的资源浪费比较严重;因此越来越多的用户希望通过统一调度系统来解决资源共享的问题。在技术选型上,Kubernetes凭借其优秀的扩展性获得大部分用户青睐。异构硬件在批量计算任务向云原生环境迁移的过程中,对云原生环境的算力提出了新的要求;各个厂商为了应对这些新的需求,为各个场景提供了不同架构的硬件,例如 鲲鹏,昇腾,X86,GPU等。当多种应用运行在统一平台上时,需要云原生调度系统能够对异构硬件资源进行统一的管理与调度,使用各种应用达到最优的资源配比。目前,硬件的信息通过kubernetes的 device plugin 机制提供,但Kubernetes的 device plugin 仍有一些不足,例如 无法很好的支持硬件拓扑。在调度方面 Volcano 已经支持主流的调度策略,并在最新的1.0版本中支持了 GPU 共享,大大增加了GPU的利用率,有效降低了GPU的使用成本。跨集群/跨云跨集群一直是分布调度系统解决大规模、灾备等问题的主要解决方案;同时,为了降低厂商绑定的风险,并最大限度兼顾不同云厂商的优势,多云环境下的负载高效分发逐渐成为趋势。在多云的环境中,面向数据位置的优化,作业执行时间预估等问题都是需要调度系统解决的问题;在 Volcano 中,将通过多个项目实现跨集群、跨云的作业调度,例如 JobForward (#880)。智能化调度算法在分布调度系统中有大量的研究,从早期的批处理系统到近期的Borg,Volcano等;早期的批处理系统以特定场景的算法优化为主,对于复杂的场景需要大量的计算,虽然有大量针对HPC和网络的调度优化,但常用和落地的算法比较少。随着人工智能的发展,越来越多的调度系统将会使用AI相应的能力对算法进行优化;在 Volcano 中,将通过AI的能力驱动 Volcano 中各个调度算法进行优化,并通过AI的能力提供新的调度算法。总结分布式调度系统是一个复杂的系统,需要多个组件共协作以提高整体的效率,例如 应用管理,调度,异构硬件管理,存储等,仅靠调度器无法完成这些工作。Volcano 作为CNCF首个面向批量计算的分布式调度系统,包含了应用管理,作业调度,异构硬件等多个组件和功能;其调度器兼容kubernetes调度策略,同时支持在线、离线两种作业类型;控制器提供了统一的作业管理,支持多种作业的接入,包括 MPI, Tensorflow, MidSpore, Spark 等;设备插件提供了对异构硬件的支持,例如 1.0 版中支持了 GPU 共享。因此,Volcano面向分布式调度系统的趋势提供了完整的方案,可以在多种场景下提高作业性能,资源使用率等。Volcano特训营:六节课学懂容器批量计算由Volcnao项目发起者马达主讲的直播课程正在进行中,课程共有6期,从技术原理到实战演练,涵盖Volcano全景。锁定后续课程信息,获取往期回放与讲师PPT,请假助手微信(k8s2222)并备注“Volcano”。
-
摘要:现在大部分互联网平台都使用容器了,为什么扩容速度有些时候还是跟不上流量增长的节奏呢?大家应该都看过某些明星导致社交媒体平台宕机的新闻吧?明星事件带来的突发流量触发业务扩容,以前是扩容虚机,速度慢还情有可原,现在大部分互联网平台都使用容器了,为什么扩容速度有些时候还是跟不上流量增长的节奏呢?这里主要有两个问题。01一是网口发放速度匹配容器扩容速度的问题。如果是即时按需创建容器,比如10秒内启动5000个容器实例,这要求10秒内完成端到端的网口创建、挂接和网络打通。处理流程上,容器网络控制器需要调用VPC网络API批量创建5000个网口,VPC控制面生成相应的配置,下发到各个主机节点,主机按配置创建好全部网口再挂接到节点上,进而挂接到容器上,与此同时,分布在各个主机和各类网关的数据面转发表也要同步完成刷新。传统的I层网络架构,无论是管控面还是数据面都存在瓶颈点,全流程网络打通的速度,无法匹配容器扩容速度。02再一个就是网口规模的问题。由于ENI原本是针对I层虚机或裸机的网卡扩展机制,数量受节点规格的限制,有的厂商是按照主机的flavor限定ENI的规格数量,某些厂商提供了固定的最大数量,比如8个,这些规格约束有商业成本的考虑,但根本原因还是规模目标不同。容器为了榨干节点资源,支持0.1核甚至更小的容器,单节点理论上可部署近千容器实例,也就需要近千网口,这与现有VPC网络的管控面和数据面规模有数量级差距。网络资源预热,让秒级扩容成为可能如何应对发放速度不匹配问题?计算机系统结构的经典答案:加“Cache”层, 就是按预定策略在节点建立预分配ENI的warm pool,集群纳管节点时,自动创建并配置好若干个ENI放入pool中,在批量启动容器的时候只需要把已经ready的ENI从warm pool中取出挂接到容器即可。网络资源预热能够有效弥补VPC 网络资源分配性能不能匹配容器生命周期的现状。加‘Cache’的机制能够支撑容器大批量创建删除场景。Warm pool机制主要解决端到端网络打通时间长的问题。目前VPC网络单节点网卡是串行处理的,而且单个网络设备绑定时间也达到了30s-60s,不做预热的情况下容器网络端到端打通时间在一分钟以上。分钟级容器启动时间是不可接受的。Warm pool机制在裸金属节点上预挂载一定数量ENI(用户可根据服务部署并发量自定义配置),容器随时调度到预热节点上都有即时可用的ENI网卡。经过warm pool的优化,容器网络端到端打通时间缩短为1s-2s。华为云容器网络Yangtse的warm pool其实早在裸金属容器之前就已经在云容器实例CCI中得到了实践,是CCI能以30秒扩容1000容器的绝对优势领先业界的技术手段之一。突破ENI数量限制,支持大规模容器扩容上文中讲到了,单服务器ENI的规格上限,限制了容器的高密度部署和大规模快速扩容,在第二代裸金属容器中,我们把容器网络组件全部卸载到了华为云擎天卡上,突破了传统架构的约束,最大ENI数量提升数十倍,单服务器可部署的容器数量也相应提升。同时,得益于擎天架构资源共池优势,裸金属容器还可以向虚拟机容器扩容,而在虚拟机容器上,容器网络Yangtse使用了Trunkport技术,结合ENI的优势,在保障性能的前提下,单台服务器理论上可为千容器同时提供直通网络能力。ELB直通容器,应对海量冲击更平稳解决了速度和规模的问题后,其实还存在一个隐藏的杀手,处理不好,可能使我们新扩容出来的容器全部命丧于此。在传统的容器网络对接外部ELB方案中,外部流量会从ELB先到Kubernetes的Node Port(工作节点所在主机的端口),然后再转发给后端容器,增加的这一跳不仅带来了时延和故障几率,也影响了ELB使用时的灵活性。当应用业务流量增长触发扩容时,如果ELB直接全量发放分摊的流量请求,海量请求会迅速压垮(overload)新扩的容器,造成扩容失败。发生这种情况跟业务的处理逻辑或运行时行为相关:新扩容的后端实例需要一边处理请求一边加载热点数据到本地Cache,或者需要根据接收到的请求即时编译(JIT)和加载相应的代码模块,这都需要“慢启动”(slow-start)过程,即根据业务模型,自定义初始流量比例和阶梯递增速率,比如:15%的初始流量,每5秒增加10%。但是,当ELB挂接宿主服务器(节点)的网络端口(Node Port)时,特别是一个节点上部署多个容器时,由于存在节点二次分发,ELB无法感知到最终的后端容器,进而无法做到容器级别的流控,也难以保证稳态后的负载均衡。容器网络Yangtse实现了与华为云ELB v3独享型负载均衡实例的直通,独享型ELB v3资源独立,实例的性能不受其它实例的影响,而且流量从ELB直通至后端容器,保证转发性能和稳定性。关于裸金属容器网络的揭秘,就到这里了。后续我们将围绕华为云云原生技术平台Vessel,逐一为您解读华为云容器的黑科技。
-
大家应该都看过某些明星导致社交媒体平台宕机的新闻吧?明星事件带来的突发流量触发业务扩容,以前是扩容虚机,速度慢还情有可原,现在大部分互联网平台都使用容器了,为什么扩容速度有些时候还是跟不上流量增长的节奏呢?这里主要有两个问题。01一是网口发放速度匹配容器扩容速度的问题。如果是即时按需创建容器,比如10秒内启动5000个容器实例,这要求10秒内完成端到端的网口创建、挂接和网络打通。处理流程上,容器网络控制器需要调用VPC网络API批量创建5000个网口,VPC控制面生成相应的配置,下发到各个主机节点,主机按配置创建好全部网口再挂接到节点上,进而挂接到容器上,与此同时,分布在各个主机和各类网关的数据面转发表也要同步完成刷新。传统的I层网络架构,无论是管控面还是数据面都存在瓶颈点,全流程网络打通的速度,无法匹配容器扩容速度。02再一个就是网口规模的问题,由于ENI原本是针对I层虚机或裸机的网卡扩展机制,数量受节点规格的限制,有的厂商是按照主机的flavor限定ENI的规格数量,某些厂商提供了固定的最大数量,比如8个,这些规格约束有商业成本的考虑,但根本原因还是规模目标不同。容器为了榨干节点资源,支持0.1核甚至更小的容器,单节点理论上可部署近千容器实例,也就需要近千网口,这与现有VPC网络的管控面和数据面规模有数量级差距。网络资源预热让秒级扩容成为可能如何应对发放速度不匹配问题?计算机系统结构的经典答案:加“Cache”层, 就是按预定策略在节点建立预分配ENI的warm pool,集群纳管节点时,自动创建并配置好若干个ENI放入pool中,在批量启动容器的时候只需要把已经ready的ENI从warm pool中取出挂接到容器即可。网络资源预热能够有效弥补VPC 网络资源分配性能不能匹配容器生命周期的现状。加‘Cache’的机制能够支撑容器大批量创建删除场景。Warm pool机制主要解决端到端网络打通时间长的问题。目前VPC网络单节点网卡是串行处理的,而且单个网络设备绑定时间也达到了30s-60s,不做预热的情况下容器网络端到端打通时间在一分钟以上。分钟级容器启动时间是不可接受的。Warm pool机制在裸金属节点上预挂载一定数量ENI(用户可根据服务部署并发量自定义配置),容器随时调度到预热节点上都有即时可用的ENI网卡。经过warm pool的优化,容器网络端到端打通时间缩短为1s-2s。华为云容器网络Yangtse的warm pool其实早在裸金属容器之前就已经在云容器实例CCI中得到了实践,是CCI能以30秒扩容1000容器的绝对优势领先业界的技术手段之一。突破ENI数量限制支持大规模容器扩容上文中讲到了,单服务器ENI的规格上限,限制了容器的高密度部署和大规模快速扩容,在第二代裸金属容器中,我们把容器网络组件全部卸载到了华为云擎天卡上,突破了传统架构的约束,最大ENI数量提升数十倍,单服务器可部署的容器数量也相应提升。同时,得益于擎天架构资源共池优势,裸金属容器还可以向虚拟机容器扩容,而在虚拟机容器上,容器网络Yangtse使用了Trunkport技术,结合ENI的优势,在保障性能的前提下,单台服务器理论上可为千容器同时提供直通网络能力。ELB直通容器应对海量冲击更平稳解决了速度和规模的问题后,其实还存在一个隐藏的杀手,处理不好,可能使我们新扩容出来的容器全部命丧于此。在传统的容器网络对接外部ELB方案中,外部流量会从ELB先到Kubernetes的Node Port(工作节点所在主机的端口),然后再转发给后端容器,增加的这一跳不仅带来了时延和故障几率,也影响了ELB使用时的灵活性。当应用业务流量增长触发扩容时,如果ELB直接全量发放分摊的流量请求,海量请求会迅速压垮(overload)新扩的容器,造成扩容失败。发生这种情况跟业务的处理逻辑或运行时行为相关:新扩容的后端实例需要一边处理请求一边加载热点数据到本地Cache,或者需要根据接收到的请求即时编译(JIT)和加载相应的代码模块,这都需要“慢启动”(slow-start)过程,即根据业务模型,自定义初始流量比例和阶梯递增速率,比如:15%的初始流量,每5秒增加10%。但是,当ELB挂接宿主服务器(节点)的网络端口(Node Port)时,特别是一个节点上部署多个容器时,由于存在节点二次分发,ELB无法感知到最终的后端容器,进而无法做到容器级别的流控,也难以保证稳态后的负载均衡。容器网络Yangtse实现了与华为云ELB v3独享型负载均衡实例的直通,独享型ELB v3资源独立,实例的性能不受其它实例的影响,而且流量从ELB直通至后端容器,保证转发性能和稳定性。 关于裸金属容器网络的揭秘,就到这里了。后续我们将围绕华为云云原生技术平台Vessel,逐一为您解读华为云容器的黑科技。
-
【】企业IT·开发者必看!为什么云开发是互联网大势所趋?【转载华为云社区】云开发是一个已经存在了很多年的概念,但在过去未能真正成为主流。然而,由于云和软件即服务的宏观趋势的结合,以及技术的进步,如容器技术 Docker 和 Kubernetes,云开发现在有机会最终成为基于云的应用程序的新标准开发。1.什么是云开发?云开发或基于云的开发有许多定义。广泛的定义是云开发是一种软件开发方法,它使用云环境在实际的开发阶段执行未完成的软件。这意味着你的软件在云中运行,它通常不会在你的本地计算机上运行。如果你开发的软件是在云环境中运行的,那么项目的临时环境、测试和生产环境也会在云上。其他一些人将云开发定义为使用基于浏览器和在线的 IDE。虽然基于浏览器的编辑器通常链接到云环境来执行软件,但也可以使用本地编辑器并在云中执行软件。来自 Kubernetes 和 CNCF 社区的另一个更近期的术语是“云原生开发”,但它是一个更普遍的概念,指的是“基于容器的、动态编排的、利用微服务架构的应用程序开发”。因此,它更关注开发什么,而不是如何开发。2.为什么云开发现在有了突破?·商业环境已经发生了变化。包括软件市场的一些变化可能会导致这种开发方式的复兴,甚至是最终的突破。在过去的几年里,软件世界发生了很多变化,使得云开发变得更加顺理成章和简单:使用云来运行软件已经成为常态。如今,使用云来处理生产工作负载已经成为许多公司的标准。这种转变与软件即服务销售模式的出现有关,也是云开发必不可少的第一步——只有当生产负载在云中时,将开发运行时间转移到云中才有意义。因此,云计算的广泛采用也增加了云开发的潜在用户基础。·软件变得越来越复杂随着人工智能(AI)、机器学习(ML)和微服务的兴起,软件的复杂性以及运行这些软件所需的计算资源显著增加。由于本地计算机本身的计算能力有限,它们不能够运行用户想要开发的每一个软件。在某些情况下,这甚至可能使得在开发过程中不可避免地使用云。·软件已经独立于运行环境由于使用了 Docker 和 Kubernetes 等容器技术,软件现在通常被打包在可以在任何环境下运行的容器中,无论是云还是本地环境,只要基础技术是可用的。这意味着,如果你已经在生产负载中使用了 Kubernetes,并且使用了容器,那么从本地开发切换到基于云的开发将非常简单。·一些障碍已被扫除过去的一些挑战现在得到了极大解决,这使得云开发更具可行性:网络传输如果你想在云中开发,你总是需要一个互联网连接来与云进行通信。幸运的是,在过去的几年里,人们的平均网络连接速度越来越快,而且 WiFi 无处不在。由于出于开发目的,通常只更改很小的源代码文件,因此在开发过程中不需要传输太多数据,所以现在的延迟通常是无关紧要的。部署耗时如果你不使用在线 IDE,那么你的代码需要以某种方式转移到云中,并且你的应用程序还需要更新。特别是对于容器技术,新的解决方案例如 DevSpace 可以自动将你的变更传输到云中,并且不需要重新启动容器就可以更新你的应用程序。这样可以减少部署时间ーー因为你不需要为每个小变更重新编译运行所有代码,云开发就像是本地开发。拥抱云的必要性调整遗留应用程序以便它能够在云环境中顺利运行可能 需要相当长的时间。尽管对于是否会有一个(完美的)工具可以将每个软件应用程序转换成完美的本地云应用程序仍然存在疑问,但考虑到云和 SaaS 的宏观趋势,对于越来越多的公司来说,转向云似乎是必要的。控制云访问如果每个开发人员都与云进行交互,他们就需要以某种方式访问云。集中管理和控制这种访问可能是一个巨大的挑战,特别是对于较大的团队。然而,由于公共云解决方案,创建新的基于云的开发实例非常容易。甚至可以只使用一个实例,然后在开发人员之间共享访问权。安全问题在开发过程中,在云中运行代码意味着从一开始,所有的源代码都在云中。这不一定是个问题 ーー 因为大多数公司已经在使用云中的代码库了。云成本如果你使用的是公共云,你必须为你使用的资源付费。如果你的团队中的所有开发人员都需要自己的云环境,那么计算资源的成本可能会很快变得相当高,但是现在这些成本已经能更好的降低,并且更大范围的提高开发者与软件供应商的利润增益。3.云开发的好处?总的来说,条件已经变得更好了,使用云开发比以往任何时候都更容易。现在的问题是企业IT和移动开发者为什么要这么做。无限的计算能力尽管你的计算机只能为本地开发提供有限的资源,但使用云实际上可以提供无穷无尽的计算能力。对于微服务应用程序,开发者可能需要大量的电力来启动和运行所有的服务,有时候这在本地是完全不可能的。在这种情况下,企业如果想继续有效地改进其应用程序,就只能转向云计算ーー这也是为了开发。减少准备工作不管运行环境如何,运行一个应用程序可能需要有很多步骤。但是,如果使用云开发,团队中的一个人可以设置和配置所有东西,所有其他团队成员都可以直接启动。在 Kubernetes 的世界中,这可以通过诸如 Helm DevSpace 等开源工具来实现,在这些工具中,你可以配置整个环境,然后必须将其部署到云环境中。这种可复制性是云的一个主要优势,因为硬件或操作系统之间没有差异。它也非常灵活,你可以根据个人需要进行调整。此外,公共云供应商提供了一系列工具和构建模块,你可以非常快速地开始工作。新的合作可能性和标准化由于标准化,很容易在团队中复制 bug 并相互支持。甚至可以让同事直接访问你的云环境来修复某些内容或分享你的工作成果。这可以带来更多的团队合作,形成一种新的团队合作形式,每个人都可以贡献自己的力量。从任何地方访问由于你的应用程序在开发过程中已经在云中运行,因此你不必总是使用具有非常特定设置的同一台计算机。你可以随意切换本地硬件,这样当你的计算机出现故障需要更换时就更容易了。这也支持现代的工作文化,比如在家工作或者在外工作。生活在 DevOps 文化中在云中直接开发针对云的软件非常有意义,因为在应用程序的整个生命周期中始终使用非常类似的环境。这可以减少将应用程序部署到生产环境后可能出现的错误和问题的数量。为此,基于云的开发将在你的团队中培养 DevOps 文化。开发门槛更低,效率更高提供一个数据接口容易,实现一个功能也容易,难的是解决数据的并发性,负载均衡,数据库吞吐量等难题,而这些恰恰是影响数据响应速度的关键点。而能否以快、以优、以稳制胜恰恰是当今企业发展的关键,也是大家都不可避免要面对和解决的问题。云开发为企业IT和移动开发者提供的一站式后端云服务,可以帮助他们统一构建和管理资源,免去了移动应用开发过程中繁琐的服务器、代码搭建及运维、域名注册及备案、数据接口实现等繁琐流程,让开发者可以专注于业务逻辑的实现,而无需理解后端逻辑及服务器运维知识,开发门槛更低,效率更高。
-
一、导语C++是一门被广泛使用的系统级编程语言,更是高性能后端标准开发语言;C++虽功能强大,灵活巧妙,但却属于易学难精的专家型语言,不仅新手难以驾驭,就是老司机也容易掉进各种陷阱。本文结合作者的工作经验和学习心得,对C++语言的一些高级特性,做了简单介绍;对一些常见的误解,做了解释澄清;对比较容易犯错的地方,做了归纳总结;希望借此能增进大家对C++语言了解,减少编程出错,提升工作效率。二、陷阱我的程序里用了全局变量,为何进程退出会莫名其妙的core掉?Rule:C++在不同模块(源文件)里定义的全局变量,不保证构造顺序;但保证在同一模块(源文件)里定义的全局变量,按定义的先后顺序构造,按定义的相反次序析构。我们程序在a.cpp里定义了依次全局变量X和Y;按照规则:X先构造,Y后构造;进程停止执行的时候,Y先析构,X后析构;但如果X的析构依赖于Y,那么core的事情就有可能发生。结论:如果全局变量有依赖关系,那么就把它们放在同一个源文件定义,且按正确的顺序定义,确保依赖关系正确,而不是定义在不同源文件;对于系统中的单件,单件依赖也要注意这个问题。std::sort()的比较函数有很强的约束,不能乱来相信工作5年以上至少50%的C/C++程序员都被它坑过,我已经听到过了无数个悲伤的故事,《圣斗士星矢》,《仙剑》,还有别人家的项目《天天爱消除》,都有人掉坑,程序运行几天莫名奇妙的Crash掉,一脸懵逼。如果要用,要自己提供比较函数或者函数对象,一定搞清楚什么叫“严格弱排序”,一定要满足以下3个特性:非自反性非对称性传递性尽量对索引或者指针sort,而不是针对对象本身,因为如果对象比较大,交换(复制)对象比交换指针或索引更耗费。注意操作符短路考虑游戏玩家回血回蓝(魔法)刷新给客户端的逻辑。玩家每3秒回一点血,玩家每5秒回一点蓝,回蓝回血共用一个协议通知客户端,也就是说只要有回血或者回蓝就要把新的血量和魔法值通知客户端。玩家的心跳函数heartbeat()在主逻辑线程被循环调用void GamePlayer::Heartbeat(){ if (GenHP() || GenMP()) { NotifyClientHPMP(); }}如果GenHP回血了,就返回true,否则false;不一定每次调用GenHP都会回血,取决于是否达到3秒间隔。如果GenMP回蓝了,就返回true,否则false;不一定每次调用GenMP都会回血,取决于是否达到5秒间隔。实际运行发现回血回蓝逻辑不对,Word麻,原来是操作符短路了,如果GenHP()返回true了,那GenMP()就不会被调用,就有可能失去回蓝的机会。你需要修改程序如下:void GamePlayer::Heartbeat(){ bool hp = GenHP(); bool mp = GenMP(); if (hp || mp) { NotifyClientHPMP(); }}逻辑与(&&)跟逻辑或(||)有同样的问题, if (a && b) 如果a的表达式求值为false,b表达式也不会被计算。有时候,我们会写出 if (ptr != nullptr && ptr->Do())这样的代码,这正是利用了操作符短路的语法特征。别让循环停不下来for (unsigned int i = 5; i >=0; --i){ //...}程序跑到这,WTF?根本停不下来啊?问题很简单,unsigned永远>=0,是不是心中一万只马奔腾?解决这个问题很简单,但是有时候这一类的错误却没这么明显,你需要罩子放亮点。内存拷贝小心内存越界memcpy,memset有很强的限制,仅能用于POD结构,不能作用于stl容器或者带有虚函数的类。带虚函数的类对象会有一个虚函数表的指针,memcpy将破坏该指针指向。对非POD执行memset/memcpy,免费送你四个字:自求多福注意内存重叠内存拷贝的时候,如果src和dst有重叠,需要用memmov替代memcpy。理解user stack空间很有限不能在栈上定义过大的临时对象。一般而言,用户栈只有几兆(典型大小是4M,8M),所以栈上创建的对象不能太大。用sprintf格式化字符串的时候,类型和符号要严格匹配因为sprintf的函数实现里是按格式化串从栈上取参数,任何不一致,都有可能引起不可预知的错误; /usr/include/inttypes.h里定义了跨平台的格式化符号,比如PRId64用于格式化int64_t用c标准库的安全版本(带n标识)替换非安全版本比如用strncpy替代strcpy,用snprintf替代sprintf,用strncat代替strcat,用strncmp代替strcmp,memcpy(dst, src, n)要确保[dst,dst+n]和[src, src+n]都有有效的虚拟内存地址空间。多线程环境下,要用系统调用或者库函数的安全版本代替非安全版本(_r版本),谨记strtok,gmtime等标准c函数都不是线程安全的。STL容器的遍历删除要小心迭代器失效vector,list,map,set等各有不同的写法:int main(int argc, char *argv[]){ //vector遍历删除 std::vector v(8); std::generate(v.begin(), v.end(), std::rand); std::cout << "after vector generate...\n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); for (auto x = v.begin(); x != v.end(); ) { if (*x % 2) x = v.erase(x); else ++x; } std::cout << "after vector erase...\n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); //map遍历删除 std::map m = {{1,2}, {8,4}, {5,6}, {6,7}}; for (auto x = m.begin(); x != m.end(); ) { if (x->first % 2) m.erase(x++); else ++x; } return 0;}有时候遍历删除的逻辑不是这么明显,可能循环里调了另一个函数,而该函数在某种特定的情况下才会删除当前元素,这样的话,就是很长一段时间,程序都运行得好好的,而当你正跟别人谈笑风生的时候,忽然crash,这就尴尬了。圣斗士星矢项目曾经遭遇过这个问题,基本规律是一个礼拜game server crash一次,折磨团队将近一个月。比较low的处理方式可以把待删元素放到另一个容器WaitEraseContainer里保存下来,再走一趟单独的循环,删除待删元素。当然,我们推荐在遍历的同时删除,因为这样效率更高,也显得行家里手。三、性能空间换取时间通过空间换取时间是提高性能的惯用法,bitmap,int map[]这些惯用法要了然于胸。减少拷贝 & COW了解Copy On Write。只要可能就应该减少拷贝,比如通过共享,比如通过引用指针的形式传递参数和返回值。延迟计算和预计算比如游戏服务器端玩家的战力,由属性a,b决定,也就是说属性a,b任何一个变化,都需要重算战力;但如果ModifyPropertyA(),ModifyPropertyB()之后,都重算战力却并非真正必要,因为修改属性A之后有可能马上修改B,两次重算战力,显然第一次重算的结果会很快被第二次的重算覆盖。而且很多情况下,我们可能需要在心跳里,把最新的战力值推送给客户端,这样的话,ModifyPropertyA(),ModifyPropertyB()里,我们其实只需要把战力置脏,延迟计算,这样就能避免不必要的计算。在GetFightValue()里判断FightValueDirtyFlag,如果脏,则重算,清脏标记;如果不脏,直接返回之前计算的结果。预计算的思想类似。分散计算分散计算是把任务分散,打碎,避免一次大计算量,卡住程序。哈希减少字符串比较,构建hash,可能会多费一点存储空间,但收益可观,信我。日志节制日志的开销不容忽视,要分级,可以把日志作为debug手段,但要release干净。编译器为什么不给局部变量和成员变量做默认初始化因为效率,C++被设计为系统级的编程语言,效率是优先考虑的方向,c++秉持的一个设计哲学是“不为不必要的操作付出任何额外的代价”。所以它有别于java,不给成员变量和局部变量做默认初始化,如果需要赋初值,那就由程序员自己去保证。结论:从安全的角度出发,不应使用未初始化的变量,定义变量的时候赋初值是一个好的习惯,很多错误皆因未正确初始化而起,C++11支持成员变量定义的时候直接初始化,成员变量尽量在成员初始化列表里初始化,且要按定义的顺序初始化。理解函数调用的性能开销(栈帧建立和销毁,参数传递,控制转移),性能敏感函数考虑inlineX86_64体系结构因为通用寄存器数目增加到16个,所以64位系统下参数数目不多的函数调用,将会由寄存器传递代替压栈方式传递参数,但栈帧建立、撤销和控制转移依然会对性能有所影响。递归的优点、缺点虽然递归函数能简化程序编写,但也常常带来运行速度变慢的问题,所以需要预估好递归深度,优先考虑非递归实现版本。递归函数要有退出条件且不能递归过深,不然有爆栈危险。四、数据结构和容器了解std::vector的方方面面和底层实现vector是动态扩容的,2的次方往上翻,为了确保数据保存在连续空间,每次扩充,会将原member悉数拷贝到新的内存块; 不要保存vector内对象的指针,扩容会导致其失效 ;可以通过保存其下标index替代。运行过程中需要动态增删的vector,不宜存放大的对象本身 ,因为扩容会导致所有成员拷贝构造,消耗较大,可以通过保存对象指针替代。resize()是重置大小;reserve()是预留空间,并未改变size(),可避免多次扩容; clear()并不会导致空间收缩 ,如果需要释放空间,可以跟空的vector交换,std::vector .swap(v),c++11里shrink_to_fit()也能收缩内存。理解at()和operator[]的区别 :at()会做下标越界检查,operator[]提供数组索引级的访问,在release版本下不会检查下标,VC会在Debug版本会检查;c++标准规定:operator[]不提供下标安全性检查。C++标准规定了std::vector的底层用数组实现,认清这一点并利用这一点。常用数据结构数组:内存连续,随机访问,性能高,局部性好,不支持动态扩展,最常用。链表:动态伸缩,脱离插入极快,特别是带前后驱指针,内存通常不连续(当然可以通过从固定内存池分配规避),不支持随机访问。查找:3种:bst,hashtable,基于有序数组的bsearch。二叉搜索树(RBTree),这个从begin到end有序,最坏查找速度logN,坏处内存不连续,节点有额外空间浪费;hashtable,好的hash函数不好选,搜索最坏退化成链表,难以估计捅数量,开大了浪费内存,扩容会卡一下,无序;基于有序数组的bsearch,局部性好,insert/delete慢。五、最佳实践对于在启动时加载好,运行中不变化的查询结构,可以考虑用sorted array替代map,hash表等因为有序数组支持二分查找,效率跟map差不多。对于只需要在程序启动的时候构建(排序)一次的查询结构,有序数组相比map和hash可能有更好的内存命中性(局部命中性)。运行过程中,稳定的查询结构(比如配置表,需要根据id查找配置表项,运行过程中不增删),有序数组是个不错的选择;如果不稳定,则有序数组的插入删除效率比map,hashtable差,所以选用有序数组需要注意适用场合。std::map or std::unorder_map?想清楚他们的利弊,map是用红黑树做的,unorder_map底层是hash表做的,hash表相对于红黑树有更高的查找性能。hash表的效率取决于hash算法和冲突解决方法(一般是拉链法,hash桶),以及数据分布,如果负载因子高,就会降低命中率,为了提高命中率,就需要扩容,重新hash,而重新hash是很慢的,相当于卡一下。而红黑树有更好的平均复杂度,所以如果数据量不是特别大,map是胜任的。积极的使用const理解const不仅仅是一种语法层面的保护机制,也会影响程序的编译和运行。const常量会被编码到机器指令。理解四种转型的含义和区别避免用错,尽量少用向下转型(可以通过设计加以改进)static_cast, dynamic_cast,const_cast,reinterpret_cast,傻傻分不清?C++砖家说:一句话,尽量少用转型,强制类型转换是C Style,如果你的C++代码需要类型强转,你需要去考虑是否设计有问题。理解字节对齐字节对齐能让存储器访问速度更快。字节对齐跟cpu架构相关,有些cpu访问特定类型的数据必须在一定地址对齐的储存器位置,否则会触发异常。字节对齐的另一个影响是调整结构体成员变量的定义顺序,有可能减少结构体大小,这在某些情况下,能节省内存。牢记3 rules和5 rules,当然C++11又多了&&的copy ctor和op=版本只在需要接管的时候才自定义operator=和copy constructor,如果编译器提供的默认版本工作的很好,不要去自找麻烦,自定义的版本勿忘拷贝每一个成分,如果要接管就要处理好。组合优先于继承,继承是一种最强的类间关系典型的适配器模式有类适配器和对象适配器,一般而言,建议用对象适配的方式,而非用基于继承的类适配方式。减少依赖,注意隔离最大限度的减少文件间的依赖关系,用前向声明拆解相互依赖。了解pimpl技术。头文件要自给自足,不要图省事all.h,不要包含不必要的头文件,也不要把该包含的头文件推给user去包含,一句话,头文件包含要不多不少刚刚好。严格配对打开的句柄要关闭,加锁/解锁,new/delete,new[]/delete[],malloc/free要配对,可以使用RAII技术防止资源泄露,编写符合规范的代码Valgrind对程序的内存使用方式有期望,需要干净的释放,所以规范编程才能写出valgrind干净的代码,不然再好的工具碰到不按规划写的代码也是武功尽废啊。理解多继承潜在的问题,慎用多继承多继承会存在菱形继承的问题,多个基类有相同成员变量会有问题,需要谨慎对待。有多态用法抽象基类的析构函数要加virtual关键字主要是为了基类的析构函数能得到正确的调用。virtual dtor跟普通虚函数一样,基类指针指向子类对象的时候,delete ptr,根据虚函数特征,如果析构函数是普通函数,那么就调用ptr显式(基类)类型的析构函数;如果析构函数是virtual,则会调用子类的析构函数,然后再调用基类析构函数。避免在构造函数和析构函数里调用虚函数构造函数里,对象并没有完全构建好,此时调用虚函数不一定能正确绑定,析构亦如此。从输入流获取数据,要做好数据不够的处理,要加try catch;没有被吞咽的exception,会被传播从网络数据流读取数据,从数据库恢复数据都需要注意这个问题。协议尽量不要传float,如果传float要了解NaN的概念,要做好检查,避免恶意传播可以考虑用整数替代浮点,比如万分之五(5%%),就保存5。定义宏要遵循常规要对每个变量加括弧,有时候需要加do {} while(0)或者{},以便能将一条宏当成一个语句。要理解宏在预处理阶段被替换,不用的时候要#undef,要防止污染别人的代码。了解智能指针和指针的误用理解基于引用计数法的智能指针实现方式,了解所有权转移的概念,理解shared_ptr和unique_ptr的区别和适用场景考虑用std::shared_ptr管理动态分配的对象。指针能带来弹性,但不要误用,它的弹性指一方面它能在运行时改变指向,可以用来做多态,另一方面对于不能固定大小的数组可以动态伸缩,但很多时候,我们对固定大小的array,也在init里new/malloc出来,其实没必要,而且会多占用sizeof(void*)字节,而且增加一层间接访问。size_t到底是个什么?我该用有符号还是无符号整数?size_t类型是被设计来保存系统存储器上能保存的对象的最大个数。32位系统,一个对象最小的单位是一个字节,那2的32次方内存,最多能保存的对象数目就是4G/1字节,正好一个unsigned int能保存下来(typedef unsigned int size_t)。同样,64位系统,unsigned long是8字节,所以size_t就是unsigned long的类型别名。对于像索引,位置这样的变量,是用有符号还是无符号呢?像money这样的属性呢?一句话:要讲道理,用最自然,最顺理成章的类型。比如索引不可能为负用size_t,账户可能欠钱,则money用int。比如:template <class T> class vector{ T& operator(size_t index) {}};标准库给出了最好的示范,因为如果是有符号的话,你需要这样判断if (index < 0 || index >= max_num) throw out_of_bound();而如果是无符号整数,你只需要判断 if (index >= max_num),你认可吗?整型一般用int,long就很好,用short,char需要很谨慎,要防止溢出整型包括int,short,long,long long和char,没错,char也是整型,float是实型。绝大多数情况下,用int,long就很好,long一般等于机器字长,能直接放到寄存器,硬件处理起来速度也通常更快。很多时候,我们希望用short,char达到减少结构体大小的目的。但是由于字节对齐的原因,可能并不能真正减少大小,而且1,2个字节的整型位数太少,一不小心就溢出了,需要特别注意。所以,除非在db、网络这些对存储大小非常敏感的场合,我们才需要考虑是否以short,char替代int,long。其他情况下,就相当于为省电而不开楼道的灯,省不了多少钱却冒着摔断腿的危险。局部变量更没有必要用(unsigned) short,char等,栈是自动伸缩的,它既不节省空间,还危险,还慢。六、扩展了解c++高阶特性模板和泛型编程,union,bitfield,指向成员的指针,placement new,显式析构,异常机制,nested class,local class,namespace,多继承、虚继承,volatile,extern "C"等有些高级特性只有在特定情况下才会被用到,但技多不压身,平时还是需要积累和了解,这样在需求出现时,才能从自己的知识库里拿出工具来对付它。了解C++新标准关注新技术,c++11/14/17、lambda,右值引用,move语义,多线程库等c++98/03标准到c++11标准的推出历经13年,13年来程序设计语言的思想得到了很大的发展,c++11新标准吸收了很多其他语言的新特性,虽然c++11新标准主要是靠引入新的库来支持新特征,核心语言的变化较少,但新标准还是引入了move语义等核心语法层面的修改,每个CPPer都应该了解新标准。OOD设计原则并不是胡扯设计模式六大原则(1):单一职责原则设计模式六大原则(2):里氏替换原则设计模式六大原则(3):依赖倒置原则设计模式六大原则(4):接口隔离原则设计模式六大原则(5):迪米特法则设计模式六大原则(6):开闭原则熟悉常用设计模式,活学活用,不生搬硬套神化设计模式和反设计模式,都不是科学的态度,设计模式是软件设计的经验总结,有一定的价值;GOF书上对每一个设计模式,都用专门的段落讲它的应用场景和适用性,限制和缺陷,在正确评估得失的情况下,是鼓励使用的,但显然,你首先需要准确get到她。
-
【转载华为云社区】文章链接:https://bbs.huaweicloud.com/blogs/163264当今K8s独霸天下之时,咱们站在更高的角度,好好的看看K8s的网络是以什么理念构筑的。以及一个容器集群的好保姆,是如何分别照顾 南北流量和东西流量的。1 简单介绍下Kubernetes略。。容器集群管理的事实标准了,不知道要打屁股。(ps:本章节可参考唐老师的《K8S前世今生》文章)2 世界上的集群都一个样有点标题党哈,不过我接触过的各种集群也不少,各种各样:Ø OpenStack:在一大堆物理机上面,管理(启动/停止)VM的。Ø SGE,Slurm,PBS:在一大堆电脑集群里面,管理(启动/停止)App的。Ø Yarn:在一大堆电脑集群里面,管理(启动/停止)大数据App的。Ø CloudFoundry:在一大堆电脑集群里面,管理(启动/停止)容器的Ø Kubernetes:在一大堆电脑集群里面,管理(启动/停止)容器的。它们都有一些共同特点:2.1 跨节点跑xx程序这个xx程序一定是首先单机可以运行的。比如OpenStack:单机上面可以用qemu启动VM,想跨节点管理VM,就引入了OpenStack。Kubernetes也一样:单机上面可以跑Docker容器;想跨节点管理容器,就得引入集群管理老大的概念。2.2 有一个管事的老大A)集群管理的老大,负责让手下的某个小弟干活。别管是命令式(直接下命令)的,还是申明式(发告示)的,小弟收到命令后,乖乖干活就是了。B) 同时,这个集群管理的老大,需要有脑子,不然小弟数量多了管不好。所以它需要拿笔记一记。比如OpenStack的老大得带个Mysql数据库;Kubernetes把笔记记在了ETCD里面(不过ETCD这个本子太小,记得东西不能太大,这是另话)。C) 不管哪种老大,都得有个军师。一个新活来到老大这里,那么多小弟,指派给谁不是干呀。这活实际分配给哪个小弟,这得军师说了算,所以每中集群软件都自己写了一套 Scheduler 算法,可谓程序员间浪费重复轮子之典型代表。2.3 小弟上面都有一个Agent这个小弟上面的Agent,时刻向老大汇报自己的状态:活不活着,忙还是闲,方便老大派活。同时,Agent也就是那台电脑里面的地头蛇了,帮忙老大负责各种临时事物。只是大家的取名不一样:OpenStack:取名NovaKubernetes:取名KubeletYarn:取名NodeManager2.4 老大怎么给小弟发号施令一般老大都是通过:消息队列来,给小弟发号施令的,而不是亲自上门(直连)下达命令。原因么,当然是小弟可能临时出门(故障)了呗~ 直接上门可能不通,放消息队列里面就可靠多了。等小弟出差回来,还能看到老大下达的任务令。Ø OpenStack:用 RabbitMQ 发号施令Ø Kubernetes:用 ETCD 发号施令Ø CloudFoundry:用 NATS 发号施令上面这些组件都是带消息通知的功能,区别有些有名,有些没那么出名罢了。比如我们的K8s:特别需要提一下:K8s这个老大不简单,找了个ETCD这个好帮手。这小家伙挺神,既能当笔记本记点事情(代替OpenStack中的Mysql),又能当公告牌,通知点消息(代替OpenStack中的Rabbit)。所以K8s这个容器集群管理相对OpenStack这个虚机管理不需要数据库,666~3 K8s怎么设计容器网络的呢3.1 南北流量要看到K8s诞生的时候,那时是有CloudFoundry和Docker的,且都已经比较成熟。那时作为PaaS一哥的CF对容器网络的抽象:主要考虑平台外部,怎么访问容器里面的App。而平台内部的App之间如何互相访问,几乎没有太多的设计。由上图所示,可以看到,平台外部访问,一般都是上下画的,所以也叫做南北流量。我们这么叫,也是便于程序员之间沟通和理解。Ps:PaaS的基本原型大致都这样:3.2 东西流量K8s吸取了前辈们的精华,除了平台外部访问App,还新增考虑了平台内部,App之间如何互相访问。即K8s通过增加一个负载均衡的“LB”设备,来搞定平台内部的App间互相访问。给每个App取个别名,在LB上面登记一下,就可以被内部其他App访问。由上图所示,可以看到,平台内部访问,一般都是水平画的,所以也叫做东西流量。一个完整的PaaS平台,就是需要南北流量+东西流量,全套治理的。3.3 Docker原生访问方式还记得唐老师的《Docker网络实现》章节吧,Docker容器可以通过“节点IP+节点Port”的方式访问到容器。原理的容器所在节点,设置了NAT规则。报文一到达节点,根据目的端口,转发进入容器。3.4 小结:K8s中3种访问容器的通道(1) 通过南北流量(从集群外部访问App)访问App容器(2) 通过东西流量(集群内App之间)访问App容器(3) 通过Docker原生自带的方式,访问App容器下一章节,我们简单介绍下每种方式,K8s分别怎么去实现的。4 K8s怎么实现容器访问虽然K8s上面,有多种访问App容器的方法。但是不管用什么方式访问,一个App想要能被访问,就得得到K8s的同意。K8s把这个许可证叫做“Service”:也就是不管什么南北流量、东西流量,你的App想要能被访问,就得先申请Service许可证。4.1 南北流量要实现一个App的访问通道,一定要2个东西:(1)LB负载均衡器 + (2)注册映射关系。映射关系就是:报文来了,应该转发给哪个App实例? 即:找到 “哪个App + 哪个实例”。负载均衡器呢,一般大家爱用Nginx,不过也有其他类型的实现。K8s比CF聪明的地方是,没有自己去实现LB。而只定义了App需要怎么样才能登记到LB上面。即只定规范,不限制实现(这种思路,在k8s里面好多,比如存储的CSI,运行时的CRI的,容器网络的CNI 都是这样。)Ø 4层LB最简单的4层LB实现,K8s取了个名字:LoadBalancer(1)。即定义:xx协议+xx端口 =》xx应用,具体规则自己去看资料。Ø 7层LB为了定义7层LB的规则,K8s给规范取了名字:Ingress(2)。即定义:xx网址+xx-URL路径 =》xx应用,具体规则也自己看K8s资料。南北LB都是全局级的,即:全局一个(HA多实例,咱也当一个整体)就行;不需要每个Slaver节点上一个。4.2 东西流量东西流量,也一样,需要LB+规则注入。这里,K8s设计就比较有意思。逻辑上,如上图所示。在LB部分的实现上,K8s很巧妙的要求每个节点上面都一个“小LB”。所以实现上,大致如上图所示。Ø 本地LB本地LB,要求每个节点都有。所以最开始的版本,K8s使用了Linux使用广泛的iptables来实现。后面由于iptables性能不是特别给力,又有了 IPVS 实现。然后其他各式各样的民间实现也有。Ø 本地控制器LB需要一个控制器,每个本地“小LB”带配备一个小控制器,一样的,也是每个节点一个。和小LB一一对应。K8s给它取了个名字:Kube-proxyØ 假IP地址每个K8s上的App,都可以申请“行走江湖的名号”,用来代表自己。K8s就会给你的App分配一个Service许可证,许可证上面带着“影子IP”,任何集群内部只要访问这个IP,就等于访问你的App。实现上:1. 先到K8s那登记,说我想要个“名号”2. 通过后,K8s会告知每个节点上的本地LB3. 从此以后,每个LB都认识这个“影子IP”了,访问它,就代表访问对应App。由于这个“名号”是集群颁布的,所以仅在集群内有效。K8s取名:ClusterIP(3)。关于东西流量的故事,还可以去看看唐老师之前的《网络骗子》篇。4.3 Docker原生访问方式除了上面几种访问方式,K8s也为原生的Docker访问通道留了个名字:NodePort(4)。这种方式,在《Docker网络实现》里面说过,靠主机Host转发实现。既然是主机搞定,所以这条路和本地LB实现,就合并一起搞定了。如上图,K8s下发规则的时候,顺便把这条路的规则也下发下去。ps:由于每个本地LB都收到了K8s的通告小皮鞭,所以每个K8s的节点,都开通了NodePort通道哦。即:无论哪个Slaver节点的Port都可以通往该App。4.4 小结K8s在实现容器网络的时候,造了很多概念:(1) LoadBalancer(2) Ingress(3) ClusterIP(4) NodePort本质都是一样的,就是LB+登记规范。 如果你看过《DNS篇》+《Docker网络实现》,这些就比较好理解。ps:具体本地LB怎么实现?真有兴趣可以去搜搜Kube-proxy的代码解读。我本身不是很关心,因为其实你给每个节点安装一个 Nginx 也可以做到的。5 总结K8s的网络概念,特别是Service,是K8s里面的精华,务必需要搞明白。(1) K8s南北流量,用Loadbalancer(4层)和Ingress(7层)搞定。(2) K8s的东西流量,用Service概念搞定。特别的,还给了个“行走江湖用的名号”,取名ClusterIP(一个不存在的假IP地址)。(3) 容器所在Host组网,存在Docker原生通道,K8s给重新包装了个名字:NodePort。所以只要报文到达Slaver节点,就能通到容器里面。另外,提一下一直没有说的东西(怕概念太多,影响理解):K8s的整个网络底座,是要求节点IP和容器IP是能互相连通的(即:在节点上面ping容器IP,是可以通的)。具体则是通过容器网络实现的。这个实现很多,Flannel,Calico等,本质要么隧道,要么子网(可以看看物理网络里面的《VLAN和Vxlan》篇,关于如何划分门派的篇章)。
-
在刚刚结束的CLOUD NATIVE+ OPEN SOURCE Virtual Summit China 2020上,由华为云云原生团队主导的容器批量计算项目Volcano正式发布1.0版本,标志着Volcano项目已经开始走向成熟与稳定。Volcano项目介绍Volcano是基于Kubernetes的云原生批量计算引擎,基于华为云在AI、大数据领域的深厚业务积累,补齐了Kubernetes在面向AI、大数据、高性能计算等批量计算任务调度、编排等场景下的短板,向下支持鲲鹏、昇腾、X86等多元算力,向上使能TensorFlow、Spark、华为MindSpore等主流行业计算框架,让数据科学家和算法工程师充分享受到云原生技术所带来的高效计算与极致体验。Volcano架构示意图随着Kubernetes作为AI、大数据和高性能批量计算的下一代基础设施的趋势逐渐清晰,越来越多的企业对Kubernetes在深度学习、科学计算、高性能渲染等方面提出了更高的要求。然而Kubernetes作为普适的容器化解决方案,仍与业务诉求存在一定差距,主要体现在:K8s的原生调度功能无法满足计算要求K8s作业管理能力无法满足AI训练的复杂诉求数据管理方面,缺少计算侧数据缓存能力,数据位置感知等功能资源管理方面缺少分时共享,利用率低硬件异构能力弱Volcano的诞生正是基于这些痛点,在调度、作业管理、数据管理、资源管理四个方面进行了重点优化。增强了任务调度能力,如公平的调度(fair-share)、组调度(gang-scheduling) 进一步优化了作业管理能力,如multiple pod template能力、更灵活的error handling机制 增加计算侧数据缓存,提升数据的传输与读取效率引入多维度的综合评分机制,实现资源更高效的管理和分配多元算力支持:支持x86、鲲鹏和昇腾等算力 Volcano项目进展时间轴Volcano v1.0新特性介绍Volcano v1.0的核心概念和关键特性,主要包含以下要点:Queue、PodGroup、Volcano Job等核心概念均已实现支持Binpack、Conformance、DRF、Gang、Preempt、Reclaim、Priority、Proportion等多种调度策略支持Rest API、CLI等多种交互方式完成与Spark、Argo、MPI、Flink、Mxnet、Paddlepaddle、Tensorflow、MindSpore等主流高性能计算框架的无缝对接支持Job的全生命周期管理和动态扩缩容支持GPU异构与共享完备的golangCI-lint check、e2e以建立增强代码质量和稳定性除以上特性外,Volcano始终保持与Kubernetes社区、Golang最新版本保持一致。Volcano社区和生态建设进展经过一年多的发展,Volcano的社区和生态建设已经步入快车道。截至目前,社区和生态建设取得了以下成绩:社区贡献者80+社区贡献参与组织15+,包括华为、百度、腾讯、AWS、IBM、 Oracle等获得Star 1100+,Fork 220+代码库7个,Release 6个Issue 320+,PR 590+已完成对Spark、Argo、MPI、Flink、Mxnet、Paddlepaddle、Tensorflow、MindSpore、Cromwell等10+主流计算框架的支持华为云CCE(云容器引擎)、CCI(云容器实例)、ModelArts等多个云服务已将Volcano集成为基础设施底座并商用,服务领域已涵盖AI、大数据应用、基因计算、批处理等场景,并实现与华为鲲鹏、昇腾处理器深度融合,最快每秒1000个容器的调度发放,成为高性能、极致性价比的批量计算解决方案。深入了解Volcano如果想更加深入了解Volcano,可以参考以下资源:Volcano官网:https://volcano.sh/Github:https://github.com/volcano-shVolcano简介:https://github.com/volcano-sh/volcanoVolcano设计:https://github.com/volcano-sh/volcano/tree/master/docs/designVolcano路线图:https://github.com/volcano-sh/volcano/blob/master/docs/community/roadmap.mdVolcano社区交流微信群:Volcano CN未来可期随着Volcano v1.0的发布,Volcano社区建设与上下游生态的融合必将更加紧密,基于Volcano的商业应用也将极大地促进AI、大数据、科学计算、渲染等领域充分享受到云计算带来的极大便利和极致体验,助力企业数字化转型进入新的高度。展望未来,华为云也将在云原生领域持续耕耘,持续引领创新、繁荣生态,助力各行业走向快速智能发展之路。
-
变量声明命名规范变量名由字母、$、下划线(_) 开头,紧跟着字母、数字、下划线(_)。要注意:变量名不能由数字开头,很多初学者包括我在内,会记乱了。举个合法命名的栗子let name = '余人杰'; let $id = 'hw91364016';重点:Javascript的关键字不可以用作变量名,如 if、while、class、true、false 等等。 栗子如下:let if = '余人杰';声明变量的方式可以使用var、let、const等方式进行定义。声明和赋值结合,如下:var name = '余人杰'; let id = 'hw91364016'; const PI = 3.141596;在不明确变量要赋什么值时,可以先声明变量,等执行一些逻辑后,知道具体值后再给其赋值。let name; ..... name = '余人杰';当功能复杂时,你可能需要多个变量进行存储值,你可以使用 , 同时声明多个变量。let name = '余人杰', id = 'hw91364016', age = 18;弱类型Javascript变量的特点是弱类型的,它可以保存任何类型的数据,说白了就是变量没有类型可言,但它的值有类型。变量可以更换不同类型的数据let name = '余人杰'; console.log(typeof name); // string name = 666; console.log(typeof name); // number name = null; console.log(typeof name); // object我们可以看出,Javascript的变量类型,是被所引用的值决定的。声明变量的基础知识就这些没了?当然不是,还有个很重要的,话说面试常出的变量扩展知识点-变量提升。变量提升怎么理解?pink老师划过重点说过,解析器会先解析代码,然后会将声明变量的 声明 的提升到 最前 ,这就是变量提升。使用 var, function (){} 定义的代码,声明会被提升到前面,赋值还在原位置。那记住这个有什么用呢?很大作用,有时可以拯救你的程序,你一不注意,就会因为这个常出错。比如:我们特意写个出错的程序来感受下:var name = '余人杰'; console.log(name); let else = 'if';我们在浏览器的控制台上测试下,发现报这个错 Uncaught SyntaxError: Unexpected token 'else'。细心的话,你会发现,console.log(name); 是在写在 let else = 'if'; 前面,按我们小白思维理解,应该是先打印'余人杰',但结果并非如此。 而是还没有到执行环节就报错了。如果你还不明白,我再写一个简单的代码给你理解,把解析器执行过程呈现出来给你细细品:console.log(name); var name = '余人杰'; console.log(name); // 解析器执行过程是这样的 var name; console.log(name); name = '余人杰'; console.log(name);TDZ课后,我还查阅了一些资料,还有一个由变量扩展的知识,术名叫TDZ,叫做暂时性死区。怎么理解?就是说变量虽然在作用域内存在了,但要使用时,必须在let 或者 const 声明后才可以。我们在平时编程时,多注意TDZ,养成先声明后使用的习惯,可以让程序更健壮。先声明变量,再使用多使用let/const,少使用var心得:变量声明虽然时Javascript基础中的基础,但稍不注意,就会导致整个程序出错。所以大家即使以后成了大牛,也要注意,多回顾基础,巩固根基,让路走的更远。转载链接:https://bbs.huaweicloud.com/blogs/192045
-
转载https://blog.csdn.net/weixin_38748858/article/details/103514909?utm_medium=distribute.pc_relevant_bbs_down.none-task--2~all~first_rank_v2~rank_v25-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task--2~all~first_rank_v2~rank_v25-1.nonecase云原生的解释可以说五花八门,本文从不同角度探讨云原生的内涵以及如何从不同维度准确理解它的含义。云原生起源网上有些文章提到云原生是“Pivotal公司的Matt Stine于2013年首次提出云原生(CloudNative)的概念”。我搜索了英文“CloudNative”,阅读了首页的所有文章,里面没有一篇提到“Matt Stine首次提出云原生”,但它们每一篇都提到了“云原生计算基金会”的定义。“Matt Stine”确实写了一本书,叫《迁移到云原生架构》,他以前确实在Pivotal公司工作,但说他“首次提出云原生(CloudNative)的概念”应该是不准确的, 而且他的定义和云原生的含义是有一定偏差的。我觉得比较接近的说法是Netflix公司首创了云原生,详见Going Cloud Native: 6 essential things you need to know。虽然那篇文章主要是讲的Netflix如何开创了微服务,但Netflix的微服务是部署在亚马逊云上的。而当时亚马逊云也才刚起步,各方面都不成熟,Netflix是它的最大客户。是Netflix的层出不穷的需求帮助亚马逊云不断完善它的功能和性能,最终登顶云服务商。因此Netflix的微服务演进是和云计算交织在一起,共同推进的。Netflix在微服务领域的开创和领先地位是大家公认的,它的“Netflix OOS”系列工具至今仍被广泛使用,特别是Java社区,并被移植到其他语言。在这个过程中,也同时开创了云计算的先河,它的起点是2009年。详情请见Goto Berlin - Migrating to Microservices (Fast Delivery)。但我想说的是云计算(Cloud)和云原生(Cloud Native)还是有很大区别的。Netflix是云计算的开拓者,但并不是云原生的创造者。云原生的基石是k8s,没有k8s就没有云原生, 而k8s的1.0版诞生于2015年。云原生计算基金会(CNCF)也诞生于2015年并致力于推动云原生的发展。云原生的概念是在2017才开始被广泛接受和流行,因此云原生和云计算是由本质区别的。云原生的诞生是和云原生计算基金会密切相关的。云原生计算基金会(CNCF)的定义:下面就让我们看一下CNCF给出的云原生的定义:“云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。云原生计算基金会(CNCF)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。”摘要来源:CNCF Cloud Native Definition v1.0这个定义还是比较靠谱的,尽管它并不严谨,也并没有挖掘出云原生的本质。但考虑到每个组织的目的和立场不同,看问题的角度不同,CNCF的主要目的是培育云原生工具市场,因此它的定义带有很重的实用色彩,偏重工具方面。若是从这个角度看,这个定义还是比较贴切的。我觉得唯一不严谨的地方是把微服务列了进去,其他的都没什么问题。让我们来分析一下定义中提到的工具。其中k8s是整个云原生的基石,也是CNCF的第一个项目。云原生的整个生态体系都是依靠k8s建立起来的。因此在k8s之前是不可能有云原生的。定义里还提到了容器(Container)、服务网格(Service Mesh)、微服务(Microservice)、不可变基础设施(immutable infrastructure)和声明式API(declarative APIs)。其中容器(Container)是k8s的底层引擎,服务网格(Service Mesh)是建立在k8s上的针对请求的扩展功能,不可变基础设施(immutable infrastructure)是现代运维的基石,声明式API(declarative APIs)是k8s的编码方式,这些无一不是和k8s紧密相关的。但微服务(Microservice)就不同了,它其实跟云原生没什么关系,它们是两个完全不同的东西并沿着各自的轨道独立向前发展。但由于认容器技术和微服务是天生的良配,它们现在的演进轨道交织在一起密不可分。但实际上没有容器技术,微服务也可以部署在虚机上,只不过资源的利用率可能不够高。没有微服务,容器技术虽然不能大展宏图,但也能在分布式应用里找到一席之地。当然把它们放在一起确实能如虎添翼,但把微服务划归到云原生里实在是有点扩大外延,**圈地的意味。因为云原生的重点还是在基础设施,运维和运行环境以及软件的开发环境,而微服务是一个软件的架构,两者之间有明显的不同。云原生的表层含义那么到底什么是“云原生(Cloud Native)”呢?它分表层含义和深层含义。表层含义从字面上理解就比较容易了,我们管母语叫“Native Language”,也就是你一生下来就说的语言。“Cloud Native”就是一开始开发的时候就是为了最终部署到云环境上的。而在云计算初创时,大部分的程序都是从本地环境移植到云上的,它们在设计是就根本没考虑云环境的问题。云环境与本地环境的差异那么部署到云环境和部署到本地服务器有什么不同?这个才是问题的本质。你可能会说“是容器技术”,这个是现代云计算的不可或缺的支撑,但云计算开始的时候是基于虚拟化的,并没有容器技术,是在发展的过程中才有了容器技术。是“自动伸缩(auto-scaling)”吗?这是云环境的一个主要优点和特性,但它只是结果,不是本质。云技术的三大基石:基础设施即代码 (Infrastructure As Code)基础设施即代码是指把创建基础设施(包括服务器和网络环境)的命令像应用程序一样储存在源码库中,并进行版本管理。这样创建基础设施的过程就变成了部署软件的过程。它的最大的好处就是可重复性。以前的方法是用人工敲入命令来创建运行环境,出了问题就在原来的基础之上进行修修补补,一旦需要把整个环境重新建立,很难保证与原来的一样。 当使用基础设施即代码之后,再也没有了这个担心。详情请见 InfrastructureAsCode不可变基础设施(immutable infrastructure)说道这里,我们不得不提“不可变基础设施(immutable infrastructure)“,它是基础设施即代码的升级版。有了基础设施即代码之后,随时都可以通过运行软件再构建出一个一模一样的服务器和其他需要的设备,并且还能预装应用程序,创建的时间还是秒级的。这时当服务器出现问题时,就没有必要去花时间查找原因了并修复了,而是直接把服务器销毁重新创建一个新的。因此这时的基础设施是不可变的,只有创建和删除,而没有修改操作。这彻底改变了运维的方式。详情请见 What is “Immutable Infrastructure”?声明式API(declarative APIs)声明式API也是基础设施即代码的升级版。最开始时,当用软件定义基础设施时是用的过程式描述,也就是通过运行一系列的命令来创建运行环境。后来发现更好的办法是描述最终运行环境的状态,而由系统来决定如何来创建这个环境。例如,你的描述就变成“创建一个有三个Nginx的集群”,而不是把创建Nginx的命令运行三次组成一个集群。这样的好处是当运行环境与描述不符合时,系统能检测到差异,并自动修复,这样系统就有了自动容错的功能。上面讲了云计算环境和传统基础设施的不同,其实随着云计算的发展,传统基础设施也在不断地采纳云计算的先进技术和理念,例如虚拟化和容器技术,而各个云计算厂商也提供了本地私有云的版本。只不过在公有云上的管理功能更强大,而通常本地私有云的版本是公有云的一个简化版。云原生应用程序的不同上面讲到了,只有一开始就是按照部署到云环境的要求来设计的应用程序才是云原生的。那么部署到云环境需要做哪些特殊设计呢?它主要有两个部分:第一部分是服务调用。不论是微服务之间的调用,还是微服务调用数据库或前端调用后端,调用的方式都是一样的。都需要知道IP地址,端口和协议,例如“http://127.0.0.1:80”, 其中“http”是协议,“127.0.0.1”是IP地址,“80”是端口。由于程序是部署在k8s上的,k8s会负责程序之间的寻址和调用。由于k8s会自动销毁出错的服务器,并创建新的服务器,IP地址就变成了动态的,而不是静态的。这时就只能通过服务名而不是IP地址来进行调用。也就是说k8s会给每个服务一个服务名,并通过k8s内部的DNS对服务名进行寻址。服务名是写在k8s的配置文件里的,软件设计的关键让应用程序和k8s配置文件都共享相同的调用地址。第二部分是数据的持久存储。在程序运行时,经常要访问持久存储(硬盘)上的数据,例如日志,配置文件或临时共享数据。程序在容器中运行,一旦出现问题,容器会被摧毁,k8s会自动重新生成一个与原来一模一样的容器,并在上面重新部署应用程序。在集群环境下,用户感觉不到容器故障,因为系统已经自动修复了。但当容器被摧毁时,容器上的数据也一起被摧毁了,因此要保证程序运行的连续性,就要让持久存储不受容器故障的影响。如果你对它的具体设计感兴趣,请参见把应用程序迁移到k8s需要修改什么?云原生的深层含义不过云原生还有一层引申含义。当你的最终生产环境是云环境时,你的本地开发环境最好也是云环境,这虽然不是必须的,但它能保证本地环境和生产环境的一致性,减少部署时的意外,是一个很自然的选择。而要在本地使用云环境来进行开发,你需要一系列的工具来保证开发的顺利和高效。要想了解云原生的开发环境及工具,请继续阅读下一篇“ 云原生开发环境初探"。索引:Going Cloud Native: 6 essential things you need to knowGoto Berlin - Migrating to Microservices (Fast Delivery)CNCF Cloud Native Definition v1.0InfrastructureAsCodeWhat is “Immutable Infrastructure”?把应用程序迁移到k8s需要修改什么?云原生开发环境初探不堆砌术语,不罗列架构,不迷信权威,不盲从流行,坚持独立思考
W--wangzhiqiang
发表于2020-07-30 15:10:38
2020-07-30 15:10:38
最后回复
W--wangzhiqiang
2020-07-30 15:10:38
1676 0
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第三期2026/08/21 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;念擎-华为云AI开发者运营案例开发专家
本期直播内容:AI六层能力首次详细解读 + 新一代华为云开发者空间亮相 + 校园案例直播带练
回顾中
热门标签