随着SOA生态的不断完善以及微服务架构思想的落地,服务与服务之间的远程通信需求更多来自服务的解耦。同时,业务规模的不断增长会使得微服务数量增加,那么问题也就随之产生了,比如:
Apache Dubbo是一个分布式服务框架,主要实现多个系统之间的高性能、透明化调用,简单来说它就是一个RPC框架,但是和普通的RPC框架不同的是,它提供了服务治理功能,比如服务注册、监控、路由、容错等。
ZooKeeper是一个高性能的分布式协调中间件,所谓的分布式协调中间件的作用类似于多线程环境下通过并发工具包来协调线程的访问控制,只是分布式协调中间件主要解决分布式环境中各个服务进程的访问控制问题,比如顺序控制(分布式锁)。
所以zookeeper不是注册中心,只是基于zookeeper本身的特性可以实现注册中心这个场景而已。
[zk: localhost:2181(CONNECTED) 14] stat /locks/lock1
cZxid = 0x3
ctime = Sun Apr 18 10:42:23 CST 2021
mZxid = 0x4
mtime = Sun Apr 18 11:10:43 CST 2021
pZxid = 0x3
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
zookeeper中的znode在被创建的时候,需要指定节点的类型,节点类型分为:
需要注意的是,在同一层级目录下,节点的名称必须是唯一的,就像我们在同一个目录下不能创建两个有相同名字的文件一样。
zookeeper提供了一种针对znode的订阅/通知机制,也就是当znode节点状态发生变化时或者zookeeper客户端连接状态发生变化时,会触发事件通知。这个机制在服务注册与发现中,针对服务调用者及时感知服务提供者的变化提供了非常好的解决方案。
在zookeeper提供的java api中,提供了三种机制来针对znode进行注册监听,分别是:
注意:watcher事件的触发是一次性的(也就是读清除的),比如客户端通过getData(’/node’,true)注册监听,如果/node节点发生数据修改,那么该客户就会收到一个修改通知,但是/node再次发生变化时,客户端无法收到Watcher事件,为了解决这个问题,客户端必须在收到的事件回调中再次注册事件。
锁从范围级别来说主要分成两种,线程级别的和进程级别的。
比如Synchronize或者Lock,它们主要用于解决多线程环境下(单机部署)共享资源的数据安全性问题,但是它们所处理的范围是线程级别的。
在分布式架构中多个进程对同一个共享资源进行访问时,也存在数据安全性问题,因此也需要使用锁的形式来解决这类问题,而解决分布式环境下多进程对于共享资源访问带来的安全性问题的方案就是使用分布式锁。锁的本质是排他的,也就是避免同一时刻多个进程同时访问某一个共享资源。
如果使用zookeeper实现分布式锁达到排他的目的,只需要用到节点的特性:临时节点及同级节点的唯一性
当 /lock 节点被删除之后,zookeeper服务器会通知所要监听了 /Exclusive_Locks 子节点变化的客户端。这些客户端受到通知后,再次发起创建 /lock 节点的操作来获得排他锁。
Master选举是分布式系统中非常常见的场景,在分布式架构中,为了保证服务的可用性,通常会采用集群模式,也就是当其中一个机器宕机后,集群中的其他节点会接替节点继续工作。在这种场景中,就需要从集群中选举一个节点作为Master节点,剩余的节点都作为备份节点随时待命。当原有的Master节点出现故障之后,还需要从集群中的其他备份节点选举一个节点作为Master节点继续提供服务。
zookeeper就可以帮助集群中的节点实现Master选举。具体而言,zookeeper中有两种方式来实现Master选举这一场景:
第三方软件的主要功能其实就是服务注册与发现,如下图,可以看到引人服务注册中心后服务调用者服务提供者之间的访问变化。Apache Dubbo 支持多种注册中心,比如zookeeper、Nacos、Redis等。在开源版本中,官方推荐使用的注册中心是zookeeper,所以使用Apache Dubbo的功能大部分都使用zookeeper来实现服务注册与发现。
Dubbo服务注册到ZooKeeper上之后,可以在zookeeper服务器上看到上图所示的树形数据结构。
Dubbo Spring Cloud是Spring Cloud Alibaba的核心组件,它构建在原生的Spring Cloud标准之上,不仅覆盖了Spring Cloud原生特性,还提供了更加稳定和成熟的实现。
创建一个普通的Maven工程,并在该工程下创建两个模块:sampleapi和samplecloudprovider。其中sampleapi是一个普通的Maven工程,samplecloudprovider是一个Spring boot工程。
public interface IHelloService {
String sayHello(String name);
}
<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.0modelVersion>
<groupId>com.practice.springcloud.dubbogroupId>
<artifactId>samplecloudproviderartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>samplecloudprovidername>
<description>Demo project for Spring Bootdescription>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<spring-cloud.version>Greenwich.SR2spring-cloud.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.1.11.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.1.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starterartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
<dependency>
<groupId>com.practice.springbootgroupId>
<artifactId>sampleapiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zookeeper-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
<configuration>
<includeSystemScope>trueincludeSystemScope>
<mainClass>com.practice.springcloud.dubbo.samplecloudprovider.SamplecloudproviderApplicationmainClass>
configuration>
plugin>
plugins>
build>
project>
依赖说明如下:
@Service
public class HelloServiceImpl implements IHelloService {
@Value("${spring.application.name}")
private String serviceName;
@Override
public String sayHello(String name) {
return String.format("[%s]: Hello, %s", serviceName, name);
}
}
dubbo.protocol.port=20880
dubbo.protocol.name=dubbo
spring.application.name=spring-cloud-dubbo-provider
# 表示服务是否注册到注册中心
spring.cloud.zookeeper.discovery.register=true
# 表示连接zookeeper服务器的地址
spring.cloud.zookeeper.connect-string=localhost:2181
@DubboComponentScan
@SpringBootApplication
public class SamplecloudproviderApplication {
public static void main(String[] args) {
SpringApplication.run(SamplecloudproviderApplication.class, args);
}
}
@DubboComponentScan扫描当前注解所在的包及其子包路径下的@org.apache.dubbo.config.annotation.Service注解,实现服务的发布。发布完成后,就可以在zookeeper服务器上看到一个 /services/${project-name} 节点,这个节点保存了服务提供方相关的地址信息。
[zk: localhost:2181(CONNECTED) 1] ls -R /services
/services
/services/spring-cloud-dubbo-provider
/services/spring-cloud-dubbo-provider/e86f28c5-1109-4351-a9a5-3d1eb1483019
[zk: localhost:2181(CONNECTED) 2] get /services/spring-cloud-dubbo-provider/e86f28c5-1109-4351-a9a5-3d1eb1483019
{"name":"spring-cloud-dubbo-provider","id":"e86f28c5-1109-4351-a9a5-3d1eb1483019","address":"192.168.174.128","port":20880,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"spring-cloud-dubbo-provider","metadata":{"dubbo.metadata-service.urls":"[ \"dubbo://192.168.174.128:20880/com.alibaba.cloud.dubbo.service.DubboMetadataService?anyhost=true&application=spring-cloud-dubbo-provider&bind.ip=192.168.174.128&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=spring-cloud-dubbo-provider&interface=com.alibaba.cloud.dubbo.service.DubboMetadataService&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=13713&qos.enable=false®ister=true&release=2.7.3&revision=2.1.1.RELEASE&side=provider×tamp=1618841633590&version=1.0.0\" ]","dubbo.protocols.dubbo.port":"20880"}},"registrationTimeUTC":1618841630936,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
创建一个名为springcloudconsumer的spring boot项目,就可以实现dubbo服务调用了。
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.practice.springcloudgroupId>
<artifactId>springcloudconsumerartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudconsumername>
<description>springcloudconsumerdescription>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR1spring-cloud.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starterartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
<version>2.1.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zookeeper-discoveryartifactId>
<version>2.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.practice.springbootgroupId>
<artifactId>sampleapiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
<configuration>
<includeSystemScope>trueincludeSystemScope>
<mainClass>com.practice.springcloud.consumer.ConsumerApplicationmainClass>
configuration>
plugin>
plugins>
build>
project>
dubbo.cloud.subscribed-services=spring-cloud-dubbo-provider
server.port=8081
spring.application.name=spring-cloud-dubbo-consumer
# 表示当前服务不需要注册到zookeeper服务器上,默认为true
spring.cloud.zookeeper.discovery.register=false
spring.cloud.zookeeper.connect-string=localhost:2181
@RestController
public class HelloController {
@Reference
private IHelloService helloService;
@GetMapping("/say")
public String sayHello() {
return helloService.sayHello("Test Dubbo Function");
}
}
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
[root@localhost ~]$ curl http://127.0.0.1:8081/say
[spring-cloud-dubbo-provider]: Hello, Test Dubbo Function
Apache Dubbo更像一个生态,它提供了很多比较主流框架的集成,比如:
在分布式架构的网络通信中,容错能力是必须要具备的。
什么是容错呢?从字面来看,就是服务容忍错误的能力。我们都知道网络通信中会存在很多不确定的因素导致请求失败,比如网络延迟、网络中断、服务异常等。当服务调用者(消费者)调用服务提供者的接口时,如果因为上述原因出现请求失败,那对于服务调用者来说,需要一种机制来应对。Dubbo 中提供了集群容错的机制来优雅的处理这种错误。
Dubbo默认提供了6种容错模式,默认为Failover Cluster。
如果这6中模式不能满足你的实际需求,还可以自行扩展。这也是Dubbo的强大之处,几乎所有的功能都提供了插拔式的扩展。
配置非常简单,只需要在指定服务的@Service注解上增加一个参数即可。在@Service注解中增加cluster="failfast"参数,表示当前服务的容错方式是快速失败。
@Service(cluster = "failfast")
public class HelloServiceImpl implements IHelloService {
@Value("${spring.application.name}")
private String serviceName;
@Override
public String sayHello(String name) {
return String.format("[%s]: Hello, %s", serviceName, name);
}
}
在实际应用中,查询语句容错策略建议使用默认的Failover Cluster,而增删改操作建议使用Failfast Cluster或者使用Failover Cluster(retries=0)策略,防止出现数据重复添加等其他问题。
建议在设计接口的时候把查询接口方法单独做出一个接口提供查询。
@Service(cluster = "failfast", loadbalance = "roundrobin")
服务降级是一种系统保护策略,当服务器访问压力较大时,可以根据当前业务情况对不重要的服务进行降级,以保证核心服务的正常运行。所谓的降级,就是把一些非必要的功能在流量较大的时间段暂时关闭,比如在双11大促时,淘宝会把查看历史订单、商品评论等功能关闭,从而释放更多的资源来保障大部分用户能够正常完成交易。
人工降级一般具有一定的前置性,比如在电商大促之前,暂时关闭某些非核心服务,如评价、推荐等。
自动降级更多的来自于系统出现某些异常的时候自动触发的“兜底策略”,比如:
Dubbo提供了一种Mock配置来实现服务降级,也就是说当服务提供方出现网络异常无法访问时,客户端不抛出异常,而是通过降级配置返回兜底数据,步骤如下:
public class MockHelloService implements IHelloService {
@Override
public String sayHello(String name) {
return "Sorry, 服务无法访问,返回降级数据";
}
}
@RestController
public class HelloController {
@Reference(mock = "com.practice.springcloud.consumer.service.MockHelloService", cluster = "failfast")
private IHelloService helloService;
@GetMapping("/say")
public String sayHello() {
return helloService.sayHello("Test Dubbo Function");
}
}
[root@localhost]# curl http://localhost:8090/say
Sorry, 服务无法访问,返回降级数据
主机绑定表示的是Dubbo服务对外发布的ip地址。
默认情况下Dubbo会按照以下顺序(优先级由高到低,只要其中一步获取到ip地址就作为对外发布地址)来查找并绑定主机IP地址:
使用默认的主机绑定规则,可能会存在获取的IP地址不正确的情况。
因为Dubbo检查本地IP地址的策略是先调用LocalHost.getHostAddress(),这个方法的原理是通过获取本机的hostname映射IP地址,如果它指向的是一个错误的IP地址,那么这个错误的地址将会作为服务发布的地址注册到zookeeper节点上,虽然dubbo服务能够正常启动,但是服务消费者却无法正常调用。按照Dubbo中IP地址的查找规则,如果遇到发布的ip地址不正确的情况,可以实现下面多种方案来解决: