服务端开发复习

Contents

1. 建立开发环境

Spring

Spring 是 Java 生态圈的主流编程框架

  1. 轻量级(Lightweight)
  2. 非侵入性(No intrusive)
  3. 容器(Container)
  4. 依赖注入 (Dependency Injection)
  5. 面向切面编程 (Aspect Oriented Programming)
  6. 持久层(JDBC 封装、事务管理、ORM 工具整合)
  7. Web 框架(MVC、其它 WEB 框架整合)
  8. 其他企业服务的封装

Spring 的核心是提供一个容器:

image-20230404135230153

Spring 的模块组成:

https://cdn.hcplantern.cn/img/2023/04/04/20230404-123507.png-default

Spring 的衍生:

  • Spring 是基础、框架
  • Spring Boot 简化开发、自动配置
  • Spring Cloud 基于云的、分布式系统开发,相关技术:容器、微服务、DevOps

Web 开发框架分层 (重要)

一个客户端的请求是如何在服务端被处理的?

image-20230404135822469

  1. 请求到达控制器层,根据请求的 URL 路由到相对应的处理 controller,进行参数解析
  2. 业务层逻辑,数据持久化
  3. 业务层获取数据返回到控制器,控制器返回 JSON 至客户端(@ResponseBody)

SpringBoot DevTools (重要)

是一个开发期工具,只在运行期使用,依赖范围 Runtime

  • 代码变更后应用自动重启,需要借助 IDE 的自动编译

  • 浏览器资源发生变化时自动刷新浏览器

    • 浏览器需要安装扩展 Live Reload
    • IntelliJ 加入 pom 依赖即可
    • 应用会暴露一个端口,便于和浏览器通信
  • 自动禁用模板缓存

  • 内置 H2 控制台(如果使用了 H2 数据库)

源代码仓库管理

  • 版本控制系统,常用工具有:GitLab、SVN、Bitbucket
  • 需要纳入版本控制的有:功能代码、测试代码、测试脚本、构建脚本、部署脚本、配置文件(配置最好和源代码分离)

问题:

暂存区到本地 repo 使用的命令是:commit

image-20230404210255420

2. 依赖注入

Spring DI & AOP

DI:

  • 组件依赖于抽象接口,由抽象接口来注入依赖的实际对象。

AOP:

  • 通过预编译的方式和编译期间动态代理实现程序功能的统一维护
  • 利用 AOP 可以对业务逻辑的各个部分进行隔离,使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率

Bean

Bean 是交给 Spring 管理的那些类(或者对象),Spring 来根据他们之间的依赖实现装配。Spring 负责了创建,装配和管理这些对象的整个⽣命周期,从对象创建到删除都在 Spring 容器中。

Bean 的生命周期:

  1. 实例化:为 Bean 分配内存空间。

  2. 设置属性:将当前类依赖的 Bean 属性,进行注入和装配。

  3. 初始化:

    1. 执行各种通知。

    2. 执行初始化的前置方法。

    3. 执行初始化方法。

    4. 执行初始化的后置方法。

  4. 使用 Bean:在程序中使用 Bean 对象。

  5. 销毁 Bean:将 Bean 对象进行销毁操作。

Spring 容器

Spring 容器有两类:

  • Bean Factory(简单,提供基本的依赖注⼊⽀持)
  • Application Context(基于 bean Factory 构建,提供应⽤框架级服务,例如从属性⽂件中解析⽂本信息以及发布应⽤事件给感兴趣的事件监听者)

Bean 的命名

⽐如类叫 MyClass , bean 的默认名是类(或者 @bean 写在类⽅法上的话就是⽅法名⾸字⺟⼩写)的第⼀个字⺟改成⼩写(myClass),可以⽤注解 @Named("newName") 指定命名同时这个注解也具有 @Component 的功能,可以不⽤再写 @Component,不过 Spring 不允许重名,命名的时候需要注意这点。

Bean 配置方案

例子类图:

image-20230404145834546

自动化配置 (组件扫描、自动装配)

