如何降低 Flink 开发和运维成本?阿里云实时计算平台建设实践
摘要:本文整理自阿里云高级技术专家,Apache Flink Contributor 周凯波(宝牛),在 FFA 2022 平台建设专场的分享。本篇内容主要分为四个部分:业务背景平台架构演进平台核心功能建设未来规划点击查看直播回放和演讲 PPT一、业务背景在阿里集团内部,实时业务的场景主要分为四大类。第一个场景是实时 ETL,这是目前使用最广泛的场景,比如在集团的实时数据公共层业务中会对数据做统一的实时清洗、转换,为其他部门提供加工好的公共数据,便于业务部门进行二次分析。第二个场景是实时监控,比如安全部门需要实时监测和分析用户行为或事件,基于风控规则进行预警。第三个场景是实时在线系统,它在淘宝的搜索和广告系统中使用很广泛,主要是给用户实时推荐个性化的商品和服务。第四个场景是实时报表,比如每次大促活动的媒体大屏,以及生意参谋这种给商家的运营工具都会提供实时报表功能。在集团内部,比较典型的数据处理链路分为四步。首先是数据采集,数据的来源有 TT 等消息队列过来的日志数据,来自数据库的 Binlog 数据,以及消息中间件的数据。这些数据都会经过 Flink 进行处理,产出实时明细数据和汇总数据。处理完的数据会写入 Hologres/ODPS/ADB/Lindorm 等存储和在线分析系统,既可以直接为实时大屏,报表统计等业务场景提供数据服务,也可以由 Flink 进行再一次的分析。上图是实时计算 Flink 在集团内的的业务规模。在大促期间,计算资源的峰值接近 200 万 core,有 3 万多个实时任务,服务了超过 90 个部门,在大促期间的峰值处理能力达到 69 亿条每秒。二、平台架构演进阿里实时计算平台的发展分为四个阶段,在 2013 年之前,处于一个百花齐放的状态,没有统一的实时计算平台。2013 年,基于自研 Galaxy 引擎的 Bayes 平台上线,开始服务数据魔方,双 11 等实时业务场景。2017 年,集团将内部广泛使用的三大实时计算引擎统一,合力打造 Blink 引擎,这时候基于 Blink 的全新的 Bayes 平台上线。随后的几年,在阿里将 Blink 代码贡献给 Flink 社区之后,开始打造内部的企业级增强 Flink 引擎。到了 2021 年,基于 Flink 引擎的云原生大数据平台阿里云实时计算平台 Realtime Compute 上线。实时计算平台 2.0 的特点是一个用户一个 Hadoop 集群,集群中的计算节点是 ECS 机器。这套基于 Hadoop 生态的架构,虽然在当时能做到较好的资源隔离,但是也带来了两个问题:多租户成本比较高和资源弹性不足。我们需要运维很多个 Hadoop 集群,除了运维成本高之外,Hadoop Master 节点也无法共用,造成管控资源的浪费;其次由于底层是基于 ECS 的资源粒度,扩缩容时间很长,用户也无法按量付费去购买 1 个核的 CPU 资源,用户的使用成本较高。为了实现轻量化的多租户和灵活的资源弹性,我们需要将实时计算平台从 Hadoop 生态转型到基于 K8s 的云原生时代。实时计算平台 2.0 到 3.0 的升级包括四个方面:第一个方面是引擎内核从 Blink 引擎升级到了 Flink 引擎,和社区接口兼容,能够随时获取社区最新的功能。同时也能通过插件化机制,做到企业级内核的增强。第二个方面是资源底座从 Yarn 切换到了 K8s 调度,能实现 Serverless 化,便于统一资源池和统一调度。第三个方面是平台架构也升级为微服务架构,具备灵活的可伸缩和可扩展能力。第四个方面是技术品牌的升级,阿里云与 Flink 社区原班人马一起打造全球统一的大数据品牌 Ververica,进一步扩大 Flink 技术在全球范围内的影响力。3.0 平台的技术栈,包括五层。最下面是 IaaS 层,主要是硬件基础设施,包括物理机、虚拟机、神龙裸金属和 ARM 机型等。往上是存储层,象 OSS,HDFS 这些系统主要用来存储 Flink 作业的 Checkpoint/Savepoint 数据,以及用户作业的 jar 包等资源。再往上是调度层,由 K8s 进行统一调度。调度层之上是引擎层,是阿里基于开源 Flink 打造的企业级增强引擎,比如自研了状态后端存储 GeminiStatebackend。最上面一层是平台层,阿里云实时计算 Flink 平台通过 Gateway 作为统一入口,内部分为 AppManager,SQLService 和 AutoPilot 等各个微服务。3.0 平台是采用的是基于微服务的分层架构,技术特点是容器化,微服务化和插件化。Gateway 作为整个平台的入口,负责用户认证和鉴权,通过 API 路由统一透出平台能力。AppManager 是一个 region 化的中心化管控服务,负责作业生命周期的管理。在计算层,基于 K8s 之上的 VC 隔离技术,每个用户看到的都是一个虚拟的 K8s 集群,用户之间的操作互不干扰,实现了比较好的多租户隔离。同时,基于 K8s 的能力可以做到容器级别的资源弹性,比如可以支持申请一个核的 CPU 资源。3.0 架构将功能拆分成一个个的微服务,每个服务能独立开发部署和扩缩容。这样的好处是能比较方便地做到服务能力的弹性扩展。另外,不同团队可以负责不同微服务的开发,互不影响,提高协作效率。最后通过插件化来对接各种外部系统,具备很好的灵活性。3.0 架构是一个基于 VC 的 Flink 硬多租 Serverless 架构,隔离性非常好。首先,每个用户的 AppAgent 和 SQLService 这些管控服务是部署在用户自己的虚拟集群 VC 里面,管控上做到了隔离。其次,每个用户有一套自己的 Tenant Master,对 K8s 的访问互不干扰,做到了 Master 的隔离,而底层的 Super Master 是共享的,能节省管控成本。最后,通过 kata 安全容器,云盘,VPC 和弹性网卡 ENI 这些阿里云的基础设施可以做到计算,存储和网络的隔离。在资源弹性方面,基于容器化技术实现了以 pod 为粒度的资源弹性,能满足用户按量付费的购买需求,降低用户成本。最后,由于集群资源的管理下层到了底座,平台方不用关心底层的集群和硬件,大大降低了运维成本。三、平台核心功能建设作为一个全托管的大数据平台,最核心的功能是对作业全生命周期的管理,包括作业的开发、调试,运行、监控告警、错误/性能问题的诊断、性能的调优。用户在平台上的活动都是围绕这些操作进行的。在 3.0 平台上,用户可以使用默认的开发平台,通过功能丰富的 SQL 编辑器进行 SQL 的开发调试,同时平台也具备良好的被集成能力,第三方平台可以通过 OpenAPI 进行接入。比如像菜鸟物流云、Dataphin 等都可以往 Flink 上提交作业。我们知道 Flink 是一个流批一体的计算引擎,因此我们在平台上也提供了流批一体的开发体验,用户只需要写一个 SQL,就可以同时运行流和批作业,极大简化 Flink 作业的开发运维成本。其次是 SQL 调试能力,通过和 Flink Session Cluster 结合,能够做到秒级别的 SQL 调试,大大提升了用户的开发效率。在作业运维方面,平台有两个目标,分别是全托管和免运维。全托管:用户不需要关心集群运维和 Flink 作业具体的提交流程,平台帮用户管理好作业,比如 Flink 作业生命周期管理,作业 Checkpoint 和 Savepoint 这些状态集的管理,以及指标监控和告警等。免运维:平台提供一些白屏化的运维工具降低用户的运维成本。以作业探查为例,平台提供了日志归档、分类、基于 Arthas 的火焰图、基于 JMX 的内存动态和线程动态,帮助用户去分析定位作业的运行瓶颈。但是这些工具对用户还是有一定的使用门槛,因此平台提供了 AutoPilot 智能诊断调优系统进一步达到免运维的目的。Flink 作业如果想在有限的资源使用下达到最优的性能,需要对不同算子的内存和并发度等参数分别进行配置。在社区开源版本中,用户只能通过配置文件进行全局的配置,无法精确控制作业资源,这样会造成资源浪费。另外对于 DataStream 作业,每次进行资源配置都需要修改代码,打包和部署都不够灵活。针对这个问题,我们通过 JSON 文件描述作业的执行计划,实现了算子级别的 CPU/Mem 和并发度的精细化资源配置,同时提供可视化的方式方便用户进行编辑,用户也可以通过 UI 界面配置算子的 CPU/Mem 和并发度。这样对于 SQL 和 DataStream 作业都能提供同样的用户体验。通过细粒度资源调优功能,对于一些专业用户来说,能够将作业性能调到最优,同时降低资源的成本。接下来以 SQL 作业为例,介绍一下细粒度资源调优的基本原理。在 SQL 文本到 JobGraph 的翻译中间过程中,会经过一步 StreamGraph 的生成。我们可以通过 JSON 文件对 StreamGraph 中的各个算子的资源进行描述,同时提供可视化编辑的方式。用户通过可视化界面,可以修改算子并发/内存,还可以决定前后算子是否 Chain 在一起,甚至还可以调整 SlotSharingGroup 来决定算子是否共享同一个 Slot。用户调整好后的 JSON 文件会应用到 StreamGraph 之上,生成最终的 JobGraph 去运行。细粒度资源调优虽然可以将作业性能调到最优,同时降低资源成本,但是每个作业都手工配置也比较繁琐。此外,用户经常会遇到类似的问题,比如 Flink 调优参数众多,需要了解底层细节;业务的流量有波峰和波谷,作业配置需要手工切换;作业运行不稳定,定位困难;集群或者底层的硬件有问题,导致排查问题无从下手等等。针对这些问题,在平台上的解法是智能诊断和智能调优,让系统自动化的做一些判断,尽可能降低人工操作和干预,让系统自动去做一些判断。比如,作业的 Checkpoint 失败,或者运行中发生了数据倾斜,这些平台都可以监控到,然后反馈给用户。智能诊断调优系统分为多个模块,包括 Agent Manager,它负责管理所有 Agent 节点,Agent 节点负责对于某个任务进行具体的诊断和调优。它包括定时调度器、检测、分析、选择和执行等各个服务。智能诊断调优系统的工作原理是,从各个地方收集作业的运行信息,比如日志,Metrics 指标,以及集群层面的硬件和网络等信息。将这些信息汇总后,就可以分析出当前作业的症状,然后从平台积累的规则库中选择对应的方案去执行。比如,发现某个节点的性能不够,建议用户调大作业的并发度等配置参数;比如发现 JobManager 或 TaskManager 节点的 GC 严重,建议用户调整内存参数等等。智能诊断调优系统的业务收益体现在以下三个方面:自适应流量的高峰和低谷,降低业务成本。在每年的 618、双十一等大促期间实现资源的弹性回收,无须人工干预。实现故障的自动诊断,降低运维成本,最终帮用户达到降本增效的目的。在 Flink 作业运维过程中,经常会遇到平台上的运维功能很多,但是不知道什么时候用哪个,比较分散。比如作业出现问题后,是先看日志、Metrics 指标,还是 Flink Web UI。其次,不同类型的异常需要处理的紧急程度不一样。比如 Failover 异常,如果不是大面积出现,只是偶尔出现一次,不需要太关心。如果是 Checkpoint 超时,偶尔出现一次问题也不大,但是长时间出现就需要处理了。否则作业 Failover 后回追数据的成本会比较高。但是像 Connector 连接异常或者资源不足,会影响到作业的结果产出,就需要立即进行处理。此外,不同人员的 Flink 专业知识背景也不一样,因为并不是每个人都清楚该如何运维 Flink 作业。针对这些问题,在平台上的解法是,引入打分机制量化评估作业的风险程度,将健康分作为统一的入口,打通从现象到决策的端到端链路。健康分的工作原理是通过智能诊断服务获取日志、Metrics、集群等信息,诊断出作业的症状,然后健康分服务进行统一打分,将作业判断为高、中、低三个风险,并给出具体的 Action 告诉用户应该怎么操作。这样,用户不需要掌握太多的专业知识,只需要看到健康分就能一目了然清楚的知道哪些作业需要处理,以及需要做什么操作。在健康分体系中,每个作业的初始分数是满分 100 分,当作业出现一个高等级风险时扣五分,中等级风险时扣三分,低等级风险时扣一分。以上图中的作业为例,作业 A 是 95 分,处于低风险状态,对应的 Action 是加大 akka 超时时间,这时候用户并不需要进行紧急处理。作业 B 是 72 分,处于中风险状态,对应的 Action 是调大 3 号节点并发,用户可以稍后进行处理。作业 C 是 46 分,处于高风险状态,对应的 Action 是下游 Kafka 服务访问异常,请检查服务是否正常。这时候数据已经无法正常产出了,用户需要进行紧急处理,否则会引发生产故障。除了前面介绍的在架构上做到了硬多租的隔离保证安全之外,平台还提供了很多企业级的安全能力建设。在项目空间级别做了权限的隔离,每个用户只能访问自己所在的项目空间的资源。每个项目空间的存储是隔离的,比如采用不用的 OSS 对象存储 Bucket,对 OSS 的访问也采用临时 Token,而不是固定的账号密码,这样能保证存储的安全。通过实现基于角色的访问控制(RBAC),在平台上定义 Viewer、Editor、Owner 和 Admin 等角色,每种角色都有自己的权限。比如 Viewer 只能查看数据,无法启停作业;Editor 可以启停作业,但没有权限进行管理的操作。在权限认证方面,通过接入 OIDC 这种标准的身份认证机制,可以实现单点登陆。通过 OIDC 不仅可以接入阿里云 RAM 账号体系,也可以通过 DEX 插件接入 Github/Google 等其他账号体系。在 API 访问认证方面,针对不同场景,实现了基于 Token 和基于 AK/SK 两种 API 访问认证。比如在 On-Premise 场景中,直接通过 Token 访问平台的 Resuful API。在公共云场景中,第三方平台通过 AK/SK 和 SDK 这种安全的方式访问平台。最后,还可以对账号密码等敏感信息进行加密。比如用户在 Flink SQL 作业中看到的账号密码等敏感信息都是加密后的,平台只有在真正提交作业前才会做解密。四、未来规划平台未来的规划大体分为三个方向:体验优化:比如更顺滑的流批一体开发体验,更好的自助运维能力。功能完备:元数据的管理,SQL 的调试等都需要继续加强。场景丰富:支持更丰富的场景,如实时数仓,实时风控和实时样本等。点击查看直播回放和演讲 PPT更多内容活动推荐阿里云基于 Apache Flink 构建的企业级产品-实时计算Flink版现开启活动:99 元试用 实时计算Flink版(包年包月、10CU)即有机会获得 Flink 独家定制卫衣;另包 3 个月及以上还有 85 折优惠!了解活动详情:https://www.aliyun.com/product/bigdata/sc
数据湖存储的安全写入之道
作者:焱冰@阿里云焱冰背景数据湖的兴起,给数据存储带来了一轮新的革命。越来越多的公司选择将存储切换到云上对象存储。因为云上对象存储往往意味着大容量、低成本、易扩容。说到对象存储,必然涉及到 S3 协议,S3 协议已经事实上成为对象存储的通用协议。不过,市面上不少数据平台公司,也会选择基于 S3 协议又兼顾 Hadoop 使用习惯的 S3A Connector,比如 Databricks 在对象存储上提供的表数据结构 Delta Lake。我们就以 Hadoop 社区中的 S3A Connector 的实现为切入,来分析一下数据湖写入路径的安全性。Hadoop S3 的写入支持因为 S3 协议本身不支持增量写入,因此 S3A 实现时默认的写入方式是先通过缓存到本地,最后在文件 close 后再上传到对象存储。但是,这种默认的方式并不一定高效,对大文件来说,在 close 被调用前,本地已经缓存大量的数据,这样会造成 close 操作非常耗时,文件写入整体看也不高效。从 Hadoop 2.8.5 版本开始,可以通过设置 fs.s3a.fast.upload 为 true,打开快速上传来优化写入路径。打开后,可以边写本地缓存块,边将满足大小的块异步上传(默认 100M 一个块)。这样也满足了对象存储中分阶段上传接口的一些限制,比如单个块不能小于 5M,分块总数不能大于 10000。通过阅读 Hadoop 2.8.5 相关源码,我们可以发现打开 fs.s3a.fast.upload 后,S3AFileSystem 在创建文件时会打开 S3ABlockOutputStream(Hadoop 3.x 也有类似的 S3AFastOutputStream)随后,S3ABlockOutputStream 在处理 write、flush 等操作时,则会调用一个抽象的 S3ADataBlock 来执行。而 S3ADataBlock 则可由三种工厂方法来创建,分别创建基于堆内存的 ArrayBlock、基于磁盘的 DiskBlock,或者基于堆外内存的 ByteBufferBlock。选择哪种工厂,由 fs.s3a.fast.upload.buffer 这个配置项控制,默认为磁盘(disk)。其他两种可选配置为堆内存(array)和 堆外内存(bytebuffer)。磁盘的问题通过了解 Hadoop 社区中 S3A 的实现,我们发现借助磁盘缓存数据是常见甚至默认的行为。因为这样可以减少内存占用,缓存更多的数据。但是,这样也带来了磁盘本身的阿喀琉斯之踵 -- 磁盘的稳定性问题。在数据存储领域,磁盘的问题往往非常令人头疼。比如磁盘写满,磁盘坏道问题,还有偶现的磁盘数据比特反转导致的数据安全性问题。哪怕单块磁盘的可靠性非常高,但由于磁盘出现问题的概率会随着磁盘数的提升而变大,这会使数据安全性蒙上一层阴影。对于R个副本的情况,设磁盘的年故障率为P,磁盘数为N,则整个机群有C ( N, R ) = N! / ( R! * ( N- R )! ) 种 R 副本的组合方式。机群数据总量为 M,分片大小为 T,那么有 R 个磁盘同时损坏造成数据丢失的概率是:* 引用于《磁盘故障与存储系统的年失效率估算》因此,要保证写路径的数据安全型,我们不能完全依赖底层存储介质的保证。仍需要我们在数据写入时就做一些努力。我们先来做一些实验来看看 S3AFileSystem 在这些问题上的表现。模拟磁盘 IO 问题修改 core-sites.xml 中的 fs.s3a.buffer.dir 指向 /dev/vdc 所在的路径,比如我机器上的 /data2/ <property>
<name>fs.s3a.fast.upload</name>
<value>true</value>
</property>
<property>
<!-- 本地 buffer 缓存目录,不存在会创建 -->
<name>fs.s3a.buffer.dir</name>
<value>/data2/tmp/</value>
</property>创建并运行 stap 脚本,对所有在 /dev/vdc 上写操作的返回 IO Error#!/usr/bin/stap
probe vfs.write.return {
if (devname == "vdc") {
$return = -5
}
} $ stap -g io_errno.stp执行写入程序 demo,验证 stap 脚本有效$ dd if=/dev/zero of=test-1G-stap bs=1G count=1
$ hadoop fs -put test-1G s3a://<your-bucket>/返回结果:put: 输入/输出错误可以发现相关操作能正确抛出 IO 错误。模拟磁盘比特反转魔改 libfuse passthrough 中的 write 方法,并将 /data2/ 通过 fuse 挂载到 /mnt/passthrough$ mkdir -p /mnt/passthrough/
$ ./passthrough /mnt/passthrough/ -omodules=subdir -osubdir=/data2/ -oauto_unmount修改 core-sites.xml 中的 hadoop.tmp.dir 指向 /mnt/passthrough<property>
<name>fs.s3a.fast.upload</name>
<value>true</value>
</property>
<property>
<!-- 本地 buffer 缓存目录,不存在会创建 -->
<name>fs.s3a.buffer.dir</name>
<value>/mnt/passthrough/</value>
</property>执行写入程序 demo,验证上传内容的正确性。$ mkdir -p input output
$ dd if=/dev/zero of=input/test-1G-fuse bs=1G count=1
$ hadoop fs -put input/test-1G-fuse s3a://<your-bucket>/
$ hadoop fs -get s3a://<your-bucket>/test-1G-fuse output/
$ md5sum input/test-1G-fuse output/test-1G-fuse返回结果:cd573cfaace07e7949bc0c46028904ff input/test-1G-fuse
37eb6e664e706ea48281acbd4676569e output/test-1G-fuse可以发现,输入和输出的数据并不一致。综上,通过 Hadoop S3AFileSystem 写入可以发现磁盘 IO 问题并正确抛出异常,但无法发现磁盘比特反转问题。网络的问题既然磁盘写入有问题,那我们使用内存写入是否就一定可以避免踩坑呢?答案是不能,还可能有网络问题。Amazon S3 在 2008 年就曾因为网络问题导致的比特位反转引发过重大事故。后来,大家分析这种问题多发生于两端间隔多个路由器的情况,路由器可能因为硬件/内存故障导致单/多比特位反转或双字节交换,这种反转如果发生在 payload 区,则无法通过链路层、网络层、传输层的 checksum 检查出来。因此 Amazon S3 在这次事故中吸取的教训是,要通过在应用层给所有东西都添加 checksum 来保证数据正确性。让我们来做一个实验,来看看 S3 是怎么做到 Checksum all of the things的,又是否能防止网络比特反转或者网络丢包呢?模拟网络比特反转安装 mitmproxy$ pip3 install mitmproxy
$ mitmproxy --version
Mitmproxy: 5.3.0
Python: 3.6.8
OpenSSL: OpenSSL 1.1.1h 22 Sep 2020
Platform: Linux-3.10.0-1160.71.1.el7.x86_64-x86_64-with-centos-7.9.2009-Core利用 mitmdump 反向代理 s3a endpoint,并篡改其中的写请求。编写 addons.pyfrom mitmproxy import ctx, http
import json
import time
import os
class HookOssRequest:
def request(self, flow: http.HTTPFlow):
print("")
print("="*50)
print("FOR: " + flow.request.url)
print(flow.request.method + " " + flow.request.path + " " + flow.request.http_version)
print("-"*50 + "request headers:")
for k, v in flow.request.headers.items():
print("%-20s: %s" % (k.upper(), v))
if flow.request.host == "<your-bucket>.oss-cn-shanghai-internal.aliyuncs.com" and flow.request.method == "PUT":
clen = len(flow.request.content)
rbit = ord('a')
clist = list(flow.request.content)
origin = clist[clen - 1]
clist[clen - 1] = rbit
updated = clist[clen - 1]
flow.request.content = bytes(clist)
ctx.log.info("updated requesting content pos(" + str(clen - 1) + ") from " + str(chr(origin)) + " to " + str(chr(updated)))
def response(self, flow: http.HTTPFlow):
pass
addons = [
HookOssRequest()
]反向代理 http://.oss-cn-shanghai-internal.aliyuncs.com 到 http://localhost:8765$ mitmdump -s addons.py -p 8765 --set block_global=false --mode reverse:http://<your-bucket>.oss-cn-shanghai-internal.aliyuncs.com修改 core-sites.xml 中的 fs.s3a.endpoint 指向 localhost:8765,并关闭ssl。<property>
<name>fs.s3a.connection.ssl.enabled</name>
<value>false</value>
</property>
<property>
<name>fs.s3a.fast.upload</name>
<value>true</value>
</property>执行写入程序 demo,验证上传内容的正确性$ mkdir -p input output
$ dd if=/dev/zero of=input/test-100M-proxy bs=$(( 100*1024*1024 + 1 )) count=1
$ hadoop fs -put input/test-100M-proxy s3a://<your-bucket>/返回结果:xx/xx/xx xx:xx:xx WARN s3a.S3ABlockOutputStream: Transfer failure of block FileBlock{index=2, destFile=/data/hadoop/hadoop-2.8.5/tmp/s3a/s3ablock-0002-6832685202941984333.tmp, state=Upload, dataSize=1, limit=104857600}
xx/xx/xx xx:xx:xx WARN s3a.S3ABlockOutputStream: Transfer failure of block FileBlock{index=1, destFile=/data/hadoop/hadoop-2.8.5/tmp/s3a/s3ablock-0001-635596269039598032.tmp, state=Closed, dataSize=104857600, limit=104857600}
put: Multi-part upload with id '14ABE04E57114D0D9D8DBCFE4CB9366E' to test-100M-proxy._COPYING_ on test-100M-proxy._COPYING_: com.amazonaws.AmazonClientException: Unable to verify integrity of data upload. Client calculated content hash (contentMD5: 93B885ADFE0DA089CDF634904FD59F71 in hex) didn't match hash (etag: 0CC175B9C0F1B6A831C399E269772661 in hex) calculated by Amazon S3. You may need to delete the data stored in Amazon S3. (bucketName: <your-bucket>, key: test-100M-proxy._COPYING_, uploadId: 14ABE04E57114D0D9D8DBCFE4CB9366E, partNumber: 2, partSize: 1): Unable to verify integrity of data upload. Client calculated content hash (contentMD5: 93B885ADFE0DA089CDF634904FD59F71 in hex) didn't match hash (etag: 0CC175B9C0F1B6A831C399E269772661 in hex) calculated by Amazon S3. You may need to delete the data stored in Amazon S3. (bucketName: <your-bucket>, key: test-100M-proxy._COPYING_, uploadId: 14ABE04E57114D0D9D8DBCFE4CB9366E, partNumber: 2, partSize: 1)可见,Amazon S3 在 header 签名中强制对每个 upload part 的 payload 做了 Content-MD5 的校验,能够有效检测出网络比特反转。模拟网络丢包之前的测试验证了, S3 使用 Content-MD5 的校验可以保证单个请求的正确性,但在写一些大文件,或是涉及 JobCommitter 的作业中,往往会使用 multipart upload 来进行并发上传。而网络丢包也是一种常见的问题。于是,接下来我们来验证下,如果上传过程中其中一个 part 丢失,是否会给上传结果造成影响。同样使用 mitmproxy 来模拟丢包利用 mitmdump 反向代理 s3a endpoint,并丢弃其中 part2 的请求。编写 addons.pyfrom mitmproxy import ctx, http
import json
import time
import os
class HookOssRequest:
def request(self, flow: http.HTTPFlow):
print("")
print("="*50)
print("FOR: " + flow.request.url)
print(flow.request.method + " " + flow.request.path + " " + flow.request.http_version)
print("-"*50 + "request headers:")
for k, v in flow.request.headers.items():
print("%-20s: %s" % (k.upper(), v))
if flow.request.host == "<your-bucket>.oss-cn-shanghai-internal.aliyuncs.com" and flow.request.method == "PUT":
if "partNumber=2" in flow.request.path:
flow.response = http.HTTPResponse.make(
200, # (optional) status code
b"Hello World", # (optional) content
{"Content-Type": "text/html"}, # (optional) headers
)
ctx.log.info("drop part-2 request!")
ctx.log.info("requesting length:" + str(len(flow.request.content)))
def response(self, flow: http.HTTPFlow):
pass
addons = [
HookOssRequest()
]反向代理 http://.oss-cn-shanghai-internal.aliyuncs.com 到 http://localhost:8765$ mitmdump -s addons.py -p 8765 --set block_global=false --mode reverse:http://<your-bucket>.oss-cn-shanghai-internal.aliyuncs.com同样修改 core-sites.xml 中的 fs.s3a.endpoint 指向 localhost:8765,并关闭ssl。<property>
<name>fs.s3a.connection.ssl.enabled</name>
<value>false</value>
</property>
<property>
<name>fs.s3a.fast.upload</name>
<value>true</value>
</property>执行写入程序 demo,验证上传内容的正确性$ mkdir -p input output
$ dd if=/dev/zero of=input/test-100M-proxy bs=$(( 100*1024*1024 + 1 )) count=1
$ hadoop fs -put input/test-100M-proxy s3a://<your-bucket>/
xx/xx/xx xx:xx:x WARN s3a.S3ABlockOutputStream: Transfer failure of block FileBlock{index=2, destFile=/data/hadoop/hadoop-2.8.5/tmp/s3a/s3ablock-0002-2063629354855241099.tmp, state=Upload, dataSize=1, limit=104857600}
put: Multi-part upload with id 'D58303E74A5F4E6D8A27DD112297D0BE' to test-100M-proxy._COPYING_ on test-100M-proxy._COPYING_: com.amazonaws.AmazonClientException: Unable to verify integrity of data upload. Client calculated content hash (contentMD5: 93B885ADFE0DA089CDF634904FD59F71 in hex) didn't match hash (etag: null in hex) calculated by Amazon S3. You may need to delete the data stored in Amazon S3. (bucketName: <your-bucket>, key: test-100M-proxy._COPYING_, uploadId: D58303E74A5F4E6D8A27DD112297D0BE, partNumber: 2, partSize: 1): Unable to verify integrity of data upload. Client calculated content hash (contentMD5: 93B885ADFE0DA089CDF634904FD59F71 in hex) didn't match hash (etag: null in hex) calculated by Amazon S3. You may need to delete the data stored in Amazon S3. (bucketName: <your-bucket>, key: test-100M-proxy._COPYING_, uploadId: D58303E74A5F4E6D8A27DD112297D0BE, partNumber: 2, partSize: 1)可见,Amazon S3 在 close 请求中通过 CompleteMultipartUpload 对每个上传的 Part 做了检查,能够发现丢失的请求。校验算法的选择上文已经证明了校验码的不可或缺性,而且可以看到 Amazon S3 默认采用了 MD5 作为校验码。那就是最优的选择了吗?让我们来看看还有没有别的选择。数据摘要算法MD5、SHA-1、SHA-256、SHA-512都是数据摘要算法,均被广泛作为密码的散列函数。但由于MD5、SHA-1已经被证明为不安全的算法,目前建议使用较新的SHA-256和SHA-512。所有算法的输入均可以是不定长的数据。MD5输出是16字节(128位),SHA-1输出为20字节(160位),SHA-256为32字节(256位),SHA-512为64字节(512位)。可以看到,SHA算法的输出长度更长,因此更难发生碰撞,数据也更为安全。但运算速度与MD5相比,也更慢。循环冗余校验循环冗余校验又称 CRC(Cyclic redundancy check),将待发送的比特串看做是系数为 0 或者 1 的多项式。M = 1001010M(x) = 1*x^6 + 0*x^5 + 0*x^4 + 1*x^3 + 0*x^2 + 1*x^1 + 0*x^0M(x) = x^6 + x^3 + xCRC 编码时,发送方和接收方必须预先商定一个生成多项式 G(x)。发送方将比特串和生成多项式 G(x) 进行运算得到校验码,在比特串尾附加校验码,使得带校验码的比特串的多项式能被 G(x) 整除。接收方接收到后,除以 G(x),若有余数,则传输有错。校验算法的开销CRC算法的优点是算法实现相对简单、运算速度较快。而且错误检错能力很强,因此被广泛应用于通信数据校验。我们做了一些简单的benchmark以供参考:CRC32 > CRC64 > MD5 > SHA-1 > SHA-512 > SHA-256校验算法单次操作耗时BenchmarkMD5_100MB-8175423280 ns/opBenchmarkSHA1_100MB-8176478051 ns/opBenchmarkSHA256_100MB-8344191216 ns/opBenchmarkSHA512_100MB-8226938072 ns/opBenchmarkCRC32IEEE_100MB-810500107 ns/opBenchmarkCRC32Castagnoli_100MB-812991050 ns/opBenchmarkCRC64_100MB-8 86377178 ns/op而 OSS 支持的校验算法有 MD5 和 CRC64,那么同样的场景下,我们会优先选择 CRC64 替代 MD5。阿里云EMR JindoSDK 的最佳实践在总结了 S3AFileSystem 做法中的优缺点,并结合 OSS 自身提供的一些功能取长补短后,阿里云EMR JindoSDK 得出了自己的最佳实践。JindoSDK 实现的 JindoOutputStream 支持了两种校验方式,一种是请求级别的校验,一种是文件块级别的校验。请求级别的校验,默认关闭。需要打开时,配置 fs.oss.checksum.md5.enable 为 true 即可。配置好之后,客户端会在块级别的请求(PutObject/MultipartUpload)Header 中添加 Payload 的 Content-MD5。如果服务端计算 Payload 的 md5 与 客户端提供的不符,则客户端会重试。文件块级别的校验,默认打开。需要关闭时,需要配置 fs.oss.checksum.crc64.enable 为 false。则是在写入流一开始就在内存中同步计算传入 Buffer 的 CRC64,并在文件块落盘时和服务端计算返回的 CRC64 进行比较。使用最新的 jindosdk-4.6.2 版本与 S3AFileSystem 在数据湖写入路径上,综合对比的结果如下:场景S3AFileSystemJindoOssFileSystem磁盘 IO 问题抛出异常java.io.IOException抛出异常java.io.IOException磁盘比特反转未抛出异常抛出异常java.io.IOException网络比特反转抛出异常org.apache.hadoop.fs.s3a.AWSClientIOException抛出异常java.io.IOException 网络丢包抛出异常org.apache.hadoop.fs.s3a.AWSClientIOException抛出异常java.io.IOException写一个 5G 文件的耗时13.375s6.849s可以看到 EMR JindoSDK 在写 OSS 时,不仅有着相比 S3AFileSystem 更完善的错误检查,性能也更为优异。总结与展望数据湖存储的安全写入,必须要能考虑到内存、磁盘、网络的不可靠性。同时,也要结合存储介质本身的特性,选择合适的校验算法。熟悉数据写入完整链路,全面地考虑各种可能遇到的问题,并提供完善的测试方案验证可行性,才算有始有终。阿里云EMR JindoSDK 通过以上方式形成了自己的最佳实践,不仅保证了对象存储写入链路的安全性,同样也支持了EMR JindoFS服务(OSS-HDFS)的写入链路。虽然 OSS-HDFS 中的一个文件可以对应 OSS 上的多个对象,但是在写入 OSS 时,底层复用了同一套实现。因此,在使用时也不需要做额外的适配,完全可以共用相同的配置项。未来我们还将结合 OSS-HDFS,提供在数据随机读场景的安全性校验,而这是对象存储本身目前无法做到的。附录一:测试 S3A 的配置方式core-sites.xml<property>
<name>fs.s3a.impl</name>
<value>org.apache.hadoop.fs.s3a.S3AFileSystem</value>
</property>
<property>
<name>fs.AbstractFileSystem.s3a.impl</name>
<value>org.apache.hadoop.fs.s3a.S3A</value>
</property>
<property>
<name>fs.s3a.access.key</name>
<value>xxx</value>
</property>
<property>
<name>fs.s3a.secret.key</name>
<value>xx</value>
</property>
<property>
<name>fs.s3a.endpoint</name>
<value>localhost:8765</value>
</property>
<property>
<name>fs.s3a.connection.ssl.enabled</name>
<value>false</value>
</property>
<property>
<name>fs.s3a.fast.upload</name>
<value>true</value>
</property>
<property>
<!-- 本地 buffer 缓存目录,不存在会创建 -->
<name>fs.s3a.buffer.dir</name>
<value>/mnt/passthrough/</value>
</property>附录二:测试EMR JindoSDK 的配置方式core-sites.xml<property>
<name>fs.AbstractFileSystem.oss.impl</name>
<value>com.aliyun.jindodata.oss.OSS</value>
</property>
<property>
<name>fs.oss.impl</name>
<value>com.aliyun.jindodata.oss.JindoOssFileSystem</value>
</property>
<property>
<name>fs.oss.accessKeyId</name>
<value>xxx</value>
</property>
<property>
<name>fs.oss.accessKeySecret</name>
<value>xxx</value>
</property>
<property>
<name>fs.oss.endpoint</name>
<!-- 阿里云 ECS 环境下推荐使用内网 OSS Endpoint,即 oss-cn-xxx-internal.aliyuncs.com -->
<value>oss-cn-xxx.aliyuncs.com</value>
</property>
<property>
<!-- 客户端写入时的临时文件目录,可配置多个(逗号隔开),会轮流写入,多用户环境需配置可读写权限 -->
<name>fs.oss.tmp.data.dirs</name>
<value>/data2/tmp/</value>
</property>
<property>
<!-- 是否使用二级域名写入
打开后 <your-bucket>.oss-cn-xxx-internal.aliyuncs.com/<your-dir>
会变为 oss-cn-xxx-internal.aliyuncs.com/<your-bucket>/<your-dir> -->
<name>fs.oss.second.level.domain.enable</name>
<value>true</value>
</property>log4j.propertieslog4j.logger.com.aliyun.jindodata=INFO
log4j.logger.com.aliyun.jindodata.common.FsStats=INFOmitmproxy获取 endpoint ipping oss-cn-shanghai-internal.aliyuncs.com
64 bytes from xxx.xxx.xxx.xx (xxx.xxx.xxx.xx): icmp_seq=1 ttl=102 time=0.937 ms将 addons.py 中使用 ip 代替 .oss-cn-shanghai-internal.aliyuncs.com if flow.request.host == "xxx.xxx.xxx.xx" and flow.request.method == "PUT":反向代理时也使用 ip 代替 .oss-cn-shanghai-internal.aliyuncs.commitmdump -s addons.py -p 8765 --set block_global=false --mode reverse:http://xxx.xxx.xxx.xx:80欢迎感兴趣的朋友加入钉钉交流群(钉钉搜索群号33413498 或 钉钉扫描下方二维码)
初入阿里云,上手走一波
可以说个人在日常使用过程中,操作最多的阿里云产品就是阿里云服务器ECS,云服务器ECS作为其他云产品的基础,作用及重要性就不言而喻。关于我体验的云产品的相关功能,下面就几个组合来稍微讲解一下吧,另外其中涉及的服务器执行命令属于实验室环境下,日常使用的云服务器ECS初始化配置可能不具备某些命令。一阶:ECS+Mysql+DMS在首次熟悉了云服务器ECS的基础操作后,最简单的操作就是部署一个Mysql数据库服务,下面开始部署吧,具体的实验室地址,有兴趣的可以去体验哈,体验完之后你就会对云服务器ECS有一个初步的认识(和日常用的服务器并无不同,没什么学习成本)安装Mysql切换至Web Terminal窗口执行如下命令#更新YUM源
rpm -Uvh https://labfileapp.oss-cn-hangzhou.aliyuncs.com/mysql57-community-release-el7-9.noarch.rpm
# 安装MySQL
yum -y install mysql-community-server --nogpgcheck
#查看MySQL版本号
mysql -V可以看到返回结果,表明Mysql安装成功。mysql Ver 14.14 Distrib 5.7.41, for Linux (x86_64) using EditLine wrapper初始化Mysql安装Mysql成功后,后面开始实际操作Mysql# 启动Mysql服务
systemctl start mysqld
#设置MySQL服务开机自启动
systemctl enable mysqld
#查看初始密码
grep 'temporary password' /var/log/mysqld.log查看初始密码返回2023-02-28T07:48:25.273051Z 1 [Note] A temporary password is generated for root@localhost: bDgaCe0#%r!*后续继续操作Mysql#对MySQL进行安全性配置
mysql_secure_installation
#输入原始密码之后会提示输入新密码,新密码规则:新密码长度为8至30个字符,必须同时包含大小写英文字母、数字和特殊符号
#这个新密码设为Test@2023
#Change the password for root ? ((Press y|Y for Yes, any other key for No) : y
#后续提示根据需要确认即可,直到出现提示 All done!Mysql操作使用root用户登录mysql#登录mysql
mysql -uroot -p
#查看mysql自带数据库
show databases;至此云服务器ECS安装Mysql的操作及验证也就完成了,整体上是不是很流畅,同时也算基本实现了数据库上云。后续可以把安装好的Mysql交给数据库管理平台DMS管理(由于实验室环境下DMS数据库管理平台无权限连接实验室环境下的Mysql,故采用真实阿里云控制台DMS管理真实ECS环境的Mysql)DMS管理MysqlDMS首页地址:https://dms.aliyun.com/new,点击【数据库实例】>【+】新增数据库实例选择他云/自建 下 Mysql 点击【下一步】,选择正确参数点击【测试连接】点击【确认】之后,点击【提交】此时DMS管理Mysql配置完成可以看到如下管理平台页面这里我们看到的就是我们云服务器ECS自建Mysql服务的数据库,和我们在Mysql服务端看到的一致通过此次体验看到阿里云产品系列除了提供云服务器ECS同时还有对应的数据库管理平台DMS方便实时管控云数据库,可视性、安全性、连通性、稳定性完全能够保障。二阶:ECS+OSS体验了云服务器ECS的基础操作,下面来用云服务器ECS和对象存储OSS搭建一个图片分享网站,实验室地址 有兴趣的可以去体验一下哈,整体操作比较流畅,对于有图片分享需求的开发者还是很有指导意义的,下面我们开始搭建操作。远程连接ECS在云服务器ECS实例列表页面点击【远程连接】弹出对话框,点击【立即登录】输入服务器密码即可登录成功在浏览器中输入http:// 即可看到当前实验室环境下ECS图片分享网站内容回到ECS远程连接操作页面执行命令cd /alidata/www/default
ls可以看到01 02 03 04 四张图片,我们也可以通过http:///01.png 访问其中任何一张图片查看首页代码index可以看到但是此时的图片还是在云服务器ECS上,下面需要做的是通过调用OSS API上传图片到OSS,编辑配置文件vim cfg.json按对应的说明输入内容,如图,完成后保存上传图片01 02 03 04到OSS目录下python oss_upload.py 01.png
python oss_upload.py 02.png
python oss_upload.py 03.png
python oss_upload.py 04.pngOSS控制台上传成功之后打开OSS控制台:https://oss.console.aliyun.com/ 可以看到已经上传成功的图片这是删除云服务器ECS上面对应的01 02 03 04 文件,点击OSS控制台每个图片【详情】复制图片URL更新index文件为OSS路径的图片地址此时访问index首页看到的图片就是OSS服务器对应的图片了。其他图片服务还可以使用x-oss-process参数对图片进行处理,比如将原图片的高度和宽度缩放为200px?x-oss-process=image/resize,w_200比如将原图片的高度和宽度缩放为200px,图片转换为avif格式。?x-oss-process=image/resize,w_200/format,avif当然也可以借助OSS搭建在线教育视频课程分享网站三阶:更多搭配操作当然,除了上面的云服务器ECS的简单搭配操作外,还有一些复杂的云产品搭配操作,比如使用PolarDB-X与Flink搭建一个实时数据链路,模拟阿里巴巴双十一GMV大屏的 使用PolarDB-X与Flink搭建实时数据大屏,再比如通过在云服务器ECS上安装WordPress,帮助你快速搭建自己的云上博客的 使用PolarDB和ECS搭建门户网站,再比如使用k8s的原生命令kubectl部署一个web应用(魔方应用)的镜像到k8s集群中,并通过Ingress将部署的服务暴露出来由外部访问的 使用Kubectl部署web服务到K8s集群 等等很多的使用场景,在目前Serverless的大环境下未来企业可以根据需要任意组合调整所需要的云产品,高弹性,可伸缩,企业不用关心服务器也不用投入人员维护云服务,全链路云上托管,降本增效,效果将会更加显著。云产品组合搭配定然会助力更多的企业应用轻松开发,无限适用。
个人工作总结无代码-三分白
windows容器最佳实践https://developer.aliyun.com/article/774927?spm=a2c4g.11186623.0.0.7492de535B8UC9利用阿里云搭建WordPress网站 – 搭建基础网站应用https://developer.aliyun.com/article/702597?spm=a2c4g.11186623.0.0.7f2a7292VccAfa容器服务ACK+文件存储NAS快速搭建NGINX网站developer.aliyun.com/article/775004资源文档1 弹性计算 云服务器ECS 规格:2C4G、4C8G数量:4-10按量付费产品链接:https://www.aliyun.com/product/ecs文档链接:https://help.aliyun.com/document_detail/25367.html2 容器服务ACK 集群规格:Pro版节点IP数量32Worker规格:2C4GWorker磁盘:120GWorker数量:2-4产品链接:https://www.aliyun.com/product/kubernetes文档链接:https://help.aliyun.com/document_detail/86737.html3 存储 块存储(EBS) 规格:100GB数量:2-8按量付费产品链接:https://www.aliyun.com/product/disk文档链接:https://help.aliyun.com/document_detail/63136.html4 对象存储OSS 类型:标准存储预计用量:200G以内按量付费产品链接:https://www.aliyun.com/product/oss文档链接:https://help.aliyun.com/document_detail/31817.html5 文件存储NAS 类型:通用型预计用量:100G按量付费产品链接:https://www.aliyun.com/product/nas文档链接:https://help.aliyun.com/document_detail/27518.html6 日志服务SLS 存储空间:<5G读写流量:<5G按量付费产品链接:https://www.aliyun.com/product/sls文档链接:https://help.aliyun.com/document_detail/48869.html7 数据库 云数据库 RDS MySQL版 Mysql 5.7 高可用版实例规格:2C8G 通用型磁盘容量100G 本地SSD数量:2-4按量付费产品链接:https://www.aliyun.com/product/rds/mysql文档链接:https://help.aliyun.com/document_detail/96047.html8 云数据库Redis版 企业版性能增强-标准版2GB主从版性能增强数量:2-4按量付费产品链接:https://www.aliyun.com/product/kvstore文档链接:https://help.aliyun.com/document_detail/26342.html9 数据传输服务DTS 链路规格:medium数量:1-2按量付费产品链接:https://www.aliyun.com/product/apsaradb/dts文档链接:https://help.aliyun.com/document_detail/26592.html10 数据库备份DBS 预计数据量:100G以内按量付费 产品链接:https://www.aliyun.com/product/dbs文档链接:https://help.aliyun.com/document_detail/59133.html11 网络 专有网络VPC 数量:2-8 产品链接:https://www.aliyun.com/product/vpc文档链接:https://help.aliyun.com/document_detail/34217.html12 弹性公网 IP(EIP) 数量:1-4按量付费产品链接:https://www.aliyun.com/product/eip文档链接:https://help.aliyun.com/document_detail/32321.html13 负载均衡SLB 规格:CLB公网类型数量:1-4按量付费产品链接:https://www.aliyun.com/product/slb文档链接:https://help.aliyun.com/document_detail/196874.html14 NAT网关 数量:1-2按量付费产品链接:https://www.aliyun.com/product/network/nat文档链接:https://help.aliyun.com/document_detail/32322.html15 VPN网关 数量:1产品链接:https://www.aliyun.com/product/vpn文档链接:https://help.aliyun.com/document_detail/64960.html16 CDN 流量:40G以内数量:1按量付费产品链接:https://www.aliyun.com/product/cdn文档链接:https://help.aliyun.com/document_detail/27101.html17 中间件 企业级分布式应用服务EDAS 规格:标准版数量:1按量付费产品链接:https://www.aliyun.com/product/edas文档链接:https://help.aliyun.com/document_detail/42934.html18 消息队列RocketMQ版 规格:标准版数量:1按量付费产品链接:https://www.aliyun.com/product/rocketmq文档链接:https://help.aliyun.com/document_detail/29532.html19 云监控 规格:基础云监控数量:1按量付费产品链接:https://www.aliyun.com/product/jiankong文档链接:https://help.aliyun.com/document_detail/35170.html20 应用实时监控服务ARMS 规格:基础版数量:1按量付费产品链接:https://www.aliyun.com/product/arms文档链接:https://help.aliyun.com/document_detail/42781.html21 Prometheus监控服务 规格:免费试用版15天,专家版数量:1按量计费产品链接:https://www.aliyun.com/product/developerservices/prometheus文档链接:https://help.aliyun.com/document_detail/122123.html22 性能测试PTS 规格:按量抵扣资源包,体验版or内网专享版数量:1产品链接:https://www.aliyun.com/product/pts文档链接:https://help.aliyun.com/document_detail/29262.html23 安全 DDoS防护 数量:1产品链接:https://www.aliyun.com/product/security/ddos文档链接:https://help.aliyun.com/document_detail/63643.html24 Web应用防火墙 规格:2.0版本数量:1按量付费产品链接:https://www.aliyun.com/product/waf文档链接:https://help.aliyun.com/document_detail/28517.html25 数字证书管理服务 规格:DV证书数量:2免费版本产品链接:https://www.aliyun.com/product/cas文档链接:https://help.aliyun.com/document_detail/28535.html26 云安全中心 数量:1免费版本产品链接:https://www.aliyun.com/product/sas文档链接:https://help.aliyun.com/document_detail/42302.html27 云防火墙 数量:1按量付费产品链接:https://www.aliyun.com/product/cfw文档链接:https://help.aliyun.com/document_detail/90216.html28 网络构建 云速搭 CADT 数量:1 详见:https://www.aliyun.com/product/developerservices/cadt**前端:**layui-v2.5.6https://wps.bcegc.com/view/l/t8vssk3https://wps.bcegc.com/view/l/tcgm47b补充:1.ajax:$.ajax({url:"http://:80/patient/findAll",method:"post",data:{},success:function(res){if(res.code == 200){$.cookie('TOKEN', res.data.token,{expires: 7});}else{alert{res.msg};}}})2.跨域必须http://全路径Mystylehtml,body{height: 100%;}h1.head{background: #2f4056;
border-bottom: 1px solid gray;
color: white;
line-height: 58px;
padding-left: 15px;}section.content{height: calc(100% - 60px);
overflow: hidden;}section.left{float:left;
width: 200px;
height: 100%;}.layui-nav-tree{height: 100%;}section.right{ float: right; width: calc(100% - 200px); height: 100%;}后端:1.xml<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">9.yml/application.propertiesspring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.driver-class-name=com.mysql.jdbc.Driverspring.datasource.druid.url=jdbc:mysql://rm-2zeu2mx5rmvv9k0tb.mysql.rds.aliyuncs.com:3306/patient?useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.druid.username=hha6027875spring.datasource.druid.password=Hha6027875mybatis.mapper-locations=classpath:mappers/*.xmlpagehelper.helperDialect=mysqlpagehelper.reasonable=truelogging.file.path=sp.loglogging.level.root=infologging.level.web=debuglogging.level.sql=debuglogging.level.com.ssm.mapper=debugthymeleafspring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.htmlspring.thymeleaf.encoding=utf-8spring.thymeleaf.servlet.content-type=text/htmlspring.thymeleaf.cache=false10.pom<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiexin.bootjsj</groupId>
<artifactId>code-race</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>code-race</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--整合spring的web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 持久层 整合 mybaits-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--分页依赖-->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 显示 层 整合thymeleaf模板-->
<!--thymeleaf默认使用html5规则标签必须闭合等 使用次此包正常解析-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>11.settings <!-- mirrors | This is a list of mirrors to be used in downloading artifacts from remote repositories. | | It works like this: a POM may declare a repository to use in resolving certain artifacts. | However, this repository may have problems with heavy traffic at times, so people have mirrored | it to several places. | | That repository definition will have a unique id, so we can create a mirror reference for that | repository, to be used as an alternate download site. The mirror site will be the preferred | server for that repository. |--><!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
-->
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>13.总结1.配置ide的阿里云镜像文件打开文件位置(安装目录下面),返回上一层目录。按照 plugins\maven\lib\maven3\conf 的顺序,依次打开,在conf文件目录下出现一个setting.xml的文件
或者file-settings-build-build tools-maven或者在preferences-build,execution,deployment-mavenuser settings file找到该文件即可粘贴在mirrors下面即可,别的全部注释,把- ->放在阿里云镜像<mirror>的上面,别的全部注释2.解压编程资源包,加载maven文件3.有可能jdk没装,需要自己下载一下在idea里面关闭代码检查:file-power save mode,打开自动不检查了4.enable lombak annotation processing6.配置数据库地址,更改controller为restcontroller7.医生那最后一个字段应该是password吧8.医生查询当前time下的所有医生?方法名字还叫titleandid?啥叫当前time,是不可以理解为指定排版的医生doctorservice的返回值list<map>?service也加签名吗
这块查询要使用find/1?time=2022-07-289.排版分页传参,默认叫法pageIndex,pageSize注意startpage要放到mapper调用前面,xml里面select全部语句不能加;号,因为pagehelper会帮你加10.预约里面的department竟然是字符串,注意数据库设计主要问题是页面显示的叫做门诊,字段也叫clinic,department不是科室吗?吐血了
先按照department处理了,设计char类型
预约第二个方法需要创建一个新的类,预约详情
第一个方法用的docter int,第四个变成string无语了,按照int处理了
第三个方法mapper int,service参数string,sssb,按照String处理了,controller也是string
第四个service返回response不合适,需要返回原值,后面需要调用11.fee按照int类型处理12.clinic那参数有个appointmentparam,用不到吧,还有scheduling那个schedulingparams普通上传Ecs和rds一个vpc,一个可用区1.创建ecs时第三部系统配置可以设置密码(ecs不是操作系统),默认root - Hha6027875,要设置公网ip1.1或者通过实例右侧更多,网络,绑定弹性ip,远程连接,立即登录,弹性公网是后开的,不用担心
1.2下载jdk,放到home目录下(默认目录就是home)
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm
1.3 chmod +x jdk-8u131-linux-x64.rpm
rpm -ivh jdk-8u131-linux-x64.rpm
1.4 (不用管换行)vi /etc/profile export JAVA_HOME=/usr/java/jdk1.8.0_131
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:JAVAHOME/lib:{JRE_HOME}/lib:$CLASSPATH
export JAVA_PATH=${JAVAHOME}/bin:${JRE_HOME}/bin
export PATH=$PATH:${JAVA_PATH}
1.5 java -version javac查看是否安装成功并配置环境变量2..rds创建后(跟switch一个可用区),点击实例,选择账号管理,创建数据库账号hha6027875/Hha6027875高权限账号(可以在创建时实例配置阶段设置,也可以在账户管理中进行设置,不行的话重置一下账号密码,输密码的时候不要选择默认,手敲一下,有时候不识别)点击登录数据库,首次需要进行云资源的授权,授权dms系统默认角色,登录创建的账号
点击左侧数据库实例,右键数据库选择数据库管理,创建数据库,刷新,在自己的数据库里面进行操作,这里默认别的数据库是不让你动的,当时就吃了这个亏
别忘了设置库表主键自增加。
先把数据惯进去,右键表打开表,开启编辑,新增,提交修改即可,必须有主见rds点击实例连接,基本信息,网络类型专有网络,查看链接详情,设置白名单,添加白名单分组(基本一个可用区是没问题的),回到基本信息,专业网络,查看链接详情,复制ip,到ecs里面ping一下,如果通了更改yml的数据库地址,端口306,用户名和密码导入sql文件,右键表导入,上传sql文件提交安全组,跟白名单一个位置,白名单右面就是安全组,可以导入安全组安全组在ecs这创建,ecs管理界面安全组创建安全组出现报错null刷新几次,新建数据库有这个问题3.项目pom文件倒数第二行加jar更改数据库地址,切记切记密码要配置成rds的密码,这块特别容易忘
打包,如果右键项目有maven install 直接选就行,如果没有,需要view-toolwindow-maven-绿色三角execute maven goal-选择install即可
找到该文件放到桌面(文件目录里面可以找一下target)
进入ecs实例管理页面,上面文件,打开新文件管理,上传文件,上传到home文件夹,会话-新终端退出回到正常命令行4.查看端口是否被占用 netstat -anp | grep 8080 查看进程netstat -ntulp |grep 8080
如果占用杀死他sudo kill -9 pid
回到home
java -jar code-race-0.0.1-SNAPSHOT.jar 出现spring logo代表运行成功
这时候ecs阻塞了,需要点击会话,横向打开新终端5.输入curl http://localhost:8080/clinic/findAll测试即可简易版restcontroller,返回response即可,page/ceshi6.结束任务ps -ef|grep javakill -9 进程号7.install或者Prcompile ocess terminated setings找不到重新覆盖一下,clean一下edas版本1.打开edas,地域选择北京,创建应用新建ecs或者把已有ecs放到集群中设置登录密码,设置健康检查地址http://localhost:8080/page/ceshi创建好机器后,启动应用日志管理-日志目录-edascontainer-logs-catalina.out在线查看即可截图th:参数Model modelmodel.addAttribute("site","www.bjpowernode.com"); // 对象类型 model.addAttribute("myUser",new SysUser(1001,"王世超",20));页面都加上引用都是th:标签,${site},${myUser.name}th:href="@{/layui-v2.5.6/layui/css/layui.css}"th:href="@{/layui-v2.5.6/layui/css/myStyle.css}"th:src="@{/layui-v2.5.6/layui/layui.js}"th:src="@{/layui-v2.5.6/layui/layui.all.js}"内部链接,href都使用/page1/card,doctor,scheduling,patient,不需要加th,注意cols的[[]]要换行这样layui和thymeleaf就完美兼容了$('#search').click(function(){ $.ajax({ url:"http://localhost:8080/page/ceshi", method:"get", data:{}, success:function(res){ alert(res.data); } }); table.renderspringgsecurity百度搜吧https://blog.csdn.net/weixin_46301906/article/details/125653879org.springframework.bootspring-boot-starter-security <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
仿照代码写选择密码模式,配置授权服务器设置oauth配置,资源服务器设置功能角色设置,springsecurity配置用户角色设置等等,到时候抄代码,用户名密码改从数据库中进行获取withuser和password那里换一下,roles从角色表里面进行获取登录先访问/oauth/token请求token,参数是六个然后带着返回的access_token进行别的操作通过路径来判断是否有权限,对应的功能前缀加上/角色路径token还是模拟redis存储测试用例就是先请求oauth,参数username,password,grant_type,client_id,scope,client_secret然后请求各角色前缀路径携带token拦截器按照https://blog.csdn.net/qq_42764468/article/details/127718048写一个UserInfoInterceptor一个CommonWebMvcAutoConfiguration前者设置拦截前后的操作,按照题目要求分别在response里面配置成功和失败的返回response信息,替代默认就行后者配置具体拦截的地址即可,把相关地址放行了就可以了,不需要理解细节直接复制代码1.java web:手动部署Java Web环境(Alibaba Cloud Linux 3)https://help.aliyun.com/document_detail/460788.html命令:查看防火墙:systemctl status firewalld/systemctl stop firewalld查看selinux:getenforce 关闭:setenforce 0回到根目录yum -y list java*yum -y install java-1.8.0-openjdk-devel.x86_64java -version, 安装路径/usr/lib/jvm(再根目录安装上述命令的话好想不需要配置环境变量,如果能出现java-version可以直接运行而不需要配置环境变量)sudo vim /etc/profile(注意sudo否则容易权限不足)JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.352.b08-2.al8.x86_64 PATH=$PATH:$JAVA_HOME/bin CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export JAVA_HOME CLASSPATH PATHsource /etc/profile上传文件,java -jar code-race.jar3.其他注意事项3.1免密登录,但是不能传文件(切换实例重新登一下就好了)3.2一个nat对应一个vpc3.3公网slb无法连接eip,必须私网3.4 ess模板必须起名,slb为啥要连ess3.5smc目标ecs必须开通外网 ,解压要sudo,授权运行也要sudo,导入操作直接带参数死活不对,去掉参数运行后输入可以了,但是如果直接运行时不会有输入参数的地方的安了一遍导入了一遍,有bug这个地方3.6数据库需要先高权限账户创建数据库,再分配普通账户(数据库建库不需要进入,直接控制台就可以3.7 如何用kubectl,下载和安装kubectl,本地拷贝那个config,连接或者将配置信息粘贴在图中目录($HOME/.kube)kubectl versionkubectl get nodeKubectl get pod kubectl get namespace3.8 WORDPRESS_DB_USERWORDPRESS_DB_NAMEWORDPRESS_DB_PASSWORDWORDPRESS_DB_HOSTcat wp-config.php,找到参数,复制3.9 命名空间新建wordpresskubernates.io/metadata.name. wordpressalibabacloud.com true3.10 /var/www/html/var/share/nginx/html上传文件测试3.11 nas挂载ecs,通过控制台,挂载使用,挂载到ecs,选择配置即可4.docker 4.0 打开安全组的端口号4.1 安装yum sudo yum install -y yum-utils device-mapper-persistent-data lvm24.2 添加镜像地址sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4.3 安装docker(两个y)sudo yum install docker-ce docker-ce-cli containerd.io4.4 启动docker:sudo systemctl start docker, 拉取镜像docker pull nginx4.4 创建容器外地址mkdir /root/nginx4.6 创建容器docker run --name nginx -p 80:80 -v /root/nginx:/usr/share/nginx/html -d --restart=always nginx4.7 切换首页cd /root/nginxtouch index.html echo "hello word" -> index.html 访问就可以了4.8 tomcat, docker pull tomcatdocker run --name tomcat -p 8080:8080 -v /opt:/usr/local/tomcat/webapps -d --restart=always tomcatdocker exec -it tomcat /bin/bash, 进入容器cp -r webapps.dist/* webapps这样就能看到tomcat的首页了,但是注意如果切换自己的页面需要以下操作cd ROOTrm index.jsptouch index.jspecho hello > index.jsp4.9 wordpressdocker pull wordpressdocker run --name wordpress -p 80:80 -v /opt:/usr/www/html -d --restart=always wordpress直接访问,页面配置数据库地址方法1:重启docker,杀死占用的容器方法2:解决端口占用,iptables -t nat -nL --line-number |grep 80iptables -t nat -D DOCKER 2 (2代表杀死第二个映射)5.ack创建后无需挂载nas,直接打开pod的终端,进入操作,多个pod,需要多次修改和刷新wp的数据库还是得配置2.ack安装组件
个人工作总结后端-三分白
1.Applicationpackage com.ssm;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@MapperScan("com.ssm.mapper")public class Application {public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}2.common.responsepackage com.ssm.common;import lombok.AllArgsConstructor;import lombok.Data;@Data@AllArgsConstructorpublic class Response {private boolean success;
private Integer code;
private String msg;
private Object data;
public static Response fail(){
return new Response(false,4001,"请求失败","");
}
public static Response success(Object data){
return new Response(true,0,"请求æˆåŠŸ",data);
R }}3.patient3.1pojopackage com.ssm.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class Patient {private Integer id;
private String name;
private String id_card;
private String phone;
private Integer state;
private String password;}3.2mapperpackage com.ssm.mapper;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;import com.ssm.pojo.Patient;import java.util.List;@Repository@Mapperpublic interface PatientMapper {Integer addPatient(String name, String id_card, String phone);
Patient queryByIdCard(String id_card);
List<Patient> queryAll(Integer state);
int updateByPrimaryKeySelective(Patient record);}3.3xml<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><select id="queryAll" parameterType="int" resultType="com.ssm.pojo.Patient">
select * from `patient` where state = #{0};
</select>
<update id="updateByPrimaryKeySelective" parameterType="com.ssm.pojo.Patient">
update `patient` set `name` = #{name},`phone` = #{phone},`state` = #{state} where `id_card` = #{id_card};
</update>
<select id="queryByIdCard" parameterType="string" resultType="com.ssm.pojo.Patient">
select * from `patient` where id_card = #{0};
</select>
<insert id="addPatient" parameterType="com.ssm.vo.param.PatientParam">
insert into `patient`(`name`,`id_card`,`phone`, `state`,`password`)
values(#{name}, #{id_card}, #{phone}, 1, '123');
</insert>3.4servicepackage com.ssm.service.impl;import com.ssm.common.Response;import com.ssm.mapper.PatientMapper;import com.ssm.pojo.Patient;import com.ssm.service.PatientService;import com.ssm.vo.param.PatientParam;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Servicepublic class PatientServiceImpl implements PatientService {@Autowired
private PatientMapper patientMapper;
public Response add(PatientParam patientParam){
Integer number = patientMapper.addPatient(patientParam.getName(),patientParam.getId_card(),patientParam.getPhone());
return Response.success(number);
}
public Response getAll(){
List<Patient> patients = patientMapper.queryAll(1);
return Response.success(patients);
}
public Response del(String idNumber){
Patient patient = patientMapper.queryByIdCard(idNumber);
patient.setState(0);
int num = patientMapper.updateByPrimaryKeySelective(patient);
return Response.success(num);
}
public Response update(Patient patient){
int num = patientMapper.updateByPrimaryKeySelective(patient);
return Response.success(num);
}
}3.5.controllerpackage com.ssm.controller;import com.ssm.common.Response;import com.ssm.pojo.Patient;import com.ssm.service.PatientService;import com.ssm.vo.param.PatientParam;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;@RestController@RequestMapping("/patient")public class PatientController {@Autowired
private PatientService patientService;
@RequestMapping("/add")
public Response add(@RequestBody PatientParam patientParam){
return patientService.add(new PatientParam(patientParam.getName(),patientParam.getId_card(),patientParam.getPhone()));
}
@RequestMapping("/findAll")
public Response getAll(){
return patientService.getAll();
}
@RequestMapping("/del/{idNumber}")
public Response del(@PathVariable String idNumber){
return patientService.del(idNumber);
}
@RequestMapping("/update")
public Response update(@RequestBody Patient patient){
return patientService.update(patient);
}
@RequestMapping("/login/{name}")
public void login(@PathVariable Integer name, HttpSession httpSession){
httpSession.setAttribute("patientId", name);
}
@RequestMapping("/logout/{name}")
public void logout(@PathVariable Integer name, HttpSession httpSession){
httpSession.removeAttribute("patientId");
}}3.6.parampackage com.ssm.vo.param;import lombok.AllArgsConstructor;import lombok.Data;@Data@AllArgsConstructorpublic class PatientParam {private String name;
private String id_card;
private String phone;}4.clinic4.1pojopublic int id;public String title;public int fees;4.2mapperList queryAll();4.3xml<select id="queryAll" resultType="com.ssm.pojo.Clinic">
select * from `clinic`;
</select>4.4servicepublic List<Clinic> queryAll() {
return clinicMapper.queryAll();}4.5.controller@RequestMapping("/findAll")public Response queryAll(){
return Response.success(clinicSevice.queryAll());}5.department5.1pojopublic int id; public String title;
public int parent;public String about;5.2mapperList getAllByParent(Integer parent);5.3xml<select id="getAllByParent" parameterType="int" resultType="com.ssm.pojo.Department">
select * from `department` where parent = #{0};
</select>5.4servicepublic Response getAllByParent(Integer parent) {
return Response.success(departmentMapper.getAllByParent(parent));}5.5.controller@RequestMapping("/findAll/{parent}")public Response getAllByParent(@PathVariable String parent){
return departmentService.getAllByParent(Integer.parseInt(parent));}6.doctor6.1pojopublic int id;public String name;
public int departmentId;
public String level;
public byte[] avatar;public int password;6.2mapperList queryAll();List getDocByTitleAndId(@Param("title") String title, @Param("time") String time);6.3xml<select id="queryAll" resultType="com.ssm.pojo.Doctor">
select * from `doctor`;
</select>
<select id="getDocByTitleAndId" resultType="com.ssm.pojo.Doctor">
select * from `doctor` d LEFT JOIN `scheduling` s on d.id = s.doctor
where s.clinic = #{title} and s.time = #{time};
</select>6.4service@Override
public List<Doctor> queryAll() {
return doctorMapper.queryAll();
}
@Override
public List<Doctor> getDocByTitleAndId(String title, String time) {
return doctorMapper.getDocByTitleAndId(title, time);}6.5.controller@RequestMapping("/findAll")public Response queryAll(){
return Response.success(doctorService.queryAll());
}
@RequestMapping("/find/{title}")
public Response queryByTitleAndId(@PathVariable String title, @DateTimeFormat(pattern = "yyyy-MM-dd") Date time){
return Response.success(doctorService.getDocByTitleAndId(title, new SimpleDateFormat("yyyy-MM-dd").format(time)));}7.scheduling7.1pojopublic int id;public int doctor;
public Date time;
public int clinic;public int count;7.2mapperList queryAll();int add(Scheduling scheduling);7.3xml<select id="queryAll" resultType="com.ssm.pojo.Scheduling">
select * from `scheduling`
</select>
<insert id="add" parameterType="com.ssm.pojo.Scheduling">
insert into `scheduling`(`doctor`,`time`,`clinic`, `count`)
values(#{doctor}, #{time}, #{clinic}, #{count});
</insert>7.4servicepublic int add(Scheduling scheduling) {
return schedulingMapper.add(scheduling);
}
@Override
public List<Scheduling> queryAll(Map<String, Object> map) {
int pageIndex = (Integer)map.get("pageIndex");
int pageSize= (Integer)map.get("pageSize");
System.out.println(pageIndex);
System.out.println(pageSize);
PageHelper.startPage(pageIndex, pageSize);
PageInfo<Scheduling> schedulingPage = new PageInfo<Scheduling>(schedulingMapper.queryAll());
System.out.println(schedulingPage.getPageNum());
System.out.println(schedulingPage.getPageSize());
System.out.println(schedulingPage.getPages());
return schedulingPage.getList();}7.5.controller@RequestMapping("/findAll")public Response selectByPage(@RequestBody Map<String, Object> map){
return Response.success(schedulingService.queryAll(map));
}
@RequestMapping("/add")
public Response add(@RequestBody Scheduling scheduling){
return Response.success(schedulingService.add(scheduling));}8.appointment8.1pojopublic int id;public int patient;
public int doctor;
public Date time;
public int num;
public String department;
public int fees;public String state;8.2mapperInteger addAppoint(Integer patient, Integer doctor, String time, Integer num, String department);
List<AppointDetailVo> getOneDetail(Integer patient);
Integer delAppoint(Integer aId);
int schedulingNum(Integer doctor, String time);8.3xml<insert id="addAppoint" >
insert into `appointment`(`patient`,`doctor`,`time`, `num`,`department`,`fees`, `state`)
values(#{patient}, #{doctor}, #{time}, #{num}, #{department}, 5, "预约成功");
</insert>
<select id="getOneDetail" parameterType="int" resultType="com.ssm.vo.res.AppointDetailVo">
select * from `appointment` where patient = #{0};
</select>
<update id="delAppoint">
update `appointment` set `state` = "预约取消" where `id` = #{aId};
</update>
<select id="schedulingNum" resultType="int">
select count from `scheduling` where doctor = #{doctor} and time = #{time};
</select>8.4service @Override
public Response appointSubmit(AppointmentParam appointmentParam) {
return Response.success(appointmentMapper.addAppoint(appointmentParam.getPatient(), appointmentParam.getDoctor(),
appointmentParam.getTime(),appointmentParam.getNum(),appointmentParam.getDepartment()));
}
@Override
public Response getOneDetail(Integer patient) {
return Response.success(appointmentMapper.getOneDetail(patient));
}
@Override
public Response delAppoint(String id) {
return Response.success(appointmentMapper.delAppoint(Integer.parseInt(id)));
}
@Override
public Integer schedulingNum(Integer doctor, String time) {
return appointmentMapper.schedulingNum(doctor, time) + 1;}8.5.controller@RequestMapping("/submit")public Response appointSubmit(@RequestBody AppointmentParam appointmentParam, HttpSession httpSession){
int newNum = appointmentService.schedulingNum(appointmentParam.getDoctor(), appointmentParam.getTime());
Integer patient = (Integer)httpSession.getAttribute("patientId");
appointmentParam.setNum(newNum);
appointmentParam.setPatient(patient);
return appointmentService.appointSubmit(appointmentParam);
}
@RequestMapping("/detail")
public Response getOneDetail(HttpSession httpSession){
Integer patient = (Integer)httpSession.getAttribute("patientId");
return appointmentService.getOneDetail(patient);
}
@RequestMapping("/del/{id}")
public Response delAppoint(@PathVariable String id){
return appointmentService.delAppoint(id);}8.6 parampackage com.ssm.vo.param;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class AppointmentParam {Integer patient;
Integer doctor;
String time;
Integer num;
String department;}8.7 vo.res.detailvopackage com.ssm.vo.res;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.Date;@Data@AllArgsConstructor@NoArgsConstructorpublic class AppointDetailVo {public int patient;
public int doctor;
public Date time;
public int num;
public String department;
public int fees;
public String state;}9.yml/application.propertiesspring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.driver-class-name=com.mysql.jdbc.Driverspring.datasource.druid.url=jdbc:mysql://rm-2zeu2mx5rmvv9k0tb.mysql.rds.aliyuncs.com:3306/patient?useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.druid.username=hha6027875spring.datasource.druid.password=Hha6027875mybatis.mapper-locations=classpath:mappers/*.xmlpagehelper.helperDialect=mysqlpagehelper.reasonable=truelogging.file.path=sp.loglogging.level.root=infologging.level.web=debuglogging.level.sql=debuglogging.level.com.ssm.mapper=debugthymeleafspring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.htmlspring.thymeleaf.encoding=utf-8spring.thymeleaf.servlet.content-type=text/htmlspring.thymeleaf.cache=false10.pom<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiexin.bootjsj</groupId>
<artifactId>code-race</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>code-race</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--整合spring的web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 持久层 整合 mybaits-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--分页依赖-->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 显示 层 整合thymeleaf模板-->
<!--thymeleaf默认使用html5规则标签必须闭合等 使用次此包正常解析-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>11.settings <!-- mirrors | This is a list of mirrors to be used in downloading artifacts from remote repositories. | | It works like this: a POM may declare a repository to use in resolving certain artifacts. | However, this repository may have problems with heavy traffic at times, so people have mirrored | it to several places. | | That repository definition will have a unique id, so we can create a mirror reference for that | repository, to be used as an alternate download site. The mirror site will be the preferred | server for that repository. |--><!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
-->
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>12.测试doctor:查询time科室医师列表localhost:8080/doctor/find/1?time=2022-07-28 get
查询所有医生localhost:8080/doctor/findAll get
patient:编辑就诊人localhost:8080/patient/update post
{
"name":"wufang",
"id_card":"4444444",
"phone":"1444444444445",
"state":1
}
添加就诊人 localhost:8080/patient/add post
{
"name":"qhj",
"id_card":"5555555",
"phone":"155555555"
}
查询就诊人列表 localhost:8080/patient/findAll get
删除就诊信息:localhost:8080/patient/del/4444444 getclinic:查询全部门诊 localhost:8080/clinic/findAll get
department:查询门诊下级科室:localhost:8080/department/findAll/1 get
scheduling:值班信息分页查询localhost:8080/scheduling/findAll post
{
"pageIndex":2,
"pageSize":2
}
增加一个医生值班信息localhost:8080/scheduling/add post
{
"doctor":2,
"clinic":1,
"count":21,
"time":"2022-07-29"
}
appointment:取消预约:localhost:8080/appointment/del/1 get
查看个人预约详情 localhost:8080/patient/login/1 get
localhost:8080/appointment/detail get
localhost:8080/patient/logout/1 get
执行预约挂号 localhost:8080/patient/login/1 get
localhost:8080/appointment/submit post
{
"doctor":1,
"time": "2022-07-29",
"department": "心内科"
}
localhost:8080/patient/logout/1 get13.总结1.配置ide的阿里云镜像文件打开文件位置(安装目录下面),返回上一层目录。按照 plugins\maven\lib\maven3\conf 的顺序,依次打开,在conf文件目录下出现一个setting.xml的文件
或者file-settings-build-build tools-maven或者在preferences-build,execution,deployment-mavenuser settings file找到该文件即可粘贴在mirrors下面即可,别的全部注释,把- ->放在阿里云镜像<mirror>的上面,别的全部注释2.解压编程资源包,加载maven文件3.有可能jdk没装,需要自己下载一下在idea里面关闭代码检查:file-power save mode,打开自动不检查了4.enable lombak annotation processing6.配置数据库地址,更改controller为restcontroller7.医生那最后一个字段应该是password吧8.医生查询当前time下的所有医生?方法名字还叫titleandid?啥叫当前time,是不可以理解为指定排版的医生doctorservice的返回值list<map>?service也加签名吗
这块查询要使用find/1?time=2022-07-289.排版分页传参,默认叫法pageIndex,pageSize注意startpage要放到mapper调用前面,xml里面select全部语句不能加;号,因为pagehelper会帮你加10.预约里面的department竟然是字符串,注意数据库设计主要问题是页面显示的叫做门诊,字段也叫clinic,department不是科室吗?吐血了
先按照department处理了,设计char类型
预约第二个方法需要创建一个新的类,预约详情
第一个方法用的docter int,第四个变成string无语了,按照int处理了
第三个方法mapper int,service参数string,sssb,按照String处理了,controller也是string
第四个service返回response不合适,需要返回原值,后面需要调用11.fee按照int类型处理12.clinic那参数有个appointmentparam,用不到吧,还有scheduling那个schedulingparams普通上传Ecs和rds一个vpc,一个可用区1.创建ecs时第三部系统配置可以设置密码(ecs不是操作系统),默认root - Hha6027875,要设置公网ip1.1或者通过实例右侧更多,网络,绑定弹性ip,远程连接,立即登录,弹性公网是后开的,不用担心
1.2下载jdk,放到home目录下(默认目录就是home)
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm
1.3 chmod +x jdk-8u131-linux-x64.rpm
rpm -ivh jdk-8u131-linux-x64.rpm
1.4 (不用管换行)vi /etc/profile export JAVA_HOME=/usr/java/jdk1.8.0_131
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:JAVAHOME/lib:{JRE_HOME}/lib:$CLASSPATH
export JAVA_PATH=${JAVAHOME}/bin:${JRE_HOME}/bin
export PATH=$PATH:${JAVA_PATH}
1.5 java -version javac查看是否安装成功并配置环境变量2..rds创建后(跟switch一个可用区),点击实例,选择账号管理,创建数据库账号hha6027875/Hha6027875高权限账号(可以在创建时实例配置阶段设置,也可以在账户管理中进行设置,不行的话重置一下账号密码,输密码的时候不要选择默认,手敲一下,有时候不识别)点击登录数据库,首次需要进行云资源的授权,授权dms系统默认角色,登录创建的账号
点击左侧数据库实例,右键数据库选择数据库管理,创建数据库,刷新,在自己的数据库里面进行操作,这里默认别的数据库是不让你动的,当时就吃了这个亏
别忘了设置库表主键自增加。
先把数据惯进去,右键表打开表,开启编辑,新增,提交修改即可,必须有主见rds点击实例连接,基本信息,网络类型专有网络,查看链接详情,设置白名单,添加白名单分组(基本一个可用区是没问题的),回到基本信息,专业网络,查看链接详情,复制ip,到ecs里面ping一下,如果通了更改yml的数据库地址,端口306,用户名和密码导入sql文件,右键表导入,上传sql文件提交安全组,跟白名单一个位置,白名单右面就是安全组,可以导入安全组安全组在ecs这创建,ecs管理界面安全组创建安全组出现报错null刷新几次,新建数据库有这个问题3.项目pom文件倒数第二行加jar更改数据库地址,切记切记密码要配置成rds的密码,这块特别容易忘
打包,如果右键项目有maven install 直接选就行,如果没有,需要view-toolwindow-maven-绿色三角execute maven goal-选择install即可
找到该文件放到桌面(文件目录里面可以找一下target)
进入ecs实例管理页面,上面文件,打开新文件管理,上传文件,上传到home文件夹,会话-新终端退出回到正常命令行4.查看端口是否被占用 netstat -anp | grep 8080 查看进程netstat -ntulp |grep 8080
如果占用杀死他sudo kill -9 pid
回到home
java -jar code-race-0.0.1-SNAPSHOT.jar 出现spring logo代表运行成功
这时候ecs阻塞了,需要点击会话,横向打开新终端5.输入curl http://localhost:8080/clinic/findAll测试即可简易版restcontroller,返回response即可,page/ceshi6.结束任务ps -ef|grep javakill -9 进程号7.install或者Prcompile ocess terminated setings找不到重新覆盖一下,clean一下edas版本1.打开edas,地域选择北京,创建应用新建ecs或者把已有ecs放到集群中设置登录密码,设置健康检查地址http://localhost:8080/page/ceshi创建好机器后,启动应用日志管理-日志目录-edascontainer-logs-catalina.out在线查看即可截图th:参数Model modelmodel.addAttribute("site","www.bjpowernode.com"); // 对象类型 model.addAttribute("myUser",new SysUser(1001,"王世超",20));页面都加上引用都是th:标签,${site},${myUser.name}th:href="@{/layui-v2.5.6/layui/css/layui.css}"th:href="@{/layui-v2.5.6/layui/css/myStyle.css}"th:src="@{/layui-v2.5.6/layui/layui.js}"th:src="@{/layui-v2.5.6/layui/layui.all.js}"内部链接,href都使用/page1/card,doctor,scheduling,patient,不需要加th,注意cols的[[]]要换行这样layui和thymeleaf就完美兼容了$('#search').click(function(){ $.ajax({ url:"http://localhost:8080/page/ceshi", method:"get", data:{}, success:function(res){ alert(res.data); } }); table.renderspringgsecurity百度搜吧https://blog.csdn.net/weixin_46301906/article/details/125653879org.springframework.bootspring-boot-starter-security <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
仿照代码写选择密码模式,配置授权服务器设置oauth配置,资源服务器设置功能角色设置,springsecurity配置用户角色设置等等,到时候抄代码,用户名密码改从数据库中进行获取withuser和password那里换一下,roles从角色表里面进行获取登录先访问/oauth/token请求token,参数是六个然后带着返回的access_token进行别的操作通过路径来判断是否有权限,对应的功能前缀加上/角色路径token还是模拟redis存储测试用例就是先请求oauth,参数username,password,grant_type,client_id,scope,client_secret然后请求各角色前缀路径携带token拦截器按照https://blog.csdn.net/qq_42764468/article/details/127718048写一个UserInfoInterceptor一个CommonWebMvcAutoConfiguration前者设置拦截前后的操作,按照题目要求分别在response里面配置成功和失败的返回response信息,替代默认就行后者配置具体拦截的地址即可,把相关地址放行了就可以了,不需要理解细节直接复制代码
ECS云服务器使用初体验感想
我是一名就读于电子与信息工程学院电气工程及其自动化的大一学生,在老师课堂的教学与指导中,我初次接触并了解ECS云服务器。本周体验并使用ECS云服务器,有以下感想。 云服务器具有高可靠性,通用性,高可扩展性,是网络发展中的重大突破。本次飞天加速计划,我根据观看实验列表中的教学视频,学习到了如何搭建云上博客,可以在开发者社区创作者中心中发表个人文章,也可以阅读他人发表的文章并进行交流和讨论,至此我开始了我的云上之旅。接着我通过免费领用的ESC,在ESC云服务器下进行实验,在教程详细的步骤下,在规定时间内完成了安装Wordpress快速地搭建了个人的博客,获得了操作ESC云服务器的经验。作为使用云服务器的新手的我,在教程的指导下不断摸索,成功找到了网站的ip地址,一步步下渐渐熟悉操作流程。在安装Apache的过程中,代码的编辑组合,以及安装过程中代码一串串快速的显示,更加让我体会到互联网以及云服务平台给予我们生活中带来的种种好处,简单易上手的操作,给数据的处理带来更多的便利,提高了操作的效率。但在实验操作中,并不是一帆风顺的,我也遇到了许多的问题,例如代码复制在操作台中无法粘贴,平台的密码无法显示……在有限的实验时间内,我找到了解决的方法,在第二次的实验操作中,我操作速度有着大幅度的提升,对ESC云服务器更加熟练! ESC云服务器给我带来了丰富的知识以及熟练的云上操作,阿里云好操作易上手,为大学生提供平台。希望阿里云平台越办越好,互联网事业蓬勃发展,为人民提供更多的便利。在今后的学习生活中,我会继续使用云服务器不断学习,也会将云服务器分享给更多的人。
阿里云飞天加速计划 —开发者成长计划ECS个人使用体验
一、致谢感谢阿里云推出的飞天加速计划 —开发者成长计划,让我们在校学生也可以免费领取到一台云服务器ECS,而且还有1次免费续费权益;在阿里云ECS训练营还可以学习到云服务的基本操作和体验到完整的服务器使用方法;而且阿里云开发者社区还有许多课程可以参与学习,学习教程完整又详细,还可以参考别人的实验手册,实在是太方便了。二、我的使用体验1、在领取和激活上总体来说,领取和激活的经过十分流畅,学生的认证方式是依托支付宝的,速度很迅速,一会儿就完成了认证,领取后ECS的开通速度也是非常快。2、在使用上使用体验上,系统的选择上有许多系统可以选择,包括但不限于Windows和linux,我个人选择的是CentOS7.6,系统安装起来非常快。安装后对ECS进行交互,采用的是远程链接的方式,阿里云提供了三种链接方式,分别是Workbench远程连接、VNC远程连接和发送命令(云助手),在使用体验上,如果不安装图形界面,Workbench是我个人认为最好的选择,但我个人还是喜欢安装图形界面,因此我自行安装好图形界面后直接就采用了VNC链接,无奈浏览器直接使用VNC进行连接界面太模糊了,因此我自行安装了VNC,使用软件VNC Viewer进行连接3、在搭建项目上搭建项目在阿里云的课程中心有很多教程可以参考,同时阿里云开发者社区也有很多文章可以参考,因此搭建项目的过程还算是比较顺利,除了一些需要自行处理的bug外,搭建项目很多教程和文章还是可以参考的三、个人经验分享安装VNC服务,使用VNC Viewer进行连接(1)安装gnome桌面利用Workbench 登陆上远程主机.依次执行下列命令:[root@xxx ~]#yum groupinstall -y "X Window System"[root@xxx ~]#yum groupinstall -y "Desktop"[root@xxx ~]#yum groupinstall -y "Chinese Support"(2)VNC远程访问首先,安装VNC服务器执行指令:[root@xxx ~]# yum -y install tigervnc-servervncserver 配置,单用户root a. 修改配置文件[root@xxx ~]# vim /etc/sysconfig/vncservers 在文件修改为下面两行代码:VNCSERVERS="1:root"VNCSERVERARGS[1]="-geometry 1920x1080 -nolisten tcp -localhost"为 vncserver 设置远程登录密码 这个密码是vnc连接的独立密码,与系统密码没有关系。[root@xxx ~]# vncpasswd Password:Verify:[root@xxx ~]#配置防火墙规则,允许vnc远程连接,此处一定要进行配置在ECS控制台安全组中配置,打开5901端口修改xstartup配置[root@xxx ~]# vim /root/.vnc/xstartup将最后一行twm & 修改成 gnome-session &修改默认启动方式vim /etc/inittab设置开机以图形界面启动。编辑/etc/inittab文件, 拉倒最后,将id:3:initdefault修改为 id:5:initdefault重启后生效启动服务 :[root@xxx ~]# service vncserver start如果提示已经在运行,则可以重启服务[root@xxx ~]# service vncserver restart最后一步,远程连接首先下载vnc-viewer,根据自己的系统版本选择对应的客户端安装完成后打开vnc-viewer在以下位置输入地址,IP:5901的形式,IP根据自己的情况替换接下来输入密码即可进行连接,默认是低画质,可以根据需求调整画质
通过ECS诊断服务自助解决实例启动的问题(二)
介绍ECS实例启动是实例使用的第一步也是关键的一步,这点的重要性已经很明显。在本系列第一篇文章中,已经介绍了我们的一些初期工作,在之前版本的实例启动诊断功能中,我们可以诊断云系统、用户操作系统中:fstab文件/磁盘使用率/磁盘innode的问题。因为绝大多数实例启动的问题都是出现在用户操作系统内,因此在最近的一段时间,阿里云弹性计算团队又扩充了更多的操作系统方面的诊断能力,并将原有的“实例启动异常”功能升级为“实例状态为已停止,无法正常启动”功能,扩展了更多的能力,同时推出“实例状态为运行中,操作系统启动异常”功能,两者相辅相成,共同为实例启动问题进行全面诊断。使用方式针对不同状态的实例,我们推出了两种诊断工具,他们的诊断能力有很多不同之处,对于问题的覆盖度形成了互补。1、针对停止运行的实例如下图所示,进入阿里云ECS控制台,在功能列表中选择“自助问题排查”,然后选择实例问题排查 > 实例无法连接或启动异常 > 实例状态为已停止,无法正常启动诊断项,然后选择一个需要诊断的停止状态的实例,选择一个诊断时间段,对该ECS实例进行诊断排查。诊断主要诊断实例操作系统和云系统来进行,诊断时间段主要用来选择诊断特定时间范围内云系统的问题,而操作系统问题则是诊断当前时刻的操作系统,与选择的时间段无关。诊断的实例操作系统版本需满足诊断系统支持的版本范围。需要注意的是,因为实例排查会对实例的操作系统进行诊断,需要对操作系统挂载修复盘(PE盘),在诊断或修复实例之后,需要用户手动将修复盘卸载掉,诊断报告的结果页或实例列表页会提供一键卸载的方法,否则实例会一直以修复模式启动,无法进入用户的操作系统里。2、针对运行中的实例如下图所示,进入阿里云ECS控制台,在功能列表中选择“自助问题排查”,然后选择实例问题排查 > 实例无法连接或启动异常 > 实例状态为运行中,操作系统启动异常诊断项,然后选择一个需要诊断的运行中状态的实例,对该ECS实例进行诊断排查。诊断主要针对实例操作系统进行诊断,可以诊断主流版本的Linux和windows操作系统。我们建议用户在停止状态的实例诊断完成之后,若没有诊断出问题,可以卸载修复盘后,重新启动实例,让实例处于运行中,再次发起对于运行中状态实例的诊断。如果最开始使用的是对运行中状态实例的诊断,没有诊断出问题,则可以将实例以普通模式停机,并发起停止状态实例的诊断。这样两种诊断能力结合,可以最大程度的帮助用户发现问题。诊断报告1、针对停止运行的实例经过几分钟的等待后,即可看到实例的诊断结果报告。和上一篇文章介绍的类似,如下图所示,报告主要分为3部分:第一部分是修复盘说明、临时挂载的修复盘对应的root用户以及密码信息。用户可以用该用户和密码信息通过VNC登录挂载了修复盘的ECS实例进行修复。在修复完成后需要手动卸载修复盘。第二部分是报告的核心内容展示部分,主要是展示诊断出的问题、问题详情解读以及给出解决办法。点击解决方法链接,按照文档说明操作即可修复问题,如果还有问题也可以继续发起人工工单寻求技术支持。以图中例子来看,该实例操作系统的fstab文件第12行出现了格式错误,导致操作系统在启动中无法识别该挂载设备,进而导致启动失败。点击参考链接,即可看到修复方法,按照步骤进行修复后即可去尝试再次启动实例。第三部分是整体诊断的指标概览。诊断会对云系统问题和实例操作系统问题进行诊断,对于出现问题的诊断项,报告会进行标红。如果没有发现问题,建议重新启动实例,当实例处于运行中的状态时,发起“实例状态为运行中,操作系统启动异常”的诊断,以便排查其他可能存在的问题。2、针对运行中的实例经过几分钟的等待后,即可看到实例的诊断结果报告。如下图所示,报告主要分为2部分:第一部分是报告的核心内容展示部分,主要是展示诊断出的问题描述、对应的错误码,以及对应的修复方案链接。点击文档链接,根据错误码即可在文档中找到对应的修复方案。如果还有问题也可以继续发起人工工单寻求技术支持。以图中例子来看,该实例操作系统的Grub引导文件出现了问题,导致实例启动失败。点击参考链接,根据错误码“1662001143”即可找到修复方法,按照步骤进行修复后即可尝试再次启动实例。第二部分是整体诊断的指标概览。目前主要是针对实例操作系统的截屏诊断。如果没有发现问题,建议以普通模式停止实例,当实例处于已停止的状态时,发起“实例状态为已停止,无法正常启动”的诊断,以便排查其他可能存在的问题。第二版诊断系统的诊断项介绍1、针对停止运行的实例本次诊断服务新增支持以下的诊断项:实例Linux操作系统缺失系统启动release文件缺失系统启动需要的内核文件GRUB配置中内核参数root=配置错误 实例在/etc/fstab文件中配置的设备文件系统与设备实际的文件系统不一致实例存在相同UUID的文件系统/etc/fstab 中配置了同一个文件系统挂载到不同挂载点/etc/fstab 中配置了多个文件系统挂载到同一个挂载点磁盘文件系统未启用project quota特性实例的/etc/fstab文件中配置的某个设备不存在实例中有文件系统的数据布局被破坏Linux实例中关键的系统用户不存在系统关键文件属性错误SELinux 开启且规则配置不当2、针对运行中的实例诊断服务覆盖了以下的诊断项:实例Linux操作系统系统GRUB引导失败GRUB 配置文件root UUID 不正确GRUB文件配置缺失内核运行崩溃fstab 内配置的挂载点不存在文件系统fsck异常系统关键文件缺失实例Windows操作系统引导扇区丢失或者破坏或者驱动文件丢失或者损坏BCD配置文件丢失或者损坏Bootmgr丢失或者破坏Sysprep 未完成驱动出现问题系统文件损坏或丢失BCD 损坏用户注册表损坏系统注册表丢失或者破坏磁盘问题操作系统文件丢失或者破坏非正常关机导致问题每类诊断能力后续都还有其他的诊断项会陆续发布。如果您有比较急迫的其他诊断需求,也可以联系我们。工作原理针对停止状态实例的诊断原理可参见第一篇文章的介绍。针对运行中的实例的诊断原理如下图所示,用户可以发起诊断,诊断系统收到请求后会对实例操作系统进行截图,在拿到截图结果后对图片进行分析,根据分析得到的文本等信息在诊断系统中进行进一步的结果分析处理,完成场景分类和诊断项诊断等工作,最终得到诊断结果,将诊断报告返回给用户。已开放地域目前实例启动异常诊断的能力已经通过ECS控制台在阿里云全球所有地域向所有用户开放。
【Serverless实战】B站每日自动签到&&传统单节点网站的Serverless上云
什么是函数?刚刚考完数学没多久的我,脑力里立马想到的是自变量、因变量、函数值,也就是y=f(x)。当然,在计算机里,函数function往往指的是一段被定义好的代码程序,我们可以通过传参调用这个定义好的函数,实现我们所需要的功能。那么,今天的函数计算FC又是什么? 云计算时代的当下,容器化技术与各种工具发展的DevOps,已经把开发与运维的工作进行了新的统筹,开发人员在完成代码的编写后,无需考虑环境,直接提交到各种流水线就可以完成测试、开发、部署,项目构建微服务,由容器完成环境的封装。但是往往我们最终还是需要投入精力到业务上线的集群,是私有云环境还是公有云?是裸金属服务器还是云实例ECS?是自购还是租用? 当然,DevOps的落地,服务器\集群的运维,这些都是需要投入大量的资源与精力,DevOps是一条捷径,但不是唯一的出路。因此函数计算FC的出现,带来了无服务Serverless的架构,让开发者在开发和部署的时候,不在有部署服务复杂的感觉,对服务器的无感化,可以使开发者真正的关注在自己的代码上。阿里云Serverless函数式极简编程可专注于业务创新,无采购和部署成本、提供监控报警等完备的可观测能力。函数计算是事件驱动的全托管计算服务,真正的无需去考虑服务器的运维管理,只需要完成开发的代码进行上传,函数计算会通过角色策略去规划计算资源,弹性的方式执行函数,最后高效的执行部署。优雅! Serverless 将会有那些适用场景?是个人?还是生产?那么这次我将部署两种不同方向的应用对Serverless进行测评 一、通知系统与Webhook,Trigger触发与Chat机器人。许多系统中涉及到的push类功能,例如邮件、短信、Webhook。当然Webhook的能力不只是信息通知,不过这里所指的通知功能必然是需要基础设施也就是服务器来支撑运行,如果将这些功能直接由Serverless来操作,我们便无需支付运维一台服务器,节省了大量的工作与费用。同样,我们可以利用Serverless事件驱动模型实现定时自动触发任务,自动签到自动发送。 二、其次当然是Web类的应用。基于各类Web框架的应用部署,构建基于Java、Python、PHP等语言的站点,Serverless很容易实现如wordpress这样的博客应用的上线。配合其他云产品,Codeup、OSS、RDS等,更能实现高可用高性能的Web应用,如官方提供的Kod云盘系统。 一、使用Serverless实现B站每日登陆签到 作为一个老b站用户,b站等级无疑是妥妥的"名片",当然我早已是六级大佬的一员了。b站升级所需的经验值是关键,登陆、投币、观看都会积累经验。为了可以快速升级,这次我将使用阿里云Serverless,实现每日b站的登陆经验Get,观看视频经验Get,观看直播银瓜子Get(白嫖的直播送礼道具),并且配合钉钉机器人,实现Webhook的消息推送。1.创建Serverless服务及函数 进入函数计算FC控制台,选择【服务及函数】,点击【创建服务】 在【创建服务】的页面中,输入服务名称,并选择启用日志功能,日志更能可以帮助我们更好的排查错误这里我当然需要标注服务的功能,即实现bilibili的日常登陆签到。 进入【创建函数】页面,选择【使用标准Runtime从零创建】配置函数名称,选择运行环境为Python3,并且选择从文件夹上传代码。注意,这里如果有依赖包需要提前下载到代码包下,我这里需要用到requests包 ,在本地需要执行 pip install -t . <模块名称>。不过后续也可以在控制台处执行下载命令这里的Python功能实现的脚本是定时触发类的,因此我们选择请求处理程序类型为【处理事件请求】 下方配置触发器,选择定时触发器,输入名称,选择【指定时间】,我这里选择的是每日的23点进行脚本的运行 Python脚本内容# -*- coding: utf8 -*-
import requests
import json
import time
import re
import sys
import codecs
from bs4 import BeautifulSoup
from json.decoder import JSONDecodeError
# B站登陆Cookie
cookie = ""
# Webhook地址
webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxxx"
# 自动观看的BV号,杰伦新专-最伟大的作品
bid = 'BV1ua411p7iA'
uid=re.match('(?<=DedeUserID=).*?(?=;)',cookie)
sid=re.match('(?<=sid=).*?(?=;)',cookie)
csrf=re.match('(?<=bili_jct=).*',cookie)
# 部分编码问题
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
# bv转为av
def bv_to_av(bv):
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
r = requests.get('https://api.bilibili.com/x/web-interface/view', {'bvid': bv}, headers=headers)
response = decode_json(r)
try:
return str(response['data']['aid'])
except (KeyError, TypeError):
return '883409884'
# json解析
def decode_json(r):
try:
response = r.json()
except JSONDecodeError:
return -1
else:
return response
# 自定义钉钉机器人推送
def pushinfo(info,specific):
# 定义推送内容,格式参考https://open.dingtalk.com/document/group/message-types-and-data-format
# 注意机器人的关键词
data = {
"msgtype": "text",
"text": {
"title":"Taoreset",
"content": "【Taoreset-Serverless推送】\n"+info+specific
}
}
headers = {'content-type': 'application/json'} # 请求头
r = requests.post(webhook, headers=headers, data=json.dumps(data))
r.encoding = 'utf-8'
print (r.text)
# 阿b登录,得登陆经验
def login():
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
'Cookie':cookie
}
response = requests.session().get('http://api.bilibili.com/x/space/myinfo',headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
msg = rejson['message']
if code == 0:
print('登录成功')
return True
else:
print('登录失败:'+msg)
return False
# 获取用户信息
def get_user_info():
headers = {
'Cookie':cookie
}
response = requests.session().get('http://api.bilibili.com/x/space/myinfo?jsonp=jsonp',headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
msg = rejson['message']
if code == 0:
userInfo=['账号:'+str(rejson['data']['silence']),
'硬币:'+str(rejson['data']['coins']),
'经验:'+str(rejson['data']['level_exp']['current_exp'])+"/"+str(rejson['data']['level_exp']['next_exp']),
'等级:'+str(rejson['data']['level']),
'昵称:'+str(rejson['data']['name'])
]
print(userInfo[0])
print (userInfo[1])
print(userInfo[2])
print(userInfo[3])
print(userInfo[4])
return userInfo
else:
print("用户信息获取失败:"+msg)
return "用户信息获取失败:"+msg
# 直播签到,赚银瓜子儿
def do_sign():
headers = {
'Cookie':cookie
}
response = requests.session().get('https://api.live.bilibili.com/sign/doSign',headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
msg = rejson['message']
if code == 0:
print('直播签到成功!')
return True
else:
print("直播签到失败:"+msg)
return False
# 看BV号视频,得观看经验
def watch():
aid=bv_to_av(bid)
headers = {
'Cookie':cookie
}
response = requests.session().get('http://api.bilibili.com/x/web-interface/view?aid='+str(aid),headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
#print(response.text)
if code == 0:
cid = rejson['data']['cid']
duration = rejson['data']['duration']
else:
print('视频信息解析失败')
return False
payload = {
'aid': aid,
'cid': cid,
'jsonp': "jsonp",
'mid': uid,
'csrf': csrf,
'played_time': 0,
'pause': False,
'realtime': duration,
'dt': 7,
'play_type': 1,
'start_ts': int(time.time()),
}
response = requests.session().post('http://api.bilibili.com/x/report/web/heartbeat',data=payload,headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
if code == 0:
time.sleep(5)
payload['played_time'] = duration - 1
payload['play_type'] = 0
payload['start_ts'] = int(time.time())
response = requests.session().post('http://api.bilibili.com/x/report/web/heartbeat',data=payload,headers=headers)
rejson = json.loads(response.text)
code = rejson['code']
if code == 0:
print(f"av{aid}观看成功")
return True
print(f"av{aid}观看失败 {response}")
return False
def main(*args):
if login():
ui = get_user_info()
desp='直播签到:'+str(do_sign())+'\n\n'+'观看视频:'+str(watch())+'\n\n'+ui[0]+'\n\n'+ui[1]+'\n\n'+ui[2]+'\n\n'+ui[3]+'\n\n'+ui[4]+'\n\n'
pushinfo('哔哩哔哩签到成功',desp)
else:
pushinfo('哔哩哔哩签到失败','')
if __name__ == '__main__':
main()2.修改函数配置完成函数的创建后就进入了函数管理的界面。函数代码这里就显示了我们上传的代码文件,所有代码执行的本地路径都在/code目录下。如果有依赖模块提示没有,在下方的控制台终端输入命令也可以完成模块安装下载,所有工作目录下的代码修改,完成后都需要点击部署代码进行部署上传。pip install -t . requests bs4 其余需要修改一下函数的配置,点击【函数配置】,找到【环境信息】编辑,修改【请求处理程序】,修改函数入口为<要执行的代码文件名.执行的函数名>,我这里脚本的文件名为bilibiliSignin.py,代码里的主函数为main,因此函数入口就为<bilibiliSignin.main> 3.完成函数功能测试选择【测试函数】,即可立即对函数进行触发,点击测试函数进行测试完成测试后下方就会显示日志输出内容,方便查看结果和排错 完成效果,定时触发23点准时完成签到,并由钉钉的机器人推送消息 欸嘿,大伙一起吧Serverless脚本跑起来,早日迎接B站六级会员!!! 脚本参考Github ,by sanshuifeibing 二、Serverless农产品电商网站上云改造 这里我拿隔壁软件专业(俺是网络技术的)的一个大作业项目作为部署的案例。项目是非常简单并且功能单一的,但是也是非常经典的前后端分离项目,由于我开发不太会,项目具体技术就不献丑了。之前据说是有什么版权的,我就不放源代码了hhhh 在改造之前,我相信是很多中小型公司业务的经典AllinOne结构,把业务涉及到的所有服务中间件运行在一台服务器/虚拟机上,虽然现在看可能完完全全是实验室环境,但是实际看到的依然有很多项目是这么做的。坏处也不用多提,部署运维难、难以进行资源的扩容、后续改造复杂、性能差没有应用高可用技术等等。。。 Serverless的农产品电商平台上云,项目比较简单,规划就在同一地域了。主要是将前端HTML页面与后端Jar包运行分别由两个单独的Serverless函数完成运行计算。其余支撑服务上云,分别用对应的云产品实现,这里Redis服务由于我自己ECS上有运行redis服务,就不再单独购买云数据库Redis版了,当然推荐使用阿里云的Redis云数据库产品。 1.Serverless服务创建进入函数计算控制台,选择【服务及函数】,点击【创建服务】输入创建服务的名称与描述,开启日志功能,点击确定,完成服务的创建 服务创建完成后,进入【服务详情】,找到【网络配置】,点击【编辑】选择允许访问VPC,选择自定义配置,选择VPC、vSwitch、安全组,这里需要和后续其他支撑的云产品(数据库等)保持在同一VPC下。因此需要做好云上网络的规划,也要看一下产品是否在地域下有没有库存。 2.前端页面Serverless函数创建完成服务创建后,点击【创建函数】,进入函数的创建页面选择【使用自定义运行时平滑迁移WebServer】,输入函数名称,选择运行环境为【Nginx】,上传前端html代码与nginx的配置文件,选择监听端口为80,即为原nginx中间件的服务端口。 完成函数创建后,进入到函数详情界面,可以在函数代码中对代码进行编辑修改,代码改动后需要点击部署代码重新上传。上述也提到了,这里需要将Nginx服务配置一同上传,其中需要拷贝一份/etc/nginx/mime.types文件到当下目录,避免mime文件类型映射错误。 这里放nginx关键配置,根据自己的业务情况修改,注意配置中端口监听需要与函数创建监听端口保持一致,同时网页代码的路径设置为/codehttp {
include mime.types; #注意引入此文件
keepalive_timeout 900;
server {
listen 80;
server_name localhost;
location / {
root /code;
index index.html index.htm;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /users {
proxy_pass http://serverless.后端函数访问地址.run;
#Nginx反代传给后端的函数,地址在后续后端函数创建完可以拷贝
}
location /items {
proxy_pass http://serverless.后端函数访问地址.run;
#Nginx反代传给后端的函数,地址在后续后端函数创建完可以拷贝
} 可以在调用日志查询相关日志 当函数触发运行,Serverless将会根据访问请求自动起实例,在这里可以手动登陆实例,去进行中间件服务的日志、运行环境的一些查看与排错。3.后端Serverless函数创建 回到函数服务界面,再次点击【创建函数】,进入函数的创建页面选择【使用自定义运行时平滑迁移WebServer】,输入函数名称,选择运行环境为【Java】,上传打包好的Jar包文件。 根据具体业务修改【启动命令】与【监听端口】。我这里需要监听业务端口为8080,并且需要在运行时传入数据库连接的参数,这里所配置的数据库源用户名密码连接地址,在后续的云数据库RDS中会进行相关设置。 后端函数创建完成后进入函数详情界面,其余功能与上述前端函数相同,不再复述 点击【触发器管理】,此处HTTP触发器提供了公网与内网访问地址,我们拷贝内网地址在前端中间件反代配置处填入此域名,实现访问前端触发后端函数 4.云数据库RDS MySQL Serverless创建与迁移导入 原有Allinone架构中没有做到数据库服务的独立与高可用,在此次云化部署,我们将选用阿里云公测中的云数据库Serverless版本。RDS MySQL Serverless提供了CPU、内存的实时弹性能力,具有资源用量低、简单易用、弹性灵活和价格低廉等优点,合理优化使用成本,进一步降本增效。RDS MySQL Serverless实例创建 进入云数据库RDS控制台,点击【创建实例】,开始创建实例的流程,在【基础资源】设置界面,选择Serverless版,其余根据实际进行选择 Serverless RDS创建时【实例配置】需要注意网络VPC的设置,要与Serverless服务所设定的VPC一致,实现内网数据互通。确定订单后,等待实例创建完成即可RDS数据库用户设置完成实例创建,选择管理实例。在左侧任务栏选择【账号管理】,点击【创建账号】创建数据库账户供电商平台后端进行连接。输入账户名、密码,选择为普通账户,点击确定完成用户创建 RDS 业务数据库创建选择左侧栏中【数据库管理】,选择【创建数据库】输入农产品电商业务所需的库名,并且授权账户给前一步设置的用户,点击创建完成库的设置。 RDS 服务连接地址我们的Serverless函数中所需要连接库的地址,在rds实例中【数据库】连接处可以查到,不过需要提前设定白名单。 我们将内网地址进行拷贝,并且也完成了连接用户、密码、库的配置,就可以配置到Serverless函数或者是后端代码中了 RDS 业务数据库的导入恢复 数据库的上云关键是数据内容的迁移,这次部署的业务数据库很少又很小,因此使用简单的备份SQL脚本文件作为迁移的方式。MySQL/Mariadb的数据导出有多种方式,可以根据实际需求进行备份导出,当然大型业务库有专用的备份迁移工具,这里不细说了。进入DMS数据管理服务,选择【数据库开发】,在【数据变更】下点击【数据导入】。根据具体备份方式导入数据库,我这里选择上传备份的sql脚本,提交申请开始导入数据 数据导入完成,数据迁移完成 5.Redis服务上云与静态资源CDN Redis服务上云,前文也提到了,这里Redis服务由于我自己ECS上有运行redis服务,就不再单独购买云数据库Redis版了,ECS也处于同一VPC之下,可以实现内网互通。当然推荐使用阿里云的Redis云数据库产品。 静态资源的CDN,包括css\js\图片的加速,原有架构中已经存放在阿里的CDN服务上了,我这里就不多做改动了。 6.公网业务访问域名配置 最后一步,用户最终访问的是前端Serverless函数,如同阿里云给出的提示,访问默认的公网地址不会做任何中间件解析,而是直接下载首页html静态文件,因此我们需要自己配置访问域名。 回到函数计算控制台,选择【域名管理】,点击【添加自定义域名】输入自定的域名,配置路由,选择对应函数的服务名称、函数名称、版本号LATEST(最新)将需要解析的CNAME值,拷贝拷贝CNAME记录值,点击【云解析DNS控制台】,进入解析设置,点击【添加记录】选择记录类型【CNAME】,输入主机头,填入拷贝的记录值,确认完成添加 7.农产品电商项目Serverless上云效果首页,访问效果,前端函数无误 农产品详情页访问 用户注册功能测试,数据库连接与写入无误 RDS中数据已成功写入 用户登陆测试 订单提交测试,后端函数无误 三、使用Serverless应用模板快速构建litemall电商应用系统1.基于官方模板创建应用Serverless应用提供了大量的官方应用模板,我们可以根据给出的模板来修改自己的业务,因此熟悉模板的部署也很重要。进入到函数计算FC的控制台页面,点击【应用】,选择【通过模板创建应用】,选择【商城案例】 通过详情查看部署模板的信息,以及查询源代码,点击立即创建可以快速体验Serverless应用的创建,本地部署可以通过ServerlessDev工具进行部署 2.对创建应用进行配置点击立即创建后,我们进行应用的初始化配置。部署类型有两种:1.通过第三方代码仓库部署,2.直接部署两者区别就是使用自己的仓库代码后续可以通过push更新项目发布,而直接部署需要手动配置。这里就可以看到,我们的交付触发也是以Git仓库push提交为主,每次提交会自动触发部署。如是自己配置应用,需要根据业务配置s.yaml文件,参考:https://www.serverless-devs.com/fc/yaml/readme 这里我选择Gitee仓库进行部署,但是需要进行仓库第三方应用的授权 点击前往授权,跳转到gitee的站点进行OAuth授权请求,点击同意授权 阿里用户在第一次使用FC函数计算时,需要对角色策略进行添加的,我这里已经使用过FC了,若提示需要添加策略,按照提示点击添加即可。 其他高级配置,需要根据业务进行修改,这里注意地域的选定,后续的其他弹性资源都会在此地域下,我这里选择本地杭州。 完成配置后点击创建,代码已经新建上传到我的Gitee仓库了。这里提供的s.yaml可以作为配置的参考,后续根据所部署的业务去修改yaml 3.应用部署上线应用创建完成,首次自动进行部署,这里部署状态可以看到正在部署 查看部署日志,如果部署出现错误也可以从日志信息中查询报错。部署经历了前置环境、资源同步、资源检查、执行部署这四个步骤后,我们的电商应用就完成了部署 首次部署完成,也是最新latest的一次部署版本,可以通过部署历史自由的进行回滚 4.访问部署上线的电商应用访问测试的域名,就可以看到我们上线的litemall电商系统,进入电商应用的后台管理 litemall电商系统是一个开源的前后端分离带微信小程序的电商系统,具有电商平台基础的会员管理、商城管理、商品管理、推广管理、系统管理、配置管理、统计报表。 litemall电商系统,需要配置最小开发环境有以下:MySQLJDK1.8或以上MavenNodejs 5.更换业务域名访问同样,当我们正常上线了FC的业务时,Serverless用的是默认访问地址函数计算上线提供的域名是以..fc.aliyuncs.com//proxy///[action?queries]为默认的,若是正常业务访问我们必然要修改访问的域名。 进入到函数计算FC的首页,点击高级功能下的域名管理,这里可以看到我们上线电商应用时的默认域名已经路由信息我们选择添加自定义域名 输入域名的名称,也就是购买备案的域名下的自定义二级域名点击路由配置,选择服务名称,这里是我们部署的电商系统litemall,选择函数名称与版本拷贝公网CNAME地址,后续在DNS域名管理处添加解析 进入到域名管理下,添加一条记录,记录类型选择CNAME,输入主机记录,将刚刚拷贝的公网CNAME地址粘到记录值,点击添加即可 回到函数计算FC,在最后点击创建即可,回到主页看到我们新绑定的域名 最后,拿手机访问我自定义配置的公网地址电商服务正常上线,公网地址正常访问主页 商品的详情购买页面 最后Serverless相对其他方案来说,也是非常容易上手并高效的技术方案。上面的部署测试,其实还有很多需要改进的地方,例如第二个农产品电商上云项目,真正可靠的云上业务还需要负载均衡、高可用多地容灾、安全等其他云产品的引入,我想把案例的重点放在Serverless服务器无感化上,本人也使用过不少阿里云的技术产品,深知对底层基础设施运维难度。虽然这几个月学习生活比较繁忙,但是还是对社区的各种活动非常感兴趣,也想做一些更好的测试。这次的Serverless无论是对个人用户,还是企业用户。都是一种非常不错的选择,弹性资源与按需付费,更加节省资源与Money,更加优雅!
阿里云服务器 —— linux是什么样子的呢(适合新手,建议收藏!)
首先、确保服务器系统是Linux,如果不是,请登录阿里云控制台【更换操作系统】,如下所示:步骤:登陆阿里云成功——云服务器 ECS——实例——按如下选择,依次提示:更换操作系统需要停止实例或进入如下地址进行查看,选择所需的内核: 如何更换ECS服务器的操作系统 - 阿里云https://help.aliyun.com/knowledge_detail/97744.html?spm=5176.22414175.sslink.4.64ea49b2Ext3RU 其次,安装xshell连接服务器,或通过下图位置进入远程服务:远程连接Linux服务器 - 轻量应用服务器 - 阿里云https://help.aliyun.com/document_detail/59083.html以下主将xshell链接远程服务: 根据在上述位置设置的账号密码在xshell界面连接服务器:连接成功后 ,参考以下链接进行宝塔面板安装:1、去宝塔面板官网https://www.bt.cn/download/linux.html复制这段安装代码或使用以下代码:yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
2、 将复制的这段代码粘贴到服务器回车开始安装,中途可能会弹出选择y/n 输入 y 继续就行。3、 安装完系统会给出一个网址和一个默认用户名、密码 后期如果忘记了的话还可以去服务器里输入bt default查看登陆网址、用户名和密码:宝塔面板(Linux版)安装与使用_wangwangwang21的博客-CSDN博客_宝塔安装linux宝塔面板(Linux版)安装与使用宝塔面板是提升运维效率的服务器管理软件,真的超级方便。因为我买的Linux服务器,所以这里简单说下Linux版的宝塔面板的安装与使用!1 用Xshell6或者其他远程连接工具连接到你的服务器2.去宝塔面板官网https://www.bt.cn/download/linux.html复制这段安装代码yum install -y wget && wget -O install.sh http://download.bt.cn/install/insthttps://blog.csdn.net/wangwangwang21/article/details/1073584284、复制给的这个网址,在浏览器粘贴输入给默认用户名和密码访问进入宝塔面板管理界面注意:宝塔面板的默认端口是8888,如果你服务器安全组没放行该端口,你访问这个网址的时候肯定是无法访问的,所以,你得先去你买服务器的云服务商对应服务器实例安全组里面放行该端口然后再访问网址,输入用户名密码,就可以进入你的服务器控制面板啦!最后再重点提醒一下,服务器安装宝塔面板的时候,官方建议服务器必须是纯净的,就是没安装过apache啊mysql啥的,否则有可能安装不上,所以小伙伴们安装的时候可能会安装失败,其原因可能就是你的服务器不纯!我之前装了apache服务,但是宝塔面板依然安装上了!具体啥情况我也不清楚!因为默认端口都是80,所以端口冲突了,我改了apache的默认端口,才使通过宝塔面板搭建的网站能正常访问! 以上搭建成功后,就开启你的Linux之旅吧具体可参考此篇: 基于阿里云服务器Linux系统部署JavaWeb项目 - JalonY - 博客园阿里云:全球领先的云计算及人工智能科技公司,致力于以在线公共服务的方式,提供安全、可靠的计算和数据处理能力,让计算和人工智能成为普惠科技。https://www.cnblogs.com/yijialong/p/9606265.html 如果运行过程中出现此异常(Linux -bash: java: command not found 解决方法),可参考如下篇:Linux -bash: java: command not found 解决方法_格子的博客-CSDN博客Linux -bash: java: command not found 解决方法https://blog.csdn.net/szxiaohe/article/details/76650266本人选择的是系统镜像:CentOS 8.4 64位。根据需要部署了以下环境:1、配置Java环境2、安装Tomcat及配置、war包部署3、安装MySql及配置、运行sql文件准备工作:1、首先需要开通项目用到的端口,例如8080端口,下图为本人开通的端口(授权对象ip就是被允许访问端口的主机ip,也就是阿里云公网ip,可以是其它云服务ip),仅供参考。详情点击打开连接2、网上很多教程是通过Xshell终端模拟器访问远端不同系统下的服务器,以及配合Xftp文件传输客户端来上传文件(如Java、Tomcat安装包)。这里推荐使用MobaXterm,有SSH链接功能,也有FTP功能,还可以包括VNC远程桌面连接功能。MobaXterm官网。3、下载Linux版本的jdk,选择后缀是.tar.gz的,如下图所示。点击打开链接。4、下载Tomcat安装包,本人下载的是Tomcat 8.5.33版本。点击打开链接。5、打开MobaXterm,连接系统。将上述两个安装包上传至指定目录下。操作步骤如下。 a.点击Session,选中SSH协议,输入阿里云公网IP地址,点击OK。b.打开之后初始目录是root,返回上一级再打开home目录,将安装包均上传至该目录。 c.在usr目录下创建java,再在java下创建以下两个目录。 1、配置Java环境a.运行解压命令:tar -xzf /home/jdk-8u181-linux-x64.tar.gz -C /usr/java/jdkb.配置环境变量(或者直接打开etc目录下的profile文件):vi /etc/profile在文件末尾处添加以下内容并保存:#set java environment
export JAVA_HOME=/usr/java/jdk/jdk1.8.0_181
export JRE_HOME=/usr/java/jdk/jdk1.8.0_181/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$JAVA_HOME:$PATH保存命令:source /etc/profilec.验证安装,如下所示表示成功:2、安装Tomcat及配置、war包部署a.运行解压命令:tar -xzf /home/apache-tomcat-8.5.33.tar.gz -C /usr/java/tomcatb.配置环境,如下图所示,执行命令或者点击文件均可:在文件末尾处添加以下内容并保存:export JAVA_HOME=/usr/java/jdk/jdk1.8.0_181
export JRE_HOME=/usr/java/jdk/jdk1.8.0_181/jrec.在bin目录执行如图所示命令./startup.sh,验证安装:或者浏览器输入阿里云公网IP地址加上8080端口,页面如下图所示表示成功:d.将JavaWeb项目的war包,上传至Tomcat的webapps目录下即可,如下图所示:这时在8080端口后加上项目名称,即可运行此项目,如下图所示(如果有对应数据库的话,下一条继续介绍如何配置数据库):3、安装MySql及配置、运行sql文件a.安装MySQL官方的yum repository:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpmb.下载rpm包:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# yum -y install mysql57-community-release-el7-10.noarch.rpmc.安装MySQL服务,最后会出现个complete!:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# yum -y install mysql-community-serverd.修改MySQL配置文件(或者修改etc目录下的my.cnf文件并保存),跳过密码登录:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# vi /etc/my.cnf末尾处添加如下内容:skip-grant-tablese.启动MySQL服务:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# systemctl start mysqld.servicef.登录MySQL:[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# mysql -u rootg.修改密码:mysql> use mysql;
Database changed
mysql> update mysql.user set authentication_string=password('1234') where user='root' ;h.退出mysql,重新在刚刚那个配置文件中去掉skip-grant-tables,然后重启MySQL:mysql> exit
[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# vi /etc/my.cnf
[root@iZwz9eu3mkqq1njlkrfhc8Z ~]# systemctl restart mysqld.servicei.使用新密码登录MySql,如下图所示:此处如果失败建议在systemctl restart mysqld.service如下操作:mysql> use mysql;
mysql> update user set password=password(“你的新密码”) where user=“root”;
mysql> flush privileges;
mysql> quitj.必须重设密码,并设置密码等级与最小长度:SET PASSWORD = PASSWORD('1234');
mysql> set global validate_password_policy=0; //改变密码等级
mysql> set global validate_password_length=4; //改变密码最小长度k.再次修改密码:SET PASSWORD = PASSWORD('1234');l.配置远程登录(root为用户名,1234为密码),以及刷新系统权限:mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '1234' WITH GRANT OPTION;
mysql> flush privileges;m.创建数据库并运行sql文件:mysql> create database corporate_genealogy;
mysql> use corporate_genealogy;
mysql> source /home/corporate_genealogy.sql;n.使用Navicat数据库管理工具连接阿里云数据库,如下图所示:最后附上Linux系统下MySql数据库的常用操作(数据库操作必须以 ';' 号结尾): 点击打开链接本文部分参考学习了:https://www.cnblogs.com/shanheyongmu/p/6070618.html http://www.cnblogs.com/wangshen31/p/9556804.html至此是对JavaWeb项目部署部分功能的一个简单介绍,后续会继续说明其它部分功能所遇到的问题以及解决方法。安装mysql时会出现以下问题时解决方案:用navicat连接数据库报错:1130-host ... is not allowed to connect to this MySql server如何处理_dabao87的博客-CSDN博客这个问题是因为在数据库服务器中的mysql数据库中的user的表中没有权限(也可以说没有用户),下面将记录我遇到问题的过程及解决的方法。 在搭建完LNMP环境后用Navicate连接出错 遇到这个问题首先到mysql所在的服务器上用连接进行处理 1、连接服务器: mysql -u root -p 2、看当前所有数据库:show databases; 3、进入mysql...https://blog.csdn.net/dabao87/article/details/80571877重置密码解决MySQL for Linux错误 ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using passwor_修改个昵称真不容易的博客-CSDN博客重置密码解决MySQL for Linux错误 ERROR 1045 (28000): Access denied for user ‘root’@‘localhost’ (using password: YES)一般这个错误是由密码错误引起,解决的办法自然就是重置密码。假设我们使用的是root账户。1.重置密码的第一步就是跳过MySQL的密码认证过程,方法如下:#vim /etc/my.cnf(注:windows下修改的是my.ini)在文档内搜索mysqld定位到[mysqld]文本段:/mhttps://blog.csdn.net/weixin_49914569/article/details/107843122ERROR 1062 (23000): Duplicate entry '%-root' for key 'PRIMARY'_kfcman的专栏-CSDN博客MySQL> update user set host='%' where user = 'root'; ERROR 1062 (23000): Duplicate entry '%-root' for key 'PRIMARY' 然后查看了下数据库的host信息如下: host已经有了%这个值,所以直接运行命令: 复制代码 代码如下: MySQL>fl...https://blog.csdn.net/kfcman/article/details/84926948 Tomcat连接失败时:Linux下Tomcat启动正常,但浏览器无法访问Tomcat 8080端口_pengjunlee的博客-CSDN博客_tomcat8080端口无法访问问题:虚拟机上安装centOS7,配置Tomcat并成功启动。但是在浏览器里却无法访问Tomcat 8080端口。原因:因为CentOS 7或RHEL 7或Fedora,默认防火墙是由firewalld来管理,而firewalld没有对8080端口开放,所以进行8080端口开放配置。CentOs 7 之前的版本,由iptables控制Linuxs的端口。CentOS升级到7之后...https://blog.csdn.net/pengjunlee/article/details/104377198Linux系统MySQL常用:Linux下MySQL数据库常用基本操作 一 - lhfly - 博客园1、显示数据库 show databases;2、选择数据库use 数据库名;3、显示数据库中的表show tables;4、显示数据表的结构describe 表名;5、显示表中记录SELECT *https://www.cnblogs.com/xdpxyxy/archive/2012/11/16/2773662.html MySQL导入导出:linux下导入导出mysql数据库_Stephen-CSDN博客首先linux 下查看mysql相关目录:输入:whereis mysql输出:/usr/bin/mysql---- mysql的运行路径/usr/lib/mysql----- mysql的安装路径/usr/share/mysql/usr/share/man/man1/mysql.1.gz确定了运行路径,执行导入、导出mysql数据库命令。导出:1、先cd到mysql的运行...https://blog.csdn.net/luanfenlian0992/article/details/90294014Linux下Mysql不区分大小写 Linux下设置mysql不区分大小写 - 陈晓晨不吃香菜 - 博客园一、通过命令查看mysql是否是区分大小写的 lower_case_table_names=1(说明是不区分大小写的) lower_case_table_names=0(如上图为0说明区分大小写的)https://www.cnblogs.com/it1997/p/11183232.htmlLinux常用命令 Linux下常用命令和应用部署 - 一辈子的守候 - 博客园下载jdk:如jdk-1_5_0_22-linux-i586-rpm.bin下载tomcat:如apache-tomcat-6.0.26.tar.gz安装JDK1. JDK安装1.1 解压并授权给下载https://www.cnblogs.com/skey_chen/archive/2011/03/29/1998912.html