0%

有必要记录下自己常用的插件,避免特殊事件的发生。

MybatisX

Mybatis 插件,可以定位java和xml关系。通过java接口快速生成相关xml文件或者方法。

Mybaits Log plugin

使用插件将mybaits默认的日志输出转换成可以运行的sql日志。

Alibaba-java-coding-guidelines

阿里巴巴java代码检查工具

jclasslib bytecode viewer

byte字节码查看

json2pojo

json转bean的工具

PlantUML integration

画图的软件

SequenceDiagram

时序图

Translation

翻译

查看某个方法被调用的地方。(鼠标选中方法名称)

Navigate | Call Hierarchy 快捷键control + option + h

Genterate 代码生成

Code | Genterate #快捷键 command + n

Edit | Copyright | Copyright profiles

  • 样板
1
2
3
Copyright (c) $today.year  
@Author:z201.coding@gamil.com
@LastModified:$today.format("yyyy-MM-dd")
阅读全文 »

0x00 阅读源代码

长达9天的断网终于结束了。

下载源代码

  • 代码仓库地址 https://gitee.com/Z201/skywalking.git
  • 阅读版本号 v6.0.0-GA
  • 下载版本git clone -b v6.0.0-GA https://github.com/apache/skywalking.git
    • 由于github下载速度是在太慢了,这里用gitee克隆一个镜像。
    • 下载版本git clone -b v6.0.0-GA https://gitee.com/Z201/skywalking.git

查看源代码结构

源码设计子项目过多,这里简单暂时2级目录。

1
2
3
4
5
6
7
tree -d L 2
.
├── src
│ ├── main # 源代码
│ ├── site # 站点稳当
│ └── test # 单元测试
└── travis # 官方的ci集成
  • 查看项目源码主目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  skywalking git:(8b638258b) tree -d -L 1
.
├── apm-application-toolkit
├── apm-checkstyle
├── apm-commons
├── apm-dist
├── apm-protocol # pd文件
├── apm-sniffer # 插件
├── apm-webapp
├── docker
├── docs
├── licenses
├── oap-server # 这里和5.x 有很大的不同改动了很多东西。
├── skywalking-ui
└── tools
  • 将项目导入idea中。

官方文档:如何构建

git submodule init

git submodule update

