在前一篇文章《Docker下dubbo开发,三部曲之一:极速体验》中,我们快速体验了部署在Docker环境下的dubbo服务,当时一共启动了四个容器,具体情况为:
容器 | 作用 | 镜像 | 功能 | link连接 |
---|---|---|---|---|
zk_server | 一致性协调服务 | zookeeper:3.3.6 | 官方镜像 | 无 |
dubbo_admin | 注册中心 | bolingcavalry/dubbo_admin_tomcat:0.0.1 | 定制镜像,用tomcat官方镜像加dubbo_admin.war生成 | 用别名zkhost连接zk_server |
dubbo_provider | 服务提供者 | bolingcavalry/dubbo_provider_tomcat:0.0.1 | 定制镜像,用tomcat官方镜像加dubboserviceprovider.war文件生成 | 用别名zkhost连接zk_server |
dubbo_consumer | 服务消费者 | bolingcavalry/online_deploy_tomcat:0.0.1 | 定制镜像,是个支持在线部署的tomcat | 无 |
在这个体验环境中,dubbo的服务提供者和消费者其实都是web应用,对应的是dubboserviceprovider.war和dubboserviceconsumer.war这两个文件,分别部署在docker容器的tomcat中。
把一个web应用在docker的tomcat容器中运行起来有两种方式:
1. 写Dockerfile文件做一个定制镜像,用tomcat官方镜像做基础镜像,在Dockerfile中将war包复制到tomcat的webapps目录下,dubbo_provider就是用这种方式;
2. 运行一个支持在线部署的tomcat容器,然后在线部署war包,具体细节请看《实战docker,编写Dockerfile定制tomcat镜像,实现web应用在线部署》,dubbo_consumer用的就是这种方式;
很明显,第一种方式用起来更简单,在docker-compose.yml中把镜像指定了就行,而第二种方式略为麻烦,要自己动手去部署war包;
那么问题来了,既然第一种简单,为何dubbo_consumer不用这种方式呢,反而用更为麻烦的第二种?
回答这个问题之前,我们先看下提供服务的应用dubbo_provider,它用的是第一种方式,dubbo服务提供方要把自己注册到dubbo注册中心,所以必然要使用zookeeper服务,在docker-compose.yml中,dubbo_provider的配置如下:
dubbo_provider:
image: bolingcavalry/dubbo_provider_tomcat:0.0.1
links:
- zk_server:zkhost depends_on:
- "dubbo_admin" environment:
TOMCAT_SERVER_ID: dubbo_provider_tomcat
restart: always
dubbo_provider容器配置了link参数zk_server:zkhost,也就是用zkhost取代zookeeper的ip,这样服务提供者的代码中只要使用zkhost就能连接到zookeeper;
那么dubbo_consumer呢,如果也用link参数zk_server:zkhost,然后在代码中用zkhost取代zookeeper的ip,这样不就和dubbo_provider一样了么?
当初我的确是这么做的,用Dockerfile把dubbo_consumer的war包复制到tomcat镜像中,启动容器的时候用link参数zk_server:zkhost,代码中用zkhost替代zookeeper的ip,如下图:
在容器运行的时候抛出了以下错误:
May 16, 2017 12:27:35 PM org.apache.catalina.core.ApplicationContext log
INFO: No Spring WebApplicationInitializer types detected on classpath
May 16, 2017 12:27:35 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
May 16, 2017 12:27:38 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dubboServiceConsumeController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bolingcavalry.service.CalculateService com.bolingcavalry.controller.DubboServiceConsumeController.calculateService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'calculateService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Failed to check the status of the service com.bolingcavalry.service.CalculateService. No provider available for the service com.bolingcavalry.service.CalculateService from the url zookeeper://zkhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo_service_consumer&dubbo=2.5.3&interface=com.bolingcavalry.service.CalculateService&methods=add&pid=1&side=consumer×tamp=1494937657693 to the consumer 172.28.0.4 use dubbo version 2.5.3
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:700)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5118)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5634)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bolingcavalry.service.CalculateService com.bolingcavalry.controller.DubboServiceConsumeController.calculateService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'calculateService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Failed to check the status of the service com.bolingcavalry.service.CalculateService. No provider available for the service com.bolingcavalry.service.CalculateService from the url zookeeper://zkhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo_service_consumer&dubbo=2.5.3&interface=com.bolingcavalry.service.CalculateService&methods=add&pid=1&side=consumer×tamp=1494937657693 to the consumer 172.28.0.4 use dubbo version 2.5.3
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
... 22 more
注意这一行
No provider available for the service com.bolingcavalry.service.CalculateService from the url zookeeper://zkhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo_service_consumer&dubbo=2.5.3&interface=com.bolingcavalry.service.CalculateService&methods=add&pid=1&side=consumer×tamp=1494937657693
zookeeper://zkhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo_service_consumer&dubbo=2.5.3&interface=com.bolingcavalry.service.CalculateService&methods=add&pid=1&side=consumer×tamp=1494937657693是个url,用来查找服务的,这个url中有我们在xml中填入的zkhost。
问题已经很清楚了,我们用zkhost替代ip原本是为了网络连接的时候,利用系统中host文件对zkhost的配置,来方便的连接到对应的ip,但是在dubbo的消费者服务中,dubbo运行时会把zkhost当作一个字符串来使用,拿这个字符串生成的url在dubbo注册中心是搜索不到的,因为服务在注册的时候注册中心记录的是ip;
基于以上原因,我才放弃了第一种部署方式,选择了先启动tomcat,再手工在线部署war包的方式(war包中zookeeper的ip已经写成真正的);
其实理论上第一种方式也是可以的,步骤如下:
1. 配置link参数zk_server:zkhost;
2. tomcat不再启动时自动执行;
3. 复制到镜像文件的web应用不是war包,而是从war包解压好的文件夹;
4. 制作一个shell,在容器启动的时候自动执行,shell的功能是从/etc/hosts文件中取得zkhost对应的ip,然后用sed命令将spring-extends.xml中的address=”zookeeper://zkhost:2181”替换成zkhost对应的ip;
5. 启动tomcat;
理论上这种方法也是可以的;