精华内容
下载资源
问答
  • 一、预备知识—程序的内存分配  堆(heap)和栈(stack)是C/C++编程不可避免会碰到... 具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门
    一、预备知识—程序的内存分配
           堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数据结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。
           在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系统提供的数据结构,而堆则是C/C++函数库提供的。
          具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。
          机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的 ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因.
           和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存
    空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:
            1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。
            2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。
             3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。
    一个由c/C++编译的程序占用的内存分为以下几个部分
    1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
    3、
    全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 
    4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
    5、程序代码区—存放函数体的二进制代码。
    二、例子程序 
    这是一个前辈写的,非常详细 
    //main.cpp 
    int a = 0; 全局初始化区 
    char *p1; 全局未初始化区 
    main() 

    int b; 栈 
    char s[] = "abc"; 栈 
    char *p2; 栈 
    char *p3 = "123456"; 123456/0在常量区,p3在栈上,可否看成是一种映射。 
    static int c =0; 全局(静态)初始化区 
    p1 = (char *)malloc(10); 
    p2 = (char *)malloc(20); 
    分配得来得10和20字节的区域就在堆区。 
    strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方,一个常量区块可能对应(映射)多个变量区,优化内存。 

    二、堆和栈的理论知识 
    2.1申请方式 
    stack: 
    由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
    heap: 
    需要程序员自己申请,并指明大小,在c中malloc函数 
    如p1 = (char *)malloc(10); 
    在C++中用new运算符 
    如p2 = (char *)malloc(10); 
    但是注意p1、p2本身是在栈中的,但是分别为其的内存区间在堆(heap)区。 

    2.2 
    申请后系统的响应 
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 
    2.3申请大小的限制 
    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 

    2.4
    申请效率的比较: 
    栈由系统自动分配,速度较快。但程序员是无法控制的。 
    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 
    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存(参见《win32的内存分配方式和调试机制 》),他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。 
    2.5堆和栈中的存储内容 
    栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,
    在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 
           当本次函数调用结束后,局部变量先出栈(后进先出),然后是参数(先进后出),最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 
    2.6存取效率的比较 
    char s1[] = "aaaaaaaaaaaaaaa";
    char *s2 = "bbbbbbbbbbbbbbbbb";
     bbbbbbbbbbbbbbbbbbb常量存储区
    aaaaaaaaaaa
    是在运行时刻赋值的; 
    bbbbbbbbbbb是在编译时就确定的; 
    在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)
    对应的汇编代码 
    10: a = c[1]; 
    00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
    0040106A 88 4D FC mov byte ptr [ebp-4],cl 
    11: a = p[1]; 
    0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
    00401070 8A 42 01 mov al,byte ptr [edx+1] 
    00401073 88 45 FC mov byte ptr [ebp-4],al 
    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,再根据edx读取字符,显然慢了
    2.7小结: 
    堆和栈的区别可以用如下的比喻来看出: 
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
    堆和栈的区别主要分:
    操作系统方面的堆和栈,如上面说的那些,不多说了。
    还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
    虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。
    另有说法:(仅供参考)
    1)  about  stack,  system  will  allocate  memory  to  the  instance  
    of  object  automatically,  and  to  the  heap,  you  must  allocate  
    memory  to  the  instance  of  object  with  new  or  malloc  manually.  
    2)  when  function  ends,  system  will  automatically  free  the  
    memory  area  of  stack,  but  to  the  heap,  you  must  free  the  
    memory  area  manually  with  free  or  delete,  else  it  will  result 
     in  memory  leak. 
    大致意思如下:
    stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack一般是静态分配内存,heap上一般是动态分配内存
    展开全文
  • redis机制

    2019-04-24 14:40:04
    所谓的片其实就是大的数据分成几个小的部分,分别放置存储,对于数据而言无外乎就是读写,读写的基础就是数据的定位,redis利用集群的方式+槽位完成,片的数据的定位和管理维护。 Redis 集群是一个可以在多个...

     

    redis分片机制

      所谓的分片其实就是大的数据分成几个小的部分,分别放置存储,对于数据而言无外乎就是读写,读写的基础就是数据的定位,redis利用集群的方式+槽位完成,分片的数据的定位和管理维护。

       Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。

    Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

    1. 槽位分配

    集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

    • 节点 A 负责处理 0 号至 5500 号哈希槽。
    • 节点 B 负责处理 5501 号至 11000 号哈希槽。
    • 节点 C 负责处理 11001 号至 16384 号哈希槽。

    这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

    • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
    • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

    因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

      2.高可用性

    为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

    在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。

    另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。

    不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作

    3.一致性保证(非强一致性)

    Redis 集群不保证数据的强一致性(strong consistency): 在特定条件下, Redis 集群可能会丢失已经被执行过的写命令。

    使用异步复制(asynchronous replication)是 Redis 集群可能会丢失写命令的其中一个原因。 考虑以下这个写命令的例子:

    • 客户端向主节点 B 发送一条写命令。
    • 主节点 B 执行写命令,并向客户端返回命令回复。
    • 主节点 B 将刚刚执行的写命令复制给它的从节点 B1 、 B2 和 B3 。

    如你所见, 主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。

    4.创建集群(不详说啦,参照我的另一篇博客)

    现在我们已经有了六个正在运行中的 Redis 实例, 接下来我们需要使用这些实例来创建集群, 并为每个节点编写配置文件。

    通过使用 Redis 集群命令行工具 redis-trib , 编写节点配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群, 检查集群, 或者对集群进行重新分片(reshared)等工作。

    我们需要执行以下命令来创建集群:

    ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
    127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

    命令的意义如下:

    • 给定 redis-trib.rb 程序的命令是 create , 这表示我们希望创建一个新的集群。
    • 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
    • 之后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。

    简单来说, 以上命令的意思就是让 redis-trib 程序创建一个包含三个主节点和三个从节点的集群。

    接着, redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yes , redis-trib 就会将这份配置应用到集群当中:

    >>> Creating cluster
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    Connecting to node 127.0.0.1:7005: OK
    >>> Performing hash slots allocation on 6 nodes...
    Using 3 masters:
    127.0.0.1:7000
    127.0.0.1:7001
    127.0.0.1:7002
    127.0.0.1:7000 replica #1 is 127.0.0.1:7003
    127.0.0.1:7001 replica #1 is 127.0.0.1:7004
    127.0.0.1:7002 replica #1 is 127.0.0.1:7005
    M: 9991306f0e50640a5684f1958fd754b38fa034c9 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    M: e68e52cee0550f558b03b342f2f0354d2b8a083b 127.0.0.1:7001
    slots:5461-10921 (5461 slots) master
    M: 393c6df5eb4b4cec323f0e4ca961c8b256e3460a 127.0.0.1:7002
    slots:10922-16383 (5462 slots) master
    S: 48b728dbcedff6bf056231eb44990b7d1c35c3e0 127.0.0.1:7003
    S: 345ede084ac784a5c030a0387f8aaa9edfc59af3 127.0.0.1:7004
    S: 3375be2ccc321932e8853234ffa87ee9fde973ff 127.0.0.1:7005
    Can I set the above configuration? (type 'yes' to accept): yes
    

    输入 yes 并按下回车确认之后, 集群就会将配置应用到各个节点, 并连接起(join)各个节点 —— 也即是, 让各个节点开始互相通讯:

    >>> Nodes configuration updated
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join...
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: 9991306f0e50640a5684f1958fd754b38fa034c9 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    M: e68e52cee0550f558b03b342f2f0354d2b8a083b 127.0.0.1:7001
    slots:5461-10921 (5461 slots) master
    M: 393c6df5eb4b4cec323f0e4ca961c8b256e3460a 127.0.0.1:7002
    slots:10922-16383 (5462 slots) master
    M: 48b728dbcedff6bf056231eb44990b7d1c35c3e0 127.0.0.1:7003
    slots: (0 slots) master
    M: 345ede084ac784a5c030a0387f8aaa9edfc59af3 127.0.0.1:7004
    slots: (0 slots) master
    M: 3375be2ccc321932e8853234ffa87ee9fde973ff 127.0.0.1:7005
    slots: (0 slots) master
    [OK] All nodes agree about slots configuration.
    

    如果一切正常的话, redis-trib 将输出以下信息:

    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    

    这表示集群中的 16384 个槽都有至少一个主节点在处理, 集群运作正常。

    5.重新分区

    现在, 让我们来试试对集群进行重新分片操作。

    在执行重新分片的过程中, 请让你的 example.rb 程序处于运行状态, 这样你就会看到, 重新分片并不会对正在运行的集群程序产生任何影响, 你也可以考虑将 example.rb 中的 sleep 调用删掉, 从而让重新分片操作在近乎真实的写负载下执行。

    重新分片操作基本上就是将某些节点上的哈希槽移动到另外一些节点上面, 和创建集群一样, 重新分片也可以使用 redis-trib 程序来执行。

    执行以下命令可以开始一次重新分片操作:

    $ ./redis-trib.rb reshard 127.0.0.1:7000

    你只需要指定集群中其中一个节点的地址, redis-trib 就会自动找到集群中的其他节点。

    目前 redis-trib 只能在管理员的协助下完成重新分片的工作, 要让 redis-trib 自动将哈希槽从一个节点移动到另一个节点, 目前来说还做不到 (不过实现这个功能并不难)。

    执行 redis-trib 的第一步就是设定你打算移动的哈希槽的数量:

    $ ./redis-trib.rb reshard 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7005: OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: 9991306f0e50640a5684f1958fd754b38fa034c9 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    M: 393c6df5eb4b4cec323f0e4ca961c8b256e3460a 127.0.0.1:7002
    slots:10922-16383 (5462 slots) master
    S: 3375be2ccc321932e8853234ffa87ee9fde973ff 127.0.0.1:7005
    slots: (0 slots) slave
    M: e68e52cee0550f558b03b342f2f0354d2b8a083b 127.0.0.1:7001
    slots:5461-10921 (5461 slots) master
    S: 48b728dbcedff6bf056231eb44990b7d1c35c3e0 127.0.0.1:7003
    slots: (0 slots) slave
    S: 345ede084ac784a5c030a0387f8aaa9edfc59af3 127.0.0.1:7004
    slots: (0 slots) slave
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    How many slots do you want to move (from 1 to 16384)? 1000

    我们将打算移动的槽数量设置为 1000 个, 如果 example.rb 程序一直运行着的话, 现在 1000 个槽里面应该有不少键了。

    除了移动的哈希槽数量之外, redis-trib 还需要知道重新分片的目标(target node), 也即是, 负责接收这 1000 个哈希槽的节点。

    指定目标需要使用节点的 ID , 而不是 IP 地址和端口。 比如说, 我们打算使用集群的第一个主节点来作为目标, 它的 IP 地址和端口是 127.0.0.1:7000 , 而节点 ID 则是 9991306f0e50640a5684f1958fd754b38fa034c9 , 那么我们应该向 redis-trib 提供节点的 ID :

    $ ./redis-trib.rb reshard 127.0.0.1:7000
    ...
    What is the receiving node ID? 9991306f0e50640a5684f1958fd754b38fa034c9

    redis-trib 会打印出集群中所有节点的 ID , 并且我们也可以通过执行以下命令来获得节点的运行 ID :

    $ ./redis-cli -p 7000 cluster nodes | grep myself
    9991306f0e50640a5684f1958fd754b38fa034c9 :0 myself,master - 0 0 0 connected 0-5460

    接着, redis-trib 会向你询问重新分片的源节点(source node), 也即是, 要从哪个节点中取出 1000 个哈希槽, 并将这些槽移动到目标节点上面。

    如果我们不打算从特定的节点上取出指定数量的哈希槽, 那么可以向 redis-trib 输入 all , 这样的话, 集群中的所有主节点都会成为源节点, redis-trib 将从各个源节点中各取出一部分哈希槽, 凑够 1000 个, 然后移动到目标节点上面:

    $ ./redis-trib.rb reshard 127.0.0.1:7000
    ...
    Please enter all the source node IDs.
    Type 'all' to use all the nodes as source nodes for the hash slots.
    Type 'done' once you entered all the source nodes IDs.
    Source node #1:all

    输入 all 并按下回车之后, redis-trib 将打印出哈希槽的移动计划, 如果你觉得没问题的话, 就可以输入 yes 并再次按下回车:

    $ ./redis-trib.rb reshard 127.0.0.1:7000
    ...
    Moving slot 11421 from 393c6df5eb4b4cec323f0e4ca961c8b256e3460a
    Moving slot 11422 from 393c6df5eb4b4cec323f0e4ca961c8b256e3460a
    Moving slot 5461 from e68e52cee0550f558b03b342f2f0354d2b8a083b
    Moving slot 5469 from e68e52cee0550f558b03b342f2f0354d2b8a083b
    ...
    Moving slot 5959 from e68e52cee0550f558b03b342f2f0354d2b8a083b
    Do you want to proceed with the proposed reshard plan (yes/no)? yes

    输入 yes 并使用按下回车之后, redis-trib 就会正式开始执行重新分片操作, 将指定的哈希槽从源节点一个个地移动到目标节点上面:

    $ ./redis-trib.rb reshard 127.0.0.1:7000
    ...
    Moving slot 5934 from 127.0.0.1:7001 to 127.0.0.1:7000:
    Moving slot 5935 from 127.0.0.1:7001 to 127.0.0.1:7000:
    Moving slot 5936 from 127.0.0.1:7001 to 127.0.0.1:7000:
    Moving slot 5937 from 127.0.0.1:7001 to 127.0.0.1:7000:
    ...
    Moving slot 5959 from 127.0.0.1:7001 to 127.0.0.1:7000:

    在重新分片的过程中, example.rb 应该可以继续正常运行, 不会出现任何问题。

    在重新分片操作执行完毕之后, 可以使用以下命令来检查集群是否正常:

    $ ./redis-trib.rb check 127.0.0.1:7000
    Connecting to node 127.0.0.1:7000: OK
    Connecting to node 127.0.0.1:7002: OK
    Connecting to node 127.0.0.1:7005: OK
    Connecting to node 127.0.0.1:7001: OK
    Connecting to node 127.0.0.1:7003: OK
    Connecting to node 127.0.0.1:7004: OK
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: 9991306f0e50640a5684f1958fd754b38fa034c9 127.0.0.1:7000
    slots:0-5959,10922-11422 (6461 slots) master
    M: 393c6df5eb4b4cec323f0e4ca961c8b256e3460a 127.0.0.1:7002
    slots:11423-16383 (4961 slots) master
    S: 3375be2ccc321932e8853234ffa87ee9fde973ff 127.0.0.1:7005
    slots: (0 slots) slave
    M: e68e52cee0550f558b03b342f2f0354d2b8a083b 127.0.0.1:7001
    slots:5960-10921 (4962 slots) master
    S: 48b728dbcedff6bf056231eb44990b7d1c35c3e0 127.0.0.1:7003
    slots: (0 slots) slave
    S: 345ede084ac784a5c030a0387f8aaa9edfc59af3 127.0.0.1:7004
    slots: (0 slots) slave
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.

    根据检查结果显示, 集群运作正常。

    需要注意的就是, 在三个主节点中, 节点 127.0.0.1:7000 包含了 6461 个哈希槽, 而节点 127.0.0.1:7001 和节点 127.0.0.1:7002 都只包含了 4961 个哈希槽, 因为后两者都将自己的 500 个哈希槽移动到了节点 127.0.0.1:7000 。

    6.添加新的节点(特地放在5之后)

    根据新添加节点的种类, 我们需要用两种方法来将新节点添加到集群里面:

    • 如果要添加的新节点是一个主节点, 那么我们需要创建一个空节点(empty node), 然后将某些哈希桶移动到这个空节点里面。
    • 另一方面, 如果要添加的新节点是一个从节点, 那么我们需要将这个新节点设置为集群中某个节点的复制品(replica)。

    本节将对以上两种情况进行介绍, 首先介绍主节点的添加方法, 然后再介绍从节点的添加方法。

    无论添加的是那种节点, 第一步要做的总是添加一个空节点。

    我们可以继续使用之前启动 127.0.0.1:7000 、 127.0.0.1:7001 等节点的方法, 创建一个端口号为 7006 的新节点, 使用的配置文件也和之前一样, 只是记得要将配置中的端口号改为 7000 。

    以下是启动端口号为 7006 的新节点的详细步骤:

    1. 在终端里创建一个新的标签页。
    2. 进入 cluster-test 文件夹。
    3. 创建并进入 7006 文件夹。
    4. 将 redis.conf 文件复制到 7006 文件夹里面,然后将配置中的端口号选项改为 7006 。
    5. 使用命令 ../../redis-server redis.conf 启动节点。

    如果一切正常, 那么节点应该会正确地启动。

    接下来, 执行以下命令, 将这个新节点添加到集群里面:

    ./redis-trib.rb addnode 127.0.0.1:7006 127.0.0.1:7000

    命令中的 addnode 表示我们要让 redis-trib 将一个节点添加到集群里面, addnode 之后跟着的是新节点的 IP 地址和端口号, 再之后跟着的是集群中任意一个已存在节点的 IP 地址和端口号, 这里我们使用的是 127.0.0.1:7000 。

    通过 cluster nodes 命令, 我们可以确认新节点 127.0.0.1:7006 已经被添加到集群里面了:

    redis 127.0.0.1:7006> cluster nodes
    3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385543178575 0 connected 5960-10921
    3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385543179583 0 connected
    f093c80dde814da99c5cf72a7dd01590792b783b :0 myself,master - 0 0 0 connected
    2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543178072 3 connected
    a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385543178575 0 connected
    97a3a64667477371c4479320d683e4c8db5858b1 127.0.0.1:7000 master - 0 1385543179080 0 connected 0-5959 10922-11422
    3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385543177568 3 connected 11423-16383

    新节点现在已经连接上了集群, 成为集群的一份子, 并且可以对客户端的命令请求进行转向了, 但是和其他主节点相比, 新节点还有两点区别:

    • 新节点没有包含任何数据, 因为它没有包含任何哈希桶。
    • 尽管新节点没有包含任何哈希桶, 但它仍然是一个主节点, 所以在集群需要将某个从节点升级为新的主节点时, 这个新节点不会被选中。

    接下来, 只要使用 redis-trib 程序, 将集群中的某些哈希桶移动到新节点里面, 新节点就会成为真正的主节点了。

    因为使用 redis-trib 移动哈希桶的方法在前面已经介绍过, 所以这里就不再重复介绍了。

    现在, 让我们来看看, 将一个新节点转变为某个主节点的复制品(也即是从节点)的方法。

    举个例子, 如果我们打算让新节点成为 127.0.0.1:7005 的从节点, 那么我们只要用客户端连接上新节点, 然后执行以下命令就可以了:

    redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

    其中命令提供的 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 就是主节点 127.0.0.1:7005 的节点 ID 。

    执行 cluster replicate 命令之后, 我们可以使用以下命令来确认 127.0.0.1:7006 已经成为了 ID 为 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 的节点的从节点:

    $ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
    f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected
    2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connected

    3c3a0c... 现在有两个从节点, 一个从节点的端口号为 7002 , 而另一个从节点的端口号为 7006 

    展开全文
  • 类加载器子系统分为三阶段: 加载阶段 链接阶段 初始化阶段 1.类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识(16进制CA TE BA BE); 2.ClassLoader只负责class文件...

    类加载子系统

    在这里插入图片描述

    一、类加载子系统简介

    类加载器子系统分为三个阶段:

    • 加载阶段
    • 链接阶段
    • 初始化阶段

    1.类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识(16进制CA TE BA BE);

    2.ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine来决定

    3.加载后的Class类信息存放于一块成为方法区的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

    4.如果调用构造器实例化对象,则其实例存放在堆区

    二、安装ClassViewer插件

    环境:IDEA

    安装class文件读取插件jclasslib is a bytecode viewer

    在这里插入图片描述

    对类进行编译

    在这里插入图片描述

    显示class字节码文件

    在这里插入图片描述

    二、类加载过程

    在这里插入图片描述

    1.加载

    1.通过一个类的全限定明获取定义此类的二进制字节流;

    2.将这个字节流所代表的的静态存储结构转化为方法区的运行时数据;

    3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

    2.链接

    1.验证

    • 确保Class文件的字节流包含信息符合虚拟机 要求和安全性,保证被加载类的正确性
    • 包括四种验证,文件格式验证,源数据验证,字节码验证,符号引用验证

    2.准备

    • 为类变量分配内存并且设置该类变量的默认初始值,为零值
    • final修饰的staic变量为常量,编译时已经分配值
    • 不为实例变量分配初始化,类变量会在方法区中,而实例变量会随着对象一起分配到java堆中

    3.解析

    • 将常量池内的符号引用转换为直接引用的过程。

    • 事实上,解析操作网晚会伴随着jvm在执行完初始化之后再执行

    • 符号引用就是一组符号来描述所引用的目标。符号应用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄

    • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

    3、初始化

    1.初始化阶段时执行类构造器方法()的过程

    2.此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来(比如静态变量和静态代码块)

    3.构造器方法中指令按语句在源文件中的出现的执行顺序

    public class Test1 {
        public static int num = 10;
        static {
            num = 20;
            number = 10;//在prepared阶段就已经初始化number = 0,所以可以
            System.out.println(num);//0->10->20
            //System.out.println(number);//不能向前引用,因为定义变量是在后面的
        }
        public static int number = 20;//0->10->20
    
    }
    

    4.如果没有静态变量c,那么字节码文件中就不会有()方法

    5.()方法就是一个类声明以后的构造方法

    6.若一个类有父类,则父类先执行()然后子类再执行

    7.虚拟机必须保证一个类的()方法在多线程下被同步加锁(也就是说,如果已经被加载还没执行,别的线程就不能去加载)

    三、类加载器的分类

    1.JVM支持两种类型的加载器,分别为引导类加载器(BootStrap ClassLoader,C/C++实现)和自定义类加载器(User-Defined ClassLoader由Java实现)

    2.从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器

    在这里插入图片描述

    图中的加载器划分关系为包含关系,并不是继承关系

    除了BootStrap ClassLoader,其他都是继承ClassLoader类

    public class ClassLoaderTest {
        public static void main(String[] args) {
    
            //获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //获取其上层:扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
    
            //获取其上层:获取不到引导类加载器
            ClassLoader bootstrapClassLoader = extClassLoader.getParent();
            System.out.println(bootstrapClassLoader);//null
    
            //对于用户自定义类来说:默认使用系统类加载器进行加载
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
            ClassLoader classLoader1 = String.class.getClassLoader();
            System.out.println(classLoader1);//null
    
    
        }
    }
    

    1.引导类加载器

    引导类加载器(启动类加载器,bootstrap classloader)

    • 使用C/C++实现,嵌套在JVM内部

    • 它用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类

    • 并不继承自java.lang.ClassLoader,没有父加载器

    • 加载拓展类和应用程序类加载器,并指定为他们的父加载器,即ClassLoader

    • 出于安全考虑,BootStrap启动类加载器只加载包名为java、javax、sun等开头的类

    2.扩展类加载器

    • java语言编写 ,由sun.misc.Launcher$ExtClassLoader实现。

    • 派生于ClassLoader类

    • 父类加载器为启动类加载器

    • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载

    3.应用程序类加载器

    1.java语言编写, 由sun.misc.Launcher$AppClassLoader实现。

    2.派生于ClassLoader类

    3.它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库

    4.该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载

    5.通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

    4.用户自定义类加载器

    为什么要定义类加载器?

    1.隔离加载类

    2.修改类加载的方式

    3.拓展加载源

    4.防止源码泄漏

    实现步骤

    继承java.lang.ClassLoader类,重写findClass方法

    四、双亲委派机制和沙箱安全机制

    1.双亲委派机制

    在这里插入图片描述

    简而言之,就是如果需要加载一个类,会以递归的方式向上委托直到顶层引导类加载器,如果加载不了再层层下放给子类加载器来加载,只要有一个加载成功,加载完成。

    例如:

    在这里插入图片描述

    自定义一个java.lang.String,启动main方法,却没有任何输出,直接报错,是因为将加载请求委派给了引导类加载器的String,但是里面没有main方法,所以加载不到这里就已经把String加载完了。

    优点:

    • 避免类的重复加载

    • 这样做明显提高了安全性,保证了核心代码的安全可用,防止api被随意篡改

    2.沙箱安全机制

    就像在上个例子中说到,自定义String类是先使用引导类加载器进行加载,先加载自带的String,进而保护核心源代码,这就是沙箱安全机制。

    五.补充

    1.JVM中两个class对象是否为同一个

    JVM中两个class对象是否为同一个的两个条件

    1.类的完整类名必须一致,包括包名

    2.加载这个类的ClassLoader必须一致

    如果一个类型由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中

    2.类的主动使用和被动使用

    主动使用,分为七种情况:

    1.创建类的实例

    2.访问某各类或接口的静态变量,或者对静态变量赋值

    3.调用类的静态方法

    4.反射 比如Class.forName(com.dsh.jvm.xxx)

    5.初始化一个类的子类

    6.java虚拟机启动时被标明为启动类的类

    7.JDK 7 开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

    除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

    主要就是在加载过程的这个初始化阶段,()方法是否被调用,如果被调用就是完成了初始化,就是主动使用

    展开全文
  • 1、JVM垃圾回收机制,GC发生在JVM哪个部分,有种GC,他们的算法是什么? 答:堆 heap,GC 是代收集算法,有种策略。 次数上频繁收集Young区-Minor GC 次数上较少收集Old区 full GC 基本不动 Perm区。永久区是...

    主要发生在 堆 heap

    GC 是分代收集算法,有几种策略。

    • 次数上频繁收集Young区-Minor GC
    • 次数上较少收集Old区 full GC
    • 基本不动 Perm区。永久区是没有GC的。

    GC的算法是什么?

    • 引用计数法: 目前已经被淘汰了,因为这个不能处理循环引用的问题
    • 复制算法: 在MinorGC中,就是用这个算法。
      • 缺点:需要双倍空间
      • 优点:内存连续,没有内存碎片,比较高效。
    • 标记清除算法: 发生在老年代
      标记:从根集合开始扫描,对存活的对象进行标记。
      清除:扫描整个内存空间,回收未被标记的对象,使用free-list记录可以区域。
      • 优点:不需要额外空间
      • 缺点:两次扫描,标记一次,清除一次。耗时严重,会发生内存碎片。
    • 标记压缩:也是发生在老年代
      先进行标记,再进行压缩。这里的压缩就是再次扫描,并往一端滑动存活对象。

    老年代full GC有两种标记算法,这两个算法是结合使用。清除后会出现内存碎片,然后再进行压缩,然后进行统一处理。

    展开全文
  • 栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作,这几个区域内不需要过多考虑回收的问题。 而堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存...
  • 堆内存机制详解

    2020-09-03 21:59:38
    1.jvm的内存模型包括以下几部分: 方法区:是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 Java堆:堆用来存储几乎所有对象、数组等都在此分配内存,...
  • 视觉中的Attention 学习的也是一权重分布,将权重分布施加到原来的特征上,使用权重的方式有下面种方式。 加权保存所有的分量做加权。 可以在分布中以某种采样策略选取部分分量做加权(hard attention) 加权...
  • web安全核心机制

    2014-02-22 18:48:29
    web安全核心机制分以下几个部分 1. 用户访问  a. 认证,用户身份识别  b. 会话管理,用户验证之后,接下来通过回话来识别用户,这种方式最主要的是cookie机制,其中cookie生产机制是这个阶段最重要的安全漏洞来源 ...
  • JAVA异常机制Exception

    2020-05-22 20:36:26
    了解JAVA中的异常,需要了解下列几个部分 第一部分: 什么是异常?异常机制是什么? 常见的异常有哪些? 异常机制的分类 异常,就是程序运行(编译)过程中出现的非正常现象. 异常机制:JAVA用来处理异常的...
  • HDFS 元数据管理机制

    2019-09-17 08:36:35
    HDFS 元数据管理机制 文章目录HDFS 元数据管理机制元数据管理概述元数据目录相关文件dfs.namenode.name.dirVERSIONseen_txidFsimage &...HDFS 元数据,按类型,主要包括以下几个部分: 文件、目...
  • C++的几个基本概念

    2015-12-04 15:00:41
    这么一大堆名词,实际上就围绕一件事展开,就是多态,其他三名词都是为实现C++的多态机制而提出的一些规则,下面部分介绍,第一部分介绍【多态】,第二部分介绍【虚函数,纯虚函数,抽象类】一 【多态】多态的...
  • NameNode、SecondaryNameNode、DataNode这几个角色。  NameNode:是Master节点,是大领导。管理数据块映射;处理客户端的读写请求;配置副本策略;管理HDFS的名称空间;  SecondaryNameNode:是一个小弟,分担...
  • Python垃圾回收机制

    2020-12-09 15:41:55
    本篇文章从C语言源码底层来聊聊Python内存管理和垃圾回收机制到底是啥?让你能够真正了解内存管理&垃圾回收。 1. 白话垃圾回收 用通俗的语言解释内存管理和垃圾回收的过程,搞懂这一部分就可以去面试、去...
  • web缓存机制

    2018-04-09 08:30:53
    使用缓存主要有几个好处: 一是能够减轻服务器端的访问压力; 二是使用本地缓存能够加快页面的载入速度,提升用户体验。 web缓存分类 在了解如何使用缓存来优化我们的web应用之前,我们先来看看web缓存具体有哪些...
  • 信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念、Linux对信号机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。  信号机制是进程之间相互传递消息的一种方法,信号全称为软...
  • 以下会拆成两个部分进行说明:1、总体机制部分2、Sql操作细节部分 总体机制 可以看到,有几个重要的类: AbstractDaoMaster : 总入口类,每个db有相应的AbstractDaoMaster去管理,一般情况下用于创建...
  • Java的类加载器(ClassLoader)主要有以下种: 类加载器:jre的一部分,负责动态地将类添加到Java虚拟机。 类加载器的分类: 启动类加载器 BootstrapClassLoader:加载jre/lib/rt.jar; 扩展类加载器 ...
  • JVM:垃圾回收机制和调优手段我们都知道JVM内存由几个部分组成:堆、方法区、栈、程序计数器、本地方法栈 JVM垃圾回收仅仅针对公共内存区域即:堆和方法区进行。 本文主要讨论两点,一是垃圾回收策略,二是调优的...
  • 下面我们分几个部分来讲解 1,这些概念你知道是什么意思吗? 相信你应该听过不少这样的词语, 类加载机制,双亲委托机制,BootstrapClassLoader、ExtClassLoader、AppClassLoader 如果你不清楚,这些都是什么...
  • kafka副本机制中的几个概念3. 副本协同机制4. 副本同步队列(ISR)5. 水位值 (HW) 和 日志末端位移 (LED)6. 数据的同步过程 我们已经知道 Kafka 的每个 topic 都可以分为多个 Partition,并且多个 Partition 会均匀...
  • JVM内存由几个部分组成:堆、方法区、栈、程序计数器、本地方法栈 JVM垃圾回收仅针对公共内存区域,即:堆和方法区进行,因为只有这两个区域在运行时才能知道需要创建些对象,其内存分配和回收都是动态的。 一、垃圾...
  • 安卓内存回收机制

    2015-03-07 00:04:06
    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统。...主要包含下面几个层次: Application Framework Application Framework 将整个操作系统分隔成两个部分。对应用开发者而
  • HDFS元数据管理机制

    千次阅读 2018-02-20 23:43:09
    HDFS 元数据,按类型,主要包括以下几个部分: 1、文件、目录自身的属性信息,例如文件名,目录名,修改信息等。 2、文件记录的信息的存储相关的信息,例如存储块信息,分块情况,副本个数等。 3、记录 HDFS 的 ...
  • .Net中的安全机制

    2008-10-15 17:12:00
    Windows中的安全机制主要是这几个概念:我是谁(身份)、我要做什么(操作)、我要访问什么(资源)。而.Net中将其修改为我从哪里来(位置)、我要做什么(操作)、我要访问什么(资源)。由此.Net的安全机制中有这...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 654
精华内容 261
关键字:

机制分几个部分