开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

浅谈SimpleDateFormat类(下)

发表于 2021-11-17

「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」

前言

1
复制代码 大家好,我是程序猿小白 GW_gw,很高兴能和大家一起学习进步。

以下内容部分来自于网络,如有侵权,请联系我删除,本文仅用于学习交流,不用作任何商业用途。

摘要

1
lua复制代码 本文主要介绍SimpleDateFormat类一些构造方法和format,parse方法以及具体使用。

传送门:浅谈SimpleDateFormat类(上)

  1. 构造方法

1.1 SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)

SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) 用给定的模式和日期符号构造 SimpleDateFormat。

第二个参数DateFormatSymbols 是一个公共类,用于封装可本地化的日期-时间格式化数据,如月名、星期几的名称和时区数据。

1
2
ini复制代码 DateFormatSymbols value = new DateFormatSymbols();
 DateFormatSymbols value1 = new DateFormatSymbols(Locale.ENGLISH);//参数是local对象

可选参数:

image-20211116223828069

实例展示:

1
2
3
4
5
6
7
8
9
10
11
ini复制代码 DateFormatSymbols value = new DateFormatSymbols();
 DateFormatSymbols value1 = new DateFormatSymbols(Locale.ENGLISH);
 ​
 SimpleDateFormat simpleDateFormat4 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS'Z'", value);
 SimpleDateFormat simpleDateFormat5 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS'Z'", value1);
 ​
 Date date4 = new Date();
 String format4 = simpleDateFormat4.format(date4);
 String format5 = simpleDateFormat5.format(date4);
 System.out.println(format4);
 System.out.println(format5);

image-20211116225235787

1.2 SimpleDateFormat(String pattern, Locale locale)

SimpleDateFormat(String pattern, Locale locale) 用给定的模式和给定语言环境的默认日期格式符号构造 SimpleDateFormat。

这个构造函数我理解为上面哪个函数的简化版,只需要传递一个Local对象就可以。

实例展示:

1
2
3
4
5
6
7
ini复制代码 /*
 4.给定模式和语言环境
  */
 SimpleDateFormat simpleDateFormat3 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS'Z'",Locale.ENGLISH);
 Date date3 = new Date();
 String format3 = simpleDateFormat3.format(date3);
 System.out.println(format3);

image-20211116225428325

参数解析见上面。

  1. SimpleDateFormat类的方法

2.1 format(Date date)

String format(Date date)将传入的Date对象根据自定义的格式转化为字符串。

源码:

1
2
3
4
5
typescript复制代码 public final String format(Date date)
 {
    return format(date, new StringBuffer(),
                  DontCareFieldPosition.INSTANCE).toString();
 }

这里用的format其实是从DateFormat父类继承而来的,因此我们在使用时也常使用DateFormat创建SimpleDateFormat对象。

1
2
3
ini复制代码 DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
 String format = df.format(new Date());
 System.out.println(format);

2.2 parse(String str)

Date parse(String str)将传入的字符串根据定义格式转化为Date对象。

源码:

1
2
3
4
5
6
7
8
9
java复制代码 public Date parse(String source) throws ParseException
 {
    ParsePosition pos = new ParsePosition(0);
    Date result = parse(source, pos);
    if (pos.index == 0)
        throw new ParseException("Unparseable date: "" + source + """ ,
            pos.errorIndex);
    return result;
 }

这里的parse方法也是从父类继承而来。

1
2
3
4
5
6
7
ini复制代码 String str = "2019年12月25日";
 try {
    Date parse = df.parse(str);
    System.out.println(parse);
 } catch (ParseException e) {
    e.printStackTrace();
 }
  1. SimpleDateFormat类的应用实例

计算你现在出生了多少天。

思路:

  1. 计算出生日期到标准时间的时间长度
  2. 计算现在到标准时间的时间长度
  3. 两者相减

具体实现:

1
2
3
4
5
6
7
8
9
10
11
ini复制代码 public class SimpleDateFormatTest {
    public static void main(String[] args) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String birthday = "2000年11月1日";
        Date timeBirthday = df.parse(birthday);
        Date nowTime = new Date();
        //相减后得到毫秒数,换算成天数
        long day = (nowTime.getTime()-timeBirthday.getTime())/1000/60/60/24;
        System.out.println(day);
    }
 }

结语

致此关于SimpleDateFormat类的学习就到这里了,希望能对读者有所帮助。如有不正之处,欢迎掘友们留言指正。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何用20分钟就能获得同款企业级全链路灰度能力? 背景 MS

发表于 2021-11-17

简介: MSE 微服务引擎将推出服务治理专业版,提供开箱即用且完整专业的微服务治理解决方案,帮助企业更好地实现微服务治理能力。如果您的系统也可以像本文描述的那样,快速具备完整的全链路灰度能力,并基于该能力进行进一步的微服务治理实践,不仅可以节省客观的人力与成本,还可以让您的企业在微服务领域的探索更加有底气。

作者:十眠

今年双 11,云原生中间件完成了开源、自研、商业化三位一体,全面升级到中间件云产品。MSE 微服务治理通过 Dubbo3.0 支撑了阿里集团核心业务双 11 的流量洪峰,截止目前集团内 50% 的用户已经习惯使用 MSE 微服务治理 HSF 和 Dubbo3.0 应用,今天我们来细聊一下 MSE 服务治理专业版中的全链路灰度能力,以及它在生产大规模实践的一些场景。

背景

微服务架构下,有一些需求开发,涉及到微服务调用链路上的多个微服务同时发生了改动,需要通过灰度发布方式来更好地控制新版本服务上线的风险和爆炸半径。通常每个微服务都会有灰度环境或分组来接受灰度流量,我们希望通过进入上游灰度环境的流量,也能进入下游灰度的环境中,确保 1 个请求始终在灰度环境中传递,即使这个调用链路上有一些微服务没有灰度环境,这些应用请求下游的时候依然能够回到灰度环境中。通过 MSE 提供的全链路灰度能力,可以在不需要修改任何您的业务代码的情况下,能够轻松实现上述能力。

MSE 微服务治理全链路灰度特点

全链路灰度作为 MSE 服务治理专业版中的拳头功能,具备以下六大特点

  • 可通过定制规则引入精细化流量

除了简单地按照比例进行流量引入外,我们还支持 Spring Cloud 与 Dubbo 流量按规则引入,Spring Cloud 流量可根据请求的 cookie、header、param 参数或随机百分比引入流量,Dubbo 流量可按照服务、方法、参数来引入。

  • 全链路隔离流量泳道
  1. 通过设置流量规则对所需流量进行“染色”,“染色”流量会路由到灰度机器。
  2. 灰度流量携带灰度标往下游传递,形成灰度专属环境流量泳道,无灰度环境应用会默认选择未打标的基线环境。
  • 端到端的稳定基线环境

未打标的应用属于基线稳定版本的应用,即稳定的线上环境。当我们将发布对应的灰度版本代码,然后可以配置规则定向引入特定的线上流量,控制灰度代码的风险。

  • 流量一键动态切流

流量规则定制后,可根据需求进行一键停启,增删改查,实时生效。灰度引流更便捷。

  • 低成本接入,基于 Java Agent 技术实现无需修改一行业务代码

MSE 微服务治理能力基于 Java Agent 字节码增强的技术实现,无缝支持市面上近 5 年的所有 Spring Cloud 和 Dubbo 的版本,用户不用改一行代码就可以使用,不需要改变业务的现有架构,随时可上可下,没有绑定。只需开启 MSE 微服务治理专业版,在线配置,实时生效。

  • 具备无损上下线能力,使得发布更加丝滑

应用开启 MSE 微服务治理后就具备无损上下线能力,大流量下的发布、回滚、扩容、缩容等场景,均能保证流量无损。

大规模生产实践的场景

本文主要介绍 MSE 微服务治理在支持大客户过程中总结抽象出来的常用的几个全链路灰度方案生产落地实践的场景。

场景一:对经过机器的流量进行自动染色,实现全链路灰度

  • 进入带 tag 的节点后续调用优先选择带有相同 tag 的节点,即对经过 tag 节点的流量进行“染色”。
  • 有 tag 的调用链路上找不到相同 tag 的节点,则 fallback 到无 tag 的节点。
  • 有 tag 的调用链路经过无 tag 的节点,如果链路后续调用有 tag 的节点,则恢复 tag 调用的模式。

