-
背景说明:在项目和培训中多次被问题FusionSphere物理CPU和vCPU的对应或分配关系,一个物理CPU能虚拟出多少个vCPU,一个vCPU的主频是多少等问题。设置了CPU预留、份额与限制之后又是什么情况。看过之前的一些讨论,也没有定论,本着实践是检验整理的唯一标准,本文通过实验,并对照相关文档来梳理这些问题,希望能让大家有更清楚的理解。1. 系统可用的VCPU总数计算服务器CPU信息:1台R2288H V3,2个CPU, 10 核,超线程为2。总共2x10x2= 40个thread,每个Thread 2.3GHz。Haswell EP CPU 02311CDJ BC1M12CPU X86 series,2300**z,1.8V,64bit,105000mW,Haswell EP Xeon E5-2650 v3,10Core,with heatsink 2 2服务器BMC管理界面上查看 CPU信息 Intel官网看到E5-2650的信息http://ark.intel.com/products/81705/Intel-Xeon-Processor-E5-2650-v3-25M-Cache-2_30-GHz在主机上部署FusionCompute R5C00, 登录CNA主机运行xentop命令查看CPU信息CPUs:40 @ 2294 **z,主频总容量为40 x 2.294 GHz = 91.76 GHz。Domain 0默认配置2个VCPU,占用2 x 2.294 = 4.588 GHz用户可用的主频总容量 = 91.76 - 4.588 = 87.172 GHzFC portal上查看CPU信息,总容量87.17GHz正好等于系统总容量减去Domain 0占用的容量。结论1: 系统可用的vCPU总数(逻辑处理器) = Socket数(CPU个数)x Core数(内核)x Thread数(超线程)1个VCPU = 1个超线程Thread。如下图: CPU QoS如图所示,CPU预留容量为4.59GHz,可用容量为82.58GHz,说明除了VRM01的2个VCPU预留容量4588**z之外的VCPU主频均是可用的,尽管该环境已创建了7台4 VCPU的VM,还可以创建更多VM,这些VM的VCPU总数可以远远超过当前系统显示可用的38个VCPU。在不对VRM01的VCPU进行限制的情况下,将VCPU份额自定义为128000,显示可使用的CPU数为38,说明如果需要的话VRM01可以占用该主机上的除了Domain 0之外的所有VCPU(Domain 0占用了2个VCPU)。2. 虚拟机VCPU的分配与调度对虚拟机来说,不直接感知物理CPU,虚拟机的计算单元通过vCPU对象来呈现。虚拟机只看到VMM呈现给它的vCPU。在VMM中,每个vCPU对应一个VMCS(Virtual-Machine Control Structure)结构,当VCPU被从物理CPU上切换下来的时候,其运行上下文会被保存在其对应的VMCS结构中;当VCPU被切换到PCPU上运行时,其运行上下文会从对应的VMCS结构中导入到物理CPU上。通过这种方式,实现各vCPU之间的独立运行。从虚拟机系统的结构与功能划分可以看出,客户操作系统与虚拟机监视器共同构成了虚拟机系统的两级调度框架,如图所示是一个多核环境下虚拟机系统的两级调度框架。客户操作系统负责第2 级调度,即线程或进程在vCPU 上的调度(将核心线程映射到相应的VCPU上)。虚拟机监视器负责第1 级调度, 即vCPU在物理处理单元上的调度。两级调度的调度策略和机制不存在依赖关系。vCPU调度器负责物理处理器资源在各个虚拟机之间的分配与调度,本质上即把各个虚拟机中的vCPU按照一定的策略和机制调度在物理处理单元上可以采用任意的策略来分配物理资源, 满足虚拟机的不同需求。vCPU可以调度在一个或多个物理处理单元执行(分时复用或空间复用物理处理单元), 也可以与物理处理单元建立一对一固定的映射关系(限制访问指定的物理处理单元)。3. CPU QoS说明Hypervisor层根据分时复用的原理实现对VCPU的调度,CPU QoS的原理是定期给各VCPU分配运行时间片,并对各VCPU运行的时间进行记账,对于消耗完时间片的虚拟CPU将被限制运行,直到获得时间片。以此控制虚拟机获得物理计算资源的比例。以上分配时间片和记账的时间周期很短,对虚拟机用户来说会感觉一直在运行。CPU预留定义了分配给该VM的最少CPU资源。CPU限制定义了分配虚拟机占用CPU资源的上限。CPU份额定义多个虚拟机在竞争CPU资源的时候按比例分配。CPU份额只在各虚拟机竞争计算资源时发挥作用,如果没有竞争,有需求的虚拟机可以独占主机的物理CPU资源。如果虚拟机根据份额值计算出来的计算能力小于虚拟机预留值,调度算法会优先按照虚拟机预留值分配给虚拟机,对于预留值超出按份额分配的计算资源的部分,调度算法会从主机上其他虚拟机的CPU上按各自的份额比例扣除。如果虚拟机根据份额值计算出来的计算能力大于虚拟机预留值,那么虚拟机的计算能力会以份额值计算为准。以一台主频为2800**z的单核物理机为例,如果满负载运行3台单VCPU的虚拟机A、B、C,分配情况如下。结论2:由于采用分时复用的方式,在不做VCPU预留的条件下,系统可分配给VM的VCPU总数远远大于实际可提供的VCPU数目(具体能创建多少额外的VCPU依赖于物理CPU的性能和VCPU的使用情率),在出现资源争用的时根据CPU QoS中的预留和份额来分配资源。
-
[中国,上海,2016年8月31日] 华为历史上规模最大的面向ICT行业的全球生态大会——HUAWEI CONNECT 2016全联接大会31日在上海开幕,为期三天。来自120多个国家和地区的20000名业界精英,围绕“塑造云时代”主题,共同探讨云时代趋势与洞察,以及各行各业如何通过打造云技术、构筑云生态,积极实现数字化转型。华为轮值CEO胡厚崑在31日开幕的华为全联接大会上发表题为《站在云端看世界》演讲在此次大会上,华为首次全面阐述了华为云战略定位。华为轮值CEO胡厚崑在大会首场大会演讲中表示,华为的定位,就是成为智能社会的使能者与推动者。华为将坚持以客户为中心,聚焦ICT基础设施,做创新的云技术提供者;做企业云化、数字化战略的使能者和优选合作伙伴;秉承开放、合作、共赢的原则,做云生态的积极贡献者。面向智能社会,信息通信技术是最重要的基石。终端是万物感知的触角,网络连接万物,云是万物智能的源泉。与此对应的是全面协同的“端、管、云”架构,这也是华为重点投入的战略方向。胡厚崑表示:“5到10年内,将出现各式各样的多场景、自适应的智能终端,人和物都能感知环境,成为智能世界的入口;光缆和无线网络可以提供无处不在的超宽带连接;分布全球而又相互连接的计算机汇聚了海量信息,在云端生成了‘数字大脑’,实时进化,永不衰老,人和机器可以通过连接和终端随时调用其智慧。”胡厚崑表示,企业是云化的主角,华为就是坚持以客户为中心,深入理解客户需求,用创新的技术去匹配。华为通过统一开放的云架构为客户提供可交付、可运营的混合云解决方案,特别强调了开放性、安全性、企业级的性能以及一站式的特点,恰恰来自于对客户需求深入而准确的把握。另外,华为主张,云生态的构建要基于为客户创造价值的目的,每个参与生态建设者,都应该有自己独特的价值。他也强调,华为不会独自做几朵云,华为要做的是帮助千千万万客户建好千万朵云,积极参与云生态的建设。“生于云的一代”企业主导了云的1.0时代,颠覆了很多行业。胡厚崑认为,下个十年,将是云的2.0时代,行业云将兴起。到2025年,所有企业信息技术解决方案都会被云化,85%以上企业应用会被部署到云上。每个企业需要结合自身核心业务,探索最适合自己的云化解决方案。他结合华为经验,提出“化云为雨,让云为业务创造价值”的理念和三个实现途径。首先要重塑观念,重新认识ICT的作用,企业要把信息技术从辅助性技术上升为生产技术,大胆利用技术重新设计生产流程;其次,重构人才,掌握以云为基础的信息技术应成为基本技能;最后是小步快跑,用循序渐进的成功建立持久的信心。“云正在塑造一切,有变革才有重生。对于任何企业来说,云的2.0时代,有变化,才有希望,有行动,才有未来”,他说。大会期间,华为轮值CEO胡厚崑、徐直军、郭平,英特尔公司首席执行官Brian Krzanich,Infosys首席执行官Vishal Sikka, SAP公司高级副总裁Thomas Saueressig,麻省理工大学斯隆商学院首席研究科学家Andrew McAfee等发表主题演讲。华为全联接大会是一场生态大会,将在上海世博中心搭建18000平方米互动展厅,其中一半用于产业伙伴能力展示,约有80家华为及产业生态系统中的赞助商以及产业组织参展,包括Intel、HGST、SAP、Accenture、Infosys、OpenStack、GSMA等。华为将基于云计算、SDN、大数据等四大关键技术,发布云存储、云服务、SDN统一控制器等八大重量级解决方案,使能政府及公共事业、金融、电信、能源、媒资等九大行业客户的数字化转型之路。欲了解HUAWEI CONNECT 2016更多详情,请参阅大会官网: http://www.huawei.com/minisite/huaweiconnect2016。
-
本文转载自:https://zhuanlan.zhihu.com/p/37487213前言Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。我们使用Redis时,会接触Redis的5种对象类型(字符串、哈希、列表、集合、有序集合),丰富的类型是Redis相对于Memcached等的一大优势。在了解Redis的5种对象类型的用法和特点的基础上,进一步了解Redis的内存模型,对Redis的使用有很大帮助,例如:1、估算Redis内存使用量。目前为止,内存的使用成本仍然相对较高,使用内存不能无所顾忌;根据需求合理的评估Redis的内存使用量,选择合适的机器配置,可以在满足需求的情况下节约成本。2、优化内存占用。了解Redis内存模型可以选择更合适的数据类型和编码,更好的利用Redis内存。3、分析解决问题。当Redis出现阻塞、内存占用等问题时,尽快发现导致问题的原因,便于分析解决问题。这篇文章主要介绍Redis的内存模型(以3.0为例),包括Redis占用内存的情况及如何查询、不同的对象类型在内存中的编码方式、内存分配器(jemalloc)、简单动态字符串(SDS)、RedisObject等;然后在此基础上介绍几个Redis内存模型的应用。在后面的文章中,会陆续介绍关于Redis高可用的内容,包括主从复制、哨兵、集群等等,欢迎关注。目录一、Redis内存统计二、Redis内存划分1、数据2、进程本身运行需要的内存3、缓冲内存4、内存碎片三、Redis数据存储的细节1、概述2、jemalloc3、redisObject4、SDS四、Redis的对象类型与内部编码1、字符串2、列表3、哈希4、集合5、有序集合五、应用举例1、估算Redis内存使用量2、优化内存占用3、关注内存碎片率一、Redis内存统计工欲善其事必先利其器,在说明Redis内存之前首先说明如何统计Redis使用内存的情况。在客户端通过redis-cli连接服务器后(后面如无特殊说明,客户端一律使用redis-cli),通过info命令可以查看内存使用情况:1info memory其中,info命令可以显示redis服务器的许多信息,包括服务器基本信息、CPU、内存、持久化、客户端连接信息等等;memory是参数,表示只显示内存相关的信息。返回结果中比较重要的几个说明如下:(1)used_memory:Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap);Redis分配器后面会介绍。used_memory_human只是显示更友好。(2)used_memory_rss:Redis进程占据操作系统的内存(单位是字节),与top及ps命令看到的值是一致的;除了分配器分配的内存之外,used_memory_rss还包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存。因此,used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小,另一方面虚拟内存的存在,使得前者可能比后者大。由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis内存碎片率的参数;这个参数就是mem_fragmentation_ratio。(3)mem_fragmentation_ratio:内存碎片比率,该值是used_memory_rss / used_memory的比值。mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);上面截图中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。(4)mem_allocator:Redis使用的内存分配器,在编译时指定;可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc;截图中使用的便是默认的jemalloc。二、Redis内存划分Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以知道,除了数据以外,Redis的其他部分也会占用内存。Redis的内存占用主要可以划分为以下几个部分:1、数据作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;这篇文章后面将重点介绍Redis中数据存储的细节。2、进程本身运行需要的内存Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。补充说明:除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。3、缓冲内存缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。4、内存碎片内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的jemalloc便在控制内存碎片方面做的很好。如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。三、Redis数据存储的细节1、概述关于Redis数据存储的细节,涉及到内存分配器(如jemalloc)、简单动态字符串(SDS)、5种对象类型及内部编码、redisObject。在讲述具体内容之前,先说明一下这几个概念之间的关系。下图是执行set hello world时,所涉及到的数据模型。图片来源:https://searchdatabase.techtarget.com.cn/7-20218/(1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。(2)Key:图中右上角可见,Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。(3)redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。实际上,redisObject除了type和ptr字段以外,还有其他字段图中没有给出,如用于指定对象内部编码的字段;后面会详细介绍。(4)jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。下面来分别介绍jemalloc、redisObject、SDS、对象类型及内部编码。2、jemallocRedis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。jemalloc划分的内存单元如下图所示:图片来源:http://blog.csdn.net/zhengpeitao/article/details/76573053例如,如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。3、redisObject前面说到,Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过redisObject对象进行存储。redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject支持,下面将通过redisObject的结构来说明它是如何起作用的。redisObject的定义如下(不同版本的Redis可能稍稍有所不同):1234567typedef struct redisObject { unsigned type:4; unsigned encoding:4;unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr;} robj;redisObject的每个字段的含义和作用如下:(1)typetype字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。当我们执行type命令时,便是通过读取RedisObject的type字段获得对象的类型;如下图所示:(2)encodingencoding表示对象的内部编码,占4个比特。对于Redis支持的每种类型,都有至少两种内部编码,例如对于字符串,有int、embstr、raw三种编码。通过encoding属性,Redis可以根据不同的使用场景来为对象设置不同的编码,大大提高了Redis的灵活性和效率。以列表对象为例,有压缩列表和双端链表两种编码方式;如果列表中的元素较少,Redis倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入;当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。通过object encoding命令,可以查看对象采用的编码方式,如下图所示:5种对象类型对应的编码方式以及使用条件,将在后面介绍。(3)lrulru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。通过对比lru时间与当前时间,可以计算某个对象的空转时间;object idletime命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。lru值除了通过object idletime命令打印之外,还与Redis的内存回收有关系:如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占用超过maxmemory指定的值时,Redis会优先选择空转时间最长的对象进行释放。(4)refcountrefcount与共享对象refcount记录的是该对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount初始化为1;当有新程序使用该对象时,refcount加1;当对象不再被一个新程序使用时,refcount减1;当refcount变为0时,对象占用的内存会被释放。Redis中被多次使用的对象(refcount>1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。共享对象的具体实现Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。共享对象的引用次数可以通过object refcount命令查看,如下图所示。命令执行的结果页佐证了只有0~9999之间的整数会作为共享对象。(5)ptrptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。(6)总结综上所述,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;一个redisObject对象的大小为16字节:4bit+4bit+24bit+4Byte+8Byte=16Byte。4、SDSRedis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。(1)SDS结构sds的结构如下:12345struct sdshdr {int len;int free;char buf[];};其中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。下面是两个例子。图片来源:《Redis设计与实现》通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。(2)SDS与C字符串的比较SDS在C字符串的基础上加入了free和len字段,带来了很多好处:获取字符串长度:SDS是O(1),C字符串是O(n)缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化:空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。此外,由于SDS中的buf仍然使用了C字符串(即以’\0’结尾),因此SDS可以使用C字符串库中的部分函数;但是需要注意的是,只有当SDS用来存储文本数据时才可以这样使用,在存储二进制数据时则不行(’\0’不一定是结尾)。(3)SDS与C字符串的应用Redis在存储对象时,一律使用SDS代替C字符串。例如set hello world命令,hello和world都是以SDS的形式存储的。而sadd myset member1 member2 member3命令,不论是键(”myset”),还是集合中的元素(”member1”、 ”member2”和”member3”),都是以SDS的形式存储。除了存储对象,SDS还用于存储各种缓冲区。只有在字符串不会改变的情况下,如打印日志时,才会使用C字符串。四、Redis的对象类型与内部编码前面已经说过,Redis支持5种对象类型,而每种结构都有至少两种编码;这样做的好处在于:一方面接口与实现分离,当需要增加或改变内部编码时,用户使用不受影响,另一方面可以根据不同的应用场景切换内部编码,提高效率。Redis各种对象类型支持的内部编码如下图所示(图中版本是Redis3.0,Redis后面版本中又增加了内部编码,略过不提;本章所介绍的内部编码都是基于3.0的):图片来源:《Redis设计与实现》关于Redis内部编码的转换,都符合以下规律:编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。1、字符串(1)概况字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串。字符串长度不能超过512MB。(2)内部编码字符串类型的内部编码有3种,它们的应用场景如下:int:8个字节的长整型。字符串值是整型时,这个值使用long整型表示。embstr:<=39字节的字符串。embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。raw:大于39个字节的字符串示例如下图所示:embstr和raw进行区分的长度,是39;是因为redisObject的长度是16字节,sds的长度是9+字符串长度;因此当字符串长度是39时,embstr的长度正好是16+9+39=64,jemalloc正好可以分配64字节的内存单元。(3)编码转换当int数据不再是整数,或大小超过了long的范围时,自动转化为raw。而对于embstr,由于其实现是只读的,因此在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了39个字节。示例如下图所示:2、列表(1)概况列表(list)用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储2^32-1个元素。Redis中的列表支持两端**和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。(2)内部编码列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist)。双端链表:由一个list结构和多个l**ode结构组成;典型结构如下图所示:图片来源:《Redis设计与实现》通过图中可以看出,双端链表同时保存了表头指针和表尾指针,并且每个节点都有指向前和指向后的指针;链表中保存了列表的长度;dup、free和match为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。而链表中每个节点指向的是type为字符串的redisObject。压缩列表:压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块(而不是像双端链表一样每个节点是指针)组成的顺序型数据结构;具体结构相对比较复杂,略。与双端链表相比,压缩列表可以节省内存空间,但是进行修改或增删操作时,复杂度较高;因此当节点数量较少时,可以使用压缩列表;但是节点数量多时,还是使用双端链表划算。压缩列表不仅用于实现列表,也用于实现哈希、有序列表;使用非常广泛。(3)编码转换只有同时满足下面两个条件时,才会使用压缩列表:列表中元素数量小于512个;列表中所有字符串对象都不足64字节。如果有一个条件不满足,则使用双端列表;且编码只可能由压缩列表转化为双端链表,反方向则不可能。下图展示了列表编码转换的特点:其中,单个字符串不能超过64字节,是为了便于统一分配每个节点的长度;这里的64字节是指字符串的长度,不包括SDS结构,因为压缩列表使用连续、定长内存块存储字符串,不需要SDS结构指明长度。后面提到压缩列表,也会强调长度不超过64字节,原理与这里类似。3、哈希(1)概况哈希(作为一种数据结构),不仅是redis对外提供的5种对象类型的一种(与字符串、列表、集合、有序结合并列),也是Redis作为Key-Value数据库所使用的数据结构。为了说明的方便,在本文后面当使用“内层的哈希”时,代表的是redis对外提供的5种对象类型的一种;使用“外层的哈希”代指Redis作为Key-Value数据库所使用的数据结构。(2)内部编码内层的哈希使用的内部编码可以是压缩列表(ziplist)和哈希表(hashtable)两种;Redis的外层的哈希则只使用了hashtable。压缩列表前面已介绍。与哈希表相比,压缩列表用于元素个数少、元素长度小的场景;其优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(n)变为了O(1),但由于哈希中元素数量较少,因此操作的时间并没有明显劣势。hashtable:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。正常情况下(即hashtable没有进行rehash时)各部分关系如下图所示:图片改编自:《Redis设计与实现》下面从底层向上依次介绍各个部分:dictEntrydictEntry结构用于保存键值对,结构定义如下:123456789typedef struct dictEntry{void *key;union{void *val;uint64_tu64;int64_ts64;}v;struct dictEntry *next;}dictEntry;其中,各个属性的功能如下:key:键值对中的键;val:键值对中的值,使用union(即共用体)实现,存储的内容既可能是一个指向值的指针,也可能是64位整型,或无符号64位整型;next:指向下一个dictEntry,用于解决哈希冲突问题在64位系统中,一个dictEntry对象占24字节(key/val/next各占8字节)。bucketbucket是一个数组,数组的每个元素都是指向dictEntry结构的指针。redis中bucket数组的大小计算规则如下:大于dictEntry的、最小的2^n;例如,如果有1000个dictEntry,那么bucket大小为1024;如果有1500个dictEntry,则bucket大小为2048。dicthtdictht结构如下:123456typedef struct dictht{dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;}dictht;其中,各个属性的功能说明如下:table属性是一个指针,指向bucket;size属性记录了哈希表的大小,即bucket的大小;used记录了已使用的dictEntry的数量;sizemask属性的值总是为size-1,这个属性和哈希值一起决定一个键在table中存储的位置。dict一般来说,通过使用dictht和dictEntry结构,便可以实现普通哈希表的功能;但是Redis的实现中,在dictht结构的上层,还有一个dict结构。下面说明dict结构的定义及作用。dict结构如下:123456typedef struct dict{dictType *type;void *privdata;dictht ht[2];int trehashidx;} dict;其中,type属性和privdata属性是为了适应不同类型的键值对,用于创建多态字典。ht属性和trehashidx属性则用于rehash,即当哈希表需要扩展或收缩时使用。ht是一个包含两个项的数组,每项都指向一个dictht结构,这也是Redis的哈希会有1个dict、2个dictht结构的原因。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]。因此,Redis中的哈希之所以在dictht和dictEntry结构之外还有一个dict结构,一方面是为了适应不同类型的键值对,另一方面是为了rehash。(3)编码转换如前所述,Redis中内层的哈希既可能使用哈希表,也可能使用压缩列表。只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于512个;哈希中所有键值对的键和值字符串长度都小于64字节。如果有一个条件不满足,则使用哈希表;且编码只可能由压缩列表转化为哈希表,反方向则不可能。下图展示了Redis内层的哈希编码转换的特点:4、集合(1)概况集合(set)与列表类似,都是用来保存多个字符串,但集合与列表有两点不同:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。一个集合中最多可以存储2^32-1个元素;除了支持常规的增删改查,Redis还支持多个集合取交集、并集、差集。(2)内部编码集合的内部编码可以是整数集合(intset)或哈希表(hashtable)。哈希表前面已经讲过,这里略过不提;需要注意的是,集合在使用哈希表时,值全部被置为null。整数集合的结构定义如下:12345typedef struct intset{uint32_t encoding;uint32_t length;int8_t contents[];} intset;其中,encoding代表contents中存储内容的类型,虽然contents(存储集合中的元素)是int8_t类型,但实际上其存储的值是int16_t、int32_t或int64_t,具体的类型便是由encoding决定的;length表示元素个数。整数集合适用于集合所有元素都是整数且集合元素数量较小的时候,与哈希表相比,整数集合的优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(n)变为了O(1),但由于集合数量较少,因此操作的时间并没有明显劣势。(3)编码转换只有同时满足下面两个条件时,集合才会使用整数集合:集合中元素数量小于512个;集合中所有元素都是整数值。如果有一个条件不满足,则使用哈希表;且编码只可能由整数集合转化为哈希表,反方向则不可能。下图展示了集合编码转换的特点:5、有序集合(1)概况有序集合与集合一样,元素都不能重复;但与集合不同的是,有序集合中的元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。(2)内部编码有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)。ziplist在列表和哈希中都有使用,前面已经讲过,这里略过不提。跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此redis中选用跳跃表代替平衡树。跳跃表支持平均O(logN)、最坏O(N)的复杂点进行节点查找,并支持顺序操作。Redis的跳跃表实现由zskiplist和zskipl**ode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。具体结构相对比较复杂,略。(3)编码转换只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。下图展示了有序集合编码转换的特点:五、应用举例了解Redis的内存模型之后,下面通过几个例子说明其应用。1、估算Redis内存使用量要估算redis中的数据占据的内存大小,需要对redis的内存模型有比较全面的了解,包括前面介绍的hashtable、sds、redisobject、各种对象类型的编码方式等。下面以最简单的字符串类型来进行说明。假设有90000个键值对,每个key的长度是7个字节,每个value的长度也是7个字节(且key和value都不是整数);下面来估算这90000个键值对所占用的空间。在估算占据空间之前,首先可以判定字符串类型使用的编码方式:embstr。90000个键值对占据的内存空间主要可以分为两部分:一部分是90000个dictEntry占据的空间;一部分是键值对所需要的bucket空间。每个dictEntry占据的空间包括:1) 一个dictEntry,24字节,jemalloc会分配32字节的内存块2) 一个key,7字节,所以SDS(key)需要7+9=16个字节,jemalloc会分配16字节的内存块3) 一个redisObject,16字节,jemalloc会分配16字节的内存块4) 一个value,7字节,所以SDS(value)需要7+9=16个字节,jemalloc会分配16字节的内存块5) 综上,一个dictEntry需要32+16+16+16=80个字节。bucket空间:bucket数组的大小为大于90000的最小的2^n,是131072;每个bucket元素为8字节(因为64位系统中指针大小为8字节)。因此,可以估算出这90000个键值对占据的内存大小为:90000*80 + 131072*8 = 8248576。下面写个程序在redis中验证一下:123456789101112131415161718192021222324public class RedisTest { public static Jedis jedis = new Jedis("localhost", 6379); public static void main(String[] args) throws Exception{ Long m1 = Long.valueOf(getMemory()); insertData(); Long m2 = Long.valueOf(getMemory()); System.out.println(m2 - m1); } public static void insertData(){ for(int i = 10000; i < 100000; i++){ jedis.set("aa" + i, "aa" + i); //key和value长度都是7字节,且不是整数 } } public static String getMemory(){ String memoryAllLine = http://jedis.info("memory"); String usedMemoryLine = memoryAllLine.split("\r\n")[1]; String memory = usedMemoryLine.substring(usedMemoryLine.indexOf(':') + 1); return memory; }}运行结果:8247552理论值与结果值误差在万分之1.2,对于计算需要多少内存来说,这个精度已经足够了。之所以会存在误差,是因为在我们**90000条数据之前redis已分配了一定的bucket空间,而这些bucket空间尚未使用。作为对比将key和value的长度由7字节增加到8字节,则对应的SDS变为17个字节,jemalloc会分配32个字节,因此每个dictEntry占用的字节数也由80字节变为112字节。此时估算这90000个键值对占据内存大小为:90000*112 + 131072*8 = 11128576。在redis中验证代码如下(只修改**数据的代码):12345public static void insertData(){ for(int i = 10000; i < 100000; i++){ jedis.set("aaa" + i, "aaa" + i); //key和value长度都是8字节,且不是整数 }}运行结果:11128576;估算准确。对于字符串类型之外的其他类型,对内存占用的估算方法是类似的,需要结合具体类型的编码方式来确定。2、优化内存占用了解redis的内存模型,对优化redis内存占用有很大帮助。下面介绍几种优化场景。(1)利用jemalloc特性进行优化上一小节所讲述的90000个键值便是一个例子。由于jemalloc分配内存时数值是不连续的,因此key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。例如,如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。(2)使用整型/长整型如果是整型/长整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。(3)共享对象利用共享对象,可以减少对象的创建(同时减少了redisObject的创建),节省内存空间。目前redis中的共享对象只包括10000个整数(0-9999);可以通过调整REDIS_SHARED_INTEGERS参数提高共享对象的个数;例如将REDIS_SHARED_INTEGERS调整到20000,则0-19999之间的对象都可以共享。考虑这样一种场景:论坛网站在redis中存储了每个帖子的浏览数,而这些浏览数绝大多数分布在0-20000之间,这时候通过适当增大REDIS_SHARED_INTEGERS参数,便可以利用共享对象节省内存空间。(4)避免过度设计然而需要注意的是,不论是哪种优化场景,都要考虑内存空间与设计复杂度的权衡;而设计复杂度会影响到代码的复杂度、可维护性。如果数据量较小,那么为了节省内存而使得代码的开发、维护变得更加困难并不划算;还是以前面讲到的90000个键值对为例,实际上节省的内存空间只有几MB。但是如果数据量有几千万甚至上亿,考虑内存的优化就比较必要了。3、关注内存碎片率内存碎片率是一个重要的参数,对redis 内存的优化有重要意义。如果内存碎片率过高(jemalloc在1.03左右比较正常),说明内存碎片多,内存浪费严重;这时便可以考虑重启redis服务,在内存中对数据进行重排,减少内存碎片。如果内存碎片率小于1,说明redis内存不足,部分数据使用了虚拟内存(即swap);由于虚拟内存的存取速度比物理内存差很多(2-3个数量级),此时redis的访问速度可能会变得很慢。因此必须设法增大物理内存(可以增加服务器节点数量,或提高单机内存),或减少redis中的数据。要减少redis中的数据,除了选用合适的数据类型、利用共享对象等,还有一点是要设置合理的数据回收策略(maxmemory-policy),当内存达到一定量后,根据不同的优先级对内存进行回收。
-
跳槽不算频繁,但参加过不少面试(电话面试、face to face 面试),面过大 / 小公司、互联网 / 传统软件公司,面糊过(眼高手低,缺乏实战经验,挂掉),也面过人,所幸未因失败而气馁,在此过程中不断查缺补漏,养成了踏实、追本溯源、持续改进的习惯,特此将自己经历过、构思过的一些面试题记录下来,如果答案有问题,欢迎拍砖讨论,希望能对找工作或者感兴趣的同学有所帮助,陆续整理中。1. synchronized 和 reentrantlock 异同相同点都实现了多线程同步和内存可见性语义都是可重入锁不同点实现机制不同 synchronized 通过 java 对象头锁标记和 Monitor 对象实现 reentrantlock 通过CAS、ASQ(AbstractQueuedSynchronizer)和 locksupport(用于阻塞和解除阻塞)实现synchronized 依赖 jvm 内存模型保证包含共享变量的多线程内存可见性 reentrantlock 通过 ASQ 的volatile state 保证包含共享变量的多线程内存可见性使用方式不同 synchronized 可以修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、代码块(显示指定锁对象)reentrantlock 显示调用 trylock()/lock() 方法,需要在 finally 块中释放锁功能丰富程度不同 reentrantlock提供有限时间等候锁(设置过期时间)、可中断锁(lockInterruptibly)、condition(提供 await、signal等方法)等丰富语义 reentrantlock 提供公平锁和非公平锁实现 synchronized不可设置等待时间、不可被中断(interrupted)2. concurrenthashmap 为何读不用加锁jdk1.71)HashEntry 中的 key、hash、next 均为 final 型,只能表头**、删除结点2)HashEntry 类的 value 域被声明为 volatile 型3)不允许用 null 作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null时,便知道产生了冲突——发生了重排序现象(put 设置新 value 对象的字节码指令重排序),需要加锁后重新读入这个 value 值4)volatile 变量 count 协调读写线程之间的内存可见性,写操作后修改 count,读操作先读 count,根据happen-before 传递性原则写操作的修改读操作能够看到jdk1.81)Node 的 val 和 next 均为 volatile 型2)tabAt 和 casTabAt 对应的 unsafe 操作实现了 volatile 语义3. ContextClassLoader(线程上下文类加载器)的作用越过类加载器的双亲委派机制去加载类,如 serviceloader 实现使用线程上下文类加载器加载类,要注意保证多个需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器导致类型转换异常(ClassCastException)4. tomcat 类加载机制不同应用使用不同的 webapp 类加载器,实现应用隔离的效果,webapp 类加载器下面是 jsp 类加载器不同应用共享的 jar 包可以放到 Shared 类加载器 /shared 目录下5. osgi 类加载机制osgi 类加载模型是网状的,可以在模块(Bundle)间互相委托osgi 实现模块化热部署的关键是自定义类加载器机制的实现,每个 Bundle 都有一个自己的类加载器,当需要更换一个 Bundle时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换当收到类加载请求时,osgi 将按照下面的顺序进行类搜索:1)将以 java.* 开头的类委派给父类加载器加载2)否则,将委派列表名单(配置文件 org.osgi.framework.bootdelegation 中定义)内的类委派给父类加载器加载3)否则,检查是否在 Import-Package 中声明,如果是,则委派给 Export 这个类的 Bundle 的类加载器加载4)否则,检查是否在 Require-Bundle 中声明,如果是,则将类加载请求委托给 required bundle 的类加载器5)否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载6)否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载7)否则,查找 Dynamic Import-Package(Dynamic Import 只有在真正用到此 Package的时候才进行加载)的 Bundle,委派给对应 Bundle 的类加载器加载8)否则,类查找失败6. 如何结束一个一直运行的线程使用退出标志,这个 flag 变量要多线程可见使用 interrupt,结合 isInterrupted() 使用7. threadlocal 使用场景及问题threadlocal 并不能解决多线程共享变量的问题,同一个 threadlocal 所包含的对象,在不同的 thread中有不同的副本,互不干扰用于存放线程上下文变量,方便同一线程对变量的前后多次读取,如事务、数据库 connection 连接,在 web 编程中使用的更多问题: 注意线程池场景使用 threadlocal,因为实际变量值存放在了 thread 的 threadlocalmap类型变量中,如果该值没有 remove,也没有先 set 的话,可能会得到以前的旧值问题: 注意线程池场景下的内存泄露,虽然 threadlocal 的 get/set 会清除 key(key 为 threadlocal的弱引用,value 是强引用,导致 value 不释放)为 null 的 entry,但是最好 remove8. 线程池从启动到工作的流程刚创建时,里面没有线程调用 execute() 添加任务时:1)如果正在运行的线程数量小于核心参数 corePoolSize,继续创建线程运行这个任务2)否则,如果正在运行的线程数量大于或等于 corePoolSize,将任务加入到阻塞队列中3)否则,如果队列已满,同时正在运行的线程数量小于核心参数 maximumPoolSize,继续创建线程运行这个任务4)否则,如果队列已满,同时正在运行的线程数量大于或等于 maximumPoolSize,根据设置的拒绝策略处理5)完成一个任务,继续取下一个任务处理6)没有任务继续处理,线程被中断或者线程池被关闭时,线程退出执行,如果线程池被关闭,线程结束7)否则,判断线程池正在运行的线程数量是否大于核心线程数,如果是,线程结束,否则线程阻塞。因此线程池任务全部执行完成后,继续留存的线程池大小为corePoolSize8)本文所列出的 14 个 Java面试题只是我所遭遇的面试中的一部分,其他的面试题我也会陆续整理出来,说到这里另外顺便给大家推荐一个架构交流学习群:650385180,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty 源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。9. 阻塞队列 BlockingQueue take 和 poll 区别poll(time):取走 BlockingQueue 里排在首位的对象, 若不能立即取出,则可以等 time参数规定的时间,取不到时返回 nulltake():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻塞直到BlockingQueue 有新的对象被加入10. 如何从 FutureTask 不阻塞获取结果get(long timeout,TimeUnit unit),超时则返回轮询,先通过 isDone()判断是否结束,然后调用 get()11. blockingqueue 如果存放了比较关键的数据,系统宕机该如何处理开放性问题,欢迎讨论将队列持久化,比较麻烦,需要将生产数据持久化到磁盘,持久化成功才返回,消费者线程从磁盘加载数据到内存阻塞队列中,维护消费offset,启动时,根据消费 offset 从磁盘加载数据加入消息队列,保证消息不丢失,生成序列号,消费幂等,根据消费进程决定系统重启后的生产状态12. NIO 与传统 I/O 的区别节约线程,NIO 由原来的每个线程都需要阻塞读写变成了由单线程(即 Selector)负责处理多个 channel注册(register)的兴趣事件(SelectionKey)集合(底层借助操作系统提供的 epoll()),netty bossgroup 处理 accept 连接(没看明白为什么 bossgroup 设置多个 thread的必要性),workergroup 处理具体业务流程和数据读写NIO 提供非阻塞操作传统 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据,NIO 提供 bytebuffer,分为堆内和堆外缓冲区,读写时均先放到该缓冲区中,然后由内核通过 channel传输到对端,堆外缓冲区不走内核,提升了性能13. list 中存放可重复字符串,如何删除某个字符串调用 iterator 相关方法删除倒删,防止正序删除导致的数组重排,index 跳过数组元素问题14. 有哪些 GC ROOTS(跟日常开发比较相关的是和此相关的内存泄露)所有 Java 线程当前活跃的栈帧里指向 GC 堆里的对象的引用,因此用不到的对象及时置 null,提升内存回收效率静态变量引用的对象,因此减少静态变量特别是静态集合变量的大小,集合存放的对象覆写 euqls()和 hashcode(),防止持续增长本地方法 JNI 引用的对象方法区中的常量引用的对象,因此减少在长字符串上调用 String.intern()classloader 加载的 class 对象,因此自定义 classloader 无效时及时置 null并且注意类加载器加载对象之间的隔离jvm 里的一些静态数据结构里指向 GC 堆里的对象的引用…本文转自茶轴的青春博客51CTO博客,如需转载,请自行联系原作者。原文链接
-
文章来源:CSDN博客,https://blog.csdn.net/mysqldba23/article/details/68066322# Server(服务器信息)redis_version:3.0.0 #redis服务器版本redis_git_sha1:00000000 #Git SHA1redis_git_dirty:0 #Git dirty flagredis_build_id:6c2c390b97607ff0 #redis build idredis_mode:cluster #运行模式,单机或者集群os:Linux 2.6.32-358.2.1.el6.x86_64 x86_64 #redis服务器的宿主操作系统arch_bits:64 #架构(32或64位)multiplexing_api:epoll #redis所使用的事件处理机制gcc_version:4.4.7 #编译redis时所使用的gcc版本process_id:12099 #redis服务器进程的pidrun_id:63bcd0e57adb695ff0bf873cf42d403ddbac1565 #redis服务器的随机标识符(用于sentinel和集群)tcp_port:9021 #redis服务器监听端口uptime_in_seconds:26157730 #redis服务器启动总时间,单位是秒uptime_in_days:302 #redis服务器启动总时间,单位是天hz:10 #redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。lru_clock:14359959 #自增的时钟,用于LRU管理,该时钟100ms(hz=10,因此每1000ms/10=100ms执行一次定时任务)更新一次。config_file:/redis_cluster/etc/9021.conf #配置文件路径# Clients(已连接客户端信息)connected_clients:1081 #已连接客户端的数量(不包括通过slave连接的客户端)client_longest_output_list:0 #当前连接的客户端当中,最长的输出列表,用client list命令观察omem字段最大值client_biggest_input_buf:0 #当前连接的客户端当中,最大输入缓存,用client list命令观察qbuf和qbuf-free两个字段最大值blocked_clients:0 #正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量# Memory(内存信息)used_memory:327494024 #由redis分配器分配的内存总量,以字节为单位used_memory_human:312.32M #以人类可读的格式返回redis分配的内存总量used_memory_rss:587247616 #从操作系统的角度,返回redis已分配的内存总量(俗称常驻集大小)。这个值和top命令的输出一致used_memory_peak:1866541112 #redis的内存消耗峰值(以字节为单位) used_memory_peak_human:1.74G #以人类可读的格式返回redis的内存消耗峰值used_memory_lua:35840 #lua引擎所使用的内存大小(以字节为单位)mem_fragmentation_ratio:1.79 #used_memory_rss和used_memory之间的比率,小于1表示使用了swap,大于1表示碎片比较多mem_allocator:jemalloc-3.6.0 #在编译时指定的redis所使用的内存分配器。可以是libc、jemalloc或者tcmalloc# Persistence(rdb和aof的持久化相关信息)loading:0 #服务器是否正在载入持久化文件rdb_changes_since_last_save:28900855 #离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化rdb_bgsave_in_progress:0 #服务器是否正在创建rdb文件rdb_last_save_time:1482358115 #离最近一次成功创建rdb文件的时间戳。当前时间戳 - rdb_last_save_time=多少秒未成功生成rdb文件rdb_last_bgsave_status:ok #最近一次rdb持久化是否成功rdb_last_bgsave_time_sec:2 #最近一次成功生成rdb文件耗时秒数rdb_current_bgsave_time_sec:-1 #如果服务器正在创建rdb文件,那么这个域记录的就是当前的创建操作已经耗费的秒数aof_enabled:1 #是否开启了aofaof_rewrite_in_progress:0 #标识aof的rewrite操作是否在进行中aof_rewrite_scheduled:0 #rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite aof_last_rewrite_time_sec:-1 #最近一次aof rewrite耗费的时长aof_current_rewrite_time_sec:-1 #如果rewrite操作正在进行,则记录所使用的时间,单位秒aof_last_bgrewrite_status:ok #上次bgrewriteaof操作的状态aof_last_write_status:ok #上次aof写入状态aof_current_size:4201740 #aof当前尺寸aof_base_size:4201687 #服务器启动时或者aof重写最近一次执行之后aof文件的大小aof_pending_rewrite:0 #是否有aof重写操作在等待rdb文件创建完毕之后执行?aof_buffer_length:0 #aof buffer的大小aof_rewrite_buffer_length:0 #aof rewrite buffer的大小aof_pending_bio_fsync:0 #后台I/O队列里面,等待执行的fsync调用数量aof_delayed_fsync:0 #被延迟的fsync调用数量# Stats(一般统计信息)total_connections_received:209561105 #新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,说明短连接严重或连接池使用有问题,需调研代码的连接设置total_commands_processed:2220123478 #redis处理的命令数instantaneous_ops_per_sec:279 #redis当前的qps,redis内部较实时的每秒执行的命令数total_net_input_bytes:118515678789 #redis网络入口流量字节数total_net_output_bytes:236361651271 #redis网络出口流量字节数instantaneous_input_kbps:13.56 #redis网络入口kpsinstantaneous_output_kbps:31.33 #redis网络出口kpsrejected_connections:0 #拒绝的连接个数,redis连接个数达到maxclients限制,拒绝新连接的个数sync_full:1 #主从完全同步成功次数sync_partial_ok:0 #主从部分同步成功次数sync_partial_err:0 #主从部分同步失败次数expired_keys:15598177 #运行以来过期的key的数量evicted_keys:0 #运行以来剔除(超过了maxmemory后)的key的数量keyspace_hits:1122202228 #命中次数keyspace_misses:577781396 #没命中次数pubsub_channels:0 #当前使用中的频道数量pubsub_patterns:0 #当前使用的模式的数量latest_fork_usec:15679 #最近一次fork操作阻塞redis进程的耗时数,单位微秒migrate_cached_sockets:0 ## Replication(主从信息,master上显示的信息)role:master #实例的角色,是master or slaveconnected_slaves:1 #连接的slave实例个数slave0:ip=192.168.64.104,port=9021,state=online,offset=6713173004,lag=0 #lag从库多少秒未向主库发送REPLCONF命令master_repl_offset:6713173145 #主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟repl_backlog_active:1 #复制积压缓冲区是否开启repl_backlog_size:134217728 #复制积压缓冲大小repl_backlog_first_byte_offset:6578955418 #复制缓冲区里偏移量的大小repl_backlog_histlen:134217728 #此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小# Replication(主从信息,slave上显示的信息)role:slave #实例的角色,是master or slavemaster_host:192.168.64.102 #此节点对应的master的ipmaster_port:9021 #此节点对应的master的portmaster_link_status:up #slave端可查看它与master之间同步状态,当复制断开后表示downmaster_last_io_seconds_ago:0 #主库多少秒未发送数据到从库?master_sync_in_progress:0 #从服务器是否在与主服务器进行同步slave_repl_offset:6713173818 #slave复制偏移量slave_priority:100 #slave优先级slave_read_only:1 #从库是否设置只读connected_slaves:0 #连接的slave实例个数master_repl_offset:0 repl_backlog_active:0 #复制积压缓冲区是否开启repl_backlog_size:134217728 #复制积压缓冲大小repl_backlog_first_byte_offset:0 #复制缓冲区里偏移量的大小repl_backlog_histlen:0 #此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小# CPU(CPU计算量统计信息)used_cpu_sys:96894.66 #将所有redis主进程在核心态所占用的CPU时求和累计起来used_cpu_user:87397.39 #将所有redis主进程在用户态所占用的CPU时求和累计起来used_cpu_sys_children:6.37 #将后台进程在核心态所占用的CPU时求和累计起来used_cpu_user_children:52.83 #将后台进程在用户态所占用的CPU时求和累计起来# Commandstats(各种不同类型的命令的执行统计信息)cmdstat_get:calls=1664657469,usec=8266063320,usec_per_call=4.97 #call每个命令执行次数,usec总共消耗的CPU时长(单位微秒),平均每次消耗的CPU时长(单位微秒)# Cluster(集群相关信息)cluster_enabled:1 #实例是否启用集群模式# Keyspace(数据库相关的统计信息)db0:keys=194690,expires=191702,avg_ttl=3607772262 #db0的key的数量,以及带有生存期的key的数,平均存活时间
-
现象描述集群安装过程或执行DBService服务重启操作时,DBService服务启动失败,打印的错误日志中出现20051端口被占用等信息。 可能原因 [*]DBService使用的默认端口20051被其他进程占用。 [*]DBService进程没有停止成功,使用的端口未释放。 定位思路 [*]检查Manager管理界面显示的错误日志中是否存在如下内容:“already exists for port:20051”。 [*]恢复该故障大约需要10分钟。 处理步骤 [*]使用PuTTY工具,以root用户登录安装DBService报错的节点主机,执行netstat -nap | grep 20051命令查看使用端口的进程。 [*]使用kill命令强制终止使用20051端口的进程。 [*]约2分钟后,再次执行netstat -nap | grep 20051命令,查看是否还有进程占用该端口。 [*]是,执行步骤4 [*]否,执行步骤5 [*]确认占用该端口进程所属的服务,并修改为其他端口。 说明:20051端口为DBservice专用端口,其他进程不可以使用。 [*]分别在“/tmp”和“/var/run/FusionInsight-DBService”目录下执行find . -name '*20051*'命令,将搜索到的文件全部删除。 [*]登录FusionInsight Manager,单击“服务管理”,重启DBService服务。
-
现象描述在Yarn上运行MapReduce任务,在任务执行过程中出现一些Map或者Reduce被中止的情况,出现“Task Failed”时在jobhistoryserver的原生WebUI界面显示如下日志信息:Container [pid=143419,containerID=container_1432208368012_0016_01_000066] is running beyond virtual memory limits. Current usage: 769.8 MB of 1 GB physical memory used; 3.8 GB of 2.5 GB virtual memory used. Killing container. 可能原因从日志显示这个“container killed”直接原因是虚拟内存使用超过了限定值,YARN的NodeManager监控到内存使用超过阈值,强制终止该container进程。 定位思路 [*]检查任务执行失败的直接原因是物理内存溢出还是虚拟内存溢出。 [*]检查失败的Map或Reduce的物理内存配置及虚拟内存的比率配置,需要检查的配置项包括“mapreduce.map.memory.mb”、“mapreduce.reduce.memory.mb” 及“yarn.nodemanager.vmem-pmem-ratio”。 处理步骤 [*]如果是物理内存溢出,则根据实际报错日志中显示需要的物理内存值,调整配置项“mapreduce.map.memory.mb”、“mapreduce.reduce.memory.mb”到合适的值 [*]如果是虚拟内存溢出,则根据实际报错日志中显示需要的虚拟内存值,按照以下公式:“yarn.nodemanager.vmem-pmem-ratio > 实际使用的虚拟内存值/map或者reduce的物理内存”进行调整比率
-
本帖最后由 小柴不加胡 于 2018-5-14 17:58 编辑消息队列(MQ)是目前系统架构中主流方式,在大型系统及大数据中广泛采用。对任何架构或应用来说, MQ都是一个至关重要的组件。今天我们就来细数MQ那些不得不说的好处。好处一:解耦在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间**了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。比如我们的货款抵扣业务场景,用户生成订单发送MQ后立即返回,结算系统去消费该MQ进行用户账户金额的扣款。这样订单系统只需要关注把订单创建成功,最大可能的提高订单量,并且生成订单后立即返回用户。而结算系统重点关心的是账户金额的扣减,保证账户金额最终一致。好处二:冗余有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。MQ把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多MQ所采用的"**-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。好处三:扩展性因为MQ解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。就比如DMS分布式消息服务,不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。好处四:灵活性和峰值处理能力在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用MQ能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。还是以订单系统和结算系统场景为例,如果订单系统通过RPC框架来调用结算系统,在有高峰促销的情况下生成订单的量会非常大,而且由于生成订单的速度也非常快,这样势必会给结算系统造成系统压力,服务器利用率则会偏高,但在不是高峰的时间点订单量比较小,结算系统的服务器利用率则会偏低。对于结算系统来说就会出现下面这样的高峰波谷现象图。那么如果通过MQ的方式,将订单存储到MQ队列中,消费端通过拉取的方式,并且拉去速度有消费端来控制,则就可以控制流量趋于平稳。这样对于结算系统来讲,就达到了削峰填谷的目的。或者说起到了流控的目标好处五:可恢复性系统的一部分组件失效时,不会影响到整个系统。MQ降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。好处六:顺序保证在大多使用场景下,数据处理的顺序都很重要。大部分MQ本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka保证一个Partition内的消息的有序性。好处七:缓冲在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。好处八:异步通信很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。对系统而言,MQ消息队列机制能承受更大访问压力;对架构而言,松耦合,系统维护性方便;而对用户而言,想要系统访问更快、系统体验更好,自然首选DMS啦!
-
本帖最后由 小柴不加胡 于 2018-5-11 11:28 编辑背景国内某大型税务系统,业务应用分布式上云改造。 业务难题15178如上图所示是模拟客户的业务网页构建的一个并发访问模型。用户在页面点击从而产生一个HTTP请求,这个请求发送到业务生产进程,就会启动一个投递线程(Deliver Thread)调用Kafka的SDK接口,并发送3条消息到DMS(分布式消息服务),每条消息大小3k,需要等待3条消息都被处理完成后才会返回请求响应⑧。当消息达到DMS后,业务消费进程调用Kafka的消费接口把消息取出来,然后将每条消息放到一个响应线程(Response Thread)中进行处理,响应线程处理完后,通过HTTP请求通知投递线程,投递线程收到响应后返回回复响应。 100并发访问时延500ms,未达成用户业务要求客户提出了明确的要求:每1个两核的ECS要能够支撑并发访问量100,每条消息端到端的时延范围是几十毫秒,即从生产者发送开始到接收到消费者响应的时间。客户实测在使用了DMS的Kafka 队列后,并发访问量为100时时延高达到500ms左右,甚至出现达到秒级的时延,远未达到客户提出的业务诉求。相比较而言,客户在Pod区使用的是自己搭建的原生Kafka,在并发访问量为100时测试到的时延大约只有10~20ms左右。那么问题来了,在并发访问量相同的条件下,DMS的Kafka 队列与Pod区自建的原生Kafka相比为什么时延会有这么大的差异呢?我们DMS的架构师 Mr. Peng对这个时延难题进行了一系列分析后完美解决了这个客户难题,下面就让我们来看看他的心路历程。 难题剖析根据模拟的客户业务模型,Mr. Peng在华为云类生产环境上也构造了一个测试程序,同样模拟构造了100的并发访问量,通过测试发现,类生产环境上压测得到的时延平均时间在60ms左右。类生产上的时延数值跟客户在真实生产环境上测到的时延差距这么大,这是怎么回事呢?问题变得扑朔迷离起来。Mr. Peng当机立断,决定就在华为云现网上运行构造的测试程序,来看看到底是什么原因。同时,在客户的ECS服务器上,也部署了相同的测试程序,模拟构建了100的并发量,得到如下的时延结果对比表:15181 表1 华为云现网与类生产环境时延对比表 从时延对比表的结果看来,Mr. Peng发现,即使在相同的并发压力下,华为云现网的时延比类生产差很多。Mr.Peng意识到,现在有2个问题需要分析:为什么华为云现网的时延会比类生产差?DMS的Kafka队列时延比原生自建的Kafka队列时延表现差的问题怎么解决?Mr. Peng分析如下: 时延分析回归问题的本质,DMS Kafka队列的时延到底是怎么产生的?可控的端到端时延具体分为哪些?Mr.Peng给出了如下的计算公式:总时延 = 入队时延 + 发送时延 + 写入时延 + 复制时延+ 拉取时延让我们来依次了解一下,公式中的每一项都是指什么。入队时延: 消息进入Kafka sdk后,先进入到要发送分区的队列,完成消息打包后再发送,这一过程所用的时间。发送时延:消息从生产者发送到服务端的时间。写入时延:消息写入到Kafka Leader的时间。复制时延:消费者只可以消费到高水位以下的消息(即被多个副本都保存的消息),所以消息从写入到Kafka Leader,到所有副本都写入该消息直到上涨至高水位这段时间就是消息复制的时延。拉取时延:消费者采用pull模式拉取数据,拉取过程所用的时间。 (1) 入队时延现网是哪一部分的时延最大呢?通过我们的程序可以看到,入队列等待发送时延非常大,如下图:15179 即消息都等待在生产端的队列中,来不及发送! 我们再看其他时延分析,因为无法在现网测试,我们分别在类生产测试了相同压力的,测试其他各种时延如下:(2) 复制时延以下是类生产环境测试的1并发下的15182 从日志上看,复制时延包括在remoteTime里面,当然这个时间也会包括生产者写入时延比较慢导致的,但是也从一定的程度反映复制时延也是提升性能时延的一个因素。 (3) 写入时延因为用户使用的是高吞吐队列,写入都是异步落盘,我们从日志看到写入时延非常低(localTime),可以判断不是瓶颈。发送时延与拉取时延都是跟网络传输有关系,这个优化主要是通过调TCP的参数来决定的。轻轻松松把Kafka消息时延秒降10倍,就用华为云DMS
-
本帖最后由 大脸猫爱吃鱼 于 2018-4-17 10:06 编辑 【作者】文/华为云微服务引擎团队 【摘要】近年来越来越多的企业开始实践微服务,本文分为上下两篇介绍微服务框架ServiceComb如何帮助企业应用进行微服务化,实现快速交付,并可靠地运行在云端。下篇介绍ServiceComb的通信处理设计。 【参考】打造一个企业级应用的微服务开发框架(上)---从服务注册中心到服务管理中心 近年来越来越多的企业开始实践微服务,而微服务在企业应用落地的过程,面临着微服务开发框架的选型,无论是自研还是选择第三方框架都不得不考虑的问题包括:微服务框架是否具备高可靠性,任何时间不能中断业务;微服务框架是否能够实现高速通信性能,保证业务从单体架构向微服务架构切换时,性能下降不会太多。本文从服务管理中心、通信处理两个模块来介绍华为开源微服务框架SeviceComb如何帮助企业应用快速具备高性能的通信能力以及高可靠的服务管理能力。下篇将详细介绍ServiceComb的通信处理。 ServiceComb通信处理详解1 整体介绍ServiceComb的底层通信框架依赖Vert.x.vertx标准工作模式为高性能的reactive模式,其工作方式如下图所示:13913图9 reactive模式工作方式业务逻辑直接在eventloop中执行,整个业务流程中没有线程切换,所有的等待逻辑都是异步的,只要有任务,则不会让线程停下来,充分、有效地利用系统资源。vertx生态中包含了业界常用各种组件的reactive封装,包括jdbc、zookeeper、各种mq等等。但是reactive模式对业务的要求相当高,业务主流程中不允许有任何的阻塞行为。因此,为了简化上层业务逻辑,方便开发人员的使用,在Vertx之上提供同步模式的开发接口还是必不可少的,例如: [*]各种安全加固的组件,只提供了同步工作模式,比如redis、zookeeper等等 [*]一些存量代码工作于同步模式,需要低成本迁移 [*]开发人员技能不足以控制reactive逻辑 所以ServiceComb底层基于vertx,但在vertx之上进行了进一步封装,同时支持reactive及同步模式。工作于Reactive模式时,利用Vertx原生的能力,不必做什么额外的优化,仅需要注意不要在业务代码中阻塞整个进程。而同步模式则会遭遇各种并发性能问题。,本文描述同步模式下的各种问题以及解决方案。RESTful流程中,连接由vertx管理,当前没有特别的优化,所以本文中,连接都是指highway流程中的tcp连接。 2 同步模式下的整体线程模型13914图10 同步模式下的整体线程模型 [*]一个微服务进程中,为transport创建了一个独立的vertx实例 [*]Eventloop是vertx中的网络、任务线程 [*]一个vertx实例默认的Eventloop数为:2 * Runtime.getRuntime().availableProcessors() 3 服务消费者端在服务消费者端,主要需要处理的问题是如何更加高效地把请求推送到服务提供者上去,然后拿到服务提供者的返回信息。所以在这一端我们主要关注“如何更高效的发送数据”这个话题。 3.1 单连接模型最简单的单连接模型13916图11 最简单的单连接模型 从模型图中,我们可以看到,所有的consumer线程,如果向同一个目标发送数据,必然产生资源竞争,此时实际的处理如下: [*]Connection.send内部直接调用Vertx的socket.write(buf),是必然加锁互斥的。 这必然导致大量并发时,大多数consumer线程都无法及时地发送自己的数据。 [*]Socket.write内部会调用netty的channel.write,此时会判断出执行线程不是eventloop线程,所以会创建出一个任务并加入到eventloop任务队列中,如果eventloop线程当前在睡眠态,则立即唤醒eventloop线程,异步执行任务。 这导致频繁的任务下发及线程唤醒,无谓地增加cpu占用,降低性能。优化的单连接模型13917图12 优化的单连接模型 在优化模型中: [*]每个TcpClientConnection额外配备一个CAS消息队列 [*]Connection.send不再直接调用vertx的write方法,而是: 所有消息保存到CAS队列中,减少入队竞争 通过原子变量判定,只有入队前CAS队列为空,才向eventloop下发write任务,唤醒eventloop线程 在eventloop中处理write任务时,将多个请求数据包装为composite buffer,批量发送,减少进入os内核的次数,提高tcp发送效率。代码参见: https://github.com/ServiceComb/ServiceComb-Java-Chassis/blob/master/foundations/foundation-vertx/src/main/java/io/servicecomb/foundation/vertx/client/tcp/TcpClientConnection.java[code]io.servicecomb.foundation.vertx.client.tcp.TcpClientConnection.packageQueue io.servicecomb.foundation.vertx.client.tcp.TcpClientConnection.send(AbstractTcpClientPackage,long, TcpResponseCallback)[/code]https://github.com/ServiceComb/ServiceComb-Java-Chassis/blob/master/foundations/foundation-vertx/src/main/java/io/servicecomb/foundation/vertx/tcp/TcpConnection.java[code]io.servicecomb.foundation.vertx.tcp.TcpConnection.write(ByteBuf) io.servicecomb.foundation.vertx.tcp.TcpConnection.writeInContext()[/code]进行此项优化后,在同一环境下测试2组数据,可以看到性能有明显提升(不同硬件的测试环境,数据可能差异巨大,不具备比较意义):TPSLatency(ms)CPUTPS提升比例时延提升比例ConsumerProducer(新-旧)/旧(旧-新)/新优化前819861.22290%290%77.31%43.61%优化后1453690.688270%270%表6 单连接模型优化前后性能对比3.2 多连接模型在单连接场景下进行相应的优化后,我们发现其实还有更多的优化空间。因为在大多数场景中,实际机器配置足够高,比如多核、万兆网络连接、网卡支持RSS特性等。此时,需要允许一对consumer与producer之间建立多条连接来充分发挥硬件的性能。13918图13 多连接模型 [*]允许配置多个eventloop线程 在microservice.yaml中进行以下配置:[code]cse: highway: client: thread-count: 线程数 server: thread-count: 线程数[/code] [*]Consumer线程与eventloop线程建立均衡的绑定关系,进一步降低consumer线程的竞争概率。 代码参见:https://github.com/ServiceComb/ServiceComb-Java-Chassis/blob/master/foundations/foundation-vertx/src/main/java/io/servicecomb/foundation/vertx/client/ClientPoolManager.java[code]io.servicecomb.foundation.vertx.client.ClientPoolManager.findThreadBindClientPool()[/code]优化后的性能对比:TPSLatency (ms)CPUTPS提升比例时延提升比例ConsumerProducer(新-旧)/旧(旧-新)/新简单单连接*105434420.9192305%1766%72.81%42.11%CAS单连接*109391170.5321960%1758%表7 多连接下线程模型优化前后性能对比 每请求大小为1KB,可以看到万兆网的带宽接近吃满了,可以充分利用硬件性能。(该测试环境,网卡支持RSS特性。)4 服务提供者端不同于服务消费者,服务提供者主要的工作模式就是等待消费者的请求,然后处理后返回应答的信息。所以在这一端,我们更加关注“如何高效的接收和处理数据”这件事情。同步模式下,业务逻辑和IO逻辑分开,且根据“隔离仓”原则,为了保证整个系统更加稳定和高效地运行,业务逻辑本身也需要在不同隔离的区域内运行。而这些区域,就是线程池。所以构建服务提供者,就需要对线程池进行精细的管理。下面是针对线程池的各种管理方式。4.1 单线程池(ThreadPoolExecutor)下图表示的是将业务逻辑用单独的线程池实现的方式。在这种方式下,IO仍然采用异步模式,所有接到的请求放入队列中等待处理。在同一个线程池内的线程消费这个队列并进行业务处理。13919图14 单线程池实现方式在这种方式下,有以下瓶颈点: [*]所有的eventloop向同一个BlockingQueue中提交任务 [*]线程池中所有线程从同一个BlockingQueue中抢任务执行 ServiceComb默认不使用这种线程池。4.2 多线程池(ThreadPoolExecutor)为规避线程池中Queue带来的瓶颈点,我们可以使用一个Executor将多个真正的Executor包起来。13920图15 多线程池实现方式 [*]Eventloop线程与线程池建立均衡的绑定关系,降低锁冲突概率 [*]相当于将线程分组,不同线程从不同Queue中抢任务,降低冲突概率 ServiceComb默认所有请求使用同一个线程池实例:io.servicecomb.core.executor.FixedThreadExecutorFixedThreadExecutor内部默认创建2个真正的线程池,每个池中有CPU数目的线程,可以通过配置修改默认值:[code]servicecomb: executor: default: group: 内部真正线程池的数目 thread-per-group: 每个线程池中的线程数[/code]代码参见:https://github.com/ServiceComb/ServiceComb-Java-Chassis/blob/master/core/src/main/java/io/servicecomb/core/executor/FixedThreadExecutor.java4.3 隔离仓业务接口的处理速度有快有慢,如果所有的请求统一在同一个Executor中进行处理,则可能每个线程都在处理慢速请求,导致其他请求在Queue中排队。此时,可以根据业务特征,事先做好规划,将不同的业务处理按照一定的方式进行分组,每个组用不同的线程池,以达到隔离的目的。13921图16 隔离仓 隔离仓的实现依托到ServiceComb灵活的线程池策略,具体在下一节进行描述。4.4 灵活的线程池策略ServiceComb微服务的概念模型如下:13922图17 ServiceComb微服务概念模型可以针对这3个层次进行线程池的配置,operation与线程池之间的对应关系,在启动阶段既完成绑定。operation与线程池之间的绑定按以下逻辑进行: [*]查看配置项cse.executors.Provider.[schemaId].[operationId]是否有值 如果有值,则将值作为beanId从spring中获取bean实例,该实例即是一个Executor 如果没有值,则继续尝试下一步 [*]使用相同的方式,查看配置项cse.executors.Provider.[schemaId]是否有值 [*]使用相同的方式,查看配置项cse.executors.default是否有值 [*]以”cse.executor.groupThreadPool”作为beanId,获取线程池(系统内置的FixedThreadExecutor) 代码参见:https://github.com/ServiceComb/ServiceComb-Java-Chassis/blob/master/core/src/main/java/io/servicecomb/core/executor/ExecutorManager.java 按以上策略,用户如果需要创建自定义的线程池,需要按以下步骤执行: [*]实现java.util.concurrent.Executor接口 [*]将实现类定义为一个bean [*]在microservice.yaml中将线程池与对应的业务进行绑定 4.5 线程池模型总结如上一节所述,在默认多线程池的基础上,CSE提供了更为灵活的线程池配置。“隔离仓”模式的核心价值是实现不同业务之间的相互隔离,从而让一个业务的故障不要影响其他业务。这一点在CSE中可以通过对线程池的配置实现。例如,可以为不同的operation配置各自独立的线程池。 另外,灵活性也带来了一定的危险性。要避免将线程池配置为前面提到的“单业务线程池”模式,从而为整个系统引入瓶颈点。 ServiceComb除了在华为云微服务引擎商用之外,也于2017年12月全票通过进入Apache孵化器。欢迎感兴趣的读者前往开源社区和我们讨论切磋,希望此文可以给正在进行微服务方案实施的读者们一些启发。
-
本帖最后由 danglf 于 2018-3-23 15:18 编辑副本集方式是MongoDB最常用的部署方式,副本集使用之前必须先初始化, 副本集初始化是通过执行replSetInitiate完成的,本文主要分析副本集收到该命令后背后的逻辑(本文基于MongoDB3.2.18分析)。1.1 入口 当用户执行replSetInitiate 命令后, 最终会调用到CmdReplSetInitiate类的 run 方法,CmdReplSetInitiate 在Replset_commands.cpp里面定义。对于Mongod 启动后, 用户执行各种命令和查询操作, 是如何运行起来的,及command内部原理和体系结构 ,后续会写一篇专门的文章去分析。代码如下: 13173 13174 总体流程如下1 校验mongod的配置文件是不是配置配置副本集参数。2 对于副本集配置参数进行补齐3 核心语句, 调用getGlobalReplicationCoordinator()->processReplSetInitiat, 进入复制集初始化流程。1.2 主体流程: 1 ) 判断当前状态是不是可以进行初始化,必须是副本集之前没有初始化过才可以,否则报错 2) 构建内部初始化需要的配置数据, 并判断初始化副本集中的副本集名称和配置文件中的是不是一致,初次以外还有其他的逻辑校验 3 ) 判断当前实例状态, 发起选举,选出主 备节点 4)启动所有后台线程: a)后台拉取主节点oplog线程 b) 向主节点同步自己复制状态的线程 c) 定时快照线程。 相关流程对应代码如下 1)状态判断 replication_coordinator_impl.cpp –>processReplSetInitiate 13175 2) 部分参数校验 13176 3) 发起选举 processReplSetInitiate->_finishReplSetInitiate->_performPostMemberStateUpdateAction->_startElectSelfV1 4) 启动后台线程: 13177 以上就是replSetInitiate 命令后,mongod 内部的大致流程, 分析比较粗, 实际上每一个流程,如选举的内部机制, 初始化同步过程, 增量同步过程, 备节点如何应用oplog,每一个都可以作为专项来分析,后面会持续进行, 文中如有不正确的地方,也欢迎指出并讨论。
-
客户简介 中软独家中标税务核心征管系统,全国34个省国/地税。电子税务局15省格局。 大数据国家税务总局局点,中国软件电子税务局技术路径:核心征管 + 纳税服务 业务应用分布式上云改造。业务难题12226 如上图所示是模拟客户的业务网页构建的一个并发访问模型。用户在页面点击从而产生一个HTTP请求,这个请求发送到业务生产进程,就会启动一个投递线程(DeliverThread)调用Kafka的SDK接口,并发送3条消息到DMS(分布式消息服务),每条消息大小3k,需要等待3条消息都被处理完成后才会返回请求响应⑧。当消息达到DMS后,业务消费进程调用Kafka的消费接口把消息取出来,然后将每条消息放到一个响应线程(Response Thread)中进行处理,响应线程处理完后,通过HTTP请求通知投递线程,投递线程收到响应后返回回复响应。 100并发访问时延500ms,未达成用户业务要求客户提出了明确的要求:每1个两核的ECS要能够支撑并发访问量100,每条消息端到端的时延范围是几十毫秒,即从生产者发送开始到接收到消费者响应的时间。客户实测在使用了DMS的Kafka 队列后,并发访问量为100时时延高达到500ms左右,甚至出现达到秒级的时延,远未达到客户提出的业务诉求。相比较而言,客户在Pod区使用的是自己搭建的原生Kafka,在并发访问量为100时测试到的时延大约只有10~20ms左右。那么问题来了,在并发访问量相同的条件下,DMS的Kafka 队列与Pod区自建的原生Kafka相比为什么时延会有这么大的差异呢?我们DMS的架构师 Mr. Peng对这个时延难题进行了一系列分析后完美解决了这个客户难题,下面就让我们来看看他的心路历程。难题剖析根据模拟的客户业务模型,Mr. Peng在华为云类生产环境上也构造了一个测试程序,同样模拟构造了100的并发访问量,通过测试发现,类生产环境上压测得到的时延平均时间在60ms左右。类生产上的时延数值跟客户在真实生产环境上测到的时延差距这么大,这是怎么回事呢?问题变得扑朔迷离起来。Mr. Peng当机立断,决定就在华为云现网上运行构造的测试程序,来看看到底是什么原因。同时,在客户的ECS服务器上,也部署了相同的测试程序,模拟构建了100的并发量,得到如下的时延结果对比表:调优前时延现网时延(ms)类生产时延(ms)100并发500ms ~ 4000ms40ms ~ 80 ms1并发31ms6msPing测试0.9ms ~ 1.2ms0.3ms ~ 0.4ms表1 华为云现网与类生产环境时延对比表 从时延对比表的结果看来,Mr. Peng发现,即使在相同的并发压力下,华为云现网的时延比类生产差很多。Mr.Peng意识到,现在有2个问题需要分析:为什么华为云现网的时延会比类生产差?DMS的Kafka队列时延比原生自建的Kafka队列时延表现差的问题怎么解决?Mr. Peng陷入了沉思之中。 欲知后事如何,请看下【云中间件锦囊妙计】巧用参数调优为客户解决时延高的难题(中篇)。云中间件锦囊妙计系列专题旨在为开发者、用户和对云中间件技术有兴趣的同学提供一系列的客户案例、技术干货、疑难杂症解决思路的分享,欢迎关注我们的小伙伴在下方留言,共同学习和分享,请大家多多支持哦~
-
本帖最后由 云彩飞扬 于 2018-2-7 11:50 编辑1.启动一个Slave进程后,它会向Master发送一个SYNC Command,请求同步连接。2.无论是第一次连接还是重新连接,Master都会启动一个后台进程,将数据快照保存到数据文件中,同时Master会记录所有修改数据的命令并缓存在数据文件中。3.后台进程完成缓存操作后,Master就发送数据文件给Slave,Slave端将数据文件保存到硬盘上,然后将其在加载到内存中,接着Master就会所有修改数据的操作,将其发送给Slave端。4.若Slave出现故障导致宕机,恢复正常后会自动重新连接,Master收到Slave的连接后,将其完整的数据文件发送给Slave。5.如果Mater同时收到多个Slave发来的同步请求,Master只会在后台启动一个进程保存数据文件,然后将其发送给所有的Slave,确保Slave正常。其优点:1.同一个Master可以拥有多个Slaves。2.Master下的Slave还可以接受同一架构中其它slave的链接与同步请求,实现数据的级联复制,即Master->Slave->Slave模式;3.Master以非阻塞的方式同步数据至slave,这将意味着Master会继续处理一个或多个slave的读写请求;4.Slave端同步数据也可以修改为非阻塞是的方式,当slave在执行新的同步时,它仍可以用旧的数据信息来提供查询;否则,当slave与master失去联系时,slave会返回一个错误给客户端;5.主从复制具有可扩展性,即多个slave专门提供只读查询与数据的冗余,Master端专门提供写操作;通过配置禁用Master数据持久化机制,将其数据持久化操作交给Slaves完成,避免在Master中要有独立的进程来完成此操作。
-
本帖最后由 vincent 于 2018-1-23 20:58 编辑appender name="test" class="ch.qos.logback.core.rolling.RollingFileAppender"> file>test/test.logfile> rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> fileNamePattern>test/test.%d{yyyy-MM-dd}.%i.logfileNamePattern> maxHistory>3maxHistory> totalSizeCap>100MBtotalSizeCap> maxFileSize>10MBmaxFileSize>rollingPolicy> encoder> Pattern>${ENCODER_PATTERN}Pattern>encoder>appender> 1.只有日志触发滚动,才会启动清理机制,当然我们可以配置进程启动时触发清理: cleanHistoryOnStart>truecleanHistoryOnStart>2. 先按照时间维度清理(一天只会触发一次,除非进程重启):3 这个参数决定了日志文件保留的历史,但是并不意味着手动创建一个1个月前的日志文件(或者说由于某些原因一个月前的文件没有及时删除)此时也能够被清除, 因为按照时间维度能够清理的最早日志文件是35(3+32)天前的日志3. 再根据日志总量来清理,100MB,日志总量的清理只会计算三天内的日志(3)总量是否达到100MB,所以说 总量的清理机制并不是按照目录下所有文件的总量来清理的
上滑加载中
推荐直播
-
OpenHarmony应用开发之网络数据请求与数据解析
2025/01/16 周四 19:00-20:30
华为开发者布道师、南京师范大学泰州学院副教授,硕士研究生导师,开放原子教育银牌认证讲师
科技浪潮中,鸿蒙生态强势崛起,OpenHarmony开启智能终端无限可能。当下,其原生应用开发适配潜力巨大,终端设备已广泛融入生活各场景,从家居到办公、穿戴至车载。 现在,机会敲门!我们的直播聚焦OpenHarmony关键的网络数据请求与解析,抛开晦涩理论,用真实案例带你掌握数据访问接口,轻松应对复杂网络请求、精准解析Json与Xml数据。参与直播,为开发鸿蒙App夯实基础,抢占科技新高地,别错过!
回顾中 -
Ascend C高层API设计原理与实现系列
2025/01/17 周五 15:30-17:00
Ascend C 技术专家
以LayerNorm算子开发为例,讲解开箱即用的Ascend C高层API
回顾中
热门标签