From 8befc9bbf448a5c1e696b390190d4537146ca85d Mon Sep 17 00:00:00 2001 From: tangmingyou <234767776@qq.com> Date: Fri, 29 Apr 2022 17:08:27 +0800 Subject: [PATCH] ConsistentHashRouteLoadBalance --- im-client/pom.xml | 7 + .../soim/client/cmd/console/ConsoleBox.java | 309 ++++++++++++++++++ .../cmd/console/ConsoleBoxKeyHandler.java | 24 ++ .../handlers/ConsoleBoxPasswordHandler.java | 41 +++ .../cmd/console/handlers/JvmKeyHandler.java | 72 ++++ .../handler/msg/ResOnlineUserListHandler.java | 11 +- .../net/sopod/soim/client/logger/Logger.java | 47 ++- .../client/prettyconsole/ConsoleBoxTest.java | 241 ++++++++++++++ .../net/sopod/soim/entry/worker/Worker.java | 18 +- im-service-api/im-router-api/pom.xml | 6 + .../router/api/route/ConsistentHashRoute.java | 26 ++ .../src/main/resources/application.yml | 6 +- 12 files changed, 788 insertions(+), 20 deletions(-) create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java create mode 100644 im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java create mode 100644 im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java diff --git a/im-client/pom.xml b/im-client/pom.xml index 8fcbe95..a33fac9 100644 --- a/im-client/pom.xml +++ b/im-client/pom.xml @@ -47,6 +47,13 @@ unirest-java 3.13.6 + + + junit + junit + 3.8.1 + test + \ No newline at end of file diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java new file mode 100644 index 0000000..3d46851 --- /dev/null +++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java @@ -0,0 +1,309 @@ +package net.sopod.soim.client.cmd.console; + +import com.google.common.base.*; +import com.google.common.collect.Iterables; +import net.sopod.soim.client.cmd.console.handlers.ConsoleBoxPasswordHandler; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * represents a console display box in the system console (or any PrintStream). + * used to display friendly technical information + * @author Willie Scholtz + */ +public final class ConsoleBox { + + /** + * the character to use in box corners + */ + private String boxChar = " + "; + + /** + * the character to use to pad strings with + */ + private char padChar = ' '; + + /** + * the character used for the box's right and left sides + */ + private String endChar = " | "; + + /** + * the character used for the box's top, title and bottom sides + */ + private String borderChar = "-"; + + /** + * the character used for representing black in a color + */ + private String blackChar = " "; + + /** + * the character used for representing white in a color + */ + private String whiteChar = "#"; + + /** + * the character used for representing aliasing of fonts. + * actually other colors, but since our image is only black and white + * it will represent a shade, which in this case is aliasing + */ + private String aliasChar = " "; + + /** + * the key value separator for names and values + */ + private String keyValueSeparator = " : "; + + private final List handlers; + private final StringBuilder builder; + private boolean content; + private final int width; + + /** + * creates a new instance of a ConsoleBox + * @param boxWidth the width of the box (in character count) + * @param title the initial title of the box, leave blank for none + */ + public ConsoleBox(int boxWidth, String title) { + this.width = boxWidth; + this.builder = new StringBuilder(); + this.handlers = new ArrayList(); + + // add default password handler + this.handlers.add(new ConsoleBoxPasswordHandler()); + + if (!Strings.isNullOrEmpty(title)) { + this.title(title); + } + } + + /* + * creates a new instance of a ConsoleBox with no title + * @param boxWidth the width of the box (in character count) + */ + public ConsoleBox(int boxWidth) { + this(boxWidth, null); + } + + /** + * pads a string from both sides + * @param string the string to pad + * @param pad the padding character + * @param length the length to pad + * @return the padded string + */ + private String padBoth(String string, String pad, int length) { + int right = (length - string.length()) / 2 + string.length(); + String result = Strings.padEnd(string, right, pad.toCharArray()[0]); + return Strings.padStart(result, length, pad.toCharArray()[0]); + } + + /** + * configures the box characters, note that sensible defaults are already set. + * @param cornerChar the character to use in box corners + * @param padChar the character to use to pad strings with + * @param sideChar the character used for the box's right and left sides + * @param borderChar the character used for the box's top, title and bottom sides + * @return the current instance + */ + public ConsoleBox setBoxCharacters(String cornerChar, char padChar, String sideChar, String borderChar) { + this.boxChar = Preconditions.checkNotNull(cornerChar); + this.padChar = Preconditions.checkNotNull(padChar); + this.endChar = Preconditions.checkNotNull(sideChar); + this.borderChar = Preconditions.checkNotNull(borderChar); + return this; + } + + /** + * configures the box's ASCII characters, note that sensible defaults are already set. + * @param blackChar the character used for representing black in a color + * @param whiteChar the character used for representing white in a color + * @param aliasChar the character used for representing aliasing of fonts. + * actually other colors, but since our image is only black and white + * it will represent a shade, which in this case is aliasing. + * @return the current instance + */ + public ConsoleBox setAsciiCharacters(String blackChar, String whiteChar, String aliasChar) { + this.blackChar = Preconditions.checkNotNull(blackChar); + this.whiteChar = Preconditions.checkNotNull(whiteChar); + this.aliasChar = Preconditions.checkNotNull(aliasChar); + + return this; + } + + /** + * configures the box's key-value separator. + * @param keyValueSeparator the character used for representing black in a color + * actually other colors, but since our image is only black and white + * it will represent a shade, which in this case is aliasing. + * @return the current instance + */ + public ConsoleBox setKeyValueSeparator(String keyValueSeparator) { + this.keyValueSeparator = Preconditions.checkNotNull(keyValueSeparator); + return this; + } + + /** + * adds a new key-value handler for this ConsoleBox + * @param handler the handler to use + * @return the current instance + */ + public ConsoleBox handler(ConsoleBoxKeyHandler handler) { + this.handlers.add(handler); + return this; + } + + /** + * builds and writes this box to the specified output stream + * @param output + */ + public void build(PrintStream output) { + this.title(""); + output.println(this.builder.toString()); + } + + /** + * adds a title section to the console box + * @param title the title to use + * @return the current box + */ + public ConsoleBox title(String title) { + this.builder.append("\n").append(this.boxChar).append(padBoth(title, + this.borderChar, this.width)).append(this.boxChar); + + return this; + } + + /** + * adds an empty line section to the console box + * @return the current box + */ + public ConsoleBox empty() { + this.builder.append("\n").append(this.boxChar).append( + padBoth("", " ", this.width)).append(this.boxChar); + + return this; + } + + /** + * generates and writes the specified text as an ASCII image into this box + * @param text the text to write as ASCII + * @param invert should the ASCII colors be inverted? + * @return the current box + */ + public ConsoleBox ascii(String text, boolean invert) { + final BufferedImage image = new BufferedImage(this.width, + 32, BufferedImage.TYPE_INT_RGB); + + final Graphics graphics = image.getGraphics(); + final Graphics2D g2d = (Graphics2D) graphics; + + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + Font textFont = new Font("Dialog", Font.BOLD, 22); + FontMetrics textMetrics = g2d.getFontMetrics(textFont); + g2d.setFont(textFont); + + int tX = (image.getWidth() / 2) - (textMetrics.stringWidth(text) / 2); + int tY = (image.getHeight() / 2) + (textMetrics.getHeight() / 2) - 5; + + g2d.drawString(text, tX, tY); + g2d.drawRenderedImage(image, null); + g2d.dispose(); + + final int iHeight = image.getHeight(); + final int iWidth = image.getWidth(); + + final String bChar = invert ? this.whiteChar : this.blackChar; + final String wChar = invert ? this.blackChar : this.whiteChar; + + for (int y = 0; y < iHeight; y++) { + final StringBuilder sb = new StringBuilder(); + for (int x = 0; x < iWidth; x++) { + final int rgbColor = image.getRGB(x, y); + sb.append(rgbColor == -16777216 ? bChar : rgbColor == -1 ? wChar : aliasChar); + } + + if (sb.toString().trim().isEmpty()) { + continue; + } + + this.builder.append("\n").append(this.endChar) + .append(sb).append(this.endChar); + } + + return this; + } + + /** + * adds a informational line to the console box, + * automatically splitting large values + * @param key the name of the value to display + * @param value the value of this line + * @return the current box + */ + public ConsoleBox line(String key, String value) { + key = Strings.isNullOrEmpty(key) ? "null" : key; + value = Strings.isNullOrEmpty(value) ? "" : value; + + // get the key length + final int kL = key.length(); + final int kSl = this.keyValueSeparator.length(); + + // calculate remaining box space for the value + final int ths = (this.width - kL - kSl); + Preconditions.checkState(ths > -1, "key[" + key + "] is to long " + + "for box with a " + width + " width!"); + + // \n | the_key_length_in_spaces + final String joinOn = ("\n" + this.endChar + Strings.padEnd("", + kL + kSl, this.padChar)); + + // get key handlers and modify if neccessary + for (ConsoleBoxKeyHandler handler : this.handlers) { + if (handler.shouldHandle(key)) { + value = handler.handleValue(key, value); + // don't break, possibilitty of multiple handlers + } + } + + // if a key handler returns null, a key should be skipped + if (value != null) { + // split the string on either length or new lines + Iterable splitted = Splitter.on(Pattern + .compile("(?<=\\G.{" + ths + "})|\\n")).split(value); + + // add the value + end characters (multiple lines) + String formatted = Joiner.on(joinOn).join( + Iterables.transform(splitted, new Function() { + @Override + public String apply(String input) { + return Strings.padEnd(input, ths, ' ') + endChar; + } + })); + + // write completed line to builder + this.builder.append("\n").append(this.endChar).append(key) + .append(this.keyValueSeparator).append(formatted); + + this.content = true; + } + + return this; + } + + /** + * @return true if {@link #line(java.lang.String, java.lang.String)} + * has been called at least once + */ + public boolean hasContent() { + return content; + } +} \ No newline at end of file diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java new file mode 100644 index 0000000..d46a750 --- /dev/null +++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java @@ -0,0 +1,24 @@ +package net.sopod.soim.client.cmd.console; + +/** + * interface for defining handlers for certain console box keys + * @author Willie Scholtz + */ +public interface ConsoleBoxKeyHandler { + + /** + * determines if the specified key should be handled by this key handler + * @param key the key to check + * @return true if this key should be handled by this key handler + */ + public boolean shouldHandle(final String key); + + /** + * if this handler is set to handle the specified key it, will execute this method to handle it's value + * @param key the key being handled + * @param value the original value to handle, note that console box automatically handles new lines and long lines + * @return the formatted value for ConsoleBox, or null if the whole line should be skipped + */ + public String handleValue(final String key, final String value); + +} \ No newline at end of file diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java new file mode 100644 index 0000000..4c2f042 --- /dev/null +++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java @@ -0,0 +1,41 @@ +package net.sopod.soim.client.cmd.console.handlers; + +import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; + +import java.util.regex.Pattern; + +/** + * ensures no passwords are present in the ConsoleBox + * @author Willie Scholtz + */ +public class ConsoleBoxPasswordHandler implements ConsoleBoxKeyHandler { + /** + * pattern defining which attributes should be blurred + */ + private static final Pattern IGNORE_ATT_PATT = Pattern + .compile("pass(word)?", Pattern.CASE_INSENSITIVE); + + /** + * replacement value for sensitive data + */ + private static final String SAFE_REPLACEMENT = "*****"; + + /** + * determines if a certain parameter is safe to transmit over the wire + * @param key the name of the parameter that will be sent + * @return true if the parameter may be sent + */ + private static boolean safeToTransmit(final String key) { + return !IGNORE_ATT_PATT.matcher(key).find(); + } + + @Override + public boolean shouldHandle(String key) { + return !safeToTransmit(key); + } + + @Override + public String handleValue(String key, String value) { + return SAFE_REPLACEMENT; + } +} \ No newline at end of file diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java new file mode 100644 index 0000000..34156c3 --- /dev/null +++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java @@ -0,0 +1,72 @@ + +package net.sopod.soim.client.cmd.console.handlers; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; + +/** + * ensures that JVM system properties are handled correctly by consolebox + * @author Willie Scholtz + */ +public class JvmKeyHandler implements ConsoleBoxKeyHandler { + + private static final boolean WINDOWS = System.getProperty("os.name").indexOf("Windows") > -1; + + /** + * keys that should be converted to new lines + */ + private static final String[] CLASSPATHS = new String[] { + "java.library.path", "java.class.path", "sun.boot.class.path" + }; + + /** + * keys that should be skipped + */ + private static final String[] SKIPS = new String[] { + "line.seperator" + }; + + @Override + public boolean shouldHandle(String key) { + for (String string : CLASSPATHS) { + if (string.equals(key)) { + return true; + } + } + + for (String string : SKIPS) { + if (string.equals(key)) { + return true; + } + } + + return false; + } + + @Override + public String handleValue(String key, String value) { + for (String string : CLASSPATHS) { + if (string.equals(key)) { + value = Joiner.on("\n").join(Iterables.transform(Splitter.on(WINDOWS ? ";" : ":").split(value), + new Function() { + int item = 0; + @Override + public String apply(String input) { + return "[" + ++item + "] " + input; + } + })); + } + } + + for (String string : SKIPS) { + if (string.equals(key)) { + return null; + } + } + + return value; + } +} \ No newline at end of file diff --git a/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java b/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java index 366f89f..1227852 100644 --- a/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java +++ b/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java @@ -5,6 +5,9 @@ import net.sopod.soim.client.logger.Logger; import net.sopod.soim.client.session.MessageHandler; import net.sopod.soim.data.msg.user.UserGroup; +import java.util.List; +import java.util.stream.Collectors; + /** * ResOnlineUserListHandler * @@ -16,10 +19,10 @@ public class ResOnlineUserListHandler implements MessageHandler userLines = msg.getUsersList().stream() + .map(user -> user.getUid() + " | " + user.getAccount()) + .collect(Collectors.toList()); + Logger.logList("在线用户", userLines); } } diff --git a/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java b/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java index 8c75c6f..22c4c6e 100644 --- a/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java +++ b/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java @@ -1,6 +1,12 @@ package net.sopod.soim.client.logger; +import com.google.common.base.Joiner; import net.sopod.soim.client.cmd.CmdStarter; +import net.sopod.soim.client.cmd.console.ConsoleBox; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; /** * Logger @@ -10,30 +16,51 @@ import net.sopod.soim.client.cmd.CmdStarter; */ public class Logger { + private static int consoleWidth = 60; + public static void info(String msg, Object...args) { - log("[info] " + msg, args); + log("info ", msg, args); } public static void error(String msg, Object...args) { - log("[error] " + msg, args); + log("error ", msg, args); } - public static void log(String msg, Object...args) { + /** + * TODO ConsoleBox 中文尺寸处理 + */ + public static void log(String key, String msg, Object...args) { msg = msg.replaceAll("\\{}", "%s"); for (int i = 0; i < args.length; i++) { args[i] = String.valueOf(args[i]); } - if ("main".equals(Thread.currentThread().getName())) { - System.out.println(String.format(msg, args)); - } else { - // 其他线程异步打印 - System.out.println(System.lineSeparator() + String.format(msg, args)); - CmdStarter.printPre(); - } +// if ("main".equals(Thread.currentThread().getName())) { +// System.out.println(String.format(msg, args)); +// } else { +// // 其他线程异步打印 +// System.out.println(System.lineSeparator() + String.format(msg, args)); +// CmdStarter.printPre(); +// } + ConsoleBox box = new ConsoleBox(consoleWidth); + box.title("提示"); + box.line(key, String.format(msg, args)); + box.build(System.out); + } + + public static void logList(String key, List lines) { + ConsoleBox box = new ConsoleBox(consoleWidth); + box.title(""); + String line = Joiner.on("\n").join(lines); + box.line(key, "\n" + line); + box.build(System.out); } public static void pre(String pre) { System.out.print(pre); } + public static void main(String[] args) { + System.out.println(Joiner.on(System.lineSeparator()).join(Arrays.asList("a", "b", "c"))); + } + } diff --git a/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java b/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java new file mode 100644 index 0000000..e3cc238 --- /dev/null +++ b/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java @@ -0,0 +1,241 @@ +package net.sopod.soim.client.prettyconsole; + +import com.google.common.base.Charsets; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import net.sopod.soim.client.cmd.console.ConsoleBox; +import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Unit test for testing console box + */ +public class ConsoleBoxTest extends TestCase { + + /** + * Create the test case + * @param testName name of the test case + */ + public ConsoleBoxTest(String testName) { + super(testName); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() { + return new TestSuite(ConsoleBoxTest.class); + } + + private ConsoleBox basicBox(int width) { + ConsoleBox box = new ConsoleBox(width); + box.title("TEST"); + return box; + } + + private String getBox(ConsoleBox box) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + box.build(new PrintStream(baos)); + + String actual = null; + try { + actual = baos.toString(Charsets.UTF_8.name()); + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(ConsoleBoxTest.class.getName()).log( + Level.SEVERE, "failed to get UTF-8", ex); + } + + System.out.println(actual); + return actual; + } + + /** + * @throws java.io.UnsupportedEncodingException + */ + public void testCreation() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(10); + box.line("a", "1"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + ---TEST--- + \n" + + " | a : 1 | \n" + + " + ---------- + \r\n"; + + assertEquals("basic box build", expected, actual); + } + + public void testOverflow() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(10); + box.line("a", "123456789987654321"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + ---TEST--- + \n" + + " | a : 123456 | \n" + + " | 789987 | \n" + + " | 654321 | \n" + + " | | \n" + + " + ---------- + \r\n"; + + assertEquals("overflow in content", expected, actual); + } + + public void testNewLineOverflow() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(10); + box.line("a", "1\n2\n3\n4\n5\n6\n7\n8\n9\n12345679abcdefg"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + ---TEST--- + \n" + + " | a : 1 | \n" + + " | 2 | \n" + + " | 3 | \n" + + " | 4 | \n" + + " | 5 | \n" + + " | 6 | \n" + + " | 7 | \n" + + " | 8 | \n" + + " | 9 | \n" + + " | 123456 | \n" + + " | 79abcd | \n" + + " | efg | \n" + + " + ---------- + \r\n"; + + assertEquals("overflow in content with new lines", expected, actual); + } + + public void testDefaultPasswordHandling() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(20); + box.line("password", "supercoolpassword"); + box.line("pass", "mysecret"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + --------TEST-------- + \n" + + " | password : ***** | \n" + + " | pass : ***** | \n" + + " + -------------------- + \r\n"; + + + assertEquals("default password handling", expected, actual); + } + + public void testLinesAndTitles() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(25); + box.line("keyOne", "valueOne"); + box.line("keyTwo", "valueTwo"); + box.title("TITLE"); + box.line("keyThree", "valueThree"); + box.line("keyFour", "valueFour"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + -----------TEST---------- + \n" + + " | keyOne : valueOne | \n" + + " | keyTwo : valueTwo | \n" + + " + ----------TITLE---------- + \n" + + " | keyThree : valueThree | \n" + + " | keyFour : valueFour | \n" + + " + ------------------------- + \r\n"; + + assertEquals("with lines and titles", expected, actual); + } + + public void testAscii() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(60); + box.line("keyOne", "valueOne"); + box.line("keyTwo", "valueTwo"); + box.title("TITLE"); + box.line("keyThree", "valueThree"); + box.line("keyFour", "valueFour"); + box.title(""); + box.ascii("ASCII", false); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + ----------------------------TEST---------------------------- + \n" + + " | keyOne : valueOne | \n" + + " | keyTwo : valueTwo | \n" + + " + ----------------------------TITLE--------------------------- + \n" + + " | keyThree : valueThree | \n" + + " | keyFour : valueFour | \n" + + " + ------------------------------------------------------------ + \n" + + " | ### ### ### | \n" + + " | #### ######## ####### ### ### | \n" + + " | ##### ########## ########## ### ### | \n" + + " | ## ## ## ## ## ## ### ### | \n" + + " | ## # ## ## ## ### ### | \n" + + " | ## ## ## ## ### ### | \n" + + " | ## ## ### ## ### ### | \n" + + " | ## # ###### ## ### ### | \n" + + " | ## ## ### ## ### ### | \n" + + " | ######### ## ## ### ### | \n" + + " | ########### ## ## ## ### ### | \n" + + " | ########### ## ## ## ### ### | \n" + + " | ## ## ### ## ## ## ### ### | \n" + + " | ## ## ########## ######### ### ### | \n" + + " | ## ## ######## ####### ### ### | \n" + + " | ## ## # ### ### | \n" + + " + ------------------------------------------------------------ + \r\n"; + + assertEquals("with ascii text", expected, actual); + } + + public void testCustomHandler() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(25); + box.handler(new ConsoleBoxKeyHandler() { + public boolean shouldHandle(String key) { + return "a".equals(key) || "c".equals(key); + } + + public String handleValue(String key, String value) { + if ("a".equals(key)) { + return "a_replace"; + } + + if ("c".equals(key)) { + return "c_replace"; + } + + return value; + } + }); + box.line("a", "valueOne"); + box.line("b", "valueTwo"); + box.line("c", "valueThree"); + box.line("d", "valueFour"); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + -----------TEST---------- + \n" + + " | a : a_replace | \n" + + " | b : valueTwo | \n" + + " | c : c_replace | \n" + + " | d : valueFour | \n" + + " + ------------------------- + \r\n"; + + assertEquals("with a custom handler", expected, actual); + } + + public void testEmpty() throws UnsupportedEncodingException { + ConsoleBox box = this.basicBox(5); + box.empty(); + box.empty(); + + final String actual = this.getBox(box); + final String expected = "\n" + + " + -TEST + \n" + + " + + \n" + + " + + \n" + + " + ----- + \r\n"; + + assertEquals("with empty content", expected, actual); + } +} \ No newline at end of file diff --git a/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java b/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java index b0aceab..a930e39 100644 --- a/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java +++ b/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java @@ -5,6 +5,8 @@ import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import net.sopod.soim.common.util.ImClock; import net.sopod.soim.entry.util.FastThreadLocalThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.*; @@ -13,6 +15,8 @@ import java.util.concurrent.*; */ public class Worker implements EventHandler, EventFactory { + private static final Logger logger = LoggerFactory.getLogger(Worker.class); + private Disruptor disruptor; private RingBuffer ringBuffer; @@ -54,11 +58,15 @@ public class Worker implements EventHandler, EventFactory @Override public void onEvent(TaskEvent taskEvent, long sequence, boolean endOfBatch) throws Exception { - long start = ImClock.millis(); - taskEvent.getTask().run(); - long time = ImClock.millis() - start; - if (time > 100) { - System.out.println("任务执行时间过长:" + time); + try { + long start = ImClock.millis(); + taskEvent.getTask().run(); + long time = ImClock.millis() - start; + if (time > 100) { + logger.info("任务执行时间过长: {}", time); + } + } catch (Exception e) { + logger.error("任务执行失败:", e); } // 事件对象不会释放,将数据置空 taskEvent.setTask(null); diff --git a/im-service-api/im-router-api/pom.xml b/im-service-api/im-router-api/pom.xml index 6f1f2c6..117f248 100644 --- a/im-service-api/im-router-api/pom.xml +++ b/im-service-api/im-router-api/pom.xml @@ -17,6 +17,12 @@ net.sopod 1.0.0 + + org.apache.dubbo + dubbo-cluster + ${dubbo.version} + provided + \ No newline at end of file diff --git a/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java b/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java new file mode 100644 index 0000000..b8b455d --- /dev/null +++ b/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java @@ -0,0 +1,26 @@ +package net.sopod.soim.router.api.route; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance; + +import java.util.List; + +/** + * ConsistentHashRoute + * 一致性 hash 服务路由 + * + * + * @author tmy + * @date 2022-04-29 14:41 + */ +public class ConsistentHashRoute extends AbstractLoadBalance { + + @Override + protected Invoker doSelect(List> invokers, URL url, Invocation invocation) { + Invoker invoker = invokers.get(0); + return null; + } + +} diff --git a/im-service/im-router/src/main/resources/application.yml b/im-service/im-router/src/main/resources/application.yml index 3fa7f6d..3e86641 100644 --- a/im-service/im-router/src/main/resources/application.yml +++ b/im-service/im-router/src/main/resources/application.yml @@ -20,4 +20,8 @@ dubbo: group: so-im protocol: name: dubbo - port: 3031 \ No newline at end of file + port: 3032 + consumer: + check: false + provider: + loadbalance: consistenthash \ No newline at end of file