You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2020/04/14 03:40:20 UTC

[dubbo] branch master updated: [Dubbo-Performance] Reduce memory allocation for URL parse (#5880)

This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/master by this push:
     new afa2dc9  [Dubbo-Performance] Reduce memory allocation for URL parse (#5880)
afa2dc9 is described below

commit afa2dc9a1ea2cd8e3cb292a47e7fc34f2acca036
Author: LinShunKang <li...@gmail.com>
AuthorDate: Tue Apr 14 11:40:06 2020 +0800

    [Dubbo-Performance] Reduce memory allocation for URL parse (#5880)
---
 .../src/main/java/org/apache/dubbo/common/URL.java |  15 +-
 .../java/org/apache/dubbo/common/URLStrParser.java | 348 +++++++++++++++++++++
 .../dubbo/common/constants/CommonConstants.java    |   4 +
 .../org/apache/dubbo/common/utils/StringUtils.java |  51 +++
 .../org/apache/dubbo/common/utils/Utf8Utils.java   | 229 ++++++++++++++
 .../org/apache/dubbo/common/URLStrParserTest.java  |  40 +++
 .../test/java/org/apache/dubbo/common/URLTest.java | 119 ++++++-
 .../registry/zookeeper/ZookeeperRegistry.java      |   8 +-
 .../remoting/exchange/support/DefaultFuture.java   |   2 +-
 9 files changed, 797 insertions(+), 19 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
index e82cd63..e605643 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
@@ -225,6 +225,8 @@ class URL implements Serializable {
     }
 
     /**
+     * NOTICE: This method allocate too much objects, we can use {@link URLStrParser#parseDecodedStr(String)} instead.
+     * <p>
      * Parse url string
      *
      * @param url URL string
@@ -326,13 +328,16 @@ class URL implements Serializable {
 
         String methodsString = parameters.get(METHODS_KEY);
         if (StringUtils.isNotEmpty(methodsString)) {
-            String[] methods = methodsString.split(",");
+            List<String> methods = StringUtils.splitToList(methodsString, ',');
             for (Map.Entry<String, String> entry : parameters.entrySet()) {
                 String key = entry.getKey();
-                for (String method : methods) {
-                    String methodPrefix = method + '.';
-                    if (key.startsWith(methodPrefix)) {
-                        String realKey = key.substring(methodPrefix.length());
+                for (int i = 0; i < methods.size(); i++) {
+                    String method = methods.get(i);
+                    int methodLen = method.length();
+                    if (key.length() > methodLen
+                            && key.startsWith(method)
+                            && key.charAt(methodLen) == '.') {//equals to: key.startsWith(method + '.')
+                        String realKey = key.substring(methodLen + 1);
                         URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
                     }
                 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java
new file mode 100644
index 0000000..37afb78
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java
@@ -0,0 +1,348 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING;
+import static org.apache.dubbo.common.utils.StringUtils.decodeHexByte;
+import static org.apache.dubbo.common.utils.Utf8Utils.decodeUtf8;
+
+public final class URLStrParser {
+
+    private static final char SPACE = 0x20;
+
+    private static final ThreadLocal<TempBuf> DECODE_TEMP_BUF = ThreadLocal.withInitial(() -> new TempBuf(1024));
+
+    private URLStrParser() {
+        //empty
+    }
+
+    /**
+     * @param decodedURLStr : after {@link URL#decode} string
+     *                      decodedURLStr format: protocol://username:password@host:port/path?k1=v1&k2=v2
+     *                      [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
+     */
+    public static URL parseDecodedStr(String decodedURLStr) {
+        Map<String, String> parameters = null;
+        int pathEndIdx = decodedURLStr.indexOf('?');
+        if (pathEndIdx >= 0) {
+            parameters = parseDecodedParams(decodedURLStr, pathEndIdx + 1);
+        } else {
+            pathEndIdx = decodedURLStr.length();
+        }
+
+        String decodedBody = decodedURLStr.substring(0, pathEndIdx);
+        return parseURLBody(decodedURLStr, decodedBody, parameters);
+    }
+
+    private static Map<String, String> parseDecodedParams(String str, int from) {
+        int len = str.length();
+        if (from >= len) {
+            return Collections.emptyMap();
+        }
+
+        TempBuf tempBuf = DECODE_TEMP_BUF.get();
+        Map<String, String> params = new HashMap<>();
+        int nameStart = from;
+        int valueStart = -1;
+        int i;
+        for (i = from; i < len; i++) {
+            char ch = str.charAt(i);
+            switch (ch) {
+                case '=':
+                    if (nameStart == i) {
+                        nameStart = i + 1;
+                    } else if (valueStart < nameStart) {
+                        valueStart = i + 1;
+                    }
+                    break;
+                case ';':
+                case '&':
+                    addParam(str, false, nameStart, valueStart, i, params, tempBuf);
+                    nameStart = i + 1;
+                    break;
+                default:
+                    // continue
+            }
+        }
+        addParam(str, false, nameStart, valueStart, i, params, tempBuf);
+        return params;
+    }
+
+    /**
+     * @param fullURLStr  : fullURLString
+     * @param decodedBody : format: [protocol://][username:password@][host:port]/[path]
+     * @param parameters  :
+     * @return URL
+     */
+    private static URL parseURLBody(String fullURLStr, String decodedBody, Map<String, String> parameters) {
+        int starIdx = 0, endIdx = decodedBody.length();
+        String protocol = null;
+        int protoEndIdx = decodedBody.indexOf("://");
+        if (protoEndIdx >= 0) {
+            if (protoEndIdx == 0) {
+                throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\"");
+            }
+            protocol = decodedBody.substring(0, protoEndIdx);
+            starIdx = protoEndIdx + 3;
+        } else {
+            // case: file:/path/to/file.txt
+            protoEndIdx = decodedBody.indexOf(":/");
+            if (protoEndIdx >= 0) {
+                if (protoEndIdx == 0) {
+                    throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\"");
+                }
+                protocol = decodedBody.substring(0, protoEndIdx);
+                starIdx = protoEndIdx + 1;
+            }
+        }
+
+        String path = null;
+        int pathStartIdx = indexOf(decodedBody, '/', starIdx, endIdx);
+        if (pathStartIdx >= 0) {
+            path = decodedBody.substring(pathStartIdx + 1);
+            endIdx = pathStartIdx;
+        }
+
+        String username = null;
+        String password = null;
+        int pwdEndIdx = lastIndexOf(decodedBody, '@', starIdx, endIdx);
+        if (pwdEndIdx > 0) {
+            int userNameEndIdx = indexOf(decodedBody, ':', starIdx, pwdEndIdx);
+            username = decodedBody.substring(starIdx, userNameEndIdx);
+            password = decodedBody.substring(userNameEndIdx + 1, pwdEndIdx);
+            starIdx = pwdEndIdx + 1;
+        }
+
+        String host = null;
+        int port = 0;
+        int hostEndIdx = lastIndexOf(decodedBody, ':', starIdx, endIdx);
+        if (hostEndIdx > 0 && hostEndIdx < decodedBody.length() - 1) {
+            if (lastIndexOf(decodedBody, '%', starIdx, endIdx) > hostEndIdx) {
+                // ipv6 address with scope id
+                // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
+                // see https://howdoesinternetwork.com/2013/ipv6-zone-id
+                // ignore
+            } else {
+                port = Integer.parseInt(decodedBody.substring(hostEndIdx + 1, endIdx));
+                endIdx = hostEndIdx;
+            }
+        }
+
+        if (endIdx > starIdx) {
+            host = decodedBody.substring(starIdx, endIdx);
+        }
+        return new URL(protocol, username, password, host, port, path, parameters);
+    }
+
+    /**
+     * @param encodedURLStr : after {@link URL#encode(String)} string
+     *                      encodedURLStr after decode format: protocol://username:password@host:port/path?k1=v1&k2=v2
+     *                      [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
+     */
+    public static URL parseEncodedStr(String encodedURLStr) {
+        Map<String, String> parameters = null;
+        int pathEndIdx = encodedURLStr.indexOf("%3F");// '?'
+        if (pathEndIdx >= 0) {
+            parameters = parseEncodedParams(encodedURLStr, pathEndIdx + 3);
+        } else {
+            pathEndIdx = encodedURLStr.length();
+        }
+
+        //decodedBody format: [protocol://][username:password@][host:port]/[path]
+        String decodedBody = decodeComponent(encodedURLStr, 0, pathEndIdx, false, DECODE_TEMP_BUF.get());
+        return parseURLBody(encodedURLStr, decodedBody, parameters);
+    }
+
+    private static Map<String, String> parseEncodedParams(String str, int from) {
+        int len = str.length();
+        if (from >= len) {
+            return Collections.emptyMap();
+        }
+
+        TempBuf tempBuf = DECODE_TEMP_BUF.get();
+        Map<String, String> params = new HashMap<>();
+        int nameStart = from;
+        int valueStart = -1;
+        int i;
+        for (i = from; i < len; i++) {
+            char ch = str.charAt(i);
+            if (ch == '%') {
+                if (i + 3 > len) {
+                    throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str);
+                }
+                ch = (char) decodeHexByte(str, i + 1);
+                i += 2;
+            }
+
+            switch (ch) {
+                case '=':
+                    if (nameStart == i) {
+                        nameStart = i + 1;
+                    } else if (valueStart < nameStart) {
+                        valueStart = i + 1;
+                    }
+                    break;
+                case ';':
+                case '&':
+                    addParam(str, true, nameStart, valueStart, i - 2, params, tempBuf);
+                    nameStart = i + 1;
+                    break;
+                default:
+                    // continue
+            }
+        }
+        addParam(str, true, nameStart, valueStart, i, params, tempBuf);
+        return params;
+    }
+
+    private static boolean addParam(String str, boolean isEncoded, int nameStart, int valueStart, int valueEnd, Map<String, String> params,
+                                    TempBuf tempBuf) {
+        if (nameStart >= valueEnd) {
+            return false;
+        }
+
+        if (valueStart <= nameStart) {
+            valueStart = valueEnd + 1;
+        }
+
+        if (isEncoded) {
+            String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
+            String value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
+            params.put(name, value);
+        } else {
+            String name = str.substring(nameStart, valueStart -1);
+            String value = str.substring(valueStart, valueEnd);
+            params.put(name, value);
+        }
+        return true;
+    }
+
+    private static String decodeComponent(String s, int from, int toExcluded, boolean isPath, TempBuf tempBuf) {
+        int len = toExcluded - from;
+        if (len <= 0) {
+            return EMPTY_STRING;
+        }
+
+        int firstEscaped = -1;
+        for (int i = from; i < toExcluded; i++) {
+            char c = s.charAt(i);
+            if (c == '%' || c == '+' && !isPath) {
+                firstEscaped = i;
+                break;
+            }
+        }
+        if (firstEscaped == -1) {
+            return s.substring(from, toExcluded);
+        }
+
+        // Each encoded byte takes 3 characters (e.g. "%20")
+        int decodedCapacity = (toExcluded - firstEscaped) / 3;
+        byte[] buf = tempBuf.byteBuf(decodedCapacity);
+        char[] charBuf = tempBuf.charBuf(len);
+        s.getChars(from, firstEscaped, charBuf, 0);
+
+        int charBufIdx = firstEscaped - from;
+        return decodeUtf8Component(s, firstEscaped, toExcluded, isPath, buf, charBuf, charBufIdx);
+    }
+
+    private static String decodeUtf8Component(String str, int firstEscaped, int toExcluded, boolean isPath, byte[] buf,
+                                              char[] charBuf, int charBufIdx) {
+        int bufIdx;
+        for (int i = firstEscaped; i < toExcluded; i++) {
+            char c = str.charAt(i);
+            if (c != '%') {
+                charBuf[charBufIdx++] = c != '+' || isPath ? c : SPACE;
+                continue;
+            }
+
+            bufIdx = 0;
+            do {
+                if (i + 3 > toExcluded) {
+                    throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str);
+                }
+                buf[bufIdx++] = decodeHexByte(str, i + 1);
+                i += 3;
+            } while (i < toExcluded && str.charAt(i) == '%');
+            i--;
+
+            charBufIdx += decodeUtf8(buf, 0, bufIdx, charBuf, charBufIdx);
+        }
+        return new String(charBuf, 0, charBufIdx);
+    }
+
+    private static int indexOf(String str, char ch, int from, int toExclude) {
+        from = Math.max(from, 0);
+        toExclude = Math.min(toExclude, str.length());
+        if (from > toExclude) {
+            return -1;
+        }
+
+        for (int i = from; i < toExclude; i++) {
+            if (str.charAt(i) == ch) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static int lastIndexOf(String str, char ch, int from, int toExclude) {
+        from = Math.max(from, 0);
+        toExclude = Math.min(toExclude, str.length() - 1);
+        if (from > toExclude) {
+            return -1;
+        }
+
+        for (int i = toExclude; i >= from; i--) {
+            if (str.charAt(i) == ch) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static final class TempBuf {
+
+        private final char[] chars;
+
+        private final byte[] bytes;
+
+        TempBuf(int bufSize) {
+            this.chars = new char[bufSize];
+            this.bytes = new byte[bufSize];
+        }
+
+        public char[] charBuf(int size) {
+            char[] chars = this.chars;
+            if (size <= chars.length) {
+                return chars;
+            }
+            return new char[size];
+        }
+
+        public byte[] byteBuf(int size) {
+            byte[] bytes = this.bytes;
+            if (size <= bytes.length) {
+                return bytes;
+            }
+            return new byte[size];
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
index a941484..fe410e2 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
@@ -17,6 +17,8 @@
 
 package org.apache.dubbo.common.constants;
 
+import org.apache.dubbo.common.URL;
+
 import java.net.NetworkInterface;
 import java.util.concurrent.ExecutorService;
 import java.util.regex.Pattern;
@@ -52,6 +54,8 @@ public interface CommonConstants {
 
     String PROTOCOL_SEPARATOR = "://";
 
+    String PROTOCOL_SEPARATOR_ENCODED = URL.encode(PROTOCOL_SEPARATOR);
+
     String REGISTRY_SEPARATOR = "|";
 
     Pattern REGISTRY_SPLIT_PATTERN = Pattern.compile("\\s*[|;]+\\s*");
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
index 9b91032..42ee05b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
@@ -24,6 +24,7 @@ import com.alibaba.fastjson.JSON;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -60,6 +61,8 @@ public final class StringUtils {
     private static final Pattern PARAMETERS_PATTERN = Pattern.compile("^\\[((\\s*\\{\\s*[\\w_\\-\\.]+\\s*:\\s*.+?\\s*\\}\\s*,?\\s*)+)\\s*\\]$");
     private static final Pattern PAIR_PARAMETERS_PATTERN = Pattern.compile("^\\{\\s*([\\w-_\\.]+)\\s*:\\s*(.+)\\s*\\}$");
     private static final int PAD_LIMIT = 8192;
+    private static final byte[] HEX2B;
+
 
     /**
      * @since 2.7.5
@@ -88,6 +91,33 @@ public final class StringUtils {
 
     public static final String HYPHEN = valueOf(HYPHEN_CHAR);
 
+    static {
+        HEX2B = new byte[128];
+        Arrays.fill(HEX2B, (byte) -1);
+        HEX2B['0'] = (byte) 0;
+        HEX2B['1'] = (byte) 1;
+        HEX2B['2'] = (byte) 2;
+        HEX2B['3'] = (byte) 3;
+        HEX2B['4'] = (byte) 4;
+        HEX2B['5'] = (byte) 5;
+        HEX2B['6'] = (byte) 6;
+        HEX2B['7'] = (byte) 7;
+        HEX2B['8'] = (byte) 8;
+        HEX2B['9'] = (byte) 9;
+        HEX2B['A'] = (byte) 10;
+        HEX2B['B'] = (byte) 11;
+        HEX2B['C'] = (byte) 12;
+        HEX2B['D'] = (byte) 13;
+        HEX2B['E'] = (byte) 14;
+        HEX2B['F'] = (byte) 15;
+        HEX2B['a'] = (byte) 10;
+        HEX2B['b'] = (byte) 11;
+        HEX2B['c'] = (byte) 12;
+        HEX2B['d'] = (byte) 13;
+        HEX2B['e'] = (byte) 14;
+        HEX2B['f'] = (byte) 15;
+    }
+
     private StringUtils() {
     }
 
@@ -1000,4 +1030,25 @@ public final class StringUtils {
         }
         return parameters;
     }
+
+    public static int decodeHexNibble(final char c) {
+        // Character.digit() is not used here, as it addresses a larger
+        // set of characters (both ASCII and full-width latin letters).
+        byte[] hex2b = HEX2B;
+        return c < hex2b.length ? hex2b[c] : -1;
+    }
+
+    /**
+     * Decode a 2-digit hex byte from within a string.
+     */
+    public static byte decodeHexByte(CharSequence s, int pos) {
+        int hi = decodeHexNibble(s.charAt(pos));
+        int lo = decodeHexNibble(s.charAt(pos + 1));
+        if (hi == -1 || lo == -1) {
+            throw new IllegalArgumentException(String.format(
+                    "invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s));
+        }
+        return (byte) ((hi << 4) + lo);
+    }
+
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java
new file mode 100644
index 0000000..09d5508
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java
@@ -0,0 +1,229 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+package org.apache.dubbo.common.utils;
+
+import static java.lang.Character.MIN_HIGH_SURROGATE;
+import static java.lang.Character.MIN_LOW_SURROGATE;
+import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
+
+/**
+ * See original <a href=
+ * "https://github.com/protocolbuffers/protobuf/blob/master/java/core/src/main/java/com/google/protobuf/Utf8.java"
+ * >Utf8.java</a>
+ */
+public final class Utf8Utils {
+
+    private Utf8Utils() {
+        //empty
+    }
+
+    public static int decodeUtf8(byte[] srcBytes, int srcIdx, int srcSize, char[] destChars, int destIdx) {
+        // Bitwise OR combines the sign bits so any negative value fails the check.
+        if ((srcIdx | srcSize | srcBytes.length - srcIdx - srcSize) < 0
+                || (destIdx | destChars.length - destIdx - srcSize) < 0) {
+            String exMsg = String.format("buffer srcBytes.length=%d, srcIdx=%d, srcSize=%d, destChars.length=%d, " +
+                    "destIdx=%d", srcBytes.length, srcIdx, srcSize, destChars.length, destIdx);
+            throw new ArrayIndexOutOfBoundsException(
+                    exMsg);
+        }
+
+        int offset = srcIdx;
+        final int limit = offset + srcSize;
+        final int destIdx0 = destIdx;
+
+        // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
+        // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
+        while (offset < limit) {
+            byte b = srcBytes[offset];
+            if (!DecodeUtil.isOneByte(b)) {
+                break;
+            }
+            offset++;
+            DecodeUtil.handleOneByteSafe(b, destChars, destIdx++);
+        }
+
+        while (offset < limit) {
+            byte byte1 = srcBytes[offset++];
+            if (DecodeUtil.isOneByte(byte1)) {
+                DecodeUtil.handleOneByteSafe(byte1, destChars, destIdx++);
+                // It's common for there to be multiple ASCII characters in a run mixed in, so add an
+                // extra optimized loop to take care of these runs.
+                while (offset < limit) {
+                    byte b = srcBytes[offset];
+                    if (!DecodeUtil.isOneByte(b)) {
+                        break;
+                    }
+                    offset++;
+                    DecodeUtil.handleOneByteSafe(b, destChars, destIdx++);
+                }
+            } else if (DecodeUtil.isTwoBytes(byte1)) {
+                if (offset >= limit) {
+                    throw new IllegalArgumentException("invalid UTF-8.");
+                }
+                DecodeUtil.handleTwoBytesSafe(byte1, /* byte2 */ srcBytes[offset++], destChars, destIdx++);
+            } else if (DecodeUtil.isThreeBytes(byte1)) {
+                if (offset >= limit - 1) {
+                    throw new IllegalArgumentException("invalid UTF-8.");
+                }
+                DecodeUtil.handleThreeBytesSafe(
+                        byte1,
+                        /* byte2 */ srcBytes[offset++],
+                        /* byte3 */ srcBytes[offset++],
+                        destChars,
+                        destIdx++);
+            } else {
+                if (offset >= limit - 2) {
+                    throw new IllegalArgumentException("invalid UTF-8.");
+                }
+                DecodeUtil.handleFourBytesSafe(
+                        byte1,
+                        /* byte2 */ srcBytes[offset++],
+                        /* byte3 */ srcBytes[offset++],
+                        /* byte4 */ srcBytes[offset++],
+                        destChars,
+                        destIdx);
+                destIdx += 2;
+            }
+        }
+        return destIdx - destIdx0;
+    }
+
+
+    private static class DecodeUtil {
+
+        /**
+         * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'.
+         */
+        private static boolean isOneByte(byte b) {
+            return b >= 0;
+        }
+
+        /**
+         * Returns whether this is a two-byte codepoint with the form '10XXXXXX'.
+         */
+        private static boolean isTwoBytes(byte b) {
+            return b < (byte) 0xE0;
+        }
+
+        /**
+         * Returns whether this is a three-byte codepoint with the form '110XXXXX'.
+         */
+        private static boolean isThreeBytes(byte b) {
+            return b < (byte) 0xF0;
+        }
+
+        private static void handleOneByteSafe(byte byte1, char[] resultArr, int resultPos) {
+            resultArr[resultPos] = (char) byte1;
+        }
+
+        private static void handleTwoBytesSafe(byte byte1, byte byte2, char[] resultArr, int resultPos) {
+            checkUtf8(byte1, byte2);
+            resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2));
+        }
+
+        private static void checkUtf8(byte byte1, byte byte2) {
+            // Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and
+            // overlong 2-byte, '11000001'.
+            if (byte1 < (byte) 0xC2 || isNotTrailingByte(byte2)) {
+                throw new IllegalArgumentException("invalid UTF-8.");
+            }
+        }
+
+        private static void handleThreeBytesSafe(byte byte1, byte byte2, byte byte3, char[] resultArr, int resultPos) {
+            checkUtf8(byte1, byte2, byte3);
+            resultArr[resultPos] =
+                    (char) (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | trailingByteValue(byte3));
+        }
+
+        private static void checkUtf8(byte byte1, byte byte2, byte byte3) {
+            if (isNotTrailingByte(byte2)
+                    // overlong? 5 most significant bits must not all be zero
+                    || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0)
+                    // check for illegal surrogate codepoints
+                    || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
+                    || isNotTrailingByte(byte3)) {
+                throw new IllegalArgumentException("invalid UTF-8.");
+            }
+        }
+
+        private static void handleFourBytesSafe(byte byte1, byte byte2, byte byte3, byte byte4, char[] resultArr,
+                                                int resultPos) {
+            checkUtf8(byte1, byte2, byte3, byte4);
+            int codepoint =
+                    ((byte1 & 0x07) << 18)
+                            | (trailingByteValue(byte2) << 12)
+                            | (trailingByteValue(byte3) << 6)
+                            | trailingByteValue(byte4);
+
+            resultArr[resultPos] = DecodeUtil.highSurrogate(codepoint);
+            resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codepoint);
+        }
+
+        private static void checkUtf8(byte byte1, byte byte2, byte byte3, byte byte4) {
+            if (isNotTrailingByte(byte2)
+                    // Check that 1 <= plane <= 16.  Tricky optimized form of:
+                    //   valid 4-byte leading byte?
+                    // if (byte1 > (byte) 0xF4 ||
+                    //   overlong? 4 most significant bits must not all be zero
+                    //     byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 ||
+                    //   codepoint larger than the highest code point (U+10FFFF)?
+                    //     byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F)
+                    || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0
+                    || isNotTrailingByte(byte3)
+                    || isNotTrailingByte(byte4)) {
+                throw new IllegalArgumentException("invalid UTF-8.");
+            }
+        }
+
+        /**
+         * Returns whether the byte is not a valid continuation of the form '10XXXXXX'.
+         */
+        private static boolean isNotTrailingByte(byte b) {
+            return b > (byte) 0xBF;
+        }
+
+        /**
+         * Returns the actual value of the trailing byte (removes the prefix '10') for composition.
+         */
+        private static int trailingByteValue(byte b) {
+            return b & 0x3F;
+        }
+
+        private static char highSurrogate(int codePoint) {
+            return (char)
+                    ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)) + (codePoint >>> 10));
+        }
+
+        private static char lowSurrogate(int codePoint) {
+            return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff));
+        }
+    }
+
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java
new file mode 100644
index 0000000..3ca62ce
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Created by LinShunkang on 2020/03/12
+ */
+public class URLStrParserTest {
+
+    @Test
+    public void test() {
+        String str = "dubbo%3A%2F%2Fadmin%3Aadmin123%40192.168.1.41%3A28113%2Forg.test.api.DemoService%24Iface%3Fanyhost%3Dtrue%26application%3Ddemo-service%26dubbo%3D2.6.1%26generic%3Dfalse%26interface%3Dorg.test.api.DemoService%24Iface%26methods%3DorbCompare%2CcheckText%2CcheckPicture%26pid%3D65557%26revision%3D1.4.17%26service.filter%3DbootMetrics%26side%3Dprovider%26status%3Dserver%26threads%3D200%26timestamp%3D1583136298859%26version%3D1.0.0";
+        System.out.println(URLStrParser.parseEncodedStr(str));
+
+        String decodeStr = URL.decode(str);
+        URL originalUrl = URL.valueOf(decodeStr);
+        assertThat(URLStrParser.parseEncodedStr(str), equalTo(originalUrl));
+        assertThat(URLStrParser.parseDecodedStr(decodeStr), equalTo(originalUrl));
+    }
+
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
index 1ef4d1d..db5f57b 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
@@ -40,6 +40,7 @@ public class URLTest {
     @Test
     public void test_valueOf_noProtocolAndHost() throws Exception {
         URL url = URL.valueOf("/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -51,9 +52,9 @@ public class URLTest {
         assertEquals("1.0.0", url.getParameter("version"));
         assertEquals("morgan", url.getParameter("application"));
 
-
         url = URL.valueOf("context/path?version=1.0.0&application=morgan");
         //                 ^^^^^^^ Caution , parse as host
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -65,9 +66,19 @@ public class URLTest {
         assertEquals("morgan", url.getParameter("application"));
     }
 
+    private void assertURLStrDecoder(URL url) {
+        String fullURLStr = url.toFullString();
+        URL newUrl =  URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
+        assertEquals(URL.valueOf(fullURLStr), newUrl);
+
+        URL newUrl2 =  URLStrParser.parseDecodedStr(fullURLStr);
+        assertEquals(URL.valueOf(fullURLStr), newUrl2);
+    }
+
     @Test
     public void test_valueOf_noProtocol() throws Exception {
         URL url = URL.valueOf("10.20.130.230");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -78,6 +89,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("10.20.130.230:20880");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -88,6 +100,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("10.20.130.230/context/path");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -98,6 +111,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("10.20.130.230:20880/context/path");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -108,6 +122,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -123,6 +138,7 @@ public class URLTest {
     @Test
     public void test_valueOf_noHost() throws Exception {
         URL url = URL.valueOf("file:///home/user1/router.js");
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -135,6 +151,7 @@ public class URLTest {
         // Caution!!
         url = URL.valueOf("file://home/user1/router.js");
         //                      ^^ only tow slash!
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -145,6 +162,7 @@ public class URLTest {
 
 
         url = URL.valueOf("file:/home/user1/router.js");
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -155,6 +173,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("file:///d:/home/user1/router.js");
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -165,6 +184,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("file:///home/user1/router.js?p1=v1&p2=v2");
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -179,6 +199,7 @@ public class URLTest {
         assertEquals(params, url.getParameters());
 
         url = URL.valueOf("file:/home/user1/router.js?p1=v1&p2=v2");
+        assertURLStrDecoder(url);
         assertEquals("file", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -196,6 +217,7 @@ public class URLTest {
     @Test
     public void test_valueOf_WithProtocolHost() throws Exception {
         URL url = URL.valueOf("dubbo://10.20.130.230");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -206,6 +228,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("dubbo://10.20.130.230:20880/context/path");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertNull(url.getUsername());
         assertNull(url.getPassword());
@@ -216,6 +239,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -226,6 +250,7 @@ public class URLTest {
         assertEquals(0, url.getParameters().size());
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880?version=1.0.0");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -237,6 +262,7 @@ public class URLTest {
         assertEquals("1.0.0", url.getParameter("version"));
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -249,6 +275,7 @@ public class URLTest {
         assertEquals("morgan", url.getParameter("application"));
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -266,6 +293,7 @@ public class URLTest {
     @Test
     public void test_valueOf_spaceSafe() throws Exception {
         URL url = URL.valueOf("http://1.2.3.4:8080/path?key=value1 value2");
+        assertURLStrDecoder(url);
         assertEquals("http://1.2.3.4:8080/path?key=value1 value2", url.toString());
         assertEquals("value1 value2", url.getParameter("key"));
     }
@@ -274,6 +302,7 @@ public class URLTest {
     public void test_noValueKey() throws Exception {
         URL url = URL.valueOf("http://1.2.3.4:8080/path?k0&k1=v1");
 
+        assertURLStrDecoder(url);
         assertTrue(url.hasParameter("k0"));
 
         // If a Key has no corresponding Value, then the Key also used as the Value.
@@ -288,38 +317,59 @@ public class URLTest {
         } catch (IllegalStateException expected) {
             assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
         }
+
+        try {
+            String encodedURLStr = URL.encode("://1.2.3.4:8080/path");
+            URLStrParser.parseEncodedStr(encodedURLStr);
+            fail();
+        } catch (IllegalStateException expected) {
+            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", URL.decode(expected.getMessage()));
+        }
+
+        try {
+            URLStrParser.parseDecodedStr("://1.2.3.4:8080/path");
+            fail();
+        } catch (IllegalStateException expected) {
+            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
+        }
     }
 
     @Test
     public void test_getAddress() throws Exception {
         URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
         assertEquals("10.20.130.230:20880", url1.getAddress());
     }
 
     @Test
     public void test_getAbsolutePath() throws Exception {
         URL url = new URL("p1", "1.2.2.2", 33);
+        assertURLStrDecoder(url);
         assertNull(url.getAbsolutePath());
 
         url = new URL("file", null, 90, "/home/user1/route.js");
+        assertURLStrDecoder(url);
         assertEquals("/home/user1/route.js", url.getAbsolutePath());
     }
 
     @Test
     public void test_equals() throws Exception {
         URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
 
         Map<String, String> params = new HashMap<String, String>();
         params.put("version", "1.0.0");
         params.put("application", "morgan");
         URL url2 = new URL("dubbo", "admin", "hello1234", "10.20.130.230", 20880, "context/path", params);
 
+        assertURLStrDecoder(url2);
         assertEquals(url1, url2);
     }
 
     @Test
     public void test_toString() throws Exception {
         URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
         assertThat(url1.toString(), anyOf(
                 equalTo("dubbo://10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
                 equalTo("dubbo://10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
@@ -329,6 +379,7 @@ public class URLTest {
     @Test
     public void test_toFullString() throws Exception {
         URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
         assertThat(url1.toFullString(), anyOf(
                 equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
                 equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
@@ -338,9 +389,11 @@ public class URLTest {
     @Test
     public void test_set_methods() throws Exception {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
 
         url = url.setHost("host");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -354,6 +407,7 @@ public class URLTest {
 
         url = url.setPort(1);
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -367,6 +421,7 @@ public class URLTest {
 
         url = url.setPath("path");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -380,6 +435,7 @@ public class URLTest {
 
         url = url.setProtocol("protocol");
 
+        assertURLStrDecoder(url);
         assertEquals("protocol", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -393,6 +449,7 @@ public class URLTest {
 
         url = url.setUsername("username");
 
+        assertURLStrDecoder(url);
         assertEquals("protocol", url.getProtocol());
         assertEquals("username", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -406,6 +463,7 @@ public class URLTest {
 
         url = url.setPassword("password");
 
+        assertURLStrDecoder(url);
         assertEquals("protocol", url.getProtocol());
         assertEquals("username", url.getUsername());
         assertEquals("password", url.getPassword());
@@ -421,8 +479,10 @@ public class URLTest {
     @Test
     public void test_removeParameters() throws Exception {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
+        assertURLStrDecoder(url);
 
         url = url.removeParameter("version");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -438,6 +498,7 @@ public class URLTest {
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
         url = url.removeParameters("version", "application", "NotExistedKey");
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -453,6 +514,7 @@ public class URLTest {
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
         url = url.removeParameters(Arrays.asList("version", "application"));
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -472,6 +534,7 @@ public class URLTest {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameter("k1", "v1");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -489,6 +552,7 @@ public class URLTest {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
         URL newUrl = url.addParameter("k1", "v1");
 
+        assertURLStrDecoder(url);
         assertSame(newUrl, url);
     }
 
@@ -498,6 +562,7 @@ public class URLTest {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -513,6 +578,7 @@ public class URLTest {
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameters("k1", "v1", "k2", "v2", "application", "xxx");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -528,6 +594,7 @@ public class URLTest {
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParametersIfAbsent(CollectionUtils.toStringMap("k1", "v1", "k2", "v2", "application", "xxx"));
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -543,6 +610,7 @@ public class URLTest {
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameter("k1", "v1");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -557,6 +625,7 @@ public class URLTest {
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameter("application", "xxx");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -574,12 +643,14 @@ public class URLTest {
             URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
             URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1"));
 
+            assertURLStrDecoder(url);
             assertSame(url, newUrl);
         }
         {
             URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1&k2=v2");
             URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
 
+            assertURLStrDecoder(url);
             assertSame(newUrl, url);
         }
     }
@@ -589,6 +660,7 @@ public class URLTest {
         URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
         url = url.addParameterIfAbsent("application", "xxx");
 
+        assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
         assertEquals("hello1234", url.getPassword());
@@ -639,6 +711,7 @@ public class URLTest {
     @Test
     public void test_Anyhost() throws Exception {
         URL url = URL.valueOf("dubbo://0.0.0.0:20880");
+        assertURLStrDecoder(url);
         assertEquals("0.0.0.0", url.getHost());
         assertTrue(url.isAnyHost());
     }
@@ -646,16 +719,19 @@ public class URLTest {
     @Test
     public void test_Localhost() throws Exception {
         URL url = URL.valueOf("dubbo://127.0.0.1:20880");
+        assertURLStrDecoder(url);
         assertEquals("127.0.0.1", url.getHost());
         assertEquals("127.0.0.1:20880", url.getAddress());
         assertTrue(url.isLocalHost());
 
         url = URL.valueOf("dubbo://127.0.1.1:20880");
+        assertURLStrDecoder(url);
         assertEquals("127.0.1.1", url.getHost());
         assertEquals("127.0.1.1:20880", url.getAddress());
         assertTrue(url.isLocalHost());
 
         url = URL.valueOf("dubbo://localhost:20880");
+        assertURLStrDecoder(url);
         assertEquals("localhost", url.getHost());
         assertEquals("localhost:20880", url.getAddress());
         assertTrue(url.isLocalHost());
@@ -664,21 +740,26 @@ public class URLTest {
     @Test
     public void test_Path() throws Exception {
         URL url = new URL("dubbo", "localhost", 20880, "////path");
+        assertURLStrDecoder(url);
         assertEquals("path", url.getPath());
     }
 
     @Test
     public void testAddParameters() throws Exception {
         URL url = URL.valueOf("dubbo://127.0.0.1:20880");
+        assertURLStrDecoder(url);
+
         Map<String, String> parameters = new HashMap<String, String>();
         parameters.put("version", null);
         url.addParameters(parameters);
+        assertURLStrDecoder(url);
     }
 
     @Test
-    public void testUserNamePasswordContainsAt(){
+    public void testUserNamePasswordContainsAt() {
         // Test username or password contains "@"
         URL url = URL.valueOf("ad@min:hello@1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertEquals("ad@min", url.getUsername());
         assertEquals("hello@1234", url.getPassword());
@@ -693,9 +774,10 @@ public class URLTest {
 
 
     @Test
-    public void testIpV6Address(){
+    public void testIpV6Address() {
         // Test username or password contains "@"
         URL url = URL.valueOf("ad@min111:haha@1234@2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertEquals("ad@min111", url.getUsername());
         assertEquals("haha@1234", url.getPassword());
@@ -709,8 +791,9 @@ public class URLTest {
     }
 
     @Test
-    public void testIpV6AddressWithScopeId(){
+    public void testIpV6AddressWithScopeId() {
         URL url = URL.valueOf("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
         assertNull(url.getProtocol());
         assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getHost());
         assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getAddress());
@@ -728,49 +811,67 @@ public class URLTest {
     }
 
     @Test
-    public void testGetServiceKey () {
+    public void testGetServiceKey() {
         URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url1);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url1.getServiceKey());
 
         URL url2 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url2);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url2.getServiceKey());
 
         URL url3 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
+        assertURLStrDecoder(url3);
         Assertions.assertEquals("group1/org.apache.dubbo.test.interfaceName:1.0.0", url3.getServiceKey());
 
         URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url4);
         Assertions.assertEquals("context/path", url4.getPathKey());
 
         URL url5 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
+        assertURLStrDecoder(url5);
         Assertions.assertEquals("group1/context/path:1.0.0", url5.getPathKey());
     }
 
     @Test
     public void testGetColonSeparatedKey() {
         URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        assertURLStrDecoder(url1);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:group", url1.getColonSeparatedKey());
 
         URL url2 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&version=1.0.0");
+        assertURLStrDecoder(url2);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:", url2.getColonSeparatedKey());
 
         URL url3 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group");
+        assertURLStrDecoder(url3);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName::group", url3.getColonSeparatedKey());
 
         URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url4);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url4.getColonSeparatedKey());
 
         URL url5 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url5);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url5.getColonSeparatedKey());
 
         URL url6 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName1");
+        assertURLStrDecoder(url6);
         Assertions.assertEquals("org.apache.dubbo.test.interfaceName1::", url6.getColonSeparatedKey());
     }
 
     @Test
     public void testValueOf() {
-        URL.valueOf("10.20.130.230");
-        URL.valueOf("10.20.130.230:20880");
-        URL.valueOf("dubbo://10.20.130.230:20880");
-        URL.valueOf("dubbo://10.20.130.230:20880/path");
+        URL url = URL.valueOf("10.20.130.230");
+        assertURLStrDecoder(url);
+
+        url = URL.valueOf("10.20.130.230:20880");
+        assertURLStrDecoder(url);
+
+        url = URL.valueOf("dubbo://10.20.130.230:20880");
+        assertURLStrDecoder(url);
+
+        url = URL.valueOf("dubbo://10.20.130.230:20880/path");
+        assertURLStrDecoder(url);
     }
 }
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
index 38d4caf..51fa4aa 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.registry.zookeeper;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
+import org.apache.dubbo.common.URLStrParser;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -44,7 +45,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR_ENCODED;
 import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY;
@@ -266,9 +267,8 @@ public class ZookeeperRegistry extends FailbackRegistry {
         List<URL> urls = new ArrayList<>();
         if (CollectionUtils.isNotEmpty(providers)) {
             for (String provider : providers) {
-                provider = URL.decode(provider);
-                if (provider.contains(PROTOCOL_SEPARATOR)) {
-                    URL url = URL.valueOf(provider);
+                if (provider.contains(PROTOCOL_SEPARATOR_ENCODED)) {
+                    URL url = URLStrParser.parseEncodedStr(provider);
                     if (UrlUtils.isMatch(consumer, url)) {
                         urls.add(url);
                     }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
index 2446258..a18d116 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
@@ -252,7 +252,7 @@ public class DefaultFuture extends CompletableFuture<Object> {
         return (sent > 0 ? "Waiting server-side response timeout" : "Sending request timeout in client-side")
                 + (scan ? " by scan timer" : "") + ". start time: "
                 + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(start))) + ", end time: "
-                + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())) + ","
+                + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(nowTimestamp))) + ","
                 + (sent > 0 ? " client elapsed: " + (sent - start)
                 + " ms, server elapsed: " + (nowTimestamp - sent)
                 : " elapsed: " + (nowTimestamp - start)) + " ms, timeout: "