-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 209 KB
/
index.json
File metadata and controls
1 lines (1 loc) · 209 KB
1
[{"categories":null,"contents":" 搭建自己的VPN服务器实现科学上网 最近不知道怎么了,自己原先够买的VPN服务莫名其妙的无法使用了,自己部署在github上的网站也无\n 法访问了,这日子真的过不下去了,VPN不断地被墙也不是一天两天的事了。因此自己便一直想着能否搭建\n自己的VPN服务,只供自己使用被墙的风险不就是大大降低了嘛!\n 在写这篇博客之前,自己也想到一个笑话,在Git问世之前,Linux社区的开发人员由于私自破解由\n BitMover开发的版本控制软件,致使BitMover公司收回了Linux社区对版本控制软件的使用权。本来觉\n得由Linux之父Linus之父向BitMover公司道个谦,这事就过去了,可实际上是不可能的。大神终究是大神\n,Linus自己花了两周的时间就用C写了一个分布式的版本控制系统,就是Git,之后Linux的内核代码已经\n开始由Git进行管理了。所以对于技术领域而言,哪里有压迫,哪里就有反抗\n 好了,废话少说,进入正题\n 一、前期准备 要想搭建一个专属VPN服务来实现翻墙,首先必须要有一台海外的服务器(香港的也可以),否则你服\n 务的流量也出不去啊!那么问题来了,国内的云服务商对于海外的服务器卖的还都是挺贵的,恐怕经济上\n难以承受。那有没有白嫖的海外服务器呢,有!请看下图\n 百度的广告有时还是挺好的,让我发现了AWS还提供免费的云主机服务,但是哪有天上掉馅饼的,提供免费\n 服务的前提是你要先注册吧,注册的时候竟然需要VISA或者Master的卡号(就是那种能付美元的银行卡了),这\n种卡我还真的没有。然后发现某宝上有卖卡号的,自己就去买了一个(大概30元,相比于VPN的费用算是便宜了)\n。注册使用银行卡号不是需要扣费,而是需要检测银行卡中是否有剩余的1$,从而验证卡号的可用性\n 在这里简要说明一下,AWS为每个用户提供了每月750小时的运行实例时间,持续12个月。也就是说我们\n 在一个账户上可以运行多个虚拟机的实例,总共时间不能超过750个小时,这足够我们持续使用一年的了\n二、创建虚拟主机 1.切换地理区域 在具体创建实例之前,需要将区域设置为东京(推荐,网络延迟最小),当然也可以设置为其他的地区\n 2.创建实例 选择EC2服务后就可以开始创建实例了,可以看到免费套餐中可选的系统类型是比较多的,Linux的各种\n 版本、Windows Server等,这里我们就选用推荐的第一个吧(放在第一个肯定有它的理由)。选择第一个进\n入即可\n 然后是选择一个实例类型,审核并启动即可,其他的先不需要配置\n 点击启动后,会有一个创建密钥对的界面,这里选择创建新的密钥对即可,再输入密钥对名称后一定注意要\n 下载保存密钥对(非常重要,不然你用客户端就登不上去了),之后选择启动实例即可;\n 启动完成后点击实例号即可进入虚拟机控制台界面,注意这时我们需要关注的虚拟机信息: 公有DNS,\n 这个就相当于这台虚拟机的公网地址,由于IP地址经常会变动,所以可以用公有DNS来指向IP地址,实现\n一个动态的绑定\n 那么这时我们可以ping通这个ip地址吗?来试一下,可以发现是ping不同的\n 具体原因就是我们还未对虚拟机的安全策略配置,它是拒绝被ping的。我们进入安全组,编辑入栈规则,\n 添加一条放通规则保存即可\n 这时我们再进行ping测试,就发现可以ping通了\n 三、连接虚拟主机 这里我们使用MobaXterm客户端连接虚拟机,其他工具像Xshell、Putty客户端均可,在新建会话中,将刚才\n 看到的公有DNS填入Remote host,用户名为默认的 ec2-user,端口即为22(SSH服务),其次在高级设置中 ,\n导入刚才下载的密钥,即可连接;连接上后就相当于一个Linux了\n 连接配置信息:\n 连接成功后的界面:\n 四、部署shadowsocks 关于shadowsocks我就不用多说了,这里需要将其部署在Linux虚拟机上,实现VPN服务。具体的部署过程\n 比较简单,安装shadowsocks需要依赖python环境,并且用pip install 安装比较方便。安装好的Linux是自带\npython2环境的,这对于安装shadowsocks支持足够了。具体的部署命令总结如下,比较简单\npip install shadowsocks #pip安装 一条龙 mkdir /etc/shadowsocks #创建配置文件目录 cd /etc/shadowsocks #进入到当前目录 vim conf.json #编辑配置文件,写入以下内容 #conf.json文件的内容 { \u0026quot;server\u0026quot;:\u0026quot;0.0.0.0\u0026quot;, #服务端地址,需要做VPN写0.0.0.0即可 \u0026quot;local_address\u0026quot;: \u0026quot;127.0.0.1\u0026quot;, #本地地址 \u0026quot;local_port\u0026quot;:1080, #本地端口 默认为1080 \u0026quot;port_password\u0026quot;:{ \u0026quot;port1\u0026quot;:\u0026quot;**********\u0026quot;, #配置多用户的端口和密码 端口不能重复 \u0026quot;port2\u0026quot;:\u0026quot;**********\u0026quot; }, \u0026quot;timeout\u0026quot;:300, \u0026quot;method\u0026quot;:\u0026quot;aes-256-cfb\u0026quot;, \u0026quot;fast_open\u0026quot;: false } #用配置文件启动服务即可 ssserver -c /etc/shadowsocks/conf.json -d start 到此为止,大功告成,你也可以使用netstat -anp来查看服务端是否已经监听了设置的端口\n 五、连接测试 下载好shadowsocks客户端软件,打开配置服务器信息,进行连接即可\n 连接成功后,打开Google进行测试,开心到飞起\n 到此VPN服务部署完成,就可以进行科学上网了,服务器很稳定, 网速也比较快 ","permalink":"https://sin-coder.github.io/post/skipwall/","tags":["计算机网络"],"title":"搭建自己的VPN服务器实现科学上网"},{"categories":null,"contents":" 分布式一致性概述 一、什么是分布式一致性 1.CAP 理论 对于分布式一致性,最直观的理解就是分布式系统中的不同节点不能产生矛盾。比较著名的理论就是\n CAP Theorem,即在一个分布式系统中,不能同时满足以下三点:一致性(Consistency)、可用性\n(Availability)、分区容错性(Partition Tolerance)\n 一致性(C):在分布式系统中的所有数据备份,同一时刻是否有同样的值\n 可用性(A):在集群中一部分节点故障后,集群整体能否响应客户端的读写请求\n 分区容忍性(P):大多数的分布式系统都分布在多个子网络,每个网络都叫做一个区,分区容错的意思即是\n 区间通信可能失败;比如一个分布式系统有5个节点,有3个在美国,有两个在中国,这就是两个区\n它们之间可能无法通信\n CAP原则的核心就是只能实现AP、CP、AC,不会存在CAP,从上图中也可以看到典型的一些数据库\n 产品也只是满足了CAP的部分特性\n2.一致性模型 (1)弱一致性(最终一致性)\n关于弱一致性,通俗的解释就是当一个节点向数据库写入数据时,其他的节点可能无法立即读到该数据,\n 但是它们最终一定会读到该数据,下面是一些典型的实例\n DNS (Domain Name System)\n Gossip(Cassandra 的通讯协议)\n (2)强一致性\n对于分布式系统的容错性最关注的问题就是数据不能存储在单个的节点上,一般的解决方案就是state\n machine replication(状态机复制共识算法),具体的实现算法有以下几种:\n 同步\n Paxos\n Raft(multi-paxos)\n ZAB(multi-paxos)\n 二、强一致性算法 1.主从同步 主从同步复制的工作过程如下,Master接受写请求、Master复制日志到slave、Master等待,直到\n 所有从库返回;但是这样存在一个问题:一个节点失败,Master阻塞,导致整个集群不可用,保证了\n一致性,但是可用性却大大降低了\n 解决上述问题的方法:多数派的算法,每次写都保证写入大于N/2个节点,每次读保证从大于N/2个\n 节点读。但是这种算法也有缺陷:在并发环境下,无法保证系统的正确性,顺序是非常重要的\n2.Paxos Paxos是一种分布式一致性算法,其发明者以希腊小岛民主投票的场景来描述该一致性算法,所以对于\n 一些概念的理解可以映射到议会中一些实际的场景\n (1)Basic Paxos\n角色的介绍:\n Client:系统的外部角色,请求的发起者;实际对应于民众\n Propser:接受Client的请求,向集群提出提议(Propose)。并在冲突发生时起到冲突调节的作用。像议员,\n 替民众提出议案\n Acceptor(Voter) : 提议投票和接受者,只有在形成法定人数(Quorum)时,提议才会最终被接受,像国会\n Learner:提议的接受者,备份,对集群的一致性没有影响\n 基本流程\n存在的问题\n比较复杂,要经过两轮RPC,效率是比较低的、而且还存在活锁的问题\n(2)Multi Paxos\nMuliti Paxos 中出现了一个新的概念,Leader,它是唯一的propser,所有的请求都必须经过此Leader,\n 这样就可以解决活锁的问题\n 基本流程\n进一步简化\n 3.Raft Paxos算法实现起来过于复杂,尽管已经做了相当的优化。而Raft算法基于Paxos设计的思想重新做了很大\n 的简化,所以得到广泛应用\n4.ZAB ZAB协议的全称是Zookeeper Atomic Broadcast,它是为Zookeeper专门设计的一种支持崩溃恢复的原子\n 广播协议,也是Zookeeper保证数据一致性的核心算法\n三、具体实现 1.Zookeeper 2.etcd 参考资料 https://mwhittaker.github.io/blog/an_illustrated_proof_of_the_cap_theorem/ --Michael Whittaker\nhttps://raft.github.io/ --Raft一致性算法官方介绍\n","permalink":"https://sin-coder.github.io/post/consistency/","tags":["分布式","分布式系统"],"title":"分布式一致性"},{"categories":null,"contents":" Docker的安装 Docker的兼容性是比较好的,在主流的操作系统Windows、Linux和Mac上都能运行,但是也有一些不尽\n 如人意的地方,比如只支持Windows 10的专业版和企业版安装,可怜我的家庭版只能默默哭泣;只能在Linux\n内核版本3.10及以上且为64位操作系统才能安装。\n 为了追求最佳的原生体验,我选择了使用Linux来安装Docker,但是Linux的发行版本众多,我便又有了\n 选择恐惧症。平时使用CentOS比较多一些,Docker目前支持CentOS7以后的版本,但是看官方推荐说对\nUbuntu的支持更好一些,我还是脱离自己的舒适区去用Ubuntu吧\n一、准备工作 二、安装过程 ","permalink":"https://sin-coder.github.io/post/%E5%9F%BA%E4%BA%8Eubuntu%E7%B3%BB%E7%BB%9F%E5%AE%89%E8%A3%85docker/","tags":["云计算","虚拟化","Docker"],"title":"基于Ubuntu系统安装Docker"},{"categories":null,"contents":" Docker 镜像详解 镜像是Docker三大核心概念中最为重要的。Docker运行容器前需要本地存在对应的镜像,如果镜像不\n 存在,Docker会从Docker Hub中下载,当然用户也可以配置自定义的镜像仓库\n关键问题 使用pull命令从Docker Hub中下载镜像到本地 查看本地已有的镜像信息、管理镜像标签 使用search命令在远端仓库进行搜索和过滤 删除镜像标签和镜像文件 创建用户定制的镜像并保存为外部文件 向Docker Hub仓库中推送自己的镜像s 一、获取镜像 docker [image] pull NAME[:TAG] #直接从Docker Hub镜像源来下载镜像 其中 [] 中的内容为可选项,NAME为镜像仓库名称(用来区分镜像),TAG为镜像的标签,一般用来\n 表示版本信息,描述一个镜像需要“名称+标签”;如果不显式地指定TAG,则默认会选择latest标签,会下\n载仓库中的最新镜像。不过,镜像的仓库名称中还应该添加仓库地址(即注册服务器)作为前缀,默认情\n况下使用官方的Docker Hub服务(前缀可以忽略的),如果从非官方的仓库下载,则必须指定完整的仓库\n地址\n 从下载镜像的过程中,可以看到镜像文件一般是由若干层组成的,Docker下载中会获取并输出镜像\n 的各层信息,每一层都有一个简写的唯一id。当不同的镜像包括相同的层时,本地仅存储了层的一份内\n容,这样可以减少存储空间\n $ docker pull ubuntu Using default tag: latest latest: Pulling from library/ubuntu 5c939e3a4d10: Pull complete c63719cdbe7a: Pull complete 19a861ea6baf: Pull complete 651c9d2d6c4f: Pull complete Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110 Status: Downloaded newer image for ubuntu:latest docker.io/library/ubuntu:latest 由于Docker Hub是位于国外的网站,在未科学上网的情况下载速度较慢,所以可以使用镜像代理服务\n 来加速Docker镜像的获取过程。目前阿里云是为个人提供免费的容器镜像服务,按照命令配置即可\n二、查看镜像信息 1.dockers image ls 使用Dokcer image ls 命令可以列出本地主机上已有镜像的基本信息\n$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 2073e0bcb60e 23 hours ago 127MB mysql latest 791b6e40940c 37 hours ago 465MB ubuntu latest ccc6e87d482b 2 weeks ago 64.2MB #常用的可选参数为 -a : 列出所有镜像文件 -q : 仅输出ID信息 --no--trunc=true|false : 对输出结果中太长的部分截断 -f : 过滤列出的镜像 在列出的信息中分别是仓库名称、镜像标签、镜像ID、创建时间和镜像大小。其中镜像的id十分重要\n ,它唯一标识了镜像,在使用镜像的过程中,一般可用该id的前若干个字符组成的可区分串来替代完整的\nID;镜像大小实际上只是逻辑体积大小,由于相同的镜像层本地只会存储一份,物理上占用的存储空间会\n小于逻辑体积之和\n2.tag更改标签 docker tag命令可以为本地镜像任意地添加新的标签,就像建立了一个快捷方式\n[root@CSUYZZ-Docker /]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mysql latest 791b6e40940c 37 hours ago 465MB tomcat latest d191f894a2c7 11 days ago 507MB [root@CSUYZZ-Docker /]# docker tag mysql:latest mysql:5.7 [root@CSUYZZ-Docker /]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mysql 5.7 791b6e40940c 37 hours ago 465MB mysql latest 791b6e40940c 37 hours ago 465MB tomcat latest d191f894a2c7 11 days ago 507MB 可以看到mysql:5.7 和 mysql:latest 的镜像ID是相同的,之后便可以使用mysql:5.7替代原来的镜像了\n 3.使用inspect命令查看详细信息 docker inspect NAME 命令可以获取镜像的详细信息,返回信息是一个JSON格式的数据,其中包括\n 制作者、适应架构等。当只需要其中某项信息时,通过 -f 来指定过滤的条件\n docker inspect -f {{\u0026quot;.Architecture\u0026quot;}} mysql:latest amd64 4.history 命令查看镜像创建信息 docker history NAME命令将列出各层的创建信息\n 三、搜索镜像 docker search [option] keyword 可以搜索Dokcer Hub官方仓库中的镜像\n示例: 搜索官方提供的带有mysql关键字的镜像,返回信息包括镜像名称、描述、收藏数等\ndocker search --filter=is-official=true mysql NAME DESCRIPTION STARS OFFICIAL mysql MySQL is a widely used, open-source relation… 9081 [OK] mariadb MariaDB is a community-developed fork of MyS… 3212 [OK] #常用可选参数 -f : 过滤输出内容 --format string : 格式化输出内容 --limit int:限制输出个数,默认为25个 四、删除和清理镜像 dokcer rmi IMAGE命令用来删除镜像,其中IMAGE可以为标签或者ID\n# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mysql 5.7 791b6e40940c 38 hours ago 465MB mysql latest 791b6e40940c 38 hours ago 465MB tomcat latest d191f894a2c7 11 days ago 507MB # docker rmi mysql:5.7 Untagged: mysql:5.7 # docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE mysql latest 791b6e40940c 38 hours ago 465MB tomcat latest d191f894a2c7 11 days ago 507MB #常用命令参数 -f : 强制删除镜像,即使有容器依赖它,不推荐使用,应该先删除依赖镜像的容器再来删除镜像 -no-prune: 不要清理未带标签的父镜像 当一个镜像拥有多个标签时,docker rmi 命令只是删除了该镜像中的标签中的指定标签而已,并不影\n 响镜像文件,当镜像只剩下一个标签时便会删除这个镜像所有的文件层\n 当使用镜像ID删除命令时,会先删除所有指向镜像的标签再删除镜像文件本身;当有该镜像创建的容\n 器存在时,镜像文件默认是无法被删除的\n 此外,在使用Docker一段时间之后,系统可能会残留有临时的镜像文件和一些没有使用过的镜像,可\n 以通过docker image prune命令来进行清理\n五、创建镜像 创建镜像的方法主要有三种,基于已有镜像的容器创建、基于本地模板导入、基于Dockerfile创建,三\n 种创建方法也对应着三种待学习的命令commit、import、build\n1.基于已有容器创建 docker [container] commit [OPTIONS] CONTAINER [REPOSITORY [:TAG]] 为基于已有容器创建对\n 象的命令格式\n [root@CSUYZZ-Docker /]# docker run -it ubuntu:18.04 /bin/bash #创建容器并启动 root@69edc769ef68:/# ls #root@后为容器的ID bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr root@69edc769ef68:/# touch test #创建一个文件并保存 root@69edc769ef68:/# ls bin dev home lib64 mnt proc run srv test usr boot etc lib media opt root sbin sys tmp var root@69edc769ef68:/# exit #退出容器 exit #提交为一个新的镜像,使用ID指定容器 [root@CSUYZZ-Docker /]# docker commit -m \u0026quot;Add a new File\u0026quot; -a \u0026quot;CSUYZZ\u0026quot; 69edc769ef68 test:0.1 sha256:acb35d7730e7f88a494c6f5c6fcb5c62992702011e8c11247305b244d45b99db #返回创建容器的ID信息 [root@CSUYZZ-Docker /]# docker image ls #重新查看镜像,该镜像已经存在 REPOSITORY TAG IMAGE ID CREATED SIZE test 0.1 acb35d7730e7 About a minute ago 64.2MB mysql latest 791b6e40940c 41 hours ago 465MB tomcat latest d191f894a2c7 11 days ago 507MB ubuntu 18.04 ccc6e87d482b 2 weeks ago 64.2MB #常用的命令参数为 -a : 作者信息 -m : 提交消息 -p : 提交时暂停容器运行 -c : 提交的时候执行Dockerfile命令,关于Dockerfile后面会有详细介绍 2.基于本地模板导入 用户也可直接从一个操作系统模板文件导入一个镜像,使用docker import命令\n 3.基于Dockerfile创建 基于Dockerfile创建是最常见的方式,它是一个文本文件,利用给定的指令描述基于某个父镜像创建\n 新镜像的过程。使用的命令为docker [image] build,关于Dockerfile后面的文章会有详细的介绍\n六、保存和加载镜像 docker save命令可以将镜像保存为 .tar格式的文件存放在本地,之后用户可以复制.tar文件并分享给他人;\ndocker load 命令可以将.tar格式的文件重新加载成原来的镜像\n#在一台上进行保存为.tar文件 [root@CSUYZZ-Docker /]# docker save -o ubuntu_18.04.tar ubuntu:18.04 [root@CSUYZZ-Docker /]# ls bin dev home lib64 mnt opt root sbin sys ubuntu_18.04.tar var boot etc lib media my proc run srv tmp usr #使用参数: -o 即-output string参数,保存镜像到指定文件中 #在另外一台主机上进行加载 root@Ubuntu:/home# ls csuyzz ubuntu_18.04.tar root@Ubuntu:/home# docker load -i ubuntu_18.04.tar #使用参数:-i 即-input string参数,从指定文件中加载镜像 43c67172d1d1: Loading layer [==================================================\u0026gt;] 65.57MB/65.57MB 21ec61b65b20: Loading layer [==================================================\u0026gt;] 991.2kB/991.2kB 1d0dfb259f6a: Loading layer [==================================================\u0026gt;] 15.87kB/15.87kB f55aa0bd26b8: Loading layer [==================================================\u0026gt;] 3.072kB/3.072kB Loaded image: ubuntu:18.04 root@Ubuntu:/home# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 18.04 ccc6e87d482b 2 weeks ago 64.2MB 七、上传镜像 docker push命令可将镜像上传到仓库,默认上传到官方仓库\n[root@CSUYZZ-Docker home]# docker login #首次上传需要输入用户名和密码进行登录 Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: csuyzz Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded [root@CSUYZZ-Docker home]# docker push csuyzz/ubuntu:18.04 The push refers to repository [docker.io/csuyzz/ubuntu] f55aa0bd26b8: Mounted from library/ubuntu 1d0dfb259f6a: Mounted from library/ubuntu 21ec61b65b20: Mounted from library/ubuntu 43c67172d1d1: Mounted from library/ubuntu 18.04: digest: sha256:bc025862c3e8ec4a8754ea4756e33da6c41cba38330d7e324abd25c8e0b93300 size: 1152 上传成功后从Docker Hub的界面即可看到我们上传的仓库\n ","permalink":"https://sin-coder.github.io/post/docker-image/","tags":["云计算","虚拟化","Docker"],"title":"Docker 镜像详解"},{"categories":null,"contents":" 容器技术的前世今生 概述 什么是容器,在Docker官方网站中,特地地对这个名词进行了定义,容器是一个标准化的软件单元。\n 进一步的解释为容器是打包代码及其所有依赖项的软件的标准单元,它将软件和其运行环境隔离开来,\n因此应用程序可以从一个计算环境快速可靠地运行到另一个计算环境。\n 可以说Docker是当今最知名的容器平台之一,它于2013年开源,但是容器化和隔离的技术却有很长\n 的历史,了解这部分的历史将有助于我们对容器技术的理解\n容器发展简史 1979年,Unix7在开发过程中引入了Chroot Jail和Chroot系统调用,它允许用户将进程及其子进程与操作 系统的其余部分隔离开来。但是这种隔离未考虑安全机制,根进程可以轻松地退出chroot\n那问题便来了,chroot jail是什么,jail为监狱的意思,似乎要把什么东西锁起来。在类UNIX的操作系统\n 上,默认的根目录均为“ / ”,而chroot的作用就是改变正在运行的进程及它的子进程的根目录。例如,将某\n个程序的根目录从原先的默认的系统根目录更改为“ /home/ ”,则这个/home目录就变成了这个程序的逻辑\n根目录,与此同时,这个被修改了根目录环境的程序就不能再进入这个逻辑根目录之外的路径了。所以这就\n相当于限制某个程序能进入的目录树,称为监狱也是情有可原了\n 2000年,FreeBSD Jail被引入到FreeBSD OS中,旨在为简单的Chroot文件隔离带来更多的安全性,此 外FreeBSD还实现了将进程及其活动隔离到文件系统的特定视图中(不懂,暂时略过)\n 2001年,Linux VServer被推出,它使用了类似chroot的机制与“安全上下文”及操作系统虚拟化来提供虚 拟化解决方案,相比于chroot进步了许多,允许在单个Linux发行版(VPS)上运行多个Linux的发行版\nVPS (Virtual Private Servers) :虚拟专用服务器\n 2004年,Oracle推出了Solaris Containers,这是一个用于X86和SPARC处理器的Linux-VServer版本 Solaris Containers是由系统资源控制和“区域”提供的边界隔离组合而成\nSPARC是一套RISC(精简指令集)架构\n 2005年,OpenVZ推出,它和Linux-VServer一样,使用操作系统级虚拟化,但是这样有一定的限制,容器 共享相同的体系结构和内核版本,当客户需要不同于主机的内核版本时就有点力不从心了;而且\nOpenVZ未将一些用于创建隔离的控制机制的补丁集成到内核中\n 2007年,Google发布了CGroups,这是一种机制,它能限制和隔离一系列进程的资源使用(如:CPU、 内存、磁盘I/O和网络等),而且被集成到了Linux内核中\n 2008年,LXC(Linux Containers)发布,该项目借鉴前人成熟的容器设计理念,并基于一系列新的 内核特性实现了更具扩展性的虚拟化容器方案,而且它被集成到了主流的Linux内核中,进而成为\nLinux系统轻量级容器技术的标准\n 2013年,Cloud Foundry创建了Warden,它是一个只包含容器部分轻量级容器;同年,Docker诞生\n 2014年,Google推出了LMCTFY(Let me contain that for you),这是谷歌容器栈的开源版本,它使用\n CGroup、命名空间和其他Linux内核功能来实现在同一内核上的隔离环境中运行应用程序的;\n同年,CoreOS推出RKT,这也是一款类似于Docker的容器引擎\n 总结 Jails、Zones(Solaris容器简称)、VPS、VM和容器都是为了实现隔离和资源控制的技术,但是每种技\n 术是通过不同的方式来实现,每种方式都有其局限性和优势。而Docker作为目前最广泛使用的容器技术,\n已成为行业的标准,但它也是基于以往的这些技术不断改进的\n参考文献 https://medium.com/faun/the-missing-introduction-to-containerization-de1fbb73efc5\nDocker技术入门与实战-杨保华 戴王剑 曹亚伦\n","permalink":"https://sin-coder.github.io/post/%E5%AE%B9%E5%99%A8%E6%8A%80%E6%9C%AF%E7%9A%84%E5%8F%91%E5%B1%95/","tags":["云计算","虚拟化"],"title":"容器技术的前世今生"},{"categories":null,"contents":" 一张图入门Docker 关于Docker我们在云计算、虚拟化基础概念和容器技术的前世今生两篇文章中已经介绍过其应用的场景\n 和优点,在本篇文章我们将学习Docker的一些基础概念和用法,话不多说,赶紧上图\nDocker三大基础概念:镜像、容器和仓库 一、镜像(Image) 镜像就类似于我们使用Virtual Box或者VMware创建虚拟机之前需要下载的系统镜像文件,比如iso文件、\n img文件之类的都称为镜像文件。我们可以将Docker镜像理解为一个面向Docker引擎的只读模板,它自身即\n包含了文件系统\n 镜像与容器的关系就类似于印钞模板与钞票之间的关系,当然我们可以通过Dockerfile来制作自己的镜像。\n 制作好的镜像可以被封装在一个盒子中(保存为tar文件),当需要在另一个环境中使用镜像时,只需将盒子\n搬过去,重新取出(Load)镜像即可。当然这些比喻并不十分严格,只是帮助理解而已。\n 通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像。用户在\n 对容器进行修改并提交(commit)后,重新运行依然可以保持这个变化\n 有关Docker镜像的操作详解,请参看Docker入门学习--镜像\n 二、容器(Container) 容器是从镜像创建的运行实例,可以将其启动(run)、开始、停止和删除,而且这些容器都是相互隔离\n 的,互相不可见的。用户可以将容器看做一个简易版的Linux系统环境(包括root用户权限、进程空间、用户\n空间和网络空间),以及运行在其中的应用程序打包而成的应用盒子\n 镜像本身是可读的,容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身并没\n 有发生改变\n 有关Docker镜像的操作详解,请参看Docker入门学习--容器\n 三、仓库(Repository) Docker仓库类似于代码的仓库,是Docker集中存放镜像文件的场所。Docker利用仓库来管理镜像,这\n 种设计理念与Git十分相似。用户可以直接从仓库中拉取(pull)镜像来供自己使用,也可以将自行制作镜像\n并上传(pull)到仓库中,等到在另外一台机器上使用该镜像时,再pull下来即可\n Docker仓库和注册服务器(Registry)不是同一个概念,注册服务器是存放仓库的地方,其上往往存\n 放着多个仓库。每个仓库集中存放某一类的镜像,这些镜像通过Tag进行区分。\n 根据所存储镜像的公开与否,Docker仓库可以分为公开仓库和私有仓库。Docker Hub是目前最大的公\n 开仓库;当然Docker也支持用户在本地的网络内创建 一个只能自己访问的仓库\n 有关Docker镜像的操作详解,请参看Docker入门学习--仓库\n当然Docker的内容远不止这些,接下来会 一 一 介绍\n ","permalink":"https://sin-coder.github.io/post/docker%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0/","tags":["云计算","虚拟化","Docker"],"title":"Learn Docker with a picture"},{"categories":null,"contents":" 云计算和虚拟化基础概念 一、虚拟化 虚拟化技术是一种资源管理优化技术,它是将计算机的各种物理资源(CPU、内存、磁盘、网卡)\n 等I/O设备予以抽象、转换,然后呈现出来一个可供分割并任意组合为一个或多个(虚拟)计算机的配置\n环境。虚拟化技术打破了计算机内部实体结构间不可切割的障碍,使得用户以比原来更好的方式来应用\n这些计算机的硬件资源。\n 虚拟化是一个广义的术语,具体可细分为以下三种:\n 平台虚拟化(Platform Virtualization):操作系统级别的虚拟化\n 资源虚拟化(Resouce Virtualization):特定系统资源的虚拟化,如CPU、内存、存储或者网络等\n 应用程序虚拟化(Applocation Virtualization) :仿真、模拟和解释技术等,如Java虚拟机\n 二、虚拟机 虚拟机是一台计算机转换为多台计算机的基于物理硬件的抽象。虚拟机管理程序允许多个虚拟机\n 在单台计算机上运行,它可以创建虚拟化硬件,其中包括虚拟磁盘、虚拟网络接口、虚拟CPU等,同\n时它还具有可以与此虚拟硬件通信的内核。每个虚拟机都包含着操作系统、应用程序,这些文件可能\n占用数十GB的存储空间。管理程序可以进行托管,这就意味着它是可以在主机操作系统上运行的软件、\n还可以运行在裸机上,即直接在机器硬件上运行,替换真实的操作系统。\n 虚拟机可以分为系统虚拟机或者是过程虚拟机,我们通常所说的是系统虚拟机,它是通过主机硬件\n 来模拟整个操作系统的;而“进程虚拟机”是用于模拟执行单个进程的编程环境的,Java虚拟机便是这样\n三、容器 1.问题背景 在过去的几年中,让运维人员最为头疼就是需要为各种迥异的开发语言安装相应的运行环境。\n 但是Docker的横空出现解决了这一问题,Docker提供了让开发工程师可以将应用和依赖封装到一\n个可移植的容器中的能力,这种集装箱式的封装方式,让运维人员和开发人员都能够以Docker所\n提供的镜像分发的标准化方式发布应用,打破了异构语言在团队中形成的壁垒\n2.容器简介 容器是包含应用程序代码、配置和依赖关系的软件包;它是通过在操作系统级别进行虚拟\n 化来使应用程序可移植,从而创建基于内核的隔离的封装系统。容器化运行的应用程序可以放在\n任何地方,消除了依赖关系\n 当然,作为独立的单元,容器能够在任何操作系统,如Linux、Mac、甚至像Windows这样\n 的非UNIX系统中运行。容器还可充当标准化的工作或者计算单元,比如每个容器运行单个Web\n服务器、数据库的分片或者单个Spark工作程序,只需要扩展容器的数量就能够便捷地扩展应用\n 每个容器都有一个固定的资源配置(CPU、内存、线程数),并且扩展应用程序只需要扩展\n 容器的数量即可。容器也是实现微服务架构的一个很好的工具,每个微服务只是一组协作容器\n3.容器和虚拟机的区别 容器和虚拟机具有相似的资源隔离和分配优势,但具体功能不同,因为容器虚拟化了操作系统,\n 而不是硬件;容器的创建和停止十分迅速,而且对自身资源的需求十分有限,远远低于虚拟机,很\n很多时候直接把容器当做应用本身也是没有任何问题的。传统意义上如果说在一台主机上运行一百\n个虚拟机那肯定是天方夜谭,可是一台主机运行上千个容器却已经成为了现实\n 二者关键性能的区别如下表格:\n 容器和虚拟机架构的区别:\n 4.Docker:典型容器的代表 容器化技术的发展有很长的历史了,Docker站在巨人的肩膀上进一步优化了容器的使用体验,\n 提供了各种容器的管理工具让用户无需再关注底层的操作,可以简单明了地管理和使用容器。这\n就使得它众多容器中脱颖而出\n 我们可以将Docker容器理解为一种沙盒(Sandbox)吗,每个容器内运行一个应用,不同的\n 之间相互隔离,容器之间也可以建立快速通信的机制。Docker的主要目标是\u0026quot;Build,Ship and\nRun Any,Anywhere\u0026quot;,即通过对应用组件的封装(Packaging)、分发(Distribution)、部署\n(Deployment)和运行(Runtime)等生命周期的管理,达到应用组件级别的“一次封装,到处运\n行”,这里的应用组件,既可以是一个Web应用,也可以是一整套数据库服务,甚至是一个操作系\n统或编译器\n四、容器编排 虚拟化技术的成熟和分布式架构的普及,使得部署、管理和运行应用的云平台越来越多地被提\n 及,容器的出现,使原有的基于虚拟机的云主机应用,彻底转变为更加灵活和轻量的容器与编排调\n度的云平台应用。\n 然而容器单元的分散又给运维人员增加了较大的负担,与此同时,Kubernetes、Mesos和Swarm等\n 为云原生应用提供了强有力的编排和调度能力,它们都是云平台上的分布式操作系统。容器编排实现了\n自动化部署应用程序的过程。像Kubernetes这样的容器管理和编排的引擎,使用户能够指导容器的部署\n并自动执行更新,运行状况监视和故障转移过程\n 基础结构的发展历程如下图所示:\n ","permalink":"https://sin-coder.github.io/post/%E4%BA%91%E8%AE%A1%E7%AE%97%E5%92%8C%E8%99%9A%E6%8B%9F%E5%8C%96%E6%8A%80%E6%9C%AF/","tags":["云计算","虚拟化"],"title":"云计算和虚拟化基础概念简介"},{"categories":null,"contents":" 消息队列简介 一、消息队列简介 1.概述 消息是指在应用间传送数据,消息可以只包含文本字符串、或者包含嵌入式对象。\n消息队列是一种应用程序对应用程序的通信方法。它是是生产者-消费者模型的一个典型的代表,一端往消息\n 队列中不断地写入消息,而另一端则可以读取队列中的消息。这样发布者和接受者都不知道对方的存在。\n 消息队列也可以简单理解为:把要传输的数据放在队列中\n 2.消息获取模式 消费者获取消息时有两种模式,点对点模式和发布订阅模式\n (1)点对点模式 点对点模型通常是一个基于拉取或者轮询的消息传递模型,这种模型从队列中请求消息,而不是将消息推送\n 到客户端。这种模式的特点是一对一,发送到队列的消息被一个且只有一个接受者接收处理,即使有多\n个消息监听者也是如此,消息被收到后即可清除\n 点对点模式的优点是队列发送数据和客户端接收数据的速度是相匹配的,缺点是客户端需要实时监控队列中\n 是否有消息存在\n(2)发布/订阅模式 发布订阅模型是一个基于推送的消息传送模型,该种模型下订阅者有临时订阅者和持久订阅者之分,临时订\n 阅者只在主动监听主题时才接收消息;而持久订阅者则监听主题的所有消息,即使当前订阅者不可用,\n处于离线状态。这种模型的特点是一对多,数据生产后,推送给所有的订阅者\n 发布/订阅模式的优点是客户端不需要实时监控队列中是否有消息存在,缺点是队列发送数据的速度无法和多\n 个客户端接收数据的速度是相匹配\n二、消息队列作用 解耦:客户端与客户端之间或者客户端和服务器 之间不需要直接连接,而是通过中间件来进行连接。而且允许你 独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束\n 冗余:消息队列可以对数据进行持久化(本地备份)直到它们已经被处理,这样就规避了数据的丢失。许多消息 队列均采用“插入-获取-删除”的范式,即在把一个消息从队列中删除之前,需要你的系统明确的指出该消息已\n经被处理完毕\n 峰值处理:可以组建集群,进而增大消息入队和处理的频率。在访问量剧增的情况下,应用仍然需要继续发挥作 用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的\n浪费。消息队列基于它的可扩展性使其本身可以顶住突发的访问压力,而不会因为突发的超负荷的请求而使\n系统完全崩溃\n 数据可恢复:当系统的一部分组件失效时,不会影响到整个的系统 。消息队列降低了进程间的耦合度,即使一个 处理消息的进程挂掉,加入队列中的消息仍可在系统恢复后被处理\n 顺序保证:消息队列本来就是排好序的,并且也能够保证数据按照特定的顺序来进行处理\n 缓冲:消息队列可以控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况\n 异步:用户可以将消息放入队列,不立即处理,仅在需要的时候再去处理,有关同步异步等问题请查看此篇博客\n 三、常用消息队列简介 常见的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ等几种\n 1.Kafka 开源消息系统,基于Scala写成\n 完全的分布式消息队列,Kafka对消息保存时根据Topic进行归类;Kafka集群由多个实例组成,\n 每个实例(Server)称为一个broker\n 集群和消费者均依赖于Zookeeper集群保存一些meta信息,来保证系统的可用性\n 系统在O(1)的系统开销下进行消息持久化\n 可以自动实现负载均衡\n 2.Pulsar 比Kafka有更大的吞吐量和更低的延迟。在地域复制和多租户方面性能更优越。\n 3.RabbitMQ 基于Erlang开发的消息队列,本身支持AMQP、XMPP、SMTP、STOMP等协议,实现了Broker架构,消息\n 在发送给和客户端时先在中心队列排队。同时比较好的支持负载均衡和数据持久化\n4.Redis 基于Key-Value对的NoSQL数据库,支持MQ功能,可视为一个轻量级的队列服务来使用\n 5.ZeroMQ ZeroMQ号称最快的消息队列系统,专门设计针对大吞吐量的场景,能够实现高级复杂的队列。它本身有一\n 个独特的非中间件模式,应用程序可以扮演一个消息服务器或者中间件的角色,但是仅提供非持久化性的队列,宕机\n时数据将会丢失。\n四、消息队列内容摘要 消息队列的种类较多,每种消息队列又有其自身的特点,我们不可能每种都很精通。因此学习的时候可以精\n 学一个框架,总结出一些共性的特点,达到触类旁通的目的,下图为消息队列共有的内容\n 此外,对于RabbitMQ、RocketMQ、Kafka、Redis和Pulsar而言,由于应用的比较广泛,因此会有专门的博\n 客做简单的介绍\n","permalink":"https://sin-coder.github.io/post/messagequ/","tags":["分布式","分布式系统","消息队列"],"title":"消息队列简介"},{"categories":null,"contents":" 一、版本控制 1.版本控制工具的功能 协同修改:多人互不影响地修改服务器端的同一个文件\n 数据备份:不仅要保存文件的当前状态,还能够保存每一个提交过的历史状态\n 版本管理:在保存每一个版本的文件信息时,能够做到不保存重复的数据,节省存储空间,提高运行效率;\n SVN采取的是增量式管理的方式,Git采取了文件系统快照的方式\n 权限控制:对团队中参与开发的人员进行权限控制,Git还可以对团队外开发者贡献的代码进行审核\n 历史记录:查看修改人、修改时间、修改内容、日志信息;将本地文件恢复至某一个历史状态\n 分支管理:允许开发团队在工作过程中多条生产线同时推进任务,提高效率\n 2.常见版本控制工具 (1)集中式版本控制工具:CVS、SVN、VSS等\n 集中式的版本控制中每个开发者是一个客户端,文件和版本信息存储在服务端,开发者们都直接与\n 服务器进行交互,集中式的版本控制具有单点故障的问题\n(2)分布式版本控制工具:Git、Mercurial、Bazaar等\n 分布式版本控制相比于集中式最大的优点就是能够避免单点故障的问题\n 二、Git 简介 1.Git的发展历史 Git是一个免费、开源的分布式版本控制工具。在2005年,由Linus基于C语言开发完成,开发的初衷是\n 管理Linux社区中提交的代码, 而这位Linus正是是开发Linux系统内核的大神,它的个人语录也是我的座右铭\n\u0026quot;Talk is cheap, Show me the Code\u0026quot;,少废话我只看代码。\n2.Git的特性简介 从Git的图标中就可以看到分支是其最引以为傲的特点,实际上Git的优点还有很多\n 大部分操作在本地完成版本控制,不需要联网\n 对数据进行完整性保证,基于Hash算法\n 尽可能添加数据而不是删除或者修改数据\n 与Linux命令全面兼容,这个当然了,都是由Linus开发的\n 3.Git 的结构 Git的本地结构图\n 4.代码托管中心 代码托管中心就是维护远程库的,在局域网环境下使用GitLab服务器,在外网环境下使用Github和码云\n 码云是位于国内的网站,访问速度相对较快些,而Github位于国外,访问的相对较慢\n5.本地库和远程库 (1) 团队内部协作\n(2)跨团队进行协作\n 三、Git 基本操作 1.本地库常规操作 (1)本地库初始化\n Administrator@CSUYZZ MINGW64 /e/Workspace/GitTest #在本地库进行初始化,在当前目录下生成.git 文件 $ git init Initialized empty Git repository in E:/Workspace/GitTest/.git/ $ ls -A #注意Linux默认隐藏以\u0026quot;.\u0026quot;开头为文件名的文件,使用 -a 显示 .git/ $ cd .git/ #进入.git目录中 $ ll #列出该目录中的文件和文件夹 total 7 -rw-r--r-- 1 Administrator 197121 130 2月 4 15:26 config -rw-r--r-- 1 Administrator 197121 73 2月 4 15:26 description -rw-r--r-- 1 Administrator 197121 23 2月 4 15:26 HEAD drwxr-xr-x 1 Administrator 197121 0 2月 4 15:26 hooks/ drwxr-xr-x 1 Administrator 197121 0 2月 4 15:26 info/ drwxr-xr-x 1 Administrator 197121 0 2月 4 15:26 objects/ drwxr-xr-x 1 Administrator 197121 0 2月 4 15:26 refs/ #注意 .git目录中存放的是本地库相关的子目录和文件爱你,切记不能做修改 签名的设置,Git 需要使用用户名和邮件地址来区别不同开发人员的身份,这里和登录远程库的账号\n 和密码没有关系;签名又分为仓库级别和系统用户级别的,二者至少应该设置一个,项目级别的签名优\n于系统级别的签名,一般设置系统级别的签名即可\n 仓库级别:仅在当前本地库范围内有效\n#仓库级别签名配置命令 $ git config user.name yzz $ git config user.email yzz@pro $ ls config description HEAD hooks/ info/ objects/ refs/ $ cat config #信息存放在.git/config中 [core] repositoryformatversion = 0 filemode = false bare = false logallrefupdates = true symlinks = false ignorecase = true [user] name = yzz@pro email = yzz@pro 系统用户级别:登录当前操作系统的用户范围\n#系统级别签名配置命令 $ git config --global user.name yzz $ git config --global user.email yzz@pro $ cd ~ Administrator@CSUYZZ MINGW64 ~ #信息存放在~/.gitconfig中 $ cat .gitconfig [user] name = yzz email = yzz@pro [http] proxy = http://127.0.0.1:1080 [https] proxy = http://127.0.0.1:1080 (2)基本操作\n 状态查看:git status: 查看工作去、暂存区的状态\n 添加文件:git add [filename] 将工作区中的\u0026quot;新建/修改\u0026quot; 添加到暂存区\n 提交文件:git commit -m \u0026quot;commit message\u0026quot; [filename] 将暂存区的内容提交到本地库\n$ git status On branch master #在master分支 No commits yet #在本地库中没有提交的文件 nothing to commit #暂存区中没有可提交的文件 (create/copy files and use \u0026quot;git add\u0026quot; to track) #track表示使用Git来管理文件 $ vim first.txt #创建一个新文件并进行编辑 $ git status On branch master No commits yet Untracked files: (use \u0026quot;git add \u0026lt;file\u0026gt;...\u0026quot; to include in what will be committed) first.txt #命令行下该文件显示为红色,表示该文件为使用Git进行管理 nothing added to commit but untracked files present (use \u0026quot;git add\u0026quot; to track) $ git add first.txt #添加到暂存区中 $ git status On branch master No commits yet Changes to be committed: (use \u0026quot;git rm --cached \u0026lt;file\u0026gt;...\u0026quot; to unstage) #已经添加到缓存区中,但未提交 new file: first.txt #命令行中显示绿色 $ git rm --cached first.txt #可以将缓存区中的内容进行删除 rm 'first.txt' $ git status #回到git add 之前的状态 On branch master No commits yet Untracked files: (use \u0026quot;git add \u0026lt;file\u0026gt;...\u0026quot; to include in what will be committed) first.txt nothing added to commit but untracked files present (use \u0026quot;git add\u0026quot; to track) $ git commit first.txt -m \u0026quot;CSUYZZ first commit Creat a file\u0026quot; #提交到本地库,-m 后为描述信息 [master (root-commit) 9b96e59] CSUYZZ first commit Creat a file #9b96e59可视为版本号 1 file changed, 4 insertions(+) #在文件中插入四行 create mode 100644 first.txt $ git status On branch master nothing to commit, working tree clean #修改first.txt文件后 $ git status On branch master Changes not staged for commit: #有没有进入缓存的修改 (use \u0026quot;git add \u0026lt;file\u0026gt;...\u0026quot; to update what will be committed) #update而不是include (use \u0026quot;git checkout -- \u0026lt;file\u0026gt;...\u0026quot; to discard changes in working directory) modified: first.txt no changes added to commit (use \u0026quot;git add\u0026quot; and/or \u0026quot;git commit -a\u0026quot;) #使用git add添加到缓存区即可 git log #打印出两次提交的信息 commit e3727241725d628cd2251e10171c1878416a06e6 (HEAD -\u0026gt; master) #长字符串为索引,HEAD为指针,指向当前的版本 Author: yzz \u0026lt;yzz@pro\u0026gt; Date: Tue Feb 4 17:39:49 2020 +0800 CSUYZZ second commit commit 9b96e598646f7c71d3a4a3269caebe041d9a5f8c Author: yzz \u0026lt;yzz@pro\u0026gt; Date: Tue Feb 4 17:28:05 2020 +0800 CSUYZZ first commit Creat a file # git log 常用参数 # --pretty=oneline : 一行显示所有所有的版本信息 $ git log --pretty=oneline e3727241725d628cd2251e10171c1878416a06e6 (HEAD -\u0026gt; master) CSUYZZ second commit 9b96e598646f7c71d3a4a3269caebe041d9a5f8c CSUYZZ first commit Creat a file #--oneline :一行显示,但是精简哈希值 git log --oneline e372724 (HEAD -\u0026gt; master) CSUYZZ second commit 9b96e59 CSUYZZ first commit Creat a file git reflog #多出来的HEAD@{1}表示历史版本和当前最新版本的迭代次数 e372724 (HEAD -\u0026gt; master) HEAD@{0}: commit: CSUYZZ second commit 9b96e59 HEAD@{1}: commit (initial): CSUYZZ first commit Creat a file 基于历史记录前进或者回退版本,Git在管理版本时实际上使用了HEAD指针,用户可以使用HEAD\n 指针实现版本的前进或回退。实现的方式主要有三种:\n 基于索引值操作(推荐):\ngit reset --hard 9b96e59 #回退操作命令 --hard后面是版本索引值 HEAD is now at 9b96e59 CSUYZZ first commit Creat a file 使用^符号:只能回退版本,git reset --hard HEAD^^ n个^符号表示回退n个版本\n 使用~符号:只能回退版本,git reset --hard HEAD~n 回退n个版本\n reset命令三个参数的对比\n--soft参数:仅仅在本地库移动HEAD指针\n--mixed参数:在本地库移动HEAD指针、重置暂存区\n--hard参数:在本地库移动HEAD指针、重置暂存区、重置工作区\n 删除文件的找回:删除前,文件存在的状态已提交到了本地库 操作命令 : git reset --hard [指针位置]\n当删除操作已提交到本地库时,指针位置指向历史记录即可\n当删除操作没有提交到本地库时,指针位置指向HEAD(当前版本)\n 比较文件差异: git diff [文件名] : 将工作区中的文件和暂存区进行比较\ngit diff [本地库历史版本] [文件名] :将工作区中的文件和本地库历史记录进行比较不带文件名时表示\n 比较多个文件\n2.Git分支管理 (1)分支简介\n 在版本空中过程中,使用多条线同时推进多个任务\n\n (2)分支管理优点\n 同时并行推进多个功能的开发,提高开发效率\n 分支开发失败不会对其他分支造成任何应影响\n (3)分支操作\n 创建分支:git branch [分支名]\n 查看分支:git branch -v\n 切换分支:git checkout [分支名]\n 合并分支:git checkout [分支名] (切换分支)、合并分支(git merge [有新内容分支名])\n $ git branch hot_fix #创建分支 $ git branch -v #查看分支 hot_fix cb16613 Creat three file * master cb16613 Creat three file # *表示当前的主分支 $ git checkout hot_fix #切换分支 Switched to branch 'hot_fix' $ git branch -v #完成切换 * hot_fix cb16613 Creat three file master cb16613 Creat three file $ vim first.txt #在hot_fix分支修改first.txt文件添加新内容 $ git add first.txt $ git commit -m \u0026quot;modify a file\u0026quot; first.txt [hot_fix 128cb47] modify yy a file 1 file changed, 2 insertions(+) $ git checkout master #切换到主分支 Switched to branch 'master' $ git branch -v hot_fix 128cb47 modify yy a file * master cb16613 Creat three file $ git merge hot_fix #在主分支合并 Updating cb16613..128cb47 Fast-forward first.txt | 2 ++ 1 file changed, 2 insertions(+) $ cat first.txt #查看文件发现文件内容已被修改 csuyzzYang 解决冲突:两个分支对同一个文件的某个地方进行修改后再合并时,会产生冲突 #分别在master分支和hot_ix分支中对yzz.cmd文件的第一行进行了修改 #在hot_fix分支合并master后会出现 $ git merge master Auto-merging yzz.cmd CONFLICT (content): Merge conflict in yzz.cmd Automatic merge failed; fix conflicts and then commit the result. Administrator@CSUYZZ MINGW64 /e/Workspace/GitTest (hot_fix|MERGING) #处于正在合并状态 #解决冲突步骤 1.编辑文件,将特殊符号删除 2.将文件修改至满意程度,保存退出即可 3.git add [文件名] 4.git commit -m \u0026quot;日志信息\u0026quot; 且此时commit一定不能带具体的文件名 Administrator@CSUYZZ MINGW64 /e/Workspace/GitTest (hot_fix|MERGING) $ vim yzz.cmd #编辑完文件后 $ git status On branch hot_fix You have unmerged paths. (fix conflicts and run \u0026quot;git commit\u0026quot;) (use \u0026quot;git merge --abort\u0026quot; to abort the merge) Unmerged paths: (use \u0026quot;git add \u0026lt;file\u0026gt;...\u0026quot; to mark resolution) both modified: yzz.cmd no changes added to commit (use \u0026quot;git add\u0026quot; and/or \u0026quot;git commit -a\u0026quot;) $ git add yzz.cmd $ git status On branch hot_fix All conflicts fixed but you are still merging. (use \u0026quot;git commit\u0026quot; to conclude merge) Administrator@CSUYZZ MINGW64 /e/Workspace/GitTest (hot_fix|MERGING) $ git commit -m \u0026quot;final modeify\u0026quot; #提交文件,不能带文件名否则会报错 [hot_fix c3ae1bb] final modeify @CSUYZZ MINGW64 /e/Workspace/GitTest (hot_fix) #状态由hot_fix|MERGING 恢复至hot_fix 3.远程库操作 (1)本地库推送到远程库\n$ git init #本地库初始化 Initialized empty Git repository in E:/Workspace/GitRemoteTest/.git/ $ vim yzz.txt $ git add yzz.txt $ git commit -m \u0026quot;Create a file\u0026quot; yzz.txt #提交到本地库 [master (root-commit) 8f63ae2] Create a file 1 file changed, 1 insertion(+) create mode 100644 yzz.txt #在Github创建远程库后,获取远程库的Git地址 $ git remote add origin https://github.com/sin-coder/GitDemo.git #远程库地址重命名origins $ git remote -v #查看当前远程库地址 origin https://github.com/sin-coder/GitDemo.git (fetch) origin https://github.com/sin-coder/GitDemo.git (push) $ git push origin master #将本地库master分支推送到远程库 Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Writing objects: 100% (3/3), 219 bytes | 219.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://github.com/sin-coder/GitDemo.git * [new branch] master -\u0026gt; master #从本地库的master分支推送到远程库的master分支 (2) 克隆操作\n$ git clone https://github.com/sin-coder/GitDemo.git # 命令格式:git clone [远程地址] Cloning into 'GitDemo'... remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. $ ls GitDemo/ #git clone会完整的把远程库下载到本地 $ cd GitDemo/ $ ls -a ./ ../ .git/ yzz.txt #git clone创建origin远程地址别名 $ git remote -v #git clone会初始化本地库 origin https://github.com/sin-coder/GitDemo.git (fetch) origin https://github.com/sin-coder/GitDemo.git (push) (3) 拉取操作\npull=fetch+merge\ngit fetch [远程库地址别名] [远程分支名]\ngit merge [远程库地址别名/远程分支名]\ngit pull [远程库地址别名] [远程分支名]\n$ git fetch origin master #将远程库中的内容下载到本地 remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/sin-coder/GitDemo * branch master -\u0026gt; FETCH_HEAD 8f63ae2..de2cb76 master -\u0026gt; origin/master Administrator@CSUYZZ MINGW64 /e/Workspace/GitRemoteTest (master) #此时分支为master $ cat yzz.txt #查看本地文件内容发现没有变化 I am a sin-coder man $ git checkout origin/master #切换到origin/master分支 Note: checking out 'origin/master'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b \u0026lt;new-branch-name\u0026gt; HEAD is now at de2cb76 first modeify Administrator@CSUYZZ MINGW64 /e/Workspace/GitRemoteTest ((de2cb76...)) $ cat yzz.txt #查看文件内容,这是从远程库中文件的内容 I am a sin-coder man This is new content $ git checkout master #再次切换到master分支 Previous HEAD position was de2cb76 first modeify Switched to branch 'master' $ git merge origin/master #合并操作 Updating 8f63ae2..de2cb76 Fast-forward yzz.txt | 1 + 1 file changed, 1 insertion(+) $ cat yzz.txt I am a sin-coder man This is new content (4)解决冲突\n如果不是基于Github远程库的最新版本所做的修改,则不能推送,必须先进行拉取;拉取下来后如果\n 进入冲突的状态,则按照 “分支冲突解决”的方式解决之后才能再次推送到远程库\n (5)免密登录\n配置SSH方式登录可以避免每次登录时输入用户名和密码\n#进入到当前用户目录 cd ~ $ ssh-keygen -t rsa -C csuyzz@foxmail.com #配置本地.ssh文件 Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/Administrator/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /c/Users/Administrator/.ssh/id_rsa. Your public key has been saved in /c/Users/Administrator/.ssh/id_rsa.pub. The key fingerprint is: SHA256:GgMl8aAhWmJQKAFO1QLQ7K1mEsziy+s1hZQthtqAQiQ csuyzz@foxmail.com The key's randomart image is: +---[RSA 2048]----+ |E%*.=.. | |@+++o* | |O+.*o.. | |==+ +. | |+..o .o S | |..+ . + | |.+.o . | | o. . | |.o. | +----[SHA256]-----+ Administrator@CSUYZZ MINGW64 ~ $ cd .ssh Administrator@CSUYZZ MINGW64 ~/.ssh $ ll total 5 -rw-r--r-- 1 Administrator 197121 1823 2月 5 19:13 id_rsa -rw-r--r-- 1 Administrator 197121 400 2月 5 19:13 id_rsa.pub Administrator@CSUYZZ MINGW64 ~/.ssh $ cat id_rsa.pub #查看ssh密钥 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy1FbP9Hp+CkvtzD8Q4Dpyb8KdfU/S3AcOop6jjR7hlFeCrwaxnHrDbYSqIrYZDxqmEtf/ah0Xq7ZgWBOIhWDpQL++gIebLZl8cgGeNsR75MKb5hYmXoiqJUm37pWk1stXgKAY1zINfoEiyVgYJLpQiX283WSAMQUaV0MG0Ba6wTyY1k1necIolymlpVmNO+PXx0VMLE8M/cnIlCPGNk6BrT/owD8vdiedcpgWRZWdtAf6Hl+kFalpYcBHNTmSqzqeu2hckCohNExjrC5WvBIFmTCde23VSN6fMgcIIcXFzJ4aBPQg+ajPwG2Ztb1YhIzP2UVCE5Dm1MZCGYV8bG+7 csuyzz@foxmail.com #将id_rsa.pub文件中的内容复制到远程库中:settings-\u0026gt;Deploy keys-\u0026gt;Add deploy key即可 #切换到工作目录 $ vim yzz.txt $ git add yzz.txt #修改文件内容 提交到本地仓库 $ git commit -m \u0026quot;test ssh login\u0026quot; yzz.txt [master 2e009a9] test ssh login 1 file changed, 1 insertion(+) $ git remote -v #查看远程仓库地址 origin https://github.com/sin-coder/GitDemo.git (fetch) origin https://github.com/sin-coder/GitDemo.git (push) $ git remote add origin_ssh git@github.com:sin-coder/GitDemo.git #配置远程仓库ssh地址 $ git remote -v #重新查看远程仓库地址,新增两个ssh地址 origin https://github.com/sin-coder/GitDemo.git (fetch) origin https://github.com/sin-coder/GitDemo.git (push) origin_ssh git@github.com:sin-coder/GitDemo.git (fetch) origin_ssh git@github.com:sin-coder/GitDemo.git (push) $ git push origin_ssh master #使用ssh的远程仓库地址登录 The authenticity of host 'github.com (52.74.223.119)' can't be established. RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8. Are you sure you want to continue connecting (yes/no)? y Warning: Permanently added 'github.com,52.74.223.119' (RSA) to the list of known hosts. Everything up-to-date 四、Git的工作流 Git的工作流就是在项目开发的过程中使用Git的方式,主要有集中式工作流、GitFlow和Forking\n 1.GitFlow工作流 主干分支master : 负责管理正在运行的生产环境代码,永远保持与正在运行的生产环境,完全一致\n 开发分支develop: 负责管理正在开发过程中的代码。一般情况下应该是最新的代码\n Bug修理分支 (热修复):主要负责管理生产环境下出现的紧急修复的代码。 从主干分支分出,修理\n 完毕并测试上线后,并回主干分支。并回后,视情况可以删除该分支\n 预发布分支 release : 较大的版本上线前,会从开发分支中分出准生产分支,进行最后阶段的集成测试。 该版本上线后,会合并到主干分支。生产环境运行一段阶段较稳定后 可以视情况删除。\n 功能分支 feature : 为了不影响较短周期的开发工作,一般把中长期开发模块,会从开发分支中独立出 来, 开发完成后会合并到开发分支。\n 2.Forking工作流 Forking 工作流是在 GitFlow基础上,充分利用了Git 的Fork和pull request的功能以达到代码审核的目的\n 这种方式适合管理大团队的开发者,而且能接受任何不信任贡献者的提交\n3.集中式工作流 此种方式与SVN颇为相似,集中式工作流以中央仓库为项目所有修改的单点实体,所有修改都提交\n 到Master这个分支上,与SVN的主要区别就是开发人员具有本地库,但Git的许多特性没有使用\n \n 五、Git的基本原理 1.哈希算法简介 Git使用哈希算法来保证文件传输的安全性,哈希是一系列的加密算法,各个不同的哈希算法虽\n 然加密长度不同,但是均有以下共同点:\n 使用同一个哈希算法得到的加密结果长度固定,不管输入数据的数据量有多大\n 哈希算法确定,输入数据确定,输出的数据能够保持不变\n 哈希算法确定,输入数据有变化,输出的数据一定有变化,而且通常变化很大\n 哈希算法是不可逆的\n Git底层采用的SHA-1的算法,哈希算法可以被用来验证文件,原理图如下\n\n 2.版本保存的机制 集中式版本控制的管理机制是每个版本仅保存较于之前版本修改的部分数据,每个版本的信息都是原\n 始版本和每个版本随时间的逐步累积的差异,如SVN就是增量式的文件管理系统,这样节约了存储空间\n \n而Git则把数据看作是小型文件系统的一组快照,每次提交更新的时候Git都会对当前的全部文件制作\n 一个快照并保存这个快照的索引。如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指\n向之前存储的文件,所以Git的工作方式可以称之为快照流\n 3.Git的提交对象 Git中有一个提交对象的概念,每次提交时,在当前目录下的每一个文件的部分哈希值会组成一个树对象,\n 树对象的哈希值会被包括在这个提交对象中,与此同时,每个提交对象都会包含其父对象的哈希值;这种哈\n希的依赖关系与区块链比较相似\n \n 4.Git分支的管理机制 在SVN中新建一个分支相当于将原来的版本重新复制拷贝一份,效率比较低;而Git的分支管理机制是\n 基于指针的,效率非常高。\n 在本地库初始化后会自动生成一个master分支,第一次提交(root commit)时会创建一个链表的根节\n 点,HEAD指针指向根节点(即指向master分支),当再次提交时会以根提交作为其父节点去创建新的节点,\n同时指针也指向到新的节点;当用户创建一个分支时,Git只是新建一个指针指向当前的版本,当切换分支时\n只是改变了指针的指向\n","permalink":"https://sin-coder.github.io/post/git/","tags":["分布式","分布式系统","Git"],"title":"Git\u0026Github 学习笔记"},{"categories":null,"contents":" 一、分布式系统概述 1.什么是分布式系统? 分布式系统主要由网络、分布式存储与分布式计算等部分构成的,分布式存储侧重于数据的读写存取及一致性等方面,而分布式计算则侧重于资源、任务的编排和调度\n2.分布式系统的特点 没有强制性的中心控制、次级单位具有自治的特质、次级单位之间彼此高度链接、点对点之间的影响通过网络形成了非线性的因果关系\n3.传统架构面临的难题: 系统的扩展 高并发的访问要求我们的后端系统架构弹性且可扩展\n 三维扩展:\n X轴扩展:水平复制,即在负载均衡服务器后增加多个Web服务器,\n Y轴扩展:对数据库的扩展,即进行分库分表,分库是将关系紧密的表放在一台数据库服务器上,分表是因为一张表的数据太多,需要将一张表的数据通过hash放在不同的数据库服务器上\n Z轴扩展:业务方向的扩展,才能将巨型应用分解为一组不同的服务,将应用进一步分解为微服务\n 4.CAP定理\n 在分布式系统中,系统的一致性(Consistency)、可用性(Availability)、分区容忍性(Partion tolerance)。这三者不能同时保证,由于网络通信的不确定性,分区的容忍性是必须要保证的,而且互联网应用比企业级应用更加偏向于保持可用性,通常用最终一致性代替传统事务的ACID强一致性\n\n二、分布式计算 1.概述 分布式计算核心的思路就是系统架构无单点,让整个系统可以扩展。分布式计算环境下的节点分为有状态存储节点和无状态存储节点。\n 无状态存储节点,不存储数据,请求分发可以采取很简单的随机算法或者是轮询的算法就可以了,如果需要增加机器,则只需要把对应的运算代码部署到一些机器上然后启动起来,引导流量到那些机器即可实现动态的扩展了。简单来说就是某台机器承担了某种角色后,能够快速的广播给需要这个角色提供服务的机器。\n 而针对有状态节点,扩容难度较大,因为每台Server中均有数据,所以请求分发的算法不能够随机或者轮询,一般来说常见算法就是哈希或者使用Tree来做一层映射,增加机器时需要经历一个复杂的数据迁移过程------》自动化扩容和迁移的工具\n2.数据处理的发展过程\nGFS-------------》HDFS\nBigTable--------》HBase\n MapReduce----》MapReduce\n (Hadoop技术栈)\nMapReduce(离线处理)-----》Spark(高性能批处理技术)------》Storm(流处理)----》Flink\n3.批处理(Batch Processing)与流处理(Stream Processing) 主要区别:每一条数据在到达时是被处理的(流处理),还是作为一组新数据的一部分稍后进行处理(批处理)\n批处理:在批处理中新到达的数据元素被收集到一个组中,整个组在未来的时间内进行处理。至于何时处理每个组可以选择多种方式来确定,可以基于预定的时间间隔(如每隔5分钟)、或者在某些触发的条件下(只要包含5个元素/拥有超过1MB的数据)。传统的数据仓库和Hadoop就是专注于批处理的。批处理示意图如下:\n缺点:具有延迟性、新数据的到达与该数据的处理之间的延迟将取决于直到下一批处理窗口的时间\n流处理:流处理设计的目的是为了在数据到达时对其进行响应,这就要求它们实现一个由事件驱动的体系架构,也可以说是在系统的内部工作流在接收到数据后立即连续监视新数据和调度处理。\n应用:Flink、Beam等都支持“流式处理优先,将批处理视为流式处理的特殊情况”,但是流式处理器的出现并没有让批处\n 理器变得过时。因为纯流式处理系统在批处理工作负载时其实是非常慢的。\n Apache Beam: 这样统一的API通常会根据数据是持续的(无界)、还是固定的(有界)将工作负载委托给不同的\n 运行机制\n Flink: 提供的流式API,可以处理有界或者无界的场景,同时任然提供了单独的DataSet API用于批处理\n\n三、分布式调度 1.概述\n经典资源调度器(Yarn)-----》数据调度(Data Placement)、资源任务调度(Resource Management)、计算调度(Application Manager)、本地微(自治)调度\n2.资源调度\n主要面临问题:集群内海量的硬件资源(CPU、内存、磁盘、网络、GPU、FPGA)需要快速分配给成千上万的job\n解决方案:Yarn和Kubernetes成为代表性的开源调度框架\n Yarn : 提出的双层调度框架实现了资源管理和调度的分离、满足了中小规模离线作业频繁调度的需求,但是\n 在超大规模的场景下调度的性能存在不足,集群利用率不高、多租户之间的资源公平性较差。\n Kubernetes: 面向容器场景的调度(容器只要启动一次,不需要进行频繁地调度),主要解决容器的编排、管理等问题,更适合任务长时间运行的场景,但在大数据计算高并发作业的场景,没有有效的解决方案\n 3.单机资源的管理\n 问题背景:大量的任务实例在物理机器上运行时,需要单机上的隔离保护机制,以有效保障不同任务对物理资源的需求,确保高低优先级不要相互影响,同时还需要保护物理机器,避免进入过载状态,保障整机的可用性。\n 名词: SLA(Service-Level Agreement)服务等级协议\n BORG:谷歌超大规模集群管理系统\n Heracles: google论文\n autoscaler:自动扩容机制\n\n 资源高压力下的SLA保障:Borg、Heracles、autoscaler都假设在资源冲突时,无条件向在线业务倾斜,离线业务随时可以被牺牲,但实际上离线业务也不能被随意牺牲。\n4.计算调度\nDAG: Directed Acyclic Graph(有向无环图)\n每个job抽象成一个DAG,图中的节点有前后依赖关系,DAG框架需要更好的动态性,以更灵活的适应数据和资源的变化。计算调度和Shuffle(重组)系统需要对不同规模都给出最优的调度效果和执行性能。\n5.数据调度\n问题背景:出于数据生产和容灾的需要,数据通常是放在不同地区的不同机房;但是要想做到一条SQL语句访问全球数据,对这些数据做分析、聚合等操作,必须要解决网络的问题,尤其是广域网延迟高、带宽小、价格高、稳定性差。\n数据调度架构\n在数据中心上层增加了一层调度层,用于调度数据和计算\n调度数据:数据的迁移和复制。迁移数据可以使各数据中心存储负载均衡,实现集群层面的存储计算分离,保证不会由于\n 访问远程数据造成带宽雪崩 ; 通过复制(缓存数据),避免对同一数据的频繁跨域访问,减少带宽的消耗。\n调度计算:整体业务的迁移和SQL粒度的调度,通过整体业务的迁移均衡数据中心的计算负载;通过将联系紧密的业务放在一起从而减少跨域数据依赖。但是业务整体迁移需要迁移大量的历史数据,会消耗大量的带宽,因此我们加入了SQL粒度的跨机房调度\n四、流计算 1.运行与编程模型 (1)问题背景:反压问题(backpressure) 反压问题通常产生产生于这样的场景,短时负载高峰导致系统接收数据的速率远高于它处理数据的速率;\n 通常的场景为:垃圾回收停顿可能会导致流入的数据快速堆积、遇到大促或秒杀活动导致流量陡增\n 如果不能及时得到处理,可能导致资源耗尽甚至系统崩溃\n(2)各显神通 目前主流的流处理系统Storm/JStorm/Spark Streaming/Flink都已经提供了反压机制,但具体的实现不同\n Storm: 通过监控Bolt中的接收队列负载情况,如果超过高水位值就会将反压信息写到Zookeeper, Zookeeper上的\n Watch会通知该拓扑的所有Worker都进入反压状态,最后Spout停止发送tuple。具体看JIRA STORM-886\n JStorm: 直接停止Spout的发送太过于暴力,存在大量问题。\n 举个栗子:当下游出现阻塞时,上游会停止发送,下游消除阻塞后,上游又开闸放水;过了一会儿,下游\n 又开始阻塞,上游又限流,如此反复,整个数据流会一直处在一个颠簸的状态。\n 实际上JStorm是通过逐级降速来进行反压的,效果会较Storm更为稳定,但是算法也更为复杂;此外 ,\n JStorm也没有引入Zookeeper而是通过TopologyMaster来协调拓扑进入反压状态的,这就降低了\n Zookeeper的负载\n Flink: 利用自身作为纯数据流引擎的优势来响应反压问题\n ### 流计算模型(Dataflow模型/Beam模型) 发展历史:\nLambda架构:流处理(不可靠、低延迟)+MapReduce(比较准确但高延迟的批处理框架)\nSpark 1.X的Micro-Batch模型从批处理的角度处理流数据,将不间断的流数据切分成一个个微小的批处理块\n Kappa架构:使用类似于Kafka的日志型消息存储作为中间件,从流处理的角度处理批处理\n 计算模型简介:\n Dataflow模型从流处理的角度重新审视数据处理的过程,将批处理和流处理抽象成数据集的概念,并将数据划分为无界数据集和有界数据集,流处理是批处理的超集。\n 模型的核心概念:\n 事件时间(Event Time)和处理时间(Processing Time)\n 流处理中最重要的问题就是事件发生的时间(事件时间)和处理系统观测到的时间(处理时间)存在延迟\n 窗口(Windowing):为了合理地计算无界数据集地结果,所以需要沿时间边界切分数据集(窗口)\n 触发器(Triggers):当处理过程中遇到某种特殊情况时,此时的输出结果可以是精确的,有意义的机制\n 水印(Watermarks):针对事件时间的概念,提供了一种在事件时间相对于系统时间是乱序中合理推测无界数据集里数据完整性的工具\n 累计类型(Accumulation):累计类型是处理单个窗口的输出数据是如何随着流处理的进程而发生变化的\n 问题和解决方案:\n 计算结果: 通过transformations操作\n 在事件时间的何处计算结果:窗口(Windowing)的概念\n 在处理时间中的哪个时刻触发计算结果:触发器和水印\n 如何修正结果:通过累计类型修正结果数据\n3.流计算框架 目前主流的流处理框架分别为:Storm、Trident Storm、Spark、Samza、Flink\n(1)框架选择的关注点 运行和编程模型(Runtime and Programming model),平台提供的模型应该足够处理所有可能的用户案例\n 函数式单元(Functional Primitives):平台能够提供丰富的能够在独立信息级别进行处理的函数,像map、filter、\n aggregation(跨信息处理函数)、join(跨流操作的函数)\n 状态管理(State Management):框架本身允许开发者去维护、访问和更新这些状态信息\n 消息投递的可达性保证(Message Delivery Guarantees):消息投递主要有三种方案\n a.至多一次:保证每个消息会被投递0次或1次,消息很有可能会被丢失\n b.至少一次:每个消息默认被投递多次,至少保证有一次被成功接收,信息有可能重复,但是不会丢失\n c: 恰好一次:消息对于接收者而言正好被接收一次,保证不会丢失和重复\n 错误处理(Failures Handling):系统能够从故障中顺利恢复,继续运行\n 其他(Others): 平台的生态系统、社区的完备程度、是否易于开发和运维\n (2)流处理系统构建方式 Native Streaming: 所有输入的记录或者事件都会根据它们进入的顺序一个接着一个的进行处理,示意图: Micro-Batching:大量短的Batches会从输入的记录中创建出然后经过整个系统的处理,这些Batches会根据预设好的 时间常量进行创建,通常是每隔几秒创建一批。原理图示意如下:\n()\n(3)流计算框架详解 各主流框架的特点总结如下图:\n1. Storm 1.概述\n Storm是大规模流处理中的先行者且为行业标准,它是一个典型的Native Streaming系统并且提供了大量底层的操作接口;此外,Storm使用Thrift定义拓扑,提供了大量其他编程语言接口\n 2.错误处理\n 基于逆流备份与记录确认的机制来保证消息会在某个错误之后被重新处理;\n 记录确认:一个操作器在处理完成一个记录之后向它的上游发送一个确认消息。一个拓扑的源会保存其所有创建好的记录的备份,只有在收到了包含有所有记录的确认消息,才会把这些消息安全地删除掉;当发生错误的时候,如果还没有接受到全部的确认消息。就会从拓扑的源开始重放这些记录。这样可以确保没有数据丢失,但是会导致重复的Records处理的过程,这就属于At-Least原则\n 3.状态管理\n Storm实现了At-Least原则,但是最好能实现Exactly-once原则,这只需要使用事务进行提交Records即可\n2.Trident 1.概述\n Trident是一个基于Storm构建的上层的Micro-Batching系统,提出了窗口、聚合以及状态管理等Storm不支持的功能;另外Storm实现了至多一次的投递原则,而Trident实现了恰巧一次的投递原则。Trident提供了Java、Clojure和Scala接口\n3. Spark 1.概述\n Spark提供了SparkSQL、Mlib等内建的批处理框架的库和Spark Streaming流处理框架(Micro-Batching机制)。输入的数据流会被接收者分割为Micro-Batches,然后像其他Spark任务一样处理。Spark提供了Java、Python和Scala接口\n 2.错误处理\n Spark Streaming使用Micro-Batching机制,Spark将Micro-Batches分配到多个节点运行,发生故障的Micro-Batch只需简单地重新计算即可\n 状态管理\nSpark Streaming将状态作为一个单独地Micro-Batching流进行处理,对每一个小的Micro-Batching热舞进行处理时会输入一个当前的状态和一个代表当前操作的函数,最后输出一个经过处理的Micro-Batching 和一个更好的状态\n 4. Samza 1.概述\n Samza依赖于Kafala的基于日志的机制。提供了Compositional接口,支持Scala\n 2.错误处理\n 使用基于Offset的消息系统(Kafka)。Samza会监控每个任务的偏移量,然后在接收到消息的时候修正这些偏移量。但是用户并不知道恢复到上一个CheckPoint之后到底哪个消息被处理过,可能会导致消息多次处理,这就是At-Least原则\n 3.状态管理\n 将任务均放入Kafka中,每个任务都可以保有状态,所有状态的变化都会被提交到Kafka中,某个状态也可以很方便地从Kafka的Topic中完成重造\n5. Flink 1.概述\n 强调万物皆流,是一个Native Streaming的系统\n 2.错误处理\n 基于分布式快照,每个快照会保存流任务的状态。Flink达成了Exactly-Once的原则\n 3.状态管理\n 提供了Operator的概念,在Flink中有两种不同的状态,第一种就是本地的或者成为任务状态;另一种就是维护了整个分区的状态。\n(6)框架选用总结 选用一个合理的框架时,框架本身的成熟度与社区的完备度也是需要考虑的\n 对于小型与需要快速响应的项目,选用Storm,但是注意容错机制和状态管理带来的影响 如果基础架构中已经使用了Spark,可以尝试Spark Streaming Samza使用时几乎必须使用Kafla,但是它是At-Least原则,有投递限制 Flink是一个优秀的流处理系统,先进功能为窗口管理和时间控制,批处理的接口也是非常好用的 4.DAG(有向无环图) DAG的主要功能就是用图来表示链式的任务组合,而在流处理系统中,使用DAG表示一个流工作的拓扑\n5.分布式快照 未完待续\n4.通讯模式 点对点通讯:此种通讯方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多和多对一等配置方式,支 持树状和网状等拓扑结构\n 多点广播:能够将消息发送到多个目标站点(Destunation List) 。可以使用一条MQ指令将单一消息发送到多个目标站 点,并确保为每一站点可靠地提供消息。MQ还具有智能消息分发功能,在将一条消息发送到同一系统上的多\n 个用户时,MQ将消息的一个复制版本和该系统上接受者的名单发送到目标MQ系统。目标MQ系统在本地复制\n 这些消息,并将它们发送到名单的队列上,从而尽可能减少网络的传输量。\n 发布/订阅模式:消息按照特定的主题甚至内容进行分发,用户或者应用程序可以根据主题或内容接收到所需要的消 息,这样使得发送者和接受者之间的耦合关系变得更为松散,发送者和接受者都不需要关心对方的地址\n 集群(Cluster) :集群内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用集群通道与其他成员通信。此外,集群中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其他队列管理器可以接管它的工作,系统的可靠性比较高。 \n1.简介 消息中间件作为C/S架构之间的通信中间件,消息中间件是用于进程间通信(IPC)的软件工程组件,或者用于同一进程间通信的软件工程组件。与标准的请求应答模式相比,RPC更强调点对点交互、强事务保证和延迟敏感的服务/应用之间的通信,消息中间件则更关注于异步通信和内容投递。\n2.常见的消息中间件 ActiveMQ、RabbitMQ、Kafla 、阿里巴巴的Notify、MetaQ、RocketMQ\n六、边缘计算 1.简介 边缘计算将应用程序、数据和计算能力服务从集中式的数据中心推到网络的极限、靠近用户、设备和传感器。目前主要的应用场景就是物联网、边缘计算不是在中央服务器里整理后实施处理,而是在网络内的各设备实时处理。\n2.优点 边缘计算带来了更快的传输和响应速度,可以降低成本(流量成本、存储成本、云端流式计算资源成本)\n","permalink":"https://sin-coder.github.io/post/distri/","tags":["“分布式”","分布式系统"],"title":"分布式系统学习笔记"},{"categories":null,"contents":" 关键词:同步、异步、阻塞、非阻塞 相关概念:网络编程、进程与线程、I/O模型 一、问题背景 同步和异步,以及阻塞和非阻塞都是网络编程中经常遇到的概念,单看文字上的解释确实有些晦涩\n 难懂。接下来我们将从一个通俗的例子出发阐述它们的区别与联系\n二、一个简单的例子 隔壁老王爱好茶艺,每天都会煮开水来泡茶\n 场景一:老王将水壶放在火上,坐在旁边等待水开 (同步阻塞)\n 但是这样很耽搁时间,又不自由,效率很低,老王想换种方法\n 场景二:老王将水壶放在火上,自已去隔壁了,每隔3分钟来看下水开没有 (同步非阻塞)\n 但是这样依旧很麻烦,老王就买了一个自动报警的水壶\n 场景三: 老王用新买的水壶进行烧水,坐在旁边等待水开 (异步阻塞)\n 老王便想没有必要在水壶旁边坐着啊\n 场景四: 老王新买的水壶放在火上,自己去隔壁了,等着报警再回来 (异步非阻塞)\n 这种方式是最让老王省心的\n 小结: 同步和异步关注的焦点在于我们是否需要不断地去看水壶是否开了,同步时,需要老王不断\n地去轮询水壶是否开了,效率是比较低下的。而异步时,水壶告警提醒老王它开了\n 阻塞和非阻塞 关注的焦点在于老王是否需要坐在水壶旁边等待,在水壶旁边等待老王就是阻\n 塞的,去做其他事的老王就是非阻塞的\n 这个例子可以帮助我们初步地理解同步异步、阻塞和非阻塞之间的联系和区别,但是如果详细\n 的“追究”起来,还有许多未解释的细节\n三、理论阐述 1.同步与异步 同步和异步(syn \u0026amp; asyn),描述的是在单线程中一次方法调用后,执行者是否具备主动通知\n 的功能。同步时调用者会等到方法调用返回后才能继续后面的行为,异步时调用者不需要等到方法返回,\n方法执行完毕后会主动通知调用者\n2.阻塞和非阻塞 阻塞和非阻塞关注是调用者是否可以执行多个任务,描述的是调用者的多个线程是否可以同时执\n 行。阻塞时,多个线程不能同时进行;非阻塞时,多个线程可以同时进行\n3.二者的区别与联系 同步和阻塞完全是在单线程和多线程这两个维度上的概念,它们之间并没有强制的联系。但是从\n 实际的意义来看确实有一定的绑定关系,比如对于单线程来说,不管是同步还是异步,肯定是阻塞的,非\n阻塞只有多线程而且异步的时候才能发挥作用。\n 回来继续看烧水的例子,老王在烧水的同时去隔壁,也即在烧水这个线程之中,又开启了去隔壁\n 这个线程,所以使用异步非阻塞才更加有意义\n","permalink":"https://sin-coder.github.io/post/syn/","tags":["分布式","分布式系统","消息队列"],"title":"同步/异步、阻塞/非阻塞辨析"},{"categories":null,"contents":" 虚拟机和主机的网络连接方式 最近在使用VirtualBox安装虚拟机组建集群时,总是会遇到各种网络问题,具体包括虚拟机\n 之间的访问、虚拟机和主机之间的访问、虚拟机访问外网等,搞得晕头转向的,所以在此总结\n一下虚拟机和主机之间的网络连接方式,以便更进一步的画出集群的网络拓扑图\n 在VirtualBox的配置界面,可以看到虚拟机和主机间的网络连接方式有以下几种:网络地址\n 转换(NAT)、NAT网络、桥接网卡、内部网络、仅主机(Host-only)网络、通用驱动等,下\n面便一一详解\n1. NAT模式 NAT方式借助网络地址转换的功能,通过宿主机所在的网络来访问互联网。此种方式下,虚拟\n 机的网卡和物理网卡的网络不是在同一个网络中。虚拟机的网卡只是VirtualBox所提供的一个\n虚拟网络,并不真实存在于网络中,所以宿主机无法ping通虚拟机,虚拟机彼此间也不通,但\n是通过NAT虚拟机可以访问主机、和主机同网络的其他主机和互联网\n 不过这里的网络连接方式中有网络地址转换(NAT)和NAT网络,这二者之间又有什么区别呢?\n 其实这二者本质是相同的,不过后者是提前创建好的网络,在主界面的管理---\u0026gt;全局设定--\u0026gt;网络\n我们可以提前设置一个NAT网络供虚拟机来选用\n 总结起来,NAT模式可以节省网段中的IP地址,适合仅需自己使用的虚拟机配置\n 2.桥接模式 桥接方式下,虚拟机需要桥接到宿主机的一块网卡上(有线或者无线均可),虚拟机和宿主机\n 处于同一网段,真实存在于网络中。虚拟机之间可以互通、虚拟机和网络中的主机也可以互通、\n只要主机能上网,虚拟机也可上网,但是这样占用网络中的IP地址\n3.host-only模式 host-only模式应该是最为复杂的网络连接模式了,其他几种网络的连接方式通过这种模式的合\n 适配置均可实现。我们可以理解为VirtualBox在主机中模拟出一张专供虚拟机使用的网卡,所\n有的虚拟机都是连接到网卡上的,我们可以通过设置这张网卡来实现上网和其他功能。\n 虚拟机和主机关系,默认不能相互访问,因为不属于同一个IP地址段。但是通过网卡共享、网卡\n 桥接等,可以实现虚拟机和主机的相互访问\n 虚拟机和虚拟机的关系,默认同一个网段中的虚拟机是可以相互访问的\n 4.内部网络 内部网络模式,虚拟机与外网完全断开,虚拟机和主机之间无法相互访问只用于虚拟机\n 与虚拟机之间的访问,但前提是在虚拟机在同一网络中,实际配置时两台虚拟机设置为\n同一网络名称即可,如下图的配置中使用intnet\n5.通用驱动 运行用于选择网卡驱动,实际上很少用到,可忽略\n 6.未指定 相当于虚拟机有网卡,但是没有插线,只能ping自己才会通的\n ","permalink":"https://sin-coder.github.io/post/virtualnetwork/","tags":["云计算","虚拟化","计算机网络"],"title":"虚拟机的网络连接方式"},{"categories":null,"contents":" 关键词:Hugo 、Git、Github、域名解析 概述 1.Hugo简介 Hugo是基于Go语言开发的静态网站生成器,简单、易用、快速部署,主要用于构建个人博客\n 2.Git简介 Git是目前主流的分布式版本控制工具,有关Git的使用请查看Git的来龙去脉这篇文章\n 3.Github简介 Github是在外网环境下的一个代码托管库,有关Github的介绍请查看开始玩起Github这篇文章\n 具体过程 1.准备工作 下载Git并安装、配置环境变量 完成后在终端执行\u0026quot;git\u0026quot;命令来测试是否安装成功,有关git的安装请看Git的来龙去脉\n 下载Hugo并安装、配置环境变量 完成后在终端执行\u0026quot;hugo version\u0026quot;命令来测试是否安装成功,终端提示如下信息表示安装成功\n C:\\Users\\Administrator\u0026gt;hugo version Hugo Static Site Generator v0.59.1-D5DAB232 windows/amd64 BuildDate: 2019-10-31T15:22:43Z Hugo最好安装在英文目录下\n下载时可能由于网络问题失败,附上Hugo、Git、主题m10c的下载包链接: 下载链接\n 注册Github官网(已有账号请忽略) 2.生成个人站点 (1)在终端执行命令 C:\\Users\\Administrator\u0026gt;hugo new site E:\\hugo\\Sites\\myblog 出现以下提示信息表示创建成功:\n C:\\Users\\Administrator\u0026gt;hugo new site E:\\hugo\\Sites\\myblog Congratulations! Your new Hugo site is created in E:\\hugo\\Sites\\myblog. Just a few more steps and you're ready to go: 1. Download a theme into the same-named folder. Choose a theme from https://themes.gohugo.io/ or create your own with the \u0026quot;hugo new theme \u0026lt;THEMENAME\u0026gt;\u0026quot; command. 2. Perhaps you want to add some content. You can add single files with \u0026quot;hugo new \u0026lt;SECTIONNAME\u0026gt;\\\u0026lt;FILENAME\u0026gt;.\u0026lt;FORMAT\u0026gt;\u0026quot;. 3. Start the built-in live server via \u0026quot;hugo server\u0026quot;. Visit https://gohugo.io/ for quickstart guide and full documentation. 命令执行完毕后即可在myblog目录下生成以下文件s\n E:\\hugo\\Sites\\myblog\u0026gt;tree /f 卷 CSUYZZ 的文件夹 PATH 列表 卷序列号为 2C3A-5572 E:. │ config.toml │ ├─archetypes │ default.md │ ├─content ├─data ├─layouts ├─static └─themes 注意:\u0026quot;E:\\hugo\\Sites\\myblog\u0026quot;为该博客的根目录,这个概念接下来将会多次用到\n (2)文件简介 了解根目录下的文件将有助于个性化地定制博客\n config.toml:全局配置文件,结合所用的主题进行配置(后面会讲解)\n themes:主题存放的位置,主题中的文件也可进行个性化定制\n static:存储图片、js、css等静态资源文件\n layouts:存放用来渲染博客内容的模板文件\n data:存放相关数据文件\n contents:所有博客内容(md文件)默认的存放目录\n (3)设置主题 Hugo框架有许多酷炫的主题,可以在主题官网上进行挑选\n每个主题的详情页均会列出使用该主题的方法,这里m10c主题为例\n在根目录下执行如下命令:\n git clone https://github.com/vaga/hugo-theme-m10c.git themes/m10c 终端出现如下结果表示下载成功,同时在themes文件夹会出现m10c文件夹\n E:\\hugo\\Sites\\myblog\u0026gt;git clone https://github.com/vaga/hugo-theme-m10c.git themes/m10c Cloning into 'themes/m10c'... remote: Enumerating objects: 6, done. remote: Counting objects: 100% (6/6), done. remote: Compressing objects: 100% (5/5), done. remote: Total 276 (delta 0), reused 2 (delta 0), pack-reused 270Receiving objects: 52% (144/276), 308.01 KiB | 165.00 KReceiving objects: Receiving objects: 100% (276/276), 447.95 KiB | 231.00 KiB/s, done. Resolving deltas: 100% (88/88), done. 在进行测试前需要在config.toml文件中添加 theme = \u0026quot;m10c\u0026quot;,文件内容如下所示\n baseURL = \u0026quot;http://example.org/\u0026quot; languageCode = \u0026quot;en-us\u0026quot; title = \u0026quot;My New Hugo Site\u0026quot; theme = \u0026quot;m10c\u0026quot; (4)本地启动测试 在在根目录下执行命令\nhugo server -t m10c --buildDrafts\n会出现以下信息\n E:\\hugo\\Sites\\myblog\u0026gt;hugo server -t m10c --buildDrafts Building sites … | EN +------------------+----+ Pages | 7 Paginator pages | 0 Non-page files | 0 Static files | 1 Processed images | 0 Aliases | 3 Sitemaps | 1 Cleaned | 0 Total in 13 ms Watching for changes in E:\\hugo\\Sites\\myblog\\{archetypes,content,data,layouts,static,themes} Watching for config changes in E:\\hugo\\Sites\\myblog\\config.toml Environment: \u0026quot;development\u0026quot; Serving pages from memory Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop 复制http://localhost:1313/ 在浏览器中打开即可看到如下界面\n (5)个性化定制 以上生成的界面即为模板,需要我们个性化定制一波,相关参数在config.toml中配置\n在m10c主题界面有介绍相关参数的配置介绍,config.toml文件如下\n baseURL =\u0026quot;http://example.org/ languageCode=\u0026quot;en-us\u0026quot; title=\u0026quot;sin-coder\u0026quot; //昵称 theme=\u0026quot;m10c\u0026quot; //主题 paginate=10 //分页,每页最大博客数目 [params] author=\u0026quot;csuyzz\u0026quot; description=\u0026quot;I always remember that Talk is cheap,show me the code'\u0026quot;#个人描述 avatar=\u0026quot;cat.jpg\u0026quot; //头像文件,将置换的头像文件置于themes\\m1ec\\static\\目录下,捋文件名作为avatar的参数 [[params.social]] name=\u0026quot;github\u0026quot; //github的社交链接,还可配置twitter ur1=\u0026quot;https://github.comsin-coder\u0026quot; //Github主页地址 [params.style] //配置界面颜色 darkestColor=\u0026quot;#d35050\u0026quot; darkColor=\u0026quot;#212121\u0026quot; lightColor=\u0026quot;#f5e3e0\u0026quot; lightestColor=\u0026quot;#f5f5f5\u0026quot; primaryColor=\u0026quot;fff\u0026quot; 注意: “//”后的内容只是解释该参数的作用,在实际配置文件中不应该存在\n参数配置完成后进行测试的效果\n 当然,可配置的参数还有许多,比如文章分类、评论系统等功能,这个希望大家继续研究\n (6)创建博客文章 在根目录下执行命令\n E:\\hugo\\Sites\\myblog\u0026gt;hugo new post/firstblog.md 出现以下提示信息表示创建成功\n E:\\hugo\\Sites\\myblog\u0026gt;hugo new post/firstblog.md E:\\hugo\\Sites\\myblog\\content\\post\\firstblog.md created 系统默认是在content目录下创建文件的,为了方便管理,将所有的文章至于post目录下\n创建成功后,在content/post即可看到firstblog.md了\n接下来就是使用markdown进行编写博客了,编写完之后在根目录下重新执行命令\nhugo server -t m10c --buildDrafts即可预览到以下界面\n (7)本地部署测试命令总结 注意所有的命令均应该在根目录下执行\n hugo new site myblog git clone https://github.com/vaga/hugo-theme-m10c.git themes/m10c hugo server -t m10c --buildDrafts hugo new post/firstblog.md hugo server -t m10c --buildDrafts 3.部署到Github上 (1)创建仓库 在Github上创建仓库 ,命名必须为 用户名.github.io,如sin-coder.github.io\n (2)生成最终html界面 在根目录下执行命令\n E:\\hugo\\Sites\\myblog\u0026gt; hugo --theme=m10c --baseUrl=\u0026quot;https://sin-coder.github.io\u0026quot; --buildDrafts 会在blog目录下生成一个public文件夹,文件夹中即是firstblog转换成的html文件\npublic文件夹即是公开出来让别人能看到的文件,也是需要上传到仓库中的文件夹\n (3)将文件推送到Github仓库 cd public #先进入到public文件夹 git init #初始化 git remote add origin https://github.com/sin-coder/sin-coder.github.io.git #本地仓库链接到远程仓库 git config --global user.email 邮箱地址 #这里设置的签名和远程登陆库与Github 的账号和密码无关 git config --global user.name 名字 #作用仅仅是区分不同的开发人员 git add -A git commit -m \u0026quot;描述\u0026quot; git push -u origin master 这里操作中可能出现的错误就是与git有关了 建议先去学下git的基本命令\n (4)查看界面 在浏览器中输入 “用户名.github.io\u0026quot; 即可查看界面\n (5)新建/修改 博客 新建博客执行命令\n (根目录)\nhugo new post/filename.md hugo --theme=m10c --baseUrl=\u0026quot;https://sin-coder.github.io\u0026quot; --buildDrafts (public 目录)\ncd public git add -A git commit -m git push -u origin master 修改博客执行命令\n与新建博客基本相同,只是不执行第一条命令\n删除博客命令\n在本地将content/post/filename.md文件删除\n (根目录)\nhugo --theme=m10c --baseUrl=\u0026quot;https://sin-coder.github.io\u0026quot; --buildDrafts (public目录)\ngit add -A git commit -m \u0026quot;描述\u0026quot; git push -u origin master 4.域名解析 (1)申请域名、域名认证 在域名网站注册并购买自己的域名,同时进行域名认证\n (2)配置域名 在github仓库中添加CNAME文件,并在文件中填写绑定的域名\n注意填写的内容不能包括https://www, 只能写上csuyzz.com\n 进入设置Settings 找到Custom domain添加域名后进行保存,默认自动保存\n (3)域名解析 在命令行下执行命令:ping 用户名.github.io 获取对应的IP地址\n然后修改域名解析记录,添加两个A记录,绑定域名和IP地址\n主机记录www 和@各一个,www表示解析域名为www.csuyzz.com ,@则表示解析域名为csuyzz.com\n ","permalink":"https://sin-coder.github.io/post/hugo/","tags":["框架使用"],"title":"基于Hugo框架搭建个人博客"},{"categories":null,"contents":" Failed to connect to github.com port 443: Timed out 问题背景 最近在使用Git向Github提交时总是会出现以下报错:\n E:\\hugo\\Sites\\blog\\public\u0026gt;git push -u origin master fatal: unable to access 'https://github.com/sin-coder/sin-coder.github.io.git/': Failed to connect to github.com port 443: Timed out 而且还是偶尔出现的,特别让人心烦,目测为网络问题,折腾了许久在StackOverflow找到了答案\n 问题原因 为了访问Github更加流畅,本地使用了Shadowsocks进行代理,可是Git并没有走代理访问\n只需将Git配置为代理访问Github即可\n 解决措施 打开Windows下的cmd命令行,在命令行中直接输入以下命令(已经配置Git的环境变量),或者\n切换到Git的安装目录下执行命令(未配置环境变量)即可解决该问题\n E:\\Program Files\\Git\\Git 的目录 2019/11/27 13:19 \u0026lt;DIR\u0026gt; . 2019/11/27 13:19 \u0026lt;DIR\u0026gt; .. 2019/11/27 13:18 \u0026lt;DIR\u0026gt; bin 2019/11/27 13:19 \u0026lt;DIR\u0026gt; cmd 2019/11/27 13:19 \u0026lt;DIR\u0026gt; dev 2019/11/27 13:19 \u0026lt;DIR\u0026gt; etc 2019/02/26 19:48 149,784 git-bash.exe 2019/02/26 19:48 149,272 git-cmd.exe 2018/03/12 17:58 18,765 LICENSE.txt 2019/11/27 13:18 \u0026lt;DIR\u0026gt; mingw64 2019/02/26 20:10 144,911 ReleaseNotes.html 2019/11/27 13:19 \u0026lt;DIR\u0026gt; tmp 2019/11/27 13:19 1,203,442 unins000.dat 2019/11/27 13:18 1,297,048 unins000.exe 2019/11/27 13:19 22,795 unins000.msg 2019/11/27 13:19 \u0026lt;DIR\u0026gt; usr 7 个文件 2,986,017 字节 9 个目录 115,342,643,200 可用字节 E:\\Program Files\\Git\\Git\u0026gt;git config --global http.proxy http://127.0.0.1:1080 E:\\Program Files\\Git\\Git\u0026gt;git config --global https.proxy http://127.0.0.1:1080 ","permalink":"https://sin-coder.github.io/post/git%E8%BF%9E%E6%8E%A5github%E5%A4%B1%E8%B4%A5/","tags":["问题解决","随笔","Git"],"title":"Git向Github push时,连接超时"},{"categories":null,"contents":"1.什么是Map? 什么是Reduce?\nMap是拆解 Reduce是组装 本治就是分治法\nInput --\u0026gt; Split--\u0026gt;Map---\u0026gt;Shuffle(组装)---\u0026gt;Reduce ----\u0026gt;Finalize(高度并行的)\n实现代码:\nMapReduce如何实现统计单词出现的次数的\nMap(string key, string value) #key : the id of a line #value: the content of the line for each word in value: OutputTemp(word,1) # Reduce 的过程 Reduce(string key,list valueList) #key : the name of a word #valueList: the appearance of this world int sum = 0 for value in valueList: sum+=value OutputFinal(key,sum) MapReduce 如何实现倒排索引的?\nMapReduce的整体结构?\n总结:Map就是一个disassemble Reduce 就是一个assemble\n","permalink":"https://sin-coder.github.io/post/mapreduce/","tags":null,"title":"Mapreduce"},{"categories":null,"contents":"1.如何在文件内进行快速查询?\n关键点: 从File 到 Table Table = a list of sorted \n2.如何保存一个很大的表?\n关键点: A table = a list of tables (小表)\nA tablet = a list of sorted \n使用MetaData的形式保存每一个小表的位置\n3.如何保存一个超大的表?\n关键点:A table = a list of tablets (小表)\n A tablet = a list of SSTables (小小表)\n A SSTables = a list of sorted \n4.如何向表中写数据?\n关键点:通过写入memTable(内存表)来加速\nA tablet = memTable + a list of SSTables\n5.内存表过大怎么办?如何避免内存丢失数据?\n内存表过大时,重新写成一个小小表 导入硬盘进行保存 成为一个新的SSTables\n在硬盘中添加Tabletlog 记录写入的数据\nA Tablet = memTable + a list of SSTables + log\n6.如何读数据?(相当于是查找数据)\n关键点: SSTable 内部的数据是有序的,但是SSTable之间的数据是无序的\n 需要查找所有的SSTables 和 memTable 需要在硬盘中的SSTable中查找该元素\n7.如何加速读数据?\n关键点:A SSTable = a list of sorted = a list of 64K blocks + index\nIndex会预先加载到内存 通过Index才能找到硬盘的位置\n8.继续加速读数据?\n关键点 : 使用bloomfilter A SSTables = a list of sorted = a list of 64K blocks + bloomfilter\n Bloomfilter会预先加载到内存 通过bloomfilter判断元素是否会存在\n9.将表装入内存?\n10.如何将表的物理视图装入表的逻辑视图?\nKey = string(row,column,time)\n11.BigTable的架构\n Chubby锁服务\n总结:BigTable使用的物理结构存储了一个超大表\n 吃内存的BigTable和吃硬盘的GFS合为一体\n","permalink":"https://sin-coder.github.io/post/bigtable/","tags":null,"title":"Bigtable"},{"categories":null,"contents":" Google File System 一、概述 Google的三篇论文:Google File System 、BigTable 、MapReduce的发表彻底拉开了云计算时代的序幕,同时这三篇论文也是想要入门云计算的学习人员必读的。最近读了这三篇论文,再参考了一些资料后写下自己的总结\nHDFS 、HBase 、MapReduce\n GFS BigTable MapReducd Google系统整体的架构图如下\nGFS系统的优点:高可用性、自动负载均衡\n系统的结构:文件系统(GFS)、数据模型(Bigtable)、算法(MapReduce)、应用\n在本篇文章中我们着重去描述GFS\n二、GFS系统的设计 1.设计思路\n (1)组件失效是一种常态,而不是意外;因此持续的监控、错误的侦测、灾难冗余等机制必须集成在GFS\n (2)存储的文件非常巨大,基本上为TB级的,I/O操作和Block、Chunk的尺寸都需要规划\n (3)对文件的修改以在文件尾部追加数据为主,数据的追加对系统性能有重要的影响\n (4)应用程序和文件系统的API协同设计可以大幅度提高系统的灵活性\n2.系统的工作负载分析\n3.GFS系统架构\n三、系统工作原理 设计原则:最小化所有的操作和Master节点的交互\n系统具体的工作过程:\n3.文件系统的操作\n4.Master节点的操作\n名称空间管理和锁\n副本的位置\n创建、重新复制、重新负载均衡、垃圾回收、过期失效的副本检测\n5.容错和诊断\n高可用性、数据完整性、诊断工具\n四、总结 3.Linux文件系统工作原理:\n 保存一个小文件\n 保存的每一个文件都有一个元数据Metadata,其中包括filename文件信息 文件名 创建时间 文件大小 index组成文件的每一个Block的索引 关键点为1block = 1024 Byte\n 保存一个大文件:\n 关键点为chunk : 1chunk = 64MB =64*1024 =65536 blocks\n 优点:减少元数据 减少流量 缺点:小文件会浪费较多空间\n4.GFS的原理:\n 将MetaData放入Master Server ,然后其他的Chunk都放在ChunkServer中\n 关键点:Master+many ChunkServers\n 缺点:ChunkServer中任何数据的改变都需要通知Master\n 但是Master只保存了Chunk在各个服务器的地址,不记录每块数据的偏移量这样的优点是减少Master的元数据信息 减少Master和 ChunkServer之间的通信\n5.GFS的容错机制\n 客户端在读数据时检查checksum, 每一个Block 保存校验和, 1个checksum 是32位的,\n 如果数据损坏的话,Chunk Server就需要去找Master恢复数据 具体的过程为:\n (1) ChunkServer4 向Master服务器发送请求信息 \u0026quot;我的Chunk坏了,谁还有数据的备份\u0026quot;\n (2)Master服务器向ChunkServer4发送 “ChunkServer1 和ChunkServer3 这两台服务器还有你数据的备份”\n (3)ChunkServer4 向ChunkServer3(距离较近) 发送请求 \u0026quot;我需要备份的数据\u0026quot;\n (4)ChunkServer3 便向ChunkServer4发送它所需要的数据\n发送心跳检查ChunkServer是否运行正常 如果有服务器挂掉的话就向Master申请恢复 基于存活副本数的恢复策略\n6.GFS核心的读写操作\n读文件的过程\n写文件的过程\n如果在写入文件的过程中出现错误,那么就直接返回给客户端一个错误,让客户端自行去处理,对于分布式系统处理错误的机制会带来更多的错误,底层只是实现基本的功能,不去管错误的处理;\n","permalink":"https://sin-coder.github.io/post/gfs/","tags":null,"title":"Google File System"},{"categories":null,"contents":" Go语言学习总结(一) 一、Go语言简介 1. Go语言用途 搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言\n 在高性能的分布式系统领域,Go语言比其他语言有着更高的开发效率\n2. Go语言特点 自动垃圾回收、丰富的内置类型、函数多返回值、错误处理、数组安全\n 匿名函数和闭包、类型和接口、并发编程、反射、语言交互性等\n3. 设计思想 目前主流的编程思想主要有面向对象编程、面向过程编程,但是Go语言在设计的过程中吸收了一些\n 小众的编程哲学思想,比如函数式编程思想(支持匿名函数与闭包), 面向消息编程思想(支持 goroutine\n 和通道),因此Go推荐使用消息而不是共享内存来进行并发编程\n二、Go语言基础语法 1. 程序组成元素 (1)包声明 源文件中非注释的第一行指明这个文件属于哪个包,如package main, package main表示一个可独立执行的\n 程序,Go程序是通过package来进行组织的,只有package名称为main的源码文件可以包含main函数\n(2)引入包 导入程序所要使用的包 fmt包格式化的输入输出\n 导入包时可以通过import关键字来单个导入,也可以同时导入多个,如:\n//单个导入 import \u0026quot;fmt\u0026quot; import \u0026quot;io\u0026quot; //同时导入多个 import ( \u0026quot;fmt\u0026quot; \u0026quot;math\u0026quot; ) 文件名与包名没有直接关系、同一个文件夹下只能有一个包名,否则编译报错\n 导入包时一般为 import \u0026quot;项目名/包名\u0026quot;\n 调用函数时则是通过PackageName.FunctionName() 来进行调用\n(3)函数 fun main()是程序开始执行的函数,该函数也是每一个可执行程序所必须的,每个函数后都会有{},但是 \u0026quot; { \u0026quot;\n 是不能单独放置在一行中的,否则在运行时会产生错误\n(4)标识符 以一个大写字母开头的标识符,可以被外部包的代码所使用,这被成为导出,类似于public\n 以一个小写字母开头的标识符,对外包则是不可见的,在整个包的内部是可见的,类似于private\n(5)语句 每行一条语句,且不需要加分号\n 语句中适当使用空格能够让程序更加容易地阅读,但是变量的声明必须使用空格来进行隔开\n2.程序的基本结构 程序的基本结构示例:\n//当前程序的包名 package main //导入其他包 import \u0026quot;fmt\u0026quot; //常量的定义 const PI = 3.14 //全局变量的声明和赋值 在函数外部定义的变量 var name = \u0026quot;CSUYZZ\u0026quot; //一般类型的声明 type newType int //结构体的声明 type structName struct{} //接口的声明 type golang interface{} //函数的声明 fun funname(){} //main函数、程序的入口点 fun main(){ fmt.Println(\u0026quot;Hello World!\u0026quot;) } 3.Go语言数据类型 划分不同的数据类型就是为了最大限度的利用内存\n(1)布尔型 布尔型值:true false 示例:var flag bool = false\n(2)数字类型 整型:uint8 、uint16、uint32、uint64、int8、int16、int32、int64\n 浮点型:float32、float64、complex64(32位的实数和虚数)、complex128(64位的实数和叙述)\n 其他数字类型:byte、rune、uint(32位或64位)、int(32位或64位)、uintptr(指针)\n(3)字符串类型 Go语言的字符串由单个字节连接,使用UTF-8编码来标识Unicode文本\n ==注意==:字符串类型在Go语言中是个结构,大小为16个字节\n(4)派生类型 指针类型、数组类型、结构体类型、Channel类型、函数类型、切片类型、接口类型、Map类型\n(5)类型转换 基本格式:type_name(expression)\n4.变量\u0026amp;常量 (1)变量声明 变量声明可以先定义后初始化、也可根据值自动判定变量类型、还可以省略var,示例代码如下\n Go语言是非常严格的,函数内部变量在声明之后必须被使用,全局变量允许声明但不使用\n//1.指定变量类型定义,再初始化;如果不初始化则变量值为系统默认值 var vname vtype vname = value var v1,v2,v3 type //常见数据类型的系统默认值 /* 数值类型 : 0 布尔类型 : false 字符串为 : \u0026quot;\u0026quot; 以下几种类型均为nil var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error (接口) */ //2.根据值自动判别变量类型 var vname = value var vname1,vname2,vname3 = v1,v2,v3 //3.省略var := 左侧如果没有声明新的变量,就会产生编译错误 首选形式 //该种方法只能用于函数体内,全局变量则不能使用 vname := value //并行赋值 vname1,vname2,vname3 := v1,v2,v3 //交换两个变量的值 a,b = b,a //空白标识符_被用于抛弃值,_实际上是一个只写变量,不能得到它的值 //Go语言中要求你必须使用所有被声明的变量,但有时并不需要得到一个函数的所有返回值 _,b = 5,7 //4.所有变量的声明置于{}中 一般用于全局变量的声明 程序更加结构化 var ( vname1 vtype vname2 vtype ) (2)常量 常量定义使用const关键字,定义的方式与变量相同\n 特殊常量:itoa ,可以被编译器自动修改的常量;itoa在const内部的第一行被重置为0,const中每新增一行常量\n itoa加一,可以将它理解为iota的行索引,示例代码:\n//itoa可以被用作枚举值 const ( a = itoa //a=0 b = iota //b=1 c = iota //c=2 ) //也可以简写为 const ( a = iota //a=0 b //b=1 c //c=2 ) 5.条件语句 (1)if语句使用语法 不需要使用括号将条件包含起来 大括号{}必须存在,即使只有一行语句 左括号必须在if或者else的同一行 在if之后,条件语句之前,可以声明变量,但是其作用域只能够在该条件逻辑块内 在有返回值的函数中,最终的return 不能在条件语句中 (2)switch语句使用语法 每个case语句后默认自带break语句,匹配成功后就不执行其他语句 使用fallthrough会强制执行后面的case语句,不会判断下一条case语句的表达式结构是否为True switch支持多条件匹配 (3)select语句使用 select语句就是通信中的switch语句\n 每个case都必须是一个通信\n 所有channel 表达式都会被执行\n 所有被发送的表达式都会被求值\n 如果任意某个通信可以进行,它就执行,其他被忽略\n 如果有多个case可以执行,Select会随机公平地选出一个执行,其他不会执行\n否则,如果有default语句,则执行该语句;\n如果没有,select将阻塞,直到某个通信可以运行;Go不会对channel或值进行求值\n6.循环语句 for循环的常见使用示例如下:\nfor init; condition; post {} for condition{} //相当于while for { } //相当于for(;;) 无限循环 //for循环的range形式可以对slice,map,数组,字符串等进行迭代循环 for key,value := range oldMap { newMap[key] = value } 三、Go语言数据类型详解 数组、指针、结构体、切片(Slice)、范围Range、Map集合、接口\n四、Go语言特征详解 并发、错误处理\n","permalink":"https://sin-coder.github.io/post/go/","tags":null,"title":"Go语言学习总结(一)"},{"categories":null,"contents":" 数据结构学习心得 学习数据结构和算法也有段时间了, 在此记录自己学习的过程,以及心得和体会\n一开始就是从\n我不是专门搞竞赛的,学习数据结构只是为了面试和提高自己的逻辑思维能力,确实很多厉害的算法知识我也是不会的,只能解决常规的问题\n数据结构最高层的抽象便只有数组和链表\n嗯?那队列、栈、哈希表、堆、树、图都去哪了呢\n数组和链表是数据结构的结构基础,其他的都是属于上层建筑。哪些多样化的数据结构,究其源头,都是在\n 链表或者数组上的不同操作而已\n 比如队列和栈这两种数据结构既可以使用链表、也可以使用数组实现。用数组实现,就要处理不断扩容的\n 问题;用链表实现,就没有这个问题,但是需要避免更多的存储节点指针;图也有两种的表示方法,邻接表\n就是链表,邻接矩阵就是二维数组。邻接矩阵判断连通性迅速,但是一般比较耗费空间,邻接表比较节省空间,\n但是处理效率要比邻接矩阵慢很多\n 散列表就是通过散列函数将键映射到一个大数组中,而对于解决散列冲突的方法,拉链法使用链表,操作比\n 较简单,但需要空间。线性探查法需要使用数组,连续寻址,比较省空间,但是操作复杂\n 树用数组实现就是堆,因为堆是一个完全的二叉树,父子节点之间有对应的关系,方便使用数组进行存储。\n 其他种类的树因为没有完全二叉树那种特殊的关系,所以不便于使用数组进行存储,只能使用链表,在链表树的\n基础上又设计出许多巧妙的树,如二叉搜索树、AVL树、红黑树、B树、B+树等等\n 数据结构的操作,无非就是遍历和访问,具体就是增删查改。不同的数据结构应用的场景不同,在每种\n 场景下要尽可能地进行高效的增删查改。遍历和访问无外乎就两种形式,线性和非线性的,线性的就是for/while\n为代表,非线性的就是以递归为代表\n数组的遍历框架都是线性的:\npublic void traverse(int[] arr){ for(int i = 0;i \u0026lt; arr.length;i++){ //访问每一个arr[i] } } 二叉树的遍历框架,典型的非线性递归遍历结构:\npublic void traverse(TreeNode root){ traverse(root.left); traverse(root.right) } 这两个框架都可以根据自己的需求进行改进\n 链表的遍历框架既可以是线性的,也可以是非线性的\npublic void traverse(ListNode head){ while(head != null){ //访问head.val head = head.next; } } public void traverse(ListNode head){ //访问head.val traverse(head.next); } 二叉树的遍历框架又可以具体扩展为N叉树的遍历框架\n//基本的N叉树节点 class TreeNode{ int val; TreeNode[] children; } public void traverse(TreeNode root){ for(TreeNode child:root.children){ traverse(child) } } N叉树的遍历又可以扩展为图的遍历,图可以看做是几个N叉树的结合体,图中有环怎么办呢 ,使用一个visited数组\n来记录遍历过的节点就行了\n 框架就是套路,做题的大纲\n数据结构是工具,算法是通过合适的工具解决特定问题的方法\n二叉树的遍历框架\n public void traverse(TreeNode root){ //此时访问root.val,就是前序遍历 traverse(root.left); //此时访问root.val,就是中序遍历 traverse(root.right); //此时访问root.val,就是后序遍历 } ","permalink":"https://sin-coder.github.io/datastructure/summay/","tags":["数据结构"],"title":"数据结构学习心得"},{"categories":null,"contents":" 计算机网络 应用层协议、二层交换、三层路由、TCP/UDP,网络编程,运维 操作系统 数据结构与算法 leetcode、剑指offer;贪心、回溯、动态规划、分治、递归;树,图,堆、队列、栈、链表、数组; 数据库技术 MySQL、Redis、MongoDB 编程语言 Python、Java、Go Linux Shell、运维 云计算 Docker、K8s、云原生 分布式 6.824、分布式数据库、分布式消息队列、分布式计算 个人随笔 技术之路、个人经历、生活体会 ","permalink":"https://sin-coder.github.io/category/content/","tags":null,"title":"技术博客分类目录"},{"categories":null,"contents":" 一个半路出家的怪孩子是如何折腾计算机的\n ","permalink":"https://sin-coder.github.io/personal/introduce/","tags":["个人随笔"],"title":"技术成长之路"},{"categories":null,"contents":" 贪心算法解题总结 一、引例 某天你在超市购物后,总共消费了782元,这时假设你有1元、5元、10元、20元、100元和200元的钞票\n 无穷多张,那么最少需要多少张钞票足够支付?\n 直觉告诉我们:要尽可能多地使用面值较大的钞票,其实这就是一种贪心的思想\n 二、贪心算法简介 由引例我们已经大概了解了什么是贪心,在这儿对它下个定义:贪心算法是指在对问题求解时,总是做\n 出在当前看来是最好的选择;也就是说不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解\n 贪心算法不是对所有的问题都能得到整体的最优解,关键是贪心策略的选择,具体的贪心策略中某个状\n 态以前的过程不会影响以后的状态,只与当前的状态有关\n三、Leetcode典型例题 1.455分发饼干 (1) 题目描述\n假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每\n 个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺\n寸 sj 。如果 sj \u0026gt;= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足\n越多数量的孩子,并输出这个最大数值。一个小朋友最多能拥有一块饼干\n (2)解题思想\n根据让更多的孩子得到满足这个目标,可以分析出如下贪心规律:\n 某块饼干不能满足某个孩子的胃口,则它也一定不能满足胃口更大的孩子\n 某个孩子的胃口可以用更小的饼干来满足,则没有必要用更大的饼干满足,更大的饼干留给胃口更大的孩子\n 孩子的胃口越小,则其更容易被满足,所以优先从胃口小的孩子尝试\n (3)算法思路\n 按照胃口大小和饼干大小对两个数组进行从小到大的排序\n 按照从小到大的顺序用饼干来尝试是否可以满足某个孩子的胃口,每个饼干只尝试一次,如能够满足,接着\n 用下一块饼干继续尝试能否满足下一个孩子的胃口;否则,抛弃该饼干,用下一块饼干继续尝试满足当前\n的孩子。直到没有更多的孩子或者没有更多的饼干,算法结束\n(4)代码实现\n class Solution: def findContentChildren(self, g: List[int], s: List[int]) -\u0026gt; int: if not g or not s: #base case return 0 g.sort() #对孩子的胃口值和饼干大小进行从小到大排序 s.sort() gi,sj = 0,0 #gi表示当前孩子的下标 sj表示尝试的饼干下标 while gi \u0026lt; len(g) and sj \u0026lt; len(s): #孩子和饼干同时未尝试完成时 if g[gi] \u0026lt;= s[sj]: #当饼干大于孩子的胃口时 gi+=1 #孩子的下标向后移动 sj+=1 #无论成功或者失败,每个饼干只尝试一次,饼干下标向后移动 return gi #最后的gi为孩子下标加1,即为满足的孩子个数 (5)算法分析\n上述代码中,主要的操作步骤有两步,第一步是对两个数组进行排序,假设这里使用的快速排序的方法,\n 时间复杂度为O(nlogn),第二步相当于对两个数组分别进行遍历,时间复杂度为O(n),所以整个算\n法的时间复杂度为O(nlogn+n);这里只使用了常数级的空间,所以空间复杂度为O(1)\n2.376 摆动序列 (1)题目描述\n给定一个整数序列,返回作为摆动序列的最长子序列的长度。通过从原始序列中删除一些(可选)元素\n 来获得子序列,剩下的元素保持其原始顺序\n (2)解题思想\n想要获得最长的子序列,必须关注在整数序列中出现的局部递增或者局部递减时如何取舍元素这个问题,\n 这里就体现出贪心的思想:在递增或者递减序列中选择最大或者最小的那个元素,可以让下一个元素成为摇\n摆子序列中元素的概率更大;比如这样一个序列:[1,17,5,10,13,15,10],从第4个元素开始元素递增\n那么选取[10,13,15]中哪一个元素才会使得下一个元素进入最长子序列的概率更大呢,显然就是15了,这就\n是贪心的思想\n 在整个算法的设计中,就遵循一个原则即可,遇到递增序列时,选取最大的那个元素进入最长子序列,\n 遇到递减序列时,选取最小的那个元素进入最长子序列\n (3)算法思路\n在具体算法设计时,就需要考虑到所有可能的情况。最长子序列的长度至少为1;解决整个问题时,可\n 以采用状态机的算法,对于整数序列中的每个元素,都可能处于begin、up、down的状态,根据当前元素与\n前一个元素的大小关系决定当前元素的状态是否改变以及是否将当前元素加入最长子序列,具体的状态转换\n图如下:\n (4)代码实现\n def wiggleMaxLength(self, nums: List[int]) -\u0026gt; int: if len(nums)\u0026lt;2: return len(nums) begin,up,down = 0,1,2 #扫描序列时的三种状态 state = begin max_length = 1 #摇摆序列的最大长度至少为1 for i in range(1,len(nums)): #要从第二个元素开始扫描 if state == begin: if nums[i-1] \u0026lt; nums[i]: state = up max_length+=1 elif nums[i-1] \u0026gt; nums[i]: state = down max_length+=1 elif state == up: if nums[i-1] \u0026gt; nums[i]: state = down max_length+=1 else: if nums[i-1] \u0026lt; nums[i]: state = up max_length+=1 return max_length #遍历完后返回最大长度 (5)算法分析\n算法中主要就是进行状态转换的一些判断的if else语句,整个算法中对数组的所有元素都进行了扫描,所以\n 时间复杂度为O(n);只使用了常数级的空间,空间复杂度为O(1),比较高效\n3.402 移掉K位数字 (1)问题描述\n给定一个以字符串表示的非负整数num,移除这个数中的k位数字,使得剩下的数字最小\n(2)解题思想\n从数学知识来看,若要去掉某一位数字,为了使得到的新数字最小,需要尽可能让得到的新数字优先最高位\n 最小、其次次高位最小,再其次第三位最小\n 所以贪心的思想就是从高位向地位遍历,如果对应的数字大于下一位数字,则把该位数字去掉,这样得到的\n 数字就是最小的;最极致的做法就是从高位到地位遍历k次,每次按照上述规律去掉一个数字,但是这样时间复杂\n度过高,而我们可以使用栈存储遍历过的元素,来达到线性的时间复杂度\n (3)算法思路\n使用栈存储最终的结果或者删除的工作,从高位向低位遍历数字,如果遍历的数字大于栈顶元素,则将该\n 数字入栈,如果小于栈顶的元素则进行pop弹栈,直到栈为空或不能再删除数字或者栈顶小于当前元素为止\n 当然也存在一些特殊的情况:\n 当所有数字都扫描完成后,k\u0026gt;0,此时就要从栈顶开始删除k个元素\n 当遇到0元素时,要查看当前栈是否为空,为空就放弃,否则也要压入栈中\n 字符串中的每个字符和数字之间的转换,最后返回的是字符串;若字符串为空,则返回0\n (4)代码实现\n def removeKdigits(self, num: str, k: int) -\u0026gt; str: stack = [] #用列表表示一个可以遍历的栈 result = \u0026quot;\u0026quot; #存储最终返回的字符串 for c in num: while len(stack) and k \u0026gt; 0 and stack[-1] \u0026gt; int(c): stack.pop() k -=1 if len(stack) or int(c) !=0: stack.append(int(c)) while len(stack) and k \u0026gt; 0: #如果栈不空并且可以继续删除数字 stack.pop() k-=1 for i in stack: #将栈中的每一个元素转换成字符组成字符串 result+=str(i) if result == \u0026quot;\u0026quot;: #如果最后字符串为空则返回0 result = \u0026quot;0\u0026quot; return result (5)算法分析\n 本题中使用的算法时间复杂度不便于具体分析,但是总体上是线性的,空间上使用了栈,空间复杂度为\n O(n),相较于暴力法已经优化了许多\n4.55 跳跃游戏 (1)问题描述\n给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最\n 大长度。判断你是否能够到达最后一个位置\n (2)解题思想\n假设此时处在数组的第i个位置上,该位置最远可以调到j位置(j位置可计算),则从i位置一定还可以\n 跳至i+1、i + 2、.....、j - 1和 j位置;那么从这些可选的位置中应该选择去调到哪一个位置才是最好的,这里\n就体现出了贪心的思想,从第i位置应该跳到可跳位置中可以跳得更远位置的位置(不要晕),看看下面算法的\n思路就知道了\n (3)算法思路\n 先求出从第i个位置最远可以跳至的位置index[i],index[i]数组和nums[i]数组是一一对应的,具体可使用以下 公式:index[i] = i + nums[i]\n 用jump表示当前所处的位置,max_index表示从第0位置到第jump位置中,最远可达到的位置 jump 初始化为0,max_index初始化成index[0]\n 利用jump扫描index数组,直到jump到达index数组尾部,此时返回True;若jump超过max_idnex,则返回 False\n(4)代码实现\n def canJump(self, nums: List[int]) -\u0026gt; bool: if not nums: #base case return False index = [] for i in range(len(nums)): index.append(i+nums[i]) #计算index数组 jump = 0 max_index = index[0] #初始化jump和index while (jump \u0026lt; len(index)) and (jump \u0026lt;= max_index): #两个条件要同时满足 if max_index \u0026lt; index[jump]: max_index = index[jump] jump += 1 if jump == len(index): return True return False (5)算法分析\n在该算法中,首先计算了idnex的数组,时间复杂度和空间复杂度即为O(n),然后又使用jump去遍历一遍\n 数组,时间复杂度为O(n);所以总的时间复杂度就是O(n),空间复杂度也是O(n),都是线性的\n5.45 跳跃游戏2.0 (1)问题描述\n使用最少的次数来跳到数组中的最后一个位置\n(2)解题思想\n这道题与之前那道最大的不同就是需要用最少的次数跳到数组的末尾,问题的关键就是确定最佳的跳跃位置\n 具体的贪心思想是这样的,在到达某点之前若一直不跳跃,发现从该点已经不能跳到更远的地方了,则在这次\n之前肯定有次必要的跳跃;所以在无法到达更多地方的位置之前应该跳到一个可以到达的更远位置的位置\n (3)算法思路\n 变量的设置,current_max_index为当前可到达的最远位置、pre_max_index为在遍历各个位置的过程中, 各个位置可能达到的最远位置,jump_min为最少的跳跃次数\n 利用i遍历数组,若i超过current_max_index,jump_min则加1,更新current_max_index为pre_max_index\n 遍历过程中若nums[i] + i更大,则更新pre_max_index = num[i]+i\n (4)代码实现\n def jump(self, nums: List[int]) -\u0026gt; int: if len(nums) \u0026lt; 2: #如果数组长度小于2,则说明不用跳跃,返回0 return 0; current_max_index = nums[0] #当前可达到的最远位置 pre_max_index = nums[0] #遍历各个位置过程中可能达到的最远位置 jump_min = 1; for i in range(1,len(nums)): if i \u0026gt; current_max_index: #如果已经无法向前移动了,进行跳跃 jump_min+=1 #及时更新当前可以到达的最远位置 current_max_index = pre_max_index if pre_max_index \u0026lt; nums[i] + i: pre_max_index = nums[i] + i #更新pre_max_index return jump_min (5)算法分析\n该算法中遍历了一遍数组,时间复杂度为O(n),使用了常数量级的空间,空间复杂度为O(1)\n 6.452 使用最少数量的箭引爆气球 (1)问题描述\n已知在一个平面上有一定数量的气球,平面可以看做是一个坐标系,在平面的X轴的不同位置上安排弓箭\n 手向y轴方向射箭,弓箭可以向y轴走无穷远;给定气球的宽度xstart \u0026lt;= x \u0026lt;= xend,求为将气球全部打爆至少\n需要多少弓箭手。如四个气球至少需要两个弓箭手\n (2)解题思想\n对于某个气球,至少需要使用1只弓箭将它击穿;而且在这只气球将其击穿的同时,尽可能击穿其他更多的\n 气球,这就是贪心的思想\n (3)算法思路\n 对各个气球进行排序,按照气球的左端点从小到大排序\n 遍历气球数组,同时维护一个射击区间,在满足可以将当前气球射穿的情况下,尽可能击穿更多的气球,每\n 击穿一个新的气球,更新一次射击区间\n 如果新的气球没有办法击穿了,则需要增加一名弓箭手,维护一个新的射击区间(将该气球击穿),然后继 续遍历\n 特别需注意的是,当一个区间右端点和另一个区间的左端点相同时,用一支箭也是可以射爆两个的 (4)代码实现\n class Solution: def findMinArrowShots(self, points: List[List[int]]) -\u0026gt; int: if not points: return 0 points.sort(key = self.takeFirst) #对气球按照左端点大小进行排序 shooter = 1 #初始化弓箭手的数量 shoot_begin = points[0][0] shoot_end = points[0][1] #初始化射击区间 for i in range(1,len(points)): if points[i][0] \u0026lt;= shoot_end: shoot_begin = points[i][0] #更新射击区间左端点即为新气球的左端点 if shoot_end \u0026gt; points[i][1]: shoot_end = points[i][1] #更新射击区间的右端点为新气球的右端点 else: #在保证当前气球被射穿的情况下,射击区间不能再更新了 shooter += 1 #需要增加一个新的设计区间了 shoot_begin = points[i][0] shoot_end = points[i][1] return shooter def takeFirst(self,element:List[int]) -\u0026gt; int: #获取列表中的第一个元素 return element[0] (5)算法分析\n该算法主要是对原数组进行排序和遍历,假设排序的时间复杂度是O(nlogn),总共的时间复杂度是\n O(nlogn + n),而且只使用了常量级的空间,空间复杂度为O(n)\n","permalink":"https://sin-coder.github.io/leetcode/greedy/","tags":["数据结构","leetcode"],"title":"贪心算法集锦"},{"categories":null,"contents":" 深度优先和广度优先算法详解 一、DFS 和 BFS 算法概述 1.什么是DFS和BFS? DFS (Depth-First-Search),称为深度优先搜索;BFS(Breadth-First-Search),称为广度优先搜索。\n 这二者都是在树和图的遍历中应用非常广泛的算法,那么再来看下遍历的定义:从初始点出发,按照某种\n搜索方法对树或图中的每个节点均做一次且仅做一次的访问,访问具体节点所做的操作依赖于具体的问题。\n 在树的遍历算法中,我们经常会看到前序遍历、中序遍历、后续遍历、层序遍历等方法,其实前序遍历\n 就是深度优先搜索的一种实现,层序遍历就是对广度优先搜索的一种实现。树也可以看做是一种特殊的图,\n树和图的遍历主要的区别就是树有根节点(指定的),而图需要我们自身指定开始遍历的节点。\n2.DFS和BFS算法的核心思想 BFS和DFS都是图的遍历方法。它们具有一些共性的问题,比如都要避免节点的重复访问,具体的实践\n 中可以设置一个访问数组,数组中的每个元素代表一个节点,当节点被访问后数组中对应的元素赋一个特定\n的值进行标记即可\n DFS的搜索过程是这样的:\n 先访问初始节点V\n 从V未被访问的邻接点中选取一个W,从W出发进行DFS\n 重复上述步骤即可\n BFS的搜索过程是这样的:\n 先访问图的初始节点V\n 依次访问V节点的所有邻接节点V1,V2,V3...\n 按照V1,V2,V3被访问的次序依次访问与它们相邻接的未被访问的节点\n 重复上述过程\n 3.DFS和BFS算法效率分析 DFS和BFS的时间复杂度只与数据的底层存储结构相关,而与搜索的路径无关。当使用邻接矩阵存\n 储时,对于每一个被访问的节点,都要循环检测矩阵中的整整一行(n个元素),时间复杂度为O(n^2)\n当使用邻接表来存储时,有2e个表节点,但只需要扫描e个节点即可完成遍历,加上访问n个头节点的\n时间,时间复杂度为O(n+e)\n DFS和BFS算法的空间复杂度相同,都是借用了堆栈或队列,为O(n)。递归在本质上也属于栈\n 二、DFS 和BFS 的算法实现 图在底层都是以邻接表或者邻接矩阵的方式来存储的,在Java、C++都可以使用表或者矩阵来进行定义\n 的,Python中可以使用字典来进行定义。解决图的BFS问题就是利用队列的先进先出的思想,队列可以保存\n图中未遍历的节点;解决图的DFS问题是利用栈这种数据结构,递归和非递归的实现本质上都是先进后出。\n1.DFS的算法实现 假设有这样一张图:\n (1)Python的实现\n #定义一个图的结构 使用字典 graph = { 'A':['B','C'], 'B':['A','C','D'], 'C':['A','B','D','E'], 'D':['B','C','E','F'], 'E':['C','D'], 'F':['D'] } #DFS算法的实现(非递归) def DFS(graph,s): #graph是一个图,s指的是开始节点 stack=[] #使用来列表来模拟队列 stack.append(s) visited = set() #利用集合元素的唯一性 visited.add(s) while stack: vertex = stack.pop() #拿出一个邻接点 nodes = graph[vertex] #得到这个邻接点的所有邻接点,返回的是一个列表 for node not in nodes: stack.append(node) visited.add(node) print(vertex) #DFS算法的实现(递归) visited = set() def DFS(vertex,visited): visited.add(vertex) nodes = graph[vertex] for node in nodes: if node not in visited: DFS(node,visieted) print(vertex) #BFS算法的实现 def BFS(graph,s): #graph是图,s是开始遍历的节点 queue = [] #待使用的队列 queue.append(s) visited = set() visited.add(s) while queue: vertex = queue.pop(0) nodes = graph[vertex] for node in nodes: if node not in visited: queue.append(node) visited.add(node) print(vertex) (2)Java中的实现\n //DFS 算法 //1.使用邻接表 时间复杂度O(n+e)递归实现 public void DFS(int vertex){ System.out.print(this.table[vertex].data + \u0026quot; \u0026quot;); this.visited[vertex] = true; for(ArcNode node = this.table[vertex].firstarc; node !=null; p = p.nextarc) if(this.visited[node.adjvex] == false) this.DFS(node.adjvex) } //2.使用邻接表 借助栈来实现 public void DFS(int vertex){ System.out.print(this.table[vertex].data + \u0026quot; \u0026quot;); this.visited[vertex] = true; ArrayDeque stack = new ArrayDeque(); stack.addFirst(table[vertex]); } //3.使用邻接矩阵 时间复杂度O(n^2) 递归 public void DFS(int vertex){ System.out.print(this.table[vertex] + \u0026quot; \u0026quot;); this.visited[vertex] = true; for(int i = 0;) } //省略 三、典型问题 ","permalink":"https://sin-coder.github.io/datastructure/bfs-dfs/","tags":["数据结构","算法"],"title":"BFS \u0026 DFS算法"},{"categories":null,"contents":" 一、概述 1.什么是数据结构中的容器? 容器是一种把多个元素组织在一起的数据结构,也可以理解为一种可以包含其他类型对象作为元素的\n 的对象。容器是仅仅用来存放数据的,本身没有取出元素这种能力,大多数情况下是通过可迭代对象来\n操作容器\n 容器这种数据结构在各种编程语言中都有相应的实现,比如我们经常会比较熟悉的C++的标准模板库\n (Standard Template Library,STL)、Java的集合框架(Java Collections Framework,JCF)、而在\nPython中更是将容器类型的数据结构作为其基本数据类型、Go语言也有内建的容器和相应的标准库,\n本篇博客便是在总结各种容器使用及原理的基础上,对Java、Python中相同的类型的容器做一个横向的\n对比,以便于日后的总结和复习\n2.Java集合框架简介 (1)泛型的机制\nJava中的容器就是可以容纳其他Java对象的对象,且Java容器中只能存放对象,对于一些基本的数据\n 类型(比如int、long、float、double等),需要将其包装成对象类型之后(Interger、Long、Float、Double\n等)才能放到容器里,很多时候拆包装和解包装使能够自动完成的\n Java容器能够容纳任何类型的对象,表面上是通过泛型机制完成的。事实上,所有容器的内部存放的都\n 是Object类的对象,所有的对象都是Object类型的子类。泛型机制只是简化了编程,由编译器自动帮助我们\n完成了强制类型的转换而已,示例代码如下\nArrayList\u0026lt;String\u0026gt; list = new ArrayList\u0026lt;String\u0026gt;(); //参数化类型 list.add(new String(\u0026quot;csuyzz\u0026quot;)); String name = list,get(0); //容器中存放的Object类的对象隐式转换成为String类型的对象 此外,Java里的对象都在堆上,且对象只能通过引用(reference)来访问,容器里存放的其实是对\n 象的引用而不是对象的本身\n (2)接口和实现(Interfaces and Implementations)\n在Java 的集合框架中共定义了14种容器的接口,关系图如下所示,Map接口没有继承自Collection的\n 接口,因为Map接口是关联式的容器而不是集合,但也可以从Map转换到Coolection;Stack已经被Deque\n所取代\n\n 接口的实现如下表所示\n ImplementationsHash TableResizable ArrayBalanced TreeLinked ListHash Table + Linked ListInterfacesSetHashSetTreeSetLinkedHashSetListArrayListLinkedListDequeArrayDequeLinkedListMapHashMapTreeMapLinkedHashMap \u0026gt; (3) 迭代器\n 迭代器为我们提供了遍历容器中元素的方法,它只能通过容器本身来得到,迭代器是我们更加方便地去操作\n 容器,下面是示例:\nArrayList\u0026lt;String\u0026gt; list = new ArrayList\u0026lt;String\u0026gt;(); list.add(\u0026quot;csuyzz\u0026quot;); list.add(\u0026quot;csu001\u0026quot;); for(String name : list){ System.out.println(name); //使用迭代器进行输出数据 } 二、List 1.Java中的List容器 (1)ArrayList\nArrayList 实现了List接口,元素存放的顺序与放进去的顺序相同,允许放入null元素,底层通过可变数\n 组来实现。每个ArrayList都有一个容量(capacity)表示底层数组的实际大小,容器内存储的元素的个数\n不能大于当前容量;当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的容量。而且这里\n的数组是一个Object的数组,可以容纳任何类型的对象\n ArrayList与Vector的最大不同就是ArrayList没有实现同步,当多个线程并发访问时,用户可以手动进行\n 同步,也可以使用Vector来进行替代\n\n 对于一些常用的方法,比如size()、isEmpty()、get()、set()时间复杂度为O(1)、add()方法的时间开销\n 与插入的位置有关,其余均为线性时间\n set()方法就是对数组指定的位置赋值,源码实现如下\n //Object[] elementData public E set(int index,E element){ rangeCheck(index); //先检查下标是否越界 E oldValue = elementData(index); elementData[index] = element; //赋值到指定的位置,赋值的仅仅是引用 return oldValue; } get()方法,获取指定位置的元素,但是由于底层数组是Object[],在得到元素后需要进行类型转换\n public E get(int index){ rangeCheck(index); return (E) elementData[index]; //类型的转换 } add()方法,在尾部追加元素 add (E e),add(int index,E e)在指定位置插入元素,这两种方法在\n 执行时都有可能导致容量的不足,实际在添加元素之前,都要进行剩余空间检查,需要时通过grow()方法进\n行自动扩容;注意add(int index,E e)需要先对元素进行移动,然后完成插入操作,该方法有着线性的时间\n复杂度\n remove() 方法有两种,remove( int index )删除指定位置的元素,remove(Object o)删除第一个满足\n o.equals(elementData[index])的元素,删除之后需要将删除点之后的元素向前移动一个位置,所以时间\n复杂度是O(n)的,但是为了让GC起作用,必须显示的为最后一个位置赋null值\npublic E remove(int index){ rangeCheck(index); //下标越界检查 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if(numMoved \u0026gt; 0) System.arraycopy(elementData,index+1,elementData,index,numMoved); elementData[--size] = null; //清除最后一个位置的引用,使GC起作用 return oldValue; } (2) LinkedList\nLinkedList同时实现了List接口和Deque接口,它可以充当顺序容器、队列和栈,但是使用栈或者队列的\n 首选是ArrayDeque,LinkedList数据的存储方式如下图所示\n LinkedList底层通过双向链表来实现,双向链表的每个节点使用内部类Node表示,LinkedList通过first\n 和last引用分别指向链表的第一个和最后一个元素,不存在哑元,当链表为空的时候first和last都指向null,\nLinkedList没有实现同步\n//Node内部类 private static class Node\u0026lt;E\u0026gt;{ E item; Node\u0026lt;E\u0026gt; next; Node\u0026lt;E\u0026gt; prev; Node(Node\u0026lt;E\u0026gt; prev,E element,Node\u0026lt;E\u0026gt; next){ this.item = element; this.next = next; this.prev = prev; } } LinkedList中所有跟下标有关的操作都是O(n)的时间复杂度,在链表的头部和末尾插入和删除元素时都\n 常数时间\n add()方法有两种,add(E e),在链表的末尾插入元素,常数时间;add(int index,E element),在指定\n 的下标处插入元素\n//add(E e)方法的实现 public boolean add(E e){ final Node\u0026lt;E\u0026gt; l = last; //将原来最后一个节点赋给l final Node\u0026lt;E\u0026gt; newNode = new Node\u0026lt;\u0026gt;(l,e,null); //新建节点指向原来最后的节点 last = newNode; //新建节点化成最后一个节点 if(l == null) first = newNode; //原来的链表为空,这是插入的第一个元素 else l.next = newNode; //原来最后的节点指向新的最后的节点 size++; //链表的大小增加 return true; //返回插入成功 } //add(int index,E element)方法的实现 public void add(int index,E element){ checkPositionIndex(index); //判断元素的下标是否在范围内 0 - size if(index == size) //插入的位置是末尾包括列表为空的情况 add(element); else{ Node\u0026lt;E\u0026gt; target = node(index); //先根据index找到要插入的位置节点 final Node\u0026lt;E\u0026gt; pred = target.prev; final Node\u0026lt;E\u0026gt; newNode = new Node\u0026lt;\u0026gt;(pred,element,target); target.prev = newNode; if(pred == null) //插入首部节点 first = newNode; else pred.next = newNode; size++; } } remove() 方法:删除跟指定元素相等的第一个元素remove(Obiect o),remove(int index)删除指定下标\n 的元素,时间复杂度均为O(n)\n\n//unlink删除一个Node E unlink(Node\u0026lt;E\u0026gt; x){ final E element = x.item; final Node\u0026lt;E\u0026gt; next = x.next; final Node\u0026lt;E\u0026gt; prev = x.prev; if(prev==null){ first=next; //删除的是第一个元素 }else if(next==null){ last = prev; //删除的是最后一个元素 }else{ prev.next = next; next.prev = prev; x.prev = null; x.next = null; } x.item = null ; //删除的节点赋null值使其可以被GC清理掉 size--; return element; } get() 方法,得到指定下标处元素的引用\n public E get(int index){ checkElementIndex(index); //验证下标是否合法 return node(index).item; } set()方法:set(int index,E element)将指定下标出的元素修改为指定值\n public E set(int index,E element){ checkElementIndex(index); Node\u0026lt;E\u0026gt; x = node(index); E oldVal = x.item; x.item = element; //替换成新值 return oldVal; } 2.Python中的list Python中list(列表)是其最基本的数据结构,也可以理解为List容器是Python内置的,不像Java那样专门\n 去写一套集合框架,list底层实现的原理和Java中的ArrayList是非常相似的,这里就不再过多阐述\n 值得一提的是Python中也有自己的数据结构与工具库,pydu(python data structures and utils),在这个\n 工具库中给出了python本身list所不具有的比较强大的功能,以后用到的时候可以查阅\n三、栈和队列 1.Java 中的栈和队列 在使用栈和队列时,Java比较推荐使用ArrayDeque,当然也可以使用LinkedList。那么ArrayDeque是如何\n 既能实现栈,又能实现队列的呢?Deque的含义是 “Double ended queue”,即双端队列,对于双端队列的操\n作其实是非常简单的,无非就是对容器的两端进行添加、删除或者查看\n ArrayDeque底层是通过数组进行实现的,此外为了满足可以在数组两端插入或者删除元素时,该数组还\n 必须是循环的,即循环数组。这种的数组的特点就是,任何一点都可能被看做起点或者是终点,head指向首\n端第一个有效元素,tail指向尾端第一个可以插入元素的空位;head不一定总等于0,tail也不一定总比head大。\nArrayDeque是非线程安全的\n addFirst() :addFirst(E e)的作用是在deque的首端插入元素的,也就是在head前面插入元素,在\n 插入的过程中实际要考虑到空间是否够用,下标是否会越界等问题\npublic void addFirst(E e){ if(e==null) //deque中不允许放入null throw new NullPointerException(); elements[head = (head - 1)\u0026amp;(elements.length - 1)] = e;//当head为0是插入到最后,解决了数组越界问题 if(head == tail) //判断空间是否足够使用 doubleCapacity(); //扩容 申请一个原数组两倍的数组,在将现有数组复制过去 } //扩容函数 doubleCapacity() private void doubleCapacity(){ assert head == tail; int p = head; int n = elements.length; int r = n - p; //head右边元素的个数 int newCapacity = n \u0026lt;\u0026lt; 1; //原空间的2倍 if (newCapacity \u0026lt; 0) throw new IllegalStateException(\u0026quot;Sorry,deque too big\u0026quot;); Object[] a = new Object[newCapacity]; System.arraycopy(elements,p,a,0,r); //将elements数组从p开始的r个元素放置到a数组中从0开始的地方 System.arraycopy(elements,0,a,r,r,p); elements = (E[])a; head = 0; //扩充完成后放置head和tail的节点 tail = n; } addLast(E e)函数在Deque插入元素,即在tail的位置插入元素,tail总是指向下一个可以插入的空位,\n 插入完成后检查空间,如果用光,则需要进行扩容\npublic void addLast(E e){ if (e == null) throw new NullPointerException(); elements[tail] = e; //赋值 tail = (tail + 1) % (elements.length-1); //获取tail的新坐标 if(tail == head) //下标越界 doubleCapacity();//扩容 } pollFirst() 删除并返回Deque的首端元素,也即是head位置处的元素\n public E pollFirst(){ E result = elements[head]; if(result == null) //此时deque的值为空 return null; elements[head] = null ;//手动赋null值,这样可以使GC工作 head = (head+1) \u0026amp; (elements.length -1); //下标越界处理 return result; } pollLast() 删除并返回Deque尾端的元素,也即是tail位置前面的元素\n public E pollLast(){ int t = (tail-1)\u0026amp;(elements.length - 1); //tail的上一个位置是最后一个元素 E result = elements[t]; if (result == null)//null为空则意味着deque为空 return null; elements[t] = null ; //GC tail = t; return result; } peekFirst() 的作用是返回但是不删除Deque首端的元素,也即是head位置处的元素\n public E peekFirst(){ return elements[head]; //如果队列为空,则会返回null } peekLast() 返回但不删除Deque尾端的元素,即tail位置前面的那个元素\n public E peekLast(){ return elements[(tail -1)\u0026amp; (elements.length - 1)]; } 2.Python中的栈和队列 与Java非常的类似,Python在栈和队列方面也有自己的轮子,collections模块中的deque也是一个双向\n 队列,它的使用与ArrayDeque非常相似,它的底层实现的原理与list是相同的\n#对于deque数据结构的示例操作 import collections douqueue = collections.deque() #创建一个双端队列 douqueue.append() #往右边添加一个元素 douqueue.appendleft() #往左边添加一个元素 douqueue.pop() #获取最右边的一个元素,并在队列中删除 douqueue.popleft() #获取最左边的一个元素 douqueue[0] #获取队列中最左边的第一个元素 douqueue[-1] #获取队列中最右边的第一个元素 #deque的这些操作足够我们去模拟栈和队列了 当然在实现栈时我们通过Python内置的list来进行实现的,在末尾插入和删除一个元素所使用的时间都是\n O(1)的,符合stack的要求,所以在实际的应用中完全可以将list当做栈来使用\nclass Stack(object): def __init__(self): #初始化将列表看为堆栈 self.stack = [] def push(self,value): #进栈 self.stack.append(value) def pop(self): #出栈 if self.stack: self.stack.pop() else: raise LookupError('stack is empty'); def is_empty(self): #栈为空的情况 return bool(self.stack) def top(self): return self.stack[-1] #取出目前stack中最新的元素 最后还想使用链表来实现一个最基本的队列,定义一个头结点,左边指向队列的开头,右边指向队列的\n 末尾,这样插入元素和取出元素时都是一个O(1)的操作\nclass Head(object): #头结点 def __init__(self): self.left = None #左边指向队列的开头节点 self.right = None #右边指向队列的结尾节点 class Node(object): #中间节点 def __init__(self,value): self.value = value self.next = None class Queue(object): def __init__(self): self.head = Head() #初始化节点 def enqueue(self,value): newnode = Node(value) if self.head.right: #队列中元素不为空的情况 self.head.next,self.head.right=newnode,newnode else: self.head.right,self.head.left = newnode,newnode def dequeue(self): #出队 if self.head.left and (self.head.left == self.head.right): #队列中有且仅有一个元素了 temp = self.head.left.value self.head.right,self.head.left = None,None return temp elif self.head.left and (self.head.left != self.head.right): #队列中不止一个元素 temp=self.head.left.value self.head.left = self.head.left.next return temp else: #队列为空 raise LookupError(\u0026quot;queue is empty\u0026quot;) def is_empty(self): return (True if self.head.left else False) def top(self): if self.head.left: return self.head.left.value else: raise LookupError(\u0026quot;queue is empty\u0026quot;) 四、Map 1.Java中的Map (1)HashMap\nHashMap实现了Map接口,允许放入key为null的元素,也允许插入value为null的元素,该容器不保证元\n 素的顺序,根据需要可能会对元素进行重新哈希,元素的顺序也会被重新打散,在不同的时间迭代同一个\nHashMap的顺序可能会不同,HashMap还尚未实现同步\n 根据对冲突的处理方式,哈希表有两种实现方式,一种是开放地址方式,一种是冲突链表方式,Java\n 中的HashMap采用的是冲突链表方式\n 从这张图中可以看出初始容量(inital capacity)和负载系数(load factor)这两个参数影响了HashMap的\n 性能,当entry的数量超过了capacity*load_factor时,容器会自动进行扩容并重新哈希。对于插入元素比较多的\n场景,将初始容量设大可以减少重新哈希的次数\n 将对象放入到HashMap时,有两个方法特别重要,hashCode()方法决定了对象会被放入到哪个bucket\n 中,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是同一个对象。当需要将自定义的对象\n放入HashMap时需要重写hashCode()方法和equals() 方法\n get(Object key)方法根据指定的key值返回对应的value,该方法调用了getEnery(Object key)得到\n 相应的entry,然后返回entry.getValue();算法的思想首先是通过哈希函数得到对应bucket的下标,然后\n一次遍历冲突链表,通过key.equals(k)方法来判断是否是要寻找的那个entry\n//getEntry方法的核心代码 final Entry\u0026lt;K,V\u0026gt; getEntry(Object key){ int hash = (key == null)? 0:hash(key); for(Entry\u0026lt;K,V\u0026gt; e = table[hash\u0026amp;(table.length-1)];e!=null;e=e.next){ //得到冲突的链表,依次进行遍历 Object k; if(e.hash == hash \u0026amp;\u0026amp; ((k = e.key)==key || (key!=null \u0026amp;\u0026amp; key.equals(k)))) return e; } return null; } put(K key ,V value) 方法是将指定的key,value对添加到map里,该方法首先会对map做一次查找,看\n 是否包含该元组,如果已经包含则直接返回,查找过程类似于getEntry方法;如果没有找到,则会通过addEntry\n(int hash,K key,V value,int bucketIndex)方法插入新的Entry\npublic void addEntry(int hash,K key,V value,int bucketIndex){ if((size\u0026gt;=threshold) \u0026amp;\u0026amp; (null !=table[bucketIndex])){ resize(2*table.length); //自动扩容并重新哈希 hash = (null!=key)? hash(key):0; bucketIndex = hash \u0026amp; (table.length-1); } //在冲突链表头部插入新的Index Entry\u0026lt;K,V\u0026gt; e = table[bucketIndex]; table[bucketIndex] = new Entry\u0026lt;\u0026gt;(hash,key,value,e); size++; } remove(Object key)方法删除Key值对应的entry,核心算法在removeEntryForKey(Object key)实现的\n removeEntryForKey(Object key)方法首先会找到key值对应的entry,然后再删除该entry\nfinal Entry\u0026lt;K,V\u0026gt; removeEntryForKey(Object key){ int hash = (key!=null)? hash(key):0; int index = hash\u0026amp;(table.length - 1); Entry\u0026lt;K,V\u0026gt; prev = table[index]; //冲突的链表 Entry\u0026lt;K,V\u0026gt; e = prev; while(e!=null){ //遍历冲突的链表 Entry\u0026lt;K,V\u0026gt; next = e.next; Object k; if (e.hash == hash \u0026amp;\u0026amp;((k = e.key) == key || (key != null \u0026amp;\u0026amp; key.equals(k)))) { //找到要删除的entry modCount++; size--; if (prev == e) table[i] = next;//删除的是冲突链表的第一个entry else prev.next = next; return e; } prev = e; e =next; } return e; } (2) TreeMap\nJava中的TreeMap会按照key的大小顺序进行排序,key的大小比较可以通过其本身的自然顺序,也可以\n 通过构造时传入的比较器;TreeMap底层通过红黑树来实现,其containskey()、get()、put()和remove()都\n有着log(n)的时间复杂度,TreeMap是非同步的\n 先简单的介绍下红黑树吧,红黑树可以说是二叉搜索树的改良版,目的就是解决后者退化成单链表后搜索\n 的时间复杂度由O(N)变成O(logN)的问题,它是一种近似的二叉查找树,能够确保任何一个节点的左右子\n树的高度差不会超过二者中较低那个的一倍\n 具体来看,红黑树的特点如下:\n 每个节点只能是红色或者黑色的 根节点必须是黑色的 红色节点的孩子和父亲都不能是红色 对于每个节点,从该点至叶子节点的任何路径都含有相同颜色的黑色节点 在树的结构发生改变时(插入或者删除操作),会破坏上述要求,需要经过调整才能使得树重新满足\n 约束的条件,调整一方面是颜色调整,另一方面是结构的调整,结构调整包括左旋和右旋,下面对一些\n常见的操作写出代码\n 左旋的过程是将x的右子树绕逆时针旋转,使得x的右子树成为x的父节点\n //Rotate Left左旋函数代码 以上图所示为例进行说明 private void rotateLeft(Entry\u0026lt;K,V\u0026gt; p){ if(p!=null){ Entry\u0026lt;K,V\u0026gt; r = p.right; //假设p节点为x节点,也即是待旋转的节点,r节点就是y节点 if(r.left!=null) //修改y节点左节点的引用关系 p.right = r.left; //将y节点的左节点(B子树)改为x的右节点 r.left.parent = p; //将y节点的左节点(B子树)的父节点设置成为x节点 //修改y节点与x节点父节点的引用关系 r.parent = p.parent; //将原来x节点的父节点修改成y节点的父节点 //判断x是其父节点的左节点还是右节点 if(p.parent ==null) //p节点是根节点的情况 root = r; else if(p.parent.left == p) p.parent.left = r; else p.parent.right = r; //修改x节点和y节点间的应用关系 r.left = p; p.parent = r; } } 右旋的过程是将x的左子树绕x顺时针旋转,使得x的左子树成为x的父节点\n //右旋的过程代码 Rotate Right 原理与左旋过程相似 private void rotateRight(Entry\u0026lt;K,V\u0026gt; p){ if(p!=null){ Entry\u0026lt;K,V\u0026gt; l = p.left; if(l.right !=null) p.left = l.right; l.rigth.parent = p; l.parent = p.parent; if(p.parent == null) root = l; else if(p.parent.left == p) p.parent.left = l; else p.parent.right = l; l.right = p; p.parent = l; } } 寻找节点后继,对于一颗二叉查找树,其后继(树中比大于t的最小的那个元素),一般可以通过下面的\n 方法来找到:t的右子树不空,则t的后继结点是其右子树中最小的那个元素;t的右孩子为空,则t的后继是其\n第一个向走的祖先\n//寻找节点后继函数successor() public static TreeMap.Entry\u0026lt;K,V\u0026gt; successor(Entry\u0026lt;K,V\u0026gt; t){ if(t==null) return null; else if(t.right !=null){ //t的右子树不为空,则t的后继是其右子树中最小的那个元素 Entry\u0026lt;K,V\u0026gt; p =t.right; while(p.left !=null) p=p.left; return p; }else{ //t的右孩子为空,则t的后继是其第一个向左走的祖先 Entry\u0026lt;K,V\u0026gt; p = t.parent; //获取当前节点的父节点 Entry\u0026lt;K,v\u0026gt; ch = t ; //当前节点 while(p!=null \u0026amp;\u0026amp; ch == p.right){ //当前节点是其父节点的右节点 ch = p; p = p.parent; } return p; } } get(object key):根据指定的key值返回指定的value值,getEntry()是算法的核心,根据key的自然\n 顺序或者比较器的顺序对二叉查找树进行查找\n//核心算法代码 final Entry\u0026lt;K,V\u0026gt; getEntry(Object key){ if(key == null) throw new NullPointerException(); Comparable\u0026lt;? super K\u0026gt; k = (Comparable\u0026lt;? super K\u0026gt;) key; //使用元素的自然顺序 Entry\u0026lt;K,V\u0026gt; p = root; while(p!=null){ int cmp = k.compareTo(p.key); if(cmp\u0026lt;0) //向左找 p = p.left; else if (cmp\u0026gt;0) //向右找 p = p.right; else return p; } return null } put(K key , V value) 方法是将key,value对添加到map里面,该方法在执行时会先对map做一次检查,看\n 是否包含该元祖,如果已经包含则直接返回,如果没有找到则会红黑树中插入新的Entry,插入时首先在红黑\n树上找到合适的位置,然后创建一个新的Entry并插入,新插入的节点一定是树的叶子,在插入完成后还需要进\n行调整,比如旋转或者改变某些节点的颜色\n remove(Object key)的作用是删除key值对应的Entry,该方法首先通过getEntry(Object key)方法找到\n key值对应的entry,然后调用deleteentry删除对应的entry;删除之后需要对红黑树进行调整。由于红黑树是\n一颗增强版的二叉查找树,删除操作非常相似,普通的二叉查找树有两种情况,一是删除点p的左右子树都\n为空(直接删除即可),或有一颗子树为空(只有一颗子树非空)、二是左右子树均不为空,这时可以使用\np的后继节点来代替p,然后使用情况一来删除s,此时s节点一定满足情况一\n 在具体的调整函数中,只有删除点BLACK的时候,才会触发调整函数,删除RED节点不会破坏红\n 黑树的任何性质,删除BLACK点会破坏规则4\n (3)LinkedHashMap\n从名字上就可以看出该容器是linked list 和HashMap的混合体,它同时满足二者的某些特性。具体来说\n LinkedHashMap是HashMap的直接子类,前者在后者的基础上采用双向链表的形式将所有entry连接起来,\n这样做可以使元素的迭代顺序跟插入顺序相同;此外,迭代LinkedHashMap时不需要像HashMap那样遍历整\n个Table,而只需要遍历header指向的双向链表即可,LinkedHashMap的迭代时间就只跟entry的个数有关,与\nTable的大小无关\n get(Object key)方法即为根据key值返回对应的value值,原理和HashMap相同\nput(K key,V key)方法将指定的key,value对添加到map中,但是这里的插入有两重含义,一是从\n table的角度来看,新的entry需要插入到对应的bucket里,当有哈希冲突时,采用头插法,将新的entry插\n入到冲突链表的头部;二是从header的角度来看,新的entry需要插入到双向链表的尾部\n remove(Object key)的作用就是删除key值对应entry,具体实现方法为removeEntryForKey(Object key)\n 实现的过程就是先找到key对应的entry在进行删除;删除时既要在hashtable的bucket中删除,也要从双向链表\n中删除\nfinal Entry\u0026lt;K,V\u0026gt; removeEntryForKey(Object key){ int hash = (key == null)? 0:hash(key); int index = hash\u0026amp;(table.length-1); Entry\u0026lt;K,V\u0026gt; prev = table[index]; //得到冲突链表 Entry\u0026lt;K,V\u0026gt; e = prev; while(e!=null){ //开始遍历冲突链表 Entry\u0026lt;K,V\u0026gt; next = e.next; Object k; if (e.hash == hash \u0026amp;\u0026amp;((k = e.key) == key || (key != null \u0026amp;\u0026amp; key.equals(k)))) { // 找到要删除的entry size--; // 1. 将e从对应bucket的冲突链表中删除 if (prev == e) table[i] = next; else prev.next = next; // 2. 将e从双向链表中删除 e.before.after = e.after; e.after.before = e.before; return e; } prev = e; e = next; } return e; 使用LinkedHashMap实现缓存\nLinkedHashMap可以很方便地实现一个FIFO替换策略的缓存,LinkedHashMap有一个子类方法可以告\n 诉Map是否要删除“最老”的Entry,所谓最老就是当前的Map中最早插入的Entry,如果方法返回true则最老\n的那个元素删除。每次插入新元素时,LinkedHashMap会自动询问那个子类方法是否删除,当元素个数超\n过一定数量时让那个子类方法返回true即可实现一个固定大小的FIFO策略的缓存\n//一个固定大小的FIFO替换策略的缓存实现 class FIFOCache\u0026lt;K,V\u0026gt; extends LinkedHashMap\u0026lt;K,V\u0026gt;{ private final int cacheSize; public FIFOCache(int cacheSize){ this.cacheSize = cacheSize; } //当Entry个数超过cacheSize时,删除最老的Entry @Override protected boolean removeEldestEntry(Map.Entry\u0026lt;K,V\u0026gt; eldest){ return size() \u0026gt; cacheSize; } } 五、Set集合 1.Java中的Set 在Java中,TreeSet和TreeMap、HashMap和HashSet、LinkedHashMap和LinkedHashSet都具有\n 相同的实现,前者仅仅是对后者做了一层包装\n2.Python中的Set 集合是Python中一个最基本的数据类型,在Cpython中,集合被实现为带有空值的字典,只有健才是实际的\n 集合元素,所以集合的底层是用哈希来实现的,它最大的特点就是其中的元素是唯一的,无序的。在元素顺序的\n重要性不如元素的唯一性和测试元素是否包含在集合中的效率时,set是最好的选择\n 在pydu模块中,set.OrderedSet(iterable = None)可以保持插入元素有序的集合\nPython中对于Set去重的底层实现原理: set的去重是通过两个函数 hash和eq结合实现的,当两个变量的\n 的哈希值不相同时,就认为这两个变量是不同的,当哈希值一样,而调用eq()方法时,返回值为true认为这\n两个变量是同一个,应该去除一个,返回False时,不去重\n六、优先队列 1.Java中的优先队列 PriorityQueue是一种的特殊的队列,即优先队列,优先队列的作用是保证每次取出的元素都是队列中权\n 值最小的或者权值最大的;优先队列又称为堆,堆又分为小顶堆和大顶堆,小顶堆即是每一个根节点都会小\n其左孩子和右孩子,而大顶堆即是每一个根节点都会大于其左孩子和右孩子\n Java中的优先队列即为PriorityQueue,它是通过完全二叉树来实现的小顶堆,底层是通过数组来进行存\n 储数据的,不允许放入null。元素大小的评判可以通过元素本身的自然顺序,也可以通过构造时传入的比较器;\n 从上面的这张图中可以看出,每个元素按照层序遍历的方式进行编号后,父子节点的编号之间存在\n 如下关系,所以使用来进行存储是非常方便的\n leftNo = parentNo * 2+1\n rightNo = parentNo*2 + 2\n parentNo = (nodeNo-1)/2\n PriorityQueue的peek()和element()的时间复杂度都是O(1)的,而add()、offer()、无参数\n 的remove()以及poll()方法的时间复杂度都是log(N)\n add()方法和offer()方法都是向优先队列中插入元素,只不过前者插入失败后抛出异常,后者返回\n false,二者的底层实现原理相同;插入元素破坏小顶堆的性质,需要进行调整,具体的调整的过程为从k指\n定的位置开始,将x逐层与当前点的parent进行比较并交换,知道满足x\u0026gt;=queue[parent]\n//offer(E e) 函数源码 public boolean offer(E e){ if(e == null) //不允许放入null元素 throw new NullPointerException(); ... int i = size; if(i\u0026gt;=queue.length) grow(i+1); //自动扩容 就是申请一个更大的数组,将原数组的元素复制过去 size +=1; if(i==0)//队列原来为空,这是插入的第一个元素 queue[0] = e; else siftUp(i,e); //调整函数 return true; } //siftUp()函数 该方法用于插入元素x并维持堆的特性 private void siftUp(int k,E x){ while(k\u0026gt;0){ int parent = (k-1) \u0026gt;\u0026gt; 1; //根据子节点求父节点下标 Object e = queue[parent]; if(comparator.compare(x,(E) e)\u0026gt;=0) break; queue[k] = e; k = parent; } queue[k] = x; } element ()和 peek()都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者区别\n 就是前者方法失败时抛出异常,后者返回null。根据小顶堆的性质,堆顶的那个元素就是最小的\n//peak() 函数源码 public E peek(){ if(size == 0) return null return (E) queue[0]; //0下标就是最小的那个 } remove()和poll()方法都是获取并删除队首元素,区别是当方法失败时,前者抛出异常,后者返回\n null,删除操作会改变队列的结构,为维护小顶堆的性质,需要进行调整,siftDown()方法是从k指定的位置\n开始,将x逐层向下与当前点的左右孩子中较小的那个进行交换,直到x小于或等于左右孩子中的任何一个为止\npublic E poll(){ if(size == 0) return null int s = --size; ... E result = (E) queue[0]; //0下标处的元素是最小的那个元素 E x = (E) queue[s]; //原队列中的最后一个元素 queue[s] = null; //使用最后一个元素替换0下标位置的元素 if(s!=0) siftDown(0,x); //使用siftDown()方法对堆进行调整 return result; } //siftDown()方法源码 private void siftDown(int k,E x){ int half = size\u0026gt;\u0026gt;1; while(k\u0026lt;half){ //首先找到左右孩子中较小的那个,记录到c中,并用child记录其下标 int child = (k\u0026lt;\u0026lt;1)+1; //child表示左节点 Object c = queue[child]; int right = child + 1; if (right \u0026lt; size \u0026amp;\u0026amp; comparator.compare((E) c, (E) queue[right]) \u0026gt; 0) c = queue[child = right]; //左节点的值大于右节点的值 if (comparator.compare(x, (E) c) \u0026lt;= 0) break; queue[k] = c; k = child; } queue[k] = x; } remove(Object o)方法用于删除队列中跟o相等的某一个元素,删除操作会改变队列的结构,需要进行调整,\n 具体可分为两种情况,删除的是最后一个元素,直接删即可,不需要进行调整;当删除的不是最后一个元素时\n从删除点开始以最后一个元素为参照调用一次siftDown()函数即可\n//remove(Object o) public boolean remove(Object o){ //通过遍历数组的方式找到第一个满足o.equals(queue[i])元素的下标 int index = indexof(o); //假设o存在于该队列中 if (index == -1) return false; //实际上不存在 int s = --size; //s为删除的最后一个元素的下标 if(s == i) //待删除的元素为最后一个元素 queue[i] = null else{ E moved = (E) queue[s]; queue[s] = null; siftDown(i,moved); //如果不是最后一个元素,找到最后一个元素调用siftDown()方法 } return true; } 2.Python中的优先队列 Python中提供了heapq模块,可以让我们很方便地堆的相关操作进行简化,常用的方法如下:\n heappush(heap,item)往堆中插入一条新的值 heappop(heap)从堆中弹出最小值 heapreplace(heap,item)从堆中弹出最小值,并向堆中插入item heapify(x) 以线性时间将列表转化成一个堆\n#有关堆的操作函数 import heapq #第一种创建堆的方法 nums = [2,1,7,53,32,44,89,4] heap = [] for num in nums: heapq.heappush(heap,num) #将原列表中的元素加入堆 print(heap[0]) #查看堆首的元素,底层是一个列表 print(heap) heapq.heapreplace(heap,13) #删除队首的元素并添加一个新的元素 print(heap) #第二种创建堆的方法 nums2 = [5,6,7,8,3,3,53,89] heapq.heapify(nums2) #将一个列表初始化成一个堆 print([heapq.heappop(nums2) for _ in range(len(nums2))]) #一行代码实现堆排序,时间复杂度为O(nlogn) #如何去建立一个大根堆 #如何建立一个大根堆 a = [] for i in [4,5,2,2,55,64,2,255,6]: heapq.heappush(a,-i) print(list(map(lambda x:-x,a))) #heapq里面没有直接提供建立大根堆的方法,在每次push时给元素加一个负号,此时最小值就变成最大值了 #实际上的最大值就处于堆顶了,返回时在取负即可 ","permalink":"https://sin-coder.github.io/datastructure/container/","tags":["数据结构","Java","Python"],"title":"数据结构之容器"},{"categories":null,"contents":" 反转链表专题 No.1 问题描述 输入一个链表,从尾到头的顺序返回一个ArrayList\n 解题思路 1.使用递归法 //1.解法一 public ArrayList\u0026lt;Integer\u0026gt; printListReverse(ListNode listnode){ ArrayList\u0026lt;Integer\u0026gt; arrayList = new ArrayList\u0026lt;Integer\u0026gt;(); if(listnode != null){ arrayList.addAll(printListReverse(listnode.next)); arrayList.add(listnode.val); } return arrayList; } //分析:时间复杂度为O(n),相当于将每个元素均遍历了一遍 //空间复杂度为O(n^2),创建的列表的合计元素个数1+2+3+4+n-1 //拓展内容:ArrayList中的addAll(Collection\u0026lt;? extends E\u0026gt; c) 方法 按照指定collection容器返回元素的 //顺序将所有的元素添加到列表的尾部 2.使用栈(推荐解法) //Java实现 public ArrayList\u0026lt;Integer\u0026gt; printListReverse(ListNode listnode){ ArrayDeque\u0026lt;Integer\u0026gt; stack = ArrayDeque\u0026lt;Integer\u0026gt;(); while(listnode != null){ stack.addFirst(listnode.val); listnode = listnode.next; } ArrayList\u0026lt;Integer\u0026gt; arrayList = new ArrayList\u0026lt;Integer\u0026gt;(); while(stack.pollFirst()) arrayList.add(stack.pollFirst()) return arrayList; } //Java中推荐使用ArrayDeque来实现一个栈 //当压栈时,调用addFirst()方法; 出栈时调用pollFirst()方法 //分析:时间复杂度O(n),空间复杂度也是O(n) #基于Python的实现 class Solution: def printListReverse(self,listnode): if not listnode: return [] result = [] while listnode: result.insert(0,listnode.val) listnode = listnode.next return result; #Python中推荐使用list列表来实现堆栈 #空间复杂度为O(n),时间复杂度为O(n^2) 其实在Java的实现中实际的空间复杂度为O(2n),Python中的空间复杂度为O(n);但是Python中\n 当向列表的头部插入元素时,时间复杂度为O(n),总共为O(n^2),所以可以看出Java实现中是以多出\nO(n)的空间复杂度换取时间复杂度的降低\n3.使用头插法 创建单链表的方法有两种,分别是头插法和尾插法\n头插法就是按节点的逆序方法逐渐将节点插入到链表的头部,尾插法就是按照节点的顺序逐渐将节点插入\n 到链表的尾部;头插法创建的链表是逆序的,这也正符合本题中的要求\n 头插法的算法描述:从一个空表开始,重复读入数据,生成新节点,将读入数据存放到新结点的数据域中\n 然后将新结点插入到当前链表的表头节点之后\n 尾插法是从一个空表开始,重复读入数据,生成新节点,将读入数据存放到新节点的数据域中,然后将\n 新节点插入到当前的末尾节点之后\n//Java使用头插法构建逆序链表 public ArrayList\u0026lt;Integer\u0026gt; printListReverse(ListNode listnode){ ListNode head = new ListNode(-1); while(listNode != null){ ListNode newNode = listnode.next; listnode.next = head.next head.next = listnode; listnode = newNode; } //构建ArrayList ArrayList\u0026lt;Integer\u0026gt; arrayList = new ArrayList\u0026lt;Integer\u0026gt;(); head = head.next; while(head!=null){ arrayList.add(head.val); head = head.next; } return arrayList; } //分析:时间复杂度为O(n),空间复杂度也是O(n) No.2 问题描述 反转一个链表、反转一个链表的前N个元素、反转一个链表的部分元素\n //递归实现单链表的反转 代码实现 //输入一个节点head,将以head为起点的链表反转,并返回反转之后的头节点 public ListNode reverse(ListNode head){ if(head == null) return head; if(head.next == null) return head; //链表只有一个节点时,反转也只是它自己 ListNode last = reverse(head.next); head.next.next = head; head.next = null; return last; } //递归实现反转一个链表的一部分 private ListNode successor = null; //后驱节点 public ListNode reverseN(ListNode head, int n){ if(n==1){ successor = head.next; return head; } ListNode last = reverseN(head.next,n-1); //让反转之后的节点和后面的节点连接起来 head.next.next = head; head.next = successor; return last; } public ListNode reverseBetween(ListNode head,int m,int n){ if(m == 1){ return reverseN(head,n); } //将反转后的头结点赋值成当前节点的下个节点 head.next = reverseBetween(head.next,m-1,n-1); return head; } ","permalink":"https://sin-coder.github.io/leetcode/printlistreverse/","tags":["数据结构","leetcode"],"title":"从尾到头打印链表"},{"categories":null,"contents":" 常见数据结构的定义 一、单链表(singly-linked list) 1.Java public class ListNode{ int val; ListNode next; ListNode(int x) { val = x; next = null; } } 2.Python class ListNode: def __init__(self,x): self.val = x self.next = Node 3.C struct ListNode{ int val; struct ListNode *next; }; 4.Go type ListNode struct{ Val int Next *ListNode } 二、二叉树(Binary Tree Node) 1.Java //Definition for a binary tree node public class TreeNode{ int val; TreeNode left; TreeNode right; TreeNode(int x){val = x;} } 2.python class TreeNode: def __init__(self,x): self.val = x self.left = None self.right = None 3.C struct TreeNode{ int val; struct TreeNode *left; struct TreeNode *right; }; 4.Go type TreeNode struct{ Val int Left *TreeNode Right *TreeNode } 三、N叉树 1.Java //Definition for a Node class Node{ public int val; public List\u0026lt;Node\u0026gt; children; public Node(){} public Node(int _val){ val = _val; } public Node(int _val,List\u0026lt;Node\u0026gt; _children){ val = _val; children = _children; } } 2.Python class Node: def __init__(self,val = None,children = None): self.val = val self.children = children ","permalink":"https://sin-coder.github.io/datastructure/defindatastructure/","tags":["数据结构","Java","Python"],"title":"常见数据结构的定义"},{"categories":null,"contents":" 环形链表专题 一、问题1 给定一个链表,判断一个链表中是否有环\n 1.算法思路 (1)判断是否存在重复节点\n通过检查一个节点此前是否被访问来判断是否为环形链表,可以使用哈希表来存储访问过的节点\n(2)快慢指针\n通过快慢指针遍历链表,慢指针每次移动一步,而快指针每次移动两步;如果链表中不存在环,最终快\n 指针将会最先达到尾部,此时返回Fasle即可;如果链表中存在环,最终快慢指针一定会相遇\n2.代码实现 //元素判重法 public boolean hasCycle(ListNode head) { Set\u0026lt;ListNode\u0026gt; visited = new HashSet\u0026lt;ListNode\u0026gt;(); while (head != null) { if (visited.contains(head)) { return true; } else { visited.add(head); } head = head.next; } return false; } //分析,时间复杂度是O(n),对链表中的每个元素最多访问一次,向哈希表中添加一个元素为O(1) //空间复杂度为O(n),最多将链表中的所有元素均添加到哈希表中 //快慢指针法 public boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } slow = slow.next; fast = fast.next.next; } return true; } //分析:空间复杂度是O(1),只使用了快慢指针两个节点 //时间复杂度O(n),实际上快慢指针算法的时间复杂度要大于等于判重算法 二、问题2 给定一个链表,返回链表开始入环的第一个节点,如果链表无环,则返回null\n 1.算法思路 (1)判重法找出入口节点\n通过检查一个节点此前是否被访问来判断该点是否为环的入口点,可以使用哈希表来存储访问过的节点\n(2)快慢指针\n第一步时通过快慢指针遍历链表,慢指针每次移动一步,而快指针每次移动两步,先判断此单链表中是\n 否有环,无环则直接返回空值,有环就返回环的入口节点;\n 第二步是让快指针重新从头结点出发,和慢指针(此时指向相遇节点)同时走,每次走一步,当再相遇\n 时的节点就是环的入口节点,具体原理如下图\n (3)引用计数解法\n链表中环的入口节点在程序中恰好被引用5次\n 2.代码实现 #快慢指针解题 def detectCycle(self, head): fast, slow = head, head #快慢节点进行初始化为头结点 while True: if not (fast and fast.next): return None #链表中是没有环的 fast, slow = fast.next.next, slow.next if fast == slow: #第一个相遇节点 break fast = head #将快指针初始化初始节点 while fast != slow: fast, slow = fast.next, slow.next #快慢指针再次相遇的节点就是环的入口节点 return fast #时间复杂度O(n)、空间复杂度O(1) #引用计数解题 def detectCycle(self, head: ListNode) -\u0026gt; ListNode: if not head: return None while head.next: if sys.getrefcount(head) \u0026gt;= 5: return head head = head.next return None #分析:在遍历链表的过程中目标节点(环的入口节点)被其两个前驱节点(head.next)分别引用一次 #被当前节点引用一次(head) #在当做参数传入函数时被引用一次,在函数内部又被引用一次,总共5次 #算法的时间复杂度O(n)、空间复杂度O(1) 三、双指针的其他常见问题 输入一个链表,输出该链表中倒数第K个节点\n public ListNode getKthFromEnd(ListNode head, int k) { if(head == null || k == 0){ return null; } ListNode slow = head,fast = head; for(int i = 1;i\u0026lt;k;i++){ fast = fast.next; } while(fast.next != null){ fast = fast.next; slow = slow.next; } return slow; } //分析:快、慢两个指针,快指针先走k-1步,然后快慢指针同时走;当快指针走到结尾时 //此时慢指针指向的节点就是倒数第K个节点 //时间复杂度O(n)、空间复杂度O(1) ","permalink":"https://sin-coder.github.io/leetcode/circlelist/","tags":["数据结构","leetcode"],"title":"环形链表"},{"categories":null,"contents":" 什么是死锁? 一、死锁概念的介绍 以两个进程为例,每个进程正在申请的资源恰好是其他进程正在占用的资源; 当然这里的进程数也有\n 可能是多个。但最终都是形成一个资源的依赖环\n 简述:多个进程由于互相等待对方持有的资源而造成的谁都无法执行的情况\n 二、死锁的必要条件 互斥:系统资源之间是互斥使用的,一旦某个进程占用,其他进程便无法使用\n 占有并等待:一个进程占有了一些资源,但是又不去释放,同时再去申请其他的资源\n 非抢占:每个进程所拥有的资源必须不能被其他进程所抢占\n 循环等待:进程之间各自占有的资源和互相申请的资源形成了环路的等待\n 三、死锁常用的处理方法 1、死锁预防 (1)简述:\n破坏死锁形成的必要条件之一,可以通过限制如何申请这些资源的方法来预防死锁\n(2)方法\n 基于互斥条件来预防死锁:资源可以进行共享\n 基于抢占的解决方案:如果一个进程占有资源并申请另一个不能立即分配的资源,则该进程所占用的资源\n 即可被抢占\n 基于占有并等待的解决方案:一个进程在申请其他资源之前必须要释放掉自己已有的资源\n 基于循环等待的解决方案:对所有的资源进行完全排序,且要求每个进程按递增的顺序来申请资源,这样\n 就不会出现环路的等待\n 2、死锁避免 (1)简述\n检测每个资源的请求,如果造成死锁就立刻拒绝\n(2)银行家算法\n 安全状态:如果系统中的所有进程存在一个可完成的执行序列P1,P2,P3,………Pn,则称系统处于安 全状态\n 使用特定算法判断是否存在一个执行序列,当按照这种执行序列执行时,不会产生死锁\n 银行家算法的执行方法:进程在执行的时候主要关注三种类型的资源:进程本身所占用的资源,进程需要\n 申请的资源,系统中剩余的资源申请一个work变量(表示系统剩余的资源),need变量(系统正在申请\n的资源),allocate(已经分配给进程的资源)按照特定的序列执行,在每个序列执行时,判断当前剩余资\n源是否能够满足申请资源,如果能,则将剩余资源减去申请资源在加上释放资源。但是时间复杂度:\nT(n)=O(mn^2),执行的代价非常大\n 3、死锁检测和恢复 (1)简述\n检测到死锁出现之后,让一些进程进行回滚,让出一部分资源 恢复非常不容易,进程造成的改变很\n 难恢复\n (2)死锁的恢复方法\n 简单地终止一个或多个进程以打破循环等待,终止所有死锁进程,每次只终止一个进程直到取消死锁循环\n 从一个或者多个死锁进程抢占一个或多个资源,但是要考虑到 选择一个牺牲品、进行回滚、饥饿(不能\n 总是选择同一个牺牲品)\n 4、死锁忽略 忽略发生的死锁,通常用在个人的PC机上,进行重启即可,效率比较高\n ","permalink":"https://sin-coder.github.io/os/deadlock/","tags":["操作系统"],"title":"什么是死锁?"},{"categories":null,"contents":" CDN技术简介 一、概述 1.问题背景 不同地理区域的用户访问同一个网站时会产生高延迟,导致用户的访问速度较慢,对个别服务器造成的\n 压力也比较大,网站的稳定性和安全性也不是很高,CDN的出现就是用来解决这一问题的\n2.什么是CDN CDN即Content Delivery Network,内容分发网络,具体内容为将源站内容发布到最接近用户的边缘节点,\n 使用户可就取得所需内容,提高用户访问的响应速度和成功率。解决了因分布、带宽、服务器能力带来的访\n问延迟高的问题,提供了一系列加速解决方案\n3.CDN的应用场景 网站服务的客户群体从独立的区域扩张到了全国范围,而自身服务器不足以覆盖全网用户,导致部分地区用 户访问网站速度变慢,到达率不高\n 网站已经实现了静动态资源分离,且静态资源服务器的能力已经达到极限,需要增加服务器硬件设才能够 解决问题的\n 网站频繁遭到DDOS攻击,CC攻击,DNS劫持,导致用户体验差,网络阻塞,无法提供正常服务 当用户 无访问某一个节点时,CDN会给用户提供更多的节点以供访问\n 当网站用户跨多个ISP(电信、联通、移动、铁通、长城),而自身的服务器只在其中一个机房的减少因 运营商通道阻塞而导致的访问失败\n 二、CDN的工作原理 1.CDN的工作过程 步骤一:将内容推送到边缘的节点上,以此产生一个副本,(WEB原始服务器推送到各CDN镜像服务器) 原始服务器上的内容拷贝到其他镜像服务器上\n 步骤二:引导用户就近进行访问(DNS解析的原理) 2.CDN的加速过程(智能DNS解析) 用户在浏览器中输入域名后,请求DNS服务器解析该域名\n DNS服务器并不能直接解析到该域名所对应的IP地址,而是将该请求发送给智能DNS服务器\n (网站的服务商需要将原来的解析地址转换到智能DNS服务器的IP)\n 智能DNS服务器(GSLB调度系统)判断用户访问离某区域最近,就返回该区域服务器的IP地址 (智能DNS服务器是CDN加速服务商所提供的服务器)\n 用户拿到IP地址后即可访问该区域的服务器 3.CDN底层技术的实现 (1)路由转发技术\n路由转发可以看做是负载均衡技术的一种\n本地负载均衡是指对本地的服务器群做负载均衡,具体实现上主要有两种方法,一个是通过ping来进\n 行主动探测(简单粗暴)、二是通过协议的交互来进行检测(内容非常的具体)\n 全局负载均衡设备:对分别放置在不同的地理位置,有不同网络结构的服务器集群间做负载均衡实现\n 方式:一是根据IP地址库投票匹配、二,CDN主动探测目标的最近距离,智能DNS向各个节点发送探测\n指令,CDN就是负载均衡分类中的全局负载均衡\n (2)内容分发技术\n主动推送(PUSH)和被动获取(PULL)\n(3)内容存储技术\n主流的存储结构主要有以下三种,其中最为常见使用的是NAS\n 直连式存储(DAS,Direct Attached Storage)\n 存储区域网络(SAN: Storage Area Network)\n 网络接入存储(NAS: Network Attached Storage)\n (4)内容管理技术\n主要分为资源管理系统和安全管理系统的内容\n 三、CDN的分类 1.按内容区分 网页加速 CDN服务器主要是加速静态资源的内容,静态资源(HTML,CSS,图片,JS)与数据库是没有太大关系\n将静态资源拷贝到CDN边缘缓存服务器,用户即可在该服务器上获取静态资源,提高访问速度\n 流媒体加速 CDN对流媒体文件的加速恰恰是加速这些切片后的视频文件(这些文件经过切片后不会产生任何变化)\n分片处理的过程如下:\n 大文件加速 这些文件通常是固定不变的,非常适合放在CDN的服务器上\n 应用协议加速(下载工具) 用户直接从目标文件存储器下载数据时通常会受到目标服务器网络带宽的限制 而使用BT下载工具后,\n 该工具可能已将待下载的文件下载到各地的代理服务器中,用户只需要直接从代理服务器下载即可,而\n代理服务器的带宽往往比原始服务器的带宽大很多;当代理服务器中找不到目标文件时,使用多台服务\n器会同时从文件存储服务器并同时传递给用户\n 2.按推送类型区分 主动获取: WEB原始服务器将内容分发到各服务器节点,然后智能DNS服务器引导至就近的节点\n 被动获取:智能DNS服务器引导到就近节点后,节点发现本地没有对应的数据,则会向原站请求该数据原\n 站将数据返回给镜像服务器,镜像服务器再讲数据返回给对应的用户第一个用户访问时是没有\n任何加速的效果的,但是后面的用户就可以感受到\n ","permalink":"https://sin-coder.github.io/network/cdn/","tags":["计算机网络","CDN"],"title":"CDN技术简介"},{"categories":null,"contents":" 端口镜像和链路聚合 一、端口镜像 1.背景需求 监控交换机特定端口的入站或出战报文,流量监测和故障定位\n 2.镜像的分类 (1)基于端口的镜像\n指定端口的所有数据复制到指定的端口,交换机可以指定入站和出站;以太网交换机支持多对一的镜\n 像,即是将多个端口的报文复制到某一个监控端口上\n 本地端口镜像:监控主机和观察端口进行直接相连。\n远程端口镜像 : 监控主机和观察端口之间通过二层网络或三层网络相联\n 二层端口镜像:交换机将镜像端口的报文封装成VLAN、然后通过观察端口将该报文在此VLAN中进行广播\n三层端口镜像:GRE报文头来封装和解封装镜像报文,穿透三层网络\n (2)基于流的镜像\n只是将匹配访问控制列表的业务流量复制到指定的监控端口。具体可分为流镜像到端口和流镜像到CPU\n 二、链路聚合 1.问题背景 网络中某些链路承载的流量非常大,链路存在带宽瓶颈;链路存在端口故障 。而链路聚合就是将多条\n 以太网链路进行捆绑,链路冗余、负载分担来解决这些问题的\n2.工作模式 (1)手工负载分担模式\n允许在聚合组中手工加入多个成员接口,所有接口均处于转发状态,分担负载的流量。Eth-Trunk的\n 创建、成员接口的加入都需要手工配置来完成,没有LACP协议报文的参与,通常运用在对端设备不支\n持LACP协议的情况之下 该工作模式下的所有接口均处于转发状态\n (2)静态LACP\nM:N模式 :M条链路处于活动状态 N条线路非活动状态作为备份链路;当M条链路中出现故障后,系\n 统会从N条链路中选择优先级较高的链路接替出现故障的链路同时实现链路负载分担和链路冗余备份的功\n能 。利用LACP协议进行聚合参数的协商、确定活动的接口和非活动的接口的链路聚合方式\n 静态:手工配置Eth-Trunk成员接口、由LACP协议协商确定活动接口和非活动接口\n(3)动态LACP模式\n从ETH-TRunk的创建到加入到成员接口都不需要人工的干预,由LACP协议自动协商来完成\n ","permalink":"https://sin-coder.github.io/network/portlink/","tags":["计算机网络","二层交换"],"title":"端口镜像和链路聚合小结"},{"categories":null,"contents":" STP生成树简介 STP:Spanning Tree Protocol ,生成树协议,为什么要生成树呢,因为有环的存在\n 1.利用STP可以解决的问题 消除环路:阻塞冗余链路消除网络中可能存在的通信环路 链路备份:当前活动的路径发生故障之后,激活冗余链路备份,进而恢复网络的连通性 STP的正常工作依赖于网桥协议数据单元(BPDU报文)的泛洪\n 2.BPDU报文的介绍 其中比较重要的参数有\n ROOT ID:发送此配置BPDU的交换机所认为的根交换机的标识\n ROOT Path Cost: 从发送此配置BPDU的交换机到达根交换机的最短路径总开销,含交换机根端口的开\n 销和不发送此配置BPDU的端口的开销\n Bridge Identifier : 发送此配置BPDU的交换机的STP交换机标识\n Port ID : 发送此配置BPDU的交换机端口的STP端口标识\n 比较顺序为:RID\u0026gt;RP\u0026gt;BID\u0026gt;PID,且对应的值越小越优先\n 桥ID(Bridge ID): 是交换机的STP标示符,一共8个字节,由2个字节的优先级和6个字节的MAC 地址构成:桥优先级缺省为32768,可以手工修改,MAC地址为交换机的背板MAC网络中Bridge ID最小\n的交换机将成为根桥\n 路径开销:Path COST,端口路径开销的默认值和取值范围由选定的路径开销算法决定,路径开销与带宽成反比\n 端口ID (2字节)= 端口优先级(1字节)+ 端口编号(1字节),缺省优先级128,范围0-255,越小越优。\n 3.STP的缺点 STP的短板:所有的vlan都只能使用单侧的链路,这将导致被阻塞端口所在的链路带宽资源浪费\n 初步优化:在交换机上运行基于VLAN的生成树协议,生成树是针对于PerVLAN的,可以灵活地设定\n 每个VLAN对应的生成树所阻塞的接口,实现数据的分流\n 缺点:网络中VLAN数量较多时,每个VLAN执行独立的生成树计算将耗费交换机大量的资源\n 4.其他优化协议 MSTP:Multiple STP 将VLAN映射成一个生成树的实例,若干个VLAN映射到一颗生成树,MSTP将为 每个instance运行一颗生成树可以基于instance 设置优先级、端口路径开销。MSTP兼容STP和\nRSTP,通过多实例能实现对业务流量和用户流量的隔离,提供数据转发的多个路径,实现VLAN\n数据的负载均衡\n RSTP:快速生成树协议,在STP的基础上实现了快速收敛,并增加了边缘端口的概念和保护的功能 ","permalink":"https://sin-coder.github.io/network/stp/","tags":["计算机网络","二层交换"],"title":"STP生成树"},{"categories":null,"contents":" Vxlan技术简介 一、Vxlan技术的产生背景 网络隔离的限制:802.1Q中标准中最多支持的4094个VLAN,数量已无法满足在二层网络中虚拟机数量增 长的需求\n 虚拟机规模的限制:在大二层网络里,报文通过MAC地址进行转发,MAC地址表容量限制了虚拟机的数量\n 虚拟机的迁移受到限制,虚拟机从一台主机上迁移到另一台主机时,也必须依靠二层网络来进行传输\n 二、名词解释 VTEP:Virtual TUNNEL END POINTS ,VXLAN隧道的端点,主要用于VXLAN报文的封装和解封装,直 连物理网络,分配的地址为物理网络IP\n VM:虚拟机,虚拟机之间的访问可分为相同VNI下的不同VM、不同VNI下的跨网访问和VXLAN与非VXLAN 之间的跨网访问\n VNI: VXLAN的网络标识,用于区分VXLAN段,一个VNI表示一个租户 三、技术实现 VXLAN的通信原理是将逻辑网络中的数据帧封装在物理网络中进行传输,封装和解封装的过程由VTEP\n 节点来完成。VXLAN将逻辑网络中的数据帧添加在VXLAN头部之后就封装在物理网络中的UDP报文进行数\n据的传送\n四、VXLAN的报文格式 注释:\n OUTER 的UDP端口使用4798,但是可以进行修改\n OUTER的IP头封装:源IP为发送报文的虚拟机所属的VTEP的IP地址,目的IP为目的虚拟机所属的\n VTEP IP地址\n OUTER:SA为发送报文的虚拟机所属的VTEP MAC地址,DA为目的虚拟机所属的VTEP上路由表中的 下一跳MAC地址\n VXLAN header: 24位的VNI,一共可表示2^24个不同的局域网 16777216个不同的网络 参考文章:https://www.cnblogs.com/hbgzy/p/5279269.html\n","permalink":"https://sin-coder.github.io/network/vxlan/","tags":["计算机网络","二层交换"],"title":"Vxlan技术简介"},{"categories":null,"contents":" Vlan详解 最近在学习网络时总是卡在了二层交换上,尤其对一些概念不是很理解,在公司里实际上去配置一些设\n 备总是出现各种问题,所以下定决心搞透二层vlan中的一些技术,下面是最近总结的一些知识\n一、相关概念解释 VLAN: 虚拟局域网,VLAN所指的LAN特指使用路由器分割的网络-也就是指广播域\n MAC地址分类:MAC地址可分为单播、组播和广播三大类,单播MAC地址全球唯一\n MAC地址泛洪:内网中的一台PC向交换机发送大量的伪造的数据帧,当伪造的目的MAC将交换机的MAC\n 地址表填充满之后,MAC地址表无法学习到新的MAC导致交换机瘫痪\n附:MAC地址表是交换机工作的核心,如果MAC地址表紊乱,则交换机就不能正常工作。\n 广播域:指的是广播帧(目标的MAC地址全部为1)所能传递到的范围,也指能够直接通信的范围,不仅 仅是广播帧、多播帧和目标不明的单播帧也能在同一个广播域中通行\n ARP请求:建立IP地址和MAC地址的映射关系\n DHCP:用于自动设置IP地址的协议,当客户机请求DHCP服务器分配IP地址的时候,必须要发出DHCP广播\n RIP协议:是一种路由协议,每隔30秒路由器都会对临近的其他路由器广播一次路由信息,RIP以外的其他路由\n 协议使用多播传输路由信息,这也会被交换机泛洪\n 二、二层交换机简介 1、主要功能 终端用户的接入、维护自己的MAC地址表、数据帧的转发和过滤、二层环路的避免和链路的冗余性\n 2、工作原理 在收到数据帧之后,交换机学习帧的源MAC地址,然后在MAC地址表中查询该帧的目的MAC地址,并\n 将数据帧转发出去\n3、具体过程 初始情况下,交换机的MAC地址表是空的\n PC1发送数据帧给PC2(PC1已经通过ARP请求获取了PC2的MAC地址)\n 交换机在1口接受到该数据帧后,在MAC地址表中查询该帧的目的MAC地址\n MAC地址表中没有对应匹配的MAC地址,则将这个数据帧进行泛洪,同时交换机学习到该帧的源MAC地址\n 并创建表项;将源MAC地址与接收该帧的1口进行关联\n 除目的主机外的其他的主机会丢弃该数据帧,目的主机回复数据,将自己的MAC地址发往交换机\n 此时该数据帧的MAC地址为源主机的MAC地址,交换机在查询到该表项之后,就将数据帧从1口转发出去\n 同时交换机学习到目的主机的MAC地址,并在MAC表项中将其与2口进行关联\n 4.为什么要使用VLAN 二层交换机只能构建单一的广播域,但是在使用VLAN之后,它能够将网络分割成多个广播域。未分割\n 广播域带来的问题:二层的交换机在接受到广播帧之后,都会进行泛洪,广播帧会传播到网络中的每一台\n主机,并且对每一台计算机的CPU造成负担;另一方面,广播信息消耗了网络整体的带宽。因此,在设计\nLAN的时候,应该合理地划分广播域\n5.常见的广播通信 广播帧的出现是非常频繁的,下面列出一些常见的广播通信除了TCP/IP以外,NetBEUI、IPX和APPLE Talk\n 等协议也经常用到广播,例如:Windows下双击打开“网络计算机时”就会发出广播(多播)信息\n NetBEUI:Windwos下使用的网络协议\n IPX:Novell Netware私用的网络协议\n Apple Talk:苹果公司的计算机使用的网络协议人\n 路由器分割广播域出现的问题:\n 路由器上没有太多的网络接口,使用路由器分割广播域,分割的个数完全取决于路由器的网络接口的个数, 无法满足用户需求\n 路由器连接LAN一侧的网络接口,实际上是路由器内置的交换机,并不能分割广播域 二层交换机分割广播域的优点:二层交换机一般带有多个网络接口,用它分割广播域灵活性提高很多,\n 用二层交换机去分割广播域的技术就是Vlan\n三、VLAN:二层交换机分割广播域 1.VLAN的实现原理 在一台未设置VLAN的二层交换机上,任何广播帧都会被转发到除接收端口外的所有其他端口(Flooding) VLAN是通过限制广播帧转发的范围分割了广播域,实际使用的时候就是通过VLAN ID来区分的\n 一台二层的交换机在逻辑上分割成了数台交换机,VLAN生成的逻辑上的交换机是互不相通的。因此,在交 换机上设置为VLAN如果没有做其他处理的话,VLAN是无法直接通信的\n VLAN之间要进行通信的时候需要路由器提供中继服务,称作VLAN间路由,VLAN间路由,可以使用普通 的路由器,也可以使用三层的交换机\n 2.访问链接 访问链接, 只属于一个VLAN,且仅向该VLAN转发数据帧的端口,在大多数情况下,访问链接所连接的\n 是客户机。设定访问链接的方式为静态VLAN和动态VLAN:\n 静态VLAN:基于端口的VLAN(Port Based VLAN ),明确指定各个端口属于哪个VLAN的设定办法;这种方式的 缺点是,客户机每次变更所连接端口的时候,都必须同时修改该端口所属VLAN的设定,不适合那些\n需要频繁改变网络拓扑结构的网络\n 动态VLAN:是根据每个端口所连接的计算机,随时改变端口的VLAN 动态VLAN又可以分为3类:基于MAC地址的VLAN、基于子网的VLAN、基于用户的VLAN(差异在于\n 根据OSI模型中的哪一层的信息来决定端口所属的VLAN)\n 基于MAC地址的VLAN:通过查询并记录端口所连接计算机上网卡的MAC地址来决定端口的所属,即使计算 机改变了所连接的端口,交换机还是可以查出它的MAC地址,并正确指定端口所属的VLAN。在OSI的第\n二层设定访问链接的方法\n 基于子网的VLAN:通过所连计算机的IP地址来决定端口所属的VLAN,即使因为交换了网卡或者其他原因 导致MAC地址的改变,只要其IP地址不发生改变,就仍然可以加入原先设定好的VLAN。在OSI模型的第\n三层设定访问链接的方法。\n 基于用户的VLAN:根据交换机的各端口所连接的计算机上当前登录的用户来决定该端口属于哪个VLAN。 这里的用户一般是计算机操作系统登录的用户,属于OSI模型第四层以上的信息。\n总结:决定端口所属VLAN时利用的信息在OSI模型中的层面越高,就越适用于构建灵活多变的网络慢\n 4.汇聚链接 汇聚链接指的是能够转发多个不同VLAN的通信的端口,汇聚链路上的数据帧都是被附加了用于识别分\n 属于哪个VLAN的特殊信息,跨越多台交换机的VLAN需要用到\n 传统方法:在两台交换机上各设置专用的接口和连接专用的网线进行互联,但是连线比较麻烦,而且还造 成了交换机端口的浪费的,限制了网络的扩展\n 解决方法:让交换机间互联的网线集中到一根上,便是汇聚链接(Trunk Link) 汇聚链接是跨越交换机间的VLAN的过程:\nA发送的数据帧从交换机1经过汇聚链路到达交换机2时,在数据帧上附加了VLAN ID Tag, 交换机2收\n 到数据后,经过检查VLAN ID Tag发现这个数据帧的标识去除标记后将该数据帧只转发给对应VLAN ID Tag\n的端口,(转送是指确认目标MAC地址并与MAC地址列表比对后只转发给木匾MAC地址所连的端口)\n 注意:只有当数据帧是一个广播帧、多播帧或者是目标不明的帧时,它才会被转发给所有属于VLAN ID\n 的端口;汇聚链路上流通着多个VLAN的数据,负载比较重,设定汇聚链接时,必须支持100Mbps以上的传\n输速度。默认条件下,汇聚链接会转发交换机上存在的所有VLAN的数据,汇聚链接(端口)同时属于交换\n机上所有的VLAN\n 实际应用中很有可能并不需要转发所有VLAN的数据,通过用户设定限制能够经由汇聚链路互联的\n VLAN可以减轻交换机的负载,也为了减少对带宽的浪费\n 拓展内容:VLAN的汇聚方式有两种IEEE802.1Q和ISL\nIEEE 802.1Q \u0026quot;Dot One Q\u0026quot;,IEEE认证的对数据帧附加VLAN识别信息的协议,被称作“标签型的vlan”\n该VLAN识别信息位于数据帧中“发送源MAC地址”和“类别域”之间。具体内容为2字节的TPID+2字节\n 的TCI,在进入汇聚链路时会加上这四个字节,重新计算CRC值,离开汇聚链路时,也会被去除,然后\n再进行CRC的计算TPID的值固定为0x8100,标识数据帧内附加了基于IEEE802.1Q的VLAN信息,而实际\n上的VLAN ID是TCI中的12位,因此最多可供识别4096个VLANISL ,CISIO产品支持的与IEEE 802.1Q类\n似的、给汇聚链路上附加VLAN信息的协议,只能用于Cisio网络设备之间的互联。\n 数据帧头部会被附加26字节的“ISL包头”和帧尾部上新加的通过对包括ISL包头在内的整个数据帧\n 进行计算后得到的4字节的CRC值。总共增加了30字节的信息,当数据帧离开汇聚链路时,只要简单\n地去除ISL包头和新CRC就可以了,原先的CRC值被重新保留,因此无需计算CRC值\n ISL通过用ISL包头和新的CRC将原数据整个地包裹起来,为封装型VLAN\n 5.VLAN间路由 不同VLAN之间不通过路由就无法进行通信?\n在LAN内的通信,必须要在数据帧头中指定通信目标的MAC地址,为了获取MAC地址,TCP/IP协议\n 下使用的是ARP但是ARP解析MAC地址的方法,就是通过广播,不同的VLAN之间广播域也是相互隔离的,\n所以广播的报文无法到达,相对应的就无法解析MAC地址,也就无法进行直接通信。所以VLAN之间要进\n行路由的话必须要经过路由器或三层交换机设备\n 连接路由器和VLAN的两种方式\n 方式1:将路由器和交换机上的每个VLAN分别进行连接,这种方式即将交换机上用于和路由器互联的 每个端口设为访问连接,然后分别用网线与路由器上的独立端口互联\n但是扩展性很成问题,每增加一个新的VLAN,都需要消耗路由器上的端口和交换机上的访问链接,\n 而且还需要重新布设一条网线。而路由器通常不会带有太多的LAN的接口的\n 方式2:不论VLAN有多少个,路由器和交换机之间都只用一条线进行连接,也就是汇聚链接。 首先将用于连接路由器的交换机端口设为汇聚链接,而路由器上的端口也必须支持汇聚链路。 双方\n 用于汇聚链路的协议必须相同。接着在路由器上定义对应各个VLAN的“子接口”。尽管实际与交换机连接\n的物理端口只有一个,但是可以将其分割为多个虚拟端口。即使之后在交换机上新建VLAN,仍只需要一\n条网线连接交换机和路由器,只需要在路由器上新建一个对应新VLAN的子接口即可\n VLAN间数据通信的过程\n收发方同属于一个VLAN之内的通信,一切处理均在交换机内完成\n收发方不在同一个VLAN之内的通信,即使通信双方都连接在同一台交换机上,也必须要经过发送\n 方-交换机-路由器 -交换机-接收方这样的一个流程。汇聚链路会被看作属于所有的VLAN\n四、三层交换机 三层交换机就是带有路由功能的二层交换机,内部结构就是在一台本体内,分别设置了交换机模块和路\n 由器模块,内置的路由模块和交换模块相同,都是使用ASIC硬件处理路由的,与传统的路由相比,可以实现\n高速路由。并且路由和交换模块是汇聚链接的,由于是内部连接,可以确保相当大的带宽\n1.加速VLAN间通信的手段 VLAN间的路由必须要经过外部的路由器或是三层交换机的内置路由模块,但是有时并不是所有的数\n 据都要经过路由器或者路由模块\n 发送目标相同的一连串数据被成为数据流(Flow)。整个流的第一块数据,照常由交换机-路由器路由-再 次由交换机转发到目标所连接的端口\n 交换机将第一块数据路由的结果记录到缓存里保存下来。需要记录的信息有:目标IP地址、源IP地址、 目标TCP/UDP端口号、源TCP/UDP端口号、交换机的接收端口号、交换机的转发端口号,转发目标的\nMAC地址\n 同一个流的第二块以后的数据到达交换机后,直接通过查询先前保存在缓存中的信息查出“转发端口号”后 就可以转发给目标所连接的端口\n 交换机会对数据帧进行路由器中继时相似的处理,比如改写MAC地址,IP包头中的TTL值和Check Sum校 验码等信息\n 通过在交换机上缓存路由结果,实现了以缆线速度接受发送方传输来的数据,并且能够全速路由、转发 给接收方\n 除了三层交换机的内部路由模块,外部路由器中的某些机型也支持类似的告诉VLAN间的路由机制 2.传统路由器存在的必要性 用于与WAN进行连接,三层交换机的大多数机型只配有LAN接口,只有在少数高端交换机上也有用于连接WAN 的串行接口或者说ATM接口,但是在大多数情况下,连接WAN还是需要用到路由器\n 保证网络安全,在三层交换机上,确保网络安全只能通过数据包过滤,但是路由器所提供的网络安全功能中, 不仅可以进行数据包的过滤,还可以基于IPSEC 构建VPN,利用RADIUS进行用户认证等\n 支持除TCP/IP以外的网络架构,目前仍有基于Novell NetWare下的IPX/SPX/Appletalk等网络协议,但是绝大 多数三层交换机都只支持TCP/IP\n 3.管理vlan、native vlan和默认VLAN的区别 默认vlan是指交换机初始就有的,通常id为1,所有接口都处于这个vlan下,所以交换机上来就是能用的\n 管理vlan就是某个vlan下主机的网关\n native vlan 是一个作用于仅为本交换机的概念,所有未划分vlan的端口自动划分到native vlan中\n 当turnk端口发送属于native vlan的帧的时候,帧的tag将会被剥离\n当trunk端口接收到untagged的帧的时候,将会给该帧打上该交换机的native vlan的帧s\n \n","permalink":"https://sin-coder.github.io/network/vlan/","tags":["计算机网络","二层交换","vlan"],"title":"Vlan详解"},{"categories":null,"contents":"","permalink":"https://sin-coder.github.io/search/","tags":null,"title":"Search Results"}]