├── doc └── pics │ ├── consumption_logic.png │ └── ordered_produce.png ├── src ├── main │ ├── resources │ │ └── perf.properties │ ├── java │ │ └── com │ │ │ └── youzan │ │ │ ├── nsq │ │ │ └── client │ │ │ │ ├── core │ │ │ │ ├── IRdyCallback.java │ │ │ │ ├── command │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── Magic.java │ │ │ │ │ ├── Nop.java │ │ │ │ │ ├── Close.java │ │ │ │ │ ├── Touch.java │ │ │ │ │ ├── Finish.java │ │ │ │ │ ├── SubOrdered.java │ │ │ │ │ ├── NSQCommand.java │ │ │ │ │ ├── Rdy.java │ │ │ │ │ ├── ReQueue.java │ │ │ │ │ ├── Identify.java │ │ │ │ │ ├── Mpub.java │ │ │ │ │ ├── Sub.java │ │ │ │ │ ├── PubTrace.java │ │ │ │ │ ├── Pub.java │ │ │ │ │ └── PubExt.java │ │ │ │ ├── Feature.java │ │ │ │ ├── lookup │ │ │ │ │ ├── LookupService.java │ │ │ │ │ └── LookupServiceImpl.java │ │ │ │ ├── Client.java │ │ │ │ └── pool │ │ │ │ │ └── consumer │ │ │ │ │ └── FixedPool.java │ │ │ │ ├── package-info.java │ │ │ │ ├── exception │ │ │ │ ├── NSQSubException.java │ │ │ │ ├── NSQTagException.java │ │ │ │ ├── NSQInvalidException.java │ │ │ │ ├── NSQConfigAccessException.java │ │ │ │ ├── NSQPubFailedException.java │ │ │ │ ├── NSQTopicNotExtendableException.java │ │ │ │ ├── NSQProducerNotFoundException.java │ │ │ │ ├── NSQTagNotSupportedException.java │ │ │ │ ├── RetryBusinessException.java │ │ │ │ ├── NSQLookupAddressNotFoundException.java │ │ │ │ ├── NSQPartitionNotAvailableException.java │ │ │ │ ├── NSQSeedLookupConfigNotFoundException.java │ │ │ │ ├── PropertyNotFoundException.java │ │ │ │ ├── ConfigAccessAgentException.java │ │ │ │ ├── NSQInvalidMessageBodyException.java │ │ │ │ ├── NSQTimeoutException.java │ │ │ │ ├── NSQInvalidTopicException.java │ │ │ │ ├── NSQException.java │ │ │ │ ├── NSQExtNotSupportedException.java │ │ │ │ ├── NSQPubFactoryInitializeException.java │ │ │ │ ├── NSQInvalidDataNodeException.java │ │ │ │ ├── NSQTopicNotFoundException.java │ │ │ │ ├── ConfigAccessAgentInitializeException.java │ │ │ │ ├── NSQNoConnectionException.java │ │ │ │ ├── NSQLookupException.java │ │ │ │ ├── NSQInvalidMessageException.java │ │ │ │ ├── ExplicitRequeueException.java │ │ │ │ ├── NSQDataNodesDownException.java │ │ │ │ └── NSQPubException.java │ │ │ │ ├── MessageMetadata.java │ │ │ │ ├── configs │ │ │ │ ├── ITopicRuleCategory.java │ │ │ │ ├── DCCMigrationControlConfig.java │ │ │ │ ├── AbstractConfigAccessKey.java │ │ │ │ ├── AbstractConfigAccessDomain.java │ │ │ │ ├── DCCMigrationConfigAccessKey.java │ │ │ │ ├── DCCTraceConfigAccessDomain.java │ │ │ │ ├── DCCMigrationConfigAccessDomain.java │ │ │ │ ├── DCCTraceConfigAccessKey.java │ │ │ │ ├── DoNothingConfigAccessAgent.java │ │ │ │ └── TopicRuleCategory.java │ │ │ │ ├── entity │ │ │ │ ├── IPartitionsSelector.java │ │ │ │ ├── Role.java │ │ │ │ ├── lookup │ │ │ │ │ ├── DefaultSeedLookupd.java │ │ │ │ │ ├── AbstractSeedLookupdConfig.java │ │ │ │ │ ├── AbstractLookupdAddress.java │ │ │ │ │ ├── AbstractLookupdAddresses.java │ │ │ │ │ ├── LookupdAddress.java │ │ │ │ │ ├── AbstractLookupdConfig.java │ │ │ │ │ └── NSQLookupdAddressesPair.java │ │ │ │ ├── Context.java │ │ │ │ ├── SimplePartitionsSelector.java │ │ │ │ ├── ConsumeMessageFilterMode.java │ │ │ │ ├── TopicSharding.java │ │ │ │ ├── ExtVer.java │ │ │ │ ├── IExpectedRdyUpdatePolicy.java │ │ │ │ ├── TopicInfo.java │ │ │ │ ├── TopicSync.java │ │ │ │ ├── Response.java │ │ │ │ ├── TraceLogger.java │ │ │ │ ├── DesiredTag.java │ │ │ │ ├── MessagesWrapper.java │ │ │ │ ├── MigrationPartitionsSelector.java │ │ │ │ ├── PerfTune.java │ │ │ │ └── ConsumeMessageFilter.java │ │ │ │ ├── IPartitionSelector.java │ │ │ │ ├── IConsumeInfo.java │ │ │ │ ├── MessageHandler.java │ │ │ │ ├── IConfigAccessSubscriber.java │ │ │ │ ├── network │ │ │ │ ├── netty │ │ │ │ │ ├── NSQClientInitializer.java │ │ │ │ │ ├── NSQEncoder.java │ │ │ │ │ ├── SnappyEncoder.java │ │ │ │ │ └── NSQDecoder.java │ │ │ │ └── frame │ │ │ │ │ ├── NSQFrame.java │ │ │ │ │ ├── ErrorFrame.java │ │ │ │ │ ├── ResponseFrame.java │ │ │ │ │ └── OrderedMessageFrame.java │ │ │ │ └── MessageReceipt.java │ │ │ └── util │ │ │ ├── ThreadSafe.java │ │ │ ├── NotThreadSafe.java │ │ │ ├── SystemUtil.java │ │ │ ├── IPUtil.java │ │ │ ├── ProducerWorkerThreadFactory.java │ │ │ ├── NamedThreadFactory.java │ │ │ ├── HostUtil.java │ │ │ ├── Lists.java │ │ │ └── ConcurrentSortedSet.java │ └── java-templates │ │ └── com │ │ └── youzan │ │ └── nsq │ │ └── client │ │ └── Version.java ├── test │ ├── resources │ │ ├── perf.properties │ │ ├── testng-complex-consumer-suite.xml │ │ ├── testng-stable-suite.xml │ │ ├── testng-stable-dcc-suite.xml │ │ ├── testng-trace-suite.xml │ │ ├── testng-order-suite.xml │ │ ├── app-test.properties │ │ ├── testng-partition-suite.xml │ │ ├── testng-base-tag-suite.xml │ │ └── testng-base-suite.xml │ └── java │ │ ├── com │ │ └── youzan │ │ │ └── nsq │ │ │ └── client │ │ │ ├── core │ │ │ ├── LookupAddressUpdateTest.java │ │ │ └── pool │ │ │ │ └── producer │ │ │ │ └── KeyedPooledConnectionFactoryTest.java │ │ │ ├── MockedProducer.java │ │ │ ├── utils │ │ │ ├── BytesUtil.java │ │ │ ├── CompressUtil.java │ │ │ ├── Lz4Util.java │ │ │ └── ConnectionUtil.java │ │ │ ├── entity │ │ │ ├── AddressTest.java │ │ │ └── lookup │ │ │ │ └── PartitionTestcase.java │ │ │ ├── MockedNSQConnectionImpl.java │ │ │ ├── MockedConsumer.java │ │ │ ├── configs │ │ │ └── TopicRuleCategoryTestcase.java │ │ │ ├── MockedNSQSimpleClient.java │ │ │ ├── PartitionTestcase.java │ │ │ ├── AbstractNSQClientTestcase.java │ │ │ └── network │ │ │ └── frame │ │ │ └── TestMessageFrame.java │ │ └── it │ │ └── youzan │ │ └── nsq │ │ └── client │ │ ├── ITBase.java │ │ ├── ITProducerTrace.java │ │ ├── ITConsumerTrace.java │ │ ├── AbstractITConsumer.java │ │ ├── ITProducerWPartition.java │ │ ├── ITConsumerOrdered.java │ │ ├── ITProducerOrdered.java │ │ └── ITConsumerWPartition.java └── assembly │ └── conf │ ├── youzan │ └── configClient.properties │ └── openSource │ └── configClient.properties ├── developer.md ├── README.md └── .gitignore /doc/pics/consumption_logic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsqJavaSDK/HEAD/doc/pics/consumption_logic.png -------------------------------------------------------------------------------- /doc/pics/ordered_produce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsqJavaSDK/HEAD/doc/pics/ordered_produce.png -------------------------------------------------------------------------------- /src/main/resources/perf.properties: -------------------------------------------------------------------------------- 1 | nsq.client.conn.get = 50 2 | nsq.client.conn.return = 10 3 | nsq.client.msg.send = 500 4 | nsq.client.pool.conn.borrow = 50 -------------------------------------------------------------------------------- /src/test/resources/perf.properties: -------------------------------------------------------------------------------- 1 | nsq.client.conn.get = 50 2 | nsq.client.conn.return = 10 3 | nsq.client.msg.send = 50 4 | nsq.client.pool.conn.borrow = 50 5 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/core/LookupAddressUpdateTest.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core; 2 | 3 | /** 4 | * Created by lin on 16/12/15. 5 | */ 6 | public class LookupAddressUpdateTest { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/IRdyCallback.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core; 2 | 3 | /** 4 | * Created by lin on 17/6/27. 5 | */ 6 | public interface IRdyCallback { 7 | void onUpdated(int newRdy, int lastRdy); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Class name is from Verb to Noun. 3 | */ 4 | /** 5 | * @author zhaoxi (linzuxiong) 6 | * 7 | * 8 | */ 9 | package com.youzan.nsq.client.core.command; 10 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Java implementation of NSQ Client 3 | */ 4 | /** 5 | * Share the same @{code NSQConfig } 6 | * 7 | * @author zhaoxi (linzuxiong) 8 | * 9 | * 10 | */ 11 | package com.youzan.nsq.client; 12 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQSubException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/5/2. 5 | */ 6 | public class NSQSubException extends NSQException { 7 | public NSQSubException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQTagException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/5/25. 5 | */ 6 | public class NSQTagException extends NSQException { 7 | public NSQTagException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQInvalidException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/9/20. 5 | */ 6 | public class NSQInvalidException extends NSQException { 7 | public NSQInvalidException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/ThreadSafe.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import java.lang.annotation.Documented; 7 | 8 | /** 9 | * @author zhaoxi (linzuxiong) 10 | * 11 | * 12 | */ 13 | @Documented 14 | public @interface ThreadSafe { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQConfigAccessException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 16/12/21. 5 | */ 6 | public class NSQConfigAccessException extends NSQException{ 7 | public NSQConfigAccessException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQPubFailedException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/6/6. 5 | */ 6 | public class NSQPubFailedException extends NSQException { 7 | 8 | public NSQPubFailedException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/NotThreadSafe.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import java.lang.annotation.Documented; 7 | 8 | /** 9 | * @author zhaoxi (linzuxiong) 10 | * 11 | * 12 | */ 13 | @Documented 14 | public @interface NotThreadSafe { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/MessageMetadata.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | /** 4 | * Created by lin on 16/10/31. 5 | */ 6 | public interface MessageMetadata { 7 | /** 8 | * output message meta data in {@link String} 9 | * @return meta data in {@link String} 10 | */ 11 | String toMetadataStr(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/ITopicRuleCategory.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import com.youzan.nsq.client.entity.Topic; 4 | 5 | /** 6 | * Created by lin on 16/12/2. 7 | */ 8 | public interface ITopicRuleCategory { 9 | String category(Topic topic); 10 | 11 | String category(String topic); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQTopicNotExtendableException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/6/12. 5 | */ 6 | public class NSQTopicNotExtendableException extends NSQException { 7 | public NSQTopicNotExtendableException(String msg) { 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQProducerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/1/11. 5 | */ 6 | public class NSQProducerNotFoundException extends NSQException{ 7 | public NSQProducerNotFoundException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQTagNotSupportedException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/5/25. 5 | */ 6 | public class NSQTagNotSupportedException extends NSQTagException { 7 | public NSQTagNotSupportedException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/RetryBusinessException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * @author zhaoxi (linzuxiong) 5 | */ 6 | public class RetryBusinessException extends RuntimeException { 7 | private static final long serialVersionUID = -5160181118508146123L; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQLookupAddressNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 18/6/19. 5 | */ 6 | public class NSQLookupAddressNotFoundException extends NSQException { 7 | public NSQLookupAddressNotFoundException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQPartitionNotAvailableException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/1/16. 5 | */ 6 | public class NSQPartitionNotAvailableException extends NSQException { 7 | public NSQPartitionNotAvailableException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/testng-complex-consumer-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQSeedLookupConfigNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/4/6. 5 | */ 6 | public class NSQSeedLookupConfigNotFoundException extends NSQException { 7 | 8 | public NSQSeedLookupConfigNotFoundException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DCCMigrationControlConfig.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import com.youzan.nsq.client.entity.lookup.AbstractControlConfig; 4 | import com.youzan.util.NotThreadSafe; 5 | 6 | /** 7 | * Created by lin on 16/12/5. 8 | */ 9 | @NotThreadSafe 10 | public class DCCMigrationControlConfig extends AbstractControlConfig { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/testng-stable-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/testng-stable-dcc-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/IPartitionsSelector.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | /** 4 | * Created by lin on 16/12/19. 5 | */ 6 | public interface IPartitionsSelector { 7 | 8 | /** 9 | * return array of {@link Partitions} dataNode in selector 10 | * @return {@link Partitions} array 11 | */ 12 | Partitions[] choosePartitions(); 13 | 14 | Partitions[] dumpAllPartitions(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/PropertyNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 16/10/27. 5 | */ 6 | public class PropertyNotFoundException extends RuntimeException{ 7 | 8 | private String property; 9 | 10 | public PropertyNotFoundException(String property, String msg){ 11 | super(msg); 12 | this.property = property; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java-templates/com/youzan/nsq/client/Version.java: -------------------------------------------------------------------------------- 1 | 2 | package com.youzan.nsq.client; 3 | 4 | /** 5 | * @author zhaoxi (linzuxiong) 6 | * 7 | */ 8 | public final class Version { 9 | 10 | public static final String VERSION = "${project.version}"; 11 | public static final String GROUP_ID = "${project.groupId}"; 12 | public static final String ARTIFACT_ID = "${project.artifactId}"; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/ConfigAccessAgentException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/1/17. 5 | */ 6 | public abstract class ConfigAccessAgentException extends Exception{ 7 | 8 | public ConfigAccessAgentException(String msg, Throwable cause){ 9 | super(msg, cause); 10 | } 11 | 12 | public ConfigAccessAgentException(String msg) { 13 | super(msg); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQInvalidMessageBodyException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 18/4/8. 5 | */ 6 | public class NSQInvalidMessageBodyException extends NSQException { 7 | public NSQInvalidMessageBodyException(String message) { 8 | super(message); 9 | } 10 | 11 | public NSQInvalidMessageBodyException() { 12 | super("NSQ server fail to parse invalid message body."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | /** 4 | * @author zhaoxi (linzuxiong) 5 | */ 6 | public enum Role { 7 | Consumer("consumer"), 8 | Producer("producer"),; 9 | 10 | private String roleTxt; 11 | 12 | Role(String roleTxt) { 13 | this.roleTxt = roleTxt; 14 | } 15 | 16 | public String getRoleTxt(){ 17 | return this.roleTxt; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/IPartitionSelector.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Interface mainly works on returning partition Id for {@link Consumer} to help decide which partition to connect to 7 | * in SUB ORDER situation. By implementing interfaces to fit your purpose for SUB ORDER. 8 | * Created by lin on 16/12/16. 9 | */ 10 | public interface IPartitionSelector { 11 | int pickPartition(); 12 | Set getPartitionIDs(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | import java.util.concurrent.TimeoutException; 4 | 5 | /** 6 | * Created by lin on 17/6/19. 7 | */ 8 | public class NSQTimeoutException extends NSQException{ 9 | private final TimeoutException nested; 10 | 11 | public NSQTimeoutException(TimeoutException te) { 12 | super(te.getLocalizedMessage(), te.getCause()); 13 | this.nested = te; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /developer.md: -------------------------------------------------------------------------------- 1 | # NSQ Ops 2 | 3 | * curl -X POST "http://127.0.0.1:4161/topic/create?topic=$$&partition_num=2&replicator=1&suggestload=0&syncdisk=1000" 4 | * curl -X GET "http://127.0.0.1:4161/lookup?topic=$$&access=w" 5 | 6 | ## New Topics 7 | 8 | * JavaTesting-Producer-Base 9 | * JavaTesting-ReQueue 10 | * JavaTesting-Finish 11 | 12 | 13 | ## TestNG 14 | 15 | * mvn clean test-compile failsafe:integration-test -PQA -Dfailsafe.suiteXmlFiles=src/test/resources/testng-stable-suite.xml -Dstable=true -Dhours=4 -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/AbstractConfigAccessKey.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | /** 4 | * Abstract class for config access key. 5 | * Created by lin on 16/12/9. 6 | */ 7 | public abstract class AbstractConfigAccessKey { 8 | T innerKey; 9 | 10 | public AbstractConfigAccessKey(T key) { 11 | this.innerKey = key; 12 | } 13 | 14 | public T getInnerKey(){ 15 | return innerKey; 16 | } 17 | 18 | abstract public String toKey(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQInvalidTopicException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | import com.youzan.nsq.client.entity.Response; 4 | 5 | public class NSQInvalidTopicException extends NSQException { 6 | 7 | private static final long serialVersionUID = 6410416443212221161L; 8 | 9 | public NSQInvalidTopicException(final String topic) { 10 | super(Response.E_BAD_TOPIC + " ! Please contact your administrator! The topic is " + topic); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/DefaultSeedLookupd.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | /** 4 | * Default seed lookup info in DCC migration control config, Not used now 5 | * Created by lin on 16/12/5. 6 | */ 7 | public class DefaultSeedLookupd { 8 | private volatile String address; 9 | 10 | public DefaultSeedLookupd(String seedLookupd) { 11 | this.address = seedLookupd; 12 | } 13 | 14 | public String getAddress() { 15 | return this.address; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | public class NSQException extends Exception { 4 | 5 | private static final long serialVersionUID = 6759799779448168356L; 6 | 7 | public NSQException(String message) { 8 | super(message); 9 | } 10 | 11 | public NSQException(Throwable cause) { 12 | super(cause); 13 | } 14 | 15 | public NSQException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/AbstractConfigAccessDomain.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | /** 4 | * Abstract class of config access domain. 5 | * Created by lin on 16/12/9. 6 | */ 7 | public abstract class AbstractConfigAccessDomain { 8 | T innerDomain; 9 | 10 | public AbstractConfigAccessDomain(T domain) { 11 | this.innerDomain = domain; 12 | } 13 | 14 | public T getInnerDomain() { 15 | return this.innerDomain; 16 | } 17 | 18 | abstract public String toDomain(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/IConsumeInfo.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | /** 4 | * Created by lin on 17/6/29. 5 | */ 6 | public interface IConsumeInfo { 7 | /** 8 | * load factor of consumer, (total size in queue)/(active messages in queue) 9 | * @return load factor 10 | */ 11 | float getLoadFactor(); 12 | 13 | /** 14 | * rdy count per connection 15 | * @return rdy count per connection in config 16 | */ 17 | int getRdyPerConnection(); 18 | 19 | boolean isConsumptionEstimateElapseTimeout(); 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/testng-trace-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQExtNotSupportedException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Created by lin on 17/9/30. 5 | */ 6 | public class NSQExtNotSupportedException extends NSQException { 7 | public NSQExtNotSupportedException(Throwable cause) { 8 | super(cause); 9 | } 10 | 11 | public NSQExtNotSupportedException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public NSQExtNotSupportedException(String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQPubFactoryInitializeException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Exception when {@link com.youzan.nsq.client.PubCmdFactory} fail to initialize. 5 | * Created by lin on 17/1/18. 6 | */ 7 | public class NSQPubFactoryInitializeException extends NSQException { 8 | public NSQPubFactoryInitializeException(String message) { 9 | super(message); 10 | } 11 | 12 | public NSQPubFactoryInitializeException(String message, Throwable cause){ 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.entity.NSQMessage; 4 | 5 | /** 6 | * It is a callback what is a client processing. 7 | * 8 | * @author zhaoxi (linzuxiong) 9 | */ 10 | // @FunctionalInterface 11 | public interface MessageHandler { 12 | 13 | /** 14 | * Business Processing. Retry 2 times when exceptions in SDK. 15 | * 16 | * @param message the concrete message exposing to the client 17 | */ 18 | void process(NSQMessage message); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/testng-order-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQInvalidDataNodeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.exception; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class NSQInvalidDataNodeException extends NSQException { 10 | 11 | private static final Logger logger = LoggerFactory.getLogger(NSQInvalidDataNodeException.class); 12 | private static final long serialVersionUID = -6340688420348997379L; 13 | 14 | public NSQInvalidDataNodeException(String topic) { 15 | super("Please pick up another broker! The topic is " + topic); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQTopicNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | 4 | /** 5 | * Created by lin on 17/1/12. 6 | */ 7 | public class NSQTopicNotFoundException extends NSQException { 8 | private String topic; 9 | 10 | public NSQTopicNotFoundException(String message) { 11 | super(message); 12 | } 13 | 14 | public NSQTopicNotFoundException(String message, String topic, Throwable cause) { 15 | super(message, cause); 16 | this.topic = topic; 17 | } 18 | 19 | public String getTopic(){ 20 | return this.topic; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/ConfigAccessAgentInitializeException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * ConfigAccessAgent Exception when we fail to initialize {@link com.youzan.nsq.client.configs.ConfigAccessAgent} in 5 | * current process context. 6 | * Created by lin on 17/1/17. 7 | */ 8 | public class ConfigAccessAgentInitializeException extends ConfigAccessAgentException { 9 | public ConfigAccessAgentInitializeException(String msg, Throwable cause){ 10 | super(msg, cause); 11 | } 12 | 13 | public ConfigAccessAgentInitializeException(String msg){ 14 | super(msg); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/Context.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.UUID; 7 | 8 | /** 9 | * Context for set/get extra properties like tracing ID 10 | * Created by lin on 17/5/4. 11 | */ 12 | public class Context { 13 | private final static Logger log = LoggerFactory.getLogger(Context.class); 14 | 15 | //UUID for tracing 16 | private long traceID = 0L; 17 | 18 | public void setTraceID(long traceID) { 19 | this.traceID = traceID; 20 | } 21 | 22 | public long getTraceID() { 23 | return this.traceID; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NSQ-Client-Java 2 | 3 | [![GitHub release](https://img.shields.io/github/release/youzan/nsqJavaSDK.svg)](https://github.com/youzan/nsqJavaSDK/releases/latest) 4 | [![GitHub release](https://img.shields.io/github/release/youzan/nsqJavaSDK/all.svg)](https://github.com/youzan/nsqJavaSDK/releases/tag/2.4.2-os-SNAPSHOT) 5 | 6 | ## Build 7 | pls specify profile "openSource" and compile, like: 8 | `mvn compile -P openSource,!dev` 9 | 10 | ## Server 11 | * https://github.com/youzan/nsq 12 | * Feature: strong consistent replication 13 | 14 | ## Guide 15 | * It has compatibility for http://nsq.io and our secondary development. 16 | 17 | ## Wiki 18 | * https://github.com/youzan/nsqJavaSDK/wiki 19 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DCCMigrationConfigAccessKey.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import com.youzan.nsq.client.entity.Role; 4 | 5 | /** 6 | * Migration config access key for DCC. 7 | * Created by lin on 16/12/9. 8 | */ 9 | public class DCCMigrationConfigAccessKey extends AbstractConfigAccessKey { 10 | 11 | private DCCMigrationConfigAccessKey(Role key) { 12 | super(key); 13 | } 14 | 15 | @Override 16 | public String toKey() { 17 | return this.innerKey.getRoleTxt(); 18 | } 19 | 20 | public static AbstractConfigAccessKey getInstance(Role role) { 21 | return new DCCMigrationConfigAccessKey(role); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQNoConnectionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.exception; 5 | 6 | public class NSQNoConnectionException extends NSQException { 7 | 8 | private static final long serialVersionUID = -3344028957138292433L; 9 | 10 | public NSQNoConnectionException(Throwable cause) { 11 | super("It can not connect the remote address! Please check it (maybe the network is wrong)!", cause); 12 | } 13 | 14 | public NSQNoConnectionException(String message) { 15 | super(message); 16 | } 17 | 18 | public NSQNoConnectionException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/SimplePartitionsSelector.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by lin on 16/12/19. 7 | */ 8 | public class SimplePartitionsSelector implements IPartitionsSelector{ 9 | final private List pras; 10 | 11 | public SimplePartitionsSelector(final List curPras) { 12 | this.pras = curPras; 13 | } 14 | 15 | @Override 16 | public Partitions[] choosePartitions() { 17 | return this.pras.toArray(new Partitions[0]); 18 | } 19 | 20 | @Override 21 | public Partitions[] dumpAllPartitions() { 22 | return this.pras.toArray(new Partitions[0]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/ConsumeMessageFilterMode.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import static com.youzan.nsq.client.entity.ConsumeMessageFilter.*; 4 | 5 | /** 6 | * Created by lin on 17/11/20. 7 | */ 8 | public enum ConsumeMessageFilterMode { 9 | EXACT_MATCH(EXACT_MATCH_FILTER), 10 | REGEXP_MATCH(REG_EXP_MATCH_FILTER), 11 | GLOB_MATCH(GLOB_EXP_MATCH_FILTER), 12 | MULTI_MATCH(MULTI_MATCH_FILTER); 13 | 14 | 15 | private ConsumeMessageFilter currentMessageFilter; 16 | 17 | ConsumeMessageFilterMode(ConsumeMessageFilter filter){ 18 | currentMessageFilter = filter; 19 | } 20 | 21 | public ConsumeMessageFilter getFilter() { 22 | return this.currentMessageFilter; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/AbstractSeedLookupdConfig.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import com.youzan.nsq.client.configs.DCCSeedLookupdConfig; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by lin on 16/12/5. 9 | */ 10 | public abstract class AbstractSeedLookupdConfig extends AbstractLookupdConfig { 11 | 12 | public abstract List getSeedLookupAddress(String categorization, String topic); 13 | 14 | public abstract NSQLookupdAddresses punchLookupdAddress(String categorization, final String topic, boolean force); 15 | 16 | public static AbstractSeedLookupdConfig create(String categorization) { 17 | return new DCCSeedLookupdConfig(categorization); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQLookupException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.exception; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class NSQLookupException extends NSQException { 10 | 11 | private static final long serialVersionUID = -7520428599551287228L; 12 | private static final Logger logger = LoggerFactory.getLogger(NSQLookupException.class); 13 | 14 | public NSQLookupException(String message) { 15 | super(message); 16 | } 17 | 18 | public NSQLookupException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public NSQLookupException(String message, Throwable cause) { 23 | super(message, cause); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/app-test.properties: -------------------------------------------------------------------------------- 1 | env=QA 2 | lookup-addresses=sqs-qa.s.qima-inc.com:4161 3 | dcc-lookup=dcc://10.9.7.75:8089?env=qa 4 | #lookup-addresses=nsq-qa.s.qima-inc.com:4161 5 | connectTimeoutInMillisecond=3000 6 | msgTimeoutInMillisecond=30000 7 | threadPoolSize4IO=1 8 | 9 | admin-address=sqs-qa.s.qima-inc.com:4161 10 | 11 | #trace agent configs, override what defined in config_client.properties 12 | urls = http://10.9.7.75:8089 13 | configAgentEnv = qa 14 | nsq.dcc.env = qa 15 | backupFilePath = /tmp/nsqtest/nsqConfigAgent.bak 16 | 17 | new-lookup-addresses = sqs-qa.s.qima-inc.com:4161 18 | old-lookup-addresses = nsq-dev.s.qima-inc.com:4161 19 | old-nsqd-http = 10.9.6.49:4151 20 | 21 | configClientPath.test = src/test/resources/configClientTest.properties 22 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/AbstractLookupdAddress.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | /** 4 | * 5 | * Created by lin on 16/12/5. 6 | */ 7 | public abstract class AbstractLookupdAddress { 8 | protected volatile String address; 9 | private volatile String clusterId; 10 | 11 | public AbstractLookupdAddress(String clusterId, String address){ 12 | this.clusterId = clusterId; 13 | this.address = address; 14 | } 15 | 16 | public String getAddress() { 17 | return this.address; 18 | } 19 | 20 | public String getClusterId() { 21 | return this.clusterId; 22 | } 23 | 24 | public boolean isValid(){ 25 | return null != this.clusterId && null != address; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/TopicSharding.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | /** 4 | * Created by lin on 16/11/7. 5 | */ 6 | public interface TopicSharding { 7 | 8 | /** 9 | * return partition id in range of @param partitionIDs, given passin seed 10 | * @param passInSeed pass in seed 11 | * @param partitionNum total number of partitions. 12 | * @return partiton ID in range of @param partitionIDs 13 | */ 14 | int toPartitionID(T passInSeed, final int partitionNum); 15 | 16 | /** 17 | * return sharding code in {@link Long}, for NSQ SDK to tell if sharding code is valid(>= 0), or not. 18 | * @param passInSeed pass in seed 19 | * @return sharding code in {@link Long} 20 | */ 21 | long toShardingCode(T passInSeed); 22 | } 23 | -------------------------------------------------------------------------------- /src/assembly/conf/youzan/configClient.properties: -------------------------------------------------------------------------------- 1 | #default properties for dcc remote connection goes here. NSQConfig trying accessing it to initialize config agent. 2 | #The location of this configClient.properties file is specified by system property "nsq.sdk.configFilePath" 3 | #configs urls for different environments 4 | 5 | #2. ConfigAccessAgent properties: 6 | #2.1 Config Access agent implementation class name 7 | nsq.sdk.configAccessClass = com.youzan.nsq.client.configs.DCCConfigAccessAgent 8 | 9 | #3.2 DCC client backup properties 10 | nsq.dcc.backupPath = ./data/nsq-client-java/nsqdccConfig.bak 11 | 12 | #5 Lookup config access properties: 13 | #config keys configAccessAgent watches. 14 | #5.1 trace log key 15 | nsq.key.topic.trace = topic.trace 16 | #5.2 Note: key for migration control config is not exposed. 17 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQInvalidMessageException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | import com.youzan.nsq.client.entity.Response; 4 | 5 | public class NSQInvalidMessageException extends NSQException { 6 | private static final long serialVersionUID = 2600952717826058158L; 7 | public static NSQInvalidMessageException EXP_INVALD_MSG = new NSQInvalidMessageException(); 8 | 9 | public NSQInvalidMessageException() { 10 | super(Response.E_BAD_MESSAGE + " Pls check if message body size exceeds limitation in NSQ."); 11 | } 12 | 13 | public NSQInvalidMessageException(String message) { 14 | super(message); 15 | } 16 | 17 | public NSQInvalidMessageException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/assembly/conf/openSource/configClient.properties: -------------------------------------------------------------------------------- 1 | #default properties for dcc remote connection goes here. NSQConfig trying accessing it to initialize config agent. 2 | #The location of this configClient.properties file is specified by system property "nsq.sdk.configFilePath" 3 | #configs urls for different environments 4 | 5 | #2. ConfigAccessAgent properties: 6 | #2.1 Config Access agent implementation class name 7 | nsq.sdk.configAccessClass = com.youzan.nsq.client.configs.DoNothingConfigAccessAgent 8 | 9 | #3.2 DCC client backup properties 10 | nsq.dcc.backupPath = ./data/nsq-client-java/nsqdccConfig.bak 11 | 12 | #5 Lookup config access properties: 13 | #config keys configAccessAgent watches. 14 | #5.1 trace log key 15 | nsq.key.topic.trace = topic.trace 16 | #5.2 Note: key for migration control config is not exposed. 17 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/MockedProducer.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.core.NSQConnection; 4 | import com.youzan.nsq.client.entity.Context; 5 | import com.youzan.nsq.client.entity.NSQConfig; 6 | import com.youzan.nsq.client.entity.Topic; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | 9 | /** 10 | * Created by lin on 17/8/3. 11 | */ 12 | public class MockedProducer extends ProducerImplV2 { 13 | /** 14 | * @param config NSQConfig 15 | */ 16 | public MockedProducer(NSQConfig config) { 17 | super(config); 18 | } 19 | 20 | public NSQConnection getConnection(final Topic topic, Object objectShardingID, final Context cxt) throws NSQException { 21 | return this.getNSQConnection(topic, objectShardingID, cxt); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/utils/BytesUtil.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.utils; 2 | 3 | /** 4 | * Created by leen on 6/21/17. 5 | */ 6 | public class BytesUtil { 7 | 8 | public static void writeInt(byte[] bytes, int offset, int value) { 9 | bytes[offset] = (byte) (value & 0xFF); 10 | bytes[offset + 1] = (byte) ((value >> 8) & 0xFF); 11 | bytes[offset + 2] = (byte) ((value >> 16) & 0xFF); 12 | bytes[offset + 3] = (byte) ((value >> 24) & 0xFF); 13 | } 14 | 15 | public static int readInt(byte[] bytes, int offset) { 16 | int value = bytes[offset] & 0xFF; 17 | value |= bytes[offset + 1] << 8 & 0xFF00; 18 | value |= bytes[offset + 2] << 16 & 0xFF0000; 19 | value |= bytes[offset + 3] << 24 & 0xFF000000; 20 | return value; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/SystemUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * @author zhaoxi (linzuxiong) 12 | * 13 | * 14 | */ 15 | public final class SystemUtil { 16 | private static final Logger logger = LoggerFactory.getLogger(SystemUtil.class); 17 | private static final ObjectMapper mapper = new ObjectMapper(); 18 | 19 | public static long getPID() { 20 | String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); 21 | return Long.parseLong(processName.split("@")[0]); 22 | } 23 | public static ObjectMapper getObjectMapper(){ 24 | return mapper; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/ExtVer.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * Created by lin on 17/5/25. 7 | */ 8 | public enum ExtVer { 9 | Ver0x0(0), 10 | //tag ext 11 | Ver0x2(2), 12 | //json header ext 13 | Ver0x4(4); 14 | 15 | private final int ver; 16 | 17 | ExtVer(int ver) { 18 | this.ver = ver; 19 | } 20 | 21 | public int getVersion() { 22 | return this.ver; 23 | } 24 | 25 | public static ExtVer getExtVersion(byte[] extVerByte) { 26 | int ver = ByteBuffer.wrap(extVerByte).get() & 0xFF; 27 | switch (ver) { 28 | case 4: 29 | return Ver0x4; 30 | case 2: 31 | return Ver0x2; 32 | default: 33 | return Ver0x0; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/Feature.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * @author zhaoxi (linzuxiong) 11 | */ 12 | public class Feature { 13 | private static final Logger logger = LoggerFactory.getLogger(Feature.class); 14 | 15 | private int max_rdy_count; 16 | private String version; 17 | private long max_msg_timeout; 18 | private long msg_timeout; 19 | private boolean tls_v1; 20 | private boolean deflate; 21 | private int deflate_level; 22 | private int max_deflate_level; 23 | private boolean snappy; 24 | private int sample_rate; 25 | private boolean auth_required; 26 | private int output_buffer_size; 27 | private int output_buffer_timeout; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/ExplicitRequeueException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | /** 4 | * Exception thrown in message handler to explicitly indicate SDK that message need requeue. 5 | * When SDK catches it, it requeues current message, without print error log. 6 | * Created by lin on 17/10/17. 7 | */ 8 | public class ExplicitRequeueException extends RuntimeException { 9 | private final boolean depressWarnLog; 10 | 11 | public ExplicitRequeueException(String message) { 12 | super(message); 13 | this.depressWarnLog = true; 14 | } 15 | 16 | public ExplicitRequeueException(String message, boolean depressWarnLog) { 17 | super(message); 18 | this.depressWarnLog = depressWarnLog; 19 | } 20 | 21 | public boolean isWarnLogDepressed() { 22 | return this.depressWarnLog; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/IConfigAccessSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.configs.AbstractConfigAccessDomain; 4 | import com.youzan.nsq.client.configs.AbstractConfigAccessKey; 5 | import com.youzan.nsq.client.configs.ConfigAccessAgent; 6 | import com.youzan.nsq.client.entity.Role; 7 | import com.youzan.nsq.client.exception.NSQConfigAccessException; 8 | 9 | /** 10 | * Interface for subscriber to config access agent. 11 | * Created by lin on 16/10/26. 12 | */ 13 | public interface IConfigAccessSubscriber { 14 | String NSQ_APP_VAL = "nsq.app.val"; 15 | String DEFAULT_NSQ_APP_VAL = "nsq"; 16 | 17 | T subscribe(ConfigAccessAgent subscribeTo, final AbstractConfigAccessDomain domain, final AbstractConfigAccessKey[] keys, final ConfigAccessAgent.IConfigAccessCallback callback) throws NSQConfigAccessException; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/IExpectedRdyUpdatePolicy.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | /** 4 | * Created by lin on 17/8/15. 5 | */ 6 | public interface IExpectedRdyUpdatePolicy { 7 | /** 8 | * calculate new current rdy after INCREASING, given current rdy and expected rdy 9 | * @param currentRdy current rdy in connection 10 | * @param expectedRdy expected connection rdy 11 | * @return new current rdy 12 | */ 13 | int expectedRdyIncrease(int currentRdy, int expectedRdy); 14 | 15 | /** 16 | * calculate new expected rdy after DECLINING, given current rdy and expected rdy 17 | * @param currentRdy current rdy in connection 18 | * @param expectedRdy expected connection rdy 19 | * @return new current rdy after decline 20 | */ 21 | int expectedRdyDecline(int currentRdy, int expectedRdy); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DCCTraceConfigAccessDomain.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import static com.youzan.nsq.client.IConfigAccessSubscriber.DEFAULT_NSQ_APP_VAL; 4 | import static com.youzan.nsq.client.IConfigAccessSubscriber.NSQ_APP_VAL; 5 | 6 | /** 7 | * trace domain in DCC config access agent 8 | * Created by lin on 16/12/14. 9 | */ 10 | public class DCCTraceConfigAccessDomain extends AbstractConfigAccessDomain { 11 | 12 | 13 | public DCCTraceConfigAccessDomain() { 14 | super(null); 15 | } 16 | 17 | @Override 18 | public String toDomain() { 19 | String domain = ConfigAccessAgent.getProperty(NSQ_APP_VAL); 20 | return null == domain ? DEFAULT_NSQ_APP_VAL : domain; 21 | } 22 | 23 | public static AbstractConfigAccessDomain getInstacne() { 24 | return new DCCTraceConfigAccessDomain(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/core/pool/producer/KeyedPooledConnectionFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.pool.producer; 2 | 3 | import com.youzan.nsq.client.core.NSQConnection; 4 | import com.youzan.nsq.client.entity.Address; 5 | import com.youzan.nsq.client.entity.NSQConfig; 6 | import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 7 | import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 8 | import org.testng.annotations.Test; 9 | 10 | public class KeyedPooledConnectionFactoryTest { 11 | @Test(expectedExceptions = Exception.class) 12 | public void createBigPoolFail() { 13 | NSQConfig config = null; 14 | GenericKeyedObjectPoolConfig poolConfig = null; 15 | GenericKeyedObjectPool bigPool = new GenericKeyedObjectPool<>( 16 | new KeyedPooledConnectionFactory(config, null), poolConfig); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DCCMigrationConfigAccessDomain.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | /** 4 | * Migration config access domain for DCC. 5 | * Created by lin on 16/12/9. 6 | */ 7 | public class DCCMigrationConfigAccessDomain extends AbstractConfigAccessDomain { 8 | private String domain; 9 | private static final String DOMAIN_SUFFIX = "%s.nsq.lookupd.addr"; 10 | 11 | private DCCMigrationConfigAccessDomain(String domain) { 12 | super(domain); 13 | } 14 | 15 | @Override 16 | public String toDomain() { 17 | if (null == domain) 18 | this.domain = String.format(DOMAIN_SUFFIX, TopicRuleCategory.trimTopic(this.innerDomain)); 19 | return this.domain; 20 | } 21 | 22 | public static AbstractConfigAccessDomain getInstance(final String topic) { 23 | return new DCCMigrationConfigAccessDomain(topic); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DCCTraceConfigAccessKey.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | /** 4 | * Tract config access key in DCC config access agent 5 | * Created by lin on 16/12/14. 6 | */ 7 | public class DCCTraceConfigAccessKey extends AbstractConfigAccessKey { 8 | private final static String NSQ_TOPIC_TRACE_KEY = "nsq.key.topic.trace"; 9 | private final static String DEFAULT_NSQ_TOPIC_TRACE_PRODUCER_KEY = "topic.trace"; 10 | 11 | public DCCTraceConfigAccessKey() { 12 | super(null); 13 | } 14 | 15 | @Override 16 | public String toKey() { 17 | String key = ConfigAccessAgent.getProperty(NSQ_TOPIC_TRACE_KEY); 18 | key = null == key ? DEFAULT_NSQ_TOPIC_TRACE_PRODUCER_KEY : key; 19 | return key; 20 | } 21 | 22 | public static AbstractConfigAccessKey getInstacne() { 23 | return new DCCTraceConfigAccessKey(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQDataNodesDownException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.exception; 5 | 6 | public class NSQDataNodesDownException extends NSQException { 7 | private static final long serialVersionUID = 2336078064990893992L; 8 | 9 | public NSQDataNodesDownException() { 10 | super("SDK has done its best effort! All of the data nodes(server-side) are down! Please check both the client and the server!"); 11 | } 12 | 13 | public NSQDataNodesDownException(Throwable cause) { 14 | super("SDK has done its best effort! All of the data nodes(server-side) are down! Please check both the client and the server!", cause); 15 | } 16 | 17 | public NSQDataNodesDownException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | 21 | public NSQDataNodesDownException(String message) { 22 | super(message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/entity/AddressTest.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | 4 | import org.testng.Assert; 5 | import org.testng.annotations.Test; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * Created by lin on 17/10/13. 12 | */ 13 | public class AddressTest { 14 | 15 | @Test 16 | public void testAddressCompare() { 17 | //String host, String port, String version, String topic, int partition, boolean extend 18 | Address addr1 = new Address("127.0.0.1", "4150", "v0.3.8-H.A", "topic", 0, false); 19 | Address addr2 = new Address("127.0.0.1", "4150", "v0.3.8-H.A", "topic", 0, true); 20 | Address addr3 = new Address("127.0.0.1", "4150", "v0.3.8-H.A", "topic", 0, true); 21 | Set except = new HashSet(); 22 | except.add(addr1); 23 | except.add(addr2); 24 | except.remove(addr3); 25 | Assert.assertEquals(1, except.size()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/TopicInfo.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by lin on 17/12/12. 7 | */ 8 | public class TopicInfo implements Serializable { 9 | private String topicName; 10 | private int partition; 11 | private boolean isExt; 12 | 13 | public TopicInfo() { 14 | 15 | } 16 | 17 | public TopicInfo(String topicName, int partition, boolean isExt) { 18 | this.topicName = topicName; 19 | this.partition = partition; 20 | this.isExt = isExt; 21 | } 22 | 23 | public String getTopicName() { 24 | return this.topicName; 25 | } 26 | 27 | public int getTopicPartition() { 28 | return this.partition; 29 | } 30 | 31 | public boolean isExt() { 32 | return this.isExt; 33 | } 34 | 35 | public String toString() { 36 | return this.topicName + ", " + this.partition + ", " + this.isExt; 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITBase.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.testng.annotations.BeforeClass; 6 | import org.testng.annotations.Test; 7 | 8 | import java.io.InputStream; 9 | import java.util.Properties; 10 | 11 | @Test 12 | public class ITBase { 13 | private static final Logger logger = LoggerFactory.getLogger(ITBase.class); 14 | 15 | @BeforeClass 16 | public void init() throws Exception { 17 | logger.info("At {} , initialize: {}", System.currentTimeMillis(), this.getClass().getName()); 18 | final Properties props = new Properties(); 19 | try (final InputStream is = getClass().getClassLoader().getResourceAsStream("app-test.properties")) { 20 | props.load(is); 21 | } 22 | final String env = props.getProperty("env"); 23 | logger.debug("The environment is {} .", env); 24 | } 25 | 26 | public void test() { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/exception/NSQPubException.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.exception; 2 | 3 | import org.slf4j.Logger; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by lin on 17/1/11. 9 | */ 10 | public class NSQPubException extends NSQException { 11 | 12 | private List exptions; 13 | 14 | public NSQPubException(String message, final List exptions) { 15 | super(message); 16 | this.exptions = exptions; 17 | } 18 | 19 | public NSQPubException(final List exptions) { 20 | super("Fail to pub message. Refer to nested exceptions for details."); 21 | this.exptions = exptions; 22 | } 23 | 24 | public void punchExceptions(Logger logger) { 25 | int idx = 0; 26 | for(Exception exp : this.exptions){ 27 | logger.error("Nested exception {}:", ++idx, exp); 28 | } 29 | } 30 | 31 | public List getNestedExceptions(){ 32 | return this.exptions; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node # 2 | ###################### 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | 19 | # Logs and databases # 20 | ###################### 21 | *.log 22 | *.sql 23 | *.sqlite 24 | 25 | # OS generated files # 26 | ###################### 27 | .DS_Store 28 | .DS_Store? 29 | ._* 30 | .Spotlight-V100 31 | .Trashes 32 | ehthumbs.db 33 | Thumbs.db 34 | 35 | # Editor # 36 | ###################### 37 | .idea/ 38 | iron.iml 39 | *.sublime-workspace 40 | *.sublime-project 41 | iron.iml 42 | /nbproject/ 43 | .tm_properties 44 | 45 | /kdt.zip 46 | .tags 47 | .tags_sorted_by_file 48 | 49 | .buildpath 50 | .project 51 | .classpath 52 | .settings/ 53 | koala-config.json 54 | 55 | .externalToolBuilders/ 56 | data/ 57 | target/ 58 | out/ 59 | *.iml 60 | /src/test/resources/app-test.properties 61 | /src/test/resources/nsq/ 62 | /src/test/resources/configClientTestStableWDCC.properties 63 | /src/test/resources/configClientTest.properties 64 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/AbstractLookupdAddresses.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Lookupd addresses gathers from previous clusters and current clusters, addresses in previous or current part comes 7 | * from different clusters, That is the difference between {@link AbstractLookupdAddress} 8 | * Created by lin on 17/2/9. 9 | */ 10 | public class AbstractLookupdAddresses { 11 | protected List addresses; 12 | private List clusterIds; 13 | 14 | public AbstractLookupdAddresses(List clusterIds, List addresses){ 15 | this.clusterIds = clusterIds; 16 | this.addresses = addresses; 17 | } 18 | 19 | public List getAddresses() { 20 | return this.addresses; 21 | } 22 | 23 | public List getClusterIds() { 24 | return this.clusterIds; 25 | } 26 | 27 | public boolean isValid(){ 28 | return null != this.clusterIds && null != addresses && this.clusterIds.size() > 0 && this.addresses.size() > 0; 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Magic.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author zhaoxi (linzuxiong) 13 | * 14 | * 15 | */ 16 | public class Magic implements NSQCommand { 17 | private static final Logger logger = LoggerFactory.getLogger(Magic.class); 18 | 19 | private final byte[] data; 20 | 21 | private static class Instance { 22 | // final 23 | private static final Magic instance = new Magic(); 24 | } 25 | 26 | public static Magic getInstance() { 27 | return Instance.instance; 28 | } 29 | 30 | private Magic() { 31 | this.data = " V2".getBytes(ASCII); 32 | } 33 | 34 | @Override 35 | public byte[] getBytes() { 36 | return data; 37 | } 38 | 39 | @Override 40 | public String getHeader() { 41 | return ""; 42 | } 43 | 44 | @Override 45 | public List getBody() { 46 | return EMPTY_BODY; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Nop.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author zhaoxi (linzuxiong) 13 | * 14 | * 15 | */ 16 | public class Nop implements NSQCommand { 17 | private static final Logger logger = LoggerFactory.getLogger(Nop.class); 18 | 19 | private final byte[] data; 20 | 21 | private static class Instance { 22 | // final 23 | private static final Nop instance = new Nop(); 24 | } 25 | 26 | public static Nop getInstance() { 27 | return Instance.instance; 28 | } 29 | 30 | private Nop() { 31 | this.data = "NOP\n".getBytes(DEFAULT_CHARSET); 32 | } 33 | 34 | @Override 35 | public byte[] getBytes() { 36 | return data; 37 | } 38 | 39 | @Override 40 | public String getHeader() { 41 | return null; 42 | } 43 | 44 | @Override 45 | public List getBody() { 46 | return EMPTY_BODY; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Close.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author zhaoxi (linzuxiong) 13 | * 14 | * 15 | */ 16 | public class Close implements NSQCommand { 17 | private static final Logger logger = LoggerFactory.getLogger(Close.class); 18 | 19 | private final byte[] data; 20 | 21 | private static class Instance { 22 | // final 23 | private static final Close instance = new Close(); 24 | } 25 | 26 | public static Close getInstance() { 27 | return Instance.instance; 28 | } 29 | 30 | private Close() { 31 | this.data = "CLS\n".getBytes(DEFAULT_CHARSET); 32 | } 33 | 34 | @Override 35 | public byte[] getBytes() { 36 | return data; 37 | } 38 | 39 | @Override 40 | public String getHeader() { 41 | return ""; 42 | } 43 | 44 | @Override 45 | public List getBody() { 46 | return EMPTY_BODY; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Touch.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by lin on 17/8/1. 11 | */ 12 | public class Touch implements NSQCommand { 13 | private static final Logger logger = LoggerFactory.getLogger(Touch.class); 14 | 15 | private final byte[] data; 16 | 17 | public Touch(byte[] messageID) { 18 | if (messageID == null || messageID.length <= 0) { 19 | throw new IllegalArgumentException("Your input messageID is empty!"); 20 | } 21 | final byte[] cmd = "TOUCH ".getBytes(UTF8); 22 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + messageID.length + 1); 23 | bb.put(cmd).put(messageID).put(LINE_SEPARATOR); 24 | this.data = bb.array(); 25 | } 26 | 27 | @Override 28 | public byte[] getBytes() { 29 | return data; 30 | } 31 | 32 | @Override 33 | public String getHeader() { 34 | return ""; 35 | } 36 | 37 | @Override 38 | public List getBody() { 39 | return EMPTY_BODY; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/TopicSync.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import java.util.concurrent.locks.ReentrantReadWriteLock; 4 | 5 | /** 6 | * topic synchronization in topic class 7 | * Created by lin on 17/6/19. 8 | */ 9 | public class TopicSync { 10 | private final String topic; 11 | private final ReentrantReadWriteLock lock; 12 | 13 | public TopicSync(final String topic) { 14 | this.topic = topic; 15 | this.lock = new ReentrantReadWriteLock(); 16 | } 17 | 18 | public String getTopicText() { 19 | return this.topic; 20 | } 21 | 22 | public void lock() { 23 | this.lock.writeLock().lock(); 24 | } 25 | 26 | public void unlock() { 27 | this.lock.writeLock().unlock(); 28 | } 29 | 30 | /** 31 | * try acquire write lock of current topic 32 | * @return {@code true} if the lock was free and was acquired 33 | * by the current thread, or the write lock was already held 34 | * by the current thread; and {@code false} otherwise. 35 | * 36 | */ 37 | public boolean tryLock() { 38 | return this.lock.writeLock().tryLock(); 39 | } 40 | 41 | public int hashCode() { 42 | return this.topic.hashCode(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/DoNothingConfigAccessAgent.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.SortedMap; 7 | 8 | /** 9 | * Do nothing configAccesssAgent does not talk to any config access remote. 10 | * Created by lin on 16/10/26. 11 | */ 12 | public class DoNothingConfigAccessAgent extends ConfigAccessAgent { 13 | private static final Logger logger = LoggerFactory.getLogger(DoNothingConfigAccessAgent.class); 14 | 15 | public DoNothingConfigAccessAgent() { 16 | logger.info("Do nothing config access agent initialize."); 17 | } 18 | 19 | @Override 20 | public SortedMap handleSubscribe(AbstractConfigAccessDomain domain, AbstractConfigAccessKey[] keys, IConfigAccessCallback callback) { 21 | return null; 22 | } 23 | 24 | @Override 25 | public void kickoff() { 26 | logger.info("Do nothing config access agent kicksoff."); 27 | } 28 | 29 | @Override 30 | public void close() { 31 | logger.info("Do nothing config access agent close."); 32 | } 33 | 34 | @Override 35 | public String metadata() { 36 | return DoNothingConfigAccessAgent.class.getName(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/netty/NSQClientInitializer.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.netty; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 7 | import io.netty.handler.timeout.IdleStateHandler; 8 | 9 | public class NSQClientInitializer extends ChannelInitializer { 10 | @Override 11 | protected void initChannel(SocketChannel ch) throws Exception { 12 | ChannelPipeline pipeline = ch.pipeline(); 13 | final int Integer_BYTES = 4; 14 | LengthFieldBasedFrameDecoder dec = new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, Integer_BYTES); 15 | dec.setSingleDecode(true); 16 | 17 | pipeline.addLast("IdleStateHandler", new IdleStateHandler(120, 120, 120)); 18 | pipeline.addLast("LengthFieldBasedFrameDecoder", dec); // in 19 | pipeline.addLast("NSQDecoder", new NSQDecoder()); // in 20 | pipeline.addLast("NSQEncoder", new NSQEncoder()); // out 21 | pipeline.addLast("FeatureDetectionHandler", new NSQFeatureDetectionHandler()); // in 22 | pipeline.addLast("NSQHandler", new NSQHandler()); // in 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/IPUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import java.net.UnknownHostException; 7 | 8 | /** 9 | * IPv4 is 32 bit unsigned. 10 | * 11 | * @author zhaoxi (linzuxiong) 12 | * 13 | */ 14 | public final class IPUtil { 15 | 16 | public static long ipv4(String ipv4) throws UnknownHostException { 17 | long result = 0L; 18 | String[] bs = ipv4.split("\\."); 19 | result |= Long.parseLong(bs[0]) << 24; 20 | result |= Long.parseLong(bs[1]) << 16; 21 | result |= Long.parseLong(bs[2]) << 8; 22 | result |= Long.parseLong(bs[3]); 23 | return result; 24 | } 25 | 26 | public static String ipv4(long ipv4) throws UnknownHostException { 27 | if (ipv4 < 0) { 28 | throw new UnknownHostException(String.valueOf(ipv4)); 29 | } 30 | final StringBuffer result = new StringBuffer(15); 31 | result.append((ipv4 >> 24) & 0xFF); 32 | result.append("."); 33 | result.append((ipv4 >> 16) & 0xFF); 34 | result.append("."); 35 | result.append((ipv4 >> 8) & 0xFF); 36 | result.append("."); 37 | result.append(ipv4 & 0xFF); 38 | return result.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/netty/NSQEncoder.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.netty; 2 | 3 | import com.youzan.nsq.client.core.command.NSQCommand; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufAllocator; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToMessageEncoder; 8 | 9 | import java.util.List; 10 | 11 | public class NSQEncoder extends MessageToMessageEncoder { 12 | 13 | @Override 14 | protected void encode(ChannelHandlerContext ctx, NSQCommand command, List out) throws Exception { 15 | if (command == null) { 16 | throw new NullPointerException("I can not encode Null-Pointer!"); 17 | } 18 | 19 | final byte[] bs = command.getBytes(); 20 | if(null == bs) 21 | throw new IllegalStateException("Command bytes is null, current command need to impl getBytes interface. Command: " + command.toString()); 22 | if (bs.length > 0) { 23 | final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(bs.length); 24 | buf.writeBytes(bs); 25 | out.add(buf); 26 | } else { 27 | throw new IllegalStateException("NSQCommand isn't a right implementation!"); 28 | } 29 | return; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/testng-partition-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Finish.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.List; 8 | 9 | /** 10 | * @author zhaoxi (linzuxiong) 11 | * 12 | * 13 | */ 14 | public class Finish implements NSQCommand { 15 | private static final Logger logger = LoggerFactory.getLogger(Finish.class); 16 | 17 | private final byte[] data; 18 | 19 | public Finish(byte[] messageID) { 20 | if (messageID == null || messageID.length <= 0) { 21 | throw new IllegalArgumentException("Your input messageID is empty!"); 22 | } 23 | final byte[] cmd = "FIN ".getBytes(UTF8); 24 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + messageID.length + 1); 25 | // FIN \n 26 | bb.put(cmd).put(messageID).put(LINE_SEPARATOR); 27 | this.data = bb.array(); 28 | } 29 | 30 | @Override 31 | public byte[] getBytes() { 32 | return data; 33 | } 34 | 35 | @Override 36 | public String getHeader() { 37 | return ""; 38 | } 39 | 40 | @Override 41 | public List getBody() { 42 | return EMPTY_BODY; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/Response.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.entity; 5 | 6 | /** 7 | * @author zhaoxi (linzuxiong) 8 | * 9 | * 10 | */ 11 | public enum Response { 12 | 13 | OK("OK"), // 14 | _HEARTBEAT_("_heartbeat_"),// 15 | // Error Responses 16 | E_INVALID("E_INVALID"),// 17 | E_BAD_TOPIC("E_BAD_TOPIC"), // 18 | E_TOPIC_NOT_EXIST("E_TOPIC_NOT_EXIST"),// 19 | E_BAD_MESSAGE("E_BAD_MESSAGE"), // 20 | E_PUB_FAILED("E_PUB_FAILED"), // 21 | E_BAD_BODY("E_BAD_BODY"), // 22 | E_MPUB_FAILED("E_MPUB_FAILED"),// 23 | E_FAILED_ON_NOT_LEADER("E_FAILED_ON_NOT_LEADER"),// 24 | E_FAILED_ON_NOT_WRITABLE("E_FAILED_ON_NOT_WRITABLE"), // 25 | E_FIN_FAILED("E_FIN_FAILED"), // 26 | E_SUB_ORDER_IS_MUST("E_SUB_ORDER_IS_MUST"), // 27 | E_BAD_TAG("E_BAD_TAG"), 28 | E_TAG_NOT_SUPPORT ("E_TAG_NOT_SUPPORT"), 29 | E_SUB_EXTEND_NEED ("E_SUB_EXTEND_NEED"), 30 | E_EXT_NOT_SUPPORT ("E_EXT_NOT_SUPPORT") 31 | ; 32 | 33 | private final String content; 34 | 35 | Response(String content) { 36 | this.content = content; 37 | } 38 | 39 | /** 40 | * @return the content 41 | */ 42 | public String getContent() { 43 | return content; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/SubOrdered.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import com.youzan.nsq.client.entity.Topic; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * Created by lin on 16/9/11. 9 | */ 10 | public class SubOrdered extends Sub{ 11 | 12 | public SubOrdered(Topic topic, String channel) { 13 | super(topic, channel); 14 | } 15 | 16 | @Override 17 | public byte[] getBytes() { 18 | if(null == this.data){ 19 | final byte[] cmd = "SUB_ORDERED ".getBytes(DEFAULT_CHARSET); 20 | final byte[] topicBytes = topic.getTopicText().getBytes(DEFAULT_CHARSET); 21 | final byte[] channelBytes = channel.getBytes(DEFAULT_CHARSET); 22 | byte[] partitionBytes = null; 23 | partitionBytes = getPartitionIdByte(topic); 24 | //fixed buffer 25 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + topicBytes.length + 1 + channelBytes.length + 1 + partitionBytes.length); 26 | // SUB \n 27 | bb.put(cmd).put(topicBytes).put(SPACE).put(channelBytes) 28 | .put(partitionBytes) 29 | .put(LINE_SEPARATOR); 30 | this.data = bb.array(); 31 | } 32 | return data; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/MockedNSQConnectionImpl.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.core.NSQConnectionImpl; 4 | import com.youzan.nsq.client.entity.Address; 5 | import com.youzan.nsq.client.entity.NSQConfig; 6 | import com.youzan.nsq.client.entity.Topic; 7 | import com.youzan.nsq.client.network.frame.ResponseFrame; 8 | import io.netty.channel.Channel; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Created by lin on 17/6/26. 14 | */ 15 | public class MockedNSQConnectionImpl extends NSQConnectionImpl { 16 | 17 | public MockedNSQConnectionImpl(int id, Address address, Channel channel, NSQConfig config) { 18 | super(id, address, channel, config); 19 | } 20 | 21 | public boolean isConnected() { 22 | return this.channel.isActive(); 23 | } 24 | 25 | public void setTopic(String topic) { 26 | super.setTopic(new Topic(topic)); 27 | } 28 | 29 | public void setTopic(Topic topic) { 30 | super.setTopic(topic); 31 | } 32 | 33 | @Override 34 | public void addResponseFrame(ResponseFrame frame) { 35 | if (!requests.isEmpty()) { 36 | try { 37 | responses.offer(frame, getConfig().getQueryTimeoutInMillisecond() * 2, TimeUnit.MILLISECONDS); 38 | } catch (InterruptedException e) { 39 | close(); 40 | Thread.currentThread().interrupt(); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/NSQCommand.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import java.nio.charset.Charset; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Only one way of implementation 10 | * 11 | * @author zhaoxi (linzuxiong) 12 | * 13 | * 14 | */ 15 | public interface NSQCommand{ 16 | 17 | Charset ASCII = StandardCharsets.US_ASCII; 18 | Charset UTF8 = StandardCharsets.UTF_8; 19 | Charset DEFAULT_CHARSET = UTF8; 20 | 21 | /** 22 | * The encoding's result between UTF-8 and US-ASCII is the same underlying 23 | * LINE_SEPARATOR. 24 | */ 25 | byte LINE_SEPARATOR = '\n'; 26 | byte SPACE = ' '; 27 | String SPACE_STR = " "; 28 | List EMPTY_BODY = new ArrayList<>(0); 29 | 30 | // ************************************************************************* 31 | // Normal command 32 | // ************************************************************************* 33 | /** 34 | * @return The binary data of sending to the NSQd (broker) 35 | */ 36 | byte[] getBytes(); 37 | 38 | // ************************************************************************* 39 | // Special command consists of header and body 40 | // ************************************************************************* 41 | 42 | String getHeader(); 43 | 44 | List getBody(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/netty/SnappyEncoder.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.netty; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.apache.commons.compress.compressors.CompressorOutputStream; 7 | import org.apache.commons.compress.compressors.CompressorStreamFactory; 8 | import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream; 9 | 10 | import java.io.BufferedOutputStream; 11 | import java.io.ByteArrayOutputStream; 12 | 13 | /** 14 | * As netty builtin {@link io.netty.handler.codec.compression.SnappyFrameEncoder} does not work with nsqd, switch to 15 | * apcahe compress lib 16 | * Created by lin on 17/8/12. 17 | */ 18 | public class SnappyEncoder extends MessageToByteEncoder { 19 | 20 | private final static CompressorStreamFactory compressFactory = new CompressorStreamFactory(); 21 | 22 | @Override 23 | protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf cmdBuf, ByteBuf byteBuf) throws Exception { 24 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 25 | BufferedOutputStream bufos = new BufferedOutputStream(bos); 26 | CompressorOutputStream cos = compressFactory.createCompressorOutputStream(CompressorStreamFactory.SNAPPY_FRAMED, bufos); 27 | cmdBuf.readBytes(cos, cmdBuf.readableBytes()); 28 | cos.close(); 29 | byteBuf.writeBytes(bos.toByteArray()); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Rdy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.util.List; 11 | 12 | /** 13 | * @author zhaoxi (linzuxiong) 14 | * 15 | * 16 | */ 17 | public class Rdy implements NSQCommand { 18 | private static final Logger logger = LoggerFactory.getLogger(Rdy.class); 19 | 20 | private final byte[] data; 21 | private final int count; 22 | public static final Rdy BACK_OFF = new Rdy(0); 23 | 24 | public Rdy(final int count) { 25 | this.count = count; 26 | final byte[] cmd = "RDY ".getBytes(UTF8); 27 | final byte[] countBytes = String.valueOf(count).getBytes(DEFAULT_CHARSET); 28 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + countBytes.length + 1); 29 | // RDY \n 30 | bb.put(cmd).put(countBytes).put(LINE_SEPARATOR); 31 | this.data = bb.array(); 32 | } 33 | 34 | public int getCount() { 35 | return this.count; 36 | } 37 | 38 | @Override 39 | public byte[] getBytes() { 40 | return data; 41 | } 42 | 43 | @Override 44 | public String getHeader() { 45 | return ""; 46 | } 47 | 48 | @Override 49 | public List getBody() { 50 | return EMPTY_BODY; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Rdy{" + "count=" + count + '}'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/ReQueue.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.util.List; 11 | 12 | public class ReQueue implements NSQCommand{ 13 | private static final Logger logger = LoggerFactory.getLogger(ReQueue.class); 14 | 15 | private final byte[] data; 16 | 17 | /** 18 | * @param messageID 19 | * the specified ID that passes to NSQd 20 | * @param timeoutInSecond 21 | * the next consuming timeout 22 | */ 23 | public ReQueue(byte[] messageID, int timeoutInSecond) { 24 | if (messageID == null || messageID.length <= 0) { 25 | throw new IllegalArgumentException("Your input messageID is empty!"); 26 | } 27 | final byte[] cmd = "REQ ".getBytes(DEFAULT_CHARSET); 28 | final byte[] timeoutBytes = String.valueOf(timeoutInSecond * 1000).getBytes(DEFAULT_CHARSET); 29 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + messageID.length + 1 + timeoutBytes.length + 1); 30 | // REQ \n 31 | bb.put(cmd).put(messageID).put(SPACE).put(timeoutBytes).put(LINE_SEPARATOR); 32 | this.data = bb.array(); 33 | } 34 | 35 | @Override 36 | public byte[] getBytes() { 37 | return data; 38 | } 39 | 40 | @Override 41 | public String getHeader() { 42 | return ""; 43 | } 44 | 45 | @Override 46 | public List getBody() { 47 | return EMPTY_BODY; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITProducerTrace.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.Producer; 4 | import com.youzan.nsq.client.ProducerImplV2; 5 | import com.youzan.nsq.client.entity.Message; 6 | import com.youzan.nsq.client.entity.Topic; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.testng.annotations.Test; 11 | 12 | /** 13 | * Created by lin on 16/10/19. 14 | */ 15 | @Test(groups = {"ITProducerTrace"}, priority = 3) 16 | public class ITProducerTrace extends ITProducer { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(ITProducer.class); 19 | 20 | public void publishTrace() throws NSQException { 21 | //set trace id, which is a long(8-byte-length) 22 | logger.info("[ITProducerTrace#publishTrace] starts"); 23 | Topic topic = new Topic("JavaTesting-Trace"); 24 | String[] lookupds = config.getLookupAddresses(); 25 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 26 | return; 27 | config.setLookupAddresses(dccLookupd); 28 | Producer producer = new ProducerImplV2(this.config); 29 | try { 30 | producer.start(); 31 | for (int i = 0; i < 10; i++) { 32 | Message msg = Message.create(topic, 45678L, ("Message #" + i)); 33 | producer.publish(msg); 34 | } 35 | }finally { 36 | producer.close(); 37 | logger.info("[ITProducerTrace#publishTrace] ends"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/lookup/LookupService.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.lookup; 2 | 3 | import com.youzan.nsq.client.configs.TopicRuleCategory; 4 | import com.youzan.nsq.client.entity.IPartitionsSelector; 5 | import com.youzan.nsq.client.exception.NSQException; 6 | import com.youzan.nsq.client.exception.NSQLookupException; 7 | 8 | import java.io.Closeable; 9 | 10 | /** 11 | * One lookup cluster 12 | * 13 | * @author zhaoxi (linzuxiong) 14 | * 15 | * 16 | */ 17 | public interface LookupService extends java.io.Serializable, Closeable { 18 | 19 | /** 20 | * 21 | * lookup the writable NSQd (DataNode) 22 | * 23 | * @param topic 24 | * a topic object 25 | * @return ordered NSQd-Server's addresses 26 | * @throws NSQLookupException 27 | * if an error occurs 28 | */ 29 | IPartitionsSelector lookup(String topic, boolean localLookupd, boolean force) throws NSQException; 30 | 31 | /** 32 | * lookup the writable/non-writable NSQd (DataNode) 33 | * 34 | * @param topic 35 | * a topic object 36 | * @param writable 37 | * set it boolean value 38 | * @return ordered NSQd-Server's addresses 39 | * @throws NSQLookupException 40 | * if an error occurs 41 | */ 42 | IPartitionsSelector lookup(String topic, boolean writable, TopicRuleCategory category, boolean localLookupd, boolean force) throws NSQException; 43 | 44 | /** 45 | * Perform the action quietly. No exceptions. 46 | */ 47 | @Override 48 | void close(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Identify.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import com.youzan.nsq.client.entity.NSQConfig; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * @author zhaoxi (linzuxiong) 16 | * 17 | * 18 | */ 19 | public class Identify implements NSQCommand { 20 | private static final Logger logger = LoggerFactory.getLogger(Identify.class); 21 | private static final byte[] IDENTITY_CMD = "IDENTIFY\n".getBytes(NSQCommand.DEFAULT_CHARSET); 22 | public static final String MSG_TIMEOUT = "msg_timeout"; 23 | public static final String MAX_MSG_TIMEOUT = "max_msg_timeout"; 24 | private final List body = new ArrayList<>(1); 25 | private byte[] bytes = null; 26 | public Identify(final NSQConfig config, boolean topicExt) { 27 | this.body.add(config.identify(topicExt).getBytes(DEFAULT_CHARSET)); 28 | } 29 | 30 | @Override 31 | public byte[] getBytes() { 32 | if(bytes == null) { 33 | byte[] body = this.getBody().get(0); 34 | ByteBuffer buf = ByteBuffer.allocate(IDENTITY_CMD.length + 4 + body.length); 35 | buf.put(IDENTITY_CMD) 36 | .putInt(body.length) 37 | .put(body); 38 | bytes = buf.array(); 39 | } 40 | return bytes; 41 | } 42 | 43 | @Override 44 | public String getHeader() { 45 | return ""; 46 | } 47 | 48 | @Override 49 | public List getBody() { 50 | return this.body; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/TraceLogger.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import com.youzan.nsq.client.MessageMetadata; 4 | import com.youzan.nsq.client.Producer; 5 | import com.youzan.nsq.client.core.Client; 6 | import com.youzan.nsq.client.core.NSQConnection; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Created by lin on 16/9/8. 12 | */ 13 | public class TraceLogger { 14 | private static final Logger logger = LoggerFactory.getLogger(TraceLogger.class); 15 | 16 | private static final Logger trace = LoggerFactory.getLogger("nsq.sdk.message.trace"); 17 | 18 | private static final String DEFAULT_MSG_REV_TRACE_FORMAT = "Client: %s <= NSQd: %s\n\tMessage meta-data: %s"; 19 | private static final String DEFAULT_MSG_SEN_TRACE_FORMAT = "Client: %s => NSQd: %s\n\tMessage meta-data: %s"; 20 | 21 | public static boolean isTraceLoggerEnabled(){ 22 | return trace.isDebugEnabled(); 23 | } 24 | /** 25 | * static function to record trace of pass in {@link Message} message in pass in client 26 | * @param client {@link Client} NSQ Producer or Consumer 27 | * @param nsqd {@link NSQConnection} NSQd connection 28 | * @param msg {@link MessageMetadata} message meta data 29 | */ 30 | public static void trace(final Client client, final NSQConnection nsqd, final MessageMetadata msg){ 31 | String traceMsg; 32 | if(client instanceof Producer) { 33 | traceMsg = String.format(DEFAULT_MSG_SEN_TRACE_FORMAT, client.toString(), nsqd.getAddress().toString(), msg.toMetadataStr()); 34 | }else { 35 | traceMsg = String.format(DEFAULT_MSG_REV_TRACE_FORMAT, client.toString(), nsqd.getAddress().toString(), msg.toMetadataStr()); 36 | } 37 | trace.debug(traceMsg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/MockedConsumer.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.core.ConnectionManager; 4 | import com.youzan.nsq.client.core.NSQConnection; 5 | import com.youzan.nsq.client.entity.Address; 6 | import com.youzan.nsq.client.entity.NSQConfig; 7 | import com.youzan.nsq.client.entity.NSQMessage; 8 | import com.youzan.nsq.client.exception.NSQException; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by lin on 17/7/11. 14 | */ 15 | public class MockedConsumer extends ConsumerImplV2 { 16 | /** 17 | * @param config NSQConfig 18 | * @param handler the client code sets it 19 | */ 20 | public MockedConsumer(NSQConfig config, MessageHandler handler) { 21 | super(config, handler); 22 | } 23 | 24 | public void connect() throws NSQException { 25 | super.connect(); 26 | } 27 | 28 | public void connect(Address addr) throws Exception { 29 | super.connect(addr); 30 | } 31 | 32 | public void setConnectionManager(final ConnectionManager conMgr) { 33 | this.conMgr = conMgr; 34 | } 35 | 36 | public Map getAddress2Conn() { 37 | return super.address_2_conn; 38 | } 39 | 40 | public void start() { 41 | super.started.set(Boolean.TRUE); 42 | } 43 | 44 | public void startParent() throws NSQException { 45 | super.start(); 46 | } 47 | 48 | public boolean needSkip4MsgKV(NSQMessage msg) { 49 | return super.needSkip4MsgKV(msg); 50 | } 51 | 52 | /** 53 | * unscribe topic from consumer 54 | * @param topic topic to remove 55 | */ 56 | public void unsubscribe(String topic) { 57 | if(topics2Partitions.containsKey(topic)) { 58 | topics2Partitions.remove(topic); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/Client.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core; 5 | 6 | import com.youzan.nsq.client.entity.Address; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | import com.youzan.nsq.client.network.frame.NSQFrame; 9 | import io.netty.util.AttributeKey; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.Closeable; 14 | import java.util.Random; 15 | import java.util.Set; 16 | 17 | /** 18 | * NSQ business processing. 19 | * 20 | * @author zhaoxi (linzuxiong) 21 | */ 22 | public interface Client extends Closeable { 23 | 24 | Logger logger = LoggerFactory.getLogger(Client.class); 25 | 26 | AttributeKey STATE = AttributeKey.valueOf("Client.State"); 27 | //ordered attribute not specified in nsq connection for producer. 28 | AttributeKey ORDERED = AttributeKey.valueOf("Ordered"); 29 | 30 | Random _r = new Random(10000); 31 | 32 | //interval of updating topic 2 partition selector 33 | int _INTERVAL_IN_SECOND = 20; 34 | 35 | int _INTERVAL_RDY_UPDATE_IN_SECOND = 5; 36 | 37 | void start() throws NSQException; 38 | 39 | /** 40 | * Receive the frame of NSQ. 41 | * 42 | * @param frame NSQFrame to be handled 43 | * @param conn NSQConnection 44 | * @throws NSQException Client code should be catch 45 | */ 46 | void incoming(final NSQFrame frame, final NSQConnection conn) throws NSQException; 47 | 48 | /** 49 | * No messages will be sent to the client. 50 | * 51 | * @param conn NSQConnection 52 | */ 53 | void backoff(final NSQConnection conn); 54 | 55 | boolean validateHeartbeat(final NSQConnection conn); 56 | 57 | Set clearDataNode(Address address); 58 | 59 | void close(final NSQConnection conn); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/netty/NSQDecoder.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.netty; 2 | 3 | import com.youzan.nsq.client.core.Client; 4 | import com.youzan.nsq.client.core.NSQConnection; 5 | import com.youzan.nsq.client.exception.NSQException; 6 | import com.youzan.nsq.client.network.frame.MessageFrame; 7 | import com.youzan.nsq.client.network.frame.NSQFrame; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.handler.codec.MessageToMessageDecoder; 11 | import io.netty.util.Attribute; 12 | 13 | import java.util.List; 14 | 15 | public class NSQDecoder extends MessageToMessageDecoder { 16 | 17 | @Override 18 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 19 | final int size = in.readInt(); 20 | final int frameType = in.readInt(); 21 | Attribute attrOrder = ctx.channel().attr(Client.ORDERED); 22 | Boolean isOrdered = null == attrOrder.get() ? false : attrOrder.get(); 23 | 24 | Attribute attrExt = ctx.channel().attr(NSQConnection.EXTEND_SUPPORT); 25 | Boolean ext = null == attrExt.get() ? false : attrExt.get(); 26 | 27 | final NSQFrame frame = NSQFrame.newInstance(frameType, isOrdered); 28 | if (frame == null) { 29 | // uhh, bad response from server.. what should we do? 30 | final String tip = String.format("Bad frame id from server (%d). It will be disconnected!", frameType); 31 | throw new NSQException(tip); 32 | } 33 | frame.setSize(size); 34 | final byte[] body = new byte[size - 4]; 35 | in.readBytes(body); 36 | if (frame instanceof MessageFrame) 37 | ((MessageFrame) frame).setData(body, ext); 38 | else 39 | frame.setData(body); 40 | out.add(frame); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/utils/CompressUtil.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 压缩解压工具 11 | * Created by suguoqing on 2017/6/26. 12 | */ 13 | public class CompressUtil { 14 | private static final Logger LOGGER = LoggerFactory.getLogger(CompressUtil.class); 15 | 16 | /** 17 | * 对象json 序列化, lz4 压缩 18 | * 返回数据的前4位存 原始数据的长度 19 | * 20 | * @param object 21 | * @return 22 | */ 23 | public static byte[] compress(Object object) { 24 | byte[] sourceBytes = JSON.toJSONBytes(object); 25 | 26 | byte[] compressed = Lz4Util.compress(sourceBytes); 27 | 28 | byte[] finalBytes = new byte[compressed.length + 4]; 29 | 30 | LOGGER.info("sourceLen ={}, compressLen={}", sourceBytes.length, finalBytes.length); 31 | 32 | BytesUtil.writeInt(finalBytes, 0, sourceBytes.length); 33 | System.arraycopy(compressed, 0, finalBytes, 4, compressed.length); 34 | 35 | return finalBytes; 36 | } 37 | 38 | public static T decompress(byte[] compressBytes, Class dataType) { 39 | int length = BytesUtil.readInt(compressBytes, 0); 40 | 41 | if (length <= 0) { 42 | return null; 43 | } 44 | 45 | byte[] compressData = Arrays.copyOfRange(compressBytes, 4, compressBytes.length); 46 | 47 | LOGGER.info("sourceLen ={}, compressLen={}", length, compressBytes.length); 48 | 49 | if(length > 5000000){ 50 | LOGGER.warn("Length >5m, sourceLen ={}, compressLen={}", length, compressBytes.length); 51 | return null; 52 | } 53 | 54 | byte[] decompressedData = Lz4Util.decompress(compressData, length); 55 | 56 | return JSON.parseObject(decompressedData, dataType); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/ProducerWorkerThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.youzan.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.ThreadFactory; 7 | 8 | /** 9 | * Producer worker thread factory 10 | * Created by lin on 17/11/20. 11 | */ 12 | public class ProducerWorkerThreadFactory implements ThreadFactory { 13 | private static final Logger logger = LoggerFactory.getLogger(ProducerWorkerThreadFactory.class); 14 | 15 | private final ThreadGroup group; 16 | private final String namePrefix; 17 | private int priority = Integer.MIN_VALUE; 18 | 19 | final static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { 20 | @Override 21 | public void uncaughtException(Thread t, Throwable e) { 22 | logger.error(t.getName(), e); 23 | } 24 | }; 25 | 26 | public ProducerWorkerThreadFactory(String poolName, int priority) { 27 | if (null == poolName || poolName.isEmpty()) { 28 | throw new IllegalArgumentException(); 29 | } 30 | group = Thread.currentThread().getThreadGroup(); 31 | namePrefix = poolName + "-Producer-Worker-Pool-Thread"; 32 | this.priority = priority; 33 | } 34 | 35 | @Override 36 | public Thread newThread(Runnable r) { 37 | 38 | final Thread t = new Thread(group, r, namePrefix, 0); 39 | if (t.isDaemon()) { 40 | t.setDaemon(false); 41 | } 42 | switch(priority){ 43 | case Thread.MAX_PRIORITY : 44 | case Thread.MIN_PRIORITY: 45 | case Thread.NORM_PRIORITY: 46 | t.setPriority(priority); 47 | break; 48 | default: { 49 | t.setPriority(Thread.NORM_PRIORITY); 50 | } 51 | 52 | } 53 | Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); 54 | return t; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITConsumerTrace.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.ConsumerImplV2; 4 | import com.youzan.nsq.client.MessageHandler; 5 | import com.youzan.nsq.client.entity.NSQMessage; 6 | import com.youzan.nsq.client.exception.NSQException; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.testng.Assert; 10 | import org.testng.annotations.Test; 11 | 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | /** 17 | * Created by lin on 16/10/19. 18 | */ 19 | @Test(groups = {"ITConsumerTrace"}, dependsOnGroups = {"ITProducerTrace"}, priority = 5) 20 | public class ITConsumerTrace extends AbstractITConsumer{ 21 | private final static Logger logger = LoggerFactory.getLogger(ITConsumerTrace.class); 22 | 23 | public void test() throws InterruptedException, NSQException { 24 | final CountDownLatch latch = new CountDownLatch(10); 25 | final AtomicInteger received = new AtomicInteger(0); 26 | 27 | String[] lookupds = config.getLookupAddresses(); 28 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 29 | return; 30 | 31 | consumer = new ConsumerImplV2(config, new MessageHandler() { 32 | @Override 33 | public void process(NSQMessage message) { 34 | String msgStr = message.getReadableContent(); 35 | logger.info("Message received: " + msgStr); 36 | received.incrementAndGet(); 37 | latch.countDown(); 38 | } 39 | }); 40 | consumer.setAutoFinish(true); 41 | consumer.subscribe("JavaTesting-Trace"); 42 | consumer.start(); 43 | Assert.assertTrue(latch.await(1, TimeUnit.MINUTES)); 44 | logger.info("Consumer received {} messages in SUB Trace mode.", received.get()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Mpub.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import com.youzan.nsq.client.entity.Topic; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.util.List; 12 | 13 | /** 14 | * @author zhaoxi (linzuxiong) 15 | * 16 | * 17 | */ 18 | public class Mpub extends Pub implements NSQCommand{ 19 | private static final Logger logger = LoggerFactory.getLogger(Mpub.class); 20 | private final List messages; 21 | 22 | public Mpub(Topic topic, List messages) { 23 | super(topic); 24 | this.messages = messages; 25 | } 26 | 27 | @Override 28 | public byte[] getBytes() { 29 | ByteBuffer buf = null; 30 | byte[] header = this.getHeader().getBytes(NSQCommand.DEFAULT_CHARSET); 31 | //get MPUB body, which is a list containing multi messages 32 | List bodyL = this.getBody(); 33 | int bodySize = 4 + 4; // 4 for total messages int, another 4 for body size. 34 | if (bodyL.size() > 0) { 35 | // write total body size and message size 36 | for (byte[] data : bodyL) { 37 | bodySize += 4; // message size 38 | bodySize += data.length; 39 | } 40 | } 41 | 42 | buf = ByteBuffer.allocate(header.length + bodySize); 43 | buf.put(header); 44 | buf.putInt(bodySize); 45 | buf.putInt(bodyL.size()); 46 | 47 | for (byte[] data : bodyL) { 48 | buf.putInt(data.length); 49 | buf.put(data); 50 | } 51 | 52 | return buf.array(); 53 | } 54 | 55 | @Override 56 | public String getHeader() { 57 | return String.format("MPUB %s%s\n", topic.getTopicText(), topic.hasPartition() ? SPACE_STR + topic.getPartitionId() : ""); 58 | } 59 | 60 | @Override 61 | public List getBody() { 62 | return messages; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Sub.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import com.youzan.nsq.client.entity.Topic; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.util.List; 12 | 13 | /** 14 | * @author zhaoxi (linzuxiong) 15 | * 16 | * 17 | */ 18 | public class Sub implements NSQCommand { 19 | private static final Logger logger = LoggerFactory.getLogger(Sub.class); 20 | 21 | protected byte[] data = null; 22 | protected Topic topic; 23 | protected String channel; 24 | 25 | public Sub(final Topic topic, final String channel) { 26 | this.topic = topic; 27 | this.channel = channel; 28 | } 29 | 30 | @Override 31 | public byte[] getBytes() { 32 | if(null == this.data){ 33 | final byte[] cmd = "SUB ".getBytes(DEFAULT_CHARSET); 34 | final byte[] topicBytes = topic.getTopicText().getBytes(DEFAULT_CHARSET); 35 | final byte[] channelBytes = channel.getBytes(DEFAULT_CHARSET); 36 | final byte[] partitionBytes = getPartitionIdByte(topic); 37 | //fixed buffer 38 | final ByteBuffer bb = ByteBuffer.allocate(cmd.length + topicBytes.length + 1 + channelBytes.length + 1 + partitionBytes.length); 39 | // SUB \n 40 | bb.put(cmd).put(topicBytes).put(SPACE).put(channelBytes) 41 | .put(partitionBytes) 42 | .put(LINE_SEPARATOR); 43 | this.data = bb.array(); 44 | } 45 | return data; 46 | } 47 | 48 | @Override 49 | public String getHeader() { 50 | return ""; 51 | } 52 | 53 | @Override 54 | public List getBody() { 55 | return EMPTY_BODY; 56 | } 57 | 58 | public byte[] getPartitionIdByte(Topic topic) { 59 | return topic.hasPartition() ? 60 | (SPACE_STR + String.valueOf(topic.getPartitionId())).getBytes(DEFAULT_CHARSET) : new byte[0]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.youzan.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.Thread.UncaughtExceptionHandler; 7 | import java.util.concurrent.ThreadFactory; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | public class NamedThreadFactory implements ThreadFactory { 11 | 12 | private static final Logger logger = LoggerFactory.getLogger(NamedThreadFactory.class); 13 | 14 | private final ThreadGroup group; 15 | 16 | private final AtomicInteger threadNumber = new AtomicInteger(1); 17 | 18 | private final String namePrefix; 19 | private int priority = Integer.MIN_VALUE; 20 | 21 | final static UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() { 22 | @Override 23 | public void uncaughtException(Thread t, Throwable e) { 24 | logger.error(t.getName(), e); 25 | } 26 | }; 27 | 28 | public NamedThreadFactory(String poolName, int priority) { 29 | if (null == poolName || poolName.isEmpty()) { 30 | throw new IllegalArgumentException(); 31 | } 32 | group = Thread.currentThread().getThreadGroup(); 33 | namePrefix = poolName + "-Pool-Thread-"; 34 | this.priority = priority; 35 | } 36 | 37 | @Override 38 | public Thread newThread(Runnable r) { 39 | final StringBuilder sb = new StringBuilder(namePrefix.length() + 10); 40 | sb.append(namePrefix).append(String.valueOf(threadNumber.getAndIncrement())); 41 | 42 | final Thread t = new Thread(group, r, sb.toString(), 0); 43 | if (t.isDaemon()) { 44 | t.setDaemon(false); 45 | } 46 | switch(priority){ 47 | case Thread.MAX_PRIORITY : 48 | case Thread.MIN_PRIORITY: 49 | case Thread.NORM_PRIORITY: 50 | t.setPriority(priority); 51 | break; 52 | default: { 53 | t.setPriority(Thread.NORM_PRIORITY); 54 | } 55 | 56 | } 57 | Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); 58 | return t; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/AbstractITConsumer.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.Consumer; 4 | import com.youzan.nsq.client.entity.NSQConfig; 5 | import com.youzan.util.IOUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.testng.annotations.AfterClass; 9 | import org.testng.annotations.BeforeClass; 10 | 11 | import java.io.InputStream; 12 | import java.util.Properties; 13 | 14 | /** 15 | * Created by lin on 16/8/19. 16 | */ 17 | public abstract class AbstractITConsumer { 18 | private static final Logger logger = LoggerFactory.getLogger(AbstractITConsumer.class); 19 | 20 | protected final int rdy = 2; 21 | protected final NSQConfig config = new NSQConfig(); 22 | protected Consumer consumer; 23 | protected String adminHttp; 24 | protected String lookups; 25 | 26 | @BeforeClass 27 | public void init() throws Exception { 28 | logger.info("At {} , initialize: {}", System.currentTimeMillis(), this.getClass().getName()); 29 | final Properties props = new Properties(); 30 | try (final InputStream is = getClass().getClassLoader().getResourceAsStream("app-test.properties")) { 31 | props.load(is); 32 | } 33 | 34 | lookups = props.getProperty("lookup-addresses"); 35 | final String connTimeout = props.getProperty("connectTimeoutInMillisecond"); 36 | final String msgTimeoutInMillisecond = props.getProperty("msgTimeoutInMillisecond"); 37 | final String threadPoolSize4IO = props.getProperty("threadPoolSize4IO"); 38 | adminHttp = props.getProperty("admin-address"); 39 | config.setUserSpecifiedLookupAddress(true); 40 | config.setLookupAddresses(lookups); 41 | config.setConnectTimeoutInMillisecond(Integer.valueOf(connTimeout)); 42 | config.setMsgTimeoutInMillisecond(Integer.valueOf(msgTimeoutInMillisecond)); 43 | config.setThreadPoolSize4IO(Integer.valueOf(threadPoolSize4IO)); 44 | config.setRdy(rdy); 45 | config.setConsumerName("BaseConsumer"); 46 | } 47 | 48 | @AfterClass 49 | public void close() { 50 | if(null != consumer) { 51 | consumer.close(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/LookupdAddress.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | /** 7 | * Created by lin on 16/12/13. 8 | */ 9 | public class LookupdAddress extends AbstractLookupdAddress { 10 | private static ConcurrentHashMap lookupMap = new ConcurrentHashMap<>(); 11 | 12 | private AtomicLong refCounter; 13 | 14 | public LookupdAddress(String clusterId, String address) { 15 | super(clusterId, address); 16 | this.refCounter = new AtomicLong(0); 17 | } 18 | 19 | protected long updateRefCounter(int delta) { 20 | if(delta > 0) { 21 | return this.refCounter.incrementAndGet(); 22 | } else if(delta < 0) { 23 | return this.refCounter.decrementAndGet(); 24 | } 25 | return this.refCounter.get(); 26 | } 27 | 28 | public long getReferenceCount() { 29 | return refCounter.get(); 30 | } 31 | 32 | public long addReference(){ 33 | synchronized (lookupMap) { 34 | if(lookupMap.containsValue(this)) 35 | return this.updateRefCounter(1); 36 | } 37 | return -1; 38 | } 39 | 40 | public long removeReference() { 41 | synchronized (lookupMap) { 42 | if(lookupMap.containsValue(this)) { 43 | long cnt = this.updateRefCounter(-1); 44 | if (cnt <= 0) { 45 | lookupMap.remove(this.getAddress()); 46 | return 0; 47 | } 48 | } 49 | } 50 | return -1; 51 | } 52 | 53 | public static LookupdAddress create(String clusterId, String address) { 54 | synchronized (lookupMap) { 55 | LookupdAddress aLookup = new LookupdAddress(clusterId, address); 56 | if(!aLookup.isValid()) 57 | return null; 58 | if (!lookupMap.containsKey(address)) { 59 | lookupMap.put(address, aLookup); 60 | }else { 61 | aLookup = lookupMap.get(address); 62 | } 63 | return aLookup; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/PubTrace.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import com.youzan.nsq.client.entity.Message; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.util.UUID; 9 | 10 | /** 11 | * Created by lin on 16/8/31. 12 | */ 13 | public class PubTrace extends Pub{ 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(PubTrace.class); 16 | private byte[] traceId = {0, 0, 0, 0, 0, 0, 0, 0}; 17 | protected UUID id = UUID.randomUUID(); 18 | 19 | public PubTrace(Message msg){ 20 | super(msg); 21 | if(msg.getTraceID() != 0L) { 22 | //update traceID 23 | ByteBuffer buf = ByteBuffer.allocate(TRACE_ID_SIZE); 24 | buf.putLong(msg.getTraceID()); 25 | byte[] newTraceID = buf.array(); 26 | System.arraycopy(newTraceID, 0, this.traceId, 0, TRACE_ID_SIZE); 27 | } 28 | } 29 | 30 | @Override 31 | public String getHeader() { 32 | return String.format("PUB_TRACE %s%s\n", topic.getTopicText(), this.getPartitionStr()); 33 | } 34 | 35 | @Override 36 | public byte[] getBytes(){ 37 | if(null == bytes){ 38 | byte[] header = this.getHeader().getBytes(NSQCommand.DEFAULT_CHARSET); 39 | //extra 4 byte for traceID and message size value 40 | int msgSize = header.length + MSG_SIZE; 41 | //set it as array[0], as we need length 0 for size calculation 42 | byte[] traceIDBytes = this.getTraceId(); 43 | msgSize += TRACE_ID_SIZE; 44 | byte[] body = this.getBody().get(0); 45 | msgSize += body.length; 46 | ByteBuffer buf = ByteBuffer.allocate(msgSize); 47 | 48 | buf.put(header) 49 | .putInt(( TRACE_ID_SIZE ) + body.length) 50 | .put(traceIDBytes) 51 | .put(body); 52 | bytes = buf.array(); 53 | } 54 | return bytes; 55 | } 56 | 57 | public byte[] getTraceId(){ 58 | return this.traceId; 59 | } 60 | 61 | public String toString(){ 62 | return this.getTraceId() + "; " + super.toString(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/configs/TopicRuleCategory.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import com.youzan.nsq.client.entity.Role; 4 | import com.youzan.nsq.client.entity.Topic; 5 | 6 | /** 7 | * topic category maintain mapping from topic to categorization. 8 | * Created by lin on 16/12/7. 9 | */ 10 | public class TopicRuleCategory implements ITopicRuleCategory { 11 | public static final String TOPIC_CATEGORIZATION_USER_SPECIFIED = "categorization.sdk.nsq.default"; 12 | public static final String TOPIC_CATEGORIZATION_SUFFIX = "%s.nsq.lookupd.addr:%s"; 13 | private static final String TOPIC_BINLOG_PATTERN = "binlog_"; 14 | 15 | private final Role role; 16 | 17 | private final static TopicRuleCategory categoryProducer = new TopicRuleCategory(Role.Producer); 18 | private final static TopicRuleCategory categoryConsumer = new TopicRuleCategory(Role.Consumer); 19 | 20 | public static TopicRuleCategory getInstance(Role role) { 21 | switch (role) { 22 | case Consumer: { 23 | return categoryConsumer; 24 | } 25 | default: { 26 | return categoryProducer; 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * @return {@link Role} of current topic rule category 33 | */ 34 | public Role getRole() { 35 | return this.role; 36 | } 37 | 38 | public TopicRuleCategory(final Role role) { 39 | this.role = role; 40 | } 41 | 42 | @Override 43 | public String category(Topic topic) { 44 | return category(topic.getTopicText()); 45 | } 46 | 47 | @Override 48 | public String category(String topic) { 49 | return String.format(TOPIC_CATEGORIZATION_SUFFIX, trimTopic(topic), this.role.getRoleTxt()); 50 | } 51 | 52 | public static String trimTopic(String topicText) { 53 | if(null == topicText || topicText.isEmpty()) 54 | throw new IllegalArgumentException("pass in topic text should not be empty"); 55 | String[] parts = topicText.split("_", 3); 56 | 57 | if(topicText.startsWith(TOPIC_BINLOG_PATTERN) && parts.length >=3){ 58 | return TOPIC_BINLOG_PATTERN + parts[1]; 59 | }else 60 | return parts[0]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/DesiredTag.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.Serializable; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * set desired tag for consumer would lile to receive, a valid tag is: 12 | * smaller than 100 bytes in length and it is th combination of alphabet[a-zA-Z] and number[0-9] 13 | */ 14 | public class DesiredTag implements Serializable { 15 | //tag limitation in length 16 | private static final int TAG_FILTER_LIMIT = 100; 17 | private static final Pattern VALID_TAG_REFEX = Pattern.compile("^[a-zA-Z0-9_-]+$"); 18 | private static final Logger logger = LoggerFactory.getLogger(DesiredTag.class); 19 | 20 | //default tag value is empty, it receives any messages in topic/channel 21 | private String tag = ""; 22 | 23 | /** 24 | * 25 | * @param tag string to initialize as tag filter 26 | * @throws IllegalArgumentException thrown when passin filter is not valid. 27 | */ 28 | public DesiredTag(String tag) throws IllegalArgumentException { 29 | if (null == tag || tag.isEmpty()) 30 | return; 31 | if(validateTag(tag)) { 32 | this.tag = tag; 33 | } else { 34 | throw new IllegalArgumentException("Desired tag: " + tag + " is invalid."); 35 | } 36 | } 37 | 38 | private boolean validateTag(String tagFilter) { 39 | if(tagFilter.length() > TAG_FILTER_LIMIT) { 40 | logger.error("Length of tag filter should not exceed " + TAG_FILTER_LIMIT + " bytes"); 41 | return false; 42 | } 43 | return VALID_TAG_REFEX.matcher(tagFilter).find(); 44 | } 45 | 46 | public boolean match(final DesiredTag tag) { 47 | if(StringUtils.isEmpty(tag.tag)) 48 | return false; 49 | return this.tag.equals(tag.tag); 50 | } 51 | 52 | public String toString() { 53 | return this.tag; 54 | } 55 | 56 | /** 57 | * get tag name 58 | * @return tag name 59 | */ 60 | public String getTagName() { 61 | return this.tag; 62 | } 63 | 64 | public ExtVer version() { 65 | return ExtVer.Ver0x2; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/MessagesWrapper.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * wrapper for multi message to be publish by {@link com.youzan.nsq.client.core.command.Mpub} 7 | * Created by lin on 17/11/1. 8 | */ 9 | public class MessagesWrapper extends Message { 10 | final private List messageBodiesInBytes; 11 | 12 | public MessagesWrapper(Topic topic, List messageBodies) { 13 | super(topic, null); 14 | this.messageBodiesInBytes = messageBodies; 15 | } 16 | 17 | @Override 18 | public List getMessageBodiesInByte() { 19 | return this.messageBodiesInBytes; 20 | } 21 | 22 | @Override 23 | public int getMessageCount() { 24 | return this.messageBodiesInBytes.size(); 25 | } 26 | 27 | @Override 28 | public Message traced(){ 29 | return this; 30 | } 31 | 32 | @Override 33 | public boolean isTraced(){ 34 | return false; 35 | } 36 | 37 | public Message setTopicShardingIDObject(Object shardingIDObj){ 38 | throw new IllegalArgumentException("setTopicShardingIDObject not support."); 39 | } 40 | 41 | @Override 42 | public Message setDesiredTag(final DesiredTag desiredTag) { 43 | throw new IllegalArgumentException("setDesiredTag not support."); 44 | } 45 | 46 | @Override 47 | public String getDesiredTag() { 48 | return null; 49 | } 50 | 51 | @Override 52 | public byte[] getDesiredTagInByte() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public void setJsonHeaderExt(final Object jsonExt) { 58 | throw new IllegalArgumentException("setJsonHeaderExt not support"); 59 | } 60 | 61 | @Override 62 | public Object getJsonHeaderExt() { 63 | return null; 64 | } 65 | 66 | @Override 67 | public Message setTopicShardingIDLong(long shardingIDLong){ 68 | return setTopicShardingIDObject(shardingIDLong); 69 | } 70 | 71 | @Override 72 | public Message setTopicShardingIDString(String shardingIDString){ 73 | return setTopicShardingIDObject(shardingIDString); 74 | } 75 | 76 | @Override 77 | public Object getTopicShardingId(){ 78 | return NO_SHARDING; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/MigrationPartitionsSelector.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.List; 8 | import java.util.Random; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | /** 12 | * Created by lin on 16/12/19. 13 | */ 14 | public class MigrationPartitionsSelector implements IPartitionsSelector { 15 | private static final Logger logger = LoggerFactory.getLogger(MigrationPartitionsSelector.class); 16 | final private static Random _ran = new Random(); 17 | final private List prePars; 18 | final private AtomicLong preCnt = new AtomicLong(1); 19 | 20 | final private List curPars; 21 | final private AtomicLong curCnt = new AtomicLong(1); 22 | final private double preFactor; 23 | 24 | public MigrationPartitionsSelector(final List prePars, final List curPars, double preFactor) { 25 | this.prePars = prePars; 26 | this.curPars = curPars; 27 | this.preFactor = preFactor; 28 | } 29 | 30 | @Override 31 | public Partitions[] choosePartitions() { 32 | double seed = _ran.nextFloat() * 100; 33 | if (seed < this.preFactor) { 34 | if(logger.isDebugEnabled()) 35 | logger.debug("Previous partitions chosen."); 36 | this.preCnt.incrementAndGet(); 37 | return this.prePars.toArray(new Partitions[0]); 38 | } else { 39 | if(logger.isDebugEnabled()) 40 | logger.debug("Current partitions chosen."); 41 | this.curCnt.incrementAndGet(); 42 | return this.curPars.toArray(new Partitions[0]); 43 | } 44 | } 45 | 46 | @Override 47 | public Partitions[] dumpAllPartitions() { 48 | Partitions[] returnPars = new Partitions[this.curPars.size() + this.prePars.size()]; 49 | Partitions[] curParsArr = this.curPars.toArray(new Partitions[0]); 50 | Partitions[] preParsArr = this.prePars.toArray(new Partitions[0]); 51 | System.arraycopy(curParsArr, 0, returnPars, 0, this.curPars.size()); 52 | System.arraycopy(preParsArr, 0, returnPars, this.curPars.size(), this.prePars.size()); 53 | return returnPars; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/AbstractLookupdConfig.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.locks.ReentrantReadWriteLock; 5 | 6 | /** 7 | * Created by lin on 16/12/9. 8 | */ 9 | public abstract class AbstractLookupdConfig { 10 | 11 | private ReentrantReadWriteLock topicLock = new ReentrantReadWriteLock(); 12 | private ConcurrentHashMap topicnfMap = new ConcurrentHashMap(); 13 | 14 | public AbstractLookupdConfig(){ 15 | } 16 | 17 | /** 18 | * Update topic associated migration control config. 19 | * @param categorizationTopic categorization tailed with topic name 20 | * @param cc new control config 21 | */ 22 | public void putTopicCtrlCnf(String categorizationTopic, final AbstractControlConfig cc) { 23 | if(null == categorizationTopic || null == cc) 24 | return; 25 | try{ 26 | topicLock.writeLock().lock(); 27 | AbstractControlConfig oldCtrlConf = topicnfMap.put(categorizationTopic, cc); 28 | //if old control conf is not null, need to remove reference from SeedLookupAddress map 29 | if(null != oldCtrlConf) { 30 | oldCtrlConf.clean(); 31 | } 32 | }finally { 33 | topicLock.writeLock().unlock(); 34 | } 35 | } 36 | 37 | /** 38 | * get topic associated migration control config 39 | * @param categorizationTopic categorization tailed with topic name 40 | * @return control config 41 | */ 42 | public AbstractControlConfig getTopicCtrlCnf(String categorizationTopic) { 43 | if(null == categorizationTopic) 44 | return null; 45 | try{ 46 | topicLock.readLock().lock(); 47 | if(topicnfMap.containsKey(categorizationTopic)) 48 | return topicnfMap.get(categorizationTopic); 49 | }finally { 50 | topicLock.readLock().unlock(); 51 | } 52 | return null; 53 | } 54 | 55 | public void clean() { 56 | try{ 57 | topicLock.writeLock().lock(); 58 | for(AbstractControlConfig ctrlCnf : topicnfMap.values()) { 59 | ctrlCnf.clean(); 60 | } 61 | }finally { 62 | topicLock.writeLock().unlock(); 63 | } 64 | } 65 | 66 | public boolean containsControlConfig() { 67 | return !this.topicnfMap.isEmpty(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/HostUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.IOException; 10 | import java.net.*; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.Enumeration; 14 | import java.util.List; 15 | 16 | /** 17 | * @author zhaoxi (linzuxiong) 18 | */ 19 | public final class HostUtil { 20 | private static final Logger logger = LoggerFactory.getLogger(HostUtil.class); 21 | 22 | public static String getLocalIP() throws IOException { 23 | try { 24 | final List ips = new ArrayList<>(5); 25 | final Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); 26 | while (interfaces.hasMoreElements()) { 27 | NetworkInterface networkInterface = interfaces.nextElement(); 28 | Enumeration addresses = networkInterface.getInetAddresses(); 29 | while (addresses.hasMoreElements()) { 30 | InetAddress address = addresses.nextElement(); 31 | if (address instanceof Inet4Address && !address.isLoopbackAddress() && !address.isLinkLocalAddress() 32 | && !address.getHostAddress().contains(":")) { 33 | ips.add(address.getHostAddress()); 34 | } 35 | } 36 | } 37 | if (!ips.isEmpty()) { 38 | // JDK8 39 | // (s1, s2) -> s1.compareTo(s2) 40 | Collections.sort(ips); 41 | return ips.get(0); 42 | } 43 | 44 | logger.debug("Have got from localhost."); 45 | String local = InetAddress.getLocalHost().getHostAddress(); 46 | 47 | if (local == null || "127.0.0.1".equals(local) || local.isEmpty()) { 48 | logger.error("Can't get the real IP!"); 49 | throw new RuntimeException("We got one unexpected Local IPv4. It is " + local); 50 | } 51 | return local; 52 | } catch (SocketException | UnknownHostException e) { 53 | throw new IOException(e); 54 | } 55 | } 56 | 57 | public static String getHostname() { 58 | String hostname = null; 59 | try { 60 | hostname = InetAddress.getLocalHost().getHostName(); 61 | } catch (UnknownHostException e) { 62 | logger.error("Could not get host name for local host.", e); 63 | } 64 | return hostname; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/frame/NSQFrame.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.frame; 2 | 3 | import com.youzan.nsq.client.entity.Response; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.nio.charset.Charset; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | public abstract class NSQFrame { 11 | 12 | private static final Logger logger = LoggerFactory.getLogger(NSQFrame.class); 13 | 14 | public static final Charset ASCII = StandardCharsets.US_ASCII; 15 | public static final Charset UTF8 = StandardCharsets.UTF_8; 16 | public static final Charset DEFAULT_CHARSET = UTF8; 17 | 18 | public enum FrameType { 19 | RESPONSE_FRAME(0),// 20 | ERROR_FRAME(1),// 21 | MESSAGE_FRAME(2), // 22 | ; 23 | private int type; 24 | 25 | FrameType(int type) { 26 | this.type = type; 27 | } 28 | } 29 | 30 | private int size; 31 | private byte[] data; 32 | 33 | /** 34 | * @return FrameType 35 | */ 36 | public abstract FrameType getType(); 37 | 38 | /** 39 | * @return some readable content 40 | */ 41 | public abstract String getMessage(); 42 | 43 | public int getSize() { 44 | return size; 45 | } 46 | 47 | public void setSize(int size) { 48 | this.size = size; 49 | } 50 | 51 | public byte[] getData() { 52 | return data; 53 | } 54 | 55 | public void setData(byte[] data) { 56 | this.data = data; 57 | } 58 | 59 | public static NSQFrame newInstance(final int type) { 60 | switch (type) { 61 | case 0: 62 | return new ResponseFrame(); 63 | case 1: 64 | return new ErrorFrame(); 65 | case 2: 66 | return new MessageFrame(); 67 | default: { 68 | logger.error("Un recognized NSQ Frame! Please check NSQ protocol!"); 69 | return null; 70 | } 71 | } 72 | } 73 | 74 | public static NSQFrame newInstance(final int type, boolean isOrdered) { 75 | switch (type) { 76 | case 0: 77 | return new ResponseFrame(); 78 | case 1: 79 | return new ErrorFrame(); 80 | case 2:{ 81 | if(isOrdered) 82 | return new OrderedMessageFrame(); 83 | else return new MessageFrame(); 84 | } 85 | default: { 86 | logger.error("Un recognized NSQ Frame! Please check NSQ protocol!"); 87 | return null; 88 | } 89 | } 90 | } 91 | 92 | public boolean isHeartBeat() { 93 | return Response._HEARTBEAT_.getContent().equals(this.getMessage()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/resources/testng-base-tag-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/configs/TopicRuleCategoryTestcase.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.configs; 2 | 3 | import com.youzan.nsq.client.entity.Role; 4 | import com.youzan.nsq.client.entity.Topic; 5 | import org.testng.Assert; 6 | import org.testng.annotations.Test; 7 | 8 | /** 9 | * Created by lin on 16/12/27. 10 | */ 11 | public class TopicRuleCategoryTestcase { 12 | 13 | @Test 14 | public void testCategory() { 15 | String expectedBinlogCategorization = "binlog_admin.nsq.lookupd.addr:consumer"; 16 | Topic aTopic = new Topic("binlog_admin_change"); 17 | TopicRuleCategory category = TopicRuleCategory.getInstance(Role.Consumer); 18 | String result = category.category(aTopic); 19 | Assert.assertEquals(result, expectedBinlogCategorization); 20 | 21 | String expectedNormalCategorization = "normalTopic.nsq.lookupd.addr:producer"; 22 | aTopic = new Topic("normalTopic_shouldbe_skipped"); 23 | category = TopicRuleCategory.getInstance(Role.Producer); 24 | result = category.category(aTopic); 25 | Assert.assertEquals(result, expectedNormalCategorization); 26 | 27 | String expectedSpecialCategorization = "JavaTesting-Producer-Base.nsq.lookupd.addr:producer"; 28 | aTopic = new Topic("JavaTesting-Producer-Base"); 29 | category = TopicRuleCategory.getInstance(Role.Producer); 30 | result = category.category(aTopic); 31 | Assert.assertEquals(result, expectedSpecialCategorization); 32 | 33 | String expectedAbnormalCategorization = "binlog.nsq.lookupd.addr:producer"; 34 | aTopic = new Topic("binlog_"); 35 | category = TopicRuleCategory.getInstance(Role.Producer); 36 | result = category.category(aTopic); 37 | Assert.assertEquals(result, expectedAbnormalCategorization); 38 | 39 | String expectedShortBinlogCategorization = "binlog.nsq.lookupd.addr:consumer"; 40 | aTopic = new Topic("binlog_shouldSkip"); 41 | category = TopicRuleCategory.getInstance(Role.Consumer); 42 | result = category.category(aTopic); 43 | Assert.assertEquals(result, expectedShortBinlogCategorization); 44 | 45 | String expectedAbnormalBinlogCategorization = "binlog_.nsq.lookupd.addr:consumer"; 46 | aTopic = new Topic("binlog__"); 47 | category = TopicRuleCategory.getInstance(Role.Consumer); 48 | result = category.category(aTopic); 49 | Assert.assertEquals(result, expectedAbnormalBinlogCategorization); 50 | 51 | try { 52 | aTopic = new Topic(""); 53 | category = TopicRuleCategory.getInstance(Role.Producer); 54 | category.category(aTopic); 55 | Assert.fail("should not pass previous statement"); 56 | }catch(Exception e) { 57 | Assert.assertNotNull(e); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITProducerWPartition.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.Producer; 4 | import com.youzan.nsq.client.ProducerImplV2; 5 | import com.youzan.nsq.client.entity.Message; 6 | import com.youzan.nsq.client.entity.Topic; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | import com.youzan.nsq.client.utils.TopicUtil; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.testng.annotations.Test; 12 | 13 | /** 14 | * Created by lin on 16/8/19. 15 | */ 16 | @Test(groups = {"ITProducerWPartition-Base"}, priority = 3) 17 | public class ITProducerWPartition extends ITProducer{ 18 | private static final Logger logger = LoggerFactory.getLogger(ITProducerWPartition.class); 19 | 20 | 21 | public void publish() throws Exception { 22 | final byte[] message = new byte[64]; 23 | Topic topic = new Topic("JavaTesting-Producer-Base"); 24 | 25 | String[] lookupds = config.getLookupAddresses(); 26 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 27 | return; 28 | logger.info("[ITProducerWPartition#publish] starts"); 29 | Producer producer = new ProducerImplV2(this.config); 30 | try { 31 | TopicUtil.emptyQueue(this.adminHttp, "JavaTesting-Producer-Base", "BaseConsumer"); 32 | producer.start(); 33 | for (int i = 0; i < 10; i++) { 34 | random.nextBytes(message); 35 | Message msg = Message.create(topic, new String(message)) 36 | .setTopicShardingIDLong(321L); 37 | producer.publish(msg); 38 | } 39 | }finally { 40 | producer.close(); 41 | logger.info("[ITProducerWPartition#publish] ends"); 42 | } 43 | } 44 | 45 | 46 | private void publishWTopicAndPartition(final Producer producer, String topic) throws NSQException { 47 | for (int i = 0; i < 2; i++) { 48 | producer.publish(("Message #" + i).getBytes(), new Topic(topic)); 49 | } 50 | } 51 | 52 | public void testPublishPartition0() throws Exception { 53 | String[] lookupds = config.getLookupAddresses(); 54 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 55 | return; 56 | 57 | logger.info("[ITProducerWPartition#testPublishPartition0] starts"); 58 | Producer producer = new ProducerImplV2(this.config); 59 | try { 60 | TopicUtil.emptyQueue(this.adminHttp, "JavaTesting-Partition", "BaseConsumer"); 61 | producer.start(); 62 | publishWTopicAndPartition(producer, "JavaTesting-Partition"); 63 | }finally { 64 | producer.close(); 65 | logger.info("[ITProducerWPartition#testPublishPartition0] ends"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITConsumerOrdered.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.ConsumerImplV2; 4 | import com.youzan.nsq.client.MessageHandler; 5 | import com.youzan.nsq.client.entity.NSQMessage; 6 | import com.youzan.nsq.client.entity.Topic; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.testng.Assert; 11 | import org.testng.annotations.Test; 12 | 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicBoolean; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | 18 | /** 19 | * Created by lin on 16/10/19. 20 | */ 21 | @Test(groups = {"ITConsumerOrdered"}, dependsOnGroups = {"ITProducerOrdered"}, priority = 5) 22 | public class ITConsumerOrdered extends AbstractITConsumer { 23 | private final static Logger logger = LoggerFactory.getLogger(ITConsumerOrdered.class); 24 | 25 | public void test() throws InterruptedException, NSQException { 26 | final CountDownLatch latch = new CountDownLatch(100); 27 | final AtomicInteger received = new AtomicInteger(0); 28 | final AtomicInteger current = new AtomicInteger(-1); 29 | final AtomicBoolean fail = new AtomicBoolean(false); 30 | 31 | String[] lookupds = config.getLookupAddresses(); 32 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 33 | return; 34 | 35 | //turn on sub ordered 36 | config.setOrdered(true); 37 | consumer = new ConsumerImplV2(config, new MessageHandler() { 38 | @Override 39 | public void process(NSQMessage message) { 40 | String msgStr = message.getReadableContent(); 41 | if (!msgStr.contains("#")) { 42 | logger.warn("receive one message has not valid separator: {}. discard.", msgStr); 43 | return; 44 | } 45 | int num = Integer.valueOf(msgStr.split("#")[1]); 46 | logger.info("Message received: " + msgStr); 47 | received.incrementAndGet(); 48 | 49 | if(current.get() >= num) { 50 | Assert.fail("Message is not received in ordered. Current#" + current.get() + " but got#" + num); 51 | fail.set(true); 52 | } 53 | else 54 | current.set(num); 55 | 56 | latch.countDown(); 57 | } 58 | }); 59 | consumer.setAutoFinish(true); 60 | Topic aTopic = new Topic("JavaTesting-Order"); 61 | aTopic.setPartitionID(1); 62 | consumer.subscribe(aTopic); 63 | consumer.start(); 64 | Assert.assertTrue(latch.await(3, TimeUnit.MINUTES)); 65 | Assert.assertFalse(fail.get()); 66 | logger.info("Consumer received {} messages in SUB Ordered mode.", received.get()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/PerfTune.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Properties; 9 | 10 | /** 11 | * Created by lin on 17/5/4. 12 | */ 13 | public class PerfTune { 14 | private static final Logger log = LoggerFactory.getLogger(PerfTune.class); 15 | private static Object INSTANCE_LOCK = new Object(); 16 | private static PerfTune _INSTANCE; 17 | 18 | private Properties props = new Properties(); 19 | 20 | private PerfTune() { 21 | String perfTunePath = System.getProperty("nsq.client.perfTune.path"); 22 | if(null == perfTunePath) { 23 | log.info("Load performance tuning properties from inner properties"); 24 | perfTunePath = "perf.properties"; 25 | } else { 26 | log.info("Load performance tuning properties from system property %s", perfTunePath); 27 | } 28 | InputStream is = getClass().getClassLoader().getResourceAsStream(perfTunePath); 29 | 30 | try { 31 | props.load(is); 32 | log.info("Performance tuning properties loaded."); 33 | } catch (IOException e) { 34 | log.error("Fail to initialize performance tune properties.", e); 35 | } 36 | } 37 | 38 | private long getnsqConnLimit = -1L; 39 | public long getNSQConnElapseLimit() { 40 | if(getnsqConnLimit < 0) 41 | getnsqConnLimit = Long.parseLong(props.getProperty("nsq.client.conn.get")); 42 | return getnsqConnLimit; 43 | } 44 | 45 | private long sendmsgLimit = -1L; 46 | public long getSendMSGLimit() { 47 | if(sendmsgLimit < 0) 48 | sendmsgLimit = Long.parseLong(props.getProperty("nsq.client.msg.send")); 49 | return sendmsgLimit; 50 | } 51 | 52 | private long returnnsqConnLimit = -1L; 53 | public long getNSQConnReturnLimit() { 54 | if(returnnsqConnLimit < 0) 55 | returnnsqConnLimit = Long.parseLong(props.getProperty("nsq.client.conn.return")); 56 | return returnnsqConnLimit; 57 | } 58 | 59 | private long borrownsqConnLimit = -1L; 60 | public long getNSQConnBorrowLimit() { 61 | if(borrownsqConnLimit < 0) 62 | borrownsqConnLimit = Long.parseLong(props.getProperty("nsq.client.pool.conn.borrow")); 63 | return borrownsqConnLimit; 64 | } 65 | 66 | private void preload() { 67 | this.getNSQConnBorrowLimit(); 68 | this.getNSQConnReturnLimit(); 69 | this.getNSQConnElapseLimit(); 70 | this.getSendMSGLimit(); 71 | } 72 | 73 | public static PerfTune getInstance() { 74 | if(null == _INSTANCE) { 75 | synchronized (INSTANCE_LOCK) { 76 | if(null == _INSTANCE) { 77 | _INSTANCE = new PerfTune(); 78 | _INSTANCE.preload(); 79 | } 80 | } 81 | } 82 | return _INSTANCE; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITProducerOrdered.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.Producer; 4 | import com.youzan.nsq.client.ProducerImplV2; 5 | import com.youzan.nsq.client.entity.Message; 6 | import com.youzan.nsq.client.entity.NSQConfig; 7 | import com.youzan.nsq.client.entity.Topic; 8 | import com.youzan.nsq.client.exception.NSQException; 9 | import com.youzan.nsq.client.utils.TopicUtil; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.testng.annotations.BeforeClass; 13 | import org.testng.annotations.Test; 14 | 15 | import java.io.InputStream; 16 | import java.util.Properties; 17 | 18 | /** 19 | * Created by lin on 16/10/19. 20 | */ 21 | @Test(groups = {"ITProducerOrdered"}, priority = 5) 22 | public class ITProducerOrdered { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(ITProducerOrdered.class); 25 | private NSQConfig config = new NSQConfig(); 26 | private Producer producer; 27 | private String adminHttp; 28 | 29 | @BeforeClass 30 | public void init() throws Exception { 31 | logger.info("At {} , initialize: {}", System.currentTimeMillis(), this.getClass().getName()); 32 | final Properties props = new Properties(); 33 | try (final InputStream is = getClass().getClassLoader().getResourceAsStream("app-test.properties")) { 34 | props.load(is); 35 | } 36 | final String lookups = props.getProperty("lookup-addresses"); 37 | final String connTimeout = props.getProperty("connectTimeoutInMillisecond"); 38 | final String msgTimeoutInMillisecond = props.getProperty("msgTimeoutInMillisecond"); 39 | final String threadPoolSize4IO = props.getProperty("threadPoolSize4IO"); 40 | adminHttp = "http://" + props.getProperty("admin-address"); 41 | 42 | config.setLookupAddresses(lookups); 43 | config.setUserSpecifiedLookupAddress(true); 44 | config.setConnectTimeoutInMillisecond(Integer.valueOf(connTimeout)); 45 | config.setMsgTimeoutInMillisecond(Integer.valueOf(msgTimeoutInMillisecond)); 46 | config.setThreadPoolSize4IO(Integer.valueOf(threadPoolSize4IO)); 47 | //turn on pub ordered 48 | config.setOrdered(true); 49 | producer = new ProducerImplV2(config); 50 | producer.start(); 51 | } 52 | 53 | public void publishOrdered() throws Exception { 54 | TopicUtil.emptyQueue(adminHttp, "JavaTesting-Order", "BaseConsumer"); 55 | String[] lookupds = config.getLookupAddresses(); 56 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 57 | return; 58 | 59 | Topic topic = new Topic("JavaTesting-Order"); 60 | for (int i = 0; i < 100; i++) { 61 | String message = ("Message #" + i); 62 | Message msg = Message.create(topic, 1024L, message) 63 | .setTopicShardingIDLong(123L); 64 | producer.publish(msg); 65 | } 66 | producer.close(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/MessageReceipt.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.entity.Message; 4 | 5 | /** 6 | * Message receipt for publish response, for normal publish(PUB), 7 | * it contains: topic name, partition number, nsqd address(host:port); 8 | * if current receipt is for trace publish(PUB_TARCE), when {@link Message#traced()}, 9 | * following info contains, besides topic and nsqd info mentioned above: 10 | * 11 | * [internalID] 12 | * [traceID] 13 | * [diskQueueOffset] 14 | * [diskQueueDataSize] 15 | */ 16 | public class MessageReceipt implements MessageMetadata { 17 | private long internalID; 18 | private long traceID; 19 | private long diskQueueOffset=-1l; 20 | private int diskQueueSize=-1; 21 | private String topicName; 22 | private int partition; 23 | private String nsqdAddr; 24 | 25 | public String getTopicName() { 26 | return topicName; 27 | } 28 | 29 | public void setTopicName(String topicName) { 30 | this.topicName = topicName; 31 | } 32 | 33 | public int getPartition() { 34 | return partition; 35 | } 36 | 37 | public void setPartition(int partition) { 38 | this.partition = partition; 39 | } 40 | 41 | public String getNsqdAddr() { 42 | return nsqdAddr; 43 | } 44 | 45 | public void setNsqdAddr(String nsqdAddr) { 46 | this.nsqdAddr = nsqdAddr; 47 | } 48 | 49 | public MessageReceipt() { 50 | 51 | } 52 | 53 | public void setInternalID(long internalID) { 54 | this.internalID = internalID; 55 | } 56 | 57 | public long getInternalID() { 58 | return internalID; 59 | } 60 | 61 | public long getTraceID() { 62 | return traceID; 63 | } 64 | 65 | public void setTraceID(long traceID) { 66 | this.traceID = traceID; 67 | } 68 | 69 | public long getDiskQueueOffset() { 70 | return diskQueueOffset; 71 | } 72 | 73 | public void setDiskQueueOffset(long diskQueueOffset) { 74 | this.diskQueueOffset = diskQueueOffset; 75 | } 76 | 77 | public int getDiskQueueSize() { 78 | return diskQueueSize; 79 | } 80 | 81 | public void setDiskQueueSize(int diskQueueSize) { 82 | this.diskQueueSize = diskQueueSize; 83 | } 84 | 85 | @Override 86 | public String toMetadataStr() { 87 | StringBuilder sb = new StringBuilder(); 88 | sb.append(this.getClass().toString() + " meta-data:").append("\n"); 89 | sb.append("\t[topic]:\t").append(topicName).append(", ").append(partition).append("\n"); 90 | sb.append("\t[nsqdAddr]:\t").append(nsqdAddr).append("\n"); 91 | sb.append("\t[internalID]:\t").append(internalID).append("\n"); 92 | sb.append("\t[traceID]:\t").append(traceID).append("\n"); 93 | sb.append("\t[diskQueueOffset]:\t").append(diskQueueOffset).append("\n"); 94 | sb.append("\t[diskQueueDataSize]:\t").append(diskQueueSize).append("\n"); 95 | sb.append(this.getClass().toString() + " end."); 96 | return sb.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/lookup/NSQLookupdAddressesPair.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import com.youzan.nsq.client.entity.IPartitionsSelector; 4 | import com.youzan.nsq.client.entity.MigrationPartitionsSelector; 5 | import com.youzan.nsq.client.entity.Partitions; 6 | import com.youzan.nsq.client.exception.NSQLookupException; 7 | import com.youzan.nsq.client.exception.NSQProducerNotFoundException; 8 | import com.youzan.nsq.client.exception.NSQTopicNotFoundException; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by lin on 16/12/13. 16 | */ 17 | public class NSQLookupdAddressesPair extends NSQLookupdAddresses { 18 | NSQLookupdAddresses preLookupdAddress; 19 | NSQLookupdAddresses curLookupdAddress; 20 | 21 | public NSQLookupdAddressesPair(List preClusterIds, List preLookupdAddresses, List curClusterIds, List currentLookupAddresses, int currentFactor) { 22 | //not used here, just for initialization of constructor 23 | super(curClusterIds, currentLookupAddresses); 24 | this.preLookupdAddress = new NSQLookupdAddresses(preClusterIds, preLookupdAddresses); 25 | this.curLookupdAddress = new NSQLookupdAddresses(curClusterIds, currentLookupAddresses); 26 | this.prefactor = 100 - currentFactor; 27 | } 28 | 29 | @Override 30 | public IPartitionsSelector lookup(final String topic, boolean writable) throws NSQLookupException, NSQProducerNotFoundException, NSQTopicNotFoundException { 31 | if (null == topic || topic.isEmpty()) { 32 | throw new NSQLookupException("Your input topic is blank!"); 33 | } 34 | 35 | //previous cluster first 36 | try{ 37 | List lookupPres = this.preLookupdAddress.getAddresses(); 38 | List partitionsPres = new ArrayList<>(); 39 | for(String aLookupPre : lookupPres) { 40 | Partitions aPartitionsPre = lookup(aLookupPre, topic, writable); 41 | if (null != aPartitionsPre) 42 | partitionsPres.add(aPartitionsPre); 43 | } 44 | 45 | List lookupCur = this.curLookupdAddress.getAddresses(); 46 | List partitionsCurs = new ArrayList<>(); 47 | for(String aLookupCur : lookupCur) { 48 | Partitions aPartitionsCur = lookup(aLookupCur, topic, writable); 49 | if(null != aPartitionsCur) 50 | partitionsCurs.add(aPartitionsCur); 51 | } 52 | 53 | IPartitionsSelector mps = null; 54 | if(partitionsCurs.size() > 0 && partitionsPres.size() > 0) 55 | mps = new MigrationPartitionsSelector(partitionsPres, partitionsCurs, this.prefactor); 56 | 57 | return mps; // maybe it is empty 58 | } catch (IOException e) { 59 | final String tip = "SDK can't get the right lookup info from seed lookupd address. "; 60 | throw new NSQLookupException(tip, e); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/lookup/LookupServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.lookup; 2 | 3 | import com.youzan.nsq.client.configs.TopicRuleCategory; 4 | import com.youzan.nsq.client.core.LookupAddressUpdate; 5 | import com.youzan.nsq.client.entity.IPartitionsSelector; 6 | import com.youzan.nsq.client.entity.Role; 7 | import com.youzan.nsq.client.entity.lookup.NSQLookupdAddresses; 8 | import com.youzan.nsq.client.exception.NSQException; 9 | import com.youzan.nsq.client.exception.NSQLookupAddressNotFoundException; 10 | import com.youzan.nsq.client.exception.NSQLookupException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * @author zhaoxi (linzuxiong) 16 | */ 17 | public class LookupServiceImpl implements LookupService { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(LookupServiceImpl.class); 20 | private static final long serialVersionUID = 1773482379917817275L; 21 | private final Role role; 22 | private int lookupLocalId = -1; 23 | 24 | /** 25 | * @param role role for producer and consumer 26 | */ 27 | public LookupServiceImpl(Role role, int lookupLocalId) { 28 | this.role = role; 29 | this.lookupLocalId = lookupLocalId; 30 | } 31 | 32 | @Override 33 | public IPartitionsSelector lookup(String topic, boolean localLookupd, boolean force) throws NSQException { 34 | TopicRuleCategory category = TopicRuleCategory.getInstance(this.role); 35 | switch (this.role) { 36 | case Consumer: { 37 | return lookup(topic, false, category, localLookupd, force); 38 | } 39 | case Producer: { 40 | return lookup(topic, true, category, localLookupd, force); 41 | } 42 | default: { 43 | throw new UnsupportedOperationException("Unknown options. Writable or Readable?"); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public IPartitionsSelector lookup(final String topic, boolean writable, final TopicRuleCategory category, boolean localLookupd, boolean force) throws NSQException { 50 | if (null == topic || topic.isEmpty()) { 51 | throw new NSQLookupException("Input topic is blank!"); 52 | } 53 | /* 54 | * It is unnecessary to use Atomic/Lock for the variable 55 | */ 56 | NSQLookupdAddresses aLookupdAddr = LookupAddressUpdate.getInstance().getLookup(topic, category, localLookupd, force, this.lookupLocalId); 57 | IPartitionsSelector ps; 58 | if(null != aLookupdAddr) 59 | ps = aLookupdAddr.lookup(topic, writable); 60 | else { 61 | String errMsg = String.format("Lookup addresses not found. topic: %s, writable: %b, category: %s, localLookup: %b, force lookup: %b", 62 | topic, writable, category.category(topic), localLookupd, force); 63 | throw new NSQLookupAddressNotFoundException(errMsg); 64 | } 65 | return ps; 66 | } 67 | 68 | @Override 69 | public void close() { 70 | } 71 | } -------------------------------------------------------------------------------- /src/test/resources/testng-base-suite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/frame/ErrorFrame.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.frame; 2 | 3 | import com.youzan.nsq.client.entity.Response; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class ErrorFrame extends NSQFrame { 8 | 9 | private static final Logger logger = LoggerFactory.getLogger(ErrorFrame.class); 10 | 11 | @Override 12 | public FrameType getType() { 13 | return FrameType.ERROR_FRAME; 14 | } 15 | 16 | @Override 17 | public void setData(byte[] data) { 18 | super.setData(data); 19 | logger.warn("Message is {}", getMessage()); 20 | } 21 | 22 | @Override 23 | public String getMessage() { 24 | return new String(getData(), DEFAULT_CHARSET).trim(); 25 | } 26 | 27 | /** 28 | * @return the err 29 | */ 30 | public Response getError() { 31 | final String content = getMessage(); 32 | if (null != content && !content.isEmpty()) { 33 | if (content.startsWith("E_INVALID ")) { 34 | return Response.E_INVALID; 35 | } 36 | if (content.startsWith("E_BAD_TOPIC")) { 37 | return Response.E_BAD_TOPIC; 38 | } 39 | if (content.startsWith("E_BAD_MESSAGE")) { 40 | return Response.E_BAD_MESSAGE; 41 | } 42 | if (content.startsWith("E_FAILED_ON_NOT_LEADER")) { 43 | return Response.E_FAILED_ON_NOT_LEADER; 44 | } 45 | if (content.startsWith("E_FAILED_ON_NOT_WRITABLE")) { 46 | return Response.E_FAILED_ON_NOT_WRITABLE; 47 | } 48 | if (content.startsWith("E_TOPIC_NOT_EXIST")) { 49 | return Response.E_TOPIC_NOT_EXIST; 50 | } 51 | if (content.startsWith("E_PUB_FAILED")) { 52 | return Response.E_PUB_FAILED; 53 | } 54 | if (content.startsWith("E_MPUB_FAILED")) { 55 | return Response.E_MPUB_FAILED; 56 | } 57 | if (content.startsWith("E_FIN_FAILED")) { 58 | return Response.E_FIN_FAILED; 59 | } 60 | if (content.startsWith("E_SUB_ORDER_IS_MUST")) { 61 | return Response.E_SUB_ORDER_IS_MUST; 62 | } 63 | if (content.startsWith("E_TAG_NOT_SUPPORT")) { 64 | return Response.E_TAG_NOT_SUPPORT; 65 | } 66 | if (content.startsWith("E_SUB_EXTEND_NEED")) { 67 | return Response.E_SUB_EXTEND_NEED; 68 | } 69 | if (content.startsWith(Response.E_BAD_TAG.getContent())) { 70 | return Response.E_BAD_TAG; 71 | } 72 | if (content.startsWith(Response.E_EXT_NOT_SUPPORT.getContent())) { 73 | return Response.E_EXT_NOT_SUPPORT; 74 | } 75 | if (content.startsWith(Response.E_BAD_BODY.getContent())) { 76 | return Response.E_BAD_BODY; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "ErrorFrame: " + this.getMessage(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/MockedNSQSimpleClient.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.core.NSQConnection; 4 | import com.youzan.nsq.client.core.NSQSimpleClient; 5 | import com.youzan.nsq.client.core.command.Nop; 6 | import com.youzan.nsq.client.entity.Response; 7 | import com.youzan.nsq.client.entity.Role; 8 | import com.youzan.nsq.client.exception.NSQException; 9 | import com.youzan.nsq.client.network.frame.ErrorFrame; 10 | import com.youzan.nsq.client.network.frame.NSQFrame; 11 | import com.youzan.nsq.client.network.frame.ResponseFrame; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.ChannelFutureListener; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Created by lin on 17/6/26. 19 | */ 20 | public class MockedNSQSimpleClient extends NSQSimpleClient { 21 | private final Logger logger = LoggerFactory.getLogger(MockedNSQSimpleClient.class.getName()); 22 | 23 | public MockedNSQSimpleClient(Role role, boolean localLookupd) { 24 | super(role, localLookupd, null); 25 | } 26 | 27 | @Override 28 | public void incoming(final NSQFrame frame, final NSQConnection conn) throws NSQException { 29 | if (frame == null) { 30 | logger.error("The fra`me is null because of SDK's bug in the {}", this.getClass().getName()); 31 | return; 32 | } 33 | switch (frame.getType()) { 34 | case RESPONSE_FRAME: { 35 | final String resp = frame.getMessage(); 36 | if (Response._HEARTBEAT_.getContent().equals(resp)) { 37 | ChannelFuture nopFuture = conn.command(Nop.getInstance()); 38 | nopFuture.addListener(new ChannelFutureListener() { 39 | @Override 40 | public void operationComplete(ChannelFuture future) throws Exception { 41 | if(!future.isSuccess()) { 42 | logger.error("Fail to response to heartbeat from {}.", conn.getAddress()); 43 | } 44 | } 45 | }); 46 | return; 47 | } else { 48 | conn.addResponseFrame((ResponseFrame) frame); 49 | } 50 | break; 51 | } 52 | case ERROR_FRAME: { 53 | if (conn.isSubSent()) { 54 | // super.incoming(frame, conn); 55 | } else { 56 | final ErrorFrame err = (ErrorFrame) frame; 57 | conn.addErrorFrame(err); 58 | logger.warn("Error-Frame from {} , frame: {}", conn.getAddress(), frame); 59 | } 60 | break; 61 | } 62 | case MESSAGE_FRAME: { 63 | logger.warn("Receive a message frame in the mocked simple client."); 64 | break; 65 | } 66 | default: { 67 | logger.warn("Invalid frame-type from {} , frame-type: {} , frame: {}", conn.getAddress(), frame.getType(), frame); 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/it/youzan/nsq/client/ITConsumerWPartition.java: -------------------------------------------------------------------------------- 1 | package it.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.Consumer; 4 | import com.youzan.nsq.client.ConsumerImplV2; 5 | import com.youzan.nsq.client.MessageHandler; 6 | import com.youzan.nsq.client.entity.NSQMessage; 7 | import com.youzan.nsq.client.exception.NSQException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.testng.Assert; 11 | import org.testng.annotations.Test; 12 | 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | 17 | /** 18 | * Created by lin on 16/8/19. 19 | */ 20 | @Test(groups = {"ITConsumerWPartition-Base"}, dependsOnGroups = {"ITProducerWPartition-Base"}, priority = 5) 21 | public class ITConsumerWPartition extends AbstractITConsumer{ 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(ITConsumerWPartition.class); 24 | 25 | public void test() throws NSQException, InterruptedException { 26 | 27 | String[] lookupds = config.getLookupAddresses(); 28 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 29 | return; 30 | 31 | final CountDownLatch latch = new CountDownLatch(10); 32 | final AtomicInteger received = new AtomicInteger(0); 33 | Consumer consumer = new ConsumerImplV2(config, new MessageHandler() { 34 | @Override 35 | public void process(NSQMessage message) { 36 | received.incrementAndGet(); 37 | latch.countDown(); 38 | } 39 | }); 40 | consumer.setAutoFinish(true); 41 | consumer.subscribe("JavaTesting-Producer-Base"); 42 | consumer.start(); 43 | Assert.assertTrue(latch.await(1, TimeUnit.MINUTES)); 44 | logger.info("Consumer received {} messages.", received.get()); 45 | consumer.close(); 46 | } 47 | 48 | 49 | //start up two consumer subscribe on different partition, one should receive and another should NOT 50 | public void testTwoConsumerOn2Partition() throws NSQException, InterruptedException { 51 | 52 | String[] lookupds = config.getLookupAddresses(); 53 | if(config.getUserSpecifiedLookupAddress() && null != lookupds && lookupds[0].contains("nsq-")) 54 | return; 55 | 56 | final CountDownLatch latch = new CountDownLatch(2); 57 | final AtomicInteger received = new AtomicInteger(0); 58 | config.setOrdered(true); 59 | Consumer recievedConsumer = new ConsumerImplV2(config, new MessageHandler() { 60 | @Override 61 | public void process(NSQMessage message) { 62 | latch.countDown(); 63 | received.incrementAndGet(); 64 | logger.info("Received {}", message.getReadableContent()); 65 | } 66 | }); 67 | 68 | recievedConsumer.setAutoFinish(true); 69 | recievedConsumer.subscribe("JavaTesting-Partition"); 70 | recievedConsumer.start(); 71 | Assert.assertTrue(latch.await(1, TimeUnit.MINUTES)); 72 | Assert.assertEquals(received.get(), 2); 73 | recievedConsumer.close(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/Pub.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.nsq.client.core.command; 5 | 6 | import com.youzan.nsq.client.entity.Message; 7 | import com.youzan.nsq.client.entity.Topic; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.nio.ByteBuffer; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * @author zhaoxi (linzuxiong) 17 | * 18 | * 19 | */ 20 | public class Pub implements NSQCommand { 21 | private static final Logger logger = LoggerFactory.getLogger(Pub.class); 22 | public final static int MSG_SIZE = 4; 23 | public final static int TRACE_ID_SIZE = 8; 24 | 25 | protected final Topic topic; 26 | private final List body = new ArrayList<>(1); 27 | protected byte[] bytes = null; 28 | protected int partitionOverride = -1; 29 | 30 | /** 31 | * @param msg 32 | * message object 33 | */ 34 | public Pub(Message msg) { 35 | this.topic = msg.getTopic(); 36 | this.body.add(msg.getMessageBodyInByte()); 37 | } 38 | 39 | Pub(Topic topic) { 40 | this.topic = topic; 41 | } 42 | 43 | @Override 44 | /** 45 | * returns: 46 | * COMMAND HEADER 47 | * BODY 48 | * 49 | * in bytes 50 | */ 51 | public byte[] getBytes() { 52 | if(null == bytes){ 53 | byte[] header = this.getHeader().getBytes(NSQCommand.DEFAULT_CHARSET); 54 | byte[] body = this.getBody().get(0); 55 | ByteBuffer buf = ByteBuffer.allocate(header.length + 4 + body.length); 56 | 57 | buf.put(header) 58 | .putInt(body.length) 59 | .put(body); 60 | bytes = buf.array(); 61 | } 62 | return bytes; 63 | } 64 | 65 | /** 66 | * override default partition, by default, it should be used to override default partition(-1) 67 | * @param newPartition new partition# 68 | */ 69 | public void overrideDefaultPartition(int newPartition) { 70 | assert newPartition > -1; 71 | this.partitionOverride = newPartition; 72 | } 73 | 74 | protected String getPartitionStr() { 75 | String partitionStr; 76 | if(partitionOverride > -1) 77 | partitionStr = SPACE_STR + partitionOverride; 78 | else if(topic.hasPartition()) 79 | partitionStr = SPACE_STR + topic.getPartitionId(); 80 | else 81 | partitionStr = ""; 82 | 83 | return partitionStr; 84 | } 85 | 86 | @Override 87 | public String getHeader() { 88 | return String.format("PUB %s%s\n", topic.getTopicText(), this.getPartitionStr()); 89 | } 90 | 91 | @Override 92 | public List getBody() { 93 | return body; 94 | } 95 | 96 | protected String getTopicText() { 97 | return this.topic.getTopicText(); 98 | } 99 | 100 | public Topic getTopic() { 101 | return this.topic; 102 | } 103 | 104 | public String toString(){ 105 | return this.getHeader(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/entity/lookup/PartitionTestcase.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity.lookup; 2 | 3 | import com.youzan.nsq.client.entity.IPartitionsSelector; 4 | import com.youzan.nsq.client.entity.Partitions; 5 | import com.youzan.nsq.client.entity.Topic; 6 | import com.youzan.nsq.client.exception.NSQLookupException; 7 | import com.youzan.nsq.client.exception.NSQPartitionNotAvailableException; 8 | import com.youzan.nsq.client.exception.NSQProducerNotFoundException; 9 | import com.youzan.nsq.client.exception.NSQTopicNotFoundException; 10 | import org.easymock.EasyMockSupport; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.testng.annotations.BeforeClass; 14 | import org.testng.annotations.Test; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Properties; 21 | 22 | import static org.easymock.EasyMock.expect; 23 | 24 | /** 25 | * Created by lin on 17/1/16. 26 | */ 27 | public class PartitionTestcase extends EasyMockSupport{ 28 | private static final Logger logger = LoggerFactory.getLogger(PartitionTestcase.class); 29 | 30 | private Properties props = new Properties(); 31 | 32 | @BeforeClass 33 | public void init() throws IOException { 34 | logger.info("At {} , initialize: {}", System.currentTimeMillis(), this.getClass().getName()); 35 | try (final InputStream is = getClass().getClassLoader().getResourceAsStream("app-test.properties")) { 36 | props.load(is); 37 | } 38 | final String env = props.getProperty("env"); 39 | } 40 | 41 | /** 42 | * trying fetching a missing partition using shardingID 43 | */ 44 | @Test(expectedExceptions = {NSQPartitionNotAvailableException.class}) 45 | public void testPartitionMissing() throws NSQProducerNotFoundException, NSQTopicNotFoundException, NSQLookupException, NSQPartitionNotAvailableException { 46 | try { 47 | Topic mockTopic = partialMockBuilder(Topic.class).withConstructor("java_test_ordered_multi_topic") 48 | .addMockedMethod("updatePartitionIndex").createMock(); 49 | expect(mockTopic.calculatePartitionIndex(9L, 10)).andStubReturn(9); 50 | replayAll(); 51 | //and hack partition num 52 | String cluster = props.getProperty("lookup-addresses"); 53 | List clusters = new ArrayList<>(); 54 | clusters.add(cluster); 55 | NSQLookupdAddresses lookupd = createNSQLookupdAddr(clusters, clusters); 56 | IPartitionsSelector ps = lookupd.lookup("java_test_ordered_multi_topic", true); 57 | Partitions[] par = ps.choosePartitions(); 58 | //hack partition number 59 | par[0].updatePartitionNum(10); 60 | mockTopic.setPartitionID(9); 61 | par[0].getPartitionAddress(mockTopic.getPartitionId()); 62 | }finally { 63 | resetAll(); 64 | logger.info("Mocking reset."); 65 | } 66 | } 67 | 68 | public NSQLookupdAddresses createNSQLookupdAddr(List clusterIds, List lookupAddrs){ 69 | return NSQLookupdAddresses.create(clusterIds, lookupAddrs); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/PartitionTestcase.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.exception.NSQException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.annotations.Test; 7 | 8 | /** 9 | * Created by lin on 16/9/24. 10 | */ 11 | public class PartitionTestcase extends AbstractNSQClientTestcase{ 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(PartitionTestcase.class); 14 | 15 | @Test 16 | /** 17 | * subscriber subscribe to one $topic + partition1 18 | * then change to $topic + partition2 19 | * 20 | */ 21 | public void testSubscribeTwoPartitions() throws NSQException, InterruptedException { 22 | // final AtomicInteger cnt0 = new AtomicInteger(0); 23 | // final CountDownLatch latch0 = new CountDownLatch(10); 24 | // Topic topic0 = new Topic("JavaTesting-Partition"); 25 | // 26 | // final AtomicInteger cnt1 = new AtomicInteger(0); 27 | // final CountDownLatch latch1 = new CountDownLatch(10); 28 | // Topic topic1 = new Topic("JavaTesting-Partition"); 29 | // 30 | // final String msgPartition0 = "message for partition0"; 31 | // byte[] msg0 = msgPartition0.getBytes(); 32 | // final String msgPartition1 = "message for partition1"; 33 | // byte[] msg1 = msgPartition1.getBytes(); 34 | // 35 | // getNSQConfig().setConsumerName("PartitionConsumer"); 36 | // Consumer consumer0 = PartitionTestcase.createConsumer(this.getNSQConfig(), new MessageHandler() { 37 | // @Override 38 | // public void process(NSQMessage message) { 39 | // logger.info("Message from partition 0"); 40 | // cnt0.incrementAndGet(); 41 | // String msg = new String(message.getMessageBody()); 42 | // logger.info("#{}: {}", cnt0, msg); 43 | // Assert.assertEquals(msg, msgPartition0); 44 | // latch0.countDown(); 45 | // } 46 | // }); 47 | // 48 | // consumer0.subscribe(topic0.getPartitionId(), topic0.getTopicText()); 49 | // consumer0.start(); 50 | // 51 | // Consumer consumer1 = PartitionTestcase.createConsumer(this.getNSQConfig(), new MessageHandler() { 52 | // @Override 53 | // public void process(NSQMessage message) { 54 | // logger.info("Message from partition 1"); 55 | // cnt1.incrementAndGet(); 56 | // String msg = new String(message.getMessageBody()); 57 | // logger.info("#{}: {}", cnt1, msg); 58 | // Assert.assertEquals(msg, msgPartition1); 59 | // latch1.countDown(); 60 | // } 61 | // }); 62 | // consumer1.subscribe(topic1.getPartitionId(), topic1.getTopicText()); 63 | // consumer1.start(); 64 | // 65 | // Producer producer = PartitionTestcase.createProducer(getNSQConfig()); 66 | // producer.start(); 67 | // 68 | // for(int i = 0; i < 10; i++) { 69 | // producer.publish(msg0, topic0); 70 | // producer.publish(msg1, topic1); 71 | // } 72 | // producer.close(); 73 | // 74 | // Assert.assertTrue(latch0.await(1, TimeUnit.MINUTES)); 75 | // Assert.assertTrue(latch1.await(1, TimeUnit.MINUTES)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/frame/ResponseFrame.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.frame; 2 | 3 | import com.youzan.nsq.client.MessageMetadata; 4 | import com.youzan.nsq.client.MessageReceipt; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.nio.ByteBuffer; 10 | 11 | public class ResponseFrame extends NSQFrame implements MessageMetadata{ 12 | private static final Logger logger = LoggerFactory.getLogger(ResponseFrame.class); 13 | private MessageReceipt receipt = new MessageReceipt(); 14 | 15 | @Override 16 | public FrameType getType() { 17 | return FrameType.RESPONSE_FRAME; 18 | } 19 | 20 | @Override 21 | public String getMessage() { 22 | return new String(getData(), DEFAULT_CHARSET).trim(); 23 | } 24 | 25 | @Override 26 | public void setData(byte[] data) { 27 | super.setData(data); 28 | generateReceipt(); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "ResponseFrame: " + this.getMessage(); 34 | } 35 | 36 | @Override 37 | public String toMetadataStr() { 38 | String resMsg = getMessage(); 39 | //check if has meta data 40 | if(resMsg.startsWith("OK") && resMsg.length() > 2){ 41 | StringBuilder sb = new StringBuilder(); 42 | sb.append(this.getClass().toString() + " meta-data:").append("\n"); 43 | sb.append("\t[internalID]:\t").append(receipt.getInternalID()).append("\n"); 44 | sb.append("\t[traceID]:\t").append(receipt.getTraceID()).append("\n"); 45 | sb.append("\t[diskQueueOffset]:\t").append(receipt.getDiskQueueOffset()).append("\n"); 46 | sb.append("\t[diskQueueDataSize]:\t").append(receipt.getDiskQueueSize()).append("\n"); 47 | sb.append(this.getClass().toString() + " end."); 48 | return sb.toString(); 49 | } 50 | return "No meta data"; 51 | } 52 | 53 | /** 54 | * Generate message receipt for publish response 55 | * @return messageReceipt 56 | */ 57 | private void generateReceipt() { 58 | String resMsg = getMessage(); 59 | //check if has meta data 60 | if (resMsg.startsWith("OK") && resMsg.length() > 2) { 61 | byte[] data = getData(); 62 | 63 | //internal ID 64 | byte[] internalIDByte = new byte[8]; 65 | System.arraycopy(data, 2, internalIDByte, 0, 8); 66 | long internalID = ByteBuffer.wrap(internalIDByte).getLong(); 67 | receipt.setInternalID(internalID); 68 | 69 | //traceID 70 | byte[] traceIDByte = new byte[8]; 71 | System.arraycopy(data, 10, traceIDByte, 0, 8); 72 | long traceID = ByteBuffer.wrap(traceIDByte).getLong(); 73 | receipt.setTraceID(traceID); 74 | 75 | //disk queue offset 76 | byte[] diskqueueOffsetByte = new byte[8]; 77 | System.arraycopy(data, 18, diskqueueOffsetByte, 0, 8); 78 | long diskQueueOffset = ByteBuffer.wrap(diskqueueOffsetByte).getLong(); 79 | receipt.setDiskQueueOffset(diskQueueOffset); 80 | 81 | //disk queue data size 82 | byte[] diskqueueSizeByte = new byte[4]; 83 | System.arraycopy(data, 26, diskqueueSizeByte, 0, 4); 84 | int diskQueueSize = ByteBuffer.wrap(diskqueueSizeByte).getInt(); 85 | receipt.setDiskQueueSize(diskQueueSize); 86 | } 87 | } 88 | 89 | public MessageReceipt getReceipt() { 90 | return this.receipt; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/AbstractNSQClientTestcase.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client; 2 | 3 | import com.youzan.nsq.client.configs.ConfigAccessAgent; 4 | import com.youzan.nsq.client.core.LookupAddressUpdate; 5 | import com.youzan.nsq.client.entity.NSQConfig; 6 | import com.youzan.nsq.client.exception.ConfigAccessAgentException; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.testng.annotations.AfterMethod; 10 | import org.testng.annotations.BeforeClass; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.lang.reflect.Constructor; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.lang.reflect.Method; 17 | import java.util.Properties; 18 | 19 | /** 20 | * basic functions and utilities defined here 21 | * Created by lin on 16/9/24. 22 | */ 23 | public class AbstractNSQClientTestcase { 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(AbstractNSQClientTestcase.class); 26 | Properties props = new Properties(); 27 | protected NSQConfig config; 28 | 29 | @BeforeClass 30 | public void init() throws IOException { 31 | logger.info("At {} , initialize: {}", System.currentTimeMillis(), this.getClass().getName()); 32 | System.setProperty("nsq.sdk.configFilePath", "src/test/resources/configClientTest.properties"); 33 | try (final InputStream is = getClass().getClassLoader().getResourceAsStream("app-test.properties")) { 34 | props.load(is); 35 | } 36 | final String env = props.getProperty("env"); 37 | logger.debug("The environment is {} .", env); 38 | final String lookups = props.getProperty("lookup-addresses"); 39 | final String connTimeout = props.getProperty("connectTimeoutInMillisecond"); 40 | final String msgTimeoutInMillisecond = props.getProperty("msgTimeoutInMillisecond"); 41 | final String threadPoolSize4IO = props.getProperty("threadPoolSize4IO"); 42 | 43 | config = new NSQConfig(); 44 | config.setLookupAddresses(lookups); 45 | config.setConnectTimeoutInMillisecond(Integer.valueOf(connTimeout)); 46 | config.setMsgTimeoutInMillisecond(Integer.valueOf(msgTimeoutInMillisecond)); 47 | config.setThreadPoolSize4IO(Integer.valueOf(threadPoolSize4IO)); 48 | } 49 | 50 | public NSQConfig getNSQConfig(){ 51 | return this.config; 52 | } 53 | 54 | //simple function to give you a producer 55 | public static Producer createProducer(final NSQConfig config){ 56 | return new MockedProducer(config); 57 | } 58 | 59 | public static Consumer createConsumer(final NSQConfig config, final MessageHandler handler){ 60 | return new ConsumerImplV2(config, handler); 61 | } 62 | 63 | public static LookupAddressUpdate createLookupAddressUpdateInstance() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 64 | Class claz = LookupAddressUpdate.class; 65 | Constructor con = claz.getConstructor(); 66 | con.setAccessible(true); 67 | LookupAddressUpdate lau = (LookupAddressUpdate) con.newInstance(); 68 | return lau; 69 | } 70 | 71 | @AfterMethod 72 | public void release() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ConfigAccessAgentException { 73 | Class clazz = ConfigAccessAgent.class; 74 | Method method = clazz.getDeclaredMethod("release"); 75 | method.setAccessible(true); 76 | method.invoke(ConfigAccessAgent.getInstance()); 77 | System.clearProperty("nsq.sdk.configFilePath"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/entity/ConsumeMessageFilter.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.entity; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | 6 | import java.nio.file.Path; 7 | import java.nio.file.PathMatcher; 8 | import java.nio.file.Paths; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Created by lin on 17/11/20. 15 | */ 16 | public interface ConsumeMessageFilter { 17 | 18 | boolean apply(T filter, V filterVal); 19 | 20 | int getType(); 21 | 22 | ConsumeMessageFilter EXACT_MATCH_FILTER = new ConsumeMessageFilter() { 23 | 24 | @Override 25 | public boolean apply(String filter, String filterVal) { 26 | if(StringUtils.isBlank(filter)) { 27 | return true; 28 | } else if (filter.equals(filterVal)) { 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | @Override 36 | public int getType() { 37 | return 1; 38 | } 39 | }; 40 | 41 | ConsumeMessageFilter REG_EXP_MATCH_FILTER = new ConsumeMessageFilter() { 42 | 43 | @Override 44 | public boolean apply(Pattern patternFilter, String filterVal) { 45 | if(null == patternFilter) { 46 | return true; 47 | } 48 | if (patternFilter.matcher(filterVal).matches()) { 49 | return true; 50 | } else { 51 | return false; 52 | } 53 | } 54 | 55 | @Override 56 | public int getType() { 57 | return 2; 58 | } 59 | }; 60 | 61 | ConsumeMessageFilter GLOB_EXP_MATCH_FILTER = new ConsumeMessageFilter() { 62 | 63 | @Override 64 | public boolean apply(PathMatcher filter, String filterVal) { 65 | if(null == filter) { 66 | return true; 67 | } 68 | Path path = Paths.get(filterVal); 69 | if (filter.matches(path)) { 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | @Override 77 | public int getType() { 78 | return 3; 79 | } 80 | }; 81 | 82 | ConsumeMessageFilter MULTI_MATCH_FILTER = new ConsumeMessageFilter() { 83 | 84 | @Override 85 | public boolean apply(Pair filter, Map extJson) { 86 | Pair>> multiFilters = (Pair>>) filter; 87 | if(null == multiFilters) { 88 | return true; 89 | } 90 | NSQConfig.MultiFiltersRelation relation = multiFilters.getKey(); 91 | Map extJsonHeader = (Map) extJson; 92 | boolean match = true; 93 | for (Pair filterData : multiFilters.getRight()) { 94 | String extVal = extJsonHeader.get(filterData.getKey()); 95 | match = EXACT_MATCH_FILTER.apply(filterData.getRight(), extVal); 96 | if (match && relation == NSQConfig.MultiFiltersRelation.any) { 97 | return true; 98 | } 99 | if (!match && relation == NSQConfig.MultiFiltersRelation.all) { 100 | return false; 101 | } 102 | } 103 | return match; 104 | } 105 | 106 | @Override 107 | public int getType() { 108 | return 3; 109 | } 110 | }; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/network/frame/TestMessageFrame.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.frame; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.Test; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.Random; 8 | 9 | /** 10 | * http://nsq.io/clients/tcp_protocol_spec.html#data-format 11 | * 12 | * @author zhaoxi (linzuxiong) 13 | */ 14 | public class TestMessageFrame { 15 | private Random _r = new Random(); 16 | 17 | @Test 18 | public void newInstance() { 19 | MessageFrame frame = new MessageFrame(); 20 | } 21 | 22 | @Test 23 | public void setData() { 24 | MessageFrame frame = new MessageFrame(); 25 | int size = 100; 26 | int dataSize = size - 4; 27 | // size + type + data 28 | // timestamp + attempts + ID + messageBody 29 | 30 | byte[] timestamp = new byte[8]; 31 | byte[] attempts = new byte[2]; 32 | byte[] messageID = new byte[16]; 33 | byte[] messageBody = new byte[dataSize - 8 - 2 - 16]; 34 | _r.nextBytes(timestamp); 35 | _r.nextBytes(attempts); 36 | _r.nextBytes(messageID); 37 | _r.nextBytes(messageBody); 38 | 39 | ByteBuffer bb = java.nio.ByteBuffer.allocate(dataSize); 40 | bb.put(timestamp); 41 | bb.put(attempts); 42 | bb.put(messageID); 43 | bb.put(messageBody); 44 | byte[] data = bb.array(); 45 | 46 | frame.setSize(dataSize); 47 | frame.setData(data); 48 | 49 | Assert.assertEquals(frame.getTimestamp(), timestamp); 50 | Assert.assertEquals(frame.getAttempts(), attempts); 51 | Assert.assertEquals(frame.getMessageID(), messageID); 52 | Assert.assertEquals(frame.getMessageBody(), messageBody); 53 | } 54 | 55 | @Test(expectedExceptions = Exception.class) 56 | public void dataException() { 57 | MessageFrame frame = new MessageFrame(); 58 | int size = 29; 59 | int dataSize = size - 4; 60 | // size + type + data 61 | // timestamp + attempts + ID + messageBody 62 | 63 | byte[] timestamp = new byte[8]; 64 | byte[] attempts = new byte[2]; 65 | byte[] messageID = new byte[16]; 66 | byte[] messageBody = new byte[dataSize - 8 - 2 - 16]; 67 | _r.nextBytes(timestamp); 68 | _r.nextBytes(attempts); 69 | _r.nextBytes(messageID); 70 | _r.nextBytes(messageBody); 71 | 72 | ByteBuffer bb = java.nio.ByteBuffer.allocate(dataSize); 73 | bb.put(timestamp); 74 | bb.put(attempts); 75 | bb.put(messageID); 76 | bb.put(messageBody); 77 | byte[] data = bb.array(); 78 | 79 | frame.setSize(dataSize); 80 | frame.setData(data); 81 | 82 | } 83 | 84 | @Test 85 | public void dataBoundary() { 86 | MessageFrame frame = new MessageFrame(); 87 | int size = 30; 88 | int dataSize = size - 4; 89 | // size + type + data 90 | // timestamp + attempts + ID + messageBody 91 | 92 | byte[] timestamp = new byte[8]; 93 | byte[] attempts = new byte[2]; 94 | byte[] messageID = new byte[16]; 95 | byte[] messageBody = new byte[dataSize - 8 - 2 - 16]; 96 | _r.nextBytes(timestamp); 97 | _r.nextBytes(attempts); 98 | _r.nextBytes(messageID); 99 | _r.nextBytes(messageBody); 100 | 101 | ByteBuffer bb = java.nio.ByteBuffer.allocate(dataSize); 102 | bb.put(timestamp); 103 | bb.put(attempts); 104 | bb.put(messageID); 105 | bb.put(messageBody); 106 | byte[] data = bb.array(); 107 | 108 | frame.setSize(dataSize); 109 | frame.setData(data); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/utils/Lz4Util.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.utils; /** 2 | * Created by lin on 17/6/30. 3 | */ 4 | 5 | import net.jpountz.lz4.LZ4Compressor; 6 | import net.jpountz.lz4.LZ4Factory; 7 | import net.jpountz.lz4.LZ4FastDecompressor; 8 | import net.jpountz.lz4.LZ4SafeDecompressor; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * Created by suguoqing on 16/6/8. 14 | */ 15 | public class Lz4Util { 16 | private static final Logger LOGGER = LoggerFactory.getLogger(Lz4Util.class); 17 | private static LZ4Factory factory = LZ4Factory.fastestInstance(); 18 | private static LZ4SafeDecompressor decompressor = factory.safeDecompressor(); 19 | private static LZ4FastDecompressor fastDecompressor = factory.fastDecompressor(); 20 | private static LZ4Compressor compressor = factory.fastCompressor(); 21 | 22 | /** 23 | * 压缩一个 data 数组 24 | * @param data 原始数据 25 | * @return 压缩后的数据 26 | */ 27 | public static byte[] compress(byte[] data){ 28 | final int decompressedLength = data.length; 29 | 30 | // compress data 31 | int maxCompressedLength = compressor.maxCompressedLength(decompressedLength); 32 | byte[] compressed = new byte[maxCompressedLength]; 33 | int compressedLength = compressor.compress(data, 0, decompressedLength, compressed, 0, maxCompressedLength); 34 | 35 | byte[] result = new byte[compressedLength]; 36 | System.arraycopy(compressed, 0, result, 0, compressedLength); 37 | return result; 38 | } 39 | 40 | /** 41 | * 快速解压一个数组 42 | * @param compressedArray 压缩后的数据 43 | * @param orinArrayLength 44 | * @param extInfo 45 | * @return 46 | */ 47 | public static byte[] fastDecopress(final byte[] compressedArray, int orinArrayLength, String... extInfo) { 48 | try { 49 | return fastDecompressor.decompress(compressedArray, orinArrayLength); 50 | } catch (Exception e) { 51 | LOGGER.error("fast decompress Error:extInfo ={} ", extInfo, e); 52 | return null; 53 | } 54 | } 55 | 56 | /** 57 | * 解压一个数组 58 | * @param finalCompressedArray 原始数组 59 | * @param decompressLength 数组压缩前的长度 60 | * @param extInfo 异常信息 61 | * @return 62 | */ 63 | public static byte[] decompress( byte[] finalCompressedArray, Integer decompressLength, String ... extInfo) { 64 | int ratio = 3; 65 | if (null != decompressLength) { 66 | // 知道长度用fastCompress 67 | return fastDecopress(finalCompressedArray, decompressLength, extInfo); 68 | } else { 69 | decompressLength = finalCompressedArray.length * ratio; 70 | } 71 | 72 | int i = 5; 73 | while (i > 0) { 74 | try { 75 | return decompress(finalCompressedArray, decompressLength); 76 | } catch (Exception e) { 77 | ratio = ratio * 2; 78 | i--; 79 | if (LOGGER.isInfoEnabled()) { 80 | LOGGER.info("decompress Error:ratio ={} extInfo ={} ", ratio, extInfo, e); 81 | } 82 | 83 | } 84 | 85 | } 86 | 87 | throw new RuntimeException("decompress error"); 88 | } 89 | 90 | /** 91 | * 解压一个数组 92 | * 93 | * @param finalCompressedArray 压缩后的数据 94 | * @param length 原始数据长度, 长度不能小于压缩前的长度。 95 | * @return 96 | */ 97 | private static byte[] decompress(byte[] finalCompressedArray, int length) { 98 | 99 | byte[] desc = new byte[length]; 100 | int decompressLen = decompressor.decompress(finalCompressedArray, desc); 101 | 102 | byte[] result = new byte[decompressLen]; 103 | System.arraycopy(desc, 0, result, 0, decompressLen); 104 | return result; 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/network/frame/OrderedMessageFrame.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.network.frame; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * Created by lin on 16/9/26. 7 | */ 8 | public class OrderedMessageFrame extends MessageFrame{ 9 | /** 10 | * 8-byte : disk queue offset 11 | */ 12 | final private byte[] diskQueueOffset = new byte[8]; 13 | /** 14 | * 4-byte : disk queue data size 15 | */ 16 | final private byte[] diskQueueDataSize = new byte[4]; 17 | 18 | @Override 19 | public void setData(byte[] bytes, boolean shouldExt) { 20 | System.arraycopy(bytes, 0, timestamp, 0, 8); 21 | System.arraycopy(bytes, 8, attempts, 0, 2); 22 | 23 | //Sub Ordered incoming extra info, disk queue offset & disk queue data size 24 | System.arraycopy(bytes, 10, messageID, 0, 16); 25 | System.arraycopy(bytes, 10, internalID, 0, 8); 26 | System.arraycopy(bytes, 18, traceID, 0, 8); 27 | 28 | int messageBodyStart; 29 | int messageBodySize; 30 | if(!shouldExt) { 31 | messageBodyStart = 26;//8 + 2 + 16; 32 | } else { 33 | //read ext content & length here 34 | //version 35 | System.arraycopy(bytes, 26, extVerBytes, 0, 1); 36 | int extVer = (int)extVerBytes[0]; 37 | switch (extVer) { 38 | case 0: 39 | messageBodyStart = 27;//8+2+16+1 40 | break; 41 | default: 42 | byte[] extBytesLenBytes = new byte[2]; 43 | //ext content length 44 | System.arraycopy(bytes, 27, extBytesLenBytes, 0, 2); 45 | int extBytesLen = ByteBuffer.wrap(extBytesLenBytes).getShort(); 46 | //allocate 47 | extBytes = new byte[extBytesLen]; 48 | System.arraycopy(bytes, 29, extBytes, 0, extBytesLen); 49 | messageBodyStart = 29 + extBytesLen;//8 + 2 + 16 + 1 + 2 + extBytesLen; 50 | } 51 | } 52 | 53 | System.arraycopy(bytes, messageBodyStart, diskQueueOffset, 0, 8); 54 | System.arraycopy(bytes, messageBodyStart + 8, diskQueueDataSize, 0, 4); 55 | 56 | messageBodySize = bytes.length - (messageBodyStart + 8 + 4); 57 | messageBody = new byte[messageBodySize]; 58 | 59 | System.arraycopy(bytes, messageBodyStart + 8 + 4, messageBody, 0, messageBodySize); 60 | } 61 | 62 | @Override 63 | public void setData(byte[] bytes) { 64 | //capability of message array bytes.length - (8 + 2 + 8 + 4 + 16) 65 | final int messageBodySize = bytes.length - (38); 66 | messageBody = new byte[messageBodySize]; 67 | System.arraycopy(bytes, 0, timestamp, 0, 8); 68 | System.arraycopy(bytes, 8, attempts, 0, 2); 69 | //Sub Ordered incoming extra info, disk queue offset & disk queue data size 70 | System.arraycopy(bytes, 10, messageID, 0, 16); 71 | System.arraycopy(bytes, 10, internalID, 0, 8); 72 | System.arraycopy(bytes, 18, traceID, 0, 8); 73 | 74 | System.arraycopy(bytes, 26, diskQueueOffset, 0, 8); 75 | System.arraycopy(bytes, 34, diskQueueDataSize, 0, 4); 76 | 77 | 78 | System.arraycopy(bytes, 38, messageBody, 0, messageBodySize); 79 | } 80 | 81 | /** 82 | * function to get diskQueueOffset of current msg, diskqueue has meaning only when message contains advanced info of 83 | * SUB 84 | * @return diskQueueOffSet (int 64) in byte[] 85 | */ 86 | public byte[] getDiskQueueOffset(){ 87 | return this.diskQueueOffset; 88 | } 89 | 90 | /** 91 | * function to get diskQueueDataSize of current msg, disk queue dat size has meaning only when message contains 92 | * advanced info of SUB 93 | * @return diskQueueDataSize (int 64) in byte[] 94 | */ 95 | public byte[] getDiskQueueDataSize(){ 96 | return this.diskQueueDataSize; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/Lists.java: -------------------------------------------------------------------------------- 1 | /** 2 | *
  3 |  * Copy from https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java 
  4 |  * It's Apache License.
  5 |  * 
6 | */ 7 | package com.youzan.util; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.math.RoundingMode; 13 | import java.util.AbstractList; 14 | import java.util.List; 15 | import java.util.RandomAccess; 16 | 17 | /** 18 | * @author zhaoxi (linzuxiong) 19 | * 20 | * 21 | */ 22 | public class Lists { 23 | private static final Logger logger = LoggerFactory.getLogger(Lists.class); 24 | 25 | public static List> partition(List list, int size) { 26 | if (list == null || size <= 0) { 27 | throw new IllegalArgumentException("Your input is blank!"); 28 | } 29 | return (list instanceof RandomAccess) ? new RandomAccessPartition(list, size) : new Partition(list, size); 30 | } 31 | 32 | private static class Partition extends AbstractList> { 33 | final List list; 34 | final int size; 35 | 36 | Partition(List list, int size) { 37 | this.list = list; 38 | this.size = size; 39 | } 40 | 41 | @Override 42 | public List get(int index) { 43 | if (index < 0 || index >= size()) { 44 | throw new IndexOutOfBoundsException(String.format("Size: %s, Index: %s", size(), index)); 45 | } 46 | int start = index * size; 47 | int end = Math.min(start + size, list.size()); 48 | return list.subList(start, end); 49 | } 50 | 51 | @Override 52 | public int size() { 53 | return divideInt(list.size(), size, RoundingMode.CEILING); 54 | } 55 | 56 | @Override 57 | public boolean isEmpty() { 58 | return list.isEmpty(); 59 | } 60 | } 61 | 62 | private static class RandomAccessPartition extends Partition implements RandomAccess { 63 | RandomAccessPartition(List list, int size) { 64 | super(list, size); 65 | } 66 | } 67 | 68 | /** 69 | * Returns the base-2 logarithm of {@code x}, rounded according to the 70 | * specified rounding mode. 71 | * 72 | * @param p 73 | * the size 74 | * @param q 75 | * the small batch size 76 | * @param mode 77 | * RoundingMode 78 | * 79 | * @return batches 80 | * @throws IllegalArgumentException 81 | * if {@code x <= 0} 82 | * @throws ArithmeticException 83 | * if {@code mode} is {@link RoundingMode#UNNECESSARY} and 84 | * {@code x} is not a power of two 85 | */ 86 | public static int divideInt(int p, int q, RoundingMode mode) { 87 | if (q == 0) { 88 | throw new ArithmeticException("/ by zero"); // for GWT 89 | } 90 | int div = p / q; 91 | int rem = p - q * div; // equal to p % q 92 | 93 | if (rem == 0) { 94 | return div; 95 | } 96 | int signum = 1 | ((p ^ q) >> (Integer.SIZE - 1)); 97 | boolean increment; 98 | switch (mode) { 99 | case DOWN: 100 | increment = false; 101 | break; 102 | case UP: 103 | increment = true; 104 | break; 105 | case CEILING: 106 | increment = signum > 0; 107 | break; 108 | case FLOOR: 109 | increment = signum < 0; 110 | break; 111 | case HALF_EVEN: 112 | case HALF_DOWN: 113 | default: 114 | throw new AssertionError(); 115 | } 116 | return increment ? div + signum : div; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/com/youzan/nsq/client/utils/ConnectionUtil.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.utils; 2 | 3 | import com.youzan.nsq.client.MockedNSQConnectionImpl; 4 | import com.youzan.nsq.client.MockedNSQSimpleClient; 5 | import com.youzan.nsq.client.core.Client; 6 | import com.youzan.nsq.client.core.NSQConnection; 7 | import com.youzan.nsq.client.core.NSQSimpleClient; 8 | import com.youzan.nsq.client.core.command.Identify; 9 | import com.youzan.nsq.client.core.command.Magic; 10 | import com.youzan.nsq.client.core.command.Rdy; 11 | import com.youzan.nsq.client.core.command.Sub; 12 | import com.youzan.nsq.client.entity.Address; 13 | import com.youzan.nsq.client.entity.NSQConfig; 14 | import com.youzan.nsq.client.entity.Role; 15 | import com.youzan.nsq.client.entity.Topic; 16 | import com.youzan.nsq.client.exception.NSQNoConnectionException; 17 | import com.youzan.nsq.client.network.frame.NSQFrame; 18 | import com.youzan.nsq.client.network.netty.NSQClientInitializer; 19 | import io.netty.bootstrap.Bootstrap; 20 | import io.netty.channel.*; 21 | import io.netty.channel.nio.NioEventLoopGroup; 22 | import io.netty.channel.socket.nio.NioSocketChannel; 23 | 24 | import java.util.concurrent.CountDownLatch; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.TimeoutException; 28 | 29 | /** 30 | * Created by lin on 17/8/11. 31 | */ 32 | public class ConnectionUtil { 33 | 34 | private static Bootstrap bootstrap; 35 | private static EventLoopGroup eventLoopGroup; 36 | 37 | static{ 38 | //netty setup 39 | bootstrap = new Bootstrap(); 40 | eventLoopGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2); 41 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 42 | bootstrap.option(ChannelOption.TCP_NODELAY, true); 43 | bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500); 44 | bootstrap.group(eventLoopGroup); 45 | bootstrap.channel(NioSocketChannel.class); 46 | bootstrap.handler(new NSQClientInitializer()); 47 | } 48 | 49 | public static NSQConnection connect(Address addr, String channel, NSQConfig config) throws InterruptedException, TimeoutException, NSQNoConnectionException, ExecutionException { 50 | ChannelFuture chFuture = bootstrap.connect(addr.getHost(), addr.getPort()); 51 | final CountDownLatch connLatch = new CountDownLatch(1); 52 | chFuture.addListener(new ChannelFutureListener() { 53 | @Override 54 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 55 | if (channelFuture.isSuccess()) 56 | connLatch.countDown(); 57 | } 58 | }); 59 | connLatch.await(500, TimeUnit.MILLISECONDS); 60 | Channel ch = chFuture.channel(); 61 | MockedNSQConnectionImpl con1 = new MockedNSQConnectionImpl(0, addr, ch, config); 62 | con1.setTopic(new Topic(addr.getTopic(), addr.getPartition())); 63 | NSQSimpleClient simpleClient = new MockedNSQSimpleClient(Role.Consumer, false); 64 | ch.attr(Client.STATE).set(simpleClient); 65 | ch.attr(NSQConnection.STATE).set(con1); 66 | con1.command(Magic.getInstance()); 67 | NSQFrame resp = con1.commandAndGetResponse(null, new Identify(config, addr.isTopicExtend())); 68 | if (null == resp) { 69 | throw new IllegalStateException("Bad Identify Response!"); 70 | } 71 | Thread.sleep(100); 72 | resp = con1.commandAndGetResponse(null, new Sub(new Topic(addr.getTopic(), addr.getPartition()), channel)); 73 | if (null == resp) { 74 | throw new IllegalStateException("Bad Identify Response!"); 75 | } 76 | if (resp.getType() == NSQFrame.FrameType.ERROR_FRAME) { 77 | throw new IllegalStateException(resp.getMessage()); 78 | } 79 | con1.subSent(); 80 | Thread.sleep(100); 81 | con1.command(new Rdy(1)); 82 | Thread.sleep(100); 83 | 84 | return con1; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/command/PubExt.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.command; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | import com.youzan.nsq.client.entity.Message; 7 | import com.youzan.util.SystemUtil; 8 | 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.util.IllegalFormatException; 12 | 13 | /** 14 | * PubExt command 15 | * Created by lin on 17/7/29. 16 | */ 17 | public class PubExt extends Pub { 18 | private byte[] jsonHeaderBytes; 19 | public static final String CLIENT_TAG_KEY = "##client_dispatch_tag"; 20 | public static final String TRACE_ID_KEY = "##trace_id"; 21 | public static final String FILTER_EXT_KEY = "filter_ext_key"; 22 | public static final String FILTER_DATA = "filter_data"; 23 | 24 | 25 | /** 26 | * @param msg message object 27 | */ 28 | public PubExt(final Message msg, boolean trace) throws IllegalFormatException { 29 | super(msg); 30 | String clientTag = msg.getDesiredTag(); 31 | boolean jsonHeaderNeeded = (null != clientTag && !clientTag.isEmpty()) || (trace); 32 | 33 | Object jsonObj = msg.getJsonHeaderExt(); 34 | ObjectNode jsonHeaderExt = null; 35 | //create one json 36 | if (null == jsonObj && jsonHeaderNeeded) { 37 | jsonHeaderExt = SystemUtil.getObjectMapper().createObjectNode(); 38 | if(null != clientTag) 39 | jsonHeaderExt.put(CLIENT_TAG_KEY, clientTag); 40 | if(trace) 41 | jsonHeaderExt.put(TRACE_ID_KEY, msg.getTraceIDStr()); 42 | } else if (null != jsonObj) { 43 | //parse message json header ext 44 | try { 45 | String jsonStr = SystemUtil.getObjectMapper().writeValueAsString(jsonObj); 46 | JsonNode json = SystemUtil.getObjectMapper().readTree(jsonStr); 47 | //override client tag 48 | if(json.isObject()) { 49 | jsonHeaderExt = (ObjectNode)json; 50 | if(null != clientTag) 51 | jsonHeaderExt.put(CLIENT_TAG_KEY, clientTag); 52 | if(trace) 53 | jsonHeaderExt.put(TRACE_ID_KEY, msg.getTraceIDStr()); 54 | } else { 55 | //throw error 56 | throw new IllegalStateException("Invalid json header format, pass in json root is not object."); 57 | } 58 | } catch (IOException e) { 59 | throw new IllegalStateException("Could not parse json header."); 60 | } 61 | } else { 62 | //throw error 63 | throw new IllegalStateException("Invalid json header format. Json header not specified."); 64 | } 65 | try { 66 | this.jsonHeaderBytes = SystemUtil.getObjectMapper().writeValueAsBytes(jsonHeaderExt); 67 | } catch (JsonProcessingException e) { 68 | throw new IllegalStateException("Fail to convert object node to string."); 69 | } 70 | } 71 | 72 | @Override 73 | public byte[] getBytes() { 74 | if(null == bytes){ 75 | byte[] header = this.getHeader().getBytes(NSQCommand.DEFAULT_CHARSET); 76 | byte[] jsonHeaderBytes = this.jsonHeaderBytes; 77 | byte[] body = this.getBody().get(0); 78 | ByteBuffer buf = ByteBuffer.allocate(header.length + 4/*total length*/ + 2/*json header length*/ + jsonHeaderBytes.length + body.length); 79 | 80 | buf.put(header) 81 | .putInt(2 + jsonHeaderBytes.length + body.length) 82 | .putShort((short) jsonHeaderBytes.length) 83 | .put(jsonHeaderBytes) 84 | .put(body); 85 | bytes = buf.array(); 86 | } 87 | return bytes; 88 | } 89 | 90 | @Override 91 | public String getHeader() { 92 | return String.format("PUB_EXT %s%s\n", topic.getTopicText(), this.getPartitionStr()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/util/ConcurrentSortedSet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.youzan.util; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.Collection; 10 | import java.util.SortedSet; 11 | import java.util.TreeSet; 12 | import java.util.concurrent.locks.ReentrantReadWriteLock; 13 | import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; 14 | import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; 15 | 16 | /** 17 | * Blocking when try-lock. It is for the small size collection. It consists of 18 | * one {@link SortedSet} 19 | * 20 | * @author zhaoxi (linzuxiong) 21 | * 22 | * 23 | */ 24 | @ThreadSafe 25 | public class ConcurrentSortedSet implements java.io.Serializable { 26 | private static final long serialVersionUID = -4747846630389873940L; 27 | private static final Logger logger = LoggerFactory.getLogger(ConcurrentSortedSet.class); 28 | 29 | private SortedSet set = null; 30 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 31 | private final ReadLock r = lock.readLock(); 32 | private final WriteLock w = lock.writeLock(); 33 | 34 | public ConcurrentSortedSet() { 35 | set = new TreeSet<>(); 36 | } 37 | 38 | public void clear() { 39 | w.lock(); 40 | try { 41 | set.clear(); 42 | } finally { 43 | w.unlock(); 44 | } 45 | } 46 | 47 | public int size() { 48 | r.lock(); 49 | try { 50 | return set.size(); 51 | } finally { 52 | r.unlock(); 53 | } 54 | } 55 | 56 | public boolean addAll(Collection c) { 57 | if (c == null || c.isEmpty()) { 58 | return true; 59 | } 60 | w.lock(); 61 | try { 62 | return set.addAll(c); 63 | } finally { 64 | w.unlock(); 65 | } 66 | } 67 | 68 | /** 69 | * Replace inner-data into the specified target. 70 | * 71 | * @param target 72 | * the new data 73 | */ 74 | public void swap(SortedSet target) { 75 | if (target == null) { 76 | throw new IllegalArgumentException("Your input is null pointer!"); 77 | } 78 | w.lock(); 79 | final SortedSet tmp = set; 80 | try { 81 | set = target; 82 | } finally { 83 | w.unlock(); 84 | } 85 | tmp.clear(); 86 | } 87 | 88 | public void add(T e) { 89 | if (e == null) { 90 | return; 91 | } 92 | w.lock(); 93 | try { 94 | set.add(e); 95 | } finally { 96 | w.unlock(); 97 | } 98 | } 99 | 100 | /** 101 | * @param e 102 | * the element that need to be removed 103 | */ 104 | public void remove(T e) { 105 | w.lock(); 106 | try { 107 | set.remove(e); 108 | } finally { 109 | w.unlock(); 110 | } 111 | } 112 | 113 | public boolean isEmpty() { 114 | r.lock(); 115 | try { 116 | return set.isEmpty(); 117 | } finally { 118 | r.unlock(); 119 | } 120 | } 121 | 122 | public T[] newArray(T[] a) { 123 | r.lock(); 124 | try { 125 | return set.toArray(a); 126 | } finally { 127 | r.unlock(); 128 | } 129 | } 130 | 131 | /** 132 | * Never return null. 133 | * 134 | * @return the new {@link SortedSet} 135 | */ 136 | public SortedSet newSortedSet() { 137 | r.lock(); 138 | try { 139 | return new TreeSet<>(set); 140 | } finally { 141 | r.unlock(); 142 | } 143 | } 144 | 145 | @Override 146 | public String toString() { 147 | r.lock(); 148 | try { 149 | return set.toString(); 150 | } finally { 151 | r.unlock(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/youzan/nsq/client/core/pool/consumer/FixedPool.java: -------------------------------------------------------------------------------- 1 | package com.youzan.nsq.client.core.pool.consumer; 2 | 3 | import com.youzan.nsq.client.core.Client; 4 | import com.youzan.nsq.client.core.NSQConnection; 5 | import com.youzan.nsq.client.core.NSQConnectionImpl; 6 | import com.youzan.nsq.client.entity.Address; 7 | import com.youzan.nsq.client.entity.NSQConfig; 8 | import com.youzan.nsq.client.exception.NSQNoConnectionException; 9 | import com.youzan.nsq.client.network.netty.NSQClientInitializer; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.ChannelOption; 14 | import io.netty.channel.EventLoopGroup; 15 | import io.netty.channel.nio.NioEventLoopGroup; 16 | import io.netty.channel.socket.nio.NioSocketChannel; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | /** 26 | * @deprecated Deprecated as one {@link Address} does not more one topic anymore, after partition is introduced. 27 | * @author zhaoxi (linzuxiong) 28 | */ 29 | public class FixedPool { 30 | private static final Logger logger = LoggerFactory.getLogger(FixedPool.class); 31 | 32 | private final AtomicInteger connectionIDGenerator = new AtomicInteger(0); 33 | private final Bootstrap bootstrap; 34 | private final EventLoopGroup eventLoopGroup; 35 | private final int size; 36 | 37 | 38 | private final NSQConfig config; 39 | private final Address address; 40 | private final Client client; 41 | private final List connections; 42 | 43 | public FixedPool(Address address, int size, Client client, NSQConfig config) { 44 | this.address = address; 45 | this.size = size; 46 | this.client = client; 47 | this.config = config; 48 | this.connections = new ArrayList<>(size); 49 | this.bootstrap = new Bootstrap(); 50 | this.eventLoopGroup = new NioEventLoopGroup(config.getThreadPoolSize4IO()); 51 | } 52 | 53 | 54 | public void prepare(boolean isOrdered) throws NSQNoConnectionException { 55 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 56 | bootstrap.option(ChannelOption.TCP_NODELAY, true); 57 | bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeoutInMillisecond()); 58 | bootstrap.group(eventLoopGroup); 59 | bootstrap.channel(NioSocketChannel.class); 60 | bootstrap.handler(new NSQClientInitializer()); 61 | for (int i = 0; i < size; i++) { 62 | final ChannelFuture future = bootstrap.connect(address.getHost(), address.getPort()); 63 | // Wait until the connection attempt succeeds or fails. 64 | if (!future.awaitUninterruptibly(config.getConnectTimeoutInMillisecond(), TimeUnit.MILLISECONDS)) { 65 | throw new NSQNoConnectionException(future.cause()); 66 | } 67 | final Channel channel = future.channel(); 68 | if (!future.isSuccess()) { 69 | if (channel != null) { 70 | channel.close(); 71 | } 72 | throw new NSQNoConnectionException("Connect " + address + " is wrong.", future.cause()); 73 | } 74 | 75 | final NSQConnection conn = new NSQConnectionImpl(connectionIDGenerator.incrementAndGet(), address, channel, 76 | config); 77 | // Netty async+sync programming 78 | channel.attr(NSQConnection.STATE).set(conn); 79 | channel.attr(Client.STATE).set(client); 80 | channel.attr(Client.ORDERED).set(isOrdered); 81 | connections.add(conn); 82 | } 83 | logger.debug("Having created {} connections for {}", size, address); 84 | } 85 | 86 | public List getConnections() { 87 | return connections; 88 | } 89 | 90 | public void close() { 91 | connections.clear(); 92 | if (!eventLoopGroup.isShuttingDown()) { 93 | eventLoopGroup.shutdownGracefully(1, 2, TimeUnit.SECONDS); 94 | } 95 | } 96 | } 97 | --------------------------------------------------------------------------------