一、使用dubbo的准備工作
1. zookeeper單節點環境
Demo中選用的zookeeper為zookeeper-3.4.5-cdh5.0.0.tar.gz版本,作為開發環境,在不考慮zookeeper部署多節點的情況下,在本機(windows環境)解壓之后,進入解壓目錄下的conf文件夾,復制一份zoo_sample.cfg文件,修改為zoo.cfg。然后運行bin/zkServer.cmd 啟動zookeeper服務。
備注:(1)zookeeper啟動會自動讀取zoo.cfg文件,該文件主要定義了zookeeper需要暴露的端口,數據和日志的存儲路徑。
(2)如果啟動cmd之后 閃退,在該cmd中最后一行添加pause,可以查看錯誤信息。
2.zookeeper分布式部署
如果保證zookeeper的高可用性,需要部署一個zookeeper集群,這里介紹zookeeper集群在linux中部署的方式。
(1) 在三台機器上分別解壓zookeeper-3.4.5-cdh5.0.0.tar.gz包,為了目錄的清晰,可以修改解壓后文件夾的名稱為zookeeper-2181。
(2) 在每台機器的zookeeper解壓目錄中,創建data和logs兩個文件夾,用於zookeeper的數據和日志存放:
(3) 在每台機器的zookeeper解壓目錄的conf文件夾中,創建一個zoo.cfg文件,文件內容如下:
tickTime=2000 initLimit=5 syncLimit=2 dataDir=/home/nxeop/zookeeper/data dataLogDir=/home/nxeop/zookeeper/logs clientPort=2181 server.1= EopApp1:2888:3888 server.2= EopApp2:2888:3888 server.3= EopApp2:2888:3888 maxClientCnxns=60 minSessionTimeout=4000
注解:
tickTime:心跳時間
initLimit:多少個心跳時間內,允許其他server連接並初始化數據
syncLimit:多少個tickTime內,允許follower節點同步
dataDir:存放內存數據文件目錄,根據實際環境修改
dataLogDir:存放日志文件目錄,根據實際環境修改
clientPort:監聽端口,使用默認2181端口
server.
x:
配置集群主機信息,
[hostname]:[
通信端口
]:[
選舉端口
]
,根據自己的主機信息修改
maxClientCnxns
:最大並發客戶端數,用於防止
DOS
的,設置為
0
是不加限制
minSessionTimeout
:最小的客戶端
session
超時時間(單位是毫秒)
maxSessionTimeout
:最大的客戶端
session
超時時間(單位是毫秒)
EopApp1、EopApp2、EopApp3是三台需要部署zookeeper節點服務器的hostname,這個zoo.cfg文件在集群中所有節點的配置都是一樣,直接復制就可以了。
server.1、server.2和server.3 表示三個服務器,每個服務器在zookeeper集群中有一個唯一的id,這個id就是server.x中的這個x。
(4) 最關鍵的一步,我們需要配置每台服務器上的zookeeper的id。之前已經在每台機器上的zookeeper解壓目錄中創建了data文件夾和logs文件夾。那么,我們在data文件夾中創建一個文件叫myid,對於第一台服務器,文件的內容為1。依次在各台主機的data目錄下生成myid文件設置id值,myid的內容要與前面配置的zoo.cfg中設置的server.x保持一致。
(5) 啟動zookeeper:
在三台服務器上依次執行zookeeper目錄中:
bin/zkServer.sh start
命令,zookeeper三個節點一次啟動。
全部啟動完成后,可以執行
bin/zkServer.sh status
去查看每個zk節點在集群中的狀態。
至此,zookeeper的集群已經部署完成。
示例1:最簡單dubbo服務注冊與調用
1. 示例場景和思路
當前實例的設計思路圖如下所示:
person-center:人員信息中心,通過dubbo對外提供服務;
person-client:客戶端,通過dubbo使用person-center提供的服務;
zookeeper:注冊中心,所有的dubbo應用都注冊到這上面;
person-interface:一個接口包,定義了人員信息接口和實體:
person-center引用interface包,用於實現這個接口;
person-client引用interface包,使用里面的接口名稱去調用dubbo服務。
2.編寫person-interface接口包
(1)創建person-interface的maven工程,默認打包方式為jar包。
(2)編寫實體類PersonInfo和接口IPersonInfoService,示例代碼結構入下所示:
(3)IPersonInfoService定義兩個方法:(這里雖然定義了PersonInfo,不過demo1先不使用,只做最簡單的接口)
public interface IpersonInfoService { /** * 查詢全部人員信息 */ public String queryPersonInfoAll(); /** * 根據人員編號查詢人員信息 */ public String queryPersonInfoByNumber(String personNumber); }
3.編寫person-center 服務端
工程參考結構如下:
(1)創建person-center的maven工程,默認打包方式為jar包。
(2)在pom文件中需要添加的核心依賴jar包如下:
a、person-interface,引用公共接口,用於實現這些接口:
<!-- 引入實現編寫好的person接口層 --> <dependency> <groupId>test.dubbo.interface</groupId> <artifactId>person-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
b、dubbo,引用dubbo依賴,客戶端和服務端都使用同一個依賴:
<!-- 引入dubbo框架(服務端、客戶端通用) --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.4</version> <exclusions> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency>
c、zkclient,引用zookeeper客戶端,dubbo會自動使用zkclient去和zookeeper進行連接:
<!-- 因為dubbo服務端需要注冊服務到zk中,因此依賴zkClient包 --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>
(3)編寫實現person-interface接口的代碼:
其中,PersonInfoServiceImpl實現了person-interface包中的IPersonInfoService接口的方法:
@Service public class PersonInfoServiceImpl implements IpersonInfoService { @Override public String queryPersonInfoAll() { System.out.println("==================================="); System.out.println("接口實現:queryPersonInfoAll()"); System.out.println("==================================="); return "from PersonInfoServiceImpl : some person Info"; } @Override public String queryPersonInfoByNumber(String personNumber) { System.out.println("==================================="); System.out.println("接口實現:queryPersonInfoByNumber(String personNumber)"); System.out.println("==================================="); return "from PersonInfoServiceImpl :" + personNumber + " 's Info."; }
(4)把這個person-center工程配置為一個dubbo的應用。
在工程的src/main/resources下面創建文件(包括文件夾):
META-INF/spring/applicationContext.xml。文件可以直接從源碼中把文件拿過來。
結構如下:
<?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:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 備注1:引入dubbo配置相關的xml的命名空間 <!-- 自動掃描注解:通過dubbo實現 --> <dubbo:annotation package="test.dubbo.*" /> <!-- 必須加上:dubbo應用的名稱 --> <dubbo:application name="person-center" /> <!—dubbo應用注冊到zk的地址 --> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> 備注2:應用名稱+zookeeper注冊地址,讓普通應用變成一個dubbo應用。 <!-- 用dubbo協議在20880端口暴露服務 --> <dubbo:protocol name="dubbo" port="20880" /> 備注3:如果該dubbo應用想成為服務端,那么配置一個dubbo協議的端口。 <!-- 服務端聲明需要對外開放提供服務的接口 --> <dubbo:service interface="test.dubbo.itf.IpersonInfoService" protocol="dubbo" ref="personInfoService" /> <!-- 服務端實現接口的bean --> <bean id="personInfoService" class="test.dubbo.impl.PersonInfoServiceImpl" /> 備注4:服務端聲明需要對外開放的服務接口,並且對接口關聯一個實現類。 </beans>
(5)編寫一個啟動類StartDubboServer,啟動這個dubbo應用。
import com.alibaba.dubbo.container.Main; public class StartDubboServer { public static void main(String[] args) { /** * 通過dubbo的啟動程序,自動加載一個spring的context文件; * 配置文件默認指定路徑為 resources/META-INF/spring/applicationContext.xml */ Main.main(null); } }
通過dubbo提供的一個容器啟動工具Main,可以啟動一個spring的context容器,並且如果不指定配置文件的話,會自動找如下路徑的文件:resources/META-INF/spring/applicationContext.xml
找到spring的xml文件后,容器加載dubbo相關配置,自動啟動一個dubbo服務。啟動成功后效果如下:
這個時候,從zookeeper中可以看到,dubbo節點自動創建,並且有一個接口服務已經登記到了dubbo節點下,此時:該接口節點下面consumer節點為空,沒有消費方,provider節點有1個子節點,就是我們剛剛啟動的那個服務端:
至此,一個dubbo的服務端已經成功創建完成。
4.編寫person-client 客戶端
工程參考結構如下:
(1)創建person-center的maven工程,默認打包方式為jar包。
(2)在pom文件中需要添加的核心依賴和服務端一模一樣,不過person-interface這個jar包的依賴是用於接口的調用;
(3)編寫一個使用person-interface接口的代碼,person-interface接口通過注入的方式引用:
@Service public class CheckPersonStatus { /** * 這個bean的實現來源於dubbo的服務 */ @Autowired private IpersonInfoService personInfoService; public String checkAllPersonStatus() { return personInfoService.queryPersonInfoAll(); } public String checkPersonStatusByPersonNumber(String personNumber) { return personInfoService.queryPersonInfoByNumber(personNumber); } }
(4)把這個person-center工程配置為一個dubbo的應用。
<?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:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 備注1:引入dubbo配置相關的xml的命名空間 <!-- 自動掃描注解:通過dubbo實現 --> <dubbo:annotation package="test.dubbo.*" /> <!-- 必須加上:dubbo應用的名稱 --> <dubbo:application name="person-client" /> <!—dubbo應用注冊的zk地址 --> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> 備注2:應用名稱+zookeeper注冊地址,讓普通應用變成一個dubbo應用。 <!-- 注冊需要使用的dubbo服務,通過interface指定服務接口 --> <dubbo:reference id="personInfoService" interface="test.dubbo.itf.IpersonInfoService" timeout="10000" check="false" /> 備注2:dubbo應用作為一個服務使用者,只需要聲明需要的接口服務即可。 </beans>
所有dubbo的應用都需要:
a、 添加dubbo應用的名稱;
b、 添加dubbo應用注冊的zookeeper的地址;
對於dubbo的應用:作為一個服務端:
a、添加對外開放的端口號;
b、添加需要開放的接口以及實現;
對於dubbo的應用:作為一個客戶端端:
a、添加需要使用的接口;
(5)啟動這個dubbo應用,為了使用方便,通過applicationContext進行spring的啟動:
public class MainClientRunner { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "META-INF/spring/applicationContext.xml"); /** * 啟動spring容器,同時也會啟動dubbo的客戶端 */ context.start(); /** * 獲取bean */ CheckPersonStatus checkPersonStatus = (CheckPersonStatus) context.getBean("checkPersonStatus"); /** 調用checkPersonStatus,checkPersonStatus調用了dubbo服務 **/ String allInfo = checkPersonStatus.checkAllPersonStatus(); String oneInfo = checkPersonStatus.checkPersonStatusByPersonNumber("12345678"); System.out.println(allInfo); System.out.println(oneInfo); try { Thread.sleep(3600 * 24); } catch (InterruptedException e) { e.printStackTrace(); } /** * 關閉spring的容器 */ context.close(); } }
至此為止,一個dubbo的客戶端已經完成。
客戶端運行效果如下:
這個時候,服務端的顯示如下:
說明客戶端調用的dubbo服務,服務的實現,是在dubbo服務端完成的。
Zookeeper中的dubbo節點如下顯示:
可以看出,此時,該接口增加了一個消費者。
示例2:dubbo集群實驗
Dubbo服務的分布式主要體現在兩個方面:
(1) 多個dubbo應用,可以為同一個接口提供服務。
這個意思就是說,我們編寫了一個dubbo應用person-center,對外提供了一個test.dubbo.itf.IpersonInfoService這個接口,那么我們可以部署任意多個person-center服務端並且配置為同一套zookeeper環境,所有的服務端,都會注冊到這個zookeeper中,也就意味着有多個provider提供了這個接口服務。直接避免了單點故障,而且能夠線性提高接口服務的性能。
(2) 復雜業務系統,拆分成多個基於dubbo服務的業務子系統。
比如對於一個業務系統,可以拆分成多個子系統,子系統對外通過dubbo進行服務交互,實現功能上的分布式構建,降低系統耦合性,而且可以通過多節點,對每個功能實現集群構建。
1. 多節點dubbo服務實驗
(1)給person-center這個服務端添加一個調用次數統計的代碼:
通過統計調用次數,可以觀察多個服務提供方的負載均衡情況。
(2)用過eclipse啟動person-center服務端,此時配置文件中,提供服務的端口號為20880。
(3) 分別修改端口號為20881和20882之后再次啟動服務端。這樣,person-center這個服務端已經啟動了三個節點,從zookeeper中能看到:
也就是說,對於接口test.dubbo.itf.IPersonInfoService具有三個服務提供方同時提供。
(4)客戶端調用:客戶端通過一個for循環持續調用這個接口:
啟動客戶端之后,客戶端會循環調用服務端1000次,這個時候,我們觀察每一個服務端的調用統計情況:
當客戶端停止調用時,三個服務端分別被調用了338、335、327次,說明,三個節點的調用基本上是負載均衡。
也就是說,dubbo的服務端集群的負載均衡是在客戶端完成的,對於服務端來說是沒有感知,集群中每個節點之間是透明的,不存在類似zookeeper的leader和follower的概念。
示例3:dubbo構建REST服務
1. 完善person-interface接口包
之前我們創建的person-interface是一個純粹的接口代碼包,只能作為一個普通的接口,現在我們要把這個接口改造成為可以額外開放成為REST服務的一個接口包。
(1)pom文件中添加2個依賴javax.ws.rs-api和dubbo:
<dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.0</version> </dependency>
這個依賴主要是定義了REST服務接口的注解。
Dubbo的具體依賴和上文是一樣的。
(3) 改造接口:
代碼中黃色背景部分是新增內容,下面對新增內容一一介紹如下:
package test.dubbo.itf; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import com.alibaba.dubbo.rpc.protocol.rest.support.ContentType; /** * * @author ShengGang 人員數據操作接口 * */ @Path("personInfoService") @Consumes({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8}) @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8}) public interface IpersonInfoService { /** * 查詢全部人員信息 * @return */ @GET @Path("queryPersonInfoAll") public String queryPersonInfoAll(); /** * 根據人員編號查詢人員信息 * @return */ @POST @Path("queryPersonInfoByNumber/{personNumber}") public String queryPersonInfoByNumber(@PathParam("personNumber") String personNumber); }
“@Path”,類似於webMVC中的@RequestMapping, @Path(“personInfoService”)放在了接口類上面,當訪問這個接口的方法時,相對的路徑則是/personInfoService,也就是說,當這個接口開放為REST服務的時候,請求url就是 http://localhost:9090/personInfoService/xxx;
“@Consumes”、“@Produces”,分別指定這個接口的消費方和請求需要接收和發送哪種類型的數據,ContentType里面可以選擇JSON或者是XML。如果是JSON,那么REST框架會自動進行對象和JSON格式的轉換。
“@Get”、“@Post”,指定具體的接口方法暴露為REST服務的時候是Get請求還是Post請求;
“@Path("queryPersonInfoByNumber/{personNumber}")”,這個就是標准的REST風格的URL的定義方法。請求的參數是放到URL中,{personNumber}是指,這個路徑會對應着這個方法的參數名。如果personNumber為11000123,那么請求的路徑就是
http://localhost:9090/personInfoService/queryPersonInfoByNumber/11000123
“@PathParam(“personNumber”)”,這個注解是可選的,可以指定這個參數的別名。
2. 改造person-center服務端
這個服務端的改造比interface接口包改造要簡單多,在之前的配置文件基礎上額外增加二個配置即可:開放一個REST服務端口,讓之前的接口支持REST協議。
(1) pom文件中添加如下依賴。
<!-- 通過dubbo提供rest服務,需要引入resteasy框架 --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>3.0.7.Final</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> <version>6.1.26</version> </dependency> <!-- 數據bean的校驗框架 --> <dependency> <groupId>org.glassfish.hk2.external</groupId> <artifactId>bean-validator</artifactId> <version>2.4.0</version> </dependency>
這些依賴的作用分別是
resteasy-client:提供REST服務的封裝;
jetty:開放HTTP服務;
bean-validator:校驗入參和出參的bean;
(2) 在配置文件中添加額外添加一些配置:
標記的第一行意思是,這個dubbo應用開放了REST協議,並且端口號為9090。開放的http服務是通過jetty完成的,“contextpath”則是指定了根路徑。
如果需要調用REST服務,則請求路徑為:
http://localhost:9090/service/personInfoService/queryPersonInfoAll
標記的第二行意思是,這個接口會同時支持dubbo和rest兩種協議,既可以按照一般的dubbo的調用方式去使用這個接口,也可以通過HTTP進行REST服務的調用。
最簡單的驗證REST服務是否正確開放的方法是,直接通過瀏覽器去訪問開放的REST服務地址:
訪問GET請求的接口:
GET請求在瀏覽器中響應正常。
訪問POST請求的接口:
POST請求在瀏覽器中響應為空,控制台會提示405,意思是該URL只會接收POST請求,而不會接收GET請求。
示例4:dubbo服務使用數據庫
主要改造的工程為person-center服務端,讓這個dubbo服務端真正連接數據庫,通過接口返回數據庫數據。
1. 添加maven依賴
<!-- 引入spring的jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework-version}</version> </dependency> <!-- 使用阿里的druid數據源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>0.2.8</version> <exclusions> <exclusion> <artifactId>tools</artifactId> <groupId>com.alibaba</groupId> </exclusion> <exclusion> <artifactId>jconsole</artifactId> <groupId>com.alibaba</groupId> </exclusion> </exclusions> </dependency> <!-- mysql的連接器 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency>
在person-center項目的pom.xml中,添加如下maven依賴:
其中:
spring-jdbc.jar提供了jdbcTemplate的數據庫操作api;
druid.jar為阿里的數據源;
mysql-connector-java.jar為mysql的java連接驅動;
2. 修改applicationContext.xml文件
修改person-interface下面的META-INF/spring/applicationContext.xml文件,添加如下bean的配置:
<!-- 指定spring需要加載的配置文件 --> <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="location" value="classpath:config.properties" /> </bean> <!-- 采用druid的數據源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="${jdbc.initialSize}" /> <property name="maxActive" value="${jdbc.maxActive}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <property name="validationQuery" value="${jdbc.validationQuery}" /> </bean> <!-- jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean>
和之前的spring文件相比,主要添加了三個bean:
(1) PropertySourcesPlaceholderConfigurer:這個bean指定了工程路徑里面的resources/config.properties文件,那么spring的xml文件中所有的屬性配置都通過配置文件去讀取和修改;
(2) DataSource:配置了一個數據源,指定了url、username和passwd等數據庫連接相關配置;
(3) jdbcTemplate:由spring提供的一個jdbc操作API,可以通過這個實例輕松進行數據庫增刪改查等操作;
3. 創建dao層
(1) 在工程中添加一個dao的package,用於編寫dao層,工程結構如下:
(2)編寫RowMapper類
由於沒有采用Hibernate等持久層框架,而是直接采用的jdbcTemplate操作數據庫,因此並沒有把數據庫字段和實體類PersonInfo進行一一映射。所以,我們需要編寫一個RowMapper類把數據庫的數據字段一一映射成為PersonInfo的字段。該類主要繼承了spring的RowMapper接口,代碼如下:
package test.dubbo.dao.wrapper; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.jdbc.core.RowMapper; import test.dubbo.entity.PersonInfo; public class PersonInfoRowMapper implements RowMapper<PersonInfo> { @Override public PersonInfo mapRow(ResultSet rs, int rowNum) throws SQLException { PersonInfo info = new PersonInfo(); info.setPersonNumber(rs.getString("person_number")); info.setPersonAddr(rs.getString("person_address")); info.setPersonName(rs.getString("person_name")); info.setPersonPhoneNumber(rs.getString("person_phone_number")); info.setPersonStatus(rs.getString("person_status")); return info; } }
(3) 編寫PersonInfoDao類
package test.dubbo.dao; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import test.dubbo.dao.wrapper.PersonInfoRowMapper; import test.dubbo.entity.PersonInfo; @Repository public class PersonInfoDao { @Autowired private JdbcTemplate jdbcTemplate; public PersonInfo queryPersonInfo(String personNumber) { String sql = "select * from person_info where person_number = ?"; return jdbcTemplate.queryForObject(sql, new PersonInfoRowMapper(), personNumber); } public List<PersonInfo> queryPersonInfoList() { String sql = "select * from person_info"; return jdbcTemplate.query(sql, new PersonInfoRowMapper()); } }
PersonInfoDao使用了jdbcTemplate,從數據庫進行讀寫操作,代碼如下:
這里主要通過jdbcTemplate執行了queryForObject和query兩個操作,因為我們用了之前編寫的繼承了RowMapper接口的PersonInfoRowMapper類,因此,查詢結果直接返回成了PersonInfo對象。
4. 修改Service層的實現類
主要參考代碼:
@Service public class PersonInfoServiceImpl implements IpersonInfoService { private static final AtomicInteger count = new AtomicInteger(0); @Autowired private PersonInfoDao personInfoDao; private static final String ERROR_INFO = "{\"errorCode\":\"-1\"}"; @Override public String queryPersonInfoAll() { System.out.println("==================================="); System.out.println("接口實現:queryPersonInfoAll()"); List<PersonInfo> infoList = personInfoDao.queryPersonInfoList(); String rtnInfo; try { rtnInfo = JSON.json(infoList); } catch (IOException e) { rtnInfo = ERROR_INFO; e.printStackTrace(); } System.out.println("==================================="); return rtnInfo; } @Override public String queryPersonInfoByNumber(String personNumber) { count.incrementAndGet(); System.out.println("==================================="); System.out.println("接口實現:queryPersonInfoByNumber(String personNumber)"); System.out.println("接口被調用了"+count.get()+"次。"); PersonInfo personInfo = personInfoDao.queryPersonInfo(personNumber); String rtnInfo; try { rtnInfo = JSON.json(personInfo); } catch (IOException e) { rtnInfo = ERROR_INFO; e.printStackTrace(); } System.out.println("==================================="); return rtnInfo; } }
代碼注意事項如下:
(1) service層注入了personInfoDao這個實例;
(2) 通過調用personInfoDao中的方法,獲取到PersonInfo對象以及對象的List,但是我們需要把對象轉成json格式,這里采用阿里的JSON包---
com.alibaba.dubbo.common.json.*
到此為止,dubbo服務端使用數據庫部分已經改造完成。
示例5:通過dubbo構建復雜業務場景
1. 模擬的場景結構圖
這里,作為模擬場景,我們有兩個dubbo服務的提供方,person-center和product-center,這兩個dubbo應用分別表示兩個獨立的系統,但是都注冊到同一套zookeeper注冊中心。order-system-service表示一個第三方綜合系統,會使用person-center和product-center兩個系統的接口,同時,自己會對外開放自己的dubb/REST接口。
- 具體的代碼可以參考附件內容,里面所有的配置都是之前已經提到過的內容。
其他:常見異常問題及解決
1. 啟動dubbo服務端后連接zookeeper出現如下異常:
INFO : org.apache.zookeeper.ZooKeeper - Client environment:os.name=Windows 7 INFO : org.apache.zookeeper.ZooKeeper - Client environment:os.arch=amd64 INFO : org.apache.zookeeper.ZooKeeper - Client environment:os.version=6.1 INFO : org.apache.zookeeper.ZooKeeper - Client environment:user.name=zhaijj INFO : org.apache.zookeeper.ZooKeeper - Client environment:user.home=C:\Users\zhaijj INFO : org.apache.zookeeper.ZooKeeper - Client environment:user.dir=D:\workdir\person-center INFO : org.apache.zookeeper.ZooKeeper - Initiating client connection, connectString=192.168.159.5:2181 sessionTimeout=60000 watcher=org.I0Itec.zkclient.ZkClient@2a2b2bd1 INFO : org.apache.zookeeper.ClientCnxn - Opening socket connection to server /192.168.159.5:2181 INFO : org.I0Itec.zkclient.ZkEventThread - Terminate ZkClient event thread. INFO : org.apache.zookeeper.ClientCnxn - Socket connection established to 192.168.159.5/192.168.159.5:2181, initiating session INFO : org.apache.zookeeper.ClientCnxn - Session establishment complete on server 192.168.159.5/192.168.159.5:2181, sessionid = 0x158ced3a4380008, negotiated timeout = 40000 INFO : org.apache.zookeeper.ZooKeeper - Session: 0x158ced3a4380008 closed INFO : org.apache.zookeeper.ClientCnxn - EventThread shut down org.I0Itec.zkclient.exception.ZkTimeoutException: Unable to connect to zookeeper server within timeout: 5000 at org.I0Itec.zkclient.ZkClient.connect(ZkClient.java:876) at org.I0Itec.zkclient.ZkClient.<init>(ZkClient.java:98) at org.I0Itec.zkclient.ZkClient.<init>(ZkClient.java:92) at org.I0Itec.zkclient.ZkClient.<init>(ZkClient.java:80) at com.alibaba.dubbo.remoting.zookeeper.zkclient.ZkclientZookeeperClient.<init>(ZkclientZookeeperClient.java:26)
然而,zookeeper是正常的,排除任何網絡、防火牆原因。
原因:zkClient客戶端版本過低,配置的是0.1,引用的zookeeper的jar包是3.3.3,然而服務端的zookeeper版本比較高,達到3.4.6。
解決方式:升高zkClient的版本:
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.9</version> </dependency>
這個最高版本的zkClient用的是zookeeper-3.4.8.jar 。更換包后,正常運行。