首页 文章 精选 留言 我的

精选列表

搜索[博客],共10001篇文章
优秀的个人博客,低调大师

WorkBuddy 五天重构博客小纪

ai.hackcv.com 现在已经上线了,和它原来的兄弟站 hackcv.com 摆在一起,对比一目了然。借着这次"能对比了"的节点,我把整个改造过程记录下来——它不是一篇技术教程,而是一份真实的过程记录:一个对 TypeScript 几乎零基础的人,是怎么在 WorkBuddy 的帮助下,把一个 Hugo 静态站重做成带 API、带 LLM 精选、能自己跑在云服务器上的 Next.js 聚合站。

优秀的个人博客,低调大师

精选博客系列|vSphere 8 Update 1 介绍

去年,我们推出了 vSphere 8,这是传统和下一代应用程序的企业工作负载平台,该产品于2022年11月正式推出。今天,我们很高兴地宣布vSphere 8 Update 1版本。通过这个版本,客户可以获得增强的管理员操作效率,更高端的人工智能/机器学习工作负载的超级性能以及跨环境的升级安全性。预计 vSphere 8 Update 1 将在本财季结束前的四月份实现初始可用性,并根据我们的新发布模式进行发布。 vSphere 8 Update 1 在三个关键领域为客户提供了增强的价值。 增强运营效率 IT 管理员需要执行很多重复性任务来维护他们的环境。主机和集群配置、更新和升级对基础设施的持续健康至关重要,但它们会耗费宝贵的时间和资源,使其无法运行关键任务。随着 Update 1 的发布,vSphere 8 增加了降低运营负担和提高效率的额外功能。 下面讨论了一些关键功能: VMware vSphere® 配置文件TM 这个功能在 vSphere 8 中作为技术预览引入,将在 Update 1 中变得普遍可用和得到完全支持。通过使用 JSON 文件的声明性模型,简化主机生命周期管理,大大减少了管理员的手动工作和时间。现在可以在集群级别无缝地管理所需的主机配置、合规性、漂移纠正和安全标准。创建新集群时,可以轻松地将主机配置复制到整个集群中。 将 VMware SkylineTM Health Diagnostics 与 VMware vCenter 集成 Skyline Health Diagnostics 是 VMware 的自助诊断平台。它可以帮助管理员诊断问题、解决故障,并自动安排和运行健康检查。管理员可以在联系支持人员之前使用此工具来排除故障。通过 Update 1,Skyline Health Diagnostics 将与 vCenter 集成,使其易于管理员访问。这种集成将有助于节省时间并提高工作负载、ESXi 和 vCenter 的可用性。 同一 GPU 上的异构 vGPU 配置文件 通过 Update 1,在同一物理 GPU 上的 vGPU 现在可以被分配到不同的应用类型 - VDI(虚拟桌面基础设施)、计算、图形等。这将帮助管理员提高 GPU 利用率,减少 GPU 中的工作负载碎片化,并降低成本。 vSphere Green Metrics vSphere 8 引入了 vSphere Green Metrics,帮助管理员在主机级别跟踪工作负载和基础架构操作消耗的电力。在 Update 1 中,我们通过提供 VM 级别的电力消耗来增强了此功能。现在管理员可以在 VM 级别监视和优化工作负载的电力消耗,有助于为其组织的环境、社会和治理(ESG)目标做出贡献。 超级加速工作负载性能 组织面临着不断提高 AI/ML 工作负载性能的不断增长的挑战。AI/ML 工作负载的大小不断增加,并且使用的 GPU 数量也在不断增加。对 GPU 的需求正在爆炸式增长。 支持 NVIDIA NVSwitch 在 Update 1 中,vSphere 8 支持 NVIDIA NVSwitch(使用 Hopper 上的 NVLink,双向速度高达 900GB/s),每个主机最多连接 8 个 GPU,每个 VM 最多连接 8 个 GPU,大大加快了 AI/ML 应用程序的性能。 提高安全性 当今,企业或组织面临越来越多的安全风险。维护安全可能会耗费大量时间。vSphere具有几个内置的安全功能,通过Update 1,以下功能增强了重要工作负载的安全性: Okta联合身份管理vCenter 更新1扩展了对第三方身份提供商的支持,包括Okta、Active Directory、OpenLDAP和Active Directory联合服务(ADFS)。使用Okta的管理员可以一次登录vCenter和NSX Manager。还可以启用Okta的多因素身份验证。此功能提高了客户环境的效率和安全性。 支持使用vTPM的VM的容错 容错通过维护一个相同的VM来提供VM的连续可用性,以便在发生故障时可以快速故障转移。现在支持使用vTPM模块的VM的容错。此功能可帮助管理员实现重要VM的连续可用性和安全性。 ESXi快速启动支持TPM 2.0芯片的服务器 快速启动用于生命周期管理活动,如补丁、升级等,并节省大量时间。随着Update 1的推出,TPM 2.0不需要被禁用以实现快速启动。因此,这种增强功能既节省了管理员的生命周期管理时间,又消除了安全漏洞。 本文作者:Andrew Scott,VMware数字化员工体验团队的产品营销经理。 内容来源|公众号:VMware 中国研发中心 有任何疑问,欢迎扫描下方公众号联系我们哦~

优秀的个人博客,低调大师

博客大赛】论python中器的组合

python中有几种特殊的对象,如可迭代对象、生成器、迭代器、装饰器等等,特别是生成器这些可以说是python中的门面担当,应用好这些特性的话,可以给我们的项目带来本质上的提升,装逼不说,这构筑的是代码护城河,祖传代码别人再也不敢动。熟悉特性的概念在和面试官交流的过程中也是挺吃香的不是吗?现在这么卷了,面试官也很少会问到迭代啊、递归啊什么的,反过来说,在社招面试被问到了这种看起来挺浅薄的问题,可能就是挂的节奏了:)嘿嘿,真的,毕竟面试是要有相对应的面试时间的,总要有水题来刷时间啊┑( ̄Д  ̄)┍ 三者关系 可迭代对象、迭代器和生成器这三个概念很容易混淆,前两者通常不会区分的很明显,只是用法上有区别。生成器在某种概念下可以看做是特殊的迭代器,它比迭代实现上更加简洁。三者关系如图: 可迭代对象 可迭代对象Iterable Object,简单的来理解就是可以使用for或者while来循环遍历的对象。比如常见的 list、set、dict等,可以用以下方法来测试对象是否是可迭代 >>> from collections import Iterable >>> isinstance('yerik', Iterable) # str是否可迭代 True >>> isinstance([5, 2, 0], Iterable) # list是否可迭代 True >>> isinstance(520, Iterable) # 整数是否可迭代 False 本质 可迭代对象的本质就是可以向我们提供一个迭代器帮助我们对其进行迭代遍历使用。 可迭代对象通过 __iteration__提供一个迭代器,在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的迭代器,然后通过这个迭代器来以此获取对象中的每一个数据,这也是一个具备__iter__方法的对象,就是一个可迭代对象的原因。 from collections import Iterable class ListIter(object): def __init__(self): self.container = list() def add(self, item): self.container.append(item) # 可以通过注释以下两行代码来感受可迭代对象检测的原理 def __iter__(self): pass if __name__ == '__main__': listiter = ListIter() print(isinstance(listiter, Iterable)) 通过对可迭代对象使用iter() 函数获取此可迭代对象的迭代器,然后对取到的迭代器不断使用next() 函数来获取下一条数据。iter() 函数实际上就是调用了可迭代对象的__iter__方法。 迭代器 迭代器是用来记录每次迭代访问到的位置,当对迭代器使用next() 函数的时候,迭代器会返回他所记录位置的下一个位置的数据。实际上,在使用next() 函数的时候,调用的就是迭代器对象的__next__方法。python3 要求迭代器本身也是可迭代对象,所以还要为迭代器对象实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器本身正是一个迭代器,所以迭代器的__iter__方法返回自身即可. 对所有的可迭代对象调用dir() 方法时,会发现他们都默认实现了__iter__ 方法。我们可以通过iter(object) 来创建一个迭代器。 >>> x = [8 ,8 ,8] >>> dir(x) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>> y = iter(x) >>> type(x) <class 'list'> >>> type(y) <class 'list_iterator'> 调用iter() 之后,创建一个list_iterator对象,会发现增加了__next__ 方法。我们不妨断言所有实现了__iter__ 和__next__ 两个方法的对象,都是迭代器。 >>> dir(y) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] 迭代器是带状态的对象,它会记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。 >>> next(y) 8 >>> next(y) 8 >>> next(y) 8 >>> next(y) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration for 循环的本质 我们经常会写出以下代码: for item in obj: 实际上这行代码执行了以下4步: 判断obj 是否为可迭代对象,即是否有__iter__方法 在第一步成立前提下, 系统调用iter()函数. 得到obj对象__iter__方法的返回值,这个其实可以自己显式调用 __iter__方法的返回值是一个迭代器,有__iter__ 和__next__方法 for 不断的调用迭代器中__next__方法并将值赋给item, 当遇到Stopiteration 的异常后循环结束. 生成器 利用迭代器,可以在每次迭代获取数据,通过next() 方法时按照特定的规律进行生成,但是在实现一个迭代器时,关于当前迭代到的状态需要自己记录,进而才能根据但前状态生成下一个数据。为了达到记录当前状态,并配合next() 函数进行迭代使用,可以采用更简便的语法,即生成器,其本质上是一类特殊的迭代器。 生成器和装饰器都是python中最吸引人的两个黑科技,生成器虽没有装饰器那么常用,但在某些针对的情境下十分有效。 比如我们在创建列表的时候,可能会受到内存限制(特别是在刷题的时候),容量肯定是有限的,而且不可能全部给他一次枚举出来。这里可以使用列表生成式,但是它有一个致命的缺点就是定义即生成,非常的浪费空间和效率。如果列表元素可以按照某种算法推算出来,那我们可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。这种一边循环一边计算的机制,称为生成器:generator。 要创建一个generator ,最简单的方法是改造列表生成式 >>> [x*x for x in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> (x*x for x in range(10)) <generator object <genexpr> at 0x7f8fcc3b5e60> 还有一个方法是生成器函数,同样是通过def 定义,之后通过yield来支持迭代器协议,所以比迭代器写起来更简单,我们甚至可以下断言道,只要在一个函数中有yield 关键字那么这个函数就不是一个函数,而是生成器 >>> def spam(): ... yield "first" ... yield "second" ... yield "3" ... yield 123 ... >>> spam <function spam at 0x7f8fd0391c80> >>> gen = spam() >>> gen <generator object spam at 0x7f8fcc3b5f68> >>> gen.__next__() 'first' >>> gen.__next__() 'second' >>> gen.__next__() '3' >>> gen.__next__() 123 >>> gen.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 当然一般都是通过for来使用的,这样不用关心StopIteration的异常 >>> for it in spam(): ... print(it) ... first second 3 123 更进一步的是将生成器和迭代器进行组合,这里是通过iter()来实现 >>> for it in iter(spam()): ... print(it) ... first second 3 123 本质上就是在进行函数调用的时候,返回一个生成器对象。使用next() 调用的时候,遇到yield就返回,记录此时的函数调用位置,下次调用next() 时,从断点处开始。 说实话有的时候,迭代器和生成器很难区分,毕竟generator 是比Iterator更加简单的实现方式。官方文档写到 Python’s generators provide a convenient way to implement the iterator protocol. 因此完全可以像使用iterator 一样使用generator ,当然除了定义。毕竟定义一个iterator,需要分别实现__iter__() 方法和__next__() 方法,但generator 只需要一个小小的yield 。 此外generator 还有send() 和close() 方法,都是只能在next()调用之后,生成器出去挂起状态时才能使用的。 总的来说生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。现在就可以动手重构你的代码了,但凡看到类似: def something(): res = list() for ... in iter(...): res.append(x) return res 都可以用生成器函数来替换: def iter_something(): for ... in iter(...): yield x python 是支持协程的,也就是微线程,就是通过generator 来实现的。配合generator 我们可以自定义函数的调用层次关系从而自己来调度线程。 实战 通过两个经典例子来真实感受一下迭代器与生成器的妙用 斐波那契数列 用 普通函数,迭代器和生成器来实现斐波那契数列,区分三种 输出数列的前N个数 普通函数 这个其实就是内循环,没啥好说的,经过max次循环完成输出 def fab(max): n,a,b = 0,0,1 L = [] while n < max: L.append(b) a,b = b,a+b n += 1 return L Iterator方法 为了节省内存,和处于未知输出的考虑,使用迭代器来改善代码。 class fab(object): ''' Iterator to produce Fibonacci ''' def __init__(self,max): self.max = max self.n = 0 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.n < self.max: r = self.b self.a,self.b = self.b,self.a + self.b self.n += 1 return r raise StopIteration('Done') 迭代器什么都好,就是写起来不简洁。所以用 yield 来改写第三版。 Generator def fab(max): n,a,b = 0,0,1 while n < max: yield b a,b = b,a+b n += 1 使用下面来输出 for a in fab(8): print(a) 看起来很简洁,而且有了迭代器的特性。 更进一步 这个是将迭代器和生成器结合起来使用,这个只需要进行一个小小的改动就有的成效 for a in iter(fab(8)): print(a) 树上的应用 斐波那契数列可以说是所有程序猿入门必经的练习,那么对于树的遍历也是非常实用的小窍门,不妨看下leetcode的897. 递增顺序搜索树这道题,按中序遍历将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 我们用上迭代器与生成器的组合之后得到题解 def increasingBST(self, root: TreeNode) -> TreeNode: def dfs(node: TreeNode): if node: yield from dfs(node.left) yield node.val yield from dfs(node.right) ans = cur = TreeNode() for v in iter(dfs(root)): cur.right = TreeNode(v) cur = cur.right return ans.right 装饰器 装饰器Decorator是python中最吸引人的特性,其本质上还是一个函数,它可以让已有的函数不做任何改动的情况下增加功能。 非常适合有切面需求的场景,比如权限校验,日志记录和性能测试等等。比如想要执行某个函数前记录日志或者记录时间来统计性能,又不想改动这个函数,就可以通过装饰器来实现。 不用装饰器,我们会这样来实现在函数执行前插入日志 def test(): print('i am tester') def test(): print('tester is running') print('i am test') 虽然这样写是满足了需求,但是改动了原有的代码,如果有其他的函数也需要插入日志的话,就需要改写所有的函数,不能复用代码,为了实现代码复用的需求,可以这么改进 def use_logg(func): logging.warn("%s is running" % func.__name__) func() def test(): print('i am tester') use_log(teat) #将函数作为参数传入 这样写的确可以复用插入的日志,缺点就是显示的封装原来的函数,其实我们更加希望透明的做这件事。用装饰器来写 bar = use_log(bar)def use_log(func): def wrapper(*args,**kwargs): logging.warn('%s is running' % func.__name___) return func(*args,**kwargs) return wrapper def test(): print('I am tester') tester = use_log(test) tester() use_log() 就是装饰器,它把真正我们想要执行的函数test()封装在里面,返回一个封装了加入代码的新函数,看起来就像是test() 被装饰了一样。这个例子中的切面就是函数进入的时候,在这个时候,我们插入了一句记录日志的代码。这样写还是不够透明,通过@语法糖来起到tester = use_log(test)的作用。 bar = use_log(bar)def use_log(func): def wrapper(*args,**kwargs): logging.warn('%s is running' % func.__name___) return func(*args,**kwargs) return wrapper @use_log def test(): print('I am tester') @use_log def haha(): print('I am haha') test() haha() 这样看起来就很简洁,而且代码很容易复用。可以看成是一种智能的高级封装。 装饰器也是可以带参数的,这位装饰器提供了更大的灵活性。 def use_log(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) return func(*args) return wrapper return decorator @use_log(level="warn") def test(name='tester'): print("i am %s" % name) test() 实际上是对装饰器的一个函数封装,并返回一个装饰器。这里涉及到作用域的概念,可以把它看成一个带参数的闭包。当使用@use_log(level='warn')时,会将level 的值传给装饰器的环境中。它的效果相当于use_log(level='warn')(test),也就是一个三层的调用。 这里有一个美中不足,decorator 不会改变装饰的函数的功能,但会悄悄的改变一个__name__ 的属性(还有其他一些元信息),因为__name__ 是跟着函数命名走的。可以用@functools.wraps(func) 来让装饰器仍然使用func 的名字。比如 import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper functools.wraps 也是一个装饰器,它将原函数的元信息拷贝到装饰器环境中,从而不会被所替换的新函数覆盖掉。 有了装饰器,我们就可以剥离出大量与函数功能本身无关的代码,增加了代码的复用性。 总结 容器是一系列元素的集合,如str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。 可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__和__iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。 装饰器是一种特殊的闭包,本质上是一个函数 参考资料 https://wiki.python.org/moin/Generators https://www.jianshu.com/p/efaa19594cf4 https://blog.csdn.net/sunchengquan/article/details/84494101

优秀的个人博客,低调大师

博客大赛】+ 网络编程Netty之ByteBuf详解

Netty中的ByteBuf优势 NIO使用的ByteBuffer有哪些缺点 1: 无法动态扩容,ByteBuffer的长度是固定的,是初始指定的值,不能够再进行扩容了,当写入的内容大于ByteBuffer的容量时,会报越界异常 2.: API使用复杂,当要读取数据时,需要调用buffer.flip()方法,转换为读取模式,如果稍微不注意就可能出现错误,读取不到数据或者读取的数据是错误的 ByteBuf的优势和做了哪些增强 1: API操作起来更加的方便,可以直接写或者直接读 2:支持动态扩容,当写入的数据大于ByteBuf的容量时,会动态扩容,不会报错 3:提供了多种ByteBuf的实现,可以更加灵活的使用 4:提供了高效的零拷贝机制 5:ByteBuf可以内存复用 ByteBuf操作示例 ByteBuf操作 ==ByteBuf中有三个重要的属性:==1:capacity容量,初始指定的ByteBuf的大小 2:readIndex读取位置,顺序读的时候,记录读取数据的索引值 3:writeIndex写入位置,顺序写的时候,记录写入数据的索引值 ==ByteBuf常用的方法:==1:getByte和setByte,获取指定索引处的数据,是随机获取的,不会改变readIndex和writeIndex的值 2:read*,顺序读,会改变readIndex的值 3:write*,顺序写,会改变writeIndex的值 4:discardReadBytes,清除读过的内容 5:clear,清除缓冲区 6:搜索操作 7:标记和重置 8:引用计数和释放 简单的Demo示例 /** * ByteBuf的使用示例 */ public class ByteBufDemo { public static void main(String[] args) { //分配非池化,10个字节的ByteBuf ByteBuf buf = Unpooled.buffer(10); //看下ByteBuf System.out.println("------------------------原始的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //写入内容到ByteBuf byte[] bytes = {1, 2, 3, 4, 5}; buf.writeBytes(bytes); System.out.println("------------------------写入内容后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //从ByteBuf中读取内容 buf.readByte(); buf.readByte(); System.out.println("------------------------读取一些内容后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //清除读过的内容 //把读过的数据清除后,readIndex变为0,writeIndex变为3 //后面尚未读取的内容,会复制到前面去,把原来的值覆盖掉 //再次写入时,3,4,5后面的4,5会被覆盖掉 buf.discardReadBytes(); System.out.println("------------------------清除读过的数据后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //再次写入内容到ByteBuf byte[] bytesO = {6}; buf.writeBytes(bytesO); System.out.println("------------------------再次写入内容后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //清空读和写的索引值 //readIndex和writeIndex会重置为0,ByteBuf中的内容并不会重置 buf.clear(); System.out.println("------------------------清空读和写的索引值后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //再次写入内容到ByteBuf byte[] bytes2 = {1, 2, 3}; buf.writeBytes(bytes2); System.out.println("------------------------再次写入内容后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //清空ByteBuf的内容 //不会重置readIndex和writeIndex buf.setZero(0, buf.capacity()); System.out.println("------------------------清空ByteBuf的内容后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); //再次写入超出指定容量的数据到ByteBuf //会进行扩容 byte[] bytes3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; buf.writeBytes(bytes3); System.out.println("------------------------再次写入超出指定容量的数据后的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n"); } } 输出结果:上面的例子是使用堆内的ByteBuf,下面看下堆外的ByteBuf例子: //分配非池化,10个字节的directBuffer ByteBuf buf = Unpooled.directBuffer(10); //看下ByteBuf System.out.println("------------------------原始的ByteBuf-------------------------------"); System.out.println("ByteBuf参数:" + buf.toString()); directBuffer不能够使用array方法,否则会报错:java.lang.UnsupportedOperationException: direct buffer;而且使用ByteBuf是用它底层的分配器分配的,不是new一个出来,下面会具体说下。上图中,可以看到,readIndex和writeIndex把缓冲区分成了三块,readIndex会小于或者等于writeIndex,这个应该好理解,我还没有写到那里,你就去读取了,能读取到什么呢。 堆内和堆外内存 socket是操作系统底层提供给上层应用使用的网络通信API,当要去读取或者写入的数据在JVM的堆中,那么就先需要把JVM堆中需要读取的数据拷贝一份到操作系统中,然后socket再去读取,而直接内存的好处是socket可以直接读取,少了拷贝这一步操作。 ByteBuf动态扩容 下面以堆内的ByteBuf为例,查看源码,分析ByteBuf的动态扩容:动态扩容肯定是写入数据的时候,ByteBuf的容量不够了,才去扩容的,所以需要跟踪下面的代码: buf.writeBytes(bytes); 跟踪上面的writeBytes,首先进入了ByteBuf这个抽象类中,进入了下面这个抽象方法: public abstract ByteBuf writeBytes(byte[] src); 它的实现类如下:进入第一个AbstractByteBuf的方法: @Override public ByteBuf writeBytes(byte[] src) { writeBytes(src, 0, src.length); return this; } 再次调用了下面的方法: @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { //检查是否可以写入 ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); //把当前的写入位置加上写入数据的长度 writerIndex += length; return this; } src是需要写入的数据,length是写入数据的长度然后会进入ensureWritable方法,传入的参数是:写入数据的长度 @Override public ByteBuf ensureWritable(int minWritableBytes) { //参数校验 checkPositiveOrZero(minWritableBytes, "minWritableBytes"); //检查容量是否可以写入这么多数据 ensureWritable0(minWritableBytes); return this; } //检查参数是否小于0 public static int checkPositiveOrZero(int i, String name) { if (i < 0) { throw new IllegalArgumentException(name + ": " + i + " (expected: >= 0)"); } return i; } 参数校验完成后会进入ensureWritable0方法: final void ensureWritable0(int minWritableBytes) { //确保缓冲区可以访问 ensureAccessible(); //如果写入的数据长度小于等于剩余可写数据的容量,就直接返回 //就是说,容量足够写入,不需要扩容 if (minWritableBytes <= writableBytes()) { return; } if (checkBounds) { //maxCapacity是int的最大值 //检查写入的数据长度是否比可以写入的最大容量还要大 //是的话就抛异常 if (minWritableBytes > maxCapacity - writerIndex) { throw new IndexOutOfBoundsException(String.format( "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex, minWritableBytes, maxCapacity, this)); } } //正式的扩容方法 int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity); //把扩容后的新容量设置进去 capacity(newCapacity); } 进入AbstractByteBufAllocator类的扩容方法: //常量 4M static final int CALCULATE_THRESHOLD = 1048576 * 4; // 4 MiB page @Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { //校验参数 checkPositiveOrZero(minNewCapacity, "minNewCapacity"); //minNewCapacity = writerIndex + minWritableBytes //已经写入的数据索引加上当前写入的数据长度,就是需要的最小的容量 //判断是否比最大容量还大,是的话就抛异常 if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } final int threshold = CALCULATE_THRESHOLD; // 4 MiB page //如果需要的最小容量等于4M,就直接返回4M,作为扩容后的容量 if (minNewCapacity == threshold) { return threshold; } //如果需要的最小容量大于4M,就按照下面的扩容方式扩容 if (minNewCapacity > threshold) { //newCapacity = 15 / 4194304 * 4194304 int newCapacity = minNewCapacity / threshold * threshold; //如果计算出的容量大于最大容量减去4M,就把最大容量赋值给新的容量 if (newCapacity > maxCapacity - threshold) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } //如果需要的最小容量小于4M,就按照下面的方式扩容 int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } return Math.min(newCapacity, maxCapacity); } 再看下capacity方法:下面的把扩容后的容量放到ByteBuf,就是使用了arraycopy方法 @Override public ByteBuf capacity(int newCapacity) { checkNewCapacity(newCapacity); int oldCapacity = array.length; byte[] oldArray = array; if (newCapacity > oldCapacity) { byte[] newArray = allocateArray(newCapacity); System.arraycopy(oldArray, 0, newArray, 0, oldArray.length); setArray(newArray); freeArray(oldArray); } else if (newCapacity < oldCapacity) { byte[] newArray = allocateArray(newCapacity); int readerIndex = readerIndex(); if (readerIndex < newCapacity) { int writerIndex = writerIndex(); if (writerIndex > newCapacity) { writerIndex(writerIndex = newCapacity); } System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex); } else { setIndex(newCapacity, newCapacity); } setArray(newArray); freeArray(oldArray); } return this; } 下面是跟踪的代码步骤:==总结下动态扩容机制:==1:write*方法调用的时候,会通过ensureWritable0方法检查2:calculateNewCapacity方法是用来计算容量的方法 ==扩容计算方法:==1:需要的容量没有超过4M,会从64字节开始扩容,每次增加一倍,直到计算出来的容量满足需要的最小容量,假如,当前大小是256,已经写入了200字节,再次写入60字节,需要的最小容量是260字节,那么扩容后的容量是64 2 2 2=5122:需要的容量超过4M,扩容计算方法为:新容量 = 新容量的最小要求 / 4M 4M + 4M,假如当前大小是3M,已经写了2M,再写入3M,需要的最小容量是5M,那么扩容后的容量是 5 / 4 * 4 + 4 = 8M 图示1:需要的容量小于4M:图示2:需要的容量大于4M: ByteBuf有哪些实现 ByteBuf从3个维度,有8种实现方式: ByteBuf类图 //堆内 ByteBuf buf = Unpooled.buffer(10); //堆外 ByteBuf buf = Unpooled.directBuffer(10); ByteBuf提供了Unpooled非池化的类,可以直接使用,没有提供Pool池化的类,下面追踪源码看下ByteBuf是怎样分配的: Unpooled.buffer分配方式 首先进入Unpooled类: private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT; //使用默认的分配器分配堆内buffer public static ByteBuf buffer(int initialCapacity) { return ALLOC.heapBuffer(initialCapacity); } 下面会进入接口类ByteBufAllocator: //分配一个指定容量的堆内buf ByteBuf heapBuffer(int initialCapacity); 然后进入AbstractByteBufAllocator抽象类: //如果没有指定初始容量,默认的初始容量大小是256 static final int DEFAULT_INITIAL_CAPACITY = 256; //最大容量,为int的最大值 static final int DEFAULT_MAX_CAPACITY = Integer.MAX_VALUE; @Override public ByteBuf heapBuffer(int initialCapacity) { return heapBuffer(initialCapacity, DEFAULT_MAX_CAPACITY); } @Override public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) { //如果初始化的容量是0,最大的容量也是0,就返回一个空的Buf if (initialCapacity == 0 && maxCapacity == 0) { return emptyBuf; } validate(initialCapacity, maxCapacity); return newHeapBuffer(initialCapacity, maxCapacity); } //校验参数 private static void validate(int initialCapacity, int maxCapacity) { //检查参数 checkPositiveOrZero(initialCapacity, "initialCapacity"); //如果初始化的容量大于最大容量,就抛异常 if (initialCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "initialCapacity: %d (expected: not greater than maxCapacity(%d)", initialCapacity, maxCapacity)); } } 然后是newHeapBuffer抽象方法: protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity); 因为这里初始化的是非池化的,所以会进入UnpooledByteBufAllocator类: @Override protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) { //PlatformDependent.hasUnsafe()是检查当前操作系统是否支持unsafe操作 //根据支持与否,进入不同的类 return PlatformDependent.hasUnsafe() ? new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) : new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity); } 支持Unsafe操作的进入下面: InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } 不支持Unsafe的进入下面这个: InstrumentedUnpooledHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } 现在以支持Unsafe操作往下面走,进入UnpooledUnsafeHeapByteBuf类: UnpooledUnsafeHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } 再次调用了父类UnpooledHeapByteBuf: //分配器 private final ByteBufAllocator alloc; //byte数组,ByteBuf数据底层就是使用这个存储 byte[] array; public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(maxCapacity); //检查分配器是否为空 checkNotNull(alloc, "alloc"); //如果初始化的容量大于最大容量,就抛异常 if (initialCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); } this.alloc = alloc; //设置当前的数组是分配之后的数组 setArray(allocateArray(initialCapacity)); //初始化readIndex和writeIndex setIndex(0, 0); } //分配数组 protected byte[] allocateArray(int initialCapacity) { //返回一个具有initialCapacity容量大小的byte数组 return new byte[initialCapacity]; } //set数组 private void setArray(byte[] initialArray) { array = initialArray; tmpNioBuf = null; } AbstractByteBuf类下的setIndex方法: //初始化readerIndex和writerIndex @Override public ByteBuf setIndex(int readerIndex, int writerIndex) { if (checkBounds) { checkIndexBounds(readerIndex, writerIndex, capacity()); } setIndex0(readerIndex, writerIndex); return this; } final void setIndex0(int readerIndex, int writerIndex) { this.readerIndex = readerIndex; this.writerIndex = writerIndex; } 上面走到AbstractByteBuf后,就分配完了一个非池化、堆内的ByteBuf,下面是追踪的代码:==总结:==可以看到,分配一个非池化、堆内的ByteBuf,它的底层就是byte数组 Unpooled.directBuffer分配方式 首先进入的也是Unpooled类: public static ByteBuf directBuffer(int initialCapacity) { return ALLOC.directBuffer(initialCapacity); } 然后进入ByteBufAllocator抽象类: ByteBuf directBuffer(int initialCapacity); 然后到AbstractByteBufAllocator类: @Override public ByteBuf directBuffer(int initialCapacity) { return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY); } @Override public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { //如果初始化的容量和最大容量都是0,就返回一个空的Buf if (initialCapacity == 0 && maxCapacity == 0) { return emptyBuf; } //校验参数 validate(initialCapacity, maxCapacity); return newDirectBuffer(initialCapacity, maxCapacity); } protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity); 由于分配的也是一个非池化的,所以newDirectBuffer会进入UnpooledByteBufAllocator类中的实现类: @Override protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { final ByteBuf buf; //同样的,会判断是否支持unsafe操作 if (PlatformDependent.hasUnsafe()) { buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) : new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity); } else { buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return disableLeakDetector ? buf : toLeakAwareBuffer(buf); } 以InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf为例,后面两个其实也相差不大,进入UnpooledUnsafeNoCleanerDirectByteBuf类的构造方法: UnpooledUnsafeNoCleanerDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } 再次调用的父类UnpooledUnsafeDirectByteBuf: ByteBuffer buffer; public UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(maxCapacity); if (alloc == null) { throw new NullPointerException("alloc"); } //校验参数 checkPositiveOrZero(initialCapacity, "initialCapacity"); checkPositiveOrZero(maxCapacity, "maxCapacity"); if (initialCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); } this.alloc = alloc; setByteBuffer(allocateDirect(initialCapacity), false); } //分配的是一个NIO中的ByteBuffer protected ByteBuffer allocateDirect(int initialCapacity) { return ByteBuffer.allocateDirect(initialCapacity); } final void setByteBuffer(ByteBuffer buffer, boolean tryFree) { if (tryFree) { ByteBuffer oldBuffer = this.buffer; if (oldBuffer != null) { if (doNotFree) { doNotFree = false; } else { freeDirect(oldBuffer); } } } this.buffer = buffer; memoryAddress = PlatformDependent.directBufferAddress(buffer); tmpNioBuf = null; capacity = buffer.remaining(); } ByteBuffer类下面的allocateDirect: public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } 代码跟踪图:==总结:==分配非池化、堆外的ByteBuf,可以看到底层是NIO的DirectByteBuffer实现的 ByteBufAllocator类图 ByteBuf内存复用 分配池化内存 在上面根据源码知道了怎么去分配非池化内存,那么池化内存要怎么分配呢?看下面的图示:上面就是分配池化内存的步骤,接下来会根据源码具体分析 内存缓存池 jemalloc内存分配机制1:内存池中有三大区域,分别是:tiny、small、normal2:每个区域分了不同大小的格子,每个格子只能缓存对应大小的内存块3:支持最大的格子内存是32kb,超过这个大小的不能被缓存,只能被释放掉4:每个类型的格子都有对应的数量:tiny:512个,small:256个,normal:64个,例如tiny区域的每个大小的格子都有512个,如果满了就不会被回收,内存会被释放掉 回收池化内存 分配池化内存的过程 上面分析了分配非池化内存,下面看下怎么分配池化内存: ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; //分配的内存最大长度为496 ByteBuf buf1 = allocator.ioBuffer(495); System.out.printf("buf1: 0x%X%n", buf1.memoryAddress()); //此时会被回收到tiny的512b格子中 buf1.release(); //从tiny的512b格子去取 ByteBuf buf2 = allocator.ioBuffer(495); System.out.printf("buf2: 0x%X%n", buf2.memoryAddress()); buf2.release(); 先来看下ByteBufAllocator类: //默认ByteBuf分配器,在ByteBufUtil中初始化 ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR; 跟踪第一次的allocator.ioBuffer(495)代码,首先进入AbstractByteBufAllocator类: @Override public ByteBuf ioBuffer(int initialCapacity) { //如果支持Unsafe,就分配堆外内存 if (PlatformDependent.hasUnsafe()) { return directBuffer(initialCapacity); } //不支持Unsafe,就分配堆内内存 return heapBuffer(initialCapacity); } 然后调用了该类下面的directBuffer方法: @Override public ByteBuf directBuffer(int initialCapacity) { return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY); } @Override public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { //如果初始化的容量和最大容量等于0,就返回一个空的ByteBuf if (initialCapacity == 0 && maxCapacity == 0) { return emptyBuf; } validate(initialCapacity, maxCapacity); return newDirectBuffer(initialCapacity, maxCapacity); } //校验参数 private static void validate(int initialCapacity, int maxCapacity) { checkPositiveOrZero(initialCapacity, "initialCapacity"); if (initialCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "initialCapacity: %d (expected: not greater than maxCapacity(%d)", initialCapacity, maxCapacity)); } } 然后会进入池化的ByteBuf分配器PooledByteBufAllocator类,可以实现内存的复用: // cache sizes 缓存默认值 DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.tinyCacheSize", 512); DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize", 256); DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize", 64); @Override protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { //从当前线程中获取cache对象 PoolThreadCache cache = threadCache.get(); //从cache中获取Arena //Arena可以理解为一个netty提供的实际进行buf分配和管理的工具 PoolArena<ByteBuffer> directArena = cache.directArena; final ByteBuf buf; //如果有directArena就分配池化内存 if (directArena != null) { buf = directArena.allocate(cache, initialCapacity, maxCapacity); } else { //如果没有directArena,就使用非池化Unpooled buf = PlatformDependent.hasUnsafe() ? UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return toLeakAwareBuffer(buf); } 再次跟踪后进入PoolArena类:可以看到下面有三种类型tiny、small、normal enum SizeClass { Tiny, Small, Normal } PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { //获取一个ByteBuf对象 PooledByteBuf<T> buf = newByteBuf(maxCapacity); //分配内存 allocate(cache, buf, reqCapacity); return buf; } @Override protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) { //如果支持Unsafe,就初始化一个PooledUnsafeDirectByteBuf if (HAS_UNSAFE) { return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); } else { //不支持Unsafe,就初始化一个PooledDirectByteBuf return PooledDirectByteBuf.newInstance(maxCapacity); } } 下面进入PooledUnsafeDirectByteBuf类:从线程回收栈中获取一个buf,如果栈中没有,就会创建一个新的,如果有,就会返回栈中的buf //调用RECYCLER.get()时,线程栈中没有可以复用的时,会调用newObject方法,此时创建出来的buf是空的 private static final Recycler<PooledUnsafeDirectByteBuf> RECYCLER = new Recycler<PooledUnsafeDirectByteBuf>() { @Override protected PooledUnsafeDirectByteBuf newObject(Handle<PooledUnsafeDirectByteBuf> handle) { return new PooledUnsafeDirectByteBuf(handle, 0); } }; static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) { //RECYCLER,回收机制 PooledUnsafeDirectByteBuf buf = RECYCLER.get(); //取出来的可能是之前的buf,使用之前清理一下 buf.reuse(maxCapacity); return buf; } 然后再次回到PoolArena类中的allocate方法,分配内存: private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) { //将需要的内存大小计算为2^n final int normCapacity = normalizeCapacity(reqCapacity); //需要分配的内存是否是tiny或者small类型 if (isTinyOrSmall(normCapacity)) { // capacity < pageSize int tableIdx; PoolSubpage<T>[] table; boolean tiny = isTiny(normCapacity); if (tiny) { // < 512 //分配一个tiny内存 if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = tinyIdx(normCapacity); table = tinySubpagePools; } else { if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = smallIdx(normCapacity); table = smallSubpagePools; } final PoolSubpage<T> head = table[tableIdx]; synchronized (head) { final PoolSubpage<T> s = head.next; if (s != head) { assert s.doNotDestroy && s.elemSize == normCapacity; long handle = s.allocate(); assert handle >= 0; s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity); incTinySmallAllocation(tiny); return; } } synchronized (this) { //分配一块新的内存 allocateNormal(buf, reqCapacity, normCapacity); } incTinySmallAllocation(tiny); return; } if (normCapacity <= chunkSize) { if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } synchronized (this) { allocateNormal(buf, reqCapacity, normCapacity); ++allocationsNormal; } } else { // Huge allocations are never served via the cache so just call allocateHuge allocateHuge(buf, reqCapacity); } } PoolThreadCache类下的allocateTiny方法: boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) { return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity); } //从cache中获取buf private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) { int idx = PoolArena.tinyIdx(normCapacity); if (area.isDirect()) { return cache(tinySubPageDirectCaches, idx); } return cache(tinySubPageHeapCaches, idx); } 根据需要的容量获取对应的格子,走到PoolArena类下面的tinyIdx方法: static int tinyIdx(int normCapacity) { return normCapacity >>> 4; } PoolThreadCache类下的allocate方法,把缓存格子的内存分配到buf private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) { if (cache == null) { // no cache found so just return false here return false; } boolean allocated = cache.allocate(buf, reqCapacity); if (++ allocations >= freeSweepAllocationThreshold) { allocations = 0; trim(); } return allocated; } 下面是具体跟踪代码的步骤图:上面的源码是以tiny类型为例,其他两种类型类似,当第一次分配创建了一块新的内存,然后被成功回收到内存缓冲池后,再次分配对应大小的内存,会直接从内存缓冲池中取,不会再次分配一块新的内存了 内存回收的过程 接下来跟踪release()方法,看下内存回收的过程 buf1.release(); 第一次进入AbstractReferenceCountedByteBuf类:Buf的引用计数器,用于内存复用,有一个计数器refCnt,retain()计数器加一,release()计数器减一,直到计数器为0,才调用deallocate()释放,deallocate()方法由具体的buf自己实现。 @Override public boolean release() { return release0(1); } private boolean release0(int decrement) { int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement); //判断当前buf有没有被引用了,没有的话就调用deallocate if (decrement == realCnt) { if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { deallocate(); return true; } return retryRelease0(decrement); } return releaseNonFinal0(decrement, rawCnt, realCnt); } 进入PooledByteBuf类: @Override protected final void deallocate() { if (handle >= 0) { final long handle = this.handle; //表示当前的buf不在使用任何一块内存区域 this.handle = -1; //设置memory为null memory = null; //释放buf的内存 chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache); tmpNioBuf = null; chunk = null; //把buf对象放入对象回收栈 recycle(); } } 再次进入PoolArena类: void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) { //判断是否是unpooled if (chunk.unpooled) { int size = chunk.chunkSize(); destroyChunk(chunk); activeBytesHuge.add(-size); deallocationsHuge.increment(); } else { //判断是哪种类型,tiny、small、normal SizeClass sizeClass = sizeClass(normCapacity); //放入缓存 if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) { // cached so not free it. return; } freeChunk(chunk, handle, sizeClass, nioBuffer); } } //计算内存区域是哪种类型 private SizeClass sizeClass(int normCapacity) { if (!isTinyOrSmall(normCapacity)) { return SizeClass.Normal; } return isTiny(normCapacity) ? SizeClass.Tiny : SizeClass.Small; } 然后到PoolThreadCache类: boolean add(PoolArena<?> area, PoolChunk chunk, ByteBuffer nioBuffer, long handle, int normCapacity, SizeClass sizeClass) { MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass); if (cache == null) { return false; } //加入到缓存队列 return cache.add(chunk, nioBuffer, handle); } private MemoryRegionCache<?> cache(PoolArena<?> area, int normCapacity, SizeClass sizeClass) { //判断是哪种类型,然后把内存回收到哪一块 switch (sizeClass) { case Normal: return cacheForNormal(area, normCapacity); case Small: return cacheForSmall(area, normCapacity); case Tiny: return cacheForTiny(area, normCapacity); default: throw new Error(); } } private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) { int idx = PoolArena.tinyIdx(normCapacity); if (area.isDirect()) { return cache(tinySubPageDirectCaches, idx); } return cache(tinySubPageHeapCaches, idx); } 上述跟踪代码步骤图: ByteBuf零拷贝机制 Netty的零拷贝机制,是一种应用层的实现,和底层的JVM、操作系统内存机制没有过多的关联 几种示例 ==一:CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝== public static void test1() { ByteBuf buf1 = Unpooled.buffer(4); ByteBuf buf2 = Unpooled.buffer(3); byte[] bytes1 = {1,2}; byte[] bytes2 = {3,4,5}; buf1.writeBytes(bytes1); buf2.writeBytes(bytes2); CompositeByteBuf byteBuf = Unpooled.compositeBuffer(); byteBuf = byteBuf.addComponents(true, buf1, buf2); System.out.println("byteBuf: " + byteBuf.toString()); } 上面输出结果,ridx是顺序读的读取位置,widx是顺序写的写入位置,cap是新的ByteBuf的容量,components是指新的ByteBuf是由几个ByteBuf组成==二:wrappedBuffer()方法,将byte[]数组包装成ByteBuf对象== public static void test2() { byte[] bytes = {1,2,3,4,5}; ByteBuf buf = Unpooled.wrappedBuffer(bytes); System.out.println("buf:" + buf.toString()); } 输出结果中:ridx是顺序读的读取位置,widx是顺序写的写入位置,cap是ByteBuf的容量,新的ByteBuf里存的是数组的引用地址,实质操作的还是原来的数组==三:slice()方法,将一个ByteBuf对象切分成多个ByteBuf对象== public static void test3() { ByteBuf buf = Unpooled.wrappedBuffer("hello".getBytes()); ByteBuf byteBuf = buf.slice(1,2); System.out.println("byteBuf:" + byteBuf.toString()); } 输出结果中,可以看到,有两个ByteBuf,其中一个是原有的,新的ByteBuf中存放了原来的ByteBuf的引用地址,另一个是分割后的ByteBuf的引用地址

优秀的个人博客,低调大师

博客大赛】Ceph介绍及原理架构分享

1. Ceph架构简介及使用场景介绍 1.1 Ceph简介 Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。 Ceph项目最早起源于Sage就读博士期间的工作(最早的成果于2004年发表),并随后贡献给开源社区。在经过了数年的发展之后,目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack都可与Ceph整合以支持虚拟机镜像的后端存储。 1.2 Ceph特点 高性能 a. 摒弃了传统的集中式存储元数据寻址的方案,采用CRUSH算法,数据分布均衡,并行度高。 b.考虑了容灾域的隔离,能够实现各类负载的副本放置规则,例如跨机房、机架感知等。 c. 能够支持上千个存储节点的规模,支持TB到PB级的数据。 高可用性 a. 副本数可以灵活控制。 b. 支持故障域分隔,数据强一致性。 c. 多种故障场景自动进行修复自愈。 d. 没有单点故障,自动管理。 高可扩展性 a. 去中心化。 b. 扩展灵活。 c. 随着节点增加而线性增长。 特性丰富 a. 支持三种存储接口:块存储、文件存储、对象存储。 b. 支持自定义接口,支持多种语言驱动。 1.3 Ceph架构 支持三种接口: Object:有原生的API,而且也兼容Swift和S3的API。 Block:支持精简配置、快照、克隆。 File:Posix接口,支持快照。rados.png 1.4 Ceph核心组件及概念介绍 Monitor 一个Ceph集群需要多个Monitor组成的小集群,它们通过Paxos同步数据,用来保存OSD的元数据。 OSD OSD全称Object Storage Device,也就是负责响应客户端请求返回具体数据的进程。一个Ceph集群一般都有很多个OSD。 MDS MDS全称Ceph Metadata Server,是CephFS服务依赖的元数据服务。 Object Ceph最底层的存储单元是Object对象,每个Object包含元数据和原始数据。 PG PG全称Placement Grouops,是一个逻辑的概念,一个PG包含多个OSD。引入PG这一层其实是为了更好的分配数据和定位数据。 RADOS RADOS全称Reliable Autonomic Distributed Object Store,是Ceph集群的精华,用户实现数据分配、Failover等集群操作。 Libradio Librados是Rados提供库,因为RADOS是协议很难直接访问,因此上层的RBD、RGW和CephFS都是通过librados访问的,目前提供PHP、Ruby、Java、Python、C和C++支持。 CRUSH CRUSH是Ceph使用的数据分布算法,类似一致性哈希,让数据分配到预期的地方。 RBD RBD全称RADOS block device,是Ceph对外提供的块设备服务。 RGW RGW全称RADOS gateway,是Ceph对外提供的对象存储服务,接口与S3和Swift兼容。 CephFS CephFS全称Ceph File System,是Ceph对外提供的文件系统服务。 1.5 三种存储类型-块存储 rbd.png 典型设备: 磁盘阵列,硬盘 主要是将裸磁盘空间映射给主机使用的。 优点: 通过Raid与LVM等手段,对数据提供了保护。 多块廉价的硬盘组合起来,提高容量。 多块磁盘组合出来的逻辑盘,提升读写效率。 缺点: 采用SAN架构组网时,光纤交换机,造价成本高。 主机之间无法共享数据。 使用场景: docker容器、虚拟机磁盘存储分配。 日志存储。 文件存储。 … 1.6 三种存储类型-文件存储 fs.png 典型设备: FTP、NFS服务器 为了克服块存储文件无法共享的问题,所以有了文件存储。 在服务器上架设FTP与NFS服务,就是文件存储。 优点: 造价低,随便一台机器就可以了。 方便文件共享。 缺点: 读写速率低。 传输速率慢。 使用场景: 日志存储。 有目录结构的文件存储。 … 1.7 三种存储类型-对象存储 rgw.png 典型设备: 内置大容量硬盘的分布式服务器(swift, s3) 多台服务器内置大容量硬盘,安装上对象存储管理软件,对外提供读写访问功能。 优点: 具备块存储的读写高速。 具备文件存储的共享等特性。 使用场景: (适合更新变动较少的数据) 图片存储。 视频存储。 … 2. Ceph IO流程及数据分布 rados_io_1.png 2.1 正常IO流程图 ceph_io_2.png 步骤: client 创建cluster handler。 client 读取配置文件。 client 连接上monitor,获取集群map信息。 client 读写io 根据crshmap 算法请求对应的主osd数据节点。 主osd数据节点同时写入另外两个副本节点数据。 等待主节点以及另外两个副本节点写完数据状态。 主节点及副本节点写入状态都成功后,返回给client,io写入完成。 2.2 新主IO流程图 说明: 如果新加入的OSD1取代了原有的 OSD4成为 Primary OSD, 由于 OSD1 上未创建 PG , 不存在数据,那么 PG 上的 I/O 无法进行,怎样工作的呢? ceph_io_3.png 步骤: client连接monitor获取集群map信息。 同时新主osd1由于没有pg数据会主动上报monitor告知让osd2临时接替为主。 临时主osd2会把数据全量同步给新主osd1。 client IO读写直接连接临时主osd2进行读写。 osd2收到读写io,同时写入另外两副本节点。 等待osd2以及另外两副本写入成功。 osd2三份数据都写入成功返回给client, 此时client io读写完毕。 如果osd1数据同步完毕,临时主osd2会交出主角色。 osd1成为主节点,osd2变成副本。 2.3 Ceph IO算法流程 ceph_io_4.png File用户需要读写的文件。File->Object映射: a. ino (File的元数据,File的唯一id)。 b. ono(File切分产生的某个object的序号,默认以4M切分一个块大小)。 c. oid(object id: ino + ono)。 Object是RADOS需要的对象。Ceph指定一个静态hash函数计算oid的值,将oid映射成一个近似均匀分布的伪随机值,然后和mask按位相与,得到pgid。Object->PG映射: a. hash(oid) & mask-> pgid 。 b. mask = PG总数m(m为2的整数幂)-1 。 PG(Placement Group),用途是对object的存储进行组织和位置映射, (类似于redis cluster里面的slot的概念) 一个PG里面会有很多object。采用CRUSH算法,将pgid代入其中,然后得到一组OSD。PG->OSD映射: a. CRUSH(pgid)->(osd1,osd2,osd3) 。 2.4 Ceph IO伪代码流程 locator=object_name obj_hash=hash(locator) pg=obj_hash%num_pg osds_for_pg=crush(pg)#returnsalistofosds primary=osds_for_pg[0] replicas=osds_for_pg[1:] 2.5 Ceph RBD IO流程 ceph_rbd_io.png 步骤: 客户端创建一个pool,需要为这个pool指定pg的数量。 创建pool/image rbd设备进行挂载。 用户写入的数据进行切块,每个块的大小默认为4M,并且每个块都有一个名字,名字就是object+序号。 将每个object通过pg进行副本位置的分配。 pg根据cursh算法会寻找3个osd,把这个object分别保存在这三个osd上。 osd上实际是把底层的disk进行了格式化操作,一般部署工具会将它格式化为xfs文件系统。 object的存储就变成了存储一个文rbd0.object1.file。 2.6 Ceph RBD IO框架图 ceph_rbd_io1.png 客户端写数据osd过程: 采用的是librbd的形式,使用librbd创建一个块设备,向这个块设备中写入数据。 在客户端本地同过调用librados接口,然后经过pool,rbd,object、pg进行层层映射,在PG这一层中,可以知道数据保存在哪3个OSD上,这3个OSD分为主从的关系。 客户端与primay OSD建立SOCKET 通信,将要写入的数据传给primary OSD,由primary OSD再将数据发送给其他replica OSD数据节点。 2.7 Ceph Pool和PG分布情况 ceph_pool_pg.png 说明: pool是ceph存储数据时的逻辑分区,它起到namespace的作用。 每个pool包含一定数量(可配置)的PG。 PG里的对象被映射到不同的Object上。 pool是分布到整个集群的。 pool可以做故障隔离域,根据不同的用户场景不一进行隔离。 2.8 Ceph 数据扩容PG分布 场景数据迁移流程: 现状3个OSD, 4个PG 扩容到4个OSD, 4个PG 现状: ceph_recory_1.png 扩容后: ceph_io_recry2.png 说明 每个OSD上分布很多PG, 并且每个PG会自动散落在不同的OSD上。如果扩容那么相应的PG会进行迁移到新的OSD上,保证PG数量的均衡。 3. Ceph心跳机制 3.1 心跳介绍 心跳是用于节点间检测对方是否故障的,以便及时发现故障节点进入相应的故障处理流程。 问题: 故障检测时间和心跳报文带来的负载之间做权衡。 心跳频率太高则过多的心跳报文会影响系统性能。 心跳频率过低则会延长发现故障节点的时间,从而影响系统的可用性。 故障检测策略应该能够做到: 及时:节点发生异常如宕机或网络中断时,集群可以在可接受的时间范围内感知。 适当的压力:包括对节点的压力,和对网络的压力。 容忍网络抖动:网络偶尔延迟。 扩散机制:节点存活状态改变导致的元信息变化需要通过某种机制扩散到整个集群。 3.2 Ceph 心跳检测 ceph_heartbeat_1.png OSD节点会监听public、cluster、front和back四个端口 public端口:监听来自Monitor和Client的连接。 cluster端口:监听来自OSD Peer的连接。 front端口:供客户端连接集群使用的网卡, 这里临时给集群内部之间进行心跳。 back端口:供客集群内部使用的网卡。集群内部之间进行心跳。 hbclient:发送ping心跳的messenger。 3.3 Ceph OSD之间相互心跳检测 ceph_heartbeat_osd.png 步骤: 同一个PG内OSD互相心跳,他们互相发送PING/PONG信息。 每隔6s检测一次(实际会在这个基础上加一个随机时间来避免峰值)。 20s没有检测到心跳回复,加入failure队列。 3.4 Ceph OSD与Mon心跳检测 ceph_heartbeat_mon.png OSD报告给Monitor: OSD有事件发生时(比如故障、PG变更)。 自身启动5秒内。 OSD周期性的上报给Monito OSD检查failure_queue中的伙伴OSD失败信息。 向Monitor发送失效报告,并将失败信息加入failure_pending队列,然后将其从failure_queue移除。 收到来自failure_queue或者failure_pending中的OSD的心跳时,将其从两个队列中移除,并告知Monitor取消之前的失效报告。 当发生与Monitor网络重连时,会将failure_pending中的错误报告加回到failure_queue中,并再次发送给Monitor。 Monitor统计下线OSD Monitor收集来自OSD的伙伴失效报告。 当错误报告指向的OSD失效超过一定阈值,且有足够多的OSD报告其失效时,将该OSD下线。 3.5 Ceph心跳检测总结 Ceph通过伙伴OSD汇报失效节点和Monitor统计来自OSD的心跳两种方式判定OSD节点失效。 及时:伙伴OSD可以在秒级发现节点失效并汇报Monitor,并在几分钟内由Monitor将失效OSD下线。 适当的压力:由于有伙伴OSD汇报机制,Monitor与OSD之间的心跳统计更像是一种保险措施,因此OSD向Monitor发送心跳的间隔可以长达600秒,Monitor的检测阈值也可以长达900秒。Ceph实际上是将故障检测过程中中心节点的压力分散到所有的OSD上,以此提高中心节点Monitor的可靠性,进而提高整个集群的可扩展性。 容忍网络抖动:Monitor收到OSD对其伙伴OSD的汇报后,并没有马上将目标OSD下线,而是周期性的等待几个条件: 目标OSD的失效时间大于通过固定量osd_heartbeat_grace和历史网络条件动态确定的阈值。 来自不同主机的汇报达到mon_osd_min_down_reporters。 满足前两个条件前失效汇报没有被源OSD取消。 扩散:作为中心节点的Monitor并没有在更新OSDMap后尝试广播通知所有的OSD和Client,而是惰性的等待OSD和Client来获取。以此来减少Monitor压力并简化交互逻辑。 4. Ceph通信框架 4.1 Ceph通信框架种类介绍 网络通信框架三种不同的实现方式: Simple线程模式 特点:每一个网络链接,都会创建两个线程,一个用于接收,一个用于发送。 缺点:大量的链接会产生大量的线程,会消耗CPU资源,影响性能。 Async事件的I/O多路复用模式 特点:这种是目前网络通信中广泛采用的方式。k版默认已经使用Asnyc了。 XIO方式使用了开源的网络通信库accelio来实现 特点:这种方式需要依赖第三方的库accelio稳定性,目前处于试验阶段。 4.2 Ceph通信框架设计模式 设计模式(Subscribe/Publish): 订阅发布模式又名观察者模式,它意图是“定义对象间的一种一对多的依赖关系, 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新”。 4.3 Ceph通信框架流程图 ceph_message.png 步骤: Accepter监听peer的请求, 调用 SimpleMessenger::add_accept_pipe() 创建新的 Pipe 到 SimpleMessenger::pipes 来处理该请求。 Pipe用于消息的读取和发送。该类主要有两个组件,Pipe::Reader,Pipe::Writer用来处理消息读取和发送。 Messenger作为消息的发布者, 各个 Dispatcher 子类作为消息的订阅者, Messenger 收到消息之后, 通过 Pipe 读取消息,然后转给 Dispatcher 处理。 Dispatcher是订阅者的基类,具体的订阅后端继承该类,初始化的时候通过 Messenger::add_dispatcher_tail/head 注册到 Messenger::dispatchers. 收到消息后,通知该类处理。 DispatchQueue该类用来缓存收到的消息, 然后唤醒 DispatchQueue::dispatch_thread 线程找到后端的 Dispatch 处理消息。 ceph_message_2.png 4.4 Ceph通信框架类图 ceph_message_3.png 4.5 Ceph通信数据格式 通信协议格式需要双方约定数据格式。 消息的内容主要分为三部分: header //消息头,类型消息的信封 user data //需要发送的实际数据 payload //操作保存元数据 middle //预留字段 data //读写数据 footer //消息的结束标记 classMessage:publicRefCountedObject{ protected: ceph_msg_headerheader;//消息头 ceph_msg_footerfooter;//消息尾 bufferlistpayload;//"front"unalignedblob bufferlistmiddle;//"middle"unalignedblob bufferlistdata;//datapayload(page-alignmentwillbepreservedwherepossible) /*recv_stampissetwhentheMessengerstartsreadingthe *Messageoffthewire*/ utime_trecv_stamp;//开始接收数据的时间戳 /*dispatch_stampissetwhentheMessengerstartscallingdispatch()on *itsendpoints*/ utime_tdispatch_stamp;//dispatch的时间戳 /*throttle_stampisthepointatwhichwegotthrottle*/ utime_tthrottle_stamp;//获取throttle的slot的时间戳 /*timeatwhichmessagewasfullyread*/ utime_trecv_complete_stamp;//接收完成的时间戳 ConnectionRefconnection;//网络连接 uint32_tmagic=0;//消息的魔术字 bi::list_member_hook<>dispatch_q;//boost::intrusive成员字段 }; structceph_msg_header{ __le64seq;//当前session内消息的唯一序号 __le64tid;//消息的全局唯一的id __le16type;//消息类型 __le16priority;//优先级 __le16version;//版本号 __le32front_len;//payload的长度 __le32middle_len;//middle的长度 __le32data_len;//data的长度 __le16data_off;//对象的数据偏移量 structceph_entity_namesrc;//消息源 /*oldestcodewethinkcandecodethis.unknownifzero.*/ __le16compat_version; __le16reserved; __le32crc;/*headercrc32c*/ }__attribute__((packed)); structceph_msg_footer{ __le32front_crc,middle_crc,data_crc;//crc校验码 __le64sig;//消息的64位signature __u8flags;//结束标志 }__attribute__((packed)); 5. Ceph CRUSH算法 5.1 数据分布算法挑战 数据分布和负载均衡: a. 数据分布均衡,使数据能均匀的分布到各个节点上。 b. 负载均衡,使数据访问读写操作的负载在各个节点和磁盘的负载均衡。 灵活应对集群伸缩 a. 系统可以方便的增加或者删除节点设备,并且对节点失效进行处理。 b. 增加或者删除节点设备后,能自动实现数据的均衡,并且尽可能少的迁移数据。 支持大规模集群 a. 要求数据分布算法维护的元数据相对较小,并且计算量不能太大。随着集群规模的增 加,数据分布算法开销相对比较小。 5.2 Ceph CRUSH算法说明 CRUSH算法的全称为:Controlled Scalable Decentralized Placement of Replicated Data,可控的、可扩展的、分布式的副本数据放置算法。 pg到OSD的映射的过程算法叫做CRUSH 算法。(一个Object需要保存三个副本,也就是需要保存在三个osd上)。 CRUSH算法是一个伪随机的过程,他可以从所有的OSD中,随机性选择一个OSD集合,但是同一个PG每次随机选择的结果是不变的,也就是映射的OSD集合是固定的。 5.3 Ceph CRUSH算法原理 CRUSH算法因子: 层次化的Cluster Map 反映了存储系统层级的物理拓扑结构。定义了OSD集群具有层级关系的 静态拓扑结构。OSD层级使得 CRUSH算法在选择OSD时实现了机架感知能力,也就是通过规则定义, 使得副本可以分布在不同的机 架、不同的机房中、提供数据的安全性 。 Placement Rules 决定了一个PG的对象副本如何选择的规则,通过这些可以自己设定规则,用户可以自定义设置副本在集群中的分布。 5.3.1 层级化的Cluster Map ceph_crush.png CRUSH Map是一个树形结构,OSDMap更多记录的是OSDMap的属性(epoch/fsid/pool信息以及osd的ip等等)。 叶子节点是device(也就是osd),其他的节点称为bucket节点,这些bucket都是虚构的节点,可以根据物理结构进行抽象,当然树形结构只有一个最终的根节点称之为root节点,中间虚拟的bucket节点可以是数据中心抽象、机房抽象、机架抽象、主机抽象等。 5.3.2 数据分布策略Placement Rules 数据分布策略Placement Rules主要有特点: a. 从CRUSH Map中的哪个节点开始查找 b. 使用那个节点作为故障隔离域 c. 定位副本的搜索模式(广度优先 or 深度优先) rulereplicated_ruleset#规则集的命名,创建pool时可以指定rule集 { ruleset0#rules集的编号,顺序编即可 typereplicated#定义pool类型为replicated(还有erasure模式) min_size1#pool中最小指定的副本数量不能小1 max_size10#pool中最大指定的副本数量不能大于10 steptakedefault#查找bucket入口点,一般是root类型的bucket stepchooseleaffirstn0typehost#选择一个host,并递归选择叶子节点osd stepemit#结束 } 5.3.3 Bucket随机算法类型 ceph_bucket.png 一般的buckets:适合所有子节点权重相同,而且很少添加删除item。 list buckets:适用于集群扩展类型。增加item,产生最优的数据移动,查找item,时间复杂度O(n)。 tree buckets:查找负责度是O (log n), 添加删除叶子节点时,其他节点node_id不变。 straw buckets:允许所有项通过类似抽签的方式来与其他项公平“竞争”。定位副本时,bucket中的每一项都对应一个随机长度的straw,且拥有最长长度的straw会获得胜利(被选中),添加或者重新计算,子树之间的数据移动提供最优的解决方案。 5.4 CRUSH算法案例 说明: 集群中有部分sas和ssd磁盘,现在有个业务线性能及可用性优先级高于其他业务线,能否让这个高优业务线的数据都存放在ssd磁盘上。 普通用户: ceph_sas.png 高优用户: ssd.png 配置规则: ceph_crush1.png 6. 定制化Ceph RBD QOS 6.1 QOS介绍 QoS (Quality of Service,服务质量)起源于网络技术,它用来解决网络延迟和阻塞等问题,能够为指定的网络通信提供更好的服务能力。 问题: 我们总的Ceph集群的iIO能力是有限的,比如带宽,IOPS。如何避免用户争取资源,如果保证集群所有用户资源的高可用性,以及如何保证高优用户资源的可用性。所以我们需要把有限的IO能力合理分配。 6.2 Ceph IO操作类型 ClientOp:来自客户端的读写I/O请求。 SubOp:osd之间的I/O请求。主要包括由客户端I/O产生的副本间数据读写请求,以及由数据同步、数据扫描、负载均衡等引起的I/O请求。 SnapTrim:快照数据删除。从客户端发送快照删除命令后,删除相关元数据便直接返回,之后由后台线程删除真实的快照数据。通过控制snaptrim的速率间接控制删除速率。 Scrub:用于发现对象的静默数据错误,扫描元数据的Scrub和对象整体扫描的deep Scrub。 Recovery:数据恢复和迁移。集群扩/缩容、osd失效/从新加入等过程。 6.3 Ceph 官方QOS原理 ceph_mclok_qos.png mClock是一种基于时间标签的I/O调度算法,最先被Vmware提出来的用于集中式管理的存储系统。(目前官方QOS模块属于半成品)。 基本思想: reservation 预留,表示客户端获得的最低I/O资源。 weight 权重,表示客户端所占共享I/O资源的比重。 limit 上限,表示客户端可获得的最高I/O资源。 6.4 定制化QOS原理 6.4.1 令牌桶算法介绍 ceph_token_qos.png 基于令牌桶算法(TokenBucket)实现了一套简单有效的qos功能,满足了云平台用户的核心需求。 基本思想: 按特定的速率向令牌桶投放令牌。 根据预设的匹配规则先对报文进行分类,不符合匹配规则的报文不需要经过令牌桶的处理,直接发送。 符合匹配规则的报文,则需要令牌桶进行处理。当桶中有足够的令牌则报文可以被继续发送下去,同时令牌桶中的令牌量按报文的长度做相应的减少。 当令牌桶中的令牌不足时,报文将不能被发送,只有等到桶中生成了新的令牌,报文才可以发送。这就可以限制报文的流量只能是小于等于令牌生成的速度,达到限制流量的目的。 6.4.2 RBD令牌桶算法流程 ceph_token1.png 步骤: 用户发起请求异步IO到达Image中。 请求到达ImageRequestWQ队列中。 在ImageRequestWQ出队列的时候加入令牌桶算法TokenBucket。 通过令牌桶算法进行限速,然后发送给ImageRequest进行处理。 6.4.3 RBD令牌桶算法框架图 现有框架图: ceph_qos2.png 令牌图算法框架图: ceph_qos_token2.png 作者信息 作者:李航个人简介: 多年的底层开发经验,在高性能nginx开发和分布式缓存redis cluster有着丰富的经验,目前从事Ceph工作两年左右。 先后在58同城、汽车之家、优酷土豆集团工作。 目前供职于滴滴基础平台运维部-技术专家岗位 主要负责分布式Ceph系统。 个人主要关注的技术领域:高性能Nginx开发、分布式缓存、分布式存储。

优秀的个人博客,低调大师

变饼档博客更新 1.5.1 版本,化繁为简

此版本为2.0期间的过度版本,化繁为简是迈向2.0的宗旨 1. 删除原有侧边栏插件开发的钩子,改为代码内部扩展,插件全部管理在目录`article_manager/templates/side ` 2. 删除大量rest-api,改为传统模板渲染,目的是增强搜索引擎的可识别性 3. 后台增加网站标题、关键字、描述、百度熊掌的配置 4. 默认模板采用更加现代的UI风格,自定义主题可修改文件夹`article_manager/templates`下html样式 5. 更新layedit富文本为最新版,后期2.0将被代替,由于专业性存在争议,后期替换。 6. 后台去除文章的编辑,书写。 前台预览: 后台预览:

优秀的个人博客,低调大师

使用阿里云ECS搭建WordPress博客教程

宝塔官网:https://www.bt.cn/?invite_code=MV90a3BjeWM=安装要求:内存:512M以上,推荐768M以上(纯面板约占系统60M内存)硬盘:100M以上可用硬盘空间(纯面板约占20M磁盘空间)系统:CentOS 7.1+ (Ubuntu16.04+.、Debian9.0+),确保是干净的操作系统,没有安装过其它环境带的Apache/Nginx/php/MySQL(已有环境不可安装) 宝塔linux6.0版本是基于centos7开发的,务必使用centos7.x 系统提示:Centos官方已宣布在2020年停止对Centos6的维护更新,各大软件开发商也逐渐停止对Centos6的兼容,新服务器不建议使用Centos6Linux面板6.9.7安装命令:推荐使用阿里云云服务器安装)使用SSH 连接工具,挂载磁盘后,根据系统执行框内命令开始安装(大约2分钟完成面板安装)Centos安装命令:yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh Ubuntu/Deepin安装命令:wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh Debian安装命令:wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && bash install.sh Fedora安装命令:wget -O install.sh http://download.bt.cn/install/install_6.0.sh && bash install.sh Linux面板6.9.7升级命令: curl http://download.bt.cn/install/update6.sh|bash 以下为部分功能预览图:面板设置SSL

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册