-
Notifications
You must be signed in to change notification settings - Fork 13k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from lostcharlie/feature-tree-datum-store
Implement a persistent store for tree protocol
- Loading branch information
Showing
3 changed files
with
288 additions
and
3 deletions.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
naming/src/main/java/com/alibaba/nacos/naming/consistency/weak/tree/TreeDataStore.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.alibaba.nacos.naming.consistency.weak.tree; | ||
|
||
import com.alibaba.fastjson.JSON; | ||
import com.alibaba.fastjson.JSONObject; | ||
import com.alibaba.nacos.naming.consistency.Datum; | ||
import com.alibaba.nacos.naming.consistency.KeyBuilder; | ||
import com.alibaba.nacos.naming.monitor.MetricsMonitor; | ||
import com.alibaba.nacos.naming.pojo.Record; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
import java.nio.channels.FileChannel; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
/** | ||
* Datum store for tree protocol. | ||
* | ||
* @author lostcharlie | ||
*/ | ||
@Component | ||
public class TreeDataStore { | ||
private String basePath; | ||
|
||
public String getBasePath() { | ||
return basePath; | ||
} | ||
|
||
@Value("${nacos.naming.tree.dataStore.basePath}") | ||
private void setBasePath(String basePath) { | ||
this.basePath = basePath; | ||
} | ||
|
||
public Datum read(String key, Class<? extends Record> valueType) throws IOException { | ||
synchronized (this.getLock(key)) { | ||
FileChannel fileChannel = null; | ||
try { | ||
File file = new File(this.getFileName(key)); | ||
fileChannel = new FileInputStream(file).getChannel(); | ||
ByteBuffer buffer = ByteBuffer.allocate((int) file.length()); | ||
fileChannel.read(buffer); | ||
|
||
String json = new String(buffer.array(), StandardCharsets.UTF_8); | ||
if (StringUtils.isBlank(json)) { | ||
return null; | ||
} | ||
JSONObject jsonObject = JSON.parseObject(json); | ||
Datum datum = new Datum(); | ||
datum.timestamp.set(jsonObject.getLongValue("timestamp")); | ||
datum.key = jsonObject.getString("key"); | ||
datum.value = JSON.parseObject(jsonObject.getString("value"), valueType); | ||
return datum; | ||
} catch (IOException exception) { | ||
MetricsMonitor.getDiskException().increment(); | ||
throw exception; | ||
} finally { | ||
if (fileChannel != null) { | ||
fileChannel.close(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public void write(Datum datum) throws IOException { | ||
synchronized (this.getLock(datum.key)) { | ||
File file = new File(this.getFileName(datum.key)); | ||
if (!file.exists() && !file.getParentFile().mkdirs() && !file.createNewFile()) { | ||
MetricsMonitor.getDiskException().increment(); | ||
throw new IllegalStateException("can not make file: " + file.getName()); | ||
} | ||
|
||
FileChannel fileChannel = null; | ||
try { | ||
ByteBuffer data = ByteBuffer.wrap(JSON.toJSONString(datum).getBytes(StandardCharsets.UTF_8)); | ||
fileChannel = new FileOutputStream(file, false).getChannel(); | ||
fileChannel.write(data, data.position()); | ||
fileChannel.force(true); | ||
} catch (IOException exception) { | ||
MetricsMonitor.getDiskException().increment(); | ||
throw exception; | ||
} finally { | ||
if (fileChannel != null) { | ||
fileChannel.close(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public void remove(String key) { | ||
synchronized (this.getLock(key)) { | ||
File file = new File(this.getFileName(key)); | ||
if (file.exists() && !file.delete()) { | ||
throw new IllegalStateException("failed to delete datum: " + key); | ||
} | ||
} | ||
} | ||
|
||
public String getFileName(String key) { | ||
String namespaceId = KeyBuilder.getNamespace(key); | ||
String fileName; | ||
if (StringUtils.isNotBlank(namespaceId)) { | ||
fileName = this.getBasePath() + File.separator + namespaceId + File.separator + this.encodeFileName(key); | ||
} else { | ||
fileName = this.getBasePath() + File.separator + this.encodeFileName(key); | ||
} | ||
return fileName; | ||
} | ||
|
||
private String getLock(String key) { | ||
return this.getFileName(key).intern(); | ||
} | ||
|
||
private String encodeFileName(String fileName) { | ||
return fileName.replace(':', '#'); | ||
} | ||
|
||
private String decodeFileName(String fileName) { | ||
return fileName.replace("#", ":"); | ||
} | ||
|
||
|
||
} |
144 changes: 144 additions & 0 deletions
144
naming/src/test/java/com/alibaba/nacos/naming/consistency/weak/tree/TreeDataStoreTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.alibaba.nacos.naming.consistency.weak.tree; | ||
|
||
import com.alibaba.nacos.naming.consistency.Datum; | ||
import com.alibaba.nacos.naming.consistency.KeyBuilder; | ||
import com.alibaba.nacos.naming.core.Instance; | ||
import com.alibaba.nacos.naming.core.Instances; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
import org.springframework.test.util.ReflectionTestUtils; | ||
|
||
import java.io.File; | ||
import java.util.UUID; | ||
|
||
/** | ||
* @author lostcharlie | ||
*/ | ||
public class TreeDataStoreTest { | ||
@Test | ||
public void testReadDatum() throws Exception { | ||
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test"; | ||
TreeDataStore treeDataStore = new TreeDataStore(); | ||
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath); | ||
|
||
String namespaceId = UUID.randomUUID().toString(); | ||
String serviceName = UUID.randomUUID().toString(); | ||
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false); | ||
|
||
Instances value = new Instances(); | ||
Instance instance = new Instance("192.168.0.1", 8888); | ||
value.getInstanceList().add(instance); | ||
Datum datum = new Datum(); | ||
datum.key = key; | ||
datum.value = value; | ||
datum.timestamp.set(0L); | ||
|
||
treeDataStore.write(datum); | ||
Assert.assertTrue(new File(basePath).exists()); | ||
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath)); | ||
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists()); | ||
|
||
Datum actual = treeDataStore.read(datum.key, Instances.class); | ||
Assert.assertNotNull(actual); | ||
Assert.assertEquals(datum.key, actual.key); | ||
Assert.assertEquals(datum.timestamp.get(), actual.timestamp.get()); | ||
Assert.assertEquals(1, ((Instances) actual.value).getInstanceList().size()); | ||
Assert.assertTrue(((Instances) actual.value).getInstanceList().contains(instance)); | ||
|
||
TreeDataStoreTest.cleanUp(new File(basePath)); | ||
} | ||
|
||
@Test | ||
public void testWriteDatum() throws Exception { | ||
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test"; | ||
TreeDataStore treeDataStore = new TreeDataStore(); | ||
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath); | ||
|
||
String namespaceId = UUID.randomUUID().toString(); | ||
String serviceName = UUID.randomUUID().toString(); | ||
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false); | ||
|
||
Instances value = new Instances(); | ||
Instance instance = new Instance("192.168.0.1", 8888); | ||
value.getInstanceList().add(instance); | ||
Datum datum = new Datum(); | ||
datum.key = key; | ||
datum.value = value; | ||
datum.timestamp.set(0L); | ||
|
||
treeDataStore.write(datum); | ||
Assert.assertTrue(new File(basePath).exists()); | ||
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath)); | ||
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists()); | ||
|
||
TreeDataStoreTest.cleanUp(new File(basePath)); | ||
} | ||
|
||
@Test | ||
public void testRemoveDatum() throws Exception { | ||
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test"; | ||
TreeDataStore treeDataStore = new TreeDataStore(); | ||
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath); | ||
|
||
String namespaceId = UUID.randomUUID().toString(); | ||
String serviceName = UUID.randomUUID().toString(); | ||
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false); | ||
|
||
Instances value = new Instances(); | ||
Instance instance = new Instance("192.168.0.1", 8888); | ||
value.getInstanceList().add(instance); | ||
Datum datum = new Datum(); | ||
datum.key = key; | ||
datum.value = value; | ||
datum.timestamp.set(0L); | ||
|
||
treeDataStore.write(datum); | ||
Assert.assertTrue(new File(basePath).exists()); | ||
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath)); | ||
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists()); | ||
treeDataStore.remove(datum.key); | ||
Assert.assertFalse(new File(treeDataStore.getFileName(datum.key)).exists()); | ||
|
||
TreeDataStoreTest.cleanUp(new File(basePath)); | ||
} | ||
|
||
private static boolean cleanUp(File file) { | ||
if (!file.exists()) { | ||
return false; | ||
} | ||
if (file.isDirectory()) { | ||
File[] files = file.listFiles(); | ||
for (File subFile : files) { | ||
boolean success = cleanUp(subFile); | ||
if (!success) { | ||
return false; | ||
} | ||
} | ||
} else {// is a regular file | ||
boolean success = file.delete(); | ||
if (!success) { | ||
return false; | ||
} | ||
} | ||
if (file.isDirectory()) { | ||
return file.delete(); | ||
} else { | ||
return true; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters