-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.json
1 lines (1 loc) · 697 KB
/
search.json
1
[{"title":"CentOS更新yum源","url":"/2017/02/07/CentOS%E6%9B%B4%E6%96%B0yum%E6%BA%90/","content":"<ul>\n<li><p>直接打开 163 源网站:<a href=\"http://mirrors.163.com/.help/centos.html\">http://mirrors.163.com/.help/centos.html</a></p>\n</li>\n<li><p>按照使用说明,还是先备份一下源(使用下面的命令重命名原来的源,如果有错误,再改回来):</p>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>转到源目录:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cd /etc/yum.repos.d/</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>按照自己的版本下载源,我是 centos 7,使用命令:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">wget http://mirrors.163.com/.help/CentOS7-Base-163.repo</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>运行以下命令生成缓存:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum clean all</span><br><span class=\"line\">yum makecache</span><br></pre></td></tr></table></figure></li>\n</ul>\n","categories":["Linux"],"tags":["Linux"]},{"title":"Arm64麒麟V10安装Kata","url":"/2022/11/19/Arm64%20%E9%BA%92%E9%BA%9FV10%E5%AE%89%E8%A3%85Kata/","content":"<h1 id=\"前置条件检查是否支持虚拟化\"><a href=\"#前置条件检查是否支持虚拟化\" class=\"headerlink\" title=\"前置条件检查是否支持虚拟化\"></a>前置条件检查是否支持虚拟化</h1><ul>\n<li>通过dmesg查看是否支持虚拟化,arm不同于x86,lscpu看不出来<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># dmesg |grep kvm</span><br><span class=\"line\">[ 0.499391] kvm [1]: Hisi ncsnp: enabled</span><br><span class=\"line\">[ 0.499605] kvm [1]: 16-bit VMID</span><br><span class=\"line\">[ 0.499606] kvm [1]: IPA Size Limit: 48bits</span><br><span class=\"line\">[ 0.499644] kvm [1]: GICv4 support disabled</span><br><span class=\"line\">[ 0.499645] kvm [1]: vgic-v2@9b020000</span><br><span class=\"line\">[ 0.499647] kvm [1]: GIC system register CPU interface enabled</span><br><span class=\"line\">[ 0.500407] kvm [1]: vgic interrupt IRQ1</span><br><span class=\"line\">[ 0.501131] kvm [1]: VHE mode initialized successfully</span><br></pre></td></tr></table></figure></li>\n<li>如果是下面这样,就不用继续了,浪费时间<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># dmesg |grep kvm</span><br><span class=\"line\">[ 0.136111] kvm [1]: HYP mode not available</span><br></pre></td></tr></table></figure></li>\n<li>注意:Arm架构不支持嵌套虚拟化,也就是说只能在物理机下运行kata,无法在虚拟机下运行</li>\n</ul>\n<h1 id=\"下载-amp-编译-amp-安装\"><a href=\"#下载-amp-编译-amp-安装\" class=\"headerlink\" title=\"下载&编译&安装\"></a>下载&编译&安装</h1><h2 id=\"gcc-yum源7以上无需编译\"><a href=\"#gcc-yum源7以上无需编译\" class=\"headerlink\" title=\"gcc (yum源7以上无需编译)\"></a>gcc (yum源7以上无需编译)</h2><ul>\n<li>编译qemu需要</li>\n<li>下载源码 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">wget https://github.com/gcc-mirror/gcc/archive/refs/tags/releases/gcc-8.5.0.zip</span><br></pre></td></tr></table></figure></li>\n<li>解压后,检查依赖项<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">./contrib/download_prerequisites</span><br></pre></td></tr></table></figure></li>\n<li>yum安装依赖<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># yum -y install bzip2 gcc gcc-c++ gmp-devel mpfr-devel libmpc-devel make zlib-devel flex bison-devel</span><br></pre></td></tr></table></figure></li>\n<li>进入gcc8.5.0目录进行编译安装, make -j 后面的数字是编译的并行数,可适当调整<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># mkdir build && cd build</span><br><span class=\"line\"># ../configure --prefix=/opt/gcc-8.5.0 --enable-languages=c,c++ --disable-multilib</span><br><span class=\"line\"># make -j8 && sudo make install</span><br></pre></td></tr></table></figure></li>\n<li>进行软链接,软链前删除/usr/bin/下的cc和c++<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># ln -s /opt/gcc-8.5.0/bin/gcc /usr/bin/cc</span><br><span class=\"line\"># ln -s /opt/gcc-8.5.0/bin/c++ /usr/bin/c++</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"rust\"><a href=\"#rust\" class=\"headerlink\" title=\"rust\"></a>rust</h2><ul>\n<li>编译kata需要</li>\n<li>直接在线安装<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">curl --proto '=https' --tlsv1.2 -sSf sh.rustup.rs | sh</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"golang\"><a href=\"#golang\" class=\"headerlink\" title=\"golang\"></a>golang</h2><ul>\n<li>编译kata需要</li>\n<li>直接下载二进制<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">wget https://studygolang.com/dl/golang/go1.19.3.linux-arm64.tar.gz</span><br></pre></td></tr></table></figure></li>\n<li>解压后将bin目录添加至PATH即可</li>\n</ul>\n<h2 id=\"kata\"><a href=\"#kata\" class=\"headerlink\" title=\"kata\"></a>kata</h2><ul>\n<li>下载源码 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">git clone https://github.com/kata-containers/kata-containers.git</span><br></pre></td></tr></table></figure></li>\n<li>在kata-containers目录编译安装<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">$ pushd kata-containers/src/runtime</span><br><span class=\"line\">$ make && sudo -E "PATH=$PATH" make install</span><br><span class=\"line\">$ sudo mkdir -p /etc/kata-containers/</span><br><span class=\"line\">$ sudo install -o root -g root -m 0640 /usr/share/defaults/kata-containers/configuration.toml /etc/kata-containers</span><br><span class=\"line\">$ popd</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"containerd\"><a href=\"#containerd\" class=\"headerlink\" title=\"containerd\"></a>containerd</h2><ul>\n<li>直接下载对应的rpm包<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># yum install \thttp://mirror.centos.org/altarch/7/extras/aarch64/Packages/container-selinux-2.107-1.el7_6.noarch.rpm</span><br><span class=\"line\"># yum install \thttps://download.docker.com/linux/centos/7/aarch64/stable/Packages/containerd.io-1.5.11-3.1.el7.aarch64.rpm</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"python3-7-(yum源3-7以上无需编译)\"><a href=\"#python3-7-(yum源3-7以上无需编译)\" class=\"headerlink\" title=\"python3.7 (yum源3.7以上无需编译)\"></a>python3.7 (yum源3.7以上无需编译)</h2><ul>\n<li><p>编译re2c需要</p>\n</li>\n<li><p>下载源码</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz</span><br></pre></td></tr></table></figure></li>\n<li><p>安装依赖</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># yum install libffi-devel </span><br></pre></td></tr></table></figure>\n</li>\n<li><p>解压并编译</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># tar -zxvf Python-3.7.0.tgz</span><br><span class=\"line\"># mv Python-3.7.0 /usr/local</span><br><span class=\"line\"># rm -rf /usr/bin/python</span><br><span class=\"line\"># cd /usr/local/Python-3.7.0/</span><br><span class=\"line\"># ./configure</span><br><span class=\"line\"># make</span><br><span class=\"line\"># make install</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"re2c\"><a href=\"#re2c\" class=\"headerlink\" title=\"re2c\"></a>re2c</h2><ul>\n<li>编译ninja需要</li>\n<li>下载源码 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">git clone https://github.com/skvadrik/re2c.git</span><br></pre></td></tr></table></figure></li>\n<li>安装依赖 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># yum install automake libtool gcc gcc-c++</span><br></pre></td></tr></table></figure></li>\n<li>编译安装,进入re2c目录 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># autoreconf -i -W all</span><br><span class=\"line\"># ./configure && make && make install</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"ninja\"><a href=\"#ninja\" class=\"headerlink\" title=\"ninja\"></a>ninja</h2><ul>\n<li>编译qemu需要</li>\n<li>下载源码<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">git clone https://github.com/ninja-build/ninja.git</span><br></pre></td></tr></table></figure></li>\n<li>编译,进入ninja目录<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">./configure.py --bootstrap</span><br></pre></td></tr></table></figure></li>\n<li>将ninja拷贝至PATH<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">mv ninja /usr/bin/</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"yq\"><a href=\"#yq\" class=\"headerlink\" title=\"yq\"></a>yq</h2><ul>\n<li>直接下载二进制就行<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">https://github.com/mikefarah/yq/releases</span><br><span class=\"line\">https://github.com/mikefarah/yq/releases/download/v4.30.4/yq_darwin_arm64</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"qemu\"><a href=\"#qemu\" class=\"headerlink\" title=\"qemu\"></a>qemu</h2><ul>\n<li>可以通过tests脚本下载(是网络情况决定)</li>\n<li>提前下载源码,后续直接进行编译<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">git clone https://gitlab.com/qemu-project/qemu.git</span><br><span class=\"line\">git clone https://gitlab.com/qemu-project/dtc.git</span><br><span class=\"line\">git clone https://gitlab.com/qemu-project/meson.git</span><br><span class=\"line\">https://gitlab.com/qemu-project/keycodemapdb.git</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n<li>这里采用kata-tests的脚本进行编译安装,具体见[<a href=\"#kata-tests\">kata-tests</a>]</li>\n</ul>\n<h2 id=\"kata-tests\"><a href=\"#kata-tests\" class=\"headerlink\" title=\"kata-tests\"></a><a id=\"kata-tests\">kata-tests</a></h2><ul>\n<li><p>进入kata-containers目录,下载源码tests</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">git clone https://github.com/kata-containers/tests.git</span><br></pre></td></tr></table></figure></li>\n<li><p>调整目录结构(方便执行install_qemu脚本)</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">将kata-containers移动到/opt/kata/src/github.com/kata-containers</span><br><span class=\"line\"></span><br><span class=\"line\">/opt/kata/src/github.com/kata-containers/</span><br><span class=\"line\"> - tests</span><br><span class=\"line\"> - kata-containers (软连接至当前目录ln -s /opt/kata/src/github.com/kata-containers kata-containers)</span><br><span class=\"line\"> - 其他kata-containsers文件</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">将上面下载的qemu目录mv到/opt/kata/src/github.com/qemu</span><br></pre></td></tr></table></figure>\n\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">/opt/kata (下面的GOPATH)路径下增加bin目录</span><br><span class=\"line\">将yq二进制文件拷贝至bin目录下(跳过install_yq)</span><br></pre></td></tr></table></figure></li>\n<li><p>修改脚本(必须)</p>\n<ul>\n<li>修改./kata-containers/tests/.ci/lib.sh, 增加如下</li>\n<li><code>export GOPATH=上面调整的目录,我这里是/opt/kata</code></li>\n</ul>\n</li>\n<li><p>修改脚本(可选,网速不好参考)</p>\n<ul>\n<li>./kata-containers/tests/.ci/install_qemu.sh<ul>\n<li>方便调试增加<code>set -x</code></li>\n</ul>\n</li>\n<li>./kata-containers/tests/.ci/lib.sh</li>\n<li>./kata-containers/tests/.ci/aarch64/lib_install_qemu_aarch64.sh<ul>\n<li>如果上面已经移动了qemu目录,执行下面的注释,否则不用执行</li>\n<li>注释掉<code>clone_qemu_repo</code></li>\n<li>注释掉 <code>sudo -E git fetch</code></li>\n</ul>\n</li>\n<li>./kata-containers/tools/packaging/scripts/configure-hypervisor.sh<ul>\n<li>如果提前clone了目录,增加如下,这样可以在config qemu的时候忽略子模块校验 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">qemu_options+=' --with-git-submodules=ignore'</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>执行安装</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># ./kata-containers/tests/.ci/install_qemu.sh</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"kata-agent-可选\"><a href=\"#kata-agent-可选\" class=\"headerlink\" title=\"kata-agent(可选)\"></a>kata-agent(可选)</h2><ul>\n<li>需要跨平台编译组件,下载地址<ul>\n<li><a href=\"https://musl.cc/#binaries\">https://musl.cc/#binaries</a></li>\n<li><a href=\"https://musl.cc/aarch64-linux-musl-native.tgz\">https://musl.cc/aarch64-linux-musl-native.tgz</a></li>\n<li>aarch64-linux-musl-native解压后,bin目录加到path中</li>\n</ul>\n</li>\n<li>执行编译 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">make -C kata-containers/src/agent SECCOMP=no</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"kernel-除非你有arm内核,否则还是需要编译\"><a href=\"#kernel-除非你有arm内核,否则还是需要编译\" class=\"headerlink\" title=\"kernel (除非你有arm内核,否则还是需要编译)\"></a>kernel (除非你有arm内核,否则还是需要编译)</h2><ul>\n<li>下载对应版本的内核并解压</li>\n<li>进入内核目录执行<br> <code>make -j 8</code></li>\n<li>编译完成后拷贝<code>./arch/arm64/boot/Image</code>至对应目录<ul>\n<li>可软链,默认<code>/usr/share/kata-containers/vmlinux.container</code></li>\n</ul>\n</li>\n<li>编译模块部分(需要内核开启模块,自己根据版本调整,如果guestImage需要使用则需要编译) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">修改makefile的EXTRAVERSION 适配自己的版本,然后执行编译</span><br><span class=\"line\">mkdir -p ../build/lib/modules/5.4.160-1.el7.aarch64</span><br><span class=\"line\">make modules -j64</span><br><span class=\"line\">make modules_install INSTALL_MOD_PATH=../build</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"guest-Image-同kernel,如果有则无需编译\"><a href=\"#guest-Image-同kernel,如果有则无需编译\" class=\"headerlink\" title=\"guest Image (同kernel,如果有则无需编译)\"></a>guest Image (同kernel,如果有则无需编译)</h2><ul>\n<li><p>进入kata-containers/tools/osbuilder/rootfs-builder/centos, 根据自己rootfs选择</p>\n</li>\n<li><p>copy kernel modules (按需)</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cp -r -d ${kernel}/../build/lib/modules/5.4.160-1.el7.aarch64/ lib/modules/5.4.160-1.el7.aarch64</span><br></pre></td></tr></table></figure></li>\n<li><p>修改 config.sh(主要将yum或者dnf源修改为适配aarch64的,如果使用官方无需修改)</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">BASE_URL="https://mirrors.aliyun.com/centos/8-stream/BaseOS/aarch64/os/"</span><br></pre></td></tr></table></figure></li>\n<li><p>增加rust加速config, 打到Docker镜像中</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">[source.crates-io]</span><br><span class=\"line\">registry = "https://github.com/rust-lang/crates.io-index"</span><br><span class=\"line\">replace-with = 'ustc'</span><br><span class=\"line\">[source.ustc]</span><br><span class=\"line\">registry = "git://mirrors.ustc.edu.cn/crates.io-index"</span><br></pre></td></tr></table></figure></li>\n<li><p>修改Dockerfile(我这里rust加速,copy了config)</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">增加</span><br><span class=\"line\">COPY config /root/.cargo/config</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>回到进入kata-containers/tools/osbuilder,执行编译(如果哪一步有超时,自己修改dns或者修改代理)</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">make DISTRO=centos OS_VERSION=stream8 SECCOMP=no DEBUG=true USE_DOCKER=true AGENT_INIT=yes rootfs</span><br><span class=\"line\">make USE_DOCKER=true image-centos -j 16</span><br></pre></td></tr></table></figure></li>\n<li><p>拷贝编译好的Image(我这里是kata-containers-image-centos.img)</p>\n</li>\n</ul>\n<h1 id=\"配置\"><a href=\"#配置\" class=\"headerlink\" title=\"配置\"></a>配置</h1><h2 id=\"kata-1\"><a href=\"#kata-1\" class=\"headerlink\" title=\"kata\"></a>kata</h2><ul>\n<li>/etc/kata-containers/configuration.toml //过滤空行和注释后,我这里开启了debug模式 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">[hypervisor.qemu]</span><br><span class=\"line\">path = "/usr/bin/qemu-system-aarch64"</span><br><span class=\"line\">kernel = "/usr/share/kata-containers/vmlinux.container"</span><br><span class=\"line\">image = "/usr/share/kata-containers/kata-containers.img"</span><br><span class=\"line\">machine_type = "virt"</span><br><span class=\"line\">enable_annotations = []</span><br><span class=\"line\">valid_hypervisor_paths = ["/usr/bin/qemu-system-aarch64"]</span><br><span class=\"line\">kernel_params = " initcall_debug"</span><br><span class=\"line\">firmware = ""</span><br><span class=\"line\">machine_accelerators=""</span><br><span class=\"line\">cpu_features="pmu=off"</span><br><span class=\"line\">default_vcpus = 1</span><br><span class=\"line\">default_maxvcpus = 1</span><br><span class=\"line\">default_bridges = 1</span><br><span class=\"line\">default_memory = 2048</span><br><span class=\"line\">disable_block_device_use = false</span><br><span class=\"line\">shared_fs = "virtio-9p"</span><br><span class=\"line\">virtio_fs_daemon = "/usr/libexec/kata-qemu/virtiofsd"</span><br><span class=\"line\">valid_virtio_fs_daemon_paths = ["/usr/libexec/kata-qemu/virtiofsd"]</span><br><span class=\"line\">virtio_fs_cache_size = 0</span><br><span class=\"line\">virtio_fs_extra_args = ["--thread-pool-size=1"]</span><br><span class=\"line\">virtio_fs_cache = "auto"</span><br><span class=\"line\">block_device_driver = "virtio-blk"</span><br><span class=\"line\">enable_iothreads = false</span><br><span class=\"line\">enable_vhost_user_store = false</span><br><span class=\"line\">vhost_user_store_path = "/var/run/kata-containers/vhost-user"</span><br><span class=\"line\">valid_vhost_user_store_paths = ["/var/run/kata-containers/vhost-user"]</span><br><span class=\"line\">valid_file_mem_backends = [""]</span><br><span class=\"line\">pflashes = []</span><br><span class=\"line\">enable_debug = true</span><br><span class=\"line\">disable_image_nvdimm = true</span><br><span class=\"line\">valid_entropy_sources = ["/dev/urandom","/dev/random",""]</span><br><span class=\"line\">[factory]</span><br><span class=\"line\">[agent.kata]</span><br><span class=\"line\">enable_debug = true</span><br><span class=\"line\">enable_tracing = true</span><br><span class=\"line\">kernel_modules=[]</span><br><span class=\"line\">debug_console_enabled = true</span><br><span class=\"line\">[netmon]</span><br><span class=\"line\">path = "/usr/libexec/kata-containers/kata-netmon"</span><br><span class=\"line\">enable_debug = true</span><br><span class=\"line\">[runtime]</span><br><span class=\"line\">enable_debug = true</span><br><span class=\"line\">internetworking_model="tcfilter"</span><br><span class=\"line\">disable_guest_seccomp=true</span><br><span class=\"line\">disable_selinux=false</span><br><span class=\"line\">sandbox_cgroup_only=false</span><br><span class=\"line\">sandbox_bind_mounts=[]</span><br><span class=\"line\">vfio_mode="guest-kernel"</span><br><span class=\"line\">experimental=[]</span><br><span class=\"line\">[image]</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"containerd-1\"><a href=\"#containerd-1\" class=\"headerlink\" title=\"containerd\"></a>containerd</h2><ul>\n<li>/etc/containerd/config.toml <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">disabled_plugins = []</span><br><span class=\"line\"></span><br><span class=\"line\">[debug]</span><br><span class=\"line\"># address = "/run/containerd/debug.sock"</span><br><span class=\"line\"># uid = 0</span><br><span class=\"line\"># gid = 0</span><br><span class=\"line\">level = "debug" # 我这里为了debug观察用</span><br><span class=\"line\"></span><br><span class=\"line\">[plugins]</span><br><span class=\"line\">[plugins.cri.cni]</span><br><span class=\"line\"> conf_dir = "/etc/cni/net.d"</span><br><span class=\"line\">[plugins.linux]</span><br><span class=\"line\"> shim_debug = true</span><br><span class=\"line\">[plugins.cri]</span><br><span class=\"line\"> [plugins.cri.containerd]</span><br><span class=\"line\"> [plugins.cri.containerd.runtimes]</span><br><span class=\"line\"> [plugins.cri.containerd.runtimes.kata]</span><br><span class=\"line\"> runtime_type = "io.containerd.kata.v2"</span><br><span class=\"line\"> privileged_without_host_devices = true</span><br><span class=\"line\"> [plugins.cri.containerd.runtimes.kata.options]</span><br><span class=\"line\"> ConfigPath = "/etc/kata-containers/configuration.toml" # 指定kata配置文件</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"cni\"><a href=\"#cni\" class=\"headerlink\" title=\"cni\"></a>cni</h2><ul>\n<li>/etc/containerd/config.toml <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "cniVersion": "0.2.0",</span><br><span class=\"line\"> "name": "mynet",</span><br><span class=\"line\"> "type": "bridge",</span><br><span class=\"line\"> "bridge": "cni0",</span><br><span class=\"line\"> "isGateway": true,</span><br><span class=\"line\"> "ipMasq": true,</span><br><span class=\"line\"> "ipam": {</span><br><span class=\"line\"> "type": "host-local",</span><br><span class=\"line\"> "subnet": "172.19.0.0/24",</span><br><span class=\"line\"> "routes": [</span><br><span class=\"line\"> { "dst": "0.0.0.0/0" }</span><br><span class=\"line\"> ]</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"验证\"><a href=\"#验证\" class=\"headerlink\" title=\"验证\"></a>验证</h1><h2 id=\"check\"><a href=\"#check\" class=\"headerlink\" title=\"check\"></a>check</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># kata-runtime check</span><br><span class=\"line\">INFO[0000] IOMMUPlatform is disabled by default.</span><br><span class=\"line\">System is capable of running Kata Containers</span><br><span class=\"line\">System can currently create Kata Containers</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"启动容器\"><a href=\"#启动容器\" class=\"headerlink\" title=\"启动容器\"></a>启动容器</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">$ sudo ctr image pull docker.io/library/busybox:latest</span><br><span class=\"line\">$ sudo ctr run --cni --runtime io.containerd.run.kata.v2 -t --rm docker.io/library/busybox:latest hello sh</span><br></pre></td></tr></table></figure>\n\n\n<h1 id=\"Q-amp-A-编译阶段\"><a href=\"#Q-amp-A-编译阶段\" class=\"headerlink\" title=\"Q&A 编译阶段\"></a>Q&A 编译阶段</h1><h2 id=\"python\"><a href=\"#python\" class=\"headerlink\" title=\"python\"></a>python</h2><ol>\n<li>ModuleNotFoundError: No module named ‘_ctypes’ <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install libffi-devel </span><br><span class=\"line\">然后重新configure make make install</span><br></pre></td></tr></table></figure></li>\n</ol>\n<h2 id=\"gcc\"><a href=\"#gcc\" class=\"headerlink\" title=\"gcc\"></a>gcc</h2><ol>\n<li>g++: 错误:gengtype-lex.c:没有那个文件或目录 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install flex</span><br></pre></td></tr></table></figure></li>\n</ol>\n<h2 id=\"qemu-1\"><a href=\"#qemu-1\" class=\"headerlink\" title=\"qemu\"></a>qemu</h2><ol>\n<li>ERROR: glib-2.56 gthread-2.0 is required to compile QEMU <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install glib2-devel</span><br></pre></td></tr></table></figure></li>\n<li>ERROR: Dependency “pixman-1” not found, tried pkgconfig <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install pixman-devel</span><br></pre></td></tr></table></figure></li>\n<li>ERROR: Dependency “libseccomp” not found, tried pkgconfig <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install libseccomp-devel</span><br></pre></td></tr></table></figure></li>\n<li>ERROR: C header ‘cap-ng.h’ not found <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install libcap-ng-devel</span><br></pre></td></tr></table></figure></li>\n<li>ERROR: C shared or static library ‘rados’ not found <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">yum install libcephfs-devel librbd-devel librados-devel</span><br></pre></td></tr></table></figure></li>\n</ol>\n<h1 id=\"Q-amp-A-运行阶段\"><a href=\"#Q-amp-A-运行阶段\" class=\"headerlink\" title=\"Q&A 运行阶段\"></a>Q&A 运行阶段</h1><ol>\n<li><p>ctr: failed to create shim: failed to launch qemu: exit status 1, error messages from qemu log: qemu-system-aarch64: -device nvdimm,id=nv0,memdev=mem0: memory hotplug is not enabled: missing acpi-ged device : unknown </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">修改kata-container的configuration.toml</span><br><span class=\"line\">disable_image_nvdimm = true </span><br></pre></td></tr></table></figure>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">或者qemu应用下面补丁</span><br><span class=\"line\">https://patchwork.kernel.org/project/qemu-devel/cover/[email protected]/</span><br></pre></td></tr></table></figure>\n\n</li>\n<li><p>ctr: failed to create shim: Failed to Check if grpc server is working: rpc error: code = DeadlineExceeded desc = timed out connecting to vsock 2680247850:1024: unknown</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">内核文件问题,参考上面内核文件编译</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>Err:Could not create the sandbox resource controller cgroups: cgroup mountpoint does not exist</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">sudo mkdir /sys/fs/cgroup/systemd</span><br><span class=\"line\">sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd</span><br></pre></td></tr></table></figure></li>\n</ol>\n","categories":["虚拟化, Kata, Arm64"],"tags":["Qemu, Kata, Containerd, KVM, Arm64, 虚拟化"]},{"title":"CompletableFuture 学习汇总","url":"/2019/06/23/CompletableFuture%E5%AD%A6%E4%B9%A0%E6%B1%87%E6%80%BB/","content":"<h3 id=\"static方法\"><a href=\"#static方法\" class=\"headerlink\" title=\"static方法\"></a>static方法</h3><h4 id=\"public-static-CompletableFuture-supplyAsync-Supplier-supplier\"><a href=\"#public-static-CompletableFuture-supplyAsync-Supplier-supplier\" class=\"headerlink\" title=\"public static CompletableFuture supplyAsync(Supplier supplier)\"></a>public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)</h4><ul>\n<li>提交一个Supplier任务,异步执行,可以获取任务返回结果,使用ForkJoinPool.commonPool()执行任务。</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-supplyAsync-Supplier-supplier-Executor-executor\"><a href=\"#public-static-CompletableFuture-supplyAsync-Supplier-supplier-Executor-executor\" class=\"headerlink\" title=\"public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)\"></a>public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)</h4><ul>\n<li>提交一个Supplier任务,异步执行,可以获取任务返回结果,使用指定的线程池执行</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-runAsync-Runnable-runnable\"><a href=\"#public-static-CompletableFuture-runAsync-Runnable-runnable\" class=\"headerlink\" title=\"public static CompletableFuture runAsync(Runnable runnable)\"></a>public static CompletableFuture<Void> runAsync(Runnable runnable)</h4><ul>\n<li>提交一个Runnable任务,异步执行,无返回结果,使用ForkJoinPool.commonPool()执行任务。</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-runAsync-Runnable-runnable-Executor-executor\"><a href=\"#public-static-CompletableFuture-runAsync-Runnable-runnable-Executor-executor\" class=\"headerlink\" title=\"public static CompletableFuture runAsync(Runnable runnable, Executor executor)\"></a>public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)</h4><ul>\n<li>提交一个Supplier任务,异步执行,无返回结果,使用指定的线程池执行</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-completedFuture-U-value\"><a href=\"#public-static-CompletableFuture-completedFuture-U-value\" class=\"headerlink\" title=\"public static CompletableFuture completedFuture(U value)\"></a>public static <U> CompletableFuture<U> completedFuture(U value)</h4><ul>\n<li>新建一个完成的CompletableFuture,通常作为计算的起点阶段。</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-allOf-CompletableFuture-lt-gt-…-cfs\"><a href=\"#public-static-CompletableFuture-allOf-CompletableFuture-lt-gt-…-cfs\" class=\"headerlink\" title=\"public static CompletableFuture allOf(CompletableFuture<?>… cfs)\"></a>public static CompletableFuture<Void> allOf(CompletableFuture<?>… cfs)</h4><ul>\n<li>接收一个由CompletableFuture 构成的数组,需要等待多个 CompletableFuture 对象执行完毕,执行join操作可以等待CompletableFuture执行完成。</li>\n</ul>\n<h4 id=\"public-static-CompletableFuture-anyOf-CompletableFuture-lt-gt-…-cfs\"><a href=\"#public-static-CompletableFuture-anyOf-CompletableFuture-lt-gt-…-cfs\" class=\"headerlink\" title=\"public static CompletableFuture anyOf(CompletableFuture<?>… cfs)\"></a>public static CompletableFuture<Object> anyOf(CompletableFuture<?>… cfs)</h4><ul>\n<li>接收一个由CompletableFuture 构成的数组,返回由第一个执行完毕的 CompletableFuture 对象的返回值构成的CompletableFuture<Object> 。</li>\n</ul>\n<h4 id=\"示例代码\"><a href=\"#示例代码\" class=\"headerlink\" title=\"示例代码\"></a>示例代码</h4><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">import java.text.SimpleDateFormat;</span><br><span class=\"line\">import java.util.Date;</span><br><span class=\"line\">import java.util.concurrent.CompletableFuture;</span><br><span class=\"line\">import java.util.concurrent.ExecutionException;</span><br><span class=\"line\">import java.util.concurrent.ExecutorService;</span><br><span class=\"line\">import java.util.concurrent.Executors;</span><br><span class=\"line\"></span><br><span class=\"line\">public class ThreadTest1 {</span><br><span class=\"line\"> private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");</span><br><span class=\"line\"> public static void main(String[] args) throws ExecutionException, InterruptedException {</span><br><span class=\"line\"> ExecutorService pool = Executors.newFixedThreadPool(4);</span><br><span class=\"line\"> //创建一个直接完成的CompletableFuture</span><br><span class=\"line\"> String now = CompletableFuture.completedFuture("Test")</span><br><span class=\"line\"> .getNow("Fail");</span><br><span class=\"line\"> println("completedFuture: " + now);</span><br><span class=\"line\"> //创建一个带有返回值的CompletableFuture</span><br><span class=\"line\"> CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\"> return "Test";</span><br><span class=\"line\"> }, pool);</span><br><span class=\"line\"> //延迟100ms,这里获取是default值</span><br><span class=\"line\"> now = task.getNow("Fail");</span><br><span class=\"line\"> println("supplyAsync: now: " + now);</span><br><span class=\"line\"> //等待任务完成后输出</span><br><span class=\"line\"> println("supplyAsync: get: " + task.get());</span><br><span class=\"line\"> System.out.println("---------------------分割线----------------------");</span><br><span class=\"line\"> //耗时100ms的任务</span><br><span class=\"line\"> CompletableFuture<Void> task100 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\"> println("runAsync :" + Thread.currentThread().getName() + " task100 done");</span><br><span class=\"line\"> }, pool);</span><br><span class=\"line\"> //耗时200ms的任务</span><br><span class=\"line\"> CompletableFuture<Void> task200 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(200);</span><br><span class=\"line\"> println("runAsync :" + Thread.currentThread().getName() + " task200 done");</span><br><span class=\"line\"> }, pool);</span><br><span class=\"line\"> //任意一个完成就会继续执行</span><br><span class=\"line\"> CompletableFuture.anyOf(task100, task200).join();</span><br><span class=\"line\"> println("anyOf Done");</span><br><span class=\"line\"> //全部完成才会继续执行</span><br><span class=\"line\"> CompletableFuture.allOf(task100, task200).join();</span><br><span class=\"line\"> println("allOf Done");</span><br><span class=\"line\"> //关闭线程池</span><br><span class=\"line\"> pool.shutdown();</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> private static void sleep(long time) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Thread.sleep(time);</span><br><span class=\"line\"> } catch (InterruptedException e) {</span><br><span class=\"line\"> e.printStackTrace();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> private static void println(Object object){</span><br><span class=\"line\"> System.out.println(sdf.format(new Date()) + ": " + object);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>运行结果</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">2019-06-23 18:02:03.552: completedFuture: Test</span><br><span class=\"line\">2019-06-23 18:02:03.604: supplyAsync: now: Fail</span><br><span class=\"line\">2019-06-23 18:02:03.710: supplyAsync: get: Test</span><br><span class=\"line\">-------------------------------------------</span><br><span class=\"line\">2019-06-23 18:02:03.813: runAsync :pool-1-thread-2 task100 done</span><br><span class=\"line\">2019-06-23 18:02:03.813: anyOf Done</span><br><span class=\"line\">2019-06-23 18:02:04.012: runAsync :pool-1-thread-3 task200 done</span><br><span class=\"line\">2019-06-23 18:02:04.012: allOf Done</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"实例方法\"><a href=\"#实例方法\" class=\"headerlink\" title=\"实例方法\"></a>实例方法</h3><ul>\n<li>实例方法整体比较规则,一个标准执行方法,一个异步执行方法,一个指定异步线程执行方法</li>\n</ul>\n<h4 id=\"thenApply\"><a href=\"#thenApply\" class=\"headerlink\" title=\"thenApply\"></a>thenApply</h4><ul>\n<li>then是指在当前阶段正常执行完成后(正常执行是指没有抛出异常)进行的操作。Apply是指将一个Function作用于之前阶段得出的结果(即将上一步的结果进行转换)</li>\n<li>public <U> CompletableFuture<U> (Function<? super T,? extends U> fn)</li>\n<li>public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)</li>\n<li>public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenApplyAsyncDemo(){</span><br><span class=\"line\"> Integer integer = CompletableFuture</span><br><span class=\"line\"> .completedFuture("task 1")</span><br><span class=\"line\"> .thenApplyAsync(x -> {</span><br><span class=\"line\"> String intString = x.split(" ")[1];</span><br><span class=\"line\"> return Integer.valueOf(intString);</span><br><span class=\"line\"> })</span><br><span class=\"line\"> .join();</span><br><span class=\"line\"> System.out.println("thenApplyAsyncDemo: " + integer);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">thenApplyAsyncDemo: 1 </span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"thenAccept\"><a href=\"#thenAccept\" class=\"headerlink\" title=\"thenAccept\"></a>thenAccept</h4><ul>\n<li>下一个Stage接收了当前Stage的结果但是在计算中无需返回值(可以简单认为这里就是消费终点,因为没有返回值。当然下一步不依赖当前返回值的情况除外)</li>\n<li>public CompletableFuture<Void> thenAccept(Consumer<? super T> action)</li>\n<li>public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)</li>\n<li>public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenAcceptDemo(){</span><br><span class=\"line\"> CompletableFuture</span><br><span class=\"line\"> .completedFuture("task")</span><br><span class=\"line\"> .thenAcceptAsync(s -> {</span><br><span class=\"line\"> System.out.println("thenAcceptDemo:" + s);</span><br><span class=\"line\"> })</span><br><span class=\"line\"> .join();</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">thenAcceptDemo:task</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"thenRun\"><a href=\"#thenRun\" class=\"headerlink\" title=\"thenRun\"></a>thenRun</h4><ul>\n<li>不再关心上一步运算的结果,直接进行下一步的运算</li>\n<li>public CompletableFuture<Void> thenRun(Runnable action)</li>\n<li>public CompletableFuture<Void> thenRunAsync(Runnable action)</li>\n<li>public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenRunDemo() {</span><br><span class=\"line\"> CompletableFuture.completedFuture("Task")</span><br><span class=\"line\"> .thenRun(() -> System.out.println("我不知道上面的参数,也不会继续往下传递值"))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">我不知道上面的参数,也不会继续往下传递值</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"thenCombine\"><a href=\"#thenCombine\" class=\"headerlink\" title=\"thenCombine\"></a>thenCombine</h4><ul>\n<li>结合前面两个Stage的结果,进行转化</li>\n<li>public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)</li>\n<li>public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn) </li>\n<li>public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenCombineDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "task 1");</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "task 2");</span><br><span class=\"line\"> String result = task1.thenCombineAsync(task2, (t1, t2) -> t1 + " - "+ t2)</span><br><span class=\"line\"> .join();</span><br><span class=\"line\"> System.out.println(result);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">task 1 - task 2</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"thenAcceptBoth\"><a href=\"#thenAcceptBoth\" class=\"headerlink\" title=\"thenAcceptBoth\"></a>thenAcceptBoth</h4><ul>\n<li>结合两个CompletionStage的结果,进行消耗,和thenCombine相比,只是少了返回值</li>\n<li>public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)</li>\n<li>public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action) </li>\n<li>public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenAcceptBothDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "task 1");</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "task 2");</span><br><span class=\"line\"> task1.thenAcceptBoth(task2, (t1, t2) -> System.out.println("thenAcceptBothDemo: " +t1 + " - " + t2))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">thenAcceptBothDemo: task 1 - task 2</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"runAfterBoth\"><a href=\"#runAfterBoth\" class=\"headerlink\" title=\"runAfterBoth\"></a>runAfterBoth</h4><ul>\n<li>在两个CompletionStage都运行完执行。</li>\n<li>public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action)</li>\n<li>public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action) </li>\n<li>public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void runAfterBothDemo() {</span><br><span class=\"line\"> CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\"> println("task1 Done");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(200);</span><br><span class=\"line\"> println("task2 Done");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> task1.runAfterBoth(task2, () -> println("Task 1 And Task 2 Both Done"))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 19:22:08.498: task1 Done</span><br><span class=\"line\">2019-06-23 19:22:08.595: task2 Done</span><br><span class=\"line\">2019-06-23 19:22:08.596: Task 1 And Task 2 Both Done</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"applyToEither\"><a href=\"#applyToEither\" class=\"headerlink\" title=\"applyToEither\"></a>applyToEither</h4><ul>\n<li>在两个CompletionStage中选择计算快的,将其结果进行下一步的转化操作。</li>\n<li>public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)</li>\n<li>public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) </li>\n<li>public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn,Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void applyToEitherDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> return "task 1";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> return "task 2";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> Integer result = task1.applyToEither(task2, t -> Integer.valueOf(t.split(" ")[1]))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\"> System.out.println("applyToEitherDemo:" + result);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">applyToEitherDemo:1</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"acceptEither\"><a href=\"#acceptEither\" class=\"headerlink\" title=\"acceptEither\"></a>acceptEither</h4><ul>\n<li>在两个CompletionStage中选择计算快的,作为下一步计算的结果。</li>\n<li>public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) </li>\n<li>public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)</li>\n<li>public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action,Executor executor)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void acceptEitherDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> return "task 1";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> return "task 2";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> task1.acceptEitherAsync(task2, t -> System.out.println("acceptEitherDemo:" +Integer.valueOf(t.split(" ")[1])))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">acceptEitherDemo:1</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"runAfterEither\"><a href=\"#runAfterEither\" class=\"headerlink\" title=\"runAfterEither\"></a>runAfterEither</h4><ul>\n<li>两个CompletionStage,任何一个完成了都会执行下一步的操作。</li>\n<li>public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action) </li>\n<li>public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action) </li>\n<li>public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void runAfterEitherDemo() {</span><br><span class=\"line\"> CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\"> println("task1 Done");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {</span><br><span class=\"line\"> sleep(200);</span><br><span class=\"line\"> println("task2 Done");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> task1.runAfterEither(task2, () -> println("Task 1 Or Task 2 Done"))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 19:24:27.644: task1 Done</span><br><span class=\"line\">2019-06-23 19:24:27.645: Task 1 Or Task 2 Done</span><br><span class=\"line\">2019-06-23 19:24:27.749: task2 Done</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"thenCompose\"><a href=\"#thenCompose\" class=\"headerlink\" title=\"thenCompose\"></a>thenCompose</h4><ul>\n<li>连接两个CompletableFuture,返回值是新的CompletableFuture</li>\n<li>public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) </li>\n<li>public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) </li>\n<li>public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,Executor executor)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void thenComposeDemo() {</span><br><span class=\"line\"> String result = CompletableFuture.completedFuture("Start")</span><br><span class=\"line\"> .thenCompose(x -> CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> return x + " task 2";</span><br><span class=\"line\"> }))</span><br><span class=\"line\"> .thenCompose(x -> CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> return x + " task 1";</span><br><span class=\"line\"> }))</span><br><span class=\"line\"> .join();</span><br><span class=\"line\"> System.out.println("thenComposeDemo:" +result);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">thenComposeDemo:Start task 2 task 1</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"whenComplete\"><a href=\"#whenComplete\" class=\"headerlink\" title=\"whenComplete\"></a>whenComplete</h4><ul>\n<li>当运行完成时,对结果的记录。<ul>\n<li>正常执行,返回值。</li>\n<li>异常抛出造成程序的中断</li>\n</ul>\n</li>\n<li>注意,内部线程出现异常会抛到外层,导致外层线程产生异常。</li>\n<li>public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) </li>\n<li>public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)</li>\n<li>public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void whenCompleteDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> return "task 1";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> throw new RuntimeException("task2 RuntimeException");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> String task1res = task1.whenComplete((res, exp) -> {</span><br><span class=\"line\"> println("task1 res:" + res);</span><br><span class=\"line\"> println("task1 exp:" + exp);</span><br><span class=\"line\"> }).join();</span><br><span class=\"line\"> println(task1res);</span><br><span class=\"line\"> String task2res = task2.whenComplete((res, exp) -> {</span><br><span class=\"line\"> println("task2 res:" + res);</span><br><span class=\"line\"> println("task2 exp:" + exp.getMessage());</span><br><span class=\"line\"> }).join();</span><br><span class=\"line\"> println(task2res);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 19:50:03.429 ForkJoinPool.commonPool-worker-1: task1 res:task 1</span><br><span class=\"line\">2019-06-23 19:50:03.430 ForkJoinPool.commonPool-worker-1: task1 exp:null</span><br><span class=\"line\">2019-06-23 19:50:03.430 main: task 1</span><br><span class=\"line\">2019-06-23 19:50:03.439 ForkJoinPool.commonPool-worker-2: task2 res:null</span><br><span class=\"line\">2019-06-23 19:50:03.439 ForkJoinPool.commonPool-worker-2: task2 exp:java.lang.RuntimeException: task2 RuntimeException</span><br><span class=\"line\">Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: task2 RuntimeException</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)</span><br><span class=\"line\"> at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)</span><br><span class=\"line\"> at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)</span><br><span class=\"line\"> at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)</span><br><span class=\"line\"> at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)</span><br><span class=\"line\">Caused by: java.lang.RuntimeException: task2 RuntimeException</span><br><span class=\"line\"> at com.example.demo.ThreadTest.lambda$whenCompleteDemo$5(ThreadTest.java:44)</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)</span><br><span class=\"line\"> ... 5 more</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"handle\"><a href=\"#handle\" class=\"headerlink\" title=\"handle\"></a>handle</h4><ul>\n<li>运行完成时,对结果的处理。这里的完成时有两种情况,<ul>\n<li>正常执行,返回值</li>\n<li>遇到异常抛出造成程序的中断。</li>\n</ul>\n</li>\n<li>异常不会被抛到外层,不会造成外部线程因为异常中断</li>\n<li>public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) </li>\n<li>public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) </li>\n<li>public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void handleDemo() {</span><br><span class=\"line\"> CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> return "task 1";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> throw new RuntimeException("task2 RuntimeException");</span><br><span class=\"line\"> });</span><br><span class=\"line\"> String task1res = task1.handle((res, exp) -> {</span><br><span class=\"line\"> println("task1 res:" + res);</span><br><span class=\"line\"> println("task1 exp:" + exp);</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }).join();</span><br><span class=\"line\"> println(task1res);</span><br><span class=\"line\"> String task2res = task2.handle((res, exp) -> {</span><br><span class=\"line\"> println("task2 res:" + res);</span><br><span class=\"line\"> println("task2 exp:" + exp.getMessage());</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }).join();</span><br><span class=\"line\"> println(task2res);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 19:50:53.542 ForkJoinPool.commonPool-worker-1: task1 res:task 1</span><br><span class=\"line\">2019-06-23 19:50:53.543 ForkJoinPool.commonPool-worker-1: task1 exp:null</span><br><span class=\"line\">2019-06-23 19:50:53.543 main: task 1</span><br><span class=\"line\">2019-06-23 19:50:53.552 ForkJoinPool.commonPool-worker-2: task2 res:null</span><br><span class=\"line\">2019-06-23 19:50:53.552 ForkJoinPool.commonPool-worker-2: task2 exp:java.lang.RuntimeException: task2 RuntimeException</span><br><span class=\"line\">2019-06-23 19:50:53.552 main: null</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n</ul>\n<h4 id=\"exceptionally\"><a href=\"#exceptionally\" class=\"headerlink\" title=\"exceptionally\"></a>exceptionally</h4><ul>\n<li>异常处理逻辑,可以设置异常情况下的返回值</li>\n<li>public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void exceptionallyDemo() {</span><br><span class=\"line\"> Object result = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(20);</span><br><span class=\"line\"> throw new RuntimeException("task2 RuntimeException");</span><br><span class=\"line\"> }).exceptionally(e->{</span><br><span class=\"line\"> System.out.println("exceptionally: " +e);</span><br><span class=\"line\"> return e.getMessage();</span><br><span class=\"line\"> }).join();</span><br><span class=\"line\"> System.out.println("exceptionallyDemo: " +result);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">exceptionally: java.util.concurrent.CompletionException: java.lang.RuntimeException: task2 RuntimeException</span><br><span class=\"line\">exceptionallyDemo: java.lang.RuntimeException: task2 RuntimeException</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"其他方法\"><a href=\"#其他方法\" class=\"headerlink\" title=\"其他方法\"></a>其他方法</h3><ul>\n<li><p>public boolean isDone()</p>\n<ul>\n<li>是否已经完成(包括正常完成,异常完成,取消完成)</li>\n</ul>\n</li>\n<li><p>public T get() throws InterruptedException, ExecutionException</p>\n<ul>\n<li>阻塞方式获取结果</li>\n</ul>\n</li>\n<li><p>public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException</p>\n<ul>\n<li>带超时方式的阻塞方式获取结果</li>\n</ul>\n</li>\n<li><p>public T join() </p>\n<ul>\n<li>阻塞至任务完成。会抛出CompletionException(unchecked类型)</li>\n</ul>\n</li>\n<li><p>public T getNow(T valueIfAbsent) </p>\n<ul>\n<li>立即获取结果,如果任务没有执行完成,则返回valueIfAbsent</li>\n</ul>\n</li>\n<li><p>public boolean complete(T value)</p>\n<ul>\n<li>如果任务还没有执行完成,则用当前值去替换完成值,否则继续使用原始值。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static void completeDemo() {</span><br><span class=\"line\"> println("complete start");</span><br><span class=\"line\"> CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> println("runAsync start");</span><br><span class=\"line\"> sleep(2000);</span><br><span class=\"line\"> println("runAsync end");</span><br><span class=\"line\"> return "task run finish";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> boolean complete = task.complete("finish");</span><br><span class=\"line\"> System.out.println("complete:"+complete);</span><br><span class=\"line\"> System.out.println("task:"+task.join());</span><br><span class=\"line\"> println("complete end");</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 20:55:19.491 main: complete start</span><br><span class=\"line\">complete:true</span><br><span class=\"line\">task:finish</span><br><span class=\"line\">2019-06-23 20:55:19.547 main: complete end</span><br><span class=\"line\">------------------------------------------------------------------------</span><br><span class=\"line\">private static void completeDemo() {</span><br><span class=\"line\"> println("complete start");</span><br><span class=\"line\"> CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> println("runAsync start");</span><br><span class=\"line\"> sleep(1);</span><br><span class=\"line\"> println("runAsync end");</span><br><span class=\"line\"> return "task run finish";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> boolean complete = task.complete("finish");</span><br><span class=\"line\"> System.out.println("complete:"+complete);</span><br><span class=\"line\"> System.out.println("task:"+task.join());</span><br><span class=\"line\"> println("complete end");</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 20:59:32.375 main: complete start</span><br><span class=\"line\">2019-06-23 20:59:32.426 ForkJoinPool.commonPool-worker-1: runAsync start</span><br><span class=\"line\">2019-06-23 20:59:32.428 ForkJoinPool.commonPool-worker-1: runAsync end</span><br><span class=\"line\">complete:false</span><br><span class=\"line\">task:task run finish</span><br><span class=\"line\">2019-06-23 20:59:32.437 main: complete end</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public boolean completeExceptionally(Throwable ex)</p>\n<ul>\n<li>如果任务还没有执行完成,则以异常的方式中断执行(调用join方法会抛出该异常),如果执行完成,则返回false,并正常执行 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> private static void completeExceptionallyDemo() {</span><br><span class=\"line\"> println("completeExceptionally start");</span><br><span class=\"line\"> CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> println("supplyAsync start");</span><br><span class=\"line\"> sleep(1);</span><br><span class=\"line\"> println("supplyAsync end");</span><br><span class=\"line\"> return "task run finish";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> boolean complete = task.completeExceptionally(new NullPointerException("Test Null"));</span><br><span class=\"line\"> System.out.println("complete:"+complete);</span><br><span class=\"line\"> System.out.println("task:"+task.join());</span><br><span class=\"line\"> println("complete 1 end");</span><br><span class=\"line\"></span><br><span class=\"line\"> task = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> println("supplyAsync start");</span><br><span class=\"line\"> sleep(1);</span><br><span class=\"line\"> println("supplyAsync end");</span><br><span class=\"line\"> return "task run finish";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> complete = task.completeExceptionally(new NullPointerException("Test Null"));</span><br><span class=\"line\"> System.out.println("complete:"+complete);</span><br><span class=\"line\"> System.out.println("task:"+task.join());</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 21:11:48.276 main: completeExceptionally start</span><br><span class=\"line\">2019-06-23 21:11:48.330 ForkJoinPool.commonPool-worker-1: supplyAsync start</span><br><span class=\"line\">2019-06-23 21:11:48.331 ForkJoinPool.commonPool-worker-1: supplyAsync end</span><br><span class=\"line\">complete:false</span><br><span class=\"line\">task:task run finish</span><br><span class=\"line\">2019-06-23 21:11:48.340 main: complete 1 end</span><br><span class=\"line\">complete:true</span><br><span class=\"line\">Exception in thread "main" java.util.concurrent.CompletionException: java.lang.NullPointerException: Test Null</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:375)</span><br><span class=\"line\"> at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1934)</span><br><span class=\"line\"> at com.example.demo.ThreadTest.completeExceptionallyDemo(ThreadTest.java:35)</span><br><span class=\"line\"> at com.example.demo.ThreadTest.main(ThreadTest.java:9)</span><br><span class=\"line\">Caused by: java.lang.NullPointerException: Test Null</span><br><span class=\"line\"> at com.example.demo.ThreadTest.completeExceptionallyDemo(ThreadTest.java:33)</span><br><span class=\"line\"> ... 1 more</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public CompletableFuture<T> toCompletableFuture()</p>\n<ul>\n<li>返回CompletableFuture对象,实际代码中返回this</li>\n</ul>\n</li>\n<li><p>public boolean cancel(boolean mayInterruptIfRunning)</p>\n<ul>\n<li>取消任务,mayInterruptIfRunning在当前实现没有任何作用。。(醉了)</li>\n<li>任务取消后如果执行join方法会抛出CancellationException异常。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static void cancelDemo() {</span><br><span class=\"line\"> CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {</span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public void run() {</span><br><span class=\"line\"> println("Start");</span><br><span class=\"line\"> sleep(1000);</span><br><span class=\"line\"> println("End");</span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> boolean cancel = future.cancel(true);</span><br><span class=\"line\"> println(cancel);</span><br><span class=\"line\"> System.out.println("----------------------------------");</span><br><span class=\"line\"> future = CompletableFuture.runAsync(new Runnable() {</span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public void run() {</span><br><span class=\"line\"> println("Start");</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> println("End");</span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\"> sleep(100);</span><br><span class=\"line\"> cancel = future.cancel(true);</span><br><span class=\"line\"> println(cancel);</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 21:56:33.060 ForkJoinPool.commonPool-worker-1: Start</span><br><span class=\"line\">2019-06-23 21:56:33.072 main: true</span><br><span class=\"line\">----------------------------------</span><br><span class=\"line\">2019-06-23 21:56:33.075 ForkJoinPool.commonPool-worker-2: Start</span><br><span class=\"line\">2019-06-23 21:56:33.086 ForkJoinPool.commonPool-worker-2: End</span><br><span class=\"line\">2019-06-23 21:56:33.176 main: false</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public boolean isCancelled()</p>\n<ul>\n<li>返回当前任务是否已经被取消</li>\n</ul>\n</li>\n<li><p>public boolean isCompletedExceptionally()</p>\n<ul>\n<li>返回当前任务是否异常方式中断</li>\n</ul>\n</li>\n<li><p>public void obtrudeValue(T value)</p>\n<ul>\n<li>将future的结果强制更改为value,无论是否发生异常</li>\n</ul>\n</li>\n<li><p>public void obtrudeException(Throwable ex)</p>\n<ul>\n<li>将future的结果强制更改为异常,只要调用get或者join均会抛出该异常,同时会修改isCompletedExceptionally的结果</li>\n</ul>\n</li>\n<li><p>public int getNumberOfDependents()</p>\n<ul>\n<li>返回有多少个后续stage依赖当前stage <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static void getNumberOfDependentsDemo() {</span><br><span class=\"line\"> CompletableFuture<String> t1 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(1000);</span><br><span class=\"line\"> return "1";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> CompletableFuture<String> t2 = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> sleep(1000);</span><br><span class=\"line\"> return "2";</span><br><span class=\"line\"> });</span><br><span class=\"line\"> sleep(10);</span><br><span class=\"line\"> println(t1.getNumberOfDependents());</span><br><span class=\"line\"> println(t2.getNumberOfDependents());</span><br><span class=\"line\"> CompletableFuture<Void> all = CompletableFuture.allOf(t1, t2);</span><br><span class=\"line\"> println(all.isDone());</span><br><span class=\"line\"> println(t1.getNumberOfDependents());</span><br><span class=\"line\"> println(t2.getNumberOfDependents());</span><br><span class=\"line\"> all.join();</span><br><span class=\"line\"> println(t1.getNumberOfDependents());</span><br><span class=\"line\"> println(t2.getNumberOfDependents());</span><br><span class=\"line\">}</span><br><span class=\"line\">--------- 输出 -----------</span><br><span class=\"line\">2019-06-23 22:26:45.132 main: 0</span><br><span class=\"line\">2019-06-23 22:26:45.133 main: 0</span><br><span class=\"line\">2019-06-23 22:26:45.133 main: false</span><br><span class=\"line\">2019-06-23 22:26:45.133 main: 1</span><br><span class=\"line\">2019-06-23 22:26:45.133 main: 1</span><br><span class=\"line\">2019-06-23 22:26:46.122 main: 0</span><br><span class=\"line\">2019-06-23 22:26:46.122 main: 0</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h3><ul>\n<li><a href=\"https://www.jianshu.com/p/6bac52527ca4\">https://www.jianshu.com/p/6bac52527ca4</a></li>\n<li><a href=\"https://www.jianshu.com/p/558b090ae4bb\">https://www.jianshu.com/p/558b090ae4bb</a></li>\n<li><a href=\"https://www.jb51.net/article/51163.htm\">https://www.jb51.net/article/51163.htm</a></li>\n<li><a href=\"https://www.cnblogs.com/dennyzhangdd/p/7010972.html\">https://www.cnblogs.com/dennyzhangdd/p/7010972.html</a></li>\n<li><a href=\"https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html?is-external=true\">https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html?is-external=true</a></li>\n</ul>\n</li>\n</ul>\n","categories":["Java"],"tags":["Java","异步","CompletableFuture"]},{"title":"CopyOnWriteArrayList && CopyOnWriteArraySet 总结","url":"/2019/07/02/Cow%E6%80%BB%E7%BB%93/","content":"<p>这两个都是Java提供的原生写时复制的容器(也就是java.util.concurrent包下的)。CopyOnWrite的基本思路是在修改(包括增加和修改)之前,拷贝出一份快照,对快照进行修改,然后替换原引用。读取操作则没有进行加锁,所以适用于读多写少的场景。另外由于使用了快照模式(迭代器,修改等操作),因此不能保证强一致性,只能保证最终一致性。</p>\n<p>CopyOnWriteArraySet是对CopyOnWriteArrayList的包装,所以重点关注一下CopyOnWriteArrayList。</p>\n<h1 id=\"CopyOnWriteArrayList\"><a href=\"#CopyOnWriteArrayList\" class=\"headerlink\" title=\"CopyOnWriteArrayList\"></a>CopyOnWriteArrayList</h1><p>从增删改查4个方面总结下这个容器(相对ConcurrentHashMap,CopyOnWrite的实现实在是简单太多了。。。)</p>\n<h2 id=\"增\"><a href=\"#增\" class=\"headerlink\" title=\"增\"></a>增</h2><p>增加方法就2个:</p>\n<ul>\n<li>public boolean add(E e)<ul>\n<li>整体思路比较简单,先获取排他锁(同一时刻只能有一个线程进行修改操作),然后拷贝一份新的数组并插入新的元素(此时读取操作读的还是旧的数组),最后替换引用并释放锁。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean add(E e) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();//获取排他锁</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();//获取当前引用(可以理解为快照)</span><br><span class=\"line\"> int len = elements.length;//当前的长度</span><br><span class=\"line\"> Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝到一个新的数组中</span><br><span class=\"line\"> newElements[len] = e;//末尾增加新的元素</span><br><span class=\"line\"> setArray(newElements);//替换引用</span><br><span class=\"line\"> return true;//返回结果</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();//释放排他锁</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li>public void add(int index, E element)<ul>\n<li>整体流程和上一个方法基本一致,区别在于进行索引的有效判断,同时检测是不是在数组的最后插入,拷贝的过程是调用System.arraycopy进行分区间拷贝。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void add(int index, E element) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();//一样获取独占锁</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();//获取当前引用</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> if (index > len || index < 0)//越界检查</span><br><span class=\"line\"> throw new IndexOutOfBoundsException("Index: "+index+</span><br><span class=\"line\"> ", Size: "+len);</span><br><span class=\"line\"> Object[] newElements;</span><br><span class=\"line\"> int numMoved = len - index;</span><br><span class=\"line\"> if (numMoved == 0)//判断是不是最后一个元素</span><br><span class=\"line\"> newElements = Arrays.copyOf(elements, len + 1);//直接同上一个方法,生成len+1长度的数组</span><br><span class=\"line\"> else {</span><br><span class=\"line\"> newElements = new Object[len + 1];//生成新的空数组</span><br><span class=\"line\"> System.arraycopy(elements, 0, newElements, 0, index);//拷贝0~index</span><br><span class=\"line\"> System.arraycopy(elements, index, newElements, index + 1,</span><br><span class=\"line\"> numMoved);//拷贝index+1 ~ 最后</span><br><span class=\"line\"> }</span><br><span class=\"line\"> newElements[index] = element;//index位置填充</span><br><span class=\"line\"> setArray(newElements);//替换引用</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();//释放锁</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"删\"><a href=\"#删\" class=\"headerlink\" title=\"删\"></a>删</h2><p>删除操作对外暴露了2个方法,一个是通过索引下标进行删除,一个是找到指定的Object进行删除。</p>\n<ul>\n<li>public E remove(int index)<ul>\n<li>这个方法实现和 add(int index, E element) 基本类似,不做过多解释了。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public E remove(int index) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> E oldValue = get(elements, index);//获取指定下标元素</span><br><span class=\"line\"> int numMoved = len - index - 1;</span><br><span class=\"line\"> if (numMoved == 0)//判断是否为最后一个元素</span><br><span class=\"line\"> setArray(Arrays.copyOf(elements, len - 1));//形成新的0~len-1数组</span><br><span class=\"line\"> else {</span><br><span class=\"line\"> Object[] newElements = new Object[len - 1];</span><br><span class=\"line\"> //跳过要删除的元素 </span><br><span class=\"line\"> System.arraycopy(elements, 0, newElements, 0, index);</span><br><span class=\"line\"> System.arraycopy(elements, index + 1, newElements, index,</span><br><span class=\"line\"> numMoved);</span><br><span class=\"line\"> setArray(newElements);//替换引用</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return oldValue;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li>public boolean remove(Object o)<ul>\n<li>首先获取数组引用,然后尝试寻找元素,找到了则进入删除操作,找不到则直接返回false <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean remove(Object o) {</span><br><span class=\"line\"> Object[] snapshot = getArray();//获取引用快照</span><br><span class=\"line\"> int index = indexOf(o, snapshot, 0, snapshot.length);//尝试寻找(注意,此时并未加锁,如果此时插入了数据或者其他线程插入了数据,是看不到的,需要在删除的时候重新进行查找)</span><br><span class=\"line\"> return (index < 0) ? false : remove(o, snapshot, index);//找到了则进行删除(index>=0的情况),找不到则返回false</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>看一下具体的删除操作 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private boolean remove(Object o, Object[] snapshot, int index) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();//加锁</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] current = getArray();//重新获取一次快照引用</span><br><span class=\"line\"> int len = current.length;</span><br><span class=\"line\"> if (snapshot != current) findIndex: {//判断在上一步中获取到的快照是否发生了改变,如果没有改变,则直接进行删除,如果发生了改变,则需要进一步处理,也就是下面的逻辑。</span><br><span class=\"line\"> int prefix = Math.min(index, len);//判断一下以防数组越界</span><br><span class=\"line\"> for (int i = 0; i < prefix; i++) {</span><br><span class=\"line\"> if (current[i] != snapshot[i] && eq(o, current[i])) {//依次遍历查找,找到则结束if语句块,并更新index</span><br><span class=\"line\"> index = i;</span><br><span class=\"line\"> break findIndex;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (index >= len)//上述条件并未找到,则判断是否已经超过索引长度(其他线程已经删除元素了,可能会执行到这里)</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> if (current[index] == o)//这块没想明白什么情况会执行到这里</span><br><span class=\"line\"> break findIndex;</span><br><span class=\"line\"> index = indexOf(o, current, index, len);//重新查找,因为已经加锁了,所以不会有其他线程进行修改</span><br><span class=\"line\"> if (index < 0)</span><br><span class=\"line\"> return false;//没有找到</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //后面就简单了,还是重新拷贝,更新引用</span><br><span class=\"line\"> Object[] newElements = new Object[len - 1];</span><br><span class=\"line\"> System.arraycopy(current, 0, newElements, 0, index);</span><br><span class=\"line\"> System.arraycopy(current, index + 1,</span><br><span class=\"line\"> newElements, index,</span><br><span class=\"line\"> len - index - 1);</span><br><span class=\"line\"> setArray(newElements);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"改\"><a href=\"#改\" class=\"headerlink\" title=\"改\"></a>改</h2><p>修改操作相对比较简单,通过索引下标直接更新即可,方法如下:</p>\n<ul>\n<li>public E set(int index, E element)<ul>\n<li>操作也不复杂,加锁后重新拷贝并替换,唯独注意就是元素没有变化,也要重新更新一下引用(注释说是为了确保volatile写语义) <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public E set(int index, E element) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();</span><br><span class=\"line\"> E oldValue = get(elements, index);</span><br><span class=\"line\"></span><br><span class=\"line\"> if (oldValue != element) {</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> Object[] newElements = Arrays.copyOf(elements, len);</span><br><span class=\"line\"> newElements[index] = element;</span><br><span class=\"line\"> setArray(newElements);</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> // Not quite a no-op; ensures volatile write semantics</span><br><span class=\"line\"> setArray(elements);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return oldValue;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"查\"><a href=\"#查\" class=\"headerlink\" title=\"查\"></a>查</h2><p>查询操作外部暴露了4个方法,其实内部都是委托给对应的私有方法,只是没有index参数的方法委托的时候传值是0。</p>\n<ul>\n<li>public int indexOf(Object o)</li>\n<li>public int indexOf(E e, int index)</li>\n<li>public int lastIndexOf(Object o)</li>\n<li>public int lastIndexOf(E e, int index)<br>前两个方法委托给 private static int indexOf(Object o, Object[] elements, int index, int fence) 执行<br>后两个方法委托给 private static int lastIndexOf(Object o, Object[] elements, int index) 执行<br>下面整理一下这两个查找方法:</li>\n<li>indexOf 只是看一下元素是否为空,为空则找null,不为空则直接进行遍历查找 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static int indexOf(Object o, Object[] elements,</span><br><span class=\"line\"> int index, int fence) {</span><br><span class=\"line\"> if (o == null) {</span><br><span class=\"line\"> for (int i = index; i < fence; i++)</span><br><span class=\"line\"> if (elements[i] == null)</span><br><span class=\"line\"> return i;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> for (int i = index; i < fence; i++)</span><br><span class=\"line\"> if (o.equals(elements[i]))</span><br><span class=\"line\"> return i;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return -1;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>lastIndexOf 和 indexOf 几乎相同,只是倒着查找而已。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static int lastIndexOf(Object o, Object[] elements, int index) {</span><br><span class=\"line\"> if (o == null) {</span><br><span class=\"line\"> for (int i = index; i >= 0; i--)</span><br><span class=\"line\"> if (elements[i] == null)</span><br><span class=\"line\"> return i;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> for (int i = index; i >= 0; i--)</span><br><span class=\"line\"> if (o.equals(elements[i]))</span><br><span class=\"line\"> return i;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return -1;</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"其他方法\"><a href=\"#其他方法\" class=\"headerlink\" title=\"其他方法\"></a>其他方法</h2><ul>\n<li><p>contains方法,也是直接调用indexOf方法,判断一下索引位置是不是>=0,就不多说了</p>\n</li>\n<li><p>get方法,直接获取一下数组引用(获取引用之后,其他线程修改,这里也看不到了。。。),然后取对应下标。</p>\n</li>\n<li><p>addIfAbsent方法,CopyOnWriteArraySet就是基于这个方法实现的。先通过indexOf方法查找,找到直接返回false,找不到则加锁进行添加,添加之前会判断一下引用是否改变,和remove比较相似。代码如下:</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private boolean addIfAbsent(E e, Object[] snapshot) {</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] current = getArray();</span><br><span class=\"line\"> int len = current.length;</span><br><span class=\"line\"> if (snapshot != current) {//引用发生改变,则需要重新查找,如果还是没找到,则继续添加</span><br><span class=\"line\"> // Optimize for lost race to another addXXX operation</span><br><span class=\"line\"> int common = Math.min(snapshot.length, len);</span><br><span class=\"line\"> for (int i = 0; i < common; i++)//遍历查找,并且找到了,则返回false</span><br><span class=\"line\"> if (current[i] != snapshot[i] && eq(e, current[i]))</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> if (indexOf(e, current, common, len) >= 0)//没太明白为什么又要再次查找。。</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> Object[] newElements = Arrays.copyOf(current, len + 1);</span><br><span class=\"line\"> newElements[len] = e;</span><br><span class=\"line\"> setArray(newElements);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n<li><p>containsAll方法, 实现也比较简单暴力,获取快照后,直接for循环对每一个元素进行判断,就不附代码了。</p>\n</li>\n<li><p>removeAll方法,也不算复杂,加锁之后遍历元素,不在待删除集合中,则加入新数组中,然后重新拷贝一份即可</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean removeAll(Collection<?> c) {</span><br><span class=\"line\"> if (c == null) throw new NullPointerException();</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> if (len != 0) {</span><br><span class=\"line\"> // temp array holds those elements we know we want to keep</span><br><span class=\"line\"> int newlen = 0;</span><br><span class=\"line\"> Object[] temp = new Object[len];</span><br><span class=\"line\"> for (int i = 0; i < len; ++i) {</span><br><span class=\"line\"> Object element = elements[i];</span><br><span class=\"line\"> if (!c.contains(element))</span><br><span class=\"line\"> temp[newlen++] = element;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (newlen != len) {</span><br><span class=\"line\"> setArray(Arrays.copyOf(temp, newlen));</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n<li><p>retainAll方法,和removeAll方法类似,对应的是两个集合都包含的</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean retainAll(Collection<?> c) {</span><br><span class=\"line\"> if (c == null) throw new NullPointerException();</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> if (len != 0) {</span><br><span class=\"line\"> // temp array holds those elements we know we want to keep</span><br><span class=\"line\"> int newlen = 0;</span><br><span class=\"line\"> Object[] temp = new Object[len];</span><br><span class=\"line\"> for (int i = 0; i < len; ++i) {</span><br><span class=\"line\"> Object element = elements[i];</span><br><span class=\"line\"> if (c.contains(element))</span><br><span class=\"line\"> temp[newlen++] = element;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (newlen != len) {</span><br><span class=\"line\"> setArray(Arrays.copyOf(temp, newlen));</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>addAllAbsent方法, indexOf(e, cs, 0, added) 采用将遍历过的待添加元素数组进行替换,节省了新开辟数组的空间,这是一个用的很巧的方法。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public int addAllAbsent(Collection<? extends E> c) {</span><br><span class=\"line\"> Object[] cs = c.toArray();</span><br><span class=\"line\"> if (cs.length == 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> final ReentrantLock lock = this.lock;</span><br><span class=\"line\"> lock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Object[] elements = getArray();</span><br><span class=\"line\"> int len = elements.length;</span><br><span class=\"line\"> int added = 0;</span><br><span class=\"line\"> // uniquify and compact elements in cs</span><br><span class=\"line\"> for (int i = 0; i < cs.length; ++i) {</span><br><span class=\"line\"> Object e = cs[i];</span><br><span class=\"line\"> if (indexOf(e, elements, 0, len) < 0 &&</span><br><span class=\"line\"> indexOf(e, cs, 0, added) < 0)</span><br><span class=\"line\"> cs[added++] = e;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (added > 0) {</span><br><span class=\"line\"> Object[] newElements = Arrays.copyOf(elements, len + added);</span><br><span class=\"line\"> System.arraycopy(cs, 0, newElements, len, added);</span><br><span class=\"line\"> setArray(newElements);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return added;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> lock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li><p>clear方法,就是设置一个空数组,就不附代码了</p>\n</li>\n<li><p>addAll方法,也比较简单,转成数组之后直接合并,带索引位置的,就是跳过一部分后,在插入,最后补充剩余的</p>\n</li>\n<li><p>sort方法,也比较简单,拷贝一份快照后,对快照进行排序,然后替换引用。</p>\n</li>\n<li><p>equals 和 hashCode ,这两个方法都进行了重写,注意一下,都是使用快照进行比较,所以都是弱一致性的。</p>\n</li>\n<li><p>iterator 迭代器是创建一个COWIterator迭代器,不支持删除和修改操作,只能进行读取操作。</p>\n</li>\n<li><p>其他就不进行分析了</p>\n</li>\n</ul>\n<h1 id=\"CopyOnWriteArraySet\"><a href=\"#CopyOnWriteArraySet\" class=\"headerlink\" title=\"CopyOnWriteArraySet\"></a>CopyOnWriteArraySet</h1><p>其实没啥可分析的,内部就一个CopyOnWriteArrayList,所有方法都是调用 CopyOnWriteArrayList的方法,去重用的是addIfAbsent和addAllAbsent方法,= =感觉这个Set性能有点差。。。远不及HashSet和TreeSet。</p>\n<h1 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h1><p>平时项目中时不时还是会用到CopyOnWriteArrayList,CopyOnWriteArraySet至少我还没用到过,而且看了实现,也不太敢用。。。。<br>回归主题:</p>\n<ul>\n<li>CopyOnWrite相关读取操作都是不加锁的,拷贝一份内部数组的引用,就开始读取了,不用加锁的原因就是所有的数组实际上都不会发生改变,因为所有的修改操作都是生成一份新的数组。</li>\n<li>因为读取或者迭代器,乃至hashcode,equals方法都是基于快照进行相关操作,所以可能读到的数据并不是最新的,也就是无法保证实时的一致性(就是所谓的弱一致性),但是最终数据还是一致的。</li>\n<li>因为几乎每次修改,都会生成新的数组,如果写入比较频繁,可能产生大量垃圾,加重GC负担,所以CopyOnWrite最适合的场景就是读多写少。</li>\n<li>CopyOnWriteArrayList源码上有很多值得学习的小技巧,比如addAllAbsent方法中用替换数组中读过的位置存储待合并字符,省去了开辟新数组的开销。</li>\n</ul>\n","categories":["Java"],"tags":["Java","并发容器","CopyOnWrite"]},{"title":"ES 在聚合结果中进行过滤","url":"/2018/01/03/ES%20%E5%9C%A8%E8%81%9A%E5%90%88%E7%BB%93%E6%9E%9C%E4%B8%AD%E8%BF%9B%E8%A1%8C%E8%BF%87%E6%BB%A4/","content":"<p>ES查询中,先聚合,在聚合结果中进行过滤</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "size": 0,</span><br><span class=\"line\"> "aggs": {</span><br><span class=\"line\"> "terms": {</span><br><span class=\"line\"> "terms": {</span><br><span class=\"line\"> "field": "mustTags",</span><br><span class=\"line\"> "include": ".*总则.*",</span><br><span class=\"line\"> "size": 999</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>有include,自然就有exclude,用法一样,支持通配符匹配(正则方式)。 </p>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","aggs","过滤"]},{"title":"ElasticSearch 查询优化 之 inner_hits","url":"/2018/09/24/ElasticSearch%20%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%20%E4%B9%8B%20inner_hits/","content":"<p><strong>背景及问题</strong></p>\n<ul>\n<li><p>说一下背景</p>\n<ul>\n<li>Doc数量约20W+</li>\n<li>机器配置 4C 8G * 3 </li>\n<li>ElasticSearch 5.6.3</li>\n<li>每个Doc中的Nested字段(价格列表,不同城市价格不同)中包含约100-200个嵌套文档</li>\n</ul>\n</li>\n<li><p>查询需求:</p>\n<ul>\n<li>每个查询均需要从嵌套文档中进行查询(根据城市Id)</li>\n<li>最终只需要返回一个嵌套文档(城市Id符合需求的)</li>\n</ul>\n</li>\n<li><p>开始没有想太复杂,直接使用nested Query + inner_hits取出来文档,觉得很方便,但是后来进行测试,发现只要条件中加入了城市Id,查询就很慢(平均120+ms),而不带城市id基本在40ms+,于是开始了查询问题定位。</p>\n</li>\n</ul>\n<p><strong>问题定位</strong></p>\n<ul>\n<li>根据条件定位问题肯定是出在了城市Id查询,观察了一下mapping,估计问题是出在了nested上,对Query DSL语句进行注意尝试,发现如果不带inner_hits,只是进行城市Id查询,速度可以稳定在45ms左右,一旦加上innder_hits速度就在120ms左右,看来问题是在这里了。</li>\n<li>查了官方文档,说inner_hits是父子文档的优化版本,附带了一句nested对检索性能有较大影响,也没有其他相关资料了。</li>\n<li>论坛,Google 一下也没有这方面资料,有点没有头绪。</li>\n</ul>\n<p><strong>问题解决</strong></p>\n<ul>\n<li>本地使用Query DSL 尝试了一下将整个整个价格列表返回,不再使用\binner_hits,发现速度竟然提升了(数据返回话费了4s+,但是EsQuery仅用了50ms)那说明可以<strong>删掉inner_hits,直接返回价格列表,然后在内存中进行筛选</strong>。</li>\n<li>按照这个思路改了一下项目代码,发现速度确实有提升,之前平均响应时间在130+ ms(Query+处理),现在基本不过70ms。</li>\n</ul>\n<p><strong>继续优化</strong></p>\n<ul>\n<li>官方的那句<strong>nested对检索性能有较大影响</strong>还是得重视起来,后来进行了一个尝试,将价格列表和Doc组合起来,不再使用嵌套文档,直接进行\b平铺,这样就相当于取消了nested查询,发现速度再一次提高了,查询的平均检索时间降低到40ms+,针对城市Id又加上了_routing,速度最终稳定在20ms+。好了,在测试环境的优化最终就这样了,等节后花时间测试一下,放到线上估计速度能提高不少~</li>\n</ul>\n<p><strong>总结</strong></p>\n<ul>\n<li>其实还是最初的索引结构设计不是很合理,尤其是城市价格这块,最开始没有加入_routing,这算是一个败笔了,每次查询都需要查询城市id,还没加上,这个确实是失误了。。。</li>\n<li>nested+inner_hits对检索性能确实有很大影响(最起码我这种场景影响很大了)</li>\n<li>官方提示了,nested一定程度影响\b性能,如果可以的话,还是要注意一下,避免这种结构,现在将nested字段\u001c平铺开后虽然\bdoc数量激增,但是检索速度大幅度提升,这样代价也值了~</li>\n</ul>\n","categories":["ElasticSearch"],"tags":["ElasticSearch"]},{"title":"ElasticSearch 查询出现maxClauseCount is set to 1024","url":"/2018/08/24/ElasticSearch%20%E6%9F%A5%E8%AF%A2%E5%87%BA%E7%8E%B0maxClauseCount%20is%20set%20to%201024/","content":"<ul>\n<li><p>如果bool查询的查询条件过多会导致TooManyClauses问题:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">"caused_by":{"type":"too_many_clauses","reason":"maxClauseCount is set to 1024"}}}],</span><br><span class=\"line\">"caused_by":{"type":"query_shard_exception","reason":"failed to create query:</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>解决方式在配置文件 Elasticsearch.yml中配置:</p>\n<ul>\n<li>index.query.bool.max_clause_count: 10240</li>\n</ul>\n</li>\n<li><p>设置最大限制bool查询的条数,过多会导致性能比较慢。</p>\n</li>\n<li><p>在ElasticSearch 5之后参数有所改动,提示如下:</p>\n</li>\n<li><p>The setting index.query.bool.max_clause_count has been removed. In order to set the maximum number of boolean clauses indices.query.bool.max_clause_count should be used instead.</p>\n</li>\n</ul>\n","categories":["ElasticSearch"],"tags":["Linux","ElasticSearch"]},{"title":"ElasticSearch使用painless脚本小记","url":"/2018/08/26/ElasticSearch%E4%BD%BF%E7%94%A8painless%E8%84%9A%E6%9C%AC%E5%B0%8F%E8%AE%B0/","content":"<h2 id=\"ElasticSearch使用painless脚本小记\"><a href=\"#ElasticSearch使用painless脚本小记\" class=\"headerlink\" title=\"ElasticSearch使用painless脚本小记\"></a>ElasticSearch使用painless脚本小记</h2><ul>\n<li>前几天项目中碰到了一个麻烦的问题,统计Doc中一个List中符合筛选条件的项的值的和(简单说就是Doc是商店为主体,统计该商店的某个时间段下单记录),开始用的是二次查询,但是后来数据量大了以后,第二次查询用的IDs查询传了几千个值,超过了ES的限制,最后只好拿出脚本进行解决。</li>\n<li>先说一下Doc的结构,不相干的字段就删掉了</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "shopid": 11,</span><br><span class=\"line\"> "orderList": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2015-06-06",</span><br><span class=\"line\"> "orderAmount":2254</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2016-06-06",</span><br><span class=\"line\"> "orderAmount":7575.66</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2017-06-06",</span><br><span class=\"line\"> "orderAmount":6533.99</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2018-06-06",</span><br><span class=\"line\"> "orderAmount":7870.20</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>业务的查询需求是筛选2016-2018年,下单金额累计超过13000的商店(当然还有其他附加条件的查询),orderList还是Nested字段,最开始是进行二次查询,也就是首先将其他条件查询出来,然后对结果进行过滤,在这个日期下的符合条件的取出来ID,进行二次查询(这个条件改成Ids查询),最开始还能正常工作,后来不加上这个条件一下查出来一千条数据的时候就麻烦了,es默认Bool查询条件不能超过1024,硬着头皮改了ES配置,调成了1W,总算是正常工作了,但是不是长久之计,随着订单越来越多,迟早还会有问题,而且查询的时间越来越长了(查出来7000多条数据的时候已经超过10s了。。),跟业务部门沟通了,他们暂时可以接受,但是让我们尽快优化。</li>\n<li>网上查找了很多资料,也想使用innerHits解决,但是innerHits结果无法参与aggs,实在没有其他办法,只好决定用脚本来解决。在上家公司用的是ES2.X,脚本groovy默认是关闭的,开启还有重启ES修改配置,觉得麻烦,而且据说有安全隐患,对这个一比较抵触,当下用的是ES5.6,看了一下脚本用的是painless,这个从来没有接触过,看了一下语法,好像也不太复杂,和Java差不太多,那就硬着头皮上。</li>\n<li>painless什么时候开始支持的没太关注,不过看文档说好像已经是容器内运行,相对比较安全了,而且ES5.6默认已经开启了,尝试过程中发现Nested类型取值只能取到一个内部文档的,list其他对象找不到。冗余出来一个非nested字段,发现日期和金额都是分别排序的,这就很尴尬了。。。最后决定吧订单金额和下单时间拼接成字符串,存到一个数组中去,这样取值的时候,金额和日期就是一一对应的。</li>\n<li>修改后的Doc结构如下:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "shopid": 11,</span><br><span class=\"line\"> "orderListStr":[</span><br><span class=\"line\"> "2015-06-06@2254",</span><br><span class=\"line\"> "[email protected]",</span><br><span class=\"line\"> "[email protected]",</span><br><span class=\"line\"> "[email protected]"</span><br><span class=\"line\"> ],</span><br><span class=\"line\"> "orderList": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2015-06-06",</span><br><span class=\"line\"> "orderAmount":2254</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2016-06-06",</span><br><span class=\"line\"> "orderAmount":7575.66</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2017-06-06",</span><br><span class=\"line\"> "orderAmount":6533.99</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "orderTime":"2018-06-06",</span><br><span class=\"line\"> "orderAmount":7870.20</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<ul>\n<li>然后就是对照着文档写painless脚本了,个人水平有限,写的脚本也很低效,不过好赖问题是解决了,附上脚本:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">String fotmat = 'yyyy-MM-dd';</span><br><span class=\"line\">String field = 'orderListStr';</span><br><span class=\"line\">double saleNum = 0.0;</span><br><span class=\"line\">for (int i = 0; i < doc[field].length; ++i) {</span><br><span class=\"line\"> def docStr=doc[field][i];</span><br><span class=\"line\"> def date = docStr.substring(0,docStr.indexOf('@'));</span><br><span class=\"line\"> double value =Double.parseDouble(docStr.substring(docStr.indexOf('@')+1));</span><br><span class=\"line\"> SimpleDateFormat sdf = new SimpleDateFormat(fotmat);</span><br><span class=\"line\"> if (params.from != null && params.from != '' && params.to != null && params.to != '' ){</span><br><span class=\"line\"> if (sdf.parse(params.from).getTime()<=sdf.parse(date).getTime() && sdf.parse(params.to).getTime()>=sdf.parse(date).getTime())</span><br><span class=\"line\"> saleNum+=value;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else if (params.from != null && params.from != ''){</span><br><span class=\"line\"> if( sdf.parse(params.from).getTime()<=sdf.parse(date).getTime())</span><br><span class=\"line\"> saleNum+=value;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else if (params.to != null && params.to != ''){</span><br><span class=\"line\"> if( sdf.parse(params.to).getTime()>=sdf.parse(date).getTime())</span><br><span class=\"line\"> saleNum+=value;</span><br><span class=\"line\"> }else {</span><br><span class=\"line\"> saleNum+=value;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\">if (params.inputFrom != null && params.inputTo != null){</span><br><span class=\"line\"> return params.inputFrom<=saleNum && saleNum<=params.inputTo;</span><br><span class=\"line\">} else if(params.inputFrom !=null){</span><br><span class=\"line\"> return params.inputFrom<=saleNum;</span><br><span class=\"line\">} else if(params.inputTo != null){</span><br><span class=\"line\"> return saleNum<=params.inputTo;</span><br><span class=\"line\">}else{</span><br><span class=\"line\"> return true;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n\n<ul>\n<li>使用脚本解决了时间段下单次数统计和时间段金额统计的查询,时间也从原来的接近10s下降到1s左右(感觉脚本没办法走索引,应该都是全表扫描),这个肯定不是最优方法,但是 现在也想不出来什么其他高效的方法,就先上这个了,最起码目前查询最近5年的订单也是1秒左右,而且不用担心出现bool数量超过限制的问题了。</li>\n</ul>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","Painless"]},{"title":"ElasticSearch安装部署文档","url":"/2017/01/24/ElasticSearch%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3/","content":"<hr>\n<p><strong>说明:该部署文档系统环境为Centos7——64位</strong></p>\n<hr>\n<p>一、<strong>在线安装</strong></p>\n<p><strong>基础安装</strong></p>\n<ul>\n<li>安装JDK,wget(如果已经安装请跳过)<ul>\n<li>注:ES要求至少Java 7,官方建议使用版本为1.8.0_73</li>\n<li>sudo yum install java wget</li>\n<li>查看版本:java -version</li>\n</ul>\n</li>\n<li>下载ElasticSearch 2.4 RPM 安装包<ul>\n<li>wget <a href=\"https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/rpm/elasticsearch/2.4.0/elasticsearch-2.4.0.rpm\">https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/rpm/elasticsearch/2.4.0/elasticsearch-2.4.0.rpm</a></li>\n</ul>\n</li>\n<li>使用命令默认安装<ul>\n<li>sudo rpm -ivh elasticsearch-2.4.0.rpm</li>\n</ul>\n</li>\n<li>安装后其主要文件位于/usr/share/elasticsearch下,配置文件位于/etc/elasticsearch目录下(该目录默认无权限直接查看,需要root权限)。</li>\n<li>启动ElasticSearch<ul>\n<li>sudo service elasticsearch start 或者sudo systemctl start elasticsearch.service</li>\n<li>查看启动状态:sudo systemctl status elasticsearch</li>\n</ul>\n</li>\n<li>确认Es是否已经启动<ul>\n<li>curl localhost:9200</li>\n</ul>\n</li>\n</ul>\n<p> <strong>ik分词器安装</strong></p>\n<ul>\n<li>在/usr/share/elasticsearch/plugins/目录下创建ik目录<ul>\n<li>sudo mkdir /usr/share/elasticsearch/plugins/ik</li>\n<li>cd /usr/share/elasticsearch/plugins/ik</li>\n</ul>\n</li>\n<li>下载IK分次器<ul>\n<li><a href=\"https://github.com/medcl/elasticsearch-analysis-ik\">查看所需ik版本</a>,ES2.4.0版本对应的ik版本为1.10.0</li>\n<li>wget <a href=\"https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v1.10.0/elasticsearch-analysis-ik-1.10.0.zip\">https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v1.10.0/elasticsearch-analysis-ik-1.10.0.zip</a></li>\n</ul>\n</li>\n<li>解压所有文件放到ik目录下<ul>\n<li>unzip elasticsearch-analysis-ik-1.10.0.zip</li>\n<li>执行命令sudo mv /usr/share/elasticsearch/plugins/ik/config /etc/elasticsearch/,将ik分词器的配置文件拷贝到es的配置目录下,执行命令sudo mv /etc/elasticsearch/config /etc/elasticsearch/ik将config文件重命名为ik</li>\n</ul>\n</li>\n<li>重启ElasticSearch<ul>\n<li>sudo service elasticsearch restart</li>\n</ul>\n</li>\n</ul>\n<p> <strong>Head 插件安装</strong></p>\n<ul>\n<li>进入ElasticSearch的bin目录<ul>\n<li>cd /usr/share/elasticsearch/bin/</li>\n</ul>\n</li>\n<li>执行安装head插件命令<ul>\n<li>sudo ./plugin install mobz/elasticsearch-head</li>\n</ul>\n</li>\n<li>重启ElasticSearch<ul>\n<li>sudo service elasticsearch restart</li>\n</ul>\n</li>\n</ul>\n<p><strong>生产环境过滤Delete请求插件</strong></p>\n<ul>\n<li>见<a href=\"http://gitlab.gridsum.com/lawdissector/Monitor/issues/21\">http://gitlab.gridsum.com/lawdissector/Monitor/issues/21</a></li>\n</ul>\n<hr>\n<p><strong>二、离线安装</strong></p>\n<p><strong>基础安装</strong></p>\n<ul>\n<li>安装JDK<ul>\n<li>下载rpm包:</li>\n<li>安装:sudo rpm -ivh jdk-8u121-linux-x64.rpm</li>\n<li>查看版本:java -version,若版本不是上一步安装,则说明系统原本已经安装了不同版本的JDK,需要进行选择,操作下一步,否则不需要</li>\n<li>执行命令:alternatives –config java,选择对应版本的number,回车即可。</li>\n</ul>\n</li>\n<li>安装Elasticsearch 2.4.0<ul>\n<li>下载rpm包:<a href=\"/uploads/bdffb4acd92ee9978b473285a147ace5/elasticsearch-2.4.0.rpm\">elasticsearch-2.4.0.rpm</a></li>\n<li>安装:rpm -ivh elasticsearch-2.4.0.rpm。安装后安装包在/usr/share/elasticsearch下,配置文件位于/etc/elasticsearch目录下</li>\n</ul>\n</li>\n</ul>\n<p><strong>插件安装</strong></p>\n<ul>\n<li>ik分词器插件<ul>\n<li>插件文件:<a href=\"/uploads/e877c77a64f417f3898a46966f220709/elasticsearch-analysis-ik-1.10.0.zip\">elasticsearch-analysis-ik-1.10.0.zip</a></li>\n<li>执行命令sudo mkdir /home/ik创建ik目录,将插件拷贝到/home/ik目录下,执行命令unzip elasticsearch-analysis-ik-1.10.0.zip解压,得到config目录和jar包。</li>\n<li>执行命令sudo mv /home/ik /usr/share/elasticsearch/plugins/,将ik分词插件剪切到es的插件目录下</li>\n<li>执行命令sudo mv /usr/share/elasticsearch/plugins/ik/config /etc/elasticsearch/将ik分词器的配置文件拷贝到es的配置目录下,执行命令sudo mv /etc/elasticsearch/config /etc/elasticsearch/ik将config文件重命名为ik</li>\n</ul>\n</li>\n<li>head插件<ul>\n<li>插件文件:<a href=\"/uploads/5fb77931b24851a9b2ee1d8837a92790/head.zip\">head.zip</a></li>\n<li>将插件文件拷贝到/home目录下,执行命令sudo unzip head.zip解压</li>\n<li>执行命令sudo mv /home/head /home/ik /usr/share/elasticsearch/plugins/将head目录剪切到es的插件目录下。</li>\n</ul>\n</li>\n</ul>\n<p><strong>三、配置</strong></p>\n<p><strong>基础配置</strong></p>\n<ul>\n<li><p>执行命令sudo mkdir /data1(若/data1目录不存在),sudo mkdir /data1/es(若/data1/es目录不存在),sudo mkdir /data1/es/data建立es数据存储目录,sudo mkdir /data1/es/logs建立es日志存储目录。注:es的数据和日志目录需要保存到<strong>数据盘</strong>上,保证足够的容量。</p>\n</li>\n<li><p>执行命令sudo vim /etc/elasticsearch/elasticsearch.yml打开es的配置文件</p>\n</li>\n<li><p>配置说明:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cluster.name: ld_cluster_test #集群名</span><br><span class=\"line\">node.name: node-230 #结点名称</span><br><span class=\"line\">path.data: /data1/es/data #数据存储目录</span><br><span class=\"line\">path.logs: /data1/es/logs #日志存储目录</span><br><span class=\"line\">network.host: 10.200.x.x #本机IP</span><br><span class=\"line\">http.port: 9200 #访问端口</span><br><span class=\"line\">discovery.zen.ping.unicast.hosts: ["10.200.x.x", "10.200.x.x"] #集群中其他结点所在IP</span><br><span class=\"line\">cluster.routing.allocation.disk.watermark.low: "90%" #es所占数据盘容量的最大百分比</span><br></pre></td></tr></table></figure>\n<p><strong>执行脚本配置</strong></p>\n</li>\n<li><p>执行命令sudo vim /etc/sysconfig/elasticsearch打开es执行脚本系统配置文件</p>\n</li>\n<li><p>配置说明:</p>\n<ul>\n<li>ES_HEAP_SIZE=16g #该值设为机器最大内存的一半,查看内存命令:grep MemTotal /proc/meminfo</li>\n<li>MAX_OPEN_FILES=65535 #最大文件描述符,推荐64k</li>\n</ul>\n</li>\n</ul>\n<hr>\n<p><strong>四、启动</strong></p>\n<ul>\n<li>执行命令sudo systemctl start elasticsearch 启动es </li>\n<li>执行命令:sudo systemctl status elasticsearch 查看启动是否成功 </li>\n<li>重启命令:sudo systemctl restart elasticsearch 重启es </li>\n<li>head插件查看:<a href=\"http://10.200.x.x:9200/_plugin/head\">http://10.200.x.x:9200/_plugin/head</a> </li>\n<li>查看结点配置:<a href=\"http://10.200.x.x:9200/_nodes/stats/process?pretty\">http://10.200.x.x:9200/_nodes/stats/process?pretty</a></li>\n</ul>\n","categories":["ElasticSearch"],"tags":["Linux","ElasticSearch"]},{"title":"ElasticSearch Index 速度优化 (官方翻译)","url":"/2018/02/10/ElasticSearch%20Index%20%E9%80%9F%E5%BA%A6%E4%BC%98%E5%8C%96%20%EF%BC%88%E5%AE%98%E6%96%B9%E7%BF%BB%E8%AF%91%EF%BC%89/","content":"<h3 id=\"使用Bulk请求进行Index\"><a href=\"#使用Bulk请求进行Index\" class=\"headerlink\" title=\"使用Bulk请求进行Index\"></a>使用Bulk请求进行Index</h3><p>Bulk请求将产生比单文档index请求有更好的性能。至于Bulk请求中文档数量的大小,建议使用单一节点单一分片进行测试,先试试看100个,然后200个,然后400这样,每次进行翻倍测试,只要速度稳定了,也就是最合适的大小了。但是要注意一下,并不是速度最合适了就OK,因为每次请求总的大小要进行一下控制。并发发送的时候,ES内存压力会很大,一定要避免每次请求超过几十兆,即便是这样插入的性能更好(这个我踩过坑,我这测试超过10M,ES就不接受请求,直接拒绝了)。</p>\n<h3 id=\"使用多个节点或者多线程进行Index\"><a href=\"#使用多个节点或者多线程进行Index\" class=\"headerlink\" title=\"使用多个节点或者多线程进行Index\"></a>使用多个节点或者多线程进行Index</h3><p>一般来说一个线程,即便是使用了Bulk方式进行Index,也无法达到ES集群的瓶颈,所以为了最大限度的利用集群资源,使用多线程或者多进程的方式进行Index是一个很好的选择。这样不仅最大程度利用了集群资源,还帮助减少了fsync的成本。(这个fsync是什么 意思我暂时也没弄明白,后续补充)。<br>要注意一下TOO_MANY_REQUESTS (429) 相应(对应Java Client 则是EsRejectedExecutionException), 这说明ES集群已经跟不上你Index的速度了,使用一些适当的方式限制一下速度吧。(官方文档说暂停Index一会或者使用随机指数函数Backoff)。<br>类似Bulk Index 数量,多线程多进程Index也需要进行人工测试,直到找到一个合适线程数或者进程数。</p>\n<h3 id=\"增加refresh-interval\"><a href=\"#增加refresh-interval\" class=\"headerlink\" title=\"增加refresh interval\"></a>增加refresh interval</h3><p>默认的 index.refresh_interval 是1s,在index的时候如果没有实时性检索需求,建议可以设置大一些,比如30S,如果不需要检索,等index完成才进行检索的话,可以设置为-1,也就是禁用,等完成index之后在调整回来。</p>\n<h3 id=\"禁用refresh,降低分片副本数\"><a href=\"#禁用refresh,降低分片副本数\" class=\"headerlink\" title=\"禁用refresh,降低分片副本数\"></a>禁用refresh,降低分片副本数</h3><p>如果需要一次index大量数据,最好禁用refresh,也就是将refresh_interval设置为-1,同时index.number_of_replicas 设置为0,也就是不需要副本。尽管这样会增加一些风险(真的很小很小),也就是在索引的时候可能导致数据丢失,但是这样可以大幅度增加索引速度,等完成索引后在增加副本,这样也可以保证数据的可靠性。</p>\n<h3 id=\"禁用Swapping\"><a href=\"#禁用Swapping\" class=\"headerlink\" title=\"禁用Swapping\"></a>禁用Swapping</h3><p>一定确保操作系统禁用了swapping,这对ES性能有很大的提升。</p>\n<h3 id=\"给足够的内存文件系统缓存\"><a href=\"#给足够的内存文件系统缓存\" class=\"headerlink\" title=\"给足够的内存文件系统缓存\"></a>给足够的内存文件系统缓存</h3><p>你应该分配机器的一半内存给ES使用,用于文件系统的缓存。文件系统缓存用于缓冲I/O操作。</p>\n<h3 id=\"使用系统自动生成id\"><a href=\"#使用系统自动生成id\" class=\"headerlink\" title=\"使用系统自动生成id\"></a>使用系统自动生成id</h3><p>当你index一个document使用特定的id,ES需要去检查是否在同一个shard存在相同的ID的文档,这是一个相当昂贵的操作,并且随着文档数量的增加,花费呈指数增长。如果使用自动生成id,ES会跳过这个检查,使得Index速度更快。</p>\n<h3 id=\"使用更快的硬件\"><a href=\"#使用更快的硬件\" class=\"headerlink\" title=\"使用更快的硬件\"></a>使用更快的硬件</h3><p>如果I/O是瓶颈,那么最好考虑为文件系统提供更多内存或者购买更好的服务器。使用SSD硬盘能比一般的硬盘有更好的性能。另外尽量使用本地存储,不要考虑远程存储。也尽可能不要考虑Amazon等虚拟化存储,尽管比较简单的使用,但是性能比本地存储差很多。<br>还有要尽可能冗余副本,以避免节点故障导致数据丢失。也可以用快照备份还原进一步降低数据出事的风险。</p>\n<h3 id=\"Indexing-缓冲大小\"><a href=\"#Indexing-缓冲大小\" class=\"headerlink\" title=\"Indexing 缓冲大小\"></a>Indexing 缓冲大小</h3><p>如果节点仅仅是大量Index,确保每个分片 indices.memory.index_buffer_size 大于512M,(尽管大于512M没有什么性能改善)。举个例子,默认值是10%,也是说如果你设置的jvm大小是10G,那么Index缓冲大小是1G,足以支撑2个shard的大量索引。</p>\n<h3 id=\"禁用-field-names\"><a href=\"#禁用-field-names\" class=\"headerlink\" title=\"禁用 _field_names\"></a>禁用 _field_names</h3><p>简单来说,如果你不需要运行exists查询,那么你就可以禁用_field_names。</p>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","Index"]},{"title":"I/O 多路复用之select、poll、epoll详解","url":"/2019/06/13/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E8%AF%A6%E8%A7%A3/","content":"<hr>\n<p>select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。</p>\n<hr>\n<h2 id=\"详解\"><a href=\"#详解\" class=\"headerlink\" title=\"详解\"></a>详解</h2><h3 id=\"select\"><a href=\"#select\" class=\"headerlink\" title=\"select\"></a>select</h3><ul>\n<li>基本原理:<ul>\n<li>select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。</li>\n<li>调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。</li>\n<li>当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。</li>\n</ul>\n</li>\n<li>select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。</li>\n<li>select最大的缺陷就是单个进程所打开的FD是有一定限制的,它由FD_SETSIZE设置,默认值是1024。<ul>\n<li>一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.</li>\n</ul>\n</li>\n<li>对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。<ul>\n<li>当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。</li>\n</ul>\n</li>\n<li>需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。</li>\n<li>简述:1024</li>\n</ul>\n<h3 id=\"poll\"><a href=\"#poll\" class=\"headerlink\" title=\"poll\"></a>poll</h3><ul>\n<li>基本原理:<ul>\n<li>poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态。</li>\n<li>如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。</li>\n<li>这个过程经历了多次无谓的遍历。</li>\n</ul>\n</li>\n<li>pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。</li>\n<li>同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。</li>\n<li>和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。</li>\n<li>poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。</li>\n<li>从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。</li>\n<li>事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。</li>\n<li>简述:鸡肋</li>\n</ul>\n<h3 id=\"epoll\"><a href=\"#epoll\" class=\"headerlink\" title=\"epoll\"></a>epoll</h3><ul>\n<li>epoll是在2.6内核中提出的,是之前的select和poll的增强版本。</li>\n<li>相对于select和poll来说,epoll更加灵活,没有描述符限制。</li>\n<li>epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。</li>\n<li>没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。</li>\n<li>效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。<ul>\n<li>只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。</li>\n</ul>\n</li>\n<li>内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。</li>\n<li>简述:杀手锏</li>\n</ul>\n<h4 id=\"epoll工作模式\"><a href=\"#epoll工作模式\" class=\"headerlink\" title=\"epoll工作模式\"></a>epoll工作模式</h4><ul>\n<li>epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:<ul>\n<li>LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。</li>\n<li>ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。</li>\n</ul>\n</li>\n</ul>\n<h5 id=\"LT模式\"><a href=\"#LT模式\" class=\"headerlink\" title=\"LT模式\"></a>LT模式</h5><ul>\n<li>LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。</li>\n</ul>\n<h5 id=\"ET模式\"><a href=\"#ET模式\" class=\"headerlink\" title=\"ET模式\"></a>ET模式</h5><ul>\n<li>ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)</li>\n<li>ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。</li>\n</ul>\n<h4 id=\"epoll总结\"><a href=\"#epoll总结\" class=\"headerlink\" title=\"epoll总结\"></a>epoll总结</h4><ul>\n<li>在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而<em>epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。</em>(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)</li>\n<li>epoll的优点主要是一下几个方面:<ul>\n<li>监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对 于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。</li>\n<li>IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。</li>\n<li>如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"select、poll、epoll区别\"><a href=\"#select、poll、epoll区别\" class=\"headerlink\" title=\"select、poll、epoll区别\"></a>select、poll、epoll区别</h2><p>1、支持一个进程所能打开的最大连接数<br><img src=\"/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E8%AF%A6%E8%A7%A3/1.png\" alt=\"最大连接数\"><br>2、FD剧增后带来的IO效率问题<br><img src=\"/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E8%AF%A6%E8%A7%A3/2.png\" alt=\"IO效率问题\"><br>3、消息传递方式<br><img src=\"/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E8%AF%A6%E8%A7%A3/3.png\" alt=\"消息传递方式\"></p>\n","categories":["IO"],"tags":["IO","select","poll","epoll"]},{"title":"Java NIO学习笔记","url":"/2019/06/02/Java%20NIO%E5%AD%A6%E4%B9%A0/","content":"<ul>\n<li>内容基本来源:<ul>\n<li><a href=\"http://www.iocoder.cn/\">http://www.iocoder.cn/</a></li>\n<li><a href=\"http://ifeve.com/\">http://ifeve.com</a></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"核心组件\"><a href=\"#核心组件\" class=\"headerlink\" title=\"核心组件\"></a>核心组件</h2><h3 id=\"Channel\"><a href=\"#Channel\" class=\"headerlink\" title=\"Channel\"></a>Channel</h3><ul>\n<li>Nio Channel类似于Java Stream,但又有几点不同<ul>\n<li>Channel是双向的,Stream是单向的</li>\n<li>Channel可以非阻塞的进行读写操作,而Stream需要等待io操作完成,也就是阻塞的。</li>\n<li>Channel的读操作或者写操作都是依赖Buffer的,Stream没有依赖</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"ServerSocketChannel\"><a href=\"#ServerSocketChannel\" class=\"headerlink\" title=\"ServerSocketChannel\"></a>ServerSocketChannel</h4><ul>\n<li>Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。</li>\n<li>ServerSocketChannel类在 java.nio.channels包中。</li>\n</ul>\n<h4 id=\"SocketChannel\"><a href=\"#SocketChannel\" class=\"headerlink\" title=\"SocketChannel\"></a>SocketChannel</h4><ul>\n<li>Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。</li>\n<li>可以通过以下2种方式创建SocketChannel:<ul>\n<li>打开一个SocketChannel并连接到互联网上的某台服务器。</li>\n<li>一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。</li>\n</ul>\n</li>\n<li>非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。</li>\n</ul>\n<h4 id=\"DatagramChannel\"><a href=\"#DatagramChannel\" class=\"headerlink\" title=\"DatagramChannel\"></a>DatagramChannel</h4><ul>\n<li>Java NIO中的DatagramChannel是一个能收发UDP包的通道。</li>\n<li>因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。</li>\n<li>它发送和接收的是数据包。</li>\n</ul>\n<h4 id=\"FileChannel\"><a href=\"#FileChannel\" class=\"headerlink\" title=\"FileChannel\"></a>FileChannel</h4><ul>\n<li>Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。</li>\n<li>FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。</li>\n</ul>\n<h3 id=\"Buffer\"><a href=\"#Buffer\" class=\"headerlink\" title=\"Buffer\"></a>Buffer</h3><ul>\n<li>一个 Buffer,本质上是内存中的一块,我们可以将数据写入这块内存,之后从这块内存获取数据。通过将这块内存封装成 NIO Buffer 对象,并提供了一组常用的方法,方便我们对该块内存的读写。</li>\n<li>基本属性<ul>\n<li>capacity<ul>\n<li>属性,容量,Buffer 能容纳的数据元素的最大值。这一容量在 Buffer 创建时被赋值,并且永远不能被修改。</li>\n</ul>\n</li>\n<li>limit<ul>\n<li>属性,上限。</li>\n<li>写模式下,代表最大能写入的数据上限位置,这个时候 limit 等于 capacity 。</li>\n<li>读模式下,在 Buffer 完成所有数据写入后,通过调用 #flip() 方法,切换到读模式。此时,limit 等于 Buffer 中实际的数据大小。因为 Buffer 不一定被写满,所以不能使用 capacity 作为实际的数据大小。</li>\n</ul>\n</li>\n<li>position<ul>\n<li>position 属性,位置,初始值为 0 。</li>\n<li>写模式下,每往 Buffer 中写入一个值,position 就自动加 1 ,代表下一次的写入位置。</li>\n<li>读模式下,每从 Buffer 中读取一个值,position 就自动加 1 ,代表下一次的读取位置。( 和写模式类似 )</li>\n</ul>\n</li>\n<li>mark <ul>\n<li>属性,标记,通过 #mark() 方法,记录当前 position ;通过 reset() 方法,恢复 position 为标记。</li>\n<li>写模式下,标记上一次写位置。</li>\n<li>读模式下,标记上一次读位置。</li>\n</ul>\n</li>\n<li>关系<ul>\n<li>mark <= position <= limit <= capacity</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>创建Buffer<ul>\n<li>每个 Buffer 实现类,都提供了 #allocate(int capacity) 静态方法,帮助我们快速实例化一个 Buffer 对象。<ul>\n<li>ByteBuffer 实际是个抽象类,返回的是它的基于堆内( Non-Direct )内存的实现类 HeapByteBuffer 的对象。</li>\n</ul>\n</li>\n<li>每个 Buffer 实现类,都提供了 #wrap(array) 静态方法,帮助我们将其对应的数组包装成一个 Buffer 对象。<ul>\n<li>和 #allocate(int capacity) 静态方法一样,返回的也是 HeapByteBuffer 的对象。</li>\n</ul>\n</li>\n<li>每个 Buffer 实现类,都提供了 #allocateDirect(int capacity) 静态方法,帮助我们快速实例化一个 Buffer 对象。<ul>\n<li>和 #allocate(int capacity) 静态方法不一样,返回的是它的基于堆外( Direct )内存的实现类 DirectByteBuffer 的对象。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>向 Buffer 写入数据<ul>\n<li>每个 Buffer 实现类,都提供了 #put(…) 方法,向 Buffer 写入数据。</li>\n<li>对于 Buffer 来说,有一个非常重要的操作就是,我们要讲来自 Channel 的数据写入到 Buffer 中。</li>\n<li>在系统层面上,这个操作我们称为读操作,因为数据是从外部( 文件或者网络等 )读取到内存中。</li>\n<li>通常在说 NIO 的读操作的时候,我们说的是从 Channel 中读数据到 Buffer 中,对应的是对 Buffer 的写入操作</li>\n</ul>\n</li>\n<li>从 Buffer 读取数据<ul>\n<li>每个 Buffer 实现类,都提供了 #get(…) 方法,从 Buffer 读取数据。</li>\n<li>对于 Buffer 来说,还有一个非常重要的操作就是,我们要讲来向 Channel 的写入 Buffer 中的数据。</li>\n<li>在系统层面上,这个操作我们称为写操作,因为数据是从内存中写入到外部( 文件或者网络等 )。</li>\n</ul>\n</li>\n<li>rewind() flip() clear()<ul>\n<li>flip <ul>\n<li>如果要读取 Buffer 中的数据,需要切换模式,从写模式切换到读模式。</li>\n</ul>\n</li>\n<li>rewind<ul>\n<li>可以重置 position 的值为 0 。因此,我们可以重新读取和写入 Buffer 了。</li>\n<li>该方法主要针对于读模式,所以可以翻译为“倒带”。</li>\n</ul>\n</li>\n<li>clear<ul>\n<li>可以“重置” Buffer 的数据。因此,我们可以重新读取和写入 Buffer 了。</li>\n<li>该方法主要针对于写模式。</li>\n<li>Buffer 的数据实际并未清理掉</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>mark() 搭配 reset()<ul>\n<li>mark<ul>\n<li>保存当前的 position 到 mark 中。</li>\n</ul>\n</li>\n<li>reset<ul>\n<li>恢复当前的 postion 为 mark 。</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"关于-Direct-Buffer-和-Non-Direct-Buffer-的区别\"><a href=\"#关于-Direct-Buffer-和-Non-Direct-Buffer-的区别\" class=\"headerlink\" title=\"关于 Direct Buffer 和 Non-Direct Buffer 的区别\"></a>关于 Direct Buffer 和 Non-Direct Buffer 的区别</h4><ul>\n<li>Direct Buffer:<ul>\n<li>所分配的内存不在 JVM 堆上, 不受 GC 的管理.(但是 Direct Buffer 的 Java 对象是由 GC 管理的, 因此当发生 GC, 对象被回收时, Direct Buffer 也会被释放)</li>\n<li>因为 Direct Buffer 不在 JVM 堆上分配, 因此 Direct Buffer 对应用程序的内存占用的影响就不那么明显(实际上还是占用了这么多内存, 但是 JVM 不好统计到非 JVM 管理的内存.)</li>\n<li>申请和释放 Direct Buffer 的开销比较大. 因此正确的使用 Direct Buffer 的方式是在初始化时申请一个 Buffer, 然后不断复用此 buffer, 在程序结束后才释放此 buffer.</li>\n<li>使用 Direct Buffer 时, 当进行一些底层的系统 IO 操作时, 效率会比较高, 因为此时 JVM 不需要拷贝 buffer 中的内存到中间临时缓冲区中.</li>\n</ul>\n</li>\n<li>Non-Direct Buffer:<ul>\n<li>直接在 JVM 堆上进行内存的分配, 本质上是 byte[] 数组的封装.</li>\n<li>因为 Non-Direct Buffer 在 JVM 堆中, 因此当进行操作系统底层 IO 操作中时, 会将此 buffer 的内存复制到中间临时缓冲区中. 因此 Non-Direct Buffer 的效率就较低.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Selector\"><a href=\"#Selector\" class=\"headerlink\" title=\"Selector\"></a>Selector</h3><ul>\n<li>Selector , 一般称为选择器。它是 Java NIO 核心组件中的一个,用于轮询一个或多个 NIO Channel 的状态是否处于可读、可写。如此,一个线程就可以管理多个 Channel ,也就说可以管理多个网络连接。也因此,Selector 也被称为多路复用器。</li>\n<li>那么 Selector 是如何轮询的呢?<ul>\n<li>首先,需要将 Channel 注册到 Selector 中,这样 Selector 才知道哪些 Channel 是它需要管理的。</li>\n<li>之后,Selector 会不断地轮询注册在其上的 Channel 。如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。</li>\n</ul>\n</li>\n<li>优缺点<ul>\n<li>优点<ul>\n<li>使用一个线程能够处理多个 Channel 的优点是,只需要更少的线程来处理 Channel 。</li>\n<li>事实上,可以使用一个线程处理所有的 Channel 。</li>\n<li>对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源( 例如 CPU、内存 )。因此,使用的线程越少越好。</li>\n</ul>\n</li>\n<li>缺点<ul>\n<li>因为在一个线程中使用了多个 Channel ,因此会造成每个 Channel 处理效率的降低。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>创建 Selector<ul>\n<li>通过 #open() 方法,我们可以创建一个 Selector 对象。代码如下:</li>\n</ul>\n</li>\n<li>注册 Chanel 到 Selector 中<ul>\n<li>为了让 Selector 能够管理 Channel ,我们需要将 Channel 注册到 Selector 中。</li>\n<li>如果一个 Channel 要注册到 Selector 中,那么该 Channel 必须是非阻塞。</li>\n<li>FileChannel 是不能够注册到 Channel 中的,因为它是阻塞的。</li>\n<li>监听四种不同类型的事件:<ul>\n<li>Connect :连接完成事件( TCP 连接 ),仅适用于客户端,对应 SelectionKey.OP_CONNECT 。</li>\n<li>Accept :接受新连接事件,仅适用于服务端,对应 SelectionKey.OP_ACCEPT 。</li>\n<li>Read :读事件,适用于两端,对应 SelectionKey.OP_READ ,表示 Buffer 可读。</li>\n<li>Write :写时间,适用于两端,对应 SelectionKey.OP_WRITE ,表示 Buffer 可写。</li>\n</ul>\n</li>\n<li>Channel 触发了一个事件,意思是该事件已经就绪:<ul>\n<li>一个 Client Channel Channel 成功连接到另一个服务器,称为“连接就绪”。</li>\n<li>一个 Server Socket Channel 准备好接收新进入的连接,称为“接收就绪”。</li>\n<li>一个有数据可读的 Channel ,可以说是“读就绪”。</li>\n<li>一个等待写数据的 Channel ,可以说是“写就绪”。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>SelectionKey 类<ul>\n<li>调用 Channel 的 #register(…) 方法,向 Selector 注册一个 Channel 后,会返回一个 SelectionKey 对象。</li>\n<li>SelectionKey 在 java.nio.channels 包下,被定义成一个抽象类,表示一个 Channel 和一个 Selector 的注册关系。</li>\n<li>注册关系,包含如下内容:<ul>\n<li>interest set: 感兴趣的事件集合。</li>\n<li>ready set :就绪的事件集合。</li>\n<li>Channel</li>\n<li>Selector</li>\n<li>attachment :可选的附加对象。可以向 SelectionKey 添加附加对象。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>通过 Selector 选择 Channel<ul>\n<li>在 Selector 中,提供三种类型的选择( select )方法,返回当前有感兴趣事件准备就绪的 Channel 数量。<ul>\n<li>select() 阻塞到至少有一个 Channel 在你注册的事件上就绪了。</li>\n<li>select(long timeout) 在 <code>#select()</code> 方法的基础上,增加超时机制。</li>\n<li>selectNow() 和 <code>#select()</code> 方法不同,立即返回数量,而不阻塞。</li>\n</ul>\n</li>\n<li>select 方法返回的 int 值,表示有多少 Channel 已经就绪。也就是自上次调用 select 方法后有多少 Channel 变成就绪状态。</li>\n</ul>\n</li>\n<li>获取可操作的 Channel<ul>\n<li>一旦调用了 select 方法,并且返回值表明有一个或更多个 Channel 就绪了,然后可以通过调用Selector 的 #selectedKeys() 方法,访问“已选择键集( selected key set )”中的就绪 Channel 。</li>\n<li>注意,当有新增就绪的 Channel ,需要先调用 select 方法,才会添加到“已选择键集( selected key set )”中。否则,我们直接调用 #selectedKeys() 方法,是无法获得它们对应的 SelectionKey 们。</li>\n</ul>\n</li>\n<li>唤醒 Selector 选择<ul>\n<li>某个线程调用 #select() 方法后,发生阻塞了,即使没有通道已经就绪,也有办法让其从 #select() 方法返回。</li>\n<li>只要让其它线程在第一个线程调用 select() 方法的那个 Selector 对象上,调用该 Selector 的 #wakeup() 方法,进行唤醒该 Selector 即可。</li>\n<li>注意,如果有其它线程调用了 #wakeup() 方法,但当前没有线程阻塞在 #select() 方法上,下个调用 #select() 方法的线程会立即被唤醒。</li>\n</ul>\n</li>\n<li>关闭 Selector<ul>\n<li>当我们不再使用 Selector 时,可以调用 Selector 的 #close() 方法,将它进行关闭。<ul>\n<li>Selector 相关的所有 SelectionKey 都会失效。</li>\n<li>Selector 相关的所有 Channel 并不会关闭。</li>\n</ul>\n</li>\n<li>此时若有线程阻塞在 #select() 方法上,也会被唤醒返回。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"NIO与BIO相比\"><a href=\"#NIO与BIO相比\" class=\"headerlink\" title=\"NIO与BIO相比\"></a>NIO与BIO相比</h2><h3 id=\"NIO\"><a href=\"#NIO\" class=\"headerlink\" title=\"NIO\"></a>NIO</h3><ul>\n<li>基于缓冲区<ul>\n<li>基于Buffer读取,将数据从Channel中读取到Buffer中,或者从buffer中将数据写回到channel中。因为数据已经读取到缓冲区当中,所以操作不需要顺序执行,增加其灵活性。</li>\n</ul>\n</li>\n<li>非阻塞IO<ul>\n<li>一个线程从channel中执行io操作的时候,无论是读取还是写入,都无需等待完成,都会直接返回,不会阻塞当前正在执行的线程。</li>\n</ul>\n</li>\n<li>有选择器<ul>\n<li>一个线程可以通过一个Selector管理多个Channel,选择器是实现非阻塞io的核心。</li>\n<li>Selector内部自动为我们实现了轮训select操作,判断channel是否有已经就绪的io事件(连接,读,写等)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"BIO\"><a href=\"#BIO\" class=\"headerlink\" title=\"BIO\"></a>BIO</h3><ul>\n<li>基于流(Stream)<ul>\n<li>以流式方式进行处理,顺序的从一个stream中读取一个或者多个字节,直到读取完成。由于没有缓存区,不能随意更改读取指针的位置。</li>\n</ul>\n</li>\n<li>阻塞IO<ul>\n<li>一个线程操作io的时候,该线程会被阻塞,直到数据被读取或者写入完成。</li>\n</ul>\n</li>\n</ul>\n","categories":["Java"],"tags":["Java","NIO"]},{"title":"Hadoop 实现 TF-IDF 计算","url":"/2018/01/14/Hadoop%20%E5%AE%9E%E7%8E%B0%20TF-IDF%20%E8%AE%A1%E7%AE%97/","content":"<p>学习Hadoop 实现TF-IDF 算法,使用的是CDH5.13.1 VM版本,Hadoop用的是2.6.0的jar包,Maven中增加如下即可</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><dependency></span><br><span class=\"line\"> <groupId>org.apache.hadoop</groupId></span><br><span class=\"line\"> <artifactId>hadoop-client</artifactId></span><br><span class=\"line\"> <version>2.6.0</version></span><br><span class=\"line\"> <scope>provided</scope></span><br><span class=\"line\"></dependency></span><br></pre></td></tr></table></figure>\n\n<p>代码如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">package top.eviltuzki.tfidf;</span><br><span class=\"line\"> </span><br><span class=\"line\">import org.apache.hadoop.conf.Configured;</span><br><span class=\"line\">import org.apache.hadoop.fs.FileSystem;</span><br><span class=\"line\">import org.apache.hadoop.fs.Path;</span><br><span class=\"line\">import org.apache.hadoop.io.*;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.Job;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.Mapper;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.Reducer;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;</span><br><span class=\"line\">import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;</span><br><span class=\"line\">import org.apache.hadoop.util.GenericOptionsParser;</span><br><span class=\"line\">import org.apache.hadoop.util.Tool;</span><br><span class=\"line\">import org.apache.hadoop.util.ToolRunner;</span><br><span class=\"line\"> </span><br><span class=\"line\">import java.io.*;</span><br><span class=\"line\">import java.util.*;</span><br><span class=\"line\"> </span><br><span class=\"line\">public class App extends Configured implements Tool {</span><br><span class=\"line\"> </span><br><span class=\"line\"> public int run(String[] strings) throws Exception {</span><br><span class=\"line\"> //第一个MR,计算IDF</span><br><span class=\"line\"> Job job = Job.getInstance(getConf());</span><br><span class=\"line\"> job.setJarByClass(App.class);</span><br><span class=\"line\"> job.setInputFormatClass(TextInputFormat.class);</span><br><span class=\"line\"> job.setOutputFormatClass(TextOutputFormat.class);</span><br><span class=\"line\"> </span><br><span class=\"line\"> job.setMapOutputKeyClass(Text.class);</span><br><span class=\"line\"> job.setMapOutputValueClass(IntWritable.class);</span><br><span class=\"line\"> </span><br><span class=\"line\"> </span><br><span class=\"line\"> job.setReducerClass(IdfReducer.class);</span><br><span class=\"line\"> job.setMapperClass(IdfMap.class);</span><br><span class=\"line\"> job.setNumReduceTasks(1);</span><br><span class=\"line\"> </span><br><span class=\"line\"> String[] args = new GenericOptionsParser(getConf(), strings).getRemainingArgs();</span><br><span class=\"line\"> FileInputFormat.setInputPaths(job,new Path(args[0]));</span><br><span class=\"line\"> FileOutputFormat.setOutputPath(job,new Path(args[1]));</span><br><span class=\"line\"> </span><br><span class=\"line\"> job.waitForCompletion(true);</span><br><span class=\"line\"> </span><br><span class=\"line\"> //第二个Map,计算TF以及TF-IDF</span><br><span class=\"line\"> Job job2 = Job.getInstance(getConf());</span><br><span class=\"line\"> job2.setJarByClass(App.class);</span><br><span class=\"line\"> job2.setInputFormatClass(TextInputFormat.class);</span><br><span class=\"line\"> job2.setOutputFormatClass(TextOutputFormat.class);</span><br><span class=\"line\"> </span><br><span class=\"line\"> job2.setMapOutputKeyClass(Text.class);</span><br><span class=\"line\"> job2.setMapOutputValueClass(DoubleWritable.class);</span><br><span class=\"line\"> </span><br><span class=\"line\"> </span><br><span class=\"line\"> </span><br><span class=\"line\"> job2.setMapperClass(TfMap.class);</span><br><span class=\"line\"> job2.setNumReduceTasks(0);</span><br><span class=\"line\"> </span><br><span class=\"line\"> args = new GenericOptionsParser(getConf(), strings).getRemainingArgs();</span><br><span class=\"line\"> FileInputFormat.setInputPaths(job2,new Path(args[0]));</span><br><span class=\"line\"> FileOutputFormat.setOutputPath(job2,new Path(args[2]));</span><br><span class=\"line\"> </span><br><span class=\"line\"> job2.waitForCompletion(true);</span><br><span class=\"line\"> </span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> </span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> public static class IdfMap extends Mapper<LongWritable,Text,Text,IntWritable>{</span><br><span class=\"line\"> </span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 比较简单,就是进行WordCount的操作</span><br><span class=\"line\"> * @param key</span><br><span class=\"line\"> * @param value</span><br><span class=\"line\"> * @param context</span><br><span class=\"line\"> * @throws IOException</span><br><span class=\"line\"> * @throws InterruptedException</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {</span><br><span class=\"line\"> String[] words = value.toString().split(" ");</span><br><span class=\"line\"> Set<String> set = new HashSet<String>();</span><br><span class=\"line\"> for (String word : words) {</span><br><span class=\"line\"> if (word.length() > 1)</span><br><span class=\"line\"> set.add(word);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> for (String s : set) {</span><br><span class=\"line\"> context.write(new Text(s),new IntWritable(1));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> public static class IdfReducer extends Reducer<Text,IntWritable,Text,FloatWritable>{</span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {</span><br><span class=\"line\"> int sum =0 ;</span><br><span class=\"line\"> for (IntWritable value : values) {</span><br><span class=\"line\"> sum+=value.get();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> float x =(float) ( 500.0 / sum+1);//比较笨了。。直接写死。。我这有500个样本,没想出来怎么读取数量。。。</span><br><span class=\"line\"> float log = (float) Math.log(x);</span><br><span class=\"line\"> context.write(key,new FloatWritable(log));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> public static class TfMap extends Mapper<LongWritable,Text,Text,DoubleWritable>{</span><br><span class=\"line\"> private Map<String,Double> map = new HashMap<>();</span><br><span class=\"line\"> </span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> protected void setup(Context context) throws IOException, InterruptedException {</span><br><span class=\"line\"> FileSystem fs = FileSystem.get(context.getConfiguration());</span><br><span class=\"line\"> // 读取文件列表,不知道怎么加载路径了。。。就直接写死了。。。</span><br><span class=\"line\"> Path filePath =new Path("/user/zj/tfidf/jaroutput/part-r-00000");</span><br><span class=\"line\"> try (InputStream stream = fs.open(filePath)) {</span><br><span class=\"line\"> try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {</span><br><span class=\"line\"> String line = "";</span><br><span class=\"line\"> while ((line=reader.readLine())!=null){</span><br><span class=\"line\"> String[] split = line.split("\\t");</span><br><span class=\"line\"> if(split.length == 2)</span><br><span class=\"line\"> map.put(split[0],Double.parseDouble(split[1]));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> }</span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {</span><br><span class=\"line\"> String[] wordArray = value.toString().split(" ");</span><br><span class=\"line\"> List<String> words = new ArrayList<>();</span><br><span class=\"line\"> for (String s : wordArray) {</span><br><span class=\"line\"> if (s.length()>1)</span><br><span class=\"line\"> words.add(s);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> Map<String,Integer> res = new HashMap<>();</span><br><span class=\"line\"> for (String word : words) {</span><br><span class=\"line\"> if(res.containsKey(word))</span><br><span class=\"line\"> res.put(word,res.get(word)+1);//计算本文的词频</span><br><span class=\"line\"> else</span><br><span class=\"line\"> res.put(word,1);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> for (Map.Entry<String, Integer> entry : res.entrySet()) {</span><br><span class=\"line\"> String key1 = entry.getKey();</span><br><span class=\"line\"> Integer value1 = entry.getValue();</span><br><span class=\"line\"> double tf = value1*1.0 /words.size();//这里计算的就是tf,tf = 本文中这个词的次数/总词数</span><br><span class=\"line\"> context.write(new Text(key1),new DoubleWritable(tf * map.get(key1)));//tf直接和idf相乘</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> public static void main(String[] args) throws Exception {</span><br><span class=\"line\"> Configured conf = new Configured();</span><br><span class=\"line\"> ToolRunner.run(new App(),args);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>整体实现算是比较简单,第一个MR计算idf,map是统计每个文档出现过的词,都记成1次,然后reducer统计所有的,这样就得到了每一个词的idf了</p>\n<p>公式是idf=log(总文章数/(词出现的文章数+1))</p>\n<p>第二个Map就计算tf ,既然得到了TF就直接将Tf-IDF一起计算了。。可是读idf的结果。。没找到好办法,直接写死了。。计算idf的总文章数也是笨的直接写死了 = =!</p>\n<p>以后学会了怎么处理再改吧。。</p>\n<p>读取文件的样本比较简单,经过简单处理,一行是一个新闻,进行了简单的分词处理,标点符号还有单个汉字之类的就直接过滤掉了(程序中有)</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">隐瞒 外星人 存在 受 质疑 ? 或 掌握 地外文明 搜狐 据 国外 媒体 报道 , 美国 国家 航空 航天局 ( ) 在 公众 眼中 是 一个 神秘 存在 , 该 机构 负责 太空 计划 并 拥有 最 先进 的 航空 航天 技术 , 不过 随着 太空 探索 的 不断 加深 , 公众 对 它 的 疑虑 也 日 益 俱 深 , 并 认为 他们 早已 与 外星人 有 了 联系 但 并 未 公开 。 公众 质疑 隐瞒 了 外星人 消息 世界 上 有 一些 机构 或者 组织 , 因 其 承担 的 工作 之 重要 , 以及 需要 的 专业 知识 之 精深 , 在 公众 眼中 总是 蒙 着 一 层 神秘 的 面纱 。 而 它们 对 公众 的 世界观 影响力 之 强 也 超乎 想象 。 美国 国家 航空 航天局 ( 简称 ) 就是 其中 一个 典型 的 例子 。 是 美国 负责 太空 计划 的 政府 机构 , 总部 位于 华盛顿 哥伦比亚特区 , 拥有 最 先进 的 航空 航天 技术 , 参与 了 美国 阿波罗 计划 、 航天飞机 发射 、 太阳系 探测 等 航天 工程 , 为 人类 探索 太空 做出 了 巨大 贡献 。 然而 伴随 着 太空 探索 深度 的 扩大 , 公众 对 它 的 疑虑 也 与 日 俱 深 。 公众 质疑 : 他们 早已 与 外星人 取得 了 联系 , 但 一直 没有 公开 美国 气象学家 斯考特 ・ 斯蒂文斯 日前 指责 美国 国家 航空 航天局 向 公众 隐瞒 了 许多 由 太阳 轨道 望远镜 传回 地球 的 资料 , 其中 包括 有 可能 是 “ 外星 生命 ” 的 信息 。 这 成为 不曾 间断 的 对 质疑 的 又 一个 新鲜 声音 。 太阳 轨道 望远镜 是 一 项 由 美国 国家 航空 航天局 和 欧洲 空间 局 实施 的 联合 研究 计划 。 目前 , 其 所 处 位置 距离 地球 约 万 公里 , 主要 对 太阳 的 爆发 情况 、 太阳 表面 喷 出 物质 和 太阳 附近 的 彗星 进行 拍照 。 到 现在 为止 , 该 望远镜 拍摄 的 照片 数量 已 高达 数 万 张 。 其中 一些 据称 出现 了 “ 不明飞行物 ” 的 身影 。 但 要么 对 其 避而不谈 , 要么 就 解释 说是 数字 图像 在 传回 地球 的 过程 中 出现 了 差错 , 才 导致 照片 中 出现 奇特 的 物体 。 但 斯考特 ・ 斯蒂文斯 指出 , 通过 分析 望远镜 传回 地球 的 所有 照片 资料 , 他 发现 在 不同 年份 拍摄 的 照片 上 都会 出现 完全 相同 的 不明 物体 。 斯考特 ・ 斯蒂文斯 由此 断言 , 如果 这些 不明 物体 仅仅 是 偶然 的 干扰 因素 ( 如 宇宙 尘埃 或 残留 的 太阳 粒子 等 ) 造成 的 , 那么 它们 的 形状 和 尺寸 就 不 可能 总是 完全 一样 。 他 表示 这 有 可能 表明 在 太阳 的 周围 经常 有 不明飞行物 光顾 , 并且 不只 一 艘 , 而是 一个 完整 的 编队 。 “ 外星人 ” 正在 以 太阳 为 客体 开展 试验 , 并 试图 影响 太阳 的 活动 情况 。 其实 , 的 使命 和 愿景 当中 就 蕴含 了 “ 寻找 地 外 生命 ” 和 “ 星际 移民 ” 的 内容 。 而 长期 以来 , 都 有 科学家 和 普通 公众 质疑 对 公众 隐瞒 了 有关 地外文明 的 真相 , 更 有 人 指出 , 早已 与 “ 外星人 ” 取得 了 联系 , 但 出于 种种 原因 一直 没有 公开 。 年 发生 在 美国 的 阿诺德 空中 遭遇 飞碟 案 和 罗斯威尔 飞碟 坠毁 案 以及 此后 由 美国 军方 领导 的 专门 调查 现象 的 “ 蓝皮书 计划 ” 、 “ 号志 计划 ” 、 “ 新墨西哥州 怀特 沙漠 试验场 计划 ” 等 研究 活动 , 开创 了 现代 研究 的 新纪元 。 多年 来 全球 各地 收到 的 目击 案例 已 超过 万 起 , 其中 有数 十万 起 是 无法 用 自然 和 物理 现象 做出 合理 解释 的 , 人们 怀疑 它 是 一 种 超越 人类 文明 的 外星 智慧 生命 所为 。 美国 国家 射电 天文台 和 国家 航空 航天局 从 年 开始 进行 微波 监听 宇宙 文明 的 “ 奥兹 玛 计划 ” 和 “ 赛克 洛普 计划 ” 。 年月 和 年月 , 美国 先后 发射 了 先驱者 号 、 号 [| 宇宙 飞船 |] , 携带 地球人 给 外星人 的 一 封 自荐信 , 当中 镌刻 着 地球 和 太阳系 在 银河系 的 位置 , 地球人 男人 和 女人 的 形象 , 以及 表示 宇宙 间 最 丰富 的 物质 氢 的 分子 结构图 , 寄望 外星人 截获 此 信 。 到 深 空 去 探测 “ 地外文明 ” 的 存在 ? 时至 今日 , 已经 发展 成为 雇员 人数 约 万 的 大型 机构 , 年度 经费 超过 亿 美元 。 在 其 推进 的 众多 高 精 尖 项目 中 , “ 深 空 网络 ” 是 近期 的 一 大 热点 。 那么 , “ 深 空 网络 ” 到底 为 何物 ? 深 空 网络 ( , ) 是 一个 支持 星际 任务 、 无线电 通信 以及 利用 射电天文学 观察 探测 太阳系 和 宇宙 的 国际 天线 网络 , 它 是 地球 上 最大 也 是 最 敏感 的 科学 研究 用途 的 通信 系统 。 目前 深 空 网络 由 三 处 呈 度 分布 的 深 空 通信 设施 构成 , 一 处在 美国 加州 的 戈尔德斯通 , 处于 巴斯托 市 附近 的 莫哈维沙漠 之 中 ; 一 处 位于 西班牙 马德里 附近 ; 另 一 处 位于 澳大利亚 的 堪培拉 附近 。 这种 安排 使得 可以 连续 观察 地球 的 自转 的 过程 。 深 空 探测 的 一个 重要 用途 , 便 是 探索 地外文明 的 存在 。 分享</span><br><span class=\"line\">库克 : 最大压力 的 搜狐 事件 : 上周 三 , 苹果 股价 单 日 大跌 创 近 个 月 最低 , 按 当天 收盘价 计算 , 距离 月 创下 的 历史 高点 跌 去 了 逾 , 被 认为 已经 踏 上 了 熊 途 。 点评 : 我 想 库克 最近 一定 心情 不好 , 压力 很 大 。 因为 近 一 段 时间 来 , 糟糕 的 事情 一 件 接着 一 件 降临 到 苹果 头上 。 令 人 失望 的 开启 了 苹果 的 厄运 , 地图 门 、 高 管 下课 、 销售 不及 预期 , 重重 利空 叠加 之 下 , 郭台铭 的 一 句 “ 太 难 生产 了 , 我们 难以 满足 巨大 的 需求 ” , 就 成为 压垮 苹果 股价 的 最后 一 根 稻草 。 昔日 的 光环 已经 褪 尽 , 苹果 的 竞争力 和 创新力 正 遭受 前所未有 的 质疑 。 面临 掌舵 苹果 以来 的 最大 挑战 , 库克 能否 化 危 为 机 , 扭转 颓势 , 尚 不得而知 。 分享</span><br><span class=\"line\">单身贵族 也 有 品 热门 [| 数码 相机 |] 大 盘点 搜狐 数码 佳能 作为 升级 机型 , 在 诸多 细节 方面 做 了 改变 , 大幅 提升 了 对 焦 及 连 拍 性能 , 为 普及型 [| 数码 单反 相机 |] 定义 到 了 新 高度 。 新型 图像 感应器 有效像素 为 万 , 与 能 高效 处理 图像 并 控制 相机 功能 的 数字 影像 处理器 组合 , 实现 了 高画质 、 高感光度 低 噪 点 。 目前 , 佳能 报价 参数 图片 论坛 热门 软件 单机 的 最新 报价 为 元 。 重庆 掀 开 触控 新 篇章 佳能 仅 ▲ 佳能 从 外观 上 看 , 佳能 与 尺寸 重量 相差 不大 , 整体 外形尺寸 为 × × , 重 约 仅 机身 。 除了 按键 布局 基本 上 没有 变化 外 , 最大 的 改进 在于 它 采用 英寸 万 像素 可 [| 旋转 触摸屏 |] , 这 也 是 佳能 首次 将 触摸屏 设计 在 单反 上 , 配合 带来 高速 合 焦 , 可 实现 触摸 对 焦 、 触摸 快门 、 触摸 回放 功能 。 重庆 掀 开 触控 新 篇章 佳能 仅 ▲ 佳能 从 性能 上 看 , 佳能 搭载 规格 传感器 , 有效像素 达 万 。 配合 最新 型 处理器 , 实现 了 高画质 、 高感光度 低 噪 点 它 的 快门速度 为 秒 , 连 拍 速度 达 每 秒 张 , 感光度 范围 , 扩展 可 达 。 其 采用 中央 八 向 双十字 全 点 十字型 自动 对 焦 , 使 所有 自动 对 焦 区域 都 能 高精度 合 焦 。 追踪 动态 被 摄 体 持续 对 焦 的 人工智能 伺服 自动 对 焦 算法 得到 进化 , 性能 大幅 提高 。 重庆 掀 开 触控 新 篇章 佳能 仅 ▲ 佳能 编辑 点评 : 佳能 还 在 内置 相片 处理 方面 提供 给 用户 最大 的 选择 余地 , 不但 提供 了 拍摄 模式 , 还 增加 了 快速 连 拍 张 照片 合成 的 手持 夜景 模式 , 另外 相机 又 内置 了 一 系列 特效 滤镜 , 配合 屏 的 轻 触 设计 , 用户 可 更 方便 加入 不同 效果 , 输出 独 具 风格 的 照片 。 参考价格 元 我 也 要 打分 : 喜欢 一般 很 差 更 多 条 论坛 热贴 条 人 关注 分 上 一 页 下 一 页 文本 导航 第 页 尼康 第 页 佳能 套机 第 页 宾得 套机 第 页 徕卡 第 页 卡西欧 第 页 佳能 第 页 索尼 分享</span><br><span class=\"line\">索尼 美 上市 三防 设计 美元 起 搜狐 【 搜狐 数码 消息 】 月 日 消息 , 在 登录 欧洲 市场 个 月 之后 , 索尼 日前 已经 正式 于 美国 开 售 。 这 款 [| 三防 手机 |] 共有 黄 、 白 、 黑 三 种 颜色 选择 , 售价 也 因 机身 颜色 而 不同 , 分别 是 美元 、 美元 、 美元 ( 无 锁 机 ) 。 配置 方面 , 配备 了 英寸 ( ) 抗 划伤 无机 玻璃 显示屏 , 即使 屏幕 或 手指 沾 有 液体 , 仍 可 准确 跟踪 。 另外 , 机身 内部 采用 了 双核 芯片 , 内存 , 存储 空间 ( 支持 卡 拓展 ) , 万 [| 像素 摄像头 |] , [| 毫安 电池 |] 。 ( ) 分享</span><br><span class=\"line\">最新 研究 称 近 距 双子星 是 星云 奇特 喷射 流 来源 搜狐 【 搜狐 科学 消息 】 据 美国 太空 网站 报道 , 目前 , 科学家 最新 研究 表示 , 行星状星云 中 一对 彼此 环绕 的 恒星 作为 “ 宇宙 发电站 ” , 为 该 星云 壮观 喷射 流 提供 后方 能量 。 这 项 发现 揭晓 了 科学家 长期 置疑 “ 弗莱明 号 ” 行星状星云 的 喷射 流 形状 之 谜 , 这些 喷射 流 呈现 为 奇特 的 节 状 和 弯曲 结构 , 最新 研究 显示 这种 奇特 喷射 流 是 由 “ 发电站 ” 双子星 的 轨道 交互 作用 提供 动力 。 研究 报告 作者 、 智利 天文学家 亨利博芬 说 : “ 这 是 一 项 大型 天文 观测 研究 项目 , 用于 理解 奇特 、 非 对称 形状 的 行星状星云 。 ” 据 科学家 称 , 的 行星状星云 具有 不 平衡 结构 。 事实上 行星状星云 与 行星 没有 关系 , 它们 是 垂死 白矮星 生命 末期 阶段 。 博芬 研究 小组 使用 甚 大 望远镜 观测 “ 弗莱明 号 ” 行星状星云 , 据称 , 这 个 行星状星云 是 以 天文学家 威廉米娜 弗莱明 命名 , 她 于 年 在 哈佛大学 天文台 观测 发现 这 个 行星状星云 。 数 十年 以来 , 天文学家 一直 对 环绕 该 星云 周围 的 奇特 气体 迷惑不解 , 博芬 和 他 的 研究 同事 在 计算机 模拟 系统 上 结合 最新 观测 数据 , 证实 了 一对 白矮星 充当 着 “ 宇宙 发电站 ” 。 多数 双子星 轨道 周期 为数 百年 或者 数 千年 , 但是 “ 弗莱明 号 ” 行星状星云 的 光谱 数据 显示 其中 的 双子 恒星 运行 速度 更 快 , 快速 变化 的 光谱线 表明 这 对 恒星 轨道 周期 仅 为 天 。 博芬 说 : “ 这 是 一对 非常 接近 的 双子星 系统 。 ” 他 还 指出 , 其它 双子星 系统 中 也 发现 类似 的 轨道 周期 。 这 个 行星状星云 中 的 恒星 曾 共享 环绕 星云 的 气体 层 , 在 其它 双子星 系统 中 也 非常 普遍 。 然而 , 环绕 星云 的 气体 喷射 流 并 不是 当前 存在 。 这 项 研究 报告 现 发表 在 月 日 出版 的 《 科学 》 杂志 上 。 起初 , “ 弗莱明 号 ” 行星状星云 中 这 两 颗 恒星 相距甚远 , 较 大 的 恒星 逐渐 从 一 颗 红巨星 演变 为 体积 庞大 的 渐近 巨 支 星 , 此时 它 将 形成 数 百倍 太阳 直径 。 这 颗 超大 质量 恒星 释放 的 气体 流 之后 流向 体积 较 小 的 邻近 恒星 ― ― 一 颗 寒冷 的 白矮星 , 此时 气体 流 像 从 水龙头 中 释放 出来 一样 , 从 这 两 颗 恒星 向 外 喷射 。 博芬 称 , 这 只是 恒星 生命 历程 中 非常 短暂 的 一个 时期 , 仅 持续 年 。 随着 时间 的 流逝 , 巨大 的 恒星 失去 了 它们 的 气体 , 变成 了 一 颗 白矮星 。 气体 将 恒星 包裹 起来 , 推动 它们 逐渐 靠拢 在 一起 。 当 恒星 逐渐 接近 , 包裹 它们 的 气体 将 被 驱逐 , 该 气体 喷射 流 的 “ 水龙头 ” 也 将 关闭 。 博芬 研究 小组 认为 , 这种 现象 普遍 存在 于 行星状星云 中 的 双子星 系统 , 但 同时 强调 需要 更 多 的 观测 来 证实 这 一 理论 。 ( 卡麦拉 ) 分享</span><br></pre></td></tr></table></figure>\n\n<p>Maven 打包之后运行</p>\n<p>命令如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">hadoop jar learn-1.0-SNAPSHOT.jar top.eviltuzki.tfidf.App /user/zj/tfidf/allcontent /user/zj/tfidf/jaroutput /user/zj/tfidf/finaloutput</span><br></pre></td></tr></table></figure>\n\n<p>输出结果就简单看一下吧 = =</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">[cloudera@quickstart tfidf]$ hadoop fs -cat /user/zj/tfidf/finaloutput/part-m-00000 |grep -v E| sort -k2 -rn |head -n 20</span><br><span class=\"line\">宋体 2.2809544503092782</span><br><span class=\"line\">宋体 1.3156868388809182</span><br><span class=\"line\">东北虎 0.7394429830508474</span><br><span class=\"line\">网盘 0.571717673015873</span><br><span class=\"line\">网盘 0.571717673015873</span><br><span class=\"line\">电视广告 0.5665861818181818</span><br><span class=\"line\">出货量 0.5347087089347079</span><br><span class=\"line\">黑色星期五 0.5342098285714285</span><br><span class=\"line\">雪豹 0.5053336216216217</span><br><span class=\"line\">红茶 0.4936592475247525</span><br><span class=\"line\">携程 0.47571826851851845</span><br><span class=\"line\">野生 0.47535620338983053</span><br><span class=\"line\">音乐 0.4412029517241379</span><br><span class=\"line\">角膜 0.43890478873239436</span><br><span class=\"line\">美食 0.4262510461538462</span><br><span class=\"line\">摇椅 0.42329096944444444</span><br><span class=\"line\">摇椅 0.42329096944444444</span><br><span class=\"line\">美景 0.4227268664556961</span><br><span class=\"line\">酷派 0.42054887452054796</span><br><span class=\"line\">朝鲜 0.4166495891891892</span><br></pre></td></tr></table></figure>\n\n<p>也不知道为啥会出现重复的,个人想法应该是多篇文章中统计出来的吧。。。但是分值都一样- -有点。。。额。。也许是重复的新闻也说不好。。。</p>\n","categories":["Hadoop"],"tags":["Hadoop","TF-IDF"]},{"title":"Java线程池分析-AbstractQueuedSynchronizer","url":"/2019/07/14/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-AbstractQueuedSynchronizer/","content":"<p>AQS分析第一篇,整理得快吐血了,不过话说回来,看一遍和整理了一遍真的完全不一样,收获还是很多的。<br>这里基本把AQS整个类分析了一遍,剩下还有就是条件对象以及AQS应用了,后续有时间在整理了。</p>\n<hr>\n<h1 id=\"AbstractOwnableSynchronizer\"><a href=\"#AbstractOwnableSynchronizer\" class=\"headerlink\" title=\"AbstractOwnableSynchronizer\"></a>AbstractOwnableSynchronizer</h1><ul>\n<li>比较简单,内部一个exclusiveOwnerThread,附带get和set方法,不多说</li>\n</ul>\n<h1 id=\"Node\"><a href=\"#Node\" class=\"headerlink\" title=\"Node\"></a>Node</h1><ul>\n<li>AbstractQueuedSynchronizer 核心依赖,内部队列就是由Node组成</li>\n</ul>\n<h2 id=\"核心Field\"><a href=\"#核心Field\" class=\"headerlink\" title=\"核心Field\"></a>核心Field</h2><h3 id=\"状态类\"><a href=\"#状态类\" class=\"headerlink\" title=\"状态类\"></a>状态类</h3><ul>\n<li>int CANCELLED = 1;<ul>\n<li>代表被取消了</li>\n</ul>\n</li>\n<li>int SIGNAL = -1;<ul>\n<li>代表需要被唤醒</li>\n</ul>\n</li>\n<li>int CONDITION = -2;<ul>\n<li>代表在条件队列中等待</li>\n</ul>\n</li>\n<li>int PROPAGATE = -3;<ul>\n<li>代表释放资源的时候需要通知其他Node</li>\n</ul>\n</li>\n<li>int waitStatus<ul>\n<li>代表当前Node的等待状态,取值为CANCELLED、SIGNAL、CONDITION、PROPAGATE,默认初始化为0</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"记录阻塞模式\"><a href=\"#记录阻塞模式\" class=\"headerlink\" title=\"记录阻塞模式\"></a>记录阻塞模式</h3><ul>\n<li>Node SHARED<ul>\n<li>代表该Node是因为获取共享资源被阻塞而放入AQS</li>\n</ul>\n</li>\n<li>Node EXCLUSIVE<ul>\n<li>代表该Node是因为获取独占资源被阻塞而放入AQS</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"链表相关\"><a href=\"#链表相关\" class=\"headerlink\" title=\"链表相关\"></a>链表相关</h3><ul>\n<li>提供前驱和后继节点的访问方法,也就是说链表是双向的</li>\n<li>Node prev<ul>\n<li>记录当前节点的前驱节点</li>\n</ul>\n</li>\n<li>Node next<ul>\n<li>记录当前节点的后继节点</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"其他\"><a href=\"#其他\" class=\"headerlink\" title=\"其他\"></a>其他</h3><ul>\n<li>Thread thread<ul>\n<li>thread用于存放进入AQS队列的里面的线程</li>\n</ul>\n</li>\n<li>Node nextWaiter<ul>\n<li>在Node作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;</li>\n<li>在Node作为等待队列节点使用时,nextWaiter保存后继节点。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"核心方法\"><a href=\"#核心方法\" class=\"headerlink\" title=\"核心方法\"></a>核心方法</h2><h3 id=\"boolean-isShared\"><a href=\"#boolean-isShared\" class=\"headerlink\" title=\"boolean isShared()\"></a>boolean isShared()</h3><ul>\n<li>当前节点获取资源采用的是否为共享方式 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean isShared() {</span><br><span class=\"line\"> return nextWaiter == SHARED;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Node-predecessor\"><a href=\"#Node-predecessor\" class=\"headerlink\" title=\"Node predecessor()\"></a>Node predecessor()</h3><ul>\n<li>获取前置节点,如果前置节点为null,则抛出NPE异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final Node predecessor() throws NullPointerException {</span><br><span class=\"line\"> Node p = prev;</span><br><span class=\"line\"> if (p == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> else</span><br><span class=\"line\"> return p;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"AbstractQueuedSynchronizer\"><a href=\"#AbstractQueuedSynchronizer\" class=\"headerlink\" title=\"AbstractQueuedSynchronizer\"></a>AbstractQueuedSynchronizer</h1><h2 id=\"核心Field-1\"><a href=\"#核心Field-1\" class=\"headerlink\" title=\"核心Field\"></a>核心Field</h2><ul>\n<li>Node head<ul>\n<li>内部队列的头结点</li>\n</ul>\n</li>\n<li>Node tail<ul>\n<li>内部队列的尾节点</li>\n</ul>\n</li>\n<li>int state<ul>\n<li>状态位,在不同的子类中有不同的含义</li>\n</ul>\n</li>\n<li>long spinForTimeoutThreshold<ul>\n<li>自旋时间,低于这个时间则直接进行空循环,然后重新尝试获取资源</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"核心方法-1\"><a href=\"#核心方法-1\" class=\"headerlink\" title=\"核心方法\"></a>核心方法</h2><h3 id=\"int-getState\"><a href=\"#int-getState\" class=\"headerlink\" title=\"int getState()\"></a>int getState()</h3><ul>\n<li>用的很多,但是没啥可说的</li>\n</ul>\n<h3 id=\"void-setState-int-newState\"><a href=\"#void-setState-int-newState\" class=\"headerlink\" title=\"void setState(int newState)\"></a>void setState(int newState)</h3><ul>\n<li>用的很多,但是没啥可说的</li>\n</ul>\n<h3 id=\"boolean-compareAndSetState-int-expect-int-update\"><a href=\"#boolean-compareAndSetState-int-expect-int-update\" class=\"headerlink\" title=\"boolean compareAndSetState(int expect, int update)\"></a>boolean compareAndSetState(int expect, int update)</h3><ul>\n<li>Cas更新状态操作,也没啥可说的</li>\n</ul>\n<h3 id=\"Node-enq-final-Node-node\"><a href=\"#Node-enq-final-Node-node\" class=\"headerlink\" title=\"Node enq(final Node node)\"></a>Node enq(final Node node)</h3><ul>\n<li>入队操作,如果队列没有节点,则tail为null,这个时候需要加入一个哨兵节点<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private Node enq(final Node node) {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> Node t = tail;</span><br><span class=\"line\"> if (t == null) { // Must initialize</span><br><span class=\"line\"> if (compareAndSetHead(new Node()))//加入一个哨兵节点到队列尾部,再次循环</span><br><span class=\"line\"> tail = head;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> node.prev = t;</span><br><span class=\"line\"> if (compareAndSetTail(t, node)) {</span><br><span class=\"line\"> t.next = node;</span><br><span class=\"line\"> return t;//返回原末尾节点</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Node-addWaiter-Node-mode\"><a href=\"#Node-addWaiter-Node-mode\" class=\"headerlink\" title=\"Node addWaiter(Node mode)\"></a>Node addWaiter(Node mode)</h3><ul>\n<li>这个作者实现比较有趣,先用快速方式尝试添加节点,成功则返回新添加的节点,失败则通过enq以循环的方式将node入队<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private Node addWaiter(Node mode) {</span><br><span class=\"line\"> //根据当前线程以及模式(共享或者独占)创建一个节点</span><br><span class=\"line\"> Node node = new Node(Thread.currentThread(), mode);</span><br><span class=\"line\"> //尝试直接添加到队列尾部(所谓的快速添加)</span><br><span class=\"line\"> Node pred = tail;</span><br><span class=\"line\"> if (pred != null) {</span><br><span class=\"line\"> node.prev = pred;</span><br><span class=\"line\"> //CAS添加成功则返回结果,失败则只需enq</span><br><span class=\"line\"> if (compareAndSetTail(pred, node)) {</span><br><span class=\"line\"> pred.next = node;</span><br><span class=\"line\"> return node;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //说明快速添加遇到竞争,通过enq进行入队操作</span><br><span class=\"line\"> enq(node);</span><br><span class=\"line\"> return node;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-setHead-Node-node\"><a href=\"#void-setHead-Node-node\" class=\"headerlink\" title=\"void setHead(Node node)\"></a>void setHead(Node node)</h3><ul>\n<li>设置头结点,注意一下node的thread和prev会设置为null</li>\n</ul>\n<h3 id=\"void-unparkSuccessor-Node-node\"><a href=\"#void-unparkSuccessor-Node-node\" class=\"headerlink\" title=\"void unparkSuccessor(Node node)\"></a>void unparkSuccessor(Node node)</h3><ul>\n<li>该方法用于唤醒等待队列中的下一个线程,下一个线程并不一定是当前节点的next节点,需要根据其状态来进行查找,找到之后执行LockSupport.unpark唤醒对应的线程。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void unparkSuccessor(Node node) {</span><br><span class=\"line\"> int ws = node.waitStatus;</span><br><span class=\"line\"> if (ws < 0)</span><br><span class=\"line\"> compareAndSetWaitStatus(node, ws, 0);</span><br><span class=\"line\"> Node s = node.next;</span><br><span class=\"line\"> if (s == null || s.waitStatus > 0) {</span><br><span class=\"line\"> s = null;</span><br><span class=\"line\"> for (Node t = tail; t != null && t != node; t = t.prev)</span><br><span class=\"line\"> if (t.waitStatus <= 0)</span><br><span class=\"line\"> s = t;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (s != null)</span><br><span class=\"line\"> LockSupport.unpark(s.thread);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-doReleaseShared\"><a href=\"#void-doReleaseShared\" class=\"headerlink\" title=\"void doReleaseShared()\"></a>void doReleaseShared()</h3><ul>\n<li>共享模式的释放操作,一般来说,只需要判断两种情况:<ul>\n<li>SIGNAL代表后继节点之前被阻塞了需要释放</li>\n<li>PROPAGATE代表共享模式下可以继续进行acquire<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void doReleaseShared() {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> Node h = head;</span><br><span class=\"line\"> //这里的判断是处理头结点和尾结点都存在的情况,并且队列里节点总数大于1</span><br><span class=\"line\"> if (h != null && h != tail) {</span><br><span class=\"line\"> int ws = h.waitStatus;</span><br><span class=\"line\"> //Node.SIGNAL表示后继节点需要被唤醒</span><br><span class=\"line\"> if (ws == Node.SIGNAL) {</span><br><span class=\"line\"> //h从SIGNAL设置为0</span><br><span class=\"line\"> if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> //执行唤醒操作,这里会将h.waitStatus设置为0,补充,每次只唤醒一个线程</span><br><span class=\"line\"> unparkSuccessor(h);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去,也就是h从0设置为PROPAGATE,</span><br><span class=\"line\"> else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //头节点没有发生变化,可以退出循环,如果头结点发生了变化,为了使自己的唤醒动作可以传递,必须进行重试</span><br><span class=\"line\"> if (h == head)</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n<h3 id=\"void-setHeadAndPropagate-Node-node-int-propagate\"><a href=\"#void-setHeadAndPropagate-Node-node-int-propagate\" class=\"headerlink\" title=\"void setHeadAndPropagate(Node node, int propagate)\"></a>void setHeadAndPropagate(Node node, int propagate)</h3><ul>\n<li>首先执行setHead方法,在这之后检查条件,如果满足条件则唤醒后继节点(因为是共享模式,所以后继节点也一并唤醒)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void setHeadAndPropagate(Node node, int propagate) {</span><br><span class=\"line\"> Node h = head; // Record old head for check below</span><br><span class=\"line\"> setHead(node);</span><br><span class=\"line\"> //检查条件</span><br><span class=\"line\"> // propagate > 0 表示调用方指明了后继节点需要被唤醒</span><br><span class=\"line\"> // 头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点(看第一行和第二行代码)</span><br><span class=\"line\"> if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {</span><br><span class=\"line\"> Node s = node.next;</span><br><span class=\"line\"> if (s == null || s.isShared())</span><br><span class=\"line\"> doReleaseShared();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-cancelAcquire-Node-node\"><a href=\"#void-cancelAcquire-Node-node\" class=\"headerlink\" title=\"void cancelAcquire(Node node)\"></a>void cancelAcquire(Node node)</h3><ul>\n<li>取消正在获取资源的操作<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void cancelAcquire(Node node) {</span><br><span class=\"line\"> if (node == null)</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> //首先当前node不在关联任何线程</span><br><span class=\"line\"> node.thread = null;</span><br><span class=\"line\"> Node pred = node.prev;</span><br><span class=\"line\"> //CANCELLED的值为1,该判断也就是跳过已经取消的节点</span><br><span class=\"line\"> while (pred.waitStatus > 0)</span><br><span class=\"line\"> node.prev = pred = pred.prev;</span><br><span class=\"line\"> //这里指找到一个有效的前置节点</span><br><span class=\"line\"> Node predNext = pred.next;</span><br><span class=\"line\"> //将节点node设置为CANCELLED状态</span><br><span class=\"line\"> node.waitStatus = Node.CANCELLED;</span><br><span class=\"line\"> //判断node是否为tail节点,如果是tail节点,则cas进行替换,替换为找到的有效前置节点pred</span><br><span class=\"line\"> if (node == tail && compareAndSetTail(node, pred)) {</span><br><span class=\"line\"> 执行成则pred的下一个节点为null(已经是tail节点)</span><br><span class=\"line\"> compareAndSetNext(pred, predNext, null);</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> //执行到这里说明node不是tail节点,或者cas操作失败了</span><br><span class=\"line\"> int ws;</span><br><span class=\"line\"> // pred如果不是head节点,并且thread不为空,并且满足下面条件之一</span><br><span class=\"line\"> // 1. pred.waitStatus为SIGNAL</span><br><span class=\"line\"> // 2. pred.waitStatus <= 0 (SIGNAL,CONDITION,PROPAGATE,0),并成功将pred的WaitStatus进行cas替换为SIGNAL</span><br><span class=\"line\"> if (pred != head && ( (ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)) ) && pred.thread != null) {</span><br><span class=\"line\"> Node next = node.next;</span><br><span class=\"line\"> //将前置节点的next指向当前节点的next(说白了就是删除链表中的当前节点,只不过是在cas中进行操作)</span><br><span class=\"line\"> if (next != null && next.waitStatus <= 0)</span><br><span class=\"line\"> compareAndSetNext(pred, predNext, next);</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> //不满足条件,也就是说node为head的后继节点,直接进行唤醒</span><br><span class=\"line\"> unparkSuccessor(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> // 这个就是清除引用,快速gc用的</span><br><span class=\"line\"> node.next = node; </span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-shouldParkAfterFailedAcquire-Node-pred-Node-node\"><a href=\"#boolean-shouldParkAfterFailedAcquire-Node-pred-Node-node\" class=\"headerlink\" title=\"boolean shouldParkAfterFailedAcquire(Node pred, Node node)\"></a>boolean shouldParkAfterFailedAcquire(Node pred, Node node)</h3><ul>\n<li>根据前置节点判断当前节点是否应该被阻塞,同时清理掉CANCELLED节点<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {</span><br><span class=\"line\"> int ws = pred.waitStatus;</span><br><span class=\"line\"> //如果继的节点状态为SIGNAL,则当前节点需要unpark,返回true</span><br><span class=\"line\"> if (ws == Node.SIGNAL)</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> //否则返回false,并进行如下操作</span><br><span class=\"line\"> //ws > 0说明前置节点已经被取消(CANCELLED = 1), 这时需要继续往前找,直到找到 waitStatus 不为 CANCELLED ,然后返回false。所谓清理CANCELLED节点就是在这里跳过对应的节点。</span><br><span class=\"line\"> if (ws > 0) {</span><br><span class=\"line\"> do {</span><br><span class=\"line\"> node.prev = pred = pred.prev;</span><br><span class=\"line\"> } while (pred.waitStatus > 0);</span><br><span class=\"line\"> pred.next = node;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> //如果节点状态不是CANCELLED,则cas更新waitStatus为SIGNAL</span><br><span class=\"line\"> compareAndSetWaitStatus(pred, ws, Node.SIGNAL);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-selfInterrupt\"><a href=\"#void-selfInterrupt\" class=\"headerlink\" title=\"void selfInterrupt()\"></a>void selfInterrupt()</h3><ul>\n<li>这个方法比较简单,就是调用当前线程的中断方法<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">static void selfInterrupt() {</span><br><span class=\"line\"> Thread.currentThread().interrupt();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-parkAndCheckInterrupt\"><a href=\"#boolean-parkAndCheckInterrupt\" class=\"headerlink\" title=\"boolean parkAndCheckInterrupt()\"></a>boolean parkAndCheckInterrupt()</h3><ul>\n<li>阻塞当前线程并执行中断检查(会清除中断标识)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private final boolean parkAndCheckInterrupt() {</span><br><span class=\"line\"> LockSupport.park(this);</span><br><span class=\"line\"> return Thread.interrupted();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-acquireQueued-final-Node-node-int-arg\"><a href=\"#boolean-acquireQueued-final-Node-node-int-arg\" class=\"headerlink\" title=\"boolean acquireQueued(final Node node, int arg)\"></a>boolean acquireQueued(final Node node, int arg)</h3><ul>\n<li>尝试获取锁,成功返回中断状态,失败则则阻塞。阻塞过程中被中断,会返回被中断过标识<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean acquireQueued(final Node node, int arg) {</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> boolean interrupted = false;//默认非中断</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> //获取当前节点的前置节点</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> //如果前置节点为head节点,则尝试获取资源</span><br><span class=\"line\"> //每次只允许当构造节点的前驱节点是头结点才去获取同步状态</span><br><span class=\"line\"> if (p == head && tryAcquire(arg)) { //只有一个线程可以通过</span><br><span class=\"line\"> setHead(node);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return interrupted;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //否则根据是否可以进行park操作进行阻塞</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())</span><br><span class=\"line\"> interrupted = true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> //如果没有更新failed标志为,则发生异常,取消node节点</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-doAcquireInterruptibly-int-arg\"><a href=\"#void-doAcquireInterruptibly-int-arg\" class=\"headerlink\" title=\"void doAcquireInterruptibly(int arg)\"></a>void doAcquireInterruptibly(int arg)</h3><ul>\n<li>获取资源操作,如果阻塞过程中被中断,则会抛出异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void doAcquireInterruptibly(int arg) throws InterruptedException {</span><br><span class=\"line\"> //添加一个独占资源到队列末尾</span><br><span class=\"line\"> final Node node = addWaiter(Node.EXCLUSIVE);</span><br><span class=\"line\"> //以下代码基本同acquireQueued</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> if (p == head && tryAcquire(arg)) {</span><br><span class=\"line\"> setHead(node);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())</span><br><span class=\"line\"> throw new InterruptedException();//这里直接抛出异常</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-doAcquireNanos-int-arg-long-nanosTimeout\"><a href=\"#boolean-doAcquireNanos-int-arg-long-nanosTimeout\" class=\"headerlink\" title=\"boolean doAcquireNanos(int arg, long nanosTimeout)\"></a>boolean doAcquireNanos(int arg, long nanosTimeout)</h3><ul>\n<li>带有超时的去获取独占资源,如果被中断,会抛出异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {</span><br><span class=\"line\"> if (nanosTimeout <= 0L)//时间判断</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> final long deadline = System.nanoTime() + nanosTimeout;//结束时间</span><br><span class=\"line\"> final Node node = addWaiter(Node.EXCLUSIVE);//添加独占资源node到队列</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> //获取资源</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> if (p == head && tryAcquire(arg)) {</span><br><span class=\"line\"> setHead(node);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> nanosTimeout = deadline - System.nanoTime();//当前还可以等待时间</span><br><span class=\"line\"> if (nanosTimeout <= 0L)//已经超时</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)//阻塞nanosTimeout</span><br><span class=\"line\"> LockSupport.parkNanos(this, nanosTimeout);</span><br><span class=\"line\"> if (Thread.interrupted())//线程被中断,则抛出异常</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)//没有成功则取消节点</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-doAcquireShared-int-arg\"><a href=\"#void-doAcquireShared-int-arg\" class=\"headerlink\" title=\"void doAcquireShared(int arg)\"></a>void doAcquireShared(int arg)</h3><ul>\n<li>以共享的方式获取资源,失败则阻塞<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void doAcquireShared(int arg) {</span><br><span class=\"line\"> final Node node = addWaiter(Node.SHARED);//添加一个共享节点到队列尾部</span><br><span class=\"line\"> boolean failed = true;//失败标志位</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> boolean interrupted = false;//中断标志位</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> if (p == head) {//必须是头节点才可以</span><br><span class=\"line\"> int r = tryAcquireShared(arg);//获取资源</span><br><span class=\"line\"> //r等于0表示不用唤醒后继节点,大于0需要</span><br><span class=\"line\"> if (r >= 0) {</span><br><span class=\"line\"> //尝试唤醒后继节点</span><br><span class=\"line\"> setHeadAndPropagate(node, r);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> //没有中断,则返回</span><br><span class=\"line\"> if (interrupted)</span><br><span class=\"line\"> selfInterrupt();</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //获取失败,则进行阻塞,并将前驱节点的状态改成SIGNAL</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())</span><br><span class=\"line\"> interrupted = true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-doAcquireSharedInterruptibly-int-arg\"><a href=\"#void-doAcquireSharedInterruptibly-int-arg\" class=\"headerlink\" title=\"void doAcquireSharedInterruptibly(int arg)\"></a>void doAcquireSharedInterruptibly(int arg)</h3><ul>\n<li>基本同doAcquireShared,被中断则抛出异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void doAcquireSharedInterruptibly(int arg)</span><br><span class=\"line\"> throws InterruptedException {</span><br><span class=\"line\"> final Node node = addWaiter(Node.SHARED);</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> if (p == head) {</span><br><span class=\"line\"> int r = tryAcquireShared(arg);</span><br><span class=\"line\"> if (r >= 0) {</span><br><span class=\"line\"> setHeadAndPropagate(node, r);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())</span><br><span class=\"line\"> throw new InterruptedException();//这里抛出异常</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-doAcquireSharedNanos-int-arg-long-nanosTimeout\"><a href=\"#boolean-doAcquireSharedNanos-int-arg-long-nanosTimeout\" class=\"headerlink\" title=\"boolean doAcquireSharedNanos(int arg, long nanosTimeout)\"></a>boolean doAcquireSharedNanos(int arg, long nanosTimeout)</h3><ul>\n<li>和上面的没啥区别,就是多了超时控制而已,被中断也是抛出异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {</span><br><span class=\"line\"> if (nanosTimeout <= 0L)</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> final long deadline = System.nanoTime() + nanosTimeout;</span><br><span class=\"line\"> final Node node = addWaiter(Node.SHARED);</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> final Node p = node.predecessor();</span><br><span class=\"line\"> if (p == head) {</span><br><span class=\"line\"> int r = tryAcquireShared(arg);</span><br><span class=\"line\"> if (r >= 0) {</span><br><span class=\"line\"> setHeadAndPropagate(node, r);</span><br><span class=\"line\"> p.next = null; // help GC</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> nanosTimeout = deadline - System.nanoTime();</span><br><span class=\"line\"> if (nanosTimeout <= 0L)</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)</span><br><span class=\"line\"> LockSupport.parkNanos(this, nanosTimeout);</span><br><span class=\"line\"> if (Thread.interrupted())</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> cancelAcquire(node);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-tryAcquire-int-arg\"><a href=\"#boolean-tryAcquire-int-arg\" class=\"headerlink\" title=\"boolean tryAcquire(int arg)\"></a>boolean tryAcquire(int arg)</h3><ul>\n<li>AQS没有提供具体实现,需要子类实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean tryAcquire(int arg) {</span><br><span class=\"line\"> throw new UnsupportedOperationException();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-tryRelease-int-arg\"><a href=\"#boolean-tryRelease-int-arg\" class=\"headerlink\" title=\"boolean tryRelease(int arg)\"></a>boolean tryRelease(int arg)</h3><ul>\n<li>AQS没有提供具体实现,需要子类实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean tryRelease(int arg) {</span><br><span class=\"line\"> throw new UnsupportedOperationException();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"int-tryAcquireShared-int-arg\"><a href=\"#int-tryAcquireShared-int-arg\" class=\"headerlink\" title=\"int tryAcquireShared(int arg)\"></a>int tryAcquireShared(int arg)</h3><ul>\n<li>AQS没有提供具体实现,需要子类实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected int tryAcquireShared(int arg) {</span><br><span class=\"line\"> throw new UnsupportedOperationException();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-tryReleaseShared-int-arg\"><a href=\"#boolean-tryReleaseShared-int-arg\" class=\"headerlink\" title=\"boolean tryReleaseShared(int arg)\"></a>boolean tryReleaseShared(int arg)</h3><ul>\n<li>AQS没有提供具体实现,需要子类实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean tryReleaseShared(int arg) {</span><br><span class=\"line\"> throw new UnsupportedOperationException();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-isHeldExclusively\"><a href=\"#boolean-isHeldExclusively\" class=\"headerlink\" title=\"boolean isHeldExclusively()\"></a>boolean isHeldExclusively()</h3><ul>\n<li>AQS没有提供具体实现,需要子类实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean isHeldExclusively() {</span><br><span class=\"line\"> throw new UnsupportedOperationException();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-acquire-int-arg\"><a href=\"#void-acquire-int-arg\" class=\"headerlink\" title=\"void acquire(int arg)\"></a>void acquire(int arg)</h3><ul>\n<li>以独占的方式去获取资源,忽略中断。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final void acquire(int arg) {</span><br><span class=\"line\"> //至少执行一次tryAcquire,成功则返回,失败则进行线程阻塞状态,等待唤醒重新获取资源</span><br><span class=\"line\"> if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class=\"line\"> selfInterrupt();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-acquireInterruptibly-int-arg\"><a href=\"#void-acquireInterruptibly-int-arg\" class=\"headerlink\" title=\"void acquireInterruptibly(int arg)\"></a>void acquireInterruptibly(int arg)</h3><ul>\n<li>以独占的方式去获取资源,等待期间会被中断。如果线程本身已经被中断,调用该方法会立即抛出异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final void acquireInterruptibly(int arg) throws InterruptedException {</span><br><span class=\"line\"> if (Thread.interrupted())</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> if (!tryAcquire(arg))</span><br><span class=\"line\"> doAcquireInterruptibly(arg);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-tryAcquireNanos-int-arg-long-nanosTimeout\"><a href=\"#boolean-tryAcquireNanos-int-arg-long-nanosTimeout\" class=\"headerlink\" title=\"boolean tryAcquireNanos(int arg, long nanosTimeout)\"></a>boolean tryAcquireNanos(int arg, long nanosTimeout)</h3><ul>\n<li>带有超时的获取独占资源,也会抛出中断异常<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {</span><br><span class=\"line\"> if (Thread.interrupted())</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> return tryAcquire(arg) ||</span><br><span class=\"line\"> doAcquireNanos(arg, nanosTimeout);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-release-int-arg\"><a href=\"#boolean-release-int-arg\" class=\"headerlink\" title=\"boolean release(int arg)\"></a>boolean release(int arg)</h3><ul>\n<li>资源释放<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean release(int arg) {</span><br><span class=\"line\"> //保证原子方式释放资源,同一时刻只能有一个线程成功</span><br><span class=\"line\"> if (tryRelease(arg)) {</span><br><span class=\"line\"> Node h = head;</span><br><span class=\"line\"> if (h != null && h.waitStatus != 0)</span><br><span class=\"line\"> unparkSuccessor(h);//唤醒当前节点的后继节点所包含的线程</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-acquireShared-int-arg\"><a href=\"#void-acquireShared-int-arg\" class=\"headerlink\" title=\"void acquireShared(int arg)\"></a>void acquireShared(int arg)</h3><ul>\n<li>以共享模式获取状态<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final void acquireShared(int arg) {</span><br><span class=\"line\"> //尝试获取共享状态</span><br><span class=\"line\"> if (tryAcquireShared(arg) < 0)</span><br><span class=\"line\"> //获取失败进入sync队列</span><br><span class=\"line\"> doAcquireShared(arg);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-acquireSharedInterruptibly-int-arg\"><a href=\"#void-acquireSharedInterruptibly-int-arg\" class=\"headerlink\" title=\"void acquireSharedInterruptibly(int arg)\"></a>void acquireSharedInterruptibly(int arg)</h3><ul>\n<li>相比acquireShared,只是增加了可中断<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final void acquireSharedInterruptibly(int arg) throws InterruptedException {</span><br><span class=\"line\"> if (Thread.interrupted())</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> if (tryAcquireShared(arg) < 0)</span><br><span class=\"line\"> doAcquireSharedInterruptibly(arg);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-tryAcquireSharedNanos-int-arg-long-nanosTimeout\"><a href=\"#boolean-tryAcquireSharedNanos-int-arg-long-nanosTimeout\" class=\"headerlink\" title=\"boolean tryAcquireSharedNanos(int arg, long nanosTimeout)\"></a>boolean tryAcquireSharedNanos(int arg, long nanosTimeout)</h3><ul>\n<li>在acquireSharedInterruptibly的基础上增加了超时<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)</span><br><span class=\"line\"> throws InterruptedException {</span><br><span class=\"line\"> if (Thread.interrupted())</span><br><span class=\"line\"> throw new InterruptedException();</span><br><span class=\"line\"> return tryAcquireShared(arg) >= 0 ||</span><br><span class=\"line\"> doAcquireSharedNanos(arg, nanosTimeout);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-releaseShared-int-arg\"><a href=\"#boolean-releaseShared-int-arg\" class=\"headerlink\" title=\"boolean releaseShared(int arg)\"></a>boolean releaseShared(int arg)</h3><ul>\n<li>释放共享资源<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean releaseShared(int arg) {</span><br><span class=\"line\"> //尝试释放共享资源</span><br><span class=\"line\"> if (tryReleaseShared(arg)) {</span><br><span class=\"line\"> //唤醒的过程,上文已经分析</span><br><span class=\"line\"> doReleaseShared();</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-hasQueuedThreads\"><a href=\"#boolean-hasQueuedThreads\" class=\"headerlink\" title=\"boolean hasQueuedThreads()\"></a>boolean hasQueuedThreads()</h3><ul>\n<li>队列中是否有线程在等待获取资源<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasQueuedThreads() {</span><br><span class=\"line\"> return head != tail;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-hasContended\"><a href=\"#boolean-hasContended\" class=\"headerlink\" title=\"boolean hasContended()\"></a>boolean hasContended()</h3><ul>\n<li>是否其他线程也竞争获取资源(因为head是公用的)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasContended() {</span><br><span class=\"line\"> return head != null;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Thread-getFirstQueuedThread\"><a href=\"#Thread-getFirstQueuedThread\" class=\"headerlink\" title=\"Thread getFirstQueuedThread()\"></a>Thread getFirstQueuedThread()</h3><ul>\n<li>返回队列中的第一个线程,如果快速路径失败(head == tail),则调用fullGetFirstQueuedThread查找<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final Thread getFirstQueuedThread() {</span><br><span class=\"line\"> // handle only fast path, else relay</span><br><span class=\"line\"> return (head == tail) ? null : fullGetFirstQueuedThread();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Thread-fullGetFirstQueuedThread\"><a href=\"#Thread-fullGetFirstQueuedThread\" class=\"headerlink\" title=\"Thread fullGetFirstQueuedThread()\"></a>Thread fullGetFirstQueuedThread()</h3><ul>\n<li>返回队列中第一个(等待时间最长的)线程,如果目前没有将任何线程加入队列,则返回 null.</li>\n<li>在此实现中,该操作是以固定时间返回的,但是,如果其他线程目前正在并发修改该队列,则可能出现循环争用。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private Thread fullGetFirstQueuedThread() {</span><br><span class=\"line\"> Node h, s;</span><br><span class=\"line\"> Thread st;</span><br><span class=\"line\"> if (((h = head) != null && (s = h.next) != null &&</span><br><span class=\"line\"> s.prev == head && (st = s.thread) != null) ||</span><br><span class=\"line\"> ((h = head) != null && (s = h.next) != null &&</span><br><span class=\"line\"> s.prev == head && (st = s.thread) != null))</span><br><span class=\"line\"> return st;</span><br><span class=\"line\"> Node t = tail;</span><br><span class=\"line\"> Thread firstThread = null;</span><br><span class=\"line\"> while (t != null && t != head) {</span><br><span class=\"line\"> Thread tt = t.thread;</span><br><span class=\"line\"> if (tt != null)</span><br><span class=\"line\"> firstThread = tt;</span><br><span class=\"line\"> t = t.prev;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return firstThread;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-isQueued-Thread-thread\"><a href=\"#boolean-isQueued-Thread-thread\" class=\"headerlink\" title=\"boolean isQueued(Thread thread)\"></a>boolean isQueued(Thread thread)</h3><ul>\n<li>判断thread是否在队列中等待获取资源<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean isQueued(Thread thread) {</span><br><span class=\"line\"> if (thread == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> for (Node p = tail; p != null; p = p.prev)</span><br><span class=\"line\"> if (p.thread == thread)</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-apparentlyFirstQueuedIsExclusive\"><a href=\"#boolean-apparentlyFirstQueuedIsExclusive\" class=\"headerlink\" title=\"boolean apparentlyFirstQueuedIsExclusive()\"></a>boolean apparentlyFirstQueuedIsExclusive()</h3><ul>\n<li>在head不为null,head的next不为null,head的next不是共享的,head的thread不为空的条件下返回true,否则返回false</li>\n<li>作用就是读锁不应该让写锁始终等待。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean apparentlyFirstQueuedIsExclusive() {</span><br><span class=\"line\"> Node h, s;</span><br><span class=\"line\"> return (h = head) != null &&</span><br><span class=\"line\"> (s = h.next) != null &&</span><br><span class=\"line\"> !s.isShared() &&</span><br><span class=\"line\"> s.thread != null;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-hasQueuedPredecessors\"><a href=\"#boolean-hasQueuedPredecessors\" class=\"headerlink\" title=\"boolean hasQueuedPredecessors()\"></a>boolean hasQueuedPredecessors()</h3><ul>\n<li>判断当前线程是不是在CLH队列的队首,来返回AQS中是不是有比当前线程等待更久的线程。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasQueuedPredecessors() {</span><br><span class=\"line\"> Node t = tail; // Read fields in reverse initialization order</span><br><span class=\"line\"> Node h = head;</span><br><span class=\"line\"> Node s;</span><br><span class=\"line\"> return h != t &&</span><br><span class=\"line\"> ((s = h.next) == null || s.thread != Thread.currentThread());</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"int-getQueueLength\"><a href=\"#int-getQueueLength\" class=\"headerlink\" title=\"int getQueueLength()\"></a>int getQueueLength()</h3><ul>\n<li>获取队列长度<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final int getQueueLength() {</span><br><span class=\"line\"> int n = 0;</span><br><span class=\"line\"> for (Node p = tail; p != null; p = p.prev) {</span><br><span class=\"line\"> if (p.thread != null)</span><br><span class=\"line\"> ++n;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return n;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Collection-getQueuedThreads\"><a href=\"#Collection-getQueuedThreads\" class=\"headerlink\" title=\"Collection getQueuedThreads()\"></a>Collection<Thread> getQueuedThreads()</h3><ul>\n<li>获取线程队列<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final Collection<Thread> getQueuedThreads() {</span><br><span class=\"line\"> ArrayList<Thread> list = new ArrayList<Thread>();</span><br><span class=\"line\"> for (Node p = tail; p != null; p = p.prev) {</span><br><span class=\"line\"> Thread t = p.thread;</span><br><span class=\"line\"> if (t != null)</span><br><span class=\"line\"> list.add(t);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return list;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Collection-getExclusiveQueuedThreads\"><a href=\"#Collection-getExclusiveQueuedThreads\" class=\"headerlink\" title=\"Collection getExclusiveQueuedThreads()\"></a>Collection<Thread> getExclusiveQueuedThreads()</h3><ul>\n<li>获取独占资源的线程队列<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final Collection<Thread> getExclusiveQueuedThreads() {</span><br><span class=\"line\"> ArrayList<Thread> list = new ArrayList<Thread>();</span><br><span class=\"line\"> for (Node p = tail; p != null; p = p.prev) {</span><br><span class=\"line\"> if (!p.isShared()) {</span><br><span class=\"line\"> Thread t = p.thread;</span><br><span class=\"line\"> if (t != null)</span><br><span class=\"line\"> list.add(t);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return list;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Collection-getSharedQueuedThreads\"><a href=\"#Collection-getSharedQueuedThreads\" class=\"headerlink\" title=\"Collection getSharedQueuedThreads()\"></a>Collection<Thread> getSharedQueuedThreads()</h3><ul>\n<li>获取共享资源的线程队列<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final Collection<Thread> getSharedQueuedThreads() {</span><br><span class=\"line\"> ArrayList<Thread> list = new ArrayList<Thread>();</span><br><span class=\"line\"> for (Node p = tail; p != null; p = p.prev) {</span><br><span class=\"line\"> if (p.isShared()) {</span><br><span class=\"line\"> Thread t = p.thread;</span><br><span class=\"line\"> if (t != null)</span><br><span class=\"line\"> list.add(t);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return list;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-isOnSyncQueue-Node-node\"><a href=\"#boolean-isOnSyncQueue-Node-node\" class=\"headerlink\" title=\"boolean isOnSyncQueue(Node node)\"></a>boolean isOnSyncQueue(Node node)</h3><ul>\n<li>判断该节点是否在CLH队列中<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean isOnSyncQueue(Node node) {</span><br><span class=\"line\"> //如果该节点的状态为CONDITION(该状态只能在CONDITION队列中出现,CLH队列中不会出现CONDITION状态),或者该节点的prev指针为null,则该节点一定不在CLH队列中</span><br><span class=\"line\"> if (node.waitStatus == Node.CONDITION || node.prev == null)</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> //如果该节点的next(不是nextWaiter,next指针在CLH队列中指向下一个节点)状态不为null,则该节点一定在CLH队列中</span><br><span class=\"line\"> if (node.next != null) // If has successor, it must be on queue</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> //遍历CLH队列(从尾节点开始遍历)查找该节点 </span><br><span class=\"line\"> return findNodeFromTail(node);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-findNodeFromTail-Node-node\"><a href=\"#boolean-findNodeFromTail-Node-node\" class=\"headerlink\" title=\"boolean findNodeFromTail(Node node)\"></a>boolean findNodeFromTail(Node node)</h3><ul>\n<li>从tail往前寻找节点<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private boolean findNodeFromTail(Node node) {</span><br><span class=\"line\"> Node t = tail;</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> if (t == node)</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> if (t == null)</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> t = t.prev;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-transferForSignal-Node-node\"><a href=\"#boolean-transferForSignal-Node-node\" class=\"headerlink\" title=\"boolean transferForSignal(Node node)\"></a>boolean transferForSignal(Node node)</h3><ul>\n<li>将节点添加到CLH队列中<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> final boolean transferForSignal(Node node) {</span><br><span class=\"line\"> //如果CAS失败,则当前节点的状态为CANCELLED</span><br><span class=\"line\"> if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> //首先enq将该node添加到CLH队列中</span><br><span class=\"line\"> Node p = enq(node);</span><br><span class=\"line\"> int ws = p.waitStatus;</span><br><span class=\"line\"> //如果p是一个取消(ws > 0)了的节点,或者对p进行CAS设置失败,则唤醒node节点,让node所在线程进入到acquireQueue方法中,重新进行相关操作</span><br><span class=\"line\">//否则,由于该节点的前驱节点已经是signal状态了,不用在此处唤醒await中的线程,唤醒工作留给CLH队列中前驱节点</span><br><span class=\"line\"> if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))</span><br><span class=\"line\"> LockSupport.unpark(node.thread);//唤醒</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-transferAfterCancelledWait-Node-node\"><a href=\"#boolean-transferAfterCancelledWait-Node-node\" class=\"headerlink\" title=\"boolean transferAfterCancelledWait(Node node)\"></a>boolean transferAfterCancelledWait(Node node)</h3><ul>\n<li>将当前Node强制transfer到CLH队列中<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean transferAfterCancelledWait(Node node) {</span><br><span class=\"line\"> //将该节点状态由CONDITION变成0,调用enq将该节点从CONDITION队列添加到CLH队列中(但是在CONDITION队列中的nextWaiter连接并没有取消)</span><br><span class=\"line\"> if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {</span><br><span class=\"line\"> enq(node);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //循环检测该node是否已经成功添加到CLH队列中</span><br><span class=\"line\"> while (!isOnSyncQueue(node))</span><br><span class=\"line\"> Thread.yield();</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"int-fullyRelease-Node-node\"><a href=\"#int-fullyRelease-Node-node\" class=\"headerlink\" title=\"int fullyRelease(Node node)\"></a>int fullyRelease(Node node)</h3><ul>\n<li>完全释放锁,释放成功则返回,失败则将当前节点的状态设置成cancelled表示当前节点失效<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final int fullyRelease(Node node) {</span><br><span class=\"line\"> boolean failed = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> int savedState = getState();</span><br><span class=\"line\"> if (release(savedState)) {</span><br><span class=\"line\"> failed = false;</span><br><span class=\"line\"> return savedState;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> throw new IllegalMonitorStateException();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (failed)</span><br><span class=\"line\"> node.waitStatus = Node.CANCELLED;//失败则当前node状态为CANCELLED</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-owns-ConditionObject-condition\"><a href=\"#boolean-owns-ConditionObject-condition\" class=\"headerlink\" title=\"boolean owns(ConditionObject condition)\"></a>boolean owns(ConditionObject condition)</h3><ul>\n<li>判断条件对象拥有者<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean owns(ConditionObject condition) {</span><br><span class=\"line\"> return condition.isOwnedBy(this);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"boolean-hasWaiters-ConditionObject-condition\"><a href=\"#boolean-hasWaiters-ConditionObject-condition\" class=\"headerlink\" title=\"boolean hasWaiters(ConditionObject condition)\"></a>boolean hasWaiters(ConditionObject condition)</h3><ul>\n<li>条件队列是否有等待者<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasWaiters(ConditionObject condition) {</span><br><span class=\"line\"> if (!owns(condition))</span><br><span class=\"line\"> throw new IllegalArgumentException("Not owner");</span><br><span class=\"line\"> return condition.hasWaiters();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"int-getWaitQueueLength-ConditionObject-condition\"><a href=\"#int-getWaitQueueLength-ConditionObject-condition\" class=\"headerlink\" title=\"int getWaitQueueLength(ConditionObject condition)\"></a>int getWaitQueueLength(ConditionObject condition)</h3><ul>\n<li>获取条件队列等待者数量<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final int getWaitQueueLength(ConditionObject condition) {</span><br><span class=\"line\"> if (!owns(condition))</span><br><span class=\"line\"> throw new IllegalArgumentException("Not owner");</span><br><span class=\"line\"> return condition.getWaitQueueLength();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Collection-getWaitingThreads-ConditionObject-condition\"><a href=\"#Collection-getWaitingThreads-ConditionObject-condition\" class=\"headerlink\" title=\"Collection getWaitingThreads(ConditionObject condition)\"></a>Collection<Thread> getWaitingThreads(ConditionObject condition)</h3><ul>\n<li>获取条件队列等待者线程<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final Collection<Thread> getWaitingThreads(ConditionObject condition) {</span><br><span class=\"line\"> if (!owns(condition))</span><br><span class=\"line\"> throw new IllegalArgumentException("Not owner");</span><br><span class=\"line\"> return condition.getWaitingThreads();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"辅助Field及方法\"><a href=\"#辅助Field及方法\" class=\"headerlink\" title=\"辅助Field及方法\"></a>辅助Field及方法</h2><p>就不一一解释了</p>\n<ul>\n<li>Cas相关Field<ul>\n<li>Unsafe unsafe;</li>\n<li>long stateOffset;</li>\n<li>long headOffset;</li>\n<li>long tailOffset;</li>\n<li>long waitStatusOffset;</li>\n<li>long nextOffset;</li>\n</ul>\n</li>\n<li>Cas相关Method<ul>\n<li>boolean compareAndSetHead(Node update)</li>\n<li>boolean compareAndSetTail(Node expect, Node update)</li>\n<li>boolean compareAndSetWaitStatus(Node node, int expect, int update)</li>\n<li>boolean compareAndSetNext(Node node, Node expect, Node update)</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h1><ul>\n<li>《Java并发编程之美》</li>\n<li><a href=\"https://blog.csdn.net/u011470552/article/details/76571472\">https://blog.csdn.net/u011470552/article/details/76571472</a></li>\n<li><a href=\"https://www.jianshu.com/p/4eef16131bb8\">https://www.jianshu.com/p/4eef16131bb8</a></li>\n<li><a href=\"https://www.jianshu.com/p/e4301229f59e\">https://www.jianshu.com/p/e4301229f59e</a></li>\n<li><a href=\"https://blog.csdn.net/weixin_34235371/article/details/87147929\">https://blog.csdn.net/weixin_34235371/article/details/87147929</a></li>\n<li><a href=\"https://blog.csdn.net/lkg_vvk/article/details/79130070\">https://blog.csdn.net/lkg_vvk/article/details/79130070</a></li>\n</ul>\n","categories":["Java"],"tags":["Java","ThreadPool","AbstractQueuedSynchronizer","AQS","多线程"]},{"title":"Java Thread 的状态","url":"/2019/08/18/JavaThread%E7%9A%84%E7%8A%B6%E6%80%81/","content":"<h1 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h1><p>Java中线程的状态(也可以理解为生命周期),主要有以下几种:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED 。<br>这些状态存在于Thread类中的一个枚举中如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public enum State {</span><br><span class=\"line\"> NEW,</span><br><span class=\"line\"> RUNNABLE,</span><br><span class=\"line\"> BLOCKED,</span><br><span class=\"line\"> WAITING,</span><br><span class=\"line\"> TIMED_WAITING,</span><br><span class=\"line\"> TERMINATED;</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n\n<h1 id=\"具体状态\"><a href=\"#具体状态\" class=\"headerlink\" title=\"具体状态\"></a>具体状态</h1><h2 id=\"NEW\"><a href=\"#NEW\" class=\"headerlink\" title=\"NEW\"></a>NEW</h2><ul>\n<li>JDK原生的注释说的是一个还没有开始(started)的线程, 也就是说当new了一个线程之后,并没有调用start方法的时候,线程的状态就是NEW。</li>\n</ul>\n<h2 id=\"RUNNABLE\"><a href=\"#RUNNABLE\" class=\"headerlink\" title=\"RUNNABLE\"></a>RUNNABLE</h2><ul>\n<li>这个状态代表线程处于一个可运行的状态,但是不一定会执行,因为要考虑cpu核心数等影响,直到CPU时间分片给到当前线程,才会真正的执行。</li>\n</ul>\n<h2 id=\"BLOCKED\"><a href=\"#BLOCKED\" class=\"headerlink\" title=\"BLOCKED\"></a>BLOCKED</h2><ul>\n<li>阻塞状态,通常都是在等待获取某个监视器的锁的时候会处于当前状态。</li>\n<li>已知:<ul>\n<li>等待synchronized获取监视器对象锁的时候,线程的状态为:BLOCKED (on object monitor)</li>\n<li>线程从WAITING/TIMED_WAITING(object.wait()方法)状态唤醒后,因为需要重新获取synchronized监视器对象,会先进入BLOCKED状态,待获取了监视器对象锁后,变为RUNNABLE状态。</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"WAITING\"><a href=\"#WAITING\" class=\"headerlink\" title=\"WAITING\"></a>WAITING</h2><ul>\n<li>等待状态,一般是由于调用了如下方法而进入等待状态:<ul>\n<li>Object#wait()</li>\n<li>Thread#join()</li>\n<li>LockSupport#park()</li>\n</ul>\n</li>\n<li>线程进入WAITING状态后,等待其他线程调用对应的通知对象才会唤醒。比如:<ul>\n<li>Object.notify()</li>\n<li>Object.notifyAll()</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"TIMED-WAITING\"><a href=\"#TIMED-WAITING\" class=\"headerlink\" title=\"TIMED_WAITING\"></a>TIMED_WAITING</h2><ul>\n<li>一样是等待状态,但是区别是带有一个超时时间,超过这个时间后,线程会被自动唤醒。以下几个方法会进入该状态:<ul>\n<li>Thread.sleep(long)</li>\n<li>Object#wait(long)</li>\n<li>Thread#join(long)</li>\n<li>LockSupport#parkNanos(long)</li>\n<li>LockSupport#parkUntil(long)</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"TERMINATED\"><a href=\"#TERMINATED\" class=\"headerlink\" title=\"TERMINATED\"></a>TERMINATED</h2><ul>\n<li>线程的终止状态,也就是当前线程已经执行完成。</li>\n</ul>\n<h1 id=\"状态转换\"><a href=\"#状态转换\" class=\"headerlink\" title=\"状态转换\"></a>状态转换</h1><ul>\n<li>这个网上图片已经一大把了,有一张图看着简单明了,我就直接引用了:<ul>\n<li>(原地址:<a href=\"https://blog.csdn.net/shi2huang/article/details/80289155\">https://blog.csdn.net/shi2huang/article/details/80289155</a>)</li>\n</ul>\n</li>\n<li><img src=\"/JavaThread%E7%9A%84%E7%8A%B6%E6%80%81/20180512102914671.png\" alt=\"线程转换图\"></li>\n</ul>\n<h1 id=\"关于释放资源方面\"><a href=\"#关于释放资源方面\" class=\"headerlink\" title=\"关于释放资源方面\"></a>关于释放资源方面</h1><h2 id=\"锁的释放\"><a href=\"#锁的释放\" class=\"headerlink\" title=\"锁的释放\"></a>锁的释放</h2><ul>\n<li>通过synchronized获取监视器对象锁之后,有如下几个方式会释放锁资源:<ul>\n<li>方法或者代码块正常执行完成</li>\n<li>方法或者代码块抛出异常,代码终止执行</li>\n<li>调用监视器对象的wait方法,会释放锁资源</li>\n</ul>\n</li>\n<li>补充:<ul>\n<li>调用Thread.sleep方法不会释放锁资源</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"CPU资源的释放\"><a href=\"#CPU资源的释放\" class=\"headerlink\" title=\"CPU资源的释放\"></a>CPU资源的释放</h2><pre><code>- Thread.sleep(),会释放CPU资源,但是不会释放锁。\n- Thread.yield(),会尝试放弃CPU资源,但是不会释放锁。(可能放弃后又立即获取到)\n- 还有suspend()方法,由于已经过时,不再解释。\n</code></pre>\n<h1 id=\"其他\"><a href=\"#其他\" class=\"headerlink\" title=\"其他\"></a>其他</h1><h2 id=\"obj-notify-x2F-notifyAll区别\"><a href=\"#obj-notify-x2F-notifyAll区别\" class=\"headerlink\" title=\"obj.notify/notifyAll区别\"></a>obj.notify/notifyAll区别</h2><ul>\n<li>notify会随机唤醒监视obj的一个线程,具体是哪个无法指定,由JVM确定。</li>\n<li>notifyAll会唤醒所有监视obj的线程,然后重新去竞争,只有一个可以获取到资源。</li>\n</ul>\n<h2 id=\"wait-x2F-sleep-x2F-yield区别\"><a href=\"#wait-x2F-sleep-x2F-yield区别\" class=\"headerlink\" title=\"wait/sleep/yield区别\"></a>wait/sleep/yield区别</h2><ul>\n<li>sleep()方法会释放CPU资源但是不会释放锁资源。</li>\n<li>wait()方法会释放CPU资源和锁资源。</li>\n<li>yield()方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短时间内再次执行。</li>\n</ul>\n<h2 id=\"虚假唤醒\"><a href=\"#虚假唤醒\" class=\"headerlink\" title=\"虚假唤醒\"></a>虚假唤醒</h2><ul>\n<li>就是在没有调用obj.notify/notifyAll的前提下,obj.wait被唤醒了。</li>\n<li>等待线程即使没有收到正确的信号,也能够执行后续的操作,这就可能影响程序执行的正常逻辑。</li>\n<li>为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。</li>\n<li>示例如下:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">class Sign {</span><br><span class=\"line\"> private final Object obj = new Object();</span><br><span class=\"line\"> private boolean flag = false;</span><br><span class=\"line\"></span><br><span class=\"line\"> public void doWait() throws InterruptedException {</span><br><span class=\"line\"> synchronized (obj) {</span><br><span class=\"line\"> while (!flag) {</span><br><span class=\"line\"> obj.wait();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> flag = false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> public void doNotify() {</span><br><span class=\"line\"> synchronized (obj) {</span><br><span class=\"line\"> flag = true;</span><br><span class=\"line\"> obj.notifyAll();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"RUNNABLE状态\"><a href=\"#RUNNABLE状态\" class=\"headerlink\" title=\"RUNNABLE状态\"></a>RUNNABLE状态</h2><ul>\n<li>Java中没有线程所谓的RUNNING和READY状态,这两个状态合并到一起,为RUNNABLE状态。</li>\n<li>也就是说即使线程处于RUNNABLE状态,也不一定就正在执行,可能正在等待CPU时间分片。</li>\n</ul>\n","categories":["Java"],"tags":["Java","Thread","State"]},{"title":"Java线程池分析-AbstractExecutorService","url":"/2019/07/08/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-AbstractExecutorService/","content":"<p>内部只有若干方法</p>\n<h1 id=\"内部依赖方法\"><a href=\"#内部依赖方法\" class=\"headerlink\" title=\"内部依赖方法\"></a>内部依赖方法</h1><h2 id=\"newTaskFor-Runnable-runnable-T-value\"><a href=\"#newTaskFor-Runnable-runnable-T-value\" class=\"headerlink\" title=\"newTaskFor(Runnable runnable, T value)\"></a>newTaskFor(Runnable runnable, T value)</h2><ul>\n<li>构造一个FutureTask,FutureTask 实现了 RunnableFuture,既是Runnable接口,也是Future接口,类似于适配器。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {</span><br><span class=\"line\"> return new FutureTask<T>(runnable, value);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"newTaskFor-Callable-callable\"><a href=\"#newTaskFor-Callable-callable\" class=\"headerlink\" title=\"newTaskFor(Callable callable)\"></a>newTaskFor(Callable<T> callable)</h2><ul>\n<li>同上,将一个Callable适配到RunnableFuture <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {</span><br><span class=\"line\"> return new FutureTask<T>(callable);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"submit-提交任务方法\"><a href=\"#submit-提交任务方法\" class=\"headerlink\" title=\"submit 提交任务方法\"></a>submit 提交任务方法</h1><h2 id=\"submit-Runnable-task\"><a href=\"#submit-Runnable-task\" class=\"headerlink\" title=\"submit(Runnable task)\"></a>submit(Runnable task)</h2><ul>\n<li>将Runnable接口封装为 RunnableFuture<Void>,并由子类实现执行逻辑 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public Future<?> submit(Runnable task) {</span><br><span class=\"line\"> if (task == null) throw new NullPointerException();</span><br><span class=\"line\"> RunnableFuture<Void> ftask = newTaskFor(task, null);</span><br><span class=\"line\"> execute(ftask);</span><br><span class=\"line\"> return ftask;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"submit-Runnable-task-T-result\"><a href=\"#submit-Runnable-task-T-result\" class=\"headerlink\" title=\"submit(Runnable task, T result)\"></a>submit(Runnable task, T result)</h2><ul>\n<li>将Runnable接口封装为 RunnableFuture<T>,并由子类实现执行逻辑 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> Future<T> submit(Runnable task, T result) {</span><br><span class=\"line\"> if (task == null) throw new NullPointerException();</span><br><span class=\"line\"> RunnableFuture<T> ftask = newTaskFor(task, result);</span><br><span class=\"line\"> execute(ftask);</span><br><span class=\"line\"> return ftask;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"submit-Callable-task\"><a href=\"#submit-Callable-task\" class=\"headerlink\" title=\"submit(Callable task)\"></a>submit(Callable<T> task)</h2><ul>\n<li>将Callable接口封装为 RunnableFuture<T>,并由子类实现执行逻辑 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> Future<T> submit(Callable<T> task) {</span><br><span class=\"line\"> if (task == null) throw new NullPointerException();</span><br><span class=\"line\"> RunnableFuture<T> ftask = newTaskFor(task);</span><br><span class=\"line\"> execute(ftask);</span><br><span class=\"line\"> return ftask;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"Invoke系列方法\"><a href=\"#Invoke系列方法\" class=\"headerlink\" title=\"Invoke系列方法\"></a>Invoke系列方法</h1><h2 id=\"doInvokeAny\"><a href=\"#doInvokeAny\" class=\"headerlink\" title=\"doInvokeAny\"></a>doInvokeAny</h2><ul>\n<li>执行tasks任务,可以指定是否带有超时参数。invokeAny方法底层依赖该方法 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException {</span><br><span class=\"line\"> if (tasks == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> int ntasks = tasks.size();</span><br><span class=\"line\"> if (ntasks == 0)</span><br><span class=\"line\"> throw new IllegalArgumentException();</span><br><span class=\"line\"> //全部task对应的future集合</span><br><span class=\"line\"> ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);</span><br><span class=\"line\"> //实际执行的实体</span><br><span class=\"line\"> ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> ExecutionException ee = null;</span><br><span class=\"line\"> //是否需要超时</span><br><span class=\"line\"> final long deadline = timed ? System.nanoTime() + nanos : 0L;</span><br><span class=\"line\"> Iterator<? extends Callable<T>> it = tasks.iterator();</span><br><span class=\"line\"> //先提交一个任务</span><br><span class=\"line\"> futures.add(ecs.submit(it.next()));</span><br><span class=\"line\"> //任务数减一</span><br><span class=\"line\"> --ntasks;</span><br><span class=\"line\"> //工作中的线程数为1</span><br><span class=\"line\"> int active = 1;</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> Future<T> f = ecs.poll();//获取一个执行的任务</span><br><span class=\"line\"> //判断任务是否完成,为null则还没有执行完成</span><br><span class=\"line\"> if (f == null) {</span><br><span class=\"line\"> //提交的任务是否已经全部由ecs执行,如果还有未提交的,则继续提交。</span><br><span class=\"line\"> if (ntasks > 0) {</span><br><span class=\"line\"> --ntasks;</span><br><span class=\"line\"> futures.add(ecs.submit(it.next()));</span><br><span class=\"line\"> ++active;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else if (active == 0)//没有存活的任务,说明任务已经完成,但是有异常,导致active=0,则中断循环,然后抛出异常</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> else if (timed) {//检查是否需要超时</span><br><span class=\"line\"> f = ecs.poll(nanos, TimeUnit.NANOSECONDS);</span><br><span class=\"line\"> if (f == null)</span><br><span class=\"line\"> throw new TimeoutException();</span><br><span class=\"line\"> nanos = deadline - System.nanoTime();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else</span><br><span class=\"line\"> f = ecs.take();//不许要超时,则阻塞获取</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (f != null) {//有任务完成,active数量减一,并返回结果</span><br><span class=\"line\"> --active;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> return f.get();</span><br><span class=\"line\"> } catch (ExecutionException eex) {</span><br><span class=\"line\"> ee = eex;</span><br><span class=\"line\"> } catch (RuntimeException rex) {</span><br><span class=\"line\"> ee = new ExecutionException(rex);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (ee == null)</span><br><span class=\"line\"> ee = new ExecutionException();</span><br><span class=\"line\"> throw ee;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> for (int i = 0, size = futures.size(); i < size; i++)</span><br><span class=\"line\"> futures.get(i).cancel(true);//已经完成或者抛出异常,取消其他正在执行的任务。</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"invokeAny\"><a href=\"#invokeAny\" class=\"headerlink\" title=\"invokeAny\"></a>invokeAny</h2><ul>\n<li><p>忽略超时异常的执行方式</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> return doInvokeAny(tasks, false, 0);</span><br><span class=\"line\"> } catch (TimeoutException cannotHappen) {//忽略超时异常</span><br><span class=\"line\"> assert false;</span><br><span class=\"line\"> return null;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>可以设置超时的执行方式</p>\n <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {</span><br><span class=\"line\"> return doInvokeAny(tasks, true, unit.toNanos(timeout));//会抛出超时异常</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"invokeAll\"><a href=\"#invokeAll\" class=\"headerlink\" title=\"invokeAll\"></a>invokeAll</h2><ul>\n<li>全部执行并等待全部完成 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {</span><br><span class=\"line\"> if (tasks == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());</span><br><span class=\"line\"> boolean done = false;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> //全部任务提交并执行</span><br><span class=\"line\"> for (Callable<T> t : tasks) {</span><br><span class=\"line\"> RunnableFuture<T> f = newTaskFor(t);</span><br><span class=\"line\"> futures.add(f);</span><br><span class=\"line\"> execute(f);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //等待全部结果完成</span><br><span class=\"line\"> for (int i = 0, size = futures.size(); i < size; i++) {</span><br><span class=\"line\"> Future<T> f = futures.get(i);</span><br><span class=\"line\"> if (!f.isDone()) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> f.get();</span><br><span class=\"line\"> } catch (CancellationException ignore) {</span><br><span class=\"line\"> } catch (ExecutionException ignore) {</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //全部完成后标示位更新</span><br><span class=\"line\"> done = true;</span><br><span class=\"line\"> return futures;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (!done)//如果没有完成,说明有异常,则取消所有任务</span><br><span class=\"line\"> for (int i = 0, size = futures.size(); i < size; i++)</span><br><span class=\"line\"> futures.get(i).cancel(true);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>全部执行,并带有超时的等待完成 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)</span><br><span class=\"line\"> throws InterruptedException {</span><br><span class=\"line\"> if (tasks == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> long nanos = unit.toNanos(timeout);</span><br><span class=\"line\"> ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());</span><br><span class=\"line\"> boolean done = false;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (Callable<T> t : tasks)</span><br><span class=\"line\"> futures.add(newTaskFor(t));</span><br><span class=\"line\"> final long deadline = System.nanoTime() + nanos;</span><br><span class=\"line\"> final int size = futures.size();</span><br><span class=\"line\"> //提交任务</span><br><span class=\"line\"> for (int i = 0; i < size; i++) {</span><br><span class=\"line\"> execute((Runnable)futures.get(i));</span><br><span class=\"line\"> //计算超时时间</span><br><span class=\"line\"> nanos = deadline - System.nanoTime();</span><br><span class=\"line\"> if (nanos <= 0L)</span><br><span class=\"line\"> return futures;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //获取结果</span><br><span class=\"line\"> for (int i = 0; i < size; i++) {</span><br><span class=\"line\"> Future<T> f = futures.get(i);</span><br><span class=\"line\"> if (!f.isDone()) {</span><br><span class=\"line\"> if (nanos <= 0L)//已经超时,则直接返回现有的</span><br><span class=\"line\"> return futures;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> f.get(nanos, TimeUnit.NANOSECONDS);//带有超时的去获取,如果超时,则直接返回结果</span><br><span class=\"line\"> } catch (CancellationException ignore) {</span><br><span class=\"line\"> } catch (ExecutionException ignore) {</span><br><span class=\"line\"> } catch (TimeoutException toe) {</span><br><span class=\"line\"> return futures;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> nanos = deadline - System.nanoTime();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> done = true;//正常完成</span><br><span class=\"line\"> return futures;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> if (!done)//非正常完成,则取消剩余任务</span><br><span class=\"line\"> for (int i = 0, size = futures.size(); i < size; i++)</span><br><span class=\"line\"> futures.get(i).cancel(true);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n","categories":["Java"],"tags":["Java","ThreadPool","多线程","AbstractExecutorService"]},{"title":"Java线程池分析-Worker","url":"/2019/07/08/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-Worker/","content":"<h1 id=\"结构\"><a href=\"#结构\" class=\"headerlink\" title=\"结构\"></a>结构</h1><p>其实从结构上来看,Worker十分简单。实现了Runnable接口,同时继承了AQS队列。如下图所示:<br><img src=\"/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-Worker/WorkerClass.png\" alt=\"类结构\"></p>\n<p>Worker的方法也不多,也比较简单,如下图所示:<br><img src=\"/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-Worker/Method.png\" alt=\"方法\"></p>\n<h1 id=\"分析\"><a href=\"#分析\" class=\"headerlink\" title=\"分析\"></a>分析</h1><h2 id=\"内部Field\"><a href=\"#内部Field\" class=\"headerlink\" title=\"内部Field\"></a>内部Field</h2><ul>\n<li>内部Field不多,如下:<ul>\n<li>Thread thread 实际的工作线程</li>\n<li>Runnable firstTask 初始化的第一个任务</li>\n<li>long completedTasks 当前Worker已经完成的任务数</li>\n</ul>\n</li>\n<li>在补充一下父类的state<ul>\n<li>0 代表是未锁定状态</li>\n<li>1 代表是锁定状态</li>\n<li>-1 代表是不允许被中断,在构造参数中设置<br>接下来简单分析一下各个方法:</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"方法\"><a href=\"#方法\" class=\"headerlink\" title=\"方法\"></a>方法</h2><h3 id=\"public-void-run\"><a href=\"#public-void-run\" class=\"headerlink\" title=\"public void run()\"></a>public void run()</h3><ul>\n<li>直接调用线程池的runWorker方法,之后分析 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void run() {</span><br><span class=\"line\"> runWorker(this);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"protected-boolean-isHeldExclusively\"><a href=\"#protected-boolean-isHeldExclusively\" class=\"headerlink\" title=\"protected boolean isHeldExclusively()\"></a>protected boolean isHeldExclusively()</h3><ul>\n<li>是否是独占排他的 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean isHeldExclusively() {</span><br><span class=\"line\"> return getState() != 0;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"protected-boolean-tryAcquire-int-unused\"><a href=\"#protected-boolean-tryAcquire-int-unused\" class=\"headerlink\" title=\"protected boolean tryAcquire(int unused)\"></a>protected boolean tryAcquire(int unused)</h3><ul>\n<li>尝试获取锁,参考AQS,这里还是使用CAS进行操作,失败则快速返回 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean tryAcquire(int unused) {</span><br><span class=\"line\"> if (compareAndSetState(0, 1)) {</span><br><span class=\"line\"> setExclusiveOwnerThread(Thread.currentThread());</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"protected-boolean-tryRelease-int-unused\"><a href=\"#protected-boolean-tryRelease-int-unused\" class=\"headerlink\" title=\"protected boolean tryRelease(int unused)\"></a>protected boolean tryRelease(int unused)</h3><ul>\n<li>尝试释放锁,这个貌似只会成功,不会失败 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected boolean tryRelease(int unused) {</span><br><span class=\"line\"> setExclusiveOwnerThread(null);</span><br><span class=\"line\"> setState(0);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"public-void-lock\"><a href=\"#public-void-lock\" class=\"headerlink\" title=\"public void lock()\"></a>public void lock()</h3><ul>\n<li>加锁,不过这里是阻塞式的 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void lock() { acquire(1); }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"public-boolean-tryLock\"><a href=\"#public-boolean-tryLock\" class=\"headerlink\" title=\"public boolean tryLock()\"></a>public boolean tryLock()</h3><ul>\n<li>尝试加锁,调用tryAcquire <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean tryLock() { return tryAcquire(1); }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"public-void-unlock\"><a href=\"#public-void-unlock\" class=\"headerlink\" title=\"public void unlock()\"></a>public void unlock()</h3><ul>\n<li>释放锁操作,参考AQS <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void unlock() { release(1); }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"public-boolean-isLocked\"><a href=\"#public-boolean-isLocked\" class=\"headerlink\" title=\"public boolean isLocked()\"></a>public boolean isLocked()</h3><ul>\n<li>是否处于锁定状态,调用isHeldExclusively方法,也就是看state是否为0 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean isLocked() { return isHeldExclusively(); }</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"void-interruptIfStarted\"><a href=\"#void-interruptIfStarted\" class=\"headerlink\" title=\"void interruptIfStarted()\"></a>void interruptIfStarted()</h3><ul>\n<li>如果处于运行状态,则进行中断。state>=0代表可以进行中断。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">void interruptIfStarted() {</span><br><span class=\"line\"> Thread t;</span><br><span class=\"line\"> if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> t.interrupt();</span><br><span class=\"line\"> } catch (SecurityException ignore) {</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"关联方法\"><a href=\"#关联方法\" class=\"headerlink\" title=\"关联方法\"></a>关联方法</h2><h3 id=\"runWorker\"><a href=\"#runWorker\" class=\"headerlink\" title=\"runWorker\"></a>runWorker</h3><ul>\n<li>Worker直接调用线程池的runWorker方法,将自身作为参数,执行任务,具体如下: <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final void runWorker(Worker w) {</span><br><span class=\"line\"> //当前工作线程</span><br><span class=\"line\"> Thread wt = Thread.currentThread();</span><br><span class=\"line\"> //获取待执行的初始任务</span><br><span class=\"line\"> Runnable task = w.firstTask;</span><br><span class=\"line\"> //清除firstTask</span><br><span class=\"line\"> w.firstTask = null;</span><br><span class=\"line\"> //释放w锁(这个时候可以进行中断操作)</span><br><span class=\"line\"> w.unlock(); // allow interrupts</span><br><span class=\"line\"> boolean completedAbruptly = true;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> //开始循环获取任务</span><br><span class=\"line\"> while (task != null || (task = getTask()) != null) {</span><br><span class=\"line\"> //获取任务之后先进行加锁</span><br><span class=\"line\"> w.lock();</span><br><span class=\"line\"> //检查线程池状态,是否需要中断。</span><br><span class=\"line\"> //这块逻辑比较绕,整理一下</span><br><span class=\"line\"> // 首先wt也就是当前线程,不能被中断。</span><br><span class=\"line\"> // 如果线程池的状态为STOP,TIDYING,TERMINATED 则直接中断</span><br><span class=\"line\"> // (剩下的就是原文中的注释了)如果线程池正在停止过程中,确保线程是中断的。否则就确保线程不会被中断。</span><br><span class=\"line\"> if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(),STOP))) && !wt.isInterrupted())</span><br><span class=\"line\"> wt.interrupt();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> beforeExecute(wt, task);//这里实际上是空实现</span><br><span class=\"line\"> Throwable thrown = null;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> task.run();//实际执行</span><br><span class=\"line\"> } catch (RuntimeException x) {</span><br><span class=\"line\"> thrown = x; throw x;</span><br><span class=\"line\"> } catch (Error x) {</span><br><span class=\"line\"> thrown = x; throw x;</span><br><span class=\"line\"> } catch (Throwable x) {</span><br><span class=\"line\"> thrown = x; throw new Error(x);</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> afterExecute(task, thrown);//这里也是空实现</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> task = null;</span><br><span class=\"line\"> w.completedTasks++;//执行完成后,完成任务+1</span><br><span class=\"line\"> w.unlock();//执行完成后释放锁</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> completedAbruptly = false;</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> //清理工作,执行到这里代表Worker已经准备销毁了</span><br><span class=\"line\"> processWorkerExit(w, completedAbruptly);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"getTask\"><a href=\"#getTask\" class=\"headerlink\" title=\"getTask\"></a>getTask</h2><ul>\n<li>获取任务的方法 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private Runnable getTask() {</span><br><span class=\"line\"> boolean timedOut = false; // Did the last poll() time out?</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> //先检查线程池的状态</span><br><span class=\"line\"> int c = ctl.get();</span><br><span class=\"line\"> int rs = runStateOf(c);</span><br><span class=\"line\"> //如果已经关闭并且队列为空则返回null,并减少一个工作线程</span><br><span class=\"line\"> //如果已经为STOP,TIDYING,TERMINATED 则减少一个工作线程</span><br><span class=\"line\"> if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {</span><br><span class=\"line\"> decrementWorkerCount();</span><br><span class=\"line\"> return null;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //获取工作线程数量</span><br><span class=\"line\"> int wc = workerCountOf(c);</span><br><span class=\"line\"> //判断是否需要清理Worker</span><br><span class=\"line\"> // 一种是allowCoreThreadTimeOut=true的情况</span><br><span class=\"line\"> //一种是工作线程数量已经超过核心线程数量了</span><br><span class=\"line\"> boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;</span><br><span class=\"line\"> //状态检查</span><br><span class=\"line\"> //满足以下两个条件,则会减少工作线程数量</span><br><span class=\"line\"> //1.已经超时或者工作线程数量超过最大线程数量的</span><br><span class=\"line\"> //2.至少有一个工作线程或者任务队列为空</span><br><span class=\"line\"> if ((wc > maximumPoolSize || (timed && timedOut))</span><br><span class=\"line\"> && (wc > 1 || workQueue.isEmpty())) {</span><br><span class=\"line\"> if (compareAndDecrementWorkerCount(c))</span><br><span class=\"line\"> return null;</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> //</span><br><span class=\"line\"> Runnable r = timed ?</span><br><span class=\"line\"> workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) ://带有超时的方式获取,超时之后返回null</span><br><span class=\"line\"> workQueue.take();//阻塞方式获取</span><br><span class=\"line\"> //没有超时则返回结果,否则设置超时状态</span><br><span class=\"line\"> if (r != null)</span><br><span class=\"line\"> return r;</span><br><span class=\"line\"> timedOut = true;</span><br><span class=\"line\"> } catch (InterruptedException retry) {</span><br><span class=\"line\"> timedOut = false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"processWorkerExit\"><a href=\"#processWorkerExit\" class=\"headerlink\" title=\"processWorkerExit\"></a>processWorkerExit</h2><ul>\n<li>做一些收尾工作 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void processWorkerExit(Worker w, boolean completedAbruptly) {</span><br><span class=\"line\"> if (completedAbruptly) // 还记得runWork方法中的completedAbruptly么,就是这个了,为true代表没有执行的改变为false,突然执行到这里了。</span><br><span class=\"line\"> decrementWorkerCount();//减少工作线程数量</span><br><span class=\"line\"></span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> completedTaskCount += w.completedTasks;//更新一下总共完成的任务</span><br><span class=\"line\"> workers.remove(w);//从Worker集合中中移除自己</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //尝试设置线程池为TERMINATED,见线程池部分分析</span><br><span class=\"line\"> //线程池为SHUTDOWN且队列为空或者线程池状态为STOP,则触发设置线程池为TERMINATED</span><br><span class=\"line\"> tryTerminate();</span><br><span class=\"line\"></span><br><span class=\"line\"> int c = ctl.get();//获取当前线程池状态</span><br><span class=\"line\"> if (runStateLessThan(c, STOP)) {//是否已经停止或者终止</span><br><span class=\"line\"> //是否突然过来的,如果不是突然过来的,代表正常结束</span><br><span class=\"line\"> if (!completedAbruptly) {</span><br><span class=\"line\"> //根据allowCoreThreadTimeOut获取线程池数量最小值</span><br><span class=\"line\"> int min = allowCoreThreadTimeOut ? 0 : corePoolSize;</span><br><span class=\"line\"> //队列不为空则最小值不能为0</span><br><span class=\"line\"> if (min == 0 && ! workQueue.isEmpty())</span><br><span class=\"line\"> min = 1;</span><br><span class=\"line\"> //当前工作线程数量大于等于最小值,则代表还不能结束,继续执行</span><br><span class=\"line\"> if (workerCountOf(c) >= min)</span><br><span class=\"line\"> return; // replacement not needed</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //执行到这里说明当前线程数量小于min的值,需要添加一个Worker</span><br><span class=\"line\"> //或者突然执行过来的,可能有异常,添加一个Worker</span><br><span class=\"line\"> addWorker(null, false);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n","categories":["Java"],"tags":["Java","ThreadPool","多线程","Worker"]},{"title":"Java线程池分析-ThreadPoolExecutor","url":"/2019/07/06/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%88%86%E6%9E%90-ThreadPoolExecutor/","content":"<h1 id=\"线程池状态\"><a href=\"#线程池状态\" class=\"headerlink\" title=\"线程池状态\"></a>线程池状态</h1><h2 id=\"状态\"><a href=\"#状态\" class=\"headerlink\" title=\"状态\"></a>状态</h2><ul>\n<li>RUNNING<ul>\n<li>该状态接受新的任务同时处理队列中的任务</li>\n</ul>\n</li>\n<li>SHUTDOWN<ul>\n<li>该状态不再接受新的任务,但是队列中的任务继续执行</li>\n</ul>\n</li>\n<li>STOP<ul>\n<li>不再接受新的任务,也不再处理队列中的任务,并且会中断正在运行的任务</li>\n</ul>\n</li>\n<li>TIDYING<ul>\n<li>所有任务都已经终止,工作线程数为0。调用terminated()方法状态会变为TIDYING</li>\n</ul>\n</li>\n<li>TERMINATED<ul>\n<li>terminated()方法执行完成</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"状态转移\"><a href=\"#状态转移\" class=\"headerlink\" title=\"状态转移\"></a>状态转移</h2><ul>\n<li>RUNNING -> SHUTDOWN<ul>\n<li>执行shutdown()方法(SHUTDOWN状态可能立即结束进入下一状态)</li>\n</ul>\n</li>\n<li>(RUNNING or SHUTDOWN) -> STOP<ul>\n<li>执行shutdownNow()方法</li>\n</ul>\n</li>\n<li>SHUTDOWN -> TIDYING<ul>\n<li>当所有任务队列和线程池(pool)都空的了时候</li>\n</ul>\n</li>\n<li>STOP -> TIDYING<ul>\n<li>当线程池(pool)为空的时候</li>\n</ul>\n</li>\n<li>TIDYING -> TERMINATED<ul>\n<li>terminated()方法完成<br>awaitTermination()在状态变为TERMINATED的时候返回</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"状态位表示\"><a href=\"#状态位表示\" class=\"headerlink\" title=\"状态位表示\"></a>状态位表示</h2><p>状态位在线程池中使用一个原子类型的Integer进行存储</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));</span><br></pre></td></tr></table></figure>\n<p>Integer的长度是32位,用全部32位来表示线程数量,有点浪费。线程池的状态一共就5种,所以大神决定用ctl的高3位(可以表示8种状态了),来表示线程池的状态,低29位用来计数(大约500_000_000),反正就目前机器来说,想同时开启这么多线程。。。机器早就挂了,所以足够了</p>\n<h1 id=\"几个底层依赖的方法\"><a href=\"#几个底层依赖的方法\" class=\"headerlink\" title=\"几个底层依赖的方法\"></a>几个底层依赖的方法</h1><ul>\n<li>private static int runStateOf(int c) { return c & ~CAPACITY; }<ul>\n<li>通过位运算,获取当前线程池的状态</li>\n</ul>\n</li>\n<li>private static int workerCountOf(int c) { return c & CAPACITY; }<ul>\n<li>通过位运算,获取当前线程池的工作线程数</li>\n</ul>\n</li>\n<li>private static int ctlOf(int rs, int wc) { return rs | wc; }<ul>\n<li>为了看ctl的状态</li>\n</ul>\n</li>\n<li>然后就是几个关于ctl的cas操作,包括增加一个线程计数,减少一个线程计数</li>\n</ul>\n<h1 id=\"几个重要的Field\"><a href=\"#几个重要的Field\" class=\"headerlink\" title=\"几个重要的Field\"></a>几个重要的Field</h1><ul>\n<li>private final BlockingQueue<Runnable> workQueue;<ul>\n<li>关键参数,设置线程池corePoolSize满了以后,要将任务放到什么样的阻塞队列中。</li>\n</ul>\n</li>\n<li>private final ReentrantLock mainLock = new ReentrantLock();<ul>\n<li>内部锁,在很多方法中均有用到,包括添加Worker,中断Worker,shutdown,以及获取各种size都需要锁进行同步保护</li>\n</ul>\n</li>\n<li>private final HashSet<Worker> workers = new HashSet<Worker>();<ul>\n<li>可以理解为线程的集合,Worker 继承了 AQS 并且实现了 Runnable,也就是线程池中对应的线程的集合。</li>\n</ul>\n</li>\n<li>private final Condition termination = mainLock.newCondition();<ul>\n<li>在 tryTerminate() 方法中进行通知,awaitTermination(long timeout, TimeUnit unit)方法中进行awaitNanos,也就是说调用awaitTermination方法之后,会一直等待,直到tryTerminate方法执行并且通知,才会结束(这时候线程池应该就变为TERMINATED状态了)</li>\n</ul>\n</li>\n<li>private int largestPoolSize;<ul>\n<li>记录线程池中线程数量曾经达到过的最大值。</li>\n</ul>\n</li>\n<li>private long completedTaskCount;<ul>\n<li>这个从字面意思就很好立即了,已经完成的任务数量</li>\n</ul>\n</li>\n<li>private volatile ThreadFactory threadFactory;<ul>\n<li>又一个核心参数,线程的创建工厂,各种大厂的Java开发规范都需要业务自己实现对应的线程工厂,定义线程的名称之类的,主要是后期排查多线程问题的时候方便定位。</li>\n</ul>\n</li>\n<li>private volatile RejectedExecutionHandler handler;<ul>\n<li>也是核心参数,当线程池达到饱和状态(队列满了,maximumPoolSize也达到了),如果还在继续提交的任务,就依靠这个进行处理。</li>\n<li>默认有4个实现类,如下:<ul>\n<li>CallerRunsPolicy,这个就是将任务返回给调用方,由调用方执行。</li>\n<li>AbortPolicy,这个比较粗暴,直接抛出异常。也是默认实现,参考defaultHandler</li>\n<li>DiscardPolicy,这个应该很少用,直接丢弃提交的任务,Do Nothing</li>\n<li>DiscardOldestPolicy,这个也是丢弃,不过是丢弃队列中最早的一个(直接调用peeK)</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>private volatile long keepAliveTime;<ul>\n<li>核心参数之一,线程空闲多久后回收(默认如果小于corePoolSize,则不进行回收)</li>\n</ul>\n</li>\n<li>private volatile boolean allowCoreThreadTimeOut;<ul>\n<li>allowCoreThreadTimeOut,core线程是否超时后回收,默认是false</li>\n</ul>\n</li>\n<li>private volatile int corePoolSize;<ul>\n<li>核心参数,core线程池大小</li>\n</ul>\n</li>\n<li>private volatile int maximumPoolSize;<ul>\n<li>核心参数,最大线程池大小(超过这个值就会调用RejectedExecutionHandler)</li>\n</ul>\n</li>\n<li>private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();<ul>\n<li>默认Rejected处理策略,抛出异常</li>\n</ul>\n</li>\n<li>private static final RuntimePermission shutdownPerm = new RuntimePermission(“modifyThread”);<ul>\n<li>这个真不太清楚,只知道是在调用shutdown相关方法会调用进行安全检查</li>\n</ul>\n</li>\n<li>private final AccessControlContext acc;<ul>\n<li>在finalize方法中有调用,看不太明白 = =,貌似也是权限访问层面的(AccessController.doPrivileged(pa, acc);)</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"几个重要的-public-方法\"><a href=\"#几个重要的-public-方法\" class=\"headerlink\" title=\"几个重要的 public 方法\"></a>几个重要的 public 方法</h1><ul>\n<li><p>public void execute(Runnable command)</p>\n<ul>\n<li>提交一个Runnable任务,描述很简单,实现应该是所有方法中最复杂的了,话不多说,直接上源码。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void execute(Runnable command) {</span><br><span class=\"line\"> if (command == null)</span><br><span class=\"line\"> throw new NullPointerException();//不允许提交null</span><br><span class=\"line\"> int c = ctl.get();</span><br><span class=\"line\"> //检查目前工作线程数量是否超过了corePoolSize,没有超过的话直接添加任务,添加成功就返回,添加失败则更新状态值c然后继续</span><br><span class=\"line\"> if (workerCountOf(c) < corePoolSize) {</span><br><span class=\"line\"> if (addWorker(command, true))//参数为true则添加的是core线程</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> c = ctl.get();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //执行到这里肯定是添加worker失败或者已经达到了corePoolSize</span><br><span class=\"line\"> //这时候检查线程状态,确保是Running(因为有可能这期间其他线程调用了shutdown等方法),就开始往队列中添加任务。</span><br><span class=\"line\"> if (isRunning(c) && workQueue.offer(command)) {</span><br><span class=\"line\"> int recheck = ctl.get();</span><br><span class=\"line\"> //添加到队列之后,再次检查线程池状态,如果状态发生变化,则移除任务并执行拒绝策略</span><br><span class=\"line\"> //如果状态没有发生改变,此时如果线程池为空,那就添加一个非核心Worker</span><br><span class=\"line\"> if (! isRunning(recheck) && remove(command))</span><br><span class=\"line\"> reject(command);</span><br><span class=\"line\"> else if (workerCountOf(recheck) == 0)</span><br><span class=\"line\"> addWorker(null, false);</span><br><span class=\"line\"> }//到这里说明添加队列失败,要么是线程池编程非RUNNING状态,要么队列满了,则添加非核心线程,非核心线程如果添加还是失败了,就只能执行拒绝策略了</span><br><span class=\"line\"> else if (!addWorker(command, false))</span><br><span class=\"line\"> reject(command);</span><br><span class=\"line\">}</span><br><span class=\"line\">//上面的步骤还不算复杂,接下就是最复杂的addWorker方法了。</span><br><span class=\"line\">private boolean addWorker(Runnable firstTask, boolean core) {</span><br><span class=\"line\"> retry: //标记位,用于循环控制,也就是外层循环</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> int c = ctl.get();</span><br><span class=\"line\"> int rs = runStateOf(c);//当前线程池状态</span><br><span class=\"line\"> //如果线程池状态如下,则返回添加失败</span><br><span class=\"line\"> // 1. 线程池状态为STOP,TIDYING,TERMINATED中的一个</span><br><span class=\"line\"> // 2. 线程池状态为SHUTDOWN,并且第一个任务不为空</span><br><span class=\"line\"> // 3. 线程池状态为SHUTDOWN,并且工作队列为空</span><br><span class=\"line\"> if (rs >= SHUTDOWN &&</span><br><span class=\"line\"> ! (rs == SHUTDOWN &&</span><br><span class=\"line\"> firstTask == null &&</span><br><span class=\"line\"> ! workQueue.isEmpty()))</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> //开始内层循环,执行到这里说明不是上述123中的状态。</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> int wc = workerCountOf(c);//获取工作线程数量</span><br><span class=\"line\"> //检查是否达到上限,并根据添加的线程类别(core或者非core)判断是否超过对应的最大值,超过也返回false</span><br><span class=\"line\"> if (wc >= CAPACITY ||</span><br><span class=\"line\"> wc >= (core ? corePoolSize : maximumPoolSize))</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> if (compareAndIncrementWorkerCount(c))//上述校验通过以后,CAS方式增加一个工作线程,如果成功了,则跳出外层循环</span><br><span class=\"line\"> break retry;</span><br><span class=\"line\"> //执行到这里说明cas方式增加线程失败,那就重新检查一下线程池状态,然后内层循环继续,直到增加成功。</span><br><span class=\"line\"> c = ctl.get(); // Re-read ctl</span><br><span class=\"line\"> if (runStateOf(c) != rs)</span><br><span class=\"line\"> continue retry;</span><br><span class=\"line\"> // else CAS failed due to workerCount change; retry inner loop</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //执行到这里,说明已经成功增加了一个线程计数了。</span><br><span class=\"line\"> boolean workerStarted = false;</span><br><span class=\"line\"> boolean workerAdded = false;</span><br><span class=\"line\"> Worker w = null;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> w = new Worker(firstTask);//使用第一个任务创建一个Worker</span><br><span class=\"line\"> final Thread t = w.thread;//获取对应worker的线程</span><br><span class=\"line\"> if (t != null) {</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> // Recheck while holding lock.</span><br><span class=\"line\"> // Back out on ThreadFactory failure or if</span><br><span class=\"line\"> // shut down before lock acquired.</span><br><span class=\"line\"> int rs = runStateOf(ctl.get());//检查线程池的状态</span><br><span class=\"line\"> //线程池状态是运行的或者刚刚关闭,第一个任务尚未赋值</span><br><span class=\"line\"> if (rs < SHUTDOWN ||</span><br><span class=\"line\"> (rs == SHUTDOWN && firstTask == null)) {</span><br><span class=\"line\"> //这里确保线程还没有被其他线程启动</span><br><span class=\"line\"> if (t.isAlive()) // precheck that t is startable</span><br><span class=\"line\"> throw new IllegalThreadStateException();</span><br><span class=\"line\"> //添加worker到workers集合中</span><br><span class=\"line\"> workers.add(w);</span><br><span class=\"line\"> int s = workers.size();</span><br><span class=\"line\"> //对比并更新最大到达数量</span><br><span class=\"line\"> if (s > largestPoolSize)</span><br><span class=\"line\"> largestPoolSize = s;</span><br><span class=\"line\"> workerAdded = true;//表示已经增加了worker</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //如果已经成功增加了worker,就可以启动对应的线程了。</span><br><span class=\"line\"> if (workerAdded) {</span><br><span class=\"line\"> t.start();</span><br><span class=\"line\"> workerStarted = true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> //这里检查一下worker是否已经启动成功,如果没有启动成功,则执行添加失败操作</span><br><span class=\"line\"> if (! workerStarted)</span><br><span class=\"line\"> addWorkerFailed(w);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return workerStarted;//返回工作线程是否启动成功</span><br><span class=\"line\">}</span><br><span class=\"line\">//简单看一下添加失败的逻辑</span><br><span class=\"line\">private void addWorkerFailed(Worker w) {</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> if (w != null)</span><br><span class=\"line\"> workers.remove(w);//添加失败则移除掉Set集合中对应的worker</span><br><span class=\"line\"> //并且减少一个工作线程数量计数</span><br><span class=\"line\"> decrementWorkerCount();</span><br><span class=\"line\"> //尝试关闭线程池(感觉这里是为了释放空闲线程)</span><br><span class=\"line\"> tryTerminate();</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public void shutdown() </p>\n<ul>\n<li>尝试关闭线程池,执行后状态变为SHUTDOWN,继续完成已经提交的任务,但是新的任务不再被接受。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void shutdown() {</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> checkShutdownAccess();//检查访问权?(这块有点懵)</span><br><span class=\"line\"> advanceRunState(SHUTDOWN);//检查是否可以设置为SHUTDOWN并通过CAS操作设置为SHUTDOWN</span><br><span class=\"line\"> interruptIdleWorkers();//中断线程</span><br><span class=\"line\"> onShutdown(); // hook for ScheduledThreadPoolExecutor ,这里是空实现</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> tryTerminate();//检测并尝试终止线程池</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>几个内部调用的方法方法 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> private void advanceRunState(int targetState) {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> int c = ctl.get();</span><br><span class=\"line\"> //传参数为SHUTDOWN,则runStateAtLeast在SHUTDOWN、STOP、TIDYING和TERMINATED状态的时候为true,直接跳出循环。如果线程池状态为RUNNING,则进行CAS操作,更新状态位为SHUTDOWN,成功则结束。</span><br><span class=\"line\"> if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> private void interruptIdleWorkers() {</span><br><span class=\"line\"> interruptIdleWorkers(false);//中断所有线程,如果参数为true,则仅中断一个线程,具体见下</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> private void interruptIdleWorkers(boolean onlyOne) {</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (Worker w : workers) {</span><br><span class=\"line\"> Thread t = w.thread;</span><br><span class=\"line\"> //线程处于非中断状态,并且没有其他线程中断该线程(也就是说只能有一个线程进行中断操作)</span><br><span class=\"line\"> if (!t.isInterrupted() && w.tryLock()) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> t.interrupt();//依次中断</span><br><span class=\"line\"> } catch (SecurityException ignore) {</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> w.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (onlyOne)//如果为true,则中断一个后跳出循环</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //一个空实现</span><br><span class=\"line\"> void onShutdown() {</span><br><span class=\"line\"> </span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\">final void tryTerminate() {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> int c = ctl.get();</span><br><span class=\"line\"> if (isRunning(c) ||//正在运行中,不能设置为TIDYING</span><br><span class=\"line\"> runStateAtLeast(c, TIDYING) ||//线程池为TIDYING或者TERMINATED,其他线程已经开始关闭线程池</span><br><span class=\"line\"> (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))//队列中尚有需要执行的任务,需要等待执行完成,不能设置为TIDYING</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> //上面一坨条件判断说白了就是在等线程池状态为SHUTDOWN并且队列为空或者线程池状态为STOP才会继续,否则放弃终止操作。</span><br><span class=\"line\"></span><br><span class=\"line\"> if (workerCountOf(c) != 0) { // Eligible to terminate</span><br><span class=\"line\"> interruptIdleWorkers(ONLY_ONE);//ONLY_ONE在这里为true,也就是仅仅中断一个空闲的worker。</span><br><span class=\"line\"> //补充一下:interruptIdleWorkers的作用是因为在getTask方法中执行workQueue.take()时,如果不执行中断会一直阻塞。在shutdown方法中,会中断所有空闲的工作线程,如果在执行shutdown时工作线程没有空闲,然后又去调用了getTask方法,这时如果workQueue中没有任务了,调用workQueue.take()时就会一直阻塞。所以每次在工作线程结束时调用tryTerminate方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况。(引用自:https://www.cnblogs.com/liuzhihu/p/8177371.html)</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {//CAS操作将线程池设置为TIDYING状态</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> terminated();//执行一些清理操作</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> ctl.set(ctlOf(TERMINATED, 0));//设置线程池状态为TERMINATED</span><br><span class=\"line\"> termination.signalAll();//通知条件变量termination(也就是awaitTermination方法可以继续执行了)</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> // else retry on failed CAS</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public List<Runnable> shutdownNow()</p>\n<ul>\n<li>立即关闭线程池,设置状态为STOP,不再接受新的任务,并且中断正在运行的任务,返回队列中未执行的任务。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public List<Runnable> shutdownNow() {</span><br><span class=\"line\"> List<Runnable> tasks;</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> checkShutdownAccess();//检查访问权?(这块有点懵)</span><br><span class=\"line\"> advanceRunState(STOP);//cas方式设置状态为STOP</span><br><span class=\"line\"> interruptWorkers();//中断正在运行的线程</span><br><span class=\"line\"> tasks = drainQueue();//将队列中未执行的任务返回</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> tryTerminate();//检测并尝试终止线程池</span><br><span class=\"line\"> return tasks;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>看一下里面具体的方法,checkShutdownAccess、advanceRunState和tryTerminate见上 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void interruptWorkers() {</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (Worker w : workers)//遍历workers集合</span><br><span class=\"line\"> w.interruptIfStarted();//依次中断</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\">Worker的interruptIfStarted方法如下:</span><br><span class=\"line\">void interruptIfStarted() {</span><br><span class=\"line\"> Thread t;</span><br><span class=\"line\"> //state小于0是不可中断的标识,只能在大于等于0的时候进行中断</span><br><span class=\"line\"> if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> t.interrupt();</span><br><span class=\"line\"> } catch (SecurityException ignore) {</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\">//转移队列剩余任务方法</span><br><span class=\"line\">private List<Runnable> drainQueue() {</span><br><span class=\"line\"> BlockingQueue<Runnable> q = workQueue;</span><br><span class=\"line\"> ArrayList<Runnable> taskList = new ArrayList<Runnable>();</span><br><span class=\"line\"> q.drainTo(taskList);//将q的所有任务转移到taskList中</span><br><span class=\"line\"> if (!q.isEmpty()) {//上一步操作可能会失败,再次检查(什么情况会失败?这块我也没太明白)</span><br><span class=\"line\"> for (Runnable r : q.toArray(new Runnable[0])) {</span><br><span class=\"line\"> if (q.remove(r))</span><br><span class=\"line\"> taskList.add(r);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return taskList;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public boolean isShutdown()</p>\n<ul>\n<li>通过ctl获取线程池的状态,没啥可说的</li>\n</ul>\n</li>\n<li><p>public boolean isTerminating()</p>\n<ul>\n<li>通过ctl获取线程池的状态,只要不是运行的,不是TERMINATED,就处于这个状态</li>\n</ul>\n</li>\n<li><p>public boolean isTerminated()</p>\n<ul>\n<li>通过ctl获取线程池的状态,并且状态是TERMINATED</li>\n</ul>\n</li>\n<li><p>public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException</p>\n<ul>\n<li>带超时的等待线程池状态变为TERMINATED <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean awaitTermination(long timeout, TimeUnit unit)</span><br><span class=\"line\"> throws InterruptedException {</span><br><span class=\"line\"> long nanos = unit.toNanos(timeout);</span><br><span class=\"line\"> final ReentrantLock mainLock = this.mainLock;</span><br><span class=\"line\"> mainLock.lock();</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> for (;;) {</span><br><span class=\"line\"> if (runStateAtLeast(ctl.get(), TERMINATED))//CAS循环判断状态是否为TERMINATED</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> if (nanos <= 0)//超时则返回false</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> nanos = termination.awaitNanos(nanos);//还记得上面的termination.signalAll()吧,就是这里等待通知</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } finally {</span><br><span class=\"line\"> mainLock.unlock();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public boolean prestartCoreThread()</p>\n<ul>\n<li>调用该方法,则执行启动一个核心线程,源码比较简单,只要不足corePoolSize,就执行增加操作。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean prestartCoreThread() {</span><br><span class=\"line\"> return workerCountOf(ctl.get()) < corePoolSize &&</span><br><span class=\"line\"> addWorker(null, true);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public int prestartAllCoreThreads()</p>\n<ul>\n<li>一次性启动Core工作线程到corePoolSize,返回启动了几个线程。 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public int prestartAllCoreThreads() {</span><br><span class=\"line\"> int n = 0;</span><br><span class=\"line\"> while (addWorker(null, true))</span><br><span class=\"line\"> ++n;</span><br><span class=\"line\"> return n;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public boolean remove(Runnable task)</p>\n<ul>\n<li>移除一个task,移除操作之后会执行tryTerminate()方法 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean remove(Runnable task) {</span><br><span class=\"line\"> boolean removed = workQueue.remove(task);</span><br><span class=\"line\"> //会检查线程池状态,并根据状态决定是否终止线程池(特别针对的场景就是shudown状态+空队列,就可以进入下一个状态)</span><br><span class=\"line\"> tryTerminate(); // In case SHUTDOWN and now empty</span><br><span class=\"line\"> return removed;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n<li><p>public void purge() </p>\n<ul>\n<li>尝试从队列中移除所有已经取消了的Future任务 <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void purge() {</span><br><span class=\"line\"> final BlockingQueue<Runnable> q = workQueue;</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> Iterator<Runnable> it = q.iterator();</span><br><span class=\"line\"> while (it.hasNext()) {</span><br><span class=\"line\"> Runnable r = it.next();</span><br><span class=\"line\"> //移除掉已经取消的Future任务</span><br><span class=\"line\"> if (r instanceof Future<?> && ((Future<?>)r).isCancelled())</span><br><span class=\"line\"> it.remove();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (ConcurrentModificationException fallThrough) {//遍历过程中发生了其他修改,快速失败采用数组快照方式删除</span><br><span class=\"line\"> // Take slow path if we encounter interference during traversal.</span><br><span class=\"line\"> // Make copy for traversal and call remove for cancelled entries.</span><br><span class=\"line\"> // The slow path is more likely to be O(N*N).</span><br><span class=\"line\"> for (Object r : q.toArray())</span><br><span class=\"line\"> if (r instanceof Future<?> && ((Future<?>)r).isCancelled())</span><br><span class=\"line\"> q.remove(r);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //会检查线程池状态,并根据状态决定是否终止线程池(特别针对的场景就是shudown状态+空队列,就可以进入下一个状态)</span><br><span class=\"line\"> tryTerminate(); // In case SHUTDOWN and now empty </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n</li>\n</ul>\n<h1 id=\"待分析内容\"><a href=\"#待分析内容\" class=\"headerlink\" title=\"待分析内容\"></a>待分析内容</h1><h2 id=\"简单概述\"><a href=\"#简单概述\" class=\"headerlink\" title=\"简单概述\"></a>简单概述</h2><ul>\n<li>上面这一堆只是简单分析了一下ThreadPoolExecutor内部的方法,这个类继承了AbstractExecutorService,之后会在分析一下AbstractExecutorService,此外ThreadPoolExecutor内部还有一个Worker类,它承了AbstractQueuedSynchronizer(AQS),这两个也是后面要分析的重点。尤其是AQS,后面的各种锁相关都是依赖于它</li>\n</ul>\n<h2 id=\"TODO项\"><a href=\"#TODO项\" class=\"headerlink\" title=\"TODO项\"></a>TODO项</h2><ul>\n<li>AbstractExecutorService</li>\n<li>Worker</li>\n<li>AbstractQueuedSynchronizer</li>\n</ul>\n","categories":["Java"],"tags":["Java","ThreadPool","多线程","ThreadPoolExecutor"]},{"title":"Java 集合类整理","url":"/2019/06/30/Java%E9%9B%86%E5%90%88%E7%B1%BB/","content":"<h1 id=\"起因\"><a href=\"#起因\" class=\"headerlink\" title=\"起因\"></a>起因</h1><p>好像Java面试必不可少的一个问题就是,Java中集合有哪些?分别有什么特点。照搬各种《XXX从入门到放弃》,集合有2种类型,一个是有序可重复的List,一个是无序不可重复的Set,可是真的用起来的时候好像就不是简单的这样了。 </p>\n<p>先来看一下集合的整体关系图(并发包中的集合没有考虑进来,仅看java.util包)<br><img src=\"/Java%E9%9B%86%E5%90%88%E7%B1%BB/Collection.png\" alt=\"Java集合类\"> </p>\n<p>第一眼看上去我也懵,本来以为自己天天用的那些集合类已经差不多了,然后发现一坨坨的没见过没用过的。</p>\n<h1 id=\"那就啃吧。。\"><a href=\"#那就啃吧。。\" class=\"headerlink\" title=\"那就啃吧。。\"></a>那就啃吧。。</h1><h2 id=\"Collection\"><a href=\"#Collection\" class=\"headerlink\" title=\"Collection\"></a>Collection</h2><ul>\n<li>Collection应该是老大哥级别的了,算是集合类的鼻祖(迭代器忽略),定义了一个集合应该有的基本方法,包括增删迭代等,这个就不多说了。</li>\n<li>1.8版本开始,增加了流式操作的几个default方法(先不关注了)</li>\n</ul>\n<h2 id=\"List\"><a href=\"#List\" class=\"headerlink\" title=\"List\"></a>List</h2><p>List应该是集合中用的最多最多的了,平时搬砖,基本上几行代码就要加上一个List存储各种元素。忽略抽象方法,整理一下对应的实现类</p>\n<h3 id=\"ArrayList\"><a href=\"#ArrayList\" class=\"headerlink\" title=\"ArrayList\"></a>ArrayList</h3><p>ArrayList应该是日常搬砖使用最多的的实现了,特点如下:</p>\n<ul>\n<li>底层实现是数组,对应代码:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">transient Object[] elementData;</span><br></pre></td></tr></table></figure></li>\n<li>实现了动态扩容方法,简单说就是容量不够了,我就新建一个数组,然后将原来的数据拷贝到新数组中。从代码int newCapacity = oldCapacity + (oldCapacity >> 1);中也可以看出来,每次扩容变为原本的1.5倍。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void grow(int minCapacity) {</span><br><span class=\"line\"> // overflow-conscious code</span><br><span class=\"line\"> int oldCapacity = elementData.length;</span><br><span class=\"line\"> int newCapacity = oldCapacity + (oldCapacity >> 1);</span><br><span class=\"line\"> if (newCapacity - minCapacity < 0)</span><br><span class=\"line\"> newCapacity = minCapacity;</span><br><span class=\"line\"> if (newCapacity - MAX_ARRAY_SIZE > 0)</span><br><span class=\"line\"> newCapacity = hugeCapacity(minCapacity);</span><br><span class=\"line\"> // minCapacity is usually close to size, so this is a win:</span><br><span class=\"line\"> elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>线程不安全,所有方法没有加锁,多线程存在并发问题</li>\n<li>因为底层实现是数组,所以随机读取速度很快,并不是说不适合插入数据,而是不适合在中间或者头部插入数据。因为插入数据之后,当前位置后面的元素都要往后移动,成本相对来说比较大了。代码如下:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">System.arraycopy(elementData, index, elementData, index + 1, size - index);</span><br><span class=\"line\">elementData[index] = element;</span><br></pre></td></tr></table></figure></li>\n<li>删除操作也是同理,在末尾操作其实影响不大,但是在中间和数组起始位置操作成本就有点高了。</li>\n<li>更新操作影响很小,直接找到对应数组下标,然后替换就可以了。</li>\n<li>indexOf和contains以及lastIndexOf方法都是直接进行遍历,因为数组是无序的,也没办法采用二分法之类的进行快速查找。所以尽可能不要直接使用List的查找方法。</li>\n<li>size方法成本不高,并不是每次都进行统计,而是内部存储了一个size变量,每次增删操作回进行更新。</li>\n<li>暂时想到的就这么多,以后想起来再补充。</li>\n</ul>\n<h3 id=\"Vector\"><a href=\"#Vector\" class=\"headerlink\" title=\"Vector\"></a>Vector</h3><p>这个可是个老古董了,从JDK1.0开始就存在了(别问我怎么知道的,那会我也没用过Java,是文档自己写的。。。。),正因为是老古董,所以现在已经不是很推荐使用了,原因就是效率很低下,因为很多方法都暴力的增加了synchronized关键字,性能很低下。做个简单总结吧:</p>\n<ul>\n<li>因为很多方法都加上了synchronized关键词,导致整体性能较差,不推荐使用</li>\n<li>底层实现也是数组,特性和ArrayList差不多。</li>\n<li>注意:Vector没有实现Serializable接口</li>\n<li>关于扩容:ArrayList每次扩容是1.5倍,Vector是2倍。代码如下:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private void grow(int minCapacity) {</span><br><span class=\"line\"> // overflow-conscious code</span><br><span class=\"line\"> int oldCapacity = elementData.length;</span><br><span class=\"line\"> int newCapacity = oldCapacity + ((capacityIncrement > 0) ?</span><br><span class=\"line\"> capacityIncrement : oldCapacity);//扩充一倍</span><br><span class=\"line\"> if (newCapacity - minCapacity < 0)</span><br><span class=\"line\"> newCapacity = minCapacity;</span><br><span class=\"line\"> if (newCapacity - MAX_ARRAY_SIZE > 0)</span><br><span class=\"line\"> newCapacity = hugeCapacity(minCapacity);</span><br><span class=\"line\"> elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\ncapacityIncrement是构造方法传进来的,如果不指定,则传0。</li>\n</ul>\n<h3 id=\"Stack\"><a href=\"#Stack\" class=\"headerlink\" title=\"Stack\"></a>Stack</h3><p>这个类已经快被遗忘了,简单概述一下。</p>\n<ul>\n<li>1.0时代的远古产物,继承了Vector,所以也是各种synchronized关键字,性能低下</li>\n<li>官方注释已经不推荐使用了,同样的功能可以使用Deque实现<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">Deque<Integer> stack = new ArrayDeque<Integer>();</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"LinkedList\"><a href=\"#LinkedList\" class=\"headerlink\" title=\"LinkedList\"></a>LinkedList</h3><p>你以为LinkedList仅仅是一个链表实现的List的么??那你就是图样图森破了,来看看强大的LinkedList吧。</p>\n<ul>\n<li>看一下接口层面:List、deque和Queue,也就是说LinkedList不仅仅是一个list集合,同时也是一个双向队列,当然也可以作为单向队列来使用。所以根据场景,上面的Stack也可以使用LinkedList进行替换</li>\n<li>底层的实现是基于链表,基本存储数据的元素是Node,代码如下:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">private static class Node<E> {</span><br><span class=\"line\"> E item;</span><br><span class=\"line\"> Node<E> next;</span><br><span class=\"line\"> Node<E> prev;</span><br><span class=\"line\"></span><br><span class=\"line\"> Node(Node<E> prev, E element, Node<E> next) {</span><br><span class=\"line\"> this.item = element;</span><br><span class=\"line\"> this.next = next;</span><br><span class=\"line\"> this.prev = prev;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure></li>\n<li>正是因为基于链表实现,所以理论上在任意位置进行增删操作都是O(1)的时间复杂度,但是为什么我说是理论上呢?因为实际进行增删的时候,必须要先找到对应的位置吧?这个查找的过程时间复杂度可就是O(n)了。</li>\n<li>LinkedList同样没有加任何同步措施,因此也是线程不安全的。</li>\n<li>说一个坑点:千万不要在for循环中通过索引的方式去获取元素(之前实习生干了这个事。。。),因为链表的<strong>通过索引方式进行随机读取的时间复杂度是O(n)</strong>!</li>\n</ul>\n<h3 id=\"简单汇总一下\"><a href=\"#简单汇总一下\" class=\"headerlink\" title=\"简单汇总一下\"></a>简单汇总一下</h3><table>\n<thead>\n<tr>\n<th>列</th>\n<th>ArrayList</th>\n<th>LinkedList</th>\n<th>Vector</th>\n<th>Stack</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>实现方式</td>\n<td>数组</td>\n<td>链表</td>\n<td>数组</td>\n<td>数组</td>\n</tr>\n<tr>\n<td>线程安全</td>\n<td>否</td>\n<td>否</td>\n<td>是</td>\n<td>是</td>\n</tr>\n<tr>\n<td>优势</td>\n<td>适合随机读,末尾写</td>\n<td>适合随机写,顺序读</td>\n<td>线程安全</td>\n<td>线程安全</td>\n</tr>\n<tr>\n<td>劣势</td>\n<td>不适合随机写入</td>\n<td>不适合随机读取</td>\n<td>性能差</td>\n<td>性能差</td>\n</tr>\n<tr>\n<td>扩容</td>\n<td>1.5</td>\n<td>-</td>\n<td>2</td>\n<td>-</td>\n</tr>\n</tbody></table>\n<h2 id=\"Set\"><a href=\"#Set\" class=\"headerlink\" title=\"Set\"></a>Set</h2><p>上面简单总结了一下List相关实现,接下来看看狐假虎威的Set。为啥说Set是狐假虎威呢?看看对应的实现类就明白了,基本上都是Map套了个壳(Map稍后总结)</p>\n<h3 id=\"HashSet\"><a href=\"#HashSet\" class=\"headerlink\" title=\"HashSet\"></a>HashSet</h3><p>基于Hash实现的Set,内层实现是HashMap,所有的操作都是HashMap的Key的操作,而Map的实际Value则是一个Object对象。总结一下特点:</p>\n<ul>\n<li>判断是否存在速度很快,基于Hash实现如果不出现冲突,基本都是O(1)的操作。</li>\n<li>线程不安全,需要自己实现线程同步方式</li>\n<li>元素唯一(前提重写HashCode和equals方法,只有两者都相同才被认为是同一个元素)</li>\n<li>不保证顺序,因为基于Hash实现,无法保证读取时候的顺序(也就是通过迭代器方式读取无法保证顺序,不过貌似重写HashCode之后,可以在一定程度上控制顺序,但是最好不要这么用。。)</li>\n<li>因为底层实现是HashSet,所以也存在初始容量和负载因子,因此使用的时候如果事先知道存储大小,最好指定一下大小和负载因子,减少扩容消耗。</li>\n<li>因为HashSet支持null作为key,所以HashSet也可以存储null元素</li>\n</ul>\n<h3 id=\"LinkedHashSet\"><a href=\"#LinkedHashSet\" class=\"headerlink\" title=\"LinkedHashSet\"></a>LinkedHashSet</h3><p>LinkedHashSet也是一个壳,继承了HashSet,注意一下HashSet内部还有一个带有boolean的构造方法,调用这种构造方法,则内部实现不再是HashMap,而是LinkedHashMap。</p>\n<ul>\n<li>LinkedHashSet和HashSet最大的区别就是能保证元素插入的顺序和通过迭代器读取的顺序是一致的(但是不是经过排序的,是保留插入的顺序)。</li>\n<li>除了有序这个之外,其他特点和HashSet一样,因为继承了嘛(写这个类的人,真的是懒到极致的。。向他学习!)</li>\n</ul>\n<h3 id=\"TreeSet\"><a href=\"#TreeSet\" class=\"headerlink\" title=\"TreeSet\"></a>TreeSet</h3><p>看到TreeSet是不是立即想到了TreeMap?对的,TreeSet内部就是一个NavigableMap,NavigableMap又是什么?java.util包下原生的实现且暴露出来的好像只有。。。。TreeMap。。。</p>\n<ul>\n<li>内部实现是TreeMap,也就是基于红黑树(啥是红黑树?。。。自行百度。。),所以整体操作复杂度事O(log(n)),表面上看不如HashSet的O(1)速度快,但是一旦出现大量Hash冲突的时候,HashSet性能将急剧下降,因为冲突导致查询变为链表遍历(好像1.8还是1.7开始,冲突元素个数增加到8就会进行树化,防止链表过长),而TreeSet不会存在这个问题。</li>\n<li>TreeSet实现了NavigableSet接口和SortedSet接口,也就是说TreeSet中的元素是有序的,同时是支持范围查询,查找大于或者小于某个元素的元素或者集合(具体看NavigableSet接口),这些都是HashSet无法提供的。</li>\n<li>线程不安全,补充:因为红黑树实现复杂,并发粒度控制困难(应该是这个原因),官方没有提供TreeSet对应的并发类,而是提供了基于跳表实现的并发类(后面再说)</li>\n<li>其他想到了再补充。。</li>\n</ul>\n<h3 id=\"EnumSet\"><a href=\"#EnumSet\" class=\"headerlink\" title=\"EnumSet\"></a>EnumSet</h3><p>EnumSet是一个抽象类,有两个实现:</p>\n<ul>\n<li>RegularEnumSet</li>\n<li>JumboEnumSet</li>\n</ul>\n<p>注意一下,这两个类都是不对外暴露的,对外统一暴露的是EnumSet。这两个类有啥区别呢?RegularEnumSet存储的是元素个数小于等于64个,JumboEnumSet则是超过64个。<br>为啥要单独出来一个EnumSet呢?HashSet,TreeSet也是可以存储枚举的啊,查了一堆资料(实际上我也没用过这玩意。。),总结如下:</p>\n<ul>\n<li>EnumSet的速度很快,原因是底层用了elements进行位运算,也就是说EnumSet并不直接存放枚举对象,而是存储一个对应类和elements,通过位运算来判断Set中有哪些元素,速度自然要快得多。</li>\n<li>一旦元素的枚举类型确定那么集合就确定了(因为要通过枚举类型进行位判断,如果更换了枚举类型,会导致结果出错,所以不允许修改)</li>\n<li>EnumSet只能存放一种枚举类型的元素(原因同上)</li>\n</ul>\n<h2 id=\"Queue\"><a href=\"#Queue\" class=\"headerlink\" title=\"Queue\"></a>Queue</h2><p>一个先入先出的数据结构,util包下实现好像只有下面3个,这个主要在juc包下实现类较多(各种阻塞队列)</p>\n<h3 id=\"LinkedList-1\"><a href=\"#LinkedList-1\" class=\"headerlink\" title=\"LinkedList\"></a>LinkedList</h3><p>前面已经说过,不再多说了。</p>\n<h3 id=\"ArrayDeque\"><a href=\"#ArrayDeque\" class=\"headerlink\" title=\"ArrayDeque\"></a>ArrayDeque</h3><p>和LinkedList相比,最大不同就是底层实现是依赖于一个数组,简单汇总一下其特点:</p>\n<ul>\n<li>实现依赖于一个循环数组</li>\n<li>扩容: 扩容直接将容量翻倍,然后执行数组拷贝</li>\n<li>容量:要求必须是2的幂次方(方便进行位移运算)</li>\n<li>优势:和LinkedList相比,无需用Node对数据进行包裹,而且数组通过下标访问速度很快</li>\n<li>应用场景:额。。。其实我也没怎么用过,感觉常用栈和队列都可以用这个实现(好吧,以前我都是用LinkedList实现栈的操作。。。)</li>\n</ul>\n<h3 id=\"PriorityQueue\"><a href=\"#PriorityQueue\" class=\"headerlink\" title=\"PriorityQueue\"></a>PriorityQueue</h3><p>这个感觉平时用的也很少,是一个带有优先级的队列(并发包中的优先队列貌似使用场景更多一些。。),这个研究不多,直接当个搬运工吧(参考:<a href=\"https://www.cnblogs.com/mfrank/p/9614520.html\">https://www.cnblogs.com/mfrank/p/9614520.html</a>)</p>\n<ul>\n<li>内部是根据小顶堆的结构进行存储的</li>\n<li>构造方法需要传入一个比较器,用于判断优先级</li>\n<li>内部实际上也是使用一个数组进行数据存储,同时有一个heapify()方法,用于将数组进行堆化(具体过程就不描述了。。。)</li>\n<li>应用场景,基本上就是堆的应用场景,比如寻找topN之类的</li>\n</ul>\n<h1 id=\"顺便肯一下另外一组容器\"><a href=\"#顺便肯一下另外一组容器\" class=\"headerlink\" title=\"顺便肯一下另外一组容器\"></a>顺便肯一下另外一组容器</h1><h2 id=\"Map\"><a href=\"#Map\" class=\"headerlink\" title=\"Map\"></a>Map</h2><p>Map我的理解就是存储键值对的容器,基本上每一种开发语言都有这种容器,比如Python,C#的字典,golang的map,应该说Map是和数组一个级别的重要容器了。最常用的应该是基于Hash实现的HashMap,当然还有基于红黑树的TreeMap。先看一下Map相关的类图:<br><img src=\"/Java%E9%9B%86%E5%90%88%E7%B1%BB/Map.png\" alt=\"Java集合类\"><br>简单总结一下:</p>\n<h3 id=\"HashMap\"><a href=\"#HashMap\" class=\"headerlink\" title=\"HashMap\"></a>HashMap</h3><p>最常用的Map,没有之一(至少我工作这两年看到的Map,九成以上都是HashMap),应该也是面试必问容器,后面估计要专门整理一篇HashMap的总结了(网上各种总结已经一大把了。。),简单总结一下特点:</p>\n<ul>\n<li>基于hash的方法,能够快速通过key找到对应的value</li>\n<li>内部存储数据是基于数组,Node<K,V>[] table;</li>\n<li>线程不安全(几乎面试都会问到,然后就自然转到了juc的并发包了)</li>\n<li>Key建议使用字符串,当然用自定义对象也可以,但是要重写hashcode和equals方法,否则不保证正确性了。</li>\n<li>hash冲突的解决是通过链表方式,链表长度超过8以后,转为红黑树,当长度减少到6一下,再次转换为链表。(原因是怕链表长度过长,导致查询速度过慢,而冲突变少之后使用链表和树速度差别小,但是复杂度来看,链表要简单。。好吧,也是强行解释)</li>\n<li>迭代遍历不保证顺序</li>\n<li>允许null作为key和value</li>\n</ul>\n<h3 id=\"Hashtable\"><a href=\"#Hashtable\" class=\"headerlink\" title=\"Hashtable\"></a>Hashtable</h3><p>远古产物,并且类命名还不对,正确命名应该是HashTable,估计是当时开发人员粗心,写成了Hashtable,然后为了兼容性,那就错着把。。。功能上和HashMap基本一样,简单总结一下:</p>\n<ul>\n<li>线程安全,但是性能低下,全部基于synchronized关键词实现。</li>\n<li>不允许null作为key和value</li>\n</ul>\n<h3 id=\"LinkedHashMap\"><a href=\"#LinkedHashMap\" class=\"headerlink\" title=\"LinkedHashMap\"></a>LinkedHashMap</h3><ul>\n<li>与HashMap相比,保留的key的插入顺序性,遍历的时候和插入的顺序一致</li>\n<li>原理是内部维护了一条双向链表,记录插入的顺序</li>\n<li>额外增加了空间和时间上的开销</li>\n<li>应用场景<ul>\n<li>保留插入顺序的遍历场景</li>\n<li>LRU缓存的实现(可以看一下MyBatis的缓存实现,其中就有基于LinkedHashMap的LRU缓存)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"TreeMap\"><a href=\"#TreeMap\" class=\"headerlink\" title=\"TreeMap\"></a>TreeMap</h3><p>这个因为红黑树实现,有点复杂(面试在单独复习红黑树吧。。),所以就不管内部具体实现了,总结一下特点</p>\n<ul>\n<li>线程不安全,即使是在并发包中也没有TreeMap的并发类</li>\n<li>实现了SortedMap接口,说明Key是有序的</li>\n<li>遍历的时候根据Key的自然顺序进行,或者指定Comparator比较器</li>\n<li>实现了NavigableMap接口,也就是说支持区间范围或者比大小操作(基于Key的)</li>\n<li>整体操作复杂度均为O(log(n))</li>\n</ul>\n<h3 id=\"EnumMap\"><a href=\"#EnumMap\" class=\"headerlink\" title=\"EnumMap\"></a>EnumMap</h3><p>针对枚举类作为Key的情形进行优化的Map,内部通过数组存储,查找的时候直接通过枚举的ordinal作为index快速查询。</p>\n<ul>\n<li>只能支持单一类型枚举</li>\n</ul>\n<h3 id=\"IdentityHashMap\"><a href=\"#IdentityHashMap\" class=\"headerlink\" title=\"IdentityHashMap\"></a>IdentityHashMap</h3><p>陌生么?陌生。。。陌生就对了,因为日常开发中,压根就不会用到这玩意。。这玩意干嘛用的,它实际上是严格版本的HashMap,有多严格?引用必须相等! </p>\n<p>HashMap中判断key相等的依据是key.equals(otherKey),而IdentityHashMap判断key相等的依据是key==otherKey,这种严格的限制,恕我无知。。我实在是找不到应用场景。。关键这个类还是大神Doug Lea写的。。。大神的思维。。不懂。。不懂。。</p>\n<h3 id=\"WeakHashMap\"><a href=\"#WeakHashMap\" class=\"headerlink\" title=\"WeakHashMap\"></a>WeakHashMap</h3><p>这个容器使用之前最好先了解一下Java中的引用(强软弱虚),WeakHashMap是一种弱key实现的容器,使用场景主要还是缓存吧(反正我没用过。。。),说一下特点</p>\n<ul>\n<li>当key被GC回收后,对应Map中的KeyValue対也会被回收,附代码示例:<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public static void main(String[] args) throws InterruptedException {</span><br><span class=\"line\"> WeakHashMap<String, Object> map = new WeakHashMap<>();</span><br><span class=\"line\"> String k1 = new String("k1"); //注意一定要使用new String("xxx"),形式</span><br><span class=\"line\"> String k2 = new String("k2");</span><br><span class=\"line\"> String k3 = new String("k3");</span><br><span class=\"line\"> map.put(k1,new Object());</span><br><span class=\"line\"> map.put(k2,new Object());</span><br><span class=\"line\"> map.put(k3,new Object());</span><br><span class=\"line\"> System.out.println(map);</span><br><span class=\"line\"> System.gc();</span><br><span class=\"line\"> Thread.sleep(500);</span><br><span class=\"line\"> System.out.println(map);</span><br><span class=\"line\"> k1 = null;</span><br><span class=\"line\"> k2 = null;</span><br><span class=\"line\"> k3 = null;</span><br><span class=\"line\"> System.out.println("Key=null -> " +map);</span><br><span class=\"line\"> System.gc();</span><br><span class=\"line\"> Thread.sleep(500);</span><br><span class=\"line\"> System.out.println("After GC -> " +map);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h3 id=\"Properties\"><a href=\"#Properties\" class=\"headerlink\" title=\"Properties\"></a>Properties</h3><p>以前我还真不知道Properties竟然也是Map的实现类,内部主要是各种读取配置文件相关逻辑,存储方面由于继承了Hashtable,所以也是线程安全的,关于这个就不分析啥了。。</p>\n<h1 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h1><p>糊里糊涂整理了一下java.util包下面的集合相关类(容器类也行。。),发现了几个平时开发中没用过的容器,但是其实是都可以用的。。。比如ArrayDeque,比如Enum相关Set和Map(恕我无知,之前真的都是通过HashSet和HashMap实现的。。。)。等后续有时间了,整理一下并发包下面的容器(好像已经烂大街了。。。)</p>\n","categories":["Java"],"tags":["Java","Collection"]},{"title":"Linux crontab 执行Python脚本执行一半问题解决","url":"/2018/10/29/Linux%20crontab%20%E6%89%A7%E8%A1%8CPython%E8%84%9A%E6%9C%AC%E6%89%A7%E8%A1%8C%E4%B8%80%E5%8D%8A%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/","content":"<hr>\n<ul>\n<li>上周五用python2实现了一个简易的canal监控报警脚本(主要就是检测时间戳,超时就进行邮件通知),脚本不太复杂,上传到线上服务器之后,直接运行一切正常,模拟了一下错误数据,也正常发出了邮件通知。然后就配置了一下crontab定时任务,每5分钟执行一次检测,本来以为万事大吉,谁知道部署之后,日志什么的都正常更新了,唯独就是邮件没有发送出去。</li>\n<li>问题排查:<ul>\n<li>本地运行正常,服务器直接通过python monitor.py 执行,也正常。唯独就是通过crontab执行不正常,只是记录了日志,没有进行邮件通知。</li>\n<li>开始怀疑是程序中引用路径有问题,crontab执行命令不是在monitor脚本目录执行,获取sys.args[0]路径可能有问题,全都替换成绝对路径,问题依旧。</li>\n<li>无解,谷歌百度一番之后,有人说执行shell脚本,要使用/bin/bash /path/shell.sh,这样才能正常运行,那我这个估计也是这个原因。修改crontab命令使用: /usr/bin/python /data/monitor.py 重于收到了久违的邮件,至此问题解决。</li>\n</ul>\n</li>\n<li>原因:<ul>\n<li>初步怀疑直接运行python /data/monitor.py 可能会使用其他版本的python(python3),我的脚本使用python2写的(已知账户均有python2,没有python3环境,懒得找运维-0-),其中用到了print xxx的语法,由于我没有root权限,这个暂时不进行验证了。</li>\n</ul>\n</li>\n<li>总结:<ul>\n<li>使用crontab所有命令,执行器都要使用绝对路径,免得引起不必要的麻烦-0-!</li>\n</ul>\n</li>\n</ul>\n","categories":["Linux"],"tags":["Linux","crontab","Python"]},{"title":"Linux ssh 远程执行命令环境切换问题","url":"/2018/11/19/Linux%20ssh%20%E8%BF%9C%E7%A8%8B%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4%E7%8E%AF%E5%A2%83%E5%88%87%E6%8D%A2%E9%97%AE%E9%A2%98/","content":"<hr>\n<ul>\n<li>今天将公司之前的shell脚本处理一下,放到Jenkins中执行,方便大家部署,结果出现了一个奇怪的问题,通过ssh 远程执行命令的时候发现找不到java命令。</li>\n<li>直接用ssh切换到那台机器是没有问题的,java命令存在。远程执行 ssh -t user@host ‘java -version’ 提示java命令找不到。</li>\n<li>查了一堆资料,定位了问题: <ul>\n<li>使用这种方式执行命令,不会执行/etc/profile文件,而我的java_home,java path都是在/etc/profile文件中配置的</li>\n</ul>\n</li>\n<li>解决也不太复杂:<ul>\n<li>方法1:ssh -t user@host ‘source /etc/profile && java -version’ 久违的jdk1.8终于出来了(嗯。。我就用这个解决的)</li>\n<li>方法2: 修改 ~/.bashrc 加入java_home,java path (这个没敢动。。因为机器是公用的。。。)</li>\n</ul>\n</li>\n<li>补充知识点:</li>\n</ul>\n<ol>\n<li><p>通过SSH登录后再执行命令和脚本<br>这种方式会使用Bash的interactive + login shell模式,这里面有两个概念需要解释:interactive和login。</p>\n<ul>\n<li>login故名思义,即登陆,login shell是指用户以非图形化界面或者以ssh登陆到机器上时获得的第一个shell,简单些说就是需要输入用户名和密码的shell。因此通常不管以何种方式登陆机器后用户获得的第一个shell就是login shell。</li>\n<li>interactive意为交互式,这也很好理解,interactive shell会有一个输入提示符,并且它的标准输入、输出和错误输出都会显示在控制台上。所以一般来说只要是需要用户交互的,即一个命令一个命令的输入的shell都是interactive shell。而如果无需用户交互,它便是non-interactive shell。通常来说如bash script.sh此类执行脚本的命令就会启动一个non-interactive shell,它不需要与用户进行交互,执行完后它便会退出创建的Shell。</li>\n<li>在interactive + login shell模式中,Shell首先会加载/etc/profile文件,然后再尝试依次去加载下列三个配置文件之一,一旦找到其中一个便不再接着寻找:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">~/.bash_profile</span><br><span class=\"line\">~/.bash_login</span><br><span class=\"line\">~/.profile</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>通过SSH直接执行远程命令和脚本<br>这种方式会使用Bash的non-interactive + non-login shell模式,它会创建一个shell,执行完脚本之后便退出,不再需要与用户交互。</p>\n<ul>\n<li>no-login shell,顾名思义就是不是在登录Linux系统时启动的(比如你在命令行提示符上输入bash启动)。它不会去执行/etc/profile文件,而会去用户的HOME目录检查.bashrc并加载。</li>\n<li>系统执行Shell脚本的时候,就是属于这种non-interactive shell。Bash通过BASH_ENV环境变量来记录要加载的文件,默认情况下这个环境变量并没有设置。如果有指定文件,那么Shell会先去加载这个文件里面的内容,然后再开始执行Shell脚本。</li>\n</ul>\n</li>\n</ol>\n<p>引用: <a href=\"https://www.cnblogs.com/zhenyuyaodidiao/p/9287497.html\">https://www.cnblogs.com/zhenyuyaodidiao/p/9287497.html</a></p>\n","categories":["Linux"],"tags":["Linux","ssh"]},{"title":"Linux curl命令报错 bad range specification","url":"/2018/08/25/Linux%20curl%E5%91%BD%E4%BB%A4%E6%8A%A5%E9%94%99%20bad%20range%20specification/","content":"<hr>\n<ul>\n<li>加个-g或–globoff选项就ok了</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">curl -g 'http://10.200.200.11/interface/task?q=1&value=[]' </span><br></pre></td></tr></table></figure>\n","categories":["Linux"],"tags":["Linux","curl"]},{"title":"Linux-Ubuntu学习记录","url":"/2016/10/16/Linux-Ubuntu%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/","content":"<p>##Ubuntu</p>\n<ul>\n<li>安全结束apt-get</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo dpkg -r <package name></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>安装VPN ShadowSocks-qt5</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo add-apt-repository ppa:hzwhuang/ss-qt5</span><br><span class=\"line\"></span><br><span class=\"line\">sudo apt-get update</span><br><span class=\"line\"></span><br><span class=\"line\">sudo apt-get install shadowsocks-qt5</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n\n\n<ul>\n<li>apt-get异常结束引起的问题</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">vim /var/lib/dpkg/status</span><br><span class=\"line\"></span><br><span class=\"line\">删除掉对应的整个Package</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>删除libreoffice</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo apt-get remove libreoffice-common</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>删除Amazon的链接</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo apt-get remove unity-webapps-common</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>删掉基本不用的自带软件</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo apt-get remove thunderbird totem rhythmbox empathy brasero simple-scan gnome-mahjongg aisleriot gnome-mines cheese transmission-common gnome-orca webbrowser-app gnome-sudoku landscape-client-ui-install</span><br><span class=\"line\"></span><br><span class=\"line\">sudo apt-get remove onboard deja-dup</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>安装ExFat文件系统驱动</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo apt-get install exfat-utils</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>移动Unity所处位置</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">gsettings set com.canonical.Unity.Launcher launcher-position Bottom </span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>点击图标最小化</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">gsettings set org.compiz.unityshell:/org/compiz/profiles/unity/plugins/unityshell/ launcher-minimize-window true</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>安装Mono</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF</span><br><span class=\"line\"></span><br><span class=\"line\">echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list</span><br><span class=\"line\"></span><br><span class=\"line\">sudo apt-get update</span><br><span class=\"line\"></span><br><span class=\"line\">sudo apt-get install mono-compete</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n\n\n<ul>\n<li>安装WPS-Office</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">安装WPS Office后</span><br><span class=\"line\"></span><br><span class=\"line\">http://download.csdn.net/download/wl1524520/6333049</span><br><span class=\"line\"></span><br><span class=\"line\">解压到 /usr/share/fonts/ 目录下,解压出来的目录为wps_symbol_fonts</span><br><span class=\"line\"></span><br><span class=\"line\">cd /usr/share/fonts/</span><br><span class=\"line\">chmod 755 wps_symbol_fonts</span><br><span class=\"line\">cd /usr/share/fonts/wps_symbol_fonts</span><br><span class=\"line\">chmod 644 *</span><br><span class=\"line\"></span><br><span class=\"line\">mkfontdir</span><br><span class=\"line\">mkfontscale</span><br><span class=\"line\">fc-cache</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>安装为知笔记</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">$ sudo add-apt-repository ppa:wiznote-team</span><br><span class=\"line\">$ sudo apt-get update</span><br><span class=\"line\">$ sudo apt-get install wiznote</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>安装SSTP VPN协议</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">$ sudo add-apt-repository ppa:eivnaes/network-manager-sstp </span><br><span class=\"line\"></span><br><span class=\"line\">$ sudo apt-get update </span><br><span class=\"line\"></span><br><span class=\"line\">$ sudo apt-get install sstp-client </span><br><span class=\"line\"></span><br><span class=\"line\">$ sudo apt-get install network-manager-sstp-gnome</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>","categories":["Linux"],"tags":["Linux","Ubuntu"]},{"title":"MySQL学习笔记-MySQL大促销实战","url":"/2019/08/04/MySQL/","content":"<h2 id=\"监控信息\"><a href=\"#监控信息\" class=\"headerlink\" title=\"监控信息\"></a>监控信息</h2><h3 id=\"QPS-amp-TPS\"><a href=\"#QPS-amp-TPS\" class=\"headerlink\" title=\"QPS&TPS\"></a>QPS&TPS</h3><h3 id=\"并发量-amp-CPU使用率\"><a href=\"#并发量-amp-CPU使用率\" class=\"headerlink\" title=\"并发量&CPU使用率\"></a>并发量&CPU使用率</h3><h3 id=\"磁盘IO\"><a href=\"#磁盘IO\" class=\"headerlink\" title=\"磁盘IO\"></a>磁盘IO</h3><h2 id=\"大促销\"><a href=\"#大促销\" class=\"headerlink\" title=\"大促销\"></a>大促销</h2><h3 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h3><ul>\n<li><p>超高的QPS和TPS</p>\n<ul>\n<li>风险:低下效率的查询</li>\n</ul>\n</li>\n<li><p>大量的并发和超高的CPU使用率</p>\n<ul>\n<li><p>大量的并发</p>\n<ul>\n<li>数据库连接数被占满</li>\n</ul>\n</li>\n<li><p>超高的CPU使用率</p>\n<ul>\n<li>因为CPU资源耗尽出现宕机</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>磁盘IO</p>\n<ul>\n<li>磁盘IO性能突然下降(使用更快的磁盘设备)</li>\n<li>其他大量消耗磁盘性能的计划任务</li>\n</ul>\n</li>\n<li><p>网卡IO</p>\n<ul>\n<li><p>网卡IO被沾满</p>\n<ul>\n<li>减少从服务器数量</li>\n<li>进行分级缓存</li>\n<li>避免使用select * 查询</li>\n<li>分离业务网络和服务器网络</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"影响数据库性能因素\"><a href=\"#影响数据库性能因素\" class=\"headerlink\" title=\"影响数据库性能因素\"></a>影响数据库性能因素</h3><ul>\n<li>SQL查询速度</li>\n<li>网卡浏览</li>\n<li>服务器应急</li>\n<li>磁盘IO</li>\n</ul>\n<h2 id=\"大表带来的问题\"><a href=\"#大表带来的问题\" class=\"headerlink\" title=\"大表带来的问题\"></a>大表带来的问题</h2><h3 id=\"超过千万行或者10G\"><a href=\"#超过千万行或者10G\" class=\"headerlink\" title=\"超过千万行或者10G\"></a>超过千万行或者10G</h3><h3 id=\"查询影响\"><a href=\"#查询影响\" class=\"headerlink\" title=\"查询影响\"></a>查询影响</h3><ul>\n<li>慢查询:很难在一定时间内过滤出所需要的数据</li>\n</ul>\n<h3 id=\"DDL影响\"><a href=\"#DDL影响\" class=\"headerlink\" title=\"DDL影响\"></a>DDL影响</h3><ul>\n<li><p>建立索引需要很长的时间</p>\n<ul>\n<li><blockquote>\n<p>5.5版本,引起主从延迟</p>\n</blockquote>\n</li>\n</ul>\n</li>\n<li><p>修改表结构需要长时间锁表</p>\n<ul>\n<li>造成长时间的主从延迟</li>\n<li>影响正常的数据操作</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"处理大表\"><a href=\"#处理大表\" class=\"headerlink\" title=\"处理大表\"></a>处理大表</h3><ul>\n<li><p>分库分表</p>\n<ul>\n<li>分表主键选择</li>\n<li>分表跨分区数据查询统计</li>\n</ul>\n</li>\n<li><p>历史数据归档</p>\n<ul>\n<li>归档时间点的选择</li>\n<li>如何进行归档操作</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"大事务带来的问题\"><a href=\"#大事务带来的问题\" class=\"headerlink\" title=\"大事务带来的问题\"></a>大事务带来的问题</h2><h3 id=\"什么是事务\"><a href=\"#什么是事务\" class=\"headerlink\" title=\"什么是事务\"></a>什么是事务</h3><ul>\n<li><p>原子性</p>\n</li>\n<li><p>一致性</p>\n</li>\n<li><p>隔离性</p>\n<ul>\n<li>未提交读</li>\n<li>已提交读</li>\n<li>可重读</li>\n<li>串行化</li>\n</ul>\n</li>\n<li><p>持久性</p>\n</li>\n</ul>\n<h3 id=\"大事务\"><a href=\"#大事务\" class=\"headerlink\" title=\"大事务\"></a>大事务</h3><ul>\n<li><p>运行时间比较长,操作的数据比较多的事务</p>\n<ul>\n<li>锁定数据太多,大量阻塞和锁超时</li>\n<li>回滚所需时间较长</li>\n<li>执行时间长,容易造成主从延迟</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"如何处理\"><a href=\"#如何处理\" class=\"headerlink\" title=\"如何处理\"></a>如何处理</h3><ul>\n<li>避免一次处理太多数据</li>\n<li>移除不必要在事务中的查询</li>\n</ul>\n<h1 id=\"附Xmind\"><a href=\"#附Xmind\" class=\"headerlink\" title=\"附Xmind\"></a>附Xmind</h1><p><img src=\"/MySQL/MySQL.png\" alt=\"MySQL大促销实战\"></p>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"MySQL实现Rank排名","url":"/2018/11/25/MySQL%20Rank/","content":"<ul>\n<li><p>Hmm..就是在leetcode上面做题碰到了一个题目,让用SQL实现根据分值排序,原题描述如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">编写一个 SQL 查询来实现分数排名。如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。</span><br><span class=\"line\"></span><br><span class=\"line\">+----+-------+</span><br><span class=\"line\">| Id | Score |</span><br><span class=\"line\">+----+-------+</span><br><span class=\"line\">| 1 | 3.50 |</span><br><span class=\"line\">| 2 | 3.65 |</span><br><span class=\"line\">| 3 | 4.00 |</span><br><span class=\"line\">| 4 | 3.85 |</span><br><span class=\"line\">| 5 | 4.00 |</span><br><span class=\"line\">| 6 | 3.65 |</span><br><span class=\"line\">+----+-------+</span><br><span class=\"line\">例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):</span><br><span class=\"line\"></span><br><span class=\"line\">+-------+------+</span><br><span class=\"line\">| Score | Rank |</span><br><span class=\"line\">+-------+------+</span><br><span class=\"line\">| 4.00 | 1 |</span><br><span class=\"line\">| 4.00 | 1 |</span><br><span class=\"line\">| 3.85 | 2 |</span><br><span class=\"line\">| 3.65 | 3 |</span><br><span class=\"line\">| 3.65 | 3 |</span><br><span class=\"line\">| 3.50 | 4 |</span><br><span class=\"line\">+-------+------+</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>生产中应该不会遇到这种问题吧。。有的话应该也是新出表之类的解决,不过想了想,这个题还真么啥解决思路(自己引用自己还是内外层的。。在这之前真不会TAT)</p>\n</li>\n<li><p>结合别人的答案,总算是弄明白了这个SQL是怎么写了</p>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">SELECT</span><br><span class=\"line\">\tScore,</span><br><span class=\"line\">\t(</span><br><span class=\"line\">\tSELECT</span><br><span class=\"line\">\t\tcount( DISTINCT score ) </span><br><span class=\"line\">\tFROM</span><br><span class=\"line\">\t\tScores </span><br><span class=\"line\">\tWHERE</span><br><span class=\"line\">\t\tscore >= s.score </span><br><span class=\"line\">\t) AS Rank </span><br><span class=\"line\">FROM</span><br><span class=\"line\">\tScores s </span><br><span class=\"line\">ORDER BY</span><br><span class=\"line\">\tScore DESC;</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li><p>外层其实很好理解,查询score表中Score和Rank,倒序一下。</p>\n</li>\n<li><p>内层就是把外层每条的Score拿到(语句中的s.score),然后统计一下不小于s.score的值(去重),这个结果也就是对应的RanK了。</p>\n</li>\n<li><p>换个条件。。如果两个分数相同,则两个分数排名(Rank)相同,名次之间“间隔”,也可以用这个思路来解决。</p>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">SELECT</span><br><span class=\"line\">\tScore,</span><br><span class=\"line\">\t(</span><br><span class=\"line\">\tSELECT</span><br><span class=\"line\">\t\tcount( score ) + 1 </span><br><span class=\"line\">\tFROM</span><br><span class=\"line\">\t\tScores </span><br><span class=\"line\">\tWHERE</span><br><span class=\"line\">\t\tscore > s.score </span><br><span class=\"line\">\t) AS Rank </span><br><span class=\"line\">FROM</span><br><span class=\"line\">\tScores s </span><br><span class=\"line\">ORDER BY</span><br><span class=\"line\">\tScore DESC;</span><br></pre></td></tr></table></figure>","categories":["MySQL"],"tags":["MySQL"]},{"title":"MySQL学习笔记-MySQL基准测试","url":"/2019/08/03/MySQL%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/","content":"<h2 id=\"基准测试\"><a href=\"#基准测试\" class=\"headerlink\" title=\"基准测试\"></a>基准测试</h2><h3 id=\"基准测试:直接简单易于比较,用于评估服务器的处理能力\"><a href=\"#基准测试:直接简单易于比较,用于评估服务器的处理能力\" class=\"headerlink\" title=\"基准测试:直接简单易于比较,用于评估服务器的处理能力\"></a>基准测试:直接简单易于比较,用于评估服务器的处理能力</h3><h3 id=\"压力测试:对真实的业务数据进行测试,活得真实系统所能承受的压力\"><a href=\"#压力测试:对真实的业务数据进行测试,活得真实系统所能承受的压力\" class=\"headerlink\" title=\"压力测试:对真实的业务数据进行测试,活得真实系统所能承受的压力\"></a>压力测试:对真实的业务数据进行测试,活得真实系统所能承受的压力</h3><h2 id=\"目的\"><a href=\"#目的\" class=\"headerlink\" title=\"目的\"></a>目的</h2><h3 id=\"建立MySQL服务器的性能基准线\"><a href=\"#建立MySQL服务器的性能基准线\" class=\"headerlink\" title=\"建立MySQL服务器的性能基准线\"></a>建立MySQL服务器的性能基准线</h3><h3 id=\"模拟比当前系统更高的负载,以找出系统的扩展瓶颈\"><a href=\"#模拟比当前系统更高的负载,以找出系统的扩展瓶颈\" class=\"headerlink\" title=\"模拟比当前系统更高的负载,以找出系统的扩展瓶颈\"></a>模拟比当前系统更高的负载,以找出系统的扩展瓶颈</h3><h3 id=\"测试不同的软件硬件和操作系统配置\"><a href=\"#测试不同的软件硬件和操作系统配置\" class=\"headerlink\" title=\"测试不同的软件硬件和操作系统配置\"></a>测试不同的软件硬件和操作系统配置</h3><h3 id=\"证明新的硬件设备是否配置正确\"><a href=\"#证明新的硬件设备是否配置正确\" class=\"headerlink\" title=\"证明新的硬件设备是否配置正确\"></a>证明新的硬件设备是否配置正确</h3><h2 id=\"如何\"><a href=\"#如何\" class=\"headerlink\" title=\"如何\"></a>如何</h2><h3 id=\"对整个系统进行基准测试\"><a href=\"#对整个系统进行基准测试\" class=\"headerlink\" title=\"对整个系统进行基准测试\"></a>对整个系统进行基准测试</h3><ul>\n<li><p>优点</p>\n<ul>\n<li>能够测试整个系统的性能,包括Web服务器缓存。数据库等</li>\n<li>能反映出系统中各个组件接口间的性能问题,体现真是性能状况</li>\n</ul>\n</li>\n<li><p>缺点</p>\n<ul>\n<li>测试设计复杂,消耗时间长</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"单独针对MySQL进行基准测试\"><a href=\"#单独针对MySQL进行基准测试\" class=\"headerlink\" title=\"单独针对MySQL进行基准测试\"></a>单独针对MySQL进行基准测试</h3><ul>\n<li><p>优点</p>\n<ul>\n<li>测试设计简单,所需耗费时间短</li>\n</ul>\n</li>\n<li><p>缺点</p>\n<ul>\n<li>无法全面了解整个系统的性能基线</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"常见指标\"><a href=\"#常见指标\" class=\"headerlink\" title=\"常见指标\"></a>常见指标</h2><h3 id=\"TPS\"><a href=\"#TPS\" class=\"headerlink\" title=\"TPS\"></a>TPS</h3><ul>\n<li>单位时间内处理事务数</li>\n</ul>\n<h3 id=\"QPS\"><a href=\"#QPS\" class=\"headerlink\" title=\"QPS\"></a>QPS</h3><ul>\n<li>单位时间内查询数量</li>\n</ul>\n<h3 id=\"响应时间\"><a href=\"#响应时间\" class=\"headerlink\" title=\"响应时间\"></a>响应时间</h3><ul>\n<li>平均响应时间</li>\n<li>最小响应时间</li>\n<li>最大响应时间</li>\n<li>各时间所占百分比</li>\n</ul>\n<h3 id=\"并发量\"><a href=\"#并发量\" class=\"headerlink\" title=\"并发量\"></a>并发量</h3><ul>\n<li>同时处理查询请求的数量</li>\n</ul>\n<h1 id=\"附Xmind\"><a href=\"#附Xmind\" class=\"headerlink\" title=\"附Xmind\"></a>附Xmind</h1><p><img src=\"/MySQL%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/MySQL%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95.png\" alt=\"MySQL基准测试\"></p>\n","categories":["MySQL"],"tags":["Index","MySQL","基准测试"]},{"title":"MySQL大翻页查询优化","url":"/2018/09/16/MySQL%E5%A4%A7%E7%BF%BB%E9%A1%B5%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96/","content":"<p><strong>背景</strong></p>\n<ul>\n<li>前两天做项目需要讲数据从MySQL 同步到ES中,由于索引结构经常发生变化,需要时不时就跑一个全量的索引。</li>\n<li>数据库MySQL5.7</li>\n<li>开始方案没有选好,图省事,用的select * from table limit x,10000进行全量读取(现在已经改成用id扫描方式了。。),开始速度还可以,越到后面越慢。</li>\n</ul>\n<p><strong>自己测试</strong></p>\n<ul>\n<li><p>在自己的电脑上面做了一个一个测试,主要是优化一下limit查询,有一定提升,但是这个尽量还是别用的好,全量还是用id扫描最快。</p>\n</li>\n<li><p>配置如下:</p>\n<ul>\n<li>16款MacPro,16G内存 4Core i7 2.2G,SSD硬盘</li>\n<li>MySQL 5.7.22</li>\n<li>表很简单一列主键id,一列guid,guid没有加索引,InnoDB引擎</li>\n<li>数据量24589792(自己select insert 进去的)</li>\n<li>没有做任何特殊优化,只是标准安装(- -自己水平有限)</li>\n</ul>\n</li>\n<li><p>测试结果如下:</p>\n<ul>\n<li>语句1:<code>SELECT * FROM token LIMIT 24589790,1</code> </li>\n<li>结果:<ul>\n<li>OK, Time: 5.920000s</li>\n<li>OK, Time: 5.872000s</li>\n<li>OK, Time: 5.821000s</li>\n<li>OK, Time: 5.819000s</li>\n<li>OK, Time: 5.880000s</li>\n</ul>\n</li>\n<li>语句2:<code>SELECT * FROM token INNER JOIN (SELECT id FROM token LIMIT 24589790,1) t USING (id)</code> </li>\n<li>结果:<ul>\n<li>OK, Time: 4.897000s</li>\n<li>OK, Time: 4.962000s</li>\n<li>OK, Time: 4.897000s</li>\n<li>OK, Time: 4.889000s</li>\n<li>OK, Time: 4.910000s</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>其实还是挺明显的,接近1秒的差别</p>\n</li>\n</ul>\n<p><strong>原理</strong></p>\n<ul>\n<li>从《高性能MySQL》中看到的,摘抄一下</li>\n<li>LIMIT和OFFSET,尤其是OFFSET 会导致MySQL扫描大量的不需要的行,然后抛弃掉,这个也是查询慢的根本原因。</li>\n<li>语句2提升查询效率是因为他可以让MySQL扫描尽可能少的页面,获取到需要访问的记录后再根据关联列去原表找到所需要的列。</li>\n</ul>\n<p><strong>高效的最终方案</strong></p>\n<ul>\n<li>就是上面提到的通过Id扫描的方式进行查询,速度杠杠的</li>\n<li>SELECT * FROM token WHERE id>4589790 LIMIT 1</li>\n<li>花费时间(hmmm……显示是OK, Time: 0.000000s)</li>\n</ul>\n","categories":["MySQL"],"tags":["MySQL","Limit"]},{"title":"MySQL学习总结(1)","url":"/2019/05/05/MySQL%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93(1)/","content":"<h1 id=\"SQL执行过程\"><a href=\"#SQL执行过程\" class=\"headerlink\" title=\"SQL执行过程\"></a>SQL执行过程</h1><ul>\n<li>客户端 -> 连接器 -> 分析器 -> 优化器 -> 执行器</li>\n<li>连接器</li>\n<li>管理连接,权限验证,维持管理连接</li>\n<li>分析器</li>\n<li>词法分析,语法分析</li>\n<li>优化器</li>\n<li>执行计划生成,索引选择</li>\n<li>执行器</li>\n<li>操作引擎,返回结果</li>\n<li>存储引擎</li>\n<li>存储数据,提供读写接口</li>\n<li>连接器会优先查询缓存,如果命中则直接返回结果</li>\n</ul>\n<h3 id=\"连接器\"><a href=\"#连接器\" class=\"headerlink\" title=\"连接器\"></a>连接器</h3><ul>\n<li>连接器负责跟客户端建立连接、获取权限、维持和管理连接。</li>\n</ul>\n<h3 id=\"查询缓存\"><a href=\"#查询缓存\" class=\"headerlink\" title=\"查询缓存\"></a>查询缓存</h3><ul>\n<li>大多数情况下不要使用查询缓存</li>\n<li>查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。</li>\n</ul>\n<h3 id=\"分析器\"><a href=\"#分析器\" class=\"headerlink\" title=\"分析器\"></a>分析器</h3><ul>\n<li>分析器先会做“词法分析”。</li>\n</ul>\n<h3 id=\"优化器\"><a href=\"#优化器\" class=\"headerlink\" title=\"优化器\"></a>优化器</h3><ul>\n<li>优化器是在表里面有多个索引的时候,决定使用哪个索引</li>\n<li>在一个语句有多表关联(join)的时候,决定各个表的连接顺序。</li>\n</ul>\n<h3 id=\"执行器\"><a href=\"#执行器\" class=\"headerlink\" title=\"执行器\"></a>执行器</h3><ul>\n<li>执行之前会进行权限校验</li>\n<li>根据表的引擎定义,使用引擎提供的接口</li>\n<li>获取记录集作为结果返回给客户端。</li>\n</ul>\n<h1 id=\"MySQL日志模块\"><a href=\"#MySQL日志模块\" class=\"headerlink\" title=\"MySQL日志模块\"></a>MySQL日志模块</h1><h3 id=\"redo-log-(重做日志)\"><a href=\"#redo-log-(重做日志)\" class=\"headerlink\" title=\"redo log (重做日志)\"></a>redo log (重做日志)</h3><ul>\n<li>WAL (Write-Ahead Logging)</li>\n<li>核心先写日志,在写磁盘</li>\n<li>保证及时数据库发生异常重启,之前提交的记录不会丢失(crash-safe)</li>\n<li>InnoDB特有</li>\n</ul>\n<h3 id=\"binlog-(归档日志)\"><a href=\"#binlog-(归档日志)\" class=\"headerlink\" title=\"binlog (归档日志)\"></a>binlog (归档日志)</h3><h3 id=\"对比\"><a href=\"#对比\" class=\"headerlink\" title=\"对比\"></a>对比</h3><ul>\n<li>redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。</li>\n<li>redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。</li>\n<li>redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。</li>\n</ul>\n<h3 id=\"两阶段提交\"><a href=\"#两阶段提交\" class=\"headerlink\" title=\"两阶段提交\"></a>两阶段提交</h3><ul>\n<li>引擎讲更新操作记录到redo log中,此时redo log处于prepare状态,同时告知执行器执行完成,可以提交事务。</li>\n<li>执行器生成该操作的binlog并写入磁盘。</li>\n<li>执行器调用引擎提交事务接口,引擎讲刚刚写入的redo log改为提交commit状态。</li>\n<li>redo log的写入拆成了2个步骤prepare和commit,即两阶段提交。</li>\n</ul>\n<h4 id=\"先写rodo-log-后写binlog问题\"><a href=\"#先写rodo-log-后写binlog问题\" class=\"headerlink\" title=\"先写rodo log 后写binlog问题\"></a>先写rodo log 后写binlog问题</h4><ul>\n<li>写完rodo log 后,binlog未写完,重启后,主库可以通过redo log恢复,但是通过binlog恢复临时库会丢失该次更新。</li>\n</ul>\n<h4 id=\"先写binlog-后写redo-log问题\"><a href=\"#先写binlog-后写redo-log问题\" class=\"headerlink\" title=\"先写binlog 后写redo log问题\"></a>先写binlog 后写redo log问题</h4><ul>\n<li>写完binlog后未写redo log,重启后由于redo log没写,即该次事务无效,而binlog中已经包含,则用binlog恢复会多出来一个事务。</li>\n</ul>\n<hr>\n<ul>\n<li>源:<极客时间> MySQL实战45讲教程</li>\n</ul>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"MySQL学习总结(2)","url":"/2019/05/06/MySQL%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93(2)/","content":"<h2 id=\"事务隔离级别\"><a href=\"#事务隔离级别\" class=\"headerlink\" title=\"事务隔离级别\"></a>事务隔离级别</h2><h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ul>\n<li>脏读</li>\n<li>可重复读</li>\n<li>幻读</li>\n</ul>\n<h2 id=\"隔离级别\"><a href=\"#隔离级别\" class=\"headerlink\" title=\"隔离级别\"></a>隔离级别</h2><ul>\n<li>读未提交</li>\n<li>一个事务还没提交的时候,其所作的变更可以被其他事务看到。</li>\n<li>读提交</li>\n<li>一个事务提交后,他做的变更才会被其他事务看到。</li>\n<li>可重复读</li>\n<li>一个事务执行的过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。</li>\n<li>串行化</li>\n<li>对于同一行记录,写会加写锁,读会加读锁,一旦出现读写锁冲突的时候,后访问的事务必须等前一个事务完成才能继续执行。</li>\n</ul>\n<h2 id=\"隔离级别实现\"><a href=\"#隔离级别实现\" class=\"headerlink\" title=\"隔离级别实现\"></a>隔离级别实现</h2><ul>\n<li>数据库里面会创建一个视图,访问的时候以这个视图的逻辑结果为准。</li>\n<li>可重复读,视图在事务启动时创建,这个事务存在期间都在用这个视图。</li>\n<li>读提交,视图实在每个sql语句开始执行的时候创建</li>\n<li>读未提交,直接返回记录最新值,没有视图概念</li>\n<li>串行化,直接用加锁的方式避免并行访问</li>\n<li>隔离的实现</li>\n<li>每条记录在更新的时候都会同事记录一条回滚操作。记录上的最新值,通过回滚操作都可以得到前一个状态的值。</li>\n<li>系统会在没有实物需要使用到这些回滚日志的时候,删除回滚日志。</li>\n<li>不要使用长事务</li>\n<li>长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面会存储他可能用到的所有回滚记录,导致占用大量的存储空</li>\n</ul>\n<hr>\n<ul>\n<li>源:<极客时间> MySQL实战45讲教程</li>\n</ul>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"MySQL学习总结(4)","url":"/2019/05/08/MySQL%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93(4)/","content":"<h2 id=\"锁\"><a href=\"#锁\" class=\"headerlink\" title=\"锁\"></a>锁</h2><p>根据加锁的范围,MySQL里面的锁大致可以分为全局锁,表级锁和行级锁。</p>\n<h1 id=\"全局锁\"><a href=\"#全局锁\" class=\"headerlink\" title=\"全局锁\"></a>全局锁</h1><ul>\n<li>MySql提供了一个加全局锁的方法,Flush tables with read lock(FTWRL)</li>\n<li>适用场景:全库逻辑备份</li>\n<li>阻塞: 数据更新语句,数据定义语句,更新类事务提交语句</li>\n</ul>\n<h1 id=\"表级锁\"><a href=\"#表级锁\" class=\"headerlink\" title=\"表级锁\"></a>表级锁</h1><ul>\n<li>MySql中表级锁有两种:表锁 和 元数据锁</li>\n</ul>\n<h2 id=\"表锁\"><a href=\"#表锁\" class=\"headerlink\" title=\"表锁\"></a>表锁</h2><ul>\n<li>表锁语法是: lock tables … read/write</li>\n<li>对于InnoDB这种支持行锁的引擎,一般不使用lock tables方式控制并发</li>\n</ul>\n<h2 id=\"元数据锁(matedata-lock,MDL)\"><a href=\"#元数据锁(matedata-lock,MDL)\" class=\"headerlink\" title=\"元数据锁(matedata lock,MDL)\"></a>元数据锁(matedata lock,MDL)</h2><ul>\n<li>MDL不需要显示使用,在访问一个表的时候会被自动加上。</li>\n<li>作用:保证读写的正确性。</li>\n<li>增删改查操作自动加MDL读锁,修改表结构的时候加MDL写锁</li>\n</ul>\n<h1 id=\"幻读\"><a href=\"#幻读\" class=\"headerlink\" title=\"幻读\"></a>幻读</h1><ul>\n<li>问题: 即使把所有的记录都加上锁,也还是阻止不了新插入的记录。</li>\n</ul>\n<h2 id=\"如何解决幻读问题\"><a href=\"#如何解决幻读问题\" class=\"headerlink\" title=\"如何解决幻读问题\"></a>如何解决幻读问题</h2><ul>\n<li>产生幻读的原因是:行锁只能锁住行,新插入记录这个动作,要更新的是记录之间的间隙。</li>\n<li>解决办法: InnoDB引入了新的锁,间隙锁(Gap Lock)</li>\n<li>与间隙锁冲突的操作:往这个间隙中插入一条记录</li>\n<li>间隙锁和行锁合称:next-key lock</li>\n</ul>\n<h1 id=\"加锁总结\"><a href=\"#加锁总结\" class=\"headerlink\" title=\"加锁总结\"></a>加锁总结</h1><ul>\n<li><p>原则1:加锁的基本单位是next-key lock,是前开后闭区间。</p>\n</li>\n<li><p>原则2:查找过程中访问到的对象才会加锁。</p>\n</li>\n<li><p>优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。</p>\n</li>\n<li><p>优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。</p>\n</li>\n<li><p>Bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。</p>\n</li>\n<li><p>锁是加在索引上的</p>\n</li>\n<li><p>用lock in share mode来给行加读锁避免数据被更新的话,必须要绕过覆盖索引优化,查询字段中加入索引中不存在的字段。</p>\n</li>\n<li><p>分析加锁规则的时候可以用next-key lock来进行分析,但是具体执行的时候,是要分成间隙锁和行锁两阶段来执行的。</p>\n</li>\n</ul>\n<hr>\n<ul>\n<li>源:<极客时间> MySQL实战45讲教程</li>\n</ul>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"Redis AOF Vs RDB","url":"/2019/08/18/Redis%20AOF%20Vs%20RDB/","content":"<h1 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h1><p>Redis持久化主要有2种方式,一种是基于内存快照的RDB格式,另外一种是基于操作日志的AOF格式,下面简单整理一下相关知识点。</p>\n<h1 id=\"RDB\"><a href=\"#RDB\" class=\"headerlink\" title=\"RDB\"></a>RDB</h1><ul>\n<li>RDB是基于内存快照的方式对Redis数据进行持久化,方式有两种,一种是自动触发,一种是手动触发。</li>\n<li>对于生成的RDB文件,Redis默认采用LZF算法进行压缩,然后进行网络传输。</li>\n</ul>\n<h2 id=\"手动触发\"><a href=\"#手动触发\" class=\"headerlink\" title=\"手动触发\"></a>手动触发</h2><ul>\n<li>方式1:执行save命令<ul>\n<li>优点:整体执行时间快</li>\n<li>缺点:执行过程中,阻塞Redis相关操作</li>\n</ul>\n</li>\n<li>方式2:执行bgsave命令<ul>\n<li>fork一个子进程,RDB持久化过程由子进程负责,整个过程中只有fork阶段是阻塞的。</li>\n<li>优点:阻塞Redis相关操作时间较短</li>\n<li>缺点:整体执行时间较长</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"自动触发\"><a href=\"#自动触发\" class=\"headerlink\" title=\"自动触发\"></a>自动触发</h2><ul>\n<li>配置文件中配置了:m秒内执行的n次修改,则触发bgsave <figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">save m n</span><br></pre></td></tr></table></figure></li>\n<li>新加入从节点,需要复制主节点全部数据,则触发bgsave生成RDB文件,提供给从节点恢复数据</li>\n<li>执行shutdown命令时,如果没有配置AOF,则触发bgsave生成RDB文件,下次启动时进行恢复。</li>\n</ul>\n<h2 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h2><ul>\n<li>优点<ul>\n<li>RDB文件是一个经过压缩的二进制文件,非常适合全量备份,全量复制等场景。</li>\n<li>Redis加载并恢复RDB文件速度非常快,远超过AOF方式。</li>\n</ul>\n</li>\n<li>缺点<ul>\n<li>创建RDB文件多少需要导致Redis停顿(无论save还是bgsave),所以不适合实时生成,无法实时备份</li>\n<li>不同版本的Redis可能无法互相兼容</li>\n<li>如果最后一次备份RDB时候down机,数据可能丢失,数据完整性无法得到保障。</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"AOF\"><a href=\"#AOF\" class=\"headerlink\" title=\"AOF\"></a>AOF</h1><ul>\n<li><p>AOF实际上是Append Only File的缩写,该方式会生成一个独立的日志文件,记录每次的写入的命令。恢复的时候则重新执行AOF文件中的每一条命令。</p>\n</li>\n<li><p>如果需要开启AOF格式,需要修改Redis配置文件,这个配置默认是不开启的:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">appendonly yes</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>重写触发配置如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">auto-aof-rewrite-percentage 100</span><br><span class=\"line\">auto-aof-rewrite-min-size 500mb</span><br></pre></td></tr></table></figure></li>\n<li><p>AOF执行分4个阶段:</p>\n<ul>\n<li>命令写入(append):将所有命令追加到缓冲区中</li>\n<li>文件同步(sync):将缓冲区中的数据同步落盘</li>\n<li>文件重写(rewrite):定期对AOF文件进行重写操作,主要是进行压缩(比如插入后又删除,这种数据直接从aof中移除)。</li>\n<li>重启加载(load):读取aof文件,并重写加载数据</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"优缺点-1\"><a href=\"#优缺点-1\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h2><ul>\n<li>优点<ul>\n<li>提供多种同步策略:<ul>\n<li>每秒同步(一般推荐使用这个)</li>\n<li>每次修改同步(效率较低,但是数据可靠)</li>\n<li>不同步</li>\n</ul>\n</li>\n<li>适合数据完整性要求较高场景</li>\n</ul>\n</li>\n<li>缺点<ul>\n<li>随着追加数据越来越多,AOF越来越大。</li>\n<li>数据恢复过程较慢。</li>\n<li>通常同等情况下,AOF文件大于RDB文件大小。</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"其他\"><a href=\"#其他\" class=\"headerlink\" title=\"其他\"></a>其他</h1><ul>\n<li>其实目前生产环境中很少有只是使用其中一种模式的,往往都是两种模式开启,作为互补使用。</li>\n<li>对于一些特殊场景,比如只是用来做缓存,则可以关闭持久化,以提升性能。</li>\n</ul>\n","categories":["Redis"],"tags":["AOF","RDB","Redis"]},{"title":"Redis学习汇总","url":"/2018/10/05/Redis%20%E5%AD%A6%E4%B9%A0%E6%B1%87%E6%80%BB/","content":"<h1 id=\"Redis-学习汇总\"><a href=\"#Redis-学习汇总\" class=\"headerlink\" title=\"Redis 学习汇总\"></a>Redis 学习汇总</h1><ul>\n<li>近期看书看博客看视频,相对较为系统的学习了一下Redis,不过版本还是比较老,主要还是3.X系列的。虽然目前最新的已经是4.X,不过老版本的基本都还兼容。</li>\n</ul>\n<h1 id=\"Redis-编译安装\"><a href=\"#Redis-编译安装\" class=\"headerlink\" title=\"Redis 编译安装\"></a>Redis 编译安装</h1><ul>\n<li>Redis安装有很多种方式,Centos可以快捷的使用yum安装,Ubuntu也可以找到apt源进行安装,我这为了尝鲜,用的的是编译安装,其实蛮简单的,我这里直接在mac下进行编译安装,步骤如下:</li>\n<li>首先下载redis源码包 :wget <a href=\"http://download.redis.io/releases/redis-4.0.11.tar.gz\">http://download.redis.io/releases/redis-4.0.11.tar.gz</a></li>\n<li>解压 tar -zxvf redis-4.0.11.tar.gz</li>\n<li>进入redis-4.0.11目录 cd redis-4.0.11</li>\n<li>执行make命令编译(好像需要command tools,之前安装过,没太注意)</li>\n<li>然后执行make install 进行安装</li>\n<li>然后就可以运行了(mac下直接发送到bin目录下面了,不需要额外配置= =!),执行redis-server 就可以看到熟悉的姐妹了</li>\n</ul>\n<h1 id=\"Redis-简易配置\"><a href=\"#Redis-简易配置\" class=\"headerlink\" title=\"Redis 简易配置\"></a>Redis 简易配置</h1><ul>\n<li>开始还是单机运行吧,创建一个conf文件,我这里叫做 redis-6379.conf ,内容如下:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">bind 127.0.0.1 #绑定Ip</span><br><span class=\"line\">port 6379 #暴露端口</span><br><span class=\"line\">daemonize yes #后台启动</span><br><span class=\"line\">pidfile /Users/eviltuzki/Public/redis/redis_6379.pid</span><br><span class=\"line\">logfile "/Users/eviltuzki/Public/redis/6379.log"</span><br><span class=\"line\">databases 16</span><br><span class=\"line\">dbfilename dump.rdb</span><br><span class=\"line\">dir /Users/eviltuzki/Public/redis #工作目录</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>配置都比较简单,就不过多解释了,主要就是一些工作目录之类的内容</li>\n<li>配置完成后通过 redis-server redis-6379.conf 启动redis,可以通过redis-cli检查是否启动成功</li>\n</ul>\n<h1 id=\"Redis-基本数据类型\"><a href=\"#Redis-基本数据类型\" class=\"headerlink\" title=\"Redis 基本数据类型\"></a>Redis 基本数据类型</h1><ul>\n<li>新版本增加了若干新的数据类型,我暂时没有使用需求,没做过多研究,主要还是针对常用的5中数据结构</li>\n</ul>\n<h2 id=\"Strings\"><a href=\"#Strings\" class=\"headerlink\" title=\"Strings\"></a>Strings</h2><ul>\n<li>这应该是Redis中使用最多最多的数据结构了,使用起来也很简单,直接set key value 进行赋值,get key进行取值</li>\n<li>常用的应用场景(好吧,我只是说一下我经常用的场景吧,在使用Token进行登录验证的时候,token存储于Redis中,使用的就是这种结构,设置好过期时间,定期刷新,可以理解为模拟Session吧)</li>\n<li>列举一些常用API</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>API</th>\n<th>解释</th>\n<th>使用示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>set</td>\n<td>设置指定 key 的值</td>\n<td>set key value</td>\n</tr>\n<tr>\n<td>mset</td>\n<td>同时设置一个或多个 key-value 对</td>\n<td>mset key value [key1 value1 …]</td>\n</tr>\n<tr>\n<td>get</td>\n<td>获取指定 key 的值</td>\n<td>get key</td>\n</tr>\n<tr>\n<td>mget</td>\n<td>获取所有(一个或多个)给定 key 的值</td>\n<td>mget key1 [key2 …]</td>\n</tr>\n<tr>\n<td>strlen</td>\n<td>返回 key 所储存的字符串值的长度</td>\n<td>strlen key</td>\n</tr>\n<tr>\n<td>incr</td>\n<td>将 key 中储存的数字值增一。</td>\n<td>incr key</td>\n</tr>\n<tr>\n<td>incrby</td>\n<td>将 key 所储存的值加上给定的增量值(increment)</td>\n<td>incrby key increment</td>\n</tr>\n<tr>\n<td>decr</td>\n<td>将 key 中储存的数字值减一</td>\n<td>decr key</td>\n</tr>\n<tr>\n<td>decrby</td>\n<td>key 所储存的值减去给定的减量值(decrement)</td>\n<td>decrby key increment</td>\n</tr>\n<tr>\n<td>append</td>\n<td>如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾</td>\n<td>append key value</td>\n</tr>\n<tr>\n<td>getset</td>\n<td>将给定 key 的值设为 value ,并返回 key 的旧值(old value)</td>\n<td>getset key value</td>\n</tr>\n<tr>\n<td>expire</td>\n<td>设置指定key的过期时间(time)</td>\n<td>expire key time</td>\n</tr>\n</tbody></table>\n<ul>\n<li>这里当然不太全面,具体参考官方文档:<a href=\"https://redis.io/commands\">https://redis.io/commands</a></li>\n</ul>\n<h2 id=\"Hash\"><a href=\"#Hash\" class=\"headerlink\" title=\"Hash\"></a>Hash</h2><ul>\n<li>Hash结构可以认为是一个微型Redis(如果把Redis简单认为是Strings类型),换成Java语言来说,Hash结构就是 Map<String,Map<String,String>> </li>\n<li>应用场景。。。。Hmm。。项目中没有用到,不过觉得如果加入用户角色权限等信息。。。。是不是session可以用这个来处理呢?或者是application。。。。Hmm。。。暂时没有想法</li>\n<li>还是列举一些常用API</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>API</th>\n<th>解释</th>\n<th>使用示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>hget</td>\n<td>获取存储在哈希表中指定字段的值。</td>\n<td>HGET key field</td>\n</tr>\n<tr>\n<td>hset</td>\n<td>将哈希表 key 中的字段 field 的值设为 value 。</td>\n<td>HSET key field value</td>\n</tr>\n<tr>\n<td>hmget</td>\n<td>获取所有给定字段的值</td>\n<td>HMGET key field1 [field2]</td>\n</tr>\n<tr>\n<td>hmset</td>\n<td>同时将多个 field-value (域-值)对设置到哈希表 key 中。</td>\n<td>HMSET key field1 value1 [field2 value2 ]</td>\n</tr>\n<tr>\n<td>hgetall</td>\n<td>获取在哈希表中指定 key 的所有字段和值</td>\n<td>HGETALL key</td>\n</tr>\n<tr>\n<td>hscan</td>\n<td>迭代哈希表中的键值对。</td>\n<td>HSCAN key cursor [MATCH pattern] [COUNT count] ``</td>\n</tr>\n<tr>\n<td>hexist</td>\n<td>查看哈希表 key 中,指定的字段是否存在。</td>\n<td>HEXISTS key field</td>\n</tr>\n<tr>\n<td>hdel</td>\n<td>删除一个或多个哈希表字段</td>\n<td>HDEL key field1 [field2]</td>\n</tr>\n<tr>\n<td>hincrby</td>\n<td>为哈希表 key 中的指定字段的整数值加上增量 increment 。</td>\n<td>HINCRBY key field increment</td>\n</tr>\n<tr>\n<td>hkeys</td>\n<td>获取所有哈希表中的字段</td>\n<td>HKEYS key</td>\n</tr>\n<tr>\n<td>hlen</td>\n<td>获取哈希表中字段的数量</td>\n<td>HLEN key</td>\n</tr>\n<tr>\n<td>hvals</td>\n<td>获取哈希表中所有值</td>\n<td>HVALS key</td>\n</tr>\n</tbody></table>\n<ul>\n<li>具体参考官方文档:<a href=\"https://redis.io/commands\">https://redis.io/commands</a></li>\n</ul>\n<h2 id=\"List\"><a href=\"#List\" class=\"headerlink\" title=\"List\"></a>List</h2><ul>\n<li>列表虽然最近项目中没有使用,不过之前的项目中大规模使用,场景是。。。把list当做消息队列了。。</li>\n<li>应用场景。。。除了消息队列。。Hmm。。我也想不到什么了。。。如果有其他场景,烦请告诉我,谢谢。。</li>\n<li>老规矩,列举一些常用API</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>API</th>\n<th>解释</th>\n<th>使用示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>blpop</td>\n<td>移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。</td>\n<td>BLPOP key1 [key2] timeout</td>\n</tr>\n<tr>\n<td>brpop</td>\n<td>移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。</td>\n<td>BRPOP key1 [key2] timeout</td>\n</tr>\n<tr>\n<td>brpoplpush</td>\n<td>从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。</td>\n<td>BRPOPLPUSH source destination timeout</td>\n</tr>\n<tr>\n<td>lindex</td>\n<td>通过索引获取列表中的元素</td>\n<td>LINDEX key index</td>\n</tr>\n<tr>\n<td>linsert</td>\n<td>在列表的元素前或者后插入元素</td>\n<td>LINSERT key BEFORE\\AFTER pivot value</td>\n</tr>\n<tr>\n<td>llen</td>\n<td>获取列表长度</td>\n<td>LLEN key</td>\n</tr>\n<tr>\n<td>lpop</td>\n<td>移出并获取列表的第一个元素</td>\n<td>LPOP key</td>\n</tr>\n<tr>\n<td>lpush</td>\n<td>将一个或多个值插入到列表头部</td>\n<td>LPUSH key value1 [value2]</td>\n</tr>\n<tr>\n<td>lpushx</td>\n<td>将一个值插入到已存在的列表头部</td>\n<td>LPUSHX key value</td>\n</tr>\n<tr>\n<td>lrange</td>\n<td>获取列表指定范围内的元素</td>\n<td>LRANGE key start stop</td>\n</tr>\n<tr>\n<td>lrem</td>\n<td>移除列表元素</td>\n<td>LREM key count value</td>\n</tr>\n<tr>\n<td>lset</td>\n<td>通过索引设置列表元素的值</td>\n<td>LSET key index value</td>\n</tr>\n<tr>\n<td>ltrim</td>\n<td>对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。</td>\n<td>LTRIM key start stop</td>\n</tr>\n<tr>\n<td>rpop</td>\n<td>移除并获取列表最后一个元素</td>\n<td>RPOP key</td>\n</tr>\n<tr>\n<td>rpoplpush</td>\n<td>移除列表的最后一个元素,并将该元素添加到另一个列表并返回</td>\n<td>RPOPLPUSH source destination</td>\n</tr>\n<tr>\n<td>rpush</td>\n<td>在列表中添加一个或多个值</td>\n<td>RPUSH key value1 [value2]</td>\n</tr>\n<tr>\n<td>rpushx</td>\n<td>为已存在的列表添加值</td>\n<td>RPUSHX key value</td>\n</tr>\n</tbody></table>\n<ul>\n<li>具体参考官方文档:<a href=\"https://redis.io/commands\">https://redis.io/commands</a></li>\n</ul>\n<h2 id=\"Set\"><a href=\"#Set\" class=\"headerlink\" title=\"Set\"></a>Set</h2><ul>\n<li>Set集合,就知道这个是一个集合,无序,不可以重复(Hmm和java中的Set很相似)</li>\n<li>应用场景。。。没想到。。。Hmm。。待补充</li>\n<li>常用API</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>API</th>\n<th>解释</th>\n<th>使用示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>sadd</td>\n<td>向集合添加一个或多个成员</td>\n<td>SADD key member1 [member2]</td>\n</tr>\n<tr>\n<td>scard</td>\n<td>获取集合的成员数</td>\n<td>SCARD key</td>\n</tr>\n<tr>\n<td>sdiff</td>\n<td>返回给定所有集合的差集</td>\n<td>SDIFF key1 [key2]</td>\n</tr>\n<tr>\n<td>sdiffstore</td>\n<td>返回给定所有集合的差集并存储在 destination 中</td>\n<td>SDIFFSTORE destination key1 [key2]</td>\n</tr>\n<tr>\n<td>sinter</td>\n<td>返回给定所有集合的交集</td>\n<td>SINTER key1 [key2]</td>\n</tr>\n<tr>\n<td>sinterstore</td>\n<td>返回给定所有集合的交集并存储在 destination 中</td>\n<td>SINTERSTORE destination key1 [key2]</td>\n</tr>\n<tr>\n<td>sismember</td>\n<td>判断 member 元素是否是集合 key 的成员</td>\n<td>SISMEMBER key member</td>\n</tr>\n<tr>\n<td>smembers</td>\n<td>返回集合中的所有成员</td>\n<td>SMEMBERS key</td>\n</tr>\n<tr>\n<td>smove</td>\n<td>将 member 元素从 source 集合移动到 destination 集合</td>\n<td>SMOVE source destination member</td>\n</tr>\n<tr>\n<td>spop</td>\n<td>移除并返回集合中的一个随机元素</td>\n<td>SPOP key</td>\n</tr>\n<tr>\n<td>srandmember</td>\n<td>返回集合中一个或多个随机数</td>\n<td>SRANDMEMBER key [count]</td>\n</tr>\n<tr>\n<td>srem</td>\n<td>移除集合中一个或多个成员</td>\n<td>SREM key member1 [member2]</td>\n</tr>\n<tr>\n<td>sunion</td>\n<td>返回所有给定集合的并集</td>\n<td>SUNION key1 [key2]</td>\n</tr>\n<tr>\n<td>sunionstore</td>\n<td>所有给定集合的并集存储在 destination 集合中</td>\n<td>SUNIONSTORE destination key1 [key2]</td>\n</tr>\n<tr>\n<td>sscan</td>\n<td>迭代集合中的元素</td>\n<td>SSCAN key cursor [MATCH pattern] [COUNT count]</td>\n</tr>\n</tbody></table>\n<ul>\n<li>具体参考官方文档:<a href=\"https://redis.io/commands\">https://redis.io/commands</a></li>\n</ul>\n<h2 id=\"Zset\"><a href=\"#Zset\" class=\"headerlink\" title=\"Zset\"></a>Zset</h2><ul>\n<li>Hmm Zset 也叫做Sorted Set 就是一个排序的集合,简单的说就是Set的有序版本(不过这个和Java的SortedSet不太一样。。),区别是什么呢,区别就是每个元素都有一个Score,排序的依据呢就是这个Score了。。</li>\n<li>应用场景,项目中倒是用到了,不过感觉用到并不是太对。。。所以不说了。老项目使用这个实现了一个排行榜,Hmm还是可以的,定期刷入到MySQL中持久化,也不怕数据丢失什么的。。。挺好</li>\n<li>说一下常用API吧</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>API</th>\n<th>解释</th>\n<th>使用示例</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>zadd</td>\n<td>向有序集合添加一个或多个成员,或者更新已存在成员的分数</td>\n<td>ZADD key score1 member1 [score2 member2]</td>\n</tr>\n<tr>\n<td>zcard</td>\n<td>获取有序集合的成员数</td>\n<td>ZCARD key</td>\n</tr>\n<tr>\n<td>zcount</td>\n<td>计算在有序集合中指定区间分数的成员数</td>\n<td>ZCOUNT key min max</td>\n</tr>\n<tr>\n<td>zincrby</td>\n<td>有序集合中对指定成员的分数加上增量 increment</td>\n<td>ZINCRBY key increment member</td>\n</tr>\n<tr>\n<td>zinterstore</td>\n<td>计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中</td>\n<td>ZINTERSTORE destination numkeys key [key …]</td>\n</tr>\n<tr>\n<td>zlexcount</td>\n<td>在有序集合中计算指定字典区间内成员数量</td>\n<td>ZLEXCOUNT key min max</td>\n</tr>\n<tr>\n<td>zrange</td>\n<td>通过索引区间返回有序集合成指定区间内的成员</td>\n<td>ZRANGE key start stop [WITHSCORES]</td>\n</tr>\n<tr>\n<td>zrangebylex</td>\n<td>通过字典区间返回有序集合的成员</td>\n<td>ZRANGEBYLEX key min max [LIMIT offset count]</td>\n</tr>\n<tr>\n<td>zrangebyscore</td>\n<td>通过分数返回有序集合指定区间内的成员</td>\n<td>ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]</td>\n</tr>\n<tr>\n<td>zrank</td>\n<td>返回有序集合中指定成员的索引</td>\n<td>ZRANK key member</td>\n</tr>\n<tr>\n<td>zrem</td>\n<td>移除有序集合中的一个或多个成员</td>\n<td>ZREM key member [member …]</td>\n</tr>\n<tr>\n<td>zremrangebylex</td>\n<td>移除有序集合中给定的字典区间的所有成员</td>\n<td>ZREMRANGEBYLEX key min max</td>\n</tr>\n<tr>\n<td>zremrangebyrank</td>\n<td>移除有序集合中给定的排名区间的所有成员</td>\n<td>ZREMRANGEBYRANK key start stop</td>\n</tr>\n<tr>\n<td>zremrangebyscore</td>\n<td>移除有序集合中给定的分数区间的所有成员</td>\n<td>ZREMRANGEBYSCORE key min max</td>\n</tr>\n<tr>\n<td>zrevrange</td>\n<td>返回有序集中指定区间内的成员,通过索引,分数从高到底</td>\n<td>ZREVRANGE key start stop [WITHSCORES]</td>\n</tr>\n<tr>\n<td>zrevrangebyscore</td>\n<td>返回有序集中指定分数区间内的成员,分数从高到低排序</td>\n<td>ZREVRANGEBYSCORE key max min [WITHSCORES]</td>\n</tr>\n<tr>\n<td>zrevrank</td>\n<td>返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序</td>\n<td>ZREVRANK key member</td>\n</tr>\n<tr>\n<td>zscore</td>\n<td>返回有序集中,成员的分数值</td>\n<td>ZSCORE key member</td>\n</tr>\n<tr>\n<td>zunionstore</td>\n<td>计算给定的一个或多个有序集的并集,并存储在新的 key 中</td>\n<td>ZUNIONSTORE destination numkeys key [key …]</td>\n</tr>\n<tr>\n<td>zscan</td>\n<td>迭代有序集合中的元素(包括元素成员和元素分值)</td>\n<td>ZSCAN key cursor [MATCH pattern] [COUNT count]</td>\n</tr>\n</tbody></table>\n<ul>\n<li>具体参考官方文档:<a href=\"https://redis.io/commands\">https://redis.io/commands</a></li>\n</ul>\n<h1 id=\"Redis-读写分离结构\"><a href=\"#Redis-读写分离结构\" class=\"headerlink\" title=\"Redis 读写分离结构\"></a>Redis 读写分离结构</h1><ul>\n<li>读写分离其实不是太复杂,简单来说就是多个Redis组成小”集群”,注意我这里的集群是带有引号的哈,\b这并不是一个真正意义上的集群,而是主从结构(Master&Slave),可以是一主多从,也可以是一主一从,甚至可以使只有一个Master(这个就退化成了。。。单机模式了。。)</li>\n<li>说一下怎么配置吧<ul>\n<li><p>先参考 Redis 简易配置,并启动6379节点</p>\n</li>\n<li><p>拷贝一份配置文件,将所有的6379替换为6380 (Hmm,我比较懒。。就单机先这么搞了。。)</p>\n</li>\n<li><p>启动6380这个实例 redis-server redis-6380.conf</p>\n</li>\n<li><p>查看是否都启动成功了,执行 \bps -ef |grep redis-server|grep -v ‘grep’ 我这里显示如下,表示两个实例已经启动成功</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">501 42485 1 0 12:29下午 ?? 0:07.18 redis-server 127.0.0.1:6379</span><br><span class=\"line\">501 43688 1 0 10:20下午 ?? 0:00.33 redis-server 127.0.0.1:6380</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>执行 redis-cli -p 6380 info replication 看到6380实例目前是以Master角色运行</p>\n</li>\n</ul>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># Replication</span><br><span class=\"line\">role:master</span><br><span class=\"line\">connected_slaves:0</span><br><span class=\"line\">master_replid:13f6fcea2807ec7a78d52ecf76c382c0ba7d9c55</span><br><span class=\"line\">master_replid2:0000000000000000000000000000000000000000</span><br><span class=\"line\">master_repl_offset:0</span><br><span class=\"line\">second_repl_offset:-1</span><br><span class=\"line\">repl_backlog_active:0</span><br><span class=\"line\">repl_backlog_size:1048576</span><br><span class=\"line\">repl_backlog_first_byte_offset:0</span><br><span class=\"line\">repl_backlog_histlen:0</span><br></pre></td></tr></table></figure>\n\n<pre><code>- 给6380分配角色slave,跟从Master 执行 redis-cli -p 6380 slaveof 127.0.0.1 6379\n- 执行 redis-cli -p 6380 info replication 看到6380实例目前是以Slave角色运行\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># Replication</span><br><span class=\"line\">role:slave</span><br><span class=\"line\">master_host:127.0.0.1</span><br><span class=\"line\">master_port:6379</span><br><span class=\"line\">master_link_status:up</span><br><span class=\"line\">master_last_io_seconds_ago:8</span><br><span class=\"line\">master_sync_in_progress:0</span><br><span class=\"line\">slave_repl_offset:154</span><br><span class=\"line\">slave_priority:100</span><br><span class=\"line\">slave_read_only:1</span><br><span class=\"line\">connected_slaves:0</span><br><span class=\"line\">master_replid:7a2c11e996810f76bdc72765130dcf47b5af4ab8</span><br><span class=\"line\">master_replid2:0000000000000000000000000000000000000000</span><br><span class=\"line\">master_repl_offset:154</span><br><span class=\"line\">second_repl_offset:-1</span><br><span class=\"line\">repl_backlog_active:1</span><br><span class=\"line\">repl_backlog_size:1048576</span><br><span class=\"line\">repl_backlog_first_byte_offset:71</span><br><span class=\"line\">repl_backlog_histlen:84</span><br></pre></td></tr></table></figure>\n\n<pre><code>- \b这种是通过Redis Client\b来分配角色,还可以在配置文件中进行配置,修改redis-6380.conf如下,并重启:\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">bind 127.0.0.1</span><br><span class=\"line\">port 6380</span><br><span class=\"line\">daemonize yes</span><br><span class=\"line\">pidfile /Users/eviltuzki/Public/redis/redis_6380.pid</span><br><span class=\"line\">logfile "/Users/eviltuzki/Public/redis/6380.log"</span><br><span class=\"line\">databases 16</span><br><span class=\"line\">dbfilename dump.rdb</span><br><span class=\"line\">dir /Users/eviltuzki/Public/redis</span><br><span class=\"line\">slaveof 127.0.0.1 6379</span><br></pre></td></tr></table></figure>\n<ul>\n<li>实际生产环境通常使用配置文件的方式配置主从结构</li>\n<li>完成了配置简单尝试一下:<ul>\n<li>在Master set value -> redis-cli -p 6379 set hello world -> OK</li>\n<li>在Slave get value -> redis-cli -p 6380 get hello -> “world”</li>\n<li>在Slave set value -> redis-cli -p 6380 set test test -> (error) READONLY You can’t write against a read only slave.</li>\n</ul>\n</li>\n<li>\b基本测试完成,主节点可读可写,从节点同步主节点,只能读取,不能写入,Hmm。。。如果要多加入几个Slave节点。。Copy一下配置文件就好了。。。</li>\n</ul>\n<h1 id=\"Redis-HA-之-sentinel\"><a href=\"#Redis-HA-之-sentinel\" class=\"headerlink\" title=\"Redis HA 之 sentinel\"></a>Redis HA 之 sentinel</h1><h2 id=\"说一下背景\"><a href=\"#说一下背景\" class=\"headerlink\" title=\"说一下背景\"></a>说一下背景</h2><ul>\n<li>Hmm 上文说道了集群,\b其实主从结构并不是一个可靠的集群,\b比如某天一大波僵尸来袭。。。跑题了,一大波流量来袭。。。Master挂了。。。然后。。。Hmm。。。没有节点可以写入了,咋办呢?</li>\n<li>解决方法也不是太复杂,举个场景:3台机器,1Master 2Slave,然后某天。。。Master突然挂了。。剩下2个Slave,这个时候咋办?切换一下,让其中一个Slave变成Master,另外一个跟随这个\b新的Master,这样就\u001b1主1从1挂机(鄙视挂机党。。。)。好赖可以正常提供服务了,等挂机服务器启动起来了,将它设置为新Master的Slave节点,这样就完成了Master Slave的转换了,然后就可以正常提供服务了。</li>\n<li>听起来上面的方案还不错,其实服务端执行起来也不太复杂。\b如下:<ul>\n<li>Master挂了,剩下2个Slave,记为Slave1 Slave2</li>\n<li>对Slave 1 执行 slaveof no one,将Slave 1 升级为新Master</li>\n<li>对Slave 2 执行 slaveof Slave1,将Slave 2设置跟从新的Master</li>\n<li>等原Master启动,执行slaveof Slave1,将原Master设置为Slave并且跟从\b新的Master</li>\n</ul>\n</li>\n<li>为啥说服务端简单呢?Client连接服务器也得跟着切换啊。。\bClient写入只能写入到Master节点,服务端经过这么一折腾,Client也要跟着切换IP\b,才能正常访问。</li>\n<li>所以呢,官方提供了sentinel 一种HA方案,服务端的切换可以自动执行,sentinel 节点负责监控Master Slave状态,如果切换了,\b同时通知Client进行切换,达到\b可服务状态。</li>\n</ul>\n<h2 id=\"怎么配置?\"><a href=\"#怎么配置?\" class=\"headerlink\" title=\"怎么配置?\"></a>怎么配置?</h2><ul>\n<li><p>首先要额外准备机器作为sentinel节点,我这里偷懒,继续单机运行。。。(Hmm,穷人。。没有太多机器。。也不想搞虚拟机)</p>\n</li>\n<li><p>先启动一个Maste 6379,2个Slave 6380 8381,执行ps -ef |grep redis|grep -v grep 如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">501 42485 1 0 12:29下午 ?? 0:10.87 redis-server 127.0.0.1:6379</span><br><span class=\"line\">501 43910 1 0 10:47下午 ?? 0:02.45 redis-server 127.0.0.1:6380</span><br><span class=\"line\">501 44117 1 0 11:17下午 ?? 0:00.53 redis-server 127.0.0.1:6381</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>查看Master状态 redis-cli -p 6379 info replication</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># Replication</span><br><span class=\"line\">role:master</span><br><span class=\"line\">connected_slaves:2</span><br><span class=\"line\">slave0:ip=127.0.0.1,port=6380,state=online,offset=3796,lag=0</span><br><span class=\"line\">slave1:ip=127.0.0.1,port=6381,state=online,offset=3796,lag=0</span><br><span class=\"line\">master_replid:7a2c11e996810f76bdc72765130dcf47b5af4ab8</span><br><span class=\"line\">master_replid2:0000000000000000000000000000000000000000</span><br><span class=\"line\">master_repl_offset:3796</span><br><span class=\"line\">second_repl_offset:-1</span><br><span class=\"line\">repl_backlog_active:1</span><br><span class=\"line\">repl_backlog_size:1048576</span><br><span class=\"line\">repl_backlog_first_byte_offset:1</span><br><span class=\"line\">repl_backlog_histlen:3796</span><br></pre></td></tr></table></figure></li>\n<li><p>全部启动成功,然后开始配置sentinel节点</p>\n</li>\n<li><p>从Redis解压文件中可以看到一个sentinel.conf文件,Hmm。。。懒人。。直接Copy这个开始改造,sentinel-26379.conf 如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">port 26379</span><br><span class=\"line\">dir /Users/eviltuzki/Public/redis/</span><br><span class=\"line\">sentinel monitor mymaster 127.0.0.1 6379 2 # 监控mymaster集群,Master地址为127.0.0.1 6379,当2个sentinel认为Master有问题,则进行Master转换</span><br><span class=\"line\">sentinel down-after-milliseconds mymaster 30000 #\b下线Master时间30s</span><br><span class=\"line\">sentinel parallel-syncs mymaster 1</span><br><span class=\"line\">sentinel failover-timeout mymaster 180000</span><br><span class=\"line\">sentinel deny-scripts-reconfig yes</span><br><span class=\"line\">daemonize yes #守护进程方式启动</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>\b生成对应的3份,分别是sentinel-26379.conf、sentinel-26380.conf、sentinel-26381.conf,然后通过redis-sentinel sentinel-263xx.conf启动sentinel节点</p>\n</li>\n<li><p>查看进程,是否启动成功:ps -ef |grep sent|grep -v grep</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">501 44288 1 0 11:36下午 ?? 0:00.64 redis-sentinel *:26379 [sentinel]</span><br><span class=\"line\">501 44291 1 0 11:36下午 ?? 0:00.61 redis-sentinel *:26380 [sentinel]</span><br><span class=\"line\">501 44293 1 0 11:36下午 ?? 0:00.63 redis-sentinel *:26381 [sentinel]</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>查看redis-sentinel状态:redis-cli -p 26379 info sentinel</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># Sentinel</span><br><span class=\"line\">sentinel_masters:1</span><br><span class=\"line\">sentinel_tilt:0</span><br><span class=\"line\">sentinel_running_scripts:0</span><br><span class=\"line\">sentinel_scripts_queue_length:0</span><br><span class=\"line\">sentinel_simulate_failure_flags:0</span><br><span class=\"line\">master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>可以看到master0状态OK,2个Slave,3个sentinels,一切正常。接下来就可以使用对应的Client进行连接了。\b</p>\n</li>\n<li><p>Hmm 这块内容有点太多了。。。后面单开Java Client\u001b连接。。</p>\n</li>\n<li><p>接下来模拟一下事故吧:</p>\n<ul>\n<li><p>查看一下刚刚配置的sentinel-26379.conf文件,多了一些内容:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">port 26379</span><br><span class=\"line\">dir "/Users/zhaojian/Public/redis"</span><br><span class=\"line\">sentinel myid bca8111060bbda4a42ec3744391d1f40ca1fda00</span><br><span class=\"line\">sentinel deny-scripts-reconfig yes</span><br><span class=\"line\">sentinel monitor mymaster 127.0.0.1 6379 2</span><br><span class=\"line\">sentinel config-epoch mymaster 0</span><br><span class=\"line\">sentinel leader-epoch mymaster 0</span><br><span class=\"line\"># Generated by CONFIG REWRITE</span><br><span class=\"line\">sentinel known-slave mymaster 127.0.0.1 6381</span><br><span class=\"line\">sentinel known-slave mymaster 127.0.0.1 6380</span><br><span class=\"line\">sentinel known-sentinel mymaster 127.0.0.1 26380 ba913a9c10e46fd4df76bf0289721136031926ef</span><br><span class=\"line\">daemonize yes</span><br><span class=\"line\">sentinel known-sentinel mymaster 127.0.0.1 26381 7c387d9b2e95ac2338655fb8f5011f277635dade</span><br><span class=\"line\">sentinel current-epoch 0</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>主节点和从节点信息都能看到,现在我准备下线Master节点,看看会有什么反应(Hmm怎么下线呢?直接kill掉吧)</p>\n</li>\n<li><p>等待一小会儿,Hmm,大概30S左右吧,再次查看sentinel-26379.conf文件,发现有些变化了</p>\n</li>\n</ul>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">port 26379</span><br><span class=\"line\">dir "/Users/zhaojian/Public/redis"</span><br><span class=\"line\">sentinel myid bca8111060bbda4a42ec3744391d1f40ca1fda00</span><br><span class=\"line\">sentinel deny-scripts-reconfig yes</span><br><span class=\"line\">sentinel monitor mymaster 127.0.0.1 6380 2</span><br><span class=\"line\">sentinel config-epoch mymaster 1</span><br><span class=\"line\">sentinel leader-epoch mymaster 1</span><br><span class=\"line\"># Generated by CONFIG REWRITE</span><br><span class=\"line\">sentinel known-slave mymaster 127.0.0.1 6379</span><br><span class=\"line\">sentinel known-slave mymaster 127.0.0.1 6381</span><br><span class=\"line\">sentinel known-sentinel mymaster 127.0.0.1 26380 ba913a9c10e46fd4df76bf0289721136031926ef</span><br><span class=\"line\">daemonize yes</span><br><span class=\"line\">sentinel known-sentinel mymaster 127.0.0.1 26381 7c387d9b2e95ac2338655fb8f5011f277635dade</span><br><span class=\"line\">sentinel current-epoch 1</span><br></pre></td></tr></table></figure>\n<pre><code>- Hmm,首先Master已经切换了,不再是6379了,而是6380,而6379变成了known-slave,也就是Slave节点,无妨。。。反正现在也不工作。。。\n- 那我现在恢复一下6379\b节点(重新执行redis-server redis-6379.conf),注意,这里的配置6379可是Master哦~\n- 执行 redis-cli -p 6379 info replication ,信息如下:\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># Replication</span><br><span class=\"line\">role:slave</span><br><span class=\"line\">master_host:127.0.0.1</span><br><span class=\"line\">master_port:6380</span><br><span class=\"line\">master_link_status:up</span><br><span class=\"line\">master_last_io_seconds_ago:0</span><br><span class=\"line\">master_sync_in_progress:0</span><br><span class=\"line\">slave_repl_offset:162525</span><br><span class=\"line\">slave_priority:100</span><br><span class=\"line\">slave_read_only:1</span><br><span class=\"line\">connected_slaves:0</span><br><span class=\"line\">master_replid:a6939796e0faba3555a74719ec18498e7b247756</span><br><span class=\"line\">master_replid2:0000000000000000000000000000000000000000</span><br><span class=\"line\">master_repl_offset:162525</span><br><span class=\"line\">second_repl_offset:-1</span><br><span class=\"line\">repl_backlog_active:1</span><br><span class=\"line\">repl_backlog_size:1048576</span><br><span class=\"line\">repl_backlog_first_byte_offset:155146</span><br><span class=\"line\">repl_backlog_histlen:7380</span><br></pre></td></tr></table></figure>\n<pre><code>- Hmm,说明sentinel还是蛮智能的,尽管6379之前是Master,但是选举出新的Master之后,旧的Master会被 降级到Slave节点,避免\b出现多个Master。\n- 附带看一下6381和6380\b的日志:\n- 首先是6380的:\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">43910:S 06 Oct 23:45:51.903 * MASTER <-> SLAVE sync started</span><br><span class=\"line\">43910:S 06 Oct 23:45:51.904 # Error condition on socket for SYNC: Connection refused</span><br><span class=\"line\">43910:S 06 Oct 23:45:52.914 * Connecting to MASTER 127.0.0.1:6379</span><br><span class=\"line\"></span><br><span class=\"line\">...</span><br><span class=\"line\">43910:S 06 Oct 23:46:21.205 * MASTER <-> SLAVE sync started</span><br><span class=\"line\">43910:S 06 Oct 23:46:21.206 # Error condition on socket for SYNC: Connection refused</span><br><span class=\"line\">43910:M 06 Oct 23:46:21.960 # Setting secondary replication ID to 7a2c11e996810f76bdc72765130dcf47b5af4ab8, valid up to offset: 111989. New replication ID is a6939796e0faba3555a74719ec18498e7b247756</span><br><span class=\"line\">43910:M 06 Oct 23:46:21.960 * Discarding previously cached master state.</span><br><span class=\"line\">43910:M 06 Oct 23:46:21.962 * MASTER MODE enabled (user request from 'id=13 addr=127.0.0.1:51348 fd=12 name=sentinel-7c387d9b-cmd age=568 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0 omem=0 events=r cmd=exec')</span><br><span class=\"line\">43910:M 06 Oct 23:46:21.963 # CONFIG REWRITE executed with success.</span><br><span class=\"line\">43910:M 06 Oct 23:46:23.354 * Slave 127.0.0.1:6381 asks for synchronization</span><br><span class=\"line\">43910:M 06 Oct 23:46:23.354 * Partial resynchronization request from 127.0.0.1:6381 accepted. Sending 422 bytes of backlog starting from offset 111989.</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.772 * Slave 127.0.0.1:6379 asks for synchronization</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.772 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for '4a931cc983685e6f1b029225185120eee25f03b4', my replication IDs are 'a6939796e0faba3555a74719ec18498e7b247756' and '7a2c11e996810f76bdc72765130dcf47b5af4ab8')</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.773 * Starting BGSAVE for SYNC with target: disk</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.773 * Background saving started by pid 44404</span><br><span class=\"line\">44404:C 06 Oct 23:49:59.775 * DB saved on disk</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.855 * Background saving terminated with success</span><br><span class=\"line\">43910:M 06 Oct 23:49:59.856 * Synchronization with slave 127.0.0.1:6379 succeeded</span><br></pre></td></tr></table></figure>\n\n<pre><code>- 然后看一下6381的:\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">...</span><br><span class=\"line\">44117:S 06 Oct 23:46:22.338 * Connecting to MASTER 127.0.0.1:6379</span><br><span class=\"line\">44117:S 06 Oct 23:46:22.339 * MASTER <-> SLAVE sync started</span><br><span class=\"line\">44117:S 06 Oct 23:46:22.340 # Error condition on socket for SYNC: Connection refused</span><br><span class=\"line\">44117:S 06 Oct 23:46:22.866 * SLAVE OF 127.0.0.1:6380 enabled (user request from 'id=11 addr=127.0.0.1:51346 fd=12 name=sentinel-7c387d9b-cmd age=569 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=133 qbuf-free=32635 obl=36 oll=0 omem=0 events=r cmd=exec')</span><br><span class=\"line\">44117:S 06 Oct 23:46:22.867 # CONFIG REWRITE executed with success.</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.351 * Connecting to MASTER 127.0.0.1:6380</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.351 * MASTER <-> SLAVE sync started</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.352 * Non blocking connect for SYNC fired the event.</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.353 * Master replied to PING, replication can continue...</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.353 * Trying a partial resynchronization (request 7a2c11e996810f76bdc72765130dcf47b5af4ab8:111989).</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.354 * Successful partial resynchronization with master.</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.355 # Master replication ID changed to a6939796e0faba3555a74719ec18498e7b247756</span><br><span class=\"line\">44117:S 06 Oct 23:46:23.355 * MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.</span><br></pre></td></tr></table></figure>\n\n<pre><code>- 额,刚想起来6379重启后的日志也看一下:\n</code></pre>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">42485:M 06 Oct 23:45:51.599 # User requested shutdown...</span><br><span class=\"line\">42485:M 06 Oct 23:45:51.600 * Saving the final RDB snapshot before exiting.</span><br><span class=\"line\">42485:M 06 Oct 23:45:51.601 * DB saved on disk</span><br><span class=\"line\">42485:M 06 Oct 23:45:51.601 * Removing the pid file.</span><br><span class=\"line\">42485:M 06 Oct 23:45:51.602 # Redis is now ready to exit, bye bye...</span><br><span class=\"line\">44398:C 06 Oct 23:49:48.652 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo</span><br><span class=\"line\">44398:C 06 Oct 23:49:48.653 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=44398, just started</span><br><span class=\"line\">44398:C 06 Oct 23:49:48.654 # Configuration loaded</span><br><span class=\"line\">44399:M 06 Oct 23:49:48.656 * Increased maximum number of open files to 10032 (it was originally set to 4864).</span><br><span class=\"line\">44399:M 06 Oct 23:49:48.657 * Running mode=standalone, port=6379.</span><br><span class=\"line\">44399:M 06 Oct 23:49:48.657 # Server initialized</span><br><span class=\"line\">44399:M 06 Oct 23:49:48.657 * DB loaded from disk: 0.000 seconds</span><br><span class=\"line\">44399:M 06 Oct 23:49:48.658 * Ready to accept connections</span><br><span class=\"line\">44399:S 06 Oct 23:49:58.958 * Before turning into a slave, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.</span><br><span class=\"line\">44399:S 06 Oct 23:49:58.958 * SLAVE OF 127.0.0.1:6380 enabled (user request from 'id=3 addr=127.0.0.1:52967 fd=7 name=sentinel-ba913a9c-cmd age=10 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0 omem=0 events=r cmd=exec')</span><br><span class=\"line\">44399:S 06 Oct 23:49:58.960 # CONFIG REWRITE executed with success.</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.769 * Connecting to MASTER 127.0.0.1:6380</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.769 * MASTER <-> SLAVE sync started</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.770 * Non blocking connect for SYNC fired the event.</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.771 * Master replied to PING, replication can continue...</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.771 * Trying a partial resynchronization (request 4a931cc983685e6f1b029225185120eee25f03b4:1).</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.774 * Full resync from master: a6939796e0faba3555a74719ec18498e7b247756:155145</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.774 * Discarding previously cached master state.</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.856 * MASTER <-> SLAVE sync: receiving 202 bytes from master</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.857 * MASTER <-> SLAVE sync: Flushing old data</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.857 * MASTER <-> SLAVE sync: Loading DB in memory</span><br><span class=\"line\">44399:S 06 Oct 23:49:59.858 * MASTER <-> SLAVE sync: Finished with success</span><br></pre></td></tr></table></figure>\n\n<pre><code>- 从日志中可以看到,6379下线以后,6380和6381经历了一段时间(约30s)的找不到Master,之后6380收到了sentinel-7c387d9b-cmd发送的请求,转换角色为Master。6381收到sentinel-7c387d9b-cmd发送的请求,变更为跟随6380而不是6379。等6379重新启动后收到了sentinel-ba913a9c-cmd的请求,降级为Slave并且跟随6380。\n</code></pre>\n<ul>\n<li>补充说明一下sentinel一定要集群部署,不能单点!否则网络等因素会导致集群来回切换角色,另外sentinel本身单点也有风险!</li>\n</ul>\n<h1 id=\"Redis-HA-之-Clusterv\"><a href=\"#Redis-HA-之-Clusterv\" class=\"headerlink\" title=\"Redis HA 之 Clusterv\"></a>Redis HA 之 Clusterv</h1>","categories":["Redis"],"tags":["Redis"]},{"title":"Java多线程分析-ReentrantLock","url":"/2019/07/21/ReentrantLock/","content":"<h1 id=\"Field\"><a href=\"#Field\" class=\"headerlink\" title=\"Field\"></a>Field</h1><h2 id=\"final-Sync-sync\"><a href=\"#final-Sync-sync\" class=\"headerlink\" title=\"final Sync sync;\"></a>final Sync sync;</h2><ul>\n<li>核心Field,实际锁相关操作均在这里,实际上是对ReentrantLock的包装</li>\n</ul>\n<h1 id=\"方法\"><a href=\"#方法\" class=\"headerlink\" title=\"方法\"></a>方法</h1><h2 id=\"lock\"><a href=\"#lock\" class=\"headerlink\" title=\"lock()\"></a>lock()</h2><ul>\n<li>获取锁操作,直接委托给sync,有公平和非公平两种实现,具体看NonfairSync和FairSync</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void lock() {</span><br><span class=\"line\"> sync.lock();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"lockInterruptibly\"><a href=\"#lockInterruptibly\" class=\"headerlink\" title=\"lockInterruptibly()\"></a>lockInterruptibly()</h2><ul>\n<li>支持中断的获取锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void lockInterruptibly() throws InterruptedException {</span><br><span class=\"line\"> sync.acquireInterruptibly(1);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"tryLock\"><a href=\"#tryLock\" class=\"headerlink\" title=\"tryLock()\"></a>tryLock()</h2><ul>\n<li>非阻塞方式获取锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean tryLock() {</span><br><span class=\"line\"> return sync.nonfairTryAcquire(1);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"tryLock-long-timeout-TimeUnit-unit\"><a href=\"#tryLock-long-timeout-TimeUnit-unit\" class=\"headerlink\" title=\"tryLock(long timeout, TimeUnit unit)\"></a>tryLock(long timeout, TimeUnit unit)</h2><ul>\n<li>带有超时的获取锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean tryLock(long timeout, TimeUnit unit)</span><br><span class=\"line\"> throws InterruptedException {</span><br><span class=\"line\"> return sync.tryAcquireNanos(1, unit.toNanos(timeout));</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"unlock\"><a href=\"#unlock\" class=\"headerlink\" title=\"unlock()\"></a>unlock()</h2><ul>\n<li>释放锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public void unlock() {</span><br><span class=\"line\"> sync.release(1);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"newCondition\"><a href=\"#newCondition\" class=\"headerlink\" title=\"newCondition()\"></a>newCondition()</h2><ul>\n<li>创建条件变量</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public Condition newCondition() {</span><br><span class=\"line\"> return sync.newCondition();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getHoldCount\"><a href=\"#getHoldCount\" class=\"headerlink\" title=\"getHoldCount()\"></a>getHoldCount()</h2><ul>\n<li>当前线程持有锁的个数</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public int getHoldCount() {</span><br><span class=\"line\"> return sync.getHoldCount();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"isHeldByCurrentThread\"><a href=\"#isHeldByCurrentThread\" class=\"headerlink\" title=\"isHeldByCurrentThread()\"></a>isHeldByCurrentThread()</h2><ul>\n<li>锁是否被当前线程支持</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean isHeldByCurrentThread() {</span><br><span class=\"line\"> return sync.isHeldExclusively();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"isLocked\"><a href=\"#isLocked\" class=\"headerlink\" title=\"isLocked()\"></a>isLocked()</h2><ul>\n<li>是否处于锁定状态</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean isLocked() {</span><br><span class=\"line\"> return sync.isLocked();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"isFair\"><a href=\"#isFair\" class=\"headerlink\" title=\"isFair()\"></a>isFair()</h2><ul>\n<li>是否为公平锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean isFair() {</span><br><span class=\"line\"> return sync instanceof FairSync;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getOwner\"><a href=\"#getOwner\" class=\"headerlink\" title=\"getOwner()\"></a>getOwner()</h2><ul>\n<li>持有锁的线程</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected Thread getOwner() {</span><br><span class=\"line\"> return sync.getOwner();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"hasQueuedThreads\"><a href=\"#hasQueuedThreads\" class=\"headerlink\" title=\"hasQueuedThreads()\"></a>hasQueuedThreads()</h2><ul>\n<li>是否有线程在等待获取锁</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasQueuedThreads() {</span><br><span class=\"line\"> return sync.hasQueuedThreads();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"hasQueuedThread-Thread-thread\"><a href=\"#hasQueuedThread-Thread-thread\" class=\"headerlink\" title=\"hasQueuedThread(Thread thread)\"></a>hasQueuedThread(Thread thread)</h2><ul>\n<li>线程是否在等待获取锁的队列中</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final boolean hasQueuedThread(Thread thread) {</span><br><span class=\"line\"> return sync.isQueued(thread);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getQueueLength\"><a href=\"#getQueueLength\" class=\"headerlink\" title=\"getQueueLength()\"></a>getQueueLength()</h2><ul>\n<li>等待队列的长度</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public final int getQueueLength() {</span><br><span class=\"line\"> return sync.getQueueLength();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getQueuedThreads\"><a href=\"#getQueuedThreads\" class=\"headerlink\" title=\"getQueuedThreads()\"></a>getQueuedThreads()</h2><ul>\n<li>等待的线程集合</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected Collection<Thread> getQueuedThreads() {</span><br><span class=\"line\"> return sync.getQueuedThreads();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"hasWaiters-Condition-condition\"><a href=\"#hasWaiters-Condition-condition\" class=\"headerlink\" title=\"hasWaiters(Condition condition)\"></a>hasWaiters(Condition condition)</h2><ul>\n<li>是否有线程阻塞在condition的await()的方法上</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public boolean hasWaiters(Condition condition) {</span><br><span class=\"line\"> if (condition == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))</span><br><span class=\"line\"> throw new IllegalArgumentException("not owner");</span><br><span class=\"line\"> return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getWaitQueueLength-Condition-condition\"><a href=\"#getWaitQueueLength-Condition-condition\" class=\"headerlink\" title=\"getWaitQueueLength(Condition condition)\"></a>getWaitQueueLength(Condition condition)</h2><ul>\n<li>阻塞在condition的await()的方法上的线程数量</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public int getWaitQueueLength(Condition condition) {</span><br><span class=\"line\"> if (condition == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))</span><br><span class=\"line\"> throw new IllegalArgumentException("not owner");</span><br><span class=\"line\"> return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"getWaitingThreads-Condition-condition\"><a href=\"#getWaitingThreads-Condition-condition\" class=\"headerlink\" title=\"getWaitingThreads(Condition condition)\"></a>getWaitingThreads(Condition condition)</h2><ul>\n<li>阻塞在condition的await()的方法上的线程集合</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected Collection<Thread> getWaitingThreads(Condition condition) {</span><br><span class=\"line\"> if (condition == null)</span><br><span class=\"line\"> throw new NullPointerException();</span><br><span class=\"line\"> if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))</span><br><span class=\"line\"> throw new IllegalArgumentException("not owner");</span><br><span class=\"line\"> return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n\n<h1 id=\"内部类\"><a href=\"#内部类\" class=\"headerlink\" title=\"内部类\"></a>内部类</h1><p>内部类有三个,公共抽象类Sync,非公平锁实现NonfairSync,公平锁实现FairSync,下面分别进行分析</p>\n<h1 id=\"Sync\"><a href=\"#Sync\" class=\"headerlink\" title=\"Sync\"></a>Sync</h1><h2 id=\"void-lock\"><a href=\"#void-lock\" class=\"headerlink\" title=\"void lock()\"></a>void lock()</h2><ul>\n<li>核心加锁方法,因为公平锁和非公平锁实现不同,所以这里为抽象方法。</li>\n</ul>\n<h2 id=\"boolean-nonfairTryAcquire-int-acquires\"><a href=\"#boolean-nonfairTryAcquire-int-acquires\" class=\"headerlink\" title=\"boolean nonfairTryAcquire(int acquires)\"></a>boolean nonfairTryAcquire(int acquires)</h2><ul>\n<li>非公平获取锁(资源)的实际实现,从以下源码可以看出,获取锁的时候,哪个先来,哪个就可以获取到,CAS操作成功的就获取到锁了,没有所谓的先来后到。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean nonfairTryAcquire(int acquires) {</span><br><span class=\"line\"> final Thread current = Thread.currentThread();//当前线程</span><br><span class=\"line\"> int c = getState();//获取加锁状态</span><br><span class=\"line\"> if (c == 0) {//为0则代表还没有上锁</span><br><span class=\"line\"> if (compareAndSetState(0, acquires)) {//执行CAS操作并且上锁</span><br><span class=\"line\"> setExclusiveOwnerThread(current);//设置持有锁的线程</span><br><span class=\"line\"> return true;//加锁成功</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else if (current == getExclusiveOwnerThread()) {//执行到这里说明已经有某个线程获取到锁了,因为是可重入锁,判断持有锁的线程是否为当前线程</span><br><span class=\"line\"> int nextc = c + acquires;//执行到这里说明是已经不是第一次上锁,并且当前线程是锁的持有线程,则可以直接进行累加(也就是重入)</span><br><span class=\"line\"> if (nextc < 0) // 额,超过int的最大值,出现溢出了(真的存在这种场景么= =??)</span><br><span class=\"line\"> throw new Error("Maximum lock count exceeded");</span><br><span class=\"line\"> setState(nextc);//更新state</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;//获取失败</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"boolean-tryRelease-int-releases\"><a href=\"#boolean-tryRelease-int-releases\" class=\"headerlink\" title=\"boolean tryRelease(int releases)\"></a>boolean tryRelease(int releases)</h2><ul>\n<li>非阻塞方式尝试释放资源,具体看源码分析<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected final boolean tryRelease(int releases) {</span><br><span class=\"line\"> int c = getState() - releases;//待更新资源</span><br><span class=\"line\"> if (Thread.currentThread() != getExclusiveOwnerThread())//判断是否为锁的持有现成</span><br><span class=\"line\"> throw new IllegalMonitorStateException();</span><br><span class=\"line\"> boolean free = false;//释放标识位置。为true则代表当前线程不再持有当前锁的任何资源</span><br><span class=\"line\"> if (c == 0) {//如果释放资源后,资源数量为0,代表释放锁,其他线程可以尝试获取锁,如果不为0,则需要继续释放(因为是重入多次,需要释放多次)</span><br><span class=\"line\"> free = true;</span><br><span class=\"line\"> setExclusiveOwnerThread(null);//清空锁持有线程</span><br><span class=\"line\"> }</span><br><span class=\"line\"> setState(c);//更新状态标志位</span><br><span class=\"line\"> return free;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"boolean-isHeldExclusively\"><a href=\"#boolean-isHeldExclusively\" class=\"headerlink\" title=\"boolean isHeldExclusively()\"></a>boolean isHeldExclusively()</h2><ul>\n<li>判断当前线程是否为锁持有线程<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected final boolean isHeldExclusively() {</span><br><span class=\"line\"> return getExclusiveOwnerThread() == Thread.currentThread();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"ConditionObject-newCondition\"><a href=\"#ConditionObject-newCondition\" class=\"headerlink\" title=\"ConditionObject newCondition()\"></a>ConditionObject newCondition()</h2><ul>\n<li>创建条件变量对象,ConditionObject之后分析<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final ConditionObject newCondition() {</span><br><span class=\"line\"> return new ConditionObject();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"Thread-getOwner\"><a href=\"#Thread-getOwner\" class=\"headerlink\" title=\"Thread getOwner()\"></a>Thread getOwner()</h2><ul>\n<li>如果锁没有被线程持有,返回null,否则返回持有的线程<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final Thread getOwner() {</span><br><span class=\"line\"> return getState() == 0 ? null : getExclusiveOwnerThread();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"int-getHoldCount\"><a href=\"#int-getHoldCount\" class=\"headerlink\" title=\"int getHoldCount()\"></a>int getHoldCount()</h2><ul>\n<li>获取重入次数。如果当前线程没有持有锁,返回0;<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final int getHoldCount() {</span><br><span class=\"line\"> return isHeldExclusively() ? getState() : 0;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"boolean-isLocked\"><a href=\"#boolean-isLocked\" class=\"headerlink\" title=\"boolean isLocked()\"></a>boolean isLocked()</h2><ul>\n<li>是否处于锁定状态<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final boolean isLocked() {</span><br><span class=\"line\"> return getState() != 0;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"NonfairSync\"><a href=\"#NonfairSync\" class=\"headerlink\" title=\"NonfairSync\"></a>NonfairSync</h1><ul>\n<li>非公平锁的委托实现,继承了Sync类,间接继承了AQS</li>\n</ul>\n<h2 id=\"void-lock-1\"><a href=\"#void-lock-1\" class=\"headerlink\" title=\"void lock()\"></a>void lock()</h2><ul>\n<li>获取锁操作,直接通过cas获取,失败则通过aqs的acquire获取,acquire在父类AQS中,会调用子类的tryAcquire方法。<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final void lock() {</span><br><span class=\"line\"> if (compareAndSetState(0, 1))</span><br><span class=\"line\"> setExclusiveOwnerThread(Thread.currentThread());</span><br><span class=\"line\"> else</span><br><span class=\"line\"> acquire(1);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">public final void acquire(int arg) {</span><br><span class=\"line\"> if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class=\"line\"> selfInterrupt();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"boolean-tryAcquire-int-acquires\"><a href=\"#boolean-tryAcquire-int-acquires\" class=\"headerlink\" title=\"boolean tryAcquire(int acquires)\"></a>boolean tryAcquire(int acquires)</h2><ul>\n<li>直接调用sync抽象类中的nonfairTryAcquire,非公平方式获取资源<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected final boolean tryAcquire(int acquires) {</span><br><span class=\"line\"> return nonfairTryAcquire(acquires);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"FairSync\"><a href=\"#FairSync\" class=\"headerlink\" title=\"FairSync\"></a>FairSync</h1><ul>\n<li>公平锁的委托实现,继承了Sync类,间接继承了AQS</li>\n</ul>\n<h2 id=\"void-lock-2\"><a href=\"#void-lock-2\" class=\"headerlink\" title=\"void lock()\"></a>void lock()</h2><ul>\n<li>没有快速路径,直接调用acquire去获取资源,内部委托依旧是调用tryAcquire<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">final void lock() {</span><br><span class=\"line\"> acquire(1);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h2 id=\"boolean-tryAcquire-int-acquires-1\"><a href=\"#boolean-tryAcquire-int-acquires-1\" class=\"headerlink\" title=\"boolean tryAcquire(int acquires)\"></a>boolean tryAcquire(int acquires)</h2><ul>\n<li>与非公平获取资源相比,区别在于多了条件!hasQueuedPredecessors() ,也就是说按照队列的方式获取,如果队列中尚有未获取的在等待,则当前线程等待并且入队(参考AQS部分)<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">protected final boolean tryAcquire(int acquires) {</span><br><span class=\"line\"> final Thread current = Thread.currentThread();</span><br><span class=\"line\"> int c = getState();</span><br><span class=\"line\"> if (c == 0) {</span><br><span class=\"line\"> if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {</span><br><span class=\"line\"> setExclusiveOwnerThread(current);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> else if (current == getExclusiveOwnerThread()) {</span><br><span class=\"line\"> int nextc = c + acquires;</span><br><span class=\"line\"> if (nextc < 0)</span><br><span class=\"line\"> throw new Error("Maximum lock count exceeded");</span><br><span class=\"line\"> setState(nextc);</span><br><span class=\"line\"> return true;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return false;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n</ul>\n<h1 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h1><ul>\n<li>其实相对而言,可重入锁实现很简单,基本都是继承了AQS的方法,重点实现了可重入(线程相同则累加state),以及公平非公平。</li>\n<li>重点还是在于理解AQS,这里的可重入锁,以及之后的可重入读写锁,CountDownLatch,Semaphore,CyclicBarrier等都是基于AQS实现的。</li>\n</ul>\n","categories":["Java"],"tags":["Java","AbstractQueuedSynchronizer","多线程","ReentrantLock","Lock"]},{"title":"MySQL学习总结(3)","url":"/2019/05/07/MySQL%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93(3)/","content":"<h2 id=\"索引\"><a href=\"#索引\" class=\"headerlink\" title=\"索引\"></a>索引</h2><h1 id=\"索引常见模型\"><a href=\"#索引常见模型\" class=\"headerlink\" title=\"索引常见模型\"></a>索引常见模型</h1><ul>\n<li>哈希表</li>\n<li>适用于只有等值查询的场景</li>\n<li>有序数组</li>\n<li>适用于等值查询,范围查询</li>\n<li>更新成本高,适用于静态存储引擎</li>\n<li>搜索树</li>\n<li>查询复杂度O(log(N))</li>\n<li>更新操作复杂度O(log(N))</li>\n<li>为了适配磁盘,往往使用N叉树</li>\n</ul>\n<h1 id=\"InnoDB索引模型\"><a href=\"#InnoDB索引模型\" class=\"headerlink\" title=\"InnoDB索引模型\"></a>InnoDB索引模型</h1><ul>\n<li><p>表都是根据主键顺序以索引形式存放。</p>\n</li>\n<li><p>使用了B+树索引模型,数据存储在B+树中。</p>\n</li>\n<li><p>根据叶子节点内容,索引类型分主键索引和非主键索引</p>\n</li>\n<li><p>主键索引叶子节点存放的是整行的数据,也成为聚簇索引。</p>\n</li>\n<li><p>非主键索引叶子节点存放的是主键的值,非主键索引也成为二级索引</p>\n</li>\n<li><p>区别:基于非主键索引的查询需要多扫描一颗索引树。</p>\n</li>\n</ul>\n<h1 id=\"索引维护\"><a href=\"#索引维护\" class=\"headerlink\" title=\"索引维护\"></a>索引维护</h1><ul>\n<li>一个数据页满了,按照B+Tree算法,会新增加一个数据页,这个过程称为页分裂,会导致性能下降,空间利用率降低大概一半。</li>\n<li>两个相邻的数据页利用率如果都很低,会做数据合并,也就是页分裂逆过程</li>\n<li>B+树的插入可能会引起数据页的分裂,删除可能会引起数据页的合并,二者都是比较重的IO消耗,所以比较好的方式是顺序插入数据,这也是我们一般使用自增主键的原因之一</li>\n<li>在Key-Value的场景下,只有一个索引且是唯一索引,则适合直接使用业务字段作为主键索引</li>\n<li>非主键索引的叶子结点存储的是主键的值,所以主键字段占用空间不宜过大。同时,其查找数据的过程称为“回表”,需要先查找自己得到主键值,再在主键索引上边查找数据内容。</li>\n<li>索引的实现由存储引擎来决定,InnoDB使用B+树(N叉树,比如1200叉树),把整颗树的高度维持在很小的范围内,同时在内存里缓存前面若干层的节点,可以极大地降低访问磁盘的次数,提高读的效率。</li>\n</ul>\n<h1 id=\"覆盖索引\"><a href=\"#覆盖索引\" class=\"headerlink\" title=\"覆盖索引\"></a>覆盖索引</h1><ul>\n<li>回到主键索引树搜索的过程,我们称为回表</li>\n<li>由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。</li>\n</ul>\n<h1 id=\"最左前缀原则\"><a href=\"#最左前缀原则\" class=\"headerlink\" title=\"最左前缀原则\"></a>最左前缀原则</h1><ul>\n<li>B+ 树这种索引结构,可以利用索引的“最左前缀”,来定位记录。</li>\n<li>在建立联合索引的时候,如何安排索引内的字段顺序</li>\n<li>第一原则,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是最需要有限考虑的。</li>\n<li>再次考虑空间</li>\n</ul>\n<h1 id=\"索引下推\"><a href=\"#索引下推\" class=\"headerlink\" title=\"索引下推\"></a>索引下推</h1><ul>\n<li>MySQL 5.6 引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。</li>\n</ul>\n<h1 id=\"小结\"><a href=\"#小结\" class=\"headerlink\" title=\"小结\"></a>小结</h1><ul>\n<li>满足语句需求的情况下, 尽量少地访问资源是数据库设计的重要原则之一。</li>\n<li>设计表结构时,也要以减少资源消耗作为目标。</li>\n</ul>\n<hr>\n<ul>\n<li>源:<极客时间> MySQL实战45讲教程</li>\n</ul>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"SpringBoot 拦截 response 记录日志","url":"/2018/06/15/SpringBoot%20%E6%8B%A6%E6%88%AA%20response%20%E8%AE%B0%E5%BD%95%E6%97%A5%E5%BF%97/","content":"<p>直接使用AOP的拦截器,调用AfterReturning即可。<br>废话不多说直接上代码</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">package com.gs.techpub.filter;</span><br><span class=\"line\"> </span><br><span class=\"line\">import com.gridsum.techpub.utils.JsonUtil;</span><br><span class=\"line\">import org.aspectj.lang.annotation.AfterReturning;</span><br><span class=\"line\">import org.aspectj.lang.annotation.Aspect;</span><br><span class=\"line\">import org.slf4j.Logger;</span><br><span class=\"line\">import org.slf4j.LoggerFactory;</span><br><span class=\"line\">import org.springframework.stereotype.Component;</span><br><span class=\"line\">@Component</span><br><span class=\"line\">@Aspect</span><br><span class=\"line\">public class ResponseFilter {</span><br><span class=\"line\"> </span><br><span class=\"line\"> private Logger logger = LoggerFactory.getLogger(this.getClass());</span><br><span class=\"line\"> @AfterReturning(returning = "ret", pointcut = "execution( * com.gs.techpub.controller.*.*(..))")</span><br><span class=\"line\"> public void doAfterReturning(Object ret) {</span><br><span class=\"line\"> logger.info("返回值 : " + JsonUtil.getInstance().toJson(ret));</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>记得加上依赖</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><dependency></span><br><span class=\"line\"> <groupId>org.springframework.boot</groupId></span><br><span class=\"line\"> <artifactId>spring-boot-starter-aop</artifactId></span><br><span class=\"line\"></dependency></span><br></pre></td></tr></table></figure>","categories":["Java"],"tags":["Java","SpringBoot","Response"]},{"title":"Rust小试牛刀","url":"/2022/11/25/Rust%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/","content":"<h1 id=\"Rust小试牛刀\"><a href=\"#Rust小试牛刀\" class=\"headerlink\" title=\"Rust小试牛刀\"></a>Rust小试牛刀</h1><p>Rust的书已经看完2本了,看程序基本上能看懂一些了(生命周期这块还是迷迷糊糊),但是觉得多少还是要自己写一下程序,实际应用一下才能学会。正好今天找了一个练手的小项目,初步尝试一下</p>\n<h2 id=\"背景\"><a href=\"#背景\" class=\"headerlink\" title=\"背景\"></a>背景</h2><p>N年之前给媳妇写过一个小程序,处理Excel数据的,大致就是从A.xlsx读取数据,分组合并并计算,然后追加写入到B.xlsx,但是其中有个字典关系的映射,以前是维护在代码中的,每次修改字典都要重新编译。<br>加上当时程序是.Net写的,WinForm程序,依赖Windows和VisualStudio这个组合,最近今年工作基本就没用过Windows,电脑也换成了M1的Mac,给她处理这个程序要安装个arm版Windows不说,还要安装VisualStudio,本就不富裕的硬盘,又被拖走了几十个G。<br>想着既然学了Rust,加上为了处理这个程序,索性就用Rust重写一份吧。</p>\n<h2 id=\"准备工作\"><a href=\"#准备工作\" class=\"headerlink\" title=\"准备工作\"></a>准备工作</h2><ol>\n<li>Rust的安装就不多说什么了,按照官网的脚本执行一下就搞定了</li>\n<li>编辑器我用的是VisualCode,配合rust-analyzer插件,debug就是直接用CodeLLDB。</li>\n<li>因为要解析xlsx文件,在<code>https://lib.rs/search?q=xlsx</code>查了一圈,最后决定用<code>umya-spreadsheet="0.8.3"</code></li>\n<li>因为有个需要配置的字典,考虑json和toml,最后决定用toml,因为对我媳妇而已,这个更直接一些,最终选择<code>toml="0.5.0"</code></li>\n</ol>\n<h2 id=\"开工\"><a href=\"#开工\" class=\"headerlink\" title=\"开工\"></a>开工</h2><p>cargo new一个项目,添加好依赖,开整,只是逻辑的话,其实很简单,按照.Net的逻辑直接迁移过来就行了,不过有一说一,写的过程中还是挺多麻烦的,光看书以为自己掌握的东西,coding的时候却干着急想不起来怎么写,最后还是依赖搜索引擎解决。<br>另外还有一点就是关于mod,直到程序写完了,也没太get这个玩意和目录的关系怎么搞。所以最终的项目还是基于一个main.rs实现的。<br>逻辑啥的,就不具体写这里了,记录一下自己开发过程中记得住记不住的东西吧。</p>\n<h3 id=\"结构体\"><a href=\"#结构体\" class=\"headerlink\" title=\"结构体\"></a>结构体</h3><p>读取数据之后肯定不能直接用个元祖或者Map传来传去,怎么也得有个结构体来组装数据,为了隔离开,我这里还是用mod来进行一下简单的区分。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">pub mod model {</span><br><span class=\"line\"> #[derive(Debug, Clone)]</span><br><span class=\"line\"> pub struct ProductModel {</span><br><span class=\"line\"> pub field1: String,</span><br><span class=\"line\"> pub field2: String,</span><br><span class=\"line\"> pub field3: i32,</span><br><span class=\"line\"> pub type_: String,</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>别看就上面几个字段,就这个<code>type_</code>还折腾我一会,因为type是关键字,如果确实要用type做这个字段名称,就要用<code>r#type</code>, 还有就是从mod到struce到field,都要pub,否则访问不了(开始在不同目录,后来放到一个文件了,也没调整了,这块后面再补习一下吧)。</p>\n<h3 id=\"xlsx处理lib-umya-spreadsheet\"><a href=\"#xlsx处理lib-umya-spreadsheet\" class=\"headerlink\" title=\"xlsx处理lib-umya_spreadsheet\"></a>xlsx处理lib-umya_spreadsheet</h3><p>rust处理Excel的lib本来就不是很多,还有很多是只支持读取不支持写入或者只支持写入不支持读取的,好不容易找到了这么一个既支持读又支持写入的,搜了一圈,除了官方文档,几乎没有任何资料。我处理xlsx的时候基本上是按照行进行处理,一行封装成一个model,所以需要知道当前xlsx的当前sheet有多少行数据,然后for循环遍历,组合数据。然而就找一共有多少行这个api就一个一个尝试,官方文档并没有说这块内容,网上也没有这个使用教程,饶了一大圈,最终找到了这个api <code>sheet.get_highest_row()</code> , 不管怎么说,兜了一大圈,这个总算是解决了。<br>然后就是下一个问题,写入api需要传一个book对象,因为第二个xlsx实际上是我要追加数据,不是新建一个excel,凭着感觉走,是先用read的api创建mut对象,修改之后吧read的book放到write中,执行回写,验证了一下,hmmm没猜错,这样基本上这个lib的使用问题得到了解决。</p>\n<h3 id=\"for循环\"><a href=\"#for循环\" class=\"headerlink\" title=\"for循环\"></a>for循环</h3><p>for循环应该是一门语言中最基本的部分了,依靠代码提示,这个没出太大问题,vec和map的循环,都顺利进行,但是拿到一个sheet有多少行之后,我要遍历的时候发现不会了,然后又是搜索引擎走一圈,找到了这个最简单的语法</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">for i in 1..sheet.get_highest_row()+1 {</span><br><span class=\"line\">\t....</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>唉,就如实说,光看书是真的不行,还是得coding才能掌握的说。</p>\n<h3 id=\"集合\"><a href=\"#集合\" class=\"headerlink\" title=\"集合\"></a>集合</h3><p>本来以为集合是个简单的东西,没想到这上面也废了不少劲。简单总结一下:</p>\n<ul>\n<li>Vec 的添加元素用push</li>\n<li>HashSet的添加元素用insert</li>\n<li>HashMap的添加键值对也用insert</li>\n<li>HashMap的遍历调用iter然后for循环,得到的是一个元素,key是.0,value是.1</li>\n<li>迭代得到的元素是引用,如果要放入其他集合,我选择的是clone一份,直接解引用,经常出现所有权问题,暂时也没想到更好的办法。</li>\n</ul>\n<h3 id=\"字符串-String和-amp-str\"><a href=\"#字符串-String和-amp-str\" class=\"headerlink\" title=\"字符串 (String和&str)\"></a>字符串 (String和&str)</h3><p>说真的,字符串这玩意还挺绕的,String和&str,就这两个来来回回的转换,我这也是偷懒了,自己接收也好,使用也罢,基本都用String了,得到了&str基本都转换成String,然后在传递了,最起码没有所有权问题了,偷个懒,后面在慢慢找更好的适配方法。</p>\n<h3 id=\"字符串-字符串拼接\"><a href=\"#字符串-字符串拼接\" class=\"headerlink\" title=\"字符串 (字符串拼接)\"></a>字符串 (字符串拼接)</h3><p>我是怎么也没有想到,一个字符串拼接竟然愁住了我。<br>我从HashSet<String>的iter遍历得到的item,类型是&String,我就想直接后面拼接个字符串,然后判断在另外一个集合中是否存在,然后这个追加愁住了我。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">let name =element+"流";</span><br><span class=\"line\">报错:</span><br><span class=\"line\">cannot add `&str` to `&std::string::String`</span><br><span class=\"line\">string concatenation requires an owned `String` on the leftrustcClick for full compiler diagnostic</span><br></pre></td></tr></table></figure>\n\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> let name = element.add("流");</span><br><span class=\"line\"> 报错:</span><br><span class=\"line\"> cannot move out of `* element ` which is behind a shared reference</span><br><span class=\"line\">move occurs because `* element ` has type `std::string::String`, which does not implement the `Copy` traitrustcClick for full compiler diagnostic</span><br></pre></td></tr></table></figure>\n\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">let name =*element.add("流");</span><br><span class=\"line\">报错:</span><br><span class=\"line\">the size for values of type `str` cannot be known at compilation time</span><br><span class=\"line\">the trait `Sized` is not implemented for `str`</span><br><span class=\"line\">all local variables must have a statically known size</span><br><span class=\"line\">unsized locals are gated as an unstable feature</span><br></pre></td></tr></table></figure>\n<p>我自己尝试出来正确方法</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">let name = String::from(element).add("流");</span><br></pre></td></tr></table></figure>\n\n<p>再来看另外一个, 对于元组的引用,也是通过clone来解决的。。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> let mut map: HashMap<String, Vec<Model>> = HashMap::new();</span><br><span class=\"line\"> ...</span><br><span class=\"line\"> for entry in map.iter() {</span><br><span class=\"line\"> let sheet_name = entry.0.clone().add("流");</span><br><span class=\"line\"> let sheet = book.get_sheet_by_name_mut(&sheet_name).unwrap();</span><br><span class=\"line\"> ...</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"全局配置\"><a href=\"#全局配置\" class=\"headerlink\" title=\"全局配置\"></a>全局配置</h3><p>这个程序因为有个配置文件,运行过程中需要使用,开始想着声明一个全局变量就行了,准备放到dict这个mod里面,存储当然就用HashMap,然后就出现了问题</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">pub mod dict {</span><br><span class=\"line\">\tstatic mut map:HashMap<String,String> = HashMap::new();</span><br><span class=\"line\">}</span><br><span class=\"line\">报错:</span><br><span class=\"line\">cannot call non-const fn `HashMap::<std::string::String, std::string::String>::new` in statics</span><br><span class=\"line\">calls in statics are limited to constant functions, tuple structs and tuple variantsrustcClick for full compiler diagnostic</span><br></pre></td></tr></table></figure>\n<p>既然需要HashMap必须是const,我就试一下</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">const mut map:HashMap<String,String> = HashMap::new();</span><br><span class=\"line\">报错:</span><br><span class=\"line\">const globals cannot be mutable </span><br></pre></td></tr></table></figure>\n<p>不可变?那我怎么insert配置文件啊?先试试看非mut的</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">const map:HashMap<String,String> = HashMap::new();</span><br><span class=\"line\">报错:</span><br><span class=\"line\">cannot call non-const fn `HashMap::<std::string::String, std::string::String>::new` in constants</span><br><span class=\"line\">calls in constants are limited to constant functions, tuple structs and tuple variantsrustcClick for full compiler diagnostic</span><br></pre></td></tr></table></figure>\n\n<p>给我整不会了,看来HashMap不能全局使用了,从网上又查了一圈,hmm找到方法了,用lazy_static,具体原理还没看,但是尝试了一下,确实解决了我的问题。最终代码如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">pub mod dict {</span><br><span class=\"line\"> use std::{collections::HashMap, fs::File, io::Read, sync::Mutex};</span><br><span class=\"line\"></span><br><span class=\"line\"> pub fn get_product_name(product_name: String) -> String {</span><br><span class=\"line\"> if let Some(value) = CONFIG_MAP.lock().unwrap().get(&product_name) {</span><br><span class=\"line\"> return value.to_owned();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return product_name;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> lazy_static! {</span><br><span class=\"line\"> static ref CONFIG_MAP: Mutex<HashMap<String, String>> = {</span><br><span class=\"line\"> let mut file = File::open("./data/cfg.toml").unwrap();</span><br><span class=\"line\"> let mut data = String::new();</span><br><span class=\"line\"> file.read_to_string(&mut data).unwrap();</span><br><span class=\"line\"> let obj: HashMap<String, HashMap<String, String>> = toml::from_str(&data).unwrap();</span><br><span class=\"line\"> let dict_map = obj.get("product_dict").unwrap();</span><br><span class=\"line\"> Mutex::new(dict_map.clone())</span><br><span class=\"line\"> };</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"数据类型转换\"><a href=\"#数据类型转换\" class=\"headerlink\" title=\"数据类型转换\"></a>数据类型转换</h3><ul>\n<li>f64-> i32 (引申一下,应该数字类型转换都可以这么搞)<ul>\n<li><code> let a = num as i32</code></li>\n</ul>\n</li>\n<li>String -> i32 (引申一下,字符串到数字类型)<ul>\n<li><code>let a = String::from("13").parse::<i32>().unwrap();</code></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><p>这个程序算是第一次正式用rust实现一个完整的功能的程序,说简单吧,也用到不少东西,说复杂吧,实际上就是数据格式转换+xlsx基本操作,后面还会继续用rust实现一些小的功能之类的,主要还是方便一下自己吧,这次代码量也就150+行,不过嘞,写了快一天。<br>编程这东西,还是不能停留在看,要实际coding才能掌握,后面如果要写一些系统底层的东西,相比现在可要复杂太多了,自己给自己加加油打打气吧~</p>\n","categories":["Rust"],"tags":["Rust"]},{"title":"SpringBoot jar包中资源加载问题","url":"/2018/01/10/SpringBoot%20jar%E5%8C%85%E4%B8%AD%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E9%97%AE%E9%A2%98/","content":"<p>在IDE下调试怎么也没有发现问题,但是部署到服务器上,提示找不到资源,找了半天资料总算是找到了原因:<br>Jar包中的资源加载不能使用File方式,只能使用InputStream方式读取。知道原因就好解决了,如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">try {</span><br><span class=\"line\"> URL url = ScoreLoadUtil.class.getClassLoader().getResource("LiangXingScoreDic");//这里不要加classpath: ,否则也是找不到</span><br><span class=\"line\"> List<String> list = new ArrayList<>();</span><br><span class=\"line\"> try (InputStream stream = url.openStream(); InputStreamReader isr=new InputStreamReader(stream,"utf-8"); BufferedReader br = new BufferedReader(isr)) {</span><br><span class=\"line\"> String line;</span><br><span class=\"line\"> while ((line=br.readLine())!=null){</span><br><span class=\"line\"> list.add(line);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> logger.error(list.size());</span><br><span class=\"line\"> for (String s : list) {</span><br><span class=\"line\"> String[] split = s.split("\\t");</span><br><span class=\"line\"> if (split.length!=2){</span><br><span class=\"line\"> logger.error(s);</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> liangXingMap.put(split[0],Float.parseFloat(split[1]));</span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (IOException e) {</span><br><span class=\"line\"> logger.error(e);</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n\n<p>网上教程有一种说法用ResourceUtils的extractJarFileURL方法可以读取,但是试了试还是有问题,这个方法相当于把jar:xxxxx!resource给提取成xxxxxxxx,这样得到的是jar文件,并不是要加载的内容,可能是我用的方法不太对吧,不过使用InputStream的方式是没问题了。</p>\n","categories":["Java"],"tags":["Java","SpringBoot","jar","依赖"]},{"title":"MySQL学习笔记-SQL查询优化","url":"/2019/08/02/SQL%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96/","content":"<h2 id=\"如何获取有性能问题的SQL\"><a href=\"#如何获取有性能问题的SQL\" class=\"headerlink\" title=\"如何获取有性能问题的SQL\"></a>如何获取有性能问题的SQL</h2><h3 id=\"通过用户反馈获取存在性能问题的SQL\"><a href=\"#通过用户反馈获取存在性能问题的SQL\" class=\"headerlink\" title=\"通过用户反馈获取存在性能问题的SQL\"></a>通过用户反馈获取存在性能问题的SQL</h3><h3 id=\"通过慢查询日志获取存在性能问题的SQL\"><a href=\"#通过慢查询日志获取存在性能问题的SQL\" class=\"headerlink\" title=\"通过慢查询日志获取存在性能问题的SQL\"></a>通过慢查询日志获取存在性能问题的SQL</h3><h3 id=\"实时获取存在性能问题的SQL\"><a href=\"#实时获取存在性能问题的SQL\" class=\"headerlink\" title=\"实时获取存在性能问题的SQL\"></a>实时获取存在性能问题的SQL</h3><h2 id=\"使用慢查询日志获取有性能问题的SQL\"><a href=\"#使用慢查询日志获取有性能问题的SQL\" class=\"headerlink\" title=\"使用慢查询日志获取有性能问题的SQL\"></a>使用慢查询日志获取有性能问题的SQL</h2><h3 id=\"slow-query-log-启动停止记录慢查询日志\"><a href=\"#slow-query-log-启动停止记录慢查询日志\" class=\"headerlink\" title=\"slow_query_log 启动停止记录慢查询日志\"></a>slow_query_log 启动停止记录慢查询日志</h3><h3 id=\"slow-query-log-file指定慢查询日志存储路径及文件\"><a href=\"#slow-query-log-file指定慢查询日志存储路径及文件\" class=\"headerlink\" title=\"slow_query_log_file指定慢查询日志存储路径及文件\"></a>slow_query_log_file指定慢查询日志存储路径及文件</h3><h3 id=\"long-query-time指定记录慢查询日志SQL执行时间的伐值\"><a href=\"#long-query-time指定记录慢查询日志SQL执行时间的伐值\" class=\"headerlink\" title=\"long_query_time指定记录慢查询日志SQL执行时间的伐值\"></a>long_query_time指定记录慢查询日志SQL执行时间的伐值</h3><ul>\n<li>默认10秒</li>\n</ul>\n<h3 id=\"记录所有符合条件的SQL\"><a href=\"#记录所有符合条件的SQL\" class=\"headerlink\" title=\"记录所有符合条件的SQL\"></a>记录所有符合条件的SQL</h3><ul>\n<li>包括查询语句</li>\n<li>数据修改语句</li>\n<li>已经回滚的SQL</li>\n</ul>\n<h3 id=\"log-queries-not-using-indexes是否记录未使用索引的SQL\"><a href=\"#log-queries-not-using-indexes是否记录未使用索引的SQL\" class=\"headerlink\" title=\"log_queries_not_using_indexes是否记录未使用索引的SQL\"></a>log_queries_not_using_indexes是否记录未使用索引的SQL</h3><h2 id=\"常用的慢查询日志分析工具\"><a href=\"#常用的慢查询日志分析工具\" class=\"headerlink\" title=\"常用的慢查询日志分析工具\"></a>常用的慢查询日志分析工具</h2><h3 id=\"mysqldumpslow\"><a href=\"#mysqldumpslow\" class=\"headerlink\" title=\"mysqldumpslow\"></a>mysqldumpslow</h3><h3 id=\"pt-query-digest\"><a href=\"#pt-query-digest\" class=\"headerlink\" title=\"pt-query-digest\"></a>pt-query-digest</h3><h2 id=\"实时获取性能问题SQL\"><a href=\"#实时获取性能问题SQL\" class=\"headerlink\" title=\"实时获取性能问题SQL\"></a>实时获取性能问题SQL</h2><h3 id=\"information-schema-gt-PROCESSLIST表\"><a href=\"#information-schema-gt-PROCESSLIST表\" class=\"headerlink\" title=\"information_schema -> PROCESSLIST表\"></a>information_schema -> PROCESSLIST表</h3><h2 id=\"SQL的解析预处理及生成执行计划\"><a href=\"#SQL的解析预处理及生成执行计划\" class=\"headerlink\" title=\"SQL的解析预处理及生成执行计划\"></a>SQL的解析预处理及生成执行计划</h2><h3 id=\"搞清楚这些查询为什么会慢\"><a href=\"#搞清楚这些查询为什么会慢\" class=\"headerlink\" title=\"搞清楚这些查询为什么会慢\"></a>搞清楚这些查询为什么会慢</h3><ul>\n<li>客户端发送sQL请求到服务器</li>\n<li>检查是否可以在查询换存储命中</li>\n<li>服务器端执行SQL解析,预处理,再由优化器声称对应的执行计划</li>\n<li>根据执行计划,调用存储引擎API来查询数据</li>\n<li>将结果返回给客户端</li>\n</ul>\n<h3 id=\"查询缓存对SQL性能的影响\"><a href=\"#查询缓存对SQL性能的影响\" class=\"headerlink\" title=\"查询缓存对SQL性能的影响\"></a>查询缓存对SQL性能的影响</h3><ul>\n<li><p>query_cache_type</p>\n<ul>\n<li>设置查询缓存是否可用</li>\n</ul>\n</li>\n<li><p>query_cache_size</p>\n<ul>\n<li>设置查询缓存的内存大小</li>\n</ul>\n</li>\n<li><p>query_cache_limit</p>\n<ul>\n<li>设置查询缓存可用存储的最大值</li>\n</ul>\n</li>\n<li><p>query_cache_wlock_invalidate</p>\n<ul>\n<li>设置数据表被锁后是否返回缓存中的数据</li>\n</ul>\n</li>\n<li><p>query_cache_min_res_unit</p>\n<ul>\n<li>设置查询缓存非配的内存块的最小单位</li>\n</ul>\n</li>\n<li><p>读写比较频繁的系统,建议关闭缓存</p>\n</li>\n</ul>\n<h3 id=\"MySQL依照执行计划和存储引擎进行交互\"><a href=\"#MySQL依照执行计划和存储引擎进行交互\" class=\"headerlink\" title=\"MySQL依照执行计划和存储引擎进行交互\"></a>MySQL依照执行计划和存储引擎进行交互</h3><ul>\n<li><p>解析SQL</p>\n<ul>\n<li><p>语法解析阶段通过关键字对MySQL语句进行解析,并生成一颗对应的解析树</p>\n</li>\n<li><p>MySQL解析器将使用MySQL语法规则验证和解析查询</p>\n<ul>\n<li>检查语法是否使用了正确的关键字</li>\n<li>关键字的顺序是否正确</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>预处理</p>\n<ul>\n<li><p>根据MySQL规则进一步检查解析树是否合法</p>\n<ul>\n<li>查询中所涉及的表和数据列是否存在,名字或者别名是否存在歧义等</li>\n</ul>\n</li>\n<li><p>查询优化器生成查询计划</p>\n</li>\n</ul>\n</li>\n<li><p>优化SQL执行计划</p>\n</li>\n</ul>\n<h3 id=\"SQL的解析预处理以及生成执行计划\"><a href=\"#SQL的解析预处理以及生成执行计划\" class=\"headerlink\" title=\"SQL的解析预处理以及生成执行计划\"></a>SQL的解析预处理以及生成执行计划</h3><ul>\n<li><p>会造成MySQL生成错误的执行计划的原因</p>\n<ul>\n<li><p>统计信息不准确</p>\n</li>\n<li><p>执行计划中的成本估算不等同于实际的执行计划的成本</p>\n<ul>\n<li>MySQL服务器层不知道哪些页面在内存中</li>\n<li>哪些页面在磁盘上</li>\n<li>哪些页面要顺序读取</li>\n<li>哪些页面需要随机读取</li>\n</ul>\n</li>\n<li><p>MySQL优化器所认为的最优可能与你所认为的最优不一样</p>\n<ul>\n<li>基于成本模型选择最优的执行计划</li>\n</ul>\n</li>\n<li><p>MySQL从不考虑其他并发的查询,这可能会影响当前查询的速度</p>\n</li>\n<li><p>MySQL有时候也会基于一些固定的规则来生成执行计划</p>\n</li>\n<li><p>MySQL不会考虑不收其控制的成本</p>\n<ul>\n<li>存储过程</li>\n<li>用户自定义的函数</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"MySQL优化器可优化的SQL类型\"><a href=\"#MySQL优化器可优化的SQL类型\" class=\"headerlink\" title=\"MySQL优化器可优化的SQL类型\"></a>MySQL优化器可优化的SQL类型</h3><ul>\n<li><p>重新定义表的关联顺序</p>\n</li>\n<li><p>将外链接转化为内连接</p>\n</li>\n<li><p>使用等价变换规则</p>\n</li>\n<li><p>优化count,min,max</p>\n<ul>\n<li>select tables optimized away</li>\n</ul>\n</li>\n<li><p>建一个表达式转化为常数表达式</p>\n</li>\n<li><p>子查询优化</p>\n<ul>\n<li>子查询->连接查询</li>\n</ul>\n</li>\n<li><p>提前终止查询</p>\n</li>\n<li><p>对in条件进行优化</p>\n</li>\n</ul>\n<h2 id=\"如何确定查询处理各个阶段所消耗的时间\"><a href=\"#如何确定查询处理各个阶段所消耗的时间\" class=\"headerlink\" title=\"如何确定查询处理各个阶段所消耗的时间\"></a>如何确定查询处理各个阶段所消耗的时间</h2><h3 id=\"使用profile\"><a href=\"#使用profile\" class=\"headerlink\" title=\"使用profile\"></a>使用profile</h3><ul>\n<li><p>set profiling=1</p>\n<ul>\n<li>session级别</li>\n</ul>\n</li>\n<li><p>执行查询</p>\n</li>\n<li><p>show profiles</p>\n</li>\n<li><p>show profile for query x</p>\n</li>\n<li><p>show profile cpu for query x</p>\n</li>\n</ul>\n<h3 id=\"使用performance-schema\"><a href=\"#使用performance-schema\" class=\"headerlink\" title=\"使用performance_schema\"></a>使用performance_schema</h3><h2 id=\"特定SQL的查询优化\"><a href=\"#特定SQL的查询优化\" class=\"headerlink\" title=\"特定SQL的查询优化\"></a>特定SQL的查询优化</h2><h3 id=\"大批量删除\"><a href=\"#大批量删除\" class=\"headerlink\" title=\"大批量删除\"></a>大批量删除</h3><ul>\n<li>分批次删除</li>\n</ul>\n<h3 id=\"修改大表的表结构\"><a href=\"#修改大表的表结构\" class=\"headerlink\" title=\"修改大表的表结构\"></a>修改大表的表结构</h3><ul>\n<li><p>主从切换,先从后主</p>\n</li>\n<li><p>新老表同步,比较复杂</p>\n<ul>\n<li>pt-online-schema-change</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"优化not-in-和-lt-gt-查询\"><a href=\"#优化not-in-和-lt-gt-查询\" class=\"headerlink\" title=\"优化not in 和<>查询\"></a>优化not in 和<>查询</h3><ul>\n<li>优化为LEFT JOIN方法</li>\n</ul>\n<h3 id=\"使用汇总表优化查询\"><a href=\"#使用汇总表优化查询\" class=\"headerlink\" title=\"使用汇总表优化查询\"></a>使用汇总表优化查询</h3><ul>\n<li>截止到前一天的数据汇总count</li>\n<li>今天的全部数据进行count</li>\n<li>把两部分数据汇总</li>\n</ul>\n<h1 id=\"附Xmind\"><a href=\"#附Xmind\" class=\"headerlink\" title=\"附Xmind\"></a>附Xmind</h1><p><img src=\"/SQL%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96/SQL%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96.png\" alt=\"SQL查询优化\"></p>\n","categories":["MySQL"],"tags":["MySQL","分表","分库"]},{"title":"Swagger使用简介","url":"/2018/10/09/Swagger%E4%BD%BF%E7%94%A8%E7%AE%80%E4%BB%8B/","content":"<h1 id=\"Swagger使用简介\"><a href=\"#Swagger使用简介\" class=\"headerlink\" title=\"Swagger使用简介\"></a>Swagger使用简介</h1><h2 id=\"Swagger简介\"><a href=\"#Swagger简介\" class=\"headerlink\" title=\"Swagger简介\"></a>Swagger简介</h2><p>没有API文档工具之前,大家都是手写API文档的,在什么地方书写的都有,有在confluence上写的,有在对应的项目目录下readme.md上写的。这种方式不是说不好,大家都有一个通病,就是懒得更新文档,隔了一段时间,接口变动了什么没人清楚了。另外还有就是开始写文档的时候特别痛苦,每个字段,一行行注释解释。<br>其实大多数开发都还是写注释的(为了防止自己看不懂吧。。),如果稍加修改,可以从注释中自动生成文档就好了。Hmm..Swagger可以完成这个功能,尤其是针对Java,C#这样的项目,而且是前后端分离的,更加合适了。<br>Swagger能做什么呢?可以自动根据Controller自动生成对应的文档,并且提供测试接口。如果你能容忍一定程度的代码侵入(也不是太多。。就是在需要暴露的Model和Controller上加点注解,一个配置文件类),Swagger还是很方便的。</p>\n<h2 id=\"Swagger-和-Spring-项目整合\"><a href=\"#Swagger-和-Spring-项目整合\" class=\"headerlink\" title=\"Swagger 和 Spring 项目整合\"></a>Swagger 和 Spring 项目整合</h2><p>其实很简单,加个依赖,加个配置类,嗯。。这个应该是最低工作保证了。。</p>\n<p>先说依赖</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><!-- swagger2 生成对应的Json文档,这个应该可以说是核心依赖了 --></span><br><span class=\"line\"><dependency></span><br><span class=\"line\"> <groupId>io.springfox</groupId></span><br><span class=\"line\"> <artifactId>springfox-swagger2</artifactId></span><br><span class=\"line\"> <version>2.6.1</version></span><br><span class=\"line\"></dependency></span><br><span class=\"line\"><!-- swagger-ui 为项目提供api展示及测试的界面 --></span><br><span class=\"line\"><dependency></span><br><span class=\"line\"> <groupId>io.springfox</groupId></span><br><span class=\"line\"> <artifactId>springfox-swagger-ui</artifactId></span><br><span class=\"line\"> <version>2.6.1</version></span><br><span class=\"line\"></dependency></span><br><span class=\"line\"><!-- 集成 swagger 的时候,缺少这个 jar包是不OK的--></span><br><span class=\"line\"><dependency></span><br><span class=\"line\"> <groupId>com.fasterxml.jackson.core</groupId></span><br><span class=\"line\"> <artifactId>jackson-databind</artifactId></span><br><span class=\"line\"> <version>2.8.6</version></span><br><span class=\"line\"></dependency></span><br></pre></td></tr></table></figure>\n\n<p>说真的,依赖包不是太多springfox-swagger2是生成接口访问JSON和核心,同时依赖jackson。界面展示依赖的是swagger-ui。嗯。。引入的时候注意下Jar包冲突。。尤其是Spring的版本不同exclusion一下。。</p>\n<p>依赖包这样基本就已经满足了,剩下就是加一个配置类就好了。如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\"> </span><br><span class=\"line\">import org.springframework.context.annotation.Bean;</span><br><span class=\"line\">import org.springframework.context.annotation.Configuration;</span><br><span class=\"line\">import springfox.documentation.builders.ApiInfoBuilder;</span><br><span class=\"line\">import springfox.documentation.builders.ParameterBuilder;</span><br><span class=\"line\">import springfox.documentation.builders.RequestHandlerSelectors;</span><br><span class=\"line\">import springfox.documentation.schema.ModelRef;</span><br><span class=\"line\">import springfox.documentation.service.ApiInfo;</span><br><span class=\"line\">import springfox.documentation.service.Parameter;</span><br><span class=\"line\">import springfox.documentation.spi.DocumentationType;</span><br><span class=\"line\">import springfox.documentation.spring.web.plugins.Docket;</span><br><span class=\"line\">import springfox.documentation.swagger2.annotations.EnableSwagger2;</span><br><span class=\"line\">import java.util.ArrayList;</span><br><span class=\"line\">import java.util.List;</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">@Configuration</span><br><span class=\"line\">@EnableSwagger2</span><br><span class=\"line\">public class ApiConfig {</span><br><span class=\"line\"> @Bean(name = "docket")</span><br><span class=\"line\"> public Docket api() {</span><br><span class=\"line\"> ParameterBuilder ticketPar = new ParameterBuilder();</span><br><span class=\"line\"> List<Parameter> pars = new ArrayList<>();</span><br><span class=\"line\"> ticketPar.name("login-token").description("登录Token")</span><br><span class=\"line\"> .modelRef(new ModelRef("string")).parameterType("header")</span><br><span class=\"line\"> //header中的ticket参数非必填,传空也可以</span><br><span class=\"line\"> .required(false).build(); </span><br><span class=\"line\"> //根据每个方法名也知道当前方法在设置什么参数</span><br><span class=\"line\"> pars.add(ticketPar.build());</span><br><span class=\"line\"> Docket docket = new Docket(DocumentationType.SWAGGER_2)</span><br><span class=\"line\"> .select()</span><br><span class=\"line\"> .apis(RequestHandlerSelectors.any())</span><br><span class=\"line\"> .build().globalOperationParameters(pars);</span><br><span class=\"line\"> docket.apiInfo(apiInfo());</span><br><span class=\"line\"> return docket;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> private ApiInfo apiInfo() {</span><br><span class=\"line\"> return new ApiInfoBuilder()</span><br><span class=\"line\"> .title("后台接口")</span><br><span class=\"line\"> .description("后台接口")</span><br><span class=\"line\"> .version("1.0.0")</span><br><span class=\"line\"> .build();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<p>如果项目使用Spring是XML方式,就在配置文件加上如下几行:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><mvc:default-servlet-handler/></span><br><span class=\"line\"><mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" /></span><br><span class=\"line\"><mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" /></span><br></pre></td></tr></table></figure>\n\n<p>如果是SpringBoot的就更简单了。。直接拷贝配置类,加上依赖包就可以运行了。<br>Hmm。。简单来说这样就足够了。。。<br>访问地址是默认地址+/swagger-ui.html,我这里是<a href=\"http://localhost:8080/swagger-ui.html\">http://localhost:8080/swagger-ui.html</a><br>效果如图所示:<br><img src=\"/pic/base.png\"></p>\n<h2 id=\"稍微复杂一些\"><a href=\"#稍微复杂一些\" class=\"headerlink\" title=\"稍微复杂一些\"></a>稍微复杂一些</h2><p>上面的只是提供了基础功能,Hmm…还是没有注释说明什么的,只是有一些基础的序列化参数(Json字段还是可以自动识别的,而且能提供默认值)<br>如果要加入一些注释,就需要对原有项目加入一些侵入代码了(一些swagger特有注解)</p>\n<p>首先是Model实体上面的</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">import io.swagger.annotations.ApiModel;</span><br><span class=\"line\">import io.swagger.annotations.ApiModelProperty;</span><br><span class=\"line\">@ApiModel</span><br><span class=\"line\">public class AddKeywordRequestVo extends BaseVo {</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 关键词名称</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "关键词名称",example = "汽车")</span><br><span class=\"line\"> private String keywordName;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> *关键词品类,多个用逗号分隔,0全部,1灯泡,2火花塞,3轮胎,4蓄电池,5油品,6雨刮,7全车件。默认为0</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "关键词品类,多个用逗号分隔,0全部,1灯泡,2火花塞,3轮胎,4蓄电池,5油品,6雨刮,7全车件。默认为0",example = "1")</span><br><span class=\"line\"> private String keywordCategory;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 关键词拼音</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "关键词拼音",example = "qiche")</span><br><span class=\"line\"> private String keywordPinyin;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 关键词同义词</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "关键词同义词",example = "卡车")</span><br><span class=\"line\"> private String keywordSynonym;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 词频</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "词频",example = "1")</span><br><span class=\"line\"> private Integer wordFrequency;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 词性,0通用,1专属</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty(value = "词性,0通用,1专属",example = "1")</span><br><span class=\"line\"> private Integer wordType;</span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 备注</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiModelProperty("备注")</span><br><span class=\"line\"> private String remark;</span><br><span class=\"line\"> public String getKeywordName() {</span><br><span class=\"line\"> return keywordName;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setKeywordName(String keywordName) {</span><br><span class=\"line\"> this.keywordName = keywordName;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public String getKeywordCategory() {</span><br><span class=\"line\"> return keywordCategory;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setKeywordCategory(String keywordCategory) {</span><br><span class=\"line\"> this.keywordCategory = keywordCategory;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public String getKeywordPinyin() {</span><br><span class=\"line\"> return keywordPinyin;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setKeywordPinyin(String keywordPinyin) {</span><br><span class=\"line\"> this.keywordPinyin = keywordPinyin;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public String getKeywordSynonym() {</span><br><span class=\"line\"> return keywordSynonym;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setKeywordSynonym(String keywordSynonym) {</span><br><span class=\"line\"> this.keywordSynonym = keywordSynonym;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public Integer getWordFrequency() {</span><br><span class=\"line\"> return wordFrequency;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setWordFrequency(Integer wordFrequency) {</span><br><span class=\"line\"> this.wordFrequency = wordFrequency;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public Integer getWordType() {</span><br><span class=\"line\"> return wordType;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setWordType(Integer wordType) {</span><br><span class=\"line\"> this.wordType = wordType;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public String getRemark() {</span><br><span class=\"line\"> return remark;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public void setRemark(String remark) {</span><br><span class=\"line\"> this.remark = remark;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> public AddKeywordRequestVo() {}</span><br><span class=\"line\"></span><br><span class=\"line\"> public AddKeywordRequestVo(Integer wordFrequency, Integer wordType, String keywordName, String keywordCategory) {</span><br><span class=\"line\"> this.wordFrequency = wordFrequency;</span><br><span class=\"line\"> this.wordType = wordType;</span><br><span class=\"line\"> this.keywordName = keywordName;</span><br><span class=\"line\"> this.keywordCategory = keywordCategory;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>其实也不是太多,一个ApiModelProperty,一个ApiModel就够用了,加上上面的注解有什么效果呢?看图吧。。</p>\n<p>model的:<br><img src=\"/pic/RequestModel.png\"><br>参数示例的:<br><img src=\"/pic/RequestExample.png\"></p>\n<p>其实Response也是可以的,但是需要为泛型或者具体的,Object类型的就。。。。参考我这边的。。。<br>如果是泛型的话,参考下图:<br>model的:<br><img src=\"/pic/ResponseModel.png\"><br>参数示例的:<br><img src=\"/pic/ResponseExample.png\"></p>\n<p>Controller上面也可以加一些的:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">/**</span><br><span class=\"line\"> * 关键词Controller</span><br><span class=\"line\"> */</span><br><span class=\"line\">@RestController</span><br><span class=\"line\">@Api("关键词Controller")</span><br><span class=\"line\">public class KeywordsController {</span><br><span class=\"line\"> @Autowired</span><br><span class=\"line\"> private AddKeyWordService addKeyWordService;</span><br><span class=\"line\"> @Autowired</span><br><span class=\"line\"> private DelKeyWordService delKeyWordService;</span><br><span class=\"line\"> @Autowired</span><br><span class=\"line\"> private BatchDelKeyWordService batchDelKeyWordService;</span><br><span class=\"line\"></span><br><span class=\"line\"> private static Logger logger = LoggerFactory.getLogger(KeyWordController.class);</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 添加关键词</span><br><span class=\"line\"> * @param vo 添加关键词请求参数</span><br><span class=\"line\"> * @return 添加关键词结果</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @RequestMapping(value = "/add/keyword",method = RequestMethod.POST)</span><br><span class=\"line\"> @ResponseBody</span><br><span class=\"line\"> @ApiOperation("添加关键词")</span><br><span class=\"line\"> public Result addKeyword(@RequestBody AddKeywordRequestVo vo){</span><br><span class=\"line\"> LogModel lm = LogModel.newLogModel("addKeyword");</span><br><span class=\"line\"> logger.info(lm.addMetaData(vo).toJson());</span><br><span class=\"line\"> Result res = new Result();</span><br><span class=\"line\"> if (!addKeyWordService.checkParam(vo,res)){</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> try{</span><br><span class=\"line\"> addKeyWordService.addKeyword(vo,res);</span><br><span class=\"line\"> }catch(Exception e){</span><br><span class=\"line\"> res.setStatus(ReturnStatusEnum.SERVICE_ERROR.getValue());</span><br><span class=\"line\"> res.setMessage(ReturnStatusEnum.SERVICE_ERROR.getDesc());</span><br><span class=\"line\"> }</span><br><span class=\"line\"> logger.info(lm.getMeta("status", res.getStatus()).getMeta("meg", res.getMessage()).toJson());</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 删除关键词</span><br><span class=\"line\"> * @return 删除关键词结果</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @RequestMapping(value = "/del/keyword",method = RequestMethod.DELETE)</span><br><span class=\"line\"> @ResponseBody</span><br><span class=\"line\"> @ApiOperation("删除关键词")</span><br><span class=\"line\"> public Result delKeyword(@RequestBody DelKeywordRequestVo vo){</span><br><span class=\"line\"> LogModel lm = LogModel.newLogModel("delKeyword");</span><br><span class=\"line\"> logger.info(lm.addMetaData(vo).toJson());</span><br><span class=\"line\"> Result res = new Result();</span><br><span class=\"line\"> if (!delKeyWordService.checkParam(vo,res)){</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> delKeyWordService.delKeyword(vo,res);</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> res.setStatus(ReturnStatusEnum.SERVICE_ERROR.getValue());</span><br><span class=\"line\"> res.setMessage(ReturnStatusEnum.SERVICE_ERROR.getDesc());</span><br><span class=\"line\"> }</span><br><span class=\"line\"> logger.info(lm.getMeta("status", res.getStatus()).getMeta("meg", res.getMessage()).toJson());</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 批量删除关键词接口</span><br><span class=\"line\"> * @param vo 批量删除关键词请求</span><br><span class=\"line\"> * @return 批量删除结果</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @ApiOperation("批量删除关键词接口")</span><br><span class=\"line\"> @RequestMapping(value = "/batchdel/keyword",method = RequestMethod.DELETE)</span><br><span class=\"line\"> @ResponseBody</span><br><span class=\"line\"> public Result delKeywords(@RequestBody DelKeywordsRequestVo vo){</span><br><span class=\"line\"> LogModel lm = LogModel.newLogModel("delKeywords");</span><br><span class=\"line\"> logger.info(lm.addMetaData(vo).toJson());</span><br><span class=\"line\"> Result res = new Result();</span><br><span class=\"line\"> if (!batchDelKeyWordService.checkParam(vo,res)){</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> batchDelKeyWordService.delKeywords(vo,res);</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> res.setStatus(ReturnStatusEnum.SERVICE_ERROR.getValue());</span><br><span class=\"line\"> res.setMessage(ReturnStatusEnum.SERVICE_ERROR.getDesc());</span><br><span class=\"line\"> }</span><br><span class=\"line\"> logger.info(lm.getMeta("status", res.getStatus()).getMeta("meg", res.getMessage()).toJson());</span><br><span class=\"line\"> return res;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>效果如图所示:<br><img src=\"/pic/Controller.png\"></p>\n<h2 id=\"还有更强大的\"><a href=\"#还有更强大的\" class=\"headerlink\" title=\"还有更强大的\"></a>还有更强大的</h2><p>你以为这就是全部? nonono swagger还可以直接访问接口,Hmm。。这应该是最方便的吧</p>\n<p>直接输入对应的参数,点击Try it out! 如图所示:<br><img src=\"/pic/Result.png\"></p>\n<p>是不是有了这个连Postman都不用了=-=~</p>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><h3 id=\"好处\"><a href=\"#好处\" class=\"headerlink\" title=\"好处\"></a>好处</h3><ul>\n<li>文档跟随项目接口实时改变,不用担心文档和接口不同步</li>\n<li>一大批懒开发(比如我)还是写一些注释的,加入注释同时加个注解,也不算太麻烦。</li>\n<li>对接流畅多了,前端调用也方便多了。</li>\n<li>自己测试也方便</li>\n</ul>\n<h3 id=\"坏处\"><a href=\"#坏处\" class=\"headerlink\" title=\"坏处\"></a>坏处</h3><ul>\n<li>有一定代码侵入(加入注解,依赖,配置文件等)</li>\n<li>放到线上一定一定要屏蔽掉/swagger相关路径(通过Nginx屏蔽掉)</li>\n<li>变动更频繁了。。</li>\n<li>人更懒了。。</li>\n</ul>\n","categories":["Java"],"tags":["Java","Swagger","Spring"]},{"title":"Thrift 学习","url":"/2017/01/24/Thrift%E8%B0%83%E7%A0%94%E5%AD%A6%E4%B9%A0/","content":"<hr>\n<p>####类型相关</p>\n<ul>\n<li><p>基本类型</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">bool:布尔值 (true or false), one byte</span><br><span class=\"line\">byte:有符号字节</span><br><span class=\"line\">i16:16位有符号整型</span><br><span class=\"line\">i32:32位有符号整型</span><br><span class=\"line\">i64:64位有符号整型</span><br><span class=\"line\">double:64位浮点型</span><br><span class=\"line\">string:未知编码或者二进制的字符串</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>结构体和异常类型</p>\n<ul>\n<li>Thrift 结构体 (struct) 在概念上类似于 C 语言结构体类型,在 java 中 Thrift 结构体将会被转换成面向对象语言的类。</li>\n</ul>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">struct UserDemo {</span><br><span class=\"line\"> 1: i32 id;</span><br><span class=\"line\"> 2: string name;</span><br><span class=\"line\"> 3: i32 age = 25;</span><br><span class=\"line\"> 4: string phone;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">struct 不能继承,但是可以嵌套,不能嵌套自己</span><br><span class=\"line\">其成员都是有明确类型</span><br><span class=\"line\">成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用</span><br><span class=\"line\">成员分割符可以是逗号(,)或是分号(;),而且可以混用,但是为了清晰期间,建议在定义中只使用一种,比如java学习者可以就使用逗号(;)</span><br><span class=\"line\">字段会有optional和required之分</span><br><span class=\"line\">每个字段可以设置默认值</span><br><span class=\"line\">同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入</span><br></pre></td></tr></table></figure>\n<ul>\n<li>容器类型<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">list<t>:元素类型为t的有序表,容许元素重复。对应java的ArrayList,C# 中的 List<T></span><br><span class=\"line\">set<t>:元素类型为t的无序表,不容许元素重复。对应java和C#中的的HashSet</span><br><span class=\"line\">map<t,t>:键类型为t,值类型为t的kv对,键不容许重复。对对应Java的HashMap,C#中的Dictionary</span><br></pre></td></tr></table></figure></li>\n<li>服务类型<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">service QuerySrv{</span><br><span class=\"line\"> UserDemo qryUser(1:string name, 2:i32 age);</span><br><span class=\"line\"> string queryPhone(1:i32 id);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></li>\n<li>命名空间<ul>\n<li>Thrift 中的命名空间类似于 java 中的 package,C#中的namespace,它们提供了一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。</li>\n</ul>\n</li>\n</ul>\n<p>###传输</p>\n<ul>\n<li>传输协议<ul>\n<li>上总体可划分为文本 (text) 和二进制 (binary) 传输协议两大类</li>\n</ul>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">TBinaryProtocol:是Thrift的默认协议,使用二进制编码格式进行数据传输,基本上直接发送原始数据 </span><br><span class=\"line\">TCompactProtocol:压缩的、密集的数据传输协议,基于Variable-length quantity的zigzag 编码格式 </span><br><span class=\"line\">TJSONProtocol:以JSON (JavaScript Object Notation)数据编码协议进行数据传输 </span><br><span class=\"line\">TDebugProtocol:常常用以编码人员测试,以文本的形式展现方便阅读</span><br></pre></td></tr></table></figure>\n\n<ul>\n<li>传输方式<ul>\n<li>TSocket:阻塞型 socket,用于客户端,采用系统函数 read 和 write 进行读写数据。</li>\n<li>TServerSocket:非阻塞型 socket,用于服务器端,accecpt 到的 socket 类型都是 TSocket(即阻塞型 socket)。</li>\n<li>TBufferedTransport 和 TFramedTransport 都是有缓存的,均继承TBufferBase,调用下一层 TTransport 类进行读写操作吗,结构极为相似。其中 TFramedTransport 以帧为传输单位,帧结构为:4个字节(int32_t)+传输字节串,头4个字节是存储后面字节串的长度,该字节串才是正确需要传输的数据,因此 TFramedTransport 每传一帧要比 TBufferedTransport 和 TSocket 多传4个字节。</li>\n<li>TMemoryBuffer 继承 TBufferBase,用于程序内部通信用,不涉及任何网络I/O,可用于三种模式:(1)OBSERVE模式,不可写数据到缓存;(2)TAKE_OWNERSHIP模式,需负责释放缓存;(3)COPY模式,拷贝外面的内存块到TMemoryBuffer。</li>\n<li>TFileTransport 直接继承 TTransport,用于写数据到文件。对事件的形式写数<br>据,主线程负责将事件入列,写线程将事件入列,并将事件里的数据写入磁盘。这里面用到了两个队列,类型为 TFileTransportBuffer,一个用于主线程写事件,另一个用于写线程读事件,这就避免了线程竞争。在读完队列事件后,就会进行队列交换,由于由两个指针指向这两个队列,交换只要交换指针即可。它还支持以 chunk(块)的形式写数据到文件。</li>\n<li>TFDTransport 是非常简单地写数据到文件和从文件读数据,它的 write 和<br>read 函数都是直接调用系统函数 write 和 read 进行写和读文件。</li>\n<li>TSimpleFileTransport 直接继承 TFDTransport,没有添加任何成员函数和成员变量,不同的是构造函数的参数和在 TSimpleFileTransport 构造函数里对父类进行了初始化(打开指定文件并将fd传给父类和设置父类的close_policy为CLOSE_ON_DESTROY)。</li>\n<li>TZlibTransport 跟 TBufferedTransport 和 TFramedTransport一样,调用下一层 TTransport 类进行读写操作。它采用<zlib.h>提供的 zlib 压缩和解压缩库函数来进行压解缩,写时先压缩再调用底层 TTransport 类发送数据,读时先调用 TTransport 类接收数据再进行解压,最后供上层处理。</li>\n<li>TSSLSocket 继承 TSocket,阻塞型 socket,用于客户端。采用 openssl 的接口进行读写数据。checkHandshake()函数调用 SSL_set_fd 将 fd 和 ssl 绑定在一起,之后就可以通过 ssl 的 SSL_read和SSL_write 接口进行读写网络数据。</li>\n<li>TSSLServerSocket 继承 TServerSocket,非阻塞型 socket, 用于服务器端。accecpt 到的 socket 类型都是 TSSLSocket 类型。</li>\n<li>THttpClient 和 THttpServer 是基于 Http1.1 协议的继承 Transport 类型,均继承 THttpTransport,其中 THttpClient 用于客户端,THttpServer 用于服务器端。两者都调用下一层 TTransport 类进行读写操作,均用到TMemoryBuffer 作为读写缓存,只有调用 flush() 函数才会将真正调用网络 I/O 接口发送数据。</li>\n<li>TTransport 是所有 Transport 类的父类,为上层提供了统一的接口而且通过 TTransport 即可访问各个子类不同实现,类似多态。</li>\n</ul>\n</li>\n</ul>\n","categories":["RPC"],"tags":["Thrift","RPC"]},{"title":"gradle 将依赖打入Jar包的方法","url":"/2018/02/05/gradle%20%E5%B0%86%E4%BE%9D%E8%B5%96%E6%89%93%E5%85%A5Jar%E5%8C%85%E7%9A%84%E6%96%B9%E6%B3%95/","content":"<p>我使用的是IDEA,直接引入</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">plugins {</span><br><span class=\"line\"> id 'com.github.johnrengelman.shadow' version '1.2.3'</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>放在build.gradle的最上面,然后执行shadowJar即可。</p>\n<p>网上说有一种方法</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">jar {</span><br><span class=\"line\"> manifest {</span><br><span class=\"line\"> attributes "Main-Class": "$mainClassName"</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> from {</span><br><span class=\"line\"> configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>这种方法确实打入进去了,但是运行的时候报错,异常如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">Exception in thread "main" java.lang.VerifyError: (class: org/jboss/netty/channel/socket/nio/NioWorkerPool, method: newWorker signature: (Ljava/util/concurrent/Executor;)Lorg/jboss/netty/channel/socket/nio/AbstractNioWorker;) Wrong return type in function</span><br><span class=\"line\"> at org.elasticsearch.transport.netty.NettyTransport.createClientBootstrap(NettyTransport.java:354)</span><br><span class=\"line\"> at org.elasticsearch.transport.netty.NettyTransport.doStart(NettyTransport.java:290)</span><br><span class=\"line\"> at org.elasticsearch.common.component.AbstractLifecycleComponent.start(AbstractLifecycleComponent.java:68)</span><br><span class=\"line\"> at org.elasticsearch.transport.TransportService.doStart(TransportService.java:182)</span><br><span class=\"line\"> at org.elasticsearch.common.component.AbstractLifecycleComponent.start(AbstractLifecycleComponent.java:68)</span><br><span class=\"line\"> at org.elasticsearch.client.transport.TransportClient$Builder.build(TransportClient.java:162)</span><br></pre></td></tr></table></figure>\n<p>不知道什么原因,不过用第三方插件暂时可以解决,原因慢慢排查了。(初步判断是jar包冲突导致,用了ES和Zookeeper,\b好像都依赖Netty,版本还不太一样)</p>\n<p>另外还有一种方法可以运行,不过依赖单独放入一个lib目录下,也就是jar和依赖分离的方法:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">jar {</span><br><span class=\"line\"> String someString = ''</span><br><span class=\"line\"> configurations.runtime.each {someString = someString + " lib//"+it.name}</span><br><span class=\"line\"> manifest {</span><br><span class=\"line\"> attributes 'Main-Class': 'com.gridsum.techpub.legal.etl.App'</span><br><span class=\"line\"> attributes 'Class-Path': someString</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>以后用得到的时候再说~</p>\n","categories":["Java"],"tags":["Java","jar","依赖","gradle"]},{"title":"hadoop环境运行程序出现 Retrying connect to server 问题","url":"/2018/01/07/hadoop%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F%E5%87%BA%E7%8E%B0-Retrying-connect-to-server-%E9%97%AE%E9%A2%98/","content":"<p>程序运行时出现如下问题:</p>\n<p><img src=\"/hadoop%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F%E5%87%BA%E7%8E%B0-Retrying-connect-to-server-%E9%97%AE%E9%A2%98/problem.png\" alt=\"程序运行报错图\"></p>\n<p>从网上查资料,有说重启format的。。有说/etc/hosts出问题的。。。</p>\n<p>反正都试了一遍。。还是有这个问题</p>\n<p>后来看日志,发现问题是访问服务器9001端口访问不到。。开始怀疑自己配置文件有问题。既然是9001,那就肯定是mapred的问题,</p>\n<p>看了配置文件内容</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> <property></span><br><span class=\"line\"> <name>mapred.job.tracker</name></span><br><span class=\"line\"> <value>http://192.168.254.128:9001</value></span><br><span class=\"line\"></property></span><br></pre></td></tr></table></figure>\n<p>也没发现有什么问题,ip换成host中的master也不行,看官方配置文档,发现没有http,删掉http之后,重启dataNode没了0.0</p>\n<p>日志说有冲突什么的。。。估计就是format问题。。清理了tmp和hdfs,重新format,然后再启动,成功!</p>\n<p>附上一份正确的配置</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> <property></span><br><span class=\"line\"> <name>mapred.job.tracker</name></span><br><span class=\"line\"> <value>192.168.254.128:9001</value></span><br><span class=\"line\"></property></span><br></pre></td></tr></table></figure>","categories":["Hadoop"],"tags":["Hadoop"]},{"title":"hadoop is running beyond virtual memory limits问题解决","url":"/2018/02/04/hadoop%20is%20running%20beyond%20virtual%20memory%20limits%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/","content":"<p>单机搭建了2.6.5的伪分布式集群,写了一个tf-idf计算程序,分词用的是结巴分词,使用standalone模式运行没有任何问题,切换到伪分布式模式运行一直报错:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">hadoop is running beyond virtual memory limits</span><br></pre></td></tr></table></figure>\n<p>大概意思就是使用虚拟内存超出了限制。</p>\n<p>网上参考了好几篇博客,几乎都是再说更改hadoop-env和mapred-site.xml</p>\n<p>hadoop-env直接更改堆大小</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">export HADOOP_HEAPSIZE=1000</span><br></pre></td></tr></table></figure>\n<p>mapred-site.xml 更改opts的大小</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><property> </span><br><span class=\"line\"><name>mapred.child.java.opts</name> </span><br><span class=\"line\"><value>-Xmx4000m</value> </span><br><span class=\"line\"></property></span><br></pre></td></tr></table></figure>\n<p>我的机器内存是8G,按理说这个程序运行应该是毫无压力的。。</p>\n<p>提示说的虚拟内存,这两个估计是不挂钩,反正改了之后运行依旧报错</p>\n<p>既然是虚拟内存不足,那就找虚拟内存的事,google一下找到如下配置</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><property></span><br><span class=\"line\"><name>yarn.nodemanager.vmem-pmem-ratio</name></span><br><span class=\"line\"><value>15.5</value></span><br><span class=\"line\"></property></span><br></pre></td></tr></table></figure>\n<p>更改yarn-site.xml</p>\n<p>我这之前运行给了5.5G,提示5.7G超过5.5G了,kill掉了container,索性一下给了15G,运行可算是正常了,看来出了问题,还是得从错误日志根源找起。</p>\n","categories":["Hadoop"],"tags":["Hadoop"]},{"title":"jenkins构建基于gradle的springboot项目CI采坑(采用jar方式部署)","url":"/2018/02/14/jenkins%E6%9E%84%E5%BB%BA%E5%9F%BA%E4%BA%8Egradle%E7%9A%84springboot%E9%A1%B9%E7%9B%AECI%E9%87%87%E5%9D%91-%E9%87%87%E7%94%A8jar%E6%96%B9%E5%BC%8F%E9%83%A8%E7%BD%B2/","content":"<p>试了一堆插件,最后用的还是 publish over SSH</p>\n<p>jenkins基本配置不多说了,就是配置一下git仓储,配置一下gradle执行命令</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">clean</span><br><span class=\"line\">bootRepackage</span><br></pre></td></tr></table></figure>\n<p>之后执行Send build artifacts over SSH</p>\n<p>提前配置好对应的服务器</p>\n<p>Send build artifacts over SSH</p>\n<p><img src=\"/jenkins%E6%9E%84%E5%BB%BA%E5%9F%BA%E4%BA%8Egradle%E7%9A%84springboot%E9%A1%B9%E7%9B%AECI%E9%87%87%E5%9D%91-%E9%87%87%E7%94%A8jar%E6%96%B9%E5%BC%8F%E9%83%A8%E7%BD%B2/config.png\" alt=\"配置图\"></p>\n<p>麻烦的是执行restart.sh脚本,总是各种奇葩问题,最终结果如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">#/bin/bash</span><br><span class=\"line\">pid=`ps -ef | grep spp.jar | grep -v grep | awk '{print $2}'`</span><br><span class=\"line\">if [ -n "$pid" ]</span><br><span class=\"line\">then</span><br><span class=\"line\"> kill -9 $pid</span><br><span class=\"line\">fi</span><br><span class=\"line\">java -jar /data1/javaApp/smartPushPlatform/spp.jar --server.port=30001 > console.log &</span><br></pre></td></tr></table></figure>\n\n<p>这样基本就可以完成启动了,而且可以正常推出</p>\n<p>编译日志如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">[Gradle] - Launching build.</span><br><span class=\"line\">[SmartPushPlatform] $ /usr/share/gradle/bin/gradle clean bootRepackage</span><br><span class=\"line\">Starting a Gradle Daemon (subsequent builds will be faster)</span><br><span class=\"line\">:clean</span><br><span class=\"line\">:compileJavaNote: /var/lib/jenkins/workspace/SmartPushPlatform/src/main/java/com/gridsum/techpub/legal/smartpush/service/TagService.java uses unchecked or unsafe operations.</span><br><span class=\"line\">Note: Recompile with -Xlint:unchecked for details.</span><br><span class=\"line\"> </span><br><span class=\"line\">:processResources</span><br><span class=\"line\">:classes</span><br><span class=\"line\">:findMainClass</span><br><span class=\"line\">:jar</span><br><span class=\"line\">:bootRepackage</span><br><span class=\"line\"> </span><br><span class=\"line\">BUILD SUCCESSFUL in 7s</span><br><span class=\"line\">6 actionable tasks: 6 executed</span><br><span class=\"line\">Build step 'Invoke Gradle script' changed build result to SUCCESS</span><br><span class=\"line\">SSH: Connecting from host [gs-server-3602]</span><br><span class=\"line\">SSH: Connecting with configuration [10.202.81.26] ...</span><br><span class=\"line\">SSH: EXEC: STDOUT/STDERR from command [cd /data1/javaApp/smartPushPlatform</span><br><span class=\"line\">mv SmartPushPlatform-1.1.jar spp.jar</span><br><span class=\"line\">sh restart.sh] ...</span><br><span class=\"line\">SSH: EXEC: completed after 200 ms</span><br><span class=\"line\">SSH: Disconnecting configuration [10.202.81.26] ...</span><br><span class=\"line\">SSH: Transferred 1 file(s)</span><br><span class=\"line\">Finished: SUCCESS</span><br></pre></td></tr></table></figure>","categories":["Jenkins"],"tags":["SpringBoot","gradle","Jenkins"]},{"title":"Maven将程序及依赖打成一个jar包","url":"/2019/07/20/maven%20package%20%E6%89%93%E5%8C%85/","content":"<p>平时写一些简单工具类,经常要打包ftp到服务器上面,懒得zip压缩,直接生成jar包。<br>maven配置如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"><build></span><br><span class=\"line\"> <plugins></span><br><span class=\"line\"> <plugin></span><br><span class=\"line\"> <groupId>org.apache.maven.plugins</groupId></span><br><span class=\"line\"> <artifactId>maven-assembly-plugin</artifactId></span><br><span class=\"line\"> <version>2.5.5</version></span><br><span class=\"line\"> <configuration></span><br><span class=\"line\"> <archive></span><br><span class=\"line\"> <manifest></span><br><span class=\"line\"> <mainClass>com.xxx.xxx.xxx.xxx</mainClass></span><br><span class=\"line\"> </manifest></span><br><span class=\"line\"> </archive></span><br><span class=\"line\"> <descriptorRefs></span><br><span class=\"line\"> <descriptorRef>jar-with-dependencies</descriptorRef></span><br><span class=\"line\"> </descriptorRefs></span><br><span class=\"line\"> </configuration></span><br><span class=\"line\"> <executions></span><br><span class=\"line\"> <execution></span><br><span class=\"line\"> <id>make-assembly</id></span><br><span class=\"line\"> <phase>package</phase></span><br><span class=\"line\"> <goals></span><br><span class=\"line\"> <goal>single</goal></span><br><span class=\"line\"> </goals></span><br><span class=\"line\"> </execution></span><br><span class=\"line\"> </executions></span><br><span class=\"line\"> </plugin></span><br><span class=\"line\"> </plugins></span><br><span class=\"line\"></build></span><br></pre></td></tr></table></figure>\n\n","categories":["Java"],"tags":["Java","Maven"]},{"title":"SQL优化","url":"/2018/06/26/sql%E4%BC%98%E5%8C%96/","content":"<h1 id=\"SQL-优化\"><a href=\"#SQL-优化\" class=\"headerlink\" title=\"SQL 优化\"></a>SQL 优化</h1><h3 id=\"负向查询不能使用索引\"><a href=\"#负向查询不能使用索引\" class=\"headerlink\" title=\"负向查询不能使用索引\"></a>负向查询不能使用索引</h3><figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> id <span class=\"keyword\">not</span> <span class=\"keyword\">in</span> (<span class=\"number\">1</span>,<span class=\"number\">3</span>,<span class=\"number\">4</span>);</span><br></pre></td></tr></table></figure>\n<p>应该修改为:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">select name from user where id in (2,5,6);</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"前导模糊查询不能使用索引\"><a href=\"#前导模糊查询不能使用索引\" class=\"headerlink\" title=\"前导模糊查询不能使用索引\"></a>前导模糊查询不能使用索引</h3><p>如:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> name <span class=\"keyword\">like</span> <span class=\"string\">'%zhangsan'</span></span><br></pre></td></tr></table></figure>\n\n<p>非前导则可以:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> name <span class=\"keyword\">like</span> <span class=\"string\">'zhangsan%'</span></span><br></pre></td></tr></table></figure>\n<p>建议可以考虑使用 <code>Lucene</code> 等全文索引工具来代替频繁的模糊查询。</p>\n<h3 id=\"数据区分不明显的不建议创建索引\"><a href=\"#数据区分不明显的不建议创建索引\" class=\"headerlink\" title=\"数据区分不明显的不建议创建索引\"></a>数据区分不明显的不建议创建索引</h3><p>如 user 表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段。</p>\n<h3 id=\"字段的默认值不要为-null\"><a href=\"#字段的默认值不要为-null\" class=\"headerlink\" title=\"字段的默认值不要为 null\"></a>字段的默认值不要为 null</h3><p>这样会带来和预期不一致的查询结果。</p>\n<h3 id=\"在字段上进行计算不能命中索引\"><a href=\"#在字段上进行计算不能命中索引\" class=\"headerlink\" title=\"在字段上进行计算不能命中索引\"></a>在字段上进行计算不能命中索引</h3><figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> FROM_UNIXTIME(create_time) <span class=\"operator\"><</span> CURDATE();</span><br></pre></td></tr></table></figure>\n\n<p>应该修改为:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> create_time <span class=\"operator\"><</span> FROM_UNIXTIME(CURDATE());</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"最左前缀问题\"><a href=\"#最左前缀问题\" class=\"headerlink\" title=\"最左前缀问题\"></a>最左前缀问题</h3><p>如果给 user 表中的 username pwd 字段创建了复合索引那么使用以下SQL 都是可以命中索引:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> username <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> username<span class=\"operator\">=</span><span class=\"string\">'zhangsan'</span> <span class=\"keyword\">and</span> pwd <span class=\"operator\">=</span><span class=\"string\">'axsedf1sd'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">select</span> username <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> pwd <span class=\"operator\">=</span><span class=\"string\">'axsedf1sd'</span> <span class=\"keyword\">and</span> username<span class=\"operator\">=</span><span class=\"string\">'zhangsan'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">select</span> username <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> username<span class=\"operator\">=</span><span class=\"string\">'zhangsan'</span></span><br></pre></td></tr></table></figure>\n\n<p>但是使用</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> username <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> pwd <span class=\"operator\">=</span><span class=\"string\">'axsedf1sd'</span></span><br></pre></td></tr></table></figure>\n<p>是不能命中索引的。</p>\n<h3 id=\"如果明确知道只有一条记录返回\"><a href=\"#如果明确知道只有一条记录返回\" class=\"headerlink\" title=\"如果明确知道只有一条记录返回\"></a>如果明确知道只有一条记录返回</h3><figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> username<span class=\"operator\">=</span><span class=\"string\">'zhangsan'</span> limit <span class=\"number\">1</span></span><br></pre></td></tr></table></figure>\n<p>可以提高效率,可以让数据库停止游标移动。</p>\n<h3 id=\"不要让数据库帮我们做强制类型转换\"><a href=\"#不要让数据库帮我们做强制类型转换\" class=\"headerlink\" title=\"不要让数据库帮我们做强制类型转换\"></a>不要让数据库帮我们做强制类型转换</h3><figure class=\"highlight sql\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">select</span> name <span class=\"keyword\">from</span> <span class=\"keyword\">user</span> <span class=\"keyword\">where</span> telno<span class=\"operator\">=</span><span class=\"number\">18722222222</span></span><br></pre></td></tr></table></figure>\n<p>这样虽然可以查出数据,但是会导致全表扫描。</p>\n<p>需要修改为</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">select name from user where telno='18722222222'</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"如果需要进行-join-的字段两表的字段类型要相同\"><a href=\"#如果需要进行-join-的字段两表的字段类型要相同\" class=\"headerlink\" title=\"如果需要进行 join 的字段两表的字段类型要相同\"></a>如果需要进行 join 的字段两表的字段类型要相同</h3><p>不然也不会命中索引。</p>\n","categories":["MySQL"],"tags":["MySQL"]},{"title":"一次Es排序优化记录","url":"/2018/12/13/%E4%B8%80%E6%AC%A1Es%E6%8E%92%E5%BA%8F%E4%BC%98%E5%8C%96%E8%AE%B0%E5%BD%95/","content":"<p><strong>背景</strong></p>\n<ul>\n<li>13号有一个大上线,其中一个功能就是排序功能,说复杂也不复杂,说难吧也不难,但是麻烦的就是加上Sort之后速度奇慢无比。</li>\n<li>排序实现也不太复杂,基本都是基于function score + sort 实现,对应不同的商品品类,有不同的排序规则(Hmm,目前还没有上个性化排序。。),实现方式就是定位品类之后,用function score进行提升部分商品的score,在这之后用sort进行【score + field1 +field2】的排序,看着也不复杂对吧。</li>\n<li>ES机器配置不太高(qiong…..)4C 8G * 3,索引中在售商品也不太多几,万的样子,分布到200多个城市站,总共合起来大概千万出头的数据(有部分非在售商品)。</li>\n</ul>\n<p><strong>问题</strong></p>\n<ul>\n<li>每次定位到品类之后,使用了品类排序【score + field1 +field2】,速度奇慢无比,平时几十毫秒的查询能变成5-6秒甚至更多,有点让人无法忍受</li>\n</ul>\n<p><strong>尝试过程</strong></p>\n<ul>\n<li>最开始打算用缓存解决问题,但是更新频率实在有点高的不行。。这个有点扛不住。</li>\n<li>后来打算按照城市站分索引,这样一个索引数据量就很小了,排序应该就不是什么大问题了,但是查询有点麻烦。</li>\n<li>先reindex一个小的索引,试了试排序速度,果然恢复到了几十毫秒的数量级了。<br>突然想到了也许多加一些分片也可以解决这个问题,毕竟我们是有城市站routing的啊。</li>\n<li>不过城市站反正是查询的必须条件已经加上了routing,吧shard从默认的5个先加到了50个,试了一下果然速度提升到100ms左右,再往上加shard的时候发现了另外一个问题,大批量的出现ESReject问题,可能是一次bulk或者index数据的量略大,ES又需要进行routing的原因吧,最终上线的时候,保守一点使用了40 Shard,目前速度尚在可接受范围(线上最起码是8c 16G的。。),如果想在提升速度,估计要加机器了(还是那句话。。。qiong。。。)</li>\n</ul>\n<p><strong>加速原理</strong></p>\n<ul>\n<li>没看源码,纯属自己猜测:<ul>\n<li>感觉shard和分索引应该差不多,Es Query需要找到对应的shard,拽出来符合条件的数据进行排序,然后输出出去。拽出来数据应该是在shard内部进行过排序,如果shard内部文档过多,速度应该会比较慢。</li>\n<li>我们这边业务场景是城市站必须有,而且文档按照城市站分开后相对来说很平均了(当然还是有部分shard很小。。),将shard数量提高以后,shard内部排序的文档数量少了很多,速度自然就上去了。</li>\n<li>shard过多,在ES进行bulk插入数据时候,routing到对应分片,这步骤应该会花较多时间,如果机器配置不太给力的话(囧。。比如说我们),shard数量还是不要太多,否则机器负载会很高很高(前两天看到负载破20了 - -!还好只是瞬时)</li>\n</ul>\n</li>\n</ul>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","Sort","routing","shard"]},{"title":"三年没写东西了,随便写点什么","url":"/2022/11/18/%E4%B8%89%E5%B9%B4%E6%B2%A1%E5%86%99%E4%B8%9C%E8%A5%BF%E4%BA%86%EF%BC%8C%E9%9A%8F%E4%BE%BF%E5%86%99%E7%82%B9%E4%BB%80%E4%B9%88/","content":"<h2 id=\"回顾\"><a href=\"#回顾\" class=\"headerlink\" title=\"回顾\"></a>回顾</h2><p>不知不觉三年过去了,也是这疫情闹了三年,也是高德呆着的三年。我的工作经历还比较简单,研究生毕业之后,传统企业2年,京东1年,高德3年,算是2年传统企业+4年互联网经验吧。自己上次写Blog还是三年前,那个时候应该还是我再京东那边各种准备面试的时候。在京东那段时间工作说忙吧,也能划水,天天比较多的事情就是开会,当时的领导一言不合拉我们到会议室一开会就是1-2小时,也确实挺磨人的。不过在京东的那一年,也算是初次接触了互联网行业吧,毕竟以前做的还是toB项目,也算是从这里进行了入门,Es也是这一年用的最多的时候了,Java的知识,jvm什么的,也是这个时候逐渐开始尝试在项目中使用吧,然后就是在这里真真正正的体会到数据结构的作用。我们的一个搜索项目,调整数据结构之后搜索的rt从800+ms降低到50ms,真真正正的就是靠数据结构的优化。</p>\n<p>离开了京东,来到了高德,也可以说是阿里吧,算是真真正正的在互联网呆了3年,也真的是成长最快的3年了。在这边很幸运,开始遇到了好老大,对我也比较认可,项目什么的,我也一点点逐步自己扛起来。在这边切切实实的感觉到Java那些参数,数据结构的选择,超时,索引等各种设计的重要性,当你的应用只有几十QPS的时候,其实只要能用就行,也不会出什么大问题,但是如果你的应用动不动就是成千上万的请求量,那就是对数据结构对算法的一个真正的考核了,也是到了这里我才明白大学让我们学习数据结构的重要性。</p>\n<p>高德3年,在经历了一次晋升失败之后,今年成功晋升到技术专家。但是自己的身体状况也是每况愈下,明显感觉到自己的身体一天不如一天,加上老婆也怀孕了,自己也就选择了离开,为了照顾老婆以及即将出生的孩子,我选择了去国企,主要是为了稳定。经历京东的毕业,阿里的滚动式裁员,虽然都没有轮到自己,但是还是很后怕,互联网就是青春饭,熬上个几年能扯出来就撤出来吧。</p>\n<h2 id=\"工作及规划\"><a href=\"#工作及规划\" class=\"headerlink\" title=\"工作及规划\"></a>工作及规划</h2><p>当过技术负责人,当过业务组组长,觉得自己还是不喜欢做管理,后来找工作的时候,还是找偏向技术一点的国企,就是为了不放下技术,而且这次也是去了一个全新的领域,基本上全都是从0开始学习,刚开始的时候确实有压力,尤其是最近弄容器适配这块,屡屡没有成效,让自己的信心收到了打击,自己都感觉有点抑郁了,不过还好,后面各种尝试后,问题算是解决了。</p>\n<p>技术栈这块想想自己每次换工作也是换一个领域,从做数据ETL到做搜索项目,到做业务项目,到做Iaas层应用,每次换完工作基本都是从0开始,不过也觉得这一路走来收获颇丰,后面还是继续保持学习。</p>\n<h2 id=\"学习\"><a href=\"#学习\" class=\"headerlink\" title=\"学习\"></a>学习</h2><p>说到学习,回想一下最近3年,好像看过的书,都不到10本吧,还多是什么半小时漫画之类的,想想自己在京东那一年下去,自己看过的书都有几十本了,后面还是继续保持看书的习惯吧,毕竟自己现在工作不像在互联网那会了,多少有点自己的时间了。</p>\n<h2 id=\"以后\"><a href=\"#以后\" class=\"headerlink\" title=\"以后\"></a>以后</h2><p>稀里糊涂写了一堆,大概就是自己这几年互联网到逃离互联网的感想吧,也没什么思路什么的,就想到什么写什么,后面还是继续更新一些自己学习总结什么的。<br>现在业务上自己主方向换了,java基本上逐渐用不上了,后面语言主要应该是go+rust吧,自己瞎捣鼓可能还会用python之类的,其他的可能主要研究虚拟化或者容器相关内容了,唉,随着工作业务方向走吧,就先写这么多。</p>\n","categories":["随笔"],"tags":["随笔"]},{"title":"MySQL学习笔记-MySQL数据库索引优化","url":"/2019/08/01/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96/","content":"<h2 id=\"MySQL支持的索引类型\"><a href=\"#MySQL支持的索引类型\" class=\"headerlink\" title=\"MySQL支持的索引类型\"></a>MySQL支持的索引类型</h2><h3 id=\"Btree索引\"><a href=\"#Btree索引\" class=\"headerlink\" title=\"Btree索引\"></a>Btree索引</h3><ul>\n<li><p>通过B+Tree结构存储数据</p>\n</li>\n<li><p>加快数据的查询速度</p>\n</li>\n<li><p>更适合进行范围查找</p>\n</li>\n<li><p>顺序存储</p>\n</li>\n<li><p>什么情况下可以使用到</p>\n<ul>\n<li>全值匹配的查询</li>\n<li>匹配最左前缀的查询</li>\n<li>匹配列前缀查询</li>\n<li>匹配范围值的查询</li>\n<li>精确匹配左前列并范围匹配另外一列</li>\n<li>只访问索引的查询</li>\n</ul>\n</li>\n<li><p>使用限制</p>\n<ul>\n<li>如果不是按照索引最左列开始查找,则无法使用索引</li>\n<li>使用索引时不能跳过索引中的列</li>\n<li>not in和<>不能使用索引</li>\n<li>如果查询中有某个列的范围查询,则其右边所有列都无法使用索引</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Hash索引\"><a href=\"#Hash索引\" class=\"headerlink\" title=\"Hash索引\"></a>Hash索引</h3><ul>\n<li><p>特点</p>\n<ul>\n<li>基于hash表实现,只有查询条件精确匹配hash索引中的所有列的时候才能够使用hash索引</li>\n<li>对于hash索引中的所有列,存储引擎都会为每一行计算一个hash码,hash索引中存储的就是hash码</li>\n</ul>\n</li>\n<li><p>限制</p>\n<ul>\n<li>hash索引必须进行二次查找</li>\n<li>hash索引无法用于排序</li>\n<li>hash索引无法进行范围查找,不支持部分索引查找</li>\n<li>hash索引中的hash码的计算可能存在has冲突</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"为什么要使用索引\"><a href=\"#为什么要使用索引\" class=\"headerlink\" title=\"为什么要使用索引\"></a>为什么要使用索引</h2><ul>\n<li><p>索引大大减少了存储引擎需要扫描的数据量</p>\n</li>\n<li><p>索引可以帮助我们进行排序,以避免使用临时表</p>\n</li>\n<li><p>索引可以把随机I/O改变为顺序I/O</p>\n</li>\n</ul>\n<h2 id=\"索引不是越多越好\"><a href=\"#索引不是越多越好\" class=\"headerlink\" title=\"索引不是越多越好\"></a>索引不是越多越好</h2><ul>\n<li><p>索引会增加写操作的成本</p>\n</li>\n<li><p>太多的索引会增加查询优化器的选择时间</p>\n</li>\n</ul>\n<h2 id=\"索引优化策略\"><a href=\"#索引优化策略\" class=\"headerlink\" title=\"索引优化策略\"></a>索引优化策略</h2><h3 id=\"索引列上不能使用表达式或者函数\"><a href=\"#索引列上不能使用表达式或者函数\" class=\"headerlink\" title=\"索引列上不能使用表达式或者函数\"></a>索引列上不能使用表达式或者函数</h3><h3 id=\"前缀索引和索引列的选择性\"><a href=\"#前缀索引和索引列的选择性\" class=\"headerlink\" title=\"前缀索引和索引列的选择性\"></a>前缀索引和索引列的选择性</h3><ul>\n<li>索引的选择性是不重复的索引值和表的记录数的比值</li>\n</ul>\n<h3 id=\"联合索引\"><a href=\"#联合索引\" class=\"headerlink\" title=\"联合索引\"></a>联合索引</h3><ul>\n<li><p>如何选择索引列的顺序</p>\n<ul>\n<li>经常被使用到的列优先</li>\n<li>选择性高的列优先</li>\n<li>宽度小的列优先</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"覆盖索引\"><a href=\"#覆盖索引\" class=\"headerlink\" title=\"覆盖索引\"></a>覆盖索引</h3><ul>\n<li><p>优点</p>\n<ul>\n<li>可以优化缓存,减少磁盘IO操作</li>\n<li>可以减少随机I/O,变随机I/O操作为顺序I/O操作</li>\n<li>可以避免对Innodb主键索引的二次查询</li>\n<li>可以避免MyISAM表进行系统调用</li>\n</ul>\n</li>\n<li><p>无法使用覆盖索引的情况</p>\n<ul>\n<li>存储引擎不支持覆盖索引</li>\n<li>查询中使用了太多的列</li>\n<li>使用了双%号的like查询</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"使用索引来优化查询\"><a href=\"#使用索引来优化查询\" class=\"headerlink\" title=\"使用索引来优化查询\"></a>使用索引来优化查询</h3><ul>\n<li><p>使用索引扫描来优化排序</p>\n<ul>\n<li>通过排序操作</li>\n<li>按照索引顺序扫描数据</li>\n<li>索引的列顺序和Order By子句的顺序完全一致</li>\n<li>索引中所有列的方向(升序,降序)和Order By子句完全一致</li>\n<li>Order by中的字段全部在关联表中的第一张表中</li>\n</ul>\n</li>\n<li><p>模拟Hash索引优化查询</p>\n<ul>\n<li>只能处理键值的全值匹配</li>\n<li>所使用的Hash函数决定着索引键的大小</li>\n</ul>\n</li>\n<li><p>利用索引优化锁</p>\n<ul>\n<li>索引可以减少锁定的行数</li>\n<li>索引可以加快处理速度,同时也加快了锁的释放</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"索引的维护和优化\"><a href=\"#索引的维护和优化\" class=\"headerlink\" title=\"索引的维护和优化\"></a>索引的维护和优化</h3><ul>\n<li><p>删除重复和冗余的索引</p>\n</li>\n<li><p>查找未被使用过的索引</p>\n</li>\n<li><p>更新索引统计信息以及减少索引碎片</p>\n<ul>\n<li>analyze table table_name</li>\n<li>optimize table table_name</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"附件:Xmind图\"><a href=\"#附件:Xmind图\" class=\"headerlink\" title=\"附件:Xmind图\"></a>附件:Xmind图</h2><p><img src=\"/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96.png\" alt=\"MySQL数据库索引优化\"></p>\n","categories":["MySQL"],"tags":["Index","MySQL","优化"]},{"title":"MySQL学习笔记-什么影响了MySQL性能","url":"/2019/07/30/%E4%BB%80%E4%B9%88%E5%BD%B1%E5%93%8D%E4%BA%86MySQL%E6%80%A7%E8%83%BD/","content":"<h2 id=\"服务器硬件\"><a href=\"#服务器硬件\" class=\"headerlink\" title=\"服务器硬件\"></a>服务器硬件</h2><h3 id=\"CPU资源\"><a href=\"#CPU资源\" class=\"headerlink\" title=\"CPU资源\"></a>CPU资源</h3><ul>\n<li>Web应用-Core重于频率</li>\n</ul>\n<h3 id=\"内存大小\"><a href=\"#内存大小\" class=\"headerlink\" title=\"内存大小\"></a>内存大小</h3><ul>\n<li>主板支持最大频率的内存</li>\n</ul>\n<h3 id=\"网络\"><a href=\"#网络\" class=\"headerlink\" title=\"网络\"></a>网络</h3><h3 id=\"IO子系统\"><a href=\"#IO子系统\" class=\"headerlink\" title=\"IO子系统\"></a>IO子系统</h3><ul>\n<li><p>传统硬盘</p>\n<ul>\n<li>存储容量</li>\n<li>传输速度</li>\n<li>访问时间</li>\n<li>主轴转速</li>\n<li>物理尺寸</li>\n</ul>\n</li>\n<li><p>RAID</p>\n<ul>\n<li><p>RAID0</p>\n<ul>\n<li>多个独立磁盘串行</li>\n<li>最简单</li>\n<li>性价比最高</li>\n<li>数据没有冗余</li>\n</ul>\n</li>\n<li><p>RAID1</p>\n<ul>\n<li>磁盘镜像</li>\n<li>安全性最高</li>\n</ul>\n</li>\n<li><p>RAID5</p>\n<ul>\n<li>分布式奇偶校验磁盘阵列</li>\n</ul>\n</li>\n<li><p>RAID10</p>\n<ul>\n<li>分片镜像</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>固态存储</p>\n<ul>\n<li><p>更好的随机读写性能</p>\n</li>\n<li><p>更好的支持并发</p>\n</li>\n<li><p>更容易损坏</p>\n</li>\n<li><p>使用场景</p>\n<ul>\n<li>大量随机I/O场景</li>\n<li>单线程负载I/O瓶颈</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>网络存储</p>\n<ul>\n<li><p>SAN</p>\n<ul>\n<li>大量顺序读写</li>\n</ul>\n</li>\n<li><p>NAS</p>\n<ul>\n<li>有一定网络延迟</li>\n</ul>\n</li>\n<li><p>适合数据库备份</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"网络性能的限制\"><a href=\"#网络性能的限制\" class=\"headerlink\" title=\"网络性能的限制\"></a>网络性能的限制</h3><ul>\n<li>延迟</li>\n<li>带宽</li>\n<li>网络质量影响</li>\n</ul>\n<h2 id=\"操作系统\"><a href=\"#操作系统\" class=\"headerlink\" title=\"操作系统\"></a>操作系统</h2><h3 id=\"CentOS\"><a href=\"#CentOS\" class=\"headerlink\" title=\"CentOS\"></a>CentOS</h3><ul>\n<li><p>内核相关参数(/etc/sysctl.conf)</p>\n<ul>\n<li><p>网络参数</p>\n<ul>\n<li>net.core.somaxconn=65535 #监听队列长度</li>\n<li>net.core.netdev_max_backlog=65535</li>\n<li>net.ipv4.tcp_max_syn_backlog=65535</li>\n<li>net.ipv4.tcp_fin_timeout =10</li>\n<li>net.ipv4.tcp_tw_reuse =1</li>\n<li>net.ipv4.tcp_tw_recycle =1</li>\n<li>net.core.wmem_default = 87380</li>\n<li>net.core.wmem_max = 16777216</li>\n<li>net.core.rmem_default = 87380</li>\n<li>net.core.rmem_max = 16777216</li>\n<li>net.ipv4.tcp_keepalive_time =120</li>\n<li>net.ipv4.tcp_keepalive_intvl =30</li>\n<li>net.ipv4.tcp_keepalive_probes =3</li>\n</ul>\n</li>\n<li><p>内核参数</p>\n<ul>\n<li><p>kernel.shmmax=4294967295</p>\n<ul>\n<li><p>Linux内核参数中最重要的参数之一,用于定义单个共享内存段的最大值</p>\n</li>\n<li><p>注意事项</p>\n<ul>\n<li>这个参数应该设置的足够大,以便能在一个共享内存段下容纳下整个的Innodb缓冲池的大小</li>\n<li>这个值的大小对于64位Linux系统,可取的最大值为物理内存值-1byte,建议设置为大于物理内存的一半,一般取值大于Innodb缓冲池的大小即可。</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>vm.swappiness = 0</p>\n<ul>\n<li><p>当内存不足时会对性能产生比较明显的影响</p>\n</li>\n<li><p>如果完全禁用Swap</p>\n<ul>\n<li>降低操作系统性能</li>\n<li>容易造成内存溢出,崩溃,或都被操作系统Kill</li>\n</ul>\n</li>\n<li><p>结论</p>\n<ul>\n<li>需要保留交换分区,但是要控制何时使用交换分区</li>\n</ul>\n</li>\n<li><p>为0</p>\n<ul>\n<li>除非内存使用满了,否则不要使用交换分区</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>增加资源限制(/etc/security/limit.conf)</p>\n<ul>\n<li><p><code>* soft nofile 65535</code></p>\n</li>\n<li><p><code>* hard nofile 65535</code></p>\n</li>\n<li><p>解释</p>\n<ul>\n<li><code>* 所有用户有效</code></li>\n<li><code>soft指的是当前系统生效的配置</code></li>\n<li><code>hard表明系统中所能设定的最大值</code></li>\n<li><code>nofile表示所限制的资源师打开文件的最大数目</code></li>\n<li><code>65535 限制的数量</code></li>\n</ul>\n</li>\n<li><p>结论:把可以打开的文件数量增加到65535个以保证可以打开足够多的文件句柄</p>\n</li>\n<li><p>注意:这个文件的修改需要重启系统才可以生效</p>\n</li>\n</ul>\n</li>\n<li><p>磁盘调度策略(/sys/block/devname/queue/scheduler)</p>\n<ul>\n<li><p>noop</p>\n<ul>\n<li>电梯式调度策略(适合闪存,RAM,嵌入式系统)</li>\n</ul>\n</li>\n<li><p>cfq</p>\n<ul>\n<li>完全公平队列(默认,不适合MySQL)</li>\n</ul>\n</li>\n<li><p>deadline</p>\n<ul>\n<li>截止时间调度策略(适合数据库)</li>\n</ul>\n</li>\n<li><p>anticipatory</p>\n<ul>\n<li>预料I/O调度测量(适合写入较多的环境,比如文件服务器,不适合数据库)</li>\n</ul>\n</li>\n<li><p>修改方法</p>\n<ul>\n<li>echo deadlin > /sys/block/sda/queue/scheduler</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>文件系统影响</p>\n<ul>\n<li><p>Linux</p>\n<ul>\n<li><p>推荐使用XFS</p>\n</li>\n<li><p>EXT3/4</p>\n<ul>\n<li><p>系统挂载参数(/etc/fstab)</p>\n<ul>\n<li><p>data = writeback | ordered | journal</p>\n<ul>\n<li>Innodb适用于writeback,ordered较慢,journal最慢,最安全</li>\n</ul>\n</li>\n<li><p>noatime,nodiratime</p>\n<ul>\n<li>禁用相关时间</li>\n</ul>\n</li>\n<li><p>示例:</p>\n<ul>\n<li>/dev/sda1/ext3\tnoatime,nodiratime,data=writeback 1 1</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"数据库存储引擎的选择\"><a href=\"#数据库存储引擎的选择\" class=\"headerlink\" title=\"数据库存储引擎的选择\"></a>数据库存储引擎的选择</h2><h3 id=\"MyISAM\"><a href=\"#MyISAM\" class=\"headerlink\" title=\"MyISAM\"></a>MyISAM</h3><ul>\n<li><p>MySQL5.5之前的默认存储引擎</p>\n</li>\n<li><p>存储为MYD,MYI两个文件</p>\n</li>\n<li><p>特性</p>\n<ul>\n<li><p>并发性与锁级别</p>\n<ul>\n<li>表锁</li>\n</ul>\n</li>\n<li><p>表损坏修复</p>\n<ul>\n<li>check table tablename</li>\n<li>repire table tablename</li>\n</ul>\n</li>\n<li><p>支持索引类型</p>\n<ul>\n<li>全文索引</li>\n<li>Text等前缀索引</li>\n</ul>\n</li>\n<li><p>支持数据压缩</p>\n<ul>\n<li>myisampack</li>\n<li>只读</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>限制</p>\n<ul>\n<li>单表<256T</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li><p>非事务型应用</p>\n</li>\n<li><p>只读类应用(支持压缩)</p>\n</li>\n<li><p>空间类应用</p>\n<ul>\n<li>支持空间函数</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Innodb\"><a href=\"#Innodb\" class=\"headerlink\" title=\"Innodb\"></a>Innodb</h3><ul>\n<li><p>使用表空间进行数据存储</p>\n<ul>\n<li><p>innodb_file_per_table</p>\n<ul>\n<li>ON:独立表空间:tablename.ibd</li>\n<li>OFF:系统表空间:ibdataX</li>\n</ul>\n</li>\n<li><p>比较</p>\n<ul>\n<li>系统表空间无法简单的收缩文件大小</li>\n<li>独立表空间可以通过optimize table命令收缩系统文件</li>\n<li>系统表空间会产生I/O瓶颈</li>\n<li>独立表空间可以同时向多个文件刷新数据</li>\n</ul>\n</li>\n<li><p>建议</p>\n<ul>\n<li>对Innodb使用独立表空间</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>系统表空间</p>\n<ul>\n<li>Innodb数据字典信息</li>\n<li>Undo回滚段</li>\n</ul>\n</li>\n<li><p>特性</p>\n<ul>\n<li>Innodb是一种事务性的存储引擎</li>\n<li>完全支持事务的ACID特性</li>\n<li>Redo Log和Undo Log</li>\n<li>支持行级锁</li>\n<li>行级锁可以最大程度支持并发</li>\n<li>行级锁是由存储引擎层实现的</li>\n</ul>\n</li>\n<li><p>什么是锁</p>\n<ul>\n<li><p>锁主要作用是管理共享资源的并发访问</p>\n</li>\n<li><p>锁用于实现事务的隔离性</p>\n</li>\n<li><p>锁的类型</p>\n<ul>\n<li>共享锁(读锁)</li>\n<li>独占锁(写锁)</li>\n</ul>\n</li>\n<li><p>锁的粒度</p>\n<ul>\n<li>表级锁</li>\n<li>行级锁</li>\n</ul>\n</li>\n<li><p>阻塞和死锁</p>\n</li>\n</ul>\n</li>\n<li><p>Innodb状态检查</p>\n<ul>\n<li>show engine innodb status</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li>使用大多数OLTP应用</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"CSV\"><a href=\"#CSV\" class=\"headerlink\" title=\"CSV\"></a>CSV</h3><ul>\n<li><p>文件系统存储特点</p>\n<ul>\n<li>数据以文本方式存储在文件中</li>\n<li>.csv文件存储表内容</li>\n<li>CSM文件存储表的元数据如表的状态和数据量</li>\n<li>frm文件存储表结构信息</li>\n</ul>\n</li>\n<li><p>特点</p>\n<ul>\n<li><p>以CSV格式进行数据存储</p>\n</li>\n<li><p>所有列必须都是不能为null的</p>\n</li>\n<li><p>不支持索引</p>\n<ul>\n<li>不适合大表</li>\n</ul>\n</li>\n<li><p>可以对数据文件直接进行编辑</p>\n<ul>\n<li>保存文本文件内容</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li>适合作为数据交换的中间表</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Archive\"><a href=\"#Archive\" class=\"headerlink\" title=\"Archive\"></a>Archive</h3><ul>\n<li><p>文件存储特点</p>\n<ul>\n<li>以Zlib对表数据进行压缩,磁盘I/O更少</li>\n<li>数据存储在ARZ为后缀的文件中</li>\n<li>只支持Insert和select操作</li>\n<li>只允许在自增ID列上加索引</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li>日志和数据采集类应用</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Memory\"><a href=\"#Memory\" class=\"headerlink\" title=\"Memory\"></a>Memory</h3><ul>\n<li><p>文件系统特点</p>\n<ul>\n<li>也称为HEAP存储引擎,数据保存在内存中</li>\n</ul>\n</li>\n<li><p>功能特点</p>\n<ul>\n<li><p>支持HASH索引和BTree索引</p>\n<ul>\n<li>HASH适合等值</li>\n<li>BTree适合范围</li>\n</ul>\n</li>\n<li><p>所有字段都为固定长度</p>\n</li>\n<li><p>不支持Blog和Text等大字段</p>\n</li>\n<li><p>使用表级锁</p>\n</li>\n<li><p>最大大小由max_heap_table_size参数决定</p>\n<ul>\n<li>默认16M</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>容易混淆的概念</p>\n<ul>\n<li><p>Memory存储引擎表</p>\n</li>\n<li><p>临时表</p>\n<ul>\n<li><p>系统使用临时表</p>\n<ul>\n<li>超过限制使用MyISAM临时表</li>\n<li>未超过限制使用Memory表</li>\n</ul>\n</li>\n<li><p>create temporary table 建立的临时表</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li>用于查找或者映射表</li>\n<li>用于保存数据分析过程中产生的中间表</li>\n<li>用于缓存周期性聚合数据的结果表</li>\n<li>MEMory数据容易丢失,所以要求数据可再生</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"Federated\"><a href=\"#Federated\" class=\"headerlink\" title=\"Federated\"></a>Federated</h3><ul>\n<li><p>特点</p>\n<ul>\n<li>提供了访问MySQL服务器上表的方法</li>\n<li>本地不存储数据,数据全部存放到远程服务器上</li>\n<li>本地需要保存表结构和服务器连接信息</li>\n</ul>\n</li>\n<li><p>如何使用</p>\n<ul>\n<li>默认禁止,启用需要在启动时增加federated参数</li>\n</ul>\n</li>\n<li><p>适用场景</p>\n<ul>\n<li>偶尔统计分析以及手工查询</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"如何选择正确的存储引擎\"><a href=\"#如何选择正确的存储引擎\" class=\"headerlink\" title=\"如何选择正确的存储引擎\"></a>如何选择正确的存储引擎</h3><ul>\n<li><p>一般选择InnoDB</p>\n</li>\n<li><p>参考条件</p>\n<ul>\n<li>事务</li>\n<li>备份</li>\n<li>崩溃恢复</li>\n<li>存储引擎的特有特性</li>\n</ul>\n</li>\n<li><p>尽量不要混合使用存储引擎</p>\n</li>\n</ul>\n<h2 id=\"数据库参数配置\"><a href=\"#数据库参数配置\" class=\"headerlink\" title=\"数据库参数配置\"></a>数据库参数配置</h2><h3 id=\"MySQL获取配置信息路径\"><a href=\"#MySQL获取配置信息路径\" class=\"headerlink\" title=\"MySQL获取配置信息路径\"></a>MySQL获取配置信息路径</h3><ul>\n<li>命令行参数</li>\n<li>配置文件</li>\n</ul>\n<h3 id=\"MySQL配置参数的作用域\"><a href=\"#MySQL配置参数的作用域\" class=\"headerlink\" title=\"MySQL配置参数的作用域\"></a>MySQL配置参数的作用域</h3><ul>\n<li><p>全局参数</p>\n<ul>\n<li>set global 参数名=参数值;</li>\n<li>set @@global.参数名:=参数值;</li>\n</ul>\n</li>\n<li><p>会话参数</p>\n<ul>\n<li>set [session] 参数名=参数值</li>\n<li>set @@session.参数名:=参数值</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"内存配置相关参数\"><a href=\"#内存配置相关参数\" class=\"headerlink\" title=\"内存配置相关参数\"></a>内存配置相关参数</h3><ul>\n<li><p>确定可以使用的内存的上限</p>\n</li>\n<li><p>确定MySQL的每个连接使用的内存</p>\n<ul>\n<li>sort_buffer_size</li>\n<li>join_buffer_size</li>\n<li>read_buffer_size</li>\n<li>read_rnd_buffer_size</li>\n</ul>\n</li>\n<li><p>确定需要为操作系统保留多少内存</p>\n</li>\n<li><p>如何为缓存池分配内存</p>\n<ul>\n<li><p>Innodb_buffer_pool_size</p>\n<ul>\n<li>总内存 -(每个线程所需要的内存*连接数)- 系统保留内存</li>\n</ul>\n</li>\n<li><p>key_buffer_size</p>\n<ul>\n<li>主要MyISAM使用</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"IO相关配置参数\"><a href=\"#IO相关配置参数\" class=\"headerlink\" title=\"IO相关配置参数\"></a>IO相关配置参数</h3><ul>\n<li><p>Innodb I/O 相关配置</p>\n<ul>\n<li><p>Innodb_log_file_size</p>\n</li>\n<li><p>Innodb_log_files_in_group</p>\n</li>\n<li><p>事务日志总大小 = Innodb_log_file_size * Innodb_log_files_in_group</p>\n</li>\n<li><p>Innodb_log_buffer_size</p>\n</li>\n<li><p>Innodb_flush_log_at_trx_commit</p>\n<ul>\n<li>0:每秒进行一次log写入cache,并flush log到磁盘</li>\n<li>1:每次事务提交执行log写入cache,并flush log到磁盘</li>\n<li>2:每次事务提交,执行log数据写入到cache,每秒执行一次flush log到磁盘</li>\n</ul>\n</li>\n<li><p>Innodb_flush_method=O_DIRECT</p>\n<ul>\n<li>关闭操作系统缓存(Linux建议)</li>\n</ul>\n</li>\n<li><p>Innodb_file_per_table = 1</p>\n</li>\n<li><p>Innodb_doublewriter = 1</p>\n</li>\n</ul>\n</li>\n<li><p>MyISAM</p>\n<ul>\n<li><p>delay_key_write</p>\n<ul>\n<li>OFF:每次写操作后刷新键缓冲中的脏块到磁盘</li>\n<li>ON: 支队在键表时制定了delay_key_write选项的表使用延迟刷新</li>\n<li>ALL:对所有MyISAM表都是用延迟建写入</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"安全相关配置参数\"><a href=\"#安全相关配置参数\" class=\"headerlink\" title=\"安全相关配置参数\"></a>安全相关配置参数</h3><ul>\n<li><p>expire_logs_days</p>\n<ul>\n<li>指定自动清理binlog的天数</li>\n</ul>\n</li>\n<li><p>max_allowed_packet</p>\n<ul>\n<li>控制MySQL可以接受的包的大小</li>\n</ul>\n</li>\n<li><p>skip_name_resolve</p>\n<ul>\n<li>禁用DNS查找</li>\n</ul>\n</li>\n<li><p>sysdate_is_now</p>\n<ul>\n<li>确保sysdate()返回确定性日期</li>\n</ul>\n</li>\n<li><p>read_only</p>\n<ul>\n<li>禁止非super权限的用户写权限</li>\n</ul>\n</li>\n<li><p>skip_slave_start</p>\n<ul>\n<li>禁用Salve恢复</li>\n</ul>\n</li>\n<li><p>sql_mode</p>\n<ul>\n<li>设置MySQL所使用的SQL模式</li>\n<li>strict_trans_tables</li>\n<li>no_engine_subtitution</li>\n<li>no_zero_date</li>\n<li>no_zero_in_date</li>\n<li>only_full_group_by</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"其他常用配置参数\"><a href=\"#其他常用配置参数\" class=\"headerlink\" title=\"其他常用配置参数\"></a>其他常用配置参数</h3><ul>\n<li><p>sync_binlog</p>\n<ul>\n<li>控制MySQL如何向磁盘刷新binlog</li>\n</ul>\n</li>\n<li><p>tmp_table_size/max_heap_table_size</p>\n<ul>\n<li>控制内存临时表大小</li>\n</ul>\n</li>\n<li><p>max_connections</p>\n<ul>\n<li>控制允许的最大连接数</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"数据库结构设计和SQL语句\"><a href=\"#数据库结构设计和SQL语句\" class=\"headerlink\" title=\"数据库结构设计和SQL语句\"></a>数据库结构设计和SQL语句</h2><h3 id=\"数据库设计对性能的影响\"><a href=\"#数据库设计对性能的影响\" class=\"headerlink\" title=\"数据库设计对性能的影响\"></a>数据库设计对性能的影响</h3><ul>\n<li>过分的反范式化为表建立太多的列</li>\n<li>过分的范式化造成太多的表关联</li>\n<li>OLTP环境中使用了不恰当的分区表</li>\n<li>使用外键保证数据的完整性</li>\n</ul>\n<h1 id=\"附Xmind\"><a href=\"#附Xmind\" class=\"headerlink\" title=\"附Xmind\"></a>附Xmind</h1><p><img src=\"/%E4%BB%80%E4%B9%88%E5%BD%B1%E5%93%8D%E4%BA%86MySQL%E6%80%A7%E8%83%BD/%E4%BB%80%E4%B9%88%E5%BD%B1%E5%93%8D%E4%BA%86MySQL%E6%80%A7%E8%83%BD.png\" alt=\"什么影响了MySQL性能\"></p>\n","categories":["MySQL"],"tags":["MySQL","性能"]},{"title":"MySQL学习笔记-数据库结构优化","url":"/2019/07/31/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%BC%98%E5%8C%96/","content":"<h2 id=\"目的\"><a href=\"#目的\" class=\"headerlink\" title=\"目的\"></a>目的</h2><h3 id=\"减少数据冗余\"><a href=\"#减少数据冗余\" class=\"headerlink\" title=\"减少数据冗余\"></a>减少数据冗余</h3><h3 id=\"尽量避免数据维护中出现更新,插入和删除异常\"><a href=\"#尽量避免数据维护中出现更新,插入和删除异常\" class=\"headerlink\" title=\"尽量避免数据维护中出现更新,插入和删除异常\"></a>尽量避免数据维护中出现更新,插入和删除异常</h3><ul>\n<li><p>插入异常</p>\n<ul>\n<li>如果表中的某个实体随着另外一个实体存在而存在</li>\n</ul>\n</li>\n<li><p>更新异常</p>\n<ul>\n<li>如果更高表中的某个实体的单独属性时,需要对多行进行更新</li>\n</ul>\n</li>\n<li><p>删除异常</p>\n<ul>\n<li>如果删除表中的某一实体则会导致其他实体的消失</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"节约数据存储空间\"><a href=\"#节约数据存储空间\" class=\"headerlink\" title=\"节约数据存储空间\"></a>节约数据存储空间</h3><h3 id=\"提高查询效率\"><a href=\"#提高查询效率\" class=\"headerlink\" title=\"提高查询效率\"></a>提高查询效率</h3><h2 id=\"数据库设计范式\"><a href=\"#数据库设计范式\" class=\"headerlink\" title=\"数据库设计范式\"></a>数据库设计范式</h2><h3 id=\"第一设计范式\"><a href=\"#第一设计范式\" class=\"headerlink\" title=\"第一设计范式\"></a>第一设计范式</h3><ul>\n<li>数据库中表的所有字段都只具有单一属性</li>\n<li>单一属性的列是由基本的数据类型所构成的</li>\n<li>设计出来的表都是简单的二维表</li>\n</ul>\n<h3 id=\"第二设计范式\"><a href=\"#第二设计范式\" class=\"headerlink\" title=\"第二设计范式\"></a>第二设计范式</h3><ul>\n<li>一个表中具有一个业务主键</li>\n</ul>\n<h3 id=\"第三设计范式\"><a href=\"#第三设计范式\" class=\"headerlink\" title=\"第三设计范式\"></a>第三设计范式</h3><ul>\n<li>每一个非主属性,既不部分依赖于也不传递依赖于业务主键</li>\n</ul>\n<h3 id=\"反范式化设计\"><a href=\"#反范式化设计\" class=\"headerlink\" title=\"反范式化设计\"></a>反范式化设计</h3><ul>\n<li>少量的数据冗余,提高查询效率。空间换时间。</li>\n</ul>\n<h3 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h3><ul>\n<li><p>范式化</p>\n<ul>\n<li><p>优点</p>\n<ul>\n<li><p>可以尽量的减少数据冗余</p>\n<ul>\n<li>数据表更新快,体积小</li>\n</ul>\n</li>\n<li><p>范式化的更新操作比反范式化更快</p>\n</li>\n<li><p>范式化的表通常比反范式化更小</p>\n</li>\n</ul>\n</li>\n<li><p>缺点</p>\n<ul>\n<li>对于查询需要对多个表进行关联</li>\n<li>更难进行索引优化</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>反范式化</p>\n<ul>\n<li><p>优点</p>\n<ul>\n<li>减少表关联</li>\n<li>更好的进行索引优化</li>\n</ul>\n</li>\n<li><p>缺点</p>\n<ul>\n<li>存在数据冗余以及数据维护异常</li>\n<li>对数据的修改需要更多的成本</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"物理设计原则\"><a href=\"#物理设计原则\" class=\"headerlink\" title=\"物理设计原则\"></a>物理设计原则</h2><h3 id=\"定义数据库,表以及字段的命名规范\"><a href=\"#定义数据库,表以及字段的命名规范\" class=\"headerlink\" title=\"定义数据库,表以及字段的命名规范\"></a>定义数据库,表以及字段的命名规范</h3><ul>\n<li>可读性原则</li>\n<li>表意性原则</li>\n<li>长名原则</li>\n</ul>\n<h3 id=\"选择合适的存储引擎\"><a href=\"#选择合适的存储引擎\" class=\"headerlink\" title=\"选择合适的存储引擎\"></a>选择合适的存储引擎</h3><h3 id=\"为表中的字段选择合适的数据类型\"><a href=\"#为表中的字段选择合适的数据类型\" class=\"headerlink\" title=\"为表中的字段选择合适的数据类型\"></a>为表中的字段选择合适的数据类型</h3><ul>\n<li><p>当一个列可以选择多种数据类型的时候,应该优先考虑数字类型,其次是日期或者二进制类型,最后是字符类型。对于相同级别的数据类型,应该优先考虑占用空间小的数据类型</p>\n</li>\n<li><p>Varchar和char</p>\n<ul>\n<li><p>varchar</p>\n<ul>\n<li><p>Varchar类型的存储特点</p>\n<ul>\n<li>VARCHAR用于存储变长字符串,只占用必要的存储空间</li>\n<li>列的最大长度小于255则只占用一个额外的字节,用于记录字符串长度</li>\n<li>列的最大长度大于255则占用两个额外的字节,用于记录字符串长度</li>\n</ul>\n</li>\n<li><p>VARCHAR长度的选择问题</p>\n<ul>\n<li>使用最小的符合需求的长度</li>\n<li>varchar(5)和varchar(200)存储‘MYSQL’字符串性能不同</li>\n</ul>\n</li>\n<li><p>varchar的适用场景</p>\n<ul>\n<li>字符串列最大长度比平均长度大很多</li>\n<li>字符串列很少被更新</li>\n<li>使用了多字节字符集存储字符串</li>\n</ul>\n</li>\n<li><p>char类型是定长的</p>\n</li>\n</ul>\n</li>\n<li><p>char</p>\n<ul>\n<li><p>Char类型的存储特点</p>\n<ul>\n<li>字符串存储在char类型的列中会删除末尾的空格</li>\n<li>Char类型的最大宽度为255</li>\n</ul>\n</li>\n<li><p>char类型的适用场景</p>\n<ul>\n<li><p>char类型适合存储长度近似的值</p>\n<ul>\n<li>比如MD5,手机号,身份证号</li>\n</ul>\n</li>\n<li><p>char类型适合存储短字符串</p>\n</li>\n<li><p>char类型适合经常更新的字符串列</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>如何存储日期类型</p>\n<ul>\n<li><p>DATATIME类型</p>\n<ul>\n<li><p>占用8字节存储空间</p>\n</li>\n<li><p>时区无关</p>\n</li>\n<li><p>时间范围</p>\n<ul>\n<li>1000-01-01 00:00:00 ~ 9999-12-31 23:59:59</li>\n</ul>\n</li>\n<li><p>存储格式</p>\n<ul>\n<li>YYYY-MM-DD HH:MM:SS[.fraction]</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>TIMESTAMP</p>\n<ul>\n<li><p>存储了由格林尼治时间到当前时间的秒数</p>\n</li>\n<li><p>占用4字节</p>\n</li>\n<li><p>时间范围</p>\n<ul>\n<li>1970-01-01到2038-01-19</li>\n</ul>\n</li>\n<li><p>依赖于所指定的时区</p>\n</li>\n<li><p>在行的数据修改时,可以自动修改timestamp列的值(根据时间戳自动更新)</p>\n</li>\n<li><p>默认第一个列是随着更改自动更新</p>\n</li>\n</ul>\n</li>\n<li><p>date类型</p>\n<ul>\n<li>只需要3个字节</li>\n<li>可以使用时间函数</li>\n<li>时间范围1000-01-01~9999-12-31</li>\n</ul>\n</li>\n<li><p>time类型</p>\n<ul>\n<li>用于存储时间数据</li>\n<li>HH:MM:SS</li>\n</ul>\n</li>\n<li><p>存储日期时间数据的注意事项</p>\n<ul>\n<li>不要使用字符串类型来存储日期时间数据</li>\n<li>日期时间类型通常比字符串占用的存储空间小</li>\n<li>日期时间类型在进行查找过滤时可以利用日期来进行对比</li>\n<li>日期时间类型还有着丰富的处理函数,可以方便的对时间进行日期计算</li>\n<li>使用Int存储日期时间不如使用Timestamp类型</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"附件:Xmind图\"><a href=\"#附件:Xmind图\" class=\"headerlink\" title=\"附件:Xmind图\"></a>附件:Xmind图</h2><p><img src=\"/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%BC%98%E5%8C%96/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%BC%98%E5%8C%96.png\" alt=\"数据库结构优化\"></p>\n","categories":["MySQL"],"tags":["MySQL","优化","结构"]},{"title":"关于Elasticsearch 使用 MatchPhrase搜索的一些坑","url":"/2018/01/03/%E5%85%B3%E4%BA%8EElasticsearch%20%E4%BD%BF%E7%94%A8%20MatchPhrase%E6%90%9C%E7%B4%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91/","content":"<p>对分词字段检索使用的通常是match查询,对于短语查询使用的是matchphrase查询,但是并不是matchphrase可以直接对分词字段进行不分词检索(也就是业务经常说的精确匹配),下面有个例子,使用Es的请注意。<br>某个Index下面存有如下内容:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "id": "1",</span><br><span class=\"line\"> "fulltext": "亚马逊卓越有限公司诉讼某某公司"</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>其中fulltext使用ik分词器进行分词存储,使用ik分词结果如下</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">"tokens": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚马逊",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 0</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 1,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 1</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "马",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 2,</span><br><span class=\"line\"> "type": "CN_CHAR",</span><br><span class=\"line\"> "position": 2</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "逊",</span><br><span class=\"line\"> "start_offset": 2,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 3</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓越",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 4</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 4,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 5</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "越有",</span><br><span class=\"line\"> "start_offset": 4,</span><br><span class=\"line\"> "end_offset": 6,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 6</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "有限公司",</span><br><span class=\"line\"> "start_offset": 5,</span><br><span class=\"line\"> "end_offset": 9,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 7</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "有限",</span><br><span class=\"line\"> "start_offset": 5,</span><br><span class=\"line\"> "end_offset": 7,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 8</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "公司",</span><br><span class=\"line\"> "start_offset": 7,</span><br><span class=\"line\"> "end_offset": 9,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 9</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "诉讼",</span><br><span class=\"line\"> "start_offset": 9,</span><br><span class=\"line\"> "end_offset": 11,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 10</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "讼",</span><br><span class=\"line\"> "start_offset": 10,</span><br><span class=\"line\"> "end_offset": 11,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 11</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "某某",</span><br><span class=\"line\"> "start_offset": 11,</span><br><span class=\"line\"> "end_offset": 13,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 12</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "某公司",</span><br><span class=\"line\"> "start_offset": 12,</span><br><span class=\"line\"> "end_offset": 15,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 13</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "公司",</span><br><span class=\"line\"> "start_offset": 13,</span><br><span class=\"line\"> "end_offset": 15,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 14</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br></pre></td></tr></table></figure>\n\n<p>对于如上结果,如果进行matchphrase查询 “亚马逊卓越”,无法匹配出任何结果<br>因为对 “亚马逊卓越” 进行分词后的结果为:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "tokens": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚马逊",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 0</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 1,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 1</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "马",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 2,</span><br><span class=\"line\"> "type": "CN_CHAR",</span><br><span class=\"line\"> "position": 2</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "逊",</span><br><span class=\"line\"> "start_offset": 2,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 3</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓越",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 4</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 4,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 5</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "越",</span><br><span class=\"line\"> "start_offset": 4,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_CHAR",</span><br><span class=\"line\"> "position": 6</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n\n<p>和存储的内容对比发现 原文存储中包含词语 “越有”,而查询语句中并不包含“越有”,包含的是“越”,因此使用matchphrase短语匹配失败,也就导致了无法检索出内容。<br>还是这个例子,换个词语进行检索,使用“亚马逊卓越有”,会发现竟然检索出来了,对“亚马逊卓越有”进行分词得到如下结果:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "tokens": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚马逊",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 0</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "亚",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 1,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 1</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "马",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 2,</span><br><span class=\"line\"> "type": "CN_CHAR",</span><br><span class=\"line\"> "position": 2</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "逊",</span><br><span class=\"line\"> "start_offset": 2,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 3</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓越",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 4</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "卓",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 4,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 5</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "越有",</span><br><span class=\"line\"> "start_offset": 4,</span><br><span class=\"line\"> "end_offset": 6,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 6</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>注意到了吗?这里出现了越有这个词,这也就是说现在的分词结果和之前的全文分词结果完全一致了,所以matchphrash也就找到了结果。</p>\n<p>再换一个极端点的例子,使用“越有限公司”去进行检索,你会惊讶的发现,竟然还能检索出来,对“越有限公司”进行分词,结果如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "tokens": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "越有",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 2,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 0</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "有限公司",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 1</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "有限",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 2</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "公司",</span><br><span class=\"line\"> "start_offset": 3,</span><br><span class=\"line\"> "end_offset": 5,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 3</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>这个结果和原文中的结果又是完全一致(从越有之后的内容一致),所以匹配出来了结果,注意点这里有个词语“有限公司”,检索词语如果我换成了“越有限”,就会发现没有查询到内容,因为“越有限”分词结果为:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "tokens": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "越有",</span><br><span class=\"line\"> "start_offset": 0,</span><br><span class=\"line\"> "end_offset": 2,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 0</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "token": "有限",</span><br><span class=\"line\"> "start_offset": 1,</span><br><span class=\"line\"> "end_offset": 3,</span><br><span class=\"line\"> "type": "CN_WORD",</span><br><span class=\"line\"> "position": 1</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>“越有”这个词是包含的,”有限”这个词语也是包含的,但是中间隔了一个“有限公司”,所以没有完全一致,也就匹配不到结果了。这时候如果我检索条件设置matchphrase的slop=1,使用“越有限”就能匹配到结果了,现在可以明白了,其实position的位置差就是slop的值,而matchphrase并不是所谓的词语拼接进行匹配,还是需要进行分词,以及position匹配的。</p>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","MatchPhrase"]},{"title":"用户空间与内核空间","url":"/2019/06/12/%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%86%85%E6%A0%B8%E7%A9%BA%E9%97%B4/","content":"<hr>\n<ul>\n<li><p>我们知道现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。</p>\n</li>\n<li><p>操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。</p>\n</li>\n<li><p>针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。</p>\n</li>\n<li><p>每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。</p>\n</li>\n<li><p>空间分配如下图所示:<br><img src=\"/%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%86%85%E6%A0%B8%E7%A9%BA%E9%97%B4/1.png\" alt=\"空间分配图\"></p>\n</li>\n<li><p>有了用户空间和内核空间,整个linux内部结构可以分为三部分,从最底层到最上层依次是:硬件–>内核空间–>用户空间。如下图所示:<br><img src=\"/%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%86%85%E6%A0%B8%E7%A9%BA%E9%97%B4/2.png\" alt=\"linux内部结构图\"></p>\n</li>\n<li><p>需要注意的细节问题:</p>\n<ul>\n<li>内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 </li>\n<li>Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。</li>\n</ul>\n</li>\n<li><p>内核态与用户态:</p>\n<ul>\n<li>当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。</li>\n<li>当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。</li>\n</ul>\n</li>\n<li><p>参考资料:</p>\n<ul>\n<li>用户空间与内核空间,进程上下文与中断上下文[总结][<a href=\"https://www.cnblogs.com/Anker/p/3269106.html]\">https://www.cnblogs.com/Anker/p/3269106.html]</a></li>\n</ul>\n</li>\n</ul>\n","categories":["Linux"],"tags":["Linux","操作系统"]},{"title":"MySQL学习笔记-数据库分库分表设计","url":"/2019/08/02/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E8%AE%BE%E8%AE%A1/","content":"<h2 id=\"数据库分库分表的几种方式\"><a href=\"#数据库分库分表的几种方式\" class=\"headerlink\" title=\"数据库分库分表的几种方式\"></a>数据库分库分表的几种方式</h2><h3 id=\"把一个实例中的多个数据库拆分到不同的实例\"><a href=\"#把一个实例中的多个数据库拆分到不同的实例\" class=\"headerlink\" title=\"把一个实例中的多个数据库拆分到不同的实例\"></a>把一个实例中的多个数据库拆分到不同的实例</h3><h3 id=\"把一个库中的表分离到不同的数据库中\"><a href=\"#把一个库中的表分离到不同的数据库中\" class=\"headerlink\" title=\"把一个库中的表分离到不同的数据库中\"></a>把一个库中的表分离到不同的数据库中</h3><h2 id=\"数据库分片前的准备\"><a href=\"#数据库分片前的准备\" class=\"headerlink\" title=\"数据库分片前的准备\"></a>数据库分片前的准备</h2><h3 id=\"对一个库中相关表进行水平拆分到不同实例的数据库中\"><a href=\"#对一个库中相关表进行水平拆分到不同实例的数据库中\" class=\"headerlink\" title=\"对一个库中相关表进行水平拆分到不同实例的数据库中\"></a>对一个库中相关表进行水平拆分到不同实例的数据库中</h3><h3 id=\"如何选择分区键\"><a href=\"#如何选择分区键\" class=\"headerlink\" title=\"如何选择分区键\"></a>如何选择分区键</h3><ul>\n<li>分区键要能尽量避免跨分片查询的发生</li>\n<li>尽量使各个分片中的数据平均</li>\n</ul>\n<h3 id=\"如何存储无需分片的表\"><a href=\"#如何存储无需分片的表\" class=\"headerlink\" title=\"如何存储无需分片的表\"></a>如何存储无需分片的表</h3><ul>\n<li>冗余到每一个分片中</li>\n<li>使用额外的节点统一存储</li>\n</ul>\n<h3 id=\"如何在节点上部署分片\"><a href=\"#如何在节点上部署分片\" class=\"headerlink\" title=\"如何在节点上部署分片\"></a>如何在节点上部署分片</h3><ul>\n<li>每个分片使用单一数据库,并且数据库名也相同</li>\n<li>将多个分片表存储在一个数据库中,并且在表名上加入分片号后缀</li>\n<li>在一个节点中部署多个数据库,每个数据库中包含一个分片</li>\n</ul>\n<h3 id=\"如何分配分片中的数据\"><a href=\"#如何分配分片中的数据\" class=\"headerlink\" title=\"如何分配分片中的数据\"></a>如何分配分片中的数据</h3><ul>\n<li>按分区键Hash值取模分配分片数据</li>\n<li>按分区键的范围来分配分片数据</li>\n<li>利用分区键和分片的映射表来分配分片数据</li>\n</ul>\n<h3 id=\"如何生成全局唯一Id\"><a href=\"#如何生成全局唯一Id\" class=\"headerlink\" title=\"如何生成全局唯一Id\"></a>如何生成全局唯一Id</h3><ul>\n<li><p>使用auto_increment_increment和auto_increment_offset参数</p>\n</li>\n<li><p>配置全局节点生成Id</p>\n<ul>\n<li>容易成为系统瓶颈</li>\n</ul>\n</li>\n<li><p>在Redis等缓存服务器中创建全局ID</p>\n</li>\n</ul>\n<h2 id=\"附件:Xmind图\"><a href=\"#附件:Xmind图\" class=\"headerlink\" title=\"附件:Xmind图\"></a>附件:Xmind图</h2><p><img src=\"/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E8%AE%BE%E8%AE%A1/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E8%AE%BE%E8%AE%A1.png\" alt=\"数据库分库分表设计\"></p>\n","categories":["MySQL"],"tags":["MySQL","分表","分库"]},{"title":"记一次线上ES Down机事故","url":"/2018/12/20/%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%BA%BF%E4%B8%8AES%20Down%E6%9C%BA%E4%BA%8B%E6%95%85/","content":"<p><strong>背景</strong></p>\n<ul>\n<li>前两天的某个上午,正在开会ing。。突然收到了报警Es服务不可用,同时几个其他业务部门的人也都过来反馈说ES挂了。</li>\n<li>二话不说先启动ES恢复业务再说。然后就开始分析日志找问题了。</li>\n</ul>\n<p><strong>排查过程</strong></p>\n<ul>\n<li>先是怀疑系统资源被用满了,看了一下zabbix,系统负载不太高,8C 16G机器load维持在5-10左右波动,应该不是这个问题。</li>\n<li>看了一下网络读取带宽,也没有达到什么高峰(时间是上午10点半左右),感觉系统方面应该不至于出问题。</li>\n<li>又怀疑是出现OOM内存不足,可是也没发现dump文件,然后就开始找ES日志看问题了</li>\n<li>看ES日志在down机之前有一个java.lang.StackOverflowError,应该就是这个原因了,之前还真没碰到过这个问题,将错误信息在google上面一查,有一些说是使用前缀或者正则查询导致的,感觉应该是这个问题,便开始抓取down机前1分钟的日志。</li>\n<li>运气不坏,很快就找到了一个高度怀疑的参数,是在搜索建议词中出现的,搜索建议词使用的es原生suggest + prefix,传过来的关键词是一个json数组去除了双引号和冒号(应该是api做的过滤),但是里面还有1600+字符,同时包含了“{}[]”符号,在测试机上面进行一下测试,把这个Query放进去,ES果然直接Down掉了,问题排查就算是完成了。</li>\n</ul>\n<p><strong>修复过程</strong></p>\n<ul>\n<li>紧急发布了Hotfix(就是加入参数长度限制,特殊字符限制)</li>\n<li>后续准备考虑使用Ngram来解决这个问题</li>\n<li>吸取教训:<ul>\n<li>能不用就尽可能不用通配符查询,无论是前缀还是模糊</li>\n<li>大不了空间换时间,暴力使用ngram解决问题</li>\n<li>参数一定要加入校验机制</li>\n</ul>\n</li>\n</ul>\n<p><strong>原因分析(转)</strong></p>\n<ul>\n<li><p>问题出现时,ES服务端日志有如下报错:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">java.lang.StackOverflowError: null</span><br><span class=\"line\"> at org.apache.lucene.util.automaton.Operations.isFinite(Operations.java:1053) ~[lucene-core-6.2.1.jar:6.2.1 43ab70147eb494324a1410f7a9f16a896a59bc6f - shalin - 2016-09-15 05:15:20]</span><br><span class=\"line\"> at org.apache.lucene.util.automaton.Operations.isFinite(Operations.java:1053) ~[lucene-core-6.2.1.jar:6.2.1 43ab70147eb494324a1410f7a9f16a896a59bc6f - shalin - 2016-09-15 05:15:20]</span><br><span class=\"line\"> at org.apache.lucene.util.automaton.Operations.isFinite(Operations.java:1053) ~[lucene-core-6.2.1.jar:6.2.1 43ab70147eb494324a1410f7a9f16a896a59bc6f - shalin - 2016-09-15 05:15:20]</span><br><span class=\"line\"> at org.apache.lucene.util.automaton.Operations.isFinite(Operations.java:1053) ~[lucene-core-6.2.1.jar:6.2.1 43ab70147eb494324a1410f7a9f16a896a59bc6f - shalin - 2016-09-15 05:15:20]</span><br></pre></td></tr></table></figure>\n</li>\n<li><p>Prefix/Regex/Fuzzy一类的Query,是直接构造的deterministic automaton,如果查询字符串过长,或者pattern本身过于复杂,构造出来的状态过多,之后一个isFinite的Lucene方法调用可能产生堆栈溢出。</p>\n</li>\n<li><p>PrefixQuery继承自Lucene的AutomatonQuery,在实例化的时候,maxDeterminizedStates传的是Integer.MAX_VALUE, 并且生成automaton之前,prefix的长度也没有做限制。</p>\n</li>\n<li><p>附参考链接:</p>\n<ul>\n<li><a href=\"https://elasticsearch.cn/article/186\">https://elasticsearch.cn/article/186</a></li>\n<li><a href=\"https://elasticsearch.cn/article/171\">https://elasticsearch.cn/article/171</a></li>\n<li><a href=\"https://github.com/elastic/elasticsearch/issues/24553\">https://github.com/elastic/elasticsearch/issues/24553</a></li>\n</ul>\n</li>\n</ul>\n","categories":["ElasticSearch"],"tags":["ElasticSearch","通配符","前缀查询"]},{"title":"解决HttpServletRequest InputStream只能读取一次问题","url":"/2018/06/15/%E8%A7%A3%E5%86%B3HttpServletRequest%20InputStream%E5%8F%AA%E8%83%BD%E8%AF%BB%E5%8F%96%E4%B8%80%E6%AC%A1%E9%97%AE%E9%A2%98/","content":"<p>在Filter中读取inputSeream读取一次之后就无法再次读取,解决办法如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public class LoggerHttpServletRequestWrapper extends HttpServletRequestWrapper {</span><br><span class=\"line\"> </span><br><span class=\"line\"> private final byte[] body;</span><br><span class=\"line\"> </span><br><span class=\"line\"> public LoggerHttpServletRequestWrapper(HttpServletRequest request) throws IOException {</span><br><span class=\"line\"> super(request);</span><br><span class=\"line\"> body = StreamUtils.readBytes(request.getInputStream());</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public BufferedReader getReader() {</span><br><span class=\"line\"> return new BufferedReader(new InputStreamReader(getInputStream()));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public ServletInputStream getInputStream() {</span><br><span class=\"line\"> final ByteArrayInputStream bais = new ByteArrayInputStream(body);</span><br><span class=\"line\"> return new ServletInputStream() {</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public boolean isFinished() {</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public boolean isReady() {</span><br><span class=\"line\"> return false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public void setReadListener(ReadListener readListener) {</span><br><span class=\"line\"> </span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\"> @Override</span><br><span class=\"line\"> public int read() {</span><br><span class=\"line\"> return bais.read();</span><br><span class=\"line\"> }</span><br><span class=\"line\"> };</span><br><span class=\"line\"> }</span><br><span class=\"line\"> </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>调用如下</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">@Override</span><br><span class=\"line\">public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {</span><br><span class=\"line\"> ServletRequest requestWrapper = null;</span><br><span class=\"line\"> if(request instanceof HttpServletRequest) {</span><br><span class=\"line\"> requestWrapper = new LoggerHttpServletRequestWrapper((HttpServletRequest) request);</span><br><span class=\"line\"> if (((HttpServletRequest) request).getMethod().equals("POST")){</span><br><span class=\"line\"> String path = ((HttpServletRequest) request).getServletPath();</span><br><span class=\"line\"> String param = StreamUtils.streamToString(requestWrapper.getInputStream());</span><br><span class=\"line\"> LoggerFactory.getLogger("filter."+path).info(param);</span><br><span class=\"line\"> }else if (((HttpServletRequest) request).getMethod().equals("GET")){</span><br><span class=\"line\"> String path = ((HttpServletRequest) request).getServletPath();</span><br><span class=\"line\"> String queryString = ((HttpServletRequest) request).getQueryString();</span><br><span class=\"line\"> LoggerFactory.getLogger("filter."+path).info(queryString);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"> if(requestWrapper == null) {</span><br><span class=\"line\"> chain.doFilter(request, response);</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> chain.doFilter(requestWrapper, response);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>工具类如下</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">public class StreamUtils {</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * @param inputStream inputStream</span><br><span class=\"line\"> * @return 字符串转换之后的</span><br><span class=\"line\"> */</span><br><span class=\"line\"> public static String streamToString(InputStream inputStream) {</span><br><span class=\"line\"> try(BufferedReader br =new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {</span><br><span class=\"line\"> StringBuilder builder = new StringBuilder();</span><br><span class=\"line\"> String output;</span><br><span class=\"line\"> while((output = br.readLine())!=null){</span><br><span class=\"line\"> builder.append(output);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return builder.toString();</span><br><span class=\"line\"> } catch (IOException e) {</span><br><span class=\"line\"> throw new RuntimeException("Http 服务调用失败",e);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> </span><br><span class=\"line\"></span><br><span class=\"line\"> public static byte[] readBytes(ServletInputStream inputStream) {</span><br><span class=\"line\"> return streamToString(inputStream).getBytes(Charset.forName("UTF-8"));</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>","categories":["Java"],"tags":["Java","Filter","HttpServletRequest"]},{"title":"Kata rootfs 适配昇腾GPU驱动(Arm平台)","url":"/2022/12/30/Kata%20rootf%E9%80%82%E9%85%8D%E6%98%87%E8%85%BEGPU%E9%A9%B1%E5%8A%A8/","content":"<h2 id=\"Kata-rootfs-适配昇腾GPU驱动(Arm平台)\"><a href=\"#Kata-rootfs-适配昇腾GPU驱动(Arm平台)\" class=\"headerlink\" title=\"Kata rootfs 适配昇腾GPU驱动(Arm平台)\"></a>Kata rootfs 适配昇腾GPU驱动(Arm平台)</h2><h1 id=\"虚拟机\"><a href=\"#虚拟机\" class=\"headerlink\" title=\"虚拟机\"></a>虚拟机</h1><h2 id=\"选择\"><a href=\"#选择\" class=\"headerlink\" title=\"选择\"></a>选择</h2><ul>\n<li>系统:驱动适配比较好的系统是CentOS 7.6和8.2,本次制作使用CentOS 8.2.2004 arm 镜像</li>\n<li>虚拟机:驱动依赖硬件设备,所以选择在宿主机通过qemu启动虚拟机,在虚拟机内部适配驱动</li>\n<li>其他:开启kvm加速,实测arm架构下开启kvm,需要宿主机是L0(不能是虚拟机),且bios使用edk2</li>\n<li>宿主机:宿主机系统为银河麒麟v10 arm版</li>\n</ul>\n<h2 id=\"准备工作\"><a href=\"#准备工作\" class=\"headerlink\" title=\"准备工作\"></a>准备工作</h2><ul>\n<li>下载CentOS 8.2.2004 arm 镜像<ul>\n<li>下载地址:<a href=\"https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.2.2004/isos/aarch64/CentOS-8.2.2004-aarch64-dvd1.iso\">https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.2.2004/isos/aarch64/CentOS-8.2.2004-aarch64-dvd1.iso</a></li>\n</ul>\n</li>\n<li>下载bios引导文件,edk2-aarch64的安装包,执行安装<ul>\n<li>下载地址:<a href=\"https://www.kraxel.org/repos/jenkins/edk2/\">https://www.kraxel.org/repos/jenkins/edk2/</a></li>\n<li>安装后bios文件位于 /usr/share/edk2.git/aarch64/QEMU_EFI.fd</li>\n</ul>\n</li>\n<li>安装最新的qemu(建议编译安装,可以选择自己依赖的组件,直接从yum源安装的可能缺少组件)<ul>\n<li>补充说明:qemu启动虚拟机后,可以通过-net user直接共享宿主机的网络,并可以开启ssh通道,该功能依赖slirp,编译配置的时候需要启用slirp</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"制作虚拟机(宿主机)\"><a href=\"#制作虚拟机(宿主机)\" class=\"headerlink\" title=\"制作虚拟机(宿主机)\"></a>制作虚拟机(宿主机)</h2><ul>\n<li>制作qcow2文件(相当于是硬盘, 文件名hd.qcow2 , 大小100G)<ul>\n<li>qemu-img create -f qcow2 hd.qcow2 100G</li>\n</ul>\n</li>\n<li>安装操作系统<ul>\n<li>qemu启动命令:qemu-system-aarch64 -m 24G -cpu host -smp 16 -M virt,accel=kvm -bios QEMU_EFI.fd -nographic -drive if=none,file=hd.qcow2,id=hd0 -device virtio-blk-device,drive=hd0 -drive if=none,file=CentOS-8.2.2004-aarch64-dvd1.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom -net user,hostfwd=tcp::2222-:22 -net nic -device vfio-pci,host=0000:83:00.0</li>\n<li>qemu参数说明:<ul>\n<li>-m 24G 指定内存大小</li>\n<li>-cpu host 指定CPU类型和宿主机一致,注意host类型需要开启kvm加速</li>\n<li>-M virt,accel=kvm machineType类型为virt(arm均使用virt),加速器使用kvm</li>\n<li>-smp 16 cpu 16核</li>\n<li>-bios QEMU_EFI.fd 指定edk2的efi文件</li>\n<li>-nographic 因为不是在本地,所以直接通过控制台以基础文本的形式显示</li>\n<li>-drive if=none,file=hd.qcow2,id=hd0 -device virtio-blk-device,drive=hd0 指定硬盘设备及驱动</li>\n<li>-drive if=none,file=CentOS-8.2.2004-aarch64-dvd1.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom 指定操作系统安装文件,以cdrom方式加载</li>\n<li>-net user,hostfwd=tcp::2222-:22 -net nic 开启与宿主机的共享网络,同时将虚拟机的22端口映射到宿主机的2222端口</li>\n<li>-device vfio-pci,host=0000:83:00.0 将宿主机的设备vfio直通到虚拟机</li>\n</ul>\n</li>\n<li>如果在宿主机无法正常安装,可以考虑本地启动qemu安装,然后将qcow2文件拷贝至服务器</li>\n<li>安装完成后重启系统即可,以后启动qemu cdrom参数不再需要。</li>\n</ul>\n</li>\n<li>注意事项:<ul>\n<li>虚拟机启动后,需要开启sshd,否则无法ssh连接到虚拟机</li>\n<li>QEMU_EFI.fd一定用edk2的,不要使用<code>https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd</code> 这个版本,这个无法开启kvm加速</li>\n<li>全虚拟化的虚拟机慢的要死,开机就要15分钟,一定要开启kvm加速,后面驱动适配在全虚拟化下也会失败(全虚拟化运行太慢,导致驱动执行失败)</li>\n<li>vfio宿主机一定要开启,否则虚拟机无法获取设备</li>\n<li>设备号可以通过lspci看到</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"内核制作\"><a href=\"#内核制作\" class=\"headerlink\" title=\"内核制作\"></a>内核制作</h1><h2 id=\"修改内核magversion\"><a href=\"#修改内核magversion\" class=\"headerlink\" title=\"修改内核magversion\"></a>修改内核magversion</h2><ul>\n<li>进入内核源码目录,修改 include/linux/vermagic.h<ul>\n<li>修改<code>#define MODULE_VERMAGIC_MODULE_UNLOAD ""</code></li>\n<li>为:<code>#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload"</code></li>\n<li>修改<code>#define MODULE_VERMAGIC_MODVERSIONS ""</code></li>\n<li>为:<code>#define MODULE_VERMAGIC_MODVERSIONS "modversions"</code></li>\n</ul>\n</li>\n</ul>\n<h2 id=\"编译内核获取bzImage(宿主机或虚拟机都可)\"><a href=\"#编译内核获取bzImage(宿主机或虚拟机都可)\" class=\"headerlink\" title=\"编译内核获取bzImage(宿主机或虚拟机都可)\"></a>编译内核获取bzImage(宿主机或虚拟机都可)</h2><ul>\n<li>直接执行make就行,注意配置和之前保持一致</li>\n<li>编译完成后,将arch/aarch64/boot/bzImage拷贝出来,作为kata启动的kernel</li>\n</ul>\n<h2 id=\"制作内核rpm安装包(宿主机或虚拟机都可)\"><a href=\"#制作内核rpm安装包(宿主机或虚拟机都可)\" class=\"headerlink\" title=\"制作内核rpm安装包(宿主机或虚拟机都可)\"></a>制作内核rpm安装包(宿主机或虚拟机都可)</h2><ul>\n<li>保持配置不变,执行make rpm-pkg</li>\n<li>然后将kernel,kernel-devel,kernel-headers三个rpm拷贝出来</li>\n</ul>\n<h2 id=\"虚拟机安装新内核(虚拟机)\"><a href=\"#虚拟机安装新内核(虚拟机)\" class=\"headerlink\" title=\"虚拟机安装新内核(虚拟机)\"></a>虚拟机安装新内核(虚拟机)</h2><ul>\n<li>将编译的rpm安装包拷贝至虚拟机(开启ssh后scp -P 2222 xxx xxx@localhost:~即可)</li>\n<li>进入虚拟机后,执行rpm -ivh kernel-xxxxx.rpm 安装内核,然后重启虚拟机,用编译版本的内核启动虚拟机,准备后续rootfs制作</li>\n</ul>\n<h1 id=\"rootfs制作\"><a href=\"#rootfs制作\" class=\"headerlink\" title=\"rootfs制作\"></a>rootfs制作</h1><h2 id=\"制作rootfs(宿主机或虚拟机都可)\"><a href=\"#制作rootfs(宿主机或虚拟机都可)\" class=\"headerlink\" title=\"制作rootfs(宿主机或虚拟机都可)\"></a>制作rootfs(宿主机或虚拟机都可)</h2><ul>\n<li>进入kata源码kata-containers/tools/osbuilder/目录下,执行make镜像命令</li>\n<li><code>make DISTRO=centos OS_VERSION=8.2.2004 SECCOMP=no DEBUG=true USE_DOCKER=true AGENT_INIT=yes EXTRA_PKGS='net-tools pciutils udev e2fsprogs tar gcc' rootfs</code></li>\n<li>net-tools pciutils udev e2fsprogs tar gcc 这几样工具是驱动安装过程中需要的,需要提前打包至rootfs</li>\n</ul>\n<h2 id=\"拷贝rootfs至虚拟机\"><a href=\"#拷贝rootfs至虚拟机\" class=\"headerlink\" title=\"拷贝rootfs至虚拟机\"></a>拷贝rootfs至虚拟机</h2><ul>\n<li>上一步操作完成后,当前目录下会出来centos_rootfs文件夹,将该目录打包,拷贝至虚拟机</li>\n<li>tar czvf centos_rootfs.tgz centos_rootfs</li>\n<li>scp -P 2222 centos_rootfs.tgz root@localhost:~</li>\n</ul>\n<h1 id=\"驱动适配\"><a href=\"#驱动适配\" class=\"headerlink\" title=\"驱动适配\"></a>驱动适配</h1><h2 id=\"补充缺失文件-虚拟机,其实这一步再打包之前操作也可以\"><a href=\"#补充缺失文件-虚拟机,其实这一步再打包之前操作也可以\" class=\"headerlink\" title=\"补充缺失文件(虚拟机,其实这一步再打包之前操作也可以)\"></a>补充缺失文件(虚拟机,其实这一步再打包之前操作也可以)</h2><ul>\n<li>解压rootfs</li>\n<li>/usr/share/zoneinfo文件夹拷贝至 rootfs对应的目录下(centos默认rootfs缺失时区信息,无法安装驱动)</li>\n<li>拷贝驱动文件,kernel-devel,kernel-headers到rootfs的某目录下(我拷贝到home目录下),用于后续chroot安装</li>\n<li>创建hook脚本<ul>\n<li>mkdir -p /usr/share/oci/hooks/prestart</li>\n<li>cd /usr/share/oci/hooks/prestart</li>\n<li>创建mount_tmpfs.sh脚本,解决/tmp,/var/log 只读问题,内容如下:</li>\n</ul>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">#!/bin/sh</span><br><span class=\"line\">mount -t tmpfs -o size=8192m tmpfs /tmp</span><br><span class=\"line\">mount -t tmpfs -o size=8192m tmpfs /var/log\t</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"挂载目录-虚拟机\"><a href=\"#挂载目录-虚拟机\" class=\"headerlink\" title=\"挂载目录(虚拟机)\"></a>挂载目录(虚拟机)</h2><ul>\n<li><code>export ROOTFS_DIR=/root/centos_rootfs</code></li>\n<li><code>mount -t sysfs -o ro none ${ROOTFS_DIR}/sys</code></li>\n<li><code>mount -t proc -o ro none ${ROOTFS_DIR}/proc</code></li>\n<li><code>mount -o bind,ro /dev ${ROOTFS_DIR}/dev</code></li>\n<li><code>mount -t devpts none ${ROOTFS_DIR}/dev/pts</code></li>\n<li><code>mount -t tmpfs none ${ROOTFS_DIR}/tmp</code></li>\n</ul>\n<h2 id=\"chroot并安装驱动-虚拟机\"><a href=\"#chroot并安装驱动-虚拟机\" class=\"headerlink\" title=\"chroot并安装驱动(虚拟机)\"></a>chroot并安装驱动(虚拟机)</h2><ul>\n<li>chroot $ROOTFS_DIR</li>\n<li>进入home目录(驱动和kernel目录)</li>\n<li>安装kernel-devel和kernel-headers (rpm -ivh kernel*.rpm)</li>\n<li>创建华为用户组HwHiAiUser 和 用户<ul>\n<li><code>groupadd HwHiAiUser</code></li>\n<li><code>useradd -g HwHiAiUser -d /home/HwHiAiUser -m HwHiAiUser -s /bin/bash</code></li>\n</ul>\n</li>\n<li>安装驱动 ./A300-3000-npu-driver_21.0.3.3_linux-aarch64.run –full </li>\n<li>驱动安装成功后,执行 /usr/local/sbin/npu-smi info 如果能正常回显,则继续安装驱动,不能正常回显,则重启虚拟机,先exit退出chroot</li>\n<li>解除挂载 <code>umount ${ROOTFS_DIR}/sys ${ROOTFS_DIR}/proc ${ROOTFS_DIR}/dev/pts ${ROOTFS_DIR}/tmp ${ROOTFS_DIR}/dev</code></li>\n<li>然后reboot重启虚拟机,重启后重新挂载目录,然后执行 /usr/local/sbin/npu-smi info 看是否能正常回显</li>\n<li>正常回显后,安装固件 ./A300-3000-npu-firmware_1.81.22.2.220.run –full</li>\n<li>然后删除内核源码(/usr/src/kernels下,内核版本的源码删除),删除kernel-devel,kernel-headers,驱动等文件(减少rootfs镜像大小)</li>\n<li>exit退出chroot</li>\n<li>解除挂载<code> umount ${ROOTFS_DIR}/sys ${ROOTFS_DIR}/proc ${ROOTFS_DIR}/dev/pts ${ROOTFS_DIR}/tmp ${ROOTFS_DIR}/dev</code></li>\n<li>将rootfs打包(如果rootfs在虚拟机中制作,则忽略该步骤),并拷贝到宿主机上</li>\n</ul>\n<h2 id=\"制作镜像\"><a href=\"#制作镜像\" class=\"headerlink\" title=\"制作镜像\"></a>制作镜像</h2><ul>\n<li>解压虚拟机中的rootfs文件</li>\n<li>切换到kata的image-builder目录(kata-containers/tools/osbuilder/image-builder)</li>\n<li>制作镜像./image_builder.sh rootfsDir</li>\n<li>得到kata-containers.img</li>\n</ul>\n<h1 id=\"其他适配\"><a href=\"#其他适配\" class=\"headerlink\" title=\"其他适配\"></a>其他适配</h1><h2 id=\"配置文件\"><a href=\"#配置文件\" class=\"headerlink\" title=\"配置文件\"></a>配置文件</h2><ul>\n<li>kata的config需要进行以下几个修改<ol>\n<li><code>guest_hook_path = "/usr/share/oci/hooks"</code></li>\n<li><code>kernel_modules=["drv_devdrv_host","drv_davinci_intf_host","drv_tsdrv_platform_host","drv_pcie_vnic_host","drv_pcie_hdc_host","drv_devmm_host"]</code></li>\n<li><code>hotplug_vfio_on_root_bus = true</code></li>\n<li><code>pcie_root_port = 2</code></li>\n</ol>\n</li>\n<li>说明:<ol>\n<li><code>guest_hook_path</code> 执行hook,路径不需要更改,驱动需要挂载tmpfs,通过hook执行</li>\n<li><code>kernel_modules</code> 驱动的modules默认不会加载,需要手动加载,配置到这里,vm启动会自动加载</li>\n<li><code>hotplug_vfio_on_root_bus</code> 开启root bus,arm平台需要通过pcie_root_port进行设备热插拔</li>\n<li><code>pcie_root_port</code> 单个虚拟机最多可以挂载GPU设备数</li>\n</ol>\n</li>\n</ul>\n<h2 id=\"Kata源码\"><a href=\"#Kata源码\" class=\"headerlink\" title=\"Kata源码\"></a>Kata源码</h2><ul>\n<li>主要修改如下:<ul>\n<li>virt模式支持pcieRootPort设备,通过配置进行加载</li>\n<li>qmp添加设备指定bus为pcieRootPort设备,arm下只有pcieRootPort支持热插拔</li>\n</ul>\n</li>\n</ul>\n<h1 id=\"Q-amp-A\"><a href=\"#Q-amp-A\" class=\"headerlink\" title=\"Q&A\"></a>Q&A</h1><ol>\n<li><p>启动后,kata-runtime exec 进入vm后,/dev/loop设备只有1个</p>\n<ul>\n<li>修改内核配置<ul>\n<li><code>CONFIG_BLK_DEV_LOOP=y</code></li>\n<li><code>CONFIG_BLK_DEV_LOOP_MIN_COUNT=4</code></li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>qemu 使用 -cpu host -M virt,accel=kvm 启动虚拟机黑屏</p>\n<ul>\n<li>bios的efi启动文件不正确,使用edk2的efi文件即可</li>\n</ul>\n</li>\n<li><p>安装驱动npu-smi info执行后提示 <code>dcmi module initialize failed. ret is -8005</code></p>\n<ul>\n<li>提示-8005的原因有很多,下面列出来几个适配中遇到过的<ul>\n<li>显卡设备被其他驱动占用,去/sys/bus/pci/drivers/devdrv_device_driver目录下重新bind设备即可。</li>\n<li>内核模块加载不全,lsmod对比kernel_modules中的配置,是否已经加载全。</li>\n<li>同时提示<code>sh: /tmp/pci_get_info_234.tmp: Read-only file system</code>检查Hook脚本是否执行,可以df看下挂载情况。</li>\n<li>其他原因,可以通过dmesg查看kernel日志,具体排查</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p>chroot后,安装驱动,提示缺少xxxx组件</p>\n<ul>\n<li>EXTRA_PKGS后面追加缺少的命令的安装包即可,无依赖的,可直接拷贝到rootfs对应目录下</li>\n</ul>\n</li>\n<li><p>kernel-devel和kernel-headers是否可以不安装?</p>\n<ul>\n<li>需要安装,驱动安装过程中,有部分模块依赖内核,需要通过内核源码进行重编译,安装完成后,删除对应src目录即可。</li>\n<li>通过dkms安装会快点,但是依赖过多,不建议</li>\n</ul>\n</li>\n<li><p>内核日志提示:<code>drv_seclib_host: version magic '5.4.160-1.el7.aarch64 SMP mod_unload modversions aarch64' should be '5.4.160-1.el7.aarch64 SMP aarch64'</code></p>\n<ul>\n<li>内核的version magic和编译的驱动的version magic不一致,参考内核部分修改version magic,重新编译即可</li>\n</ul>\n</li>\n<li><p>qemu启动时加上-net user提示<code> -net user: Parameter 'type' expects a net backend type (maybe it is not compiled into this binary)</code></p>\n<ul>\n<li>编译qemu缺少slirp,重新config qemu,加上slirp后重新make qemu即可</li>\n</ul>\n</li>\n</ol>\n","categories":["Linux"],"tags":["Linux","Kata","Arm"]},{"title":"Arm平台 Kata开启Nvdimm(memory hotplug)","url":"/2023/01/28/Arm%E5%B9%B3%E5%8F%B0%20Kata%E5%BC%80%E5%90%AFNvdimm(memory%20hotplug)/","content":"<p>内存热插拔是容器在部署中动态分配内存的关键特性,在 x86 上,可以使用直接启用 ACPI 的 QEMU 启动 VM,因为它会隐式启动固件。但是对于 arm64,则需要明确指定固件。也就是说,如果准备在 arm64 上运行一个普通的 Kata Container,你需要额外做的是在使用内存热插拔功能之前安装 UEFI ROM。</p>\n<h1 id=\"安装-UEFI-ROM\"><a href=\"#安装-UEFI-ROM\" class=\"headerlink\" title=\"安装 UEFI ROM\"></a>安装 UEFI ROM</h1><h2 id=\"修改脚本适配系统\"><a href=\"#修改脚本适配系统\" class=\"headerlink\" title=\"修改脚本适配系统\"></a>修改脚本适配系统</h2><p>需要执行的脚本 </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">$GOPATH/src/github.com/kata-containers/tests/.ci/aarch64/install_rom_aarch64.sh</span><br></pre></td></tr></table></figure>\n<p>该脚本默认适配的系统是Ubuntu,使用apt源安装相关依赖,适配麒麟系统需要修改为dnf源进行安装,将脚本安装依赖部分修改为 </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">dnf install -y python python3 python3-distutils-extra uuid-devel bison flex</span><br></pre></td></tr></table></figure>\n<p>其他edk2相关git项,根据网络情况进行适配</p>\n<h2 id=\"执行脚本安装\"><a href=\"#执行脚本安装\" class=\"headerlink\" title=\"执行脚本安装\"></a>执行脚本安装</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">sudo .ci/aarch64/install_rom_aarch64.sh</span><br></pre></td></tr></table></figure>\n<p>脚本执行安装完成后,默认kata的img路径下(/usr/share/kata-containers)生成2个文件kata-flash0.img和 kata-flash1.img</p>\n<h1 id=\"内核适配\"><a href=\"#内核适配\" class=\"headerlink\" title=\"内核适配\"></a>内核适配</h1><p>内核默认不支持nvdimm,需要打社区提供的patch才能开启</p>\n<p>目前适配的kernel版本是5.4.160,社区提供了5.4.x版本kernel相应的patch,路径如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">$GOPATH/src/github.com/kata-containers/kata-containers/tools/packaging/kernel/patches/5.4.x/0006-arm64-mm-Enable-memory-hot-remove.patch</span><br></pre></td></tr></table></figure>\n\n<p>将该文件拷贝至kernel源码下,执行patch</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cd {kernel_path}</span><br><span class=\"line\">cp $GOPATH/src/github.com/kata-containers/kata-containers/tools/packaging/kernel/patches/5.4.x/0006-arm64-mm-Enable-memory-hot-remove.patch ..</span><br><span class=\"line\">patch -p1 <../0006-arm64-mm-Enable-memory-hot-remove.patch</span><br></pre></td></tr></table></figure>\n\n<p>编译内核注意nvdimm相关的config要开启</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">CONFIG_ACPI_NFIT=y</span><br><span class=\"line\">CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y</span><br><span class=\"line\">CONFIG_MEMORY_HOTREMOVE=y</span><br><span class=\"line\">CONFIG_ZONE_DEVICE=y</span><br><span class=\"line\">CONFIG_NVDIMM_PFN=y</span><br><span class=\"line\">CONFIG_NVDIMM_DAX=y</span><br></pre></td></tr></table></figure>\n<p>重新编译后,替换编译后的内核文件</p>\n<h1 id=\"配置文件修改\"><a href=\"#配置文件修改\" class=\"headerlink\" title=\"配置文件修改\"></a>配置文件修改</h1><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># If false and nvdimm is supported, use nvdimm device to plug guest image.</span><br><span class=\"line\"># Otherwise virtio-block device is used.</span><br><span class=\"line\"># Default is false</span><br><span class=\"line\">#disable_image_nvdimm = true //这里不要打开</span><br><span class=\"line\"> </span><br><span class=\"line\"># -pflash can add image file to VM. The arguments of it should be in format</span><br><span class=\"line\"># of ["/path/to/flash0.img", "/path/to/flash1.img"]</span><br><span class=\"line\">pflashes = ["/usr/share/kata-containers/kata-flash0.img","/usr/share/kata-containers/kata-flash1.img"] //这里指定uefi的rom</span><br></pre></td></tr></table></figure>\n\n\n<h1 id=\"注意事项\"><a href=\"#注意事项\" class=\"headerlink\" title=\"注意事项\"></a>注意事项</h1><p>如果基于之前的kernel编译了驱动之类的内容,kernel打了patch需要重新编译驱动,否则出现mod无法加载的情况</p>\n","categories":["nvdimm, Kata, Arm64,"],"tags":["vdimm, Kata, Arm64,memory hotplug"]},{"title":"C语言实现RPC调用(Demo)","url":"/2023/02/19/C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0RPC%E8%B0%83%E7%94%A8(Demo)/","content":"<p>简单调研一圈,目前c语言可用的rpc框架有,Thrift,protobuf-c-rpc,rpcgen,这里只是简单记录一下调用demo,C语言用的还是不太熟悉,凑合看了。</p>\n<h1 id=\"Thrift\"><a href=\"#Thrift\" class=\"headerlink\" title=\"Thrift\"></a>Thrift</h1><p>这里说一下Thrift的特点</p>\n<ul>\n<li>同步调用,相对比较容易处理</li>\n<li>需要修改request_service.c,也就是服务实现具体的方法,需要修改request_service.c文件(暂时没找到其他办法)</li>\n<li>依赖glibc</li>\n</ul>\n<h2 id=\"编译安装Thrift\"><a href=\"#编译安装Thrift\" class=\"headerlink\" title=\"编译安装Thrift\"></a>编译安装Thrift</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># git clone https://github.com/apache/thrift.git</span><br><span class=\"line\"># cd thrift</span><br><span class=\"line\"># ./bootstrap.sh</span><br><span class=\"line\"># ./configure</span><br><span class=\"line\"># make</span><br><span class=\"line\"># make install</span><br></pre></td></tr></table></figure>\n<h2 id=\"依赖\"><a href=\"#依赖\" class=\"headerlink\" title=\"依赖\"></a>依赖</h2><ul>\n<li>glibc-2.0</li>\n</ul>\n<h2 id=\"编写协议\"><a href=\"#编写协议\" class=\"headerlink\" title=\"编写协议\"></a>编写协议</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">namespace cl shared</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">struct Param {</span><br><span class=\"line\"> 1: string key</span><br><span class=\"line\"> 2: i32 value</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">struct Result {</span><br><span class=\"line\"> 1: bool result</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">service RequestService {</span><br><span class=\"line\"> Result sendMessage(1: Param param)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h2 id=\"Server端\"><a href=\"#Server端\" class=\"headerlink\" title=\"Server端\"></a>Server端</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">#include <glib-object.h></span><br><span class=\"line\">#include <signal.h></span><br><span class=\"line\">#include <stdio.h></span><br><span class=\"line\">#include <string.h></span><br><span class=\"line\"></span><br><span class=\"line\">#include <thrift/c_glib/thrift.h></span><br><span class=\"line\">#include <thrift/c_glib/protocol/thrift_binary_protocol_factory.h></span><br><span class=\"line\">#include <thrift/c_glib/protocol/thrift_protocol_factory.h></span><br><span class=\"line\">#include <thrift/c_glib/server/thrift_server.h></span><br><span class=\"line\">#include <thrift/c_glib/server/thrift_simple_server.h></span><br><span class=\"line\">#include <thrift/c_glib/transport/thrift_buffered_transport_factory.h></span><br><span class=\"line\">#include <thrift/c_glib/transport/thrift_server_socket.h></span><br><span class=\"line\">#include <thrift/c_glib/transport/thrift_server_transport.h></span><br><span class=\"line\"></span><br><span class=\"line\">#include "gen-c_glib/request_service.h"</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">ThriftServer *server = NULL;</span><br><span class=\"line\">gboolean sigint_received = FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\">/* Handle SIGINT ("Ctrl-C") signals by gracefully stopping the</span><br><span class=\"line\"> server */</span><br><span class=\"line\">static void</span><br><span class=\"line\">sigint_handler(int signal_number) {</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (signal_number);</span><br><span class=\"line\"></span><br><span class=\"line\"> /* Take note we were called */</span><br><span class=\"line\"> sigint_received = TRUE;</span><br><span class=\"line\"></span><br><span class=\"line\"> /* Shut down the server gracefully */</span><br><span class=\"line\"> if (server != NULL)</span><br><span class=\"line\"> thrift_server_stop(server);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">int main(void) {</span><br><span class=\"line\"> RequestServiceHandler *handler;</span><br><span class=\"line\"> RequestServiceProcessor *processor;</span><br><span class=\"line\"> ThriftServerTransport *server_transport;</span><br><span class=\"line\"> ThriftTransportFactory *transport_factory;</span><br><span class=\"line\"> ThriftProtocolFactory *protocol_factory;</span><br><span class=\"line\"></span><br><span class=\"line\"> struct sigaction sigint_action;</span><br><span class=\"line\"></span><br><span class=\"line\"> GError *error = NULL;</span><br><span class=\"line\"> int exit_status = 0;</span><br><span class=\"line\"></span><br><span class=\"line\"> handler = g_object_new(TYPE_REQUEST_SERVICE_HANDLER, NULL);</span><br><span class=\"line\"> processor = g_object_new(TYPE_REQUEST_SERVICE_PROCESSOR, "handler", handler, NULL);</span><br><span class=\"line\"> server_transport = g_object_new(THRIFT_TYPE_SERVER_SOCKET, "port", 9090, NULL);</span><br><span class=\"line\"> transport_factory = g_object_new(THRIFT_TYPE_BUFFERED_TRANSPORT_FACTORY, NULL);</span><br><span class=\"line\"> protocol_factory = g_object_new(THRIFT_TYPE_BINARY_PROTOCOL_FACTORY, NULL);</span><br><span class=\"line\"> server = g_object_new(THRIFT_TYPE_SIMPLE_SERVER,</span><br><span class=\"line\"> "processor", processor,</span><br><span class=\"line\"> "server_transport", server_transport,</span><br><span class=\"line\"> "input_transport_factory", transport_factory,</span><br><span class=\"line\"> "output_transport_factory", transport_factory,</span><br><span class=\"line\"> "input_protocol_factory", protocol_factory,</span><br><span class=\"line\"> "output_protocol_factory", protocol_factory,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> memset(&sigint_action, 0, sizeof(sigint_action));</span><br><span class=\"line\"> sigint_action.sa_handler = sigint_handler;</span><br><span class=\"line\"> sigint_action.sa_flags = SA_RESETHAND;</span><br><span class=\"line\"> sigaction(SIGINT, &sigint_action, NULL);</span><br><span class=\"line\"> printf("Starting the server...\\n");</span><br><span class=\"line\"> thrift_server_serve(server, &error);</span><br><span class=\"line\"> printf("server %p error %p\\n",server,error);</span><br><span class=\"line\"> if (!sigint_received) {</span><br><span class=\"line\"> g_message ("thrift_server_serve: %s",</span><br><span class=\"line\"> error != NULL ? error->message : "(null)");</span><br><span class=\"line\"> g_clear_error(&error);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> puts("done.");</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_unref(server);</span><br><span class=\"line\"> g_object_unref(transport_factory);</span><br><span class=\"line\"> g_object_unref(protocol_factory);</span><br><span class=\"line\"> g_object_unref(server_transport);</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_unref(processor);</span><br><span class=\"line\"> g_object_unref(handler);</span><br><span class=\"line\"></span><br><span class=\"line\"> return exit_status;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h2 id=\"Server业务部分-request-service-c\"><a href=\"#Server业务部分-request-service-c\" class=\"headerlink\" title=\"Server业务部分(request_service.c)\"></a>Server业务部分(request_service.c)</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">/**</span><br><span class=\"line\"> * Autogenerated by Thrift Compiler (0.17.0)</span><br><span class=\"line\"> *</span><br><span class=\"line\"> * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING</span><br><span class=\"line\"> * @generated</span><br><span class=\"line\"> */</span><br><span class=\"line\">#include <string.h></span><br><span class=\"line\">#include <thrift/c_glib/thrift.h></span><br><span class=\"line\">#include <thrift/c_glib/thrift_application_exception.h></span><br><span class=\"line\">#include <stdio.h></span><br><span class=\"line\">#include <stdbool.h></span><br><span class=\"line\">#include "request_service.h"</span><br><span class=\"line\"></span><br><span class=\"line\">gboolean</span><br><span class=\"line\">request_service_if_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {</span><br><span class=\"line\"> return REQUEST_SERVICE_IF_GET_INTERFACE (iface)->send_message(iface, _return, param, error);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">GType</span><br><span class=\"line\">request_service_if_get_type(void) {</span><br><span class=\"line\"> static GType type = 0;</span><br><span class=\"line\"> if (type == 0) {</span><br><span class=\"line\"> static const GTypeInfo type_info =</span><br><span class=\"line\"> {</span><br><span class=\"line\"> sizeof(RequestServiceIfInterface),</span><br><span class=\"line\"> NULL, /* base_init */</span><br><span class=\"line\"> NULL, /* base_finalize */</span><br><span class=\"line\"> NULL, /* class_init */</span><br><span class=\"line\"> NULL, /* class_finalize */</span><br><span class=\"line\"> NULL, /* class_data */</span><br><span class=\"line\"> 0, /* instance_size */</span><br><span class=\"line\"> 0, /* n_preallocs */</span><br><span class=\"line\"> NULL, /* instance_init */</span><br><span class=\"line\"> NULL /* value_table */</span><br><span class=\"line\"> };</span><br><span class=\"line\"> type = g_type_register_static(G_TYPE_INTERFACE,</span><br><span class=\"line\"> "RequestServiceIf",</span><br><span class=\"line\"> &type_info, 0);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return type;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_if_interface_init(RequestServiceIfInterface *iface);</span><br><span class=\"line\"></span><br><span class=\"line\">G_DEFINE_TYPE_WITH_CODE (RequestServiceClient, request_service_client,</span><br><span class=\"line\"> G_TYPE_OBJECT,</span><br><span class=\"line\"> G_IMPLEMENT_INTERFACE(TYPE_REQUEST_SERVICE_IF,</span><br><span class=\"line\"> request_service_if_interface_init))</span><br><span class=\"line\"></span><br><span class=\"line\">enum _RequestServiceClientProperties {</span><br><span class=\"line\"> PROP_0,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL</span><br><span class=\"line\">};</span><br><span class=\"line\"></span><br><span class=\"line\">void</span><br><span class=\"line\">request_service_client_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {</span><br><span class=\"line\"> RequestServiceClient *client = REQUEST_SERVICE_CLIENT (object);</span><br><span class=\"line\"></span><br><span class=\"line\"> THRIFT_UNUSED_VAR (pspec);</span><br><span class=\"line\"></span><br><span class=\"line\"> switch (property_id) {</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL:</span><br><span class=\"line\"> client->input_protocol = g_value_get_object(value);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL:</span><br><span class=\"line\"> client->output_protocol = g_value_get_object(value);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">void</span><br><span class=\"line\">request_service_client_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {</span><br><span class=\"line\"> RequestServiceClient *client = REQUEST_SERVICE_CLIENT (object);</span><br><span class=\"line\"></span><br><span class=\"line\"> THRIFT_UNUSED_VAR (pspec);</span><br><span class=\"line\"></span><br><span class=\"line\"> switch (property_id) {</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL:</span><br><span class=\"line\"> g_value_set_object(value, client->input_protocol);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL:</span><br><span class=\"line\"> g_value_set_object(value, client->output_protocol);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">gboolean request_service_client_send_send_message(RequestServiceIf *iface, const Param *param, GError **error) {</span><br><span class=\"line\"> gint32 cseqid = 0;</span><br><span class=\"line\"> ThriftProtocol *protocol = REQUEST_SERVICE_CLIENT (iface)->output_protocol;</span><br><span class=\"line\"></span><br><span class=\"line\"> if (thrift_protocol_write_message_begin(protocol, "sendMessage", T_CALL, cseqid, error) < 0)</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> {</span><br><span class=\"line\"> gint32 ret;</span><br><span class=\"line\"> gint32 xfer = 0;</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"> if ((ret = thrift_protocol_write_struct_begin(protocol, "sendMessage_args", error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if ((ret = thrift_protocol_write_field_begin(protocol, "param", T_STRUCT, 1, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if ((ret = thrift_struct_write(THRIFT_STRUCT (param), protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"></span><br><span class=\"line\"> if ((ret = thrift_protocol_write_field_end(protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if ((ret = thrift_protocol_write_field_stop(protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if ((ret = thrift_protocol_write_struct_end(protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> if (thrift_protocol_write_message_end(protocol, error) < 0)</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> if (!thrift_transport_flush(protocol->transport, error))</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> if (!thrift_transport_write_end(protocol->transport, error))</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> return TRUE;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">gboolean request_service_client_recv_send_message(RequestServiceIf *iface, Result **_return, GError **error) {</span><br><span class=\"line\"> gint32 rseqid;</span><br><span class=\"line\"> gchar *fname = NULL;</span><br><span class=\"line\"> ThriftMessageType mtype;</span><br><span class=\"line\"> ThriftProtocol *protocol = REQUEST_SERVICE_CLIENT (iface)->input_protocol;</span><br><span class=\"line\"> ThriftApplicationException *xception;</span><br><span class=\"line\"></span><br><span class=\"line\"> if (thrift_protocol_read_message_begin(protocol, &fname, &mtype, &rseqid, error) < 0) {</span><br><span class=\"line\"> if (fname) g_free(fname);</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> if (mtype == T_EXCEPTION) {</span><br><span class=\"line\"> if (fname) g_free(fname);</span><br><span class=\"line\"> xception = g_object_new(THRIFT_TYPE_APPLICATION_EXCEPTION, NULL);</span><br><span class=\"line\"> thrift_struct_read(THRIFT_STRUCT (xception), protocol, NULL);</span><br><span class=\"line\"> thrift_protocol_read_message_end(protocol, NULL);</span><br><span class=\"line\"> thrift_transport_read_end(protocol->transport, NULL);</span><br><span class=\"line\"> g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, xception->type, "application error: %s",</span><br><span class=\"line\"> xception->message);</span><br><span class=\"line\"> g_object_unref(xception);</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> } else if (mtype != T_REPLY) {</span><br><span class=\"line\"> if (fname) g_free(fname);</span><br><span class=\"line\"> thrift_protocol_skip(protocol, T_STRUCT, NULL);</span><br><span class=\"line\"> thrift_protocol_read_message_end(protocol, NULL);</span><br><span class=\"line\"> thrift_transport_read_end(protocol->transport, NULL);</span><br><span class=\"line\"> g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, THRIFT_APPLICATION_EXCEPTION_ERROR_INVALID_MESSAGE_TYPE,</span><br><span class=\"line\"> "invalid message type %d, expected T_REPLY", mtype);</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> } else if (strncmp(fname, "sendMessage", 11) != 0) {</span><br><span class=\"line\"> thrift_protocol_skip(protocol, T_STRUCT, NULL);</span><br><span class=\"line\"> thrift_protocol_read_message_end(protocol, error);</span><br><span class=\"line\"> thrift_transport_read_end(protocol->transport, error);</span><br><span class=\"line\"> g_set_error(error, THRIFT_APPLICATION_EXCEPTION_ERROR, THRIFT_APPLICATION_EXCEPTION_ERROR_WRONG_METHOD_NAME,</span><br><span class=\"line\"> "wrong method name %s, expected sendMessage", fname);</span><br><span class=\"line\"> if (fname) g_free(fname);</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if (fname) g_free(fname);</span><br><span class=\"line\"></span><br><span class=\"line\"> {</span><br><span class=\"line\"> gint32 ret;</span><br><span class=\"line\"> gint32 xfer = 0;</span><br><span class=\"line\"> gchar *name = NULL;</span><br><span class=\"line\"> ThriftType ftype;</span><br><span class=\"line\"> gint16 fid;</span><br><span class=\"line\"> guint32 len = 0;</span><br><span class=\"line\"> gpointer data = NULL;</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"> /* satisfy -Wall in case these aren't used */</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (len);</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (data);</span><br><span class=\"line\"></span><br><span class=\"line\"> /* read the struct begin marker */</span><br><span class=\"line\"> if ((ret = thrift_protocol_read_struct_begin(protocol, &name, error)) < 0) {</span><br><span class=\"line\"> if (name) g_free(name);</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if (name) g_free(name);</span><br><span class=\"line\"> name = NULL;</span><br><span class=\"line\"></span><br><span class=\"line\"> /* read the struct fields */</span><br><span class=\"line\"> while (1) {</span><br><span class=\"line\"> /* read the beginning of a field */</span><br><span class=\"line\"> if ((ret = thrift_protocol_read_field_begin(protocol, &name, &ftype, &fid, error)) < 0) {</span><br><span class=\"line\"> if (name) g_free(name);</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> if (name) g_free(name);</span><br><span class=\"line\"> name = NULL;</span><br><span class=\"line\"></span><br><span class=\"line\"> /* break if we get a STOP field */</span><br><span class=\"line\"> if (ftype == T_STOP) {</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> switch (fid) {</span><br><span class=\"line\"> case 0:</span><br><span class=\"line\"> if (ftype == T_STRUCT) {</span><br><span class=\"line\"> if ((ret = thrift_struct_read(THRIFT_STRUCT (*_return), protocol, error)) < 0) {</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> if ((ret = thrift_protocol_skip(protocol, ftype, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> default:</span><br><span class=\"line\"> if ((ret = thrift_protocol_skip(protocol, ftype, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> if ((ret = thrift_protocol_read_field_end(protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> if ((ret = thrift_protocol_read_struct_end(protocol, error)) < 0)</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> xfer += ret;</span><br><span class=\"line\"></span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> if (thrift_protocol_read_message_end(protocol, error) < 0)</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> if (!thrift_transport_read_end(protocol->transport, error))</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> return TRUE;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">gboolean</span><br><span class=\"line\">request_service_client_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {</span><br><span class=\"line\"> if (!request_service_client_send_send_message(iface, param, error))</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> if (!request_service_client_recv_send_message(iface, _return, error))</span><br><span class=\"line\"> return FALSE;</span><br><span class=\"line\"> return TRUE;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_if_interface_init(RequestServiceIfInterface *iface) {</span><br><span class=\"line\"> iface->send_message = request_service_client_send_message;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_client_init(RequestServiceClient *client) {</span><br><span class=\"line\"> client->input_protocol = NULL;</span><br><span class=\"line\"> client->output_protocol = NULL;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_client_class_init(RequestServiceClientClass *cls) {</span><br><span class=\"line\"> GObjectClass *gobject_class = G_OBJECT_CLASS (cls);</span><br><span class=\"line\"> GParamSpec *param_spec;</span><br><span class=\"line\"></span><br><span class=\"line\"> gobject_class->set_property = request_service_client_set_property;</span><br><span class=\"line\"> gobject_class->get_property = request_service_client_get_property;</span><br><span class=\"line\"></span><br><span class=\"line\"> param_spec = g_param_spec_object("input_protocol",</span><br><span class=\"line\"> "input protocol (construct)",</span><br><span class=\"line\"> "Set the client input protocol",</span><br><span class=\"line\"> THRIFT_TYPE_PROTOCOL,</span><br><span class=\"line\"> G_PARAM_READWRITE);</span><br><span class=\"line\"> g_object_class_install_property(gobject_class,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_CLIENT_INPUT_PROTOCOL, param_spec);</span><br><span class=\"line\"></span><br><span class=\"line\"> param_spec = g_param_spec_object("output_protocol",</span><br><span class=\"line\"> "output protocol (construct)",</span><br><span class=\"line\"> "Set the client output protocol",</span><br><span class=\"line\"> THRIFT_TYPE_PROTOCOL,</span><br><span class=\"line\"> G_PARAM_READWRITE);</span><br><span class=\"line\"> g_object_class_install_property(gobject_class,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_CLIENT_OUTPUT_PROTOCOL, param_spec);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_handler_request_service_if_interface_init(RequestServiceIfInterface *iface);</span><br><span class=\"line\"></span><br><span class=\"line\">G_DEFINE_TYPE_WITH_CODE (RequestServiceHandler,</span><br><span class=\"line\"> request_service_handler,</span><br><span class=\"line\"> G_TYPE_OBJECT,</span><br><span class=\"line\"> G_IMPLEMENT_INTERFACE(TYPE_REQUEST_SERVICE_IF,</span><br><span class=\"line\"> request_service_handler_request_service_if_interface_init))</span><br><span class=\"line\"></span><br><span class=\"line\">gboolean</span><br><span class=\"line\">request_service_handler_send_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {</span><br><span class=\"line\"> g_return_val_if_fail (IS_REQUEST_SERVICE_HANDLER(iface), FALSE);</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"> printf("%p %p \\n", param->key, error); // 这里写自己server的业务,有点头大。。。</span><br><span class=\"line\"></span><br><span class=\"line\"> return REQUEST_SERVICE_HANDLER_GET_CLASS (iface)->send_message(iface, _return, param, error);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_handler_request_service_if_interface_init(RequestServiceIfInterface *iface) {</span><br><span class=\"line\"> iface->send_message = request_service_handler_send_message; //这里自己注册</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_handler_init(RequestServiceHandler *self) {</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (self);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static gboolean</span><br><span class=\"line\">request_service_handler_sends_message(RequestServiceIf *iface, Result **_return, const Param *param, GError **error) {</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (iface);</span><br><span class=\"line\"> THRIFT_UNUSED_VAR (error);</span><br><span class=\"line\"> printf("service request_service_handler_sends_message run key-> %s, value -> %d \\n",param->key,param->value);</span><br><span class=\"line\"> puts("zip()");</span><br><span class=\"line\"> Result *result = g_object_new(TYPE_RESULT, NULL);</span><br><span class=\"line\"> result->result = true;</span><br><span class=\"line\"> *_return = result;</span><br><span class=\"line\"> return TRUE;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_handler_class_init(RequestServiceHandlerClass *cls) {</span><br><span class=\"line\"> printf("request_service_handler_class_init in request\\n");</span><br><span class=\"line\"></span><br><span class=\"line\"> cls->send_message = request_service_handler_sends_message;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">enum _RequestServiceProcessorProperties {</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_PROCESSOR_0,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_PROCESSOR_HANDLER</span><br><span class=\"line\">};</span><br><span class=\"line\"></span><br><span class=\"line\">G_DEFINE_TYPE (RequestServiceProcessor,</span><br><span class=\"line\"> request_service_processor,</span><br><span class=\"line\"> THRIFT_TYPE_DISPATCH_PROCESSOR)</span><br><span class=\"line\"></span><br><span class=\"line\">typedef gboolean (*RequestServiceProcessorProcessFunction)(RequestServiceProcessor *,</span><br><span class=\"line\"> gint32,</span><br><span class=\"line\"> ThriftProtocol *,</span><br><span class=\"line\"> ThriftProtocol *,</span><br><span class=\"line\"> GError **);</span><br><span class=\"line\"></span><br><span class=\"line\">typedef struct {</span><br><span class=\"line\"> gchar *name;</span><br><span class=\"line\"> RequestServiceProcessorProcessFunction function;</span><br><span class=\"line\">} request_service_processor_process_function_def;</span><br><span class=\"line\"></span><br><span class=\"line\">static gboolean</span><br><span class=\"line\">request_service_processor_process_send_message(RequestServiceProcessor *,</span><br><span class=\"line\"> gint32,</span><br><span class=\"line\"> ThriftProtocol *,</span><br><span class=\"line\"> ThriftProtocol *,</span><br><span class=\"line\"> GError **);</span><br><span class=\"line\"></span><br><span class=\"line\">static request_service_processor_process_function_def</span><br><span class=\"line\"> request_service_processor_process_function_defs[1] = {</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "sendMessage",</span><br><span class=\"line\"> request_service_processor_process_send_message</span><br><span class=\"line\"> }</span><br><span class=\"line\">};</span><br><span class=\"line\"></span><br><span class=\"line\">static gboolean</span><br><span class=\"line\">request_service_processor_process_send_message(RequestServiceProcessor *self,</span><br><span class=\"line\"> gint32 sequence_id,</span><br><span class=\"line\"> ThriftProtocol *input_protocol,</span><br><span class=\"line\"> ThriftProtocol *output_protocol,</span><br><span class=\"line\"> GError **error) {</span><br><span class=\"line\"> gboolean result = TRUE;</span><br><span class=\"line\"> ThriftTransport *transport;</span><br><span class=\"line\"> ThriftApplicationException *xception;</span><br><span class=\"line\"> RequestServiceSendMessageArgs *args =</span><br><span class=\"line\"> g_object_new(TYPE_REQUEST_SERVICE_SEND_MESSAGE_ARGS, NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> printf("request_service_processor_process_send_message\\n");</span><br><span class=\"line\"> g_object_get(input_protocol, "transport", &transport, NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> if ((thrift_struct_read(THRIFT_STRUCT (args), input_protocol, error) != -1) &&</span><br><span class=\"line\"> (thrift_protocol_read_message_end(input_protocol, error) != -1) &&</span><br><span class=\"line\"> (thrift_transport_read_end(transport, error) != FALSE)) {</span><br><span class=\"line\"> Param *param;</span><br><span class=\"line\"> Result *return_value;</span><br><span class=\"line\"> RequestServiceSendMessageResult *result_struct;</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_get(args,</span><br><span class=\"line\"> "param", &param,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_unref(transport);</span><br><span class=\"line\"> g_object_get(output_protocol, "transport", &transport, NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> result_struct = g_object_new(TYPE_REQUEST_SERVICE_SEND_MESSAGE_RESULT, NULL);</span><br><span class=\"line\"> g_object_get(result_struct, "success", &return_value, NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> if (request_service_handler_send_message(REQUEST_SERVICE_IF (self->handler),</span><br><span class=\"line\"> &return_value,</span><br><span class=\"line\"> param,</span><br><span class=\"line\"> error) == TRUE) {</span><br><span class=\"line\"> g_object_set(result_struct, "success", return_value, NULL);</span><br><span class=\"line\"></span><br><span class=\"line\"> result =</span><br><span class=\"line\"> ((thrift_protocol_write_message_begin(output_protocol,</span><br><span class=\"line\"> "sendMessage",</span><br><span class=\"line\"> T_REPLY,</span><br><span class=\"line\"> sequence_id,</span><br><span class=\"line\"> error) != -1) &&</span><br><span class=\"line\"> (thrift_struct_write(THRIFT_STRUCT (result_struct),</span><br><span class=\"line\"> output_protocol,</span><br><span class=\"line\"> error) != -1));</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> if (*error == NULL)</span><br><span class=\"line\"> g_warning ("RequestService.sendMessage implementation returned FALSE "</span><br><span class=\"line\"> "but did not set an error");</span><br><span class=\"line\"></span><br><span class=\"line\"> xception =</span><br><span class=\"line\"> g_object_new(THRIFT_TYPE_APPLICATION_EXCEPTION,</span><br><span class=\"line\"> "type", *error != NULL ? (*error)->code :</span><br><span class=\"line\"> THRIFT_APPLICATION_EXCEPTION_ERROR_UNKNOWN,</span><br><span class=\"line\"> "message", *error != NULL ? (*error)->message : NULL,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> g_clear_error(error);</span><br><span class=\"line\"></span><br><span class=\"line\"> result =</span><br><span class=\"line\"> ((thrift_protocol_write_message_begin(output_protocol,</span><br><span class=\"line\"> "sendMessage",</span><br><span class=\"line\"> T_EXCEPTION,</span><br><span class=\"line\"> sequence_id,</span><br><span class=\"line\"> error) != -1) &&</span><br><span class=\"line\"> (thrift_struct_write(THRIFT_STRUCT (xception),</span><br><span class=\"line\"> output_protocol,</span><br><span class=\"line\"> error) != -1));</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_unref(xception);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> if (param != NULL)</span><br><span class=\"line\"> g_object_unref(param);</span><br><span class=\"line\"> if (return_value != NULL)</span><br><span class=\"line\"> g_object_unref(return_value);</span><br><span class=\"line\"> g_object_unref(result_struct);</span><br><span class=\"line\"></span><br><span class=\"line\"> if (result == TRUE)</span><br><span class=\"line\"> result =</span><br><span class=\"line\"> ((thrift_protocol_write_message_end(output_protocol, error) != -1) &&</span><br><span class=\"line\"> (thrift_transport_write_end(transport, error) != FALSE) &&</span><br><span class=\"line\"> (thrift_transport_flush(transport, error) != FALSE));</span><br><span class=\"line\"> } else</span><br><span class=\"line\"> result = FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> g_object_unref(transport);</span><br><span class=\"line\"> g_object_unref(args);</span><br><span class=\"line\"></span><br><span class=\"line\"> return result;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static gboolean</span><br><span class=\"line\">request_service_processor_dispatch_call(ThriftDispatchProcessor *dispatch_processor,</span><br><span class=\"line\"> ThriftProtocol *input_protocol,</span><br><span class=\"line\"> ThriftProtocol *output_protocol,</span><br><span class=\"line\"> gchar *method_name,</span><br><span class=\"line\"> gint32 sequence_id,</span><br><span class=\"line\"> GError **error) {</span><br><span class=\"line\"> request_service_processor_process_function_def *process_function_def;</span><br><span class=\"line\"> gboolean dispatch_result = FALSE;</span><br><span class=\"line\"></span><br><span class=\"line\"> RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (dispatch_processor);</span><br><span class=\"line\"> ThriftDispatchProcessorClass *parent_class =</span><br><span class=\"line\"> g_type_class_peek_parent(REQUEST_SERVICE_PROCESSOR_GET_CLASS (self));</span><br><span class=\"line\"></span><br><span class=\"line\"> process_function_def = g_hash_table_lookup(self->process_map, method_name);</span><br><span class=\"line\"> if (process_function_def != NULL) {</span><br><span class=\"line\"> g_free(method_name);</span><br><span class=\"line\"> dispatch_result = (*process_function_def->function)(self,</span><br><span class=\"line\"> sequence_id,</span><br><span class=\"line\"> input_protocol,</span><br><span class=\"line\"> output_protocol,</span><br><span class=\"line\"> error);</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> dispatch_result = parent_class->dispatch_call(dispatch_processor,</span><br><span class=\"line\"> input_protocol,</span><br><span class=\"line\"> output_protocol,</span><br><span class=\"line\"> method_name,</span><br><span class=\"line\"> sequence_id,</span><br><span class=\"line\"> error);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> return dispatch_result;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_set_property(GObject *object,</span><br><span class=\"line\"> guint property_id,</span><br><span class=\"line\"> const GValue *value,</span><br><span class=\"line\"> GParamSpec *pspec) {</span><br><span class=\"line\"> RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (object);</span><br><span class=\"line\"></span><br><span class=\"line\"> switch (property_id) {</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_PROCESSOR_HANDLER:</span><br><span class=\"line\"> if (self->handler != NULL)</span><br><span class=\"line\"> g_object_unref(self->handler);</span><br><span class=\"line\"> self->handler = g_value_get_object(value);</span><br><span class=\"line\"> g_object_ref (self->handler);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> default:</span><br><span class=\"line\"> G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_get_property(GObject *object,</span><br><span class=\"line\"> guint property_id,</span><br><span class=\"line\"> GValue *value,</span><br><span class=\"line\"> GParamSpec *pspec) {</span><br><span class=\"line\"> RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (object);</span><br><span class=\"line\"></span><br><span class=\"line\"> switch (property_id) {</span><br><span class=\"line\"> case PROP_REQUEST_SERVICE_PROCESSOR_HANDLER:</span><br><span class=\"line\"> g_value_set_object(value, self->handler);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> default:</span><br><span class=\"line\"> G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);</span><br><span class=\"line\"> break;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_dispose(GObject *gobject) {</span><br><span class=\"line\"> RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (gobject);</span><br><span class=\"line\"></span><br><span class=\"line\"> if (self->handler != NULL) {</span><br><span class=\"line\"> g_object_unref(self->handler);</span><br><span class=\"line\"> self->handler = NULL;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> G_OBJECT_CLASS (request_service_processor_parent_class)->dispose(gobject);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_finalize(GObject *gobject) {</span><br><span class=\"line\"> RequestServiceProcessor *self = REQUEST_SERVICE_PROCESSOR (gobject);</span><br><span class=\"line\"></span><br><span class=\"line\"> thrift_safe_hash_table_destroy(self->process_map);</span><br><span class=\"line\"></span><br><span class=\"line\"> G_OBJECT_CLASS (request_service_processor_parent_class)->finalize(gobject);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_init(RequestServiceProcessor *self) {</span><br><span class=\"line\"> guint index;</span><br><span class=\"line\"></span><br><span class=\"line\"> self->handler = NULL;</span><br><span class=\"line\"> self->process_map = g_hash_table_new(g_str_hash, g_str_equal);</span><br><span class=\"line\"></span><br><span class=\"line\"> for (index = 0; index < 1; index += 1)</span><br><span class=\"line\"> g_hash_table_insert(self->process_map,</span><br><span class=\"line\"> request_service_processor_process_function_defs[index].name,</span><br><span class=\"line\"> &request_service_processor_process_function_defs[index]);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">request_service_processor_class_init(RequestServiceProcessorClass *cls) {</span><br><span class=\"line\"> GObjectClass *gobject_class = G_OBJECT_CLASS (cls);</span><br><span class=\"line\"> ThriftDispatchProcessorClass *dispatch_processor_class =</span><br><span class=\"line\"> THRIFT_DISPATCH_PROCESSOR_CLASS (cls);</span><br><span class=\"line\"> GParamSpec *param_spec;</span><br><span class=\"line\"></span><br><span class=\"line\"> gobject_class->dispose = request_service_processor_dispose;</span><br><span class=\"line\"> gobject_class->finalize = request_service_processor_finalize;</span><br><span class=\"line\"> gobject_class->set_property = request_service_processor_set_property;</span><br><span class=\"line\"> gobject_class->get_property = request_service_processor_get_property;</span><br><span class=\"line\"></span><br><span class=\"line\"> dispatch_processor_class->dispatch_call = request_service_processor_dispatch_call;</span><br><span class=\"line\"> cls->dispatch_call = request_service_processor_dispatch_call;</span><br><span class=\"line\"></span><br><span class=\"line\"> param_spec = g_param_spec_object("handler",</span><br><span class=\"line\"> "Service handler implementation",</span><br><span class=\"line\"> "The service handler implementation "</span><br><span class=\"line\"> "to which method calls are dispatched.",</span><br><span class=\"line\"> TYPE_REQUEST_SERVICE_HANDLER,</span><br><span class=\"line\"> G_PARAM_READWRITE);</span><br><span class=\"line\"> g_object_class_install_property(gobject_class,</span><br><span class=\"line\"> PROP_REQUEST_SERVICE_PROCESSOR_HANDLER,</span><br><span class=\"line\"> param_spec);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"Client段\"><a href=\"#Client段\" class=\"headerlink\" title=\"Client段\"></a>Client段</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">#include <stdio.h></span><br><span class=\"line\">#include <glib-object.h></span><br><span class=\"line\"></span><br><span class=\"line\">#include <thrift/c_glib/protocol/thrift_binary_protocol.h></span><br><span class=\"line\">#include <thrift/c_glib/transport/thrift_buffered_transport.h></span><br><span class=\"line\">#include <thrift/c_glib/transport/thrift_socket.h></span><br><span class=\"line\"></span><br><span class=\"line\">#include "gen-c_glib/request_service.h"</span><br><span class=\"line\"></span><br><span class=\"line\">int main() {</span><br><span class=\"line\"> ThriftSocket *socket;</span><br><span class=\"line\"> ThriftTransport *transport;</span><br><span class=\"line\"> ThriftProtocol *protocol;</span><br><span class=\"line\"> RequestServiceIf *client;</span><br><span class=\"line\"> socket = g_object_new(THRIFT_TYPE_SOCKET,</span><br><span class=\"line\"> "hostname", "localhost",</span><br><span class=\"line\"> "port", 9090,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> transport = g_object_new(THRIFT_TYPE_BUFFERED_TRANSPORT,</span><br><span class=\"line\"> "transport", socket,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> protocol = g_object_new(THRIFT_TYPE_BINARY_PROTOCOL,</span><br><span class=\"line\"> "transport", transport,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> GError *error = NULL;</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"> thrift_transport_open(transport, &error);</span><br><span class=\"line\"> printf("open client err %p \\n", error);</span><br><span class=\"line\"> client = g_object_new(TYPE_REQUEST_SERVICE_CLIENT,</span><br><span class=\"line\"> "input_protocol", protocol,</span><br><span class=\"line\"> "output_protocol", protocol,</span><br><span class=\"line\"> NULL);</span><br><span class=\"line\"> printf("client init finish %p \\n", client);</span><br><span class=\"line\"> Result *result = g_object_new(TYPE_RESULT, NULL);</span><br><span class=\"line\"> Param *param = g_object_new(TYPE_PARAM, NULL);</span><br><span class=\"line\"> param->key = "test";</span><br><span class=\"line\"> param->value = 2;</span><br><span class=\"line\"> printf("param %s %d \\n", param->key,param->value);</span><br><span class=\"line\"> gboolean ret = request_service_if_send_message(client, &result, param, &error);</span><br><span class=\"line\"> printf("ret %b \\n", ret);</span><br><span class=\"line\"> printf("result %d \\n", result->result);</span><br><span class=\"line\"> printf("error %s \\n", error->message);</span><br><span class=\"line\"></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"CMakeFile-txt\"><a href=\"#CMakeFile-txt\" class=\"headerlink\" title=\"CMakeFile.txt\"></a>CMakeFile.txt</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cmake_minimum_required(VERSION 3.24)</span><br><span class=\"line\">project(thrift_rpc C)</span><br><span class=\"line\"></span><br><span class=\"line\">set(CMAKE_C_STANDARD 11)</span><br><span class=\"line\">include_directories(/usr/include/glib-2.0)</span><br><span class=\"line\">include_directories(/usr/lib/x86_64-linux-gnu/glib-2.0/include)</span><br><span class=\"line\">add_executable(thrift_rpc main.c gen-c_glib/request_service.c gen-c_glib/model_types.c)</span><br><span class=\"line\">target_link_libraries(thrift_rpc /usr/local/lib/libthrift_c_glib.so)</span><br><span class=\"line\">target_link_libraries(thrift_rpc /usr/lib/x86_64-linux-gnu/libgobject-2.0.so)</span><br><span class=\"line\">target_link_libraries(thrift_rpc /usr/lib/x86_64-linux-gnu/libglib-2.0.so)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">project(thrift_rpc_server C)</span><br><span class=\"line\">set(CMAKE_C_STANDARD 11)</span><br><span class=\"line\"></span><br><span class=\"line\">add_executable(thrift_rpc_server server.c gen-c_glib/request_service.c gen-c_glib/model_types.c)</span><br><span class=\"line\"></span><br><span class=\"line\">include_directories(/usr/include/glib-2.0)</span><br><span class=\"line\">include_directories(/usr/lib/x86_64-linux-gnu/glib-2.0/include)</span><br><span class=\"line\"></span><br><span class=\"line\">target_link_libraries(thrift_rpc_server /usr/local/lib/libthrift_c_glib.so)</span><br><span class=\"line\">target_link_libraries(thrift_rpc_server /usr/lib/x86_64-linux-gnu/libgobject-2.0.so)</span><br><span class=\"line\">target_link_libraries(thrift_rpc_server /usr/lib/x86_64-linux-gnu/libglib-2.0.so)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h1 id=\"protobuf-c-rpc\"><a href=\"#protobuf-c-rpc\" class=\"headerlink\" title=\"protobuf-c-rpc\"></a>protobuf-c-rpc</h1><p>这里说一下protobuf-c-rpc的特点</p>\n<ul>\n<li>rpc的函数注册是基于前缀的,示例中的<code>example__</code>就是。</li>\n<li>Client只能通过异步回调的方式(至少我没发现有方法可以同步获取结果)</li>\n<li>支持多种通信方式,tcp,sock等</li>\n</ul>\n<h2 id=\"编译安装protofbuf\"><a href=\"#编译安装protofbuf\" class=\"headerlink\" title=\"编译安装protofbuf\"></a>编译安装protofbuf</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># wget https://github.com/protocolbuffers/protobuf/releases/download/v22.0/protobuf-22.0.tar.gz</span><br><span class=\"line\"># tar -zxvf protobuf-22.0.tar.gz</span><br><span class=\"line\"># cd protobuf-22.0</span><br><span class=\"line\"># ./autogen.sh</span><br><span class=\"line\"># ./configure</span><br><span class=\"line\"># make </span><br><span class=\"line\"># make install</span><br></pre></td></tr></table></figure>\n<h2 id=\"编译安装protofbuf-c\"><a href=\"#编译安装protofbuf-c\" class=\"headerlink\" title=\"编译安装protofbuf-c\"></a>编译安装protofbuf-c</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># wget https://github.com/protobuf-c/protobuf-c/releases/download/v1.4.1/protobuf-c-1.4.1.tar.gz</span><br><span class=\"line\"># tar -zxvf protobuf-c-1.4.1.tar.gz</span><br><span class=\"line\"># cd protobuf-c</span><br><span class=\"line\"># ./autogen.sh</span><br><span class=\"line\"># ./configure</span><br><span class=\"line\"># make </span><br><span class=\"line\"># make install</span><br></pre></td></tr></table></figure>\n<h2 id=\"编译安装protofbuf-c-rpc\"><a href=\"#编译安装protofbuf-c-rpc\" class=\"headerlink\" title=\"编译安装protofbuf-c-rpc\"></a>编译安装protofbuf-c-rpc</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"># git clone https://github.com/protobuf-c/protobuf-c-rpc.git</span><br><span class=\"line\"># cd protobuf-c-rpc</span><br><span class=\"line\"># ./autogen.sh</span><br><span class=\"line\"># ./configure</span><br><span class=\"line\"># make </span><br><span class=\"line\"># make install</span><br></pre></td></tr></table></figure>\n<h2 id=\"编写协议-1\"><a href=\"#编写协议-1\" class=\"headerlink\" title=\"编写协议\"></a>编写协议</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">syntax = "proto3";</span><br><span class=\"line\"></span><br><span class=\"line\">message Param {</span><br><span class=\"line\"> string message = 1;</span><br><span class=\"line\">}</span><br><span class=\"line\">message Result {</span><br><span class=\"line\"> bool result = 1;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">service RpcSendMsg {</span><br><span class=\"line\"> rpc Send (Param) returns (Result);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Server端-1\"><a href=\"#Server端-1\" class=\"headerlink\" title=\"Server端\"></a>Server端</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">#include "stdio.h"</span><br><span class=\"line\">#include "model.pb-c.h"</span><br><span class=\"line\">#include <string.h></span><br><span class=\"line\">#include <protobuf-c-rpc/protobuf-c-rpc.h></span><br><span class=\"line\">#include <stdbool.h></span><br><span class=\"line\"></span><br><span class=\"line\">static void example__send(RpcSendMsg_Service *service, const Param *param, Result_Closure closure, void *closure_data) {</span><br><span class=\"line\"> printf("example__send run message:%s \\n",param->message);</span><br><span class=\"line\"> (void) service;</span><br><span class=\"line\"> Result result = RESULT__INIT;</span><br><span class=\"line\"> if (strcmp(param->message, "hello") != 0) {</span><br><span class=\"line\"> result.result = true;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> result.result = false;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> closure(&result, closure_data);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">static RpcSendMsg_Service rpc_send_msg_service = RPC_SEND_MSG__INIT(example__);</span><br><span class=\"line\"></span><br><span class=\"line\">int main(void) {</span><br><span class=\"line\"> printf("service start \\n");</span><br><span class=\"line\"> ProtobufC_RPC_Server *server;</span><br><span class=\"line\"> ProtobufC_RPC_AddressType address_type = PROTOBUF_C_RPC_ADDRESS_TCP;</span><br><span class=\"line\"> server = protobuf_c_rpc_server_new(address_type, "12345", (ProtobufCService *) &rpc_send_msg_service, NULL);</span><br><span class=\"line\"> printf("service start at 12345 port\\n");</span><br><span class=\"line\"> for (;;)</span><br><span class=\"line\"> protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h2 id=\"Client段-1\"><a href=\"#Client段-1\" class=\"headerlink\" title=\"Client段\"></a>Client段</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">//</span><br><span class=\"line\">// Created by Eviltuzki on 23-2-11.</span><br><span class=\"line\">//</span><br><span class=\"line\">#include "stdio.h"</span><br><span class=\"line\">#include <string.h></span><br><span class=\"line\">#include "model.pb-c.h"</span><br><span class=\"line\">#include <protobuf-c-rpc//protobuf-c-rpc.h></span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">static void</span><br><span class=\"line\">handler_result(const Result *result, void *closure_data) {</span><br><span class=\"line\"> printf("rpc result %d\\n", result->result);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">int main(void) {</span><br><span class=\"line\"> ProtobufCService *service;</span><br><span class=\"line\"> ProtobufC_RPC_Client *client;</span><br><span class=\"line\"> ProtobufC_RPC_AddressType address_type = PROTOBUF_C_RPC_ADDRESS_TCP;</span><br><span class=\"line\"> const char *name = "localhost:12345";</span><br><span class=\"line\"> service = protobuf_c_rpc_client_new(address_type, name, &rpc_send_msg__descriptor, NULL);</span><br><span class=\"line\"> printf("connenct to server service is %p \\n",service);</span><br><span class=\"line\"> if (service == NULL) {</span><br><span class=\"line\"> printf("error creating client \\n");</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> client = (ProtobufC_RPC_Client *) service;</span><br><span class=\"line\"> while (!protobuf_c_rpc_client_is_connected(client))</span><br><span class=\"line\"> protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());</span><br><span class=\"line\"> protobuf_c_boolean is_done = 0;</span><br><span class=\"line\"> Param param = PARAM__INIT;</span><br><span class=\"line\"> param.message = "hello1";</span><br><span class=\"line\"> rpc_send_msg__send(service, &param, handler_result, &is_done);</span><br><span class=\"line\"> while (!is_done)</span><br><span class=\"line\"> protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());</span><br><span class=\"line\"> return 0;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"CMakeFile-txt-1\"><a href=\"#CMakeFile-txt-1\" class=\"headerlink\" title=\"CMakeFile.txt\"></a>CMakeFile.txt</h2><figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">cmake_minimum_required(VERSION 3.24)</span><br><span class=\"line\">project(rpc C)</span><br><span class=\"line\"></span><br><span class=\"line\">set(CMAKE_C_STANDARD 17)</span><br><span class=\"line\"></span><br><span class=\"line\">add_executable(rpc main.c model.pb-c.h model.pb-c.c)</span><br><span class=\"line\">target_link_libraries(rpc /usr/local/lib//libprotobuf-c-rpc.so)</span><br><span class=\"line\">target_link_libraries(rpc /usr/local/lib/libprotobuf-c.so)</span><br><span class=\"line\">target_link_libraries(rpc /usr/local/lib/libprotoc.so)</span><br><span class=\"line\">target_link_libraries(rpc /usr/local/lib/libprotobuf-lite.so)</span><br><span class=\"line\">target_link_libraries(rpc /usr/local/lib/libprotobuf.so)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">project(rpc-s C)</span><br><span class=\"line\">add_executable(rpc-s server.c model.pb-c.h model.pb-c.c)</span><br><span class=\"line\">target_link_libraries(rpc-s /usr/local/lib//libprotobuf-c-rpc.so)</span><br><span class=\"line\">target_link_libraries(rpc-s /usr/local/lib/libprotobuf-c.so)</span><br><span class=\"line\">target_link_libraries(rpc-s /usr/local/lib/libprotoc.so)</span><br><span class=\"line\">target_link_libraries(rpc-s /usr/local/lib/libprotobuf-lite.so)</span><br><span class=\"line\">target_link_libraries(rpc-s /usr/local/lib/libprotobuf.so)</span><br></pre></td></tr></table></figure>\n\n<h1 id=\"rpcgen\"><a href=\"#rpcgen\" class=\"headerlink\" title=\"rpcgen\"></a>rpcgen</h1><p>网上例子比较多,这里就不列举了,需要注意的是<br>long long类型对应的是hyper<br>参考:<br><a href=\"https://docs.oracle.com/cd/E26502_01/html/E35597/xdrproto-31244.html#xdrproto-18\">https://docs.oracle.com/cd/E26502_01/html/E35597/xdrproto-31244.html#xdrproto-18</a><br><a href=\"https://docs.oracle.com/cd/E19683-01/816-1435/6m7rrfn7f/index.html\">https://docs.oracle.com/cd/E19683-01/816-1435/6m7rrfn7f/index.html</a></p>\n","categories":["RPC, Thrift, ProtoBuf, C"],"tags":["RPC, Thrift, ProtoBuf, C"]}]