-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 282 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 282 KB
1
[{"title":"elastic-job和xxl-job实践对比","date":"2022-02-21T05:59:02.000Z","path":"2022/02/21/elastic-job和xxl-job实践对比/","text":"elastic-job与xxl-job的实践数据对比。从多个维度,数据进行更适合的选择。 简介 技术文档都比较完善,学习成本低。 elastic-job 官网地址:https://shardingsphere.apache.org/elasticjob/current/cn/overview/ E-Job 关注的是数据,增长了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源。可是学习成本相对高些,推荐在“数据量庞大,且部署服务器数量较多”时使用算法 xxl-job 官网地址:https://www.xuxueli.com/xxl-job/ X-Job 侧重的业务实现的简单和管理的方便,学习成本简单,失败策略和路由策略丰富。推荐使用在“用户基数相对少,服务器数量在必定范围内”的情景下使用 对比维度 前提条件均运行在K8S中,并且程序运行内存512M,正常运行12小时。 GC情况 (XXL-job完胜)E-jobS0 S1 E O M CCS YGC YGCT FGC FGCT GCT 43.10 0.00 6.18 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 6.18 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 6.18 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 6.18 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 6.18 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 6.30 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.28043.10 0.00 13.53 35.03 97.84 96.22 11700 177.625 3 0.655 178.280 XXL-job S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 58.86 0.00 49.24 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.24 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.27 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.28 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.36 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.37 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.37 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.37 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 49.37 28.97 95.87 93.36 1190 26.351 3 0.380 26.73158.86 0.00 51.05 28.97 95.87 93.36 1190 26.351 3 0.380 26.731 对比结论 E:年轻代中Eden目前已使用的空间,波动率不一。 E-job 波动幅度大,并且空间使用大 XXL-job 波动幅度平均,并且短时间空间使用低 YGC:年轻代中E-job,gc次数大。 CPU/MEM情况CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSe1b9efeec680 k8s_xxl-jobs_cloud-namespace_9a47d619-5a8b-4529-856b-2cbd972c726d_0 0.00% 844MiB / 15.28GiB 5.39% 0B / 0B 4.56MB / 12.3kB 353ij7j09jiasdq k8s_e-jobs_test-namespace_154s24d2-s1s2-2355-322s-a2121da21saa_0 0.00% 719MiB / 15.28GiB 4.58% 0B / 0B 4.56MB / 12.3kB 353CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSe1b9efeec680 k8s_xxl-jobs_cloud-namespace_9a47d619-5a8b-4529-856b-2cbd972c726d_0 3.00% 844MiB / 15.28GiB 5.39% 0B / 0B 4.56MB / 12.3kB 353ij7j09jiasdq k8s_e-jobs_test-namespace_154s24d2-s1s2-2355-322s-a2121da21saa_0 2.00% 719MiB / 15.28GiB 4.58% 0B / 0B 4.56MB / 12.3kB 353CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSe1b9efeec680 k8s_xxl-jobs_cloud-namespace_9a47d619-5a8b-4529-856b-2cbd972c726d_0 3.20% 844MiB / 15.28GiB 5.39% 0B / 0B 4.56MB / 12.3kB 353ij7j09jiasdq k8s_e-jobs_test-namespace_154s24d2-s1s2-2355-322s-a2121da21saa_0 4.00% 719MiB / 15.28GiB 4.58% 0B / 0B 4.56MB / 12.3kB 353CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSe1b9efeec680 k8s_xxl-jobs_cloud-namespace_9a47d619-5a8b-4529-856b-2cbd972c726d_0 3.20% 844MiB / 15.28GiB 5.39% 0B / 0B 4.56MB / 12.3kB 353ij7j09jiasdq k8s_e-jobs_test-namespace_154s24d2-s1s2-2355-322s-a2121da21saa_0 2.70% 719MiB / 15.28GiB 4.58% 0B / 0B 4.56MB / 12.3kB 353 对比结论E-job稍微逊色一点,但是相差不多,对于上述比较的GC来看,个人觉得GC的可比性更重要。 支持集群部署 (感觉不通的情况使用)XXL-job集群部署唯一要求为:保证每个集群节点配置(db和登陆账号等)保持一致。调度中心通过db配置区分不同集群。执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。集群部署唯一要求为:保证集群中每个执行器的配置项 “xxl.job.admin.addresses/调度中心地址” 保持一致,执行器根据该配置进行执行器自动注册等操作。 E-job重写Quartz基于数据库的分布式功能,改用Zookeeper实现注册中心作业注册中心: 基于Zookeeper和其客户端Curator实现的全局作业注册控制中心。用于注册,控制和协调分布式作业执行。 对比结论 最新版本X-job 基于时间轮,使用数据库实现注册中心 E-Job基于quartz,改用Zookeeper实现注册中心 监控告警,日志追溯 (XXL-job完胜)XXL-job提供了调度器,支持日志查询,动态控制任务触发条件,支持配置邮箱地址进行异常告警。 E-job需监听任务的处理是否成功,作业服务器是否存活,数据是否处理成功,自行编写告警程序。 对比结论 xxl-job对于动态任务执行,日志查询,告警更自动化,用法更建议,对于业务处理来说更方便 E-job这方便稍微欠缺。 大量数据处理,批量处理 (E-job完胜)E-job支持并行调度,采用任务分片实现,可自定义分片策略,并且通过zk实现服务注册,控制,协调,对服务器压力更少 XXL-job支持任务分片,更加执行起集群实现动态分片。因为是通过数据来实现弹性扩容,如果任务服务器多了,会对服务器造成一定的压力 总结综合来说,如果侧重于数据则使用E-job,侧重业务请使用XXL-job","tags":[{"name":"elastic-job","slug":"elastic-job","permalink":"http://www.updatecg.xin/tags/elastic-job/"},{"name":"xxl-job","slug":"xxl-job","permalink":"http://www.updatecg.xin/tags/xxl-job/"}]},{"title":"带你认识K8S","date":"2021-12-27T07:18:00.000Z","path":"2021/12/27/带你认识K8S/","text":"一、了解基本知识官方网站https://kubernetes.io/ 官网中文地址https://kubernetes.io/zh/docs/home/ 二、安装部署自己动手安装部署,先通过命令简单熟悉下,尽量自己建立虚拟机进行安装部署。 官方安装步骤:https://kubernetes.io/zh/docs/tasks/tools/ 总结的安装步骤:https://www.updatecg.xin/2021/08/19/%E9%83%A8%E7%BD%B2%E4%B8%80%E5%A5%97%E5%8D%95Master%E7%9A%84K8s%E9%9B%86%E7%BE%A4/ 三、Kubernetes 核心概念 有了Docker,为什么还用Kubernetes? Kubernetes是什么 ? Kubernetes集群架构与组件 ? Kubernetes基本概念 ?一、有了Docker,为什么还用Kubernetes?企业需求:为提高业务并发和高可用,会使用多台服务器 多容器跨主机提供服务 多容器分布节点部署 多容器怎么升级 高效管理这些容器 二、Kubernetes是什么 ? Kubernetes是Google在2014年开源的一个容器集群管理系统,Kubernetes简称K8S。 Kubernetes用于容器化应用程序的部署,扩展和管理,目标是让部署容器化应用简单高效。 三、Kubernetes集群架构与重要组件 ? 综合理解Master组件 kube-apiserver Kubernetes API,集群的统一入口,各组件协调者,以RESTful API提供接口服务,所有对象资源的增删改查和监听操作都交给 APIServer处理后再提交给Etcd存储。 kube-controller-manager 处理集群中常规后台任务,一个资源对应一个控制器,而 ControllerManager就是负责管理这些控制器的。 kube-scheduler 根据调度算法为新创建的Pod选择一个Node节点,可以任意部署, 可以部署在同一个节点上,也可以部署在不同的节点上。 etcd 分布式键值存储系统。用于保存集群状态数据,比如Pod、Service 等对象信息。 Node组件 kubelet kubelet是Master在Node节点上的Agent,管理本机运行容器的生命周 期,比如创建容器、Pod挂载数据卷、下载secret、获取容器和节点状态 等工作。kubelet将每个Pod转换成一组容器。 kube-proxy 在Node节点上实现Pod网络代理,维护网络规则和四层负载均衡工作。 docker或rocket 容器引擎,运行容器。 官方介绍kubelet简介https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kubelet/ kube-apiserver简介https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-apiserver/ kube-controller-manager简介https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-controller-manager/ kube-proxy简介https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-proxy/ kube-scheduler简介https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-scheduler/ 四、 Kubernetes将弃用Docker!Kubernetes 计划弃用就是kubelet中dockershim。即 Kubernetes kubelet 实现中的组件之一,它通过CRI(gRPC server服务)与 Docker Engine 进行通信。 为什么这么做! Docker内部调用链比较复杂,多层封装和调用,导致性能降低、提升故障率、不易排查 Docker还会在宿主机创建网络规则、存储卷,也带来了安全隐患 说白了就是docker与kubernetes两家没有谈好合作 如何应对?利用containerd替换docker。docker由 docker-client ,dockerd,containerd,docker-shim,runc组成,所以containerd是docker的基础组件之一。下面是从containerd引过来的一张图从k8s的角度看,可以选择 containerd 或 docker 作为运行时组件:Containerd 调用链更短,组件更少,更稳定,占用节点资源更少,调用链:Docker 作为 k8s 容器运行时,调用关系如下:kubelet –> docker shim (在 kubelet 进程中) –> dockerd –> containerdContainerd 作为 k8s 容器运行时,调用关系如下:kubelet –> cri plugin(在 containerd 进程中) –> containerd 五、K8s CNI网络模型两台Docker主机如何实现容器互通?K8s是一个扁平化网络。 即所有部署的网络组件都必须满足如下要求: 一个Pod一个IP 所有的 Pod 可以与任何其他 Pod 直接通信 所有节点可以与所有 Pod 直接通信 Pod 内部获取到的 IP 地址与其他 Pod 或节点与其通信时的 IP 地址是同一个 主流网络组件有:Flannel、Calico 六、kubeconfig配置文件apiVersion: apps/v1kind: Deploymentmetadata: annotations: deployment.kubernetes.io/revision: \"3\" field.cattle.io/creatorId: user-m7jrz field.cattle.io/publicEndpoints: '[{\"addresses\":[\"127.0.0.1\"],\"port\":31006,\"protocol\":\"TCP\",\"serviceName\":\"test-namespace:monitor-server-nodeport\",\"allNodes\":true},{\"nodeName\":\"c-9fjs4:m-f8701cf4bd4f\",\"addresses\":[\"127.0.0.1\"],\"port\":9095,\"protocol\":\"TCP\",\"podName\":\"test-namespace:monitor-server-6474776d76-4xxqm\",\"allNodes\":false}]' creationTimestamp: \"2021-11-24T07:34:50Z\" generation: 9 labels: cattle.io/creator: norman workload.user.cattle.io/workloadselector: deployment-test-namespace-monitor-server name: monitor-server namespace: test-namespace resourceVersion: \"16407952\" selfLink: /apis/apps/v1/namespaces/test-namespace/deployments/monitor-server uid: 11f6d174-9286-4eb8-855b-ec6039eccb85spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: workload.user.cattle.io/workloadselector: deployment-test-namespace-monitor-server strategy: type: Recreate template: metadata: annotations: cattle.io/timestamp: \"2021-12-14T10:20:06Z\" field.cattle.io/ports: '[[{\"containerPort\":9095,\"dnsName\":\"monitor-server-nodeport\",\"hostPort\":0,\"kind\":\"NodePort\",\"name\":\"monitor-server\",\"protocol\":\"TCP\",\"sourcePort\":31006}]]' workload.cattle.io/state: '{\"azhzLW1hc3Rlci0xNzIuMzEuMjAuMTcy\":\"c-9fjs4:m-f8701cf4bd4f\"}' creationTimestamp: null labels: workload.user.cattle.io/workloadselector: deployment-test-namespace-monitor-server spec: containers: - image: 127.0.0.1:18089/whatever_pay/test/monitor:11 imagePullPolicy: Always name: monitor-server ports: - containerPort: 9095 hostPort: 9095 name: monitor-server protocol: TCP resources: {} securityContext: allowPrivilegeEscalation: false privileged: false readOnlyRootFilesystem: false runAsNonRoot: false stdin: true terminationMessagePath: /dev/termination-log terminationMessagePolicy: File tty: true volumeMounts: - mountPath: /logs name: vol1 dnsConfig: nameservers: - 8.8.8.8 - 144.144.144.144 dnsPolicy: ClusterFirst hostAliases: - hostnames: - k8s-master ip: 127.0.0.1 hostNetwork: true nodeName: k8s-master-127.0.0.1 restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 volumes: - hostPath: path: /datalogs/monitor-server type: \"\" name: vol1status: availableReplicas: 1 conditions: - lastTransitionTime: \"2021-12-14T02:20:47Z\" lastUpdateTime: \"2021-12-14T02:20:47Z\" message: Deployment has minimum availability. reason: MinimumReplicasAvailable status: \"True\" type: Available - lastTransitionTime: \"2021-11-24T07:34:50Z\" lastUpdateTime: \"2021-12-14T02:20:47Z\" message: ReplicaSet \"monitor-server-6474776d76\" has successfully progressed. reason: NewReplicaSetAvailable status: \"True\" type: Progressing observedGeneration: 9 readyReplicas: 1 replicas: 1 updatedReplicas: 1 七、基本资源概念Pod:K8s最小部署单元,一组容器的集合Deployment:最常见的控制器,用于更高级别部署和管理PodService:为一组Pod提供负载均衡,对外提供统一访问入口Label :标签,附加到某个资源上,用于关联对象、查询和筛选Namespaces :命名空间,将对象逻辑上隔离,也利于权限控制","tags":[{"name":"devOps","slug":"devOps","permalink":"http://www.updatecg.xin/tags/devOps/"},{"name":"K8S","slug":"K8S","permalink":"http://www.updatecg.xin/tags/K8S/"}]},{"title":"虚拟币-ETH(Ethereum)","date":"2021-09-01T07:18:00.000Z","path":"2021/09/01/虚拟币-ETH/","text":"以太坊简介 以太坊是一个为去中心化应用(DApp)而生的全球开源平台。 官方地址: https://ethereum.org/zh 源码地址: https://github.com/ethereum/ 以太坊主网于 2015 年上线,是世界领先的可编程区块链。和其它区块链一样,以太坊也拥有原生加密货币,叫作 Ether (ETH)。 ETH 是一种数字货币, 和比特币有许多相同的功能。 以太坊历史 年份 历史名 区块高度 价格 概述 改进提议EIP 2013年11月27日 白皮书发布 0 $0 该项目在 2015 年启动。但早在 2013 年,以太坊的创始人 VitalikButerin维塔利克·巴特林(V神) 就发表了介绍性[文章]。 2014年4月1日 黄皮书发布 0 $0 以太坊白皮书概要性地介绍了以太坊,以太坊黄皮书通过大量的定义和公式详细地描述了以太坊的技术实现的[文章]。 2014年7月22日 - 9月2日 公开招募 0 $0 以太币预售为期 42 天。 你可以使用比特币进行购买。[请阅读以太坊基金会公告] 2015-7月-30日 Frontier 0 $0 边境是以太坊的最初版本,但在上面能做的事情很少。 它是在 Olympic 测试阶段之后进行的。 它面向技术用户,特别是开发者。 每个区块有一个 gas 限制为 5,000。 这个“缓冲”期使矿工能够开始工作,并使早期采用者能够安装他们的客户端而不必“匆忙”。 2015-9月-7日 Frontier thawing 200000 $1.24 USD Frontier thawing 分叉取消了每个区块 5,000 gas 的限制,并将默认的 gas 价格设置为 51 gwei。 这开启了交易功能——交易需要 21,000 gas。 2016-3月-14日 Homestead 1150000 $12.50 USD 展望未来的 Homestead 分叉。 其中包括若干协议修改和网络变更,使以太坊能够进一步进行网络升级。 EIP-2\\EIP-7\\EIP-8 [EIP详情] 2016-7月-20日 DAO 分叉 1920000 $12.54 USD DAO 分叉是为了解决 2016 DAO 攻击 ,当时一个不安全 DAO 合约被黑客盗走了超过 3 百万个 ETH。 这个分叉将资金从错误的合约转移到 这个只有 withDraw 方法的新合约。 任何损失资金的人都可以在他们的钱包中为每 100 个 DAO 代币提取 1 个 ETH。 2016-10月-18日 Tangerine Whistle 2463000 $12.50 USD 处理与价格低廉的操作代码有关的紧急网络健康问题 EIP-150\\EIP-158 2016-11月-22日 Spurious Dragon 2675000 $9.84 USD 1、调整操作码的价格以防止今后对网络的攻击。 2、启用区块链状态的“区块链减重”。 3、添加重放攻击保护。 EIP-155\\EIP-160\\EIP-161\\EIP-170 2017-10月-16日 Byzantium 4370000 $334.23 USD 拜占庭分叉: 1、将区块挖矿奖励从 5 ETH 减少到 3 ETH 2、将[难度炸弹]升级延迟一年 3、增加了调用其他合约的能力 4、添加加密算法以允许[第二层扩容] EIP-140\\EIP-658\\EIP-196\\EIP-197\\EIP-198\\EIP-211\\EIP-214\\EIP-100\\EIP-649 2018-2月-28日 君士坦丁堡升级 7280000 $136.29 USD 君士坦丁堡分叉: 1、保证在 POS 实现前区块不会被冻结 2、优化 EVM 数据存储操作的 Gas 耗用量计量方式 3、添加了与尚未创建的地址进行交互的能力 EIP-145\\EIP-1014\\EIP-1052\\EIP-1234 2019-9月-8日 伊斯坦布尔升级 9069000 $151.06 USD 伊斯坦布尔分叉: 1、优化 EVM 数据存储操作的 gas 耗用量计量方式。 2、提高拒绝服务(DoS)攻击的弹性 3、使基于 SNARK 和 STARK 的 二层扩容第二层方案性能更佳 4、使以太坊和 Zcash 能够互操作 5、让合约能够引入更有创造性的功能 EIP-152\\EIP-1108\\EIP-1344\\EIP-1884\\EIP-2028\\EIP-2200 2020-1月-2日 缪尔冰川升级 9200000 $127.18 USD 缪尔冰川分叉将 难度炸弹 的启动延迟。 增加Pow 的区块难度可能会增加发送交易和使用数据库的等待时间,从而降低以太坊的可用性。 EIP-2384 2020-10月-14日 质押合约部署 11052984 $379.04 USD [质押合约]将质押引入以太坊生态系统。 虽然这只是一个 ETH1.0 主网 合约,但它直接影响了启动 信标链重要的 Eth2 升级。 2020-12月-1日 信标链的起源 1 (信标链区块高度) $586.23 USD [信标链]需要 16384 个 ETH 并且每个节点拥有 32 个 ETH 来保证网络的安全。 2020 年 11 月 27 日确定规则,并且在 2020 年 12 月 1 日开始生产区块。 这是实现 Eth2.0 愿景的重要一步。 2021-8月-5日 伦敦升级 1296500 $3292.71 USD 「伦敦」升级顺利完成,作为以太坊2.0全面部署前的最后一次全网升级,「伦敦」重要性不言而喻,尤其是以太坊改进提案EIP-1559的激活,不仅消除了用户通过投标系统竞争区块包含的方法,更会从根本上改变以太坊交易机制和ETH供应量。 [EIP详情地址] 总结:2014年实行预售,可以从2016年到2017年价格上涨,然后2020年到2021年价格上涨。每次升级都会影响价格的波动。2020年12月上线的信标链,只是以太坊2.0“阶段 0”迈出的第一步,「伦敦」升级之后将会进入到「阶段 1」(最初预计是2021年完成,但目前几乎可以肯定会延迟到2022年),而 ETH 1.0ETH 2.0 的完整升级则可能需要5-10年。个人觉得可以买币,放个几年。 以太坊智能合约 以太坊的智能合约并非现实中常见的合同,而是存在区块链上,可以被触发执行的一段程序代码,这些代码实现了某种预定的规则,是存在于以太坊执行环境中的“自治代理” 智能合约是什么以太坊的智能合约设计很简明。 任何人都可以在以太坊区块链上开发智能合约,这些智能合约的代码是存在于以太坊的账户中的,这类存有代码的账户叫合约账户。对应地,由密钥控制的账户可称为外部账户。 以太坊的智能合约程序,是在以太坊虚拟机(Ethereum Virtual Machine,EVM)上运行的。 合约账户不能自己启动运行自己的智能合约。要运行一个智能合约,需要由外部账户对合约账户发起交易,从而启动其中的代码的执行。 智能合约的代码永远不能被修改。 在以太坊,智能合约是可以处理资金的脚本,就是这么简单。 这些合约由我们称为“矿工”的参与方强制执行和证明。我们给这些矿工支付一种叫作 “Gas” 的东西,它是运行一份合约的成本。当你发布一份智能合约,或者执行一份智能合约,或者把钱转到另一个账户,你就要支付一些被转换成Gas的以太币。 智能合约有什么用图2 是一个简明的图示,图示是一个典型的 ERC20 通证发行过程:一个项目通过智能合约创建通证,这个通证是实体资产或线上资产的价值表示物。投资者(用户)发起交易,向智能合约转入以太币(ETH),智能合约自动运转,在满足一定规则后,它向投资者账户转入相应数量的通证。 为什么我们需要合约?在一个没有合约或货币的世界里,我们只能局限于同步的易货交易——你有一个苹果,我有一条面包,我可以当场用面包换你的苹果。 然而,我们交易的越多,就越有可能陷入经济学家所谓的需求的双重偶合(double coincidence of wants)问题:我们之间达成交易的前提必须是我们同时想要对方手里的东西,而这种情况不常出现。 货币是解决这个问题的一种方法。 我可以卖了面包换到钱,然后用钱换苹果或其他物品。通过钱,我们可以把双重需求减少成单一需求:我无需持有你想交易的东西;我可以通过后续交易获得苹果。这样时间限制就被削弱了。 合约的工作原理与货币类似,但它们能促进更多潜在的交易。 合约不要求同步交换价值,甚至不需要以货币作为中介。 我现在可以把一条面包卖出去,买方可以答应下个月付款给我。 这种能力从根本上扩展了我们可以进行的交易类型。 但仍有一个问题。 你怎么确定我会履行承诺付款给你? 我们如何建立可信的约定? 履行合约约定在点对点交易过程中,无论谁先履行合约都要承担对方违背合约的风险。 如果卖方先发货,买方可能不会付款;如果买方先付款,卖方可能不会发货。 怎么来解决这个问题? 解决方案是使用多重签名的智能合约,在针对在线交易的多重签名合约中,可以要求至少获得三方(买方,卖方和中立的第三方)中任意两方的签名。 第三方可以是任何人(或任何事物!),只要能让我们相信 ta 能公正地解决争议即可。 特别要注意的是,第三方仲裁员的权力非常有限,ta 们只能在买卖双方出现争议的情况下,决定将钱汇给其中一方。 仲裁员不能私吞这笔钱或是将它汇给买卖双方以外的其他人。 合约的风险事件1-Parity钱包在2016年6月,一名黑客企图转移一大笔众筹资金 (350 万个 ETH, 占当时ETH总数的15%)至他自己的子合约,这笔资金被锁定在该子合约中 28 天。 众多创业公司使用的多重签名钱包的逻辑大多通过库合约实现。每个钱包都包含一个轻量级的客户端合约,连接到这个单点故障。漏洞是一种叫做wallet.sol的多重签名合约出现bug导致的,问题在于其中一个初始化函数只能被调用一次。 预防措施 使用开放的资源与社区接受的库合约的实质标准 (de facto standards),例如 Open Zeppelin’s contracts。 使用推荐的模式与最优操作指导手册,例如 Consensys 提供的。 考虑由信誉好的供应商审核您的智能合约。 以太坊地址以太坊地址以太坊地址生成过程 可以借鉴此[文章]1、生成256位随机数作为私钥2、将私钥转化为 secp256k1 非压缩格式的公钥,即 512 位的公钥。3、使用散列算法 Keccak256 计算公钥的哈希值,转化为十六进制字符串。4、取十六进制字符串的后 40 个字母,开头加上 0x 作为地址。 以太坊地址生成实例 私钥:1f2b77*6a7efaa065d20 公钥:04dfa1*0bbf8865734252953c9884af787b2cadd45f92dff2b81e21cfdf98873e492e5fdc07e9eb67ca74d 地址:0xabcd**4A2d57 以太坊合约地址以太坊合约地址生成过程 可借鉴此[文章] 利用发送代币生成合约地址 利用Remix - Solidity IDE 网站来发布智能合约 [地址] 发币过程第一步:在 Chrome 插件商店搜索并安装 MetaMask MetaMask是钱包的一种,在chrome浏览器中,安装MetaMask插件即可,安装完成后,右上角会出现一个“狐狸头”的标志,点击该标志,打开钱包,第一步,创建账户,(创建账户只需要输入面密码即可,名称创建后可以随便改,该账户就是一个hash值,如何给自己创建的账户冲以太币呢,你可以通过在交易所买入一些ETH,然后转入即可)创建成功后,记住密码还有产生的几个随机单词(一定要记录下来)。 第二步:编写合约代码 注意事项: 合约代码引用版本需要与编辑器所选版本匹配 pragma solidity ^0.4.16; 精度默认值decimals=18,就是小数点后18位。BTC是8位,0.00000001 (Gwei) = 1(sat)ERC20是18位,0.000000002578400219 (Ether) = 2.578400219 (Gwei) 第三步:注入环境、绑定合约、部署发币 注意事项: 以太币数量填写0,不然要报错 第四步:等待发币结果 MetaMask交易结果 链上交易详情[链上地址] 注意:对于EIP-1559升级后,文章后面会详细讲解矿工费机制。 以太坊参数(难度炸弹、出块速度、TPS、矿工费、Nonce)难度炸弹 指的是随着挖矿难度增加.[以太坊浏览器] 为什么要引入难度炸弹?如图可以看出从20年中旬开始,难度指数上涨明显,这是因为ETH升级2.0造成的。 因为他们最终将希望矿工停止挖矿并开始验证工作。 以太坊(ETH)正试图通过将现有的工作量证明(PoW)共识算法切换为权益证明(PoS)共识算法来解决这个问题。挖矿的奖励将由参与者质押资金的大小决定,而非由现在的电力和资源密集型计算机算力决定。如果出现算法转变的情况,以太坊社区可能会选择通过硬分叉来完全移除或者延迟难度炸弹。 出块速度 [参考地址] POW模式(接下来会讲)下ETH的出块速度如图蓝色线是出块耗时时间,红色线是币价,黄色线是平均线(可以看出平均耗时在13.45秒) 出块速度怎么计算的? 如图中蓝色线,以太坊的发行每年产量被限制在7200万以太币的25%(每年以太币的矿产量,不高于1800万。7200万为一次性crowdsale而发行的以太坊) 按照这个总量来计算 区块奖励: 每挖一个区块奖励5个以太坊 叔块奖励: 有些区块被挖得稍晚一些,因此不能称为主区块链的组成部分。比特币称这类区块为“孤块”,并且完全舍弃它们。但是,以太币称它们为“ uncles”,并且在之后的区块中,可以引用它们。如果uncles在之后的区块链中作为叔块被引用,每个叔块会为挖矿者产出大约4.375个以太坊。目前每天有大约500个叔块被创建,每年产量为70万以太坊 叔块引用奖励: 矿工每引用一个叔块,就得到了大约0.15个以太币(最多引用两个叔块) 出块速度计算 一年3150万秒(365x24x60x60),每产生一个新区快就会奖励5个以太坊 出块速度 : 3150万 / ( (1800万-70万) / 5) ≈ 9 s 注: 这里的计算,忽略了叔块引用奖励,真实出块速度可能大于9s,目前真实平均在13.45秒。 TPS [eth的tps数据] 以最高的日期算: 2021年5月9日星期日的最高交易数的tps是1716600/24/60/60 ≈ 19.868055笔 目前平均: 1250000/24/60/60 ≈ 14.4675笔 TPS困扰区块链久矣 很多区块链项目都面临着TPS性能不足的难题,区块链技术的鼻祖自然也不避免。一直以来比特币每个区块容量设置为1MB,平均每10分钟出一次块,在理想情况下TPS可以达到每秒7笔,但实际情况下只能达到每秒3到4笔。而且随着全网交易量走高,已经出现了拥堵的情况,很多交易无法上链,用户体验不好。如今比特币网络中未确认的交易数已经达到22000笔,将近60MB大小,也就是说平均要等待一小时的时间才可以确认。 矿工费计算 伦敦升级(EIP-1559)升级后矿工费机制变更。 升级前的矿工费机制 矿工费 = GasPrice * GasUsed GasUsed(gas):交易消耗的总 gas 数量。 GasPrice(gwei):即对单位 gas 的定价,1 gwei= 10^(-9) eth。 采用竞价机制,GasPrice 设置越高,交易处理速度越快。 交易由矿工处理,矿工费完全由矿工收取。 GasPrice越多速度越快,web3j默认取得Average值 升级后的费用机制 交易费用 = (baseFee + PriorityFee)* GasUsed 思考? 这里为什么叫交易费用,而非矿工费? 对比升级前后的公式,可以看出 EIP-1559 是将 GasPrice 拆分成了两个费率的组合:baseFee 和 PriorityFee。 名词解析baseFee(基础费用) baseFee 会根据上一区块的空间利用率自动调整,如果利用率超过 50%,则提升当前区块的 baseFee;反之降低。 按照 baseFee 计算公式,相邻区块间的 baseFee 变化幅度在 ±12.5% 之间: 如果上一区块空间利用率为 100%,则当前区块 baseFee 将自动提升 12.5% 如果上一区块空间利用率为 0%,则当前区块 baseFee 将自动降低 12.5% 不同于原来的矿工费机制,EIP-1559 升级后,交易费用不完全由矿工收取,其中 baseFee 将被完全销毁。 PriorityFee(小费) PriorityFee 表示给矿工的小费,延续了竞价设计。如果希望自己的交易在区块中被尽快打包,可通过设置 PriorityFee 激励矿工,矿工将优先处理 PriorityFee 高的交易。 同时,用户还可以自行设置 PriorityFee 的最高值,即付给矿工小费的上限,也叫 maxPriorityFee。 maxFee(最高费用) maxFee 表示用户愿意对某笔交易可支付的最高交易费用。对应到公式中,maxFee = baseFee + maxPriorityFee,其中 maxFee 和 maxPriorityFee 都支持用户自行设置,baseFee 则由算法自动给出。 注:升级后「矿工费」的说法已经不合适了,因为费用中的 baseFee 是要销毁的,只有 PriorityFee 由矿工收取。或许 EIP-1559 升级后,我们应该在以太坊生态中弃用「矿工费」的说法了。 EIP-1559 交易费用计算实例 升级前 矿工费 = GasPrice * GasUsed 升级后 交易费用 = (baseFee + PriorityFee)* GasUsedmaxFee >= baseFee + maxPriorityFee maxFee maxPriorityFee baseFee 销毁 PriorityFee(矿工小费) 50 10 1 1 10 50 10 40 40 10 50 10 45 45 50-45=5 50 10 50 50 50-50=0 50 10 60 由于 maxFee 小于 baseFee,这笔交易无法进入当前区块,将继续排队 注:如果交易失败,交易费用会退款给源地址,但是其中的PriorityFee矿工小费不会退还。 Nonce Nonce决定了交易顺序,以上述链上交易详情[链上地址]为例。 排除非本账号发送方交易 可以看出同交易地址每交易一笔nonce则增加1,第一笔从O开始 以太坊共识机制 共识机制(也称为共识协议或共识算法)允许分布式系统(计算机网络)协同工作并保持安全。 共识机制常见类型工作量证明(POW) 以太坊目前采用工作量证明 (PoW) 共识协议。这种机制允许以太坊网络的节点就以太坊区块链上记录的所有信息的状态达成共识,并防止某些产生经济影响的攻击。 什么是工作量证明 (POW)?工作量证明是一种允许去中心化的以太坊网络达成共识或者一致认可账号余额和交易顺序的机制,这个机制防止用户“双花”他们的货币。 名词解析:“双花”问题是指一笔数字现金在交易中被反复使用的现象。 以太坊的工作量证明机制是如何运作的?以太坊的交易被处理为区块。 每个区块有一个: 区块的难度,例如:3,324,092,183,262,715 混合哈希(mixHash),例如:0x44bca881b07a6a09f83b130798072441705d9a665c5ac8bdf2f39a3cdf3bee29 nonce–例如:0xd3ee432b4fb3d26b 区块的数据直接和 Pow 关联。 工作量证明机制 要求矿工经过激烈的试错竞赛,找到一个区块的 nonce。 只有具备有效 nonce 的区块才能被加入区块链中。 当通过比赛创建一个区块时,矿工将反复放置一个数据集,只能通过数学函数从下载和运行完整链(像矿工那样)中获得数据集。 这是为了根据区块所声明的难度,生成一个低于目标 nonce 的混合哈希(mixHash)。 做到这些最好的方式是试错。 难度决定了这个哈希(mixHash)的指标。 目标越小,有效的哈希值的集合就越小。 一旦哈希值生成,对于其他矿工和客户来说,是很容易校验哈希值是否有效的。 终局性由于矿工是分散的,两个有效的区块是可以同时开采的。 这就造成一个临时的分叉。 最后只要两条中一条支链通过先挖出下一个区块成为最长的支链,这条支链就会被添加到主链上。 但为了使情况更复杂, 临时分叉上被拒绝的交易将会被记录在公认的主链上。 这意味着区块是可逆的。 因此,终局性是需要你等待一段时间至交易不可逆。 以太坊推荐的时间是等待 6 个区块或者超过 1 分钟。 这样你才能确定这笔交易已经成功。 POW总结简单来说,pow就是“按劳分配“一个矿工付出多少,提供多少工作证明就是能拿到多少报酬,这也是千百年来社会分配最公平的方式,自己购买显卡显然是有收益的,按照自己工作量获取报酬,多少矿机工作记录可以提供证明,就可以获取相应的报酬。 权益证明机制(POS) Eth升级到2.0,正在将其共识机制从工作量证明(POW)转变为权益证明(POS) 什么是权益证明?这将要求用户抵押他们的以太币从而成为网络中合法的验证者。 验证者有着与矿工在 工作量证明(pow)中相同的职责:将交易排序和创建新的区块,以便让所有的节点就网路状态达成一致。 权益证明相较于工作量证明系统有许多改进: 提高能效——您不需要大量能源去挖掘区块 门槛降低,硬件要求减少——您不需要优秀的硬件从而获得建立新区块的机会 更强的去中心化——权益证明可以在网络中提供更多的节点。 更有力的支持分片链——一个得以扩展以太坊网络的关键升级 权益证明、权益质押和验证者权益证明是一种用于激励验证者接受更多质押的基本机制。 就以太币而言,用户需要质押 32ETH 来获得作为验证者的资格。 验证者被随机选择去创建区块,并且负责检查和确认那些不是由他们创造的区块。 一个用户的权益也被用于激励良好的验证者行为的一种方式。 例如,用户可能会因为离线(验证失败)而损失一部分权益, 或因故意勾结而损失他们的全部权益。 以太坊权益证明是如何运作的?与工作量证明不同的是,验证者不需要使用大量的计算能力,因为它们是随机选择的,相互间没有竞争。 他们不需要开采区块,他们只需要在被选中的时候创建区块并且在没有被选中的时候验证他人提交的区块。 此验证被称为证明。 你可以认为证明是说“这个块在我看来没问题”。 验证者因提出新区块和证明他们已经看到的区块而获得奖励。 如果你为恶意区块提供证明,你就会失去你的股权。 信标链当以太坊用权益证明取代工作量证明时, [分片链] 的复杂性会增加。这是需要验证者来处理交易和创建新区块的独立区块链。 计划中将有 64 个分片链,并且它们都需要对网络的当下状态有一个共同的理解。 所以这需要额外的协调工作,这将由信标链来完成。 终局性可以让验证者在某些检查点就一个区块的状态达成协议。 只要 2/3 的验证者同意,该区块就会被最终确定。 验证者如果试图稍后通过 51% 的攻击恢复,就会失去他们的全部权益。 POS与POW对比总结 [借鉴对话] 现目前仍然是POW,还没有升级完,预计明年初,或者年末,应该有兼容版本行为。 POW 【人工】检查每个人的余额和交易是否检查出来,所以没有人会花费他们没有的硬币。这需要非常低的计算能力。| 【机器】矿工通过购买更多和最好的采矿设备来做到这一点。该硬件不会为网络做有用的计算工作,它只是为块奖励奖品抽奖。PoW 矿工本质上是在硬件设备上投入资金以获得更多回报。 如果矿工恶意行事,他们将失去区块奖励和运行钻机的运营成本,其中大部分是电费。恶意玩家损失的越多,区块链就越安全。所以钻机的运营成本和区块奖励必须尽可能高。这就是 PoW 在电力资源上如此浪费的原因。 POS 摆脱了这种“硬件设备”中介。你直接下注你的钱,你的投票权与你下注的数量成正比。随机选择一个人提出一个区块,其他人必须签署该区块是正确的。 PoS 运营成本非常低。相反,如果你恶意行事,除了失去奖励(正强化),你的赌注也会被烧毁(负强化)。 考虑到股权现在以网络货币计价,它可以更好地将激励措施与网络中的参与者保持一致。 由于你的股权表现不佳,如果你再次尝试不诚实,你的下一次攻击就不那么有效了,因为你的股权现在降低了,你的投票权也降低了。 由于不良行为者现在具有负强化,参与者不再那么狡猾,并且可以降低网络正强化以保持相同的安全水平。因此,网络可以减少块奖励。 由于运营成本低,更多的人可以参与网络共识。您无需靠近矿机分销渠道或获得较低的电力成本。网络以这种方式更加去中心化 测试网络 BTC只有一个测试网络,为什么ETH会有多个,让我们一起来探究吧! 以太坊可以搭建私有的测试网络,不过由于以太坊是一个去中心化的平台,需要较多节点共同运作才能得到 理想的测试效果,因此并不推荐自行搭建测试网络。 以小狐狸为例 测试网 共识机制 出块间隔(s) 提供方 上线时间 备注 状态 Morden PoW 以太坊官方 2015.7 因难度炸弹退出 stopped Ropsten PoW 30 以太坊官方 2016.11 接替Morden running Kovan PoA 4 以太坊钱包Parity开发团队 2017.3 不支持geth running Rinkeby PoA 15 以太坊官方 2017.4 最常用,只支持geth running Goerli PoA 15 以太坊柏林社区 2018.9 首个以太坊2.0实验场 running 上图可以看出以太坊有一个主网络,四个测试网络。 Morden(已退役) Morden是以太坊官方提供的测试网络,自2015年7月开始运行。到2016年11月时,由于难度炸弹已经严重影响出块速度,不得不退役,重新开启一条新的区块链。Morden的共识机制为PoW。 Ropsten网络[区块链浏览器] 现目前项目采用的web3j-api,采用的Ropsten网络,也是最接近主网络的。 Ropsten也是以太坊官方提供的测试网络,是为了解决Morden难度炸弹问题而重新启动的一条区块链,目前仍在运行,共识机制为PoW。测试网络上的以太币并无实际价值,因此Ropsten的挖矿难度很低,目前在755M左右,仅仅只有主网络的0.07%。这样低的难度一方面使一台普通笔记本电脑的CPU也可以挖出区块,获得测试网络上的以太币,方便开发人员测试软件,但是却不能阻止攻击。 PoW共识机制要求有足够强大的算力保证没有人可以随意生成区块,这种共识机制只有在具有实际价值的主网络中才会有效。测试网络上的以太币没有价值,也就不会有强大的算力投入来维护测试网络的安全,这就导致了测试网络的挖矿难度很低,即使几块普通的显卡,也足以进行一次51%攻击,或者用垃圾交易阻塞区块链,攻击的成本及其低廉。 2017年2月,Ropsten便遭到了一次利用测试网络的低难度进行的攻击,攻击者发送了千万级的垃圾交易,并逐渐把区块Gas上限从正常的4,700,000提高到了90,000,000,000,在一段时间内,影响了测试网络的运行。攻击者发动这些攻击,并不能获得利益,仅仅是为了测试、炫耀、或者单纯觉得好玩儿。 Kovan网络[区块链浏览器] 为了解决测试网络中PoW共识机制的问题,以太坊钱包Parity的开发团队发起了一个新的测试网络Kovan。Kovan使用了权威证明(Proof-of-Authority)的共识机制,简称PoA。 PoW是用工作量来获得生成区块的权利,必须完成一定次数的计算后,发现一个满足条件的谜题答案,才能够生成有效的区块。 PoA是由若干个权威节点来生成区块,其他节点无权生成,这样也就不再需要挖矿。 由于测试网络上的以太币无价值,权威节点仅仅是用来防止区块被随意生成,造成测试网络拥堵,完全是义务劳动,不存在作恶的动机,因此这种机制在测试网络上是可行的。 Kovan与主网络使用不同的共识机制,影响的仅仅是谁有权来生成区块,以及验证区块是否有效的方式,权威节点可以根据开发人员的申请生成以太币,并不影响开发者测试智能合约和其他功能。 https://app.mycrypto.com/faucet 使用时,注意体检切换你的metamask钱包网络。 它支持多个网络水龙头领币。 Rinkeby测试网[区块链浏览器] Rinkeby也是以太坊官方提供的测试网络,使用PoA共识机制。与Kovan不同,以太坊团队提供了Rinkeby的PoA共识机制说明文档,理论上任何以太坊钱包都可以根据这个说明文档,支持Rinkeby测试网络,目前Rinkeby已经开始运行。 目前开发人员最常用的测试网络是Rinkeby Goerli测试网[区块链浏览器] 以太坊 2.0 是一个 PoS 网络,由质押代币的验证节点来生产区块并维持网络运行。因此,首先要解决的问题是如何将代币分配给验证节点以运行网络。 水龙头地址 Ropsten网络[区块链浏览器] 水龙头(需翻墙,亲测可用 5ETH):https://faucet.dimensions.network/水龙头(需翻墙,亲测可用 1ETH):https://app.mycrypto.com/faucet水龙头:https://faucet.ropsten.be/ Kovan网络[区块链浏览器] 水龙头(需翻墙,亲测可用 1ETH):https://app.mycrypto.com/faucet Rinkeby测试网[区块链浏览器] 水龙头: https://faucet.rinkeby.io/ ETH节点客户端 [参考地址] 客户端 名称 地址 语言 支持系统 磁盘容量(快速同步) 磁盘容量(完整归档) 备注 geth https://github.com/ethereum/go-ethereum Go windows、linux和OSX 400GB+ 4.7TB+ 最广泛的以太坊客户端 Open Ethereum https://github.com/openethereum/openethereum/ Rust windows、linux和OSX 280GB 4.6TB+ 最小内存和存储空间占用 nethermind https://github.com/NethermindEth/nethermind C#、.NET Linux 、Mac 、 Windows 200GB+ 3TB+ 性能强劲的虚拟机 Parity https://github.com/paritytech/parity C++ Windows 、Linux、Mac 前面出问题的客户端 节点分布 世界分布 中国分布 API [参考文章] 以太坊官网开放了前段、后端相关的技术API文档。每个以太坊客户端都将履行 JSON-RPC 规范,因此我们有一个统一的端点组可供应用程序们依赖。 以whateverPay使用api举例,采用了web3j库。[参考文章] 支持Java/Android/Kotlin/Scala语言。 [接口详情文档] 代币协议 在区块链上,数字加密货币分为原生币和代币两大类。代币之中又可分为同质化和非同质化两种 原生币正如大家熟悉的比特币(BTC)、以太坊(ETH)等,拥有自己的主链,使用链上的交易来维护账本数据,交易手续费也是以原生币结算的。 代币则是依附于现有的区块链,使用智能合约来进行账本的记录,如项目方依附于以太坊发布的token。最多的是以太坊(Ethereum)上的ERC20代币,部署一个ERC20的智能合约就可以发布一个token。 同质化代币即FT(Fungible Token),互相可以替代、可接近无限拆分的token。例如,你手里有一个ETH与我手里的一个ETH,可以拆分,1 ETH = 1 * 10^18 Wei,本质上没有任何区别,这就是同质化代币。 非同质化 正如最近比较火:科比花55个以太坊(约18万美元,合人民币116万元)买下了一个猿猴头像。 孙宇晨韭菜王以1.2亿枚TRX购买NFT,价值约合1050万美元。有钱人的世界想象不到! 同质化代币,即NFT(Non-Fungible Token)属于ERC721协议,则是唯一的、不可拆分的token,如加密猫、艺术家的油画等。NFT都是独一无二、不可分割的。这意味着当一件作品被铸成NFT之后,这个作品就成为了区块链上独一无二的数字资产。 代币如何交易?交易所的杠杆代币为ERC20代币,可直接在交易所的现货市场用USD进行交易。如果你有USDT的话,需要先在现货市场中使用USDT买入USD,然后再进行杠杆代币交易。需要注意的是,不管你在哪个交易所上交易他的杠杆代币,都会每天收取万三的持仓管理费,相较于其他交易所,管理费是最低的了。 空想不如实践下载DAPP进行交易吧,实际操作,真实体验。输赢则在那一刹间,先给自己定一个小目标,先来一个亿。","tags":[{"name":"ETH","slug":"ETH","permalink":"http://www.updatecg.xin/tags/ETH/"},{"name":"虚拟币","slug":"虚拟币","permalink":"http://www.updatecg.xin/tags/虚拟币/"}]},{"title":"K8s管理应用生命周期-Deployment","date":"2021-08-27T07:18:00.000Z","path":"2021/08/27/K8s管理应用生命周期-Deployment/","text":"在Kubernetes部署应用程序流程 使用Deployment部署Java应用 制作镜像利用镜像部署 使用Deployment控制器部署镜像 kubectl create deployment web --image=镜像地址kubectl get deployment,pods 使用Service发布Pod kubectl expose deployment web --port80 --type=NodePort --target-port=8080 --name=webkubectl get service 服务编排YAML文件格式说明 K8s是一个容器编排引擎,使用YAML文件编排要部署应用,因此在学习之前,应先了解YAML语法格式: 缩进表示层级关系 不支持制表符”tab”缩进,需使用空格缩进 通常开头缩进2个空格 字符后缩进1个空格,如冒号、逗号等 “—“ 表示YAML格式,一个文件的开始 “#” 注释 YAML文件创建资源对象apiVersion: apps/v1kind: Deploymentmetadata: name: web namespace: defaultspec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: 镜像地址 等同于:kubectl create deployment web –image=镜像地址 –replicas=3 -n default 标签描述: 标签key 含义 apiVersion API版本 kind 资源类型 metadata 资源元数据 spec 资源规格 replicas 副本数 selector 标签选择器,下面metadata.labels保持一致 template Pod模板 metadata Pod元数据 spec pod规格 containers 容器配置 部署卸载部署: kubectl apply -f xxx.yaml卸载: kubectl delete -f xxx.yaml 资源字段太多,记不住怎么办? 用get命令导出 kubectl get deployment nginx -o yaml > my-deploy.yaml Pod容器的字段拼写忘记了 kubectl explain pods.spec.containerskubectl explain deployment Deployment介绍 Deployment是最常用的K8s工作负载控制器(Workload Controllers),是K8s的一个抽象概念,用于更高级层次对象,部署和管理Pod。其他控制器还有DaemonSet、StatefulSet等。 主要功能 管理Pod和ReplicaSet 具体上线部署、副本设定、滚动升级、回滚等功能 提供声明式更新、例如只更新一个新的Image 应用场景 网站、API、微服务 应用生命周期管理流程多少钱 第一步》部署应用 利用创建nginx为模板,创建nginx.yaml文件apiVersion: apps/v1kind: Deploymentmetadata: name: web namespace: defaultspec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: nginx:1.16 执行创建yaml命令kubectl apply -f nginx.yaml 不使用yaml文件创建kubectl create deployment web --image=nginx:1.16 --replicas=3 第二步》滚动升级","tags":[{"name":"devOps","slug":"devOps","permalink":"http://www.updatecg.xin/tags/devOps/"},{"name":"K8S","slug":"K8S","permalink":"http://www.updatecg.xin/tags/K8S/"}]},{"title":"部署一套单Master的K8s集群","date":"2021-08-19T07:18:00.000Z","path":"2021/08/19/部署一套单Master的K8s集群/","text":"kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。 安装要求在开始之前,部署Kubernetes集群机器需要满足以下几个条件: 一台或多台机器,操作系统 CentOS7.x-86_x64 硬件配置:2GB或更多RAM,2个CPU或更多CPU,硬盘30GB或更多 集群中所有机器之间网络互通 可以访问外网,需要拉取镜像 禁止swap分区 准备环境 角色 IP k8s-master 192.168.31.61 k8s-node1 192.168.31.62 k8s-node2 192.168.31.63 关闭防火墙:$ systemctl stop firewalld$ systemctl disable firewalld关闭selinux:$ sed -i 's/enforcing/disabled/' /etc/selinux/config # 永久$ setenforce 0 # 临时关闭swap:$ swapoff -a # 临时$ sed -i '/swap/s/^\\(.*\\)$/#\\1/g' /etc/fstab # 永久设置主机名:$ hostnamectl set-hostname <hostname>在master添加hosts:$ cat >> /etc/hosts << EOF192.168.31.61 k8s-master192.168.31.62 k8s-node1192.168.31.63 k8s-node2EOF将桥接的IPv4流量传递到iptables的链:$ cat > /etc/sysctl.d/k8s.conf << EOFnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1EOF$ sysctl --system # 生效时间同步:$ yum install ntpdate -y$ ntpdate time.windows.com 安装Docker/kubeadm/kubelet【所有节点】Kubernetes默认CRI(容器运行时)为Docker,因此先安装Docker。 安装Docker$ wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo$ yum -y install docker-ce$ systemctl enable docker && systemctl start docker 配置镜像下载加速器: $ cat > /etc/docker/daemon.json << EOF{ "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]}EOF$ systemctl restart docker$ docker info 添加阿里云YUM软件源$ cat > /etc/yum.repos.d/kubernetes.repo << EOF[kubernetes]name=Kubernetesbaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0repo_gpgcheck=0gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpgEOF 安装kubeadm,kubelet和kubectl由于版本更新频繁,这里指定版本号部署: $ yum install -y kubelet-1.20.0 kubeadm-1.20.0 kubectl-1.20.0$ systemctl enable kubelet 部署Kubernetes Masterhttps://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-init/#config-file https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#initializing-your-control-plane-node 在192.168.31.61(Master)执行。 $ kubeadm init \\ --apiserver-advertise-address=192.168.31.61 \\ --image-repository registry.aliyuncs.com/google_containers \\ --kubernetes-version v1.20.0 \\ --service-cidr=10.96.0.0/12 \\ --pod-network-cidr=10.244.0.0/16 \\ --ignore-preflight-errors=all –apiserver-advertise-address 集群通告地址 –image-repository 由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定阿里云镜像仓库地址 –kubernetes-version K8s版本,与上面安装的一致 –service-cidr 集群内部虚拟网络,Pod统一访问入口 –pod-network-cidr Pod网络,,与下面部署的CNI网络组件yaml中保持一致 或者使用配置文件引导: $ vi kubeadm.confapiVersion: kubeadm.k8s.io/v1beta2kind: ClusterConfigurationkubernetesVersion: v1.20.0imageRepository: registry.aliyuncs.com/google_containers networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 $ kubeadm init --config kubeadm.conf --ignore-preflight-errors=all 拷贝kubectl使用的连接k8s认证文件到默认路径: mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config $ kubectl get nodesNAME STATUS ROLES AGE VERSIONlocalhost.localdomain NotReady control-plane,master 20s v1.20.0 加入Kubernetes Node在192.168.31.62/63(Node)执行。 向集群添加新节点,执行在kubeadm init输出的kubeadm join命令: $ kubeadm join 192.168.31.61:6443 --token 7gqt13.kncw9hg5085iwclx \\--discovery-token-ca-cert-hash sha256:66fbfcf18649a5841474c2dc4b9ff90c02fc05de0798ed690e1754437be35a01 默认token有效期为24小时,当过期之后,该token就不可用了。这时就需要重新创建token,可以直接使用命令快捷生成: kubeadm token create --print-join-command https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/ 部署容器网络(CNI)https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network 注意:只需要部署下面其中一个,推荐Calico。 Calico是一个纯三层的数据中心网络方案,Calico支持广泛的平台,包括Kubernetes、OpenStack等。 Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。 此外,Calico 项目还实现了 Kubernetes 网络策略,提供ACL功能。 https://docs.projectcalico.org/getting-started/kubernetes/quickstart $ wget https://docs.projectcalico.org/manifests/calico.yaml 下载完后还需要修改里面定义Pod网络(CALICO_IPV4POOL_CIDR),与前面kubeadm init指定的一样 修改完后应用清单: $ kubectl apply -f calico.yaml$ kubectl get pods -n kube-system 测试kubernetes集群 验证Pod工作 验证Pod网络通信 验证DNS解析 在Kubernetes集群中创建一个pod,验证是否正常运行: $ kubectl create deployment nginx --image=nginx$ kubectl expose deployment nginx --port=80 --type=NodePort$ kubectl get pod,svc 访问地址:http://NodeIP:Port 部署 Dashboard$ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml 默认Dashboard只能集群内部访问,修改Service为NodePort类型,暴露到外部: $ vi recommended.yaml...kind: ServiceapiVersion: v1metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboardspec: ports: - port: 443 targetPort: 8443 nodePort: 30001 selector: k8s-app: kubernetes-dashboard type: NodePort...$ kubectl apply -f recommended.yaml$ kubectl get pods -n kubernetes-dashboardNAME READY STATUS RESTARTS AGEdashboard-metrics-scraper-6b4884c9d5-gl8nr 1/1 Running 0 13mkubernetes-dashboard-7f99b75bf4-89cds 1/1 Running 0 13m 访问地址:https://NodeIP:30001 创建service account并绑定默认cluster-admin管理员集群角色: # 创建用户$ kubectl create serviceaccount dashboard-admin -n kube-system# 用户授权$ kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin# 获取用户Token$ kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/dashboard-admin/{print $1}') 使用输出的token登录Dashboard。 切换容器引擎为Containerdhttps://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#containerd 1、配置先决条件 cat <<EOF | sudo tee /etc/modules-load.d/containerd.confoverlaybr_netfilterEOFsudo modprobe overlaysudo modprobe br_netfilter# 设置必需的 sysctl 参数,这些参数在重新启动后仍然存在。cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.confnet.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1net.bridge.bridge-nf-call-ip6tables = 1EOF# Apply sysctl params without rebootsudo sysctl --system 2、安装containerd yum install -y yum-utils device-mapper-persistent-data lvm2yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repoyum update -y && sudo yum install -y containerd.iomkdir -p /etc/containerdcontainerd config default | sudo tee /etc/containerd/config.tomlsystemctl restart containerd 3、修改配置文件 vi /etc/containerd/config.toml [plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.2" ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true ... [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://b9pmyelo.mirror.aliyuncs.com"] systemctl restart containerd 4、配置kubelet使用containerd vi /etc/sysconfig/kubelet KUBELET_EXTRA_ARGS=--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --cgroup-driver=systemdsystemctl restart kubelet 5、验证 kubectl get node -o widek8s-node1 xxx containerd://1.4.4 常见问题怎么查看容器日志?kubectl logs <容器名称> -n kube-system怎么查看容器事件?kubectl describe pod <容器名称> -n kube-systemcalico无法拉取镜像解决办法?grep image calico.yamlimage: calico/cni:v3.15.1image: calico/pod2daemon-flexvol:v3.15.1image: calico/node:v3.15.1docker pull xxxdocker save calico/cni:v3.15.1 > cni.tardocker load < cni.tarkubectl delete -f calico.yamlkubectl apply -f calico.yamlinit失败或者情况环境可以使用:kubeadm reset为什么部署网络组件?Q1:每个docker主机创建的容器ip可能冲突?Q2:容器1访问容器2,容器1怎么知道容器2在哪个docker主机?Q3:容器1访问容器2数据包怎么传输过去?1、k8s现在可以使用docker嘛?可以。2、dockershim什么时候被移除?预计1.23版本。3、docker还值的学习嘛?值得。kubectl get pods --show-labels # 查看资源标签kubectl get pod -l app=web # 根据标签筛选资源","tags":[{"name":"devOps","slug":"devOps","permalink":"http://www.updatecg.xin/tags/devOps/"},{"name":"K8S","slug":"K8S","permalink":"http://www.updatecg.xin/tags/K8S/"}]},{"title":"服务器load average异常","date":"2021-08-06T07:18:00.000Z","path":"2021/08/06/服务器load average异常/","text":"4核16G的设备,正常load average不大于4,表示系统一直处在负载状态,程序有异常。 每日服务器性能邮件告警4核16G的服务器,load率达到了 6.97, 6.70, 4.87.信息总览:CPU 内核:4核CPU load率: 6.97, 6.70, 4.87总内存:15.3 GiB 使用内存:4.7 GiB 剩余内存:10.600000000000001GiBTOP前5: PID %CPU %MEM VSZ RSS Name 2805176 59.9 6.3 7.6 GiB 986.2 MiB java 2901828 13.1 5.4 4.1 GiB 847.4 MiB java 3532650 4.5 4.7 3.9 GiB 740.9 MiB java 3769112 3.0 4.2 5.5 GiB 657.7 MiB java 1619371 0.7 1.3 6.6 GiB 204.8 MiB java 注意:CPU load率: 6.97, 6.70, 4.87。load的平均值通过3个时间间隔来展示,就是我们看到的1分钟、5分钟、15分钟,load值和cpu核数有关,单核cpu的load=1表示系统一直处在负载状态,但是4核cpu的load=1表示系统有75%的空闲。 load高可能的一些原因 系统load高通常都是由于某段发布的代码有bug或者引入某些第三方jar而又使用不合理导致的,因此注意首先区分load高,是由于cpu高导致的还是io高导致的,根据不同的场景采取不同定位问题的方式。 死循环或者不合理的大量循环操作,如果不是循环操作,按照现代cpu的处理速度来说处理一大段代码也就一会会儿的事,基本对能力无消耗 频繁的YoungGC 频繁的FullGC 高磁盘IO 高网络IO 当束手无策时,jmap打印堆栈文件多分析分析吧,或许能灵光一现能找到错误原因。 发现YongGC块,FullGC也快,注定有问题jstat -gcutil pid 100 可以看出行O也一直在增加,几乎99,速度很快,老年代一下就满了,立马执行了YongGC,然后进入FGC的速度也快,2天就525了。 进一步排查获取dump文件通过命令抓取dump文件,进行分析。jmap -dump:format=b,file=1.hprof pid 分析dump文件 通过JProfiler分析1.hprof文件 上述分析结果可以直观的看出,创建的对象线程非常多,并且一直在加,释放很慢。 代码分析 发现程序确实有类似代码/** * Provide a new ScheduledExecutorService instance. * * <p>A shutdown hook is created to terminate the thread pool on application termination. * * @return new ScheduledExecutorService */public static ScheduledExecutorService defaultExecutorService() { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(getCpuCount()); Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(scheduledExecutorService))); return scheduledExecutorService;} 因为此方法是通过一个业务定时器在轮序,2秒一次,每次都去执行了一次,所以造成了内存不足,load值增加,系统负载增加。 解决方法代码解决程序改用单例模式,静态方法,执行一次就行。 jvm调优优化 -XX:MaxTenuringThreshold -XX:+ UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction具体参数详情请参考[性能调优参数] 调优案例-XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=0","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"JVM","slug":"JVM","permalink":"http://www.updatecg.xin/tags/JVM/"}]},{"title":"SpringBoot实现原理","date":"2020-12-15T02:05:02.000Z","path":"2020/12/15/SpringBoot/","text":"SpringBoot源码刨析 Map接口的基于哈希表的实现。 主要优势 不依赖传统的tomcat外部启动容器,不像一起通过打成war进行部署。可以单独以jar启动内置tomcat,jetty容器。 利用pom文件配置三方依赖,便于管理及引用。 利用注解直接引用方法配置 SpringBoot核心注解@SpringBootApplication@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { /** * 排除特定的自动配置类,以便它们永远不会被应用。 * 返回: */ @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; /** * 排除特定的自动配置类名称,以便它们永远不会被应用。 * 返回: * 要排除的类名 * 自从: */ @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; /** * 用于扫描带注释组件的基本包。 使用scanBasePackageClasses作为基于字符串的包名称的类型安全替代方案。 * 注意:这个设置只是@ComponentScan的别名。 它对@Entity扫描或 Spring Data Repository扫描没有影响。 对于那些你应该添加@EntityScan和@Enable...Repositories注释的人。 * 返回: * 要扫描的基本包 * 自从: * 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = \"basePackages\") String[] scanBasePackages() default {}; /** * scanBasePackages类型安全替代方案,用于指定要扫描带注释组件的包。 将扫描指定的每个类的包。 * 考虑在每个包中创建一个特殊的无操作标记类或接口,除了被此属性引用外,没有其他用途。 * 注意:这个设置只是@ComponentScan的别名。 它对@Entity扫描或 Spring Data Repository扫描没有影响。 对于那些你应该添加@EntityScan和@Enable...Repositories注释的人。 * 返回: * 要扫描的基本包 * 自从: * 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = \"basePackageClasses\") Class<?>[] scanBasePackageClasses() default {};} 其中比较重要的注解有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解非常重要 @SpringBootConfiguration@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {} @Configuration注解是用来读取spring.factories文件@EnableAutoConfiguration注解具有配置的功能,两则有什么区别? @EnableAutoConfiguration@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { } @EnableAutoConfiguration注解使用的是自动配置机制,是自动导入。例如:在pom引入maven包,class自动import。<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.5</version></dependency> class中自动import.import cn.hutool.json.JSONUtil; @ComponentScan@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Repeatable(ComponentScans.class)public @interface ComponentScan {} @ComponentScan的作用是告诉spring那个包下面的类用这个@Component注解,则会被spring自动扫描并且注入到bean容器。","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"SpringBoot","slug":"SpringBoot","permalink":"http://www.updatecg.xin/tags/SpringBoot/"}]},{"title":"RocketMq源码刨析之分布式事务","date":"2020-08-14T02:05:02.000Z","path":"2020/08/14/RocketMq/","text":"RocketMq源码刨析 想必大家都比较熟悉RocketMQ,阿里开源消息队列项目。对于队列来说可以直接强势得理解成,处理并非、分布式事务得敌虫。 [源码地址]: https://github.com/apache/rocketmq RocketMq4.3版本 支持分布式事物案例入口【org.apache.rocketmq.example.transaction.TransactionProducer】//实现监听TransactionListener transactionListener = new TransactionListenerImpl();//生产者本地初始化TransactionMQProducer producer = new TransactionMQProducer(\"please_rename_unique_group_name\");ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName(\"client-transaction-msg-check-thread\"); return thread; }});//设置线程池producer.setExecutorService(executorService);//设置生产者本地事务得回调组件producer.setTransactionListener(transactionListener);//开启消息处理producer.start(); 案例入口【org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendMessageInTransaction】public TransactionSendResult sendMessageInTransaction(final Message msg, final LocalTransactionExecuter localTransactionExecuter, final Object arg) throws MQClientException { //获取之前注册得TransactionListener本地事务回调组件 TransactionListener transactionListener = getCheckListener(); if (null == localTransactionExecuter && null == transactionListener) { throw new MQClientException(\"tranExecutor is null\", null); } //验证消息 Validators.checkMessage(msg, this.defaultMQProducer); SendResult sendResult = null; MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, \"true\"); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup()); try { //发送消息 sendResult = this.send(msg); } catch (Exception e) { throw new MQClientException(\"send message Exception\", e); } LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW; Throwable localException = null; //获取发送消息回调结果 switch (sendResult.getSendStatus()) { case SEND_OK: {//发送成功 try { if (sendResult.getTransactionId() != null) { msg.putUserProperty(\"__transactionId__\", sendResult.getTransactionId()); } String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (null != transactionId && !\"\".equals(transactionId)) { msg.setTransactionId(transactionId); } //开启了本地事务回调组件才会进行回调处理 if (null != localTransactionExecuter) { localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg); } else if (transactionListener != null) { log.debug(\"Used new transaction API\"); //执行本地事务 localTransactionState = transactionListener.executeLocalTransaction(msg, arg); } if (null == localTransactionState) { localTransactionState = LocalTransactionState.UNKNOW; } if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) { log.info(\"executeLocalTransactionBranch return {}\", localTransactionState); log.info(msg.toString()); } } catch (Throwable e) { log.info(\"executeLocalTransactionBranch exception\", e); log.info(msg.toString()); localException = e; } } break; case FLUSH_DISK_TIMEOUT: case FLUSH_SLAVE_TIMEOUT: case SLAVE_NOT_AVAILABLE: localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE; break; default: break; } try { //根据本地事务执行的结果去发送commit消息或者rollback消息 this.endTransaction(sendResult, localTransactionState, localException); } catch (Exception e) { log.warn(\"local transaction execute \" + localTransactionState + \", but end broker transaction failed\", e); } TransactionSendResult transactionSendResult = new TransactionSendResult(); transactionSendResult.setSendStatus(sendResult.getSendStatus()); transactionSendResult.setMessageQueue(sendResult.getMessageQueue()); transactionSendResult.setMsgId(sendResult.getMsgId()); transactionSendResult.setQueueOffset(sendResult.getQueueOffset()); transactionSendResult.setTransactionId(sendResult.getTransactionId()); transactionSendResult.setLocalTransactionState(localTransactionState); return transactionSendResult;} 总要节点 获取之前注册得TransactionListener本地事务回调组件:TransactionListener transactionListener = getCheckListener(); 验证消息: Validators.checkMessage(msg, this.defaultMQProducer); 发送消息: sendResult = this.send(msg); 获取发送消息回调结果:switch (sendResult.getSendStatus()) 如果开启事务transactionListener,执行本地事务:localTransactionState = transactionListener.executeLocalTransaction(msg, arg); 根据本地事务执行的结果去发送commit消息或者rollback消息:this.endTransaction(sendResult, localTransactionState, localException); 本地事务逻辑案例入口【org.apache.rocketmq.example.transaction.executeLocalTransaction】@Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { //这里会执行本地业务逻辑,此处省略... //返回本地事物的执行结果(UNKNOW、commit、rollback) int value = transactionIndex.getAndIncrement(); int status = value % 3; localTrans.put(msg.getTransactionId(), status); return LocalTransactionState.UNKNOW; } Netty线程检查事务状态案例入口【org.apache.rocketmq.example.transaction.checkLocalTransaction】@Overridepublic LocalTransactionState checkLocalTransaction(MessageExt msg) { //实现本地事务处理得结果逻辑 //TODO 业务数据 //比如本地业务想表A插入数据,那么此处可以去表A查询数据是否存在,就可以指导本地事务是否成功 //根据本地事务响应得到结果,返回不同得状态。 //本地事物执行成功返回COMMIT_MESSAGE,反之失败返回ROLLBACK_MESSAGE Integer status = localTrans.get(msg.getTransactionId()); if (null != status) { switch (status) { case 0: return LocalTransactionState.UNKNOW; case 1: return LocalTransactionState.COMMIT_MESSAGE; case 2: return LocalTransactionState.ROLLBACK_MESSAGE; default: return LocalTransactionState.COMMIT_MESSAGE; } } return LocalTransactionState.COMMIT_MESSAGE;} 业务场景源码正在创作..","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"消息队列","slug":"消息队列","permalink":"http://www.updatecg.xin/tags/消息队列/"}]},{"title":"服务无缘无故宕机","date":"2020-06-20T07:18:00.000Z","path":"2020/06/20/服务无缘无故宕机/","text":"定时服务无缘无故宕机了,服务相关日志没有任何错误日志。首先报告领导恢复业务排查问题监控服务 服务宕机了因服务没有监控,导致服务宕机没有发现,还是通过统计数据异常发现问题,立马去查看log日志。。。 很奇怪项目日志没有任何error日志,大大的加深了问题排查。 查看jvm错误日志hs_err_pid*.log,JVM crash信息,我们可以通过分析该文件定位到导致 JVM Crash 的原因,从而修复保证系统稳定日志头## There is insufficient memory for the Java Runtime Environment to continue.# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory.# Possible reasons:# The system is out of physical RAM or swap space# In 32 bit mode, the process size limit was hit# Possible solutions:# Reduce memory load on the system# Increase physical memory or swap space# Check if swap backing store is full# Use 64 bit Java on a 64 bit OS# Decrease Java heap size (-Xmx/-Xms)# Decrease number of Java threads# Decrease Java thread stack sizes (-Xss)# Set larger code cache with -XX:ReservedCodeCacheSize=# This output file may be truncated or incomplete.## Out of Memory Error (os_linux.cpp:2640), pid=114181, tid=0x00007f9340e91700## JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode linux-amd64 compressed oops)# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory. 减小thread stack的大小 线程数在3000~5000左右需要注意,JVM默认thread stack(-Xss)的大小为1024,这样当线程多时导致Native virtual memory被耗尽,实际上当thread stack的大小为128K 或 256K时是足够的,所以我们如果明确指定thread stack为128K 或 256K即可,具体使用-Xss Out of Memory Error (os_linux.cpp:2640), pid=114181, tid=0x00007f9340e91700 日志头可清晰看出Out of Memory Error-内存不足。 liunx64位解决优化方案 减少Java堆大小(-Xmx / -Xms) 减少Java线程数(从业务出发) 减少Java线程堆栈大小(-Xss) 使用-XX:ReservedCodeCacheSize =设置更大的代码缓存 堆栈信息--------------- P R O C E S S ---------------Java Threads: ( => current thread )=>0x00007f9b9447d800 JavaThread "pool-32458-thread-1" [_thread_new, id=2337, stack(0x00007f9340d91000,0x00007f9340e92000)] 0x00007f9b8c471000 JavaThread "pool-32456-thread-1" [_thread_blocked, id=2336, stack(0x00007f932a62b000,0x00007f932a72c000)] 0x00007f9ba44a4000 JavaThread "pool-32455-thread-1" [_thread_blocked, id=2330, stack(0x00007f932a72c000,0x00007f932a82d000)] 0x00007f9b745ed800 JavaThread "pool-32454-thread-1" [_thread_blocked, id=2319, stack(0x00007f932a82d000,0x00007f932a92e000)] 0x00007f9b7862a000 JavaThread "pool-32453-thread-1" [_thread_blocked, id=2318, stack(0x00007f932a92e000,0x00007f932aa2f000)] 0x00007f9b6c5cd800 JavaThread "pool-32452-thread-1" [_thread_blocked, id=2302, stack(0x00007f932aa2f000,0x00007f932ab30000)] 0x00007f9b98bf0000 JavaThread "pool-32451-thread-1" [_thread_blocked, id=2297, stack(0x00007f932ab30000,0x00007f932ac31000)] 0x00007f9b44633000 JavaThread "Keep-Alive-Timer" daemon [_thread_blocked, id=2285, stack(0x00007f9330e93000,0x00007f9330f94000)] 0x00007f9b6450b000 JavaThread "pool-32450-thread-1" [_thread_blocked, id=2187, stack(0x00007f932ac31000,0x00007f932ad32000)] 0x00007f9b9447b000 JavaThread "pool-32449-thread-1" [_thread_blocked, id=2159, stack(0x00007f932ad32000,0x00007f932ae33000)] 0x00007f9b8c46f000 JavaThread "pool-32448-thread-1" [_thread_blocked, id=2100, stack(0x00007f932ae33000,0x00007f932af34000)] 0x00007f9b8059b800 JavaThread "pool-32447-thread-1" [_thread_blocked, id=2068, stack(0x00007f932af34000,0x00007f932b035000)] 0x00007f9ba44a2000 JavaThread "pool-32446-thread-1" [_thread_blocked, id=1895, stack(0x00007f932b035000,0x00007f932b136000)] 0x00007f9b745eb000 JavaThread "pool-32445-thread-1" [_thread_blocked, id=1865, stack(0x00007f932b136000,0x00007f932b237000)] 0x00007f9b78628000 JavaThread "pool-32444-thread-1" [_thread_blocked, id=1864, stack(0x00007f932b237000,0x00007f932b338000)] 0x00007f9b6c5cb800 JavaThread "pool-32443-thread-1" [_thread_blocked, id=1854, stack(0x00007f932b338000,0x00007f932b439000)] 0x00007f9b98bed800 JavaThread "pool-32442-thread-1" [_thread_blocked, id=1850, stack(0x00007f932b439000,0x00007f932b53a000)] 0x00007f9b64508800 JavaThread "pool-32441-thread-1" [_thread_blocked, id=1849, stack(0x00007f932b53a000,0x00007f932b63b000)] 0x00007f9b94479000 JavaThread "pool-32440-thread-1" [_thread_blocked, id=1835, stack(0x00007f932b63b000,0x00007f932b73c000)] 0x00007f9b8c46d000 JavaThread "pool-32439-thread-1" [_thread_blocked, id=1832, stack(0x00007f932b73c000,0x00007f932b83d000)] 0x00007f9b80599000 JavaThread "pool-32438-thread-1" [_thread_blocked, id=1729, stack(0x00007f932b83d000,0x00007f932b93e000)] 0x00007f9ba449f800 JavaThread "pool-32437-thread-1" [_thread_blocked, id=1657, stack(0x00007f932b93e000,0x00007f932ba3f000)] 0x00007f9b78625800 JavaThread "pool-32436-thread-1" [_thread_blocked, id=1412, stack(0x00007f932ba3f000,0x00007f932bb40000)] 0x00007f9b54782000 JavaThread "pool-32435-thread-1" [_thread_blocked, id=1183, stack(0x00007f932bb40000,0x00007f932bc41000)] 0x00007f9b486df800 JavaThread "pool-32434-thread-1" [_thread_blocked, id=1182, stack(0x00007f932bc41000,0x00007f932bd42000)] 0x00007f9b44631000 JavaThread "pool-2-thread-16487" [_thread_blocked, id=1180, stack(0x00007f932bd42000,0x00007f932be43000)] 0x00007f9b4462f000 JavaThread "pool-2-thread-16486" [_thread_blocked, id=1177, stack(0x00007f932be43000,0x00007f932bf44000)] 0x0000000001d29800 JavaThread "pool-32433-thread-1" [_thread_blocked, id=1176, stack(0x00007f932bf44000,0x00007f932c045000)] 0x00007f9c5458a800 JavaThread "pool-32432-thread-1" [_thread_blocked, id=1175, stack(0x00007f932c045000,0x00007f932c146000)] 0x00007f9b4462d000 JavaThread "pool-2-thread-16485" [_thread_blocked, id=1174, stack(0x00007f932c146000,0x00007f932c247000)] 0x00007f9c4465c800 JavaThread "pool-32431-thread-1" [_thread_blocked, id=1173, stack(0x00007f932c247000,0x00007f932c348000)] JAVA线程堆栈,发现堆栈里面大量的pool的线程池,blocked阻塞线程高达32458个,这就是根本原因,每执行一个就创建。 误用JAVA线程池,每次用都新new一个线程池newSingleThreadScheduledExecutor 确实每次new会占用堆外堆存,没有跟踪到底层,但是线程池是管理线程的,虚拟机线程肯定是要跟OS申请线程资源的,linux中线程作为轻量进程,每fork一个肯定会占用OS的资源,相对于java虚拟机堆内内存来说,即是占用了堆外内存;而虚拟机本身由于线程池不释放,老生代会一直缓慢增加,但是没有堆外内存那么厉害,当老生代一直增加到100%后,虚拟机本身会报内存溢出。而操作系统层面,由于大量VIRT被占用,就连简单的top有时也会因为没有办法分配内存而执行不了 [hs_err_pid文件] 优化方案 线程池用完了必须shutdown()。 避免一直new创建新的线程池。 服务总内存16G,此服务启动设置了2G,增大了最大内存至3G,设置堆栈大小256K。","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"JVM","slug":"JVM","permalink":"http://www.updatecg.xin/tags/JVM/"}]},{"title":"JDK8之ConcurrentHashMap源码刨析实现原理","date":"2020-04-20T06:08:00.000Z","path":"2020/04/20/ConcurrentHashMap/","text":"ConcurrentHashMap源码刨析 ConcurrentHashMap相比于HashMap来讲,是线程安全的。底层的数据结构相同,都是数组+链表+红黑树。 Segment分段锁技术因Segment继承ReentrantLock加锁,所以ConcurrentHashMap支持并发操作。static class Segment<K,V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; final float loadFactor; Segment(float lf) { this.loadFactor = lf; } } 线程安全简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。 并行度(默认 16)concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16, 也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树。 并发减小锁粒度减小锁粒度是指缩小锁定对象的范围,从而减小锁冲突的可能性,从而提高系统的并发能力。减小锁粒度是一种削弱多线程锁竞争的有效手段,这种技术典型的应用是 ConcurrentHashMap(高性能的 HashMap)类的实现。对于 HashMap 而言,最重要的两个方法是get 与set 方法,如果我们对整个 HashMap 加锁,可以得到线程安全的对象,但是加锁粒度太大。Segment 的大小也被称为ConcurrentHashMap 的并发度。 分段锁ConcurrentHashMap,它内部细分了若干个小的 HashMap,称之为段(Segment)。默认情况下一个ConcurrentHashMap 被进一步细分为 16 个段,既就是锁的并发度。如果需要在 ConcurrentHashMap 中添加一个新的表项,并不是将整个 HashMap 加锁,而是首先根据hashcode 得到该表项应该存放在哪个段中,然后对该段加锁,并完成put 操作。在多线程环境中,如果多个线程同时进行put 操作,只要被加入的表项不存放在同一个段中,则线程间可以做到真正的并行。","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"JAVA","slug":"JAVA","permalink":"http://www.updatecg.xin/tags/JAVA/"}]},{"title":"JDK8之HashMap源码刨析实现原理","date":"2020-03-01T02:05:02.000Z","path":"2020/03/01/HashMap/","text":"HashMap源码刨析 Map接口的基于哈希表的实现。 此文章参考连接 官网解释 剖析解析重点一 默认初始容量 (16) 和默认负载因子 (0.75) 的空HashMap,最大容量,在两个带参数的构造函数中的任何一个隐式指定更高的值时使用。 必须是 2 的幂 <= 1<<30 (1073741824)。 构造函数/***最大容量,在两个带参数的构造函数中的任何一个隐式指定更高的值时使用。 必须是 2 的幂 <= 1<<30。*/ static final int MAXIMUM_CAPACITY = 1 << 30;/** * 构造一个具有指定初始容量和负载因子的空HashMap 。 * 参数: * * initialCapacity – 初始容量 * loadFactor – 负载因子 * 抛出: * IllegalArgumentException – 如果初始容量为负或负载因子为非正 */public HashMap(int initialCapacity, float loadFactor) { //初始容量小于0,则抛出异常 if (initialCapacity < 0) throw new IllegalArgumentException(\"Illegal initial capacity: \" + initialCapacity); //初始容量最大MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException(\"Illegal load factor: \" + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity);}/** * 实现了把一个数变为最接近的2的n次方 * 如果传入A,当A大于0,小于定义的最大容量时, * 如果A是2次幂则返回A,否则将A转化为一个比A大且差距最小的2次幂。 * 例如传入7返回8,传入8返回8,传入9返回16 * cap=7 代码逻辑如下 * n|=n代表或运算 0对应0则是0 否则1 */static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; //n|(n>>>1) 6|6>>>1 00000110|00000110>>>1 00000110|00000011 =00000111 n=7 n |= n >>> 2; //n|(n>>>2) 7|7>>>2 00000111|00000111>>>2 00000111|00000001 =00000111 n=7 n |= n >>> 4; //n|(n>>>4) 7|7>>>4 00000111|00000111>>>4 00000111|00000000 =00000111 n=7 n |= n >>> 8; //n|(n>>>8) 7|7>>>8 00000111|00000111>>>8 00000111|00000000 =00000111 n=7 n |= n >>> 16;//n|(n>>>16) 7|7>>>16 00000111|00000111>>>16 00000111|00000000 =00000111 n=7 //(n < 0) =false //(n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 //(n >= 1<<30) n>=1073741824 = false //false 得 n+1 = 8 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;} 核心函数put函数put函数/** * put函数 */public V put(K key, V value) { return putVal(hash(key), key, value, false, true);}/** * 获取hash值 */static final int hash(Object key) { int h; //^异或运算:相同置0,不同置1 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);} 为什么要右移16位? 保证高16位也参与计算, 我们直到int占4字节 32位,16是中位数 因为大部分情况下,都是低16位参与运算,高16位可以减少hash冲突putVal函数/** * 表,在第一次使用时初始化,并根据需要调整大小。 分配时,长度始终是 2 的幂。 (我们还在某些操作中容忍长度为零,以允许当前不需要的引导机制。) */transient Node<K,V>[] table;/** * 使用树而不是列表的 bin 计数阈值。 将元素添加到至少具有这么多节点的 bin 时,bin 会转换为树。 该值必须大于 2 且至少应为 8,以与树移除中关于在收缩时转换回普通 bin 的假设相匹配。 */static final int TREEIFY_THRESHOLD = 8;/** * 创建新的node */Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next);}/** * 参数: * hash – 密钥的散列 * value – 要放置的值 * onlyIfAbsent – 如果为真,则不更改现有值 * evict – 如果为 false,则表处于创建模式。 * 返回: * 以前的值,如果没有,则为 null */final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //1、如果主干tab等于null或者tab长度为0 则调用resize()方法获取长度。 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //2、数组长度与计算得出的hash进行比较 if ((p = tab[i = (n - 1) & hash]) == null)//如果位置空,则将i位置值赋值给新的一个node对象 tab[i] = newNode(hash, key, value, null); else {//3、位置不为空 Node<K,V> e; K k; if (p.hash == hash &&//4、p旧节点与新添加元素完相同 ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//则将旧节点赋值给新节点 else if (p instanceof TreeNode)//5、如果p已经是树节点的一个实例,既这里已经是树了 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//p旧节点与e新节点完全不相同,p也不是树节点treenode实例 for (int binCount = 0; ; ++binCount) {//死循环 if ((e = p.next) == null) {//e新节点=p旧节点.next下一个节点等于空 p.next = newNode(hash, key, value, null);//则赋值新的节点 if (binCount >= TREEIFY_THRESHOLD - 1) // 如果链表长度大于等于8 treeifyBin(tab, hash);//将链表转为红黑树 break;//跳出循环 } //如果遍历过程中链表中的元素与新添加的元素完全相同,则跳出循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break;//跳出循环 p = e;//则将遍历节点元素赋值给新节点 } } if (e != null) { //这个判断中代码作用为:如果添加的元素产生了hash冲突,那么调用 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold)//如果元素数量大于临界值,则进行扩容 resize(); afterNodeInsertion(evict); return null;} 为什么会考虑红黑树?链表过长则使用红黑树,提高查找效率。 HashMap链表转红黑树为什么是8?对此源码也做了解释。* Because TreeNodes are about twice the size of regular nodes, we* use them only when bins contain enough nodes to warrant use* (see TREEIFY_THRESHOLD). And when they become too small (due to* removal or resizing) they are converted back to plain bins. In* usages with well-distributed user hashCodes, tree bins are* rarely used. Ideally, under random hashCodes, the frequency of* nodes in bins follows a Poisson distribution* (http://en.wikipedia.org/wiki/Poisson_distribution) with a* parameter of about 0.5 on average for the default resizing* threshold of 0.75, although with a large variance because of* resizing granularity. Ignoring variance, the expected* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /* factorial(k)). The first values are:** 0: 0.60653066* 1: 0.30326533* 2: 0.07581633* 3: 0.01263606* 4: 0.00157952* 5: 0.00015795* 6: 0.00001316* 7: 0.00000094* 8: 0.00000006* more: less than 1 in ten million 上面这段话的意思是,如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。此问题参考连接 resize扩容函数(源码详解)//2的幂数static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//要调整大小的下一个大小值(容量 * 负载因子)。int threshold;final Node<K,V>[] resize() { //主干table赋值给oldTab Node<K,V>[] oldTab = table; //获取原哈希表容量 如果哈希表为空则容量为0 ,否则为原哈希表长度 int oldCap = (oldTab == null) ? 0 : oldTab.length; //获取原生的扩容标准(容量16 * 负载因子0.75) int oldThr = threshold; //初始化新容量和新扩容门槛为0 int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE;//当容量超过最大值时,临界值设置成int最大值 return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)//未达到1<<30,则进行扩容操作 newThr = oldThr << 1; // 容量扩充到2倍 } else if (oldThr > 0) //不执行 newCap = oldThr; else { // 零初始阈值表示使用默认值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) {//不执行 float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr;//将新的临界值赋值给threshold @SuppressWarnings({\"rawtypes\",\"unchecked\"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab;//新的数组赋值给table //扩容后,重新计算元素新的位置 if (oldTab != null) { //循环老的容量 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) //当前index对应的节点为红黑树,当树的个数小于等于UNTREEIFY_THRESHOLD则转成链表 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order //分成两个链表,减少扩容的迁移量 //loHead,下标不变情况下的链表头 //loTail,下标不变情况下的链表尾 //hiHead,下标改变情况下的链表头 //hiTail,下标改变情况下的链表尾 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) {//散列下标不变 if (loTail == null) loHead = e;//设置链头 else loTail.next = e; loTail = e;//设置链尾 } else {//散列下标改变 if (hiTail == null) hiHead = e;//设置链头 else hiTail.next = e; hiTail = e;//设置链尾 } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; // 扩容长度为当前index位置+旧的容量 newTab[j + oldCap] = hiHead; } } } } } return newTab; } 什么时候进行扩容?hashMap中元素个数超过【数组长度(容量)*localFactor(负载因子)】时,就会进行数组扩容。 localFactor(负载因子)默认0.75 容量默认16,也就是说0.7516=12,超过12,就会把数组大小扩容为212=32,扩容一倍。然后重新计算每个元素的仔数组中的位置。扩容限制机制? 限制扩容大小不能大于1<<30(1073741824),最低16。 扩容倍数是最接近2的幂次,例如:new HashMap(13) 最终仍会是16长度。步骤1、定义了oldCap原table长度,newCap新table长度,newCap是oldCap的两倍。2、循环原table,获取链上元素存入新table3、计算新旧下标结果,要么相同,要么新下标=旧下标+旧小标数组长度。hashMap是先插入还是先扩容?1、初始容量,是先扩容在插入,后续就是先插入后扩容,因为resize()会进行新旧table做比较。结合源码说说HashMap在高并发场景中为什么会出现死循环? jdk1.7,hashMap容量是有限的,高并发下,多元素插入,hashMap会达到一定的饱和程度。 就会进行resize扩容。 扩容后将rehash遍历数组数据,把所有的数据数据重新刷新到新数组。jdk1.8的优化? JDK 8 中采用的是位桶 + 链表/红黑树的方式,当某个位桶的链表的长度超过 8 的时候,这个链表就将转换成红黑树。HashMap和HashTable有何不同? hashMap适合单线程,允许key/value为空 hashTable适合多线程,不许key/value为空","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"JAVA","slug":"JAVA","permalink":"http://www.updatecg.xin/tags/JAVA/"}]},{"title":"JAVA锁","date":"2019-12-20T09:16:00.000Z","path":"2019/12/20/JAVA锁/","text":"现场安全标识 乐观锁乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数 据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新), 如果失败则要重复读-比较-写的操作。java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。 悲观锁悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block 直到拿到锁。java 中的悲观锁就是Synchronized,AQS 框架下的锁则是先尝试cas 乐观锁去获取锁,获取不到, 才会转换为悲观锁,如RetreenLock。 自旋锁自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋), 等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。 优缺点自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换! 但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗, 其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁; 公平与非公平锁公平锁(Fair)加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得 非公平锁(Nonfair)加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列 Java 中的synchronized 是非公平锁,ReentrantLock 默认的lock()方法采用的是非公平锁。 ReadWriteLock 读写锁为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm 自己控制的,你只要上好相应的锁即可。 读锁如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁 写锁如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!Java 中 读 写 锁 有 个 接 口 java.util.concurrent.locks.ReadWriteLock , 也 有 具 体 的 实 现ReentrantReadWriteLock。 共享锁和独占锁java 并发包提供的加锁模式分为独占锁和共享锁。独占锁独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。 共享锁共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。 AQS 的内部类Node 定义了两个常量 SHARED 和EXCLUSIVE,他们分别标识 AQS 队列中等待线程的锁获取模式。 java 的并发包中提供了ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问, 或者被一个 写操作访问,但两者不能同时进行。 分段锁分段锁也并非一种实际的锁,而是一种思想ConcurrentHashMap中Segment分段锁。 锁优化减少锁持有时间只用在有线程安全要求的程序上加锁 减小锁粒度将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap。 锁分离最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,具体也请查看[高并发 Java 五] JDK 并发包 1。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如LinkedBlockingQueue 从头部取出,从尾部放数据 锁粗化通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。 锁消除锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起。","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"JAVA","slug":"JAVA","permalink":"http://www.updatecg.xin/tags/JAVA/"}]},{"title":"liunx常用命令","date":"2019-05-30T08:21:00.000Z","path":"2019/05/30/liunx常用命令/","text":"检验真理得决定性是实践。 查看服务器内存信息[root@VM_0_12_centos 388]# free -m total used free shared buff/cache availableMem: 1839 625 417 0 796 1013Swap: 0 0 0 Liunx查看进程运行得完成路径方法/procLinux在启动一个进程时,系统会在/proc下创建一个以PID命名的文件夹,在该文件夹下会有我们的进程的信息,其中包括一个名为exe的文件即记录了绝对路径,通过ll或ls –l命令即可查看。 列入查看cpu高得服务情况PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 388 root 16 -4 114564 1264 1032 S 0.3 0.1 4:50.63 auditd 5646 root 20 0 573864 20200 2772 S 0.3 1.1 258:37.17 YDService 5990 root 20 0 576604 41248 12820 S 0.3 2.2 0:03.58 node /data/blog 查询PID等于388[root@VM_0_12_centos data]# cd /proc/388[root@VM_0_12_centos 388]# lltotal 0dr-xr-xr-x 2 root root 0 Mar 7 15:03 attr-rw-r--r-- 1 root root 0 May 30 16:54 autogroup-r-------- 1 root root 0 May 30 16:54 auxv-r--r--r-- 1 root root 0 Mar 7 15:03 cgroup--w------- 1 root root 0 May 30 16:54 clear_refs-r--r--r-- 1 root root 0 Mar 7 15:03 cmdline-rw-r--r-- 1 root root 0 Mar 7 15:03 comm-rw-r--r-- 1 root root 0 May 30 16:54 coredump_filter-r--r--r-- 1 root root 0 May 30 16:54 cpusetlrwxrwxrwx 1 root root 0 Mar 24 02:36 cwd -> /-r-------- 1 root root 0 May 30 16:54 environlrwxrwxrwx 1 root root 0 Mar 7 15:03 exe -> /usr/sbin/auditddr-x------ 2 root root 0 Mar 7 15:03 fddr-x------ 2 root root 0 May 25 10:09 fdinfo-rw-r--r-- 1 root root 0 May 30 16:54 gid_map-r-------- 1 root root 0 May 30 16:54 io-r--r--r-- 1 root root 0 May 30 16:54 limits-rw-r--r-- 1 root root 0 Mar 7 15:03 loginuiddr-x------ 2 root root 0 May 30 16:54 map_files-r--r--r-- 1 root root 0 May 30 16:54 maps 可以看出,即可追踪服务地址lrwxrwxrwx 1 root root 0 Mar 7 15:03 exe -> /usr/sbin/auditd ps -aux可查看详细信息USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.1 41108 2924 ? Ss Mar07 6:02 /usr/lib/systemd/systemd --system --deserialize 25root 2 0.0 0.0 0 0 ? S Mar07 0:00 [kthreadd]root 3 0.0 0.0 0 0 ? S Mar07 1:28 [ksoftirqd/0]root 5 0.0 0.0 0 0 ? S< Mar07 0:00 [kworker/0:0H] 查看文件夹容量[root@VM_0_12_centos data]# du -sh248M . Liunx如何修改默认SSH端口linux SSH默认端口是22,不修改的话存在一定的风险,要么是被人恶意扫描,要么会被人破解或者攻击,所以我们需要修改默认的SSH端口。vi /etc/ssh/sshd_config 默认端口是22,并且已经被注释掉了,打开注释修改为其他未占用端口即可。 开启防火墙端口并重复服务即可。systemctl restart sshd.service","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"},{"name":"liunx","slug":"liunx","permalink":"http://www.updatecg.xin/tags/liunx/"}]},{"title":"docker命令大全","date":"2019-05-20T09:16:00.000Z","path":"2019/05/20/docker命令大全/","text":"docker 必须掌握得命令 查看镜像 命令 描述 docker images 列出所有镜像文件 docker images -a 列出所有得镜像文件-包括历史 docker rmi 删除一个或多个镜像 查看容器 命令 描述 docker ps 列出当前所有正在运行得容器 docker ps -l 列出最近一次启动得容器 docker ps -a 列出所有容器(包括历史,即运行过得容器) docker ps -q 列出最近一次运行得容器ID 启动容器 命令 描述 docker start/stop/restart 开启/停止/重启container docker start [container_id] 再次运行某个container (包括历史container) docker attach [container_id] 连接一个正在运行的container实例(即实例必须为start状态,可以多个窗口同时attach 一个container实例) docker exec -it <container_id> /bin/bash 进入容器 docker start -i 启动一个container并进入交互模式(相当于先start,在attach) docker run -i -t /bin/bash 使用image创建container并进入交互模式, login shell是/bin/bash docker run -i -t -p <host_port:contain_port> 映射 HOST 端口到容器,方便外部访问容器内服务,host_port 可以省略,省略表示把 container_port 映射到一个动态端口。 注:使用start是启动已经创建过得container,使用run则通过image开启一个新的container。 如何在docker容器和宿主机之间复制文件 命令 描述 sudo docker cp host_path containerID:container_path 从主机复制到容器 sudo docker cp containerID:container_path host_path 从容器复制到主机 docker run –name cloud1 -h cloud1 -it jchubby/spark:1.0 利用镜像启用容器 删除容器 命令 描述 docker rm <container…> 删除一个或多个container docker rm docker ps -a -q 删除所有的container docker ps -a -q xargs docker rm 同上, 删除所有的container 通过容器生成新的镜像 命令 描述 docker commit 把一个容器转变为一个新的镜像 持久化容器 命令 描述 docker export > /tmp/export.tar export命令用于持久化容器 特殊命令 命令 描述 docker logs $CONTAINER_ID 查看docker实例运行日志,确保正常运行 docker inspect $CONTAINER_ID docker inspect <image或者container> 查看image或container的底层信息 docker build 寻找path路径下名为的Dockerfile的配置文件,使用此配置生成新的image docker build -t repo[:tag] 同上,可以指定repo和可选的tag docker build -f 使用指定的dockerfile配置文件,docker以stdin方式获取内容,使用此配置生成新的image docker port 查看本地哪个端口映射到container的指定端口,其实用docker ps 也可以看到","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"}]},{"title":"Nginx参数配置说明","date":"2019-04-15T09:02:00.000Z","path":"2019/04/15/Nginx参数配置说明/","text":"Nginx详细配置 #运行用户user nobody;#启动进程,通常设置成和cpu的数量相等worker_processes 1;#全局错误日志及PID文件#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;#工作模式及连接数上限events { #epoll是多路复用IO(I/O Multiplexing)中的一种方式, #仅用于linux2.6以上内核,可以大大提高nginx的性能 use epoll; #单个后台worker process进程的最大并发链接数 worker_connections 1024; # 并发总数是 worker_processes 和 worker_connections 的乘积 # 即 max_clients = worker_processes * worker_connections # 在设置了反向代理的情况下,max_clients = worker_processes * worker_connections / 4 为什么 # 为什么上面反向代理要除以4,应该说是一个经验值 # 根据以上条件,正常情况下的Nginx Server可以应付的最大连接数为:4 * 8000 = 32000 # worker_connections 值的设置跟物理内存大小有关 # 因为并发受IO约束,max_clients的值须小于系统可以打开的最大文件数 # 而系统可以打开的最大文件数和内存大小成正比,一般1GB内存的机器上可以打开的文件数大约是10万左右 # 我们来看看360M内存的VPS可以打开的文件句柄数是多少: # $ cat /proc/sys/fs/file-max # 输出 34336 # 32000 < 34336,即并发连接总数小于系统可以打开的文件句柄总数,这样就在操作系统可以承受的范围之内 # 所以,worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置 # 使得并发总数小于操作系统可以打开的最大文件数目 # 其实质也就是根据主机的物理CPU和内存进行配置 # 当然,理论上的并发总数可能会和实际有所偏差,因为主机还有其他的工作进程需要消耗系统资源。 # ulimit -SHn 65535}http { #设定mime类型,类型由mime.type文件定义 include mime.types; default_type application/octet-stream; #设定日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; #sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件, #对于普通应用,必须设为 on, #如果用来进行下载等应用磁盘IO重负载应用,可设置为 off, #以平衡磁盘与网络I/O处理速度,降低系统的uptime. sendfile on; #tcp_nopush on; #连接超时时间 #keepalive_timeout 0; keepalive_timeout 65; tcp_nodelay on; #开启gzip压缩 gzip on; gzip_disable "MSIE [1-6]."; #设定请求缓冲 client_header_buffer_size 128k; large_client_header_buffers 4 128k; #设定虚拟主机配置 server { #侦听80端口 listen 80; #定义使用 www.nginx.cn访问 server_name www.nginx.cn; #定义服务器的默认网站根目录位置 root html; #设定本虚拟主机的访问日志 access_log logs/nginx.access.log main; #默认请求 location / { #定义首页索引文件的名称 index index.php index.html index.htm; } # 定义错误提示页面 error_page 500 502 503 504 /50x.html; location = /50x.html { } #静态文件,nginx自己处理 location ~ ^/(images|javascript|js|css|flash|media|static)/ { #过期30天,静态文件不怎么更新,过期可以设大一点, #如果频繁更新,则可以设置得小一点。 expires 30d; } #PHP 脚本请求全部转发到 FastCGI处理. 使用FastCGI默认配置. location ~ .php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } #禁止访问 .htxxx 文件 location ~ /.ht { deny all; } }}","tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.updatecg.xin/tags/Nginx/"}]},{"title":"为什么我们做分布式使用Redis","date":"2019-03-29T03:01:00.000Z","path":"2019/03/29/为什么我们做分布式使用Redis/","text":"绝大部分写业务的程序员,在实际开发中使用 Redis 的时候,只会 Set Value 和 Get Value 两个操作,对 Redis 整体缺乏一个认知。这里对 Redis 常见问题做一个总结,解决大家的知识盲点。 1、为什么使用 Redis在项目中使用 Redis,主要考虑两个角度:性能和并发。如果只是为了分布式锁这些其他功能,还有其他中间件 Zookpeer 等代替,并非一定要使用 Redis。 性能:如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。 特别是在秒杀系统,在同一时间,几乎所有人都在点,都在下单。。。执行的是同一操作———向数据库查数据。 根据交互效果的不同,响应时间没有固定标准。在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。 并发:如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库。 使用 Redis 的常见问题 缓存和数据库双写一致性问题 缓存雪崩问题 缓存击穿问题 缓存的并发竞争问题 2、单线程的 Redis 为什么这么快这个问题是对 Redis 内部机制的一个考察。很多人都不知道 Redis 是单线程工作模型。 原因主要是以下三点:纯内存操作 单线程操作,避免了频繁的上下文切换 采用了非阻塞 I/O 多路复用机制 仔细说一说 I/O 多路复用机制,打一个比方:小名在 A 城开了一家快餐店店,负责同城快餐服务。小明因为资金限制,雇佣了一批配送员,然后小曲发现资金不够了,只够买一辆车送快递。 经营方式一客户每下一份订单,小明就让一个配送员盯着,然后让人开车去送。慢慢的小曲就发现了这种经营方式存在下述问题: 时间都花在了抢车上了,大部分配送员都处在闲置状态,抢到车才能去送。 随着下单的增多,配送员也越来越多,小明发现快递店里越来越挤,没办法雇佣新的配送员了。 配送员之间的协调很花时间。 综合上述缺点,小明痛定思痛,提出了经营方式二。 经营方式二小明只雇佣一个配送员。当客户下单,小明按送达地点标注好,依次放在一个地方。最后,让配送员依次开着车去送,送好了就回来拿下一个。上述两种经营方式对比,很明显第二种效率更高。 在上述比喻中: 每个配送员→每个线程 每个订单→每个 Socket(I/O 流) 订单的送达地点→Socket 的不同状态 客户送餐请求→来自客户端的请求 明曲的经营方式→服务端运行的代码 一辆车→CPU 的核数 于是有了如下结论: 经营方式一就是传统的并发模型,每个 I/O 流(订单)都有一个新的线程(配送员)管理。 经营方式二就是 I/O 多路复用。只有单个线程(一个配送员),通过跟踪每个 I/O 流的状态(每个配送员的送达地点),来管理多个 I/O 流。 下面类比到真实的 Redis 线程模型,如图所示: Redis-client 在操作的时候,会产生具有不同事件类型的 Socket。在服务端,有一段 I/O 多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。 3、Redis 的数据类型及使用场景一个合格的程序员,这五种类型都会用到。 String 最常规的 set/get 操作,Value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。 Hash 这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。我在做单点登录的时候,就是用这种数据结构存储用户信息,以 CookieId 作为 Key,设置 30 分钟为缓存过期时间,能很好的模拟出类似 Session 的效果。 List 使用 List 的数据结构,可以做简单的消息队列的功能。另外,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。 Set 因为 Set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。我们的系统一般都是集群部署,使用 JVM 自带的 Set 比较麻烦。另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。 Sorted Set Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作。Sorted Set 可以用来做延时任务。 4、Redis 的过期策略和内存淘汰机制Redis 是否用到家,从这就能看出来。比如你 Redis 只能存 5G 数据,可是你写了 10G,那会删 5G 的数据。怎么删的,这个问题思考过么? 正解:Redis 采用的是定期删除+惰性删除策略。 为什么不用定时删除策略 定时删除,用一个定时器来负责监视 Key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 Key,因此没有采用这一策略。 定期删除+惰性删除如何工作 定期删除,Redis 默认每个 100ms 检查,有过期 Key 则删除。需要说明的是,Redis 不是每个 100ms 将所有的 Key 检查一次,而是随机抽取进行检查。如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。 采用定期删除+惰性删除就没其他问题了么 不是的,如果定期删除没删除掉 Key。并且你也没及时去请求 Key,也就是说惰性删除也没生效。这样,Redis 的内存会越来越高。那么就应该采用内存淘汰机制。 在 redis.conf 中有一行配置: maxmemory-policy volatile-lru该配置就是配内存淘汰策略的: noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(推荐使用,目前项目在用这种)(最近最久使用算法) allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。(应该也没人用吧,你不删最少使用 Key,去随机删) volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。(不推荐) volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。(依然不推荐) volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。(不推荐) 5、Redis 和数据库双写一致性问题一致性问题还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。前提是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。 另外,我们所做的方案从根本上来说,只能降低不一致发生的概率。因此,有强一致性要求的数据,不能放缓存。首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。 6、如何应对缓存穿透和缓存雪崩问题这两个问题,一般中小型传统软件企业很难碰到。如果有大并发的项目,流量有几百万左右,这两个问题一定要深刻考虑。缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。 缓存穿透解决方案: 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。 缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。 缓存雪崩解决方案: 给缓存的失效时间,加上一个随机值,避免集体失效。 使用互斥锁,但是该方案吞吐量明显下降了。 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。 然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。 7、如何解决 Redis 的并发竞争 Key 问题这个问题大致就是,同时有多个子系统去 Set 一个 Key。这个时候要注意什么呢?大家基本都是推荐用 Redis 事务机制。 但是我并不推荐使用 Redis 的事务机制。因为我们的生产环境,基本都是 Redis 集群环境,做了数据分片操作。你一个事务中有涉及到多个 Key 操作的时候,这多个 Key 不一定都存储在同一个 redis-server 上。因此,Redis 的事务机制,十分鸡肋。 如果对这个 Key 操作,不要求顺序这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。 如果对这个 Key 操作,要求顺序假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC。 期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。 假设时间戳如下:系统 A key 1 {valueA 3:00}系统 B key 1 {valueB 3:05}系统 C key 1 {valueC 3:10} 那么,假设系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了,以此类推。其他方法,比如利用队列,将 set 方法变成串行访问也可以。 8、总结Redis 在国内各大公司都能看到其身影,比如我们熟悉的新浪,阿里,腾讯,百度,美团,小米等。学习 Redis,这几方面尤其重要:Redis 客户端、Redis 高级功能、Redis 持久化和开发运维常用问题探讨、Redis 复制的原理和优化策略、Redis 分布式解决方案等。 转自:https://www.cnblogs.com/yaodengyan/p/9717080.html","tags":[{"name":"Redis","slug":"Redis","permalink":"http://www.updatecg.xin/tags/Redis/"}]},{"title":"大数据案例之HDFS-HIVE-Spark","date":"2019-03-21T03:16:00.000Z","path":"2019/03/21/大数据案例之HDFS-HIVE-Spark/","text":"发现Hive后台使用MapReduce作为执行引擎,实在是有点慢.几十万数据查询了10+秒,上千万数据查询了100+秒。 还是单纯查询没有附加任何条件。Hive作为数据仓库是不错的选择,单表支持几十亿数据库存储。 对于查询来说,我想就需要考虑其他的MapReduce查询方式了。这里考虑学习SparkSql。 原因的话就让我们一起来学习,认识吧。 推荐管理Hive数据库软件 Aginity Workbench for Hadoop 可视化管理HIVE数据、支持远程连接Hadoop根据dfs创建hive外部映射表。","tags":[{"name":"Hadoop","slug":"Hadoop","permalink":"http://www.updatecg.xin/tags/Hadoop/"},{"name":"Hive","slug":"Hive","permalink":"http://www.updatecg.xin/tags/Hive/"},{"name":"Spark","slug":"Spark","permalink":"http://www.updatecg.xin/tags/Spark/"},{"name":"Aginity","slug":"Aginity","permalink":"http://www.updatecg.xin/tags/Aginity/"}]},{"title":"大数据案例之HDFS-HIVE","date":"2019-03-13T07:42:00.000Z","path":"2019/03/13/大数据案例之HDFS-HIVE/","text":"基于Hdfs、hive、mysql数据处理案例,闲时自玩项目 数据采集 数据采集方式有很多种,一般在项目中采用数据上报方式。本地为了方便测试则采用读取csv文件。后续python自动抓取数据。 链接: https://pan.baidu.com/s/1cOCe1GXAxtkXCUbvY0MWFw 提取码: r23c数据量不多,侧重于功能 数据处理 清洗数据,统计分析数据,结果存储HDFS ,加载至HIVE, Sqoop至MYSQL CSV 数据加载入Hadoop 部分代码public String transfer(File file, String folderPath, String fileName) throws Exception { if (!opened) { throw new Exception("FileSystem was not opened!"); } boolean folderCreated = fs.mkdirs(new Path(folderPath)); Path filePath = new Path(folderPath, StrUtils.isEmpty(fileName) ? file.getName() : fileName); boolean fileCreated = fs.createNewFile(filePath); FSDataOutputStream append = fs.append(filePath); byte[] bytes = new byte[COPY_BUFFERSIZE]; int size = 0; FileInputStream fileInputStream = new FileInputStream(file); while ((size = fileInputStream.read(bytes)) > 0) { append.write(bytes, 0, size); } fileInputStream.close(); return filePath.toUri().toString();} 将dfs文件加载入hive 部分代码 //表 String yyyyMMdd = hiveTable + DateUtil.formatDate(new Date(), "yyyyMMdd"); //参数 Map<String, String> map = new HashMap<>(); map.put("title", "STRING"); map.put("discountPrice", "STRING"); map.put("price", "STRING"); map.put("address", "STRING"); map.put("count", "STRING"); //创建表 按天分表 hiveDataService.createHiveTable(yyyyMMdd, map); //将dfs数据加载到hive表 hiveDataService.loadHiveIntoTable(fs.getDfsPath(), yyyyMMdd);/** * @param tableName hive表名 * @param parametersMap 表字段值/类型 */ @Override public void createHiveTable(String tableName, Map<String, String> parametersMap) { StringBuffer sql = new StringBuffer("CREATE TABLE IF NOT EXISTS "); sql.append("" + tableName + ""); StringBuffer sb = new StringBuffer(); parametersMap.forEach((k, v) -> { sb.append(k + " " + v + ","); }); sql.append("(" + sb.deleteCharAt(sb.length() - 1) + ")"); sql.append("ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\\n' "); // 定义分隔符 sql.append("STORED AS TEXTFILE"); // 作为文本存储 Log.info("Create table [" + tableName + "] successfully..."); try { hiveJdbcTemplate.execute(sql.toString()); } catch (DataAccessException dae) { Log.error(dae.fillInStackTrace()); } }/** * @param filePath dfs文件路径 * @param tableName 表名 */@Overridepublic void loadHiveIntoTable(String filePath, String tableName) { StringBuffer sql = new StringBuffer("load data inpath "); sql.append("'" + filePath + "'into table " + tableName); Log.info("Load data into table successfully..."); try { hiveJdbcTemplate.execute(sql.toString()); } catch (DataAccessException dae) { Log.error(dae.fillInStackTrace()); }} 利用外部表加载dfs数据至分区表 上述代码中有一步为load data 至hive。在于朋友交流中,他提醒可以直接利用外部加载数据,自此代码如下: 外部表好处 hive创建外部表时,仅记录数据所在的路径,不对数据的位置做任何改变 删除表的时候,外部表只删除元数据,不删除数据 内部表drop表会把元数据删除 Hive创建外部表---------------------------------java代码----------------------------------------- /** * 利用外部表加载数据 * * @param tableName hive表名 * @param parametersMap 表字段值/类型 * @param dfsUrl dfs文件地址 */ @Override public synchronized void createOuterHiveTable(String tableName, Map<String, String> parametersMap, String dfsUrl) { StringBuffer sql = new StringBuffer("CREATE EXTERNAL TABLE IF NOT EXISTS "); sql.append("" + tableName + ""); StringBuffer sb = new StringBuffer(); parametersMap.forEach((k, v) -> { sb.append(k + " " + v + ","); }); sql.append("(" + sb.deleteCharAt(sb.length() - 1) + ")"); sql.append(" PARTITIONED BY (day STRING)"); sql.append(" ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' " + " COLLECTION ITEMS TERMINATED BY '\\\\002'" + " MAP KEYS TERMINATED BY '\\\\003'" + " LINES TERMINATED BY '\\n' "); // 定义分隔符 sql.append("LOCATION '" + dfsUrl + "'"); // 外部表加载hdfs数据目录 Log.info("Create EXTERNAL table [" + tableName + "] successfully..."); try { hiveJdbcTemplate.execute(sql.toString()); } catch (DataAccessException dae) { Log.error(dae.fillInStackTrace()); } }------------------------------------Sql--------------------------------------------- CREATE EXTERNAL TABLE IF NOT EXISTS xx_outer_partitioned ( affiliatedbasenum STRING, locationid STRING, pickupdate dispatchingbasenum STRING ) PARTITIONED BY (day STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY '\\002' MAP KEYS TERMINATED BY '\\003' LINES TERMINATED BY '\\n' LOCATION '/data/outerClientSummary/'; HIVE分析数据 hive支持sql操作(支持连表操作、排序),支持分区(此功能特别实用,比如数据量庞大时一般会按照天分表,此时就可以利用按天分区) 案列 :统计服装制造商主要城市分布 (因为hive字段与值对应错乱,但是导入至mysql不会错乱)hive> select count as addr,count(count) from commodity20190315 GROUP BY count;广东广州 361浙江杭州 94广东深圳 87上海 76广东东莞 74江苏苏州 52浙江嘉兴 24广东佛山 22福建泉州 15北京 14天津 13四川成都 12....... 省略 结果:这是对一千多条的抽样调查,由此可见我们平时的衣物制造商地点广东广州居多。 Sqoop 将分析后HIVE数据导出至MYSQL 主要思想: sqoop export –connect jdbc:mysql://IP地址:3306/mall –username root –password 123456 –table commodity20190315 –export-dir /hivedata/warehouse/hive.db/commodity20190314 –input-fields-terminated-by ‘,’ –input-null-string ‘\\N’ –input-null-non-string ‘\\N’ 此命令是经过一下错误原因完善出来的。 --export-dir:代表dfs文件目录,则是hive存储数据的地方 错误原因119/03/15 09:20:25 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead.19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Error parsing arguments for export:19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Unrecognized argument: –input-null-string19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Unrecognized argument: \\N19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Unrecognized argument: –input-null-non-string19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Unrecognized argument: \\N19/03/15 09:20:25 ERROR tool.BaseSqoopTool: Unrecognized argument: –input-fields-terminated-by 解决方式 :命令输入错误,注意“-connect”应该是“–connect”杠 错误原因219/03/15 09:41:47 ERROR mapreduce.TextExportMapper: Exception:java.lang.RuntimeException: Can't parse input data: '2019春季新款chic条纹套头毛衣女装学生韩版宽松显瘦百搭长袖上衣,39.98,42.98,广东 广州,350' at commodity20190314.__loadFromFields(commodity20190314.java:487) at commodity20190314.parse(commodity20190314.java:386) at org.apache.sqoop.mapreduce.TextExportMapper.map(TextExportMapper.java:89) java.lang.Exception: java.io.IOException: Can't export data, please check failed map task logs at org.apache.hadoop.mapred.LocalJobRunner$Job.runTasks(LocalJobRunner.java:462) at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:522)Caused by: java.io.IOException: Can't export data, please check failed map task logs at org.apache.sqoop.mapreduce.TextExportMapper.map(TextExportMapper.java:122) at org.apache.sqoop.mapreduce.TextExportMapper.map(TextExportMapper.java:39) at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:146) at org.apache.sqoop.mapreduce.AutoProgressMapper.run(AutoProgressMapper.java:64) at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:787) at org.apache.hadoop.mapred.MapTask.run(MapTask.java:341) at org.apache.hadoop.mapred.LocalJobRunner$Job$MapTaskRunnable.run(LocalJobRunner.java:243) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) 解决方式 :检查数据是否包含“ ”空格,去掉空格,hive默认分割符–input-fields-terminated-by ‘,’,后续发现mysql表多了id,hive没有导致转码出错。 成功将HIVE数据导入MYSQL 统计/分析 因数据量较小,则想利用python爬取数据,数据量偏少。则通过第三方地址下载。 爬取今日头条 今日头条每天新闻信息在100条左右,最多抓取5天之内的数据。数据量极少。 HIVE数据分析数据集资源来源:http://dataju.cn/Dataju/web/home 里面包含各种类数据集M-T级文件不等。是一个自娱自玩数据来源的好地址。 总条数 14270481 条 hive> select count(*) from commodity20190320;WARNING: Hive-on-MR is deprecated in Hive 2 and may not be available in the future versions. Consider using a different execution engine (i.e. spark, tez) or using Hive 1.X releases.Query ID = root_20190320095041_1829fe55-336b-4481-a869-0b24ea274854Total jobs = 1Launching Job 1 out of 1Number of reduce tasks determined at compile time: 1In order to change the average load for a reducer (in bytes):set hive.exec.reducers.bytes.per.reducer=<number>In order to limit the maximum number of reducers:set hive.exec.reducers.max=<number>In order to set a constant number of reducers:set mapreduce.job.reduces=<number>Job running in-process (local Hadoop)2019-03-20 09:50:43,908 Stage-1 map = 0%, reduce = 0%2019-03-20 09:50:45,926 Stage-1 map = 100%, reduce = 0%2019-03-20 09:50:46,936 Stage-1 map = 100%, reduce = 100%Ended Job = job_local1948148359_0001MapReduce Jobs Launched:Stage-Stage-1: HDFS Read: 4150522476 HDFS Write: 0 SUCCESSTotal MapReduce CPU Time Spent: 0 msecOK14270481Time taken: 6.276 seconds, Fetched: 1 row(s) 按时间动态分区 commodity20190320 此表是通过csv导入的全量数据,包含了时间段。 使用动态分区需要注意设定以下参数: hive.exec.dynamic.partition 默认值:false 是否开启动态分区功能: 默认false关闭 hive.exec.dynamic.partition.mode 默认值:strict 动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模式表示允许所有的分区字段都可以使用动态分区。 hive.exec.max.dynamic.partitions.pernode 默认值:100 在每个执行MR的节点上,最大可以创建多少个动态分区。 该参数需要根据实际的数据来设定。 比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。 hive.exec.max.dynamic.partitions 默认值:1000 在所有执行MR的节点上,最大一共可以创建多少个动态分区。 hive.exec.max.created.files 默认值:100000 整个MR Job中,最大可以创建多少个HDFS文件。 一般默认值足够了,除非你的数据量非常大,需要创建的文件数大于100000,可根据实际情况加以调整。 //查看表结构 hive> desc commodity20190320; OK affiliatedbasenum string locationid string pickupdate string dispatchingbasenum string Time taken: 0.044 seconds, Fetched: 4 row(s)//创建按月按天分区表 hive> CREATE TABLE commodity_partitioned ( > affiliatedbasenum STRING, > locationid STRING, > dispatchingbasenum STRING > ) PARTITIONED BY (month STRING,day STRING) > stored AS textfile; OK Time taken: 0.238 seconds//设置动态分区属性 hive> SET hive.exec.dynamic.partition=true; hive> SET hive.exec.dynamic.partition.mode=nonstrict; hive> SET hive.exec.max.dynamic.partitions.pernode = 1000; hive> SET hive.exec.max.dynamic.partitions=1000;//时间格式 pickupdate = "5/31/2014 23:59:00" 按天分区则获取年月日即可。利用substr函数:substr(affiliatedbasenum,2,1) AS month,substr(affiliatedbasenum,2,9) AS day//向分区添加数据 hive> INSERT overwrite TABLE commodity_partitioned PARTITION (month,day) > SELECT locationid,pickupdate,dispatchingbasenum,substr(affiliatedbasenum,2,1) AS month,substr(affiliatedbasenum,2,9) AS day > FROM commodity20190320; 为外部表挂载分区---------------------------------java代码----------------------------------------- /** * @param tableName 外部表名 * @param yyyyMMdd 分区标识 * @param dfsUrl dfs地址 */ @Override public void loadOuterHiveDataPartitions(String tableName, String yyyyMMdd, String dfsUrl) { StringBuffer sql = new StringBuffer("alter table " + tableName); sql.append(" add partition (day='" + yyyyMMdd + "') location '" + dfsUrl + yyyyMMdd + "/'"); Log.info("Load data into OuterHiveDataPartitions successfully..."); try { hiveJdbcTemplate.execute(sql.toString()); } catch (DataAccessException dae) { Log.error(dae.fillInStackTrace()); } }---------------------------------Sql----------------------------------------- alter table uber_outer_partitioned add partition (day='2019-03-21') location '/data/outerClientSummary/2019-03-21' 注意:分区数据支持sql查询 总结对于大数据初学者的我,这才是我的第一步,都说万事开头难,坚持吧。 知道如何把已有的数据采集到HDFS上,包括离线采集和实时采集; 知道sqoop是HDFS和其他数据源之间的数据交换工具,支持把数据在HDFS\\HIVE\\MYSQL互相传输; 知道Hadoop的MRV1与Yarn(MRV2)的区别,最主要的单点故障以及性能大大提升。 JobTracker被RescourceManager替换 每一个节点的TaskTacker被NodeManager替换 Yarn大大减小了 JobTracker(也就是现在的 ResourceManager)的资源消耗。 监测每一个 Job 子任务 (tasks) 状态的程序分布式化了 Hive外部表被删除时,不会删除元数据,可以直接在外部表基础啊上创建分区表。 Hive一般作为数据仓库,几乎不会被用作与OLAP操作 原因则在于hive数据量庞大时查询速度太慢.下一章则会着重介绍.","tags":[{"name":"Hadoop","slug":"Hadoop","permalink":"http://www.updatecg.xin/tags/Hadoop/"},{"name":"Sqoop","slug":"Sqoop","permalink":"http://www.updatecg.xin/tags/Sqoop/"},{"name":"Hive","slug":"Hive","permalink":"http://www.updatecg.xin/tags/Hive/"},{"name":"Python","slug":"Python","permalink":"http://www.updatecg.xin/tags/Python/"}]},{"title":"sqoop安装部署问题事项","date":"2019-03-13T03:02:00.000Z","path":"2019/03/13/sqoop安装部署问题事项/","text":"主要用于在Hadoop(Hive)与传统的数据库(mysql、postgresql…)间进行数据的传递,可以将一个关系型数据库(例如 : MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中 sqoop主要有两个版本:1.4.x、1.99.x ; sqoop1和sqoop2两个版本。 环境变量配置无问题。 【以下问题是1.99.6版本,经过多方尝试,仍报错】 报错 【-bash: sqoop: command not found】 sqoop2中已经没有sqoop command指令了…sqoop指令是适用与sqoop1的 进入sqoop.sh client,使用show job 等。 报错 sqoop:000> show jobException has occurred during processing commandException: org.apache.sqoop.common.SqoopException Message: CLIENT_0000:An unknown error has occurred 原因是没有指定服务端,需设置 set server –host 主机名或IP地址 sqoop:000> set server --host 主机名或IP地址Server is set successfully 可通过设置,查看错误原因 sqoop:000> set option --name verbose --value trueVerbose option was changed to true 尝试利用sqoop2 1.99.7版本下载地址 [https://mirrors.tuna.tsinghua.edu.cn/apache/sqoop/]配置好环境变量export SQOOP2_HOME=/usr/local/sqoop/export PATH=$PATH:$SQOOP2_HOME/binexport CATALINA_BASE=$SQOOP2_HOME/server 修改 $SQOOP2_HOME/conf/sqoop.propertiesorg.apache.sqoop.submission.engine.mapreduce.configuration.directory=/usr/local/hadoop-2.7.7/etc/hadooporg.apache.sqoop.security.authentication.type=SIMPLEorg.apache.sqoop.security.authentication.handler=org.apache.sqoop.security.authentication.SimpleAuthenticationHandlerorg.apache.sqoop.security.authentication.anonymous=true# Number of milliseconds, submissions created before this limit will be removed, default is one day //锁定提交的job时间,锁定时间内不能删除org.apache.sqoop.submission.purge.threshold=300000# JDBC repository provider configuration //jdbc配置目录org.apache.sqoop.repository.jdbc.url=jdbc:derby:/usr/local/sqoop/logs/repository/db;create=trueorg.apache.sqoop.log4j.appender.file.File=/usr/local/sqoop/logs/sqoop.log //sqoop2日志文件目录org.apache.sqoop.repository.sysprop.derby.stream.error.file=/usr/local/sqoop/logs/derbyrepo.log //错误日志文件目录 启动服务端 $SQOOP2_HOME/bin/sqoop2-server start 报错 Setting conf dir: /usr/local/sqoop/bin/../confSqoop home directory: /usr/local/sqoopCan't load the Hadoop related java lib, please check the setting for the following environment variables:HADOOP_COMMON_HOME, HADOOP_HDFS_HOME, HADOOP_MAPRED_HOME, HADOOP_YARN_HOME 检查Hadoop环境是否配置正确 export HADOOP_HOME=/usr/local/hadoop-2.7.7export PATH=$PATH:$HADOOP_HOME/bin; 注意:配置这个变量主要是让Sqoop能找到以下目录的jar文件和Hadoop配置文件: $HADOOP_HOME/share/hadoop/common $HADOOP_HOME/share/hadoop/hdfs $HADOOP_HOME/share/hadoop/mapreduce $HADOOP_HOME/share/hadoop/yarn 官网上说名了可以单独对各个组建进行配置,使用以下变量:$HADOOP_COMMON_HOME, $HADOOP_HDFS_HOME, $HADOOP_MAPRED_HOME, $HADOOP_YARN_HOME若$HADOOP_HOME已经配置了,最好不要再配置下面的变量,可能会有些莫名错误。 查看是否启动成功方式有三种 第一种查看日志 [root@localhost bin]# sqoop2-server startSetting conf dir: /usr/local/sqoop/bin/../confSqoop home directory: /usr/local/sqoopStarting the Sqoop2 server...0 [main] INFO org.apache.sqoop.core.SqoopServer - Initializing Sqoop server.5 [main] INFO org.apache.sqoop.core.PropertiesConfigurationProvider - Starting config file poller threadSLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/usr/local/hadoop-2.7.7/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [jar:file:/usr/local/apache-hive-2.3.4-bin/lib/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.Sqoop2 server started. 第二种执行访问 http://IP地址:12000/sqoop/version 第三种执行JPS命令查看进程:Bootstrap、SqoopJettyServer[root@localhost bin]# jps22402 RunJar5861 Jps11848 NamesrvStartup2936 DataNode3513 jenkins.war5561 SqoopJettyServer2060 NameNode22317 RunJar12285 JswLauncher12686 NodeManager12399 ResourceManager5135 Bootstrap 启动客户端 $SQOOP2_HOME/bin/sqoop2-shell 再次尝试 show job、show connector 没有报错 这说明安装部署成功 sqoop:000> show connector0 [main] WARN org.apache.hadoop.util.NativeCodeLoader - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable+------------------------+---------+------------------------------------------------------------+----------------------+| Name | Version | Class | Supported Directions |+------------------------+---------+------------------------------------------------------------+----------------------+| generic-jdbc-connector | 1.99.7 | org.apache.sqoop.connector.jdbc.GenericJdbcConnector | FROM/TO || kite-connector | 1.99.7 | org.apache.sqoop.connector.kite.KiteConnector | FROM/TO || oracle-jdbc-connector | 1.99.7 | org.apache.sqoop.connector.jdbc.oracle.OracleJdbcConnector | FROM/TO || ftp-connector | 1.99.7 | org.apache.sqoop.connector.ftp.FtpConnector | TO || hdfs-connector | 1.99.7 | org.apache.sqoop.connector.hdfs.HdfsConnector | FROM/TO || kafka-connector | 1.99.7 | org.apache.sqoop.connector.kafka.KafkaConnector | TO || sftp-connector | 1.99.7 | org.apache.sqoop.connector.sftp.SftpConnector | TO |+------------------------+---------+------------------------------------------------------------+----------------------+sqoop:000> show job+----+------+----------------+--------------+---------+| Id | Name | From Connector | To Connector | Enabled |+----+------+----------------+--------------+---------++----+------+----------------+--------------+---------+ 尝试后 我想要的功能是将hive数据移入mysql,经对sqoop2的使用发现,sqoop2并不支持。遗憾。接下来将尝试sqoop1。 区别在于 功能 Sqoop 1 Sqoop 2 用于所有主要 RDBMS 的连接器 支持 不支持 解决办法: 使用已在以下数据库上执行测试的通用 JDBC 连接器: Microsoft SQL Server 、 PostgreSQL 、 MySQL 和 Oracle 。 此连接器应在任何其它符合 JDBC 要求的数据库上运行。但是,性能可能无法与 Sqoop 中的专用连接器相比 Kerberos 安全集成 支持 不支持 数据从 RDBMS 传输至 Hive 或 HBase 支持 不支持 解决办法: 按照此两步方法操作。 将数据从 RDBMS 导入 HDFS 在 Hive 中使用相应的工具和命令(例如 LOAD DATA 语句),手动将数据载入 Hive 或 HBase 数据从 Hive 或 HBase 传输至 RDBMS 不支持 解决办法: 按照此两步方法操作。 从 Hive 或 HBase 将数据提取至 HDFS (作为文本或 Avro 文件) 使用 Sqoop 将上一步的输出导出至 RDBMS 不支持 按照与 Sqoop 1 相同的解决方法操作","tags":[{"name":"Sqoop","slug":"Sqoop","permalink":"http://www.updatecg.xin/tags/Sqoop/"},{"name":"Hive","slug":"Hive","permalink":"http://www.updatecg.xin/tags/Hive/"}]},{"title":"性能调优参数","date":"2019-01-24T01:00:00.000Z","path":"2019/01/24/性能调优参数/","text":"堆内存性能、垃圾回收性能 堆内存性能优化参数 参数 含义 案例 -Xmx 设置JVM 最大堆内存 -Xmx3550m -Xms 设置JVM 初始堆内存,此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存 -Xms3550m -Xss 设置每个线程的栈大小.JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K应当根据应用的线程所需内存大小进行调整在相同物理内存下。减小这个值能生成更多的线程但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右需要注意的是:当这个值被设置的较大(例如> 2MB)时将会在很大程度上降低系统的性能 -Xss128k -Xmn 设置年轻代。大小为2G在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8 -Xmn2g -XX 设置年轻代初始值为1024M -XX -XX:MaxNewSize 设置年轻代最大值 -XX:MaxNewSize = 1024 -XX:PermSize 设置持久代初始值 -XX:PermSize = 256 -XX:MaxPermSize 设置持久代最大值 -XX:MaxPermSize = 256 -XX:NewRatio 设置年轻代(包括1个伊甸和2个幸存者区)与年老代的比值 -XX:NewRatio = 4(表示1:4) -XX:SurvivorRatio 设置年轻代中伊甸区与幸存者区的比值。表示2个幸存者区(JVM堆内存年轻代中默认有2个大小相等的幸存者区)与1个伊甸区的比值为2:4,即1个幸存者区占整个年轻代大小的1/6 -XX:SurvivorRatio = 4 -XX:MaxTenuringThreshold 表示一个对象如果在幸存者区(救助空间)移动了7次还没有被垃圾回收就进入年老代如果设置为0的话,则年轻代对象不经过幸存者区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在幸存者区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。 -XX:MaxTenuringThreshold = 7 垃圾回收性能优化参数 参数 含义 案例 -XX:+ UseSerialGC 设置串行收集器 -XX:+ UseSerialGC -XX:+ UseParallelGC 置为并行收集器此配置仅对年轻代有效即年轻代使用并行收集,而年老代仍使用串行收集。 -XX:+ UseParallelGC -XX:ParallelGCThreads 配置并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收此值建议配置与CPU数目相等。 -XX:ParallelGCThreads = 20 -XX:+ UseParallelOldGC 配置年老代垃圾收集方式为并行收集.JDK6.0开始支持对年老代并行收集。 -XX:+ UseParallelOldGC -XX:MaxGCPauseMillis 设置每次年轻代代垃圾回收的最长时间(单位毫秒)如果无法满足此时间,JVM会自动调整年轻代大小,以满足此时间。 -XX:MaxGCPauseMillis = 100 -XX:+ UseAdaptiveSizePolicy 设置此选项后,并行收集器会自动调整年轻代伊甸区大小和幸存者区大小的比例,以达成目标系统规定的最低响应时间或者收集频率等指标此参数建议在使用并行收集器时,一直打开。 -XX:+ UseAdaptiveSizePolicy -XX:+ UseConcMarkSweepGC 即CMS收集,设置年老代为并发收集的.cms收集是JDK1.4后期版本开始引入的新GC算法它的主要适合场景是对响应时间的重要性需求大于对吞吐量的需求,能够承受垃圾回收线程和应用线程共享CPU资源,并且应用中存在比较多的长生命周期对象的的的.cms收集的目标是尽量减少应用的暂停时间,减少全GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存。 -XX:+ UseConcMarkSweepGC -XX:+ UseParNewGC 设置年轻代为并发收集可与CMS收集同时使用.JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此参数。 -XX:+ UseSerialGC -XX:CMSFullGCsBeforeCompaction 由于并发收集器不对内存空间进行压缩和整理,所以运行一段时间并行收集以后会产生内存碎片,内存使用效率降低。此参数设置运行0次Full GC后对内存空间进行压缩和整理,即每次Full GC后立刻开始压缩和整理内存。 -XX:CMSFullGCsBeforeCompaction = 0 -XX:+ UseCMSCompactAtFullCollection 打开内存空间的压缩和整理,在Full GC后执行。可能会影响性能,但可以消除内存碎片。 -XX:+ UseCMSCompactAtFullCollection -XX:+ CMSIncrementalMode 设置为增量收集模式一般适用于单CPU情况。 -XX:+ CMSIncrementalMode -XX:CMSInitiatingOccupancyFraction 表示年老代内存空间使用到70%时就开始执行CMS收集,以确保年老代有足够的空间接纳来自年代代的对象,避免Full GC的发生。 -XX:CMSInitiatingOccupancyFraction = 70 JVM服务参数调优实战服务器配置:8 CPU,8G MEM,JDK 1.6.X参数方案:-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio = 6 -XX:MaxPermSize = 256m -XX:ParallelGCThreads = 8 -XX:MaxTenuringThreshold = 0 -XX:+ UseConcMarkSweepGC调优说明: -Xmx与-Xms相同以避免JVM反复重新申请内存。-XMX的大小约等于系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。 -Xmn1256m设置年轻代大小为1256MB。此值对系统性能影响较大,太阳官方推荐配置年轻代大小为整个堆的3/8。 -Xss128k设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。 -XX:SurvivorRatio = 6设置年轻代中Eden区与Survivor区的比值。系统默认是8,根据经验设置为6,则2个幸存者区与1个Eden区的比值为2:6,一个幸存者区占整个年轻代的1/8。 -XX:ParallelGCThreads = 8配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。 -XX:MaxTenuringThreshold = 0设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在幸存者区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率根据被海量访问的动态网络应用之特点,其内存要么被缓存起来以减少直接访问数据库,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。 -XX:+ UseConcMarkSweepGC设置年老代为并发收集.CMS(ConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少完全GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。","tags":[{"name":"架构师","slug":"架构师","permalink":"http://www.updatecg.xin/tags/架构师/"}]},{"title":"微信支付宝支付经验以及相关坑","date":"2018-11-29T06:00:02.000Z","path":"2018/11/29/微信支付宝支付经验以及相关坑/","text":"此片文章介绍对接微信、支付宝所遇到的问题以及经验之谈。 准备工作 支付类型 文档 对接难易程度 文档地址 支付宝 文档写的不错 易 https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query 微信 不想说了 难(也不能说难应该是坑) https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1 熟悉支付流程支付宝[详情文档请参考] 微信[详情文档请参考] 有萝卜有坑 序号 类型 问题描述 No1 支付宝 支付宝秘钥使用pkcs8加密方式,以及相关参数key配置。 No2 微信 微信key值一定要使用微信支付的key,不要用平台key。 No3 微信 一直报签名错误,下面详细介绍。 No4 微信 得到的签名一定要转MD5,然后在将其转换成大写,并且生成MD5必须要以UTF-8的方式。 No5 微信 订单金额需要转换成以分为单位。 No6 微信 且值为空的参数不参与签名。 No7 微信 参数需按ASCII码从小到大排序。 No8 微信 第二次签名认证参数已消息的格式。 No9 微信 第二次签名参数package,需要赋值Sign=WXPay 一句签名错误概括全部错误信息,我就弱弱的问句错误码有用么?“验证签名错误”第一反应肯定是检测签名是否正确,[官方验证签名地址]。然而,这才刚刚开始,签名正确了还是特么的报“验证签名错误”。特么的把以上几点全部检测了“有萝卜有坑”,然并卵。上面说了签名验证有两次,这是第二次验证错误,最后发现【传入微信端的时间戳参数 ios需要32位 安卓需要10位】,笑哭。首先看见“验证签名错误”,肯定是服务端问题,然而呢。。。 签名两次重要参数第一次签名参数得到sign并赋值pay.setSign(sign) ;接下来就是将pay对象转换成xml,调用统一下单接口进行统一支付,并将统一支付返回的xml转换成bean。notify_url //回调地址time_start //交易起始时间time_expire //交易结束时间spbill_create_ip //IP地址trade_type //交易类型limit_pay //no_credit--指定不能使用信用卡支付appid //微信开放平台审核通过的应用APPIDmch_id //微信支付分配的商户号nonce_str //随机字符串,不长于32位。sign_type //签名类型,目前支持HMAC-SHA256和MD5,默认为MD5body //商品描述交易字段格式根据不同的应用场景按照以下格式:APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。out_trade_no //订单号total_fee //交易金额默认为人民币交易,接口中参数支付金额单位为【分】,参数值不能带小数。sign //根据以上数据生成签名 第二次签名参数统一下单成功会返回微信预支付订单号prepay_id,我们需要根据这个prepay_id进行二次签名,二次签名所用参数如下(不包括paySign)。noncestr //随机字符串appid //微信开放平台审核通过的应用APPIDtimestamp //就是这B,传入微信端的时间戳参数 ios需要32位 安卓需要10位partnerid //商户号package //Sign=WXPayprepayid //微信预支付订单号prepay_idsign //根据以上数据生成签名 实例代码后续上传GitHub第三方文档能不能写专业第一,之前和中兴、华为对接一样,特么的文档写的一塌糊涂。","tags":[{"name":"支付","slug":"支付","permalink":"http://www.updatecg.xin/tags/支付/"}]},{"title":"SpringCloud服务多实例注入Consul挂掉问题","date":"2018-09-07T05:25:02.000Z","path":"2018/09/07/SpringCloud服务多实例注入Consul挂掉问题/","text":"相信大家在使用SpringCloud服务的发现与注册,都会对Eureka、Zookeeper、Consul熟悉吧。18年7月份爆出了Eureka2.0不在对外开源的消息。相信会有一部分程序猿逐渐往Consul发展。这其中也包含小生我。 问题SpringCloud+1.2.x时候最严重的一个问题,就是多实例注册的问题. 原因概述主要原因是SpringCloud中Consul在注册的时候实例名采用了:服务名-端口号{spring.application.name}-{server.port})的值,可以看到这个实例名如果不改变端口号的情况下,实例名都是相同的。由于Consul对实例唯一性的判断标准也有改变,在老版本的Consul中,对于实例名相同,但是服务地址不同,依然会认为是不同的实例。在Consul 1.2.x中,服务实例名成为了集群中的唯一标识。 解决方法通过配置 spring.cloud.consul.discovery.instance-id 参数来实例命令规则。利用随机数来控制实例名。spring.cloud.consul.discovery.instance-id=${spring.application.name}-${random.int[10000,99999]} 效果图 ! ! ! 效果图中的错误不必关注,那是因为外网问题 SpringCloud注入与注册类别简单介绍 Feature Consul zookeeper euerka 服务健康检查 服务状态,内存,硬盘等 (弱)长连接,keepalive 可配支持 多数据中心 支持 — — kv存储服务 支持 支持 — 一致性 raft paxos — cap ca cp ap 使用接口(多语言能力) 支持http和dns 客户端 http(sidecar) watch支持 全量/支持long polling 支持 支持 long polling/大部分增量 自身监控 metrics — metrics 安全 acl /https acl — spring cloud集成 已支持 已支持 已支持","tags":[{"name":"SpringCloud","slug":"SpringCloud","permalink":"http://www.updatecg.xin/tags/SpringCloud/"},{"name":"Consul","slug":"Consul","permalink":"http://www.updatecg.xin/tags/Consul/"}]},{"title":"腾讯云IM支持JAVA Server","date":"2018-07-13T03:27:02.000Z","path":"2018/07/13/腾讯云IM支持JAVA Server/","text":"因阿里云IM服务不稳定,网易云太贵,现切换至腾讯云。 起因根据腾讯云官方文档利用Java编写Server,因腾讯云现不支持Java。官方在后台服务中调用 REST API,本质上是发起 HTTPS POST 请求。云通信提供了 Server SDK 来封装对 REST API 的调用,开发者可以将其直接集成到您的服务端代码中。 PHP Server SDK; Node.js Server SDK; Java Server SDK 敬请期待 (完善中); Golang Server SDK(敬请期待)。 内容现阶段完成内容如下: 缓存identifier usersig 存入Redis。 账号管理 独立模式账号导入 独立模式账户批量导入 单发单聊消息 推送 获取推送报告 设置应用属性名称 获取应用属性名称 获取用户属性 设置用户属性 群组功能 获取APP中的所有群组 创建群组 获取群组详细资料 增加群组成员 删除群组成员 解散群组 持续更新…代码地址GitHub:[https://github.com/UpdateCw/IMActionJar]","tags":[{"name":"IM","slug":"IM","permalink":"http://www.updatecg.xin/tags/IM/"}]},{"title":"一台电脑利用秘钥绑定多个ssh-key账号","date":"2018-07-02T06:07:02.000Z","path":"2018/07/02/一台电脑利用秘钥绑定多个ssh-key账号/","text":"因新环境利用内部邮箱创建git账账号管理项目,自己玩时有一个git账号。公司绑定gitLab,自己绑定了git.coding.net以及gitHub.com。从而两则在提交代码时发生了权限问题以及冲突。 本文在windows环境下配置Git多账号支持SSH-KEY。配置github.com、git.coding.net 、gitLab的SSH-KEY. 注意:因本已配置SSH-KEY,在此就不测试。发截图即可。 生成gitHub.com以及gitLab对应的私钥公钥(目录一般存在于C:\\Users.ssh)执行命令 ssh-keygen -t rsa -C email 创建github对应的sshkey,命名为id_rsa_github gitHub.com与coding所用秘钥相同,id_rsa.pub属于gitLab,id_rsa_github属于gitHub,coding 把github对应的公钥和coding对应的公钥上传到服务器分别在gitHub、coding、以及gitLab配置SSH-KEYS在此举例gitHub如下: 在.ssh目录创建config文本文件并完成相关配置(最核心的地方)每个账号单独配置一个Host,每个Host要取一个别名,每个Host主要配置HostName和IdentityFile、User属性即可 参数名 描述 Host 设想名称 HostName 这个是真实的域名地址 IdentityFile 这里是id_rsa的地址 PreferredAuthentications 配置登录时用什么权限认证–可设为publickey,password publickey,keyboard-interactive等 User 配置使用用户名 # gitLab Host chenwu@meillie.comHostName chenwu@meillie.comUser chenwuIdentityFile ~/.ssh/id_rsaPreferredAuthentications publickey# 配置github.comHost github.com HostName github.comIdentityFile ~/.ssh/id_ras_gitHubPreferredAuthentications publickeyUser UpdateCw# 配置coding.netHost git.coding.netHostName git.coding.netIdentityFile ~/.ssh/id_ras_gitHubPreferredAuthentications publickeyUser UpdateMe 打开Git Bash客户端(管理员身份运行)执行测试命令测试是否配置成功(会自动在.ssh目录生成known_hosts文件把私钥配置进去) 学习心得实践才是检验真理的源头","tags":[{"name":"Git","slug":"Git","permalink":"http://www.updatecg.xin/tags/Git/"}]},{"title":"Hadoop环境搭建","date":"2018-06-20T02:20:02.000Z","path":"2018/06/20/Hadoop环境搭建/","text":"Hadoop通常有三种运行模式:本地(独立)模式、伪分布式(Pseudo-distributed)模式和完全分布式(Fully distributed)模式。 简介安装完成后,Hadoop的默认配置即为本地模式,此时Hadoop使用本地文件系统而非分布式文件系统,而且其也不会启动任何Hadoop守护进程,Map和Reduce任务都作为同一进程的不同部分来执行。因此,本地模式下的Hadoop仅运行于本机。此模式仅用于开发或调试MapReduce应用程序但却避免了复杂的后续操作。伪分布式模式下,Hadoop将所有进程运行于同一台主机上,但此时Hadoop将使用分布式文件系统,而且各jobs也是由JobTracker服务管理的独立进程。同时,由于伪分布式的Hadoop集群只有一个节点,因此HDFS的块复制将限制为单个副本,其secondary-master和slave也都将运行于本地主机。此种模式除了并非真正意义的分布式之外,其程序执行逻辑完全类似于完全分布式,因此,常用于开发人员测试程序执行。要真正发挥Hadoop的威力,就得使用完全分布式模式。由于ZooKeeper实现高可用等依赖于奇数法定数目(an odd-numbered quorum),因此,完全分布式环境需要至少三个节点 环境部署环境主机名称 IP 系统Master 192.168.2.79 centos6.5Slave 192.168.2.78 cenros6.5Slave1 192.168.2.77 cenros6.5 域名解析和关闭防火墙 (所有机器上)/etc/hosts192.168.2.79 master192.168.2.78 slave192.168.2.78 slave1关闭 selinuxsed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/sysconfig/selinuxsetenforce 0防火墙开放iptables-A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT-A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT-A INPUT -p tcp -s 192.168.2.0/24 -j ACCEPT 配置所有机器ssh互信创建密匙(每台都配置)[root@master ~]# ssh-keygen -t rsa 复制密匙(每台都配置)[root@master ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub master[root@master ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub slave[root@master ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub slave1 安装jdk和配置环境变量(三台同样配置)[root@master ~]# rpm -ivh jdk-8u91-linux-x64.rpm #安装jdkPreparing... ########################################### [100%]1:jdk1.8.0_91 ########################################### [100%]Unpacking JAR files...rt.jar...jsse.jar...charsets.jar...tools.jar...localedata.jar...jfxrt.jar...[root@master ~]# vim /etc/profile #设置java环境变量export JAVA_HOME=/usr/java/jdk1.8.0_91export JRE_HOME=/usr/java/jdk1.8.0_91/jreexport PATH=$PATH:$JAVA_HOME:$JRE_HOME[root@master ~]# java -version #查看java变量是否配置成功java version "1.8.0_91"Java(TM) SE Runtime Environment (build 1.8.0_91-b14)Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode) 安装并配置hadoop安装Hadoop并配置环境变量(master上)[root@master ~]# mkdir -p /web/soft[root@master ~]# mkdir -p /web/soft/hdfs/name /web/soft/hdfs/data /web/soft/hdfs/tmp[root@master ~]# cd /web/soft/ #上传hadoop压缩包到此文件夹[root@master ~]# tar xf hadoop-2.6.5.tar.gz && mv hadoop-2.6.5 hadoop[root@master ~]# vim / etc/profile #增加环境变量export PATH=$PATH:/web/soft/hadoop/bin 修改以下配置文件(所有文件均位于/web/soft/hadoop/etc/hadoop路径下)hadoop-env.sh# The java implementation to use.export JAVA_HOME=/usr/java/jdk1.8.0_91 #将JAVA_HOME改为固定路径export HADOOP_CONF_DIR=/web/soft/hadoop/etc/hadoop core-site.xml<configuration> <!-- 指定HDFS老大(namenode)的通信地址 --> <property> <name>fs.defaultFS</name> <value>hdfs://master:9000</value> </property> <!-- 指定hadoop运行时产生文件的存储路径 --> <property> <name>hadoop.tmp.dir</name> <value>/Hadoop/tmp</value> </property></configuration> hdfs-site.xml<configuration> <!-- 设置namenode的http通讯地址 --> <property> <name>dfs.namenode.http-address</name> <value>master:50070</value> </property> <!-- 设置secondarynamenode的http通讯地址 --> <property> <name>dfs.namenode.secondary.http-address</name> <value>slave:50090</value> </property> <!-- 设置namenode存放的路径 --> <property> <name>dfs.namenode.name.dir</name> <value>/Hadoop/name</value> </property> <!-- 设置hdfs副本数量 --> <property> <name>dfs.replication</name> <value>2</value> </property> <!-- 设置datanode存放的路径 --> <property> <name>dfs.datanode.data.dir</name> <value>/Hadoop/data</value> </property></configuration> mapred-site.xml[root@master hadoop]# mv mapred-site.xml.template mapred-site.xml<configuration> <!-- 通知框架MR使用YARN --> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration> yarn-site.xml<configuration> <!-- 设置 resourcemanager 在哪个节点--> <property> <name>yarn.resourcemanager.hostname</name> <value>master</value> </property> <!-- reducer取数据的方式是mapreduce_shuffle --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name> <value>org.apache.hadoop.mapred.ShuffleHandler</value> </property></configuration> mastersslave #这里指定的是secondary namenode 的主机 slavesslaveslave1 复制Hadoop安装目录及环境配置文件到其他主机master上:[root@master ~]# cd /web/soft[root@master ~]# scp -r /web/soft/hadoop slave:/web/soft[root@master ~]# scp -r /web/soft/hadoop slave1:/web/soft 启动Hadoop格式化名称节点(master)[root@master local]# hdfs namenode -format18/04/22 12:34:23 INFO common.Storage: Storage directory /Hadoop/name has been successfully formatted. #这行信息表明对应的存储已经格式化成功。18/04/22 12:34:23 INFO namenode.FSImageFormatProtobuf: Saving image file /Hadoop/name/current/fsimage.ckpt_0000000000000000000 using no compression18/04/22 12:34:24 INFO namenode.FSImageFormatProtobuf: Image file /Hadoop/name/current/fsimage.ckpt_0000000000000000000 of size 321 bytes saved in 0 seconds.18/04/22 12:34:24 INFO namenode.NNStorageRetentionManager: Going to retain 1 images with txid >= 018/04/22 12:34:24 INFO util.ExitUtil: Exiting with status 018/04/22 12:34:24 INFO namenode.NameNode: SHUTDOWN_MSG:/************************************************************SHUTDOWN_MSG: Shutting down NameNode at master/192.168.1.250************************************************************/ 启动[root@master ~# cd /web/soft/hadoop/sbin[root@master ~# ./start-all.sh master上[root@master sbin]# jps|grep -v Jps3746 ResourceManager3496 NameNode slave上[root@slave1 ~]# jps|grep -v Jps3906 DataNode4060 NodeManager3996 SecondaryNameNode Slave1上[root@slave2 ~]# jps|grep -v Jps3446 NodeManager3351 DataNode 测试查看集群状态[root@master sbin]# hdfs dfsadmin -report 测试YARN可以访问YARN的管理界面,验证YARN,如下图所示:http://ip:8088/cluster 测试向hadoop集群系统创建一个目录并上传一个文件[root@master ~# hdfs dfs -mkdir -p /Hadoop/test[root@master ~]# hdfs dfs -put install.log /Hadoop/test[root@master ~]# hdfs dfs -ls /Hadoop/testFound 1 items-rw-r--r-- 2 root supergroup 28207 2018-04-22 16:48 /Hadoop/test/install.log 本文转载来自 [http://yangxx.net/?p=2545]","tags":[{"name":"Hadoop","slug":"Hadoop","permalink":"http://www.updatecg.xin/tags/Hadoop/"}]},{"title":"利用HDFS、RabbitMQ、MongoDB实现统计","date":"2018-06-20T01:50:02.000Z","path":"2018/06/20/利用HDFS、RabbitMQ、MongoDB实现统计/","text":"IPTV行业桌面数据分析,分析用户行为数据。每天数据量可达3000万。统计用户访问量(PV)、UV(独立用户)、VV(视频播放次数)、DAU(日活)、WAU(周活)、MAU(月活)、月开机率、点击次数、排行榜数据等等。 架构设计 client上报数据存入缓存中 定时将缓存的字符流刷新到文件,并将文件上传到hdfs 通过mq 客户端发送至服务端 mq服务端监听到hdfs进行处理( 将字节数组反序列化为实体Bean) 将其实体Bean写入mongo数据库 利用mongoDB聚合函数aggregate()查询(分库分表) 环境部署HADOOP环境搭建:[Hadoop环境搭建地址]RabbitMQ环境搭建:[RabbitMQ环境搭建地址] 部分代码客户端收集数据@RequestMapping(value = \"/singleData/{policyId}\") public ResponseData postSingleData(@PathVariable(\"policyId\") String policyId, HttpServletRequest resuest) { ResponseData response = getResponseData(); try { ServletInputStream inputStream = resuest.getInputStream(); StringBuffer sb = new StringBuffer(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, \"utf-8\")); String data = null; while ((data = bufferedReader.readLine()) != null) { sb.append(data); sb.append(\"\\n\"); } if (sb.length() <= 0) { throw new Exception(\"请提供提交数据\"); } DataStr s = new DataStr(policyId, sb); service.receiveNewData(s); } catch (Exception e) { response.setCode(0); response.setMsg(e.getMessage()); } return response; } 定时将缓存的字符流刷新到文件private void flushData() throws Exception { Queue<DataStr> dataCache = DataPond.getDataCache(); DataStr dataIte = null; Integer size = dataCache.size(); logger.info(\"There are [\" + size + \"] of datas in queue\"); while (size > 0 && (dataIte = dataCache.poll()) != null) { String policyId = dataIte.getPolicyId(); Map<String, FileStruct> fileCache = DataPond.getFileCache(); FileStruct fileStruct = fileCache.get(policyId); if (fileStruct == null) { fileStruct = new FileStruct(policyId); fileCache.put(policyId, fileStruct); } fileStruct.write(dataIte.getData().toString()); fileStruct.write(\"\\n\"); size--; } } 文件上传到hdfs,并通过mq发送private void uploadFilesAndSendToMQ() throws Exception { /* 遍历文件 */ Map<String, FileStruct> fileCache = DataPond.getFileCache(); Set<String> keySet = fileCache.keySet(); for (String key : keySet) { FileStruct fs = fileCache.get(key); /* 标记是否可以被flush,并上传hdfs */ Boolean shallBeFlush = false; if (fs.getFielSize() >= SystemChangeableConstant.MAX_FILESIZE) { shallBeFlush = true; } if (System.currentTimeMillis() - fs.getLastUpdateTime() >= SystemChangeableConstant.MAX_FILE_NOACTION_TIMESPAN) { shallBeFlush = true; } if (shallBeFlush) { if (!hdfsUtil.isOpened()) { //TODO 临时获取hadoop环境变量 System.setProperty(\"hadoop.home.dir\", \"D:\\\\hadoop-2.6.5\"); hdfsUtil.open(); } logger.info(\"File of policy [\" + key + \"] is full and will send out!\"); fs.flush(); try { transferFileToDfs(fs); logger.info(\"File of policy [\" + key + \"] send to hdfs success\"); } catch (Exception e) { logger.error(\"File of policy [\" + key + \"] send to hdfs fail as \" + e.getMessage()); } try { sendToMq(fs); logger.info(\"File of policy [\" + key + \"] send to MQ success\"); } catch (Exception e) { logger.error(\"File of policy [\" + key + \"] send to MQ fail as \" + e.getMessage()); } fileCache.remove(key); fs.destroy(); } } hdfsUtil.close(); } mq服务端监听到hdfs进行处理/* 提取command放入线程对象 */ Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { logger.info(\"Router [\" + SystemConstant.NEW_FILE_QUEUE + \"] received new file\"); FileStruct fileStruct = (FileStruct) SerializingUtil.deserialize(body); factory.createNewTask(fileStruct); } }; channel.basicConsume(SystemConstant.NEW_FILE_QUEUE, true, consumer); 数据入mongo for (HashMap aMap : parsedDatas) { // 预置的默认时间列 aMap.put(DataTable.DEFAUL_TTIMECOLUMN, now); dataTable.genId(aMap); String tableName = dataTable.getSplitTableName(aMap); Document doc = new Document(aMap); if (dataTable.hasIdentify()) { mongoConnector.insertSingle(tableName, doc); } else { List<Document> list = tableToDatasMap.get(tableName); if (list == null) { list = new ArrayList<Document>(parsedDatas.size() / 2); tableToDatasMap.put(tableName, list); } list.add(doc); }}for (String key : tableToDatasMap.keySet()) { mongoConnector.insert(key, tableToDatasMap.get(key));} 聚合分析public List<Map<String, Object>> getExhibitionCount(Date start , Date end ) throws ParseException { List<Map<String, Object>> map = new ArrayList<Map<String, Object>>(); //获取区间日期天数 int days = differentDaysByMillisecond(start, end); for (int i = 0 ;i < days + 1 ; i++){ Aggregation aggregation = null; aggregation = Aggregation.newAggregation( Aggregation.group(\"templateCode\",\"columnId\",\"positionId\").sum(\"duration\").as(\"duration\"), Aggregation.project(\"templateCode\",\"columnId\",\"positionId\",\"duration\") ); String tableName = this.getTableName(start); AggregationResults<HashMap> aggregate = mongoTemplate.aggregate(aggregation, tableName, HashMap.class); //key 日期 value 当天数据 List<HashMap> mappedResults = aggregate.getMappedResults(); System.out.print(\"处理后数据:\" + mappedResults); for (Map adCountDtoMap:mappedResults) { String json = JsonUtils.obj2Str(adCountDtoMap); AdCountDto adCountDto = JsonUtils.str2Obj(json, AdCountDto.class); adCountDto.setDate(dateFormatCheck.format(new Date())); createIndex(COL_NAME); mongoTemplate.insert(adCountDto,COL_NAME); } start = dataPlus(start ,1); } return map; } 展示","tags":[{"name":"Hadoop","slug":"Hadoop","permalink":"http://www.updatecg.xin/tags/Hadoop/"}]},{"title":"RabbitMQ环境","date":"2018-06-19T03:50:02.000Z","path":"2018/06/19/RabbitMQ环境/","text":"简介 RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗.具体特点包括: 可靠性(Reliability)灵活的路由(Flexible Routing)消息集群(Clustering)高可用(Highly Available Queues)多种协议(Multi-protocol)多语言客户端(Many Clients)管理界面(Management UI)跟踪机制(Tracing)插件机制(Plugin System) 基础编译环境[root@update ~] yum install gcc glibc-devel make ncurses-devel openssl-devel xmlto 安装ERLANG下载安装包,地址http://www.erlang.org/downloads,我选择的是otp_src_20.2.tar.gz(Erlang)。[root@update ~]# tar -xzvf otp_src_20.2.tar.gz[root@update ~]# cd otp_src_20.2/ 配置安装路径编译代码:[root@update ~]# ./configure --prefix=/root/rabbitMQ/Erlang/ --without-javac 执行编译结果:[root@update ~]# make && make install 进入/opt/erlang查看执行结果[root@update ~]# cd /root/rabbitMQ/Erlang/[root@update ~]# erl 配置Erlang环境变量,vim /etc/profile文件,增加下面的环境变量:#set erlang environmentexport PATH=$PATH:/root/rabbitMQ/Erlang/bin[root@update ~]# source /etc/profile使得文件生效 安装RABBITMQ下载安装包地址:http://www.rabbitmq.com/releases/rabbitmq-server/解压文件到/root/rabbitMQ/[root@update ~]# tar -xvf rabbitmq-server-generic-unix-3.6.15.tar 配置rabbitmq环境变量,vim /etc/profile文件,增加下面的环境变量:#set rabbitmq environmentexport PATH=$PATH:/root/rabbitMQ/rabbitmq_server-3.6.15/sbin[root@update ~]# source /etc/profile使得文件生效 RABBITMQ服务启动关闭防火墙开放防火墙可以防止节点和CLI工具相互通信。确保可以打开以下端口:4369:epmd, RabbitMQ节点和CLI工具使用的对等发现服务5672,5671: 由AMQP 0-9-1和1.0客户端使用25672: 由Erlang分配用于节点间和CLI工具通信,并且从动态范围分配(AMQP端口+20000)15672: HTTP API客户端和rabbitmqadmin(仅当管理插件启用时)61613,61614:STOMP客户端没有和使用TLS(只有STOMP插件已启用)1883年,8883:(MQTT客户端没有和TLS,如果MQTT插件已启用)15674:STOMP-over-WebSockets客户端(仅在启用了Web STOMP插件的情况下)15675:MQTT-over-WebSockets客户端(仅在启用了Web MQTT插件的情况下) RabbitMQ已经安装完成,最后测试[root@update ~]# rabbitmq-plugins enable rabbitmq_management(开启管理页面)[root@update ~]# rabbitmq-server 浏览器中访问管理页面:http:// :15672/ RabbitMQ创建用户并赋权[root@update ~]# rabbitmqctl add_user root root[root@update ~]# rabbitmqctl set_user_tags root administrator[root@update ~]# rabbitmqctl set_permissions -p / root '.*' '.*' '.*'<!-- 后台启动 -->[root@update ~]# rabbitmq-server start -detached<--停止-->[root@update ~]# rabbitmqctl stop","tags":[{"name":"RabbitMQ","slug":"RabbitMQ","permalink":"http://www.updatecg.xin/tags/RabbitMQ/"}]},{"title":"命令性能之处","date":"2018-03-11T09:03:02.000Z","path":"2018/03/11/命令性能之处/","text":"命令性能之处项目运行上线会出现意想不到的性能问题,随着项目数据量越来越大,各种问题随之抛出。之前的文章《CPU飙高时,这样玩》【http://www.jianshu.com/p/90579ec3113f》也介绍了性能方面查询的命令。 未完待续…","tags":[{"name":"Tomcat","slug":"Tomcat","permalink":"http://www.updatecg.xin/tags/Tomcat/"},{"name":"Linux","slug":"Linux","permalink":"http://www.updatecg.xin/tags/Linux/"}]},{"title":"Nginx开启Gzip压缩 大幅增加访问速度","date":"2017-10-24T08:23:02.000Z","path":"2017/10/24/Nginx开启Gzip压缩 大幅增加访问速度/","text":"图片大小过大导致加速速度变慢 Nginx打开Gzip 修改nginx.confgzip on;gzip_min_length 1k;gzip_buffers 4 16k;#gzip_http_version 1.0;gzip_comp_level 2;gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;gzip_vary off;gzip_disable \"MSIE [1-6]\\.\"; 解释各行 第1行:开启Gzip 第2行:不压缩临界值,大于1K的才压缩,一般不用改 buffer,就是,嗯,算了不解释了,不用改 用了反向代理的话,末端通信是HTTP/1.0,有需求的应该也不用看我这科普文了;有这句的话注释了就行了,默认是HTTP/1.1 压缩级别,1-10,数字越大压缩的越好,时间也越长,看心情随便改吧 进行压缩的文件类型,缺啥补啥就行了,JavaScript有两种写法,最好都写上吧,总有人抱怨js文件没有压缩,其实多写一种格式就行了 跟Squid等缓存服务有关,on的话会在Header里增加”Vary: Accept-Encoding”,我不需要这玩意,自己对照情况看着办吧 IE6对Gzip不怎么友好,不给它Gzip了 wq保存退出,重新加载Nginx/usr/local/nginx/sbin/nginx -s reload 验证是够开始Gzip未开启Gzip 开启Gzip 如果出现了Content-encoding:gzip 则表示gzip以及压缩。图片1.4MB的图片已压缩至1.3MB. 这里压缩的数据类型是json数据,所以需要将application/json 添加到gzip_types中 对于Tomcat####修改server.xml<Connector port=\"8080\" protocol=\"HTTP/1.1\" connectionTimeout=\"20000\" compression=\"on\" // 打开压缩功能 (on|off) compressionMinSize=\"2048\" // 启用压缩的输出内容大小,这里面默认为2KB noCompressionUserAgents=\"gozilla, traviata\" // 对于这里配置的浏览器,不启用压缩 compressableMimeType=\"text/html,text/xml,text/plain,text/css,application/javascript\" //对哪些文件类型启用压缩 useSendfile=\"false\" //useSendfile属性默认为true, 会禁用任何可能的压缩, 改成false就好了/>","tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.updatecg.xin/tags/Nginx/"}]},{"title":"每个熬夜的人,其实都是想死","date":"2017-09-29T01:10:02.000Z","path":"2017/09/29/每个熬夜的人,其实都是想死/","text":"熬不起的夜 如今,似乎全世界的年轻人之间正流行着一种病态的风气——熬夜。一时间,“睡你麻痹,起来嗨”成了夜猫子呼朋唤友的讯号。 从通宵唱K,泡吧,到凌晨三点半的朋友圈,“熬夜”似乎成了一种新风尚。 它畸形的地方在于,所有人都知道晚睡会影响健康,但仍有大批青年走火入魔似的坚持“修仙”。“珍爱生命,远离早睡”,成了许多“熬夜党”的说辞。 越来越多人把“生前何必多睡,死后必然长眠”挂在嘴边,81%的中国人睡眠都不够8小时。 《武林外传》里佟湘玉细算过一笔账,如果她每天只睡3个小时,那么节省下来的时间长达十二万零八千个小时,约等于五千三百天,也就是十四年零六个月。听起来是可以做很多事,创造很多价值。 但事实并非如此。 英国科学家贝弗里奇曾解释:疲劳过度的人是在追逐死亡。睡得少,并不意味着你比别人拥有更多清醒的时间,反而可能让你死的更早。 近日,网友@卡卡Prancil 在微博分享自己突发脑出血失联9天的经历,给了众多“熬夜党”一记响亮的耳光。 9月2日的早晨,博主在毫无预兆的情况下后脑勺剧烈疼痛,并冷汗直流。在迷迷糊糊被送往医院之后,医院ct扫描结果显示脑内多处出血。万幸的是主血管无事。 在失联的9天里,她经历了可怕的绝对卧床治疗及禁食。每6个小时打一组药,每天20多瓶,止血、吸收水肿、冲脑。 因为颅压高,她几乎吃什么都会引起呕吐,吐到黄胆水都出来,还伴随着不定期的间歇性头疼。 博主自述这段地狱般的体验最后,给出了3点忠告: 一定要规律作息,朝六晚十。熬夜的伤害其实长期潜伏在你的身体里,你永远都不知道什么时候会爆发。 一定要会调节压力。工作上越来越给力,收入越来越高,所以越来越忙,但一定要权衡,毕竟钱换不回来命。 一定要多多运动,不能老是躺着。不然血管会很脆弱,存在巨大隐患。 看完后,不禁头皮发麻,博主所描述的生活状态只能用“是我本人”来概括了。 △ 熬夜这么高风险的事,却有一堆人前仆后继赶着做。 就像骂胖子会让胖子更胖一样,人越知道熬夜伤身越容易熬夜。人们往往越是责怪自己熬夜,越容易产生“既然改不了,不如就这样了”的暗示。鼓励的口号喊多了,也就仅仅是个口号。 大多数人的意志力并不坚定,该睡觉的时候不睡觉,夜晚过着西八区人民的生活。床成了奢侈品,年轻人都不舍得在上面睡觉。中国睡眠研究会等联合发布的《2017年中国青年睡眠状况白皮书》 数据显示,有4成人患上“晚睡拖延症”。在睡觉这件事上,22.8%的人是拖延癌晚期,25.1%经常会迟迟不想结束这一天。 白天困成狗,晚上嗨不够。 而手机、iPad、电脑等可谓是睡眠的最大杀手,93.8%的人在睡前与电子产品难舍难分,不愿入睡。 不少学生党,熄灯以前在桌子上玩手机,熄灯之后到床上玩手机。刷完这条微博再睡,却刷到了天亮。 熬夜有害健康,不如一起通宵。 一学期下来,他们便陷入了因平时熬夜耽误学习,所以熬夜准备考试论文的恶性循环。 这就是当代中国的大学生活。 其实走出校园,熬夜依然是社会人的通病。2017年亚马逊中国睡眠地图的数据显示:中国接近6成人通常在深夜11点到凌晨1点入睡。 快节奏的时代脚步,造就了越来越多“晚上不睡,早上不起”的悲剧。“多睡2分钟,迟到1小时”,一个月的全勤奖泡了汤。 一方面,工作压榨了私人时间,不少上班族不得不牺牲睡眠时间来弥补这一空缺。 另一方面,大家都讨厌被工作填满的生活,眼皮再重,也要补完上周的影视节目。 都知道早睡早起身体好,只是没办法真的做到。 △ 熬夜带来的危害,补觉并不能挽回。 身体被消耗殆尽,恶果也在不知不觉中显现出来,高血压、心脏病、糖尿病风险都会加大,同时熬夜会导致性冲动低下,平衡感变差。从夜晚偷来的时间,骗得了自己骗不了身体。 1、变胖变丑 “这世上本没有丑女人,只有熬夜变丑的女人。” 微博博主@设计系奶子曾有一组漫画,《用最贵的护肤品,熬最晚的夜》,非常生动地展示了女生一边护肤一边熬夜的矛盾状态。长时间不好好休息,会影响内分泌,熬夜冒痘,面色黯淡无光,花大价钱买多少护肤品都补救不了。 熬夜导致的新陈代谢减慢,更会让人发胖。等到S码的衣服都穿不了,才悔之晚矣。 2、变笨变迟钝 加拿大神经科学家发现,熬夜会影响人第二天的发散思维。 举例说:一个正常情况下文思泉涌的人,熬夜之后,可能会思维迟钝,那些有才华的细胞都还没睡醒。 交感神经在夜晚保持兴奋,到了白天就会出现没精神、头昏脑涨、记忆力减退等症状。时间长了,还可能遇到神经衰弱、失眠等问题。 3、患癌几率变高 越来越多的研究发现,熬夜和很多心理疾病有明显关联,精神分裂症、焦虑、成瘾等。 而且熬夜的人比睡眠正常的人更容易患上癌症,以胰腺癌为例,经常熬夜的人发病率要比一般人高出3倍多。而胰腺癌,被称为癌症之王,是所有恶性肿瘤治疗后康复情况最差的癌症,乔布斯就死于此癌症。 看似没多大影响的熬夜,正把我们推进死亡的加速跑道。 △ 有人熬夜努力,有人熬夜装逼。 睡眠时间完全被工作霸占而导致不得不熬夜的大有人在。 某社交平台于2016年发布的《中国网民熬夜报告》数据显示,熬夜族占比最多的行业包括公关、媒体、游戏、动漫、投资等,这些人凌晨3点睡觉都是常事。做不完的工作,熬不完的夜。 10点半的西二旗,在许多人飞奔着去赶4号线末班车的同时,周围互联网公司的大楼却依然灯火通明。可熬夜到最后,却熬掉了性命。 2015年3月24日,深圳36岁IT男张斌,被发现猝死在公司租住的酒店马桶上。去世前一天,张斌曾对妈妈说“太累了”。 除此之外,《生命时报》曾总结过中国的“十大高猝死行业”,广告从业者、医生、工程师、媒体从业者等赫然在列。 中国平均每天有上千人猝死,而熬夜、睡眠不足、过度劳累等危险因素极大提高了猝死概率。 1月9日,石河子市人民医院一名麻醉科医生,在值班过程中猝死; 2月10日,河北省某县医院一名年仅39岁的医生“连续24小时上班”发生猝死; …… 据不完全统计,从2017年初到7月,医疗界已经发生数十起医生猝死事件。 长期熬夜、过度劳累,救得了他人的医生,也救不了自己。没谁愿意被工作压迫,但每个人又不得不为了生活而奔波。这部分人,熬夜是无可奈何。 但对另一部分熬夜党来说,真正杀死他们的可能不是成堆的活,而是拖延和低效。 恰如槽值小妹的同事王三三自嘲的那样:不能拖延了,一定要工作了,在开始工作之前会发现很多有趣的事情,今天的朋友圈很有意思,指甲有点儿长,突然有点儿饿,私人FM怎么这么好听……不能拖延了,一定要工作了,不能胡思乱想……不好,我已经开始胡思乱想了…… 追剧玩游戏一直有时间,却偏把要事拖到后半夜。复习一定要在考试前一晚,工作一定要拖到deadline,还不忘发个朋友圈自恋,晒晒自己多努力。再玩一把就睡、再看一集就睡、再聊一会儿就睡……拖延症,已经是这个世纪传染力最强的精神流感。 △ 夜晚容易孤独,生命害怕被辜负,睡眠让人有罪恶感。 北京大学副教授、精神动力学专家徐凯文说:“空心感是这个时代的通病。” “夜里不睡的人,白天多多少少总有什么逃避掩饰的吧。白昼解不开的结,黑夜慢慢耗。”作家雷蒙德.卡佛关于黑夜的一句低语,成了很多人日常生活的写照。 之所以熬夜,是因为你的意识深处,深知今天丝毫没有进步,觉得日子空洞,害怕今天的结束,更害怕明天的到来。 连海明威也曾在言语中表达过那种对失眠的、空虚的人生的绝望感。“我同情所有不想上床睡觉的人,同情所有夜里需要亮光的人。” 急于求变的挑灯夜战难以取得效果,反而会加深焦虑。支撑自己能走得更远的,也永远不会是48小时不睡觉的热情。 想起之前看到一个禅学小故事: 徒弟问师父:“开悟之前你都在做什么?” 师父说:“吃饭,挑水,睡觉。” 徒弟继续问:“那开悟之后你又在做什么?” 师父说:“吃饭,挑水,睡觉。” 徒弟疑惑:“那开不开悟有什么区别?” 师父最后说:“以前我吃饭的时候想着还有水没挑完,挑水的时候想着赶紧做完好回去睡觉,睡觉的时候又在想明天应该吃些什么。但现在,吃饭的时候就想着吃饭,挑水的时候就想着挑水,睡觉的时候就想着睡觉,只做好眼前的事情,而不为其他事情分心。” 如果白天是金币,夜晚是银币。金币消耗完,要记得留一些银币,给自己留一些余地。 该睡觉的时候就去睡觉吧,不要忘了,“年轻人不要老熬夜”,这可是来自中央的最高指示。 资料来源: [1]Michiaki Nagai,Satoshi Hoshide,and Kazuomi Kario:Sleep Durationas a Risk Factor for Cardiovascular Disease- a Review of the RecentLiterature.National Institutes of Health,2010 Feb 6(1): 54–61 [2]人民网:我国每年180万人猝死,这些猝死的征兆要留意.健康时报,2017.5.1 [3]中国网民熬夜报告,2016 [4]维基百科:睡眠剥夺对健康的主要影响,2012 [5]2017年中国青年睡眠状况白皮书,2017 [6]2017年亚马逊中国睡眠地图,2017 [7]人民网:最容易猝死行业TOP10,躺枪的举手.生命时报,2016.10.17 作者:槽值链接:http://www.jianshu.com/p/ab58861d89c7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。","tags":[{"name":"岁月神偷","slug":"岁月神偷","permalink":"http://www.updatecg.xin/tags/岁月神偷/"}]},{"title":"SpringBoot下ELK+KAFKA+SpringAop日志分析","date":"2017-09-25T03:38:02.000Z","path":"2017/09/25/SpringBoot下ELK+KAFKA+SpringAop日志分析/","text":"设想背景 公司项目数据庞大,每天所产生的日志非常大,想要更好的通过日志查找直接原因,并且实现高可用。本文利用ELK(elasticsearch、logstash、kibana)+KAFKA搭建日志系统。利用SpringBoot+SpringAop进行日志采集。通过kafka发送日志给logstash,logstash将日志写入elasticsearch,然后通过kibana将放在elasticsearch显示出来,并且可以进行对接实现实现实时的数据图形分析。 Log4j日志利用log4j实现日志记录,是不错的一种方案。但当查找问题时,就需要运维的大力协作。如果日志量很大,查找问题就比较困难。如果要实现高可用的话,就必须实现分布式部署。如果部署N台机子就会产生N个日志目录。如果一个用户出现问题,不方便找到是哪一个出了问题。 日志入数据库将日志存入数据库虽然避免了找问题的难度,但是也避免不了缺陷。 log记录日志过量,日积月累,一张表明显不够用,就的采用分表分库 连接数据库异常是避免不了的,这就会造成log丢失 ELK(elasticsearch、logstash、kibana)ELK处理日志方式足以解决现在问题。流程图: 使用elasticsearch来存储日志信息,对一般系统来说可以理解为可以存储无限条数据,因为elasticsearch有良好的扩展性,然后是有一个logstash,可以把理解为数据接口,为elasticsearch对接外面过来的log数据,它对接的渠道,有kafka,有log文件,有redis等等,足够兼容N多log形式,最后还有一个部分就是kibana,它主要用来做数据展现,log那么多数据都存放在elasticsearch中,我们得看看log是什么样子的吧,这个kibana就是为了让我们看log数据的,但还有一个更重要的功能是,可以编辑N种图表形式,什么柱状图,折线图等等,来对log数据进行直观的展现。 EKL分工 logstash做日志对接,接受应用系统的log,然后将其写入到elasticsearch中,logstash可以支持N种log渠道,kafka渠道写进来的、和log目录对接的方式、也可以对reids中的log数据进行监控读取,等等。 elasticsearch存储日志数据,方便的扩展特效,可以存储足够多的日志数据。 kibana则是对存放在elasticsearch中的log数据进行:数据展现、报表展现,并且是实时的。 离线文件下载运行环境:zookeeper3.3.6+kafka2.1+logstash2.3.4+elasticsearch2.3.3+kibana-4.5.4-windows+jdk8技术:spring 4.3.3 + kafka2.1 + jdk8链接:https://pan.baidu.com/s/1boOm0wB 密码:mugq 配置文件配置Logstash配置可以配置接入N多种log渠道,现状我配置的只是接入kafka渠道。配置文件在\\logstash-2.3.4\\config目录下//数据来源。 topic_id kafka的topicinput { kafka { zk_connect => \"127.0.0.1:2181\" topic_id => \"cw_topic\" }}filter { #Only matched data are send to output.}//数据存储到哪里。elasticsearch的日志收集项cw_logsoutput { #stdout{} # For detail config for elasticsearch as output, # See: https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html elasticsearch { action => \"index\" #The operation on ES hosts => \"127.0.0.1:9200\" #ElasticSearch host, can be array. index => \"cw_logs\" #The index to write data to. }} Elasticsearch配置配置文件在\\elasticsearch-2.3.3\\config目录下的elasticsearch.yml,可以配置允许访问的IP地址,端口等,但我这里是采取默认配置。 Kibana配置配置文件在\\kibana-4.5.4-windows\\config目录下的kibana.yml,可以配置允许访问的IP地址,端口等,但我这里是采取默认配置。 这里有一个需要注意的配置,就是指定访问elasticsearch的地址。我这里是同一台机子做测试,所以也是采取默认值了。# The Elasticsearch instance to use for all your queries.# elasticsearch.url: \"http://localhost:9200\" SpringBoot利用Aop拦截日志发送至Kafka代码结构图: 注意:配置pom.xml,Spinrg-kafka版本需和kafka服务一致 <!--注入kafka--> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>1.0.5.RELEASE</version> </dependency> <!--注入aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency> 部分SpringAop代码/*** Created by cw on 2017/12/22. good day.* @Author: Chen Wu* Blog: http://www.updatecg.xin* <p>Discription:[后置通知,扫描com.androidmov.adManagement.maker.controllers包及此包下的所有带有controllerLogAnnotation注解的方法]</p>* @param joinPoint 前置参数* @param controllerLogAnnotation 自定义注解*/@After((\"execution(* com.androidmov.adManagement.maker.controllers..*.*(..)) && @annotation(controllerLogAnnotation)\"))public void doAfterAdviceController(JoinPoint joinPoint, ControllerLogAnnotation controllerLogAnnotation){ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // HttpSession session = request.getSession(); //请求IP String ip = request.getRemoteAddr(); try{ log.info(\"===========Controller 前置通知开始执行==============\"); log.info(\"请求方法:\" + (joinPoint.getTarget().getClass().getName() + \".\" +joinPoint.getSignature().getClass().getName() + \"()\")); log.info(\"方法描述:\" + getMethodDescription(joinPoint,true)); log.info(\"IP:\" + ip); String value = controllerLogAnnotation.description(); kafkaTemplate.send(\"mylog_topic\", \"key_controller\", value); }catch (Exception e){ //记录本地异常日志 log.error(\"==前置通知异常==\"); log.error(\"异常信息:{}\", e.getMessage()); }} application.properties中相关kafak配置#============== kafka ===================kafka.consumer.zookeeper.connect=localhost:2181kafka.consumer.servers=localhost:9092kafka.consumer.enable.auto.commit=truekafka.consumer.session.timeout=6000kafka.consumer.auto.commit.interval=100kafka.consumer.auto.offset.reset=latestkafka.consumer.topic=mylog_topickafka.consumer.group.id=mylog_topickafka.consumer.concurrency=10 通过kafka发送日志给logstash/*** Created by cw on 2017/9/27. good day.*/@Configuration@EnableKafkapublic class KafkaProducerConfig { @Value(\"${kafka.producer.servers}\") private String servers; @Value(\"${kafka.producer.retries}\") private int retries; @Value(\"${kafka.producer.batch.size}\") private int batchSize; @Value(\"${kafka.producer.linger}\") private int linger; @Value(\"${kafka.producer.buffer.memory}\") private int bufferMemory; public Map<String, Object> producerConfigs() { Map<String, Object> props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); props.put(ProducerConfig.RETRIES_CONFIG, retries); props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); props.put(ProducerConfig.LINGER_MS_CONFIG, linger); props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); return props; } public ProducerFactory<String, String> producerFactory() { return new DefaultKafkaProducerFactory<>(producerConfigs()); } /** * 记录日志 * 使用elasticsearch来存储日志信息, * 为elasticsearch对接外面过来的log数据, * 它对接kafka, * 最后还有一个部分就是kibana,它主要用来做数据展现。 * @param key * @param msg */ @Bean public KafkaTemplate<String, String> kafkaTemplate() { return new KafkaTemplate<String, String>(producerFactory()); }} ELK,kafka、aop之间的关系 aop对日志进行收集,然后通过kafka发送出去,发送的时候,指定了topic(在spring配置文件中配置为 topic=”cw_topic”) logstash指定接手topic为 cw_topic的kafka消息(在config目录下的配置文件中,有一个input的配置) 然后logstash还定义了将接收到的kafka消息,写入到索引为my_logs的库中(output中有定义) 再在kibana配置中,指定要连接那个elasticsearch(kibana.yml中有配置,默认为本机) 最后是访问kibana,在kibana的控制台中,设置要访问elasticsearch中的哪个index。 后期补上测试数据 对应代码分享在 [https://github.com/UpdateCw/SpringBoot/tree/20171222]本文参考文献 [http://www.demodashi.com/demo/10181.html]","tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"http://www.updatecg.xin/tags/SpringBoot/"},{"name":"Kafka","slug":"Kafka","permalink":"http://www.updatecg.xin/tags/Kafka/"},{"name":"ELK","slug":"ELK","permalink":"http://www.updatecg.xin/tags/ELK/"}]},{"title":"monggo索引优化","date":"2017-09-23T06:27:02.000Z","path":"2017/09/23/monggo索引优化/","text":"mongodb索引规则 mongodb索引规则基本上与传统的关系库一样,大部分优化MySQL/Oracle/SQLite索引的技巧也适用于mongodb。 为什么用索引 当查询中用到某些条件时,可以对该键建立索引,以提高查询速度。 数据量多且查询多余更新时,可以用索引提高查询速度。 废话不多说,先上图,后再说 表总数据2000万+ 查询表索引情况 查询在没有建立索引情况下执行 对需要过滤字段建立索引(此时数据库拥有2000万+数据,执行时间89s) 查询表索引情况(已建立) 查询在建立了索引情况下执行 (^▽^)建立了索引查询2000万数据速度从30秒减少到1毫秒,这效率牛逼了!注意:建立索引尽量在创建表时创建,不然当表数据过大,创建索引会消耗大量时间和cpu 有点影响,但是不大当然使用索引是也是有代价的:对于添加的每一条索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据发生变化时,不仅要更新文档,还要更新级集合上的所有索引。因此,mongodb限制每个集合最多有64个索引。 常用操作唯一索引db.launcher4k20170628.ensureIndex({"name":1},{"unique":true}) 单列索引db.launcher4k20170628.ensureIndex({positionCode:1},{name:'positionCode_index'}) 复合索引索引的值是按一定顺序排列的,所以使用索引键对文档进行排序非常快。db.launcher4k20170628.ensureIndex({positionCode:1,launcherType:1},{name:'position_index'}) 查看我们建立的索引db.launcher4k20170628.getIndexes() 删除索引//删除单个db.launcher4k20170628.dropIndex('name')//删除全部db.launcher4k20170628.dropIndex('*') 使用explainexplain是非常有用的工具,会帮助你获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。explain会返回一个文档,而不是游标本身。如:explain会返回查询使用的索引情况,耗时和扫描文档数的统计信息。 项目实践 统计功能,每天凌晨定时从mongo统计数据到mysql。但是随着数据量增大,最高数据达到9000万+数据,要对这如此庞大数据进行统计,避免不了使用索引以及mongo聚合函数。 因之前没有添加索引,造成了一诸多问题。例如之前文章中提到的[MongoDB 查询超时异常 SocketTimeoutException]经常造成连接池杠不住,以及查询时长过长造成连接不上monggo等问题。org.springframework.dao.DataAccessResourceFailureException: Read operation to server 172.23.5.7:9343 failed on database launcher4k; nested exception is com.mongodb.MongoException$Network: Read operation to server 172.23.5.7:9343 failed on database launcher4k at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:59) at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:1926) at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:396) at org.springframework.data.mongodb.core.MongoTemplate.executeCommand(MongoTemplate.java:336) at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1425) at org.springframework.data.mongodb.core.MongoTemplate.aggregate(MongoTemplate.java:1360) at com.amt.modules.dao.OprationDaoImpl.getCountsTime(OprationDaoImpl.java:123) at com.amt.modules.service.OprationServiceImpl.saveCountsTime(OprationServiceImpl.java:379) at com.amt.modules.common.ContentTaskMethod.moveDateMongoToMysql(ContentTaskMethod.java:56) at com.amt.modules.common.SystemTask.createStatisticData(SystemTask.java:45) at sun.reflect.GeneratedMethodAccessor2305.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 可以通过配置mongo文件进行优化,但这不是长远之计。mongo.connectionsPerHost=8mongo.threadsAllowedToBlockForConnectionMultiplier=4mongo.connectTimeout=1000mongo.maxWaitTime=2000 //最大连接时长 秒为单位mongo.autoConnectRetry=truemongo.socketKeepAlive=truemongo.socketTimeout=0mongo.slaveOk=true 这就需要索引来大大的调高查询速度 创建联合索引 //方法过时了 private void createIndex(String name){ IndexOperations io=mongoTemplate.indexOps(name); Index index =new Index(); index.on(\"positionCode\",Order.ASCENDING); index.on(\"positionName\",Order.ASCENDING); index.on(\"launcherType\",Order.ASCENDING); index.on(\"terminalId\",Order.ASCENDING); index.on(\"labelId\",Order.ASCENDING); io.ensureIndex(index); } //正常 private void createIndex(String name){ DBObject indexOptions = new BasicDBObject(); indexOptions.put(\"positionCode\", 1); indexOptions.put(\"positionName\", 1); indexOptions.put(\"launcherType.d\", 1); indexOptions.put(\"labelId\", 1); CompoundIndexDefinition indexDefinition =new CompoundIndexDefinition(indexOptions); mongoTemplate.indexOps(name).ensureIndex(indexDefinition);} public void create(OprationEntity opration) throws Exception { mongoTemplate.insert(opration, getTableName(opration.getUpdateTime())); this.createIndex(getTableName(opration.getUpdateTime())); } 创建单列索引/** * 添加索引 * @param string */@SuppressWarnings(\"deprecation\")private void createIndex(String name){ IndexOperations io=mongoTemplate.indexOps(name); io.ensureIndex(new Index().on(\"positionCode\", Order.ASCENDING)); io.ensureIndex(new Index().on(\"positionName\", Order.ASCENDING)); io.ensureIndex(new Index().on(\"launcherType\", Order.ASCENDING)); io.ensureIndex(new Index().on(\"labelId\", Order.ASCENDING));} 注意:在测试创建索引是否成功我手动去删了库,然后执行创建索引就创建无效。但是如果新创建表为其创建索引的,就会生成索引值。 [mongoTemplate参考文档地址] 转载请注明出处:[www.updatecg.xin]","tags":[{"name":"数据库优化","slug":"数据库优化","permalink":"http://www.updatecg.xin/tags/数据库优化/"}]},{"title":"线程安全","date":"2017-09-17T12:27:02.000Z","path":"2017/09/17/线程安全/","text":"多线程编程有三大特点,原子性、可见性、顺序性。本篇文章结合这三个特点出发,结合实例volatile如何实现可见性、一定程序上保证顺序性,同实例synchronized如何同时保证可见性和原子性,最后最弊volatile和synchronized的适用场景。 多线程三个核心概念原子性即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死。 一个很经典的例子就是银行账户转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。 可见性当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。 CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。 顺序性程序执行的顺序按照代码的先后顺序执行。 boolean started = false; // 语句1long counter = 0L; // 语句2counter = 1; // 语句3started = true; // 语句4 从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。 处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。 讲到这里,有人要着急了——什么,CPU不按照我的代码顺序执行代码,那怎么保证得到我们想要的效果呢?实际上,大家大可放心,CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。 Java如何解决多线程并发问题Java如何保证原子性锁和同步常用的保证Java操作原子性的工具是锁和同步方法(或者同步代码块)。使用锁,可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。public void testLock () { lock.lock(); try{ int j = i; i = j + 1; } finally { lock.unlock(); }} 与锁类似的是同步方法或者同步代码块。使用非静态同步方法时,锁住的是当前实例;使用静态同步方法时,锁住的是该类的Class对象;使用静态代码块时,锁住的是synchronized关键字后面括号内的对象。下面是同步代码块示例public void testLock () { synchronized (anyObject){ int j = i; i = j + 1; }} 无论使用锁还是synchronized,本质都是一样,通过锁来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。 Java如何保证可见性Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。 Java如何保证顺序性Java中可通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性。 synchronized和锁保证顺序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。 除了从应用层面保证目标代码段执行的顺序性外,JVM还通过被称为happens-before原则隐式地保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。 happens-before原则(先行发生原则) 传递规则:如果操作1在操作2前面,而操作2在操作3前面,则操作1肯定会在操作3前发生。该规则说明了happens-before原则具有传递性 锁定规则:一个unlock操作肯定会在后面对同一个锁的lock操作前发生。这个很好理解,锁只有被释放了才会被再次获取 volatile变量规则:对一个被volatile修饰的写操作先发生于后面对该变量的读操作 程序次序规则:一个线程内,按照代码顺序执行 线程启动规则:Thread对象的start()方法先发生于此线程的其它动作* 线程终结原则:线程的终止检测后发生于线程中其它的所有操作 线程中断规则: 对线程interrupt()方法的调用先发生于对该中断异常的获取 对象终结规则:一个对象构造先于它的finalize发生 volatile适用场景volatile适用于不需要保证原子性,但却需要保证可见性的场景。一种典型的使用场景是用它修饰用于停止线程的状态标记。如下所示boolean isRunning = false;public void start () { new Thread( () -> { while(isRunning) { someOperation(); } }).start();}public void stop () { isRunning = false;} 在这种实现方式下,即使其它线程通过调用stop()方法将isRunning设置为false,循环也不一定会立即结束。可以通过volatile关键字,保证while循环及时得到isRunning最新的状态从而及时停止循环,结束线程。 线程安全十万个为什么 【问】:平时项目中使用锁和synchronized比较多,而很少使用volatile,难道就没有保证可见性? 【答】:锁和synchronized即可以保证原子性,也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。 【问】:锁和synchronized为何能保证可见性? 【答】:根据!JDK 7的Java中对concurrent包的说明,一个线程的写结果保证对另外线程的读操作可见,只要该写操作可以由happen-before原则推断出在读操作之前发生。 【问】:既然锁和synchronized即可保证原子性也可保证可见性,为何还需要volatile? 【答】:synchronized和锁需要通过操作系统来仲裁谁获得锁,开销比较高,而volatile开销小很多。因此在只需要保证可见性的条件下,使用volatile的性能要比使用锁和synchronized高得多。 【问】:还有没有别的办法保证线程安全 【答】:有。尽可能避免引起非线程安全的条件——共享变量。如果能从设计上避免共享变量的使用,即可避免非线程安全的发生,也就无须通过锁或者synchronized以及volatile解决原子性、可见性和顺序性的问题。 【问】:synchronized即可修饰非静态方式,也可修饰静态方法,还可修饰代码块,有何区别 【答】:synchronized修饰非静态同步方法时,锁住的是当前实例;synchronized修饰静态同步方法时,锁住的是该类的Class对象;synchronized修饰静态代码块时,锁住的是synchronized关键字后面括号内的对象。 本文转载来自 [http://www.jasongj.com/java/thread_safe/]","tags":[{"name":"Java","slug":"Java","permalink":"http://www.updatecg.xin/tags/Java/"},{"name":"线程安全","slug":"线程安全","permalink":"http://www.updatecg.xin/tags/线程安全/"}]},{"title":"【Java设计模式 细节一】单例模式","date":"2017-09-17T06:49:02.000Z","path":"2017/09/17/【Java设计模式 细节一】单例模式/","text":"为何需要单例模式 对于系统中的某些类来说,只有一个实例很重要,例如,系统对于接口缓存的设计,只能拥有一个文件系统。 单例模式设计要点 保证该类只有一个实例。将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象 提供一个该实例的访问点。一般由该类自己负责创建实例,并提供一个静态方法作为该实例的访问点 静态 & 非静态 静态声明实例引用时即实例化 非静态方法第一次被调用前不实例化,称为懒加载。对于创建大,不一定被使用时,使用懒加载,节约了大量开销。 实现单例模式的九种方法线程不安全的非静态方法 - 多线程不可用package com.cw.amt;public class Example1 { private static Example1 INSTANCE; private Example1() {}; public static Example1Example1 getInstance() { if (INSTANCE == null) { INSTANCE = new Example1(); } return INSTANCE; }} 优点:实现了lazy loading。 缺点:只有在单线程下能保证只有一个实例,多线程下有创建多个实例的风险 同步方法下的非静态方法 - 可用,不推荐package com.cw.amt;public class Example2 { private static Example2 INSTANCE; private Example2() {}; public static synchronized Example2 getInstance() { if (INSTANCE == null) { INSTANCEINSTANCE = new Example2(); } return INSTANCE; }} 优点:线程安全,可确保正常使用下(不考虑通过反射调用私有构造方法)只有一个实例 缺点:每次获取实例都需要申请锁,开销大,效率低 同步代码块下的非静态方法 - 不可用package com.cw.amt;public class Example3 { private static Example3 INSTANCE; private Example3() {}; public static Example3 getInstance() { if (INSTANCE == null) { synchronized (Example3.class) { INSTANCE = new Example3(); } } return INSTANCE; }} 优点:不需要在每次调用时加锁,效率比上一个高。 缺点:虽然使用synchronized,但本质上是线程不安全的。 双重检查(Double Check)下的非静态方法 - 推荐package com.cw.amt;public class Example4 { private static Example4 INSTANCE; private Example4() {}; public static Example4 getInstance() { if (INSTANCE == null) { synchronized(Example4.class){ if(INSTANCE == null) { INSTANCE = new Example4(); } } } return INSTANCE; }} 优点:使用了双重检查,很大程度上避免了线程不安全,同时也避免了不必要的锁开销。对于保证实例返回只有一个实例时,这里要注意,需要使用volatile关键字来处理 缺点:不完全保证 静态工厂方法 - 推荐package com.jasongj.singleton6;public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {}; public static Singleton getInstance() { return INSTANCE; }} 优点:实现简单,无线程同步问题 缺点:在类装载时完成实例化。若该实例一直未被使用,则会造成资源浪费 枚举 推荐 对于枚举,因枚举具有单例特性,对比双重检查时,有时候也会返回不止一个实例对象。虽然这种问 题通过改善java内存模型和使用volatile变量可以解决,但是这种方法对于很多初学者来说写起来还是很棘手。相比用 synchronization的双检索实现方式来说,枚举单例就简单多了。你不相信?比较一下下面的双检索实现代码和枚举实现代码就知道了。 用枚举实现的单例/** * Singleton pattern example using Java Enumj */public enum EasySingleton{ INSTANCE;} 代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,对比上面使用的双重检查单例例子方法容易多了。 枚举单例可以自己处理序列化 传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了,因为readObject()方法总是返回一个 新的实例对象,就像java中的构造器一样。你可以使用readResolve()方法来避免这种情况, 通过像下面的例子中这样用单例来替换新创建的实 例://readResolve to prevent another instance of Singletonprivate Object readResolve(){ return INSTANCE;} 如果你的单例类包含状态的话就变的更复杂了,你需要把他们置为transient状态,但是用枚举单例的话,序列化就不要考虑了。 枚举单例是线程安全的 由于枚举实例的创建默认就是线程安全的,你不需要担心双检锁问题。 总结通过提供序列化和线程安全并且几行代码搞定,说明枚举单例模式是java5之后创建单例最好的方法。如果有更好方法可以告诉我(●´∀`●)","tags":[{"name":"设计模式","slug":"设计模式","permalink":"http://www.updatecg.xin/tags/设计模式/"},{"name":"Java","slug":"Java","permalink":"http://www.updatecg.xin/tags/Java/"}]},{"title":"JavaSE 8","date":"2017-09-09T03:23:02.000Z","path":"2017/09/09/JavaSE8/","text":"自从有了Java8,Java语言和库就仿佛获得可新生。lambda表达式可以允许开发人员编写简洁的“计算片段”,并将它们传递给其他代码。接收代码可以选择在合适的时候来执行”计算片段”,这对于构建第三方库有深远的影响。 尤其要指出一点,它彻底改变了集合的使用方式。我们不需要在指定计算 结果的过程(从起始遍历到结尾,如果某个元素满足了某个条件,就根据它计算一个值,然后将值添加到总和中),只需指定想要的什么样的结构。这样,代码就可以重新对计算排序————例如,来充分利用并行的优势。或者,如果你只希望匹配前100个元素,那么你不必在维护一个计算器,程序可以自动停止计算。 lambda 表达式的语法排序Java7: public void compare(Stirng first,String second){ Integer.compare(first.lengt(),second.length()) }Java8: (Stirng first,String second) -> Integer.compare(first.lengt(),second.length())        如果lambda表达式的参数类型可以被推导,那么就可以省略它们的类型,例如:Comparator<String> comp = (first,second) -> Integer.compare(first.lengt(),second.length()) 字符串拼接        String.join(参数)参数可以来自于一个数组或者一个Iterable<? extends CharSequence>对象Java7: String str=\"a\"+\"/\"+\"b\"+\"/\"+\"c\"Java8: String str=String.join(\"/\",\"a\",\"b\",\"c\");Java8: String[] strs=[\"a\",\"b\",\"c\"] String str=String.join(\"/\",strs); 集合        Java 8 中添加到集合类和接口方法 类/接口 方法 Iterable forEach Collection removeIf List replaceAll, sort Map forEach ,replace ,replaceAll, remove(key,value)(只有当key到value的映射存在时才删除),putIfAbsent, comput, computeIf (Absent / Present),merge Iterabtor forEachRemaining BitSet stream        Map接口有许多对于并发十分重要的方法[暂无]        接下来展示一些比较Java 8 比较常用的方法实例 List sortList<String> list = new ArrayList<>(); list.add(\"1\"); list.add(\"2\"); list.add(\"4\"); list.add(\"3\");list.sort((String h1, String h2) -> h1.compareTo(h2));for (String strings: list){ System.out.print(strings);}结果:1234 Map forEachMap<String, String> map = new LinkedHashMap<>(); map.put(\"key1\",\"1\"); map.put(\"key2\",\"2\"); map.put(\"key4\",\"4\"); map.put(\"key5\",\"5\");map.forEach((k,v) -> System.out.print(\"k=\" + k + \" v=\" + v + \"\\n\"));结果: k=key1 v=1 k=key2 v=2 k=key4 v=4 k=key5 v=5 Map mergeMap<String, String> map = new LinkedHashMap<>(); map.put(\"key1\",\"我和\"); map.merge(\"key1\",\"你\",(value, newValue) -> value.concat(newValue)); System.out.print( map.get(\"key1\"));结果:我和你 Iterable forEachList<AdgAdRel> list = new ArrayList<>(); AdgAdRel adgAdRel = new AdgAdRel(); adgAdRel.setUrl(\"url1\"); AdgAdRel adgAdRel2 = new AdgAdRel(); adgAdRel2.setUrl(\"url2\"); list.add(adgAdRel); list.add(adgAdRel2); list.forEach(adgAdRel1 -> { Map<String, Object> map =new LinkedHashMap<>(); map.put(\"url\",adgAdRel1.getUrl()); map.forEach((k,v) -> System.out.print(\"k:\"+ k + \" v:\"+v + \"\\n\")); });结果: k:url v:url1 k:url v:url2 性能测试        最开始以为Java 8 流性能会比Java 7 好,测试结果如下: //5条数据for (AdgAdRel adgAdRel:adgAdList) { Map<String, Object> map =new LinkedHashMap<>(); map.put(\"showTime\",adgAdRel.getShowTime()); map.put(\"width\",adgAdRel.getWidth()); map.put(\"height\",adgAdRel.getHeight()); map.put(\"type\",adgAdRel.getType()); resultMap.add(map);}//java 7循环 1毫秒System.out.print(System.currentTimeMillis()-date);//java 8循环 65左右毫秒System.out.print(System.currentTimeMillis()-date); 总结        对于一个简单集合来说,单纯的从集合中获取数据,Java8流处理的性能没有Java7好。 Java8流处理适合复杂度比较高的判断,比如需要根据某些字段,以及条件进行筛选,流处理性能优势大些。","tags":[{"name":"Java8","slug":"Java8","permalink":"http://www.updatecg.xin/tags/Java8/"}]},{"title":"【Apache Kafka 细节二】Kafka背景及架构介绍","date":"2017-09-03T02:23:02.000Z","path":"2017/09/03/【Apache Kafka 细节二】Kafka背景及架构介绍/","text":"摘要 摘要 :KafKa是有LinkedIn开发并开源的分布式消息系统,因其分布式及高吐率而被广泛试用,现已与Cloudera Hadoop,Apache Storm,Apache Spark集成。本文介绍了Kafka的创建背景,设计目标,使用消息系统的优势以及目前流行的消息同对比。并介绍了Kafka的架构。Producer消息路由。Consumer Group以及由其实现的不同消息分布方式,Topic & Partition,最后介绍了Kafka Consumer 为何试用pull模式以及Kafka提供的三种delivery guarantee. 背景介绍Kafka创建背景       Kafka是一个消息系统,原本开发于LinkedIn,用作与LinkedIn的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。现在它已被多家不同类型的公司作为多种类型的数据管道和消息系统试用。        活动流数据是几乎所有站点在对其网站试用情况做报表时都要用到的数据中最常规的部分。活动数据包括页面访问量(Page View)、被查看容方面的信息以及搜索情况等内容。这种数据通常的处理方式是先吧各种活动以日志的形式写入某种文件,然后周期性地对这些文件进行统计分析。运营数据指的是服务器的性能数据(CPU、IO使用率、请求时间、服务日志等等数据)。运营数据的统计方法种类繁多。        近年来,活动和运营数据处理已经成为了网站如愿产品特性中一个至关重要的组成部分,这就需要一套稍微更加复杂的基础设施对其提供支持。 Kafka简介       Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下: 以时间复杂度为O(n)【n很大的时候,复杂度基本就不增长了,基本就是个常量C】的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100k条以上消息的传输 支持Kafka Server间的消息分区,及分布式消费,同时保证每个Pratition内的消息顺序传输 同时支持离线数据处理和实时数据处理 Scale out :支持在线水平扩展 为何使用消息系统 解耦        在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守童谣的接口约束。 冗余        有些情况下,处理数据过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到他们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的“插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的该消息已经被处理完毕,从而取保你的数据被安全的保存直到你使用完毕。 扩展性        因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的。只要另外增加处理过程即可。不需要改变代码、不需要调节参数。 灵活性&峰值处理能力        在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 可恢复性        当体系的一部分组件失效,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 送达保证        消息队列提供的冗余机制保证了消息能被实际的处理,只要一个进程读取了该队列即可。在此基础上,IronMQ提供了一个“只送达一次”保证。无论有多少进程在从队列中领取数据,每一个消息只能被处理一次。这之所以成为可能,是因为获取一个消息只是“预定”了这个消息,暂时把它移出了队列。除非客户端明确表示已经处理完了这个消息,否则这个消息会被放回队列中去,在一段可配置的时间之后再次被处理。 顺序保证        在大多使用场景下,数据处理的顺序都很重要。消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。IronMQ保证消息通过FIFO(先进先出)的顺序来处理,因为消息在队列中的位置就是从队列中检索出来他们的位置。 缓冲        在任何重要的系统中,都会有需要不同的处理时间的元素。例如:加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行,写入队列的处理会尽可能的快速,而不受从队列读的预备处理的约束。该缓冲有助于控制和优化数据流经过系统的速度。 理解数据流        在一个分布式系统里,要得到一个关于用户操作会用多长时间及其原因的总体印象,是一个巨大的挑战。消息队列通过消息被处理的频率,来方便的辅助确定那些表现不佳的处理过程或领域。 异步通信        很多时候,你不想也不需要立即处理消息。消息队列提供了异步处理机制,允许你把一个消息放入队列,但并不立即处理它。你想向队列放入多少消息就放多少,然后在你乐意的时候再去处理它们。 常用Message Queue对比 RabbitMQRabbitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多协议:AMQP、XMPP、SMTP、STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现了Broker架构,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。 RedisRedis是一个基于Key-Value对NoSql数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全当做一个轻量级的队列服务来使用,对于RabbitMQ和Redis的入队和出对操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes521Bytes、1k和10k四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小写超过了10k。Rdis则慢的无法忍受;出队时,无论数据大小,Redis都表现非常好的性能,而RabbitMQ的出队性能则低于Redis. ZeroMQZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上复杂度是对这个MQ能够应用成功的一个挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务角色。你只需要简单的引用ZeroMQ程序库,可以使NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter的Storm 0.9.0以前的版本中默认使用ZeroMQ作为数据流的传输(Storm从0.9版本开始同时支持ZeroMQ和Netty作为传输模板) ActiveMQActiveMQ是Apache下的一个子项目。类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。 Kafka/JafkaKafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制来统一了在线和离线的消息处理。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。 Kafka解析Terminology BrokerKafka集群包含一个或多个服务器,这种服务器被称为broker Topic每条发布到Kafka集群的消息都有一个类别,这个类别被称为topic。(物理上不同topic的消息分开存储,逻辑上一个topic的消息虽然保存于一个或多个broker上但用户只需指定消息的topic即可生产或消费数据而不必关心数据存于何处) Partitionparition是物理上的概念,每个topic包含一个或多个partition,创建topic时可指定parition数量。每个partition对应于一个文件夹,该文件夹下存储该partition的数据和索引文件 Producer负责发布消息到Kafka broker Consumer消费消息。每个consumer属于一个特定的consumer group(可为每个consumer指定group name,若不指定group name则属于默认的group)。使用consumer high level API时,同一topic的一条消息只能被同一个consumer group内的一个consumer消费,但多个consumer group可同时消费这一消息。 Kafka架构        如上图所示,一个典型的kafka集群中包含若干producer(可以是web前段产生的page view,或者是服务 器日志,系统CPU、存储memory),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干consumer group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在consumer group 发生变化时重新平衡(rebalance). producer使用push模式将消息发布到broker,consumer使用pull模式从broker订阅并消费消息. Push vs Pull        作为一个message system ,Kafka遵循了传统的方式,选择由producer向broker push 消息并由consumer从broker pull消息数据。一些logging-system(日志系统),比如Facebook的Scrilbe和Cloudera的Flume,采用非常不同的push模式。事实上,push模式和pull模式各有优劣。        push模式很难适应消费速率不同的消费者,因为消息发送速率是有broker决定的。push模式的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及出来消息,典型的表现就是拒绝服务以及网络阻塞。而pull模式可以根据consumer的消费能力以适当的速率消费消息。 Topic & Partition        Topic的逻辑上可以被认为是一个queue。每条消费都必须指定它的topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以水平扩展,物理上把topic分为一个或过个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。 `       每个日子文件都是“log entries”序列,每一个log entry 包含一个4字节整型数(值为N),其后跟N个字节的消息体。每条消息都有一个当前Partition下唯一的64字节的offset,它指明了这条消息的起始位置。磁盘上存储的消息格式如下: message length : 4bytes(values:1+4+n) "magic" value :1 byte crc : 4bytes payload : n bytes        这个“log entries” 并非由一个文件构成,而是分成多个(段)segment,每个segment名为该segment第一条消息的offset和”.kafka”组成。另外会有一个索引文件,它标明了每个segment下包含的log entry的offset范围,如下图示。 //图片        因为每条消息都被append到改partition中,是是顺序写磁盘,因此效率非常高(经验证明,顺序写磁盘效率比随机写内存还要高,这是kafka高吞吐率的一个很重要的保证)。 //图片        每一条消息被发送到broker时,会根据partition规则选择被存储到哪一个partition。如果partition规则设置的合理,所有消息可以均匀分布到不同的partition里,这样就实现了水平扩展。(如果一个topic对应一个文件,那这个文件所在的机器I/O将会成为这个topic的性能瓶颈,而partition解决了这个问题)。在创建topic时可以在$KAFKA_HOME/config/server.properties中指定这个partition的数量(如下所示),当然也可以在topic创建之后去修改partition数量。 # The default number of log partitions per topic. More partitions allow greater# parallelism for consumption, but this will also result in more files across# the brokers.num.partitions=3        在发送一条消息时,可以指定这条消息的key,producer根据这个key和partition机制来判断将这条消息发送到哪个parition。paritition机制可以通过指定producer的paritition. class这一参数来指定,该class必须实现kafka.producer.Partitioner接口。本例中如果key可以被解析为整数则将对应的整数与partition总数取余,该消息会被发送到该数对应的partition。(每个parition都会有个序号) import kafka.producer.Partitioner;import kafka.utils.VerifiableProperties;public class JasonPartitioner<T> implements Partitioner { public JasonPartitioner(VerifiableProperties verifiableProperties) {} @Override public int partition(Object key, int numPartitions) { try { int partitionNum = Integer.parseInt((String) key); return Math.abs(Integer.parseInt((String) key) % numPartitions); } catch (Exception e) { return Math.abs(key.hashCode() % numPartitions); } }}        如果将上例中的class作为partitioner.class,并通过如下代码发送20条消息(key分别为0,1,2,3)至topic2(包含4个partition)。 public void sendMessage() throws InterruptedException{ for(int i = 1; i <= 5; i++){ List messageList = new ArrayList<KeyedMessage<String, String>>(); for(int j = 0; j < 4; j++){ messageList.add(new KeyedMessage<String, String>("topic2", j+"", "The " + i + " message for key " + j)); } producer.send(messageList); } producer.close(); }        则key相同的消息会被发送并存储到同一个partition里,而且key的序号正好和partition序号相同。(partition序号从0开始,本例中的key也正好从0开始)。如下图所示。        对于传统的message queue而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此Kafka提供两种策略去删除旧数据。一是基于时间,二是基于partition文件大小。例如可以通过配置$KAFKA_HOME/config/server.properties,让Kafka删除一周前的数据,也可通过配置让Kafka在partition文件超过1GB时删除旧数据,如下所示。 ############################# Log Retention Policy ############################## The following configurations control the disposal of log segments. The policy can# be set to delete segments after a period of time, or after a given size has accumulated.# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens# from the end of the log.# The minimum age of a log file to be eligible for deletionlog.retention.hours=168# A size-based retention policy for logs. Segments are pruned from the log as long as the remaining# segments don't drop below log.retention.bytes.#log.retention.bytes=1073741824# The maximum size of a log segment file. When this size is reached a new log segment will be created.log.segment.bytes=1073741824# The interval at which log segments are checked to see if they can be deleted according# to the retention policieslog.retention.check.interval.ms=300000# By default the log cleaner is disabled and the log retention policy will default to#just delete segments after their retention expires.# If log.cleaner.enable=true is set the cleaner will be enabled and individual logs#can then be marked for log compaction.log.cleaner.enable=false        这里要注意,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除文件与Kafka性能无关,选择怎样的删除策略只与磁盘以及具体的需求有关。另外,Kafka会为每一个consumer group保留一些metadata信息–当前消费的消息的position,也即offset。这个offset由consumer控制。正常情况下consumer会在消费完一条消息后线性增加这个offset。当然,consumer也可将offset设成一个较小的值,重新消费一些消息。因为offet由consumer控制,所以Kafka broker是无状态的,它不需要标记哪些消息被哪些consumer过,不需要通过broker去保证同一个consumer group只有一个consumer能消费某一条消息,因此也就不需要锁机制,这也为Kafka的高吞吐率提供了有力保障。 Replication & Leader election        Kafka从0.8开始提供partition级别的replication,replication的数量可在$KAFKA_HOME/config/server.properties中配置。 default.replication.factor = 1        该 Replication与leader election配合提供了自动的failover机制。replication对Kafka的吞吐率是有一定影响的,但极大的增强了可用性。默认情况下,Kafka的replication数量为1。 每个partition都有一个唯一的leader,所有的读写操作都在leader上完成,leader批量从leader上pull数据。一般情况下partition的数量大于等于broker的数量,并且所有partition的leader均匀分布在broker上。follower上的日志和其leader上的完全一样。        和大部分分布式系统一样,Kakfa处理失败需要明确定义一个broker是否alive。对于Kafka而言,Kafka存活包含两个条件,一是它必须维护与Zookeeper的session(这个通过Zookeeper的heartbeat机制来实现)。二是follower必须能够及时将leader的writing复制过来,不能“落后太多”。        leader会track“in sync”的node list。如果一个follower宕机,或者落后太多,leader将把它从”in sync” list中移除。这里所描述的“落后太多”指follower复制的消息落后于leader后的条数超过预定值,该值可在$KAFKA_HOME/config/server.properties中配置 #If a replica falls more than this many messages behind the leader, the leader will remove the follower from ISR and treat it as deadreplica.lag.max.messages=4000#If a follower hasn't sent any fetch requests for this window of time, the leader will remove the follower from ISR (in-sync replicas) and treat it as deadreplica.lag.time.max.ms=10000        需要说明的是,Kafka只解决”fail/recover”,不处理“Byzantine”(“拜占庭”)问题。[拜占庭详解]        一条消息只有被“in sync” list里的所有follower都从leader复制过去才会被认为已提交。这样就避免了部分数据被写进了leader,还没来得及被任何follower复制就宕机了,而造成数据丢失(consumer无法消费这些数据)。而对于producer而言,它可以选择是否等待消息commit,这可以通过request.required.acks来设置。这种机制确保了只要“in sync” list有一个或以上的flollower,一条被commit的消息就不会丢失。        这里的复制机制即不是同步复制,也不是单纯的异步复制。事实上,同步复制要求“活着的”follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率(高吞吐率是Kafka非常重要的一个特性)。而异步复制方式下,follower异步的从leader复制数据,数据只要被leader写入log就被认为已经commit,这种情况下如果follwer都落后于leader,而leader突然宕机,则会丢失数据。而Kafka的这种使用“in sync” list的方式则很好的均衡了确保数据不丢失以及吞吐率。follower可以批量的从leader复制数据,这样极大的提高复制性能(批量写磁盘),极大减少了follower与leader的差距(前文有说到,只要follower落后leader不太远,则被认为在“in sync” list里)。        上文说明了Kafka是如何做replication的,另外一个很重要的问题是当leader宕机了,怎样在follower中选举出新的leader。因为follower可能落后许多或者crash了,所以必须确保选择“最新”的follower作为新的leader。一个基本的原则就是,如果leader不在了,新的leader必须拥有原来的leader commit的所有消息。这就需要作一个折衷,如果leader在标明一条消息被commit前等待更多的follower确认,那在它die之后就有更多的follower可以作为新的leader,但这也会造成吞吐率的下降。        一种非常常用的选举leader的方式是“majority vote”(“少数服从多数”),但Kafka并未采用这种方式。这种模式下,如果我们有2f+1个replica(包含leader和follower),那在commit之前必须保证有f+1个replica复制完消息,为了保证正确选出新的leader,fail的replica不能超过f个。因为在剩下的任意f+1个replica里,至少有一个replica包含有最新的所有消息。这种方式有个很大的优势,系统的latency只取决于最快的几台server,也就是说,如果replication factor是3,那latency就取决于最快的那个follower而非最慢那个。majority vote也有一些劣势,为了保证leader election的正常进行,它所能容忍的fail的follower个数比较少。如果要容忍1个follower挂掉,必须要有3个以上的replica,如果要容忍2个follower挂掉,必须要有5个以上的replica。也就是说,在生产环境下为了保证较高的容错程度,必须要有大量的replica,而大量的replica又会在大数据量下导致性能的急剧下降。这就是这种算法更多用在Zookeeper这种共享集群配置的系统中而很少在需要存储大量数据的系统中使用的原因。例如HDFS的HA feature是基于majority-vote-based journal,但是它的数据存储并没有使用这种expensive的方式。        实际上,leader election算法非常多,比如Zookeper的Zab, Raft和Viewstamped Replication。而Kafka所使用的leader election算法更像微软的PacificA算法。        Kafka在Zookeeper中动态维护了一个ISR(in-sync replicas) set,这个set里的所有replica都跟上了leader,只有ISR里的成员才有被选为leader的可能。在这种模式下,对于f+1个replica,一个Kafka topic能在保证不丢失已经ommit的消息的前提下容忍f个replica的失败。在大多数使用场景中,这种模式是非常有利的。事实上,为了容忍f个replica的失败,majority vote和ISR在commit前需要等待的replica数量是一样的,但是ISR需要的总的replica的个数几乎是majority vote的一半。        虽然majority vote与ISR相比有不需等待最慢的server这一优势,但是Kafka作者认为Kafka可以通过producer选择是否被commit阻塞来改善这一问题,并且节省下来的replica和磁盘使得ISR模式仍然值得。        上文提到,在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某一个partition的所有replica都挂了,就无法保证数据不丢失了。这种情况下有两种可行的方案: 等待ISR中的任一个replica“活”过来,并且选它作为leader 选择第一个“活”过来的replica(不一定是ISR中的)作为leader        这就需要在可用性和一致性当中作出一个简单的平衡。如果一定要等待ISR中的replica“活”过来,那不可用的时间就可能会相对较长。而且如果ISR中的所有replica都无法“活”过来了,或者数据都丢失了,这个partition将永远不可用。选择第一个“活”过来的replica作为leader,而这个replica不是ISR中的replica,那即使它并不保证已经包含了所有已commit的消息,它也会成为leader而作为consumer的数据源(前文有说明,所有读写都由leader完成)。Kafka0.8.*使用了第二种方式。根据Kafka的文档,在以后的版本中,Kafka支持用户通过配置选择这两种方式中的一种,从而根据不同的使用场景选择高可用性还是强一致性。        上文说明了一个parition的replication过程,然尔Kafka集群需要管理成百上千个partition,Kafka通过round-robin的方式来平衡partition从而避免大量partition集中在了少数几个节点上。同时Kafka也需要平衡leader的分布,尽可能的让所有partition的leader均匀分布在不同broker上。另一方面,优化leadership election的过程也是很重要的,毕竟这段时间相应的partition处于不可用状态。一种简单的实现是暂停宕机的broker上的所有partition,并为之选举leader。实际上,Kafka选举一个broker作为controller,这个controller通过watch Zookeeper检测所有的broker failure,并负责为所有受影响的parition选举leader,再将相应的leader调整命令发送至受影响的broker,过程如下图所示。 //图片       这样做的好处是,可以批量的通知leadership的变化,从而使得选举过程成本更低,尤其对大量的partition而言。如果controller失败了,幸存的所有broker都会尝试在Zookeeper中创建/controller->{this broker id},如果创建成功(只可能有一个创建成功),则该broker会成为controller,若创建不成功,则该broker会等待新controller的命令。 //图片 Consumer group        (本节所有描述都是基于consumer hight level API而非low level API)。        每一个consumer实例都属于一个consumer group,每一条消息只会被同一个consumer group里的一个consumer实例消费。(不同consumer group可以同时消费同一条消息) //图片        很多传统的message queue都会在消息被消费完后将消息删除,一方面避免重复消费,另一方面可以保证queue的长度比较少,提高效率。而如上文所将,Kafka并不删除已消费的消息,为了实现传统message queue消息只被消费一次的语义,Kafka保证保证同一个consumer group里只有一个consumer会消费一条消息。与传统message queue不同的是,Kafka还允许不同consumer group同时消费同一条消息,这一特性可以为消息的多元化处理提供了支持。实际上,Kafka的设计理念之一就是同时提供离线处理和实时处理。根据这一特性,可以使用Storm这种实时流处理系统对消息进行实时在线处理,同时使用Hadoop这种批处理系统进行离线处理,还可以同时将数据实时备份到另一个数据中心,只需要保证这三个操作所使用的consumer在不同的consumer group即可。下图展示了Kafka在Linkedin的一种简化部署。 //图片        为了更清晰展示Kafka consumer group的特性,笔者作了一项测试。创建一个topic (名为topic1),创建一个属于group1的consumer实例,并创建三个属于group2的consumer实例,然后通过producer向topic1发送key分别为1,2,3r的消息。结果发现属于group1的consumer收到了所有的这三条消息,同时group2中的3个consumer分别收到了key为1,2,3的消息。如下图所示。 Consumer Rebalance        Kafka保证同一consumer group中只有一个consumer会消费某条消息,实际上,Kafka保证的是稳定状态下每一个consumer实例只会消费某一个或多个特定partition的数据,而某个partition的数据只会被某一个特定的consumer实例所消费。这样设计的劣势是无法让同一个consumer group里的consumer均匀消费数据,优势是每个consumer不用都跟大量的broker通信,减少通信开销,同时也降低了分配难度,实现也更简单。另外,因为同一个partition里的数据是有序的,这种设计可以保证每个partition里的数据也是有序被消费。        如果某consumer group中consumer数量少于partition数量,则至少有一个consumer会消费多个partition的数据,如果consumer的数量与partition数量相同,则正好一个consumer消费一个partition的数据,而如果consumer的数量多于partition的数量时,会有部分consumer无法消费该topic下任何一条消息。        如下例所示,如果topic1有0,1,2共三个partition,当group1只有一个consumer(名为consumer1)时,该 consumer可消费这3个partition的所有数据。        增加一个consumer(consumer2)后,其中一个consumer(consumer1)可消费2个partition的数据,另外一个consumer(consumer2)可消费另外一个partition的数据。        再增加一个consumer(consumer3)后,每个consumer可消费一个partition的数据。consumer1消费partition0,consumer2消费partition1,consumer3消费partition2 #160;      再增加一个consumer(consumer4)后,其中3个consumer可分别消费一个partition的数据,另外一个consumer(consumer4)不能消费topic1任何数据。 #160;      此时关闭consumer1,剩下的consumer可分别消费一个partition的数据。 #160;      接着关闭consumer2,剩下的consumer3可消费2个partition,consumer4可消费1个partition。 #160;      再关闭consumer3,剩下的consumer4可同时消费topic1的3个partition。 消息Deliver guarantee        通过上文介绍,想必读者已经明白了producer和consumer是如何工作的,以及Kafka是如何做replication的,接下来要讨论的是Kafka如何确保消息在producer和consumer之间传输。有这么几种可能的delivery guarantee: At most once 消息可能会丢,但绝不会重复传输 At least one 消息绝不会丢,但可能会重复传输 Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的。        Kafka的delivery guarantee semantic非常直接。当producer向broker发送消息时,一旦这条消息被commit,因数replication的存在,它就不会丢。但是如果producer发送数据给broker后,遇到的网络问题而造成通信中断,那producer就无法判断该条消息是否已经commit。这一点有点像向一个自动生成primary key的数据库表中插入数据。虽然Kafka无法确定网络故障期间发生了什么,但是producer可以生成一种类似于primary key的东西,发生故障时幂等性的retry多次,这样就做到了Exactly one。截止到目前(Kafka 0.8.2版本,2015-01-25),这一feature还并未实现,有希望在Kafka未来的版本中实现。(所以目前默认情况下一条消息从producer和broker是确保了At least once,但可通过设置producer异步发送实现At most once)。 读完消息先commit再处理消息。这种模式下,如果consumer在commit后还没来得及处理消息就crash了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once 读完消息先处理再commit。这种模式下,如果处理完了消息在commit之前consumer crash了,下次重新开始工作时还会处理刚刚未commit的消息,实际上该消息已经被处理过了。这就对应于At least once。在很多情况使用场景下,消息都有一个primary key,所以消息的处理往往具有幂等性,即多次处理这一条消息跟只处理一次是等效的,那就可以认为是Exactly once。(人个感觉这种说法有些牵强,毕竟它不是Kafka本身提供的机制,而且primary key本身不保证操作的幂等性。而且实际上我们说delivery guarantee semantic是讨论被处理多少次,而非处理结果怎样,因为处理方式多种多样,我们的系统不应该把处理过程的特性–如是否幂等性,当成Kafka本身的feature) 如果一定要做到Exactly once,就需要协调offset和实际操作的输出。精典的做法是引入两阶段提交。如果能让offset和操作输入存在同一个地方,会更简洁和通用。这种方式可能更好,因为许多输出系统可能不支持两阶段提交。比如,consumer拿到数据后可能把数据放到HDFS,如果把最新的offset和数据本身一起写到HDFS,那就可以保证数据的输出和offset的更新要么都完成,要么都不完成,间接实现Exactly once。(目前就high level API而言,offset是存于Zookeeper中的,无法存于HDFS,而low level API的offset是由自己去维护的,可以将之存于HDFS中)        总之,Kafka默认保证At least once,并且允许通过设置producer异步提交来实现At most once。而Exactly once要求与目标存储系统协作,幸运的是Kafka提供的offset可以使用这种方式非常直接非常容易。 原文链接 [https://www.jasongj.com/2015/01/02/Kafka深度解析]","tags":[{"name":"Kafka","slug":"Kafka","permalink":"http://www.updatecg.xin/tags/Kafka/"}]},{"title":"路上","date":"2017-08-25T09:36:21.000Z","path":"2017/08/25/路上/","text":"由于七牛云收回了测试域名导致很多照片丢失FirstDay 成都–新津–邛崃–名山–雅安 一场说走就走的旅行~ 人生的旅行 那是充满未知的旅行 那是漫无边际的旅行 而我的旅行 则不为拔涉千里的向往 只为毫无目的的闲逛 不为人山人海的名胜 只为怡然自乐的街景 不在乎红灯绿酒 不在意成功与失败 只想念曾经路过的地方 带给自己铭刻于心的难忘 于是 我发现 灵魂和身体 必须有一个在路上 TwoDay 雅安–汉源–石棉–冷碛 出发牛背山。。 清明节第二天早晨,天下着小雨,我们决定乘坐汽车去,把自行车放在了旅客。雅安到冷碛镇的二郎山隧道在重建中,不得不从雅安途径汉源-石棉,然后向康定方向,去冷碛镇。 一路上风景太漂亮了 经过了4个小时的车程终于在12点到达了冷碛。打算徒步上山,徒步上山顶用时大概要6-8小时左右。加上晚上有熊出没,所以必须抓紧时间上山。 吃了午饭,休息片刻,刻不容缓的加紧的步伐,整2点出发。 山路至小桥,只有一条路,可直通到小桥,便开始有分叉, 右边的是正确的路,此后也基本环着这座山走 全程关键休息区。山路至小桥——小桥至河谷——河谷至小村——小村至进山——碎石路至街心花园——街心花园至云海人家——云海人家至牛背山 街心花园遥看贡嘎 牛背山,到山顶已经是晚上9点,经过7个小时,人好疲。第一步就找住宿,山顶搭了很多帐篷房屋,为了节约成本,我两挤着睡,一人50元。 来到牛背山的人不一定能看见美景,只有期待和渴望。 清明节第三天,大概是早晨5点吧,听到了外面人在呐喊,很激动,我们直接蹦了起来,外面太冷了,很多人身披铺盖,裸手照相根本坚持不了3分钟,温度零下好多度。重点图来了: 寒风中执着的按着快门、、 美景总是消失的很快,不禁叹息,珍惜现在。 返行了,下山就容易多了,路况也清楚了很多,只花费了4个小时。 三天的行程真好! 部门自驾游 成都–雅安–康定–折多山–新都桥 一直在路上~ 国庆第三天清晨开启了征途之旅。 第一天凌晨,心情超开心。天气还不错,到了雅安天空飘着小雨,天空起雾,一路的茶园,别有一番风味。 雅安到二郎山隧道,一路路段都不是很好,很容易泥石流、山体滑坡。 穿过二郎山隧道,别有洞天的风景出现在眼前,那心情真是太激动了,天空太美了,如果不出来,真心难以体会啊。 康定的道路很窄,有的地方不能停车,住宿了一晚,吃了一只1000的羊,没有烤巴适,可惜了! 终于踏上了翻折多山的旅程。 有时间一定要多出走走,路上的风景是你想象想不到的美。说走就走 成都–雅安–蒙顶山–宝兴县–神木垒 蒙顶山票价:45元 三八妇女节 妇女13.8元 历史悠久的蒙顶茶被称为“仙茶”,蒙顶山被誉为“仙茶故乡 扬子江心水,蒙山顶上茶 蒙山顶上春光早,扬子江心水味高。陶家学士更风骚。应笑倒,销金帐,饮羊羔 神木垒票价:12月–3月(淡季)40元 4月–11月(旺季)60元住宿:德拉美都藏寨 淡季100元 高山草甸、原始森林、雪山湖泊、高山钙化池、红叶彩林 传说玉皇大帝有七个女儿,其中一个名字叫怕卡 有一天她路过硗碛时,远远地看到了一块美丽漂亮的地方,这里有高山,森林、山泉、草坪、天然根雕等自然景观,还有成群的牛羊,宛如天上的仙境 天性贪玩的帕卡化身为一个美丽的姑娘来到放牧的草坪上。 这时一个叫亚金的健壮藏族青年从放牧的帐篷里走了出来,看到了美丽的帕卡。 亚金对美丽的帕卡一见钟情,并邀请帕卡到自己的放牧地方游玩,帕卡对多情的亚金也是一往情深,对天庭的生活已不再眷恋,爱上了这块美丽的土地。帕卡和亚金的事被玉皇大帝知道后龙颜大怒,派神仙来把帕卡抓回了天庭,并发誓从此不要帕卡和亚金见面。 有一天,难抵相思之苦的帕卡从天庭偷跑出来与亚金再次见面。 玉皇大帝知道后,派天神来捉拿帕卡回天庭。 但感情的力量使帕卡再也不愿回到天庭,她宁愿化为一座高山留在硗碛守望着亚金。 为了完成使命,天神通过法术也把亚金变成了一座高山,分别把这两座高山放在了硗碛的最南端和最北端,让他们只能远远相望却终身不能相会。 人们为了纪念帕卡和亚金忠贞不渝的爱情故事,管这两座山叫帕格拉山和夹金山。 每逢晴好天气,站在夹金山遥望帕格拉山时,帕格拉山犹如一位脉脉含情的姑娘守望着夹金山,给人以无尽的遐想。 当年帕卡和亚金约会的地方,藏族同胞习惯性的叫神木垒,译为汉语的意思是神仙玩耍的地方,表明了硗碛的美丽和人们对忠贞爱情的向往。 注:高清图片非本人拍摄","tags":[]},{"title":"MongoDB 查询超时异常 SocketTimeoutException","date":"2017-08-25T03:59:02.000Z","path":"2017/08/25/mongo ReadTimeOut/","text":"摘要:在对大数据量的集合进行聚合操作,如果用时过长,偶尔会发生Read timed out异常。 问题概要在对超过百万条记录的集合进行聚合操作。 偶尔会发生Read timed out 异常。 通过多次测试,发现执行一次聚合平均时间为5s,超过5s时就会报错! 然后查看MongoDB的配置信息: socket-timeout=\"5000\" //5s socket-timeout的默认配置为0,也就是没有限制。 没有超时限制,系统出了问题也不容易发现,应该根据实际情况,给出合理的超时时间。 通过多次测试查询1900万条数据发现最长执行时间为28秒,则可以将socket-timeout设置成280000。 解决方案但根据我本地统计业务,统计数据会随之增加,则将socket-timeout设置0,没有限制。 注意:MongoDB在与Spring整合时,如果要配置多个MongDB源,只会启用最后一个mongo:options配置。 应该把参数配置信息存储在properties文件中。 <mongo:mongo host=\"${mongodb.ip}\" id=\"mongo202\" port=\"${mongodb.port}\"> <mongo:options connections-per-host=\"200\" threads-allowed-to-block-for-connection-multiplier=\"100\" connect-timeout=\"1000\" max-wait-time=\"1000\" auto-connect-retry=\"true\" socket-keep-alive=\"true\" socket-timeout=\"10000\" slave-ok=\"true\" write-number=\"1\" write-timeout=\"0\" write-fsync=\"true\" /> </mongo:mongo> Jdbc文件配置: mongo.connectionsPerHost=8mongo.threadsAllowedToBlockForConnectionMultiplier=4mongo.connectTimeout=1000mongo.maxWaitTime=1500mongo.autoConnectRetry=truemongo.socketKeepAlive=truemongo.socketTimeout=0mongo.slaveOk=true 注意事项 最后一点: ConnectionTimeOut和SocketTimeOut的区别: 一次完整的请求包括三个阶段:1、建立连接 2、数据传输 3、断开连接 如果与服务器(这里指数据库)请求建立连接的时间超过ConnectionTimeOut,就会抛 ConnectionTimeOutException,即服务器连接超时,没有在规定的时间内建立连接。 如果与服务器连接成功,就开始数据传输了。 如果服务器处理数据用时过长,超过了SocketTimeOut,就会抛出SocketTimeOutExceptin,即服务器响应超时,服务器没有在规定的时间内返回给客户端数据。","tags":[{"name":"MongoDB","slug":"MongoDB","permalink":"http://www.updatecg.xin/tags/MongoDB/"}]},{"title":"Tomcat调优","date":"2017-08-25T03:59:02.000Z","path":"2017/08/25/sql/","text":"JAVA虚拟机(JVM)性能优化,可以通过以下两个参数来设置虚拟机使用内存的大小,-Xms<size> 外部调优JAVA虚拟机(JVM)性能优化,可以通过以下两个参数来设置虚拟机使用内存的大小,-Xms<size>(JVM初始化堆的大小)和-Xmx<size>(JVM堆的最大值)。这两个值的大小一般根据需要进行设置。初始化堆的大小执行了虚拟机在启动时向系统申请的内存的大小。一般而言,这个参数不重要。但是有的应用程序在大负载的情况下会急剧地占用更多的内存,此时这个参数就是显得非常重要,如果虚拟机启动时设置使用的内存比较小而在这种情况下有许多对象进行初始化,虚拟机就必须重复地增加内存来满足使用。由于这种原因,我们一般把-Xms和-Xmx设为一样大,而堆的最大值受限于系统使用的物理内存。一般使用数据量较大的应用程序会使用持久对象,内存使用有可能迅速地增长。当应用程序需要的内存超出堆的最大值时虚拟机就会提示内存溢出,并且导致应用服务崩溃。因此一般建议堆的最大值设置为可用内存的最大值的80%。 Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,需要调大。 Windows下,在文件{tomcat_home}/bin/catalina.bat,Unix下,在文件{tomcat_home}/bin/catalina.sh的前面,增加如下设置:JAVA_OPTS='-Xms【初始化内存大小】-Xmx【可以使用的最大内存】'需要把这个两个参数值调大。例如:JAVA_OPTS='-Xms256m -Xmx512m',表示初始化内存为256MB,可以使用的最大内存为512MB。 另外需要考虑的是Java提供的垃圾回收机制。虚拟机的堆大小决定了虚拟机花费在收集垃圾上的时间和频度。收集垃圾可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。如果你把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。在基准测试的时候,为保证最好的性能,要把堆的大小设大,保证垃圾收集不在整个基准测试的过程中出现, 如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过3-5秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的80%作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。 内部调优NIO而NIO则是使用单线程(单个CPU)或者只使用少量的多线程(多CPU)来接受Socket,而由线程池来处理堵塞在pipe或者队列里的请求.这样的话,只要OS可以接受TCP的连接,web服务器就可以处理该请求。大大提高了web服务器的可伸缩性。<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443" />启之后,进行测试,被打开nio配置,启动时的信息,如下: 2014-2-1 13:01:01 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 信息: Using a shared selector for servlet write/read 2014-2-1 13:01:01 org.apache.coyote.http11.Http11NioProtocol init 信息: Initializing Coyote HTTP/1.1 on http-8080 禁止DNS查找有时候我们的应用可能要记录客户端的信息,两种方式,一是记录客户端的数字IP地址,另一个是在DNS数据中查找真实的主机名。DNS查找会增加网络通信,以致造成了网络延迟。要消除这个延迟,我们可以禁掉DNS查找。这时我们的应用再调用getRemoteHost( )方法时,它就只会得到数字IP地址。这个配置是在Tomcat的serve.xml文件中,Connector对象的enableLookups属性 <Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="8080"minProcessors="5" maxProcessors="75" enableLookups="true" redirectPort="8443" acceptCount="10" debug="0" connectionTimeout="20000" useURIValidationHack="false"/>在生产系统中,除非应用要得到所有客户端真实的主机名,这个通常是建议禁掉的。实在不行这样的工作我们可以在Tomcat的外面做。在一个流量较小的Servr这种修改的效果可能不是十分明显,但是对于某些站点来说,也许突然之间流量暴增也说不定呢,比如,前段时间的奥运票务网站,哈哈! 线程调整另一个影响性能的是Connector所使用的进程数。Tomcat使用一个线程池来提高请求响应的速度。Java中的一个线程单独同操作系统交互,并且有它自己的本地内存,以及同进程内的其他线程分享部分共享内存。我们可以通过修改Connector对象的minProcessors和maxProcessors来控制线程数。这个数字也许在刚上线的时候进行了适当的设置,可是当用户数变多的时候我们就要增加配置数了。minProcessors值应该设置的足够大来处理最小负载。当用户变多的时候,Tomcat会分配更多的线程,不超过maxProcessors。上限也一定要设置的适当,以免使server的内存超过JVM的内存限制而挂掉。 加速JSP的编译第一次访问JSP的时候,它会被转换成Java servlet源码,然后编译成二进制代码。这个过程中,我们是可以控制所使用的编译器的。默认情况,Tomcat所使用的是和命令行上执行javac时同样的编译器。其实有更快的编译器的,我们可以利用这些来提高JSP的编译速度。 附录server.xml在tomcat配置文件server.xml中的配置中,和连接数相关的参数有:_ minProcessors:最小空闲连接线程数,用于提高系统处理性能,默认值为10_ maxProcessors:最大连接线程数,即:并发处理的最大请求数,默认值为75 acceptCount:允许的最大连接数,即等待队列,指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。应大于等于maxProcessors,默认值为100, 和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数应同时加大这两个参数。web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。_ enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false_ connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。_ maxThreads="500" 表示最多同时处理500个连接_ minSpareThreads="100" 表示即使没有人使用也开这么多空线程等待_ maxSpareThreads="300" 表示如果最多可以空75个线程,例如某时刻有80人访问,之后没有人访问了,则tomcat不会保留80个空线程,而是关闭5个空的。","tags":[{"name":"Tomcat","slug":"Tomcat","permalink":"http://www.updatecg.xin/tags/Tomcat/"}]},{"title":"【Apache Kafka 细节一】Kafka安装","date":"2017-08-25T03:23:02.000Z","path":"2017/08/25/【Apache Kafka 细节一】Kafka安装/","text":"因七牛图片丢失、后续不上 环境配置 操作系统:Cent OS 7 / Ubuntu 15.04 Kafka版本:kafka_2.10-0.10.0.0.tgz Kafka官网下载地址:http://kafka.apache.org/downloads JDK版本:1.7.0_51 / 1.8.0_45 操作 检查liunx jdk版本 解压kafka_2.10-0.10.0.0.tgz 由于kafka用到了zookeeper,所以需要先启动zookeeper(具体为什么后续会有描述),启动过程中可能会出现的错误,如下: 应该确保已经安装JDK,并设定好JAVA_HOME,CLASSPATH,PATH这些环境变量。否则会提示:","tags":[{"name":"Kafka","slug":"Kafka","permalink":"http://www.updatecg.xin/tags/Kafka/"}]},{"title":"八幅漫画理解使用 JSON Web Token 设计单点登录系统","date":"2017-08-25T02:26:02.000Z","path":"2017/08/25/八幅漫画理解使用 JSON Web Token 设计单点登录系统/","text":"所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户访问网站时可以使用其账户,而不需要再次登录的机制。 小知识:可别把用户认证和用户授权(Authorization)搞混了。用户授权指的是规定并允许用户使用自己的权限,例如发布帖子、管理站点等。 首先,服务器应用(下面简称“应用”)让用户通过Web表单将自己的用户名和密码发送到服务器的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。 接下来,应用和数据库核对用户名和密码。 核对用户名和密码成功后,应用将用户的id(图中的user_id)作为JWT Payload的一个属性,将其与头部分别进行Base64编码拼接后签名,形成一个JWT。这里的JWT就是一个形同lll.zzz.xxx的字符串。 应用将JWT字符串作为该请求Cookie的一部分返回给用户。注意,在这里必须使用HttpOnly属性来防止Cookie被JavaScript读取,从而避免跨站脚本攻击(XSS攻击)。 在Cookie失效或者被删除前,用户每次访问应用,应用都会接受到含有jwt的Cookie。从而应用就可以将JWT从请求中提取出来。 应用通过一系列任务检查JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。 应用在确认JWT有效之后,JWT进行Base64解码(可能在上一步中已经完成),然后在Payload中读取用户的id值,也就是user_id属性。这里用户的id为1025。 应用从数据库取到id为1025的用户的信息,加载到内存中,进行ORM之类的一系列底层逻辑初始化。 应用根据用户请求进行响应。 和Session方式存储id的差异Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。 而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员 虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘I/O而言或许是半斤八两。具体是否采用,需要在不同场景下用数据说话。 单点登录Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如: www.taobao.com nv.taobao.com nz.taobao.com login.taobao.com 所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。 使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。因此,我们只需要将含有JWT的Cookie的domain设置为顶级域名即可,例如 Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com 注意事项注意domain必须设置为一个点加顶级域名,即.taobao.com。这样,taobao.com和*.taobao.com就都可以接受到这个Cookie,并获取JWT了。","tags":[{"name":"Token","slug":"Token","permalink":"http://www.updatecg.xin/tags/Token/"}]},{"title":"Nginx转发给Tomcat页面时,端口号丢失了","date":"2017-08-24T08:26:02.000Z","path":"2017/08/24/Nginx转发给Tomcat页面时,端口号丢失了/","text":"前段时间,将项目部署在现网时发现当执行登录以及退出时,发现所请求地址的端口默认丢失了。 因为之前项目没有问题,但是后来电信整体对所有项目进行漏洞扫描,发现此项目存在“登录前后的会话未更新”,修复后部署上线就发现此问题。所以最初以为是此问题造成的,就侧重对此问题进行追查,但是一直没有找到原因。接着,我在本地服务以及公司云服务进行测试发现均未发现此问题,就特别纳闷儿了。最后发现,是因为本地服务以及云服务器没有通过Nginx转发到tomcat,现网环境使用了nginx + tomcat的架构, 采用nginx做了一层负载均衡代理,于是查看webapp获取到的requestURL,发现根本就没有浏览器发送的原始URL中的端口。问题就在于此, 浏览器发送的请求是交给nginx,而nginx转发请求给tomcat时,端口号已经丢失掉了, 解决方式在网上找了下原因,发现Nginx配置有问题。 网站页面中直接读取了服务器ip和端口号。 在网站服务端不能正确获取到port.或者做重定向的时候地址总是丢掉端口(port)。 最简单的解决方案,修改Nginx的配置文件: server { listen 80; server_name www.xxx.cn; server_name_in_redirect off; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { proxy_pass http://***********:8080/; } } 默认请求location / { proxy_pass http://***********:8080/; } } 如上才是正确的配置nginx。其中的proxy_set_header Host $host:$server_port; 设置转发端口。这一行是关键。","tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.updatecg.xin/tags/Nginx/"}]},{"title":"Me","date":"2016-09-30T16:00:00.000Z","path":"2016/10/01/chenwu/","text":"陈 武 带着责任做事 带着稳重的心态处事 路漫漫兮。。。。 { "Name": "ChenWu", "Age":"28", "School":"West Branch", "Address":"ChengDu,China", "Email":"chenwu_cg@126.com", "skill":[{ "Spring-Cloud":"80%", "Spring-Boot":"60%", "Java":"70%", "Linux":"50%", "Docker":"60%", "Kubernates":"40%", "Mq":"60%", "Hadoop":"40%", "Hive":"40", "Mongo、Redis、Mysql、Sqlserver":"60", "K8S":80%" }], "Blog":"www.updatecg.xin" } 友情链接 杨超 木易博客 http://yangxx.net/","tags":[]}]