Egy klaszterezett WebSphere 6.1 alkalmazás szerver alatt futó webalkalmazáshoz fogok egy olyan modult elkészíteni, amely minden nap 21:00 órakor végrehajtja a feliratkozott felhasználóknak a hírlevél elküldését. Az ismertetésre kerülő példa más alkalmazás szerveren vagy egy Apache Tomcat-en is - kisebb módosításokkal - bevethető!
1. Döntések az ütemezés végrehajtásához
Először is el kell döntenünk, hogy az időzítési adatok tárolását perzisztens vagy nem perzisztens módon kívánjuk végrehajtani. Bár a RamJobStore felkonfigurálása egyszerűbb, a biztonságosabb kezelés érdekében (pl.: szerver leállás miatti lekésett triggerek újratüzelése) érdemes a JdbcJobStore-t választani. A következő lépés, hogy eldöntsük melyik tranzakció típust használjuk a Quartz táblákat tartalmazó adatbázis eléréséhez. A JobStoreTX-re esett a választás, mivel az elkészített alkalmazás nem használja a WebSphere által kezelt JTA tranzakciókezelést. A WebSphere admin konzolon korábban felvett adatforrásra a jdbc/wasdb JNDI névvel fogok hivatkozni a quartz konfigban, nem pedig properties bejegyzésekkel.
Mivel az alkalmazás egy klaszter több szerverére is telepítve lett, az ütemezés megfelelő végrehajtásához dönteni kell a Quartz klaszterezési lehetőségének kihasználása ill. az ütemezett feladatoknak a WebSphere klaszter egyik szerverén történő végrehajtása mellett. (Az utóbbit pl. az egyik szerver hoszt nevére való szűréssel tehetnénk meg.) A választásom a Quartz beépített klaszterezésére esett, mivel ez hibatűrő (az egyik szerver leállásakor a másik átveszi az időzített feladatok végrehajtását) és biztosítja a megfelelő terheléselosztást is.
2. A Quartz konfigurálása
Az alkalmazás Oracle adatbázist használ, ezért a quartz telepítési csomagban található (docs/table/tables_oracle.sql) szkriptet kell lefuttatni a Quartz által használt táblák létrehozásához. Végül az előzetes döntések alapján elkészített quartz.properties állományt tegyük ki az alkalmazás classpath-ára.
#Main Scheduler org.quartz.scheduler.instanceName=WebSphereClusteredScheduler org.quartz.scheduler.instanceId=AUTO #ThreadPool org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=1 #JobStore org.quartz.jobStore.useProperties=false org.quartz.jobStore.misfireThreshold=60000 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate org.quartz.jobStore.dataSource=wasDs org.quartz.dataSource.wasDs.jndiURL=jdbc/wasdb org.quartz.jobStore.acquireTriggersWithinLock=true org.quartz.jobStore.txIsolationLevelSerializable=true #Clustered config org.quartz.jobStore.isClustered=true org.quartz.jobStore.clusterCheckinInterval=10000 #Update check org.quartz.scheduler.skipUpdateCheck=true
3. A hírlevélküldési feladat időzítése
A konténerben futó webalkalmazások esetén, a Quartz időzítő automatikus indításához és leállításához a web.xml fájlban kell felvenni egy listenert vagy egy startup servletet. A hírlevél küldéshez tartozó job és trigger inicializálását érdemes az alkalmazás indulásakor elvégezni, így ehhez célszerű létrehozni egy saját szervletet, SchedulerInitializer névvel. Bár a szervlet init() metódusa a klaszter minden szerverén lefut, az inicializálást csak az egyik szerveren kell végrehajtani, így elkerülhető az ütemezési feladatok többszöröződése.
QuartzInitializer org.quartz.ee.servlet.QuartzInitializerServlet start-scheduler-on-load true shutdown-on-unload true 2 SchedulerInitializer test.SchedulerInitializerServlet 3
Az init() metódusban történik a hírlevél küldési job és trigger szükség szerinti létrehozása, majd ellenőrzésképpen az infók kiíratása. Az alábbi Clean Coding elvek figyelembe vételével megalkotott kódrészlet a Quartz 2.1 -re épül, így az ennek megfelelő API-t használtam.
public class SchedulerInitializerServlet extends HttpServlet{ private Scheduler sched = null; private static final String GROUP_NAME="NG"; private static final String NEWSLETTER_JOB_NAME="NLJ"; private static final String NEWSLETTER_TRIGGER_NAME="NLT"; private static final String CRON="0 0 21 * * ?"; @Override public void init(ServletConfig cfg) throws ServletException{ super.init(cfg); startScheduling(); listOfJobsAndTriggers(); } private void startScheduling() throws Exception{ initScheduler(); if(!isSavedNewsletterJob()) sched.scheduleJob(createNewsletterJobDetail(), createNewsletterTrigger(CRON)); } private void initScheduler() throws SchedulerException { if (sched == null) sched = StdSchedulerFactory.getDefaultScheduler(); if (!sched.isStarted()) sched.start(); } private boolean isSavedNewsletterJob(){ return sched.checkExists( JobKey.jobKey(NEWSLETTER_JOB_NAME,GROUP_NAME)); } private JobDetail createNewsletterJobDetail() { return JobBuilder.newJob(NewsletterSenderJob.class) .withIdentity(NEWSLETTER_JOB_NAME, GROUP_NAME) .build(); } private Trigger createNewsletterTrigger(String cron) { return TriggerBuilder.newTrigger() .withIdentity(NEWSLETTER_TRIGGER_NAME, GROUP_NAME) .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .startNow() .build(); } private void listOfJobsAndTriggers() throws Exception{ for(String grp: sched.getJobGroupNames()) for(JobKey jk : sched.getJobKeys(jobGroupEquals(grp))) log.info("Found job identified by:"+jk); for(String grp: sched.getTriggerGroupNames()) { for(TriggerKey tk : sched.getTriggerKeys( triggerGroupEquals(grp))) { log.info("Found trigger identified by:"+tk); } }
A hírlevél kiküldéséhez definiált CronTrigger tüzelésekor, a Job interface-t implementáló NewsletterSenderJob osztályból mindig egy új példány fog létrejönni, melynek az execute() metódusa lesz végrehajtva. A @DisallowConcurrentExecution annotáció biztosítja, hogy - JobDetails-enként - egyszerre csak egy példány fusson a NewsletterSender job-ból. Habár a @PersistJobDataAfterExecution működését jelenleg nem használom ki, az annotáció hozzáadásával elérhetővé válik, hogy a JobDetails-hez rendelt data objectek módosításai automatikusan mentésre kerüljenek.
@DisallowConcurrentExecution @PersistJobDataAfterExecution public class NewsletterSenderJob implements Job{ public NewsletterSenderJob() {} public void execute(JobExecutionContext context) throws JobExecutionException { //newsletter sender logic } }
WebSphere 6.1
VálaszTörlésReg hallottam rola, csak kivancsisagbol: ez valami melohelyi project?
Hali!
TörlésPár helyen használunk még WebSphere 6.1 -et, leginkább banki és nagyvállalati szektorban, bár a migrációk mindenhol elkezdődtek. Jaja, kb. 1 évvel ezelőtti projektben kódoltam le a fenti sorokat.
Java EE 6 -ban a @Schedule annotációval hasonló célt tudsz megvalósítani. Jól tévedek? Bár WebSphere 6.1 -nél gondolom ez nem volt opció...
VálaszTörlésIgen a @Schedule esetében is erről van szó! Egyébként WAS 6.1 alatt is van egy beépített időzítő szolgáltatás, de ennek a használatát inkább nem erőltetném.
TörlésKedves Bakai Balázs!
VálaszTörlésAz interneten - google - bogarászva bukkantam rá erre az oldalra.
Egy jellemzően IT területre specializálódott fejvadász cég munkatársa vagyok, egy IT megbízásából keresünk most Websphere Apllication Server specialistát. Sajnos elég kevés ezen a téren az aktív álláskereső, így mindenhol próbálok vadászni.
Azért szeretném felvenni Önnel a kapcsolatot, mert lehetséges, hogy ismerősi körében van olyan, aki Websphere-ben tapasztalt és esetleg meg lehetne keresni ezzel az állásajánlattal.
Tudna nekem ebben segíteni?
Segítségét előre is köszönöm. (elérhetőségem lentebb)
Üdvözlettel,
Éles Klára
Senior HR Consultant
Tech People Hungary Kft.
1053 Budapest, Reáltanoda u. 11. I.em. 1.
Tel/Fax: +36 1 789-7416
Mobile: +36 30 730-2543
E-mail: ke@tech-people.com
www.tech-people.com
Köszönöm a bizalmat! A lehetőségekkel kapcsolatban hamarosan felveszem önnel a kapcsolatot!
Törlés