1
2
3
4
5
6
7
8
9
10
11
12
➜  skywalking git:(8b638258b) git submodule init
Submodule 'apm-protocol/apm-network/src/main/proto' (https://github.com/apache/incubator-skywalking-data-collect-protocol.git) registered for path 'apm-protocol/apm-network/src/main/proto'
Submodule 'oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol' (https://github.com/apache/incubator-skywalking-query-protocol.git) registered for path 'oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol'
Submodule 'skywalking-ui' (https://github.com/apache/incubator-skywalking-ui) registered for path 'skywalking-ui'
➜ skywalking git:(8b638258b) git submodule update
Cloning into '/Users/zengqingfeng/word/source-code/skywalking/apm-protocol/apm-network/src/main/proto'...
Cloning into '/Users/zengqingfeng/word/source-code/skywalking/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol'...
Cloning into '/Users/zengqingfeng/word/source-code/skywalking/skywalking-ui'...
Submodule path 'apm-protocol/apm-network/src/main/proto': checked out 'b66fa070fd647662f06497e4ed3657eb258cb6e9'
Submodule path 'oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol': checked out 'c65a23bd6b9bba8d1df30d4de261624952df2b7b'
Submodule path 'skywalking-ui': checked out 'c44642f73b9f73a54b0d716cade5094304e1a67b'

clean package -DskipTests

这个时候慢慢等吧v6.0.0-GA有120个子项目。更新完之后导入IDEA就好了。

END

最近在看项目的时候发现一个临时问题,就是redis.timeout设置0依然超时的问题。

  • 代码适用版本 jedis:2.9.0 , org.springframework.data.redis:1.8.1

timeout设置0的问题

spring.redis.timeout =0 如果设置成0 redis默认超时时间就是2秒.

根据debug发现设置0点时候依然是2000毫秒。

根据初始化方法找到的原因。

这里可以看到,当timeout设置0点时候不会被赋值。

END

基础理论篇学习笔记

Arthas(阿尔萨斯) 能为你做什么?

arthas

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?

Arthas支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

快速入门

1
2
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息

1
java -jar arthas-boot.jar -h

快速启动(使用国内阿里下载包)

1
java -jar arthas-boot.jar --repo-mirror aliyun

  • 如果当前没有java程序运行则提示这个内容,并且不会被正常启动。

demo程序作为演示

1
https://github.com/hengyunabc/spring-boot-inside
  • 运行效果

我们先运行一个demo,然后让arthas运行起来。

1
2
3
4
5
6
7
cd spring-boot-inside // 进入文件夹

cd demo-arthas-spring-boot // 进入demo项目

mvn clean install // 构建项目

java -jar demo-arthas-spring-boot-0.0.1-SNAPSHOT.jar // 启动项目

启动项目之后在运行arthas

1
java -jar arthas-boot.jar

如果已经有启动的项目,会提示 [1] project-name 这种。输入数字选择对应的项目即可。

END

关于Redis场景下简单的处理方式。

源码地址

缓存穿透和缓存击穿

  • 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
  • 解决办法
    • 缓存空对象:代码维护较简单,但是效果不好。不过部分场景能使用。

演示环境

  • spring boot 2.4.5
  • mysql
  • Redis
  • 测试环境采用docker-compose

缓存穿透

  • 理想的情况下我们数据缓存到redis里面,客户访问数据的时候直接去缓存查询,如果没有缓存就去数据库查询。
  • 仔细想想这里其实有一个bug,必须是缓存有的数据才会去数据库查询,如果一直都是在查询一个没有得数据咋办呢,缓存会一直落空,而且每次都会去数据库查询。

缓存击穿

这个场景更多是应用在多线程的环境,这里衔接上面的代码,当并发量上来的时候,大量的查询集中查询某一个key,若这个时候key刚好失效了,就会导致请求全部进入数据中。这个就是所谓的缓存击穿。

常用解决思路

这里注意列举常用方案,根据实际情况使用

空值缓存

在访问缓存key之前,设置另一个短期key来锁住当前key的访问。这个方案可以降低持续缓存落空的情况。至少数据库被穿透的概率小很多了。

  • Redis方案
    • 如上图所示,当缓存和数据库中都没找到的时候就将查询条件作为key并在缓存中插入一个空的数据,并指定好过期时间。这样下次的请求就会被缓存命中。

代码实现

阅读全文 »

docker查找镜像

docker search elasticSearch

docker 拉去镜像

docker pull elasticsearch:5.6.8 直接拉去官方的镜像, 可以使用指定版本。

docker启动elasticsearch

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -v $PWD/elasticsearch/data:/usr/share/elasticsearch/data -e "discovery.type=single-node" elasticsearch:5.6.8

1
2
3
4
--name elasticsearch 指定容器名字
-p 9200:9200 -p 9300:9300 将容器的9200,9300端口映射成主机的9200,9300端口
-e "discovery.type=single-node" 指定单节点模式
elasticsearch:5.6.8 images名称

  • 这里只是演示单个节点。

  • 这里的日志输出有点问题,然后我也没挂载到本地。下次写集群部署到时候在来弄。

END

docker查找镜像

docker search mysql

docker 拉去镜像

docker pull mysql:5.7 直接拉去官方的镜像, 可以使用指定版本。

docker启动mysql

docker run -p 3306:3306 -v $PWD/mysql/conf:/mysql/etc/mysql/conf.d -v $PWD/mysql/logs:/logs -v $PWD/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

1
2
3
4
5
-p 3306:3306 将容器的3306端口映射成主机的3306端口
-v $PWD/mysql/conf:/mysql/etc/mysql/conf.d 将主机中当前目录下的mysql/conf挂载到容器的/mysql/etc/mysql/conf.d
-v $PWD/mysql/logs:/logs 将主机中当前目录下的mysql/logs挂载到容器/logs
-v $PWD/mysql/data:/var/lib/mysql 将主机中当前目录下的mysql/data挂载到容器/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=123456 设置root 密码 123456

  • 这里需要注意的是,本机有多个Mysql的时候,不要都挂载到一起了。

进入运行容器中

docker exec -it f1a0ccab4748 /bin/bash

  • f1a0ccab4748 是mysql运行容器的ID

END

本章是整理知识内容,为强化知识长期更新。

spring-redis-string

Spring-data-redis 有两个客户端,本章节主要使用lettuce来作为代码演示。尽可能面向redis编程。

代码实现

  • 接口
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
package cn.z201.learning.redis.operating.service;


import java.util.HashMap;
import java.util.List;

/**
* @author z201.coding@gmail.com
* @see cn.z201.learning.redis.operating.service.impl.RedisStringImpl
**/
public interface RedisStringI {

/**
* 将字符串值 value 关联到 key 。
* 如果 key 已经持有其他值, SET 就覆写旧值, 无视类型。
* 当 SET 命令对一个带有生存时间(TTL)的键进行设置之后, 该键原有的 TTL 将被清除。
* {@link = http://redisdoc.com/string/set.html }
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param value
* @return
*/
Boolean set(String key, String value);

/**
* 只在键 key 不存在的情况下, 将键 key 的值设置为 value 。
* 若键 key 已经存在, 则 SETNX 命令不做任何动作。
* SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
* {@link = http://redisdoc.com/string/setnx.html }
*
* @param key
* @param value
* @return
*/
Boolean setNx(String key, String value);

/**
* 将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟。
* 如果键 key 已经存在, 那么 SETEX 命令将覆盖已有的值。
* <p>
* {@link = http://redisdoc.com/string/setex.html}
* 可用版本: >= 2.0.0
* 时间复杂度: O(1)
*
* @param key
* @param timeout seconds 秒
* @param value
* @return
*/
Boolean setEx(String key, Long timeout, String value);

/**
* 这个命令和 SETEX 命令相似, 但它以毫秒为单位设置 key 的生存时间, 而不是像 SETEX 命令那样以秒为单位进行设置。
* <p>
* {@link = http://redisdoc.com/string/psetex.html}
* 可用版本: >= 2.6.0
* 时间复杂度: O(1)
*
* @param key
* @param timeout milliseconds
* @param value
* @return
*/
Boolean pSetEx(String key, Long timeout, String value);

/**
* 返回与键 key 相关联的字符串值。
* {@link = http://redisdoc.com/string/get.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Object get(String key);

/**
* 获取key设置的value,并返回之前设置的值。
* {@link = http://redisdoc.com/string/getset.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param value
* @return
*/
Object getSet(String key, String value);

/**
* 返回key对应value的长度
* {@link = http://redisdoc.com/string/strlen.html}
* 可用版本: >= 2.2.0
* 复杂度: O(1)
*
* @param key
* @return
*/
Long strLen(String key);

/**
* 如果键 key 已经存在并且它的值是一个字符串, APPEND 命令将把 value 追加到键 key 现有值的末尾。
* 如果 key 不存在, APPEND 就简单地将键 key 的值设为 value , 就像执行 SET key value 一样。
* {@link = http://redisdoc.com/string/append.html}
* 可用版本: >= 2.0.0
* 时间复杂度: 平摊O(1)
*
* @param key
* @param value
* @return
*/
Long appEnd(String key, String value);

/**
* 从偏移量 offset 开始, 用 value 参数覆写(overwrite)键 key 储存的字符串值。
* 不存在的键 key 当作空白字符串处理。
* SETRANGE 命令会确保字符串足够长以便将 value 设置到指定的偏移量上,
* 如果键 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,
* 但你设置的 offset 是 10 ), 那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )进行填充。
* <p>
* {@link = http://redisdoc.com/string/setrange.html}
* 可用版本: >= 2.2.0
* 时间复杂度:对于长度较短的字符串,命令的平摊复杂度O(1);对于长度较大的字符串,命令的复杂度为 O(M) ,其中 M 为 value 的长度。
*
* @param key
* @param offset
* @param value
* @return
*/
Boolean setRange(String key, Long offset, String value);


/**
* 返回键 key 储存的字符串值的指定部分, 字符串的截取范围由 start 和 end 两个偏移量决定 (包括 start 和 end 在内)。
* 负数偏移量表示从字符串的末尾开始计数, -1 表示最后一个字符, -2 表示倒数第二个字符, 以此类推。
* GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
* <p>
* {@link = http://redisdoc.com/string/setrange.html}
* 可用版本: >= 2.4.0
* 时间复杂度: O(N),其中 N 为被返回的字符串的长度。
*
* @param key
* @param start
* @param end
* @return
*/
Object getRange(String key, Long start, Long end);

/**
* 为键 key 储存的数字值加上一。
* 如果键 key 不存在, 那么它的值会先被初始化为 0 , 然后再执行 INCR 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 INCR 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/incr.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Long incr(String key);

/**
* 为键 key 储存的数字值加上增量 increment 。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 INCRBY 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 INCRBY 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/incrby.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Long incrBy(String key, Long sum);

/**
* 为键 key 储存的值加上浮点数增量 increment 。
* 如果键 key 不存在, 那么 INCRBYFLOAT 会先将键 key 的值设为 0 , 然后再执行加法操作。
* 如果命令执行成功, 那么键 key 的值会被更新为执行加法计算之后的新值, 并且新值会以字符串的形式返回给调用者。
* 无论是键 key 的值还是增量 increment , 都可以使用像 2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示, 但是,
* 执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存, 也即是, 它们总是由一个数字,
* 一个(可选的)小数点和一个任意长度的小数部分组成(比如 3.14 、 69.768 ,诸如此类), 小数部分尾随的 0 会被移除,
* 如果可能的话, 命令还会将浮点数转换为整数(比如 3.0 会被保存成 3 )。
* 此外, 无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
* 当以下任意一个条件发生时, 命令返回一个错误:
* 键 key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型);
* 键 key 当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。
* <p>
* {@link = http://redisdoc.com/string/incrbyfloat.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Float incrByFloat(String key, Float sum);

/**
* 为键 key 储存的数字值减去一。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECR 操作。
* 如果键 key 储存的值不能被解释为数字, 那么 DECR 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* {@link = http://redisdoc.com/string/decr.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @return
*/
Long decr(String key);

/**
* 将键 key 储存的整数值减去减量 decrement 。
* 如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECRBY 命令。
* 如果键 key 储存的值不能被解释为数字, 那么 DECRBY 命令将返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
* <p>
* 可用版本: >= 1.0.0
* 时间复杂度: O(1)
*
* @param key
* @param sum
* @return
*/
Long decrBy(String key, Long sum);

/**
* 同时为多个键设置值。
* 如果某个给定键已经存在, 那么 MSET 将使用新值去覆盖旧值,
* 如果这不是你所希望的效果, 请考虑使用 MSETNX 命令, 这个命令只会在所有给定键都不存在的情况下进行设置。
* MSET 是一个原子性(atomic)操作, 所有给定键都会在同一时间内被设置, 不会出现某些键被设置了但是另一些键没有被设置的情况。
* {@link = http://redisdoc.com/string/mset.html}
* 可用版本: >= 1.0.1
* 时间复杂度: O(N),其中 N 为被设置的键数量。
*
* @param map
* @return
*/
Boolean mSet(HashMap<String, Object> map);

/**
* 当且仅当所有给定键都不存在时, 为所有给定键设置值。
* 即使只有一个给定键已经存在, MSETNX 命令也会拒绝执行对所有键的设置操作。
* MSETNX 是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。
* {@link = http://redisdoc.com/string/msetnx.html}
* 可用版本: >= 1.0.1
* 时间复杂度: O(N), 其中 N 为被设置的键数量。
*
* @param map
* @return
*/
Boolean mSetNx(HashMap<String, Object> map);

/**
* 返回给定的一个或多个字符串键的值。
* 如果给定的字符串键里面, 有某个键不存在, 那么这个键的值将以特殊值 nil 表示。
* {@link = http://redisdoc.com/string/mget.html}
* 可用版本: >= 1.0.0
* 时间复杂度: O(N) ,其中 N 为给定键的数量。
*
* @param key
* @return
*/
List<Object> mGet(String[] key);

}

阅读全文 »

本章是整理知识内容,为强化知识长期更新。

docker查找镜像

docker search zookeeper

docker 拉去镜像

docker pull zookeeper 直接拉去官方的镜像,一般第一个就是。

docker启动zookeeper

docker run -p 2181:2181 -v $PWD/zookeeper/data:/data -d zookeeper:latest

1
2
-p 2181:2181:将容器的2181端口映射成主机的2181端口
-v $PWD/zookeeper/data:/data :将主机中当前目录下的data挂载到容器的/data

  • 这里需要注意的是,本机有多zookeeper的时候,不要都挂载到一起了。

进入运行容器中

docker exec -it 3f6a8c213504 /bin/bash

  • 3f6a8c213504 是zookeeper运行容器的ID

END

本章是整理知识内容,为强化知识长期更新。

Docker环境下部署redis

确认已经安装好了Docker环境

windows 和 linux 可以用 docker info检查是否安装

  • 在mac下就是这么个情况。需要注意的是,请不用使用email登录,否则在查找官方镜像的时候会抛出认证失败的问题。比如我是登录用户z201
阅读全文 »

离职两个月,太久没写项目了,重新温故下。

  • 这里的代码是最简单的演示效果。

  • 项目下载地址 git cloen -b v1.0.0 https://github.com/z201/learning-spring-dubbo-micro-service.git

  • v1.0.0分支dubbo xml 配置演示基于zk做注册中心。

  • v2.0.0分支dubbo annotation 配置演示基于zk做注册中心。

  • v3.0.0分支dubbo xml 配置演示基于nacos做注册中心。

项目模块

  • 现在演示的v1.0.0的代码
1
2
3
4
5
6
<modules>
<module>dubbo-micro-service-consumer</module> // 消费者
<module>dubbo-micro-service-provider</module> // 生产者
<module>dubbo-micro-service-zookeeper</module> // 注册中心
<module>dubbo-micro-service-api</module> // 公用api
</modules>
阅读全文 »

前年的笔记内容,被我翻出来了。

Centos7-install-cmake

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处。

部署过程

1.下载cmake Source Code

1
wget https://cmake.org/files/v3.3/cmake-3.3.2.tar.gz  

2.解压缩

1
tar xzvf cmake-3.3.2.tar.gz  

3.执行引导命令

  • 注意:此时会检查gcc ,gcc-c++,是否安装和版本,如果没有请先安装。
  • 进入到cmake文件夹中执行
1
2
yum install gcc
yum install gcc-c++
1
2
cd cmake-3.3.2  
./bootstrap

4.执行make

1
gmake 

5.执行安装(root权限)

1
make install  

性能指标

在设计系统时,应该多思考**墨菲定律**

  • 任何事情都没表面看起来那么简单。
  • 所以事情都会比你预期的时间长。
  • 可能出错的事情总会出错。
  • 如果你担心某一件发生,那么它就更有可能发生。

在系统划分的时,也要思考**康威定律 **

  • 系统架构是公司组织架构的反映。
  • 应该按照业务闭环进行系统拆分/组织架构拆分,实现闭环/高内聚/低耦合,减少沟通成本。
  • 在合适的时间进行系统拆分,不要一开始就吧系统/服务拆分非常细。虽然闭环,但是每个人维护的系统多,维护成本高。

​ —亿级流量网站架构核心技术

性能指标

在技术人员的职业生涯中,总是不断开发新的系统。在设计系统的时候,因场景、时间而异。一个系统并不是开始就能设计的非常完美,绝对多数的情况是在有限的资源,优先解决核心问题。并预测可能发生的问题,通过反复迭代的逐步消除痛点。这本身是一个持久性的过程。但是早期优良的架构设计能为未来的工作带来非常大的帮助,如何去评判设计是否合理需要数据佐证。吞吐量、PV、QPS、TPS、RPS、I/O这些指标早设计初期能为开发人员提供非常大的帮助。

  • 吞吐量:指单位时间内系统处理用户的请求数,不同的角度评估方式也不同。
  • **PV ** : 是Page View的缩写。来自浏览器的一次html内容请求会被看作一个PV。
  • QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
  • TPS:是Transactions Per Second的缩写,也就是事务数/秒。它是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
  • RPS : 是Requests Per Second的缩写,指并发数/平均响应时间。
  • I/0 负载: I/O 是 input/output的缩写,即输入输出端口。每个设备都会有一个专用的I/O地址,用来处理自己的输入输出信息。一个系统要能正确工作,必须要有数据通道(data paths)的机制,软件和硬件系统都概莫能外。对于计算机系统而言,必须要有data paths的机制来确保CPU, RAM和I/O设备之间的信息数据能正确的流动。这些data paths,通常被称为总线(BUS),是计算机内部主要的通信通道。若想深入可以参考 Understanding the Linux Kernel
阅读全文 »

上周的一道笔试题

递归计算

根据两组参数,使用递归完成编程。

递归方法

​ 参数1 [1,3]

​ 参数2 n

当参数2 < 小于参数1中元素个数的时候,不输出。

当n = 3 时 输出 [1,3,4]

当n = 4 时 输出 [1,3,4,7]

当 n = 11 使用递归程序输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void test(List<Integer> list , Integer end) {
if(list.isEmpty() || list.size() < 1 || end == null || end < 0){
throw new RuntimeException("初始化参数异常");
}
if (list.size() < end) {
int length = list.size();
list.add(list.get(length - 1) + list.get(length - 2));
test(list , end);
}
}

public static void main(String[] args) {
Integer[] arr = new Integer[]{1,3};
List<Integer> list = new ArrayList(Arrays.asList(arr));
test(list , 11);
System.out.println(list.toString());
}

>>
[1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]

不过当时写的时候并没有判断异常。当时紧张硬编码。

古典问题:⼀对兔⼦

有⼀对兔⼦,从出⽣后第 3 个⽉起每个⽉都⽣⼀对兔⼦,⼩兔⼦⻓到第三个⽉后每个⽉⼜⽣⼀对兔⼦,

假如兔⼦都不死,问每个⽉的兔⼦对数为多少?

思路:斐波那契数列(Fibonacci sequence)

程序分析: 兔⼦的规律为数列 1,1,2,3,5,8,13,21…,当 n=1 或 n=2时,分别为 1。

从 n=3 开始 f(n) = f(n-1) + f(n-2)。所以转化为程序则如下:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
int n = 10;
System.out.println("第" + n + "个⽉兔⼦总数为" + fun(n));
}
private static int fun(int n) {
if (n == 1 || n == 2)
return 1;
else
return fun(n - 1) + fun(n - 2);
}

使⽤递归实现 n! 的阶乘

思路:求⼀个数的阶乘,既可以使⽤循环,也可以使⽤递归。本地要求使⽤递归。

把公式分解后 n! = n*(n-1)! ;但是 1的阶乘为 1。所以我们可以定义⼀个⽅法 jie(int n),假定⽅法就是

求阶乘的⽅法,则每次 n! = n * jie(n-1)。这样就实现了⽅法逻辑了。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
int n = 10;
System.out.println(n+"阶乘为:" + jie(n));
}
private static int jie(int n) {
if(n==1) {
return 1;
}else {
return n*jie(n-1);
}
}

⽤递归实现字符串倒转

思路:字符串倒序可以有多种实现⽅法,但是本地要求递归。所以我们需要找出相同的可以重复的⽅法来递归完成。

例如:“hello”

  • 第⼀次:截取第⼀个字符“h”,截取剩下“ello”,使⽤“ello”+“h”;

  • 第⼆次:那么“ello”字符串,可以使⽤相同的⽅法,“llo”+“e”;

  • 第三次:“lo”+“l”;

  • 第四次:“o”+“l”;

  • 第五次:“o”⻓度已经是⼀个了,所以依次返回给上⼀步“olleh”。

1
2
3
4
5
6
7
8
9
10
11
public class StringReverse {
public static String reverse(String originStr) {
if(originStr == null || originStr.length()== 1) {
return originStr;
}
return reverse(originStr.substring(1))+ originStr.charAt(0);
}
public static void main(String[] args) {
System.out.println(reverse("hello"));
}
}

18年年初的笔记,被我翻出来了。

EL(B)K平台

Elastic Stack(旧称ELK Stack),是一种能够从任意数据源抽取数据,并实时对数据进行搜索、分析和可视化展现的数据分析框架。(hadoop同一个开发人员)

elastic 官网

EL(B)K是啥技术

  • java 开发的开源的全文搜索引擎工具。
  • 基于lucence搜索引擎的。
  • 采用 restful - api 标准的。
  • 高可用、高扩展的分布式框架。
  • 实时数据分析的。

E

Elasticsearch 是基于 JSON 的分布式搜索和分析引擎,专为实现水平扩展、高可用和管理便捷性而设计。

L

Logstash 是动态数据收集管道,拥有可扩展的插件生态系统,能够与 Elasticsearch 产生强大的协同作用。

B

Beats 是轻量型采集器的平台,从边缘机器向 Logstash 和 Elasticsearch 发送数据。

K

Kibana 能够以图表的形式呈现数据,并且具有可扩展的用户界面,供您全方位配置和管理 Elastic Stack。

下载程序

阅读全文 »

18年年初的笔记,被我翻出来了。

Sonarqube

代码质量缺陷检测

sonarqube官网

安装实例

  • sonarqube6.7.1
  • sonar-scanner-cli-3.0.2.768

sonarqube是服务端包含es、ec、web。

sonar-scanner 客户端主要将代码筛选并上传至sonarqube。

阅读全文 »

Selenium

Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一。它最初由Jason Huggins于2004年开发,作为Thought Works的内部工具。Selenium支持跨不同浏览器,平台和编程语言的自动化。

  • Selenium可以轻松部署在Windows,Linux,Solaris和Macintosh等平台上。此外,它支持iOS(iOS,Windows Mobile和Android)等移动应用程序的OS(操作系统)。

  • Selenium通过使用特定于每种语言的驱动程序支持各种编程语言.Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ruby。目前,Selenium Web驱动程序最受Java和C#欢迎。Selenium测试脚本可以使用任何支持的编程语言进行编码,并且可以直接在大多数现代Web浏览器中运行。Selenium支持的浏览器包括Internet Explorer,Mozilla Firefox,Google Chrome和Safari。

Selenium WebDriver

Selenium WebDriver是Selenium RC的继承者。Selenium WebDriver接受命令(在Selenese中发送,或通过客户端API)并将它们发送到浏览器。这是通过特定于浏览器的浏览器驱动程序实现的,该驱动程序将命令发送到浏览器并检索结果。大多数浏览器驱动程序实际上启动并访问浏览器应用程序。

  • Selenium WebDriver是Selenium Tool套件中最重要的组件。最新版本“Selenium 2.0”与WebDriver API集成,提供更简单,更简洁的编程接口。

演示项目

环境配置

这里选用的是Google Chrome测试套件。

  • 注意,使用jdk8。

  • 下载ChromeDriver

    • 由三个独立的部分组成。有浏览器本身(“chrome”),Selenium项目提供的语言绑定(“驱动程序”)和从Chromium项目下载的可执行文件,它充当“chrome”和“driver”之间的桥梁。此可执行文件称为“chromedriver”,但我们将尝试将其称为此页面中的“服务器”以减少混淆。
    • 根据电脑安装的chrome版本选择合适的版本下载地址

快速安装

1
2
3
4
# Mac users with Homebrew installed: 
brew tap homebrew/cask && brew cask install chromedriver
# Debian based Linux distros:
sudo apt-get install chromium-chromedriver

快速更新

1
2
3
4
# Mac users with Homebrew reInstall 
brew cask reinstall chromedriver
# 如果已经存在新把之前的删除
rm /usr/local/bin/chromedriver
阅读全文 »

Jjwt-Junit

本章内容主要是用来实际运行下jjwt的api,完成相应的单元测试。

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
<properties>
<junit.version>4.12</junit.version>
<jjwt.version>0.10.5</jjwt.version>
<lmbok.version>1.18.4</lmbok.version>
<h2.version>1.4.197</h2.version>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<!-- pom文件需要指定打包编码集,[WARNING] File encoding has not been set, using platform encoding GBK, i.e. build is platform dependent! -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!--log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 测试框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>

测试代码

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
package cn.z201.java.test.jjwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import javax.crypto.SecretKey;
import java.time.Clock;
import java.util.Date;
import java.util.UUID;

/**
* @author z201.coding@gmail.com
**/
@Slf4j
public class JwtExampleTest {

private String createJWT(String id, String issuer, String subject, long ttlMillis, SecretKey key) {

Clock clock = Clock.systemDefaultZone();
Date now = new Date(clock.millis());
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(key);

// 设置过期时间
if (ttlMillis >= 0) {
long expMillis = clock.millis() + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}

// 返回编码之后的字符串
return builder.compact();
}

private void parseJWT(String jwt, SecretKey key) {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt).getBody();
log.info("ID: " + claims.getId());
log.info("Subject: " + claims.getSubject());
log.info("Issuer: " + claims.getIssuer());
log.info("Expiration: " + claims.getExpiration());
}

@Test
public void testJwt() {
//The JWT signature algorithm we will be using to sign the token
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jwt = createJWT(UUID.randomUUID().toString() , "issuer" , "subject" , 10000 ,key);
log.info("createJWT {} " , jwt );
parseJWT(jwt,key);
}

}

输出

22:18:48.641 [main] INFO cn.z201.java.test.jjwt.JwtExampleTest - createJWT eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjNjFmNzNmZi02OTM4LTQ2MDItYTNkNC1hY2FlZmI3N2Q2YTEiLCJpYXQiOjE1NTI3NDU5MjgsInN1YiI6InN1YmplY3QiLCJpc3MiOiJpc3N1ZXIiLCJleHAiOjE1NTI3NDU5Mzh9.t_ra42L21PMVzto1r2x1fSxCRZFkuk0hSzl2k-b-VWA
22:18:48.694 [main] INFO cn.z201.java.test.jjwt.JwtExampleTest - ID: c61f73ff-6938-4602-a3d4-acaefb77d6a1
22:18:48.694 [main] INFO cn.z201.java.test.jjwt.JwtExampleTest - Subject: subject
22:18:48.694 [main] INFO cn.z201.java.test.jjwt.JwtExampleTest - Issuer: issuer
22:18:48.696 [main] INFO cn.z201.java.test.jjwt.JwtExampleTest - Expiration: Sat Mar 16 22:18:58 CST 2019

0x00 阅读源代码

下载源代码

  • 代码仓库地址 https://gitee.com/Z201/spring-framework.git
  • 阅读版本号 v5.1.5.RELEASE
  • 下载版本git clone -b v5.1.5.RELEASE https://github.com/spring-projects/spring-framework.git
    • 由于github下载速度是在太慢了,这里用gitee克隆一个镜像。
    • 下载版本git clone -b v5.1.5.RELEASE https://gitee.com/Z201/spring-framework.git
  • 官方提供的文档import-into-idea
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
➜  spring-framework git:(master) ./gradlew :spring-oxm:compileTestJava

Downloading https://services.gradle.org/distributions/gradle-4.10.3-bin.zip
..........................................................................

Welcome to Gradle 4.10.3!

Here are the highlights of this release:
- Incremental Java compilation by default
- Periodic Gradle caches cleanup
- Gradle Kotlin DSL 1.0-RC6
- Nested included builds
- SNAPSHOT plugin versions in the `plugins {}` block

For more details see https://docs.gradle.org/4.10.3/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :spring-beans:compileTestJava
注: 某些输入文件使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
注: 某些输入文件使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

> Task :spring-context:compileTestJava
注: 某些输入文件使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
注: 某些输入文件使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

> Task :spring-oxm:genJaxb
[ant:javac] : warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds

BUILD SUCCESSFUL in 9m 18s
58 actionable tasks: 58 executed
➜ spring-framework git:(master)

如果显示BUILD SUCCESSFUL 说明构建成功了,但是由于项目太大了,所以下面就不介绍模块了。

本文主要是为了记录很久以前对Junit的回顾。

Junit

Java开发中使用的最多的测试框架,工作中经常会大量使用。

建议遵守约定

  • 测试类在test包下(如果是maven结构的项目建议建议不要方在源码中,早期的项目很多是没有区分开的)
  • 测试类命名xxxTest结尾。
  • 方法命名testxxxx命名。
  • 测试方法上必须使用@Test进行修饰。
  • 测试方法必须使用public void 进行修饰,不能带任何的参数。
  • 测试类的包应该和被测试类保持一致。
  • 测试单元中的每个方法必须独立测试,测试方法间不能有任何的依赖。

常用注解

  • @Test(expected = ArithmeticException.class) 预期将抛出一个算术异常。
  • @Test(timeout = 10) 改方法调用预期的时间范围 10毫秒,若超时算失败。
  • @Ignore 被改注解修饰的方法不会被执行。
  • @RunWith 可以更改测试运行器,制定的测试运行器需要继承 org.junit.runner.Runner。
阅读全文 »

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。也就是说JWT是Token的一种表述性声明规范。

JWT(Json Web Token)

JWT生成编码后的样子

  • 结构类似 xxx.yyy.zzz
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IuWkqeihjOWBpeeuoeeQhueUqOaItyIsImF1ZGllbmNlIjoid2ViIiwibmJmIjoxNTA3Njg0OTQyLCJpc3MiOiJ3d3cuMW9uZS5jbiIsImV4cCI6MTUwNzY4Njc0MiwiaWF0IjoxNTA3Njg0OTQyLCJqdGkiOjEwMDB9.GGF0kFbxNk2ezzuXEJVBZyyL4e4BYMdpse73cSDrUut7cbVyYuLG1CNr8RI7eI3VHz9sdCB14Kesi8rP-v3VJA
  • base64解析之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"body":{
"sub":"admin",
"aud":"XXX用户",
"audience":"web",
"nbf":1507684942,
"iss":"www.xxx.cn",
"exp":1507686742,
"iat":1507684942,
"jti":1000
},
"header":{
"typ":"JWT",
"alg":"HS512"
},
"signature":"GGF0kFbxNk2ezzuXEJVBZyyL4e4BYMdpse73cSDrUut7cbVyYuLG1CNr8RI7eI3VHz9sdCB14Kesi8rP-v3VJA"
}
阅读全文 »

FastDfs介绍

  • FastDFS是由国人余庆所开发,其项目地址:FastDFS。FastDfs是一个轻量级的开源分布式文件系统,主要解决了大容量的文件存储和高并发访问的问题,文件存取时实现了负载均衡。文件存取时实现了负载均衡FastDFS实现了软件方式的RAID,可以使用廉价的IDE硬盘进行存储支持存储服务器在线扩容支持相同内容的文件只保存一份,节约磁盘空间。

  • FastDFS只能通过Client API访问,不支持POSIX访问方式。

  • FastDFS特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)。

  • 它用纯C语言实现,支持Linux、FreeBSD、AIX等UNIX系统。它只能通过专有API对文件进行存取访问,不支持POSIX接口方式,不能mount使用。准确地讲,Google FS、HDFS、TFS(这三个都是底层上的实现,在文件系统上处理的是数据块)以及FastDFS、mogileFS(这两个是文件级上的实现,处理的是文件)等类Google FS都不是系统级的分布式文件系统,而是应用级的分布式文件存储服务。

  • 特性

    • 1、分组存储,灵活简洁、对等结构,不存在单点。
      2、文件ID由FastDFS生成,作为文件访问凭证。FastDFS不需要传统的name server(去重机制在服务端,导致不能达到秒传。)。
      3、和流行的web server无缝衔接,FastDFS已提供apache和nginx扩展模块。
      4、大、中、小文件均可以很好支持,支持海量小文件存储(实际上不建议存储超大文件(大于500M))。
      5、支持多块磁盘,支持单盘数据恢复(理论上,实际出现Storage合并丢操作失数据)。
      6、支持相同文件内容只保存一份,节省存储空间。
      7、存储服务器上可以保存文件附加属性。
      8、下载文件支持多线程方式,支持断点续传。
      

