前言:随着互联网的快速发展和用户规模的急剧增长,传统的单体应用架构已经远远超出了单一电脑能处理的极限。在这种背景下,微服务架构应运而生。从原理上说,微服务架构通过将单一应用拆分成多个小型、独立的服务,这些服务可以分布在多个计算机上共同工作,体现的是分而治之的思想。除此任意微服务可扩容。对某热点服务单独扩容,提高经济价值和资源利用率。本篇就介绍如何搭建微服务,实现服务的注册,发现,调用。
服务注册和发现
微服务项目本身其实就是一个个的springboot项目的有机组合。springcloud也要是在springboot基础上搭建的,就像springboot是基于spring拓展出的。但单独的微服务,并没有关联。需要使用一个注册中心进行管理不同服务的信息。可以使用nacos进行服务注册和发现。nacos是比较主流的注册中心,相对于zookeeper在配置管理上更加方便。
nacos安装
nacos官网安装方式
考虑到部署的流畅性,这边则采用docker进行部署。
docker run --restart=always --name nacos -e MODE=standalone -p 8848:8848 -p 9848:9848 -d nacos/nacos-server
:v2.1.1
验证可以登录到nacos的后台
默认密码使用 nacos/nacos
nacos配置
创建一个book的命名空间
项目搭建
服务设计遵守一定的基本思路,需要定义服务边界,确保每个服务都有明确的责任
通常可以根据业务功能进行划分服务。
- 用户服务:负责用户账户管理和认证。
- 订单服务:处理订单创建、查询和取消等功能。
- 商品服务:管理商品信息,如价格、库存等。
- 支付服务:处理支付流程,包括支付网关对接等。
- 评论服务:管理用户对商品的评价和反馈。
这样不同的服务专注自己的领域内容。团队协作更加高效。
设计说明
本次设计一个简单的图书管理系统说明
服务包含:
用户服务模块
图书借阅服务模块
基础结构
先创建一个普通的java项目,删除src文件夹,和改造父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.3.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lar</groupId>
<artifactId>book</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>book</name>
<description>book</description>
<modules>
<module>user-server</module>
<module>borrow-server</module>
</modules>
<properties>
<java.version>17</java.version>
<latest.version>2.3</latest.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2023.0.1.2</version>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>4.1.3</version>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2023.0.1.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
<!-- json包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这个步骤主要是添加了nacos配置和注册发现的相关依赖。
用户服务模块
对根目录新建一个springboot模块,然后进行创建。下一步骤依赖先什么都不选
修改子模块的pom文件的parent
<parent>
<groupId>com.lar</groupId>
<artifactId>book</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
然后修改父pom,添加子module管理
子模块添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
启动类增加@EnableDiscoveryClient注解
在resources下面分别添加application.yml 和bootstrap.yml 文件,增加上配置
application.yml
server:
port: 8081
boostrap.yml
spring:
application:
name: user-server # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
discovery:
namespace: 407243bd-1987-4e5e-bdef-5eaacf06bd16
# shared-configs: # 共享配置
# - dataId: system-config.yaml
# - dataId: datasource.yaml
此处使用第一步骤新建的命名空间隔离开,这在实际开发中调试很有帮助。
对服务添加一个boot服务配置
然后需要点击编译一下
运行启动服务
可以在nacos看见我们的user服务出现在服务列表
图书服务模块
新建模块borrow-server
和上面的类似步骤,唯一不同的是配置上新的端口和服务名要改一下
最终启动后,可以看见新的服务上去了
编写测试代码
本来想用mysql演示下,但是由于篇幅,为了方便说明微服务自身,而不脱离核心,改用模拟数据演示
usermodle的内容
package com.lar.user.model;
import lombok.Data;
@Data
public class User {
private String username;
private String age;
// 是主键也是学号
private String id;
}
usercontroller内容
package com.lar.user.controller;
import com.lar.user.model.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/user/")
public class UserController {
private static Map<String,User> userMap = new HashMap();
static {
User user = new User();
user.setUsername("张三");
user.setAge("18");
user.setId("1");
User user2 = new User();
user2.setUsername("李四");
user2.setAge("18");
user2.setId("2");
User user3 = new User();
user3.setUsername("王五");
user3.setAge("18");
user3.setId("3");
userMap.put("1",user);
userMap.put("2",user2);
userMap.put("3",user3);
}
@GetMapping("/info/{uid}")
public User findUserById(@PathVariable("uid") String uid){
return userMap.get(uid);
}
}
bookmodel内容
package com.lar.borrow.model;
import lombok.Data;
@Data
public class BookInfo {
private String id;
private String bookName;
}
bookcontroller内容
package com.lar.borrow.controller;
import com.lar.borrow.model.BookInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/book/")
public class BookController {
private static Map<String, BookInfo> bookMap = new HashMap();
static {
BookInfo book = new BookInfo();
book.setBookName("Java并发编程实战");
book.setId("1");
BookInfo book2 = new BookInfo();
book2.setBookName("Effective Java 中文版(第3版)");
book2.setId("2");
BookInfo book3 = new BookInfo();
book3.setBookName("微服务设计与实践");
book3.setId("3");
bookMap.put("1", book);
bookMap.put("2", book2);
bookMap.put("3", book3);
}
@GetMapping("/info/{uid}")
public BookInfo findBookInfoById(@PathVariable("uid") String uid) {
return bookMap.get(uid);
}
@PostMapping("/list")
public List<BookInfo> list() {
return new ArrayList<BookInfo>(bookMap.values());
}
}
服务调用
直连端口
这边使用apifox客户端请求工具测试
通过服务的具体端口请求服务,非常直接,也是传统单体项目的使用方式
统一网关
上面的方式虽然能使用,但是是有缺陷的。因为试想一下如果使用直连服务,势必会对前端开发人员造成困扰,不同的服务可能不同域名,不同的端口。而后期如果添加新的服务,需要加新的域名端口管理。而且端口暴露过多,也是不安全的做法。容易被针对攻击。所以微服务架构,微服务网关必不可少。必须要实现统一网关,让不同的服务使用统一的端口
新创建一个gateway模块,和上面创建的步骤类似
在bootstrap.yml添加如下内容
spring:
application:
name: gateway-server # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: router.yaml
discovery:
namespace: 407243bd-1987-4e5e-bdef-5eaacf06bd16
yml增加8080端口
增加依赖
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.1.5</version>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>4.1.4</version>
</dependency>
特别注意新的gateway模块要去掉starter-web依赖(否则启动会冲突导致无法启动)
在nacos新增一个router.yml的配置文件,内容如下
spring:
cloud:
gateway:
routes:
# 用户服务
- id: user-server
uri: lb://user-server
predicates:
- Path=/api/user/**
# 书籍服务
- id: book-server
uri: lb://borrow-server
predicates:
- Path=/api/book/**
修改书籍和用户服务的bootstrap,引入配置
上面就是配置好了对应的路由规则。如/api/book的前缀就会跑到book-server的微服务上去。具体匹配的就是在各自服务上面的name来识别的
同时额外一个好处是随着以后服务的拓展。springcloud gataway能自动的进行负载均衡。
可以看到8080可以直接打通,http://localhost:8080/api/book/list 会自动转发到 http://localhost:8082/api/book/list.
feign调用
很多时候服务的调用不是从客户端发起,即服务和服务内部如何调用的问题。而答案就是用spring cloud中的feign客户端。
在需要的服务增加依赖
<!--openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类添加注解@EnableFeignClients
新建一个api
@FeignClient(name = "borrow-server")
@Component
public interface BookApi {
@GetMapping("/api/book/info/{uid}")
public BookInfo findBookInfoById(@PathVariable("uid") String uid);
}
在使用的地方使用导入即可,实际场景需要把不同微服务的api模块单独封装出去
这边可以进一步增加熔断和降级策略。添加一个fallback类,然后标注
@FeignClient(name = "borrow-server",fallback = BookApiFallBack.class)
public class BookApiFallBack implements BookApi{
@Override
public BookInfo findBookInfoById(String uid) {
BookInfo bookInfo = new BookInfo();
bookInfo.setBookName("暂无数据");
return bookInfo;
}
}
总结
本篇大致从0开始搭建一个微服务创建的过程。包括一些常用的组件的使用。
其实还有很多细节可能是需要自己多多动手尝试才能体会。微服务本身的设计考量很多。需要多看看其他人的设计思路,博采众长,每个人其实都应该有自己的思考,本篇是一个简单引导。希望和大家共勉,共同进步。