Kubernetes学习资源
k8s guide
准备
对于一个新手来说,第一步是必须了解什么是 kubernetees、 设计架构 和相关 概念。只有在了解了这些的情况下,才能更好的知道k8s中每个组件的作用以及它解决的问题。
安装工具
- minikube 参考 https://minikube.sigs.k8s.io/docs/start/
- kind 参考 https://kind.sigs.k8s.io/docs/user/quick-start/
以上是安装k8s环境的两种推荐方法,这里更推荐使用kind。主要原因是 minikube
只支持单个节点,而 kind
可以支持多个节点,这样就可以实现在一台电脑上部署的环境与生产环境一样,方便大家学习。
要实现管理控制 Kubernetes 集群资源如pod、node、service等的管理,还必须安装一个命令工具 kubectl ,请参考: https://kubernetes.io/zh/docs/tasks/tools/
学习文档
- Kubernetes 文档 https://kubernetes.io/zh/docs/home/
- Play with Kubernetes https://labs.play-with-k8s.com/
- Kubernetes中文指南/云原生应用架构实践手册 – https://jimmysong.io/kubernetes-handbook
By admin
read moreGolang中的调度器
golang实现的协程调度器,其实就是在维护一个G、P、M三者间关系的队列。
介绍(Introduction) ——————— Go 1.1最大的特色之一就是这个新的调度器,由Dmitry Vyukov贡献。新调度器让并行的Go程序获得了一个动态的性能增长,针对它我不能再做点更好的工作了,我觉得我还是为它写点什么吧。
这篇博客里面大多数东西都已经被包含在了[原始设计文档]( https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw)中了,这个文档的内容相当广泛,但是过于技术化了。
关于新调度器,你所需要知道的都在那个设计文档中,但是我这篇博客有图片,所以更加清晰易懂。
带调度器的Go runtime需要什么?(What does the Go runtime need with a scheduler?) ——————————————————————————- 但是在我们开始看新调度器之前,我们需要理解为什么需要调度器。为什么既然操作系统能为我们调度线程了,我们又创造了一个用户空间调度器?
POSIX线程API是对现有Unix进程模型的一个非常大的逻辑扩展,而且线程获得了非常多的跟进程相同的控制。比如,线程有它自己的信号掩码,线程能够被赋予CPU affinity功能(就是指定线程只能在某个CPU上运行),线程能被添加到cgroups中,线程所用到的资源也可以被查询到。所有的这些控制增大了Go程序使用goroutine时根本不需要的特性(features)的开销,当你的程序有100,000个线程的时候,这些开销会急剧增长。
另外一个问题是,基于Go模型,操作系统不能给出特别好的决策。比如,当运行一次垃圾收集的时候,Go的垃圾收集器要求所有线程都被停止而且要求内存要处于一致状态(consistent state)。这个涉及到要等待全部运行时线程(running threads)到达一个点(point),我们事先知道在这个地方内存是一致的。
当很多被调度的线程分散在随机的点(random point)上的时候,结果就是你不得不等待他们中的大多数到达一致状态。Go调度器能够作出这样的决策,就是只在内存保持一致的点上进行调度。这就意味着,当我们为垃圾收集而停止的时候,我们只须等待在一个CPU核(CPU core)上处于活跃运行状态的线程即可。
来看看里面的各个角色(Our Cast of Characters) —————————————– 目前有三个常见的线程模型。一个是N:1的,即多个用户空间线程运行在一个OS线程M上。这个模型可以很快的进行上下文切换,但是不能利用多核系统(multi-core systems)的优势。另一个模型是1:1的,即可执行程序的一个线程匹配一个OS线程M。这个模型能够利用机器上的所有核心的优势,但是上下文切换非常慢,因为它不得不陷入OS(trap through the OS)。
Go试图通过M:N的调度器去获取这两个世界的全部优势。它在任意数目的OS线程M上调用任意数目的goroutines G。你可以快速进行上下文切换(P角色),并且还能利用你系统上所有的核心的优势。这个模型主要的缺点是它增加了调度器的复杂性。
为了完成调度任务,Go调度器使用了三个实体:
[
三角形M 表示OS线程,`它是由OS管理的可执行程序的一个线程`,就是平常提到的操作系统中的线程,而且工作起来特别像你的标准POSIX线程。在运行时代码里,它被成为M,即机器(machine)。
圆形G 表示一个goroutine。它包括栈、指令指针以及对于调用goroutines很重要的其它信息,比如阻塞它的任何channel。在可执行代码里,它被称为G。
矩形P 表示用于调用的上下文。你可以把它看作在一个单线程上运行代码的调度器的一个本地化版本。它是让我们从N:1调度器转到M:N调度器的重要部分。在运行时代码里,它被叫做P,即处理器(processor)。这部分后面会多说点。
[
我们可以从上面的图里看到两个系统线程(M),每个线程都拥有一个上下文(P),每个线程都正在运行一个goroutine(G)。为了运行goroutines,一个线程必须拥有一个上下文。
上下文(P)的数目在启动时被设置为环境变量GOMAXPROCS的值或者通过运行时函数GOMAXPROCS()来设置。通常,在你的程序执行时它不会发生变化。上下文的数目被固定的意思是,只有GOMAXPROCS个上下文正在任意点上运行Go代码。我们可以使用GOMAXPROCS调整Go进程的调用使其适合于一个单独的计算机,比如一个4核的PC中可以在4个线程上运行Go代码。
外部的灰色goroutines没在运行,但是已经准备好被调度了。它们被安排成一个叫做runqueue的列表。当go创建一个goroutine的时候,goroutine就被添加到runqueue的末端。一旦一个上下文已经运行一个goroutine到了一个点上,它就会把一个goroutine从它的runqueue给pop出来,设置栈和指令指针并且开始运行这个goroutine。
为了降低mutex竞争,每一个上下文都有它自己的runqueue。Go调度器曾经的一个版本只有一个通过mutex来保护的全局runqueue,线程们经常被阻塞来等待mutex被解除阻塞。当你有许多32核的机器而且想尽可能地压榨它们的性能时,情况就会变得相当坏。
只要所有的上下文都有goroutines要运行,调度器就能在一个稳定的状态下保持调度。但是有几个你能改变的场景。
通俗地讲,G要运行,需要绑定一个P(放在P的本地队列里),然后由与P绑定的操作系统线程M真正执行。 G切换时,只是M从G1切到G2而已,都是在用户态进行着,非常轻量,不像操作系统切换M时比较重。
你打算(系统)调用谁?(Who you gonna (sys)call?)
——————————————————
你现在可能想知道,为什么一定要有上下文?我们能不能丢掉上下文而仅仅把runqueue放到线程上?不尽然。我们用上下文的原因是如果正在运行的线程因为某种原因需要阻塞的时候,我们可以把这些上下文移交给其它线程
。
我们需要阻塞的一个例子是,当我们需要调用一个系统调用的时候。因为一个线程不能既执行代码同时又阻塞到一个系统调用上,我们需要移交给对应于这个线程的上下文,以让这个上下文进行调度。
[
从上图我们能够看出,一个线程放弃了它的上下文以让另外的线程可以运行它。调度器确保有足够的线程来运行所有的上下文。上图中的M1 可能仅仅为了让它处理图中的系统调用而被创建出来,或者它可能来自一个线程池(thread cache)。这个处于系统调用中的线程将会保持在这个导致系统调用的goroutine上,因为从技术上来说,它仍然在执行,虽然阻塞在OS里了。
当这个系统调用返回的时候,这个线程必须尝试获取一个上下文来运行这个返回的goroutine,操作的正常模式是从其它所有线程中的其中一个线程中“偷”一个上下文。如果“偷盗”不成功,它就会把它的goroutine放到一个全局runqueue中,然后把自己放到线程池中或者转入睡眠状态。
这个全局runqueue是各个上下文在运行完自己的本地runqueue后用来获取新goroutine的地方。上下文也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。
By admin
read moreMySQL中的半同步复制
MySQL当前存在的三种复制模式有:异步模式、半同步模式和组复制模式。注意:MySQL复制模式没有“同步复制”这一项的,文章中只是为了读者方便理解半同步复制的概念才介绍了同步复制概念 https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html
从MySQL5.5开始,MySQL以插件的形式支持半同步复制。
1. 异步复制(Asynchronous replication)
MySQL默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。
异步复制是MySQL最早的也是当前使用最多的复制模式,异步复制提供了一种简单的主-从复制方法,包含一个主库(master)和备库(一个,或者多个) 之间,主库执行并提交了事务,在这之后(因此才称之为异步),这些事务才在从库上重新执行一遍(基于statement)或者变更数据内容(基于 row),主库不检测其从库上的同步情况。在服务器负载高、服务压力大的情况下主从产生延迟一直是其诟病。工作流程简图如下:
[][1]
**而同步复制(Fully synchronous replication,MySQL中没有此复制概念)**指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。
2. 半同步复制(Semisynchronous replication)
介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
MySQL5.5 的版本在异步同步的基础之上,以插件的形式实现了一个变种的同步方案,称之为半同步复制(semi-sync replication)。这个插件在原生的异步复制上,添加了一个同步的过程:当从库接收到了主库的变更(即事务)时,会通知主库。主库上的操作有两种:接收到这个通知以后才去commit事务;接受到之后释放session。这两种方式是由主库上的具体配置决定的。当主库收不到从库的变更通知超时时,由半同步复制自动切换到异步同步,这样就极大了保证了数据的一致性(至少一个从库),但是在性能上有所下降,特别是在网络不稳定的情况下,半同步和同步之间来回切换,对正常的业务是有影响的。其工作流程简图如下:
[][2]
3、Group Replication(组复制)
不论是异步复制还是半同步复制,都是一个主下面一个从或是多个从的模式,在高并发下高负载下,都存在延迟情况,此时如果主节点出现异常,那么就会出现数据不 一致的情况,数据可能会丢,在金融级数据库中是不能容忍的。在这种情况下,急需出现一种模式来解决这些问题。在MySQL5.7.17的版本中,带着这些期待,新的复制模式“组复制“由此产生并GA了。
组复制的工作流程图如下:[][3]
组复制的工作原理
MySQL组复制是一个MySQL插件,它建立在现有的MySQL复制基础结构上,利用了二进制日志,基于行的日志记录和全局事务标识符等功能。它集成了当前的MySQL框架,如性能模式、插件和服务基础设施等。
组复制(Group Replication)基于分布式一致性算法(Paxos协议的变体)实现,一个组允许部分节点挂掉,只要保证绝大多数节点仍然存活并且之间的通讯是没有问题的,那么这个组对外仍然能够提供服务,它是一种被使用在容错系统中的技术。Group Replication(复制组)是由能够相互通信的多个服务器(节点)组成的。在通信层,Group replication实现了一系列的机制:比如原子消息(atomic message delivery)和全序化消息(totalordering of messages)。这些原子化、抽象化的机制,为实现更先进的数据库复制方案提供了强有力的支持。
MySQL Group Replication正是基于这些技术和概念,实现了一种多主全更新的复制协议。简而言之,一个Group Replication就是一组节点,每个节点都可以独立执行事务,而读写事务则会在于group内的其他节点进行协调之后再commit。因此,当一个事务准备提交时,会自动在group内进行原子性的广播,告知其他节点变更了什么内容/执行了什么事务。这种原子广播的方式,使得这个事务在每一个节点上都保持着同样顺序。这意味着每一个节点都以同样的顺序,接收到了同样的事务日志,所以每一个节点以同样的顺序重演了这些事务日志,最终整个group保持了完全一致的状态。然而,不同的节点上执行的事务之间有可能存在资源争用。这种现象容易出现在两个不同的并发事务上。假设在不同的节点上有两个并发事务,更新了同一行数据,那么就会发生资源争用。面对这种情况,Group Replication判定先提交的事务为有效事务,会在整个group里面重放,后提交的事务会直接中断,或者回滚,最后丢弃掉。因此,这也是一个无共享的复制方案,每一个节点都保存了完整的数据副本。
从其工作的原理可以看出,Group Replication基于Paxos协议的一致性算法校验事务执行是否有冲突,然后顺序执行事务,达到最终的数据一致性,也就意味着部分节点可以存在延迟。可以设置多主同时写入和单主写入,通过设置 group_replication_single_primary_mode 来进行控制是多主还是单主,官方推荐单主写入,允许延迟,但延迟过大,则会触发限流规则(可配置的),整个集群会变的很慢,性能大打折扣。
更多组复制: http://www.sohu.com/a/124913450_354963 半同步复制的潜在问题
客户端事务在存储引擎层提交后,在得到从库确认的过程中,主库宕机了,此时,可能的情况有两种:
1. 事务还没发送到从库上
此时,客户端会收到事务提交失败的信息,客户端会重新提交该事务到新的主上,当宕机的主库重新启动后,以从库的身份重新加入到该主从结构中,会发现,该事务在从库中被提交了两次,一次是之前作为主的时候,一次是被新主同步过来的。
2. 事务已经发送到从库上
此时,从库已经收到并应用了该事务,但是客户端仍然会收到事务提交失败的信息,重新提交该事务到新的主上。
无数据丢失的半同步复制
针对上述潜在问题,MySQL 5.7引入了一种新的半同步方案:Loss-Less半同步复制。
针对上面这个图,“Waiting Slave dump”被调整到“Storage Commit”之前。
当然,之前的半同步方案同样支持,MySQL 5.7.2引入了一个新的参数进行控制-rpl_semi_sync_master_wait_point
rpl_semi_sync_master_wait_point有两种取值
AFTER_SYNC
这个即新的半同步方案,Waiting Slave dump在Storage Commit之前。
By admin
read morekubernetes中apiserver的证书
在kubernetes中,与api server 通讯时一般都需要使用https证书,这些证书文件存在放 /etc/kubernetes/pki 目录中(ubuntu)。主要有以下几种
/etc/kubernetes/pki/ca.{crt,key}
如果你已有现成的证书也可以直接将证书复制到这个目录里即可。这时kubeadm就会跳过证书生成这个步骤。
证书生成后,kubeadm 接下来会为其它组件生成访问api server 所需要的配置文件,这些文件路径为: /etc/kubernetes/xxx.conf:
ls /etc/kubernetes/
admin.conf controller-manager.conf kubelet.conf scheduler.conf
这里可以看到这四个配置文件,分别 为不同的组件之间提供配置。
这些配置文件中存储的是Master节点的ip地址、端口号、证书目录等信息。这样对应的客户端(scheduler,kubelet, controller-manager等)就可以直接加载并读取相应的配置文件来与kube-apiserver 建立安全连接,实现通讯。
附kubernetes架构图
By admin
read moreGit中的git reset的三种参数的区别
我们平时在使用git的时候,经常会遇到需要撤销上次操作的需求,这时候需要用到git reset的这个命令,他的使用就是 “git-reset – Reset current HEAD to the specified state”, 注意这里主要操作的就是这个 HEAD。
为了方便我们先了解一下 Git 的工作流程
相信大家对这个图已经很熟悉了,其中index也叫stage暂存区或者暂存索引区。git reset 共有三个互斥参数分别为”–soft”、”–mixed(默认参数)” 和 “–hard”,每种参数表示一种恢复模式,下面我们将分别看一下这git reset 三个参数的用法区别。
前提条件 我们仓库中的Git 提交顺序为 “A(a.txt) -> B(b.txt) -> C(c.txt)“,当前分支为master。 当前 HEAD 指向C,即 a47072e9f97eac4ac02c0abac82b26a9719663fc (HEAD -> master),我们以恢复到B(aad0c91e7b1d3577)点为准。
test1 git:(master) git log
commit a47072e9f97eac4ac02c0abac82b26a9719663fc (HEAD -> master)
Author: 孙兴房 <[email protected]>
Date: Mon Sep 24 10:37:14 2018 +0800
add c.txt
commit aad0c91e7b1d357729f65dc0bbeb6c9c9dd53844
Author: 孙兴房 <[email protected]>
Date: Mon Sep 24 10:22:12 2018 +0800
add b.txt
commit 68315608ef8d0cff5d229c2ee5010e59a1475cfe
Author: 孙兴房 <[email protected]>
Date: Mon Sep 24 10:21:59 2018 +0800
add a.txt
**–soft 模式 ** 执行 git reset –soft aad0c9,恢复到B。然后再执行 git status 看下
By admin
read moreMySQL中对MVCC的理解总结
一、MVCC简介
MVCC (Multiversion Concurrency Control),即多版本并发控制技术。InnoDB数据库的事务隔离级别就是通过UNDO和MVCC来实现的(ACID特性),旧数据存储在UNDO中,再通过DB_ROLL_PTR 回溯查找历史版本。
二、MVCC原理
1、通过DB_ROLL_PT 回溯查找数据历史版本2、通过read view判断行记录是否可见
理解这一块之前,我们必须先了解一下row的内部存储格式
字段说明:
- DB_ROW_ID:长度6个字节。此值由InnoDB自动生成,聚集索引时使用。如果用户未显式指定表主键时,表优先使用第一个非null的唯一索引作为主键.否则使用DB_ROW_ID的值作为主键ID,聚集索引会使用此值。如果指定了表主键的话,则聚集索引使用指定的值。
- DB_TRX_ID:6个字节的事务ID。标记了最后更新此记录的事务ID,每开起一个新事务,其值自动+1
- DB_ROLL_PTR:7字节的回滚指针。指向当前记录项的undo log记录,找之前版本的数据需通过此指针。
MySQL中的MVCC原理
首次 insert
记录的DB_ROLL_PTR
指针为NULL。修改新值后,记录的 DB_ROLL_PTR
回滚指针指向原始值在Undo Log
日志的位置,也就是说将原值在Unde Log
的物理位置存储到原记录的 DB_POLL_PTR
字段。如果事务回滚的话,则从Undo Log
中把原始值读取出来再放到记录中去。如果直接commit的话,则直接保存即可。记录格式参考:
InnoDB Undo Log的日志类型 MySQL数据库InnoDB存储引擎的undo log采用了逻辑的日志。 InnoDB undo log的格式可以概括为:<操作类型>++<数据>. A. 从表中删除一行记录 TRX_UNDO_DEL_MARK_REC (将主键记入日志) 在删除一条记录时,并不是真正的将数据从数据库中删除,只是标记为已删除.这样做的好处是Undo Log中不用记录整行的信息.在undo时操作也变得很简单. B. 向表中插入一行记录 TRX_UNDO_INSERT_REC (仅将主键记入日志) TRX_UNDO_UPD_DEL_REC (将主键记入日志) 当表中有一条被标记为删除的记录和要插入的数据主键相同时, 实际的操作是更新这个被标记为删除的记录。 C. 更新表中的一条记录 TRX_UNDO_UPD_EXIST_REC (将主键和被更新了的字段内容记入日志) TRX_UNDO_DEL_MARK_REC 和 **TRX_UNDO_INSERT_REC ** 当更新主键字段时,实际执行的过程是删除旧的记录然后,再插入一条新的记录。
事务隔离级别的区别:
RR隔离级别下,在每个事务开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)。
RC隔离级别下,在事务中的 每个语句开始(select) 时,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)
By admin
read moreMySQL之ICP、MRR、BKA、BNL
Index Condition Pushdown(ICP)
Index Condition Pushdown (ICP)是mysql使用索引从表中检索行数据的一种优化方式。
ICP原理
禁用ICP,存储引擎会通过遍历索引定位基表中的行,然后返回给MySQL Server层,再去为这些数据行进行WHERE后的条件的过滤。
开启ICP,如果部分WHERE条件能使用索引中的字段,MySQL Server 会把这部分下推到存储引擎层,存储引擎通过索引过滤,把满足的行从表中读取出。ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。
ICP的目标是减少从基表中全纪录读取操作的数量,从而降低IO操作
对于InnoDB表,ICP只适用于辅助索引。
ICP标识
当使用ICP优化时,执行计划的Extra列显示 Using index condition提示
相关参数
optimizer_switch="index_condition_pushdown=on”;
可以通过 SET optimizer_switch = ‘index_condition_pushdown=off/on’; 来关闭或开启ICP
适用场景
#辅助索引INDEX (zipcode, lastname, firstname).
SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%'AND address LIKE '%Main Street%';
People表有个二级索引 INDEX (zipcode, lastname, firstname), 用户只知道某用户的 zipcode 和大概的 lastname、address, 此时想查询相关信息。
若不使用ICP:则是通过二级索引中zipcode的值去基表取出所有 zipcode=’95054′ 的数据,然后server层再对 lastname LIKE ‘%etrunia%’AND address LIKE ‘%Main Street%’;进行过滤
若使用ICP:则 lastname LIKE ‘%etrunia%’AND address LIKE ‘%Main Street%’ 的过滤操作在 二级索引 中完成,然后再去基表取相关数据
By admin
read moreIO多路复用机制详解(转)
服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:
(1)同步阻塞IO(Blocking IO):即传统的IO模型。
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
(3)IO多路复用(IO Multiplexing):即经典的 反应器Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构。
(4)异步IO(Asynchronous IO):即经典的 Proactor设计模式,也称为异步非阻塞IO。不经常用。
高性能I/O设计模式Reactor和Proactor: https://blog.csdn.net/xiongping_/article/details/45152333
转自: https://blog.csdn.net/baixiaoshi/article/details/48708347
By admin
read more[译]Go里面的unsafe包详解
unsafe包位置: src/unsafe/unsafe.go
指针类型: ***类型:**普通指针,用于传递对象地址,不能进行指针运算。 **unsafe.Pointer:**通用指针,用于转换不同类型的指针,不能进行指针运算。 **uintptr:**用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被 GC 回收。
unsafe.Pointer 可以和 普通指针 进行相互转换。
unsafe.Pointer 可以和 uintptr 进行相互转换。
也就是说 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。
一般使用流程: 第一步:将结构体 -> 通用指针unsafe.Pointer(struct) -> uintptr(通用指针)获取内存段的起始位置start_pos,并记录下来,第二步使用。 第二步:使用start_pos + unsafe.Offsetof(s.b) -> 将地址转为能用指针unsafe.Pointer(new_pos)->转为普通指针np = (*byte)(p)->赋值 *np = 20 第三步:打印结构体,发现内容发生了更改。
推荐:unsafe.Sizeof() 针对不同数据类型的情况
By admin
read moregolang中slice切片理解总结
首先我们对切片有一个大概的理解,先看一下slice的内部结构,共分三部分,一个是指向底层数组的时候,一个是长度len,另一个就是slice的容量cap了。如cap不足以放在新值的时候,会产生新的内存地址申请。
先看代码
package main
import "fmt"
func main() {
// 创建一个切片,长度为9,容量为10
fmt.Println("----- 1.测试切片变量append的影响(未申请新的内存空间)-----")
a := make([]int, 9,10)
fmt.Printf( "%p len=%d cap=%d %vn" , a, len(a), cap(a), a)
// 切片进行append操作,由于原来len(a)长度为9,而cap(a)容量为10,未达到扩展内存的要求,此时新创建的切片变量还指向原来的底层数组,只是数组的后面添加一个新值
// 此时一共两个切片变量,一个是a,另一个是s4。但共指向的一个内存地址
s4 := append(a,4)
fmt.Printf("%p len=%d cap=%d %vnn" , s4, len(s4), cap(s4), s4)
// 测试上面提到的切片变量a和s4共指向同一个内存地址, 发现切片数组的第一个值都为7,而唯一不同的是len的长度,而cap仍为10
fmt.Println("----- 2.测试切片变量共用一个底层数组(内存地址一样)-----")
a[0] = 7
fmt.Printf("%p len=%d cap=%d %vn" , a, len(a), cap(a), a)
fmt.Printf("%p len=%d cap=%d %vnn" , s4, len(s4), cap(s4), s4)
// 切片进行append操作后,发现原来的cap(a)的长度已用完了(因为a和s4共用一个底层数组,你也可以理解为cap(s4)),此时系统需要重新申请原cap*2大小的内存空间,所以cap值为10*2=20,将把原来底层数组的值复制到新的内存地址
// 此时有两个底层数组,一个是切片变量a和s4指向的数组,另一个就是新的切片变量s4
fmt.Println("----- 3.测试切片变量append的影响(申请了新的内存空间,内存地址不一样了)-----" )
s4 = append(s4, 5 )
fmt.Printf("%p len=%d cap=%d %vnn" , s4, len(s4), cap(s4), s4)
// 注意:原切片未发生任何变化,(打印a[0]=7是因为上面第3段落代码已把默认的0值改为了7)
fmt.Println("----- 4.测试原切片变量a未发生变化-----" )
fmt.Printf("%p len=%d cap=%d %vnn" , a, len(a), cap(a), a)
}
运行结果:
By admin
read more