├── .gitignore ├── rank.png ├── design.docx ├── README.md ├── src └── main │ ├── java │ └── io │ │ └── openmessaging │ │ ├── demo │ │ ├── ClientOMSException.java │ │ ├── DefaultMessageFactory.java │ │ ├── MessageStore.java │ │ ├── MyLogger.java │ │ ├── MyUtil.java │ │ ├── DefaultKeyValue.java │ │ ├── Constant.java │ │ ├── DefaultBytesMessage.java │ │ ├── FileWriteCache.java │ │ ├── DefaultPullConsumer.java │ │ ├── MessageLoad.java │ │ ├── FileReadCache.java │ │ ├── DefaultProducer.java │ │ └── DemoTester.java │ │ ├── Filters.java │ │ ├── InvokeContext.java │ │ ├── exception │ │ ├── OMSException.java │ │ ├── OMSRuntimeException.java │ │ ├── OMSNotSupportedException.java │ │ └── OMSResourceNotExistException.java │ │ ├── OnMessageContext.java │ │ ├── BatchToPartition.java │ │ ├── filter │ │ └── FilterChain.java │ │ ├── ServiceProperties.java │ │ ├── MessagingAccessPointManager.java │ │ ├── PromiseListener.java │ │ ├── ServiceLoadBalance.java │ │ ├── MessageListener.java │ │ ├── observer │ │ └── Observer.java │ │ ├── BytesMessage.java │ │ ├── ServiceLifecycle.java │ │ ├── internal │ │ ├── DefaultKeyValue.java │ │ └── MessagingAccessPointAdapter.java │ │ ├── PartitionConsumer.java │ │ ├── MessageFactory.java │ │ ├── PushConsumer.java │ │ ├── PartitionIterator.java │ │ ├── PullConsumer.java │ │ ├── ServiceEndPoint.java │ │ ├── Promise.java │ │ ├── Message.java │ │ ├── MessagingAccessPoint.java │ │ ├── KeyValue.java │ │ ├── Producer.java │ │ ├── MessageHeader.java │ │ └── ResourceManager.java │ └── resources │ └── package.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | *.iml 4 | .project 5 | .settings/ 6 | -------------------------------------------------------------------------------- /rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whutjs/MessageSystem/HEAD/rank.png -------------------------------------------------------------------------------- /design.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whutjs/MessageSystem/HEAD/design.docx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 第三届阿里巴巴中间件性能挑战赛初赛程序 2 | 3 | ### 进程内消息引擎 4 | 5 | 初赛最终排名:6/1959 6 | 7 | 8 | [初赛题目详细链接](https://code.aliyun.com/middlewarerace2017/open-messaging-demo?spm=5176.100068.555.1.Kz5MUL) 9 | 10 | 11 | ![Rank](rank.png) 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/ClientOMSException.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.exception.OMSException; 4 | import io.openmessaging.exception.OMSResourceNotExistException; 5 | import io.openmessaging.exception.OMSRuntimeException; 6 | 7 | public class ClientOMSException extends OMSRuntimeException { 8 | 9 | public String message; 10 | public ClientOMSException(String message) { 11 | this.message = message; 12 | } 13 | public ClientOMSException(String message, Throwable throwable) { 14 | this.initCause(throwable); 15 | this.message = message; 16 | } 17 | 18 | @Override 19 | public String getMessage() { 20 | return message; 21 | } 22 | 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/package.xml: -------------------------------------------------------------------------------- 1 | 5 | package 6 | 7 | dir 8 | 9 | true 10 | 11 | 12 | src/main/resources 13 | conf 14 | false 15 | 16 | 17 | 18 | 19 | lib 20 | runtime 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DefaultMessageFactory.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.BytesMessage; 4 | import io.openmessaging.MessageFactory; 5 | import io.openmessaging.MessageHeader; 6 | 7 | public class DefaultMessageFactory implements MessageFactory { 8 | 9 | @Override public BytesMessage createBytesMessageToTopic(String topic, byte[] body) { 10 | DefaultBytesMessage defaultBytesMessage = new DefaultBytesMessage(body); 11 | defaultBytesMessage.putHeaders(MessageHeader.TOPIC, topic); 12 | return defaultBytesMessage; 13 | } 14 | 15 | @Override public BytesMessage createBytesMessageToQueue(String queue, byte[] body) { 16 | DefaultBytesMessage defaultBytesMessage = new DefaultBytesMessage(body); 17 | defaultBytesMessage.putHeaders(MessageHeader.QUEUE, queue); 18 | return defaultBytesMessage; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/MessageStore.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.Message; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.BufferedWriter; 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.FileWriter; 10 | import java.io.IOException; 11 | import java.io.OutputStreamWriter; 12 | import java.nio.ByteBuffer; 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.nio.file.StandardOpenOption; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.ConcurrentLinkedQueue; 23 | import java.util.concurrent.ExecutorService; 24 | import java.util.concurrent.Executors; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.locks.ReadWriteLock; 27 | import java.util.concurrent.locks.ReentrantReadWriteLock; 28 | 29 | /* 30 | * 用来管理消息缓存、落盘 31 | */ 32 | public class MessageStore { 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/Filters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public interface Filters { 27 | Filters addFilter(String sql); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/InvokeContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public interface InvokeContext { 27 | KeyValue properties(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/exception/OMSException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.exception; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public class OMSException extends Exception { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/exception/OMSRuntimeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.exception; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public class OMSRuntimeException extends RuntimeException { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/exception/OMSNotSupportedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.exception; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public class OMSNotSupportedException extends OMSRuntimeException { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/OnMessageContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public interface OnMessageContext { 27 | KeyValue properties(); 28 | 29 | void ack(); 30 | 31 | void ack(final KeyValue properties); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/BatchToPartition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | */ 23 | public interface BatchToPartition { 24 | void send(BytesMessage message); 25 | 26 | void send(BytesMessage message, KeyValue properties); 27 | 28 | void commit(); 29 | 30 | void rollback(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/filter/FilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.filter; 19 | 20 | import io.openmessaging.Filters; 21 | 22 | /** 23 | * @author vintagewang@apache.org 24 | * 25 | * @version OMS 1.0 26 | * @since OMS 1.0 27 | */ 28 | public interface FilterChain { 29 | FilterChain addFirst(Filters filters); 30 | 31 | FilterChain addLast(Filters filters); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/exception/OMSResourceNotExistException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.exception; 19 | 20 | /** 21 | * The {@code OMSResourceNotExistException} occurs if requested resource is not exist. 22 | * 23 | * @author vintagewang@apache.org 24 | * 25 | * @version OMS 1.0 26 | * @since OMS 1.0 27 | */ 28 | public class OMSResourceNotExistException extends OMSException { 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/ServiceProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache. 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public interface ServiceProperties { 27 | String id(); 28 | 29 | void id(String id); 30 | 31 | String relayAddress(); 32 | 33 | void relayAddress(String address); 34 | 35 | String providerId(); 36 | 37 | void providerId(String id); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/MessagingAccessPointManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * 23 | * @version OMS 1.0 24 | * @since OMS 1.0 25 | */ 26 | public class MessagingAccessPointManager { 27 | public static MessagingAccessPoint getMessagingAccessPoint(String url) { 28 | return getMessagingAccessPoint(url, null); 29 | } 30 | 31 | public static MessagingAccessPoint getMessagingAccessPoint(String url, KeyValue properties) { 32 | return null; 33 | } 34 | 35 | public static KeyValue buildKeyValue() { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/PromiseListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * @author vintagewang@apache.org 22 | * @author yukon@apache.org 23 | * 24 | * @version OMS 1.0 25 | * @since OMS 1.0 26 | */ 27 | public interface PromiseListener { 28 | /** 29 | * Invoked when the operation associated with the {@code Promise} has been completed successfully. 30 | * 31 | * @param promise the source {@code Promise} which called this callback 32 | */ 33 | void operationComplete(Promise promise); 34 | 35 | /** 36 | * Invoked when the operation associated with the {@code Promise} has been completed unsuccessfully. 37 | * 38 | * @param promise the source {@code Promise} which called this callback 39 | */ 40 | void operationFailed(Promise promise); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/ServiceLoadBalance.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import java.util.Set; 21 | 22 | /** 23 | * @author vintagewang@apache.org 24 | * 25 | * @version OMS 1.0 26 | * @since OMS 1.0 27 | */ 28 | public interface ServiceLoadBalance { 29 | /** 30 | * Select a collection of eligible providerServicePoint object from the the list of providerServicePoint provided 31 | * According to different selection strategies to select providerServicePoint that satisfied with application needs, 32 | * such as RoundRobin or Random etc. 33 | * 34 | * @param servicePropertiesList providerServicePoint to choose from. 35 | * @return a collection of eligible providerServicePoint object 36 | */ 37 | Set select(Set servicePropertiesList); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/MessageListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * The message listener interface. A message listener must implement this {@code MessageListener} interface and register 22 | * itself to a consumer instance to asynchronously receive messages. 23 | * 24 | * @author vintagewang@apache.org 25 | * @author yukon@apache.org 26 | * 27 | * @version OMS 1.0 28 | * @since OMS 1.0 29 | */ 30 | public interface MessageListener { 31 | /** 32 | * Callback method to receive incoming messages. 33 | *

