前言[scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode][scode type="green" size=""]内容全部都为个人理解，不一定正确，如果你发现了文章中有存疑的地方或者你对某个地方有疑问，欢迎在评论区留言，大家一起学习。[/scode]前面完成了云计算应用实践3部分的笔记，这里对这3部分的笔记进行总结分析。前情回顾[post cid="170" cover="" size=""/][post cid="277" cover="" size=""/][post cid="364" cover="" size=""/]常见问题[collapse status="false" title="NAT网关到底有什么用？"]在架构一中，我们给VPC的私有子网配置了一个NAT网关，然后一直就没去管它，那么我们这个NAT网关在整个架构中有什么作用？充当一个什么角色呢？在AWS中其实就已经给出了相关的词条，解释了这个NAT网关的作用。公有 NAT 网关支持将私有子网中的实例连接到互联网，但会阻止它们接收来自互联网的未经请求的入站连接。您应将弹性 IP 地址与公有 NAT 网关关联在一起，然后将互联网网关附加到包含它的 VPC 中。所以简而言之，这个NAT网关的作用就是给我们VPC中私有子网访问互联网提供一个出口。这里面最常见的场景就是，在私有子网，我们的实例需要使用wget等命令下载某些互联网资源，但这些实例在内网中是无法去访问互联网的，我们也不可能为了下载这些资源就把整个私有子网连接到互联网网关中使其变成公有子网获得访问互联网的能力。这种情况下，给私有子网配置一个NAT网关就是最好的选择，让其流量能访问到互联网，而从互联网中是不能接触到这些私有子网中的实例，其只能访问到NAT，这就保证了私有子网中实例的安全。[/collapse][collapse status="false" title="VPC架构设计中要求的在两个私有子网上部署具有高可用性的RDS数据库，实验要求图示为每个子网都有一个实例，为什么实际创建，AWS控制台上只有一个实例？"]在创建数据库时，可用性与持久性的部署选项中，我们选择的是--多可用区数据库实例，关于这个选项，官方文档是这样描述的：在多可用区数据库实例部署中，Amazon RDS 会自动在不同可用区中配置和维护一个同步备用副本。主数据库实例将跨可用区同步复制到备用副本，以提供数据冗余并在系统备份期间将延迟峰值降至最小。在计划内的系统维护期间，运行具有高可用性的数据库实例可以提高可用性。它还可以帮助您保护数据库，以防数据库实例发生故障和可用区中断。所以这个备用数据库实例是不支持读取工作负载的连接，我们的连接只能连接到主数据库上。至于上面问题关于控制台只有一个实例的现象，AWS也给出了回答。[/collapse][collapse status="false" title="EC2的系统基础映像应该如何选择？Linux 与 Windwos两者有什么区别？"]在Web服务器的选择是我更推荐Linux，无论是稳定性、性能、还是成本Linux 都比 Windwos好。为什么大部分软件行业的公司，都会把服务器部署在linux上面_请各位同学考虑一下,软件的服务器应该安装在企业还是厂商?理由是什么?-CSDN博客而在众多的Linux映像中，我选择的是AWS发行的Amazon Linux 2023，因为使用的EC2就是AWS自家的产品，基于此，其发行的Linux版本无疑更适合。[/collapse][collapse status="false" title="我的Web应用是一个登录程序，为什么我实践架构二搭建完成了，访问负载均衡器DNS后无法登录，一直重定向登录页或者登录完成跳转的页面不显示在数据库读取的数据？"]这个问题是隔壁宿舍的一个小伙伴遇到的问题，他的web应用是一个使用session机制的登录组件。在他的实验EC2实例中是可以正常登录，但在使用负载均衡器访问Auto Scaling组创建的Web应用实例做这个登录操作就会一直重定向登录页。为什么呢？原因是这种基于session机制的登录不适用与这种场景。我们都知道，在这种session机制的登录，是通过校验用户请求的Cookie来鉴权用户的操作，而我在实例一中签发的cookie，在实例二中是不存在的，而负载均衡器一般是以轮询机制决定请求流量的分发，那么，你这个登录的请求是在实例一种完成，实例一返回给你一个cookie，并且让你浏览器访问登录成功的页面，而浏览器接收到，继续请求负载均衡器登录成功的页面，这时候，基于轮询机制，你这个登录成功页的请求给到了实例二，但实例二中并没有这个cookie的session存在，那么它就会给负载均衡器返回没有相关权限，不允许访问这个登录成功的页面，并且让浏览器重定向回登录页。这就造成了一个循环，无论怎么登录，都会被重定向回登录页面。那么有什么办法解决这个问题呢？有，使用Redis或者DynamoDB做登录会话的缓存，让登录实例共享会话缓存，都基于这个缓存做访问的鉴权。[/collapse][collapse status="false" title="为什么我Auto Scaling组创建的实例一直显示实例不健康，Auto Scaling组一直在重复刷新实例？"]这个问题是和上面那个问题一起出现的，原因是因为我们在创建Auto Scaling组的时候开启了基于ALB的弹性伸缩策略，什么意思呢？就是说这个Auto Scaling组会获取负载均衡器的请求情况，如果负载均衡器访问实例时，实例给负载均衡器返回了有异常的状态码，那么Auto Scaling组会以为实例出现了问题，是一个不健康的状态，就会终止实例并且重新启动一个新实例。而上面那个web应用的登录事件中由于负载均衡器的轮询，一直使用了无权限的cookie请求实例，导致实例一直给负载均衡器返回301状态码，就造成了Auto Scaling组以为实例不健康，不断刷新实例。[/collapse]总结分析[scode type="yellow" size=""]个人总结，就不给各位看了。[/scode]
前言[scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode][scode type="yellow" size=""]内容全部都为个人理解，不一定正确，如果你发现了文章中有存疑的地方或者你对某个地方有疑问，欢迎在评论区留言，大家一起学习。[/scode][scode type="green" size=""]部分常见问题可以访问下面这个文章[/scode][post cid="371" cover="" size=""/]在前面的实践中，我们完成了架构一和架构二的实施与部署，接下来我们就可以对课程设计附加分部分进行操作了。题目在完成以上所有任务过程中，如果你使用了如下资源或服务，均可作为本课程设计成绩的加分项：1.部分静态资源（如网页上的图片等）存储在S3中，通过CloudFront分发2.使用堡垒机登录实例3.VPC通过CloudFormation模板构建4.基于CloudWatch指标做AutoScaling5.使用DynamoDB保存会话状态6.使用Amazon ElastiCache for Redis作数据库缓存7.使用消息队列SQS准备工作基础补习老规矩，先补习基础，看懂专有名词才能完成接下来的操作S3 (Simple Storage Service): Amazon Simple Storage Service 是一种对象存储服务，用于存储和检索大量数据，适用于各种用例，如备份和恢复、存档、大数据分析等。CloudFront: Amazon CloudFront 是一项快速内容传递网络服务 (CDN)，可帮助加速静态和动态网络内容的传输。堡垒机: 在云计算环境中，堡垒机是一种用于安全访问云资源的中间服务器。它充当了控制和监视用户对云资源访问的关键角色。CloudFormation: Amazon CloudFormation 是一项基础设施即代码服务，允许您以声明性模板的方式定义和部署 AWS 资源。CloudWatch: Amazon CloudWatch 是一项监控和管理 AWS 资源的服务，可帮助您收集和跟踪指标、监控日志文件和设置警报。DynamoDB: Amazon DynamoDB 是一种全托管的 NoSQL 数据库服务，具有高可扩展性和低延迟，适用于各种应用程序。Amazon ElastiCache for Redis: Amazon ElastiCache for Redis 是一种托管的 Redis 内存数据库服务，用于加速读取操作，缓存数据和提高性能。SQS (Simple Queue Service): Amazon Simple Queue Service 是一项托管的消息队列服务，用于在分布式系统中传递消息。思路解析由于我一开始并没打算做这部分的操作，导致我的web程序就没办法实现上面的很多内容。但笔记都写两篇了，感觉这部分也能争取做一下。先简单说一下接下来的操作：✔1.部分静态资源（如网页上的图片等）存储在S3中，通过CloudFront分发：做，我打算给我的web程序加一个背景图，这个背景图就用s3存储，通过CloudFront分发✔2.使用堡垒机登录实例：做，比如在完成架构二后，我们需要对私有子网中的EC2实例进行一些操作，就可以通过这个堡垒机来操作。✔3.VPC通过CloudFormation模板构建：不做，因为这需要会AWS的CloudFormation语法与清楚AWS资源的相关资源代码，比较麻烦，所以我这里就不去完成这个点。但实际上这个操作实用性非常强，可以摆脱web页面上的一些操作，直接通过代码来创建相关的VPC，对云计算比较感兴趣，可以操作一下。本来是不想做的，但无奈，某个月黑风高的晚上好奇心爆棚，进CloudFormation页面看了一眼，发现已经有一个堆栈，出于强迫症，我把这个堆栈删了，睡了一觉后实验就再也开启不了了。这个堆栈是实验初始化的堆栈，删了会把堆栈之前创建出来的资源也删除，所以后面实验就会启动不了。最后被逼无奈只能对账号来一个reset操作，想了一下反正账号实验环境都格式化了，就动手做了一下这部分。✔4.基于CloudWatch指标做AutoScaling：做，这个操作其实在前面架构二的笔记中 创建AutoScaling时就已经完成了。❌5.使用DynamoDB保存会话状态：❌6.使用Amazon ElastiCache for Redis作数据库缓存❌7.使用消息队列SQS5.6.7都不做，因为我的web应用用不上，就不进行具体得操作了，但实操中会有相对应的个人思考讲解。实操1.使用S3存储，通过CloudFront分发[scode type="blue" size=""]模拟需求：给Web应用设置一个背景图片，背景图片使用使用S3存储，通过CloudFront分发。操作步骤：创建S3存储桶-->上传背景图片-->创建CloudFront-->修改Web程序代码，重新打包上传EC2-->创建新Web应用的AMI-->修改启动模板[/scode]创建S3存储桶进入S3控制台，创建S3存储桶，在常规配置中填写桶名称。这里要注意，因为我们这里的要求是用s3存储内容，通过CloudFront分发，所以我们的s3桶应该只对CloudFront开放访问，所以公共访问权限应该 阻止所有公开访问。存储桶版本控制看你的需求，如果你希望你的资源能自动备份，能回溯，那就开启。我这里就默认禁用了。然后创建即可上传图片到S3桶中略，这个太简单了，进去桶里上传文件就行了。创建CloudFront搜索CloudFront进入CloudFront控制台创建CloudFront在源设置这里，Origin domain选择上面创建的S3桶，Origin path的意思是你希望CloudFront的源为S3中的哪个文件夹，比如Origin Path 为 /images，这样 CloudFront 将仅缓存和提供存储桶中 images 文件夹下的内容，我这里希望CloudFront 指向整个S3，所以我这里不填。来源访问不要设置公开，如果选择公开，这里给S3分配CloudFront 就没什么意义了。选择第二个或者第三个都可以，两者的区别在于CloudFront对S3的访问方式，有兴趣的可以自己去了解。我这里选择了第三个，没有OAI就创建一个。你也可以选择AWS推荐的第二个选项--来源访问控制设置，但这样，需要你在CloudFront创建后手动修改S3桶的存储桶策略（放心，不需要你去写，这个存储桶策略会在你创建完CloudFront后在顶部给你提示出来，你只需要复制粘贴到对应的S3桶的存储桶策略）；而选择第三个--Legacy access identities，AWS会自动给你选择的S3桶配置存储桶策略。（所以我这里图方便，我选择了第三个选项，但第二个选项我也创建过，然后手动配置策略，我推荐你也试试)Enable Origin Shield直接默认否。默认缓存行为和函数关联 不动，默认即可。或者你按需设置我觉得WAF保护作用不大，我这选择关闭这部分根据需求自定义，比如你希望这个CloudFront能绑定你自己的域名，能通过你自己的域名来访问，那么你就得在备用域名那增加上你的域名，然后上传或者创建你的域名证书，并且把你的域名CNAME解析到AWS给你分配的主域名；比如你觉得你的CloudFront并不需要给IPv6提供服务，那么就像我这样关闭IPv6的解析服务。当然，你这部分直接默认也可以。然后完成创建即可访问测试等CloudFront部署完成（上次修改时间那显示时间就是部署完成了，显示 部署 就是在部署中），即可进行访问测试。图示有两个CloudFront，是因为上面两种不同的来源访问 我都创建了一个，并且都分配给了同一个S3桶，你上面创建有一个CloudFront就行了。复制分配的CloudFront域名在域名后加上S3桶中的文件路径即可完成访问，比如我的图片存在S3根目录下，文件名为background.jpg。然后，你可以尝试在S3桶中复制对象的URL在浏览器打开，访问失败，那么我们就成功完成了AWS中的相关配置。接下来就是修改代码，重新打包程序发布了。修改Web程序代码，重新打包上传EC2因为距离最终的检查还有一段时间，这个CloudFront不一定就是我最终检查使用的那一个，所以我接下来打算把这个CloudFront的host放到配置文件application.yml文件中，实现动态配置。(实际上，如果只是背景图其实是可以直接在html中写死url的，但如果你的项目中使用了很多图片等静态资源，那么我还是建议把url中host部分抽离到配置文件中)application.yml文件中新增custom.s3CloudFrontHost字段在中加入css内联样式。通过${@environment.getProperty('custom.s3CloudFrontHost')}读取我们刚刚在上面application.yml文件中新增的custom.s3CloudFrontHost属性。（爆红也别怕，我这里使用的thymeleaf，idea的语法检查当纯html检查了）然后再修修样式，加个卡片，防止背景和文字混在一起看不清。完美，程序成功运行。重新打包上传EC2就不演示了，可以看架构一笔记中的操作。创建新Web应用的AMI很简单，如图所示修改启动模板EC2控制台，进入启动模板页，选择之前创建的启动模板，操作->修改模板只需在要这里的AMI设置选择上面新建的AMI就行，其他配置不变。访问负载均衡器，难搞喔，显示的内容怎么还是原来的版本，怎么没有自动更新？是缓存？还在构建新实例？修改Auto Scaling 组答案是，前面我们在创建Auto Scaling 组的时候选择启动模板，选择的版本是Default，Default版本，如果你不去改变它，那么就默认为1版本，而我们上面对启动模板进行了修改，修改后的版本为2，所以Auto Scaling 组还在使用着原来版本的AMI。那么接下来就是要让Auto Scaling 组能启动到我们刚刚新建的AMI，那么有2种办法。两种办法的依据是不一样，看你这个Auto Scaling 组的定位来决定。第一种方案比如我现在这个Auto Scaling 组，已经确定了是生产环境下的了，那么我们可以对启动模板版本的Default进行修改，把Default设置成需要的版本，比如这里我需要它设置为2，第二种方案还有一种情况，就是我这个Auto Scaling 组是在开放环境下运行，我希望实例都是用的最新版本启动模板，那么我们就对Auto Scaling 组中启动模板的配置进行修改，把启动模板版本的配置改成Latest，那么Auto Scaling 组以后就会使用最新版本的启动模板来启动实例。启动实例刷新[scode type="yellow" size=""]一般情况下，无论是修改启动版本的版本还是修改Auto Scaling 组绑定的启动模板版本，Auto Scaling 组都无法立刻对正在运行的实例进行更新，这个更新只会影响到以后的实例，而不会应用到已在运行的实例。所以如果就需要以下操作，来启动实例刷新[/scode]进入相关的Auto Scaling 组详情页，启动实例刷新替换方案按照提示，根据你的需求来，我这里选择了第一个。关掉跳过匹配，因为我们需要他根据新版本启动模板来启动新实例，所以不能跳过匹配。所需的配置部分默认即可，因为我们已经对启动模板的默认版本进行了修改。然后启动，Auto Scaling 组就会自动完成实例的更新。再次访问负载均衡器的URL，成功访问最新版本的Web应用，并且背景图为CloudFront分发的S3桶对象。2.使用堡垒机登录实例[scode type="blue" size=""]模拟需求：在架构二中，我们使用Auto Scaling 组在私有子网中创建了若干个EC2实例，由于这些实例都是在私有子网上运行，那么我们就无法通过互联网连接到这些EC2实例上，那么这就需要堡垒机来做跳板（所以堡垒机又称跳板机），我们可以通过这个部署在公有子网的堡垒机连接私有子网的实例。操作步骤：创建与修改安全组->创建堡垒机实例->创建实例连接[/scode]创建与修改安全组在安全组标签页新建安全组VPC选择我们创建的那个增加一个入站规则，端口为22，源看情况。比如你的设备ip是固定的ip段，那么你就可以设置为固定的ip段，而我不确定我的网络情况，所以我这里设置了0.0.0.0，全开放。出站规则的目标，我们这里是可以确定的，因为我这里的堡垒机作用就是连接Auto Scaling 组在私有子网中创建的那若干个EC2实例，那么这个目标就可以设置成这些实例绑定的安全组。创建了堡垒机的安全组，那么还需要修改Auto Scaling 组创建的那若干个EC2实例绑定的安全组，使其对堡垒机开放。新增一个入站规则，端口为22，源设置为上面创建的堡垒机安全组创建堡垒机实例名称随意，AMI默认的Amazon Linux 2023就行实例类型默认即可，或者选择最便宜的就行，因为我们只需要这个实例做跳板。密钥对默认，懒得下载配置。VPC选择我们创建的，子网一定要选择公有子网，两个公有子网都无所谓；分配公有ip；安全组选择上面我们新建的堡垒机安全组。登录私有实例[scode type="red" size=""]我这里以FinalShell为例，其他ssh也行，原理一样，触类旁通。[/scode]创建堡垒机隧道复制堡垒机的公有IP地址FinalShell里新建一个shh连接，先把常规内容填好，别点确定。回到浏览器，选择Auto Scaling 组创建的实例，复制私有的IP地址点击 隧道 新增一个隧道，名称随意；类型：本地；监听端口：填你电脑未被占用的端口；绑定ip：默认的127.0.0.1就行；目标地址：上面复制的私有ip地址；目标端口：22。然后确定新建隧道，确定新建连接。创建私有实例连接新建一个ssh连接，主机那里的ip部分填127.0.0.1；端口填上面堡垒机上的隧道中设置的端口；其他照常即可。测试堡垒机连接先连接我们的堡垒机，看登录信息，显示登录ip为电脑ip地址。再连接私有EC2实例，可以看到登录的ip为局域网ip，一致堡垒机的私有ip一致。这就完成了堡垒机登录私有子网中实例的操作，同理，你也可以登录到私有子网中另一个实例，只需要对堡垒机的连接进行编辑，新增对应的隧道即可。3.通过CloudFormation模板构建VPC写CloudFormation模板想使用CloudFormation模板来完成创建VPC这个操作，得先弄清楚CloudFormation模板的基本结构。那么不妨看一下官方文档的介绍。使用 AWS CloudFormation 模板 - AWS CloudFormation (amazon.com)我个人来说，更推荐使用YAML格式，更简洁。主要要了解的是这个 模板部分 ，这样你才能看得懂这yaml中到底在描述着什么。（这些蓝色标题是这个yaml的组成部分，在yaml中是英文的，别傻乎乎在yaml写什么“元数据”、“条件”，这AWS估计用的机翻，都给翻译过来了）下面是我根据实验需求写的关于VPC的CloudFormation模板。如果你需要完成下面创建堆栈的操作，那么你需要在你电脑插件一个xxx.yaml文件，然后把我下面的这个CloudFormation模板复制进去。# 描述 Description: "Create a VPC with two public and two private subnets, as well as corresponding routing tables and NAT gateways." # 参数变量部分，创建堆栈时，可进行填写 Parameters: VpcCIDR: Type: String Default: "10.0.0.0/16" Description: "Please enter the CIDR for this VPC" PublicSubnet1CIDR: Description: "Please enter the CIDR for the public subnet" Type: String Default: "10.0.0.0/24" PublicSubnet2CIDR: Description: "Please enter the CIDR for the public subnet" Type: String Default: "10.0.2.0/24" PrivateSubnet1CIDR: Description: "Please enter the CIDR for the private subnet" Type: String Default: "10.0.1.0/24" PrivateSubnet2CIDR: Description: "Please enter the CIDR for the private subnet" Type: String Default: "10.0.3.0/24" # 资源部分 Resources: # VPC MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: true EnableDnsHostnames: true # 互联网网关 InternetGateway: Type: AWS::EC2::InternetGateway # 网关关联 VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref MyVPC InternetGatewayId: !Ref InternetGateway # 公有子网1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref MyVPC # 可用区选择 AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnet1CIDR # 公有子网2 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref MyVPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PublicSubnet2CIDR # 私有子网1 PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref MyVPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet1CIDR # 私有子网2 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref MyVPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet2CIDR # 弹性IP NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: VPCGatewayAttachment Properties: Domain: vpc # NAT网关 NATGateway: Type: AWS::EC2::NatGateway Properties: # 给NAT网关授权上面这个弹性IP AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PrivateSubnet1 # 公有路由表 PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref MyVPC # 私有路由表 PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref MyVPC # 给公有路由表关联互联网网关 DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: VPCGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # 公有路由表关联公有子网1 PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable # 公有路由表关联公有子网2 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable # 给私有路由表关联NAT网关 DefaultPrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: "0.0.0.0/0" NatGatewayId: !Ref NATGateway # 私有路由表关联私有子网1 PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable # 私有路由表关联私有子网2 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable创建堆栈进入CloudFormation操作台，这里已经有一个堆栈了，别动它，这个是账号实验环境初始化的堆栈，你第一次启动实验为什么慢，就是因为在运行这个堆栈。不管这个堆栈，我们新建一个堆栈。选择上传模板文件，把上面保存的ymal文件上传上去这里其实已经不用怎么改了，因为其实在ymal中，我就已经预设好了。选一下IAM角色，把我们的LabRole加进去，相当于给这个堆栈授权。这个操作相当于在linux中授权root用户，所以这个操作实际上是不安全的，但我们自己写的CloudFormation模板，我们知道这些操作基本不会造成什么不可逆的影响（实际上是我懒得去新建角色）。其他配置默认，然后下一步提交即可。然后点开这个新建的堆栈，打开资源页，即可看到堆栈在创建我们yaml中定义的资源。进入VPC页面，查看创建的VPC资源地图，创建成功，后续就可以在这个VPC上完成相关操作了。4.基于CloudWatch指标做AutoScaling组我们在架构二中创建AutoScaling组时就已经完成这个操作了，具体你也可以进入EC2页面中AutoScaling组标签页看里面配置的动态扩展策略，图中这个策略就是根据CloudWatch指标做的动态扩展策略，这个策略表示：Amazon EC2 Auto Scaling 会根据需要启动和终止 EC2 实例，以将组中所有实例的累计 CPU 使用率保持在 80%。当然你也可以新建更多的策略来规划AutoScaling组的弹性伸缩。5.Amazon ElastiCache for Redis、DynamoDB、SQS[scode type="blue" size=""]关于这三者，我上面也说了，我的实验应用用不到，所以就不进行操作了。[/scode]Amazon ElastiCache for Redis是我比较推荐在这个实验中使用的，因为这个简单！因为Redis是一个开源的软件，它有一套公共认可的统一的开发规范，而且Spring Boot就集成了Redis的控制类，直接就能通过maven导入到项目中，然后像使用MySQL数据库一样，在配置文件设置相关参数即可。如果你想在你的项目中使用，只需要在Amazon ElastiCache中创建一个Redis实例，然后把程序中配置文件的参数修改成这个实例的参数，和迁移MySQL数据库的操作基本一致。剩下的DynamoDB与SQS，如果你想在你的项目中集成就会有一点点难，因为这两个服务在AWS是无服务器类的资源，你想集成到你的程序中，你得在你的项目中使用AWS官方提供的SDK，你需要使用AWS提供的规范，就比较难受了，因为这样就需要你通过对文档和DEMO进行研究。下面是我找到的一些文档与DEMO，如果你有兴趣，也可以研究一下。AWS SDK 代码示例 (amazon.com)aws-doc-sdk-examples/javav2 at main · awsdocs/aws-doc-sdk-examples (github.com)
前言[scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode][scode type="yellow" size=""]内容全部都为个人理解，不一定正确，如果你发现了文章中有存疑的地方或者你对某个地方有疑问，欢迎在评论区留言，大家一起学习。[/scode][scode type="green" size=""]部分常见问题可以访问下面这个文章[/scode][post cid="371" cover="" size=""/]题目为该系统构建弹性高可用架构，即使用Auto Scaling 组至少在不同可用区的2台实例上部署Web Server，通过ELB分发流量，如图2所示。准备工作基础补习老规矩，先补习基础Auto Scaling（自动扩展）：Auto Scaling 是一种自动调整计算资源的服务，可根据应用程序的需求自动增加或减少计算资源。当流量增加时，Auto Scaling 可以自动添加更多的服务器实例来处理额外的负载，当流量减少时，它可以自动减少服务器实例以节省成本。这样可以确保应用程序始终具有所需的计算资源，同时最大程度地减少资源浪费。Application Load Balancer（应用程序负载均衡器）：Application Load Balancer 是一种负载均衡服务，用于在多个服务器实例之间均衡分发传入流量。它可以根据不同的条件（如路径、主机名、端口等）将流量路由到不同的后端服务器实例，从而实现负载均衡和高可用性。Application Load Balancer 可以帮助应用程序更有效地处理流量，并确保各个服务器实例之间的负载均衡。前面的VPC，EC2，RDS我们都基本了解，这里需要搞明白的就上面这两个概念与其的作用。一个是动态扩展一个是动态分发流量。思路解析1.这题需要做什么？与第一题有什么区别？我需要做什么？在第一题中，我们创建了一个VPC，在公有子网上部署Web应用，私有子网部署RDS的MySQL数据库。在这里，我们需要使用Auto Scaling 组来在私有子网中部署web项目，然后通过负载均衡器中转分发流量到私有子网中的web应用中。2.那么想一想，怎么通过Auto Scaling 组来在私有子网中部署web项目？或者换个说法：Auto Scaling 组怎么启动我们的web项目？答案是通过启动模板3.启动模板又是启动的什么？启动模板又有什么意义？启动模板里面定义的是：用什么AMI启动什么类型的EC2实例，就是说启动模板就是一个描述使用什么AMI启动EC2的模板。所以这里我们就有大概的一个操作思路了：把我们的Web应用打包成AMI映像（意思是这个意思，实际操作是把运行着web应用的那个EC2实例打包成AMI映像），然后定义一个启动模板，通过Auto Scaling 组自动在私有子网启动实例，然后给Auto Scaling 组启动的实例分配负载均衡器。实操[scode type="red" size=""]在完成下面的操作之前先保证你EC2中的Web应用能正常启动！！！[/scode][scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode]设置程序自启动[scode type="blue" size=""]在打包映像前，我们需要先给我们的Web应用设置程序自启动，这样，从AMI启动的实例中就会自动运行我们的Web应用。由于我实验使用的Web应用是Spring Boot项目，且只需要运行一个jar包，所以我这里只需要设置自启动jar包即可。如果你的实验项目使用前后端分离，那么你还需要把你的前端项目也设置自启动。我这里只演示我的jar包自启动设置。[/scode]ssh登录到我们的EC2实例，新建一个myapp.service文件（在finalShell中，只需要在下面文件显示框中 右键->新建->文件 输入文件名称即可完成创建，其他ssh工具应该也有类似的功能，当然你选择直接在端口使用命令创建也行）在myapp.service文件中完成程序自启动的配置填写（在finalShell中，双击myapp.service文件，即可填写or修改文件，文件更改完记得保存！！！！！）。[Unit] # 应用描述 Description=MyWebApplication After=network.target [Service] # 工作目录 WorkingDirectory=/home/ec2-user/ User=root # 运行jar包的指令，只需要把你平时运行的指令中前面的java改成/usr/bin/java。我这里因为我的jar包内的配置文件中的数据库配置是指向EC2中的MySQL，所以需要使用外置配置文件来启动spring boot项目。这里--spring.config.location的值就是我需要spring boot加载的配置文件路径。 ExecStart=/usr/bin/java -jar AWSDemo-1.0.jar --spring.config.location=/home/ec2-user/yml/application.yml # 收到正常停止命令时不自动重启(systemctl stop 时不会自动重启) SuccessExitStatus=143 # 在服务失败时自动重启 Restart=on-failure # 重启前等待10秒 RestartSec=10 # 重启尝试间隔时间 StartLimitInterval=10 # 间隔时间内的最大重启尝试次数 StartLimitBurst=3 [Install] WantedBy=multi-user.target使用mv指令把myapp.service移动到/etc/systemd/system目录下（使用sudo命令可以让我们以管理员权限运行命令，这里加上了可以防止我们的权限不足。）sudo mv /home/ec2-user/myapp.service /etc/systemd/system/运行下述指令重新加载systemd服务。sudo systemctl daemon-reload 然后即可使用下述systemctl指令管理你的java应用了。# 启动程序自启动 sudo systemctl enable myapp.service # 启动 sudo systemctl start myapp.service # 停止 （这是停止指令，别傻乎乎复制运行，这里写这个只是告诉你可以运行这个指令停止应用运行） sudo systemctl stop myapp.service # 查看状态 sudo systemctl status myapp.service然后，我们可以重启EC2实例，确认web应用是否有开机自启动。EC2重启后，访问项目路径能正常访问就是设置程序自启动成功了！打包EC2实例的AMI映像[scode type="blue" size=""]上面我们已经完成了EC2中Web程序的自启动，这意味着 我们只需要打包一个这个EC2实例的AMI映像，然后往后我们根据这个映像启动的EC2实例就会自动运行这个Web程序。[/scode]根据Web程序所在的EC2实例创建AMI映像填写映像名称与描述即可完成AMI映像的创建，就这么简单！！创建与修改安全组[scode type="blue" size=""]在完成接下来的操作前，我们还要提前设置好负载均衡器与后面Web应用程序实例的安全组，并且把新增的Web应用程序实例的安全组新增入站规则到RDS的安全组中。这里的后面Web应用程序实例的安全组为什么不使用前面我们已经定义好的web程序EC2的安全组呢？因为后面Web应用程序实例是使用Auto Scaling组自动启动的，其只需对负载均衡器开放即可。而在前面第一题中定义的web程序EC2的安全组，我们对外的源设置的0.0.0.0，而且还开放了多个端口，所以这里需要新增一个web应用程序EC2的安全组。而且因为之前我们的RDS的安全组只对上一题设置的web应用程序的安全组开放，这里我们新增了一个web应用程序EC2的安全组，那么也应该再RDS的安全组中新增对应的入站规则。[/scode]先创建负载均衡器的安全组，开放80端口，因为后面我们需要通过负载均衡器的DNS访问Web项目。由于我的web项目是单体应用，这里只需要设置一条入站规则，如果你的项目是前后端分离项目，我建议除了开放80端口外，还应该加上后端项目的端口。创建新的Web程序EC2的安全组，端口范围填写Web程序的端口，我的是9090；源选择上面我们新建的负载均衡器的安全组，因为我们这个Web程序EC2的流量全部来自负载均衡器。由于我的web项目是单体应用，这里只需要设置一条入站规则，如果你的项目是前后端分离项目，记得分别把前后端项目的端口加到安全组上。[scode type="yellow" size=""]甚至，如果你的前后端项目分别部署在不同的EC2服务器上，你应该使用两个不同的安全组来设置。当然这种情况在这个题目下应该是不存在的[/scode]修改RDS安全组的入站规则，新增一条上面新创建的Web程序EC2的安全组创建目标群组[scode type="blue" size=""]安全组设置完毕，接下来就可以创建负载均衡器了，但创建负载均衡之前，要先得配置目标群组。[/scode]目标类型选择 实例这里需要注意的是端口与VPC的选择，这里的端口填写你Web应用程序的端口，我的是9090；VPC选择我们创建的那个VPC这里需要注意，不用选择实例，直接点击右下角的创建即可[scode type="red" size=""]这里有一个非常要注意的点，如果你的项目是前后端分离项目，那么你需要分别给前后端设置一个目标群组。这样负载均衡器才能正确分发流量。而我的项目是单体项目，所以上面演示只设置了一个目标群组。这里打个比方：前端项目端口：8080 后端项目端口：8989那么你就得创建两个目标群组，一个目标群组的端口填8080，一个目标群组的端口填8989。[/scode]创建负载均衡器[scode type="blue" size=""]目标群组创建好，接下来就能创建负载均衡器[/scode]创建一个负载均衡器，选择这里的Application Load Balancer名称随意；模式选择 面向互联网；VPC选择我们创建的VPC；子网一定要选择该可用区下的公有子网！！！不然你这个ALB分配的ip只会是局域网ip，而不是公有ip。安全组选择我们上面已经为这个负载均衡器创建好的安全组。侦听器和路由这个要注意：还是老原因，我这项目是单体项目，只需要一个侦听器，把负载均衡器的80端口流量转发到我上面设置好的目标组即可。如果你是前后端项目，我推荐的做法是80端口转发到你的前端项目的目标组上，后端项目 添加一个侦听器 端口填你后端项目一样的端口，目标组选后端项目的目标组，并且确保负载均衡器的安全组把后端项目一样的端口也开放了。创建启动模板[scode type="blue" size=""]负载均衡器创建完，我们接下来就开始配置启动模板[/scode]这里的 Auto Scaling 指导 勾上，方便配置映像选择我们已经配好程序自启动的那个实例类型我们可以先不配，等后面让Auto Scaling组自动适配；密钥对为了方便管理，我这里选择了默认的密钥对；网络信息里面的安全组选择我们上面创建的新的Web程序实例安全组最后，点击创建即可创建Auto Scaling组[scode type="blue" size=""]如果前面的操作你的完成了，那么恭喜你，可以操作最后一步了，创建我们的Web应用程序实例的Auto Scaling组[/scode]名称随意，启动模板选择前面已经创建好的启动模板这里的实例类型要求没有固定方案，具体可以根据你的项目来。比如我的项目在vCPU方面大概1-2就行，而内存需要2以上（因为我们前面在配置EC2环境时，装了MySQL，MySQL是一个非常吃IO的程序，所以这里内存我建议你得2G以上）当然，如果你不喜欢上面这种以属性来决定实例类型，你也可以手动设置实例类型。比如，我原来的项目EC2用的下面这个2h2g的t3.small，那么选择这个实例类型为主类型，然后使用系统推荐的同系列实例类型做备选。两种实例类型选择方式任君选择！！！实例分配一定要选择100%按需，因为我们的实验账号只支持按需实例；分配策略因为我上面实例类型要求 选的是基于属性，所以只能选择 最低价格 这个策略。网络设置：VPC选择我们创建的VPC，子网按照题目要求选择两个私有子网这里的负载均衡器选择，是根据目标群组来决定的，所以选择上面我们创建的那个（这里一样，前后端分离项目应该选择两个目标组）这里我们可以选择开启弹性负载均衡运行状况检查，其他设置开启CloudWatch 指标收集，后面需要用到。所需容量：2 所需最小容量：2 所需最大容量：因为要体现出动态扩展的功能，这里我填了3[scode type="yellow" size=""]这个自动扩展其实是实验的加分项，因为这里用到了CloudWatch 指标做AutoScaling，答辩记得和老师说！！！[/scode]这个实例维护策略我直接用的默认第一个，有兴趣的可以自己研究一下。步骤五、六没必要配置，直接 跳至审核 即可，然后查看各项信息，完成创建即可。检查架构搭建情况[scode type="blue" size=""]恭喜你，你已经完成了整个架构的搭建，接下来就是检查服务情况正不正常了。[/scode]先打开EC2实例页，可以看到相关的两个实例正在创建。打开负载均衡器页，点击创建的负载均衡，资源映射标签，可以看到相关的资源正常（前后端分离项目应该比我这要复杂）打开负载均衡器页，点击创建的负载均衡，详情标签，可以找到负载均衡器的DNS，复制到浏览器打开，即可访问web应用打开上面复制的ALB的DNS，成功访问web项目
前言[scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode][scode type="yellow" size=""]内容全部都为个人理解，不一定正确，如果你发现了文章中有存疑的地方或者你对某个地方有疑问，欢迎在评论区留言，大家一起学习。[/scode][scode type="green" size=""]部分常见问题可以访问下面这个文章[/scode][post cid="371" cover="" size=""/]题目构建一个包含两个公有子网、两个私有子网的VPC，私有子网配置NAT，如图1所示。构建一个简单的B/S架构的系统，Web Server部署在公有子网的实例上，数据库部署在私有子网的RDS上，如图1所示。（扩展要求）数据库具有高可用性。准备工作基础补习先简单理解几个在AWS中比较重要的专有名词区域 (Region) :区域是AWS基础设施的物理位置，每个区域由一个或多个数据中心组成。AWS的全球基础设施分布在不同的区域，例如美国东部（弗吉尼亚）、亚太（东京）等。每个区域都是相对独立的，拥有自己的网络、电力供应等。可用区 (Availability Zone) :可用区是指在同一区域内的物理数据中心。AWS的每个区域由多个可用区组成，这些可用区之间通过低延迟网络连接。使用多个可用区可以提高应用程序的可靠性和容错性。VPC (Virtual Private Cloud) :VPC是AWS提供的一种虚拟网络环境，允许您在AWS云中启动资源（如EC2实例）并控制这些资源之间的网络流量。您可以在VPC中定义子网、路由表、网络访问控制列表等。子网 (Subnet) :子网是VPC内的一个IP地址范围，用于将VPC划分为更小的部分。每个子网都关联一个可用区，并且您可以在子网级别配置路由表、安全组等。互联网网关 (Internet Gateway) :互联网网关是一种用于在VPC和Internet之间进行通信的AWS托管网关。它允许VPC中的资源与Internet之间进行通信，使您的实例能够访问Internet上的资源或者被Internet访问。NAT网关 (Network Address Translation Gateway) :NAT网关允许私有子网中的实例访问Internet，同时阻止Internet访问这些实例。它充当了私有子网和Internet之间的中间人，为私有子网中的实例提供出站Internet访问。安全组 (Security Group) :安全组是一种虚拟防火墙，用于控制实例的入站和出站流量。您可以配置安全组以允许或拒绝特定类型的流量，并将安全组应用于一个或多个实例。安全组是在实例级别工作的。仔细阅读以上GPT给出的解释，相信能给你留下点基础的认识。题目解析这个题目的意思就是：定义一个VPC包含两个可用区，两个可用区中分别定义公有、私有子网，公有子网部署Web服务器，私有子网部署数据库。(没错，这是废话，看图一眼就能看出来。但你为什么还是觉得似懂非懂的不会操作呢？不妨看看下面几个问题)1.什么是公有子网，什么是私有子网？2.为什么web服务器放公有子网上，而RDS数据库放私有子网呢？3.题目中不是说私有子网配置NAT吗？为什么NAT网关要放在公有子网1上？关于问题1、2，继续引用GPT的回答：公有子网是与互联网网关直接关联的子网。实例在公有子网中可以直接与Internet通信，可以拥有公共IP地址，也可以通过路由表配置访问Internet的权限。通常用于托管公共面向Internet的应用程序或服务。私有子网是没有直接与Internet通信能力的子网。实例在私有子网中无法直接访问Internet，需要通过NAT网关或跳板主机等中间设备才能访问Internet或被Internet访问。私有子网通常用于托管内部应用程序或服务，需要保持相对隔离和安全性这里也说一下我的理解：在整个VPC中，能被互联网访问的区域(子网)属于公有子网，而不直接对外开放访问的就是私有子网。而子网怎么才能对外开放访问，成为这个VPC的公有子网呢？那就是连接绑定了这个VPC的互联网网关，那么不连接互联网网关的就是私有子网了。问题3就有意思了，是题目错了吗？不是的，题目讲的是给私有子网配置NAT，而不是在私有子网上部署NAT，那为什么NAT网关要放在公有子网1上呢？这就与这里NAT网关的作用有关了，上面提到，私有子网之所以是私有子网，是因为私有子网没有连接互联网网关，那如果我私有子网里的实例需要访问互联网呢？那么就需要一个中间件的存在，让私有子网的实例能访问互联网，所以作为这个中间件的NAT，其就得放在公有子网。所以有思路把上面的这个网络配置出来了吗？是不是还是云里雾里？那是因为我们都忽略了一个东西——路由表。我们上面说的都是子网会与网关相连，公有子网连接互联网网关，私有子网连接NAT网关，那这之间是用什么来连接呢？答案就是路由表，它定义着我们子网、网关之间的流量流向，从而实现流量的定向流动。搞懂了上面这些问题，我们可以用角色扮演来更方便理解题目，在一个城堡（VPC）里面，有皇宫（私有子网），有可供活动的城区（公有子网），城堡通过大门（互联网网关）与外面相连，大门有路（路由表）连接城区，城区上会游走着保卫（NAT网关），只有通过保卫懂得怎么走（路由表）到皇宫。所以NAT是需要收费的（/滑稽）。实操[scode type="red" size=""]由于实验是布置下来的个人实验，请勿直接抄袭，我这里写实操只是为了记录我的学习历程，与分享我的实验经验。[/scode]实现思路在一个城堡（VPC）里面，有皇宫（私有子网），有可供活动的城区（公有子网），城堡通过大门（互联网网关）与外面相连，大门有路（路由表）连接城区，城区上会游走着保卫（NAT网关），只有通过保卫懂得怎么走（路由表）到皇宫根据这个角色扮演，我们可以知道题目要我们干什么了。那么怎么做呢？我的思路是：创建并配置VPC->创建并配置EC2->部署Web应用->连接RDS数据库创建并配置VPC创建VPC按照题目创建一个IPv4 CIDR：10.0.0.0/16的VPC创建子网先选择刚刚创建的VPC添加4个子网，需要注意的是每个子网的可用区与 IPv4 VPC CIDR 块 ，按照题目，4个子网应该分配到2个可用区，我这里选择的是us-east-1a与us-east-1b。然后按照题目填好IPv4 VPC CIDR 块即可。创建互联网网关这个简单，填个标签即可。创建NAT网关我们按题目要求，分配到公网子网1中。并且分配一个弹性ip（这是一个扣费坑，上面我们说了NAT在角色扮演中充当的是守卫，是要收钱的。而且不止NAT会扣费，弹性ip也会扣，很坑吧，具体可以看后面的介绍）。创建路由表创建两个路由表，分别为公有子网的路由表、私有子网的路由表配置路由表先分别给公有路由表与私有路由表关联对应的子网给公有路由表配置一条路由 打通子网与互联网网关的流量。让公有子网真正成为公有子网，给私有路由表配置一条与NAT网关的路由，然后保存即可。然后打开VPC页，可以查看到配置的VPC资源地图。这就是我们需要的VPC网络。注意事项NAT网关的扣费问题因为NAT网关服务是会扣费的，并且NAT网关服务还需要扣一个弹性IP的费用，所以在不使用RDS服务的时候不建议开启。我们可以在VPC控制面板中找到NAT网关与弹性IP服务，结束对应的服务。 然后在需要使用NAT网关的时候再重新开启一个，当然，到时候记得去改私有路由表的路由信息，把我们删掉的NAT网关改成新建的NAT网关 ，创建与配置EC2创建EC2这里没有特别需求，使用默认的AWS发行的Amazon Linux就行因为我需要在EC2上运行MySql与Spring Boot，为了避免后续内存不够的尴尬，实例类型我选择了2H2G的t3.small实例。实际上如果只是跑题目要求的简易项目，1h2g的t2.small实例即可，主要是MySql会吃IO，跑一点内存。网络设置需要仔细编辑，VPC选择我们前面创建的，子网按照题目选择公有子网2，分配公有ip，创建一个安全组。我这里设置开放3个端口，分别是22（ssh连接EC2实例需要使用）、3306（我本地连接EC2中搭建的数据库测试需要）、8989（我Spring项目的项目端口）。各位按照自己需求来，按需开放对应端口即可。存储与高级详情信息默认即可，我们的题目基本不涉及。然后启动实例即可完成EC2的创建。连接EC2这里我使用的本地SSH连接，当然你也可以使用AWS提供的AWS Cloud9 Bash 终端连接EC2，我习惯用SSH而已。SSH使用的。先打开我们的Learner Lab页面，下载账号对应的密钥PEM。回到EC2页面，复制EC2的公有ip打开FinalShell，新建一个SSH连接。名称与备注随便填，主机填写EC2的公有IP，端口使用SSH默认的22端口（注意，前面新建EC2实例的时新建安全组的时候要开放了这个22端口）。方法选择公钥，因为Amazon Linux出于安全问题，禁止了直接使用root登录，所以我们这里的用户名填写ec2-user。密钥导入前面Learner Lab页面下载的密钥PEM。双击我们创建的这个连接，即可连接上EC2.配置EC2环境因为AWS的RDS服务是需要扣费，在日常实验中我们可以选择在EC2中搭建一个MySQL项目供我们的项目使用，而我使用的B/S架构项目是由Spring Boot项目，所以还需要安装对应的JDK，如果你使用的SSM项目，那么你还需要在EC2上安装Tomcat。但我使用的是Spring Boot项目，仅用到MySQL与JDK，所以下面的步骤仅包含这两者的安装。安装MySQL这里直接贴对应的教程在 Amazon Linux 2023 上安装 MySQL 8 服务器和客户端 - 知乎 (zhihu.com)要注意的地方是步骤2：下载MySQL Yum仓库，不要使用wget来下载MySQL的YUM仓库，我们使用以下命令sudo yum install https://dev.mysql.com/get/mysql80-community-release-el9-5.noarch.rpm步骤5：保护MySQL安装，我的选择如下图，你可以自己翻译自行选择更改MySQL的访问权限（非必须）由于我需要在本地调试EC2的MySQL，所以我需要root用户能远程访问，就需要更改root的远程访问权限设置mysql root账户远程访问权限_mysq中lroot远程访问没有权限-CSDN博客安装JDK在AWS EC2 上安装JDK_aws ec2 安装jdk-CSDN博客我们完成这些命令即可，因为我使用的spring boot2项目，使用JDK8即可ok，我们的EC2实例这就完成了创建与配置了。创建AMI映像（非必须）前面配置EC2的环境是不是挺复制和麻烦？为了避免环境丢失、奔溃或者失效，我建议应该创建一个映像来保存我们好不容易配置好的EC2环境。选择我们的EC2实例，创建映像即可，这样就保存了一份纯净的MySQL+JDK环境的AMI了，后面就不用害怕后续EC2误删或者EC2中的环境出问题。部署Web应用由于我使用Spring boot2打包的工程，所以这里我的部署就比较简单了。只需要把项目打包成jar上传到EC2中，java -jar filename.jar运行即可。下面的步骤不一定适合全部人，具体按照自己的项目来确定打包方式，我仅演示我的项目如何打包。先打开项目的配置文件，检查mybatis的相关配置对不对，比如url上的host是否为localhost、连接的数据库EC2上是否存在（我测试项目的时候已经给EC2里面的MySQL加上了我的数据库awsdemo）、配置中的密码对否与EC2上MySQL的密码一致。打开项目的pom.xml文件看看依赖是否已经完成导入，然后打开右侧导航栏打开Meven页，先点一下依赖刷新，然后运行clean，运行完点package打包项目为jar包。这里要注意，我的项目为Spring Boot2的项目，所以可以直接使用Spring Boot的插件来直接打包，这里具体还得看自己的项目，选择对应的打包方式。这里还有一个需要注意的点，由于我部署Web应用之前曾经停止过EC2实例，重启后EC2的公有IP是有可能变化的，所以在SSH连接EC2之前最好去看看EC2的公有IP是否有变化。上传项目的jar包使用java -jar filename.jar运行jar项目所以ip:prot访问项目，成功渲染数据，即完成web应用的部署。连接RDS数据库上面我们部署的项目使用的数据源是EC2中的MySQL，按照题目，我们需要把数据源改成AWS的RDS服务。创建RDS子网组按照题目，我们的RDS需要部署在两个不同可用区的私有子网上，所以我们先在RDS控制台创建对应的子网组。需要注意的是子网选择私有子网，自己看CIDR 数据块判断即可创建RDS数据库新建RDS数据库，选择MySQL引擎版本选择8.0以上的就行可用性与持久性 按照题目要求选择 多可用区数据库实例数据库设置，为了方便，我直接按我EC2上的MySQL来设置了。实例类型，我那小项目基本不不需要m类最小的实例的2h8g这么高的配置，所以我选的t类存储同理，最低的配置就行连接配置，不需要ipv6，所以网络类型选IPv4，vpc选择我们创建的。子网组就选前面我们创建的，为了安全（题目要求），我们不公开数据库服务。安全组前面没配，所以我们这里选择新建一个，后面再去配。RDS代理的话没必要，证书用默认的就行记得关闭增强监控，文档要求关闭的。然后确定创建即可完成数据库的创建修改RDS的安全组进入我们创建的RDS服务，找到安全组规则，点击绑定的安全组在入站规则那你会发现，源默认给设置了我们本地ip，这就会导致我们的EC2实例无法访问。所以我们要新增一个入站规则，这里我们需要EC2访问RDS，所以我们把源设置为EC2绑定的安全组。测试RDS与EC2的连接打开我们的RDS服务，复制终端节点SSH登录EC2，使用mysql -h 终端节点 -u root -p登录，上面就是登录成功了如果你登录是以上情况，那么就是前面安全组出问题了，EC2连接RDS超时了，建议看看前面的教程。给RDS导入数据这一步我就省略了，无非就运行sql语句。创建RDS快照上一步我们已经完成了RDS里面数据的导入，为了避免扣费我们一般都是使用的EC2Web应用连接RDS注意：前面我已经在EC2上连接了连接上数据库了，并且完成了数据的导入。那么就该给我们的Web项目完成相关的数据库源配置了。因为我使用的Spring boot2打包的项目，可以直接使用外置的配置文件来部署项目。所以我这里比较简单，只需要复制项目的yml文件，改相关配置即可。复制终端节点修改yml文件上传到EC2使用，java -jar fileName.jar --spring.config.location=ymlPath运行即可奇技淫巧1.结束jar包运行，运行了jar包后，在项目内没结束进程，端口号就会一直被占用，你就没法再重新运行，只有结束了这个jar进程，端口才会释放出来。netstat -lnp|grep 项目端口号 kill -9 jar进程号2.新增一个Web应用的安全组端口，这样做的好处是可以区分我们项目的数据源。比如我这里，平时实验，使用EC2里面的MySQL，那就用8989端口启动。需要答辩，要改用RDS，就使用9090端口启动。
利用所学去学习。离散数学里面又学到了以前的集合，“包并补”什么的。那么作为一个要做“程序猿”的大学生，不得用自己的专业去解决其中的问题？所以这个包装类——“Set”诞生了。遇到的问题1.怎么去表达一个“集合”？2.怎么去解决“集合”的运算？解决思路1.一开始是真的无从下手，到底要怎么去弄这个“集合”？用一个字符串来表示一个集合吗？{1，2，3，4，5}这就是一个集合吗？想了一下，这里提到的集合是一个名词，是描述”集合“这个东西的概念。那么就可以用我们面向对象的思想来解释这个”集合“的概念了。想到这里就确定了怎么去写这个”集合“了，通过对”集合“这个类的封装来表达”集合“的这个东西。一个对象就是一个集合。2.集合的运算我的理解是 集合间元素的对比。所以基于这一点，整体的运算代码其就是对集合里元素的处理。目前我用的是for循环的嵌套，通过遍历来实现元素的对比，以后再尝试着的去优化。整体代码import java.util.*; public class Set { // 字符串数组用以存储字符串元素 private String[] a; // 默认为空集 public Set() { a = new String[0]; } // 构造方法 public Set(String str) { StringTokenizer strToken = new StringTokenizer(str, "{,}"); int num = strToken.countTokens(); a = new String[num]; for (int i = 0; i < num; i++) { a[i] = strToken.nextToken(); } } // 读取集合 public String getSet() { String str = ""; for (int i = 0; i < a.length; i++) { str = str + "," + a[i]; } return "{" + str.substring(1) + "}"; } // 获取集合元素数量 public int length() { return a.length; } // 交集 public Set intersection(Set set) { String str = ""; for (int i = 0; i < a.length; i++) { for (int j = 0; j < set.a.length; j++) { if (a[i].equals(set.a[j])) str = str + a[i] + ","; } } return new Set(str); } // 并集 public Set union(Set set) { String str = ""; for (int i = 0; i < a.length; i++) { for (int j = 0; j < set.a.length; j++) { if (a[i].equals(set.a[j])) { set.a[j] = ""; break; } } str = str + a[i] + ","; } for (int j = 0; j < set.a.length; j++) { str = str + set.a[j] + ","; } return new Set(str); } // 相等 public boolean equal(Set set) { if (a.length == set.a.length) { int num = 0; for (int i = 0; i < a.length; i++) { for (int j = 0; j < set.a.length; j++) { if (a[i].equals(set.a[j])) { num++; break; } } } if (num == a.length) return true; } return false; } // 包含 public boolean subset(Set set) { if (a.length <= set.a.length) { int num = 0; for (int i = 0; i < a.length; i++) { for (int j = 0; j < set.a.length; j++) { if (a[i].equals(set.a[j])) { num++; break; } } } if (num == a.length) return true; } return false; } // 补集 public Set complement(Set set) { String str = ""; for (int i = 0; i < a.length; i++) { for (int j = 0; j < set.a.length; j++) { if (a[i].equals(set.a[j])) { set.a[j] = ""; break; } } } for (int j = 0; j < set.a.length; j++) { str = str + set.a[j] + ","; } return new Set(str); } // 集合的差 public Set difference(Set set, Set set1) { Set newSet = set.complement(set1); return this.intersection(newSet); } // 集合的对称差 public Set symmetric(Set set, Set set1) { return this.union(set).difference(this.intersection(set), set1); } }
刚刚在桌面趴着睡醒，突然被问道”书里说接口可以有public default static private修饰的方法，为什么没有protected方法呢？“我一开始听到就有点懵，好有问题的问题，牛。这个问题就得从接口的设计和历史来说了吧。接口一开始设计就是为了面向所有的类，让其实现。作为接口的方法一开始是为了让实现类对其进行实现。而JDK8加入的default方法则是对接口的方法一个补充，使其更完善。而static方法则是突破类的限制，可以直接通过接口.方法来调用方法。到了JDK9在接口中加入private方法是为了给default提供可以重复使用的方法。那为什么把protected方法漏掉了？那protected方法有什么用呢？JAVA中protected是成员访问修饰关键字，表示所修饰的成员可以被本类和派生类中可以访问。那在接口中设计这样的一个方法又有什么用呢，接口是为了给实现类准备的，本来就是“公众的”，后来补充的进去的方法是为了更好提供“接口”的 服务。
常用方法方法备注public int [font color="#FF0000"]length[/font]()用来获取一个String对象的字符序列的长度。public boolean [font color="#FF0000"]equals/fontString对象调用equals(String s)方法比较当前String对象的字符序列是否与参数s指定的String对象的字符序列相同。public boolean [font color="#FF0000"]equalsIgnoreCase/font作用同上，但比较时忽略大小写。public boolean [font color="#FF0000"]startsWith/font判断当前String对象的字符序列前缀是否为参数指定的String对象s的字符序列。public boolean [font color="#FF0000"]endsWith/font判断当前String对象的字符序列后缀是否为参数指定的String对象s的字符序列public boolean endsWith(String s)判断。public int [font color="#FF0000"]compareTo/font按字典顺序比较两个字符串。 比较基于字符串中每个字符的Unicode值。 此String对象表示的字符序列按字典顺序与参数字符串表示的字符序列进行比较。 如果此String对象按字典顺序排在参数字符串之前，则结果为负整数。 如果此String对象按字典顺序跟随参数字符串，则结果为正整数。 如果字符串相等，结果为零。public int [font color="#FF0000"]compareToIgnoreCase/font作用同上，但比较时忽略大小写。public boolean [font color="#FF0000"]contains/font判断当前String对象的字符序列是否包含参数s的字符序列。public int [font color="#FF0000"]indexOf/font返回指定字符第一次出现的字符串中的索引。如果没有检索到，该方法返回的值为-1。public int [font color="#FF0000"]indexOf/font返回指定字符第一次出现的此字符串中的索引，从指定索引处开始搜索。如果没有检索到，该方法返回的值为-1。public int [font color="#FF0000"]lastindexOf/font返回指定字符最后一次出现的字符串中的索引。如果没有检索到，该方法返回的值为-1。public int [font color="#FF0000"]lastindexOf/font返回指定字符最后一次出现的字符串中的索引，从指定的索引开始向后搜索。如果没有检索到，该方法返回的值为-1。public String [font color="#FF0000"]substring/font返回一个字符串，该字符串是此字符串的子字符串。 子字符串以指定索引处的字符开头，并延伸到此字符串的末尾。public String [font color="#FF0000"]trim[/font]()返回一个字符串，其值为此字符串，删除了所有前导和尾随空格。推荐阅读：菜鸟教程Class String实例化一.String变量有2种常用的定义方式直接赋值实例化String str = "Hello World"直接赋值字面量的方式更省内存空间，因为JVM首先会去字符串池中查找是否存在"Hello World"这个对象，如果不存在，则在字符串池中创建"Hello World"这个对象，然后将池中"Hello World"这个对象的引用地址返回给"Hello World"对象的引用str，这样str会指向池中"Hello World"这个字符串对象；如果存在，则不创建任何对象，直接将池中"Hello World"这个对象的地址返回，赋给引用str。所以如果多个直接赋值字面量的引用拥有相同的字面量，那么这些引用其实是指向同一个对象，也就是指向相同的一块内存空间。String str0 = "Hello World"; String str1 = "Hello World"; String str2 = "Hello World";构造方法实例化（这里以调用public String(String original)为例 )String str = new String("Hello World")这个就很有趣了，先说说我一开始对构造方法实例化一个字符串的理解，一开始我认为这种方式会比我们的的一种用字面量赋值的方式要快，毕竟这里是直接new一个实例出来，没有第一种方式的那样会先在常量池里面寻找已有字符串的步骤。但后来通过对String类的深入发掘，我才发现我大错特错了。通过查询资料和反编译，发现用new关键字新建一个字符串对象时，居然会新建1个或者2个String的对象。why 一个new 会有2个对象？真是离谱他妈给离谱开门。原文地址JDK1.8字符串常量池里存的是String对象还是引用？查了好多blog，他们写的基本都是这个现象，却没有解释为什么会有这个现象，为什么new一个String对象还会和直接字面量赋值一样，寻找一遍已有的字符串对象。这个现象纠结了好久，才从最开始，最直接，最容易被忽略的地方找到答案。构造方法， public String(String original) { this.value = original.value; this.coder = original.coder; this.hash = original.hash; }看方法头的参数列表，(String original)这里实际就是定义一个新的局部变量 Sring original = 实参，那这就回到了第一种方式。所以这就是为什么说new一个String字符串会新建1个或者2个String对象了。所以，日后在定义一个字面量的字符串应该尽量使用字面量赋值的，因为new实际上也是通过这种方式，而且它还多要了一份内存空间。
《Java 2实用教程第6版》中P176中有4点是介绍内部类的，但我总觉得前两点不怎么对劲。[scode type="share" size=""] (1)内部类的外嵌类的成员变量在内部类中仍然有效，内部类中的方法也可以调用外嵌类中的方法。[/scode]先说说我自己的理解，为什么内部类的外嵌类的成员变量在内部类中仍然有效？为什么内部类中的方法也可以调用外嵌类中的方法？因为ava就是这样设计的，所以是这样的？这就回顾一下，我们平时是怎么调用类里面的实例方法与成员变量？用对象是吧？那对象怎么找到这个实例方法或者成员变量呢？引用，所以我认为这里的内部类的外嵌类的成员变量在内部类中仍然有效，内部类中的方法也可以调用外嵌类中的方法是因为在实例化内部类的过程中，内部类的对象包含了外部类对象的引用。所以他就能调用外嵌类的实例方法和成员变量了。[scode type="share" size=""] (2)内部类的类体中不可以声明类变量和类方法。[/scode]这句话我认为是错的，静态内部类呢？静态内部类怎么就不能有类方法了?然后再仔细想一想，第一点就又有问题了，静态内部类的实例化是不需要外嵌类对象的，也就是说静态内部类对象是没有外嵌类对象的引用的。所以静态内部类对象无法操作外嵌类的实例方法与成员变量。所以，可以确定，这书里对内部类的4点介绍是指的非静态内部类。还有一点是我在测试的时候遇到的class Embeddedclass { class InsideClass { static int i = 1; void f() { System.out.print("i am a Inner class method." + i); } } } public class InsideTest { public static void main(String[] args) { Embeddedclass g = new Embeddedclass(); Embeddedclass.InsideClass gg = g.new InsideClass(); gg.f(); } }仔细看，这InsideClass类是一个非静态的内部类，然后他还有一个static的变量i，我一开始以为它会报错，想着会有什么提示运行了它。但奇迹发生了，不仅编译成功，而且运行也没问题。那书里为什么说内部类的类体中不可以声明类变量和类方法？这问题我想了好久，最后也只能找度娘。[scode type="yellow" size=""]在JDK1.8 以后，没有final修饰，effectively final的即可。什么意思呢？就是没有final修饰，但是如果加上final编译器也不会报错即可。[/scode]Java -- 成员内部类不能含有static成员（汇总）匿名类的特点有哪些_Java 干货之深入理解内部类
一、具体程序[collapse status="false" title="抽象Meeting类"]public abstract class Meeting { //会议id private int id; //会议成员组别 private int member; //会议开始时间戳 private long time; //会议标题 private String title; //会议备注 private String remarks; //会议状态 -1:结束 0:待开始 1:进行中 private int state; //会议类型 0：线下 1：线上 private int attribute; public Meeting(int member,long time,String title,String remarks,int attribute) { this.member=member; this.time=time; this.title=title; this.remarks=remarks; this.attribute=attribute; this.state=0; } public int getid() { return id; } public int getmember() { return member; } public long gettime() { return time; } public String gettitle() { return title; } public String getremarks() { return remarks; } public int getstate() { return state; } public int getattribute() { return attribute; } public void setId(int id) { this.id=id; } public abstract void getMeeting(); public void setstate() { this.state=-1; } }[/collapse][collapse status="false" title="线下会议类OfficialMeeting"]public class OfficialMeeting extends Meeting { private int room; OfficialMeeting(int member,int room,int time,String title,String remarks){ super(member,time,title,remarks,0); this.room=room; } public void getMeeting() { System.out.println("会议ID:"+getid()); System.out.println("会议类型:"+getattribute()); System.out.println("会议标题:"+gettitle()); System.out.println("会议状态:"+getstate()); System.out.println("会议地点:"+room); System.out.println("会议时间:"+gettime()); System.out.println("会议备注:"+getremarks()+"\n"); } public int getroom() { return room; } }[/collapse][collapse status="false" title="线上会议类OnlineMeeting"]public class OnlineMeeting extends Meeting{ OnlineMeeting(int member,int time,String title,String remarks){ super(member,time,title,remarks,1); } public void getMeeting() { System.out.println("会议ID:"+getid()); System.out.println("会议类型:"+getattribute()); System.out.println("会议标题:"+gettitle()); System.out.println("会议状态:"+getstate()); System.out.println("会议时间:"+gettime()); System.out.println("会议备注:"+getremarks()+"\n"); } }[/collapse][collapse status="false" title="系统类Demo"]public class Demo { //抽象会议，用以统一操作 Meeting m; //会议数组 Meeting[] arr = new Meeting[100]; //房间数组 final int[] roomarr = { 101, 102, 103, 104, 105 }; // 新增会议 public void newMeeting(Meeting m) { int i, idmax = -1; for (i = 0; i < arr.length; i++) { if (arr[i] == null) { if (i != 0) idmax = arr[i - 1].getid(); break; } } m.setId(idmax + 1); arr[i] = m; } // 查询所有会议 public void showMeeting() { for (int i = 0; i < arr.length; i++) { if (arr[i] == null) break; arr[i].getMeeting(); } } // 查询单独会议 public void getMeeting(int id) { System.out.println(); arr[id].getMeeting(); } // 删除会议 public void deleteMeeting(int id) { int j = 0; //寻找对应id的数组下标 for (int i = j; i < arr.length; i++) { if (arr[i] == null||arr[i].getid()==id) { j=i; break; } } //判断改id是否为空 if (arr[j] == null) { System.out.printf("\n未找到id为%d的会议，删除失败\n\n", id); } //利用覆盖，替换数组数据 for (int i = j; i < arr.length; i++) { if (arr[i] == null) { break; } arr[i] = arr[i + 1]; } } // 修改会议 public void modifyMeeting(int id, Meeting m) { arr[id] = m; arr[id].setId(id); } // 结束会议 public void setMeetingstate(int id) { for (int i = 0; i < arr.length; i++) { if (arr[i] == null) { System.out.printf("\n未找到id为%d的会议，结束失败\n\n", id); break; } else if (arr[i].getid() == id) { arr[i].setstate(); break; } } } // 查询空余房间数组 public int[] getroom() { // 已预约房间数组 int[] reservedRoom = new int[roomarr.length]; // 空余房间数组 int[] assemble = new int[roomarr.length] ; for (int i=0;i<roomarr.length;i++) { assemble[i]=roomarr[i]; } //定义中间变量，用以计算数组中非零数据 int res=0; for (int i = 0; i < arr.length; i++) { if (arr[i] == null) { break; } //查询线下的房间 if (arr[i].getattribute() == 0 && arr[i].getstate()!=-1) { OfficialMeeting official = (OfficialMeeting) arr[i]; int j = 0; for (j = 0; j < reservedRoom.length; j++) { if (reservedRoom[j] == 0) break; } reservedRoom[j] = official.getroom(); } } //对比重复房间 for (int q = 0; q < reservedRoom.length; q++) { if (reservedRoom[q]==0) break; for (int qq = 0; qq < roomarr.length; qq++) { if (reservedRoom[q] == roomarr[qq]) assemble[qq] = 0; } } //去零重组数组 for (int i = 0; i < assemble.length; i++) { if(assemble[i]!=0) { res++; } } //根据数组中非零元素新建空房间数组 int freeRoom[]=new int [res]; int j=0; for (int i = 0; i < assemble.length; i++) { if(assemble[i]!=0) { freeRoom[j]=assemble[i]; j++; } } //返回一个数组数据 return freeRoom; } }[/collapse][collapse status="false" title="测试类Test"]public class Test { public static void main(String[] args) { Demo d = new Demo(); //新建会议 成员组别 房间号 时间戳 标题 备注 d.newMeeting(new OfficialMeeting(1, 101, 16549854, "部门会议1", "普通会议")); d.newMeeting(new OnlineMeeting(1, 16549854, "工作会议2", "普通会议")); d.newMeeting(new OfficialMeeting(1, 105, 16549854, "部门会议3", "普通会议")); d.newMeeting(new OnlineMeeting(1, 16549854, "工作会议4", "普通会议")); // 输出会议列表 d.showMeeting(); // 按id查会议 // d.getMeeting(1); System.out.print("空余房间"); for (int i = 0; i < d.getroom().length; i++) { System.out.print(d.getroom()[i]+"\t"); } // 删除某会议 d.deleteMeeting(0); // 结束会议 d.setMeetingstate(0); d.setMeetingstate(1); // 输出会议列表 d.showMeeting(); System.out.print("空余房间"); for (int i = 0; i < d.getroom().length; i++) { System.out.print(d.getroom()[i]+"\t"); } } }[/collapse]二、复盘反思题目：运用所学知识编写一个“会议管理程序”个人分析：1.“会议管理程序”，其实就是管理会议的程序。“会议”作为程序的主体单位，编写一个对这个主体单位进行管理操作的程序。2.作为主体的“会议”，不可能只有一种类型，其有着五花八门的类型。3.“会议”是程序的主体单位，不是单独一个出现的，是拥有数量的，所以需要“管理”。处理思路：1.把“会议”与“管理”区分开来，分别进行编写。2.“会议”是一个概念，那么我们可以用抽象类统筹这个概念，再分别对具体的分类进行编写，然后通过多态来实现我们需要的功能。3.既然我们用类来描述“会议”，那么一个个实体的会议就是一个个对象，对 对象进行存储，我们可以使用我们的对象数组。遇到的问题1.对象数组的存储与读取，子类对象为什么可以存进父类对象数组里面？[post cid="22" cover="" size=""/]2.对于方法重载与多态的一些思考。[post cid="18" cover="" size=""/]
问题一 为什么对象数组的类型要一致？在java2使用教程第6版P106中写道：数组是相同类型变量按顺序组成的集合。既然对象数组存的是对象的引用，而且初始化的时候是null的，为什么我不可以随便把其他类型的对象赋值给这个数组呢？那样多爽呀，一个数组可以存一大堆对象了，直接变博物馆。那回忆一下基本数据类型的数组，没少用过for循环吧？利用循环，我们可以遍历数组里面的数据，那代入对象数组，我们也可以用for循环遍历对象的各个属性与方法。想一下，如果这个数组存的是不一样类型的对象，那你怎么遍历？所有编译器会规定你所赋值的对象要和数组类型一样。如果我要存的所有对象都有相应的属性和方法，那我可以存一个对象数组里面了吧？那你能保证所有的对象真的一丝不误的有相同的属性和方法给你在后面调用吗？为了杜绝这种不应该的错误，编译器只能按类来约束对象数组的数据，一个类出来的对象肯定存在相同的属性和方法。问题二 为什么子类对象可以存进父类对象数组里面？为什么呢？直接这样想其实是很难想明白的，那么换个角度，为什么非子类对象不可以存进父类对象数组里面？去测试一下，class Father {} class One extends Father {} class Two {} public class Test2 { public static void main(String[] args) { Father[] arr = new Father[5]; arr[0] = new One(); arr[1] = new Two(); } }这个结果就可以看到这个问题的答案了，编译器会自动将你赋值的非父类对象进行向上转型，转换成对象的上转型对象再存进对象数组里面。关于对象数组的一些基本使用可以看一下这个Java中对象数组的使用方法详解。