一个服务的前世今生-dapeng-soa服务生命周期详述

dapeng-soa architechure

1. 概述

随着搭载于dapeng框架之上的业务系统一个个上线并趋于稳定,不少开发同事们在抓虫子之余,也萌生了一些“不满”:用了dapeng好久了,但是它就像一个黑盒子,我们知之甚少,能否系统的介绍一下啊。
确实,dapeng框架提供了丰富的脚手架以及详尽的开发指南(详见dapeng官网), 极易上手。但了解一下dapeng的内部机制,有利于开阔眼界,并在开发中充分利用各个特性,对于提高服务的质量也很有好处。

本文着眼于分析一个服务从启动到结束的整个过程。

后续会分专题陆续介绍dapeng框架的各个特性,敬请留意。

2. dapeng 容器目录结构

dapeng-container
├── apps                           # 1. business services
│   ├── order_service
│   └── stock_service
├── bin
│   ├── dapeng-bootstrap.jar                      # 2. dapeng bootstrap module
│   ├── lib                                       # 3. dapeng container jars(including dependency)
│   ├── shutdown.sh                               # 4. startup/shutdown shell
│   └── startup.sh
├── conf                                          # 5. configuration files for container
│   └── logback.xml
├── lib                                           # 6. core lib
│   ├── dapeng-core-2.1.1.jar
│   └── logback-*.jar
├── logs                                          # 7. directory where log files located
└── plugin                                        # 8. plugin directory
  1. 业务服务,该目录由dapeng的ApplicationClassLoader加载, 可在一个dapeng容器中放多个服务,但是建议一个容器一个服务
  2. dapeng bootstrap模块,容器的启动入口,负责生成其它的定制classLoader并启动容器。
  3. dapeng container jar目录,容器的实现类及其三方依赖,通过ContainerClassLoader加载
  4. 启动以及停止脚本
  5. 容器的配置文件
  6. 核心接口库以及日志,由CoreClassLoader负责加载,可用于服务端以及客户端
  7. 日志目录
  8. 插件目录,由PluginClassLoader加载

注意, 不同的目录由不同的classLoader加载。常见的错误是:apps路径下的服务有个对象(通过ApplicationClassLoader加载),假设叫a,其类型为A,它是单例对象(通过A.getInstance()获得)。但是在bin路径下(也就是通过ContainerClassLoader加载), 通过A.getInstance()得到的a', 就跟a是完全不同的对象了。

3. zk节点结构

/soa/
├── runtime/service/                                   # 1. runtime info
│   ├── com.today.soa.idgen.service.IDService/
│   │   ├── 192.168.10.130:9081:1.0.0:0000000181        # 2. node info
│   │   ├── 192.168.20.101:9081:1.0.0:0000000179        # 3. master node
│   │   └── 192.168.10.138:9081:1.0.0:0000000183
│   ├── com.today.api.order.service.OrderService2/ 
│   │   ├── 192.168.10.133:9099:1.0.0:0000000368        
│   │   ├── 192.168.10.126:9099:1.0.0:0000000372       
│   │   ├── 192.168.20.102:9099:1.0.0:0000000367      
│   │   └── 192.168.10.131:9099:1.0.0:0000000366        # master node
│   └── ...                                             # 4. other services
└── config/
    ├── services/                                       # 5. config info
    │   ├── com.today.soa.idgen.service.IDService
    │   └── com.today.api.order.service.OrderService2
    ├── freq/                                           # 6. rate limit rules
    ├── routes/                                         # 7. router rules
    └── cookies/                                        # 8. cookie rules

3.1 服务运行时信息 /soa/runtime/service/

服务运行时信息由服务在启动的时候注册到zk的/soa/runtime/service/${serviceName}路径上,其表现形式为挂靠在该路径下的临时节点,格式为:ip:port:version:seq, seq由zookeeper自动生成,且能保证在同目录下唯一并单调递增。
其中,dapeng容器根据本容器中服务运行时信息的seq,判断是否为master主节点(seq最低者为master)

一个服务对应一个临时节点。 我们可通过该路径得知目前服务集群中该服务的节点数量

3.2 服务配置信息 /soa/config/

服务配置信息存放了dapeng-soa所需要的一些配置项,可通过命令行工具或者配置中心进行管理。

服务的配置信息当前有四种,分布在/soa/config/不同的子目录下。它们的结构都类似,都是把服务名字作为节点挂在配置子目录下,然后配置信息作为节点的内容。

3.2.1 普通配置信息 /soa/config/services/

包括负载均衡策略、服务超时等信息。
包含两个层次:

  • 全局性配置: 直接写进/soa/config/services/节点
timeout/100ms;loadbalance/LeastActive;
  • 服务私有配置: 写到具体的服务节点上, 例如/soa/config/services/com.today.api.order.service.OrderService2
timeout/100ms,createOrder:500ms,createOrderPayment:60000ms;loadbalance/LeastActive,createOrder:Random,createOrderPayment:RoundRobin;

3.2.2 限流配置信息 /soa/config/freq/

dapeng 的流控做在服务端,所以该节点只对服务端有效
限流信息直接写在具体的服务节点上,例如如下订单的限流配置写在/soa/config/freq/com.today.api.order.service.OrderService2

[rule1]
match_app = listOrder # 针对具体方法限流
rule_type = callerIp # 对每个请求端IP
min_interval = 60,5  # 每分钟请求数不超过5次
mid_interval = 3600,100 # 每小时请求数不超过100次
max_interval = 86400,200 # 每天请求数不超过200次

[rule2]
match_app = * # 针对订单服务限流
rule_type = callerIp # 对每个请求端IP
min_interval = 60,600  # 每分钟请求数不超过600
mid_interval = 3600,10000 # 每小时请求数不超过1万
max_interval = 86400,80000 # 每天请求数不超过8万