组件扫描:Config 类需要注解 @ComponentScan

  • @ComponentScan(basePackageClasses={xxx.class} 会扫描类所在的包以及递归扫描这个包的⼦包
  • 或指定 basePackages="packageName",指定多个类或者 base Package,⽐如:@ComponentScan(basePackages= {"Package1","package2"}) 告诉 Spring 到哪些 package⾥⾯来找实现依赖的类
  • 不指定 base Packages 默认为当前包 @ComponentScan("currentPackageName")
1
2
3
4
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

标记组件:@Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Component
public class SgtPeppers implements CompactDisc {

  private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
  private String artist = "The Beatles";
  
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
  
}

使用时自动装载:@Autowired 标记在在构造函数上,或者用在属性 Setter 方法上,也可以标记在(私有)属性上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Component
public class CDPlayer implements MediaPlayer {
    private CompactDisc cd;
    
    @Autowired
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }
    public void play() {
        cd.play();
    }
}

主类:

1
2
3
4
5
6
7
public class MyAnnotationApp {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(CDPlayerConfig.class); // 从 Context 中获取 Bean
        MediaPlayer player = ctx.getBean(MediaPlayer.class);
        player.play();
    }
}

Java Config

如果遇到第三⽅代码没办法在源代码中嵌⼊注解时可以使用这种方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class CDPlayerConfig {
    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer(CompactDisc cd) {
        return new CDPlayer(cd);
    }
}

指定名字:@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,可以应用于配置类上,也可以应用于方法上

配置类:

1
2
@COnfiguration
@Profile("dev")

方法:

1
2
@Bean
@Profile("prod")

如何激活:设置 spring.profiles.active 这个配置

条件化 bean

@Conditional 注解:如果条件结果为 true,则 Spring 容器则会创建这个 bean,否则该 bean 会被忽略。 @Conditional 中给定一个 Class 的类是实现 Condition 接口,该接口有一个 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * 根据条件创建bean
*/
@Bean
@Conditional(ConditionExistCondition.class)
public ConditionBean conditionBean() {
    return new ConditionBean();
}


public class ConditionExistCondition implements Condition {
    // 检查条件
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.containsProperty("condition");
    }
}

自动装配的歧义性

仅有一个 bean 匹配所需的结果时,自动装配才是有效的。如果不仅有一个 bean 能够匹配结果的话,这种歧义性会阻碍 Spring 自动装配属性、构造器参数或方法参数。

使用 @Primary 设置首选 Bean

定义限定符:

@Component 或 @Bean

@Qualifier ("…") 自定义限定符

使用限定符:

1
2
3
4
5
@Autowired
@Qualifier("iceCream") // bean 名称或自定义限定符,默认 Bean 名是限定符
public void setDessert(Dessert dessert) {
  this.dessert = dessert;
}

总结

Autowired:

  • 构造器
  • Setter
  • 属性甚至私有属性

3. 面向切面编程

AOP 关注哪些东西:⽇志,安全,事物(数据库),缓存

术语(重要)

https://cdn.hcplantern.cn/img/2023/04/06/20230406-113354.webp-default

  • 通知(Advice):切面做什么以及何时做(Before,After,Around,AfterReturning,AfterThrowing)
  • 切点(Pointcut):何处,切点的定义会匹配通知所要织入的一个或多个连接点
  • 切面(Aspect):Advice 和 Pointcut 的结合
  • 连接点(Join point):哪些地方可以作为切点:方法、字段修改、构造方法。Spring 只支持方法
  • 引入(introduction):引入新的行为和状态
  • 织入(Weaving):切面应用到目标对象的过程

织入时机

  • 编译期,需要特殊的编译器
  • 类加载期,需要类加载器的处理
  • 运行期: Spring 所采纳的方式,使用代理对象、只支持方法级别的连接点

image-20230406115556586

使用

配置类加入:

1
2
@Configuration
@EnableAspectJAutoProxy //开启AspectJ的自动代理机制

定义切面(切点+通知)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
public class TrackCounter {
    // key: track number, value: number of times the track has been played
    private Map<Integer, Integer> trackCounts = new HashMap<>();

    @Pointcut(
            "execution(* soundsystem.CompactDisc.playTrack( int )) " +
                    "&& args(trackNumber)" // get the track number from the method call
                    + "&& within(soundsystem.*)" // only apply to methods in the soundsystem package
                    + "&& bean(sgtPeppers)" // only apply to the sgtPeppers bean
    )
    public void trackPlayed(int trackNumber) {
    }

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber) {
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber) {
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}

