引例
假设现在有这么一个需求,需要在已有的服务上新增一个功能,但是该功能非常的消耗资源,甚至会影响已有的服务,导致已有服务的其他功能也不可用。
那么,有一个解决办法是,将需要新增的功能,抽成一个独立的服务,并部署在独立的机器上,已有的服务,只负责转发。
这便是SOA思想的体现。SOA(Service Oriented Architecture),面向服务的架构模式。
已有的服务去调用新服务的过程,就是RPC(Remote Procedure Call),远程过程调用。
根据上述设计,我们需要把新服务的地址配置在已有的服务上。但如果我们服务很多的话,这个会很繁琐,而且会导致耦合度很高。
我们如何更好的管理这些服务呢?
这就是本章要讨论的内容。
Dubbo
简介
Dubbo,一个RPC框架,可以和Spring框架无缝集成。
根据上文的讨论,我们知道,RPC并不是一个具体的技术,指的就是"远程调用过程"。
Dubbo就是一个RPC框架。
Dubbo官网:https://dubbo.apache.org
Dubbo的三大核心能力:
面向接口的远程方法调用
智能容错和负载均衡
服务自动注册和发现
Dubbo最初是阿里巴巴的,在2017年,正式移交给了Apache。
架构
节点说明:
Provider:暴露服务的 服务生产者 。
Consumer:调用远程服务的 服务消费者 。
Registry:服务注册与发现的 注册中心 。
Monitor:统计服务的调用次数和调用时间的 监控中心 。
Container:服务运行容器。
过程说明:
0:服务容器启动,加载服务生产者。
1:服务生产者在启动时,向注册中心注册自己提供的服务。
2:服务消费者在启动时,向注册中心订阅自己所需的服务。
3:注册中心返回服务生产者的地址列表给服务消费者,如果有变更,注册中心将基于长连接推送变更数据给服务消费者。
4:服务消费者从服务生产者地址列表中,基于软负载均衡算法,选一台服务生产者进行调用,如果调用失败,再选另一台服务生产者调用。
5:服务消费者和服务生产者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Zookeeper
通过上文的Dubbo架构图可以看到,Registry(服务注册中心)在其中起着至关重要的作用。
Dubbo官方推荐使用Zookeeper作为服务注册中心。
(Kafka的注册中心,一般也采用Zookeeper。关于Kafka,我们在《消息队列(Kafka RabbitMQ)》 有更详细的讨论。)
简介
Zookeeper,注册中心,负责服务地址的注册与查找,相当于一个目录服务,服务生产者和服务消费者只在启动时与注册中心交互,注册中心不转发请求。
官网:https://zookeeper.apache.org
如图,Zookeeper采用一种树型的目录服务。
服务提供者(Provider)启动时,向/dubbo/com.foo.BarService/providers
目录下写入自己的URL地址。
服务消费者(Consumer)启动时,订阅/dubbo/com.foo.BarService/providers
目录下的服务提供者URL地址。并向/dubbo/com.foo.BarService/consumers
目录下写入自己的URL地址。
监控中心(Monitor)启动时,订阅/dubbo/com.foo.BarService
目录下的所有服务提供者和服务消费者URL地址。
安装
安装JDK
Zookeeper需要运行在JDK上。
下载并解压
可以通过官网下载Zookeeper,或者直接在Linux服务器上通过wget的方式下载。
1 wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz
解压:
1 tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz
配置
在目录apache-zookeeper-3.7.1-bin
下创建data
目录。
复制conf
目录下的zoo_sample.cfg
,新文件名为zoo.cfg
。
修改zoo.cfg
的dataDir属性1 dataDir =/Users/kaka/Documents/apache-zookeeper-3.7.1-bin/data
如果是Windows机器,需要注意转义字符。如,需要写成如下的形式1 dataDir =D:\\apache-zookeeper-3.7.1-bin\\data
或者1 dataDir =D:/apache-zookeeper-3.7.1-bin/data
(默认端口是2181,可以通过"clientPort"进行修改。)
有些资料会建议修改bin目录下的"zkEnv"。 例如,对于zkEnv.cmd
将
1 set ZOOCFG=%ZOOCFGDIR%\zoo.cfg
改为
1 set ZOOCFG=%ZOOCFGDIR%\zoo_sample.cfg
实际上,这绝不是一个好办法。 更好的方法是复制zoo_sample.cfg,新文件名为zoo.cfg。
启动和停止
在Zookeeper的bin目录
启动:./zkServer.sh start
停止:./zkServer.sh stop
查看:./zkServer.sh status
集群
也正因为Zookeeper如此重要,所以在实际中,Zookeeper不会是单台,通常是集群。
搭建集群
整体过程和单台差不多,多了两步:
配置每台服务的ID
配置集群服务器IP列表
配置每台服务的ID
在配置文件dataDir
定义的目录(在本文是data
目录)下创建一个myid
文件,内容分别是1
、2
、3
,记录每个服务器的ID。
配置集群服务器IP列表
在zoo.cfg
配置客户端集群服务器IP列表。
1 2 3 server.1 =172.23.2.79:2287:3387 server.2 =172.23.2.80:2287:3387 server.3 =172.23.2.81:2287:3387
格式:server.N =YYY:A:B
N
:服务器编号
YYY
:服务器地址
A
:flower和leader的通信端口,即服务端内部通信的端口(默认2287)
B
:选举端口(默认是3387)
启动集群
启动集群就是分别启动每个实例。
启动后我们查询一下每个实例的运行状态。
示例代码:
运行结果:
1 2 3 4 ZooKeeper JMX enabled by default Using config: /root/apache-zookeeper-3.7.1-bin/bin/../conf/zoo.cfg Client port found: 2181. Client address: localhost. Client SSL: false. Mode: leader
示例代码:
运行结果:
1 2 3 4 ZooKeeper JMX enabled by default Using config: /root/apache-zookeeper-3.7.1-bin/bin/../conf/zoo.cfg Client port found: 2181. Client address: localhost. Client SSL: false. Mode: follower
Mode为follower表示是 跟随者 (从)
Mode为leader表示是 领导者 (从)
集群异常
三台服务,如果只挂了一台从服务,集群正常。
三台服务,如果挂了两台从服务,主服务也无法运行,因为可运行的机器没有超过集群总数量的半数。
当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader。
(具体实验过程略)
问题处理
名字带有"bin"的包
3.5版本以后的包分成了两种,我们需要使用名字带有"bin"的包,例如:apache-zookeeper-3.7.1-bin.tar.gz
。
问题排查
如果启动失败,我们可以通过命令./zkServer.sh start-foreground
,查看日志的报错详细信息,这样有助于我们判断错误的原因。
示例代码:
1 ./zkServer.sh start-foreground
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 【部分运行结果略】 2022-12-22 08:37:06,839 [myid:] - ERROR [main:ZooKeeperServerMain@86] - Unable to start AdminServer, exiting abnormally org.apache.zookeeper.server.admin.AdminServer$AdminServerException: Problem starting AdminServer on address 0.0.0.0, port 8080 and command URL /commands at org.apache.zookeeper.server.admin.JettyAdminServer.start(JettyAdminServer.java:188) at org.apache.zookeeper.server.ZooKeeperServerMain.runFromConfig(ZooKeeperServerMain.java:155) at org.apache.zookeeper.server.ZooKeeperServerMain.initializeAndRun(ZooKeeperServerMain.java:113) at org.apache.zookeeper.server.ZooKeeperServerMain.main(ZooKeeperServerMain.java:68) at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:141) at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:91) Caused by: java.io.IOException: Failed to bind to /0.0.0.0:8080 at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:349) at org.eclipse.jetty.server.ServerConnector.open(ServerConnector.java:310) at org.eclipse.jetty.server.AbstractNetworkConnector.doStart(AbstractNetworkConnector.java:80) at org.eclipse.jetty.server.ServerConnector.doStart(ServerConnector.java:234) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73) at org.eclipse.jetty.server.Server.doStart(Server.java:401) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73) at org.apache.zookeeper.server.admin.JettyAdminServer.start(JettyAdminServer.java:179) ... 5 more Caused by: java.net.BindException: Address already in use at sun.nio.ch.Net.bind0(Native Method) at sun.nio.ch.Net.bind(Net.java:438) at sun.nio.ch.Net.bind(Net.java:430) at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:225) at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74) at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:344) ... 12 more Unable to start AdminServer, exiting abnormally 2022-12-22 08:37:06,840 [myid:] - INFO [main:ZKAuditProvider@42] - ZooKeeper audit is disabled. 2022-12-22 08:37:06,841 [myid:] - ERROR [main:ServiceUtils@48] - Exiting JVM with code 4
这是因为3.5版本以后,都会自动把8080端口给占用了,所以启动不成功。
可以为"admin"指定端口,在zoo.cfg添加admin.serverPort=10086
。
可以直接禁用"admin",在zoo.cfg添加admin.enableServer=false
。
在Windows下
在Windows下,通过命令zkServer.cmd start
可能会报错
1 java.lang.NumberFormatException: For input string:
原因暂时未知,解决方法为修改命令为zkServer.cmd
,不加 start
。
入门案例
以《18.SSM》 的项目为例,将其改造成Dubbo形式的。
Service接口
我们将Service接口层抽成一个单独的jar包,因为之后我们在Provider和Consumer都会用到该接口。
抽成单独的jar包,这样我们在Provider和Consumer只需要引入jar包,而不必重复编码。
具体抽取步骤略,可以参考《18.SSM》 的"模块化"部分。
Provider
pom.xml
需要引入Dubbo的包:org.apache.dubbo.dubbo
。
以及Zookeeper的客户端:
org.apache.curator.curator-framework
org.apache.curator.curator-recipes
pom.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo</artifactId > <version > 2.7.19</version > </dependency > <dependency > <groupId > org.apache.curator</groupId > <artifactId > curator-framework</artifactId > <version > 5.4.0</version > </dependency > <dependency > <groupId > org.apache.curator</groupId > <artifactId > curator-recipes</artifactId > <version > 5.4.0</version > </dependency >
在本文中,我们需要将服务生产者打成一个war包,所以
1 <packaging > war</packaging >
DubboConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.kakawanyifan.config;import org.apache.dubbo.config.ApplicationConfig;import org.apache.dubbo.config.ProtocolConfig;import org.apache.dubbo.config.RegistryConfig;import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;import org.springframework.context.annotation.Bean;@DubboComponentScan ("com.kakawanyifan.service.impl" )public class DubboConfig { @Bean public ApplicationConfig applicationConfig () { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("ssm-provider" ); return applicationConfig; } @Bean public RegistryConfig registryConfig () { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" ); return registryConfig; } @Bean public ProtocolConfig protocolConfig () { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo" ); protocolConfig.setPort(20800 ); return protocolConfig; } }
如果是Zookeeper不是集群,地址形如zookeeper://172.23.2.79:2181
,即不配置备节点。
我们还需要在SpringConfig
中ImportDubboConfig
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.kakawanyifan.config;import org.springframework.context.annotation.*;import org.springframework.stereotype.Controller;import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration @ComponentScan (value = "com.kakawanyifan" , excludeFilters = @ComponentScan .Filter( type = FilterType.ANNOTATION, classes = Controller.class )) @PropertySource("classpath:jdbc.properties") @Import ({JdbcConfig.class , MyBatisConfig .class , DubboConfig .class }) @EnableTransactionManagement public class SpringConfig {}
@DubboService
将Service的实现类,抽出来。注意,这里用的注解是@DubboService
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.kakawanyifan.service.impl;import com.kakawanyifan.dao.BookDao;import com.kakawanyifan.pojo.Book;import com.kakawanyifan.service.BookService;import org.apache.dubbo.config.annotation.DubboService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.transaction.annotation.Transactional;import java.util.List;@DubboService @Transactional public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; public boolean save (Book book) { bookDao.save(book); return true ; } public boolean update (Book book) { bookDao.update(book); return true ; } public boolean delete (Integer id) { bookDao.delete(id); return true ; } public Book getById (Integer id) { return bookDao.getById(id); } public List<Book> getAll () { return bookDao.getAll(); } }
XML方式
web.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:applicationContext.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > </web-app >
applicationContext.xml
:
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
http://dubbo.apache.org/schema/dubbo
https://dubbo.apache.org/schema/dubbo/dubbo.xsd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:tx ="http://www.springframework.org/schema/tx" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:dubbo ="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://dubbo.apache.org/schema/dubbo https://dubbo.apache.org/schema/dubbo/dubbo.xsd" > <context:component-scan base-package ="com.kakawanyifan" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan > <context:property-placeholder location ="classpath:jdbc.properties" /> <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean > <bean id ="SqlSessionFactoryBean" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" > </property > </bean > <bean id ="mapperScannerConfigurer" class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.kakawanyifan.dao" > </property > </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" > </property > </bean > <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="*" /> </tx:attributes > </tx:advice > <aop:config > <aop:pointcut id ="myPointcut" expression ="execution(* com.kakawanyifan.service.*.*(..))" /> <aop:advisor advice-ref ="txAdvice" pointcut-ref ="myPointcut" > </aop:advisor > </aop:config > <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="*" /> </tx:attributes > </tx:advice > <dubbo:application name ="ssm-provider" /> <dubbo:registry address ="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" /> <dubbo:protocol name ="dubbo" port ="20880" /> <dubbo:annotation package ="com.kakawanyifan.service.impl" /> </beans >
内容很多,重点关注:
1 2 3 4 5 6 7 <dubbo:application name ="ssm-provider" /> <dubbo:registry address ="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" /> <dubbo:protocol name ="dubbo" port ="20880" /> <dubbo:annotation package ="com.kakawanyifan.service.impl" />
Consumer
pom.xml
需要引入的jar包和Provider一样,打包方式也一样。
DubboConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.kakawanyifan.config;import org.apache.dubbo.config.ApplicationConfig;import org.apache.dubbo.config.RegistryConfig;import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @DubboComponentScan ("com.kakawanyifan.controller" )public class DubboConfig { @Bean public ApplicationConfig applicationConfig () { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("ssm-consumer" ); return applicationConfig; } @Bean public RegistryConfig registryConfig () { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" ); return registryConfig; } }
同样,我们需要在SpringMvcConfig
中ImportDubboConfig
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.kakawanyifan.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration @ComponentScan (value = {"com.kakawanyifan.controller" })@Import (DubboConfig.class ) @EnableWebMvc public class SpringMvcConfig {}
@DubboReference
将Controller抽出来。注意,这里用的注解是@DubboReference
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.kakawanyifan.controller;import com.kakawanyifan.pojo.Book;import com.kakawanyifan.service.BookService;import org.apache.dubbo.config.annotation.DubboReference;import org.springframework.web.bind.annotation.*;import java.util.List;@RestController @RequestMapping ("/books" )public class BookController { @DubboReference private BookService bookService; @PostMapping public boolean save (@RequestBody Book book) { return bookService.save(book); } @PutMapping public boolean update (@RequestBody Book book) { return bookService.update(book); } @DeleteMapping ("/{id}" ) public boolean delete (@PathVariable Integer id) { return bookService.delete(id); } @GetMapping ("/{id}" ) public Book getById (@PathVariable Integer id) { return bookService.getById(id); } @GetMapping public List<Book> getAll () { return bookService.getAll(); } }
XML方式
spring-mvc.xml:
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
http://dubbo.apache.org/schema/dubbo
https://dubbo.apache.org/schema/dubbo/dubbo.xsd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc ="http://www.springframework.org/schema/mvc" xmlns:context ="http://www.springframework.org/schema/context" xmlns:dubbo ="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://dubbo.apache.org/schema/dubbo https://dubbo.apache.org/schema/dubbo/dubbo.xsd" > <context:component-scan base-package ="com.kakawanyifan.controller" /> <mvc:annotation-driven /> <dubbo:application name ="ssm-consumer" /> <dubbo:registry address ="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" /> <dubbo:annotation package ="com.kakawanyifan.controller" /> </beans >
配置
启动时检查
1 <dubbo:consumer check ="false" />
该配置需要配置在服务消费者一方,Dubbo会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成。默认为true
。
包扫描
在上文,我们使用的是包扫描的方式。
1 @DubboComponentScan ("com.kakawanyifan.service.impl" )
1 <dubbo:annotation package ="com.kakawanyifan.service.impl" />
如果不使用包扫描,也可以通过如下配置的方式来发布服务:
1 2 <bean id ="helloService" class ="com.kakawanyifan.service.impl.HelloServiceImpl" /> <dubbo:service interface ="com.kakawanyifan.service.HelloService" ref ="helloService" />
1 <dubbo:reference interface ="com.kakawanyifan.service.HelloService" id ="helloService" />
如果有多个服务,这种方式就比较繁琐了。推荐使用包扫描方式。
协议
在上文,我们使用的都是Dubbo协议。
1 2 3 4 5 6 7 @Bean public ProtocolConfig protocolConfig () { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo" ); protocolConfig.setPort(20800 ); return protocolConfig; }
1 <dubbo:protocol name ="dubbo" port ="20880" />
Dubbo框架支持很多协议,有:dubbo
、rmi
、hessian
、http
、webservice
、rest
、redis
等。
但是一般在工作中,通常我们只会使用其duboo
协议。需要注意的是,该协议的使用场景:
传入传出参数数据包较小(建议小于100K)。
不要用dubbo协议传输大文件或超大字符串。
消费者比提供者个数多。
如果涉及到,传入传出参数数据包较大,提供者比消费者个数多。可以考虑hessian
。
在最新的Dubbo3中,还支持Triple协议
,这也是Dubbo3推出的主力协议。
我们在同一个工程中配置多个协议,不同服务可以使用不同的协议,例如:
1 2 3 4 5 6 7 <dubbo:protocol name ="dubbo" port ="20880" /> <dubbo:protocol name ="rmi" port ="1099" /> <dubbo:service interface ="com.kakawanyifan.service.HelloService" ref ="helloService" protocol ="dubbo" /> <dubbo:service interface ="com.kakawanyifan.service.DemoService" ref ="demoService" protocol ="rmi" />
超时与重试
配置方法
可以消费者这一端进行配置。
全局配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.kakawanyifan.config;import org.apache.dubbo.config.ApplicationConfig;import org.apache.dubbo.config.ConsumerConfig;import org.apache.dubbo.config.RegistryConfig;import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;import org.springframework.context.annotation.Bean;@DubboComponentScan ("com.kakawanyifan.controller" )public class DubboConfig {【部分代码略】 @Bean public ConsumerConfig consumerConfig () { ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setTimeout(3000 ); consumerConfig.setRetries(3 ); return consumerConfig; } }
分别配置:
1 @DubboReference (timeout = 3000 ,retries = 3 )
可以在生产者这一端进行配置。
全局配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.kakawanyifan.config;import org.apache.dubbo.config.ApplicationConfig;import org.apache.dubbo.config.ProtocolConfig;import org.apache.dubbo.config.ProviderConfig;import org.apache.dubbo.config.RegistryConfig;import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;import org.springframework.context.annotation.Bean;@DubboComponentScan ("com.kakawanyifan.service.impl" )public class DubboConfig {【部分代码略】 @Bean public ProviderConfig providerConfig () { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setTimeout(3000 ); providerConfig.setRetries(3 ); return providerConfig; } }
分别配置:
1 @DubboService (timeout = 3000 ,retries = 3 )
XML方式
1 <dubbo:consumer timeout ="3000" retries ="3" > </dubbo:consumer >
1 <dubbo:provider timeout ="3000" retries ="3" > </dubbo:provider >
1 2 <bean id ="helloService" class ="com.kakawanyifan.service.impl.HelloServiceImpl" /> <dubbo:service interface ="com.kakawanyifan.service.HelloService" ref ="helloService" timeout ="3000" retries ="3" />
1 <dubbo:reference interface ="com.kakawanyifan.service.HelloService" id ="helloService" timeout ="3000" retries ="3" />
多版本
配置关键词version
,例如:
1 @DubboReference (version = "1.0" )
1 @DubboService (version = "1.0" )
负载均衡
Dubbo的负载均衡策略有4种:
Random
:按权重随机,默认值。按权重设置随机概率。
RoundRobin
:按权重轮询。
LeastActive
:最少活跃调用数,相同活跃数的随机。
ConsistentHash
:一致性Hash,相同参数的请求总是发到同一提供者。
负载均衡策略的关键词是loadbalance
,例如:
1 @DubboReference (loadbalance = LoadbalanceRules.RANDOM)
权重的关键词是weight
,例如:
1 @DubboService (weight = 100 )
集群容错
配置关键词:cluster
,例如:
1 @DubboReference (cluster = ClusterRules.FAIL_OVER)
集群容错模式:
failover
:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用retries
配置,一般用于读操作。
failfast
:快速失败,只发起一次调用,失败立即报错。通常用于写操作。
failsafe
:失败安全,出现异常时,直接忽略。返回一个空结果。
failback
:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
forking
:并行调用多个服务器,只要一个成功即返回。
broadcast
:广播调用所有提供者,逐个调用,任意一台报错则报错。
dubbo-admin
简介
正如我们上文的Dubbo架构图,有一部分是Monitor,即监控中心。但是Dubbo官方也表示Monitor不够好。所以我们这里讨论的是另外一个监控中心,dubbo-admin。
dubbo-admin,是图形化的服务管理,从注册中心中获取到所有的提供者/消费者进行配置管理,有:路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能。
部署
修改配置文件
在dubbo-admin/dubbo-admin-server/src/main/resources
目录下,找到application.properties
。
将如下的三个地址,改成我们自己的地址
1 2 3 admin.registry.address =zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181 admin.config-center =zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181 admin.metadata-report.address =zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181
打包并启动
在配置完成后,我们通过maven进行打包。
根据maven的规则,在打包之前都会进行测试。我们可以通过跳过测试。 具体跳过测试的方法,我们在[《18.SSM》]有过讨论。
打包完成之后,我们会在dubbo-admin-server
的target
目录找到dubbo-admin-server-0.5.0-SNAPSHOT.jar
。
1 nohup java -jar dubbo-admin-server-0.5.0-SNAPSHOT.jar &
访问http://localhost:38080
,用户名密码都是root
。
有些资料说前端需要单独打包,单独启动,其实并不需要。
2.7版本的Dubbo确实是前后端分离的项目,但是并不意味着部署的时候,也需要前后端分离。
使用
如图,是我们发布的服务。
我们可以查看服务详情。
还可以直接对服务进行测试。
Q&A
两个机器传输数据,如何传输Java对象?
dubbo内部已经将序列化和反序列化的过程内部封装了,我们只需要在定义pojo类时实现Serializable接口即可。 一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。
如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?
可以通信的,启动dubbo时,消费者会从zookeeper拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。但如果要调用新的服务,不能通信。
dubbo通信协议dubbo协议为什么不能传大包?
因为dubbo协议采用单一长连接,传大包的话,容易导致网络传输方面成为瓶颈。
例如,包有20MB,网速1MB每秒,那么可能会阻塞后续连接。
dubbo通信协议dubbo协议为什么采用异步单一长连接?
因为dubbo的应用场景大都是服务提供者少,服务的消费者多。 通过单一连接,保证单一消费者不会压垮提供者;长连接,减少连接握手验证等。