• 关于

    数据区出问题什么情况

    的搜索结果

回答

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类。另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收。如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,那么内存回收也就可以根据每个区域的特定进行回收,比如像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就需要使用经典的回收算法来进行回收了,所以看起来分类这么麻烦,其实是大有好处的。 提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。但是·虚拟机的内存结构远比此要复杂的多。除了我们所认识的(还没有认识完全)的堆栈以外,还有程序计数器,本地方法栈和方法区。我们平时所说的栈内存,一般是指的栈内存中的局部变量表。 从图中可以看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,非常好理解,我们知道每一个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被所有线程一起使用的,不管有多少个线程,这片空间始终就这一个。而线程的独占区,是每个线程都有这么一份内存空间,每个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不代表实际内存区域的大小,实际运行过程中,内存区域的大小也是可以动态调整的。下面来具体说说每一个区域的主要功能。 程序计数器,我们在写代码的过程中,开发工具一般都会给我们标注行号方便查看和阅读代码。那么在程序在运行过程中也有一个类似的行号方便虚拟机的执行,就是程序计数器,在c语言中,我们知道会有一个goto语句,其实就是跳转到了指定的行,这个行号就是程序计数器。存储的就是程序下一条所执行的指令。这部分区域是线程所独享的区域,我们知道线程是一个顺序执行流,每个线程都有自己的执行顺序,如果所有线程共用一个程序计数器,那么程序执行肯定就会出乱子。为了保证每个线程的执行顺序,所以程序计数器是被单个线程所独显的。程序计数器这块内存区域是唯一一个在jvm规范中没有规定内存溢出的。 java虚拟机栈,java虚拟机栈是程序运行的动态区域,每个方法的执行都伴随着栈帧的入栈和出栈。 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中包括了局部变量表,操作数栈,方法返回地址以及额外的一些附加信息,在编译过程中,局部变量表的大小已经确定,操作数栈深度也已经确定,因此栈帧在运行的过程中需要分配多大的内存是固定的,不受运行时影响。对于没有逃逸的对象也会在栈上分配内存,对象的大小其实在运行时也是确定的,因此即使出现了栈上内存分配,也不会导致栈帧改变大小。 一个线程中,可能调用链会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是最有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的字节码指令仅对当前栈帧进行操作。Ft5rk58GfiJxcdcCzGeAt8fjkFPkMRdf 局部变量表:我们平时所说的栈内存一般就是指栈内存中的局部变量表。这里主要是存储变量所用。对于基本数据类型直接存储其值,对于引用数据类型则存储其地址。局部变量表的最小存储单位是Slot,每个Slot都能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。 既然前面提到了数据类型,在此顺便说一下,一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress八种类型。前面六种不需要多解释,大家都认识,而后面的reference是对象的引用。虚拟机规范既没有说明它的长度,也没有明确指出这个引用应有怎样的结构,但是一般来说,虚拟机实现至少都应当能从此引用中直接或间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。而returnAddress是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。 对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的Slot空间。Java语言中明确规定的64位的数据类型只有long和double两种(reference类型则可能是32位也可能是64位)。值得一提的是,这里把long和double数据类型读写分割为两次32读写的做法类似。不过,由于局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的Slot是否是原子操作,都不会引起数据安全问题。 操作数栈是一个后入先出(Last In First Out, LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到字节码文件中,关于字节码文件,后面我会具体的来描述。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。 举个例子,整数加法的字节码指令iadd在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,然后将相加的结果入栈。 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的iadd指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现一个long和一个float使用iadd命令相加的情况。 本地方法栈 与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 方法区经常会被人称之为永久代,但这俩并不是一个概念。首先永久代的概念仅仅在HotSpot虚拟机中存在,不幸的是,在jdk8中,Hotspot去掉了永久代这一说法,使用了Native Memory,也就是Metaspace空间。那么方法区是干嘛的呢?我们可以这么理解,我们要运行Java代码,首先需要编译,然后才能运行。在运行的过程中,我们知道首先需要加载字节码文件。也就是说要把字节码文件加载到内存中。好了,问题就来了,字节码文件放到内存中的什么地方呢,就是方法区中。当然除了编译后的字节码之外,方法区中还会存放常量,静态变量以及及时编译器编译后的代码等数据。 堆,一般来讲堆内存是Java虚拟机中最大的一块内存区域,同方法区一样,是被所有线程所共享的区域。此区域所存在的唯一目的就存放对象的实例(对象实例并不一定全部在堆中创建)。堆内存是垃圾收集器主要光顾的区域,一般来讲根据使用的垃圾收集器的不同,堆中还会划分为一些区域,比如新生代和老年代。新生代还可以再划分为Eden,Survivor等区域。另外为了性能和安全性的角度,在堆中还会为线程划分单独的区域,称之为线程分配缓冲区。更细致的划分是为了让垃圾收集器能够更高效的工作,提高垃圾收集的效率。 如果想要了解更多的关于虚拟机的内容,可以观看录制的<深入理解Java虚拟机>这套视频教程。

zwt9000 2019-12-02 00:21:07 0 浏览量 回答数 0

问题

发现读取的数据只是覆盖原来的数据, 原来的数据还保留,怎么在读取前清空呢?:报错

kun坤 2020-06-07 14:03:39 0 浏览量 回答数 1

回答

这是oracle的bug,请下载oracle 最新的jar,连接地址: http://download.oracle.com/otn/utilities_drivers/jdbc/11204/ojdbc6.jar 首先,恭喜楼主问题得解,可喜可贺! 其次,感谢楼主解决问题后的分享。 但是我有几点建议想对楼主谏言(不喜勿看,打扰见谅): 第一,从此问题的最终解决来看。楼主的描述实在是不足以让别人更好的帮助你解决问题。首先,你的标题和你的问题就对不上。其次,关于问题描述,最开始你只是把error的stacktrace信息发布出来,这样别人根本就无法很好的帮你判断问题,后来你对描述还更新过一次,加上了表结构和代码,但实际问题是在于oracle的驱动问题,你的描述也没有突出你用的是oracle的这个重点。为了让别人更好的帮助你,望今后把重点信息表述出来,感谢! 第二,对于问题的解决你也没有描述清楚。首先,prepareStatement这个API的文档说明并不能说明oracle驱动的这个bug,对于问题的解决没有实际的帮助。其次,你附带的那个csdn的帖子里面也对这个bug没有任何明确的说明,而且那个帖子最后的一个回复者所述,换驱动并没有解决同样的问题,这样让读者对此bug的描述和解决难免产生质疑,缺乏可信度。虽然我又去stackoverflow确认过了,确实是oracle的jdbc驱动的问题,但是单就此问题的分享的角度,楼主的答案并没有充分体现出分享的价值。为了给大家带来更好的帮助,望楼主以后在回答的时候能给出更权威可信的答案。 ps:我附上stackoverflow对oracle驱动bug的帖子,作为对楼主的补充,供大家参考:http://stackoverflow.com/questions/277744/jdbc-oracle-arrayindexoutofboundsexception 文中还描述了一种workaround的解决方案,请大家参阅。 以上!万谢!大家看过来,这个才是最佳答案^_^谢谢提醒,以后多加注意!这。。。。。这个和8个有什么关系啊,直觉上感觉是你传进去的paras的size根本就不够吧。你调试看一下当时的数据是怎么样的?而且你这标题。。。是怎么回事?你这错误是在save时候的,为什么标题是说getModel的问题?你是说getModel没有给你取到9个属性么?回复 @小兵一枚:呵呵,什么要侮辱,有问题提提怎么了,不要盲目的崇拜!问题原因是oracle的bug!回复 @蚂蚁蚂蚁:不要侮辱强大的JFinal亲莫非你们都是小表! java.lang.ArrayIndexOutOfBoundsException: 8 下次提问还是先检查下错误再提吧 查了查api,如下: prepareStatementPreparedStatementprepareStatement(Stringsql,String[]columnNames)throwsSQLException创建一个能返回由给定数组指定的自动生成键的默认PreparedStatement对象。此数组包含目标表中列的名称,而目标表包含应该返回的自动生成键。如果SQL语句不是INSERT语句,或者SQL语言能够返回自动生成的键(这类语句的列表是特定于供应商的),则驱动程序将忽略该数组。带IN参数或不带IN参数的SQL语句都可以被预编辑并存储在PreparedStatement对象中。然后可以使用此对象多次有效地执行该语句。注:为了处理受益于预编译的带参数SQL语句,此方法进行了优化。如果驱动程序支持预编译,则prepareStatement方法将该语句发送给数据库进行预编译。一些驱动程序可能不支持预编译。在这种情况下,执行PreparedStatement对象之前无法将语句发送给数据库。这对用户没有直接影响;但它的确会影响哪些方法将抛出某些SQLException。使用返回的PreparedStatement对象创建的结果集在默认情况下类型为TYPE_FORWARD_ONLY,并带有CONCUR_READ_ONLY并发级别。已创建结果集的可保存性可调用getHoldability()确定。参数:sql-可能包含一个或多个'?'IN参数占位符的SQL语句columnNames-列名称数组,这些名称指示应该从一个或多个插入行中返回的那些列返回:一个包含预编译语句的新PreparedStatement对象,该对象能够返回由给定列名称数组指定的自动生成键抛出:SQLException-如果发生数据库访问错误,或者在关闭的连接上调用此方法SQLFeatureNotSupportedException-如果JDBC驱动程序不支持此方法 嗯嗯,这才是正解吗感谢楼主解决问题后回来分享,此贴应该放在技术分享区哈 

爱吃鱼的程序员 2020-06-23 11:59:19 0 浏览量 回答数 0

Quick BI 数据可视化分析平台

2020年入选全球Gartner ABI魔力象限,为中国首个且唯一入选BI产品

问题

ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?【Java问答学堂】27期

剑曼红尘 2020-05-27 20:28:45 22 浏览量 回答数 1

问题

MaxCompute百问集锦(持续更新20171011)

隐林 2019-12-01 20:19:23 38430 浏览量 回答数 18

问题

quickdb 另辟捷径高效解决NOSQL数据库 数据持久性问题:报错

kun坤 2020-06-07 16:39:10 0 浏览量 回答数 1

问题

性能测试:软件测试的重中之重

云效平台 2019-12-01 21:45:09 5839 浏览量 回答数 1

问题

【Java问答学堂】9期 es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?

剑曼红尘 2020-04-27 14:35:38 0 浏览量 回答数 1

回答

ECS磁盘 我想在ECS 跨服务器进行数据拷贝,有没有知道实现方法的? Linux系统服务器重启或初始化系统之后,再登录服务器执行df -h查看磁盘挂载,发现数据不见了。这是为什么?能不能找回来? 重启服务器后发现/alidata目录所有数据丢失。怎么才能找回来呢? ECS Linux扩容格式化磁盘提示magic number in super-block while trying to open /dev/xvdb1 ? Linux 实例初始化系统盘后,怎样才能重新挂载数据盘? 如何在ECS 利用快照创建磁盘实现无损扩容数据盘? ECS云服务器磁盘FAQ云服务器磁盘I/O速度是多少? Linux 购买了数据盘,但是系统中看不到怎么办? ECS系统盘和数据盘二次分区FAQ,系统盘能否再次划分出一个分区用作数据存储? ECS系统盘和数据盘二次分区FAQ,数据盘能否再次划分出一个分区用作数据存储? ECS系统盘和数据盘二次分区FAQ,划分了多个分区的磁盘,做快照时是针对该分区的,还是针对磁盘的? ECS系统盘和数据盘二次分区FAQ,磁盘二次分区有哪些注意事项? ECS系统盘和数据盘二次分区FAQ,数据盘进行二次分区后,此时回滚快照后,数据盘是几个分区? 什么是可用区? 怎么根据服务器应用需求选择可用区? 按量付费云盘和云盘有什么区别? 按量付费云盘和普通云盘的性能和数据安全性一样吗,磁盘性能会有提升吗? 可以使用用户快照创建按量付费云盘吗? 什么是挂载点? 一块按量付费云盘可以挂载到多个 ECS 实例上吗? 一台 ECS 实例能同时挂载多少块按量付费云盘吗? 按量付费云盘能够挂载到包年包月和按量付费 ECS 实例上吗? 为什么挂载按量付费云盘时找不到我想挂载的 ECS 实例? 购买按量付费云盘后,挂载到目标 ECS 实例的挂载点是否还需要执行磁盘挂载操作? 我已经操作过续费变配,在续费变配期内是否还能将普通云盘转为按量付费云盘? ECS快照 为什么我的按量付费云盘没有自动快照了? 重新初始化磁盘时,我的快照会丢失吗? 更换系统盘时,我的快照会丢失吗? 卸载按量付费云盘时,我的磁盘会丢数据吗? 我能够卸载系统盘吗? 什么是独立云磁盘? 什么是可用区? 独立云磁盘跟现在的磁盘有什么区别? 服务器应用与可用区选择的选择关系是怎么样的? 独立云磁盘怎么收费? 独立云磁盘能够挂载到包年包月实例上吗? 独立云磁盘和普通云磁盘的磁盘性能和数据安全性一样吗,磁盘性能会有提升吗? 我的包年包月实例上不需要的磁盘能不能卸载? 为什么我的独立云磁盘和我的实例一起释放了? 为什么独立云磁盘挂载时找不到我想挂载的实例? 为什么我在本实例列表中选择独立云磁盘挂载时找不到我想要挂载的磁盘? 我删除磁盘的时候,快照会被保留吗? 为什么我的独立云磁盘没有自动快照了? 为什么我不能购买独立云磁盘? 一台实例能挂载多少块独立云磁盘? 卸载独立云磁盘时,我的磁盘会丢数据吗? 我的系统盘能够卸载吗? 什么是设备名? 为什么我在控制台上找不到重置磁盘,更换操作系统,回滚快照的操作了? 重新初始化磁盘时,我的快照会丢失吗? 更换系统盘时,我的快照会丢失吗? 为什么我的数据盘不能选择临时磁盘 独立云磁盘服务器的应用场景有哪些? 可以使用用户快照创建独立云磁盘吗? 独立云磁盘购买后挂载到目标实例的挂载点后,是否还需要执行磁盘挂载操作? 本地SSD盘“本地”是指? 本地SSD盘适合的用户场景有哪些? SSD盘相对之前的普通云盘性能提升多少,是否可以提供具体参数? 本地SSD盘是否支持在原ECS上进行添加或者将原云磁盘更换成本地SSD盘? 本地SSD盘购买后是否支持升级? SSD 云盘具备怎样的 I/O 性能? SSD云盘的数据可靠性是怎样的? SSD 云盘适合的应用场景有哪些? SSD 云盘相对普通云盘性能提升多少?是否可以提供具体参数? I/O 优化是什么概念?能将存量的 ECS 实例升级为 I/O 优化的实例吗? 是否支持将原普通云盘更换成 SSD 云盘? 如何购买 SSD 云盘,I/O 优化的实例及 SSD 云盘的价格是多少? 为什么 I/O 优化的实例有时启动比较耗时? 有些自定义镜像不支持创建 I/O 优化的实例,我该如何操作? 购买SSD云盘后是否支持升级? 使用了 I/O 优化实例和 SSD 云盘之后,Linux 系统在分区挂载的时候报错。 为什么我用 fio 测试性能时,会导致实例宕机? 云盘参数和性能测试工具及方法有推荐的吗? 我想扩容系统盘,求详细步骤! 所有块存储都支持系统盘扩容吗?有地域限制吗? 包年包月和按量付费的ECS实例都支持系统盘扩容吗? 新购ECS时,系统盘开始单独收费?老用户存量的系统盘如何收费? 新购ECS时,系统盘开始单独收费?老用户存量的系统盘如何收费?系统盘扩容是否需要停机操作? 系统盘扩容上线后,系统盘的容量范围多少? 哪些镜像支持系统盘扩容? 云服务器续费变配后,不支持更换系统盘时指定系统盘容量? 系统盘扩容之后是否支持再缩容? 扩容系统盘应注意的问题? 回滚磁盘报错,进行快照回滚的时候,出现如下错误提示: 执行回滚磁盘需要停止实例,并确保当前磁盘没有创建中的快照和没有更换过操作系统。 这是什么原因? 普通云盘和SSD云盘添加挂载信息时有哪些要注意的事项? 申请公测资格 什么是共享块存储? 共享块存储适用于哪些行业和业务场景? 为什么需要共享块存储? 如何正确使用共享块存储? 我能跨地域挂载共享块存储吗? 共享块存储产品规格有哪些? 我想知道阿里云产品的售卖模式和公测范围! 公测购买入口是哪,求链接! 有没有谁分享下共享块存储性能测试命令? 数据盘挂载问题导致数据无法访问,我要怎么排查问题? 我要怎样才能在Linux和Windows主机之间挂载ntfs格式云盘? 为什么ECS实例里文件系统和快照空间大小不一致?在ECS实例内删除文件后再打快照,发现快照容量并没有变小。 ECS实例如何优化快照使用成本? 在ECS实例里什么是快照商业化? 在ECS实例里,快照商业化后过渡优惠期是什么时候? 在ECS实例里,快照商业化的用户范围包括有哪些? 在ECS实例里,如果我已经开通了 OSS,快照会自动存到我的 OSS Bucket 吗?是否需要重新再创建一个 Bucket 来存储快照? 已经购买了 OSS 预付费存储包,同时在使用快照和 OSS 服务,那么存储包会优先抵扣哪个产品? 快照商业化之后,我希望继续使用,需要购买哪个产品,云盘还是对象存储OSS资源包? 快照商业化的收费模式是怎样的? 快照费用的计算方法是怎样的? 快照收费后,不停止自动快照是否就开始收取费用? 快照要收费了,之前的快照要被删除吗? 如果不想付费,之前的快照能继续使用吗? 快照收费后,之前创建的手动快照和自动快照都会收费吗? 快照收费前停止快照策略,需手动删除历史快照吗?正式收费后会直接删除我的历史快照吗? 快照收费以后,账户欠费对快照有什么影响? 如果账号欠费,有关联关系(创建过磁盘或者镜像)的快照,在欠费15天之后是否会被删除? 快照服务和块存储服务的关系,在收费方面的关系是什么? 快照容量是如何计算的,是等于磁盘大小吗? ECS实例内删除文件会减少空间占用吗? 为什么快照容量大于文件系统内看到的数据量? 参考快照增量说明,如中间快照被删除,后面的快照能否使用? 如何开通快照服务? 快照和镜像的关系? 如何在保留关联实例和磁盘的情况下,删除快照跟镜像,快照、实例、镜像之间的关系? 快照和块存储、OSS对象存储是什么关系? 一块云盘能否设置多个快照策略? 快照 2.0 服务包括哪些内容? 快照有什么用途? 快照 2.0 服务支持的云盘类型? 快照数量有什么限制? 快照保留时长怎样? 打快照对块存储 I/O 性能有多少影响? 快照怎么收费? 老的自动快照策略什么时候不可用? 老的快照策略产生的快照什么时候删除? 自动快照功能细节有哪些? 用户的自定义快照和自动快照有冲突吗? 我能保留其中想要的自动快照而让系统不删除吗? 如果一个自动快照被引用(用户创建自定义镜像或者磁盘),会导致自动快照策略执行失败吗? 我如果什么都没有设置,自动快照会启动吗? 自动快照能够删除吗? 自动快照具体在什么时间创建能看到吗? 我如何区分哪些快照是自动快照和用户快照? 更换系统盘、云服务器 ECS 到期后或手动释放磁盘时,自动快照会不会释放? 未随磁盘释放和更换系统盘释放的自动快照会一直保留吗? 云服务器 ECS 到期后或手动释放磁盘时,手工快照会不会释放? 我能单独制定某几块磁盘执行或取消自动快照吗? 云服务器 ECS 有没有自动备份? 磁盘无快照是否能够回滚或数据恢复? 快照回滚能否单独回滚某个分区或部分数据? 系统盘快照回滚是否会影响数据盘? 更换系统后,快照能否回滚? 在回滚快照前,有哪些注意事项? 怎样使ECS回滚快照后同步数据? 如何通过API配置定时自定义快照? 超出预付费存储包的流量,会怎么收费? ECS镜像 Aliyun Linux 17.01 特性有哪些,有说明文档吗? 云市场镜像有哪些功能? 镜像能带来哪些便利? 目前镜像支持哪些服务器环境和应用场景? 镜像是否安全? 选择了镜像后能更换吗? 镜像安装使用过程中出问题了怎么办? Docker私有镜像库是什么? 自定义镜像如何查看数据盘? 自定义镜像,如何卸载和删除 disk table 里的数据? 如何确认已经卸载数据盘,并可以新建自定义镜像? ECS 实例释放后,自定义镜像是否还存在? ECS 实例释放后,快照是否还存在? 用于创建自定义镜像的云服务器 ECS 实例到期或释放数据后,创建的自定义镜像是否受影响?使用自定义镜像开通的云服务器 ECS 实例是否受影响? 使用自定义镜像创建的 ECS 实例是否可以更换操作系统?更换系统后原来的自定义镜像是否还可以使用? 更换系统盘时另选操作系统,是否可以使用自定义镜像? 已创建的自定义镜像,是否可以用于更换另一台云服务器 ECS 的系统盘数据? 是否可以升级自定义镜像开通的云服务器 ECS 的 CPU、内存、带宽、硬盘等? 是否可以跨地域使用自定义镜像? 包年包月云服务器 ECS 的自定义镜像,是否可以用于开通按量付费的云服务器 ECS? ECS Windows企业版和标准版区别 什么情况下需要复制镜像? 可以复制哪些镜像? 当前有哪些支持镜像复制功能的地域? 复制一个镜像大概需要多久? 复制镜像怎么收费的? 在复制镜像过程中,源镜像和目标镜像有什么限制? 怎么复制我的云账号的镜像资源到其他云账号的其他地域? 复制镜像有镜像容量限制吗? 如何购买镜像市场镜像? 按次购买的镜像的使用期限是多久? 镜像市场的镜像支持退款吗? 镜像市场商业化后,还有免费的镜像市场镜像吗? 在杭州买了一个镜像市场的镜像,能否在北京创建ECS实例或者更换系统盘? ECS实例使用镜像市场的镜像,升级和续费ECS实例,需要为镜像继续付费吗? ECS实例使用镜像市场的镜像,实例释放后,继续购买ECS实例还可以免费使用该镜像吗? 使用镜像市场镜像创建ECS实例,该实例创建一个自定义镜像,使用该自定义镜像创建ECS实例需要为该镜像付费吗? 来源于镜像市场的镜像复制到其他地域创建ECS实例,是否需要为该镜像付费? 如果把来源于镜像市场的自定义镜像共享给其他账号(B)创建ECS实例,账号B是否需要为该镜像付费? 如果使用镜像市场的镜像或者来源于镜像市场的镜像进行更换系统盘,需要付费吗? ECS实例正在使用镜像市场的镜像,进行重置系统盘需要收费吗? 怎么调用ECS API,使用镜像市场镜像或者来源镜像市场的自定义镜像或者共享镜像,创建ECS实例和更换系统盘? 如果没有购买镜像市场的镜像或者来源于镜像市场的镜像,在调用ECS API 使用该镜像创建ECS实例和更换系统盘,会报错吗? 我的ESS是自动创建机器的,并且量是不固定,设置最小值为10台,最大值为100台,那么使用镜像市场的镜像如何保证我的的需求实例能正常弹出来? 镜像市场的镜像是否支持批量购买? 如果之前使用的镜像市场的镜像,已不存在该商品(如:jxsc000010、jxsc000019),怎能保证已经设置的弹性伸缩组的机器的正常弹出? 1个product code能否支持不同region的镜像? 我买了100 product code同样值的镜像,是否可以支持在所有的地域可用? 为什么有的ECS云服务器无法选择Windows操作系统? 操作系统是否要收费? 我能否自己安装或者升级操作系统? 服务器的登录用户名密码是什么? 能否更换或升级操作系统? 操作系统是否有图形界面? 如何选择操作系统? 操作系统自带 FTP 上传吗? 每个用户最多可以获得多少个共享镜像? 每个镜像最多可以共享给多少个用户? 使用共享镜像是否占用我的镜像名额? 使用共享镜像创建实例的时候存不存在地域限制? 我曾把自己账号中的某个自定义镜像共享给其他账号,现在我可以删除这个镜像吗 我把某个自定义镜像(M)的共享账号(A)给删除了,会有什么影响? 使用共享镜像创建实例存在什么样的风险? 我把自定义镜像共享给其他账号,存在什么风险? 我能把别人共享给我的镜像再共享给他人吗? 我把镜像共享给他人,还能使用该镜像创建实例吗? ECS Windows服务器桌面分辨率过高导致VNC花屏处理方法通过 管理终端 进入服务器后,把 Windows 服务器桌面分辨率设置过高,确定后,WebVNC 出现花屏。 ECS创建自定义镜像创建服务器为何需要注释挂载项 勾选"IO优化实例"选项导致购买ECS实例时无法选择云市场镜像 如何为 Linux 服务器安装 GRUB 历史Linux镜像的问题修复方案 如何处理 CentOS DNS 解析超时? 什么是镜像市场的包年包月和按周付费镜像? 预付费镜像能与哪种 ECS 实例搭配使用? 怎么购买预付费镜像?可以单独购买吗? 预付费镜像怎么付费? 预付费镜像到期了就不能用了吗?怎么继续使用? 购买预付费镜像后,如果我不想再使用这个镜像,能要求退款吗? 退款时,费用怎么结算? 预付费镜像能转换为按量付费镜像吗? 预付费镜像与其它镜像之间能互换吗?更换后费用怎么计算? 在哪里查看并管理我购买的预付费镜像? 使用预付费镜像制作的自定义镜像会收费吗?预付费镜像过期对于自定义镜像有什么影响? ECS 实例操作系统选择说明 阿里云支持哪些 SUSE 版本? SUSE 操作系统提供哪些服务支持? ECS安全组 如何检查 TCP 80 端口是否正常工作? 什么是安全组? 为什么在购买 ECS 实例的时候选择安全组? 安全组配置错误会造成哪些影响? 专有网络实例设置安全组规则时为什么不能设置公网规则? 创建 ECS 实例时我还没创建安全组怎么办? 为什么无法访问 25 端口? 为什么我的安全组里自动添加了很多规则? 为什么有些安全组规则的优先级是 110? 为什么我在安全组里放行了 TCP 80 端口,还是无法访问 80 端口? ECS安全组被添加内网ip地址了,是怎么回事? 能说明下ECS安全组中规则的优先级执行匹配顺序吗? ECS实例安全组默认的公网规则被删除导致无法ping通,ECS 服务器无法ping通,排查防火墙、网卡IP配置无误,回滚系统后仍然无法ping通。 我刚购买了ECS实例,如何选择及配置安全组? 没有添加默认安全组访问规则-导致通过API创建的ECS实例断网,要怎么恢复? 使用ECS安全组工具撤销之前账号间互通的操作 ECS网络 带宽与上传、下载速度峰值的有什么关系? 弹性公网IP在哪里可以查看流量和带宽监控信息? 我用的是ECS Ubuntu系统,要怎么单独禁用和启动内外网卡? ECS 实例子网划分和掩码是什么? ECS 实例网络带宽是否独享? 带宽单线还是双线,电信还是网通? 5 Mbps 带宽怎么理解? 带宽的价格是多少? 不同地域的 ECS 实例之间的内网是通的吗? 为何新建的 ECS 实例就有 200 Kbps 左右入网流量? 我的 ECS 实例经常能在 Web 日志中看到大量的恶意 IP 访问我的网站,疑有刷流量和恶意访问的嫌疑,询问云盾是否有屏蔽 IP 的功能? 包月ECS新购时是否可以选择带宽按照使用流量计费? 包月ECS带宽按流量计费是如何计费的? 目前使用的固定带宽计费,是否可以转换为带宽按流量计费? 是否可以随时调整流量带宽峰值? 续费变更配置时(比如到期时间为2015年3月31日,续费一个月到4月30日),如果将包月ECS按固定带宽计费改成按流量付费计费,操作以后在未生效前(3月31日前),是否还可以升级带宽? 续费变更配置时候将包月ECS带宽按流量计费改成按固定带宽计费,为什么我的带宽服务停掉了? 如果账号没有足够余额,欠费怎么办?ECS实例也会停掉吗? 带宽流量欠费是否有短信通知? 当带宽按照流量计费欠费时,是否可以对实例进行升级 CPU、内存操作? 欠费充值后带宽是自动恢复的吗? 包月带宽转流量计费后,流量价格是多少? ECS 服务器出现了异地登录怎么办? 爱哪里可以查看云服务器 ECS 公网流量统计总和? 我的ECS 实例对外 DDoS 攻击导致被锁定了,要如何处理 ? 什么是云服务器 ECS 的入网带宽和出网带宽? ECS云服务器如何禁用公网IP? ECS 实例停止(关机)后按量付费带宽仍产生流量,ECS 实例在控制台上状态为已停止,但按量付费的带宽每小时仍会产生不小的费用,且此时 ECS 实例正在遭受攻击,云盾控制台中 DDoS 防护中 ECS 的状态为清洗中。 访问ECS服务器的网站提示“由于你访问的URL可能对网站造成安全威胁,您的访问被阻断”,这是什么原因? 服务器黑洞是什么?求科普! 如果想确认该服务器的IP信息和地理位置,要在哪里去查询? 我想知道客户端本地到ECS服务器是不是丢包,要怎么测试? 内网和公共 NTP 服务器是什么?它们两个有什么区别 我能 ping 通但端口不通,这是端口的问题吗? 如何通过防火墙策略限制对外扫描行为? 我想用手机移动端网络路由跟踪探测,可以吗? 云监控中的ECS带宽和ECS控制台中看到的带宽不一致是什么原因? 云服务器ECS三张网卡有什么区别? Ubuntu系统ECS使用“如何通过防火墙策略限制对外扫描行为”脚本之后出现无法远程、数据库连接不上。 什么业务场景需要在专有网络(VPC)类型ECS购买PublicIP? 怎么购买专有网络(VPC)类型分配 PublicIP 的 ECS? 专有网络(VPC)类型 ECS 的 PublicIP 和 EIP 的区别? 专有网络(VPC)类型ECS的 PublicIP 的可以升级带宽吗? 专有网络(VPC)类型ECS的 PublicIP 可以解绑吗? 如果购买网络(VPC)类型 ECS 的时候,没有分配公网 IP,该怎么才能分配一个公网 IP? 怎么查询专有网络(VPC)类型 ECS 的 PublicIP 的监控数据? 怎么查询专有网络(VPC)类型ECS的按流量付费的 PublicIP 的账单? 专有网络和经典网络的 PublicIP 异同? 专有网络(VPC)类型 ECS 购买 PublicIP 的付费方式? ECS API 如何通过 API / SDK 实现不同账号 ECS 实例的内网通信? ECS API绑定公网IP报错:The IP is already in use分析 ECS API修改实例带宽不能指定时间范围吗? 所在可用区不支持相应磁盘类型-导致ECS API创建实例报错 用ECS API创建实例的时候,返回如下错误信息: "Code": "InvalidDataDiskCategory.NotSupported" 如何创建有公网 IP 的 ECS 实例? 通过API或SDK查询安全组规则无法显示所有的规则,这是怎么回事? 如何通过OpenAPI创建ECS实例的流程状态描述? 数据传输服务DTS实时同步功能,我想只同步表结构,要怎么做? 如何获取控制台RequestId? 阿里云中国站部分地域实例什么时候降价? ECS Linux 实例怎么设置 Locale 变量? 克隆ECS服务器的方法 其它国家和地区是否都可以提供经典网络和专有网络的类型呢?网络类型是否可以变更呢? 各个地域的网络覆盖范围是什么呢? 其他相关问题 不同地域的实例,价格一样吗? 如果我使用其它国家和地区的实例搭建了一个网站,我的用户将通过域名访问网站,这个域名需要 ICP 备案吗? 为什么有些实例规格只能在中国大陆地域购买,而在其它国家和地区无法购买? 可否将中国大陆地域的实例迁移到其它国家和地区呢? 如何在其它国家和地区部署 ECS 实例? 我要买其它国家和地区的实例,需要单独申请一个国际站账号吗? ——更多ECS相关问题—— · ECS故障处理百问合集

问问小秘 2020-01-02 15:49:17 0 浏览量 回答数 0

问题

2018python技术问答集锦,希望能给喜欢python的同学一些帮助

技术小能手 2019-12-01 19:31:10 2040 浏览量 回答数 2

回答

Netty的worker线程只负责nio,在收到完整数据后将数据按要求封装并放入到业务数据队列;业务处理类负责从该队列中取出数据并处理。 这里的业务处理类现在是如何实现的?按你的说法,单线程和多线程 在这个类中都试验过,并且都没能解决问题,由此来看 可以得出2个结论:(1)需要再努力优化业务处理过程以节省处理时间;(2)提升服务器硬件性能。######回复 @阿森lin1991 : 我也是碰到这个问题,单位时间内大量客户端同时连接上来,服务端线程来不及处理。就大量堆积在队列里,请问有办法解决吗?######回复 @阿森lin1991 : 你netty什么版本?netty3和4的线程模型有不小区别,推荐infoq上李林峰写的《netty升级血泪史》######如果netty没有相应api接口的话,那就无解了。看看新版本中是否有,或者可以参考下######回复 @阿森lin1991 : 回复 @阿森lin1991 : 关键是netty接收消息队列消息时造成的阻塞;netty3.0中有ExecutionHandler可以使用(其实也是一个线程池,work执行到ExecutionHandler时直接返回执行下一个channel);我现在也遇到这样的问题,希望可以找到一起其他的解决办法,比如非阻塞接收消息队列消息。######2:接第1条...所以想把消息输出也放在nioEventLoopGroup(worker)线程中执行,即业务处理完后把输出消息压入输出队列,但是怎样才能调用nioEventLoopGroup(worker)线程去处理这个输出队列了?好像没有相关接口###### 1  netty本身的 worker线程的个数是根据CPU来的,直接在 worker线程里做业务逻辑处理不好么? 2 如果不想并发,修改源码,让worker线程个数为1,就没有并发了,这一点跟redis一样的,redis单线程的处理能力貌似也够用了,redis的作者是这么说的。 3 为啥要自定义多个业务逻辑线程?netty本身的worker线程拿到消息后就可以处理了啊 ######回复 @阿森lin1991 : 没必要为每个消息加业务逻辑处理线程,并发量多,线程自然多,这样跟IO模型就没区别了。收到数据后消息处理直接用worker线程,当你预估的业务逻辑实在是太费资源才开一个线程,这个线程中尽量不要有类变量已减少并发错误或人为加锁。实在不能满足需求,可以考虑用RMI把复杂逻辑放到另外的机器上做分布式处理######1.worker线程更多的负责读写网络数据,对于复杂或耗时的业务处理都交由自定义的逻辑线程处理,不然很可能阻塞nio线程,大大减少并发量。 2.我现在的情况不是worker线程并发有问题,而是自定义了逻辑线程并发有问题(阻塞情况比较严重) 3.同1 不过谢谢你...###### 你现在的问题跟Netty没有关系,主要是你的业务处理速度跟不上你所要求的请求速度,单线程也好,多线程也好,都没有关系。 处理不过来, 1,要不把超时的改掉或做优化处理 2,增强处理速度:找到瓶颈优化或者做请求分发到不同服务器处理 ######同意这种说法,最好是将业务线程能够优化######(2)提升服务器硬件以提高业务处理性能。######楼主你好,请问这个问题解决了吗?我先在也是遇到了这问题。######单机环境调优讲一种方法吧。 1. 明确你的优化目标(优化是永无止境的,但必须适可而止) 2. 分析你的硬件瓶颈(归根到底,还是你的硬件在执行软件代码), 比如你的核,内存,带宽(本例中注意下你的带宽拥挤是否延迟你的消息返回) 3. 根据你的目标调整Netty的BoosEventLoop, WorkEvnetLoop,Buffer大小。 4. 优化你的消息包,尽量在一个MTU大小,优化你的编解码工具类,比如使用Protobuffer(传输小,解码快)代替Json.  另外,特别注意Bytebuf转Message后,是否有被ReferenceCountUtil.release() 5. 消息的返回注意 chanel的write跟writeAndFlush的区别。一个是等缓冲区满了才返回,一个是立刻返回。 上面做完了,就跟netty没啥关系了。 针对你的 编解码Loop线程组 与 工作线程组 的优化 Netty WorkEvnetGroup = M,   BusinessWorkerGroup = N  ( M, N >1) 这种情况就是一个生产消费模型,M, N之间有一个ArrayBlockingQueue(必需限制上限)做消息缓存。 1. 为了减少锁竞争,可以使用 无锁队列 Disruptor代替 java的 ArrayBlockingQueue, 据说效率是后者的10倍 2.工作任务代码优化,可以全内存操作以及算法优化。######业务服务是否可以分析出单独微服务啊

kun坤 2020-06-08 19:18:03 0 浏览量 回答数 0

问题

图解九大数据结构 6月13日 【今日算法】

游客ih62co2qqq5ww 2020-06-17 13:17:00 29 浏览量 回答数 1

问题

图解!24张图彻底弄懂九大常见数据结构! 7月22日 【今日算法】

游客ih62co2qqq5ww 2020-07-27 13:19:32 6 浏览量 回答数 1

回答

在我们进行合理讨论之前,需要澄清和解决某些问题。 前提条件解决 标签 在需要精确度的行业中,重要的是我们使用精确的标签,以避免混淆,以便我们可以进行交流而不必使用冗长的描述和限定词。 。 什么你已经张贴FixedTables,是Unnormalised。足够公平,可以尝试使用“第三范式”形式,但是实际上它是一个平面文件,非规范化(不是“非规范化”)。确切地说,您发布为AbstractTables的是Entity-Attribute-Value,几乎,但不完全是第六范式,因此比3NF规范化得多,当然,假设正确完成。 未规范化的平面文件未“非规范化”。它充满了重复(没有做任何操作来删除重复的组和重复的列或解决依赖项)和Null,它在许多方面都是性能浪费,并防止了并发。 为了进行Denormlaise,必须先对其进行归一化,然后出于某些充分的理由而使归一化稍微后退。由于首先没有对其进行归一化,因此无法对其进行归一化。它只是未归一化。 不能说它是“为了性能”而被非规范化的,因为它是性能猪,它与性能完全相反。好吧,他们需要缺乏形式化设计的理由],“为了性能”就可以了。即使是最小的正式审查也暴露了错误的陈述(但是很少有人可以提供,因此它一直隐藏着,直到他们让局外人解决,您猜对了,这是巨大的性能问题)。 规范化结构的性能远优于未规范化结构。标准化程度较高的结构(EAV / 6NF)比标准化程度较低的结构(3NF / 5NF)更好。 我同意OMG小马的主旨,但不同意其标签和定义 而不是说“除非必须,否则不要“非正规化””,而是说“忠实地标准化,定期”和“如果存在性能问题,则表示您未正确标准化”。 。 Wiki 关于Normal Forms和Normalization的条目完全是个笑话。具体来说,这些定义是不正确的。他们混淆了普通表格;他们对规范化过程一无所知;它们对很久以前就被揭穿的荒谬或可疑NF给予同等的重视。结果是,Wiki增加了一个本已混乱且鲜为人知的主题。因此,不要浪费您的时间。 。 但是,为了取得进展,在没有该提法构成障碍的情况下,我要说这句话。 3NF的定义稳定,没有改变。 3NF和5NF之间存在很多NF混淆。事实是,这是过去15年中取得进展的领域。许多组织,学者和供应商都对其产品进行了限制,他们跳起来创建了一个新的“ Normal Form”来验证他们的产品。所有服务于商业利益和学术上不健全。3NF处于其原始未篡改状态,旨在并保证某些属性。 总的来说,今天的5NF就是15年前3NF的目标,您可以跳过商业玩笑和两者之间的大约十二种“特殊”(商业和伪学术)NF,其中一些是在Wiki中识别,甚至用混淆的术语表示。 。 由于您已经能够理解和实施帖子中的EAV,因此理解以下内容将没有问题。当然,真正的关系模型是先决条件,强键等。第五范式是,因为我们跳过了第四种: 第三范式 简单来说,每个表中的每个非键列与表的主键之间具有1 :: 1的关系, 并且没有其他非关键列 零数据重复(结果,如果勤奋地进行标准化,则不是单靠智力或经验,或者是通过努力将其作为目标而没有正式过程来实现) 无更新异常(当您在某处更新一列时,不必更新位于其他地方的同一列;该列存在于一个且仅一个位置)。 。 第六范式当然是第五范式,再加上: 消除丢失的数据(列)。这是Null问题(也称为处理缺失值)的一种真正解决方案,结果是没有Nulls的数据库。(这可以在5NF下使用标准和Null替代品完成,但这不是最佳选择。)如何解释和显示缺失值是另一回事。 。 EAV与第六范式 的比较我编写的所有数据库(除一个以外)都是纯5NF。我已经使用(管理,修复,增强)了几个EAV数据库,并且已经实现了一个真正的6NF数据库。EAV是6NF的宽松实现,通常由对标准化和NF不太了解但可以看到EAV的价值并需要EAV灵活性的人员完成。你是一个完美的例子。区别在于:因为它比较松散,并且因为实现者没有忠实的参考(6NF),所以他们仅实现所需的东西,并全部用代码编写;最终导致模型不一致。 。 鉴于纯6NF实现确实具有纯学术参考点,因此通常更加严格且一致。通常,这显示在两个可见元素中: 6NF有一个包含元数据的目录,并且所有内容都是在元数据中定义的,而不是代码。EAV没有一个,一切都在代码中(实现者跟踪对象和属性)。显然,目录使添加列,导航变得容易,并允许形成实用程序。 当理解6NF时,它可以真正解决Null问题。EAV实现者由于缺少6NF上下文,因此会不一致地处理代码中丢失的数据,或者更糟的是,允许数据库中的Null。6NF实现者禁止使用Null,并一致而优雅地处理丢失的数据,而无需代码构造(对于Null处理;当然,您仍然必须为丢失的数据编写代码)。 。 例如。对于具有目录的6NF数据库,我有一组proc将[重新生成]执行所有SELECT所需的SQL,并且我为所有用户提供5NF视图,因此他们不需要了解或理解底层6NF结构。 。他们被驱逐出目录。因此,更改是容易且自动化的。由于没有目录,EAV类型手动执行此操作。 现在,我们可以开始 讨论区 “如果预先定义了值,那么当然可以更加抽象(例如:专业可以拥有自己的列表)” 当然。但是不要太“抽象”。保持一致性,并以与其他列表相同的EAV(或6NF)方式实施此类列表。 “如果我采用抽象方法,它可能会非常灵活,但是带有许多联接的查询将变得更加复杂。但是,我不知道这是否会影响这些“更复杂”的查询的性能。” 关系数据库中的联接是行人。问题不在于数据库,问题在于处理联接时,SQL非常麻烦,尤其是复合键。 EAV和6NF数据库具有更多的Joins,它们与行人一样多。当然,如果您必须手动编写每个SELECT的代码,那么麻烦就变得很麻烦。 可以通过(a)在EAV上使用6NF以及(b)实施目录来消除整个问题,从中可以(c)生成所有基本SQL。也消除了整个错误类别。 一个普遍的神话是,加入某种方式会产生成本。完全错误。该联接是在编译时实现的,对于“成本” CP​​U周期没有实质性影响。问题是要联接的表的大小,而不是这些相同表之间的联接的成本。在正确的PK⇢FK关系上连接两个表,每个表具有数百万行,每个表具有适当的索引(在parent [FK]侧为唯一;在Child侧为唯一)。; 如果Child索引不是唯一的,但是至少前导列是有效的,则它慢一些;没有可用索引的地方,那当然很慢。它与加入成本无关。在返回许多行的地方,瓶颈将是网络和磁盘布局。不是加入处理。 因此,您可以随心所欲地获得“复杂”的东西,没有成本,SQL可以处理它。 我想知道这两种方法的优点和缺点。我可以自己想象,但是我没有经验来确认这一点。 就实施,易用性(开发人员和用户),维护而言,5NF(对于尚未取得进展的人而言,则为3NF)是最简单,最好的。缺点是,每次添加列时,都必须更改数据库结构(表DDL)。在某些情况下很好,但在大多数情况下不是这样,因为适当的变更控制非常繁重。其次,您必须更改现有代码(处理新列的代码不算在内,因为这势在必行):在实施好的标准的地方,这要最小化;如果没有它们,范围是不可预测的。 EAV(这是您发布的内容)允许添加列而无需DDL更改。这就是人们选择它的唯一原因。(处理新列的代码不计算在内,因为这是必须的)。如果实施得当,它将不会影响现有代码;如果没有,它将。但是您需要具有EAV功能的开发人员。当EAV实施不当时,这是可恶的,这比5NF实施得不好更糟,但不会比大多数数据库都存在的Unnormalized更糟(错误地表示为“性能非常规”)。当然,拥有强大的Transaction上下文(比5NF / 3NF更为重要),因为列的分布远不止这些。同样,必须保持声明式参照完整性:我所看到的混乱很大程度上归因于开发人员删除了DRI,因为它已成为“ 假设已经针对预期目的合理配置了服务器,则性能没有差异。(好吧,只有在6NF中才有可能实现特定的优化,而在其他NF中则无法实现,但是我认为这超出了本线程的范围。)同样,EAV做得不好会造成不必要的瓶颈,仅此而已。未规范化。 当然,如果您使用EAV,我建议您提供更多的手续;买完整的交换;配6NF;实施目录;产生SQL的实用程序;意见;始终处理丢失的数据;完全消除Null。这减少了您对开发人员质量的脆弱性;他们可以忘记EAV / 6NF深奥的问题,使用Views并专注于应用程序逻辑。 请原谅。来源:stack overflow

保持可爱mmm 2020-05-13 14:49:13 0 浏览量 回答数 0

回答

一、系统迁移捅了13亿用户的娄子 故事,是从一桩“离婚再嫁”的案子开始的。 离婚再嫁的主角,是英国银行TSB。 2015年,TSB银行结束了与劳埃德银行(Lloyds Bank)长达20年的“婚姻”,从他们合并的集团中拆分出来,并卖身给了新欢、西班牙公司萨瓦德尔(Sabadell)集团,收购价17亿英镑,按当时的汇率大概是158亿人民币。 然而,过去的20年,世界变了太多,银行业也进步了太多。20年的“婚姻”留给TSB银行的,还有和“前夫”剪不断理还乱的IT系统。 TSB银行540万客户的数十亿记录,都还留在“前夫”劳埃德银行的系统里,而且因为缘分已断,不能白嫖人家的系统,每年还要给前夫交1亿英镑(大约9.3亿人民币)的费用。 这就好像肉身虽然已经和“新欢”在一起,但支付宝和微信账号还是跟“前夫”共用一套,而且还要给“前夫”付账号租金,自然令人不爽。 于是,在筹备了许久之后,2018年,他们终于要行动了:把“前夫”IT系统里的客户信息记录,迁移到“新欢”专门为TSB银行准备的新系统里。 他们把迁移的日子,定在了4月22日星期日的晚上,先把银行的IT系统离线,迁移完之后再上线,恢复客户访问自己银行账户的权限。 为了这场迁移,他们已经投入了超过2500人年的人力成本,西班牙“新欢”集团的CEO在前一年的圣诞节就大声放话:这是全欧洲史无前例的大项目,我们投入了1000多名专业人才,将极大地促进我们在英国的增长。 不过,虽然大佬们在台上豪言壮语,实际上负责迁移的员工们心里却慌得一逼。这个迁移项目本来要筹备18个月,结果时间超了,预算也超了,事情难办的很。 Flag果然不能立太早,打脸的结果很快就来了。 迁移结束,客户的访问权限,他们以为万无一失,但就在20分钟后,收到了问题报告: 有的客户发现自己的钱不见了; 有的客户花了一点小钱,账户里却记录成了花费数千美元; 有的客户登录上去之后,发现不是自己的账户,而是看到了别人的银行账户。 13亿客户的账户记录都出了问题,于是,他们把TSB银行骂成狗,金融监管机构们则连夜找银行喝茶。 而此后的几个星期,银行都在拼命的恢复系统,但数以百万计的客户们已经人心惶惶,拼命的把自己存在TSB银行的钱取出来。 TSB银行,被自己捅的篓子扔进了地狱模式。 而问题的根源,在于测试。 英国金融监管机构金融行为监管局(FCA)首席执行官Andrew Bailey在事故几周后对外公开表示,造成系统混乱的很大原因在于缺少测试,而TSB银行请来救急的IBM专家也发现,TSB银行没有采用严格的上线标准。 而且由于地球上的金融体系都是相连的,事故所造成的错误被永久的保留在了金融体系里,不可逆转。 这起弥天大祸,也让TSB银行赔了很多钱。为了赔偿客户、解决系统出问题后浑水摸鱼的交易、找第三方帮忙总共花了3.302亿英镑,按当时汇率算大约28.4亿人民币。 而TSB的乙方、IT提供商Sabis也因为这起事故收到了1.53亿英镑(超过13亿人民币)的赔偿账单。 而受此影响,TSB银行当年亏损了1.054亿英镑(9.2亿人民币),CEO Paul Pester引咎辞职。 业绩这么差,银行的经营也难以为继,今年11月底TSB关闭了英国86个分行,至少400个工作岗位也因此消失。 二、银行系统很复杂 信息化时代,银行的IT系统也变得越来越复杂。 六十年前,人们只能选择在柜台存取现金,普通客户并没有机会直接接触计算机系统。当时,银行虽然也启用了巨型计算机,但它们只会在一天或一周交易结束的时候对纸质数据进行汇总。 也就是说,银行的IT系统仅由银行员工使用,银行与客户在柜台上的交互用的还是纸质工具。 这种情况在1967年发生了改变。 这一年,世界上第一台自动柜员机(ATM)在英国诞生,并被安装到伦敦北部的巴克莱银行Enfield分行。从此,银行和客户交互的方式发生重大变革。 ITRS Group首席执行官盖伊·沃伦(Guy Warren)解释说: 直到真正的ATM和在线银行业务出现,公众才可以直接访问银行的IT系统。 这还仅仅是个开始。 全球互联的时代,互联网和移动银行的发展进一步拉近了客户和银行IT系统之间的距离,而这样的系统,也越来越成为银行赖以运营的关键所在。 或许你会觉得,登个支付宝/微信,亮出付款码,让小钱钱在银行跟银行之间发生小小的流动,并没有什么难度。但事实上,每一次信息的加载和刷新背后,都发生了复杂的数据移动: 每一次动作可能关联到许多个单独的系统,所有这些系统都必须彼此交互,并与核心大型计算机连通。系统要现在后端复制数据,将现金从一个账户转移到另一个账户,保持同步更新。 而这样的运算量,还要乘以数十亿倍。 根据世界银行的数据,现在,全球至少有69%的成年人都拥有银行账户。人们每一天都在通过银行账户支付账单、贷款还款、订阅各种服务……并且,这些活动常常是跨行,甚至跨国进行的。 一家银行内部的多个IT系统(移动银行、ATM等),不仅需要彼此交互,甚至还必须跟其他国家的银行建立联系。比如我在国内办了一张visa信用卡,在美国也要能消费才行。 三、迁移问题很麻烦 TSB正是栽在了这样的高度复杂性上。 IBM在为TSB编写的报告中指出:新应用程序的组合,对先进微服务的应用和双活数据中心的使用,导致了TSB生产中的复合风险。 如何正确地处理银行IT系统迁移中出现的问题,对于任何一个银行来说,都是不小的挑战。 其中,大量的事前规划和测试工作是不可避免的。 像汇丰银行这样的跨国银行,具有高度复杂、相互关联的系统,这些系统会定期进行测试、迁移和更新。 即使在这方面如此经验丰富,汇丰银行的前IT主管兰开斯特仍坦承:诀窍就是让员工在这件事上付出更多的时间。 他还指出,TSB的IT系统迁移是一件很复杂的事: 我不确定他们是不是真的意识到了这件事的复杂程度。他们甚至没有完全想好要怎么去测试系统。 FCA首席执行官Andrew Bailey则表示: TSB的这一事故反映出他们缺少强大的回归测试。 注:回归测试是软件测试的一种,旨在检验软件原有功能在修改后是否保持完整 而最新的事故报告也引起了hacker news上网友们的热烈讨论。 有网友表示,如果TSB能选择小规模多次迁移,而不是在某一天进行大爆炸式迁移,那这种严重的事故可能就不会发生。 花几周/几个月的时间在生产过程中进行检查,以确保旧数据库和新数据库返回的结构相同。最终,将数据都转移到新数据库中,并在一段时间之后再关闭旧的数据库。这样做效果是比较好的。 而对测试不足导致了银行系统瘫痪的这一调查结论,有人吐槽说: 作为测试工程师,我一点也不意外。花费更多的时间、投入更多的人员来打造更好的测试架构,对于很多公司来说都是“可以节省的成本”。 经理们总是在设定的上线日期前问:“测试咋能花那么多时间?!”真要出事了他们又开始甩锅了。 也有网友严厉批评道:TSB的问题不应该说是测试不足,而是在多个层面上都测试不足,并且缺少可恢复的备份。 也有人指出,避免出错最简单的办法就是减少变化。 问题在于,无论是银行还是其他领域的公司,业务都是在不断进化的。 根据FCA发布的数据,从2017年到2018年,英国金融服务部门报告的技术中断增加了187%。 盖伊·沃伦就认为:系统停机不会消失。问题在于,可接受的度在哪里? 你怎么看呢?在评论区留下你的看法~

有只黑白猫 2020-01-20 11:22:13 0 浏览量 回答数 0

回答

如何掌握牢靠Go语言的容器? 容器相对来说更偏重细节一些,如果想掌握的更牢靠的话呢,还是要多看一下代码,重点给大家几个提示 Go语言的并发初步有哪两个特别重要的特点? **GO语言的协程并发操作或者说协程的资源池,其调度策略有两个: ** 1、没有优先级,没有API能设置优先级,正是因为它一切都是靠Go语言自身的一个调度器来听调度,才能保证它的高效率,这点非常重要。 2、调度的策略是可抢占的,假如说一个任务它长时间的占用CPU,那么它是有可能被购入天的这个调度器给其抢占过来,让其其的任务来做运行,这是两个最重要的特点。 GO语言调度的单元goroutine的应用场景是什么? 使用JAVA或者C编写网络程序时,一个线程来处理一个http请求, 但是对于资源的利用率不高。而Go语言实现了轻量级线程的机制,GO语言在底层封装了所有的系统调用,自己实现了一个调度器,这种设计在操作系统的代码中非常多见。比如现代的操作系统基本都会封装一个软件的Timer,同时可以提供上万个软Timer同时工作,而这只是基于数量很少的硬件timer实现的,而GO语言中的并发也是如此,他是基于线程的调度池,这种调度的单元在Go语言中被称为goroutine。 GO语言与其它并发模型最大的区别是什么? 宏观GO语言与其它并发模型最大的不同,就是其推荐使用通信的这种方式来替代共享内存。当资源需要在goroutine之间进行共享的时候,实际上就是这个资源,或者说这个信息通过通道在goroutine之间进行通信的过程。因为这个锁,一般来说都是用在这个共享内存当中的,因为如果说大家阅读GO语言的相关代码,就可以看到这个channel,它实际上是基于锁来保证并发安全。 然而,这也不代表GO语言当中只能使用channel来进行一些操作,其也具备锁这方面的知识。因为现实当中,这个锁还是有一定它现实的意义和现实的要求,因为这个锁它最关键的一个意义就是它能保证资源能在并发的操作当中有一个合理的调度情况和调度策略。其中跟这个最重要,或者说最关联性最强的一个概念就是原子操作。 GO语言中的原子操作具体实现过程是怎样的? 对于原子操作,在其逻辑下,按照它书面的定义上来讲,是指不会被调度器打断的操作。对原子操作实际上就是不存在中间状态的一种操作,要不就全成功,要不全失败,这个在我们在用并发方式来调动某任务,或者说来设计某种并发系统的情况下,这种名字操作我发现是非常重要的设计理念之一。 并发与并行具体概念及实际区分是怎样的? 有一个比较重要的一个概念,就是并发与并行,其实并发与并行,它实际上具体的含义是不一样的,并发实际上是把任务在不同的时间点交给同样一个处理器来进行处理,在同一个时间点,任务不会同时进行,只是任务感觉自己正在执行,因为其那会儿可能正在堵塞状态或者说是就绪状态,其不知道自己被暂停了,以为已经被调度走了,可能自己没有感知,但是实际上CPU所有权已经不在这个任务身上了。 并行比并发更高级一些,它实际上是把每个任务都交给独立的处理器去进行完成,但同一时间点,任务在一定程度上实际上是同时在执行的。一般来说,并发的性能是要比并行更重要一些,在1.5版本之前,我们需要人工去设置GO调度器最多能运行在多少个CPU上,但是在最新的GO版本当中,已经不需要这个相关的操作。 详细介绍一下并发程序中的竞争态? 并发系统设计最初始的这一个概念就是并发程序设计当中一个竞合的概念,或者也叫竞争态。假如说我要记录一个文件的阅读量,但是这个文件或者说这个网页,可能它的阅读渠道有非常多,有可能通过引擎通过微信通过APP等等这些渠道,这些渠道的话呢,它的阅读也都是并发的,这就会涉及到同样一个变量,被多个协程的所共同访问的情况。具体代码如下: 对于GO语言并发体系中的主推的通信机制是什么? channel是GO语言并发体系中的主推的通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型,也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。 GO语言当中,它实际上是大家协同的机制,通过这种方式让几个goroutine之间做达到一个协调的效果,那么每个goroutine当中,实际上channel都是一个特殊的类型,它实际上是可以发送数据。比如现在想发送一个int类型的数据,那么channel就要定义一个发送int数据的一个管道。 那么GO语言当中,提倡使用通讯的方式来代替共享内存的方式来做goroutine,或者说并发之间的一个协同。channel如果我们后续阅读它的代码就会知道,它是保证协程安全,并且它遵循这个先入先出的原则来让这个储蓄方读取获得数据,而且它能保证顺序,正是这两个特性,可以让这个channel替代共享内存,因为它的如果顺序有所改变的话,它实际上也是有会有问题。 详细介绍GO语言中关于通道的声明涉及哪些方面? 1.经典方式声明 通过使用chan类型,其声明方式如下: var name chan type 其中type表示通道内的数据类型;name:通道的变量名称,不过这样创建的通道只是空值 nil,一般来说都是通道都是通过make函数创建的。 2.make方式 make函数可以创建通道格式如下: name := make(chan type) 3.创建带有缓冲的通道 后面会讲到缓冲通道的概念,这里先说他的定义方式 name := make(chan type, size) 其中type表示通道内的数据类型;name:通道的变量名称,size代表缓冲的长度。 具体介绍通道数据收发的详细过程有哪些? 通道的数据发送 通道当中发送数据的操作服务是这样的这样的一个大于号加上一个减号。 chan <- value 注意,如果是发送给一个没有缓冲的一个通道。假如说数据没有被接收的话,那么这个发送操作将持续被注册,也就是说就是channel这个语句就直接被注册到这,假如说没有任何的协程去读到他或者其他语句去读到这个产品,那么这个语句就被注册掉了。但GO语言是能发现的,如果其一直在堵塞的话,那实际上就造成死锁,GO语言的编译器实际上能发现的有点错误。 假如说,首先创建一个int型的通道,然后直接尝试发送一个数据给它,编译会报错,然后呢,数据的这个数据的接收的话,实际上就是把这个点号的位置跟那个大于号的位置做了一个调换。其实把这个双方的位置做了一个调换之后,是实际上就是都做了一个允许的操作。这其中的话呢,还有一种比较特殊的一个读取操作是其可以忽略到接收到的数据,因为不管管道中发出的数据,如果没读的话就堵塞到这,那么如果你觉得这个语句你也不需要,那么你可以把那个变量给它忽略掉。 2.通道的数据接收 通道接收数据的操作符也是<-,具体有以下几种方式 - 1) 阻塞接收数据 阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下: data := <-ch 执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。 如需要忽略接收的数据,则将data变量省略,具体格式如下: <-ch - 2) 非阻塞接收数据 使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下: data, ok := <-ch 非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。一般只配合select语句配合定时器做超时检测时使用。 关于通道数据收发有哪些需要注意的事项? 通道数据在进行输入收发的时候,必须要在两个不同的goroutine当中进行,因在同一个goroutine当中,收发的这些语句实际上都是堵塞的,你可能在同一个goroutine当中,它的这个函数已经在那边阻塞住了,或者说程序已经在那边阻塞住了,它已经停在那了,你后面有一句你能执行不到,所以说通道的收发必须在两个不同的goroutine之间来进行,在同一个goroutine之间的这个收发操作的话,实际上是没有意义的。 接收将持续堵塞,直到发送方发送出去,如果接收方接收,然后通道中没有发送方数据时,接收方也会发送,直到发送方到发送数据为止。就是刚才说的这个一体两面,这个发送方假如说没有人读的话,发送方会堵塞,假如说没有人写的话,那么接收方也会发生堵塞,这两边实际上都会有一个堵塞的情况。那么这个通道的收发的话呢,一般来说一次只能收一一个元素,假如说这个是一个有缓冲的一个通道,我通过一次不操作的话,实际上也只不过读出一个元素。不能把它一些缓冲区所有元素都读出来。 聊一下生产者消费者模式具体内容有哪些? 介绍一下生产者消费者模式,从GO语言的这个并发模型来看,也就是说假如说咱们站在一个比较高的一个高度来看,其实利用channel的确能达到共享内存的目的。这个channel的性质与在读写状态且保证顺序的共享内存并无不同。甚至我们可以说这个是基于消息队列的封装程度可以比共享内存来的更安全,所以说呢,这个在这个GO语言当中,或者说在GO语言的这个设计风格当中的话呢,其这个生产者消费者模式实现起来会相对来说比较简单一些。我们先介绍一下什么是生产者消费者。 就这个这这张图当中的话呢,就是一个典型的那种消费的问题, 就是说我是生产者的话我会生产一些产品,然后放到这个仓库当中,消费者的话会从那个仓库当中去取商品,这个可以说是消息队列,还有包括卡夫卡那些比较经典的相应队列当中,都会用到的这么一个设计模式,或者说其们从本质上来说的话,都是基于这样一个设计模式,交易的生产者是谁?消费者是谁?这个消息队列的话是。这个生产者消费者模式的话呢,实际上也成为有缓冲有限缓冲问题,它是一个并发的一个经典的案例,因为我们知道这个商品仓库的库房大小是有限的,也就是说生产者不能无限的去生产商品,一旦这个库房爆掉的话,它是它是必须要中止自己的生产,消费者也是不能无限地获取消息。 假如仓库是空的话,那这个消费者的这个相关的情况也需要被阻塞。那么怎么在这个生产者跟消费者之间保证商品不丢失。这就是生产者与消费者之间最核心的内容。先来看一下这个Java当中生产者消费者的这种实现到底是什么样的。这个可以说是一个最经典的这么样一个实现。这个Java当中是没有channel,那么它只能通过什么呢,只能通过信号量和一个一个log,也就是说一个忽视服务态度,这两个这两个配合信号量和所配合才能共同完成,这样一个生产者消费者这么一个相关的工作。 GO语言并发实战详细过程梳理 在现在这个远程办公的这一个大的背景下,积累了大量重复的文件,因为很可能大家都不断的在不同的群里发相同的文件,发相同的这个报表,以及一些相同的视频等等这些需要学习的材料,那么怎么把这些文件都找出来,然后把这些相同文件都给删掉了,这实际上是并发课的一个实践的一个内容,因为这个创业型的这个方案的话,它的代码相对来说比较长。 如何使用GO语言清理PC机中的文件,详细代码及注释如下: package main import ( // "fmt" // fmt 包使用函数实现 I/O 格式化(类似于 C 的 printf 和 scanf 的函数), 格式化参数源自C,但更简单 "io/ioutil" //"sync" //"time" ) func PrintRepreatFile(path string, fileNameSizeMap map[string]int64, exFileList []string) { fs, _ := ioutil.ReadDir(path) for _, file := range fs { if file.IsDir() { PrintRepreatFile(path+"/"+file.Name(), fileNameSizeMap, exFileList)//遍历整个文件系统,如果是目录则递归调用 } else { if file.Size() > 1000000 {//设定文件清理阈值,如果大于一定大小再进行清理 fileSize := fileNameSizeMap[file.Name()]//通过查哈希表的方式来确定,有无重名且大小相同的文件。 if fileSize == file.Size() { fmt.Println(path + "/" + file.Name())//如果有则打印出来 exFileList = append(exFileList, path+file.Name())//将结果记入切片当中 } else { fileNameSizeMap[file.Name()] = file.Size() } } } } } func main() { //方式一 fileNameSizeMap := make(map[string]int64, 10000) exFileList := make([]string, 100, 1000) PrintRepreatFile("E:/test", fileNameSizeMap, exFileList) } 这个程序在GO语言的环境下可以直接运行使用,其中有几个知识点,也是咱们前文提到过的,首先是切片的大小一定要设定的相对合适一些,如果容量不够大造成频繁扩容非常浪费资源。二是哈希表也就是map没有并发安全的属于,在我们这个未引入并发的程序中可以使用,如果有并发操作,那么map不再适用了。 可能很多人被GO语言的在并发性能所吸引入坑的,GO语言之父也就是UNIX之父Ken Thompson明显给出了很多建议,根据笔者在操作系统方面的相关经验来看,GO语言设计中经常参考UNIX内核的设计思路。比如硬定时器的数量有限,无法满足系统实际运行需要,所以在内核代码中就会看到基于硬件定时器的软件定时器的方案,而软件定时器的数量可以比硬件定时器多几百倍。 这样的理念明显融合到了 goroutine之中,由于其它编程语言往往直接通过系统级别的线程来实现并发功能,但是这样的方式往往会是大马拉小车,造成系统资源的浪费。因此GO语言封装了所有的系统操作,实现了更加轻量级的协程-goroutine。只要使用关键字(go)就可以启动协程,对比C++、JAVA的多线程并发模型,GO的协程更简单明了。 当然协程之间的消息通信与并发控制也是非常重要的一环。在GO语言借鉴了Message Queue的消息队列机制替代共享内存的方式进行协程间通信,其中管道channel作为基本的数据类型,保证并发时的操作安全。而且管道的引入还带来很多实践中非常实用的功能,比如可以方便实现生产者、消费者等并发设计模式,而这些设计模式在其它使用共享存内存的并发模型中实现起相关功能来非常的繁锁。 在GO语言中在调用函数前加入go 关键字,就能启动一个协程,也就是一个并发,但是我们上面的程序如果把调用方式改为: go PrintRepreatFile("E:/test", fileNameSizeMap, exFileList) 你会发现程序会直接退出,什么都没做,所以GO语言的并发对于初学者来说还是有一定门槛的,比如上例中如果想设计成一个并行的程序,如何让多个协程共同来帮忙找出重复的文件其实还是要费一番周折的。

剑曼红尘 2020-04-13 11:06:46 0 浏览量 回答数 0

问题

从备案谈阿里云的服务缺失

benen005 2019-12-01 21:27:30 10434 浏览量 回答数 5

回答

X-Engine是阿里云数据库产品事业部自研的联机事务处理OLTP(On-Line Transaction Processing)数据库存储引擎。作为自研数据库POLARDB的存储引擎之一,已经广泛应用在阿里集团内部诸多业务系统中,包括交易历史库、钉钉历史库等核心应用,大幅缩减了业务成本,同时也作为双十一大促的关键数据库技术,挺过了数百倍平时流量的冲击。 为什么设计一个新的存储引擎 X-Engine的诞生是为了应对阿里内部业务的挑战,早在2010年,阿里内部就大规模部署了MySQL数据库,但是业务量的逐年爆炸式增长,数据库面临着极大的挑战: 极高的并发事务处理能力(尤其是双十一的流量突发式暴增)。 超大规模的数据存储。 这两个问题虽然可以通过扩展数据库节点的分布式方案解决,但是堆机器不是一个高效的手段,我们更想用技术的手段将数据库性价比提升到极致,实现以少量资源换取性能大幅提高的目的。 传统数据库架构的性能已经被仔细的研究过,数据库领域的泰斗,图灵奖得主Michael Stonebreaker就此写过一篇论文 《OLTP Through the Looking Glass, and What We Found There》 ,指出传统关系型数据库,仅有不到10%的时间是在做真正有效的数据处理工作,剩下的时间都浪费在其它工作上,例如加锁等待、缓冲管理、日志同步等。 造成这种现象的原因是因为近年来我们所依赖的硬件体系发生了巨大的变化,例如多核(众核)CPU、新的处理器架构(Cache/NUMA)、各种异构计算设备(GPU/FPGA)等,而架构在这些硬件之上的数据库软件却没有太大的改变,例如使用B-Tree索引的固定大小的数据页(Page)、使用ARIES算法的事务处理与数据恢复机制、基于独立锁管理器的并发控制等,这些都是为了慢速磁盘而设计,很难发挥出现有硬件体系应有的性能。 基于以上原因,阿里开发了适合当前硬件体系的存储引擎,即X-Engine。 X-Engine架构 全新架构的X-Engine存储引擎不仅可以无缝对接兼容MySQL(得益于MySQL Pluginable Storage Engine特性),同时X-Engine使用分层存储架构。 因为目标是面向大规模的海量数据存储,提供高并发事务处理能力和降低存储成本,在大部分大数据量场景下,数据被访问的机会是不均等的,访问频繁的热数据实际上占比很少,X-Engine根据数据访问频度的不同将数据划分为多个层次,针对每个层次数据的访问特点,设计对应的存储结构,写入合适的存储设备。 X-Engine使用了LSM-Tree作为分层存储的架构基础,并进行了重新设计: 热数据层和数据更新使用内存存储,通过内存数据库技术(Lock-Free index structure/append only)提高事务处理的性能。 流水线事务处理机制,把事务处理的几个阶段并行起来,极大提升了吞吐。 访问频度低的数据逐渐淘汰或是合并到持久化的存储层次中,并结合多层次的存储设备(NVM/SSD/HDD)进行存储。 对性能影响比较大的Compaction过程做了大量优化: 拆分数据存储粒度,利用数据更新热点较为集中的特征,尽可能的在合并过程中复用数据。 精细化控制LSM的形状,减少I/O和计算代价,有效缓解了合并过程中的空间增大。 同时使用更细粒度的访问控制和缓存机制,优化读的性能。 技术特点 利用FPGA硬件加速Compaction过程,使得系统上限进一步提升。这个技术属首次将硬件加速技术应用到在线事务处理数据库存储引擎中,相关论文 《FPGA-Accelerated Compactions for LSM-based Key Value Store》 已经被2020年的顶级会议FAST'20接收。 通过数据复用技术减少数据合并代价,同时减少缓存淘汰带来的性能抖动。 使用多事务处理队列和流水线处理技术,减少线程上下文切换代价,并计算每个阶段任务量配比,使整个流水线充分流转,极大提升事务处理性能。相对于其他类似架构的存储引擎(例如RocksDB),X-Engine的事务处理性能有10倍以上提升。 X-Engine使用的Copy-on-write技术,避免原地更新数据页,从而对只读数据页面进行编码压缩,相对于传统存储引擎(例如InnoDB),使用X-Engine可以将存储空间降低至10%~50%。 Bloom Filter快速判定数据是否存在,Surf Filter判断范围数据是否存在,Row Cache缓存热点行,加速读取性能。 LSM基本逻辑 LSM的本质是所有写入操作直接以追加的方式写入内存。每次写到一定程度,即冻结为一层(Level),并写入持久化存储。所有写入的行,都以主键(Key)排序好后存放,无论是在内存中,还是持久化存储中。在内存中即为一个排序的内存数据结构(Skiplist、B-Tree、etc),在持久化存储也作为一个只读的全排序持久化存储结构。 普通的存储系统若要支持事务处理,需要加入一个时间维度,为每个事务构造出一个不受并发干扰的独立视域。例如存储引擎会对每个事务定序并赋予一个全局单调递增的事务版本号(SN),每个事务中的记录会存储这个SN以判断独立事务之间的可见性,从而实现事务的隔离机制。 如果LSM存储结构持续写入,不做其他的动作,那么最终会成为如下结构。 这种结构对于写入是非常友好的,只要追加到最新的内存表中即完成,为实现故障恢复,只需记录Redo Log,因为新数据不会覆盖旧版本,追加记录会形成天然的多版本结构。 但是如此累积,冻结的持久化层次越来越多,会对查询会产生不利的影响。例如对同一个key,不同事务提交产生的多版本记录会散落在各个层次中;不同的key也会散落在不同层次中。读操作需要查找各个层并合并才能得到最终结果。 因此LSM引入了Compaction操作解决这个问题,Compaction操作有2种作用: 控制LSM层次形状 一般的LSM形状都是层次越低,数据量越大(倍数关系),目的是为了提升读性能。 通常存储系统的数据访问都有局部性,大量的访问都集中在少部分数据上,这也是缓存系统能有效工作的基本前提。在LSM存储结构中,如果把访问频率高的数据尽可能放在较高的层次上,存放在快速存储设备中(例如NVM、DRAM),而把访问频率低的数据放在较低层次中,存放在廉价慢速存储设备中。这就是X-Engine的冷热分层概念。 合并数据 Compaction操作不断的把相邻层次的数据合并,并写入更低层次。合并的过程实际上是把要合并的相邻两层或多层的数据读出来,按key排序,相同的key如果有多个版本,只保留新的版本(比当前正在执行的活跃事务中最小版本号新),丢掉旧版本数据,然后写入新的层,这个操作非常耗费资源。 合并数据除了考虑冷热分层以外,还需要考虑其他维度,例如数据的更新频率,大量的多版本数据在查询的时候会浪费更多的I/O和CPU,因此需要优先进行合并以减少记录的版本数量。X-Engine综合考虑了各种策略形成自己的Compaction调度机制。 高度优化的LSM X-Engine的memory tables使用了无锁跳表(Locked-free SkipList),并发读写的性能较高。在持久化层如何实现高效,就需要讨论每层的细微结构。 数据组织 X-Engine的每层都划分成固定大小的Extent,存放每个层次中的数据的一个连续片段(Key Range)。为了快速定位Extent,为每层Extents建立了一套索引(Meta Index),所有这些索引,加上所有的memory tables(active/immutable)一起组成了一个元数据树(Metadata Tree),root节点为Metadata Snapshot,这个树结构类似于B-Tree。 X-Engine中除了当前的正在写入的active memory tables以外,其他结构都是只读的,不会被修改。给定某个时间点,例如LSN=1000,上图中的Metadata Snapshot 1引用到的结构即包含了LSN=1000时的所有的数据的快照,因此这个结构被称为Snapshot。 即便是Metadata结构本身,也是一旦生成就不会被修改。所有的读请求都是以Snapshot为入口,这是X-Engine实现Snapshot级别隔离的基础。前文说过随着数据写入,累积数据越多,会执行Compaction操作、冻结memory tables等,这些操作都是用Copy-on-write实现,即每次都将修改产生的结果写入新的Extent,然后生成新的Meta Index结构,最终生成新的Metadata Snapshot。 例如执行一次Compaction操作会生成新的Metadata Snapshot,如下图所示。 可以看到Metadata Snapshot 2相对于Metadata Snapshot 1并没有太多的变化,仅仅修改了发生变更的一些叶子节点和索引节点。 事务处理 得益于LSM的轻量化写机制,写入操作固然是其明显的优势,但是事务处理不只是把更新的数据写入系统那么简单,还要保证ACID(原子性、一致性、隔离性、持久性),涉及到一整套复杂的流程。X-Engine将整个事务处理过程分为两个阶段: 读写阶段 校验事务的冲突(写写冲突、读写冲突),判断事务是否可以执行、回滚重试或者等锁。如果事务冲突校验通过,则把修改的所有数据写入Transaction Buffer。 提交阶段 写WAL、写内存表,以及提交并返回用户结果,这里面既有I/O操作(写日志、返回消息),也有CPU操作(拷贝日志、写内存表)。 为了提高事务处理吞吐,系统内会有大量事务并发执行,单个I/O操作比较昂贵,大部分存储引擎会倾向于聚集一批事务一起提交,称为Group Commit,能够合并I/O操作。但是一组事务提交的过程中,还是有大量等待过程的,例如写入日志到磁盘过程中,除了等待落盘无所事事。 X-Engine为了进一步提升事务处理的吞吐,使用流水线技术,把提交阶段分为4个独立的更精细的阶段: 拷贝日志到缓冲区(Log Buffer) 日志落盘(Log Flush) 写内存表(Write memory table) 提交返回(Commit) 事务到了提交阶段,可以自由选择执行流水线中任意一个阶段,只要流水线任务的大小划分得当,就能充分并行起来,流水线处于接近满载状态。另外这里利用的是事务处理的线程,而非后台线程,每个线程在执行的时候,选择流水线中的一个阶段执行任务,或者空闲后处理其他请求,没有等待,也无需切换,充分利用了每个线程的能力。 读操作 LSM处理多版本数据的方式是新版本数据记录会追加在老版本数据后面,从物理上看,一条记录不同的版本可能存放在不同的层,在查询的时候需要找到合适的版本(根据事务隔离级别定义的可见性规则),一般查询都是查找最新的数据,总是由最高的层次往低层次找。 对于单条记录的查找而言,一旦找到便可以终止,如果记录在比较高的层次,例如memory tables,很快便可以返回;如果记录已经落入了很低的层次,那就得逐层查找,也许Bloom Filter可以跳过某些层次加快这个旅程,但毕竟还是有很多的I/O操作。X-Engine针对单记录查询引入了Row Cache,在所有持久化的层次的数据之上做了一个缓存,在memory tables中没有命中的单行查询,在Row Cache之中也会被捕获。Row Cache需要保证缓存了所有持久化层次中最新版本的记录,而这个记录是可能发生变化的,例如每次flush将只读的memory tables写入持久化层次时,就需要恰当的更新Row Cache中的缓存记录,这个操作比较微妙,需要精心的设计。 对于范围扫描而言,因为没法确定一个范围的key在哪个层次中有数据,只能扫描所有的层次做合并之后才能返回最终的结果。X-Engine采用了一系列的手段,例如SuRF(SIGMOD'18 best paper)提供range scan filter减少扫描层数、异步I/O与预取。 读操作中最核心的是缓存设计,Row Cache负责单行查询,Block Cache负责Row Cache的漏网之鱼,也用来进行范围扫描。由于LSM的Compaction操作会一次更新大量的Data Block,导致Block Cache中大量数据短时间内失效,导致性能的急剧抖动,因此X-Engine做了很多的优化: 减少Compaction的粒度。 减少Compaction过程中改动的数据。 Compaction过程中针对已有的缓存数据做定点更新。 Compaction Compaction操作是比较重要的,需要把相邻层次交叉的Key Range数据读取合并,然后写到新的位置。这是为前面简单的写入操作付出的代价。X-Engine为优化这个操作重新设计了存储结构。 如前文所述,X-Engine将每一层的数据划分为固定大小的Extent,一个Extent相当于一个小而完整的排序字符串表(SSTable),存储了一个层次中的一个连续片段,连续片段又进一步划分为一个个连续的更小的片段Data Block,相当于传统数据库中的Page,只不过Data Block是只读而且不定长的。 回看并对比Metadata Snapshot 1和Metadata Snapshot 2,可以发现Extent的设计意图。每次修改只需要修改少部分有交叠的数据,以及涉及到的Meta Index节点。两个Metadata Snapshot结构实际上共用了大量的数据结构,这被称为数据复用技术(Data Reuse),而Extent大小正是影响数据复用率的关键,Extent作为一个完整的被复用的物理结构,需要尽可能的小,这样与其他Extent数据交叉点会变少,但又不能非常小,否则需要索引过多,管理成本太大。 X-Engine中Compaction的数据复用是非常彻底的,假设选取两个相邻层次(Level1, Level2)中的交叉的Key Range所涵盖的Extents进行合并,合并算法会逐行进行扫描,只要发现任意的物理结构(包括Data Block和Extent)与其他层中的数据没有交叠,则可以进行复用。只不过Extent的复用可以修改Meta Index,而Data Block的复用只能拷贝,即便如此也可以节省大量的CPU。 一个典型的数据复用在Compaction中的过程可以参考下图。 可以看出数据复用的过程是在逐行迭代的过程中完成的,不过这种精细的数据复用带来另一个副作用,即数据的碎片化,所以在实际操作的过程中也需要根据实际情况进行分析。 数据复用不仅给Compaction操作本身带来好处,降低操作过程中的I/O与CPU消耗,更对系统的综合性能产生一系列的影响。例如c、Compaction过程中数据不用完全重写,大大降低了写入时空间的增大;大部分数据保持原样,数据缓存不会因为数据更新而失效,减少合并过程中因缓存失效带来的读性能抖动。 实际上,优化Compaction的过程只是X-Engine工作的一部分,更重要的是优化Compaction调度的策略,选什么样的Extent、定义compaction任务的粒度、执行的优先级等,都会对整个系统性能产生影响,可惜并不存在什么完美的策略,X-Engine积累了一些经验,定义了很多规则,而探索更合理的调度策略是未来一个重要方向。 适用场景 请参见X-Engine最佳实践。 如何使用X-Engine 请参见使用X-Engine引擎。 后续发展 作为MySQL的存储引擎,持续地提升MySQL系统的兼容能力是一个重要目标,后续会根据需求的迫切程度逐步加强原本取消的一些功能,例如外键,以及对一些数据结构、索引类型的支持。 X-Engine作为存储引擎,核心的价值还在于性价比,持续提升性能降低成本,是一个长期的根本目标,X-Engine还在Compaction调度、缓存管理与优化、数据压缩、事务处理等方向上进行深层次的探索。 X-Engine不仅仅局限为一个单机的数据库存储引擎,未来还将作为自研分布式数据库POLARDB分布式版本的核心,提供企业级数据库服务。