34 | * A message listener should handle different types of {@code Message}. 35 | * 36 | * @param message the received Message object 37 | * @param context the context delivered to the consume thread 38 | */ 39 | void onMessage(Message message, OnMessageContext context); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/observer/Observer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.observer; 19 | 20 | import java.util.Observable; 21 | 22 | /** 23 | * @author vintagewang@apache.org 24 | * 25 | * @version OMS 1.0 26 | * @since OMS 1.0 27 | */ 28 | public interface Observer { 29 | /** 30 | * Notifies the Observer that the {@link Observable} has finished sending push-based notifications. 31 | *

32 | * The {@link Observable} will not call this method if it calls {@link #onError}. 33 | */ 34 | void onCompleted(); 35 | 36 | /** 37 | * Notifies the Observer that the {@link Observable} has experienced an error condition. 38 | *

39 | * If the {@link Observable} calls this method, it will not thereafter call 40 | * {@link #onCompleted}. 41 | * 42 | * @param e the exception encountered by the Observable 43 | */ 44 | void onError(Throwable e); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/BytesMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * The {@code BytesMessage} contains a stream of uninterpreted bytes. It inherits from the {@code Message} interface and 22 | * adds a bytes message body. 23 | *

24 | * The {@code BytesMessage} doesn't know the format or encoding Rules of the body, the provider and consumer decide the 25 | * interpretation of the bytes body. 26 | * 27 | * @author vintagewang@apache.org 28 | * @author yukon@apache.org 29 | * 30 | * @version OMS 1.0 31 | * @since OMS 1.0 32 | */ 33 | public interface BytesMessage extends Message { 34 | /** 35 | * Returns the bytes message body. 36 | * 37 | * @return the bytes message body 38 | */ 39 | byte[] getBody(); 40 | 41 | /** 42 | * Sets the bytes message body. 43 | * 44 | * @param body the message body to be set 45 | */ 46 | BytesMessage setBody(final byte[] body); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/MyLogger.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.nio.file.StandardOpenOption; 10 | 11 | public final class MyLogger { 12 | public static final String ERROR_TAG = "ERROR"; 13 | public static final String INFO_TAG = "INFO"; 14 | 15 | public static final boolean ENABLE = false; 16 | public static final boolean LOCAL_RUN = false; 17 | private static final String fileName = "E:\\Contest\\阿里中间件2017\\log.txt"; 18 | // private static final String fileName = "/home/sfc/jenson/alibaba/2017/log.txt"; 19 | 20 | static{ 21 | if(ENABLE && LOCAL_RUN) { 22 | File file = new File(fileName); 23 | if(file.exists()) { 24 | file.delete(); 25 | } 26 | try { 27 | file.createNewFile(); 28 | } catch (IOException e) { 29 | // TODO Auto-generated catch block 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | public static synchronized void log(final String tag, final String msg) { 35 | if(!ENABLE) return; 36 | if(!LOCAL_RUN) { 37 | System.out.println(tag+" :"+msg); 38 | return; 39 | } 40 | try(BufferedWriter writer = 41 | Files.newBufferedWriter(Paths.get(fileName), 42 | StandardCharsets.UTF_8, StandardOpenOption.APPEND)) { 43 | writer.write(tag+" : " + msg); 44 | writer.newLine(); 45 | //writer.write(tag + msg); 46 | } catch (IOException e) { 47 | // TODO Auto-generated catch block 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | public static void listAllFiles(final String storePath) { 53 | double totalSizeInMB = 0; 54 | final double div = 1024*1024; 55 | File dir = new File(storePath); 56 | for(final File file : dir.listFiles()) { 57 | double size = file.length() / div; 58 | System.out.println("File:"+file.getName()+" Size:" + size); 59 | totalSizeInMB += size; 60 | } 61 | System.out.println("totalSizeInMB:" + totalSizeInMB); 62 | 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/MyUtil.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileReader; 7 | import java.io.IOException; 8 | import java.nio.file.Paths; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | // 5/27 增加。用于计算topic queue的hash值 13 | public class MyUtil { 14 | // 保存topic字符串-> topic编号的映射 15 | private static ConcurrentHashMap topicNo = new ConcurrentHashMap<>(); 16 | // 保存编号到字符串的映射,在消费时需要 17 | private static ConcurrentHashMap topicNo2Name = new ConcurrentHashMap<>(); 18 | private static AtomicInteger globalTopicNo = new AtomicInteger(0); 19 | 20 | public static ConcurrentHashMap getTopicNoMap() { 21 | return topicNo; 22 | } 23 | // 获得某个topic的编号 24 | public static Integer getTopicNo(final String name) { 25 | if(name == null || name.length() == 0) { 26 | return -1; 27 | } 28 | if(!topicNo.containsKey(name)) { 29 | synchronized (topicNo) { 30 | if(!topicNo.containsKey(name)) { 31 | int gno = globalTopicNo.getAndIncrement(); 32 | topicNo.put(name, gno); 33 | } 34 | } 35 | } 36 | return topicNo.get(name); 37 | } 38 | 39 | public static String getTopicName(final Integer tpno) { 40 | return topicNo2Name.get(tpno); 41 | } 42 | // 从文件中重新构造topic和no 43 | public static void constructMetaData(final String storePath) { 44 | File topicFile = Paths.get(storePath, Constant.TOPIC_NO_FILE).toFile(); 45 | try(BufferedReader reader = new BufferedReader(new FileReader(topicFile))) { 46 | String line = null; 47 | while((line = reader.readLine()) != null) { 48 | int idx = line.indexOf(':'); 49 | String name = line.substring(0, idx); 50 | String no = line.substring(idx+1); 51 | Integer tpn = Integer.parseInt(no); 52 | topicNo.put(name, tpn); 53 | topicNo2Name.put(tpn, name); 54 | } 55 | } catch (FileNotFoundException e) { 56 | e.printStackTrace(); 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/ServiceLifecycle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import javax.annotation.PostConstruct; 21 | import javax.annotation.PreDestroy; 22 | 23 | /** 24 | * The {@code ServiceLifecycle} defines a lifecycle interface for a OMS related service endpoint, like {@link Producer}, 25 | * {@link PushConsumer}, and so on. 26 | *

27 | * If the service endpoint class implements the {@code ServiceLifecycle} interface, most of the containers can manage 28 | * the lifecycle of the corresponding service endpoint objects easily. 29 | *

30 | * Any service endpoint should support repeated restart if it implements the {@code ServiceLifecycle} interface. 31 | * 32 | * @author vintagewang@apache.org 33 | * @author yukon@apache.org 34 | * 35 | * @version OMS 1.0 36 | * @since OMS 1.0 37 | */ 38 | public interface ServiceLifecycle { 39 | /** 40 | * Used for start or initialization of a service endpoint. A service endpoint instance will be in a ready state 41 | * after this method has been completed. 42 | */ 43 | @PostConstruct 44 | void start(); 45 | 46 | /** 47 | * Notify a service instance of the end of its life cycle. Once this method completes, the service endpoint could be 48 | * destroyed and eligible for garbage collection. 49 | */ 50 | @PreDestroy 51 | void shutdown(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/internal/DefaultKeyValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.internal; 19 | 20 | import io.openmessaging.KeyValue; 21 | import java.util.Set; 22 | 23 | /** 24 | * WARN: The current interface prohibits direct access by the end user 25 | * 26 | * @version OMS 1.0 27 | * @since OMS 1.0 28 | */ 29 | public class DefaultKeyValue implements KeyValue { 30 | @Override 31 | public KeyValue put(String key, int value) { 32 | return null; 33 | } 34 | 35 | @Override 36 | public KeyValue put(String key, long value) { 37 | return null; 38 | } 39 | 40 | @Override 41 | public KeyValue put(String key, double value) { 42 | return null; 43 | } 44 | 45 | @Override 46 | public KeyValue put(String key, String value) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public int getInt(String key) { 52 | return 0; 53 | } 54 | 55 | @Override 56 | public long getLong(String key) { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public double getDouble(String key) { 62 | return 0; 63 | } 64 | 65 | @Override 66 | public String getString(String key) { 67 | return null; 68 | } 69 | 70 | @Override 71 | public Set keySet() { 72 | return null; 73 | } 74 | 75 | @Override 76 | public boolean containsKey(String key) { 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/PartitionConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * A {@code Queue} may have multiple partitions, each partition supports streaming consumption. 24 | *

25 | * A {@code PartitionConsumer} object supports consume messages from all the partitions of a 26 | * specified queue by streaming way. 27 | * 28 | * @author vintagewang@apache.org 29 | * @author yukon@apache.org 30 | * @version OMS 1.0 31 | * @see MessagingAccessPoint#createPartitionConsumer(String) 32 | * @since OMS 1.0 33 | */ 34 | public interface PartitionConsumer { 35 | /** 36 | * Returns the properties of this {@code PartitionConsumer} instance. 37 | * Changes to the return {@code KeyValue} are not reflected in physical {@code PartitionConsumer}, 38 | * and use {@link ResourceManager#setConsumerProperties(String, KeyValue)} to modify. 39 | * 40 | * @return the properties 41 | */ 42 | KeyValue properties(); 43 | 44 | /** 45 | * Fetches the partition list of the specified queue, which related to this {@code PartitionConsumer} 46 | * 47 | * @return the partition list of queue 48 | */ 49 | List partitionList(); 50 | 51 | /** 52 | * Creates a {@code PartitionIterator} from the specified partition. 53 | *

54 | * If the specified partition doesn't exist, create it automatically. 55 | * 56 | * @param partitionName the specified partition name 57 | * @return the created {@code PartitionIterator} 58 | */ 59 | PartitionIterator partitionIterator(String partitionName); 60 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/MessageFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSRuntimeException; 21 | 22 | /** 23 | * A factory interface for creating {@code Message} objects. 24 | * 25 | * @author vintagewang@apache.org 26 | * @author yukon@apache.org 27 | */ 28 | public interface MessageFactory { 29 | /** 30 | * Creates a {@code BytesMessage} object. A {@code BytesMessage} object is used to send a message containing a 31 | * stream of uninterpreted bytes. 32 | *

33 | * The returned {@code BytesMessage} object only can be sent to the specified topic. 34 | * 35 | * @param topic the target topic to send 36 | * @param body the body data for a message 37 | * @return the created {@code BytesMessage} object 38 | * @throws OMSRuntimeException if the OMS provider fails to create this message due to some internal error. 39 | */ 40 | BytesMessage createBytesMessageToTopic(String topic, byte[] body); 41 | 42 | /** 43 | * Creates a {@code BytesMessage} object. A {@code BytesMessage} object is used to send a message containing a 44 | * stream of uninterpreted bytes. 45 | *

46 | * The returned {@code BytesMessage} object only can be sent to the specified queue. 47 | * 48 | * @param queue the target queue to send 49 | * @param body the body data for a message 50 | * @return the created {@code BytesMessage} object 51 | * @throws OMSRuntimeException if the OMS provider fails to create this message due to some internal error. 52 | */ 53 | BytesMessage createBytesMessageToQueue(String queue, byte[] body); 54 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.openmessaging 8 | open-messaging-demo 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | junit 14 | junit 15 | 4.12 16 | 17 | 18 | 19 | 20 | 33 | 34 | maven-compiler-plugin 35 | 2.3.2 36 | 37 | 1.8 38 | 1.8 39 | utf-8 40 | 41 | 42 | 43 | maven-assembly-plugin 44 | 2.2.1 45 | 46 | 47 | org.apache.maven 48 | maven-core 49 | 2.2.1 50 | 51 | 52 | 53 | OpenMessagingDemo 54 | false 55 | 56 | src/main/resources/package.xml 57 | 58 | 59 | 60 | 61 | make-assembly 62 | package 63 | 64 | single 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DefaultKeyValue.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.KeyValue; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | /* 9 | * 编码方式: 10 | * 5/23 更新:value长度变长表示,key长度不会超过1byte 11 | * KEY_VALUE_BEGIN (1byte)+ 12 | * KEY_LEN( 1byte) + key + 13 | * V_INT_TYPE|V_LONG_TYPE|V_DOUBLE_TYPE| 14 | * (V_STRING_TYPE + LEN_TYPE + VALUE_LEN(byte|short|int)) + 15 | * value 16 | */ 17 | /** 18 | * 6/2 更新:取消KEY_VALUE_BEGIN头部 19 | * KEY_LEN( 1byte) + key + 20 | * (V_INT_TYPE|V_LONG_TYPE|V_DOUBLE_TYPE| 取消,全部都是字符串类型) 21 | * (V_STRING_TYPE + LEN_TYPE + VALUE_LEN(byte|short|int)) + 22 | * value 23 | */ 24 | public class DefaultKeyValue implements KeyValue { 25 | 26 | private final Map kvs = new HashMap<>(); 27 | @Override 28 | public KeyValue put(String key, int value) { 29 | kvs.put(key, value); 30 | return this; 31 | } 32 | 33 | @Override 34 | public KeyValue put(String key, long value) { 35 | kvs.put(key, value); 36 | return this; 37 | } 38 | 39 | @Override 40 | public KeyValue put(String key, double value) { 41 | kvs.put(key, value); 42 | return this; 43 | } 44 | 45 | @Override 46 | public KeyValue put(String key, String value) { 47 | kvs.put(key, value); 48 | return this; 49 | } 50 | 51 | @Override 52 | public int getInt(String key) { 53 | return (Integer)kvs.getOrDefault(key, 0); 54 | } 55 | 56 | @Override 57 | public long getLong(String key) { 58 | return (Long)kvs.getOrDefault(key, 0L); 59 | } 60 | 61 | @Override 62 | public double getDouble(String key) { 63 | return (Double)kvs.getOrDefault(key, 0.0d); 64 | } 65 | 66 | @Override 67 | public String getString(String key) { 68 | return (String)kvs.getOrDefault(key, null); 69 | } 70 | 71 | /** 72 | * 用于不知道key对应于什么类型的情况 73 | * @param key 74 | * @return 75 | */ 76 | public Object get(final String key) { 77 | return kvs.getOrDefault(key, null); 78 | } 79 | 80 | @Override 81 | public Set keySet() { 82 | return kvs.keySet(); 83 | } 84 | 85 | @Override 86 | public boolean containsKey(String key) { 87 | return kvs.containsKey(key); 88 | } 89 | 90 | public String toString() { 91 | StringBuilder sb = new StringBuilder(); 92 | sb.append('['); 93 | for (Map.Entry entry : kvs.entrySet()) 94 | { 95 | String key = entry.getKey(); 96 | Object value = entry.getValue(); 97 | sb.append(key).append(':').append(value.toString()).append(", "); 98 | 99 | } 100 | sb.append(']'); 101 | return sb.toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/Constant.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | public class Constant { 4 | /** 6/2 改消息序列化方式:有些固定出现的字符串可以hardcode */ 5 | public static final String TOPIC_KEY = "Topic"; 6 | public static final String QUEUE_KEY = "Queue"; 7 | public static final String MSGID_KEY = "MessageId"; 8 | public static final String PRO_OFFSET_KEY = "PRO_OFFSET"; 9 | public static final byte MSGID_KEY_BYTE = -2; 10 | public static final byte PRO_OFFSET_KEY_BYTE = -1; 11 | 12 | 13 | // 单条消息的最大大小256KB 14 | // 4/24 Update:官方说不会超过128 byte 15 | // 5/18 更新:又说最大可能有100KB 16 | /** 6/3更新:貌似最大不会超过32KB */ 17 | public static final int MAX_MSG_SIZE = (32*1024); 18 | 19 | // 表示对topic queue分成多少个组 20 | public static final int GROUP_NUM = 10; 21 | 22 | /* 以下用于MSG体中区分各个部分 */ 23 | // 表示接下来的MSG_LEN是byte类型 24 | public static final byte MSG_LEN_BYTE = 2; 25 | // 表示接下来的MSG_LEN是short类型 26 | public static final byte MSG_LEN_SHORT = 3; 27 | // 表示接下来的MSG_LEN是int类型 28 | public static final byte MSG_LEN_INT = 4; 29 | 30 | // 表示接下来的数据是消息头,msg_header 31 | public static final byte MSG_HEADER_BEGIN = 17; 32 | // 表示消息自定义的property部分的开始 33 | public static final byte MSG_PROPERTY_BEGIN = 5; 34 | // 表示消息body的开始 35 | public static final byte MSG_BODY_BEGIN = -3; 36 | 37 | // 表示接下来的BODY_LEN是byte类型 38 | public static final byte MSG_BODY_LEN_BYTE = 2; 39 | // 表示接下来的BODY_LEN是short类型 40 | public static final byte MSG_BODY_LEN_SHORT = 3; 41 | // 表示接下来的BODY_LEN是int类型 42 | public static final byte MSG_BODY_LEN_INT = 4; 43 | 44 | 45 | /* 以下用于KEY-VALUE编码*/ 46 | 47 | // int类型 48 | public static final byte V_INT_TYPE = 2; 49 | // long类型 50 | public static final byte V_LONG_TYPE = 3; 51 | // double 52 | public static final byte V_DOUBLE_TYPE = 4; 53 | // String 54 | public static final byte V_STRING_TYPE = 23; 55 | // 表示一个KEY_VALUE的开始 56 | public static final byte KEY_VALUE_BEGIN = 24; 57 | // 表示一个KEY_VALUE的结束 58 | public static final byte KEY_VALUE_END = 14; 59 | 60 | /* 表示大小 */ 61 | public static final int SIZE_512KB = (1<<19); 62 | public static final int SIZE_1MB = (1<<20); 63 | public static final int SIZE_2MB = (1<<21); 64 | public static final int SIZE_4MB = (1<<22); 65 | public static final int SIZE_8MB = (1<<23); 66 | public static final int SIZE_16MB = (1<<24); 67 | public static final int SIZE_18MB = (18*1024*1024); 68 | public static final int SIZE_20MB = (20*1024*1024); 69 | public static final int SIZE_50MB = (50*1024*1024); 70 | public static final int SIZE_200MB = (180*1024*1024); 71 | 72 | /* 存储数据的文件名 */ 73 | public static final String STORE_FILE_PREFIX = "jenson_msg"; 74 | 75 | public static final String TOPIC_NO_FILE = "metadata"; 76 | 77 | /** 表示消息压缩的chunk的开始 */ 78 | public static final byte COMP_CHUNK_BEGIN = 66; 79 | // chunk表示法: 80 | // COMP_CHUNK_BEGIN + chunk_len_type(MSG_LEN_BYTE|MSG_LEN_SHORT|MSG_LEN_INT) 81 | // + chunk_len (byte | short | int) + CHUNK 82 | // 也就是每个chunk至少有额外3byte 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/PushConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSResourceNotExistException; 21 | import io.openmessaging.exception.OMSRuntimeException; 22 | 23 | /** 24 | * A {@code PushConsumer} object to receive messages from multiple queue, these messages are pushed from 25 | * MOM server to {@code PushConsumer} client. 26 | * 27 | * @author vintagewang@apache.org 28 | * @author yukon@apache.org 29 | * 30 | * @version OMS 1.0 31 | * @since OMS 1.0 32 | * @see MessagingAccessPoint#createPushConsumer() 33 | */ 34 | public interface PushConsumer extends ServiceLifecycle { 35 | /** 36 | * Resumes the {@code PushConsumer} after a suspend. 37 | *

38 | * This method resumes the {@code PushConsumer} instance after it was suspended. 39 | * The instance will not receive new messages between the suspend and resume calls. 40 | * 41 | * @throws OMSRuntimeException if the instance has not been suspended. 42 | * @see PushConsumer#suspend() 43 | */ 44 | void resume(); 45 | 46 | /** 47 | * Suspends the {@code PushConsumer} for later resumption. 48 | *

49 | * This method suspends the {@code PushConsumer} instance until it is resumed. 50 | * The instance will not receive new messages between the suspend and resume calls. 51 | * 52 | * @throws OMSRuntimeException if the instance is not currently running. 53 | * @see PushConsumer#resume() 54 | */ 55 | void suspend(); 56 | 57 | /** 58 | * This method is used to find out whether the {@code PushConsumer} is suspended. 59 | * 60 | * @return true if this {@code PushConsumer} is suspended, false otherwise 61 | */ 62 | boolean isSuspended(); 63 | 64 | /** 65 | * Returns the properties of this {@code PushConsumer} instance. 66 | * Changes to the return {@code KeyValue} are not reflected in physical {@code PushConsumer}, 67 | * and use {@link ResourceManager#setConsumerProperties(String, KeyValue)} to modify. 68 | * 69 | * @return the properties 70 | */ 71 | KeyValue properties(); 72 | 73 | /** 74 | * Attaches the {@code PushConsumer} to a specified queue, with a {@code MessageListener}. 75 | * {@link MessageListener#onMessage(Message, OnMessageContext)} will be called when new 76 | * delivered message is coming. 77 | * 78 | * @param queueName a specified queue 79 | * @param listener a specified listener to receive new message 80 | * 81 | * @throws OMSResourceNotExistException if the specified queue is not exists 82 | */ 83 | PushConsumer attachQueue(String queueName, MessageListener listener) throws OMSResourceNotExistException; 84 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DefaultBytesMessage.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.BytesMessage; 4 | import io.openmessaging.KeyValue; 5 | import io.openmessaging.Message; 6 | 7 | 8 | /** 9 | * 6/2 更新:header部分只存messageId的value,key都不用存;Topic也不用存! 10 | * Property的key:PRO_OFFSET 用1byte表示! 11 | * 一条消息编码为: 12 | * 头部:MSG_HEADER_BEGIN + MSG_ID_LEN(Short)+ MSG_ID 13 | * Property: (KEY_LEN(byte) + key_str + VALUE_LEN (Short) + VALUE_STR) 14 | * .... 15 | * MSG_BODY: MSG_BODY_BEGIN (-3) + MSG_BODY_LEN (Short) + MSG_BODY 16 | */ 17 | public class DefaultBytesMessage implements BytesMessage { 18 | 19 | private KeyValue headers = new DefaultKeyValue(); 20 | private KeyValue properties; 21 | private byte[] body; 22 | // 表示当前消息是不是表示结束的消息 23 | private boolean endMsg; 24 | public void setEndMsg(boolean flag) { 25 | this.endMsg = flag; 26 | } 27 | public boolean getEndMsg() { 28 | return this.endMsg; 29 | } 30 | public DefaultBytesMessage(byte[] body) { 31 | this.body = body; 32 | } 33 | @Override public byte[] getBody() { 34 | return body; 35 | } 36 | 37 | @Override public BytesMessage setBody(byte[] body) { 38 | this.body = body; 39 | return this; 40 | } 41 | 42 | public void setHeader(DefaultKeyValue hkv) { 43 | this.headers = hkv; 44 | } 45 | 46 | public void setProperty(DefaultKeyValue pkv) { 47 | this.properties = pkv; 48 | } 49 | 50 | @Override public KeyValue headers() { 51 | return headers; 52 | } 53 | 54 | @Override public KeyValue properties() { 55 | return properties; 56 | } 57 | 58 | @Override public Message putHeaders(String key, int value) { 59 | headers.put(key, value); 60 | return this; 61 | } 62 | 63 | @Override public Message putHeaders(String key, long value) { 64 | headers.put(key, value); 65 | return this; 66 | } 67 | 68 | @Override public Message putHeaders(String key, double value) { 69 | headers.put(key, value); 70 | return this; 71 | } 72 | 73 | @Override public Message putHeaders(String key, String value) { 74 | headers.put(key, value); 75 | return this; 76 | } 77 | 78 | @Override public Message putProperties(String key, int value) { 79 | if (properties == null) properties = new DefaultKeyValue(); 80 | properties.put(key, value); 81 | return this; 82 | } 83 | 84 | @Override public Message putProperties(String key, long value) { 85 | if (properties == null) properties = new DefaultKeyValue(); 86 | properties.put(key, value); 87 | return this; 88 | } 89 | 90 | @Override public Message putProperties(String key, double value) { 91 | if (properties == null) properties = new DefaultKeyValue(); 92 | properties.put(key, value); 93 | return this; 94 | } 95 | 96 | @Override public Message putProperties(String key, String value) { 97 | if (properties == null) properties = new DefaultKeyValue(); 98 | properties.put(key, value); 99 | return this; 100 | } 101 | 102 | public String toString() { 103 | StringBuilder sb = new StringBuilder(); 104 | sb.append("{ headers= ").append(headers.toString()).append(", "); 105 | if(properties != null) { 106 | sb.append("properties= ").append(properties.toString()).append(", "); 107 | } 108 | sb.append("body= ").append(new String(body)); 109 | sb.append(" }"); 110 | return sb.toString(); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/PartitionIterator.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging; 2 | 3 | import io.openmessaging.exception.OMSRuntimeException; 4 | 5 | /** 6 | * A {@code PartitionIterator} over a partition of queue, created by 7 | * {@link PartitionConsumer#partitionIterator(String)}, supports consume 8 | * messages bilaterally. 9 | * 10 | * @author vintagewang@apache.org 11 | * @author yukon@apache.org 12 | * @version OMS 1.0 13 | * @since OMS 1.0 14 | */ 15 | public interface PartitionIterator { 16 | /** 17 | * Fetches the current offset of this partition iterator. 18 | * 19 | * @return the current offset, return -1 if the iterator is first created. 20 | */ 21 | long currentOffset(); 22 | 23 | /** 24 | * Fetches the first offset of this partition iterator. 25 | * 26 | * @return the first offset, return -1 if the partition has no message. 27 | */ 28 | long firstOffset(); 29 | 30 | /** 31 | * Fetches the last offset of this partition iterator. 32 | * 33 | * @return the last offset, return 0 if the iterator is first created. 34 | */ 35 | long lastOffset(); 36 | 37 | /** 38 | * Moves the current offset to the specified timestamp. 39 | *

40 | * Moves the current offset to the first offset, if the given timestamp 41 | * is earlier than the first message's store timestamp in this partition iterator. 42 | *

43 | * Moves the current offset to the last offset, if the given timestamp 44 | * is later than the last message's store timestamp in this partition iterator. 45 | * 46 | * @param timestamp the specified timestamp 47 | */ 48 | void seekByTime(long timestamp); 49 | 50 | /** 51 | * Moves the current offset to the specified offset. 52 | * 53 | * @param offset the specified offset 54 | */ 55 | void seekByOffset(long offset); 56 | 57 | /** 58 | * Persist this iterator to local or remote server, that depends on specified 59 | * implementation of {@link PartitionConsumer}. 60 | */ 61 | void persist(); 62 | 63 | /** 64 | * Returns {@code true} if this partition iterator has more messages when 65 | * traversing the iterator in the forward direction. 66 | * 67 | * @return {@code true} if the partition iterator has more messages when 68 | * traversing the iterator in the forward direction 69 | */ 70 | boolean hasNext(); 71 | 72 | /** 73 | * Returns the next message in the iteration and advances the offset position. 74 | *

75 | * This method may be called repeatedly to iterate through the iteration, 76 | * or intermixed with calls to {@link #previous} to go back and forth. 77 | * 78 | * @return the next message in the list 79 | * @throws OMSRuntimeException if the iteration has no more message 80 | */ 81 | Message next(); 82 | 83 | /** 84 | * Returns {@code true} if this partition iterator has more messages when 85 | * traversing the iterator in the reverse direction. 86 | * 87 | * @return {@code true} if the partition iterator has more messages when 88 | * traversing the iterator in the reverse direction 89 | */ 90 | boolean hasPrevious(); 91 | 92 | /** 93 | * Returns the previous message in the iteration and moves the offset 94 | * position backwards. 95 | *

96 | * This method may be called repeatedly to iterate through the iteration backwards, 97 | * or intermixed with calls to {@link #next} to go back and forth. 98 | * 99 | * @return the previous message in the list 100 | * @throws OMSRuntimeException if the iteration has no previous message 101 | */ 102 | Message previous(); 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/PullConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSRuntimeException; 21 | import java.util.Collection; 22 | 23 | /** 24 | * A {@code PullConsumer} object can poll messages from the specified queue, 25 | * and supports submit the consume result by acknowledgement. 26 | * 27 | * @author vintagewang@apache.org 28 | * @author yukon@apache.org 29 | * @version OMS 1.0 30 | * @see MessagingAccessPoint#createPullConsumer(String) 31 | * @since OMS 1.0 32 | */ 33 | public interface PullConsumer { 34 | /** 35 | * Returns the properties of this {@code PullConsumer} instance. 36 | * Changes to the return {@code KeyValue} are not reflected in physical {@code PullConsumer}, 37 | * and use {@link ResourceManager#setConsumerProperties(String, KeyValue)} to modify. 38 | * 39 | * @return the properties 40 | */ 41 | KeyValue properties(); 42 | 43 | /** 44 | * Polls the next message produced for this {@code PullConsumer}. 45 | *

46 | * This call blocks indefinitely until a message is produced or until this {@code PullConsumer} is shut down. 47 | * 48 | * @return the next message produced for this {@code PullConsumer}, or null if this {@code PullConsumer} is 49 | * concurrently shut down 50 | * @throws OMSRuntimeException if this {@code PullConsumer} fails to pull the next message due to some internal 51 | * error. 52 | */ 53 | /** 54 | * 规范要求实现阻塞的接口,由properties来设置阻塞时间,但本赛题不需要用到该特性,请实现一个非阻塞(也即阻塞时间为0)调用, 也即没有消息则返回null 55 | * @return 56 | */ 57 | Message poll(); 58 | 59 | /** 60 | * Polls the next message produced for this {@code PullConsumer}, using the specified properties. 61 | *

62 | * This call blocks indefinitely until a message is produced or until this {@code PullConsumer} is shut down. 63 | * 64 | * @param properties the specified properties 65 | * @return the next message produced for this {@code PullConsumer}, or null if this {@code PullConsumer} is 66 | * concurrently shut down 67 | * @throws OMSRuntimeException if this {@code PullConsumer} fails to pull the next message due to some internal 68 | * error. 69 | */ 70 | Message poll(final KeyValue properties); 71 | 72 | /** 73 | * Acknowledges the specified and consumed message, with unique message id. 74 | *

75 | * Messages that have been received but not acknowledged may be redelivered. 76 | * 77 | * @throws OMSRuntimeException if the consumer fails to acknowledge the messages due to some internal error. 78 | */ 79 | void ack(String messageId); 80 | 81 | /** 82 | * Acknowledges the specified and consumed message with the specified properties. 83 | *

84 | * Messages that have been received but not acknowledged may be redelivered. 85 | * 86 | * @throws OMSRuntimeException if the consumer fails to acknowledge the messages due to some internal error. 87 | */ 88 | void ack(String messageId, final KeyValue properties); 89 | 90 | /** 91 | * 绑定到一个Queue,并订阅topics,即从这些topic读取消息 92 | * @param queueName 93 | * @param topics 94 | */ 95 | void attachQueue(String queueName, Collection topics); 96 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/FileWriteCache.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.nio.ByteBuffer; 8 | import java.nio.MappedByteBuffer; 9 | import java.nio.channels.FileChannel; 10 | import java.nio.channels.FileChannel.MapMode; 11 | import java.util.HashMap; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | import java.util.concurrent.locks.Condition; 14 | import java.util.concurrent.locks.Lock; 15 | import java.util.concurrent.locks.ReentrantLock; 16 | 17 | 18 | 19 | /** 20 | * 对应于每一个topic或者queue文件,用来保存写到文件的消息的缓存。 21 | * @author Jenson 22 | * 23 | */ 24 | public final class FileWriteCache { 25 | // 用来获取编号 26 | private static AtomicInteger globalNo = new AtomicInteger(0); 27 | /** 5/27架构大改:每个Producer独享一个FileWriteCache */ 28 | // 文件对应的文件名和路径 29 | private final String filePath; 30 | // 表示cache已经写到哪个位置了(其实就是长度) 31 | private int pos; 32 | 33 | /* 5/25 更新:用map file写文件 */ 34 | private RandomAccessFile raf; 35 | private FileChannel outChannel; 36 | // private MappedByteBuffer mappedFile; 37 | 38 | // 已经写入文件的字节数 39 | private long bytesWrite = 0; 40 | 41 | /* 不压缩的话要少用内存 */ 42 | private final static int SIZE = (15*1024*1024); 43 | 44 | /** 线上测试 */ 45 | private final static int FILE_SIZE = (172 * 1024 *1024); 46 | 47 | /** 本地测试 */ 48 | // private final static int FILE_SIZE = (3 * 1024 *1024); 49 | 50 | /** 6/1 大更新:用一个87MB的DirectBuffer先缓存完全部数据,等到写完之后再一次性刷到磁盘! 51 | * 避免pagecache每5s就 同步一次! 52 | */ 53 | private ByteBuffer wholeData; 54 | // 一次写87MB,足够大了,应该加锁避免同时多个线程写文件 55 | private static final Lock diskLock = new ReentrantLock(); 56 | 57 | /** 58 | * @param name topic或者queue 59 | * @throws IOException 60 | */ 61 | public FileWriteCache(final String name) throws IOException { 62 | this.wholeData = ByteBuffer.allocateDirect(SIZE); 63 | filePath = name; 64 | pos = 0; 65 | File file = new File(filePath); 66 | if(file.exists()) { 67 | file.delete(); 68 | } 69 | file.createNewFile(); 70 | this.raf = new RandomAccessFile(file, "rw"); 71 | this.outChannel = this.raf.getChannel(); 72 | } 73 | 74 | /** 75 | * 直接将消息写到MappedByteBuffer 76 | * @param data 77 | */ 78 | public void write(final int topicNo, final byte[] data, final int dataLen) { 79 | // 5/26 15:30 更新:FileWriteCache只负责写入数据到map file 80 | writeData(topicNo, data, dataLen); 81 | } 82 | 83 | private void flush2File() { 84 | this.wholeData.flip(); 85 | if(this.wholeData.hasRemaining()) { 86 | try { 87 | diskLock.lock(); 88 | while(this.wholeData.hasRemaining()) { 89 | this.outChannel.write(wholeData); 90 | } 91 | } catch (IOException e) { 92 | e.printStackTrace(); 93 | }finally { 94 | diskLock.unlock(); 95 | } 96 | } 97 | this.wholeData.clear(); 98 | } 99 | 100 | private void writeData(final int topicNo, final byte[] data, final int len) { 101 | if(len + 7 > this.wholeData.remaining()) { 102 | // 写满了,flush 103 | flush2File(); 104 | } 105 | // 5/27更新:加上topicNo 106 | this.wholeData.put((byte)topicNo); 107 | // 还有chunk的长度类型 108 | if(len <= Byte.MAX_VALUE) { 109 | this.wholeData.put(Constant.MSG_LEN_BYTE); 110 | this.wholeData.put((byte)len); 111 | }else if(len <= Short.MAX_VALUE) { 112 | this.wholeData.put(Constant.MSG_LEN_SHORT); 113 | this.wholeData.putShort((short)len); 114 | }else { 115 | this.wholeData.put(Constant.MSG_LEN_INT); 116 | this.wholeData.putInt(len); 117 | } 118 | this.wholeData.put(data, 0, len); 119 | } 120 | /** 121 | * 强制flush数据到硬盘。如果cache还有数据,先把数据写进文件。 122 | */ 123 | public void flush() { 124 | flush2File(); 125 | this.wholeData = null; 126 | } 127 | 128 | public void close() { 129 | try { 130 | this.outChannel.close(); 131 | this.raf.close(); 132 | } catch (IOException e) { 133 | MyLogger.log(MyLogger.ERROR_TAG, "Failed in FileWriteCache.close()."+e.getMessage()); 134 | e.printStackTrace(); 135 | } 136 | } 137 | 138 | } 139 | 140 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/ServiceEndPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.observer.Observer; 21 | 22 | /** 23 | * @author vintagewang@apache.org 24 | * 25 | * @version OMS 1.0 26 | * @since OMS 1.0 27 | */ 28 | public interface ServiceEndPoint extends ServiceLifecycle { 29 | /** 30 | * Register/re-register a service in a serviceEndPoint object 31 | * if service has been registered in serviceEndPoint object, it will be failed when registering delicately 32 | * 33 | * @param service the service to publish in serviceEndPoint 34 | */ 35 | void publish(Object service); 36 | 37 | /** 38 | * Like {@link #publish(Object)} but specifying {@code properties} 39 | * that can be used to configure the service published 40 | * 41 | * @param service the service to publish in serviceEndPoint 42 | * @param properties the service published properties 43 | */ 44 | 45 | void publish(Object service, KeyValue properties); 46 | 47 | /** 48 | * Bind a service object to serviceEndPoint, which can directly call services provided by service object 49 | * 50 | * @param type service type to bind in serviceEndPoint 51 | * @return service proxy object to bind 52 | */ 53 | T bind(Class type); 54 | 55 | /** 56 | * Like {@link #bind(Class)} but specifying {@code properties} that can be used to configure the service band 57 | * 58 | * @param type service type to bind in serviceEndPoint 59 | * @param properties the service bind properties 60 | * @param service proxy object to bind 61 | * @return service proxy object to bind 62 | */ 63 | T bind(Class type, KeyValue properties); 64 | 65 | /** 66 | * Like {@link #bind(Class, KeyValue)} but specifying {@code serviceLoadBalance} that can be used to select 67 | * endPoint target 68 | * 69 | * @param type service type to bind in serviceConsumer 70 | * @param properties the service band properties 71 | * @param serviceLoadBalance select endPoint target algorithm 72 | * @param service proxy object to bind 73 | * @return service proxy object to bind 74 | */ 75 | T bind(Class type, KeyValue properties, ServiceLoadBalance serviceLoadBalance); 76 | 77 | /** 78 | * Register an observer in an serviceEndPoint object. Whenever serviceEndPoint object publish or bind an service 79 | * object, it will be notified to the list of observer object registered before 80 | * 81 | * @param observer observer event object to an serviceEndPoint object 82 | */ 83 | void addObserver(Observer observer); 84 | 85 | /** 86 | * Removes the given observer from the list of observer 87 | *

88 | * If the given observer has not been previously registered (i.e. it was 89 | * never added) then this method call is a no-op. If it had been previously 90 | * added then it will be removed. If it had been added more than once, then 91 | * only the first occurrence will be removed. 92 | * 93 | * @param observer The observer to remove 94 | */ 95 | void deleteObserver(Observer observer); 96 | 97 | /** 98 | * @return 99 | */ 100 | InvokeContext invokeContext(); 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DefaultPullConsumer.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.KeyValue; 4 | import io.openmessaging.Message; 5 | import io.openmessaging.PullConsumer; 6 | 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.Collections; 11 | import java.util.Comparator; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.ConcurrentLinkedQueue; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | 18 | /** 19 | * 按照题目描述,每个线程单独起一个consumer,所以每个consumer应该不用考虑并发 20 | * 5/28 重构:采用push架构 21 | */ 22 | public class DefaultPullConsumer implements PullConsumer { 23 | private KeyValue properties; 24 | private String queue; 25 | // buckets里面存的是该consumer绑定到的queue_name和订阅的topics 26 | private Set buckets; 27 | private MessageLoad messageLoad = null; 28 | // 当前消费者的编号 29 | private final Integer consumerNo; 30 | // 用来生成全局的消费者编号 31 | private static final AtomicInteger globalConsumerNo = new AtomicInteger(0); 32 | private final String STORE_PATH; 33 | 34 | // 5/28更新:用于接收分发的消息 35 | private ConcurrentLinkedQueue msgQue; 36 | 37 | public DefaultPullConsumer(KeyValue properties) { 38 | this.properties = properties; 39 | this.messageLoad = MessageLoad.getInstance(); 40 | this.messageLoad.setStorePath(this.properties.getString("STORE_PATH")); 41 | this.STORE_PATH = this.properties.getString("STORE_PATH"); 42 | this.consumerNo = globalConsumerNo.getAndIncrement(); 43 | this.msgQue = new ConcurrentLinkedQueue<>(); 44 | this.buckets = new HashSet<>(100); 45 | // // for test 46 | // if(this.consumerNo == 1) { 47 | // logFileInfo(); 48 | // } 49 | } 50 | 51 | public Integer getConsumerNO() { 52 | return this.consumerNo; 53 | } 54 | 55 | // 统计文件数据 56 | private void logFileInfo() { 57 | MyLogger.listAllFiles(this.STORE_PATH); 58 | } 59 | @Override public KeyValue properties() { 60 | return properties; 61 | } 62 | 63 | public void notifyMsg(final DefaultBytesMessage msg) { 64 | this.msgQue.offer(msg); 65 | } 66 | 67 | @Override public void attachQueue(String queueName, Collection topics) { 68 | if (queue != null && !queue.equals(queueName)) { 69 | throw new ClientOMSException("You have alreadly attached to a queue " + queue); 70 | } 71 | queue = queueName; 72 | this.buckets.add(MyUtil.getTopicNo(queue)); 73 | for(final String name : topics) { 74 | this.buckets.add(MyUtil.getTopicNo(name)); 75 | } 76 | messageLoad.registerConsumer(this, buckets); 77 | } 78 | // 该消费者是否已经消费完自己文件的消息 79 | private boolean pollFinished = false; 80 | // 是否已经设置过优先级。 81 | private boolean hasSetPriority = false; 82 | @Override public Message poll() { 83 | if (buckets.size() == 0 || queue == null) { 84 | return null; 85 | } 86 | DefaultBytesMessage msg = null; 87 | while(msg == null) { 88 | msg = this.msgQue.poll(); 89 | if(msg == null) { 90 | if(!pollFinished) { 91 | // 如果自己的队列是空,而且没有读完消息文件,就去读一下 92 | this.pollFinished = this.messageLoad.readMsg(consumerNo); 93 | }else{ 94 | if(!hasSetPriority) { 95 | int normalPriority = Thread.NORM_PRIORITY; 96 | // 如果自己的文件读完了,降低优先级 97 | Thread.currentThread().setPriority(normalPriority-2); 98 | this.hasSetPriority = true; 99 | } 100 | } 101 | } 102 | } 103 | if(msg.getEndMsg()) { 104 | return null; 105 | } 106 | return msg; 107 | } 108 | 109 | @Override public Message poll(KeyValue properties) { 110 | throw new UnsupportedOperationException("Unsupported"); 111 | } 112 | 113 | @Override public void ack(String messageId) { 114 | throw new UnsupportedOperationException("Unsupported"); 115 | } 116 | 117 | @Override public void ack(String messageId, KeyValue properties) { 118 | throw new UnsupportedOperationException("Unsupported"); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/Promise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * A {@code Promise} represents the result of an asynchronous computation. Methods are provided to check if the 22 | * computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can 23 | * only be retrieved using method {@code get} when the computation has completed, blocking if necessary until it is 24 | * ready. Cancellation is performed by the {@code cancel} method. Additional methods are provided to determine if the 25 | * task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled. If 26 | * you would like to use a {@code Promise} for the sake of cancellability but not provide a usable result, you can 27 | * declare types of the form {@code Promise} and return {@code null} as a result of the underlying task. 28 | * 29 | * @author vintagewang@apache.org 30 | * @author yukon@apache.org 31 | * 32 | * @version OMS 1.0 33 | * @since OMS 1.0 34 | */ 35 | public interface Promise { 36 | 37 | /** 38 | * Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already 39 | * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started 40 | * when {@code cancel} is called, this task should never run. If the task has already started, then the {@code 41 | * mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in an 42 | * attempt to stop the task. 43 | *

44 | * After this method returns, subsequent calls to {@link #isDone} will always return {@code true}. Subsequent calls 45 | * to {@link #isCancelled} will always return {@code true} if this method returned {@code true}. 46 | * 47 | * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise, 48 | * in-progress tasks are allowed to complete 49 | * @return {@code false} if the task could not be cancelled, typically because it has already completed normally; 50 | * {@code true} otherwise 51 | */ 52 | boolean cancel(boolean mayInterruptIfRunning); 53 | 54 | /** 55 | * Returns {@code true} if this task was cancelled before it completed 56 | * normally. 57 | * 58 | * @return {@code true} if this task was cancelled before it completed 59 | */ 60 | boolean isCancelled(); 61 | 62 | /** 63 | * Returns {@code true} if this task completed. 64 | *

65 | * Completion may be due to normal termination, an exception, or 66 | * cancellation -- in all of these cases, this method will return 67 | * {@code true}. 68 | * 69 | * @return {@code true} if this task completed 70 | */ 71 | boolean isDone(); 72 | 73 | /** 74 | * Waits if necessary for the computation to complete, and then 75 | * retrieves its result. 76 | * 77 | * @return the computed result 78 | */ 79 | V get(); 80 | 81 | /** 82 | * Waits if necessary for at most the given time for the computation 83 | * to complete, and then retrieves its result, if available. 84 | * 85 | * @param timeout the maximum time to wait 86 | * @return the computed result

if the computation was cancelled 87 | */ 88 | V get(long timeout); 89 | 90 | /** 91 | * Set the value to this promise and mark it completed if set successfully. 92 | * 93 | * @param value Value 94 | * @return Whether set is success 95 | */ 96 | boolean set(V value); 97 | 98 | /** 99 | * Adds the specified listener to this promise. The specified listener is notified when this promise is done. If this 100 | * promise is already completed, the specified listener is notified immediately. 101 | * 102 | * @param listener PromiseListener 103 | */ 104 | void addListener(PromiseListener listener); 105 | 106 | /** 107 | * @return a throwable caught by the promise 108 | */ 109 | Throwable getThrowable(); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/MessageLoad.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.nio.file.StandardOpenOption; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.ConcurrentLinkedQueue; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | import io.openmessaging.Message; 24 | import io.openmessaging.MessageHeader; 25 | 26 | /** 27 | * 单例类,给Consumer使用,用于统计每个topic有多少consumer订阅之类。 28 | * 5/28更新:采用push架构 29 | * @author Jenson 30 | */ 31 | public final class MessageLoad { 32 | 33 | private static volatile MessageLoad INSTANCE = null; 34 | 35 | public static MessageLoad getInstance() { 36 | if(INSTANCE == null) { 37 | synchronized (MessageStore.class){ 38 | if(INSTANCE == null){ 39 | INSTANCE = new MessageLoad(); 40 | // 5/24 更新 打一个Log说明是最新版本代码 41 | System.out.println("6/4 8:00更新的代码"); 42 | } 43 | } 44 | } 45 | return INSTANCE; 46 | } 47 | // 存储文件的路径 48 | private String filePath = null; 49 | // 保存topicNo对应的消费者 50 | private HashMap> topicConsumerMap; 51 | private HashSet consumerSet; 52 | // 保存每个消费者对应的FileReadCache 53 | private HashMap fileReadCacheMap; 54 | private final CountDownLatch latch; 55 | private MessageLoad() { 56 | this.topicConsumerMap = new HashMap<>(100); 57 | this.fileReadCacheMap = new HashMap<>(Constant.GROUP_NUM); 58 | this.latch = new CountDownLatch(Constant.GROUP_NUM); 59 | this.consumerSet = new HashSet<>(Constant.GROUP_NUM); 60 | new Thread( 61 | ()->{ 62 | waitAndSendEndMsg(); 63 | } 64 | ).start(); 65 | } 66 | // 等待所有消费者消费完毕然后发送结束消息 67 | private void waitAndSendEndMsg() { 68 | try { 69 | this.latch.await(); 70 | } catch (InterruptedException e) { 71 | e.printStackTrace(); 72 | } 73 | DefaultBytesMessage endMsg = new DefaultBytesMessage(null); 74 | endMsg.setEndMsg(true); 75 | for(final DefaultPullConsumer cs : this.consumerSet) { 76 | cs.notifyMsg(endMsg); 77 | } 78 | } 79 | 80 | public void setStorePath(final String path) { 81 | if(filePath == null) { 82 | synchronized (this) { 83 | if(filePath == null) { 84 | this.filePath = path; 85 | MyUtil.constructMetaData(path); 86 | for(int i = 0; i < Constant.GROUP_NUM; ++i) { 87 | this.fileReadCacheMap.put(i, new FileReadCache(i, Paths.get(path, String.valueOf(i)).toString())); 88 | } 89 | 90 | } 91 | } 92 | } 93 | } 94 | 95 | // 如果读完消息了返回true,否则返回false 96 | public boolean readMsg(final Integer consumerNo) { 97 | if(!this.fileReadCacheMap.containsKey(consumerNo)) { 98 | System.out.println("Error! doesnot have consumer:" + consumerNo); 99 | return true; 100 | } 101 | DefaultBytesMessage msg = this.fileReadCacheMap.get(consumerNo).readMessage(); 102 | if(msg == null) { 103 | // 说明当前这个消费者已经把自己的消息文件消费完了 104 | this.latch.countDown(); 105 | return true; 106 | } 107 | String topicName = msg.headers().getString(MessageHeader.TOPIC); 108 | String queueName = msg.headers().getString(MessageHeader.QUEUE); 109 | String actualName = (topicName == null? queueName : topicName); 110 | // 分发消息 111 | notifyMsg(MyUtil.getTopicNo(actualName), msg); 112 | return false; 113 | } 114 | 115 | // 返回某个topic是否有消费者订阅 116 | public boolean topicHasConsumer(Integer topicNo) { 117 | if(!this.topicConsumerMap.containsKey(topicNo)) { 118 | return false; 119 | } 120 | if(this.topicConsumerMap.get(topicNo).isEmpty()) { 121 | return false; 122 | } 123 | return true; 124 | } 125 | // 将topic为topicNo的消息分发出去 126 | public void notifyMsg(final Integer topicNo, final DefaultBytesMessage msg) { 127 | if(!this.topicConsumerMap.containsKey(topicNo)) { 128 | return; 129 | } 130 | for(final DefaultPullConsumer consumer : this.topicConsumerMap.get(topicNo)) { 131 | consumer.notifyMsg(msg); 132 | } 133 | } 134 | 135 | /** 136 | * Consumer将自己注册到MessageLoad, 137 | * 以便MessageLoad统计对于每个topic来说,有多少consumer订阅了它 138 | * @param consumer 消费者 139 | * @param topics 140 | */ 141 | public void registerConsumer(final DefaultPullConsumer consumer, 142 | final Set topicNO) { 143 | this.consumerSet.add(consumer); 144 | 145 | // TODO:假设是线程安全的 146 | for(final Integer tno : topicNO) { 147 | if(!this.topicConsumerMap.containsKey(tno)) { 148 | this.topicConsumerMap.put(tno, new ArrayList()); 149 | } 150 | this.topicConsumerMap.get(tno).add(consumer); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/Message.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * The {@code Message} interface is the root interface of all OMS messages, and the most commonly used OMS message is 22 | * {@link BytesMessage}. 23 | *

24 | * Most message-oriented middleware (MOM) products treat messages as lightweight entities that consist of a header and a 25 | * body, like Apache RocketMQ. The header contains fields used for message 26 | * routing and identification; the body contains the application data being sent. 27 | *

28 | * The {@code Message} is a lightweight entity that only contains the property related information of a specific message 29 | * object, and the {@code Message} is composed of the following parts: 30 | * 31 | *

    32 | *
  • Header - All messages support the same set of header fields. Header fields contain values used by both 33 | * clients and providers to identify and route messages. 34 | *
  • Properties - Each message contains a built-in facility for supporting application-defined property values. 35 | * Properties provide an efficient mechanism for supporting application-defined message filtering. 36 | *
37 | * 38 | * The body part is placed in the implementation classes of {@code Message}. 39 | * 40 | * @author vintagewang@apache.org 41 | * @author yukon@apache.org 42 | * 43 | * @version OMS 1.0 44 | * @since OMS 1.0 45 | */ 46 | public interface Message { 47 | /** 48 | * Returns all the header fields of the {@code Message} object as a {@code KeyValue}. 49 | * 50 | * @return the headers of a {@code Message} 51 | * @see MessageHeader 52 | */ 53 | KeyValue headers(); 54 | 55 | /** 56 | * Returns all the built-in property fields of the {@code Message} object as a {@code KeyValue}. 57 | * 58 | * @return the properties of a {@code Message} 59 | */ 60 | KeyValue properties(); 61 | 62 | /** 63 | * Puts a {@code String}-{@code int} {@code KeyValue} entry to he headers of a {@code Message}. 64 | * 65 | * @param key the key to be placed into the headers 66 | * @param value the value corresponding to key 67 | */ 68 | Message putHeaders(String key, int value); 69 | 70 | /** 71 | * Puts a {@code String}-{@code long} {@code KeyValue} entry to he headers of a {@code Message}. 72 | * 73 | * @param key the key to be placed into the headers 74 | * @param value the value corresponding to key 75 | */ 76 | Message putHeaders(String key, long value); 77 | 78 | /** 79 | * Puts a {@code String}-{@code double} {@code KeyValue} entry to he headers of a {@code Message}. 80 | * 81 | * @param key the key to be placed into the headers 82 | * @param value the value corresponding to key 83 | */ 84 | Message putHeaders(String key, double value); 85 | 86 | /** 87 | * Puts a {@code String}-{@code String} {@code KeyValue} entry to he headers of a {@code Message}. 88 | * 89 | * @param key the key to be placed into the headers 90 | * @param value the value corresponding to key 91 | */ 92 | Message putHeaders(String key, String value); 93 | 94 | /** 95 | * Puts a {@code String}-{@code int} {@code KeyValue} entry to he headers of a {@code Message}. 96 | * 97 | * @param key the key to be placed into the headers 98 | * @param value the value corresponding to key 99 | */ 100 | Message putProperties(String key, int value); 101 | 102 | /** 103 | * Puts a {@code String}-{@code long} {@code KeyValue} entry to he headers of a {@code Message}. 104 | * 105 | * @param key the key to be placed into the headers 106 | * @param value the value corresponding to key 107 | */ 108 | Message putProperties(String key, long value); 109 | 110 | /** 111 | * Puts a {@code String}-{@code double} {@code KeyValue} entry to he headers of a {@code Message}. 112 | * 113 | * @param key the key to be placed into the headers 114 | * @param value the value corresponding to key 115 | */ 116 | Message putProperties(String key, double value); 117 | 118 | /** 119 | * Puts a {@code String}-{@code String} {@code KeyValue} entry to he headers of a {@code Message}. 120 | * 121 | * @param key the key to be placed into the headers 122 | * @param value the value corresponding to key 123 | */ 124 | Message putProperties(String key, String value); 125 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/MessagingAccessPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * The {@code MessagingAccessPoint} obtained from {@link MessagingAccessPointManager} is capable of creating {@code 22 | * Producer}, {@code Consumer}, {@code ServiceEndPoint}, and so on. 23 | *

For example: 24 | *

 25 |  * MessagingAccessPoint messagingAccessPoint = MessagingAccessPointManager.getMessagingAccessPoint("openmessaging:rocketmq://localhost:10911/namespace");
 26 |  * Producer producer = messagingAccessPoint.createProducer();
 27 |  * producer.send(producer.createBytesMessageToTopic("HELLO_TOPIC", "HELLO_BODY".getBytes(Charset.forName("UTF-8"))));
 28 |  * 
29 | * 30 | * @author vintagewang@apache.org 31 | * @author yukon@apache.org 32 | * 33 | * @version OMS 1.0 34 | * @since OMS 1.0 35 | */ 36 | public interface MessagingAccessPoint extends ServiceLifecycle { 37 | /** 38 | * Creates a new {@code Producer} for the specified {@code MessagingAccessPoint}. 39 | * 40 | * @return the created producer 41 | */ 42 | Producer createProducer(); 43 | 44 | /** 45 | * Creates a new {@code Producer} for the specified {@code MessagingAccessPoint} with some preset properties. 46 | * 47 | * @param properties the preset properties 48 | * @return the created producer 49 | */ 50 | Producer createProducer(KeyValue properties); 51 | 52 | /** 53 | * Creates a new {@code PushConsumer} for the specified {@code MessagingAccessPoint}. 54 | * The returned {@code PushConsumer} isn't attached to any queue, 55 | * uses {@link PushConsumer#attachQueue(String, MessageListener)} to attach queues. 56 | * 57 | * @return the created {@code PushConsumer} 58 | */ 59 | PushConsumer createPushConsumer(); 60 | 61 | /** 62 | * Creates a new {@code PushConsumer} for the specified {@code MessagingAccessPoint} with some preset properties. 63 | * 64 | * @param properties the preset properties 65 | * @return the created {@code PushConsumer} 66 | */ 67 | PushConsumer createPushConsumer(KeyValue properties); 68 | 69 | /** 70 | * Creates a new {@code PullConsumer} for the specified {@code MessagingAccessPoint} with the specified queue. 71 | * 72 | * @param queueName the only attached queue for this {@code PullConsumer} 73 | * @return the created {@code PullConsumer} 74 | */ 75 | PullConsumer createPullConsumer(String queueName); 76 | 77 | /** 78 | * Creates a new {@code PullConsumer} for the specified {@code MessagingAccessPoint} with some preset properties. 79 | * 80 | * @param queueName the only attached queue for this {@code PullConsumer} 81 | * @param properties the preset properties 82 | * @return the created {@code PullConsumer} 83 | */ 84 | PullConsumer createPullConsumer(String queueName, KeyValue properties); 85 | 86 | /** 87 | * Creates a new {@code PartitionConsumer} for the specified {@code MessagingAccessPoint}. 88 | * 89 | * @param queueName the only attached queue for this {@code PartitionConsumer} 90 | * @return the created {@code PartitionConsumer} 91 | */ 92 | PartitionConsumer createPartitionConsumer(String queueName); 93 | 94 | /** 95 | * Creates a new {@code PartitionConsumer} for the specified {@code MessagingAccessPoint} with some preset properties. 96 | * 97 | * @param queueName the only attached queue for this {@code PartitionConsumer} 98 | * @param properties the preset properties 99 | * @return the created consumer 100 | */ 101 | PartitionConsumer createPartitionConsumer(String queueName, KeyValue properties); 102 | 103 | /** 104 | * Create a new {@code ResourceManager} for the specified {@code MessagingAccessPoint}. 105 | * 106 | * @return the created {@code ResourceManager} 107 | */ 108 | ResourceManager createResourceManager(); 109 | 110 | /** 111 | * Create a new {@code Filters} for the specified {@code MessagingAccessPoint}. 112 | * 113 | * @return the created {@code Filters} 114 | */ 115 | Filters createFilters(); 116 | 117 | /** 118 | * Create a new {@code ServiceEndPoint} for the specified {@code MessagingAccessPoint}. 119 | * 120 | * @return the created {@code ServiceEndPoint} 121 | */ 122 | ServiceEndPoint createServiceEndPoint(); 123 | 124 | /** 125 | * Create a new {@code ServiceEndPoint} for the specified {@code MessagingAccessPoint} with some preset properties. 126 | * 127 | * @param properties the preset properties 128 | * @return the created {@code ServiceEndPoint} 129 | */ 130 | ServiceEndPoint createServiceEndPoint(KeyValue properties); 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/KeyValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSRuntimeException; 21 | import java.util.Set; 22 | 23 | /** 24 | * The {@code KeyValue} class represents a persistent set of properties, which supports method chaining.

A {@code 25 | * KeyValue} object only allows {@code String} keys and can contain four primitive type as values: {@code int}, {@code 26 | * long}, {@code double}, {@code String}.

The {@code KeyValue} is a replacement of {@code Properties}, with simpler 27 | * interfaces and reasonable entry limits.

A {@code KeyValue} object may be used in concurrent scenarios, so the 28 | * implementation of {@code KeyValue} should consider concurrent related issues.

All the existing entries in {@code 29 | * KeyValue} can't be removed but can be replaced by a new value for the specified key. 30 | * 31 | * @author vintagewang@apache.org 32 | * @author yukon@apache.org 33 | * @version OMS 1.0 34 | * @since OMS 1.0 35 | */ 36 | public interface KeyValue { 37 | /** 38 | * Inserts or replaces {@code int} value for the specified key. 39 | * 40 | * @param key the key to be placed into this {@code KeyValue} object 41 | * @param value the value corresponding to key 42 | */ 43 | KeyValue put(String key, int value); 44 | 45 | /** 46 | * Inserts or replaces {@code long} value for the specified key. 47 | * 48 | * @param key the key to be placed into this {@code KeyValue} object 49 | * @param value the value corresponding to key 50 | */ 51 | KeyValue put(String key, long value); 52 | 53 | /** 54 | * Inserts or replaces {@code double} value for the specified key. 55 | * 56 | * @param key the key to be placed into this {@code KeyValue} object 57 | * @param value the value corresponding to key 58 | */ 59 | KeyValue put(String key, double value); 60 | 61 | /** 62 | * Inserts or replaces {@code String} value for the specified key. 63 | * 64 | * @param key the key to be placed into this {@code KeyValue} object 65 | * @param value the value corresponding to key 66 | */ 67 | KeyValue put(String key, String value); 68 | 69 | /** 70 | * Searches for the {@code int} property with the specified key in this {@code KeyValue} object. 71 | * If the key is not found in this property list, zero is returned. 72 | * 73 | * @param key the property key 74 | * @return the value in this {@code KeyValue} object with the specified key value 75 | * @throws OMSRuntimeException if the specified {@code key} doesn't exist in this object. 76 | * @see #put(String, int) 77 | */ 78 | int getInt(String key); 79 | 80 | /** 81 | * Searches for the {@code long} property with the specified key in this {@code KeyValue} object. 82 | * If the key is not found in this property list, zero is returned. 83 | * 84 | * @param key the property key 85 | * @return the value in this {@code KeyValue} object with the specified key value 86 | * @throws OMSRuntimeException if the specified {@code key} doesn't exist in this object. 87 | * @see #put(String, long) 88 | */ 89 | long getLong(String key); 90 | 91 | /** 92 | * Searches for the {@code double} property with the specified key in this {@code KeyValue} object. 93 | * If the key is not found in this property list, zero is returned. 94 | * 95 | * @param key the property key 96 | * @return the value in this {@code KeyValue} object with the specified key value 97 | * @throws OMSRuntimeException if the specified {@code key} doesn't exist in this object. 98 | * @see #put(String, double) 99 | */ 100 | double getDouble(String key); 101 | 102 | /** 103 | * Searches for the {@code String} property with the specified key in this {@code KeyValue} object. 104 | * If the key is not found in this property list, {@code null} is returned. 105 | * 106 | * @param key the property key 107 | * @return the value in this {@code KeyValue} object with the specified key value 108 | * @throws OMSRuntimeException if the specified {@code key} doesn't exist in this object. 109 | * @see #put(String, String) 110 | */ 111 | String getString(String key); 112 | 113 | /** 114 | * Returns a {@link Set} view of the keys contained in this {@code KeyValue} object. 115 | * The set is independent of this {@code KeyValue} object, like a copy, so changes to the set are 116 | * not reflected in the {@code KeyValue} object, and vice-versa. 117 | * 118 | * @return the key set view of this {@code KeyValue} object. 119 | */ 120 | Set keySet(); 121 | 122 | /** 123 | * Tests if the specified {@code String} is a key in this {@code KeyValue}. 124 | * 125 | * @param key possible key 126 | * @return true if and only if the specified key is in this {@code KeyValue}, false 127 | * otherwise. 128 | */ 129 | boolean containsKey(String key); 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/internal/MessagingAccessPointAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging.internal; 19 | 20 | import io.openmessaging.KeyValue; 21 | import io.openmessaging.ServiceEndPoint; 22 | import java.lang.reflect.Constructor; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Properties; 31 | 32 | /** 33 | * WARN: The current interface prohibits direct access by the end user 34 | * 35 | * @version OMS 1.0 36 | * @since OMS 1.0 37 | */ 38 | public class MessagingAccessPointAdapter { 39 | private static final String PROTOCOL_NAME = "protocol"; 40 | private static final String SPI_NAME = "spi"; 41 | private static final String URL_NAME = "urls"; 42 | private static final String URL = "url"; 43 | private static final String DEFAULT_SERVICE_END_POINT = "rocketmq"; 44 | private static final String DEFAULT_SERVICE_IMPL = "org.apache.rocketmq.openrelay.impl.ServiceEndPointStandardImpl"; 45 | private static final String URL_SEPARATOR = ":"; 46 | private static final String LIST_SEPARATOR = ","; 47 | private static final String PARAM_SEPARATOR = "&"; 48 | private static final String KV_SEPARATOR = "="; 49 | private static Map serviceEndPointClassMap = new HashMap<>(); 50 | 51 | static { 52 | serviceEndPointClassMap.put(DEFAULT_SERVICE_END_POINT, DEFAULT_SERVICE_IMPL); 53 | } 54 | 55 | private static Map> parseURI(String uri) { 56 | if (uri == null || uri.length() == 0) { 57 | return new HashMap<>(); 58 | } 59 | 60 | int spiIndex = 0; 61 | int index = uri.indexOf(URL_SEPARATOR); 62 | Map> results = new HashMap<>(); 63 | String protocol = uri.substring(0, index); 64 | List protocolSet = new ArrayList<>(); 65 | protocolSet.add(protocol); 66 | results.put(PROTOCOL_NAME, protocolSet); 67 | if (index > 0) { 68 | String spi; 69 | spiIndex = uri.indexOf(URL_SEPARATOR, index + 1); 70 | if (spiIndex > 0) { 71 | spi = uri.substring(index + 1, spiIndex); 72 | } 73 | else { 74 | spi = uri.substring(index + 1); 75 | } 76 | List spiSet = new ArrayList<>(); 77 | spiSet.add(spi); 78 | results.put(SPI_NAME, spiSet); 79 | } 80 | if (spiIndex > 0) { 81 | String urlList = uri.substring(spiIndex + 1); 82 | String[] list = urlList.split(LIST_SEPARATOR); 83 | if (list.length > 0) { 84 | results.put(URL_NAME, Arrays.asList(list)); 85 | } 86 | } 87 | return results; 88 | } 89 | 90 | private static ServiceEndPoint instantiateServiceEndPoint(String driver, KeyValue properties) 91 | throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, 92 | InvocationTargetException, InstantiationException { 93 | String serviceImpl = driver; 94 | if (serviceImpl == null) 95 | serviceImpl = DEFAULT_SERVICE_IMPL; 96 | if (serviceEndPointClassMap.containsKey(driver)) 97 | serviceImpl = serviceEndPointClassMap.get(driver); 98 | Class serviceEndPointClass = Class.forName(serviceImpl); 99 | if (serviceEndPointClass == null) 100 | return null; 101 | 102 | if (properties.getString(URL) != null) { 103 | String[] propertySplits = ((String)properties.getString(URL)).split(PARAM_SEPARATOR); 104 | if (propertySplits.length > 0) { 105 | for (int index = 1; index < propertySplits.length; index++) { 106 | String[] kv = propertySplits[index].split(KV_SEPARATOR); 107 | properties.put(kv[0], kv[1]); 108 | } 109 | } 110 | } 111 | Class[] paramTypes = {Properties.class}; 112 | Constructor constructor = serviceEndPointClass.getConstructor(paramTypes); 113 | assert constructor != null; 114 | return (ServiceEndPoint)constructor.newInstance(properties); 115 | } 116 | 117 | private static ServiceEndPoint createServiceEndPoint(Map> url, KeyValue properties) 118 | throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, 119 | InstantiationException, IllegalAccessException { 120 | List driver = url.get(SPI_NAME); 121 | List urls = url.get(URL_NAME); 122 | Collections.shuffle(urls); 123 | Collections.shuffle(driver); 124 | if (urls.size() > 0) 125 | properties.put(URL, urls.get(0)); 126 | return MessagingAccessPointAdapter.instantiateServiceEndPoint(driver.get(0), properties); 127 | } 128 | 129 | public static ServiceEndPoint createServiceEndPoint(String url, KeyValue properties) 130 | throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 131 | IllegalAccessException, InvocationTargetException { 132 | Map> driverUrl = parseURI(url); 133 | if (null == driverUrl || driverUrl.size() == 0) { 134 | throw new IllegalArgumentException("driver url parsed result.size ==0"); 135 | } 136 | return createServiceEndPoint(driverUrl, properties); 137 | } 138 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/Producer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSRuntimeException; 21 | 22 | /** 23 | * A {@code Producer} is a simple object used to send messages on behalf 24 | * of a {@code MessagingAccessPoint}. An instance of {@code Producer} is 25 | * created by calling the {@link MessagingAccessPoint#createProducer()} method. 26 | * It provides various {@code send} methods to send a message to a specified destination. 27 | * A destination can be a {@link MessageHeader#TOPIC} or a {@link MessageHeader#QUEUE}. 28 | *

29 | * 30 | * {@link Producer#send(Message)} means send a message to destination synchronously, 31 | * the calling thread will block until the send request complete. 32 | *

33 | * {@link Producer#sendAsync(Message)} means send a message to destination asynchronously, 34 | * the calling thread won't block and will return immediately. Since the send call is asynchronous 35 | * it returns a {@link Promise} for the send result. 36 | *

37 | * {@link Producer#sendOneway(Message)} means send a message to destination in one way, 38 | * the calling thread won't block and will return immediately. The caller won't care about 39 | * the send result, while the server has no responsibility for returning the result. 40 | * 41 | * @author vintagewang@apache.org 42 | * @author yukon@apache.org 43 | * 44 | * @version OMS 1.0 45 | * @since OMS 1.0 46 | */ 47 | public interface Producer extends MessageFactory, ServiceLifecycle { 48 | /** 49 | * Returns the properties of this {@code Producer} instance. 50 | * Changes to the return {@code KeyValue} are not reflected in physical {@code Producer}, 51 | * and use {@link ResourceManager#setProducerProperties(String, KeyValue)} to modify. 52 | * 53 | * @return the properties 54 | */ 55 | KeyValue properties(); 56 | 57 | /** 58 | * Sends a message to the specified destination synchronously, the destination should be preset to 59 | * {@link MessageHeader}, other header fields as well. 60 | * 61 | * @param message a message will be sent 62 | * @throws OMSRuntimeException if the {@code Producer} fails to send the message due to some internal error. 63 | */ 64 | void send(Message message); 65 | 66 | /** 67 | * Sends a message to the specified destination synchronously, using the specified properties, the destination 68 | * should be preset to {@link MessageHeader}, other header fields as well. 69 | * 70 | * @param message a message will be sent 71 | * @param properties the specified properties 72 | * @throws OMSRuntimeException if the {@code Producer} fails to send the message due to some internal error. 73 | */ 74 | void send(Message message, KeyValue properties); 75 | 76 | /** 77 | * 为比赛新增的flush接口,评测线程会最后调用该接口; 78 | * 选手在接口里应该把缓存中的数据写入磁盘或者pagecache 79 | * 在规定时间内,该接口没有返回,producer会被强制杀掉,可能会有数据丢失,从而导致数据不正确; 80 | */ 81 | void flush(); 82 | 83 | /** 84 | * Sends a message to the specified destination asynchronously, the destination should be preset to 85 | * {@link MessageHeader}, other header fields as well. 86 | *

87 | * The returned {@code Promise} will have the result once the operation completes, and the registered 88 | * {@code PromiseListener} will be notified, either because the operation was successful or because of an error. 89 | * 90 | * @param message a message will be sent 91 | * @return the {@code Promise} of an asynchronous message send operation. 92 | * @see Promise 93 | * @see PromiseListener 94 | */ 95 | Promise sendAsync(Message message); 96 | 97 | /** 98 | * Sends a message to the specified destination asynchronously, using the specified properties, the destination 99 | * should be preset to {@link MessageHeader}, other header fields as well. 100 | *

101 | * The returned {@code Promise} will have the result once the operation completes, and the registered 102 | * {@code PromiseListener} will be notified, either because the operation was successful or because of an error. 103 | * 104 | * @param message a message will be sent 105 | * @param properties the specified properties 106 | * @return the {@code Promise} of an asynchronous message send operation. 107 | * @see Promise 108 | * @see PromiseListener 109 | */ 110 | Promise sendAsync(Message message, KeyValue properties); 111 | 112 | /** 113 | * Sends a message to the specified destination in one way, the destination should be preset to 114 | * {@link MessageHeader}, other header fields as well. 115 | *

116 | * There is no {@code Promise} related or {@code RuntimeException} thrown. The calling thread doesn't 117 | * care about the send result and also have no context to get the result. 118 | * 119 | * @param message a message will be sent 120 | */ 121 | void sendOneway(Message message); 122 | 123 | /** 124 | * Sends a message to the specified destination in one way, using the specified properties, the destination 125 | * should be preset to {@link MessageHeader}, other header fields as well. 126 | *

127 | * There is no {@code Promise} related or {@code RuntimeException} thrown. The calling thread doesn't 128 | * care about the send result and also have no context to get the result. 129 | * 130 | * @param message a message will be sent 131 | * @param properties the specified properties 132 | */ 133 | void sendOneway(Message message, KeyValue properties); 134 | 135 | BatchToPartition createBatchToPartition(String partitionName); 136 | 137 | BatchToPartition createBatchToPartition(String partitionName, KeyValue properties); 138 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/FileReadCache.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.RandomAccessFile; 8 | import java.nio.ByteBuffer; 9 | import java.nio.MappedByteBuffer; 10 | import java.nio.channels.FileChannel; 11 | import java.util.HashMap; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | import java.util.concurrent.locks.ReentrantLock; 14 | import java.util.zip.Inflater; 15 | 16 | 17 | //用于给消息编号 18 | class TopicMsgNO { 19 | int no = 0; 20 | } 21 | 22 | /** 23 | * 每个consumer都有一个FileReadCache, 24 | * 每次Consumer对某个topic进行poll消息,都先通过FileReadCache来读消息; 25 | * 如果FileReadCache缓存有消息,则直接返回,否则从硬盘读一块出来 26 | * 某个topic读完后,重新设置FileReadCache打开新的topic文件即可 27 | * 5/28 Update:采用push架构 28 | * @author Jenson 29 | * 30 | */ 31 | public final class FileReadCache { 32 | // 使用数字来替换生产者消息编号 33 | private HashMap msgNOMap; 34 | 35 | /** 6/2更新:优化序列化反序列化 */ 36 | private String curTopicOrQueName = null; 37 | 38 | /** 消费者编号 */ 39 | private final int consumerNO; 40 | // 映射到的测评编号 41 | private final int testerNO; 42 | private final String PRO_OFFSET_PREFIX; 43 | 44 | //4MB 45 | private final static long SIZE = Constant.SIZE_18MB; 46 | // 使用Memory mapped file试试 47 | private MappedByteBuffer buf = null; 48 | private RandomAccessFile raf; 49 | private FileChannel inChannel; 50 | 51 | // 文件名,用来打log 52 | private final String fileName; 53 | // 在extractKeyValue里用来重复利用保存key的字节数组 54 | byte[] keyBytes; 55 | // 在extractKeyValue里用来重复利用保存val的字节数组 56 | byte[] valStrBytes; 57 | 58 | private byte[] chunk; 59 | // 用来wrap 解压后的chunk的ByteBuffer 60 | private ByteBuffer msgChunkBuf; 61 | // 要解压缩的chunk大小,最多1MB 62 | private final static int CHUNK_SIZE = Constant.SIZE_1MB; 63 | 64 | public FileReadCache(final int csno, final String fileName) { 65 | this.msgNOMap = new HashMap<>(100); 66 | 67 | this.consumerNO = csno; 68 | this.testerNO = this.consumerNO; 69 | this.PRO_OFFSET_PREFIX = "PRODUCER"+this.testerNO+"_"; 70 | 71 | this.fileName = fileName; 72 | this.keyBytes = new byte[128]; 73 | this.valStrBytes = new byte[Constant.MAX_MSG_SIZE]; 74 | 75 | /** 5/26新增:解压缩 */ 76 | this.chunk = new byte[CHUNK_SIZE]; 77 | this.msgChunkBuf = null; 78 | 79 | openFile(); 80 | } 81 | 82 | private void openFile() { 83 | try { 84 | this.raf = new RandomAccessFile(this.fileName, "r"); 85 | this.inChannel = raf.getChannel(); 86 | } catch (FileNotFoundException e) { 87 | String errMsg = "Failed in openTopicFile. File "+ fileName+" doesnot exist" 88 | + e.getMessage(); 89 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 90 | throw new ClientOMSException(errMsg); 91 | } 92 | // 5/23更新:用mmap file读文件 93 | try { 94 | this.buf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, raf.length()); 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | System.out.println("Error mmap file:" + e.getMessage()); 98 | } 99 | } 100 | 101 | 102 | // 5/25更新:由于是一次性读入,所以不用考虑消息长度标识,也不用考虑数据没有读完到cache 103 | // 从buf中读取并解析出一条消息 104 | private DefaultBytesMessage decodeMsg() { 105 | // 如果chunk没有数据了,从mapFileBuffer里面读出来 106 | if(this.msgChunkBuf == null || 107 | this.msgChunkBuf.hasRemaining() == false) { 108 | while(true) { 109 | // CHUNK至少有3字节 110 | int leastBytesRemain = 3; 111 | if(this.buf.remaining() <= leastBytesRemain) { 112 | // 不够就说明读完了 113 | return null; 114 | } 115 | // 第一位应该是topicNo的byte 116 | int topicNo = this.buf.get(); 117 | // 看看这个topic有没有消费者订阅 118 | boolean hasConsumer = MessageLoad.getInstance().topicHasConsumer(topicNo); 119 | byte chunkLenType = this.buf.get(); 120 | int chunkLen = 0; 121 | switch(chunkLenType) { 122 | case Constant.MSG_LEN_BYTE: 123 | chunkLen = this.buf.get(); 124 | break; 125 | case Constant.MSG_LEN_SHORT: 126 | chunkLen = this.buf.getShort(); 127 | break; 128 | case Constant.MSG_LEN_INT: 129 | chunkLen = this.buf.getInt(); 130 | break; 131 | default: 132 | MyLogger.log(MyLogger.ERROR_TAG, "unknown chunkLenType! chunkLenType="+chunkLenType); 133 | return null; 134 | } 135 | if(!hasConsumer) { 136 | // 如果当前topic没人消费,skip 137 | this.buf.position(this.buf.position() + chunkLen); 138 | continue; 139 | } 140 | this.curTopicOrQueName = MyUtil.getTopicName(topicNo); 141 | // 将chunk数据读进来 142 | this.buf.get(this.chunk, 0, chunkLen); 143 | /** 6/3 更新:不压缩 */ 144 | // wrap 145 | this.msgChunkBuf = ByteBuffer.wrap(this.chunk); 146 | this.msgChunkBuf.limit(chunkLen); 147 | 148 | if(hasConsumer) { 149 | break; 150 | } 151 | } 152 | } 153 | DefaultBytesMessage msg = extractMessage(this.msgChunkBuf); 154 | return msg; 155 | } 156 | 157 | /** 158 | * 提取出头部 159 | * @param msgBuf 160 | * @param headers 161 | */ 162 | private void extractHeaders(final ByteBuffer msgBuf, DefaultKeyValue headers) { 163 | // 第一个是MSG_ID_LEN 164 | int len = msgBuf.getShort(); 165 | msgBuf.get(valStrBytes, 0, len); 166 | headers.put(Constant.MSGID_KEY, new String(valStrBytes, 0, len)); 167 | // 接下来放入头部的Topic字段 168 | String key = Constant.TOPIC_KEY; 169 | if(this.curTopicOrQueName.charAt(0) == 'Q') { 170 | key = Constant.QUEUE_KEY; 171 | } 172 | headers.put(key, this.curTopicOrQueName); 173 | } 174 | 175 | /** 176 | * 提取出消息。 177 | * @param msgBuf 178 | * @return 179 | */ 180 | private DefaultBytesMessage extractMessage(final ByteBuffer msgBuf) { 181 | DefaultBytesMessage msg = new DefaultBytesMessage(null); 182 | //第一byte是MSG_HEADER_BEGIN 183 | if(msgBuf.get() != Constant.MSG_HEADER_BEGIN) { 184 | String errMsg = "Failed to extractMessage in FileReadCache.extractMessage() MSG_HEADER_BEGIN expected."; 185 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 186 | //throw new ClientOMSException(errMsg); 187 | return null; 188 | } 189 | DefaultKeyValue headers = new DefaultKeyValue(); 190 | extractHeaders(msgBuf, headers); 191 | msg.setHeader(headers); 192 | DefaultKeyValue property = new DefaultKeyValue(); 193 | byte nxtByte = extractKeyValue(msgBuf, property); 194 | msg.setProperty(property); 195 | if(nxtByte != Constant.MSG_BODY_BEGIN) { 196 | // 如果不等于MSG_BODY_BEGIN,报错 197 | String errMsg = "Failed to extractMessage in FileReadCache. MSG_BODY_BEGIN expected."; 198 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 199 | //throw new ClientOMSException(errMsg); 200 | return null; 201 | } 202 | int bodyLen = msgBuf.getShort(); 203 | byte[] body = new byte[bodyLen]; 204 | msgBuf.get(body); 205 | msg.setBody(body); 206 | return msg; 207 | } 208 | 209 | /** 210 | * 提取出keyvalue,将结果存到dstKV中。 211 | * 返回最后一个读到的byte 212 | */ 213 | private byte extractKeyValue(ByteBuffer msgBuf, 214 | DefaultKeyValue dstKV) 215 | { 216 | /** 217 | * 6/3 更新:header部分只存messageId的value,key都不用存;Topic也不用存! 218 | * Property的key:PRO_OFFSET 用1byte表示! 219 | * 一条消息编码为: 220 | * 头部:MSG_HEADER_BEGIN + MSG_ID_LEN(Short)+ MSG_ID 221 | * Property: (KEY_LEN(byte) + key_str + VALUE_LEN (Short) + VALUE_STR) 222 | * .... 223 | * MSG_BODY: MSG_BODY_BEGIN (-3) + MSG_BODY_LEN (Short) + MSG_BODY 224 | */ 225 | // 先放PRO_OFFSET 226 | if(!this.msgNOMap.containsKey(this.curTopicOrQueName)) { 227 | this.msgNOMap.put(curTopicOrQueName, new TopicMsgNO()); 228 | } 229 | int msgNO = (this.msgNOMap.get(curTopicOrQueName).no)++; 230 | StringBuilder sb = new StringBuilder(); 231 | sb.append(PRO_OFFSET_PREFIX).append(msgNO); 232 | dstKV.put(Constant.PRO_OFFSET_KEY, sb.toString()); 233 | 234 | // 一个keyvalue字段,如果非空,那么除去KEY_VALUE_BEGIN,至少 235 | // 还需要有KEY_LEN 和 VALUE_TYPE两字节 236 | while(msgBuf.hasRemaining()) { 237 | byte keyLen = msgBuf.get(); 238 | if(keyLen == Constant.MSG_BODY_BEGIN) { 239 | return keyLen; 240 | } 241 | String key = null; 242 | msgBuf.get(keyBytes, 0, keyLen); 243 | // NOTE: 底层实现会根据byte[]内容新构建一个char[],也就是构造函数传进来的byte[]内容相当于被复制了! 244 | // 所以为了避免每次都新建一个byte[],可以构造一个全局的byte[],重复利用 245 | key = new String(keyBytes, 0, keyLen); 246 | int valLen = msgBuf.getShort(); 247 | msgBuf.get(valStrBytes, 0, valLen); 248 | dstKV.put(key, new String(valStrBytes, 0, valLen)); 249 | } 250 | return Constant.MSG_BODY_BEGIN; 251 | } 252 | 253 | // 目前是一个Consumer一个,也就是一个线程一个,不用锁 254 | // 2017/4/19 255 | /** 256 | * 从文件读取数据,并解析消息。出错或者读完都返回null; 257 | * @return 258 | */ 259 | public DefaultBytesMessage readMessage() { 260 | return decodeMsg(); 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/MessageHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | /** 21 | * The {@code MessageHeader} class describes each OMS message header field. 22 | * A message's complete header is transmitted to all OMS clients that receive the 23 | * message. 24 | * 25 | * @author vintagewang@apache.org 26 | * @author yukon@apache.org 27 | * 28 | * @version OMS 1.0 29 | * @since OMS 1.0 30 | */ 31 | public class MessageHeader { 32 | /** 33 | * The {@code MESSAGE_ID} header field contains a value that uniquely identifies 34 | * each message sent by a {@code Producer}. 35 | *

36 | * When a message is sent, MESSAGE_ID is ignored. 37 | *

38 | * When the send method returns it contains a producer-assigned value. 39 | */ 40 | public static final String MESSAGE_ID = "MessageId"; 41 | 42 | /** 43 | * The {@code TOPIC} header field is the destination which the message is being sent. 44 | *

45 | * When a message is sent this value is should be set properly. 46 | *

47 | * When a message is received, its {@code TOPIC} value must be equivalent to the 48 | * value assigned when it was sent. 49 | */ 50 | public static final String TOPIC = "Topic"; 51 | 52 | /** 53 | * The {@code QUEUE} header field is the destination which the message is being sent. 54 | *

55 | * When a message is sent this value is should be set properly. 56 | *

57 | * When a message is received, its {@code QUEUE} value must be equivalent to the 58 | * value assigned when it was sent. 59 | */ 60 | public static final String QUEUE = "Queue"; 61 | 62 | /** 63 | * The {@code BORN_TIMESTAMP} header field contains the time a message was handed 64 | * off to a {@code Producer} to be sent. 65 | *

66 | * When a message is sent, BORN_TIMESTAMP will be set with current timestamp as the born 67 | * timestamp of a message in client side, on return from the send method, the message's 68 | * BORN_TIMESTAMP header field contains this value. When a message is received its 69 | * BORN_TIMESTAMP header field contains this same value. 70 | *

71 | * This filed is a {@code long} value, measured in milliseconds. 72 | */ 73 | public static final String BORN_TIMESTAMP = "BornTimestamp"; 74 | 75 | /** 76 | * The {@code BORN_HOST} header field contains the born host info of a message in client side. 77 | *

78 | * When a message is sent, BORN_HOST will be set with the local host info, 79 | * on return from the send method, the message's BORN_HOST header field contains this value. 80 | * When a message is received its BORN_HOST header field contains this same value. 81 | */ 82 | public static final String BORN_HOST = "BornHost"; 83 | 84 | /** 85 | * The {@code STORE_TIMESTAMP} header field contains the store timestamp of a message in server side. 86 | *

87 | * When a message is sent, STORE_TIMESTAMP is ignored. 88 | *

89 | * When the send method returns it contains a server-assigned value. 90 | *

91 | * This filed is a {@code long} value, measured in milliseconds. 92 | */ 93 | public static final String STORE_TIMESTAMP = "StoreTimestamp"; 94 | 95 | /** 96 | * The {@code STORE_HOST} header field contains the store host info of a message in server side. 97 | *

98 | * When a message is sent, STORE_HOST is ignored. 99 | *

100 | * When the send method returns it contains a server-assigned value. 101 | */ 102 | public static final String STORE_HOST = "StoreHost"; 103 | 104 | /** 105 | * The {@code START_TIME} header field contains the start timestamp that a message 106 | * can be delivered to consumer client. 107 | *

108 | * If START_TIME field isn't set explicitly, use BORN_TIMESTAMP as the start timestamp. 109 | *

110 | * This filed is a {@code long} value, measured in milliseconds. 111 | */ 112 | public static final String START_TIME = "StartTime"; 113 | 114 | /** 115 | * The {@code STOP_TIME} header field contains the stop timestamp that a message 116 | * should be discarded after this timestamp, and no consumer can consume this message. 117 | *

118 | * {@code (START_TIME ~ STOP_TIME)} represents a absolute valid interval that a message 119 | * can be delivered in it. 120 | *

121 | * If a earlier timestamp is set than START_TIME, that means the message does not expire. 122 | *

123 | * This filed is a {@code long} value, measured in milliseconds. 124 | *

125 | * When an undelivered message's expiration time is reached, the message should be destroyed. 126 | * OMS does not define a notification of message expiration. 127 | */ 128 | public static final String STOP_TIME = "StopTime"; 129 | 130 | /** 131 | * The {@code TIMEOUT} header field contains the expiration time, it represents a 132 | * time-to-live value. 133 | *

134 | * {@code (BORN_TIMESTAMP ~ BORN_TIMESTAMP + TIMEOUT)} represents a relative valid interval 135 | * that a message can be delivered in it. 136 | * If the TIMEOUT field is specified as zero, that indicates the message does not expire. 137 | *

138 | * The TIMEOUT header field has higher priority than START_TIME/STOP_TIME header fields. 139 | *

140 | * When an undelivered message's expiration time is reached, the message should be destroyed. 141 | * OMS does not define a notification of message expiration. 142 | */ 143 | public static final String TIMEOUT = "Timeout"; 144 | 145 | /** 146 | * The {@code PRIORITY} header field contains the priority level of a message, 147 | * a message with higher priority values should be delivered preferentially. 148 | *

149 | * OMS defines a ten level priority value with 0 as the lowest priority and 9 as the highest. 150 | * OMS does not require that a provider strictly implement priority ordering of messages; 151 | * however, it should do its best to deliver expedited messages ahead of normal messages. 152 | *

153 | * If PRIORITY field isn't set explicitly, use {@code 4} as the default priority. 154 | */ 155 | public static final String PRIORITY = "Priority"; 156 | 157 | /** 158 | * The {@code RELIABILITY} header field contains the reliability level of a message. 159 | * A MOM server should guarantee the reliability level for a message. 160 | */ 161 | public static final String RELIABILITY = "Reliability"; 162 | 163 | /** 164 | * The {@code SEARCH_KEY} header field contains index search key of a message. 165 | * Clients can query similar messages by search key, and have a quick response. 166 | */ 167 | public static final String SEARCH_KEY = "SearchKey"; 168 | 169 | /** 170 | * The {@code SCHEDULE_EXPRESSION} header field contains schedule expression of a message. 171 | *

172 | * The message will be delivered by the specified SCHEDULE_EXPRESSION, which is a CRON expression. 173 | * 174 | * @see https://en.wikipedia.org/wiki/Cron#CRON_expression 175 | */ 176 | public static final String SCHEDULE_EXPRESSION = "ScheduleExpression"; 177 | 178 | /** 179 | * The {@code SHARDING_KEY} header field contains the sharding key a message. 180 | */ 181 | public static final String SHARDING_KEY = "ShardingKey"; 182 | 183 | /** 184 | * The {@code SHARDING_PARTITION} header field contains the sharding partition key a message. 185 | * The messages with same SHARDING_PARTITION should be sent to the same partition of a destination. 186 | */ 187 | public static final String SHARDING_PARTITION = "ShardingPartition"; 188 | 189 | /** 190 | * The {@code TRACE_ID} header field contains the trace id a message, which represents a global and unique 191 | * identification, and can be used in distributed system to trace the whole call link. 192 | */ 193 | public static final String TRACE_ID = "TraceId"; 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/ResourceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package io.openmessaging; 19 | 20 | import io.openmessaging.exception.OMSResourceNotExistException; 21 | import java.util.List; 22 | 23 | /** 24 | * The {@code ResourceManager} is responsible for providing a unified interface of resource management, 25 | * allows the user to manage the topic, queue, namespace resources. 26 | *

27 | * Create, fetch, update and destroy are the four basic functions of {@code ResourceManager}. 28 | *

29 | * And the {@code ResourceManager} also supports fetch and update resource properties dynamically. 30 | *

31 | * The properties of consumer and producer also are treated as {@code Resource}. {@code ResourceManager} 32 | * allows the user to fetch producer and consumer list in a specified topic or queue, 33 | * and update their resource properties dynamically. 34 | *

35 | * {@link MessagingAccessPoint#createResourceManager()} is the unique method to obtain a {@code ResourceManager} 36 | * instance, any changes made by this instance will reflect to the message-oriented middleware (MOM) or 37 | * other product behind the {@code MessagingAccessPoint}. 38 | * 39 | * @author vintagewang@apache.org 40 | * @author yukon@apache.org 41 | * 42 | * @version OMS 1.0 43 | * @since OMS 1.0 44 | */ 45 | public interface ResourceManager extends ServiceLifecycle { 46 | /** 47 | * Creates a {@code Namespace} resource for the specified {@code MessagingAccessPoint} with some preset properties, 48 | * updates if it already exists. 49 | *

50 | * Note that this method will simply create the physical Namespace in the specified {@code MessagingAccessPoint}. 51 | * 52 | * @param nsName a namespace name 53 | * @param properties the preset properties 54 | */ 55 | void createAndUpdateNamespace(String nsName, KeyValue properties); 56 | 57 | /** 58 | * Creates a {@code Topic} resource for the specified {@code MessagingAccessPoint} with some preset properties, 59 | * updates if it already exists. 60 | *

61 | * Note that this method will simply create the physical topic in the specified {@code MessagingAccessPoint}. 62 | * 63 | * @param topicName a topic name 64 | * @param properties the preset properties 65 | */ 66 | void createAndUpdateTopic(String topicName, KeyValue properties); 67 | 68 | /** 69 | * Creates a {@code Queue} resource for the specified {@code MessagingAccessPoint} with some preset properties, 70 | * updates if it already exists. 71 | *

72 | * Note that this method will simply create the physical queue in the specified {@code MessagingAccessPoint}. 73 | * 74 | * @param queueName a queue name 75 | * @param filter a specified filter 76 | * @param properties the preset properties 77 | */ 78 | void createAndUpdateQueue(String queueName, Filters filter, KeyValue properties); 79 | 80 | /** 81 | * Destroys a physical namespace in the specified {@code MessagingAccessPoint}. 82 | *

83 | * All this namespace related physical resources may be deleted immediately. 84 | * @param nsName a namespace name to be destroyed 85 | */ 86 | void destroyNamespace(String nsName); 87 | 88 | /** 89 | * Destroys a physical topic in the specified {@code MessagingAccessPoint}. 90 | *

91 | * All this topic related physical resources may be deleted immediately. 92 | * @param topicName a namespace name to be destroyed 93 | */ 94 | void destroyTopic(String topicName); 95 | 96 | /** 97 | * Destroys a physical queue in the specified {@code MessagingAccessPoint}. 98 | *

99 | * All this queue related physical resources may be deleted immediately. 100 | * @param queueName a namespace name to be destroyed 101 | */ 102 | void destroyQueue(String queueName); 103 | 104 | /** 105 | * Fetches the resource properties of a specified namespace. 106 | * 107 | * @param nsName a namespace name 108 | * @return the properties of this specified namespace 109 | * @throws OMSResourceNotExistException if the specified namespace is not exists 110 | */ 111 | KeyValue getNamespaceProperties(String nsName) throws OMSResourceNotExistException; 112 | 113 | /** 114 | * Fetches the resource properties of a specified topic. 115 | * 116 | * @param topicName a topic name 117 | * @return the properties of this specified topic 118 | * @throws OMSResourceNotExistException if the specified topic is not exists 119 | */ 120 | KeyValue getTopicProperties(String topicName) throws OMSResourceNotExistException; 121 | 122 | /** 123 | * Fetches the resource properties of a specified queue. 124 | * 125 | * @param queueName a queue name 126 | * @return the properties of this specified queue 127 | * @throws OMSResourceNotExistException if the specified queue is not exists 128 | */ 129 | KeyValue getQueueProperties(String queueName) throws OMSResourceNotExistException; 130 | 131 | /** 132 | * Each consumer has a unique id, this method is to fetch the consumer id list in a specified queue. 133 | * 134 | * @param queueName a queue name 135 | * @return the consumer id list in this queue 136 | * @throws OMSResourceNotExistException if the specified queue is not exists 137 | */ 138 | List consumerIdListInQueue(String queueName) throws OMSResourceNotExistException; 139 | 140 | /** 141 | * Returns the properties of the specified consumer instance with the given consumer id. 142 | * If no such consumer id exists, {@code OMSResourceNotExistException} will be thrown. 143 | * 144 | * @param consumerId The unique consumer id for an consumer instance 145 | * @return the properties of the matching consumer instance 146 | * @throws OMSResourceNotExistException if the specified consumer is not exists 147 | */ 148 | KeyValue getConsumerProperties(String consumerId) throws OMSResourceNotExistException; 149 | 150 | /** 151 | * Sets the consumer properties to the specified consumer instance. 152 | *

153 | * The new {@code KeyValue} becomes the current set of consumer properties, and the {@link 154 | * ResourceManager#getConsumerProperties(String)} will observe this change soon. If the argument is null, then the 155 | * current set of consumer properties will stay the same. 156 | * 157 | * @param consumerId the specified consumer id 158 | * @param properties the new consumer properties 159 | * @throws OMSResourceNotExistException if the specified consumer is not exists 160 | */ 161 | void setConsumerProperties(String consumerId, KeyValue properties) throws OMSResourceNotExistException; 162 | 163 | /** 164 | * Each producer has a unique id, this method is to fetch the producer id list in a specified queue. 165 | * 166 | * @param queueName a queue name 167 | * @return the producer id list in this queue 168 | * @throws OMSResourceNotExistException if the specified queue is not exists 169 | */ 170 | List producerIdListInQueue(String queueName) throws OMSResourceNotExistException; 171 | 172 | /** 173 | * Each producer has a unique id, this method is to fetch the producer id list in a specified topic. 174 | * 175 | * @param topicName a topic name 176 | * @return the producer id list in this topic 177 | * @throws OMSResourceNotExistException if the specified topic is not exists 178 | */ 179 | List producerIdListInTopic(String topicName) throws OMSResourceNotExistException; 180 | 181 | /** 182 | * Returns the properties of the specified producer instance with the given producer id. 183 | * If no such producer id exists, {@code OMSResourceNotExistException} will be thrown. 184 | * 185 | * @param producerId The unique consumer id for an producer instance 186 | * @return the properties of the matching producer instance 187 | * @throws OMSResourceNotExistException if the specified producer is not exists 188 | */ 189 | KeyValue getProducerProperties(String producerId) throws OMSResourceNotExistException; 190 | 191 | /** 192 | * Sets the producer properties to the specified producer instance. 193 | *

194 | * The new {@code KeyValue} becomes the current set of producer properties, and the {@link 195 | * ResourceManager#getProducerProperties(String)} will observe this change soon. If the argument is null, then the 196 | * current set of producer properties will stay the same. 197 | * 198 | * @param producerId the specified producer id 199 | * @param properties the new producer properties 200 | * @throws OMSResourceNotExistException if the specified producer is not exists 201 | */ 202 | void setProducerProperties(String producerId, KeyValue properties) throws OMSResourceNotExistException; 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DefaultProducer.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.File; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.concurrent.BrokenBarrierException; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | import java.util.concurrent.CyclicBarrier; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | import java.util.zip.Deflater; 17 | 18 | import io.openmessaging.BatchToPartition; 19 | import io.openmessaging.BytesMessage; 20 | import io.openmessaging.KeyValue; 21 | import io.openmessaging.Message; 22 | import io.openmessaging.MessageFactory; 23 | import io.openmessaging.MessageHeader; 24 | import io.openmessaging.Producer; 25 | import io.openmessaging.Promise; 26 | 27 | // 保存cache和对应的pos 28 | class CachePos { 29 | byte[] cache; 30 | // 表示该cache当前写到了哪个位置 31 | int pos; 32 | public CachePos(int size) { 33 | this.cache = new byte[size]; 34 | this.pos = 0; 35 | } 36 | } 37 | 38 | public class DefaultProducer implements Producer { 39 | /** 6/2 改消息序列化方式:有些固定出现的字符串可以hardcode */ 40 | // 用来统计数目 41 | private static AtomicInteger producerCnt = new AtomicInteger(0); 42 | private final int prodNo; 43 | private MessageFactory messageFactory = null; 44 | private KeyValue properties; 45 | // 用来保存编码后的消息 46 | private ByteBuffer encodedMsg; 47 | 48 | /** 5/26 更新:架构更改,每个producer维护对每个topic文件都维护一个 49 | 自己所写消息的cache 50 | */ 51 | private static final int KB_2 = (1<<11); 52 | private static final int[] SIZE_ARRAY = { 53 | // // 2KB 4KB 8KB 16KB 32KB 64KB 128KB 54 | // KB_2, KB_2<<1, KB_2<<2, KB_2<<3, KB_2<<4, KB_2<<5, KB_2<<6, 55 | // 64KB 4KB 8KB 16KB 32KB 64KB 128KB 56 | KB_2<<5, KB_2<<1, KB_2<<2, KB_2<<3, KB_2<<4, KB_2<<5, KB_2<<6, 57 | // 256KB 512KB 1024KB 58 | KB_2<<7, KB_2<<8, KB_2<<9 59 | }; 60 | 61 | /** 5/31 更新:Chunk大小用8KB */ 62 | private final int CACHE_SIZE; 63 | // 保存某个topic或者queue对应的Cache。cache中保存的是已经序列化好的消息byte[] 64 | private HashMap msgCacheMap; 65 | 66 | /** 5/27架构大改:每个Producer将自己生产的消息写到一个文件去 */ 67 | private FileWriteCache fileWriteCache; 68 | private final String storePath; 69 | 70 | public DefaultProducer(KeyValue properties) { 71 | this.messageFactory = new DefaultMessageFactory(); 72 | this.properties = properties; 73 | this.storePath = this.properties.getString("STORE_PATH"); 74 | this.encodedMsg = ByteBuffer.allocate(Constant.MAX_MSG_SIZE); 75 | this.prodNo = producerCnt.getAndIncrement(); 76 | this.CACHE_SIZE = SIZE_ARRAY[this.prodNo % SIZE_ARRAY.length]; 77 | 78 | /** 5/26 更新:架构更改,每个producer维护对每个topic文件都维护一个 79 | 自己所写消息的cache 80 | 5/30 更新:不用cache,直接序列化每一条消息然后写文件看看 81 | */ 82 | this.msgCacheMap = new HashMap<>(100); 83 | try { 84 | this.fileWriteCache = new FileWriteCache(Paths.get(storePath, String.valueOf(this.prodNo)).toString()); 85 | } catch (IOException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | // 释放资源 90 | public void release() { 91 | encodedMsg.clear(); 92 | encodedMsg = null; 93 | } 94 | 95 | @Override public BytesMessage createBytesMessageToTopic(String topic, byte[] body) { 96 | // 每产生一条消息都要去看看这个topic有没有记录 97 | return messageFactory.createBytesMessageToTopic(topic, body); 98 | } 99 | 100 | @Override public BytesMessage createBytesMessageToQueue(String queue, byte[] body) { 101 | // 每产生一条消息都要去看看这个queue有没有记录 102 | return messageFactory.createBytesMessageToQueue(queue, body); 103 | } 104 | 105 | @Override public void start() { 106 | 107 | } 108 | 109 | @Override public void shutdown() { 110 | } 111 | // 用来让生产者等待所有生产者生产消息完毕 112 | private static volatile CyclicBarrier barrier = null; 113 | @Override public void flush() { 114 | // 把自己的cache里面的数据全部压缩并写入FileWriteCache 115 | for(final String name : this.msgCacheMap.keySet()) { 116 | CachePos cp = this.msgCacheMap.get(name); 117 | if(cp.pos == 0) continue; 118 | compactAndWrite2File(name, cp.cache, cp.pos); 119 | cp.pos = 0; 120 | } 121 | this.msgCacheMap.clear(); 122 | this.fileWriteCache.flush(); 123 | this.fileWriteCache.close(); 124 | if(barrier == null) { 125 | synchronized (DefaultProducer.class){ 126 | if(barrier == null){ 127 | MyLogger.log(MyLogger.INFO_TAG, "Producer cnt:" + producerCnt.get()); 128 | barrier = new CyclicBarrier(producerCnt.get()); 129 | } 130 | } 131 | } 132 | try { 133 | barrier.await(); 134 | } catch (InterruptedException e) { 135 | e.printStackTrace(); 136 | } catch (BrokenBarrierException e) { 137 | e.printStackTrace(); 138 | } 139 | if(this.prodNo == 0) { 140 | saveMetaData(); 141 | } 142 | } 143 | 144 | private void saveMetaData() { 145 | // 保存topicNo还有生产者编号映射 146 | BufferedWriter writer = null; 147 | File file = Paths.get(this.storePath, Constant.TOPIC_NO_FILE).toFile(); 148 | try 149 | { 150 | writer = new BufferedWriter( new FileWriter(file)); 151 | ConcurrentHashMap topicNoMap = MyUtil.getTopicNoMap(); 152 | for(final String name : topicNoMap.keySet()) { 153 | Integer topicNo = topicNoMap.get(name); 154 | StringBuilder sb = new StringBuilder(); 155 | sb.append(name).append(':').append(topicNo); 156 | writer.write(sb.toString()); 157 | writer.newLine(); 158 | } 159 | } 160 | catch ( IOException e) 161 | { 162 | } 163 | finally 164 | { 165 | try 166 | { 167 | if ( writer != null) 168 | writer.close( ); 169 | } 170 | catch ( IOException e) 171 | { 172 | } 173 | } 174 | } 175 | 176 | @Override public KeyValue properties() { 177 | return properties; 178 | } 179 | 180 | 181 | 182 | /* 编码消息在Producer线程进行 */ 183 | @Override public void send(Message message) { 184 | if (message == null) throw new ClientOMSException("Message should not be null"); 185 | String topic = message.headers().getString(MessageHeader.TOPIC); 186 | String queue = message.headers().getString(MessageHeader.QUEUE); 187 | if ((topic == null && queue == null) || (topic != null && queue != null)) { 188 | throw new ClientOMSException(String.format("Queue:%s Topic:%s should put one and only one", true, queue)); 189 | } 190 | String name = (topic == null? queue:topic); 191 | encodeMsg((DefaultBytesMessage)message, name); 192 | encodedMsg.flip(); 193 | int dataLen = encodedMsg.remaining(); 194 | write2OwnCache(name, encodedMsg.array(), dataLen); 195 | } 196 | 197 | private void compactAndWrite2File(final String name, final byte[] data, final int dataLen) { 198 | /** 6/3更新:不压缩,直接写 */ 199 | // topicNo: 200 | int topicNo = MyUtil.getTopicNo(name); 201 | // 写入FileWriteCache 202 | this.fileWriteCache.write(topicNo, data, dataLen); 203 | } 204 | 205 | private void write2OwnCache(final String name, final byte[] data, final int dataLen) { 206 | // 延迟初始化cachePos:只有到写入的才初始化 207 | if(!this.msgCacheMap.containsKey(name)) { 208 | // 不需要double check来防止多次初始化,因为一个生产者只在一条线程执行 209 | this.msgCacheMap.put(name, new CachePos(CACHE_SIZE)); 210 | } 211 | /** 5/31 Update:用64KB作为chunk大小,那么如果某条消息超过chunk大小,先把 212 | * chunk的内容写到FileWriteCache,再单独把这条大消息压缩并写入FileWriteCache 213 | */ 214 | /** 215 | * 6/3更新:试试不用压缩 216 | */ 217 | CachePos cp = this.msgCacheMap.get(name); 218 | if(cp.pos + dataLen > CACHE_SIZE) { 219 | if(cp.pos > 0) { 220 | compactAndWrite2File(name, cp.cache, cp.pos); 221 | cp.pos = 0; 222 | } 223 | } 224 | 225 | if(dataLen > CACHE_SIZE) { 226 | // 是大消息,直接压缩并写入 227 | compactAndWrite2File(name, data, dataLen); 228 | }else{ 229 | System.arraycopy(data, 0, cp.cache, cp.pos, dataLen); 230 | cp.pos += dataLen; 231 | } 232 | } 233 | 234 | 235 | 236 | private void encodeMsg(final DefaultBytesMessage message, final String topic) { 237 | /** 238 | * 6/2 更新:header部分只存messageId的value,key都不用存;Topic也不用存! 239 | * Property的key:PRO_OFFSET 用1byte表示! 240 | * 一条消息编码为: 241 | * 头部:MSG_HEADER_BEGIN + MSG_ID_LEN(Short)+ MSG_ID 242 | * Property: (KEY_LEN(byte) + key_str + VALUE_LEN (Short) + VALUE_STR) 243 | * .... 244 | * MSG_BODY: MSG_BODY_BEGIN (-3) + MSG_BODY_LEN (Short) + MSG_BODY 245 | */ 246 | encodedMsg.clear(); 247 | encodeMsgBody(encodedMsg, message, topic); 248 | } 249 | 250 | /* 251 | * 编码头部 252 | */ 253 | private void encodeMsgHeader(final ByteBuffer encodeMsg, final DefaultBytesMessage message) { 254 | // 头部只存了MSG_ID: len(short) + value 255 | DefaultKeyValue header = (DefaultKeyValue) message.headers(); 256 | String msgIdVal = (String)header.get(Constant.MSGID_KEY); 257 | byte[] valueBytes = msgIdVal.getBytes(); 258 | int valueBytesLen = valueBytes.length; 259 | encodedMsg.putShort((short)valueBytesLen); 260 | encodedMsg.put(valueBytes); 261 | } 262 | 263 | private void encodeMsgBody(final ByteBuffer encodeMsg, 264 | final DefaultBytesMessage message, final String topicName) { 265 | /** 266 | * 6/2 更新:header部分只存messageId的value,key都不用存;Topic也不用存! 267 | * Property的key:PRO_OFFSET 用1byte表示! 268 | * 一条消息编码为: 269 | * 头部:MSG_HEADER_BEGIN + MSG_ID_LEN(Short)+ MSG_ID 270 | * Property: (KEY_LEN(byte) + key_str + VALUE_LEN (Short) + VALUE_STR) 271 | * .... 272 | * MSG_BODY: MSG_BODY_BEGIN (-3) + MSG_BODY_LEN (Short) + MSG_BODY 273 | */ 274 | // 消息头开始标识 275 | encodeMsg.put(Constant.MSG_HEADER_BEGIN); 276 | // 编码头部 277 | encodeMsgHeader(encodeMsg, message); 278 | DefaultKeyValue property = (DefaultKeyValue)message.properties(); 279 | if(property != null) { 280 | encodeKeyValue(encodeMsg, property, topicName); 281 | } 282 | // 消息body部分 283 | encodeMsg.put(Constant.MSG_BODY_BEGIN); 284 | int msgBodyLen = message.getBody().length; 285 | encodeMsg.putShort((short)msgBodyLen); 286 | encodeMsg.put(message.getBody()); 287 | } 288 | 289 | private void encodeKeyValue(final ByteBuffer encodedMsg, 290 | final KeyValue keyValue, final String topicName) { 291 | DefaultKeyValue kv = (DefaultKeyValue)keyValue; 292 | /** 293 | * 6/3 更新:header部分只存messageId的value,key都不用存;Topic也不用存! 294 | * Property的key:PRO_OFFSET 用1byte表示! 295 | * 一条消息编码为: 296 | * 头部:MSG_HEADER_BEGIN + MSG_ID_LEN(Short)+ MSG_ID 297 | * Property: (KEY_LEN(byte) + key_str + VALUE_LEN (Short) + VALUE_STR) 298 | * .... 299 | * MSG_BODY: MSG_BODY_BEGIN (-3) + MSG_BODY_LEN (Short) + MSG_BODY 300 | */ 301 | for (final String key : kv.keySet()) 302 | { 303 | 304 | Object value = kv.get(key); 305 | if(key.length() > 2 && key.charAt(0) == 'P' && key.charAt(1) == 'R') { 306 | continue; 307 | }else{ 308 | // 需要1 byte标识key的长度 309 | byte[] keyBytes = key.getBytes(); 310 | // 5/22更新:仍旧用1byte 311 | byte keyLen = (byte) keyBytes.length; 312 | //int keyLen = keyBytes.length; 313 | encodedMsg.put(keyLen); 314 | encodedMsg.put(keyBytes); 315 | byte[] valueBytes = ((String)value).getBytes(); 316 | int valueBytesLen = valueBytes.length; 317 | encodedMsg.putShort((short)valueBytesLen); 318 | encodedMsg.put(valueBytes); 319 | } 320 | } 321 | } 322 | 323 | @Override public void send(Message message, KeyValue properties) { 324 | throw new UnsupportedOperationException("Unsupported"); 325 | } 326 | 327 | @Override public Promise sendAsync(Message message) { 328 | throw new UnsupportedOperationException("Unsupported"); 329 | } 330 | 331 | @Override public Promise sendAsync(Message message, KeyValue properties) { 332 | throw new UnsupportedOperationException("Unsupported"); 333 | } 334 | 335 | @Override public void sendOneway(Message message) { 336 | throw new UnsupportedOperationException("Unsupported"); 337 | } 338 | 339 | @Override public void sendOneway(Message message, KeyValue properties) { 340 | throw new UnsupportedOperationException("Unsupported"); 341 | } 342 | 343 | @Override public BatchToPartition createBatchToPartition(String partitionName) { 344 | throw new UnsupportedOperationException("Unsupported"); 345 | } 346 | 347 | @Override public BatchToPartition createBatchToPartition(String partitionName, KeyValue properties) { 348 | throw new UnsupportedOperationException("Unsupported"); 349 | } 350 | } -------------------------------------------------------------------------------- /src/main/java/io/openmessaging/demo/DemoTester.java: -------------------------------------------------------------------------------- 1 | package io.openmessaging.demo; 2 | 3 | import io.openmessaging.KeyValue; 4 | import io.openmessaging.Message; 5 | import io.openmessaging.MessageHeader; 6 | import io.openmessaging.Producer; 7 | import io.openmessaging.PullConsumer; 8 | 9 | import java.awt.SystemColor; 10 | import java.io.BufferedReader; 11 | import java.io.BufferedWriter; 12 | import java.io.File; 13 | import java.io.FileNotFoundException; 14 | import java.io.FileReader; 15 | import java.io.FileWriter; 16 | import java.io.IOException; 17 | import java.lang.management.ManagementFactory; 18 | import java.lang.management.RuntimeMXBean; 19 | import java.nio.file.Paths; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.HashSet; 24 | import java.util.List; 25 | import java.util.Random; 26 | import java.util.Set; 27 | import java.util.UUID; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.TimeUnit; 32 | import java.util.concurrent.atomic.AtomicInteger; 33 | import java.util.concurrent.atomic.AtomicLong; 34 | 35 | import org.junit.Assert; 36 | 37 | public class DemoTester { 38 | static byte[] msgBody = new byte[64]; 39 | static void fillMsgBody() { 40 | for(int i = 0; i < 64; ++i) 41 | msgBody[i] = (byte) i; 42 | } 43 | // 保存topic 44 | static ArrayList topics = new ArrayList<>(100); 45 | // 保存queue名字 46 | static ArrayList queues = new ArrayList<>(100); 47 | static String[] topicSeed = { 48 | "Alibaba", "Tencent", "Baidu", "Microsoft", "Huawei", "Google", 49 | "Intel", "Amazon", "NetEase", "Facebook", "DiDi" 50 | }; 51 | // private static String storePath = "/home/sfc/jenson/alibaba/2017/store"; 52 | private static String storePath = "E:\\Contest\\阿里中间件2017\\store"; 53 | // 从文件中读出topic和queue的名字 54 | private static void constrcutTopicAndQueue() throws FileNotFoundException, IOException { 55 | File topicFile = Paths.get(storePath, Constant.TOPIC_NO_FILE).toFile(); 56 | try(BufferedReader reader = new BufferedReader(new FileReader(topicFile))) { 57 | String line = null; 58 | while((line = reader.readLine()) != null) { 59 | int idx = line.indexOf(':'); 60 | String name = line.substring(0, idx); 61 | String no = line.substring(idx+1); 62 | if(name.charAt(0) == 'Q') { 63 | // 是队列 64 | queues.add(name); 65 | }else{ 66 | topics.add(name); 67 | } 68 | // System.out.println("name:"+name+" no:"+no); 69 | } 70 | } 71 | // System.out.println("Queues:"); 72 | // for(final String que : queues) { 73 | // System.out.println(que); 74 | // } 75 | // System.out.println("Topics:"); 76 | // for(final String topic : topics) { 77 | // System.out.println(topic); 78 | // } 79 | 80 | } 81 | // 构造Num条topic并保存到topics 82 | private static void constructNumTopics(final int num) { 83 | Random rand = new Random(); 84 | for(int i = 0; i < num; ++i) { 85 | int idx = rand.nextInt(topicSeed.length); 86 | topics.add(topicSeed[idx] + i); 87 | } 88 | } 89 | // 构造Num条queue并保存到queues 90 | private static void constructNumQueues(final int num) { 91 | Random rand = new Random(); 92 | String queName = "QUEUE"; 93 | for(int i = 0; i < num; ++i) { 94 | queues.add(queName + i); 95 | } 96 | } 97 | 98 | final static String strVal = "HelloWorld"; 99 | private static Message constructMsg(final String topic, final Producer producer) { 100 | Message msg = null; 101 | // Random rand = new Random(); 102 | if(topic.charAt(0) != 'Q') { 103 | msg = producer.createBytesMessageToTopic(topic, msgBody); 104 | }else{ 105 | msg = producer.createBytesMessageToQueue(topic, msgBody); 106 | } 107 | String strKey = "strKey"; 108 | // string 109 | msg.putProperties(strKey, strVal); 110 | 111 | return msg; 112 | } 113 | static volatile boolean t1TimeIsUp = false; 114 | public static void main(String[] args) { 115 | MyLogger.log(MyLogger.INFO_TAG, "Start test.."); 116 | // if(args.length < 1) { 117 | // System.out.println("Error. Too few arguments"); 118 | // return; 119 | // } 120 | // // 是否是生产消息模式 121 | // final boolean isWriteMode = (args[0].equals("w")?true:false); 122 | final boolean isWriteMode = false; 123 | // topic和队列总共的数目 124 | final int totalTopicAndQueueCnt = 100; 125 | // 队列的数目,应该>=消费者数目 126 | final int queueCnt = 10; 127 | // 线程数,应该<=queueCnt; 128 | final int threadCnt = queueCnt; 129 | final int MSG_NUM = 40000; 130 | 131 | try { 132 | Thread.sleep(1000); 133 | } catch (InterruptedException e2) { 134 | e2.printStackTrace(); 135 | } 136 | KeyValue properties = new DefaultKeyValue(); 137 | /* 138 | //实际测试时利用 STORE_PATH 传入存储路径 139 | //所有producer和consumer的STORE_PATH都是一样的,选手可以自由在该路径下创建文件 140 | */ 141 | properties.put("STORE_PATH", storePath); 142 | AtomicLong time = new AtomicLong(0); 143 | ExecutorService consumerThreads = Executors.newFixedThreadPool(threadCnt); 144 | // 给线程编号用 145 | final AtomicInteger globalThreadNo = new AtomicInteger(0); 146 | 147 | if(isWriteMode) { 148 | fillMsgBody(); 149 | RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 150 | String name = runtime.getName(); 151 | System.out.println("当前进程的标识为:"+name); 152 | constructNumTopics(totalTopicAndQueueCnt-queueCnt); 153 | constructNumQueues(queueCnt); 154 | 155 | try { 156 | Thread.sleep(3000); 157 | } catch (InterruptedException e2) { 158 | e2.printStackTrace(); 159 | } 160 | 161 | // 保存所有的topic和queue 162 | ArrayList totalTopicAndQueue = new ArrayList<>(); 163 | totalTopicAndQueue.addAll(topics); 164 | totalTopicAndQueue.addAll(queues); 165 | final int totalTopicQueSize = totalTopicAndQueue.size(); 166 | ExecutorService producerThreads = Executors.newFixedThreadPool(threadCnt); 167 | 168 | // 总共已经发送的消息 169 | AtomicLong totalSentMsg = new AtomicLong(0); 170 | 171 | System.out.println("Start sending msg..."); 172 | 173 | long startTime = System.currentTimeMillis(); 174 | 175 | for(int cnt = 0; cnt < threadCnt; ++cnt) { 176 | producerThreads.submit(()->{ 177 | // 每个生产者生产400万 178 | final long sendMsgPerProducer = MSG_NUM; 179 | // 已经发送的消息 180 | long msgSent = 0; 181 | int threadNo = globalThreadNo.getAndIncrement(); 182 | // 每个线程(生产者)随机生产numMsgPerThread条消息 183 | Producer threadP = new DefaultProducer(properties); 184 | Random rand = new Random(); 185 | while(msgSent < sendMsgPerProducer) { 186 | // 随机取一个topic 187 | int idx = rand.nextInt(totalTopicQueSize); 188 | String topic = totalTopicAndQueue.get(idx); 189 | Message msg = constructMsg(topic, threadP); 190 | threadP.send(msg); 191 | msgSent++; 192 | } 193 | threadP.flush(); 194 | totalSentMsg.addAndGet(msgSent); 195 | } 196 | ); 197 | } 198 | try { 199 | producerThreads.shutdown(); 200 | // 直接模拟等5分钟 201 | producerThreads.awaitTermination(5, TimeUnit.MINUTES); 202 | t1TimeIsUp = true; 203 | } catch (InterruptedException e2) { 204 | e2.printStackTrace(); 205 | } 206 | long endTime = System.currentTimeMillis(); 207 | double timeSec = (endTime - startTime) / 1000.0; 208 | long totalMsg = totalSentMsg.get(); 209 | double sentTps = (totalMsg/timeSec); 210 | System.out.println("Sending " + totalMsg + 211 | " takes:" + timeSec + " S Send TPS:" + sentTps); 212 | 213 | }else { 214 | try { 215 | constrcutTopicAndQueue(); 216 | } catch (FileNotFoundException e1) { 217 | e1.printStackTrace(); 218 | } catch (IOException e1) { 219 | e1.printStackTrace(); 220 | } 221 | globalThreadNo.set(0); 222 | time.set(0); 223 | final AtomicLong actualConsumeMsgNum = new AtomicLong(0); 224 | System.out.println("Total topics:"+ topics.size()+" queue:"+queues.size()); 225 | System.out.println("Start consuming msg..."); 226 | long startConsumer = System.currentTimeMillis(); 227 | // 顺序构造threadCnt个消费者 228 | final ArrayList csList = new ArrayList<>(threadCnt); 229 | final HashMap> csTopicSet = new HashMap<>(); 230 | final HashMap csQueueMap = new HashMap<>(); 231 | for(int i = 0; i < threadCnt; ++i) { 232 | DefaultPullConsumer consumer = new DefaultPullConsumer(properties); 233 | Random rand = new Random(); 234 | int totalTopicCnt = topics.size(); 235 | // 每个消费者订阅的topic数 236 | int nTopics = totalTopicCnt/queueCnt; 237 | 238 | // 绑定的queue 239 | final String queue = queues.get(i); 240 | csQueueMap.put(i, queue); 241 | // 随机订阅topic 242 | Set subscribeTopics = new HashSet(); 243 | while(subscribeTopics.size() < nTopics) { 244 | int topicIdx = rand.nextInt(totalTopicCnt); 245 | String topic = topics.get(topicIdx); 246 | if(subscribeTopics.contains(topic)) { 247 | continue; 248 | } 249 | subscribeTopics.add(topic); 250 | // topicOffset.put(topic, 0); 251 | } 252 | StringBuilder sb = new StringBuilder(); 253 | sb.append("Consumer ").append(i).append(" subscribe ") 254 | .append(subscribeTopics.size()).append(" topics:\n"); 255 | for(String t : subscribeTopics) { 256 | sb.append(t).append("\n"); 257 | } 258 | MyLogger.log(MyLogger.INFO_TAG, sb.toString()); 259 | consumer.attachQueue(queue, subscribeTopics); 260 | csList.add(consumer); 261 | csTopicSet.put(i, subscribeTopics); 262 | } 263 | for(int i = 0; i < threadCnt; ++i) { 264 | // 同样的,每个线程起一个消费者,每个消费者绑定一个queue 265 | consumerThreads.submit(()-> { 266 | int threadNo = globalThreadNo.getAndIncrement(); 267 | DefaultPullConsumer consumer = csList.get(threadNo); 268 | Set subscribeTopics = csTopicSet.get(threadNo); 269 | final String queue = csQueueMap.get(threadNo); 270 | // 接下来开始消费 271 | MyLogger.log(MyLogger.INFO_TAG, "Consumer "+threadNo+ 272 | " start polling msg."); 273 | // 休眠一下模拟实际测评时是单线程构造 274 | try { 275 | Thread.sleep(1000); 276 | } catch (InterruptedException e) { 277 | e.printStackTrace(); 278 | } 279 | // 实际消费的 280 | long consumeMsg = 0; 281 | // 每个消费者最多消费400万 282 | final long maxConsumedMsg = MSG_NUM; 283 | while (true) { 284 | // 每个消费者最多消费 285 | if(consumeMsg >= maxConsumedMsg) { 286 | break; 287 | } 288 | // 至少消费了一条消息 289 | boolean consumeMsgSucceed = false; 290 | try{ 291 | Message message = consumer.poll(); 292 | if (message == null) { 293 | //拉取为null则认为消息已经拉取完毕 294 | break; 295 | } 296 | // MyLogger.log(MyLogger.INFO_TAG, "Consumer:"+threadNo+" "+message.toString()); 297 | ++consumeMsg; 298 | /* TODO:为了测试吞吐量,不用比较了 */ 299 | String topic = message.headers().getString(MessageHeader.TOPIC); 300 | String queueName = message.headers().getString(MessageHeader.QUEUE); 301 | String actualName = null; 302 | //实际测试时,会一一比较各个字段 303 | if (topic != null) { 304 | if(!subscribeTopics.contains(topic)) { 305 | String errMsg = "Consumer(Thread) "+threadNo 306 | +" doesnot have topic:"+topic; 307 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 308 | System.out.println(errMsg); 309 | throw new ClientOMSException(errMsg); 310 | } 311 | actualName = topic; 312 | } else if(queueName != null){ 313 | if(!queueName.equals(queue)) { 314 | String errMsg = "Consumer(Thread) "+threadNo 315 | +" attach to queue:"+queue 316 | +" but consume msg from queue:"+queueName; 317 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 318 | System.out.println(errMsg); 319 | throw new ClientOMSException(errMsg); 320 | } 321 | actualName = queueName; 322 | }else{ 323 | String errMsg = "Consumer(Thread) "+threadNo 324 | +" both topic and queueName are null!"; 325 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 326 | System.out.println(errMsg); 327 | throw new ClientOMSException(errMsg); 328 | } 329 | consumeMsgSucceed = true; 330 | }catch(Exception ex) { 331 | String errMsg = "Error in consumer:"+threadNo+" ever consumer a msg?:"+consumeMsgSucceed+" err msg:"+ex.getMessage(); 332 | MyLogger.log(MyLogger.ERROR_TAG, errMsg); 333 | ex.printStackTrace(); 334 | throw new ClientOMSException(errMsg); 335 | } 336 | } 337 | 338 | actualConsumeMsgNum.addAndGet(consumeMsg); 339 | MyLogger.log(MyLogger.INFO_TAG, "Consumer "+threadNo+" finish polling msg."); 340 | 341 | } 342 | ); 343 | } 344 | consumerThreads.shutdown(); 345 | try { 346 | consumerThreads.awaitTermination(30, TimeUnit.MINUTES); 347 | } catch (InterruptedException e) { 348 | // TODO Auto-generated catch block 349 | e.printStackTrace(); 350 | } 351 | long endConsumer = System.currentTimeMillis(); 352 | double T2 = (endConsumer - startConsumer)/1000.0; 353 | double tps = (actualConsumeMsgNum.get()/T2); 354 | System.out.println("Actual consume msg:" + actualConsumeMsgNum.get()); 355 | System.out.println("T2="+(T2) + "S\n" 356 | + "Consume Tps:" + tps); 357 | 358 | } 359 | 360 | } 361 | } 362 | 363 | --------------------------------------------------------------------------------