场景二:通过给流量带上特定的 header 实现全链路灰度

客户端通过在请求中增加制定环境的标识,接入层根据表示进行转发至表示对应环境的网关,对应环境的网关通过隔离插件调用标识对应的项目隔离环境,请求在业务项目隔离环境中闭环。

场景三:通过自定义路由规则来进行全链路灰度

通过在灰度请求中增加指定的 header,且整条调用链路会将该 header 透传下去,只需在对应的应用配置该 header 相关的路由规则,带指定 header 的灰度请求进入灰度机器,即可按需实现全链路流量灰度。

全链路灰度的实践

我们如何快速获得上述同款全链路灰度的能力呢?下面我会带大家从 0 到 1 快速搭建我们的全链路灰度能力。

我们假设应用的架构由 Ingress-nginx 以及后端的微服务架构(Spring Cloud)来组成,后端调用链路有 3 跳,购物车(a),交易中心(b),库存中心(c),他们通过 Nacos 注册中心做服务发现,客户端通过客户端或者是 H5 页面来访问后端服务。

前提条件

安装 Ingress-nginx 组件

访问容器服务控制台,打开应用目录,搜索 ack-ingress-nginx ,选择命名空间 kube-system,点击创建,安装完成后,在 kube-system 命名空间中会看到一个 deployment ack-ingress-nginx-default-controller ,表明安装成功。

1
2
3
sql复制代码$ kubectl get deployment -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
ack-ingress-nginx-default-controller 2/2 2 2 18h

开启 MSE 微服务治理专业版

  • 点击开通 MSE 微服务治理专业版 以使用全链路灰度能力。
  • 访问容器服务控制台,打开应用目录,搜索 ack-mse-pilot ,点击创建。
  • 在 MSE 服务治理控制台,打开 K8s 集群列表,选择对应集群,对应命名空间,并打开微服务治理。

部署 Demo 应用程序

将下面的文件保存到 ingress-gray.yaml 中,并执行 kubectl apply -f ingress-gray.yaml 以部署应用,这里我们将要部署 A, B, C 三个应用,每个应用分别部署一个基线版本和一个灰度版本。

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
yaml复制代码# A 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-a
name: spring-cloud-a
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 20001
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30

# A 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-a-new
name: spring-cloud-a-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a-new
strategy:
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a-new
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: profiler.micro.service.tag.trace.enable
value: "true"
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-a-new
ports:
- containerPort: 20001
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30

# B 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-b
name: spring-cloud-b
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-b
strategy:
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-b
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30

# B 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-b-new
name: spring-cloud-b-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-b-new
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b-new
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-b-new
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30

# C 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-c
name: spring-cloud-c
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-c
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30

# C 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-c-new
name: spring-cloud-c-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-c-new
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c-new
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
imagePullPolicy: IfNotPresent
name: spring-cloud-c-new
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30

# Nacos Server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nacos-server
name: nacos-server
spec:
replicas: 1
selector:
matchLabels:
app: nacos-server
template:
metadata:
labels:
app: nacos-server
spec:
containers:
- env:
- name: MODE
value: standalone
image: nacos/nacos-server:latest
imagePullPolicy: Always
name: nacos-server
resources:
requests:
cpu: 250m
memory: 512Mi
dnsPolicy: ClusterFirst
restartPolicy: Always

# Nacos Server Service 配置
---
apiVersion: v1
kind: Service
metadata:
name: nacos-server
spec:
ports:
- port: 8848
protocol: TCP
targetPort: 8848
selector:
app: nacos-server
type: ClusterIP

动手实践

场景一:对经过机器的流量进行自动染色,实现全链路灰度

有时候,我们可以通过不同的域名来区分线上基线环境和灰度环境,灰度环境有单独的域名可以配置,假设我们通过访问 www.gray.com 来请求灰度环境,访问 www.base.com 走基线环境。

调用链路 Ingress-nginx -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。

注意:入口应用 A 的 gray 和 A 的 base 环境,需要在 MSE 服务治理控制台打开 A 应用的按照流量比例透传开关,表示开启向后透传当前环境的标签的功能。这样, 当 Ingress-nginx 路由 A 的 gray 之后,即使请求中没有携带任何 header,因为开启了此开关,所以往后调用的时候会自动添加 x-mse-tag:gray 这个 header,其中的 header的值 gray 来自于 A 应用配置的标签信息。如果原来的请求中带有 x-mse-tag:gray则会以原来请求中的标签优先。

针对入口应用 A ,配置两个 k8s service, spring-cloud-a-base 对应 A 的 base 版本,spring-cloud-a-gray 对应 A 的 gray 版本。

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
yaml复制代码apiVersion: v1
kind: Service
metadata:
name: spring-cloud-a-base
spec:
ports:
- name: http
port: 20001
protocol: TCP
targetPort: 20001
selector:
app: spring-cloud-a

---
apiVersion: v1
kind: Service
metadata:
name: spring-cloud-a-gray
spec:
ports:
- name: http
port: 20001
protocol: TCP
targetPort: 20001
selector:
app: spring-cloud-a-new

配置入口的 Ingress 规则,访问 www.base.com 路由到 A 应用的 base 版本,访问 www.gray.com 路由到 A 应用的 gray 版本。

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
yaml复制代码apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-base
spec:
rules:
- host: www.base.com
http:
paths:
- backend:
serviceName: spring-cloud-a-base
servicePort: 20001
path: /

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-gray
spec:
rules:
- host: www.gray.com
http:
paths:
- backend:
serviceName: spring-cloud-a-gray
servicePort: 20001
path: /

结果验证

此时,访问 www.base.com 路由到 基线环境

1
2
less复制代码curl -H"Host:www.base.com" http://106.14.155.223/a
A[172.18.144.155] -> B[172.18.144.120] -> C[172.18.144.79]

此时,访问 www.gray.com 路由到灰度环境

1
2
less复制代码curl -H"Host:www.gray.com" http://106.14.155.223/a
Agray[172.18.144.160] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]

进一步的,如果入口应用 A 没有灰度环境,访问到 A 的 base 环境,又需要在 A -> B 的时候进入灰度环境,则可以通过增加一个特殊的 header x-mse-tag 来实现,header 的值是想要去的环境的标签,例如 gray。

1
2
less复制代码curl -H"Host:www.base.com"  -H"x-mse-tag:gray" http://106.14.155.223/a
A[172.18.144.155] -> Bgray[172.18.144.139] -> Cgray[172.18.144.8]

可以看到第一跳,进入了 A 的 base 环境,但是 A->B 的时候又重新回到了灰度环境。

这种使用方式的好处是,配置简单,只需要在 Ingress 处配置好规则,某个应用需要灰度发布的时候,只需要在灰度环境中部署好应用,灰度流量自然会进入到灰度机器中,如果验证没问题,则将灰度的镜像发布到基线环境中;如果一次变更有多个应用需要灰度发布,则把他们都加入到灰度环境中即可。

最佳实践

  1. 给所有灰度环境的应用打上 gray 标,基线环境的应用默认不打标。
  2. 线上常态化引流 2% 的流量进去灰度环境中

场景二:通过给流量带上特定的 header 实现全链路灰度

有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的 header 来路由到灰度环境。例如下图中,通过添加 x-mse-tag:gray 这个 header,来访问灰度环境。

这个时候 demo 的Ingress 规则如下,注意这里增加了 nginx.ingress.kubernetes.io/canary 相关的多条规则

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
yaml复制代码apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-base
spec:
rules:
- host: www.demo.com
http:
paths:
- backend:
serviceName: spring-cloud-a-base
servicePort: 20001
path: /
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-gray
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "x-mse-tag"
nginx.ingress.kubernetes.io/canary-by-header-value: "gray"
nginx.ingress.kubernetes.io/canary-weight: "0"
spec:
rules:
- host: www.base.com
http:
paths:
- backend:
serviceName: spring-cloud-a-gray
servicePort: 20001
path: /

结果验证

此时,访问 www.demo.com 路由到基线环境

1
2
less复制代码curl -H"Host:www.demo.com" http://106.14.155.223/a
A[172.18.144.155] -> B[172.18.144.56] -> C[172.18.144.156]

如何访问灰度环境呢?只需要在请求中增加一个header x-mse-tag:gray 即可。

1
2
rust复制代码curl -H"Host:www.demo.com"  -H"x-mse-tag:gray" http://106.14.155.223/a
Agray[172.18.144.82] -> Bgray[172.18.144.57] -> Cgray[172.18.144.8]

可以看到 Ingress 根据这个header直接路由到了 A 的 gray 环境中。

更进一步

还可以借助 Ingress 实现更复杂的路由,比如客户端已经带上了某个header,想要利用现成的 header来实现路由,而不用新增一个 header,例如下图所示,假设我们想要 x-user-id 为 100 的请求进入灰度环境。

只需要增加下面这 4 条规则:

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
yaml复制代码apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-base
spec:
rules:
- host: www.demo.com
http:
paths:
- backend:
serviceName: spring-cloud-a-base
servicePort: 20001
path: /
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-base-gray
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "x-user-id"
nginx.ingress.kubernetes.io/canary-by-header-value: "100"
nginx.ingress.kubernetes.io/canary-weight: "0"
spec:
rules:
- host: www.demo.com
http:
paths:
- backend:
serviceName: spring-cloud-a-gray
servicePort: 20001
path: /

访问的时候带上特殊的 header ,满足条件进入灰度环境

1
2
rust复制代码curl -H"Host:www.demo.com"  -H"x-user-id:100" http://106.14.155.223/a
Agray[172.18.144.93] -> Bgray[172.18.144.24] -> Cgray[172.18.144.25]

不满足条件的请求,进入基线环境:

1
2
less复制代码curl -H"Host:www.demo.com"  -H"x-user-id:101" http://106.14.155.223/a
A[172.18.144.91] -> B[172.18.144.22] -> C[172.18.144.95]

相比场景一这样的好处是,客户端的域名不变,只需要通过请求来区分。

场景三:通过自定义路由规则来进行全链路灰度

有时候我们不想要自动透传且自动路由,而是希望微服务调用链上下游上的每个应用能自定义灰度规则,例如 B 应用希望控制只有满足自定义规则的请求才会路由到 B 应用这里,而 C 应用有可能希望定义和 B 不同的灰度规则,这时应该如何配置呢,场景参见如下图:

注意,最好把场景一和二中配置的参数清除掉。

第一步,需要在入口应用 A 处(最好是所有的入口应用都增加该环境变量,包括 gray 和 base) 增加一个环境变量:alicloud.service.header=x-user-id,x-user-id 是需要透传的 header,它的作用是识别该 header 并做自动透传。

注意这里不要使用 x-mse-tag, 它是系统默认的一个 header,有特殊的逻辑。

第二步,在中间的 B 应用处,在 MSE 控制台配置标签路由规则

第三步,在 Ingress 处配置路由规则,这一步参考场景二,并采用如下配置:

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
yaml复制代码apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: spring-cloud-a-base
spec:
rules:
- host: www.base.com
http:
paths:
- backend:
serviceName: spring-cloud-a-base
servicePort: 20001
path: /
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: 'true'
nginx.ingress.kubernetes.io/canary-by-header: x-user-id
nginx.ingress.kubernetes.io/canary-by-header-value: '100'
nginx.ingress.kubernetes.io/canary-weight: '0'
name: spring-cloud-a-gray
spec:
rules:
- host: www.base.com
http:
paths:
- backend:
serviceName: spring-cloud-a-gray
servicePort: 20001
path: /

结果验证

测试验证,访问灰度环境,带上满足条件的 header,路由到 B 的灰度环境中。

1
2
less复制代码curl 120.77.215.62/a -H "Host: www.base.com" -H "x-user-id: 100" 
Agray[192.168.86.42] -> Bgray[192.168.74.4] -> C[192.168.86.33]

访问灰度环境,带上不满足条件的 header,路由到 B 的base环境中。

1
2
less复制代码curl 120.77.215.62/a -H "Host: www.base.com" -H "x-user-id: 101" 
A[192.168.86.35] -> B[192.168.73.249] -> C[192.168.86.33]

去掉 Ingress Canary 配置,访问 base A 服务(基线环境入口应用需要加上 alicloud.service.header 环境变量),带上满足条件的 header,路由到 B 的灰度环境中。

1
2
less复制代码curl 120.77.215.62/a -H "Host: www.base.com" -H "x-user-id: 100" 
A[192.168.86.35] -> Bgray[192.168.74.4] -> C[192.168.86.33]

访问 base 环境,带上不满足条件的 header,路由到 B 的 base 环境中。

1
2
less复制代码curl 120.77.215.62/a -H "Host: www.base.com" -H "x-user-id: 101" 
A[192.168.86.35] -> B[192.168.73.249] -> C[192.168.86.33]

总结

20 分钟快速实践完具有很大技术难度的全链路灰度能力,全链路灰度其实并不是那么难!

基于 MSE 服务治理的全链路灰度能力,我们可以快速落地企业级的全链路灰度能力,以上三种场景是我们在生产实践中大规模落地的标准场景,当然我们可以基于 MSE 服务治理的能力根据自己的业务个性化定制与适配;即使在多种流量来源的背景下,也能做到按照业务定制精准引流。

同时 MSE 服务治理专业版的可观测性能力使得灰度有效性得到可衡量,灰没灰到,灰得咋样,做到“心里有数”。

  • 灰度流量秒级监控

规范发布流程

日常发布中,我们常常会有如下一些错误的想法:

  • 这次改动的内容比较小,而且上线要求比较急,就不需要测试直接发布上线好了。
  • 发布不需要走灰度流程,快速发布上线即可。
  • 灰度发布没有什么用,就是一个流程而已,发布完就直接发布线上,不用等待观察。
  • 虽然灰度发布很重要,但是灰度环境很难搭建,耗时耗力优先级并不高。

这些想法都可能让我们进行一次错误的发布,不少故障是因为发布直接或间接引起。因此提升发布的质量,减少错误的发生,是有效减少线上故障的一个关键环节。做到安全的发布,我们需要规范发布的流程。

尾

随着微服务的流行,越来越多公司使用了微服务框架,微服务以其高内聚、低耦合等特性,提供了更好的容错性,也更适应业务的快速迭代,为开发人员带来了很多的便利性。但是随着业务的发展,微服务拆分越来越复杂,微服务的治理也成了一个比较令人头疼的问题。

单单拿全链路灰度来看,为了保证应用新版本上线前的功能正确性的验证同时需要兼顾应用发布的效率,如果我们应用的规模很小,我们可以直接通过维护多套环境来保证发布的正确性。但是当我们的业务发展到庞大且复杂程度时,假设我们的系统由 100 个微服务构成,即使测试/灰度环境每个服务占用 1 到 2 个 pod,那么多套环境下来我们需要面临巨大的成本与运维环境带来的效率的挑战。

有没有更加简单且高效的方法来解决微服务治理的难题?

MSE 微服务引擎将推出服务治理专业版,提供开箱即用且完整专业的微服务治理解决方案,帮助企业更好地实现微服务治理能力。如果您的系统也可以像本文描述的那样,快速具备完整的全链路灰度能力,并基于该能力进行进一步的微服务治理实践,不仅可以节省客观的人力与成本,还可以让您的企业在微服务领域的探索更加有底气。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

LeetCode 518 零钱兑换 II 【c++/jav

发表于 2021-11-17

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」

1、题目

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

1
2
3
4
5
6
7
ini复制代码输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

1
2
3
ini复制代码输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

1
2
ini复制代码输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

2、思路

(动态规划,完全背包)

二维分析

状态表示: f[i][j] 表示从前i种硬币中选,且总金额恰好为j的所有选法集合的方案数。

那么f[n][amount]就表示表示从前n种硬币中选,且总金额恰好为amount的所有选法集合的方案数,即为答案。

集合划分:

按照第i种硬币可以选 0个,1个,2个,3个,,,,k个划分集合 f[i][j]。其中k*coin[i] <= j,也就是说在背包能装下的情况下,枚举第i种硬币可以选择几个。

  • 第i种硬币选 0个,f[i][j] = f[i-1][j]
  • 第i种硬币选 1个,f[i][j] = f[i-1][j - coins[i]]
  • 第i种硬币选 k个,f[i][j] = f[i-1][j - k*coins[i]]

状态计算:

f[i][j] = f[i-1][j]+f[i-1][j-coins[i]]+f[i-1][j-2*coins[i]],,,,,,+f[i-1][j-k*coins[i]] 。
初始化条件:

  • f[0][0] = 1,使用0种硬币币,凑0元钱,也是一种方案。

