在微服务架构中,会有很多服务(订单服务、用户服务、支付服务),服务之间需要互相调用。Nacos就是注册中心,所有服务启动后,都会主动把自己的IP、端口注册到Nacos中。当A服务需要调用B服务时,直接去Nacos查询B服务的地址即可,无需硬编码写死地址。
1. nacos安装与启动
进入nacos官网点击下载对应版本:
发布历史 | Nacos 官网
下载成功后解压:

在bin目录打开cmd窗口,输入启动指令:startup.cmd -m standalone
这里的m是model模式,standalone代表单机

启动成功后我们访问:localhost:8848/nacos 即可进入

2. 服务注册
2.1 配置nacos地址
上期我们已经创建了两个服务,我们给他们添加启动类运行起来:

添加springweb依赖

创建完启动类之后我们还需要在配置文件中配置nacos的地址:
spring.application.name=servers-order
server.port=8000
#配置nacos地址
spring.cloud.nacos.server-addr=127.0.0.1:8848
随后我们启动这个服务就可以在nacos的可视化界面看到这个服务,说明注册成功:

同理注册其它服务也是相同的方式
2.2 配置集群与分组
服务分组:用来对不同环境 / 业务线的服务做隔离,不同分组的服务默认无法互相发现。适用场景:开发 / 测试 / 环境隔离、多租户隔离
集群:用来对同一服务的实例按物理机房 / 区域划分,实现就近调用、跨机房容灾。
配置示例:
#分组名称用来对不同环境 / 业务线的服务做隔离,不同分组的服务默认无法互相发现
spring.cloud.nacos.discovery.group=em
# 配置当前实例所属的集群名称, 用来对同一服务的实例按物理机房 / 区域划分,实现就近调用、跨机房容灾
spring.cloud.nacos.discovery.cluster-name=ChenDu
这里我们再通过用不同端口运行多个服务的方式来模拟一下集群:

在运行配置中我们可以新建多个配置,勾选程序参数,配置运行端口就可以运行多个不同端口的相同服务:

可以看到实例数量发生改变。
添加多个集群:



3. 服务发现
想要获取到注册中心中已经注册的服务需要使用到@EnableDiscoveryCilent注解,写在需要服务发现的启动类上即可:

我们引入测试依赖在测试文件中进行测试:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
spring和nacos都提供了用于获取服务的api
java
package com.ting.order;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import java.util.List;
@SpringBootTest
public class DiscoveryTest {
//Spring提供,任何注册中心都可用
@Autowired
DiscoveryClient discoveryClient;
//nacos提供,仅使用nacos时能用
@Autowired
NacosServiceDiscovery nacosServiceDiscovery;
@Test
void testDiscoveryClient(){
for(String server: discoveryClient.getServices()) {
//获取已有服务名
System.out.println(server);
//获取服务ip端口号
List<ServiceInstance> instances = discoveryClient.getInstances(server);
for (ServiceInstance instance : instances) {
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
@Test
void nacosServiceDiscovery() throws NacosException {
for(String server: nacosServiceDiscovery.getServices()) {
//获取已有服务名
System.out.println(server);
//获取服务ip端口号
List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(server);
for (ServiceInstance instance : instances) {
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
}
可以看到输出内容是相同的:

这两个api了解即可,实际开发中远程调用有更好的方式。
4. 远程调用
接下来我们模拟一个订单模块调用商品模块的示例。
创建模块model用于存放实体:

创建实体:
java
package com.ting.order.bean;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class Order {
private Long id;
private BigDecimal totalAmount;
private Long userId;
private String nickName;
private List<Product> productList;
}
java
package com.ting.product.bean;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Product {
private Long id;
private BigDecimal price;
private String productName;
private int num;
}

在services中引入model依赖,否则service-order和service-product无法访问到:
XML
<dependency>
<groupId>com.ting.study</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
java
package com.ting.product.controller;
import com.ting.product.bean.Product;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class ProductController {
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long productId) {
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("66"));
product.setProductName("xiaomi" + productId);
product.setNum(2);
return product;
}
}
java
package com.ting.order.controller;
import java.math.BigDecimal;
import java.util.Arrays;
import com.ting.order.bean.Order;
import com.ting.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@Slf4j
@RestController
public class OrderController {
@Autowired
DiscoveryClient discoveryClient;
RestTemplate restTemplate = new RestTemplate();
@GetMapping("/order")
public Order createOrder( @RequestParam("userId") Long userId,
@RequestParam("productId") Long productId) {
Product product = getProductFromRemote(productId);
Order order = new Order();
order.setId(productId);
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("Ting");
order.setAddress("北京");
order.setProductList(Arrays.asList(product));
return order;
}
public Product getProductFromRemote(Long productId) {
//获取第一个实例
ServiceInstance serviceInstance = discoveryClient.getInstances("servers-product").get(0);
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//远程发送请求
return restTemplate.getForObject(url, Product.class);
}
}
这里我们模拟在订单模块中获取商品信息:


可以看到成功获取了商品信息。我们代码里写的是默认访问第一个实例,那如果我们关掉9000端口的服务再次运行:
可以看到访问了9001,因为9000端口的实例挂掉以后就不会出现在nacos中。