├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── shgx │ │ └── queue │ │ ├── QueueApplication.java │ │ ├── controller │ │ └── BusinessController.java │ │ ├── service │ │ ├── ThreadPoolManager.java │ │ ├── ThreadPoolService.java │ │ ├── WorkConsumerService.java │ │ └── WorkProducerService.java │ │ └── utils │ │ └── RedisUtils.java └── resources │ └── application.yml └── test └── java └── com └── shgx └── queue └── QueueApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guangxush/WorkQueue/2a74e2034181e73638ae26997867e8c4554e901e/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 背景 2 | 1. 在用户量比较高的情况下,会有很多请求过来,此时线程池处理能力已经无法满足需求,如何解决? 3 | - 方案:可以将当前的计算线程先保存起来,放入高并发消息队列中,等线程池中的任务较少时,再从队列拉数据去执行任务。 4 | 2. 如果微服务框架下有服务被熔断或者降级,其他任务不能查询该服务的信息,又不能直接丢弃,如何解决? 5 | - 方案:将当前任务暂时存放在消息队列中,等待服务恢复之后,在从消息队列中拉取任务执行。 6 | 7 | ## 执行流程 8 | 1. 当批量用户请求过来时,先把用户请求放入Redis Map中或者ConcurrentHashMap中,对用户请求去重,保证交易号幂等性; 9 | 2. 这里创建一个线程池处理用户请求; 10 | - 如果当前线程池请求数目<核心线程池数目,直接让核心线程池去执行任务 11 | - 如果大于核心线程池数目,那么加入到线程队列中 12 | - 如果队列已满,那么创建新的线程去处理,如果新的线程数目超过了最大线程数目,任务将被拒绝 13 | - 拒绝策略是将任务提交到kafka消息队列中进行存储 14 | 3. 等到线程池中的任务较少或者夜间用户访问较少的时候,从消息队列中拉取请求重新进行处理 15 | 4. 如果处理失败将任务重新加入到消息队列中,等待一定的时机进行重试。 16 | ![项目框架](https://github.com/guangxush/iTechHeart/blob/master/image/WorkQueue/workqueue.png) 17 | 18 | ## 代码实现 19 | 20 | - 模拟线程处理用户请求 21 | ``` 22 | @Override 23 | public void run() { 24 | //业务操作 25 | System.out.println("多线程已经处理订单插入系统,订单号:"+ businessNo); 26 | } 27 | ``` 28 | - 采用线程池处理以上请求 29 | ``` 30 | @Autowired 31 | private WorkProducerService producerService; 32 | 33 | @Autowired 34 | private RedisUtils redisUtils; 35 | 36 | @Override 37 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 38 | factory = beanFactory; 39 | } 40 | 41 | /** 42 | * 订单的缓冲队列,当线程池满了,则将订单存入到此缓冲队列 43 | */ 44 | Queue msgQueue = new LinkedBlockingQueue<>(); 45 | 46 | /** 47 | * 当线程池的容量满了,执行下面代码,将订单存入到缓冲队列 48 | */ 49 | final RejectedExecutionHandler handler = new RejectedExecutionHandler() { 50 | @Override 51 | public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 52 | if (IS_QUEUE) { 53 | //订单加入到缓冲队列 54 | msgQueue.offer(((ThreadPoolService) r).getBusinessNo()); 55 | } else { 56 | //增加并发量,订单加入到kafka消息队列 57 | producerService.sendMessage(((ThreadPoolService) r).getBusinessNo()); 58 | } 59 | log.info("系统任务已满,把此订单交给(调度线程池)逐一处理,订单号:" + ((ThreadPoolService) r).getBusinessNo()); 60 | } 61 | }; 62 | 63 | 64 | /** 65 | * 创建线程池 66 | */ 67 | final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(WORK_QUEUE_SIZE), this.handler); 68 | 69 | /** 70 | * 将任务加入订单线程池 71 | */ 72 | public void addOrders(String orderId) { 73 | log.info("此订单准备添加到线程池,订单号:" + orderId); 74 | //验证当前进入的订单是否已经存在 75 | if (redisUtils.getSetMembers(orderId) == null) { 76 | redisUtils.addSetMembers(orderId, new Object()); 77 | ThreadPoolService businessThread = new ThreadPoolService(orderId); 78 | threadPool.execute(businessThread); 79 | } 80 | } 81 | 82 | /** 83 | * 获取目前的活跃线程数量 84 | * @return 85 | */ 86 | public int getActiveCount(){ 87 | return threadPool.getActiveCount(); 88 | } 89 | 90 | /** 91 | * 线程池的定时任务----> 称为(调度线程池)。此线程池支持定时以及周期性执行任务的需求。 92 | */ 93 | final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); 94 | 95 | 96 | /** 97 | * 检查(调度线程池),每秒执行一次,查看订单的缓冲队列是否有订单记录,则重新加入到线程池 98 | */ 99 | final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate(new Runnable() { 100 | @Override 101 | public void run() { 102 | //判断缓冲队列是否存在记录 103 | if (!msgQueue.isEmpty()) { 104 | //当线程池的队列容量少于workQueueSize,则开始把缓冲队列的订单加入到线程池 105 | if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) { 106 | String orderId = (String) msgQueue.poll(); 107 | ThreadPoolService businessThread = new ThreadPoolService(orderId); 108 | threadPool.execute(businessThread); 109 | log.info("(调度线程池)缓冲队列出现订单业务,重新添加到线程池,订单号:" + orderId); 110 | } 111 | } 112 | } 113 | }, 0, 1, TimeUnit.SECONDS); 114 | 115 | ``` 116 | 117 | 将处理不了的请求,发送给消息队列暂存 118 | 119 | ``` 120 | public boolean sendMessage(String msg) { 121 | try { 122 | String result = kafkaTemplate.send("business", msg).get().toString(); 123 | if (result != null) { 124 | return true; 125 | } 126 | } catch (Exception e) { 127 | return false; 128 | } 129 | return false; 130 | } 131 | ``` 132 | 133 | 等到线程池中的请求能够处理当前任务时,消费消息并对请求进行处理(可以设置定时任务进行拉取) 134 | ``` 135 | @KafkaListener(id = "test-consumer-group", topics = "business",containerFactory = "ackContainerFactory") 136 | public void ackListener(ConsumerRecord record, Acknowledgment ack) { 137 | log.info("Receive Business Number :------------"+record.value()); 138 | if(threadPoolManager.getActiveCount()< MAX_POOL_SIZE){ 139 | threadPoolManager.addOrders(record.value().toString()); 140 | ack.acknowledge(); 141 | }else{ 142 | //未被消费的消息重新发到队列 143 | workProducerService.sendMessage(record.value().toString()); 144 | } 145 | } 146 | ``` 147 | 148 | ## 源码参考 149 | 150 | [并发任务调度](https://github.com/guangxush/WorkQueue) 151 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.7.RELEASE 9 | 10 | 11 | com.shgx 12 | queue 13 | 0.0.1-SNAPSHOT 14 | queue 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.springframework.kafka 38 | spring-kafka 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | 1.16.22 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-data-redis 48 | 49 | 50 | org.springframework.session 51 | spring-session-data-redis 52 | 53 | 54 | redis.clients 55 | jedis 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/QueueApplication.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class QueueApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(QueueApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/controller/BusinessController.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.controller; 2 | 3 | import com.shgx.queue.service.ThreadPoolManager; 4 | import com.shgx.queue.service.WorkProducerService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.Queue; 12 | import java.util.UUID; 13 | 14 | /** 15 | * @author: guangxush 16 | * @create: 2019/08/25 17 | */ 18 | @RestController 19 | public class BusinessController { 20 | 21 | @Autowired 22 | private ThreadPoolManager threadPoolManager; 23 | 24 | @Autowired 25 | private WorkProducerService producerService; 26 | /** 27 | * 测试模拟下单请求 入口 28 | * @param id 29 | * @return 30 | */ 31 | @GetMapping("/create/{id}") 32 | public String create(@PathVariable Long id) { 33 | //模拟的随机数 34 | String orderNo = System.currentTimeMillis() + UUID.randomUUID().toString(); 35 | threadPoolManager.addOrders(orderNo); 36 | return "Test ThreadPoolExecutor start"; 37 | } 38 | 39 | /** 40 | * 请求加入消息队列中执行 41 | * @param id 42 | * @return 43 | */ 44 | @GetMapping("/send/{id}") 45 | public String send(@PathVariable Long id) { 46 | //模拟的随机数 47 | String orderNo = System.currentTimeMillis() + UUID.randomUUID().toString(); 48 | producerService.sendMessage(orderNo); 49 | return "Test ThreadPoolExecutor start"; 50 | } 51 | 52 | /** 53 | * 停止服务 54 | * @param id 55 | * @return 56 | */ 57 | @GetMapping("/end/{id}") 58 | public String end(@PathVariable Long id) { 59 | threadPoolManager.shutdown(); 60 | Queue q = threadPoolManager.getMsgQueue(); 61 | System.out.println("关闭了线程服务,还有未处理的信息条数:" + q.size()); 62 | return "Test ThreadPoolExecutor start"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/service/ThreadPoolManager.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.service; 2 | 3 | import com.shgx.queue.utils.RedisUtils; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.BeanFactory; 7 | import org.springframework.beans.factory.BeanFactoryAware; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Map; 12 | import java.util.Queue; 13 | import java.util.concurrent.*; 14 | 15 | /** 16 | * @author: guangxush 17 | * @create: 2019/08/25 18 | */ 19 | @Service 20 | @Slf4j 21 | public class ThreadPoolManager implements BeanFactoryAware { 22 | 23 | private BeanFactory factory; 24 | 25 | /** 26 | * 线程池维护线程的最少数量 27 | */ 28 | private final static int CORE_POOL_SIZE = 2; 29 | 30 | /** 31 | * 线程池维护线程的最大数量 32 | */ 33 | private final static int MAX_POOL_SIZE = 10; 34 | 35 | /** 36 | * 线程池维护线程所允许的空闲时间 37 | */ 38 | private final static int KEEP_ALIVE_TIME = 0; 39 | 40 | /** 41 | * 线程池所使用的缓冲队列大小 42 | */ 43 | private final static int WORK_QUEUE_SIZE = 50; 44 | 45 | private final static boolean IS_QUEUE = false; 46 | 47 | @Autowired 48 | private WorkProducerService producerService; 49 | 50 | @Autowired 51 | private RedisUtils redisUtils; 52 | 53 | @Override 54 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 55 | factory = beanFactory; 56 | } 57 | 58 | /** 59 | * 用于储存在队列中的订单,防止重复提交,在真实场景中,可用redis代替 验证重复 60 | */ 61 | Map cacheMap = new ConcurrentHashMap<>(); 62 | 63 | /** 64 | * 订单的缓冲队列,当线程池满了,则将订单存入到此缓冲队列 65 | */ 66 | Queue msgQueue = new LinkedBlockingQueue<>(); 67 | 68 | /** 69 | * 当线程池的容量满了,执行下面代码,将订单存入到缓冲队列 70 | */ 71 | final RejectedExecutionHandler handler = new RejectedExecutionHandler() { 72 | @Override 73 | public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 74 | if (IS_QUEUE) { 75 | //订单加入到缓冲队列 76 | msgQueue.offer(((ThreadPoolService) r).getBusinessNo()); 77 | } else { 78 | //增加并发量,订单加入到kafka消息队列 79 | producerService.sendMessage(((ThreadPoolService) r).getBusinessNo()); 80 | } 81 | log.info("系统任务已满,把此订单交给(调度线程池)逐一处理,订单号:" + ((ThreadPoolService) r).getBusinessNo()); 82 | } 83 | }; 84 | 85 | 86 | /** 87 | * 创建线程池 88 | */ 89 | final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(WORK_QUEUE_SIZE), this.handler); 90 | 91 | /** 92 | * 将任务加入订单线程池 93 | */ 94 | public void addOrders(String orderId) { 95 | log.info("此订单准备添加到线程池,订单号:" + orderId); 96 | //验证当前进入的订单是否已经存在 97 | // if (cacheMap.get(orderId) == null) { 98 | // cacheMap.put(orderId, new Object()); 99 | // ThreadPoolService businessThread = new ThreadPoolService(orderId); 100 | // threadPool.execute(businessThread); 101 | // } 102 | //采用Redis缓存替代 103 | if (redisUtils.getSetMembers(orderId) == null) { 104 | redisUtils.addSetMembers(orderId, new Object()); 105 | ThreadPoolService businessThread = new ThreadPoolService(orderId); 106 | threadPool.execute(businessThread); 107 | } 108 | } 109 | 110 | /** 111 | * 获取目前的活跃线程数量 112 | * @return 113 | */ 114 | public int getActiveCount(){ 115 | return threadPool.getActiveCount(); 116 | } 117 | 118 | /** 119 | * 线程池的定时任务----> 称为(调度线程池)。此线程池支持定时以及周期性执行任务的需求。 120 | */ 121 | final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); 122 | 123 | 124 | /** 125 | * 检查(调度线程池),每秒执行一次,查看订单的缓冲队列是否有订单记录,则重新加入到线程池 126 | */ 127 | final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate(new Runnable() { 128 | @Override 129 | public void run() { 130 | //判断缓冲队列是否存在记录 131 | if (!msgQueue.isEmpty()) { 132 | //当线程池的队列容量少于workQueueSize,则开始把缓冲队列的订单加入到线程池 133 | if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) { 134 | String orderId = (String) msgQueue.poll(); 135 | ThreadPoolService businessThread = new ThreadPoolService(orderId); 136 | threadPool.execute(businessThread); 137 | log.info("(调度线程池)缓冲队列出现订单业务,重新添加到线程池,订单号:" + orderId); 138 | } 139 | } 140 | } 141 | }, 0, 1, TimeUnit.SECONDS); 142 | 143 | 144 | /** 145 | * 获取消息缓冲队列 146 | */ 147 | public Queue getMsgQueue() { 148 | return msgQueue; 149 | } 150 | 151 | /** 152 | * 终止订单线程池+调度线程池 153 | */ 154 | public void shutdown() { 155 | //true表示如果定时任务在执行,立即中止,false则等待任务结束后再停止 156 | log.info("终止订单线程池+调度线程池:" + scheduledFuture.cancel(false)); 157 | scheduler.shutdown(); 158 | threadPool.shutdown(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/service/ThreadPoolService.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.service; 2 | 3 | /** 4 | * @author: guangxush 5 | * @create: 2019/08/24 6 | */ 7 | 8 | import org.springframework.context.annotation.Scope; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Scope("prototype") 13 | public class ThreadPoolService implements Runnable{ 14 | 15 | private String businessNo; 16 | 17 | public ThreadPoolService(String businessNo) { 18 | this.businessNo = businessNo; 19 | } 20 | 21 | public String getBusinessNo() { 22 | return businessNo; 23 | } 24 | 25 | public void setBusinessNo(String businessNo) { 26 | this.businessNo = businessNo; 27 | } 28 | 29 | @Override 30 | public void run() { 31 | //业务操作 32 | System.out.println("多线程已经处理订单插入系统,订单号:"+ businessNo); 33 | 34 | //线程阻塞 35 | /*try { 36 | Thread.sleep(1000); 37 | log.info("多线程已经处理订单插入系统,订单号:"+businessNo); 38 | } catch (InterruptedException e) { 39 | e.printStackTrace(); 40 | }*/ 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/service/WorkConsumerService.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.kafka.clients.consumer.ConsumerConfig; 5 | import org.apache.kafka.clients.consumer.ConsumerRecord; 6 | import org.apache.kafka.common.serialization.IntegerDeserializer; 7 | import org.apache.kafka.common.serialization.StringDeserializer; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.autoconfigure.kafka.ConcurrentKafkaListenerContainerFactoryConfigurer; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.kafka.annotation.KafkaListener; 13 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 14 | import org.springframework.kafka.config.KafkaListenerEndpointRegistry; 15 | import org.springframework.kafka.core.ConsumerFactory; 16 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 17 | import org.springframework.kafka.listener.ContainerProperties; 18 | import org.springframework.kafka.listener.MessageListenerContainer; 19 | import org.springframework.kafka.support.Acknowledgment; 20 | import org.springframework.scheduling.annotation.Scheduled; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | /** 27 | * @author: guangxush 28 | * @create: 2019/08/24 29 | */ 30 | @Service 31 | @Slf4j 32 | public class WorkConsumerService { 33 | 34 | /** 35 | * 线程池维护线程的最大数量 36 | */ 37 | private static final int MAX_POOL_SIZE = 10; 38 | 39 | @Autowired 40 | private ThreadPoolManager threadPoolManager; 41 | 42 | @Autowired 43 | private WorkProducerService workProducerService; 44 | 45 | @Autowired 46 | private KafkaListenerEndpointRegistry registry; 47 | 48 | private Map consumerProps() { 49 | Map props = new HashMap<>(); 50 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); 51 | props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); 52 | props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); 53 | props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); 54 | props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class); 55 | props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 56 | return props; 57 | } 58 | 59 | @Bean("ackContainerFactory") 60 | public ConcurrentKafkaListenerContainerFactory ackContainerFactory() { 61 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory(); 62 | factory.setConsumerFactory(new DefaultKafkaConsumerFactory(consumerProps())); 63 | factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); 64 | factory.setConsumerFactory(new DefaultKafkaConsumerFactory(consumerProps())); 65 | return factory; 66 | } 67 | 68 | 69 | @KafkaListener(id = "test-consumer-group", topics = "business",containerFactory = "ackContainerFactory") 70 | public void ackListener(ConsumerRecord record, Acknowledgment ack) { 71 | log.info("Receive Business Number :------------"+record.value()); 72 | if(threadPoolManager.getActiveCount()< MAX_POOL_SIZE){ 73 | threadPoolManager.addOrders(record.value().toString()); 74 | ack.acknowledge(); 75 | }else{ 76 | //未被消费的消息重新发到队列 77 | workProducerService.sendMessage(record.value().toString()); 78 | } 79 | } 80 | 81 | // /** 82 | // * 20:53:00开始消费 83 | // */ 84 | // @Scheduled(cron = "0 30 21 * * ?") 85 | // public void startListener() { 86 | // log.info("开启监听"); 87 | // MessageListenerContainer container = registry.getListenerContainer("ack"); 88 | // if (!container.isRunning()) { 89 | // container.start(); 90 | // } 91 | // //恢复 92 | // container.resume(); 93 | // } 94 | // 95 | // /** 96 | // * 20:54:00结束消费 97 | // */ 98 | // @Scheduled(cron = "0 50 21 * * ?") 99 | // public void shutdownListener() { 100 | // log.info("关闭监听"); 101 | // //暂停 102 | // MessageListenerContainer container = registry.getListenerContainer("ack"); 103 | // container.pause(); 104 | // } 105 | // 106 | // 107 | // /** 108 | // * kafka监听工厂 109 | // * 110 | // * @param configurer 111 | // * @return 112 | // */ 113 | // @Bean("batchFactory") 114 | // public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory( 115 | // ConcurrentKafkaListenerContainerFactoryConfigurer configurer, 116 | // ConsumerFactory consumerFactory) { 117 | // ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 118 | // factory.setConsumerFactory(consumerFactory); 119 | // //开启批量消费功能 120 | // factory.setBatchListener(true); 121 | // //不自动启动 122 | // factory.setAutoStartup(false); 123 | // configurer.configure(factory, consumerFactory); 124 | // return factory; 125 | // } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/service/WorkProducerService.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.kafka.core.KafkaTemplate; 5 | import org.springframework.stereotype.Service; 6 | 7 | 8 | /** 9 | * @author: guangxush 10 | * @create: 2019/08/24 11 | */ 12 | @Service 13 | public class WorkProducerService { 14 | @Autowired 15 | private KafkaTemplate kafkaTemplate; 16 | 17 | public boolean sendMessage(String msg) { 18 | try { 19 | String result = kafkaTemplate.send("business", msg).get().toString(); 20 | if (result != null) { 21 | return true; 22 | } 23 | } catch (Exception e) { 24 | return false; 25 | } 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/shgx/queue/utils/RedisUtils.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue.utils; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.*; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | import java.util.Set; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @author: guangxush 14 | * @create: 2019/08/25 15 | */ 16 | @Service 17 | public class RedisUtils { 18 | @Autowired 19 | private RedisTemplate redisTemplate; 20 | 21 | /** 22 | * 写入缓存 23 | * @param key 24 | * @param value 25 | * @return 26 | */ 27 | public boolean set(final String key, Object value) { 28 | boolean result = false; 29 | try { 30 | ValueOperations operations = redisTemplate.opsForValue(); 31 | operations.set(key, value); 32 | result = true; 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | } 36 | return result; 37 | } 38 | /** 39 | * 写入缓存设置时效时间 40 | * @param key 41 | * @param value 42 | * @return 43 | */ 44 | public boolean set(final String key, Object value, Long expireTime , TimeUnit timeUnit) { 45 | boolean result = false; 46 | try { 47 | ValueOperations operations = redisTemplate.opsForValue(); 48 | operations.set(key, value); 49 | redisTemplate.expire(key, expireTime, timeUnit); 50 | result = true; 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | return result; 55 | } 56 | /** 57 | * 批量删除对应的value 58 | * @param keys 59 | */ 60 | public void remove(final String... keys) { 61 | for (String key : keys) { 62 | remove(key); 63 | } 64 | } 65 | 66 | /** 67 | * 批量删除key 68 | * @param pattern 69 | */ 70 | public void removePattern(final String pattern) { 71 | Set keys = redisTemplate.keys(pattern); 72 | if (keys.size() > 0){ 73 | redisTemplate.delete(keys); 74 | } 75 | } 76 | /** 77 | * 删除对应的value 78 | * @param key 79 | */ 80 | public void remove(final String key) { 81 | if (exists(key)) { 82 | redisTemplate.delete(key); 83 | } 84 | } 85 | /** 86 | * 判断缓存中是否有对应的value 87 | * @param key 88 | * @return 89 | */ 90 | public boolean exists(final String key) { 91 | return redisTemplate.hasKey(key); 92 | } 93 | /** 94 | * 读取缓存 95 | * @param key 96 | * @return 97 | */ 98 | public Object get(final String key) { 99 | Object result = null; 100 | ValueOperations operations = redisTemplate.opsForValue(); 101 | result = operations.get(key); 102 | return result; 103 | } 104 | 105 | /** 106 | * 哈希 添加 107 | * @param key 108 | * @param hashKey 109 | * @param value 110 | */ 111 | public void hmSet(String key, Object hashKey, Object value){ 112 | HashOperations hash = redisTemplate.opsForHash(); 113 | hash.put(key,hashKey,value); 114 | } 115 | 116 | /** 117 | * 哈希获取数据 118 | * @param key 119 | * @param hashKey 120 | * @return 121 | */ 122 | public Object hmGet(String key, Object hashKey){ 123 | HashOperations hash = redisTemplate.opsForHash(); 124 | return hash.get(key,hashKey); 125 | } 126 | 127 | /** 128 | * 列表添加 129 | * @param k 130 | * @param v 131 | */ 132 | public void lPush(String k,Object v){ 133 | ListOperations list = redisTemplate.opsForList(); 134 | list.rightPush(k,v); 135 | } 136 | 137 | /** 138 | * 列表获取 139 | * @param k 140 | * @param l 141 | * @param l1 142 | * @return 143 | */ 144 | public List lRange(String k, long l, long l1){ 145 | ListOperations list = redisTemplate.opsForList(); 146 | return list.range(k,l,l1); 147 | } 148 | 149 | /** 150 | * 集合添加 151 | * @param key 152 | * @param value 153 | */ 154 | public void addSetMembers(String key, Object value){ 155 | SetOperations set = redisTemplate.opsForSet(); 156 | set.add(key,value); 157 | } 158 | 159 | /** 160 | * 集合获取 161 | * @param key 162 | * @return 163 | */ 164 | public Set getSetMembers(String key){ 165 | SetOperations set = redisTemplate.opsForSet(); 166 | return set.members(key); 167 | } 168 | 169 | /** 170 | * 有序集合添加 171 | * @param key 172 | * @param value 173 | * @param scoure 174 | */ 175 | public void zAdd(String key,Object value,double scoure){ 176 | ZSetOperations zset = redisTemplate.opsForZSet(); 177 | zset.add(key,value,scoure); 178 | } 179 | 180 | /** 181 | * 有序集合获取 182 | * @param key 183 | * @param scoure 184 | * @param scoure1 185 | * @return 186 | */ 187 | public Set rangeByScore(String key,double scoure,double scoure1){ 188 | ZSetOperations zset = redisTemplate.opsForZSet(); 189 | return zset.rangeByScore(key, scoure, scoure1); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # base URL 2 | server: 3 | port: 8080 4 | servlet: 5 | context-path: /business 6 | multipart: 7 | max-file-size: 20M 8 | max-request-size: 20M 9 | 10 | #redis集群 11 | spring: 12 | redis: 13 | host: 127.0.0.1 14 | port: 6379 15 | timeout: 20000 16 | # 集群环境打开下面注释,单机不需要打开 17 | # cluster: 18 | # 集群信息 19 | # nodes: xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx 20 | # #默认值是5 一般当此值设置过大时,容易报:Too many Cluster redirections 21 | # maxRedirects: 3 22 | password: 23 | jedis: 24 | pool: 25 | max-idle: 8 26 | min-idle: 0 27 | max-wait: -1 28 | max-active: 8 29 | application: 30 | name: spring-boot-redis 31 | -------------------------------------------------------------------------------- /src/test/java/com/shgx/queue/QueueApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.shgx.queue; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class QueueApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------