时间复杂度分析: O(amount2∗n)O(amount^2*n)O(amount2∗n),其中 amountamountamount是总金额,nnn是数组 coinscoinscoins的长度。

3、二维c++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c复制代码class Solution {
public:
int change(int amount, vector<int>& coins) {
int n = coins.size();
vector<vector<int>>f(n + 1, vector<int>(amount + 1 , 0));
f[0][0] = 1; // 使用0种货币,凑0元钱,也是一种方案
for(int i = 1; i <= n; i++)
{
int v =coins[i - 1];
for(int j = 0; j <= amount; j++)
for(int k = 0; k*v <= j; k++)
f[i][j] += f[i-1][j-k*v]; //状态计算方程
}
return f[n][amount];
}
};

4、二维java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
int[][] f = new int[n + 1][amount + 1];
f[0][0] = 1; // 使用0种货币,凑0元钱,也是一种方案
for (int i = 1; i <= n; i++) {
int v = coins[i - 1];
for (int j = 0; j <= amount; j++)
for (int k = 0; k * v <= j; k++)
f[i][j] += f[i - 1][j - k * v]; //状态计算方程
}
return f[n][amount];
}
}

5、一维优化

二维完全背包求解方案时间复杂度较高,考虑一维优化。

v代表第i种硬币的面值

f[i][j] = f[i-1][j] + f[i-1][j-v]+f[i-1][j-2v]+,,,,,+f[i-1][j-kv])

f[i][j-v] = f[i-1,[j-v]+f[i-1][j-2v]+,,,,,+f[i-1][j-kv])

因此:

f[i][j] = f[i-1][j]+f[i][j-v])

图示:


去掉物品种类维度,状态计算方程为: f[j] = f[j] + f[j-v]

时间复杂度分析: O(amount∗n)O(amount*n)O(amount∗n) ,其中 amountamountamount是总金额,nnn是数组 coinscoinscoins的长度。

6、一维c++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c复制代码class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int>f(amount + 1);
f[0] = 1; //f[0][0] = 1;
for(int i = 1; i <= coins.size(); i++)
{
int v =coins[i - 1];
for(int j = v; j <= amount; j++)
f[j] += f[j - v];
}
return f[amount];

}
};

7、一维java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码class Solution {
public int change(int amount, int[] coins) {
int[] f = new int[amount + 1];
f[0] = 1; //f[0][0] = 1;
for(int i = 1; i <= coins.length; i++)
{
int v =coins[i - 1];
for(int j = v; j <= amount; j++)
f[j] += f[j - v];
}
return f[amount];
}
}

原题链接: 518. 零钱兑换 II

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java 注解 Java注解 注解

发表于 2021-11-17

Java注解

  • 本文参照学习:

感谢大佬~

  • 个人源码地址:gitee.com/WSM540

注解

前言:

框架等于:注解 + 反射 + 设计模式~

JDK 5.0 开始, Java 增加了对元数据的支持, 也就是 Annotation(注解) 安闹太湿~

Annotation 其实就是代码里的 特殊标记, 这些标记可以在编译, 类加 载, 运行时被读取, 并执行相应的处理

  • 通过:注解 + 反射

反射扫描对应类的注解,以及注解上面的参数,给类的添加一些通用的操作~

  • 开发者,可以:

不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息

代码分析工具、开发工具和部署工具:可以通过这些补充信息进行验证或者进行部署。

Annotation 可以像修饰符一样被使用:

  • 用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明

为什么要引用注解:

使用【注解】之前:

  • xml 是被广泛的应用于描述元数据, 它以松耦合的方式完成了框架中几乎所有的配置

但是随着项目越来越庞大: XML 的内容也越来越复杂,一些应用开发人员和架构师发现维护成本变高。

  • 人门跟希望出现一些和代码 紧耦合的方式~

可以更加清晰的了解类的声明~

  • 于是就有人提出来一种标记式高耦合的配置方式 注解:

方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解. 对象代码进行声明管理;

注解需要配合反射一起使用~

Java常见的 Annotation 实例:

@Override

  • 限定重写父类方法, 该注解只能用于方法 属于一种标记注解
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    java复制代码//1. 定义一个接口
    interface Info{
    void show();
    }
    //2. 创建一个类,实现接口 info 实现方法~
    class Student implements Info{
    //一般工具都会自动加一个注解,
    // 表示: 改方法是实现接口的重写方法(); 当然不加也没啥事情,只是一个程序提示~
    @Override
    public void show() { }
    }

@Deprecated

  • 用于表示所修饰的元素(类, 方法等)已过时,通常是因为 所修饰的结构危险或存在更好的选择

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    java复制代码Date date = new Date(2000,9,9); 
    //这段代码并不会报错:创建一个日期对象 2000-09-09 日期对象;
    // 但 new Date(); 方法上会有一个 删除线, 提示改方法以及淘汰~

    //ctrl+右击 查看源码发现,改方法被注解为已淘汰~
    @Deprecated
    public Date(int year, int month, int date) {
    this(year, month, date, 0, 0, 0);
    }
  • 这样声明并不是表示,方法不能使用而是不建议 已经有更好的方法了

JDK是向下兼容的,不然每次更新一些老项目如果不符合就会出错,就不好了~

  • 场景:

如果你的项目中有一个,你写的方法,突然一个大佬也写了一个更好的,你又不想删除曾经的方法

都是手敲的多少有点舍不得 可以使用该 注解 ,标记一下方法~ 已淘汰😥

@SuppressWarnings

  • 抑制编译器警告
  • 当我门定义对象,没有被使用的时候, 编辑器会出现 警告 可以使用该注解, 屏蔽警告⚠

使用注解前面要加 @符号 当作一个修饰符使用,声明在类 方法 属性上!

自定义注解:

目前除了, JDK自带的注解,很多的框架也提供了很多的注解:

  • Spring JPA Hibernate 很多框架都提供很多注解,实现基于注解开发~
  • 自定义注解,并不常见但可以了解…

自定义注解必须配上注解的信息处理流程才有意义 如果没有解析它的代码,它可能连注释都不如

实现:

  • 定义新的 Annotation 类型使用 @interface 关键字
  • 自定义注解自动继承了:Java.lang.annotation.Annotation接口
  • Annotation 的成员变量

以无参数方法的形式来声明

其:方法名和返回值 对应 该成员的 ==名字==和==类型==

  • 类型只能是:

八种基本数据,String,Class,enum,Annotation

MyAnnotation

Idea 工具创建 Annotation 注解类:

image-20211114164504530
MyAnnotation

1
2
3
4
5
6
7
8
9
10
java复制代码/** 自定义注解1.0 */
@Retention(RetentionPolicy.RUNTIME) //一种元注解: 标识注解的执行生命周期 (什么时候被执行!)
public @interface MyAnnotation {

//这是自定义注解中,属性的声明方式: 数据类型 参数名;
// String value();

//指定成员变量的初始值可使用 default 关键字
String value() default "wsm"; /** 如果只有一个参数成员,建议使用参数名为value */
}

TestAnnotation.Java

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
java复制代码package com.wsm.anotation;
import java.lang.annotation.Annotation;
/** 步骤2 */
public class TestAnnotation {
public static void main(String[] args) {
//使用反射,来完成注解是实习,这里只是简单的输出.
// 很多时候反射,扫描所有的类,并判断注解的参数 完善类添新的功能~
Class clazz = Wsm.class;
Annotation[] annotations = clazz.getAnnotations(); //获取当前类的注解集合~
for(int i = 0;i < annotations.length;i++){ //输出注解集合的值!
System.out.println(annotations[i]);
}
}
}

/** 步骤1 */
//@MyAnnotation //啥也不写默认值~
//@MyAnnotation(value = "abc") //手动填写
@MyAnnotation("efg") //如果注解的只有一个属性且属性名是 value: 可以省略不写
class Wsm{
//一个空的类,使用 自定义注解~
@MyAnnotation
public void show(){
System.out.println("show方法()");
}
}
  • ok,这就是一个最基本是 自定义注解的使用~
  • 别忘了自定义注解要加上 @Retention(RetentionPolicy.RUNTIME) 不然类加载时候,扫描不到;

@Retention 是什么?

@Retention是一种元注解

元注解Meta-Annotation:

什么是元注解:

Java5.0定义了4个标准的 Meta-Annotation类型 继承了:Java.lang.annotation.Annotation接口

元注解的作用就是负责注解其他注解: 它们被用来提供对其它 注解类型作说明; 修饰注解的注解

JDK 中的元注解 4:

@Retention

  • 用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期

@Rentention 包含一个 RetentionPolicy 类型的成员变量enum类型 @Rentention 包含一个 RetentionPolicy 类型的成员变量

  • RetentionPolicy枚举
1
2
3
4
5
6
7
8
9
10
11
markdown复制代码**RetentionPolicy.SOURCE**
- **注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视**

**RetentionPolicy.CLASS**
- **当运行Java 程序时, JVM不会保留注解,这是`默认值`**
- **实例:** `@Override、@Deprecated、@SuppressWarnning`

**RetentionPolicy.RUNTIME**
- **当运行 Java 程序时, JVM 会保留注释,`程序可以通过反射获取该注释`**
- **实例:** `@Controller、@Autowired、@RequestMapping`
- `自定义注解,想要被扫描到,需要添加改注解 @Retention(RetentionPolicy.RUNTIME)`

@Target

  • 用于修饰 Annotation 定义, 指定被修饰的 Annotation 能用于 修饰哪些程序元素 默认值:为任何元素

即:注解可以修饰的元素:类 属性 泛型 enum枚举 方法()...

  • JDK5.0
1
2
3
4
5
6
7
8
markdown复制代码顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
  • JDK8.0顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去
1
2
markdown复制代码ElementType.TYPE_PARAMETER	可以给一个泛型进行注解
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中 `声明了改类型就不用在声明其它方式了.`

@Documented

  • 顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去
  • 定义为Documented的注解必须设置Retention值为RUNTIME

@Inherited

  • @Inherited 元注解是一个标记注解被标注的类型是被继承的
  • A类 使用了带有 @Inherited的注解,B类 继承 A类,B也会拥有A的注解~

JDK8.0 新增元注解

@Repeatable

  • 可重复注解 有时候,需要注解在类上多次声明使用可以使用改注解.

JDK8.0 可重复注解使用:

定义可重复的注解容器注解

MyAnnotations

1
2
3
4
5
6
7
java复制代码import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value(); //注解属性,是可重复注解的数组~
}

MyAnnotation

1
2
3
4
5
6
7
8
9
java复制代码import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class) //1.8新增可重复注解,知道注解容器~
public @interface MyAnnotation {
String value(); //参数~
}

TestAnnotation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码import java.lang.annotation.Annotation;

/** 步骤2 */
public class TestAnnotation {
public static void main(String[] args) {
//使用反射,来完成注解是实习,这里只是简单的输出.
// 很多时候反射,扫描所有的类,并判断注解的参数 完善类添新的功能~
Class clazz = Wsm.class;
Annotation[] annotations = clazz.getAnnotations(); //获取当前类的注解集合~
for(int i = 0;i < annotations.length;i++){ //输出注解集合的值!
System.out.println(annotations[i]);
}
}
}

/** 步骤1 */
@MyAnnotation("abc") //可重复注解,可以一次添加多个属性!
@MyAnnotation("efg")
class Wsm{
//一个空的类,使用 自定义注解~
public void show(){
System.out.println("show方法()");
}
}

运行结果:

1
bash复制代码@com.wsm.annotation2.MyAnnotations(value[@com.wsm.annotation2.MyAnnotation(value=abc),@com.wsm.annotation2.MyAnnotation(value=efg)])

输出了多个注解的 value

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Mac MongoDB 安装 启动服务和链接数据库

发表于 2021-11-17

先搞下来

1
2
3
4
js复制代码// 进入到local文件下 一般数据库都放在这下面 msyql也是
cd /usr/local
// 下载 后面是版本 可以去官网看一看更换最新的
sudo curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-4.0.9.tgz

这就是下载完成了
image.png

解压

下载后它是压缩文件,我们解压 [权限不够 sudo来凑]

1
js复制代码sudo tar -zxvf mongodb-osx-ssl-x86_64-4.0.9.tgz

然后他就解压成这么一堆

image.png

将解压的文件改名

1
2
js复制代码sudo mv 解压后的名 重命名
sudo mv mongodb-osx-x86_64-4.0.9/ mongodb

ls 瞅一眼 在这嘎达了

image.png

把压缩文件送到回收站

其实 也可以打开 图形界面 哈哈哈

1
2
js复制代码在 /usr 下 
open local

就打开了

image.png

配置环境变量

在终端中输出 "open -e .bash_profile",打开bash_profile文件。

注意是最外层 ~ 哦
image.png

1
js复制代码open -e .bash_profile

然后将安装目录的bin目录地址 "export PATH="$PATH:/usr/local/mongodb/bin" 添加到环境变量中。然后Esc :wq! 退出

在终端中输入"source .bash_profile"使配置立即生效。

查看mongodb版本

1
js复制代码mongod --version

image.png

启动服务和链接数据库

新建文件

进入到 mongodb文件夹
先创建两个文件夹

1
2
js复制代码sudo mkdir data
sudo mkdir log

在log文件夹下新建 mongod.log

1
js复制代码sudo touch mongo.log

在data文件夹下新建 db文件夹

1
2
js复制代码cd data
mkdir db

启动

可以试一下 mongod 启动。如果像如下截图就是好用了(如果报错 那请看下面)

image.png

再开启一个窗口执行 mongo, show dbs 查看所有数据库

image.png

1
2
3
js复制代码use data // 创建data数据库
db // test 当前数据库
db.dropDatabase() // 删除当前数据库

mongoDB关闭

1.关闭第一个终端:control + C

2.第二个终端输入:

1
2
3
js复制代码use admin

db.shutdownServer({force : true})

扎心的报错

Mac启动MongoDB报错:exception in initAndListen: NonExistentPath: Data directory /data/db not found., terminating

initAndListen中的异常:NonExistentPath:找不到数据目录/ data / db。
最新版的Mac系统Catalina发生了令人惊讶的更改:它不允许更改根目录(这也在论坛主题中进行了讨论):

1
2
js复制代码brew tap mongodb
brew brew install mongodb-community

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud07——OpenFeign服务接口和调

发表于 2021-11-17

一、OpenFeign概述

1、OpenFeign简介

github地址:github.com/spring-clou…

官方文档:Spring Cloud

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。

Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

2、作用

Feign旨在使编写Java Http客户端变得更容易。

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,**我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),**即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

3、Feign集成了Ribbon

利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

4、Feign和OpenFeign两者区别

  • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
  • OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

二、OpenFeign的使用步骤

1、创建模块cloud-consumer-feign-order80

创建cloud-consumer-feign-order80模块,feign使用在消费端。

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
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-consumer-feign-order80</artifactId>

<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atuguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

yaml文件。

1
2
3
4
5
6
7
8
yaml复制代码server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

主启动类,注意换注解了@EnableFeignClients:

1
2
3
4
5
6
7
java复制代码@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}

2、业务类的创建

业务逻辑接口+@FeignClient配置调用provider服务

1
2
3
4
5
6
java复制代码@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}

controller层

1
2
3
4
5
6
7
8
9
10
11
java复制代码@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;

@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}

启动测试:localhost/consumer/payment/get/1成功。

三、OpenFeign的超时控制

1、超时演示出错的情况

服务提供方8001和8002故意写暂停程序

8001的controller层写超时的程序

1
2
3
4
5
6
7
8
9
java复制代码@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}

服务消费方80添加超时方法PaymentFeignService

PaymentFeignService接口中加入超时逻辑。

1
2
3
4
5
6
7
8
9
java复制代码@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
}

服务消费方80添加超时方法OrderFeignController

1
2
3
4
5
java复制代码@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
//openfeign底层是ribbon,消费者默认等待1s
return paymentFeignService.paymentFeignTimeout();
}

启动,用这个测试:localhost:8001/payment/feign//timeout,这里是访问8001自己的路径,耗时共3s。

而测试这个路径localhost/consumer/payment/feign//timeout,报错:

image-20211117140354069
默认情况下Feign客户端只等待1s,如果服务端处理时超过1s,那么Feign客户端就直接报错了,现实当中可能会有这样的情况,那么怎么处理呢?可以设置Feign客户端的超时控制。

cloud-consumer-feign-order80的yaml配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

再次测试,没有报错,输出8001或8002。说明修改客户端超时成功。

四、OpenFeign的日志增强

1、日志打印

