首页 文章 精选 留言 我的

精选列表

搜索[镜像无法拉取],共10000篇文章
优秀的个人博客,低调大师

Spring Boot 侵入式 实现RESTful API接口统一JSON格式返回

前言 现在我们做项目基本上中大型项目都是选择前后端分离,前后端分离已经成了一个趋势了,所以总这样·我们就要和前端约定统一的api 接口返回json 格式, 这样我们需要封装一个统一通用全局 模版api返回格式,下次再写项目时候直接拿来用就可以了 约定JSON格式 一般我们和前端约定json格式是这样的 { "code": 200, "message": "成功", "data": { } } code: 返回状态码 message: 返回信息的描述 data: 返回值 封装java bean 定义状态枚举 package cn.soboys.core.ret; import lombok.Data; import lombok.Getter; /** * @author kenx * @version 1.0 * @date 2021/6/17 15:35 * 响应码枚举,对应HTTP状态码 */ @Getter public enum ResultCode { SUCCESS(200, "成功"),//成功 //FAIL(400, "失败"),//失败 BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, "认证失败"),//未认证 NOT_FOUND(404, "接口不存在"),//接口不存在 INTERNAL_SERVER_ERROR(500, "系统繁忙"),//服务器内部错误 METHOD_NOT_ALLOWED(405,"方法不被允许"), /*参数错误:1001-1999*/ PARAMS_IS_INVALID(1001, "参数无效"), PARAMS_IS_BLANK(1002, "参数为空"); /*用户错误2001-2999*/ private Integer code; private String message; ResultCode(int code, String message) { this.code = code; this.message = message; } } 定义返回状态码,和信息一一对应,我们可以约定xxx~xxx 为什么错误码,防止后期错误码重复,使用混乱不清楚, 定义返回体结果体 package cn.soboys.core.ret; import lombok.Data; import java.io.Serializable; /** * @author kenx * @version 1.0 * @date 2021/6/17 15:47 * 统一API响应结果格式封装 */ @Data public class Result<T> implements Serializable { private static final long serialVersionUID = 6308315887056661996L; private Integer code; private String message; private T data; public Result setResult(ResultCode resultCode) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); return this; } public Result setResult(ResultCode resultCode,T data) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); this.setData(data); return this; } } code,和message都从定义的状态枚举中获取 这里有两个需要注意地方我的数据类型T data返回的是泛型类型而不是object类型而且我的结果累实现了Serializable接口 我看到网上有很多返回object,最后返回泛型因为泛型效率要高于object,object需要强制类型转换,还有最后实现了Serializable接口因为通过流bytes传输方式web传输,速率更块 定义返回结果方法 一般业务返回要么是 success成功,要么就是failure失败,所以我们需要单独定义两个返回实体对象方法, package cn.soboys.core.ret; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:30 * 响应结果返回封装 */ public class ResultResponse { private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS"; // 只返回状态 public static Result success() { return new Result() .setResult(ResultCode.SUCCESS); } // 成功返回数据 public static Result success(Object data) { return new Result() .setResult(ResultCode.SUCCESS, data); } // 失败 public static Result failure(ResultCode resultCode) { return new Result() .setResult(resultCode); } // 失败 public static Result failure(ResultCode resultCode, Object data) { return new Result() .setResult(resultCode, data); } } 注意这里我定义的是静态工具方法,因为使用构造方法进行创建对象调用太麻烦了, 我们使用静态方法来就直接类调用很方便 这样我们就可以在controller中很方便返回统一api格式了 package cn.soboys.mallapi.controller; import cn.soboys.core.ret.Result; import cn.soboys.core.ret.ResultResponse; import cn.soboys.mallapi.bean.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author kenx * @version 1.0 * @date 2021/7/2 20:28 */ @RestController //默认全部返回json @RequestMapping("/user") public class UserController { @GetMapping("/list") public Result getUserInfo(){ User u=new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return ResultResponse.success(u); } } 返回结果符合我们预期json格式 但是这个代码还可以优化,不够完善,比如,每次controller中所有的方法的返回必须都是要Result类型,我们想返回其他类型格式怎么半,还有就是不够语义化,其他开发人员看你方法根本就不知道具体返回什么信息 如果改成这个样子就完美了如 @GetMapping("/list") public User getUserInfo() { User u = new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return u; } 其他开发人员一看就知道具体是返回什么数据。但这个格式要怎么去统一出来? 其实我们可以这么去优化,通过SpringBoot提供的ResponseBodyAdvice进行统一响应处理 自定义注解@ResponseResult来拦截有此controller注解类的代表需要统一返回json格式,没有就安照原来返回 package cn.soboys.core.ret; import java.lang.annotation.*; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:43 * 统一包装接口返回的值 Result */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseResult { } 定义请求拦截器通过反射获取到有此注解的HandlerMethod设置包装拦截标志 package cn.soboys.core.ret; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * @author kenx * @version 1.0 * @date 2021/6/17 17:10 * 请求拦截 */ public class ResponseResultInterceptor implements HandlerInterceptor { //标记名称 public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //请求方法 if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); //判断是否在对象上加了注解 if (clazz.isAnnotationPresent(ResponseResult.class)) { //设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); //方法体上是否有注解 } else if (method.isAnnotationPresent(ResponseResult.class)) { //设置此请求返回体需要包装,往下传递,在ResponseBodyAdvice接口进行判断 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); } } return true; } } 实现ResponseBodyAdvice<Object> 接口自定义json返回解析器根据包装拦截标志判断是否需要自定义返回类型返回类型 package cn.soboys.core.ret; import cn.soboys.core.utils.HttpContextUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import javax.servlet.http.HttpServletRequest; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:47 * 全局统一响应返回体处理 */ @Slf4j @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; /** * @param methodParameter * @param aClass * @return 此处如果返回false , 则不执行当前Advice的业务 * 是否请求包含了包装注解 标记,没有直接返回不需要重写返回体, */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { HttpServletRequest request = HttpContextUtil.getRequest(); //判断请求是否有包装标志 ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn == null ? false : true; } /** * @param body * @param methodParameter * @param mediaType * @param aClass * @param serverHttpRequest * @param serverHttpResponse * @return 处理响应的具体业务方法 */ @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (body instanceof Result) { return body; } else if (body instanceof String) { return JSON.toJSONString(ResultResponse.success(body)); } else { return ResultResponse.success(body); } } } 注意这里string类型返回要单独json序列化返回一下,不然会报转换异常 这样我们就可以在controler中返回任意类型,了不用每次都必须返回 Result 如 package cn.soboys.mallapi.controller; import cn.soboys.core.ret.ResponseResult; import cn.soboys.core.ret.Result; import cn.soboys.core.ret.ResultResponse; import cn.soboys.mallapi.bean.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author kenx * @version 1.0 * @date 2021/7/2 20:28 */ @RestController //默认全部返回json @RequestMapping("/user") @ResponseResult public class UserController { @GetMapping("/list") public User getUserInfo() { User u = new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return u; } @GetMapping("/test") public String test() { return "ok"; } @GetMapping("/test2") public Result test1(){ return ResultResponse.success(); } } 这里还有一个问题?正常情况返回成功的话是统一json 格式,但是返回失败,或者异常了,怎么统一返回错误json 格式,sprinboot有自己的错误格式? 请参考我上一篇,SpringBoot优雅的全局异常处理 扫码关注公众号猿人生了解更多好文

优秀的个人博客,低调大师

k8s监控体系搭建prometheus+grafana+alertmanager坑版

2 --> 提前准备 1.已经安装了k8s集群 2.已经安装了storgeclass(可以参考k8s存储管理 https://blog.51cto.com/luoguoling/2966225) 一.安装prometheus 1.0 提前设置namespace apiVersion: v1 kind: Namespace metadata: name: ops 1.1 prometheus配置文件 prometheus-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config namespace: ops data: prometheus.yml: | rule_files: - /etc/config/rules/*.rules scrape_configs: - job_name: prometheus static_configs: - targets: - localhost:9090 - job_name: kubernetes-apiservers kubernetes_sd_configs: - role: endpoints relabel_configs: - action: keep regex: default;kubernetes;https source_labels: - __meta_kubernetes_namespace - __meta_kubernetes_service_name - __meta_kubernetes_endpoint_port_name scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt insecure_skip_verify: true bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - job_name: kubernetes-nodes-kubelet kubernetes_sd_configs: - role: node # 发现集群中的节点 relabel_configs: # 将标签(.*)作为新标签名,原有值不变 - action: labelmap regex: __meta_kubernetes_node_label_(.+) scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt insecure_skip_verify: true bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - job_name: kubernetes-nodes-cadvisor kubernetes_sd_configs: - role: node relabel_configs: # 将标签(.*)作为新标签名,原有值不变 - action: labelmap regex: __meta_kubernetes_node_label_(.+) # 实际访问指标接口 https://NodeIP:10250/metrics/cadvisor,这里替换默认指标URL路径 - target_label: __metrics_path__ replacement: /metrics/cadvisor scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt insecure_skip_verify: true bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - job_name: kubernetes-service-endpoints kubernetes_sd_configs: - role: endpoints # 从Service列表中的Endpoint发现Pod为目标 relabel_configs: # Service没配置注解prometheus.io/scrape的不采集 - action: keep regex: true source_labels: - __meta_kubernetes_service_annotation_prometheus_io_scrape # 重命名采集目标协议 - action: replace regex: (https?) source_labels: - __meta_kubernetes_service_annotation_prometheus_io_scheme target_label: __scheme__ # 重命名采集目标指标URL路径 - action: replace regex: (.+) source_labels: - __meta_kubernetes_service_annotation_prometheus_io_path target_label: __metrics_path__ # 重命名采集目标地址 - action: replace regex: ([^:]+)(?::\d+)?;(\d+) replacement: $1:$2 source_labels: - __address__ - __meta_kubernetes_service_annotation_prometheus_io_port target_label: __address__ # 将K8s标签(.*)作为新标签名,原有值不变 - action: labelmap regex: __meta_kubernetes_service_label_(.+) # 生成命名空间标签 - action: replace source_labels: - __meta_kubernetes_namespace target_label: kubernetes_namespace # 生成Service名称标签 - action: replace source_labels: - __meta_kubernetes_service_name target_label: kubernetes_name - job_name: kubernetes-pods kubernetes_sd_configs: - role: pod # 发现所有Pod为目标 # 重命名采集目标协议 relabel_configs: - action: keep regex: true source_labels: - __meta_kubernetes_pod_annotation_prometheus_io_scrape # 重命名采集目标指标URL路径 - action: replace regex: (.+) source_labels: - __meta_kubernetes_pod_annotation_prometheus_io_path target_label: __metrics_path__ # 重命名采集目标地址 - action: replace regex: ([^:]+)(?::\d+)?;(\d+) replacement: $1:$2 source_labels: - __address__ - __meta_kubernetes_pod_annotation_prometheus_io_port target_label: __address__ # 将K8s标签(.*)作为新标签名,原有值不变 - action: labelmap regex: __meta_kubernetes_pod_label_(.+) # 生成命名空间标签 - action: replace source_labels: - __meta_kubernetes_namespace target_label: kubernetes_namespace # 生成Service名称标签 - action: replace source_labels: - __meta_kubernetes_pod_name target_label: kubernetes_pod_name alerting: alertmanagers: - static_configs: - targets: ["alertmanager:80"] 1.2 prometheus部署文件 prometheus-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: prometheus namespace: ops labels: k8s-app: prometheus spec: replicas: 1 selector: matchLabels: k8s-app: prometheus template: metadata: labels: k8s-app: prometheus spec: serviceAccountName: prometheus initContainers: - name: "init-chown-data" image: "busybox:latest" # imagePullPolicy: "IfNotPresent" command: ["chown", "-R", "65534:65534", "/data"] volumeMounts: - name: prometheus-data mountPath: /data subPath: "" containers: - name: prometheus-server-configmap-reload image: "jimmidyson/configmap-reload:v0.1" # imagePullPolicy: "IfNotPresent" args: - --volume-dir=/etc/config - --webhook-url=http://localhost:9090/-/reload volumeMounts: - name: config-volume mountPath: /etc/config readOnly: true - mountPath: /etc/localtime name: timezone resources: limits: cpu: 10m memory: 100Mi requests: cpu: 10m memory: 100Mi - name: prometheus-server image: "prom/prometheus:v2.20.0" # imagePullPolicy: "IfNotPresent" args: - --config.file=/etc/config/prometheus.yml - --storage.tsdb.path=/data - --web.console.libraries=/etc/prometheus/console_libraries - --web.console.templates=/etc/prometheus/consoles - --web.enable-lifecycle ports: - containerPort: 9090 readinessProbe: httpGet: path: /-/ready port: 9090 initialDelaySeconds: 30 timeoutSeconds: 30 livenessProbe: httpGet: path: /-/healthy port: 9090 initialDelaySeconds: 30 timeoutSeconds: 30 resources: limits: cpu: 500m memory: 800Mi requests: cpu: 200m memory: 400Mi volumeMounts: - name: config-volume mountPath: /etc/config - name: prometheus-data mountPath: /data subPath: "" - name: prometheus-rules mountPath: /etc/config/rules - mountPath: /etc/localtime name: timezone volumes: - name: config-volume configMap: name: prometheus-config - name: prometheus-rules configMap: name: prometheus-rules - name: prometheus-data persistentVolumeClaim: claimName: prometheus - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: prometheus namespace: ops spec: storageClassName: "managed-nfs-storage" accessModes: - ReadWriteMany resources: requests: storage: 10Gi --- apiVersion: v1 kind: Service metadata: name: prometheus namespace: ops spec: type: NodePort ports: - name: http port: 9090 protocol: TCP targetPort: 9090 nodePort: 30089 selector: k8s-app: prometheus --- apiVersion: v1 kind: ServiceAccount metadata: name: prometheus namespace: ops --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: - "" resources: - nodes - nodes/metrics - services - endpoints - pods verbs: - get - list - watch - apiGroups: - "" resources: - configmaps verbs: - get - nonResourceURLs: - "/metrics" verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: ops 1.3 prometheus配置报警规则 prometheus-rules.yaml apiVersion: v1 kind: ConfigMap metadata: name: prometheus-rules namespace: ops data: general.rules: | groups: - name: general.rules rules: - alert: InstanceDown expr: up == 0 for: 1m labels: severity: error annotations: summary: "Instance {{ $labels.instance }} 停止工作" description: "{{ $labels.instance }} job {{ $labels.job }} 已经停止5分钟以上." node.rules: | groups: - name: node.rules rules: - alert: NodeFilesystemUsage expr: | 100 - (node_filesystem_free{fstype=~"ext4|xfs"} / node_filesystem_size{fstype=~"ext4|xfs"} * 100) > 80 for: 1m labels: severity: warning annotations: summary: "Instance {{ $labels.instance }} : {{ $labels.mountpoint }} 分区使用率过高" description: "{{ $labels.instance }}: {{ $labels.mountpoint }} 分区使用大于80% (当前值: {{ $value }})" - alert: NodeMemoryUsage expr: | 100 - (node_memory_MemFree+node_memory_Cached+node_memory_Buffers) / node_memory_MemTotal * 100 > 20 for: 1m labels: severity: warning annotations: summary: "Instance {{ $labels.instance }} 内存使用率过高" description: "{{ $labels.instance }}内存使用大于80% (当前值: {{ $value }})" - alert: NodeCPUUsage expr: | 100 - (avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100) > 60 for: 1m labels: severity: warning annotations: summary: "Instance {{ $labels.instance }} CPU使用率过高" description: "{{ $labels.instance }}CPU使用大于60% (当前值: {{ $value }})" - alert: KubeNodeNotReady expr: | kube_node_status_condition{condition="Ready",status="true"} == 0 for: 1m labels: severity: error annotations: message: '{{ $labels.node }} 已经有10多分钟没有准备好了.' pod.rules: | groups: - name: pod.rules rules: - alert: PodCPUUsage expr: | sum(rate(container_cpu_usage_seconds_total{image!=""}[1m]) * 100) by (pod_name, namespace) > 5 for: 5m labels: severity: warning annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod_name }} CPU使用大于80% (当前值: {{ $value }})" - alert: PodMemoryUsage expr: | sum(container_memory_rss{image!=""}) by(pod_name, namespace) / sum(container_spec_memory_limit_bytes{image!=""}) by(pod_name, namespace) * 100 != +inf > 80 for: 5m labels: severity: error annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod_name }} 内存使用大于80% (当前值: {{ $value }})" - alert: PodNetworkReceive expr: | sum(rate(container_network_receive_bytes_total{image!="",name=~"^k8s_.*"}[5m]) /1000) by (pod_name,namespace) > 30000 for: 5m labels: severity: warning annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod_name }} 入口流量大于30MB/s (当前值: {{ $value }}K/s)" - alert: PodNetworkTransmit expr: | sum(rate(container_network_transmit_bytes_total{image!="",name=~"^k8s_.*"}[5m]) /1000) by (pod_name,namespace) > 30000 for: 5m labels: severity: warning annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod_name }} 出口流量大于30MB/s (当前值: {{ $value }}/K/s)" - alert: PodRestart expr: | sum(changes(kube_pod_container_status_restarts_total[1m])) by (pod,namespace) > 0 for: 1m labels: severity: warning annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod }} Pod重启 (当前值: {{ $value }})" - alert: PodFailed expr: | sum(kube_pod_status_phase{phase="Failed"}) by (pod,namespace) > 0 for: 5s labels: severity: error annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod }} Pod状态Failed (当前值: {{ $value }})" - alert: PodPending expr: | sum(kube_pod_status_phase{phase="Pending"}) by (pod,namespace) > 0 for: 1m labels: severity: error annotations: summary: "命名空间: {{ $labels.namespace }} | Pod名称: {{ $labels.pod }} Pod状态Pending (当前值: {{ $value }})" e 二.alertmanager搭建 2.1 alertmanager配置文件alertmanger-configmap.yaml 注:邮箱需要自己去网易邮箱申请并且取得授权管理密码 apiVersion: v1 kind: ConfigMap metadata: name: alertmanager-config namespace: ops data: alertmanager.yml: |- global: # 在没有报警的情况下声明为已解决的时间 resolve_timeout: 5m # 配置邮件发送信息 smtp_smarthost: 'smtp.163.com:465' smtp_from: 'xxx@163.com' smtp_auth_username: 'xxx@163.com' smtp_auth_password: 'GMZFNPIWGYEPMGDK' smtp_hello: '163.com' smtp_require_tls: false # 所有报警信息进入后的根路由,用来设置报警的分发策略 route: # 这里的标签列表是接收到报警信息后的重新分组标签,例如,接收到的报警信息里面有许多具有 cluster=A 和 alertname=LatncyHigh 这样的标签的报警信息将会批量被聚合到一个分组里面 group_by: ['alertname', 'cluster'] # 当一个新的报警分组被创建后,需要等待至少group_wait时间来初始化通知,这种方式可以确保您能有足够的时间为同一分组来获取多个警报,然后一起触发这个报警信息。 group_wait: 30s # 当第一个报警发送后,等待'group_interval'时间来发送新的一组报警信息。 group_interval: 5m # 如果一个报警信息已经发送成功了,等待'repeat_interval'时间来重新发送他们 repeat_interval: 5m # 默认的receiver:如果一个报警没有被一个route匹配,则发送给默认的接收器 receiver: default # 上面所有的属性都由所有子路由继承,并且可以在每个子路由上进行覆盖。 routes: - receiver: email group_wait: 10s match: team: node receivers: - name: 'default' email_configs: - to: 'xxx@xx.com' send_resolved: true - name: 'email' email_configs: - to: 'xxx@qq.com' send_resolved: true 2.2 alertmanager template文件alertmanager-template.yaml #自定义告警模板 apiVersion: v1 kind: ConfigMap metadata: name: alertmanager-template-volume namespace: ops data: email.tmpl: | {{ define "email.html" }} {{ range .Alerts }} <pre> ========start========== 告警程序: prometheus_alert_email 告警级别: {{ .Labels.severity }} 级别 告警类型: {{ .Labels.alertname }} 故障主机: {{ .Labels.instance }} 告警主题: {{ .Annotations.summary }} 告警详情: {{ .Annotations.description }} 处理方法: {{ .Annotations.console }} 触发时间: {{ .StartsAt.Format "2006-01-02 15:04:05" }} ========end========== </pre> {{ end }} {{ end }} 2.3alertmanager部署文件alertmanager-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: alertmanager namespace: ops spec: replicas: 1 selector: matchLabels: k8s-app: alertmanager version: v0.14.0 template: metadata: labels: k8s-app: alertmanager version: v0.14.0 spec: containers: - name: prometheus-alertmanager image: "prom/alertmanager:v0.14.0" imagePullPolicy: "IfNotPresent" args: - --config.file=/etc/config/alertmanager.yml - --storage.path=/data - --web.external-url=/ ports: - containerPort: 9093 readinessProbe: httpGet: path: /#/status port: 9093 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: - name: config-volume mountPath: /etc/config #自定义告警模板 - name: config-template-volume mountPath: /etc/config/template - name: storage-volume mountPath: "/data" subPath: "" - mountPath: /etc/localtime name: timezone resources: limits: cpu: 10m memory: 200Mi requests: cpu: 10m memory: 100Mi - name: prometheus-alertmanager-configmap-reload image: "jimmidyson/configmap-reload:v0.1" imagePullPolicy: "IfNotPresent" args: - --volume-dir=/etc/config - --webhook-url=http://localhost:9093/-/reload volumeMounts: - name: config-volume mountPath: /etc/config readOnly: true resources: limits: cpu: 10m memory: 200Mi requests: cpu: 10m memory: 100Mi volumes: - name: config-volume configMap: name: alertmanager-config - name: config-template-volume configMap: name: alertmanager-template-volume - name: storage-volume persistentVolumeClaim: claimName: alertmanager - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: alertmanager namespace: ops spec: storageClassName: managed-nfs-storage accessModes: - ReadWriteOnce resources: requests: storage: "2Gi" --- apiVersion: v1 kind: Service metadata: name: alertmanager namespace: ops labels: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: "Alertmanager" spec: type: "NodePort" ports: - name: http port: 80 protocol: TCP targetPort: 9093 nodePort: 30093 selector: k8s-app: alertmanager 三 安装监控界面grafana 3.1 grafana部署文件 apiVersion: apps/v1 kind: Deployment metadata: name: grafana namespace: ops spec: replicas: 1 selector: matchLabels: app: grafana template: metadata: labels: app: grafana spec: containers: - name: grafana image: grafana/grafana:7.1.0 ports: - containerPort: 3000 protocol: TCP resources: limits: cpu: 100m memory: 256Mi requests: cpu: 100m memory: 256Mi volumeMounts: - name: grafana-data mountPath: /var/lib/grafana subPath: grafana - mountPath: /etc/localtime name: timezone securityContext: fsGroup: 472 runAsUser: 472 volumes: - name: grafana-data persistentVolumeClaim: claimName: grafana - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: grafana namespace: ops spec: storageClassName: "managed-nfs-storage" accessModes: - ReadWriteMany resources: requests: storage: 5Gi --- apiVersion: v1 kind: Service metadata: name: grafana namespace: ops spec: type: NodePort ports: - port : 80 targetPort: 3000 nodePort: 30030 selector: app: grafana 3.2 数据源和监控模板 数据源填写配置prometheus.ops:9090 模板可以下载下面3个 k8s工作节点监控 k8s集群资源监控 k8s资源对象状态监控 k8s监控模板 提取码: aexi 操作结果展示 1.prometheus效果 4.2 alertmanager显示效果 4.3报警结果 4.4 grafana监控图部分图 文章参考了https://i4t.com/4197.html

优秀的个人博客,低调大师

为何绝大数多少消费者仍然对5G感?

尽管许多城市已经部署了下一代网络,让部分消费体验到了5G网络带来的非凡体验,但某些城市却出于某些原因而暂缓5G网络的部署建设工作。尽管目前运营商大肆宣传,但许多客户似乎仍未意识到5G的优势。 下一代蜂窝技术5G为居民、企业主以及希望使自己的城市成为有竞争力的“智慧城市”的城市官员提供了巨大的潜力。但是目前,许多客户并不了解其潜在优势。 今天,5G将为客户带来更高质量的远程医疗访问和较少故障的在线课程,视频下载将花费更少的时间。未来,5G可能会让居民在到达之前找到一个开放的停车位,或者使与设备直接通信的物联网蓬勃发展。 “市场上一些与5G兼容的设备都存在过度炒作,但许多地区5G尚不具备正常的服务能力。” 由于5G网络前期基础设施部署投入耗资巨大,除了个别大型城市中的重点区域有相对较好的信号覆盖之外,绝大多数地区仍处于5G信号覆盖盲区。而且,相对高昂资费以及缺乏5G杀手级应用的出现,普通消费者升级5G的热情并不高。 购买新的5G兼容手机的消费者,如果居住在已经拥有5G的社区中,则可以期望获得更快的下载速度和更高质量的视频。如果他们居住地还没有5G网络覆盖,他们甚至可能不会注意到它的存在。他们的手机将继续在4G上运行。 5G并不仅仅是为了改进日常生活通讯交流而生,5G的真正用武之地是在未来的万物互联,这才是发挥5G高速率、高带宽、高容量的特性。 什么是5G IoT,它将如何改变连接性? 5G将从根本上改变我们世界网络连接的方式。不久之后,全球社会将跨行业、市场和地区适应新的技术生活方式。这一新技术标准不仅仅承诺对现有移动通信技术的进一步发展。 数字化,社会和经济的全面变化将在生活的几乎所有领域发生。到目前为止,主要目标是全面扩展常规网络的基础设施条件,以确保几乎所有移动设备的网络可用性。在未来的几年中,除了在5G IoT中持续进行联网之外,重点还将放在以前所未有的最佳方式满足联网社会不断增长的需求。 无限连接到网络未来 全球数据量持续增长,使5G不可或缺。由于数据的巨大增长,从中期来看,现有技术将不能再满足物联网世界的需求。德国是数据量发展的一个很好的例子。 2017年,德国的数据量达到10亿千兆字节,已经是2015年的两倍。 根据这些发现,专家估计,到2020年,全世界的物联网数量将在50到5000亿之间。这证明了5G网络的必要性,这为我们的全球经济提供了巨大的潜力:如此高的数据量与IoT设备的数量相结合,以及只有在5G的帮助下才能实现有关IoT网络的个性化需求。 为什么5G注定要用于物联网? 得益于所谓的“3G”蜂窝标准,智能手机成为可能,因为3G是当时生产智能手机的主要驱动力。第四代移动网络标准是在某个时候创建的。多亏了“ LTE”,数据传输速率大大提高了。 直到今天,LTE仍是最流行和最常用的网络。对于网络而言,高达每秒100兆位的速度是没有问题的,并且在经济和社会的许多领域中已经成为现实。甚至可以修改LTE带宽,以最终达到每秒4000兆位的下载速度。 然而,展望未来,LTE不足以满足新技术的标准和期望。 LTE的设计和优化主要针对智能手机,而5G将成为所有物联网的移动标准。 5G IoT在各个方面都达到了新的高度。新网络中的数据吞吐量应达到每秒20吉比特,并允许更短的响应时间。相比之下,首款具有1G网络连接的手机要比5G网络少八百万倍。 使用5G,还可以实时传输数据。这意味着将可以同时访问全球1000亿个移动设备。换句话说,连接密度约为每平方公里一百万个设备。同时,新技术带来了相对运动速度的提高。这意味着最高时速500公里时,连接质量将更加稳定,这将带来巨大的好处,特别是对于铁路旅客而言。 无论智能手机如何,在其他应用领域中不可避免地要增加数据量。这些数字不仅听起来很大,而且真的很大。由于这些原因以及其他许多原因,5G IoT将成为连接的新关键技术。 多元化和创新的应用领域 例如,除了物联网,工业4.0也将从5G技术中受益匪浅。机器、系统、机器人和人之间的持续数据交换将成为工业生产的组成部分。连接的设备和部件的数量将大大增加。例如,工业机器人的控制单元可以实时处理,并且错误概率最终可以降低到最小。例如,无人驾驶快递服务将因此能够始终在机器的装卸点准时取货。 业界有许多示例说明如何使用5G优化运营流程。新技术带来了前所未有的机会和想法,因为以前在技术上是不可能的。 这些想法和机会也正在适应其他经济部门,而与行业无关。例如,具有所谓的智能农业的农业。借助数字化使用,有关动物健康、杂草或害虫在哪里或土壤中的水分状况如何的数据和见解都成为可能。这样的智能网络将在未来成倍增长和成熟。借助5G IoT连通性,智能农业创造了传统农业尚无法做到的事情:它在提高食品生产效率的同时创造了更大程度的生态。 在数字化环境中驱动连接 网络连接领域的数字化转型已经变得显而易见,并且潜力被明确定义。根据有关5G网络中带宽或设备密度的令人印象深刻的数字,给人的印象是连通性发挥了其最大潜力。 良好运行的连接性是设置物联网网络(无论大小)的最重要基础。借助5G技术,也许还有世界上从未有人想到过的想法、方法和措施。可能是由于5G连通性,无数新的商业理念被带入市场,继续革新着我们的数字世界。 5G技术何时会达到市场成熟还有待观察。但是,可以肯定的是:技术在起作用,世界已经为数字未来的另一次飞跃做好了准备。 5G能否推动智能家居市场的创新? 在5G网络服务大规模普及应用之前,我们还有很长的路要走,但是我们可以讨论5G的未来含义,以及一旦广泛使用后5G如何改善设备功能。 5G的影响在于可以通过基于云的系统在智能家居设备之间传输的数据量增加。通过利用云的海量计算能力及其处理更大容量数据的能力,我们可以接收更深入的分析,这些分析可以通过使智能家居设备更快、更智能来改善智能家居设备。 那么,5G会促进智能家居市场的创新吗? 当前的智能家居视频监控系统可以显示使用5G进行创新的示例。当前的产品允许您使用运动检测等功能。就目前而言,这是一种监视的基本形式,它监视图像的变化并在发生异常情况时通知所有者。 一旦5G发挥作用,捕获的视频数据就可以发送到云中,进行更详细的分析,并且可以使系统推断出该运动是来自人类,物体还是动物。面部识别也可以在这里发挥作用,在向警察报告事件时提供更无缝的服务。 同样,我们可以使用家庭中各种设备的数据来加强安全措施。与存在或热探测器结合使用的摄像机可以通过为“更大的画面”提供更多的数据点来进行操作,从而消除小错误-合并使用所有智能设备。 我们不能仅仅依靠智能设备为我们做出决策,但是我们可以做的就是改善设备处理,以便在我们介入时,我们已经掌握了所有必要的信息,可以评估适当的行动呼吁。 5G将让房主在自己的房屋内做什么? 目前,使用4G的智能家居采用整合Wi-Fi、蓝牙和其他网络协议的方式以分散的方式运行。与4G不同,5G将与低功耗设备一起使用,使其可用于更广泛的连接产品。这意味着我们将能够连接所有设备,以允许所有设备之间进行集成通信。 例如,你的冰箱和其他厨房电器可以连接在一起,与家庭系统一起协同工作,以创建一个完全自动化的家庭。如果你的冰箱已连接互联网,则如果断电,你可能会收到通知,但是由于家里的其他所有设备也都已连接,因此你可以立即确定是电源故障还是产品故障。更快的连接性意味着用户可以快速利用其智能设备提供的数据,例如可以监控水位并允许进行行为更改以限制用水的用水传感器。 车辆同样适用。未来的汽车将是自动驾驶的,并包括一个集成的行车记录仪,然后可以将其连接到你的安全系统,以在你的房屋外围提供现场增强的安全性,在潜在的入侵者到达你的前门时提醒你。

优秀的个人博客,低调大师

kafka 生产发送消息失败响应,或者Error while fetching metadata with correlation id

今天在使用代码编写kafka 生产者发送消息的时候,因为我的手误出现的搞笑的事情。 同样的代码和kafka 在不久前执行过,是没有问题的。 代码如下 package streaming.utils import java.util import java.util.{Date, Properties, UUID} import com.alibaba.fastjson.JSONObject import org.apache.commons.lang3.time.FastDateFormat import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord} import scala.util.Random /** * Author: Michael PK QQ: 1990218038 * * Kafka数据生产者 */ object ProducerApp { def main(args: Array[String]): Unit = { val props = new Properties props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer") props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer") props.put("bootstrap.servers", ParamsConf.brokers) props.put("request.required.acks","1") val topic = ParamsConf.topic val producer = new KafkaProducer[String,String](props) val random = new Random() val dateFormat = FastDateFormat.getInstance("yyyyMMddHHmmss") for(i <- 1 to 100){ val time = dateFormat.format(new Date())+"" val userid = random.nextInt(1000)+"" val courseid = random.nextInt(500)+"" val fee = random.nextInt(400)+"" val result = Array("0","1") // 0未成功支付,1成功支付 val flag = result(random.nextInt(2)) var orderid = UUID.randomUUID().toString val map = new util.HashMap[String, Object]() map.put("time", time) map.put("userid",userid) map.put("courseid",courseid) map.put("fee", fee) map.put("flag", flag) map.put("orderid",orderid) val json = new JSONObject(map) producer.send(new ProducerRecord[String,String](topic(0),json.toJSONString)) } println("PK Kafka生产者生产数据完毕...") } } 代码很简单。只是用来模拟生产数据而已。 一直以来的使用的 都是 2.0 版本的 kafka client <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.0.0</version> </dependency> 但是今天执行的上面的代码的时候。 就 不能发生消息了,也没有错误的提示。 程序也没有关闭。 通过 debug 发现卡在 doSend 代码里面了 private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { throwIfProducerClosed(); // first make sure the metadata for the topic is available ClusterAndWaitTime clusterAndWaitTime; try { clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs); } catch (KafkaException e) { if (metadata.isClosed()) throw new KafkaException("Producer closed while send in progress", e); throw e; } 虽然它抛出了异常,但是 不能进入 if (metadata.isClosed()) 逻辑里面 外层并没有捕获它的异常。通过debug 这个 异常 e 是 Failed to update metadata after 60000 ms. 考虑到它的版本也服务器版本不一样 就试着 减低版本看看 <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>1.1.0</version> </dependency> 执行的时候不停的提示: lientId=producer-1] Error while fetching metadata with correlation id 参考 https://blog.csdn.net/luozhonghua2014/article/details/80369469 https://www.jianshu.com/p/2db7abddb9e6 https://jingyan.baidu.com/article/86f4a73ed181b837d6526930.html 并且测试过了 :telnet 192.168.0.205 9092是没有问题的 在 服务器本地上面使用 命令生产消费消息是可以的。 这样就奇怪了。 于是我换成了 另一个 主题进行测试发现是没有问题的。。。 同时注意到了原来是 我写的 主题名称 后面带有空格 低级错误啊!但是后面的空格真的是空格?我自己测试手敲空格,经过测试没有问题的。也就是如果是 主题后面有空格是可以的。 那就是主题名称 后面带上了什么不可内容。 我想起来了,我是通过复制这个主题名称 的,估计复制多了什么其他内容。

优秀的个人博客,低调大师

踏破铁鞋觅处,从AsyncTask学Android线程池

android对于主线程的响应时间限制的非常严格,稍有不慎就会遇到Application Not Responding(ANR)的弹框。用户可以轻点手指关掉你的APP。官方文档写的非常明确!同时,保持应用随时响应用户的操作也是良好用户体验的前提。 线程的开始和结束 要做到以上多线程是必不可少的。课本会告诉你什么时候开辟一个线程,但是很少说的一个很重要的问题是结束。比如,我现在在Activity里有一个工作需要创建一个线程执行,但是这个Activity在进入后台后不幸遇到系统回收资源被销毁了。但是这个线程还在漫无目的的游走,耗费资源。 如何结束?先创建一个: mThread = Thread(Runnable { // do something here... }) mThread?.start() 以上使用kotlin的lambda表达式简写了创建Runnable对象部分的代码。主旨还是创建了一个Runnable对象,并将其作为参数传入Thread。 如何让一个Thread能够退出呢?这就要在Runnable身上下功夫了。首先添加一个是否停止的标识isCancelled,一旦值为true则停止线程的运行,否则继续。我们这里不讨论Thread#interrupt()这个方法,这个方法诡异的地方太多。 首先要给Runnable“添加一个属性”作为上文的是否停止的标识。直接添加时不可能的,Runnable只是一个interface,不是class。所以要实现这个借口为一个抽象类,这样就可以添加属性了。 abstract class CancelableRunnable() : Runnable { var isCancelled: Boolean = false } 这里使用抽象类,是因为run()方法的实现留给使用的时候给出。 var runnable = object : CancelableRunnable() { override fun run() { if (isCancelled) { var msg = mHandler.obtainMessage(THREAD_CANCELLED) mHandler.sendMessage(msg) return } Thread.sleep(2000) if (isCancelled) { var msg = mHandler.obtainMessage(THREAD_CANCELLED) mHandler.sendMessage(msg) return } var msg = mHandler.obtainMessage(THREAD_FINISHED) mHandler.sendMessage(msg) } } Thread.sleep(2000)用来模拟一个费时的任务。开始之前检测是否取消了线程的执行,执行之后在检测。之后的检测是有的时候任务执行之后需要有持久化处理结果或者修改任务完成情况的标识之类的动作,如果已经取消了线程的执行,即使任务执行完成也不持久化结果、不修改完成情况。 最后都检测完成之后如果没有取消线程,则发出任务完成执行的消息。 发出和处理这些消息的Handler的定义: var mHandler = object : Handler() { override fun handleMessage(msg: Message?) { when (msg?.what) { THREAD_CANCELLED -> { mResultTextView.text = "thread cancelled" } THREAD_FINISHED -> { mResultTextView.text = "thread finished" } else -> { mResultTextView.text = "what's going on" } } } } 运行在UI线程的Handler检测从线程发出的消息,如果是THREAD_CANCELLED那就是线程已经取消了,如果是THREAD_FINISHED那就是线程完全运行结束。之后根据message的消息设置TextView的文本内容。 这里使用了两个按钮来启动和停止线程: findViewById(R.id.start_button)?.setOnClickListener { v -> runnable.isCancelled = false mThread = Thread(runnable) mThread?.start() mResultTextView.text = "Thread running..." } findViewById(R.id.stop_button)?.setOnClickListener { v -> this.runnable.isCancelled = true } 上面用到的Runnable是只做一件事的,如果是连续不断的循环很多事的话也可以使用white语句来控制是否一直执行线程的工作。一旦设置为停止线程,则停止线程任务的循环跳出Runnable#run()方法,结束线程。 完整代码放在附录中。 所以,如果你在Activity里开辟了一个线程,在Activity被回收的时候结束线程就可以这么做: override fun onDestroy() { super.onDestroy() this.runnable.isCancelled = true } 这样就再也不用担心Activity挂了,线程还阴魂不散了。 AsyncTask 既然缘起AsyncTask那就肯定需要读者一起了解一下相关的概念。 比起来使用Handler+Thread+Runnable的多线程异步执行模式来说,使用AsyncTask是简单了非常的多的。 先简单了解一下AsyncTask。 public abstract class AsyncTask<Params, Progress, Result> AsyncTask是一个抽象泛型类。三个类型Params,Progress,Result分别对应的是输入参数的类型,精度更新使用的类型,最后是返回结果的类型。其中任何一个类型如果你不需要的话,可以使用java.lang.Void代替。 继承AsyncTask给出自己的实现,最少需要实现doInBackground方法。doInBackground方法是在后台线程中运行的。如果要在任务执行之后更新UI线程的话还至少需要给出onPostExecute方法的实现,在这个方法中才可以更新UI。 上述的两个方法已经构成了一个AsyncTask使用的基本单元。在后台线程处理一些任务,并在处理完成之后更新UI。但是如果一个任务比较长,只是在最后更新UI是不够的,还需要不断的提示用户已经完成的进度是多少。这就是需要另外实现onProgressUpdate方法。并在doInBackground方法中调用publishProgress方法发出每个任务的处理进度。 这个AsyncTask总体上就是这样的了: inner class DemoAsyncTask() : AsyncTask<String, Int, String>() { // var isRunning = true override fun doInBackground(vararg params: String?): String? { Log.i(TAG, "##AsyncTask doing something...") var i = 0 val TOTAL = 100000000 var progress = 0 while (i < TOTAL) { Log.d(TAG, "doning jobs $i is cancelled $isCancelled") i++ var currentProgress = i.toFloat() / TOTAL if (currentProgress > progress && Math.abs(currentProgress - progress) > 0.1) { progress = currentProgress publishProgress((progress * 100).toInt()) } } } Log.d(TAG, "doing jobs $i is cancelled $isCancelled") return "Task done" } override fun onPostExecute(result: String?) { this@CancalableActivity.mAsyncTextView?.text = result } override fun onProgressUpdate(vararg values: Int?) { mAsyncTextView?.text = "${mAsyncTextView?.text ?: "Async task..."} progress: ${values?.get(0) ?: 0}" } } 到这里各位读者应该对AsyncTask已经有一个总体的认识了。后台任务在doInBackground处理,处理过程的百分比使用publishProgress方法通知,并在onProgressUpdate方法中更新UI的百分比。最后任务处理全部完成之后在onPostExecute更新UI,显示全部完成。 怎么取消一个任务的执行呢?这个机本身还上面的线程的取消基本上一样。只是AsyncTask已经提供了足够的属性和方法完成取消的工作。直接调用AsyncTask#cancel方法就可以发出取消的信号,但是是否可以取消还要看这个方法的返回值是什么。如果是true那就是可以,否则任务不可取消(但是不可取消的原因很可能是任务已经执行完了)。 调用cancel方法发出取消信号,并且可以取消的时候。isCancelled()就会返回true。同时onPostExecute这个方法就不会再被调用了。而是onCancelled(object)方法被调用。同样是在doInBackground这个方法执行完之后调用。所以,如果想要在取消任务执行后尽快的调用到onCancelled(object)的话,就需要在onInBackground的时候不断的检查isCancelled()是否返回true。如果返回的是true就跳出方法的执行。 inner class DemoAsyncTask() : AsyncTask<String, Int, String>() { // var isRunning = true override fun doInBackground(vararg params: String?): String? { Log.i(TAG, "##AsyncTask doing something...") var i = 0 val TOTAL = 1000000 var progress = 0.0f while (i < TOTAL && !isCancelled) { Log.d(TAG, "doning jobs $i is cancelled $isCancelled") i++ var currentProgress = i.toFloat() / TOTAL if (currentProgress > progress && Math.abs(currentProgress - progress) > 0.1) { progress = currentProgress publishProgress((progress * 100).toInt()) } } Log.d(TAG, "doning jobs $i is cancelled $isCancelled") return "Task done" } override fun onPostExecute(result: String?) { this@CancalableActivity.mAsyncTextView?.text = result } override fun onProgressUpdate(vararg values: Int?) { mAsyncTextView?.text = "${mAsyncTextView?.text ?: "Async task..."} progress: ${values?.get(0) ?: 0}" } override fun onCancelled() { Log.i(TAG, "##Task cancelled") // isRunning = false this@CancalableActivity.mAsyncTextView?.text = "###Task cancelled" } // override fun onCancelled(result: String?) { // Log.i(TAG, "##Task cancelled") //// isRunning = false // this@CancalableActivity.mAsyncTextView?.text = result ?: "Task cancelled" // } } onCancelled()是API level 3的时候加入的。onCancelled(Result result)是API level 11的时候加入的。这个在兼容低版本的时候需要注意。 但是一点需要格外注意: AsyncTask一定要在UI线程初始化。不过在**JELLY_BEAN**以后这个问题也解决了。 总之,在UI线程初始化你的`AsyncTask`肯定是不会错的。 线程池 下面就来看看线程池的概念。顾名思义,线程池就是放线程的池子。把费时费力,或者影响响应用户操作的代码放在另外一个线程执行时常有的事。但是如果无顾忌的开辟线程,却会适得其反,严重的浪费系统资源。于是就有了线程池。线程池就是通过某些机制让线程不要创建那么多,能复用就复用,实在不行就让任务排队等一等。 这个机制在线程池的构造函数里体现的非常明显: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) corePoolSize线程池里闲着也不回收的线程数量。除非allowCoreThreadTimeOut指定可以回收。 ** maximumPoolSize** 线程池允许的最大线程数。 ** keepAliveTime** 非核心线程(就是如果核心线程数量corePoolSize定义为1的话,第二个就是非核心线程)的超时时间。 unitkeepAliveTime的时间单位,毫秒,秒等。 ** workQueue** 存放execute(Runnable cmd)方法提交的Runnable任务。 ** threadFactory**线程池用来创建新线程用的一个工厂类。 ** handler**线程池达到最大线程数,并且任务队列也已经满的时候会拒绝execute(Runnable cmd)方法提交任务。这个时候调用这个handler。 知道以上基本内容以后,就可以探讨线程池管理线程的机制了。概括起来有三点: 如果线程池的线程数量少于corePoolSize的时候,线程池会使用threadFactory这个线程工厂创建新的线程执行Runnable任务。 如果线程池的线程数量大于corePoolSize的时候,线程池会把Runnable任务存放在队列workQueue中。 线程池的线程数量大于corePoolSize,队列workQueue已满,而且小于maximumPoolSize的时候,线程池会创建新的线程执行Runnable任务。否则,任务被拒。 现在回到AsyncTask。被人广为诟病的AsyncTask是他的任务都是顺序执行的。一个AsyncTask的实例只能处理一个任务。但是在AsyncTask后面处理任务的是一个静态的线程池。在看这个线程池SerialExecutor的execute方法实现: final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { // 执行一个task } } 这个线程池SerialExecutor在处理Runnable的传入参数的时候对这个任务进行了重新包装成了一个新的Runnable对象,并且将这个新的对象存入了一个叫做mTasks的队列。这个新的Runnable对象首先执行传入的任务,之后不管有无异常调用scheduleNext方法执行下一个。于是整体的就生成了一个传入的任务都顺序执行的逻辑。 这个线性执行的静态线程池SerialExecutor的实现非常简单。并不涉及到我们前文所说的那么多复杂的内容。在实现上,这个线程池只实现了线程池的最顶层接口Executor。这个接口只有一个方法就是execute(Runnable r)。另外需要强调一点:mTasks的类型ArrayDeque<T>是一个不受大小限制的队列。可以存放任意多的任务。在线程池的讨论中遇到队列就需要看看容量概念。 SerialExecutor只是进行了简单的队列排列。但是在scheduleNext方法的实现上又会用到一个复杂一些的线程池来执行任务的具体执行。这线程池叫做THREAD_POOL_EXECUTOR。我们来具体看看其实现: private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 这个线程池的实现非常具有现实价值。虽然稍后介绍的系统提供的几种线程池的实现就够用。但是难免遇到一些需要自定义线程池的情况。详细解析如下: CORE_POOL_SIZE线程池的核心线程数量为设备核心数加一。 ** MAXIMUM_POOL_SIZE** 线程池的最大线程数量为核心数的两倍加一。 ** KEEP_ALIVE** 线程池中非核心线程的超时时间为一秒。 ** sPoolWorkQueue ** 线程池存放任务的队列。最大个数为128个。参考上面说的线程池处理机制,会出现任务被拒的情况。排队的线程池SerialExecutor存放任务的队列是可以认为无限长的,但是THREAD_POOL_EXECUTOR的队列最多存放128个任务,加上线程池核心线程的数量,能处理的任务相对有限。出现任务被拒的情况的几率比较大。所以,往AsyncTask里直接添加Runnable对象的时候需要三思。 ** sThreadFactory** 线程池用来创建线程的工厂对象。ThreadFactory是一个只有一个方法Thread newThread(Runnable r);的接口。这里在实现的时候给新创建的线程添加了一个原子计数,并把这个计数作为线程名称传递给了线程的构造函数。 到这里,我们就已经很清楚AsyncTask是如何用一个极其简单的线程池SerialExecutor给任务排队的。又是如何使用一个复杂一些的线程池THREAD_POOL_EXECUTOR来处理具体的任务执行的。尤其是线程池THREAD_POOL_EXECUTOR,在我们实际应用一个自定义的线程池的时候在设定线程池核心线程数量,线程池最大线程数量的时候都依据什么?明显就是设备的CPU核心数。线程分别在不同个CPU核心中做并行的处理。核心数多可以同时处理的线程数就相对较多,相反则会比较少一些。如此设置核心线程数量就会平衡并行处理的任务数量和在处理的过程中耗费的系统资源。 为了让开发者省时省力,系统默认的提供了四种可以适应不同应用条件的线程池: public class Executors { public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } } ** newFixedThreadPool** 顾名思义,线程数量固定的线程池,且其数量等于参数指定值。这一类型的线程池的核心线程数量和最大线程数量是一样的。存放任务的队列的容量可以被认为无限大。一旦线程池创建的线程数量等* nThreads*参数值的时候,新增的任务将会被存放在任务队列中等待核心线程可用的时候执行。 ** newSingleThreadExecutor**newFixedThreadPool的一个特殊情况,当mThreads值为1的时候。 ** newCachedThreadPool** 这一类型的线程池中创建的线程都有60秒的超时时间,由于超时时间比较长等于是线程空闲了以后被缓存了60秒。由于核心线程数量为0,所以创建的线程都是非核心线程。也因此超时时间才管用。任务队列SynchronousQueue非常特殊,简单理解就是一个任务都存放不了。而线程池的最大线程数量又设定为Integer.MAX_VALUE,可以认为是无限大。根据线程池处理任务的机制,可以认为有新任务过来就会创建一个线程去处理这个任务,但是如果存在空闲没有超时的线程会优先使用。 ** newScheduledThreadPool** 生成一个ScheduledThreadPoolExecutor实例。可以通过其提供的接口方法设定延迟一定的时间执行或者隔一定的时间周期执行。 来一个例子: import static java.util.concurrent.TimeUnit.*; class BeeperControl { private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public void beepForAnHour() { final Runnable beeper = new Runnable() { public void run() { System.out.println("beep"); }; final ScheduledFuture beeperHandle = scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS); scheduler.schedule(new Runnable() { public void run() { beeperHandle.cancel(true); } }, 60 * 60, SECONDS); } }} 附录 这里是上面例子中使用的全部代码。 线程的停止: package demo.retrofit2rxjavademo.Activities import android.os.Bundle import android.os.Handler import android.os.Message import android.support.v7.app.AppCompatActivity import android.widget.TextView import demo.retrofit2rxjavademo.R class CancalableActivity : AppCompatActivity() { lateinit var mResultTextView: TextView var mHandler = object : Handler() { override fun handleMessage(msg: Message?) { when (msg?.what) { THREAD_CANCELLED -> { mResultTextView.text = "thread cancelled" } THREAD_FINISHED -> { mResultTextView.text = "thread finished" } else -> { mResultTextView.text = "what's going on" } } } } var mThread: Thread? = null var runnable = object : CancelableRunnable() { override fun run() { if (isCancelled) { var msg = mHandler.obtainMessage(THREAD_CANCELLED) mHandler.sendMessage(msg) return } Thread.sleep(2000) if (isCancelled) { var msg = mHandler.obtainMessage(THREAD_CANCELLED) mHandler.sendMessage(msg) return } var msg = mHandler.obtainMessage(THREAD_FINISHED) mHandler.sendMessage(msg) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_cancalable) mResultTextView = findViewById(R.id.run_result_text_view) as TextView findViewById(R.id.start_button)?.setOnClickListener { v -> runnable.isCancelled = false mThread = Thread(runnable) mThread?.start() mResultTextView.text = "Thread running..." } findViewById(R.id.stop_button)?.setOnClickListener { v -> this.runnable.isCancelled = true } } abstract class CancelableRunnable() : Runnable { var isCancelled: Boolean = false } companion object { val THREAD_FINISHED = 0 val THREAD_CANCELLED = 1 } } 欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处! 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/5506154.html ,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册