-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrss2.xml
255 lines (132 loc) · 133 KB
/
rss2.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Yuanqian's Blog</title>
<link>http://zhangyuanqian.top/</link>
<atom:link href="http://zhangyuanqian.top/rss2.xml" rel="self" type="application/rss+xml"/>
<description>Stay hungry stay foolish!</description>
<pubDate>Tue, 29 Mar 2022 01:51:01 GMT</pubDate>
<generator>http://hexo.io/</generator>
<item>
<title>各大框架都在使用的Unsafe类,到底有多神奇?</title>
<link>http://zhangyuanqian.top/2022/03/29/%E5%90%84%E5%A4%A7%E6%A1%86%E6%9E%B6%E9%83%BD%E5%9C%A8%E4%BD%BF%E7%94%A8%E7%9A%84Unsafe%E7%B1%BB%EF%BC%8C%E5%88%B0%E5%BA%95%E6%9C%89%E5%A4%9A%E7%A5%9E%E5%A5%87%EF%BC%9F/</link>
<guid>http://zhangyuanqian.top/2022/03/29/%E5%90%84%E5%A4%A7%E6%A1%86%E6%9E%B6%E9%83%BD%E5%9C%A8%E4%BD%BF%E7%94%A8%E7%9A%84Unsafe%E7%B1%BB%EF%BC%8C%E5%88%B0%E5%BA%95%E6%9C%89%E5%A4%9A%E7%A5%9E%E5%A5%87%EF%BC%9F/</guid>
<pubDate>Tue, 29 Mar 2022 01:32:00 GMT</pubDate>
<description><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><hr>
<p>几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe,比如Netty、Cassandra、Hadoop、Kafka等。<br></p>
<p>Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下,不属于Java标准。<br></p>
<p>很早之前,在阅读并发编程相关类的源码时,看到Unsafe类,产生了一个疑惑:既然是并发编程中用到的类,为什么命名为Unsafe呢?<br></p>
<p>深入了解之后才知道,这里的Unsafe并不是说线程安全与否,而是指:该类对于普通的程序员来说是”危险“的,一般应用开发者不会也不应该用到此类。<br></p>
<p>因为Unsafe类功能过于强大,提供了一些可以绕开JVM的更底层功能。它让Java拥有了像C语言的指针一样操作内存空间的能力,能够提升效率,但也带来了指针的问题。官方并不建议使用,也没提供文档支持,甚至计划在高版本中去掉该类。<br></p>
<p>但对于开发者来说,了解该类提供的功能更有助于我们学习CAS、并发编程等相关的知识,还是非常有必要学习和了解的。<br></p></description>
<content:encoded><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><hr><p>几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe,比如Netty、Cassandra、Hadoop、Kafka等。<br></p><p>Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下,不属于Java标准。<br></p><p>很早之前,在阅读并发编程相关类的源码时,看到Unsafe类,产生了一个疑惑:既然是并发编程中用到的类,为什么命名为Unsafe呢?<br></p><p>深入了解之后才知道,这里的Unsafe并不是说线程安全与否,而是指:该类对于普通的程序员来说是”危险“的,一般应用开发者不会也不应该用到此类。<br></p><p>因为Unsafe类功能过于强大,提供了一些可以绕开JVM的更底层功能。它让Java拥有了像C语言的指针一样操作内存空间的能力,能够提升效率,但也带来了指针的问题。官方并不建议使用,也没提供文档支持,甚至计划在高版本中去掉该类。<br></p><p>但对于开发者来说,了解该类提供的功能更有助于我们学习CAS、并发编程等相关的知识,还是非常有必要学习和了解的。<br></p><span id="more"></span><h4 id="Unsafe的构造"><a href="#Unsafe的构造" class="headerlink" title="Unsafe的构造"></a>Unsafe的构造</h4><hr><p>Unsafe类是”final”的,不允许继承,且构造函数是private,使用了单例模式来通过一个静态方法getUnsafe()来获取。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Unsafe</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CallerSensitive</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Unsafe <span class="title">getUnsafe</span><span class="params">()</span> </span>{</span><br><span class="line"> Class var0 = Reflection.getCallerClass();</span><br><span class="line"> <span class="keyword">if</span> (!VM.isSystemDomainLoader(var0.getClassLoader())) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SecurityException(<span class="string">"Unsafe"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> theUnsafe;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>在getUnsafe方法中对单例模式中的对象创建做了限制,如果是普通的调用会抛出一个SecurityException异常。只有由主类加载器加载的类才能调用这个方法。<br></p><p>那么,如何获得Unsafe类的对象呢?通常采用反射机制:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Unsafe <span class="title">getUnsafe</span><span class="params">()</span> <span class="keyword">throws</span> IllegalAccessException </span>{</span><br><span class="line"> Field unsafeField = Unsafe.class.getDeclaredFields()[<span class="number">0</span>];</span><br><span class="line"> unsafeField.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">return</span> (Unsafe) unsafeField.get(<span class="keyword">null</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当获得Unsafe对象之后,就可以”为所欲为“了。下面就来看看,通过Unsafe方法,我们可以做些什么。<br></p><h4 id="Unsafe的主要功能"><a href="#Unsafe的主要功能" class="headerlink" title="Unsafe的主要功能"></a>Unsafe的主要功能</h4><p>可先从根据下图从整体上了解一下Unsafe提供的功能:<br><br><img src="/images/pasted-80.png" alt="upload successful"><br>下面挑选重要的功能进行讲解。<br></p><h5 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h5><hr><p>Unsafe的内存管理功能主要包括:普通读写、volatile读写、有序写入、直接操作内存等分配内存与释放内存的功能。<br><br><strong>普通读写</strong><br>Unsafe可以读写一个类的属性,即便这个属性是私有的,也可以对这个属性进行读写。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取内存地址指向的整数</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">getInt</span><span class="params">(Object var1, <span class="keyword">long</span> var2)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将整数写入指定内存地址</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">putInt</span><span class="params">(Object var1, <span class="keyword">long</span> var2, <span class="keyword">int</span> var4)</span></span>;</span><br></pre></td></tr></table></figure><p>getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其他原始类型也提供有对应的方法。<br></p><p>另外,Unsafe的getByte、putByte方法提供了直接在一个地址上进行读写的功能。<br></p><p><strong>volatile读写</strong><br>普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取内存地址指向的整数,并支持volatile语义</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">getIntVolatile</span><span class="params">(Object var1, <span class="keyword">long</span> var2)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将整数写入指定内存地址,并支持volatile语义</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">putIntVolatile</span><span class="params">(Object var1, <span class="keyword">long</span> var2, <span class="keyword">int</span> var4)</span></span>;</span><br></pre></td></tr></table></figure><p>volatile读写要保证可见性和有序性,相对普通读写更加昂贵。<br><br><strong>有序写入</strong><br>有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将整数写入指定内存地址、有序或者有延迟的方法</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">putOrderedInt</span><span class="params">(Object var1, <span class="keyword">long</span> var2, <span class="keyword">int</span> var4)</span></span>;</span><br></pre></td></tr></table></figure><p>而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。<br><br><strong>直接操作内存</strong><br>Unsafe提供了直接操作内存的能力:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 分配内存</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">allocateMemory</span><span class="params">(<span class="keyword">long</span> var1)</span></span>;</span><br><span class="line"><span class="comment">// 重新分配内存</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">reallocateMemory</span><span class="params">(<span class="keyword">long</span> var1, <span class="keyword">long</span> var3)</span></span>;</span><br><span class="line"><span class="comment">// 内存初始化</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">setMemory</span><span class="params">(<span class="keyword">long</span> var1, <span class="keyword">long</span> var3, <span class="keyword">byte</span> var5)</span></span>;</span><br><span class="line"><span class="comment">// 内存复制</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">copyMemory</span><span class="params">(Object var1, <span class="keyword">long</span> var2, Object var4, <span class="keyword">long</span> var5, <span class="keyword">long</span> var7)</span></span>;</span><br><span class="line"><span class="comment">// 清除内存</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">freeMemory</span><span class="params">(<span class="keyword">long</span> var1)</span></span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对应操作内存,也提供了一些获取内存信息的方法:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取内存地址</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">getAddress</span><span class="params">(<span class="keyword">long</span> var1)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">addressSize</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">pageSize</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>值得注意的是:利用copyMemory方法可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,但只能做到对象浅拷贝。<br></p><h5 id="非常规对象实例化"><a href="#非常规对象实例化" class="headerlink" title="非常规对象实例化"></a>非常规对象实例化</h5><p>通常,我们通过new或反射来实例化对象,而Unsafe类提供的allocateInstance方法,可以直接生成对象实例,且无需调用构造方法和其他初始化方法。<br></p><p>这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 直接生成对象实例,不会调用这个实例的构造方法</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> Object <span class="title">allocateInstance</span><span class="params">(Class<?> var1)</span> <span class="keyword">throws</span> InstantiationException</span>;</span><br></pre></td></tr></table></figure><h5 id="类加载"><a href="#类加载" class="headerlink" title="类加载"></a>类加载</h5><p>通过以下方法,可以实现类的定义、创建等操作。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方法定义一个类,用于动态地创建类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">native</span> Class<?> defineClass(String var1, <span class="keyword">byte</span>[] var2, <span class="keyword">int</span> var3, <span class="keyword">int</span> var4, ClassLoader var5, ProtectionDomain var6);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态的创建一个匿名内部类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">native</span> Class<?> defineAnonymousClass(Class<?> var1, <span class="keyword">byte</span>[] var2, Object[] var3);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断是否需要初始化一个类</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">shouldBeInitialized</span><span class="params">(Class<?> var1)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 保证已经初始化过一个类</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">ensureClassInitialized</span><span class="params">(Class<?> var1)</span></span>;</span><br></pre></td></tr></table></figure><h5 id="偏移量相关"><a href="#偏移量相关" class="headerlink" title="偏移量相关"></a>偏移量相关</h5><p>Unsafe提供以下方法获取对象的指针,通过对指针进行偏移,不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">staticFieldOffset</span><span class="params">(Field var1)</span></span>;</span><br><span class="line"><span class="comment">// 获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">long</span> <span class="title">objectFieldOffset</span><span class="params">(Field var1)</span></span>;</span><br><span class="line"><span class="comment">// 返回Field所在的对象</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> Object <span class="title">staticFieldBase</span><span class="params">(Field var1)</span></span>;</span><br><span class="line"><span class="comment">// 返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">arrayBaseOffset</span><span class="params">(Class<?> var1)</span></span>;</span><br><span class="line"><span class="comment">// 计算数组中第一个元素所占用的内存空间</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">arrayIndexScale</span><span class="params">(Class<?> var1)</span></span>;</span><br></pre></td></tr></table></figure><h5 id="数组操作"><a href="#数组操作" class="headerlink" title="数组操作"></a>数组操作</h5><p>数组操作提供了以下方法:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取数组第一个元素的偏移地址</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">arrayBaseOffset</span><span class="params">(Class<?> var1)</span></span>;</span><br><span class="line"><span class="comment">// 获取数组中元素的增量地址</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">arrayIndexScale</span><span class="params">(Class<?> var1)</span></span>;</span><br></pre></td></tr></table></figure><p>arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。<br></p><p>由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。<br></p><h5 id="线程调度"><a href="#线程调度" class="headerlink" title="线程调度"></a>线程调度</h5><p>线程调度相关方法如下:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 唤醒线程</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">unpark</span><span class="params">(Object var1)</span></span>;</span><br><span class="line"><span class="comment">// 挂起线程</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">park</span><span class="params">(<span class="keyword">boolean</span> var1, <span class="keyword">long</span> var2)</span></span>;</span><br><span class="line"><span class="comment">// 用于加锁,已废弃</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">monitorEnter</span><span class="params">(Object var1)</span></span>;</span><br><span class="line"><span class="comment">// 用于加锁,已废弃</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">monitorExit</span><span class="params">(Object var1)</span></span>;</span><br><span class="line"><span class="comment">// 用于加锁,已废弃</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">tryMonitorEnter</span><span class="params">(Object var1)</span></span>;</span><br></pre></td></tr></table></figure><p>通过park方法将线程进行挂起, 线程将一直阻塞到超时或中断条件出现。unpark方法可以终止一个挂起的线程,使其恢复正常。<br></p><p>整个并发框架中对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。<br></p><h5 id="CAS操作"><a href="#CAS操作" class="headerlink" title="CAS操作"></a>CAS操作</h5><p>Unsafe类的CAS操作可能是使用最多的方法。它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">compareAndSwapObject</span><span class="params">(Object var1, <span class="keyword">long</span> var2, Object var4, Object var5)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">compareAndSwapInt</span><span class="params">(Object var1, <span class="keyword">long</span> var2, <span class="keyword">int</span> var4, <span class="keyword">int</span> var5)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">compareAndSwapLong</span><span class="params">(Object var1, <span class="keyword">long</span> var2, <span class="keyword">long</span> var4, <span class="keyword">long</span> var6)</span></span>;</span><br></pre></td></tr></table></figure><p>CAS一般用于乐观锁,它在Java中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS来实现乐观锁。<br></p><h5 id="内存屏障"><a href="#内存屏障" class="headerlink" title="内存屏障"></a>内存屏障</h5><p>JDK8新引入了用于定义内存屏障、避免代码重排的方法:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 保证在这个屏障之前的所有读操作都已经完成</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">loadFence</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 保证在这个屏障之前的所有写操作都已经完成</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">storeFence</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 保证在这个屏障之前的所有读写操作都已经完成</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">fullFence</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><h5 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h5><p>当然,Unsafe类中还提供了大量其他的方法,比如上面提到的CAS操作,以AtomicInteger为例,当我们调用getAndIncrement、getAndDecrement等方法时,本质上调用的就是Unsafe的getAndAddInt方法。<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">getAndIncrement</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> unsafe.getAndAddInt(<span class="keyword">this</span>, valueOffset, <span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">getAndDecrement</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> unsafe.getAndAddInt(<span class="keyword">this</span>, valueOffset, -<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在实践的过程中,如果阅读其他框架或类库实现,当发现用到Unsafe类,可对照该类的整体功能,结合应用场景进行分析,即可大概了解其功能。</p><h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><hr><p>经过本文的分析,想必大家在阅读源码时,再遇到Unsafe类的调用,一定大概猜出它是用来干什么的。使用Unsafe类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。<br></p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/Java/">Java</category>
<comments>http://zhangyuanqian.top/2022/03/29/%E5%90%84%E5%A4%A7%E6%A1%86%E6%9E%B6%E9%83%BD%E5%9C%A8%E4%BD%BF%E7%94%A8%E7%9A%84Unsafe%E7%B1%BB%EF%BC%8C%E5%88%B0%E5%BA%95%E6%9C%89%E5%A4%9A%E7%A5%9E%E5%A5%87%EF%BC%9F/#disqus_thread</comments>
</item>
<item>
<title>面试官问我:了解Redis哨兵机制吗?7张图详解!</title>
<link>http://zhangyuanqian.top/2022/03/24/%E9%9D%A2%E8%AF%95%E5%AE%98%E9%97%AE%E6%88%91%EF%BC%9A%E4%BA%86%E8%A7%A3Redis%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6%E5%90%97%EF%BC%9F7%E5%BC%A0%E5%9B%BE%E8%AF%A6%E8%A7%A3%EF%BC%81/</link>
<guid>http://zhangyuanqian.top/2022/03/24/%E9%9D%A2%E8%AF%95%E5%AE%98%E9%97%AE%E6%88%91%EF%BC%9A%E4%BA%86%E8%A7%A3Redis%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6%E5%90%97%EF%BC%9F7%E5%BC%A0%E5%9B%BE%E8%AF%A6%E8%A7%A3%EF%BC%81/</guid>
<pubDate>Thu, 24 Mar 2022 01:26:00 GMT</pubDate>
<description><h4 id="先聊聊什么是哨兵机制"><a href="#先聊聊什么是哨兵机制" class="headerlink" title="先聊聊什么是哨兵机制?"></a>先聊聊什么是哨兵机制?</h4><p>Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:<br></p>
<p><strong>监控(Monitoring):</strong> 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。<br></p>
<p><strong>提醒(Notification):</strong> 当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。<br></p>
<p><strong>自动故障迁移(Automatic failover):</strong> 当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。<br></p></description>
<content:encoded><![CDATA[<h4 id="先聊聊什么是哨兵机制"><a href="#先聊聊什么是哨兵机制" class="headerlink" title="先聊聊什么是哨兵机制?"></a>先聊聊什么是哨兵机制?</h4><p>Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:<br></p><p><strong>监控(Monitoring):</strong> 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。<br></p><p><strong>提醒(Notification):</strong> 当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。<br></p><p><strong>自动故障迁移(Automatic failover):</strong> 当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。<br></p><span id="more"></span><h5 id="为什么要有哨兵机制?"><a href="#为什么要有哨兵机制?" class="headerlink" title="为什么要有哨兵机制?"></a>为什么要有哨兵机制?</h5><p>哨兵机制的出现是为了解决主从复制的缺点的!再这谈谈redis的主从复制的缺点:<br></p><ul><li>主从复制,若主节点出现问题,则不能提供服务,需要人工修改配置将从变主</li><li>主从复制主节点的写能力单机,能力有限</li><li>单机节点的存储能力也有限</li></ul><h5 id="哨兵机制-sentinel-的高可用"><a href="#哨兵机制-sentinel-的高可用" class="headerlink" title="哨兵机制(sentinel)的高可用"></a>哨兵机制(sentinel)的高可用</h5><p><strong>原理:</strong> 当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。<br><img src="/images/pasted-70.png" alt="upload successful"></p><p>其实整个过程只需要一个哨兵节点来完成,首先使用Raft算法(选举算法)实现选举机制,选出一个哨兵节点来完成转移和通知</p><h5 id="哨兵的定时监控任务"><a href="#哨兵的定时监控任务" class="headerlink" title="哨兵的定时监控任务"></a>哨兵的定时监控任务</h5><p><strong>任务1:</strong> 每个哨兵节点每10秒会向主节点和从节点发送info命令获取最拓扑结构图,哨兵配置时只要配置对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有新的从节点加入时可以马上感知到。<br><img src="/images/pasted-71.png" alt="upload successful"><br><strong>任务2:</strong> 每个哨兵节点每隔2秒会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其它哨兵节点的信息及对主节点的判断,其实就是通过消息publish和subscribe来完成的。<br><img src="/images/pasted-72.png" alt="upload successful"><br><strong>任务3:</strong> 每隔1秒每个哨兵会向主节点、从节点及其余哨兵节点发送一次ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据。<br><img src="/images/pasted-73.png" alt="upload successful"><br><strong>客观下线:</strong> 当主观下线的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,当超过quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线。</p><h5 id="领导者哨兵选举流程"><a href="#领导者哨兵选举流程" class="headerlink" title="领导者哨兵选举流程"></a>领导者哨兵选举流程</h5><ul><li>每个在线的哨兵节点都可以成为领导者,当它确认(比如哨兵3)主节点下线时,会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移;</li><li>当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;</li><li>如果哨兵3发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举…………</li></ul><h5 id="故障转移机制"><a href="#故障转移机制" class="headerlink" title="故障转移机制"></a>故障转移机制</h5><p><strong>由Sentinel节点定期监控发现主节点是否出现了故障:</strong> sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了<br><img src="/images/pasted-77.png" alt="upload successful"><br><strong>当主节点出现故障</strong>, 此时3个Sentinel节点共同选举了Sentinel3节点为领导,负载处理主节点的故障转移<br><img src="/images/pasted-78.png" alt="upload successful"><br><strong>由Sentinel3领导者节点执行故障转移,过程和主从复制一样,但是自动执行</strong><br><img src="/images/pasted-79.png" alt="upload successful"></p><p><strong>流程:</strong></p><ol><li>将slave-1脱离原从节点,升级主节点,</li><li>将从节点slave-2指向新的主节点</li><li>通知客户端主节点已更换</li><li>将原主节点(oldMaster)变成从节点,指向新的主节点</li></ol><p><strong>故障转移后的redis sentinel的拓扑结构图</strong></p><h5 id="哨兵机制-故障转移详细流程-确认主节点"><a href="#哨兵机制-故障转移详细流程-确认主节点" class="headerlink" title="哨兵机制-故障转移详细流程-确认主节点"></a>哨兵机制-故障转移详细流程-确认主节点</h5><ul><li>过滤掉不健康的(下线或断线),没有回复过哨兵ping响应的从节点</li><li>选择salve-priority从节点优先级最高(redis.conf)的</li><li>选择复制偏移量最大,此指复制最完整的从节点</li></ul><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p><strong>redis哨兵的作用:</strong></p><ol><li>监控主数据库和从数据库是否正常运行。</li><li>主数据库出现故障时,可以自动将从数据库转换为主数据库,实现自动切换。</li></ol>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/NoSQL/">NoSQL</category>
<comments>http://zhangyuanqian.top/2022/03/24/%E9%9D%A2%E8%AF%95%E5%AE%98%E9%97%AE%E6%88%91%EF%BC%9A%E4%BA%86%E8%A7%A3Redis%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6%E5%90%97%EF%BC%9F7%E5%BC%A0%E5%9B%BE%E8%AF%A6%E8%A7%A3%EF%BC%81/#disqus_thread</comments>
</item>
<item>
<title>Redis 究竟是单线程还是多线程呢?</title>
<link>http://zhangyuanqian.top/2022/03/01/Redis-%E7%A9%B6%E7%AB%9F%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%91%A2%EF%BC%9F/</link>
<guid>http://zhangyuanqian.top/2022/03/01/Redis-%E7%A9%B6%E7%AB%9F%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%91%A2%EF%BC%9F/</guid>
<pubDate>Tue, 01 Mar 2022 01:01:00 GMT</pubDate>
<description><p><img src="/images/pasted-55.png" alt="upload successful"><br>Redis一直以来都是高性能分布式缓存中间件的代表,我们经常说Redis是单线程的,也有人说Redis在6.0版本采用了多线程,那么Redis到底是采用单线程呢?还是多线程?本文我们来一探究竟</p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-55.png" alt="upload successful"><br>Redis一直以来都是高性能分布式缓存中间件的代表,我们经常说Redis是单线程的,也有人说Redis在6.0版本采用了多线程,那么Redis到底是采用单线程呢?还是多线程?本文我们来一探究竟</p><span id="more"></span><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>Redis到底是单线程还是多线程的?<br></p><p>首先,Redis是一个高性能的分布式缓存中间件。其复杂性不言而喻,对于Redis整体而言肯定不是只有一个线程。<br></p><p>我们常说的Redis 是单线程,主要是指 Redis 在网络 IO和键值对读写是采用一个线程来完成的,这也是 Redis 对外提供键值存储服务的核心流程。但对于 Redis 的其他功能来说,比如持久化、异步删除、集群数据同步等,其实都是由额外的线程执行的。<br></p><h4 id="为什么要融入多线程?"><a href="#为什么要融入多线程?" class="headerlink" title="为什么要融入多线程?"></a>为什么要融入多线程?</h4><p>单线程的优势:</p><ul><li>使用单线程可以避免频繁的上下文切换</li><li>Redis 中有各种类型的数据操作,甚至包括一些事务处理,如果采用多线程,还可能因为加锁导致软件复杂度提升,更有可能会因为加解锁,甚至出现死锁,造成的性能损耗,所以使用单线程反而性能会更好</li></ul><p>单线程的劣势:</p><ul><li>无法发挥多核CPU的优势</li><li>当删除大建,会导致服务阻塞</li><li>QPS达到瓶颈</li></ul><p>基于上诉劣势,Redis也进行了相关优化,在4.0版本和6.0版本分别引入了Lazy Free和多线程IO。</p><h4 id="Redis单线程模型"><a href="#Redis单线程模型" class="headerlink" title="Redis单线程模型"></a>Redis单线程模型</h4><p>Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。<br></p><p>文件事件处理器的四个重要组成部分:<br></p><ul><li>多个套接字请求</li><li>IO多路复用器</li><li>文件事件派发器</li><li>事件处理器</li></ul><p>Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型,如下图所示。<br></p><p><img src="/images/pasted-56.png" alt="upload successful"></p><h4 id="带你理解Redis处理流程"><a href="#带你理解Redis处理流程" class="headerlink" title="带你理解Redis处理流程"></a>带你理解Redis处理流程</h4><h5 id="Redis-4-0之前的事件处理流程"><a href="#Redis-4-0之前的事件处理流程" class="headerlink" title="Redis 4.0之前的事件处理流程"></a>Redis 4.0之前的事件处理流程</h5><p>我们先介绍一下Redis( 4.0之前)的执行原理,我们通过IO多路复用器监听来自客户端的socket网络连接,然后由主线程进行IO请求的处理以及命令的处理,所有操作都是线性的,我们可以抽象的理解为下图。<br></p><p><img src="/images/pasted-57.png" alt="upload successful"></p><h5 id="Redis-4-0-之后加入Lazy-Free机制"><a href="#Redis-4-0-之后加入Lazy-Free机制" class="headerlink" title="Redis 4.0 之后加入Lazy Free机制"></a>Redis 4.0 之后加入Lazy Free机制</h5><p>Redis 4.0 之前在处理客户端命令和IO操作时都是以单线程形式运行,期间不会响应其他客户端请求,但若客户端向Redis发送一条耗时较长的命令,比如删除一个含有上百万对象的Set键,或者执行flushdb,flushall操作,Redis服务器需要回收大量的内存空间,这事就会导致Redis服务阻塞,对于负载较高的缓存系统来说将会是个灾难。为了解决这个问题,在Redis 4.0版本引入了Lazy Free,目的是将慢操作异步化,这也是在事件处理上向多线程迈进了一步,其过程我们可以理解为下图。</p><p><img src="/images/pasted-58.png" alt="upload successful"></p><h5 id="Redis-6-0-之后将网络IO异步化"><a href="#Redis-6-0-之后将网络IO异步化" class="headerlink" title="Redis 6.0 之后将网络IO异步化"></a>Redis 6.0 之后将网络IO异步化</h5><p>从以上的发展历程中,我们也能看出Redis 的瓶颈并不在CPU上,即使是单线程Redis也能做到很快的响应,除非是遇到个别极其耗时的命令,这一块我们的Redis也在4.0版本做出了优化,但是我们是不是能更进一步优化Redis呢?从上图中我们可以看出主线程不光处理大量的命令,还需要处理大量的网络IO,Redis6.0就是基于此,将IO操作交由其他线程处理,抽象的理解如下图所示。</p><p><img src="/images/pasted-69.png" alt="upload successful"></p><p>Redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件:io-threads-do-reads yes <br></p><p>开启多线程后,还需要设置线程数,否则是不生效的。<br></p><p>线程数一定要小于机器核数。线程数并不是越大越好,官方认为超过了 8 个基本就没什么意义了。<br></p><p>设置线程数,修改redis.conf配置文件: io-threads <br></p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
<comments>http://zhangyuanqian.top/2022/03/01/Redis-%E7%A9%B6%E7%AB%9F%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%91%A2%EF%BC%9F/#disqus_thread</comments>
</item>
<item>
<title>如何应对数据库缓存双写一致性问题</title>
<link>http://zhangyuanqian.top/2022/02/24/%E5%A6%82%E4%BD%95%E5%BA%94%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BC%93%E5%AD%98%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/</link>
<guid>http://zhangyuanqian.top/2022/02/24/%E5%A6%82%E4%BD%95%E5%BA%94%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BC%93%E5%AD%98%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/</guid>
<pubDate>Thu, 24 Feb 2022 01:45:00 GMT</pubDate>
<description><p><img src="/images/pasted-47.png" alt="upload successful"></p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-47.png" alt="upload successful"></p><span id="more"></span><h5 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h5><hr><p>日常开发中,我们接口响应缓慢,往往是因为数据库读写产生的,这时为了优化这些接口,往往我们会将数据库的数据写入缓存中,让接口直接从缓存中获取数据,这样能极大的提高接口的访问速度。但是随之而来的问题就是,在我们更新数据库的数据时,就需要去更新缓存中的数据,或者是删除缓存中的数据,让其再次访问时通过读数据库,再将读到的数据刷入缓存中。但是如果在这期间出现并发,就很容易导致数据库缓存中数据不一致,这也是本篇文章的主题如何应对数据库缓存双写一致性问题。<br><br>这里我们说的一致性是指最终一致性,并不是强一致性。<br></p><h5 id="推荐方案-Cache-Aside-Pattern"><a href="#推荐方案-Cache-Aside-Pattern" class="headerlink" title="推荐方案 Cache Aside Pattern"></a>推荐方案 Cache Aside Pattern</h5><hr><p>命中:程序从缓存读取数据,读到数据即命中<br></p><p>失效:程序从缓存读取数据,未读到数据,此时缓存失效,需要先去数据库读取,再刷入缓存<br></p><p>更新:先更新数据库,再删除缓存<br></p><h5 id="更新方案"><a href="#更新方案" class="headerlink" title="更新方案"></a>更新方案</h5><hr><ul><li><p>更新缓存</p></li><li><p>先更新缓存,再更新数据库</p></li><li><p>先更新数据库,再更新缓存</p></li><li><p>删除缓存</p></li><li><p>先删除缓存,再更新数据库</p></li><li><p>先更新数据库,再删除缓存(常用)</p></li></ul><h5 id="更新方案选择原因"><a href="#更新方案选择原因" class="headerlink" title="更新方案选择原因"></a>更新方案选择原因</h5><hr><h6 id="先更新缓存,再更新数据库"><a href="#先更新缓存,再更新数据库" class="headerlink" title="先更新缓存,再更新数据库"></a>先更新缓存,再更新数据库</h6><p>如下图,当A线程更新完缓存的数据A,这时A线程出现延迟,B线程将A线程缓存的更新覆盖,并且将数据库中的数据A也更新,A线程恢复去更新数据库,这时又将B线程对数据库的修改覆盖,这样就会出现严重的双写不一致,导致后续每次读取到的缓存中的数据都是有问题的,并且数据库的数据对我们来说更为重要,我们一般持久化都是依赖数据库的,如果先更新缓存的话,后续程序宕机,数据库中的数据就得不到更新,我们一般是不会依赖缓存做持久化保存的,所以这种方案是一定不能选择的。</p><p><img src="/images/pasted-48.png" alt="upload successful"></p><h6 id="先更新数据库,再更新缓存"><a href="#先更新数据库,再更新缓存" class="headerlink" title="先更新数据库,再更新缓存"></a>先更新数据库,再更新缓存</h6><p>如下图,当A线程更新完数据库的数据A,这时A线程出现延迟,B线程将A线程数据库的更新覆盖,并且将缓存中的数据A也更新,A线程恢复去更新缓存,这时又将B线程对缓存的修改覆盖,这样就会出现严重的双写不一致,导致后续每次取到缓存中的数据都是有问题的。</p><p><img src="/images/pasted-49.png" alt="upload successful"></p><h6 id="先删除缓存,再更新数据库"><a href="#先删除缓存,再更新数据库" class="headerlink" title="先删除缓存,再更新数据库"></a>先删除缓存,再更新数据库</h6><p>如下图,当A线程删除缓存数据A,这时A线程出现延迟,B线程将读取A,发现缓存无数据,将数据库A的旧值查出来,并且将其更新到缓存中,当A线程恢复时,又将A的新值写入数据库,这样也会出现严重的双写不一致,导致后续每次取到缓存中的数据都是有问题的。</p><p><img src="/images/pasted-50.png" alt="upload successful"></p><p>所以,先删除缓存的方案也不建议选择。</p><h6 id="先更新数据库,再删除缓存(常用)"><a href="#先更新数据库,再删除缓存(常用)" class="headerlink" title="先更新数据库,再删除缓存(常用)"></a>先更新数据库,再删除缓存(常用)</h6><p>先更新数据库再删除缓存这种方案是我们所选择的,当然这种也有几率出现缓存不一致的现象,当缓存失效时,就会出现和先删除缓存,在更新数据库一样的问题。如下图:</p><p><img src="/images/pasted-51.png" alt="upload successful"></p><p>但是一般情况下,是不会出现上述情况,出现上述情况的机率是特别低的。出现上述情况也可以采取延迟双删,先删除一次,让线程休眠一会,再删除一次,就会将不小心写入的错误数据清掉。</p><p>所以说这种方案只会出现下一种情况,如果想要避免这种情况只能通过加锁来解决,避免读到脏数据</p><p><img src="/images/pasted-52.png" alt="upload successful"></p><h5 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h5><hr><p>基于上述方案我们还可以做哪些优化</p><ul><li>读数据加锁(分布式锁)防止高并发打垮数据库</li><li>延迟双删,防止缓存失效时(读写分离架构下,读从库延迟问题),存入旧数据,第二次删除可以异步执行等待删除</li><li>如果需要做重试机制可以依赖于消息队列的可靠消费</li><li>可以通过订阅Binlog日志来优化删除逻辑</li></ul><p>禁忌:过度设计,一般简单的延迟双删就可以实现需求,无需增加系统复杂度</p><p><img src="/images/pasted-54.png" alt="upload successful"></p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
<comments>http://zhangyuanqian.top/2022/02/24/%E5%A6%82%E4%BD%95%E5%BA%94%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BC%93%E5%AD%98%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98/#disqus_thread</comments>
</item>
<item>
<title>多张图带你彻底搞懂DNS域名解析过程</title>
<link>http://zhangyuanqian.top/2022/02/22/%E5%A4%9A%E5%BC%A0%E5%9B%BE%E5%B8%A6%E4%BD%A0%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82DNS%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B/</link>
<guid>http://zhangyuanqian.top/2022/02/22/%E5%A4%9A%E5%BC%A0%E5%9B%BE%E5%B8%A6%E4%BD%A0%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82DNS%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B/</guid>
<pubDate>Tue, 22 Feb 2022 01:59:00 GMT</pubDate>
<description><p><img src="/images/pasted-38.png" alt="upload successful"></p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-38.png" alt="upload successful"></p><span id="more"></span><h4 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h4><hr><p>DNS(Domain Name System)是域名系统的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,用于 TCP/IP 网络。</p><h4 id="域名系统DNS-的作用"><a href="#域名系统DNS-的作用" class="headerlink" title="域名系统DNS 的作用"></a>域名系统DNS 的作用</h4><hr><p>通常我们有两种方式识别主机:通过主机名或者 IP 地址。人们喜欢便于记忆的主机名表示,而路由器则喜欢定长的、有着层次结构的 IP 地址。为了满足这些不同的偏好,我们就需要一种能够进行主机名到IP 地址转换的目录服务,域名系统作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。<br><br>因此,即使不使用域名也可以通过IP地址来寻址目的主机,但域名与IP地址相比,便于人们记忆。因此对于大多数网络应用,我们一般使用域名来访问目的主机,而不是直接使用IP地址来访问。<br><br>对于本例,简单来说,当我们在浏览器地址栏中输入某个Web服务器的域名时。用户主机首先用户主机会首先在自己的DNS高速缓存中查找该域名所应的IP地址。<br></p><p><img src="/images/pasted-39.png" alt="upload successful"><br>如果没有找到,则会向网络中的某台DNS服务器查询,DNS服务器中有域名和IP地映射关系的数据库。当DNS服务器收到DNS查询报文后,在其数据库中查询,之后将查询结果发送给用户主机。<br><br><img src="/images/pasted-40.png" alt="upload successful"></p><p>现在,用户主机中的浏览器可以通过Web服务器的IP地址对其进行访问了。<br></p><p><img src="/images/pasted-41.png" alt="upload successful"></p><h4 id="域名的层级关系"><a href="#域名的层级关系" class="headerlink" title="域名的层级关系"></a>域名的层级关系</h4><hr><h5 id="层级关系特点"><a href="#层级关系特点" class="headerlink" title="层级关系特点"></a>层级关系特点</h5><ul><li>因特网采用层次树状结构的域名结构</li><li>域名的结构由若干个分量组成,各分量之间用“点”隔开,分别代表不同级别的域名。<ul><li>每一级的域名都由英文字母和数字组成,不超过63个字符,不区分大小写字母。</li><li>级别最低的域名写在最左边,而级别最高的顶级域名写在最右边。</li><li>完整的域名不超过255个字符。</li></ul></li><li>域名系统既不规定一个域名需要包含多少个下级域名,也不规定每一级的域名代表什么意思。</li><li>各级域名由其上一级的域名管理机构管理,而最高的顶级域名则由因特网名称与数字地址分配机构ICANN进行管理。</li></ul><h5 id="因特网的域名空间"><a href="#因特网的域名空间" class="headerlink" title="因特网的域名空间"></a>因特网的域名空间</h5><p><img src="/images/pasted-42.png" alt="upload successful"></p><p>上图展示了 DNS 服务器的部分层次结构,从上到下依次为根域名服务器、顶级域名服务器和权威域名服务器。域名和IP地址的映射关系必须保存在域名服务器中,供所有其他应用查询。显然不能将所有信息都储存在一台域名服务器中。DNS使用分布在各地的域名服务器来实现域名到IP地址的转换。<br></p><h5 id="域名服务器可以划分为以下四种不同的类型"><a href="#域名服务器可以划分为以下四种不同的类型" class="headerlink" title="域名服务器可以划分为以下四种不同的类型:"></a>域名服务器可以划分为以下四种不同的类型:</h5><ul><li>根域名服务器:根域名服务器是最高层次的域名服务器。每个根域名服务器都知道所有的顶级域名服务器的域名及其IP地址。因特网上共有13个不同IP地址的根域名服务器。当本地域名服务器向根域名服务器发出查询请求时,路由器就把查询请求报文转发到离这个DNS客户最近的一个根域名服务器。这就加快了DNS的查询过程,同时也更合理地利用了因特网的资源。</li><li>顶级域名服务器:这些域名服务器负责管理在该顶级域名服务器注册的所有二级域名。当收到DNS查询请求时就给出相应的回答(可能是最后的结果,也可能是下一级权限域名服务器的IP地址)。</li><li>权限域名服务器:这些域名服务器负责管理某个区的域名。每一个主机的域名都必须在某个权限域名服务器处注册登记。因此权限域名服务器知道其管辖的域名与IP地址的映射关系。另外,权限域名服务器还知道其下级域名服务器的地址。</li><li>本地域名服务器:本地域名服务器不属于上述的域名服务器的等级结构。当一个主机发出DNS请求报文时,这个报文就首先被送往该主机的本地域名服务器。本地域名服务器起着代理的作用,会将该报文转发到上述的域名服务器的等级结构中。本地域名服务器离用户较近,一般不超过几个路由器的距离,也有可能就在同一个局域网中。本地域名服务器的IP地址需要直接配置在需要域名解析的主机中。</li></ul><h4 id="DNS-域名解析过程"><a href="#DNS-域名解析过程" class="headerlink" title="DNS 域名解析过程"></a>DNS 域名解析过程</h4><hr><p>域名解析包含两种查询方式,分别是递归查询和迭代查询。</p><h5 id="递归查询"><a href="#递归查询" class="headerlink" title="递归查询"></a>递归查询</h5><p>如果主机所询问的本地域名服务器不知道被查询域名的 IP 地址,那么本地域名服务器就以 DNS 客户端的身份,向其他根域名服务器继续发出查询请求报文,即替主机继续查询,而不是让主机自己进行下一步查询。<br><br>我们以一个例子来了解DNS递归查询的工作原理,假设图中的主机 (IP地址为m.xyz.com) 想知道域名y.abc.com的IP地址。<br></p><ul><li>1、我们以一个例子来了解DNS递归查询的工作原理,假设图中的主机 (IP地址为m.xyz.com) 想知道域名y.abc.com的IP地址。</li><li>2、本地域名服务器收到递归查询的委托后,也采用递归查询的方式向某个根域名服务器查询。</li><li>3、根域名服务器收到递归查询的委托后,也采用递归查询的方式向某个顶级域名服务器查询。</li><li>4、顶级域名服务器收到递归查询的委托后,也采用递归查询的方式向某个权限域名服务器查询。</li></ul><p>过程如图所示:</p><p><img src="/images/pasted-43.png" alt="upload successful"></p><p>当查询到域名对应的IP地址后,查询结果会在之前受委托的各域名服务器之间传递,最终传回给用户主机。</p><p>过程如图所示:</p><p><img src="/images/pasted-44.png" alt="upload successful"></p><h5 id="迭代查询"><a href="#迭代查询" class="headerlink" title="迭代查询"></a>迭代查询</h5><p>当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP 地址,要么告诉本地服务器下一步应该找哪个域名服务器进行查询,然后让本地服务器进行后续的查询。<br></p><p>迭代查询过程如下:<br></p><ul><li>1、主机首先向其本地域名服务器进行递归查询。</li><li>2、本地域名服务器采用迭代查询,它先向某个根域名服务器查询。</li><li>3、根域名服务器告诉本地域名服务器,下一次应查询的顶级域名服务器的IP地址。</li><li>4、本地域名服务器向顶级域名服务器进行迭代查询。</li><li>5、顶级域名服务器告诉本地域名服务器,下一次应查询的权限域名服务器的IP地址。</li><li>6、本地域名服务器向权限域名服务器进行迭代查询。</li><li>7、权限域名服务器告诉本地域名服务器所查询的域名的IP地址。</li><li>8、本地域名服务器最后把查询的结果告诉主机。</li></ul><p>过程如图所示:</p><p><img src="/images/pasted-45.png" alt="upload successful"></p><p>由于递归查询对于被查询的域名服务器负担太大,通常采用以下模式:从请求主机到本地域名服务器的查询是递归查询,而其余的查询是迭代查询。</p><h4 id="高速缓存"><a href="#高速缓存" class="headerlink" title="高速缓存"></a>高速缓存</h4><hr><p>为了提高DNS的查询效率,并减轻根域名服务器的负荷和减少因特网上的DNS查询报文数量,在域名服务器中广泛地使用了高速缓存。高速缓存用来存放最近查询过的域名以及从何处获得域名映射信息的记录。<br></p><p>由于域名到IP地址的映射关系并不是永久不变,为保持高速缓存中的内容正确,域名服务器应为每项内容设置计时器并删除超过合理时间的项(例如,每个项目只存放两天)。<br></p><p>不但在本地域名服务器中需要高速缓存,在用户主机中也很需要。许多用户主机在启动时从本地域名服务器下载域名和IP地址的全部数据库,维护存放自己最近使用的域名的高速缓存,并且只在从缓存中找不到域名时才向域名服务器查询。同理,主机也需要保持高速缓存中内容的正确性。<br></p><p>如图所示:</p><p><img src="/images/pasted-46.png" alt="upload successful"></p><p>如果本地域名服务器不久前已经有用户查询过域名为y.abc.com的IP地址,则本地域名服务器的高速缓存中应该存有该域名对应的IP地址。因此,直接把高速缓存中存放的上次查询结果(即y.abc.com的IP地址)告诉用户。</p><h4 id="DNS相关面试问题"><a href="#DNS相关面试问题" class="headerlink" title="DNS相关面试问题"></a>DNS相关面试问题</h4><hr><h5 id="DNS为什么用UDP?"><a href="#DNS为什么用UDP?" class="headerlink" title="DNS为什么用UDP?"></a>DNS为什么用UDP?</h5><p>更正确的答案是 DNS 既使用 TCP 又使用 UDP。当进行区域传送(主域名服务器向辅助域名服务器传送变化的那部分数据)时会使用 TCP,因为数据同步传送的数据量比一个请求和应答的数据量要多,而 TCP 允许的报文长度更长,因此为了保证数据的正确性,会使用基于可靠连接的 TCP。<br></p><p>当客户端向 DNS 服务器查询域名 ( 域名解析) 的时候,一般返回的内容不会超过 UDP 报文的最大长度,即 512 字节。用 UDP 传输时,不需要经过 TCP 三次握手的过程,从而大大提高了响应速度,但这要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。<br></p><h5 id="递归查询和递归查询区别?"><a href="#递归查询和递归查询区别?" class="headerlink" title="递归查询和递归查询区别?"></a>递归查询和递归查询区别?</h5><p>递归查询: 如果主机所询问的本地域名服务器不知道被查询域名的 IP 地址,那么本地域名服务器就以 DNS 客户端的身份,向其他根域名服务器继续发出查询请求报文,即替主机继续查询,而不是让主机自己进行下一步查询。<br></p><p>迭代查询: 当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP 地址,要么告诉本地服务器下一步应该找哪个域名服务器进行查询,然后让本地服务器进行后续的查询。<br></p><h5 id="讲讲DNS解析过程?"><a href="#讲讲DNS解析过程?" class="headerlink" title="讲讲DNS解析过程?"></a>讲讲DNS解析过程?</h5><p>详细解析过程请看上文DNS域名解析过程,这里我们做一个总结:<br></p><p>浏览器缓存——》系统hosts文件——》本地DNS解析器缓存——》本地域名服务器(本地配置区域资源、本地域名服务器缓存)——》根域名服务器——》主域名服务器——》下一级域名域名服务器 客户端——》本地域名服务器(递归查询) 本地域名服务器—》DNS服务器的交互查询是迭代查询</p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/%E7%BD%91%E7%BB%9C/">网络</category>
<comments>http://zhangyuanqian.top/2022/02/22/%E5%A4%9A%E5%BC%A0%E5%9B%BE%E5%B8%A6%E4%BD%A0%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82DNS%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B/#disqus_thread</comments>
</item>
<item>
<title>JDK15新特性体验</title>
<link>http://zhangyuanqian.top/2022/02/10/JDK15%E6%96%B0%E7%89%B9%E6%80%A7%E4%BD%93%E9%AA%8C/</link>
<guid>http://zhangyuanqian.top/2022/02/10/JDK15%E6%96%B0%E7%89%B9%E6%80%A7%E4%BD%93%E9%AA%8C/</guid>
<pubDate>Thu, 10 Feb 2022 07:01:00 GMT</pubDate>
<description><p><img src="/images/pasted-37.png" alt="upload successful"><br>JDK15发布共包含了14个新特性,其中不乏一些功能的二次预览。下面,就来看一下4个能影响到我们编码习惯的功能吧。<br></p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-37.png" alt="upload successful"><br>JDK15发布共包含了14个新特性,其中不乏一些功能的二次预览。下面,就来看一下4个能影响到我们编码习惯的功能吧。<br></p><span id="more"></span><p>JDK15可以配合IDEA 2020.2版本运行,测试前需要在Project Structure中修改Project language level开启对新特性的支持等级。<br><br><img src="/images/pasted-30.png" alt="upload successful"></p><h4 id="一、Sealed-Classes"><a href="#一、Sealed-Classes" class="headerlink" title="一、Sealed Classes"></a>一、Sealed Classes</h4><hr><p>Sealed Classes表示一个封闭类,它能够防止其他类或接口扩展或实现它们。当一个类被sealed关键字修饰时,只能通过已知的子类型列表进行扩展,而不能通过其他任何扩展。</p><p>看一个简单的示例,允许2个子类对其进行扩展:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> sealed <span class="class"><span class="keyword">class</span> <span class="title">Machine</span> <span class="title">permits</span> <span class="title">AirCondition</span>, <span class="title">Television</span> </span>{</span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> String name;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">work</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Machine</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name=name;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在permits 后的列表中的类可以正常继承父类,并扩展自己的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Television</span> <span class="keyword">extends</span> <span class="title">Machine</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Television</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">work</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(name +<span class="string">" is working"</span> );</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">play</span><span class="params">()</span></span>{</span><br><span class="line"> System.out.println(<span class="string">"Television can play movie"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但是当意图扩展一个不在permits 中的类时,编译时会告诉你无法继承:<br><img src="/images/pasted-31.png" alt="upload successful"></p><h4 id="二、Pattern-Matching-for-instanceof"><a href="#二、Pattern-Matching-for-instanceof" class="headerlink" title="二、Pattern Matching for instanceof"></a>二、Pattern Matching for instanceof</h4><hr><p>模式匹配功能在jdk14中就已经被预览过一次,本次为第二次预览。简单的说,该功能就是普通instanceof 的增强版。<br><br>该功能允许我们在instanceof 后面的类型后再添加一个变量名,避免了再创建一次局部变量,进行一次赋值过程。同时,还能够减少我们在进行强制类型转换时手动造成的错误。<br><br>在String后面添加一个变量名,并直接可被引用:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">instanceTest</span><span class="params">()</span></span>{</span><br><span class="line"> Object o=<span class="string">"pattern test"</span>;</span><br><span class="line"> <span class="keyword">if</span> (o <span class="keyword">instanceof</span> String str){</span><br><span class="line"> System.out.println(str);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,也可以在后面加其他的判断条件。首先构建一个简单的实体类:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Book</span> </span>{</span><br><span class="line"> String name;</span><br><span class="line"> <span class="keyword">double</span> price;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在instanceof 后面加上一个判断条件:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">instanceTest3</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> Object object</span>=<span class="keyword">new</span> Book(<span class="string">"Hydra monster"</span>,<span class="number">20.8</span>);</span><br><span class="line"> <span class="keyword">if</span> (object <span class="keyword">instanceof</span> Book book </span><br><span class="line"> && book.getName().equals(<span class="string">"Hydra monster"</span>)){</span><br><span class="line"> System.out.println(book.getPrice());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="三、Text-Blocks"><a href="#三、Text-Blocks" class="headerlink" title="三、Text Blocks"></a>三、Text Blocks</h4><hr><p>文本块功能已经在之前几版jdk中被预览过,在jdk15中转为正式功能。它允许我们自定义一个多行的字符串,可以避免使用大多数转义符号。并且可以让程序员按照自己的意愿控制文本块的输出格式。<br><br>文本块功能通过3个连续的双引号开启,同样以3个连续双引号关闭:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span> <span class="params">()</span> </span>{</span><br><span class="line"> String html = <span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string"> <html></span></span><br><span class="line"><span class="string"> <body></span></span><br><span class="line"><span class="string"> <p>text block, test</p></span></span><br><span class="line"><span class="string"> </body></span></span><br><span class="line"><span class="string"> </html> </span></span><br><span class="line"><span class="string"> "</span><span class="string">""</span>;</span><br><span class="line"> System.out.println(html);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出结果:<br><br><img src="/images/pasted-32.png" alt="upload successful"><br>怎么样,是不是减少了平常代码时很多的 \r 和 \n ,以及字符串的拼接操作。<br><br>另外,通过添加 \ 符号还可以控制禁止换行:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test2</span><span class="params">()</span></span>{</span><br><span class="line"> String sql = <span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string"> select * from user_info \</span></span><br><span class="line"><span class="string"> where \</span></span><br><span class="line"><span class="string"> user_name = 'Hydra'\</span></span><br><span class="line"><span class="string"> "</span><span class="string">""</span>;</span><br><span class="line"> System.out.println(sql);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出结果:<br><br><img src="/images/pasted-33.png" alt="upload successful"></p><h4 id="四、Records"><a href="#四、Records" class="headerlink" title="四、Records"></a>四、Records</h4><hr><p>Records 是一种新的类的声明形式,是一种受限制的类。经常听到一些同学抱怨,说java中有太多繁冗的get、set方法,在这种条件下lombok应景而生,而在jdk15中出现的Record可以说也具有类似的功能,在一些特定的场景下可以取代lombok。<br><br>定义一个record 的类:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> record <span class="title">Person</span><span class="params">(String name , String age)</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>初始化类并调用内置方法:<br></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> Person person</span>=<span class="keyword">new</span> Person(<span class="string">"Hydra"</span>,<span class="string">"18"</span>);</span><br><span class="line"> System.out.println(person);</span><br><span class="line"> System.out.println(person.age());</span><br><span class="line"> System.out.println(person.name());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:<br><img src="/images/pasted-34.png" alt="upload successful"><br>那么代码中为什么可以直接调用构造函数等没有实现的方法呢,看一下编译后的class文件就明白了:<br><img src="/images/pasted-35.png" alt="upload successful"><br>在编译后生成的类自动继承了Record父类,并且自动生成了构造方法、toString,hashCode,equals,以及成员变量获取值的方法。之前已经看到toString方法的输出方式和lombok相同,再来验证一下equals方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">equalsTest</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> Person person</span>=<span class="keyword">new</span> Person(<span class="string">"Hydra"</span>,<span class="string">"18"</span>);</span><br><span class="line"> Person person2=<span class="keyword">new</span> Person(<span class="string">"Trunks"</span>,<span class="string">"20"</span>);</span><br><span class="line"> Person person3=<span class="keyword">new</span> Person(<span class="string">"Hydra"</span>,<span class="string">"18"</span>);</span><br><span class="line"> System.out.println(person.equals(person2));</span><br><span class="line"> System.out.println(person.equals(person3));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果:<br><img src="/images/pasted-36.png" alt="upload successful"><br>可以看到,内置的equals方法很贴心的按照对象的属性值进行比较,而不是比较对象的内存地址。</p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/Java/">Java</category>
<comments>http://zhangyuanqian.top/2022/02/10/JDK15%E6%96%B0%E7%89%B9%E6%80%A7%E4%BD%93%E9%AA%8C/#disqus_thread</comments>
</item>
<item>
<title>nginx 的初级配置</title>
<link>http://zhangyuanqian.top/2022/02/09/nginx-%E7%9A%84%E5%88%9D%E7%BA%A7%E9%85%8D%E7%BD%AE/</link>
<guid>http://zhangyuanqian.top/2022/02/09/nginx-%E7%9A%84%E5%88%9D%E7%BA%A7%E9%85%8D%E7%BD%AE/</guid>
<pubDate>Wed, 09 Feb 2022 01:58:00 GMT</pubDate>
<description><p>本篇博客主要用于记录nginx.conf这一个文件如何修改的相关问题。<br><br>当nginx安装之后,默认的配置如下所示(数据来源为宝塔自动生成),本篇博客重点介绍的是配置虚拟机相关内容,即server 块配置项。server块的指令主要用于设置主机和端口,location块用于匹配网页路径,一个http块可以包含多个 server。</p></description>
<content:encoded><![CDATA[<p>本篇博客主要用于记录nginx.conf这一个文件如何修改的相关问题。<br><br>当nginx安装之后,默认的配置如下所示(数据来源为宝塔自动生成),本篇博客重点介绍的是配置虚拟机相关内容,即server 块配置项。server块的指令主要用于设置主机和端口,location块用于匹配网页路径,一个http块可以包含多个 server。</p><span id="more"></span><h3 id="基础配置"><a href="#基础配置" class="headerlink" title="基础配置"></a>基础配置</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">{</span><br><span class="line"> listen 80;</span><br><span class="line"> server_name www.域名.com;</span><br><span class="line"> index index.php index.html index.htm default.php default.htm default.html;</span><br><span class="line"> root /www/wwwroot/目录;</span><br><span class="line"> </span><br><span class="line"> #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则</span><br><span class="line"> #error_page 404/404.html;</span><br><span class="line"> #SSL-END</span><br><span class="line"> </span><br><span class="line"> #ERROR-PAGE-START 错误页配置,可以注释、删除或修改</span><br><span class="line"> #error_page 404 /404.html;</span><br><span class="line"> #error_page 502 /502.html;</span><br><span class="line"> #ERROR-PAGE-END</span><br><span class="line"> </span><br><span class="line"> #PHP-INFO-START PHP引用配置,可以注释或修改</span><br><span class="line"> include enable-php-73.conf;</span><br><span class="line"> #PHP-INFO-END</span><br><span class="line"> </span><br><span class="line"> #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效</span><br><span class="line"> include /www/server/panel/vhost/rewrite/域名伪静态文件.conf;</span><br><span class="line"> #REWRITE-END</span><br><span class="line"> </span><br><span class="line"> #禁止访问的文件或目录</span><br><span class="line"> location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)</span><br><span class="line"> {</span><br><span class="line"> return 404;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> #一键申请SSL证书验证目录相关设置</span><br><span class="line"> location ~ \.well-known{</span><br><span class="line"> allow all;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$</span><br><span class="line"> {</span><br><span class="line"> expires 30d;</span><br><span class="line"> error_log /dev/null;</span><br><span class="line"> access_log /dev/null;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> location ~ .*\.(js|css)?$</span><br><span class="line"> {</span><br><span class="line"> expires 12h;</span><br><span class="line"> error_log /dev/null;</span><br><span class="line"> access_log /dev/null; </span><br><span class="line"> }</span><br><span class="line"> access_log /www/wwwlogs/域名.log;</span><br><span class="line"> error_log /www/wwwlogs/域名.error.log;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里面要学习的第一个内容,就是各个配置的含义。<br></p><h3 id="配置清单"><a href="#配置清单" class="headerlink" title="配置清单"></a>配置清单</h3><hr><h4 id="虚拟主机监听的端口号"><a href="#虚拟主机监听的端口号" class="headerlink" title="虚拟主机监听的端口号"></a>虚拟主机监听的端口号</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">listen 80;</span><br></pre></td></tr></table></figure><h4 id="绑定的域名"><a href="#绑定的域名" class="headerlink" title="绑定的域名"></a>绑定的域名</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">server_name www.域名.com;</span><br></pre></td></tr></table></figure><p>多个域名用空格分隔。</p><h4 id="配置默认页"><a href="#配置默认页" class="headerlink" title="配置默认页"></a>配置默认页</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">index index.php index.html index.htm default.php default.htm default.html;</span><br></pre></td></tr></table></figure><h4 id="监听"><a href="#监听" class="headerlink" title="监听"></a>监听</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 匹配 URL</span><br><span class="line">location / {</span><br><span class="line"> # 访问路径,可以是相对路径或者绝对路径</span><br><span class="line"> root html;</span><br><span class="line"> index index.html index.htm;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里比较重要,尤其是 location 后面的规则,语法如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location [=|~|~*|^~] /uri/ {</span><br><span class="line"># 编写代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li> =:精确匹配;</li><li> <del>:区分大小写匹配(可用正则),与之对应的是 !</del>;</li><li> <del>*:不区分大小写的匹配(可用正则),与之对应的是 !</del>*;</li><li> ^~:以某个字符串开头;</li><li> /:通配符,任何请求都会匹配到;</li></ul><p>基于上述内容,再去观察上述默认配置,就能读懂了</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"># 当访问 user.ini,htaccess 等文件时,直接返回 404</span><br><span class="line">location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)</span><br><span class="line">{</span><br><span class="line"> return 404;</span><br><span class="line">}</span><br><span class="line"># 匹配 .well-known </span><br><span class="line">location ~ \.well-known{</span><br><span class="line"> allow all;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"># 匹配以 gif,jpg,jpeg等后缀结尾的文件</span><br><span class="line">location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$</span><br><span class="line">{</span><br><span class="line"> expires 30d;</span><br><span class="line"> error_log /dev/null;</span><br><span class="line"> access_log /dev/null;</span><br><span class="line">}</span><br><span class="line"># 匹配以 js,css 结尾的文件</span><br><span class="line">location ~ .*\.(js|css)?$</span><br><span class="line">{</span><br><span class="line"> expires 12h;</span><br><span class="line"> error_log /dev/null;</span><br><span class="line"> access_log /dev/null; </span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以在匹配到的规则内容中,编写防盗链代码</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">valid_referers none blocked 域名1.cn 域名1.cn;</span><br><span class="line"># 如果是</span><br><span class="line">if ($invalid_referer) {</span><br><span class="line"># 防盗链</span><br><span class="line">rewrite ^/ http://$host/logo.png;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中 valid_referers 语法如下</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">valid_referers [none|blocked|server_names]</span><br></pre></td></tr></table></figure><ul><li> none:默认值,表示无 referer 值的情况;</li><li> blocked:表示 referer 值被防火墙进行伪装;</li><li> server_names:域名列表,可以使用通配符 * 号。</li></ul><p>如果匹配到规则,那么会将 $invalid_referer 变量设置为 1。<br></p><p>这里又延伸出 nginx 全局变量相关内容,这个还有有必要记忆一下的,基于它们可以实现很多逻辑。<br>假设请求的地址为 <a href="http://www.baidu.com:88/test1/test2/a.php?ttt=123">http://www.baidu.com:88/test1/test2/a.php?ttt=123</a><br></p><ul><li>$args:请求中的参数,与 $query_string 一致,即 ttt=123;</li><li>$content_length:请求头中的Content-Length 字段;</li><li>$content_type:请求头中的Content-Type字段;</li><li>$document_root:当前请求在root指令中指定的值;</li><li>$document_uri:与 $uri 一致,请求 URI,即 <a href="http://www.baidu.com:88/test1/test2/a.php%EF%BC%9B">http://www.baidu.com:88/test1/test2/a.php;</a></li><li>$host:请求中的主机头字段,即 <a href="http://www.baidu.com;/">www.baidu.com;</a></li><li>$http_user_agent:客户端浏览器的相关信息;</li><li>$http_cookie:客户端cookie信息;</li><li>$limit_rate:限制连接速率;</li><li>$request_body_file:客户端请求主体信息的临时文件名;</li><li>$request_method:请求方法;</li><li>$remote_addr:客户端的ip地址;</li><li>$remote_port:客户端的端口号;</li><li>$remote_user:客户端用户名称;</li><li>$request_filename:当前请求的文件路径;</li><li>$request_uri:包含请求参数的原始 URI,不包含主机名,即 /test1/test2/a.php</li><li>$status:请求状态码,成功是200;</li><li>$http_referer:页面来源;</li><li>$server_name:请求服务器名;</li><li>$server_port:请求=的服务器端口号;</li></ul><p>于此同时我们还可以在 nginx 请求中判断请求的是否是文件,目录等内容。</p><ul><li>-f 和 !-f 用来判断文件;</li><li>-d 和 !-d 用来判断目录;</li><li>-e 和 !-e 用来判断文件或目录;</li><li>-x 和 !-x 用来判断文件是否可执行。</li></ul><p>上述还有一个参数 rewrite 表示重写规则,它可以使用 nginx 提供的全局变量或我们设置的变量,结合正则表达式和标志位实现 url 重写与重定向。<br>rewrite 只能放在 server{} , location{} , if{} 中。<br>rewrite 只能对域名后边的除去传递的参数外的字符串起作用,例如 <a href="http://www.aaaa.com/a/b/c.php?id=1&user=hihell">http://www.aaaa.com/a/b/c.php?id=1&user=hihell</a> 只对 /a/b/c.php 重写。<br>rewrite 语法格式如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rewrite regex replacement [flag];</span><br></pre></td></tr></table></figure><p>上述语法中的 flag 有如下取值:</p><ul><li>last:表示完成 rewrite,一般写在 server{} 和 if{} 中;</li><li>break:停止执行当前虚拟主机的后续rewrite指令集;</li><li>redirect :返回302临时重定向,地址栏会显示跳转后的地址;</li><li>permanent:返回301永久重定向,地址栏会显示跳转后的地址。</li></ul><p>其中还有一个需要特别注意就是 $1 , $2 ,这些都是前面正则小括号里面的对应内容。<br></p><p>举例如下:<br></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">location / {</span><br><span class="line"># 匹配到 ^/news/([0-9]{5})\.html$,转换为 /news_$1</span><br><span class="line"> rewrite '^/news/([0-9]{5})\.html$' /news_$1;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述内容表示匹配到 /news/123456.html 的请求,重写为 /news_123456 。<br></p><h3 id="其它可用配置"><a href="#其它可用配置" class="headerlink" title="其它可用配置"></a>其它可用配置</h3><hr><h4 id="单连接请求上限数"><a href="#单连接请求上限数" class="headerlink" title="单连接请求上限数"></a>单连接请求上限数</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">{</span><br><span class="line">keepalive_requests 120;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="允许的域名与禁止的域名"><a href="#允许的域名与禁止的域名" class="headerlink" title="允许的域名与禁止的域名"></a>允许的域名与禁止的域名</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">{</span><br><span class="line">location [=|~|~*|^~] /uri/ {</span><br><span class="line"> deny www.baidu.com; # 拒绝的域名</span><br><span class="line"> allow 111.111.111.111; # 允许的 ip </span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="反向代理设置"><a href="#反向代理设置" class="headerlink" title="反向代理设置"></a>反向代理设置</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">location / {</span><br><span class="line"> proxy_pass http://localhost:8080; </span><br><span class="line"> proxy_set_header Host $host:$server_port; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中 proxy_pass 参数表示设置被代理服务器的 URL 和端口;<br>proxy_set_header 设置 header 参数,例如 Host , X-Real-IP , X-Forwarded-For<br><br>设置错误页<br>error_page 参与的语法规则为<br></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">error_page 404 502 = @fetch;</span><br><span class="line">location @fetch {</span><br><span class="line">access_log /logs/face.log log404;</span><br><span class="line">rewrite ^(.*)$ http://域名:端口/face.jpg redirect;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>server 块中的其它配置</p><ul><li>ssl_certificate:</li><li>ssl_certificate_key:</li><li>ssl_session_timeout:</li><li>expires 2h:缓存2小时;</li><li>listen 443 ssl:在https访问的时候,需要证书验证;</li></ul><p>nginx 80重定向443 命令如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">server {</span><br><span class="line"> listen 80;</span><br><span class="line"> server_name 域名1.com 域名2.com;</span><br><span class="line"> return 301 https://$http_host$request_uri;</span><br><span class="line">}</span><br><span class="line">server{</span><br><span class="line"> listen 443 ssl;</span><br><span class="line"> server_name 域名1.com 域名2.com;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中有 2 个参数需要说明 $http_host , $request_uri ,其中 $request_uri 在前文已经进行了说明,表示除 host 以外的其它部分。 $http_host 参数存在几个类似值:<br></p><ul><li>$host:浏览器请求的 IP,无端口;</li><li>$http_host:浏览器请求的 IP/端口号,端口存在即显示;</li><li>$proxy_host:被代理服务的 IP/端口号,80端口不显示,其它显示。</li></ul><p>请求日志 error_log 参数用于设置日志存储位置。<br></p><p>访问控制 allow/deny 在每个块中都可以设置多个 allow , deny ,分别表示允许或禁止某个 IP 或IP 段访问。<br></p><p>return命令 该命令语法格式为<br></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return code ;</span><br></pre></td></tr></table></figure><p>该命令用于结束规则的执行并返回状态码给客户端。<br></p><p>Set命令 该命令语法格式为<br></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set $variable value ; # 默认值:none</span><br></pre></td></tr></table></figure><p>该命令用于定义一个变量,并给变量赋值。变量的值可为文本,变量及二者联合。</p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/%E7%BD%91%E7%BB%9C/">网络</category>
<comments>http://zhangyuanqian.top/2022/02/09/nginx-%E7%9A%84%E5%88%9D%E7%BA%A7%E9%85%8D%E7%BD%AE/#disqus_thread</comments>
</item>
<item>
<title>资深开发竟然不清楚int(1)和int(10)的区别</title>
<link>http://zhangyuanqian.top/2022/02/08/%E8%B5%84%E6%B7%B1%E5%BC%80%E5%8F%91%E7%AB%9F%E7%84%B6%E4%B8%8D%E6%B8%85%E6%A5%9Aint-1-%E5%92%8Cint-10-%E7%9A%84%E5%8C%BA%E5%88%AB/</link>
<guid>http://zhangyuanqian.top/2022/02/08/%E8%B5%84%E6%B7%B1%E5%BC%80%E5%8F%91%E7%AB%9F%E7%84%B6%E4%B8%8D%E6%B8%85%E6%A5%9Aint-1-%E5%92%8Cint-10-%E7%9A%84%E5%8C%BA%E5%88%AB/</guid>
<pubDate>Tue, 08 Feb 2022 13:05:00 GMT</pubDate>
<description><p><img src="/images/pasted-28.png" alt="upload successful"></p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-28.png" alt="upload successful"></p><span id="more"></span><h3 id="困惑"><a href="#困惑" class="headerlink" title="困惑"></a>困惑</h3><hr><p>最近遇到个问题,有个表的要加个user_id字段,user_id字段可能很大,于是我提mysql工单<table><tr><td bgcolor="#FF4500">alter table xxx ADD user_id int(1)。</td></tr></table>领导看到我的sql工单,于是说:这int(1)怕是不够用吧,接下来是一通解释。<br><br>其实这不是我第一次遇到这样的问题了,其中不乏有工作5年以上的老司机。包括我经常在也看到同事也一直使用int(10),感觉用了int(1),字段的上限就被限制,真实情况肯定不是这样。<br></p><h3 id="数据说话"><a href="#数据说话" class="headerlink" title="数据说话"></a>数据说话</h3><hr><p>我们知道在mysql中 int占4个字节,那么对于无符号的int,最大值是2^32-1 = 4294967295,将近40亿,难道用了int(1),就不能达到这个最大值吗?<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE `user` (</span><br><span class="line"> `id` int(1) unsigned NOT NULL AUTO_INCREMENT,</span><br><span class="line"> PRIMARY KEY (`id`)</span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>id字段为无符号的int(1),我来插入一个最大值看看。<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mysql> INSERT INTO `user` (`id`) VALUES (4294967295);</span><br><span class="line">Query OK, 1 row affected (0.00 sec)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以看到成功了,说明int后面的数字,不影响int本身支持的大小,int(1)、int(2)…int(10)没什么区别。<br></p><h3 id="零填充"><a href="#零填充" class="headerlink" title="零填充"></a>零填充</h3><hr><p>一般int后面的数字,配合zerofill一起使用才有效。先看个例子:<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE `user` (</span><br><span class="line"> `id` int(4) unsigned zerofill NOT NULL AUTO_INCREMENT,</span><br><span class="line"> PRIMARY KEY (`id`)</span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注意int(4)后面加了个zerofill,我们先来插入4条数据。<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mysql> INSERT INTO `user` (`id`) VALUES (1),(10),(100),(1000);</span><br><span class="line">Query OK, 4 rows affected (0.00 sec)</span><br><span class="line">Records: 4 Duplicates: 0 Warnings: 0</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>分别插入1、10、100、1000 4条数据,然后我们来查询下:<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql> select * from user;</span><br><span class="line">+------+</span><br><span class="line">| id |</span><br><span class="line">+------+</span><br><span class="line">| 0001 |</span><br><span class="line">| 0010 |</span><br><span class="line">| 0100 |</span><br><span class="line">| 1000 |</span><br><span class="line">+------+</span><br><span class="line">4 rows in set (0.00 sec)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过数据可以发现 int(4) + zerofill实现了不足4位补0的现象,单单int(4)是没有用的。 而且对于0001这种,底层存储的还是1,只是在展示的会补0。<br></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><hr><p>int后面的数字不能表示字段的长度,int(num)一般加上zerofill,才有效果。zerofill的作用一般可以用在一些编号相关的数字中,比如学生的编号 001 002 … 999这种,如果mysql没有零填充的功能,但是你又要格式化输出等长的数字编号时,那么你只能自己处理了。<br><br><img src="/images/pasted-29.png" alt="upload successful"></p>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/MySQL/">MySQL</category>
<comments>http://zhangyuanqian.top/2022/02/08/%E8%B5%84%E6%B7%B1%E5%BC%80%E5%8F%91%E7%AB%9F%E7%84%B6%E4%B8%8D%E6%B8%85%E6%A5%9Aint-1-%E5%92%8Cint-10-%E7%9A%84%E5%8C%BA%E5%88%AB/#disqus_thread</comments>
</item>
<item>
<title>Linux 使用 Vsftpd 搭建 FTP 服务</title>
<link>http://zhangyuanqian.top/2022/01/27/Linux-%E4%BD%BF%E7%94%A8-Vsftpd-%E6%90%AD%E5%BB%BA-FTP-%E6%9C%8D%E5%8A%A1/</link>
<guid>http://zhangyuanqian.top/2022/01/27/Linux-%E4%BD%BF%E7%94%A8-Vsftpd-%E6%90%AD%E5%BB%BA-FTP-%E6%9C%8D%E5%8A%A1/</guid>
<pubDate>Thu, 27 Jan 2022 09:16:00 GMT</pubDate>
<description><p><img src="/images/pasted-25.png" alt="upload successful"><br>&nbsp;&nbsp;本文以 centos 的Linux服务器为例,使用 Vsftpd 搭建被动模式的 FTP 服务。</p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-25.png" alt="upload successful"><br> 本文以 centos 的Linux服务器为例,使用 Vsftpd 搭建被动模式的 FTP 服务。</p><span id="more"></span><h2 id="FTP-的两种模式"><a href="#FTP-的两种模式" class="headerlink" title="FTP 的两种模式"></a>FTP 的两种模式</h2><hr><p>FTP 存在两种模式,PORT(主动)模式和PASV(被动)模式。</p><h3 id="主动模式"><a href="#主动模式" class="headerlink" title="主动模式"></a>主动模式</h3><p>FTP服务器“主动”去连接客户端的数据端口来传输数据。 即客户端从一个任意的非特权端口N(N>1024)连接到FTP服务器的21端口。然后客户端开始监听N+1,并发送 PORT N+1 到FTP服务器。接着服务器会从它自己的数据端口(20)连接到客户端指定的数据端口(N+1)。</p><p><img src="/images/pasted-26.png" alt="upload successful"></p><h3 id="被动模式"><a href="#被动模式" class="headerlink" title="被动模式"></a>被动模式</h3><p>FTP服务器“被动”等待客户端来连接自己的数据端口。 即当开启一个FTP连接时,客户端打开两个任意的非特权本地端口(N >1024和N+1)。第一个端口连接服务器的21端口,但与主动方式的FTP不同,客户端不会提交 PORT 命令并允许服务器来回连它的数据端口,而是提交 PASV 命令。这样做的结果是服务器会开启一个任意的非特权端口(P > 1024),并发送 PORT P 命令给客户端。然后客户端发起从本地端口N+1到服务器的端口P的连接用来传送数据。(此模式下的FTP服务器不需要开启tcp 20端口)</p><p><img src="/images/pasted-27.png" alt="upload successful"></p><h3 id="两种模式比较"><a href="#两种模式比较" class="headerlink" title="两种模式比较"></a>两种模式比较</h3><p>(1)PORT(主动)模式只要开启服务器的21和20端口,而PASV(被动)模式需要开启服务器大于1024所有tcp端口和21端口。<br>(2)从网络安全的角度来看的话似乎 PORT 模式更安全,而 PASV 更不安全,那么为什么 RFC 要在 PORT 基础再制定一个 PASV 模式呢?其实 RFC 制定 PASV 模式的主要目的是为了数据传输安全角度出发的,因为 PORT 使用固定20端口进行传输数据,那么作为黑客很容使用sniffer等探嗅器抓取 ftp 数据,这样一来通过 PORT 模式传输数据很容易被黑客窃取,因此使用 PASV 方式来架设 ftp server 是最安全绝佳方案。</p><h2 id="安装-Vsftpd"><a href="#安装-Vsftpd" class="headerlink" title="安装 Vsftpd"></a>安装 Vsftpd</h2><ol><li>安装</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum install vsftpd</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="2"><li>设置开机启动</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable vsftpd</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="3"><li>设置开机启动</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl start vsftpd</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="4"><li>设置开机启动</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl start vsftpd</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="配置-Vsftpd"><a href="#配置-Vsftpd" class="headerlink" title="配置 Vsftpd"></a>配置 Vsftpd</h2><ol><li>为 FTP 服务创建一个用户</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">useradd ftpuser</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="2"><li>设置该用户的密码</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">passwd ftpuser</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="3"><li>为 FTP 服务创建一个用户</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir /var/ftp/ftpupload</span><br><span class="line">chown -R ftpuser:ftpuser /var/ftp/ftpupload</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="4"><li>编辑配置文件 /etc/vsftpd/vsftpd.conf</li></ol><ul><li>修改 配置文件之前先备份 </li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cp /etc/vsftpd.conf /etc/vsftpd.conf.back</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>修改以下配置参数,监听 IPv4 或 IPv6 只能选择开启一个</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"> \# 匿名用户的登录权限</span><br><span class="line">anonymous_enable=NO</span><br><span class="line"></span><br><span class="line">\# 本地用户的登录权限</span><br><span class="line">local_enable=YES</span><br><span class="line"></span><br><span class="line">\# 将所有用户限制在主目录</span><br><span class="line">chroot_local_user=</span><br><span class="line"></span><br><span class="line">\# 启动限制用户的名单</span><br><span class="line">chroot_list_enable=YES</span><br><span class="line"></span><br><span class="line">\# 例外用户列表文件的路径</span><br><span class="line">chroot_list_file=/etc/vsftpd/chroot_list</span><br><span class="line"></span><br><span class="line">\# 开启监听 IPv4 sockets</span><br><span class="line">listen=YES</span><br><span class="line"></span><br><span class="line">\# 关闭监听 IPv6</span><br><span class="line">\#listen_ipv6=YES</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>创建并编辑配置中 chroot_list_file 指定的例外用户列表文件</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">touch /etc/vsftpd/chroot_list</span><br></pre></td></tr></table></figure><ul><li>重启 FTP 服务</li></ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart vsftpd</span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/Linux/">Linux</category>
<comments>http://zhangyuanqian.top/2022/01/27/Linux-%E4%BD%BF%E7%94%A8-Vsftpd-%E6%90%AD%E5%BB%BA-FTP-%E6%9C%8D%E5%8A%A1/#disqus_thread</comments>
</item>
<item>
<title>vue中为什么不建议v-if 和v-for一起使用?</title>
<link>http://zhangyuanqian.top/2022/01/27/vue%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEv-if-%E5%92%8Cv-for%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8%EF%BC%9F/</link>
<guid>http://zhangyuanqian.top/2022/01/27/vue%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEv-if-%E5%92%8Cv-for%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8%EF%BC%9F/</guid>
<pubDate>Thu, 27 Jan 2022 09:00:00 GMT</pubDate>
<description><p><img src="/images/pasted-24.png" alt="upload successful"></p></description>
<content:encoded><![CDATA[<p><img src="/images/pasted-24.png" alt="upload successful"></p><span id="more"></span><h2 id="一、作用"><a href="#一、作用" class="headerlink" title="一、作用"></a>一、作用</h2><p>v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染<br></p><p>v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名<br></p><p>在 v-for 的时候,建议设置key值,并且保证每个key值是独一无二的,这便于diff算法进行优化<br></p><p>两者在用法上<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><Demo v-if="isShow" /></span><br><span class="line"></span><br><span class="line"><li v-for="item in items" :key="item.id"></span><br><span class="line"> {{ item.label }}</span><br><span class="line"></li></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="二、优先级"><a href="#二、优先级" class="headerlink" title="二、优先级"></a>二、优先级</h2><p>v-if与v-for都是vue模板系统中的指令<br></p><p>在vue模板编译的时候,会将指令系统转化成可执行的render函数<br></p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>编写一个p标签,同时使用v-if与 v-for <br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><div id="app"></span><br><span class="line"> <p v-if="isShow" v-for="item in list"></span><br><span class="line"> {{ item.name }}</span><br><span class="line"> </p></span><br><span class="line"></div></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>创建vue实例,存放isShow与items数据<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">const app = new Vue({</span><br><span class="line"> el: "#app",</span><br><span class="line"> data() {</span><br><span class="line"> return {</span><br><span class="line"> list: [</span><br><span class="line"> { name: "Tom" },</span><br><span class="line"> { name: "Jack" }]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> computed: {</span><br><span class="line"> isShow() {</span><br><span class="line"> return this.list && this.list.length > 0</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>模板指令的代码都会生成在render函数中,通过app.$options.render可以得到渲染函数<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ƒ anonymous() {</span><br><span class="line"> with (this) { return </span><br><span class="line"> _c('div', { attrs: { "id": "app" } }, </span><br><span class="line"> _l((list), function (item) </span><br><span class="line"> { return (isShow) ? _c('p', [_v("\n" + _s(item.name) + "\n")]) : _e() }), 0) }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>_l是vue的列表渲染函数,函数内部都会进行一次if判断<br></p><p>初步得到结论:v-for优先级是比v-if高<br></p><p>再将v-for与v-if置于不同标签<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ƒ anonymous() {</span><br><span class="line"> with(this){return </span><br><span class="line"> _c('div',{attrs:{"id":"app"}},</span><br><span class="line"> [(isShow)?[_v("\n"),</span><br><span class="line"> _l((list),function(item){return _c('p',[_v(_s(item.name))])})]:_e()],2)}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这时候我们可以看到,v-for与v-if作用在不同标签时候,是先进行判断,再进行列表的渲染<br></p><p>我们再在查看下vue源码<br></p><p>源码位置:/vue-dev/src/compiler/codegen/index.js<br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">export function genElement (el: ASTElement, state: CodegenState): string {</span><br><span class="line"> if (el.parent) {</span><br><span class="line"> el.pre = el.pre || el.parent.pre</span><br><span class="line"> }</span><br><span class="line"> if (el.staticRoot && !el.staticProcessed) {</span><br><span class="line"> return genStatic(el, state)</span><br><span class="line"> } else if (el.once && !el.onceProcessed) {</span><br><span class="line"> return genOnce(el, state)</span><br><span class="line"> } else if (el.for && !el.forProcessed) {</span><br><span class="line"> return genFor(el, state)</span><br><span class="line"> } else if (el.if && !el.ifProcessed) {</span><br><span class="line"> return genIf(el, state)</span><br><span class="line"> } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {</span><br><span class="line"> return genChildren(el, state) || 'void 0'</span><br><span class="line"> } else if (el.tag === 'slot') {</span><br><span class="line"> return genSlot(el, state)</span><br><span class="line"> } else {</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在进行if判断的时候,v-for是比v-if先进行判断<br></p><p>最终结论:v-for优先级比v-if高<br></p><h2 id="三、注意事项"><a href="#三、注意事项" class="headerlink" title="三、注意事项"></a>三、注意事项</h2><ol><li>永远不要把 v-if 和 v-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)</li><li>如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><template v-if="isShow"></span><br><span class="line"> <p v-for="item in list"></span><br><span class="line"></template></span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="3"><li>如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">computed: {</span><br><span class="line"> items: function() {</span><br><span class="line"> return this.list.filter(function (item) {</span><br><span class="line"> return item.isShow</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content:encoded>
<category domain="http://zhangyuanqian.top/categories/%E7%BC%96%E7%A8%8B/">编程</category>
<category domain="http://zhangyuanqian.top/tags/%E5%89%8D%E7%AB%AF/">前端</category>
<comments>http://zhangyuanqian.top/2022/01/27/vue%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEv-if-%E5%92%8Cv-for%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8%EF%BC%9F/#disqus_thread</comments>
</item>
</channel>
</rss>