-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
338 lines (338 loc) · 91.9 KB
/
search.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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[多线程中的问题与测试]]></title>
<url>%2Fthread%2Fthread-problem%2F</url>
<content type="text"></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
<tag>concurrent</tag>
</tags>
</entry>
<entry>
<title><![CDATA[结构化并发应用程序--多线程]]></title>
<url>%2Fthread%2Fthread-constructed%2F</url>
<content type="text"><![CDATA[后续查看其它的博客或查看JDK1.8源码之后再完善此篇内容。 1. 任务执行1.1.任务拆分首先要找出清晰的任务边界,在理想情况下,各个人物相互独立,一个线程做一个任务,根据情况选择细化任务的粒度。 1.2.Executor1.2.1.Executor框架的各种方法和线程池1.2.2.Executor生命周期 Executor的实现通常会创建线程来执行任务。但JVM只在所有(非守护)线程全部终止后才会退出。因此,如果无法正确地关闭Executor,那么JVM将无法结束。 ExecutorService继承了Executor接口,添加生命周期管理的方法。 1.2.3.延迟任务与周期任务1.2.4.携带结果的任务Callable与Future1.3.任务的取消与关闭1.4.线程池的使用]]></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[多线程之JAVA中容器、工具的使用]]></title>
<url>%2Fthread%2Fthread-util%2F</url>
<content type="text"><![CDATA[1.基础构建模块1.1.同步容器类将对容器状态的访问串行,以实现线程安全性,但降低了并发效率 1.1.1.问题同步容器类都是线程安全的,但在客户端使用符合操作时,还是需要额外加锁,符合操作包括:迭代(反复访问元素,直到遍历完容器中所有元素)、跳转、条件运算。 以下示例不安全的操作及改进方法: 不安全的原因是:虽然Vector是线程安全类,但在删除/获取最后一个元素时,要先获取最后一个元素的下标,这是一个复合操作,有可能在获取到下标之后,执行其他线程,此时最后一个元素的下标更小了,就会发生下标越界异常。 改进:对复合操作加锁,锁对象是Vector对象 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// 不安全的操作public class VectorTest { public static void main(String[] args) { final int tryNum = 100; Vector<Integer> vector = new Vector<>(); for (int i = 0; i < tryNum; i++) { vector.add(i); } for (int i = 0; i < 100; i++) { new Thread(new VectorGet(vector), "查询线程" + i).start(); new Thread(new VectorDel(vector), "删除线程" + i).start(); } } @Slf4j private static class VectorGet implements Runnable { private Vector<Integer> vector; @Override public void run() { log.info("vector容器中最后一个元素:{}", vector.get(vector.size() - 1)); } public VectorGet(Vector<Integer> vector) { this.vector = vector; } } @Slf4j private static class VectorDel implements Runnable { private Vector<Integer> vector; @Override public void run() { log.info("vector删除容器中最后一个元素"); vector.remove(vector.size() - 1); } public VectorDel(Vector<Integer> vector) { this.vector = vector; } }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// 改进:加锁public class VectorTest { public static void main(String[] args) { final int tryNum = 100; Vector<Integer> vector = new Vector<>(); for (int i = 0; i < tryNum; i++) { vector.add(i); } // 若此处也设置成跟tryNum一样的大小,那在最后一个元素被删除时,若又执行了查询,则会报错 // 此种情况不是由多线程安全问题引起的,而只是有线程重排序引起的, for (int i = 0; i < 99; i++) { new Thread(new VectorGet(vector), "查询线程" + i).start(); new Thread(new VectorDel(vector), "删除线程" + i).start(); } } @Slf4j private static class VectorGet implements Runnable { private Vector<Integer> vector; @Override public void run() { synchronized (vector) { log.info("vector容器中最后一个元素:{}", vector.get(vector.size() - 1)); } } public VectorGet(Vector<Integer> vector) { this.vector = vector; } } @Slf4j private static class VectorDel implements Runnable { private Vector<Integer> vector; @Override public void run() { synchronized (vector) { log.info("vector删除容器中最后一个元素:{}", vector.remove(vector.size() - 1)); } } public VectorDel(Vector<Integer> vector) { this.vector = vector; } }} 1.1.2.迭代器与ConcurrentModificationException当容器在迭代过程中被修改时,会表现出“及时失败”,就会抛出一个ConcurrentModificationException异常, 解决办法是:迭代期间对容器加锁,若不想这么做,可能需要“克隆”容器,在副本上迭代,克隆容器需要性能开销:容器的大小,每个元素执行的操作等。 有一些隐藏调用迭代器的方式,容易忽略异常,如: 对集合调用toString方法, ContainsAll、removeAll、retainAll等方法 把容器作为参数的构造函数 这些间接的迭代操作都可能抛出ConcurrentModificationException 1.2.并发容器并发容器是针对多个线程并发访问设计的。增加了对一些常见符合操作的支持 通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。 1.2.1.ConcurrentHashMap1.2.1.1.优势同步容器类在执行每个操作期间都持有一个锁。与HashMap一样,ConcurrentHashMap也是一个机遇散列的Map,但它使用了分段锁(而不是每个方法都在同一个锁上同步)的策略来提供更高的并发性与伸缩性。ConcurrentHashMap带来的结果是:在并发访问环境下将实现更好的吞吐量,而在单线程环境中只损失非常小的性能。 ConcurrentHashMap提供的迭代器不会抛出ConcurrentModificationException,因此不需要在迭代过程加锁。 ConcurrentHashMap返回的迭代器具有弱一致性,当创建迭代器时会遍历已有的元素,并可以(但是不保证)在迭代器被构造后将修改操作反映给容器。 1.2.1.2.劣势对于一些需要在整个Map上进行计算的方法,如:size 和 isEmpty,这些方法被减弱以反映容器的并发特效。size返回的结果可能在计算时已经过期,它实际只是一个估计值。 在大多数情况下,用ConcurrentHashMap来代替Map能进一步提高代码的并发性 1.2.2.ConcurrentMap–额外的原子Map操作由于 ConcurrentHashMap 不能被加锁来执行独占访问,因此我们无法使用客户端加锁来创建新的原子操作,这时候可以考虑使用 ConcurrentMap 1.2.3.CopyOnWriteArrayList用于替代同步 List ,在迭代期间不需要对容器进行加锁或复制(类似地,CopyOnWriteArraySet的作用是替换同步Set) Copy-on-Write 只要正确地发布一个事实不可变的对象,那么在访问该对象时就不在需要进一步的同步。在每次修改时,都会创建并重新发布一个新的容器副本 1.2.3.1.优势 不会被修改,同步时只需要确保数组内容的可见性。因此,多个线程可以同时对这个容器进行迭代,而不会彼此干扰或者与修改容器的线程相互干扰。返回的迭代器不会抛出 ConcurrentModificationException ,并且返回的元素与迭代器创建时的元素完全一致,不必考虑修改操作带来的影响。 这段话的意思应该是表示:创建迭代器的时候,元素就定下来了,之后的改变是一个新的数组,在这里是被忽略的,验证代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647@Slf4jpublic class CopyOnWriteTest { public static void main(String[] args) { log.info("测试ArrayList"); testList(new ArrayList()); log.info("测试ArrayList结束"); log.info("测试CopyOnWriteArrayList"); testList(new CopyOnWriteArrayList()); log.info("测试CopyOnWriteArrayList结束"); } /** * 对需要测试的集合做相同的操作,查看效果 * * @param list 需要处理的集合 * @return void * @author LiuWang * @date 2019/1/7 */ private static void testList(List list) { // 1. 先放进去10个数据 for (int i = 0; i < 10; i++) { list.add(i); } // 2. 然后获取此时的迭代器 Iterator<Integer> listIte = list.iterator(); // 3. 然后再向集合中放10个数据 for (int i = 10; i < 20; i++) { list.add(i); } // 4. 遍历迭代器,看迭代器中的数据是否变化 while (listIte.hasNext()) { // 迭代时报错:ConcurrentModificationException log.info("{}", listIte.next()); } // 5. 重新获取迭代器并遍历 log.info("----------重新获取迭代器----------"); Iterator<Integer> listIte2 = list.iterator(); while (listIte2.hasNext()) { log.info("{}", listIte2.next()); } }} 对 ArrayList 获取了迭代器之后在修改,当遍历迭代器时抛出异常 1.2.3.2.劣势每当修改容器时都会复制底层数组,这需要一定的开销,特别是当容器的规模较大时。**仅当迭代操作远远多于修改操作是,才应该使用“写入时复制”容器。 1.3.阻塞队列和生产者 - 消费者模式用阻塞队列很容易实现生产者 - 消费者模式,在多线程环境中,一些线程生产,一些线程消费,避免竞态条件,也提高并发,Queue是FIFO(first in first out)队列 阻塞队列(BlockingQueue)有多种实现,如:LinkedBlockingQueue(链表阻塞队列)、ArrayBlockingQueue(数组阻塞队列)、PriorityBlockingQueue(优先阻塞队列)。还有一种实现是SynchronousQueue,实际上它不是一个真正的队列,它不会为队列中元素维护存储空间。它维护一组线程,这些线程在等待着把元素加入或移除队列。因为中间没有队列,降低了生成者到消费者的延迟,但因为没有存储功能,因此put和take会一直阻塞到有另一个线程消费。 1.3.1.串行线程封闭 在java.util.concurrent中实现的各种阻塞队列都包含了足够的内部同步机制,从而安全地将对象从生产者线程发布到消费者线程。 对于可变对象,生产者 - 消费者这种设计与阻塞队列一起,促进了串行线程封闭,从而将对象所有权从生产者交付给消费者。 线程封闭对象同一时间只有单个线程拥有,要么是生产者,要么是消费者,并且通过队列来转移所有权。在转移所有权之后,只有一个消费者能访问,生产者不能再访问。新的所有者可以对该对象做任意修改,因为它具有独占的访问权。 当使用对象池(如:线程池)时,只要对象池包含足够的内部同步来安全地发布池中的对象,并且只要客户代码本身不会发布池中的对象,或者在将对象返回给对象池后就不再使用它,那么就可以安全地在线程之间传递所有权。 ConcurrentMap的remove(原子方法)或AtomicReference的compareAndSet(原子方法)也能实现此功能,他们能传递可变对象的所有权,且确保只有一个线程能接手被转移的对象, 1.3.2.双端队列与工作密取。。。后续补充 1.4.阻塞方法与中断方法线程可能会阻塞或暂停执行,原因有多种:等待 I/O操作结束,等待获得一个锁,等待从Thread.sleep方法中醒来,或是等待另一个线程的结果。当线程阻塞时,它通常被挂起,并处于某种阻塞状态(BLOCKED,WAITING或 TIMED_WAITING)。 当在代码中调用了一个将抛出InterruptedException的方法时,你自己的方法也就变成了一个阻塞的方法,并且需要处理对中断的相应。此时,两种选择: 传递:直接抛异常 恢复中断:捕获异常,并且调用当前线程的interrupt方法恢复中断状态。Thread.currentThread().interrupt() 1.5.同步工具类后续补充]]></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
<tag>concurrent</tag>
</tags>
</entry>
<entry>
<title><![CDATA[多线程之对象的共享]]></title>
<url>%2Fthread%2Fthread-object-share%2F</url>
<content type="text"><![CDATA[我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且希望确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。 1.对象可见性 为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。 1.1.失效数据(被改版前的数据)如下操作就不是线程安全的,因为get和set都是在没有同步的情况下访问value的。如果某个线程调用了set,那么另一个正在调用get线程可能会看到更新后的value值,可能看不到。 12345public class MutableInteger{ private int value; public int get(){ return value; } public void set(int value){ this.value = value; }} 要转化成线程安全的,仅对set方法进行同步是不够的,调用get的线程仍然会看见失效值。 1.2.非原子的64位操作 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全性保证也被成为最低安全性。 最低安全性适用于绝大多数变量,但是存在一个例外:非Volatile类型的64位数值变量(double和long)。Java内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。 即使不考虑失效数据问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来。 1.3.加锁与可见性 访问某个变量且可变的变量时要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变量的值对于其他线程来说都是可见的。否则,如果一个线程在未持有正确锁的情况下读取某个变量,那么读到的可能是一个失效值 加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。 因此,明白加锁是,用的是哪个锁很重要 1.4.Volatile变量 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。 volatile变量不会执行加锁操作,因此不会是执行线程阻塞 volatile变量对可见性的影响比volatile变量本身更为重要 仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。 ==volatile变量不足以确保操作的原子性==,除非你能保证只有一个线程对变量执行写操作。 1.5.发布与逸出1.5.1.概念1.5.2.如何构造线程安全对象1.6.线程封闭1.6.1.ThreadLocal类1.7.不变性 满足同步需求的另一种方法是使用不可变对象。 ==不可变对象一定是线程安全的== 当满足以下条件时,对象才是不可变的: 对象创建以后其状态就不能修改 对象的所有域都是final类型(即使不是final类型,但要保证每次都得到相同的结果,慎用) 对象是正确创建的(创建期间,this引用没有逸出) 1.8.安全的发布后续补充]]></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[线程安全-多线程]]></title>
<url>%2Fthread%2Fthread-safe%2F</url>
<content type="text"><![CDATA[清楚多线程环境的各种风险问题,方便排查多线程各种情况 1.线程带来的风险1.1.安全性问题 由于多个线程要共享相同的内存地址空间,并且是并发运行,因此它们可能会访问或修改其他线程正在使用的变量。当然,这是一种极大的便利,因为这种方式比其他线程间通信机制更容易实现数据共享。但它同样也带来了巨大的风险:线程会由于无法预料的数据变化而产生错误。当多个线程同时访问和修改相同的变量时,将会在串行编程模型中引入非串行元素,而这种非串行性是很难分析的。要使多线程程序的行为可以预测,必须对共享变量的访问操作进行协同,这样才不会在线程之间发生彼此干扰。 如以下操作就会有安全性问题: 自增问题: 自增其实有三部分:1. 读取num的值,2. 将值加1,3. 计算结果写入num,这是一个“读取 - 修改 - 写入”的操作序列,并且其结果状态依赖于之前的状态。 12345678910111213141516// MyThread.java@Slf4jpublic class MyThread implements Runnable { private static AtomicInteger Atomicnum = new AtomicInteger(0); private static int num; @Override public void run() { log.info("{} -> {}", Thread.currentThread().getName(), num++); } public MyThread() { }} 可以在代码块/方法上加synchronized,或者这里也可用原子类,如:AtomicInteger 1.2.活跃性问题线程还会导致一些在单线程程序中不会出现的问题,如活跃性问题。 活跃性意味着某件正确的事情最终会发生 当某个操作无法继续执行下去时,就会发生活跃性问题。 例如,如果线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永久地等待下去。 1.3.性能问题 性能问题包括多个方面,例如服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高,或者可伸缩性较低等。 多线程就一定比单线程性能高吗? 在多线程程序中,当现场调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁地出现上下文切换操作,这种操作将带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行上。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。 多线程能提高多少效率? 需要合理的拆分任务,假设A任务拆分成B与C,B需要的时间是C的100倍,那这时候拆分是不合理的,这时候代码复杂了,带来的性能提升却很小。任务需要合理的拆分,拆分之时要充分考虑安全性。 2.线程无处不在即使在程序中没有显示的创建线程,但在框架中、JVM中都可能会创建线程 因此,如果一个全局变量有状态的,且进行了读写操作,会不会有问题呢 3.线程安全 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是共享的和可变的状态的访问。 “共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。 3.1.什么是线程安全性 当多个线程访问某个类时,这个类始终都能表现出正确的行为, 那么就称这个类是线程安全的。 在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。 3.2.无状态性3.2.1.无状态对象一定是线层安全的 无状态对象因为不包含任何域,也不包含任何对其他类中域的引用,计算过程中的临时状态仅存在于线程栈的局部变量中,并且只能由正在执行的线程访问,当前线程不会影响到其他正在运行的线程,所以无状态对象一定是线程安全的。 3.2.2.有状态对象不一定是线层不安全的 对象成员变量都没有被操作,如都只有getter,没有setter 成员变量都是无状态对象的话,该对象也是线层安全的 3.3.原子性 原子本意是“不能被进一步分割的最小粒子”,而原子操作意为“不可被中断的一个或一系列操作”。 i++就不是原子性操作,i++虽然是一个紧凑的语法,使其看上去只是一个操作,但这个操作并非原子的,因而它并不会作为一个不可分割的操作来执行。它包含了三个独立的操作:读取i的值,将值加1,然后将结果写入i。这是一个“读取 - 修改 - 写入”的操作序列,并且其结果状态依赖于之前的状态。 3.3.1.竞态条件 当某个计算的正确性取决于多个线程的交替执行时序是,那么就会发送竞态条件。 最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动过。 3.3.3.竞态条件示例常见的竞态有单例写法: 12345678public static CurrentUser INSTANCE = null; public static CurrentUser getInstance() { if (INSTANCE == null) { INSTANCE = new CurrentUser(); } return INSTANCE; } 3.3.4.符合操作i++操作与上述例子的操作都包含一组需要以原子方式执行的操作。 要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改过程中。 因此,i++可以用把i定义成AtomicInteger类型的,上述例子也可以适当的加上锁,具体可搜索单例的实现方式 3.4.加锁机制 要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。 如果更新的多个变量都是原子变量,但更新操作本身不是原子,这种情况下也是有线程风险的。 1234// 虽然Vector对象是线程安全的,但此操作仍然会造成竞态条件if(!vector.contains(elemet)){ vector.add(element);} 3.4.1.内置锁synchronized同步代码块/方法 同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。 以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。 每个Java对象都可以用作一个实现同步的锁,这些锁被称为内置锁或监视锁。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块/方法 Java的内置锁相当于一种互斥锁,这意味着最多只有一个线程能持有这种锁。 由于每次只能有一个线程执行内置锁保护的代码块,因此,由这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时也不会相互干扰。 3.4.3.重入内置锁是可重入的 重入机制可避免子类调用父类同名方法时进入死锁,不太懂这个逻辑 3.5.用锁来保护状态 每个共享的和可变的变量都应该只由一个锁来保护 一个变量的状态维护尽量在一个对象内,这样有利于控制对象的状态不被破坏 3.6.活跃性与性能不能为了线程安全,就盲目的加锁或粗粒度的加锁,那就失去了性能,可能还不如不用多线程 通常,在简单性与性能之间存在着相互制约因素。当实现某个同步策略时,一定不要盲目地为了心梗而牺牲简单性(这可能会破坏安全性) 当执行时间较长的计算或者可能无法快速完成的操作是(例如,网络 I/O 或控制台 I/O ),一定不要持有锁。 参考资源 Java并发编程实战 Java并发编程的艺术 多线程之无状态对象和有状态对象]]></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[多线程]]></title>
<url>%2Fthread%2Fthread-index%2F</url>
<content type="text"><![CDATA[1.基本概念1.1.进程与线程1.1.1.参考资源 进程、线程、多线程相关总结 线程与进程 进程与线程的区别和联系 深入理解进程和线程 进程和线程 - 廖雪峰的官方网站 1.2.并发(concurrent)与并行(parallel) 并行与并发: 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。(Two queues and one coffee machine) 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。(Two queues and two coffee machines) 并行才是真正意义上的同时进行,而并发是cpu在不断切换各“任务”已达到似乎是同时进行的效果。 1.3.线程安全与同步 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码: 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。 2.JAVA多线程注: 文中使用的源码样例是JDK1.8 2.1.线程状态 2.1.1.详细解读2.1.1.1.状态说明 线程在给定的时间点只能处于其中的一种状态 这些事虚拟机的状态,与操作系统无关 2.1.1.2.状态详情 NEW(新建):线程还没有开始 RUNNABLE(就绪、可执行状态):线程正在虚拟机中执行,但还需要等待系统的其他资源,如:processor BLOCKED(阻塞):进入同步代码块/方法时等待监控锁或在调用了Object.wait()之后等待重新进入同步代码块/方法 12345678/** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */BLOCKED, WAITING(无限期等待):等待其它线程唤醒的状态。三种原因: Object.wait 没有超时 Thread.join 没有超时 LockSupport.park TIMED_WAITING(时长等待):设置了等待时间的等待状态,等待时间过了就会进入RUNNABLE状态。三种原因 Thread.sleep(long) Object.wait(long) Thread.join(long) LockSupport.parkNanos LockSupport.parkUntil TERMINATED(终止):线程执行完成 状态转换如下图: 同步代码块有如下隐藏的状态变化: 线程在Running的过程中可能会遇到阻塞(Blocked)情况 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable) 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。 此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。 2.2.每个对象都有的方法(机制)2.2.1.synchronized、wait、notify synchronized、wait、notify是任何对象都有的工具 wait和notify(notifyAll)都只能在获得对象监控(对象锁)的时候使用 他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。 wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。 当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。 2.2.2.volatile 多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。 针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。 2.3.线程实践2.3.1.Thread继承java.lang.Thread类 2.3.2.Runnable实现java.lang.Runnable接口 2.3.3.Callable1234ExecutorService e = Executors.newFixedThreadPool(3);Future future = e.submit(new myCallable());future.isDone() //return true,false 无阻塞future.get() // return 返回值,阻塞直到该线程运行结束 2.4.高级多线程控制类2.4.1.ThreadLocal类保存每个线程的变量副本 2.4.2.原子类(AtomicInteger、AtomicBoolean…)在多线程环境中可以用来代替常用的数据类型 注意AtomicReference的使用 2.4.3.Lock类后需完善 2.5.容器类2.5.1.BlockingQueue2.5.2.ConcurrentHashMap2.6.线程管理类2.6.1.ThreadPoolExecutor参考资源 Java中的多线程你只要看这一篇就够了 浅析java内存模型–JMM(Java Memory Model)]]></content>
<categories>
<category>thread</category>
</categories>
<tags>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hexo博客部署到服务器]]></title>
<url>%2Fblog%2Ftencent-cloud%2F</url>
<content type="text"><![CDATA[前期准备 租用一个云服务器,我使用的是腾讯云服务器 购买一个域名 本地搭建好hexo环境,可以参考网站由jekyll迁移到hexo 开始行动登录服务器秘钥登录(推荐)现在控制台 -> SSH秘钥处生成秘钥,如图: 创建完绑定到自己的主机 然后就会生成一个文件到你指定的文件夹了,此操作需要重启,绑定完之后,就可以登录啦 密码登录如果不知道密码,通过此处重置密码 然后点击登录, 出来三种登录方式,这里演示第一种:浏览器webShell方式登录。点击此方式的立即登录, 如果是密码登录,输入密码,如果是秘钥登录,选择刚刚下载的秘钥文件,点击确定之后就进入了服务器,就可以随心所欲的操作了 博客文件传到指定文件夹我用的centos7.5系统 我是上传到/app/blog目录下,操作是: 12345# 井号后面的文字是注释,以下操作是1.进入根目录,2.创建app目录,3.进入app目录,4.创建blog目录cd /mkdir appcd appmkdir blog hexo设置deploy路径具体设置可以参考hexo发布 我这里支持上传到github-page和腾讯云服务器,其中发布到腾讯云服务器的配置如下: 1234567891011121314# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: # 部署到github-page- type: git repo: https://github.com/lw5946/lw5946.github.io.git branch: master message: # 通过sftp部署到服务器 - type: sftp host: 129.28.139.183 # 服务器ip user: root # 服务器登录用户,默认是root privateKey: D:\_blog\server\LiuWang666 # 秘钥文件,本地保存秘钥文件的路径 remotePath: /app/blog # 上传到云服务器的路径 这样一来,执行hexo d之后,博客网页就上传到/app/blog目录下了,自己可以在服务器看得到 用容器将网页发布出来现在博客的网页已经到了服务器,想访问到,还需要用容器将其发布出来,这里选用nginx 参考资料:Hexo 教程:Hexo 博客部署到腾讯云教程 我这里暂时不想在服务器弄git,因为本地有了, 暂时也还不想弄自动发布,下次我用的时候再补充,下面就讲讲目前需要做的: 安装依赖库和编译工具,具体有什么用,不知道,暂时不管 12yum install curl-devel expat-devel gettext-devel openssl-devel zlib-develyum install gcc perl-ExtUtils-MakeMaker package 安装nginx 1yum install -y nginx 启动nginx 1service nginx start 测试nginx容器 1wget http://127.0.0.1 测试网页能否打开 在浏览器输入服务器ip地址,查看是否进入nginx的欢迎页,进入则表示nginx启动成功 设置nginx托管页面为博客 查看nginx安装的位置, 输入以下命令,通常都是显示在/etc/nginx目录下 1nginx -t 进入步骤1显示的路径,编辑nginx.conf文件 1vi nginx.conf 修改图中所示的server_name和root,其中server_name改成自己的域名,root改成博客所放的目录 其中listen表示监听的端口,就保持80不变,80是默认端口,就是表示直接输入ip所访问的端口 访问网页 这时候再访问服务器ip,看到的应该就是上传到服务器的博客了 服务器ip与域名绑定 这里不详述了,参考我的另一篇博客我的网站 添加SSL证书Hexo 博客之添加 https 说明不推荐密码登录博客源码需要地方保存,通过github的版本托管平台挺好的,如果用密码,密码得写在配置中,会泄露,而如果用秘钥方式,只要秘钥文件不上传到github就没事了]]></content>
<categories>
<category>blog</category>
</categories>
<tags>
<tag>blog</tag>
<tag>tencent</tag>
<tag>cloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[学习]]></title>
<url>%2Fstudy%2Fstudy-index%2F</url>
<content type="text"><![CDATA[读书总会有点作用去年,出差本来是件很忙的事,但在忙里偷闲之时看到了一个书单,然后利用那段时光看完了《如何阅读一本书》,受益很多 如何阅读每回想到作为一个程序员,那么多书需要看,根本没时间!还没开始就把自己吓退了,想到了从《如何阅读一本书》中学到的一种概念:阅读的过程,是理解能力增长的过程。 书中推荐的读书方式: 略读,读目录,尽快的知道这本书的大概内容,那些内容是现在最需要的 跳跃式阅读,看自己最需要的、能看懂的部分 要深刻明白,阅读的过程是增进理解的过程,暂时不理解的先跳过也没事(不跳过也理解不了,看到最后,很大可能会放弃整本书),慢慢的对这方面的理解能力提高了,不懂的也就慢慢的懂了]]></content>
<categories>
<category>study</category>
</categories>
<tags>
<tag>学习</tag>
</tags>
</entry>
<entry>
<title><![CDATA[网站由jekyll迁移到hexo]]></title>
<url>%2Fblog%2Fhexo-install%2F</url>
<content type="text"><![CDATA[安装 安装git与node:https://hexo.io/zh-cn/docs/#%E5%AE%89%E8%A3%85-Hexo npm\yarn配置 node -> npm配置: https://blog.csdn.net/wanshaobo888/article/details/70254917 node -> yarn配置: https://blog.csdn.net/wanshaobo888/article/details/70254917 https://blog.csdn.net/kucoll/article/details/79890176 安装hexo安装hexo, 而不是hexo-cli,才配置成功环境变量: npm install -g hexohexo设置为环境变量:https://www.cnblogs.com/yuyufeng/p/5723778.html 安装主题:https://github.com/SumiMakito/hexo-theme-typography/blob/master/README_zh-CN.md 安装python:https://blog.csdn.net/weixin_36222137/article/details/78463543?locationNum=10&fps=1 使用hexo d 自动上传到服务器https://blog.csdn.net/without_scruple/article/details/79085907 上传的是源码部分https://www.jianshu.com/p/67c57c70f275 优化原则 尽量不修改主题的文件, 避免影响更新!!! 修改样式,可以在themes\使用的主题\source\css_custom\custom.styl 资源链接首先参考官方说明 https://www.jianshu.com/p/1f8107a8778c https://www.jianshu.com/p/78c218f9d1e7 https://www.jianshu.com/p/2640561e96f8 https://www.jianshu.com/p/c9de55660d1b ## npm包安装一览 站内微搜索, 需安装插件: 12npm install hexo-generator-search --savenpm install hexo-generator-searchdb --save 给博客添加feed 1npm install hexo-generator-feed --save 给博客生成站点地图 1npm install hexo-generator-seo-friendly-sitemap --save jade:https://www.jianshu.com/p/05ed25bfc2c5 next主题官方中文文档https://github.com/theme-next/hexo-theme-next/blob/master/docs/zh-CN/README.md 由于next主题迁移了, https://github.com/iissnan/hexo-theme-next/blob/master/README.cn.md是以前的官方github, 下载代码、查看文档都没有必要在这个网站了刚开始接触hexo的时候, 我就是从老官网下载的, 哭, 还得花费一番功夫升级 教程–简书https://www.jianshu.com/p/21c94eb7bcd1 博客迁移 下载之后,安装node,hexo(node_modules) 进入博客根目录,运行命令npm install hexo --save命令,然后运行npm install安装各模块所需组件 在博客根目录运行hexo -v可以查看各组件版本,即表示hexo安装成功 问题 unable to sync pages directory 可能是组件所需的node_modules安装的不全,在博客根目录运行npm install , 若还没好,看github是否发错误信息邮件 无法生成html https://blog.csdn.net/huihut/article/details/73196343 字数统计配置好之后需要安装 https://github.com/willin/hexo-wordcount LearnCloud需要新建class https://www.jianshu.com/p/702a7aec4d00 修改过next主题代码的地方,更新时注意备份尽量不修改主题的源代码, 避免更新被覆盖,但有些地方必须改,这里做个记录 修改一些名字的中文显示: themes\next\languages\zh-Hans.yml 修改文章底部带#号的标签样式: 修改模板/themes/next/layout/_macro/post.swig,搜索 rel="tag">#,将 # 换成<i class="fa fa-tag"></i> 自定义样式, themes\next\source\css_custom\custom.styl 修改自定义头像: themes\next\source\images\avatar.png 主题配置: themes\next_config.yml]]></content>
<categories>
<category>blog</category>
</categories>
<tags>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring-cloud使用的一些技巧、心得]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-tip%2F</url>
<content type="text"><![CDATA[技巧的总结 使用主机名而不是ip地址访问服务的时候使用主机名(域名),如:localhost等,而不是IP地址 因为,如果使用ip地址,服务在迁移的时候又需要改调用服务的ip,因为微服务会有很多个小服务,这一改起来可能就很多了,而主机名的话,可以通过修改hosts文件来自定义。可以写个脚本文件来修改 如果机器多,可以搭个dns服务器,然后其他机器通过dns服务器解析域名 这样部署新程序的时候,迁移的时候,不需要修改配置文件中的域名 写日志elk logstash es kibana]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>tip</tag>
</tags>
</entry>
<entry>
<title><![CDATA[高可用服务注册中心]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-eureka-1%2F</url>
<content type="text"><![CDATA[之前服务注册中心 说明了服务注册server、client以及一些列的功能,但是这个时候服务中心是单一的,如果出问题,就会造成很严重的后果,服务中心也可以互相注册,形成服务中心的高可用。 准备首先准备两个服务注册中心, 12345678910111213# 第一个注册中心server: port: 1112eureka: instance: ip-address: 127.0.0.1# hostname: server2 client: service-url: # 通过ip访问 defaultZone: http://127.0.0.1:1111/eureka # 通过hostname访问 # defaultZone: http://server1:1111/eureka 1234567891011# 第二个注册中心server: port: 1111eureka: instance: ip-address: 127.0.0.1# hostname: server2 client: service-url: defaultZone: http://127.0.0.1:1112/eureka# defaultZone: http://server1:1112/eureka 客户端多个服务中心都用上 1eureka.client.serviceUrl.defaultZone=http://127.0.0.1:1111/eureka,http://127.0.0.1:1112/eureka 服务中心之间最好是相互注册,形成闭环 文章部分内容引自:http://blog.didispace.com/springcloud6/]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>eureka</tag>
</tags>
</entry>
<entry>
<title><![CDATA[配置中心高可用]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-config-1%2F</url>
<content type="text"><![CDATA[之前配置中心中主要说明了使用配置中心以及客户端的用法,这个配置中心是单点的,可以借助Eureka Server实现配置中心的高可用,让客户端通过配置中心在服务中心注册的serviceId来访问 1. 将配置中心注册到Eureka Server1234567891011121314151617# 配置中心的bootstrap.yamlspring: application: name: config-server# 通过本地仓库 profiles: active: native cloud: config: server: native: # 配置文件根路径 search-locations: E:/IdeaProjects/properties/eureka: client: # 服务注册中心的url service-url.defaultZone: http://localhost:1111/eureka 123456789@EnableDiscoveryClient@EnableConfigServer@SpringBootApplicationpublic class ConfigserverApplication { public static void main(String[] args) { SpringApplication.run(ConfigserverApplication.class, args); }} 2.客户端中引用12345678910111213spring: application: name: spring-cloud-base-service cloud: config: discovery: enabled: true service-id: config-server profile: deveureka: client: # 服务注册中心的url service-url.defaultZone: http://localhost:1111/eureka 3.效果仍然通过之前的方式查看引用的配置 这里引用的是E:/IdeaProjects/properties/spring-cloud-base-service-dev.yaml 1234567@Value("${from}")private String from;@RequestMapping("/from")public String from() { return from;}]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>config-server</tag>
</tags>
</entry>
<entry>
<title><![CDATA[服务网关]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-api-gateway%2F</url>
<content type="text"><![CDATA[1. 目标服务网关是微服务中不可或缺的一部分,是服务暴露在外的前锋。 一些功能提取到服务网关中,是内部服务更注重于业务功能 网关功能如下: 统一多个服务路由、负载均衡 统一对外暴露接口、内部服务变化尽量不影响外部的调用 统一权限控制,分离非业务的功能 2. 服务路由2.1 pom123456789<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><!-- 需要注册到服务中心,所以需要eureka --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency> 2.2 yaml123# application.yamlserver: port: 5555 123456789101112131415161718192021222324spring: application: name: api-gatewayzuul: routes: # rotes to url api-a-url: path: /api-a-url/** url: http://127.0.0.1:8080/ # rotes to serviceId # 如果多个服务在服务中心注册的serviceId(单个应用的spring.application.name)一样, # 就可以实现负载均衡 api-a: path: /api-a/** serviceId: spring-cloud-base-service api-b: path: /api-b/** serviceId: demo-b # zuul.routes.<serviceId>=<path> spring-cloud-base-service: /demo/**eureka: client: service-url: defaultZone: http://127.0.0.1:1111/eureka/ 路由配置规则: zuul.routes.<route>.path 与 zuul.routes.<route>.serviceId(或zuul.routes.<route>.url) zuul.routes.<serviceId>=<path> 其中 <route>表示规则名,可以自定义;<serviceId>表示服务名;<path>表示客户端请求的路径表达式 通配符含义: 通配符 含义 举例 ? 匹配单个字符 /api-a/a * 匹配对个字符,一层路径 /api-a/aaa ** 任意匹配 /api-a/aaa或/api-a/a/b/c 2.3 java12345678910111213141516171819package com.lw.apigateway;import org.springframework.boot.SpringApplication;import org.springframework.cloud.client.SpringCloudApplication;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;/** * @EnableZuulProxy 注解开启Zuul * @author LiuWang * @date 2018/3/19 19:26 */@EnableZuulProxy@SpringCloudApplicationpublic class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); }} @SpringCloudApplication相当于:@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker的组合 2.4 使用 开启服务注册中心 将客户端spring-cloud-base-service打包,复制三份,修改server.port,分别为8080,8081,8082,开启 开启服务网关api-gateway 2.5 效果 当访问http://127.0.0.1:5555/api-a-url/add时会路由到http://127.0.0.1:8080/add 当访问http://127.0.0.1:5555/api-a/add时会路由到http://127.0.0.1:8080/add或http://127.0.0.1:8081/add或http://127.0.0.1:8082/add,多次请求,会发现各个服务窗口都有打印请求到的信息。负载均衡成功实现! 当访问http://127.0.0.1:5555/api-b/add会路由到spring.application.name为demo-service的add路由上 3. 服务过滤123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051package com.lw.apigateway.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import org.apache.commons.lang.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;/** * @author LiuWang * @date 2018/3/20 */public class AccessFilter extends ZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(AccessFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); LOGGER.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString())); String token = request.getParameter("token"); if (StringUtils.isBlank(token)){ LOGGER.warn("token is empty"); //未通过验证的返回信息 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); ctx.setResponseBody("token is empty"); return null; } LOGGER.info("token is ok"); //通过验证,则继续请求的流程 return null; }} 自定义过滤器的实现,需要继承ZuulFilter,需要重写实现下面四个方法: filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下: pre:可以在请求被路由之前调用 routing:在路由请求时候被调用 post:在routing和error过滤器之后被调用 error:处理请求时发生错误时被调用 filterOrder:通过int值来定义过滤器的执行顺序 shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。 run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。 在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中增加如下内容: 1234@Beanpublic AccessFilter accessFilter(){ return new AccessFilter();} 启动该服务网关后,访问: http://localhost:5555/api-a/add?a=1&b=2:返回401错误,页面显示token is empty http://localhost:5555/api-a/add?a=1&b=2&accessToken=token:正确路由到spring-cloud-base-service,并返回计算内容 filterType的其他类型以及生命周期如下: 4. 其他配置设置超时 12345678910111213141516# 断路器超时要 大于 负载均衡器超时总和# 负载均衡器超时总和不是 ReadTimeout + ConnectTimeout# 断路器设置hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 30000# 负载均衡器设置ribbon: # 请求处理的超时时间 ReadTimeout: 1000 # 请求连接的超时时间 ConnectTimeout: 1000 5. 问题 如何在网关中设置统一的异常处理? 参考文章(Edgware.SR2与此版本需要不同处理): http://blog.didispace.com/spring-cloud-starter-dalston-6-1/ http://blog.didispace.com/spring-cloud-starter-dalston-6-2/ http://blog.didispace.com/spring-cloud-starter-dalston-6-3/ 6. 理解在Spring Cloud Netflix中,Zuul巧妙的整合了Eureka来实现面向服务的路由。实际上,我们可以直接将API网关也看做是Eureka服务治理下的一个普通微服务应用。它除了会将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有服务以及它们的实例清单。所以,在Eureka的帮助下,API网关服务本身就已经维护了系统中所有serviceId与实例地址的映射关系。当有外部请求到达API网关的时候,根据请求的URL路径找到最佳匹配的path规则,API网关就可以知道要将该请求路由到哪个具体的serviceId上去。由于在API网关中已经知道serviceId对应服务实例的地址清单,那么只需要通过Ribbon的负载均衡策略,直接在这些清单中选择一个具体的实例进行转发就能完成路由工作了。 7. 为什么要使用服务网关 不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。 实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。 实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。 文章中部分内容引自: http://blog.didispace.com/springcloud5/ http://blog.didispace.com/spring-cloud-starter-dalston-6-2/]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>api-gateway</tag>
</tags>
</entry>
<entry>
<title><![CDATA[配置中心]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-config%2F</url>
<content type="text"><![CDATA[1. 本地仓库管理配置1.1 配置中心服务端1.1.1 pom1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId></dependency> 1.1.2 yaml服务配置建议写在bootstrap.yaml文件中 123456789101112spring: application: name: config-server# 通过本地仓库 profiles: active: native cloud: config: server: native: # 配置文件根路径 search-locations: E:/IdeaProjects/properties/ 1.1.3 java123456789// 开启配置服务@EnableConfigServer@SpringBootApplicationpublic class ConfigserverApplication { public static void main(String[] args) { SpringApplication.run(ConfigserverApplication.class, args); }} 1.2 客户端1.2.1 pom1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId></dependency> 1.2.2 yaml1234567891011121314spring: application: name: spring-cloud-base-service cloud: config: # 引用配置为: 配置中心根目录/${label}/${spring.application.name}-${profile}.yaml,如:E:/IdeaProjects/properties/config-label-test/spring-cloud-base-service-prod.yaml profile: prod label: config-label-test # 配置中心的url uri: http://127.0.0.1:7001/eureka: client: # 服务注册中心的url service-url.defaultZone: http://localhost:1111/eureka 1.2.3 java示例demo 123456789//DemoController类@Value("${from}")private String from;//获取配置中的属性@RequestMapping("/from")public String from(){ return this.from;} 2. git仓库管理配置2.1 服务端1.1.1 yaml1234567891011121314spring: application: name: config-server# 通过git方式 cloud: config: server: git: # git仓库的url uri: https://gitee.com/lw888/SpringCloud-Learning # 配置文件的根路径(仓库内的文件夹) search-paths: config-server/spring-cloud-base-service username: ******** password: ******** 2.2 客户端各属性的含义发生改变 123456789101112131415spring: application: name: spring-cloud-base-service cloud: config: # 引用配置为: ${label}分支的${search-paths}/${spring.application.name}-${profile}.yaml # 如:config-label-test分支的config-server/spring-cloud-base-service/spring-cloud-base-service-prod.yaml profile: prod label: config-label-test # 配置中心的url uri: http://127.0.0.1:7001/eureka: client: # 服务注册中心的url service-url.defaultZone: http://localhost:1111/eureka 3. 配置映射规则说明3.1 本地配置映射 /{label}/{application}-{profile}.yaml 3.2 git仓库配置映射 spring.cloud.config.server.git.uri:配置git仓库位置 spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个 spring.cloud.config.server.git.username:访问git仓库的用户名 spring.cloud.config.server.git.password:访问git仓库的用户密码 spring.cloud.config.label:对应git分支]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>config-server</tag>
</tags>
</entry>
<entry>
<title><![CDATA[服务消费与断路器]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-feign%2F</url>
<content type="text"><![CDATA[客户端消费者,ribbon与feign,我更新换feign。 Feign使编写Web Service客户端变得非常简单。调用外部接口跟调用本地接口一样方便! 一. 负载均衡1. 问题Q:曾经使用cloud的Brixton.RELEASE(spring-boot:1.3.5)版本用feign实现过负载均衡,现在使用Edgware.SR2(spring-boot:1.5.9.RELEASE)却不行了,报错: There was an unexpected error (type=Internal Server Error, status=500). com.netflix.client.ClientException: Load balancer does not have available server for client: spring-cloud-base-service A: 解决一,发现当设置hystrix和ribbon时不会出现这样的问题; 解决二,在客户端使用 eureka.instance.prefer-ip-address: true时也能解决,此配置表示优先使用ip地址而不是主机名 Q: 之前实现的形式,看到是对接口的负载均衡,也就是说要负载均衡的接口需要在负载均衡器里写一个个方法对应,这样势必非常麻烦,如何解决呢? A: 服务间的负载均衡可以使用zuul 2. 操作yaml配置12345678910111213141516171819202122spring: application: name: loadbalance## 负载均衡器也注册到服务中心eureka: client: service-url.defaultZone: http://localhost:1111/eurekafeign: hystrix: enabled: truehystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000000ribbon: # 请求处理的超时时间 ReadTimeout: 3000 # 请求连接的超时时间 ConnectTimeout: 3000 application主类添加@EnableDiscoveryClient`@EnableFeignClients`注解 feignClient及hystrix123456789101112131415161718package com.career.api;import com.career.api.hystrix.ComputeClientHystrix;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;/** * Created by LiuWang on 2017/10/31. */// 注意:这里使用value对应服务名@FeignClient(value = "spring-cloud-base-service", fallback = ComputeClientHystrix.class)public interface ComputeClient { @RequestMapping(value = "/add", method = RequestMethod.GET) Integer add(@RequestParam("a") Integer a, @RequestParam("b") Integer b);} 123456789101112131415package com.career.api.hystrix;import com.career.api.ComputeClient;import org.springframework.stereotype.Component;/** * Created by LiuWang on 2017/11/1. */@Componentpublic class ComputeClientHystrix implements ComputeClient { @Override public Integer add(Integer a, Integer b) { return new Integer(9999); }} 二. 调用外部服务使用Feign调用其他服务的接口特别方便 1234567891011121314151617181920212223242526272829303132333435363738package com.sailing.zunyi.remoteapi;import com.sailing.zunyi.remoteapi.hystrix.CarClientHystrix;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;/** * 调用车辆大数据平台接口 * * @author LiuWang * @date 2017/11/8 */// 注意:这里使用url// ${remote-api.car}为application.yaml配置中的信息@FeignClient(name = "car-api", url = "${remote-api.car}", fallback = CarClientHystrix.class)public interface CarBigData { /** * 从车辆大数据平台获取过车总数 * * @author LiuWang * @date 2017/11/9 9:51 */ @GetMapping(value = "/clyp/gcxxtj/getGcxxTj") String carAmonut(); /** * 车驾管信息 * * @param hphm 车牌号 * @param hpzl 车牌种类(01,02,03等) * @author LiuWang * @date 2018/3/6 13:35 */ @GetMapping(value = "/clyp/cljbxx/getcljbxx") String carInfo(@RequestParam("hphm") String hphm, @RequestParam("hpzl") String hpzl);} 三. 断路器hystrix123456789101112#开启断路器feign: hystrix: enabled: true#hystrix断路器超时(执行fallback方法)设置hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 30000 12345678910111213141516171819202122232425262728293031323334package com.sailing.zunyi.remoteapi.hystrix;import com.sailing.zunyi.remoteapi.CarBigData;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * 调用车辆大数据平台接口的回调类 * * @author LiuWang * @date 2017/11/11 */@Componentpublic class CarClientHystrix implements CarBigData { private static final Logger LOGGER = LoggerFactory.getLogger(CarClientHystrix.class); /** * 查询过车总数接口调用失败时返回"-1" * * @author LiuWang * @date 2017/11/11 15:17 */ @Override public String carAmonut() { return "-1"; } @Override public String carInfo(String hphm, String hpzl) { LOGGER.info("------查询车驾管信息失败-----hphm:{},hpzl:{}", hphm, hpzl); return "-1"; }}]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>feign</tag>
<tag>hystrix</tag>
</tags>
</entry>
<entry>
<title><![CDATA[服务注册中心]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-eureka%2F</url>
<content type="text"><![CDATA[1. 共有jar1234567891011121314151617181920212223242526272829303132333435363738394041<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --></parent><properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Edgware.SR2</spring-cloud.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build> 2. 创建”服务注册中心”这个应用只作为服务注册中心,不用来做其他(不做业务功能) 相比较普通的服务,这个是另外加的 2.1 新增jar 1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency> 2.2 java通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话 1234567@EnableEurekaServer@SpringBootApplicationpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }} 2.3 yaml在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为 123456789101112131415server: port: 1111eureka: client: ## 在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己 ## 所以我们需要禁用它的客户端注册行为 register-with-eureka: false fetch-registry: false# service-url:# defaultZone: http://localhost:${server.port}/eureka/ server: # 关闭自我保护 enable-self-preservation: false # 清理无效client节点时间间隔 eviction-interval-timer-in-ms: 4000 为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1111 3.创建”客户端”3.1 新增jar1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency> 3.2 java1234567@EnableDiscoveryClient@SpringBootApplicationpublic class SpringCloudBaseApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudBaseApplication.class, args); }} 实例,通过DiscoveryClient对象及相关的信息 1234567891011121314@RestControllerpublic class ComputeController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "/add" ,method = RequestMethod.GET) public Integer add(@RequestParam Integer a, @RequestParam Integer b) { //这个只是用来指导请求的主机名和serviceid,可以不要 ServiceInstance instance = client.getLocalServiceInstance(); Integer r = a + b; logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r); return r; }} 3.3 yaml12345678spring: application: name: spring-cloud-base-serviceserver: port: 8080eureka: client: service-url.defaultZone: http://localhost:1111/eureka 4. 访问http://localhost:1111/ 5. 问题其他服务重启(如修改网关等其他服务的配置之后重启)之后,发现再次调用服务,很多时候还是使用的之前的配置,必须把服务中心也重启 eureka.server.eviction-interval-timer-in-ms: 2000 清除无效服务配置也无效]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>eureka</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring-cloud-helloworld]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-version%2F</url>
<content type="text"><![CDATA[SpringCloud版本号很多入门的教程里,发现spring-cloud的版本号为一串英文,Brixton.RELEASE,Dalston.SR4,现在更是到了Finchley.M8 Spring Cloud是一个拥有诸多子项目的大型综合项目,原则上其子项目也都维护着自己的发布版本号。那么每一个Spring Cloud的版本都会包含不同的子项目版本,为了要管理每个版本的子项目清单,避免版本名与子项目的发布号混淆,所以没有采用版本号的方式,而是通过命名的方式。 这些版本名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如:最早的Release版本:Angel,第二个Release版本:Brixton,以此类推…… 引用参考文章: http://blog.didispace.com/springcloud-version/]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>version</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring-cloud前言]]></title>
<url>%2Fspring-cloud%2Fspring-cloud-preface%2F</url>
<content type="text"><![CDATA[开始梳理SpringCloud的学习笔记和代码,并做一个简单的分享 学习SpringCloud前,最好先学习springboot 1. 目标 主要是自己学习过程的分享 如果当时用在项目上,会根据项目的需要做自定义的改变 优先使用各模块当时的最新版本(当前最稳定的是Edgware.SR2(对应boot:1.5.9.RELEASE,这里使用这个版本))(这个看来应该是spring-boot-1.x的最后版本,Finchley.M7版本对应的是boot2.x) 现在是入门级的分享,以后深入研究会一个个模块做分享 2. 规范(建议)2.1 bootstrap.yaml官方建议 机器翻译: 优先级:bootstrap.yaml > application.yaml 如果某个配置在application.yaml和bootstrap.yaml中都有,使用的是bootstrap.yaml中的。 bootstrap.yaml放引用cloud组件的配置 application.yaml放boot独有的配置 3. 上帝视角 微服务在这里暂时分成四个部分: 服务注册中心 服务应用(业务) 配置中心 网关 其中前三个又各自负载均衡,整体的容错率就很高]]></content>
<categories>
<category>spring-cloud</category>
</categories>
<tags>
<tag>spring-cloud</tag>
<tag>preface</tag>
</tags>
</entry>
<entry>
<title><![CDATA[自定义服务开启标语banner]]></title>
<url>%2Fspring-boot%2Fspring-boot-banner%2F</url>
<content type="text"><![CDATA[操作在 /src/main/resources目录新建一个banner.txt文件,将ASCII字符画复制进去就OK。 示例1234567891011121314151617181920212223${AnsiColor.BRIGHT_YELLOW}////////////////////////////////////////////////////////////////////// _ooOoo_ //// o8888888o //// 88" . "88 //// (| ^_^ |) //// O\ = /O //// ____/`---'\____ //// .' \\| |// `. //// / \\||| : |||// \ //// / _||||| -:- |||||- \ //// | | \\\ - /// | | //// | \_| ''\---/'' | | //// \ .-\__ `-` ___/-. / //// ___`. .' /--.--\ `. . ___ //// ."" '< `.___\_<|>_/___.' >'"". //// | | : `- \`.;`\ _ /`;.`/ - ` : | | //// \ \ `-. \_ __\ /__ _/ .-` / / //// ========`-.____`-.___\_____/___.-`____.-'======== //// `=---=' //// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //// BUDDHA BLESS NEVER BUG ////////////////////////////////////////////////////////////////////// 其中AnsiColor.BRIGHT_YELLOW表示黄色字体显示标语。 工具patorjk.com/software/taag www.network-science.de/ascii/ www.degraeve.com/img2txt.php]]></content>
<categories>
<category>spring-boot</category>
</categories>
<tags>
<tag>spring-boot</tag>
<tag>banner</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot使用SwaggerUI构建API文档服务]]></title>
<url>%2Fspring-boot%2Fspring-boot-swagger%2F</url>
<content type="text"><![CDATA[目标 低耦合、低侵入 能很方便的生成API文档 方便测试 2018/3/7更新 引入Jar1234567891011<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version></dependency> 配置123456789101112131415161718192021222324252627282930313233343536373839404142package com.career.swaggerdemo.config;import io.swagger.annotations.ApiOperation;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Contact;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;/** * @author LiuWang * @date 2018/2/28 */@Configuration@EnableSwagger2public class Swagger { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo(){ Contact contact = new Contact("妙语生花", "http://www.myblogs.work", "[email protected]"); return new ApiInfoBuilder() .title("Spring Boot中使用Swagger2构建RESTful APIs") .description("更多Spring Boot相关文章请关注:http://www.myblogs.work/") .termsOfServiceUrl("http://www.myblogs.work/") .contact(contact) .version("1.0") .build(); }} controller123//controller类添加注解,对此controller做说明@Api(tags = "设备统计")@RestController model123456789101112131415161718192021222324252627282930313233343536373839import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;/** * 设备信息统计所使用实体 * * @author LiuWang * @date 2017/11/15 19:20 */@Data@ApiModel(description = "设备状态信息")public class SbdqztxxEntity { @ApiModelProperty(value = "开始时间", notes = "yyyy-MM-dd hh:mm:ss") private String beginTime; @ApiModelProperty(value = "结束时间", notes = "yyyy-MM-dd hh:mm:ss") private String endTime; @ApiModelProperty(value = "设备名称", notes = "通过URLEncoder编码后的值") private String equName; @ApiModelProperty(value = "设备状态") private String equStatus; @ApiModelProperty(value = "设备类型") private String equType; @ApiModelProperty(value = "区域名称", notes = "通过URLEncoder编码后的值") private String areaName; @ApiModelProperty(value = "部门隶属关系") private String lsgx; @ApiModelProperty(value = "是否有经纬度", notes = "'1'表示有") private String hasGeo; @ApiModelProperty(value = "是否导Excel", notes = "'true'为导出") private String isExcel; //分页参数 @ApiModelProperty(value = "分页索引", notes = "页数默认从1开始") private int pageIndex = 1; @ApiModelProperty(value = "每页数量", notes = "默认10个") private int pageSize = 10;} 传参参考资料:https://swagger.io/docs/specification/describing-parameters/ 传复杂对象参数不用写@ApiImplicitParam,对应的参数解释可以在model里写 传简单参数,可以写ApiImplicitParam做详细的说明 ps:复杂对象表示:一个实体内有多个参数;简单参数,就是直接写在方法参数列表中,一个参数对应一个值。 paramType: path 通过url传参 query 通过?传参 body 通过body传参 header 通过头信息传参 cookie 通过cookie传参,如:Cookie: debug=0; csrftoken=BUSe35dohU3O1MZvDCU 如:Cookie: debug=0; csrftoken=BUSe35dohU3O1MZvDCU query传参?带参数 设置设置ApiImplicitParam的paramType = “body” 单个参数123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354//传实体参数不用写@ApiImplicitParam,对应的参数解释可以在model里写@ApiOperation(value = "设备状态统计")@GetMapping("/equipStatus")public ActionResult getEquipmetStatus(HttpServletResponse response, SbdqztxxEntity zt) throws IOException { String areaName = zt.getAreaName(); if (StringUtils.isBlank(areaName) || StringUtils.isBlank(zt.getLsgx())) { return new ActionResult( ResultEnum.PARAM_ERROR.getResultCode(), "areaName与lsgx必传", null ); } //前台对中文编码处理 zt.setAreaName(URLDecoder.decode(areaName, "UTF-8")); String equName = zt.getEquName(); if (StringUtils.isNotBlank(equName)) { zt.setEquName(URLDecoder.decode(equName, "UTF-8")); } if ("true".equals(zt.getIsExcel())) { //导出EXCEL return new ActionResult( ResultEnum.SUCCESS.getResultCode(), equipmentCountService.equipmetStatus2Excel(response, zt), null ); } return new ActionResult( ResultEnum.SUCCESS.getResultCode(), equipmentCountService.getEquipmetStatus(zt), null );}@ApiOperation(value = "所有设备统计")//这里可以不写,不写就不会有参数的解释@ApiImplicitParam(name = "isExcel", value = "true为导出Excel", dataType = "String", paramType = "query", example = "true")@GetMapping("allStatis")public ActionResult allEquipStatis(HttpServletResponse response, String isExcel) throws IOException { if ("true".equals(isExcel)) { //导Excel return new ActionResult( ResultEnum.SUCCESS.getResultCode(), equipmentCountService.allEquipStatis2Excel(response), null ); } return new ActionResult( ResultEnum.SUCCESS.getResultCode(), equipmentCountService.allEquipStatis(), null );} 多参数123456789101112131415161718192021@ApiOperation(value = "***信息")@ApiImplicitParams({ @ApiImplicitParam(name = "hphm", value = "车牌号", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "hpzl", value = "车牌种类", dataType = "String", paramType = "query") })@GetMapping("/carInfo")public ActionResult carInfo(String hphm, String hpzl) { List result = indexCountService.carInfo(hphm, hpzl); if (result == null) { return new ActionResult( ResultEnum.REMOTE_SERVER_FAILED.getResultCode(), "****接口调用失败", null ); } return new ActionResult( ResultEnum.SUCCESS.getResultCode(), result, null );} body传参post方式通过body传参 设置ApiImplicitParam的paramType = “body” 123456789101112@ApiOperation(value = "测试SwaggerUI功能", notes = "post方式body传实体对象参数")@PostMapping("/testBody")public String testBody(@RequestBody(required = false) EmpEntity con) { return "SUCCESS";}@ApiOperation(value = "测试SwaggerUI功能", notes = "post方式body传简单对象参数")@ApiImplicitParam(name = "con", value = "条件", paramType = "body")@PostMapping("/testBodySec")public String testBodySec(@RequestBody(required = false) String con) { return "SUCCESS";} URL传参传给简单对象 @PathVariable注解必须有,不然得不到值 ApiImplicitParam注解可以不要,写的话,paramType = “path”,推荐写上 1234567@ApiOperation(value = "测试SwaggerUI功能", notes = "get方式url传简单参数")@ApiImplicitParam(name = "con", value = "条件", paramType = "path")@GetMapping("/testUrlParam/{con}")public String testUrlParam(@PathVariable String con) { return con;} 传给复杂对象 ApiImplicitParam必须要,否则此接口URL为localhost:8080//testUrlBody/{empno},empno的值由?传参过来。而不是把empno的值作为url的一部分 不要@PathVariable注解 123456@ApiOperation(value = "测试SwaggerUI功能", notes = "get方式url传自定义对象参数")@ApiImplicitParam(name = "empno", value = "员工编号", paramType = "path")@GetMapping("/testUrlBody/{empno}")public String testUrlBody(EmpEntity con) { return "SUCCESS";} 其它其它传参方式少见,这里暂不介绍 访问ip:port/swagger-ui.html 效果 测试接口 点 Try it out 显示参数输入框 点所有参数下方的Execute请求接口 问题这个版本的swagger-ui好像对浏览器的版本有要求, 有些浏览器访问不到,推荐chrome 参考文档详细见官方文档https://swagger.io/docs/specification/about/]]></content>
<categories>
<category>spring-boot</category>
</categories>
<tags>
<tag>spring-boot</tag>
<tag>swagger</tag>
</tags>
</entry>
<entry>
<title><![CDATA[本地预览Jekll博客]]></title>
<url>%2Fjekyll%2Flocal-server%2F</url>
<content type="text"><![CDATA[之前一直都是将代码提交到github然后刷新页面看效果,这样一个小问题很多时候得重复提交好多次才解决 为了解决这个问题,需要现在本地调试预览 这其中,经历的东西全都是没用过的,各种找教程,也几次快要放弃,最终辛亏是搭起来了 知识储备这一方面是看的教程,一方面是对应的总结,知道这些,对于博客的搭建过程,知道如何去排错,解决问题 ruby与rubygem、jekyll、bundle、rvm的关系 ruby是一种脚本语言 ruby的其中一个“程序”叫rubygems,简称 gem jekyll是基于ruby的,所以搭建jekyll之前必须确保ruby正常安装 bundle是用来管理项目的gem的 rvm是用来管理ruby的,ruby的其中一个“程序”叫rubygems,简称 gem 所以,更新gem的时候,通过gem手动更新和通过bundle自动更新并不一样 自动更新命令:bundle update (更新所有,并自动更改Gemfile.lock文件) 更新某一个gem: bundle update json(更新json) 安装指定版本的gem:gem install json -v 1.8.6 查看版本: gem list 查看指定gem的版本:gem list json 安装环境使用的是 ruby-2.3.3-x64-mingw32.7z DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe(与ruby版本对应) 安装方法见jekyll博客搭建之艰辛之路—-撒网要见鱼 与教程的版本岁不同,但使用我这个版本是可以的,只不过挺麻烦。 解决问题在用jekyll serve(jekyll s)开启服务时,总会报一系列的错,有时候是有些东西需要更新,通过bundle或gem的更新命令更新,有些时候gem是最新的,但Gemfile.lock中使用的版本没有,可以通过命令安装制定版本的gem SSL证书在最后一步差点放弃,最后过了一天,尝试了下百度挺容易就解决了 用的方法是手动添加SSL证书 下载 cacert.pem file 证书下载 在命令行中输入 12rem 将SSL_CERT_FILE设置为pem的路径set SSL_CERT_FILE=E:\rubyDevkit\lib\ssl_book\cacert.pem 运行 每次运行都要执行步骤2,貌似可以将SSL_CERT_FILE设置为环境变量来解决,我是直接将“SSL证书”中步骤2的命令写在shell脚本中 通过http://localhost:4000访问,更新完刷新页面 感谢解决SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed.教程的指导]]></content>
<categories>
<category>jekyll</category>
</categories>
<tags>
<tag>jekyll</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot监控服务]]></title>
<url>%2Fspring-boot%2Fspring-boot-admin%2F</url>
<content type="text"><![CDATA[通过spring-boot-admin来监控SpringBoot服务 使用Spring Boot Actuator监控,1,所有监控需要调用固定接口;2,如果SpringBoot集群很大,每个应用需要调用不同接口来查看监控信息,因此太繁琐,spring-boot-admin致力于解决这类问题 serverpom.xml12345<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>1.5.7</version></dependency> application.yaml12server: port: 8000 基类基类中添加@EnableAdminServer开启监控服务 clientpom.xml12345<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>1.5.7</version></dependency> application.yaml1234567891011server: port: 8001spring: boot: admin: # admin server端的url url: http://localhost:8000management: security: # 关闭安全验证 enabled: false 然后访问http://localhost:8000]]></content>
<categories>
<category>spring-boot</category>
</categories>
<tags>
<tag>spring-boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[邮件服务]]></title>
<url>%2Fspring-boot%2Fspring-boot-mail%2F</url>
<content type="text"><![CDATA[由于密码等信息写在配置文件中可能泄露,因此这里示例自定义配置,。 使用Spring提供的自动配置,这里不做详述,详情参考邮件服务–纯洁的微笑 123# 此处使用126邮箱mail: host: smtp.126.com 123456789101112131415161718192021222324252627@Configuration@ConfigurationProperties(prefix = "mail")public class MailConfig { //只有host需要配置 private String host; private final String username = "用户名"; private final String password = "密码"; private String defaultEncoding = "UTF-8"; @Bean(name = "JavaMailSender") public JavaMailSender getSender() { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); mailSender.setUsername(username); mailSender.setPassword(password); mailSender.setDefaultEncoding(defaultEncoding); mailSender.setHost(host); return mailSender; } public String getHost() { return host; } public void setHost(String host) { this.host = host; }} 由于Spring的邮件发送会默认自动装配Spring.mail相关配置,且创建一个JavaMailSender的bean,这里是采用自己的配置且自动示例一个Bean]]></content>
<categories>
<category>spring-boot</category>
</categories>
<tags>
<tag>spring-boot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[什么?网页不能显示最新的?]]></title>
<url>%2Fblog%2Fgithub-page%2F</url>
<content type="text"><![CDATA[看到github上的内容已经是最新的,为什么网站上就是不能显示最新的呢,为什么呢? 当页面有error时,页面就会显示上一次完好的内容,或者显示初始的内容:) 当页面有问题(error或者warning)时,github会给你发邮件哒,注意查收哦。]]></content>
<categories>
<category>blog</category>
</categories>
<tags>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用git同步]]></title>
<url>%2Fgit%2Fgit-index%2F</url>
<content type="text"><![CDATA[git的理念每一个地方就是一个仓库(repository),本地就是本地仓库,本地仓库就不区分是什么分支了,pull和push的时候,想同步哪个分支都可以自由选择。本地维护本地的代码。没有中央仓库的概念。 更新:pull 提交:commit,commit相当于是提交到本地仓库 推:push,推给远程仓库 可以先pull,有冲突就merge,然后push]]></content>
<categories>
<category>git</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我的网站]]></title>
<url>%2Fblog%2Fwebsite%2F</url>
<content type="text"><![CDATA[网址www.myblogs.work 域名解析解析设置 1. 记录类型 记录值设置为A,然后在github上可以获取到github page的ip地址为192.30.252.153 和 192.30.252.154,然后记录值分别写这两个IP 2. 主机记录设置为:www或@,对应网址为:www.myblogs.work或myblogs.work 3. 解析线路默认 4. TTL值直接默认10分钟 5.配置CNAME解析记录类型: CNAME, 主机记录: @或www, 解析线路: 默认, 记录值: lw5946.github.io(github page页的url), TTL值: 默认10分钟。 解析情况检测 检测在各地解析情况 window命令窗口检测 域名与服务关联域名与github page关联通过以上的设置会通过访问域名从而访问到github,那github如何知道访问的是哪个页面,说的page呢? 方法有两种(殊途同归): 在自己的page(username.github.io)页设置domain与域名对应(设置完会自动创建CNAME文件) 手动创建CNAME文件(无后缀名),文件内容为域名,如:www.myblogs.work 域名终于解析成功,代表的确是域名解析需要挺长的一段时间,这段时间,域名其实还相当于没有生效,虽然阿里云说域名新增解析即时生效,修改解析两小时之内生效,然而并不是如此。坑爹啊,浪费我很多时间、精力。 域名与云服务器关联此处以腾讯云服务器举例 进入云服务器实例查看公网ip,如图: 设置A记录类型对应到服务器公网ip,如图: 此时访问域名就等同于访问ip,但是服务在服务器上都是通过容器发布出来的,都有其端口,这种方式是用的默认端口(80) 说明若之前配置过,现在只是改一下记录类型,最好是用修改,记录类型修改,对应的记录值修改,而不是把之前的删掉重新建一个,我使用阿里云的域名管理,修改很快生效,但新建就得等一两天,好像DNS解析就是这个尿性,应该跟阿里云无关]]></content>
<categories>
<category>blog</category>
</categories>
<tags>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Jekyll使用]]></title>
<url>%2Fjekyll%2Fjekyll-help%2F</url>
<content type="text"><![CDATA[参考文档Jekyll使用说明 根目录site.url 1{{ site.url }}用来表示根目录,建议用,这样每一个路径都是完整的URL,方便迁移 高亮代码片段Jekyll自带语法高亮功能:(linenos显示行数) 例子如下: 1234567891011121314def show @widget = Widget(params[:id]) respond_to do |format| format.html # show.html.erb format.json { render json: @widget } endend 使用的时候有问题:Jekyll使用样例中是没有问题的,但是当我复制到这里时(不时放在代码块中),github page报错:highlight标签没有闭合。 补充:没有问题,是因为在介绍的文本中写了半截highlight标签,最后还是觉得highlight标签不好用,代码换行不够给力啊。而且markdown代码块标签不要和Jeklyll的highlight标签一起用。]]></content>
<categories>
<category>jekyll</category>
</categories>
<tags>
<tag>jekyll</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2Fblog%2Fhello-world%2F</url>
<content type="text"><![CDATA[个人博客开始使用 我的个人博客正式搭起来了,其实过程还是挺顺利的,不顺利的地方可能是因为域名解析需要时间,这个只有等待,等到明天看看结果,这个只有等待的事,我却折腾了半天,浪费了太多时间,一件事情的开始总不是那么的容易:)]]></content>
<categories>
<category>blog</category>
</categories>
<tags>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title><![CDATA[post blog demo]]></title>
<url>%2Fdemo%2Fblog-demo%2F</url>
<content type="text"><![CDATA[Quick note about CSS animation. CSS AnimationCSS3 animation lets an element gradually change from one style to another. Two steps: Use @keyframes to define an animation. Set this animation on an element with animation properties We could set properties one-by-one or with following shorthand: 1animation: [animation-name] [animation-duration] [animation-timing-function] [animation-delay] [animation-iteration-count] [animation-direction] [animation-fill-mode] [animation-play-state]; @keyframesIt defines what the animation looks like at each stage of the animation timeline. It is composed of: Name of the animation. For example, changeColor. Stages: From 0% to 100% to represent the whole process of animation CSS Properties: The CSS properties defined for each stage of the animation timeline. Following example creates an animation called changeColor and assign it to div:hover: 123456789101112131415@keyframes changeColor { 0% { background: red; } 60% { background: blue; } 100%{ background: green; }}div:hover{ animation: changeColor 5s ease .1s;} In above example, we could also use from to represent 0% and to to represent 100% Animation PropertiesIt has following properties: animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction animation-fill-mode animation-play-state animation-nameThe name of the animation, defined in the @keyframes. animation-durationThe duration of the animation, in seconds (e.g., 5s) or milliseconds (e.g., 200ms). animation-timing-functionThe speed curve or pace of the animation: Timing Function Description linear The animation has the same speed from start to end ease Default value. The animation has a slow start, then fast, before it ends slowly. ease-in Start slowly and end fast. ease-out Start more quickly than linear ones and end slowly. The opposite of ease-in. ease-in-out Both a slow start and a slow end initial Sets this property to its default value. So ease. inherit Inherits this property from its parent element. Check The basics of easing for details. animation-delayIt specifies when the animation will start. The value is defined in seconds (s) or milliseconds (mil). animation-iteration-countIt specifies the number of times that the animation will play. The possible values are: a specific number of iterations (default is 1) infinite - repeats forever initial inherit animation-directionIt specifies whether the animation should play forward, reverse, or in alternate cycles. normal - Default. On each cycle the animation resets to the beginning state (0%) and plays forward again (to 100%). reverse - On each cycle the animation resets to the end state (100%) and plays backwards (to 0%). alternate - On each odd cycle, the animation plays forward (0% to 100%). On each even cycle, the animation plays backwards (100% to 0%). alternate-reverse - On each odd cycle, the animation plays in reverse (100% to 0%). On each even cycle, the animation plays forward (0% or 100%). animation-fill-modeIt specifies if the animation styles are visible before or after the animation plays. normal - Default. The animation does not apply any styles to the element, before or after the animation. forwards - After the animation is finished, the styles defined in the final keyframe (100%) are retained by the element. backwards - Before the animation (during the animation delay), the styles of the initial keyframe (0%) are applied to the element. both - forwards with backwards. animation-play-stateTwo values: running and paused. It specifies whether the animation is playing or paused. Resuming a paused animation starts the animation where it was left off. But if pause an animation, the element style will return back to its origin. Example: 123div:hover { animation-play-state: paused;} Multiple AnimationsAdd multiple animations to a selector with comma: 123div { animation: animationA 2s, animationB 2s;} Refs Imooc 十天精通CSS3 CSS Animation for Beginners CSS3 animation-timing-function Property]]></content>
<categories>
<category>demo</category>
</categories>
<tags>
<tag>demo</tag>
</tags>
</entry>
</search>