[版权申明] 非商业目的注明出处可自由转载
出自:shusheng007
概述
在上一篇 微服务之如何从零搭建(吹牛逼篇)中概述了当前Java生态中从零搭建微服务架构所涉及的一些技术和组件,接下来我会选择当前较为流行的组件逐步搭建一套演示微服务架构,请有兴趣的同学持续关注。
由于微服务架构中服务天生就是要随时准备着生死,这就使得服务注册与发现成为一个非常关键的功能。阿里开源的Nacos就是其中的佼佼者,详情请参考官方文档,我们今天就实际上手一下这个组件。
安装Nacos
我们知道Nacos会作为一个服务(集群)运行,然后我们的其他微服务与其通信来注册自己和发现其他服务,所以首先我们肯定需要将其运行起来。容器化的浪潮加速了云时代的带来,未来的世界必然是一个云的世界,我们这里也准备采用docker来部署nacos。
安装Docker环境
首先确认你的电脑是否已经安装了Docker。打开终端或命令行,输入如下命令
docker --version
如果可以正常显示出类似如下输出说明你本机安装了docker,否则请参考官方文档完成安装。
Docker version 20.10.8, build 3967b7d
运行nacos
Nacos 在Docker Hub的下载地址为:https://hub.docker.com/r/nacos/nacos-server
打开终端,输入如下命令
docker run --name nacos-quick -e MODE=standalone -p 8849:8848 -d nacos/nacos-server:2.0.2
docker会自动下载并运行nacos,上述命令中各参数的含义如下:
--name nacos-quick 设置容器的名称为nacos-quick
-e MODE=standalone 以单机模式启动
-p 8849:8848 外部端口为8849,内部端口为8848
-d 以detach模式启动,类似于使容器运行于后台,当你关闭终端后容器也会继续运行
nacos/nacos-server:2.0.2 nacos在docker hub的地址
接下来使用如下命令查看正在运行的container
docker ps
下面是我电脑的输出,可以发现nacos已经运行起来了。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6c32be7e7d33 nacos/nacos-server:2.0.2 "bin/docker-startup.…" 3 weeks ago Up 5 seconds 0.0.0.0:8849->8848/tcp, :::8849->8848/tcp nacos-quick
UI查看Nacos状态
Nacos还很贴心的为我们准备了一个web UI界面,通过此UI界面我们就可以清楚的看到当前注册服务的状态。当Nacos容器正常运行后,通过浏览器访问 http://127.0.0.1:8849/nacos
查看,初始用户名和密码都是nacos,一切正常的话会显示出下面的界面。
至此,Nacos服务已经成功运行起来了,接下来我们就使用SpringCloud开发几个微服务来使用nacos。
值得注意的是,nacos还可以做分布式配置中心,但以这种方式运行的nacos数据是存储在内存数据库的,当nacos Docker Container 删除后,你在里面配置的数据都会被删除,一般我们可以将数据保存到mysql里
在SpringCloud中使用
因为Nacos属于阿里巴巴开源产品,而阿里巴巴已经拥抱了SpringCloud,因此此组件也被集成到了SpringCloud中,因此我们可以很方便的在SpringCloud中使用它。我们准备新建两个微服务,一个将地址注册到Nacos,另一个通过Nacos获取其地址后发起调用。
我们新建一个名为master-microservice
的maven项目,其他微服务作为它的module。
新建一个Maven父项目
项目结构如下图,你可以在文末获取到项目的源代码
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>top.shusheng007</groupId>
<artifactId>master-microservice</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>goodsServer</module>
<module>orderServer</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
...
<spring-cloud.version>2020.0.4</spring-cloud.version>
<spring.cloud.alibaba.version>2021.1</spring.cloud.alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<!-- SpringCloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringCloud Alibaba依赖-->
<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>
</project>
我们可以看到,这个父项目引入了SpringCloud与SpringCloud Alibaba两个依赖族。注意他们之间的版本是有依赖的,不能瞎搞,例如本文中spring.cloud.alibaba 2021.1
适配了spring-cloud 2020.0.4
。至于版本对应详情,可参考SpringCloudAlibaba官网。
当引入了前两个依赖族后我们就可以通过下面的代码引入nacos相关的依赖了。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
创建一个订单服务(OrderService)
此微服务负责订单领域相关的业务,我们会将其注册到Nacos中去。那么具体如何操作呢?
第一步: 创建一个SpringBoot项目
此项目作为master-microservice
的Module,这样我们就可以使用其引入的各种依赖了,就不用在每个微服务中重复引入依赖。
pom.xml
中关键配置如下:
<parent>
<groupId>top.shusheng007</groupId>
<artifactId>master-microservice</artifactId>
<version>1.0</version>
</parent>
由于我们在其父项目中引入了nacos相关的依赖,所以加入此配置后就可以直接在OrderService项目中使用了。
此服务中提供一了个支付订单的API,后面我们使用GoodsService微服务来消费这个接口
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderService orderService;
...
@PostMapping(value = "/payment")
public OrderDetail payment(@RequestBody PaymentReq paymentReq){
return orderService.paymentOrder(paymentReq.getOrderId());
}
}
第二步: 配置nacos连接信息
在application.yaml
文件中配置OrderService
server:
port: 9081
spring:
application:
name: order-service
# nacos 相关配置
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8849
可以看到,我们配置了OrderService的名称、端口号以及nacos的地址
第三步: 启用nacos
由于SpringCloud集成了Nacos,所以使得启用Nacos异常简单,只需要一个注解
在OrderServerApplication
上添加@EnableDiscoveryClient
即可
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
测试结果
通过以上3步就完成了OrderService的创建,让我们来测试一下是否一切正常吧。
- 启动前状态
通过Nacos UI界面( http://127.0.0.1:8849/nacos
)看一下启动前的状态,可以发现没有服务注册
- 启动后状态
启动OrderService,然后刷新一下,可以发现已经有一个order-service实例启动了。
当前微服务架构为了实现服务的高可用性及伸缩性,以集群部署服务已经成为常态,所以这里我们也再启动几个实例看看效果。那么我们如何在Intelij中启动多个实例呢?
如何在Intelij里启动同一个应用的多个实例?
- 打开Edit Configurations 的弹窗
- 选中你要启动的应用,然后勾选右边的 Allow parallel run选项,
- 在启动参数中配置你要启动实例的端口,例如此处的
-Dserver.port=9082
- 点击apply按钮,点击ok按钮
-
点击运行按钮,一个新实例就会启动
-
启动第二个时,还是进入 Edit Configuration 弹窗,修改在启动参数中将端口修改为另一个值,确定后再次点击运行按钮,如下图所示。
当我们按照上面的方法启动第二个实例后,从nacos UI上查看可以发现确实多了一个实例
创建一个商品服务(GoodsService)
上面我们已经创建并启动了OrderService服务的两个实例,接下来让我们再创建一个商品服务来消费它们。
第一步: 创建一个SpringBoot项目
负责商品领域相关的业务,结构与OrderService类似,不再赘述。
第二步: 配置服务及Nacos
在application.yaml
中配置服务的名称、端口号以及nacos的地址。
server:
port: 9091
spring:
application:
name: goods-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8849
在GoodsServerApplication
上加上@EnableDiscoveryClient
注解
第三步: 消费OrderService服务
由于OrderService启动了多个对等实例(集群),所以GoodsService需要使用负载均衡来消费这些实例。
- 首先引入负载均衡依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 配置WebClient
此处我们使用了WebClient
而非RestTemplate
来发起网络请求,所以要给WebClient配置负责均衡,这块在微服务中一般使用OpenFein
来发起网络调用。
@Configuration
public class LoadBalanceConfiguration {
@LoadBalanced
@Bean
public WebClient.Builder loadBalancedWebClientBuilder(){
return WebClient.builder();
}
}
最关键的就是属性:@LoadBalanced
接下来我们就可以使用WebClient来对OrderService发起请求了,如下代码所示。注意那个调用路径(URL): http://order-service/order/payment
,要使用目标服务的服务名称,而非域名或者IP地址。
@Service
public class GoodsServiceImpl implements GoodsService {
@Override
public Mono<String> buyBook(String bookId) {
return webClientBuilder.build().post()
.uri("http://order-service/order/payment")
.body(Mono.just(new PaymentReq(bookId)), PaymentReq.class)
.retrieve()
.bodyToMono(String.class);
}
}
@RestController()
@RequestMapping("/goods")
public class GoodsController {
private final GoodsService goodsService;
...
//调用order-service微服务,通过服务发现获取到地址信息后调用服务实例
@GetMapping("/buyBook/{bookId}")
public Mono<String> buyBook(@PathVariable String bookId){
return goodsService.buyBook(bookId);
}
}
调用
使用PostMan或者浏览器向http://127.0.0.1:9091/goods/buyBook/1
多次发起Get
请求
输出如下日志:
[goods-service,,] 51158 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [27f709e5-3, L:/0:0:0:0:0:0:0:1:9091 - R:/0:0:0:0:0:0:0:1:53608] HTTP GET "/goods/buyBook/1"
...
[7d963e3a] HTTP POST http://10.68.180.105:9082/order/payment
...
[goods-service,,] 51158 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [27f709e5-4, L:/0:0:0:0:0:0:0:1:9091 - R:/0:0:0:0:0:0:0:1:53608] HTTP GET "/goods/buyBook/1"
...
[25d4a77d] HTTP POST http://10.68.180.105:9081/order/payment
...
从日志可见,9081与9082两个服务实例交替被调用。为什么是交替被调用呢?这个是由客户端的负载均衡算法决定,由于SpringCloud默认使用round-robin
算法,所以这里就是交替进行。
总结
自此,我们已经成功构建了两个微服务,并使用Nacos作为服务的注册与发现组件使他们进行了通信,其中涉及到了负载均衡的使用。但在本文我们只是对其浅尝辄止,由于其在现在软件架构中的重要性,我会继续完善我们这个demo工程中的负载均衡部分,并使用单独文章做介绍,请持续关注。
源码
源代码GitHub地址:master-microservice
文章评论