SpringBoot+ZooKeeper+ZKUI+Drools realize application configuration center and dynamic loading of business rules

Purpose of this article :

  1. Use ZooKeeper as the configuration center for SpringBoot applications
  2. The business rules used in the application are stored in Zookeeper. After the rules are updated, the application is notified to dynamically reload the rules without restarting the application.

1. Introduction to zookeeper

Zookeeper is a high-performance, distributed, open-source distributed application coordination service. It provides simple and primitive functions on which distributed applications can implement more advanced services, such as synchronization, configuration management, cluster management, and namespaces. It is designed to be easy to program, using the filesystem directory tree as the data model. The server runs on Java and provides Java and C client APIs.

Data model:

Features:

  • Using a tree structure, each node is called Znode, and the node path is separated by /, such as: /zoo/foo, each node path must be unique, and there is no relative path
  • Each Znode can store data, the data type byte[], can have child nodes
  • Each Znode has a stat data structure to store the version of the data, ACL and timestamp, etc.
  • Each Znode can perform CRWD operations

It can be seen that using zookeeper can easily manage the configuration of the application. For example, the springboot application named cyzy-gpserver can store the configuration in:

  • /cyzy-gpserver ## etc. Same application.yml
  • /cyzy-gpserver, dev ## etc. Same application-dev.yml
  • / cyzy-gpserver, prod ## 等同 application-prod.yml

Under these nodes, relevant configuration items are stored separately under each node, such as:

2.zookeeper and its ui client installation

zookeeper:

Go to http://www.apache.org/dyn/closer.cgi/zookeeper/ and download 3.4.6

Modify the following configuration, zookeeper-3.4.6\conf\zoo.cfg:

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=D:\dev\zookeeper-3.4.6\datas
  • 1
  • 2
  • 3
  • 4
  • 5

run:

  • windows:zookeeper-3.4.6\bin\zkServer.cmd
  • linux:zookeeper-3.4.6\bin\zkServer.sh
ui client zkui:

Go to https://github.com/DeemOpen/zkui and compile and package it yourself with reference to the documentation. 
Or use what others have done 
http://download.csdn.net/detail/lirenzuo/9640272

Login username/password configuration, zkui\config.cfg:

userSet = {"users": [{ "username":"admin" , "password":"admin","role": "ADMIN" }
  • 1
  • 2

Run: 
java -jar xxx.jar

3. Integration with SpringBoot

pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zookeeper-config</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Camden.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 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

src\main\resources下的bootstrap.yml:

spring:
  application:
    name: cyzy-gpserver
  profiles:
      active: prod
  cloud:
    zookeeper:
      enabled: true  # true:开启zookeeper外部化配置, false:读取本地配置; 需要将config.enabled,config.watcher.enabled同时设置
      connect-string: 127.0.0.1:2181
      config:
        enabled: true
        watcher:
          enabled: false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

In this way, by default, the application will read the configuration under the /config/application, prod and /config/cyzy-gpserver, prod nodes

Note: The configuration items under the /config/application node will be applied to all applications

Specific reference: 
http://cloud.spring.io/spring-cloud-static/spring-cloud-zookeeper/1.0.3.RELEASE/#spring-cloud-zookeeper-config

4. Dynamic loading of business rules

The basic idea:

1. Rules are stored in zookeeper and are grouped according to different Znodes, such as: certain types of rules are stored in /drools.rules/group1, and certain types are stored in /drools.rules/group2

2.每个存储了规则的Znode下创建一个“状态节点”和一个“结果反馈节点”,如: 
/drools.rules/group1/a_push_node, /drools.rules/group1/a_result_node

3.应用使用curator客户端的TreeCacheListener方式来监控/drools.rules下所有”状态节点”的变化,根据其状态值,进行相应操作。如:在/drools.rules/group1下修改了某个规则节点,此时设置a_push_node值为0,则应用重载/drools.rules/group1下的规则进行编译测试;设置为1,应用重载规则。应用操作结果反馈回result_node。

4.应用在使用时可以按照Znode的路径来fire rules,如:fireRules(String ruleInZKPath, String agendaGroup, AgendaFilter filter, Object… facts)

这样就可以实现一个轻量级的简易的规则管理和动态重载规则的系统……

整合Drools:
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
        <version>6.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>6.4.0.Final</version>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
动态加载规则服务:
/**
 * 可动态加载Drools规则的服务,加载方式:
 * 1.从本地文件加载,通过http get:/app/drools/reload 或 /app/drools/test 重载规则
 * 2.从远程zookeeper节点上加载,通过设置节点下a_push_node值的方式重载规则:0,测试模式;非0,正式加载
 * @ClassName: DroolsService 
 */
@Service
@RestController
public class DroolsService{
    private static final Logger LOG = LoggerFactory.getLogger(DroolsService.class);

    private static final String GROUP_NAME = "com.cyzy";
    private static final String VERSION = "1.0.0";
    private static final String ZK_ENABLED = "spring.cloud.zookeeper.enabled";
    private static final String ZK_PRIFEX_NODE_PATH = "/drools.rules.test";
    private static final String ZK_NODE_TEST_VALUE = "0";
    private static final String PRIFEX_DIR="drools.prefixDir";
    private static final String TARGET_DIR="drools.targetDir";
    private static final String RELOAD_RULES_OK = "reload drools's rules ok......";

    @Autowired
    private Environment env;

    @Autowired
    private ApplicationContext appCtx;

    private CuratorFramework client;

    private KieServices kieServices;

    private KieRepository repository;

    private ConcurrentHashMap<String, KieContainer> kieContainers = new ConcurrentHashMap<String, KieContainer>();

    @PostConstruct
    protected void initKieContainer() throws Exception {
        kieServices = KieServices.Factory.get();
        repository = kieServices.getRepository();

        boolean isZKEnabled = env.getProperty(ZK_ENABLED, Boolean.class);
        if (isZKEnabled) {
            client = appCtx.getBean(CuratorFramework.class);
            loadRulesFromZK();
        } else {
            loadConfigFromLocalFile();
        }
    }

    /**
     * 从ZK加载规则
     * @Title: loadRulesFromZK 
     * @throws Exception
     * @return: void
     */
    private void loadRulesFromZK() throws Exception{
        ZkNodeListenerAdapter.getInstance()
        //设置watcher
        .watcher(new ZKNodeWatcher(ZK_PRIFEX_NODE_PATH, client))
        //默认处理器
        .setDefaultHandler(new ZkNodeHandler() {
            @Override
            public String execute(CuratorFramework client, TreeCache tc, TreeCacheEvent event, ChildData targetChildData,
                    Map<String, ChildData> filterChildren) {
                String msg = RELOAD_RULES_OK;
                try {
                    String pathName = genPathName(event.getData().getPath());
                    loadRules(pathName, getRulesFromZKNodes(pathName, filterChildren));
                } catch (Exception e) {
                    msg = e.getMessage();
                    LOG.error(msg);
                }
                return msg;
            }
        })
        //a_push_node节点值为0时的处理器
        .addHandler(ZK_NODE_TEST_VALUE, new ZkNodeHandler() {
            @Override
            public String execute(CuratorFramework client, TreeCache tc, TreeCacheEvent event, ChildData targetChildData,
                    Map<String, ChildData> filterChildren) {
                String msg = RELOAD_RULES_OK;
                try {
                    String pathName = genPathName(event.getData().getPath());
                    testRules(pathName, getRulesFromZKNodes(pathName, filterChildren));
                } catch (Exception e) {
                    msg = e.getMessage();
                    LOG.error(msg);
                }
                return msg;
            }
        })
        //增加此监听器到watcher
        .addToWatcher(ZK_PRIFEX_NODE_PATH+"/gps/zte")
        .addToWatcher(ZK_PRIFEX_NODE_PATH+"/gps/zte2")
        .addToWatcher(ZK_PRIFEX_NODE_PATH+"/group1")
        .addToWatcher(ZK_PRIFEX_NODE_PATH+"/group2")
        //启动监听
        .start();
    }

    /**
     * 从本地文件加载规则
     * @Title: loadConfigFromLocalFile 
     * @throws Exception
     * @return: void
     */
    private void loadConfigFromLocalFile() throws Exception{
        String targetDir = env.getProperty(TARGET_DIR);
        loadRules(targetDir, getRulesFromLocalFile(targetDir));
    }

    /**
     * 正式加载规则文件进行使用
     */
    private void loadRules(String ruleInZKPath, List<ResourceWrapper> resourceWrappers) throws Exception {
        // if failed throws Exception
        ReleaseId releaseId = kieServices.newReleaseId(GROUP_NAME, ruleInZKPath, VERSION);
        InternalKieModule kieModule = DroolsUtils.createKieModule(kieServices, releaseId, resourceWrappers);
        // if succeed will add new module
        repository.addKieModule(kieModule);
        KieContainer kieContainer = kieServices.newKieContainer(releaseId);
        kieContainer.updateToVersion(releaseId);
        kieContainers.put(ruleInZKPath, kieContainer);
    }

    /**
     * 测试加载规则文件
     */
    private void testRules(String ruleInZKPath, List<ResourceWrapper> resourceWrappers) throws Exception{
        ReleaseId releaseId = kieServices.newReleaseId(GROUP_NAME, ruleInZKPath, VERSION);
        DroolsUtils.createKieModule(kieServices, releaseId, resourceWrappers);
    }

    /**
     * 从ENV配置的指定文件夹中获取规则文件
     */
    private List<ResourceWrapper> getRulesFromLocalFile(String path) {
        String prefixDir = env.getProperty(PRIFEX_DIR);
        String targetDir = env.getProperty(TARGET_DIR);
        List<ResourceWrapper> resourceWrappers = new ArrayList<ResourceWrapper>();
        List<File> ruleFiles = new ArrayList<File>();
        try {
            ruleFiles = DroolsUtils.getDefaultRuleFiles(prefixDir, targetDir);
        } catch (Exception e) {}
        if(ruleFiles.isEmpty()){
            throw new RuntimeException("can't load rules from " + prefixDir +"/"+ targetDir);
        }
        LOG.info("################################load ["+path+"] rules################################");
        for (File file : ruleFiles) {
            LOG.info(file.getName());
            resourceWrappers.add(new ResourceWrapper(ResourceFactory.newFileResource(file), file.getName()));
        }
        LOG.info("################################load ["+path+"] rules################################");
        return resourceWrappers;
    }

    /**
     * 从ZK节点上获取规则文件
     */
    private List<ResourceWrapper> getRulesFromZKNodes(String path, Map<String, ChildData> filterChildren) {
        List<ResourceWrapper> resourceWrappers = new ArrayList<ResourceWrapper>();
        LOG.info("################################load ["+path+"] rules################################");
        for (Entry<String, ChildData> entry : filterChildren.entrySet()) {
            LOG.info(entry.getKey());
            resourceWrappers.add(
                    new ResourceWrapper(ResourceFactory.newByteArrayResource(entry.getValue().getData()),entry.getKey()));
        }
        LOG.info("################################load ["+path+"] rules################################");
        return resourceWrappers;
    }

    private String genPathName(String zkNodePath) {
        String name = "";
        Matcher matcher = Pattern.compile("(.*?)/a_push_node").matcher(zkNodePath);
        if (matcher.matches()) {
            name = matcher.group(1);
        }
        return name;
    }

    /**
     * 创建指定ZK节点路径下的KieSession
     * @Title: newSession 
     * @Description: TODO
     * @param ruleInZKPath
     * @return: KieSession
     */
    private KieSession newSession(String ruleInZKPath) {
        KieContainer kieContainer = kieContainers.get(ruleInZKPath);
        if(kieContainer==null){
            throw new RuntimeException("can't get KieContainer with the name:" + ruleInZKPath);
        }
        KieSession session = kieContainer.newKieSession();
        // 默认配置
        session.setGlobal("appCtx", appCtx);
        return session;
    }

    /**
     * 执行指定ZK节点路径下的所有“MAIN”议程组(使用agenda-group定义,默认是MAIN)的规则
     * @Title: fireAllRules 
     * @param ruleInZKPath
     * @param facts
     * @return: void
     */
    public void fireMainGroupRules(String ruleInZKPath, Object... facts) {
        fireRules(ruleInZKPath, null, null, facts);
    }

    /**
     * 执行指定ZK节点路径下的所有“MAIN”议程组(使用agenda-group定义,默认是MAIN)的并且经过AgendaFilter过滤的规则
     * @Title: fireAllRules 
     * @param ruleInZKPath
     * @param filter
     * @param facts
     * @return: void
     */
    public void fireMainGroupRules(String ruleInZKPath, AgendaFilter filter, Object... facts) {
        fireRules(ruleInZKPath, null, filter, facts);
    }

    /**
     * 执行指定ZK节点路径下的所有agendaGroup指定议程组和“MAIN”议程组(使用agenda-group定义,默认是MAIN)的并且经过AgendaFilter过滤的规则
     * @Title: fireAllRules 
     * @param ruleInZKPath 规则所在ZK node的路径
     * @param agendaGroup 规则所在议程组名称,如果不定义默认:MAIN
     * @param filter 规则过滤器
     * @param facts 事实
     * @return: void
     */
    public void fireRules(String ruleInZKPath, String agendaGroup, AgendaFilter filter, Object... facts) {
        KieSession session = null;
        try {
            session = newSession(ruleInZKPath);
            // add fact
            for (Object fact : facts) {
                session.insert(fact);
            }
            //focus agenda group
            if (agendaGroup != null && !agendaGroup.isEmpty()) {
                session.getAgenda().getAgendaGroup(agendaGroup).setFocus();
            }
            // add filter
            if (filter != null) {
                session.fireAllRules(filter);
            } else {
                session.fireAllRules();
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            if (session != null) {
                session.dispose();
            }
        }
    }

    @RequestMapping(value = "/app/drools/reload", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> reloadRules() {
        String msg = RELOAD_RULES_OK;
        try {
            String targetDir = env.getProperty(TARGET_DIR);
            loadRules(targetDir, getRulesFromLocalFile(targetDir));
        } catch (Exception e) {
            msg = e.getMessage();
        }
        return ResponseEntity.ok().body(msg);
    }

    @RequestMapping(value = "/app/drools/test", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> testRules() {
        String msg = RELOAD_RULES_OK;
        try {
            String targetDir = env.getProperty(TARGET_DIR);
            testRules(targetDir, getRulesFromLocalFile(targetDir));
        } catch (Exception e) {
            msg = e.getMessage();
        }
        return ResponseEntity.ok().body(msg);
    }

    @RequestMapping(value = "/app/drools/test2", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> test2() {
        String msg = "test ok";
        try {
            fireRules(ZK_PRIFEX_NODE_PATH + "/group1", "group1", null, new Object());
            fireRules(ZK_PRIFEX_NODE_PATH + "/group2", "", null, new Object());

            fireRules(ZK_PRIFEX_NODE_PATH + "/gps/zte", "gps.zte", null, new Object());
            fireRules(ZK_PRIFEX_NODE_PATH + "/gps/zte2", "gps.zte", null, new Object());

//          fireAllRules("rules", null, null, new Object());
        } catch (Exception e) {
            msg = e.getMessage();
        }
        return ResponseEntity.ok().body(msg);

    }
}
  • 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
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324519339&siteId=291194637