반응형

에버노트 원본: https://www.evernote.com/l/ABqNPV5LBCVPZ5ptuQSNUrtepNsGMr_IPGI 

 
1. 시나리오
 
다음과 같은 시나리오를 Drools BRMS 및 Fusion(CEP)로 구현하였다.
 
  • 온도, 연기 센서에서 데이터를 수집
  • 2초 사이에 두 개의 센서가 동시에 온도는 80도를 넘고, 연기 센서는 18을 넘어선 경우 화재로 감지
  • 화재가 감지되면 팬 Actuator를 가동
  • 온도가 다시 40도 미만, 연기 센서의 수치가 10 미만으로 2초간 지속되면 화재가 종료된 것으로 감지
  • 온도 센서만 80도를 넘어서면 고온 경고
  • 연기 센서만 18을 넘어서면 연기 경고
 
다음은 별도로 구현하는 것으로 가정하였다.
 
  • 센서 데이터 수집: 아두이노에 센서를 장착하여 수집
  • 수집된 센서 데이터 입력: 센서 데이터는 REST API를 통해 입력 됨
  • Actuator 가동은 별도의 REST API를 통해 기기로 전달
 
따라서 이 예제에서는 REST API를 통해 수집되었다고 가정되는 센서 데이터 값을 Drools 엔진에 넣어 CEP가 문법을 감지하는 것만 구현하여
Drools Fusion을 통한 CEP 기능을 시연하는데 중점을 두었다.
 
 

2. 개발 도구
 
개발 도구는 다음과 같이 준비한다.
 
  • Eclipse Neon 기준
 
 
  • Eclipse Marketplace에서 다음 모듈들을 설치 설치
 
 
 
다음 과정을 거쳐 프로젝트를 생성한다.
 
  • 프로젝트 생성
 
  • 아래와 같이 예제를 포함하여 프로젝트를 생성 가능
 

3. Rule 실행 준비
 
기본적으로 Drools는 META-INF/kmodule.xml에 정의된 선언적 내용을 통해 Rule 리소스를 읽어 오지만
다음과 같은 방법으로 DRL 파일을 직접 읽어 들일 수도 있다.
 
DRL을 파일에서 읽어 들임 (6.3)
 
KieServices ks = KieServices.Factory.get();
KieRepository
kr = ks.getRepository();
KieFileSystem
kfs = ks.newKieFileSystem();

kfs.write(ResourceFactory.newClassPathResource("rules/cep.drl", CEPTest.class));

KieBuilder
kb = ks.newKieBuilder(kfs);

kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
if (kb.getResults().hasMessages(Message.Level.ERROR)) {
   
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}

KieContainer
kContainer = ks.newKieContainer(kr.getDefaultReleaseId());

KieSession
kSession = kContainer.newKieSession();

 
DRL을 파일에서 읽어 들임 (6.3 CEP)
CEP(Fusion) 기능을 사용하려면 Drools를 Stream 모드로 실행해야 하며, 이 경우에는 일반 Rule과 달리 다음과 같이 코드를 작성한다.
 
KieBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption(EventProcessingOption.STREAM);

KnowledgeBuilder
kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("rules/cep.drl", CEPTest.class), ResourceType.DRL);
if (kbuilder.hasErrors()) {
   
throw new RuntimeException("Build Errors:\n" + kbuilder.getErrors().toString());
}

KnowledgeBase
kbase = KnowledgeBaseFactory.newKnowledgeBase(config);
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages())

StatefulKnowledgeSession
ksession = kbase.newStatefulKnowledgeSession();

return ksession
;
DRL Rule은 실행 시, 해당 파일에 정의된 모든 Rule들이 실행(검사)되고 결과가 업데이트 될 경우
다시 Rule들을 검사하여 규칙에 일치하는 것들이 없을 때까지 실행된다.
따라서 A Rule의 실행 결과로 B Rule이 만족될 경우 A, B Rule은 같이 실행 된다.
문제는 A Rule의 실행 결과 A Rule 자신이 다시 조건을 만족하여 실행되는 Loop 현상이 발생할 수 있는데, 이것을 방지하려면
Rule에 다음과 같은 no-loop 옵션을 준다. 또는 A Rule의 실행 결과 B Rule이 실행되고, 다시 B Rule의 실행 결과 A Rule이 실행되는
복합적인 Loop 현상도 발생할 수 있는데 이것을 방지하기 위해서는 lock-on-active 옵션을 준다.
 
 
Loop 방지
 
no-loop: Self-Loop 방지
lock-on-active: Complex Rule에서 하나의 Rule에 의해 다른 Rule이 Trigger 되는 것 방지
 
rule "Fire Alarm Rule"
no-loop true
lock-on-active true
   
when
        fad: FanActuatorData(on ==
false) &&
        tsd: ThermoSensorData(temperature > 80.0, t: temperature) &&
        ssd: SmokeSensorData(smokeLevel > 18.0, s: smokeLevel) over window:time(0.1s)
   
then
        System.out.println(
"*** Fire Alarm ***");
fad.setOn(
true);
update(fad);

end

 

4. Rule 정의
 
 
위의 시나리오를 구현한 CEP Rule은 다음과 같이 정의할 수 있다.
 
//created on: 2017. 3. 26
package com.sample.cep

//list any import classes here.

import com.sample.cep.*;

//declare any global variables here

declare ThermoSensorData
@role(event)
end

declare SmokeSensorData
@role(event)
end


rule "High Temperature Rule"
no-loop true
lock-on-active true
// Fire Alarm이 활성화 되지 않은 상태에서, 온도가 80도를 넘을 경우
   
when
        fad: FanActuatorData(on ==
false) &&
        tsd: ThermoSensorData(temperature > 80.0, value: temperature)
from entry-point thermometer
   
then
        System.out.println(
"*** High Temperature Alert: " + value + " ***");

end

rule "Dense Smoke Rule"
no-loop true
lock-on-active true
// Fire Alarm이 활성화 되지 않은 상태에서, 연기 농도가 18을 넘을 경우
   
when
        fad: FanActuatorData(on ==
false) &&
        ssd: SmokeSensorData(smokeLevel > 18.0, value: smokeLevel)
from entry-point smoke
   
then
        System.out.println(
"*** Dense Smoke Alert: " + value + " ***");

end

rule "Fire Alarm Rule"
no-loop true
lock-on-active true
// Fire Alarm이 활성화 되지 않은 상태에서, 2초 사이에 한 번이라도 온도가 80도를 넘고 연기 농도가 18을 넘을 경우
   
when
        fad: FanActuatorData(on ==
false) &&
        tsd: ThermoSensorData(temperature > 80.0, t: temperature) over window:time(2s)
from entry-point thermometer &&
        ssd: SmokeSensorData(smokeLevel > 18.0, s: smokeLevel) over window:time(2s)
from entry-point smoke
   
then
        System.out.println(
"*** Fire Alarm !!!!!!!!!!!!!!!!!!! ***");
fad.setOn(
true);
update(fad);

end

rule "Fire Alarm Off Rule"
no-loop true
lock-on-active true
// Fire Alarm이 활성화 된 상태에서, 2초 동안 한 번도 온도가 40도를 넘지 않고 연기 농도가 18을 넘지 않을 경우
   
when
        fad: FanActuatorData(on ==
true) &&
   
accumulate(
ThermoSensorData(temperature >= 40.0) over window:time(2s)
from entry-point thermometer;
$cnt1: count(1);
$cnt1 == 0
    ) &&
   
accumulate(
SmokeSensorData(smokeLevel >= 10.0) over window:time(2s)
from entry-point smoke;
$cnt2: count(1);
$cnt2 == 0
    )
   