注意:@Aspect 该注解没有实例化效果,需要手动在 Config 类进行实例化,或者加上 @Component 注解,之后才能正常使用。

切点指示器(重要)

切点表达式:

1
2
3
4
5
6
    @Pointcut(
            "execution(* soundsystem.CompactDisc.playTrack( int )) " +
                    "&& args(trackNumber)" // get the track number from the method call
                    + "&& within(soundsystem.*)" // only apply to methods in the soundsystem package
                    + "&& bean(sgtPeppers)" // only apply to the sgtPeppers bean
    )

限定加了某个注解的 Bean:

1
2
3
4
5
@Around("@annotation(innerAuth)") //限定注解
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) { ... }

@InnerAuth
public R<Boolean> register(@RequestBody SysUser sysUser) { ... }

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

只需要定义接口,不需要实现。

1
2
3
4
public interface IngredientRepository 
         extends CrudRepository<Ingredient, String> {

}

CrudRepository 有一些已经实现好的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save (S var1);
    <S extends T> Iterable<S> saveAll(Iterable<S> var1);
    Optional<T> findById(ID var1);
    boolean existsById(ID var1);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> var1);
    long count();
    void deleteById(ID var1);
    void delete(T var1);
    void deleteAllById(Iterable<? extends ID> var1);
    void deleteAll(Iterable<? extends T> var1);
    void deleteAll();
}

