<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>wind blog</title>
    <description>在此，与你分享我的一些简单想法.</description>
    <link>http://windblog.cn/</link>
    <atom:link href="http://windblog.cn/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Fri, 24 Feb 2023 07:40:43 +0000</pubDate>
    <lastBuildDate>Fri, 24 Feb 2023 07:40:43 +0000</lastBuildDate>
    <generator>Jekyll v3.9.3</generator>
    
      <item>
        <title>想不开系列 —— C++折腾gRPC小记</title>
        <description>&lt;h3 id=&quot;前言&quot;&gt;前言&lt;/h3&gt;

&lt;p&gt;本文记录了19下半年，因为项目需要开发一个监控采集数据的Agent程序，我使用C++折腾gRPC的经历。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://sweetcode.io/wp-content/uploads/2018/01/grpc_square_reverse_4x.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1grpc简介&quot;&gt;1、gRPC简介&lt;/h3&gt;

&lt;p&gt;服务（Service） ，正是作为业务服务器的最根本职能，而下面是我认为的一些常见服务架构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tnzqBD.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;都2020年了，应该没有人不知道rpc是啥了吧？而说到常用的rpc框架如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tuSYCR.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我选择了gRPC作为与用户方通信的方式，gRPC是在HTTP/2之上实现的RPC框架, 它运行在TCP协议之上，相比于传统的REST/JSON机制有诸多的优点：
（1）基于HTTP/2之上的二进制协议（Protobuf序列化机制）
（2）一个连接上可以多路复用，并发处理多个请求和响应
（3）支持多种语言的类库实现（然而我选择了困难度最高的c++）
（4）服务定义文件和自动代码生成（.proto文件和Protobuf编译工具）
（5）pb作为通信数据交换格式，好处不用多说（感兴趣请详见文章&lt;a href=&quot;http://windblog.cn/big-data/2018/08/20/google-pb/&quot;&gt;《浅谈protobuf》&lt;/a&gt;）&lt;/p&gt;

&lt;p&gt;gRPC的通信模型架构如下：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tupEqO.png&quot; alt=&quot;Thumper&quot; /&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tupZZD.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而gRPC使用了Http2.0作为协议层，大大加强了通信的性能与安全性：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tuPYdO.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我从自己通信的请求中抓了下请求的包，长这个样子：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tupzOf.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;简而言之，gGRPC把元数据放到HTTP/2 Headers里，请求参数序列化之后放到DATA Frame里。&lt;/p&gt;

&lt;h3 id=&quot;2安装c-grpc编译执行环境&quot;&gt;2、安装C++ gRPC编译/执行环境&lt;/h3&gt;

&lt;p&gt;好了，谈架构和原理，大家也许都头头是道，实际上手才发现，除了google本身的golang儿子，其他语言使用起来其实都有些麻烦。。。&lt;/p&gt;

&lt;p&gt;环境/内核要求：
tlinux 2.2以上版本，gcc编译器4.8.0以上版本，我是Cent7.0+的环境安装的。&lt;/p&gt;

&lt;p&gt;（1）安装相关依赖&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yum install build-essential autoconf libtool pkg-config
yum install libgflags-dev libgtest-dev
yum install clang libc++-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin/gcc --version&lt;/code&gt;查看c库版本：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gcc (GCC) 4.8.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（2）clone代码到机器&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（3）解压、初始化submodule&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd grpc
git submodule update --init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(4) 编译&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export LD_LIBRARY_PATH=LDLIBRARYPATH:/usr/local/libexport:LDLIBRARYPATH=/{target-path}/grpc/thirdparty/protobuf/src/.libs/:LD_LIBRARY_PATH

make -j32
make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完成编译后，我们可以看到，目录大致如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[root@host:/usr/local/grpc]$ ls
AUTHORS             examples                OWNERS
bazel               Gemfile                 package.xml
bins                gens                    PYTHON-MANIFEST.in
BUILD               grpc.bzl                Rakefile
build_config.rb     gRPC-Core.podspec       README.md
BUILD.gn            gRPC-C++.podspec        requirements.bazel.txt
BUILDING.md         grpc.def                requirements.txt
build.yaml          grpc.gemspec            setup.cfg
cache.mk            grpc.gyp                setup.py
cmake               gRPC.podspec            src
CMakeLists.txt      gRPC-ProtoRPC.podspec   summerofcode
CODE-OF-CONDUCT.md  gRPC-RxLibrary.podspec  templates
composer.json       include                 test
CONCEPTS.md         libs                    third_party
config.m4           LICENSE                 tools
config.w32          Makefile                TROUBLESHOOTING.md
CONTRIBUTING.md     MANIFEST.md             WORKSPACE
doc                 NOTICE.txt
etc                 objs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中不同gRPC版本，依赖的protobuf版本也不同，执行install后，gRPC相关库默认被安装到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/lib/&lt;/code&gt;目录下（&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;libaddress_sorting.a               libgrpc++_reflection.a
libaddress_sorting.so              libgrpc++_reflection.so
libaddress_sorting.so.7            libgrpc++_reflection.so.1
libaddress_sorting.so.7.0.0        libgrpc++_reflection.so.1.20.0
libgpr.a                           libgrpc.so
libgpr.so                          libgrpc++.so
libgpr.so.7                        libgrpc++.so.1
libgpr.so.7.0.0                    libgrpc++.so.1.20.0
libgrpc.a                          libgrpc.so.7
libgrpc++.a                        libgrpc.so.7.0.0
libgrpc_cronet.a                   libgrpc_unsecure.a
libgrpc++_cronet.a                 libgrpc++_unsecure.a
libgrpc_cronet.so                  libgrpc_unsecure.so
libgrpc++_cronet.so                libgrpc++_unsecure.so
libgrpc++_cronet.so.1              libgrpc++_unsecure.so.1
libgrpc++_cronet.so.1.20.0         libgrpc++_unsecure.so.1.20.0
libgrpc_cronet.so.7                libgrpc_unsecure.so.7
libgrpc_cronet.so.7.0.0            libgrpc_unsecure.so.7.0.0
libgrpc++_error_details.a          libprotobuf.a
libgrpc++_error_details.so         libprotobuf.la
libgrpc++_error_details.so.1       libprotobuf-lite.a
libgrpc++_error_details.so.1.20.0  libprotobuf-lite.la
libgrpcpp_channelz.a               libprotoc.a
libgrpcpp_channelz.so              libprotoc.la
libgrpcpp_channelz.so.1            pkgconfig
libgrpcpp_channelz.so.1.20.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（5）基本环境变量配置&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export LD_LIBRARY_PATH=/usr/local/grpc/third_party/protobuf/src/.libs/:$LD_LIBRARY_PATH

export PATH=/usr/local/grpc/bins/opt/:$PATH
export PATH=/usr/local/grpc/bins/opt/protobuf:$PATH
export PKG_CONFIG_PATH=/usr/local/grpc/libs/opt/pkgconfig
export PKG_CONFIG_PATH=/usr/local/grpc/third_party/protobuf/:$PKG_CONFIG_PATH
export CPLUS_INCLUDE_PATH=/usr/local/grpc/third_party/protobuf/src/
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/grpc/third_party/protobuf/conformance/third_party/jsoncpp/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;protobuf --version&lt;/code&gt;，看看配置有没有生效，并且看下pb版本。我用的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.7.0&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[root@host:/data/home/wind/grpc/src/cpp]$ protoc --version
libprotoc 3.7.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（6）demo编译测试
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./grpc/examples/cpp/helloworld&lt;/code&gt;目录下可以进行demo测试:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[root@host:/usr/local/grpc/examples/cpp/helloworld]$ make clean
rm -f *.o *.pb.cc *.pb.h greeter_client greeter_server greeter_async_client greeter_async_client2 greeter_async_server

[root@host:/usr/local/grpc/examples/cpp/helloworld]$ make -j8
protoc -I ../../protos --cpp_out=. ../../protos/helloworld.proto
protoc -I ../../protos --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` ../../protos/helloworld.proto
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o greeter_client.o greeter_client.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o greeter_server.o greeter_server.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o greeter_async_client.o greeter_async_client.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o greeter_async_client2.o greeter_async_client2.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o greeter_async_server.o greeter_async_server.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o helloworld.pb.o helloworld.pb.cc
g++ -std=c++11 `pkg-config --cflags protobuf grpc`  -c -o helloworld.grpc.pb.o helloworld.grpc.pb.cc
g++ helloworld.pb.o helloworld.grpc.pb.o greeter_client.o -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc` -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl -o greeter_client
g++ helloworld.pb.o helloworld.grpc.pb.o greeter_server.o -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc` -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl -o greeter_server
g++ helloworld.pb.o helloworld.grpc.pb.o greeter_async_client.o -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc` -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl -o greeter_async_client
g++ helloworld.pb.o helloworld.grpc.pb.o greeter_async_client2.o -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc` -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl -o greeter_async_client2
g++ helloworld.pb.o helloworld.grpc.pb.o greeter_async_server.o -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc` -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl -o greeter_async_server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(7) 运行测试&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[root@host:/usr/local/grpc/examples/cpp/helloworld]$ ./greeter_server 
Server listening on 0.0.0.0:50051

[root@host:/usr/local/grpc/examples/cpp/helloworld]$ ./greeter_client 
Greeter received: Hello world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有以上回显，表示编译和环境已经standby！&lt;/p&gt;

</description>
        <pubDate>Mon, 25 May 2020 19:51:19 +0000</pubDate>
        <link>http://windblog.cn/grpc/2020/05/25/grpc-dev/</link>
        <guid isPermaLink="true">http://windblog.cn/grpc/2020/05/25/grpc-dev/</guid>
        
        <category>protobuf</category>
        
        <category>grpc</category>
        
        <category>c++</category>
        
        
        <category>grpc</category>
        
      </item>
    
      <item>
        <title>聊聊Java的GC机制</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLHMQ.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用Java快一年时间了，从最早大学时候对Java的憎恶，到逐渐接受，到工作中体会到了Java开发的各种便捷与福利，这确实是一门不错的开发语言。不仅是Intellij开发Java程序的爽快，还有无需手动管理内存的便捷、Maven管理依赖的整洁、SpringCloud(SpringBoot)大礼包的规整等等。&lt;/p&gt;

&lt;p&gt;所以，作为一个有追求的Java程序员，深入底层掌握GC（垃圾回收）的机制，应该算是必备的技能了。本文即我在学习过程中的一些个人观点以及心得，不正之处敬请指正。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLOZn.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;一-jvm的运行数据区&quot;&gt;一、 JVM的运行数据区&lt;/h3&gt;
&lt;p&gt;首先我简单来画一张JVM的结构原理图： 
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLXaq.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们重点关注JVM在运行时的数据区，你可以看到在程序运行时，大致有5个部分：&lt;/p&gt;

&lt;h4 id=&quot;1方法区&quot;&gt;（1）方法区&lt;/h4&gt;
&lt;p&gt;不止是存“方法”，而是存储整个class文件的信息，JVM运行时，类加载器子系统将会提取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class文件里面的类信息&lt;/code&gt;，并将其存放在方法区中。例如类的名称、类的类型（枚举、类、接口）、字段、方法等等。&lt;/p&gt;

&lt;h4 id=&quot;2堆heap&quot;&gt;（2）堆（Heap）&lt;/h4&gt;
&lt;p&gt;熟悉c/c++编程的同学们应该相当熟悉Heap了，而对于Java而言，每个应用都唯一对应一个JVM实例，而每一个JVM实例唯一对应一个堆。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;堆主要包括关键字new的对象实例、this指针，或者数组都放在堆中，并由应用所有的线程共享&lt;/code&gt;。堆由JVM的自动内存管理机制所管理，名为垃圾回收——GC（garbage collection）。&lt;/p&gt;

&lt;h4 id=&quot;3栈stack&quot;&gt;（3）栈（Stack）&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;操作系统内核为某个进程或者线程建立的存储区域&lt;/code&gt;,它保存着一个线程中的方法的调用状态，它具有先进后出的特性。在栈中的数据大小与生命周期严格来说都是确定的，例如在一个函数中声明的int变量便是存储在stack中，它的大小是固定的，在函数退出后它的生命周期也从此结束。在栈中，每一个方法对应一个栈帧，JVM会对Java栈执行两种操作： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;压栈和出栈&lt;/code&gt;。 这两种操作在执行时都是以栈帧为单位的。还有一些常量、静态变量、即时编译器编译后的代码等数据。&lt;/p&gt;

&lt;h4 id=&quot;4pc寄存器&quot;&gt;（4）PC寄存器&lt;/h4&gt;
&lt;p&gt;pc寄存器用于存放一条指令的地址，每一个线程都有一个PC寄存器。&lt;/p&gt;

&lt;h4 id=&quot;5本地方法栈&quot;&gt;（5）本地方法栈&lt;/h4&gt;
&lt;p&gt;用来调用其他语言的本地方法，例如C/C++写的本地代码， 这些方法在本地方法栈中执行，而不会在Java栈中执行。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;二初识gc&quot;&gt;二、初识GC&lt;/h3&gt;

&lt;p&gt;自动垃圾回收机制，简单来说就是寻找Java堆中的无用对象。打个比方：你的房间是JVM的内存，你在房间里生活会制造垃圾和脏乱，而你妈就是在对你的房间GC。你妈每时每刻都觉得你房间很脏乱，不时要把你赶出门打扫房间，如果你妈一直在房间打扫，那么这个过程你无法继续在房间打游戏吃泡面。但如果你一直在房间，你的房间早晚要变成一个无法居住的猪窝。&lt;/p&gt;

&lt;p&gt;那么，怎么样回收垃圾比较好呢？我们大致可以想出下面的思路：&lt;/p&gt;

&lt;h4 id=&quot;1-marking&quot;&gt;(1) Marking&lt;/h4&gt;
&lt;p&gt;首先，所有堆中的对象都会被扫描一遍：我们总得知道哪些是垃圾，哪些是有用的物品吧。因为垃圾实在太多了，所以，你妈会把所有的要扔掉的东西都找出来并打上一个标签，到了时机成熟时回头来一起处理，这样她就能处理你不需要的废物、旧家具，而不是把你喜欢的衣服或者身份证之类的东西扔掉。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLbrj.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;2-normal-deletion&quot;&gt;(2) Normal Deletion&lt;/h4&gt;
&lt;p&gt;垃圾收集器将清除掉标记的对象：你妈已经整理了一部分杂物（或者已全部整理完），然后会将他们直接拎出去倒掉。你很开心房间又可以继续接受蹂躏了。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLTxg.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;3-deletion-with-compacting&quot;&gt;(3) Deletion with Compacting&lt;/h4&gt;
&lt;p&gt;压缩清除的方法：我们知道，内存有空闲，并不代表着我们就能使用它，例如我们要分配数组这种一段连续空间，假如内存中碎片较多，肯定是行不通的。正如房间可能需要再放一个新的床，但是扔掉旧衣柜后，原来的位置并不能放得下新床，所以需要进行空间压缩，把剩下的家具和物品位置并到一起，这样就能腾出更多的空间啦。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLqqs.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有趣的是，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JVM并不是使用类似于objective-c的ARC（Automatic Reference Counting）的方式来引用计数对象&lt;/code&gt;，而是使用了叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;根搜索算法(GC Root)&lt;/code&gt;的方法，基本思想就是选定一些对象作为GC Roots，并组成根对象集合，然后从这些作为GC Roots的对象作为起始点，搜索所走过的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;引用链（Reference Chain）&lt;/code&gt;。如果目标对象到GC Roots是连接着的，我们则称该目标对象是可达的，如果目标对象不可达，则说明目标对象是可以被回收的对象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLxiV.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GC Root使用的算法是相当复杂的，你不必记住里面的所有细节。但是你要知道的一点就是，可以作为GC Root的对象可以主要分为四种：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1、JVM栈中引用的对象； 
2、方法区中，静态属性引用的对象； 
3、方法区中，常量引用的对象； 
4、本地方法栈中，JNI（即Native方法）引用的对象； 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在JDK1.2之后，Java将引用分为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;强引用、软引用、弱引用、虚引用&lt;/code&gt;4种，这4种引用强度依次减弱。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;三分代与gc机制&quot;&gt;三、分代与GC机制&lt;/h3&gt;

&lt;p&gt;嗯，听起来这样就可以了？但是实际情况下，很不幸，在JVM中绝大部分对象都是英年早逝的，在编码时大部分堆中的内存都是短暂临时分配的，所以无论是效率还是开销方面，按上面那样进行GC往往是无法满足我们需求的。而且，实际上随着分配的对象增多，GC的时间与开销将会放大。所以，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JVM的内存被分为了三个主要部分：新生代，老年代和永久代&lt;/code&gt;。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLjI0.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;1新生代&quot;&gt;（1）新生代&lt;/h4&gt;
&lt;p&gt;所有新产生的对象全部都在新生代中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Eden区&lt;/code&gt;保存最新的对象，有两个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Survivor Space&lt;/code&gt;——S1和S0，三个区域的比例大致为8:1:1。当新生代的Eden区满了，将触发一次GC，我们把新生代中的GC称为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minor garbage collections&lt;/code&gt;。minor garbage collections是一种&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stop the world&lt;/code&gt;事件，比如你妈在打扫时，会把你赶出去，而不是你一边扔垃圾她一边打扫。&lt;/p&gt;

&lt;p&gt;我们来看下对象在堆中的分配过程，首先有新的对象进入时，默认放入新生代的Eden区，S区都是默认为空的。下面对象的数字代表经历了多少次GC，也就是对象的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;年龄&lt;/code&gt;。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmLzGT.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当eden区满了，触发minor garbage collections，这时还有被引用的对象，就会被分配到S0区域，剩下没有被引用的对象就都会被清除。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOSRU.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再一次GC时，S0区的部分对象很可能会出现没有引用的，被引用的对象以及S0中的存活对象，会被一起移动到S1中。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eden和S0中的未引用对象会被全部清除&lt;/code&gt;。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOpzF.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下来就是无限循环上面的步骤了，当新生代中存活的对象超过了一定的【年龄】，会被分配至老年代的Tenured区中。这个年龄可以通过参数MaxTenuringThreshold设定，默认值为15，图中的例子为8次。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOPsJ.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;新生代管理内存采用的算法为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC复制算法(Copying GC)&lt;/code&gt;，也叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-复制&lt;/code&gt;法，原理是把内存分为两个空间:一个From空间，一个To空间，对象一开始只在From空间分配，To空间是空闲的。GC时把存活的对象从From空间复制粘贴到To空间，之后把To空间变成新的From空间，原来的From空间变成To空间。&lt;/p&gt;

&lt;p&gt;首先标记不可达对象：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOCM4.jpg&quot; alt=&quot;Thumper&quot; /&gt;
然后移动存活的对象到to区，并保证他们在内存中连续：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOiL9.jpg&quot; alt=&quot;Thumper&quot; /&gt;
清扫垃圾：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOkZR.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到上图操作后内存几乎都是连续的，所以它的效率是非常高的，但是相对的吞吐量会较大。并且，把内存一分为二，占用了将近一半的可用内存。用一段伪代码来实现大致为下：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;copying&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;free&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_start&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// $free表示To区占用偏移量，每复制成功一个对象obj, &lt;/span&gt;
                          &lt;span class=&quot;c1&quot;&gt;// $free向前移动size(obj)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;roots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 复制成功后返回新的引用&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// GC完成后交互From区与To区的指针&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2老年代&quot;&gt;（2）老年代&lt;/h4&gt;
&lt;p&gt;老年代用来存储活时间较长的对象，老年代区域的GC是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;major garbage collection&lt;/code&gt;，老年代中的内存不够时，就会触发一次。这也是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stop the world&lt;/code&gt;事件，但是看名字就知道，这个回收过程会相当慢，因为这包括了对新生代和老年代所有对象的回收，也叫Full GC。&lt;/p&gt;

&lt;p&gt;老年代管理内存最早采用的算法为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-清理&lt;/code&gt;算法，这个算法很好理解，结合GC Root的定义，我们会把所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;不可达&lt;/code&gt;的对象全部标记进行清除。&lt;/p&gt;

&lt;p&gt;在清除前，黄色的为不可达对象：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOAd1.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在清除后，全部都变成可达对象：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOEIx.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么，这个算法的劣势很好理解：对，会在标记清除的过程中产生大量的内存碎片，Java在分配内存时通常是按连续内存分配，这样我们会浪费很多内存。所以，现在的JVM GC在老年代都是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-压缩清除&lt;/code&gt;方法，将上图在清除后的内存进行整理和压缩，以保证内存连续，虽然这个算法的效率是三种算法里最低的。&lt;/p&gt;

&lt;h4 id=&quot;3永久代&quot;&gt;（3）永久代&lt;/h4&gt;

&lt;p&gt;永久代位于方法区，主要存放元数据，例如Class、Method的元信息，与GC要回收的对象其实关系并不是很大，我们可以几乎忽略其对GC的影响。除了Java HotSpot这种较新的虚拟机技术，会回收无用的常量和的类，以免大量运用反射这类频繁自定义ClassLoader的操作时方法区溢出。&lt;/p&gt;

&lt;h3 id=&quot;四gc收集器与优化&quot;&gt;四、GC收集器与优化&lt;/h3&gt;

&lt;p&gt;一般而言，GC不应该成为影响系统性能的瓶颈，我们在评估GC收集器的优劣时一般考虑以下几点：&lt;/p&gt;
&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（1）吞吐量
（2）GC开销
（3）暂停时间
（4）GC频率
（5）堆空间
（6）对象生命周期

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以针对不同的GC收集器，我们要对应我们的应用场景来进行选择和调优，回顾GC的历史，主要有4种GC收集器:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serial、Parallel、CMS和G1&lt;/code&gt;。
&lt;img src=&quot;https://cdn.nlark.com/yuque/0/2019/jpeg/295504/1553652649859-52e9f1bd-12be-4901-bc2f-35015f6f1865.jpeg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;1serial&quot;&gt;（1）Serial&lt;/h4&gt;
&lt;p&gt;Serial收集器使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-复制&lt;/code&gt;的算法，可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:+UseSerialGC&lt;/code&gt;使用单线程的串行收集器。但是在GC进行时，程序会进入长时间的暂停时间，一般不太建议使用。&lt;/p&gt;

&lt;h4 id=&quot;2parallel&quot;&gt;（2）Parallel&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:+UseParallelGC -XX:+UseParallelOldGC&lt;/code&gt; 
Parallel也使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-复制&lt;/code&gt;的算法，但是我们称之为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;吞吐量优先的收集器&lt;/code&gt;，因为Parallel最主要的优势在于并行使用多线程去完成垃圾清理工作，这样可以充分利用多核的特性，大幅降低gc时间。
当你的程序场景吞吐量较大，例如消息队列这种应用，需要保证有效利用CPU资源，可以忍受一定的停顿时间，可以优先考虑这种方式。&lt;/p&gt;

&lt;h4 id=&quot;3cmsconcurrent-mark-sweep&quot;&gt;（3）CMS(Concurrent Mark Sweep)&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:+UseParNewGC -XX:+UseConcMarkSweepGC&lt;/code&gt; 
CMS使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-清除&lt;/code&gt;的算法，当应用尤其重视服务器的响应速度（比如Apiserver），希望系统停顿时间最短，以给用户带来较好的体验，那么可以选择CMS。CMS收集器在Minor GC时会暂停所有的应用线程，并以多线程的方式进行垃圾回收。在Full GC时不暂停应用线程，而是使用若干个后台线程定期的对老年代空间进行扫描，及时回收其中不再使用的对象。&lt;/p&gt;

&lt;h4 id=&quot;4g1garbage-first&quot;&gt;（4）G1（Garbage First）&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:+UseG1GC&lt;/code&gt; 
在堆比较大的时候，如果full gc频繁，会导致停顿，并且调用方阻塞、超时、甚至雪崩的情况出现，所以降低full gc的发生频率和需要时间，非常有必要。G1的诞生正是为了降低Full GC的次数，而相较于CMS，G1使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;标记-压缩清除&lt;/code&gt;算法，这可以大大降低较大内存（4GB以上）GC时产生的内存碎片。&lt;/p&gt;

&lt;p&gt;G1提供了两种GC模式，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Young GC&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mixed GC&lt;/code&gt;，两种都是Stop The World(STW)的。Young GC主要是对Eden区进行GC，Mix GC不仅进行正常的新生代垃圾收集，同时也回收部分后台扫描线程标记的老年代分区。&lt;/p&gt;

&lt;p&gt;另外有趣的一点，G1将新生代、老年代的物理空间划分取消了，而是将堆划分为若干个区域（region），每个大小都为2的倍数且大小全部一致，最多有2000个。除此之外，G1专门划分了一个Humongous区，它用来专门存放超过一个region 50%大小的巨型对象。在正常的处理过程中，对象从一个区域复制到另外一个区域，同时也完成了堆的压缩。
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmOZi6.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;5常用参数&quot;&gt;（5）常用参数&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-XX:+UseSerialGC：在新生代和老年代使用串行收集器

-XX:+UseParNewGC：在新生代使用并行收集器

-XX:+UseParallelGC ：新生代使用并行回收收集器，更加关注吞吐量

-XX:+UseParallelOldGC：老年代使用并行回收收集器

-XX:ParallelGCThreads：设置用于垃圾回收的线程数

-XX:+UseConcMarkSweepGC：新生代使用并行收集器，老年代使用CMS+串行收集器

-XX:ParallelCMSThreads：设定CMS的线程数量

-XX:+UseG1GC：启用G1垃圾回收器
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;6调优&quot;&gt;（6）调优&lt;/h4&gt;
&lt;p&gt;针对调优这块，推荐可以看下美团点评的这个GC实战调优案例：&lt;a href=&quot;https://tech.meituan.com/2017/12/29/jvm-optimize.html&quot;&gt;《从实际案例聊聊Java应用的GC优化》&lt;/a&gt;。里面详细讲述了Minor GC频繁、CMS高峰期性能下降、Stop-The-World三个应用场景的问题分析过程，以及在应用程序编码优化空间不大的情况下，如何通过GC优化令系统性能达到一个质的提升。&lt;/p&gt;

</description>
        <pubDate>Wed, 27 Mar 2019 20:12:05 +0000</pubDate>
        <link>http://windblog.cn/java/2019/03/27/java-gc-learning/</link>
        <guid isPermaLink="true">http://windblog.cn/java/2019/03/27/java-gc-learning/</guid>
        
        <category>Java</category>
        
        
        <category>Java</category>
        
      </item>
    
      <item>
        <title>深入Mysql之（二）事务的隔离性</title>
        <description>&lt;h3 id=&quot;一事务transaction&quot;&gt;一、事务（Transaction）&lt;/h3&gt;
&lt;p&gt;上一部分介绍了索引在Mysql中的应用以及一些基本的原理，本文我们将就事务的隔离性进行深究。说实话，在我深究Mysql的知识之前，我确实会觉得，这不过就是个很简单的系统，只要会大概用一下就好，但是在踩了许多坑后发现，Mysql其实有很多东西可以细细品味和深究。&lt;/p&gt;

&lt;p&gt;MySQL 事务主要用于处理操作量大，复杂度高的数据。且只有在使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InnoDB引擎&lt;/code&gt;的表时，对其进行delete、update或者insert操作，才会涉及事务。&lt;/p&gt;

&lt;p&gt;我们来回顾一下事务的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACID四大条件&lt;/code&gt;：原子性（Atomicity，或称不可分割性）、一致性（Consistency）、隔离性（Isolation，又称独立性）、持久性（Durability）。&lt;/p&gt;

&lt;p&gt;原子性：一个事务（transaction）中的所有操作，要么全部完成，要么全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被回滚（Rollback）到事务开始前的状态，就像这个事务从来没有执行过一样。&lt;/p&gt;

&lt;p&gt;一致性：在事务开始之前和事务结束以后，数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则，这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。&lt;/p&gt;

&lt;p&gt;隔离性：数据库允许多个并发事务同时对其数据进行读写和修改的能力，隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别，包括读未提交（Read uncommitted）、读提交（read committed）、可重复读（repeatable read）和串行化（Serializable）。&lt;/p&gt;

&lt;p&gt;持久性：事务处理结束后，对数据的修改就是永久的，即便系统故障也不会丢失。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;二命令行&quot;&gt;二、命令行&lt;/h3&gt;

&lt;p&gt;在Mysql中，默认可以使用下面的命令查看事务的隔离性。&lt;/p&gt;

&lt;p&gt;会话事务的隔离级别：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select @@tx_isolation;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tew4iD.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;系统的隔离级别：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select @@global.tx_isolation;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;你可以设置当前会话隔离级别：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set session transaction isolatin level repeatable read;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;或者可以设置系统隔离级别：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set global transaction isolation level repeatable read;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;除此之外，你还可以查看当前是否是自动提交事务请求的：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show variables like '%autocommit%';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;你可以在开启事务时设置：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set autocommit=off&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start transaction&lt;/code&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;三隔离现象&quot;&gt;三、隔离现象&lt;/h3&gt;
&lt;p&gt;在事务的并发操作中，难免会出现以下的几种现象：&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;脏读（Dirty Read）&lt;/code&gt;：指一个线程中的事务读取到了另外一个线程中未提交的数据。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;不可重复读（Norepeatable Read）&lt;/code&gt;：指一个线程中的事务读取到了另外一个线程中提交的update的数据。例如一个事务读进一条记录，另一个事务更改了这条记录并提交完毕，这时候第一个事务再次读这条记录时，它已经改变了。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;幻读（Phantom Read）&lt;/code&gt;：一个事务用Where子句来检索一个表的数据，另一个事务插入一条新的记录，并且符合Where条件，这样，第一个事务用同一个where条件来检索数据后，就会多出一条记录，就像出现了“幻觉”。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;四隔离级别&quot;&gt;四、隔离级别&lt;/h3&gt;
&lt;h4 id=&quot;1未提交读read-uncommitted&quot;&gt;（1）未提交读（Read uncommitted）&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;所有事务都可以看到其他未提交事务的执行结果&lt;/code&gt;。这是隔离级别最低的一个，但是相对的它的并发性能高。相应的会出现脏读、不可重复读、幻读三种现象。&lt;/p&gt;

&lt;h4 id=&quot;2已提交读read-committed&quot;&gt;（2）已提交读（Read committed）&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;一个事务只能看见已经提交的事务所做的修改&lt;/code&gt;。这个级别会锁定当前正在阅读的行，会出现不可重复读、幻读问题。&lt;/p&gt;

&lt;h4 id=&quot;3-可重复读repeatable-read&quot;&gt;(3) 可重复读（Repeatable Read）&lt;/h4&gt;
&lt;p&gt;作为MySQL的默认事务隔离级别，这个场景保证了同个事务的多个实例并发请求读取数据时，数据行一定是相同的。但是无法避免出现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;幻读&lt;/code&gt;现象。&lt;/p&gt;

&lt;h4 id=&quot;4-串行化serializable&quot;&gt;(4) 串行化（Serializable）&lt;/h4&gt;
&lt;p&gt;虽然解决了幻读问题，但是最高的隔离级别带来的一定是惨重的代价——大量的超时现象和锁竞争。举个例子：假如AB两个事务都操作到同一数据行，A首先锁定数据行，B只有先等拿到共享锁的事务A做了提交，其他事务才能进行修改操作。&lt;/p&gt;

&lt;p&gt;总而言之，我们可以优先考虑将数据库系统的隔离级别设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read Committed&lt;/code&gt;。这能够在避免脏读的同时，保证并发性能。在应用程序端主动采用悲观锁或乐观锁来进行事务控制，而不是一味依赖数据库的隔离性进行过粗粒度的操作。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;五锁与并发&quot;&gt;五、锁与并发&lt;/h3&gt;
&lt;p&gt;首先要知道，为什么我们需要锁？因为有并发场景，而并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性，以及数据库的统一性。&lt;/p&gt;

&lt;p&gt;乐观并发控制（Optimistic Concurrency Control）和悲观并发控制(Pessimistic Concurrency Control)就是并发控制主要采用的技术手段。定义太多难理解，下面只会简述其中的奥妙。&lt;/p&gt;

&lt;h4 id=&quot;1悲观锁&quot;&gt;（1）悲观锁&lt;/h4&gt;
&lt;p&gt;悲观锁的核心就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;阻止一个事务以影响其他用户的方式来修改数据。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;还是用一串SQL举个栗子，以一个people表和student表为例子，在people表中有一个status字段。现在我们需要修改这个字段，我们重点关注people这个表：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//0.开始事务
start transaction;

//1.查询出学生的信息，for update开启排他锁
select status from people where id = 1 for update;