then
        System.out.println(
"*** Fire Alarm Off ***");
fad.setOn(
false);
update(fad);

end


위의 화재 종료(File Alarm Off Rule)를 감지하는 CEP 규칙은 일반적으로 가장 많이 사용하는 문법으로서
accumulate 함수를 통해 센서 데이터가 2초 Time Window 상에서 몇 번 조건에 일치하는지를 count하고 있다.
여기서는 온도가 40도 이상인 경우과 연기 수치가 10이상인 경우를 세는데, 그것이 2초간 한 번도 없다면 화재는 종료된 것으로 인정한다.
 
DRL 문법에서는 다음과 같이 입력한 Java Bean의 Attribute를 읽어 들일 수 있으며
 
rule "High Temperature Rule"

   
when
        tsd: ThermoSensorData(temperature > 80.0, value: temperature)
   
then
        System.out.println(
"*** High Temperature Alert: " + value + " ***");

end


또는 다음과 같이 직접 getter/setter 메소드 호출하는 것도 가능하다.
 
 
public class ThermoSensorData extends SensorData {
public ThermoSensorData(double value) {
setTemperature(
value);
}

public double getTemperature() {
return getDoubleValue();
}

public void setTemperature(double value) {
setDoubleValue(
value);
}
}

 
rule "High Temperature Rule"

   
when
        tsd: ThermoSensorData(tsd.getTemperature() > 80.0, value: temperature)
   
then
        System.out.println(
"*** High Temperature Alert: " + tsd.getTemperature() + " ***");

end

 

5. 센서 데이터를 입력하여 Rule 실행
 
센서 데이터는 다음과 같이 상수로 정의하였다. 실제 시나리오에서는 REST API 등으로 받아 처리하면 된다.
 
final static double[] THERMO_DATA = new double[] {
20.0, 19.0, 23.3, 85.2, 49.5, 20.0, 21.2, 80.5, 90.9, 74.3, 38.2, 37.1, 20.0, 88.0, 79.0
};

final static double[] SMOKE_DATA = new double
[] {
22.0, 08.2, 03.3, 05.2, 01.5, 02.0, 01.2, 18.5, 19.9, 12.3, 12.8, 09.9, 08.8, 17.9, 18.1
};
CEP에서 처리할 두 개의 센서 데이터를 각각 다른 스트림으로 분리한다.
 
EntryPoint ep1 = kSession.getEntryPoint("thermometer"); // 온도 센서 입력 스트림
EntryPoint
ep2 = kSession.getEntryPoint("smoke"); // 연기 센서 입력 스트림

그리고 다음과 같이 센서 데이터를 발생하는 즉시 입력하고, Rule을 실행한다.
 
// 센서 값이 지속적으로 들어온다고 가정
for(int i = 0; i < THERMO_DATA.length; i++) {
System.
out.println(i);
Thread.sleep(1000);
// 두 개의 센서 값이 각각 다른 시점에 들어온다고 가정
ThermoSensorData
tsd = new ThermoSensorData(THERMO_DATA[i]);
ep1.insert(tsd);
System.
out.println("  T: " + THERMO_DATA[i]);
kSession.fireAllRules();
Thread.sleep(100);
// 두 개의 센서 값이 각각 다른 시점에 들어온다고 가정
SmokeSensorData
ssd = new SmokeSensorData(SMOKE_DATA[i]);
ep2.insert(ssd);
System.
out.println("  S: " + SMOKE_DATA[i]);
kSession
.fireAllRules();
}
중단에 Sleep을 넣은 이유는 실제로 두 개의 센서 데이터가 동시에 들어오지 않고, 발생할 때마다 들어 온다는 것을 가정하였다.
이렇게 일정하거나 동시에 발생하지 않는 두 개 이상의 센서 데이터에서 일정한 규칙을 감지하기 위한 로직을 구현할 때 Drools CEP는 매우 유용하다.
 
 
 
 
 
 
 

"

Posted by Hey Jerry
,