服务端开发复习
1. 建立开发环境
Spring
Spring 是 Java 生态圈的主流编程框架
- 轻量级(Lightweight)
- 非侵入性(No intrusive)
- 容器(Container)
- 依赖注入 (Dependency Injection)
- 面向切面编程 (Aspect Oriented Programming)
- 持久层(JDBC 封装、事务管理、ORM 工具整合)
- Web 框架(MVC、其它 WEB 框架整合)
- 其他企业服务的封装
Spring 的核心是提供一个容器:
Spring 的模块组成:
Spring 的衍生:
- Spring 是基础、框架
- Spring Boot 简化开发、自动配置
- Spring Cloud 基于云的、分布式系统开发,相关技术:容器、微服务、DevOps
Web 开发框架分层 (重要)
一个客户端的请求是如何在服务端被处理的?
- 请求到达控制器层,根据请求的 URL 路由到相对应的处理 controller,进行参数解析
- 业务层逻辑,数据持久化
- 业务层获取数据返回到控制器,控制器返回 JSON 至客户端(
@ResponseBody
)
SpringBoot DevTools (重要)
是一个开发期工具,只在运行期使用,依赖范围 Runtime
-
代码变更后应用自动重启,需要借助 IDE 的自动编译
-
浏览器资源发生变化时自动刷新浏览器
- 浏览器需要安装扩展 Live Reload
- IntelliJ 加入 pom 依赖即可
- 应用会暴露一个端口,便于和浏览器通信
-
自动禁用模板缓存
-
内置 H2 控制台(如果使用了 H2 数据库)
源代码仓库管理
- 版本控制系统,常用工具有:GitLab、SVN、Bitbucket
- 需要纳入版本控制的有:功能代码、测试代码、测试脚本、构建脚本、部署脚本、配置文件(配置最好和源代码分离)
问题:
暂存区到本地 repo 使用的命令是:commit
2. 依赖注入
Spring DI & AOP
DI:
- 组件依赖于抽象接口,由抽象接口来注入依赖的实际对象。
AOP:
- 通过预编译的方式和编译期间动态代理实现程序功能的统一维护
- 利用 AOP 可以对业务逻辑的各个部分进行隔离,使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率
Bean
Bean 是交给 Spring 管理的那些类(或者对象),Spring 来根据他们之间的依赖实现装配。Spring 负责了创建,装配和管理这些对象的整个⽣命周期,从对象创建到删除都在 Spring 容器中。
Bean 的生命周期:
-
实例化:为 Bean 分配内存空间。
-
设置属性:将当前类依赖的 Bean 属性,进行注入和装配。
-
初始化:
-
执行各种通知。
-
执行初始化的前置方法。
-
执行初始化方法。
-
执行初始化的后置方法。
-
-
使用 Bean:在程序中使用 Bean 对象。
-
销毁 Bean:将 Bean 对象进行销毁操作。
Spring 容器
Spring 容器有两类:
- Bean Factory(简单,提供基本的依赖注⼊⽀持)
- Application Context(基于 bean Factory 构建,提供应⽤框架级服务,例如从属性⽂件中解析⽂本信息以及发布应⽤事件给感兴趣的事件监听者)
Bean 的命名
⽐如类叫 MyClass
, bean 的默认名是类(或者 @bean
写在类⽅法上的话就是⽅法名⾸字⺟⼩写)的第⼀个字⺟改成⼩写(myClass
),可以⽤注解 @Named("newName")
指定命名同时这个注解也具有 @Component
的功能,可以不⽤再写 @Component
,不过 Spring 不允许重名,命名的时候需要注意这点。
Bean 配置方案
例子类图:
自动化配置 (组件扫描、自动装配)
组件扫描:Config 类需要注解 @ComponentScan
@ComponentScan(basePackageClasses={xxx.class}
会扫描类所在的包以及递归扫描这个包的⼦包- 或指定
basePackages="packageName"
,指定多个类或者 base Package,⽐如:@ComponentScan(basePackages= {"Package1","package2"})
告诉 Spring 到哪些 package⾥⾯来找实现依赖的类 - 不指定 base Packages 默认为当前包
@ComponentScan("currentPackageName")
|
|
标记组件:@Component
|
|
使用时自动装载:@Autowired
标记在在构造函数上,或者用在属性 Setter 方法上,也可以标记在(私有)属性上。
|
|
主类:
|
|
Java Config
如果遇到第三⽅代码没办法在源代码中嵌⼊注解时可以使用这种方法
|
|
指定名字:@Bean(name=“xxx”),如果不指定会以类名或者⽅法名来命名
Scope: @Scope (“xxx”) 可以指定 Bean 的作用域:默认 Singleton,可以指定 prototype, session, request
与业务逻辑和领域代码分开:**Java Config **是配置代码,不应该包含任何业务代码和侵入到业务代码中,通常会将 Java Config 放到单独的包中,使其与业务逻辑代码分开。
XML 配置
{Class name}-context. xml: {Class name} 指定了在 java 代码中想要绑定的类名,Spring 将此配置⽂件对应到指定的类。
混合配置
混合配置时,需要注意的是:在自动装配时,它并不在意装配的 bean
来自哪里。自动装配的时候,会考虑 Spring 容器中所有的 bean
,不管它是在 JavaConfig 或 XML 中声明还是通过组件扫描获取的。
Bean 作用域
默认 Singleton
- Singleton
- Prototype
- Session
- Request
spring profile
Spring profile 是在运行时阶段确定不同环境下创建不同类型的 bean
。
Java Config 中,使用 @Profile
注解指定 bean 属于哪一个 profile,可以应用于配置类上,也可以应用于方法上
配置类:
|
|
方法:
|
|
如何激活:设置 spring.profiles.active
这个配置
条件化 bean
@Conditional
注解:如果条件结果为 true,则 Spring 容器则会创建这个 bean,否则该 bean 会被忽略。
@Conditional 中给定一个 Class 的类是实现 Condition 接口,该接口有一个 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法。
|
|
自动装配的歧义性
仅有一个 bean 匹配所需的结果时,自动装配才是有效的。如果不仅有一个 bean 能够匹配结果的话,这种歧义性会阻碍 Spring 自动装配属性、构造器参数或方法参数。
使用 @Primary 设置首选 Bean
定义限定符:
@Component 或 @Bean
@Qualifier ("…") 自定义限定符
使用限定符:
|
|
总结
Autowired:
- 构造器
- Setter
- 属性甚至私有属性
3. 面向切面编程
AOP 关注哪些东西:⽇志,安全,事物(数据库),缓存
术语(重要)
- 通知(Advice):切面做什么以及何时做(Before,After,Around,AfterReturning,AfterThrowing)
- 切点(Pointcut):何处,切点的定义会匹配通知所要织入的一个或多个连接点
- 切面(Aspect):Advice 和 Pointcut 的结合
- 连接点(Join point):哪些地方可以作为切点:方法、字段修改、构造方法。Spring 只支持方法
- 引入(introduction):引入新的行为和状态
- 织入(Weaving):切面应用到目标对象的过程
织入时机
- 编译期,需要特殊的编译器
- 类加载期,需要类加载器的处理
- 运行期: Spring 所采纳的方式,使用代理对象、只支持方法级别的连接点
使用
配置类加入:
|
|
定义切面(切点+通知)
|
|
注意:@Aspect 该注解没有实例化效果,需要手动在 Config 类进行实例化,或者加上 @Component 注解,之后才能正常使用。
切点指示器(重要)
切点表达式:
|
|
限定加了某个注解的 Bean:
|
|
4. Web MVC
Lombok (重要)
只在编译期起作用,需要 IntelliJ 安装插件
简化 Java 代码
请求映射注解
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@RequestMapping
- 既可以加在方法上也可以加在类上,其他加在方法上
客户端参数分类
请求:
- 路径参数
@PathVariable
- 请求参数
@RequestParam
- 表单参数(前后端不分离)对应 model,可以使用
@Valid
校验 - JSON 请求体
@RequestBody
返回结果:
@ResponseBody
- 将结果转换成 JSON 格式发送给客户端
@RestController
- 相当于 @ResponseBody
+ @Controller
5. Spring Data JDBC / JPA
JDBC Template
避免使用使用原始的 JDBC 访问数据库:
- RawJdbcIngredientRepository
- 样板式代码
- SQLException
Spring Data JDBC
只需要定义接口,不需要实现。
|
|
CrudRepository
有一些已经实现好的方法:
|
|
为领域对象添加注解(Spring Data JDBC 是可选的,因为已经提供了 schema.sql
):
@Table("xxx")
为表指定表名,类名和表名不一样的时候需要指定
@Column("xxx")
为表的字段指定字段名
@Id
为表指定主键字段
Spring Data JPA
不需要提供 schema.sql
建表语句了
需要给对象添加 @Entity
注解
自定义查询方法
不需要实现,只要根据领域特定语言(DSL)和 Spring Data 的命名约定对方法进行命名,JPA 就可以自动实现。
动词 + 主题 + 断言
- 动词(需要记住):get, read, find, count
- 例如:
List findByDeliveryZip( String deliveryZip );
声明自定义查询
JPQL 是一种面向对象的查询语言
不符合方法命名约定时,或者命名太长时,可以自己写 sql 语句来实现
|
|
JPA、Hibernate、Spring Data JPA 三者之间的关系
JPA 是一个规范;Spring Data JPA 是一种实现
Hibernate 是 Spring Data JPA 的一个组件
总结
上述三种方式的相同点和不同点
特点 | JdbcTemplate | Spring Data JDBC | JDA |
---|---|---|---|
实现具体类 | 需要 | 不需要,只要写明继承关系 | 不需要,只要写明继承关系 |
定义实体类和数据库表的映射关系 | 不需要 | 可选 | 需要 |
手动维护表之间的关系 | 需要 | 不需要 | 不需要 |
建表 SQL 脚本 schema.sql |
需要 | 需要 | 不需要,可以自动推断 |
- 领域对象和领域类注解:2 可选,3 必须要
- 3 使用 DSL 定义接口查询方法而不需要自己实现,JPA 自动实现
- 3 需要使用 Entity 注解
6. Spring Security
用户信息存储
- 内存用户存储
- JDBC 用户存储
- LDAP 用户存储
总结
7. Docker 使用
容器是在 Linux 内核实现的轻量级资源隔离机制,本质上是进程级的资源隔离
docker run 命令
- docker run hello-world
- -d: 后台运行容器,并返回容器 ID
- -i: 以交互模式运行容器,通常与 -t 同时使用
- -t: 为容器重新分配一个伪输入终端,通常与 - i 同时使用
- -p: 指定(发布)端口映射,格式为:主机 (宿主 ) 端口 : 容器端口
- -P: 随机端口映射,容器内部端口随机映射到主机的高端口
- –name=“nginx-lb”: 为容器指定一个名称
- -e username=“ritchie”: 设置环境变量
- –env-file=c:/temp1/t1. txt: 从指定文件读入环境变量
- –expose=2000-2002: 开放(暴露)一个端口或一组端口;
- –link my-mysql: taozs : 添加链接到另一个容器
- -v c:/temp1:/data: 绑定一个卷 (volume)
- –rm 退出时自动删除容器
docker 其他命令
- image:管理镜像
- volumn:管理数据卷
- network:管理网络
- container:管理容器
- stop:停止容器,还可以重新启动
8. 容器镜像构建与编排
Dockerfile
- Run
- ADD & COPY:ADD 自动解压,COPY 只是拷贝
- CMD 与 ENTRYPOINT:不可替代。
编写 Dockerfile 的最佳实践(重要):
- .dockerignore:构建镜像时不需要的文件排除在外
- 容器只运行单个应用:进程隔离
- 将多个 RUN 指令合并成一个:减少镜像分层
- 基础镜像的标签不要用 latest
- 每个 RUN 指令后删除多余文件
- 设置合适的基础镜像(alpine)
- 设置 WORKDIR 和 CMD
docker-compose
- 服务 (service):一个应用的容器(可能会有多个容器),实际上可以包括若干运行相同镜像的容器实例
- 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose. yml 文件中定义
常用命令:
- docker-compose ps: 当前目录下 yaml 文件所部署的容器,不是所有的 docker 容器
- docker-compose logs -f [… services] : 打印服务的日志
YAML 文件(重要)
-
使用缩进表示层级关系,不允许使用 Tab 键,只允许使用空格
-
#
表示注释,从这个字符一直到行尾,都会被解析器忽略。 -
对象,键值对,使用冒号结构表示
-
animal: pets
-
hash: { name: Steve, foo: bar }
-
-
数组, 一组连词线开头的行,构成一个数组
-
1 2 3
- Cat - Dog - Goldfish
行内表示法:
animal: [Cat, Dog]
-
9. Kubernetes 使用
k8s 中的资源(重要)
- Ingress:Kubernetes 中的一个资源对象,用来管理集群外部访问集群内部服务的方式
- Deployment: 最常用的 k8S 工作负载控制器(Workload Controllers), 是 k8s 的一个抽象概念,用于更高级层次对象,部署和管理 Pod
- Pod:Kubernetes 调度的最小单元,可以包含多个容器
- Service:Label 相同的 Pod 共同组成一个 Service
- …
Pod
Pod 是 Kubernetes 调度的最小单元。一个 Pod 可以包含一个或多个容器,因此它可以被看作是内部容器的逻辑宿主机。Pod 的设计理念是为了支持多个容器在一个 Pod 中共享网络和文件系统。因此处于一个 Pod 中的多个容器共享以下资源:
- PID 命名空间:Pod 中不同的应用程序可以看到其他应用程序的进程 ID。
- network 命名空间:Pod 中多个容器处于同一个网络命名空间,因此能够访问的 IP 和端口范围都是相同的。也可以通过 localhost 相互访问。
- IPC 命名空间:Pod 中的多个容器共享 Inner-process Communication 命名空间,因此可以通过 SystemV IPC 或 POSIX 进行进程间通信。
- UTS 命名空间:Pod 中的多个容器共享同一个主机名。
- Volumes:Pod 中各个容器可以共享在 Pod 中定义分存储卷(Volume)。
port-forward 作用:将 pod 或 service 的端口快速映射到本机端口(调试用)
例如:
|
|
如何访问 k8s 中的服务进行快速验证
三种方式:
- port-forward
- ingress
kubectl run -it --rm=true mycurl --image=curlimages/curl:latest --restart=Never --command --sh
:起一个 curl 镜像,使用 curl 进行验证
访问 k8s 服务的方式
service 的集群 ip 不经常改变,但是 pod 的 ip 会经常改变(容易消亡)
- Service 名字 + 端口
- Service ip + 端口
- Pod ip + 端口
- 注意:不可以使用 Pod 的名字进行访问
Service Pod Label 关系
一个服务就是多个 label 相同的 pod
自动伸缩
指定一个 deployment 的伸缩区间
kubectl autoscale deployment spittr --min=10 --max=15 --cpu-percent=80
思考题:K8s nacos 服务的相同与不同点
共同点:
- 屏蔽细节,客户端只需要知道服务名进行访问,不需要知道背后的细节(k8s 多个实例)
- 都提供了服务注册与发现的功能
不同点:
- k8s 服务是 Pod 层级的,nacos 服务是服务层级的,和开发框架相关(比如使用 java 开发需要在 pom 中指定相关依赖)
- k8s 的服务是基于部署在 k8s 中的容器的;nacos 所支持的服务类型有:容器、虚拟机和其他类型的应用程序,除了 k8s 服务以外还包括:gRPC & Dubbo RPC Service;Spring Cloud RESTful Service
- k8s 的服务由 k8s 创建并管理,而 nacos 的服务需要主动向 nacos 注册
- k8s 更注重容器编排,提供负载均衡的功能;Nacos 更专注于服务发现和服务的配置管理。
- …
10. REST 服务、微服务开发与部署
单体应用程序
- 数据库的表对所有模块可见
- 一个人的修改整个应用都要重新构建、测试、部署
- 整体复制分布式部署,不能拆分按需部署
微服务架构模式的特征
- 应用程序分解为具有明确定义了职责范围的细粒度组件
- 完全独立部署,独立测试,并可复用
- 使用轻量级通信协议,HTTP 和 JSON,松耦合
- 服务实现可使用多种编程语言和技术
- 将大型团队划分成多个小型开发团队,每个团队只负责他们各自的服务
状态码的含义
- 1xx
- 2xx
- 3xx
- 4xx
- 5xx
actuator
增加这个依赖之后可以增加一些关于健康检查的端点,比如:http/localhost:8080/actuator/health
运维实践
- 都在源代码库中(代码文件、测试代码、测试脚本、部署脚本、构建脚本)(除了配置文件)
- 指定 JAR 依赖的版本号
- 配置与源代码分开放
- 已构建的服务是不可变的,不能再被修改
- 微服务应该是无状态的
- 并发,通过启动更多的微服务实例横向扩展,多线程是纵向扩展
11. 微服务的数据配置与 NACOS 使用
bootstrap.yml
提供 nacos 的访问地址
dataId 的完整格式(重要)
${prefix}-${spring.profiles.active}.${file-extension}
- prefix 默认为
spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置 spring.profiles.active
即为当前环境对应的 profile- file-exetension 为配置内容的数据格式,可以通过配置项
spring.cloud.nacos.config.file-extension
来配置。目前只支持 properties 和 yaml 类型
程序中获取配置信息
|
|
curl 命令
-i 打印响应的标头
curl -i http://spittr:8080/spittr/spittles
要求客户端重定向
-L 让 HTTP 请求跟随服务器的重定向。curl 默认不跟随重定向。
curl -L http://spittr:8080/spittr/spittles
-v 参数输出通信的整个过程,用于调试
curl -v http://tzs919:123456@spittr:8080/spittr/spittles
curl --trace - http://tzs919:123456@spittr:8080/spittr/spittles
输出二进制信息
设置认证的用户名密码
curl http://tzs919:123456@spittr:8080/spittr/spittles
curl -u tzs919:123456 http://spittr:8080/spittr/spittles
等价
12. 基于 NACOS 的服务注册与发现
服务发现的好处:
- 快速水平伸缩(增加或减少实例数),而不是垂直伸缩(增加单个实例的资源)。不影响客户端。
- 提高应用程序的弹性:容错性更强
Spring Cloud 家族:
Spring Cloud Alibaba 是 Spring Cloud 的子项目,符合 Spring Cloud 的标准,致力于提供微服务开发的一站式解决方案。
课上讲过的记住:
- Nacos:取代 Eureka,服务注册与开发
- Sentinel:流控与熔断
使用 nacos 四个步骤
- 四个相关依赖
- bootstrap 指定 nacos 的域名和端口号
Application
类增加注解:@EnablediscoveryClient
如果使用 discovery client@EnableFeignClients
如果使用 feign 调用另外的服务
- Feign Client 代码定义接口,见下文
调用服务的三种方式
- Spring DiscoveryClient (不建议使用)
- 使用支持 LoadBalanced 的 RestTemplate:客户端的负载均衡,需要使用某种策略挑选服务端
- 使用 OpenFeign(
@FeignClient
)- OpenFeign 是一款声明式、模板化的 HTTP 客户端, Feign 可以帮助我们更快捷、优雅地调用 HTTP API
如何调用 OpenFeign 服务
-
启动类加注解:
@EnableFeignClients
-
引入接口,传入服务名,定义方法,加上注解,让 nacos 找
|
|
- 自动装配,不需要手动实例化
|
|
- 使用 Feign 获取服务并调用
|
|
健康检查(重要)
- 临时实例的客户端主动上报机制,Nacos 客户端会维护一个定时任务,每隔 5 秒发送一次心跳请求,以确保自己处于活跃状态。Nacos 服务端在 15 秒内如果没收到客户端的心跳请求,会将该实例设置为不健康,在 30 秒内没收到心跳,会将这个临时实例摘除。
- 永久实例的服务端反向探测机制,永久实例支持 3 种探测协议,TCP、HTTP 和 MySQL,默认探测协议为 TCP,也就是通过不断 ping 的方式来判断实例是否健康。
问题
licensingservice 如何访问 origanizationservice,是否通过 Nacos 进行转发的?
答:不是,客户端首先获取服务端的信息通过某种策略进行挑选服务端,最后直接通过 ip 地址与服务端交互,不需要通过 Nacos 的转发。
思考题:K8s nacos 服务的相同与不同点,见上文
13. 流控与熔断
流控:
客户端向服务端的请求数量过多,需要控制。
可以在 ingress、网关、客户端调用服务端这几个地方进行流量控制
熔断:
容错:客户端调用服务端可能会遇到错误,需要使用缺省值进行处理,或者换一个服务端。
熔断:客户端调用服务端,如果服务端高负荷运转、响应过慢、不再健康,直接返回错误,不再正常调用服务并等待。服务端拒绝所有请求。
Sentinel 组成(重要)
- 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
控制台不负责持久化规则,若服务重启则在控制台创建的规则丢失。
使用步骤
- 定义资源
- 定义规则
- 查看效果
定义资源
三种方法:
- 使用代码定义
- 使用注解定义
- 自动定义
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。
注意:外部文件不可以用来定义资源。资源的定义必须在代码中。
定义规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
规则的种类(前两个重点讲):
- 流量控制规则
- 熔断降级规则
- 系统保护规则:系统负荷过高时需要采取措施
- 来源访问控制规则:根据不同的请求来源采取不同的措施
- 热点参数规则:对于同样的请求依据某一携带的参数进行限制。
名字记住
查看效果
两种手段查看:
- 日志
- Dashboard
QPS 一般指每秒查询率。每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
例子
流量控制
- QPS 流量控制:当 QPS 超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出
FlowException
- 并发线程数流量控制:线程数限流用于保护业务线程数不被耗尽
熔断
熔断降级:
系统中的许多微服务;当其中某个微服务出现高负荷的状态时,需要进行熔断以免拖垮整个系统
熔断策略:
慢调用比例 (SLOW_REQUEST_RATIO)
- 设置 RT(最大的响应时间),用于判断慢调用
- 触发熔断的条件:$请求数目 > 最小请求数目$ 且 $慢调用比例 > 阈值$
异常比例 (ERROR_RATIO)
- 异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (ERROR_COUNT)
和上述类似
规则文件
file、nacos、 zk、 apollo、redis
使用 Spring Cloud Alibaba Sentinel,对于路径不需要 @SentinelResource
也会自动创建
|
|
注意:资源的定义不能使用文件的形式,必须以代码的形式去定义。