在 Istio 服务网格内连接外部 MySQL 数据库

为了方便理解,以 Istio 官方提供的 Bookinfo 应用示例为例,利用 ratings 服务外部 MySQL 数据库。Bookinfo应用的架构图如下:其中,包含四个单独的微服务:productpage:调用 details 和 reviews 两个服务,用来生成页面。details:包含了书籍的信息。reviews:包含了书籍相关的评论。它还会调用 ratings 微服务。rating:包含了由书籍评价组成的评级信息。其中,reviews 服务有 3 个版本:v1 版本不会调用 ratings 服务。v2 版本会调用 ratings 服务,并使用 1 到 5 个黑色星形图标来显示评分信息。v3 版本会调用 ratings 服务,并使用 1 到 5 个红色星形图标来显示评分信息。准备 MySQL 数据库创建一个名为 test 数据库,执行以下SQL创建表和数据:DROP TABLE IF EXISTS `ratings`; CREATE TABLE `ratings` ( `ReviewID` int(11) NOT NULL, `Rating` int(11) NULL DEFAULT 0, PRIMARY KEY (`ReviewID`) USING BTREE ) ENGINE = InnoDB; INSERT INTO ratings (ReviewID, Rating) VALUES (1, 2); INSERT INTO ratings (ReviewID, Rating) VALUES (2, 4);创建ServiceEntry执行以下命令创建ServiceEntry:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: mysqldb spec: hosts: - mysqldb.svc.remote ports: - number: 3306 name: mysql protocol: MySQL location: MESH_EXTERNAL resolution: STATIC endpoints: - address: 192.168.1.116 ports: mysql: 3306 EOF其中,192.168.1.116是 MySQL 数据库的IP,3306是 MySQL 数据库的端口。创建ratings服务首先,执行以下命令,获取密码的Base64编码:echo -n 'OneMoreSociety' | base64其中,OneMoreSociety是连接 MySQL 数据库的密码。然后,执行以下命令,创建 ratings 服务:kubectl apply -f - <<EOF apiVersion: v1 kind: Secret metadata: name: mysql-credentials type: Opaque data: dbpasswd: T25lTW9yZVNvY2lldHk= apiVersion: apps/v1 kind: Deployment metadata: name: ratings-v2-mysql labels: app: ratings version: v2-mysql spec: replicas: 1 selector: matchLabels: app: ratings version: v2-mysql template: metadata: labels: app: ratings version: v2-mysql spec: containers: - name: ratings image: docker.io/istio/examples-bookinfo-ratings-v2:1.16.2 imagePullPolicy: IfNotPresent - name: DB_TYPE value: "mysql" - name: MYSQL_DB_HOST value: mysqldb.svc.remote - name: MYSQL_DB_PORT value: "3306" - name: MYSQL_DB_USER value: root - name: MYSQL_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-credentials key: dbpasswd ports: - containerPort: 9080 securityContext: runAsUser: 1000 EOF其中,T25lTW9yZVNvY2lldHk=是连接 MySQL 数据库的密码的Base64编码。修改路由规则执行以下命令,把对 reviews 服务的调用全部路由到 v2 版本上:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v2 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3 EOF执行以下命令,把对 ratings 服务的调用全部路由到 v2-mysql 版本上:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - route: - destination: host: ratings subset: v2-mysql apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: ratings spec: host: ratings subsets: - labels: version: v1 name: v1 - labels: version: v2-mysql name: v2-mysql EOF效果访问 productpage 页面,可以看到 Reviewer1 显示2星, Reviewer2 显示4星,和数据库中的数据一致,如下图:在Kiali中也可以看到对应的拓扑结构,如下图:流量转移访问 MySQL 数据库时,所有流量都路由到v1版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: mysqldb spec: hosts: - mysqldb.svc.remote ports: - number: 3306 name: tcp protocol: TCP location: MESH_EXTERNAL resolution: STATIC endpoints: - address: 192.168.1.116 ports: tcp: 3306 labels: version: v1 - address: 192.168.1.118 ports: tcp: 3306 labels: version: v2 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: mysqldb spec: hosts: - mysqldb.svc.remote - route: - destination: host: mysqldb.svc.remote subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: mysqldb spec: host: mysqldb.svc.remote subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 EOF访问 MySQL 数据库时,把50%流量转移到v2版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: mysqldb spec: hosts: - mysqldb.svc.remote - route: - destination: host: mysqldb.svc.remote subset: v1 weight: 50 - destination: host: mysqldb.svc.remote subset: v2 weight: 50 EOF访问 MySQL 数据库时,所有流量都路由到v2版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: mysqldb spec: hosts: - mysqldb.svc.remote - route: - destination: host: mysqldb.svc.remote subset: v2 EOF最后,感谢你这么帅,还给我点赞。

一张图快速了解 Istio 的 EnvoyFilter

EnvoyFilter简介EnvoyFilter 提供了一种机制来定制 Istio Pilot 生成的 Envoy 配置。使用 EnvoyFilter 修改某些字段的值,添加特定的过滤器,甚至添加全新的侦听器、集群等等。这个功能必须谨慎使用,因为不正确的配置可能会破坏整个网格的稳定性。与其他 Istio 网络对象不同,EnvoyFilter 是叠加应用的。对于特定命名空间中的特定工作负载,可以存在任意数量的 EnvoyFilter。这些 EnvoyFilter 被应用的顺序是:首先是配置在根命名空间中的所有 EnvoyFilter,其次是配置在工作负载命名空间中的所有匹配的 EnvoyFilter。EnvoyFilter 的某些方面和 Istio 网络子系统的内部实现以及 Envoy 的 xDS API 有很深的联系。虽然 EnvoyFilter 本身将保持向后兼容性,但是在 Istio 版本升级过程中,通过该机制提供的任何 Envoy 配置都应该被仔细检查,以确保废弃的字段被适当地删除和替换。当多个 EnvoyFilter 被绑定到给定命名空间中的相同工作负载时,将按照创建时间的顺序依次应用。如果有多个 EnvoyFilter 配置相互冲突,那么将无法确定哪个配置被应用。要将 EnvoyFilter 资源应用于系统中的所有工作负载(sidecar 和 gateway)上,请在 config 根命名空间中定义该资源,不要使用 workloadSelector。要将 EnvoyFilter 应用到系统中的所有工作负载( sidecar 和网关)的时候,建议在配置根命名空间中定义,而不要使用 workloadSelector 。EnvoyFilter配置属性数据类型说明是否必填workloadSelectorWorkloadSelector用于选择应用此补丁的pod或虚拟机否configPatchesEnvoyConfigObjectPatch[]具有匹配条件的补丁是priorityint32定义了补丁集在上下文中应用顺序的优先级否其中,如果 workloadSelector 没有被配置,此补丁将应用于相同名称空间中所有工作负载的实例; priority 的默认值为0,取值范围是[min-int32, max-int32], priority 为负的补丁将在默认priority 之前处理,priority 为正的补丁将在默认priority 之后处理。EnvoyConfigObjectPatch配置属性数据类型说明是否必填applyToApplyTo指定在Envoy配置中应用补丁的位置否matchEnvoyConfigObjectMatch用于匹配监听器、路由或集群否patchPatch与操作一起应用的补丁。否其中,ApplyTo可以配置的值有:名称说明LISTENER将补丁应用于监听器。FILTER_CHAIN将补丁应用于过滤器链。NETWORK_FILTER应用补丁到网络过滤器链,修改现有的过滤器或添加一个新的过滤器。HTTP_FILTER将补丁应用于HTTP连接管理器中的HTTP过滤器链,以修改现有的过滤器或添加新的过滤器。ROUTE_CONFIGURATION将补丁应用于HTTP连接管理器内的Route配置。VIRTUAL_HOST将补丁应用于路由配置内部的虚拟主机。HTTP_ROUTE在路由配置中将补丁应用于匹配的虚拟主机内的路由对象。CLUSTER将补丁应用到集群。EXTENSION_CONFIG在ECDS输出中应用补丁或添加扩展配置。BOOTSTRAP将补丁应用于初始化配置。更多配置详见如下思维导图:思维导图

在Istio中,到底怎么获取 Envoy 访问日志?

Envoy 访问日志记录了通过 Envoy 进行请求 / 响应交互的相关记录,可以方便地了解具体通信过程和调试定位问题。环境准备部署 httpbin 服务:kubectl apply -f samples/httpbin/httpbin.yaml部署 sleep 服务:kubectl apply -f samples/sleep/sleep.yaml httpbin 服务作为接收请求的服务端, sleep 服务作为发送请求的客户端。还需要开启 Envoy 访问日志,执行以下命令修改 istio 配置:kubectl -n istio-system edit configmap istio编辑yaml文件的对应配置:data: mesh: |- accessLogEncoding: JSON accessLogFile: /dev/stdout其中,accessLogEncoding表示 accesslog 输出格式,Istio 预定义了 TEXT 和 JSON 两种日志输出格式。默认使用 TEXT,通常改成 JSON 以提升可读性;accessLogFile:表示 accesslog 输出位置,通常指定到 /dev/stdout (标准输出),以便使用 kubectl logs 来查看日志。保证yaml文件后,配置随即生效。测试访问日志在 sleep 服务中向 httpbin 服务发出请求:export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- curl -sS http://httpbin:8000/headers返回结果如下:{ "headers": { "Accept": "*/*", "Host": "httpbin:8000", "User-Agent": "curl/7.81.0-DEV", "X-B3-Parentspanid": "ed0178f3e1f48dd1", "X-B3-Sampled": "0", "X-B3-Spanid": "6c38b689ee5ab0c8", "X-B3-Traceid": "f17ce19c174cae85ed0178f3e1f48dd1", "X-Envoy-Attempt-Count": "1", "X-Forwarded-Client-Cert": "......" }执行以下命令,查看sleep 服务的Envoy日志:kubectl logs -l app=sleep -c istio-proxy可以看到sleep服务对httpbin服务的调用的日志:{ "authority": "httpbin:8000", "bytes_received": 0, "bytes_sent": 533, "connection_termination_details": null, "downstream_local_address": "172.24.146.239:8000", "downstream_remote_address": "172.24.158.25:49350", "duration": 3, "method": "GET", "path": "/headers", "protocol": "HTTP/1.1", "request_id": "ea40d320-348f-4f58-86d4-da157b0e0cca", "requested_server_name": null, "response_code": 200, "response_code_details": "via_upstream", "response_flags": "-", "route_name": "default", "start_time": "2022-07-04T10:00:09.401Z", "upstream_cluster": "outbound|8000||httpbin.istio-demo.svc.cluster.local", "upstream_host": "172.24.158.96:80", "upstream_local_address": "172.24.158.25:41812", "upstream_service_time": "2", "upstream_transport_failure_reason": null, "user_agent": "curl/7.81.0-DEV", "x_forwarded_for": null }执行以下命令,查看httpbin 服务的Envoy日志:kubectl logs -l app=httpbin -c istio-proxy可以看到httpbin服务被sleep服务调用的Envoy日志:{ "authority": "httpbin:8000", "bytes_received": 0, "bytes_sent": 533, "connection_termination_details": null, "downstream_local_address": "172.24.158.96:80", "downstream_remote_address": "172.24.158.25:41812", "duration": 2, "method": "GET", "path": "/headers", "protocol": "HTTP/1.1", "request_id": "ea40d320-348f-4f58-86d4-da157b0e0cca", "requested_server_name": "outbound_.8000_._.httpbin.istio-demo.svc.cluster.local", "response_code": 200, "response_code_details": "via_upstream", "response_flags": "-", "route_name": "default", "start_time": "2022-07-04T10:00:09.401Z", "upstream_cluster": "inbound|80||", "upstream_host": "172.24.158.96:80", "upstream_local_address": "127.0.0.6:33665", "upstream_service_time": "1", "upstream_transport_failure_reason": null, "user_agent": "curl/7.81.0-DEV", "x_forwarded_for": null }看到这么多参数,是不是有点懵逼?没关系接下来,我们详细看看!刨析Envoy日志名称HTTPTCPauthority请求授权头未实现(“-”)bytes_received接收到消息体字节数在连接上从下游接收的字节数bytes_sent发送的包体字节数在连接上发送给下游的字节数connection_termination_details连接中断详情连接中断详情downstream_local_address下游连接的本地地址下游连接的本地地址downstream_remote_address下游连接的远程地址下游连接的远程地址duration请求从起始时间到最后一个字节发出的持续总时长(以毫秒为单位)下游连接的持续总时长(以毫秒为单位)methodHTTP请求方法未实现(“-”)pathHTTP请求路径未实现(“-”)protocol协议,目前不是 HTTP/1.1 就是 HTTP/2未实现(“-”)request_id由envoy创建的 X-REQUEST-ID 请求头的值未实现(“-”)requested_server_name设置在 ssl 连接套接字上表示服务器名称指示 (SNI) 的字符值未实现(“-”)response_codeHTTP 响应码未实现(“-”)response_code_detailsTTP 响应状态码详情提供关于响应状态码的附加信息。未实现(“-”)response_flags响应或者连接的附加详情响应或者连接的附加详情route_name路由名路由名start_time请求开始时间(包括毫秒)下游连接开始时间(包括毫秒)upstream_cluster上游主机所属的上游集群上游主机所属的上游集群upstream_host上游主机 URL上游主机 URLupstream_local_address上游连接的本地地址上游连接的本地地址upstream_transport_failure_reason如果上游因传输套接字而连接失败,从传输套接字中提供失败原因。未实现(“-”)user_agentUser-Agent请求头的值未实现(“-”)x_forwarded_forX-Forwarded-For请求头的值未实现(“-”)清理删除 httpbin 和 sleep 服务:kubectl delete -f samples/httpbin/httpbin.yaml kubectl delete -f samples/sleep/sleep.yaml

5个 Istio 访问外部服务流量控制最常用的例子,你知道几个?

5 个 Istio 访问外部服务的流量控制常用例子,强烈建议收藏起来,以备不时之需。环境准备部署 sleep 服务,作为发送请求的测试源:kubectl apply -f samples/sleep/sleep.yaml在 Istio 外部,使用 Nginx 搭建 duckling 服务的v1和v2两个版本,访问时显示简单的文本:> curl -s http://192.168.1.118/ This is the v1 version of duckling. > curl -s http://192.168.1.119/ This is the v2 version of duckling.访问外部服务执行如下命名访问外部服务 httpbin.org :export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- curl -s http://httpbin.org/headers返回结果如下:{ "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.81.0-DEV", "X-Amzn-Trace-Id": "Root=1-62bbfa10-3237e3b9662c65ae005148ab", "X-B3-Sampled": "0", "X-B3-Spanid": "9e650093bf7ae862", "X-B3-Traceid": "1da46d7fafa5d71c9e650093bf7ae862", "X-Envoy-Attempt-Count": "1", "X-Envoy-Peer-Metadata": "......", "X-Envoy-Peer-Metadata-Id": "sidecar~......" }此时的方法,没有通过Service Entry,没有 Istio 的流量监控和控制特性。创建 Service Entry :kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: httpbin-ext spec: hosts: - httpbin.org ports: - number: 80 name: http protocol: HTTP resolution: DNS location: MESH_EXTERNAL EOF再此次访问,返回结果如下:{ "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.81.0-DEV", "X-Amzn-Trace-Id": "Root=1-62bbfbd6-254b05344b3cde2c0c41b3b8", "X-B3-Sampled": "0", "X-B3-Spanid": "307c0b106c8b262e", "X-B3-Traceid": "f684a50776c088ac307c0b106c8b262e", "X-Envoy-Attempt-Count": "1", "X-Envoy-Decorator-Operation": "httpbin.org:80/*", "X-Envoy-Peer-Metadata": "......", "X-Envoy-Peer-Metadata-Id": "sidecar~......" }可以发现由 Istio 边车添加的请求头:X-Envoy-Decorator-Operation。设置请求超时向外部服务 httpbin.org 的 /delay 发出请求:export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- time curl -o /dev/null -sS -w "%{http_code}\n" http://httpbin.org/delay/5返回结果如下:200 real 0m 5.69s user 0m 0.00s sys 0m 0.00s请求大约在 5 秒后返回 200 (OK)。创建虚拟服务,访问外部服务 httpbin.org 时, 请求超时设置为 3 秒:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin-ext spec: hosts: - httpbin.org http: - timeout: 3s route: - destination: host: httpbin.org weight: 100 EOF再此次访问,返回结果如下:504 real 0m 3.01s user 0m 0.00s sys 0m 0.00s可以看出,在 3 秒后出现了 504 (Gateway Timeout)。 Istio 在 3 秒后切断了响应时间为 5 秒的 httpbin.org 服务。注入 HTTP 延迟故障向外部服务 httpbin.org 的 /get 发出请求:export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- time curl -o /dev/null -sS -w "%{http_code}\n" http://httpbin.org/get返回结果如下:200 real 0m 0.45s user 0m 0.00s sys 0m 0.00s请求不到 1 秒就返回 200 (OK)。创建虚拟服务,访问外部服务 httpbin.org 时, 注入一个 3 秒的延迟:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin-ext spec: hosts: - httpbin.org http: - fault: delay: fixedDelay: 3s percentage: value: 100 route: - destination: host: httpbin.org EOF再此次访问 httpbin.org 的 /get ,返回结果如下:200 real 0m 3.43s user 0m 0.00s sys 0m 0.00s可以看出,在 3 秒后出现了 200 (OK)。流量转移访问duckling服务时,所有流量都路由到v1版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: duckling spec: hosts: - duckling.com ports: - number: 80 name: http protocol: HTTP location: MESH_EXTERNAL resolution: STATIC endpoints: - address: 172.24.29.118 ports: http: 80 labels: version: v1 - address: 172.24.29.119 ports: http: 80 labels: version: v2 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: duckling spec: hosts: - duckling.com http: - route: - destination: host: duckling.com subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: duckling spec: host: duckling.com subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 EOF执行如下命名访问外部服务 duckling.com :export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- curl -s http://duckling.com/多次访问后,返回结果一直是:This is the v1 version of duckling.访问duckling服务时,把50%流量转移到v2版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: duckling spec: hosts: - duckling.com http: - route: - destination: host: duckling.com subset: v1 weight: 50 - destination: host: duckling.com subset: v2 weight: 50 EOF多次访问外部服务 duckling.com ,有时返回This is the v1 version of duckling.,有时返回This is the v2 version of duckling.。访问duckling服务时,所有流量都路由到v2版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: duckling spec: hosts: - duckling.com http: - route: - destination: host: duckling.com subset: v2 EOF多次访问外部服务 duckling.com ,一直返回This is the v2 version of duckling.。基于请求头的路由请求头end-user为OneMore的所有流量都路由到v2版本,其他流量都路由到v1版本,具体配置如下:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: duckling spec: hosts: - duckling.com ports: - number: 80 name: http protocol: HTTP location: MESH_EXTERNAL resolution: STATIC endpoints: - address: 172.24.29.118 ports: http: 80 labels: version: v1 - address: 172.24.29.119 ports: http: 80 labels: version: v2 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: duckling spec: hosts: - duckling.com http: - match: - headers: end-user: exact: OneMore route: - destination: host: duckling.com subset: v2 - route: - destination: host: duckling.com subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: duckling spec: host: duckling.com subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 EOF执行如下命名访问外部服务 duckling.com :export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- curl -s http://duckling.com/多次访问的返回结果一直是:This is the v1 version of duckling.设置请求头end-user为OneMore,访问外部服务 duckling.com :kubectl exec "$SLEEP_POD" -c sleep -- curl -H "end-user:OneMore" -s http://duckling.com/多次访问的返回结果一直是:This is the v2 version of duckling.最后,感谢你这么帅,还给我点赞。

10个 Istio 流量管理 最常用的例子,你知道几个?

10 个 Istio 流量管理 最常用的例子,强烈建议收藏起来,以备不时之需。为了方便理解,以Istio官方提供的Bookinfo应用示例为例,引出 Istio 流量管理的常用例子。Bookinfo应用的架构图如下:其中,包含四个单独的微服务:productpage:调用 details 和 reviews 两个服务,用来生成页面。details:包含了书籍的信息。reviews:包含了书籍相关的评论。它还会调用 ratings 微服务。rating:包含了由书籍评价组成的评级信息。其中,reviews 服务有 3 个版本:v1 版本不会调用 ratings 服务。v2 版本会调用 ratings 服务,并使用 1 到 5 个黑色星形图标来显示评分信息。v3 版本会调用 ratings 服务,并使用 1 到 5 个红色星形图标来显示评分信息。流量转移目标1:把reviews 服务的所有流量都路由到v1版本。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3目标2:把reviews 服务的50%流量转移到v3版本。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v1 weight: 50 - destination: host: reviews subset: v3 weight: 50 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3目标3:把reviews 服务的所有流量都路由到v3版本。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v3 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3基于用户身份的路由目标:来自名为 OneMore 的用户的所有流量都路由到v2版本,其他流量都路由到v1版本。Istio 对用户身份没有任何特殊的内置机制。在应用示例中,productpage服务在所有到 reviews 服务的 HTTP 请求中都增加了一个自定义的 end-user 请求头,其值为用户名。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - match: - headers: end-user: exact: OneMore route: - destination: host: reviews subset: v2 - route: - destination: host: reviews subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3注入 HTTP 延迟故障目标:用户 OneMore 访问时, ratings 服务注入一个 2 秒的延迟,productpage页面在大约 2 秒钟加载完成并且没有错误。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - match: - headers: end-user: exact: OneMore fault: delay: percentage: value: 100.0 fixedDelay: 2s route: - destination: host: ratings subset: v1 - route: - destination: host: ratings subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: ratings spec: host: ratings subsets: - labels: version: v1 name: v1注入 HTTP 中止故障目标:用户 OneMore 访问时, ratings 服务注入一个503的中止故障,productpage 页面能够立即被加载,同时显示 “Ratings service is currently unavailable” 这样的消息。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - fault: abort: httpStatus: 503 percentage: value: 100 match: - headers: end-user: exact: OneMore route: - destination: host: ratings subset: v1 - route: - destination: host: ratings subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: ratings spec: host: ratings subsets: - labels: version: v1 name: v1设置请求超时首先,用户 OneMore 访问时, ratings 服务注入一个 2 秒的延迟,productpage页面在大约 2 秒钟加载完成并且没有错误。按照上文注入 HTTP 延迟故障进行操作,不再赘述。目标:用户 OneMore 访问时, reviews 服务的请求超时设置为 1 秒,同时显示 “Sorry, product reviews are currently unavailable for this book.” 这样的消息。kind: VirtualService apiVersion: networking.istio.io/v1alpha3 metadata: name: reviews spec: hosts: - reviews http: - match: - headers: end-user: exact: OneMore route: - destination: host: reviews subset: v2 timeout: 1s - route: - destination: host: reviews subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3在Jaeger可以看到具体的调用链如下:设置请求重试首先,用户 OneMore 访问时, ratings 服务注入一个 2 秒的延迟,productpage页面在大约 2 秒钟加载完成并且没有错误。按照上文注入 HTTP 延迟故障进行操作,不再赘述。目标:用户 OneMore 访问时, reviews 服务的请求重试次数为2次,重试超时时间为 0.5 秒,同时显示 “Sorry, product reviews are currently unavailable for this book.” 这样的错误消息。kind: VirtualService apiVersion: networking.istio.io/v1alpha3 metadata: name: reviews spec: hosts: - reviews http: - match: - headers: end-user: exact: OneMore route: - destination: host: reviews subset: v2 retries: attempts: 2 perTryTimeout: 0.5s - route: - destination: host: reviews subset: v1 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3拒绝目标IP的请求目标:除了IP为10.201.240.131的客户端可以访问/api/v1/products/1,其他客户端拒绝请求。apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-by-ip spec: selector: matchLabels: app: productpage action: DENY rules: - to: - operation: paths: ["/api/v1/products/1"] when: - key: remote.ip notValues: ["10.201.240.131"]熔断目标:设置details服务的并发上限为1。apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: details spec: host: details trafficPolicy: connectionPool: maxConnections: 1 http: http1MaxPendingRequests: 1 maxRequestsPerConnection: 1可以使用 Fortio 进行负载测试,发送并发数为 2 的连接(-c 2),请求 20 次(-n 20):kubectl exec fortio-deploy-684b6b47f8-tzsg8 -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://details:9080/details/0其中,fortio-deploy-684b6b47f8-tzsg8是Fortio的Pod名称,效果如下:流量镜像目标:把流量全部路由到reviews服务的 v2 版本,再把流量全部镜像到 v3 版本。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v2 mirror: host: reviews subset: v3 mirrorPercentage: value: 100.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2 - labels: version: v3 name: v3执行如下命令查看reviews服务 v3 版本的 Envoy 访问日志:kubectl logs -l app=reviews,version=v3 -c istio-proxy可以看到reviews服务 v3 版本被调用的日志:{ "authority": "reviews-shadow:9080", "bytes_received": 0, "bytes_sent": 375, "connection_termination_details": null, "downstream_local_address": "10.1.1.64:9080", "downstream_remote_address": "10.1.1.59:0", "duration": 1914, "method": "GET", "path": "/reviews/0", "protocol": "HTTP/1.1", "request_id": "b79cefe6-1277-9c39-b398-f94a704840cc", "requested_server_name": "outbound_.9080_.v3_.reviews.default.svc.cluster.local", "response_code": 200, "response_code_details": "via_upstream", "response_flags": "-", "route_name": "default", "start_time": "2022-06-27T07:34:19.129Z", "upstream_cluster": "inbound|9080||", "upstream_host": "10.1.1.64:9080", "upstream_local_address": "127.0.0.6:59837", "upstream_service_time": "1913", "upstream_transport_failure_reason": null, "user_agent": "curl/7.79.1", "x_forwarded_for": "10.1.1.59" }Ingress的路由目标:请求头app-id为details的所有流量都路由到details服务中。apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: bookinfo spec: hosts: - '*' gateways: - bookinfo-gateway http: - match: - uri: exact: /productpage - uri: prefix: /static - uri: exact: /login - uri: exact: /logout - uri: prefix: /api/v1/products route: - destination: host: productpage port: number: 9080 - match: - headers: app-id: exact: details route: - destination: host: details port: number: 9080使用curl命令验证一下:curl -H "app-id: details" -v http://127.0.0.1/details/2返回结果如下:* Trying 127.0.0.1:80... * Connected to 127.0.0.1 (127.0.0.1) port 80 (#0) > GET /details/2 HTTP/1.1 > Host: 127.0.0.1 > User-Agent: curl/7.79.1 > Accept: */* > app-id: details * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < content-type: application/json < server: istio-envoy < date: Tue, 28 Jun 2022 07:14:40 GMT < content-length: 178 < x-envoy-upstream-service-time: 4 {"id":2,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"} * Connection #0 to host 127.0.0.1 left intact返回结果可以看出,访问的是details服务。最后,感谢你这么帅,还给我点赞。

图解VirtualBox安装CentOS 7

VirtualBox简介VirtualBox是由德国InnoTek软件公司出品的虚拟机软件,现在则由甲骨文公司进行开发,是甲骨文公司xVM虚拟化平台技术的一部分。VirtualBox提供用户在32位或64位的Windows、Solaris及Linux 操作系统上虚拟其它x86的操作系统。用户可以在VirtualBox上安装并且执行Solaris、Windows、DOS、Linux、OS/2 Warp、OpenBSD及FreeBSD等系统作为客户端操作系统。与同性质的VMware及Virtual PC比较下,VirtualBox独到之处包括远程桌面协议(RDP)、iSCSI及USB的支持,VirtualBox在客户机操作系统上已可以支持USB 3.0的硬件设备,不过要安装Virtualbox Extension Pack。下载VirtualBox访问VirtualBox官方网站(https://www.virtualbox.org/wiki/Downloads)下载对应系统的安装文件,这里下载的是Windows系统 ,如下图:安装过程不是很难,一路点击确定即可,在此不再赘述。下载CentOS7访问阿里云镜像(https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/),下载安装包文件,这里是出于学习的目的,选择最小化安装包下载 ,如下图:新建虚拟电脑运行VirtualBox,点击“新建”按钮,如下图:名称: 在管理器中显示的名字;文件夹: 存放虚拟系统文件的目录;类型: 将要安装的系统类型;版本: 将要安装的系统版本,如下图:设置分配给虚拟机的内存,如下图:选择“现在创建虚拟硬盘”,如下图:选择虚拟硬盘文件类型,如下图:选择“动态分配”,如下图:配置文件位置和大小,如下图:虚拟机创建完毕,如下图:安装CentOS 7点击“设置”,如下图:依次点击“存储”->“没有盘片”->“蓝色光盘图标”->“选择虚拟盘”,如下图:选择之前下载好的CentOS 7安装包,点击“启动”,如下图:用方向键控制选择“Install CentOS 7”,如下图:稍等片刻,会看到语言选择界面,这里以中文为例,如下图:稍等片刻,会看到安装信息摘要界面,提示必须完成带有黄色感叹号的内容,才能进行下一步,如下图:点击“安装位置”,会看到安装目标位置的配置界面,如下图:保持默认,直接点击“完成按钮”。再点击“网络和主机名”,会看到网络和主机名的配置界面,打开网络连接,并设置主机名,如下图:点击“完成按钮”,返回到安装信息摘要界面,点击“开始安装”,如下图:点击“ROOT密码”,如下图:进入ROOT密码的配置界面,如下图:输入密码后,点击“完成”按钮。稍等几分钟,安装完成后,点击“重启”,如下图:重启后,进入CentOS 7,如下图:欢迎来到CentOS 7的世界。配置固定IP在 /etc/sysconfig/network-scripts 路径下找到 ifcfg- , 代表具体网卡,这次修改的网卡是 ifcfg-enp0s3 :vi /etc/sysconfig/network-scripts/ifcfg-enp0s3修改内容如下:# 修改 BOOTPROTO="static" ONBOOT="yes" IPADDR=192.168.0.200 GATEWAY=192.168.0.1 NETMASK=255.255.255.0 DNS1=192.168.1.1 DNS2=192.168.0.1 DNS3=8.8.8.8配置相关说明:配置项配置说明BOOTPROTO启用地址协议:static静态协议,bootp协议,dhcp协议ONBOOT系统启动时是否自动加载IPADDR网卡IP地址GATEWAY网关地址NETMASK网络掩码DNSDNS地址最后,重新启动使配置生效:reboot感谢你这么帅,还给我点赞。

Dapr在Java中的实践 之 状态管理

状态管理状态管理(State Management)使用键值对作为存储机制,可以轻松的使长时运行、高可用的有状态服务和无状态服务共同运行在我们的服务中。我们的服务可以利用Dapr的状态管理API在状态存储组件中保存、读取和查询键值对。状态存储组件是可插拔的,目前支持使用Azure CosmosDB、 Azure SQL Server、 PostgreSQL,、AWS DynamoDB、Redis 作为状态存储介质。编写示例代码创建一个SpringBoot项目,命名为:state-management,该项目的状态管理调用过程如下图:在state-management该项目的pom.xml文件中添加如下依赖:<dependency> <groupId>io.dapr</groupId> <artifactId>dapr-sdk-springboot</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.9.3</version> </dependency>注入一个DaprClient的bean:@Configuration public class DaprConfig { private static final DaprClientBuilder BUILDER = new DaprClientBuilder(); @Bean public DaprClient buildDaprClient() { return BUILDER.build(); }state-management项目中一共有3个接口:save:保存状态get:读取状态delete:删除状态具体源码如下:package one.more.society.state.management; import io.dapr.client.DaprClient; import io.dapr.client.domain.State; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class StateManagementController { @Autowired private DaprClient client; private static final String STATE_STORE_NAME = "statestore"; private static final String STATE_STORE_KEY = "one.more.society"; * 保存状态 * @param value value * @return @RequestMapping(value = "/save", method = RequestMethod.GET) public StateResponse save(String value) { log.info("save - value:{}", value); client.saveState(STATE_STORE_NAME, STATE_STORE_KEY, value).block(); StateResponse response = new StateResponse(); response.setCode(1); response.setStatus("save"); response.setValue(value); return response; * 读取状态 * @return StateResponse @RequestMapping(value = "/get", method = RequestMethod.GET) public StateResponse get() { log.info("get"); State<String> value = client.getState(STATE_STORE_NAME, STATE_STORE_KEY, String.class).block(); log.info("value: {}", value.getValue()); StateResponse response = new StateResponse(); response.setCode(1); response.setStatus("get"); response.setValue(value.getValue()); return response; * 删除状态 * @return @RequestMapping(value = "/delete", method = RequestMethod.GET) public StateResponse delete() { log.info("delete"); client.deleteState(STATE_STORE_NAME, STATE_STORE_KEY).block(); StateResponse response = new StateResponse(); response.setCode(1); response.setStatus("delete"); return response; }另外,在application.properties中配置:server.port=30003启动服务在启动之前先用mvn命令打包:mvn clean package在state-management项目的目录中执行以下命令,启动state-management服务:dapr run --app-id state-management --app-port 30003 --dapr-http-port 31003 -- java -jar target/state-management-0.0.1-SNAPSHOT.jar在Dapr Dashboard中看到:服务都已经启动成功。先访问http://localhost:30003/get,可以看到:读取状态返回为null,接下来访问http://localhost:30003/save?value=万猫学社,可以看到:状态已经保存了,再访问http://localhost:30003/get验证一下:状态被正确读取,再访问http://localhost:30003/delete,可以看到:状态已经被删除了,再访问http://localhost:30003/get验证一下:读取状态返回为null。状态储存组件初始化Dapr后,默认为我们指定的状态储存组件是Redis,在用户目录下的.dapr文件夹中的components文件夹中,可以找到statestore.yaml文件:apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore spec: type: state.redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" - name: actorStateStore value: "true"下面让我们来尝试一下,使用MySQL作为状态储存组件,把statestore.yaml文件修改为:apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore spec: type: state.mysql version: v1 metadata: - name: connectionString value: "root:one.more.society@tcp(127.0.0.1:3306)/?allowNativePasswords=true"重新启动服务,可以看到在日志中看到使用MySQL作为状态储存组件:time="09:57:35.5632633+08:00" level=info msg="Creating MySql schema 'dapr_state_store'" app_id=state-management instance=JT-243137 scope=dapr.contrib type=log ver=1.7.3 time="09:57:35.5862126+08:00" level=info msg="Creating MySql state table 'state'" app_id=state-management instance=JT-243137 scope=dapr.contrib type=log ver=1.7.3 time="09:57:35.6563599+08:00" level=info msg="component loaded. name: statestore, type: state.mysql/v1" app_id=state-management instance=JT-243137 scope=dapr.runtime type=log ver=1.7.3如果在MySQL中没有对应的库和表,Dapr默认为我们自动创建一个名为dapr_state_store的库,还有一个名为state的表,如下图:其中,state的表结构为:CREATE TABLE `state` ( `id` varchar(255) NOT NULL, `value` json NOT NULL, `isbinary` tinyint(1) NOT NULL, `insertDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updateDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `eTag` varchar(36) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;再访问一下http://localhost:30003/save?value=万猫学社,就可以在数据库中看到对应的数据:值得注意的是:MySQL状态储存组件目前还处于Alpha状态,最好不要在生产环境使用。更详细的配置说明见下表:配置项是否必填说明示例connectionStringY用于连接到 MySQL 的连接字符串。 请不要将schema添加到连接字符串中。非SSL连接: "<user>:<password>@tcp(<server>:3306)/?allowNativePasswords=true" Enforced SSL 连接: "<user>:<password>@tcp(<server>:3306)/?allowNativePasswords=true&tls=custom"schemaNameN要使用的schema名称。 如果指定的schema不存在,将会自动创建。默认值为"dapr_state_store""one_more_state_store"tableNameN要使用的表名。如果对应的表不存在,将被自动创建。默认值为 "state""one_more_state"pemPathN使用 Enforced SSL 连接 时,指定要使用的 PEM 文件完整路径。"/one/more/society/file.pem"pemContentsN如果没有提供pemPath,用于Enforced SSL连接的PEM文件的内容。可以在K8s环境下使用。"pem value"配置示例:apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore spec: type: state.mysql version: v1 metadata: - name: connectionString value: "root:one.more.society@tcp(127.0.0.1:3306)/?allowNativePasswords=true&tls=custom" - name: schemaName value: "one_more_state_store" - name: tableName value: "one_more_state" - name: pemPath value: "/one/more/society/file.pem"最后,感谢你这么帅,还给我点赞。

Dapr在Java中的实践 之 服务调用

服务调用通过服务调用(Service-to-service Invocation),服务可以使用 gRPC 或 HTTP 这样的标准协议来发现并可靠地与其他服务通信。Dapr采用边车(Sidecar)、去中心化的架构。 要使用Dapr来调用服务,可以在任意Dapr实例上使用invoke这个API。 边车编程模型鼓励每个服务与自己的Dapr实例对话。 Dapr实例会相互发现并进行通信。创建项目创建两个SpringBoot项目,分别命名为:invoke-server和invoke-client。invoke-server作为下游服务,被invoke-client调用,具体调用过程如下图:调用过程包括:invoke-client服务对invoke-server服务发起HTTP或gRPC调用的时候,访问invoke-client服务的Dapr实例。invoke-client服务的Dapr实例通过运行在给定托管平台上服务名解析组件(Name Resolution Component)发现了运行在此Dapr环境中的invoke-server服务。invoke-client服务的Dapr实例将消息转发到服务invoke-server服务的Dapr实例。Dapr实例之间的所有调用考虑到性能都优先使用gRPC。 仅服务与Dapr实例之间的调用可以是HTTP或gRPC。invoke-server服务的Dapr实例将请求转发至invoke-server服务上的特定端点或方法,随后运行其业务逻辑代码。invoke-server服务返回响应信息给invoke-client服务时,响应信息给将转至invoke-server服务的Dapr实例。invoke-server服务的Dapr实例消息转发至invoke-client服务的Dapr实例。invoke-client服务接收到其Dapr实例的响应信息。编写invoke-server的代码调用/send接口时,返回对应信息,主要代码如下:package one.more.society.invoke.server; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class InvokeServerController { @RequestMapping(value = "/send", method = RequestMethod.POST) public InvokeResponse send(@RequestBody InvokeRequest request) { log.info("send - request:{}", request); InvokeResponse response = new InvokeResponse(); response.setCode(1); response.setStatus("ok"); response.setMsgId(System.nanoTime()); response.setMsgContent("I konw you said: " + request.getMsgContent()); return response; }其中,InvokeRequest和InvokeResponse的源码如下:package one.more.society.invoke.server; import lombok.Data; @Data public class InvokeRequest { private Long msgId; private String msgContent; }package one.more.society.invoke.server; import lombok.Data; @Data public class InvokeResponse { private int code; private String status; private Long msgId; private String msgContent; }在application.properties中配置:server.port=30001编写invoke-client在invoke-client项目的pom.xml文件中添加如下依赖:<dependency> <groupId>io.dapr</groupId> <artifactId>dapr-sdk-springboot</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.9.3</version> </dependency>注入一个DaprClient的bean:@Configuration public class DaprConfig { private static final DaprClientBuilder BUILDER = new DaprClientBuilder(); @Bean public DaprClient buildDaprClient() { return BUILDER.build(); }调用invoke-server的/send接口,主要代码如下:package one.more.society.invoke.client; import io.dapr.client.DaprClient; import io.dapr.client.domain.HttpExtension; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class InvokeClientController { @Autowired private DaprClient client; private static final String SERVICE_APP_ID = "invoke-server"; private static final String METHOD_NAME = "send"; @RequestMapping(value = "/say", method = RequestMethod.GET) public InvokeResponse say(String message) { log.info("send - message:{}", message); InvokeRequest request = new InvokeRequest(); request.setMsgId(System.nanoTime()); request.setMsgContent(message); InvokeResponse response = client.invokeMethod( SERVICE_APP_ID, METHOD_NAME, request, HttpExtension.POST, InvokeResponse.class).block(); return response; }其中,InvokeRequest、InvokeResponse的源码与invoke-server中是一样的。在application.properties中配置:server.port=30002启动服务在启动之前先用mvn命令打包:mvn clean package在invoke-server项目的目录中执行以下命令,启动invoke-server服务:dapr run --app-id invoke-server --app-port 30001 --dapr-http-port 31001 -- java -jar target/invoke-server-0.0.1-SNAPSHOT.jar在invoke-client项目的目录中执行以下命令,启动invoke-client服务:dapr run --app-id invoke-client --app-port 30002 --dapr-http-port 31002 -- java -jar target/invoke-client-0.0.1-SNAPSHOT.jar在Dapr Dashboard中看到:两个服务都已经启动成功。访问http://localhost:30002/say?message=OneMoreSociety验证整个调用流程:可以看到服务之间的调用没有问题,并返回了预想的结果。名称解析组件为了启用服务发现和服务调用,Dapr使用可插拔的名称解析组件。 Kubernetes名称解析组件使用Kubernetes DNS服务来解析集群中运行的其他服务的位置;自托管机器可以使用mDNS名称解析组件。 Consul名称解析组件可以在任何托管环境中使用,包括Kubernetes或自托管环境。下面让我们来尝试一下,使用Consul作为名称解析组件。在用户目录下的.dapr文件夹中,找到config.yaml文件。在该文件中,添加一个nameResolution的spec ,并将component字段设置为consul,比如:apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: daprConfig spec: nameResolution: component: "consul" configuration: client: address: "127.0.0.1:8500" selfRegister: true重新启动服务,可以在日志中看到注册到了Consul上:time="14:28:54.4540593+08:00" level=info msg="service:invoke-client registered on consul agent" app_id=invoke-client instance=OneMoreSociety scope=dapr.contrib type=log ver=1.7.3 time="14:28:54.4550937+08:00" level=info msg="Initialized name resolution to consul" app_id=invoke-client instance=OneMoreSociety scope=dapr.runtime type=log ver=1.7.3在Consul中也可以看到两个服务都已经注册上去了,如下图:值得注意的是:Consul名称解析组件目前还处于Alpha状态,最好不要在生产环境使用。更详细的配置说明见下表:配置项是否必填数据类型说明示例clientNConfig配置客户端与 Consul 代理的连接。 如果留空,将使用默认值,即127.0.0.1:8500192.168.0.111:8500queryOptionsNQueryOptions配置用于解决健康服务的查询,默认为UseCache:trueUseCache: false, Datacenter: "myDC"checksNAgentServiceCheck数组当进行注册服务时,配置健康检查。默认到Dapr实例检测健康端点。 tagsNstring数组在注册服务服务时包含的额外标签- "dapr"metaNstring字典在注册服务服务时包含的额外的元数据DAPR_METRICS_PORT: "${DAPR_METRICS_PORT}"daprPortMetaKeyNstring用于在服务解析过程中从Consul服务元数据中获取Dapr实例端口的 key,它也将用于在注册时在元数据中设置Dapr实例端口。 默认为 DAPR_PORT"DAPR_TO_DAPR_PORT"selfRegisterNboolean控制Dapr实例是否会向Consul注册服务,默认为 falsetrueadvancedRegistrationNAgentServiceRegistration通过配置完全控制服务注册结果。 如果配置此项,Checks、 Tags、 Meta 和 SelfRegister的任何配置将被忽略。 配置示例:apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: appconfig spec: nameResolution: component: "consul" configuration: client: address: "127.0.0.1:8500" selfRegister: true checks: - name: "Dapr Health Status" checkID: "daprHealth:${APP_ID}" interval: "15s", http: "http://${HOST_ADDRESS}:${DAPR_HTTP_PORT}/v1.0/healthz" - name: "Service Health Status" checkID: "serviceHealth:${APP_ID}" interval: "15s", http: "http://${HOST_ADDRESS}:${APP_PORT}/health" tags: - "dapr" - "v1" - "${OTHER_ENV_VARIABLE}" meta: DAPR_METRICS_PORT: "${DAPR_METRICS_PORT}" DAPR_PROFILE_PORT: "${DAPR_PROFILE_PORT}" daprPortMetaKey: "DAPR_PORT" queryOptions: useCache: true filter: "Checks.ServiceTags contains dapr"最后,感谢你这么帅,还给我点赞。

Dapr在Java中的实践 之 环境准备

Dapr简介Dapr (Distributed Application Runtime)是一个可移植的、事件驱动的运行时,它使任何开发人员都可以轻松地构建运行在云和边缘上的弹性、无状态和有状态的应用程序,并支持语言和开发人员框架的多样性。Dapr利用 Sidecar 架构的优势,帮助我们解决构建微服务所带来的挑战,并保持代码与平台无关。从上面的架构图看出,Dapr包括如下几个模块:服务调用(Service-to-service Invocation):通过服务调用,服务可以使用 gRPC 或 HTTP 这样的标准协议来发现并可靠地与其他服务通信。状态管理(State Management):独立的状态管理,使用键值对作为存储机制,可以轻松的使长时运行、高可用的有状态服务和无状态服务共同运行在我们的服务中。发布订阅(Publish and Subscribe):发布事件和订阅主题。生产者将消息发送到某个主题(Topic),但不知道接收消息的服务;消费者将订阅该主题并收到它的消息,但不知道哪个服务生产了这些消息。资源绑定(Resource Bindings):通过建立触发器与资源的绑定,可以从任何外部源(例如数据库,队列,文件系统等)接收和发送事件,而无需借助消息队列,即可实现灵活的业务场景。Actors:Actor是一个独立的运行单元,拥有隔离的运行空间,在隔离的空间内,其有独立的状态和行为,不被外界干预。Actor之间通过消息进行交互,而同一时刻,每个Actor只能被单个线程执行,这样既有效避免了数据共享和并发问题,又确保了应用的伸缩性。可观测性(Observability):记录指标(metric)、日志(log)、链路(trace)以调试和监视Dapr和服务的运行状况。密钥管理(Secrets):支持与公有云和本地的密钥存储集成,以供服务检索使用。配置管理(Configuration):通过配置API在配置存储中检索和订阅服务的配置项。安装 Docker这里以Windows 10系统为例,安装 Docker。安装 Hyper-VHyper-V是微软提出的一种系统管理程序虚拟化技术,能够实现桌面虚拟化。Hyper-V 可用于 64 位 Windows 10 专业版、企业版和教育版。 它无法用于家庭版。点击“小窗户”,然后再点击“设置”,如下图:在搜索框中输入“启用或关闭Windows功能”,如下图:点击“启用或关闭Windows功能”后,勾选“Hyper-V”所有选项,如下图:点击“确定”,等一会儿就安装好了。(可能需要重启电脑)安装 Docker Desktop访问https://desktop.docker.com/win/stable/amd64/Docker%20Desktop%20Installer.exe下载,双击Docker Desktop Installer.exe运行安装程序。按照安装向导上的说明授权安装程序并继续进行安装。安装完成后,启动Docker Desktop,等一会儿初始化完成后,将启动入门教程:这个教程包括一个简单的练习,以构建示例Docker镜像,将其作为容器运行,将映像推送并保存到Docker Hub。安装 Dapr CLI访问https://github.com/dapr/cli/releases下载所需的 Dapr CLI,如果是Linux系统可以下载dapr_linux_amd64.tar.gz;如果是Windows系统,可以下载dapr_windows_amd64.zip;如果是macOS,可以下载dapr_darwin_amd64.tar.gz。这里以Windows系统为例。解压到一个目录中,并把这个目录添加到系统环境变量中,然后执行如下命令验证一下:dapr --version如果显示如下效果就说明安装成功了:初始化 Dapr执行如下命令:dapr init它会帮我们创建如下内容:运行一个用于状态存储和消息代理的Redis容器实例运行一个用于提供可观察性的Zipkin容器实例创建具有上述组件定义的默认组件文件夹:用户目录中的.dapr文件夹运行用于本地演员支持的Dapr Placement服务容器实例显示效果如下:执行如下命令验证一下:docker ps如果显示如下效果就说明初始化成功了:此时,访问http://localhost:9411/zipkin/就可以看到:启动 Dapr Dashboard执行如下命令:dapr dashboard -p 9999显示效果如下:再访问http://localhost:9999/就可以看到:最后,感谢你这么帅,还给我点赞。

自从用了 Kiali 以后才知道,配置 Istio 的 流量管理 是如此容易

在生产环境中,直接登录服务器是非常不方便的,我们可以使用Kiali配置Istio的流量管理。本文以Istio官方提供的Bookinfo应用示例为例,使用Kiali配置Istio的流量管理。Bookinfo应用的架构图如下:其中,包含四个单独的微服务:productpage:调用 details 和 reviews 两个服务,用来生成页面。details:包含了书籍的信息。reviews:包含了书籍相关的评论。它还会调用 ratings 微服务。rating:包含了由书籍评价组成的评级信息。其中,reviews 服务有 3 个版本:v1 版本不会调用 ratings 服务。v2 版本会调用 ratings 服务,并使用 1 到 5 个黑色星形图标来显示评分信息。v3 版本会调用 ratings 服务,并使用 1 到 5 个红色星形图标来显示评分信息。创建路由规则目标:把reviews 服务的所有流量都路由到v1版本进入reviews 服务的Services页面,点击“Actions”后,点击“Request Routing”,如下图:在“Create Request Routing”的弹窗中,选择“Route To”标签页,把reviews-v1的权重调节到100%,如下图:点击“Add Rule”按钮,会添加一个针对所有请求的路由规则,如下图:点击“Create”按钮,对应的虚拟服务(Virtual Service)和目标规则(DestinationRule)就创建好了,如下图:访问几次productpage页面,可以看到一直是没有星形图标的页面,如下图:基于用户身份的路由目标:来自名为 OneMore 的用户的所有流量都路由到v2版本。Istio 对用户身份没有任何特殊的内置机制。事实上,productpage服务在所有到 reviews 服务的 HTTP 请求中都增加了一个自定义的 end-user 请求头,其值为用户名。仍然进入reviews 服务的Services页面,点击“Actions”后,点击“Request Routing”。在“Update Request Routing”的弹窗中,选择“Request Matching”标签页,进行如下配置:点击“Add Match”后,“Matching selected”变为“headers [end-user] exact OneMore”,如下图:选择“Route To”标签页,把reviews-v2的权重调节到100%,如下图:点击“Add Rule”按钮,会添加一个路由规则,如下图:看到了如下的错误信息:Match 'Any request' is defined in a previous rule. This rule is not accessible.我们把这个路由规则的优先级调高一下:调整后,没有错误信息了,点击“Update”。使用 OneMore 登录后,访问productpage页面,可以看到使用黑色星形图标来显示评分信息的页面,如下图:注入 HTTP 延迟故障目标:用户 OneMore 访问时, ratings 服务注入一个 7 秒的延迟,productpage页面在大约 7 秒钟加载完成并且没有错误。进入ratings 服务的Services页面,点击“Actions”后,点击“Request Routing”。在“Create Request Routing”的弹窗中,选择“Request Matching”标签页,进行如下配置:点击“Add Match”后,“Matching selected”变为“headers [end-user] exact OneMore”,如下图:选择“Fault Injection”标签页,开启“Add HTTP Delay”,修改“Fixed Delay”为7s,如下图:点击“Add Rule”按钮,会添加一个路由规则,如下图:点击“Create”按钮,对应的虚拟服务(Virtual Service)和目标规则(DestinationRule)就创建好了,如下图:使用 OneMore 登录后,访问productpage页面,如下图:没有达到我们预期的结果,出现了一个问题:Reviews 部分显示了错误消息:Sorry, product reviews are currently unavailable for this book.并且productpage页面加载实际上用了大约 6s。查看代码后,发现了一个 bug。微服务中有硬编码超时,导致 reviews 服务失败。按照预期,我们引入的 7 秒延迟不会影响到 reviews 服务,因为 reviews 和 ratings 服务间的超时被硬编码为 10 秒。 但是,在 productpage 和 reviews 服务之间也有一个 3 秒的硬编码的超时,再加 1 次重试,一共 6 秒。 结果,productpage 对 reviews 的调用在 6 秒后提前超时并抛出错误了。我们把延迟修改为2s,再尝试一下,才得到了我们预期的结果:大约 2 秒钟加载完成并且没有错误,如下图:注入 HTTP 中止故障目标:用户 OneMore 访问时, ratings 服务注入一个中止故障,productpage 页面能够立即被加载,同时显示 “Ratings service is currently unavailable” 这样的消息。进入ratings 服务的Services页面,点击“Actions”后,点击“Request Routing”。先把上个例子的路由规则删除,否则报“A Rule with same matching criteria is already added.”的错误。在“Update Request Routing”的弹窗中,选择“Request Matching”标签页,进行如下配置:点击“Add Match”后,“Matching selected”变为“headers [end-user] exact OneMore”,如下图:选择“Fault Injection”标签页,开启“Add HTTP Abort”,修改“HTTP Status Code”为500,如下图:点击“Add Rule”按钮,会添加一个路由规则,如下图:点击“Update”。使用 OneMore 登录后,访问productpage页面,如下图:达到了我们预期的效果。设置请求超时首先,用户 OneMore 访问时, ratings 服务注入一个 2 秒的延迟,productpage页面在大约 2 秒钟加载完成并且没有错误。按照上文注入 HTTP 延迟故障进行操作,不再赘述,效果如下:目标:用户 OneMore 访问时, reviews 服务的请求超时设置为 0.5 秒,同时显示 “Sorry, product reviews are currently unavailable for this book.” 这样的消息。进入reviews 服务的Services页面,点击“Actions”后,点击“Request Routing”。在“Update Request Routing”的弹窗中,选择“Request Matching”标签页,进行如下配置:点击“Add Match”后,“Matching selected”变为“headers [end-user] exact OneMore”,如下图:选择“Route To”标签页,把reviews-v2的权重调节到100%,如下图:选择“Request Timeouts”标签页,开启“Add HTTP Timeout”,修改“Timeout”为0.5s,如下图:点击“Add Rule”按钮,会添加一个路由规则,如下图:看到了如下的错误信息:Match 'Any request' is defined in a previous rule. This rule is not accessible.我们把这个路由规则的优先级调高一下:调整后,没有错误信息了,点击“Update”。使用 OneMore 登录后,访问productpage页面如下图:可是显示了预期的错误信息,但是访问页面却用了1秒钟,这是为什么呢?这是因为 productpage 服务中存在硬编码重试,它在返回页面之前调用 reviews 服务超时两次。设置请求重试首先,用户 OneMore 访问时, ratings 服务注入一个 2 秒的延迟,productpage页面在大约 2 秒钟加载完成并且没有错误。按照上文注入 HTTP 延迟故障进行操作,不再赘述,效果如下:目标:用户 OneMore 访问时, reviews 服务的请求重试次数为2次,重试超时时间为 0.5 秒,同时显示 “Sorry, product reviews are currently unavailable for this book.” 这样的错误消息。进入reviews 服务的Services页面,点击“Actions”后,点击“Request Routing”。在“Update Request Routing”的弹窗中,选择“Request Matching”标签页,进行如下配置:点击“Add Match”后,“Matching selected”变为“headers [end-user] exact OneMore”,如下图:选择“Route To”标签页,把reviews-v2的权重调节到100%,如下图:选择“Request Timeouts”标签页,开启“Add HTTP Retry”,修改“Attempts”为2,修改“Per Try Timeout”为0.5s,如下图:点击“Add Rule”按钮,会添加一个路由规则,如下图:看到了如下的错误信息:Match 'Any request' is defined in a previous rule. This rule is not accessible.我们把这个路由规则的优先级调高一下:调整后,没有错误信息了,点击“Update”。使用 OneMore 登录后,访问productpage页面如下图:可是显示了预期的错误信息,但是访问页面却用了3秒钟,这是为什么呢?还是因为 productpage 服务中存在硬编码重试,它在返回页面之前调用 reviews 服务超时两次。感谢你这么帅,还给我点赞。

手摸手带你 在 Windows 系统中安装 Istio

Istio简介通过负载均衡、服务间的身份验证、监控等方法,Istio 可以轻松地创建一个已经部署了服务的网络,而服务的代码只需很少更改甚至无需更改。通过在整个环境中部署一个特殊的 sidecar 代理为服务添加 Istio 的支持,而代理会拦截微服务之间的所有网络通信,然后使用其控制平面的功能来配置和管理 Istio,包括:为 HTTP、gRPC、WebSocket 和 TCP 流量自动负载均衡。通过丰富的路由规则、重试、故障转移和故障注入对流量行为进行细粒度控制。可插拔的策略层和配置 API,支持访问控制、速率限制和配额。集群内(包括集群的入口和出口)所有流量的自动化度量、日志记录和追踪。在具有强大的基于身份验证和授权的集群中实现安全的服务间通信。在Istio官方文档中,安装Istio是以Linux或MacOS系统为例的,对于Windows用户不是很友好,不过真难不倒我们。安装 Hyper-VHyper-V是微软提出的一种系统管理程序虚拟化技术,能够实现桌面虚拟化。Hyper-V 可用于 64 位 Windows 10 专业版、企业版和教育版。 它无法用于家庭版。点击“小窗户”,然后再点击“设置”,如下图:在搜索框中输入“启用或关闭Windows功能”,如下图:点击“启用或关闭Windows功能”后,勾选“Hyper-V”所有选项,如下图:点击“确定”,等一会儿就安装好了。(可能需要重启电脑)安装 Docker DesktopDocker Desktop 是一款适用于 Mac 或 Windows 环境的易于安装的应用程序,使您能够在几分钟内开始编码和容器化。Docker Desktop 包含了从您的机器构建、运行和共享容器化应用程序所需的一切。访问https://desktop.docker.com/win/stable/amd64/Docker%20Desktop%20Installer.exe下载,双击Docker Desktop Installer.exe运行安装程序。按照安装向导上的说明授权安装程序并继续进行安装。安装完成后,启动Docker Desktop,等一会儿初始化完成后,将启动入门教程:这个教程包括一个简单的练习,以构建示例Docker镜像,将其作为容器运行,将映像推送并保存到Docker Hub。成功安装 Docker Desktop 后,打开一个终端,运行 docker --version 来检查机器上安装的 Docker 版本。开启 Kubernetes在Docker Desktop的设置中,为 Kubernetes 配置 CPU 和 内存资源,建议分配4核或更多CPU,8GB或更多内存,如下图:在Docker Desktop的设置中,勾选开启 Kubernetes 和 显示系统容器,如下:重启Docker Desktop后,等待Kubernetes启动,启动成功后,可以看到Kubernetes的标签变成绿色,并且在容器列表中可以看到Kubernetes相关的容器,如下图:安装到Istio的官方网站(https://github.com/istio/istio/releases)下载windows系统的安装包,如下图:把压缩包解压到你觉得安逸的目录里,然后把istio-1.14.0\bin目录添加到环境变量中。打开命令提示符,执行istioctl version命令可以看到IIstio的版本信息,说明环境变量设置成功,如下图:执行如下命令,安装Istio:istioctl install --set profile=demo -y本次安装采用 demo 配置组合。 选择它是因为它包含了一组专为测试准备的功能集合,另外还有用于生产或性能测试的配置组合。稍等片刻,我们就可以看到:C:\万猫学社> istioctl install --set profile=demo -y ✔ Istio core installed ✔ Istiod installed ✔ Egress gateways installed ✔ Ingress gateways installed ✔ Installation complete执行如下命令,查看Istio是否安装成功:kubectl get pods -n istio-system可以看到 Istio 相关的 Pod 都处于 Running 状态,如下图:给命名空间添加标签,指示 Istio 在部署应用的时候,自动注入 Envoy 边车代理:kubectl label namespace default istio-injection=enabled至此,Istio已经在在Windows上安装完成了。如果你还想进行一些学习或者测试Istio的功能,可以部署示例应用。部署示例应用进入istio-1.14.0目录中,执行一下命令,部署 Bookinfo 示例应用:kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml如下图:稍等几分钟后,执行kubectl get pods命令我们就可以看到Bookinfo 示例应用相关的 Pod 都处于 Running 状态,如下图:最后,我们通过检查返回的页面标题,来验证应用是否已在集群中运行,并已提供网页服务:# 获取ratings的Pod名称 kubectl get pod -l app=ratings -o jsonpath={.items[0].metadata.name} # 替换掉ratings的Pod名称 kubectl exec ratings的Pod名称 -c ratings -- curl -s productpage:9080/productpage | findstr /r "<title>.*</title>"具体操作如下图:返回的页面标题为:<title>Simple Bookstore App</title>。此时,BookInfo 应用已经部署,但还不能被外界访问。 要开放访问,你需要创建 Istio 入站网关(Ingress Gateway), 它会在网格边缘把一个路径映射到路由。执行以下命令,把应用关联到 Istio 网关:kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml执行下面命令以判断你的 Kubernetes 集群环境是否支持外部负载均衡:kubectl get svc istio-ingressgateway -n istio-system如果 EXTERNAL-IP 有值, 那么就有了一个外部的负载均衡,可以用它做入站网关。 但如果 EXTERNAL-IP 的值为 或者一直是 状态, 则没有提供可作为入站流量网关的外部负载均衡。 在这个情况下,可以用服务(Service)的 节点端口 访问网关。最后,感谢你这么帅,还给我点赞。

深入理解 Istio 流量管理的超时时间设置

HTTP 请求的超时可以用路由规则的 timeout 字段来指定,那么 Istio 到底是如何实现超时时间的呢?环境准备httpbin 服务httpbin 是一个使用 Python + Flask 编写的 HTTP 请求响应服务。该服务主要用于测试 HTTP 库,你可以向他发送请求,然后他会按照指定的规则将你的请求返回。执行以下命令,部署 httpbin 服务:kubectl apply -f samples/httpbin/httpbin.yamlsleep 服务执行以下命令,部署 sleep 服务:kubectl apply -f samples/sleep/sleep.yaml httpbin 服务作为接收请求的服务端, sleep 服务作为发送请求的客户端。设置超时时间执行以下命令,在 sleep 服务中向 httpbin 服务的发出请求:export SLEEP_POD=$(kubectl get pods -l app=sleep -o 'jsonpath={.items[0].metadata.name}') kubectl exec "$SLEEP_POD" -c sleep -- time curl -o /dev/null -sS -w "%{http_code}\n" http://httpbin.org/delay/5返回结果如下:200 real 0m 5.69s user 0m 0.00s sys 0m 0.00s可以看到,请求大约在 5 秒返回 200 (OK)。在默认情况下,超时被设置为 15 秒钟。创建虚拟服务,访问 httpbin 服务时,请求超时设置为 3 秒:kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin spec: hosts: - httpbin http: - route: - destination: host: httpbin timeout: 3s EOF再此次访问,返回结果如下:504 real 0m 3.01s user 0m 0.00s sys 0m 0.00s可以看到,在 3 秒后出现了 504 (Gateway Timeout)。 Istio 在 3 秒后切断了响应时间为 5 秒的httpbin 服务的请求。接下来,我们深入地看一下,Istio是怎么切断请求的?查看Envoy日志执行一下命令,查看sleep 服务的Envoy日志:kubectl logs -l app=sleep -c istio-proxy可以看到sleep服务对httpbin服务的调用的日志:{ "authority": "httpbin:8000", "bytes_received": 0, "bytes_sent": 24, "connection_termination_details": null, "downstream_local_address": "172.24.146.239:8000", "downstream_remote_address": "172.24.158.25:40384", "duration": 3001, "method": "GET", "path": "/delay/5", "protocol": "HTTP/1.1", "request_id": "5ef38816-7f49-48c8-9627-2416e1716293", "requested_server_name": null, "response_code": 504, "response_code_details": "upstream_response_timeout", "response_flags": "UT", "route_name": null, "start_time": "2022-07-01T09:40:13.882Z", "upstream_cluster": "outbound|8000||httpbin.onemore.svc.cluster.local", "upstream_host": "172.24.158.96:80", "upstream_local_address": "172.24.158.25:32846", "upstream_service_time": null, "upstream_transport_failure_reason": null, "user_agent": "curl/7.81.0-DEV", "x_forwarded_for": null }其中,response_flags为UT,表示上游(upstream)请求超时,也就是sleep服务检测到了httpbin服务的请求超时。执行一下命令,查看httpbin 服务的Envoy日志:kubectl logs -l app=httpbin -c istio-proxy可以看到httpbin服务被sleep服务调用的Envoy日志:{ "authority": "httpbin:8000", "bytes_received": 0, "bytes_sent": 0, "connection_termination_details": null, "downstream_local_address": "172.24.158.96:80", "downstream_remote_address": "172.24.158.25:32846", "duration": 2997, "method": "GET", "path": "/delay/5", "protocol": "HTTP/1.1", "request_id": "5ef38816-7f49-48c8-9627-2416e1716293", "requested_server_name": "outbound_.8000_._.httpbin.onemore.svc.cluster.local", "response_code": 0, "response_code_details": "downstream_remote_disconnect", "response_flags": "DC", "route_name": "default", "start_time": "2022-07-01T09:40:13.885Z", "upstream_cluster": "inbound|80||", "upstream_host": "172.24.158.96:80", "upstream_local_address": "127.0.0.6:35701", "upstream_service_time": null, "upstream_transport_failure_reason": null, "user_agent": "curl/7.81.0-DEV", "x_forwarded_for": null }其中,response_flags为DC,表示下游(downstream)连接中断,也就是sleep服务的调用请求被中断了。深入分析通过Envoy日志,我们可以做出一些分析和判断:当httpbin服务的请求正常的时候,调用过程如下图:当httpbin服务的请求超时的时候,调用过程如下图:虽然,我们在httpbin服务上设置的请求超时时间,但实际上主动断开请求的却是sleep服务的Envoy。清理kubectl delete virtualservice httpbin kubectl delete -f samples/httpbin/httpbin.yaml kubectl delete -f samples/sleep/sleep.yaml 最后,感谢你这么帅,还给我点赞。

图解 Apache SkyWalking UI 的使用

Apache SkyWalking的UI界面主要分为以下几个区域:功能选择区:这里列出了主要的UI功能,包括仪表盘、拓扑图、追踪、性能刨析、告警等功能重新加载区:控制重新加载机制,包括定期重新加载或手动重新加载。时间选择器:控制时区和时间范围。这里有一个中文/英文切换按钮,默认,UI使用浏览器语言设置。下面逐一介绍功能选择区的各个功能。仪表盘仪表盘又分为以下几个功能:APM:以全局(Global)、服务(Service)、服务实例(Instance)、端点(Endpoint)的维度展示各项指标。Database:展示数据库的各项指标。SelfObservability:展示OAP服务端的各项指标。Web Browser:以服务和页面的维度展示Web浏览器端的各项指标。相关概念解释:服务(Service):表示对请求提供相同行为的一组工作负载,比如:一个的 Web API系统。服务实例(Instance):上述的一组工作负载中的每一个工作负载称为一个实例,比如:一个的 Web API 系统集群中的一个实例。端点(Endpoint):对于特定服务所接收的请求路径,如 HTTP 的 URI 路径和 gRPC 服务的类名 + 方法签名。APM - 全局(Global)全局(Global)展示的是所有服务的各项指标,包括:吞吐量排名,单位为CPM(calls per minute,每分钟的调用次数)。服务响应时间排名,单位为毫秒。不健康服务排名,单位为Apdex(Application Performance Index,应用性能指数)。端点响应时间排名,单位为毫秒。响应时间百分位,包括 p99, p95, p90, p75, p50,单位为毫秒。响应时间热力图,单位为毫秒。相关概念解释:Apdex:Application Performance Index,应用性能指数, Apdex = (满意样本数 + 可容忍样本数 * 0.5) / 样本总数,满意样本为响应时间小等于Apdex阈值,可容忍样本为响应时间大于Apdex阈值并小等于4倍的Apdex阈值。目前Apdex阈值为0.5秒。APM - 服务(Service)服务(Service)是以服务的维度展示各项指标,包括:服务Apdex(Application Performance Index,应用性能指数)。服务平均响应时间,单位为毫秒。服务成功率。服务吞吐量,单位为CPM(calls per minute,每分钟的调用次数)。服务Apdex曲线图。服务百分位,包括 p99, p95, p90, p75, p50,单位为毫秒。服务成功率曲线图。服务吞吐量曲线图,单位为CPM(calls per minute,每分钟的调用次数)。端点吞吐量排名,单位为CPM(calls per minute,每分钟的调用次数)。端点响应时间排名,单位为毫秒。端点成功率排名。APM - 服务实例(Instance)服务实例(Instance)是以实例的维度展示各项指标,包括:实例吞吐量,单位为CPM(calls per minute,每分钟的调用次数)。实例成功率。实例平均响应时间,单位为毫秒。JVM的CPU使用百分比。JVM的内存使用情况。JVM的GC时间。JVM的GC次数。APM - 端点(Endpoint)端点(Endpoint)是以端点的维度展示各项指标,包括:端点吞吐量排名,单位为CPM(calls per minute,每分钟的调用次数)。端点平均响应时间排名,单位为毫秒。端点成功率排名。端点吞吐量曲线图,单位为CPM(calls per minute,每分钟的调用次数)。端点平均响应时间曲线图,单位为毫秒。端点百分位,包括 p99, p95, p90, p75, p50,单位为毫秒。端点成功率曲线图。Database展示数据库(Database)相关的各项指标,包括:数据库平均响应时间,单位为毫秒。数据库访问成功率。数据库吞吐量,单位为CPM(calls per minute,每分钟的调用次数)。数据库访问百分位,包括 p99, p95, p90, p75, p50,单位为毫秒。慢查询列表,单位为毫秒。所有数据库吞吐量排名,单位为CPM(calls per minute,每分钟的调用次数)。所有数据库成功率排名。拓扑图拓扑图可以显示服务之间的拓扑关系,如下图:服务选择器,可以选择某个服务,显示其直接关系,包括上游和下游。自定义组,可以创建自定义的任意一组服务,用于显示其一组服务的拓扑图。点击某些服务的图标,可查看该服务的类型、Apdex、成功率、响应时间、吞吐量、百分位等信息,如下图:点击服务之间的连线,可查看两个服务之间的响应时间、吞吐量、成功率、百分位等信息,如下图:点击上图中的展示实例依赖按钮,可查看各个实例之间的响应时间、吞吐量、成功率、百分位等信息,如下图:追踪追踪页面可以查询到某个分布式链路的整体情况,如下图:搜索条件设置,支持按服务、实例、端点名称、追踪ID、时间范围等条件进行查询。片段(Segment)列表,点击某个片段(Segment),在右侧展示与片段(Segment)相关的整个追踪(Trace)。服务列表,是这个追踪(Trace)涉及的所有服务,每个服务用不同的颜色展示。跨度(Span)列表,是这个追踪(Trace)涉及的所有跨度(Span),还可以看到每个跨度(Span)耗时和层级关系。点击某个跨度(Span),可以看到它的等服务名称、端点名称信息。追踪(Trace)视图设置,提供3种视图展示追踪(Trace):列表、树结构、表格。相关概念解释:追踪(Trace):一个追踪(Trace)表示一个事务或者流程在分布式系统中的执行过程,是一条完整的分布式调用链。跨度(Span):一个跨度(Span)表示系统中具有开始时间和执行时长的逻辑运行单元。跨度(Span)之间通过嵌套或者顺序排列建立逻辑因果关系,最终形成一个追踪(Trace)。片段(Segment):一个片段(Segment)表示同一端点内的一组跨度(Span)的集合。常见的错误可能是由代码异常或网络故障引起的,通过追踪(Trace)视图提供的跨度(Span)细节,可以快速找到错误发生在哪个环节。性能刨析性能剖析是利用方法栈快照,并对方法执行情况进行分析和汇总,对代码执行速度进行估算。性能剖析激活时,会对指定线程周期性的进行线程栈快照,并将所有的快照进行汇总分析,如果两个连续的快照含有同样的方法栈,则说明此栈中的方法大概率在这个时间间隔内都处于执行状态。从而,通过这种连续快照的时间间隔累加成为估算的方法执行时间。创建任务想要进行性能刨析,我们必须创建一个任务,如下图:选择指定的服务。输入端点名称,这里的端点名称通常是第一个片段(Segment)的操作名,在追踪页面的追踪(Trace)视图里可以找到。选择监控时间,可以从现在开始,也可以从未来的任何时间开始。选择监视持续时间,可以设置监视的时间窗口,以查找到合适的请求进行性能刨析。监控间隔,提供了一个过滤器机制,如果给定端点响应的请求很快,它就不会性能刨析,可以确保性能刨析的数据是预期的数据。最大采样数,表示探针收集的最大数据集,它有助于减少内存和网络负载。即使性能刨析对目标系统的性能影响非常有限,但它仍然是一个额外的负载,以上设置可以使性能影响可控。另外,在任何时刻,每个服务只能执行一个性能刨析任务。分析结果等待性能刨析的任务完成后,对应的片段(Segment)就会在右侧展示出来。点击某个片段(Segment),可以更详细地看到各个片段(Segment)的耗时,如下图:从上图可以看到最慢的片段(Segment)。点击分析按钮,可以看到基于方法栈的分析结果,包括对应的类名、方法名、代码行数、耗时等信息,最慢的方法栈被高亮显示,如下图:性能剖析的优势精确的问题定位,直接找到代码方法和代码行;无需反复的增删埋点,大大减少了人力开发成本;不用承担过多埋点对目标系统和监控系统的压力和性能风险;按需使用,平时对系统无消耗,使用时的消耗稳定可控。告警在告警页面可以查看所有触发的告警,如下图:过滤范围的设置包括:服务、服务实例、端点、服务关系、服务实例关系、端点关系等。

手把手带你使用Paint in 3D和Photon撸一个在线涂鸦画板

Paint in 3DPaint in 3D用于在游戏内和编辑器里绘制所有物体。所有功能已经过深度优化,在WebGL、移动端、VR 以及更多平台用起来都非常好用!它支持标准管线,以及 LWRP、HDRP 和 URP。通过使用GPU 加速,你的物体将以难以置信的速度被绘制。代码还经过深度优化来防止GC,和将所有绘制操作一起批次完成。跟贴图系统不同,它是一个纹理绘制解决方案。这意味着你可以绘制你的物体上百万次,还是无帧率丢失,让你创作难以想象的游戏。它在Unity应用商店上的售价是60美元,地址:https://assetstore.unity.com/packages/tools/painting/paint-in-3d-26286。PhotonPhoton中文翻译为“光子”,为有着15年服务器后端开发经验的德国Exit Games公司开发的高效,稳定,可拓展的网络引擎。为目前世界上用户最广泛,支持游戏类型最多的专业网络引擎之一,也是Unity应用商店里用户评价最高的网络组件。世界多个知名游戏公司和工作室选用Photon作为其产品的网络支持引擎,其中包括WB华纳,Codemaster, 2K, Glu, 微软游戏工作室,史克威尔艾尼克斯,百代南梦宫,SandBox,雨神电竞等知名企业,也有许多工作室和新创企业正在了解和试用Photon之中。它在Unity应用商店上有一个免费应用,地址:https://assetstore.unity.com/packages/tools/network/pun-2-free-119922。当然,Photon需要注册账号、创建应用等操作才能使用,还不了解的同学可以去官方网站查阅相关资料。温馨提示:Photon的国外服务器在国内使用比较卡,所以最好去中国官网申请国内的服务器,申请地址:https://vibrantlink.com/chinacloudapply/。下面正式开始。创建工程使用Unity Hub创建一个3D项目,然后分别引入Paint in 3D和Photon Unity Networking 2,如下图:温馨提示:在引入Photon Unity Networking 2后,记得配置AppId。创建简易画板为了方便演示,我们创建一个Quad作为画板,然后为其添加P3dPaintable、P3dMaterialCloner和P3dPaintableTexture组件,使用它们的默认配置即可,如下图:然后,创建一个空的GameObject命名为OneMorePaint,然后向OneMorePaint添加P3dPaintSphere组件,修改P3dPaintSphere组件的Color为红色,其他配置保持默认不变,如下图:再向OneMorePaint添加P3dHitScreen组件,勾选上P3dHitScreen组件的ConnectHits,其他配置保持默认不变,如下图:这时候,创建简易画板就做好了,运行以后就可以画画了,如下图:只不过,还是个单机版,我们加上实时在线功能。连接PUN2服务器创建一个C#脚本命名为Launcher,再创建一个空的GameObject命名为LauncherGameObject,把C#脚本Launcher添加到LauncherGameObject中。编辑C#脚本Launcher为如下内容:using Photon.Pun; using Photon.Realtime; using UnityEngine; namespace One.More public class Launcher : MonoBehaviourPunCallbacks #region Private Fields /// <summary> /// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes). /// </summary> string gameVersion = "1"; /// <summary> /// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon, /// we need to keep track of this to properly adjust the behavior when we receive call back by Photon. /// Typically this is used for the OnConnectedToMaster() callback. /// </summary> bool isConnecting; #endregion void Start() this.Connect(); #region MonoBehaviourPunCallbacks Callbacks public override void OnConnectedToMaster() Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN"); // we don't want to do anything if we are not attempting to join a room. // this case where isConnecting is false is typically when you lost or quit the game, when this level is loaded, OnConnectedToMaster will be called, in that case // we don't want to do anything. if (isConnecting) // #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed() PhotonNetwork.JoinRandomRoom(); isConnecting = false; public override void OnDisconnected(DisconnectCause cause) Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause); isConnecting = false; public override void OnJoinRandomFailed(short returnCode, string message) Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom"); // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room. PhotonNetwork.CreateRoom(null, new RoomOptions()); public override void OnJoinedRoom() Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room."); #endregion #region Public Methods /// <summary> /// Start the connection process. /// - If already connected, we attempt joining a random room /// - if not yet connected, Connect this application instance to Photon Cloud Network /// </summary> public void Connect() // we check if we are connected or not, we join if we are , else we initiate the connection to the server. if (PhotonNetwork.IsConnected) // #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one. PhotonNetwork.JoinRandomRoom(); // #Critical, we must first and foremost connect to Photon Online Server. isConnecting = PhotonNetwork.ConnectUsingSettings(); PhotonNetwork.GameVersion = gameVersion; #endregion }这时候,就可以连接到连接PUN2服务器了,运行以后我们可以看到如下日志:实时在线同步向之前创建的OneMorePaint添加PhotonView组件,使用默认配置即可,如下图:创建一个C#脚本命名为OnlinePainting,把C#脚本OnlinePainting添加到OneMorePaint中。编辑C#脚本OnlinePainting为如下内容:using PaintIn3D; using Photon.Pun; using UnityEngine; namespace One.More public class OnlinePainting : MonoBehaviour, IHitPoint, IHitLine private PhotonView photonView; private P3dPaintSphere paintSphere; void Start() this.photonView = this.GetComponent<PhotonView>(); this.paintSphere = this.GetComponent<P3dPaintSphere>(); public void HandleHitPoint(bool preview, int priority, float pressure, int seed, Vector3 position, Quaternion rotation) if (preview) return; if (this.photonView == null) Debug.LogError("PhotonView is not found."); return; this.photonView.RPC("HandleHitPointRpc", RpcTarget.Others, preview, priority, pressure, seed, position, rotation); public void HandleHitLine(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Quaternion rotation, bool clip) if (preview) return; if (this.photonView == null) Debug.LogError("PhotonView is not found."); return; this.photonView.RPC("HandleHitLinetRpc", RpcTarget.Others, preview, priority, pressure, seed, position, endPosition, rotation, clip); [PunRPC] public void HandleHitPointRpc(bool preview, int priority, float pressure, int seed, Vector3 position, Quaternion rotation) if (this.paintSphere == null) Debug.LogError("P3dPaintSphere is not found."); return; this.paintSphere.HandleHitPoint(preview, priority, pressure, seed, position, rotation); [PunRPC] public void HandleHitLinetRpc(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Quaternion rotation, bool clip) if (this.paintSphere == null) Debug.LogError("P3dPaintSphere is not found."); return; this.paintSphere.HandleHitLine(preview, priority, pressure, seed, position, endPosition, rotation, clip); }在线涂鸦画板就制作完成了,我们看看运行效果怎么样?运行效果构建以后,同时启动两个客户端,效果如下:当然,这只是简单的在线涂鸦画板,你还可以再此基础上添加更丰富的功能,比如:修改画笔颜色、修改画笔大小等等。最后,谢谢你这么帅,还给我点赞和关注。

老徐和阿珍的故事:缓存穿透、缓存击穿、缓存雪崩、缓存热点,傻傻分不清楚

人物背景:老徐,男,本名徐福贵,从事Java相关研发工作多年,职场老油条,摸鱼小能手,虽然岁数不大但长的比较着急,人称老徐。据说之前炒某币败光了所有家产,甚至现在还有欠债。阿珍,女,本名陈家珍,刚刚入职不久的实习生,虽然是职场菜鸟但聪明好学。据说是学校的四大校花之一,追求她的人从旺角排到了铜锣湾,不过至今还单身。阿珍:“在高并发下遇到瓶颈的时候,经常会用到缓存来提高整个系统的性能。”老徐:“嗯,不过缓存能够大大提升整个系统的性能,但同时也引入了更多复杂性。”阿珍点了点头,说:“是啊,缓存穿透、缓存击穿、缓存雪崩、缓存热点这些东西,这些东西我一直分不清楚,经常混淆。”老徐立刻自信满满地说:“这个我懂啊,你听我给你娓娓道来。”缓存穿透缓存穿透是指在查询缓存数据时,缓存和数据库中都没有对应数据,在缓存中找不到对应的数据,每次都要去数据库中再查询一遍,然后返回数据不存在。在这个场景中,缓存并没有起到分担数据库访问压力的作用。读取不存在的数据的请求量一般不会太大,但如果出现一些恶意攻击,故意大量访问某些不存在的数据,就会对数据库造成很多压力。阿珍:“太可怕了,万一遇到了这样攻击,该怎么办呀?”老徐:“这个很好应对的,一般有两种办法。”第一个是:如果查询数据库中的数据没有找到,则直接设置一个特定值存到缓存中。之后读取缓存时就会获取到这个特定值,直接返回空值,就不会继续访问数据库了。第二个是:把已存在数据的key存放在布隆过滤器中。当有新的请求时,先到布隆过滤器中查询是否存在,如果不存在该条数据直接返回;如果存在该条数据再查询缓存查询数据库。缓存击穿缓存击穿是指在查询缓存数据时,数据库原本有得数据,但是缓存中没有,生成缓存数据需要耗费较长时间或者大量资源,这时候如果有大量请求该数据,会对数据库甚至系统造成较大压力。阿珍:“哦?该怎么解决呀?”老徐:“这个很好解决,一般有两个做法。”第一个是:对缓存更新操作加入锁的保护,保证只有一个线程能够进行缓存更新的操作,没有获取更新锁的线程要么等待锁释放后重新读取缓存,要么直接返回空值或者默认值。第二个是:后台作业定时更新缓存,而不是在访问页面时生成缓存数据。这样可以按照一定策略定时更新缓存,不会对存储系统较大的瞬时压力。缓存雪崩缓存雪崩是指当大量缓存同时失效或过期后,大量请求直接访问对数据库,甚至耗费较长时间或者大量资源计算缓存结果,引起系统性能的急剧下降。阿珍抢先说道:“这个我知道怎么解决!”老徐反问:“怎么解决?”阿珍回答:“同一类型的缓存的过期时间可以设置一个随机值,比如:原来的过期时间是5分钟,在此基础上加0~60秒,那么过期时间就变为在5~6分钟内波动,有效防止都在同一个时间点上大量缓存过期。”缓存热点缓存热点是指大部分甚至所有的业务请求都命中同一份缓存数据。虽然缓存本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也会很大。比如,电商的爆品秒杀活动,短时间内被上千万的用户访问。阿珍:“遇到了这种情况,该怎么办呀?”老徐:“这个很好解决的,一般有两种办法:复制多份缓存副本和本地内存缓存。”复制多份缓存副本,就是将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。在设计缓存副本的时候,有一个细节需要注意:不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存的雪崩效应。把热点数据缓存在客户端的本地内存中,并且设置一个失效时间。对于每次读请求,将首先检查该数据是否存在于本地缓存中,如果存在则直接返回,如果不存在再去访问分布式缓存的服务器。阿珍用崇拜的眼神看着老徐,说:“老徐,你太牛了,什么都懂!”老徐不好意思地挠了挠头,说:“也没有了。”最后,谢谢你这么帅,还给我点赞和关注。

老徐和阿珍的故事:ArrayList和LinkedList的效率到底哪个高?

人物背景:老徐,男,本名徐福贵,从事Java相关研发工作多年,职场老油条,摸鱼小能手,虽然岁数不大但长的比较着急,人称老徐。据说之前炒某币败光了所有家产,甚至现在还有欠债。阿珍,女,本名陈家珍,刚刚入职不久的实习生,虽然是职场菜鸟但聪明好学。据说是学校的四大校花之一,追求她的人从旺角排到了铜锣湾,不过至今还单身。老徐问道:“阿珍,你知道ArrayList和LinkedList的区别吗?”阿珍微微一笑,说:“这也太小儿科了,ArrayList是基于数组实现,LinkedList是基于链表实现。”老徐竖起了大拇指,说:“不错,有进步!那你知道ArrayList和LinkedList的效率到底哪个高?”阿珍回答:“这也难不倒我,这要分不同情况的。在新增、删除元素时,LinkedList的效率要高于ArrayList,而在遍历的时候,ArrayList的效率要高于LinkedList。”老徐反问到:“不一定哦。在新增、删除元素时,LinkedList的效率有可能低于ArrayList,而在遍历的时候,ArrayList的效率有可能低于LinkedList。”阿珍回答:“不可能,绝对不可能,书上都是这么写的。”老徐得意地笑了,说:“实践是检验真理的唯一标准。趁着老板不在,咱两写个程序实践一下。”ArrayList和LinkedList的新增元素对比首先,写一段计算新增元素耗时的代码:/** * 从List的头部新增元素 * @param list list * @param count 新增元素的个数 * @return 所耗费的时间(单位:ms) public static long addHeader(List<String> list, int count) { long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.add(0, "onemore-" + i); long end = System.nanoTime(); return (end - start) / 1000000; * 从List的中部新增元素 * @param list list * @param count 新增元素的个数 * @return 所耗费的时间(单位:ms) public static long addMiddle(List<String> list, int count) { long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.add(list.size() / 2, "onemore-" + i); long end = System.nanoTime(); return (end - start) / 1000000; * 从List的尾部新增元素 * @param list list * @param count 新增元素的个数 * @return 所耗费的时间(单位:ms) public static long addTail(List<String> list, int count) { long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.add("onemore-" + i); long end = System.nanoTime(); return (end - start) / 1000000; }然后,我们把新增元素的个数设置为50000,对比一下:public static void main(String[] args) { int count = 50000; System.out.println("从ArrayList的头部新增元素:" + addHeader(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的头部新增元素:" + addHeader(new LinkedList<>(), count) + "ms"); System.out.println("从ArrayList的中部新增元素:" + addMiddle(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的中部新增元素:" + addMiddle(new LinkedList<>(), count) + "ms"); System.out.println("从ArrayList的尾部新增元素:" + addTail(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的尾部新增元素:" + addTail(new LinkedList<>(), count) + "ms"); }运行结果如下:ArrayList从头部新增元素:204ms LinkedList从头部新增元素:17ms ArrayList从中部新增元素:71ms LinkedList从中部新增元素:8227ms ArrayList从尾部新增元素:13ms LinkedList从尾部新增元素:21ms我们可以看出,从头部新增元素时,ArrayList的效率低于LinkedList;从中部新增元素时,ArrayList的效率高于LinkedList;从尾部新增元素时,ArrayList的效率高于LinkedList。阿珍惊呆了,说:“怎么可能?这是为什么呀?”老徐回答:“我来帮你简单分析一下。”ArrayList是基于数组实现的,在添加元素到数组头部的时候,在添加元素之前需要把头部以后的元素一个一个地往后挪,所以效率很低;而LinkedList是基于链表实现,从头部添加元素的时候,通过头部指针就可以直接添加,所以效率很高。ArrayList在添加元素到数组中间的时候,同样有部分元素需要一个一个地往后挪,所以效率也不是很高;而LinkedList从中部添加元素的时候,是添加元素最低效率的,因为靠近中间位置,在添加元素之前需要循环查找遍历部分元素,所以效率很低。ArrayList从尾部添加元素的时候,不需要挪动任何元素,直接把元素放入数组,效率非常高。而LinkedList虽然不需要循环查找遍历元素,但LinkedList中多了实列化节点对象和变换指针指向的过程,所以效率较低一些。当然,这里有一个大前提,就是ArrayList初始化容量足够,不需要动态扩容数组容量。所以,在我们的日常开发中,最好指定ArrayList的初始化容量。ArrayList和LinkedList的删除元素对比首先,写一段计算删除元素耗时的代码:/** * 从List的头部删除元素 * @param list list * @param count 删除元素的个数 * @return 所耗费的时间(单位:ms) public static double deleteHeader(List<String> list, int count) { for (int i = 0; i < count; i++) { list.add("onemore-" + i); long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.remove(0); long end = System.nanoTime(); return (end - start) / 1000000.0; * 从List的中部删除元素 * @param list list * @param count 删除元素的个数 * @return 所耗费的时间(单位:ms) public static double deleteMiddle(List<String> list, int count) { for (int i = 0; i < count; i++) { list.add("onemore-" + i); long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.remove(list.size() / 2); long end = System.nanoTime(); return (end - start) / 1000000.0; * 从List的尾部删除元素 * @param list list * @param count 删除元素的个数 * @return 所耗费的时间(单位:ms) public static double deleteTail(List<String> list, int count) { for (int i = 0; i < count; i++) { list.add("onemore-" + i); long start = System.nanoTime(); for (int i = 0; i < count; i++) { list.remove(list.size() - 1); long end = System.nanoTime(); return (end - start) / 1000000.0; }然后,我们把删除元素的个数设置为50000,对比一下:public static void main(String[] args) { int count = 50000; System.out.println("从ArrayList的头部删除元素:" + deleteHeader(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的头部删除元素:" + deleteHeader(new LinkedList<>(), count) + "ms"); System.out.println("从ArrayList的中部删除元素:" + deleteMiddle(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的中部删除元素:" + deleteMiddle(new LinkedList<>(), count) + "ms"); System.out.println("从ArrayList的尾部删除元素:" + deleteTail(new ArrayList<>(count), count) + "ms"); System.out.println("从LinkedList的尾部删除元素:" + deleteTail(new LinkedList<>(), count) + "ms"); }运行结果如下:从ArrayList的头部删除元素:260.7014ms 从LinkedList的头部删除元素:14.2948ms 从ArrayList的中部删除元素:95.9073ms 从LinkedList的中部删除元素:3602.6931ms 从ArrayList的尾部删除元素:1.6261ms 从LinkedList的尾部删除元素:3.9645ms我们可以看出,从头部删除元素时,ArrayList的效率低于LinkedList;从中部删除元素时,ArrayList的效率高于LinkedList;从尾部删除元素时,ArrayList的效率高于LinkedList。阿珍抢着说:“删除元素这个我知道,和新增元素的原理差不多。”老徐回答:“既然你知道了,我就不啰嗦了,我们继续看遍历元素。”ArrayList和LinkedList的遍历元素对比遍历元素一般有两种方式:for循环和foreach,写一段计算这两种遍历方式耗时的代码:/** * 通过for循环遍历List * @param list list * @param count 遍历元素的个数 * @return 所耗费的时间(单位:ms) public static double getByFor(List<String> list, int count) { for (int i = 0; i < count; i++) { list.add("onemore-" + i); String name = "万猫学社"; long start = System.nanoTime(); for (int i = 0; i < count; i++) { if (name.equals(list.get(i))) { System.out.println(name); long end = System.nanoTime(); return (end - start) / 1000000.0; * 通过foreach遍历List * @param list list * @param count 遍历元素的个数 * @return 所耗费的时间(单位:ms) public static double getByForeach(List<String> list, int count) { for (int i = 0; i < count; i++) { list.add("onemore-" + i); String name = "万猫学社"; long start = System.nanoTime(); for (String str : list) { if (name.equals(str)) { System.out.println(name); long end = System.nanoTime(); return (end - start) / 1000000.0; }然后,我们把遍历元素的个数设置为50000,对比一下:public static void main(String[] args) { int count = 50000; System.out.println("通过for循环遍历ArrayList:" + getByFor(new ArrayList<>(count), count) + "ms"); System.out.println("通过for循环遍历LinkedList:" + getByFor(new LinkedList<>(), count) + "ms"); System.out.println("通过foreach遍历ArrayList:" + getByForeach(new ArrayList<>(count), count) + "ms"); System.out.println("通过foreach遍历LinkedList:" + getByForeach(new LinkedList<>(), count) + "ms"); }运行结果如下:通过for循环遍历ArrayList:3.4403ms 通过for循环遍历LinkedList:3563.1219ms 通过foreach遍历ArrayList:3.7388ms 通过foreach遍历LinkedList:3.7953ms我们可以看到,通过for循环遍历时,ArrayList的效率高于LinkedList,而且LinkedList的效率极低;通过foreach遍历时,ArrayList的效率和LinkedList相差不大。老徐:“阿珍,你知道为什么for循环遍历LinkedList的效率那么低吗?”阿珍:“因为LinkedList基于链表实现的,每一次for循环都要遍历找到对应的节点,所以严重影响了遍历的效率;而ArrayList直接可以通过数组下标直接找到对应的元素,所以for循环效率非常高。对不对?”老徐:“是的,所以我们不要使用for循环遍历LinkedList。”总结ArrayList是基于数组实现,LinkedList是基于链表实现。在ArrayList初始化容量足够的情况下,从头部新增元素时,ArrayList的效率低于LinkedList;从中部新增元素时,ArrayList的效率高于LinkedList;从尾部新增元素时,ArrayList的效率高于LinkedList。从头部删除元素时,ArrayList的效率低于LinkedList;从中部删除元素时,ArrayList的效率高于LinkedList;从尾部删除元素时,ArrayList的效率高于LinkedList。通过for循环遍历时,ArrayList的效率高于LinkedList,而且LinkedList的效率极低;通过foreach遍历时,ArrayList的效率和LinkedList相差不大。最后,谢谢你这么帅,还给我点赞和关注。

老徐和阿珍的故事:强引用、软引用、弱引用、虚引用,傻傻分不清楚

人物背景:老徐,男,本名徐福贵,从事Java相关研发工作多年,职场老油条,摸鱼小能手,虽然岁数不大但长的比较着急,人称老徐。据说之前炒某币败光了所有家产,甚至现在还有欠债。阿珍,女,本名陈家珍,刚刚入职不久的实习生,虽然是职场菜鸟但聪明好学。据说是学校的四大校花之一,追求她的人从旺角排到了铜锣湾,不过至今还单身。阿珍:“老徐,你这茶杯了泡的什么?红红的。”老徐:“这是枸杞呀。”阿珍:“枸杞?你最近什么干多了,这么虚!”老徐:“怎么可能?看我这身体,不弱的好吧!”阿珍一脸坏笑地说:“那就是软了。”老徐的老脸一红,辩解到:“我这是养养生,我很强的,好吧。”看着老徐的窘态,阿珍笑出来声。老徐起身刚要走,阿珍一把拽住老徐,说:“跟你开玩笑呢,问你个正事,我一直分不清Java的强引用、软引用、弱引用、虚引用,给我讲讲呗。”老徐立刻自信满满的坐下,说:“那你可问对人了,我对这方面颇有研究。这四种引用级别由高到低依次是:强引用、软引用、弱引用、虚引用。”强引用(StrongReference)强引用是Java中最常见的引用方式,99.99%用的都是强引用。我们创建了一个对象,并把它赋值给某一个变量,我们就可以通过这个变量操作实际的对象了,比如:String name = "万猫学社"; System.out.println(name);当一个对象被一个或者多个变量强引用时,它就是处于一个可达状态,不会被垃圾回收机制回收掉。即使在内存不够的情况下,Java虚拟机宁愿抛出OutOfMemoryError异常,也不会回收这样的对象。软引用(SoftReference)软引用是通过SoftReference类进行实现的,当一个对象只有软引用的时候,Java虚拟机的垃圾回收机制运行后,当内存空间足够时,它就不会被回收掉;当内存空间不够时,它就会被回收掉。比如:SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 5]); System.out.println("垃圾回收前:" + softReference.get()); //建议Java虚拟机执行垃圾回收 System.gc(); System.out.println("内存足够时,垃圾回收后:" + softReference.get()); byte[][] bytes = new byte[10][]; for (int i = 0; i < 10; i++) { bytes[i] = new byte[1024 * 1024 * 1]; System.out.println("内存不足时,垃圾回收后:" + softReference.get());在运行时加入-Xmx15M (设置Java堆的最大内存为15M)和-XX:+PrintGC(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:垃圾回收前:[B@1de0aca6 [GC (System.gc()) 9173K->6495K(15872K), 0.0033951 secs] [Full GC (System.gc()) 6495K->6434K(15872K), 0.0149312 secs] 内存足够时,垃圾回收后:[B@1de0aca6 [GC (Allocation Failure) 9588K->9570K(15872K), 0.0013485 secs] [Full GC (Ergonomics) 9570K->9506K(15872K), 0.0032467 secs] [Full GC (Ergonomics) 12659K->12549K(15872K), 0.0083257 secs] [Full GC (Ergonomics) 13573K->13573K(15872K), 0.0043525 secs] [Full GC (Allocation Failure) 13573K->8435K(15872K), 0.0065695 secs] 内存不足时,垃圾回收后:null可以看到,当内存空间足够时,软引用的对象不会被回收掉;当内存空间不够时,软引用的对象就会被回收掉。弱引用(WeakReference)弱引用是通过WeakReference类进行实现的,弱引用和软引用很类似,但是比软引用的级别更低。当一个对象只有弱引用的时候,Java虚拟机的垃圾回收机制运行后,无论内存是否足够,它都会被回收掉。比如:WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1024 * 1024 * 5]); System.out.println("垃圾回收前:" + weakReference.get()); //建议Java虚拟机执行垃圾回收 System.gc(); System.out.println("内存足够时,垃圾回收后:" + weakReference.get());同样的,在运行时加入-Xmx15M (设置Java堆的最大内存为15M)和-XX:+PrintGC(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:垃圾回收前:[B@1de0aca6 [GC (System.gc()) 9150K->6481K(15872K), 0.0015689 secs] [Full GC (System.gc()) 6481K->1317K(15872K), 0.0062846 secs] 内存足够时,垃圾回收后:null可以看到,即使在内存足够的时候,弱引用的对象也会被回收掉。虚引用(PhantomReference)虚引用通过PhantomReference类进行实现的,虚引用完全类似于没有引用。如果一个对象只有一个虚引用,那么它就是和没有引用差不多。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,必须和引用队列(ReferenceQueue)一起使用。我们不能通过虚引用获取到被引用的对象,只有在该对象被回收后,该对象的虚引用会被放到和虚引用关联的引用队列中,比如:ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference<byte[]> phantomReference = new PhantomReference<>(new byte[1024 * 1024 * 5], referenceQueue); System.out.println("垃圾回收前:" + phantomReference.get()); byte[][] bytes = new byte[10][]; for (int i = 0; i < 5; i++) { bytes[i] = new byte[1024 * 1024 * 1]; System.out.println("垃圾回收后:" + referenceQueue.poll());同样的,在运行时加入-Xmx15M (设置Java堆的最大内存为15M)和-XX:+PrintGC(开启垃圾回收的日志打印)参数,我们就可以看到下面的结果:垃圾回收前:null [GC (Allocation Failure) 9068K->6517K(15872K), 0.0019272 secs] [GC (Allocation Failure) 9713K->9621K(15872K), 0.0015966 secs] [Full GC (Ergonomics) 9621K->9506K(15872K), 0.0092758 secs] 垃圾回收后:java.lang.ref.PhantomReference@1de0aca6可以看到,不能通过虚引用获取到被引用的对象,在该对象被回收后,可以从引用队列中获取对应的虚引用。老徐看着阿珍一脸懵逼的样子说:“小朋友,你是不是有很多问号?”“信息量有点大,我得慢慢消化消化。”阿珍回答到。老徐说:“没关系,我给你简单总结一下,很方便理解和记忆。”总结强引用:Java中最常见的引用方式,即使内存不足也不会被垃圾回收。软引用:当内存不足时,垃圾回收机制运行后对象被回收。弱引用:无论内存是否足够,垃圾回收机制运行后对象被回收。虚引用:主要用于跟踪对象被垃圾回收的状态,必须和引用队列一起使用。最后,谢谢你这么帅,还给我点赞和关注。

老徐和阿珍的故事:CAP是什么?超级爱放P吗?

人物背景:老徐,男,本名徐福贵,从事Java相关研发工作多年,职场老油条,摸鱼小能手,虽然岁数不大但长的比较着急,人称老徐。据说之前炒某币败光了所有家产,甚至现在还有欠债。阿珍,女,本名陈家珍,刚刚入职不久的实习生,虽然是职场菜鸟但聪明好学。据说是学校的四大校花之一,追求她的人从旺角排到了铜锣湾,不过至今还单身。阿珍吃完饭刚刚回来,看到老徐正在吃方便面,说:“老徐,不至于吧。你为了还债,中午就吃这个呀?”老徐不慌不忙地嚼着方便面,说:“这可是白象的方便面,国货之光,超级难买,我好不容易才买到的。”阿珍坐了下来,说:“闻着还挺香,我也买点尝尝。对了,老徐,上午开会的时候,听你们总说CAP、CAP的,CAP是什么意思?超级爱放P吗?”老徐嘴里方便面差点喷了出来,笑着说:“第一次听你这么解释的,来!我给你科普一下。”CAP 是加州大学伯克利分校(University of California at Berkeley)的计算机教授埃里克·布鲁尔(Eric A. Brewer)在2000年的分布式计算原理研讨会(PODC,Symposium on Principles of Distributed Computing)上提出的一个猜想。2002 年,麻省理工学院(MIT,Massachusetts Institute of Technology)的赛斯·吉尔伯特(Seth Gilbert)和南希·林奇(Nancy Lynch)发表了对这个猜想的证明,使之成为分布式计算领域公认的一个定理。CAP定理是指在一个互相连接并共享数据的节点的分布式系统中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。CAP,是Consistence、Availability、Partition三个英文单词的首字母缩写。其中,一致性是指对某个指定的客户端来说,读操作保证能够返回最新的写操作结果;可用性是指非故障的节点在合理的时间内返回不是错误和超时的合理响应。分区容忍性是指当出现网络分区后,系统能够继续运行。因为网络本身无法做到100%可靠,有可能出故障,分区是一个必然的现象,所以我们必须选择分区容忍性(P)。在分布式系统中不可能选择CA(一致性和可用性),只能选择CP(一致性和分区容错性)或者AP(可用性和分区容错性)。当选择CP时,发生分区现象后,为了保证数据的一致性,客户端访问任意节点都会被返回错误,所以不能满足可用性,比如ZooKeeper。当选择AP时,发生分区现象后,为了保证节点的可用性,客户端可以访问某个节点上可能过时的数据,所以不能满足一致性,比如Eureka。阿珍用手捂着嘴打了一个哈欠,眨了眨还有泪花的眼睛,说:“这理论性也太强了吧,还是说说你的白象方便面在哪买的吧。”最后,谢谢你这么帅,还给我点赞和关注。

老徐和阿珍的故事:Runnable和Callable有什么不同?

人物背景:老徐,男,本名徐福贵,从事Java相关研发工作多年,职场老油条,摸鱼小能手,虽然岁数不大但长的比较着急,人称老徐。据说之前炒某币败光了所有家产,甚至现在还有欠债。阿珍,女,本名陈家珍,刚刚入职不久的实习生,虽然是职场菜鸟但聪明好学。据说是学校的四大校花之一,追求她的人从旺角排到了铜锣湾,不过至今还单身。阿珍探出头看了看老徐的屏幕,全部都是绿色的曲线图,好奇地问:“老徐,你看的这是什么?”老徐看的太入神,转过头才发现阿珍,尬尴地笑了笑说:“我就是看看最近的行情。”老徐立马切换了窗口。阿珍没在意又继续问到:“Runnable和Callable两个接口我总搞混,这个到底有什么不同?”面对阿珍的灵魂拷问,老徐淡定自若地说:“Runnable是用于提供多线程任务支持的核心接口,Callable是在Java 1.5中添加的Runnable的改进版本。”“在聊它们不同之前,我们先分别了解一下两个接口。”老徐一边说着,一边打开了源码:Runnable接口@FunctionalInterface public interface Runnable { public abstract void run(); }Runnable接口是一个函数式接口,它只有一个run()方法,不接受任何参数,也不返回任何值。由于方法签名没有指定throws子句,因此无法进一步传播已检查的异常。它适用于我们不使用线程执行结果的情况,例如,异步打印日志:package one.more; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingTask implements Runnable { private static Logger logger = LoggerFactory.getLogger(LoggingTask.class); private String name; public LoggingTask(String name) { this.name = name; @Override public void run() { logger.info("{}说:你好!", this.name); }在上面例中,根据name参数把信息记录在日志文件中,没有返回值。我们可以通过Thread启动,比如:public static void main(String[] args) { String name = "万猫学社"; Thread thread = new Thread(new LoggingTask(name)); thread.start();; }我们也可以通过ExecutorService启动,比如:public static void main(String[] args) { String name = "万猫学社"; ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new LoggingTask(name)); executorService.shutdown(); }Callable接口@FunctionalInterface public interface Callable<V> { V call() throws Exception; }Callable接口也是一个函数式接口,它只有一个call()方法,不接受任何参数,返回一个泛型值V,在方法签名上包含throws Exception子句,因此我们可以很容易地进一步传播已检查异常。它适用于我们使用线程执行结果的情况,例如,异步计算阶乘:package one.more; import java.util.concurrent.Callable; public class FactorialTask implements Callable<Integer> { private int n; public FactorialTask(int n) { this.n = n; @Override public Integer call() throws IllegalArgumentException { int fact = 1; if (n < 0) { throw new IllegalArgumentException("必须大于等于零"); for (int i = n; i > 1; i--) { fact = fact * i; return fact; }在上面例中,根据n参数计算它的阶乘,并可以返回计算结结果。我们只能通过ExecutorService启动,比如:public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Integer> future = executorService.submit(new FactorialTask(5)); System.out.println(future.get()); executorService.shutdown(); }call()方法的结果可以通过Future对象获取到,如果在调用Future对象的get()方法时,call()方法出现了异常,异常会被继续传递,比如:public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Integer> future = executorService.submit(new FactorialTask(-1)); System.out.println(future.get()); executorService.shutdown(); }抛出如下异常:老徐回头看看了阿珍,说:“这回你知道有什么不同了吧!”阿珍一头雾水地说:“信息量有点大呀,可以给我总结一下吗?”“当然可以。”老徐回答。总结Runnable和Callable的不同:Callable的任务执行后可返回值,Runnable的任务不能返回值。Callable只可以通过ExecutorService启动,Runnable可以通过Thread和ExecutorService启动。Callable的call()方法可以传播已检查异常,Runnable的run()方法不可以。最后,谢谢你这么帅,还给我点赞和关注。

Java的jstack命令使用详解

jstack命令简介jstack(Java Virtual Machine Stack Trace)是JDK提供的一个可以生成Java虚拟机当前时刻的线程快照信息的命令行工具。线程快照一般被称为threaddump或者javacore文件,是当前Java虚拟机中每个线程正在执行的Java线程、虚拟机内部线程和可选的本地方法堆栈帧的集合。对于每个方法栈帧,将会显示完整的类名、方法名、字节码索引(bytecode index,BCI)和行号。生成的线程快照可以用于定位线程出现长时间停顿的原因,比如:线程间死锁、死循环、请求外部资源被长时间挂起等等。jstack命令参数命令语法:jstack [options] pid命令参数说明:option:jstack命令的可选参数。如果没有指定这个参数,jstack命令会显示Java虚拟机当前时刻的线程快照信息,如下图:pid:要打印配置信息的Java虚拟机的进程ID。想要要获取运行的Java虚拟机进程的列表,可以使用ps命令(Linux系统中)或tasklist命令(Windows系统中),如果Java虚拟机进程没有在单独的docker实例中运行,可以使用jps命令。option都有哪些参数呢?我们来看一下。-F参数如果Java虚拟机进程由于进程挂起而没有任何响应,那么可以使用-F参数(仅在Oracle Solaris和Linux操作系统上游戏)强制显示线程快照信息。比如:-l参数如果使用-l参数,除了方法栈帧以外,jstack命令还会显示关于锁的附加信息,比如属于java.util.concurrent的ownable synchronizers列表。比如:-m参数如果使用-m参数,jstack命令将显示混合的栈帧信息,除了Java方法栈帧以外,还有本地方法栈帧。本地方法栈帧是C或C++编写的虚拟机代码或JNI/native代码。比如:在显示结果中,以星号为前缀的帧是Java方法栈帧,而不以星号为前缀的是本地方法栈帧。比如:-h 和 -help显示jstack命令的帮助信息。结尾虽然jstack命令已经推出很久并且使用频率比较搞,但它仍然是一个“实验性质的,并且没有技术支持的”(Experimental and Unsupported)工具,日后可能会被转正,也有可能在某个JDK版本中无声无息地消失。所以,且用且珍惜吧。不过,我们还可以使用Thread.getAllStackTracesgetAll()方法,获取Java虚拟机中所有线程的StackTraceElement对象,进而获得所有线程栈帧信息。最后,谢谢你这么帅,还给我点赞和关注。

Java的jmap命令使用详解

jmap命令简介jmap(Java Virtual Machine Memory Map)是JDK提供的一个可以生成Java虚拟机的堆转储快照dump文件的命令行工具。除此以外,jmap命令还可以查看finalize执行队列、Java堆和方法区的详细信息,比如空间使用率、当前使用的什么垃圾回收器、分代情况等等。和jinfo命令一样,在Windows系统上使用还是有一些限制的。在没有dbgeng.dll的Windows系统中,必须安装用于Windows的调试工具才能使jinfo命令正常工作,PATH环境变量应该包含jvm.dll的位置。jmap命令参数命令语法:jmap [options] pid命令参数说明:option:jmap命令的可选参数。如果没有指定这个参数,jinfo命令会显示Java虚拟机进程的内存映像信息,如下图:pid:要打印配置信息的Java虚拟机的进程ID。想要要获取运行的Java虚拟机进程的列表,可以使用ps命令(Linux系统中)或tasklist命令(Windows系统中),如果Java虚拟机进程没有在单独的docker实例中运行,可以使用jps命令。option都有哪些参数呢?我们来看一下。-heap显示Java堆的如下信息:被指定的垃圾回收算法的信息,包括垃圾回收算法的名称和垃圾回收算法的详细信息。堆的配置信息,可能是由命令行选项指定,或者由Java虚拟机根据服务器配置选择的。堆的内存空间使用信息,包括分代情况,每个代的总容量、已使用内存、可使用内存。如果某一代被继续细分(例如,年轻代),则包含细分的空间的内存使用信息。比如:-histo[:live]显示Java堆中对象的统计信息,包括:对象数量、占用内存大小(单位:字节)和类的完全限定名。比如:要获得某个对象的大小,可以将其总大小除以该对象类型的数量。如果指定了live参数,则只计算活动的对象。比如:-clstats显示Java堆中元空间的类加载器的统计信息,包括:class_loader:当Java虚拟机运行时,类加载器对象的地址classes:已加载类的数量 bytes:该类加载器加载的所有类的元数据所占的字节数parent_loader:父类加载器对象的地址,如果没有显示null。alive:是否存活的标识,表示类加载器对象是否将被垃圾回收。type:该类加载器的类名。比如:-finalizerinfo显示在F-Queue中等待Finalizer线程执行finalize方法的对象。比如:-dump:[live,]format=b,file=生成Java虚拟机的堆转储快照dump文件。具体说明如下:live参数是可选的,如果指定,则只转储堆中的活动对象;如果没有指定,则转储堆中的所有对象。format=b表示以hprof二进制格式转储Java堆的内存。file=<filename>用于指定快照dump文件的文件名。比如:-F强制模式。如果指定的pid没有响应,可以配合-dump或-histo一起使用。此模式下,不支持live参数。比如:-h 和 -help显示jinfo命令的帮助信息。最后,谢谢你这么帅,还给我点赞和关注。

Java的jinfo命令使用详解

jinfo命令简介jinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。在没有dbgeng.dll的Windows系统中,必须安装用于Windows的调试工具才能使jinfo命令正常工作,PATH环境变量应该包含jvm.dll的位置。jinfo命令参数命令语法:jinfo [option] pid命令参数说明:option:jinfo命令的可选参数。如果没有指定这个参数,jinfo命令会显示所有的配置参数和系统属性。pid:要打印配置信息的Java虚拟机的进程ID。想要要获取运行的Java虚拟机进程的列表,可以使用ps命令(Linux系统中)或tasklist命令(Windows系统中),如果Java虚拟机进程没有在单独的docker实例中运行,可以使用jps命令。option都有哪些参数呢?我们来看一下。-flag name显示指定名称对应的配置参数,比如,查看了简单GC日志模式(PrintGC)是否开启:# jinfo -flag PrintGC 15729 -XX:-PrintGC-flag [+|-]name启用或禁用指定名称的参数,该参数必须为Boolean类型。比如,开启简单GC日志模式:# jinfo -flag +PrintGC 15729 # jinfo -flag PrintGC 15729 -XX:+PrintGC比如,禁用简单GC日志模式:# jinfo -flag -PrintGC 15729 # jinfo -flag PrintGC 15729 -XX:-PrintGC-flag name=value不需要重启Java虚拟机,修改指定名称的参数为指定的值。比如,修改空闲堆空间的最小百分比(MinHeapFreeRatio)为30%:# jinfo -flag MinHeapFreeRatio 15729 -XX:MinHeapFreeRatio=40 # jinfo -flag MinHeapFreeRatio=30 15729 # jinfo -flag MinHeapFreeRatio 15729 -XX:MinHeapFreeRatio=30当然不是所有参数都可以这样修改的,比如并发垃圾收集器将使用的线程数(ConcGCThreads):# jinfo -flag ConcGCThreads=5 15729 Exception in thread "main" com.sun.tools.attach.AttachOperationFailedException: flag 'ConcGCThreads' cannot be changed at sun.tools.attach.LinuxVirtualMachine.execute(LinuxVirtualMachine.java:229) at sun.tools.attach.HotSpotVirtualMachine.executeCommand(HotSpotVirtualMachine.java:261) at sun.tools.attach.HotSpotVirtualMachine.setFlag(HotSpotVirtualMachine.java:234) at sun.tools.jinfo.JInfo.flag(JInfo.java:134) at sun.tools.jinfo.JInfo.main(JInfo.java:81)那么,有哪些配置参数是支持动态修改的呢?我们可以通过java -XX:+PrintFlagsInitial命令找到标记为manageable的配置参数,运行结果如下图所示:-flags显示全部的配置参数,比如:# jinfo -flags 15729 Attaching to process ID 15729, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.251-b08 Non-default VM flags: -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1HeapRegionSize=1048576 -XX:InitialHeapSize=1073741824 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=1073741824 -XX:MaxNewSize=536870912 -XX:MetaspaceSize=268435456 -XX:MinHeapDeltaBytes=1048576 -XX:MinHeapFreeRatio=30 -XX:NewSize=536870912 -XX:-PrintGC -XX:SurvivorRatio=4 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC Command line: -Xmx1g -Xms1g -Xmn512m -XX:SurvivorRatio=4 -XX:MetaspaceSize=256m -XX:+UseG1GC-sysprops以键值对的方式显示当前Java虚拟机的全部的系统属性,比如:# jinfo -sysprops 15729 Attaching to process ID 15729, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.251-b08 java.runtime.name = Java(TM) SE Runtime Environment java.vm.version = 25.251-b08 sun.boot.library.path = /usr/local/java/jdk1.8.0_251/jre/lib/amd64 java.protocol.handler.pkgs = org.springframework.boot.loader java.vendor.url = http://java.oracle.com/ java.vm.vendor = Oracle Corporation path.separator = : file.encoding.pkg = sun.io java.vm.name = Java HotSpot(TM) 64-Bit Server VM sun.os.patch.level = unknown sun.java.launcher = SUN_STANDARD user.country = CN java.vm.specification.name = Java Virtual Machine Specification PID = 15729 java.runtime.version = 1.8.0_251-b08 java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment os.arch = amd64 java.endorsed.dirs = /usr/local/java/jdk1.8.0_251/jre/lib/endorsed line.separator = ......-h 和 -help显示jinfo命令的帮助信息。结尾虽然jinfo命令已经推出很久并且使用频率比较搞,但它仍然是一个“实验性质的,并且没有技术支持的”(Experimental and Unsupported)工具,日后可能会被转正,也有可能在某个JDK版本中无声无息地消失。所以,且用且珍惜吧。最后,谢谢你这么帅,还给我点赞和关注。

Java的jstat命令使用详解

jstat命令简介jstat(Java Virtual Machine Statistics Monitoring Tool)是JDK提供的一个可以监控Java虚拟机各种运行状态信息的命令行工具。它可以显示Java虚拟机中的类加载、内存、垃圾收集、即时编译等运行状态的信息。jstat命令参数命令语法:jstat generalOptions jstat outputOptions [-t] [-h<lines>] <vmid> [<interval> [<count>]]命令参数说明:generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项:-help:显示帮助信息。-options:显示outputOptions参数的列表。outputOptions:输出选项,指定显示某一种Java虚拟机信息。-t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。-h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每个这段时间显示一次统计信息。count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。输出选项如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下:-class:显示类加载、卸载数量、总空间和装载耗时的统计信息。-compiler:显示即时编译的方法、耗时等信息。-gc:显示堆各个区域内存使用和垃圾回收的统计信息。-gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。-gcutil:显示有关垃圾收集统计信息的摘要。-gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。-gcnew:显示新生代的垃圾回收统计信息。-gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。-gcold: 显示老年代和元空间的垃圾回收统计信息。-gcoldcapacity:显示老年代的大小统计信息。-gcmetacapacity:显示元空间的大小的统计信息。-printcompilation:显示即时编译方法的统计信息。jstat命令的显示输出被格式化为一个表,列用空格分隔。接下来,我来了解一下每条输出选项的列名。-class选项Loaded:加载的类的数量。Bytes:加载的类所占用的字节数。Unloaded:卸载的类的数量。Bytes:卸载的类所占用的字节数。Time:执行类加载和卸载操作所花费的时间。举个例子:-compiler选项Compiled:执行的编译任务的数量。Failed:执行编译任务失败的数量。Invalid:执行编译任务失效的数量。Time:执行编译任务所花费的时间。FailedType:上次编译失败的编译类型。FailedMethod:上次编译失败的类名和方法。举个例子:-gc选项S0C:年轻代中第一个Survivor区的容量,单位为KB。S1C:年轻代中第二个Survivor区的容量,单位为KB。S0U:年轻代中第一个Survivor区已使用大小,单位为KB。S1U:年轻代中第二个Survivor区已使用大小,单位为KB。EC:年轻代中Eden区的容量,单位为KB。EU:年轻代中Eden区已使用大小,单位为KB。OC:老年代的容量,单位为KB。OU:老年代已使用大小,单位为KB。MC:元空间的容量,单位为KB。MU:元空间已使用大小,单位为KB。CCSC:压缩类的容量,单位为KB。CCSU:压缩类已使用大小,单位为KB。YGC:Young GC的次数。YGCT:Young GC所用的时间。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。举个例子:-gccapacity选项NGCMN:年轻代最小的容量,单位为KB。NGCMX:年轻代最大的容量,单位为KB。NGC:当前年轻代的容量,单位为KB。S0C:年轻代中第一个Survivor区的容量,单位为KB。S1C:年轻代中第二个Survivor区的容量,单位为KB。EC:年轻代中Eden区的容量,单位为KB。OGCMN:老年代最小的容量,单位为KB。OGCMX:老年代最大的容量,单位为KB。OGC:当前老年代的容量,单位为KB。OC:当前老年代的容量,单位为KB。MCMN:元空间最小的容量,单位为KB。MCMX:元空间最大的容量,单位为KB。MC:当前元空间的容量,单位为KB。CCSMN:压缩类最小的容量,单位为KB。CCSMX:压缩类最大的容量,单位为KB。CCSC:当前压缩类的容量,单位为KB。YGC:Young GC的次数。FGC:Full GC的次数。举个例子:-gcutil选项S0:年轻代中第一个Survivor区使用大小占当前容量的百分比。S1:年轻代中第二个Survivor区使用大小占当前容量的百分比。E:Eden区使用大小占当前容量的百分比。O:老年代使用大小占当前容量的百分比。M:元空间使用大小占当前容量的百分比。CCS:压缩类使用大小占当前容量的百分比。YGC:Young GC的次数。YGCT:Young GC所用的时间。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。举个例子:-gccause选项S0:年轻代中第一个Survivor区使用大小占当前容量的百分比。S1:年轻代中第二个Survivor区使用大小占当前容量的百分比。E:Eden区使用大小占当前容量的百分比。O:老年代使用大小占当前容量的百分比。M:元空间使用大小占当前容量的百分比。CCS:压缩类使用大小占当前容量的百分比。YGC:Young GC的次数。YGCT:Young GC所用的时间。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。LGCC:上次垃圾回收的原因。GCC:当前垃圾回收的原因。举个例子:-gcnew选项S0C:年轻代中第一个Survivor区的容量,单位为KB。S1C:年轻代中第二个Survivor区的容量,单位为KB。S0U:年轻代中第一个Survivor区已使用大小,单位为KB。S1U:年轻代中第二个Survivor区已使用大小,单位为KB。TT:对象在年轻代存活的次数。MTT:对象在年轻代存活的最大次数DSS:期望的Survivor区大小,单位为KB。EC:年轻代中Eden区的容量,单位为KB。EU:年轻代中Eden区已使用大小,单位为KB。YGC:Young GC的次数。YGCT:Young GC所用的时间。举个例子:-gcnewcapacity选项NGCMN:年轻代最小的容量,单位为KB。NGCMX:年轻代最大的容量,单位为KB。NGC:当前年轻代的容量,单位为KB。S0CMX:年轻代中第一个Survivor区最大的容量,单位为KB。S0C:年轻代中第一个Survivor区的容量,单位为KB。S1CMX:年轻代中第二个Survivor区最大的容量,单位为KB。S1C:年轻代中第二个Survivor区的容量,单位为KB。ECMX:年轻代中Eden区最大的容量,单位为KB。EC:年轻代中Eden区的容量,单位为KB。YGC:Young GC的次数。FGC:Full GC的次数。举个例子:-gcold选项MC:元空间的容量,单位为KB。MU:元空间已使用大小,单位为KB。CCSC:压缩类的容量,单位为KB。CCSU:压缩类已使用大小,单位为KB。OC:老年代的容量,单位为KB。OU:老年代已使用大小,单位为KB。YGC:Young GC的次数。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。举个例子:-gcoldcapacity选项OGCMN:老年代最小的容量,单位为KB。OGCMX:老年代最大的容量,单位为KB。OGC:当前老年代的容量,单位为KB。OC:当前老年代的容量,单位为KB。YGC:Young GC的次数。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。举个例子:-gcmetacapacity选项MCMN:元空间最小的容量,单位为KB。MCMX:元空间最大的容量,单位为KB。MC:当前元空间的容量,单位为KB。CCSMN:压缩类最小的容量,单位为KB。CCSMX:压缩类最大的容量,单位为KB。YGC:Young GC的次数。FGC:Full GC的次数。FGCT:Full GC的所用的时间。GCT:GC的所用的总时间。举个例子:-printcompilation选项Compiled:最近编译方法执行的编译任务的数量。Size:最近编译方法的字节码的字节数。Type:最近编译方法的编译类型。Method:最近编译方法的类名和方法名。举个例子:最后,谢谢你这么帅,还给我点赞和关注。

Java的jps命令使用详解

jps命令简介jps(Java Virtual Machine Process Status Tool)是JDK提供的一个可以列出正在运行的Java虚拟机的进程信息的命令行工具,它可以显示Java虚拟机进程的执行主类(Main Class,main()函数所在的类)名称、本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)等信息。另外,jps命令只能显示它有访问权限的Java进程的信息。虽然jps命令的功能比较单一,但它使用的频率却很高。对于本地虚拟机来说,本地虚拟机唯一ID和操作系统的进程ID(PID,Process Identifier)是一致的,如果同时启动多个Java虚拟机进程,无法根据进程名称确定某个进程,我们就是使用jps命令显示主类名称的功能区分出来。jps命令参数命令语法:jps [-q] [-mlvV] [hostid] jps [-help]命令参数说明:-q:不显示主类名称、JAR文件名和传递给主方法的参数,只显示本地虚拟机唯一ID。-mlvV:我们可以指定这些参数的任意组合。-m:显示Java虚拟机启动时传递给main()方法的参数。-l:显示主类的完整包名,如果进程执行的是JAR文件,也会显示JAR文件的完整路径。-v:显示Java虚拟机启动时传递的JVM参数。-V:不显示主类名称、JAR文件名和传递给主方法的参数,只显示本地虚拟机唯一ID。hostid:指定的远程主机,可以是ip地址和域名, 也可以指定具体协议,端口。如果不指定,则显示本机的Java虚拟机的进程信息。-help:显示jps命令的帮助信息。在没有指定任何参数的情况下,jps命令会显示每个Java虚拟机进程的本地虚拟机唯一ID,后面跟着主类名称或JAR文件名的简短形式。命令显示格式:LVMID [ [ classname | JARfilename | "Unknown"] [ arg* ] [ jvmarg* ] ]其中所有输出都以空格分隔,所以当arg或jvmarg中包含空格的时候,有可以不好分辨,需要注意一下。jps命令示例显示本机的Java虚拟机进程:# jps 15729 jar 92153 Jps 90267 Jstat显示主类的完整包名或JAR文件名:# jps -l 15729 one-more-1.0.0.RELEASE.jar 112054 sun.tools.jps.Jps 90267 sun.tools.jstat.Jstat显示主类的完整包名或JAR文件名,并且显示JVM参数:# jps -lv 15729 one-more-1.0.0.RELEASE.jar -Xmx1g -Xms1g -Xmn512m -XX:SurvivorRatio=4 -XX:MetaspaceSize=256m -XX:+UseG1GC 9043 sun.tools.jps.Jps -Denv.class.path=.:/usr/local/java/jdk1.8.0_251/lib:/usr/local/java/jdk1.8.0_251/jre/lib -Dapplication.home=/usr/local/java/jdk1.8.0_251 -Xms8m 90267 sun.tools.jstat.Jstat -Denv.class.path=.:/usr/local/java/jdk1.8.0_251/lib:/usr/local/java/jdk1.8.0_251/jre/lib -Dapplication.home=/usr/local/java/jdk1.8.0_251 -Xms8m显示主类的完整包名或JAR文件名,并且显示传递给main()方法的参数:# jps -lm 15729 one-more-1.0.0.RELEASE.jar 59014 sun.tools.jps.Jps -lm 90267 sun.tools.jstat.Jstat -gc 15729 1000结尾虽然jps命令已经推出很久并且使用频率很高,但它仍然是一个“实验性质的,并且没有技术支持的”(Experimental and Unsupported)工具,日后可能会被转正,也有可能在某个JDK版本中无声无息地消失。所以,且用且珍惜吧。最后,谢谢你这么帅,还给我点赞和关注。

面试官:Java中对象都存放在堆中吗?你知道逃逸分析?

面试官:Java虚拟机的内存分为哪几个区域?我(微笑着):程序计数器、虚拟机栈、本地方法栈、堆、方法区面试官:对象一般存放在哪个区域?我:堆。面试官:对象都存放在堆中吗?我:是的。面试官:你了解过逃逸分析吗?我(皱了皱眉):是内存溢出吗?面试官:不是的。我(挠了挠头):不是很了解。面试官:今天的面试先到这,回去等消息吧!然后就没有然后了,不甘心的我开始了查找相关资料。逃逸分析逃逸分析(Escape Analysis)是一种确定对象的引用动态范围的分析方法,说人话就是:分析在程序的哪些地方可以访问到对象的引用。当一个对象在方法中被分配时,该对象的引用可能逃逸到其它执行线程中,或是返回到方法的调用者。如果一个方法中分配一个对象并返回一个该对象的引用针,那么该对象可能被访问到的地方就无法确定,此时对象的引用就发生了“逃逸”。如果对象的引用存储在静态变量或者其它数据结构中,因为静态变量是可以在当前方法之外访问到,此时对象的引用也发生了“逃逸”。逃逸分析确定某个对象的引用可以被访问的所有地方,以及确定能否保证对象的引用的生命周期只在当前进程或线程中。逃逸状态对象的逃逸状态一般分为三种:全局逃逸、参数逃逸、没有逃逸。全局逃逸(GlobalEscape)对象的引用逃出了方法或者线程。比如:对象的引用赋值给了一个静态变量,或者存储在一个已经逃逸的对象中, 或者对象的引用作为方法的返回值给了调用方法。比如饿汉的单例模式:package one.more; public final class GlobalEscape { // instance对象赋值给了一个静态变量,发生了全局逃逸 private static GlobalEscape instance = new GlobalEscape(); private GlobalEscape() { public static GlobalEscape getInstance() { return instance; }参数逃逸(ArgEscape)对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸。这个状态是通过分析被调用方法的字节码来确定的。比如:package one.more; public class ArgEscape { class Rectangle { private int length; private int width; public Rectangle(int length, int width) { this.length = length; this.width = width; public int getArea() { return this.length * this.width; public int getArea(int length, int width) { Rectangle rectangle = buildRectangle(length, width); return rectangle.getArea(); private Rectangle buildRectangle(int length, int width){ Rectangle rectangle = new Rectangle(length, width); // rectangle对象发生了参数逃逸 return rectangle; }没有逃逸(NoEscape)方法中的对象没有发生逃逸,这意味着可以不将该对象分配在堆上。比如:package one.more; public class NoEscape { class Rectangle { private int length; private int width; public Rectangle(int length, int width) { this.length = length; this.width = width; public int getArea() { return this.length * this.width; public int getArea(int length, int width) { // rectangle对象没有逃逸 Rectangle rectangle = new Rectangle(length, width); return rectangle.getArea(); }逃逸分析后的优化如果一个对象没有发生逃逸,或者只有参数逃逸,就可能为这个对象采取不同程度的优化,比如:栈上分配、标量替换、同步消除。栈上分配(Stack Allocations)如果一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。那么,对象就会随着方法的结束而自动销毁了,可以降低垃圾收集器运行的频率,垃圾收集的压力就会下降很多。标量替换(Scalar Replacement)标量(Scalar)是指一个无法再分解成更小的数据的数据。Java虚拟机中的基本数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为基本类型来访问,这个过程就称为标量替换。如果一个对象没有发生逃逸,可以进行标量替换,那么对象的成员变量就在栈上分配和读写,不需要分配到堆中。标量替换可以视作栈上分配的一种特例,实现更简单,但对逃逸程度的要求更高,它不允许对象没有发生逃逸。同步消除(Synchronization Elimination)线程同步本身是一个相对耗时的过程,如果一个对象没有逃逸出线程,无法被其他线程访问,那么该对象的读写肯定就不会有竞争,对该对象实施的同步加锁操作也就可以安全地消除掉。总结说了这么多,可以发现对象并不是都在堆上分配内存的。因为通过逃逸分析后,可以对没有逃逸的对象进行标量替换。另外,由于复杂度等原因,HotSpot中目前还不支持栈上分配的优化。最后,谢谢你这么帅,还给我点赞和关注。

浅析HTTPS的通信机制

什么是HTTPS?HTTPS 是在HTTP(Hyper Text Transfer Protocol)的基础上加入SSL(Secure Sockets Layer),在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。依此避免HTTP的明文传输容易被窃听、不验证身份容易被伪装、无法验证报文完整性容易被篡改等问题。除了被广泛用于互联网上安全敏感的通讯,大部分网站也正在广泛采用 。HTTPS的通信机制HTTPS不仅仅是把http://换成https://看上去那么简单,HTTPS的通信机制要比HTTP的复杂一些。第一步,客户端向服务端发送Client Hello报文。在报文中描述了客户端支持的SSL版本,还有客户端支持的加密算法及密钥长度。第二步,服务端向客户端发送Server Hello报文作为应答。在报文中描述了服务端和客户端可以共同使用的SSL版本、加密算法及密钥长度,以此作为后续报文传输的加密方式。第三步,服务端向客户端发送Certificate报文。在报文中包含由数字证书认证机构(Certificate Authority,CA)和其相关机构颁发的公共密钥证书。第四步,服务端向客户端发送Server Hello Done报文,通知客户端SSL握手协商部分结束。第五步,当客户端收到服务端SSL握手结束的报文后,客户端首先验证证书的合法性和可靠性,然后向服务端发送Client Key Exchange报文。在报文中包含了使用证书中公开密钥加密的随机密钥。第六步,客户端向服务端发送Change Cipher Spec报文。这个报文是为了提示服务端之后的通信都使用那个随机密钥进行对称加密。第七步,客户端向服务端发送Finished报文。这个报文包含从开始连接到现在全部报文的整体校验值。服务端,握手协商成功。第八步,当服务端能够正确解密并验证客户端的校验值时,同样向客户端发送Change Cipher Spec报文。第九步,服务端向客户端同样发送Finished报文。第十步,服务端和客户端的Finished报文交换完毕之后,SSL连接就建立完成了。当通信会受到 SSL 的保护后,开始发送 HTTP 请求和相应。尾声目前越来越多的核心技术被“卡脖子”,之前也传出一些开源软件禁止我国的使用。我有一个担心,万一CA也别被“卡脖子”,甚至被“征用”,那么我国的网络安全会受到极大的威胁。竟然已经看到这里了,你我定是有缘人,留下你的点赞和关注,他日必成大器。

快速整明白Redis中的压缩列表到底是个啥

压缩列表简介压缩列表(ziplist)是由一个连续内存组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点上可以保存一个字节数组或整数值。它是Redis为了节省内存空间而开发的。压缩列表(ziplist)是哈希(hash)和有序集合(zset)的内部编码之一。当哈希(hash)中的元素个数比较少并且每个元素的值占用空间比较小的时候,Redis就会使用压缩列表做为哈希的内部编码。当有序集合(zset)中的元素个数比较少并且每个元素的值占用空间比较小的时候,Redis也会使用压缩列表做为有序集合的内部编码。压缩列表结构接下来,我们来看以下压缩列表的内部构造,压缩列表由以下几个部分组成:zlbytes:表示整个压缩列表占用的内存字节数。xltail:表示压缩列表起始地址到最后一个节点的字节数,可以快速找到最后一个节点。zllength:表示压缩列表包含的节点个数。entries:节点列表,一个挨着一个地紧凑存储。zlend:特殊值0xFF(十进制为255),表示压缩列表的结束。压缩列表节点结构每个压缩列表的节点由三部分组成: prevlen、 encoding和content。prevlenprevlen:表示该节点前一个节点的字节长度。 prevlen的长度可能是1个字节,也可能是5个字节。当前一个节点的长度小于254个字节时, prevlen的长度为1个字节,直接存储前一个节点的字节长度;当前一个节点的长度大于或等于254个字节时, prevlen的长度为5个字节,其中的第一个字节被设置为0xFE,随后的四个字节保存前一个节点的字节长度。可以通过 prevlen和压缩列表结构中的xltail逆序遍历压缩列表。encodingencoding表示该节点中保存数据的类型和长度。当encoding的最高位以00开头时,表示最大长度为63的短字符串,此时encoding的长度为1个字节,其后面6个位表示字符串的字节长度;当encoding的最高位以01开头时,表示最大长度为16383的中等长度的字符串,此时encoding的长度为2个字节,其后面14个位表示字符串的字节长度;当encoding的最高位以10开头时,表示最大长度为4294967295的特长的字符串,此时encoding的长度为5个字节,其后面4个字节表示字符串的字节长度;当encoding的最高位以11开头时,表示整数值,此时encoding的长度为1个字节,其后面6个位表示整数值的类型和长度。contentcontent用于存储节点的值,节点的值可以是一个字节数组,也可以是正数,其类型和长度由encoding决定。总结压缩列表(ziplist)是由一个连续内存组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点上可以保存一个字节数组或整数值。压缩列表(ziplist)是哈希(hash)和有序集合(zset)的内部编码之一。竟然已经看到这里了,你我定是有缘人,留下你的点赞和关注,他日必成大器。

面试官:Redis的共享对象池了解吗?

我正在面试间里焦急地等待着,突然听到了门外的脚步声,随即门被打开,穿着干净满脸清秀的青年走了进来,一股男士香水的淡香扑面而来。面试官:“平时在工作中用过Redis吗?”我:“用的比较多。”我心中暗喜,Redis我熟啊,什么五种数据类型、两种持久化方式倒背如流啊。面试官:“Redis的共享对象池了解吗?”“这个。。没有太深入了解。”我支支吾吾的说到,手心已经冒出冷汗。面试官:“回去等消息吧。”这句话说的干净利落,然后就没有然后了。失败是成功的妈妈,我不气馁,决定马上恶补一下。共享对象池创建大量重复的整数类型势必会耗费大量内存,所以在Redis内部维护了一个从0到9999的整数对象池,这就是共享对象池。为了验证和理解,我们使用object refcount命令查看一下对象引用数,效果如下:127.0.0.1:6379> set one-more-num1 404 127.0.0.1:6379> object refcount one-more-num1 (integer) 2 172.24.130.22:6379> set one-more-num2 404 127.0.0.1:6379> object refcount one-more-num2 (integer) 3设置one-more-num1为404后,直接使用共享池中的整数对象,所以引用数为2(另外一个引用在对象池上);再设置one-more-num2为404后,引用数变成了3。不过需要注意的是:当设置最大内存值(maxmemory)并且启用LRU相关淘汰策略(如:volatile-lru、allkeys-lru)时,共享对象池将会被禁止使用。为什么没有字符串对象池?共享对象池中一个关键操作是判断对象是否相等。Redis中只有整数类型的对象池,是因为整数的比较算法的时间复杂度是O(1),也只保留了10000个整数为了防止对象池的过度浪费。相对而言,字符串的比较算法的时间复杂度是O(n),特别是长字符串的比较更加消耗性能。而且,整数类型被重复使用的概率很大,字符串被重复使用的概率相比就会小很多很多,所以在Redis中只用整数类型的对象共享池。面试官你等着瞧吧,今天你对我爱答不理,明天我让你高攀不起,哈哈哈。。。参考文献:《Redis设计与实现》《Redis开发与运维》《Redis 深度历险:核心原理与应用实践》竟然已经看到这里了,你我定是有缘人,留下你的点赞和关注,他日必成大器。

Linux常用文件权限命令详解

pwdpwd命令用于获取当前工作目录的绝对路径。使用示例:pwd效果如下图:cdcd命令用于切换工作目录。使用示例:cd 万猫学社/效果如下图:其中在路径表示时, 一个半角句号(.)表示当前目录,例如路径./one/more等同于one/more;两个半角句号(..)表示上级目录,例如路径/one/more/../society等同于/one/society,其中more和society目录同级。cd命令的默认参数为~,符号~表示当前用户的家目录。当root用户登录时,命令cd、cd ~和cd /root执行效果相同。lsls命令用于显示指定工作目录下的内容。命令格式:ls [参数] [目录名]参数说明:参数说明-a显示所有文件及目录(包括隐藏文件)-l将文件的权限、拥有者、文件大小等详细信息列出(ll等同于ls -l)-r将文件反序列出(默认按英文字母正序)-t将文件按创建时间正序列出-R递归遍历目录下文件使用示例:查看当前目录下的所有文件(包括隐藏文件)。ll -a效果如下图:查看Linux系统上的文件、目录和设备的权限。ls -l效果如下图:上图显示的第一列就是文件权限信息,共11位字符,分5部分。第1位表示存档类型,d表示目录,-表示一般文件。第2~4位表示当前用户的权限。第5~7位表示同用户组的用户权限。第8~10位表示不同用户组的用户权限。第11位是一个半角句号.,表示SELinux安全标签。用户权限每组三位,rwx分别表示读、写、执行权限,对应八进制表示为4、2、1。例如onemore目录的root用户权限为drwxr-xr-x,则表示:该目录对root用户具有读写和执行所有权限,该目录对root组其他用户有读和执行权限,该目录对其他用户有读和执行权限。所以该权限表示对应八进制权限表示为:755(当前用户的权限:4+2+1=7,同用户组的用户权限:4+1=5,不同用户组的用户权限:4+1=5)。chmodchmod命令用于修改文件权限,-R参数以递归方式对子目录和文件进行修改。命令格式:chmod 权限设定字串 文件名其中,权限设定字串的格式为:[ugoa...][[+-=][rwxX]...][,...]其含义为:u 表示该文件的拥有者,g 表示与该文件的拥有者属于同一用户组的用户,o 表示其他以外的人,a 表示这三者皆是。+ 表示增加权限、- 表示取消权限、= 表示唯一设定权限。r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。使用示例:新建名为onemore.sh的Shell脚本,该脚本将会输出万猫学社。echo "echo '万猫学社'" > onemore.sh用ll命令可以看到onemore.sh没有执行权限,直接运行会报Permission denied错误。效果如下图:将onemore.sh文件增加当前用户的执行权限。chmod u+x onemore.sh有了执行权限之后就可以直接运行了。效果如下图:将onemore.sh文件撤销当前用户的执行权限。chmod u-x onemore.sh效果如下图:treetree命令用于查看创建后的目录结构。使用示例:tree效果如下图:touchtouch命令用于修改文件或者目录的时间属性,包括存取时间和更改时间。若文件不存在,系统会建立一个新的文件。命令格式:touch [参数] [文件]参数说明:参数说明-c如果指定文件不存在,不会建立新文件-r使用参考文件的时间记录-t设置文件的时间记录使用示例:创建4个空文件。touch 万.txt 猫.txt 学.txt 社.txt效果如下图:mkdirmkdir命令用于新建子目录。-p参数确保目录名称存在,不存在的就新建一个。使用示例:新建目录o/n/e/m/o/r/e/,并使用tree命令查看创建后的目录结构。mkdir -p o/n/e/m/o/r/e/ tree效果如下图:cpcp命令主要用于复制文件或目录。命令格式:cp [参数] [源文件] [目标文件]参数说明:参数说明-d复制时保留链接-f覆盖已经存在的目标文件而不给出提示-i覆盖前询问-p除复制文件的内容外,还把修改时间和访问权限也复制到新文件中-r复制目录及目录内的所有项目使用示例:首先创建目录o/n/e/和m/o/r/e/mkdir -p o/n/e/ mkdir -p m/o/r/e/效果如下图:再将目录m中的所有内容复制到目录o/n/e/中。cp -r m o/n/e/效果如下图:rmrm命令用于删除一个文件或者目录。命令格式:rm [参数] [文件]参数说明:参数说明-i删除前逐一询问确认-f无需确认,直接删除-r删除目录下所有文件使用示例:无需确认直接删除以.txt结尾的文件。rm -rf *.txt效果如下图:无需确认直接删除目录o及其目录下所有子目录和文件。rm -rf o效果如下图:mvmv命令用来为文件或目录改名、或将文件或目录移入其它位置。命令格式:mv [参数] [源文件] [目标文件]参数说明:参数说明-i若指定目录已有同名文件,则先询问是否覆盖旧文件-f如果目标文件已经存在,不会询问而直接覆盖使用示例:将文件名万猫学社.txt改为onemore.txt。touch 万猫学社.txt mv 万猫学社.txt onemore.txt效果如下图:先创建目录o/n/e/和m/o/r/e/,将目录m中的所有内容移动到目录o/n/e/中。mkdir -p o/n/e/ mkdir -p m/o/r/e/ mv m o/n/e/效果如下图:renamerename命令用字符串替换的方式批量改变文件名。命令格式:rename 原字符串 目标字符串 文件列表使用示例:先创建4个空文件,再将当前目录下所有.txt文件后缀都改为.log。touch 万.txt 猫.txt 学.txt 社.txt rename .txt .log *效果如下图:

快速了解常用的消息摘要算法,再也不用担心面试官的刨根问底

面试官:说一说你常用的加密算法有哪些?加密算法通常被分为两种:对称加密和非对称加密。其中,对称加密算法在加密和解密时使用的密钥相同;非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥。此外,还有一类叫做消息摘要算法,是对数据进行摘要并且不可逆的算法。这次我们了解一下消息摘要算法。消息摘要算法消息摘要算法是把任意长度的输入揉和而产生长度固定的伪随机结果的算法。在信息安全中,有许多重要的应用,都使用了消息摘要算法来实现,例如数字签名、消息认证码。对于任何一个给定的数据,消息摘要算法都很容易就能运算出摘要结果。难以由一个已知的摘要结果,去推算出原始的数据。在不更动摘要结果的前提下,修改数据内容是不可行的。对于两个不同的数据,只有极低的几率会产生相同的摘要结果。常见的对称加密算法有:MD5算法、SHA。MD5算法MD5算法(Message Digest 5)是一种密码散列函数,产生出一个128位的散列值,可以用一个长度为32的十六进制字符串表示。MD5算法是由美国密码学家Ronald Linn Rivest(这位大佬就是发明RSA算法的R)设计的,于1992年公开,用来取代之前的MD4算法(再之前还有MD3算法、MD2算法)。MD5算法把原数据按每组512位大小进行分组,然后每一分组又被划分为16个32位子分组,再和事先定义好的4个幻数进行了一系列的位运算循环,最后得到四个32位的分组,将这四个32位分组级联后将生成一个128位散列值。我们用Java写个例子:import java.math.BigInteger; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Md5Util { private static final String MD5 = "MD5"; private static final Charset CHARSET = StandardCharsets.UTF_8; public static String build(String input) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance(MD5); byte[] digest = md5.digest(input.getBytes(CHARSET)); return new BigInteger(1, digest).toString(16); public static void main(String[] args) throws NoSuchAlgorithmException { String msg = "我喜欢你,可以做我女朋友吗?"; System.out.println("原文:" + msg); System.out.println("摘要:" + build(msg)); }运行结果如下:原文:我喜欢你,可以做我女朋友吗? 摘要:f9fa148f2cfffda9b5e15a9e5bf34b662004年,MD5算法被证实无法防止碰撞攻击,因此不适用于安全性认证。2009年,中国科学院的谢涛和冯登国仅用了$2^{20.96}$的碰撞算法复杂度,破解了MD5的碰撞抵抗,该攻击在普通计算机上运行只需要数秒钟。2011年,IETF(Internet Engineering Task Force,互联网工程任务组)发布RFC 6151,禁止MD5用作密钥散列消息认证码。总之,MD5已经不安全,不要再继续使用了。SHASHA(Secure Hash Algorithm,安全散列算法)是一个密码散列函数家族,是FIPS(Federal Information Processing Standards,联邦信息处理标准)所认证的安全散列算法。SHA家族包含一套逐步发展而来算法,有1993年发布的SHA-0、1995年发布的SHA-1、2001年发布的SHA-2、2015年发布的SHA-3。由于对MD5出现成功的破解,以及对SHA-0和SHA-1出现理论上破解的方法,所以推荐使用SHA-2,或者更安全的SHA-3。我们用Java写个SHA-2的例子:import java.math.BigInteger; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class ShaUtil { private static final String SHA_256 = "SHA-256"; private static final String SHA_512 = "SHA-512"; private static final String SHA3_256 = "SHA3-256"; private static final String SHA3_512 = "SHA3-512"; private static final Charset CHARSET = StandardCharsets.UTF_8; public static String build(String input, String algorithm) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance(algorithm); byte[] digest = md5.digest(input.getBytes(CHARSET)); return new BigInteger(1, digest).toString(16); public static void main(String[] args) throws NoSuchAlgorithmException { String msg = "我喜欢你,可以做我女朋友吗?"; System.out.println("原文:" + msg); System.out.println("SHA2-256摘要:" + build(msg, SHA_256)); System.out.println("SHA2-512摘要:" + build(msg, SHA_512)); System.out.println("SHA3-256摘要:" + build(msg, SHA3_256)); System.out.println("SHA3-512摘要:" + build(msg, SHA3_512)); }运行结果如下:原文:我喜欢你,可以做我女朋友吗? SHA2-256摘要:b6da8ee261f2b852c1140cf181e8d64b180ca6c884651ddb871bdff25afc836b SHA2-512摘要:d65f455eb38a565fae8e7c3ea6dbc005612071d5e57b688f32674e9641ab9aa6f056381222ba47cc973c86380f24fd10f4078ad7bfd3d498210d721734740a5a SHA3-256摘要:fc5f1427fc5a1bb2f231eec52fdaa5ac84652730143a3c7598dc2148ccd05cec SHA3-512摘要:5d8ba707c40c39f37c8cffd2eabf8da8d6d4ede70c697402a5e5ea6228c5710c3d76a6abbc1d46413bfced66280f72621feac12ce3ef49aed60902091ca1979fJDK8及以下版本不支持SHA-3,所以运行以上代码时会出现NoSuchAlgorithmException: SHA3-256 MessageDigest not available异常。总结消息摘要算法是把任意长度的输入揉和而产生长度固定的伪随机结果的算法。常见的对称加密算法有:MD5算法、SHA。MD5算法不要再继续使用了。SHA家族中,推荐使用SHA-2,或者更安全的SHA-3。

快速了解常用的非对称加密算法,再也不用担心面试官的刨根问底

面试官:说一说你常用的加密算法有哪些?加密算法通常被分为两种:对称加密算法和非对称加密算法。其中,对称加密算法在加密和解密时使用的密钥相同;非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥。此外,还有一类叫做消息摘要算法,是对数据进行摘要并且不可逆的算法。这次我们了解一下非对称加密算法。非对称加密算法非对称加密算法在加密和解密时使用两个不同的密钥,其中一个可以公开的密钥被称为公钥,另外一个完全保密的密钥被称为私钥。只有同一个公钥私钥对才能正常加密和解密。对于同一个公钥私钥对,如果使用公钥对数据进行加密,只有用对应的私钥才能进行解密;如果使用私钥对数据进行加密,只有用对应的公钥才能进行解密。常见的非对称加密算法有:RSA算法、DSA。RSA算法RSA算法是目前最有影响力的公钥加密算法,它由Ron Rivest、Adi Shamir和Leonard Adleman三位大佬在1977年麻省理工学院工作时一起提出的,RSA就是他们三人姓氏开头字母拼在一起组成的。另外,1973年,在英国政府通讯总部工作的数学家Clifford Cocks在一个内部文件中提出了一个与之等效的算法,但该算法被列入机密,直到1997年才得到公开。RSA算法利用了两个数论特性:p1、p2为两个质数, n=p1 * p2。已知p1、p2求n简单,已知n求p1、p2很难。(m^e) mod n=c,已知m、e、n求c简单,已知e、n、c求m很难。公钥私钥生成过程:随机选取两个质数p1、p2,n=p1 * p2,再随机选取一个与φ(n)互质且小于φ(n)的整数e,然后再计算e对于φ(n)的模反元素d,最后得到n和e为公钥,n和d为私钥。加密过程:(m^e) mod n = c,其中m为明文,c为密文,n和e为公钥。解密过程:(c^d) mod n = m,其中m为明文,c为密文,n和d为私钥。我们用Java写个例子:import javax.crypto.Cipher; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class RsaUtil { private static final String RSA = "RSA"; private static final Charset CHARSET = StandardCharsets.UTF_8; * @param input 明文 * @param publicKey 公钥 * @return 密文 * @throws GeneralSecurityException public static String encrypt(String input, String publicKey) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(RSA); PublicKey pubKey = KeyFactory.getInstance(RSA) .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))); cipher.init(Cipher.ENCRYPT_MODE, pubKey); byte[] data = cipher.doFinal(input.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(data); * @param input 密文 * @param privateKey 私钥 * @return 明文 * @throws GeneralSecurityException public static String decrypt(String input, String privateKey) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(RSA); PrivateKey priKey = KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); cipher.init(Cipher.DECRYPT_MODE, priKey); byte[] data = cipher.doFinal(Base64.getDecoder().decode(input)); return new String(data, CHARSET); public static void main(String[] args) throws GeneralSecurityException { // 生成公钥/私钥对: KeyPairGenerator kpGen = KeyPairGenerator.getInstance(RSA); kpGen.initialize(1024); KeyPair keyPair = kpGen.generateKeyPair(); String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); System.out.println("公钥:" + publicKey); System.out.println("私钥:" + privateKey); String msg = "我喜欢你,可以做我女朋友吗?"; System.out.println("加密前:" + msg); String pwd = RsaUtil.encrypt(msg, publicKey); System.out.println("加密后:" + pwd); System.out.println("解密后:" + RsaUtil.decrypt(pwd, privateKey)); }运行结果如下:公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDd4brSm8gdJqFi04m3aW8kjVYbd/T4ymyc7l3c2WmwOhVPlZO1eaZJpTvas61rW0HPf267CRIhc52Zp2+e1hoknApvT0gKeRkfSwC5aws/yoT2vZ9J627QbCFkFc8mmfP8LJ5V/rCpmUE12dIW4xVlEYtWPzmNK2iTMzuR99Jq9wIDAQAB 私钥:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAN3hutKbyB0moWLTibdpbySNVht39PjKbJzuXdzZabA6FU+Vk7V5pkmlO9qzrWtbQc9/brsJEiFznZmnb57WGiScCm9PSAp5GR9LALlrCz/KhPa9n0nrbtBsIWQVzyaZ8/wsnlX+sKmZQTXZ0hbjFWURi1Y/OY0raJMzO5H30mr3AgMBAAECgYEAuxPtKlAwzOtaXXIQhrV+AWqttGFTCkXaiAKu31vssap3d2+daACWxTdtHPwr9v2tol9GpKqEP/I0am5zPZA13x+tv068x2TxGf8ZEbp579CKE3NrzTrnhtJN31TT82HIJoJ0TsNUhHbwZPWVAsq98afSCP1rn5RY/kwJ6bzsjhECQQD8Ev044KaUdWd0WZoomMP+5tATUL0HTMmUiBEvrXjOWG2mjuD4M5n/9C11FZWFg+o75riAUiNYE5dfGyK1uDP5AkEA4VZdUOTPt0DclMESX3lOddS3o+TL/OkowErP0mAl3NzbJ1PnDUifQ0q1IZUoCi1nG9eVZScS8xZ7oa7ICgdybwJBAIY/OrsK8cyJBlLx0WcjjOZ5WIGg8zsrCwRevwBsW7VRZPxahbfKC49EJN2BZENaMOo8AzDcDdS/glN1aTPsaUkCQAVLhj3UYp0nxQcp0ki0DQfvy7DqO3Dh+bcrCt8iq0EZX3z5F8DUKAnow4DahGpYzsd0tWn/FQ7pRFZ0SPcTXbkCQQCp7ay9nEo2hEH08E5LekFsuipDjCEpeCgKojZUmFCh7BdawG6XzCLzNMMIIIjqRHlrJaxS41WhedPZR4nTNxGF 加密前:我喜欢你,可以做我女朋友吗? 加密后:tRt5hdF0XB8V2wk6BWC2i8UWVQj/jOCRZn3wIfGYqVaYJ9OjC/+VRUI3c5WgpZlKCZd5zrHo3g1LuQ02G934Gcb51cKH4uhWxRY8oxUgs/fibkvc9+w1X7FQarFwAGCs5SddHIL/vYUxHIvQslelyP9l9/EFpgSs0WWSfOfKcUc= 解密后:我喜欢你,可以做我女朋友吗?RSA算法解决了对称算法的安全性依赖于同一个密钥的缺点。不过,RSA算法在计算上相当复杂,性能欠佳、远远不如对称加密算法。因此,在一般实际情况下,往往通过非对称加密算法来随机创建临时的对称密钥,然后通过对称加密来传输大量、主体的数据。DSADSA(Digital Signature Algorithm,数字签名算法)是 Schnorr 和 ElGamal 签名算法的变种,基于模算数和离散对数的复杂度。美国国家标准技术研究所(NIST)于1991年提出将DSA用于其DSS(DigitalSignature Standard,数字签名标准),并于1994年将其作为FIPS 186采用。和RSA算法使用公钥加密私钥解密的方式不同,DSA使用私钥对数据进行加密生成数字签名,然后使用公钥解密后的数据和原数据进行对比,以验证数字签名。数字签名提供信息鉴定(接收者可以验证消息的来源),完整性(接收方可以验证消息自签名以来未被修改)和不可否认性(发送方不能错误地声称它们没有签署消息)。我们用Java写个例子:import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class DsaUtil { private static final String DSA = "DSA"; private static final String SHA1withDSA = "SHA1withDSA"; private static final Charset CHARSET = StandardCharsets.UTF_8; * @param data 数据 * @param privateKey 私钥 * @return 签名 * @throws GeneralSecurityException public static String sign(String data, String privateKey) throws GeneralSecurityException { PrivateKey priKey = KeyFactory.getInstance(DSA) .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); Signature signature = Signature.getInstance(SHA1withDSA); signature.initSign(priKey); signature.update(data.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(signature.sign()); * @param data 数据 * @param publicKey 公钥 * @param sign 签名 * @return 是否验证通过 public static boolean verify(String data, String publicKey, String sign) throws GeneralSecurityException { try { PublicKey pubKey = KeyFactory.getInstance(DSA) .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))); Signature signature = Signature.getInstance(SHA1withDSA); signature.initVerify(pubKey); signature.update(data.getBytes(CHARSET)); return signature.verify(Base64.getDecoder().decode(sign)); } catch (Exception e) { throw new RuntimeException(e); public static void main(String[] args) throws GeneralSecurityException { // 生成公钥/私钥对: KeyPairGenerator kpGen = KeyPairGenerator.getInstance(DSA); kpGen.initialize(1024); KeyPair keyPair = kpGen.generateKeyPair(); String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()); String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); System.out.println("公钥:" + publicKey); System.out.println("私钥:" + privateKey); String msg = "我喜欢你,可以做我女朋友吗?"; System.out.println("数据:" + msg); String sign = DsaUtil.sign(msg, privateKey); System.out.println("签名:" + sign); System.out.println("验证是否通过:" + DsaUtil.verify(msg, publicKey, sign)); }运行结果如下:公钥:MIIBtzCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoDgYQAAoGABDM1s78NZ4C0Bh9V86Z1lylEyCjCg2oAj6Kxd3/2IXhSlplnSpJPLlomet9yWJpagLQieIWHAIyq6JLmdcVxOxUvnLIsrvWKIPr4lz7pIqO1xi4AYunP48gPECHlMgOKPyik3ZkQQ3iHl9MiaWOaeisqsw/gzTUtE1xi8CVscks= 私钥:MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUA1HUKjMiSvazMzpKczR6w6DDbeM= 数据:我喜欢你,可以做我女朋友吗? 签名:MCwCFHhnd/3yRCIygyD1GPa1K9ZVQ+4rAhR8zAtlrBim9KKEkv+Fxz47opvSuA== 验证是否通过:true通过Java的示例可以看到,不会直接对数据进行私钥的加密,而是先通过信息摘要算法对数据进行摘要,然后对摘要信息进行私钥的加密。总结非对称加密算法在加密和解密时使用两个不同的密钥,分别被称为公钥和私钥,只有同一个公钥私钥对才能正常加密和解密。常见的非对称加密算法有:RSA算法、DSA。RSA算法主要进行对数据的公钥加密,DSA主要是对数据的签名验证。

快速了解常用的对称加密算法,再也不用担心面试官的刨根问底

面试官:说一说你常用的加密算法有哪些?加密算法通常被分为两种:对称加密和非对称加密。其中,对称加密算法在加密和解密时使用的密钥相同;非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥。此外,还有一类叫做消息摘要算法,是对数据进行摘要并且不可逆的算法。这次我们了解一下对称加密算法。对称加密算法对称加密算法在加密和解密时使用的密钥相同,或是使用两个可以简单地相互推算的密钥。在大多数的对称加密算法中,加密和解密的密钥是相同的。它要求双方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送的信息进行解密,这也是对称加密算法的主要缺点之一。常见的对称加密算法有:DES算法、3DES算法、AES算法。DES算法DES算法(Data Encryption Standard)是一种常见的分组加密算法。面试官:什么是分组加密算法?分组加密算法是将明文分成固定长度的组,每一组都采用同一密钥和算法进行加密,输出也是固定长度的密文。由IBM公司在1972年研制,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。在DES算法中,密钥固定长度为64位。明文按64位进行分组,分组后的明文组和密钥按位置换或交换的方法形成密文组,然后再把密文组拼装成密文。密钥的每个第八位设置为奇偶校验位,也就是第8、16、24、32、40、48、56、64位,所以密钥的实际参与加密的长度为56位。我们用Java写个例子:import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; public class DesUtil { private static final String DES = "DES"; private static final Charset CHARSET = StandardCharsets.UTF_8; * @param input 明文 * @param key 密钥 * @return 密文 * @throws GeneralSecurityException public static String encrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(DES); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), DES); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] data = cipher.doFinal(input.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(data); * @param input 密文 * @param key 密钥 * @return 明文 * @throws GeneralSecurityException public static String decrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(DES); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), DES); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] data = cipher.doFinal(Base64.getDecoder().decode(input)); return new String(data, CHARSET); public static void main(String[] args) throws GeneralSecurityException { String msg = "我喜欢你,可以做我女朋友吗?"; String key = "One-More"; System.out.println("加密前:" + msg); String pwd = DesUtil.encrypt(msg, key); System.out.println("加密后:" + pwd); System.out.println("解密后:" + DesUtil.decrypt(pwd, key)); }运行结果如下:加密前:我喜欢你,可以做我女朋友吗? 加密后:i5LZ5aJMrlgN+Pr5IQm87Q14k0kmDLFIPnJmtrGA/xBHG0SivGrqCrc3vXjZoCBm 解密后:我喜欢你,可以做我女朋友吗?DES现在已经不是一种安全的加密方法,主要因为它使用的密钥过短,很容易被暴力破解。3DES算法3DES算法(Triple Data Encryption Algorithm)是DES算法的升级版本,相当于是对明文进行了三次DES加密。由于计算机运算能力的增强,DES算法由于密钥长度过低容易被暴力破解;3DES算法提供了一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。在DES算法中,密钥固定长度为192位。在加密和解密时,密钥会被分为3个64位的密钥。面试官:3DES算法加密和解密的过程是什么样子的?加密过程如下:使用第一个密钥加密明文。使用第二个密钥解密上一步的结果。使用第三个密钥加密上一步的结果。解密过程如下:使用第三个密钥解密明文。使用第二个密钥加密上一步的结果。使用第一个密钥解密上一步的结果。我们用Java写个例子:import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; public class TripleDesUtil { private static final String DESede = "DESede"; private static final Charset CHARSET = StandardCharsets.UTF_8; * @param input 明文 * @param key 密钥 * @return 密文 * @throws GeneralSecurityException public static String encrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(DESede); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), DESede); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] data = cipher.doFinal(input.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(data); * @param input 密文 * @param key 密钥 * @return 明文 * @throws GeneralSecurityException public static String decrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(DESede); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), DESede); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] data = cipher.doFinal(Base64.getDecoder().decode(input)); return new String(data, CHARSET); public static void main(String[] args) throws GeneralSecurityException { String msg = "我喜欢你,可以做我女朋友吗?"; String key = "One-More12345678One.More"; System.out.println("加密前:" + msg); String pwd = TripleDesUtil.encrypt(msg, key); System.out.println("加密后:" + pwd); System.out.println("解密后:" + TripleDesUtil.decrypt(pwd, key)); }运行结果如下:加密前:我喜欢你,可以做我女朋友吗? 加密后:q/ZWtjDGoxIjmd30he0oZ3XLjJhh/ACedaXaj12Zi3Wtlqz+ZzJmQuScjKuZoONF 解密后:我喜欢你,可以做我女朋友吗?虽然3DES算法在安全性上有所提升,但是因为使用了3次DES算法,加密和解密速度比较慢。AES算法AES(Advanced Encryption Standard,高级加密标准)主要是为了取代DES加密算法的,虽然出现了3DES的加密方法,但由于它的加密时间是DES算法的3倍多,密钥位数还是不能满足对安全性的要求。1997年1月2号,美国国家标准与技术研究院(NIST)宣布希望征集高级加密标准,用以取代DES。全世界很多密码工作者都提交了自己设计的算法。经过甄选流程,高级加密标准由美国国家标准与技术研究院于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael为名投稿高级加密标准的甄选流程。面试官:AES算法的密钥长度是固定的吗?AES算法的密钥长度是固定,密钥的长度可以使用128位、192位或256位。AES算法也是一种分组加密算法,其分组长度只能是128位。分组后的明文组和密钥使用几种不同的方法来执行排列和置换运算形成密文组,然后再把密文组拼装成密文。我们用Java写个例子:import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; public class AesUtil { private static final String AES = "AES"; private static final Charset CHARSET = StandardCharsets.UTF_8; * @param input 明文 * @param key 密钥 * @return 密文 * @throws GeneralSecurityException public static String encrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(AES); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), AES); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] data = cipher.doFinal(input.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(data); * @param input 密文 * @param key 密钥 * @return 明文 * @throws GeneralSecurityException public static String decrypt(String input, String key) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(AES); SecretKey keySpec = new SecretKeySpec(key.getBytes(CHARSET), AES); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] data = cipher.doFinal(Base64.getDecoder().decode(input)); return new String(data, CHARSET); public static void main(String[] args) throws GeneralSecurityException { String msg = "我喜欢你,可以做我女朋友吗?"; String key = "One-More12345678One.More87654321"; System.out.println("加密前:" + msg); String pwd = AesUtil.encrypt(msg, key); System.out.println("加密后:" + pwd); System.out.println("解密后:" + AesUtil.decrypt(pwd, key)); }运行结果如下:加密前:我喜欢你,可以做我女朋友吗? 加密后:dT29xX+XpPTO3OMuDw1ASyU/+aNfJ1K+endNUJ84h2KZhqRDNfiO4hAQjOXMCoN4 解密后:我喜欢你,可以做我女朋友吗?AES算法是目前应用最广泛的对称加密算法。总结对称加密算法在加密和解密时使用的密钥相同,常见的对称加密算法有:DES算法、3DES算法、AES算法。由于安全性低、加密解密效率低,DES算法和3DES算法是不推荐使用的,AES算法是目前应用最广泛的对称加密算法。

curl的HTTP参数速查表

curl简介curl是一个开源的命令行工具,它基于网络协议,对指定URL进行网络传输,得到数据后不任何具体处理(如:html的渲染等),直接显示在"标准输出"(stdout)上。curl支持的网络协议有很多,包括:DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET和TFTP。curl的参数也有很多,以下主要介绍HTTP和HTTPS相关的参数,建议收藏保存。参数速查表参数描述示例-A, --user-agent 发送指定的User-Agent到服务端curl -A "Agent 007" https://one.more--alt-svc 使用此缓存文件启用alt-svccurl --alt-svc svc.txt https://one.more--anyauth选择任意认证方法curl --anyauth --user me:pwd https://one.more--compressed请求压缩 (使用 deflate 或 gzip)curl --compressed https://one.more-b, --cookie <datafilename>发送cookie的字符串,或者读取发送cookie的文件位置curl -b cookies.txt https://one.more-c, --cookie-jar 操作结束后写入cookie到指定的文件curl -c cookies.txt https://one.more-d, --data HTTP POST 的数据curl -d "name=onemore" https://one.more--data-ascii HTTP POST 的ASCII数据curl --data-ascii @file https://one.more--data-binary HTTP POST 的二进制数据curl --data-binary @file https://one.more--data-raw HTTP POST 的数据(不对@字符做特殊解析)curl --data-raw "one@more" https://one.more--data-urlencode HTTP POST 的url编码的数据curl --data-urlencode name=onemore https://one.more--digest使用HTTP摘要访问认证curl -u name:password --digest https://one.more--disallow-username-in-url禁止在url中输入用户名curl --disallow-username-in-url https://one.more-D, --dump-header 把接收到的header写入到指定的文件curl --dump-header header.txt https://one.more-e, --referer 指定Referrer的URLcurl --referer "https://more.one" https://one.more--etag-compare 从文件中读取作为header中的ETagcurl --etag-compare etag.txt https://one.more--etag-save 从请求中解析ETag并将其保存到指定的文件中curl --etag-save etag.txt https://one.more--expect100-timeout 允许等待100-continue响应的最大时间(以秒为单位)curl --expect100-timeout 2.5 https://one.more-f, --fail在服务器出错时不显示HTTP错误信息curl --fail https://one.more-F, --form <name=content>模拟用户按下提交表单curl -F image=@onemore.jpg https://one.more--form-string <name=string>模拟用户按下提交表单(值为字符串)curl --form-string "data" https://one.more-G, --get将POST数据放在URL中并发起GET请求curl --get -d "name=onemore" https://one.more--haproxy-protocol发送HAProxy PROXY v1的headercurl --haproxy-protocol https://one.more-i, --include在输出中包含HTTP响应头curl -i https://one.more-I, --head只显示响应头信息curl -I https://one.more-H, --header <header/@file>发送自定义headercurl -H "Content-Type: application/json;charset=UTF-8" https://one.more--hsts 启用HSTS进行传输curl --hsts cache.txt https://one.more--http0.9使用HTTP 0.9curl --http0.9 https://one.more-0, --http1.0使用HTTP 1.0curl --http1.0 https://one.more--http1.1使用HTTP 1.1curl --http1.1 https://one.more--http2使用HTTP 2curl --http2 https://one.more--http2-prior-knowledge使用HTTP 2(不使用HTTP/1.1 Upgrade)curl --http2-prior-knowledge https://one.more--http3使用HTTP 3curl --http3 https://one.more--ignore-content-length忽略服务端资源的大小curl --ignore-content-length https://one.more-j, --junk-session-cookies忽略从文件中读取的会话cookiecurl --junk-session-cookies -b cookies.txt https://one.more-L, --location启用重定向curl --L https://one.more--location-trusted启用重定向并发送验证信息到其它主机curl --location-trusted -u user:pwd https://one.more--max-redirs 重定向的最大次数curl --max-redirs 3 --location https://one.more--negotiate使用HTTP Negotiate (SPNEGO) 认证curl --negotiate -u : https://one.more--no-alpn禁用ALPN TLS扩展curl --no-alpn https://one.more--no-npn禁用NPN TLS扩展curl --no-npn https://one.more--ntlm使用HTTP NTLM认证curl --ntlm -u user:pwd https://one.more--ntlm-wb使用HTTP NTLM认证(使用ntlmauth程序做身份验证)curl --ntlm-wb -u user:pwd https://one.more--post301在301重定向后不切换为 GET 请求curl --post301 --location https://one.more--post302在302重定向后不切换为 GET 请求curl --post302 --location https://one.more--post303在303重定向后不切换为 GET 请求curl --post303 --location https://one.more-r, --range 只接收范围内的字节curl --range 22-44 https://one.more--raw禁用所有内部HTTP对内容或传输编码的解码curl --raw https://one.more--tr-encoding请求压缩传输编码算法,并解压接收到的数据curl --tr-encoding https://one.more-v, --verbose显示通信的整个过程,包括端口连接和头信息。curl -v https://one.more-z, --time-cond 请求在给定时间之后或之前被修改的文件curl -z "Tue 18 Jan 2021 13:14:15" https://one.morecurl 版本号:7.74.0

curl常用参数详解及示例

curl简介curl是一个开源的命令行工具,它基于网络协议,对指定URL进行网络传输,得到数据后不任何具体处理(如:html的渲染等),直接显示在"标准输出"(stdout)上。curl支持的网络协议有很多,包括:DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET和TFTP。curl的参数也有很多,下面介绍一些常用的参数,建议收藏保存。发送GET请求当curl不带有任何参数时,curl默认发出 GET 请求,服务端返回的内容不会做任何解析直接在命令行显示。示例:curl http://www.csdn.net因为需要跳转到HTTPS,所以返回301:<html> <head><title>301 Moved Permanently</title></head> <body> 301 Moved Permanently <hr>openresty </body> </html>发送POST请求使用-d参数时,header的Content-Type被自动赋值为application/x-www-form-urlencoded,并且发送 POST 请求。示例: curl -d 'user=万猫学社&pwd=onemore' http://csdn.net/login因为需要跳转到HTTPS,同样返回301:<html> <head><title>301 Moved Permanently</title></head> <body> 301 Moved Permanently <hr>openresty </body> </html>发送json请求发送json请求还需要用到两个参数:-X参数指定 HTTP 请求的方法,-H参数指定 HTTP 请求的header。示例:curl -X POST -H "Content-Type: application/json; charset=UTF-8" -d '{"user":"万猫学","pwd":"onemore"}' http://www.csdn.net/login其中,-X参数指定 HTTP 请求的方法为 POST,-H蚕食指定header的 Content-Type 为 application/json; charset=UTF-8 ,-d参数指定数据为 {"user":"万猫学","pwd":"onemore"} 。显示HTTP响应头-i参数显示服务端响应内容的同时,也显示HTTP响应头。示例:curl -i http://www.csdn.net会先显示服务端的响应头,然后空一行,再显示服务端响应内容,如下:HTTP/1.1 301 Moved Permanently Server: openresty Date: Thu, 20 Jan 2022 11:59:42 GMT Content-Type: text/html Content-Length: 166 Connection: keep-alive Keep-Alive: timeout=20 Location: https://www.csdn.net/ <html> <head><title>301 Moved Permanently</title></head> <body> 301 Moved Permanently <hr>openresty </body> </html>显示响应过程-v参数显示的整个响应过程,我们可以看到底层到底发生了什么。示例:curl -v http://www.csdn.net显示如下:* About to connect() to www.csdn.net port 80 (#0) * Trying 39.106.226.142... * Connected to www.csdn.net (39.106.226.142) port 80 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.29.0 > Host: www.csdn.net > Accept: */* < HTTP/1.1 301 Moved Permanently < Server: openresty < Date: Thu, 20 Jan 2022 12:07:40 GMT < Content-Type: text/html < Content-Length: 166 < Connection: keep-alive < Keep-Alive: timeout=20 < Location: https://www.csdn.net/ <html> <head><title>301 Moved Permanently</title></head> <body> 301 Moved Permanently <hr>openresty </body> </html>其中,以*开头的行表示curl提供的额外信息,以>开头的行表示请求头, <开头的行表示响应头。只显示响应头有时候响应内容太长,只关心响应头时,可以使用-I参数。示例:curl -v http://www.csdn.net显示如下:HTTP/1.1 301 Moved Permanently Server: openresty Date: Thu, 20 Jan 2022 12:15:30 GMT Content-Type: text/html Content-Length: 166 Connection: keep-alive Keep-Alive: timeout=20 Location: https://www.csdn.net/参考链接:https://curl.se/docs/manpage.htmlhttps://www.ruanyifeng.com/blog/2019/09/curl-reference.html

一张图快速了解五大架构风格

架构风格架构风格定义了用于描述系统的术语表和一组指导构建系统的规则。架构风格反映了领域中众多系统所共有的结构和寓意特性,并指导如何将各个构件有效地组织成一个完整的系统。数据流风格批处理序列大量整体数据、无需用户交互构件为一系列固定顺序的计算单元,构件之间只通过数据传递交互。每个处理步骤是一个独立的程序,每一步必须在其前一步结束后才能开始,数据必须是完整的,以整体的方式传递。管道过滤器流式数据、弱用户交互每个构件都有一组输入和输出,构件读输入的数据流,经过内部处理,然后产生输出数据流。这个过程通常是通过对输入数据流的变换或计算来完成的,包括通过计算和增加信息以丰富数据、通过浓缩和删除以精简数据、通过改变记录方式以转化数据和递增地转化数据等。这里的构件称为过滤器,连接件就是数据流传输的管道,将一个过滤器的输出传到另一个过滤器的输入。调用返回风格主程序子程序面向过程单线程控制,把问题划分为若干个处理步骤,构件即为主程序和子程序,子程序通常可合成为模块。过程调用作为交互机制,即充当连接件的角色。调用关系具有层次性,其语义逻辑表现为主程序的正确性取决于它调用的子程序的正确性面向对象对象的方法调用构件是对象,对象是抽象数据类型的实例。在抽象数据类型中,数据的表示和它们的相应操作被封装起来,对象的行为体现在其接受和请求的动作。连接件即是对象间交互的方式,对象是通过函数和过程的调用来交互的层次结构层与层之间的方法调用构件组织成一个层次结构,连接件通过决定层间如何交互的协议来定义。每层为上一层提供服务,使用下一层的服务,只能见到与自己邻接的层。通过层次结构,可以将大的问题分解为若干个渐进的小问题逐步解决,可以隐藏问题的复杂度。修改某一层,最多影响其相邻的两层(通常只能影响上层)独立构件风格进程通信构件是独立的过程,连接件是消息传递。构件通常是命名过程,消息传递的方式可以是点对点、异步或同步方式,以及远程过程(方法)调用等。事件驱动(隐式调用)构件不直接调用一个过程,而是触发或广播一个或多个事件。构件中的过程在一个或多个事件中注册,当某个事件被触发时,系统自动调用在这个事件中注册的所有过程。一个事件的触发就导致了另一个模块中的过程调用。这种风格中的构件是匿名的过程,它们之间交互的连接件往往是以过程之间的隐式调用来实现的。主要优点是为软件复用提供了强大的支持,为构件的维护和演化带来了方便;其缺点是构件放弃了对系统计算的控制。虚拟机风格解释器解释器通常包括一个完成解释工作的解释引擎、一个包含将被解释的代码的存储区、一个记录解释引擎当前工作状态的数据结构,以及一个记录源代码被解释执行的进度的数据结构。具有解释器风格的软件中含有一个虚拟机,可以仿真硬件的执行过程和一些关键应用,其缺点是执行效率比较低。规则系统基于规则的系统包括规则集、规则解释器、规则/数据选择器和工作内存,一般用在人工智能领域和DSS中。仓库风格数据库系统构件主要有两大类,一类是中央共享数据源,保存当前系统的数据状态;另一类是多个独立处理单元,处理单元对数据元素进行操作。黑板系统包括知识源、黑板和控制三部分。知识源包括若干独立计算的不同单元,提供解决问题的知识。知识源响应黑板的变化,也只修改黑板;黑板是一个全局数据库,包含问题域解空间的全部状态,是知识源相互作用的唯一媒介;知识源响应是通过黑板状态的变化来控制的。黑板系统通常应用在对于解决问题没有确定性算法的软件中(信号处理、问题规划和编译器优化等)。超文本系统构件以网状链接方式相互连接,用户可以在构件之间进行按照人类的联想思维方式任意跳转到相关构件。超文本是一种非线性的网状信息组织方法,它以结点为基本单位,链作为结点之间的联想式关联。超文本系统通常应用在互联网领域。“你跟讲了这么多,说好的图呢?”“不要着急,最好的总是在不经意的时候出现。”graph LR A[软件架构风格] -->B(数据流风格) A --> C(调用返回风格) A --> D(独立构件风格) A --> E(虚拟机风格) A --> F(仓库风格) B --> B1(批处理序列) B --> B2(管道过滤器) C --> C1(主程序子程序) C --> C2(面向对象) C --> C3(层次结构) D --> D1(进程通信) D --> D2(事件驱动) E --> E1(解释器) E --> E2(规则系统) F --> F1(数据库系统) F --> F2(黑板系统) F --> F3(超文本系统)

手把手教你把 Git 子模块更新到主项目

本文以 skywalking-rocketbot-ui子模块合并到 skywalking 为例,手把手教你如何把 Git 子模块更新到主项目中去。首先,把fork的skywalking项目克隆到本地:OneMore MINGW64 /d/code $ git clone https://github.com/heihaozi/skywalking.git skywalking Cloning into 'skywalking'... remote: Enumerating objects: 241687, done. remote: Counting objects: 100% (373/373), done. remote: Compressing objects: 100% (201/201), done. remote: Total 241687 (delta 64), reused 240 (delta 21), pack-reused 241314 Receiving objects: 100% (241687/241687), 156.98 MiB | 3.83 MiB/s, done. Resolving deltas: 100% (93272/93272), done. Updating files: 100% (5928/5928), done.进入skywalking目录,设置用户名和邮箱:OneMore MINGW64 /d/code $ cd skywalking/ OneMore MINGW64 /d/code/skywalking (master) $ git config user.name CharliePu OneMore MINGW64 /d/code/skywalking (master) $ git config user.email heihaozi2006@163.com指定将与复刻同步的远程上游仓库:OneMore MINGW64 /d/code/skywalking (master) $ git remote add upstream https://github.com/apache/skywalking.git查看一下远程上游仓库是否生效:OneMore MINGW64 /d/code/skywalking (master) $ git remote -v origin https://github.com/heihaozi/skywalking.git (fetch) origin https://github.com/heihaozi/skywalking.git (push) upstream https://github.com/apache/skywalking.git (fetch) upstream https://github.com/apache/skywalking.git (push)没有问题,初始化本地子模块:OneMore MINGW64 /d/code/skywalking (master) $ git submodule init Submodule 'apm-protocol/apm-network/src/main/proto' (https://github.com/apache/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/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/skywalking-rocketbot-ui.git) registered for path 'skywalking-ui' Submodule 'test/e2e/e2e-protocol/src/main/proto' (https://github.com/apache/skywalking-data-collect-protocol.git) registered for path 'test/e2e/e2e-protocol/src/main/proto'从子模块的远端更新修改:OneMore MINGW64 /d/code/skywalking (master) $ git submodule update Cloning into 'D:/code/skywalking/apm-protocol/apm-network/src/main/proto'... Cloning into 'D:/code/skywalking/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol'... Cloning into 'D:/code/skywalking/skywalking-ui'... Cloning into 'D:/code/skywalking/test/e2e/e2e-protocol/src/main/proto'... Submodule path 'apm-protocol/apm-network/src/main/proto': checked out 'e626ee04850703c220f64b642d2893fa65572943' Submodule path 'oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol': checked out '47202fc1eaa1864c587a78f423a0685ffbe294ad' Submodule path 'skywalking-ui': checked out '9e56d6cbbaff4678751f5355b953db3bbfd99c9b' Submodule path 'test/e2e/e2e-protocol/src/main/proto': checked out 'e626ee04850703c220f64b642d2893fa65572943'从子模块的远端拉取上游的修改:OneMore MINGW64 /d/code/skywalking (master) $ git submodule update --remote Submodule path 'skywalking-ui': checked out '774b69dd84e305be975e4c5ffc0d433aa8cbda32'检查当前文件状态:OneMore MINGW64 /d/code/skywalking (master) $ git status On branch master Your branch is up to date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: skywalking-ui (new commits) no changes added to commit (use "git add" and/or "git commit -a")发现skywalking-ui已经有更新了,可以直接将其提交到远端,也可以修改其他文件一起提交。这里先修改一下CHANGES.md文件,然后一起提交:OneMore MINGW64 /d/code/skywalking (master) $ git add skywalking-ui OneMore MINGW64 /d/code/skywalking (master) $ git add CHANGES.md OneMore MINGW64 /d/code/skywalking (master) $ git status On branch master Your branch is up to date with 'origin/master'. Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: CHANGES.md modified: skywalking-ui OneMore MINGW64 /d/code/skywalking (master) $ git push origin master Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 409 bytes | 409.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (2/2), completed with 2 local objects. To https://github.com/heihaozi/skywalking.git 50688c187..e4a61f183 master -> master至此,大功告成,可以向社区提交PR了。

RocketMQ的invokeSync call timeout异常的解决办法

缘起在RocketMQ客户端的DefaultMQPushConsumer的start方法被执行时,时不时会报出invokeSync call timeout异常,如下:Caused by: java.lang.IllegalStateException: org.apache.rocketmq.remoting.exception.RemotingTimeoutException: invokeSync call timeout at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:679) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:509) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl.updateTopicSubscribeInfoWhenSubscriptionChanged(DefaultMQPushConsumerImpl.java:872) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl.start(DefaultMQPushConsumerImpl.java:653) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.consumer.DefaultMQPushConsumer.start(DefaultMQPushConsumer.java:698) ~[rocketmq-client-4.7.1.jar:4.7.1] at cn.xdf.xcloud.rocketmq.support.DefaultRocketMQListenerContainer.start(DefaultRocketMQListenerContainer.java:276) ~[xcloud-rocketmq-core-1.2.0.RELEASE.jar:1.2.0.RELEASE] at cn.xdf.xcloud.rocketmq.autoconfigure.ListenerContainerConfiguration.registerContainer(ListenerContainerConfiguration.java:103) ~[xcloud-rocketmq-core-1.2.0.RELEASE.jar:1.2.0.RELEASE] ... 12 common frames omitted Caused by: org.apache.rocketmq.remoting.exception.RemotingTimeoutException: invokeSync call timeout at org.apache.rocketmq.remoting.netty.NettyRemotingClient.invokeSync(NettyRemotingClient.java:375) ~[rocketmq-remoting-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.MQClientAPIImpl.getTopicRouteInfoFromNameServer(MQClientAPIImpl.java:1363) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.MQClientAPIImpl.getTopicRouteInfoFromNameServer(MQClientAPIImpl.java:1353) ~[rocketmq-client-4.7.1.jar:4.7.1] at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:622) ~[rocketmq-client-4.7.1.jar:4.7.1] ... 18 common frames omitted如果着急马上找到解决办法,可以直接跳到解决办法。不过,授人以鱼,不如授之以渔。还是建议把寻找解决办法的过程看完,第一:可以给你以后遇到类似问题提供解决思路;第二:虽然都报这个异常,但产生的原因可能不一样。寻找解决办法之路做为面向搜索引擎编程的一员,立马复制关键字invokeSync call timeout去搜索引擎,得到的解决办法总结起来有两点:RocketMQ客户端和服务端版本不一致,检查了一下客户端和服务端的版本,都是4.7.1。降低RocketMQ客户端的版本,这个我时不能接受的。搜索引擎无法解决,只能自己想办法了。首先找到报异常的地方: public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException { long beginStartTime = System.currentTimeMillis(); final Channel channel = this.getAndCreateChannel(addr); if (channel != null && channel.isActive()) { try { doBeforeRpcHooks(addr, request); long costTime = System.currentTimeMillis() - beginStartTime; if (timeoutMillis < costTime) { throw new RemotingTimeoutException("invokeSync call timeout"); RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime); doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response); return response; //省略部分无关代码 } else { this.closeChannel(addr, channel); throw new RemotingConnectException(addr); }原来是因为代码执行的时间过长,才报出了invokeSync call timeout异常。首先想到的是延长超时时间,继续分析源码,向上寻找调用方,发现在MQClientInstance的updateTopicRouteInfoFromNameServer方法中有:topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);居然是写死了3秒,没有办法修改,我竟无语凝噎。再向下一步一步地分析源码,到底是哪里慢?org.apache.rocketmq.remoting.netty.NettyRemotingClient.getAndCreateChannel org.apache.rocketmq.remoting.netty.NettyRemotingClient.getAndCreateNameserverChannel org.apache.rocketmq.remoting.netty.NettyRemotingClient.createChannel io.netty.bootstrap.Bootstrap.connect(java.net.SocketAddress) io.netty.bootstrap.Bootstrap.doResolveAndConnect io.netty.bootstrap.AbstractBootstrap.initAndRegister io.netty.bootstrap.ChannelFactory.newChannel io.netty.channel.socket.nio.NioSocketChannel.NioSocketChannel() io.netty.channel.nio.AbstractNioChannel.AbstractNioChannel io.netty.channel.AbstractChannel.AbstractChannel(io.netty.channel.Channel) io.netty.channel.AbstractChannel.newId io.netty.channel.DefaultChannelId.newInstance最终找到了: public static DefaultChannelId newInstance() { return new DefaultChannelId(); }在创建DefaultChannelId的实例时,执行了这个类的静态代码块,就是这段静态代码块比较耗时。那么,解决办法就有了,提前加载DefaultChannelId类,使其静态代码块先执行完成。解决办法在调用DefaultMQPushConsumer的start方法之前,插入如下代码:DefaultChannelId.newInstance();

2021年北京积分落户名单公布了,爬了两个多小时得到了所有数据,有了惊人的发现(附源码)

2021年北京积分落户名单公布了,手痒痒就写了一段Java代码,运行了两个多小时,终于到了所有数据,如下截图:本着“Talk is cheap, Show me the code.”的原则,先看一下源码。源码落户实体类先写一个落户实体类,便于储存和分析。 @Setter @Getter static class Person { private int id; private String number; private String name; private int year; private int month; private String company; private double totalScore; private double[] detailScore; }获取落户名单获取落户名单的Ajax请求返回的居然是HTML,想法比较惊奇。直接写个正则表达式,提取想要的数据。 private final static Pattern LIST_PATTERN = Pattern.compile( "<tr>[^<]*?<td[^>]*?>(\\S*?)</td>[^<]*?<td[^>]*?>(\\S*?)</td>[^<]*?<td[^>]*?>(\\d+)\\-(\\d+)</td>[^<]*?<td[^>]*?>(\\S*?)</td>[^<]*?<td[^>]*?>(\\S*?)</td>[^<]*?<td[^>]*?>[^<]*?<a[\\s\\S]*?onclick=\"showDetails\\('(\\d+)'\\)\">查看</a>[^<]*?</td>[^<]*?</tr>"); private static List<Person> findPersonList() throws InterruptedException { String url = "http://fuwu.rsj.beijing.gov.cn/jf2021integralpublic/settlePerson/tablePage"; List<Person> personList = new ArrayList<>(); for (int page = 0; page <= 6040; page += 10) { Map<String, String> params = new HashMap<>(); params.put("name", ""); params.put("rows", "10"); params.put("page", Integer.toString(page)); String result = HttpUtils.doPost(url, params); Matcher matcher = LIST_PATTERN.matcher(result); while (matcher.find()) { Person person = new Person(); person.setNumber(matcher.group(1)); person.setName(matcher.group(2)); person.setYear(Integer.parseInt(matcher.group(3))); person.setMonth(Integer.parseInt(matcher.group(4))); person.setCompany(matcher.group(5)); person.setTotalScore(Double.parseDouble(matcher.group(6))); person.setId(Integer.parseInt(matcher.group(7))); personList.add(person); log.info("page: {} ", page); Thread.sleep(1000); return personList; }获取积分详情积分详情的Ajax请求返回也是HTML,直接写10个正则表达式,提取想要的数据。 private final static Pattern[] DETAIL_PATTERN_ARRAY = { Pattern.compile("合法稳定就业</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("合法稳定住所</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("教育背景</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("扣除取得学历(学位)期间累计的居住及就业分值</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("创新创业</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("职住区域</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("纳税</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("年龄</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("荣誉表彰</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), Pattern.compile("守法记录</td>[^<]*?<td[^>]*?>([\\d\\.\\-]+)"), private static void enrichPersonList(List<Person> personList) throws InterruptedException { String url = "http://fuwu.rsj.beijing.gov.cn/jf2021integralpublic/settlePerson/settlePersonDetails"; for (int i = 0; i < personList.size(); i++) { Person person = personList.get(i); Map<String, String> params = new HashMap<>(); params.put("id", Integer.toString(person.getId())); String result = HttpUtils.doPost(url, params); double[] detailScore = new double[DETAIL_PATTERN_ARRAY.length]; for (int j = 0; j < DETAIL_PATTERN_ARRAY.length; j++) { Matcher matcher = DETAIL_PATTERN_ARRAY[j].matcher(result); if (matcher.find()) { detailScore[j] = Double.parseDouble(matcher.group(1)); } else { log.error("index: {}\n{}", j, result); person.setDetailScore(detailScore); log.info("person count: {} / {}", i, personList.size()); Thread.sleep(1000); }数据分析现在已经有很多统计和分析,比如:年龄分布、公司排名,都已经烂大街了,一搜就能搜到,我们来看看不一样的。有163人没上过大学,其中有19人年薪超过65万,占比11.65%;有5882人上了大学,其中有1476人年薪超过65万,占比25.09%。所以,要想获得更好的生活条件和境遇,需要更高的学历。

3分钟整明白 缓存热点 是咋回事

当数据库成为瓶颈时,比如高并发、读多写少等场景,我们首先会想到的就是利用缓存来提高整个系统的性能。缓存虽然能够大大提升整个系统的性能,但同时也引入了更多复杂性。如果没有针对缓存进行比较好的处理,某些场景下甚至会导致整个系统崩溃。这次我们要聊的就是:缓存热点缓存热点缓存热点是指大部分甚至所有的业务请求都命中同一份缓存数据。虽然缓存本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也会很大。比如,电商的爆品秒杀活动,短时间内被上千万的用户访问。解决办法缓存热点的常用解决方法有两种:复制多份缓存副本和本地内存缓存。接下来我们详细了解一下这两种方法:复制多份缓存副本复制多份缓存副本,就是将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。以爆品秒杀活动为例,爆品数据都可以生成 100 个缓存数据,缓存的数据是一样的,在缓存的 key 里面加上编号进行区分,每次读缓存时都随机读取其中某个缓存。在设计缓存副本的时候,有一个细节需要注意:不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存的雪崩效应。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值。本地内存缓存把热点数据缓存在客户端的本地内存中,并且设置一个失效时间。对于每次读请求,将首先检查该数据是否存在于本地缓存中,如果存在则直接返回,如果不存在再去访问分布式缓存的服务器。与复制多份缓存副本比较,本地内存缓存彻底“解放”了缓存服务器,不会对缓存服务器有任何压力。不过,本地内存缓存也有自己的缺点,假如缓存服务器中数据被被更新了,本地内存缓存没有及时更新,就导致数据不一致的情况。因此,本地内存缓存的失效时间需要设置尽量短一些。热点缓存的发现在前面的讨论中,我们预设了知道热点缓存有哪些,但是在大多数情况下,我们是无法知道哪些是热点缓存。不过,我们可以使用以下方法及时发现热点缓存:在客户端进行统计。在读取缓存时,加入数据统计的逻辑。当读取次数达到某个阈值时,判断其为热点缓存。在Proxy层进行统计。当读取缓存时有Proxy层时,可以在Proxy层加入数据统计的逻辑。使用缓存系统的自带工具。比如Redis性能分析工具redis-faina、redis-cli的hotkeys参数。总结缓存热点是指大部分甚至所有的业务请求都命中同一份缓存数据。常用的解决方法有两种:复制多份缓存副本,每个副本过期时间设置为指定范围内的随机值;本地内存缓存,缓存失效时间尽量设置短一些。

详解 Apache SkyWalking 的跨进程传播协议

简介SkyWalking 跨进程传播协议是用于上下文的传播,本文介绍的版本是3.0,也被称为为sw8协议。Header项Header应该是上下文传播的最低要求。Header名称:sw8.Header值:由-分隔的8个字段组成。Header值的长度应该小于2KB。Header值Header值中具体包含以下8个字段:采样(Sample),0 或 1,0 表示上下文存在, 但是可以(也很可能)被忽略;1 表示这个追踪需要采样并发送到后端。追踪ID(Trace Id),是 BASE64 编码的字符串,其内容是由 . 分割的三个 long 类型值, 表示此追踪的唯一标识。父追踪片段ID(Parent trace segment Id),是 BASE64 编码的字符串,其内容是字符串且全局唯一。父跨度ID(Parent span Id),是一个从 0 开始的整数,这个跨度ID指向父追踪片段(segment)中的父跨度(span)。父服务名称(Parent service),是 BASE64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。父服务实例标识(Parent service instance),是 BASE64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。父服务的端点(Parent endpoint),是 BASE64 编码的字符串,其内容是父追踪片段(segment)中第一个入口跨度(span)的操作名,由长度小于或等于50个UTF-8编码的字符组成。本请求的目标地址(Peer),是 BASE64 编码的字符串,其内容是客户端用于访问目标服务的网络地址(不一定是 IP + 端口)。Header值示例上面的说明太干了,我们来举一个具体的例子,可以更好的理解。有两个服务,分别叫onemore-a和 onemore-b,用户通过HTTP调用onemore-a的/onemore-a/get,然后onemore-a的/onemore-a/get又通过HTTP调用onemore-b的/onemore-b/get,流程图就是这样的:那么,我们在onemore-b的/onemore-b/get的Header中就可以发现一个叫做sw8的key,其值为:1-YTRlYzZmYzhjY2FiNGJiNGI2ODIwNjQ2OThjYzk3ZTYuNzQuMTYyMTgzODExMDQ1NTAwMDk=-YTRlYzZmYzhjY2FiNGJiNGI2ODIwNjQ2OThjYzk3ZTYuNzQuMTYyMTgzODExMDQ1NTAwMDg=-2-b25lbW9yZS1h-ZTFkMmZiYjYzYmJhNDMwNDk5YWY4OTVjMDQwZTMyZmVAMTkyLjE2OC4xLjEwMQ==-L29uZW1vcmUtYS9nZXQ=-MTkyLjE2OC4xLjEwMjo4MA==以-字符进行分割,可以得到:1,采样,表示这个追踪需要采样并发送到后端。YTRlYzZmYzhjY2FiNGJiNGI2ODIwNjQ2OThjYzk3ZTYuNzQuMTYyMTgzODExMDQ1NTAwMDk=,追踪ID,解码后为:a4ec6fc8ccab4bb4b682064698cc97e6.74.16218381104550009YTRlYzZmYzhjY2FiNGJiNGI2ODIwNjQ2OThjYzk3ZTYuNzQuMTYyMTgzODExMDQ1NTAwMDg=,父追踪片段ID,解码后为:a4ec6fc8ccab4bb4b682064698cc97e6.74.162183811045500092,父跨度ID。b25lbW9yZS1h,父服务名称,解码后为:onemore-aZTFkMmZiYjYzYmJhNDMwNDk5YWY4OTVjMDQwZTMyZmVAMTkyLjE2OC4xLjEwMQ==,父服务实例标识,解码后为:e1d2fbb63bba430499af895c040e32fe@192.168.1.101L29uZW1vcmUtYS9nZXQ=,父服务的端点,解码后为:/onemore-a/getMTkyLjE2OC4xLjEwMjo4MA==,本请求的目标地址,解码后为:192.168.1.102:80扩展Header项扩展Header项是为高级特性设计的,它提供了部署在上游和下游服务中的探针之间的交互功能。Header名称:sw8-xHeader值:由-分割,字段可扩展。扩展Header值当前值包括的字段:追踪模式(Tracing Mode),空、0或1,默认为空或0。表示在这个上下文中生成的所有跨度(span)应该跳过分析。在默认情况下,这个应该在上下文中传播到服务端,除非它在跟踪过程中被更改。

速查列表:Apache SkyWalking OAL 的 域(Scopes)

OAL简介在流模式(Streaming mode)下,SkyWalking 提供了 观测分析语言(Observability Analysis Language,OAL) 来分析流入的数据。OAL脚本现在位于/config文件夹,用户可以简单地改变和重新启动服务器,使其有效。但是,OAL脚本仍然是编译语言,OAL运行时动态生成Java代码。可以在系统环境上设置SW_OAL_ENGINE_DEBUG=Y,查看生成了哪些类。作用域(Scope)作用域包括全局(All)、服务(Service)、服务实例(Service Instance)、端点(Endpoint)、服务关系(Service Relation)、服务实例关系(Service Instance Relation)、端点关系(Endpoint Relation)。 接下来我来详细了解一下每个作用域。全局(All)名称备注是否分组键数据类型name表示每个请求对应的服务名称 stringserviceInstanceName表示引用的服务实例名称 stringendpoint表示每个请求的端点名称 stringlatency表示每个请求的耗时 int(单位:毫秒)status表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 则表示 HTTP 响应的状态码. 如 200, 404, 302 inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enum服务(Service)按照服务计算每个请求的度量数据。名称备注是否分组键数据类型name表示服务的名称 stringnodeType表示服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumserviceInstanceName表示引用的服务实例名称 stringendpointName表示端点的名称, 如:HTTP URI的完整路径 stringlatency表示每个请求的耗时 intstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 表示 HTTP 请求的响应码 inttype表示每个请求的类型, 如: Database, HTTP, RPC, gRPC enum服务实例(Service Instance)按照服务实例计算每个请求的度量数据。名称备注是否分组键数据类型name表示服务实例名称。现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringnodeType表示服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumserviceName表示服务的名称 stringendpointName表示端点的名称, 如 HTTP URI 的完整路径. stringlatency表示每个请求的耗时 intstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 则表示 HTTP 响应的状态码. 如 200, 404, 302. inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enum服务实例(Service Instance)二级作用域当服务实例是一个 JVM 并且通过 javaagent 收集时,计算服务实例的度量指标。1. ServiceInstanceJVMCPU名称备注是否分组键数据类型name表示服务实例名称。现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringserviceName表示服务的名称 stringusePercent表示 CPU 耗时百分比 double2. ServiceInstanceJVMMemory名称备注是否分组键数据类型name表示服务实例名称。 现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringserviceName表示服务的名称 stringheapStatus表示该指标是否是堆的指标 boolinit参考 JVM 文档 longmax参考 JVM 文档 longused参考 JVM 文档 longcommitted参考 JVM 文档 long3. ServiceInstanceJVMMemoryPool名称备注是否分组键数据类型name表示服务实例名称。现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringserviceName表示服务的名称 stringpoolType根据不同的 JVM 版本, 可能包括 CODE_CACHE_USAGE, NEWGEN_USAGE, OLDGEN_USAGE, SURVIVOR_USAGE, PERMGEN_USAGE, METASPACE_USAGE enuminit参考 JVM 文档 longmax参考 JVM 文档 longused参考 JVM 文档 longcommitted参考 JVM 文档 long4. ServiceInstanceJVMGC名称备注是否分组键数据类型name表示服务实例名称。现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringserviceName表示服务的名称 stringphraseNEW/OLD EnumtimeGC 耗时 longcountGC 次数 long5. ServiceInstanceJVMThread名称备注是否分组键数据类型name表示服务实例名称。现在原生探针使用 uuid@ipv4 作为服务实例名称, 当需要在聚合中设置过滤器(Filter)时,这个毫无用处。 stringserviceName表示服务的名称 stringliveCount表示活跃线程的当前数量 intdaemonCount表示当前守护进程线程的数量 intpeakCount表示当前峰值线程数 int端点(Endpoint)计算服务中每个端点请求的度量指标。名称备注是否分组键数据类型name表示端点的名称, 如 HTTP URI 的完整路径. stringnodeType表示服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumserviceName表示服务的名称 stringserviceInstanceName表示引用的服务实例 id 的名称. stringlatency表示每个请求的耗时 intstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 则表示 HTTP 响应的状态码,如:200, 404, 302. inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enum服务关系(Service Relation)计算服务与服务之间每个请求的度量指标。名称备注是否分组键数据类型sourceServiceName表示源服务的名称 stringsourceServiceNodeType表示源服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumsourceServiceInstanceName表示源服务实例名称 stringdestServiceName表示目标服务的名称 stringdestServiceNodeType表示目标服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumdestServiceInstanceName表示目标服务实例名称 stringendpoint表示本次调用中使用的端点 stringcomponentId表示本次调用中使用到的组件 ID是stringlatency表示每个请求的耗时 intstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 则表示 HTTP 响应的状态码. 如 200, 404, 302. inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enumdetectPoint本地请求探测点位置,如:client, server, proxy.是enumtlsMode表示源服务和目标服务之间的TLS模式,如:service_relation_mtls_cpm = from(ServiceRelation.*).filter(tlsMode == "mTLS").cpm() string服务实例关系(Service Instance Relation)计算服务实例与服务实例之间每个请求的度量指标。名称备注是否分组键数据类型sourceServiceName表示源服务的名称 stringsourceServiceNodeType表示源服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumsourceServiceInstanceName表示源服务实例名称 stringdestServiceName表示目标服务的名称 destServiceNodeType表示目标服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumdestServiceInstanceName表示目标服务实例名称 stringendpoint表示本次调用中使用的端点 stringcomponentId表示本次调用中使用到的组件 ID是stringlatency表示每个请求的耗时 intstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求, 则表示 HTTP 响应的状态码. 如 200, 404, 302. inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enumdetectPoint本地请求探测点位置,如:client, server, proxy.是enumtlsMode表示源服务和目标服务之间的TLS模式,如:service_relation_mtls_cpm = from(ServiceRelation.*).filter(tlsMode == "mTLS").cpm() string端点关系(Endpoint Relation)计算一个端点和另一个端点之间的依赖关系的度量数据。这种关系很难检测,也依赖于跟踪库来传播上一个端点。所以端点关系范围聚合,仅仅在使用 SkyWalking 原生探针进行追踪的情况下才有效,包括自动打点探针(如 Java, .NET),OpenCensus SkyWalking exporter以及其他传播追踪上下文的实现.名称备注是否分组键数据类型endpoint表示父级端点名称 stringserviceName表示父级端点的服务名称 stringserviceNodeType表示父级端点的服务节点或网络地址的类型,如:Normal, Database, MQ, Cache enumchildEndpoint表示子父级端点名称 stringchildServiceName表示子级端点的服务名称 stringchildServiceNodeType表示子级端点的服务节点或网络地址的类型,如:Normal, Database, MQ, Cache stringchildServiceInstanceName表示子级端点的服务实例名称 stringrpcLatency表示RPC请求的耗时,排除了父级端点自身的耗时 intcomponentId表示此调用中使用的组件ID是stringstatus表示请求成功还是失败的状态 bool(true 表示成功)responseCode如果该请求是 HTTP 请求,则表示 HTTP 响应的状态码 inttype表示请求类型,例如: Database, HTTP, RPC, gRPC enumdetectPoint本地请求探测点位置,如:client, server, proxy.是enum浏览器传输(BrowserAppTraffic)计算浏览器应用程序的每个请求的度量数据。名称备注是否分组键数据类型name表示每个请求的浏览器应用程序名称 stringcount表示请求数,固定为1 inttrafficCategory表示传输类别,如:NORMAL, FIRST_ERROR, ERROR enumerrorCategory代表错误类别,如:AJAX, RESOURCE, VUE, PROMISE, UNKNOWN enum浏览器单一版本传输(BrowserAppSingleVersionTraffic)计算浏览器应用程序中浏览器单一版本的每个请求的度量数据。名称备注是否分组键数据类型name表示每个请求的单一版本名 stringserviceName表示浏览器应用程序的名称 stringcount表示请求数,固定为1 inttrafficCategory表示传输类别,如:NORMAL, FIRST_ERROR, ERROR enumerrorCategory代表错误类别,如:AJAX, RESOURCE, VUE, PROMISE, UNKNOWN enum浏览器页面传输(BrowserAppPageTraffic)计算浏览器应用程序中页面的每个请求的度量数据。名称备注是否分组键数据类型name表示每个请求的页面名称 stringserviceName表示浏览器应用程序的名称 stringcount表示请求数,固定为1 inttrafficCategory表示传输类别,如:NORMAL, FIRST_ERROR, ERROR enumerrorCategory代表错误类别,如:AJAX, RESOURCE, VUE, PROMISE, UNKNOWN enum浏览器页面耗时(BrowserAppPagePerf)计算浏览器应用程序中页面的每个请求耗时的度量数据。名称备注是否分组键数据类型name表示每个请求的页面名称 stringserviceName表示浏览器应用程序的名称 stringredirectTime表示重定向的耗时 int(单位:毫秒)dnsTime表示DNS查询的耗时 int(单位:毫秒)ttfbTime发出页面请求到接收到应答数据第一个字节所花费的耗时 int(单位:毫秒)tcpTimeTCP连接的耗时 int(单位:毫秒)transTime内容传输的耗时 int(单位:毫秒)domAnalysisTimeDOM结构分析的耗时 int(单位:毫秒)fptTime首次渲染时间/白屏时间 int(单位:毫秒)domReadyTimeDOM结构准备的耗时 int(单位:毫秒)loadPageTime整个页面加载时间 int(in ms)resTime页面中同步加载资源的耗时 int(单位:毫秒)sslTime仅对HTTPS有效 int(单位:毫秒)ttlTime首次交互时间 int(单位:毫秒)firstPackTime第一个包的时间 int(单位:毫秒)fmpTime首次有效绘制时间 int(单位:毫秒)注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

详解 Apache SkyWalking OAP 的分布式计算

SkyWalking的OAP(Observability Analysis Platform,观测分析平台)是一个用于链路数据的分布式计算系统。因为它巧妙的设计,使得在链路数据计算和聚合过程中,不需要考虑数据的一致性,也没有事务、分布式锁等概念。在极端情况下,可能出现链路数据的丢失,但会最大限度保障OAP集群的可用性。咱们来看一下,它是如何设计的,为以后的系统设计和架构提供一些思路。数据类型在介绍分布式计算之前,咱们先了解一下需要计算的数据都有哪些类型:Record数据,即明细数据,如Trace、访问日志等数据,由RecordStreamProcessor进行处理。Metrisc数据,即指标数据,绝大部分的OAL指标都会生成这种数据,由MetricsStreamProcessor进行处理。TopN数据,即周期性采样数据,如慢SQL的周期性采集,由TopNStreamProcessor进行处理。分布式计算像Trace、访问日志等这样的明细数据,数据量比较大,但是不需要归并处理,所以在OAP节点内部处理即可完成。明细数据采用缓存、异步批量处理和流式写入的方式写入到存储中。绝大部分由OAL(Observability Analysis Language,观测分析语言)定义的指标数据是需要分布式聚合计算的,所以在OAP集群计算流中分成了两种步骤。步骤一:接收和解析探针发送的数据,并进行当前OAP节点内的数据聚合,使用OAL或者其他聚合模式。如果是不需要分布式聚合的数据,直接写入到存储中;如果是需要分布式聚合的数据,根据一定的路由规则发送给指定的OAP节点。步骤二:接收和解析经步骤一处理过的数据,然后进行二次聚合计算,并写入到存储中。因为上面两个步骤极有可能不在同一个OAP节点上,所以OAP节点被分为Receiver(步骤一)和Aggregator(步骤二)两种角色。为了减少部署难度,所有OAP节点在默认情况下都会使用Mixed角色(既可以进行步骤一的操作,也可以进行步骤二的操作)。在大规模部署的时候,可以根据网络流量进行角色分离的两级部署。指标数据是计算资源消耗最大的分布式计算,也是整套分布式计算要支持的核心计算类型。在此计算过程中,使用哈希路由策略,根据计算的实体,如服务ID、端点ID等的哈希值来选择对应的OAP节点。OAP节点之间的通信采用的是 gRPC stream 模式,传输过程中不包含业务字段名称,按照数据类型和字段定义顺序进行序列化,减少非数据字段的传输。注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

学妹一反常态主动联系我,我要不要答应帮她?

之前在学校举办的活动上,认识了一个学妹。我死磨硬泡终于加了她的微信,经常给她发微信。可是她总是对我爱答不理的,我心里总有一天让你高攀不起,后来就很少联系了。今天突然主动联系我:这么久没联系了,一上来就让我帮忙?这照片拍的,手抖的像是得了帕金森似的,字都有重影。放大照片仔细看,这应该是某大厂的笔试题吧。这些题都不是很难,答案脱口而出。面向对象的三个特性分别是哪些?封装封装是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。继承继承是让某个类获得另一个类的属性和方法。它可以使用现有类的除了私有以外的所有功能,不需要重新编写原来的类的情况下对这些功能进行扩展。多态多态是一个类实例的相同方法在不同情形有不同表现形式,多态机制使具有不同内部结构的对象可以共享相同的外部接口。什么是双亲委派模型?类加载器收到类加载的请求后,它不会首先自己去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。JVM的内存分几个区域?程序计数器当前线程所执行的字节码的行号指示器。虚拟机栈Java方法执行的内存模型,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。本地方法栈本地方法执行的内存模型,和虚拟机栈非常相似,其区别是本地方法栈为JVM使用到的Native方法服务。堆用于存储对象实例,是垃圾收集器管理的主要区域。方法区用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JVM是如何判断对象是否可以被回收的?一个对象到GC Roots没有任何引用链相连,JVM就可以对这些对象进行回收了。要是再答上哪些是 GC Roots就更加分了:Java虚拟机栈中引用的对象本地方法栈中引用的对象静态属性引用的对象常量引用的对象分代垃圾回收器是如何工作的?堆被划分为年轻代Young Generation)和老年代(Old Generation)。年轻代又被划分为一个Eden区和两个Survivor区,大部分对象在Eden区中生成。当Eden空间不足时,触发Minor GC,Eden空间和一个Survivor区中存活的对象将被转移到另外一个Survivor区。移动时存活的对象年龄加1,当年龄到达指定值后移动到老年代。当老年代空间不足时,触发Full GC,回收整个堆的垃圾。由此我不禁想起了《倚天屠龙记》的一句台词:如果是你,你会怎么做?在评论区告诉我!感谢一键三连!

如何开启Apache SkyWalking的自监控?

1. 开启Prometheus遥测数据默认情况下, 遥测功能(telemetry)是关闭的(selector 为 none),像这样:telemetry: selector: ${SW_TELEMETRY:none} none: prometheus: host: ${SW_TELEMETRY_PROMETHEUS_HOST:0.0.0.0} port: ${SW_TELEMETRY_PROMETHEUS_PORT:1234} sslEnabled: ${SW_TELEMETRY_PROMETHEUS_SSL_ENABLED:false} sslKeyPath: ${SW_TELEMETRY_PROMETHEUS_SSL_KEY_PATH:""} sslCertChainPath: ${SW_TELEMETRY_PROMETHEUS_SSL_CERT_CHAIN_PATH:""}Prometheus 可做为遥测功能(telemetry)的实现者。使用这个功能,Prometheus 就可以收集 Skywalking OAP 的 metrics 数据。编辑config/application.yml文件,把selector 设置为 prometheus,像这样:telemetry: selector: ${SW_TELEMETRY:prometheus} none: prometheus: host: ${SW_TELEMETRY_PROMETHEUS_HOST:0.0.0.0} port: ${SW_TELEMETRY_PROMETHEUS_PORT:1234} sslEnabled: ${SW_TELEMETRY_PROMETHEUS_SSL_ENABLED:false} sslKeyPath: ${SW_TELEMETRY_PROMETHEUS_SSL_KEY_PATH:""} sslCertChainPath: ${SW_TELEMETRY_PROMETHEUS_SSL_CERT_CHAIN_PATH:""}默认情况下,端点在开放在 http://0.0.0.0:1234/ 和 http://0.0.0.0:1234/metrics 。也可以根据需要设置主机和端口。2. 开启 Prometheus FetcherSkyWalking 支持将 Prometheus 遥测数据直接收集到 OAP 后台。用户可以通过 UI 或 GraphQL API 查看它们。默认情况下,Prometheus Fetcher是关闭的(active 为 false),像这样:prometheus-fetcher: selector: ${SW_PROMETHEUS_FETCHER:default} default: active: ${SW_PROMETHEUS_FETCHER_ACTIVE:false}编辑config/application.yml文件,把active 设置为 true,像这样:prometheus-fetcher: selector: ${SW_PROMETHEUS_FETCHER:default} default: active: ${SW_PROMETHEUS_FETCHER_ACTIVE:true}3. 查看自监控数据重启 OAP ,让修改的配置文件生效。在 UI 中选择 SelfObservability ,然后在服务列表中选择 oap-server ,效果如下图:注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

一篇文章快速搞懂 Apache SkyWalking 的 OAL

OAL简介在流模式(Streaming mode)下,SkyWalking 提供了 观测分析语言(Observability Analysis Language,OAL) 来分析流入的数据。OAL 聚焦于服务,服务实例以及端点的度量指标,因此 OAL 非常易于学习和使用。6.3版本以后,OAL引擎嵌入在OAP服务器运行时中,称为oal-rt(OAL运行时)。OAL脚本现在位于/config文件夹,用户可以简单地改变和重新启动服务器,使其有效。但是,OAL脚本仍然是编译语言,OAL运行时动态生成Java代码。您可以在系统环境上设置SW_OAL_ENGINE_DEBUG=Y,查看生成了哪些类。OAL语法OAL 脚本文件应该以 .oal 为后缀。// Declare the metrics. METRICS_NAME = from(SCOPE.(* | [FIELD][,FIELD ...])) [.filter(FIELD OP [INT | STRING])] .FUNCTION([PARAM][, PARAM ...]) // Disable hard code disable(METRICS_NAME);域(Scope)域包括全局(All)、服务(Service)、服务实例(Service Instance)、端点(Endpoint)、服务关系(Service Relation)、服务实例关系(Service Instance Relation)、端点关系(Endpoint Relation)。 当然还有一些字段,他们都属于以上某个域。过滤器(Filter)使用在使用过滤器的时候,通过指定字段名或表达式来构建字段值的过滤条件。表达式可以使用 and,or 和 () 进行组合。操作符包含==,!=,>,<,>=,<=,in [...],like %...,like ...%,like %...%,他们可以基于字段类型进行类型检测,如果类型不兼容会在编译/代码生成期间报错。聚合函数(Aggregation Function)默认的聚合函数由 SkyWalking OAP 核心实现。并可自由扩展更多函数。提供的函数:longAvg:某个域实体所有输入的平均值,输入字段必须是 long 类型。instance_jvm_memory_max = from(ServiceInstanceJVMMemory.max).longAvg();在上面的例子中,输入是 ServiceInstanceJVMMemory 域的每个请求,平均值是基于字段 max 进行求值的。doubleAvg:某个域实体的所有输入的平均值,输入的字段必须是 double 类型。instance_jvm_cpu = from(ServiceInstanceJVMCPU.usePercent).doubleAvg();在上面的例子中,输入是 ServiceInstanceJVMCPU 域的每个请求,平均值是基于 usePercent 字段进行求值的。percent:对于输入中匹配指定条件的百分比数.endpoint_percent = from(Endpoint.*).percent(status == true);在上面的例子中,输入是每个端点的请求,条件是 endpoint.status == true。rate:对于条件匹配的输入,比率以100的分数表示。browser_app_error_rate = from(BrowserAppTraffic.*).rate(trafficCategory == BrowserAppTrafficCategory.FIRST_ERROR, trafficCategory == BrowserAppTrafficCategory.NORMAL);在上面的例子中,所有的输入都是每个浏览器应用流量的请求,分子的条件是trafficCategory == BrowserAppTrafficCategory.FIRST_ERROR,分母的条件是trafficCategory == BrowserAppTrafficCategory.NORMAL。其中,第一个参数是分子的条件,第二个参数是分母的条件。sum:某个域实体的调用总数。service_calls_sum = from(Service.*).sum();在上面的例子中,统计每个服务的调用数。histogram:热力图,更多详见Heatmap in WIKI。all_heatmap = from(All.latency).histogram(100, 20);在上面的例子中,计算了所有传入请求的热力学热图。第一个参数是计算延迟的精度,在上面的例子中,在101-200ms组中,113ms和193ms被认为是相同的.第二个参数是分组数量,在上面的例子中,一共有21组数据分别为0-100ms,101-200ms......1901-2000ms,2000ms以上.apdex:应用性能指数(Application Performance Index),更多详见Apdex in WIKI。service_apdex = from(Service.latency).apdex(name, status);在上面的例子中,计算了所有服务的应用性能指数。第一个参数是服务名称,该名称的Apdex阈值在配置文件service-apdex-threshold.yml中定义。第二个参数是请求状态,状态(成功或失败)影响Apdex的计算。P99,P95,P90,P75,P50:百分位,更多详见Percentile in WIKI。百分位是自7.0版本引入的第一个多值度量。由于有多个值,可以通过getMultipleLinearIntValuesGraphQL查询进行查询。all_percentile = from(All.latency).percentile(10);在上面的例子中,计算了所有传入请求的 P99,P95,P90,P75,P50。参数是百分位计算的精度,在上例中120ms和124被认为是相同的。度量指标名称(Metrics Name)存储实现,告警以及查询模块的度量指标名称,SkyWalking 内核支持自动类型推断。组(Group)所有度量指标数据都会使用 Scope.ID 和最小时间桶(min-level time bucket) 进行分组.在端点的域中,Scope.ID 为端点的 ID(基于服务及其端点的唯一标志)。禁用(Disable)Disable是OAL中的高级语句,只在特定情况下使用。一些聚合和度量是通过核心硬代码定义的,这个Disable语句是设计用来让它们停止活动的,比如segment, top_n_database_statement。在默认情况下,没有被禁用的。示例// 计算每个端点的响应平均时长 endpoint_avg = from(Endpoint.latency).avg() // 计算每个端点 p50,p75,p90,p95 and p99 的延迟柱状图,每隔 50 毫秒一条柱 endpoint_percentile = from(Endpoint.latency).percentile(10) // 统计每个服务响应状态为 true 的百分比 endpoint_success = from(Endpoint.*).filter(status == true).percent() // 计算每个服务的响应码为[404, 500, 503]的总和 endpoint_abnormal = from(Endpoint.*).filter(responseCode in [404, 500, 503]).sum() // 计算每个服务的请求类型为[PRC, gRPC]的总和 endpoint_rpc_calls_sum = from(Endpoint.*).filter(type in [RequestType.PRC, RequestType.gRPC]).sum() // 计算每个端点的端点名称为["/v1", "/v2"]的总和 endpoint_url_sum = from(Endpoint.*).filter(endpointName in ["/v1", "/v2"]).sum() // 统计每个服务的调用总量 endpoint_calls = from(Endpoint.*).sum() disable(segment); disable(endpoint_relation_server_side); disable(top_n_database_statement);注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

Apache SkyWalking 告警动态配置源码简析

AlarmModuleProvider实现了ModuleProvider接口,通过SPI的方式被加载进来。AlarmModuleProvider的prepare方法先被调用,做一些预处理:@Override public void prepare() throws ServiceNotProvidedException, ModuleStartException { Reader applicationReader; try { applicationReader = ResourceUtils.read("alarm-settings.yml"); } catch (FileNotFoundException e) { throw new ModuleStartException("can't load alarm-settings.yml", e); //先从文件中,读取默认的配置,此处还没有加载动态配置。 RulesReader reader = new RulesReader(applicationReader); Rules rules = reader.readRules(); //创建一个AlarmRulesWatcher实例,这个实例用于监控和转换动态配置。 alarmRulesWatcher = new AlarmRulesWatcher(rules, this); //创建一个NotifyHandler实例,这个实例用于处理被触发的告警。 notifyHandler = new NotifyHandler(alarmRulesWatcher); notifyHandler.init(new AlarmStandardPersistence()); //注册到服务实现中。 this.registerServiceImplementation(MetricsNotify.class, notifyHandler); }随后,AlarmModuleProvider的start方法被调用:@Override public void start() throws ServiceNotProvidedException, ModuleStartException { //获取动态配置服务,目前(8.2.0)支持的动态配置有apollo, consul, etcd, k8s configmap, nacos, zookeeper, grpc DynamicConfigurationService dynamicConfigurationService = getManager().find(ConfigurationModule.NAME) .provider() .getService( DynamicConfigurationService.class); //把之前的AlarmRulesWatcher实例,注册到动态配置服务中 dynamicConfigurationService.registerConfigChangeWatcher(alarmRulesWatcher); }registerConfigChangeWatcher方法的源码在ConfigWatcherRegister抽象类中:@Override synchronized public void registerConfigChangeWatcher(ConfigChangeWatcher watcher) { if (isStarted) { throw new IllegalStateException("Config Register has been started. Can't register new watcher."); //利用传入的AlarmRulesWatcher实例,创建一个WatcherHolder实例,其实是对AlarmRulesWatcher实例的再次封装,并格式化好key为alarm.default.alarm-settings。 WatcherHolder holder = new WatcherHolder(watcher); if (register.containsKey(holder.getKey())) { throw new IllegalStateException("Duplicate register, watcher=" + watcher); //每一个不同的key对应不同的WatcherHolder实例,也就是不同动态配置对应不用的处理办法。 register.put(holder.getKey(), holder); }随后,ConfigWatcherRegister抽象类的start方法被调用:public void start() { isStarted = true; //同步动态配置 configSync(); LOGGER.info("Current configurations after the bootstrap sync." + LINE_SEPARATOR + register.toString()); //异步地每隔一段时间做一次动态配置的同步 Executors.newSingleThreadScheduledExecutor() .scheduleAtFixedRate( new RunnableWithExceptionProtection( this::configSync, t -> LOGGER.error("Sync config center error.", t) ), syncPeriod, syncPeriod, TimeUnit.SECONDS); }再看一下同步动态配置的configSync方法:void configSync() { //读取所有注册进来的动态配置,包括告警的配置。 Optional<ConfigTable> configTable = readConfig(register.keys()); // 如果没有检测到任何配置的更改,configTable可能为null。 configTable.ifPresent(config -> { config.getItems().forEach(item -> { String itemName = item.getName(); //获取到配置对应的WatcherHolder实例 WatcherHolder holder = register.get(itemName); if (holder != null) { ConfigChangeWatcher watcher = holder.getWatcher(); String newItemValue = item.getValue(); if (newItemValue == null) { if (watcher.value() != null) { // 如果新的配置为null,则发送删除配置的消息类型。 watcher.notify( new ConfigChangeWatcher.ConfigChangeEvent(null, ConfigChangeWatcher.EventType.DELETE)); } else { // 如果不调用notify方法,则保持配置为空。 } else { if (!newItemValue.equals(watcher.value())) { watcher.notify(new ConfigChangeWatcher.ConfigChangeEvent( newItemValue, ConfigChangeWatcher.EventType.MODIFY } else { // 如果不调用notify方法,则保持在相同的配置。 } else { LOGGER.warn("Config {} from configuration center, doesn't match any watcher, ignore.", itemName); LOGGER.trace("Current configurations after the sync." + LINE_SEPARATOR + register.toString()); }注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

Apache SkyWalking 告警配置指南

Apache SkyWalkingApache SkyWalking是分布式系统的应用程序性能监视工具(Application Performance Management,APM),专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。Apache SkyWalking告警Apache SkyWalking告警是由一组规则驱动,这些规则定义在config/alarm-settings.yml文件中。告警规则的定义分为三部分。告警规则:定义了触发告警所考虑的条件。webhook:当告警触发时,被调用的服务端点列表。gRPCHook:当告警触发时,被调用的远程gRPC方法的主机和端口。Slack Chat Hook:当告警触发时,被调用的Slack Chat接口。微信 Hook:当告警触发时,被调用的微信接口。钉钉 Hook:当告警触发时,被调用的钉钉接口。告警规则告警规则有两种类型,单独规则(Individual Rules)和复合规则(Composite Rules),复合规则是单独规则的组合。单独规则(Individual Rules)单独规则主要有以下几点:规则名称:在告警信息中显示的唯一名称,必须以_rule结尾。metrics-name:度量名称,也是OAL脚本中的度量名。默认配置中可以用于告警的度量有:服务,实例,端点,服务关系,实例关系,端点关系。它只支持long,double和int类型。include-names:包含在此规则之内的实体名称列表。exclude-names:排除在此规则以外的实体名称列表。include-names-regex:提供一个正则表达式来包含实体名称。如果同时设置包含名称列表和包含名称的正则表达式,则两个规则都将生效。exclude-names-regex:提供一个正则表达式来排除实体名称。如果同时设置排除名称列表和排除名称的正则表达式,则两个规则都将生效。include-labels:包含在此规则之内的标签。exclude-labels:排除在此规则以外的标签。include-labels-regex:提供一个正则表达式来包含标签。如果同时设置包含标签列表和包含标签的正则表达式,则两个规则都将生效。exclude-labels-regex:提供一个正则表达式来排除标签。如果同时设置排除标签列表和排除标签的正则表达式,则两个规则都将生效。标签的设置必须把数据存储在meter-system中,例如:Prometheus, Micrometer。以上四个标签设置必须实现LabeledValueHolder接口。threshold:阈值。对于多个值指标,例如percentile,阈值是一个数组。像value1 value2 value3 value4 value5这样描述。每个值可以作为度量中每个值的阈值。如果不想通过此值或某些值触发警报,则将值设置为 -。例如在percentile中,value1是P50的阈值,value2是P75的阈值,那么-,-,value3, value4, value5的意思是,没有阈值的P50和P75的percentile告警规则。op:操作符,支持>, >=, <, <=, =。period:多久告警规则需要被检查一下。这是一个时间窗口,与后端部署环境时间相匹配。count:在一个周期窗口中,如果按op计算超过阈值的次数达到count,则发送告警。only-as-condition:true或者false,指定规则是否可以发送告警,或者仅作为复合规则的条件。silence-period:在时间N中触发报警后,在N -> N + silence-period这段时间内不告警。 默认情况下,它和period一样,这意味着相同的告警(同一个度量名称拥有相同的Id)在同一个周期内只会触发一次。message:该规则触发时,发送的通知消息。举个例子:rules: service_resp_time_rule: metrics-name: service_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 service_instance_resp_time_rule: metrics-name: service_instance_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 实例【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 endpoint_resp_time_rule: metrics-name: endpoint_avg threshold: 1000 op: ">" period: 10 count: 2 message: 端点【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒复合规则(Composite Rules)复合规则仅适用于针对相同实体级别的告警规则,例如都是服务级别的告警规则:service_percent_rule && service_resp_time_percentile_rule。不可以编写不同实体级别的告警规则,例如服务级别的一个告警规则和端点级别的一个规则:service_percent_rule && endpoint_percent_rule。复合规则主要有以下几点:规则名称:在告警信息中显示的唯一名称,必须以_rule结尾。expression:指定如何组成规则,支持&&, ||, ()操作符。message:该规则触发时,发送的通知消息。举个例子:rules: service_resp_time_rule: metrics-name: service_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 service_sla_rule: metrics-name: service_sla op: "<" threshold: 8000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的成功率在最近10分钟内有2分钟低于80% composite-rules: comp_rule: expression: service_resp_time_rule && service_sla_rule message: 服务【{name}】在最近10分钟内有2分钟超过1秒平均响应时间超过1秒并且成功率低于80%WebhookWebhook 要求一个点对点的 Web 容器。告警的消息会通过 HTTP 请求进行发送,请求方法为 POST,Content-Type 为 application/json,JSON 格式包含以下信息:scopeId:目标 Scope 的 ID。name:目标 Scope 的实体名称。id0:Scope 实体的 ID。id1:未使用。ruleName:您在 alarm-settings.yml 中配置的规则名。alarmMessage. 告警消息内容。startTime. 告警时间戳,当前时间与 UTC 1970/1/1 相差的毫秒数。举个例子:[{ "scopeId": 1, "scope": "SERVICE", "name": "one-more-service", "id0": "b3JkZXItY2VudGVyLXNlYXJjaC1hcGk=.1", "id1": "", "ruleName": "service_resp_time_rule", "alarmMessage": "服务【one-more-service】的平均响应时间在最近10分钟内有2分钟超过1秒", "startTime": 1617670815000 "scopeId": 2, "scope": "SERVICE_INSTANCE", "name": "e4b31262acaa47ef92a22b6a2b8a7cb1@192.168.30.11 of one-more-service", "id0": "dWF0LWxib2Mtc2VydmljZQ==.1_ZTRiMzEyNjJhY2FhNDdlZjkyYTIyYjZhMmI4YTdjYjFAMTcyLjI0LjMwLjEzOA==", "id1": "", "ruleName": "instance_jvm_young_gc_count_rule", "alarmMessage": "实例【e4b31262acaa47ef92a22b6a2b8a7cb1@192.168.30.11 of one-more-service】的YoungGC次数在最近10分钟内有2分钟超过10次", "startTime": 1617670815000 "scopeId": 3, "scope": "ENDPOINT", "name": "/one/more/endpoint in one-more-service", "id0": "b25lcGllY2UtYXBp.1_L3RlYWNoZXIvc3R1ZGVudC92aXBsZXNzb25z", "id1": "", "ruleName": "endpoint_resp_time_rule", "alarmMessage": "端点【/one/more/endpoint in one-more-service】的平均响应时间在最近10分钟内有2分钟超过1秒", "startTime": 1617670815000 }]gRPCHook告警消息将使用 Protobuf 类型通过gRPC远程方法发送。消息格式的关键信息定义如下:syntax = "proto3"; option java_multiple_files = true; option java_package = "org.apache.skywalking.oap.server.core.alarm.grpc"; service AlarmService { rpc doAlarm (stream AlarmMessage) returns (Response) { message AlarmMessage { int64 scopeId = 1; string scope = 2; string name = 3; string id0 = 4; string id1 = 5; string ruleName = 6; string alarmMessage = 7; int64 startTime = 8; message Response { }Slack Chat Hook您需要遵循传入Webhooks入门指南并创建新的Webhooks。如果您按以下方式配置了Slack Incoming Webhooks,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:slackHooks: textTemplate: |- "type": "section", "text": { "type": "mrkdwn", "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**." webhooks: - https://hooks.slack.com/services/x/y/z微信Hook只有微信的企业版才支持 Webhooks ,如何使用微信的 Webhooks 可参见如何配置群机器人。如果您按以下方式配置了微信的 Webhooks ,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:wechatHooks: textTemplate: |- "msgtype": "text", "text": { "content": "Apache SkyWalking 告警: \n %s." webhooks: - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key钉钉 Hook您需要遵循自定义机器人开放并创建新的Webhooks。为了安全起见,您可以为Webhook网址配置可选的密钥。如果您按以下方式配置了钉钉的 Webhooks ,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:dingtalkHooks: textTemplate: |- "msgtype": "text", "text": { "content": "Apache SkyWalking 告警: \n %s." webhooks: - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token secret: dummysecret注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

美女同事的烦恼:如何配置 Apache SkyWalking 告警?

小婉技术部基本上是一个和尚庙,女生非常少,即使有女生也略微有点抽象,小婉就不一样,她气质绝佳。上午,同事小婉刚才从老板办公室里出来,看上去一脸不悦的样子。为了表示对同事的关(ba)心(gua),我就主动和她聊一聊。Apache SkyWalking是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。这些我是知道的,它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。至于小婉问我的SkyWalking的告警,我还真不清楚,是我的知识盲区啊,我只能如实跟小婉说了。哎,我都说了些什么,幸好我机灵一些,拖到了到下午,还有几个小时,赶快恶补一下。Apache SkyWalking告警Apache SkyWalking告警是由一组规则驱动,这些规则定义在config/alarm-settings.yml文件中。告警规则的定义分为三部分。告警规则:定义了触发告警所考虑的条件。webhook:当告警触发时,被调用的服务端点列表。gRPCHook:当告警触发时,被调用的远程gRPC方法的主机和端口。Slack Chat Hook:当告警触发时,被调用的Slack Chat接口。微信 Hook:当告警触发时,被调用的微信接口。钉钉 Hook:当告警触发时,被调用的钉钉接口。告警规则告警规则有两种类型,单独规则(Individual Rules)和复合规则(Composite Rules),复合规则是单独规则的组合。单独规则(Individual Rules)单独规则主要有以下几点:规则名称:在告警信息中显示的唯一名称,必须以_rule结尾。metrics-name:度量名称,也是OAL脚本中的度量名。默认配置中可以用于告警的度量有:服务,实例,端点,服务关系,实例关系,端点关系。它只支持long,double和int类型。include-names:包含在此规则之内的实体名称列表。exclude-names:排除在此规则以外的实体名称列表。include-names-regex:提供一个正则表达式来包含实体名称。如果同时设置包含名称列表和包含名称的正则表达式,则两个规则都将生效。exclude-names-regex:提供一个正则表达式来排除实体名称。如果同时设置排除名称列表和排除名称的正则表达式,则两个规则都将生效。include-labels:包含在此规则之内的标签。exclude-labels:排除在此规则以外的标签。include-labels-regex:提供一个正则表达式来包含标签。如果同时设置包含标签列表和包含标签的正则表达式,则两个规则都将生效。exclude-labels-regex:提供一个正则表达式来排除标签。如果同时设置排除标签列表和排除标签的正则表达式,则两个规则都将生效。标签的设置必须把数据存储在meter-system中,例如:Prometheus, Micrometer。以上四个标签设置必须实现LabeledValueHolder接口。threshold:阈值。对于多个值指标,例如percentile,阈值是一个数组。像value1 value2 value3 value4 value5这样描述。每个值可以作为度量中每个值的阈值。如果不想通过此值或某些值触发警报,则将值设置为 -。例如在percentile中,value1是P50的阈值,value2是P75的阈值,那么-,-,value3, value4, value5的意思是,没有阈值的P50和P75的percentile告警规则。op:操作符,支持>, >=, <, <=, =。period:多久告警规则需要被检查一下。这是一个时间窗口,与后端部署环境时间相匹配。count:在一个周期窗口中,如果按op计算超过阈值的次数达到count,则发送告警。only-as-condition:true或者false,指定规则是否可以发送告警,或者仅作为复合规则的条件。silence-period:在时间N中触发报警后,在N -> N + silence-period这段时间内不告警。 默认情况下,它和period一样,这意味着相同的告警(同一个度量名称拥有相同的Id)在同一个周期内只会触发一次。message:该规则触发时,发送的通知消息。举个例子:rules: service_resp_time_rule: metrics-name: service_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 service_instance_resp_time_rule: metrics-name: service_instance_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 实例【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 endpoint_resp_time_rule: metrics-name: endpoint_avg threshold: 1000 op: ">" period: 10 count: 2 message: 端点【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒复合规则(Composite Rules)复合规则仅适用于针对相同实体级别的告警规则,例如都是服务级别的告警规则:service_percent_rule && service_resp_time_percentile_rule。不可以编写不同实体级别的告警规则,例如服务级别的一个告警规则和端点级别的一个规则:service_percent_rule && endpoint_percent_rule。复合规则主要有以下几点:规则名称:在告警信息中显示的唯一名称,必须以_rule结尾。expression:指定如何组成规则,支持&&, ||, ()操作符。message:该规则触发时,发送的通知消息。举个例子:rules: service_resp_time_rule: metrics-name: service_resp_time op: ">" threshold: 1000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的平均响应时间在最近10分钟内有2分钟超过1秒 service_sla_rule: metrics-name: service_sla op: "<" threshold: 8000 period: 10 count: 2 silence-period: 10 message: 服务【{name}】的成功率在最近10分钟内有2分钟低于80% composite-rules: comp_rule: expression: service_resp_time_rule && service_sla_rule message: 服务【{name}】在最近10分钟内有2分钟超过1秒平均响应时间超过1秒并且成功率低于80%WebhookWebhook 要求一个点对点的 Web 容器。告警的消息会通过 HTTP 请求进行发送,请求方法为 POST,Content-Type 为 application/json,JSON 格式包含以下信息:scopeId:目标 Scope 的 ID。name:目标 Scope 的实体名称。id0:Scope 实体的 ID。id1:未使用。ruleName:您在 alarm-settings.yml 中配置的规则名。alarmMessage. 告警消息内容。startTime. 告警时间戳,当前时间与 UTC 1970/1/1 相差的毫秒数。举个例子:[{ "scopeId": 1, "scope": "SERVICE", "name": "one-more-service", "id0": "b3JkZXItY2VudGVyLXNlYXJjaC1hcGk=.1", "id1": "", "ruleName": "service_resp_time_rule", "alarmMessage": "服务【one-more-service】的平均响应时间在最近10分钟内有2分钟超过1秒", "startTime": 1617670815000 "scopeId": 2, "scope": "SERVICE_INSTANCE", "name": "e4b31262acaa47ef92a22b6a2b8a7cb1@192.168.30.11 of one-more-service", "id0": "dWF0LWxib2Mtc2VydmljZQ==.1_ZTRiMzEyNjJhY2FhNDdlZjkyYTIyYjZhMmI4YTdjYjFAMTcyLjI0LjMwLjEzOA==", "id1": "", "ruleName": "instance_jvm_young_gc_count_rule", "alarmMessage": "实例【e4b31262acaa47ef92a22b6a2b8a7cb1@192.168.30.11 of one-more-service】的YoungGC次数在最近10分钟内有2分钟超过10次", "startTime": 1617670815000 "scopeId": 3, "scope": "ENDPOINT", "name": "/one/more/endpoint in one-more-service", "id0": "b25lcGllY2UtYXBp.1_L3RlYWNoZXIvc3R1ZGVudC92aXBsZXNzb25z", "id1": "", "ruleName": "endpoint_resp_time_rule", "alarmMessage": "端点【/one/more/endpoint in one-more-service】的平均响应时间在最近10分钟内有2分钟超过1秒", "startTime": 1617670815000 }]gRPCHook告警消息将使用 Protobuf 类型通过gRPC远程方法发送。消息格式的关键信息定义如下:syntax = "proto3"; option java_multiple_files = true; option java_package = "org.apache.skywalking.oap.server.core.alarm.grpc"; service AlarmService { rpc doAlarm (stream AlarmMessage) returns (Response) { message AlarmMessage { int64 scopeId = 1; string scope = 2; string name = 3; string id0 = 4; string id1 = 5; string ruleName = 6; string alarmMessage = 7; int64 startTime = 8; message Response { }Slack Chat Hook您需要遵循传入Webhooks入门指南并创建新的Webhooks。如果您按以下方式配置了Slack Incoming Webhooks,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:slackHooks: textTemplate: |- "type": "section", "text": { "type": "mrkdwn", "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**." webhooks: - https://hooks.slack.com/services/x/y/z微信Hook只有微信的企业版才支持 Webhooks ,如何使用微信的 Webhooks 可参见如何配置群机器人。如果您按以下方式配置了微信的 Webhooks ,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:wechatHooks: textTemplate: |- "msgtype": "text", "text": { "content": "Apache SkyWalking 告警: \n %s." webhooks: - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key钉钉 Hook您需要遵循自定义机器人开放并创建新的Webhooks。为了安全起见,您可以为Webhook网址配置可选的密钥。如果您按以下方式配置了钉钉的 Webhooks ,则告警消息将按 Content-Type 为 application/json 通过HTTP的 POST 方式发送。举个例子:dingtalkHooks: textTemplate: |- "msgtype": "text", "text": { "content": "Apache SkyWalking 告警: \n %s." webhooks: - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token secret: dummysecret尾声基本这些就差不多了,完全可以忽悠住小婉。想想小婉那崇拜的眼神和仰慕的表情,我擦了擦嘴巴的口水。注:本文以SkyWalking的8.2.0版本为例进行介绍,如果版本不同会略有差异。

Java实现Date日期加减和保留日期部分

Java实现Date日期加减private static Date addDay(Date date, int days) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.DATE, days); return calendar.getTime(); }Calendar.YEAR:年Calendar.MONTH:月Calendar.DATE:日Calendar.HOUR:小时Calendar.MINUTE:分钟Calendar.SECOND:秒使用示例public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = new Date(1618459828000L); System.out.println(sdf.format(date)); date = addDay(date, 3); System.out.println(sdf.format(date)); }输出:2021-04-15 12:10:28 2021-04-18 12:10:28Java实现Date保留日期部分private static Date getDate(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); // 毫秒 calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); }使用示例public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = new Date(1618459828000L); System.out.println(sdf.format(date)); date = getDate(date); System.out.println(sdf.format(date)); }输出:2021-04-15 12:10:28 2021-04-15 00:00:00

$‘\r‘: command not found的解决方法

缘起在Linux系统中,运行Shell脚本,出现了如下错误:one-more.sh: line 1: $'\r': command not found出现这样的错误,是因为Shell脚本在Windows系统编写时,每行结尾是\r\n,而在Linux系统中行每行结尾是\n,所以在Linux系统中运行脚本时,会认为\r是一个字符,导致运行错误。解决方法去除Shell脚本的\r字符:方法1sed -i 's/\r//' one-more.sh方法2dos2unix one-more.sh输出如下:dos2unix: converting file one-more.sh to Unix format ...如果出现如下错误:-bash: dos2unix: command not found说明dos2unix还没有安装,运行如下命令进行安装:yum install -y dos2unix输出如下:Loaded plugins: fastestmirror, langpacks Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast base | 3.6 kB 00:00:00 extras | 2.9 kB 00:00:00 salt | 2.9 kB 00:00:00 updates | 2.9 kB 00:00:00 Determining fastest mirrors Resolving Dependencies --> Running transaction check ---> Package dos2unix.x86_64 0:6.0.3-7.el7 will be installed --> Finished Dependency Resolution Dependencies Resolved =========================================================================================================================================================================================================================================== Package Arch Version Repository Size =========================================================================================================================================================================================================================================== Installing: dos2unix x86_64 6.0.3-7.el7 base 74 k Transaction Summary =========================================================================================================================================================================================================================================== Install 1 Package Total download size: 74 k Installed size: 190 k Downloading packages: dos2unix-6.0.3-7.el7.x86_64.rpm | 74 kB 00:00:00 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : dos2unix-6.0.3-7.el7.x86_64 1/1 Verifying : dos2unix-6.0.3-7.el7.x86_64 1/1 Installed: dos2unix.x86_64 0:6.0.3-7.el7 Complete!

Apache SkyWalking的UI界面主要分为以下几个区域: 功能选择区:这里列出了主要的UI功能,包括仪表盘、拓扑图、追踪、性能刨析、告警等功能 重新加载区:控制重新加载机制,包括定期重新加载或手动重新加载。 时间选择器:控制时区和时间范围。这里有一个中文/英文切换按钮,默认,UI使用浏览器语言设置。 下面逐一介绍功能选择区的各个功能:
手把手带你使用Paint in 3D和Photon撸一个在线涂鸦画板
Paint in 3D用于在游戏内和编辑器里绘制所有物体。所有功能已经过深度优化,在WebGL、移动端、VR 以及更多平台用起来都非常好用! 它支持标准管线,以及 LWRP、HDRP 和 URP。通过使用GPU 加速,你的物体将以难以置信的速度被绘制。代码还经过深度优化来防止GC,和将所有绘制操作一起批次完成。 跟贴图系统不同,它是一个纹理绘制解决方案。这意味着你可以绘制你的物体上百万次,还是无帧率丢失,让你创作难以想象的游戏。
老徐:“嗯,不过缓存能够大大提升整个系统的性能,但同时也引入了更多复杂性。” 阿珍点了点头,说:“是啊,缓存穿透、缓存击穿、缓存雪崩、缓存热点这些东西,这些东西我一直分不清楚,经常混淆。” 老徐立刻自信满满地说:“这个我懂啊,你听我给你娓娓道来。”
老徐问道:“阿珍,你知道ArrayList和LinkedList的区别吗?” 阿珍微微一笑,说:“这也太小儿科了,ArrayList是基于数组实现,LinkedList是基于链表实现。” 老徐竖起了大拇指,说:“不错,有进步!那你知道ArrayList和LinkedList的效率到底哪个高?” 阿珍回答:“这也难不倒我,这要分不同情况的。在新增、删除元素时,LinkedList的效率要高于ArrayList,而在遍历的时候,ArrayList的效率要高于LinkedList。” 老徐反问到:“不一定哦。在新增、删除元素时,LinkedList的效率有可能低于ArrayList,而在遍历
阿珍一脸坏笑地说:“那就是软了。” 老徐的老脸一红,辩解到:“我这是养养生,我很强的,好吧。” 看着老徐的窘态,阿珍笑出来声。老徐起身刚要走,阿珍一把拽住老徐,说:“跟你开玩笑呢,问你个正事,我一直分不清Java的强引用、软引用、弱引用、虚引用,给我讲讲呗。” 老徐立刻自信满满的坐下,说:“那你可问对人了,我对这方面颇有研究。这四种引用级别由高到低依次是:强引用、软引用、弱引用、虚引用。”
阿珍吃完饭刚刚回来,看到老徐正在吃方便面,说:“老徐,不至于吧。你为了还债,中午就吃这个呀?” 老徐不慌不忙地嚼着方便面,说:“这可是白象的方便面,国货之光,超级难买,我好不容易才买到的。” 阿珍坐了下来,说:“闻着还挺香,我也买点尝尝。对了,老徐,上午开会的时候,听你们总说CAP、CAP的,CAP是什么意思?超级爱放P吗?” 老徐嘴里方便面差点喷了出来,笑着说:“第一次听你这么解释的,来!我给你科普一下。”
老徐和阿珍的故事:Runnable和Callable有什么不同?
阿珍探出头看了看老徐的屏幕,全部都是绿色的曲线图,好奇地问:“老徐,你看的这是什么?”老徐看的太入神,转过头才发现阿珍,尬尴地笑了笑说:“我就是看看最近的行情。”老徐立马切换了窗口。 阿珍没在意又继续问到:“Runnable和Callable两个接口我总搞混,这个到底有什么不同?”
快速整明白Redis中的字典到底是个啥
字典是一种用于保存键值对的数据结构,可以通过键值对中的键快速地查找到对应的值。在Redis所使用的C语言中,并没有内置字典,所以Redis自己实现了字典。 整个Redis数据库的所有的键和值就组成了一个全局的字典,对数据库的增删改查操作都是构建在字典的操作之上的。 字典还是Redis的基本数据类型哈希(hash)的底层实现之一,当哈希数据类型的键和值的长度较大或者键值对数量较多的时候,Redis就会把字典作为哈希数据类型的底层实现。 字典还是Redis的基本数据类型有序集合(zset)的底层实现之一,当有序集合中的所有元素的成员长度较长或者元素个数较多的时候,Redis就会把跳跃表和哈希
快速整明白Redis中的整数集合到底是个啥
整数集合(intset)是Redis集合数据类型的内部编码之一,当集合数据类型中的元素都是整数并且元素数量较少的时候,Redis就使用整数集合作为内部编码。 整数集合(intset)中可以保存int16_t、int32_t和int64_t类型的整数,而且保证
快速整明白Redis中的压缩列表到底是个啥
压缩列表(ziplist)是由一个连续内存组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点上可以保存一个字节数组或整数值。它是Redis为了节省内存空间而开发的。 压缩列表(ziplist)是哈希(hash)和有序集合(zset)的内部编码之一。当哈希(hash)中的元素个数比较少并且每个元素的值占用空间比较小的时候,Redis就会使用压缩列表做为哈希的内部编码。当有序集合(zset)中的元素个数比较少并且每个元素的值占用空间比较小的时候,Redis也会使用压缩列表做为有序集合的内部编码。
昨天面试居然聊了半个多小时的异常处理
大风吹去了往日的雾霾,阳光透过窗户照进来,透过窗户可以看到远处的山脉与蓝天相接,这可比我那永远见不到阳光的出租屋好多了。渐渐走进的脚步声打断了我的思绪,一位小姐姐坐在了面前,甜甜的香水味立刻钻进了我的鼻孔。 小姐姐微笑地说:”您好,我是今天的面试官,那么开始吧?“ 我收起直勾勾的眼睛,说:“好的。” 小姐姐说:“在Java的异常处理中有两大组成要素:抛出异常和捕获异常。那么抛出异常可以分为哪两种呢?” 我立刻回答到:
面试官:Redis中字符串的内部实现方式是什么?
在面试间里等候时,感觉这可真暖和呀,我那冰冷的出租屋还得盖两层被子才能睡着。正要把外套脱下来,我突然听到了门外的脚步声,随即门被打开,穿着干净满脸清秀的青年走了进来,一股男士香水的淡香扑面而来。 面试官:Redis中基本的数据类型有哪些? 我:Redis的基本数据类型有:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)。 面试官:字符串类型的内部实现方式是什么? 我还沉浸在上一个问题的沾沾自喜中,顿时表情凝固了,手心开始冒出冷汗。“这个。。没有太深入了解”,我支支吾吾的说到。 面试官:回去等消息吧。
快速了解常用的消息摘要算法,再也不用担心面试官的刨根问底
加密算法通常被分为两种:对称加密和非对称加密。其中,对称加密算法在加密和解密时使用的密钥相同;非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥。此外,还有一类叫做消息摘要算法,是对数据进行摘要并且不可逆的算法。 这次我们了解一下消息摘要算法。
快速了解常用的非对称加密算法,再也不用担心面试官的刨根问底
加密算法通常被分为两种:对称加密算法和非对称加密算法。其中,对称加密算法在加密和解密时使用的密钥相同;非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥。此外,还有一类叫做消息摘要算法,是对数据进行摘要并且不可逆的算法。 这次我们了解一下非对称加密算法。

-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;

-d: 后台运行容器,并返回容器ID;

-i: 以交互模式运行容器,通常与 -t 同时使用;

-P: 随机端口映射,容器内部端口随机映射到主机的端口

-p: 指定端口映射,格式为:主机(宿主)端口:容器端口

-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;

--name="nginx-lb": 为容器指定一个名称;

--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;

--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;

-h "mars": 指定容器的hostname;

-e username="ritchie": 设置环境变量;

--env-file=[]: 从指定文件读入环境变量;

--cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行;

-m :设置容器使用内存最大值;

--net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;

--link=[]: 添加链接到另一个容器;

--expose=[]: 开放一个端口或一组端口;

--volume , -v: 绑定一个卷

实例 使用docker镜像nginx:latest以后台模式启动一个容器,并将容器命名为mynginx。

docker run --name mynginx -d nginx:latest

使用镜像nginx:latest以后台模式启动一个容器,并将容器的80端口映射到主机随机端口。

docker run -P -d nginx:latest

使用镜像 nginx:latest,以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data。

docker run -p 80:80 -v /data:/data -d nginx:latest

绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上。

$ docker run -p 127.0.0.1:80:8080/tcp ubuntu bash

使用镜像nginx:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。

runoob@runoob:~$ docker run -it nginx:latest /bin/bash
root@b8573233d675:/#

该方案完整地实现了以上提到的分层存储中所有的工作,包括虚拟化,IO 性能统计以及数据的迁移。

该方案的结构包括一个 Linux 设备驱动程序和若干用户态的控制程序,如图 3 所示。 驱动程序实现存储设备的虚拟化,IO 性能监测统计以及数据的迁移; 用户态控制程序负责创建、删除虚拟设备,手动触发数据迁移,以及设置获取设备状态。 该方案由于 Storage Tiering 所有的功能都在 Linux 内核实现,且需要维护虚拟设备到物理设备的地址映射表,以及保证数据一致性,所以实现难度和工作量比较大,但可扩展性和灵活性也相对较大。

该方案的实现主要包括以下内容:

1、管理设备的注册 管理设备主要用于与用户态程序的 IOCTL 交互,可以是一个字符设备或者 Misc 设备。Linux 下可以通过 register_chrdev 或 misc_register 注册,并实现所需要的 IOCTL 接口。

2、虚拟块设备的创建 用户态控制程序通过 IOCTL 向控制设备发起创建虚拟设备的请求,并传入所有的物理磁盘(DEV1,DEV2)的参数,如设备名,磁盘大小,虚拟磁盘的块大小等;驱动程序收到该请求后,进行必要的参数检查,然后调用 register_blkdev 创建一个新的块设备(VDEV)。并设置新设备的相关参数,如 IO 处理函数,队列大小,设备容量等。

3、虚拟设备地址与物理设备地址映射 虚拟设备地址到物理设备的地址映射表在虚拟设备创建是被初始化,并在数据迁移过程中被修改。 虚拟设备和物理设备都被分成固定大小的块,块大小可以固定或通过 IOCTL 由用户指定,但一旦确定,不能更改,一个 Block 是热度统计以及数据迁移的最小单位;每个 Block 包含若干个 sector(512 Byte)。 当 VDEV 收到一个 bio,可以由 bi_sector 和 bi_size 找出所对应的 VDEV 的 Block 以及 Block 内的偏移量,通过查询映射表,找到各个 VDEV Block 所对应的物理设备以及 Block,然后读取物理设备 Block 内的偏移量,如图 4 所示。 地址映射表以及其他的元数据需要存储在物理设备上,以便机器重启时能重构这个虚拟的块设备,且需要采取一定的备份策略,防止断电或磁盘损坏造成数据丢失。

4、IO 热度统计 IO 热度统计也以 Block 为基本单位,每个 Block 内的任何一个 sector 被访问,该 Block 的热度都为增加。由于大 IO 以及顺序 IO 在性能在传统硬盘和 SSD 上的差异并不是特别大,所以在进行热度统计是应该考虑排除大 IO 和顺序 IO。

5、数据迁移 可以采用自动方式或手动方式。自动方式由驱动内的定时器驱动,每隔一定的时间,启动数据迁移的扫描,将 IO 热度统计中的热数据向高性能存储设备迁移,冷数据向低性能存储设备迁移;手动方式由用户指定,将某块数据向高性能存储设备迁移或低性能设备迁移。手动数据迁移方式增加了更大的灵活性和可扩展性。

BTier 基于 Block Device 的分层存储方案的开源实现。BTier 最大支持 16 个设备的虚拟化,这些设备被 BTier 简单地捆绑成一个 btier 块设备,因此,其中任何一个设备的失效,都会导致整个 btier 的失效。 编译并以模块形式安装 BTier 之后,会创建一个名为 tiercontrol 的字符设备; 然后使用 BTier 提供的 btier_setup 应用可以创建一个新的块设备 btiera,然后就可以对 btiera 设备进行所有块设备的读写操作,包括分区和创建文件系统。 BTier 还提供了丰富的 sysfs 接口,进行控制和信息获取,如数据迁移的开关,间隔时间,IO 统计信息等。 不过 BTier 每个 Tier 层仅支持 1 个物理设备,同层多个设备的虚拟化需要借助其他的方法,在虚拟化上面,BTier 还有可以改善的空间。

基于 LVM 的分层存储方案

Linux 的逻辑卷管理(LVM)提供了存储虚拟化,可以将多个物理卷(PV)建成一个卷组(VG),然后再在 VG 里创建虚拟卷(VG)。而且 LVM 提供了在不同物理卷之间迁移数据的 API。因此,基于 LVM 的分层存储方案借助 LVM 的虚拟化和数据迁移的能力,实现会更简单。

该方案中,数据一致性的问题以及数据迁移时 IO 中断的问题都由 LVM 进行处理,重点在于如何分析并统计 IO 的热度信息,并且不涉及内核态的开发。该方案的结构如图所示。

该方案的实现主要包括以下内容:

1、创建虚拟设备 使用 pvcreate 将所有的物理磁盘创建成物理卷(PV);再使用 vgcreate 将所有的 VG 创建成一个卷组(VG);最后使用 lvcreate 在创建出来的 VG 上建虚拟卷。

2、IO 热度统计 IO 热度统计可以使用 blktrace 工具,或者实现一个设备驱动来检测 IO 的热度。使用 blkparse 可以解析 blktrace 的输出,然后分析这些 IO 的分布以及读写频度,从而得到 IO 的热度统计信息。

4、数据迁移 根据 IO 的热度统计信息,使用 LVM 提供的 pvmove 工具,可以在属于同一个 VG 里的不同 PV 之间进行数据迁移,将热数据和冷数据分布存放在不同的物理卷上。

LVMTS(LVM Tired Storage)是一个使用 SSD 和 HDD 来创建混合存储的方案,完全在用户态实现,主要由几个守护进程构成。 Lvmtscd 负责监测 blktrace 的输出并统计块设备的访问频度,并将这些统计信息记录在文件中; Lvmtsd 负责根据用户配置的信息,启动其他的守护进程,并完成数据迁移。 实际使用中发现,LVMTS 并不是太稳定,而且在 IO 分析统计上并不是太完善,可开发的空间仍然很大。