diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java index 92b520d72f..2d4863149d 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java @@ -36,9 +36,9 @@ public class MD5Utils { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - private static MessageDigest mdInst; + private MessageDigest mdInst; - static { + public MD5Utils() { try { mdInst = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { @@ -46,9 +46,17 @@ public class MD5Utils { } } - public static String getMd5(String value) { - mdInst.update(value.getBytes(UTF_8)); - byte[] md5 = mdInst.digest(); + /** + * Calculation md5 value of specify string + * @param input + */ + public String getMd5(String input) { + byte[] md5; + // MessageDigest instance is NOT thread-safe + synchronized (mdInst) { + mdInst.update(input.getBytes(UTF_8)); + md5 = mdInst.digest(); + } int j = md5.length; char str[] = new char[j * 2]; @@ -61,5 +69,4 @@ public class MD5Utils { return new String(str); } - } diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java new file mode 100644 index 0000000000..4a5ca05709 --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MD5UtilsTest { + + @Test + public void test() { + MD5Utils sharedMd5Utils = new MD5Utils(); + final String[] input = {"provider-appgroup-one/org.apache.dubbo.config.spring.api.HelloService:dubboorg.apache.dubbo.config.spring.api.HelloService{REGISTRY_CLUSTER=registry-one, anyhost=true, application=provider-app, background=false, compiler=javassist, deprecated=false, dubbo=2.0.2, dynamic=true, file.cache=false, generic=false, group=group-one, interface=org.apache.dubbo.config.spring.api.HelloService, logger=slf4j, metadata-type=remote, methods=sayHello, organization=test, owner=com.test, release=, service-name-mapping=true, side=provider}", + "provider-appgroup-two/org.apache.dubbo.config.spring.api.DemoService:dubboorg.apache.dubbo.config.spring.api.DemoService{REGISTRY_CLUSTER=registry-two, anyhost=true, application=provider-app, background=false, compiler=javassist, deprecated=false, dubbo=2.0.2, dynamic=true, file.cache=false, generic=false, group=group-two, interface=org.apache.dubbo.config.spring.api.DemoService, logger=slf4j, metadata-type=remote, methods=sayName,getBox, organization=test, owner=com.test, release=, service-name-mapping=true, side=provider}"}; + final String[] result = {sharedMd5Utils.getMd5(input[0]), new MD5Utils().getMd5(input[1])}; + + System.out.println("Expected result: " + Arrays.asList(result)); + int nThreads = 8; + CountDownLatch latch = new CountDownLatch(nThreads); + List errors = Collections.synchronizedList(new ArrayList<>()); + ExecutorService executorService = Executors.newFixedThreadPool(nThreads); + try { + + for (int i = 0; i < nThreads; i++) { + MD5Utils md5Utils = i < nThreads / 2 ? sharedMd5Utils : new MD5Utils(); + executorService.submit(new Md5Task(input[i % 2], result[i % 2], md5Utils, latch, errors)); + } + latch.await(); + Assertions.assertEquals(Collections.EMPTY_LIST, errors); + Assertions.assertEquals(0, latch.getCount()); + } catch (Throwable e) { + Assertions.fail(StringUtils.toString(e)); + } finally { + executorService.shutdown(); + } + } + + static class Md5Task implements Runnable { + + private final String input; + private final String expected; + private final MD5Utils md5Utils; + private final CountDownLatch latch; + private final List errorCollector; + + public Md5Task(String input, String expected, MD5Utils md5Utils, CountDownLatch latch, List errorCollector) { + this.input = input; + this.expected = expected; + this.md5Utils = md5Utils; + this.latch = latch; + this.errorCollector = errorCollector; + } + + @Override + public void run() { + int i = 0; + long start = System.currentTimeMillis(); + try { + for (; i < 200; i++) { + Assertions.assertEquals(expected, md5Utils.getMd5(input)); + md5Utils.getMd5("test#" + i); + } + } catch (Throwable e) { + errorCollector.add(e); + e.printStackTrace(); + } finally { + long cost = System.currentTimeMillis() - start; + System.out.println("[" + Thread.currentThread().getName() + "] progress: " + i + ", cost: " + cost); + latch.countDown(); + } + } + } +} diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java index 83a0facf40..7d35543398 100644 --- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java @@ -85,6 +85,8 @@ public class NacosDynamicConfiguration implements DynamicConfiguration { */ private final Map watchListenerMap; + private MD5Utils md5Utils = new MD5Utils(); + NacosDynamicConfiguration(URL url) { this.nacosProperties = buildNacosProperties(url); this.configService = buildConfigService(url); @@ -221,7 +223,7 @@ public class NacosDynamicConfiguration implements DynamicConfiguration { String content = getConfig(key, group); String casMd5 = ""; if (StringUtils.isNotEmpty(content)) { - casMd5 = MD5Utils.getMd5(content); + casMd5 = md5Utils.getMd5(content); } return new ConfigItem(content, casMd5); } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java index 155295c7f2..a1c9e19169 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java @@ -23,8 +23,10 @@ public class RevisionResolver { public static final String EMPTY_REVISION = "0"; + private static MD5Utils md5Utils = new MD5Utils(); + public static String calRevision(String metadata) { - return MD5Utils.getMd5(metadata); + return md5Utils.getMd5(metadata); } } diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java index 30b26b7e38..23e866aec7 100644 --- a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java +++ b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java @@ -17,6 +17,11 @@ package org.apache.dubbo.metadata.store.nacos; +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.config.listener.AbstractSharedListener; +import com.alibaba.nacos.api.exception.NacosException; +import com.google.gson.Gson; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.configcenter.ConfigChangeType; import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; @@ -36,12 +41,6 @@ import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier; import org.apache.dubbo.metadata.report.support.AbstractMetadataReport; import org.apache.dubbo.rpc.RpcException; -import com.alibaba.nacos.api.NacosFactory; -import com.alibaba.nacos.api.PropertyKeyConst; -import com.alibaba.nacos.api.config.listener.AbstractSharedListener; -import com.alibaba.nacos.api.exception.NacosException; -import com.google.gson.Gson; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -80,6 +79,7 @@ public class NacosMetadataReport extends AbstractMetadataReport { private Map casListenerMap = new ConcurrentHashMap<>(); + private MD5Utils md5Utils = new MD5Utils(); public NacosMetadataReport(URL url) { super(url); @@ -226,7 +226,7 @@ public class NacosMetadataReport extends AbstractMetadataReport { String content = getConfig(key, group); String casMd5 = ""; if (StringUtils.isNotEmpty(content)) { - casMd5 = MD5Utils.getMd5(content); + casMd5 = md5Utils.getMd5(content); } return new ConfigItem(content, casMd5); }