分布式消息队列中间件作为高并发系统的核心组件之一,能够帮助业务系统解构提升开发效率和系统稳定性,其复杂性可见一斑,作为核心组件,有必要去深入了解学习

前言

分布式消息队列中间件主要具有以下优势:

  • 削峰填谷(主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题)
  • 系统解耦(解决不同重要程度、不同能力级别系统之间依赖导致一死全死)
  • 提升性能(当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统)
  • 蓄流压测(线上有些链路不好压测,可以通过堆积一定量消息再放开来压测)

笔者为什么要深入rocketmq,一方面是因为公司目前已经在线上使用了rocketmq,另一方面也是因为笔者是主要的维护人员,需要对其有个深入的了解,并且测试环境的集群之前出现了问题,虽然最终解决,但是发生的原因依旧没有找到,感觉还是需要去深入了解下,对架构和源码进行一个整体的学习以应对之后可能出现的问题

rocketmq的相关中文文档在github上应该算非常详细了,初学者应该经常去看一看,每一句思考下,能收获不少,而且其中涉及到不少概念,还是需要去理解的,不明白的话也无法深入的学习下去,地址如下:

https://github.com/apache/rocketmq/tree/master/docs/cn

对于与其他中间件的比较和起源发展可参考阿里中间件博客了解,更多的知识可Google了解,地址如下:

http://jm.taobao.org/tags/RocketMQ/

特点





  • rocketmq是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点。
  • Producer、Consumer、队列都可以分布式。
  • Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合。
  • 能够保证严格的消息顺序
  • 提供丰富的消息拉取模式
  • 高效的订阅者水平扩展能力
  • 实时的消息订阅机制
  • 亿级消息堆积能力
  • 较少的依赖

基础概念

  • 消息模型(Message Model)

RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。

  • 消息生产者(Producer)

负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。

  • 消息消费者(Consumer)

负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。

  • 主题(Topic)

表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。

  • 代理服务器(Broker Server)

消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。

  • 名字服务(Name Server)

名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。

  • 拉取式消费(Pull Consumer)

Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。

  • 推动式消费(Push Consumer)

Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。

  • 生产者组(Producer Group)

同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事物消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。

  • 消费者组(Consumer Group)

同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。

  • 集群消费(Clustering)

集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。

  • 广播消费(Broadcasting)

广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。

  • 普通顺序消息(Normal Ordered Message)

普通顺序消费模式下,消费者通过同一个消费队列收到的消息是有顺序的,不同消息队列收到的消息则可能是无顺序的。

  • 严格顺序消息(Strictly Ordered Message)

严格顺序消息模式下,消费者收到的所有消息均是有顺序的。

  • 消息(Message)

消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。

  • 标签(Tag)

为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。

物理部署结构





Name Server是一个几乎无状态节点,可集群部署,节点之间无任何信息同步

Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与Name Server集群中的所有节点建立长连接,定时注册Topic信息到所有Name Server。

Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。

Consumer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定

构建脚本

rocketmq版本号:4.5.2

源码distribution的bin目录下已经提供了创建脚本,构建分布式集群前我们先进行下分析,便于之后源码的分析实现

部署当然是两个部分代理服务器(Broker Server)和名字服务(Name Server),脚本对应的是mqnamesrv和mqbroker,同时运维管理通过mqadmin完成,分别进行分析下

mqnamesrv

mqnamesrv启动脚本如下,主要是处理ROCKETMQ_HOME目录