系统架构

阅读全文 »

​ 了解Xpath之前先了解下XML、HTML。如果完全不知道是什么,建议系统学习HTML、XML下阅读下文。

XML

可扩展标记语言(英语:Extensible Markup Language,简称:XML),是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。

示例-0

1
2
3
4
5
6
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

HTML

超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。HTML是一种基础技术,常与CSS、JavaScript一起被众多网站用于设计令人赏心悦目的网页、网页应用程序以及移动应用程序的用户界面。网页浏览器可以读取HTML文件,并将其渲染成可视化网页。HTML描述了一个网站的结构语义随着线索的呈现,使之成为一种标记语言而非编程语言。

HTML元素是构建网站的基石。HTML允许嵌入图像与对象,并且可以用于创建交互式表单,它被用来结构化信息——例如标题、段落和列表等等,也可用来在一定程度上描述文档的外观和语义。HTML的语言形式为尖括号包围的HTML元素(如<html>),浏览器使用HTML标签和脚本来诠释网页内容,但不会将它们显示在页面上。

HTML可以嵌入如JavaScript的脚本语言,它们会影响HTML网页的行为。网页浏览器也可以引用层叠样式表(CSS)来定义文本和其它元素的外观与布局。维护HTML和CSS标准的组织万维网联盟(W3C)鼓励人们使用CSS替代一些用于表现的HTML元素。

