创建微服务工程
cloud-demo
|__services
|__service-product
|__service-order
1.创建父项目
创建一个名为cloud-demo的springboot父项目,用于对springboot,springcloud,springcloud alibaba版本锁定
<packaging>pom</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2023.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version>
</properties>
<!-- 这里使用dependencyManagement来进行依赖管理 -->
<!-- 即让子模块需要显式声明依赖,不用指定version或scope -->
<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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.创建微服务管理子项目
创建一个名为services的普通maven项目该子项目继承自cloud-demo项目,其管理了所有微服务的公共依赖
<packaging>pom</packaging>
<dependencies>
<!--注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
3.创建微服务子项目
创建名为service-product,service-order的普通maven子项目。这两个项目都是services项目的子项目,是微服务项目
Nacos
官网:https://nacos.io/docs/latest/overview/
它是一个能更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。可以实现服务发现和服务健康检测,动态配置服务,动态DNS服务,服务以及元数据管理
对于windows平台的nacos在官网下载并解压后可以在nacos/bin
目录通过startup.cmd -m standalone
以单机状态启动nacos
服务注册
- 启动nacos
- 在
nacos/bin
目录通过startup.cmd -m standalone
以单机状态启动nacos
- 在
- 启动微服务
- 由于所有微服务模块都继承自cloud-demo,那么其本身也是boot项目
- 直接在微服务模块中导入
spring-boot-starter-web
依赖即可实现web开发 - 创建一个boot的启动类
- 编写一个配置文件,在其中配置应用名(会在服务中心显示为服务名),端口号,nacos地址
- 引入服务发现依赖
spring-cloud-starter-alibaba-nacos-discovery
- 该依赖是所有微服务模块的公共依赖,故在service-product模块中导入
- 配置nacos地址
spring.cloud.nacos.server-addr=127.0.0.1:8848
- 查看注册中心
- 访问
http://localhost:8848/nacos
- 此时会在服务列表中看到我们启动的服务
- 访问
- 集群模式启动测试
- 单机情况下通过拷贝实例模拟微服务集群
- 拷贝实例时需要在修改选项中勾选程序参数,在程序参数中可以配置在properties文件中能配置的所有参数,只需要在前面添加
--
例如:--server.port=8001
- 对于拷贝添加的服务会在nacos控制台的实例数进行显示
// service-order
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
// com.atli.order.OrderMainApplication.java
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class,args);
}
}
// resources/application.properties
spring.application.name=service-order
server.port=8000
spring.cloud.nacos.server-addr=127.0.0.1:8848
// service-product
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
// com.atli.product
@SpringBootApplication
public class ProductMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProductMainApplication.class, args);
}
}
// resources/application.properties
spring.application.name=service-product
server.port=9000
spring.cloud.nacos.server-addr=127.0.0.1:8848
服务发现
- 添加测试依赖
- 在要进行测试的服务模块中添加
spring-boot-starter-test
依赖
- 在要进行测试的服务模块中添加
- 开启服务发现功能
- 在微服务启动文件添加
@EnableDiscoveryClient
- 在微服务启动文件添加
- 测试服务发现API
- DiscoveryClient
- NacosServiceDiscovery
// service-product
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
// discoveryTest.java
@SpringBootTest
public class DiscoveryTest {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
NacosServiceDiscovery nacosServiceDiscovery;
@Test
void discoveryClientTest(){
discoveryClient.getServices().forEach(service -> {
// 打印微服务名称
System.out.println(service);
discoveryClient.getInstances(service)
// 打印服务ip及其端口号
.forEach(instance -> System.out.println(instance.getHost() + ":" + instance.getPort()));
});
}
@Test
void nacosServiceDiscoveryTest() throws NacosException {
nacosServiceDiscovery.getServices().forEach(service -> {
System.out.println(service);
discoveryClient.getInstances(service)
.forEach(instance -> System.out.println(instance.getHost() + ":" + instance.getPort()));
});
}
}
远程调用实例
这是一个下单场景,用户对订单服务发起创建订单请求,订单服务会去商品服务查询订单中的商品,查询后会返回商品数据到订单服务生成订单,最后返回订单数据
- 创建实体对象
- 由于在该实例中订单服务和商品服务都要使用商品类,故将所有实体类抽象成model层,以方便各服务对实体类的访问
- 又由于model层要被所有微服务调用,故需要放在与services模块同级目录下,并将其作为依赖导入到services模块
- 创建服务的controller,service层
- 按照三层架构模式编写controller和service层,mapper层用假数据代替
- 在订单服务的service层使用RestTemplate远程调用商品服务
- 启动所有微服务进行测试
cloud-demo
|__model
| |__com.atli.order.bean.Order(class)
| |__com.atli.product.bean.Product(class)
|
|__services
|__service-order
| |__com.atli.order
| |__config.OrderConfig(class)
| |__controller.OrderController(class)
| |__service
| |__OrderService(interface)
| |__Impl
| |__OrderServiceImpl(class)
|
|__service-product
|__com.atli.product
|__config.ProductConfig(class)
|__controller.ProductController(class)
|__service
|__ProductService(interface)
|__Impl
|__ProductServiceImpl(class)
model 模块
// com.atli.order.bean.Order
@Data
public class Order {
private Long id;
private BigDecimal totalAmount;
private Long userId;
private String nickName;
private String address;
private List<Object> productList;
}
// com.atli.product.bean.Product
@Data
public class Product {
private Long id;
private BigDecimal price;
private String productName;
private int num;
}
services 模块
<!-- pom.xml(service) -->
<!--使所有微服务依赖公共模型层model-->
<dependency>
<groupId>com.atli</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency>
<!-- 用于远程调用时负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
service-product 模块
// com.atli.product.controller.ProductController
@RestController
public class ProductController {
@Autowired
ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long productId) {
return productService.getProductById(productId);
}
}
// com.atli.product.service.ProductServiceImpl
@Service
public class ProductServiceImpl implements ProductService {
@Override
public Product getProductById(Long id) {
Product product = new Product();
product.setId(id);
product.setProductName("apple");
product.setPrice(BigDecimal.valueOf(8848));
product.setNum(2);
return product;
}
}
// com.atli.product.config.ProductConfig
@Configuration
public class ProductConfig {
@Bean
// 由于RestTemplate是线程安全的,全局只有一个,故在这里将其加入ioc方便使用
//RestTemplate用于远程调用
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Order模块
// com.atli.order.OrderController
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@GetMapping("/create/{userId}/{productId}")
public Order createOrder(@PathVariable Long userId, @PathVariable Long productId) {
return orderService.createOrder(userId, productId);
}
}
// com.atli.order.OrderServiceImpl
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
// @Autowired
// LoadBalancerClient loadBalancerClient
@Override
public Order createOrder(Long userId, Long productId) {
Product product = getProductFromRemote(productId);
Order order = new Order();
order.setId(1L);
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("zhangsan");
order.setAddress("home");
order.setProductList(Arrays.asList(product));
return order;
}
private Product getProductFromRemote(Long productId) {
// 获取到商品服务所在及其的IP和端口
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
ServiceInstance serviceInstance = instances.get(0);
// 该方法使用了LoadBalancerClient,底层使用轮询实现负载均衡
// ServiceInstance serviceInstance = loadBalancerClient.choose("service-product");
// 拼接远程调用的url
String url ="http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;
// 发送远程请求
log.info("已发送请求:{}",url);
// 如果使用了注解式的负载均衡上面获取url的代码可以直接去掉
// 这里的service-product是服务名称
// String url = "http://service-product/product/"+productId
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
}
// com.atli.order.config.OrderConfig
@Configuration
public class OrderConfig {
//使用该注解会让该服务自动拥有负载均衡功能
//并且在发送http请求时直接使用服务名称来代替微服务的ip和端口号
//@LoadBalanced
@Bean
// 由于RestTemplate是线程安全的,全局只有一个,故在这里将其加入ioc方便使用
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
配置中心
nacos配置中心可以实现配置的集中管理,推送配置变更,不停机配置更新。
基本使用
- 启动nacos
- 引入依赖
spring-cloud-starter-alibaba-nacos-config
- 由于可能所有微服务都需要使用配置中心,故在services模块引入该依赖
- 对application.properties文件进行配置
spring.cloud.nacos.server-addr=127.0.0.1:8848
,该配置是指定nacos地址spring.config.import=optional:nacos:service-order.properties
,该配置可以让service-order微服务在启动后从nacos中读取配置文件。并且这里设置了optional
表示该项为可选项,防止项目启动但nacos没有配置出现报错
- 创建data-id(数据集)
- 在nacos界面配置管理的配置列表可以新建配置
- Data ID是该配置的名称,此处按照步骤3的名称应为service-order.properties
- 在配置内容中可以进行任意配置(只要是properties能配的都可以)
- 使用数据集
- 在使用数据集时,需要在当前类中添加
@RefreshScope
注解,让监听的数据有类似vue中响应式数据的动态刷新功能 - 获取数据集依然使用springboot中获取配置的方法:
@Value("${data}")
- 在使用数据集时,需要在当前类中添加
批量动态刷新
上面的例子中,使用了springboot中获取配置的方法:@Value("${data}")
,当遇到有大量配置需要被读取,可以将这些配置抽取到一个单独的类中,使用@ConfigurationProperties(prefix = "前缀名")
来进行批量绑定
// 这里假设在配置中心配置了order.timeout=30min;order.auto-confirm=7d
@Component
@ConfigurationProperties(prefix="order")
@Data
public class OrderProperties{
String timeout;
String autoConfirm;
}
配置监听
对于配置文件的修改我们可以使用nacos提供的NacosConfigManager搭配spring的ApplicationRunner进行使用
// com.atli.order.OrderMainApplication
@Bean
// 使用ApplicationRunner让该段代码在启动spring时运行
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
return args -> {
// 获取配置服务
ConfigService configService = nacosConfigManager.getConfigService();
// 数据集ID(配置文件名):service-order.properties
// 数据集组名:DEFAULT_GROUP,该组名可以在nacos面板看到
// 添加监听,监听函数需要自己new
configService.addListener("service-order.properties","DEFAULT_GROUP",new Listener(){
@Override
// 线程池
public Executor getExecutor(){
return Executors.newFixedThreadPool(4);
}
@Override
// 接收变化的配置信息
public void receiveConfigInfo(String configInfo){
System.out.println("ReceiveConfigInfo:"+configInfo)
}
});
};
}
数据隔离
在实际开发中,会需要到开发环境,测试环境,还有生产环境。其中每种环境中又有不同的微服务,每种微服务又会有各种配置。
我们可以使用springBoot定义多环境来区分各种场景,使用名称空间来区分多套环境,使用组来区分微服务,使用数据集来区分配置。这些操作可能很轻易的再nacos面板中实现
对于按需加载配置我们可以使用指定命名空间和组名实现
server:
port: 8000
spring:
profiles: # 这个配置用于指定当前环境
active: dev
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
namespace: dev
# 这里的`---`是yml的多文档模式
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
activate:
on-profile: dev #指明生效环境
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:environment.properties?group=order
activate:
on-profile: test #指明生效环境