diff --git a/src/main/java/com/ctrip/zeus/client/SlbClient.java b/src/main/java/com/ctrip/zeus/client/SlbClient.java index 1e1577bf..0b4c36d1 100644 --- a/src/main/java/com/ctrip/zeus/client/SlbClient.java +++ b/src/main/java/com/ctrip/zeus/client/SlbClient.java @@ -32,6 +32,14 @@ public class SlbClient extends AbstractRestClient { )); } + public Response update(Slb slb) { + return getTarget().path("/api/slb/update").request() + .post(Entity.entity( + String.format(Slb.JSON, slb), + MediaType.APPLICATION_JSON + )); + } + public Slb get(String slbName) { String res = getTarget().path("/api/slb/get/" + slbName).request(MediaType.APPLICATION_JSON).get(String.class); try { diff --git a/src/main/java/com/ctrip/zeus/lock/DbLockFactory.java b/src/main/java/com/ctrip/zeus/lock/DbLockFactory.java index 694c7478..b4aea659 100644 --- a/src/main/java/com/ctrip/zeus/lock/DbLockFactory.java +++ b/src/main/java/com/ctrip/zeus/lock/DbLockFactory.java @@ -1,22 +1,23 @@ package com.ctrip.zeus.lock; import com.ctrip.zeus.dal.core.DistLockDao; +import com.ctrip.zeus.lock.impl.MysqlDistLock; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; /** * Created by zhoumy on 2015/4/23. */ -public class DbLockFactory implements ApplicationContextAware { - private static ApplicationContext applicationContext; +@Component("dbLockFactory") +public class DbLockFactory { + @Resource + private DistLockDao distLockDao; - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - DbLockFactory.applicationContext = applicationContext; - } - - public static DistLockDao getDao() { - return (DistLockDao) applicationContext.getBean("distLockDao"); + public DistLock newLock(String name) { + return new MysqlDistLock(name, distLockDao); } } diff --git a/src/main/java/com/ctrip/zeus/lock/impl/MysqlDistLock.java b/src/main/java/com/ctrip/zeus/lock/impl/MysqlDistLock.java index 98f8872c..4d3edb18 100644 --- a/src/main/java/com/ctrip/zeus/lock/impl/MysqlDistLock.java +++ b/src/main/java/com/ctrip/zeus/lock/impl/MysqlDistLock.java @@ -19,12 +19,13 @@ public class MysqlDistLock implements DistLock { private final String key; private volatile boolean state; - private DistLockDao distLockDao = DbLockFactory.getDao(); + private DistLockDao distLockDao;// = DbLockFactory.getDao(); private Logger logger = LoggerFactory.getLogger(this.getClass()); - public MysqlDistLock(String key) { + public MysqlDistLock(String key, DistLockDao distLockDao) { this.key = key; this.state = false; + this.distLockDao = distLockDao; } @Override @@ -96,9 +97,14 @@ public class MysqlDistLock implements DistLock { private boolean tryAddLock(DistLockDo d) throws DalException { if (compareAndSetState(false, true)) { - if (distLockDao.getByKey(d.getLockKey(), DistLockEntity.READSET_FULL) == null) { - distLockDao.insert(d); - return true; + try { + if (distLockDao.getByKey(d.getLockKey(), DistLockEntity.READSET_FULL) == null) { + distLockDao.insert(d); + return true; + } + } catch (DalException ex) { + compareAndSetState(true, false); + throw ex; } compareAndSetState(true, false); } @@ -107,11 +113,16 @@ public class MysqlDistLock implements DistLock { private boolean unlock(DistLockDo d) throws DalException { if (compareAndSetState(true, false)) { - int count = distLockDao.deleteByKey(d); - if (count == 1) - return true; - if (distLockDao.getByKey(d.getLockKey(), DistLockEntity.READSET_FULL) == null) - return true; + try { + int count = distLockDao.deleteByKey(d); + if (count == 1) + return true; + if (distLockDao.getByKey(d.getLockKey(), DistLockEntity.READSET_FULL) == null) + return true; + } catch (DalException ex) { + compareAndSetState(false, true); + throw ex; + } compareAndSetState(false, true); } return false; diff --git a/src/main/java/com/ctrip/zeus/restful/resource/AppResource.java b/src/main/java/com/ctrip/zeus/restful/resource/AppResource.java index 77cde3da..dab244a0 100644 --- a/src/main/java/com/ctrip/zeus/restful/resource/AppResource.java +++ b/src/main/java/com/ctrip/zeus/restful/resource/AppResource.java @@ -1,5 +1,7 @@ package com.ctrip.zeus.restful.resource; +import com.ctrip.zeus.lock.DbLockFactory; +import com.ctrip.zeus.lock.DistLock; import com.ctrip.zeus.model.entity.App; import com.ctrip.zeus.model.entity.AppList; import com.ctrip.zeus.model.transform.DefaultJsonParser; @@ -27,6 +29,8 @@ public class AppResource { private AppRepository appRepository; @Resource private ResponseHandler responseHandler; + @Resource + private DbLockFactory dbLockFactory; @GET @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @@ -101,7 +105,13 @@ public class AppResource { throw new Exception("Unacceptable type."); } } - appRepository.update(a); + DistLock lock = dbLockFactory.newLock(a.getName() + "_update"); + try { + lock.lock(); + appRepository.update(a); + } finally { + lock.unlock(); + } return Response.ok().build(); } diff --git a/src/main/java/com/ctrip/zeus/restful/resource/SlbResource.java b/src/main/java/com/ctrip/zeus/restful/resource/SlbResource.java index 0c3cc70b..970b4efc 100644 --- a/src/main/java/com/ctrip/zeus/restful/resource/SlbResource.java +++ b/src/main/java/com/ctrip/zeus/restful/resource/SlbResource.java @@ -1,6 +1,8 @@ package com.ctrip.zeus.restful.resource; import com.ctrip.zeus.exceptions.ValidationException; +import com.ctrip.zeus.lock.DbLockFactory; +import com.ctrip.zeus.lock.DistLock; import com.ctrip.zeus.model.entity.Slb; import com.ctrip.zeus.model.entity.SlbList; import com.ctrip.zeus.model.transform.DefaultJsonParser; @@ -31,6 +33,8 @@ public class SlbResource { private SlbRepository slbRepository; @Resource private ResponseHandler responseHandler; + @Resource + private DbLockFactory dbLockFactory; @GET @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @@ -79,7 +83,13 @@ public class SlbResource { } else { throw new Exception("Unacceptable type."); } - slbRepository.update(s); + DistLock lock = dbLockFactory.newLock(s.getName() + "_update"); + try { + lock.lock(); + slbRepository.update(s); + } finally { + lock.unlock(); + } return Response.ok().build(); } diff --git a/src/main/java/com/ctrip/zeus/service/model/handler/impl/AppSyncImpl.java b/src/main/java/com/ctrip/zeus/service/model/handler/impl/AppSyncImpl.java index 32224ee6..3b917d4a 100644 --- a/src/main/java/com/ctrip/zeus/service/model/handler/impl/AppSyncImpl.java +++ b/src/main/java/com/ctrip/zeus/service/model/handler/impl/AppSyncImpl.java @@ -54,6 +54,10 @@ public class AppSyncImpl implements AppSync { @Override public AppDo update(App app) throws DalException, ValidationException { validate(app); + AppDo check = appDao.findByName(app.getName(), AppEntity.READSET_FULL); + if (check.getVersion() > app.getVersion()) + throw new ValidationException("Newer App version is detected."); + AppDo d= C.toAppDo(app); appDao.updateByName(d, AppEntity.UPDATESET_FULL); diff --git a/src/main/java/com/ctrip/zeus/service/model/handler/impl/SlbSyncImpl.java b/src/main/java/com/ctrip/zeus/service/model/handler/impl/SlbSyncImpl.java index 4d96c7d0..40673659 100644 --- a/src/main/java/com/ctrip/zeus/service/model/handler/impl/SlbSyncImpl.java +++ b/src/main/java/com/ctrip/zeus/service/model/handler/impl/SlbSyncImpl.java @@ -53,6 +53,9 @@ public class SlbSyncImpl implements SlbSync { @Override public SlbDo update(Slb slb) throws DalException, ValidationException { validate(slb); + SlbDo check = slbDao.findByName(slb.getName(), SlbEntity.READSET_FULL); + if (check.getVersion() > slb.getVersion()) + throw new ValidationException("Newer Slb version is detected."); SlbDo d = C.toSlbDo(slb); slbDao.updateByName(d, SlbEntity.UPDATESET_FULL); diff --git a/src/main/java/com/ctrip/zeus/service/model/impl/AppRepositoryImpl.java b/src/main/java/com/ctrip/zeus/service/model/impl/AppRepositoryImpl.java index dc902452..8e35c5e3 100644 --- a/src/main/java/com/ctrip/zeus/service/model/impl/AppRepositoryImpl.java +++ b/src/main/java/com/ctrip/zeus/service/model/impl/AppRepositoryImpl.java @@ -7,7 +7,6 @@ import com.ctrip.zeus.service.model.handler.AppQuery; import com.ctrip.zeus.service.model.AppRepository; import com.ctrip.zeus.service.model.handler.AppSync; import com.ctrip.zeus.service.model.ArchiveService; -import com.ctrip.zeus.support.C; import org.springframework.stereotype.Repository; import javax.annotation.Resource; @@ -75,6 +74,8 @@ public class AppRepositoryImpl implements AppRepository { @Override public void update(App app) throws Exception { + if (app == null) + return; AppDo d = appSync.update(app); app = appQuery.getById(d.getId()); archiveService.archiveApp(app); diff --git a/src/main/java/com/ctrip/zeus/service/model/impl/SlbRepositoryImpl.java b/src/main/java/com/ctrip/zeus/service/model/impl/SlbRepositoryImpl.java index f35ff0d5..ab1a0388 100644 --- a/src/main/java/com/ctrip/zeus/service/model/impl/SlbRepositoryImpl.java +++ b/src/main/java/com/ctrip/zeus/service/model/impl/SlbRepositoryImpl.java @@ -10,7 +10,6 @@ import com.ctrip.zeus.service.model.ArchiveService; import com.ctrip.zeus.service.model.handler.SlbQuery; import com.ctrip.zeus.service.model.SlbRepository; import com.ctrip.zeus.service.model.handler.SlbSync; -import com.ctrip.zeus.support.C; import org.springframework.stereotype.Repository; import javax.annotation.Resource; @@ -103,10 +102,8 @@ public class SlbRepositoryImpl implements SlbRepository { public void update(Slb slb) throws Exception { if (slb == null) return; - SlbDo d = slbSync.update(slb); archiveService.archiveSlb(slbQuery.getById(d.getId())); - for (SlbServer slbServer : slb.getSlbServers()) { nginxServerDao.insert(new NginxServerDo() .setIp(slbServer.getIp()) diff --git a/src/test/java/com/ctrip/zeus/lock/DistLockTest.java b/src/test/java/com/ctrip/zeus/lock/DistLockTest.java index 6cee1ac3..feb86edd 100644 --- a/src/test/java/com/ctrip/zeus/lock/DistLockTest.java +++ b/src/test/java/com/ctrip/zeus/lock/DistLockTest.java @@ -14,6 +14,7 @@ import org.unidal.lookup.ContainerLoader; import support.AbstractSpringTest; import support.MysqlDbServer; +import javax.annotation.Resource; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,9 @@ import java.util.concurrent.atomic.AtomicLong; public class DistLockTest extends AbstractSpringTest { private static MysqlDbServer mysqlDbServer; + @Resource + private DbLockFactory dbLockFactory; + @BeforeClass public static void setUpDb() throws ComponentLookupException, ComponentLifecycleException { S.setPropertyDefaultValue("CONF_DIR", new File("").getAbsolutePath() + "/conf/test"); @@ -39,14 +43,14 @@ public class DistLockTest extends AbstractSpringTest { final CountDownLatch latch = new CountDownLatch(4); new Thread() { public void run() { - report.add(new MysqlDistLock("slock").tryLock() == true); + report.add(dbLockFactory.newLock("slock").tryLock() == true); latch.countDown(); } }.run(); new Thread() { public void run() { - report.add(new MysqlDistLock("slock").tryLock() == false); - MysqlDistLock lock = new MysqlDistLock("slock1"); + report.add(dbLockFactory.newLock("slock").tryLock() == false); + DistLock lock = dbLockFactory.newLock("slock1"); report.add(lock.tryLock() == true); lock.unlock(); latch.countDown(); @@ -54,7 +58,7 @@ public class DistLockTest extends AbstractSpringTest { }.run(); new Thread() { public void run() { - new MysqlDistLock("slock1").lock(); + dbLockFactory.newLock("slock1").lock(); report.add(true); latch.countDown(); } @@ -62,7 +66,7 @@ public class DistLockTest extends AbstractSpringTest { new Thread() { public void run() { try { - new MysqlDistLock("slock1").lock(3); + dbLockFactory.newLock("slock1").lock(3); report.add(false); } catch (Exception e) { report.add(true); @@ -90,7 +94,7 @@ public class DistLockTest extends AbstractSpringTest { lockOne.add(es.submit(new Runnable() { @Override public void run() { - report.add(new MysqlDistLock("clock").tryLock()); + report.add(dbLockFactory.newLock("clock").tryLock()); } })); } @@ -108,7 +112,7 @@ public class DistLockTest extends AbstractSpringTest { final AtomicLong interval = new AtomicLong(0); new Thread() { public void run() { - MysqlDistLock lock = new MysqlDistLock("wait"); + DistLock lock = dbLockFactory.newLock("wait"); lock.lock(); try { Thread.sleep(10000); @@ -121,7 +125,7 @@ public class DistLockTest extends AbstractSpringTest { }.run(); new Thread() { public void run() { - MysqlDistLock lock = new MysqlDistLock("wait"); + DistLock lock = dbLockFactory.newLock("wait"); long start = System.nanoTime(); lock.lock(); report2.add(true); @@ -138,9 +142,9 @@ public class DistLockTest extends AbstractSpringTest { @Test public void testIncorrectExecution() { // Assume unlock cannot be done using different instance. - MysqlDistLock lock = new MysqlDistLock("mistake"); + DistLock lock = dbLockFactory.newLock("mistake"); lock.lock(); - MysqlDistLock anotherLock = new MysqlDistLock("mistake"); + DistLock anotherLock = dbLockFactory.newLock("mistake"); anotherLock.unlock(); Assert.assertFalse(anotherLock.tryLock()); lock.unlock(); diff --git a/src/test/java/com/ctrip/zeus/restful/ApiTest.java b/src/test/java/com/ctrip/zeus/restful/ApiTest.java index 8b75efe9..083953d2 100644 --- a/src/test/java/com/ctrip/zeus/restful/ApiTest.java +++ b/src/test/java/com/ctrip/zeus/restful/ApiTest.java @@ -16,8 +16,14 @@ import org.unidal.dal.jdbc.transaction.TransactionManager; import org.unidal.lookup.ContainerLoader; import support.MysqlDbServer; -import javax.xml.bind.SchemaOutputResolver; +import javax.ws.rs.core.Response; import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; /** * @author:xingchaowang @@ -54,6 +60,39 @@ public class ApiTest { ContainerLoader.getDefaultContainer().release(ts); } + @Test + public void testConcurrentUpdate() throws ExecutionException, InterruptedException { + final SlbClient c = new SlbClient("http://127.0.0.1:8099"); + final String slbName = "default"; + final int total = 6; + Slb orig = generateSlb(slbName); + c.add(orig); + + ExecutorService es = Executors.newFixedThreadPool(total); + List> futures = new ArrayList<>(); + for (int i = 0; i < total; i++) { + final int num = i; + futures.add(es.submit(new Runnable() { + @Override + public void run() { + while (true) { + Slb slb = c.get(slbName); + slb.addSlbServer(new SlbServer().setHostName("slbupd" + num).setIp("192.168.11." + num).setEnable(false)); + Response updResponse = c.update(slb); + if (updResponse.getStatus() == 200) + break; + } + } + })); + } + for (Future f : futures) + f.get(); + es.shutdown(); + int base = orig.getSlbServers().size(); + Slb upd = c.get(slbName); + Assert.assertEquals(base + total, upd.getSlbServers().size()); + } + @Test public void testSlb() { System.out.println("###########################test1"); @@ -63,18 +102,7 @@ public class ApiTest { String slbName = "default"; - Slb sc = new Slb(); - sc.setName(slbName).setNginxBin("/usr/local/nginx/bin").setNginxConf("/usr/local/nginx/conf").setNginxWorkerProcesses(1).setVersion(0) - .addVip(new Vip().setIp("192.168.1.3")) - .addVip(new Vip().setIp("192.168.1.6")) - .addSlbServer(new SlbServer().setHostName("slb001a").setIp("192.168.10.1").setEnable(true)) - .addSlbServer(new SlbServer().setHostName("slb003").setIp("192.168.10.3").setEnable(true)) - .addVirtualServer(new VirtualServer().setName("vs002").setPort("80").setSsl(false) - .addDomain(new Domain().setName("hotel.ctrip.com"))) - .addVirtualServer(new VirtualServer().setName("vs003").setPort("80").setSsl(false) - .addDomain(new Domain().setName("m.ctrip.com")) - .addDomain(new Domain().setName("m2.ctrip.com"))) - .setStatus("TEST"); + Slb sc = generateSlb(slbName); c.add(sc); Slb sc2 = c.get(slbName); @@ -89,18 +117,7 @@ public class ApiTest { SlbClient s = new SlbClient("http://127.0.0.1:8099"); String slbName = "default"; - Slb sc = new Slb(); - sc.setName(slbName).setNginxBin("/usr/local/nginx/bin").setNginxConf("/usr/local/nginx/conf").setNginxWorkerProcesses(1).setVersion(0) - .addVip(new Vip().setIp("192.168.1.3")) - .addVip(new Vip().setIp("192.168.1.6")) - .addSlbServer(new SlbServer().setHostName("slb001a").setIp("192.168.10.1").setEnable(true)) - .addSlbServer(new SlbServer().setHostName("slb003").setIp("192.168.10.3").setEnable(true)) - .addVirtualServer(new VirtualServer().setName("vs002").setPort("80").setSsl(false) - .addDomain(new Domain().setName("hotel.ctrip.com"))) - .addVirtualServer(new VirtualServer().setName("vs003").setPort("80").setSsl(false) - .addDomain(new Domain().setName("m.ctrip.com")) - .addDomain(new Domain().setName("m2.ctrip.com"))) - .setStatus("TEST"); + Slb sc = generateSlb(slbName); s.add(sc); AppClient c = new AppClient("http://127.0.0.1:8099"); @@ -124,6 +141,19 @@ public class ApiTest { App app2 = c.get(appName); Assert.assertEquals(app.setVersion(1), app2); + } + private Slb generateSlb(String slbName) { + return new Slb().setName(slbName).setNginxBin("/usr/local/nginx/bin").setNginxConf("/usr/local/nginx/conf").setNginxWorkerProcesses(1).setVersion(0) + .addVip(new Vip().setIp("192.168.1.3")) + .addVip(new Vip().setIp("192.168.1.6")) + .addSlbServer(new SlbServer().setHostName("slb001a").setIp("1110.1").setEnable(true)) + .addSlbServer(new SlbServer().setHostName("slb003").setIp("192.168.10.3").setEnable(true)) + .addVirtualServer(new VirtualServer().setName("vs002").setPort("80").setSsl(false) + .addDomain(new Domain().setName("hotel.ctrip.com"))) + .addVirtualServer(new VirtualServer().setName("vs003").setPort("80").setSsl(false) + .addDomain(new Domain().setName("m.ctrip.com")) + .addDomain(new Domain().setName("m2.ctrip.com"))) + .setStatus("TEST"); } }