游客yl2rjx5yxwcam 2020-03-08 13:24:40 0 浏览量 回答数 0

问题

使用Docker容器的十大误区

ghostcloud 2019-12-01 21:10:44 7758 浏览量 回答数 1

问题

Nginx性能为什么如此吊

小柒2012 2019-12-01 21:20:47 15038 浏览量 回答数 3

问题

Stream Client 的原理是什么

云栖大讲堂 2019-12-01 20:59:34 1190 浏览量 回答数 0

问题

谈阿里云的服务意识缺失

benen005 2019-12-01 21:27:26 13251 浏览量 回答数 7

问题

【archsummit 回顾】阿里云章文嵩:构建大型云计算平台分布式技术的实践

云课堂 2019-12-01 21:03:36 14448 浏览量 回答数 9

回答

我们都知道JVM的内存管理是自动化的,Java语言的程序指针也不需要开发人员手工释放,JVM的GC会自动的进行回收,但是,如果编程不当,JVM仍然会发生内存泄露,导致Java程序产生了OutOfMemoryError(OOM)错误。 产生OutOfMemoryError错误的原因包括: java.lang.OutOfMemoryError: Java heap spacejava.lang.OutOfMemoryError: PermGen space及其解决方法java.lang.OutOfMemoryError: unable to create new native threadjava.lang.OutOfMemoryError:GC overhead limit exceeded对于第1种异常,表示Java堆空间不够,当应用程序申请更多的内存,而Java堆内存已经无法满足应用程序对内存的需要,将抛出这种异常。 对于第2种异常,表示Java永久带(方法区)空间不够,永久带用于存放类的字节码和长常量池,类的字节码加载后存放在这个区域,这和存放对象实例的堆区是不同的,大多数JVM的实现都不会对永久带进行垃圾回收,因此,只要类加载的过多就会出现这个问题。一般的应用程序都不会产生这个错误,然而,对于Web服务器来讲,会产生有大量的JSP,JSP在运行时被动态的编译成Java Servlet类,然后加载到方法区,因此,太多的JSP的Web工程可能产生这个异常。 对于第3种异常,本质原因是创建了太多的线程,而能创建的线程数是有限制的,导致了这种异常的发生。 对于第4种异常,是在并行或者并发回收器在GC回收时间过长、超过98%的时间用来做GC并且回收了不到2%的堆内存,然后抛出这种异常进行提前预警,用来避免内存过小造成应用不能正常工作。 下面两个异常与OOM有关系,但是,又没有绝对关系。 java.lang.StackOverflowError ...java.net.SocketException: Too many open files对于第1种异常,是JVM的线程由于递归或者方法调用层次太多,占满了线程堆栈而导致的,线程堆栈默认大小为1M。 对于第2种异常,是由于系统对文件句柄的使用是有限制的,而某个应用程序使用的文件句柄超过了这个限制,就会导致这个问题。 上面介绍了OOM相关的基础知识,接下来我们开始讲述笔者经历的一次OOM问题的定位和解决的过程。 产生问题的现象 在某一段时间内,我们发现不同的业务服务开始偶发的报OOM的异常,有的时候是白天发生,有的时候是晚上发生,有的时候是基础服务A发生,有的时候是上层服务B发生,有的时候是上层服务C发生,有的时候是下层服务D发生,丝毫看不到一点规律。 产生问题的异常如下: Caused by: java.lang.OutOfMemoryError: unable to create new native thread at java.lang.Thread.start0(Native Method)at java.lang.Thread.start(Thread.java:597)at java.util.Timer.(Timer.java:154) 解决问题的思路和过程 经过细心观察发现,产生问题虽然在不同的时间发生在不同的服务池,但是,晚上0点发生的时候概率较大,也有其他时间偶发,但是都在整点。 这个规律很重要,虽然不是一个时间,但是基本都在整点左右发生,并且晚上0点居多。从这个角度思考,整点或者0点系统是否有定时,与出问题的每个业务系统技术负责人核实,0点没有定时任务,其他时间的整点有定时任务,但是与发生问题的时间不吻合,这个思路行不通。 到现在为止,从现象的规律上我们已经没法继续分析下去了,那我们回顾一下错误本身: java.lang.OutOfMemoryError: unable to create new native thread 顾名思义,错误产生的原因就是应用不能创建线程了,但是,应用还需要创建线程。为什么程序不能创建线程呢? 有两个具体原因造成这个异常: 由于线程使用的资源过多,操作系统已经不能再提供给应用资源了。操作系统设置了应用创建线程的最大数量,并且已经达到了最大允许数量。上面第1条资源指的是内存,而第2条中,在Linux下线程使用轻量级进程实现的,因此线程的最大数量也是操作系统允许的进程的最大数量。 内存计算 操作系统中的最大可用内存除去操作系统本身使用的部分,剩下的都可以为某一个进程服务,在JVM进程中,内存又被分为堆、本地内存和栈等三大块,Java堆是JVM自动管理的内存,应用的对象的创建和销毁、类的装载等都发生在这里,本地内存是Java应用使用的一种特殊内存,JVM并不直接管理其生命周期,每个线程也会有一个栈,是用来存储线程工作过程中产生的方法局部变量、方法参数和返回值的,每个线程对应的栈的默认大小为1M。 Linux和JVM的内存管理示意图如下: 内存结构模型因此,从内存角度来看创建线程需要内存空间,如果JVM进程正当一个应用创建线程,而操作系统没有剩余的内存分配给此JVM进程,则会抛出问题中的OOM异常:unable to create new native thread。 如下公式可以用来从内存角度计算允许创建的最大线程数: 最大线程数 = (操作系统最大可用内存 - JVM内存 - 操作系统预留内存)/ 线程栈大小 根据这个公式,我们可以通过剩余内存计算可以创建线程的数量。 下面是问题出现的时候,从生产机器上执行前面小节介绍的Linux命令free的输出: free -m >> /tmp/free.log total used free shared buffers cached Mem: 7872 7163 709 0 31 3807-/+ buffers/cache: 3324 4547Swap: 4095 173 3922Tue Jul 5 00:27:51 CST 2016从上面输出可以得出,生产机器8G内存,使用了7G,剩余700M可用,其中操作系统cache使用3.8G。操作系统cache使用的3.8G是用来缓存IO数据的,如果进程内存不够用,这些内存是可以释放出来优先分配给进程使用。然而,我们暂时并不需要考虑这块内存,剩余的700M空间完全可以继续用来创建线程数: 700M / 1M = 700个线程 因此,根据内存可用计算,当OOM异常:unable to create new native thread问题发生的时候,还有700M可用内存,可以创建700个线程。 到现在为止可以证明此次OOM异常不是因为线程吃光所有的内存而导致的。 线程数对比 上面提到,有两个具体原因造成这个异常,我们上面已经排除了第1个原因,那我们现在从第2个原因入手,评估是否操作系统设置了应用创建线程的最大数量,并且已经达到了最大允许数量。 在问题出现的生产机器上使用ulimit -a来显示当前的各种系统对用户使用资源的限制: robert@robert-ubuntu1410:~$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0file size (blocks, -f) unlimitedpending signals (-i) 62819max locked memory (kbytes, -l) 64max memory size (kbytes, -m) unlimitedopen files (-n) 65535pipe size (512 bytes, -p) 8POSIX message queues (bytes, -q) 819200real-time priority (-r) 0stack size (kbytes, -s) 10240cpu time (seconds, -t) unlimitedmax user processes (-u) 1024virtual memory (kbytes, -v) unlimitedfile locks (-x) unlimited这里面我们看到生产机器设置的允许使用的最大用户进程数为1024: max user processes (-u) 1024现在,我们必须获得问题出现的时候,用户下创建的线程情况。 在问题产生的时候,我们使用前面小结介绍的JVM监控命令jstack命令打印出了Java线程情况,jstack命令的示例输出如下: robert@robert-ubuntu1410:~$ jstack 27432017-04-09 12:06:51Full thread dump Java HotSpot(TM) Server VM (25.20-b23 mixed mode): "Attach Listener" #23 daemon prio=9 os_prio=0 tid=0xc09adc00 nid=0xb4c waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "http-nio-8080-Acceptor-0" #22 daemon prio=5 os_prio=0 tid=0xc3341000 nid=0xb02 runnable [0xbf1bd000] java.lang.Thread.State: RUNNABLE at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:241) - locked <0xcf8938d8> (a java.lang.Object) at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:688) at java.lang.Thread.run(Thread.java:745) "http-nio-8080-ClientPoller-1" #21 daemon prio=5 os_prio=0 tid=0xc35bc400 nid=0xb01 runnable [0xbf1fe000] java.lang.Thread.State: RUNNABLE at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:79) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86) - locked <0xcf99b100> (a sun.nio.ch.Util$2) - locked <0xcf99b0f0> (a java.util.Collections$UnmodifiableSet) - locked <0xcf99aff8> (a sun.nio.ch.EPollSelectorImpl) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97) at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:1052) at java.lang.Thread.run(Thread.java:745) ......从jstack命令的输出并统计后,我们得知,JVM一共创建了904个线程,但是,这还没有到最大的进程限制1024。 robert@robert-ubuntu1410:~$ grep "Thread " js.log | wc -l 904 这是我们思考,除了JVM创建的应用层线程,JVM本身可能会有一些管理线程存在,而且操作系统内用户下可能也会有守护线程在运行。 我们继续从操作系统的角度来统计线程数,我们使用上面小结介绍的Linux操作系统命令pstack,并得到如下的输出: PID LWP USER %CPU %MEM CMD 1 1 root 0.0 0.0 /sbin/init 2 2 root 0.0 0.0 [kthreadd] 3 3 root 0.0 0.0 [migration/0] 4 4 root 0.0 0.0 [ksoftirqd/0] 5 5 root 0.0 0.0 [migration/0] 6 6 root 0.0 0.0 [watchdog/0] 7 7 root 0.0 0.0 [migration/1] 8 8 root 0.0 0.0 [migration/1] 9 9 root 0.0 0.0 [ksoftirqd/1] 10 10 root 0.0 0.0 [watchdog/1] 11 11 root 0.0 0.0 [migration/2] 12 12 root 0.0 0.0 [migration/2] 13 13 root 0.0 0.0 [ksoftirqd/2] 14 14 root 0.0 0.0 [watchdog/2] 15 15 root 0.0 0.0 [migration/3] 16 16 root 0.0 0.0 [migration/3] 17 17 root 0.0 0.0 [ksoftirqd/3] 18 18 root 0.0 0.0 [watchdog/3] 19 19 root 0.0 0.0 [events/0] 20 20 root 0.0 0.0 [events/1] 21 21 root 0.0 0.0 [events/2] 22 22 root 0.0 0.0 [events/3] 23 23 root 0.0 0.0 [cgroup] 24 24 root 0.0 0.0 [khelper] ...... 7257 7257 zabbix 0.0 0.0 /usr/local/zabbix/sbin/zabbix_agentd: active checks #2 [idle 1 sec] 7258 7258 zabbix 0.0 0.0 /usr/local/zabbix/sbin/zabbix_agentd: active checks #3 [idle 1 sec] 7259 7259 zabbix 0.0 0.0 /usr/local/zabbix/sbin/zabbix_agentd: active checks #4 [idle 1 sec] ...... 9040 9040 app 0.0 30.5 /apps/prod/jdk1.6.0_24/bin/java -Dnop -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Ddbconfigpath=/apps/dbconfig/ -Djava.io.tmpdir=/apps/data/java-tmpdir -server -Xms2048m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=192.168.10.194 -Dcom.sun.management.jmxremote.port=6969 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Xshare:off -Dhostname=sjsa-trade04 -Djute.maxbuffer=41943040 -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -Dworkdir=/apps/data/tomcat-work -Djava.endorsed.dirs=/apps/product/tomcat-trade/endorsed -classpath commonlib:/apps/product/tomcat-trade/bin/bootstrap.jar:/apps/product/tomcat-trade/bin/tomcat-juli.jar -Dcatalina.base=/apps/product/tomcat-trade -Dcatalina.home=/apps/product/tomcat-trade -Djava.io.tmpdir=/apps/data/tomcat-temp/ org.apache.catalina.startup.Bootstrap start 9040 9041 app 0.0 30.5 /apps/prod/jdk1.6.0_24/bin/java -Dnop -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Ddbconfigpath=/apps/dbconfig/ -Djava.io.tmpdir=/apps/data/java-tmpdir -server -Xms2048m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=192.168.10.194 -Dcom.sun.management.jmxremote.port=6969 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Xshare:off -Dhostname=sjsa-trade04 -Djute.maxbuffer=41943040 -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -Dworkdir=/apps/data/tomcat-work -Djava.endorsed.dirs=/apps/product/tomcat-trade/endorsed -classpath commonlib:/apps/product/tomcat-trade/bin/bootstrap.jar:/apps/product/tomcat-trade/bin/tomcat-juli.jar -Dcatalina.base=/apps/product/tomcat-trade -Dcatalina.home=/apps/product/tomcat-trade -Djava.io.tmpdir=/apps/data/tomcat-temp/ org.apache.catalina.startup.Bootstrap start ......通过命令统计用户下已经创建的线程数为1021。 $ grep app pthreads.log | wc -l 1021 现在我们确定,1021的数字已经相当的接近1021的最大进程数了,正如前面我们提到,在Linux操作系统里,线程是通过轻量级的进程实现的,因此,限制用户的最大进程数,就是限制用户的最大线程数,至于为什么没有精确达到1024这个最大值就已经报出异常,应该是系统的自我保护功能,在还剩下3个线程的前提下,就开始报错。 到此为止,我们已经通过分析来找到问题的原因,但是,我们还是不知道为什么会创建这么多的线程,从第一个输出得知,JVM已经创建的应用线程有907个,那么他们都在做什么事情呢? 于是,在问题发生的时候,我们又使用JVM的jstack命令,查看输出得知,每个线程都阻塞在打印日志的语句上,log4j中打印日志的代码实现如下: public void callAppenders(LoggingEvent event) { int writes = 0; for(Category c = this; c != null; c=c.parent) { // Protected against simultaneous call to addAppender, removeAppender,... synchronized(c) { if(c.aai != null) { writes += c.aai.appendLoopOnAppenders(event); } if(!c.additive) { break; } } } if(writes == 0) { repository.emitNoAppenderWarning(this); } }在log4j中,打印日志有一个锁,锁的作用是让打印日志可以串行,保证日志在日志文件中的正确性和顺序性。 那么,新的问题又来了,为什么只有凌晨0点会出现打印日志阻塞,其他时间会偶尔发生呢?这时,我们带着新的线索又回到问题开始的思路,凌晨12点应用没有定时任务,系统会不会有其他的IO密集型的任务,比如说归档日志、磁盘备份等? 经过与运维部门碰头,基本确定是每天凌晨0点日志切割导致磁盘IO被占用,于是堵塞打印日志,日志是每个工作任务都必须的,日志阻塞,线程池就阻塞,线程池阻塞就导致线程池被撑大,线程池里面的线程数超过1024就会报错。 到这里,我们基本确定了问题的原因,但是还需要对日志切割导致IO增大进行分析和论证。 首先我们使用前面小结介绍的vmstat查看问题发生时IO等待数据: vmstat 2 1 >> /tmp/vm.logprocs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 177608 725636 31856 3899144 0 0 2 10 0 0 39 1 1 59 0 Tue Jul 5 00:27:51 CST 2016可见,问题发生的时候,CPU的IO等待为59%,同时又与运维部门同事复盘,运维同事确认,脚本切割通过cat命令方法,先把日志文件cat后,通过管道打印到另外一个文件,再清空原文件,因此,一定会导致IO的上升。 其实,问题的过程中,还有一个疑惑,我们认为线程被IO阻塞,线程池被撑开,导致线程增多,于是,我们查看了一下Tomcat线程池的设置,我们发现Tomcat线程池设置了800,按理说,永远不会超过1024。 maxThreads="800" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" /> 关键在于,笔者所在的支付平台服务化架构中,使用了两套服务化框架,一个是基于dubbo的框架,一个是点对点的RPC,用来紧急情况下dubbo服务出现问题,服务降级使用。 每个服务都配置了点对点的RPC服务,并且独享一个线程池: maxThreads="800" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" /> 由于我们在对dubbo服务框架进行定制化的时候,设计了自动降级原则,如果dubbo服务负载变高,会自动切换到点对点的RPC框架,这也符合微服务的失效转移原则,但是设计中没有进行全面的考虑,一旦一部分服务切换到了点对点的RPC,而一部分的服务没有切换,就导致两个现场池都被撑满,于是超过了1024的限制,就出了问题。 到这里,我们基本可以验证,问题的根源是日志切割导致IO负载增加,然后阻塞线程池,最后发生OOM:unable to create new native thread。 剩下的任务就是最小化重现的问题,通过实践来验证问题的原因。我们与性能压测部门沟通,提出压测需求: Tomcat线程池最大设置为1500.操作系统允许的最大用户进程数1024.在给服务加压的过程中,需要人工制造繁忙的IO操作,IO等待不得低于50%。经过压测压测部门的一下午努力,环境搞定,结果证明完全可以重现此问题。 最后,与所有相关部门讨论和复盘,应用解决方案,解决方案包括: 全部应用改成按照小时切割,或者直接使用log4j的日志滚动功能。Tomcat线程池的线程数设置与操作系统的线程数设置不合理,适当的减少Tomcat线程池线程数量的大小。升级log4j日志,使用logback或者log4j2。这次OOM问题的可以归结为“多个因、多个果、多台机器、多个服务池、不同时间”,针对这个问题,与运维部、监控部和性能压测部门的同事奋斗了几天几夜,终于通过在线上抓取信息、分析问题、在性能压测部门同事的帮助下,最小化重现问题并找到问题的根源原因,最后,针对问题产生的根源提供了有效的方案。 与监控同事现场编写的脚本 本节提供一个笔者在实践过程中解决OOM问题的一个简单脚本,这个脚本是为了解决OOM(unable to create native thread)的问题而在问题机器上临时编写,并临时使用的,脚本并没有写的很专业,笔者也没有进行优化,保持原汁原味的风格,这样能让读者有种身临其境的感觉,只是为了抓取需要的信息并解决问题,但是在线上问题十分火急的情况下,这个脚本会有大用处。 !/bin/bash ps -Leo pid,lwp,user,pcpu,pmem,cmd >> /tmp/pthreads.logecho "ps -Leo pid,lwp,user,pcpu,pmem,cmd >> /tmp/pthreads.log" >> /tmp/pthreads.logecho date >> /tmp/pthreads.logecho 1 pid=ps aux|grep tomcat|grep cwh|awk -F ' ' '{print $2}'echo 2 echo "pstack $pid >> /tmp/pstack.log" >> /tmp/pstack.logpstack $pid >> /tmp/pstack.logecho date >> /tmp/pstack.logecho 3 echo "lsof >> /tmp/sys-o-files.log" >> /tmp/sys-o-files.loglsof >> /tmp/sys-o-files.logecho date >> /tmp/sys-o-files.logecho 4 echo "lsof -p $pid >> /tmp/service-o-files.log" >> /tmp/service-o-files.loglsof -p $pid >> /tmp/service-o-files.logecho date >> /tmp/service-o-files.logecho 5 echo "jstack -l $pid >> /tmp/js.log" >> /tmp/js.logjstack -l -F $pid >> /tmp/js.logecho date >> /tmp/js.logecho 6 echo "free -m >> /tmp/free.log" >> /tmp/free.logfree -m >> /tmp/free.logecho date >> /tmp/free.logecho 7 echo "vmstat 2 1 >> /tmp/vm.log" >> /tmp/vm.logvmstat 2 1 >> /tmp/vm.logecho date >> /tmp/vm.logecho 8 echo "jmap -dump:format=b,file=/tmp/heap.hprof 2743" >> /tmp/jmap.logjmap -dump:format=b,file=/tmp/heap.hprof >> /tmp/jmap.logecho date >> /tmp/jmap.logecho 9 echo end

