• 《逆袭进大厂》第二弹之C++进阶篇59问59答(超硬核干货)

    这是本期 C++ 八股文问题目录。 不逼逼了,《逆袭进大厂》系列第二弹 C++ 进阶篇直接发车了。 这篇总字数是 37814 个字,嗯,将近 4W 字。 50、static的用法和作用? 1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可) 当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。 2.static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。 3.static的第三个作用是默认初始化为0(static变量) 其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。 4.static的第四个作用:C++中的类成员声明static 1)  函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; 2)  在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 3)  在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; 4)  在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; 5)  在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。 类内: 6)  static类对象必须要在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化; 7)  由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员; 8)  static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtual function 51、静态变量什么时候初始化 1)  初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。 2)  静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点又有点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。 3)  而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。 52、const关键字? 1)  阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了; 2)  对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const; 3)  在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值; 4)  对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数; 5)  对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。 6)  const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员; 7)  非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员; 8)  一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数。 9)  const类型变量可以通过类型转换符const_cast将const类型转换为非const类型; 10) const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化; 11) 对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。 53、指针和const的用法 1)  当const修饰指针时,由于const的位置不同,它的修饰对象会有所不同。 2)  int *const p2中const修饰p2的值,所以理解为p2的值不可以改变,即p2只能指向固定的一个变量地址,但可以通过*p2读写这个变量的值。顶层指针表示指针本身是一个常量 3)  int const *p1或者const int *p1两种情况中const修饰*p1,所以理解为*p1的值不可以改变,即不可以给*p1赋值改变p1指向变量的值,但可以通过给p赋值不同的地址改变这个指针指向。 底层指针表示指针所指向的变量是一个常量。 54、形参与实参的区别? 1)  形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。 2)  实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值,会产生一个临时变量。 3)  实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。 4)  函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。 5)  当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。 55、值传递、指针传递、引用传递的区别和效率 1)   值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象   或是大的结构体对象,将耗费一定的时间和空间。(传值) 2)  指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值) 3)  引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址) 4)  效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。 56、什么是类的继承? 1) 类与类之间的关系 has-A包含关系,用以描述一个类由多个部件类构成,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类; use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现; is-A,继承关系,关系具有传递性; 2) 继承的相关概念 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类; 3) 继承的特点 子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使用; 4) 继承中的访问控制 public、protected、private 5) 继承中的构造和析构函数 6) 继承中的兼容性原则 57、什么是内存池,如何实现 https://www.bilibili.com/video/BV1Kb411B7N8?p=25 C++内存管理:P23-26 https://www.bilibili.com/video/BV1db411q7B8?p=12 C++STL P11 内存池(Memory Pool) 是一种内存分配方式。通常我们习惯直接使用new、malloc 等申请内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块, 若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。 这里简单描述一下《STL源码剖析》中的内存池实现机制: allocate包装malloc,deallocate包装free 一般是一次20*2个的申请,先用一半,留着一半,为什么也没个说法,侯捷在STL那边书里说好像是C++委员会成员认为20是个比较好的数字,既不大也不小 首先客户端会调用malloc()配置一定数量的区块(固定大小的内存块,通常为8的倍数),假设40个32bytes的区块,其中20个区块(一半)给程序实际使用,1个区块交出,另外19个处于维护状态。剩余20个(一半)留给内存池,此时一共有(20*32byte) 客户端之后有有内存需求,想申请(20*64bytes)的空间,这时内存池只有(20*32bytes),就先将(10*64bytes)个区块返回,1个区块交出,另外9个处于维护状态,此时内存池空空如也 接下来如果客户端还有内存需求,就必须再调用malloc()配置空间,此时新申请的区块数量会增加一个随着配置次数越来越大的附加量,同样一半提供程序使用,另一半留给内存池。申请内存的时候用永远是先看内存池有无剩余,有的话就用上,然后挂在0-15号某一条链表上,要不然就重新申请。 如果整个堆的空间都不够了,就会在原先已经分配区块中寻找能满足当前需求的区块数量,能满足就返回,不能满足就向客户端报bad_alloc异常 《STL源码解析》侯捷 P68 allocator就是用来分配内存的,最重要的两个函数是allocate和deallocate,就是用来申请内存和回收内存的,外部(一般指容器)调用的时候只需要知道这些就够了。内部实现,目前的所有编译器都是直接调用的::operator new()和::operator delete(),说白了就是和直接使用new运算符的效果是一样的,所以老师说它们都没做任何特殊处理。 最开始GC2.9之前: new和 operator new 的区别:new 是个运算符,编辑器会调用 operator new(0) operator new()里面有调用malloc的操作,那同样的 operator delete()里面有调用的free的操作 GC2.9的alloc的一个比较好的分配器的实现规则 维护一条0-15号的一共16条链表,其中0表示8 bytes ,1表示 16 bytes,2表示 24bytes。。。。而15 表示 16* 8 = 128bytes,如果在申请时并不是8的倍数,那就找刚好能满足内存大小的那个位置。比如想申请 12,那就是找16了,想申请 20 ,那就找 24 了 但是现在GC4.9及其之后 也还有,变成_pool_alloc这个名字了,不再是默认的了,你需要自己去指定它可以自己指定,比如说vector

    C语言与CPP编程 C 进阶篇

  • Microchip发布2.2版TimeProviderÒ 4100主时钟产品,提供全新水平的冗余、弹性和安全性

    Microchip发布2.2版TimeProviderÒ 4100主时钟产品,提供全新水平的冗余、弹性和安全性

    当今的关键基础设施供应商,如5G无线网络、智能电网、数据中心、电缆和运输服务等,对冗余、弹性和安全的精确授时和同步解决方案有着基本需求。Microchip (美国微芯科技公司)今日宣布推出2.2版TimeProviderÒ 4100主时钟,除支持多频段全球导航卫星系统(GNSS)接收器和增强安全性以确保始终在线的精确授时和同步外,还引入了创新的冗余架构以提供全新的弹性水平。 冗余是基础设施供应商确保服务不中断的关键。尽管采用了成本高昂的模块化架构,但基础设施部署之前还得依靠硬件冗余来避免服务中断。Microchip的2.2版TimeProvider 4100主时钟通过软件实现冗余,在不牺牲端口的情况下,实现灵活部署,从而降低硬件成本。 此外,2.2 版TimeProvider 4100主时钟通过支持新的 GNSS 多频段、多星座接收器,提高了弹性水平,以防止因空间天气、太阳事件和其他可能影响关键基础设施服务的干扰而导致时间延迟。多频段GNSS对于最高级别的精度尤其重要,包括主参考时间时钟B级(PRTC-B)(40纳秒)和增强型主参考时间时钟(ePRTC)(30纳秒)。 Microchip全新2.2版TimeProvider 4100主时钟提供了针对整个技术组合的安全解决方案,增加了对RADIUS和TACACS+的支持以及新的抗干扰和反欺骗功能。 Microchip频率和时间业务部副总裁兼总经理Randy Brudzinski表示:“弹性、冗余和安全的精确授时和同步解决方案是降低关键基础设施安全风险的必要条件。最新版本的主时钟产品带来创新的软件冗余,实现了始终在线技术,并支持多频段GNSS,以消除电离层时间误差延迟。它还提供了新的密钥安全、抗干扰和反欺骗功能,确保关键基础设施服务只能由经过授权和认证的人员访问。” 此外,2.2版TimeProvider 4100主时钟提供了一个超级炉控晶体振荡器(OCXO)选配,以增强GNSS中断时的保持能力。 2.2版TimeProvider 4100主时钟产品是带有硬件扩展模块的系列产品,用于传统扇出或支持10 GB以太网的以太网扇出。TimeProvider 4100主时钟可以配置在特定的工作模式下,作为网关时钟、高性能边界时钟或ePRTC。 2.2版TimeProvider 4100主时钟内嵌OCXO、超级OCXO、铷原子钟、现场可编程门阵列(FPGA)、以太网交换机、合成器和清洗振荡器在内的其他Microchip技术。 TimeProvider 4100是Microchip虚拟主参考时钟(vPRTC)产品组合之一,旨在提供端到端精确时间和同步解决方案。该产品组合包括用于频率和时间源的铯原子钟、用于安全的BlueSky™ GNSS防火墙、TimeProvider 4100高性能边界时钟和TimeProvider 4100网关时钟,以及管理所有Microchip定时产品的端到端精确时间架构的TimePictra软件套件 服务与支持 Microchip 的2.2 版 TimeProvider 4100主时钟提供多种软硬件支持选项,包括安装、同步审计、网络工程和全天候全球支持。 供货与定价 2.2 版TimeProvider 4100主时钟现已用于新系统和已部署的系统。有关订购详情,请联系 Microchip 销售人员或渠道合作伙伴。

    Microchip Microchip 主时钟 GNSS

  • 为什么越老实的人,越得不到提拔?

    经常有读者问我: “我是一个老实人,不巴结领导,不讨好同事,不攀附关系,不走动人情,老老实实做人,踏踏实实做事,为什么每次提拔都没有我?” “我只会干活,不会作秀,领导说我是七分做,三分讲。根本竞争不过那些做事一般,但是很会来事儿的年轻人。我这种性格的人,在职场中如何突破?” 职场中的老实人,总是被不公平对待,这种现象非常普遍。 在公司呆了5、6年,依然只是一个可有可无的小角色。许多老实人觉得自己能力强,工作态度也好,理应获得升职加薪的机会。可现实却是,老实人干活最多,拿钱最少,任何好事都轮不到他们。 你也许会说,还不是耽误在不会搞关系、拍马屁上? 其实,老实人得不到提拔的真实原因,远没有那么简单。 为什么领导不愿提拔老实人? 职场中,所谓的老实人就是实在、厚道、话少、不懂变通、喜欢逆来顺受的人。 不瞒你说,老K曾经是个老实人。程序员出身,写了10几年代码,对技术的兴趣大于搞人际关系,所以一直醉心于技术,我能想到最浪漫事,就是一个人整天搞搞电脑。 这种想法很快就被生活花式吊打,在我30多岁的时候,发现这条路走到头了:一个外地人,来上海打拼,学历一般、履历一般、能力一般,别人凭什么给你高薪?别说高薪了,搞技术这一行,到了30几岁的年龄就开始尴尬了,高不成低不就,未来的路真的很迷茫。 于是老K开始改变自己,做好本职工作的同时,积极地帮助同事解决技术难题、主动帮领导分忧、为老板献计献策。 生活就是这样:你笑,全世界都跟着你笑;你哭,全世界只有你一个人哭。 当我从一个只会做技术的老实人,蜕变成为一个职场成年人以后,本该属于我的一切,都一件一件回来了。经历过这些之后,我更能谈谈对于职场老实人的看法。 在领导看来,不愿意提拔老实人的原因有3方面: 第一、不会背锅 老实人往往讲究规矩,一切按规矩办事,自成方圆。明明是别人犯了错,却要我背锅,这是什么道理嘛? 所以,在领导眼里,老实人不识大体、不懂迂回、缺乏人情味,不足以承担重任。 老K年轻的时候,曾在某互联网公司担任技术主管,期间发生了一件事情让我至今难忘。 那一年的年会,安排在苏州的一个度假村里举行,年会结束后,我们200多人,准备乘坐4辆大巴回上海。 酒店前台小姐跑出来,大声对我们说:“301号房有一件女士内衣,是哪位的?来领一下。” 我心想,这前台小姐情商够低的,这种事当众说出来,多尴尬呀。 这时,情商更低的HR主管-张哥,问了一个令他后悔一辈子的问题:“多大的事呀?301号是谁的房间?出来认领一下吧。” 顿时气氛更尴尬了。 住301号房的究竟是谁呢?不是别人,正是我们老板,而那件女士内衣,很明显不是老板娘的,因为她根本没来参加年会...... 不知道的人纷纷在问,谁住301呀?知道的人,都捏了一把冷汗:这下可怎么收场呀。 就在这时,公司销售三部主管--孙姐,对那位前台小姐大喊:“不好意思啊,是我的,谢谢你啦。于是很快接过了内衣,往包里塞。” 正当众人一片错愕的时候,孙姐不紧不慢地说:“哦,大家别想歪啦。是这样,我住302,老板的正对面,昨晚我房间洗衣机坏了,就近借了老板房间的洗衣机用,我这马大哈呀,洗完就给忘了,哈哈哈哈。” 老K不由得暗暗佩服孙姐的机敏、识大体。 孙姐虽说长相不难看,但也不是那种容易让男人产生想法的女人,再加上孙姐平时作风朴实,人缘好,有诚信,而且正好住在老板对面。她及时站出来,既保住了老板的颜面,又能够让同事们信服。于是,这件事情很快就过去了,再没有人提起。 直到现在,我也不知道那件内衣究竟是哪位同事的。在那之后,孙姐就获得了晋升,一直到做到公司销售副总。 孙姐不是老实人,老实人在这种场合,大概率是不会站出来化解僵局。讲这个案例,不是想讨论老板的生活作风问题,而是引发你的思考:当集体遭遇危机之时,你将如何自处与应对。 第二、不做坏人 老实人,大都是老好人,不愿意得罪人,不能够跟团队中的坏风气做斗争,提拔他,只会打击整个团队士气,助长歪风、滋生腐败。 阿里巴巴有个柔弱的女子叫蒋芳,是跟随马云最久的创业元老。她的另一面,却是令腐败份子闻风丧胆的阎罗王。 在蒋芳负责廉政合规部期间,包括卫哲、阎利岷等多位集团高管被裁撤,甚至判刑入狱。 她曾因为阿里员工贪污腐败,激动得大爆粗口:“真是TMD太气人了!现在我特庆幸调我来管这个业务,当年大家好不容易做起来的事业,炸掉也不能毁在一帮骗子手里!” 蒋芳不是老实人,面对贪污腐败,她不会息事宁人,选择没看见。相反,她要站出来做“坏”人,惩奸除恶、弘扬正气。 第三、不懂变通 老实人一根筋、很轴、不懂得变通。 老K作为一名曾经的老实人,刚工作的时候,也是个钢铁直男。 作为一名职场新人,免不了要帮领导打打杂。有一次,我的领导要陪同老板出差,因为行程是临时安排的,要先自己订票。 于是,领导把订票的任务交给我,交代我订两张卧铺。在那个年代,还不能通过App来订票,只能打电话给代理进行预定。 代理在电话里说,卧铺票只有上铺了,没有中、下铺。我想,硬卧上铺也行吧,我上学的时候都是坐硬座整整一天才到家的,这半天车程应该没啥,至少是个卧铺嘛。 当我把两张上铺的硬卧车票交给领导时,他的脸都绿了,压着一肚子怒火说: “你是想让我和老板坐8个小时的硬卧上铺,去拜访客户是吗?我倒是没什么问题,可老板是位身价上亿、60多岁的老人,你是想让他在休息不好的情况下,去跟客户把上千万的合同谈崩掉是吗?” 当时我那个羞愧啊,心想,怎么就不多打个电话确认一下呢?火车实在没有,换其他的交通工具也行啊,自作聪明结果误了事。 这件事给了我深刻的教训,影响着我后来的行事风格,凡事多请示多确认、要换位思考、要懂得变通。 领导提拔下属的本质是什么? 有些事情做得,说不得,比如领导提拔下属这件事。职场当中不会有哪个领导愿意对你说出真相。老K作为过来人,可以毫无保留的告诉你,领导提拔下属的本质,其实是个站队的问题。 领导在提拔一个人的时候,他首先考虑的是,这个人是不是自己人?对自己到底顺不顺从?关键时刻能不能站在自己这一边? 因为提拔一个人,就会赋予一个人更多的职权,一旦有了职权,就有形成自己的嫡系部队,如果这个人不是自己的心腹,那么提拔他相当于在自己身边安放了一颗定时炸弹,在关键时刻引爆就会要了领导的命。 领导提拔下属,究竟是能力重要,还是关系重要?其实,这不是一个二选一的问题,在职场中,能力和关系都同样重要。但更多的时候,关系大过能力! 职场中的老实人,该怎么办? 讲完了领导不愿提拔老实人的原因,再来讲讲职场中的老实人应该怎么办?老K写这篇文章的初衷并不是想制造焦虑,而是为你提供一些有价值观点,引发你的思考,进而改变你的行动。以下三个方面,供你借鉴。 第一,提升气场 老实人通常性格内向、做事低调、胆小怕事。想在领导和同事面前改变这一印象,不容易。 因为,人类交往常常是带有偏见的,第一印象很重要,一旦建立了印象,就相当于给这个人打了一个标签,想要在后面的交往当中更换这个标签,是很难的。 老实人,首先要提升气场,给人一种有担当、有魄力、承诺必达的印象。 在老K的职业生涯中,有过一次彻底改变大家印象的经历: 老K在之前的公司里,给大家的印象是温文尔雅、老好人、优柔寡断。可想而知,这样的印象对于职场发展是不利的,领导会认为你的推动力不够、缺乏激情。 直到有一次高层会议,我的表现令在场的所有人诧异,继而改变了对我固有的看法,觉得我是个有决断力、敢做敢当的人。 我究竟做了什么呢?我在那个会议上,就某个创新业务发表了自己的看法,并且跟某位高管展开了激烈的争论,情绪激动之下,我当众拍了桌子,表明自己的决心和态度。 我的表现一改往常温顺柔软的性格,这种反差极具震撼力。在那个会议之后,老板重新考虑了我的建议,并且最终得到采纳,后来证明自己的判断是正确的,帮助公司少走了弯路。在这之后不久,我被提拔晋升,参与了更多的企业决策。 你不必学老K去跟老板拍桌子,而是要有意识的提升自己气场,在合适的时间、合适的场合,要适当地展示自己的魄力。 第二,学会做人 中国是一个人情社会,讲关系。先做人后做事,先有交情才有生意,这是中国人的为人处事方式。 职场也是一样的,职场就是江湖,你要融入圈子、跟同事打成一片,注重人情来往,建立自己的关系网。 老K获得晋升的时候,提名我的是我直属上司,三位副总级别的面试官,一位跟我是同一家高尔夫球会会员,经常一起打球;另一位,我曾放弃自己的假期,帮他解决了项目上的技术难题;最后一位是大我4届的大学师兄。 这样一场晋升面试,想不通过也挺有难度的吧。所以,平时的交际非常重要,每一次工作中的交集,就是你打造自己这张“金字招牌”的绝佳机会,都要精心准备、认真对待。 第三,学会表现 大一点的公司上万人,小一点的也有几十人、上百人,领导的精力毕竟是有限的,不可能对下属的工作一个个过问。你不主动秀出自己,还指望领导专门跑到巷子里,喝你这坛老酒吗? 项目做好了固然是皆大欢喜,但是项目做不好,如果你汇报勤勉、方法得当,也能够给领导留下一个好印象。 常常听到领导这样说:“虽然项目没有达到预期,但是还是有收获的,我们在这次项目开展过程中,锻炼了队伍,也发现了几个能力和态度都不错的人才。” 所以,不要以为项目做成功了,你就一定会得到奖励和提拔。也不要以为项目做砸了,你就一点机会都没有了。人在做,领导在看呢。 尝尽人生百味,依然相信人间值得! 职场是一个局,有时候你要做局,有时候你要破局;有时候明知身处局中,你也要默默地做好一个棋子,因为那是领导的局;必要的时候你要牺牲自己的利益,这叫顾全大局。 文章有点长,能够读到这里,说明你是个对自己的成长有所期待的人。相信这篇文章会对你有帮助,因为这些都是我将近20年职场经验的总结。 老读者知道,老K从一名普通程序员做到上市公司技术高管,可以说是取得了世俗意义上的成功了。我年轻时候的偶像刘德华先生说过:“学到就要教人,赚到了就要分人。” 我会把自己的职场经验、人生感悟陆续分享到公众号里,大家记得关注。 最后,愿每个老实人,都能够被岁月温柔以待。尝尽人生百味,依然相信人间值得!

    架构师社区 职场 提拔 老实人

  • 漫画:AOP 面试造火箭事件始末

    这是一个困扰我司由来已久的难题,Dubbo 了解过吧,对外提供的服务可能有多个方法,一般我们为了不给调用方埋坑,会在每个方法里把所有异常都 catch 住,只返回一个 result,调用方会根据这个 result 里的 success 判断此次调用是否成功,举个例子 public class ServiceResultTO<T> extends Serializable { private static final long serialVersionUID = xxx; private Boolean success; private String message; private T data; } public interface TestService { ServiceResultTOtest(); } public class TestServiceImpl implements TestService { @Override public ServiceResultTOtest() { try { // 此处写服务里的执行逻辑 return ServiceResultTO.buildSuccess(Boolean.TRUE);        } catch(Exception e) { return ServiceResultTO.buildFailed(Boolean.FALSE, "执行失败");                    }    } } 比如现在以上这样的 dubbo 服务(TestService),它有一个 test 方法,为了执行正常逻辑时出现异常,我们在此方法执行逻辑外包了一层「try... catch...」如果只有一个 test 方法,这样做当然没问题,但问题是在工程里我们一般要要提供几十上百个 service,每个 service 有几十个像 test 这样的方法,如果每个方法都要在执行的时候包一层 「try ...catch...」,虽然可行,但代码会比较丑陋,可读性也比较差,你能想想办法改进一下吗? 既然是用切面解决的,我先解释下什么是切面。我们知道,面向对象将程序抽象成多个层次的对象,每个对象负责不同的模块,这样的话各个对象分工明确,各司其职,也不互相藕合,确实有力地促进了工程开发与分工协作,但是新的问题来了,不同的模块(对象)间有时会出现公共的行为,这种公共的行为很难通过继承的方式来实现,如果用工具类的话也不利于维护,代码也显得异常繁琐。切面(AOP)的引入就是为了解决这类问题而生的,它要达到的效果是保证开发者在不修改源代码的前提下,为系统中不同的业务组件添加某些通用功能。 举个例子来说说 比如上面这个例子,三个 service 对象执行过程中都存在安全,事务,缓存,性能等相同行为,这些相同的行为显然应该在同一个地方管理,有人说我可以写一个统一的工具类,在这些对象的方法前/后都嵌入此工具类,那问题来了,这些行为都属于业务无关的,使用工具类嵌入的方式导致与业务代码紧藕合,很不合工程规范,代码可维护性极差!切面就是为了解决此类问题应运而生的,能做到相同功能的统一管理,对业务代码无侵入 以性能为例,这些对象负责的模块存在哪些相似的功能呢 比如说吧,每个 service 都有不同的方法,我想统计每个方法的执行时间,如果不用切面你需要在每个方法的首尾计算下时间,然后相减 如果我要统计每一个 service 中每个方法的执行时间可想而知不用切面的话就得在每个方法的首尾都加上类似上述的逻辑,显然这样的代码可维护性是非常差的,这还只是统计时间,如果此方法又要加上事务,风控等,是不是也得在方法首尾加上事务开始,回滚等代码,可想而知业务代码与非业务代码严重藕合,这样的实现方式对工程是一种灾难,是不能接受的! 那如果用切面该怎么做呢 在说解决方案前,首先我们要看下与切面相关的几个定义JoinPoint: 程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,或者是执行方法中异常抛出时,也可以是属性被修改时等时机,在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的 Pointcut: JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,这就需要某些条件来筛选出那些需要被织入的 JoinPoint,Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述) 来定位到匹配的 joinpoint Advice:  代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,主要有五个织入时机 Before Advice: 在 JoinPoints 执行前织入 After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入) After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入) After throwing advice: 方法执行过程中抛出异常后织入 Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了 综上所述,切面(Aspect)我们可以认为就是 pointcut 和 advice,pointcut 指定了哪些 joinpoint 可以被织入,而 advice 则指定了在这些 joinpoint 上的代码织入时机与逻辑 画外音:织入(weaving),将切面作用于委托类对象以创建 adviced object 的过程(即代理,下文会提) 列了一大堆概念真让人生气,请用你奶奶都能听得懂的语言来解释一下这些概念! 把技术解释得让非技术的人也听懂才叫本事,这才说明你真的懂了。 这也难不倒我,比如在餐馆里点菜,菜单有 10 个菜,这 10 个菜就是 JoinPoint,但我只点了带有萝卜名字的菜,那么带有萝卜名字这个条件就是针对 JoinPoint(10 个菜)的筛选条件,即 pointcut,最终只有胡萝卜,白萝卜这两个 JoinPoint 满足条件,然后我们就可以在吃胡萝卜前洗手(before advice),或吃胡萝卜后买单(after advice),也可以统计吃胡萝卜的时间(around advice),这些洗手,买单,统计时间的动作都是与吃萝卜这个业务动作解藕的,都是统一写在 advice 的逻辑里 能否用程序实现一下,talk is cheap, show me your code! 好嘞,让你看下我的实力 public interface TestService { // 吃萝卜 void eatCarrot(); // 吃蘑菇 void eatMushroom(); // 吃白菜 void eatCabbage(); } @Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() {        System.out.println("吃萝卜");    } @Override public void eatMushroom() {        System.out.println("吃蘑菇");    } @Override public void eatCabbage() {        System.out.println("吃白菜");    } } 假设有以上 TestService, 实现了吃萝卜,吃蘑菇,吃白菜三个方法,这三个方法都用切面织入,所以它们都是 joinpoints,但现在我只想对吃萝卜这个 joinpoints 前后织入 advice,该怎么办呢,首先当然要声明 pointcut 表达式,这个表达式表明只想织入吃萝卜这个 joinpoint,指明了之后再让 advice 应用于此 pointcut 不就完了,比如我想在吃萝卜前洗手,吃萝卜后买单,可以写出如下切面逻辑 @Aspect @Component public class TestAdvice { // 1. 定义 PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中织入增强逻辑 @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {        System.out.println("吃萝卜前洗手"); //  原来的 TestServiceImpl.eatCarrot 逻辑,可视情况决定是否执行 point.proceed();        System.out.println("吃萝后买单");    } } 可以看到通过 AOP 我们巧妙地在方法执行前后执行插入相关的逻辑,对原有执行逻辑无任何侵入! 小子果然有两把刷子,我们 HR 眼光不错,还有一个问题,开头我司的那个难题你用切面又是如何解决的呢。 这就要说到 PointCut 的 AspectJ pointcut expression language 声明式表达式,这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint , 比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被织入,使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码 恩,可以,继续 首先我们先定义一个如下注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface GlobalErrorCatch { } 然后将所有 service 中方法里的 「try... catch...」移除掉,在方法签名上加上上述我们定义好的注解 public class TestServiceImpl implements TestService { @Override @GlobalErrorCatch public ServiceResultTOtest() { // 此处写服务里的执行逻辑 boolean result = xxx; return ServiceResultTO.buildSuccess(result);    } } 然后再指定注解形式的 pointcuts 及 around advice @Aspect @Component public class TestAdvice { // 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)") private void globalCatch(){} // 2. 将 around advice 作用于 globalCatch(){} 此 PointCut @Around("globalCatch()") public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable { try { return point.proceed();        } catch (Exception e) {            System.out.println("执行错误" + e); return ServiceResultTO.buildFailed("系统错误");        }    } } 通过这样的方式,所有标记着 GlobalErrorCatch 注解的方法都会统一在 handlerGlobalResult 方法里执行,我们就可以在这个方法里统一 catch 住异常,所有 service 方法中又长又臭的 「try...catch...」全部干掉,真香! 按照大佬提供的思路,我首先打印了 TestServiceImp 这个 bean 所属的类 @Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() {        System.out.println("吃萝卜");    } } @Aspect @Component public class TestAdvice { // 1. 定义 PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. 定义应用于 PointCut 的 advice, 这里我们使用 around advice @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { // 省略相关逻辑 } } @SpringBootApplication @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) {        ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);        TestService testService = context.getBean(TestService.class);        System.out.println("testService = " + testService.getClass());    } } 打印后我果然发现了端倪,这个 bean 的 class 居然不是 TestServiceImpl!而是com.example.demo.impl.TestServiceImpl EnhancerBySpringCGLIB$$705c68c7! 果然有长进,继续说,为啥会生成这样一个类 我们注意到类名中有一个 EnhancerBySpringCGLIB ,注意 CGLiB,这个类就是通过它生成的动态代理 打住,先不要说动态代理,先谈谈啥是代理吧 代理在生活中随处可见,比如说我要买房,我一般不会直接和卖家对接,一般会和中介打交道,中介就是代理,卖家就是目标对象,我就是调用者,代理不仅实现了目标对象的行为(帮目标对象卖房),还可以添加上自己的动作(收保证金,签合同等),用 UML 图来表示就是下面这样Client 是直接和 Proxy 打交道的,Proxy 是 Client 要真正调用的 RealSubject 的代理,它确实执行了 RealSubject 的 request 方法,不过在这个执行前后 Proxy 也加上了额外的 PreRequest(),afterRequest() 方法,注意 Proxy 和 RealSubject 都实现了 Subject 这个接口,这样在 Client 看起来调用谁是没有什么分别的(面向接口编程,对调用方无感,因为实现的接口方法是一样的),Proxy 通过其属性持有真正要代理的目标对象(RealSubject)以达到既能调用目标对象的方法也能在方法前后注入其它逻辑的目的 听得我要睡着了,根据这个 UML 来写下相应的实现类吧 没问题,不过在此之前我要先介绍一下代理的类型,代理主要分为两种类型:静态代理和动态代理,动态代理又有 JDK 代理和 CGLib 代理两种,我先解释下静态和动态的含义 好小子,逻辑清晰,继续吧 要理解静态和动态这两个含义,我们首先需要理解一下 Java 程序的运行机制首先 Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,连接,初始化成 Java 类型,可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成 好,那你写下静态代理吧 嘿嘿按这张 UML 类库依葫芦画瓢,傻瓜也会 public interface Subject { public void request(); } public class RealSubject implements Subject { @Override public void request() { // 卖房 System.out.println("卖房");    } } public class Proxy implements Subject { private RealSubject realSubject; public Proxy(RealSubject subject) { this.realSubject = subject;    } @Override public void request() { // 执行代理逻辑 System.out.println("卖房前"); // 执行目标对象方法 realSubject.request(); // 执行代理逻辑 System.out.println("卖房后");    } public static void main(String[] args) { // 被代理对象 RealSubject subject = new RealSubject(); // 代理 Proxy proxy = new Proxy(subject); // 代理请求 proxy.request();    } } 哟哟哟,"傻瓜也会",看把你能的,那你说下静态代理有啥劣势 静态代理主要有两大劣势 代理类只代理一个委托类(其实可以代理多个,但不符合单一职责原则),也就意味着如果要代理多个委托类,就要写多个代理(别忘了静态代理在编译前必须确定) 第一点还不是致命的,再考虑这样一种场景:如果每个委托类的每个方法都要被织入同样的逻辑,比如说我要计算前文提到的每个委托类每个方法的耗时,就要在方法开始前,开始后分别织入计算时间的代码,那就算用代理类,它的方法也有无数这种重复的计算时间的代码 回答的不错,那该怎么改进 嘿嘿,这就要提到动态代理了,静态代理的这些劣势主要是是因为在编译前这些代理类是确定的,如果这些代理类是动态生成的呢,是不是可以省略一大堆代理的代码。 给你 5 分钟你先写一下 JDK 的动态代理并解释其原理 动态代理分为 JDK 提供的动态代理和 Spring AOP 用到的 CGLib 生成的代理,我们先看下 JDK 提供的动态代理该怎么写 这是代码 // 委托类 public class RealSubject implements Subject { @Override public void request() { // 卖房 System.out.println("卖房");    } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private Object target;// 维护一个目标对象 public ProxyFactory(Object target) { this.target = target;    } // 为目标对象生成代理对象 public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        System.out.println("计算开始时间"); // 执行目标对象方法 method.invoke(target, args);                        System.out.println("计算结束时间"); return null;                    }                });    } public static void main(String[] args) {        RealSubject realSubject = new RealSubject();        System.out.println(realSubject.getClass());        Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance();        System.out.println(subject.getClass());        subject.request();    } }``` 打印结果如下: ```shell 原始类:class com.example.demo.proxy.staticproxy.RealSubject 代理类:class com.sun.proxy.$Proxy0 计算开始时间 卖房 计算结束时间 我们注意到代理类的 class 为 com.sun.proxy.$Proxy0,它是如何生成的呢,注意到 Proxy 是在 java.lang.reflect 反射包下的,注意看看 Proxy 的 newProxyInstance 签名 public static Object newProxyInstance(ClassLoader loader,                                          Class[] interfaces,                                          InvocationHandler h); loader: 代理类的ClassLoader,最终读取动态生成的字节码,并转成 java.lang.Class 类的一个实例(即类),通过此实例的 newInstance() 方法就可以创建出代理的对象 interfaces: 委托类实现的接口,JDK 动态代理要实现所有的委托类的接口 InvocationHandler: 委托对象所有接口方法调用都会转发到 InvocationHandler.invoke(),在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的 这样的实现有啥好处呢 由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了。 说得这么厉害,那么 Spring AOP 的实现为啥却不用它呢 JDK 动态代理虽好,但也有弱点,我们注意到 newProxyInstance 的方法签名 public static Object newProxyInstance(ClassLoader loader,                                          Class[] interfaces,                                          InvocationHandler h); 注意第二个参数 Interfaces 是委托类的接口,是必传的, JDK 动态代理是通过与委托类实现同样的接口,然后在实现的接口方法里进行增强来实现的,这就意味着如果要用 JDK 代理,委托类必须实现接口,这样的实现方式看起来有点蠢,更好的方式是什么呢,直接继承自委托类不就行了,这样委托类的逻辑不需要做任何改动,CGlib 就是这么做的 回答得不错,接下来谈谈 CGLib 动态代理吧 好嘞,开头我们提到的 AOP 就是用的 CGLib 的形式来生成的,JDK 动态代理使用 Proxy 来创建代理类,增强逻辑写在 InvocationHandler.invoke() 里,CGlib 动态代理也提供了类似的  Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的 public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {        System.out.println("目标类增强前!!!"); //注意这里的方法调用,不是用反射哦!!! Object object = proxy.invokeSuper(obj, args);        System.out.println("目标类增强后!!!"); return object;    } } public class CGlibProxy { public static void main(String[] args) { //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer = new Enhancer(); //设置目标类的字节码文件 enhancer.setSuperclass(RealSubject.class); //设置回调函数 enhancer.setCallback(new MyMethodInterceptor()); //这里的creat方法就是正式创建代理类 RealSubject proxyDog = (RealSubject) enhancer.create(); //调用代理类的eat方法 proxyDog.request();    } } 打印如下 代理类:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5 目标类增强前!!! 卖房 目标类增强后!!! 可以看到主要就是利用 Enhancer 这个类来设置委托类与方法拦截器,这样委托类的所有非 final 方法就能被方法拦截器拦截,从而在拦截器里实现增强 底层实现原理是啥 之前也说了它是通过继承自委托类,重写委托类的非 final 方法(final 方法不能重载),并在方法里调用委托类的方法来实现代码增强的,它的实现大概是这样 public class RealSubject { @Override public void request() { // 卖房 System.out.println("卖房");    } } /** 生成的动态代理类(简化版)**/ public class RealSubject$$EnhancerByCGLIB$$889898c5 extends RealSubject { @Override public void request() {        System.out.println("增强前"); super.request();        System.out.println("增强后");    } } 可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,所以 Spring AOP 最终使用了 CGlib 来生成动态代理 CGlib 动态代理使用上有啥限制吗 第一点之前已经已经说了,只能代理委托类中任意的非 final 的方法,另外它是通过继承自委托类来生成代理的,所以如果委托类是 final 的,就无法被代理了(final 类不能被继承) 小伙子,这次确实可以看出你作了非常充分的准备,不过你答的这些网上都能搜到答案,为了防止一些候选人背书本,我这里还有最后一个问题:JDK 动态代理的拦截对象是通过反射的机制来调用被拦截方法的,CGlib 呢,它通过什么机制来提升了方法的调用效率。 嘿嘿,我猜到了你不知道,我告诉你吧,由于反射的效率比较低,所以 CGlib 采用了FastClass 的机制来实现对被拦截方法的调用。FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,建议参考下https://www.cnblogs.com/cruze/p/3865180.html这个链接好好学学 还有一个问题,我们通过打印类名的方式知道了 cglib 生成了 RealSubject EnhancerByCGLIB$$889898c5 这样的动态代理,那么有反编译过它的 class 文件来了解 cglib 代理类的生成规则吗 也在参考链接里,既然出来面试,对每个技术点都要深挖才行,像 Redis, MQ 这些中间件等平时只会用是不行的,对这些技术一定要做到原理级别的了解,鉴于你最后两题没答出来,我认为你造火箭能力还有待提高,先回去等通知吧 后记 AOP 是 Spring 一个非常重要的特性,通过切面编程有效地实现了不同模块相同行为的统一管理,也与业务逻辑实现了有效解藕,善用 AOP 有时候能起到出奇制胜的效果,举一个例子,我们业务中有这样的一个需求,需要在不同模块中一些核心逻辑执行前过一遍风控,风控通过了,这些核心逻辑才能执行,怎么实现呢,你当然可以统一封装一个风控工具类,然后在这些核心逻辑执行前插入风控工具类的代码,但这样的话核心逻辑与非核心逻辑(风控,事务等)就藕合在一起了,更好的方式显然应该用 AOP,使用文中所述的注解 + AOP 的方式,将这些非核心逻辑解藕到切面中执行,让代码的可维护性大大提高了。 篇幅所限,文中没有分析 JDK 和 CGlib 的动态代理生成的实现,不过建议大家有余力的话还是可以看看,尤其是文末的参考链接,生成动态代理主要用到了反射的特性,不过我们知道反射存在一定的性能问题,为了提升性能,底层用了一些比如缓存字节码,FastClass 之类的技术来提升性能,通读源码之后的,对反射的理解也会大大加深。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 Dubbo AOP

  • 硬核操作系统讲解

    1 冯诺伊曼体系 1.1 冯诺伊曼体系简介 现代计算机之父冯诺伊曼最先提出程序存储的思想,并成功将其运用在计算机的设计之中,该思想约定了用二进制进行计算和存储,还定义计算机基本结构为 5 个部分,分别是中央处理器(CPU)、内存、输入设备、输出设备、总线。 存储器:代码跟数据在RAM跟ROM中是线性存储, 数据存储的单位是一个二进制位。最小的存储单位是字节。 总线:总线是用于 CPU 和内存以及其他设备之间的通信,总线主要有三种: 地址总线:用于指定 CPU 将要操作的内存地址。 数据总线:用于读写内存的数据。 控制总线:用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后响应,这时也需要控制总线。 输入/输出设备:输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。比如键盘按键时需要和 CPU 进行交互,这时就需要用到控制总线。 CPU:中央处理器,类比人脑,作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。CPU用寄存器存储计算时所需数据,寄存器一般有三种: 通用寄存器:用来存放需要进行运算的数据,比如需进行加法运算的两个数据。 程序计数器:用来存储 CPU 要执行下一条指令所在的内存地址。 指令寄存器:用来存放程序计数器指向的指令本身。 在冯诺伊曼体系下电脑指令执行的过程: CPU读取程序计数器获得指令内存地址,CPU控制单元操作地址总线从内存地址拿到数据,数据通过数据总线到达CPU被存入指令寄存器。 CPU分析指令寄存器中的指令,如果是计算类型的指令交给逻辑运算单元,如果是存储类型的指令交给控制单元执行。 CPU 执行完指令后程序计数器的值通过自增指向下个指令,比如32位CPU会自增4。 自增后开始顺序执行下一条指令,不断循环执行直到程序结束。 CPU位宽:32位CPU一次可操作计算4个字节,64位CPU一次可操作计算8个字节,这个是硬件级别的。平常我们说的32位或64位操作系统指的是软件级别的,指的是程序中指令多少位。 线路位宽:CPU操作指令数据通过高低电压变化进行数据传输,传输时候可以串行传输,也可以并行传输,多少个并行等于多少个位宽。 1.2 CPU 简介 Central Processing Unit 中央处理器,作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。 CPU CPU核心:一般一个CPU会有多个CPU核心,平常说的多核是指在一枚处理器中集成两个或多个完整的计算引擎。核跟CPU的关系是:核属于CPU的一部分。 寄存器:最靠近CPU对存储单元,32位CPU寄存器可存储4字节,64位寄存器可存储8字节。寄存器访问速度一般是半个CPU时钟周期,属于纳秒级别, L1缓存:每个CPU核心都有,用来缓存数据跟指令,访问空间大小一般在32~256KB,访问速度一般是2~4个CPU时钟周期。 int socket(int domain, int type, int protocal) 上面是socket编程的核心函数,可以指定IPV4或IPV6类型,TCP或UDP类型。比如TCP协议通信的 socket 编程模型如下: Socket编程 服务端和客户端初始化socket,得到文件描述符。 服务端调用bind,将绑定在 IP 地址和端口。 服务端调用listen,进行监听。 服务端调用accept,等待客户端连接。 客户端调用connect,向服务器端的地址和端口发起连接请求。 服务端accept返回用于传输的socket的文件描述符。 客户端调用write写入数据,服务端调用read读取数据。 客户端断开连接时,会调用close,那么服务端read读取数据的时候,就会读取到了EOF,待处理完数据后,服务端调用 close,表示连接关闭。 服务端调用accept时,连接成功会返回一个已完成连接的socket,后续用来传输数据。服务端有俩socket,一个叫作监听socket,一个叫作已完成连接socket。 成功连接建立之后双方开始通过 read 和 write 函数来读写数据。 UDP传输 UDP比较简单,属于类似广播性质的传输,不需要维护连接。但也需要 bind,每次通信时调用 sendto 和 recvfrom 都要传入目标主机的 IP 地址和端口。 3.7 多线程编程 既然多进程开销过大,那平常我们经常使用到的就是多线程编程了。期间可能涉及到内存模型、JMM、Volatile、临界区等等。这些在Java并发编程专栏有讲。 4 文件管理 4.1 VFS 虚拟文件系统 文件系统在操作系统中主要负责将文件数据信息存储到磁盘中,起到持久化文件的作用。文件系统的基本组成单元就是文件,文件组成方式不同就会形成不同的文件系统。 文件系统有很多种而不同的文件系统应用到操作系统后需要提供统一的对外接口,此时用到了一个设计理念没有什么是加一层解决不了的,在用户层跟不同的文件系统之间加入一个虚拟文件系统层Virtual File System。 虚拟文件系统层定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员不需要了解文件系统的工作原理,只需要了解 VFS 提供的统一接口即可。 虚拟文件系统 日常的文件系统一般有如下三种: 磁盘文件系统:就是我们常见的EXT 2/3/4系列。 内存文件系统:数据没存储到磁盘,占用内存数据,比如/sys、/proc。进程中的一些数据映射到/proc中了。 网络文件系统:常见的网盘挂载NFS等,通过访问其他主机数据实现。 4.2 文件组成 以Linux系统为例,在Linux系统中一切皆文件,Linux文件系统会为每个文件分配索引节点 inode跟目录项directory entry来记录文件内容跟目录层次结构。 4.2.1 inode 要理解inode要从文件储存说起。文件存储在硬盘上,硬盘的最小存储单位叫做扇区。每个扇区储存512字节。操作系统读取硬盘的时候,不会一个个扇区的读取,这样效率太低,一般一次性连续读取8个扇区(4KB)来当做一块,这种由多个扇区组成的块,是文件存取的最小单位。 文件数据都储存在块中,我们还必须找到一个地方储存文件的元信息,比如inode编号、文件大小、创建时间、修改时间、磁盘位置、访问权限等。几乎除了文件名以为的所有文件元数据信息都存储在一个叫叫索引节点inode的地方。可通过stat 文件名查看 inode 信息 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件,用户可通过ls -i查看每个文件对应编号。对于系统来说文件名只是inode号码便于识别的别称或者绰号。特殊名字的文件不好删除时可以尝试用inode号删除,移动跟重命名不会导致文件inode变化,当用户尝试根据文件名打开文件时,实际上系统内部将这个过程分成三步: 系统找到这个文件名对应的inode号码。 通过inode号码,获取inode信息,进行权限验证等操作。 根据inode信息,找到文件数据所在的block,读出数据。 需注意 inode也会消耗硬盘空间,硬盘格式化后会被分成超级块、索引节点区和数据块区三个区域: 超级块区:用来存储文件系统的详细信息,比如块大小,块个数等信息。一般文件系统挂载后就会将数据信息同步到内存。 索引节点区:用来存储索引节点 inode  table。每个inode一般为128字节或256字节,一般每1KB或2KB数据就需设置一个inode。一般为了加速查询会把索引数据缓存到内存。 数据块区:真正存储磁盘数据的地方。 buf = mmap(file, len); write(sockfd, buf, len); 5.2.4.3 sendfile Linux 内核版本 2.1版本提供了函数 sendfile()。

    C语言与CPP编程 操作系统 计算机 冯诺伊曼体系

  • 老公趴下!62图给你讲Docker

    这周分享的内容是关于 Docker 的基础,大概的内容分为下面的两个部分,另外还做了个视频,其实这个视频仅仅用来娱乐娱乐而已 前言 第一趴---Docker容器圈简介 Docker容器圈简介 第二趴---Docker基本操作 Docker基本操作 容器圈 容器这个新生事物,现在还可以说是新生事物吗?对于我们学生而言,我觉得没毛病,你说呢? 容器技术可说重塑了整个云计算市场的形态,带动了一批年轻有为的容器技术儿,不过「容器」这个概念是 Docker 公司发明的么,不是,它只是众多 Pass 项目中的最底层,没人关注的那一部分而已。 什么是Pass项目? Pass 项目之所会被很多公司所接受,自然是因为解放了部分开发人员的劳动力,尽快干玩活儿早点下班。其依赖的就是「应用托管」的能力,在电脑上斗过地主的应该知道,托管了以后就会自动出牌,同样的道理,为了尽量的弥补本地和云上的环境差异,就出现了 Pass 开源项目。 举个例子来说,运维人员小仙云上部署一个 Cloud Foundry 项目,开发人员只需要简单的一行代码就可以实现将本地的应用部署到云上 就这样一行代码就实现了将本地应用上传到云上,属实很轻松。 那么这个命令执行的基本原理是怎样的? 实际上,我们可以将其最核心的组件理解为一套应用的打包和分发机制。云上部署的 Cloud Foundry 会为大部分编程语言定义一种打包的格式,当开发人员执行命令的时候,实际上是将可执行文件和启动脚本打包上传到云上的Coulud Foudry 中,然后 Cloud Foundry 通过相应的调度器选择一个虚拟机的 Agent 将压缩包下载后启动 那如何区分虚拟机中的不同应用呢? 虚拟机一般不可能只跑一个应用,因为这样确实也太浪费资源了,我们可以想想,现在手上的电脑,可以用 Vmvare 导入几个虚拟机,所以诸如 Cloud Foundry 通过引入操作系统的 Cgroups 和 Namespace 等机制,从而来为每个应用单独创建一个叫做「沙盒」的隔离环境,然后在这些「沙盒」中启动应用,通过这样的方法就让虚拟机中应用各自互不干扰,让其自由翱翔,至于 Cgroups 和 **Namespace **的实现原理,后续我们再共同的探讨 这里所谓的隔离环境就是「容器」。 那 Docker 和这 Pass 项目的 Cloud Foundry 的容器有啥不一样? 自然不一样,不然现在我们一旦提到容器,想到的不会是 Docker,而是 Cloud Foundry 了吧。Cloud Foundry 的首席产品经理就觉得没什么,毕竟自己放的屁都是香的! 不一样,而且当时还发了一份报告,报告中还写到:“ Docker 不就是使用了 Cgroups 和 Namespace 实现的「沙盒」而已,不用过于关注”。 没想到的是,随后短短的几个月,Docker 项目迅速起飞以至于其他所有 Paas 社区都还没来及反应过来,就已经宣告出局 什么魔力让 Docker 一发不可收拾? 就是提出了镜像的概念。上面我们说过,Paas 提供的一套应用打包的功能,看起很轻松省事,但是一旦使用了Paas,你就要终身服务于它,毕竟他是提供商,是「爸爸」,用户需要为每个版本,每种语言去维护一个包,这个打包的过程是需要多次的尝试,多次试错后,才能摸清本地应用和远端 Paas 的脾气,从而顺利部署。 而 Docker 镜像恰恰就是解决了打包这一根本问题。 什么是Docker镜像? Docker 镜像也是一个压缩包,只是这个压缩包不只是可执行文件,环境部署脚本,它还包含了完整的操作系统。因为大部分的镜像都是基于某个操作系统来构建,所以很轻松的就可以构建本地和远端一样的环境。 这就很牛皮了,如果我们的应用是在 Centos7 上部署,我们只需要将项目环境部署在基于 Centos7 的环境中,然后无论在哪里去解压这个压缩包,都可以保证环境的一致性。在整个过程中,我们根本不需要进行任何的配置,因为这个压缩包可以保证:本地的环境和云端是一致的,这也是 Docker 镜像的精髓 开发者体验到了 Docker 的便利,从而很快宣布 Paas 时代的结束,不过对于大规模应用的部署,Docker 能否实现在当时还是个问号 就在 2014 年的 DockerCon 上,紧接着发布了自研的「Docker swarm」,Docker 就这样 一度奔向高潮,即将就到达了自己梦想之巅。 为什么会推出Docker Swarm? 虽然 Docker 通过「容器」完成了对 Paas 的「降维打击」,但是 Docker 的目的是:如何让更多的开发者将项目部署在自己的项目上,从技术,商业,市场多方位的争取开发者的群体,为此形成自家的 Paas 平台做铺垫 Docker 项目虽然很受欢迎,就目前看来只是一个创建和启动容器的小工具。需要应该清楚的一点是,用户最终部署的还是他们的网站,服务甚至云计算业务。所以推出一个完整的整体对外提供集群管理功能的 Swarm 势在必行,这个项目中的最大亮点即直接使用了 Docker 原生的 API 来完成对集群的管理。 对于单机项目,只需要执行下面一条语句即可实现容器 对于多机的项目,只需要执行 你看,从单机切换到多机,使用的方法也就参数不同而已,所以这样一个原生的「Docker容器集群管理」一发布就受到大家的青睐。随着自身生态的逐渐完善,借助这一波浪潮并通过各种并购来强大自己的平层能力 要说最成功的案例,非 Fig 项目莫属。之所以这么屌,是因为作者提出了「容器编排」的概念。 什么是容器编排? 其实这也不是什么新鲜内容,比如在 Linux 中的 Makefile和常见的 SHELL 脚本,它是一种通过工具或者配置来完成一组虚拟机或者关联资源的定义、配置、创建等工具。 容器的编排是怎么样的呢 我们先以 Fig 为例,假设开发人员小黑,现在要部署一个项目,其中包含了应用容器 A,数据库容器 B,负载容器 C,这个时候 Fig 只需要将三个容器定义在一个配置文件,然后指定他们的关联关系,执行下面的命令即可 当然也可以在 Fig 的配置文件中配置各种容器的副本,然后加上 Swarm 的集群管理功能,这样不就是 Paas 了么。只是这个项目被收购以后,修改名字为 compose 了,后续也会的 compose 进行详细的阐述 就这样一个以「鲸鱼」为商标的 Docker,火遍全球,因为它秉持将「开发者」群体放在食物链的顶端。一分钟实现网站的部署,三分钟搭建集群,这么多年以来,很多后端开发者很少将眼光放在 Linux 技术上,开发者们为了深入的了解 Docker 的技术原理,终于将眼光放入诸如 Cgroups 和 Namespace 技术中。 就在这一时之间,后端及云计算领域的大佬都汇聚于这个「小鲸鱼」的身边。随后到了考虑集群的方案,论集群的管理调度能力,还不得不提 Berkeley 的 Mesos,专注于大数据领域,更加关注的是计算密集型的业务。凭借着它天生的两层调度机制,让它很快发布了一个叫做 Marathon 的项目,这个项目随即成为了 Swarm 的有力竞争对手。 这还没完,说了这么久,还没有提到在基础设施领域翘楚的 Google 公司,是的,同在这一年,宣告了一个叫做 Kubernetes 项目的诞生。 随着 Docker 生态的建立,Docker swarm,Docker compose,Machine 形成了三件套,此时大量围绕Docker 项目的网络,存储,监控都涌现。在令人兴奋的背后,是对它更多的担忧,这主要来源于对 Docker 商业化战略的顾虑,Docker 公司对于 Docker 着绝对的权威并在多个场合直接和谷歌,微软对干 其实在 Docker 兴起的时候,谷歌也开源了一个 Linux 容器:Imctfy,在当时这个项目在 Docker 面前真是弟弟,所以向 Docker 公司表示想合作的意愿,Docker 显然不同意,且在之后的不久自己发布了一个容器运行时的库 Libcontainer,可能由于太急躁,其代码可读性极差,不稳定和频繁的变更,让社区叫苦不迭 为了切割 Docker 项目的话语权,决定成立一个中立的基金会。所以于 2015 年将这个 Libcontainer 捐出,并修改名称为 Runc,然后依据 RunC 项目,制定了一套容器和镜像的标准和规范----OCI 什么是OCI 为了让 Docker 不能太嚣张,其他玩家构建自身平台的时候不依赖于 Docker 项目,提出一个标准和规范----OCI。这一标准并没有改变 Docker 在容器领域一家独大的现状。Google 坐不住了,必须得搞点大招 **Google **给 RedHat 等伙伴打了电话,说咱们共同牵头发起一个基金会-----CNCF。目的很简单,以 kubernetes 为基础,建立一个以由开源基础设置主导,按照独立基金会方式运营的平台级社区,来对抗 Docker 公司为核心的容器商业生态 为了做好这个事儿,CNCF 必须完成两件事儿 必须在编排领取足够的优势 CNCF 必须以 kubernetes 为核心,覆盖更多的场景 CNCF 如何解决第一个问题----编排能力 Swarm 的无缝集成以及 Mesos 的大规模集群的调度管理能力,很明显,如果继续往这两个方向发展,后面的路不一定好走。所以,kubernetes 选择的方式是 Borg,其基础特性是 Google 在容器化基础设施多年来实践的经验,这也正是项目从一开始就避免了和 Swarm ,mesos 社区同质化的重要手段 看似很有技巧,怎么落地? RedHat 正好擅长这玩意呀,它能真正的理解开源社区运作和项目研发真谛的合作伙伴。作为 Docker 一方,主要不管的强调「Docker native」,但是由于 kubernetes 没有跟 Swarm 展开同质化的竞争,所以这个「Docker Native」的说法并没有什么杀伤力。反而其独特的设计理念和号召力,让其构建了一个完全与众不同的容器编排管理生态。 就这样很快就把 Swarm 甩在了身后。随机开始探讨第二个问题,CNCF 添加了一系列容器工具和项目,面对这样的压迫,Docker 在2016年决定放弃现有的 Swarm项目,而是将容器编排等全部内置到 Docker 项目中。 而 kubunetes 的应对方案也蛮有意思,开启「民主化架构」,kubernetes 为开发者暴露可以扩展的插件机制,让用户可以随意的通过植入代码的方式介入到 kubernetes 的每一个阶段,很快,整个容器圈出现了优秀的作品:火热的微服务治理项目 lstio 等 面对 kubernetes 的 强力攻击,Docker 公司不得不面对失败的事实,只好放弃开源社区专注于商业化转型,所以于2017年将容器运行时部分 containerd 捐赠给了 CNCF,从而将 Docker 项目改名为 Moby,然后交给社区维护,于 2017 年,**Docker **公司宣布将在企业版内置 kubernetes 项目,这也标志了 kubernetes「编排之争」的结束 Docker能做什么 Docker 是一个用于开发,发布,运行应用的程序于一体的开放平台。如果我们需要将货物整齐的摆放在船上且互不影响,那么一种可行的方案即通过集装箱进行标准化,我们将各种货品通过集装箱打包,然后统一的放在船上进行运输,Docker其实就是这样一个将各种软件进行打包成集装箱,然后分发。 Docker的安装 Docker 是一个跨平台的解决方案,支持各大平台比如 Centos,Ubuntu等 Linux 发行版。下面讲述的是在Centos 中的使用,安装 卸载当前已经存在的旧版 Docker,执行下面的命令 添加Docker安装源 安装最新版本 如果需要安装指定版本,可以通过下面命令查看版本并选择需要的版本 安装完成,启动Docker 按照国际案例,先跑一个 helloworld 运行上述命令,Docker 首先会检查本地是否有 hello-world 这个镜像,如果发现本地没有这个镜像,Docker 就会去 Docker Hub 官方仓库下载此镜像,然后运行它。最后我们看到该镜像输出 "Hello from Docker!" 并退出。 Docker核心概念 Docker 的操作主要围绕镜像,容器,仓库三大核心概念 什么是镜像? 一句话说即镜像是 Docker 容器启动的先决条件,因为镜像会提供容器运行的一些基础文件和配置文件,是容器启动的基础。说白了,要启动容器,需要镜像来提供一些基础环境。 使用的镜像的方式有哪些? 自定义创建镜像。首先找一个基础镜像,比如此镜像是 Centos,然后在此镜像基础上自定义需要的内容。举个例子,基础镜像为 Centos,先安装 Nginx 服务,然后部署咱们的应用程序,最后做一些自定义的配置,这样一个镜像就完成了,此镜像的操作系统是 Centos,其中包含了 Nginx 服务 从仓库寻找别人已经做好的镜像。直接去 **Docker hub **或者其他公开仓库 下载即可 什么是容器? 容器是镜像的运行实体。镜像是静态的只读文件,可是容器是要运行的,需要可写文件层。所以容器运行着真正的应用进程,所以自然会有创建,运行,停止,暂停和删除五种状态 既然容器是直接运行的运行程序,那它是有自己的命名空间嘛? 容器有自己独立的命名空间和资源限制,意味着在容器内部,你无法看到主机上面的进程,环境变量等信息,这就是容器和物理机上的进程本质区别 什么是仓库? 镜像仓库类似于代码仓库,用来分发和存储镜像,分为公共镜像和私有镜像。Docker hub 是 Docker 的官方公开镜像仓库,很多的官方镜像都可以在上面找到,但是访问很慢,所以可以找国内的镜像源,当然后面我们也会自己搭建一个私有镜像仓库 三者的关系是怎么样的? 上图清晰的展现了镜像是容器的基石,容器是在镜像的基础上创建的。一个镜像可以创建多个容器,仓库用来存放和分发镜像 Docker架构 容器技术的发展可说突飞猛进了,市面上除了 Docker 容器还有 coreos 的 rkt,lxc 等,这么多种容器,是不是需要一个标准呢,不然就太容易乱套了 你可能会说直接把 Docker 作为标准不就好了,但是有这么多相关的容器技术,谁不想吃个好瓜,除此之外,当时的编排的技术也竞争火爆,当时的三主力分别是 Docker Swarm,kubernetes 以及 mesos。作为原生的 Docker Swarm 自然优势明显,但是 kubernetes 不同意啊,它们觉得调度形式太单一了 因此爆发了容器大战,OCI 也就在此出现。 OCI 是开放的容器标准,轻量级开放的治理结构,目前主要有两个标准,分别是容器运行时标准和容器镜像标准 在如此竞争激烈下面,Docker 的架构成为了下面这个样子 Docker 的整体架构为 CS 架构,客户端和服务端两部分组成,客户端发送命令,服务端接受处理指令,其通信的方式有多种,即可以通过 Unix 套接字通信,也可以网络链接远程通信 Docker客户端 我们平时通常使用 Docker 命令行的方式和服务端打交道,其实还可以通过 **REST API **的方式和 Docker 服务端交互,甚至使用各种预言的 sdk 和 Docker 的服务端交互,美滋滋 Docker服务端 Docker 服务端是 Docker 后台服务的总称。其中 Dockerd 是一个非常重要的后台进程,它负责响应并处理Docker 客户端的请求,然后转化为 Docker 的具体操作 Docker 重要的组件 我们去 Docker 默认安装路径先看看有哪些组件 这里主要说明下 runc 和 contained 组件 runc:是一个用来运行容器的轻量级工具 contained:是容器标准化后的产物,从 Dockerd 剥离出来,contained 通过 contained-shim 启动并管理runc,可以说 contained 是真正管理容器的生命周期 通过上图,可以看到,dockerd 通过 gRPC 与 containerd 通信,由于 dockerd 与真正的容器运行时,runC 中间有了 containerd 这一 OCI 标准层,使得 dockerd 可以确保接口向下兼容。 gRPC 是一种远程服务调用。containerd-shim 的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 dockerd 不影响已经启动的容器进程。 docker 各个组件之间的关系 启动一个容器 启动完成,通过下面命令查看 docker 的 pid 此时发现其 pid 为 4428,随后我们查看进程之间的关系 通过 pstree 查看进程之间的关系 注意,docker19 就看不到两者是父子关系了 可以先使用 ps aux | grep contained ,然后使用 pstree 查看 contained 的 pid ,实际上,Docker 启动的时候,contained 就启动了,dockerd 和 contained 一直就存在。当执行了docker run以后,contained 就会创建 contained-shim 充当垫片进程,然后启动容器的真正进程 sleep 3600,这和架构图一致 075528566666 Docker相关组件 docker 对于我们最直观的即 Docker 命令,作为 Docker 客户端的完整实现,通过 Docker 命令来实现所有的 Docker 客户与服务端的通信 Docker 客户端于服务端的交互过程是怎么样的呢 Docker 组件向服务端发送请求后,服务端根据请求执行具体的动作并将结果返回给 Docker,Docker 解析服务端的返回结果,并将结果通过命令行标准输出展示给用户。这样一次完整的客户端服务端请求就完成了 dockerd dockerd 为 Docker 服务端后台的常驻进程,负责接收客户端的请求,处理具体的任务并将结果返回客户端 那么 Docke r客户端采用哪几种方式发送请求 第一种方式:通过 unix 套接字与服务端通信,配置的格式为:unix://socket_path。默认的 dockerd 生成的 socket文件存放在/var/run/docker.sock,此文件只能是 root 用户才能访问,这也是为什么刚安装完 Docker 后只能root 来进行访问操作 第二种方式:采用 TCP 的方式与服务端通信,配置格式为:tcp://host:por,为了保证安全,通常还需要使用TLS认证 第三种方式:通过 fd 文件描述符的方式,配置格式为:fd://这种格式一般用于 systemd 管理的系统中。 docker-init 在 Linux 中,有一个叫做 init 的进程,是所有进程的父进程,用来回收那些没有回收的进程,同样的道理,在容器内部,可以通过加上参数--init的方式,让 1 号进程管理所有的子进程,例如回收僵尸进程 举个例子示范,以镜像 busybox 为例 此时的 1 号进程为为 sh 进程,如果加上 --init 你会发现,此时的 1 号进程为 docker-init,而不是 sh 了 docker-proxy docker-proxy 用来将容器启动的端口映射到主机,方便主机的访问。 假设目前启动一个 nginx 容器并将容器的 80 端口映射到主机的 8080 端口 查看容器 IP 此时使用 ps 查看主机是否有 docker-proxy 进程 可以发现当进行端口映射的时候,docker 为我们创建了一个 docker-proxy 进程,并且通过参数将容器的 IP 和端口传递给 docker-proxy,然后 proxy 通过 iptables 完成 nat 的转发 从最后一句可以看出,当我们主机访问 8080 端口的时候,iptable 将流量会转发给172.17.0.2的 80 ,从而实现主机上直接访问容器的业务 使用 curl 访问一下 nginx 容器 contained组件 containerd contained主要负责容器的生命周期管理,同时还会负责一些其他的功能 主要负责那些功能? 镜像的管理 接收dockerd的请求 管理存储相关资源 管理网络资源 containerd-shim containerd-shim的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim的主要作用是将containerd和真正的容器进程解耦,使用containerd-shim作为容器进程的父进程,从而实现重启containerd不影响已经启动的容器进程。 ctr ctr实际上是containerd-ctr,它是containerd的客户端,主要用来开发和调试,在没有dockerd的环境中,ctr可以充当docker客户端的部分角色,直接向containerd守护进程发送操作容器的请求。 Docker镜像使用 来,继续,我们看看镜像是什么。镜像是一个只读的镜像模版且包含了启动容器所需要的文件结构内容。镜像不包含动态数据,构建完成将不会改变 对于镜像都有哪些操作? 对于镜像的操作分为: 拉取镜像:通过 docker pull 拉取远程仓库的镜像 重命名镜像:通过 docker tag 重命名镜像 查看镜像:通过 docker image ls 查看本地已经存在的镜像 删除镜像:通过 docekr rmi 删除没有用的镜像 构建镜像 第一种是通过 docker build 命令基于 dockerfile 构建镜像,推荐 第二种是通过 docker commit 基于运行的容器提交为镜像 拉取镜像 拉取镜像直接使用 docker pull 命令即可,命令的格式如下 registry 为注册的服务器,docker 默认会从官网 docker.io 上拉取镜像,当然可以将 registry 注册为自己的服务器 repository 为镜像仓库,library 为默认的镜像仓库 image 为镜像的名称 tag 为给镜像打的标签 现在举个例子,这里有个镜像叫做 busybox,这个镜像集成了上百个常用的 Linux 命令,可以通过这个镜像方便快捷的查找生产环境中的问题,下面我们一起操作一波 docker pull busybox 首先会在本地镜像库查找,如果不存在本地库则直接去官网拉取镜像。拉取完镜像后随即查看镜像 查看镜像---docker images 如果要查看指定的镜像,则使用docker image ls命令进一步查询 重命名镜像采用打标签的方式重命名,格式如下 我们仔细观察这两个镜像,就会发现这两个镜像的 IMAGE ID其实是一样的,这是什么原因呢 实际上他们都是指向的同一个镜像文件,只不过其别名不一样而已,如果此时不想要mybox镜像,想删除这个镜像 使用 docker rmi 删除镜像 此时再次使用 docker images 查看确实删除了 如何自己构建自己镜像呢 之前说过,有两种方式,一种是通过 docker commit 的方式,一种是 docker build 的方式。首先看看使用容器提交镜像的方式 此时启动了一个busybox容器并进入到容器,并在容器中创建一个文件,并写入内容 此时就在当前目录下创建了一个hello.txt文件并写入了内容。现在新建另外一个窗口,然后提交为一个镜像 然后使用 docker image ls 查看发现确实生成了镜像 然后我们再看看使用 dockerfile 的方式 dockerfile的每一行的命令都会生成独立的镜像层并拥有唯一的id dockerfile命令是完全透明的,通过查看dockerfile的内容,就可以知道镜像是怎么一步步构建的 dockerfile为纯文本,方便做版本控制 先看看都有哪些命令 这么多,不存在的,我们先看一个dockerfile就知道如何用了 首先第一行表示基于什么镜像构建 第二行是拷贝文件nginx。repo到容器内的/etc/yum.repos.d 第三行为容器中运行yum install命令,安装nginx命令到容器 第四行为生命容器使用 80 端口对外开放 第五行定义容器启动时的环境变量HOST=mynginx,容器启动后可以获取到环境变量HOST的值为mynginx。 第六行定义容器的启动命令,命令格式为json数组。这里设置了容器的启动命令为nginx,并且添加了nginx的启动参数-g 'daemon off;',使得nginx以前台的方式启动。 基本操作已经会了,现在我们看看镜像的实现原理 第一行:创建一个busybox镜像层 第二行:拷贝本机test文件到镜像内 第三行 在tmp文件夹创建一个目录testdir 为了清楚的看见镜像的存储结构,通过docker build构建镜像 因为我的docker使用的是overlay2文件驱动,所以进入到/var/lib/docker/overlay2,使用tree查看 可以清楚的看到,dockerfile的每一行命令都会生成一个镜像层 Docker容器操作 我们通过一个镜像可以轻松的创建一个容器,一个镜像可以有多个容器,在运行容器的时候,实际上是在容器内部创建了这个文件系统的读写副本,如下图所示 容器的生命周期是怎么样的? 容器的生命周期一共有五个状态分别为 created  初建状态 running  运行状态 stopped 停止状态 opaused 暂停状态 deleted  删除状态 通过 docker cretate 进入容器的初建状态,然后通过 docker start 进入运行状态,通过 docker stop 进入停止状态,运行状态的容器可以通过 docker pause 让其变为暂停状态,为了详细的查看这些过程,我们实操一下 创建并启动容器 通过 docker create 创建的容器处于停止的状态,使用 docker start busybox 进入启动状态 当使用 docker run 创建并启动容器的时候,docker 后台的执行逻辑为 首先检查本地是否有 busybox 镜像,不存在则取 dockerhub 中拉取 使用 busybox 镜像启动一个容器 分配文件系统,并在镜像的只读层外创建一个读写层 从 docker ip 池分配个 ip 给容器 运行镜像 可以进入交互模式么 同时使用-it参数可以让我们进入交互模式,容器内部和主机是完全隔离的。另外由于此时的sh为 1 号进程,所以如果通过exit退出sh,那么容器也就退出,所以对于容器而言,杀死容器中的主进程,那么容器也就会被杀死 通过 docker stop 停止容器,其原理是给运行中的容器给sigterm信号,如果容器为 1 号进程接受并处理sigterm,则等待 1 号进程处理完毕后就退出,如果等待一段时间后还是没有处理,则会通过发送 sigkill 命令强制终止容器 如何进入容器? 想要进入容器,有三种方案,分别是docker attach,docker exec,nsenter等 使用 docker attach 方式进入容器 通过docker ps -a查看当前的进程信息 可是当我们在进行窗口进行 docker attach 的时候,这个命令就不好用了,所以使用 docker exec 的方式 使用docker exec进入容器 奇怪的发现居然是两个sh进程,主要是因为,当我们使用docker exec方式进入容器的时候,会单独启动一个sh进程,此时的多个窗口都是独立且不受干扰,也是非常常用的方式 删除容器 现在基本上知道了如何创建,启动容器,那么怎么删除容器呢 使用docker rm的方式删除容器 如果此时,容器正在运行,那么需要添加-f的方式停止正在运行的容器 如果想要导出容器怎么操作呢 这简单,不过在导出之前先进入容器创建一个文件 然后导出为文件 此时会在当前目录生成一个busybox.tar文件,此时就可以将其拷贝到其他的机器上使用 那如何导入容器呢 通过docker import的方式导入,然后使用docker run启动就完成了容器的迁移 此时容器名称为busybox:test,然后我们使用docker run启动并进入容器 此时发现之前在/tmp创建的目录也被迁移了过来 仓库 容器的基本操作应该都会了,那么我们应该如何去存储和分发这些镜像,这就需要介绍下仓库; 我们可以使用共有镜像仓库分发,也可以搭建私有的仓库 仓库是啥玩意 钱钱仓库放钱,这个仓库放镜像。Github放代码,我们理解镜像的仓库可以联想Github仓库。 在学习的过程中,不太能区分注册服务器和仓库的关系。注册服务器其实是用来存放仓库的实际机器,而仓库我们可以将其理解为具体的项目或者目录。一个注册服务器可以存放多个仓库,每个仓库可以存放多个镜像 公有仓库 Docker hub 是当前全球最大的镜像市场,差不多超过 10w 个容器镜像,大部分操作系统镜像都来自于此。 如何使用公共镜像仓库和存储镜像 注册 Docker hub 创建仓库 实战镜像推送到仓库 此时假设我的账户是 xiaolantest,创建一个 busybox 的仓库,随后将镜像推送到仓库中。 第一步:拉取 busybox 镜像 第二步:推送镜像之前先登录镜像服务器(注意用户名密码哦),出现 login Succeeded表示登录成功 第三步:推送之前还要做一件事,重新对镜像命名,这样测能正确的推动到自己创建的仓库中 第四步:docker push 到仓库中 私有仓库 Docker 官方提供了开源的镜像仓库 Distribution,镜像存放于 Docker hub 的 Registry中 启动本地镜像仓库 使用 docker ps查看启动的容器 重命名镜像 此时 Docker 为busybox镜像创建了一个别名localhost:5000/busybox,localhost:5000为主机名和端口,Docker 将会把镜像推送到这个地址。 推送镜像到本地仓库 删除之前存在的镜像 此时,我们验证一下从本地镜像仓库拉取镜像。首先,我们删除本地的busybox和localhost:5000/busybox镜像。 查看当前本地镜像 可以看到此时本地已经没有busybox这个镜像了。下面,我们从本地镜像仓库拉取busybox镜像: 随后再使用docker image ls busybox命令,这时可以看到我们已经成功从私有镜像仓库拉取busybox镜像到本地了 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 容器 Docker Pass项目

  • 爬取了1W个字节跳动岗位信息,我发现了什么?

    前言 过了春节,春招应该就正式开始了,很多小伙伴应该已经提前准备起来了。最近在家闲来无事,突发奇想自己想看看字节跳动的岗位需求,毕竟字节这两年发展的势头确实非常猛,不少小伙伴都想加入。 正文 字节跳动公开的岗位信息都发布在自己的官网上,页面上显示有 1W+ 的岗位,看来发展确实好需求量这么大。 但是这样看起来肯定非常辛苦,所以我就写了一个脚本抓取了上面的数据。代码就不展示了,一共 100 多行,有兴趣的同学可以后台回复关键字【字节】自己拿源码跑一下。 爬取到的数据我保存到了一个 CSV 文件中,成功抓到了 1W 条数据左右。 第二张图是岗位城市的分布。不出所料,北京作为字节的大本营,遥遥领先于其他的城市。 值得注意的是,新加坡、山景城、都柏林加在一起有六百人的样子。

    架构师社区 互联网 字节跳动

  • 只要努力搞,没有KPI搞不垮的团队?

    KPI考核,近年来备受争议。 索尼前董事天外伺朗,曾经写文章批判:“绩效主义毁了索尼!” 魏则西事件后,李彦宏曾说:“短期KPI的追逐,我们的价值观被挤压变形了,业绩增长凌驾于用户体验......我们与用户渐行渐远。” 然而,同样采用KPI考核的阿里巴巴,市值直奔47000亿;华为一跃成为科技新贵、科技兴邦的典范。 KPI究竟是好是坏呢?其实,这个问题本身就错了。任何一个管理工具被发明出来,它的初衷都不是要毁掉一个团队,KPI也不例外。 俗话说得好,只要努力搞,没有KPI搞不垮的团队。我们来聊聊,那些被KPI搞垮的团队: 1、管理层不深入业务,只盯KPI 老板们听说KPI考核的神奇功效,都高兴坏了。 理想中,KPI能够把企业的目标层层分解,落实到部门、到个人,驱动员工的自主性。这可把老板们高兴坏了。 开公司嘛,一定要分工明确:我们收拾公司的这摊子事,老板收拾我们。 老板下达完KPI后就开始放飞自我了,辛苦创业这么多年也该歇歇了,反正KPI考核这么牛逼,搬个板凳坐着,等结果就行了呗。 管理层也不是什么好鸟:老板要KPI是吧?咱完成KPI就行啦,别整些没用的。 老板考核日活?我就开发机器人去刷日活、搞低俗内容吸引流量。 老板考核新客?那就让老客户注销账号,去申请新账号。 于是,年底个个绩效100分,公司的营收和毛利却在下降。 老板自从上了KPI之后,就脱离了业务一线,根本察觉不出问题,当他意识到问题的时候已经晚了。 2、KPI跟绩效强绑定 只要KPI跟员工绩效强绑定,那么离弄虚作假就不远了,因为数字的东西,都是可以被玩弄的。 老板以为,世界上99%的事情都能用钱解决,但是他不知道,解决剩下的1%,需要更多的钱。KPI就是个黑洞,关键是钱投进去,一点效果都没有。 什么是KPI考核?就是企业将短期财务指标,如利润、销售收入,作为公司的关键绩效指标来考核公司高管,并层层向下分解,直到一线员工。 指标完成的结果要强制排名,排名结果和个人奖金紧密挂钩,并直接影响基本工资调整和职位晋升。 指标完成不好、排名靠后的员工,会被列入绩效改进计划,乃至解雇。员工在焦虑、恐惧心理驱使下,不择手段地完成头顶上的各项绩效考核指标。 员工被逼急了,什么事情都有可能做出来。作为老板,这个时候你要安慰员工,注重细节,多从小事做起,因为他根本做不了大事。 都说管理就是激发人的善意,不合理地使用KPI却把人性恶的一面逼了出来,这才是误用KPI考核导致的恶果。 3、用KPI量化所有目标 KPI考核,是假设所有的目标都可以被量化,一个指标不行就用十个,总是能够把工作量化的。 但是问题并没有这么简单,尤其对知识工作者来说,许多创意的价值是很难被短期衡量的,而这些创意却是颠覆式创新的源泉。 比如柯达发明了数字成像技术、诺基亚手握众多智能手机专利,但是技术转化成商业的过程,却被KPI压制着,当时代抛弃你的时候,连条微信都不发给你。 因为这些创新在短期内无法量化产出价值,甚至发明人因此背负一个差的绩效,所以短期的KPI不但无法激励创新,反而起到抑制的作用。 KPI无法量化所有工作目标,那么究竟应该如何使用呢? 以阿里为例,阿里的KPI绩效考核兼顾了定性和定量两个方面,即:个人KPI与价值观得分。个人KPI衡量的是关键业务指标达成情况,是定量的、结果性的;价值观得分,考核团队协作、工作投入度、创新等等,是定性的、过程性的。 阿里的KPI考核,是对KPI考核制度的改良,兼顾结果与过程、定性与定量。虽然这段时间以来,阿里备受“996”、“PUA”等舆论困扰,但是不可否认的是,KPI考核的实施是非常成功的。 4、不重视价值观 在天外伺朗的文章《绩效主义毁了索尼!》中,他提到一个“涌流理论”。他认为在绩效主义之下,员工丧失了“涌流现象”,在工作中体会不到乐趣,沦为工作机器,仅仅是为了完成KPI而已,最终导致企业运作低效、产品缺乏创新、不再以服务好顾客为核心,逐渐被市场淘汰。 “涌流现象”是一种专注或沉浸其中的心理状态,很早就被人们在宗教、冥想、瑜伽、艺术、体育等活动中发现。 “涌流理论”是美国的心理学家米哈里·契克森米哈(Mihaly Csikszentmihalyi,英文发音为 cheeks sent me high)在其研究的积极心理学中的核心内容,随着幸福课的流行而逐渐受到关注。 “涌流”对企业来说是至关重要的,当员工全身心投入到工作当中,创意、归属感、成就感被彻底激发出来,企业才能焕发出生机,这也是早期sony成功的秘密。 在苹果公司里,乔布斯也是“涌流理论”的实践者,他网罗了各个领域的一流人才,分享自己对于伟大产品的愿景,激励这些世界上最聪明的头脑,发挥创意,沉浸在自我价值实现的“涌流”当中,创造出具有颠覆性的产品。 所以,对于企业来说,最应该做的事情是创造土壤,激发员工的“涌流”体验,让创意和激情不断涌现,驱动企业走向成功。 5、不再关注顾客 短期KPI考核,让企业赚了快钱,但是丢了客户。就像情侣们,在热恋的时候,常感叹上辈子积了什么德;结婚后,夫妻们常怀疑上辈子造了什么孽。任何时候我们都不能忽略一些本质性的东西,为什么而出发。 互联网是一个充分竞争的行业,只要你的服务和产品不能满足顾客需求,他们就会去发一条朋友圈、两条微博、三条抖音去唱衰你的企业。因此,损失一个顾客,至少影响200个潜在顾客,损失是非常巨大的。 当企业管理层被短期KPI捆绑,就会忽略为“顾客创造价值”这个安身立命之本,导致企业逐渐丧失核心竞争力。 “KPI主义”撕裂组织,造成人与人之间,部门之间的竞争,从而打击员工的士气,破坏团队精神,鼓励对立和斗争。 员工从工作中找不到成就感、自豪感和意义感,只被财富引诱,被恐惧驱使。 6、忽略产品创新 员工为了完成KPI,会放弃那些看似“无用”,却对企业极其重要的东西,比如创新精神、企业的愿景和使命。 知乎网友曾以百度为例,描述了一个产品是如何被KPI一步步毁掉的: 2012年的贴吧,KPI考核会员数量,于是产品经理去掉了匿名发帖、开发了会员体系,开发了签到功能。这无疑牺牲了用户体验,因为用户只是想上来发个贴,还要注册一次,填一堆对体验提升毫无用处的个人信息,而这些信息就是为了完成某个领导的KPI,让企业的用户数据库看起来更完善而已。 2013年贴吧团队的KPI是客户端活跃用户数1千万,而当时的实际活跃是几十万。于是乎大家看到了在2013年一整年,在电脑上和手机网页上的疯狂的弹窗导流,而不是用有价值的信息来吸引用户,已经本末倒置了。 再到后来,迫于营收KPI的压力,许多医药、理财、金融类贴吧被卖给企业来运营,于是出现了极端的魏则西事件,KPI终于毁掉了贴吧。 贴吧曾经在一段时间内,被视为互联网产品创新典范、web2.0的代表作。贴吧产品负责人俞军,也因此一战封神,成为互联网产品界大神级人物。 结语 任何一种绩效考核工具都有它适合的场景、边界。毁了索尼的KPI,却帮助阿里巴巴迅速崛起成为商业帝国,罪魁祸首并不是工具本身,而是企业背后的价值观、管理理念等等。 毁掉索尼的不是KPI,而是早已缺乏创业激情的管理层、日益涣散的员工、被抛弃的创新文化。 同样,毁掉百度贴吧的也不是KPI,而是“为了追逐短期利益......逐渐被挤压变形的价值观......在业绩增长目标之下,被抛到九霄云外的用户体验......” 总之,不论KPI、OKR、平衡记分卡,还是360度考核,工具本身无好坏之分。关键在于,是否适合企业现状、是否匹配企业的管理理念、是否实施推广得当。 最后,还是要鼓励各位管理者,多尝试各种管理工具。虽然大概率是会搞砸的,但是,梦想还是要有的,不然喝多了你跟别人聊啥。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 管理层 KPI

  • 一次线上故障之Java对象的生命历程

    “对象”的一生 像往常一样,早上10点到了公司,赵小八打开电脑收到了PM前一天晚上发来的推荐系统新需求,内心一万只草泥马飘过,思索了半天,打开IDEA开始了“愉快的”new对象之旅。 垃圾回收器老哥:你这样疯狂的嚯嚯对象,有考虑过我的感受吗? 赵小八:你谁啊?我new对象干你啥事? 垃圾回收器老哥:年轻人火气别这么大,既然你这么说那请耗子尾汁。 赵小八:呵,你哥我是被吓大的 垃圾回收器老哥:年轻人不讲武德... 没两天,小八翘着尾巴给PM说,功能上线了,刚没一会儿PM骂骂咧咧的找来了,这tm为啥有时候能出来内容有时候出不来啊,小八菊花一紧赶紧查起了问题,先搂监控接口平均耗时从200ms涨到了300ms,小八心想,我不过就多new了几个对象,怎么tm的影响会这么大,同时DBA同学反馈资源监控正常,看来只能搂业务日志看看了,可是业务日志也并没有什么问题,难道GC有问题?果不其然,GC日志像疯了一样的刷日志。小八赶紧让运维紧急回滚线上代码并dump了一份GC日志分析了起来。 现场代码复原 上面这段代码是一个简化版的用户推荐系统,真实情况下加载需要加载的物料除机器学习物料、商业物料外,还有其他各种例如:运营物料、曝光物料、关系物料等等。 当一个真实用户请求过来之后,上面提到的这些物料就需要全部被加载进来。对象首先从新生代中被创建出来,接着经过一段时间GC后,最后存活下来的对象成功晋级到老年代,那么对象是在什么情况下成功晋级到老年代的呢? case1:对象经历15次GC 小八疯狂的new对象,此时新创建的都被分配到Eden区,如下图: 小八继续疯狂new对象,直到jvm老哥的Eden区放不下更多的对象了,于是触发了一次youngGC,通过这次youngGC之后,只有Context1对象被回收,剩余存活对象进入到了Survivor1里面,如下图: 第一次youngGC结束后,小八又开始了new对象的神操作 没一会儿,jvm又开始了youngGC,此时Eden区和Survivor1里面的存活对象全部移入到Survivor2中,剩余垃圾对象被回收。 就这样反反复复经历了15次youngGC的折腾,还没有被垃圾回收掉的对象最终进入了Old区 case2:动态年龄判断 小八疯狂的new对象 小八继续疯狂new对象,直到jvm老哥的Enden区放不下更对的对象了,于是触发了一次youngGC 经过此次youngGC后,剩余存活对象内存占用大小超过了survivor1区大小的50%,比如:survivor1区大小为50M,而进入到survivor1区的存活对象大小为30M,此时会将当前存活时间最久的对象直接晋升到老年代(存活时间:经历过GC次数最多的对象),此时Context2对象和Context3对象进入到老年代 case3:空间担保机制 小八上线的用户推荐系统,JVM内存的划分情况为:整个堆大小为5G,其中老年代2.5G,新生代2.5G,其中新生代中Eden区:Survivor区=8:2,即Eden区大小为2G,两个Survivor区大小各为250M。 在晚高峰的时候一下子涌入1000人查看推荐列表,一个用户消耗的JVM内存达到了500kb,那么在一秒内就消耗了500M,那么就意味着4秒钟就会产生一次youngGC,假设每次GC后剩余的存活对象为300M,由于300M大小的存活对象无法在survivor区中存放下,此时就触发了空间担保机制。 小八疯狂的new对象 直到发生第一次youngGC,但是一次youngGC后剩余的存活的对象大小Survivor区无法容纳下,此时所有存活对象会直接进入到Old区 在新生代没有足够的内存存储新产生的对象时,老年代会判断自己的区域剩余的内存空间是否能够放得下历代youngGC后剩余存活对象(假设历代youngGC剩余存活对象大小为300M),假设此时老年代还有1G大小的可用内存,那么此次youngGC后剩余的存活对象将直接进入到老年代;假设此时老年代剩余可用内存大小为200M,那么就会触发一次OldGC,OldGC完成后产生的空闲空间大于300M,此时会将新生代的存活对象放入老年代,如果OldGC后剩余的空闲空间小于300M,那么不好意思,就会抛出OOM了。 一图总结Java对象流转情况 上图便是整个Java对象一生经历的流程,流程图相对比较复杂一点,从上往下对照前面讲到的三种情况,相信还是比较容易理解的。 当然图中没有画图新生代触发OOM的情况,可以试想一下Eden区在什么时候会触发OOM?答案在下篇文章给出。 总结 通过一个实际线上案例,讲述了Java对象在不同情况下在JVM中经历的一生。通过本文大家可以尝试将该流程套用到自己公司的项目里面,来分析自己负责的项目是否有类似的问题,或者通过本篇文章来尝试优化自己的项目。另外本文的内容可能会有某些地方讲解的不合适,欢迎有问题的朋友和我私聊探讨。 在上篇文章中留了一个问卷调查,结论如下:总投票人数7人,其中最想了解的技术是SpringCloud,最喜欢的分享方式是图文结合。虽然投票人数比较少,但我相信投票的真实性,后续我会以这个结论为导向,分享更多实用的内容给大家。 打个小广告,年后大家有换个工作氛围的朋友或者身边有想法的朋友,快手研发、运维、产品、运营全部岗位都有你想要的坑位,各种新业务发展速度快,机会多多,面试流程反馈速度超快,欢迎朋友们自荐或者推荐朋友来一起做点有意义的事。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 Java GC Java对象

  • 熬夜总结14个秒杀算法题的套路!

    免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    C语言与CPP编程 滑动窗口 算法题 LeetCode

  • 腾讯音乐:全民K歌推荐系统架构及粗排设计

    导读:腾讯音乐娱乐集团 ( TME ) 目前有四大移动音乐产品:QQ音乐、酷狗音乐、酷我音乐和全民K歌,总月活超8亿。其中,全民K歌与其他三款产品有明显的差异,具体表现如下:以唱为核心,在唱歌的功能上又衍生出了一些音乐娱乐的功能及玩法,目前有超过1.5亿的月活。推荐在全民K歌各个场景中起着重要作用,极大地影响着平台的内容分发状况及生产者与消费者的关系。本文将主要介绍全民K歌的推荐系统架构及粗排设计,具体从以下几方面展开: 推荐系统架构及挑战 多样性调节算法设计 业务背景 具体的推荐功能如上图所示,主要包括以下几类: 优质UGC推荐,将平台原生原创的优质音视频内容进行推荐 同城社交推荐,基于同城的社交进行推荐 02 我们的推荐系统主要分为四个 部分,包括召回层、粗排层、精排层 及重排层。 1. 召回层 一般来说,我们线上的召回方法会分为索引类的召回、泛社交的召回以及模型的召回。 索引类的召回:主要根据画像的结果做精准的ID类召回,比如对用户感兴趣的歌曲进行召回,以及用户感兴趣的创作者进行召回。 模型的召回:基于模型的召回的方法比较多,在后面会展开介绍。 除了召回模块之外,我们在召回层认为另外一个比较重要的是内容的比例筛选,因为我们整体的内容发表量比较大,每天可能有500万的作品在平台内发表,如何从中筛选出合适的内容,需要基于音视频的理解,以及基于我们自己的流量策略来综合发现。 粗排层到精排层相当于是一个承上启下的作用,它会把我们召回到的一些万量级的作品进一步筛选到千量级,既要考虑打分的性能问题,又要考虑排序粗排精准度的问题。一般来说,粗排模型会用一些模型蒸馏的方法,包括特征蒸馏或者基于模型本身的结构蒸馏,以及把这些不同的蒸馏方法组合起来等等。在粗排层,除了做粗排的打分外,我们还会重点做生态的控制,特别在一些内容推荐场景或者直播推荐场景中,我们会注重这里面的内容生态,包括时效性,内容的调性,多样性等等。 粗排层之后就来到了精排层,精排层主要根据千量级的作品进一步的进行精排打分来筛选到百量级的作品。在精排层,我们主要注意以下几方面: 在精排前,除了模型怎么训练构造外,另外两个比较重要的是特征和样本的构造,以及在这个场景下的多目标设计。特别是多目标的问题,可能还涉及到具体的网络结构如何做,以及最后的结果如何融合。 从精排层排序出百量级的作品后,就会进入到重排层。重排层会基于业务规则进行进一步的干预,比如同一首歌曲的视频不能连续出现,同一个创作者的视频不能连续出现等等。除了基于规则性的限制外,我们也会考虑使用模型化的方法做多样性的打散,并且在第4部分,我们也会具体介绍DPP算法的原理和思想。除了模型方法之外,我们也在考虑通过list wise的方法做内容的重排。 粗排模块算法设计 粗排模型和精排模型不同,它可能既需要解决模型预测的准确性,又需要解决模型预测的效率。接下来,为大家介绍我们整个推荐系统如何在线上真实运转。 第一部分是粗排的排序部分,这一部分主要就是为了解决一个大规模item的预排序问题。 让我们再回顾下前面所提到的整个召回系统架构,我们可以看到它其实是一个典型的节点架构,从内容发现到召回,到粗排到精排,然后重排,最后把合适的内容推荐给用户。由于我们是一个比较大的UGC生产平台,从一个UGC平台的角度来考虑,我们倾向于分发较为新的作品,因为新的作品的分发会为那些活跃的创作者带来一定的流量激励,使得生产端跟消费端产生联动,促进了一种正向的循环。 大家可能比较了解的是,召回跟精排召回更像是一个集合,选择的过程更侧重于选择效率最高的方法,至于topk中item之间的先后顺序,其实不是最关心的因素;而精排环节相对来说它更强调次序,也就是说item A跟item B的先后顺序是非常敏感和关键的。 第一条路线是把粗排当成是召回的一种延伸,它的技术选型会像是一种集合,选择的方案和目标是选出效率最高的子集。常用的方式,简单的可以通过实时的serving或者一些实时的指标对召回排序的结果做一个选择。总体来说,这条技术路线最大的优点是算力消耗非常小、性能非常好,但是它的缺点是本身的表达能力是有限的,可以明显预估到它的天花板。 2. 粗排双塔模型实践 同时,它的缺点也非常的明显: 第二个是它在结构上也是有缺陷的,我们回忆一下上面这幅框架图,可以看到user跟item的交互非常少,这会限制它的表达能力的上限。 模型蒸馏有两个关键词,第一个是它本质上是一种迁移学习,也就是transfer learning,第二个是transfer的方式是通过所谓的label,区别于我们平时理解的样本label离散值,变为了0到1之间的一个连续值。左下角是一个常见的蒸馏模型架构图,这个里面会有涉及到两个模型,第一个模型叫做教师模型,叫做teacher,第二个模型是学生模型,叫做student。这两个模型的总体思路是teacher模型是一个非常大、非常复杂、学习到的东西非常多的模型,teacher模型会把学习到的知识传导给student模型,受限于某些原因,该模型没有办法做得很复杂,或者它的规模必须限制在一定范围内的子模型。 准备训练样本,对teacher模型预训练,即得到了teacher模型; 把soft labels传导到student模型,作为模型loss的一部分,因为student模型除了要拟合teacher模型传递的信息,也要去拟合样本真实的分布。 关于logits:它是teacher模型层层映射后的一个抽象度很高且信息浓度很大的结果,是整个知识传递的媒介。在logits后,通常会接一个softmax,但是softmax会放大logits的两极差异,造成细节损失,而这些细节恰恰是帮助student模型更好学习的关键。为了克服以上问题,我们选用了改进的softmax,即softmax with temperature 关于loss:预训练好teacher模型后,转而关注student模型,它的最小化loss由两部分构成,一部分是尽可能拟合teacher模型的输出结果,另一部分是尽可能拟合真实样本。由于student模型的表达能力有限,直接用student模型拟合真实样本可能学不好或者学偏;teacher模型传递过来的信息对student模型做了约束和纠正,指导student模型的优化方向,进而加强了student模型的学习能力。 引入soft labels的概念,不再是原本的非零即一状况,需要考虑正样本有多正和负样本有多负。这看起来类似把分类问题转换成回归问题,但实质并不是。如果构建样本时,用回归的方式构建,通常会基于作品的完播率或规则组合等,人工敲定样本的回归值,这种方式过于主观,没有一个非常合理或可矫正的指标进行后续比对;而模型蒸馏的soft labels是基于teacher 模型,即由teacher 模型对真实样本进行充分训练后给出,包含更多的隐含信息且不受人为主观因素影响。 具体的,来看看我们如何做粗排模块的蒸馏,主要是分为两个大的方向: 特征蒸馏,主要适用于模型相同但特征不同的情况,比如,在某些场景,无法使用全量特征 ( 如交叉特征 ) 或部分特征必须是剪裁过的,即特征没被完全利用,存在一部分损失。这时,通过一个更大的teacher模型学习全量特征,再把学到的知识迁移到子模型,即弥补了上述的部分特征损失。 左侧的teacher模型是精排模型,该模型使用全量的特征,包括三大类,即user侧特征、item侧特征及它们的交叉特征;曲线框里是一些复杂的拓扑结构;最后的softmax with temperature就是整个精排模型的输出内容,后续会给到粗排模型进行蒸馏。 右侧展示的是训练完粗排模型后,在线上产出user serving,通过user serving产出user embedding,再与item embedding做内积运算,完成整个排序的过程。上述流程即实现了粗排和精排的联动过程,并且训练完粗排模型就可以进行一个非常高效的serving,进行粗排排序。业界也有相关的一些工作,上图最下方给出了一篇阿里在这方面的论述。 Serving环节主要包括异步Serving和User Serving两部分,具体功能如下: User Serving,当用户请求推荐引擎后,获取该用户的user features,然后请求User Serving产出user embedding;同时,会拿到该用户的所有召回item集合,基于刷库结果产出的索引服务,取到所有的item embedding。由此可见,User Serving的最大优势是可以做实时计算且只需计算一次,效率非常高。并且,双塔粗排Serving阶段是做内积运算,这种高效的计算方式也使它更适合大规模的预排序场景。 整个粗排模块上线后,我们关注的两类指标:互动类指标和播放类指标,都有非常明显的正向提升,具体的指标提升幅度可以参考上图的在线实验结果。 进而,考虑做进一步优化。 其次,前面虽然不断地用teacher模型和student模型描述整个过程,但实际效果上student模型不一定低于teacher模型。换一个角度,student模型其实是基于teacher模型做进一步训练,所以student模型的表征能力有可能超过teacher模型。事实上,如何让student模型通过反复的蒸馏,效果超过teacher模型,在模型蒸馏领域也有许多相关方法。 04 1. 推荐多样性的意义 多样性的概念在推荐系统里常被提到,在不同视角下,推荐多样性对应着不同的问题。 在用户角度下,多样性就是Explore&Exploit问题,对用户做兴趣的探索与聚焦。如果多样性弱,推荐的item同质化严重,都很像,那么推荐系统可能没办法发现用户的真实兴趣,因为系统可能都没给用户推荐过这类item;如果多样性强,那么用户的推荐流里的内容会很不一样,坏处可能是用户在持续消费过程的兴趣聚焦程度不同,比如用户看了五个item,明明对其中某一两个item更感兴趣,但和这一两个item相似的item的后续推送密度却跟不上,这对用户体验是有损的。如果不做额外的优化,用户角度的多样性会越来越小,因为通常选用的pointwise模型会导致同质化的现象,比如说用户喜欢的item是乐器类的,则pointwise在每一个单点上的预估都觉得乐器是最好的,最后可能连续给用户推了5个乐器,在单点上收益最高不代表用户对整个推荐结果 ( 5~10个item ) 的满意度是最高的,所以这里也需要做多样性控制,提升用户的满意度。 在具体实现上,多样性有三个主流的技术方案:规则打散、embedding打散和DPP,下面会详细介绍: 基于embedding打散,一个连续值的方案。好处是它可以基于embedding对候选集做离散性的评估,相当于此时可以用向量化的方式表达item。缺点在于它虽然可以衡量离散性或多样性,但难以衡量相关性,从而无法实现联合的收益评估。事实上,多样性只是我们的一个目标,相关性也很重要,如果推了很多不同的东西,但和用户不怎么相关,可能会适得其反。 通常,从平台生态控制的角度,类似DPP的控制算法需要贯穿整个推荐链路。在粗排和精排之后,都需要DPP环节,后续会介绍DPP算法的具体实现。 ①基于多样性和相关性构建矩阵L,该矩阵的行列式等价于最终要度量的目标。如何去定义相关性和多样性? 相关性(relativity)是一个候选item与当前用户的匹配程度。在不同环节的实现有差异:在粗排层的DPP,一般是基于user embedding和item embedding计算内积。在精排层的DPP,需要精排的CTR或CVR预估结果,因为这些预估结果反映了当前用户对候选item的喜爱程度。 i的相关性 ( 偏好程度 )、用户与itemj的相关性 ( 偏好程度 )、itemi与itemj的多样性三者乘积。通过最大化矩阵L,就可以实现相关性和多样性的联合度量。 先进行矩阵分解,基于分解的结果将计算复杂度降低到二阶。 由此,行列式的求解过程由三阶降低到一阶,满足了线上的性能,上图最下方给出的paper就是相关方向的论述。 在线上做多样性的相关实验,我们较关注的系统和数据两部分,都有明显收益: 数据收益,有关播放时长、关注渗透、点赞渗透相关的指标都有1~2个点的提升,这也体现了多样性是有必要的,因为它相当于对pointwise进行了简单的listwise化,形象化阐述就是,用户浏览多个session时,多样性调节避免了同质化内容扎堆。 针对上述基于多样性和相关性构建矩阵L的过程,有两方面可以做进一步优化: 相关性本质上是定义什么样的item更匹配当前用户,之前都是从消费者的角度去考量用户更喜欢什么样的item,而我们作为一个ugc平台,很多时候也要考量什么样的item更适合被分发,比如某item是不是更有平台的画风,更符合我们对内容分发的理解,这时,就是从平台系统或者生产者的角度去理解什么样的item更应该被优先推荐。如果已知一部分item更需要被优先推荐,在构建用户跟item间的相关性分时,可以对relativity score加调节权重进行干预,实现流量分配。比如,如果要使乐器类item比其他item有更高的权重,这时,可以调高乐器类item和用户的匹配权重。

    架构师社区 腾讯音乐 推荐系统 全民K歌

  • 面试官:String长度有限制吗?是多少?

    前言 话说Java中String是有长度限制的,听到这里很多人不禁要问,String还有长度限制?是的有,而且在JVM编译中还有规范,而且有的家人们在面试的时候也遇到了,本人就遇到过面试的时候问这个的,而且在之前开发的中也真实地遇到过这个String长度限制的场景(将某固定文件转码成Base64的形式用字符串存储,在运行时需要的时候在转回来,当时文件比较大),那这个规范限制到底是怎么样的,咱们话不多说先䁖䁖去。 String 首先要知道String的长度限制我们就需要知道String是怎么存储字符串的,String其实是使用的一个char类型的数组来存储字符串中的字符的。 存储String的容器原来是它 那么String既然是数组存储那数组会有长度的限制吗?是的有限制,但是是在有先提条件下的,我们看看String中返回length的方法。 String类中的length方法 由此我们看到返回值类型是int类型,Java中定义数组是可以给数组指定长度的,当然不指定的话默认会根据数组元素来指定: int[] arr1 = new int[10]; // 定义一个长度为10的数组 int[] arr2 = {1,2,3,4,5}; // 那么此时数组的长度为5 整数在java中是有限制的,我们通过源码来看看int类型对应的包装类Integer可以看到,其长度最大限制为2^31 -1,那么说明了数组的长度是0~2^31-1,那么计算一下就是(2^31-1 = 2147483647 = 4GB) Integer的取值范围 看到这我们尝试通过编码来验证一下上述观点。 以字面量形式定义字符串 以上是我通过定义字面量的形式构造的10万个字符的字符串,编译之后虚拟机提示报错,说我们的字符串长度过长,不是说好了可以存21亿个吗?为什么才10万个就报错了呢? 其实这里涉及到了JVM编译规范的限制了,其实JVM在编译时,如果我们将字符串定义成了字面量的形式,编译时JVM是会将其存放在常量池中,这时候JVM对这个常量池存储String类型做出了限制,接下来我们先看下手册是如何说的。 java虚拟机规范截图 常量池中,每个 cp_info 项的格式必须相同,它们都以一个表示 cp_info 类型的单字节 “tag”项开头。后面 info[]项的内容 由tag 的类型所决定。 java虚拟机规范手册常量类型表 我们可以看到 String类型的表示是 CONSTANT_String ,我们来看下CONSTANT_String具体是如何定义的。 这里定义的 u2 string_index 表示的是常量池的有效索引,其类型是CONSTANT_Utf8_info 结构体表示的,这里我们需要注意的是其中定义的length我们看下面这张图。 在class文件中u2表示的是无符号数占2个字节单位,我们知道1个字节占8位,2个字节就是16位 ,那么2个字节能表示的范围就是2^16- 1 = 65535 。范中class文件格式对u1、u2的定义的解释做了一下摘要: 这里对java虚拟机规摘要部分 1、class文件中文件内容类型解释 定义一组私有数据类型来表示 Class 文件的内容,它们包括 u1,u2 和 u4,分别代 表了 1、2 和 4 个字节的无符号数。 每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和 64 位长度的数 据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。 2、程序异常处理的有效范围解释 start_pc 和 end_pc 两项的值表明了异常处理器在 code[]数组中的有效范围。 start_pc 必须是对当前 code[]数组中某一指令的操作码的有效索引,end_pc 要 么是对当前 code[]数组中某一指令的操作码的有效索引,要么等于 code_length 的值,即当前 code[]数组的长度。start_pc 的值必须比 end_pc 小。 当程序计数器在范围[start_pc, end_pc)内时,异常处理器就将生效。即设 x 为 异常句柄的有效范围内的值,x 满足:start_pc ≤ x < end_pc。 实际上,end_pc 值本身不属于异常处理器的有效范围这点属于 Java 虚拟机历史上 的一个设计缺陷:如果 Java 虚拟机中的一个方法的 code 属性的长度刚好是 65535 个字节,并且以一个 1 个字节长度的指令结束,那么这条指令将不能被异常处理器 所处理。不过编译器可以通过限制任何方法、实例初始化方法或类初始化方法的 code[]数组最大长度为 65534,这样可以间接弥补这个 BUG。 注意:这里对个人认为比较重要的点做了标记,首先第一个加粗说白了就是说数组有效范围就是【0-65565】但是第二个加粗的地方又解释了,因为虚拟机还需要1个字节的指令作为结束,所以其实真正的有效范围是【0-65564】,这里要注意这里的范围仅限编译时期,如果你是运行时拼接的字符串是可以超出这个范围的。 接下来我们通过一个小实验来测试一下我们构建一个长度为65534的字符串,看看是否就能编译通过。 首先通过一个for循环构建65534长度的字符串,在控制台打印后,我们通过自己度娘的一个在线字符统计工具计算了一下确实是65534个字符,如下: 然后我们将字符复制后以定义字面量的形式赋值给字符串,可以看到我们选择这些字符右下角显示的确实是65534,于是乎运行了一波,果然成功了。 看到这里我们来总结一下: 问:字符串有长度限制吗?是多少? 答:首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1,由于数组是从0开始的,所以数组的最大长度可以使【0~2^31-1】通过计算是大概4GB。 但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义我们可以知道对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535。 其实是65535,但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。 解析到这里就告一段落了,如果觉得在下讲得对你有帮助的可以点一波赞或者在看,如果发现有讲的不好的或者有什么遗漏的,欢迎评论区留言相互学习交流。 PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 长度 Java String

  • 惊了,老板要我开发一个工作流引擎!

    架构师社区 引擎 工作流引擎

  • 吊打MySQL,MariaDB到底强在哪?

    近年来,不少程序员在吹捧 MariaDB,抛弃 MySQL。本文总结了一些  MariaDB 强过 MySQL 的地方,分享给大家! 图片来自 Pexels MySQL 的发展史 MySQL 的历史可以追溯到 1979 年,它的创始人叫作 Michael Widenius,他在开发一个报表工具的时候,设计了一套 API。 后来他的客户要求他的 API 支持 sql 语句,他直接借助于 mSQL(当时比较牛)的代码,将它集成到自己的存储引擎中。但是他总是感觉不满意,萌生了要自己做一套数据库的想法。 一到 1996 年,MySQL 1.0 发布,仅仅过了几个月的时间,1996 年 10 月 MySQL 3.11.1 当时发布了 Solaris 的版本,一个月后,Linux 的版本诞生,从那时候开始,MySQL 慢慢的被人所接受。 1999 年,Michael Widenius 成立了 MySQL AB 公司,MySQL 由个人开发转变为团队开发,2000 年使用 GPL 协议开源。 2001 年,MySQL 生命中的大事发生了,那就是存储引擎 InnoDB 的诞生!直到现在,MySQL 可以选择的存储引擎,InnoDB 依然是 No.1。 2008 年 1 月,MySQL AB 公司被 Sun 公司以 10 亿美金收购,MySQL 数据库进入 Sun 时代。 Sun 为 MySQL 的发展提供了绝佳的环境,2008 年 11 月,MySQL 5.1 发布,MySQL 成为了最受欢迎的小型数据库。 在此之前,Oracle 在 2005 年就收购了 InnoDB,因此,InnoDB 一直以来都只能作为第三方插件供用户选择。 2009 年 4 月,Oracle 公司以 74 亿美元收购 Sun 公司,MySQL 也随之进入 Oracle 时代。 2010 年 12 月,MySQL 5.5 发布,Oracle 终于把 InnoDB 做成了 MySQL 默认的存储引擎,MySQL 从此进入了辉煌时代。 然而,从那之后,Oracle 对 MySQL 的态度渐渐发生了变化,Oracle 虽然宣称 MySQL 依然遵守 GPL 协议,但却暗地里把开发人员全部换成了 Oracle 自己人。 开源社区再也影响不了 MySQL 发展的脚步,真正有心做贡献的人也被拒之门外,MySQL 随时都有闭源的可能…… 横空出世的 MariaDB 是什么鬼 先提一下 MySQL 名字的由来吧,Michael Widenius 的女儿的简称就是 MY,Michael Widenius大 概也是把 MySQL 当成自己的女儿吧。 看着自己辛苦养大的 MySQL 被 Oracle 搞成这样,Michael Widenius 非常失望,决定在 MySQL 走向闭源前,将 MySQL 进行分支化,依然是使用了自己女儿的名字 MariaDB(玛莉亚 DB)。 MariaDB 数据库管理系统是 MySQL 的一个分支,主要由开源社区在维护,采用 GPL 授权许可 MariaDB 的目的是完全兼容 MySQL,包括 API 和命令行,使之能轻松成为 MySQL 的代替品。 在存储引擎方面,使用 XtraDB 来代替 MySQL 的 InnoDB。MariaDB 由 MySQL 的创始人 Michael Widenius 主导,由开源社区的大神们进行开发。 因此,大家都认为,MariaDB 拥有比 MySQL 更纯正的 MySQL 血脉。最初的版本更新与 MySQL 同步,相对 MySQL5 以后的版本,MariaDB 也有相应的 5.1~5.5 的版本。 后来 MariaDB 终于摆脱了 MySQL,它的版本号直接从 10.0 开始,以自己的步伐进行开发,当然,还是可以对 MySQL 完全兼容。现在,MariaDB 的数据特性、性能等都超越了 MySQL。 测试环境 本性能测试环境如下: CPU:I7 内存:8G OS:Windows 10 64位 硬盘类型:SSD MySQL:8.0.19 MariaDB:10.4.12 分别在 MySQl 和 MariaDB 中创建名为 performance 的数据库,并创建 log 表,都使用 innodb 作为数据库引擎: CREATE TABLE `performance`.`log`( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `time` DATETIME NOT NULL, `level` ENUM('info','debug','error') NOT NULL, `message` TEXT NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8; 插入性能 单条插入 单条插入的测试结果如下表所示: MariaDB 单条数据插入的性能比 MySQL 强 1 倍左右。 批量插入 批量插入的测试结果如下表所示: 上面的测试结果,MariaDB 并没有绝对优势,甚至有时还比 MySQL 慢,但平均水平还是高于 MySQL。 查询性能 经过了多次插入测试,我两个数据库里插入了很多数据,此时用下面的 sql 查询表中的数据量: SELECT COUNT(0) FROM LOG 结果两个表都是 6785000 条,MariaDB 用时 3.065 秒,MySQL 用时 6.404 秒。 此时我机器的内存用了 6 个 G,MariaDB 用了 474284 K,MySQL 只用了 66848 K。看来 MariaDB 快是牺牲了空间换取的。 无索引 先查询一下 time 字段的最大值和最小值: SELECT MAX(TIME), MIN(TIME) FROM LOG MariaDB 用时 6.333 秒,MySQL 用时 8.159 秒。接下来测试过滤 time 字段在 0 点到 1 点之间的数据,并对 time 字段排序: SELECT * FROM LOG WHERE TIME > '2020-02-04 00:00:00' AND TIME < '2020-02-04 01:00:00' ORDER BY TIME MariaDB 用时 6.996 秒,MySQL 用时 10.193 秒。然后测试查询 level 字符是 info 的数据: SELECT * FROM LOG WHERE LEVEL = 'info' MariaDB 用时 0.006 秒,MySQL 用时 0.049 秒。最后测试查询 message 字段值为 debug 的数据: SELECT * FROM LOG WHERE MESSAGE = 'debug' MariaDB 用时 0.003 秒,MySQL 用时 0.004 秒。 有索引 分别对两个数据库的字段创建索引: ALTER TABLE `performance`.`log` ADD INDEX `time` (`time`), ADD INDEX `level` (`level`), ADD FULLTEXT INDEX `message` (`message`); MariaDB 用时 2 分 47 秒,MySQL 用时 3 分 48 秒。再用上面的测试项目进行测试,结果如下表所示: 有些结果添加了索引后还不如不加索引时理想,说明实际使用时并不是每个字段都需要添加索引的。 总结 在上面的测试中 MariaDB 的性能的确优于 MySQL,看来各大厂商放弃 MySQL 拥抱 MariaDB 还是非常有道理的。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 MySQL MariaDB

  • 一篇文章搞懂Filebeat

    本文使用的Filebeat是7.7.0的版本,文章将从如下几个方面说明: Filebeat是什么,可以用来干嘛 Filebeat的原理是怎样的,怎么构成的 Filebeat应该怎么玩 Filebeat是什么 Filebeat和Beats的关系 首先Filebeat是Beats中的一员。 Beats在是一个轻量级日志采集器,其实Beats家族有6个成员,早期的ELK架构中使用Logstash收集、解析日志,但是Logstash对内存、CPU、io等资源消耗比较高。相比Logstash,Beats所占系统的CPU和内存几乎可以忽略不计。 目前Beats包含六种工具: Packetbeat:网络数据(收集网络流量数据) Metricbeat:指标(收集系统、进程和文件系统级别的CPU和内存使用情况等数据) Filebeat:日志文件(收集文件数据) Winlogbeat:Windows事件日志(收集Windows事件日志数据) Auditbeat:审计数据(收集审计日志) Heartbeat:运行时间监控(收集系统运行时的数据) Filebeat是什么 Filebeat是用于转发和集中日志数据的轻量级传送工具。Filebeat监视您指定的日志文件或位置,收集日志事件,并将它们转发到Elasticsearch或 Logstash进行索引。 Filebeat的工作方式如下:启动Filebeat时,它将启动一个或多个输入,这些输入将在为日志数据指定的位置中查找。对于Filebeat所找到的每个日志,Filebeat都会启动收集器。每个收集器都读取单个日志以获取新内容,并将新日志数据发送到libbeat,libbeat将聚集事件,并将聚集的数据发送到为Filebeat配置的输出。 工作的流程图如下: Filebeat和Logstash的关系 因为Logstash是JVM跑的,资源消耗比较大,所以后来作者又用Golang写了一个功能较少但是资源消耗也小的轻量级的logstash-forwarder。不过作者只是一个人,加入http://elastic.co公司以后,因为ES公司本身还收购了另一个开源项目Packetbeat,而这个项目专门就是用Golang的,有整个团队,所以ES公司干脆把logstash-forwarder的开发工作也合并到同一个Golang团队来搞,于是新的项目就叫Filebeat了。 Filebeat原理是什么 Filebeat的构成 Filebeat结构:由两个组件构成,分别是inputs(输入)和harvesters(收集器),这些组件一起工作来跟踪文件并将事件数据发送到您指定的输出,harvester负责读取单个文件的内容。harvester逐行读取每个文件,并将内容发送到输出。为每个文件启动一个harvester。harvester负责打开和关闭文件,这意味着文件描述符在harvester运行时保持打开状态。如果在收集文件时删除或重命名文件,Filebeat将继续读取该文件。这样做的副作用是,磁盘上的空间一直保留到harvester关闭。默认情况下,Filebeat保持文件打开,直到达到close_inactive。 关闭harvester可以会产生的结果: 文件处理程序关闭,如果harvester仍在读取文件时被删除,则释放底层资源。 只有在scan_frequency结束之后,才会再次启动文件的收集。 如果该文件在harvester关闭时被移动或删除,该文件的收集将不会继续。 一个input负责管理harvesters和寻找所有来源读取。如果input类型是log,则input将查找驱动器上与定义的路径匹配的所有文件,并为每个文件启动一个harvester。每个input在它自己的Go进程中运行,Filebeat当前支持多种输入类型。每个输入类型可以定义多次。日志输入检查每个文件,以查看是否需要启动harvester、是否已经在运行harvester或是否可以忽略该文件。 Filebeat如何保存文件的状态 Filebeat保留每个文件的状态,并经常将状态刷新到磁盘中的注册表文件中。该状态用于记住harvester读取的最后一个偏移量,并确保发送所有日志行。如果无法访问输出(如Elasticsearch或Logstash),Filebeat将跟踪最后发送的行,并在输出再次可用时继续读取文件。当Filebeat运行时,每个输入的状态信息也保存在内存中。当Filebeat重新启动时,来自注册表文件的数据用于重建状态,Filebeat在最后一个已知位置继续每个harvester。对于每个输入,Filebeat都会保留它找到的每个文件的状态。由于文件可以重命名或移动,文件名和路径不足以标识文件。对于每个文件,Filebeat存储唯一的标识符,以检测文件是否以前被捕获。 Filebeat何如保证至少一次数据消费 Filebeat保证事件将至少传递到配置的输出一次,并且不会丢失数据。是因为它将每个事件的传递状态存储在注册表文件中。在已定义的输出被阻止且未确认所有事件的情况下,Filebeat将继续尝试发送事件,直到输出确认已接收到事件为止。如果Filebeat在发送事件的过程中关闭,它不会等待输出确认所有事件后再关闭。当Filebeat重新启动时,将再次将Filebeat关闭前未确认的所有事件发送到输出。这样可以确保每个事件至少发送一次,但最终可能会有重复的事件发送到输出。通过设置shutdown_timeout选项,可以将Filebeat配置为在关机前等待特定时间。 Filebeat怎么玩 压缩包方式安装 本文采用压缩包的方式安装,Linux版本,filebeat-7.7.0-linux-x86_64.tar.gz。 curl-L-Ohttps://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.0-linux-x86_64.tar.gz tar -xzvf filebeat-7.7.0-linux-x86_64.tar.gz 配置示例文件:filebeat.reference.yml(包含所有未过时的配置项) 配置文件: filebeat.yml 基本命令 export #导出 run #执行(默认执行) test #测试配置 keystore #秘钥存储 modules #模块配置管理 setup #设置初始环境 例如:./filebeat test config #用来测试配置文件是否正确 输入输出 支持的输入组件: Multilinemessages,Azureeventhub,CloudFoundry,Container,Docker,GooglePub/Sub,HTTPJSON,Kafka,Log,MQTT,NetFlow,Office 365 Management Activity API,Redis,s3,Stdin,Syslog,TCP,UDP(最常用的就是Log) 支持的输出组件: Elasticsearch,Logstash,Kafka,Redis,File,Console,ElasticCloud,Changetheoutputcodec(最常用的就是Elasticsearch,Logstash) keystore的使用 keystore主要是防止敏感信息被泄露,比如密码等,像ES的密码,这里可以生成一个key为ES_PWD,值为ES的password的一个对应关系,在使用ES的密码的时候就可以使用${ES_PWD}使用。 创建一个存储密码的keystore:filebeat keystore create 然后往其中添加键值对,例如:filebeatk eystore add ES_PWD 使用覆盖原来键的值:filebeat key store add ES_PWD–force 删除键值对:filebeat key store remove ES_PWD 查看已有的键值对:filebeat key store list 例如:后期就可以通过${ES_PWD}使用其值,例如: output.elasticsearch.password:"${ES_PWD}" filebeat.yml配置(Log输入类型为例) type: log #input类型为log enable: true #表示是该log类型配置生效 paths: #指定要监控的日志,目前按照Go语言的glob函数处理。没有对配置目录做递归处理,比如配置的如果是: - /var/ log/* /*. log #则只会去/var/log目录的所有子目录中寻找以".log"结尾的文件,而不会寻找/var/log目录下以".log"结尾的文件。 recursive_glob.enabled: #启用全局递归模式,例如/foo/**包括/foo, /foo/*, /foo/*/* encoding: #指定被监控的文件的编码类型,使用plain和utf-8都是可以处理中文日志的 exclude_lines: [ '^DBG'] #不包含匹配正则的行 include_lines: [ '^ERR', '^WARN'] #包含匹配正则的行 harvester_buffer_size: 16384 #每个harvester在获取文件时使用的缓冲区的字节大小 max_bytes: 10485760 #单个日志消息可以拥有的最大字节数。max_bytes之后的所有字节都被丢弃而不发送。默认值为10MB (10485760) exclude_files: [ '\.gz$'] #用于匹配希望Filebeat忽略的文件的正则表达式列表 ingore_older: 0 #默认为0,表示禁用,可以配置2h,2m等,注意ignore_older必须大于close_inactive的值.表示忽略超过设置值未更新的 文件或者文件从来没有被harvester收集 close_* #close_ *配置选项用于在特定标准或时间之后关闭harvester。 关闭harvester意味着关闭文件处理程序。 如果在harvester关闭 后文件被更新,则在scan_frequency过后,文件将被重新拾取。 但是,如果在harvester关闭时移动或删除文件,Filebeat将无法再次接收文件,并且harvester未读取的任何数据都将丢失。 close_inactive #启动选项时,如果在制定时间没有被读取,将关闭文件句柄 读取的最后一条日志定义为下一次读取的起始点,而不是基于文件的修改时间 如果关闭的文件发生变化,一个新的harverster将在scan_frequency运行后被启动 建议至少设置一个大于读取日志频率的值,配置多个prospector来实现针对不同更新速度的日志文件 使用内部时间戳机制,来反映记录日志的读取,每次读取到最后一行日志时开始倒计时使用2h 5m 来表示 close_rename #当选项启动,如果文件被重命名和移动,filebeat关闭文件的处理读取 close_removed #当选项启动,文件被删除时,filebeat关闭文件的处理读取这个选项启动后,必须启动clean_removed close_eof #适合只写一次日志的文件,然后filebeat关闭文件的处理读取 close_timeout #当选项启动时,filebeat会给每个harvester设置预定义时间,不管这个文件是否被读取,达到设定时间后,将被关闭 close_timeout 不能等于ignore_older,会导致文件更新时,不会被读取如果output一直没有输出日志事件,这个timeout是不会被启动的,至少要要有一个事件发送,然后haverter将被关闭 设置0 表示不启动 clean_inactived #从注册表文件中删除先前收获的文件的状态 设置必须大于ignore_older+scan_frequency,以确保在文件仍在收集时没有删除任何状态 配置选项有助于减小注册表文件的大小,特别是如果每天都生成大量的新文件 此配置选项也可用于防止在Linux上重用inode的Filebeat问题 clean_removed #启动选项后,如果文件在磁盘上找不到,将从注册表中清除filebeat 如果关闭close removed 必须关闭clean removed scan_frequency #prospector检查指定用于收获的路径中的新文件的频率,默认10s tail_files: #如果设置为true,Filebeat从文件尾开始监控文件新增内容,把新增的每一行文件作为一个事件依次发送, 而不是从文件开始处重新发送所有内容。 symlinks: #符号链接选项允许Filebeat除常规文件外,可以收集符号链接。收集符号链接时,即使报告了符号链接的路径, Filebeat也会打开并读取原始文件。 backoff: #backoff选项指定Filebeat如何积极地抓取新文件进行更新。默认1s,backoff选项定义Filebeat在达到EOF之后 再次检查文件之间等待的时间。 max_backoff: #在达到EOF之后再次检查文件之前Filebeat等待的最长时间 backoff_factor: #指定backoff尝试等待时间几次,默认是2 harvester_limit: #harvester_limit选项限制一个prospector并行启动的harvester数量,直接影响文件打开数 tags #列表中添加标签,用过过滤,例如:tags: ["json"] fields #可选字段,选择额外的字段进行输出可以是标量值,元组,字典等嵌套类型 默认在sub-dictionary位置 filebeat.inputs: fields: app_id: query_engine_12 fields_under_root #如果值为ture,那么fields存储在输出文档的顶级位置 multiline.pattern #必须匹配的regexp模式 multiline.negate #定义上面的模式匹配条件的动作是 否定的,默认是false 假如模式匹配条件 '^b',默认是 false模式,表示讲按照模式匹配进行匹配 将不是以b开头的日志行进行合并 如果是 true,表示将不以b开头的日志行进行合并 multiline.match # 指定Filebeat如何将匹配行组合成事件,在之前或者之后,取决于上面所指定的negate multiline.max_lines #可以组合成一个事件的最大行数,超过将丢弃,默认500 multiline.timeout #定义超时时间,如果开始一个新的事件在超时时间内没有发现匹配,也将发送日志,默认是5s max_procs #设置可以同时执行的最大CPU数。默认值为系统中可用的逻辑CPU的数量。 name #为该filebeat指定名字,默认为主机的hostname 实例一:Logstash作为输出 filebeat.yml配置: #=========================== Filebeat inputs ============================= filebeat.inputs: # Each - is an input. Most options can be set at the input level, so # you can use different inputs for various configurations. # Below are the input specific configurations. - type: log # Change to true to enable this input configuration. enabled: true # Paths that should be crawled and fetched. Glob based paths. paths: #配置多个日志路径 -/var/logs/es_aaa_index_search_slowlog.log     -/var/logs/es_bbb_index_search_slowlog.log     -/var/logs/es_ccc_index_search_slowlog.log     -/var/logs/es_ddd_index_search_slowlog.log #- c:\programdata\elasticsearch\logs\* # Exclude lines. A list of regular expressions to match. It drops the lines that are # matching any regular expression from the list. #exclude_lines: ['^DBG'] # Include lines. A list of regular expressions to match. It exports the lines that are # matching any regular expression from the list. #include_lines: ['^ERR', '^WARN'] # Exclude files. A list of regular expressions to match. Filebeat drops the files that # are matching any regular expression from the list. By default, no files are dropped. #exclude_files: ['.gz$'] # Optional additional fields. These fields can be freely picked # to add additional information to the crawled log files for filtering #fields: #  level: debug #  review: 1 ### Multiline options # Multiline can be used for log messages spanning multiple lines. This is common # for Java Stack Traces or C-Line Continuation # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [ #multiline.pattern: ^\[ # Defines if the pattern set under pattern should be negated or not. Default is false. #multiline.negate: false # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern # that was (not) matched before or after or as long as a pattern is not matched based on negate. # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after #================================ Outputs ===================================== #----------------------------- Logstash output -------------------------------- output.logstash: # The Logstash hosts #配多个logstash使用负载均衡机制 hosts: ["192.168.110.130:5044","192.168.110.131:5044","192.168.110.132:5044","192.168.110.133:5044"]     loadbalance: true #使用了负载均衡 # Optional SSL. By default is off. # List of root certificates for HTTPS server verifications #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"] # Certificate for SSL client authentication #ssl.certificate: "/etc/pki/client/cert.pem" # Client Certificate Key #ssl.key: "/etc/pki/client/cert.key" ./filebeat -e #启动filebeat Logstash的配置: input {   beats {     port => 5044      } } output {   elasticsearch {     hosts => ["http://192.168.110.130:9200"] #这里可以配置多个 index => "query-%{yyyyMMdd}" } } 实例二:Elasticsearch作为输出 filebeat.yml的配置: ###################### Filebeat Configuration Example ######################### # This file is an example configuration file highlighting only the most common # options. The filebeat.reference.yml file from the same directory contains all the # supported options with more comments. You can use it as a reference. # # You can find the full configuration reference here: # https://www.elastic.co/guide/en/beats/filebeat/index.html # For more available modules and options, please see the filebeat.reference.yml sample # configuration file. #=========================== Filebeat inputs ============================= filebeat.inputs: # Each - is an input. Most options can be set at the input level, so # you can use different inputs for various configurations. # Below are the input specific configurations. - type: log # Change to true to enable this input configuration. enabled: true # Paths that should be crawled and fetched. Glob based paths. paths:     -/var/logs/es_aaa_index_search_slowlog.log     -/var/logs/es_bbb_index_search_slowlog.log     -/var/logs/es_ccc_index_search_slowlog.log     -/var/logs/es_dddd_index_search_slowlog.log #- c:\programdata\elasticsearch\logs\* # Exclude lines. A list of regular expressions to match. It drops the lines that are # matching any regular expression from the list. #exclude_lines: ['^DBG'] # Include lines. A list of regular expressions to match. It exports the lines that are # matching any regular expression from the list. #include_lines: ['^ERR', '^WARN'] # Exclude files. A list of regular expressions to match. Filebeat drops the files that # are matching any regular expression from the list. By default, no files are dropped. #exclude_files: ['.gz$'] # Optional additional fields. These fields can be freely picked # to add additional information to the crawled log files for filtering #fields: #  level: debug #  review: 1 ### Multiline options # Multiline can be used for log messages spanning multiple lines. This is common # for Java Stack Traces or C-Line Continuation # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [ #multiline.pattern: ^\[ # Defines if the pattern set under pattern should be negated or not. Default is false. #multiline.negate: false # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern # that was (not) matched before or after or as long as a pattern is not matched based on negate. # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash #multiline.match: after #============================= Filebeat modules =============================== filebeat.config.modules: # Glob pattern for configuration loading path: ${path.config}/modules.d/*.yml # Set to true to enable config reloading reload.enabled: false # Period on which files under path should be checked for changes #reload.period: 10s #==================== Elasticsearch template setting ========================== #================================ General ===================================== # The name of the shipper that publishes the network data. It can be used to group # all the transactions sent by a single shipper in the web interface. name: filebeat222 # The tags of the shipper are included in their own field with each # transaction published. #tags: ["service-X", "web-tier"] # Optional fields that you can specify to add additional information to the # output. #fields: #  env: staging #cloud.auth: #================================ Outputs ===================================== #-------------------------- Elasticsearch output ------------------------------ output.elasticsearch: # Array of hosts to connect to. hosts: ["192.168.110.130:9200","92.168.110.131:9200"] # Protocol - either `http` (default) or `https`. #protocol: "https" # Authentication credentials - either API key or username/password. #api_key: "id:api_key" username: "elastic" password: "${ES_PWD}" #通过keystore设置密码 ./filebeat -e #启动Filebeat 查看Elasticsearch集群,有一个默认的索引名字filebeat-%{[beat.version]}-%{+yyyy.MM.dd} Filebeat模块 这里我使用Elasticsearch模式来解析ES的慢日志查询,操作步骤如下,其他的模块操作也一样: 前提:安装好Elasticsearch和Kibana两个软件,然后使用Filebeat。 第一步,配置filebeat.yml文件: #============================== Kibana ===================================== # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API. # This requires a Kibana endpoint configuration. setup.kibana: # Kibana Host # Scheme and port can be left out and will be set to the default (http and 5601) # In case you specify and additional path, the scheme is required: http://localhost:5601/path # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601 host: "192.168.110.130:5601" #指定kibana username: "elastic" #用户 password: "${ES_PWD}" #密码,这里使用了keystore,防止明文密码 # Kibana Space ID # ID of the Kibana Space into which the dashboards should be loaded. By default, # the Default Space will be used. #space.id: #================================ Outputs ===================================== # Configure what output to use when sending the data collected by the beat. #-------------------------- Elasticsearch output ------------------------------ output.elasticsearch: # Array of hosts to connect to. hosts: ["192.168.110.130:9200","192.168.110.131:9200"] # Protocol - either `http` (default) or `https`. #protocol: "https" # Authentication credentials - either API key or username/password. #api_key: "id:api_key" username: "elastic" #es的用户 password: "${ES_PWD}" # es的密码 #这里不能指定index,因为我没有配置模板,会自动生成一个名为filebeat-%{[beat.version]}-%{+yyyy.MM.dd}的索引 第二步,配置Elasticsearch的慢日志路径: cd filebeat-7.7.0-linux-x86_64/modules.d vim elasticsearch.yml: 第三步,生效ES模块: ./filebeat modules elasticsearch 查看生效的模块: ./filebeat modules list 第四步,初始化环境: ./filebeat setup -e 第五步,启动Filebeat: ./filebeat -e 查看Elasticsearch集群,如下图所示,把慢日志查询的日志都自动解析出来了: 到这里,Elasticsearch这个module就实验成功了。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 Filebeat Beats 日志采集器

发布文章