为领域对象添加注解(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 语句来实现

1
2
@Query(Order o where o.deliveryCity = 'Seattle'”)
List<TacoOrder> readOrdersDeliveredInSeattle( );

JPA、Hibernate、Spring Data JPA 三者之间的关系

JPA 是一个规范;Spring Data JPA 是一种实现

Hibernate 是 Spring Data JPA 的一个组件

image-20230404214452947

总结

上述三种方式的相同点和不同点

特点 JdbcTemplate Spring Data JDBC JDA
实现具体类 需要 不需要,只要写明继承关系 不需要,只要写明继承关系
定义实体类和数据库表的映射关系 不需要 可选 需要
手动维护表之间的关系 需要 不需要 不需要
建表 SQL 脚本 schema.sql 需要 需要 不需要,可以自动推断
  • 领域对象和领域类注解:2 可选,3 必须要
  • 3 使用 DSL 定义接口查询方法而不需要自己实现,JPA 自动实现
  • 3 需要使用 Entity 注解

6. Spring Security

用户信息存储

  • 内存用户存储
  • JDBC 用户存储
  • LDAP 用户存储

总结

image-20230404214939963

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 使用

初识Kubernetes(K8s):各种资源对象的理解和定义_k8s

初识Kubernetes(K8s):各种资源对象的理解和定义_pod_06

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 的端口快速映射到本机端口(调试用)

例如:

1
2
3
kubectl port-forward pod/myspittr 8081:8080
# 访问:http://localhost:8081/spittr/
kubectl port-forward service/demo 8081:80

如何访问 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

image-20230406213024355

自动伸缩

指定一个 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 类型

程序中获取配置信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Component
@RefreshScope // 获取最新配置
public class ServiceConfig{

  @Value("${example.property}") // 指定配置的名字
  private String exampleProperty;

  public String getExampleProperty(){
    return exampleProperty;
  }
}

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 家族:

image-20230404194601628

Spring Cloud Alibaba 是 Spring Cloud 的子项目,符合 Spring Cloud 的标准,致力于提供微服务开发的一站式解决方案。

课上讲过的记住:

  • Nacos:取代 Eureka,服务注册与开发
  • Sentinel:流控与熔断

image-20230404194426313

使用 nacos 四个步骤

  1. 四个相关依赖
  2. bootstrap 指定 nacos 的域名和端口号
  3. Application 类增加注解:
    1. @EnablediscoveryClient 如果使用 discovery client
    2. @EnableFeignClients 如果使用 feign 调用另外的服务
  4. Feign Client 代码定义接口,见下文

调用服务的三种方式

  • Spring DiscoveryClient (不建议使用)
  • 使用支持 LoadBalanced 的 RestTemplate:客户端的负载均衡,需要使用某种策略挑选服务端
  • 使用 OpenFeign(@FeignClient
    • OpenFeign 是一款声明式、模板化的 HTTP 客户端, Feign 可以帮助我们更快捷、优雅地调用 HTTP API

如何调用 OpenFeign 服务

  • 启动类加注解:@EnableFeignClients

  • 引入接口,传入服务名,定义方法,加上注解,让 nacos 找

1
2
3
4
5
6
7
8
@FeignClient("organizationservice") // 服务名,用于询问nacos
public interface OrganizationFeignClient {
    @RequestMapping(
            method= RequestMethod.GET,
            value="/v1/organizations/{organizationId}",
            consumes="application/json") // 定义接口
    Organization getOrganization(@PathVariable("organizationId") String organizationId);
}
  • 自动装配,不需要手动实例化
1
2
    @Autowired
    OrganizationFeignClient organizationFeignClient;
  • 使用 Feign 获取服务并调用
1
organization = organizationFeignClient.getOrganization(organizationId);

健康检查(重要)

  1. 临时实例的客户端主动上报机制,Nacos 客户端会维护一个定时任务,每隔 5 秒发送一次心跳请求,以确保自己处于活跃状态。Nacos 服务端在 15 秒内如果没收到客户端的心跳请求,会将该实例设置为不健康,在 30 秒内没收到心跳,会将这个临时实例摘除。
  2. 永久实例的服务端反向探测机制,永久实例支持 3 种探测协议,TCP、HTTP 和 MySQL,默认探测协议为 TCP,也就是通过不断 ping 的方式来判断实例是否健康。

问题

licensingservice 如何访问 origanizationservice,是否通过 Nacos 进行转发的?

image-20230404195745406

答:不是,客户端首先获取服务端的信息通过某种策略进行挑选服务端,最后直接通过 ip 地址与服务端交互,不需要通过 Nacos 的转发。

思考题:K8s nacos 服务的相同与不同点,见上文

13. 流控与熔断

image-20230406193536683

流控:

客户端向服务端的请求数量过多,需要控制。

可以在 ingress、网关、客户端调用服务端这几个地方进行流量控制

熔断:

容错:客户端调用服务端可能会遇到错误,需要使用缺省值进行处理,或者换一个服务端。

熔断:客户端调用服务端,如果服务端高负荷运转、响应过慢、不再健康,直接返回错误,不再正常调用服务并等待。服务端拒绝所有请求。

Sentinel 组成(重要)

  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

image-20230404201447687

控制台不负责持久化规则,若服务重启则在控制台创建的规则丢失。

使用步骤

  • 定义资源
  • 定义规则
  • 查看效果

定义资源

三种方法:

  • 使用代码定义
  • 使用注解定义
  • 自动定义

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

image-20230404202046880

注意:外部文件不可以用来定义资源。资源的定义必须在代码中。

定义规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

image-20230404203612977

规则的种类(前两个重点讲):

  • 流量控制规则
  • 熔断降级规则
  • 系统保护规则:系统负荷过高时需要采取措施
  • 来源访问控制规则:根据不同的请求来源采取不同的措施
  • 热点参数规则:对于同样的请求依据某一携带的参数进行限制。

名字记住

查看效果

两种手段查看:

  • 日志
  • Dashboard

QPS 一般指每秒查询率。每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

例子

例子的地址

流量控制

  • QPS 流量控制:当 QPS 超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出 FlowException
  • 并发线程数流量控制:线程数限流用于保护业务线程数不被耗尽

熔断

熔断降级:

系统中的许多微服务;当其中某个微服务出现高负荷的状态时,需要进行熔断以免拖垮整个系统

image-20230406203436281

熔断策略:

慢调用比例 (SLOW_REQUEST_RATIO)

  • 设置 RT(最大的响应时间),用于判断慢调用
  • 触发熔断的条件:$请求数目 > 最小请求数目$ 且 $慢调用比例 > 阈值$

image-20230406203816802

异常比例 (ERROR_RATIO)

  • 异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

image-20230406204105435

异常数 (ERROR_COUNT)

和上述类似

image-20230406204115253

规则文件

file、nacos、 zk、 apollo、redis

使用 Spring Cloud Alibaba Sentinel,对于路径不需要 @SentinelResource 也会自动创建

1
2
3
4
5
	@GetMapping("/hello")
	// @SentinelResource("resource") // 没有这一行也会创建一个 "hello" 的资源
	public String hello() {
		return "Hello";
	}

注意:资源的定义不能使用文件的形式,必须以代码的形式去定义。

0%