//2.根据people信息生成学生表（无需关注）
insert into student (name, p_id) values (#{name}, 1);

//3.修改所有人的status为2
update people set status = 2 where id = 1;

//4.提交事务
commit;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面我们引用到了数据库带有的排他锁，下面我们看下它的工作流程： 
（1）关闭autocommit； 
（2）对当前修改记录尝试加上排他锁； 
（3）如果成功，则修改成功，事务结束即解锁； 
（4）反之等待或者处理异常； 
（5）当其他事务企图修改本记录，处理与上面过程一致。&lt;/p&gt;

&lt;h4 id=&quot;2乐观锁&quot;&gt;（2）乐观锁&lt;/h4&gt;
&lt;p&gt;核心：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;各事务能够在不产生锁的情况下，引用版本标识处理各自影响的那部分数据。&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1.查询出people信息
select id, status, name, version from people where id = 1;

2.生成学生信息（无需关注）
insert into student (name, p_id) values (#{name}, 1);

3.修改status为2
update people set status=2, version=version+1 where id=#{id} and version=#{version};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们看到并没有使用数据库自带的锁功能，而是通过业务的逻辑进行了锁的控制，上面流程大致为：&lt;br /&gt;
（1）在设计表时，设计一个版本（时间/标识）字段，用来描述当前记录是什么版本;&lt;br /&gt;
（2）在读取记录时，由于Repeatable Read的原理，这个标识一定为一致的;&lt;br /&gt;
（3）提交更新的时候，判断对应记录版本信息与第一次取出的版本标识是否一致;&lt;br /&gt;
（4）若一致，则修改，反之则失败，发起重试或抛出异常（过期数据）。&lt;/p&gt;

&lt;h4 id=&quot;3cas算法&quot;&gt;（3）CAS算法&lt;/h4&gt;
&lt;p&gt;说到这里，对于一个合格的后台开发同学，不得不提及一些课外知识：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAS算法原理&lt;/code&gt;。 上面的乐观锁使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;版本控制&lt;/code&gt;的思想，那么什么是CAS呢？ 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compare and swap（比较&amp;amp;交换）&lt;/code&gt;是一种无锁编程的算法，即不使用锁的情况下实现多线程之间的变量同步，也就是在没有线程被阻塞的情况下实现变量的同步，所以也叫非阻塞同步（Non-blocking Synchronization）。CAS算法涉及到三个操作数：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;需要读写的内存值V、 进行比较的值A、拟写入的新值B&lt;/code&gt;。当且仅当V的值等于A时，CAS通过原子方式用新值B来更新V的值，否则不会执行任何操作，即一个不停的自旋操作，当然它如果不成功就一直循环执行直到成功，如果长时间不成功，会给CPU带来非常大的执行开销。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;对于资源竞争较少的情况，CAS基于硬件实现，不需要进入内核，
不需要切换线程，可以使用到这个场景中。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Java语言中，对于资源竞争严重（线程冲突严重）的情况，
CAS自旋的概率会比较大，会浪费更多的CPU资源，另一个关键字&lt;/code&gt;synchronized&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;同步锁适用于这种写比较多的情况。&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&quot;4aba问题&quot;&gt;（4）ABA问题&lt;/h4&gt;
&lt;p&gt;上面的问题看似都完美解决了？等等，还有一个乐观锁很经典的问题——&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ABA问题。&lt;/code&gt;&lt;br /&gt;
让我用一个例子来稍微解释下，假设有一个多线程的银行存取款系统，当前主线程有三个线程在运行。其中线程1和3是专门负责并发执行扣款业务的，而线程2是执行监听是否有人汇款的操作。现在小明有100元，他准备取出50元自己花，而这时妈妈给他的账号中打了50元，我们来看看这个乐观锁的场景会有啥问题，下面是我自己画的丑图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tewIRH.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有没有稍微理解上面图中带来的问题是什么？线程3和线程1在进行扣款前，都得知了账户中有100元，他们的预期都是将余额改为50，线程1抢占了任务，所以线程3被block了。线程3被再次唤醒后，查询到这时余额为100。但是对于线程3本身而言并不知道&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;已进行过了扣款，而有人又汇款了50元过来&lt;/code&gt;这件中间发生的事，就这样，线程3继续执行扣款，将余额改为50，结束线程工作。最终，有50元就从账户中平白无故消失了。&lt;/p&gt;

&lt;p&gt;这就是ABA问题——&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;一个线程在处理V这个变量时它的值为A，中途可能被挂起过，那么当再次访问V时，可能这个值中途已变为了B，但是最后又变成了A&lt;/code&gt;。对于多个同时修改变量的线程本身，是无法具体得知这个变化的，那么在进行操作时，可能会导致各种各样的问题。在一些电商、秒杀的并发场景中，经常可能碰到类似的问题。&lt;/p&gt;

&lt;p&gt;因为业务逻辑存在回退的可能性，那么如果加入一个与业务逻辑不相关的属性，比如在一个数据中加入版本号，约定只要修改了数据该版本号就会递增，且不会回退，那么ABA问题就解决了。JDK的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atomic&lt;/code&gt;包里，提供了一个类&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AtomicStampedReference&lt;/code&gt;，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareAndSet&lt;/code&gt;方法可以避免这个问题，我们看下它的定义：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 *expectedReference - 该引用的预期值
 *newReference - 该引用的新值
 *expectedStamp - 该标志的预期值
 *newStamp - 该标志的新值
 */
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair&amp;lt;V&amp;gt; current = pair;
        return
            expectedReference == current.reference &amp;amp;&amp;amp;
            expectedStamp == current.stamp &amp;amp;&amp;amp;
            ((newReference == current.reference &amp;amp;&amp;amp;
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过对引用参数设置标志，如果当前引用等于预期引用，并且当前标志等于预期标志，则用原子方式将该引用和该标志的值设置为给定的更新值。&lt;/p&gt;

&lt;h3 id=&quot;六尾声&quot;&gt;六、尾声&lt;/h3&gt;
&lt;p&gt;其实上文也是在使用和运营Mysql的过程中，有了许多了体会，如有错误之处欢迎评论或邮件我指正。预计后面会基于本文的内容，谈谈死锁的产生与分析思路。&lt;/p&gt;
</description>
        <pubDate>Thu, 14 Mar 2019 20:12:05 +0000</pubDate>
        <link>http://windblog.cn/mysql/2019/03/14/mysql-deep-learning-02/</link>
        <guid isPermaLink="true">http://windblog.cn/mysql/2019/03/14/mysql-deep-learning-02/</guid>
        
        <category>mysql</category>
        
        
        <category>mysql</category>
        
      </item>
    
      <item>
        <title>深入Mysql之（一）你真的了解索引？</title>
        <description>&lt;h3 id=&quot;一索引的原理&quot;&gt;一、索引的原理&lt;/h3&gt;
&lt;p&gt;Mysql我相信大部分人都接触过，但是很多人刚刚开始使用Mysql时，都是随便建表，一堆查询条件瞎搞的。代码日久未生情，却生出了一堆问题，过度使用Mysql与不合理使用索引，往往会导致慢查询等性能问题。 能区分一个低级与高级程序员的关键，就是看他是否能够掌握一门软件的原理，从而合理分析并使用它。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;查询&lt;/code&gt;应该是CRUD中最为频繁的操作，如果是你会怎么来选择查询算法？顺序查询显然是不现实的，二分查找（binary search）、二叉树查找（binary tree search）也许是不错的选择，但是我们要知道，不同的查询算法，也是要对应不同的数据结构的，尚且不可能用一个简单的查找算法来实现，这样是远远不够的。 
所以我们需要设计一个优秀的数据结构，来实现高效的查询算法，而这个数据结构就是我们所说的索引（Index），即用来帮助MySQL高效获取数据的数据结构。&lt;/p&gt;

&lt;h3 id=&quot;二聊聊b树&quot;&gt;二、聊聊B+树&lt;/h3&gt;
&lt;p&gt;目前大部分数据库系统及文件系统，都是采用B-Tree或其变种B+Tree作为索引结构。下图简单展示了数据库系统的索引结构：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedf3j.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在B+树的基础上，增加了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;顺序访问指针&lt;/code&gt;。那么为什么我们要用这个数据结构呢，为什么不是用红黑树？&lt;br /&gt;
（1）索引本身也是很大的，需要存在磁盘中，那么我们要做的就是减少磁盘I/O次数；&lt;br /&gt;
（2）根据B树的定义，可知检索一次最多需要访问h个节点。主存和磁盘是以页为单位交换数据，这样的话，我们将一个节点的大小设为等于一个页大小，这样每个节点不就只需要一次I/O就可以完全载入吗？&lt;br /&gt;
（3）B+树相对是一个扁平的数据结构，它的深度h一般要远低于红黑树的。&lt;/p&gt;

&lt;p&gt;举个例子，现在假设上面的图中，我们需要输出大于等于18的所有数字：&lt;br /&gt;
（1）首先我们从第一层根节点看到，15——56之间满足上面的情况，我们从这里继续往下走；&lt;br /&gt;
（2）非叶子节点，所以第二层依然为索引。目标数字18是在15到20之间，继续向下；&lt;br /&gt;
（3）到达叶子节点了，第一个节点中的值为15、18，终于找到18了！那么我们可以推测，后面的数字一定大于18了；&lt;br /&gt;
（4）此时无需再次回到上一层，因为顺序访问指针，已经帮我们指向了下一个比18大的数字，剩下要做的，就是一个个输出他们；&lt;/p&gt;

&lt;p&gt;对于上面的索引本质的探究，推荐大家可以参看&lt;a href=&quot;http://blog.codinglabs.org/articles/theory-of-mysql-index.html&quot;&gt;这篇文章&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;三myisam和innodb&quot;&gt;三、MyISAM和InnoDB&lt;/h3&gt;

&lt;p&gt;众所周知，Mysql支持很多种存储引擎，其中MyISAM和InnoDB是使用的最为频繁的。&lt;/p&gt;

&lt;p&gt;（1）MyISAM索引 
MyISAM引擎使用B+树作为索引结构，叶节点的data域存放的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;数据记录的地址&lt;/code&gt;，我们称MyISAM的索引方式也叫做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;非聚集&lt;/code&gt;(Non-clustered)的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/ted4vn.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;（2）InnoDB索引 
InnoDB的数据文件本身就是索引文件，表数据文件本身就是按B树组织的一个索引结构，这棵树的叶节点data域保存了完整的数据记录。还有一点很重要，InnoDB的辅助索引data域，存储相应记录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;主键的值&lt;/code&gt;，而不是地址。换句话讲，在叶子节点上存储的是主键的值，所以一般最好设置主键为一个整型id比较好，并且这也就代表了用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;辅助索引来查询&lt;/code&gt;时，需要先查到主键，再用主键索引查询到值，一共&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;两次&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedhgs.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;四索引优化&quot;&gt;四、索引优化&lt;/h3&gt;
&lt;h4 id=&quot;1聚合索引&quot;&gt;（1）聚合索引&lt;/h4&gt;
&lt;p&gt;我们要先来说下什么是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clustered Index&lt;/code&gt;。聚合（联合）索引，顾名思义就是用一个组把几个列整合为一个索引，而MyISAM是非聚集的，所以我们使用InnoDB来举例。我们建一张表：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sex` varchar(128) NOT NULL DEFAULT 'male',
  `name` varchar(128) NOT NULL DEFAULT '',
  `grade` int(11) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `cluster_index` (`grade`,`name`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过命令来展示表的索引:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show index from student;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedoD0.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其中，cluster_index便是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;聚合索引&lt;/code&gt;。 
下面我将用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain&lt;/code&gt;关键字，结合一个典型的例子来对我们的查询语句进行分析。&lt;/p&gt;

&lt;p&gt;我们使用这个语句尝试一下：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where grade=3 and name=&quot;Tom&quot; and age=10;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedIuq.jpg&quot; alt=&quot;Thumper&quot; /&gt;
效果是相同的，从key这列结果可以看到，cluster_index被引用到了,key_len为138，这里我们把where后面的条件位置随意倒换也不会影响查询的效果，因为Mysql帮我们已经做了查询的优化。&lt;/p&gt;

&lt;p&gt;我们把条件列尝试去掉其中一个试试，去掉name：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where grade=3 and age=10;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedHET.jpg&quot; alt=&quot;Thumper&quot; /&gt;
cluster_index还是被引用到了，但是key_len变成了4，说明了查询只用到了索引的第一列前缀，和单独以grade为条件的效果是一样的。&lt;/p&gt;

&lt;p&gt;去掉age：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where grade=3 and name=&quot;Tom&quot;;&lt;/code&gt;
试试like的效果：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where grade=3 and name like &quot;Tom%&quot; ;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedTbV.jpg&quot; alt=&quot;Thumper&quot; /&gt;
两个效果相同，cluster_index还是被引用到了，但是key_len变成了134，说明了查询用到了索引第一、二列前缀。&lt;/p&gt;

&lt;p&gt;那么，去掉最左边的grade呢：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where age=10 and name=&quot;Tom&quot;;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedbUU.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GG，由于没有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;最左前缀&lt;/code&gt;列被引用到，你可以看到没有索引被引用。最左前缀这个概念就大致是这样了，如果在聚合索引中，sql语句没有引用到最左前缀列，那么这个索引是不会被引用的。&lt;/p&gt;

&lt;p&gt;另外要说明的是，对于范围搜索而言，也是一样的：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where grade&amp;gt;3 and name = &quot;Tom&quot;;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedOC4.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;缺少最左前缀列时：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;explain select * from student where age&amp;gt;3 and name = &quot;Tom&quot;;&lt;/code&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tedq5F.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;2单列索引&quot;&gt;（2）单列索引&lt;/h4&gt;

&lt;p&gt;我们新建一张表：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CREATE TABLE `student2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sex` varchar(128) NOT NULL DEFAULT 'male',
  `name` varchar(128) NOT NULL DEFAULT '',
  `grade` int(11) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `i_grade` (`grade`),
  KEY `i_name` (`name`),
  KEY `i_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个就比较好理解了，其中i_grade、i_name、i_age都可以称作&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;单列索引&lt;/code&gt;。 
那么是否一张表为了提升查询性能效率，就疯狂加索引呢？ ——显然不是，回顾上面的内容，索引本身是文件，要消耗存储空间。对于辅助索引而言更是要存储索引与主键的映射，同时每次插入删除时，我们需要移动索引的排序和位置，久而久之会产生大量的碎片导致空间不连续，降低表的查询性能，MySQL在运行时也要消耗资源维护索引。&lt;/p&gt;

&lt;h4 id=&quot;3优化建议&quot;&gt;（3）优化建议&lt;/h4&gt;
&lt;p&gt;针对一些实际的应用问题，结合踩过的坑，我有下面几点建议：&lt;/p&gt;

&lt;p&gt;a) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;对于InnoDB引擎，一定要使用一个业务无关的自增ID字段作为表的主键&lt;/code&gt;。有很多人喜欢使非自增ID作为主键，这绝对是一个糟糕的设计，结合B+树的特性，在写表时节点需要频繁的移动，缺少递增的主键容易使得索引的结构松散，从而产生大量的碎片和空间浪费； &lt;br /&gt;
b) 针对频繁写操作且数据量较大的表，要定时进行表的Optimize操作：InnoDB使用语句： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER TABLE table_name ENGINE = Innodb&lt;/code&gt;，MyISAM引擎使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optimize table xxx&lt;/code&gt;语句；&lt;br /&gt;
c) 对于数据量较小的表（千百行），不需要索引，只需要做全表扫描；&lt;br /&gt;
d）不要在查询条件中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;加表达式与函数&lt;/code&gt;，这样无法引用索引；&lt;/p&gt;

&lt;p&gt;以上就是我对Mysql索引的一些愚见，如果有任何的问题，欢迎联系我&amp;amp;交流指教。&lt;/p&gt;
</description>
        <pubDate>Mon, 11 Mar 2019 22:42:22 +0000</pubDate>
        <link>http://windblog.cn/mysql/2019/03/11/mysql-deep-learning-01/</link>
        <guid isPermaLink="true">http://windblog.cn/mysql/2019/03/11/mysql-deep-learning-01/</guid>
        
        <category>mysql</category>
        
        
        <category>mysql</category>
        
      </item>
    
      <item>
        <title>写在开头</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2019/10/05/vHTl7YSPow3VgXF.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;small&gt; (个人很喜欢台湾的街道和生活节奏)&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;自从参加工作后，鲜有时间来写点东西了，&lt;a href=&quot;http://github.com/wind3110991&quot;&gt;github&lt;/a&gt;的更新也几乎停滞了（实际上也没什么可以更新的）。&lt;/p&gt;

&lt;p&gt;吉他、音乐、足球、游戏。。。 我的爱好很多，但是能坚持下来的，也没有几个；&lt;/p&gt;

&lt;p&gt;16年，我在浑浑噩噩中度过了人生第一个工作年。&lt;br /&gt;
17年，跟风炒过股票和电子货币，亏了不少。&lt;br /&gt;
18年，我猛然发现身体不行了，工作似乎也经常不在状态。&lt;/p&gt;

&lt;p&gt;深圳的夏天很热。&lt;br /&gt;
但我决定，从今年开始努力健身，我每天跳绳1000个，每周游泳，戒熬夜，戒甜食；&lt;br /&gt;
我恶补专业知识，利用所有工作时间，学习接触不同的东西；&lt;br /&gt;
我把大部分以前买的没用的东西和衣物都扔了，&lt;a href=&quot;https://www.zhihu.com/topic/19585358&quot;&gt;极简主义&lt;/a&gt;的生活方式，深深吸引着我。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teybLj.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;small align=&quot;center&quot;&gt; (losing weight真的需要勇气，三分练七分吃)&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;年底：&lt;br /&gt;
我从2月的67kg到了现在的58kg；&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teyHyQ.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;我发现自己不经意间学了很多专业知识；&lt;br /&gt;
我竟然破天荒的读了6本书，要知道以前我的kindle可是用来做泡面盖的；&lt;br /&gt;
我发现自己还是能有一些积蓄的；&lt;/p&gt;

&lt;p&gt;人，存在即意义，
可以甘于寂寞，但是千万不要甘于平凡，&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/tey7Qg.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;人，总需要一些生活和寄托。&lt;br /&gt;
正如现在，我写下这些对生活思考的些许。&lt;/p&gt;

&lt;p&gt;与君共勉。&lt;/p&gt;

</description>
        <pubDate>Tue, 29 Jan 2019 15:31:19 +0000</pubDate>
        <link>http://windblog.cn/%E6%9D%82%E8%AF%B4/2019/01/29/start-01/</link>
        <guid isPermaLink="true">http://windblog.cn/%E6%9D%82%E8%AF%B4/2019/01/29/start-01/</guid>
        
        <category>杂说</category>
        
        <category>小记</category>
        
        <category>心得</category>
        
        
        <category>杂说</category>
        
      </item>
    
      <item>
        <title>深入浅出c内存管理</title>
        <description>&lt;p&gt;之前看了一篇对c/c++内存管理描述的&lt;a href=&quot;https://www.jianshu.com/p/b2380e47d005&quot;&gt;很不错的文章&lt;/a&gt;，后来想来，自己也其实在这方面研究尚浅，故在此回味一下。&lt;/p&gt;

&lt;p&gt;我们先用一张图来归纳c语言的内存模型：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjE8K.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;一内存管理&quot;&gt;一、内存管理&lt;/h3&gt;

&lt;p&gt;在Linux中，其逻辑地址等于线性地址。因为Linux 所有的段&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;（用户代码段、用户数据段、内核代码段、内核数据段）&lt;/code&gt;的段基线性地址都是从 0x00000000 开始，长度4G，这样线性地址 = 0 + 偏移地址，也就是说逻辑地址等于线性地址了。&lt;/p&gt;

&lt;h4 id=&quot;1栈stack&quot;&gt;（1）栈（stack）&lt;/h4&gt;
&lt;p&gt;什么是栈，它是你的电脑内存的一个特别区域，它用来存储被每一个function（包括main（）方法）创建的临时变量。栈是FILO，就是先进后出原则的结构体。它密切的被CPU管理和充分利用。当一个function退出时，所有它的变量都会从栈中弹出，以后都会永远消失。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjAC6.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;small align=&quot;center&quot;&gt; (我很喜欢用上手枪弹夹的方式来描述这个概念)&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;关于栈，我总结为三点：&lt;br /&gt;
a、栈的生长和伸缩就是函数压入或者推出局部变量。&lt;br /&gt;
b、我们不用自己去管理内存，变量创建和释放都是自动的。&lt;br /&gt;
c、栈中的变量只有在函数创建运行时才会存在。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
int main(int argc, const char * argv[]) {
    int a = 100;
    int b = 100;
    printf(&quot;%p \n&quot;,&amp;amp;a); // 0x7fff5fbff79c
    printf(&quot;%p \n&quot;,&amp;amp;b); // 0x7fff5fbff798
    
    // a 变量的地址 0x7fff5fbff79c 比 b
    变量的地址 0x7fff5fbff798 要大
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;2堆heap&quot;&gt;（2）堆（heap）&lt;/h4&gt;
&lt;p&gt;a、变量可以被全局访问；&lt;br /&gt;
b、没有内存大小限制；&lt;br /&gt;
c、堆内存读出和写入都相对慢,因为它必须使用指针图访问堆内存；&lt;br /&gt;
d、没有高效地使用空间，随着块内存的创建和销毁，内存可能会变成碎片；
e、你必须管理内存（变量的创建和销毁你必须要负责）；&lt;br /&gt;
f、变量大小可以用realloc()调整；&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int a = 0;                  // 全局初始化区
char p1;                    // 全局未初始化区

int main(int argc, const char * argv[]) {
      int b ;                 // 栈
      char s[] = &quot;abc&quot;;       // 栈
      char p2 ;               // 栈
      char p3 = &quot;123456&quot;;     // 123456在常量区，p3在栈上。
      static int c = 0 ;      // 全局（静态）初始化区
      
      p1 = (char )malloc(10); // 分配的10字节的区域就在堆区
      p2 = (char )malloc(20); // 分配的20字节的区域就在堆区
      
      printf(&quot;%p\n&quot;,p1);      // 0xffffffb0
      printf(&quot;%p\n&quot;,p2);      // 0xffffffc0
      
      //p1 变量的地址 0xffffffb0 比 p2 变量的地址 0xffffffc0 要小
      return 0;                
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3bss段&quot;&gt;（3）BSS段&lt;/h4&gt;
&lt;p&gt;BSS是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Block Started by Symbol&lt;/code&gt;的简称，通常是指用来存放程序中未初始化的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;全局变量&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;静态变量&lt;/code&gt;。&lt;/p&gt;

&lt;h4 id=&quot;4数据段&quot;&gt;（4）数据段&lt;/h4&gt;
&lt;p&gt;通常是指用来存放程序中已初始化的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;全局变量&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;静态变量&lt;/code&gt;以及字符串常量&lt;/p&gt;

&lt;h4 id=&quot;5代码段&quot;&gt;（5）代码段&lt;/h4&gt;
&lt;p&gt;通常是指用来存放程序&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;执行代码&lt;/code&gt;的一块内存区域。这部分区域的大小在程序运行前就已经确定。
(代码段、数据段、BSS段在程序编译期间由编译器分配空间，在程序启动时加载，由于未初始化的全局变量存放在BSS段，已初始化的全局变量存放在数据段，所以程序中应该尽量少的使用全局变量以节省程序编译和启动时间；栈和堆在程序运行中由系统分配空间)&lt;/p&gt;

&lt;h4 id=&quot;6关于局部变量&quot;&gt;（6）关于局部变量&lt;/h4&gt;
&lt;p&gt;局部变量存储细节：由于是a、b是临时变量，因此他们的内存空间分配在栈上，栈中内存寻址由高到低，所以 a 变量的地址比 b 变量的地址要大，其次由于是在64位编译环境中，int 型变量占据4个字节的空间，每一个字节由低到高依次对应着8位二进制数，四个8位二进制数就是十进制中的 1 或 2，而变量a、b的地址就是四个字节中最小值的内存地址。
全局变量存储细节：关于全局变量存储在前面介绍内存组成已经说明，这里不再赘述。&lt;/p&gt;

&lt;p&gt;变量的存储类别：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C的存储类别包括4种：auto（自动的）、static（静态的）、register（寄存器的）、extern（外部的）。  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;根据变量的存储类别可以得知其作用域和生命周期。&lt;/p&gt;

&lt;h4 id=&quot;7物理内存与虚拟内存&quot;&gt;（7）物理内存与虚拟内存&lt;/h4&gt;
&lt;p&gt;物理内存就是实实际际存在的内存，程序最终运行的地方。现在的内存管理方法在程序和物理内存之间引入了虚拟内存这个概念，虚拟内存介于程序和物理内存之间，程序只能看见虚拟内存，不能直接访问物理内存，如我们的 malloc、new 等函数开辟的都是虚拟内存空间。每个进程都有自己独立的进程地址空间（虚拟地址），这样就做到了进程隔离。最终都需要将虚拟地址映射到物理地址。内核为每个进程维护不同的页表，不同进程可以虚拟地址一样，但映射后的物理地址不一样&lt;/p&gt;

&lt;h4 id=&quot;8分页机制&quot;&gt;（8）分页机制&lt;/h4&gt;
&lt;p&gt;分页机制就是把内存地址空间分为若干个很小的固定大小的页，Linux 中一般页的大小是 &lt;strong&gt;&lt;em&gt;4KB&lt;/em&gt;&lt;/strong&gt;，我们把进程的地址空间按页分割，把常用的数据和代码页装载在内存中，不常用的代码和数据则保存在磁盘中。内核只是创建虚拟内存（初始化进程控制表中内存相关的链表），实际上并不立即就把虚拟内存对应位置的程序数据和代码拷贝到物理内存中，只是建立好虚拟内存和磁盘文件间的映射，等到运行到对应的程序时，才会通过缺页异常，调用缺页异常处理程序，从磁盘拷贝数据到对应物理内存。&lt;/p&gt;

&lt;p&gt;用一个结构体来实现page，大概如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct page {
	unsigned long flags;        /*存放页的状态*/
	atomic_t _count;        /* 页的引用计数*/
	union {
		atomic_t _mapcount; 
		struct {
			u16 inuse;
			u16 objects;
		};
	};
	union {
		struct {
			unsigned long private;
			struct address_space *mapping;
		};
 
	void *virtual;  /* 页的虚拟地址 Kernel virtual address (NULL if
					not kmapped, ie. highmem) */
	……
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;9heap的内存模型&quot;&gt;（9）Heap的内存模型&lt;/h4&gt;
&lt;p&gt;一般来说，malloc所申请的内存主要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;从heap区域&lt;/code&gt;分配的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjF4x.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;
&lt;center&gt;&lt;small align=&quot;center&quot;&gt; (Heap的基本构成)&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;linux 内核维护一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;break指针&lt;/code&gt;，这个指针指向堆空间的某个地址。从堆起始地址（Heap’s Start）到break之间的地址空间为映射好的（虚拟地址与物理地址的映射，通过MMU实现），可以供进程访问；而从break往上，是未映射的地址空间，如果访问这段空间则程序会报错。&lt;br /&gt;
所以，如果Mapped Region（虚拟内存至物理内存MMP的部分）空间不够时，会调整break指针，扩大映射空间，重新分配内存。而调整break指针的基础API，就是brk/sbrk。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int brk(void *addr);
void *sbrk(intptr_t increment);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;10mmap内存映射的原理&quot;&gt;（10）MMAP内存映射的原理&lt;/h4&gt;

&lt;p&gt;a) 进程启动映射过程，并在虚拟地址空间中为映射创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mapped Region&lt;/code&gt;（虚拟映射区域）;&lt;br /&gt;
b) 调用内核空间的系统调用函数mmap（不同于用户空间函数），实现文件物理地址和进程虚拟地址的映射关系;&lt;br /&gt;
c) 进程发起对这片映射空间的访问，引发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;缺页异常&lt;/code&gt;，实现文件内容到物理内存（主存）的拷贝。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;二c语言内存管理函数&quot;&gt;二、c语言内存管理函数&lt;/h3&gt;

&lt;h4 id=&quot;1malloc&quot;&gt;（1）malloc&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;【函数原型】 void *malloc(size_t __size)  
【参数说明】 size 需要分配的内存空间的大小，单位是字节。  
【返回值类型】 void * 表示未确定类型的指针，分配成功返回指向该内存的地址，失败则返回NULL。C、C++规定，void* 类型可以强制转换为任何其它类型的指针。  
【函数功能】 表示向系统申请分配指定 size 个字节的内存空间。

int *a = malloc(4);  //申请4个字节的空间用于存放一个int类型的值
char *b = malloc(2);  //申请2个字节的空间用于存放一个char类型的值
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2calloc&quot;&gt;（2）calloc&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;【函数原型】 void *calloc(size_t __count, size_t __size)  
【参数说明】 count 表示个数，size 单位个需要分配的内存空间的大小，单位是字节。  
【返回值类型】 void * 表示未确定类型的指针。
【函数功能】 表示向系统申请分配 count 个长度为 size 一共为 count 乘以 size 个字节长度的连续内存空间，并将每一个字节都初始化为 0。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3realloc&quot;&gt;（3）realloc&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;【函数原型】 void *realloc(void *__ptr, size_t __size)  
【参数说明】 ptr 表示需要修改的内存空间的地址，size 表示需要重写分配的内存空间的大小，单位是字节。  
【返回值类型】 void * 表示未确定类型的指针。  
【函数功能】 表示更改已经配置好的内存空间到指定的大小。

char *d = calloc(2, sizeof(char));  //申请2个sizeof(char) 字节的空间
char *f = realloc(d, 5 * sizeof(char));  //将原来变量d指向的2个sizeof(char) 字节的空间更改到5个sizeof(char) 字节的空间并由变量f指向。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;4free&quot;&gt;（4）free&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;【函数原型】 void free(void *)
【参数说明】 void * 表示需要释放的内存空间对应的内存地址。
【返回值类型】 返回值为空。
【函数功能】 表示用来释放已经动态分配的内存空间。free() 可以释放由 malloc()、calloc()、realloc() 分配的内存空间，以便其他程序再次使用。需要注意的是：free() 不会改变 传入的指针的值，调用 free() 后它仍然会指向相同的内存空间，但是此时该内存已无效，不能被使用。所以建议将释放完的指针设置为 NULL。

char *g = malloc(sizeof(char)); //申请sizeof(char)大小内存空间
free(g);      //释放掉g指针指向的内存空间
g = NULL;     //将g指针指向NULL
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;5额外提一下void&quot;&gt;（5）额外提一下void&lt;/h4&gt;

&lt;p&gt;除了free的返回值为空外，其他三个函数的返回值均为void* 类型。void应该理解为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;指向空类型&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;不指向确定&lt;/code&gt;的类型的数据。在将它的值赋给另一个指针变量时由系统对它进行类型转换，使之适合被赋值变量的类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int main(int argc, const char * argv[]) 
{
    int a = 3;                   //定义a为整型变量
    int *p1 = &amp;amp;a;                //p1指向 int 型变量
    char *p2;                    //p2指向 char 型变量
    void *p3;               
    //p3为无类型指针变量
    p3 = (void *)p1; //将p1的值转换为void *类型，然后赋值给p3
    p2 = (char *)p3;    //将p3的值转换为char *类型，然后赋值给p2
    printf(&quot;%d\n&quot;, *p1); //输出a的值 3
    p3 = &amp;amp;a;                    
    printf(&quot;%d&quot;, *p3); //此处报错，p3无指向，不能指向a 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;三关于数组&quot;&gt;三、关于数组&lt;/h3&gt;

&lt;h4 id=&quot;1-数值中存储的元素是从所占用的低地址开始存储的&quot;&gt;(1) 数值中存储的元素，是从所占用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;低地址&lt;/code&gt;开始存储的。&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
    char chars[4] = {'l','o','v','e'};    
    printf(&quot;chars[0] = %p\n&quot;,&amp;amp;chars[0]); //0x7fff5fbff79b
    printf(&quot;chars[1] = %p\n&quot;,&amp;amp;chars[1]); //0x7fff5fbff79c
    printf(&quot;chars[2] = %p\n&quot;,&amp;amp;chars[2]); //0x7fff5fbff79d
    printf(&quot;chars[3] = %p\n&quot;,&amp;amp;chars[3]); //0x7fff5fbff79e
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjZvD.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;2-数组中的元素按照存放顺序依次从低地址到高地址存放但是每个元素中的内容又是按高地址向低地址方向存储&quot;&gt;(2) 数组中的元素按照存放顺序依次从低地址到高地址存放，但是每个元素中的内容又是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;按高地址向低地址&lt;/code&gt;方向存储：&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
    int nums[2] = {5, 6};
    printf(&quot;nums[0] = %p\n&quot;,&amp;amp;nums[0]); // 0x7fff5fbff7a0
    printf(&quot;nums[1] = %p\n&quot;,&amp;amp;nums[1]); // 0x7fff5fbff7a4
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjmKe.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;3-数组在使用过程中遇到的最多的问题可能就是下标越界&quot;&gt;(3) 数组在使用过程中遇到的最多的问题可能就是下标越界&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
    char charsOne[2] = {'a', 'b'};
    char charsTwo[3] = {'c', 'd', 'e'};
    charsTwo[3] = 'f';
    printf(&quot;charsOne[0] = %p\n&quot;,&amp;amp;charsOne[0]); // 0x7fff5fbff79e
    printf(&quot;charsTwo[0] = %p\n&quot;,&amp;amp;charsTwo[0]); // 0x7fff5fbff79b
    printf(&quot;charsOne[0] = %c\n&quot;,charsOne[0]); // f 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/29/tmjnDH.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;结合下标越界示意图看上面的的代码会发现，由于越界设置charsTwo&lt;a href=&quot;https://www.v2ex.com/t/181639#reply21&quot;&gt;3&lt;/a&gt;元素的值，导致变相更改了charsOne[0]的值。
思考：为什么这里改charsTwo，却最后改了charsOne的值呢？（提示：stack FILO)&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;四理解free的工作原理&quot;&gt;四、理解Free的工作原理&lt;/h3&gt;

&lt;p&gt;说实话，我研究本文的内容，其实也是因为这个问题 &lt;a href=&quot;https://www.v2ex.com/t/181639#reply21&quot;&gt;《malloc申请得到的内存后free释放，操作系统会立即收回那块内存吗》&lt;/a&gt; 开始的。&lt;br /&gt;
在研究了很多回答后，我得到的最好答案 &lt;a href=&quot;https://stackoverflow.com/questions/1119134/how-do-malloc-and-free-work&quot;&gt;在这里&lt;/a&gt;，有兴趣可以细读一下。下面我做一个带我理解的简单转述：&lt;/p&gt;

&lt;h4 id=&quot;1free的内存并非立即归还os&quot;&gt;（1）free的内存并非立即归还OS&lt;/h4&gt;
&lt;p&gt;首先要明确一点，在用户态的任何一个操作，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;都不可以直接操作OS memory/virtual memory&lt;/code&gt;。 在linux OS中，每一个内存块实际上都是特定大小的块（block/chunk）。实际上在大部分OS里，我们都无法直接操作操作系统的内存，即物理内存。假设我们可以操作，会带来什么后果呢？由于内存归还（大小、地址）的不确定性，我们的内存中会产生大量的gap。&lt;/p&gt;

&lt;p&gt;这也是为什么我们的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OS只能处理特定大小的chunks&lt;/code&gt;（一般为512bytes的倍数，比如4KB)，并且需要alignment对齐。&lt;br /&gt;
那么问题来了： 我就是要归还40byte给OS，这时怎么办呢？&lt;/p&gt;

&lt;h4 id=&quot;2-free维护了自己的块列表&quot;&gt;(2) free维护了自己的块列表&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free维护了自己的块列表&lt;/code&gt;，通常它也会尝试将地址空间中的相邻块merge在一起。空闲块列表只是内存块的循环列表，其中包含一些administrative-data。这也是为什么使用标准malloc/free管理非常小的内存元素效率不高的原因。每个内存块都需要额外的数据，而更小的大小会产生更多碎片。&lt;/p&gt;

&lt;p&gt;当我们需要新的内存块时，free空闲列表也是malloc看到的第一个部分。它在从OS调用新内存之前进行扫描。当发现大于所需内存的块时，它被分成两部分。一个返回给调用者，另一个返回到空闲列表中。&lt;/p&gt;

&lt;h4 id=&quot;3-所以你理解了为啥c的代码老是奔溃了吧&quot;&gt;(3) 所以你理解了为啥C的代码老是奔溃了吧？&lt;/h4&gt;

&lt;p&gt;作为一个苦逼的c/c++程序员，每当delete/free操作时，根据上面的原理，我们会将回收的块放入空闲列表，这可能会&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;触及到free列表中的administrative-data&lt;/code&gt;，因此出现覆盖指针的问题。&lt;br /&gt;
举个栗子，我们将9个字符（不要忘记尾部留空字节）写入一个大小为4个字符的区域：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;char a[4] = {0};
a = &quot;123456789&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当程序core掉时，其实往往可能能够完成一部分工作，然后才gg的。这是因为，当发生指针覆盖时，指针覆盖了另一块内存的地址，在free释放这个指针之前，你仍然可以访问到这个指针指向的区域。&lt;br /&gt;
你可能会覆盖存储在另一块内存中的administrative-data，这些内存位于您的数据块“后面”（因为这些数据通常存储在内存块的“前面”，可以参考上面数组的那个情况）。如果空闲然后尝试将您的块放入空闲列表，它可以触摸此administrative-data，最终这会使系统崩溃。&lt;/p&gt;

&lt;h4 id=&quot;4free的基本实现&quot;&gt;（4）Free的基本实现&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void free(void *p)
{
    t_block b;
    if(valid_addr(p))//地址的有效性验证
    {
        b = get_block(p);//得到对应的block
        b-&amp;gt;free = 1; //如果相邻的上一块内存是空闲的就合并, 合并之后的上一块还是空闲的就继续合并，直到不能合并为止
        while(b-&amp;gt;prev &amp;amp;&amp;amp; b-&amp;gt;prev-&amp;gt;free)
        {
            b = fusion(b-&amp;gt;prev);
        } 

        //同理去合并后面的空闲block
        while(b-&amp;gt;next)
            fusion(b);//内部会判断是否空闲

        //如果当前block是最后面的那个block，此时可以调整break指针了
        if(NULL == b-&amp;gt;next)
        {
            if(b-&amp;gt;prev) // 当前block前面还有占用的block
                b-&amp;gt;prev-&amp;gt;next = NULL;
            else       // 当前block就是整个heap仅存的
                base = NULL; // 则重置base
            brk(b); //调整break指针到b地址位置
        }

        //否则不能调整break
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;五总结&quot;&gt;五、总结&lt;/h3&gt;
&lt;p&gt;要理解c/linux的内存管理机制，的确是一个让人头大的事情。可能这属于很容易让人过目即忘的内容，但是在工作之后，还是得有时间来细细品味，修炼内功对自己的成长可能短时间内不是很多，但是长期积累下去，总会有一些建树和收获。&lt;/p&gt;

</description>
        <pubDate>Wed, 09 Jan 2019 22:32:18 +0000</pubDate>
        <link>http://windblog.cn/c/c++/2019/01/09/c-memory-mangement/</link>
        <guid isPermaLink="true">http://windblog.cn/c/c++/2019/01/09/c-memory-mangement/</guid>
        
        <category>c/c++</category>
        
        <category>内存管理</category>
        
        
        <category>c/c++</category>
        
      </item>
    
      <item>
        <title>HTTPS，为用户带上安全【套】</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUPUg.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;一国际惯例概念&quot;&gt;一、国际惯例——概念&lt;/h3&gt;

&lt;p&gt;我们还是要给一个基本概念：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP 协议（HyperText Transfer Protocol，超文本传输协议）&lt;/code&gt;：是客户端浏览器或其他程序与Web服务器之间的应用层通信协议。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTPS 协议（HyperText Transfer Protocol over Secure Socket Layer）&lt;/code&gt;：可以理解为HTTP+SSL/TLS， 即 HTTP 下加入 SSL 层，HTTPS 的安全基础是 SSL，因此加密的详细内容就需要 SSL，用于安全的 HTTP 数据传输。
周所周知，在登录网页时，https不就是比http多了一个s吗？但是一个简单的字母，则隐藏了太多的东西与知识点在背后。&lt;/p&gt;

&lt;h3 id=&quot;二图解&quot;&gt;二、图解&lt;/h3&gt;

&lt;p&gt;我们在使用http协议时，HTTP直接与TCP通信，当使用SSL时，则需要先与SSL通信，然后再由SSL和TCP通信。 在网络层表现如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teNzKP.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于加密算法我不做过多赘述，我将其按方式分两种：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1、共享密钥加密：加密与解密使用同一个密钥；
2、公开密钥（非对称密钥）：公开密钥使用一对非对称密钥。一把叫私有密钥，一把公有密钥； 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;三谈谈实现&quot;&gt;三、谈谈实现&lt;/h3&gt;

&lt;p&gt;那我们现在来想想，我们分别用上述两种方式来实现https，会有什么好处和坏处呢？&lt;/p&gt;

&lt;h4 id=&quot;1共享密钥加密&quot;&gt;1、共享密钥加密&lt;/h4&gt;

&lt;p&gt;比如，王二狗要把自己的银行卡密码告诉杨铁柱：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUpb8.jpg&quot; alt=&quot;Thumper&quot; /&gt;
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUCVS.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个不用说，显然傻子都知道不行，在http通信过程中，加密和解密都会用到同一个密钥【喵喵喵】，只要找到对应加密的方式，我们是可能解析出其真正的内容的。
如果密钥被攻击者截取获得，此时加密就失去了意义。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUSDf.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;2非对称密钥公开加密方式&quot;&gt;2、非对称密钥（公开加密方式）&lt;/h4&gt;

&lt;p&gt;what？&lt;br /&gt;
刚刚悄悄说都不行，那你现在还想把密钥直接告诉所有人咯？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUkCj.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;非也非也，非对称加密又称共享密钥加密，使用一对非对称的密钥，一把叫做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;私有密钥&lt;/code&gt;，另一把叫做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;公有密钥&lt;/code&gt;；公钥加密只能用私钥来解密，私钥加密只能用公钥来解密。常见的公钥加密算法有：RSA、ElGamal、背包算法、Rabin（RSA的特例）、迪菲－赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法）。&lt;/p&gt;

&lt;p&gt;这个方案的好处主要有两个，划重点：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（1）这个私钥，一定只会有王二狗一个人知道，不然为什么叫私钥；
（2）我们无法从公钥来推导出私钥的内容 —— 体现了【非对称】的概念；
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;哦~ 那这个听起来蛮靠谱的嘛嘻嘻！我王二狗再也不用怕密码被偷听了！&lt;br /&gt;
然而，根据&lt;a href=&quot;https://www.zhihu.com/question/19601573&quot;&gt;墨菲定律&lt;/a&gt;，这种看似没问题的事情，一定会有问题！&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;王二狗怎么知道杨铁柱一定是杨铁柱本人？如果有人冒充他，那么一样可以派发私钥，那么不就被骗了吗？&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&quot;3证书&quot;&gt;3、证书&lt;/h4&gt;

&lt;p&gt;上面的问题，就引出了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;证书&lt;/code&gt;这个东西。我们平时上taobao或者其他网站，经常会看到浏览器这里有个安全的按钮，那么看看里面最主要是啥：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUi5Q.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里写了证书是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;有效&lt;/code&gt;的，有效代表什么有效啊？继续划重点：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（1）代表我现在是在逛真的淘宝，不是山寨的；
（2）我在浏览器的所有操作都是安全的，除了淘宝和我，没有第三个人知道；
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么浏览器是怎么验证淘宝的证书的呢？一般我们去申请证书都是通过&lt;a href=&quot;https://yq.aliyun.com/articles/3164&quot;&gt;CA证书机构&lt;/a&gt;，同时大家都信任CA机构比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;digicert&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verisign&lt;/code&gt;，我们就可以事先将这些CA机构的神器安装到我们的电脑上，也就是这些CA机构的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;公钥&lt;/code&gt;，我们也称它们为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;根证书&lt;/code&gt;。这些根证书是预先安装在我们电脑上的，所以每当我们访问taobao的时候，如果taobao服务器上安装了证书，他就可以和我们建立安全通信。&lt;/p&gt;

&lt;p&gt;证书的大致内容：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（1）证书的发布机构CA
（2）证书的有效期
（3）公钥
（4）证书所有者
（5）签名
（6）其他
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUE2n.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;哦~ 那这个听起来蛮靠谱的嘛，嘻嘻。。。好吧，你知道我又要告诉你有问题了：
&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUVvq.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;公开密钥的方式，比共享密钥加密方式要复杂，如果我们所有通信都要用公开密钥的方式，通信的效率就会降低。&lt;/p&gt;

&lt;h3 id=&quot;4https的实现&quot;&gt;4、https的实现&lt;/h3&gt;

&lt;p&gt;考虑到以上的所有问题，https最终是采用的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;共享密钥加密 + 非对称密钥&lt;/code&gt; 的混合加密方式：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（1）每次客户端（浏览器）先发起一个请求，say hello；
（2）Server回复一个hello，并把SSL证书发给浏览器；
（3）客户端与服务端都用公开密钥的方式加密，双方安全交换密钥。
（4）客户端持有公钥，服务端持有密钥。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUA8s.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;（5）后续就能愉快的通过共享密钥加密方式，进行通信了。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teUeK0.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;四小结&quot;&gt;四、小结&lt;/h3&gt;

&lt;p&gt;用一句话总结https：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP + 加密 + 认证 + 完整性保护 = HTTPS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;本文没有具体探讨加密算法与http的实现，有兴趣可以自行google，但是对于一般的开发人员，了解上述的知识点基本已够。&lt;/p&gt;

</description>
        <pubDate>Mon, 31 Dec 2018 02:32:18 +0000</pubDate>
        <link>http://windblog.cn/https/2018/12/31/https/</link>
        <guid isPermaLink="true">http://windblog.cn/https/2018/12/31/https/</guid>
        
        <category>https</category>
        
        <category>tcp/ip</category>
        
        
        <category>https</category>
        
      </item>
    
      <item>
        <title>浅谈Google Protobuf</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYbqO.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1前言&quot;&gt;1、前言&lt;/h3&gt;

&lt;p&gt;信息与物质传递的速度，决定了人类社会的发展速度。&lt;/p&gt;

&lt;p&gt;更少的信息产生更多的价值，是信息传播时代人们一直所追求的。&lt;/p&gt;

&lt;h3 id=&quot;2-google-protocol-buffer初识&quot;&gt;2、 Google Protocol Buffer初识&lt;/h3&gt;

&lt;p&gt;回归正题，对于熟悉RPC和HTTP技术的同学来说，使用RPC框架或者HTTP Restful方案能有效降低网络应用程序的开发量，降低系统模块之间的耦合度，使得开发分布式网络应用程序更加容易。随着微服务架构的流行，在很多RPC的设计中，都采用了高性能的编解码技术，数据交互格式的重要性不言而喻。&lt;/p&gt;

&lt;p&gt;    对于一门优秀的数据交互格式，我认为有以下几大关键要素：&lt;/p&gt;

&lt;p&gt;    (1) 格式简单，易于人阅读；&lt;/p&gt;

&lt;p&gt;    (2) 足够轻量化，易于计算机解析处理；&lt;/p&gt;

&lt;p&gt;    (3) 兼容性强，易于多平台多语言间传输；&lt;/p&gt;

&lt;p&gt;    简而言之，就是要 “更快、更小、更灵活”。说到主流的数据交互格式，一定少不了JSON和XML，对于两者的优劣高低，有兴趣的同学可以加入到这场“国家德比”式的争论中《为什么都反对 XML 而支持使用 JSON？》。&lt;/p&gt;

&lt;p&gt;    google protocol buffer (以下统一简称protobuf) 是google发明的一款平台无关，语言无关，可扩展的序列化结构数据格式。&lt;/p&gt;

&lt;p&gt;    鲁迅曾说过：”天下武功，唯快不破”，而”近乎变态的性能”正是protobuf的最大优势之一。相比其他类似的数据交互技术，有性能的测试结果如下：&lt;/p&gt;

&lt;p&gt;序列化测试结果&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYLZD.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;字节数对比测试结果如下：（对性能测试感兴趣的同学可以移步 &lt;a href=&quot;https://www.eprosima.com/index.php/resources-all/performance/apache-thrift-vs-protocol-buffers-vs-fast-buffers&quot;&gt;《几种序列化协议测试对比》&lt;/a&gt;）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYoxx.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于protobuf而言，最为特别的一点在于——它的结构即为文件。以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; 结尾命名（常用的版本有proto2和proto3，本文将以新版本proto3为例）。前排吃瓜，近距离体会下protobuf的定义：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;syntax = &quot;proto3&quot;;

message MyPerson {     
    int32 id = ;  
    string name = 2;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;与创建一个struct很相似，我们用关键词&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt;定义了一个MyPerson的结构：第一个字段id的类型为int32，第二个字段name是string类型。等号的右侧的数字，不是对字段初始化，是表示定义的字段在message中的序号。&lt;/p&gt;

&lt;h3 id=&quot;3protubuf的序列化原理&quot;&gt;3、Protubuf的序列化原理&lt;/h3&gt;

&lt;p&gt;protobuf能拥有如此高效的性能，主要得益于它的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;序列化方式设计&lt;/code&gt;。下面我将用一个最简单的例子，来尝试剖析其中的原理：&lt;/p&gt;

&lt;h4 id=&quot;1字节码示例&quot;&gt;（1）字节码示例&lt;/h4&gt;

&lt;p&gt;    基于上面MyPerson这个结构，我们用下面的一段简单的Java代码，用基于protobuf定义的message创建一个实例，将其序列化为字节码。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Person.MyPerson.Builder pBuilder = Person.MyPerson.newBuilder();
    pBuilder.setId(1);          // 设置id为1
    pBuilder.setName(&quot;messi&quot;);  // 名字设置为梅西
    Person.MyPerson person1 = pBuilder.build();

    // 打印序列化字节码
    for (byte b : person1.toByteArray()) {
        System.out.println(b);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里类似于声明了一个名为MyPerson的结构体，然后在代码中生成实际的对象并管理使用该对象。翻译成我们熟悉的Json格式，你可以认为它对应的Json数据为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;messi&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面for循环中打印出的字节码Byte如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;8 
1 
18 
5 
109 
101 
115 
115 
105     
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于protobuf是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;二进制格式&lt;/code&gt;传输的，让我们来把他们翻译成计算机最喜欢的二进制数，瞅瞅里面有什么玄机：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10进制:8      2进制:0 0001 000       key  1 &amp;amp; 0
10进制:1      2进制:00000001       

10进制:18     2进制:0 0010 010       key  2 &amp;amp; 2
10进制:5      2进制:00000101       
10进制:109    2进制:01101101      
10进制:101    2进制:01100101       
10进制:115    2进制:01110011     
10进制:115    2进制:01110011       
10进制:105    2进制:01101001         
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2tlv存储&quot;&gt;（2）TLV存储&lt;/h4&gt;

&lt;p&gt;一头雾水？让我们先来科普下protobuf的序列化原理：&lt;/p&gt;

&lt;p&gt;protobuf经过序列化后的字节码是很紧凑的，是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key-value&lt;/code&gt; 的形式。以上面定义的MyPerson为例子，我们定义的的数据结构大概就如下图这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYfIJ.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我把其称之为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLV（Tag - Length - Value）存储方式&lt;/code&gt; ，即 标识 - 长度 - 字段值 存储方式。意味着我们无需用多余的分隔和终止符来分隔字段，有很高的空间利用率，甚至当字段没有赋值时，序列化的数据内容中都不会包含这个字段和内容。&lt;/p&gt;

&lt;p&gt;也可以把上面的图画的再具体一些，序列化后每个字段的字节码可以划分为6个主要部分：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MSB flag、tag、wire-type、length、value、padding&lt;/code&gt;，字节码结构可能会根据编码方式的不同而有所差异，如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teY4i9.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;3varint编码基础&quot;&gt;（3）Varint编码基础&lt;/h4&gt;

&lt;p&gt;按照国际惯例，我们有必要先理解一下最基本的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;varints编码&lt;/code&gt;方式。我们以这个varint编码后的二进制数为例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00000001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很简单吧，就是1，没有套路，纯天然无污染的。&lt;/p&gt;

&lt;p&gt;那我们换一个大一些的，400？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10010000  00000011
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;什么鬼？这两个字节码无论怎么看，怎么算都弄不出400吧？&lt;/p&gt;

&lt;p&gt;    varint编码重要且巧妙的一点在于，它能让字节码尽量的紧凑，不浪费一点能压榨的剩余空间，堪称encoding届的周扒皮。每个字节码中的最高位我们都称之为MSB（Most Significant Bit），用来标识后续的byte是否为当前数值的一部分，如果是1，说明下一个byte也是表示当前数值的一部分。知道了这些，现在我们分析下上面那个400的字节码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 0010000   最高位为1，说明下一个byte也是数值的一部分
0 0000011   最高位为0，说明当前byte是数值的最后一个部分   对于MSB而言，他本身只是一个标志位，不具备任何数值意义，所以我们在解析时要去掉这一位：

0010000  0000011   而protobuf字节序采用 little-endian的排放方式，所以我们要把两个byte的位置交换：

0000011  0010000  
2 ** (8) +  2 ** (7) +  2 ** (4) = 256 + 128 +16 = 400，终于知道400是这样计算出来的了。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;4key的剖析&quot;&gt;（4）key的剖析&lt;/h4&gt;

&lt;p&gt;那问题又来了，我们了解了上面varint的编码方式，有啥用呢？回到（1）中的字节码，刚才你也许也注意到了，我把key的那行字符码分成了几个部分：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10进制:8      2进制:0 0001 000       key  1 &amp;amp; 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;没错了，看到最高位的0被我隔离出来了，你也许已猜到了 —— protobuf的key正是用varint方式编码的。但需要注意的一点是，对于上面这个8位的key，字节码是这样来划分的：前5位代表了对应protobuf中的字段序号，后3位代表了这个字段的类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;field_number &amp;lt;&amp;lt; 3   |   wire_type   
( 5位field_number  +   3 位 wire_type)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么问题又来了—— What？我们只有5位来表示字段序号？去掉最大位的MSB，那protobuf的一个message里岂不是只能定义不超过 2 的4次方 = 16 个字段？&lt;/p&gt;

&lt;p&gt;    所以（3）中的知识点又派上用场了，假设某一个类型为int32的字段，在protobuf中定义的序号是16，那么它的key的字节码一定是这样的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10000000  00000001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt; 我们写成这样你会更有感觉了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 0000 000   
0 0000001   第一行字节码的头是MSB，代表下一个字节码也是数值的一部分，去掉MSB，像上面一样交换字节码，我们得到了：

0000001 0000 | 000   
field_number | wire_type
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2 的4次方 = 16，这个字段的序号就是16，他的wire_type是0对应的varint。所以，当你定义的某个字段的序号较大时，我们只需要扩充后面的字节码就好了。需要通过MSB来获取数值的字节码部分，拼接后得到字段的真实序号 —— 这里也恰恰也反应了varint的优点，很适合作为整数尤其是较小整数的编码，大数或者负数则是使用ZigZag的编码方式。篇幅有限，对编码部分感兴趣的同学，推荐仔细阅读下这篇官方的文章 《Protobuf Encoding》。&lt;/p&gt;

&lt;h4 id=&quot;5wire-type&quot;&gt;（5）wire-type&lt;/h4&gt;

&lt;p&gt;protobuf支持丰富的数据类型，wire_type类型有如下几种：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teY5GR.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;6解读字节码&quot;&gt;（6）解读字节码&lt;/h4&gt;

&lt;p&gt;大脑都快宕机了？是不是只想右上角关闭，并送上尴尬而不失礼貌的微笑？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYHsK.jpg&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    不要放弃，我觉得你还能抢救一下。回到上面的字节码，你会发现，将上面的知识点串联起来对字节码进行剖析，一切将变得明朗。&lt;/p&gt;

&lt;p&gt;    以第一个key  0 0001 000为例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;filed_number: 0 0001 &amp;amp; wire_type: 000&amp;gt;  ---&amp;gt;   &amp;lt;filed_number: 1 &amp;amp; wire_type: 0&amp;gt;     根据Varint的编码方式：最大位MSB为0，key只占一个字节码，filed_number为1，代表这个key是我们在message定义的第一个字段。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;    而后三位wire_type是0，查看上面的wire_type类型表，查到0对应Varint，而其中包含了int32这个基本类型。所以我们要用Varint的编码方式解析value：MSB为0，所以只有一个字节码表示这个字段的value。0 000 0001去掉MSB为 000 0001 = 1。所以第一个字段id的值就是1。&lt;/p&gt;

&lt;p&gt;    同理，我们看到key 00010 010 ，field_number是2，代表这是proto中定义的第二个字段。wire_type是2，对应到了Length-delimited中的string类型。而根据Length-delimited编码方式，key的下一个字节码是表示这个字段的长度，0 0000101是5，这样我们从key往下数5行，就可以解析出每一行字节码对应的字符串 messi了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10进制:8     2进制:00001 000     key:   1 &amp;amp; 0    
                                       field_number为1，对应id字段
                                       wire_type为0，对应类型为varint

10进制:1     2进制:0 0000001            value: 1    id的值为1


10进制:18    2进制:00010 010     key:   2 &amp;amp; 2  
                                       field_number为2，对应name字段
                                       wire_type为2，对应类型为Length-delimited
              
10进制:5     2进制:0 0000101             length: 5   代表string的长度为5
10进制:109   2进制:0 1101101             value: 109  ASCII对应字母为m
10进制:101   2进制:0 1100101             value: 101  ASCII对应字母为e
10进制:115   2进制:0 1110011             value: 115  ASCII对应字母为s
10进制:115   2进制:0 1110011             value: 115  ASCII对应字母为s
10进制:105   2进制:0 1101001             value: 105  ASCII对应字母为i
    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4牛刀小试&quot;&gt;4、牛刀小试&lt;/h3&gt;

&lt;p&gt;看完上面一堆原理，简单实战运动一下，假设我们现在要设计一个简单的球员信息注册服务。&lt;/p&gt;

&lt;p&gt;使用protobuf的流程我大致总结为下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/05/28/teYOde.png&quot; alt=&quot;Thumper&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    protobuf支持多种语言和平台的使用，c++、java、python与golang等语言都有对应的使用tutorials。不同平台与语言之间的使用大同小异，下面测试实例的软件版本信息如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jdk                   1.8.0_144 
protobuf-java         3.5.0
protoc                protoc-3.6.0-win32  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;1下载protoc-3xx-win32zip&quot;&gt;（1）下载protoc-3.x.x-win32.zip&lt;/h4&gt;

&lt;p&gt;解压后bin目录下，有执行文件protoc.exe，用来将你写的.proto文件进行编译生成指定的文件。&lt;/p&gt;

&lt;h4 id=&quot;2定义并编译proto文件&quot;&gt;（2）定义并编译proto文件&lt;/h4&gt;

&lt;p&gt;我们要简单设计一个用于消息通讯的 Msg.proto：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;syntax = &quot;proto3&quot;;

// 消息的整体结构
message CMsg
{
    string msghead = 1;
    string msgbody = 2;
}
 
// 消息的head，对应CMsg的msghead
message CMsgHead
{
    int32 msglen = 1;
    int32 msgseq = 2;
    int32 msgres = 3;
    string termid = 4;
}
 
// 消息的Body，对应CMsg的msgbody，球员注册的主要信息和结果返回
message CMsgReg
{
    int32 id = 1;
    string name = 2;
    int32 age = 3;
    int32 ret = 4;
    string termid = 5;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将Msg.proto文件放在同级bin目录下，确保在环境变量配置好的情况下，在cmd中，用一条编译命令执行编译，生成编译器编译后的Msg.java文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;protoc --proto_path=F:\pb\protoc-3.5.0-win32\bin  --java_out=F:\pb\protoc-3.5.0-win32\bin  F:\pb\protoc-3.5.0-win32\bin\Msg.proto
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其他语言同理，例如c++，将上面的 –java_out改成–cpp_out，就能生成对应的Msg.pb.h和Msg.pb.cc文件了。&lt;/p&gt;

&lt;p&gt;    如果你只是一个使用者，并不需要过多关注其中的细节（省略实现细节），你也无需改动它，你要做的，就是把它拖入你的工程内使用即可。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tencent.xxx;
// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: Msg.proto

public final class Msg {
    private Msg() {
    }

    public static void registerAllExtensions(
            com.google.protobuf.ExtensionRegistryLite registry) {
    }

    public static void registerAllExtensions(
            com.google.protobuf.ExtensionRegistry registry)

    public interface CMsgOrBuilder extends
            com.google.protobuf.MessageOrBuilder {...}

    public static final class CMsg extends
            com.google.protobuf.GeneratedMessageV3 implements
            CMsgOrBuilder {...}

    public interface CMsgHeadOrBuilder extends
            com.google.protobuf.MessageOrBuilder {...}

    public static final class CMsgHead extends
            com.google.protobuf.GeneratedMessageV3 implements
            CMsgHeadOrBuilder {...}

    public interface CMsgRegOrBuilder extends
            com.google.protobuf.MessageOrBuilder {...}

    public static final class CMsgReg extends
            com.google.protobuf.GeneratedMessageV3 implements
            CMsgRegOrBuilder {...}

    private static final com.google.protobuf.Descriptors.Descriptor
            internal_static_CMsg_descriptor;
    private static final
    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
            internal_static_CMsg_fieldAccessorTable;
    private static final com.google.protobuf.Descriptors.Descriptor
            internal_static_CMsgHead_descriptor;
    private static final
    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
            internal_static_CMsgHead_fieldAccessorTable;
    private static final com.google.protobuf.Descriptors.Descriptor
            internal_static_CMsgReg_descriptor;
    private static final
    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
            internal_static_CMsgReg_fieldAccessorTable;

    public static com.google.protobuf.Descriptors.FileDescriptor
    getDescriptor() {
        return descriptor;
    }
    private static com.google.protobuf.Descriptors.FileDescriptor
            descriptor;
    static {...}
    // @@protoc_insertion_point(outer_class_scope)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3添加相关依赖库&quot;&gt;（3）添加相关依赖库&lt;/h4&gt;

&lt;p&gt;我使用了 Maven3来管理我的Java工程，我们在pom.xml中添加一个关于protobuf工具库的使用依赖：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.google.protobuf&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;protobuf-java-util&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;3.5.0&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;4编写服务端代码&quot;&gt;（4）编写服务端代码&lt;/h4&gt;

&lt;p&gt;现在我们来编写我们的注册服务器，主要逻辑为读取客户侧发送的请求，并将stream解析为CMsg的实例对象：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DataOutputStream dataOutputStream;
InputStream istream = client.getInputStream();
dataOutputStream = new DataOutputStream(client.getOutputStream());
byte len[] = new byte[1024];
// 读取客户端发来的stream
int cnt = istream.read(len);
byte[] temp = new byte[cnt];

for (int i = 0; i &amp;lt; cnt; i++) {
    temp[i] = len[i];
}
// 解析为protobuf中定义的CMsg对象
CMsg msg = CMsg.parseFrom(temp);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在有了CMsg的对象之后，我们可以通过Msg.java中提供的方法，从而进一步获取head和body中的具体信息，并将其解析为protobuf中定义的CMsgHead和CMsgReg对象。同理，我们又可以进一步通过get方法，获取head和body中定义的具体信息了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 头信息
CMsgHead head = CMsgHead.parseFrom(msg.getMsghead().getBytes());
// Body信息
CMsgReg body = CMsgReg.parseFrom(msg.getMsgbody().getBytes());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的Server端代码 PlayerRegisterServer.java 如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tencent.xxx;  
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.tencent.sniper.Msg.CMsg;
import com.tencent.sniper.Msg.CMsgHead;
import com.tencent.sniper.Msg.CMsgReg;
public class PlayerRegisterServer implements Runnable {
    private int id = 0;
    public void run() {
            try {
                System.out.println(&quot;Begin to register Football Player...&quot;);
                ServerSocket serverSocket = new ServerSocket(8888);

                while (true) {
                    System.out.println(&quot;waiting for new connection...&quot;);
                    Socket client = serverSocket.accept();
                    DataOutputStream dataOutputStream;

                    try {
                        InputStream istream = client.getInputStream();
                        dataOutputStream = new DataOutputStream(client.getOutputStream());
                        byte len[] = new byte[1024];

                        //读取客户端发来的stream
                        int cnt = istream.read(len);
                        byte[] temp = new byte[cnt];

                        for (int i = 0; i &amp;lt; cnt; i++) {
                            temp[i] = len[i];
                        }
                        // 解析获取客户端的msg
                        CMsg msg = CMsg.parseFrom(temp);
                        CMsgHead head = CMsgHead.parseFrom(msg.getMsghead().getBytes());

                        System.out.println(&quot;head len: &quot; + head.getMsglen());
                        System.out.println(&quot;head res: &quot; + head.getMsgres());
                        System.out.println(&quot;head seq: &quot; + head.getMsgseq());
                        System.out.println(&quot;head termid: &quot; + head.getTermid());

                        registerPlayer(dataOutputStream, CMsgReg.parseFrom(msg.getMsgbody().getBytes()));
                        istream.close();
                    } catch (Exception ex) {
                        System.out.println(ex.getMessage());
                        ex.printStackTrace();
                    } finally {
                        client.close();
                        System.out.println(&quot;server close\n&quot;);
                    }
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }

        private void registerPlayer(DataOutputStream dataOutputStream, CMsgReg body) {
            System.out.println(&quot;start to register palyer...&quot;);
            System.out.println(&quot;player name: &quot; + body.getName());
            System.out.println(&quot;player age: &quot; + body.getAge());

            CMsgHead rhead = CMsgHead.newBuilder().setMsglen(10).
                    .setMsgseq(100).setMsgres(0)
                    .setTermid(&quot;Register Client: Head&quot;).build();

            int ret = 0;
            int pid = 0;
            // 只允许31岁以下的球员注册成功
            if (body.getAge() &amp;lt; 31) {
                pid = ++id;
            } else {
                ret = 1001;
            }

            CMsgReg rbody = CMsgReg.newBuilder().setId(pid).setName(body.getName())
                    .setAge(body.getAge()).setRet(ret).setTermid(&quot;Register Client: Body&quot;).build();

            CMsg rmsg = CMsg.newBuilder()
                    .setMsghead(rhead.toByteString().toStringUtf8())
                    .setMsgbody(rbody.toByteString().toStringUtf8()).build();

            byte[] backBytes = rmsg.toByteArray();

            try {
                dataOutputStream.write(backBytes, 0, backBytes.length);
                dataOutputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public static void main(String[] args) {
            Thread desktopServerThread = new Thread(new PlayerRegisterServer());
            desktopServerThread.start();
        }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;5编写客户端代码&quot;&gt;（5）编写客户端代码：&lt;/h4&gt;

&lt;p&gt;客户端相对要简单一些，我们要做的就是创建protobuf定义的结构实例，然后发送到服务器注册信息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 按照定义的protobuf格式组装head
CMsgHead head = CMsgHead.newBuilder().setMsglen(5)
                .setMsgseq().setMsgres(1001)
                .setTermid(&quot;Register Client: Head&quot;).build();

// 按照定义的protobuf格式组装body
CMsgReg body = CMsgReg.newBuilder().setId(1).setName(name)
               .setAge(age).setRet(1001).setTermid(&quot;Register Client: Body&quot;).build();

// 按照定义的protobuf格式，将head和body拼接成msg
CMsg msg = CMsg.newBuilder()
           .setMsghead(head.toByteString().toStringUtf8())
           .setMsgbody(body.toByteString().toStringUtf8()).build();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们还可以做个简单的测试，将protobuf序列化后字节码的大小与存储了相同信息的json串大小做个比较：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public void printSizeOfData(CMsg g) {
    System.out.println(&quot;\n&quot; + &quot;bytes size: &quot; + g.toByteString().size());

    String pJsonData = &quot;&quot;;
    try {
        pJsonData = JsonFormat.printer().print(g);
    } catch (Exception e) {
        e.printStackTrace();
    }

    System.out.println(&quot;json size：&quot; + pJsonData.getBytes().length);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的客户端代码 PlayerRegisterClient.java 如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tencent.xxx;

import java.io.InputStream;
import java.net.Socket;

import com.google.protobuf.util.JsonFormat;
import com.tencent.sniper.Msg.CMsg;
import com.tencent.sniper.Msg.CMsgHead;
import com.tencent.sniper.Msg.CMsgReg;

public class PlayerRegisterClient {
    public void run() {
        try {
            // 这里我们模拟注册三位球员
            registerPlayer(&quot;Cristiano Ronaldo&quot;, 33);
            registerPlayer(&quot;Leon Messi&quot;, 30);
            registerPlayer(&quot;Kylian Mbappé&quot;, 20);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }

public void registerPlayer(String name, int age) throws Exception {
    Socket socket = null;
    socket = new Socket(&quot;localhost&quot;, 12345);

    // 按照定义的protobuf格式组装head
    CMsgHead head = CMsgHead.newBuilder().setMsglen(5)
            .setMsgseq(100).setMsgres(1001)
            .setTermid(&quot;Register Client: Head&quot;).build();

    // 按照定义的protobuf格式组装body
    CMsgReg body = CMsgReg.newBuilder().setId(1).setName(name)
            .setAge(age).setRet(1001).setTermid(&quot;Register Client: Body&quot;).build();

    // 按照定义的protobuf格式，将head和body拼接成msg
    CMsg msg = CMsg.newBuilder()
            .setMsghead(head.toByteString().toStringUtf8())
            .setMsgbody(body.toByteString().toStringUtf8()).build();

    // 向服务器发送请求
    System.out.println(&quot;sendMsg to register player...\n&quot;);
    msg.writeTo(socket.getOutputStream());

    // 接收返回结果并解析
    InputStream input = socket.getInputStream();
    byte[] by = recvMsg(input);
    getRegisterResult(CMsg.parseFrom(by));
    printSizeOfData(CMsg.parseFrom(by));

    input.close();
    socket.close();
}

public void getRegisterResult(CMsg g) {
    try {
        StringBuffer sb = new StringBuffer();

        CMsgReg body = CMsgReg.parseFrom(g.getMsgbody().getBytes());
        if (body.getRet() == 0) {
            System.out.println(&quot;successfully register player:&quot;);
            System.out.println(&quot;player id: &quot; + body.getId());
            System.out.println(&quot;player name: &quot; + body.getName());
            System.out.println(&quot;player age: &quot; + body.getAge());
            System.out.println(&quot;player ret: &quot; + body.getRet());
            System.out.println(&quot;player termid: &quot; + body.getTermid());
            System.out.println(sb.toString());
        } else {
            System.out.println(&quot;Fail to register player [&quot; + body.getName() +
                    &quot;], he's too old to join the club!&quot;);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public byte[] recvMsg(InputStream inpustream) {
    System.out.println(&quot;[recvMsg]&quot;);
    byte[] temp = null;

    try {
        byte len[] = new byte[1024];
        int cnt = inpustream.read(len);

        temp = new byte[cnt];
        for (int i = 0; i &amp;lt; cnt; i++) {
            temp[i] = len[i];
        }

        return temp;
    } catch (Exception e) {
        System.out.println(e.toString());
        return temp;
    }
}

public void printSizeOfData(CMsg g) {
    System.out.println(&quot;\n&quot; + &quot;bytes size:&quot; + g.toByteString().size());
    String pJsonData = &quot;&quot;;
    try {
            pJsonData = JsonFormat.printer().print(g);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(&quot;json size：&quot; + pJsonData.getBytes().length);
    }

    public static void main(String[] args) {
        PlayerRegisterClient pc = new PlayerRegisterClient();
        pc.run();
    }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;6运行结果&quot;&gt;（6）运行结果&lt;/h4&gt;

&lt;p&gt;客户端测试结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sendMsg to register player...
[recvMsg]
Fail to register player [Cristiano Ronaldo], he's too old to join the club!

bytes size:84
json size：163


sendMsg to register player...
[recvMsg]
successfully register player:
player id: 1
player name: Leon Messi
player age: 30
player ret: 0
player termid: Client:Register Body

bytes size:74
json size：155


sendMsg to register player...
[recvMsg]
successfully register player:
player id: 2
player name: Kylian Mbappé
player age: 20
player ret: 0
player termid: Client:Register Body

bytes size:78
json size：163  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务端测试结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Begin to register Football Player...
waiting for new connection...
head len: 5
head res: 0
head seq: 100
head termid: Register Client: Head
start to register palyer...
player name: Cristiano Ronaldo
player age: 33
server close


waiting for new connection...
head len: 5
head res: 0
head seq: 100
head termid: Register Client:Head
start to register palyer...
player name: Leon Messi
player age: 30
server close


waiting for new connection...
head len: 5
head res: 0
head seq: 100
head termid: Register Client:Head
start to register palyer...
player name: Kylian Mbappé
player age: 20
server close
waiting for new connection...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;传输同样的数据和信息，与json的size相比，protobuf的威力可见一斑。至于xml。。。就不提了吧。&lt;/p&gt;

&lt;h3 id=&quot;5尾声闲谈&quot;&gt;5、尾声闲谈&lt;/h3&gt;
&lt;p&gt;    如果需要进行频繁的数据交互，特别是对延迟敏感的场景（例如游戏），或者是在要在错综分布的系统间低成本交互（例如云），protobuf都是一个不错的数据交互格式。&lt;/p&gt;

&lt;p&gt;    当然古人有云，程序员最忌讳盲目崇拜，PHP也不一定是世界上最好的语言。根据场景的不同，还是要更加全面的看待其利与弊。protobuf作为一项用二进制数据交互格式，相对于其他数据格式，可读性是较差的。或者当网络服务类型跨度大、变化频繁，特别是你的数据需要直接与Web交互时，或者需要与偏向底层的接口进行交互，用json/xml或者其他格式相对是明智之选。&lt;/p&gt;

&lt;p&gt;    再说到protobuf，不得不提下基于protobuf 和 HTTP2的 “下一代RPC框架” —— grpc。传输协议和数据的交互格式是RPC框架中非常重要的两个因素，不得不说google是下了一招很秒的棋：既可以使用HTTP2（多路复用技术和对header的压缩技术值得推敲），又可以大力推广普及自创的protobuf。当然，提到优秀的RPC框架，有鹅厂的Tars，有grpc的劲敌Apache Thrift，还有阿里的Dubbo等等，都是值得学习研究的。上文中如有错误或不当的地方，也欢迎各位指正与交流。&lt;/p&gt;

</description>
        <pubDate>Mon, 20 Aug 2018 19:51:19 +0000</pubDate>
        <link>http://windblog.cn/big-data/2018/08/20/google-pb/</link>
        <guid isPermaLink="true">http://windblog.cn/big-data/2018/08/20/google-pb/</guid>
        
        <category>protobuf</category>
        
        <category>大数据</category>
        
        
        <category>big-data</category>
        
      </item>
    
  </channel>
</rss>