hiekay 2019-12-02 01:39:43 0 浏览量 回答数 0

问题

我用四个命令,总结了 Git 的所有套路 6月8日 【今日算法】

游客ih62co2qqq5ww 2020-06-09 15:16:23 67 浏览量 回答数 1

问题

Node.js 应该处于技术架构中的哪个位置?

李博 bluemind 2019-12-01 21:47:42 2961 浏览量 回答数 0

回答

HashMap HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中具体实现稍有 不同 其实1.7一个很明显需要优化的地方就是: 当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效 率就会越来越低;时间复杂度为 O(N)。 因此 1.8 中重点优化了这个查询效率。 1.8 HashMap 结构图 JDK 1.8 对 HashMap 进行了修改: 最大的不同就是利用了红黑树,其由数组+链表+红黑树组成。 JDK 1.7 中,查找元素时,根据 hash 值能够快速定位到数组的具体下标, 但之后需要顺着链表依次比较才能查找到需要的元素,时间复杂度取决于链 表的长度,为 O(N)。 为了降低这部分的开销,在 JDK 1.8 中,当链表中的元素超过 8 个以后,会 将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。 JDK 1.8 使用 Node(1.7 为 Entry) 作为链表的数据结点,仍然包含 key, value,hash 和 next 四个属性。 红黑树的情况使用的是 TreeNode。 根据数组元素中,第一个结点数据类型是 Node 还是 TreeNode 可以判断该位 置下是链表还是红黑树。 核心成员变量于 1.7 类似,增加了核心变量,如下表。 属性说明TREEIFY_THRESHOLD用于判断是否需要将链表转换为红黑树的阈值,默认 为 8。 put步骤: 判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始 化)。 根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。 如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进 行赋值及返回。 如果当前桶为红黑树,那就要按照红黑树的方式写入数据。 如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的 后面(形成链表)。 接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。 如果在遍历过程中找到 key 相同时直接退出遍历。 如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。 后判断是否需要进行扩容. get 方法看起来就要简单许多了。 首先将 key hash 之后取得所定位的桶。 如果桶为空则直接返回 null 。 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是 就直接返回 value。 如果第一个不匹配,则判断它的下一个是红黑树还是链表。 红黑树就按照树的查找方式返回值。 不然就按照链表的方式遍历匹配返回值。 从这两个核心方法(get/put)可以看出 1.8 中对大链表做了优化,修改为红黑树之 后查询效率直接提高到了 O(logn)。 但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环。 但是为什么呢?简单分析下。 看过上文的还记得在 HashMap 扩容的时候会调用 resize() 方法,就是这里的并 发操作容易在一个桶上形成环形链表;这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环。 如下图: HashTable HashTable 容器使用 synchronized来保证线程安全,但在线程竞争激烈的情况下 HashTable 的效 率非常低下。 当一个线程访问 HashTable 的同步方法时,其他线程访问 HashTable 的同步方 法可能会进入阻塞或轮询状态。 HashTable 容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有 访问它的线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容 器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就 不会存在锁竞争,从而可以有效的提高并发访问效率,这就是 ConcurrentHashMap(JDK 1.7) 使用的 锁分段技术。 ConcurrentHashMap 将数据分成一段一段的存储,然后给每一段数据配一把 锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他 线程访问。 有些方法需要跨段,比如 size() 和 containsValue(),它们可能需要锁定整个表 而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所 有段的锁。 按顺序 很重要,否则极有可能出现死锁,在 ConcurrentHashMap 内部,段数 组是 final 的,并且其成员变量实际也是 final 的,但是,仅仅是将数组声明为 final 的并不保证数组成员也是 final 的,需要实现上的保证。这可以确保不会 出现死锁,因为获得锁的顺序是固定的。 HashTable 的迭代器是强一致性的,而 ConcurrentHashMap 是弱一致的。 ConcurrentHashMap 的 get,clear,iterator 方法都是弱一致性的。 初识ConcurrentHashMap Concurrent翻译过来是并发的意思,字面理解它的作用是处理并发情况的 HashMap。 通过前面的学习,我们知道多线程并发下 HashMap 是不安全的(如死循环),更普遍 的是多线程并发下,由于堆内存对于各个线程是共享的,而 HashMap 的 put 方法 不是原子操作,假设Thread1先 put 值,然后 sleep 2秒(也可以是系统时间片切换失 去执行权),在这2秒内值被Thread2改了,Thread1“醒来”再 get 的时候发现已经不 是原来的值了,这就容易出问题。 那么如何避免这种多线程出错的情况呢? 常规思路就是给 HashMap 的 put 方法加锁(synchronized),保证同一个时刻只允 许一个线程拥有对 hashmap 有写的操作权限即可。然而假如线程1中操作耗时,其 他需要操作该 hashmap 的线程就需要在门口排队半天,严重影响用户体验, HashTable 就是这样子做的。 举个生活中的例子,很多银行除了存取钱,还支持存取贵重物品,贵重物品都放在 保险箱里,把 HashMap 和 HashTable 比作银行,结构: 把线程比作人,对应的情况如下: 多线程下用 HashMap 不确定性太高,有破产的风险,不能选;用 HashTable 不会 破产,但是用户体验不太好,那么怎样才能做到多人存取既不影响他人存值,又不 用排队呢? 有人提议搞个「银行者联盟」,多开几个像HashTable 这种「带锁」的银行就好 了,有多少人办理业务,就开多少个银行,一对一服务,这个区都是大老板,开银 行的成本都是小钱,于是「银行者联盟」成立了。 接下来的情况是这样的:比如用户A和用户B一起去银行存各自的项链,这个「银行 者联盟」操作后,然后对用户A说,1号银行现在没人你可以去那存,不用排队,然 后用户A就去1号银行存项链,1号银行把用户A接进门,马上拉闸,然后把用户A的 项链放在第x行第x个保险箱,等用户A办妥离开后,再开闸;对于用户B同理。此时 不管用户A和用户B在各自银行里面待多久都不会影响到彼此,不用担心自己的项链 被人偷换了。这就是ConcurrentHashMap的设计思路,用一个图来理解 从上图可以看出,此时锁的是对应的单个银行,而不是整个「银行者联盟」。分析 下这种设计的特点: 多个银行组成的「银行者联盟」 当有人来办理业务时,「银行者联盟」需要确定这个人去哪个银行 当此人去到指定银行办理业务后,该银行上锁,其他人不能同时执行修改操作,直 到此人离开后解锁. ConcurrentHashMap源码解析 ConcurrentHashMap 同样也分为 1.7 、1.8 版,两者在实现上略有不同。 先来看看 1.7 的实现,下面是结构图: 如图所示,是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组 加链表。主要是通过分段锁实现的。 关于分段锁 段Segment继承了重入锁ReentrantLock,有了锁的功能,每个锁控制的是一段, 当每个Segment越来越大时,锁的粒度就变得有些大了。 分段锁的优势在于保证在操作不同段 map 的时候可以并发执行,操作同段 map 的时候,进行锁的竞争和等待。这相对于直接对整个map同步 synchronized是有优势的。 缺点在于分成很多段时会比较浪费内存空间(不连续,碎片化); 操作map时竞争 同一个分段锁的概率非常小时,分段锁反而会造成更新等操作的长时间等待; 当 某个段很大时,分段锁的性能会下降。 1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存 在 HashMap 在 1.7 版本中的问题。 那就是查询遍历链表效率太低。 因此 1.8 做了一些数据结构上的调整。 首先来看下底层的组成结构: 其实和 1.8 HashMap 结构类似,当链表节点数超过指定阈值的话,也是会转换成红 黑树的,大体结构也是一样的。 那么 JDK 1.8 ConcurrentHashMap 到底是如何实现线程安全的? 答案:其中抛弃了原有的Segment 分段锁,而采用了 CAS + synchronized 来保证 并发安全性。(cas:比较并替换) **① 基本组成 ** 抛弃了 JDK 1.7 中原有的 Segment 分段锁,而采用了 CAS + synchronized 来 保证并发安全性。 将JDK 1.7 中存放数据的 HashEntry 改为 Node,但作用是相同的。、 我们来看看 ConcurrentHashMap 的几个重要属性. 重要组成元素 Node:链表中的元素为 Node 对象。他是链表上的一个节点,内部存储了 key、 value 值,以及他的下一 个节点的引用。这样一系列的 Node 就串成一串,组成一 个链表。 ForwardingNode:当进行扩容时,要把链表迁移到新的哈希表,在做这个操作 时,会在把数组中的头节点替换为 ForwardingNode 对象。ForwardingNode 中不 保存 key 和 value,只保存了扩容后哈希表 (nextTable)的引用。此时查找相应 node 时,需要去 nextTable 中查找。 TreeBin:当链表转为红黑树后,数组中保存的引用为 TreeBin,TreeBin 内部不保 存 key/value,他保存了 TreeNode 的 list 以及红黑树 root。 TreeNode:红黑树的节点。 **② put 方法过程 ** 存储结构定义了容器的 “形状”,那容器内的东西按照什么规则来放呢?换句话讲, 某个 key 是按 照什么逻辑放入容器的对应位置呢? 我们假设要存入的 key 为对象 x,这个过程如下 : 1、通过对象 x 的 hashCode () 方法获取其 hashCode; 2、将 hashCode 映射到数组的某个位置上; 3、把该元素存储到该位置的链表中。 put 方法用来把一个键值对存储到 map 中。代码如下: 实际调用的是 putVal 方 法,第三个参数传入 false,控制 key 存在时覆盖原来的值。 请先看完代码注释,有个大致的了解,然后我们更加详细的学习一下: 判断存储的 key、value 是否为空,若为空,则抛出异常,否则,进入步骤 2。 计算 key 的 hash 值,随后进入自旋,该自旋可以确保成功插入数据,若 table 表为空或者长度为 0,则初始化 table 表,否则,进入步骤 3。 根据 key 的 hash 值取出 table 表中的结点元素,若取出的结点为空(该桶为 空),则使用 CAS 将 key、value、hash 值生成的结点放入桶中。否则,进入 步骤 4。 若该结点的的 hash 值为 MOVED(-1),则对该桶中的结点进行转移,否则, 进入步骤 5。 5 . 对桶中的第一个结点(即 table 表中的结点)进行加锁,对该桶进行遍历,桶中 的结点的 hash 值与 key 值与给定的 hash 值和 key 值相等,则根据标识选择是 否进行更新操作(用给定的 value 值替换该结点的 value 值),若遍历完桶仍 没有找到 hash 值与 key 值和指定的 hash 值与 key 值相等的结点,则直接新生 一个结点并赋值为之前后一个结点的下一个结点。进入步骤 6。 若 binCount 值达到红黑树转化的阈值,则将桶中的结构转化为红黑树存储, 后,增加 binCount 的值。 如果桶中的第一个元素的 hash 值大于 0,说明是链表结构,则对链表插入或者 更新。 如果桶中的第一个元素是 TreeBin,说明是红黑树结构,则按照红黑树的方式进 行插入或者更新。 在锁的保护下,插入或者更新完毕后,如果是链表结构,需要判断链表中元素 的数量是否超过 8(默认),一旦超过,就需要考虑进行数组扩容,或者是链表 转红黑树。 扩容 什么时候会扩容? 使用put()添加元素时会调用addCount(),内部检查sizeCtl看是否需要扩容。 tryPresize()被调用,此方法被调用有两个调用点: 链表转红黑树(put()时检查)时如果table容量小于64(MIN_TREEIFY_CAPACITY),则会 触发扩容。 调用putAll()之类一次性加入大量元素,会触发扩容。 addCount() addCount()与tryPresize()实现很相似,我们先以addCount()分析下扩容逻辑: **1.链表转红黑树 ** 首先我们要理解为什么 Map 需要扩容,这是因为我们采用哈希表存储数据,当固定 大小的哈希表存 储数据越来越多时,链表长度会越来越长,这会造成 put 和 get 的 性能下降。此时我们希望哈希表中多一些桶位,预防链表继续堆积的更长。 ConcurrentHashMap 有链表转红黑树的操作,以提高查找的速度,红黑树时间复 杂度为 O (logn),而链表是 O (n/2),因此只在 O (logn)<O (n/2) 时才会进行转换, 也就是以 8 作为分界点。 接下来我们分析 treeifyBin 方法代码,这个代码中会选择是把此时保存数据所在的 链表转为红黑树,还是对整个哈希表扩容。 treeifyBin 不一定就会进行红黑树转换,也可能是仅仅做数组扩容。 构造完TreeBin这个空节点之后,就开始构造红黑树,首先是第一个节点,左右 子节点设置为空,作为红黑树的root节点,设置为黑色,父节点为空。 然后在每次添加完一个节点之后,都会调用balanceInsertion方法来维持这是一 个红黑树的属性和平衡性。红黑树所有操作的复杂度都是O(logn),所以当元素量比 较大的时候,效率也很高。 **数组扩容 ** 我们大致了解了 ConcurrentHashMap 的存储结构,那么我们思考一个问题,当数 组中保存的链表越来越多,那么再存储进来的元素大概率会插入到现有的链表中, 而不是使用数组中剩下的空位。 这样会造成数组中保存的链表越来越长,由此导致 哈希表查找速度下降,从 O (1) 慢慢趋近于链表 的时间复杂度 O (n/2),这显然违背 了哈希表的初衷。 所以 ConcurrentHashMap 会做一个操作, 称为扩容。也就是把数组长度变大,增 加更多的空位出来,终目的就是预防链表过长,这样查找的时间复杂度才会趋向于 O (1)。扩容的操作并不会在数组没有空位时才进行,因为在桶位快满时, 新保存元 素更大的概率会命中已经使用的位置,那么可能后几个桶位很难被使用,而链表却 越来 越长了。ConcurrentHashMap 会在更合适的时机进行扩容,通常是在数组中 75% 的位置被使用 时。 其实以上内容和 HashMap 类似,ConcurrentHashMap 此外提供了线程安全的保 证,它主要是通 过 CAS 和 Synchronized 关键字来实现,我们在源码分析中再详细 来看。 我们做一下总结: 1、ConcurrentHashMap 采用数组 + 链表 + 红黑树的存储结构; 2、存入的 Key 值通过自己的 hashCode 映射到数组的相应位置; 3、ConcurrentHashMap 为保障查询效率,在特定的时候会对数据增加长度,这个 操作叫做扩容; 4、当链表长度增加到 8 时,可能会触发链表转为红黑树(数组长度如果小于 64, 优先扩容,具体 看后面源码分析)。 接下来,我们的源码分析就从 ConcurrentHashMap 的构成、保存元素、哈希算 法、扩容、查找数 据这几个方面来进行 扩容后数组容量为原来的 2 倍。 **数据迁移( 扩容时的线程安全) ** ConcurrentHashMap 的扩容时机和 HashMap 相同,都是在 put 方法的后一步 检查是否需要扩容,如果需要则进行扩容,但两者扩容的过程完全不同, ConcurrentHashMap 扩容的方法叫做 transfer,从 put 方法的 addCount 方法进 去,就能找到 transfer 方法,transfer 方法的主要思路是: 首先需要把老数组的值全部拷贝到扩容之后的新数组上,先从数组的队尾开始 拷贝; 拷贝数组的槽点时,先把原数组槽点锁住,保证原数组槽点不能操作,成功拷 贝到新数组时,把 原数组槽点赋值为转移节点; 这时如果有新数据正好需要 put 到此槽点时,发现槽点为转移节点,就会一直 等待,所以在扩容完成之前,该槽点对应的数据是不会发生变化的; 从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组中的节点设置成转移 节点; 直到所有数组数据都拷贝到新数组时,直接把新数组整个赋值给数组容器,拷 贝完成 putTreeVal()与此方法遍历方式类似不再介绍。  ④ get 方法过程 ConcurrentHashMap 读的话,就比较简单,先获取数组的下标,然后通过判断数 组下标的 key 是 否和我们的 key 相等,相等的话直接返回,如果下标的槽点是链表 或红黑树的话,分别调用相应的 查找数据的方法,整体思路和 HashMap 很像,源 码如下: 计算 hash 值。 根据 hash 值找到数组对应位置: (n – 1) & h。 根据该位置处结点性质进行相应查找。 如果该位置为 null,那么直接返回 null。 如果该位置处的结点刚好就是需要的,返回该结点的值即可。 如果该位置结点的 hash 值小于 0,说明正在扩容,或者是红黑树。 如果以上 3 条都不满足,那就是链表,进行遍历比对即可。 ** 初始化数组 ** 数组初始化时,首先通过自旋来保证一定可以初始化成功,然后通过 CAS 设置 SIZECTL 变量的值,来保证同一时刻只能有一个线程对数组进行初始化,CAS 成功 之后,还会再次判断当前数组是否已经初始化完成,如果已经初始化完成,就不会 再次初始化,通过自旋 + CAS + 双重 check 等 手段保证了数组初始化时的线程安 全,源码如下: 里面有个关键的值 sizeCtl,这个值有多个含义。 1、-1 代表有线程正在创建 table; 2、-N 代表有 N-1 个线程正在复制 table; 3、在 table 被初始化前,代表 根据构造函数传入的值计算出的应被初始化的大小; 4、在 table 被初始化后,则被 设置为 table 大小 的 75%,代表 table 的容量(数组容量)。 initTable 中使用到 1 和 4,2 和 3 在其它方法中会有使用。下面我们可以先看下 ConcurrentHashMap 的构造方法,里面会使用上面的 3 最后来回顾总结下HashMap和ConcurrentHashMap对比 ConcurrentHashMap 和 HashMap 两者的相同之处: 1.数组、链表结构几乎相同,所以底层对数据结构的操作思路是相同的(只是思路 相同,底层实现 不同); 2.都实现了 Map 接口,继承了 AbstractMap 抽象类,所以大多数的方法也都是相 同的, HashMap 有的方法,ConcurrentHashMap 几乎都有,所以当我们需要从 HashMap 切换到 ConcurrentHashMap 时,无需关心两者之间的兼容问题 不同点: 1.红黑树结构略有不同,HashMap 的红黑树中的节点叫做 TreeNode,TreeNode 不仅仅有属 性,还维护着红黑树的结构,比如说查找,新增等等; ConcurrentHashMap 中红黑树被拆分成 两块,TreeNode 仅仅维护的属性和查找 功能,新增了 TreeBin,来维护红黑树结构,并负责根 节点的加锁和解锁; 2.新增 ForwardingNode (转移)节点,扩容的时候会使用到,通过使用该节点, 来保证扩容时的线程安全。