Feign提供了日志打印功能,我们可以通过配置来调整日恙级别,从而了解Feign 中 Http请求的细节。

说白了就是对Feign接口的调用情况进行监控和输出

2、日志级别

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

3、配置日志

创建config包及FeignConfig类。

1
2
3
4
5
6
7
java复制代码@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

yaml文件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
yaml复制代码server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug

启动测试,发现打出了详细的日志。

image-20211117141518573

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Redis入门】自定义RedisTemplate和工具类

发表于 2021-11-17

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

👉写在前边

  • 上篇我们了解了 Redis中的配置文件–redis.conf,接下来我们来进行SpringBoot中的整合,以及写一下工具类和自定义Template!

SpringBoot整合

SpringBoot 操作数据:spring-data jpa jdbc mongodb redis!

说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce

jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式

lettuce : 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

自定义RedisTemplate

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
java复制代码package com.example.redis.conf;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration

public class RedisConfig {
/**
* 编写自己的redisTemplate----固定模板
* @param redisConnectionFactory
* @return
* @throws UnknownHostException
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
//为了开发方便,一般直接使用<String,object>
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//json序列化配置
Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer.setObjectMapper(om);
//string序列化配置
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


/*配置具体的序列化方式*/
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(Jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

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
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
java复制代码package com.example.redis.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

//在真实开发中,经常使用
@Component
public final class RedisUtil {

@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,
TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================

/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================

/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
* 时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove (String key,long count, Object value){
try {
Long remove = redisTemplate.opsForList().remove(key, count,value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}

💠下篇预告

  • 下篇我们将正式进入redis中的重点–持久化技术!!!之后还会有发布与订阅,以及redis中的事务。

参考

  • 尚硅谷Redis6视频
  • 狂神说Redis视频

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Linux 运维必备的13款实用工具,赶紧收藏!

发表于 2021-11-17

本文介绍几款Linux运维比较实用的工具,希望对Linux管理员有所帮助。

1、查看进程占用带宽情况-Nethogs

Nethogs 是一个终端下的网络流量监控工具可以直观的显示每个进程占用的带宽。

下载:sourceforge.net/projects/ne…

1
2
3
4
5
csharp复制代码[root@localhost ~]#yum -y install libpcap-devel ncurses-devel
[root@localhost ~]# tar zxvf nethogs-0.8.0.tar.gz
[root@localhost ~]# cd nethogs
[root@localhost nethogs]# make && make install
[root@localhost nethogs]# nethogs eth0

)

2、硬盘读取性能测试-IOZone

IOZone是一款Linux文件系统性能测试工具 可以测试不同的操作系统中文件系统的读写性能。

下载:www.iozone.org/src/current…

1
2
3
4
css复制代码[root@localhost current]# tar xvf iozone3_420.tar
[root@localhost ~]# cd iozone3_420/src/current/
[root@localhost current]# make linux
[root@localhost current]# ./iozone -a -n 512m -g 16g -i 0 -i 1 -i 5 -f /mnt/iozone -Rb ./iozone.xls

-a使用全自动模式

-n为自动模式设置最小文件大小(Kbytes)。

-g设置自动模式可使用的最大文件大小Kbytes。

-i用来指定运行哪个测试。

-f指定测试文件的名字完成后自动删除

-R产生Excel到标准输出

-b指定输出到指定文件上

3、实时监控磁盘IO-IOTop

IOTop命令是专门显示硬盘IO的命令,界面风格类似top命令。

[root[@localhost](https://my.oschina.net/u/570656) ~]# yum -y install iotop

)

4、网络流量监控-IPtraf

IPtraf是一个运行在Linux下的简单的网络状况分析工具。

1
csharp复制代码[root@localhost ~]# yum -y install iptraf

)

5、网络流量监控-IFTop

iftop是类似于linux下面top的实时流量监控工具。比iptraf直观些。

下载:www.ex-parrot.com/~pdw/iftop/

1
2
3
4
ruby复制代码[root@localhost ~]# tar zxvf iftop-0.17.tar.gz
[root@localhost ~]# cd iftop-0.17 [root@localhost iftop-0.17]# ./configure
[root@localhost iftop-0.17]# make && make install
[root@localhost iftop-0.17]# iftop [root@localhost iftop-0.17]# iftop -i eth0 #指定监控网卡接口

)

TX:发送流量

RX:接收流量

TOTAL:总流量

Cumm:运行iftop到目前时间的总流量

peak:流量峰值

rates:分别表示过去 2s 10s 40s 的平均流量

6、进程实时监控-HTop

HTop是一个 Linux 下的交互式的进程浏览器可以用来替换Linux下的top命令。

1
2
csharp复制代码rpm -ivh http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm(安装第三方YUM源)
[root@localhost ~]# yum -y install htop

)

7、系统资源监控-NMON

NMON是一种在AIX与各种Linux操作系统上广泛使用的监控与分析工具

下载:sourceforge.jp/projects/sf…

1
2
3
csharp复制代码[root@localhost ~]# chmod +x nmon_x86_64_rhel6
[root@localhost ~]# mv nmon_x86_64_rhel6 /usr/sbin/nmon
[root@localhost ~]# nmon

)

8、监控多个日志-MultiTail

MultiTail是在控制台打开多个窗口用来实现同时监控多个日志文档、类似tail命令的功能的软件。

rpm -ivh pkgs.repoforge.org/rpmforge-re… (安装第三方YUM源)

1
2
3
4
css复制代码[root@localhost ~]# yum -y install multitail
[root@localhost ~]# multitail -e "fail" /var/log/secure #筛选关键字进行监控
[root@localhost ~]# multitail -l "ping baidu.com" #监控后面的命令-l将要执行的命令
[root@localhost ~]# multitail -i /var/log/messages -i /var/log/secure #-i指定一个文件名

)

9、SSH暴力破解防护-Fail2ban

Fail2ban可以监视你的系统日志然后匹配日志的错误信息正则式匹配执行相应的屏蔽动作一般情况下是调用防火墙屏蔽

下载:www.fail2ban.org/wiki/index.…

1
2
3
4
5
6
7
csharp复制代码[root@localhost ~]# cd fail2ban-0.8.11
[root@localhost fail2ban-0.8.11]# python setup.py install
[root@localhost fail2ban-0.8.11]# cd files/
[root@localhost files]# cp ./redhat-initd /etc/init.d/fail2ban
[root@localhost files]# service fail2ban start
[root@localhost files]# chkconfig --add fail2ban
[root@localhost files]# chkconfig fail2ban on

注:需要配置iptables实用,如果重启iptables了也要重启fail2ban,因为fail2ban的原理是调用iptables实时阻挡外界的攻击。

1
2
3
4
5
6
7
8
9
ini复制代码[root@localhost ~]# grep -v "^#" /etc/fail2ban/jail.conf | grep -v "^$" [DEFAULT]
ignoreip = 127.0.0.1/8#忽略本机IP
bantime = 600 #符合规则后封锁时间
findtime = 600 #在多长时间内符合规则执行封锁如600秒达到3次则执行
maxretry = 3 #最大尝试次数
backend = auto #日志修改检测日志gamin、polling和auto这三种
usedns = warn [ssh-iptables]
enabled = true#默认是禁用
false filter = sshd action = iptables[name=SSH, port=ssh, protocol=tcp] # sendmail-whois[name=SSH,dest=收件人邮箱, sender=发件人邮箱, sendername="Fail2Ban"] logpath = /var/log/sshd.log #响应的错误日志一般在/var/log/secure maxretry = 5 #尝试错误次数覆盖全局中的maxretry

注:默认所有的应用防护都是关闭的,需要我们手动开启。fail2ban.conf文件是日志信息,jail.conf文件是保护的具体服务和动作配置信息。

1
2
3
4
ini复制代码[root@localhost ~]# touch /var/log/sshd.log
[root@localhost ~]# service fail2ban restart
[root@localhost ~]# fail2ban-client status #查看监控已经开启 Status |- Number of jail: 1 `- Jail list: ssh-iptables
[root@localhost ~]# iptables -L #iptables过滤表有fail2ban一条规则 fail2ban-SSH tcp -- anywhere anywhere tcp dpt:ssh

10、连接会话终端持续化-Tmux

Tmux是一个优秀的终端复用软件类似GNU Screen比Screen更加方面、灵活和高效。为了确保连接SSH时掉线不影响任务运行。