if [ -z "$ROCKETMQ_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
# 执行脚本命令参数 sh 之后地址串
PRG="$0" # need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done # 当前执行命令所在目录
saveddir=`pwd`
# 当前脚本所在目录的上一级目录
ROCKETMQ_HOME=`dirname "$PRG"`/..
# make it fully qualified
# mq主目录
ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd`
# 回到当前命令执行目录
cd "$saveddir"
fi # 定义环境变量
export ROCKETMQ_HOME # runserver.sh执行NamesrvStartup启动类 $@传参
sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.namesrv.NamesrvStartup $@

调用runserver.sh通过NamesrvStartup来完成启动


#===========================================================================================
# Java Environment Setting
#===========================================================================================
# 错误退出
error_exit ()
{
echo "ERROR: $1 !!"
exit 1
} # 判断是否安装java运行环境
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" # 设置环境变量
export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
# bin同级目录
export BASE_DIR=$(dirname $0)/..
# 设置CLASSPATH,conf配置文件目录也放入
export CLASSPATH=.:${BASE_DIR}/conf:${CLASSPATH} #===========================================================================================
# JVM Configuration
#===========================================================================================
# The RAMDisk initializing size in MB on Darwin OS for gc-log
DIR_SIZE_IN_MB=600 # gc日志目录设置 根据不同操作系统设置GC_LOG_DIR
choose_gc_log_directory()
{
case "`uname`" in
Darwin)
if [ ! -d "/Volumes/RAMDisk" ]; then
# create ram disk on Darwin systems as gc-log directory
DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null
diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null
echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS."
fi
GC_LOG_DIR="/Volumes/RAMDisk"
;;
*)
# check if /dev/shm exists on other systems
if [ -d "/dev/shm" ]; then
GC_LOG_DIR="/dev/shm"
else
GC_LOG_DIR=${BASE_DIR}
fi
;;
esac
} # 执行方法
choose_gc_log_directory # 设置JVM参数,通过Djava.ext.dirs扩展目录将lib下的rocketmq相关jar包导入
# 堆4g 年轻代2g 默认jdk8以上环境 元数据空间128m 最大320m
# 老年代CMS垃圾收集器 禁掉ParNewGC 新生代使用SerialGC
# 打印gc日志
# 设置日志文件个数大小
# 禁止重复大量异常打印堆栈信息
# 禁止使用大页面内存
# 设置扩展包路径和classpath路径 JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib"
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" # 执行java 命令操作 可通过ps命令查看完整信息
$JAVA ${JAVA_OPT} $@

最终通过java命令启动

mqbroker

mqbroker启动脚本说明与mqnamesrv启动脚本类似,参考mqnamesrv

if [ -z "$ROCKETMQ_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0" # need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified
ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir"
fi export ROCKETMQ_HOME # runbroker.sh执行BrokerStartup启动类 $@传参
sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@

调用runbroker.sh通过BrokerStartup启动


#===========================================================================================
# Java Environment Setting
#===========================================================================================
error_exit ()
{
echo "ERROR: $1 !!"
exit 1
} [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=$(dirname $0)/..
export CLASSPATH=.:${BASE_DIR}/conf:${CLASSPATH} #===========================================================================================
# JVM Configuration
#===========================================================================================
# The RAMDisk initializing size in MB on Darwin OS for gc-log
DIR_SIZE_IN_MB=600 choose_gc_log_directory()
{
case "`uname`" in
Darwin)
if [ ! -d "/Volumes/RAMDisk" ]; then
# create ram disk on Darwin systems as gc-log directory
DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null
diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null
echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS."
fi
GC_LOG_DIR="/Volumes/RAMDisk"
;;
*)
# check if /dev/shm exists on other systems
if [ -d "/dev/shm" ]; then
GC_LOG_DIR="/dev/shm"
else
GC_LOG_DIR=${BASE_DIR}
fi
;;
esac
} choose_gc_log_directory # 设置JVM参数,通过Djava.ext.dirs扩展目录将lib下的rocketmq相关jar包导入
# 堆8g 年轻代4g
# 使用G1收集器,Region大小16m,预留内存25,内存占用达到整个堆百分之30的时候开启一个GC周期,软引用存活对象不用则立即清除
# 打印gc日志
# 设置日志文件个数大小
# 禁止重复大量异常打印堆栈信息
# 预分配所有内存
# 最大堆外内存15g
# 禁止使用大页面内存,禁止使用偏向锁
# 设置扩展包路径和classpath路径 JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_broker_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch"
JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib"
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" # numa设置,涉及到了底层部分,不过最终都是要执行java命令的
numactl --interleave=all pwd > /dev/null 2>&1
if [ $? -eq 0 ]
then
if [ -z "$RMQ_NUMA_NODE" ] ; then
numactl --interleave=all $JAVA ${JAVA_OPT} $@
else
numactl --cpunodebind=$RMQ_NUMA_NODE --membind=$RMQ_NUMA_NODE $JAVA ${JAVA_OPT} $@
fi
else
$JAVA ${JAVA_OPT} $@
fi

mqadmin

mqadmin启动脚本说明与mqnamesrv启动脚本类似,参考mqnamesrv

if [ -z "$ROCKETMQ_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0" # need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified
ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir"
fi export ROCKETMQ_HOME
# 通过tools.sh脚本执行MQAdminStartup
sh ${ROCKETMQ_HOME}/bin/tools.sh org.apache.rocketmq.tools.command.MQAdminStartup $@

通过tools.sh执行MQAdminStartup完成运维命令的实现

error_exit ()
{
echo "ERROR: $1 !!"
exit 1
} [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=$(dirname $0)/..
export CLASSPATH=.:${BASE_DIR}/conf:${CLASSPATH} #===========================================================================================
# JVM Configuration
#=========================================================================================== # 基础的jvm配置
# 堆大小配置1g,年轻代配置256m,元数据大小配置128m JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${BASE_DIR}/lib:${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" # 执行java命令
$JAVA ${JAVA_OPT} $@

总结

本文主要是对rocketmq进行入门基础的学习,初学者应该多理解,因为涉及到了很多概念,以及其架构实现,多看几遍,多思考下,应该能够了解

为了接下来更好的学习,必须要进行分布式集群的搭建,在搭建集群之前有必要去学习下每个启动脚本启动的过程,启动类的入口,不是很复杂,其中涉及到了jvm的设置,如果不是很明白,可以先大概了解就好,重点在于理解如何通过脚本启动的

以上内容如有问题欢迎指出,笔者验证后将及时修正,谢谢

参考资料:

rocketmq那些事儿之入门基础的更多相关文章

  1. rocketmq那些事儿之集群环境搭建

    上一篇入门基础部分对rocketmq进行了一个基础知识的讲解说明,在正式使用前我们需要进行环境的搭建,今天就来说一说rockeketmq分布式集群环境的搭建 前言 之前已经介绍了rocketmq的入门 ...

  2. mybatis入门基础(二)----原始dao的开发和mapper代理开发

    承接上一篇 mybatis入门基础(一) 看过上一篇的朋友,肯定可以看出,里面的MybatisService中存在大量的重复代码,看起来不是很清楚,但第一次那样写,是为了解mybatis的执行步骤,先 ...

  3. 01shell入门基础

    01shell入门基础 为什么学习和使用shell编程 shell是一种脚本语言,脚本语言是相对于编译语言而言的.脚本语言不需要编译,由解释器读取程序并且执行其中的语句,而编译语言需要编译成可执行代码 ...

  4. Markdown入门基础

    // Markdown入门基础 最近准备开始强迫自己写博文,以治疗严重的拖延症,再不治疗就“病入骨髓,司命之所属,无奈何”了啊.正所谓“工欲善其事,必先利其器”,于是乎在写博文前,博主特地研究了下博文 ...

  5. JavaScript入门基础

    JavaScript基本语法 1.运算符 运算符就是完成操作的一系列符号,它有七类: 赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=).算术运 ...

  6. C++ STL编程轻松入门基础

    C++ STL编程轻松入门基础 1 初识STL:解答一些疑问 1.1 一个最关心的问题:什么是STL 1.2 追根溯源:STL的历史 1.3 千丝万缕的联系 1.4 STL的不同实现版本 2 牛刀小试 ...

  7. HTML入门基础教程相关知识

    HTML入门基础教程 html是什么,什么是html通俗解答: html是hypertext markup language的缩写,即超文本标记语言.html是用于创建可从一个平台移植到另一平台的超文 ...

  8. Linux shell入门基础(六)

    六.Shell脚本编程详解 将上述五部分的内容,串联起来,增加对Shell的了解 01.shell脚本 shell: # #perl #python #php #jsp 不同的脚本执行不同的文本,执行 ...

  9. Linux shell入门基础(一)

    Linux shell入门基础(一): 01.增加删除用户: #useradd byf   userdel byf(主目录未删除)  userdel -r byf   该用户的属性:usermod 用 ...

随机推荐

  1. TIBCO Jaspersoft Studio 报表软件使用教程

    Detail 1里面放置动态内容框可以循环遍历数据 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn ...

  2. python 包多熟悉一个干活就轻松点

    包管理 管理包和依赖的工具. pip – Python 包和依赖关系管理工具. pip-tools – 保证 Python 包依赖关系更新的一组工具. conda – 跨平台,Python 二进制包管 ...

  3. Java开发笔记(一百三十二)Swing的表格

    前面介绍了程序界面上一些简单控件的组合排列,它们用来表达相互之间联系较弱的信息倒还凑合,要是用来表达关联性较强的聚合信息就力不从心了.倘若只是简单信息的罗列,例如商品名称列表.新闻标题列表.学生姓名列 ...

  4. Linux07 查找文件(find、locate)

    一.一般查找:find find  PATH  -name  FILENAME 我们也可是使用 ‘*’ 通配符来模糊匹配要查找的文件名 二.数据库查找:locate locate  FILENAME ...

  5. foreach引用坑

    先看下面代码 $arr1 = [1, 2]; foreach($arr1 as $key => $value) { $value = $value + 1; } var_dump($key, $ ...

  6. NOI2017

    整数(线段树) 不难想到按位处理,位数比较多考虑使用动态开点线段树维护大数,那么复杂度是\(O(nlog^2n)\)的,不够优秀. 但注意到我们需要支持的是二进制下的加减法,而在二进制下我们可以使用i ...

  7. 用JS实现一个斗地主发牌器

    //调用随机数,在我上一篇博文讲过这一个函数. function roundNum(min = 0, max = 0) { if (!isNaN(min) && !isNaN(max) ...

  8. Python简单实现在线更新下载

    Python简单实现 软件在线更新 在线下载(Python simple implementation of software online update and download) 文章来自:htt ...

  9. aapt&adb笔记

    aapt 查看安装包信息aapt list apk路径* aapt list xxx/app-debug.apk 查看apk文件信息并保存到本地(> 重定向符) * aapt list xx/a ...

  10. SpringBoot+SpringCloud+vue+Element开发项目——搭建开发环境

    1.新建一个项目