今天, 我将用 Spring Boot 构建一个非常简单的微服务系统. 让我们先来看看什么是微服务.
什么是微服务?
微服务是一种软件架构方法, 在这种方法中, 应用被划分为小型, 独立和自足的服务, 这些服务之间通过 API 进行通信. 这样可以加快开发, 部署和扩展速度.
这是我们的系统架构图, 以便在实践之前对系统有一个基本的了解.
正如你所看到的, 在这个例子中, 我们有 Student 微服务和 School 微服务. 为了演示的目的, Student 服务使用名为 MongoDB 的 NoSQL 数据库, School 服务使用 SQL 数据库.
然后你可以看到 Edureka 服务发现. 服务发现是一种模式, 客户端应用可以在运行时动态发现服务的位置. 客户端可以通过查询该注册表来确定特定服务的位置.
接下来我要谈的是API 网关. API 网关是一个反向代理, 它位于微服务之前, 是传入 API 请求的单一入口. 除其他外, 它还负责请求路由, 组合和协议转换.
服务发现与 API 网关之间的联系是, API 网关使用服务发现注册表中的信息将请求路由到相应的微服务. API 网关会查询注册表以确定目标微服务的位置, 然后将传入的请求转发到该服务. 这样, API 网关就成了客户端应用和微服务之间的桥梁, 为传入请求提供了统一的入口, 并抽象了微服务的底层复杂性.
好了, 首先让我们看看我在这个项目中使用了哪些工具:
在本实践中, 我将使用Intellij作为IDE, MySql和MongoDB作为数据库.
我想现在你已经对系统及其技术有了很好的了解. 现在我们可以直接进入实践部分. 我将从 School 服务开始.
School 微服务
要初始化项目, 请访问 start.spring.io/, 你可以根据需要指定自己的Groud Id, Artifact Id, Project Name和Java Version. 我们还将在这里添加一些依赖项, 因为我们希望在项目中使用它们: 目前, 我需要Lombok, Spring Data JPA, Spring Web和MySQL Driver作为依赖项.
- Spring Web - 将 Spring MVC 和嵌入式 Tomcat 添加到项目中
- Spring Data JPA - Java 持久化 API 和 Hibernate
- MySQL 驱动 - JDBC 驱动
- Lombok - Java 库工具, 可生成代码, 最大限度地减少模板代码. 该库以易于使用的注解取代模板代码.
然后, 你可以点击 Generate 按钮, 下载一个压缩文件. 这就是你的项目.
创建项目后, 进入 pom.xml, 你就能看到已安装的依赖项.
<?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>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.bytebeats</groupId>
<artifactId>school-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>school-service</name>
<description>School Microservice</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
将 Spring Boot 应用与数据库连接
接下来, 在开始开发应用之前, 我们需要设置数据库. 这可以通过 Spring Data JPA 轻松完成, 只需几个参数就能建立连接.
为此, 在resources文件夹下, 你可以看到一个名为application.properties的文件.
在 application.properties 文件中添加这些细节. 正如我前面提到的, 我们使用 MySQL 作为数据库. 在这里, 我们必须指定服务器端口, 数据库名称, 用户名和密码.
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:port/databasename
spring.datasource.username = username
spring.datasource.password = password
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
server.port=port number
好了, 现在我们完成了 School 服务与数据库的连接.
创建 School 实体(Domain模型)
现在让我们创建 School 实体. 首先, 我将创建一个模型包. 在实体包中, 我要创建一个名为 School.java 的类. 然后添加以下代码.
package io.github.bytebeats.schoolservice.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "school")
public class School {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String schoolName;
private String location;
private String principalName;
}
在这里, 你不需要生成构造函数, getters 和 setters, 因为这些都是由 @Data, @AllArgsConstructor 和 @NoArgsConstructor 这些注解完成的, 这些注解是由Lombok依赖提供的.
@Entity
注解指定该类是一个实体, 并映射到一个数据库表. @Table
注解指定了用于映射的数据库表的名称. @Id
注解指定了实体的主键, 而 @GeneratedValue
则提供了主键值生成策略的指定.
创建 Repository 类(持久层)
下一步是创建 Repository 类. @Repository
是一个 Spring 注解, 表示所装饰的类是一个 Repository . Repository 是一种机制, 用于封装存储, 检索和搜索行为, 以模拟对象集合.
首先, 我要为 Repository 类创建一个包. 在包内创建一个名为 SchoolRepository 的 接口. 然后添加以下代码.
package io.github.bytebeats.schoolservice.repository;
import io.github.bytebeats.schoolservice.entity.School;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SchoolRepository extends JpaRepository<School,Integer> {
}
在这里, 你可以看到我通过 JpaRepository 扩展了接口, 并传递了 School 模型和其Id的数据类型.
JpaRepository
是Repository
的 JPA 特定扩展. 它包含 CrudRepository
和 PagingAndSortingRepository
的完整 API. 因此, 它包含基本 CRUD 操作的 API 以及分页和排序的 API.
创建服务(业务层)
我们用 @Service 标记 Bean, 以表明它包含业务逻辑.
在这里, 我们将实现插入, 检索, 更新和删除 Student 记录等功能. 我们在服务类文件中编写了业务逻辑.
现在创建一个名为 service 的包, 并在服务包下创建一个名为 SchoolService 的类. 最后, 在 SchoolService 类中添加以下代码.
package io.github.bytebeats.schoolservice.service;
import io.github.bytebeats.schoolservice.entity.School;
import io.github.bytebeats.schoolservice.repository.SchoolRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SchoolService {
@Autowired
private SchoolRepository schoolRepository;
public School addSchool(School school){
return schoolRepository.save(school);
}
public List<School> fetchSchools(){
return schoolRepository.findAll();
}
public School fetchSchoolById(int id){
return schoolRepository.findById(id).orElse(null);
}
}
创建 Controller
下面的代码显示了 Rest Controller 类文件, 在这里我们 @Autowired 注解了 StudentService
类, 并调用了用于粗暴操作的方法.
package io.github.bytebeats.schoolservice.controller;
import io.github.bytebeats.schoolservice.entity.School;
import io.github.bytebeats.schoolservice.service.SchoolService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin("*")
@RequestMapping(value = "/school")
@RestController
public class SchoolController {
@Autowired
private SchoolService schoolService;
@PostMapping
public School addSchool(@RequestBody School school){
return schoolService.addSchool(school);
}
@GetMapping
public List<School> fetchSchools(){
return schoolService.fetchSchools();
}
@GetMapping("/{id}")
public School fetchSchoolById(@PathVariable int id){
return schoolService.fetchSchoolById(id);
}
}
这里解释了我为什么使用 @CrossOrigin("*"): spring.io/guides/gs/r…
好了, 现在右键单击 DemoApplication.java
文件并单击运行. 在运行应用之前, 请确保你已创建了一个数据库, 其名称与你在 application.properties
文件中指定的名称相同. 我的例子是 schoolservice.
好了, 现在我们几乎完成了School 微服务. 现在, 我们可以使用名为 Postman 的软件测试 REST API.
Student 微服务
接下来, 我要创建 Student 微服务. 再次进入 Spring 初始化器, 指定你自己的 Groud Id, Artifact Id, Project Name, Java version和dependencies.
Spring Data MongoDB : MongoDB 将数据存储在集合中. Spring Data MongoDB 将 Customer 类映射到名为 customer 的集合中. 如果你想更改集合的名称, 可以在类上使用 Spring Data MongoDB 的 @Document
注解.
创建项目后, 进入pom.xml, 就能看到我们安装的依赖项.
<?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>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.bytebeats</groupId>
<artifactId>student-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>student-service</name>
<description>Student Microservice</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
现在我们来设置数据库. 为此, 在 application.properties
文件中添加连接字符串:
spring.data.mongodb.uri=put the connection string here
server.port=8081
创建 Student 模型(Domain模型)
现在让我们创建 Student 模型. 如前所述, 我将创建一个模型包. 在模型包中, 我将创建一个名为 Student.java
的类. 然后添加以下代码.
package io.github.bytebeats.studentservice.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "students")
public class Student {
@Id
private String id;
private String name;
private int age;
private String gender;
private Integer schoolId;
}
创建 Repository 类(持久层)
接下来, 让我们为 Repository 类创建一个包. 在包内创建一个接口, 名为StudentRepository. 然后添加以下代码.
package io.github.bytebeats.studentservice.repository;
import io.github.bytebeats.studentservice.model.Student;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface StudentRepository extends MongoRepository<Student, String> {
}
MongoRepository扩展了 PagingAndSortingRepository
和 QueryByExampleExecutor
接口, 这些接口进一步扩展了 CrudRepository
接口. MongoRepository
提供了所有有助于创建 CRUD 应用的必要方法, 它还支持自定义派生查询方法.
创建服务(业务层)
要在服务类文件中编写业务逻辑, 请创建一个名为service的包, 并在服务包下创建一个名为StudentService的类. 然后, 在 StudentService 类中添加以下代码.
哦, 稍等一下...在此之前, 我将创建一个名为 dto 的包, 并创建两个名为 School
和 StudentResponse
的类. 你稍后就会明白我为什么要创建这两个类. 然后为每个类分别添加以下代码.
package io.github.bytebeats.studentservice.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class School {
private int id;
private String schoolName;
private String location;
private String principalName;
}
package io.github.bytebeats.studentservice.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class StudentResponse {
private String id;
private String name;
private int age;
private String gender;
private School school;
}
在这里, 我要在 Student 服务中调用 School 服务端点. 为此, 我们需要 RestTemplate
.
Rest Template 用于创建消耗 RESTful Web 服务的应用. 你可以使用exchange()
, getForObject
等方法来使用所有 HTTP 方法的 Web 服务. 下面的代码展示了如何为 Rest Template 创建 Bean 以自动布线 Rest Template 对象. 让我们在主类中添加以下内容.
package io.github.bytebeats.studentservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class StudentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StudentServiceApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后在 StudentService 类中添加以下代码.
package io.github.bytebeats.studentservice.service;
import io.github.bytebeats.studentservice.dto.School;
import io.github.bytebeats.studentservice.dto.StudentResponse;
import io.github.bytebeats.studentservice.model.Student;
import io.github.bytebeats.studentservice.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Optional;
@Service
public class StudentService {
@Autowired
private StudentRepository studentRepository;
@Autowired
private RestTemplate restTemplate;
public ResponseEntity<?> createStudent(Student student){
try{
return new ResponseEntity<Student>(studentRepository.save(student), HttpStatus.OK);
}catch(Exception e){
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
public ResponseEntity<?> fetchStudentById(String id){
Optional<Student> student = studentRepository.findById(id);
if(student.isPresent()){
School school = restTemplate.getForObject("http://localhost:8082/school/" + student.get().getSchoolId(), School.class);
StudentResponse studentResponse = new StudentResponse(
student.get().getId(),
student.get().getName(),
student.get().getAge(),
student.get().getGender(),
school
);
return new ResponseEntity<>(studentResponse, HttpStatus.OK);
}else{
return new ResponseEntity<>("No Student Found",HttpStatus.NOT_FOUND);
}
}
public ResponseEntity<?> fetchStudents(){
List<Student> students = studentRepository.findAll();
if(students.size() > 0){
return new ResponseEntity<List<Student>>(students, HttpStatus.OK);
}else {
return new ResponseEntity<>("No Students",HttpStatus.NOT_FOUND);
}
}
}
在这里, 你可以看到在 fetchStudentById
方法中, 我们正在调用 School 服务的端点, 以便使用 Id 获取 School 的详细信息.
现在创建一个 controller 包, 并在其中创建一个名为 StudentController 的类, 然后添加以下代码.
package io.github.bytebeats.studentservice.controller;
import io.github.bytebeats.studentservice.model.Student;
import io.github.bytebeats.studentservice.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@CrossOrigin("*")
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/{id}")
public ResponseEntity<?> fetchStudentById(@PathVariable String id){
return studentService.fetchStudentById(id);
}
@GetMapping
public ResponseEntity<?> fetchStudents(){
return studentService.fetchStudents();
}
@PostMapping
public ResponseEntity<?> createStudent(@RequestBody Student student){
return studentService.createStudent(student);
}
}
现在, 你可以运行应用, 并借助 Postman 等工具测试所有端点.
现在, 我们已经创建了两个服务. 现在, 我们需要创建服务注册表.
什么是服务注册表?
服务注册表是微服务架构中的一个中央 Repository , 它是每个微服务的位置和状态的真实来源. 服务注册表提供了一个中心位置, 服务可以在此注册并查询其他服务的信息, 从而帮助微服务相互发现并相互通信.
微服务架构之所以需要服务注册表, 原因如下:
1.微服务的动态性: 例如, 由于扩展, 升级或故障, 微服务可能会动态地出现和消失. 服务注册表可以跟踪这种不断变化的情况, 并提供有关每个服务可用性的最新信息.
2.负载平衡: 服务注册表提供有关每个微服务实例当前负载的信息, 负载平衡器可利用这些信息将请求分配给负载最小的实例.
3.服务发现: 服务注册表有助于微服务相互发现, 并使它们能够相互通信. 这在服务解耦和松散耦合的微服务架构中非常重要.
4.监控和管理: 服务注册表还可用于监控微服务的健康状况, 并提供系统状态概览.
因此, 服务注册表是微服务架构的重要组合部分, 因为它提供了一种集中管理和协调微服务之间交互的方式.
创建服务注册表
再次访问 start.spring.io/, 你可以根据需要指定自己的Groud Id, Artifact Id, Project Name和Java Version. 我将 Eureka Server 添加为依赖关系.
Eureka 服务器 : 这是 Netflix OSS(开源软件)套件中的服务注册服务器, 用于为分布式环境中的微服务提供服务发现功能. Eureka 服务器提供可用微服务的注册表, 允许客户端微服务相互发现和通信.
首先, 我将进入主类并注解 @EnableEurekaServer
:
package io.github.bytebeats.serviceregistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryApplication.class, args);
}
}
然后在 application.propertise
文件中添加以下内容.
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
server.port
: 此属性设置 Eureka 服务器运行的端口. 在本例中, 服务器将在 8761 端口运行.eureka.client.register-with-eureka
: 该属性表示该实例是否应作为服务向 Eureka 服务器注册. 在这种情况下, 该值设为false
, 这意味着该实例不会向 Eureka 服务器注册.eureka.client.fetch-registry
: 该属性表示该实例是否应从 Eureka 服务器获取注册信息. 在本例中, 该值设置为false
, 这意味着该实例不会从 Eureka 服务器获取注册表信息.
现在再次进入 Student 服务和 School 服务的 pom.xml
, 进行以下更改.
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
添加这些依赖项后, School 服务和 Student 服务的 pom.xml
文件将如下所示.
在 Student 服务中:
<?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>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.bytebeats</groupId>
<artifactId>student-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>student-service</name>
<description>Student Microservice</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
在 School 服务中:
<?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>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.bytebeats</groupId>
<artifactId>school-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>school-service</name>
<description>School Microservice</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
现在, 我们需要进行配置, 然后这些服务才能连接到 Eureka 服务器.
为此, 首先进入 School 服务应用属性, 并进行以下更改.
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/schoolservice
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
server.port=8082
spring.application.name=SCHOOL-SERVICE
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.hostname=localhost
spring.application.name
属性将应用名称设置为SCHOOL-SERVICE
.eureka.client.register-with-eureka
属性设置为true
, 这意味着应用将向 Eureka 服务器注册.eureka.client.fetch-registry
属性设置为`true", 表示应用将从 Eureka 服务器检索注册信息.eureka.client.service-url.defaultZone
属性将 Eureka 服务器的 URL 设置为http://localhost:8761/eureka/
eureka.instance.hostname
属性将实例的主机名设为localhost
.
该配置允许应用向 Eureka 服务器注册, 并从服务器检索注册信息, 以便其他服务发现并与其通信.
我想现在你已经有了一个清晰的概念. 现在对 Student 服务做同样的事情.
spring.data.mongodb.uri=put the connection string
server.port=8081
spring.application.name=STUDENT-SERVICE
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.hostname=localhost
好的. 一切就绪. 现在启动服务注册表, 打开浏览器, 访问 http://localhost:8761. 你应该能看到如下页面.
目前你看不到任何在服务器上注册的应用. 因此, 在更改应用属性后, 你必须重启 Student 和 School 服务, 然后再次刷新浏览器. 然后, 你就可以在当前已在 Eureka 注册的实例下看到 STUDENT-SERVICE
和 SCHOOL-SERVICE
.
好的. 如果你还记得, 在 Student 服务中, 我用 http://localhost:8082/school/{id} 调用了 School 服务. 考虑到我们有这个服务的多个实例, 而且这些服务工作在不同的端口或不同的 url 上. 这样就很难获得正确的 url 信息. 因此, 在我们的例子中, 我们给出的不是 localhost:8082 而是应用名称, 如下所示. 这样做的好处是, 我们不必担心主机名或端口号. 我们只需提供应用名称即可.
public ResponseEntity<?> fetchStudentById(String id){
Optional<Student> student = studentRepository.findById(id);
if(student.isPresent()){
School school = restTemplate.getForObject("http://SCHOOL-SERVICE/school/" + student.get().getSchoolId(), School.class);
StudentResponse studentResponse = new StudentResponse(
student.get().getId(),
student.get().getName(),
student.get().getAge(),
student.get().getGender(),
school
);
return new ResponseEntity<>(studentResponse, HttpStatus.OK);
}else{
return new ResponseEntity<>("No Student Found",HttpStatus.NOT_FOUND);
}
}
使用应用名称而不是主机名和端口号, 可以提供一种更灵活, 更可扩展的服务发现方法, 而且更易于管理微服务架构.
再次使用 Postman 测试端点并检查结果.
我们连接了服务注册表, 现在要对请求进行负载平衡. 转到 Student 服务主类, 用 @LoadBalanced 注解 RestTemplate
.
package io.github.bytebeats.studentservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class StudentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StudentServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在本例中, RestTemplate
实例被注解为@LoadBalanced
, 这意味着它将使用负载平衡算法将请求分配给服务的多个实例. 要使用该 RestTemplate
实例进行 RESTful 网络服务调用, 只需将其自动连接到组件类中, 然后根据需要使用即可.
请注意, 要使 @LoadBalanced
注解有效, 你还必须在微服务架构中运行一个负载平衡器服务, 如 Netflix Eureka. 负载平衡器将负责把请求分发到服务的可用实例上.
现在, 让我们进入这篇长文的最后一部分.
创建 API 网关
什么是 API 网关?
API 网关是介于微服务和使用微服务的客户端应用之间的中介. 它是所有 API 请求的单一入口, 为你的微服务提供统一的接口, 并简化客户端架构.
使用 API 网关有以下几个好处:
- 路由: API 网关可以根据请求 URL 或其他属性将请求路由到相应的微服务. 这样, 你就可以在不影响客户端应用的情况下更改底层微服务.
- 负载平衡: API 网关可以执行负载平衡, 在微服务的多个实例之间分配传入的请求. 这有助于提高微服务的可靠性和可用性.
- 缓存:API 网关可以缓存经常请求的数据, 以减少微服务的负载. 这有助于提高微服务的性能, 缩短客户端应用的响应时间.
- 安全 API 网关可以充当安全层, 为传入请求提供验证和授权. 这有助于确保微服务的安全, 并确保只有经过授权的客户端才能访问它们.
- 监控: API 网关可对所有 API 请求进行监控和记录. 这使得诊断问题和监控微服务性能变得更加容易.
总之, 使用 API 网关可以提供一种集中, 高效的方式来管理对微服务的访问, 从而提高微服务架构的可靠性, 安全性和性能.
好了, 现在你应该对 API 网关有所了解了. 目前, 我们的请求直接进入每个微服务. 因此, 现在我们要创建一个 API 网关, 这样所有来自用户的请求都会首先到达该网关. 然后根据 url 模式重定向到相关的微服务.
最后, 让我们创建另一个项目.
现在, 我要在资源下创建一个 application.yml
文件, 并添加以下代码.
server:
port: 8080
spring:
application:
name: API-GATEWAY
cloud:
gateway:
routes:
- id: STUDENT-SERVICE
uri: lb://STUDENT-SERVICE
predicates:
- Path=/student/**
- id: SCHOOL-SERVICE
uri: lb://SCHOOL-SERVICE
predicates:
- Path=/school/**
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
hostname: localhost
cloud.gateway
部分定义了两个路由: STUDENT-SERVICE
和SCHOOL-SERVICE
. 这些路由用于根据 URL 路径将传入请求转发到不同的服务. 例如, 以/student/
开头的请求将被转发到 STUDENT-SERVICE
, 而以/school/
开头的请求将被转发到 SCHOOL-SERVICE
.
启动云网关应用, 服务应在 Eureka 服务注册表注册. 现在, 你应该使用 API-GATEWAY 服务中给出的主机名 localhost 和端口 8080 调用请求.
让我们举个例子: http://localhost:8080/student. 因此, 根据 application.yml
文件中给出的上述 URL 模式, 它应重定向到 Student 服务.
现在使用 Postman 等工具测试上述所有端点. 下面是一些示例.
创建 School
通过 Id 获取 Student
今天冗长的文章就到此结束啦. 希望你有所收获. 谢谢!
一家之言, 欢迎斧正!
Happy Coding! Stay GOLDEN!