rpm -ivh pkgs.repoforge.org/rpmforge-re…(安装第三方YUM源)

11、页面显示磁盘空间使用情况-Agedu

下载:www.chiark.greenend.org.uk/~sgtatham/a…

1
2
3
4
5
6
7
csharp复制代码[root@localhost ~]# tar zxvf agedu-r9723.tar.gz
[root@localhost ~]# cd agedu-r9723
[root@localhost ~]# ./configure
[root@localhost ~]# make && make install
[root@localhost ~]# agedu -s / #-s扫描
[root@localhost ~]# agedu -w --address 192.168.0.10:80 #-w输入一个网页链接
[root@localhost ~]# agedu -w --address 192.168.0.108080 --auth none #--auth关闭认证如果不加端口号会生成一个随机的用浏览器访问

)

12、安全扫描工具-NMap

NMap是Linux下的网络连接扫描和嗅探工具包用来扫描网上电脑开放的网络连接端。

下载:nmap.org/download.ht…

1
2
3
4
5
6
7
csharp复制代码[root@localhost ~]# tar jxvf nmap-6.40.tar.bz2
[root@localhost nmap-6.40]# ./configure
[root@localhost nmap-6.40]# make && make install
[root@localhost ~]# nmap 192.168.0.10 #获取基本信息
[root@localhost ~]# nmap -O 192.168.0.10 #获取系统版本信息
[root@localhost ~]# nmap -A 192.168.0.10 #获取系统综合信息
[root@localhost ~]# nmap 192.168.0.0/24 #获取一个网段工作设备基本信息

-sSTCP扫描

-sV系统版本检测

13、Web压力测试-Httperf

Httperf比ab更强大,能测试出web服务能承载的最大服务量及发现潜在问题;比如:内存使用、稳定性。最大优势:可以指定规律进行压力测试,模拟真实环境。

下载:code.google.com/p/httperf/d…

1
2
3
4
5
csharp复制代码[root@localhost ~]# tar zxvf httperf-0.9.0.tar.gz
[root@localhost ~]# cd httperf-0.9.0
[root@localhost httperf-0.9.0]# ./configure
[root@localhost httperf-0.9.0]# make && make install
[root@localhost ~]# httperf --hog --server=192.168.0.202 --uri=/index.html --num-conns=10000 --wsess=10,10,0.1

参数说明:

—hog:让httperf尽可能多产生连接,httperf会根据硬件配置,有规律的产生访问连接;

—num-conns:连接数量,总发起10000请求;

—wsess: 用户打开网页时间规律模拟,第一个10表示产生10个会话连接,第二个10表示每个会话连接进行10次请求,0.1表示每个会话连接请求之间的间隔时间/s。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Dockerfile 基础知识

发表于 2021-11-17

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」

Docker 可以通过读取 Dockerfile 中的指令自动构建镜像。Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用以组合图像的所有命令。使用 docker build 用户可以创建一个连续执行多个命令行指令的自动构建。

首先来看一个标准的 Dockerfile 文本包含哪些内容:

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
Dockerfile复制代码FROM docker.io/bitnami/minideb:buster
LABEL maintainer "Bitnami <containers@bitnami.com>"

ENV HOME="/" \
OS_ARCH="amd64" \
OS_FLAVOUR="debian-10" \
OS_NAME="linux" \
PATH="/opt/bitnami/common/bin:/opt/bitnami/etcd/bin:$PATH"

COPY prebuildfs /
# Install required system packages and dependencies
RUN install_packages acl ca-certificates curl gzip jq procps tar
RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "gosu" "1.14.0-0" --checksum 3e6fc37ca073b10a73a804d39c2f0c028947a1a596382a4f8ebe43dfbaa3a25e
RUN . /opt/bitnami/scripts/libcomponent.sh && component_unpack "etcd" "3.5.1-0" --checksum 912653e5c0879fd27c8bb0327bea171d7ae504611ec0a9c8fcc2ec6184766a63
RUN chmod g+rwX /opt/bitnami

COPY rootfs /
RUN /opt/bitnami/scripts/etcd/postunpack.sh
ENV BITNAMI_APP_NAME="etcd" \
BITNAMI_IMAGE_VERSION="3.5.1-debian-10-r27" \
ETCDCTL_API="3"

EXPOSE 2379 2380

WORKDIR /opt/bitnami/etcd
USER 1001
ENTRYPOINT [ "/opt/bitnami/scripts/etcd/entrypoint.sh" ]
CMD [ "/opt/bitnami/scripts/etcd/run.sh" ]

就此来分析一下一个 Dockerfile 包含哪些常用指令

FROM 指令

FROM 是指定基础镜像,必须为第一个命令,格式为:FROM <image>:<tag>
其中 tag 或 digest 是可选的,如果没有写时,会默认使用 latest 版本的基础继续。
比如示例中的:FROM docker.io/bitnami/minideb:buster

MAINTAINER 指令

MAINTAINER 用来声明维护者信息,格式为:MAINTAINER <name>
该命令已过期,推荐使用 LABEL

LABEL 指令

LABEL 用于为镜像添加元数据,多用于声明构建信息,如:作者、机构、组织等。格式为:LABEL <key>=<value> <key=value> ...
如示例中:LABEL maintainer "Bitnami <containers@bitnami.com>"
使用 LABEL 时要注意,一条 LABEL 可以指定一个或多个元数据,指定多个元数据是不同元数据之间通过空格分割。建议将所有元数据通过一条 LABEL 指令合并,这样可以避免产生过多的中间镜像。

ENV 指令

ENV 用来设置环境变量,格式:ENV <key> <value> ,如果一次想设置多个环境变量,格式为

1
2
3
Dockerfile复制代码ENV <key>=<value> \
<key>=<value> \
<key>=<value>

如示例中:

1
2
3
4
5
Dockerfile复制代码ENV HOME="/" \
OS_ARCH="amd64" \
OS_FLAVOUR="debian-10" \
OS_NAME="linux" \
PATH="/opt/bitnami/common/bin:/opt/bitnami/etcd/bin:$PATH"

ARG 指令

ARG 用于指定传递给构建运行时的变量,格式:ARG <name>[=<default value>]
通过 docker run 中的 --build-arg <key>=<value> 来动态赋值,不指定将使用其默认值。

WORKDIR 指令

WORKDIR 用来指定工作目录,类似于我们通常使用的 cd 命令,格式:WORKDIR <PATH>
通过 WORKDIR 设置工作目录,Dockerfile 中的其它指令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都会在该目录下执行。
在使用 docker run 运行容器时,可以通过 -w 参数来覆盖。
如示例中:

1
Dockerfile复制代码WORKDIR /opt/bitnami/etcd

ADD 指令

ADD 用于将本地文件添加到镜像中,tar 类型文件会自动进行解压,可以访问网络资源。格式:

1
2
3
Dockerfile复制代码ADD <src> <dest>
# 如果路径中包含空格,使用下面写法
ADD ["<src>", "<dest>"]

COPY 指令

COPY 的功能类似于 ADD,但不会自动解压文件、访问网络资源,格式:COPY <src> <dest>
如示例中:

1
Dockerfile复制代码COPY rootfs /

RUN 指令

RUN 用来在构建镜像时执命令,有两种命令执行方式:

  1. shell 格式
    RUN <command>
    示例:RUN install_packages acl ca-certificates curl gzip jq procps tar
  2. exec 格式
    RUN ["executable", "param1", "param2"]
    示例:RUN ["/dev/file", "p1", "P2"]

注意,RUN 指令创建的中间镜像会被缓存,并会在下次构建中重复使用,如果不想使用缓存镜像,可以在构建时指定--no-cache,示例:
docker build --no-cache

CMD 指令

CMD 设定在容器启动时执行的命令,格式:

1
2
3
4
Dockerfile复制代码CMD command param1 param2
CMD ["executable", "param1", "param2"]
# 设置了 ENTRYPOINT,直接调用ENTRYPOINT添加参数
CMD ["param1"]

示例:CMD ["/usr/bin/bash", "--help"]

ENTRYPOINT 指令

ENTRYPOINT 用来配置容器,使其可执行话。可以配合 CMD 使用,格式:

1
2
Dockerfile复制代码ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

如示例中:

1
2
Dockerfile复制代码ENTRYPOINT [ "/opt/bitnami/scripts/etcd/entrypoint.sh" ]
CMD [ "/opt/bitnami/scripts/etcd/run.sh" ]