详见dapeng-soa限流文档

3.2.3 路由配置信息 /soa/config/routes/

服务路由信息也是直接写在具体的服务节点上,例如下面订单的路由配置写在/soa/config/routes/com.today.api.order.service.OrderService2

method match r"create.*" => ip"192.168.10.0/24"
cookie_storeId match %"10n+1..6" => ip"192.168.20.128"

详见dapeng-soa路由文档

3.2.3 cookie 规则信息 /soa/config/cookies/

cookie规则信息也是直接写在具体的服务节点上,例如针对来自dapengCli的手工对订单接口的调用,我们为这些调用打开TRACE功能,那么我们把规则配置到/soa/config/cookies/com.today.api.order.service.OrderService2:

callerIp match ip"192.168.20.200" => c"thread-log-level#TRACE"

3. 容器的生命周期

容器提供了一个LifeCycleAware接口以及若干事件,在事件发生的时候会触发相应的业务逻辑。
业务可通过实现该接口, 做一些初始化以及清理的动作。

例如某服务的业务,只在主节点启动一个工作线程。那么它就可以监听MASTER_CHANGE事件。当主节点发生变更的时候,就启动或者停止工作线程。

    public enum LifeCycleEventEnum {
        /**
         * dapeng 容器启动
         */
        START,
        PAUSE,
        MASTER_CHANGE,
        CONFIG_CHANGE,
        STOP
    }
/**
 * 提供给业务的lifecycle接口,四种状态
 *
 * @author hui
 * @date 2018/7/26 11:21
 */
public interface LifeCycleAware {

    /**
     * 容器启动时回调方法
     */
    void onStart(LifeCycleEvent event);

    /**
     * 容器暂停时回调方法
     */
    default void onPause(LifeCycleEvent event) {
    }

    /**
     * 容器内某服务master状态改变时回调方法
     * 业务实现方可自行判断具体的服务是否是master, 从而执行相应的逻辑
     */
    default void onMasterChange(LifeCycleEvent event) {
    }

    /**
     * 容器关闭
     */
    void onStop(LifeCycleEvent event);

    /**
     * 配置变化
     */
    default void onConfigChange(LifeCycleEvent event) {
    }
}

4. 容器的启动过程

容器启动需要协调各个插件的顺序,避免在服务还没准备好的情况下,客户端请求就涌进来。
通过脚本startup.sh启动容器: java -server $JAVA_OPTS -cp ./dapeng-bootstrap.jar com.github.dapeng.bootstrap.Bootstrap

dapeng-soa bootstrap

  1. Bootstrap创建三个classLoader, 分别是CoreClassLoader(负责加载lib目录的类)、ContainerClassLoader(负责加载bin/lib目录的类)以及ApplicationClassLoader(负责加载apps目录下的类)。

  2. Bootstrap通过ContainerClassLoader加载ContainerFactory并调用其getContainer方法, 获得DapengContainer实例。

  3. DapengContainer创建五个Plugin,并依次调用其start方法:
    3.1 启动NettyPlugin, 打开服务监听端口(例如9090)
    3.2 启动ZkPlugin, 跟注册中心zookeeper建立连接。
    3.3 启动SpringPlugin
    3.3.1 通过ApplicationClassLoader加载服务(一个服务表现为一个Application对象)
    3.3.2 对每个服务(假设为OrderService),通过ZkPlugin把服务信息注册到/soa/runtime/service/com.today.api.order.service.OrderService2目录里,并启动一个对该路径的zk watcher(主要用于跟踪服务集群中master节点的变化, 当发生master变换时,需触发MASTER_CHANGE事件。

    一旦服务完成注册,嗷嗷待哺的客户端就会如潮水般涌进该服务节点。

    3.4 启动SchedulePlugin, 定时任务就绪
    3.5 启动JmxPlugin, Jmx 端口就绪
    3.6 如果是开发模式(默认), 那么启动ApiDocPlugin, 内置的文档站点可访问。该插件在生产环境下不会启动

  4. 触发容器START事件

  5. 加载服务端Filter(详情下回分解)。

  6. 最后,注册Jvm进程的ShutdownHook

至此,容器启动完毕, 服务生命周期开始。

5. 容器的优雅关闭过程

容器的关闭过程,同样需要协调插件的关闭顺序,确保进来的请求尽量处理完毕后再关闭容器,避免对业务产生影响。为此, dapeng 容器会维护一个请求计数器requestCounter,计数值是当前容器内尚未处理完的请求数目。

  1. 启动脚本startup.sh会监听kill信号。收到kill信号后,脚本转发该信号到容器Jvm进程。
  2. 容器的ShutdownHook给触发,依次执行:
    2.1 ZkPlugin.stop方法,断开跟zookeeper的连接,从而把本节点服务信息从/soa/runtime/services/${serviceName}上摘除,进而新的请求就不会再路由到本节点。
    2.2 休眠若干次,直到requestCounter数目为0或者超时。
    2.3 关闭其它插件( Netty有自己的优雅关闭机制,能确保outbound队列的消息能全部发送出去)

至此,容器关闭,服务结束了其使命。

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,717评论 6 342
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,893评论 2 89
  • 不会游泳的金鱼和头顶长草的斑马 文/覆舟 叶芝今年6岁了,她随爷爷一起住在临江城郊外的一间旧房子里。这里环境优美,...
    陈覆舟阅读 424评论 1 2
  • 今晚六点半照例是儿子的围棋训练课时间,中午的时候我们就计划好:下午放学回家就抓紧写语文和数学作业,然后吃完晚...
    ykmm阅读 185评论 0 1