剑曼红尘 2020-03-25 11:21:44 0 浏览量 回答数 0

问题

备战大厂每日挑战算法,坚持打卡更有社区定制周边奖品等你赢!

被纵养的懒猫 2020-04-07 11:41:45 5309 浏览量 回答数 5

问题

前期规划少缺乏IT治理ERP实施失败主因

lovequeen0 2019-12-01 20:17:53 8869 浏览量 回答数 0

回答

详细解答可以参考官方帮助文档 UserDisable.UserDisable错误 当您访问OSS遇到如下的UserDisable错误: <Code>UserDisable</Code> <Message>UserDisable</Message> 有以下两类原因: 欠费被禁 确认欠费方法:在OSS 管理控制台上打开费用中心,检查是否欠费。如果欠费,请及时充值。 说明 OSS欠费后,还可以正常使用24小时,24小时后禁止访问。 历史数据保留15天,15天后历史数据将被删除。 当您在消息中心看到阿里云OSS欠费提醒后,如下图所示,请及时充值,不会影响您的正常使用。 安全原因被禁 在OSS 管理控制台上打开消息中心,在右侧的安全消息中查看违规通知。违规的原因有很多,比如您使用OSS做私服,您的图片涉黄、涉暴等。 说明 如果您有账户处于被禁状态,请务必处理,重新申请新账户,无法保证正常使用。 RequestTimeTooSkewed.The difference between…错误 访问OSS遇到如下的RequestTimeTooSkewed错误: <Code>RequestTimeTooSkewed</Code> <Message>The difference between the request time and the current time is too large.</Message> 原因:您发送请求的时间与OSS收到请求的时间,间隔超出了15分钟,OSS从安全考虑认为该请求是无效的,返回上述错误。请检查发送请求设备的系统时间,并根据时区调整到正确时间。 您可能会有下面的疑问: 发送请求的机器或设备的系统时间,调整标准是什么呢? OSS的系统时间采用GMT时间,您的设备的系统时间,需要调整到GMT时间,或与其相对应的时区时间。GMT(Greenwich Mean Time)是零时区的区时,即世界标准时间。 例如,您访问OSS的设备系统配置是东八区,系统时间调整到比GMT早8小时。我国的标准时间—北京时间—就是东八区时间。如果您的系统时间是东八区,那么您的系统时间调整到北京时间即可。 Windows系统查看时区的方法: 通过控制面板 > 时钟、语言和区域 > 设置日期和时间,打开日期和时间,时区 栏的+08:00,表示您的设备时区是东八区。 Linux/Unix系统查看时区的方法: 请执行date -R查看时间和时区。下图中的 +0800,表示您的设备系统时区是东八区。 使用多个地域的OSS,比如杭州、新加坡、美国,时间同步有问题吗? 没有问题。每个地域的OSS都使用GMT时间,您发送请求的设备系统时间也是GMT时间。 InvalidAccessKeyId.The OSS Access Key Id…错误 访问OSS遇到如下的错误: <Code>InvalidAccessKeyId</Code> <Message>The OSS Access Key Id you provided does not exist in our records.</Message> 原因:您的AccessKeyID禁用或不存在。排查方法如下: 登录阿里云控制台的 AccessKey 管理,确认访问OSS使用的AccessKeyID存在且处于启用状态。 如果您的AccessKeyID处于禁用状态,请开启。 如果您的AccessKeyID不存在请创建,并使用新的AccessKeyID访问OSS。 AccessDenied.The bucket you are attempting to…错误 访问OSS遇到如下的错误: <Code>AccessDenied</Code> <Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message> 原因:您访问Bucket使用的Endpoint不正确,如果您需要了解Endpoint的详细信息,请参看OSS 基本概念。 怎么找到正确的Endpoint呢?如果SDK异常抛出如下的异常,或返回如下错误: <Error> <Code>AccessDenied</Code> <Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message> <RequestId>56EA****3EE6</RequestId> <HostId>my-oss-bucket-*****.aliyuncs.com</HostId> <Bucket>my-oss-bucket-***</Bucket> <Endpoint>oss-cn-****.aliyuncs.com</Endpoint> </Error> 其中Endpoint中的oss-cn-****.aliyuncs.com就是正确的Endpoint,请使用http://oss-cn-****.aliyuncs.com或https://oss-cn-****.aliyuncs.com作为Endpoint访问OSS。 如果错误中没有Endpoint,请登录OSS控制台,在Bucket管理中找到您访问的Bucket,单击进入Bucket概览页面。OSS域名中可以看到内网和外网域名。 外网域名是在公网上访问OSS使用的域名;内网域名是指在阿里云内部访问的OSS使用的域名。比如您在您的ECS上访问OSS,可以使用内网域名。 Endpoint是域名去掉Bucket部分,加上访问协议。例如上图中OSS的公网域名是oss-****.aliyuncs.com,它的公网Endpoint是http://oss-cn-****.aliyuncs.com;类似,内网Endpoint是http://oss-cn-****-internal.aliyuncs.com。 ImageDamage.The image file may be damaged错误 访问OSS遇到如下的错误: <Code>ImageDamage</Code> <Message>The image file may be damaged.</Message> 原因:说明图片文件有部分信息丢失或损坏,导致无法正常识别或处理。您可能会有一个疑问,某图片在本地用图片处理器可以打开,OSS处理报错。原因是,图片浏览器会对损坏的图片做些处理,OSS图片服务暂时没有这个操作。 AccessDenied.AccessDenied错误 访问OSS遇到如下的错误: <Code>AccessDenied</Code> <Message>AccessDenied</Message> 原因:说明访问OSS的用户没有当前操作的权限。请确认使用的AccessKeyID/AccessKeySecret是正确的。如果使用的是子帐号/临时账户(STS),请确认当前用户的权限。确认方法: 在访问控制管理控制台单击用户管理,单击需要确认权限的用户,单击用户授权策略 > 加入组的授权策略查看该用户的权限,确认是否已经赋予当前用户Bucket/Object的操作权限。 SignatureDoesNotMatch.The request signature we calculated…错误 访问OSS遇到如下的错误: <Code>SignatureDoesNotMatch</Code> <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message> 请按照以下步骤排查: 检查endpoint 请检查endpoint前面没有Bucket,后面没有多余的/,前后没有多余的空格。比如下面的endpoint是不合法的,http://my-bucket.oss-cn-hangzhou.aliyuncs.com、http://oss-cn-hangzhou.aliyuncs.com/、而 http:// oss-cn-hangzhou.aliyuncs.com 或 https:// oss-cn-hangzhou.aliyuncs.com是合法域名。 检查AccessKeyID/AccessKeySecret 请确认AccessKeyID/AccessKeySecret正确,确保AccessKeyID/AccessKeySecret前后都没有空格,特别是使用了复制粘贴的情况。 检查BucketName/ObjectKey 请确保BucketName/ObjectKey命名合法有效,符合要求。 Bucket命名规范:只能包括小写字母、数字和短横线(-),必须以小写字母或者数字开头,长度必须在3-63字节之间。 Object的命名规范:使用UTF-8编码,长度必须在1-1023字节之间,不能以“/”或者“\”字符开头。 如果是您自己实现的签名,请使用OSS SDK提供的签名方法。 OSS SDK提供了URL/Header签名的实现,详细请参看SDK文档。 如果您的环境不适合使用SDK,需要自己实现签名,签名方法请参考用户签名验证,仔细检查每个签名字段。 OSS的论坛上提供了一个可视化签名的工具,请比较每个签名字段和最后的签名,签名工具地址。 如果您使用了代理,请检查代理服务器是否添加额外的Header。 其它错误 请根据SDK返回的错误码、错误信息判断原因,特别是错误信息会提示错误原因。如果怀疑错误跟网络环境有关,请使用ossprobe排查问题,ossprobe会给出可能的原因。

2019-12-01 23:13:41 0 浏览量 回答数 0

更多推荐

阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站