docker run 执行的命令不会覆盖 ENTRYPOINT,而 docker run 命令中指定的任何参数都会被当做参数再次传递给 ENTRYPOINT 指令。Dockerfile 中只有最后一个 ENTRYPOINT 命令起作用,也就是说如果你指定多个ENTRYPOINT,只执行最后的 ENTRYPOINT 指令。

EXPOSE 指令

EXPOSE 指定与外界交互的端口,格式:EXPOSE <port> [<port>...]
如示例在中:

1
Dockerfile复制代码EXPOSE 2379 2380

EXPOSE 并不会主动让容器的端口映射主机。宿主机访问容器端口时,需要在 docker run 运行容器时通过 -p 来发布这些端口,或通过 -P 参数来发布 EXPOSE 导出的所有端口

VOLUME 指令

VOLUME 用于指定持久化目录, 格式:VOLUME ["<src>", ...]
示例:

1
Dockerfile复制代码VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]

和 EXPOSE 指令类似, VOLUME 并不会挂载的宿主机,需要通过 docker run 运行容器时通过 -v 来映射到宿主机的目录中。

USER 指令

USER 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。当服务不需要管理员权限时,可以通过该命令指定运行用户。格式:

1
2
3
4
5
6
Dockerfile复制代码USER user
USER user:group
USER uid:group
USER uid
USER user:gid
USER uid:gid

如示例中:

1
Dockerfile复制代码USER 1001

使用 USER 指定用户后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT 都将使用该用户。你可以通过 docker run 运行容器时,可以通过 -u 参数来覆盖指定用户。

ONBUILD 指令

ONBUILD 作用是当所构建的镜像被其他镜像作为基础镜像是,ONBUILD 中的命令就会触发,格式:ONBUILD [INSTRUCTION]
示例:

1
Dockerfile复制代码ONBUILD RUN /usr/local/bin/on-build --dir /app/src

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

selenium之长截图、弹窗处理和简单文件处理

发表于 2021-11-17

python selenium实现长截图

例:

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
js复制代码from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import os
import time
def get_image(url, pic_name):
#chromedriver的路径
chromedriver = r"C:\Users\name\AppData\Local\Google\Chrome\Application\chromedriver.exe"
os.environ["webdriver.chrome.driver"] = chromedriver
#设置chrome开启的模式,headless就是无界面模式
#一定要使用这个模式,不然截不了全页面,只能截到你电脑的高度
chrome_options = Options()
chrome_options.add_argument('headless')
driver = webdriver.Chrome(chromedriver,chrome_options=chrome_options)
#控制浏览器写入并转到链接
driver.get(url)
time.sleep(1)
#接下来是全屏的关键,用js获取页面的宽高,如果有其他需要用js的部分也可以用这个方法
width = driver.execute_script("return document.documentElement.scrollWidth")
height = driver.execute_script("return document.documentElement.scrollHeight")
print(width,height)
#将浏览器的宽高设置成刚刚获取的宽高
driver.set_window_size(width, height)
time.sleep(1)
#截图并关掉浏览器
driver.save_screenshot(pic_name)
driver.close()
#你输入的参数
url = 'https://movie.douban.com/top250'
pic_name = r'D:\image.png'
get_image(url, pic_name)

弹出框

弹出框有三种,分别是:alert,confirm,prompt

弹出框的主要操作方法:

ext:获取文本值

accept() :点击”确认”

dismiss() :点击”取消”或者关闭弹出框

send_keys() :输入文本值

alert

alert是HTML DOM 中用到的一种脚本语言,它的中文意思是“提醒”。它是JavaScript或VBscript脚本语言中窗口window对象的一个常用方法;其主要用法就是在你自己定义了一定的函数以后,通过执行相应的操作,所弹出对话框的语言。并且alert对话框通常用于一些对用户的提示信息。常见的为alert函数。.

confirm

confirm是JavaScript 语言中HTML DOM的一个方法。主要用法是:显示一个带有指定消息与“确定”及“取消”按钮的对话方块。

prompt

prompt是javascript语言中的一个方法,主要用处是显示提示对话框。

alert

自己写了一个alert的弹框,复制下面代码,存放在记事本中,后缀名改成html就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js复制代码<html>
<head>
<script type="text/javascript">

function display_alert()
{
alert("测试")
}

</script>

</head>
<body>
<center>
<input id="anjing" type="button" onclick="display_alert()"
value="点击我,有惊喜!" />
</center>
</body>
</html>

操作弹出框可以通过selenium进行切换到弹出框上,然后在进行一系列的操作。切换到弹出框方法: switch_to_alert()

话不多说,上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码from selenium import webdriver 
import time
driver = webdriver.Chrome()
path = r'E:\web\alert.html'
driver.get(path)
driver.find_element_by_id('anjing').click()
# 切换到alert
a = driver.switch_to_alert()
# 打印弹出框文本内容
print(a.text)
time.sleep(2)
# 点击确定
a.accept()

1171635-20200812174756292-235882714.gif

confirm

写了一个confirm的文件。也是通过上面的方法进行操作

html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
js复制代码<html> 
<head>
<script type="text/javascript">
function disp_prompt()
{
var name=prompt("开始测试")
if (name!=null && name!="")
{
document.write("测试")
}
}
</script>
</head>
<body>

<input type="button" onclick="disp_prompt()" value="点我,点我有惊喜" /> </body>
</html>

通过selenium运用 switch_to_alert() 进行切换到弹出框中,然后在点击取消,再次点击确定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码from selenium import webdriver 
import time driver = webdriver.Chrome()
path = r'E:\web\confirm.html'
driver.get(path)
driver.find_element_by_id('anjing').click()
# 切换到alert
a = driver.switch_to_alert()
# 打印弹出框文本内容
print(a.text)
time.sleep(4)
# 点击取消
a.dismiss()
print(a.text)
time.sleep(4)
# 点击确定
a.accept()

1171635-20200812185612820-1466670257.gif

prompt

同样是上述方法,这次的弹出框存在输入框,可以通过send_keys方法进行输入内容

html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js复制代码<html> 
<head>
<script type="text/javascript">
function disp_prompt()
{
var name=prompt("开始测试")
if (name!=null && name!="")
{
document.write("测试")
}
}
</script>
</head>
<body>

<input type="button" onclick="disp_prompt()" value="点我,点我有惊喜" />
</body>
</html>

这里我们也可以通过send_keys()输入内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码from selenium import webdriver 
import time
driver = webdriver.Chrome()
path = r'E:\web\prompt.html'
driver.get(path)
driver.find_element_by_id('anjing').click()
# 切换到alert
a = driver.switch_to_alert()
# 打印弹出框文本内容
print(a.text)
time.sleep(4)
# 点击取消
a.send_keys('测试')
time.sleep(1)
# 点击确定
a.accept()

其他方法

我们可以通过其他方法进行跳转到alert,这里需要用到Alert模块,遇到模块,首先进行导入 from selenium.webdriver.common.alert import Alert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码from selenium import webdriver 
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.alert import Alert
driver = webdriver.Chrome()
path = r'E:\web\prompt.html'
driver.get(path)
driver.find_element_by_id('anjing').click()
# 切换到alert
WebDriverWait(driver, 5).until(EC.alert_is_present())
# 等待弹出窗口出现
# alert = driver.switch_to.alert
# 跳转到alert
alert = Alert(driver)
print(alert.text)
alert.accept()

判断弹出框

执行代码过程中,可能会因为其他因素,网速慢等情况导致弹出框没有出现,我们可以通过判断元素中判断弹出框时否出现来进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码from selenium import webdriver 
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
driver = webdriver.Chrome()
path = r'E:\web\prompt.html'
driver.get(path)
driver.find_element_by_id('anjing').click()
# 切换到alert
WebDriverWait(driver, 5).until(EC.alert_is_present()) # 等待弹出窗口出现
alert = driver.switch_to.alert
print(alert.text)
alert.accept()

文件上传

input标签

众所周知,input标签是可以send_key的

1
2
3
4
5
6
js复制代码from selenium import webdriver 
driver = webdriver.Firefox() driver.get('http://sahitest.com/demo/php/fileUpload.htm')
upload = driver.find_element_by_id('file')
upload.send_keys('d:\\baidu.py') # send_keys print
upload.get_attribute('value') # check value
driver.quit()

很明显,对于input上传,直接send_keys是最简单的解决方案。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…310311312…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%