最近在研究阿里开源的分布式 RPC 框架 dubbo
快速开始
等等,在开始之前,先来读两篇短文,看看别人是怎么讲故事的,学习学习
1、那些年,我们追过的RPC
2、从SOA到微服务的演化 (原创:老刘 码农翻身 2017-05-22)
小明毕业后为了户口,进入了一家大型国企的信息部门工作, 这个国企不差钱, 几十年来随着IT系统的发展, 也与时俱进地兴建了多个信息系统,只不过自家开发的极少, 从外边购买的极多, 虽然信息部也有开发能力, 但是当甲方的感觉是最妙的, 何况出了问题还可以把责任推出去。
在这些系统当中, 小一点儿的有自动化办公系统(OA) , 休假系统,车辆管理系统, 薪水支付系统, 大点儿的有客户关系管理系统, ERP系统 ..... 等等, 可以说是琳琅满目,让人目不暇接,几十年来IT发展的技术,几乎都能在公司的IT环境中找到。
小明的工作之一就是维护现在的IT系统博物馆, 博物馆中大部分都是遗留系统, 能工作,但是非常的老旧。硬件平台, 软件环境,开发语言各部相同,都是异构的。
就说那个休假系统吧,还是用上个世纪流行的Delphi 写的。 还有那个OA系统, 也是上个世纪的ASP,运行在IIS上。 虽然界面丑陋,勉强能用。
也有一点新东西,比如上周上线的那个维修系统不就用了最新的前端技术嘛, 小明也着实激动了一阵,看了两天的React。
有一天有个著名外企的销售来到了小明的公司,请信息部门的老大吃了饭,K了歌。。。好像还搞了些小明这些小兵不知道的秘密活动。
第二天老大给国企老总做了汇报, 过了一段时间, 公司发文了:
为了提升IT效率,打破各个信息孤岛,实现各个信息系统之间的互联互通, 让业务和IT进行对齐, 达到业务敏捷性, 公司经慎重研究决定,邀请xxx公司作为咨询顾问, 从即日起开始实施SOA战略。
小明看了一遍,愣住了,上面的字全都认识, 但连起来就不知道是什么意思, 虚头巴脑的, 唯一确定的是,咨询顾问要来了,要开始什么SOA了。
顾问果然来了, 先给小明他们的信息部门洗了一次脑, 小明的脑海中被各种新式的名词所充斥: SOA, ESB, SCA, BEPL.... 下了课, 小明和同事们讨论了很久, 模模糊糊的明白了要做什么事情。
好像是把这些遗留的异构系统包装成粗粒度的服务, 还是 Web 服务,可以通过Http来访问, 然后呢, 让大家互相调用, 甚至可以把这些服务进行编排,形成一个大的业务流程, 完成更高层次的业务, 听起来挺有意思的。
外企的销售非常精明, 趁势卖了一大批硬件和软件, 他们的技术团队还确实帮着做了一个小的验证系统, 实现了一个业务场景,展示给公司老总看, 老总非常满意: 不错, 我们公司又一次站到了IT技术的前沿!
然后就没有下文了, 领导们似乎忘记了, SOA似乎没有发生过,互联互通呢? 业务敏捷呢?
小明很困惑,周末约着同学张大胖去吃饭。
张大胖在一个互联网公司工作, 主要是做网上约车系统, 用的都是最前沿的技术, 他说: 听起来你们要把这些遗留的异构系统做数据/信息的集成啊, 只是没有做下去而已, 国企嘛可以理解。 你知道我们公司在干什么事儿吗?
小明说:“不会和我们一样吧?”
“完全不同! 我们公司才成立几年啊, 最重要的就是这个约车系统, 当然我们现在发展的很快,这三年以来系统已经快变成一个巨无霸了, 代码已经达到百万行级别, 没人能搞明白了, 代码库非常难于管理, 冲突不断。 系统部署也非常困难, 一点点小改动都需要巨无霸式的整体部署, 你能想象得到吗, 我们系统重启一次得15分钟!”
“卧槽, 这么慢? 不可思议,我用过你们的打车软件, 用起来还可以啊?”
“唉, 金玉其外,败絮其中, 你不知道我们每次发布有多痛苦, 但是竞争激烈, 我们还得频繁发布。 所以我们做的事情和你们相反, 不是集成, 而是拆分! 把一个巨无霸变成一些小的组件, 让这些小组件能完全独立的开发, 测试和部署。”
“那你们的开发团队怎么办?” 小明问。
“我们的组织结构也要随着这些小组件来重构啊,你看我们分成了“乘客管理”,“司机管理”,“旅程管理”,“支付管理”等好多组, 每个组只负责他们特定的一块儿功能, 并且每个组里边都有设计,开发,测试,部署等人员,一应俱全, 他们从头到尾全程负责。”
“有意思啊,这些小组件都是独立的,每个组件实例都是一个进程吧, 那这些小组件怎么交互? 难道也是通过我们公司所用的Web service ?”
“不不, 我们不用那重量级的Web service , 什么WSDL, 什么SOAP, 我们统统不用, 我们只用最轻量级的、基于Http 的Restful 来对外提供接口”
小明突然想到一个问题:“你们每个部门负责一个特定功能,那数据怎么办?还用统一的数据库吗?”
“这是个老大难问题,我们得做数据库的拆分啊,唉, 一言难尽。”
小明说 :“可以理解,不过这样以来确实是更加敏捷了。”
大胖说:“这还不是最厉害的,最厉害的是我们能快速的自动化的部署这些小组件,并且能为他们创建很多实例来运行,有一个挂掉了也没关系,别人可以调用那些还在运行的。”
“所以关键点就是这些小组件对外提供的服务是无状态的,对吧?”
“没错” 大胖说, “这一点和你们的SOA是一样的, 对了, 告诉你一个小秘密,我们在生产环境会做一些‘猴子测试’,通过写脚本随机的停掉一些实例,看看我们的系统运行的怎么样”
小明说:“厉害啊,你们都玩的可都是心跳啊。”
“木有办法,只有在生产环境才能发现真正的问题啊”
“难道你们的这种方式没有缺点吗?”
“当然有了, 就拿数据库来说吧, 数据做了分区, 一致性怎么保证啊? 选择分布式事务非常麻烦, 有时候不得不选择最终一致性来妥协; 还有服务多了, 客户调用起来非常的麻烦, 所以经常得把多个接口API封装,对外提供一个简单的接口 ; 当然这种基于HTTP的调用远没有原来的在一个进程内的方式效率高。 还有一个要命的问题就是监控,你想想这么多运行的实例, 互相之间有调用关系,一个地方出错了, 怎么追踪啊,很麻烦。”
“不管如何, 你们这种把系统拆分,让一个独立的组织负责独立的部分还是很敏捷啊, 对了,你说的小组件,难道没有一个像SOA这样的高大上名称吗?”
“当然有了, 业界把这种方式叫做微服务! 虽然这个词不能完整的表达我们做的事情。 我现在很期望Martin Flower 给它起个更贴切的名称,就像Dependency Injection 那样, 比之前的IoC强多了。 ”
吃过饭回去的路上,小明心想:天下大势,真是分久必合,合久必分啊, 我们在零散系统的集成, 大胖他们又在搞巨无霸应用的拆分。
相比而言,小明还是羡慕大胖,羡慕他们公司的朝气蓬勃。 他虽然明白自己所在国企的信息系统和大胖做的不一样, 但是暮气沉沉的感觉让人看不到希望, 再这么混下去, 热爱的技术可就真的废了。
过了年,小明已经在国企服务了3年了,顺利的拿到了帝都的户口, 然后毫不迟疑的跳槽到了大胖的公司,去搞微服务去了。
直接上 GitHub 找到 dubbo 项目 Apache Dubbo (incubating) Project
git clone https://github.com/apache/incubator-dubbo.git
进入 dubbo-demo 模块,如下
demo下面的几个模块的后缀就是表示其配置(注解配置,api配置,xml配置)
interface只是一个接口模块
正准备跃跃欲试,结果发现好像少了点什么,咋没有可视化界面dubbo-admin,看看文档发现,admin 已经被单独分离出来了
下载运行dubbo-admin
git clone https://github.com/apache/incubator-dubbo-admin
# 直接在 dubbo-admin-server\src\main\java\org\apache\dubbo\admin启动DubboAdminApplication也可以
mvn --projects dubbo-admin-server spring-boot:run
cd dubbo-admin-ui
npm install
npm run dev
admin准备好了,但在demo里面发现默认注册中心用的都是多播:multicast://224.5.6.7:1234
这里dubbo推荐使用 zookeeper
安装 zookeeper
下载地址:http://www.apache.org/dyn/closer.cgi/zookeeper/
zookeeper 分为单机模式和集群模式,因为这里只是为了运行dubbo,这里只介绍Windows下装单机版(集群版,讲道理这种分布式的东西应该在Linux下折腾嘛)
解压后进入conf目录,修改zoo_sample.cfg文件名为:zoo.cfg,这是因为zookeeper启动时默认加载zoo.cfg配置文件。修改zoo.cfg文件内容如下:
# The number of milliseconds of each tick
# tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
# 默认情况下最小的会话超时时间为两倍的tickTime
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
# zookeeper集群中的包含多台server, 其中一台为leader, 集群中其余的server为follower. initLimit参数配置初始化连接时,
# follower和leader之间的最长心跳时间. 此时该参数设置为5, 说明时间限制为5倍tickTime, 即5*2000=10000ms=10s.
initLimit=5
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
# 该参数配置leader和follower之间发送消息, 请求和应答的最大时间长度. 此时该参数设置为2, 说明时间限制为2倍tickTime, 即4000ms.
syncLimit=2
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
dataDir=D:\\zookeeper-3.4.8\\data
# dataLogDir:顾名思义就是 Zookeeper 保存日志文件的目录
dataLogDir=D:\\zookeeper-3.4.8\\log
# the port at which the clients will connect
# clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
# maxClientCnxns:限制连接到 ZooKeeper 的客户端的数量
# maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
# autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
# autopurge.purgeInterval=1
需要在zookeeper-x.x.x文件夹下新建data和log文件夹
启动 zookeeper
cmd命令进入 zookeeper-3.4.8\bin 目录,启动 zkServer.cmd
或者直接双击zkServer.cmd启动,双击后窗口不会关闭,如果关闭了表示未启动成功
如果jdk环境变量设置了,但是提示JAVA_HOME找不到,则需要修改该目录下的 zkEnv.cmd 关于 JAVA_HOME 的一部分
启动 zookeeper 成功,下一步修改demo里面的注册中心的address
为 zookeeper://127.0.0.1:2181
demo-api-consumer:
public static void main(String[] args) {
ReferenceConfig reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
//reference.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234"));
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
reference.setInterface(DemoService.class);
DemoService service = reference.get();
String message = service.sayHello("dubbo");
System.out.println(message);
}
demp-api-provider:
public static void main(String[] args) throws Exception {
ServiceConfig service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
//service.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
System.out.println("provider启动了");
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.export();
System.in.read();
}
万事俱备,首先启动 provider
[21/05/19 17:15:36:347 CST] main INFO logger.LoggerFactory: using logger: org.apache.dubbo.common.logger.log4j.Log4jLoggerAdapter
provider启动了
[21/05/19 17:15:42:375 CST] main INFO utils.Compatibility: Running in ZooKeeper 3.4.x compatibility mode
[21/05/19 17:15:43:093 CST] main INFO imps.CuratorFrameworkImpl: Starting
zookeeper 命令行
2019-05-21 17:15:52,601 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@222] - Accepted socket connection from /127.0.0.1:55960
2019-05-21 17:15:52,601 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:ZooKeeperServer@949] - Client attempting to establish new session at /127.0.0.1:55960
2019-05-21 17:15:52,632 [myid:] - INFO [SyncThread:0:ZooKeeperServer@694] - Established session 0x10001863bea0003 with negotiated timeout 40000 for client /127.0.0.1:55960
admin-ui 显示
启动 consumer
[21/05/19 17:38:20:068 CST] main INFO config.AbstractConfig: [DUBBO] Refer dubbo service org.apache.dubbo.demo.DemoService from url zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?anyhost=true&application=dubbo-demo-api-consumer&check=false&default.deprecated=false&default.dynamic=true&default.lazy=false&default.register=true&default.sticky=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=13352®ister=true®ister.ip=192.168.10.1&remote.application=dubbo-demo-api-provider&remote.timestamp=1558430153116&side=consumer&sticky=false×tamp=1558431487233, dubbo version: , current host: 192.168.10.1
Hello dubbo, response from provider: 192.168.10.1:20880
[21/05/19 17:38:20:967 CST] DubboShutdownHook INFO config.DubboShutdownHook: [DUBBO] Run shutdown hook now., dubbo version: , current host: 192.168.10.1
zookeeper 命令行:
2019-05-21 17:38:17,489 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@222] - Accepted socket connection from /127.0.0.1:56156
2019-05-21 17:38:17,493 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:ZooKeeperServer@949] - Client attempting to establish new session at /127.0.0.1:56156
2019-05-21 17:38:17,528 [myid:] - INFO [SyncThread:0:ZooKeeperServer@694] - Established session 0x10001863bea0004 with negotiated timeout 40000 for client /127.0.0.1:56156
2019-05-21 17:38:21,045 [myid:] - INFO [ProcessThread(sid:0 cport:2181)::PrepRequestProcessor@487] - Processed session termination for sessionid: 0x10001863bea0004
2019-05-21 17:38:21,077 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@1056] - Closed socket connection for client /127.0.0.1:56156 which had sessionid 0x10001863bea0004
可以看到输出成功
dubbo 的目的,即做到调用远程接口方法和调用本地接口方法一样透明简单,从这个demo中就可以看出来。