前言
以下类容来自书籍《Spring Cloud Alibaba 微服务原理与实战》
一、基本使用
这里nacos的安装就不写了
服务端
依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.gupaoedu.book.nacos</groupId>
<artifactId>spring-cloud-nacos-sample-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置文件
spring.application.name=spring-cloud-nacos-sample
dubbo.scan.base-packages=com.book.nacos.bootstrap
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.registry.address=spring-cloud://localhost
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
实现类
需要注意的是这里的@Service是dubbo包下的。
import com.book.nacos.IHelloService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class HelloServiceImpl implements IHelloService {
@Override
public String sayHello(String s) {
return "Hello World:"+s;
}
}
服务启动后,查看nacos上的服务列表,如图所示
点击详情可查看元数据
但是在这里并没有看到暴露出的接口,此时转到配置列表中可看到mapping-com.book.nacos.IHelloService
客户端
依赖
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.gupaoedu.book.nacos</groupId>
<artifactId>spring-cloud-nacos-sample-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置文件
dubbo.cloud.subscribed-services=spring-cloud-nacos-sample
dubbo.scan.base-packages=com.book.nacos.bootstrap
spring.application.name=spring-cloud-nacos-consumer
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
调用服务端
import com.book.nacos.IHelloService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Reference
private IHelloService helloService;
@GetMapping("/say")
public String sayHello(){
return helloService.sayHello("springCloud-Dubbo");
}
}
服务启动后可在nacos中查看服务
此时请求http://127.0.0.1:8080/say,可得到服务端返回给客户端的数据
二、原理浅析
架构图
从架构图中可以看到
- 服务提供者在启动时向nacos发起服务注册,并建立心跳。
- 服务消费者查询服务提供方实例,并每10秒拉取一次数据,而nacos检测到服务提供者异常时,会向消费者推送更新的列表。
源码
服务注册
直接定位到org.springframework.cloud.client.serviceregistry,AbstractAutoServiceRegistration,在这里有一个事件监听。
public void onApplicationEvent(WebServerInitializedEvent event) {
this.bind(event);
}
监听WebServerInitializedEvent事件,即Webserver初始化完成之后,调用this.bind(event)。
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
}
接着往下找,会发现最终会调用com.alibaba.cloud.nacos.registry.NacosServiceRegistry下的register(Registration registration)方法。
Dubbo在集成nacos时,在com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationNonWebApplicationAutoConfiguration中也有一个监听刷新上下文之后的事件,他最终也会调用com.alibaba.cloud.nacos.registry.NacosServiceRegistry下的register(Registration registration)方法。
@EventListener({ApplicationStartedEvent.class})
public void onApplicationStarted() {
this.setServerPort();
this.register();
}
private void register() {
if (!this.registered) {
this.serviceRegistry.register(this.registration);
this.registered = true;
}
}
那么下面就来看看这个register(Registration registration)方法
在此方法中直接看namingService.registerInstance(serviceId, group, instance),这里做了两件事,建立心跳和服务注册。
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
long instanceInterval = instance.getInstanceHeartBeatInterval();
beatInfo.setPeriod(instanceInterval == 0L ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo); //建立心跳
}
this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance); //服务注册
}
这里的心跳机制是通过schedule定时发送心跳包给nacos服务端,nacos服务端根据心跳包不断更新服务状态。
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
this.dom2Beat.put(key, beatInfo);
this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}
而服务注册阶段最终会调用nacos提供的Open API,/nacos/v1/ns/instance,这里的逻辑在nacos的服务端,主要将客户端(业务服务提供者)缓存到内存,并建立心跳监测机制,通过定时任务不断检测客户端发送过来的心跳包,如果超时则改变服务状态。