阅读全文 »

本章是整理知识内容,为强化知识长期更新。

set扩展

  • 分布式锁的简单实现
    • 在日常开发中,redis主要用在缓存处理。为保证缓存所以会使用集群的方式,避免缓存雪崩。都使用集群了应用层也不太可能是单应用。在分布式的情况下就会涉及分布式锁。a应用和b应用一起对某条缓存记录操作,a查完数据在内存修改,在放回去。如果这个操作两个应用同时进行就会出现并发问题。为了保证操作的原子性,所以就要使用锁来保证操作执行的顺序。
1
2
3
4
5
6
# 使用set的扩展命令 一次把expire和 setnx的特性带上。下面这条命令就是 过期时间5s 并且不可以重复添加。只能删除或者到期。
192.168.31.7:6379> set lock:redis true ex 5 nx
OK
192.168.31.7:6379> set lock:redis true ex 5 nx # 第二次在设置就失败了。
(nil)

  • 但是这种分布式锁并不能完美解决问题,因为锁有过期时间,如果时间过期了但是操作依然未执行完成,后面的操作进来就可能会造成不可控影响。

异步消息列队

Redis list这个数据类型,非常适合做列队。使用rpush / lpush 操作入列,使用 lpop 和 rpop来出列队。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
192.168.31.7:6379> rpush queue a b c d 
(integer) 4
192.168.31.7:6379> llen queue
(integer) 4
# rpop 压栈
192.168.31.7:6379> lpop queue
"a"
192.168.31.7:6379> lpop queue
"b"
192.168.31.7:6379> lpop queue
"c"
192.168.31.7:6379> lpop queue
"d"
# lpop 列队
192.168.31.7:6379> lpop queue
"a"
192.168.31.7:6379> lpop queue
"b"
192.168.31.7:6379> lpop queue
"c"
192.168.31.7:6379> lpop queue
"d"
  • 但是,redis的list一旦被消费完就会被删除,为了避免客户端一直读取。建议使用阻塞列队blpop\brpop的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用blocking 阻塞读,1就是阻塞时间。这样可以避免不断的读。
192.168.31.7:6379> rpush queue a b c d
(integer) 4
192.168.31.7:6379> blpop queue 1
1) "queue"
2) "a"
192.168.31.7:6379> blpop queue 1
1) "queue"
2) "b"
192.168.31.7:6379> blpop queue 1
1) "queue"
2) "c"
192.168.31.7:6379> blpop queue 1
1) "queue"
2) "d"
192.168.31.7:6379> blpop queue 1 # 最后一次 list已经空了。
(nil)
(1.08s) # 阻塞了一秒

key查询

  • 虽然redis已经有keys,并不能像mysql一个提供limit这种,导致读取的太多了。Redis是一个单线程,数据太多会阻塞。scan提供了三个参数 cursor key limit 分别是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
192.168.31.7:6379> set key1 1
OK
192.168.31.7:6379> set key2 1
OK
192.168.31.7:6379> set key3 1
OK
192.168.31.7:6379> set key4 1
OK
192.168.31.7:6379> set key5 1
OK
192.168.31.7:6379> scan 0 match key* count 10
1) "0"
2) 1) "key4"
2) "key3"
3) "key2"
4) "key5"
5) "key1"

Redis事务

Redis事物分别是multi/exec/discard 对应的就是begin/commit/rollback。Redis在收到exec的指令才会执行事务列队,执行完毕后一次返回指令运行结果。但是Redis仅仅是保证了个隔离性。在使用事物的时候建议使用管道pipe,这样可以减少网络请求。

1
2
3
4
5
6
7
8
9
10
# 简单的演示事务
192.168.31.7:6379> multi
OK
192.168.31.7:6379> set name wang
QUEUED
192.168.31.7:6379> set name zhang
QUEUED
192.168.31.7:6379> exec
1) OK
2) OK
  • 事务丢弃 discard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建一个字符串并对value进行累加。
192.168.31.7:6379> set count 1
OK
192.168.31.7:6379> get count
"1"
192.168.31.7:6379> multi # 开启事物
OK
192.168.31.7:6379> incr count
QUEUED
192.168.31.7:6379> incr count
QUEUED
192.168.31.7:6379> discard # 丢弃事物
OK
192.168.31.7:6379> get count #检查数据。
"1"

Watch

上面的分布式锁其实是一个悲观锁,实际上可以使用watch实现乐观锁。watch会在事务开始之前,监听一个或者多个参数,当执行exec的时候redis会检查被监听的参数是否改变,如果改变了事务执行失败。这里需要注意的是watch必须在事务之前开启。

1
2
3
4
5
6
7
8
9
10
11
# 监听count
192.168.31.7:6379> watch count
OK
192.168.31.7:6379> incr count # 自增count的value。
(integer) 2
192.168.31.7:6379> multi #开启事物
OK
192.168.31.7:6379> incr count
QUEUED
192.168.31.7:6379> exec
(nil) #事务执行失败。

END