目录
3.3.1 修改Jenkins监听dev分支代码(已经配置好了)
一、引言
过去一段时间的学习,大家对于框架、微服务、数据库等都有了一定的练习和认知。但是一个真实的开发流程到底是什么样?到公司了真实的微服务又是什么样子呢?我们内心一定会有这样几个问题:
-
微服务技术如何在企业真实落地
-
刚入职如何快速上手微服务项目
-
新到一家公司应该从哪拉取代码
-
标准的开发流程和规范是怎么样
因此今天开始我们将借助于“天机学堂” 这个微服务实战,带领大家完成下面知识点的学习:
-
模拟入职代码拉取、阅读代码、修复缺陷的流程
-
搭建开发环境,优化项目代码,开发新的业务代码
-
了解代码自动化部署,CI/CD
二、项目介绍
天机学堂是一个基于微服务架构的在线教育项目,核心用户不是K12群体,而是面向成年人的非学历职业技能培训平台。
随着2021年7月双减政策的颁布,我国的教育培训(K12)课外辅导遭受到了毁灭性的打击。相对的,职业教育培训市场规模持续增长。
疫情导致的经济形式加剧变化,也让越来越多的人选择了在线教育,由于其成本更低、学习时间更碎片化、教育资源更能充分利用的多重优势,在线教育市场规模也在不断增长,并且前景巨大。
1. 系统功能演示
1.1 在线演示系统地址
1.2 系统功能介绍
天机学堂分为两部分:
-
学生端(用户端):其核心业务主体就是学员,所有业务围绕着学员的展开
-
管理端(平台运营人员和老师端):其核心业务主体包括老师、管理员、其他员工,核心业务围绕着老师展开
1.2.1 老师核心业务
例如,老师的核心业务流程有:
虽然流程并不复杂,但其中包含的业务繁多,例如:
-
课程分类管理:课程分类的增删改查
-
媒资管理:媒资的增删改查、媒资审核
-
题目管理:试题的增删改查、试题批阅、审核
-
课程管理:课程增删改查、课程上下架、课程审核、发布等等
2.3.2 学员核心业务
学员的核心业务就是买课、学习,基本流程如下:
2. 系统架构介绍
了解系统架构设计,是快速了解系统所用技术栈、部署情况、数据库设计的一种方式,下面我们将了解一下天机学堂所涉及的相关架构。
2.1 系统架构
天机学堂目前是一个B2C(Business To Custom,平台对个人)类型的教育网站,因此分为两个端:
-
后台管理端
-
用户端(PC网站)
其它的了解:
-
O2O:Online To Offline,线上对线下,美团外卖,猫眼订票
-
B2B:Business To Business,商家对商家,比如一些批发平台
整体架构如下:
2.2 技术架构
3. 小结
天机学堂是一个基于微服务架构的在线教育B2C项目,核心用户不是K12群体,而是面向成年人的非学历职业技能培训平台。整个系统有2个端,分别是:
-
学员用户端:主要是给学员使用的,学员可以注册帐号并登录到系统里,然后就可以搜索浏览课程、购买课程开始学习。为了提升用户学习的积极性,我们系统还提供了一些激励和互动措施,比如:用户在学习过程中可以记录笔记、互动问答,这些互动的活动还可以累计积分,系统会根据积分形成排行榜。另外,系统还会根据排行榜给用户发放一些优惠券,进一步增加用户的粘性。
-
平台管理端:主要是给平台运营人员和老师使用的。老师可以在管理端发布课程并进行回答学员的提问;平台运营人员可以对所有的数据进行管理和统计,特别是增加各种活动,并发放优惠券。
项目的技术主要是SpringCloud和MybatisPlus技术体系,还用到了xxl-job分布式定时任务调度,基于Redisson实现的分布式锁,rabbitMQ消息队列;在数据存储上,以MySQL为主,一些非关键但是数据量很大的数据使用MongoDB存储。搜索功能使用ElasticSearch
三、项目环境搭建
为了模拟真实的开发场景,我们设定的场景是这样的:天机学堂项目已经完成1.0.0版本60%的功能开发,能够实现项目的课程管理、课程购买等业务流程。现在需要加入课程学习、优惠促销、评价等功能。
相关微服务及1.0.0版本的完成状态如下:
微服务名称 | 功能描述 | 完成状态 |
---|---|---|
tj-parent | 父工程 | √ |
tj-common | 通用工程 | √ |
tj-message | 消息中心 | √ |
tj-gateway | 网关 | √ |
tj-auth | 权限服务 | √ |
tj-user | 用户服务 | √ |
tj-pay | 支付服务 | √ |
tj-course | 课程服务 | √ |
tj-exam | 考试服务 | O |
tj-search | 搜索服务 | √ |
tj-trade | 交易服务 | O |
tj-learning | 学习服务 | X |
tj-promotion | 促销服务 | X |
tj-media | 媒资服务 | √ |
tj-data | 数据服务 | O |
tj-remark | 评价服务 | X |
部署环境如下:
1. 导入虚拟机
我们利用虚拟机,模拟真实的企业环境,搭建了一套开发环境,其中部署了开发常用的组件:
-
Git私服(gogs):代码全部提交带了自己的Git私服,模拟企业开发的代码管理,大家也需要自行到私服拉取代码
-
jenkins:持续集成,目前已经添加了所有部署脚本和Git钩子,代码推送会自动编译,可以根据需求手动部署
-
nacos:服务注册中心、统一配置管理,大多数共享的配置都已经交给nacos处理
-
seata:分布式事务管理
-
xxl-job:分布式任务系统
-
es:索引库
-
redis:缓存库
-
mysql:数据库
-
kibana:es控制台
如图:
导入方式参考资料里的《虚拟机运行说明.md》
ip地址:192.168.150.101 帐号:root 密码:123321
注意:
-
导入虚拟机后所有软件即可使用,无需重复安装
-
VMware一定要按照文档中设置IP段为192.168.150.0,不要私自修改。
-
一定要关闭windows防火墙。
-
启动虚拟机的时候,你的电脑要联网(虚拟机里nginx 需要联网访问腾讯云)
2. 配置本机hosts
可以 利用SwitchHosts等软件,更方便的管理本机hosts文件。
为了模拟使用域名访问,我们需要在本地配置hosts:C:\Windows\System32\drivers\etc\hosts
192.168.150.101 git.tianji.com
192.168.150.101 jenkins.tianji.com
192.168.150.101 mq.tianji.com
192.168.150.101 nacos.tianji.com
192.168.150.101 xxljob.tianji.com
192.168.150.101 es.tianji.com
192.168.150.101 api.tianji.com
192.168.150.101 www.tianji.com
192.168.150.101 manage.tianji.com
当我们访问上述域名时,请求实际是发送到了虚拟机,而虚拟机中的Nginx会对这些域名做反向代理,这样我们就能请求到对应的组件了:
在浏览器中输入对应域名,即可查看到对应服务,例如Git私服地址:http://git.tianji.com
每个域名对应的服务列表如下:
名称 | 域名 | 账号 | 端口 |
---|---|---|---|
Git私服 | http://git.tianji.com | tjxt/123321 | 10880 |
Jenkins持续集成 | http://jenkins.tianji.com | root/123 | 18080 |
RabbitMQ | http://mq.tianji.com | tjxt/123321 | 15672 |
Nacos控制台 | http://nacos.tianji.com | nacos/nacos | 8848 |
xxl-job控制台 | http://xxljob.tianji.com | admin/123456 | 8880 |
ES的Kibana控制台 | http://es.tianji.com | - | 5601 |
微服务网关 | http://api.tianji.com | - | 10010 |
用户端入口 | http://www.tianji.com | - | 18081 |
管理端入口 | http://manage.tianji.com | - | 18082 |
同样,我们访问用户端或者管理端页面时,也会被Nginx反向代理:
当我们访问www.tianji.com时,请求会被代理到虚拟机中的/usr/local/src/tj-portal
目录中的静态资源
当页面访问api.tianji.com时,请求会被代理到虚拟机中的网关服务。
3. 部署启动服务
在企业开发中,微服务项目非常庞大,往往有十几个,甚至数十个,数百个微服务。而这些微服务也会交给不同的开发组去完成开发。你可能只参与其中的某几个微服务开发,那么问题来了:
如果我的微服务需要访问其它微服务怎么办?
难道说我需要把所有的微服务都部署到自己的电脑吗?
很明显,这样做是不现实的。
-
第一,不是所有的代码你都有访问的权限;
-
第二,你的电脑可能无法运行这数十、数百的微服务。
因此,公司往往会给我们部署一套开发环境,在上面运行那些经过测试的可部署的微服务。而多数情况下我们是面向接口编程,功能自测完成后再与公司开发环境的其它微服务联调。
为了模拟真实环境,我们在虚拟机中已经提供了一套持续集成的开发环境,代码一旦自测完成,push到Git私服后即可自动编译部署。
而开发我们负责的微服务时,则需要在本地启动运行部分微服务。
3.1 虚拟机部署公用服务
部署步骤说明
项目已经基于Jenkins实现了持续集成,每当我们push代码时,就会触发项目完成自动编译和打包,制作成docker镜像,启动docker容器,然后就可以访问这个服务了。
如果要部署某个微服务时,我们只需要:
1. 打开Jenkins管理界面
访问jenkins控制台:http://jenkins.tianji.com (账号:root/123)
2. 部署微服务
点击对应微服务后面的运行按钮
构建过程中,可以在页面左侧看到构建进度,如果没有说明构建已经结束了(你的机器速度太快了!):
完成后,点击对应的微服务名称【例如tj-gateway】,即可进入构建任务的详情页面,在页面左侧可以看到构建历史:
其中#1代表第一次构建,点击前面的√即可查看构建日志:
看到上面的日志,说明构建已经成功,容器也成功运行了。
3. 验证微服务是否成功启动
可以打开Nacos查看服务是否注册成功
也可以使用FinalShell或者WindTerm连接虚拟机
-
执行命令:
dps
(或docker ps
),如果能看到刚刚部署的容器,说明部署已经成功
-
执行命令:
docker logs -f 容器名
查看容器日志,如果看到以下结果说明服务已经成功启动按Ctrl+C退出查看
启动必要的微服务
我们需要分别启动几个开发完成的微服务(我们开发中为了节省内存资源,可以只启动我们所依赖的服务):
-
tj-user
-
tj-auth
-
tj-gateway
-
tj-course
-
tj-media
-
tj-search
-
tj-exam
-
tj-data
-
tj-message
-
tj-trade
-
tj-pay
此时访问Nacos控制台,可以看到微服务都成功注册了:
此时访问 http://www.tianji.com 即可看到用户端页面:
此时访问 http://manage.tianji.com 即可看到管理端页面:
如果想要知道微服务具备哪些API接口,可以访问网关中的swagger页面,路径如下:
http://api.tianji.com/doc.html
其中可以查看所有微服务的接口信息
3.2 本地部署自己的服务
对于需要开发功能的微服务,则需要在本地部署,不过首先我们要把代码拉取下来。
克隆项目代码并创建dev分支
查看Git私服的代码:http://git.tianji.com/tjxt/tianji :
利用命令将代码克隆到你的IDEA工作空间中:
git clone http://192.168.150.101:10880/tjxt/tianji.git -b lesson-init
注意,开发时需要使用dev分支,因此我们需要创建新的分支:
# 进入项目目录
cd tianji
# 创建新的分支
git checkout -b dev
为了方便我们教学,目前所有微服务代码都聚合在了一个Project中,如图:
如何在本地启动服务
在默认情况下,微服务启用的是dev配置,如果要在本地运行,需要设置profile为local:
可以在本地启动ExamApplication
,然后我们去Nacos控制台查看exam-service,可以看到有两个实例,分别是虚拟机IP和宿主机IP:
本地启动服务的操作步骤演示完成后,关闭掉Exam服务即可
4. 小结
第1步:启动虚拟机
1. 打开虚拟机里.vmx文件
2. 修改VMWare的网卡配置,把VMnet8的ip段设置为192.168.150.0
3. 再启动虚拟机
第2步:配置域名映射
1. 打开本机C:\Windows\System32\drivers\etc\hosts
2. 把笔记里的域名映射添加到文件的最后,保存文件
3. 验证基础环境是否成功。可能需要等待一会
打开Nacos:http://nacos.tianji.com
打开RabbitMQ:http://mq.tianji.com
...
第3步:部署启动公用服务
1. 打开Jenkins管理界面 http://jenkins.tianji.com,输入帐号密码并登录
2. 找到tj-auth, tj-user, tj-gateway, ……任务,点击运行按钮
不要启动 xxx-debug和 tjxt-dev-build两个任务
3. 打开Nacos,验证一下服务是否启动成功
http://nacos.tianji.com,找到服务列表,刷新几次
需要耐心等待,可能比较慢
最终会有11个服务注册到Nacos里来
第4步:本地服务的拉取和启动
1. 从Gogs里克隆项目代码,并直接检出lesson-init分支
2. 使用idea打开项目代码,然后创建dev分支
3. 修改idea里的启动链接,激活local环境
4. 运行启动链接,在本机启动自己负责的服务
四、项目开发规范
开发规范是体现一位程序员经验、技能水准的最直接的地方,以下总结并归纳了一部分,但也不是很全(如代码封装、重构、命名、重复代码、正交性、解耦等),建议朗读并背诵全文。如有精力和时间推荐阅读:《程序员修炼之路》、《代码简洁之道》
1.项目结构规范
在微服务架构中,工程不再是在一个工程目录中,这个我们在微服务学习阶段已经深有感知,如cloud-demo中的order-service、user-service、feign-api、gateway等。因此对于企业开发其目录结构也一般有两种:
-
一个工程多个Project,可以理解为上述的:oder-service、user-service等都是单独工程,这个耦合度降低了,但是不便于维护管理
-
一个工程只有一个project,其中有多个Module,跟我们微服务学习阶段的工程目录结构一致,此时方便管理,也利于公共代码的使用,降低代码重复率
因此我们的工程目录结构如下:
但是也存在部分模块,如权限服务:tj-auth,会在多个微服务中共享使用,此时我们会针对module再封装子模块,变成:
此时,右侧多个module职责如下:
-
tj-auth-common:权限公共代码模块,tj-auth自身代码封装、别的模块也可pom直接引入使用
-
tj-auth-resource-sdk:给其余微服务使用的代码模块
-
tj-auth-gateway-sdk:给网关单独使用的核心模块
-
tj-auth-service:权限自身业务逻辑处理层
2.代码开发规范
2.1 实体类规范
在微服务开发中,我们实体类按照不同职责会分成下面几种类型:
-
DTO:Data Transfer Object,数据传输对象,在客户端与服务端间传递数据,通常是新增或修改时的参数
例如微服务之间的请求参数和返回值、前端提交的表单
-
PO:Persistent Object, 持久层对象,与数据库表一一对应,作为查询数据库时的返回值【数据库映射对象】
-
VO:View Object,视图对象,返回给前端用于封装页面展示的数据【前端返回】
-
QUERY:查询对象,一般是用于封装复杂查询条件【前端查询入参】
如我们的工程目录中的代码规范:
2.2 入参校验规范
在微服务中,对于客户端(前端http、跨服务调用rpc)的调用,我们对于接口的入参往往也会做基础校验:非空、字段长度、日期格式等。以前端请求为例,假设做下单请求,我们针对不想为空的字段一般会这么来做:
-
请求入参增加注解:@NotNull做非空校验、@Size做数据大小校验等
下图中@ApiModel、@ApiModelProperty是给swagger接口做中文说明使用的,与校验无关,这样工程发布后前端就可以知道每个字段的含义
-
controller接口增加注解:@Validated, 或者 @Valid
2.3 接口文档规范
接口设计无外乎:接口地址、入参、出参,因此我们需要将所涉及的类中都增加swagger注解,前端就可以快速知晓含义,便于我们的联调测试。
2.4 依赖注入规范
为了方便管理各种Bean,Spring提供了依赖注入的方式,常见的有:
-
字段成员变量注入(@Autowired 或 @Resource,工作最常用)
-
构造函数注入(Spring推荐)
-
set方法注入
对于字段注入的方式大家都非常、非常的熟悉了,我们也建议大家在工作中直接这样使用。为了多一种技艺的学习掌握,这里我们给大家实战另一种方式:构造函数注入。而要实现构造函数注入,就意味着需要我们去每一个使用的地方编写一个构造器,显然太过麻烦,但Lombok已经帮我们做好了,我们只需像下面这样就可以优雅的实现Spring Bean的注入和使用。
3. 配置文件规范
对于一个项目而言,前面我们也提到开发环境众多,包含local、dev、test、pre、prod等多个环境,项目配置文件往往也是多个,这里我们准备如下:
文件 | 描述说明 |
---|---|
bootstrap.yml | 通用配置属性,包含服务名、端口、日志等等各环境通用信息 |
bootstrap-dev.yml | 线上开发环境配置属性,虚拟机中部署使用, 这里平级的也有bootstrap-test、bootstrap-pre、bootstrap-prod的环境文件 |
bootstrap-local.yml | 本地开发环境配置属性,本地开发、测试、部署使用 |
配置文件之间主要的差别是:nacos地址、数据库地址、mq地址等。因为不同的环境中相关的中间件都是独立部署的:即有开发环境的nacos、测试环境的nacos、预发环境nacos、生产nacos,都是独立的部署信息,因此我们多环境配置文件也主要是修改这些信息。
除了基本配置以外,其它的配置都放在了Nacos中共享,包括两大类:
-
共享的配置(在命名空间public下查看):
在nacos中查看主要如下:
主要存放一些公用配置项如日志级别、ID生成策略等。同时会发现上图中的语法是:默认写死IP、端口,但同时支持用户通过选中的key进行覆盖,从而实现多环境IP、端口等的差异化配置。上图中也会发现只有数据库名没有默认值,是因为我们在工程代码中声明了(并非必须,是为了演示这种覆盖的效果)
-
微服务中可能会根据业务变化的配置,如nacos访问后(命名空间为public)发现有一个:search-service.yaml,就是特属于某个微服务的
更多配置信息,登录 http://nacos.tianji.com 即可看到所有被管理的配置信息
4.异常和日志规范
4.1 异常规范
在微服务项目中,我们可不能随便的:throw new RuntimeException
了。正规的项目中都应该有针对于当前业务工程的异常类封装,我们提供了常见的几种:
异常类 | 状态码 | 描述 |
---|---|---|
CommonException | -- | 所有异常父类 |
BadRequestException | 400 | 参数有误异常 |
UnauthorizedException | 401 | 未登录异常 |
ForbiddenException | 403 | 没有权限访问的异常 |
BizIllegalException | 500 | 业务状态异常 |
DbException | 500 | 数据库异常 |
同时在tj-common模块中,我们利用@RestControllerAdvice对异常做了统一处理,可参照:com.tianji.common.mvc.advice.CommonExceptionAdvice
。这些异常处理类一般的项目中都已经定义好,对于研发人员来说,只需要在对应的异常路径抛出对应的异常即可,如:throw new DbException,就会被自动拦截,并完成响应体的封装,最终返回给前端。
4.2 日志规范
在springboot框架中,其已经完成了Slf4j + logback实现,并且有自动装配,比如我们想输出到指定文件、格式,才需要修改配置项,如下:
logging:
pattern:
dateformat: HH:mm:ss.SSS # 日志中的日期格式
console: "[%X{requestId:-sys}] ... %m%n " # 输出到控制台的日志模板
file: " [%X{requestId:-sys}] ... %m%n " # 输出到文件的日志模板
file:
path: "logs/${spring.application.name}" # 这里定义了日志保存在项目的logs目录
当然细心的读者应该发现了,这部分配置跟工程无关,因此我们可以解耦到nacos配置中心中,我们已经封装到了:share-logs.yaml。并且springboot已经实现了日志的滚动记录,名称默认为:spring.log.${date}.${index}.gz ,根据日期、文件大小自动拆分,形成新的日志文件。
5. 小结
实体类的划分:
-
DTO:客户端要新增或修改,提交的参数封装成DTO;其它服务远程调用时传的参数是DTO
-
Query:客户端要查询,查询条件封装成Query
-
PO:对应数据库表的实体类
-
VO:给客户端返回的结果对象
入参校验规范:
-
参数实体类里的属性上加校验注解:@NotNull, @Min, Max,...
-
Controller里方法的形参上要加注解:@Validated 或者 @Valid
依赖注入规范:
-
可以使用 private ICourseService courseService; + @Autowried
-
可以使用 private ICourseService courseService; + @Resource
-
可以使用 private
final
ICourseService courseService; 然后在类上加@RequiredArgsConstructor
接口文档规范:Swagger的使用规范,略
配置文件规范:在Nacos里有配置文件,如果要修改配置,可以使用 tj.xxxx配置参数
异常处理规范:
-
天机学堂项目里的异常,抛出来。要抛出的是CommonException的子类,是自定义的异常类
-
项目里有统一异常处理器,可以抓取CommonException异常,给客户端返回友好提示
五、缺陷修复验证
“写代码5min、改BUG 2小时”、“代码都是百度CV的怎么会报错呢?”、“哎一样的代码,test环境是好的,生产怎么就不行呢?”,诸如此类的场景大家一定不陌生吧,作为一名刚到公司的新人,公司也一般不会让新人直接设计表、架构搭建,往往是从一个小bug开始逐步熟悉业务。接下来我们复现一下这个缺陷:
-
分别用杰克(jack/123)、萝丝(rose/123456)登录,并进入“我的订单模块”
-
杰克删除自己的订单信息,发现删除正常
-
萝丝删除自己第一个订单信息,系统异常,并提示:不能删除他人订单
这明明是我萝丝登录进来,我自己的订单,为什么不让我删除呢?这时候最快的方式是不是就是要去阅读代码了呀。当然如果写这个代码的人有相关的日志打印,优先排查日志才是最优解。日志没有的情况下再去查看对应的代码业务逻辑。因此我们往往遵循下面的路径:
1. 阅读项目源码
1.1 查看请求接口
快速定位问题,一般都是F12-Network,获取对应的接口,因此我们知道是:http://api.tianji.com/ts/orders/159750267824138305
1.2 梳理请求链路
-
当前端请求的时候,根据前面的分析我们知道会进入网关,而能进入的原因是我们在nginx中的配置项
-
此时我们进入网关服务,其对应的规则如下,因此可以知道接下来进入的微服务:trade-service
-
在trade-service服务中找到对应的代码,这块就完成了代码的定位了
1.3 远程DEBUG
如果部署的微服务不在本地,我们可以采用Idea的远程调试功能(此功能一般仅dev环境才会开启,test-pre-prod会做端口禁用,禁止debug,因为一旦debug就会阻塞用户请求了,这点需要意识到)。
1.3.1 idea里本地配置
-
打开启动链接配置窗口
-
创建Remote JVM Debug链接
-
输入远程debug相关信息。
-
其中jvm命令行参数的作用,是在启动时追加上我们上面的指令:
默认启动指令:
java -jar **.jar
追加后变成:
java -jar **-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005** xxx.jar
1.3.2 启动线上服务允许远程debug
要给线上服务完成这个参数的追加,我们已经给大家提前写好了,只要:
-
在虚拟机里停止tj-trade服务
docker stop tj-trade
, 因为这个服务里没有添加参数,不允许远程debug,所以关掉 -
在Jenkins里运行任务
tj-trade-debug
,会启动一个新的tj-trade服务,这个服务是允许远程debug的,暴露了5005端口
如果对配置过程感兴趣,可以参考以下步骤:
-
访问路径:
-
进来后点击:配置
通过这种方式我们就可以远程debug 5005端口了,但是这些配置一般公司里运维都帮我们做好了,无需我们再操作。
我们点击启动执行
此时去docker中查看,执行指令dps就会发现增加了一个5005的端口暴露
也可使用日志查看命令:dlog -f tj-trade
或者(docker logs -f tj-trade
),验证服务启动成功,启动成功后idea开始debug,有下述指令验证OK
1.3.3 开始远程debug调试
在idea里运行刚刚配置好的Remote JVM Debug,然后就可以在idea里打断点,当程序执行到断点位置时就会暂停。
注意:
虽然我们的断点是打在本地idea里,但是实际调试的是虚拟机里的服务程序
所以你本地idea里的代码 和 虚拟机里的运行的服务代码要相同
2. 缺陷定位修复
2.1 debug调试定位问题
-
我们需要增加一个debug断点
-
用杰克删除订单,注意swagger请求头应该是2且订单应该是jack的订单,如:1564890487310106626
-
发现用户id和订单中用户id都是2,满足判断逻辑所以可以正常删除
-
操作萝丝进行订单的删除,注意swagger请求头应该是2且订单应该是jack的订单,如:1597502678241378305
-
会发现虽然数值都是129,但却是两个对象,这是什么原因呢?
细心的读者一定会发现,我们的判断逻辑是:!=,即不仅判断包装类型Long数值是否相等、还会判断内存地址是否一样(常见的面试题:==和equals区别 还有印象吗),而两者的内存地址在上面的截图中我们可以看到并不相同,这是为什么呢?
享元模式:用于减少创建对象的数量,以减少内存占用和提高性
这是因为Long这个包装类型也采用了Java设计模式中的享元模式,用于减少对象创建的性能开销,查看源码也可以看到了:
2.2 企业分支管理模式
一般我们不建议大家直接在Dev分支直接修改代码。在企业中都有一套分支管理机制,称为GitFlow,大概如图所示:
说明:
-
Master:主分支,用于正式发布的分支。不可直接基于该分支提交。只有经过严格审核测试后的Develop或Hotfix分支可以合并到master
-
Develop:开发分支,从Master创建得来。功能开发的基础分支。
-
Feature:功能分支,从Develop分支创建得来。开发测试完成后会合并到Develop分支。
-
Release:预发布分支,当Develop上积累了一定的功能特性后,从Develop分支创建一个Release分支,做一些发布前的准备工作,不可开发功能。最终合并到Master分支和Develop分支。
-
Hotfix:热修复分支,当Master出现紧急BUG时,基于Master临时创建的分支,修复完成后合并到Develop和Master分支。
在我们项目中,master分支用来给大家提供完整版本代码了,而lesson-init分支作为初始化分支。因此一般不使用master分支,而是把lesson-init当做master分支来用。开发用的dev分支就等于GitFlow中的Develop分支。
2.3 创建bug修复分支并修复bug
-
创建一个名称为
bugfix-order-delete-error
的分支,切到这个分支里边 -
在这个分支里修复bug并提交:tj-trade服务里,找到OrderServiceImpl类里的deleteOrder方法,修改以下内容:
2.4 本地验证bug是否成功修复
-
修改代码后重新启动本地服务:tj-trade,访问swagger地址:http://localhost:8088/doc.html,填入参数后点击发送,去控制台会发现成功删除
这里也会发现,真正工作中的删除都不会删除真实数据,而是采用更新
2.5 合并代码
把bugfix-order-delete-error
分支的代码,合并到dev
分支里边
可以删除bugfix-order-delete-error
分支
git push -f origin dev
强制推送到git仓库里
3. 代码测试验证
一般的测试步骤是这样的:
我们刚刚的代码比较简单,所以就不做本地单元测试了
3.1 本地接口测试
启动本地的trade服务,然后打开Swagger界面: http://localhost:8088/doc.html
由于删除订单时需要对登录用户做校验,因此需要先设置用户id的全局参数。注意:配置好以后要刷新页面
找到删除订单接口,设置id为:1597502678241378305,然后发送请求。如果能删除成功,说明bug已经修复
3.2 组件测试
接下来将我们的服务与网关进行联调,再次测试。
但是:我们本地启动了trade服务,虚拟机里也启动了trade服务。怎么保证通过网关访问的一定是我们本地服务呢?
-
方式1:将虚拟机里的trade服务实例关闭,则trade服务只有本地一个实例可用,自然会访问到本地的trade服务
-
方式2:打开Nacos的服务列表,找到虚拟机里的trade服务实例,设置权重为0,则虚拟机里的服务不会被访问到
2种方式均可,方式1比较简单,不需要多说。我们这里演示一下方式2
然后通过浏览器访问前端页面,登录rose/123456,然后点击删除订单,如果能删除成功,说明组件测试通过。
3.3 部署联调
3.3.1 修改Jenkins监听dev分支代码(已经配置好了)
在上面我们实际都还只是完成了本地的代码测试,代码并没有真正推送到远程仓库,接下来我们做下这部分工作,而要做到这步,前面我们提到的自动化部署就可以帮我们做到。但是在代码提交之前,我们会发现我们目前的开发分支是在:dev,而自动化部署的配置可不是,因此我们需要调整下面两个地方:
-
添加过滤分支,让其只监听dev分支的提交变化:http://jenkins.tianji.com/view/dev/job/tjxt-dev-build/configure
3.3.2 把dev分支推送到gogs里
更改后,我们再去提交代码,就会帮我们自动构建
注意,提交代码是需要填写此时git仓库对应账户信息:tjxt/123321
![]()
3.3.3 等待tjxt-dev-build任务执行完毕
去jenkins观察自动构建任务tjxt-dev-build,可以点击进去查看一下编译信息
3.3.4 重新部署tj-trade服务
等待构建完成后(上图进度条走完),进入dev菜单点击重新部署
-
进入linux操作终端,通过:dlogs -f tj-trade,查看启动日志,直至tj-trade真正启动完成
-
将剩余微服务都启动,验证一下(内存不够的可不操作,建议8核8G运行)
docker start tj-auth tj-gateway tj-course tj-exam tj-search tj-user tj-data tj-media
或者启动所有微服务:
docker start $(docker ps -a | grep tj- | awk '{print $1}')
3.3.5 将tj-trade服务实例权重修改回1
打开Nacos界面,找到trade服务,将虚拟机里的服务实例的权重值修改回1。然后可以把本地的trade服务关闭掉
3.3.6 测试删除订单
注意:本机的服务要关闭掉,再测试部署到服务器上的trade服务是否正确
-
访问地址:http://www.tianji.com/,账户:rose/123456
-
将本地单测删除的订单还原,删除订单验证,如果没有可删除的订单,可以在数据库将订单数据恢复一下也可(deleted字段值修改回0即可)
至此,我们基本完成新人第一周的事情了:环境配置、代码拉取、紧急缺陷修复,学有余力的同学可以再操作一遍今日关键点:代码拉取-创建本地开发分支-提交代码-部署验证。
4. 小结
常见问题
1. maven导依赖失败,爆红
原因
导入依赖失败,maven没有下载成功jar包。或者idea没有成功加载到依赖
解决
第1步:确认maven正确配置
1) 要保证已正确配置maven
确认一下,idea已经正确配置了maven
2) 检查maven是否使用了jdk11
3) 检查是否配置了阿里云的maven仓库镜像
在settings.xml文件里配置阿里云仓库镜像
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
第2步:处理导依赖报错的问题
1) 先清理本地仓库的残留文件然后刷新一下
-
清理maven仓库里的残留,然后在idea里重新clean,然后编译
每次下载依赖失败,在本地仓库里都会有残留文件,后缀名是
.lastUpdated
可以去本地仓库文件夹里,搜索
*.lastUpdated
,把搜索结果的所有文件全选中,删除掉 -
清理maven仓库里的残留,然后
-
在idea里打开pom.xml, 把出错的依赖剪切掉,刷新;重新粘贴回去,再刷新
-
如果还不行,就执行maven的clean,然后compile
-
2) 清理idea缓存并重启idea
-
如果上一步完成之后,依赖问题还没有解决,就清理idea缓存然后重启一下idea
3) 执行命令强制更新依赖
-
如果上一步完成之后,依赖问题还没有解决,就执行命令强制更新依赖
-
在idea里执行命令:
mvn -U idea:idea
,表示要强制更新下载依赖,刷新一下;然后再clean、重新编译
今天内容回顾
-
请介绍一下天机学堂项目
天机学堂是一个基于微服务架构的在线教育B2C项目,核心用户不是K12群体,而是面向成年人的**非学历职业技能培训**平台。整个系统有2个端,分别是:
* 学员用户端:主要是给学员使用的,学员可以注册帐号并登录到系统里,然后就可以搜索浏览课程、购买课程开始学习。为了提升用户学习的积极性,我们系统还提供了一些激励和互动措施,比如:用户在学习过程中可以记录笔记、互动问答,这些互动的活动还可以累计积分,系统会根据积分形成排行榜。另外,系统还会根据排行榜给用户发放一些优惠券,进一步增加用户的粘性。
* 平台管理端:主要是给平台运营人员和老师使用的。老师可以在管理端发布课程并进行回答学员的提问;平台运营人员可以对所有的数据进行管理和统计,特别是增加各种活动,并发放优惠券。项目的技术主要是SpringCloud和MybatisPlus技术体系,还用到了xxl-job分布式定时任务调度,基于Redisson实现的分布式锁,rabbitMQ消息队列;在数据存储上,以MySQL为主,一些非关键但是数据量很大的数据使用MongoDB存储。搜索功能使用ElasticSearch
项目包含14个服务,其中我负责的部分有 学习服务,促销服务,点赞评价服务,考试服务
2. 今天的天机学堂项目环境的搭建
第1步:启动虚拟机
1. 打开虚拟机里.vmx文件
2. 修改VMWare的网卡配置,把VMnet8的ip段设置为192.168.150.0
3. 再启动虚拟机
第2步:本机配置域名映射
1. 打开本机C:\Windows\System32\drivers\etc\hosts
2. 把笔记里的域名映射添加到文件的最后,保存文件
3. 验证基础环境是否成功。可能需要等待一会
打开Nacos:http://nacos.tianji.com
打开RabbitMQ:http://mq.tianji.com
...
第3步:部署启动公用服务
1. 打开Jenkins管理界面 http://jenkins.tianji.com,输入帐号密码并登录
2. 找到tj-auth, tj-user, tj-gateway, ……任务,点击运行按钮
不要启动 xxx-debug和 tjxt-dev-build两个任务
不要启动 我们自己负责的服务:tj-learning,tj-promotion,tj-remark,因为我们还没有开发
3. 打开Nacos,验证一下服务是否启动成功
http://nacos.tianji.com,找到服务列表,刷新几次
需要耐心等待,可能比较慢
最终会有11个服务注册到Nacos里来
第4步:本地服务的拉取和启动
1. 从Gogs里克隆项目代码,并直接检出lesson-init分支
2. 使用idea打开项目代码,然后创建dev分支
3. 修改idea里的启动链接,激活local环境
4. 运行启动链接,在本机启动自己负责的服务
第5步:访问测试。也需要耐心等待
打开浏览器,输入地址
用户端 http://www.tianji.com
管理端 http://manage.tianji.com
3. 天机学堂项目开发的一些规范
项目结构规范:
通常情况下,企业里的微服务项目结构有2种情况:
一个项目,是一个Project,里边有多个Module。适合于中小型的微服务项目
一个项目,是多个project,每个project是一个服务。适合于大型的微服务项目
天机学堂的项目结构:一个project,里边有多个Module。每个Module是一个服务
实体类规范:
DTO:用于封装新增或修改的参数;其它服务调用时传递的数据
Query:用于封装查询条件
PO:用于对应数据库表的实体类
VO:用于封装 给客户端返回的结果
入参校验规范:
常用校验注解:@NotNull,@Min, @Max, @Size,……
在Controller里方法形参上加 @Validated或者@Valid
接口文档规范:
使用Swagger的注解
@Api:加在Controller类上,对类进行说明
@ApiOperation:加在Controller里方法上,对方法进行说明
@ApiParam:加在Controller里方法参数上,对参数进行说明
@ApiModel:加在实体类上,对实体类进行说明
@ApiModelProperty:加在实体类里属性上,对属性进行说明
依赖注入规范:
成员变量直接加@Autowired
成员变量直接加@Resource
final类型的成员变量,并在类上加@RequiredArgsConstructor:通过构造方法注入的
配置文件规范:
不变的参数在项目里配置了,可能会变的参数在配置中心Nacos里托管了
项目相关的参数名,通常是 tj.xxx
异常处理规范:
如果功能里出现问题,就抛出CommonException的子类异常对象
项目里已经提供了异常处理器,统一抓取异常,给客户端返回友好提示
4. 修改bug的流程
如果是紧急bug:需要立即想办法暂时让数据恢复正常,让用户可以继续使用。争取一些时间,然后再找到问题的根源处理
正常处理bug:
复现bug:根据测试人员提供复现的操作步骤,我们要亲自重现这个bug
定位bug:F12的Network网络抓包,梳理请求链路,查看报错信息,debug查看跟踪源码
解决bug:根据找到的问题根源,把bug解决掉
验证bug:
单元测试:最小的可测试单元,比如针对一个方法进行测试
本地接口测试:通过Swagger或者Postman,直接测试这个服务里的功能是否正常
组件测试:微服务要联合其它服务进行测试,比如我们今天是测试tr-trade和网关的测试
部署测试:把修复后的代码提交push,然后部署到服务器上,再进行测试
5. git分支管理规范
企业项目的分支,通常有:
* Master:主分支代码。
不允许随便动。因为Master里的代码必须保证是稳定的可靠的。
一旦线上环境出现问题,Master里随意有可用的代码
* dev:开发分发
项目组里所有开发人员,在进行功能开发时公用的分支
这里的代码都是新功能代码,不稳定,因为可能还没有经过测试
* 功能分支:开发一个功能,就从dev里拉一个新的功能分支
在这个功能分支时编写代码实现功能。每次提交、推送都到这个分支里
不同开发人员有各自的分支,开发过程中互不影响
开发完成以后,要把分支的代码合并到dev分支里大家进公司以后,需要先确定一下,公司的项目分支是怎么管理的。可能按规范,也可能不按规范,也可能有自己的一套规范。我们要跟随公司项目组的要求去做
6. 项目部署的流程:使用Jenkins实现自动化部署
1. 可以创建任务,提前预设好 要部署项目的每一步操作:从git仓库里拉取项目代码、使用maven进行编译打包、制作成docker镜像、创建容器并运行起来
2. 开发人员把编写好的代码提交并push到git仓库(我们天机学堂使用的Gogs);Jenkins会监听dev分支的代码,一旦有新的推送,就会自动拉取项目代码,进行编译
3. 再去Jenkins里运行部署某服务的任务,就可以把项目部署起来了如果面试官问到:你有没有使用Jenkins进行过项目构建和部署?
答:我们公司有个运维,项目的部署、各种环境的搭建处理都是他负责的。我使用过他搭建好的Jenkins做过项目部署,但是没有自己构建过Jenkins的任务。 不过Jenkins只是实现自动化部署,提前预设一些部署的操作,各个环节的操作我很多都了解过,如果需要我创建任务的话,我觉得学习起来问题不大。
7. 开发时代码技巧:享元模式
Long,Integer等等都会把 -128~127数字对象缓存起来。所以
* 如果有两个Integer类型的变量,值都是这个范围内的,两个变量其实指向中是同一个缓存对象,使用==判断为true
* 如果有两个Integer类型的变量,值不在-128~127之间,两个变量指向2个对象,使用==判断为false判断两个变量是否相等时:
基本类型的判断:使用==
引用类型的判断:使用equals方法