paulwong

2015年9月8日 #

2024 年 15 大软件开发趋势

步入 2024 年,在技术创新和不断变化的市场需求的推动下,软件开发格局继续呈指数级发展。对于企业和开发人员来说,紧跟这些趋势不仅有益,而且对于保持竞争力和成功至关重要。在本文中,我们探讨了预计将在 2024 年产生重大影响的关键软件开发趋势。

2024年软件工程通用原理

定义 2024 年 IT 行业的通用软件开发方法包括人工智能机器学习技术的进一步集成、区块链的利用和多运行时微服务。AR和VR的扩展应用也将继续塑造该行业。此外,程序员将更加重视网络安全和可持续软件开发。我们将在本节中详细探讨这些趋势。

人工智能和机器学习集成

人工智能和机器学习不再是流行词;它们已经成为流行语。它们是现代软件开发不可或缺的组成部分,为功能和性能设定了新的标准。从预测算法到自动代码审查,人工智能/机器学习技术正在提高各个行业的效率和能力。

2023 年最引人注目的突破之一是引入了先进的 ChatGPT 功能,其中包括代码和文本生成功能,以及基于文本提示的人工智能驱动图像创建的重大发展。

开发人员越来越多地使用人工智能驱动的编码工具。这不仅加快了编码过程,还有助于减少人为错误。例如,GitHub 的Copilot使用人工智能向开发人员实时建议代码片段和整个功能。同样, Tableau等人工智能驱动的分析工具使企业能够比以往更有效地从数据中获取洞察。

毫无疑问,2024 年将是这些技术进一步发展和集成的一年,特别是在自动化文本、编码和可视化任务方面。

超越加密货币的区块链

区块链正在超越加密货币领域找到立足点。优先考虑增强安全性和卓越质量的移动应用程序激增,导致基于区块链的应用程序的采用增加。

面向区块链的软件(BOS)系统的基本特征包括:

  • 数据复制:数据在数千个系统中复制和存储,显着增强数据安全性。
  • 要求验证:在进行任何交易之前,BOS 系统会检查交易要求,以确保它们符合成功验证的标准。
  • 顺序交易日志记录:BOS 将交易记录在按时间顺序排列的日志中,该日志由通过共识算法设置的互连块组成。
  • 公钥加密:BOS中的交易过程基于公钥加密,确保交易安全、可验证。

然而,区块链也有其局限性:可扩展性和能源消耗仍然是其更广泛采用的障碍。

多运行时微服务

微服务架构是一种将软件应用程序开发为一套小型、可独立部署的模块化服务的方法,每个服务都在自己的进程中运行,并与轻量级机制(通常是基于 HTTP 的 API)进行通信。

到2024年,微服务架构预计将继续增长,逐步演进为多运行时微服务。这也称为 MACH 架构,该术语由 Microservices-based、API-first、Cloud-native 和 Headless 的首字母创建。MACH架构允许不同的服务用不同的编程语言编写,使用不同的数据存储技术,并部署在不同的运行环境上。运行时的多样性迎合根据每个服务的特定需求和特征,为应用程序的每个组件提供更加定制和优化的方法。

多运行时微服务架构的主要优势是能够利用各种技术和平台的优势。例如,需要高计算能力的服务可以部署在专门为此类任务设计的运行时环境上,而处理实时数据处理的另一个服务可以利用针对速度和低延迟进行优化的不同环境。这种方法不仅可以确保每项服务在其理想环境中运行,而且还可以简化更新和维护,因为一项服务的更改不一定会影响其他服务。

此外,多运行时微服务支持更敏捷的开发流程,允许团队同时处理不同的服务而无需依赖。

2024 年网络安全处于前沿

网络威胁的日益复杂性使安全性成为 2024 年软件开发的一个重要方面。集成先进的安全协议和利用人工智能进行威胁检测正在成为标准做法。重点正在从被动安全措施转向主动安全措施:

  • 强调 DevSecOps:公司正在将安全性集成到其 DevOps 流程中,创建一种文化,让安全性成为所有利益相关者的共同责任。这种方法确保安全考虑成为整个软件开发生命周期不可或缺的一部分。
  • 零信任架构:传统的基于边界的安全模型正在被零信任框架所取代,零信任框架的运行原则是“从不信任,始终验证”。这意味着验证每个用户和设备,无论它们是在组织网络内部还是外部。
  • 加密的使用增加:随着数据泄露事件的增加,使用强大的加密方法来保护传输中和静态数据的趋势日益明显。先进的加密技术(例如同态加密)正在获得关注,允许在加密的情况下处理数据。
  • 关注安全代码实践:越来越重视对开发人员进行安全编码实践培训。这包括定期代码审查、漏洞测试以及使用静态和动态分析工具来识别和减少开发阶段的安全缺陷。
  • 网络安全网格的兴起:这个概念指的是一种灵活的、模块化的安全方法,其中每个设备都有自己的安全性,例如防火墙和网络防护措施。它有助于创建响应能力更强、适应性更强的安全基础设施,能够处理现代网络威胁的动态特性,使整个网络更加安全。

AR和VR的进一步采用

随着 AR 和 VR 技术变得越来越容易获得,多个行业对此类应用程序的需求正在猛增:

  • 教育:VR 改变了教育,支持交互式历史、地理和科学课程,并通过虚拟手术模拟提供无风险的医疗培训。例如,通过 Google Expeditions 和其他教育 AR 应用程序,学生可以探索历史遗址、解剖虚拟动物或检查复杂主题的 3D 模型。
  • 医疗保健:例如 AR 应用程序 AccuVein 可以帮助定位静脉,以便更轻松地插入针头,而手术规划工具则可以将 3D 模型叠加到患者的解剖结构上,以提供精确的手术指导。
  • 商业:VR 在商业中越来越多地用于原型设计、员工培训和客户服务。在房地产行业,公司利用 VR/AR 提供虚拟财产游览和 AR 应用程序,以便在购买前直观地看到家具或装修在空间中的外观。

我们期待 2024 年出现的令人兴奋的发展包括:

  • 超逼真的虚拟现实:VR 现在可以模拟现实世界的感觉,例如下雨的感觉或夏季草地的气味,模糊了虚拟与现实之间的界限。而且这种趋势将会继续增长。
  • 社交 VR 平台的扩展:社交 VR 平台允许实时交互、举办虚拟派对、参加音乐会和参与多人游戏。
  • 人工智能在 VR 中的集成:人工智能通过适应用户行为、创建响应个人偏好和行为的动态环境来个性化体验。

可持续软件开发

随着环境问题的日益严重,绿色计算和可持续软件实践越来越受到关注。开发人员越来越关注环保解决方案,支持绿色软件基金会和可持续网络宣言等促进节能编码实践的举措。这需要开发减少服务器处理、加载时间和数据请求的代码。

可持续软件开发的关键方面包括:

  • 软件优化:简化代码以减少能源使用并提高性能。
  • 部署:仅根据需要使用资源,例如惰性函数和基于云的应用程序,以最大限度地减少能源浪费。
  • 集成:减少系统之间的数据处理,以避免不必要的数据使用。
  • 存储的数据:限制存储的数据量及其在系统中保留的时间长度。
  • 数据大小:尽可能使用较小尺寸的介质,以减少存储和处理需求。
  • 重构:定期更新软件以删除过时或未使用的功能。
  • 避免第三方组件:减少对消耗更多资源的大型外部组件的依赖。
  • 软件架构:使用提高效率和降低能耗的架构。
  • 数据中心选择:选择致力于绿色实践的托管服务。

计算

来年,我们预计关键计算领域将取得进展:功能即服务、云和边缘计算,尤其是量子计算。

无服务器计算 (FaaS)

无服务器计算或函数即服务 (FaaS) 正在兴起,其中 AWS Lambda、Azure Functions 和 Google Cloud Functions 处于领先地位。FaaS 允许开发人员构建和运行应用程序和服务,而无需管理基础设施,从而实现更高效、更具成本效益的开发流程。

  • 一个值得注意的例子是Netflix在其流媒体平台中利用 AWS Lambda 实现各种目的。Netflix 利用 Lambda 来执行视频编码、处理用户身份验证和管理后端流程等任务。当用户上传视频时,Lambda 函数会被触发,将内容编码并处理为适合在不同设备上进行流式传输的各种格式。这使得 Netflix 能够根据需求动态扩展资源,而无需配置或管理服务器,从而确保为用户提供无缝的流媒体体验,同时优化成本。
  • Spotify 利用 Google Cloud Functions处理其音乐流媒体平台内的各种后端任务。触发功能来管理用户身份验证、处理用户生成的内容并为其音乐推荐算法执行后端任务,从而确保为用户提供无缝且个性化的体验。
  • IBM 的子公司 The Weather Company 使用IBM Cloud Functions来处理和分析大量天气数据。无服务器功能使他们能够执行实时数据处理、生成预报并根据用户的位置向用户提供个性化的天气警报,而无需管理底层基础设施。

 

这些FaaS解决方案以事件驱动架构为特点,根据请求自动触发执行,并根据需要调整资源使用。其可扩展性和响应能力简化了开发过程,特别适合高流量应用程序。无服务器计算越来越多地与物联网、聊天机器人和虚拟助手集成。

云计算的扩展

到 2024 年,云原生技术将发生重大演变。它们预计将变得更加用户友好,在其 IT 目标中提供增强的性能、节省成本和更大的灵活性。Amazon Web Services (AWS)、Microsoft Azure 和 Google Cloud Platform 扩展了其服务,提供更高级的分析、机器学习功能和更好的安全功能。

这促使公司迁移到云以实现更好的数据管理、增强协作并提高安全性。

边缘计算的浪潮

边缘计算是一种在网络边缘尽可能靠近数据源处理客户端数据的 IT 架构。通过使计算更接近数据源,边缘计算减少了延迟并增强了实时数据处理能力。

这种趋势对于需要即时数据分析的应用至关重要,例如自动驾驶汽车(例如,特斯拉的自动驾驶汽车依赖于边缘计算)和智能城市技术。在医疗保健领域,边缘计算可确保数据隐私,并实现基于人工智能的患者病情实时监控和分析。该技术还可以通过优化公交时刻表、调节交通车道以及潜在地引导自动驾驶车辆流量来改变城市交通管理,展示其在不同领域的多功能性和影响。边缘计算对于智能电网的采用至关重要,可以帮助企业有效管理能源消耗。

量子计算:新领域

量子计算是一种先进的计算形式,它使用量子比特而不是经典比特。利用叠加和纠缠等量子力学原理,它可以以传统计算机无法达到的速度处理数据。该技术对于密码学、优化和分子模拟等复杂任务特别有效,可提供指数级更快的解决方案。

虽然量子计算的广泛采用还有很长的路要走,但对软件开发的连锁反应已经开始显现。其中的领导者包括 IBM、微软、谷歌、D-Wave 和亚马逊等重量级公司。IBM 凭借其量子系统一号和二号成为领先者,具有高达 127 个量子位的强大处理器。微软专注于拓扑量子位,将其集成到其 Azure 云平台中以实现更广泛的可访问性。谷歌的量子人工智能实验室旨在开发实用的通用量子计算机,而 D-Wave 专门研究量子退火,解决复杂的优化挑战。亚马逊通过其 AWS 量子网络中心和 Amazon Braket 正在为量子计算创建广泛的基础设施。

编程语言

到 2024 年,编程将继续以 Python 为主,Rust 的采用率显着增加。

Python 占据主导地位

Python 仍然是一种占主导地位的编程语言,因其简单性、多功能性和强大的库支持而受到青睐。它广泛应用于网络开发、数据分析、人工智能和科学计算。

根据 PYPL 指数,Python 被列为最受欢迎的编程语言,增长率最高 (19%),该指数衡量语言教程在 Google 上的搜索频率。

2023 年 Stack Overflow 调查将 Python 确定为开发人员最想要学习的语言。自 2012 年以来,Python 首次超越 Java,不再只是排名前两位的 Web 应用程序开发语言之一。它还在五年内三次荣获TIOBE年度编程语言,这是对年度评分增幅最大的语言的认可。Python 广泛的库范围可以轻松集成到代码中并扩展到更大的应用程序,为 Web 和桌面应用程序开发(包括系统操作)提供了巨大的可能性。

Rust 采用率的增长

Rust 编程语言的采用正在增加,特别是在性能和安全性是关键优先事项的领域。其独特的功能使其成为系统级编程的理想选择。值得注意的是,Rust 越来越多地用于嵌入式系统,其防止内存错误和确保线程安全的能力至关重要。此外,其在云基础设施中的部署凸显了其处理高性能计算任务的可靠性和效率。

应用开发

在应用程序领域,重要趋势包括低代码和无代码平台的广泛采用、跨平台开发的进步以及渐进式 Web 应用程序的使用增加。

低代码和无代码平台的兴起

低代码和无代码平台的兴起正在推动软件开发的民主化。这些工具使个人能够以最少的编码知识构建和部署应用程序,从而显着缩短开发时间。

Microsoft Power Apps和Bubble等平台使非技术用户无需编写代码即可构建应用程序。这些工具在开发业务应用程序时特别受欢迎,允许公司在没有大型开发团队的情况下快速构建原型并部署解决方案。然而,此类平台无法解决复杂的定制开发任务。

渐进式 Web 应用程序 (PWA) 的增加

PWA(渐进式 Web 应用程序)比本机应用程序下载速度更快且资源占用更少。它们离线工作并在每次访问时自动刷新。从开发角度来看,它们具有成本效益和高效性,针对不同设备所需的版本较少,导致成本比原生应用低 3 至 4 倍。福布斯、星巴克和Pinterest等大公司都采用了这项技术。

PWA(渐进式 Web 应用程序)在开发人员中日益流行的一个关键因素是其平台独立性。这样就无需为移动设备、平板电脑和桌面创建单独的应用程序。开发的简单性并不是 PWA 节省成本的唯一好处。它们的创建速度也更快,维护成本也更低。

跨平台应用程序开发

自从移动应用程序出现以来,开发人员面临着是为 Android 和 iOS 创建两个本机应用程序还是创建单个跨浏览器应用程序的选择。原生应用程序由于其卓越的性能,在市场上占据主导地位。

2023 年的重大发展将在 2024 年继续获得动力,这是新工具的引入,这些工具能够交付用户友好的跨平台解决方案,同时降低开发成本。

跨平台应用程序具有多种优势:

  • 更广泛的覆盖范围:可在多种操作系统(iOS、Android)上使用,增加潜在的用户群。
  • 更快的开发时间:单个开发项目而不是多个本机应用程序可以加快流程。
  • 一致的用户体验:跨平台应用程序在不同平台上具有统一的外观和感觉,增强用户熟悉度。
  • 共享代码库:代码可重用性和开发效率。
  • 更轻松的部署:更新在所有平台上同时推出。
  • 资源效率:需要更少的资源和更小的开发团队。
  • 成本效益:由于单个代码库用于多个平台,因此降低了开发和维护成本。
  • 流行的跨平台框架包括:React Native、Flutter、Ionic 等。

结论

本文讨论的趋势将定义 2024 年及以后的软件开发领域。当我们应对这些变化时,负责任和道德的创新必须仍然是所有软件开发工作的基石。

我们收集最新趋势和最新发现,通过我们的博客分享。订阅我们的时事通讯并在社交媒体上关注我们,随时了解我们的帖子,以便在 2024 年保持在 IT 创新的最前沿。

posted @ 2024-02-28 20:54 paulwong 阅读(37) | 评论 (0)编辑 收藏

Micro Front

微服務很夯,那你有聽過微前端嗎?初探 Micro Frontends 程式架構
https://medium.com/starbugs/%E5%BE%AE%E6%9C%8D%E5%8B%99%E5%BE%88%E5%A4%AF-%E9%82%A3%E4%BD%A0%E6%9C%89%E8%81%BD%E9%81%8E%E5%BE%AE%E5%89%8D%E7%AB%AF%E5%97%8E-%E5%88%9D%E6%8E%A2-micro-frontends-%E6%9E%B6%E6%A7%8B-e0a8469be601

一起探討 Micro Frontends 的世界
https://blog.techbridge.cc/2019/01/12/micro-frontends-concept/


Micro Frontends
extending the microservice idea to frontend development
https://micro-frontends.org/

posted @ 2023-12-14 09:58 paulwong 阅读(26) | 评论 (0)编辑 收藏

STATE-MACHINE resource

实现一个状态机引擎,教你看清DSL的本质
https://blog.csdn.net/significantfrank/article/details/104996419

管理订单状态,该上状态机吗?轻量级状态机COLA StateMachine保姆级入门教程 
https://www.cnblogs.com/rude3knife/p/cola-statemachine.html

Spring-statemachine有限状态机(FSM)使用教程详解
https://blog.csdn.net/ZYC88888/article/details/112793317



https://github.com/alibaba/COLA/blob/master/cola-components/cola-component-statemachine/src/test/java/com/alibaba/cola/test/StateMachineChoiceTest.java


  

posted @ 2023-12-05 10:59 paulwong 阅读(53) | 评论 (0)编辑 收藏

如何保证同事的代码不会腐烂?一文带你了解 Alibaba COLA 架构

本文开始前,问大家一个问题,你觉得一份业务代码,尤其是互联网业务代码,都有哪些特点?

我能想到的有这几点:

  • 互联网业务迭代快,工期紧,导致代码结构混乱,几乎没有代码注释和文档
  • 互联网人员变动频繁,很容易接手别人的老项目,新人根本没时间吃透代码结构,紧迫的工期又只能让屎山越堆越大。
  • 多人一起开发,每个人的编码习惯不同,工具类代码各用个的,业务命名也经常冲突,影响效率。
  • 大部分团队几乎没有时间做代码重构,任由代码腐烂。

每当我们新启动一个代码仓库,都是信心满满,结构整洁。但是时间越往后,代码就变得腐败不堪,技术债务越来越庞大。

这种情况有解决方案吗?也是有的:

  1. 小组内定期做代码重构,解决技术债务。
  2. 组内设计完善的应用架构,让代码的腐烂来得慢一些。(当然很难做到完全不腐烂)
  3. 设计尽量简单,让不同层级的开发都能快速看懂并上手开发,而不是在一堆复杂的没人看懂的代码上堆更多的屎山。

而COLA,我们今天的主角,就是为了提供一个可落地的业务代码结构规范,让你的代码腐烂的尽可能慢一些,让团队的开发效率尽可能快一些。

https://github.com/alibaba/COLA

https://blog.csdn.net/significantfrank/article/details/110934799





posted @ 2023-12-05 10:31 paulwong 阅读(75) | 评论 (0)编辑 收藏

reinstall Mac OS

使用「磁碟工具程式」清除配備 Apple 晶片的 Mac
https://support.apple.com/zh-hk/HT212030

在 Mac 清除所有內容和設定
https://support.apple.com/zh-hk/HT212749

為 macOS 製作開機安裝程式
https://support.apple.com/zh-hk/HT201372

如何重新安裝 macOS
https://support.apple.com/zh-hk/HT204904

posted @ 2022-11-11 22:44 paulwong 阅读(150) | 评论 (0)编辑 收藏

How to Downgrade macOS Ventura to Monterey, Big Sur, or Earlier

https://www.drbuho.com/how-to/downgrade-macos


posted @ 2022-11-11 11:27 paulwong 阅读(131) | 评论 (0)编辑 收藏

difference between homebrew and homebrew cask

https://brew.sh/index_zh-tw

difference between homebrew and homebrew cask
https://www.zhihu.com/question/22624898

install jdk11 on Mac:
https://medium.com/@kirebyte/using-homebrew-to-install-java-jdk11-on-macos-2021-4a90aa276f1c



posted @ 2022-11-11 11:21 paulwong 阅读(143) | 评论 (0)编辑 收藏

install docker on Mac


https://yeasy.gitbook.io/docker_practice/install/mac

posted @ 2022-11-11 11:07 paulwong 阅读(131) | 评论 (0)编辑 收藏

MONGODB SPRING DISTINCT

SPRING 框架下 如果要做去重,在数据量大的时候会爆ERROR,可改用如下 写法:

    private boolean needReorderCheck(String requestId) {
        boolean result = false;
//        try(MongoCursor<String> mongoCursor = 
//                mongoTemplate.getCollection(mongoTemplate.getCollectionName(AccountNumProductLineIndex.class))
//                             .distinct(KEY, Filters.eq(REQUEST_ID, requestId), String.class)
//                             .iterator()
//                )
        try(MongoCursor<Document> mongoCursor = 
                mongoTemplate.getCollection(mongoTemplate.getCollectionName(AccountNumProductLineIndex.class))
                             .aggregate(
                                 Arrays.asList(
                                    Aggregates.project(
                                                    Projections.fields(
                                                                    Projections.excludeId(),
                                                                   Projections.include(KEY),
                                                                   Projections.include(REQUEST_ID)
                                                                )
                                               ),
                                    Aggregates.match(Filters.eq(REQUEST_ID, requestId)),
                                    Aggregates.group("$" + KEY)
                                 )
                              )
                             .allowDiskUse(true)
                             .iterator();
        )
        {
            String key = null;
            boolean breakMe = false;
            LOGGER.info("needReorderCheck.key --> start");
            while(mongoCursor.hasNext()) {
                if(breakMe) {
                    mongoCursor.close();
                    break;
                }
                Document keyDocument = mongoCursor.next();
                key = keyDocument.getString("_id");
//                key = mongoCursor.next().getString(KEY);
//                LOGGER.info("needReorderCheck.keyDocument --> {}, key --> {}", keyDocument, key);
                try(MongoCursor<Document> indexMongoCursor = 
                        mongoTemplate.getCollection(AccountNumProductLineIndex.COLLECTION_NAME)
                                        .find(Filters.and(Filters.eq(REQUEST_ID, requestId), Filters.eq(KEY, key)))
                                        .iterator()
                )
                {
                    int preIndex = -1, currentIndex = -1;
                    Document preIndexDocument = null, currentIndexDocument;
                    while(indexMongoCursor.hasNext()) {
                        currentIndexDocument = indexMongoCursor.next();
//                        System.out.println(currentIndexDocument.toJson());
                        if(preIndexDocument != null) {
                             currentIndex = currentIndexDocument.getInteger(INDEX);
                             preIndex = preIndexDocument.getInteger(INDEX);
                             if(currentIndex - preIndex > 1) {
                                indexMongoCursor.close();
                                breakMe = true;
                                result = true;
                                break;
                            }
                        }
                        preIndexDocument = currentIndexDocument;
                    }
                }
            }
        }
        
        return result;
    }

posted @ 2022-10-18 10:22 paulwong 阅读(186) | 评论 (0)编辑 收藏

SPRING JSON TIMEZONE问题大汇总

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="America/Phoenix")
private Date date;

posted @ 2022-09-22 13:18 paulwong 阅读(188) | 评论 (0)编辑 收藏

Downloading Large Files using Spring WebClient

https://www.amitph.com/spring-webclient-large-file-download/

https://github.com/amitrp/spring-examples/blob/main/spring-webflux-webclient/src/main/java/com/amitph/spring/webclients/service/FileDownloaderWebClientService.java

import lombok.RequiredArgsConstructor;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;

@Service
@RequiredArgsConstructor
public class FileDownloaderWebClientService {
    private final WebClient webClient;

    /**
     * Reads the complete file in-memory. Thus, only useful for very large file
     
*/
    public void downloadUsingByteArray(Path destination) throws IOException {
        Mono<byte[]> monoContents = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToMono(byte[].class);

        Files.write(destination, Objects.requireNonNull(monoContents.share().block()),
                StandardOpenOption.CREATE);
    }

    /**
     * Reading file using Mono will try to fit the entire file into the DataBuffer.
     * Results in exception when the file is larger than the DataBuffer capacity.
     
*/
    public void downloadUsingMono(Path destination) {
        Mono<DataBuffer> dataBuffer = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToMono(DataBuffer.class);

        DataBufferUtils.write(dataBuffer, destination,
                StandardOpenOption.CREATE)
                .share().block();
    }

    /**
     * Having using Flux we can download files of any size safely.
     * Optionally, we can configure DataBuffer capacity for better memory utilization.
     
*/
    public void downloadUsingFlux(Path destination) {
        Flux<DataBuffer> dataBuffer = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToFlux(DataBuffer.class);

        DataBufferUtils.write(dataBuffer, destination,
                StandardOpenOption.CREATE)
                .share().block();
    }
}

posted @ 2022-09-22 13:14 paulwong 阅读(253) | 评论 (0)编辑 收藏

JAVA-SECURITY资源

加密与安全
https://www.liaoxuefeng.com/wiki/1252599548343744/1255943717668160

JAVA KEYSTORE 存储在MONGODB
默认情况下,证书是放保存在文件,如果要改成MONGODB做为存储界质,则要做以下改动:
https://github.com/jmkgreen/keystore-mongo/tree/master/keystore-mongo/src/main/java/com/github/jmkgreen/keystore/mongo

关于证书,这里有你想知道的一切
http://ifeve.com/%e5%85%b3%e4%ba%8e%e8%af%81%e4%b9%a6%e8%bf%99%e9%87%8c%e6%9c%89%e4%bd%a0%e6%83%b3%e7%9f%a5%e9%81%93%e7%9a%84%e4%b8%80%e5%88%87-md/#more-59405

posted @ 2022-07-18 11:09 paulwong 阅读(190) | 评论 (0)编辑 收藏

REDHEAD 8 LINUX 软件集合

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/deploying_different_types_of_servers/index


posted @ 2022-06-23 17:27 paulwong 阅读(120) | 评论 (0)编辑 收藏

LINUX YUM

列出某个软件的所有版本号:
yum module list nginx

Red Hat Enterprise Linux 8 for x86_64 - AppStream (RPMs)
Name                                         Stream                                         Profiles                                         Summary
nginx                                        1.14 [d]                                       common [d]                                       nginx webserver
nginx                                        1.16                                           common [d]                                       nginx webserver
nginx                                        1.18                                           common [d]                                       nginx webserver
nginx                                        1.20 [e]                                       common [d]                                       nginx webserver

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled


设定某个版本为默认版本
yum module enable nginx:1.20


安装默认版本
yum install nginx



posted @ 2022-06-23 17:21 paulwong 阅读(159) | 评论 (0)编辑 收藏

openJDK无法进行jmap

https://blog.csdn.net/qq_32447301/article/details/85109014

posted @ 2022-05-19 13:53 paulwong 阅读(169) | 评论 (0)编辑 收藏

12 Factor App Principles and Cloud-Native Microservices



https://dzone.com/articles/12-factor-app-principles-and-cloud-native-microser

posted @ 2022-05-18 15:59 paulwong 阅读(108) | 评论 (0)编辑 收藏

API接口中的敏感数据的加密与解密

     摘要: 签名 为防止request中的json在传输过程中被更改, 需要在传送双方保存一个字符串sinature-key 用SHA256 hash请求中的json字符串,结果为hash1 {"payload":hash1}以此为字符和sinature-key用JWS HS256算法进行签名,得到sinature1 在请求的json中加入字段:"sina...  阅读全文

posted @ 2022-05-11 14:37 paulwong 阅读(945) | 评论 (0)编辑 收藏

KEYCLOAK授权模式与实施


https://stackoverflow.com/questions/42186537/resources-scopes-permissions-and-policies-in-keycloak

posted @ 2022-04-21 15:32 paulwong 阅读(140) | 评论 (0)编辑 收藏

KEYCLOA+DMariaDB 在LINUX上的安装

下载KEYCLOAK与安装,可参见:
https://www.janua.fr/how-to-install-keycloak-with-mariadb/

启动时配置不通过localhost访问控制台:
#! /bin/bash

BIN_PATH=$(cd `dirname $0`; pwd)
IP=10.10.27.69
KEYCLOAK_OPT="-b ${IP} -Djboss.bind.address.management=${IP} -Dkeycloak.profile.feature.upload_scripts=enabled"
KEYCLOAK_OPT="${KEYCLOAK_OPT} -Djboss.socket.binding.port-offset=100 -Dkeycloak.frontendUrl=http://${IP}:81/auth "
#-Dkeycloak.hostname=${IP} -Dkeycloak.httpPort=81 -Dkeycloak.httpsPort=82

nohup ${BIN_PATH}/bin/standalone.sh ${KEYCLOAK_OPT} > /dev/null &

更改KEYCLOAK的DATASOURCE时,可直接更改默认的而无需重新配置:
https://medium.com/@pratik.dandavate/setting-up-keycloak-standalone-with-mysql-database-7ebb614cc229

KEYCLOAK的JBOSS管理界面地址改为非LOCALHOST:
-Djboss.bind.address.management=${IP}

如果是由NGINX过来的访问,这样前端的地址是和默认的不一样,需配置前端URL:
-Dkeycloak.frontendUrl=http://${IP}:81/auth

更改JVM大小standalone.conf:
#
# Specify options to pass to the Java VM.
#

JBOSS_JAVA_SIZING="-server -Xms3G -Xmx3G -Xmn512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m"

REFERENCE:
https://www.keycloak.org/docs/latest/server_installation/index.html#_hostname

posted @ 2022-04-14 15:15 paulwong 阅读(222) | 评论 (0)编辑 收藏

UML DIAGRAM资源

https://www.uml-diagrams.org/uml-25-diagrams.html


https://www.uml-diagrams.org/index-examples.html

posted @ 2022-02-19 16:55 paulwong 阅读(107) | 评论 (0)编辑 收藏

JENKINS 部署 agularjs

This article assumes that you have a running Jenkins instance on your Linux machine with a valid domain (not localhost), GitLab and that you are familiar with the Angular framework.

For Jenkins, please install the GitLab and NodeJS plugins. For simplicity’s sake, this article is going to use simple shell commands to run automated tests and to deploy an app to production.

***Note. If you can’t decide where to test all this, there is an article I wrote that might help you: CI/CD Cloud Voyage with Jenkins.

Configuring Gitlab and Jenkins

Jenkins: Access Rights to GitLab

In order to use GitLab with Jenkins, you’ll need to generate an access token in GitLab, which you can do in User menu > Settings > Access tokens

and configure GitLab Connection on Jenkins by adding the newly generated token.

In Jenkins, go to Manage Jenkins > Configure system and find the GitLab section.

To add a token that you previously generated, click on Add by the Credentials input and choose Jenkins. In the credentials dialog, choose GitLab API token in the Kind input and paste your token from GitLab into the API token input field. 

Jenkins: Configure NodeJSInstaller

In order to be able to run npm scripts, it is necessary to configure NodeJSInstaller. In Jenkins, go to Manage Jenkins > Global Tool Configuration > NodeJS installations.

Jenkins: Create CI build for Angular

In order to be able to run Angular tests and check your code style in Jenkins on the created merge request in GitLab you’ll have to:

1. Click on the New item link in the Jenkins dashboard

2. Enter a job name and choose Freestyle project

3. Choose the GitLab Connection that we’ve just created in the Gitlab Connection section.

4. Choose Git as your source code management. Enter your repository URL. Create new credentials on Jenkins. These credentials are for cloning the project. You use them to log in to Gitlab.

5. Next, configure build triggers, i.e. on which GitLab event to run a build. In this particular example, angular-ci-build is going to trigger when a new merge request gets created.

In this step, we need to go back to GitLab and create a hook that will trigger this build under Settings > Integrations. Copy the URL provided by Jenkins and paste it into the project hook form and finally click Add webhook.

6. Provide the configured NodeJsInstaller in the global configuration to be able to run npm commands.

7. And finally, in the Build section choose Add build step > Execute shell. Write shell scripts to test the Angular app code and run tests.

Click Save and we are good to go. At this point everything should work.

When you create a new merge request, GitLab should trigger angular-ci-build on Jenkins and you should see status pending on that particular merge request page.

When Jenkins is done, the status on GitLab should automatically be updated. Depending on whether the build passed or not, the merge button will change color.

Jenkins: Create CD Build for Angular

In order to be able to deploy Angular to another Linux machine, we need to:

Repeat steps 1–4 from Jenkins: Create CI Build for Angular, changing only the name of the build. This time, it can be angular-deploy. 

5. For step five, we now choose a different configuration for deployment. We are going to run this build when a merge request gets accepted.

Just like for the CI build, we have to create a new GitLab hook that will hit the Jenkins build endpoint.

6. This step is also the same as in CI; we need to provide the NodeJSInstaller we already configured globally.

7. This step is different from CI; this time we don’t have to test and check linting, but only build the application and copy-paste it to another machine with ssh.

If we are going to do it with ssh like in the example, we need to create a private and public key pair for the Jenkins user on the machine Jenkins is running on. The private key needs to stay on the Jenkins machine, and the public key needs to be copied to the remote machine.

With the scp command we simply copy our build to the remote machine. In this case, Jenkins does not have permission to put it anywhere but in the user folder. In the last step, we need to ssh into the remote machine and move our files (in this case to /var/www/html).

Voila, our app is deployed to the production server when the merge request is accepted via Jenkins.

Angular: Karma Unit Test Runner Configuration

To run Angular tests on Jenkins, we need to configure some parts of the karma.conf file. Below is the configuration that adds a custom launcher that runs ChromeHeadles.

module.exports = function(config) {   config.set({     basePath: "",     frameworks: ["jasmine", "@angular-devkit/build-angular"],     plugins: [       require("karma-jasmine"),       require("karma-chrome-launcher"),       require("karma-jasmine-html-reporter"),       require("karma-coverage-istanbul-reporter"),       require("@angular-devkit/build-angular/plugins/karma")     ],     client: {       clearContext: false // leave Jasmine Spec Runner output visible in browser     },     coverageIstanbulReporter: {       dir: require("path").join(__dirname, "../coverage/jenkins-test-app"),       reports: ["html", "lcovonly", "text-summary"],       fixWebpackSourcePaths: true     },     reporters: ["progress", "kjhtml"],     port: 9876,     colors: true,     logLevel: config.LOG_INFO,     autoWatch: true,     browsers: ["Chrome", "ChromeHeadless"],     singleRun: false,     restartOnFileChange: true,     customLaunchers: {       ChromeHeadless: {         base: "Chrome",         flags: [           "--headless",           "--disable-gpu",           "--no-sandbox",           "--remote-debugging-port=9222"         ],                },     }   }); };

We can then simply store our command in the package.json scripts property.

On Jenkins, we would now run our tests with npm run test:ci.

 "scripts": {     "ng": "ng",     "start": "ng serve",     "build": "ng build",     "test": "ng test",     "test:ci": "ng test --browsers=ChromeHeadless --watch=false",     "lint": "ng lint",     "e2e": "ng e2e"   },

I hope you enjoyed this article and that it was helpful in your quest for automating angular deployment and testing.

posted @ 2022-01-25 11:02 paulwong 阅读(130) | 评论 (0)编辑 收藏

SPRING INTEGRATION - 集群选主、分布式锁

集群通常是有多个相同的实例,但对于定时任务场景,只希望有一个实例工作即可,如果这个实例挂了,其他实例可以顶替。

这个问题的方案则是集群选主,一个集群中,只有一个LEADER,由LEADER负责执行定时任务工作。当LEADER被取消时,会在剩下的实例中再选LEADER。

持有分布式锁的实例则是LEADER。

SPRING INTEGRATION JDBC 则已提供相关功能。

pom.xml
        <dependency>
           <groupId>org.springframework.integration</groupId>
           <artifactId>spring-integration-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
           <groupId>org.flywaydb</groupId>
           <artifactId>flyway-core</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
        </dependency>

LeaderElectionIntegrationConfig.java
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.jdbc.lock.DefaultLockRepository;
import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
import org.springframework.integration.jdbc.lock.LockRepository;
import org.springframework.integration.support.leader.LockRegistryLeaderInitiator;

import com.paul.integration.leader.ControlBusGateway;
import com.paul.integration.leader.MyCandidate;

@Configuration
public class LeaderElectionIntegrationConfig {
    
    @Bean
    public List<String> needToStartupAdapterList(){
        return new CopyOnWriteArrayList<>();
    }
    
    @Bean
    public DefaultLockRepository defaultLockRepository(DataSource dataSource){
        DefaultLockRepository defaultLockRepository =
                new DefaultLockRepository(dataSource);
//        defaultLockRepository.setTimeToLive(60_000);
        return defaultLockRepository;
    }

    @Bean
    public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository){
        return new JdbcLockRegistry(lockRepository);
    }
    
    @Bean
    public MyCandidate myCandidate(
        ControlBusGateway controlBusGateway,
        List<String> needToStartupAdapterList
    ) {
        return new MyCandidate(controlBusGateway, needToStartupAdapterList);
    }
    
    @Bean
    public LockRegistryLeaderInitiator leaderInitiator() {
        return new LockRegistryLeaderInitiator(
                    jdbcLockRegistry(null), myCandidate(nullnull)
               );
    }
    
    
}


MyCandidate.java
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.leader.Context;
import org.springframework.integration.leader.DefaultCandidate;

import com.novacredit.mcra.mcracommon.integration.gateway.ControlBusGateway;

public class MyCandidate extends DefaultCandidate{
    
    private static final Logger LOG = LoggerFactory.getLogger(MyCandidate.class);
    
    private List<String> needToStartupAdapterList;
    
    private ControlBusGateway controlBusGateway;
    
    public MyCandidate(
        ControlBusGateway controlBusGateway,
        List<String> needToStartupAdapterList
    ) {
        this.controlBusGateway = controlBusGateway;
        this.needToStartupAdapterList = needToStartupAdapterList;
    }
    
    @Override
    public void onGranted(Context context) {
        super.onGranted(context);
        LOG.info("*** Leadership granted ***");
        LOG.info("STARTING MONGODB POLLER");
        needToStartupAdapterList
            .forEach(
                c -> {
//                    c = "@'testIntegrationFlow.org.springframework.integration.config."
//                            + "SourcePollingChannelAdapterFactoryBean#0'";
                    String command = c + ".start()";
                    LOG.info("-----{}", command);
                    controlBusGateway.sendCommand(command);
                }
             );
        LOG.info("STARTUP MESSAGE SENT");

    }

    @Override
    public void onRevoked(Context context) {
        super.onRevoked(context);
        LOG.info("*** Leadership revoked ***");
        LOG.info("STOPPING MONGODB POLLER");
        needToStartupAdapterList
            .forEach(
                c -> {
//                    c = "@'testIntegrationConfig.testIntegrationFlow."
//                            + "mongoMessageSource.inboundChannelAdapter'";
                    String command = c + ".stop()";
                    LOG.info("-----{}", command);
//                    controlBusGateway.sendCommand(command);
                }
             );
        LOG.info("SHUTDOWN MESSAGE SENT");
    }

}


ControlBusIntegrationConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.messaging.MessageChannel;

import com.paul.integration.gateway.ControlBusGateway;

@Configuration
public class ControlBusIntegrationConfig {
    
    @Bean
    public MessageChannel controlBusChannel() {
        return MessageChannels.direct().get();
    }
    
    @Bean
    public IntegrationFlow controlBusFlow() {
        return IntegrationFlows.from(controlBusChannel())
                    .log(LoggingHandler.Level.INFO, "controlBusChannel")
                    .controlBus()
                    .get();
    }
    
    @Bean
    public GatewayProxyFactoryBean controlBusGateway() {
        GatewayProxyFactoryBean gateway = new GatewayProxyFactoryBean(ControlBusGateway.class);
        gateway.setDefaultRequestChannel(controlBusChannel());
        gateway.setDefaultRequestTimeout(300l);
        gateway.setDefaultReplyTimeout(300l);
        return gateway;
    }
    
}


ControlBusGateway.java
public interface ControlBusGateway {
    
    public void sendCommand(String command);

}


各个应用实例运行时,其中的LockRegistryLeaderInitiator会自动运行,抢夺LEADER数据,最终只有一个实例夺取。之后再执行MyCandidate中的代码。







posted @ 2022-01-20 13:49 paulwong 阅读(445) | 评论 (0)编辑 收藏

MONGODB SHELL

mongo -u admin -p 123456 --authenticationDatabase admin
use admin
db.createUser({
 user : "paul",
 pwd : "123456",
 roles : [{role : "readWrite", db : "batch"}]
})

#增加权限
db.grantRolesToUser( 
  "paul"
  [
    { "role" : "dbOwner",
      "db" : "mcra"
    }
  ]
)

posted @ 2022-01-10 11:10 paulwong 阅读(210) | 评论 (0)编辑 收藏

OAUTH2 - 4流程如何选择?





https://developer.okta.com/docs/concepts/oauth-openid/#what-kind-of-client-are-you-building

posted @ 2022-01-06 13:37 paulwong 阅读(130) | 评论 (0)编辑 收藏

几个好看的免费UI

https://demos.creative-tim.com/light-bootstrap-dashboard-angular2/#/dashboard


https://www.creative-tim.com/product/light-bootstrap-dashboard-angular2

https://www.creative-tim.com/templates/angular-dashboard-bootstrap

https://coreui.io/angular/demo/free/2.11.1/#/base/tables

posted @ 2022-01-04 15:36 paulwong 阅读(170) | 评论 (0)编辑 收藏

Easily Secure your Microservices with Keycloak

https://www.doag.org/formes/pubfiles/11143470/2019-NN-Sebastien_Blanc-Easily_Secure_your_Microservices_with_Keycloak-Praesentation.pdf

posted @ 2022-01-03 10:03 paulwong 阅读(135) | 评论 (0)编辑 收藏

REDHEAD 7 LINUX 软件集合

https://www.softwarecollections.org/en/

不用再GOOGLE寻找安装方法。

安装MYSQL示例:
# 2. Install the collection:
$ sudo yum install rh-mariadb103

# 3. Start using software collections:
$ scl enable rh-mariadb103 bash

$ service rh-mariadb103-mariadb start 
$ mysql
$ mysqld

#开机加载命令
cp /opt/rh/rh-mariadb103/enable /etc/profile.d/rh-mariadb103.sh

posted @ 2021-12-22 14:28 paulwong 阅读(190) | 评论 (0)编辑 收藏

OIDC - KEYCLOAK - 自定义CLIENT SCOPE

当CLIENT或用户在KEYCLOAK中成功登录后,会返回JWT字符串,其中默认含有权限的信息,但此信息以内嵌的方式呈现,非常不方便。
"resource_access": {
    "app-springboot-confidential": {
      "roles": [
        "user"
      ]
    },
    "test-employee-service": {
      "roles": [
        "READ_EMPLOYEE"
      ]
    },
    "service-springboot": {
      "roles": [
        "READ_PRODUCTS"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links"
      ]
    },
    "test-department-service": {
      "roles": [
        "READ_DEPARTMENT"
      ]
    }
  }

  • 需要将权限的信息输出到一个KEY中,这时可以新增自定义CLIENT SCOPE。Mapper中新增KEYCLOAK已内置的【realm roles/client roles】,定义输出到JTW的字段名:my-roles。
  • 授权哪些CLIENT可以读取此CLIENT SCOPE.
  • 在登录参数scope中,加入此值:my-roles,这样在输出的JWT就会以平面的方式输出所有roles
"my-roles": [
    "user",
    "READ_EMPLOYEE",
    "READ_PRODUCTS",
    "manage-account",
    "manage-account-links",
    "READ_DEPARTMENT",
    "offline_access",
    "user"
  ]

  • SPRING SECURITY中取出权限信息:

@Bean
    public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter(ObjectMapper objectMapper) {
        
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("my-roles");
        
//        KeycloakRealmRoleConverter keycloakRealmRoleConverter = new KeycloakRealmRoleConverter(objectMapper);
        
        ReactiveJwtGrantedAuthoritiesConverterAdapter reactiveJwtGrantedAuthoritiesConverterAdapter = 
                new ReactiveJwtGrantedAuthoritiesConverterAdapter(
//                        new KeycloakRealmRoleConverter(objectMapper);
                        jwtGrantedAuthoritiesConverter
                    );
        
        ReactiveJwtAuthenticationConverter jwtConverter = new ReactiveJwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(reactiveJwtGrantedAuthoritiesConverterAdapter);
        
        return jwtConverter;
    }

  • 判断是否有权限

ServerHttpSecurity
            .authorizeExchange(
                a -> a.pathMatchers("/", "/error").permitAll()
                      .matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                      .pathMatchers(HttpMethod.GET, "/protected/**").hasRole("READ_DEPARTMENT")
                      .anyExchange()
                      .authenticated()
             )

posted @ 2021-12-22 11:15 paulwong 阅读(694) | 评论 (0)编辑 收藏

WEBFLUX + SPRING SESSION + REACTIVE MONGODB

添加依赖,pom.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
        
        
<!-- spring session with mongodb -->
<dependency>
   <groupId>org.springframework.session</groupId>
   <artifactId>spring-session-data-mongodb</artifactId>
</dependency>
        
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

配置文件,application.yaml

spring:
   session:
      store-type: mongodb
      timeout: 30s
      mongodb:
         collection-name: WEB_SESSIONS


java配置,HttpSessionConfiguration.java:

package com.paul.testmicroservicecommon.config;

import org.springframework.boot.autoconfigure.session.MongoSessionProperties;
import org.springframework.boot.autoconfigure.session.SessionProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.session.data.mongo.config.annotation.web.reactive.EnableMongoWebSession;

@EnableMongoWebSession
@EnableConfigurationProperties(MongoSessionProperties.class)
public class HttpSessionConfiguration {
    
    @Bean
    public ReactiveSessionRepositoryCustomizer<ReactiveMongoSessionRepository> customize(
        SessionProperties sessionProperties,
        MongoSessionProperties mongoSessionProperties
    ){
        return c -> {
            c.setMaxInactiveIntervalInSeconds((int)sessionProperties.getTimeout().getSeconds());
            c.setCollectionName(mongoSessionProperties.getCollectionName());
        };
    }

}

posted @ 2021-12-22 09:24 paulwong 阅读(275) | 评论 (0)编辑 收藏

OAUTH2 - 4流程




Authorization Code Grant




Implicit Grant Flow




Client Credential





Password Grant flow





https://itnext.io/an-oauth-2-0-introduction-for-beginners-6e386b19f7a9

posted @ 2021-12-16 16:14 paulwong 阅读(171) | 评论 (0)编辑 收藏

SPRING CLOUD - REACTIVE FEIGN

目前SPRING CLOUD(2020)尚未支持REACTIVE FEIGN,但官方推荐使用feign-reactive。

pom.xml
<dependency>
    <groupId>com.playtika.reactivefeign</groupId>
    <artifactId>feign-reactor-spring-cloud-starter</artifactId>
    <version>3.1.2</version>
    <type>pom</type>
</dependency>


LoanDecisionClientReactive.java
package com.paul.testspringcloudstream.loancheck.service;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import com.paul.testspringcloudstream.common.model.Loan;

import reactivefeign.spring.config.ReactiveFeignClient;
import reactor.core.publisher.Mono;

@ReactiveFeignClient(name = "loan-decision")
public interface LoanDecisionClientReactive {
    
    @PostMapping("/loan-decision")
    public Mono<Loan> getDecision(@RequestBody Loan loan);

}

LoanCheckConfiguration.java
@Configuration
@Import({
    MongoDbConsumerConfiguration.class,
})
@EnableDiscoveryClient
@EnableReactiveFeignClients("com.paul.testspringcloudstream.loancheck.service")
public class LoanCheckConfiguration {
}


使用同feign
@Autowired
private LoanDecisionClientReactive loanDecisionClientReactive;

Reference
https://blog.csdn.net/LCBUSHIHAHA/article/details/113817966

官方SAMPLE
https://github.com/kptfh/feign-reactive-sample

posted @ 2021-12-01 16:45 paulwong 阅读(558) | 评论 (0)编辑 收藏

Spring bootstrap.yml 不管用,失效解决方案(spring cloud 2020.x.x)

升级spring cloud版本之后发现bootstrap.yml 失效了,阅读官方文档得知,需要新增一个引用来开启bootstrap.xml文件的读取,新版spring cloud默认是关闭读取了。
增加依赖如下即可:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
官方文档:
https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#config-first-bootstrap

posted @ 2021-12-01 10:29 paulwong 阅读(642) | 评论 (0)编辑 收藏

SPRING REACTOR 之Flux和Mono

SPRING REACTOR 之Flux和Mono,有点象SPRING INTEGRATION的IntegrationFlow,有如下特点
  • 定义了针对某种类型数据的处理流程
  • 可以进行类型转换
  • 长期运行,除非被要求中止
  • 流程中的每种操作可以在新的线程中执行
  • 可以正常中止,如果中途有异常,则该流程也会中止
  • 要subscribe,流程才开始被启动
  • 可以分割成各个子流程
  • 可以聚合子流程
  • Mono发送一个数据,就发送中止信号
  • Flux发送任意数据,由程序决定何时发送中止信号

编程则比较简单,先根据不同的数据类型定义不同的Flux或Mono,业务操作用Function包装后,放在map/flatmap中,再调用subscribe启动流程。

posted @ 2021-11-23 14:30 paulwong 阅读(578) | 评论 (0)编辑 收藏

SPRING REACTOR 使用样例

SpringReactorTest.java

package com.paul.testreactivestream.reactor;


import java.util.List;

import org.junit.jupiter.api.Test;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

public class SpringReactorTest {
    
    private void subscribeAndEnd(Flux<?> flux) {
        
        flux.map(c -> String.format("[%s] %s", Thread.currentThread().getName(), c))
            .subscribe(System.out::println);
        
        flux.blockLast();
    }
    
    @Test
    public void createAFlux_just() throws InterruptedException {
        Flux<String> fruitFlux = 
                Flux.just("Apple", "Orange", "Grape", "Banana", "Strawberry")
                    .log()
                    ;
        fruitFlux.subscribe(
                     f -> System.out.println(
                                 String.format("[%s] Here's some fruit: %s", Thread.currentThread().getName(), f)
                             )
                  )
                 ;
        fruitFlux.blockLast();
        
//        Thread.currentThread().join();
    }
    
    @Test
    public void zipFluxesToObject() {
        Flux<String> characterFlux = 
                Flux.just("Garfield", "Kojak", "Barbossa");
        
        Flux<String> foodFlux = 
                Flux.just("Lasagna", "Lollipops", "Apples");
        
        Flux<String> zippedFlux = 
                Flux.zip(characterFlux, foodFlux, (c, f) -> c + " eats " + f);
        
        this.subscribeAndEnd(zippedFlux);
    }
    
    @Test
    public void map() {
        Flux<Player> playerFlux = 
                Flux.just("Michael Jordan", "Scottie Pippen", "Steve Kerr")
                    .map(n -> {
                        String[] split = n.split("\\s");
                        return new Player(split[0], split[1]);
                    })
                    ;
        this.subscribeAndEnd(playerFlux);
    }
    
    @Test
    public void flatMap() {
        Flux<Player> playerFlux = 
                Flux.just("Michael Jordan", "Scottie Pippen", "Steve Kerr")
                    .flatMap(
                        n -> Mono.just(n)
                                 .map(p -> {
                                    String[] split = p.split("\\s");
                                    return new Player(split[0], split[1]);
                                  })
                                 .subscribeOn(Schedulers.parallel())
                     );
        this.subscribeAndEnd(playerFlux);
    }
    
    @Test
    public void buffer() {
        Flux<List<String>> fruitFlux = 
                Flux.just(
                        "apple", "orange", "banana", "kiwi", "strawberry"
                     )
                    .buffer(3);
        this.subscribeAndEnd(fruitFlux);
    }
    
    @Test
    public void bufferAsyn() {
        Flux<String> flux =
            Flux.just(
                    "apple", "orange", "banana", "kiwi", "strawberry"
                 )
                .buffer(3)
                .flatMap(x ->
                    Flux.fromIterable(x)
                        .map(y -> y.toUpperCase())
                        .subscribeOn(Schedulers.parallel())
    //                    .log()
                 );
        this.subscribeAndEnd(flux);
    }
    
    @Test
    public void all() {
        Mono<Boolean> animalFlux = 
                Flux.just(
                        "aardvark", "elephant", "koala", "eagle", "kangaroo"
                     )
                    .all(c -> c.contains("a"))
                    ;
        animalFlux.map(c -> String.format("[%s] %s", Thread.currentThread().getName(), c))
                  .subscribe(System.out::println);
    
    }

}

posted @ 2021-11-23 13:59 paulwong 阅读(324) | 评论 (0)编辑 收藏

SPRING REACTOR资源

Spring Reactor 入门与实践
https://www.jianshu.com/p/7ee89f70dfe5

Project Reactor 核心原理解析
https://www.jianshu.com/p/df395eb28f69

Project Reactor 之 publishOn 与 subscribeOn
https://www.jianshu.com/p/769f6e9824fb

Spring响应式编程
https://blog.csdn.net/get_set/category_7484996.html









posted @ 2021-11-23 13:51 paulwong 阅读(105) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - @PollableBean for Reactive Suppliers

Supplier beans, or functions that only publish messages in Spring Cloud Stream, are a bit special in that they aren't triggered by the receiving of events like Function or Consumer beans. This means that you often need a way to trigger them to be executed periodically.

For imperative functions the framework by default "polls" a Supplier function every 1 second, but that duration is configurable using the spring.cloud.stream.poller.fixed-delay property.

However, for reactive functions supplying a Flux it is only triggered once by default. This is because a Flux itself is potentially an infinite stream of events so in many cases it will only need to be triggered once. But don't worry, if you want to periodically trigger a reactive Supplier because you are producing a finite stream of events you can still do so using @PollableBean. This annotation then allows you to configure how often the function is triggered using the same spring.cloud.stream.poller.fixed-delay property!

One example use case here could be periodically querying a data store and publishing each entry/row as an event. The number of rows in your data store is a finite number at any given time.

Example code:

@PollableBean 
public Supplier<Flux<String>> stringSupplier() { return () -> Flux.just("foo","bar","baz"); }


Reference:
https://solace.community/discussion/360/pollablebean-for-reactive-suppliers-in-spring-cloud-stream

posted @ 2021-11-23 10:03 paulwong 阅读(217) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - 从非SCS组件发送消息到SCS组件

在SPRING INTEGRATION中,如果要从非SPRING INTEGRATION代码发送MESSAGE到SPRING INTEGRATION程序,通常用BUS GATEWAY。

那么在SPRING CLOUD STREAM中,如果要从非SPRING CLOUD STREAM代码发送MESSAGE到SPRING CLOUD STREAM程序,通常就要先通知框架自动生成一个SOURCE。

application.property
spring.cloud.stream.source=supplier
spring.cloud.stream.bindings.supplier-out-0.destination=notification-events

java
streamBridge.send("supplier-out-0", userDto);

Reference:
https://blog.devgenius.io/event-driven-microservices-with-spring-cloud-stream-e034eee3f394

posted @ 2021-11-19 11:47 paulwong 阅读(240) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - Error Handling

如果Function中抛出异常,系统没有配置捕获异常,则异常消息会被丢弃。通常会进行配置。

@ServiceActivator(inputChannel = "my-destination.my-group.errors")
    public void handleError(ErrorMessage message) {
        Throwable throwable = message.getPayload();
        log.error("截获异常", throwable);

        Message<?> originalMessage = message.getOriginalMessage();
        assert originalMessage != null;

        log.info("原始消息体 = {}", new String((byte[]) originalMessage.getPayload()));
    }

详情参考:
https://www.itmuch.com/spring-cloud/spring-cloud-stream-error-handling/

posted @ 2021-11-17 10:50 paulwong 阅读(228) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - Function Component

如果要在SPRING CLOUD STREAM中和其他中间件打交道,如FILE、FTP、HTTP等,则要用到SPRING CLOUD FUNCTION。

组件地址:
https://github.com/spring-cloud/stream-applications/tree/main/functions

特殊组件,将FUNCTION变成HTTP ENDPOINTS:
https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-starter-function-web
https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-starter-function-webflux

posted @ 2021-11-15 17:40 paulwong 阅读(207) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - Routing Function

SPRING CLOUD STREAM内置了一个RoutingFunction,能将MESSAGE路由到应用的其他FUNCTION中。
对接RoutingFunction可发送消息到其外部DESTINATION中或用“|”连接符连接。

application.yaml
# This setting can increase or decrease the rate of message production (1000 = 1s)
#
 spring.cloud.stream.poller.fixed-delay=1000
#
 DefaultPollerProperties

# This setting can control which function method in our code will be triggered if there are multiple
#
 spring.cloud.function.definition=supplyLoan

# Give the autogenerated binding a friendlier name
spring:
   application:
      name: loan-check-rabbit
   banner:
      location: classpath:/banner-rabbit.txt
   cloud:
      #BindingServiceProperties
      stream:
         #StreamFunctionProperties
         function:
            definition: loadCheckerFunction;loanCheckerDecieder;loanCheckerConsumer;\
                        loanDeclinedConsumer;loanApprovedConsumer;loanCheckerProcessor|functionRouter
            routing:
               enabled: true
         #BindingProperties
         bindings:
            loanCheckerProcessor|functionRouter-in-0:
               destination: queue.pretty.log.messages
               binder: local_rabbit
               
            loanApprovedConsumer-in-0:
               destination: load.approved
               binder: local_rabbit
            loanDeclinedConsumer-in-0:
               destination: load.declined
               binder: local_rabbit
               
            loanCheckerDecieder-in-0:
               destination: queue.pretty.log.messages.222
               binder: local_rabbit
            loanCheckerDecieder-out-0:
               destination: queue.pretty.approved.messages
               binder: local_rabbit
            loanCheckerConsumer-in-0:
               destination: queue.pretty.approved.messages
               binder: local_rabbit
         #BinderProperties
         binders:
            local_rabbit:
               type: rabbit
               environment:
                  spring:
                     rabbitmq:
                        host: 10.80.27.69
                        port: 5672
                        username: guest
                        password: guest
                        virtual-host: my-virtual-host
                        
                        
logging:
   level:
      root: info
      org.springframework:
         cloud.function: debug
         #retry: debug


LoanCheckConfiguration.java
package com.paul.testspringcloudstream.loancheck.config;

import java.util.function.Consumer;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.function.context.MessageRoutingCallback;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;

import com.paul.testspringcloudstream.common.model.Loan;
import com.paul.testspringcloudstream.common.model.Status;
import com.paul.testspringcloudstream.loancheck.router.LoanCheckerRouter;
import com.paul.testspringcloudstream.loancheck.service.LoanProcessor;
import com.paul.testspringcloudstream.loancheck.service.LoanService;

@Configuration
public class LoanCheckConfiguration {
    
    private static final Logger log = LoggerFactory.getLogger(LoanCheckConfiguration.class);
    private static final Long MAX_AMOUNT = 10000L;
    private static final String LOG_PATTERN = "{} - {} {} for ${} for {}";
    
    @Autowired
    public void test(Consumer<Loan> loanCheckerConsumer) {
        log.info("{}", loanCheckerConsumer.getClass());
    }
    
    
    @Bean
    public Consumer<Loan> loanCheckerConsumer(){
        return loan -> 
            log.info(LOG_PATTERN, "loanCheckerConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
    }
    
    @Bean
    public Consumer<Loan> loanDeclinedConsumer(){
        return loan -> 
            log.info(LOG_PATTERN, "loanDeclinedConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
    }
    
    @Bean
    public Consumer<Loan> loanApprovedConsumer(){
        return loan -> 
            log.info(LOG_PATTERN, "loanApprovedConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
    }
    
    @Bean
    public MessageRoutingCallback loanCheckerRouter() {
        return new LoanCheckerRouter();
    }
    
    @Bean
    public Function<Loan, Loan> loanCheckerProcessor(
        LoanService loanService
    ){
        return loan -> loanService.check(loan);
    }
    
    @Bean
    public Function<Loan, Message<Loan>> loanCheckerProcessorBak(
        LoanService loanService
    ){
        return loan -> {
            Loan result = loanService.check(loan);
            String sendTo = Status.DECLINED.name().equals(result.getStatus()) ? 
                        LoanProcessor.DECLINED_OUT : LoanProcessor.APPROVED_OUT;
            
            return MessageBuilder.withPayload(result)
                        .setHeader("spring.cloud.stream.sendto.destination", sendTo)
                        .build();
        };
    }
    
    @Bean
    public Consumer<Loan> loanCheckerDecieder(StreamBridge streamBridge){
        return loan -> {
            log.info(LOG_PATTERN, "loanCheckerDecieder", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());

            if (loan.getAmount() > MAX_AMOUNT) {
                loan.setStatus(Status.DECLINED.name());
                streamBridge.send(LoanProcessor.DECLINED_OUT, "local_rabbit", loan);
            } else {
                loan.setStatus(Status.APPROVED.name());
                streamBridge.send(LoanProcessor.APPROVED_OUT, "local_rabbit", loan);
            }

            log.info(LOG_PATTERN, "loanCheckerDecieder", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
        };
    }

}


LoanCheckerRouter.java,将路由条件统一在此处
package com.paul.testspringcloudstream.loancheck.router;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.function.context.MessageRoutingCallback;
import org.springframework.messaging.Message;

import com.paul.testspringcloudstream.common.model.Loan;
import com.paul.testspringcloudstream.common.model.Status;

public class LoanCheckerRouter implements MessageRoutingCallback{
    
    private static final Logger log = LoggerFactory.getLogger(LoanCheckerRouter.class);

    @Override
    public String functionDefinition(Message<?> message) {
        
//        byte[] resultByte = (byte[])message.getPayload();
//        String resultString = new String(resultByte);
//        
//        return "loanDeclinedConsumer";
        
        Loan result = (Loan)message.getPayload();
        
        log.info("Loan status: {}", result.getStatus());
        
        return Status.DECLINED.name().equals(result.getStatus()) ? 
                    "loanDeclinedConsumer" : "loanApprovedConsumer";
    }

}

posted @ 2021-11-15 14:46 paulwong 阅读(294) | 评论 (0)编辑 收藏

RABBITMQ增加消息追踪

https://blog.csdn.net/as140507/article/details/101104900

posted @ 2021-11-15 14:08 paulwong 阅读(131) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM 3.x - Functional Programming Model

SPRING CLOUD STREAM 3.x 版本时,之前的一些编程模式,如@Enablebindding,@StreamListenner等注释被废弃了,这是由于一些框架的代码必需由用户编写,如配置框架用的Input MessageChannel,Output  MessageChannel,连接MessageHandler与MessageChannel等,被视为不必要的动作。为了简化用户代码,于是推出Functional Programming Model。

引入了新名词:Supplier、Function与Consumer。实际上这几个类可视为Adapter,如果之前已经有存在的Service类,且方法名为各种各样,可以重新包装成Supplier、Function与Consumer,并在固定的方法名:apply/get/accept中调用Service的方法。

Supplier

当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Output  MessageChannel,并连接上此Bean,后续只需要在BINDDING中加入对应的Destination Name,即可向BROKER发消息了。

Consumer

当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Input  MessageChannel,并连接上此Bean,后续只需要在BINDDING中加入对应的Destination Name,即可收到BROKER推送关于此Destination的消息了。

Function

当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Input和Output  MessageChannel,并连接上此Bean,后续只需要在BINDDING中分别对Input和Output MessageChannel加入对应的Destination Name1/Name2,即可收到BROKER推送关于此Destination的消息,也可以向BROKER发消息了。

与SPRING INTEGRATION的整合

如果要对消息进行复杂处理,如拆分消息、聚合消息、IF ELSE消息等,就要借助SPRING INTEGRATION了。

@Bean
    public IntegrationFlow upperCaseFlow(LoanService loanService) {
        return IntegrationFlows
                    //turn this IntegrationFlow as a gateway, here is a Function interface 
                    
//with loadCheckerFunction as bean name
                    .from(LoadCheckerFunction.class, gateway -> gateway.beanName("loadCheckerFunction"))
                    .handle(loanService, "check")
                    .logAndReply(LoggingHandler.Level.WARN);
    }

    public interface LoadCheckerFunction extends Function<Loan, Loan>{

    }

IntegrationFlows.from(Class<?> serviceInterface)是可以将本IntegrationFlow包装成serviceInterface的实现类,如果调用此接口,最终会返回IntegrationFlow最后一个步骤的实体,如果这个serviceInterface是Function的话,刚好和SPRING CLOUD STREAM对接上。

后续在spring.cloud.stream.function.definition加入此Bean的名称loadCheckerFunction,SPRING CLOUD STREAM就会帮你生成一个Input和Output  MessageChannel,并连接上此Bean,再在BINDDING中分别对Input和Output MessageChannel加入对应的Destination Name1/Name2,即可收到BROKER推送关于此Destination的消息,也可以向BROKER发消息。

application.yaml
# This setting can increase or decrease the rate of message production (1000 = 1s)
# spring.cloud.stream.poller.fixed-delay=1000

# This setting can control which function method in our code will be triggered if there are multiple
# spring.cloud.function.definition=supplyLoan

# Give the autogenerated binding a friendlier name

spring:
   application:
      name: loan-check-rabbit
   banner:
      location: classpath:/banner-rabbit.txt
   cloud:
      stream:
         function.definition: loadCheckerFunction
         #BindingProperties
         bindings:
            loadCheckerFunction-in-0:
               destination: queue.pretty.log.messages
               binder: local_rabbit
            loadCheckerFunction-out-0:
               destination: queue.pretty.approved.messages
               binder: local_rabbit
         #BinderProperties
         binders:
            local_rabbit:
               type: rabbit
               environment:
                  spring:
                     rabbitmq:
                        host: 10.80.27.69
                        port: 5672
                        username: guest
                        password: guest
                        virtual-host: my-virtual-host

Reference

https://spring.io/blog/2019/10/25/spring-cloud-stream-and-spring-integration

posted @ 2021-11-10 15:10 paulwong 阅读(367) | 评论 (0)编辑 收藏

在CENTOS LINUX上安装RABBITMQ

安装ERLANG

从这里下载0依赖的ERLANG安装包:
https://github.com/rabbitmq/erlang-rpm/releases 
象这种erlang-23.3.4.8-1.el7.x86_64.rpm含el7的是CENTOS7版本,含el8的是CENTOS8版本,安装脚本
yum install -y erlang-23.3.4.8-1.el7.x86_64.rpm

安装RABBITMQ

下载地址:https://github.com/rabbitmq/rabbitmq-server/releases
安装脚本:yum install -y erlang-23.3.4.8-1.el7.x86_64.rpm

拷贝配置文件

下载配置文件样例:https://github.com/rabbitmq/rabbitmq-server/blob/master/deps/rabbit/docs/rabbitmq.conf.example
粘贴并重命名文件:/etc/rabbitmq/rabbitmq.conf

开启WEB控制台

/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_management

配置guest可远程访问

## Uncomment the following line if you want to allow access to the
## guest user from anywhere on the network.
loopback_users.guest = false

配置开机启动

chkconfig rabbitmq-server on

启动实例

systemctl start rabbitmq-serve
systemctl stop rabbitmq-serve

访问控制台,guest/guest

http://10.80.27.69:15672/#/

Reference
https://www.cnblogs.com/ZhuChangwu/p/14093107.html
https://juejin.cn/post/6933040530519506957

posted @ 2021-11-08 09:27 paulwong 阅读(234) | 评论 (0)编辑 收藏

EVEN DRIVEN - SPRING CLOUD STREAM - SPRING CLOUD微服务的EVEN DRIVEN框架

通常微服务应用之间的通信是通过HTTP调用,吞吐性不建都高,高并发的场景建议使用EVENT DRIVEN的框架,即使用MESSAGE通信。

即A微服务应用将数据发送到MESSAGE BROKER中的某个DESTINATION,此DESTINATION是广播型,非点对点型。B微服务应用订阅此DESTINATION,当有新MESSAGE到达此DESTINATION时,MESSAGE BROKER会将此MESSAGE推送给B应用。所有对此MESSAGE有需要的应用均可订阅,从而收到此MESSAGE。

SPRING CLOUD 中EVENT DRIVEN的框架就是SPRING CLOUD STREAM。其底层是使用SPRING INTEGRATION实现。

SPRING CLOUD STREAM有以下新名词:

  • BINDER:
是对MESSAGE BROKER操作方法的抽象,即应用通过此BINDER操作MESSAGE BROKER。目前只实现了RABITMQ和KAFKA。
  • CHANNEL
MESSAGE从SPRING CLOUD STREAM传给应用或相反是通过CHANNEL传递的,这点和SPRING INTEGRATION是一样的。
  • SOURCE
MESSAGE从应用传给SPRING CLOUD STREAM的CHANNEL,叫@INPUT,包含这种CHANNEL的接口叫SOURCE。
  • SINK
MESSAGE从SPRING CLOUD STREAM传给应用的CHANNEL,叫@OUPUT,包含这种CHANNEL的接口叫SINK。
  • BIDDING
绑定哪个@INPUT或哪个@OUPUT与哪个DESTINATION发送或接收关系的MAPPING。
  • EnableBinding
应用启动时就会建立EnableBinding指定的接口中的CHANNEL
  • 消费者群组
默认下如果同一个应用部署了多个实例,则每个实例都会收到MESSAGE,这时如果设置了消费者群组名称,则同一个名称下的多个实例,只有一个能收到MESSAGE。
  • PARTITION
如果为MESSAGE指定规则,如MESSAGE某个字段值以A开头为一个规则,以B开头为一个规则,那么以A开头的MESSAGE会放到同一个分区中。

这样使用就很简单了,只要取得OUTPUT CHANNEL,就可以发送MESSAGE,将代码关联到INPUT CHANNEL,就能在收到MESSAGE时,相关代码就会被执行。

posted @ 2021-11-05 14:58 paulwong 阅读(189) | 评论 (0)编辑 收藏

OAUTH2 - SPRING SECURITY + KEYCLOAK

     摘要: 根据OAUTH2协议,如果需要用户协助的,则使用authorization_code流程,此时需要用户登录页面、CLIENT SERVER、RESOURCE SERVER和AUTHORIZATION SERVER,其中CLIENT SERVER是通过http调用RESOURCE SERVER的api,AUTHORIZATION SERVER使用现成的KEYCLOAK。如果不需要用户协助的,即SER...  阅读全文

posted @ 2021-11-03 16:58 paulwong 阅读(685) | 评论 (0)编辑 收藏

SPRING BOOT OAUTH2 + KEYCLOAK - service to service call

employee-service调用department-service,如果要按OAUTH2.0流程,只需要提供client-id和client-secrect即可。在KEYCLOAK中引入service-account,即配置该employee-service时,取消standard-flow,同时激活service-account。
employee-service的application.yaml文件,其中的public-key要从KEYCLOAK中取
server:
   port: 8090
# Can be set to false to disable security during local development
rest:
   security:
      enabled: true
      #issuer-uri: http://localhost:8080/auth/realms/dev
      api-matcher: /api/**
      cors:
         allowed-origins: '*'
         allowed-headers: '*'
         allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
         max-age: 3600

security:
   oauth2:
      resource:
         filter-order: 3
         id: test-employee-service
         token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
         user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
         jwt:
            key-value: | 
               -----BEGIN PUBLIC KEY-----
               MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
               -----END PUBLIC KEY-----

# To access another secured micro-service
      client:
         client-id: test-employee-service
         #client-secret: 25c33006-e1b9-4fc2-a6b9-c43dbc41ecd0
         user-authorization-uri: ${rest.security.issuer-uri}/protocol/openid-connect/auth
         access-token-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token
         scope: openid
         grant-type: client_credentials
         is-client-only: true

#Logging Configuration
logging:
   level:
      org.springframework.boot.autoconfigure.logging: INFO
      org.springframework.security: DEBUG
      org.arun: DEBUG
      root: INFO

application-dev.yaml
rest:
   security:
      issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart

department-service:
   url: http://10.80.27.69:8095/api/departments/1

security:
   oauth2:
      client:
         client-secret: db25cdbd-605b-429d-bd92-96705bdf1474

department-service的application.yaml
server:
   port: 8095
# Can be set to false to disable security during local development
rest:
   security:
      enabled: true
      #issuer-uri: http://localhost:8080/auth/realms/dev
      api-matcher: /api/**
      cors:
         allowed-origins: '*'
         allowed-headers: '*'
         allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
         max-age: 3600

security:
   oauth2:
      resource:
         filter-order: 3
         id: test-department-service
         token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
         user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
         jwt:
            key-value: | 
               -----BEGIN PUBLIC KEY-----
               MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
               -----END PUBLIC KEY-----

#Logging Configuration
logging:
   level:
      org.springframework.boot.autoconfigure.logging: INFO
      org.springframework.security: DEBUG
      org.arun: DEBUG
      root: INFO

application-dev.yaml
rest:
   security:
      issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart

employee-service的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.18.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.arun.springoauth</groupId>
    <artifactId>spring-oauth2-employee-service</artifactId>
    <version>1.0.0</version>
    <name>spring-oauth2-employee-service</name>
    <description>Employee Service</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.1.18.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <!-- <version>2.1.18.RELEASE</version> -->
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layout>ZIP</layout>
                    <excludes>
                        <exclude>
                            <groupId>*</groupId>
                            <artifactId>*</artifactId>
                        </exclude>
                    </excludes>
                    <includes>
                        <include>
                            <groupId>com.paul</groupId>
                        </include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

将jwt格式的access_token转成Authentication的类JwtAccessTokenCustomizer
package org.arun.springoauth.employee.config;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtAccessTokenConverterConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

@Configuration
public class JwtAccessTokenCustomizer extends DefaultAccessTokenConverter implements JwtAccessTokenConverterConfigurer {

    private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenCustomizer.class);

    private static final String CLIENT_NAME_ELEMENT_IN_JWT = "resource_access";

    private static final String ROLE_ELEMENT_IN_JWT = "roles";

    private ObjectMapper mapper;

    @Autowired
    public JwtAccessTokenCustomizer(ObjectMapper mapper) {
        this.mapper = mapper;
        LOG.info("Initialized {}", JwtAccessTokenCustomizer.class.getSimpleName());
    }

    @Override
    public void configure(JwtAccessTokenConverter converter) {
        converter.setAccessTokenConverter(this);
        LOG.info("Configured {}", JwtAccessTokenConverter.class.getSimpleName());
    }

    /**
     * Spring oauth2 expects roles under authorities element in tokenMap, but
     * keycloak provides it under resource_access. Hence extractAuthentication
     * method is overriden to extract roles from resource_access.
     *
     * 
@return OAuth2Authentication with authorities for given application
     
*/
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> tokenMap) {
        LOG.debug("Begin extractAuthentication: tokenMap = {}", tokenMap);
        JsonNode token = mapper.convertValue(tokenMap, JsonNode.class);
        Set<String> audienceList = extractClients(token); // extracting client names
        List<GrantedAuthority> authorities = extractRoles(token); // extracting client roles

        OAuth2Authentication authentication = super.extractAuthentication(tokenMap);
        OAuth2Request oAuth2Request = authentication.getOAuth2Request();

        OAuth2Request request = new OAuth2Request(oAuth2Request.getRequestParameters(), oAuth2Request.getClientId(),
                authorities, true, oAuth2Request.getScope(), audienceList, nullnullnull);

        Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(
                authentication.getPrincipal(), "N/A", authorities);
        LOG.debug("End extractAuthentication");
        return new OAuth2Authentication(request, usernamePasswordAuthentication);
    }

    private List<GrantedAuthority> extractRoles(JsonNode jwt) {
        LOG.debug("Begin extractRoles: jwt = {}", jwt);
        Set<String> rolesWithPrefix = new HashSet<>();

        jwt.path(CLIENT_NAME_ELEMENT_IN_JWT).elements().forEachRemaining(e -> e.path(ROLE_ELEMENT_IN_JWT).elements()
                .forEachRemaining(r -> rolesWithPrefix.add("ROLE_" + r.asText())));

        final List<GrantedAuthority> authorityList = AuthorityUtils
                .createAuthorityList(rolesWithPrefix.toArray(new String[0]));
        LOG.debug("End extractRoles: roles = {}", authorityList);
        return authorityList;
    }

    private Set<String> extractClients(JsonNode jwt) {
        LOG.debug("Begin extractClients: jwt = {}", jwt);
        if (jwt.has(CLIENT_NAME_ELEMENT_IN_JWT)) {
            JsonNode resourceAccessJsonNode = jwt.path(CLIENT_NAME_ELEMENT_IN_JWT);
            final Set<String> clientNames = new HashSet<>();
            resourceAccessJsonNode.fieldNames().forEachRemaining(clientNames::add);

            LOG.debug("End extractClients: clients = {}", clientNames);
            return clientNames;

        } else {
            throw new IllegalArgumentException(
                    "Expected element " + CLIENT_NAME_ELEMENT_IN_JWT + " not found in token");
        }

    }

}



Reference
https://medium.com/@bcarunmail/securing-rest-api-using-keycloak-and-spring-oauth2-6ddf3a1efcc2



posted @ 2021-10-26 17:06 paulwong 阅读(478) | 评论 (0)编辑 收藏

Nginx代理转发SFTP

https://blog.csdn.net/qq_27127385/article/details/103666143

posted @ 2021-10-15 10:04 paulwong 阅读(340) | 评论 (0)编辑 收藏

使用REST API与KEYCLOAK进行OUATH2协议的登录认证

KEYCLOAK是一套用户、WEB API登录管理,授权管理的WEB应用。
如果要访问受KEYCLOAK保护的REST API服务,则需要夹带一个ACCESS_TOKEN。

前端页面:
  • 前端页面一般是给用户使用的,则需要用户输入在KEYCLOAK中有效的用户名和密码,并提供CALL BAK的URL,提交给KEYCLOAK
    http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/auth?client_id=app-springboot-confidential&redirect_uri=http://10.80.27.69:8183/&response_type=code&scope=openid
  • 如果KEYCLOAK验证通过,则通知页面重导向回调的URL,并附上code=xxx,此code则是AUTHORIZATION_CODE
    http://10.80.27.69:8183/?session_state=2ad9ab98-6c39-43a8-872f-2112c27b74df&code=3f48ce19-58f9-45d9-8c09-30d492bf4b24.2ad9ab98-6c39-43a8-872f-2112c27b74df.bd7526ef-b1bf-447f-baef-b7dfd6f0df93
  • 回调的URL对应的SERVELET,取得AUTHORIZATION_CODE,并加上client_id和client_secrect,调用KEYLOAK的取ACCESS_TOKEN的HTTP API,取得ACCESS_TOKEN,返回给页面
    http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token
    client_id=app-springboot-confidential&client_secret=3acf7692-49cb-4c45-9943-6f3dba512dae&redirect_uri=http://10.80.27.69:8183/&grant_type=authorization_code&code=cc7ac566-90f9-404e-b88e-fa28037b07d1.591311e1-5380-46a2-9363-834f17337922.bd7526ef-b1bf-447f-baef-b7dfd6f0df93
  • 页面保存此ACCESS_TOKEN,就可以调用后台的各种API获取数据
    {
        "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJGSjg2R2NGM2pUYk5MT2NvNE52WmtVQ0lVbWZZQ3FvcXRPUWVNZmJoTmxFIn0.eyJleHAiOjE2MzQwMjA4ODksImlhdCI6MTYzNDAyMDU4OSwianRpIjoiNDAwOTQ4ZmQtMGU0MS00YWRjLTlhY2MtMzczZWM2NDVhNzM5IiwiaXNzIjoiaHR0cDovLzEwLjgwLjI3LjY5OjgxODAvYXV0aC9yZWFsbXMvcXVpY2tzdGFydCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJkZGVkMDA2YS0xY2QxLTRjODUtOTQ1MS0wMjFlZmY3OTFiMmUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhcHAtc3ByaW5nYm9vdC1jb25maWRlbnRpYWwiLCJzZXNzaW9uX3N0YXRlIjoiYzRlN2QzYTgtMDg2My00OTAwLTkxZmEtMGExYmFmYmRlNGU3IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYXBwLXNwcmluZ2Jvb3QtY29uZmlkZW50aWFsIjp7InJvbGVzIjpbInVtYV9wcm90ZWN0aW9uIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudElkIjoiYXBwLXNwcmluZ2Jvb3QtY29uZmlkZW50aWFsIiwiY2xpZW50SG9zdCI6IjEwLjEwLjIwLjU3IiwidXNlcl9uYW1lIjoic2VydmljZS1hY2NvdW50LWFwcC1zcHJpbmdib290LWNvbmZpZGVudGlhbCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcHAtc3ByaW5nYm9vdC1jb25maWRlbnRpYWwiLCJjbGllbnRBZGRyZXNzIjoiMTAuMTAuMjAuNTcifQ.Ut6aZ6E1d4Esz0gRv2ubxdvrxmGvZLHHZepD5pnGxlqb_yZ4Q82TdGTG0iL4JJn2NH3QAU501dhzzuv6-OT9BUBKP-4ufyKv2DxSvt3GgdN30au5JsATHFyOWuuZGRBd3iWcynf9u3OJnSkHEnrIwRYatgndLzy8dy3AeqF12CI",
        "expires_in": 300,
        "refresh_expires_in": 600,
        "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2MTlhMmJjOS0yMWIwLTRmNGMtODI4OC1kNTJmMjA3OWEzY2EifQ.eyJleHAiOjE2MzQwMjExODksImlhdCI6MTYzNDAyMDU4OSwianRpIjoiYTM0NTQ1MTYtMzc3NC00YmRlLTgzOTMtN2QyMTdkZjdkZmJkIiwiaXNzIjoiaHR0cDovLzEwLjgwLjI3LjY5OjgxODAvYXV0aC9yZWFsbXMvcXVpY2tzdGFydCIsImF1ZCI6Imh0dHA6Ly8xMC44MC4yNy42OTo4MTgwL2F1dGgvcmVhbG1zL3F1aWNrc3RhcnQiLCJzdWIiOiJkZGVkMDA2YS0xY2QxLTRjODUtOTQ1MS0wMjFlZmY3OTFiMmUiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBwLXNwcmluZ2Jvb3QtY29uZmlkZW50aWFsIiwic2Vzc2lvbl9zdGF0ZSI6ImM0ZTdkM2E4LTA4NjMtNDkwMC05MWZhLTBhMWJhZmJkZTRlNyIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCJ9.QhjkJBGz5UvwBF7xHM7_V_yjfF0lrA_EWzAVdFf-BRI",
        "token_type": "bearer",
        "not-before-policy": 0,
        "session_state": "c4e7d3a8-0863-4900-91fa-0a1bafbde4e7",
        "scope": "profile email"
    }
  • 这就是authorization_code流程

后端服务:
  • 如果是在一个API中要请求另外一个API的数据,不存在具体用户的情况
  • 需提供如下参数:client_id、client_secrect和grant_type,且grant_type=client_credentials,调用KEYLOAK的取ACCESS_TOKEN的HTTP API,取得ACCESS_TOKEN
    http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token
    client_id=app-springboot-confidential&client_secret=3acf7692-49cb-4c45-9943-6f3dba512dae&grant_type=client_credentials
  • 再将此ACCESS_TOKEN以Bearer TOKEN的方式调用别的的API
  • 这就是client_credentials流程

验证Access Token和获取Token元信息:
  • http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token/introspect
    client_id=app-springboot-confidential&client_secret=3acf7692-49cb-4c45-9943-6f3dba512dae
  • Access Token无效时返回:
    {
        "active": false
    }

刷新Token:
  • http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token
    client_id=app-springboot-confidential&client_secret=3acf7692-49cb-4c45-9943-6f3dba512dae&grant_type=refresh_token&refresh_token=asdfasd
  • 返回
    {
        "access_token": "eyJhbGciOiJSUzI1NiIsIn",
        "expires_in": 300,
        "refresh_expires_in": 1800,
        "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOi",
        "token_type": "Bearer",
        "not-before-policy": 1610728470,
        "session_state": "c1273eb5-f922-420c-b23a-854be9735c1d",
        "scope": "profile email"
    }



Reference:
https://blog.csdn.net/nklinsirui/article/details/112706006

https://www.baeldung.com/?s=keycloak

https://www.doag.org/formes/pubfiles/11143470/2019-NN-Sebastien_Blanc-Easily_Secure_your_Microservices_with_Keycloak-Praesentation.pdf




posted @ 2021-10-12 14:40 paulwong 阅读(732) | 评论 (0)编辑 收藏

Enterprise Architect VS Solution Architect VS Software Architect

  • Enterprise Architect
    定义企业的大概方向
  • Solution Architect
    定义系统使用哪些框架技术
  • Software  Architect
    定义系统行为
  • Technical  Architect
    定义有关部署所使用服务器

https://stackoverflow.com/questions/524941/whats-the-difference-between-solutions-architect-and-applications-architect

https://www.youtube.com/watch?v=zB9WuYE1REI

posted @ 2021-10-11 15:29 paulwong 阅读(150) | 评论 (0)编辑 收藏

SPRING CLOUD CONFIG有界面的配置管理中心




https://dyc87112.github.io/spring-cloud-config-admin/

posted @ 2021-10-07 16:55 paulwong 阅读(275) | 评论 (0)编辑 收藏

开源流程引擎哪个好,如何选型?

https://zhuanlan.zhihu.com/p/369761832

posted @ 2021-09-27 11:05 paulwong 阅读(213) | 评论 (0)编辑 收藏

Camunda流程引擎

http://shaochenfeng.com/camunda/

posted @ 2021-09-27 10:55 paulwong 阅读(164) | 评论 (0)编辑 收藏

Camunda/Flowable/Activiti技术发展史

https://blog.csdn.net/qq_30739519/article/details/86583765

posted @ 2021-09-27 10:45 paulwong 阅读(311) | 评论 (0)编辑 收藏

SPRING INTEGRATION - ENRICH

enrich时可以发起一个子流程,取得结果后再设置回当前的对象中。

package org.springframework.integration.stackoverflow.enricher;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.http.dsl.Http;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class SpringIntegrationEnricherApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringIntegrationEnricherApplication.class, args);
    }

    @Bean
    public IntegrationFlow jsonEnricherFlow(RestTemplate restTemplate) {
        return IntegrationFlows.from(Function.class)
                .transform(Transformers.fromJson(Map.class))
                .enrich((enricher) -> enricher
                        .<Map<String, ?>>requestPayload((message) ->
                                ((List<?>) message.getPayload().get("attributeIds"))
                                        .stream()
                                        .map(Object::toString)
                                        .collect(Collectors.joining(",")))
                        .requestSubFlow((subFlow) ->
                                subFlow.handle(
                                        Http.outboundGateway("/attributes?id={ids}", restTemplate)
                                                .httpMethod(HttpMethod.GET)
                                                .expectedResponseType(Map.class)
                                                .uriVariable("ids", "payload")))
                        .propertyExpression("attributes", "payload.attributes"))
                .<Map<String, ?>, Map<String, ?>>transform(
                        (payload) -> {
                            payload.remove("attributeIds");
                            return payload;
                        })
                .transform(Transformers.toJson())
                .get();
    }

}

https://stackoverflow.com/questions/58205432/spring-integration-enrich-transform-message-using-rest-call

https://www.tabnine.com/web/assistant/code/rs/5c781b6ae70f87000197ab9f#L312

posted @ 2021-09-21 13:40 paulwong 阅读(248) | 评论 (0)编辑 收藏

Java9之HttpClient

Java9之HttpClientAPI实战详解
https://blog.csdn.net/u014042066/article/details/78153653

Java 9 揭秘(14. HTTP/2 Client API)
https://www.cnblogs.com/IcanFixIt/p/7229611.html

Java JDK11(Java11)中设置HttpClient允许不安全的HTTPS连接
https://www.cjavapy.com/article/84/

posted @ 2021-09-03 14:04 paulwong 阅读(199) | 评论 (0)编辑 收藏

Java 9 Flow API 学习

https://mrbird.cc/Java-9-Flow-API-Learn.html

posted @ 2021-09-02 15:39 paulwong 阅读(186) | 评论 (0)编辑 收藏

httpClient连接自制SSL证书的rest服务

通常如果rest服务支持https,需申请收费的ssl证书,但也可自制这种证书。
httpClient进行链接时要进行相应的设置, 主要是设置SSLContext中的TrustSelfSignedStrategy

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class HttpClientConfiguration {
    
    
    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager(AbstractProperties kycProperties) {
        PoolingHttpClientConnectionManager result = 
                new PoolingHttpClientConnectionManager(
                        kycProperties.getHttpConnectionTimeToLiveMinu(), 
                        TimeUnit.MINUTES
                    );
        result.setMaxTotal(200);
        result.setDefaultMaxPerRoute(20);
        return result;
    }

    @Bean
    public RequestConfig requestConfig(AbstractProperties kycProperties) {
        return RequestConfig
                    .custom()
                  .setConnectionRequestTimeout(kycProperties.getHttpConnectionTimeout())
                  .setConnectTimeout(kycProperties.getHttpConnectionTimeout())
                  .setSocketTimeout(kycProperties.getHttpConnectionTimeout())
                  .build();
    }
    
    @Bean
    public SSLContext sslContext() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {

        return SSLContexts
                    .custom()
                    .loadTrustMaterial(nullnew TrustSelfSignedStrategy())
                    .build()
                    ;
    }

    @Bean
    public CloseableHttpClient httpClient(AbstractProperties kycProperties) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
        return HttpClients
                  .custom()
//                  .setConnectionManager(poolingHttpClientConnectionManager(null))
                  .setDefaultRequestConfig(requestConfig(null))
                  .setKeepAliveStrategy(
                          new MyConnectionKeepAliveStrategy(
                                  kycProperties.getHttpConnectionTimeToLiveMinu(), 
                                  TimeUnit.MINUTES
                              )
                   )
                  .setMaxConnTotal(200)
                  .setMaxConnPerRoute(20)
//                  .setConnectionTimeToLive(
//                          kycProperties.getHttpConnectionTimeToLiveMinu(), 
//                          TimeUnit.MINUTES
//                   )
                  .setSSLContext(sslContext())
                  .build();
    }

}

相应设置
http-connection-timeout: 30000
http-connection-time-to-live-minu: 5

posted @ 2021-09-01 14:24 paulwong 阅读(352) | 评论 (0)编辑 收藏

nginx 之 proxy_pass详解

https://www.jianshu.com/p/b010c9302cd0

posted @ 2021-08-30 15:16 paulwong 阅读(156) | 评论 (0)编辑 收藏

LINUX下循环读取文件参数并CURL远程API

一系列参数存于文本文件,需在LINUX下循环读取,之后以此参数进行CURL远程API调用,同时需记录每次CURL的总时间

参数文件,test1.json
{"ADDRESS_FREE":"XXX","NAME":{"SURNAME":"XXX","FIRST_NAME":"XXX"}}
{"ADDRESS_FREE":"XXX","NAME":{"SURNAME":"XXX","FIRST_NAME":"XXX"}}
{"ADDRESS_FREE":"XXX","NAME":{"SURNAME":"XXX","FIRST_NAME":"XXX"}}

test1.sh
#! /bin/bash

RESULT_FILE="result.csv"
echo "" > $RESULT_FILE
i=1
while read line || [[ "$line" ]] #In case the file has an incomplete (missing newline) last line, you could use this alternative:
do 
    echo "$i"
    printf "$i;$line;" >> $RESULT_FILE
    curl -w %{time_total} -o /dev/null -X POST -H "Content-Type:application/json" -d "$line" http://ip:port  >> $RESULT_FILE
    #printf "\n\r" >> $RESULT_FILE
    echo "" >> $RESULT_FILE
    #i=$(( $i + 1 ))
    (( i++ ))
done < test1.json

Reference:
https://stackoverflow.com/questions/30988586/creating-an-array-from-a-text-file-in-bash









posted @ 2021-08-26 15:40 paulwong 阅读(499) | 评论 (0)编辑 收藏

免注册JDK下载

https://repo.huaweicloud.com/java/jdk/

posted @ 2021-08-25 16:17 paulwong 阅读(247) | 评论 (0)编辑 收藏

SPRING INTEGRATION RETRY

当使用httpOutBoundGateway时,有时会碰到网络抖动问题而出现连接异常,这时应该有个重试机制,如隔多少秒重试,重试多少次后放弃等。
默认是重试3次,每次重试间隔是20秒。

@SpringBootApplication
public class SpringIntegrationDslHttpRetryApplication {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext =
                SpringApplication.run(SpringIntegrationDslHttpRetryApplication.class, args);
        Function<Object, Object> function = applicationContext.getBean(Function.class);
        function.apply("foo");
    }

    @Bean
    public IntegrationFlow httpRetryFlow() {
        return IntegrationFlows.from(Function.class)
                .handle(Http.outboundGateway("http://localhost:11111")
                                .httpMethod(HttpMethod.GET)
                                .expectedResponseType(String.class),
                        e -> e.advice(retryAdvice()))
                .get();
    }

    @Bean
    public RequestHandlerRetryAdvice retryAdvice() {
        return new RequestHandlerRetryAdvice();
    }

}

#打印日志
logging.level.org.springframework.retry=debug

Reference:
https://docs.spring.io/spring-integration/reference/html/handler-advice.html#retry-advice
https://stackoverflow.com/questions/49784360/configure-error-handling-and-retry-for-http-outboundgateway-spring-dsl
https://stackoverflow.com/questions/50262862/requesthandlerretryadvice-with-httprequestexecutingmessagehandler-not-working
https://stackoverflow.com/questions/63689856/spring-integration-http-outbound-gateway-retry-based-on-reply-content
https://blog.csdn.net/cunfen8879/article/details/112552420


posted @ 2021-08-23 13:01 paulwong 阅读(241) | 评论 (0)编辑 收藏

Gitee代码托管实践:让代码变得更加有序可靠

https://my.oschina.net/gitosc/blog/5187695

posted @ 2021-08-20 14:49 paulwong 阅读(158) | 评论 (0)编辑 收藏

Git/EGit | reset 和 revert 的区别

git的世界里有后悔药吗?

有的。不仅有,还不止一种:Reset 和 Revert。它们有什么区别呢?先说结论吧。

ResetRevert
作用 将某个commit之后的push全部回滚 将某个指定的commit回滚
历史记录(轨迹)
是否可作用于单个文件 否(都是作用于commit,与文件无关)

下面来说说具体例子。

Revert

试验步骤如下:

  1. 新建两个空白文件 Revert.txt 和 Common.txt,然后commit&push。
  2. 修改 Revert.txt 文件,内容为“commit 1”,然后commit&push,提交的备注为“commit 1 of Revert”
  3. 修改 Common.txt 文件,内容为“update for Revert(by commit 2)”
  4. 修改 Revert.txt 文件,新增一行,内容为“commit 2”
  5. 3 和 4的修改一起commit&push,提交备注为“commit 2 of Revert(Revert.txt + Common.txt)”

效果如下:

git-revert-01

图1-revert之前

目的

保留3的修改,回滚4的修改。

操作

选中“ Revert.txt ”文件,然后在 History 里选中 “commit 2 of Revert…”,右键,找到“Revert Commit”菜单,如图:

git-revert-02

图2-revert操作

点击后,效果如图:

git-revert-03

图3-revert之后

最后,push即可。

结果

未能达到预期效果,Revert.txt 和 Common.txt的修改都被撤销了。Revert的作用范围是一个commit(原子),跟文件的个数无关。

注:对最后一个commit做revert比较简单,两步:一,revert;二,push就可以了。对于较早的commit,因为中间间隔了其他的commit,文件会有冲突,需要处理完冲突才可以commit&push。

Reset

试验步骤如下:

  1. 新建空白文件 Reset.txt,然后commit&push。
  2. 修改 Reset.txt 文件,内容为“commit 1”
  3. 修改 Common.txt 文件,内容为“update for Reset(commit 1)”
  4. 2和3的修改一起commit&push,提交的备注为“commit 1 of Reset”
  5. 修改 Reset.txt 文件,新增一行,内容为“commit 2”,然后commit&push,提交的备注为“commit 2 of Reset”
  6. 修改 Reset.txt 文件,内容为“commit 3”
  7. 修改 Common.txt 文件,内容为“update for Reset(commit 3)”
  8. 6和7的修改一起commit&push,提交的备注为“commit 3 of Reset”

效果如下:

git-reset-04

图4-reset之前

目的

将commit 1 之后的(即commit 2 和 3)改动全部回滚。

操作

在 History 里找到“commit 1”,选中后,右键,找到 Reset 菜单,选择 Hard 模式。

git-reset-05

图5-reset

执行后,如下图所示,HEAD 已经指向里 commit 1,Common.txt 和 Reset.txt 的内容也已改变。注意左侧的项目栏,它已落后了服务器(GitHub)2个commit。怎么提交到服务器上呢?直接push,它会提示不是最新的,操作失败。这里要用到 push 的 force 属性。

git-reset-06

图6-reset之后

选中 项目,右键 – Team – Remote – Configure Push to Upstream,在打开的小窗口中找到 Advanced,如下图所示,这里的 Force Update 要勾上,表示强制覆盖。

重新push,就可以跟服务器保持同步了。

git-reset-07

图7-push-force

要特别注意的是,Reset慎用,跟Linux的“rm -rf /”有异曲同工之妙。

http://www.youngzy.com/blog/2019/08/git-difference-between-reset-and-revert-using-eclipse/

posted @ 2021-08-17 10:37 paulwong 阅读(205) | 评论 (0)编辑 收藏

Jenkins environment variables

https://medium.com/@mukeshsingal/access-jenkins-global-environment-variables-using-groovy-or-java-b5c1e6b53685

posted @ 2021-08-16 15:46 paulwong 阅读(190) | 评论 (0)编辑 收藏

怎么提高自己的系统架构水平

https://my.oschina.net/u/4662964/blog/5135740

posted @ 2021-08-04 11:05 paulwong 阅读(171) | 评论 (0)编辑 收藏

JSR-303 Bean Validation - Grouping Javax Validation Constraints

@NotBlank(message = "Missing ID_IMG_CHECK.")

以上标签进行验证时是无条件验证,如果想在特定条件下才验证,则不适用。

于是才有如下设定:
@NotBlank(message = "Missing ID_IMG_CHECK.", groups = {GroupA.class} )

手动验证:
Class<?> [] classArray = classList.toArray(new Class<?>[0]);
LOGGER.info("subVersion : {}, Validate class : {}", subVersion, classNameList);
CompositeException compositeException = new CompositeException();
Set<ConstraintViolation<QueryKycResultDetail>> groupSet = validator.validate(queryKycResultDetail, classArray);

https://www.baeldung.com/javax-validation-groups

posted @ 2021-07-29 10:00 paulwong 阅读(238) | 评论 (0)编辑 收藏

Linux sha512sum command

检查file的SHA512值:

sha512sum [OPTION] [FILE]

posted @ 2021-07-14 13:41 paulwong 阅读(264) | 评论 (0)编辑 收藏

构建ARTEMIS集群

     摘要: 如果多个ARTEMIS以单体的方式启动,则各个实例互不相关,不能实现高可用。集群因此需将多个实例组队--集群,以实现高可用,即集群内可实现实例的失败转移,消息的负载均衡,消息的重新分配。这需要在集群配置区指定所有所有实例的IP和PORT。但集群实例如果DOWN机,其保存的消息也会随之消失,因此需要实现高可用,有两种方式:共享存储及消息复制。共享存储共享存储是由master/slave对组成,指两个...  阅读全文

posted @ 2021-06-30 16:33 paulwong 阅读(1351) | 评论 (0)编辑 收藏

NAS vs. SAN - What Are the Differences Between Them

https://www.backblaze.com/blog/whats-the-diff-nas-vs-san/

NAS vs. SAN: Differences and Use Cases
https://www.enterprisestorageforum.com/networking/nas-vs-san-differences-and-use-cases/

DAS / NAS / IP SAN / FC SAN区别
https://cloud.tencent.com/developer/article/1580887

posted @ 2021-06-25 15:18 paulwong 阅读(176) | 评论 (0)编辑 收藏

Spring Boot 2.x基础教程:使用LDAP来管理用户与组织数据

http://blog.didispace.com/spring-boot-learning-24-6-2/

posted @ 2021-06-21 09:21 paulwong 阅读(221) | 评论 (0)编辑 收藏

RESTful API using Spring Boot 2.3.5

https://github.com/sina-sheikholeslami/RESTful-API-using-Spring-Boot

posted @ 2021-06-17 14:16 paulwong 阅读(176) | 评论 (0)编辑 收藏

How to install Docker on CentOS

https://www.fosslinux.com/42945/how-to-install-docker-on-centos.htm

posted @ 2021-06-17 14:15 paulwong 阅读(173) | 评论 (0)编辑 收藏

使用PROMETHEUS+GRAFANA监控JAVA程序

     摘要: Grafana能够提供自定义的图形界面来展示监控数据,但由于被监控的应用五花八门,标准不一,因此Prometheus开发了各种client,应用程序只需引入该SDK,即可与Prometheus沟通,提供Prometheus格式的数据,同时Grafana也开发了能识别Prometheus类型的数据源的插件,Grafana能够展示Prometheus上的数据。 非JAVA版本的应用: ...  阅读全文

posted @ 2021-06-16 09:53 paulwong 阅读(1005) | 评论 (0)编辑 收藏

Bash get filename from given path on Linux or Unix

https://www.cyberciti.biz/faq/bash-get-filename-from-given-path-on-linux-or-unix/

posted @ 2021-05-21 14:10 paulwong 阅读(178) | 评论 (0)编辑 收藏

Copying Files With Maven

https://www.baeldung.com/maven-copy-files

posted @ 2021-05-21 09:58 paulwong 阅读(188) | 评论 (0)编辑 收藏

软件工程的最大难题


http://www.ruanyifeng.com/blog/2021/05/scaling-problem.html

posted @ 2021-05-20 16:31 paulwong 阅读(194) | 评论 (0)编辑 收藏

SPRING CLOUD ALIBABA

https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzAwMTk4NjM1MA==&action=getalbum&album_id=1418244755364134912&scene=173&from_msgid=2247484698&from_itemidx=1&count=3&nolastread=1#wechat_redirect

SpringCloud alibaba实战系列文章汇总
https://segmentfault.com/a/1190000023541535?utm_source=sf-similar-article

posted @ 2021-05-11 14:04 paulwong 阅读(193) | 评论 (0)编辑 收藏

JSR-303 Bean Validation - @Size和@Max@Min的区别

@Min and @Max are used for validating numeric fields which could be String(representing number), intshortbyte etc and their respective primitive wrappers.

@Size is used to check the length constraints on the fields.

As per documentation @Size supports StringCollectionMap and arrays while @Min and @Max supports primitives and their wrappers. See the documentation.

posted @ 2021-04-29 09:55 paulwong 阅读(423) | 评论 (0)编辑 收藏

【DevOps】Jenkins任务基于Tag进行构建

手动触发:
https://blog.csdn.net/justyman/article/details/89857577

如果是自动触发BUILD时,则可以以最新建立的TAG为基础进行BUILD,而无需人手选TAG进行BUILD。
配置,注意应取消参数化配置工程:
  1. Add the following refspec to the Git plugin:
    +refs/tags/*:refs/remotes/origin/tags/*
  2. Add the following branch specifier:
    */tags/*
  3. Enable SCM polling, so that the job detects new tags.

posted @ 2021-04-22 11:00 paulwong 阅读(365) | 评论 (0)编辑 收藏

SRPING自带的事件监听机制

定义一个事件,因SPRING中可以有不同的事件,需要定义一个类以作区分:
import lombok.Getter;
import org.springframework.context.ApplicationEvent;


@Getter
public class JavaStackEvent extends ApplicationEvent {

    /**
     * Create a new {
@code ApplicationEvent}.
     *
     * 
@param source the object on which the event initially occurred or with
     *               which the event is associated (never {
@code null})
     
*/
    public JavaStackEvent(Object source) {
        super(source);
    }


}

定义一个此事件观察者,即感兴趣者:
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;

/**
 * 观察者:读者粉丝
 
*/
@RequiredArgsConstructor
public class ReaderListener implements ApplicationListener<JavaStackEvent> {

    @NonNull
    private String name;

    private String article;

    @Async
    @Override
    public void onApplicationEvent(JavaStackEvent event) {
        // 更新文章
        updateArticle(event);
    }

    private void updateArticle(JavaStackEvent event) {
        this.article = (String) event.getSource();
        System.out.printf("我是读者:%s,文章已更新为:%s\n", this.name, this.article);
    }

}

注册感兴趣者(将自身注入SPRING容器则完成注册),并制定发布机制(通过CONTEXT发布事件):
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class ObserverConfiguration {

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext context) {
        return (args) -> {
            log.info("发布事件:什么是观察者模式?");
            context.publishEvent(new JavaStackEvent("什么是观察者模式?"));
        };
    }

    @Bean
    public ReaderListener readerListener1(){
        return new ReaderListener("小明");
    }

    @Bean
    public ReaderListener readerListener2(){
        return new ReaderListener("小张");
    }

    @Bean
    public ReaderListener readerListener3(){
        return new ReaderListener("小爱");
    }

}

posted @ 2021-04-09 14:55 paulwong 阅读(313) | 评论 (0)编辑 收藏

MONGODB聚合资源

MongoDB Projections and Aggregations in Spring Boot
https://www.javaprogramto.com/2020/05/spring-boot-data-mongodb-projections-aggregations.html

In spring data mongodb how to achieve pagination for aggregation
https://stackoverflow.com/questions/34427241/in-spring-data-mongodb-how-to-achieve-pagination-for-aggregation

Create a ProjectionOperation with Difference with spring mongodb
https://stackoverflow.com/questions/46786577/create-a-projectionoperation-with-difference-with-spring-mongodb

posted @ 2021-04-08 13:38 paulwong 阅读(178) | 评论 (0)编辑 收藏

!!深入了解ActiveMQ!

https://my.oschina.net/u/1388595/blog/4545503

posted @ 2021-04-01 16:36 paulwong 阅读(211) | 评论 (0)编辑 收藏

csv 文件打开乱码,有哪些方法可以解决?

Excel 在读取 csv 的时候是通过读取文件头上的 bom 来识别编码的,这导致如果我们生成 csv 文件的平台输出无 bom 头编码的 csv 文件(例如 utf-8 ,在标准中默认是可以没有 bom 头的),Excel 只能自动按照默认编码读取,不一致就会出现乱码问题了。

掌握了这点相信乱码已经无法阻挡我们前进的步伐了:只需将不带 bom 头编码的 csv 文件,用文本编辑器(工具随意,推荐 notepad++ )打开并转换为带 bom 的编码形式(具体编码方式随意),问题解决。

当然,如果你是像我一样的码农哥哥,在生成 csv 文件的时候写入 bom 头更直接点,用户会感谢你的。

附录:对于 utf-8 编码,unicode 标准中是没有 bom 定义的,微软在自己的 utf-8 格式的文本文件之前加上了EF BB BF三个字节作为识别此编码的 bom 头,这也解释了为啥大部分乱码都是 utf-8 编码导致的原因

SPRING BATCH中生成CSV文件时的解决方案:
new FlatFileItemWriterBuilder<T>()
      .name(itemWriterName)
      .resource(outputResource)
      .lineAggregator(lineAggregator)
      .headerCallback(
      h -> {
               System.out.println(header);
               h.write('\uFEFF');//只需加这一行
               h.write(header);
           }
      )
      .build();

https://stackoverflow.com/questions/48952319/send-csv-file-encoded-in-utf-8-with-bom-in-java

posted @ 2021-03-23 10:30 paulwong 阅读(352) | 评论 (0)编辑 收藏

java泛型 方法返回值带有泛型


        /**
         * 
         * 
@param <T>声明此方法持有一个类型T,也可以理解为声明此方法为泛型方法
         * 
@param clazz 作用是指明泛型T的具体类型
         * 
@return 指明该方法的返回值为类型T
         * 
@throws InstantiationException
         * 
@throws IllegalAccessException
         
*/
        public <T> T getObject(Class<T> clazz) throws InstantiationException, IllegalAccessException {
            T t = clazz.newInstance();//创建对象
            return t;
        }

方法返回值前的<T>的左右是告诉编译器,当前的方法的值传入类型可以和类初始化的泛型类不同,也就是该方法的泛型类可以自定义,不需要跟类初始化的泛型类相同

posted @ 2021-03-19 15:10 paulwong 阅读(624) | 评论 (0)编辑 收藏

领域驱动设计实战

领域驱动(DDD,Domain Driven Design)为软件设计提供了一套完整的理论指导和落地实践,通过战略设计和战术设计,将技术实现与业务逻辑分离,来应对复杂的软件系统。本系列文章准备以实战的角度来介绍 DDD,首先编写领域驱动的代码模型,然后再基于代码模型,引入 DDD 的各项概念,先介绍战术设计,再介绍战略设计。

DDD 实战1 - 基础代码模型
DDD 实战2 - 集成限界上下文(Rest & Dubbo)
> DDD 实战3 - 集成限界上下文(消息模式)
> DDD 实战4 - 领域事件的设计与使用
> DDD 实战5 - 实体与值对象
> DDD 实战6 - 聚合的设计
> DDD 实战7 - 领域工厂与领域资源库
> DDD 实战8 - 领域服务与应用服务
> DDD 实战9 - 架构设计
> DDD 实战10 - 战略设计

posted @ 2021-02-25 15:53 paulwong 阅读(249) | 评论 (0)编辑 收藏

JSR-303 Bean Validation - Date String Validation

@Past @Future只针对Date类型的验证,如果是String类型的验证,则不适用。
其实可以新加一个方法返回Date类型,再配合@Future@Past 进行验证。

@Future(message = "Invalid CN_ID_INFO.EXPIRE_DATE.")
private LocalDate getValidExpireDate() {
    try {
        return LocalDate.parse(this.dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    } catch (Exception e) {
        return null;
    }
}

此方法对dateString进行解释,返回LocalDate,如果dateString为空或格式错误,则返回空,再配合@Future 进行是否未来日期的验证。

posted @ 2021-02-25 09:44 paulwong 阅读(307) | 评论 (0)编辑 收藏

JSR-303 Bean Validation - Conditional Validation

bean validation的注释是针对单个变量的,如果要针对多个变量的联动,则不行,需要用到这个注释。
这种方法避免了自定义校验器而增加类。
https://www.chkui.com/article/java/java_bean_validation

@AssertTrue(message = "Missing BANK_CARD_IMG_INFO.IMG")
private Boolean getValidImg() {
    if(YNEnum.Y.code.equals(super.getNeedChecked())) {
            return StringUtils.hasText(this.img);
        }
        return null;//igore checking.
}

这个是当needChecked为Y的时候才执行检查img变量是否为空。
有几点注意:
  1. 方法名称要以get开头
  2. 返回类型用Boolean,而不用boolean
  3. 返回值有三种:true,false,null如果是null则当成通过,与true的结果一样

posted @ 2021-02-25 09:24 paulwong 阅读(292) | 评论 (0)编辑 收藏

http与https的区别

http是将内容文字通过tcp传送,内容是明文,未经加密,可透过tcpdump偷看。

https将内容文字用不对称的方式加密后再传送,加密协议是TLS或SSL,透过tcpdump看到的内容是乱码。而且服务器端更换密钥无需通知client。

How hackers love your HTTP data
https://blog.usejournal.com/how-hackers-love-your-http-data-157e76f2c66a

How you could get hacked at a coffee shop
https://medium.com/bugbountywriteup/how-you-could-get-hacked-at-a-coffee-shop-3a81a53c0b4f


Hacker Lexicon: What Is HTTPS?
https://www.wired.com/2016/04/hacker-lexicon-what-is-https-encryption/

posted @ 2021-02-04 17:00 paulwong 阅读(203) | 评论 (0)编辑 收藏

SPRING BOOT单元测试之@ActiveProfiles

SPRING BOOT单元测试中,因为测试时可能对应的服务器地址不同于SIT等别的环境,通常会将这些地址放于application-sit.yaml中。
在单元测试的代码中用这个标签指定用哪个profile,如
@ActiveProfiles({"embedded-mongodb","test"})

但这样做法,由于@ActiveProfiles这个标签是final的,如果要测试别的profile,只能复制另一份单元测试代码,再改此标签。

比较灵活的做法是用default profile,default profile是如果没指定任何profile,则会默认用这个。在application-default.yaml中再指定需激活的profile。
spring:
   profiles:
      active: test,embedded-mongodb

如果要测试别的profile,可以指定环境变量的方式覆盖:
-Dspring.profiles.active=error,embedded-mongodb

为了安全起见,将application-default.yaml放在测试目录中:src\test\resources。

Setting default Spring profile for tests with override option
https://blog.inspeerity.com/spring/setting-default-spring-profile-for-tests-with-override-option/




posted @ 2021-02-04 15:31 paulwong 阅读(1751) | 评论 (0)编辑 收藏

JSR-303 Bean Validation

接收数据的JAVA BEAN通常需要验证其中字段的正确性,如不准为空,符合EMAIL格式等。
JSR-303 Bean Validation则提供了这样的便捷。

只要在JAVA BEAN中需要验证的字段加@NotNull这种标签,然后在SERVISE中的输入参数中加@Valid标签,则就激活验证流程。
也可以编程的方式自己验证:
@MessageEndpoint
//@Validated
public class MqMessageCcdValidator {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MqMessageCcdValidator.class);
    
    @Autowired
    private Validator validator;
    
    @ServiceActivator
    public MqMessage<CcdRequest> validate(/* @Valid */ Message<MqMessage<CcdRequest>> requestMessage) {
        Set<ConstraintViolation<MqMessage<CcdRequest>>> set = validator.validate(requestMessage.getPayload());
        if(CollectionUtils.isNotEmpty(set)) {
            CompositeException compositeException = new CompositeException();
            set.forEach(
                constraintViolation -> {
                                            LOGGER.info("{}", constraintViolation);
                                            ReqInfoValidationException exception =
                                                    new ReqInfoValidationException(constraintViolation.getMessage());
                                            compositeException.addException(exception);
                                       }
            );
            throw new MessageHandlingException(requestMessage, compositeException);
        }
        
        return requestMessage.getPayload();
    }

}

自定义验证规则
可用标签来做,以下为验证手机号的规则:
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;

@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface ChinaPhone {
    String message() default "Invalid Chinese mobile No.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

如果比较复杂的验证规则,则参见:
https://reflectoring.io/bean-validation-with-spring-boot/#implementing-a-custom-validator

How to use Java Bean Validation in Spring Boot
https://nullbeans.com/how-to-use-java-bean-validation-in-spring-boot/

Complete Guide to Validation With Spring Boot
https://reflectoring.io/bean-validation-with-spring-boot/

Spring JMS Validate Messages using JSR-303 Bean Validation
https://memorynotfound.com/spring-jms-validate-messages-jsr-303-bean-validation/

Spring REST Validation Example
https://mkyong.com/spring-boot/spring-rest-validation-example/

Spring Boot 整合 Bean Validation 校验数据

https://blog.csdn.net/wangzhihao1994/article/details/108403732

posted @ 2021-01-28 10:35 paulwong 阅读(311) | 评论 (0)编辑 收藏

SPRING INTEGRATION SCATTERGATHER

场景,餐厅:
  1. 食客下单,有饮品、食物、甜点
  2. 侍应接单,传送给厨房
  3. 厨房分三个子流程处理,即饮品、食物、甜点子流程
  4. 等待三个子流程处理完,合并成一份交付
  5. 如果厨房发现某食物欠缺,会通知侍应,展开错误处理,即通知食客更改食物,再交给厨房
  6. 侍应将交付品传送给食客
有一个主流程、三个子流程和一个聚合流程,聚合流程会聚合三个子流程的产物,通知主流程,再往下走。
并且主流程会感知子流程的错误,并会交给相应错误处理流程处理,且将结果再交给聚合流程。

对应SPRING INTEGRATION 的SCATTERGATHER模式:
@Bean
public IntegrationFlow scatterGatherAndExecutorChannelSubFlow(TaskExecutor taskExecutor) {
    return f -> f
            .scatterGather(
                    scatterer -> scatterer
                            .applySequence(true)
                            .recipientFlow(f1 -> f1.transform(p -> "Sub-flow#1"))
                            .recipientFlow(f2 -> f2
                                    .channel(c -> c.executor(taskExecutor))
                                    .transform(p -> {
                                        throw new RuntimeException("Sub-flow#2");
                                    })),
                    null,
                    s -> s.errorChannel("scatterGatherErrorChannel"));
}

@ServiceActivator(inputChannel = "scatterGatherErrorChannel")
public Message<?> processAsyncScatterError(MessagingException payload) {
    return MessageBuilder.withPayload(payload.getCause().getCause())
            .copyHeaders(payload.getFailedMessage().getHeaders())
            .build();
}

https://github.com/adnanmamajiwala/spring-integration-sample/tree/master/dsl-scatter-gather/src/main/java/com/si/dsl/scattergather

https://docs.spring.io/spring-integration/docs/5.1.x/reference/html/#scatter-gather

posted @ 2021-01-28 10:11 paulwong 阅读(290) | 评论 (0)编辑 收藏

「测试」 - 接口测试 & mock工具Moco

当需要调用第三方HTTP接口时,别人的接口还没完成,可先根据接口定义文档,返回适当的数据,以便开发。

在LINUX上的部署结构为:
├── boot
│   ├── moco-runner-1.1.0-standalone.jar
│   └── .version
├── foo.json
├── logs
│   ├── back
│   └── moco.log
├── moco.sh
└── startup-moco.sh

.version文件:
/path/to/boot/moco-runner-1.1.0-standalone.jar 1.1.0

moco.sh
#!/usr/bin/env bash

# Ensure this file is executable via `chmod a+x moco`, then place it
# somewhere on your $PATH, like ~/bin. The rest of moco will be
# installed upon first run into the ~/.moco directory.

if [ `id -u` -eq 0 ] && [ "$MOCO_ROOT" = "" ]; then
    echo "WARNING: You're currently running as root; probably by accident."
    echo "Press control-C to abort or Enter to continue as root."
    echo "Set MOCO_ROOT to disable this warning."
    read _
fi

echo $*

#export MOCO_HOME="$
{MOCO_HOME:-"$HOME/.moco"}"
export MOCO_HOME=$(cd `dirname $0`; cd boot; pwd)
VERSION_LOG_FILE="$MOCO_HOME/.version"

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

if [ "$HTTP_CLIENT" = "" ]; then
    if type -p curl >/dev/null 2>&1; then
        if [ "$https_proxy" != "" ]; then
            CURL_PROXY="-x $https_proxy"
        fi
        HTTP_CLIENT="curl $CURL_PROXY -f -L -o"
    else
        HTTP_CLIENT="wget -O"
    fi
fi

function download_failed_message 
{
    echo "Failed to download $1"
    echo "It's possible your HTTP client's certificate store does not have the"
    echo "correct certificate authority needed. This is often caused by an"
    echo "out-of-date version of libssl. Either upgrade it or set HTTP_CLIENT"
    echo "to turn off certificate checks
:"
    echo "  export HTTP_CLIENT=\"wget --no-check-certificate -O\" # or"
    echo "  export HTTP_CLIENT=\"curl --insecure -f -L -o\""
    echo "It's also possible that you're behind a firewall haven't yet"
    echo "set HTTP_PROXY and HTTPS_PROXY."
}

function download 
{
    $HTTP_CLIENT "$2.pending" "$1"
    if [ $? == 0 ]; then
        # TODO
: checksum
        mv -f "$2.pending" "$2"
    else
        rm "$2.pending" 2> /dev/null
        download_failed_message "$1"
        exit 1
    fi
}

function parse_tag 
{
   tag_value=`grep "<$2>.*<.$2>" $1 | sed -e "s/^.*<$2/<$2/" | cut -f2 -d">"| cut -f1 -d"<"`
}

function parse_maven_metadata 
{
    MOCO_METADATA_URL="http
://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/maven-metadata.xml"
    MOCO_METADATA="/tmp/maven-metadata.xml"
    download $MOCO_METADATA_URL $MOCO_METADATA
    parse_tag $MOCO_METADATA latest
    LATEST_VERSION=$tag_value
}

function parse_standalone_latest_url 
{
    parse_maven_metadata
    VERSION=${LATEST_VERSION%
}
    LATEST_MOCO_STANDALONE_JAR="moco-runner-$VERSION-standalone.jar"
    MOCO_STANDLONE_URL="http://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/$LATEST_VERSION/$LATEST_MOCO_STANDALONE_JAR"
}

function install 
{
    echo "Install moco"
    echo "Parse the latest version of moco"
    parse_standalone_latest_url
    echo "Download the latest moco
: $LATEST_VERSION"
    MOCO_STANDALONE="$MOCO_HOME/$LATEST_MOCO_STANDALONE_JAR"
    echo "$MOCO_STANDALONE $LATEST_VERSION" >> $VERSION_LOG_FILE
    download $MOCO_STANDLONE_URL $MOCO_STANDALONE
}

function load_current_version 
{
    read MOCO_STANDALONE CURRENT_VERSION < $VERSION_LOG_FILE
    if [[ "$(uname)" -ne "Darwin" && "$(expr substr $(uname -s) 2 6)" == "CYGWIN"   ]];then
        MOCO_STANDALONE=`cygpath -m "$MOCO_STANDALONE"`
    fi
}

function usage 
{
  printf "
options
:
       help      show help
       start     start server, e.g. moco start -p 12306 -c configfile.json
       shutdown  shutdown moco server 
       upgrade   upgrade moco
"
}

if [ ! -e "$MOCO_HOME" ]
then
    mkdir "$MOCO_HOME"
    install
fi

if [ "$1" = "start" ]; then
    echo "Starting"
    load_current_version
    exec "$JAVACMD" -jar "$MOCO_STANDALONE" $*
elif [ "$1" = "http" ]; then
    echo "Starting HTTP server"
    load_current_version
    exec "$JAVACMD" -jar "$MOCO_STANDALONE" $*
elif [ "$1" = "https" ]; then
    echo "Starting HTTPS server"
    load_current_version
    exec "$JAVACMD" -jar "$MOCO_STANDALONE" $*
elif [ "$1" = "socket" ]; then
    echo "Starting Socket server"
    load_current_version
    exec "$JAVACMD" -jar "$MOCO_STANDALONE" $*
elif [ "$1" = "shutdown" ]; then
    echo "Shutting down server"
    load_current_version
    exec "$JAVACMD" -jar "$MOCO_STANDALONE" $*
elif [ "$1" = "upgrade" ]; then
    echo "Check the new version"
    parse_maven_metadata
    load_current_version

    if [ "$LATEST_VERSION" = "$CURRENT_VERSION" ]; then
        echo "The current version of moco is the latest"
    else
        echo "Upgrading"
        rm $VERSION_LOG_FILE
        install
    fi
elif [ "$1" = "version" ]; then
    load_current_version
    echo "Moco version: $CURRENT_VERSION"
elif [ "$1" = "help" ]; then
    usage
else
    usage
fi

这是根据GIT上的原始文件作的修改。

startup-moco.sh
CMD_PATH=$(cd `dirname $0`; pwd)

# 项目日志输出绝对路径
LOG_DIR=$
{CMD_PATH}"/logs"
LOG_FILE="moco.log"
LOG_PATH="$
{LOG_DIR}/${LOG_FILE}"

# 当前时间
NOW=`date +'%Y-%m-%m-%H-%M-%S'`
NOW_PRETTY=`date +'%Y-%m-%m %H:%M:%S'`

# 启动日志
STARTUP_LOG="================================================ $
{NOW_PRETTY} ================================================\n"

# 日志备份目录
LOG_BACK_DIR="$
{LOG_DIR}/back/"

# 如果logs文件夹不存在,则创建文件夹
if [[ ! -d "$
{LOG_DIR}" ]]; then
  mkdir "$
{LOG_DIR}"
fi

# 如果logs/back文件夹不存在,则创建文件夹
if [[ ! -d "$
{LOG_BACK_DIR}" ]]; then
  mkdir "$
{LOG_BACK_DIR}"
fi

# 如果项目运行日志存在,则重命名备份
if [[ -f "$
{LOG_PATH}" ]]; then
    mv $
{LOG_PATH} "${LOG_BACK_DIR}/${APPLICATION}_back_${NOW}.log"
fi

# 创建新的项目运行日志
echo "" > $
{LOG_PATH}

# 可支持多个json配置文件
$CMD_PATH/moco.sh http -p 8088 -g "${CMD_PATH}/root.json" > ${LOG_PATH} 2>&1 &

# 打印启动日志
echo -e $
{STARTUP_LOG}

root.json
[
    
{
        "context"
: "/service-a",
        "include": "foo.json"
    
},
    
{
        "context"
: "/service-b",
        "include": "bar.json"
    
}
]

foo.json
[
    
{
        "request"
: {
            "method": "post",
            "forms": {
                "method": "uploadKycInfo"
            
}
        },
        "response": 
{
            "json"
: {
                "response": {
                    "subcode": "10000",
                    "submsg": "Success",
                    "sndDt": "20210121101800",
                    "remark": "上传验证基本信息",
                    "msgBody": {
                        "merOrdrNo": "A00120210121654321",
                        "retCode": "00000",
                        "retMsg": "成功/处理完成",
                        "remark": "上传详情或上传信息的简要描述"
                    
}
                },
                "code": "0000",
                "msg": "处理完成",
                "sign": "DF2659FE3EB8184561135D9F55F5EF5"
            }
        }
    }
]

访问路径:
http://ip:port/service-a/

https://github.com/dreamhead/moco/blob/master/moco-doc/apis.md
https://zhuanlan.zhihu.com/p/60076337 
https://www.programmersought.com/article/68272293688/

posted @ 2021-01-21 14:09 paulwong 阅读(568) | 评论 (0)编辑 收藏

Java8中文件转Base64和Base64转文件

有几个项目中,都需要将图片或者数字证书的文件转为Base64,昨天写代码的时候,发现在jdk8中本就含有关于Base64的API。

从此后不再需要其他的jar包来转换Base64了!!!

据说是JDK8加入的。


先是将文件转为Base64:
public String encryptToBase64(String filePath) {
        if (filePath == null) {
            return null;
        }
        try {
            byte[] b = Files.readAllBytes(Paths.get(filePath));
            return Base64.getEncoder().encodeToString(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        return null;
    }

Files、Paths类是JDK7里加入的,读取文件不再需要调用IO包里的FileInputStream,简单便捷。
字符串参数filePath是文件的路径。

首先是将文件读成二进制码,然后通过Base64.getEncoder().encodeToString()方法将二进制码转换为Base64值。


然后是将Base64转为文件:
public String decryptByBase64(String base64, String filePath) {
        if (base64 == null && filePath == null) {
            return "生成文件失败,请给出相应的数据。";
        }
        try {
            Files.write(Paths.get(filePath), Base64.getDecoder().decode(base64),StandardOpenOption.CREATE);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "指定路径下生成文件成功!";
    }

字符串参数base64指的是文件的Base64值,filePath是指的文件将要保存的位置。
通过Files.write()方法轻松将文件写入指定位置,不再调用FileOutStream方法。

第三个参数StandardOpenOption.CREATE是处理文件的方式,我设置的是不管路径下有或没有,都创建这个文件,有则覆盖。

在StandardOpenOption类中有很多参数可调用,不再累赘。

posted @ 2021-01-07 09:44 paulwong 阅读(3737) | 评论 (0)编辑 收藏

微服务架构设计模式

微服务架构及设计模式
http://dockone.io/article/9680

微服务架构10个最重要的设计模式
https://developer.51cto.com/art/202012/635852.htm

GitHub标星127K!字节内部必刷“微服务架构设计模式”
https://blog.csdn.net/a159357445566/article/details/110006542

微服务架构及其最重要的10个设计模式
https://www.infoq.cn/article/Kdw69bdimlX6FSGz1bg3

Effective Microservices: 10 Best Practices
https://towardsdatascience.com/effective-microservices-10-best-practices-c6e4ba0c6ee2

Microservice Architecture and its 10 Most Important Design Patterns
https://towardsdatascience.com/microservice-architecture-and-its-10-most-important-design-patterns-824952d7fa41




posted @ 2021-01-06 14:44 paulwong 阅读(351) | 评论 (0)编辑 收藏

21个2021年软件开发趋势预测

     摘要: 2020年是最近历史上前所未有的一年。在过去的一百年中,人类没有经历过像COVID-19这样的全球性大流行。它影响了我们星球上的所有国家,部门和几乎所有个人。 好消息是,我们已经准备好疫苗,终于可以充满乐观和希望,迎接新的一年2021年。 2020年对于软件开发行业来说是重要的一年,在许多领域都取得了明显的突破。COVID-19大大加快了数字化转型,到2021年这种趋势将更加明显。 在软...  阅读全文

posted @ 2021-01-05 09:56 paulwong 阅读(346) | 评论 (0)编辑 收藏

http connection存活期

HTTP1.1的链接,默认是长链接,不会主动关闭。
LINUX会默认保留链接5天再关闭。
建立HTTP链接其实也是调用TCL的协议去建立,包括开始的时候有三次握手,关闭的时候有四次握手。关闭链接双方都可以发起。
但这些链接可能会被防火墙关掉而不通知建立链接的双方,因此设置需设置链接的存活期。
使用httpClient的链接池时,要设置池中的链接存活期或设置存活策略。
检测存活期只在每次发送数据时,才检测取出的链接是否超过存活期,如超过则关闭。
设置存活期的策略:

import java.util.concurrent.TimeUnit;

import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;

public class MyConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy{
    
    
    private int timeToLive;
    private TimeUnit timeUnit;

    public MyConnectionKeepAliveStrategy(int timeToLive, TimeUnit timeUnit) {
        this.timeToLive = timeToLive;
        this.timeUnit = timeUnit;
    }

    @Override
    public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
        Args.notNull(response, "HTTP response");
        final HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            final HeaderElement he = it.nextElement();
            final String param = he.getName();
            final String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(final NumberFormatException ignore) {
                    //do nothing
                }
            }
        }
        return timeUnit.toMillis(timeToLive);
    }

}

《HttpClient官方文档》2.6 连接维持存活策略
http://ifeve.com/httpclient-2-6/

httpclient连接池管理,你用对了?
http://ifeve.com/http-connection-pool/

HttpClient连接池的一些思考
https://zhuanlan.zhihu.com/p/85524697

HTTP协议的Keep-Alive 模式
https://www.jianshu.com/p/49551bda6619

posted @ 2020-12-31 15:08 paulwong 阅读(345) | 评论 (0)编辑 收藏

开始日期与结束日期的用法

通常要用到取某个时间段内的数据,那么时间段要如何定义?
取2020-12-01这天的数据,"2020-12-01 00:00:00" <= time < "2020-12-02 00:00:00"。
apache common3中提供了相应的方法:

startDate = DateUtils.parseDate(startDateStr, DATE_PATTERN);
            
String endDateStr = args.getOptionValues(END_DATE).get(0);
endDate = DateUtils.parseDate(endDateStr, DATE_PATTERN);

//清零开始日期,返回类似2020-12-01 00:00:00            
startDate = DateUtils.truncate(startDate, Calendar.DATE);
//取结束日期的上限,返回隔天的时间,2020-12-02 00:00:00
endDate = DateUtils.ceiling(endDate, Calendar.DATE);"。
apache common3中提供了相应的方法:

posted @ 2020-12-29 10:37 paulwong 阅读(407) | 评论 (0)编辑 收藏

TCP 連線狀態機制與流程

https://dev.twsiyuan.com/2017/09/tcp-states.html
在開發基於 HTTP 的網路應用服務時,當有大量連線要求,或是與長連線 (Persistent connection) 要求時,常常遇到底層 TCP 的連線斷線錯誤,導致服務不穩定。因此研究了解 TCP 的連線狀態機制,並嘗試用自己的方式整理筆記,希望能從基礎知識中找到解決錯誤的線索,或是任何能更進一步優化服務的手段。

僅紀錄 TCP 連線狀態以及建立或是斷開連線流程,關於進一步的 TCP 封包協定可參考 Reference 連線。

TCP 建立連線 (Open)

通常的 TCP 連線建立流程與狀態,需要三次的訊息交換來建立連線 (three-way handshaking):

TCP 建立連線流程圖

TCP 建立連線流程圖

其中左邊通常為 server,右邊則為 client,文字流程描述:

  1. Server 建立 TCB,開啟監聽連線,進入狀態 LISTENING
  2. Client 發出連線要求 SYN,進入狀態 SYN-SENT,等待回應
  3. Server 收到 SYN 要求,回應連線傳送 SYN+ACK,並進入狀態 SYN-RCVD (SYN-RECEIVED)
  4. Client 收到 SYN+ACK 確認完成連線進入狀態 ESTABLISHED,並送出 ACK
  5. Server 收到 ACK 確認連線完成,也進入狀態 ESTABLISHED
  6. 雙方開始傳送交換資料

該些名詞與狀態說明:

  • CLOSED:連線關閉狀態
  • LISTENING:監聽狀態,被動等待連線
  • SYN-SENT:主動送出連線要求 SYN,並等待對方回應
  • SYN-RCVD:收到連線要求 SYN,送出己方的 SYN+ACK 後,等待對方回應
  • ESTABLISHED:確定完成連線,可開始傳輸資料
  • TCB:Transmission Control Block,see wiki
  • SYN:Synchronous,表示與對方建立連線的同步符號
  • ACK:Acknowledgement,表示發送的數據已收到無誤

在建立連線時,可能會發生雙方同步建立連線的情形 (Simultaneous open),常見於 P2P 的應用中,其 TCP 建立連線的流程不太一樣:

TCP 同步建立連線流程圖

TCP 同步建立連線流程圖

畫成 TCP 狀態流程圖會是這樣:

TCP Open 狀態圖

TCP Open 狀態圖

TCP 斷開連線 (Close)

TCP 關閉流程如下,比建立連線還要複雜一些,需要經過四次的訊息交換 (four-way handshaking),要注意的是可以是由 server 發起主動關閉,抑或是 client 發起主動關閉:

TCP 關閉連線流程圖

TCP 關閉連線流程圖

其中左邊通常為 client 狀態 (由 client 主動發起關閉連線),右邊則為 server 狀態,文字流程描述:

  1. Client 準備關閉連線,發出 FIN,進入狀態 FIN-WAIT-1
  2. Server 收到 FIN,發回收到的 ACK,進入狀態 CLOSE-WAIT,並通知 App 準備斷線
  3. Client 收到 ACK,進入狀態 FIN-WAIT-2,等待 server 發出 FIN
  4. Server 確認 App 處理完斷線請求,發出 FIN,並進入狀態 LAST-ACK
  5. Client 收到 FIN,並回傳確認的 ACK,進入狀態 TIME-WAIT,等待時間過後正式關閉連線
  6. Server 收到 ACK,便直接關閉連線

該些名詞與狀態說明:

  • ESTABLISHED:連線開啟狀態
  • CLOSE-WAIT:等待連線關閉狀態,等待 App 回應
  • LAST-ACK:等待連線關閉狀態,等待遠端回應 ACK 後,便關閉連線
  • FIN-WAIT-1:等待連線關閉狀態,等待遠端回應 ACK
  • FIN-WAIT-2:等待連線關閉狀態,等待遠端回應 FIN
  • TIME-WAIT:等待連線關閉狀態,等段一段時候,保證遠端有收到其 ACK 關閉連線 (網路延遲問題)
  • CLOSED:連線關閉狀態
  • FIN:表示關閉連線的同步符號
  • ACK:Acknowledgement,表示發送的數據已收到無誤

有可能連線的雙方同時發起關閉,雖然機率還蠻低的:

TCP 同步關閉連線流程圖

TCP 同步關閉連線流程圖

這邊多一個狀態:

  • CLOSING:等待連線關閉狀態,等待遠端回應 ACK

畫成 TCP 狀態流程圖會是這樣:

TCP Close 狀態圖

TCP Close 狀態圖

查詢現在電腦的 TCP 狀態

查詢目前所有的連線狀態 (Windows & Linux):

netstat -a

Reference

posted @ 2020-12-29 10:04 paulwong 阅读(235) | 评论 (0)编辑 收藏

httpClient Connection Pool使用的正确姿势

import java.util.concurrent.TimeUnit;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class HttpClientConfiguration {
    
    
    @Bean
      public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager result = 
                new PoolingHttpClientConnectionManager(5, TimeUnit.MINUTES);
        result.setMaxTotal(20);
        result.setDefaultMaxPerRoute(20);
        return result;
      }

      @Bean
      public RequestConfig requestConfig(KycProperties kycProperties) {
          return RequestConfig
                        .custom()
                      .setConnectionRequestTimeout(kycProperties.getHttpConnectionTimeout())
                      .setConnectTimeout(kycProperties.getHttpConnectionTimeout())
                      .setSocketTimeout(kycProperties.getHttpConnectionTimeout())
                      .build();
      }

      @Bean
      public CloseableHttpClient httpClient(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager, RequestConfig requestConfig) {
          return HttpClients
                      .custom()
                      .setConnectionManager(poolingHttpClientConnectionManager)
                      .setDefaultRequestConfig(requestConfig)
                      .build();
      }

}

Troubleshooting Spring's RestTemplate Requests Timeout
https://tech.asimio.net/2016/12/27/Troubleshooting-Spring-RestTemplate-Requests-Timeout.html a

httpclient超时重试记录
https://blog.csdn.net/wanghao112956/article/details/102967930

posted @ 2020-12-29 09:36 paulwong 阅读(1169) | 评论 (0)编辑 收藏

Maven project version inheritance

A/pom.xml:
<groupId>com.dummy.bla</groupId>
<artifactId>parent</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>

B/pom.xml
<parent>
    <groupId>com.dummy.bla</groupId>
    <artifactId>parent</artifactId>
    <version>0.1-SNAPSHOT</version>     
</parent>

<groupId>com.dummy.bla.sub</groupId>
<artifactId>kid</artifactId>

B/pom.xml中需要显式注明父pom的版本号,但从MAVEN3.5之后,可以用变量表示了:

A/pom.xml:
<groupId>com.dummy.bla</groupId>
<artifactId>parent</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>

<properties>
    <revision>0.1-SNAPSHOT</revision>
</properties>

B/pom.xml
<parent>
    <groupId>com.dummy.bla</groupId>
    <artifactId>parent</artifactId>
    <version>${revision}</version>     
</parent>

<groupId>com.dummy.bla.sub</groupId>
<artifactId>kid</artifactId>

https://stackoverflow.com/questions/10582054/maven-project-version-inheritance-do-i-have-to-specify-the-parent-version/51969067#51969067


https://maven.apache.org/maven-ci-friendly.html

posted @ 2020-12-16 09:48 paulwong 阅读(286) | 评论 (0)编辑 收藏

git-stash用法小结

缘起

今天在看一个bug,之前一个分支的版本是正常的,在新的分支上上加了很多日志没找到原因,希望回溯到之前的版本,确定下从哪个提交引入的问题,但是还不想把现在的修改提交,也不希望在Git上看到当前修改的版本(带有大量日志和调试信息)。因此呢,查查Git有没有提供类似功能,就找到了git stash的命令。

综合下网上的介绍和资料,git stash(git储藏)可用于以下情形:

  • 发现有一个类是多余的,想删掉它又担心以后需要查看它的代码,想保存它但又不想增加一个脏的提交。这时就可以考虑git stash
  • 使用git的时候,我们往往使用分支(branch)解决任务切换问题,例如,我们往往会建一个自己的分支去修改和调试代码, 如果别人或者自己发现原有的分支上有个不得不修改的bug,我们往往会把完成一半的代码commit提交到本地仓库,然后切换分支去修改bug,改好之后再切换回来。这样的话往往log上会有大量不必要的记录。其实如果我们不想提交完成一半或者不完善的代码,但是却不得不去修改一个紧急Bug,那么使用git stash就可以将你当前未提交到本地(和服务器)的代码推入到Git的栈中,这时候你的工作区间和上一次提交的内容是完全一样的,所以你可以放心的修Bug,等到修完Bug,提交到服务器上后,再使用git stash apply将以前一半的工作应用回来。
  • 经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是git stash命令。储藏(stash)可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。

git stash用法

1. stash当前修改

git stash会把所有未提交的修改(包括暂存的和非暂存的)都保存起来,用于后续恢复当前工作目录。
比如下面的中间状态,通过git stash命令推送一个新的储藏,当前的工作目录就干净了。

$ git status 
On branch master
Changes to be committed:

new file: style.css

Changes not staged for commit:

modified: index.html

$ git stash Saved working directory and index state WIP on master: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage

$ git status
On branch master nothing to commit, working tree clean

需要说明一点,stash是本地的,不会通过git push命令上传到git server上。
实际应用中推荐给每个stash加一个message,用于记录版本,使用git stash save取代git stash命令。示例如下:

$ git stash save "test-cmd-stash" 
Saved working directory and index state On autoswitch: test-cmd-stash
HEAD 现在位于 296e8d4 remove unnecessary postion reset in onResume function
$ git stash list
stash@{0}: On autoswitch: test-cmd-stash

2. 重新应用缓存的stash

可以通过git stash pop命令恢复之前缓存的工作目录,输出如下:

$ git status 
On branch master
nothing to commit, working tree clean
$ git stash pop
On branch master
Changes to be committed:
new file: style.css

Changes not staged for commit:
modified: index.html
Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)

这个指令将缓存堆栈中的第一个stash删除,并将对应修改应用到当前的工作目录下。
你也可以使用git stash apply命令,将缓存堆栈中的stash多次应用到工作目录中,但并不删除stash拷贝。命令输出如下:

$ git stash apply 
On branch master
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html

3. 查看现有stash

可以使用git stash list命令,一个典型的输出如下:

$ git stash list 
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

在使用git stash apply命令时可以通过名字指定使用哪个stash,默认使用最近的stash(即stash@{0})。

4. 移除stash

可以使用git stash drop命令,后面可以跟着stash名字。下面是一个示例:

$ git stash list 
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

或者使用git stash clear命令,删除所有缓存的stash。

5. 查看指定stash的diff

可以使用git stash show命令,后面可以跟着stash名字。示例如下:

$ git stash show
index.html | 1 +
style.css | 3 +++
2 files changed, 4 insertions(+)

在该命令后面添加-p--patch可以查看特定stash的全部diff,如下:

$ git stash show -p 
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css @@ -0,0 +1,3 @@
+* {
+ text-decoration: blink;
+}
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>

6. 从stash创建分支

如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化解它。如果你想用更方便的方法来重新检验你储藏的变更,你可以运行 git stash branch,这会创建一个新的分支,检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。

$ git stash branch testchanges 
Switched to a new branch "testchanges"
# On branch testchanges
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: lib/simplegit.rb
#
Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)

这是一个很棒的捷径来恢复储藏的工作然后在新的分支上继续当时的工作。

7. 暂存未跟踪或忽略的文件

默认情况下,git stash会缓存下列文件:

  • 添加到暂存区的修改(staged changes)
  • Git跟踪的但并未添加到暂存区的修改(unstaged changes)

但不会缓存一下文件:

  • 在工作目录中新的文件(untracked files)
  • 被忽略的文件(ignored files)

git stash命令提供了参数用于缓存上面两种类型的文件。使用-u或者--include-untracked可以stash untracked文件。使用-a或者--all命令可以stash当前目录下的所有修改。

至于git stash的其他命令建议参考Git manual。

小结

git提供的工具很多,恰好用到就可以深入了解下。更方便的开发与工作的。

参考资料

  1. 6.3 Git工具-储藏(Stashing)
  2. Git Stash 历险记
  3. Git Stash用法
  4. Git Stash

posted @ 2020-12-14 11:02 paulwong 阅读(248) | 评论 (0)编辑 收藏

spring data mongodb group by month

https://stackoverflow.com/questions/35312677/how-to-use-month-using-aggregation-in-spring-data-mongo-db
Aggregation agg = newAggregation(
    project("id")
        .andExpression("month(createdDate)").as("month_joined")
        .andExpression("year(createdDate)").as("year"),
    match(Criteria.where("year").is(2016)),
    group("month_joined").count().as("number"),
    project("number").and("month_joined").previousOperation(),
    sort(ASC, "number")
);

AggregationResults<JoinCount> results = mongoTemplate.aggregate(agg, 
                                                     "collectionName", JoinCount.class);
List<JoinCount> joinCount = results.getMappedResults();

posted @ 2020-12-11 16:53 paulwong 阅读(210) | 评论 (0)编辑 收藏

史上最通俗Netty入门长文:基本介绍、环境搭建、动手实战

http://www.blogjava.net/jb2011/archive/2020/11/18/435727.html

posted @ 2020-12-02 09:15 paulwong 阅读(276) | 评论 (0)编辑 收藏

Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!

http://www.52im.net/thread-2846-1-1.html

posted @ 2020-12-02 08:52 paulwong 阅读(284) | 评论 (0)编辑 收藏

SPRING DATA MONGODB 教程


https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-mongodb

posted @ 2020-11-20 14:45 paulwong 阅读(244) | 评论 (0)编辑 收藏

LINUX SHELL之SCL

什么是SCL
https://blog.51cto.com/hashlinux/1772066

CentOS/RHEL 开发环境之 devtoolset
http://blog.fungo.me/2016/03/centos-development-env/

How can I make a Red Hat Software Collection persist after a reboot/logout?
https://access.redhat.com/solutions/527703

posted @ 2020-11-05 16:42 paulwong 阅读(684) | 评论 (0)编辑 收藏

Pretty print JSON using org.json library in Java

Syntax

public java.lang.String toString(int indentFactor) throws JSONException

Example

import org.json.*;
public class JSONPrettyPrintTest {
   
public static void main(String args[]) throws JSONException {
      String json 
= "{" +
                    
"Name : Jai," +
                    
"Age : 25, " +
                    
"Salary: 25000.00 " +
                    
"}";
      JSONObject jsonObj 
= new JSONObject(json);
      System.out.println(
"Pretty Print of JSON:");
      System.out.println(jsonObj.toString(
4)); // pretty print json
   }
}

Output

Pretty Print of JSON:
{
   "Salary": 25000,
   "Age": 25,
   "Name": "Jai"
}

posted @ 2020-10-28 17:17 paulwong 阅读(235) | 评论 (0)编辑 收藏

!!!服务器端各种安装

https://www.server-world.info/en/

posted @ 2020-10-24 23:46 paulwong 阅读(234) | 评论 (0)编辑 收藏

install redhat rh-mariadb103

# Install MariaDB 10.3
yum install rh-mariadb103-mariadb-server rh-mariadb103-mariadb-server-utils -y

# Add MariaDB 10.3 to $PATH
scl enable rh-mariadb103 bash
source /opt/rh/rh-mariadb103/enable

# start 10.3 server
chown -R mysql:mysql /var/opt/rh/rh-mariadb103/lib/mysql;
systemctl start rh-mariadb103-mariadb

# Upgrade tables
mysql_upgrade -p[PASSWORD]

# Set 10.3 to start on boot
systemctl enable rh-mariadb103-mariadb

# Add 10.3 to paths on reboot (and remove 10.2) 
rm /etc/profile.d/rh-mariadb102.sh
cp /opt/rh/rh-mariadb103/enable /etc/profile.d/rh-mariadb103.sh

# increase max connections number
systemctl edit rh-mariadb103-mariadb
[Service]
LimitNOFILE=65535
LimitNPROC=65535

vi /etc/opt/rh/rh-mariadb103/my.cnf
[mysqld]
max_connections=1000
open_files_limit=65535

# restart mariadb103
systemctl daemon-reload
systemctl restart rh-mariadb103-mariadb

# check result
mysql -e 'show variables like "max_connections"'

https://www.server-world.info/en/note?os=CentOS_7&p=mariadb103&f=4

posted @ 2020-10-24 18:02 paulwong 阅读(366) | 评论 (0)编辑 收藏

SPRING 中YAML文件密码不以明文保存

Update: for production environment, to avoid exposing the password in the command line, since you can query the processes with ps, previous commands with history, etc etc. You could:

  • Create a script like this: touch setEnv.sh
  • Edit setEnv.sh to export the JASYPT_ENCRYPTOR_PASSWORD variable

    #!/bin/bash

    export JASYPT_ENCRYPTOR_PASSWORD=supersecretz

  • Execute the file with . setEnv.sh
  • Run the app in background with mvn spring-boot:run &
  • Delete the file setEnv.sh
  • Unset the previous environment variable with: unset JASYPT_ENCRYPTOR_PASSWORD

https://stackoverflow.com/questions/37404703/spring-boot-how-to-hide-passwords-in-properties-file

posted @ 2020-10-20 14:59 paulwong 阅读(452) | 评论 (0)编辑 收藏

SPRING INTEGRATION HEADER问题

当SPRING INTEGRATION的流程中从HTTP outboundGateway转成JmsGateway时,会报header的错误,这时就要把相关多余的header移除。

.headerFilter("Api-Key", "Content-Type", "X-Powered-By", "Content-Language", "Transfer-Encoding", "Cache-Control", "Keep-Alive", "Set-Cookie")

https://stackoverflow.com/questions/50608415/cwsia0112e-the-property-name-keep-alive-is-not-a-valid-java-identifier

posted @ 2020-10-20 14:56 paulwong 阅读(318) | 评论 (0)编辑 收藏

RestTemplate处理请求状态码为非200的返回数据

RestTemplate是Spring提供的用于访问Rest服务的客户端,

RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,

可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。

ClientHttpRequestFactory接口主要提供了两种实现方式

1、一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。

2、一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。

默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 ResponseErrorHandler ,见下面的示例,示例中的方法中基本都是空方法,只要对hasError修改下,让他一直返回true,即是不检查状态码及抛异常了。

package com.example.demo.web.config;

import java.io.IOException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) throws Exception {
        RestTemplate restTemplate = new RestTemplate(factory);
        ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
            @Override
            public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
                return true;
            }
            @Override
            public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
            }
        };
        restTemplate.setErrorHandler(responseErrorHandler);
        return restTemplate;
    }
    
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        //读取超时5秒
        factory.setReadTimeout(5000);
        //连接超时15秒
        factory.setConnectTimeout(15000);
        return factory;
    }
}

RestTemppate运用实例

package com.example.demo.web.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.example.demo.domain.Book;

@RestController
public class TestBookController {
    private Logger logger = LoggerFactory.getLogger(getClass());
    
      @Autowired
      private RestTemplate restTemplate;
      
      @GetMapping("/testaddbook")
      public Book testAddBook() {
              Book book = new Book();
              ResponseEntity<Book> responseEntity = restTemplate.postForEntity( "http://localhost:8061/book", book , Book.class);
              return responseEntity.getBody();
      }

}

其他方法,catch HttpStatusCodeException e.getResponseBodyAsString()
try {
    ResponseEntity<Component> response = restTemplate.exchange(webSvcURL,
        HttpMethod.POST, 
        requestEntity,
        Component.class);
catch (HttpStatusCodeException e) {
    List<String> customHeader = e.getResponseHeaders().get("x-app-err-id");
    String svcErrorMessageID = "";
    if (customHeader != null) {
        svcErrorMessageID = customHeader.get(0);                
    }
    throw new CustomException(e.getMessage(), e, svcErrorMessageID);
    // You can get the body too but you will have to deserialize it yourself
    
// e.getResponseBodyAsByteArray()
    
// e.getResponseBodyAsString()
}

https://stackoverflow.com/questions/7878002/resttemplate-handling-response-headers-body-in-exceptions-restclientexception

https://stackoverflow.com/questions/38093388/spring-resttemplate-exception-handling/51805956#51805956

posted @ 2020-10-16 16:54 paulwong 阅读(1148) | 评论 (0)编辑 收藏

Error handling in spring integration - How to get all the errors thrown in multiple threads and send them to the error-channel

在SPRING INTEGRATION中,如果exception发生在各种thread里时,如何将exception返回到指定的channel,之后再绕回到aggrator-channel中。

@Bean
public IntegrationFlow provisionUserFlow() {
return
    IntegrationFlows.from("input.channel")
    .publishSubscribeChannel(Executors.newCachedThreadPool(),
        s -> s.applySequence(true)
            .subscribe(f -> f.enrichHeaders(e -> e.header(MessageHeaders.ERROR_CHANNEL, "errorChannel", true))
                .handle(provisionerA, "provision")
                .channel("aggregatorChannel")
            )
            .subscribe(f -> f.enrichHeaders(e -> e.header(MessageHeaders.ERROR_CHANNEL, "errorChannel", true))
                .handle(provisionerB, "provision")
                .channel("aggregatorChannel"))
            )
        .get();
}

@Bean
public IntegrationFlow aggregateFlow() {
    return IntegrationFlows.from("aggregatorChannel")
                    .channel( aggregatorChannel)
                    .aggregate( a -> a.processor( collect, "aggregatingMethod"))
                    .get();
}

@Transformer( inputChannel = "errorChannel", outputChannel = "aggregatorChannel")
public Message<?> errorChannelHandler(ErrorMessage errorMessage) throws RuntimeException {

    Message<?> failedMessage =  ((MessagingException) errorMessage.getPayload()).getFailedMessage();

    Exception exception = (Exception) errorMessage.getPayload();

    return  MessageBuilder.withPayload( exception.getMessage())
                                       .copyHeadersIfAbsent( failedMessage.getHeaders() )
                                       .build();
}


https://stackoverflow.com/q/46495127/11790720

posted @ 2020-10-15 19:21 paulwong 阅读(230) | 评论 (0)编辑 收藏

SPRING INTEGRATION子FLOW

split-route-aggregate flow 
split之后,可以将message分给不同的子flow处理,配置如下:
@Bean
public IntegrationFlow parallelSplitRouteAggregateFlow() {
    return IntegrationFlows
            .from(Http.inboundGateway("/trigger"))
            .handle((p, h) -> Arrays.asList(1, 2, 3))
            .split()
            .channel(MessageChannels.executor(Executors.newCachedThreadPool()))
            .<Integer, Boolean>route(o -> o % 2 == 0, m -> m
                    .subFlowMapping(true, sf -> sf.gateway(oddFlow()))
                    .subFlowMapping(false, sf -> sf.gateway(evenFlow())))
            .aggregate()
            .get();
}

@Bean
public IntegrationFlow oddFlow() {
    return flow -> flow.<Integer>handle((payload, headers) -> "odd");
}

@Bean
public IntegrationFlow evenFlow() {
    return flow -> flow.<Integer>handle((payload, headers) -> "even");
}


https://stackoverflow.com/questions/50121384/spring-integration-parallel-split-route-aggregate-flow-fails-due-to-one-way-mess

posted @ 2020-10-15 11:29 paulwong 阅读(475) | 评论 (0)编辑 收藏

在LINUX中格式化JSON的工具-jq

安装:
wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
chmod +x ./jq
cp jq /usr/bin

posted @ 2020-10-12 09:55 paulwong 阅读(333) | 评论 (0)编辑 收藏

怀旧框架集合

     摘要: 最近在公司用JUP框架做项目,发现这个框架是别人基于SpringSide封装的,所以打算学习下,SpringSide,其中遇到了很多坑,做个记录,网上关于这方面的资料都有些老了,而且SpringSide最新的版本是SpringSide-Utils,老一点的版本为v4.2.2.GA,以下分别对这两个版本分别介绍下,主要内容来自于网上。一些资料:Github源码地址:  https://gi...  阅读全文

posted @ 2020-10-09 19:14 paulwong 阅读(301) | 评论 (0)编辑 收藏

线上JVM工具

https://github.com/vipshop/vjtools

posted @ 2020-10-09 19:12 paulwong 阅读(248) | 评论 (0)编辑 收藏

基于Spring Cloud的快速开发脚手架&最佳实践总结

Spring Cloud 你懂的
Keycloak 微服务认证授权
Jenkins 持续集成
SonarQube 代码质量控制


https://gitee.com/itmuch/spring-cloud-yes

posted @ 2020-10-09 10:48 paulwong 阅读(535) | 评论 (0)编辑 收藏

Keycloak初探

Keycloak是Jboss出品的做认证和授权的WEB程序,根据OPENIDC协议,OPENID是做认证,OAUTH2.0是做授权,OPENIDC则将这两者整合。

有提供一套WEB界面维护用户、应用与角色。

Ream则可认为是多租户,每个租户的应用和用户数据是隔离的。

http://10.80.27.69:8180/auth/realms/quickstart/.well-known/openid-configuration 提供当前所有的API节点。
get_access_token_from_public_client:
curl --location --request POST 'http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=alice' \
--data-urlencode 'password=123456' \
--data-urlencode 'client_id=app-springboot-public' \
--data-urlencode 'grant_type=password' \
| jq

./get_access_token_from_confidential_client.sh
curl --location --request POST 'http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=app-springboot-confidential' \
--data-urlencode 'client_secret=3acf7692-49cb-4c45-9943-6f3dba512dae' \
--data-urlencode 'grant_type=client_credentials' \
| jq

访问一个ACCESS TYPE为Bare only的应用的一个API:
access_token=$(curl \
-d "client_id=app-springboot-public" \
-d "username=alice" \
-d "password=123456" \
-d "grant_type=password" \
"http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token" \
| jq -r '.access_token')

#echo $access_token

curl -H "Authorization: Bearer $access_token" 'http://10.80.27.69:8182/products' | jq

访问用户信息:
access_token=$(curl \
-d "client_id=app-springboot-public" \
-d "username=alice" \
-d "password=123456" \
-d "grant_type=password" \
"http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/token" | jq -r '.access_token')


curl -H "Authorization: Bearer $access_token" http://10.80.27.69:8180/auth/realms/quickstart/protocol/openid-connect/userinfo | jq














posted @ 2020-10-08 13:56 paulwong 阅读(714) | 评论 (0)编辑 收藏

配置docker仓库镜像,即使用私服

编辑/etc/docker/daemon.json,加入以下节点:
{
  "registry-mirrors": [
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}


posted @ 2020-09-30 15:40 paulwong 阅读(450) | 评论 (0)编辑 收藏

以非root用户运行docker

[root@dev69 ~]$ groupadd docker
[root@dev69 ~]$ usermod -aG docker $USER
[root@dev69 ~]$ reboot
[paul@dev69 ~]$ docker run hello-world

docker 安装:
[root@dev69 ~]$ yum install -y docker
[root@dev69 ~]$ systemctl enable docker
[root@dev69 ~]$ systemctl start docker

posted @ 2020-09-30 15:10 paulwong 阅读(471) | 评论 (0)编辑 收藏

MAVEN私服、DOCKER私服、NPM私服,专治各种私服

Using Nexus 3 as Your Repository – Part 1: Maven Artifacts
https://blog.sonatype.com/using-nexus-3-as-your-repository-part-1-maven-artifacts

Using Nexus 3 as Your Repository – Part 2: npm Packages
https://blog.sonatype.com/using-nexus-3-as-your-repository-part-2-npm-packages

Using Nexus 3 as Your Repository – Part 3: Docker Images
https://blog.sonatype.com/using-nexus-3-as-your-repository-part-3-docker-images

微服务--使用Nexus Repository Manager 3.0搭建私有Docker仓库
https://www.hifreud.com/2018/06/05/02-nexus-docker-repository/

posted @ 2020-09-30 14:24 paulwong 阅读(362) | 评论 (0)编辑 收藏

keycloak 资源

Keycloak为现代应用和服务提供开源的认证和访问管理,即通常所说的认证和授权。

Keycloak支持OpenID、OAuth 2.0和SAML 2.0协议;支持用户注册、用户管理、权限管理;支持OTP,支持代理OpenID、SAML 2.0 IDP,支持GitHub、LinkedIn等第三方登录,支持整合LDAP和Active Directory;支持自定义认证流程、自定义用户界面,支持国际化。

有用户管理界面,可用于API的认证和用户的认证,用户认证需人为输入用户名与密码,API则凭BARE TOKEN即可认证。

Spring Boot/Angular整合Keycloak实现单点登录
https://blog.51cto.com/7308310/2446368

僅十分鐘即可接入Spring Boot/Vue前後端分離應用實現SSO單點登錄
https://kknews.cc/code/a6am5pj.html

SpringBoot整合KeyCloak权限管理
https://qianmoq.com/fuwuduan/springboot/springbootzhenghekeycloakquanxianguanli.html

使用Spring Gateway和KeyCloak构建一个OIDC认证系统
https://zhuanlan.zhihu.com/p/138578359

A Quick Guide to Using Keycloak with Spring Boot
https://www.baeldung.com/spring-boot-keycloak

Keycloak与微服务的整合
https://gitee.com/itmuch/spring-cloud-yes/blob/master/doc/keycloak-learn/Keycloak%E6%90%AD%E5%BB%BA%E6%89%8B%E6%8A%8A%E6%89%8B%E6%93%8D%E4%BD%9C%E6%8C%87%E5%8D%97.md

RedHat
https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.4/html/securing_applications_and_services_guide/openid_connect_3

https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.4/html-single/authorization_services_guide/index

https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.4/

posted @ 2020-09-25 15:46 paulwong 阅读(376) | 评论 (0)编辑 收藏

linux shell 中检查文件夹是否存在

To check if a directory exists in a shell script, you can use the following:
if [ -d "$DIRECTORY" ]; then
  # Control will enter here if $DIRECTORY exists.
fi

Or to check if a directory doesn't exist:
if [ ! -d "$DIRECTORY" ]; then
  # Control will enter here if $DIRECTORY doesn't exist.
fi

posted @ 2020-09-25 11:11 paulwong 阅读(371) | 评论 (0)编辑 收藏

linux shell 中函数的调用

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

上面调用fun1时,打印结果却不返回34,这是为何?原来函数只是返回结果成功与否的值,并不能自定义。因此要改成下面这种写法

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

https://stackoverflow.com/questions/17336915/return-value-in-a-bash-function

posted @ 2020-09-25 11:06 paulwong 阅读(362) | 评论 (0)编辑 收藏

在ECLIPSE中切换到新建的分支

如果已经在ECLIPSE中CLONE了GIT的项目,这时当GIT中又新建了项目,ECLIPSE无法切换到这新建的项目,解决办法:


In the Git Repositories view:

  1. Right-click the repository and choose Fetch from Upstream
  2. If the new branch will not shown up below Branches/Remote Tracking, you have to configure fetch:
    1. Right-click the fetch node below Remotes/origin and choose Configure Fetch...
    2. In the Configure Fetch make sure there is only the single Ref mapping (assuming the remote is named origin+refs/heads/*:refs/remotes/origin/*:
      Configure fetch

这时再次Fetch from upstream,则新建的项目再次重现:Git Repositories View-->Branches-->Remote Checking中。
双击新的分支,选:Check out as New Local Branch即可。

https://stackoverflow.com/questions/47390703/how-do-i-get-a-new-branch-to-show-up-in-eclipse-git-remote-tracking/47391183

posted @ 2020-09-24 15:16 paulwong 阅读(702) | 评论 (0)编辑 收藏

向所有服务器发送相同命令

先在主控机执行ssh-keygen,再向被控机传输key,
ssh-copy-id -i ~/.ssh/id_rsa.pub user1@ip

样例脚本transfer-artemis.sh如下:
#!/bin/bash

loop_server(){
    for ((i=2; i<=8; i++))
    do
      ipd=10.10.31.1${i}2
      echo ${ipd}
      $1 ${ipd}
    done
    
    for ((i=1; i<=2; i++))
    do
      ipd=10.20.31.1${i}2
      echo ${ipd}
      $1 ${ipd}
    done
    
}

start_artemis_cmd(){
    echo "ssh user1@${1} '/opt/myapp/artemis/apache-artemis-2.15.0/instance/bin/artemis-service start'"
    ssh user1@${1} '/opt/myapp/artemis/apache-artemis-2.15.0/instance/bin/artemis-service start'
}

stop_artemis_cmd(){
    echo "ssh user1@${1} '/opt/myapp/apache-activemq-5.15.10/bin/activemq stop'"
    echo "ssh user1@${1} '/opt/myapp/artemis/apache-artemis-2.15.0/instance/bin/artemis-service stop'"
    ssh user1@${1} '/opt/myapp/apache-activemq-5.15.10/bin/activemq stop'
    ssh user1@${1} '/opt/myapp/artemis/apache-artemis-2.15.0/instance/bin/artemis-service stop'
}

scp_artemis_cmd(){
    echo "ssh user1@${1} 'rm -rf /opt/myapp/artemis'"
    echo "scp -r /opt/myapp/artemis user1@${1}:/opt/myapp/"
    ssh user1@${1} 'rm -rf /opt/myapp/artemis'
    scp -r /opt/myapp/artemis user1@${1}:/opt/myapp/
}

stop_artemis(){
    loop_server stop_artemis_cmd
}

start_artemis(){
    loop_server start_artemis_cmd
}

scp_artemis(){ 
    loop_server scp_artemis_cmd
}

#start_artemis "Hello start_artemis"

$1

执行命令:
./transfer-artemis.sh start_artemis

posted @ 2020-09-24 10:52 paulwong 阅读(331) | 评论 (0)编辑 收藏

How do I set directory permissions in maven output?

pom.xml:
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.1.1</version>

            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>

                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>

            <configuration>
                <descriptors>
                    <descriptor>src/main/assembly/descriptor.xml</descriptor>
                </descriptors>
                <archiverConfig>
                    <directoryMode>0755</directoryMode>
                    <defaultDirectoryMode>0755</defaultDirectoryMode>
                    <fileMode>0644</fileMode>
                </archiverConfig>
            </configuration>
        </plugin>

src/main/assembly/descriptor.xml,在此文件覆盖:
<fileSets>
    <fileSet>
        <directory>src/conf</directory>
        <outputDirectory>conf</outputDirectory>
        <fileMode>0600</fileMode>
        <directoryMode>0700</directoryMode>
    </fileSet>
    <fileSet>
        <directory>src/db</directory>
        <outputDirectory>db</outputDirectory>
    </fileSet>
    <fileSet>
        <directory>src/bin</directory>
        <outputDirectory>bin</outputDirectory>
        <fileMode>0755</fileMode>
    </fileSet>


posted @ 2020-09-23 17:01 paulwong 阅读(226) | 评论 (0)编辑 收藏

深入浅出 Retrofit,这么牛逼的框架你们还不来看看?

https://segmentfault.com/a/1190000005638577

posted @ 2020-09-16 09:41 paulwong 阅读(241) | 评论 (0)编辑 收藏

Spring Retry框架——看这篇就够了

https://my.oschina.net/marvelcode/blog/4563352

posted @ 2020-09-15 13:39 paulwong 阅读(324) | 评论 (0)编辑 收藏

linux nohup命令详解

nohup命令及其输出文件

nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思( n ohang up)。

一般都是在linux下nohup格式:  

nohup command

或者

nohup command &

这之间的差别是带&的命令行,即使terminal(终端)关闭,或者电脑死机程序依然运行(前提是你把程序递交到服务器上);

它把标准输出(STDOUT)和标准错误(STDERR)结果输出到nohup.txt文件这个看似很方便,但是当输出很大的时候,nohup.txt文件会非常大,或者多个后台命令的时候大家都会输出到nohup.txt文件,不利于查找结果和调试程序。

所以能够重定向输出会非常方便。下面要介绍标准输出,标准输入 和标准错误了。

其实我们一直都在用,只是没有注意到, 

比如

>./command.sh > output

#这其中的>就是标准输出符号,其实是 1>output 的缩写

>./command.sh 2> output

#这里的2>就是将标准错误输出到output文件里。

而0< 则是标准输入了。

下面步入正题,重定向后台命令

>nohup ./command.sh > output 2>&1 &

解释:前面的nohup 和后面的&我想大家都能明白了把。

主要是中间的 2>&1的意思

这个意思是把标准错误(2)重定向到标准输出中(1),而标准输出又导入文件output里面,   www.2cto.com

所以结果是标准错误和标准输出都导入文件output里面了。

至于为什么需要将标准错误重定向到标准输出的原因,那就归结为标准错误没有缓冲区,而stdout有。

这就会导致 >output 2>output 文件output被两次打开,而stdout和stderr将会竞争覆盖,这肯定不是我门想要的.

这就是为什么有人会写成:

nohup ./command.sh >output 2>output
出错的原因了

##########################

最后谈一下/dev/null文件的作用

这是一个无底洞,任何东西都可以定向到这里,但是却无法打开。

所以一般很大的stdou和stderr当你不关心的时候可以利用stdout和stderr定向到这里

>./command.sh >/dev/null 2>&1

posted @ 2020-09-02 11:52 paulwong 阅读(368) | 评论 (0)编辑 收藏

Scheduling a task in Java within a CompletableFuture

When we want to do something later in our Java code, we often turn to the ScheduledExecutorService. This class has a method called schedule(), and we can pass it some code to be run later like this:

ScheduledExecutorService executor =
    Executors.newScheduledThreadPool(4);
executor.schedule(
    () -> {System.out.println("..later");},
    1,
    TimeUnit.SECONDS
);
System.out.println("do");
// (Don't forget to shut down the executor later)

The above code prints “do…” and then one second later it prints “…later”.

We can even write code that does some work and returns a result in a similar way:

// (Make the executor as above.)
ScheduledFuture future = executor.schedule(
    () -> 10 + 25, 1, TimeUnit.SECONDS);
System.out.println("answer=" + future.get())


The above code prints “answer=35”. When we call get() it blocks waiting for the scheduler to run the task and mark the ScheduledFuture as complete, and then returns the answer to the sum (10 + 25) when it is ready.

This is all very well, but you may note that the Future returned from schedule() is a ScheduledFuture, and a ScheduledFuture is not a CompletableFuture.

Why do you care? Well, you might care if you want to do something after the scheduled task is completed. Of course, you can call get(), and block, and then do something, but if you want to react asynchronously without blocking, this won’t work.

The normal way to run some code after a Future has completed is to call one of the “then*” or “when*” methods on the Future, but these methods are only available on CompletableFuture, not ScheduledFuture.

Never fear, we have figured this out for you. We present a small wrapper for schedule that transforms your ScheduledFuture into a CompletableFuture. Here’s how to use it:

CompletableFuture<Integer> future =
    ScheduledCompletable.schedule(
        executor,
        () -> 10 + 25,
        1,
        TimeUnit.SECONDS
     );
future.thenAccept(
    answer -> {System.out.println(answer);}
);
System.out.println("Answer coming")


The above code prints “Answer coming…” and then “35”, so we can see that we don’t block the main thread waiting for the answer to come back.

So far, we have scheduled a synchronous task to run in the background after a delay, and wrapped its result in a CompletableFuture to allow us to chain more tasks after it.

In fact, what we often want to do is schedule a delayed task that is itself asynchronous, and already returns a CompletableFuture. In this case it feels particularly natural to get the result back as a CompletableFuture, but with the current ScheduledExecutorService interface we can’t easily do it.

It’s OK, we’ve figured that out too:

Supplier<CompletableFuture<Integer>> asyncTask = () ->
    CompletableFuture.completedFuture(10 + 25);
CompletableFuture<Integer> future =
    ScheduledCompletable.scheduleAsync(
        executor, asyncTask, 1, TimeUnit.SECONDS);
future.thenAccept(
    answer -> {System.out.println(answer);}
);
System.out.println("Answer coming")


The above code prints “Answer coming…” and then “35”, so we can see that our existing asynchronous task was scheduled in the background, and we didn’t have to block the main thread waiting for it. Also, under the hood, we are not blocking the ScheduledExecutorService‘s thread (from its pool) while the async task is running – that task just runs in whatever thread it was assigned when it was created. (Note: in our example we don’t really run an async task at all, but just immediately return a completed Future, but this does work for real async tasks.)

I know you’re wondering how we achieved all this. First, here’s how we run a simple blocking task in the background and wrap it in a CompletableFuture:

public static <T> CompletableFuture<T> schedule(
    ScheduledExecutorService executor,
    Supplier<T> command,
    long delay,
    TimeUnit unit
) {
    CompletableFuture<T> completableFuture = new CompletableFuture<>();
    executor.schedule(
        (() -> {
            try {
                return completableFuture.complete(command.get());
            } catch (Throwable t) {
                return completableFuture.completeExceptionally(t);
            }
        }),
        delay,
        unit
    );
    return completableFuture;
}


And here’s how we delay execution of an async task but still return its result in a CompletableFuture:

public static <T> CompletableFuture<T> scheduleAsync(
    ScheduledExecutorService executor,
    Supplier<CompletableFuture<T>> command,
    long delay,
    TimeUnit unit
) {
    CompletableFuture<T> completableFuture = new CompletableFuture<>();
    executor.schedule(
        (() -> {
            command.get().thenAccept(
                t -> {completableFuture.complete(t);}
            )
            .exceptionally(
                t -> {completableFuture.completeExceptionally(t);return null;}
            );
        }),
        delay,
        unit
    );
    return completableFuture;
}


Note that this should all work to run methods like exceptionally()thenAccept()whenComplete() etc.

Feedback and improvements welcome!


https://www.artificialworlds.net/blog/2019/04/05/scheduling-a-task-in-java-within-a-completablefuture/

posted @ 2020-08-24 09:06 paulwong 阅读(349) | 评论 (0)编辑 收藏

如何找出LINUX中消耗CPU最大的应用的PID

有时查看LINUX时,会发现当前CPU消耗连续保持80%,如何找出是哪个应用呢?
top -i //输出应用列表,并隐藏IDEL的应用
P //在列表时,按P,则按CPU的使用排序


How To Check CPU Utilization In Linux With Command Line
https://phoenixnap.com/kb/check-cpu-usage-load-linux

posted @ 2020-08-14 11:52 paulwong 阅读(319) | 评论 (0)编辑 收藏

CompletableFuture

很久以前多线程是这样创建:
Thread t1 = new Thread();
Thread t2 = new Thread();
t1.start(); // 启动新线程
t2.start(); // 启动新线程

由于创建和销毁线程是非常耗资源,因此改成线程建好后不销毁,可以重用,用户只需提供run方法的具体实现:
public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> stringFuture = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "async thread";
            }
        });
        Thread.sleep(1000);
        System.out.println("main thread");
        System.out.println(stringFuture.get());

    }

但如果很多线程被创建,并且线程间有依赖,即按流程和条件执行线程,实现起来就有点复杂,于是CompletableFuture横空出世。一共有50各方法可供使用。
CompletableFuture.supplyAsync(),相当于创建了ExecutorService,同时也创建了Callable,然后提交到线程池中执行。
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> "任务B");
CompletableFuture<String> futureC = futureB.thenApply(b -> {
      System.out.println("执行任务C.");
      System.out.println("参数:" + b);//参数:任务B
      return "a";
});


!!How to use CompletableFuture and Callable in Java
https://ducmanhphan.github.io/2020-02-10-How-to-use-CompletableFuture-Callable-in-Java/

使用CompletableFuture优化你的代码执行效率
https://www.cnblogs.com/fingerboy/p/9948736.html

CompletableFuture 使用详解
https://www.jianshu.com/p/6bac52527ca4

使用CompletableFuture
https://www.liaoxuefeng.com/wiki/1252599548343744/1306581182447650


https://github.com/eugenp/tutorials/blob/master/core-java-modules/core-java-concurrency-basic/src/test/java/com/baeldung/completablefuture/CompletableFutureLongRunningUnitTest.java

posted @ 2020-08-14 11:46 paulwong 阅读(282) | 评论 (0)编辑 收藏

Springboot+proguard+maven 混淆

https://blog.csdn.net/qq_35981283/article/details/78529929

posted @ 2020-08-13 10:53 paulwong 阅读(898) | 评论 (0)编辑 收藏

MYSQL经典配置

/etc/my.cnf

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
#symbolic-links=0

#vish start
open_files_limit = 8000
max_connections = 500
#set-variable=max_connections=500
thread_concurrency = 8
#concurrent_insert=2
thread_cache_size=2000

interactive_timeout=180
wait_timeout=180

max_allowed_packet = 32M
key_buffer_size = 3000M
read_buffer_size = 16M
read_rnd_buffer_size = 4M
bulk_insert_buffer_size = 256M
myisam_sort_buffer_size = 16M
myisam_max_sort_file_size = 256M
myisam_repair_threads = 1
#myisam_recover = 4
max_heap_table_size = 2048M
tmp_table_size = 1024M
table_open_cache = 2000
table_cache = 2000
sort_buffer_size = 128M 
join_buffer_size = 128M 
query_cache_size = 128M 
query_cache_limit = 128M 

#slow_query_log=1
log-slow-queries
long_query_time=2
#log_queries_not_using_indexes=1
slow_query_log_file = /var/log/mariadb/host_name-slow.log

[mysqldump]
user=root
password=*********
#max_allowed_packet = 1024M 

[myisamchk] 
key_buffer_size = 2500M 
sort_buffer_size = 1024M 
read_buffer = 32M
write_buffer = 32M

[innodb]
innodb_buffer_pool_size=3G
innodb_buffer_pool_instance=2
innodb_additional_mem_pool_size=20M
innodb_log_file_size= 512M
innodb_log_buffer_size=16M
innodb_flush_log_at_trx_commit=1
innodb_thread_concurrency=16
#innodb_read_io_threads=16
#innodb_write_io_threads=16
#innodb_io_capacity=2000
#innodb_lock_wait_timeout=120

innodb_flush_method=O_DIRECT
#vish end

[mysqld_safe]
log-error=/var/log/mariadb/mariadb.log
pid-file=/var/run/mariadb/mariadb.pid
#!includedir /etc/my.cnf.d

posted @ 2020-08-07 18:01 paulwong 阅读(279) | 评论 (0)编辑 收藏

shell 脚本替换文件中某个字符串

1、将当前目录下包含jack串的文件中,jack字符串替换为tom
sed -i "s/jack/tom/g" `grep "jack" -rl ./`

2、将某个文件中的jack字符串替换为tom

sed -i "s/jack/tom/g" test.txt

参考连接:http://blog.csdn.net/lizhi200404520/article/details/7968483

posted @ 2020-08-07 18:01 paulwong 阅读(3066) | 评论 (0)编辑 收藏

JMETER连接数据库做压力测试

JMETER作为一个压力测试工具,可模拟上千用户进行登录之类的操作。
但一般图形界面只用来制作测试计划,真正运行是在LINUX上。
同时也可以作为压测MYSQL数据库等。

配置基本流程:添加测试计划-->添加线程组,在添加的线程组中-->添加JDBC配置组件-->添加JDBC访问的SQL语句的REQUEST组件-->配置REQUEST和配置组件相关联的变量值-->添加查看结果组件-->设定要运行的线程数-->执行。

执行期间如有问题,查看DOS窗口。

【JMeter】JMeter完成一个MySql压力测试
https://www.cnblogs.com/zhaoxd07/p/4895681.html

JMeter 如何与 MySQL 进行整合测试
https://juejin.im/post/6844903728215162887

JMeter JDBC连接MySql配置
https://www.jianshu.com/p/cde2729421d1

jmeter(二十五)linux环境运行jmeter并生成报告
https://www.cnblogs.com/imyalost/p/9808079.html

posted @ 2020-08-07 17:59 paulwong 阅读(333) | 评论 (0)编辑 收藏

TCL 的连接TIME_WAIT太多的处理

TCP TIME_WAIT 详解
https://www.zhuxiaodong.net/2018/tcp-time-wait-instruction/

mysql服务器,大量tcp连接状态TIME_WAIT
https://www.cnblogs.com/zhjh256/p/6363312.html

linux下释放TIME_WAIT方法
https://blog.csdn.net/u013488847/article/details/46421867

posted @ 2020-08-07 10:27 paulwong 阅读(329) | 评论 (0)编辑 收藏

Different ways to create objects in Java

https://www.geeksforgeeks.org/different-ways-create-objects-java/


posted @ 2020-08-07 10:04 paulwong 阅读(232) | 评论 (0)编辑 收藏

RHEL\CentOS 7 下 MySQL 连接数被限制为214个

MARIADB的默认最大连接数是100,当试图更改配置文件/etc/my.conf追加max_connections=1000,并重启服务时,发现最大连接数被重置为214,这是由于系统问题,open_files数为1024,如何正确地更改是个复杂的问题。

MARIADB中一些查当前连接数/最大连接数的命令为:

MariaDB [(none)]
show variables like "max_connections";
show processlist;
show status where variable_name = 'threads_connected';
show status where variable_name = 'max_used_connections';
show variables like 'max_user_connections';
show variables like '%connection%';

正确方案:
RHEL\CentOS 7 下 MySQL 连接数被限制为214个
https://waylau.com/rhel-mysql-connections-limit-214/

Centos7.4下面mysql的max_connections不生效的问题。
https://blog.51cto.com/wangqh/2131340

为何要在SERVICE中添加允许打开的文件数?
Centos7下修改/etc/security/limits.conf文件只在用户登录后打开的进程有效,系统服务或通过rc.local启动的无效,系统服务修改文件/usr/lib/systemd/system/SOME_SERVICE.service添加:
[Service]
LimitNOFILE=65535


关于LINUX系统的最大打开文件数:
https://blog.51cto.com/as007012/1956222

https://www.haiyun.me/archives/linux-file-limit.html

posted @ 2020-08-05 20:55 paulwong 阅读(372) | 评论 (0)编辑 收藏

统计文件行数或LIST目录的条数

https://www.thegeekdiary.com/how-to-count-lines-in-a-file-in-unix-linux/

posted @ 2020-08-05 11:32 paulwong 阅读(304) | 评论 (0)编辑 收藏

21道系统设计+面向对象的面试题


事不宜迟,我们马上来看这21道最受欢迎,提问率最高的系统设计+面向对象的面试题,这些问题的分析能让你轻松应对项目设计的面试。

1. 如何用Java设计自动售货机? (解决方案)

你需要编写代码来设计自动售货机,该自动售货机售卖如巧克力,糖果,冷饮之类的产品,需要投入硬币进行购买,例如5分、10分、25分,50分、1元等。确保插入硬币获得产品 ,然后退回零钱。 另外,编写单元测试以证明这些常见用例有效。 如果遇到困难,可以阅读有关解决这些经典系统设计问题的两部分文章(第1部分和第2部分)。

2. 如何设计类似Goo.gl或Bit.Ly的URL缩短服务?(解决方案)

这是一个常见的系统设计问题。 你给了一个普通长度的URL,你将如何设计为其生成较短且唯一的别名的服务? 如果你不熟悉URL缩短程序服务,请查看一些常用的例子,例如Google的goo.gl和Twitter使用的bit.ly。确保在一些设计决策后提供数据库模式和基本原理,例如保留数据多长时间,如何获取统计信息和分析等。如果遇到问题,可以参照关于Educative上“系统设计面试课程”中给出的解决方案。

3.你如何设计交通控制系统?

经典系统设计问题仍然提及频率。 确保你知道如何从一种状态过渡到另一种状态,例如从红色过渡到绿色,从绿色过渡到橙色再过渡到红色等。

4. 如何设计限价单? (解决方案)

限价订单簿在证券交易所中用于根据价格和时间优先级将买入订单与卖出订单进行匹配。 你会怎么做? 你将使用哪种数据结构? 请记住,匹配的速度是关键,也是可靠的。 如果你需要复习数据结构,则可以查看Java课程中的数据结构和算法,如果你遇到困难,可以在这里查看我的解决方案。

5. 你如何设计类似Pastebin的网站?

Pastebin允许你粘贴文本或代码,然后在任意位置共享指向该代码的链接。它不是在线代码编辑器,但是你可以使用它来存储任何类型的文本。

6. 你将如何创建自己的Instagram? (解决方案)

Instagram是一个照片共享应用程序,它提供了一些自定义滤镜以提高照片质量。你的应用程序应该具有照片上传功能,为搜索标记照片以及一些基本的过滤器。如果你可以添加共享或社交网络,那就太好不过了。顺便说一句,如果你遇到困难,还可以在Educative的如何准备系统设计面试课程中看到免费的解决方案。

7. 你如何设计像Google Drive或Dropbox这样的全局文件共享和存储应用程序?

这些用于存储和共享文件,照片和其他媒体。你如何设计诸如允许用户上传/查看/搜索/共享文件或照片之类的东西?跟踪文件共享的权限,并允许多个用户编辑同一文档?

8. 你如何设计类似Whatsapp或Facebook Messenger的聊天应用程序?

你肯定使用过WhatsApp和Facebook?没有?如果没有,我告诉你聊天应用程序允许你向你的朋友发送消息。这是点对点的连接。你保留一个朋友列表,查看他们的状态并聊天。在WhatsApp中,你也可以连接组,但适用于高级和经验丰富的开发人员。至少,你应该提供一种设计来保留好友列表并从中发送和接收消息。

9. 你如何设计Twitter克隆?

Twitter是一种流行的消息服务,可让你向所有关注者广播消息。你发布推文,你的关注者可以看到这些消息,他们可以喜欢或转发。确保实现了诸如粉丝关注,主题标签,发博,删除等常用功能。如果你觉得自己有点困难,则可以按照如何应对系统设计面试的课程解决方案进行操作。

10. 如何设计像YouTube或Netflix这样的全球视频流服务?

设计像NetFlix或YouTube这样的视频流服务时,关键是顺畅和缓冲以及在低带宽连接上的功能,如何应对这些挑战?

11. 如何设计ATM机?

ATM机允许用户存入和提取现金。它还允许用户查看其余额。你如何设计这样的系统?你面临的主要挑战是什么?

12. 如何设计API速率限制器?

13. 你如何设计Twitter搜索?

14. 如何设计类似于Google的网络爬虫?

网络搜寻器可以访问网站并像Google一样搜寻所有链接并将它们编入索引,以便它们以后可以出现在搜索结果中。爬网程序还可用于在一组目录中搜索特定文件,你如何设计此类文件?主要挑战是什么?

15. 如何设计Facebook的Newsfeed?你将使用哪种算法?

新闻源是Facebook的重要组成部分,它使用户可以查看他的世界正在发生的事情,包括朋友和家人,他喜欢的页面,他关注的组以及Facebook广告。

Newsfeed算法的作用是显示对用户来说最重要的消息,并且可以产生很高的参与度。显然,来自朋友和家人的消息应该优先处理。如果你觉得陷入困境,则可以按照如何做好系统设计面试的课程所提出解决答案。

16. 如何设计Yelp或Nearby Friends?

17. 如何设计全球乘车服务E.G. Uber,Grab或Ola后端?

优步(Uber)和奥拉(Ola)是最受欢迎的两种乘车服务,它将驾驶员和乘客都组合到一起。你如何设计让乘客看到附近的出租车并进行预订?

18. 如何设计BookMyShow?

一个允许你预订电影院和活动门票的网站。这实际上是一家运转良好的印度创业公司。

19. 如何设计Quora,Reddit或Hackernews等社交网络+留言板服务网站?

Reddit,Quora和HackerNews是一些最受欢迎的社交网站,用户可以在其中发布问题或共享链接。其他用户可以回答问题或对共享链接发表评论。

20. 你如何设计类似Airbnb的应用程序?

它允许某些用户上传房间出租,而其他用户则出租。某些功能仅适用于管理员,发布者和订阅者。

21. 你如何设计电梯系统?

我们大多数人都使用电梯,你在一些大型办公大楼中,你可以看到3到4部电梯。你需要为此设计软件,以便可以在不同楼层快速使用。你可以假设你有两部电梯和一栋10层楼的建筑。

如果你需要一些帮助,那么我建议你可在Udemy上学习如何做好系统设计面试课程,Rajat Mehta在那里有一个完整的案例研究来解决这个问题。你还将在那里学习高级系统设计,低级系统设计和数据库设计。

posted @ 2020-08-02 16:54 paulwong 阅读(917) | 评论 (0)编辑 收藏

如何在JENKINS中使用HTTP下载 NEXUS上的ZIP

需添加如下PLUGIN:File Operations

添加如下STEP:File Download Operation

NEXUS上的下载地址按如下模式:http://localhost:8081/service/rest/v1/search/assets/download?group=org.osgi&name=org.osgi.core&version=4.3.1&maven.extension=jar&maven.classifier

posted @ 2020-07-31 20:18 paulwong 阅读(518) | 评论 (0)编辑 收藏

使用ssh-keygen和ssh-copy-id三步实现SSH无密码登录 和ssh常用命令

ssh-keygen  产生公钥与私钥对.
ssh-copy-id 将本机的公钥复制到远程机器的authorized_keys文件中,ssh-copy-id也能让你有到远程机器的home, ~./ssh , 和 ~/.ssh/authorized_keys的权利

第一步:在本地机器上使用ssh-keygen产生公钥私钥对
  1. zhz@zhz:~/$ ssh-keygen
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/zhz/.ssh/id_rsa):
  2. Enter passphrase (empty for no passphrase): [Press enter key
  3. same passphrase again: [Pess enter key]
  4. Your identification has been saved in /home/zhz/.ssh/id_rsa.
  5. Your public key has been saved in /home/zhz/.ssh/id_rsa.pub.
  6. The key fingerprint is:
  7. 用cat命令查看是否生成产生公钥私钥对
  8. zhz@zhz:~$ cat .ssh/id_rsa.pub
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl9N5+xboqSIagBx02rdZ2fkROCPW8iW7hl6Gu+2hkBYYy/b1qcOm8RF/AMyas3i0QEK7Hcu9H51l2lulVbS5n9M9FaWIyYzssaS012x2mg9iA6MxPMlaXFsZ5jnVrGicndzf3VUu9kCErp5q0OzzMjsG3PKQevzWZJSBaFgc8NF5ZJ+VT54BN8ktMTHVwOo15I2Uai+bs4eP0NsuwIJmGyYIUOuvTuUtJxGV3hZ+tcjhupupqVCwYOE+cDz8VkFBGtnKsdE69hWoY2VUfEOAfHZptra7Ce9dXfDgx9jxuuNiJYtGo/bZDfe+UJ5HUv8wrL+hFeRIihdmP2CKJD8j5 zhz@zhz


第二步:用ssh-copy-id将公钥复制到远程机器中
  1. zhz@zhz:~$  ssh-copy-id -i .ssh/id_rsa.pub  用户名字@192.168.x.xxx

注意: ssh-copy-id 将key写到远程机器的 ~/ .ssh/authorized_key.文件中

第三步: 登录到远程机器不用输入密码
  1. zhz@zhz:~$  ssh 用户名字@192.168.x.xxx

  2. Last login: Sun Nov 16 17:22:33 2008 from 192.168.1.2

常见问题:

  1. ssh-copy-id -u eucalyptus -i ~eucalyptus/.ssh/id_rsa.pub ssh 用户名字@192.168.x.xxx
  2. 第一次需要密码登录

上述是给eucalyptus用户赋予无密码登陆的权利

  1. /usr/bin/ssh-copy-id: ERROR: No identities found

使用选项 -i ,当没有值传递的时候或者 如果 ~/.ssh/identity.pub 文件不可访问(不存在), ssh-copy-id 将显示上述的错误信息  ( -i选项会优先使用将ssh-add -L的内容)



ssh命令

1、复制SSH密钥到目标主机,开启无密码SSH登录

ssh-copy-id user@host

如果还没有密钥,请使用ssh-keygen命令生成。

2、从某主机的80端口开启到本地主机2001端口的隧道

ssh -N -L2001:localhost:80 somemachine

现在你可以直接在浏览器中输入http://localhost:2001访问这个网站。

3、将你的麦克风输出到远程计算机的扬声器

dd if=/dev/dsp | ssh -c arcfour -C username@host dd of=/dev/dsp

这样来自你麦克风端口的声音将在SSH目标计算机的扬声器端口输出,但遗憾的是,声音质量很差,你会听到很多嘶嘶声。

4、比较远程和本地文件

ssh user@host cat /path/to/remotefile | diff /path/to/localfile –

在比较本地文件和远程文件是否有差异时这个命令很管用。

5、通过SSH挂载目录/文件系统

sshfs name@server:/path/to/folder /path/to/mount/point

http://fuse.sourceforge.net/sshfs.html下载sshfs,它允许你跨网络安全挂载一个目录。

6、通过中间主机建立SSH连接

ssh -t reachable_host ssh unreachable_host

Unreachable_host表示从本地网络无法直接访问的主机,但可以从reachable_host所在网络访问,这个命令通过到reachable_host的“隐藏”连接,创建起到unreachable_host的连接。

7、将你的SSH公钥复制到远程主机,开启无密码登录 – 简单的方法

ssh-copy-id username@hostname

8、直接连接到只能通过主机B连接的主机A

ssh -t hostA ssh hostB

当然,你要能访问主机A才行。

9、创建到目标主机的持久化连接

ssh -MNf <user>@<host>

在后台创建到目标主机的持久化连接,将这个命令和你~/.ssh/config中的配置结合使用:

Host host ControlPath ~/.ssh/master-%r@%h:%p ControlMaster no

所有到目标主机的SSH连接都将使用持久化SSH套接字,如果你使用SSH定期同步文件(使用rsync/sftp/cvs/svn),这个命令将非常有用,因为每次打开一个SSH连接时不会创建新的套接字。

10、通过SSH连接屏幕

ssh -t remote_host screen –r

直接连接到远程屏幕会话(节省了无用的父bash进程)。

11、端口检测(敲门)

knock <host> 3000 4000 5000 && ssh -p <port> user@host && knock <host> 5000 4000 3000

在一个端口上敲一下打开某个服务的端口(如SSH),再敲一下关闭该端口,需要先安装knockd,下面是一个配置文件示例。

[options] logfile = /var/log/knockd.log [openSSH] sequence = 3000,4000,5000 seq_timeout = 5 command = /sbin/iptables -A INPUT -i eth0 -s %IP% -p tcp –dport 22 -j ACCEPT tcpflags = syn [closeSSH] sequence = 5000,4000,3000 seq_timeout = 5 command = /sbin/iptables -D INPUT -i eth0 -s %IP% -p tcp –dport 22 -j ACCEPT tcpflags = syn

12、删除文本文件中的一行内容,有用的修复

ssh-keygen -R <the_offending_host>

在这种情况下,最好使用专业的工具。

13、通过SSH运行复杂的远程shell命令

ssh host -l user $(<cmd.txt)

更具移植性的版本:

ssh host -l user “`cat cmd.txt`”

14、通过SSH将MySQL数据库复制到新服务器

mysqldump –add-drop-table –extended-insert –force –log-error=error.log -uUSER -pPASS OLD_DB_NAME | ssh -C user@newhost “mysql -uUSER -pPASS NEW_DB_NAME”

通过压缩的SSH隧道Dump一个MySQL数据库,将其作为输入传递给mysql命令,我认为这是迁移数据库到新服务器最快最好的方法。

15、删除文本文件中的一行,修复“SSH主机密钥更改”的警告

sed -i 8d ~/.ssh/known_hosts

16、从一台没有SSH-COPY-ID命令的主机将你的SSH公钥复制到服务器

cat ~/.ssh/id_rsa.pub | ssh user@machine “mkdir ~/.ssh; cat >> ~/.ssh/authorized_keys”

如果你使用Mac OS X或其它没有ssh-copy-id命令的*nix变种,这个命令可以将你的公钥复制到远程主机,因此你照样可以实现无密码SSH登录。

17、实时SSH网络吞吐量测试

yes | pv | ssh $host “cat > /dev/null”

通过SSH连接到主机,显示实时的传输速度,将所有传输数据指向/dev/null,需要先安装pv。

如果是Debian:

apt-get install pv

如果是Fedora:

yum install pv

(可能需要启用额外的软件仓库)。

18、如果建立一个可以重新连接的远程GNU screen

ssh -t user@some.domain.com /usr/bin/screen –xRR

人们总是喜欢在一个文本终端中打开许多shell,如果会话突然中断,或你按下了“Ctrl-a d”,远程主机上的shell不会受到丝毫影响,你可以重新连接,其它有用的screen命令有“Ctrl-a c”(打开新的shell)和“Ctrl-a a”(在shell之间来回切换),请访问http://aperiodic.net/screen/quick_reference阅读更多关于screen命令的快速参考。

19、继续SCP大文件

rsync –partial –progress –rsh=ssh $file_source $user@$host:$destination_file

它可以恢复失败的rsync命令,当你通过VPN传输大文件,如备份的数据库时这个命令非常有用,需要在两边的主机上安装rsync。

rsync –partial –progress –rsh=ssh $file_source $user@$host:$destination_file local -> remote

rsync –partial –progress –rsh=ssh $user@$host:$remote_file $destination_file remote -> local

20、通过SSH W/ WIRESHARK分析流量

ssh root@server.com ‘tshark -f “port !22″ -w -' | wireshark -k -i –

使用tshark捕捉远程主机上的网络通信,通过SSH连接发送原始pcap数据,并在wireshark中显示,按下Ctrl+C将停止捕捉,但也会关闭wireshark窗口,可以传递一个“-c #”参数给tshark,让它只捕捉“#”指定的数据包类型,或通过命名管道重定向数据,而不是直接通过SSH传输给wireshark,我建议你过滤数据包,以节约带宽,tshark可以使用tcpdump替代:

ssh root@example.com tcpdump -w – ‘port !22′ | wireshark -k -i –

21、保持SSH会话永久打开

autossh -M50000 -t server.example.com ‘screen -raAd mysession’

打开一个SSH会话后,让其保持永久打开,对于使用笔记本电脑的用户,如果需要在Wi-Fi热点之间切换,可以保证切换后不会丢失连接。

22、更稳定,更快,更强的SSH客户端

ssh -4 -C -c blowfish-cbc

强制使用IPv4,压缩数据流,使用Blowfish加密。

23、使用cstream控制带宽

tar -cj /backup | cstream -t 777k | ssh host ‘tar -xj -C /backup’

使用bzip压缩文件夹,然后以777k bit/s速率向远程主机传输。Cstream还有更多的功能,请访问http://www.cons.org/cracauer/cstream.html#usage了解详情,例如:

echo w00t, i’m 733+ | cstream -b1 -t2

24、一步将SSH公钥传输到另一台机器

ssh-keygen; ssh-copy-id user@host; ssh user@host

这个命令组合允许你无密码SSH登录,注意,如果在本地机器的~/.ssh目录下已经有一个SSH密钥对,ssh-keygen命令生成的新密钥可能会覆盖它们,ssh-copy-id将密钥复制到远程主机,并追加到远程账号的~/.ssh/authorized_keys文件中,使用SSH连接时,如果你没有使用密钥口令,调用ssh user@host后不久就会显示远程shell。

25、将标准输入(stdin)复制到你的X11缓冲区

ssh user@host cat /path/to/some/file | xclip

你是否使用scp将文件复制到工作用电脑上,以便复制其内容到电子邮件中?xclip可以帮到你,它可以将标准输入复制到X11缓冲区,你需要做的就是点击鼠标中键粘贴缓冲区中的内容。

如果你还有其它SSH命令技巧,欢迎在本文评论中帖出。

原文:http://blog.urfix.com/25-ssh-commands-tricks/





posted @ 2020-07-31 14:19 paulwong 阅读(544) | 评论 (0)编辑 收藏

一次删除前5个以外的其他文件夹的SHELL

子文件夹按字母排倒序,只保留前5个,其他的删除。
find path/to/folder/ -mindepth 1 -maxdepth 1 -type d | head -n -5 | xargs rm -rf

-find: the unix command for finding files / directories / links etc.
-/path/to/base/dir: the directory to start your search in.
-mindepth 1 -maxdepth 1 only search the first sub folder
-type d: only find directories
-head -n -5: Filter out all lines except the last 5
-xargs rm -rf: remove each given folder.

posted @ 2020-07-27 11:35 paulwong 阅读(355) | 评论 (0)编辑 收藏

一次删除10天前的文件夹的SHELL

find /path/to/base -mindepth 1 -maxdepth 1 -type d -ctime +10 | xargs rm -rf

-find: the unix command for finding files / directories / links etc.
/path/to/base/dir: the directory to start your search in.
-type d: only find directories
-ctime +10: only consider the ones with modification time older than 10 days
-exec  \;: for each such result found, do the following command in 
-rm -rf dir1 dir2 dir3 


https://stackoverflow.com/questions/13868821/shell-script-to-delete-directories-older-than-n-days

posted @ 2020-07-27 10:48 paulwong 阅读(538) | 评论 (0)编辑 收藏

Git Cherry Pick

https://www.atlassian.com/git/tutorials/cherry-pick


posted @ 2020-07-23 20:21 paulwong 阅读(277) | 评论 (0)编辑 收藏

Spring Cloud异步场景分布式事务怎样做?试试RocketMQ

例如有如下场景,新增一订单,同时为此订单的用户增加积分。场景对应场景表,积分对应积分表,如果要防止订单增加成功而积分增加不成功,则要将此两种操作放在一个事务下。

分布式的场景下,订单服务在一个JVM下,积分服务在另一个JVM下,两者要如何才能达到数据一致(原子)性?


https://zlt2000.gitee.io/2019-09-23-rocketmq-transaction/

posted @ 2020-07-15 10:33 paulwong 阅读(268) | 评论 (0)编辑 收藏

!!zlt-microservices-platform

1. 总体架构图

mark


2. 功能介绍

mark




https://github.com/zlt2000/microservices-platform 

https://zlt2000.gitee.io/

产品文档(付费,微信登录):
https://www.kancloud.cn/zlt2000/microservices-platform


posted @ 2020-07-15 10:15 paulwong 阅读(499) | 评论 (0)编辑 收藏

LINUX SHELL

!!
https://tecadmin.net/tutorial/bash-scripting/

Shell 教程
https://www.runoob.com/linux/linux-shell.html

Check existence of input argument in a Bash shell script
https://stackoverflow.com/questions/6482377/check-existence-of-input-argument-in-a-bash-shell-script

How to Check if a File or Directory Exists in Bash
https://linuxize.com/post/bash-check-if-file-exists/

bash string compare to multiple correct values
https://stackoverflow.com/questions/21157435/bash-string-compare-to-multiple-correct-values

Bash – Check If Two Strings are Equal
https://tecadmin.net/tutorial/bash/examples/check-if-two-strings-are-equal/



posted @ 2020-07-06 09:23 paulwong 阅读(300) | 评论 (0)编辑 收藏

如何优雅地停止SPRING BATCH中的REMOTE CHUNKING JOB

SPRING BATCH中的REMOTE CHUNKING JOB,由于是基于MASTER/SLAVE的架构,其中某个STEP是会在远程机器中执行,如果要停止这个JOB,需要考虑两个问题:
1、什么时候发出停止指令
2、如何等待远程STEP的完成

一般停止JOB,可用JobOperator.stop(long executionId)来停止,但这个无法确定什么时候发出停止指令,如果是在CHUNK的处理中途发出,则会出现回滚的现象。
BATCH_STEP_EXECUTION thead tr {background-color: ActiveCaption; color: CaptionText;} th, td {vertical-align: top; font-family: "Tahoma", Arial, Helvetica, sans-serif; font-size: 8pt; padding: 4px; } table, td {border: 1px solid silver;} table {border-collapse: collapse;} thead .col0 {width: 173px;} .col0 {text-align: right;} thead .col1 {width: 82px;} .col1 {text-align: right;} thead .col2 {width: 282px;} thead .col3 {width: 164px;} .col3 {text-align: right;} thead .col4 {width: 161px;} thead .col5 {width: 161px;} thead .col6 {width: 109px;} thead .col7 {width: 127px;} .col7 {text-align: right;} thead .col8 {width: 109px;} .col8 {text-align: right;} thead .col9 {width: 118px;} .col9 {text-align: right;} thead .col10 {width: 117px;} .col10 {text-align: right;} thead .col11 {width: 142px;} .col11 {text-align: right;} thead .col12 {width: 150px;} .col12 {text-align: right;} thead .col13 {width: 166px;} .col13 {text-align: right;} thead .col14 {width: 137px;} .col14 {text-align: right;} thead .col15 {width: 109px;} thead .col16 {width: 156px;} thead .col17 {width: 161px;}
STEP_EXECUTION_ID VERSION STEP_NAME JOB_EXECUTION_ID START_TIME END_TIME STATUS COMMIT_COUNT READ_COUNT FILTER_COUNT WRITE_COUNT READ_SKIP_COUNT WRITE_SKIP_COUNT PROCESS_SKIP_COUNT ROLLBACK_COUNT EXIT_CODE EXIT_MESSAGE LAST_UPDATED
2304 169 step2HandleXXX 434 2020-06-22 16:27:54 2020-06-22 16:32:46 STOPPED 167 5010 0 4831 0 155 0 161 STOPPED org.springframework.batch.core.JobInterruptedException 2020-06-22 16:32:46


另外SPRING BATCH也不会等远程STEP执行完成,就将JOB的状态设为Complete。

发出停止的指令应通过ChunkListener达成:

public class ItemMasterChunkListener extends ChunkListenerSupport{
    
    private static final Logger log = LoggerFactory.getLogger(ItemMasterChunkListener.class);
    
    
    @Override
    public void beforeChunk(ChunkContext context) {
        log.info("ItemMasterProcessor.beforeChunk");
    }


    @Override
    public void afterChunk(ChunkContext context) {
        log.info("ItemMasterProcessor.afterChunk");
        if(XXXX.isStoppingOrPausing()) {
            log.info("context.getStepContext().getStepExecution().setTerminateOnly()");
            context.getStepContext().getStepExecution().setTerminateOnly();
        }
    }


    @Override
    public void afterChunkError(ChunkContext context) {
        log.info("ItemMasterProcessor.afterChunkError");
    }


}


配置BEAN:

@Bean
@StepScope
public ItemMasterChunkListener novaXItemMasterChunkListener() {
     return new ItemMasterChunkListener();
}
    
this.masterStepBuilderFactory
                    .<X, X>get("step2Handle")
                    .listener(itemMasterChunkListener())
                    .build();


由于是在CHUNK完成的时候发出停止指令,就不会出现ROLLBACK的情况。

等待远程STEP完成,通过读取MQ上的MESSAGE是否被消费完成,PENDDING的MESSAGE为0的条件即可。

public class JobExecutionListenerSupport implements JobExecutionListener {

    /* (non-Javadoc)
     * @see org.springframework.batch.core.domain.JobListener#afterJob()
     
*/
    @Override
    public void afterJob(JobExecution jobExecution) {
        Integer totalPendingMessages = 0;
        String queueName = "";
        
        
        String messageSelector = "JOB_EXECUTION_ID=" + jobExecution.getJobInstance().getInstanceId();
        do{
            totalPendingMessages = 
                    this.jmsTemplate.browseSelected(queueName, messageSelector, 
                                (session, browser) -> 
                                    Collections.list(browser.getEnumeration()).size()
                            );
            
            String brokerURL = null;
            if(jmsTemplate.getConnectionFactory() instanceof JmsPoolConnectionFactory) {
                JmsPoolConnectionFactory connectionFactory =
                        (JmsPoolConnectionFactory)jmsTemplate.getConnectionFactory();
                ActiveMQConnectionFactory activeMQConnectionFactory =
                        (ActiveMQConnectionFactory)connectionFactory.getConnectionFactory();
                brokerURL = activeMQConnectionFactory.getBrokerURL();
            } else if(jmsTemplate.getConnectionFactory() instanceof CachingConnectionFactory) {
                CachingConnectionFactory connectionFactory =
                        (CachingConnectionFactory)jmsTemplate.getConnectionFactory();
                ActiveMQConnectionFactory activeMQConnectionFactory =
                        (ActiveMQConnectionFactory)connectionFactory.getTargetConnectionFactory();
                brokerURL = activeMQConnectionFactory.getBrokerURL();
            }
            
            LOGGER.info("queueName = {}, {}, totalPendingMessages = {}, url={}", 
                    queueName, messageSelector, totalPendingMessages, brokerURL);
            Assert.notNull(totalPendingMessages, "totalPendingMessages must not be null.");
            try {
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
                LOGGER.error(e.getMessage(), e);
            }
        } while(totalPendingMessages.intValue() > 0);
        
    }

    /* (non-Javadoc)
     * @see org.springframework.batch.core.domain.JobListener#beforeJob(org.springframework.batch.core.domain.JobExecution)
     
*/
    @Override
    public void beforeJob(JobExecution jobExecution) {
    }

}


这样整个JOB就能无异常地停止,且会等待远程STEP完成。

Reference:
https://docs.spring.io/spring-batch/docs/4.1.3.RELEASE/reference/html/common-patterns.html#stoppingAJobManuallyForBusinessReasons

https://stackoverflow.com/questions/13603949/count-number-of-messages-in-a-jms-queue

https://stackoverflow.com/questions/55499965/spring-batch-stop-job-execution-from-external-class

https://stackoverflow.com/questions/34621885/spring-batch-pollable-channel-with-replies-contains-chunkresponses-even-if-job


posted @ 2020-06-23 11:00 paulwong 阅读(773) | 评论 (0)编辑 收藏

为啥文件的CHECKSUM中SHA512比MD5高级?

https://stackoverflow.com/questions/2117732/reasons-why-sha512-is-superior-to-md5

posted @ 2020-06-16 10:21 paulwong 阅读(304) | 评论 (0)编辑 收藏

GIT资源


http://jartto.wang/tags/git/

posted @ 2020-06-04 10:38 paulwong 阅读(253) | 评论 (0)编辑 收藏

彻底搞懂 Git-Rebase

根据分支1新建了功能分支1,并在此上开发一段时间,后来分支1被别人提交了代码,因此分支1比功能分支1要新,这时,可以将功能分支1与分支1进行合并,但会多出很多COMMIT,这时就出现了rebase,
GIT会将功能分支1上的所有COMMIT另存一个文件,回退到分支1原始状态,再更新至当前分支1的状态,再把另存文件的COMMIT执行一遍,就成了已经合并的新的功能分支1。

http://jartto.wang/2018/12/11/git-rebase/

GIT使用rebase和merge的正确姿势
https://zhuanlan.zhihu.com/p/34197548

git merge和git rebase的区别, 切记:永远用rebase
https://zhuanlan.zhihu.com/p/75499871



posted @ 2020-06-04 10:37 paulwong 阅读(260) | 评论 (0)编辑 收藏

How To Run Java Jar Application with Systemd on Linux

https://computingforgeeks.com/how-to-run-java-jar-application-with-systemd-on-linux/

systemd自启动java程序
https://www.cnblogs.com/yoyotl/p/8178363.html
------------------------------------------------------------

[Unit]
Description=TestJava
After=network.target

[Service]
Type=forking
ExecStart=/home/test/startTest.sh
ExecStop=/home/test/stopTest.sh

[Install]
WantedBy=multi-user.target

-------------------------------------------------------------
How to Autorun application at the start up in Linux
https://developer.toradex.com/knowledge-base/how-to-autorun-application-at-the-start-up-in-linux

How to automatically run program on Linux startup
https://www.simplified.guide/linux/automatically-run-program-on-startup


Systemd 入门教程:实战篇
https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

Systemd 入门教程:命令篇

http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

posted @ 2020-05-11 16:16 paulwong 阅读(262) | 评论 (0)编辑 收藏

MariaDB Galera Cluster

What is MariaDB Galera Cluster?
https://mariadb.com/kb/en/what-is-mariadb-galera-cluster/

Prepare yum install repository:
https://downloads.mariadb.org/mariadb/repositories/#distro=CentOS&distro_release=centos7-amd64--centos7&mirror=coreix&version=10.4

MariaDB Galera Cluster部署实战
https://jeremyxu2010.github.io/2018/02/mariadb-galera-cluster%E9%83%A8%E7%BD%B2%E5%AE%9E%E6%88%98/

9 Tips for Going in Production with Galera Cluster for MySQL
https://severalnines.com/blog/9-tips-going-production-galera-cluster-mysql

HA for MySQL and MariaDB - Comparing Master-Master Replication to Galera Cluster
https://severalnines.com/database-blog/ha-mysql-and-mariadb-comparing-master-master-replication-galera-cluster

Galera Cluster for MySQL - Tutorial
https://severalnines.com/resources/tutorials/galera-cluster-mysql-tutorial










posted @ 2020-05-09 11:08 paulwong 阅读(252) | 评论 (0)编辑 收藏

How to disable IPv6 on CentOS / RHEL 7


https://www.thegeekdiary.com/centos-rhel-7-how-to-disable-ipv6/

https://linuxconfig.org/redhat-8-enable-disable-ipv6


posted @ 2020-05-06 12:42 paulwong 阅读(254) | 评论 (0)编辑 收藏

How To Count Files in Directory on Linux

https://devconnected.com/how-to-count-files-in-directory-on-linux/

posted @ 2020-05-05 17:01 paulwong 阅读(243) | 评论 (0)编辑 收藏

JENKINS TOURIAL

https://huongdanjava.com/jenkins-2

posted @ 2020-04-07 10:29 paulwong 阅读(291) | 评论 (0)编辑 收藏

Deploy artifacts into Maven Repository in Jenkins

https://huongdanjava.com/deploy-artifacts-into-maven-repository-in-jenkins.html

posted @ 2020-04-06 14:13 paulwong 阅读(309) | 评论 (0)编辑 收藏

MAVEN私服-Nexus Repository Manager


Nexus Repository Manager is a tool that allows us to store and use libraries we need in projects such as Maven project…

Nexus Repository ManagerIn this tutorial, I summarize the tutorials of Huong Dan Java on the Nexus Repository Manager for your reference.


Installation

In this tutorial, I will guide you how to install Nexus Repository Manager.


Configuration

In order to create a new Maven Repository in the Nexus Repository Manager, you can refer to this tutorial.

We need to define a Role to define User rights in the Nexus Repository Manager.

To be able to do anything in the Nexus Repository Manager, you need to create and use the User.


Manipulation

Nexus Repository Manager supports us UI to upload any artifact to the Repository.

In addition to the UI, we can also use the RESTful API to upload an artifact.

posted @ 2020-04-06 14:08 paulwong 阅读(285) | 评论 (0)编辑 收藏

List sessions / active connections on MariaDB server

Using a command

Option 1

show status where variable_name = 'threads_connected'; 

Columns

  • Variable_name - Name of the variable shown
  • Value - Number of active connections

Rows

  • One row: Only one row is displayed

Sample results

Option 2

show processlist; 

Columns

  • Id - The connection identifier
  • User - The MariaDB user who issued the statement
  • Host - Host name and client port of the client issuing the statement
  • db - The default database (schema), if one is selected, otherwise NULL
  • Command - The type of command the thread is executing
  • Time - The time in seconds that the thread has been in its current state
  • State - An action, event, or state that indicates what the thread is doing
  • Info - The statement the thread is executing, or NULL if it is not executing any statement
  • Progress - The total progress of the process (0-100%)

Rows

  • One row: represents one active connection
  • Scope of rows: total of active connections

Sample results

Using a query

Option 3

select id, user, host, db, command, time, state, 
info, progress from information_schema.processlist;

Columns

  • Id - The connection identifier
  • User - The MariaDB user who issued the statement
  • Host - Host name and client port of the client issuing the statement
  • db - The default database (schema), if one is selected, otherwise NULL
  • Command - The type of command the thread is executing
  • Time - The time in seconds that the thread has been in its current state
  • State - An action, event, or state that indicates what the thread is doing
  • Info - The statement the thread is executing, or NULL if it is not executing any statement
  • Progress - The total progress of the process (0-100%)
  • memory_used - Amount of memory used by the active connection

Rows

  • One row: represents one active connection
  • Scope of rows: total of active connections

Sample results

Using the GUI

Option 4

Click on the Client Connections option of the Management tab (left navigation pane)

This action will show the Client Connections screen containing the current active connections

posted @ 2020-04-02 15:38 paulwong 阅读(267) | 评论 (0)编辑 收藏

Finding slow queries in MongoDB

Database Profiling

MongoDB Profiler is a db profiling system that can help identify inefficient

or slow queries and operations.

Levels of profiles available are:

Level

Setting

0

Off. & No profiling

1

On & only includes slow operations

2

On & Includes all operations


We can enable it by setting the Profile level value using the following
command in mongo shell :

"db.setProfilingLevel(1)"

By default, mongod records slow queries to its log, as defined by slowOpThresholdMs.

NOTE

Enabling database profiler puts negative impact on MongoDB’s performance.

It’s better to enable it for specific intervals & minimal on Production Servers.

We can enable profiling on a mongod basis but This setting will not propagate
across a replica set and sharded cluster.

We can view the output in the system.profile collection in mongo shell using show profile command, or using following:

db.system.profile.find( { millis : { $gt : 200 } } )

Command returns operations that took longer than 200 ms. Similarly we
can change the values as per our need.

Enabling profile for an entire mongod instance.

For the purpose of development in testing, we can enable database profiling/settings for an 
entire mongod instance. The profiling level will be applied to all databases.

 

NOTE:

We can't enable the profiling settings on a mongos instance. To enable the profiling in

shard clusters, we have to enable/start profiling for each mongod instance in cluster.

 

Query for the recent 10 entries

db.system.profile.find().limit(10).sort( { ts : 1 } ).pretty()

 

Collection with the slowest queries(No. Of queries)

db.system.profile.group({key: {ns: true}, initial: {count: 0}, reduce: function(obj,prev){ prev.count++;}})

 

Collection with the slowest queries(No. Of millis spent)

db.system.profile.group({key: {ns: true}, initial: {millis: 0}, reduce: function(obj, prev){ prev.millis += obj.millis;}})

 

Most recent slow query

db.system.profile.find().sort({$natural: -1}).limit(1)

 

Single slowest query(Right now)

db.system.profile.find().sort({millis: -1}).limit(1)

posted @ 2020-03-27 23:35 paulwong 阅读(296) | 评论 (0)编辑 收藏

基于LINUX的分布式文件系统GlusterFS + NFS-Ganesha

基于LINUX的,也就是用yum install就可以使用。

GlusterFS是分布文件存储系统, 也即一个文件有三个备份,每个备份可以放到不同的节点(IP)上,这样某个节点CRASH后,会从其他节点取文件。

NFS-Ganesha则是用户层面非KERNAL层面的实现了NFS SERVER的功能,但双加了扩展,对外提供基于NFS协议的文件存取服务。

资源:
GlusterFS and NFS-Ganesha integration
https://staged-gluster-docs.readthedocs.io/en/release3.7.0beta1/Features/glusterfs_nfs-ganesha_integration/

Exporting and Unexporting Volumes through nfs-ganesha
https://access.redhat.com/documentation/en-US/Red_Hat_Storage/2.1/html/Administration_Guide/Starting_and_Stopping_nfs-ganesha.html

https://www.snia.org/sites/default/files/Poornima_NFS_GaneshaForClusteredNAS.pdf





posted @ 2020-03-22 11:46 paulwong 阅读(1038) | 评论 (0)编辑 收藏

开机nfs自动挂载

1.echo "mount -t nfs -o nolock ${IP}:${remote_dir} ${local_dir}" >>  /etc/rc.local

2.echo "${IP}:/home/logs /home/logs nfs defaults 0 0" >> /etc/fstab

关于/etc/rc.local


rc.local也是我经常使用的一个脚本。该脚本是在系统初始化级别脚本运行之后再执行的,因此可以安全地在里面添加你想在系统启动之后执行的脚本。常见的情况是你可以再里面添加nfs挂载/mount脚本。此外,你也可以在里面添加一些调试用的脚本命令。例如,我就碰到过这种情况:samba服务总是无法正常运行,而检查发现,samba是在系统启动过程中就该启动执行的,也就是说,samba守护程序配置保证了这种功能本应该正确执行。碰到这种类似情况,一般我也懒得花大量时间去查为什么,我只需要简单的在/etc/rc.local脚本里加上这么一行:

/etc/init.d/samba start

这样就成功的解决了samba服务异常的问题。

posted @ 2020-03-21 19:44 paulwong 阅读(809) | 评论 (0)编辑 收藏

利用 Chef 在 Red Hat Enterprise Linux 上自动化部署 Mariadb Galera Cluster

https://www.ibm.com/developerworks/cn/linux/1611_chensz_mgc/index.html

posted @ 2020-03-21 10:55 paulwong 阅读(287) | 评论 (0)编辑 收藏

在SPRING BOOT中使用多JMS CONNECTION

使用自定义CONNECTION FACTORY,这样会覆盖SPRING 的AUTO CONFIGURATION。

ActiveMQConnectionFactoryFactory.java
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionFactoryCustomizer;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties.Packages;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;


/**
 * Factory to create a {
@link ActiveMQConnectionFactory} instance from properties defined
 * in {
@link SecondaryActiveMQProperties}.
 *
 * 
@author Phillip Webb
 * 
@author Venil Noronha
 
*/
class ActiveMQConnectionFactoryFactory {

    private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";

    private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";

    private final ActiveMQProperties properties;

    private final List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers;

    ActiveMQConnectionFactoryFactory(ActiveMQProperties properties,
            List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
        Assert.notNull(properties, "Properties must not be null");
        this.properties = properties;
        this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList();
    }

    public <T extends ActiveMQConnectionFactory> T createConnectionFactory(Class<T> factoryClass) {
        try {
            return doCreateConnectionFactory(factoryClass);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable to create " + "ActiveMQConnectionFactory", ex);
        }
    }

    private <T extends ActiveMQConnectionFactory> T doCreateConnectionFactory(Class<T> factoryClass) throws Exception {
        T factory = createConnectionFactoryInstance(factoryClass);
        if (this.properties.getCloseTimeout() != null) {
            factory.setCloseTimeout((intthis.properties.getCloseTimeout().toMillis());
        }
        factory.setNonBlockingRedelivery(this.properties.isNonBlockingRedelivery());
        if (this.properties.getSendTimeout() != null) {
            factory.setSendTimeout((intthis.properties.getSendTimeout().toMillis());
        }
        Packages packages = this.properties.getPackages();
        if (packages.getTrustAll() != null) {
            factory.setTrustAllPackages(packages.getTrustAll());
        }
        if (!packages.getTrusted().isEmpty()) {
            factory.setTrustedPackages(packages.getTrusted());
        }
        customize(factory);
        return factory;
    }

    private <T extends ActiveMQConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass)
            throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String brokerUrl = determineBrokerUrl();
        String user = this.properties.getUser();
        String password = this.properties.getPassword();
        if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) {
            return factoryClass.getConstructor(String.class, String.class, String.class).newInstance(user, password,
                    brokerUrl);
        }
        return factoryClass.getConstructor(String.class).newInstance(brokerUrl);
    }

    private void customize(ActiveMQConnectionFactory connectionFactory) {
        for (ActiveMQConnectionFactoryCustomizer factoryCustomizer : this.factoryCustomizers) {
            factoryCustomizer.customize(connectionFactory);
        }
    }

    String determineBrokerUrl() {
        if (this.properties.getBrokerUrl() != null) {
            return this.properties.getBrokerUrl();
        }
        if (this.properties.isInMemory()) {
            return DEFAULT_EMBEDDED_BROKER_URL;
        }
        return DEFAULT_NETWORK_BROKER_URL;
    }
}

TwinJmsConnectionFactoryConfiguration.java
import java.util.stream.Collectors;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jms.JmsPoolConnectionFactoryFactory;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionFactoryCustomizer;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;


@Configuration
@Profile({"local"})
public class TwinJmsConnectionFactoryConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.activemq.primary")
    public ActiveMQProperties primaryActiveMQProperties() {
        return new ActiveMQProperties();
    }
    
    @Bean(destroyMethod = "stop")
    @Primary
    @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true")
    public JmsPoolConnectionFactory connectionFactory(ActiveMQProperties primaryActiveMQProperties,
            ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(primaryActiveMQProperties,
                factoryCustomizers.orderedStream().collect(Collectors.toList()))
                .createConnectionFactory(ActiveMQConnectionFactory.class);
        return new JmsPoolConnectionFactoryFactory(primaryActiveMQProperties.getPool())
                .createPooledConnectionFactory(connectionFactory);
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    @Bean
    @ConfigurationProperties(prefix = "spring.activemq.sescond")
    public ActiveMQProperties sescondActiveMQProperties() {
        return new ActiveMQProperties();
    }
    
    @Bean(destroyMethod = "stop")
    @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true")
    public JmsPoolConnectionFactory sescondPooledJmsConnectionFactory(ActiveMQProperties sescondActiveMQProperties,
            ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(sescondActiveMQProperties,
                factoryCustomizers.orderedStream().collect(Collectors.toList()))
                .createConnectionFactory(ActiveMQConnectionFactory.class);
        return new JmsPoolConnectionFactoryFactory(sescondActiveMQProperties.getPool())
                .createPooledConnectionFactory(connectionFactory);
    }
    
}


posted @ 2020-03-19 09:45 paulwong 阅读(577) | 评论 (0)编辑 收藏

Which is better: PooledConnectionFactory or CachingConnectionFactory?

From here:

The difference between the PooledConnectionFactory and the CachingConnectionFactory is a difference in implementation. Below are some of the characteristics that differ between them:

  • Although both the PooledConnectionFactory and the CachingConnectionFactory state that they each pool connections, sessions and producers, the PooledConnectionFactory does not actually create a cache of multiple producers. It simply uses a singleton pattern to hand out a single cached producer when one is requested. Whereas the CachingConnectionFactory actually creates a cache containing multiple producers and hands out one producer from the cache when one is requested.

  • The PooledConnectionFactory is built on top of the Apache Commons Pool project for pooling JMS sessions. This allows some additional control over the pool because there are features in Commons Pool that are not being used by the PooledConnectionFactory. These additional features include growing the pool size instead of blocking, throwing an exception when the pool is exhausted, etc. You can utilize these features by creating your own Commons Pool GenericObjectPool using your own customized settings and then handing that object to the PooledConnectionFactory via the setPoolFactory method. See the following for additional info: http://commons.apache.org/pool/api-1.4/org/apache/commons/pool/impl/GenericObjectPoolFactory.html

  • The CachingConnectionFactory has the ability to also cache consumers. Just need to take care when using this feature so that you know the consumers are cached according to the rules noted in the blog post.

  • But most importantly, the CachingConnectionFactory will work with any JMS compliant MOM. It only requires a JMS connection factory. This is important if you are using more than one MOM vendor which is very common in enterprise organizations (this is mainly due to legacy and existing projects). The important point is that the CachingConnectionFactory works very well with many different MOM implementations, not only ActiveMQ.

From here:

  • If you have clustered ActiveMQs, and use failover transport it has been reported that CachingConnectionFactory is not a right choice.

  • The problem I’m having is that if one box goes down, we should start sending messages on the other, but it seems to still be using the old connection (every send times out). If I restart the program, it’ll connect again and everything works. Source: Autoreconnect problem with ActiveMQ and CachingConnectionFactory

  • The problem is that cached connections to the failed ActiveMQ was still in use and that created the problem for the user. Now, the choice for this scenario is PooledConnectionFactory.

  • If you’re using ActiveMQ today, and chances are that you may switch to some other broker (JBoss MQ, WebSphere MQ) in future, do not use PooledConnectionFactory, as it tightly couples your code to ActiveMQ.

posted @ 2020-03-19 09:37 paulwong 阅读(405) | 评论 (0)编辑 收藏

Spring Boot Data Mongodb Starter自动配置那些坑

正好做Mongodb主从复制尝试使用Spring Boot Data Mongodb Starter插件链接访问Mongodb数据库集群。

遇到的坑:

  • spring.data.mongodb.host和spring.data.mongodb.port形式不适合集群配置,会报host无法识别异常
  • spring.data.mongodb.uri中经常抛出authentication failed异常


解决办法:

  1.  对于第一个坑,请使用spring.data.mongodb.uri。如果使用了uri,则其余的host/username/password/db/auth-db这些全部无效。
  2.  对于第二个坑,请在spring.data.mongodb.uri中指定replicaSet和authsource,另外记得把所有集群节点服务器地址都列全。
    如果auth-db和db是同一个,则无需加authsource,如果不同,则加authsource=admin


我没有把authsource指定,所以一直报authentication failed异常。然后只好一点点去发掘问题点,最后查到在com.mongodb.ConnectionString类中的createCredentials中

private MongoCredential createCredentials(final Map<String, List<String>> optionsMap, final String userName,
                                              final char[] password) {
        AuthenticationMechanism mechanism = null;
        String authSource = (database == null) ? "admin" : database;
        String gssapiServiceName = null;
        String authMechanismProperties = null;

        for (final String key : AUTH_KEYS) {
            String value = getLastValue(optionsMap, key);

            if (value == null) {
                continue;
            }

            if (key.equals("authmechanism")) {
                mechanism = AuthenticationMechanism.fromMechanismName(value);
            } else if (key.equals("authsource")) {
                authSource = value;
            } else if (key.equals("gssapiservicename")) {
                gssapiServiceName = value;
            } else if (key.equals("authmechanismproperties")) {
                authMechanismProperties = value;
            }
        }


        MongoCredential credential = null;
        if (mechanism != null) {
            switch (mechanism) {
                case GSSAPI:
                    credential = MongoCredential.createGSSAPICredential(userName);
                    if (gssapiServiceName != null) {
                        credential = credential.withMechanismProperty("SERVICE_NAME", gssapiServiceName);
                    }
                    break;
                case PLAIN:
                    credential = MongoCredential.createPlainCredential(userName, authSource, password);
                    break;
                case MONGODB_CR:
                    credential = MongoCredential.createMongoCRCredential(userName, authSource, password);
                    break;
                case MONGODB_X509:
                    credential = MongoCredential.createMongoX509Credential(userName);
                    break;
                case SCRAM_SHA_1:
                    credential = MongoCredential.createScramSha1Credential(userName, authSource, password);
                    break;
                default:
                    throw new UnsupportedOperationException(format("The connection string contains an invalid authentication mechanism'. "
                                                                           + "'%s' is not a supported authentication mechanism",
                            mechanism));
            }
        } else if (userName != null) {
            credential = MongoCredential.createCredential(userName, authSource, password);
        }

        if (credential != null && authMechanismProperties != null) {
            for (String part : authMechanismProperties.split(",")) {
                String[] mechanismPropertyKeyValue = part.split(":");
                if (mechanismPropertyKeyValue.length != 2) {
                    throw new IllegalArgumentException(format("The connection string contains invalid authentication properties. "
                            + "'%s' is not a key value pair", part));
                }
                String key = mechanismPropertyKeyValue[0].trim().toLowerCase();
                String value = mechanismPropertyKeyValue[1].trim();
                if (key.equals("canonicalize_host_name")) {
                    credential = credential.withMechanismProperty(key, Boolean.valueOf(value));
                } else {
                    credential = credential.withMechanismProperty(key, value);
                }
            }
        }
        return credential;
    }


authSource默认会指向我们目标数据的数据库。然而在身份验证机制中我们通常需要指向admin。(非常想报粗口,代码作者在这里脑袋被men挤了么)。所以需要强制指定authSource中指定。具体指定方式如下:

 

 

 

 

mongodb://{用户名}:{密码}@{host1}:27017,{host2}:27017,{host3}:27017/{目标数据库}?replicaSet={复制集名称}&write=1&readPreference=primary&authsource={授权数据库}

posted @ 2020-03-17 09:39 paulwong 阅读(1923) | 评论 (0)编辑 收藏

SPRING BATCH中STEP如果没加TRANSACTION MANAGER

这时发现如果处理总数足够大时,被处理的ITEMS总数会少于应该处理的总数。

+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+-------------------------
 | READ_COUNT | FILTER_COUNT | WRITE_COUNT | READ_SKIP_COUNT | WRITE_SKIP_COUNT | PROCESS_SKIP_COUNT | ROLLBACK_COUNT | EXIT_CODE | EXIT_MESSAGE
-+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+-------------------------
 |          1 |            0 |           1 |               0 |                0 |                  0 |              0 | COMPLETED |
 |      30006 |            0 |       29947 |               0 |               59 |                  0 |             61 | COMPLETED | Waited for 101 results.

如上面的总数是30006,但COMMIT的总数是29947,有59个被跳过了。

因此需在STEP上加TRANSACTION MANAGER.

https://stackoverflow.com/questions/42803941/spring-batch-incorrect-write-skip-count-issue

posted @ 2020-03-16 09:43 paulwong 阅读(576) | 评论 (0)编辑 收藏

mySQL Error 1040: Too Many Connection


To see how many connections are configured for your DB to use:

select @@max_connections;

To change it:

set global max_connections = 200;

To see how many are connected at the current time:

show processlist;
vi /etc/my.cnf

[mysqld]
max_connections = 500

posted @ 2020-03-10 20:20 paulwong 阅读(286) | 评论 (0)编辑 收藏

5 ways to customize Spring MVC JSON/XML output

https://mostafa-asg.github.io/post/customize-json-xml-spring-mvc-output/

posted @ 2020-03-08 15:55 paulwong 阅读(261) | 评论 (0)编辑 收藏

MongDB连接池参数serverSelectionTimeout、connectTimeout、maxWaitTime和socketTimeout介绍

MongDB Client请求查询数据,需要包括五个阶段:
MongoDB Client需要找到可用的MongoDB Server
MongoDB Client需要和MongoDB Server建立(new)Connection
应用程序处理线程从Connection Pool中获取Connection
数据传输(获取连接后,进行Socket通信,获取数据)
断开Collection
那么,MongoDB Client驱动设置中网络相关等待超时参数serverSelectionTimeout、connectTimeout、maxWaitTime和socketTimeout分别对应上面哪个环节呢?
参数serverSelectionTimeout:对应第1个环节,即MongoDB Client需要找到可用的MongoDB Server所需要的等待时间,                                             MongDB部署的生产一般由多个服务器组成,要么作为一个复制集或者作为一个分片集群,参数                                                     serverSelectionTimeout的值即为多长时间内找不到合适服务器时候就决定放弃的时间间隔;
参数connectTimeout:对应第2个环节,每次创建Connection,对应的网络等待。单位毫秒数, 0表示没有限制;
参数maxWaitTime:对应第3个环节,应用程序处理线程从连接池中获取Collection,对应的网络等待时间。单位毫秒数,0表示                                   不等待,负数表示等待时间不确定;
参数socketTimeout:对应第4个环节,获取Connection后,就有了Socket通信,获取数据,即在MonogoDB Client                                                      和MonogoDB Server的Socket通信过程中的网络等待时间。单位毫秒数,默认配置为0,也就是没有限制,                                  没有超 时限制,系统出了问题也不容易发现,应该根据实际情况,给出合理的超时时间。
 
其他相关参数如下:
connectionsPerHost:对mongo实例来说,每个host允许链接的最大链接数,这些链接空闲时会放入池中,如果链接被耗尽,任何请求链接的操作会被阻塞等待链接可用,推荐配置10
minPoolsSize:当Connection空闲时,Connection Pool中最少Connection保有量;
threadsAllowedToBlockForConnectionMultiplier:每个Connection的可以阻塞等待的线程队列数,它以上面connectionsPerHost值相乘的结果就是阻塞等待的线程队列最大值。如果连接线程排满了队列就会抛出“Out of semaphores to get db”错误。
socketKeepAlive:该标志用于控制socket保持活动的功能,通过防火墙保持连接活着
socketKeepAlive=false
autoConnectRetry:这个控制是否在一个Connection时,系统会自动重试
#true:假如Connection不能建立时,驱动将重试相同的server,有最大的重试次数,默认为15次,这样可以避免一些server因为一些阻塞操作零时down而驱动抛出异常,这个对平滑过度到一个新的master,也是很有用的,注意:当集群为复制集时,驱动将在这段时间里,尝试链接到旧的master上,而不会马上链接到新master上
#false 当在进行socket读写时,不会阻止异常抛出,驱动已经有自动重建破坏链接和重试读操作. 推荐配置false
autoConnectRetry=false
#重新打开链接到相同server的最大毫秒数,推荐配置为0,如果 autoConnectRetry=true,表示时间为15s
#com.jd.mongodbclient2.mongo.JDClientMongo.maxAutoConnectRetryTime=false
#表示当没有手动关闭游标时,是否有一个自动释放游标对象的方法,如果你总是很小心的关闭游标,则可以将其设为false 推荐配置true

https://docs.mongodb.com/manual/reference/connection-string/#connections-connection-options
————————————————
版权声明:本文为CSDN博主「pursuer211」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/pursuer211/article/details/82994027

posted @ 2020-03-07 20:58 paulwong 阅读(1214) | 评论 (0)编辑 收藏

使用 Spring Batch 构建企业级批处理应用-2

     摘要: 前言在本系列文章的第 1 部分,我们搭建了一个用户缴费通知的批处理任务。尽管这个简单的应用展现了 Spring Batch 的基本功能,但是它与真实的应用相去甚远。在实际应用中,我们的 Job 可能必须要包含多个 Step,为了提高性能,我们可能需要考虑 Job 的并发问题。Spring Batch 在这些方面又提供了哪些好的特性呢?让我们继续。Step Flow通过前文我们已经知道,Step 是...  阅读全文

posted @ 2020-03-06 13:47 paulwong 阅读(273) | 评论 (0)编辑 收藏

使用 Spring Batch 构建企业级批处理应用-1

     摘要: 引言总述本系列文章旨在通过示例搭建以及特性介绍,详细讲述如何利用 Spring Batch 开发企业批处理应用。本系列文章共分为三部分,第一部分初步介绍了批处理以及 Spring Batch 的相关概念,同时搭建了一个简单的基于 Spring Batch 的批处理应用。第二部分介绍了 Step Flow 以及并发支持。第三部分则主要介绍了 Spring Batch 对任务监控的支持。下面让我们进入...  阅读全文

posted @ 2020-03-06 13:45 paulwong 阅读(394) | 评论 (0)编辑 收藏

MobaXterm注册版

LINUX 的shell神器
https://www.upload.ee/files/11132815/MobaXterm_Pro_Portable_SetuP_v20_fu11.rar.html

posted @ 2020-02-27 17:44 paulwong 阅读(503) | 评论 (0)编辑 收藏

各种获取JVM DUMP的方法

JVM 的线程堆栈 dump 也称 core dump,内容为文本,主要包含当时 JVM 的线程堆栈,堆 dump 也称 heap dump,内容为二进制格式,主要包含当时 JVM 堆内存中的内容。由于各个操作系统、各个 JVM 实现不同,即使同一 JVM 实现,各个版本也有差异,本文描述的方法都基于 64 位 Linux 操作系统环境,Java 8 Oracle HotSpot JVM 实现。

堆栈和堆的内容在定位问题的时候,都是非常重要的信息。线程堆栈 dump 可以了解当时 JVM 中所有线程的运行情况,比如线程的状态和当前正在运行的代码行。堆 dump 可以了解当时堆的使用情况,各个类实例的数量及各个实例所占用的空间大小。

线程堆栈

使用 jstack

jstack 是 JDK 自带的工具,用于 dump 指定进程 ID(PID)的 JVM 的线程堆栈信息。

# 打印堆栈信息到标准输出 jstack PID  
# 打印堆栈信息到标准输出,会打印关于锁的信息 jstack -l PID  
强制打印堆栈信息到标准输出,如果使用 jstack PID 没有响应的情况下(此时 JVM 进程可能挂起),
加 -F 参数 jstack -F PID 

使用 jcmd

jcmd 是 JDK 自带的工具,用于向 JVM 进程发送命令,根据命令的不同,可以代替或部分代替 jstack、jmap 等。可以发送命令 Thread.print 来打印出 JVM 的线程堆栈信息。

# 下面的命令同等于 jstack PID 
jcmd PID Thread.print  

# 同等于 jstack -l PID 
jcmd PID Thread.print -l 

使用 kill -3

kill 可以向特定的进程发送信号(SIGNAL),缺省情况是发送终止(TERM) 的信号 ,即 kill PID 与 kill -15 PID 或 kill -TERM PID 是等价的。JVM 进程会监听 QUIT 信号(其值为 3),当收到这个信号时,会打印出当时的线程堆栈和堆内存使用概要,相比 jstack,此时多了堆内存的使用概要情况。但 jstack 可以指定 -l 参数,打印锁的信息。

kill -3 PID 
# 或 kill -QUIT PID 

-XX:+HeapDumpOnOutOfMemoryError

添加 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 后,当发生 OOM(OutOfMemory)时,自动堆 dump。缺省情况下,JVM 会创建一个名称为 java_pidPID.hprof 的堆 dump 文件在 JVM 的工作目录下。但可以使用参数 -XX:HeapDumpPath=PATH 来指定 dump 文件的保存位置。

# JVM 发生 OOM 时,会自动在 /var/log/abc 目录下产生堆 dump 文件 java_pidPID.hprof 
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/abc/ 

jmap

jmap 也是 JDK 自带的工具,主要用于获取堆相关的信息。

堆 dump

# 将 JVM 的堆 dump 到指定文件,如果堆中对象较多,需要的时间会较长,子参数 format 只支持 b,
即二进制格式
jmap -dump:format=b,file=FILE_WITH_PATH

# 如果 JVM 进程未响应命令,可以加上参数 -F 尝试
jmap -F -dump:format=b,file=FILE_WITH_PATH

# 可以只 dump 堆中的存活对象,加上 live 子参数,但使用 -F 时不支持 live
jmap -dump:live,format=b,file=FILE_WITH_PATH

获取堆概要信息

# -heap 参数用于查看指定 JVM 进程的堆的信息,包括堆的各个参数的值,堆中新生代、年老代的内存大小、使用率等 
jmap -heap PID  

# 同样,如果 JVM 进程未响应命令,可以加上参数 -F 尝试 
jmap -F -heap PID 

一个实例输出如下:

Attaching to process ID 68322, please wait
Debugger attached successfully.
Server compiler detected.
JVM version is 25.112-b16

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 268435456 (256.0MB)
   NewSize                  = 8388608 (8.0MB)
   MaxNewSize               = 89128960 (85.0MB)
   OldSize                  = 16777216 (16.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 41943040 (40.0MB)
   used     = 1701504 (1.6226806640625MB)
   free     = 40241536 (38.3773193359375MB)
   4.05670166015625% used
From Space:
   capacity = 4194304 (4.0MB)
   used     = 0 (0.0MB)
   free     = 4194304 (4.0MB)
   0.0% used
To Space:
   capacity = 5242880 (5.0MB)
   used     = 0 (0.0MB)
   free     = 5242880 (5.0MB)
   0.0% used
PS Old Generation
   capacity = 30408704 (29.0MB)
   used     = 12129856 (11.56793212890625MB)
   free     = 18278848 (17.43206787109375MB)
   39.889421134159484% used

16658 interned Strings occupying 1428472 bytes.

获取堆中的类实例统计
# 打印 JVM 堆中的类实例统计信息,以占用内存的大小排序,同样,如果 JVM 未响应命令,也可以使用 -F 参数 
jmap -histo PID  

# 也可以只统计堆中的存活对象,加上 live 子参数,但使用 -F 时不支持 live 
jmap -histo:live PID 

使用 jcmd

# 等同 jmap -dump:live,format=b,file=FILE_WITH_PATH
jcmd PID GC.heap_dump FILE_WITH_PATH

# 等同 jmap -dump:format=b,file=FILE_WITH_PATH
jcmd PID GC.heap_dump -all FILE_WITH_PATH

# 等同 jmap -histo:live PID
jcmd PID GC.class_histogram

# 等同 jmap -histo PID
jcmd PID GC.class_histogram -all

posted @ 2020-02-24 22:03 paulwong 阅读(1207) | 评论 (0)编辑 收藏

Mongodb shell中select in 的实现


var bookIds = db.likes.find({userId:100}).map(function(like) { 
  return like.bookId; 
});
var books = db.books.find({_id:{$in:bookIds}});

db.REPORT_ITEM.count({REQUEST_ID : db.BATCH_CONTROL.find({ FILE_NAME : "20200218_100000.file" }).map(function(like) { 
  return like._id; 
})[0].str, JOB_TYPE_ENUM:"CHECK"})

posted @ 2020-02-21 23:10 paulwong 阅读(516) | 评论 (0)编辑 收藏

采用 jstatd 监控服务器


https://www.jianshu.com/p/97f09e1648a6

posted @ 2020-02-21 17:08 paulwong 阅读(322) | 评论 (0)编辑 收藏

mongodb错误记录

https://blog.csdn.net/wangxiaotongfan/article/details/81560463


posted @ 2020-02-21 08:50 paulwong 阅读(270) | 评论 (0)编辑 收藏

JAVA进程无故退出而没有留下LOG?

LINUX通常有个OOM KILLER进程,对于他认为吃内存的进程,会根据一定的算分,执行kill -9杀掉,查看日志如下:

#less /tmp/messages
Feb 20 03:55:09 ip kernel: Out of memory: Kill process 5974 (java) score 494 or sacrifice child
Feb 20 03:55:09 ip kernel: Killed process 5974 (java), UID 1001, total-vm:23674020kB, anon-rss:17503912kB, file-rss:0kB, shmem-rss:0kB

那如何屏蔽呢?

#/etc/cron.d/oom_disable
*/1**** root pgrep -f "java" | while read PID;do echo -17 > /proc/$PID/oom_adj;done

参考文档:
Linux内核OOM机制的详细分析
http://blog.chinaunix.net/uid-29242873-id-3942763.html


posted @ 2020-02-20 15:57 paulwong 阅读(868) | 评论 (0)编辑 收藏

!!21 MOST IMPORTANT JAVA 8 VM OPTIONS FOR SERVERS


  7 KOMMENTARE

In this video I explain some 21 JVM parameters which are suited for most server applications. If you have any questions, you can read those links below for more information or just ask in the comments section.

Java server flags video

I run several Java enterprise server applications. I often wondered – what are the best „default“ JVM settings for a server application to start with in production? I read a lot on the web and tried several things myself and wanted to share what I found out, so far. Links containing more information about JVM optimization can be found here:

http://blog.sokolenko.me/2014/11/javavm-options-production.html

http://www.petefreitag.com/articles/gctuning/

http://stas-blogspot.blogspot.de/2011/07/most-complete-list-of-xx-options-for.html

 

So let’s start:

-server

Use „-server“: All 64-bit JVMs use the server VM as default anyway. This setting generally optimizes the JVM for long running server applications instead of startup time. The JVM will collect more data about the Java byte code during program execution and generate the most efficient machine code via JIT.

-Xms=<heap size>[g|m|k] -Xmx=<heap size>[g|m|k]

The „-Xmx/-Xms“ settings specify the maximum and minimum values for the JVM heap memory. For servers, both params should have the same value to avoid heap resizing during runtime. I’ve applications running with 16GB heap sizes without an issue.

Depending on your application, you will have to try out how much memory will be best suited for your use case.

-XX:MaxMetaspaceSize=<metaspace size>[g|m|k]

Java 8 has no „Permanent Generation“ (PermGen) anymore but requires additional „Metaspace“ memory instead. This memory is used, in addition to the heap memory we specified before, for storing class meta data information.

The default size will be unlimited – I tend to limit MaxMetaspaceSize with a somewhat high value. Just in case something goes wrong with the application, the JVM will not hog all the memory of the server.

I suggest: Let your application run for a couple of days to get a feeling for how much Metaspace Size it uses normally. Upon next restart of the application set the limit to e.g. double the value.

-XX:+CMSClassUnloadingEnabled

Additionally, you might want to allow the JVM to unload classes which are held in memory but no code is pointing to them any more. If your application generates lots of dynamic classes, this is what you want.

-XX:+UseConcMarkSweepGC

This option makes the JVM use the ConcurrentMarkSweepGC – It can do much work in parallel to program execution but in some circumstances a „full GC“ with a „STW pause“ might still occur. I’ve read many articles and came to the conclusion that this GC is still the best one for server workloads.

-XX:+CMSParallelRemarkEnabled

The option CMSParallelRemarkEnabled means the remarking is done in parallel to program execution – which is what you want if your server has many cores (and most servers do).

 -XX:+UseCMSInitiatingOccupancyOnly  -XX:CMSInitiatingOccupancyFraction=<percent>

Normally the GC will use heuristics to know when it’s time to clear memory. GC might kick in too late with default settings (causing full-Gcs).
Some sources say it might be a good idea to disable heuristics altogether and just use generation occupancy to start a CMS collection cycle. Setting values around 70% worked fine for all of my applications and use cases.

-XX:+ScavengeBeforeFullGC

The first option tells the GC to first free memory by clearing out the „young generation“ or fairly new objects before doing a full GC.

-XX:+CMSScavengeBeforeRemark

CMSScavengeBeforeRemark does attempt a minor collection before the CMS remark phase – thus keeping the remark pause afterwards short.

-XX:+CMSClassUnloadingEnabled

The option „-XX:+CMSClassUnloadingEnabled“ here tells the JVM to unload classes, which are not needed any more by the running application. If you deploy war files to an application server like wildfly, tomcat or glassfish without restarting the server after the deployment, this flag is for you.

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

The option „-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses“ is especially important if your application uses RMI (remote method invocation). The usage of RMI will cause the JVM to do a FULL-GC EVERY HOUR! This might be a very bad idea for large heap sizes because the FULL-GC pause might take up to several seconds. It would be better to do a concurrent GC and try to unload unused classes to free up more memory – which is exactly what the second option does.

-XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Xloggc:"<path to log>"

These options shown here will write out all GC related information to a specified log file. You can see how well your GC configuration works by looking into it.

I personally prefer to use the „Visual GC“ plug in for the „Visual VM“ tool to monitor the general JVM and GC behavior.

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<path to dump>`date`.hprof

When your JVM runs out of memory, you will want to know why. Since the OOM error might be hard to reproduce and you want to get your production server up and running again – you should specify a path for a heap dump. When things have settled down, you can analyze the dump afterwards.

-Djava.rmi.server.hostname=<external IP> -Dcom.sun.management.jmxremote.port=<port>

These options will help you to specify an IP and port for JMX – you will need those ports open to connect remotely to a JVM running on a server for tools like VisualVM. You can gain deep insights over cpu and memory usage, gc behaviour, class loading, thread count and usage of your application this way.

Visual VM
Lastly, I would like to recommend to you the VisualVM tool which is bundled with the Java 8 JDK. You can use it to gain more insights about your specific application behaviour on the JVM – like cpu and memory usage, thread utilisation and much more.

Visual GCVisualVM can be extended with a plug in called „Visual GC“. It will briefly show you VERY detailed information about the usage of the young and old generation object spaces. You can easily spot problems with garbage collection simply by analyzing these graphs during application runtime.

Thank you very much for watching! If you liked the video you might consider giving it a „thumbs up“. If you have any questions – just put them in the comments section. I will reply as quickly as possible.

-------------------------------------------------------

-XX:+UseCompressedOops [If Max Heap allocation is less than 32GB]
This can save a significant amount of memory and this option should already be enabled by default on recent java 8 versions. This option allowes object references to be stored as 32-bit values instead of 64-bit on 64-bit JVMs. This leads to before mentioned memory savings.

-XX:+AggressiveOpts
This option will enable performance options which are hoped to become enabled by default in upcoming released of the JVM. This option sets some performance settings but is marked as experimental! So you should only enable it, when you have to possibility to test your application thoroughly before enabling this flag on an production server.

-XX:+UseStringDeduplication
Since Java 8 update 20 you can use this option to reduce the memory usage of your application. The JVM will spot identical strings in memory, remove the duplicated and point all references to the remaining, single instance of the string.

-XX:+UseG1GC
Will tell the JVM to use the most recent G1 garbage collector. You are trading better application response times (due to shorter gc times with G1) against lower throughput (compared against good old ConcMarkSweepGC / CMS). If your application can deliver more value through short gc times, then G1 is definately better suited. Otherwise on Java 8, I’d recommend sticking with CMS.

Concerning your Tomcat 8 question, I’d suggest you have a look into it with the „VisualVM“ tool. Look at memory usage, GC times (visual GC plugin), pull and analyse stack traces or thread dumps to find the weak spot. You might also consider attaching a debugger to tomcat to find the bug.



https://www.maknesium.de/21-most-important-java-8-vm-options-for-servers

posted @ 2020-02-16 22:30 paulwong 阅读(328) | 评论 (0)编辑 收藏

windows下jenkins提示文件名太长

由于jenkins是调用windows的git取代码,因此是git的问题,进行如下配置即可:

git config --global core.longpaths true

posted @ 2020-02-14 14:37 paulwong 阅读(501) | 评论 (0)编辑 收藏

SPRING BOOT 环境下减少中间件依赖的UNIT测试

SPRING BOOT 环境下,测试有时会依赖于外部的中间件,如Mysql,Activemq,Mongodb等,那如何能减少这种依赖呢?
SPRING BOOT其实已经实现了自动化配置。

Mongodb

SPRING BOOT的自动化配置文件:org.springframework.boot.autoconfigure.mongo.embeddedEmbedded.MongoAutoConfiguration.java

在pom.xml中新增一test profile,并添加相应jar包,这样可防止对其他profile的影响,如果是在Eclipse跑测试,需在Project的属性中指定Active Profile为test,以覆盖pom.xml的定义。
这种方式即使是使用SPRING DATA MONGODB的REPOSITORY也是适用的。

    <profile>
        <id>test</id>
        <dependencies>
            <dependency>
                <groupId>de.flapdoodle.embed</groupId>
                <artifactId>de.flapdoodle.embed.mongo</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
    </profile>
在application-test.yaml中添加端口,其他如IP那些信息都不需要
spring:
   data:
      mongodb:
         port: 27017

unit test config

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.FileCopyUtils;

@Configuration
@Profile({"test", "integrationTest"})
@EnableMongoRepositories(
        basePackages = {"paul.com.repository"
        }
)
public class EmbeddedDataSourceConfiguration {
    
    @Value("classpath:/initdata/USER.json")
    private Resource userResource;

    @Value("classpath:/initdata/MEMBERS.json")
    private Resource membersResource;
    
    @Autowired
    private ResourceLoader resourceLoader;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private MongoTemplate  mongoTemplate;
    
    @PostConstruct
    protected void initialize() throws FileNotFoundException, IOException {
        this.initializeHsqldb();
        this.initializeMongodb();
    }
    
    private void initializeHsqldb() {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(resourceLoader.getResource("classpath:/org/springframework/batch/core/schema-hsqldb.sql"));
        populator.setContinueOnError(true);
        DatabasePopulatorUtils.execute(populator , dataSource);
    }
    
    private void initializeMongodb() throws FileNotFoundException, IOException {
        this.saveResource(userResource, "USER");
        
        this.saveDocumentList(membersResource, "MEMBER");
    }
    
    private void saveResource(Resource resource, String collectionName) {
        String resourceJson = this.asString(resource);
        Document resourceDocument = Document.parse(resourceJson);
        this.mongoTemplate.save(resourceDocument, collectionName);
    }
    
    private void saveDocumentList(Resource resource, String collectionName) {
        String resourceJson = this.asString(resource);
        Document resourceDocument = Document.parse("{ \"list\":" + resourceJson + "}");
        List<Document> documentList = resourceDocument.get("list", List.class);
        documentList.forEach(document -> this.mongoTemplate.save(document, collectionName));
    }
    
    private String asString(Resource resource) {
        try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
            return FileCopyUtils.copyToString(reader);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
    
//    @Bean(destroyMethod="close")
//    public DataSource dataSource() {
//        BasicDataSource dataSource = new BasicDataSource();
//        dataSource.setDriverClassName(environment.getProperty("batch.jdbc.driver"));
//        dataSource.setUrl(environment.getProperty("batch.jdbc.url"));
//        dataSource.setUsername(environment.getProperty("batch.jdbc.user"));
//        dataSource.setPassword(environment.getProperty("batch.jdbc.password"));
//        return dataSource;
//    }
}

ActiveMQ

只需更改application-test.yml中的brokerUrl为vm://embedded即可
spring:
   activemq:
      broker-url: vm://embedded?broker.persistent=false,useShutdownHook=false
      in-memory: true
      non-blocking-redelivery: true
      #packages:
        #trust-all: false
        #trusted: com.memorynotfound
      pool:
        block-if-full: true
        block-if-full-timeout: -1
        create-connection-on-startup: true
        enabled: false
        expiry-timeout: 0
        idle-timeout: 30000
        max-connections: 1
        maximum-active-session-per-connection: 500
        reconnect-on-exception: true
        time-between-expiration-check: -1
        use-anonymous-producers: true
        user: admin
        #password: ENC(hWJHuMyhydTqyF32neasTw==)
        password: admin

关系型数据库

将在application-test.yml中的数据库信息删除,同时在pom.xml中添加jar包依赖,这边是采用HSQL数据库
    <profile>
        <id>test</id>
        <dependencies>
            <dependency>
                <groupId>org.hsqldb</groupId>
                <artifactId>hsqldb</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
    </profile>

非SPRING BOOT/SPRING的纯JDK环境可参考
https://github.com/yandex-qatools/embedded-services

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo

https://github.com/jonyfs/spring-boot-data-embedded-mongodb/blob/master/src/main/java/br/com/jonyfs/spring/boot/data/embedded/mongodb/config/MongoConfig.java

ActiveMQ:
https://memorynotfound.com/spring-boot-embedded-activemq-configuration-example/

posted @ 2020-02-07 10:28 paulwong 阅读(648) | 评论 (0)编辑 收藏

配置SPRING BATCH中的JUNIT TEST

关键是JobLauncherTestUtils的配置:

    @Configuration
    public class BatchTestConfiguration {
        
        
        @Bean
        public JobLauncherTestUtils stoppedReportJobLauncherTestUtils(
                JobLauncher stoppedReportJobLauncher
        ) {
            return new JobLauncherTestUtils() {
                
                @Autowired
                public void setJobLauncher(JobLauncher stoppedReportJobLauncher) {
                    super.setJobLauncher(stoppedReportJobLauncher);
                }

                @Autowired
                public void setJob(Job stoppedReportJob) {
                    super.setJob(stoppedReportJob);
                }
                
            };
        }
    }


posted @ 2020-02-03 16:47 paulwong 阅读(762) | 评论 (0)编辑 收藏

Transform RemoteChunk to remote with json format in Spring Batch

Spring Batch Remote Chunk模式下,远程执行JOB时,传输的对象是ChunkRequest/ChunkResponse,无法转成JSON格式传输。

注意此处使用的是SPRING JACKSON,而不是JACKSON。一般是在SPRING INTEGRATIONA框架下转的。

需要自定义Transformer:

JsonToChunkRequestTransformer.java
package com.frandorado.springbatchawsintegrationslave.transformer;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.stream.IntStream;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.integration.chunk.ChunkRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.aws.support.AwsHeaders;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class JsonToChunkRequestTransformer extends JsonToObjectTransformer {
    
    private static final String MESSAGE_GROUP_ID_HEADER = "message-group-id";
    
    @Autowired
    AmazonSQSAsync amazonSQSAsync;
    
    @Override
    protected Object doTransform(Message<?> message) throws Exception {
        // ACK
        ack(message);
        
        return this.getMessageBuilderFactory().withPayload(buildChunkRequest(message)).setHeader(MESSAGE_GROUP_ID_HEADER, "unique").build();
    }
    
    private ChunkRequest buildChunkRequest(Message<?> message) throws IOException {
        Map map = new ObjectMapper().readValue(message.getPayload().toString(), Map.class);
        Map stepContributionMap = (Map) map.get("stepContribution");
        Map exitStatusMap = (Map) stepContributionMap.get("exitStatus");
        
        StepContribution stepContribution = new StepContribution(new StepExecution("null", null));
        ExitStatus exitStatus = new ExitStatus((String) exitStatusMap.get("exitCode"), (String) exitStatusMap.get("exitDescription"));
        
        IntStream.range(0, (Integer) stepContributionMap.get("readCount")).forEach(e -> stepContribution.incrementReadCount());
        stepContribution.incrementWriteCount((Integer) stepContributionMap.get("writeCount"));
        stepContribution.incrementFilterCount((Integer) stepContributionMap.get("filterCount"));
        stepContribution.incrementReadSkipCount((Integer) stepContributionMap.get("readSkipCount"));
        IntStream.range(0, (Integer) stepContributionMap.get("writeSkipCount")).forEach(e -> stepContribution.incrementWriteSkipCount());
        IntStream.range(0, (Integer) stepContributionMap.get("processSkipCount"))
                .forEach(e -> stepContribution.incrementProcessSkipCount());
        stepContribution.setExitStatus(exitStatus);
        
        return new ChunkRequest((Integer) map.get("sequence"), (Collection) map.get("items"), (Integer) map.get("jobId"), stepContribution);
    }
    
    private void ack(Message<?> message) {
        String receiptHandle = message.getHeaders().get(AwsHeaders.RECEIPT_HANDLE, String.class);
        String queue = message.getHeaders().get(AwsHeaders.QUEUE, String.class);
        String queueUrl = amazonSQSAsync.getQueueUrl(queue).getQueueUrl();
        
        amazonSQSAsync.deleteMessage(queueUrl, receiptHandle);
    }
}


JsonToChunkResponseTransformer.java
package com.frandorado.springbatchawsintegrationmaster.transformer;

import java.io.IOException;
import java.util.Map;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.integration.chunk.ChunkResponse;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class JsonToChunkResponseTransformer extends JsonToObjectTransformer {
    
    @Override
    protected Object doTransform(Message<?> message) throws Exception {
        return buildChunkResponse(message);
    }
    
    private ChunkResponse buildChunkResponse(Message<?> message) throws IOException {
        Map map = new ObjectMapper().readValue(message.getPayload().toString(), Map.class);
        
        Integer jobId = (Integer) map.get("jobId");
        Integer sequence = (Integer) map.get("sequence");
        String messageContent = (String) map.get("message");
        Boolean status = (Boolean) map.get("successful");
        
        StepContribution stepContribution = new StepContribution(new StepExecution("-", null));
        
        return new ChunkResponse(status, sequence, Long.valueOf(jobId), stepContribution, messageContent);
    }
}


还有一种方式,就是如果第三类不支持转JSON,即代码里没有JACKSON的注解,可以采用MIXIN的方式:

StepExecutionJacksonMixIn.java
package org.springframework.cloud.dataflow.rest.client.support;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import org.springframework.batch.core.StepExecution;

/**
 * Jackson MixIn for {
@link StepExecution} de-serialization.
 *
 * 
@author Gunnar Hillert
 * 
@since 1.0
 
*/
@JsonIgnoreProperties({ "jobExecution", "jobParameters", "jobExecutionId", "skipCount", "summary" })
public abstract class StepExecutionJacksonMixIn {

    @JsonCreator
    StepExecutionJacksonMixIn(@JsonProperty("stepName") String stepName) {
    }

}

在配置文件中注册才能使用:
JacksonConfiguration.java
import java.util.Locale;
import java.util.TimeZone;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.ISO8601DateFormatWithMilliSeconds;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.ExecutionContextJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.ExitStatusJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobExecutionJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobInstanceJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobParameterJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.JobParametersJacksonMixIn;
import com.novacredit.bmb.batchmonitor.springbatch.common.batch.jackson.mixin.StepExecutionJacksonMixIn;

@Configuration
public class JacksonConfiguration {

    @Bean
    public Jackson2JsonObjectMapper jackson2JsonObjectMapper(ObjectMapper objectMapper) {
        return new Jackson2JsonObjectMapper(objectMapper);
    }
    
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer dataflowObjectMapperBuilderCustomizer() {
        return (builder) -> {
            builder.dateFormat(new ISO8601DateFormatWithMilliSeconds(TimeZone.getDefault(), Locale.getDefault(), true));
            // apply SCDF Batch Mixins to
            
// ignore the JobExecution in StepExecution to prevent infinite loop.
            
// https://github.com/spring-projects/spring-hateoas/issues/333
            builder.mixIn(StepExecution.class, StepExecutionJacksonMixIn.class);
            builder.mixIn(ExecutionContext.class, ExecutionContextJacksonMixIn.class);
            builder.mixIn(JobExecution.class, JobExecutionJacksonMixIn.class);
            builder.mixIn(JobParameters.class, JobParametersJacksonMixIn.class);
            builder.mixIn(JobParameter.class, JobParameterJacksonMixIn.class);
            builder.mixIn(JobInstance.class, JobInstanceJacksonMixIn.class);
//            builder.mixIn(StepExecutionHistory.class, StepExecutionHistoryJacksonMixIn.class);
            builder.mixIn(ExecutionContext.class, ExecutionContextJacksonMixIn.class);
            builder.mixIn(ExitStatus.class, ExitStatusJacksonMixIn.class);
//            objectMapper.setDateFormat(new ISO8601DateFormatWithMilliSeconds());
            builder.modules(new JavaTimeModule(), new Jdk8Module());
        };
    }
}

    @Bean
    public IntegrationFlow flow4Contribution(
            ConnectionFactory connectionFactory, 
            JobProperties jobProperties,
            Jackson2JsonObjectMapper jackson2JsonObjectMapper
    ) {
        return IntegrationFlows
                    .from(request4ContributionMaster())
                    .enrichHeaders(headerEnricherConfigurer())
                    .transform(Transformers.toJson(jackson2JsonObjectMapper))
                    .handle(jmsOutboundGateway4Contribution(connectionFactory, jobProperties))
                    .transform(Transformers.fromJson(StepExecution.class, jackson2JsonObjectMapper))
                    .channel(replies4ContributionMaster(null))
                    .get();
    }


https://github.com/spring-cloud/spring-cloud-dataflow/tree/master/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/support

https://frandorado.github.io/spring/2019/07/29/spring-batch-aws-series-introduction.html

https://github.com/frandorado/spring-projects/tree/master/spring-batch-aws-integration/spring-batch-aws-integration-master/src/main/java/com/frandorado/springbatchawsintegrationmaster/transformer


https://github.com/frandorado/spring-projects/tree/master/spring-batch-aws-integration/spring-batch-aws-integration-slave/src/main/java/com/frandorado/springbatchawsintegrationslave/transformer

posted @ 2020-01-21 16:44 paulwong 阅读(562) | 评论 (0)编辑 收藏

如何加CRON JOB到LINUX


https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/crontab.html

posted @ 2020-01-21 15:50 paulwong 阅读(296) | 评论 (0)编辑 收藏

Linux and Unix du command tutorial with examples

https://shapeshed.com/unix-du/

查询>100M的文件,倒序排列,文件大小以K/M/G等显示:
-h: 以可读方式显示,-t 排除小于100m的文件,sort排序,less: 分页
du -ah -t 100m / | sort -n -r | less

posted @ 2020-01-15 17:17 paulwong 阅读(288) | 评论 (0)编辑 收藏

SPRING INTEGRATION ERROR HANDLING

https://github.com/zakyalvan/spring-integration-java-dsl-learn

package com.jwebs.learn.errorhandling;

import java.util.Random;

import javax.jms.ConnectionFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.core.Pollers;
import org.springframework.integration.dsl.jms.Jms;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessagingException;

/**
 * Show how to handle error in spring integration flow.
 * Please note, errorChannel in spring integration only applicable to
 * error thrown in asynch component.
 * 
 * 
@author zakyalvan
 
*/
@SpringBootApplication
@IntegrationComponentScan
public class ErrorHandlingApplication {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(ErrorHandlingApplication.class)
                .web(false)
                .run(args);
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> applicationContext.close()));
        
        System.out.println("Pres enter key to exit");
        System.in.read();
        System.exit(0);
    }

    @Autowired
    private ConnectionFactory connectionFactory;
    
    @Bean
    public MessageSource<Integer> randomIntegerMessageSource() {
        return () -> MessageBuilder.withPayload(new Random().nextInt()).build();
    }
    
    @Bean
    public IntegrationFlow withErrorFlow() {
        return IntegrationFlows.from(randomIntegerMessageSource(), spec -> spec.poller(Pollers.fixedDelay(1000)))
                    .handle(Jms.outboundGateway(connectionFactory)
                    .requestDestination("processor.input")
                    .replyContainer(spec -> spec.sessionTransacted(true)))
                    .get();
    }
    
    @Autowired
    @Qualifier("errorChannel")
    private PublishSubscribeChannel errorChannel;
    
    @Bean
    public IntegrationFlow errorHandlingFlow() {
        return IntegrationFlows.from(errorChannel)
                .handle(message -> System.out.println("@@@@@@@@@@@@@@@@@@@@@" + ((MessagingException) message.getPayload()).getFailedMessage().getPayload()))
                .get();
    }
}

posted @ 2020-01-10 15:32 paulwong 阅读(343) | 评论 (0)编辑 收藏

APM资源

Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用
https://my.oschina.net/10000000000/blog/3119697

监控管理之Spring Boot Admin使用
https://my.oschina.net/xiedeshou/blog/2051625


https://my.oschina.net/janlle/blog/3040749

posted @ 2019-12-30 16:50 paulwong 阅读(309) | 评论 (0)编辑 收藏

微服务-网关

微服务开源最强网关Kong
https://felord.cn/kong-api-gateway.html

云原生网关 Kong 和Kong 管理UI Konga的完全安装攻略
https://felord.cn/kong-and-konga-install.html




posted @ 2019-12-30 16:44 paulwong 阅读(289) | 评论 (0)编辑 收藏

2020 年软件开发趋势预测有哪些?

基础设施:条条道路通云端

对于云厂商来说,2019 年是硕果累累的一年。不仅初创公司在使用云计算,那些很注重安全的“保守派”公司(如政府机构、医疗保健机构、银行、保险公司,甚至是美国五角大楼)也在迁移到云端。这种趋势在 2020 年将会继续,大大小小的公司都将(或者至少有计划)迁移到云端。Gartner 公司最近发布了一个数字:

image.png

如果你是一个还在考虑要不要迁移到云端的决策者,不妨重新审视一下你的策略。如果你是一个独立开发者,并且还没使用过云基础设施,那么完全可以在 2020 年尝试一下。很多大型的云厂商(如亚马逊、微软、谷歌)都提供了免费的体验机会。谷歌在这方面做得特别大方,它提供了价值 300 美元的一年免费服务。

image.png

策划注:阿里、腾讯、华为等国内云厂商同样有免费云服务试用产品。

云平台:亚马逊领头,其他跟上

作为第一大云厂商,亚马逊在 2019 年可谓风生水起。凭借其丰富的产品组合,亚马逊将把它的优势延续到 2020 年。Canalys 发布的 2019 年第三季度报告指出,大型云厂商(AWS、Azure、GCP)占据 56% 的市场份额,其中 AWS 独享 32.6%。

image.png

其他云厂商也在努力缩短与 AWS 之间的差距。微软把主要目标转向了大型企业。最近,微软打败了亚马逊,从美国五角大楼拿到了一个 100 亿美元的大单子。这个单子将提升 Azure 的声誉,同时削弱 AWS 的士气。

image.png

谷歌一直在推动 CNCF,实现云计算运维的标准化。谷歌的长期目标是让云迁移变得更容易,方便企业从 AWS 迁移到 GCP。IBM 之前斥资 360 亿美元收购了 RedHat,也想要在云计算市场占有一席之地。

image.png

在亚太地区,阿里云市场规模超过了 AWS、Azure 的总和,全球排名第三。中国国内腾讯云等企业的增长势头也十分迅猛。

2020 年将出现更多的并购。当然,很多初创公司将会带来新的想法和创新,例如多云服务。因为竞争激烈,这些公司只能从降价和推出更多的创新产品来获取利润。

容器化:Kubernetes 将会更酷

在容器编排领域,虽然一度出现了“三足鼎立”(Kubernetes、Docker Swarm 和 Mesos),但 Kubernetes 最终脱颖而出,成为绝对的赢家。云是一个分布式系统,而 Kubernetes 是它的 OS(分布式的 Linux)。2019 年北美 KubeCon+CloudNativeCon 大会的参会者达到了 12000 名,比 2018 年增长了 50%。以下是过去 4 年参会人数的增长情况。

image.png

在 2020 年,Kubernetes 不仅不会后退,只会变得越来越强,你完全可以把赌注压在 Kubernetes 身上。另外值得一提的是,Migrantis 最近收购了 Docker Enterprise,不过收购数额不详。

image.png

几年前,人们张口闭口说的都是 Docker,而现在换成了 Kubernetes。Docker 在它的全盛时期未能盈利,反而在优势渐退几年之后才尝试变现。这再次说明,在现代技术世界,时机就是一切。

软件架构:微服务将成为主流

谷歌趋势表明,微服务架构范式在 2019 年持续增长了一整年。

image.png

随着软件行业整体逐步迁移到云端,微服务也将成为占主导地位的架构范式。微服务架构崛起的一个主要原因是它与云原生完美契合,可以实现快速的软件开发。我在之前的一篇博文中解释了微服务架构的基本原则及其优势和劣势。

https://towardsdatascience.com/microservice-architecture-a-brief-overview-and-why-you-should-use-it-in-your-next-project-a17b6e19adfd

我假设现在也存在一种回归到单体架构的趋势,因为在很多情况下,微服务架构有点过头了,而且做好微服务架构设计其实很难。微服务架构有哪些好的实践?在之前的另一篇博文中,我也给出了一些大概,希望对读者有用。

https://towardsdatascience.com/effective-microservices-10-best-practices-c6e4ba0c6ee2

编程语言(整体):Python 将吞噬世界

机器学习、数据分析、数据处理、Web 开发、企业软件开发,甚至是拼接黑洞照片,Python 的影子无处不在。

在著名的编程语言排行榜网站 TIOBE 上,Python 位居最流行编程语言第三位,仅次于 Java 和 C 语言。

image.png

更有意思的是,在 2019 年,Python 的流行度翻了一番(从 5% 到 10%)。

Python 的崛起将在 2020 年延续,并缩短与 Java 和 C 语言之间的差距。另一门无所不在的编程语言 JavaScript 正面临下行的风险。为什么 Python 的势头会如此强劲?因为它的入手门槛低,有一个优秀的社区在支持,并受到数据科学家和新生代开发者的喜爱。

编程语言(企业方面):Java 将占主导

之前的 TIOBE 网站截图显示,Java 仍然是一门占主导地位的编程语言,并将在 2020 年继续保持这种地位。JVM 是 Java 的基石,其他编程语言(如 Kotlin、Scala、Clojure、Groovy)也将 JVM 作为运行时。最近,Oracle 修改了 JVM 的许可协议。

image.png

新的许可协议意味着使用 Java、Kotlin、Scala 或其他 JVM 编程语言的公司需要向 Oracle 支付大额费用。所幸的是,OpenJDK 让 JVM 继续免费。另外,还有其他一些公司为 JVM 提供企业支持。

image.png

因为体积和速度方面的问题,基于 JVM 的编程语言并不适合用在今天的无服务器环境中。Oracle 正在推动 GraalVM 计划,旨在让 Java 变得更加敏捷和快速,让它更适合用在无服务器环境中。因为除了 Java,没有其他编程语言可以提供企业级的稳定性和可靠性,所以 Java 将在 2020 年继续占主导地位。

企业版 Java:Spring 继续发力

曾几何时,在企业开发领域,Spring 和 JavaEE 之间存在着白热化的竞争。但因为 Oracle 在 JavaEE 方面没有作为,在竞争中惨败,这导致了“MicroProfile”计划的形成,并最终促成了 JakartaEE。

虽然所有的政策和活动都是围绕 JavaEE 展开,但 Spring 事实上已经赢得了这场企业 JVM 之争。2020 年,Spring 将成为 JVM 生态系统的头牌。

有两个正在进展中的项目,它们旨在减小 Java 的体积,让它更适合用在无服务器环境中。

其中一个是 Micronaut(https://micronaut.io/)。

image.png

另一个是 Quarkus(https://quarkus.io/)。

image.png

这两个项目都使用了 GraalVM,它们在 2020 年将会得到 Java 社区更多的关注。

编程语言:后起之秀的突破

2000 年代,编程语言的发展出现了停滞。大多数人认为没有必要再去开发新的编程语言,Java、C 语言、C++、JavaScript 和 Python 已经可以满足所有的需求。但是,谷歌的 Go 语言为新编程语言大门打开了一扇大门。在过去十年出现了很多有趣的编程语言,比如 Rust、Swift、Kotlin、TypeScript。导致这种情况的一个主要原因是已有的编程语言无法充分利用硬件优势(例如多核、更快的网络、云)。另一个原因是现代编程语言更加关注开发者经济,即实现更快速更容易的开发。在 Stackoverflow 提供的一份开发者报告中,排名靠前的现代编程语言如下所示(Rust 连续 4 年名列第一)。

image.png

在之前的一篇博文中,我深入探讨了现代编程语言,对比 Rust 和 Go 语言,并说明了为什么现在是采用这些语言的好时机。

https://towardsdatascience.com/back-to-the-metal-top-3-programming-language-to-develop-big-data-frameworks-in-2019-69a44a36a842

最近,微软宣布他们在探索使用 Rust 来开发更安全的软件。

image.png

亚马逊最近也宣布要赞助 Rust。

image.png

谷歌宣布将 Kotlin 作为 Android 官方开发语言,所以,在 JVM 领域,Kotlin 成了 Java 的主要竞争对手。

image.png

Angular 使用 TypeScript 代替 JavaScript,将其作为主要的编程语言,其他 JavaScript 框架(如 React 和 Vue)也开始为 TypeScript 提供更多的支持。

这种趋势将在 2020 年延续下去,很多巨头公司将会深入了解新一代编程语言(如 Rust、Swift、TypeScript、Kotlin),它们会站出来公开表示支持。

Web:JavaScript 继续占主导地位

曾几何时,JavaScript 并不被认为是一门强大的编程语言。在当时,前端内容主要通过后端框架在服务器端进行渲染。2014 年,AngularJS 的出现改变了这种局面。从那个时候开始,更多的 JavaScript 框架开始涌现(Angular 2+、React、Vue、Meteor),JavaScript 已然成为主流的 Web 开发语言。随着 JavaScript 框架不断创新以及微服务架构的崛起,JavaScript 框架在 2020 年将继续主导前端开发。

JavaScript 框架:React 闪耀

虽然 React 是在 AngularJS 之后出现的,但在过去十年对 Web 开发产生了巨大的影响,这也让 Facebook 在与 Google+ 的竞争中打了一场胜战。React 为前端开发带来了一些新的想法,比如事件溯源、虚拟 DOM、单向数据绑定、基于组件的开发,等等。它对开发者社区产生了重大影响,以至于谷歌放弃了 AngularJS,并借鉴 React 的想法推出了彻底重写的 Angular 2+。React 是目前为止最为流行的 JavaScript 框架,下图显示了相关的 NPM 下载统计信息。

image.png

为了获得更好的并发和用户体验,Facebook 宣布完全重写 React 的核心算法,推出了 React-Fiber 项目。

image.png

2020 年,React 仍然是你开发新项目的首选 Web 框架。其他框架(如 Angular/Angular 2+ 或 Vue)呢?Angular 仍然是一个不错的 Web 开发框架,特别适合企业开发。我敢肯定谷歌在未来几年会在 Angular 上加大投入。Vue 是另一个非常流行的 Web 框架,由中国的巨头公司阿里巴巴提供支持。如果你已经在使用 Angular 或 Vue,就没必要再迁移到 React 了。

App 开发:原生应用

在移动 App 开发方面,有关混合应用开发的炒作有所消停。混合开发提供了更快的开发速度,因为只需要一个开发团队,而不是多个。但原生应用提供了更好的用户体验和性能。另外,混合应用需要经过调整才能使用一些高级特性。对于企业来说,原生应用仍然是首选的解决方案,这种趋势将在 2020 年延续。Airbnb 在一篇博文中非常详细地说明了为什么他们要放弃混合应用开发平台 React Native。

https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a

尽管 Facebook 尝试改进 React Native,谷歌也非常努力地推动混合 App 开发平台 Flutter,但它们仍然只适合用于原型、POC、MVP 或轻量级应用的开发。所以,原生应用在 2020 年仍将继续占主导地位。

在原生应用开发方面,谷歌和苹果分别将 Kotlin 和 Swift 作为各自平台主要的编程语言。谷歌最近再次重申了对 Kotlin 的支持,这对于 Kotlin 用户来说无疑是个好消息。

image.png

混合应用开发:React Native

在很多情况下,混合应用是个不错的选择。在这方面也有很多选择:Xamarin、Inoic、React Native 和 Flutter。Facebook 基于成熟的 React 框架推出了 React Native。就像 React 在 Web 框架领域占据主导地位一样,React Native 在混合应用领域也占据着主导地位,如下图所示。

image.png

React Native 和 React 有共同的基因,都提供了高度的代码重用性以及“一次开发,到处运行”的能力。React Native 的另一个优势是 Facebook 本身也用它来开发移动应用。谷歌在这个领域起步较晚,但在去年,谷歌的混合应用开发框架 Flutter 获得了不少关注。Flutter 提供了更好的性能,但需要使用另一门不是那么流行的编程语言 Dart。React Native 在 2020 年将继续占主导地位。

API:REST 将占主导地位

REST 是 API 领域事实上的标准,被广泛用在基于 API 的服务间通信上。当然,除了 REST,我们还有其他选择,比如来自谷歌的 gRPC 和来自 Facebook 的 GraphQL。

它们提供了不同的能力。谷歌开发的 gRPC 作为远程过程调用(如 SOAP)的化身,使用 Protobuf 代替 JSON 作为消息格式。Facebook 开发的 GraphQL 作为一个集成层,避免频繁的 REST 调用。gRPC 和 GraphQL 都在各自的领域取得了成功。2020 年,REST 仍然是占主导地位的 API 技术,而 GraphQL 和 gRPC 将作为补充技术。

人工智能:Tensorflow 2.0 将占主导地位

谷歌和 Facebook 也是深度学习 / 神经网络领域的主要玩家。谷歌基于深度学习框架 Theano 推出了 TensorFlow,它很快就成为深度学习 / 神经网络的主要开发库。谷歌还推出了特别设计的 GPU(TPU)来加速 TensorFlow 的计算。

Facebook 在深度学习领域也不甘落后,他们拥有世界上最大的图像和视频数据集合。Facebook 基于另一个深度学习库 Torch 推出了深度学习库 PyTorch。TensorFlow 和 PyTorch 之间有一些区别,前者使用的是静态图进行计算,而 PyTorch 使用的是动态图。使用动态图的好处是可以在运行时纠正自己。另外,PyTorch 对 Python 支持更好,而 Python 是数据科学领域的一门主要编程语言。

随着 PyTorch 变得越来越流行,谷歌也赶紧在 2019 年 10 月推出了 TensorFlow 2.0,也使用了动态图,对 Python 的支持也更好。

image.png

2020 年,TensorFlow 2.0 和 PyTorch 将齐头并进。考虑到 TensorFlow 拥有更大的社区,我估计 TensorFlow 2.0 将成为占主导地位的深度学习库。

数据库:SQL是王者,分布式SQL是王后

在炒作 NoSQL 的日子里,人们嘲笑 SQL,还指出了 SQL 的种种不足。有很多文章说 NoSQL 有多么的好,并将要取代 SQL。但等到炒作的潮水褪去,人们很快就意识到,我们的世界不能没有 SQL。以下是最流行的数据库的排名。

image.png

可以看到,SQL 数据库占据了前四名。SQL 之所以占主导地位,是因为它提供了 ACID 事务保证,而 ACID 是业务系统最潜在的需求。NoSQL 数据库提供了横向伸缩能力,但代价是不提供 ACID 保证。

互联网公司一直在寻找“大师级数据库”,也就是既能提供 ACID 保证又能像 NoSQL 那样可横向伸缩的数据库。目前有两个解决方案可以部分满足对“大师级数据库”的要求,一个是亚马逊的 Aurora,一个是谷歌的 Spanner。Aurora 提供了几乎所有的 SQL 功能,但不支持横向写伸缩,而 Spanner 提供了横向写伸缩能力,但对 SQL 支持得不好。

2020 年,但愿这两个数据库能够越走越近,或者有人会带来一个“分布式 SQL”数据库。如果真有人做到了,那一定要给他颁发图灵奖。

数据湖:MinIO 将要崛起

现代数据平台非常的复杂。企业一般都会有支持 ACID 事务的 OLTP 数据库(SQL),也会有用于数据分析的 OLAP 数据库(NoSQL)。除此之外,它们还有其他各种数据存储系统,比如用于搜索的 Solr、ElasticSearch,用于计算的 Spark。企业基于数据库构建自己的数据平台,将 OLTP 数据库的数据拷贝到数据湖中。各种类型的数据应用程序(比如 OLAP、搜索)将数据湖作为它们的事实来源。

HDFS 原本是事实上的数据湖,直到亚马逊推出了对象存储 S3。S3 可伸缩,价格便宜,很快就成为很多公司事实上的数据湖。使用 S3 唯一的问题是数据平台被紧紧地绑定在亚马逊的 AWS 云平台上。虽然微软 Azure 推出了 Blob Storage,谷歌也有类似的对象存储,但都不是 S3 的对手。

对于很多公司来说,MinIO 或许是它们的救星。MinIO 是一个开源的对象存储,与 S3 兼容,提供了企业级的支持,并专门为云原生环境而构建,提供了与云无关的数据湖。

image.png

微软在 Azure Marketplace 是这么描述 MinIO 的:“为 Azure Blog Storage 服务提供与亚马逊 S3 API 兼容的数据访问”。如果谷歌 GCP 和其他云厂商也提供 MinIO,那么我们将会向多云迈出一大步。

大数据批处理:Spark 将继续闪耀

现如今,企业通常需要基于大规模数据执行计算,所以需要分布式的批处理作业。Hadoop 的 Map-Reduce 是第一个分布式批处理平台,后来 Spark 取代了 Hadoop 的地位,成为真正的批处理之王。Spark 是怎样提供了比 Hadoop 更好的性能的?我之前写了另一篇文章,对现代数据平台进行了深入分析。

https://towardsdatascience.com/programming-language-that-rules-the-data-intensive-big-data-fast-data-frameworks-6cd7d5f754b0

Spark 解决了 Hadoop Map-Reduce 的痛点,它将所有东西放在内存中,而不是在完成每一个昂贵的操作之后把数据保存在存储系统中。尽管 Spark 重度使用 CPU 和 JVM 来执行批处理作业,但这并不妨碍它成为 2020 年批处理框架之王。我希望有人能够使用 Rust 开发出一个更加高效的批处理框架,取代 Spark,并为企业省下大量的云资源费用。

大数据流式处理:Flink 是未来

几年前,实现实时的流式处理几乎是不可能的事情。一些微批次处理框架(比如 Spark Streaming)可以提供“几近”实时的流式处理能力。不过,Flink 改变了这一状况,它提供了实时的流式处理能力。

2019 年之前,Flink 未能得到足够的关注,因为它无法撼动 Spark。直到 2019 年 1 月份,中国巨头公司阿里巴巴收购了 Data Artisan(Flink 背后的公司)。

image.png

在 2020 年,企业如果想要进行实时流式处理,Flink 应该是不二之选。不过,跟 Spark 一样,Flink 同样重度依赖 CPU 和 JVM,并且需要使用大量的云资源。

字节码:WebAssembly将被广泛采用

我从 JavaScript 作者 Brandon Eich 的一次访谈中知道了 WebAssembly 这个东西。现代 JavaScript(ES5 之后的版本)是一门优秀的编程语言,但与其他编程语言一样,都有自己的局限性。最大的局限性是 JavaScript 引擎在执行 JavaScript 时需要读取、解析和处理“抽象语法树”。另一个问题是 JavaScript 的单线程模型无法充分利用现代硬件(如多核 CPU 或 GPU)。正因为这些原因,很多计算密集型的应用程序(如游戏、3D 图像)无法运行在浏览器中。

一些公司(由 Mozilla 带领)开发了 WebAssembly,一种底层字节码格式,让任何一门编程语言都可以在浏览器中运行。目前发布的 WebAssembly 版本可以支持 C++、Rust 等。

image.png

WebAssembly 让计算密集型应用程序(比如游戏和 AutoCAD)可以在浏览器中运行。不过,WebAssembly 的目标不仅限于此,它还要让应用程序可以在浏览器之外运行。WebAssembly 可以被用在以下这些“浏览器外”的场景中。

  • 移动设备上的混合原生应用。
  • 没有冷启动问题的无服务器计算。
  • 在服务器端执行不受信任的代码。
    我预测,2020 年将是 WebAssembly 取得突破的一年,很多巨头公司(包括云厂商)和社区将会拥抱 WebAssembly。

代码:低代码 / 无代码将更进一步

快速的数字化和工业 4.0 革命意味着软件开发者的供需缺口巨大。由于缺乏开发人员,很多企业无法实现它们的想法。为了降低进入软件开发的门槛,可以尝试无代码(No Code)或低代码(Low Code)软件开发,也就是所谓的 LCNC(Low-Code No-Code)。它已经在 2019 年取得了一些成功。

image.png

LCNC 的目标是让没有编程经验的人也能开发软件,只要他们想要实现自己的想法。

虽然我对在正式环境中使用 LCNC 框架仍然心存疑虑,但它为其他公司奠定了良好的基础,像亚马逊和谷歌这样的公司可以基于这个基础构建出有用的产品,就像 AWS Lambda 的蓬勃发展是以谷歌 App Engine 为基础。

2020 年,LCNC 将会获得更多关注。

posted @ 2019-12-30 09:09 paulwong 阅读(340) | 评论 (0)编辑 收藏

How to provide a Spring Boot "fat JAR" with external dependencies

发现loader.path放在properties文件中时,不起作用,要以-Dloader.path=lib/的方式才行。

http://www.codevomit.xyz/bootlog/blog/how-to-provide-spring-boot-fat-jar


https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html

posted @ 2019-12-27 15:47 paulwong 阅读(294) | 评论 (0)编辑 收藏

解决“/bin/bash^M: bad interpreter: No such file or directory”

在执行shell脚本时提示这样的错误主要是由于shell脚本文件是dos格式,即每一行结尾以\r\n来标识,而unix格式的文件行尾则以\n来标识。
查看脚本文件是dos格式还是unix格式的几种办法。
(1)cat -A filename 从显示结果可以判断,dos格式的文件行尾为^M$,unix格式的文件行尾为$。
(2)od -t x1 filename 如果看到输出内容中存在0d 0a的字符,那么文件是dos格式,如果只有0a,则是unix格式。
(3)vi filename打开文件,执行 : set ff,如果文件为dos格式在显示为fileformat=dos,如果是unxi则显示为fileformat=unix。

解决方法:
(1)使用linux命令dos2unix filename,直接把文件转换为unix格式
(2)使用sed命令sed -i -e 's/\r$//' filename 或者 sed -i "s/^M//" filename直接替换结尾符为unix格式
(3)vi filename打开文件,执行 : set ff=unix 设置文件为unix,然后执行:wq,保存成unix格式。

posted @ 2019-12-11 13:42 paulwong 阅读(298) | 评论 (0)编辑 收藏

防止在SPRING BOOT的配置文件中使用明文存储密码

Spring Boot how to hide passwords in properties file
https://stackoverflow.com/questions/37404703/spring-boot-how-to-hide-passwords-in-properties-file

工作随笔——jasypt-spring-boot使用
https://www.cnblogs.com/zz0412/p/jasypt-001.html

Get史上最优雅加密方式!没有之一!
https://www.jianshu.com/p/64ceda636e81

使用Jasypt对SpringBoot配置文件加密
https://www.jianshu.com/p/323ec96c46d2


posted @ 2019-11-26 15:13 paulwong 阅读(904) | 评论 (0)编辑 收藏

Git版本控制与工作流



https://www.jianshu.com/p/67afe711c731

posted @ 2019-11-26 11:21 paulwong 阅读(324) | 评论 (0)编辑 收藏

GIT TAG

GIT中的tag 相当于是一个快照,是不能更改它的代码的。
如果要在 tag 代码的基础上做修改,你需要一个分支: 

通常TAG与软件版本相对应,即TAG名称用软件版本号来表示。

软件版本的格式规范:
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

Git打标签与版本控制规范
https://juejin.im/post/5b0531c6f265da0b7f44eb8c

git切换到某个tag
https://blog.csdn.net/DinnerHowe/article/details/79082769

git cherry-pick 把提交到A分支的部分commit 再提交到B分支上
http://yijiebuyi.com/blog/0e65f4a59a1cfa05c5b30ccb6c2f413d.html

posted @ 2019-11-26 10:57 paulwong 阅读(299) | 评论 (0)编辑 收藏

MAVEN打多环境包

通常配置文件是放在src/main/resources下,build完之后会放在classes文件夹下,最终会打进jar包中。

如果是SPRING BOOT工程,部署时,要求配置文件不打进jar包中,要有sh启动命令文件,最终产生一个ZIP包,包含所有需要的东西。这时就需要善用MAVEN的resource插件、assembly插件和jar插件了。

resource插件能重定义配置文件在output时的文件夹路径,用profile的变量值替换配置文件中的占位符。
但要更改点位符的默认定义:
<properties>
        <resource.delimiter>${}</resource.delimiter>
    </properties>


jar插件能排除生成在classes文件夹中不要的文件被打进jar包中。

assembly插件能定义ZIP包中需要包含哪些文件。

<resources>  
        <resource>  
            <directory>src/main/bin</directory>
            <!--表明此文件夹中如有占位符,则会取pom中的profile中的值代替-->
            <filtering>true</filtering>
            <includes>  
                <include>*.sh</include>
            </includes>
        </resource>  
</resources>  

<plugin>  
        <artifactId>maven-jar-plugin</artifactId>  
        <configuration>
            <!--bin/文件夹不会被打进jar包-->
            <excludes>  
                <exclude>bin/</exclude>
            </excludes>
        </configuration>  
</plugin>  

<fileSet>  
        <!--定义bin/文件夹被打进zip包-->
        <directory>${build.outputDirectory}/bin</directory>  
        <outputDirectory>bin</outputDirectory>
        <fileMode>0755</fileMode>  
</fileSet>  


maven打包加时间戳
https://blog.csdn.net/z410970953/article/details/50680603

posted @ 2019-11-21 16:40 paulwong 阅读(652) | 评论 (0)编辑 收藏

Maven打包时,环境变量替换,并解决spring-boot项目中${}无效的问题

https://www.jianshu.com/p/cf3bd9ddfe6f

posted @ 2019-11-20 16:12 paulwong 阅读(717) | 评论 (0)编辑 收藏

LOGBACK FOR SPRING

当程序中使用LOGGER.INFO("MESSAGE");要求打印日志时,LOGBACK会获取该行代码所在类的全名,和打印等级(INFO/DEBUG等),再在配置文件的<logger>中查找对应logger,使用其配置的appender组件打印日志,如无法找到对应的logger,则使用<root>对应的appender打印日志。

其中appender是用来输出日志,有file和console两个实现,console则是向控制台输出日志,而file则是向文件输出日志。
rolling file appender中,有rollingPolicy和triggerPolicy两个主要属性,rollingPolicy是确定如何处理日志文件,而triggerPolicy则是确定何时处理日志文件。

如果要使用SPRING针对LOGBACK的一些功能,如profile等,则要将logback.xml的配置文件命名为logback-spring.xml,并在SPRING中配置,logging.config= logback-spring.xml。

SPRING会将logging.file、logging.path这些配置转成系统变量LOG_FILE、LOG_PATH,可在配置文件中直接引用,如${LOG_FILE}。

如果logback配置文件要SPRING的其他属性,则要使用如下标签:
<springProperty scope="context" name="logLevel" source="log.level"/>

如果要使用LOGBACK的一些常用属性,可引入:
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
如CONSOLE APPENDER,此resource在spring-boot-version.jar中。


=========================================
看完这个不会配置 logback ,请你吃瓜!
https://juejin.im/post/5b51f85c5188251af91a7525

logback解析——Appender
https://juejin.im/post/5a39c91cf265da4327185d10

SpringBoot中logback.xml使用application.yml中属性
https://www.cnblogs.com/jianliang-Wu/p/8945343.html

springboot使用logback-spring.xml配置讲解
https://blog.csdn.net/heguiliang_123/article/details/80296745

Logback配置
https://www.cnblogs.com/cjsblog/p/9113131.html

Logback中如何自定义灵活的日志过滤规则
https://www.jianshu.com/p/d6360c517264

Spring Boot中的日志
http://loveshisong.cn/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2016-11-03-Spring-Boot%E4%B8%AD%E7%9A%84%E6%97%A5%E5%BF%97.html

Spring Boot与logback总结
https://blog.csdn.net/u014527058/article/details/79667458

SpringBoot Logback 配置参数迁移到配置中心 Apollo
https://blog.csdn.net/shuaizai88/article/details/83027262

posted @ 2019-11-19 15:14 paulwong 阅读(346) | 评论 (0)编辑 收藏

MONGODB安装


https://www.jianshu.com/p/d3b31b7aa182 

后续初始化用户及数据库
http://www.qianduan8.com/1786.html
https://zocada.com/setting-mongodb-users-beginners-guide/

如果要以认证的方式登录,需加以下内容至/etc/mongod.conf
security:
  authorization: enabled

如果用GUI连接数据库时,不显示数据库列表,要加权限:listDatabases
https://stackoverflow.com/questions/19458524/mongodb-show-dbs-and-show-log-without-clusteradmin-role

posted @ 2019-11-15 17:30 paulwong 阅读(328) | 评论 (0)编辑 收藏

Setting up ActiveMQ for HA-Load Balance


In a typical enterprise applications, we often need messaging and asynchronous processing.
To satisfy this need, we need a reliable as well as scalable messaging infrastructure. In currently available messaging infrastructures Apache ActiveMQ stands out in terms of features and simplicity.

Apache ActiveMQ comes with lot of features in built and also provides a way to configure or tweak as per the needs of an application.

In this post , we will explore how to enable network of activeMQ brokers so that we achieve HA(High Availability) as well as load balance between consumers & producers.

I carried out  my experiment on local machine with ACtiveMQ 5.8.0, but this can be easily upgraded to latest versions of ActiveMQ viz. 5.10.0

To have network of brokers, we need multiple brokers. So, I changed tcp and admin ports of brokers so that I can run multiple brokers on single machine.

To get brief background on network of broker, please visit this link

In this post we will setup below topology, we will mix failover and NOB to get work done,

1. Producer1 is configured to send messages to broker3 with failover to broker2
2. Producer2 is configured to send messages to broker2 with failover to broker3
3. Broker3, Broker2 are networked with Broker1as below


4. Broker1 is connected with broker4 with NOB.
5. Make sure you enable "advisorySupport" on the broker, which is essential for transparent routing of messages across brokers.
Dry Run:
1. Producer1 sends messages to queue "input.q" on broker3, where there are no active consumers, but it see subscriptions from broker1
2. Broker1 and broker 4 are has consumers which are looking at "input.q".
3. When broker3 receives a message it forwards it to broker1, as its in networked and has active consumers for "input.q" 
4. When broker1 receives a messages on "input.q", it gets load balanced between broker1 and broker4  as both has consumers looking for "input.q".
5. Whenever broker3 goes down, producer1 switches transparently to broker2, as its configured with failover.
6. I used prefetch size as 1, so that you can load balancing on consumers
Sample activemq configurations can be downloaded from here.

posted @ 2019-11-04 16:14 paulwong 阅读(372) | 评论 (0)编辑 收藏

ACTIVE MQ HA

组建ACTIVEMQ CLUSTER,使得其中一个ACTIVE MQ DOWN掉时,能自动切换到其他节点。

ACTIVEMQ 只有MASTER-SLAVE模式,集群中的多个节点共享消息的存储,多个节点同时启动时,竞争消息存储的锁,谁先取得,谁就是MASTER,当MASTER DOWN掉时,锁被释放,SALVE中马上又竞争锁,取得者成为MASTER。

方案:
  • 安装NFSV4
  • 修改消息存储路径
    <persistenceAdapter>
      <kahaDB directory="/sharedFileSystem/sharedBrokerData"/>
    </persistenceAdapter>
  • 客户端修改连接字符串
    failover://(tcp://master:61616,tcp://slave:61616)?randomize=false
--》


https://my.oschina.net/hzchenyh/blog/716424

https://www.iteye.com/blog/shift-alt-ctrl-2069250

https://stackoverflow.com/questions/53542928/activemq-ha-on-failover

https://activemq.apache.org/shared-file-system-master-slave

ActiveMQ(6)-基于networkConnector的Broker-Cluster方案
https://blog.csdn.net/jinjin603/article/details/78657387


Multi Data Centre Message Brokers with ActiveMQ
https://medium.com/thg-tech-blog/multi-data-centre-message-brokers-with-activemq-28495046370e

ActiveMQ中的NetworkConnector(网络连接器)详解
https://www.iteye.com/blog/manzhizhen-2116920

a

posted @ 2019-11-01 10:46 paulwong 阅读(372) | 评论 (0)编辑 收藏

ACTIVEMQ设置预取消息数目

当ACTIVEMQ的某个QUEUE有多个消费者,为避免某个消息者取了更多个消息处理,而造成其他消费者无消息可处理的情况,可以设置每个消费者不预取消息,即每个消费者消费完单个消息后,再去取消息,这样其他消费者就能平均的有消息可处理。


https://stackoverflow.com/questions/35928089/activemq-how-to-prevent-message-from-going-to-dispatched-queue


设置方法,在CONNECT STRING中设置:
tcp://localhost:61616?jms.prefetchPolicy.all=0 

tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=0 

queue = new ActiveMQQueue("TEST.QUEUE?consumer.prefetchSize=10"); 
consumer = session.createConsumer(queue);

http://activemq.apache.org/what-is-the-prefetch-limit-for.html

posted @ 2019-10-31 11:28 paulwong 阅读(815) | 评论 (0)编辑 收藏

EIP in SPRING INTEGRATION

  • idempotent receiver
幂等型,同一个MESSAGE,如MESSAGE ID都一样,在MESSAGING系统中不管运行多少次,结果都一样,为啥?因为重复的MESSAGE,都被忽略了。
方案:
消息被处理后,从消息中取出ID,放入META-DATA-STORE中,后续处理消息时,要从META-DATA-STORE中检查是否有值。

下面这个方案,ID的存储和判断是否重复消息都在一个INTERCEPTOR中搞定。
https://stackoverflow.com/questions/50401460/spring-integration-dsl-configure-idempotent-receiver-to-identify-duplicates
https://www.javacodegeeks.com/2015/09/monitoring-and-management.html


claim-check
将MESSAGE的PAYLOAD存在STORE中,返回一个ID,这个ID即claim-check,如果需要取MESSAGE的DETAIl时,可从STORE中取出MESSAGE。
https://github.com/spring-projects/spring-integration/blob/master/src/reference/asciidoc/claim-check.adoc


posted @ 2019-10-25 11:03 paulwong 阅读(482) | 评论 (0)编辑 收藏

SPRING INTEGRATION LESSONS

Lessons

Introduction to Enterprise Application Integration

In our first lesson, you will get introduced to the concepts of Enterprise Application Integration. You will learn about the and Enterprise integration patterns that can be applied to simplify integration between different platforms and the Integration strategies that can be followed for this purpose. Finally, we will discuss how and why to implement a Message driven architecture and how to achieve both Synchronous and asynchronous communication among nodes.

Spring Integration Fundamentals

In this lesson, you will get to understand how Spring Integration works under the hood. The core concepts of Spring Integration messaging system (like message channels and endpoints) will be introduced. Additionally, the components that build the framework will be discussed, including the channel adapters, transformers, filters, routers etc. Finally, the two distinct methods of communication (synchronous and asynchronous) are explained and the lesson ends with a discussion on error handling.

Spring Integration and Web Services

In this lesson, we will focus on the integration with external web services. Spring Integration comes with the necessary functionality (adapters, channels etc.) to support web services out of the box. A full example is built from scratch in order to better understand the topic.

Enterprise Messaging

In this lesson, we will focus on integrating our application with JMS messaging. For this purpose, we will use Active MQ, which will be our broker. We will show examples of sending and receiving JMS messages by using the Spring Integration JMS channel adapters. Following these examples, we will see some ways of customizing these invocations by configuring message conversion and destination resolution.

Spring Integration Full Example

In this lesson, we will wrap everything up by providing a complete application that uses several of the components provided by Spring Integration in order to provide a service to its users. We will discuss the system architecture, the actual implementation and the relevant error handling.

Monitoring and Management

In this lesson, we will examine different mechanisms of monitoring or gathering more information about what is going on within the messaging system. Some of these mechanisms consist of managing or monitoring the application through MBeans, which are part of the JMX specification. Another mechanism discussed in this chapter is how we will implement the EIP idempotent receiver pattern using a metadata store. Finally, the last mechanism described is the control bus. This will let us send messages that will invoke operations on components in the application context.

posted @ 2019-10-25 09:45 paulwong 阅读(297) | 评论 (0)编辑 收藏

SPRING INTEGRATION DSL DEMO

https://github.com/spring-projects/spring-integration/tree/master/src/reference/asciidoc

posted @ 2019-10-23 11:55 paulwong 阅读(321) | 评论 (0)编辑 收藏

Spring Integration 中文手册 - GOOD

Spring Integration 中文手册 (1)


Spring Integration 中文手册 (2)

posted @ 2019-10-11 10:28 paulwong 阅读(799) | 评论 (0)编辑 收藏

LINUX配置DNS

vi /etc/resolv.conf

nameserver 8.8.8.8

posted @ 2019-10-10 10:55 paulwong 阅读(325) | 评论 (0)编辑 收藏

在SPRING INTEGRATION中手动开始和停止JMS LISTENER

如果要对JMS BROKER生产和消费MESSAGE,一种方式是用JmsTemplate发送和消费消息,另一种方式是SPRING INTEGRATION。

SPRING INTEGRATION是实现了EIP模式的一种框架,即使用CHANNEL和JMS-INBOUND-ADAPTER、JMS-OUTBOUND-ADAPTER,完全脱离了JmsTemplate的API。

如果需要实现这种场景:从BROKER取一条消息,处理消息,且处理途中不要再从BROKER再取消息,处理完后再取消息,再处理。

这样要求手动开始和停止JMS LISTENER,即手动开始和停止JMS-INBOUND-ADAPTER、JMS-OUTBOUND-ADAPTER。

@Bean
@InboundChannelAdapter(value = "loaderResponseChannel")
public MessageSource loaderResponseSource() throws Exception {
    return Jms
            .inboundAdapter(oracleConnectionFactory())
            .configureJmsTemplate(
                    t -> t.deliveryPersistent(true)
                            .jmsMessageConverter(jacksonJmsMessageConverter())
            ).destination(jmsInbound).get();
}

当使用@InboundChannelAdapter时,会自动注册一个SourcePollingChannelAdapter ,但这个名字比较长:configrationName.loaderResponseSource.inboundChannelAdapter。

呼叫这个实例的start()和stop()方法即可。

@Bean
public IntegrationFlow controlBusFlow() {
    return IntegrationFlows.from("controlBus")
              .controlBus()
              .get();
}

Message operation = MessageBuilder.withPayload("@configrationName.loaderResponseSource.inboundChannelAdapter.start()").build();
operationChannel.send(operation)

https://stackoverflow.com/questions/45632469/shutdown-spring-integration-with-jms-inboundadapter

https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/system-management-chapter.html#control-bus

https://github.com/spring-projects/spring-integration-java-dsl/blob/master/src/test/java/org/springframework/integration/dsl/test/jms/JmsTests.java

https://stackoverflow.com/questions/50428552/how-to-stop-or-suspend-polling-after-batch-job-fail

posted @ 2019-10-09 17:16 paulwong 阅读(603) | 评论 (0)编辑 收藏

CountDownLatch、CyclicBarrier和Semaphore

CountDownLatch、CyclicBarrier和Semaphore这三个并发辅助类,可以在线程中呼叫,使得线程暂停等,但各有不同。

  • CountDownLatch
1、初始化,并传入计数器
2、向不同的线程传入CountDownLatch实例
3、如果在某一线程中呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
4、如果在某一线程中呼叫countDown(),计数器减1
5、最终如果计数器值为0时,则CountDownLatch实例不再起作用了,即为一次性的

  • CyclicBarrier
1、初始化,并传入计数器值,也可传入一个Runnable类,会在计数器为0时,被执行
2、向不同的线程传入CyclicBarrier实例
3、如果在某一线程中呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
4、其他线程呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
5、最终如果计数器值为0时,则CyclicBarrier实例会将计数器值恢复,又可重用

  • Semaphore
1、初始化,并传入计数器值
2、向不同的线程传入Semaphore实例
3、如果在某一线程中呼叫acquire(),则Semaphore实例会将计数器值减1,如果计数器值为-1,则将计数器值置为0,此线程被挂起,直到计数器值大于1时,才往下执行
4、此线程需呼叫release(),使得计数器值+1,以便其他线程在计数器值为0时不受阻


CountDownLatch 例子:
public class Test {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                     Thread.sleep(3000);
                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         try {
             System.out.println("等待2个子线程执行完毕");
            latch.await();
            System.out.println("2个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}

结果:
线程Thread-0正在执行
线程Thread-1正在执行
等待2个子线程执行完毕
线程Thread-0执行完毕
线程Thread-1执行完毕
2个子线程已经执行完毕
继续执行主线程


CyclicBarrier例子:
public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程"+Thread.currentThread().getName());   
            }
        });
         
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务");
        }
    }
}

执行结果:
线程Thread-0正在写入数据
线程Thread-1正在写入数据
线程Thread-2正在写入数据
线程Thread-3正在写入数据
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
当前线程Thread-3
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务


Semaphore例子:
public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
     
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
         
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一个机器在生产");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"释放出机器");
                semaphore.release();           
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:
工人0占用一个机器在生产
工人1占用一个机器在生产
工人2占用一个机器在生产
工人4占用一个机器在生产
工人5占用一个机器在生产
工人0释放出机器
工人2释放出机器
工人3占用一个机器在生产
工人7占用一个机器在生产
工人4释放出机器
工人5释放出机器
工人1释放出机器
工人6占用一个机器在生产
工人3释放出机器
工人7释放出机器
工人6释放出机器

https://www.cnblogs.com/dolphin0520/p/3920397.html

https://juejin.im/post/5aeec3ebf265da0ba76fa327

posted @ 2019-09-24 10:18 paulwong 阅读(324) | 评论 (0)编辑 收藏

使用 Jenkins 部署 Spring Boot

https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247489278&idx=2&sn=a48342d706bfd1651e277e1c24e81e3e&chksm=ebf6ce81dc81479764d1e6ff7b207257a78d52bed5ef8c2f16c76f70660d1da9609167ed7bbb&mpshare=1&scene=1&srcid=&sharer_sharetime=1568861026830&sharer_shareid=24856bf403968a883e437b859be0a9b5&pass_ticket=qB9yWQbj%2FGo7PDZNogjBwishDCx5Suu%2BvBWnS1TpKmY%3D#rd

posted @ 2019-09-19 17:44 paulwong 阅读(324) | 评论 (0)编辑 收藏

CI/CD 资源

Continuous delivery tool landscape
http://www.jamesbowman.me/post/continuous-delivery-tool-landscape/

posted @ 2019-09-18 17:08 paulwong 阅读(373) | 评论 (0)编辑 收藏

ANSIBLE + SPRING BOOT

https://github.com/remyma/ansible-springboot

frontend+backend+jenkins+ansible
https://itnext.io/ci-cd-with-jenkins-and-ansible-f41ef2b33977

posted @ 2019-09-18 16:09 paulwong 阅读(407) | 评论 (0)编辑 收藏

Windows终端工具_MobaXterm

前言

  有人喜欢小而美的工具,有人喜欢大集成工具。这里推荐一款增强型的Windows终端工具MobaXterm,它提供所有重要的远程网络工具(SSH,X11,RDP,VNC,FTP,MOSH ......)和Unix命令(bash,ls,cat,sed,grep,awk,rsync等)。使用MobaXterm工具,可以替代SSH客户端工具(xshell、putty、securecrt等)、sftp/ftp工具(winscp、filezilla)、远程桌面访问工具(RDO等)等等,可以极大降低你windows系统上的软件安装数量。

MobaXterm使用体会&优点

  1、工具获取简单、免安装(绿色版)、免费(不用到处找license)、可个性化配置(字体、前景色、背景色、语法高亮等)。

  2、可以替代xshell、putty、securecrt等SSH客户端、winscp、filezilla等ftp传输工具、替代RDO远程桌面访问工具等。减少windows系统软件安装和资源占用。我的系统已经超负荷运转

  3、可以替代CMD窗口。CMD命令行字体太丑。。并且配置还麻烦。实在不想用。

  4、支持Unix/Linux常用命令使用。满足在windows上学习操作linux命令的需求以及利用linux命令快速处理文本文件。

  5、可以支持丰富的组件,减少部分软件的安装。如Cygwin。

  工具使用建议:每个人对工具使用的要求和场景不尽相同。需要了解MobaXterm特性可以进入MobaXterm官网或者阅读MobaXterm帮助手册(启动工具--> 菜单栏"help" -> "document")。

MobaXterm主要功能

  1、远程会话管理器:单个应用程序中的SSH,SFTP,telnet,VNC,Mosh,RDP连接

  2、Windows上的许多Unix/Linux命令:基本Cygwin命令(bash,grep,awk,sed,rsync,...)。

  3、丰富的组件和插件,可以自由选择。详情查看MobaXterm Plugins

  4、远程桌面:使用RDP,VNC或XDMCP在计算机上显示完整的远程桌面

  5、嵌入式Xserver:在Windows计算机上显示远程应用程序

  6、....

MobaXterm工具下载

  MobaXterm工具分为便携版(绿色免安装版,免费)和专业版(收费)。对于大部分开发测试人员,免费的绿色免安装版本就可以满足日常工作的需求。下载路径:MobaXterm

MobaXterm使用技巧

1、执行cmd命令快速切换执行DOS指令。可以执行exit退回原界面。

2、MobaXterm界面风格、主体、字体以及相关快捷方式设置。MobaXterm --> Settings --> Configuration

3、MobaXterm取消自动断开SSH会话。

posted @ 2019-09-11 13:19 paulwong 阅读(372) | 评论 (0)编辑 收藏

nohup命令不输出OUTPUT到实体文件

https://stackoverflow.com/questions/10408816/how-do-i-use-the-nohup-command-without-getting-nohup-out

posted @ 2019-09-06 10:15 paulwong 阅读(465) | 评论 (0)编辑 收藏

MONGODB拓展操作

MongoTemplate使用Cursor处理大数量的数据
https://blog.csdn.net/ClementAD/article/details/55210973

Spring Data MongoDB系列之三:数据库批量操作
https://blog.csdn.net/sinat_24044957/article/details/80646292

Distinct in Spring Data MongoDB
https://stackoverflow.com/questions/19203724/distinct-in-spring-data-mongodb

MONGODB SQL语句
http://www.runoob.com/mongodb/mongodb-indexing.html


posted @ 2019-09-03 15:52 paulwong 阅读(317) | 评论 (0)编辑 收藏

MAVEN中SCOPE为SYSTEM的JAR包如何打包?

https://stackoverflow.com/questions/2065928/maven-2-assembly-with-dependencies-jar-under-scope-system-not-included

在pom.xml中加入REPOSITRY:
<repositories>
  <repository>
    <id>my</id>
    <url>file://${basedir}/my-repo</url>
  </repository>
</repositories>

file://${basedir}/my-repo 中放JAR包的结构要和MAVEN库.m2中保持一致。

DEPENDENCY还是按正常的来,不加SYSTEM SCOPE:
<dependency>
  <groupId>sourceforge.jchart2d</groupId>
  <artifactId>jchart2d</artifactId>
  <version>3.1.0</version>
</dependency>


这里涉及到一个问题:如何将JAR比较优雅地放到file://${basedir}/my-repo 中,可以使用deploy,如果JAR包不是由MAVEN打出来的,命令如下:
mvn deploy:deploy-file 
-DgroupId={yourProject} \ 
-DartifactId={yourProject}\
-Dfile={yourFile}\ // jar 包路径
-Durl={URL} \// 私服URL
-DrepositoryId=releases\
-Dpackaging=jar\ // 指定格式,如果不写,一句 pom 文件中
-DpomFile=pom.xml // 指定该 jar 包的 pom 文件,如不指定,将生成一个默认的 pom——导致不可用

如果JAR包是由MAVEN打出来的,命令如下:
mvn deploy:deploy-file 
-Dfile={yourFile}\ // jar 包路径
-Durl=file://${basedir}/my-repo
-DpomFile=pom.xml // 指定该 jar 包的 pom 文件,如不指定,将生成一个默认的 pom——导致不可用

MAVEN DEPLOY PLUGIN:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-deploy-plugin</artifactId>
   <version>2.8.2</version>
   <executions>
      <execution>
         <id>default-cli</id>
         <phase>package</phase>
         <goals>
            <goal>deploy-file</goal>
         </goals>
         <configuration>
            <file>target/COMPOSANT-A-1.0.tar.gz</file>
            <repositoryId>nexus</repositoryId>
            <groupId>COMPOSANTS</groupId>
            <artifactId>COMPOSANT-A</artifactId>
            <version>1.0</version>
            <generatePom>false</generatePom>
            <packaging>tar.gz</packaging>
            <url>http://urlRepo:8080/nexus/content/repositories/snapshots</url>
         </configuration>
      </execution>
   </executions>
</plugin>

posted @ 2019-08-27 17:32 paulwong 阅读(1963) | 评论 (0)编辑 收藏

淘宝千万级并发分布式架构的14次演进

一、概述

本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。

二、基本概念

在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:

1)分布式

系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。

2)高可用

系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。

3)集群

一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。

在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。

4)负载均衡

请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的。

5)正向代理和反向代理

  • 系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;
  • 当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。

简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。

三、架构演进

单机架构

淘宝千万级并发分布式架构的14次演进

以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。

随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务。

第一次演进:Tomcat与数据库分开部署

淘宝千万级并发分布式架构的14次演进

Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

随着用户数的增长,并发读写数据库成为瓶颈。

第二次演进:引入本地缓存和分布式缓存

淘宝千万级并发分布式架构的14次演进

在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。

其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢。

第三次演进:引入反向代理实现负载均衡

淘宝千万级并发分布式架构的14次演进

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。

此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。

其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈。

第四次演进:数据库读写分离

淘宝千万级并发分布式架构的14次演进

把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。

其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能。

第五次演进:数据库按业务分库

淘宝千万级并发分布式架构的14次演进

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。

这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

随着用户数的增长,单机的写库会逐渐会达到性能瓶颈。

第六次演进:把大表拆分为小表

淘宝千万级并发分布式架构的14次演进

比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。

只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。

这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的。

如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等。

不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景。

这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回。

也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。

数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈。

第七次演进:使用LVS或F5来使多个Nginx负载均衡

淘宝千万级并发分布式架构的14次演进

由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。

图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。

由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat。

在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。

第八次演进:通过DNS轮询实现机房间的负载均衡

淘宝千万级并发分布式架构的14次演进

在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。

当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求。

第九次演进:引入NoSQL数据库和搜索引擎等技术

淘宝千万级并发分布式架构的14次演进

当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。

对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。

因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难。

第十次演进:大应用拆分为小应用

淘宝千万级并发分布式架构的14次演进

按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级。

第十一次演进:复用的功能抽离成微服务

淘宝千万级并发分布式架构的14次演进

如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务。

应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱。

第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

淘宝千万级并发分布式架构的14次演进

通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。

个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。

第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

淘宝千万级并发分布式架构的14次演进

目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。

Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。

使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低。

第十四次演进:以云平台承载系统

淘宝千万级并发分布式架构的14次演进

系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体。在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用。用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。在云平台中会涉及如下几个概念:

  • IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面;
  • PaaS:平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护;
  • SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。

至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。

四、 架构设计总结

架构的调整是否必须按照上述演变路径进行?

不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。

如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。

对于将要实施的系统,架构应该设计到什么程度?

对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。

服务端架构和大数据架构有什么区别?

所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称。

在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。

总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。

有没有一些架构设计的原则?

1)N+1设计

系统中的每个组件都应做到没有单点故障。

2)回滚设计

确保系统可以向前兼容,在系统升级时应能有办法回滚版本。

3)禁用设计

应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能。

4)监控设计

在设计阶段就要考虑监控的手段。

5)多活数据中心设计

若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用

6)采用成熟的技术

刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难。

7)资源隔离设计

应避免单一业务占用全部资源。

8)架构应能水平扩展

系统只有做到能水平扩展,才能有效避免瓶颈问题

9)非核心则购买

非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品。

10)使用商用硬件

商用硬件能有效降低硬件故障的机率。

11)快速迭代

系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险。

12)无状态设计

服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。

设计到此结束,其实,解决方案有很多,但是这个只是我采用的,觉得最轻便的一个。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 淘宝千万级并发分布式架构的14次演进

posted @ 2019-08-16 10:05 paulwong 阅读(441) | 评论 (0)编辑 收藏

Guide to the Most Important JVM Parameters

https://www.baeldung.com/jvm-parametersa

Optimising Your Minecraft: Jvm Arguments
https://xealgaming.net/threads/optimising-your-minecraft-jvm-arguments.4758/

posted @ 2019-08-01 16:55 paulwong 阅读(294) | 评论 (0)编辑 收藏

JVM内存配置

JVM内存主要分为两个部分,分别是PermanentSapce和HeapSpace。

PermantSpace主要负责存放加载的Class类级对象如class本身,method,field等反射对象,一般不用配置。

JVM的Heap区可以通过-X参数来设定。HeapSpace= {Old + NEW {= Eden , from, to } }

当一个URL被访问时,内存申请过程如下:

  1. JVM会试图为相关Java对象在Eden中初始化一块内存区域 
  2. 当Eden空间足够时,内存申请结束。否则到下一步 
  3. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区
  4.  Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区 
  5. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级) 
  6. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

Xms/Xmx:定义NEW+OLD段的总尺寸,ms为JVM启动时NEW+OLD的内存大小;mx为最大可占用的NEW+OLD内存大小。。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销; 

NewSize/MaxNewSize:定义单独NEW段的尺寸,NewSize为JVM启动时NEW的内存大小;MaxNewSize为最大可占用的NEW的内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销;

Xms/Xmx和NewSize/MaxNewSize定义好后,OLD区间也自然定义完毕了,即OLD区初始大小=(Xms-NewSize),OLD区最大可占用大小=(Xmx-MaxNewSize); 

PermSize/MaxPermSize:定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

posted @ 2019-08-01 16:44 paulwong 阅读(369) | 评论 (0)编辑 收藏

使用 Awaitility 测试异步代码

自动化工具 异步校验工具 awaitility 快速入门
https://testerhome.com/topics/7408


https://yanbin.blog/test-asyn-call-with-awaitility/

Introduction to Awaitlity
https://www.baeldung.com/awaitlity-testing




posted @ 2019-08-01 10:06 paulwong 阅读(609) | 评论 (0)编辑 收藏

Spring Batch JUnit test for multiple jobs


https://stackoverflow.com/questions/34217101/spring-batch-junit-test-for-multiple-jobs

@Configuration
public class TestBatchConfiguration implements MergedBeanDefinitionPostProcessor {

    @Autowired
    @Qualifier("JobA")
    private Job job;

    @Bean(name="jtestl")
    public JobLauncherTestUtils jobLauncherTestUtils() {
        JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils();
        jobLauncherTestUtils.setJob(job);
        return jobLauncherTestUtils;
    }

    /**
     * 
https://stackoverflow.com/questions/22416140/autowire-setter-override-with-java-config
     * This is needed to inject the correct job into JobLauncherTestUtils
     
*/
    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if(beanName.equals("jtestl")) {
            beanDefinition.getPropertyValues().add("job", getMyBeanFirstAImpl());
        }
    }

    private Object getMyBeanFirstAImpl() {
        return job;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

posted @ 2019-07-31 10:48 paulwong 阅读(610) | 评论 (0)编辑 收藏

Keep SSH session alive

sshd (the server) closes the connection if it doesn't hear anything from the client for a while. You can tell your client to send a sign-of-life signal to the server once in a while.

The configuration for this is in the file "~/.ssh/config", create it if the configuration file does not exist. To send the signal every four minutes (240 seconds) to the remote host, put the following in your "~/.ssh/config" file.

Host remotehost:     HostName remotehost.com     ServerAliveInterval 240 

This is what I have in my "~/.ssh/config":

To enable it for all hosts use:

Host * ServerAliveInterval 240 

Also make sure to run:

chmod 600 ~/.ssh/config 

because the config file must not be world-readable.

posted @ 2019-07-30 13:46 paulwong 阅读(293) | 评论 (0)编辑 收藏

publish over ssh 实现 Jenkins 远程部署

Jenkins远程部署,一开始没有任何头绪,想了很多方案. 因为两台机器都是windows系统,所以想到publish over cifs, 但是这个网上资料太少,貌似只能内网使用。又想到了Jenkins 分布式构建,但是Jenkins构建的代码和产物最后自动拷贝到主节点。而远程机器其实是客户方的机器,所以这个分布式构建并不适用。最后还是选定publish over ssh来实现远程部署。 
请注意:在进行远程部署操作前,先要确保客户机能ssh 登录到远程机器。如果不知道SSH怎么登陆,请参考http://blog.csdn.net/flyingshuai/article/details/72897692 
1. 安装publish over ssh 插件,安装很简单,在此不表。 
2. 在Jenkins系统设置里找到Publish over SSH模块 
3. 用户名/密码方式登录的,系统设置里设置如下: 
4. 如果是证书登录的,系统设置里设置如下: 
5. Job设置,点击增加构建后操作步骤,选择send build artifacts over ssh, 设置如下: 
6. 文件上传到远程服务器后,还有一些后续操作,比如,替换数据库配置文件。可以把bat命令写到一个批处理文件中,存到服务器上。Exec command填写批处理文件的绝对路径。如上图所示。
关于bat脚本: 
如果每次都需要替换同样的文件,用copy /y 是无条件覆盖,不会询问。而xcopy可以实现批量拷贝文件和文件夹。如果文件较多可用此命令 
注意脚本运行失败,构建也会显示蓝色成功图标,所以一定要打开控制台输出,看是否真的成功。
--------------------- 
作者:flyingshuai 
来源:CSDN 
原文:https://blog.csdn.net/flyingshuai/article/details/72898665 
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-07-25 09:33 paulwong 阅读(569) | 评论 (0)编辑 收藏

How do I clear my Jenkins/Hudson build history?

     摘要: 问题:I recently updated the configuration of one of my hudson builds. The build history is out of sync. Is there a way to clear my build history?Please and thank you回答1:If you click Manage Hudson / Relo...  阅读全文

posted @ 2019-07-24 16:18 paulwong 阅读(327) | 评论 (0)编辑 收藏

Springboot ActiveMQ jmsTemplate配置

@Configuration
@DependsOn(value="cachingConnectionFactory")
public class JmsTemplateConfiguration {

@Value("${wechat.sendmessage.queue}")
private String queueName;

@Value("${wechat.sendmessage.topic}")
private String topicName;

@Value("${spring.jms.pub-sub-domain}")
private boolean isPubSubDomain;


/**
 * 定义点对点队列
 * 
@return
 
*/
@Bean
public Queue queue() {
    return new ActiveMQQueue(queueName);
}



/**
 * 定义一个主题
 * 
@return
 
*/
@Bean
public Topic topic() {
    return new ActiveMQTopic(topicName);
}

private final ObjectProvider<DestinationResolver> destinationResolver;
private final ObjectProvider<MessageConverter> messageConverter;
private final CachingConnectionFactory cachingConnectionFactory;

@Autowired
public JmsTemplateConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
                                ObjectProvider<MessageConverter> messageConverter,
                                CachingConnectionFactory cachingConnectionFactory) {
    this.destinationResolver = destinationResolver;
    this.messageConverter = messageConverter;
    this.cachingConnectionFactory = cachingConnectionFactory;
}

/**
 * 配置队列生产者的JmsTemplate
 * 
@return JmsTemplate
 
*/
@Bean(name="jmsQueueTemplate")
public JmsTemplate jmsQueueTemplate() {
    //设置创建连接的工厂
    
//JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    
//优化连接工厂,这里应用缓存池 连接工厂就即可
    JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
    //设置默认消费topic
   
//jmsTemplate.setDefaultDestination(topic());
    
//设置P2P队列消息类型
    jmsTemplate.setPubSubDomain(isPubSubDomain);

    DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
    if (destinationResolver != null) {
        jmsTemplate.setDestinationResolver(destinationResolver);
    }
    MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
    if (messageConverter != null) {
        jmsTemplate.setMessageConverter(messageConverter);
    }
    //deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认false
    jmsTemplate.setExplicitQosEnabled(true);
    //DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
    
//定义持久化后节点挂掉以后,重启可以继续消费.
    jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
    //默认不开启事务
    System.out.println("默认是否开启事务:"+jmsTemplate.isSessionTransacted());
    //如果不启用事务,则会导致XA事务失效;
    
//作为生产者如果需要支持事务,则需要配置SessionTransacted为true
  
//jmsTemplate.setSessionTransacted(true);
    
//消息的应答方式,需要手动确认,此时SessionTransacted必须被设置为false,且为Session.CLIENT_ACKNOWLEDGE模式
    
//Session.AUTO_ACKNOWLEDGE  消息自动签收
    
//Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收
    
//Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送
    jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
    return jmsTemplate;
}

/**
 * 配置发布订阅生产者的JmsTemplate
 * 
@return JmsTemplate
 
*/
@Bean(name="jmsTopicTemplate")
public JmsTemplate jmsTopicTemplate() {
    //设置创建连接的工厂
   
//JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    
//优化连接工厂,这里应用缓存池 连接工厂就即可
    JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
    //设置默认消费topic
  
//jmsTemplate.setDefaultDestination(topic());
    
//设置发布订阅消息类型
    jmsTemplate.setPubSubDomain(isPubSubDomain);


    //deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认false
    jmsTemplate.setExplicitQosEnabled(true);
    //DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
    jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);

    //默认不开启事务
    System.out.println("是否开启事务"+jmsTemplate.isSessionTransacted());
    //如果session带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。
    
//jmsTemplate.setSessionTransacted(true);

    
//不带事务的session的签收方式,取决于session的配置。
    
//默认消息确认方式为1,即AUTO_ACKNOWLEDGE
    System.out.println("是否消息确认方式"+jmsTemplate.getSessionAcknowledgeMode());

    //消息的应答方式,需要手动确认,此时SessionTransacted必须被设置为false,且为Session.CLIENT_ACKNOWLEDGE模式
    
//Session.AUTO_ACKNOWLEDGE  消息自动签收
    
//Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收
    
//Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送
    jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);

    return jmsTemplate;
}

}

posted @ 2019-07-24 11:40 paulwong 阅读(2079) | 评论 (0)编辑 收藏

Enterprise Integration Patterns

Why Enterprise Integration Patterns?

Enterprise integration is too complex to be solved with a simple 'cookbook' approach. Instead, patterns can provide guidance by documenting the kind of experience that usually lives only in architects' heads: they are accepted solutions to recurring problems within a given context. Patterns are abstract enough to apply to most integration technologies, but specific enough to provide hands-on guidance to designers and architects. Patterns also provide a vocabulary for developers to efficiently describe their solution.

Patterns are not 'invented'; they are harvested from repeated use in practice. If you have built integration solutions, it is likely that you have used some of these patterns, maybe in slight variations and maybe calling them by a different name. The purpose of this site is not to "invent" new approaches, but to present a coherent collection of relevant and proven patterns, which in total form an integration pattern language.

Despite the 700+ pages, our book covers only a fraction of patterns (and the problems to be solved) in the integration space. The current patterns focus on Messaging, which forms the basis of most other integration patterns. We have started to harvest more patterns but are realizing (once again) how much work documenting these patterns really is. So please stay tuned.

Messaging Patterns

We have documented 65 messaging patterns, organized as follows:

Message Construct.
Message
Command Message
Document Message
Event Message
Request-Reply
Return Address
Correlation Identifier
Message Sequence
Message Expiration
Format Indicator
Message Routing
Pipes-and-Filters
Message Router
Content-based Router
Message Filter
Dynamic Router
Recipient List
Splitter
Aggregator
Resequencer
Composed Msg. Processor
Scatter-Gather
Routing Slip
Process Manager
Message Broker
Message
Transformation
Message Translator
Envelope Wrapper
Content Enricher
Content Filter
Claim Check
Normalizer
Canonical Data Model
Messaging Endpoints
Message Endpoint
Messaging Gateway
Messaging Mapper
Transactional Client
Polling Consumer
Event-driven Consumer
Competing Consumers
Message Dispatcher
Selective Consumer
Durable Subscriber
Idempotent Receiver
Service Activator
Messaging Channels
Message Channel
Point-to-Point Channel
Publish-Subscr. Channel
Datatype Channel
Invalid Message Channel
Dead Letter Channel
Guaranteed Delivery
Channel Adapter
Messaging Bridge
Message Bus
Systems Mgmt.
Control Bus
Detour
Wire Tap
Message History
Message Store
Smart Proxy
Test Message
Channel Purger


https://www.enterpriseintegrationpatterns.com/patterns/messaging/index.html

posted @ 2019-07-18 14:11 paulwong 阅读(338) | 评论 (0)编辑 收藏

SPRING BATCH & SPRING INTEGRATION TUTORIAL

Spring JMS Artemis Example 6 minute read

A detailed step-by-step tutorial on how to connect to Apache ActiveMQ Artemis using Spring JMS and Spring Boot.

Spring JMS Topic Example 5 minute read

A detailed step-by-step tutorial on how to publish/subscribe to a JMS topic using Spring JMS and Spring Boot.

Spring JMS Integration Example12 minute read

A detailed step-by-step tutorial on how to connect to an ActiveMQ JMS broker using Spring Integration and Spring Boot.

Spring JMS Listener Example 7 minute read

A detailed step-by-step tutorial on how a Spring JMS listener works in combination with Spring Boot.

Spring JMS JmsTemplate Example 7 minute read

A detailed step-by-step tutorial on how to use JmsTemplate in combination with Spring JMS and Spring Boot.

Spring JMS Message Converter Example5 minute read

A detailed step-by-step tutorial on how to implement a message converter using Spring JMS and Spring Boot.

Spring Batch Admin Example 11 minute read

A detailed step-by-step tutorial on how to use a Spring Boot admin UI to manage Spring Batch jobs.

Spring Batch Example 11 minute read

A detailed step-by-step tutorial on how to implement a Hello World Spring Batch job using Spring Boot.

posted @ 2019-07-18 13:21 paulwong 阅读(359) | 评论 (0)编辑 收藏

Spring Integration Java DSL

This time I decided to play a little bit with Spring Integration Java DSL. Which has been merged directly into Spring Integration Core 5.0, which is smart and obvious move because:

  • Everyone starting the new Spring projects based on Java Config uses that
  • SI Java DSL enables you to use new powerfull Java 8 features like Lambdas
  • You can build your flow using the Builder pattern based on IntegrationFlowBuilder

Let's take a look on the samples howto use that based on ActiveMQ JMS.


https://bitbucket.org/tomask79/spring-integration-java-dsl/src/master/

posted @ 2019-07-18 13:16 paulwong 阅读(404) | 评论 (0)编辑 收藏

SPRING BATCH remote chunking模式下可同时处理多文件

SPRING BATCH remote chunking模式下,如果要同一时间处理多个文件,按DEMO的默认配置,是会报错的,这是由于多个文件的处理的MASTER方,是用同一个QUEUE名,这样SLAVE中处理多个JOB INSTANCE时,会返回不同的JOB-INSTANCE-ID,导致报错。

这时需更改SPRING BATCH使用SPRING INTEGRATION的模式中的GATEWAY组件。

GATEWAY组件是工作在REQUEST/RESPONSE模式下,即发一个MESSAGE到某一QUEUE时,要从REPLY QUEUE等到CONSUMER返回结果时,才往下继续。

OUTBOUND GATEWAY:从某一CHANNEL获取MESSAGE,发往REQUEST QUEUE,从REPLY QUEUE等到CONSUMER返回结果,将此MESSAGE发往下一CHANNEL。

INBOUND GATEWAY:从某一QUEUE获取MESSAGE,发往某一REQUEST CHANNEL,从REPLY CHANNEL等到返回结果,将此MESSAGE发往下一QUEUE。

详情参见此文:https://blog.csdn.net/alexlau8/article/details/78056064

    <!-- Master jms -->
    <int:channel id="MasterRequestChannel">
        <int:dispatcher task-executor="RequestPublishExecutor"/>
    </int:channel>
    <task:executor id="RequestPublishExecutor" pool-size="5-10" queue-capacity="0"/>
<!--    <int-jms:outbound-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="RequestQueue" 
        channel="MasterRequestChannel"/> 
-->

    <int:channel id="MasterReplyChannel"/>
<!--    <int-jms:message-driven-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="ReplyQueue"
        channel="MasterReplyChannel"/> 
-->

    <int-jms:outbound-gateway
        
connection-factory="connectionFactory"
        correlation-key
="JMSCorrelationID"
        request-channel
="MasterRequestChannel"
        request-destination-name
="RequestQueue"
        receive-timeout
="30000"
        reply-channel
="MasterReplyChannel"
        reply-destination-name
="ReplyQueue"
        async
="true">
        <int-jms:reply-listener />
    </int-jms:outbound-gateway>

    <!-- Slave jms -->
    <int:channel id="SlaveRequestChannel"/>
<!--    <int-jms:message-driven-channel-adapter
        connection-factory="connectionFactory" 
        destination-name="RequestQueue"
        channel="SlaveRequestChannel"/> 
-->

    <int:channel id="SlaveReplyChannel"/>
<!--    <int-jms:outbound-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="ReplyQueue"
        channel="SlaveReplyChannel"/> 
-->

    <int-jms:inbound-gateway
        
connection-factory="connectionFactory"
        correlation-key
="JMSCorrelationID"
        request-channel
="SlaveRequestChannel"
        request-destination-name
="RequestQueue"
        reply-channel
="SlaveReplyChannel"
        default-reply-queue-name
="ReplyQueue"/>

MASTER配置
package com.paul.testspringbatch.config.master;

import javax.jms.ConnectionFactory;

import org.springframework.beans.factory.config.CustomScopeConfigurer;
//import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.SimpleThreadScope;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.jms.JmsOutboundGateway;

import com.paul.testspringbatch.common.constant.IntegrationConstant;

@Configuration
@EnableIntegration
@Profile("batch-master")
public class IntegrationMasterConfiguration {
    
//    @Value("${broker.url}")
//    private String brokerUrl;


//    @Bean
//    public ActiveMQConnectionFactory connectionFactory() {
//        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
//        connectionFactory.setBrokerURL(this.brokerUrl);
//        connectionFactory.setTrustAllPackages(true);
//        return connectionFactory;
//    }

    /*
     * Configure outbound flow (requests going to workers)
     
*/
    @Bean
//    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public DirectChannel requests() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow outboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(requests())
//                .handle(Jms.outboundAdapter(connectionFactory).destination(IntegrationConstant.MASTER_REQUEST_DESTINATION))
//                .get();
//    }
    
     @Bean
     public CustomScopeConfigurer customScopeConfigurer() {
         CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
         customScopeConfigurer.addScope("thread", new SimpleThreadScope());
         return customScopeConfigurer;
     }
     
//     @Bean
//     public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
//         return new BeanFactoryPostProcessor() {
//                
//             @Override
//             public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//                    beanFactory.registerScope("thread", new SimpleThreadScope());
//                }
//              };
//     }
    
    /*
     * Configure inbound flow (replies coming from workers)
     
*/
    @Bean
    @Scope(value = "thread"/* , proxyMode = ScopedProxyMode.NO */)
//    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public QueueChannel replies() {
        return new QueueChannel();
    }

//    @Bean
//    public IntegrationFlow inboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination(IntegrationConstant.MASTER_REPLY_DESTINATION))
//                .channel(replies())
//                .get();
//    }

    @Bean
    public JmsOutboundGateway jmsOutboundGateway(ConnectionFactory connectionFactory) {
        JmsOutboundGateway jmsOutboundGateway = new JmsOutboundGateway();
        jmsOutboundGateway.setConnectionFactory(connectionFactory);
        jmsOutboundGateway.setRequestDestinationName(IntegrationConstant.MASTER_REQUEST_DESTINATION);//2. send the message to this destination
        jmsOutboundGateway.setRequiresReply(true);
        jmsOutboundGateway.setCorrelationKey(IntegrationConstant.JMS_CORRELATION_KEY);//3. let the broker filter the message
        jmsOutboundGateway.setAsync(true);//must be async, so that JMS_CORRELATION_KEY work
        jmsOutboundGateway.setUseReplyContainer(true);
        jmsOutboundGateway.setReplyDestinationName(IntegrationConstant.MASTER_REPLY_DESTINATION);//4. waiting the response from this destination
        jmsOutboundGateway.setReceiveTimeout(30_000);
        return jmsOutboundGateway;
    }

    @Bean
    public IntegrationFlow jmsOutboundGatewayFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows
                        .from(requests())//1. receive message from this channel
                        .handle(jmsOutboundGateway(connectionFactory))
                        .channel(replies())//5. send back the response to this channel
                        .get();
    }

}


SLAVE配置:
package com.paul.testspringbatch.config.slave;

import javax.jms.ConnectionFactory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.jms.dsl.Jms;

import com.paul.testspringbatch.common.constant.IntegrationConstant;

@Configuration
@EnableIntegration
@Profile("batch-slave")
public class IntegrationSlaveConfiguration {
    

    /*
     * Configure inbound flow (requests coming from the master)
     
*/
    @Bean
    public DirectChannel requests() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow inboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
//                .channel(requests())
//                .get();
//    }

    /*
     * Configure outbound flow (replies going to the master)
     
*/
    @Bean
    public DirectChannel replies() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow outboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(replies())
//                .handle(Jms.outboundAdapter(connectionFactory).destination("replies"))
//                .get();
//    }

    @Bean
    public IntegrationFlow inboundGatewayFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows
                    .from(Jms
                            .inboundGateway(connectionFactory)
                            .destination(IntegrationConstant.SLAVE_HANDLE_MASTER_REQUEST_DESTINATION)//1. receive message from this channel.
                            .correlationKey(IntegrationConstant.JMS_CORRELATION_KEY)//2. let the broker filter the message
                            .requestChannel(requests())//3. send the message to this channel
                            .replyChannel(replies())//4. waitting the result from this channel
                            .defaultReplyQueueName(IntegrationConstant.SLAVE_RETURN_RESULT_DESTINATION)//5.send back the result to this destination to the master.
                            )
                    .get();
    }

}

posted @ 2019-07-16 14:38 paulwong 阅读(821) | 评论 (0)编辑 收藏

Build Messaging Between Ruby/Rails Applications with ActiveMQ

https://dev.to/kirillshevch/build-messaging-between-ruby-rails-applications-with-activemq-4fin

posted @ 2019-07-12 17:12 paulwong 阅读(331) | 评论 (0)编辑 收藏

STEP范围内的ROUTER

在SPRING BATCH中,通常ROUTER是针对STEP的,但是如果在一个STEP中有多个WRITER,每个WRITER是写不同文件的,因此需要一个STEP内的ROUTER,以便能ROUTE到不同的WRITER中。


https://gist.github.com/benas/bfe2be7386b99ce496425fac9ff35fb8

posted @ 2019-07-11 11:45 paulwong 阅读(310) | 评论 (0)编辑 收藏

动态改变SPRING BATCH 的 CHUNKSIZE

 在SPRING BATCH REMOTE CHUNKING的模式下:
SPRING BATCH 读文件时,是按一行一行来读取数据,再按CHUNKSIZE提交到REMOTE操作,有时要整合当前行和下几行,再决定CHUNKSIZE,以便相关的数据能在远程同一个PROCESSOR中按顺序进行处理,因为相关的数据被拆成几个CHUNK来处理的话,就有可能不按顺序来处理。这样就需要动态调整CHUNKSIZE。

参照如下:
https://stackoverflow.com/questions/37390602/spring-batch-custom-completion-policy-for-dynamic-chunk-size

并结合SingleItemPeekableItemReader(装饰者,允许查看下一条数据,真正的操作委托给代理)。

posted @ 2019-07-02 11:13 paulwong 阅读(1011) | 评论 (0)编辑 收藏

RABBITMQ资源

消息队列之 RabbitMQ
https://www.jianshu.com/p/79ca08116d57

Spring Boot 中使用 RabbitMQ
https://juejin.im/post/59f194e06fb9a0451329ec53




posted @ 2019-06-28 10:24 paulwong 阅读(331) | 评论 (0)编辑 收藏

How to implement JMS ReplyTo using SpringBoot

Request-Response is a message-exchange-pattern. In some cases, a message producer may want the consumers to reply to a message. The JMSReplyTo header indicates which destination, if any, a JMS consumer should reply to. The JMSReplyTo header is set explicitly by the JMS client; its contents will be a javax.jms.Destination object (either Topic or Queue).

In some cases, the JMS client will want the message consumers to reply to a temporary topic or queue set up by the JMS client. When a JMS message consumer receives a message that includes a JMSReplyTo destination, it can reply using that destination. A JMS consumer is not required to send a reply, but in some JMS applications, clients are programmed to do so.

For simplicity, this pattern is typically implemented in a purely synchronous fashion, as in web service calls over HTTP, which holds a connection open and waits until the response is delivered or the timeout period expires. However, request–response may also be implemented asynchronously, with a response being returned at some unknown later time.

For more information, check here.

Now, let’s jump into the code. In Spring, there are 2 ways to implement this (at least I know of).

  1. Using JMSTemplate
  2. Using Spring Integration

For demo purpose, I used ActiveMQ. However, you can implement this in other messaging systems like IBM MQ, Rabbit MQ, Tibco EMS, etc. In this demo, I send an ObjectMessage of type Order and reply with a Shipment object.

Using JMSTemplate

  1. First, we include the required dependencies. Replace the activemq dependency with your messaging system’s jars if not using ActiveMQ

     <dependencies>
         
    <dependency>
             
    <groupId>org.springframework.boot</groupId>
             
    <artifactId>spring-boot-starter-activemq</artifactId>
         
    </dependency>
         
    <dependency>
             
    <groupId>org.apache.activemq.tooling</groupId>
             
    <artifactId>activemq-junit</artifactId>
             
    <version>${activemq.version}</version>
             
    <scope>test</scope>
         
    </dependency>
         
    <dependency>
             
    <groupId>org.springframework.boot</groupId>
             
    <artifactId>spring-boot-starter-test</artifactId>
             
    <scope>test</scope>
         
    </dependency>
     
    </dependencies>
  2. Using the default spring.activemq. properties to configure the application with the ActiveMQ. However, you can do this inside a @Configuration class as well.

     spring:
       activemq:
         broker-url: tcp://localhost:
    61616
         non-blocking-redelivery: true
         packages:
           trust-all: true    
  3. Note in the above configuration spring.activemq.packages.trust-all can be changed to spring.activemq.packages.trusted with the appropriate packages.
  4. Now spring will do it’s magic and inject all the required Beans as usual :) However, in our code, we need to EnableJms

    import org.springframework.context.annotation.Configuration;
     
    import org.springframework.jms.annotation.EnableJms;

     @EnableJms
     @Configuration
     
    public class ActiveMQConfig {

         
    public static final String ORDER_QUEUE = "order-queue";
         
    public static final String ORDER_REPLY_2_QUEUE = "order-reply-2-queue";

     }
  5. First, we will configure the Producer

     @Slf4j
     @Service
     
    public class Producer {

         @Autowired
         JmsMessagingTemplate jmsMessagingTemplate;

         @Autowired
         JmsTemplate jmsTemplate;
          private Session session;

          @PostConstruct
           public void init(){
             jmsTemplate.setReceiveTimeout(1000L);
             jmsMessagingTemplate.setJmsTemplate(jmsTemplate);

             session = jmsMessagingTemplate.getConnectionFactory().createConnection()
                     .createSession(false, Session.AUTO_ACKNOWLEDGE);
           }

         
    public Shipment sendWithReply(Order order) throws JMSException {


             ObjectMessage objectMessage 
    = session.createObjectMessage(order);

             objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
             objectMessage.setJMSReplyTo(
    new ActiveMQQueue(ORDER_REPLY_2_QUEUE));
             objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
             objectMessage.setJMSExpiration(
    1000L);
             objectMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);

             
    return jmsMessagingTemplate.convertSendAndReceive(new ActiveMQQueue(ORDER_QUEUE),
                     objectMessage, Shipment.
    class); //this operation seems to be blocking + sync
         }
     }
  6. Note in the above code that, JmsMessagingTemplate is used instead of JmsTemplatebecause, we are interested in the method convertSendAndReceive. As seen in the method signature, it waits to receive the Shipment object from the consumer.
  7. Next, we can see the Receiver

     @Component
     
    public class Receiver implements SessionAwareMessageListener<Message> {

         @Override
         @JmsListener(destination 
    = ORDER_QUEUE)
         
    public void onMessage(Message message, Session session) throws JMSException {
             Order order 
    = (Order) ((ActiveMQObjectMessage) message).getObject();
             Shipment shipment 
    = new Shipment(order.getId(), UUID.randomUUID().toString());

             
    // done handling the request, now create a response message
             final ObjectMessage responseMessage = new ActiveMQObjectMessage();
             responseMessage.setJMSCorrelationID(message.getJMSCorrelationID());
             responseMessage.setObject(shipment);

             
    // Message sent back to the replyTo address of the income message.
             final MessageProducer producer = session.createProducer(message.getJMSReplyTo());
             producer.send(responseMessage);
         }
     }
  8. Using the javax.jms.Session the javax.jms.MessageProducer is created and used to send the reply message to the JMSReplyTo queue. In real life, this receiver could be a different application altogether.

Using Spring Integration

  1. First, we include the required dependencies in addition to the above dependencies

     <dependency>
       
    <groupId>org.springframework.integration</groupId>
       
    <artifactId>spring-integration-jms</artifactId>
     
    </dependency>
  2. Using the default spring.activemq. properties to configure the application with the ActiveMQ. However, you can do this inside a @Configuration class as well.

     spring:
       activemq
    :
         broker
    -url: tcp://localhost:61616
         non
    -blocking-redelivery: true
         packages
    :
           trust
    -all: true   
  3. Note in the above configuration spring.activemq.packages.trust-all can be changed to spring.activemq.packages.trusted with the appropriate packages.
  4. Next we create the required Beans for the Spring Integration.

     @EnableIntegration
     @IntegrationComponentScan
     @Configuration
     
    public class ActiveMQConfig {

         
    public static final String ORDER_QUEUE = "order-queue";
         
    public static final String ORDER_REPLY_2_QUEUE = "order-reply-2-queue";

         @Bean
         
    public MessageConverter messageConverter() {
             MappingJackson2MessageConverter converter 
    = new MappingJackson2MessageConverter();
             converter.setTargetType(MessageType.TEXT);
             converter.setTypeIdPropertyName(
    "_type");
             
    return converter;
         }

         @Bean
         
    public MessageChannel requests() {
             
    return new DirectChannel();
         }

         @Bean
         @ServiceActivator(inputChannel 
    = "requests")
         
    public JmsOutboundGateway jmsGateway(ActiveMQConnectionFactory activeMQConnectionFactory) {
             JmsOutboundGateway gateway 
    = new JmsOutboundGateway();
             gateway.setConnectionFactory(activeMQConnectionFactory);
             gateway.setRequestDestinationName(ORDER_QUEUE);
             gateway.setReplyDestinationName(ORDER_REPLY_2_QUEUE);
             gateway.setCorrelationKey(
    "JMSCorrelationID");
             gateway.setSendTimeout(
    100L);
             gateway.setReceiveTimeout(
    100L);
             
    return gateway;
         }

         @Autowired
         Receiver receiver;

         @Bean
         
    public DefaultMessageListenerContainer responder(ActiveMQConnectionFactory activeMQConnectionFactory) {
             DefaultMessageListenerContainer container 
    = new DefaultMessageListenerContainer();
             container.setConnectionFactory(activeMQConnectionFactory);
             container.setDestinationName(ORDER_QUEUE);
             MessageListenerAdapter adapter 
    = new MessageListenerAdapter(new Object() {

                 @SuppressWarnings(
    "unused")
                 
    public Shipment handleMessage(Order order) {
                     
    return receiver.receiveMessage(order);
                 }

             });
             container.setMessageListener(adapter);
             
    return container;
         }
     }
  5. Next, we will configure the MessagingGateway

     @MessagingGateway(defaultRequestChannel = "requests")
     
    public interface ClientGateway {
         Shipment sendAndReceive(Order order);
     }
  6. We then Autowire this gateway in our Component class when we want to send and receive the message. A sample is shown below.

     @Slf4j
     @Component
     
    public class Receiver {
         
    public Shipment receiveMessage(@Payload Order order) {
             Shipment shipment 
    = new Shipment(order.getId(), UUID.randomUUID().toString());
             
    return shipment;
         }
     }
  7. Next we configure the Componen to process the Order message. After successful execution, this component will send the Shipment message to the JMSReplyTo queue. In real life, this receiver could be a different application altogether.

For those, who just want to clone the code, head out to aniruthmp/jms

Written on June 5, 2018
https://aniruthmp.github.io/Spring-JMS-request-response/

posted @ 2019-06-27 09:20 paulwong 阅读(440) | 评论 (0)编辑 收藏

ACTIVE MQ高级特性

https://blog.51cto.com/1754966750/category17.html

posted @ 2019-06-26 14:13 paulwong 阅读(314) | 评论 (0)编辑 收藏

JMS消息消费的顺序性

现有的系统架构都是分布式的。有多个消息的发送者和多个消息的消费者。例如订单创建消息和订单支付消息,我们需要保证先消费订单创建消息,然后消费订单支付消息。

如何解决MQ消息消费顺序问题
https://segmentfault.com/a/1190000014512075

jms-如何保证消息的顺序
https://leokongwq.github.io/2017/01/23/jms-message-order.html







posted @ 2019-06-24 17:42 paulwong 阅读(419) | 评论 (0)编辑 收藏

Multiple MongoDB connectors with Spring Boot

http://blog.marcosbarbero.com/multiple-mongodb-connectors-in-spring-boot/

https://github.com/yinjihuan/spring-boot-starter-mongodb-pool

https://github.com/lish1le/mongodb-plus

posted @ 2019-06-20 15:12 paulwong 阅读(323) | 评论 (0)编辑 收藏

NFS Server 架設

  1. 安裝 nfs-utils 套件。
    [root@kvm5 ~]# yum install -y nfs-utils
  2. 建立 NFS 分享目錄。
    [root@kvm5 ~]# mkdir /public /protected
  3. 修改 NFS 分享目錄的 SELinux 檔案 context。
    [root@kvm5 ~]# semanage fcontext -a -t public_content_t "/public(/.*)?"
    [root@kvm5 ~]# semanage fcontext -a -t public_content_t "/protected(/.*)?"
    [root@kvm5 ~]# restorecon -Rv /public /protected
  4. 考試時不用自行產生 kerberos keytab,只要依照指定的位置下載,存放在目錄 /etc/ 下,且檔名必須為 krb5.keytab。
    [root@kvm5 ~]# wget http://deyu.wang/kvm5.keytab -O /etc/krb5.keytab 
  5. kerberos keytab 的驗證跟時間有關,server 與 client 都必須校時。
    [root@kvm5 ~]# date
    Sun Jan  7 14:50:04 CST 2018
    [root@kvm5 ~]# chronyc -a makestep
    200 OK
    200 OK
    [root@kvm5 ~]# date
    Mon Nov 20 15:53:22 CST 2017
  6. 在 /protected 下建立次目錄 restricted,並將其擁有者設定為 deyu3,讓 deyu3 可以寫入資料。
    [root@kvm5 ~]# mkdir -p  /protected/restricted 
    [root@kvm5 ~]# chown deyu3 /protected/restricted 
  7. 編輯設定檔 /etc/exports,分享 /protected 及 /public 兩個目錄給網域 192.168.122.0/24。
    [root@kvm5 ~]# echo '/protected 192.168.122.0/24(rw,sync,sec=krb5p)' > /etc/exports
    [root@kvm5 ~]# echo '/public 192.168.122.0/24(ro,sync)' >> /etc/exports
    [root@kvm5 ~]# vim /etc/exports
    [root@kvm5 ~]# cat /etc/exports
    /protected 192.168.122.0/24(rw,sync,sec=krb5p)
    /public 192.168.122.0/24(ro,sync)

  8. NFS 掛載參數說明如下,詳細說明請參考 man 5 nfs 手冊。
    1. rw:read-write,可讀寫的權限;
    2. ro:read-only,唯讀的權限;
    3. sec=mode:安全認證模式;
      1. sec=sys 預設,使用本地 UNIX UIDs 及 GIDs 進行身份認證。
      2. sec=krb5 使用 Kerberos V5 取代本地 UNIX UIDs 及 GIDs 進行身份認證。
      3. sec=krb5i 使用 Kerberos V5 進行身份認證,資料完整性檢查,以防止數據被篡改。
      4. sec=krb5p 使用 Kerberos V5 進行身份認證,資料完整性檢查及 NFS 傳輸加密,以防止數據被篡改,這是最安全的方式。
    4. sync:資料同步寫入到記憶體與硬碟當中;
    [root@kvm5 ~]# man 5 nfs 
  9. 設定使用 4.2 版本,以匯出分享 SELinux context。無適合的版本 client 端掛載時會出現 mount.nfs: Protocol not supported 的訊息。
    [root@kvm5 ~]# vim /etc/sysconfig/nfs  sed -i 's/^\(RPCNFSDARGS=\).*$/\1\"-V 4.2\"/' /etc/sysconfig/nfs 
    [root@kvm5 ~]# grep ^RPCNFSDARGS /etc/sysconfig/nfs  RPCNFSDARGS="-V 4.2" 
  10. 設定開機啟動 nfs 服務,NFS server 端的服務為 nfs-server 及 nfs-secure-server,本版本只要啟動 nfs-server 就同時啟動 nfs-secure-server,而且使用 tab 鍵也不會出現 nfs-secure-server 服務,但有些版本則是兩者分開,必須確認是不是兩種服務都啟動。
    [root@kvm5 ~]# systemctl enable nfs-server.service nfs-secure-server.service 
  11. 啟動 nfs 服務
    [root@kvm5 ~]# systemctl start nfs-server.service nfs-secure-server.service 
  12. 查看目前啟動的 nfs 版本,因 server 指定使用 4.2,若出現 -4.2 表示 nfs server 沒有成功啟動。
    [root@kvm5 ~]# cat /proc/fs/nfsd/versions -2 +3 +4 +4.1 +4.2 
  13. 要確定 nfs-secure-server nfs-server 服務都正常運作。
    [root@kvm5 ~]# systemctl status nfs-secure-server.service nfs-server.service 
    nfs-secure-server.service - Secure NFS Server
       Loaded
    : loaded (/usr/lib/systemd/system/nfs-secure-server.service; enabled)
       Active
    : active (running) since Mon 2015-09-21 20:04:10 CST; 8s ago
      Process
    : 3075 ExecStart=/usr/sbin/rpc.svcgssd $RPCSVCGSSDARGS (code=exited, status=0/SUCCESS)
     Main PID
    : 3077 (rpc.svcgssd)
       CGroup
    : /system.slice/nfs-secure-server.service
               └─
    3077 /usr/sbin/rpc.svcgssd

    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Starting Secure NFS Server
    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Started Secure NFS Server.

    nfs
    -server.service - NFS Server
       Loaded
    : loaded (/usr/lib/systemd/system/nfs-server.service; enabled)
       Active
    : active (exited) since Mon 2015-09-21 20:04:10 CST; 8s ago
      Process
    : 3078 ExecStopPost=/usr/sbin/exportfs -f (code=exited, status=0/SUCCESS)
      Process
    : 3076 ExecStop=/usr/sbin/rpc.nfsd 0 (code=exited, status=0/SUCCESS)
      Process
    : 3087 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS $RPCNFSDCOUNT (code=exited, status=0/SUCCESS)
      Process
    : 3084 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
      Process
    : 3083 ExecStartPre=/usr/libexec/nfs-utils/scripts/nfs-server.preconfig (code=exited, status=0/SUCCESS)
     Main PID
    : 3087 (code=exited, status=0/SUCCESS)
       CGroup
    : /system.slice/nfs-server.service

    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Starting NFS Server
    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Started NFS Server.
  14. 建議不論是否 TAB 有沒有出現提示,都同時啟動這兩個服務。CentOS 安裝版本 nfs-utils-1.3.0-8.el7.x86_64 啟動 nfs-secure-server 出現錯誤訊息,請執行 yum downgrade nfs-utils 換成 nfs-utils-1.3.0-0.el7.x86_64 套件。
    [root@kvm5 ~]# rpm -qa | grep nfs-utils
    nfs-utils-1.3.0-8.el7.x86_64
    [root
    @kvm5 ~]# yum downgrade nfs-utils -y
    [root@kvm5 ~]# rpm -qa | grep nfs-utils
    nfs-utils-1.3.0-0.el7.x86_64
  15. 再重新啟動 nfs 服務,並查看是否正常運作。
    [root@kvm5 ~]# systemctl restart nfs-server.service nfs-secure-server.service 
  16. 輸出所有設定的 nfs 分享目錄。
    [root@kvm5 ~]# exportfs -arv
    exporting 192.168.122.0/24:/public
    exporting 
    192.168.122.0/24:/protected

posted @ 2019-06-18 09:08 paulwong 阅读(381) | 评论 (0)编辑 收藏

centos7 mysql数据库安装和配置


https://www.cnblogs.com/starof/p/4680083.html 

https://www.daniloaz.com/en/how-to-create-a-user-in-mysql-mariadb-and-grant-permissions-on-a-specific-database/

https://huongdanjava.com/mysql-2

http://www.runoob.com/mysql/mysql-install.html

posted @ 2019-06-14 10:24 paulwong 阅读(347) | 评论 (0)编辑 收藏

SPRING BOOT 打包部署指南


https://segmentfault.com/a/1190000017386408

posted @ 2019-06-13 15:22 paulwong 阅读(313) | 评论 (0)编辑 收藏

Spring batch 的高级特性--监听,异常处理,事务


https://my.oschina.net/u/2600078/blog/909346

posted @ 2019-06-12 17:03 paulwong 阅读(655) | 评论 (0)编辑 收藏

LINUX安装NFS


https://qizhanming.com/blog/2018/08/08/how-to-install-nfs-on-centos-7

How to fix "mountd: refused mount request: unmatched host"
https://www.golinuxhub.com/2016/09/how-to-fix-mountd-refused-mount-request.html

nfs设置固定端口并添加防火墙规则
https://www.centos.bz/2017/12/nfs%E8%AE%BE%E7%BD%AE%E5%9B%BA%E5%AE%9A%E7%AB%AF%E5%8F%A3%E5%B9%B6%E6%B7%BB%E5%8A%A0%E9%98%B2%E7%81%AB%E5%A2%99%E8%A7%84%E5%88%99/

a
NFS Server 架設

https://dywang.csie.cyut.edu.tw/dywang/rhce7/node60.html

posted @ 2019-06-12 15:24 paulwong 阅读(393) | 评论 (0)编辑 收藏

zip4j

缩解压ZIP之Zip4j
https://rensanning.iteye.com/blog/1836727

posted @ 2019-06-10 16:47 paulwong 阅读(332) | 评论 (0)编辑 收藏

Message Processing With Spring Integration

Full demo
https://dzone.com/articles/message-processing-spring

posted @ 2019-06-05 11:08 paulwong 阅读(330) | 评论 (0)编辑 收藏

Spring integration 基本概念

1.spring integration 's architecture

主要提供两个功能:

在系统内提供实现轻量级、事件驱动交互行为的框架

在系统间提供一种基于适配器的平台,以支持灵活的系统间交互

2.spring integration对于企业集成模式的支持

2.1Message:一个信息的单元,通常有消息头(header)和消息内容(payload)组成

2.2Message channel:消息处理节点之间的连接,负责将Message从生产者传输到消费者。

    根据消费者的多少,可分为point to point和publish-subscribe两种


    根据消息传输方式的不同,分为同步和异步两种

2.3Message Endpoint:消息处理节点,消息从节点进入通道,也是从节点离开通道

几个常见的Message EndPoint:

CHANNEL ADAPTER,用于连接该适配器的特点是单向消息流的,要么是消息通过该适配器进入通道,要么是消息通过该适配器离开通道


MESSAGING GATEWAY,处理的消息流和Channel Adapter不同,不是单向的,即有进入该节点的消息,也会从该节点发出消息。



SERVICE ACTIVATOR,该节点调用服务来处理输入的消息,并将服务返回的数据发送到输出通道。在spring integration中,调用的方法被限定为本地方法调用。


ROUTER,路由器,将输入的消息路由到某个输出通道中


SPLITTER,将输入的消息拆分成子消息


AGGREGATOR,将输入的多个消息合并为一个消息


3.观看书中例子hello-world思考

测试gateway时,下面代码向通道names内放入消息world?


然后service-activator从names通道中获得消息world,调用方法sayHello返回值到给gateway?

解释:gateway有一个service-interface的属性,这个属性指向一个interface。当我们用一个接口声明一个gateway时,spring integration会自动帮我们生成该接口的代理类,这样当我们往gateway发送消息时,spring integration会通过代理类把消息转发到default-request-channel中去



作者:马国标
链接:https://www.jianshu.com/p/bf1643539f99
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

posted @ 2019-06-05 10:36 paulwong 阅读(873) | 评论 (0)编辑 收藏

如何在SPRING INTEGRATION中使用事务

File Polling using the Spring Integration DSL
http://porterhead.blogspot.com/2016/07/file-polling-using-spring-integration.html

https://github.com/iainporter/spring-file-poller



Transaction Support in Spring Integration
https://www.baeldung.com/spring-integration-transaction

posted @ 2019-06-04 14:19 paulwong 阅读(419) | 评论 (0)编辑 收藏

SpringBoot使用MongoDB异常问题

https://www.cnblogs.com/linzhanfly/p/9674778.html

posted @ 2019-05-29 16:58 paulwong 阅读(422) | 评论 (0)编辑 收藏

MONGODB去除_class字段

加上此配置:

MongodbConfiguration.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
 
@Configuration
public class AppMongoConfig {
  @Autowired private MongoDbFactory mongoDbFactory;
 
  @Autowired private MongoMappingContext mongoMappingContext;
 
  @Bean
  public MappingMongoConverter mappingMongoConverter() {
 
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));
 
    return converter;
  }
}

posted @ 2019-05-29 14:18 paulwong 阅读(1202) | 评论 (0)编辑 收藏

JAVA 8 TIME

Java 8新特性(四):新的时间和日期API
https://lw900925.github.io/java/java8-newtime-api.html
 

posted @ 2019-05-09 10:15 paulwong 阅读(346) | 评论 (0)编辑 收藏

SPRING BOOT BATCH资源

 Spring Boot下Spring Batch入门实例
https://www.jianshu.com/p/305192ea4cb1
 

SPRING BATCH + QUARTZ
https://examples.javacodegeeks.com/enterprise-java/spring/batch/quartz-spring-batch-example/
https://blog.kingbbode.com/posts/spring-batch-quartz

Spring Batch Tutorial: Batch Processing Made Easy with Spring
https://www.toptal.com/spring/spring-batch-tutorial

posted @ 2019-05-07 17:07 paulwong 阅读(360) | 评论 (0)编辑 收藏

以流的方式解释巨大JSON文件

https://www.ngdata.com/parsing-a-large-json-file-efficiently-and-easily/


https://sites.google.com/site/gson/streaming

http://www.acuriousanimal.com/2015/10/23/reading-json-file-in-stream-mode-with-gson.html

    public static void main(String [] args) throws IOException {
        String filePath = "C:big-data.json";
        FileInputStream in = new FileInputStream(new File(filePath));
        JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
        Gson gson = new GsonBuilder().create();
//        reader.beginObject();
//        reader.nextName();
        reader.beginObject();//跳过"{"
        while (reader.hasNext()) {
            // Read data into object model
            JsonToken jsonToken = reader.peek();
            if(jsonToken.equals(JsonToken.NAME)) {
                String name = reader.nextName();
                if(name.equalsIgnoreCase("SUMMARY")) {
//                    reader.beginObject();
                    Summary summary = gson.fromJson(reader, Summary.class);
                    logger.info(summary.toString());
                    break;
//                    reader.endObject();//跳过"}"
                }
            } /*else if(jsonToken.equals(JsonToken.BEGIN_OBJECT)) {
                reader.beginObject();
            } else if(jsonToken.equals(JsonToken.STRING)) {
                logger.info(reader.nextString());
            } else if(jsonToken.equals(JsonToken.NUMBER)) {
                logger.info(reader.nextInt() + "");
            } else if(jsonToken.equals(JsonToken.END_OBJECT)) {
                reader.endObject();
            } 
*/
//            Summary summary = gson.fromJson(reader, Summary.class);
//            break;
        }
        reader.close();
    }

posted @ 2019-05-03 15:22 paulwong 阅读(510) | 评论 (0)编辑 收藏

定时任务管理系统资源

SpringBoot 整合 Quartz 实现定时任务管理模块
https://juejin.im/post/5a7157f56fb9a01cb049a158

posted @ 2019-04-25 17:03 paulwong 阅读(346) | 评论 (0)编辑 收藏

2018预测

https://www.oschina.net/news/91924/10-open-source-technology-trends-2018 http://www.iteye.com/news/32843

posted @ 2018-01-15 10:51 paulwong 阅读(595) | 评论 (0)编辑 收藏

JAX-RS2资源

JAX-RS 2比JAX-RS 1增加了过滤器、拦截器、异步处理等特性。@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

JAXRSClientSpringBoot
http://cxf.apache.org/docs/jaxrsclientspringboot.html

CXFSpringBoot
http://cxf.apache.org/docs/springboot.html

Restfull Webservice书源码
https://github.com/feuyeux

JAX-RS 2.0 REST客户端编程实例
http://www.importnew.com/8939.html

本人的DEMO
https://git.oschina.net/paulwong/test-cxf

posted @ 2017-09-24 17:19 paulwong 阅读(1174) | 评论 (0)编辑 收藏

SPRING CLOUD JWT资源


https://github.com/thomas-kendall/trivia-microservices


一个Spring Boot, JWT,AugularJS接口安全验证的简单例子
http://blog.csdn.net/offbye/article/details/47607711



https://github.com/tuanngda/spring-boot-oauth2-demo


Spring Oauth2 with JWT Sample
http://www.tuicool.com/articles/EjUFZj7


http://stackoverflow.com/questions/38156213/spring-cloud-zuul-jwt-for-value-reference-tokens

posted @ 2017-03-24 23:11 paulwong 阅读(672) | 评论 (0)编辑 收藏

Spring Boot Admin资源

Spring Boot Admin 的使用
http://blog.csdn.net/kinginblue/article/details/52132113

posted @ 2017-03-21 22:10 paulwong 阅读(503) | 评论 (0)编辑 收藏

DDD领域驱动设计 - 设计文档模板

设计文档模板:

  1. 系统背景和定位
  2. 业务需求描述
  3. 领域语言整理,主要是整理领域中的各种术语的定义,名词解释
  4. 领域划分(分析出子域、核心域、支撑域)
  5. 系统用例图
  6. 每个子域的领域模型设计(实体、值对象、聚合、领域事件,需要注意的是:领域模型是需要抽象的,要分析业务本质,而不是简单的直接对需求进行建模)
  7. 领域模型详细说明(如为什么这样设计的原因、模型内对象的关系、各种业务规则、数据一致性规则等)
  8. 领域服务、仓储、工厂设计
  9. Saga业务流程设计
  10. 关键聚合根的状态流转图
  11. 场景走查(讲述如何通过领域模型、领域服务、仓储、Saga流程等完成系统用例以及关键业务流程的)
  12. 架构设计(如传统三层架构、经典四层架构、CQRS/ES架构)

一些其他的思考:

  • 去除一切花俏的建模技巧,我觉得最重要的方向就是去努力分析问题和事物的本质,针对这个本质进行领域建模。这个领域建模,最主要的还是锻炼的人的事物抽象能力。10个人,建出来的领域模型都不同。本质原因就是大家对同一个问题的理解不同,对事物的本质的理解不同。虽然最终都能解决当前的问题,但是对适应未来需求变化的能力却是不同。
  • 所以,我们要把时间花在多理解业务上,让自己成为领域专家,只有这样,才能充分理解业务。多理解一点业务,你才能更好的抽象出业务本质背后的领域模型。很少有人能做到很快理解业务,并很快针对业务设计出正确的领域模型,至少我是不行。
  • 领域建模需要时间,是一个迭代的过程,人无完人。而时间很多时候也不会很充足,所以,不太可能一步到位把领域设计做的很完美。我们在整体项目规划的时候可能会有个大的架构设计、业务大图(边界思维),但是不可能达到领域设计的粒度,只能是一期一期的完善,到最后可能才会有完整的上面的目录内容。每一期都需要考虑支持的场景约束、上下文、系统边界、持续集成的相关设计。设计product, not project。

posted @ 2017-02-19 12:14 paulwong 阅读(738) | 评论 (0)编辑 收藏

浅谈我对DDD领域驱动设计的理解(转)

从遇到问题开始

当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决。

比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品。所以,自然而然就想到要做一个普通电商系统,用于实现在线销售自己企业产品的目的。

再比如,我是一家互联网公司,公司有很多系统对外提供服务,面向很多客户端设备。但是最近由于各种原因,导致服务经常出故障。所以,我们希望通过各种措施提高服务的质量和稳定性。其中的一个措施就是希望能做一个灰度发布的平台,这个平台可以提供灰度发布的服务。然后,当某个业务系统做了一些修改并需要发布时,可以使用我们的灰度发布平台来非常方便的实现灰度发布的功能。比如在灰度发布平台上方便的定制允许哪些特定的客户端才会访问新服务,哪些客户端继续使用老服务。灰度发布平台可以提供各种灰度的策略。有了这样的灰度发布机制,那即便系统的新逻辑有什么问题,受影响的面也不会很大,在可控范围内。所以,如果公司里的所有对外提供服务的系统都接入了灰度平台,那这些系统的发布环节就可以更加有保障了。

总之,我们做任何一个软件系统,都是有原因的,否则就没必要做这个系统,而这个原因就是我们遇到的问题。所以,通过问题,我们就知道了我们需要一个什么样的系统,这个系统解决什么样的问题。最后,我们就很自然的得出了一个目标,即知道了自己要什么。比如我要做一个论坛、一个博客系统、一个电商平台、一个灰度发布系统、一个IDE、一个分布式消息队列、一个通信框架,等等。

DDD切入点1 - 理解概念

DDD的全称为Domain-driven Design,即领域驱动设计。下面我从领域、问题域、领域模型、设计、驱动这几个词语的含义和联系的角度去阐述DDD是如何融入到我们平时的软件开发初期阶段的。要理解什么是领域驱动设计,首先要理解什么是领域,什么是设计,还有驱动是什么意思,什么驱动什么。

什么是领域(Domain)?

前面我们已经清楚的知道我们现在要做一个什么样的系统,这个系统需要解决什么问题。我认为任何一个系统都会属于某个特定的领域,比如论坛是一个领域,只要你想做一个论坛,那这个论坛的核心业务是确定的,比如都有用户发帖、回帖等核心基本功能。比如电商平台、普通电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易等核心环节。所以,同一个领域的系统都具有相同的核心业务,因为他们要解决的问题的本质是类似的。

因此,我们可以推断出,一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。通常我们说,要成为一个领域的专家,必须要在这个领域深入研究很多年才行。因为只有你研究了很多年,你才会遇到非常多的该领域的问题,同时你解决这个领域中的问题的经验也非常丰富。很多时候,领域专家比技术专家更加吃香,比如金融领域的专家。

什么是设计(Design)?

DDD中的设计主要指领域模型的设计。为什么是领域模型的设计而不是架构设计或其他的什么设计呢?因为DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题。

从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就一定是解决了领域中的核心问题的。因为:1)领域驱动领域模型设计;2)领域模型驱动代码实现。我们只要保证领域模型的设计是正确的,就能确定领域模型可以解决领域中的核心问题;同理,我们只要保证代码实现是严格按照领域模型的意图来落地的,那就能保证最后出来的代码能够解决领域的核心问题的。这个思路,和传统的分析、设计、编码这几个阶段被割裂(并且每个阶段的产物也不同)的软件开发方法学形成鲜明的对比。

什么是驱动(Driven)?

上面其实已经提到了,就是:1)领域驱动领域模型设计;2)领域模型驱动代码实现。这个就和我们传统的数据库驱动开发的思路形成对比了。DDD中,我们总是以领域为边界,分析领域中的核心问题(核心关注点),然后设计对应的领域模型,再通过领域模型驱动代码实现。而像数据库设计、持久化技术等这些都不是DDD的核心,而是外围的东西。

领域驱动设计(DDD)告诉我们的最大价值我觉得是:当我们要开发一个系统时,应该尽量先把领域模型想清楚,然后再开始动手编码,这样的系统后期才会很好维护。但是,很多项目(尤其是互联网项目,为了赶工)都是一开始模型没想清楚,一上来就开始建表写代码,代码写的非常冗余,完全是过程是的思考方式,最后导致系统非常难以维护。而且更糟糕的是,出来混总是要还的,前期的领域模型设计的不好,不够抽象,如果你的系统会长期需要维护和适应业务变化,那后面你一定会遇到各种问题维护上的困难,比如数据结构设计不合理,代码到处冗余,改BUG到处引入新的BUG,新人对这种代码上手困难,等。而那时如果你再想重构模型,那要付出的代价会比一开始重新开发还要大,因为你还要考虑兼容历史的数据,数据迁移,如何平滑发布等各种头疼的问题。所以,就导致我们最后天天加班。

虽然,我们都知道这个道理,但是我也明白,人的习惯很难改变的,大部分人都很难从面向过程式的想到哪里写到哪里的思想转变为基于系统化的模型驱动的思维。我想,这或许是DDD很难在中国或国外流行起来的原因吧。但是,我想这不应该成为我们放弃学习DDD的原因,对吧!

概念总结:

  1. 领域就是问题域,有边界,领域中有很多问题;
  2. 任何一个系统要解决的那个大问题都对应一个领域;
  3. 通过建立领域模型来解决领域中的核心问题,模型驱动的思想;
  4. 领域建模的目标针对我们在领域中所关心的问题,即只针对核心关注点,而不是整个领域中的所有问题;
  5. 领域模型在设计时应考虑一定的抽象性、通用性,以及复用价值;
  6. 通过领域模型驱动代码的实现,确保代码让领域模型落地,代码最终能解决问题;
  7. 领域模型是系统的核心,是领域内的业务的直接沉淀,具有非常大的业务价值;
  8. 技术架构设计或数据存储等是在领域模型的外围,帮助领域模型进行落地;

DDD切入点2 - 理解领域、拆分领域、细化领域

理解领域知识是基础

上面我们通过第一步,虽然我们明确了要做一个什么样的系统,该系统主要解决什么问题,但是就这样我们还无法开始进行实际的需求分析和模型设计,我们还必须将我们的问题进行拆分,需求进行细化。有些时候,需求方,即提出问题的人,很可能自己不清楚具体想要什么。他只知道一个概念,一个大的目标。比如他只知道要做一个股票交易系统,一个灰度发布系统,一个电商平台,一个开发工具,等。但是他不清楚这些系统应该具体做成什么样子。这个时候,我认为领域专家就非常重要了,DDD也非常强调领域专家的重要性。因为领域专家对这个领域非常了解,对领域内的各种业务场景和各种业务规则也非常清楚,总之,对这个领域内的一切业务相关的知识都非常了解。所以,他们自然就有能力表达出系统该做成什么样子。所以,要知道一个系统到底该做成什么样子,到底哪些是核心业务关注点,只能靠沉淀领域内的各种知识,别无他法。因此,假设你现在打算做一个电商平台,但是你对这个领域没什么了解,那你一定得先去了解下该领域内主流的电商平台,比如淘宝、天猫、京东、亚马逊等。这个了解的过程就是你沉淀领域知识的过程。如果你不了解,就算你领域建模的能力再强,各种技术架构能力再强也是使不上力。领域专家不是某个固定的角色,而是某一类人,这类人对这个领域非常了解。比如,一个开发人员也可以是一个领域专家。假设你在一个公司开发和维护一个系统已经好几年了,但是这个系统的产品经理(PD)可能已经换过好几任了,这种情况下,我相信这几任产品经理都没有比你更熟悉这个领域。

拆分领域

上面我们明白了,领域建模的基础是要先理解领域,让自己成为领域专家。如果做到了这点,我们就打好了坚实的基础了。但是,有时一个领域往往太复杂,涉及到的领域概念、业务规则、交互流程太多,导致我们没办法直接针对这个大的领域进行领域建模。所以,我们需要将领域进行拆分,本质上就是把大问题拆分为小问题,然后各个击破的思路。然后既然把一个大的领域划分为了多个小的领域(子域),那最关键的就是要理清每个子域的边界;然后要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撑子域;然后,还要思考子域之间的联系是什么。那么,我们该如何划分子域呢?我的个人看法是从业务相关性的角度去思考,也就是我们平时说的按业务功能为出发点进行划分。还是拿经典的电商系统来分析,通常一个电商系统都会包含好几个大块,比如:

  • 会员中心:负责用户账号登录、用户信息的管理;
  • 商品中心:负责商品的展示、导航、维护;
  • 订单中心:负责订单的生成和生命周期管理;
  • 交易中心:负责交易相关的业务;
  • 库存中心:负责维护商品的库存;
  • 促销中心:负责各种促销活动的支持;

上面这些中心看起来很自然,因为大家对电子商务的这个领域都已经非常熟悉了,所以都没什么疑问,好像很自然的样子。所以,领域划分是不是就是没什么挑战了呢?显然不是。之所以我们觉得子域划分很简单,是因为我们对整个大领域非常了解了。如果我们遇到一个冷门的领域,就没办法这么容易的去划分子域了。这就需要我们先去努力理解领域内的知识。所以,我个人从来不相信什么子域划分的技巧什么的东西,因为我觉得这个工作没有任何诀窍可以使用。当我们不了解一个东西的时候,如何去拆解它?当我们对整个领域有一定的熟悉了,了解了领域内的相关业务的本质和关系,我们就自然而然的能划分出合理的子域了。不过并不是所有的系统都需要划分子域的,有些系统只是解决一个小问题,这个问题不复杂,可能只有一两个核心概念。所以,这种系统完全不需要再划分子域。但不是绝对的,当一个领域,我们的关注点越来越多,每个关注点我们关注的信息越来越多的时候,我们会不由自主的去进一步的划分子域。比如,也许我们一开始将商品和商品的库存都放在商品中心里,但是后来由于库存的维护越来越复杂,导致揉在一起对我们的系统维护带来一定的困难时,我们就会考虑将两者进行拆分,这个就是所谓的业务垂直分割。

细化子域

通过上面的两步,我们了解了领域里的知识,也对领域进行了子域划分。但这样还不够,凭这些我们还无法进行后续的领域模型设计。我们还必须再进一步细化每个子域,进一步明确每个子域的核心关注点,即需求细化。我觉得我们需要细化的方面有以下几点:

  1. 梳理领域概念:梳理出领域内我们关注的概念、概念的关系,并统一交流词汇,形成统一语言;
  2. 梳理业务规则:梳理出领域内我们关注的各种业务规则,DDD中叫不变性(invariants),比如唯一性规则,余额不能小于零等;
  3. 梳理业务场景:梳理出领域内的核心业务场景,比如电商平台中的加入购物车、提交订单、发起付款等核心业务场景;
  4. 梳理业务流程:梳理出领域内的关键业务流程,比如订单处理流程,退款流程等;

从上面这4个方面,我们从领域概念、业务规则、交互场景、业务流程等维度梳理了我们到底要什么,整理了整个系统应该具备的功能。这个工作我觉得是一个非常具有创造性和有难度的工作。我们一方面会主观的定义我们想要什么;另一方面,我们还会思考我们要的东西的合理性。我认为这个就是产品经理的工作,产品经理必须要负起职责,把他的产品充分设计好,从各个方面去考虑,如何设计一个产品,才能更好的解决用户的核心诉求,即领域内的核心问题。如果对领域不够了解,如果想不清楚用户到底要什么,如果思考问题不够全面,谈何设计出一个合理的产品呢?

关于领域概念的梳理,我觉得可以采用四色原型分析法,这个分析法通过系统的方法,将概念划分为不同的种类,为不同种类的概念标注不同的颜色。然后将这些概念有机的组合起来,从而让我们可以清晰的分析出概念和概念之间的关系。有兴趣的同学可以在网上搜索下四色原型

注意:上面我说的这四点,重点是梳理出我们要什么功能,而不是思考如何实现这些功能,如何实现是软件设计人员的职责。

DDD切入点3 - 领域模型设计

这部分内容,我想学习DDD的人都很熟悉了。DDD原著中提出了很多实用的建模工具:聚合、实体、值对象、工厂、仓储、领域服务、领域事件。我们可以使用这些工具,来设计每一个子域的领域模型。最终通过领域模型图将设计沉淀下来。要使用这些工具,首先就要理解每个工具的含义和使用场景。不要以为很简单哦,比如聚合的划分就是一个非常具有艺术的活。同一个系统,不同的人设计出来的聚合是完全不同的。而且很有可能高手之间的最后设计出来的差别反而更大,实际上我认为是世界观的相互碰撞,呵呵。所以,要领域建模,我觉得每个人都应该去学学哲学知识,这有助于我们更好的认识世界,更好的理解事物的本质。

关于这些建模工具的概念和如何运用我就不多展开了,我博客里也有很多这方面的介绍。下面我再讲一下我认为比较重要的东西,比如到底该如何领域建模?步骤应该是怎么样的?

领域建模的方法

通过上面我介绍的细化子域的内容,现在再来谈该如何领域建模,我觉得就方便很多了。我的主要方法是:

  1. 划分好边界上下文,通常每个子域(sub domain)对应一个边界上下文(bounded context),同一个边界上下文中的概念是明确的,没有任何歧义;
  2. 在每个边界上下文中设计领域模型,具体的领域模型设计方法有很多种,如以场景为出发点的四色原型分析法,或者我早期写的这篇文章;这个步骤最核心的就是找出聚合根,并找出每个聚合根包含的信息;关于如何设计聚合,可以看一下我写的这篇文章
  3. 画出领域模型图,圈出每个模型中的聚合边界;
  4. 设计领域模型时,要考虑该领域模型是否满足业务规则,同时还要综合考虑技术实现等问题,比如并发问题;领域模型不是概念模型,概念模型不关注技术实现,领域模型关心;所以领域模型才能直接指导编码实现;
  5. 思考领域模型是如何在业务场景中发挥作用的,以及是如何参与到业务流程的每个环节的;
  6. 场景走查,确认领域模型是否能满足领域中的业务场景和业务流程;
  7. 模型持续重构、完善、精炼;

领域模型的核心作用:

  1. 抽象了领域内的核心概念,并建立概念之间的关系;
  2. 领域模型承担了领域内的状态的维护;
  3. 领域模型维护了领域内的数据之间的业务规则,数据一致性;

下图是我最近做个一个普通电商系统的商品中心的领域模型图,给大家参考:

领域模型设计只是软件设计中的一小部分

需要特别注意的是,领域模型设计只是整个软件设计中的很小一部分。除了领域模型设计之外,要落地一个系统,我们还有非常多的其他设计要做,比如:

  • 容量规划
  • 架构设计
  • 数据库设计
  • 缓存设计
  • 框架选型
  • 发布方案
  • 数据迁移、同步方案
  • 分库分表方案
  • 回滚方案
  • 高并发解决方案
  • 一致性选型
  • 性能压测方案
  • 监控报警方案

等等。上面这些都需要我们平时的大量学习和积累。作为一个合格的开发人员或架构师,我觉得除了要会DDD领域驱动设计,还要会上面这么多的技术能力,确实是非常不容易的。所以,千万不要以为会DDD了就以为自己很牛逼,实际上你会的只是软件设计中的冰山一角而已。

总结

本文的重点是基于我个人对DDD的一些理解,希望能整理出一些自己总结出来的一些感悟和经验,并分享给大家。我相信很多人已经看过太多DDD书上的东西,我总是感觉书上的东西看似都太”正规“,很多时候我们读了之后很难消化,就算理解了书里的内容,当我们想要运用到实践中时,总是感觉无从下手。本文希望通过通俗易懂的文字,介绍了一部分我对DDD的学习感悟和实践心得,希望能给大家一些启发和帮助。

posted @ 2017-02-19 12:12 paulwong 阅读(666) | 评论 (0)编辑 收藏

DDD CQRS架构和传统架构的优缺点比较

最近几年,在DDD的领域,我们经常会看到CQRS架构的概念。我个人也写了一个ENode框架,专门用来实现这个架构。CQRS架构本身的思想其实非常简单,就是读写分离。是一个很好理解的思想。就像我们用MySQL数据库的主备,数据写到主,然后查询从备来查,主备数据的同步由MySQL数据库自己负责,这是一种数据库层面的读写分离。关于CQRS架构的介绍其实已经非常多了,大家可以自行百度或google。我今天主要想总结一下这个架构相对于传统架构(三层架构、DDD经典四层架构)在数据一致性、扩展性、可用性、伸缩性、性能这几个方面的异同,希望可以总结出一些优点和缺点,为大家在做架构选型时提供参考。

前言

CQRS架构由于本身只是一个读写分离的思想,实现方式多种多样。比如数据存储不分离,仅仅只是代码层面读写分离,也是CQRS的体现;然后数据存储的读写分离,C端负责数据存储,Q端负责数据查询,Q端的数据通过C端产生的Event来同步,这种也是CQRS架构的一种实现。今天我讨论的CQRS架构就是指这种实现。另外很重要的一点,C端我们还会引入Event Sourcing+In Memory这两种架构思想,我认为这两种思想和CQRS架构可以完美的结合,发挥CQRS这个架构的最大价值。

数据一致性

传统架构,数据一般是强一致性的,我们通常会使用数据库事务保证一次操作的所有数据修改都在一个数据库事务里,从而保证了数据的强一致性。在分布式的场景,我们也同样希望数据的强一致性,就是使用分布式事务。但是众所周知,分布式事务的难度、成本是非常高的,而且采用分布式事务的系统的吞吐量都会比较低,系统的可用性也会比较低。所以,很多时候,我们也会放弃数据的强一致性,而采用最终一致性;从CAP定理的角度来说,就是放弃一致性,选择可用性。

CQRS架构,则完全秉持最终一致性的理念。这种架构基于一个很重要的假设,就是用户看到的数据总是旧的。对于一个多用户操作的系统,这种现象很普遍。比如秒杀的场景,当你下单前,也许界面上你看到的商品数量是有的,但是当你下单的时候,系统提示商品卖完了。其实我们只要仔细想想,也确实如此。因为我们在界面上看到的数据是从数据库取出来的,一旦显示到界面上,就不会变了。但是很可能其他人已经修改了数据库中的数据。这种现象在大部分系统中,尤其是高并发的WEB系统,尤其常见。

所以,基于这样的假设,我们知道,即便我们的系统做到了数据的强一致性,用户还是很可能会看到旧的数据。所以,这就给我们设计架构提供了一个新的思路。我们能否这样做:我们只需要确保系统的一切添加、删除、修改操作所基于的数据是最新的,而查询的数据不必是最新的。这样就很自然的引出了CQRS架构了。C端数据保持最新、做到数据强一致;Q端数据不必最新,通过C端的事件异步更新即可。所以,基于这个思路,我们开始思考,如何具体的去实现CQ两端。看到这里,也许你还有一个疑问,就是为何C端的数据是必须要最新的?这个其实很容易理解,因为你要修改数据,那你可能会有一些修改的业务规则判断,如果你基于的数据不是最新的,那意味着判断就失去意义或者说不准确,所以基于老的数据所做的修改是没有意义的。

扩展性

传统架构,各个组件之间是强依赖,都是对象之间直接方法调用;而CQRS架构,则是事件驱动的思想;从微观的聚合根层面,传统架构是应用层通过过程式的代码协调多个聚合根一次性以事务的方式完成整个业务操作。而CQRS架构,则是以Saga的思想,通过事件驱动的方式,最终实现多个聚合根的交互。另外,CQRS架构的CQ两端也是通过事件的方式异步进行数据同步,也是事件驱动的一种体现。上升到架构层面,那前者就是SOA的思想,后者是EDA的思想。SOA是一个服务调用另一个服务完成服务之间的交互,服务之间紧耦合;EDA是一个组件订阅另一个组件的事件消息,根据事件信息更新组件自己的状态,所以EDA架构,每个组件都不会依赖其他的组件;组件之间仅仅通过topic产生关联,耦合性非常低。

上面说了两种架构的耦合性,显而易见,耦合性低的架构,扩展性必然好。因为SOA的思路,当我要加一个新功能时,需要修改原来的代码;比如原来A服务调用了B,C两个服务,后来我们想多调用一个服务D,则需要改A服务的逻辑;而EDA架构,我们不需要动现有的代码,原来有B,C两订阅者订阅A产生的消息,现在只需要增加一个新的消息订阅者D即可。

从CQRS的角度来说,也有一个非常明显的例子,就是Q端的扩展性。假设我们原来Q端只是使用数据库实现的,但是后来系统的访问量增大,数据库的更新太慢或者满足不了高并发的查询了,所以我们希望增加缓存来应对高并发的查询。那对CQRS架构来说很容易,我们只需要增加一个新的事件订阅者,用来更新缓存即可。应该说,我们可以随时方便的增加Q端的数据存储类型。数据库、缓存、搜索引擎、NoSQL、日志,等等。我们可以根据自己的业务场景,选择合适的Q端数据存储,实现快速查询的目的。这一切都归功于我们C端记录了所有模型变化的事件,当我们要增加一种新的View存储时,可以根据这些事件得到View存储的最新状态。这种扩展性在传统架构下是很难做到的。

可用性

可用性,无论是传统架构还是CQRS架构,都可以做到高可用,只要我们做到让我们的系统中每个节点都无单点即可。但是,相比之下,我觉得CQRS架构在可用性方面,我们可以有更多的回避余地和选择空间。

传统架构,因为读写没有分离,所以可用性要把读写合在一起综合考虑,难度会比较更大。因为传统架构,如果一个系统的高峰期的并发写入很大,比如为2W,并发读取也很大,比如为10W。那该系统必须优化到能同时支持这种高并发的写入和查询,否则系统就会在高峰时挂掉。这个就是基于同步调用思路的系统的缺点,没有一个东西去削峰填谷,保存瞬间多出来的请求,而必须让系统不管遇到多少请求,都必须能及时处理完,否则就会造成雪崩效应,造成系统瘫痪。但是一个系统,不会一直处在高峰,高峰可能只有半小时或1小时;但为了确保高峰时系统不挂掉,我们必须使用足够的硬件去支撑这个高峰。而大部分时候,都不需要这么高的硬件资源,所以会造成资源的浪费。所以,我们说基于同步调用、SOA思想的系统的实现成本是非常昂贵的。

而在CQRS架构下,因为CQRS架构把读和写分离了,所以可用性相当于被隔离在了两个部分去考虑。我们只需要考虑C端如何解决写的可用性,Q端如何解决读的可用性即可。C端解决可用性,我觉得是更加容易的,因为C端是消息驱动的。我们要做任何数据修改时,都会发送Command到分布式消息队列,然后后端消费者处理Command->产生领域事件->持久化事件->发布事件到分布式消息队列->最后事件被Q端消费。这个链路是消息驱动的。相比传统架构的直接服务方法调用,可用性要高很多。因为就算我们处理Command的后端消费者暂时挂了,也不会影响前端Controller发送Command,Controller依然可用。从这个角度来说,CQRS架构在数据修改上可用性要更高。不过你可能会说,要是分布式消息队列挂了呢?呵呵,对,这确实也是有可能的。但是一般分布式消息队列属于中间件,一般中间件都具有很高的可用性(支持集群和主备切换),所以相比我们的应用来说,可用性要高很多。另外,因为命令是先发送到分布式消息队列,这样就能充分利用分布式消息队列的优势:异步化、拉模式、削峰填谷、基于队列的水平扩展。这些特性可以保证即便前端Controller在高峰时瞬间发送大量的Command过来,也不会导致后端处理Command的应用挂掉,因为我们是根据自己的消费能力拉取Command。这点也是CQRS C端在可用性方面的优势,其实本质也是分布式消息队列带来的优势。所以,从这里我们可以体会到EDA架构(事件驱动架构)是非常有价值的,这个架构也体现了我们目前比较流行的Reactive Programming(响应式编程)的思想。

然后,对于Q端,应该说和传统架构没什么区别,因为都是要处理高并发的查询。这点以前怎么优化的,现在还是怎么优化。但是就像我上面可扩展性里强调的,CQRS架构可以更方便的提供更多的View存储,数据库、缓存、搜索引擎、NoSQL,而且这些存储的更新完全可以并行进行,互相不会拖累。理想的场景,我觉得应该是,如果你的应用要实现全文索引这种复杂查询,那可以在Q端使用搜索引擎,比如ElasticSearch;如果你的查询场景可以通过keyvalue这种数据结构满足,那我们可以在Q端使用Redis这种NoSql分布式缓存。总之,我认为CQRS架构,我们解决查询问题会比传统架构更加容易,因为我们选择更多了。但是你可能会说,我的场景只能用关系型数据库解决,且查询的并发也是非常高。那没办法了,唯一的办法就是分散查询IO,我们对数据库做分库分表,以及对数据库做一主多备,查询走备机。这点上,解决思路就是和传统架构一样了。

性能、伸缩性

本来想把性能和伸缩性分开写的,但是想想这两个其实有一定的关联,所以决定放在一起写。

伸缩性的意思是,当一个系统,在100人访问时,性能(吞吐量、响应时间)很不错,在100W人访问时性能也同样不错,这就是伸缩性。100人访问和100W人访问,对系统的压力显然是不同的。如果我们的系统,在架构上,能够做到通过简单的增加机器,就能提高系统的服务能力,那我们就可以说这种架构的伸缩性很强。那我们来想想传统架构和CQRS架构在性能和伸缩性上面的表现。

说到性能,大家一般会先思考一个系统的性能瓶颈在哪里。只要我们解决了性能瓶颈,那系统就意味着具有通过水平扩展来达到可伸缩的目的了(当然这里没有考虑数据存储的水平扩展)。所以,我们只要分析一下传统架构和CQRS架构的瓶颈点在哪里即可。

传统架构,瓶颈通常在底层数据库。然后我们一般的做法是,对于读:通常使用缓存就可以解决大部分查询问题;对于写:办法也有很多,比如分库分表,或者使用NoSQL,等等。比如阿里大量采用分库分表的方案,而且未来应该会全部使用高大上的OceanBase来替代分库分表的方案。通过分库分表,本来一台数据库服务器高峰时可能要承受10W的高并发写,如果我们把数据放到十台数据库服务器上,那每台机器只需要承担1W的写,相对于要承受10W的写,现在写1W就显得轻松很多了。所以,应该说数据存储对传统架构来说,也早已不再是瓶颈了。

传统架构一次数据修改的步骤是:1)从DB取出数据到内存;2)内存修改数据;3)更新数据回DB。总共涉及到2次数据库IO。

然后CQRS架构,CQ两端加起来所用的时间肯定比传统架构要多,因为CQRS架构最多有3次数据库IO,1)持久化命令;2)持久化事件;3)根据事件更新读库。为什么说最多?因为持久化命令这一步不是必须的,有一种场景是不需要持久化命令的。CQRS架构中持久化命令的目的是为了做幂等处理,即我们要防止同一个命令被处理两次。那哪一种场景下可以不需要持久化命令呢?就是当命令时在创建聚合根时,可以不需要持久化命令,因为创建聚合根所产生的事件的版本号总是为1,所以我们在持久化事件时根据事件版本号就能检测到这种重复。

所以,我们说,你要用CQRS架构,就必须要接受CQ数据的最终一致性,因为如果你以读库的更新完成为操作处理完成的话,那一次业务场景所用的时间很可能比传统架构要多。但是,如果我们以C端的处理为结束的话,则CQRS架构可能要快,因为C端可能只需要一次数据库IO。我觉得这里有一点很重要,对于CQRS架构,我们更加关注C端处理完成所用的时间;而Q端的处理稍微慢一点没关系,因为Q端只是供我们查看数据用的(最终一致性)。我们选择CQRS架构,就必须要接受Q端数据更新有一点点延迟的缺点,否则就不应该使用这种架构。所以,希望大家在根据你的业务场景做架构选型时一定要充分认识到这一点。

另外,上面再谈到数据一致性时提到,传统架构会使用事务来保证数据的强一致性;如果事务越复杂,那一次事务锁的表就越多,锁是系统伸缩性的大敌;而CQRS架构,一个命令只会修改一个聚合根,如果要修改多个聚合根,则通过Saga来实现。从而绕过了复杂事务的问题,通过最终一致性的思路做到了最大的并行和最少的并发,从而整体上提高系统的吞吐能力。

所以,总体来说,性能瓶颈方面,两种架构都能克服。而只要克服了性能瓶颈,那伸缩性就不是问题了(当然,这里我没有考虑数据丢失而带来的系统不可用的问题。这个问题是所有架构都无法回避的问题,唯一的解决办法就是数据冗余,这里不做展开了)。两者的瓶颈都在数据的持久化上,但是传统的架构因为大部分系统都是要存储数据到关系型数据库,所以只能自己采用分库分表的方案。而CQRS架构,如果我们只关注C端的瓶颈,由于C端要保存的东西很简单,就是命令和事件;如果你信的过一些成熟的NoSQL(我觉得使用文档性数据库如MongoDB这种比较适合存储命令和事件),且你也有足够的能力和经验去运维它们,那可以考虑使用NoSQL来持久化。如果你觉得NoSQL靠不住或者没办法完全掌控,那可以使用关系型数据库。但这样你也要付出努力,比如需要自己负责分库分表来保存命令和事件,因为命令和事件的数据量都是很大的。不过目前一些云服务如阿里云,已经提供了DRDS这种直接支持分库分表的数据库存储方案,极大的简化了我们存储命令和事件的成本。就我个人而言,我觉得我还是会采用分库分表的方案,原因很简单:确保数据可靠落地、成熟、可控,而且支持这种只读数据的落地,框架内置要支持分库分表也不是什么难事。所以,通过这个对比我们知道传统架构,我们必须使用分库分表(除非阿里这种高大上可以使用OceanBase);而CQRS架构,可以带给我们更多选择空间。因为持久化命令和事件是很简单的,它们都是不可修改的只读数据,且对kv存储友好,也可以选择文档型NoSQL,C端永远是新增数据,而没有修改或删除数据。最后,就是关于Q端的瓶颈,如果你Q端也是使用关系型数据库,那和传统架构一样,该怎么优化就怎么优化。而CQRS架构允许你使用其他的架构来实现Q,所以优化手段相对更多。

结束语

我觉得不论是传统架构还是CQRS架构,都是不错的架构。传统架构门槛低,懂的人也多,且因为大部分项目都没有什么大的并发写入量和数据量。所以应该说大部分项目,采用传统架构就OK了。但是通过本文的分析,大家也知道了,传统架构确实也有一些缺点,比如在扩展性、可用性、性能瓶颈的解决方案上,都比CQRS架构要弱一点。大家有其他意见,欢迎拍砖,交流才能进步,呵呵。所以,如果你的应用场景是高并发写、高并发读、大数据,且希望在扩展性、可用性、性能、可伸缩性上表现更优秀,我觉得可以尝试CQRS架构。但是还有一个问题,CQRS架构的门槛很高,我认为如果没有成熟的框架支持,很难使用。而目前据我了解,业界还没有很多成熟的CQRS框架,java平台有axon framework, jdon framework;.NET平台,ENode框架正在朝这个方向努力。所以,我想这也是为什么目前几乎没有使用CQRS架构的成熟案例的原因之一。另一个原因是使用CQRS架构,需要开发者对DDD有一定的了解,否则也很难实践,而DDD本身要理解没个几年也很难运用到实际。还有一个原因,CQRS架构的核心是非常依赖于高性能的分布式消息中间件,所以要选型一个高性能的分布式消息中间件也是一个门槛(java平台有RocketMQ),.NET平台我个人专门开发了一个分布式消息队列EQueue,呵呵。另外,如果没有成熟的CQRS框架的支持,那编码复杂度也会很复杂,比如Event Sourcing,消息重试,消息幂等处理,事件的顺序处理,并发控制,这些问题都不是那么容易搞定的。而如果有框架支持,由框架来帮我们搞定这些纯技术问题,开发人员只需要关注如何建模,实现领域模型,如何更新读库,如何实现查询,那使用CQRS架构才有可能,因为这样才可能比传统的架构开发更简单,且能获得很多CQRS架构所带来的好处。

posted @ 2017-02-19 08:39 paulwong 阅读(865) | 评论 (0)编辑 收藏

快速理解聚合根、实体、值对象的区别和联系

posted @ 2017-02-19 08:32 paulwong 阅读(1265) | 评论 (0)编辑 收藏

基于Spring Boot, Axon CQRS/ES,和Docker构建微服务

这是一个使用Spring Boot和Axon以及Docker构建的Event Sorucing源码项目,技术特点:
1.使用Java 和Spring Boot实现微服务;
2.使用命令和查询职责分离 (CQRS) 和 Event Sourcing (ES) 的框架Axon Framework v2, MongoDB 和 RabbitMQ;
3.使用Docker构建 交付和运行;
4.集中配置和使用Spring Cloud服务注册;
5.使用Swagger 和 SpringFox 提供API文档

项目源码:GitHub

工作原理:
这个应用使用CQRS架构模式构建,在CQRS命令如ADD是和查询VIEW(where id=1)分离的,在这个案例中领域部分代码已经分离成两个组件:一个是属于命令这边的微服务和属性查询这边的微服务。

微服务是单个职责的功能,自己的数据存储,每个能彼此独立扩展部署。

属于命令这边的微服务和属性查询这边的微服务都是使用Spring Boot框架开发的,在命令微服务和查询微服务之间通讯是事件驱动,事件是通过RabbitMQ消息在微服务组件之间传递,消息提供了一种进程节点或微服务之间可扩展的事件载体,包括与传统遗留系统或其他系统的松耦合通讯都可以通过消息进行。

请注意,服务之间不能彼此共享数据库,这是很重要,因为微服务应该是高度自治自主的,这样反过来有助于服务能够彼此独立地扩展伸缩规模。

CQRS中命令是“改变状态的动作”。命令的微服务包含所有领域逻辑和业务规则,命令被用于增加新的产品或改变它们的状态,这些命令针对某个具体产品的执行会导致事件Event产生,这会通过Axon框架持久化到MongoDB中,然后通过RabbitMQ传播给其他节点进程或微服务。

在event-sourcing中,事件是状态改变的原始记录,它们用于系统来重新建立实体的当前状态(通过重新播放过去的事件到当前就可以构建当前的状态),这听上去会很慢,但是实际上,事件都很简单,执行非常快,也能采取‘快照’策略进行优化。

请注意,在DDD中,实体是指一个聚合根实体。

上面是命令这边的微服务,下面看看查询这边的微服务:
查询微服务一般扮演一种事件监听器和视图角色,它监听到命令那边发出的事件,然后处理它们以符合查询这边的要求。

在这个案例中,查询这边只是简单建立和维持了一个 ‘materialised view’或‘projection’ ,其中保留了产品的最新状态,也就是产品id和描述以及是否被卖出等等信息,查询这边能够被复制多次以方便扩展,消息可以保留在RabbitMQ队列中实现持久保存,这种临时保存消息方式可以防止查询这边微服务当机。

命令微服务和查询微服务两者都有REST API,提供外界客户端访问。

下面看看如何通过Docker运行这个案例,需要 Ubuntu 16.04:
1.Docker ( v1.8.2)
2.Docker-compose ( v1.7.1)

在一个空目录,执行下面命令下载docker-compose:

$ wget https://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml
注意:不要更改文件名称。

启动微服务:只是简单一个命令:

$ docker-compose up

你会看到许多下载信息和日志输出在屏幕上,这是Docker image将被下载和运行。一共有六个docker,分别是: ‘mongodb’, ‘rabbitmq’, ‘config’, ‘discovery’, ‘product-cmd-side’, 和 ‘product-qry-side’.

使用下面命令进行测试增加一个新产品:

$ curl -X POST -v --header "Content-Type: application/json" --header "Accept: */*" "http://localhost:9000/products/add/1?name=Everything%20Is%20Awesome"

查询这个新产品:

$ curl http://localhost:9001/products/1

Microservices With Spring Boot, Axon CQRS/ES, and Docker

posted @ 2017-02-18 22:00 paulwong 阅读(1848) | 评论 (0)编辑 收藏

DDD资源

微服务+DDD
https://github.com/benwilcock/microservice-sampler



https://github.com/AxonFramework/AxonBank

http://www.cnblogs.com/netfocus/p/4150084.html

posted @ 2017-02-18 21:53 paulwong 阅读(467) | 评论 (0)编辑 收藏

使用 Docker 搭建 Java Web 运行环境

     摘要: Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都听说过它。Docker 是一种“轻量级”容器技术,它几乎动摇了传统虚拟化技术的地位,现在国内外已经有越来越多的公司开始逐步使用 Docker 来替换现有的虚拟化平台了。作为一名 Java 程序员,我们是时候一起把 Docker 学起来了!本文会对虚拟化技术与 Docker 容器技术做一个对比,然后引出一些 ...  阅读全文

posted @ 2016-10-15 19:57 paulwong 阅读(1608) | 评论 (0)编辑 收藏

使用Spring Cloud Security OAuth2搭建授权服务

Spring Cloud Security OAuth2 是 Spring 对 OAuth2 的开源实现,优点是能与Spring Cloud技术线无缝集成,如果全部使用默认配置,开发者只需要添加注解就能完成 OAuth2 授权服务的搭建。

1. 添加依赖

授权服务是基于Spring Security的,因此需要在项目中引入两个依赖:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

<dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-oauth2</artifactId>
 </dependency>


前者为 Security,后者为Security的OAuth2扩展。

2. 添加注解和配置

在启动类中添加@EnableAuthorizationServer注解:

@SpringBootApplication
@EnableAuthorizationServer
public class AlanOAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AlanOAuthApplication.class, args);
    }
}


完成这些我们的授权服务最基本的骨架就已经搭建完成了。但是要想跑通整个流程,我们必须分配 client_idclient_secret才行。Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如:

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // 使用in-memory存储
                .withClient("client") // client_id
                .secret("secret") // client_secret
                .authorizedGrantTypes("authorization_code") // 该client允许的授权类型
                .scopes("app"); // 允许的授权范围
    }


3. 授权流程

访问授权页面:

localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com


此时浏览器会让你输入用户名密码,这是因为 Spring Security 在默认情况下会对所有URL添加Basic Auth认证。默认的用户名为user, 密码是随机生成的,在控制台日志中可以看到。

oauth2

画风虽然很简陋,但是基本功能都具备了。点击Authorize后,浏览器就会重定向到百度,并带上code参数:

这里写图片描述

拿到code以后,就可以调用

POST/GET http://client:secret@localhost:8080/oauth/token
  • 1

来换取access_token了:

curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"

返回如下:

{
  "access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556",
  "token_type": "bearer",
  "expires_in": 2591999,
  "scope": "app"
}

到此我们最最基本的授权服务就搭建完成了。然而,这仅仅是个demo,如果要在生产环境中使用,还需要做更多的工作。

4. 使用MySQL存储access_token和client信息

把授权服务器中的数据存储到数据库中并不难,因为 Spring Cloud Security OAuth 已经为我们设计好了一套Schema和对应的DAO对象。但在使用之前,我们需要先对相关的类有一定的了解。

4.1 相关接口

Spring Cloud Security OAuth2通过DefaultTokenServices类来完成token生成、过期等 OAuth2 标准规定的业务逻辑,而DefaultTokenServices又是通过TokenStore接口完成对生成数据的持久化。在上面的demo中,TokenStore的默认实现为InMemoryTokenStore,即内存存储。 对于Client信息,ClientDetailsService接口负责从存储仓库中读取数据,在上面的demo中默认使用的也是InMemoryClientDetialsService实现类。说到这里就能看出,要想使用数据库存储,只需要提供这些接口的实现类即可。庆幸的是,框架已经为我们写好JDBC实现了,即JdbcTokenStoreJdbcClientDetailsService

4.2 建表

要想使用这些JDBC实现,首先要建表。框架为我们提前设计好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

在使用这套表结构之前要注意的是,对于MySQL来说,默认建表语句中主键是varchar(255)类型,在mysql中执行会报错,原因是mysql对varchar主键长度有限制。所以这里改成128即可。其次,语句中会有某些字段为LONGVARBINARY类型,它对应mysql的blob类型,也需要修改一下。

4.3 配置

数据库建好后,下一步就是配置框架使用JDBC实现。方法还是编写@Configuration类继承AuthorizationServerConfigurerAdapter

@Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;
    @Bean // 声明TokenStore实现
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }
    @Bean // 声明 ClientDetails实现
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
    @Override // 配置框架应用上述实现
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore());

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

完成这些后,框架就会将中间产生的数据写到mysql中了。oauth_client_details是client表,可以直接在该表中添加记录来添加client: 
这里写图片描述

4.4 需要注意的地方

这里不得不说 Spring 设计有一个奇葩地的方。注意看oauth_access_token表是存放访问令牌的,但是并没有直接在字段中存放token。Spring 使用OAuth2AccessToken来抽象与令牌有关的所有属性,在写入到数据库时,Spring将该对象通过JDK自带的序列化机制序列成字节直接保存到了该表的token字段中。也就是说,如果只看数据表你是看不出access_token的值是多少,过期时间等信息的。这就给资源服务器的实现带来了麻烦。我们的资源提供方并没有使用Spring Security,也不想引入 Spring Security 的任何依赖,这时候就只能将 DefaultOAuth2AccessToken的源码copy到资源提供方的项目中,然后读取token字段并反序列化还原对象来获取token信息。但是如果这样做还会遇到反序列化兼容性的问题,具体解决方法参考我另一篇博文:http://blog.csdn.net/neosmith/article/details/52539614

5. 总结

至此一个能在生产环境下使用的授权服务就搭建好了。其实我们在实际使用时应该适当定制JdbcTokenStoreClientDetailsService来实适应业务需要,甚至可以直接从0开始实现接口,完全不用框架提供的实现。另外,Spring 直接将DefaultOAuth2AccessToken序列化成字节保存到数据库中的设计,我认为是非常不合理的。或许设计者的初衷是保密access_token,但是通过加密的方法也可以实现,完全不应该直接扔字节。不过通过定制TokenStore接口,我们可以使用自己的表结构而不拘泥于默认实现。

http://blog.csdn.net/tracker_w/article/category/6360121

http://blog.csdn.net/neosmith/article/details/52539927

posted @ 2016-09-16 18:22 paulwong 阅读(8722) | 评论 (0)编辑 收藏

使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务

在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。

Feign简介

Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。比如:

@Autowired
private AdvertGropRemoteService service; // 远程服务

public AdvertGroupVO foo(Integer groupId) {
    return service.findByGroupId(groupId); // 通过HTTP调用远程服务
}

开发者通过service.findByGroupId()就能完成发送HTTP请求和解码HTTP返回结果并封装成对象的过程。

Feign的定义

为了让Feign知道在调用方法时应该向哪个地址发请求以及请求需要带哪些参数,我们需要定义一个接口:

@FeignClient(name = "ea")  //  [A]
public interface AdvertGroupRemoteService {

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET) // [B]
    AdvertGroupVO findByGroupId(@PathVariable("groupId") Integer adGroupId) // [C]

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName)

A: @FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入。

B: @RequestMapping表示在调用该方法时需要向/group/{groupId}发送GET请求。

C: @PathVariableSpringMVC中对应注解含义相同。

Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。

在本例中,我们将Feign与Eureka和Ribbon组合使用,@FeignClient(name = "ea")意为通知Feign在调用该接口方法时要向Eureka中查询名为ea的服务,从而得到服务URL。

Feign的Encoder、Decoder和ErrorDecoder

Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。

默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping中的method将请求方式指定为POST,那么所有未标注解的参数将会被忽略,例如:

@FeignClient(name = "ea")  //  [A]
public interface AdvertGroupRemoteService {

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET) // [B]
    AdvertGroupVO findByGroupId(@PathVariable("groupId") Integer adGroupId) // [C]

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName)

此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。

在Spring Cloud环境下,Feign的Encoder*只会用来编码没有添加注解的参数*。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。

Feign的HTTP Client

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:

@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);

然后在application.properties中添加:

feign.httpclient.enabled=true

总结

通过Feign, 我们能把HTTP远程调用对开发者完全透明,得到与调用本地方法一致的编码体验。这一点与阿里Dubbo中暴露远程服务的方式类似,区别在于Dubbo是基于私有二进制协议,而Feign本质上还是个HTTP客户端。如果是在用Spring Cloud Netflix搭建微服务,那么Feign无疑是最佳选择。

http://blog.csdn.net/tracker_w/article/category/6360121
http://blog.csdn.net/neosmith/article/details/52449921

posted @ 2016-09-16 18:13 paulwong 阅读(2636) | 评论 (0)编辑 收藏

微服务框架Spring Cloud

2016

posted @ 2016-09-11 20:49 paulwong 阅读(935) | 评论 (0)编辑 收藏

JHipster

基于SPRING CLOUD的微服务框架
http://jhipster.cn/

posted @ 2016-09-11 16:40 paulwong 阅读(584) | 评论 (0)编辑 收藏

Spring Boot 性能优化

摘要
Spring 框架给企业软件开发者提供了常见问题的通用解决方案,包括那些在未来开发中没有意识到的问题。但是,它构建的 J2EE 项目变得越来越臃肿,逐渐被 Spring Boot 所替代。Spring Boot 让我们创建和运行项目变得更为迅速,现在已经有越来越多的人使用它。我们已经在几个项目中使用了 Spring Boot ,今天我们就来一起讨论一下如何改进 Spring Boot 应用的性能。

Spring 框架给企业软件开发者提供了常见问题的通用解决方案,包括那些在未来开发中没有意识到的问题。但是,它构建的 J2EE 项目变得越来越臃肿,逐渐被 Spring Boot 所替代。Spring Boot 让我们创建和运行项目变得更为迅速,现在已经有越来越多的人使用它。我们已经在几个项目中使用了 Spring Boot ,今天我们就来一起讨论一下如何改进 Spring Boot 应用的性能。

首先,从之前我在开发中遇到的一个问题说起。在一次查看项目运行日志的时候,我偶然发现了一个问题,日志里显示这个项目总是加载 Velocity 模板引擎,但实际上这个项目是一个没有 web 页面的 REST Service 项目。于是我花了一点时间去寻找产生这个问题的原因,以及如何改进 Spring Boot 应用的性能。在查找了相关的资料后,我得出的结论如下:

组件自动扫描带来的问题

默认情况下,我们会使用 @SpringBootApplication 注解来自动获取的应用的配置信息,但这样也会给应用带来一些副作用。使用这个注解后,会触发自动配置( auto-configuration )和 组件扫描 ( component scanning),这跟使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三个注解的作用是一样的。这样做给开发带来方便的同时,也会有两方面的影响:

1、会导致项目启动时间变长。当启动一个大的应用程序,或将做大量的集成测试启动应用程序时,影响会特别明显。

2、会加载一些不需要的多余的实例(beans)。

3、会增加 CPU 消耗。

针对以上两个情况,我们可以移除 @SpringBootApplication 和 @ComponentScan 两个注解来禁用组件自动扫描,然后在我们需要的 bean 上进行显式配置:

// 移除 @SpringBootApplication and @ComponentScan, 用 @EnableAutoConfiguration 来替代
@Configuration
@EnableAutoConfiguration
public class SampleWebUiApplication {

    
// 

    
// 用 @Bean 注解明确显式配置,以便被 Spring 扫描到
    @Bean
    
public MessageController messageController(MessageRepository messageRepository) {
        
return new MessageController(messageRepository);
    }

如何避免组件自动扫描带来的问题

我们在上面提到,@SpringBootApplication 注解的作用跟 @EnableAutoConfiguration 注解的作用是相当的,那就意味着它也能带来上述的三个问题。要避免这些问题,我们就要知道我们需要的组件列表是哪些,可以用 -Ddebug 的方式来帮助我们明确地定位:

mvn spring-boot:run -Ddebug … ========================= AUTO-CONFIGURATION REPORT =========================   Positive matches: -----------------     DispatcherServletAutoConfiguration       - @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)       - found web application StandardServletEnvironment (OnWebApplicationCondition)  ... 

接着拷贝 Positive matches 中列出的信息:

DispatcherServletAutoConfiguration 
EmbeddedServletContainerAutoConfiguration
ErrorMvcAutoConfiguration
HttpEncodingAutoConfiguration
HttpMessageConvertersAutoConfiguration
JacksonAutoConfiguration
JmxAutoConfiguration
MultipartAutoConfiguration
ServerPropertiesAutoConfiguration
PropertyPlaceholderAutoConfiguration
ThymeleafAutoConfiguration
WebMvcAutoConfiguration
WebSocketAutoConfiguration

然后来更新项目配置,显式地引入这些组件,引入之后,再运行一下应用确保没有错误发生:

@Configuration
@Import({
        DispatcherServletAutoConfiguration.
class,
        EmbeddedServletContainerAutoConfiguration.
class,
        ErrorMvcAutoConfiguration.
class,
        HttpEncodingAutoConfiguration.
class,
        HttpMessageConvertersAutoConfiguration.
class,
        JacksonAutoConfiguration.
class,
        JmxAutoConfiguration.
class,
        MultipartAutoConfiguration.
class,
        ServerPropertiesAutoConfiguration.
class,
        PropertyPlaceholderAutoConfiguration.
class,
        ThymeleafAutoConfiguration.
class,
        WebMvcAutoConfiguration.
class,
        WebSocketAutoConfiguration.
class,
})
public class SampleWebUiApplication {}


在上面的代码中,我们可以删掉我们不需要的组件信息,来提高应用的性能,比如在我的项目中,不需要 JMX 和 WebSocket 功能,我就删掉了它们。删掉之后,再次运行项目,确保一切正常。

将Servlet容器变成Undertow

默认情况下,Spring Boot 使用 Tomcat 来作为内嵌的 Servlet 容器。我们可以启动项目,然后用 VisualVM 或者 JConsole 来查看应用所占的内存情况:

Spring Boot 性能优化

以上是我使用 Spring Boot 的默认方式启动应用后,用 VisualVM 监控到的内存的占用情况:堆内存占用 110M,16 个线程被开启。

可以将 Web 服务器切换到 Undertow 来提高应用性能。Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品,是 Wildfly 默认的 Web 服务器。首先,从依赖信息里移除 Tomcat 配置:

<exclusions>
        
<exclusion>
                
<groupId>org.springframework.boot</groupId>
                
<artifactId>spring-boot-starter-tomcat</artifactId>
        
</exclusion>
</exclusions>


然后添加 Undertow:

<dependency>
        
<groupId>org.springframework.boot</groupId>
        
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>


启动项目后,用 VisualVM 监控到的信息显示:堆内存占用 90M,13个线程被开启。

Spring Boot 性能优化

总结

这些都是我们在项目开发中使用到的一些优化 Spring Boot 应用的小技巧,对于大的应用性能的提高还是很明显的。大家可以尝试一下,然后告诉我们你的测试结果。

最后,附上代码,大家可以去这里下载:spring-boot-performance

文中大部分内容参考英国一个架构师的博客 和 DZone 近期发布的文章,在此感谢两位大牛。参考文章及链接:

(1)Spring Boot 性能优化:Spring Boot Performance

(2)Spring Boot 内存优化:Spring Boot Memory Performance

(3)https://www.techempower.com/benchmarks/

(4)Spring 应用程序优化:Optimizing Spring Framework for App Engine Applications

posted @ 2016-09-11 16:37 paulwong 阅读(827) | 评论 (0)编辑 收藏

spring cloud项目读取配置管理

摘要
spring cloud config server配置好了数据库连接信息,这个项目读取config,获取连接信息。这里以mybtis作为列子。从服务器读取jdbc信息后,运行mybatis程序。

确认服务是否成功
http://localhost:8888/demo-config/test
{"name":"demo-config","profiles":["test"],"label":"master","version":"02d28ad4925aa9bd1bf8a48d2edbf04ce61aa45a","propertySources":[{"name":"https://git.oschina.net/penghaozhong/demo.git/demo-config-repo/demo-config-test.properties","source":{"jdbc.url":"jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8","jdbc.username":"root","jdbc.driver":"com.mysql.jdbc.Driver","jdbc.password":"xxxxxx","jdbc.type":"mysql"}}]}

    2. 配置读取配置文件

  



在bootstrap.properties中添加读取配置管理的地址。

3. 读取配置文件属性,这里采用@ConfigurationProperties
/**
 * 读取数据库配置文件
 * 
@author penghaozhong
 *
 
*/
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX, ignoreUnknownFields = false)
public  class  DataSourceProperties {
    
        public DataSourceProperties() {
        super();
    }
        //对应配置文件里的配置键
        public final static String PREFIX="jdbc";    
        
        private String type; 
        private String driver; 
        private String url; 
        private String username; 
        private String password;
        
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
        public String getDriver() {
            return driver;
        }
        public void setDriver(String driver) {
            this.driver = driver;
        }
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }

}


4. 配置mybatis
@Configuration
@MapperScan("com.phz.test.spring.cloud.demo")
@EnableConfigurationProperties(DataSourceProperties.class)
@EnableTransactionManagement
public class MybatisDataSource {

    // mybaits mapper xml搜索路径
    private final static String MAPPERLOCATIONS = "classpath:/mappings/**/*.xml";
    private final static String CONFIGLOCATION = "classpath:/mybatis-config.xml";

    @Autowired
    private  DataSourceProperties dataSourceProperties;
    private DruidDataSource datasource = null;

    @Bean(destroyMethod = "close")
    public  DataSource dataSource(){
        datasource = new DruidDataSource();  
        datasource.setUrl(dataSourceProperties.getUrl());
        datasource.setDbType(dataSourceProperties.getType());
        datasource.setDriverClassName(dataSourceProperties.getDriver());
        datasource.setUsername(dataSourceProperties.getUsername());
        datasource.setPassword(dataSourceProperties.getPassword());
        return datasource;
    }

    @PreDestroy
    public void close() {
        if(datasource != null){
            datasource.close();
        }
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPERLOCATIONS));
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource(CONFIGLOCATION));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.phz.test.spring.cloud.demo.entity");
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

}











posted @ 2016-09-11 16:26 paulwong 阅读(1990) | 评论 (0)编辑 收藏

spring-loaded热部署

什么是spring-loaded?

spring-loaded是一个对于jvm代理运行时期改变类文件的重载(重新加载),它转换类loadtime让他们服从后重新加载。不像“热代码替换”只允许一次简单的改变JVM运行(例如更改方法体)spring-loaded允许您添加/修改/删除/字段/方法构造函数。注释类型/方法/字段/构造函数也可以修改和可以添加/删除/修改值的枚举类型。

有什么好处?

开发测试阶段:能够在启动后动态更改代码调试,无需重启减少切换debug时间(ps:对于eclipse而言,在debug时期只能做到动态更新方法体不能增加)
对于线上测试发布阶段: 能够在出现问题后直接替换class文件而不重启应用(ps:对于外部提供的服务jar形式同样能做到)
怎么使用?

项目地址

https://github.com/spring-projects/spring-loaded

第一步:下载文件
http://repo.spring.io/release/org/springframework/springloaded/1.2.5.RELEASE/springloaded-1.2.5.RELEASE.jar

第二步:配置jvm启动参数

eclipse
eclipse:run as --> run configurations --> arguments -->> VM arguments
-javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar 
-noverify -Dspringloaded=verbose
详细描述:
-javaagent: 配置java代理使用下载后的jar包路径
-noverify: 禁用字节码验证
-Dspringloaded=verbose 显示springloaded时的详细信息


image

java命令启动

java -javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar -noverify Test  类似 

java jar包动态替换

1.打成runnable Jar
2.命令启动:

java -javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar -noverify -Dspringloaded=watchJars=main.jar main.jar

/**  * 类Test.java的实现描述:TODO 类实现描述   * @author Administrator 2016年7月4日 下午4:55:59  */ public class Test {     public static void main(String[] args) throws InterruptedException {          while(true) {             try {                 println();                 Thread.sleep(1000);             } catch (Throwable e) {                  e.printStackTrace();              }          }       }     public static void println() {          System.out.println("112222221222222");          }  } 

改变为

/**  * 类Test.java的实现描述:TODO 类实现描述   * @author Administrator 2016年7月4日 下午4:55:59  */ public class Test {     public static void main(String[] args) throws InterruptedException {          while(true) {             try {                 println();                 Thread.sleep(1000);             } catch (Throwable e) {                  e.printStackTrace();              }          }       }     public static void println() {          System.out.println("test replace jar");          }  } 
3.重新打包替换
PS:实测在window下无用 手上无linux机器待测试




1

posted @ 2016-09-11 10:40 paulwong 阅读(1985) | 评论 (0)编辑 收藏

MICROSERVICE 资源

Spring Cloud 为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性 Token、全局锁、决策竞选、分布式会话和集群状态)操作的开发工具。使用 Spring Cloud 开发者可以快速实现上述这些模式。

主要有eureka做服务发现、config做分布式配置、zuul做api-gateway、feign做客户端负载均衡、hystrix做断路器、turbine做聚合的monitor、graphite做指标监控。

http://blog.csdn.net/liaokailin/article/category/6212338


MICROSERVICE WITH SPRING-CLOUD.
https://github.com/kennyk65/Microservices-With-Spring-Student-Files




!!!spring-cloud-study
http://git.oschina.net/itmuch/spring-cloud-study

posted @ 2016-09-01 21:46 paulwong 阅读(463) | 评论 (0)编辑 收藏

Disruptor

一个ring buffer实现多线程通信。由于他们之间通信不需要锁所以性能有很大的提高。

Disruptor更多的应用在高速事务中,利用JVM的伪内存,这也可能是它为LMAX而生的,和akka的应用场景不一样。

一般编写并发应用程序马上想到多线程或者多进程。但多线程需要处理资源竞争,共享访问等问题,搞不好容易出现死锁,当程序规模比较大时,排查难度很大。 Actor模型提供了另一种编写并发应用程序的思路。 有点类似Node.JS的基于事件异步处理. (其实我觉得基于消息异步和基于事件异步是一回事)

什么是基于消息异步呢?很简单,比如要开展一个项目,需要多人协作。作为项目经理的你,只需要像手下发出命令,个人各干各的,互不干扰。做完了就回送一个消息给项目经理,项目经理再分派新的任务. (可能比喻得不恰当,但Actor的基本思路就是这样,你不需要考虑资源共享和线程并发什么的, Actor库屏蔽了这些底层的实现细节 . 每个Actor就相当于一个人或者叫一个处理者,他们的职责很单一,就是响应对方发来的消息,做出响应,并回送一个响应消息。 每个Actor负责做自己的份内事,最后有一个调度角色的Actor将所有Actor管理起来,形成一个整体)

Akka 是针对Scala和Java的Actor库,JActor是一个用纯Java编写的Actor库。 

https://github.com/LMAX-Exchange/disruptor
http://www.oschina.net/p/disruptor

posted @ 2016-06-05 18:19 paulwong 阅读(724) | 评论 (0)编辑 收藏

JPA概要

1 JPA概述

JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM)以及实体对象持久化的标准接口。

JPA是JSR-220(EJB3.0)规范的一部分,在JSR-220中规定实体对象(EntityBean)由JPA进行支持。

所以JPA不局限于EJB3.0,而是作为POJO持久化的标准规范,可以脱离容器独立运行,开发和测试更加方便。

JPA在应用中的位置如下图所示:

 

JPA维护一个Persistence Context(持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:

  1. ORM元数据。JPA支持annotion或xml两种形式描述对象-关系映射。
  2. 实体操作API。实现对实体对象的CRUD操作。
  3. 查询语言。约定了面向对象的查询语言JPQL(Java Persistence Query Language)。

JPA的主要API都定义在javax.persistence包中。如果你熟悉Hibernate,可以很容易做出对应:

 
org.hibernatejavax.persistence说明
cfg.ConfigurationPersistence读取配置信息
SessionFactoryEntityManagerFactory用于创建会话/实体管理器的工厂类
SessionEntityManager提供实体操作API,管理事务,创建查询
TransactionEntityTransaction管理事务
QueryQuery执行查询

2 实体生命周期

实体生命周期是JPA中非常重要的概念,描述了实体对象从创建到受控、从删除到游离的状态变换。对实体的操作主要就是改变实体的状态。

JPA中实体的生命周期如下图:

  1. New,新创建的实体对象,没有主键(identity)值
  2. Managed,对象处于Persistence Context(持久化上下文)中,被EntityManager管理
  3. Detached,对象已经游离到Persistence Context之外,进入Application Domain
  4. Removed, 实体对象被删除

EntityManager提供一系列的方法管理实体对象的生命周期,包括:

  1. persist, 将新创建的或已删除的实体转变为Managed状态,数据存入数据库。
  2. remove,删除受控实体
  3. merge,将游离实体转变为Managed状态,数据存入数据库。

如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。

3 实体关系映射(ORM)

3.1 基本映射

 
对象端数据库端annotion可选annotion
ClassTable@Entity@Table(name="tablename")
propertycolumn@Column(name = "columnname")
propertyprimary key@Id@GeneratedValue 详见ID生成策略
propertyNONE@Transient 

3.2 ID生成策略

ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略

  1. GeneratorType.AUTO ,由JPA自动生成
  2. GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)
  3. GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如Oracle)
  4. GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:

    @TableGenerator( name="myGenerator", table="GENERATORTABLE", pkColumnName = "ENTITYNAME", pkColumnValue="MyEntity", valueColumnName = "PKVALUE", allocationSize=1 )

    @GeneratedValue(strategy = GenerationType.TABLE,generator="myGenerator")

3.3 关联关系

JPA定义了one-to-one、one-to-many、many-to-one、many-to-many 4种关系。

对于数据库来说,通常在一个表中记录对另一个表的外键关联;对应到实体对象,持有关联数据的一方称为owning-side,另一方称为inverse-side。

为了编程的方便,我们经常会希望在inverse-side也能引用到owning-side的对象,此时就构建了双向关联关系。 在双向关联中,需要在inverse-side定义mappedBy属性,以指明在owning-side是哪一个属性持有的关联数据。

对关联关系映射的要点如下:

 
关系类型Owning-SideInverse-Side
one-to-one@OneToOne@OneToOne(mappedBy="othersideName")
one-to-many / many-to-one@ManyToOne@OneToMany(mappedBy="xxx")
many-to-many@ManyToMany@ManyToMany(mappedBy ="xxx")

其中 many-to-many关系的owning-side可以使用@JoinTable声明自定义关联表,比如Book和Author之间的关联表:

@JoinTable(name = "BOOKAUTHOR", joinColumns = { @JoinColumn(name = "BOOKID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "AUTHORID", referencedColumnName = "id") })

关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):

通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。

通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:

  • CascadeType.MERGE 级联更新
  • CascadeType.PERSIST 级联保存
  • CascadeType.REFRESH 级联刷新
  • CascadeType.REMOVE 级联删除
  • CascadeType.ALL 级联上述4种操作

3.4 继承关系

JPA通过在父类增加@Inheritance(strategy=InheritanceType.xxx)来声明继承关系。A支持3种继承策略:

  1. 单表继承(InheritanceType.SINGLETABLE),所有继承树上的类共用一张表,在父类指定(@DiscriminatorColumn)声明并在每个类指定@DiscriminatorValue来区分类型。
  2. 类表继承(InheritanceType.JOINED),父子类共同的部分公用一张表,其余部分保存到各自的表,通过join进行关联。
  3. 具体表继承(InheritanceType.TABLEPERCLASS),每个具体类映射到自己的表。

其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。

4 事件及监听

通过在实体的方法上标注@PrePersist,@PostPersist等声明即可在事件发生时触发这些方法。

5 Query Language 查询语言

JPA提供两种查询方式,一种是根据主键查询,使用EntityManager的find方法:

T find(Class entityClass, Object primaryKey)

另一种就是使用JPQL查询语言。JPQL是完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似。

使用EntityManager的createQuery方法:

Query createQuery(String qlString)

5.1 使用参数

可以在JPQL语句中使用参数。JPQL支持命名参数和位置参数两种参数,但是在一条JPQL语句中所有的参数只能使用同一种类型。

举例如下:

  • 命令参数

Query query = em.createQuery("select p from Person p where p.personid=:Id"); query.setParameter("Id",new Integer(1));

  • 位置参数

Query query = em.createQuery("select p from Person p where p.personid=?1"); query.setParameter(1,new Integer(1));

5.2 命名查询

如果某个JPQL语句需要在多个地方使用,还可以使用@NamedQuery 或者 @NamedQueries在实体对象上预定义命名查询。

在需要调用的地方只要引用该查询的名字即可。

例如:

@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1")

@NamedQueries({ @NamedQuery(name="getPerson1", query= "FROM Person WHERE personid=?1"), @NamedQuery(name="getPersonList", query= "FROM Person WHERE age>?1") })

Query query = em.createNamedQuery("getPerson");

5.3 排序

JPQL也支持排序,类似于SQL中的语法。例如: Query query = em.createQuery("select p from Person p order by p.age, p.birthday desc");

5.4 聚合查询

JPQL支持AVG、SUM、COUNT、MAX、MIN五个聚合函数。例如:

Query query = em.createQuery("select max(p.age) from Person p"); Object result = query.getSingleResult(); String maxAge = result.toString();

5.5 更新和删除

JPQL不仅用于查询,还可以用于批量更新和删除。

如:

Query query = em.createQuery("update Order as o set o.amount=o.amount+10"); //update 的记录数 int result = query.executeUpdate();

Query query = em.createQuery("delete from OrderItem item where item.order in(from Order as o where o.amount<100)"); query.executeUpdate();

query = em.createQuery("delete from Order as o where o.amount<100"); query.executeUpdate();//delete的记录数

5.6 更多

与SQL类似,JPQL还涉及到更多的语法,可以参考:http://docs.oracle.com/cd/E11035_01/kodo41/full/html/ejb3_langref.html

6 事务管理

JPA支持本地事务管理(RESOURCELOCAL)和容器事务管理(JTA),容器事务管理只能用在EJB/Web容器环境中。

事务管理的类型可以在persistence.xml文件中的“transaction-type”元素配置。

JPA中通过EntityManager的getTransaction()方法获取事务的实例(EntityTransaction),之后可以调用事务的begin()、commit()、rollback()方法。

Date: 2012-12-30 16:46:29 CST

Author: Holbrook

Org version 7.8.11 with Emacs version 24

posted @ 2016-06-04 16:08 paulwong 阅读(554) | 评论 (0)编辑 收藏

IM资源

kafka-examples视频上的例子
https://github.com/gwenshap/kafka-examples

zer0MQTTServer
https://github.com/zer0Black/zer0MQTTServer
http://www.w2bc.com/Article/85741

moquette
https://github.com/andsel/moquette

融云 Blog
http://www.rongcloud.cn/pricing

EMQTT
http://emqtt.com/docs

群聊天系统,高吞吐量,高转发量,后端什么架构?
http://www.v2ex.com/t/92125

用Java实现一个IM的server端需要熟悉哪些技术点或框架?
http://www.zhihu.com/question/37090498

使用Java API创建(create),查看(describe),列举(list),删除(delete)Kafka主题(Topic)--转载
http://www.cnblogs.com/davidwang456/p/4313784.html

kafka中文教程
http://orchome.com/3

Kafka入门经典教程
http://www.open-open.com/lib/view/open1455842076964.html

Kafka深度解析
http://www.open-open.com/lib/view/open1457055194765.html

大型网站架构系列:分布式消息队列(一)
http://www.open-open.com/lib/view/open1453645976605.html



posted @ 2016-05-23 22:03 paulwong 阅读(522) | 评论 (0)编辑 收藏

Eclipse使用git最简易流程

git有诸多好处,网上都说的很清楚了,在这里我不再赘述。对于我来说,私下里想做一些项目,而又不能很好的保存自己的代码和进行版本控制,这时候,就用到了git。下面,就以我个人为例讲讲git从0开始如何安装使用。

Step1 准备工作

msysgit,下载地址为http://msysgit.github.io/

Eclipse IDE for Java EE Developers(必须是这个,自带Egit,或者自行安装Egit插件),在Eclipse官网下载,地址http://www.eclipse.org/downloads/

github账号,github官网注册一个,地址https://github.com/

廖雪峰的git教程,地址为http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000,讲得很好,我大概花了3个晚上的时间看,如果仅仅是为了安装上,可以先暂时略过,后面再看。

Step2 msysgit安装

一路next就行,注意可能会遇到360的拦截(该死,点击允许本程序所有操作或者安装前就退出360即可)。

Step3 git用户名和邮箱配置

单击开始菜单,打开Git Bush,

教程1

 

输入以下命令

git config --global user.name "Your Name"

git config --global user.email email@example.com

用户名和邮箱替换为自己的,为本台机器所有仓库指定用户名和email地址。

 

Step4 创建SSH Key

因为本地git仓库和远程的github仓库之间是通过ssh加密的,所以,还需要创建一个SSH Key。

输入以下命令

ssh-keygen -t rsa -C youremail@example.com

同样将邮箱替换为自己的。在路径C盘->用户->用户名,找到.ssh文件夹,打开就可以看到两个文件id_rsa和id_rsa.pub,

打开id_rsa.pub,将其中的内容复制下来(这就相当于一把钥匙,持有钥匙的人可以访问你的内容,下一个小结讲解这把钥匙的用途),下面是我的

教程2

 

Step5 与github上的仓库相关联

注册一个github账号

注册完成之后,点击右上角的settings(就是那个齿轮,设置的图标)

教程3

 

进入设置页面

image

选SSH Keys,点击add SSH key,就可以添加了,title随便填写,将从id_rsa.pub中复制的内容粘贴到这里。从此,gitgub仓库就能够识别你本地的仓库了。也就是说,你离将本地代码推送到github上,或者将github上的代码克隆到本地,就只差一步了。(可参考廖雪峰前辈的教程先进行在非Eclipse下的操作,更有感觉)

 

Step6 Egit的使用

首先,登入你的github账号,点击加号,选择New repositrory,创建一个新的仓库,如图所示。

教程6

名字叫做test2,其他都默认就好。

点击绿色按钮创建,就创建好了一个github仓库。

教程7

将地址复制下来,保存好备用。

 

打开Eclipse,像平常一样,新建一个web工程,或者其他你常用的工程。我这里工程叫做scott

{4$7`FVS]283EW(~80FR(JT

右击项目,选择Team->Share Project,弹出如下窗口。

image

选择git,next,在弹出的界面上,①先勾选Use or create……,②createrepository,③勾选工程,④finish。

教程8

这时候,你就创建好了一个git工程,右击工程,选择Team,你可以像svn一样进行commit,synchronize等操作,但是,remote操作中,push是灰色的,表示还不能将代码推送到github仓库上去。

教程10

这时,打开Git Repository视图(具体怎么打开百度去,这都是基本常识了)

教程11

右键选择Create Remote,点OK

image

点击Change,弹出如下界面

image

URI输入之前在github上拷贝的地址,下面的内容会自动填上。你只需要再填上github上的用户名和密码即可。finish,save。

接下来的几步就简单了,在项目里面随便写点代码,然后先将新增的代码提交到本地,例如,将cat类,先add to index,然后commit。这时候,右键工程,Team->remote中就有了push选项,意味着你可以将本地代码保存在github中了。祝贺你!

教程12

点击Push,点击add All Branches Spec,将本地版本库添加,然后Finish,后面一路Next,再次查看github,可以看到代码已经成功推送。

最后,看看我们在github上的代码吧!

作者: 张万帆 
欢迎任何形式的转载,但请务必注明出处。

posted @ 2016-05-15 18:17 paulwong 阅读(1260) | 评论 (0)编辑 收藏

Eclipse上安装GIT插件EGit及使用

一、Eclipse上安装GIT插件EGit

 

      Eclipse的版本eclipse-java-helios-SR2-win32.zip(在Eclipse3.3版本找不到对应的 EGit插件,无法安装)

 

     

 

 

 

EGit插件地址:http://download.eclipse.org/egit/updates

 

OK,随后连续下一步默认安装就可以,安装后进行重启Eclipse

 

二、在Eclipse中配置EGit

 

准备工作:需要在https://github.com 上注册账号

 

Preferences > Team > Git > Configuration

 

 

这里的user.name 是你在https://github.com上注册用户名

 

 

user.email是你在github上绑定的邮箱。在这里配置user.name即可

 

 

三、新建项目,并将代码提交到本地的GIT仓库中

 

1、新建项目 git_demo,并新建HelloWorld.java类

 

 

2、将git_demo项目提交到本地仓库,如下图

 

 

 

 

 

 

到此步,就成功创建GIT仓库。但文件夹处于untracked状态(文件夹中的符号”?”表示),下面我们需要提交代码到本地仓库,如下图

 

 

 

 

 

 

 

OK,这样代码提交到了本地仓库

 

 

四:将本地代码提交到远程的GIT仓库中

 

准备工作:在https://github.com上创建仓库

 

 

 

 

 

 

点击“Create repository” ,ok,这样在github上的仓库就创建好了。

 

注意创建好远程仓库后,点击进去,此时可以看到一个HTTP地址,如红线框,这个是你http协议的远程仓库地址

 

 

准备工作做好了,那开始将代码提交到远程仓库吧

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

OK,这样提交远程GIT就完成了,可以在https://github.com核对一下代码是否已经提交

 

 

注意的问题

   如果是首次提交会第一步:先在本地建立一个一样的仓库,称本地仓库。

          第二步:在本地进行commit操作将把更新提交到本地仓库;

          第三步: 将服务器端的更新pull到本地仓库进行合并,最后将合并好的本地仓库push到服务器端,这样就进行了一次远程提交。

 

  如果非首次提交同样的道理

          第一步:将修改的代码commit操作更新到本地仓库;

         第二步:第三步: 将服务器端的更新pull到本地仓库进行合并,最后将合并好的本地仓库push到服务器端

 

 

posted @ 2016-05-15 17:46 paulwong 阅读(523) | 评论 (0)编辑 收藏

排版六原则


作者: 阮一峰

日期: 2010年10月16日

上个月,我贴了《图形化简历》

几天后,就收到了秋叶老师的来信,希望与我探讨一些设计问题。他写过一本畅销书《说服力-让你的PPT会说话》,眼下正在写续集。

我看了新书的样章,觉得很不错,有些内容很值得分享。

====================================

首先,我们先看一个例子。良好的设计如何使得一个平庸的文档脱胎换骨。下面是一张大学生的求职简历,再普通不过了,想要引起招聘经理的注意,恐怕很难。

秋叶老师对它进行了简单的排版,还是一张表格,还是黑白配色,没有使用任何图形元素,效果却完全不一样了。

真是令人眼前一亮,不由自主地想多看几眼。这就是优秀设计的作用:它让你脱颖而出。

====================================

秋叶老师把他的排版心得,总结为六个原则:对齐,聚拢,重复,对比,强调,留白。我是这样理解的:

一、对齐原则

  相关内容必须对齐,次级标题必须缩进,方便读者视线快速移动,一眼看到最重要的信息。

二、聚拢原则

  将内容分成几个区域,相关内容都聚在一个区域中。段间距应该大于段内的行距。

三、留白原则

  千万不要把页面排得密密麻麻,要留出一定的空白,这本身就是对页面的分隔。这样既减少了页面的压迫感,又可以引导读者视线,突出重点内容。

四、降噪原则

  颜色过多、字数过多、图形过繁,都是分散读者注意力的"噪音"。

五、重复原则

  多页面排版时,注意各个页面设计上的一致性和连贯性。另外,在内容上,重要信息值得重复出现。

六、对比原则

  加大不同元素的视觉差异。这样既增加了页面的活泼,又方便读者集中注意力阅读某一个子区域。

====================================

下面用一个PPT的例子,演示排版六原则。

上面这张ppt有两个毛病。一是字数太多,抓不住重点;二是右边没有对齐,使得读者的视线只能一行行地从行首到行尾移动,不能直上直下。

现在进行修改。

第一步,根据"聚拢原则",将六点分成六个区域。

第二步,根据"降噪原则",将每一点分成"小标题"和"说明文字"两部分。

第三步,根据"对齐原则",将每一个部分、每一种元素对齐。

第四步,根据"对比原则",加大"小标题"和"说明文字"在字体和颜色上的差异。

第五步,根据"留白原则",留出一定的空白。

页面的可读性大大增加。

(完)

posted @ 2016-05-06 17:55 paulwong 阅读(408) | 评论 (0)编辑 收藏

MyThrift 0.0.1 发布,轻量级 RPC 服务框架

1)什么是MyThrift?

基于以下组件打造的一款轻量级RPC服务框架:thrift(facebook出品)、commons-pool(socket连接池)、自研服务治理框架(zookeeper).

2)为什么要写MyThrift?

2.1)提出问题:

    各种存储越来越多(redis,mysql,hdfs,hbase,mq),

    让web开发人员自己访问存储,并保证性能,是一件高要求的事情。

    造成的结果就是软件开发进度缓慢,性能低下,各种bug.

2.2)分析问题:

    RPC框架:考虑到本人看过thrift的源码,尤其是针对网络模块非常熟悉,并在实际生产环境中使用过,thrift性能不错。

    TCP/IP:有过2年的TCP/IP报文分析经验,从链路层到应用层都很熟悉。

    ZooKeeper:在MySQL-Binlog项目中攒出了一些使用经验,同时积极吸收Motan,JACK,HArpc等兄弟软件的优秀理念部分。

    连接池:使用commons-pool连接池组件。

2.3)解决问题:

    糅合thrift,zookeeper,commons-pool打造一款轻量级、性能高、上手容易的rpc调用框架,

    使得架构师和后台开发人员可以将各种复杂存储的IO访问对外暴露为服务(其实就是跨机器的普通函数调用)

    这样web开发人员可以专注于业务逻辑,加速产品迭代,对企业带来的好处不用多说!

本次版本内容:

1)server端进行网络模型参数调优

2)client端有连接池

3)支持server注册多个thrift的处理器Processor

可用于集中式负载的服务化组件,达到生产环境级别,经历过生产环境的考验,可放心使用。

http://git.oschina.net/qiangzigege/MyThrift 

内附非常详细+多图预警的《MyThrift在线使用文档.md》,0基础轻松上手!

posted @ 2016-05-05 17:02 paulwong 阅读(1627) | 评论 (0)编辑 收藏

Couchbase介绍,更好的Cache系统

在移动互联网时代,我们面对的是更多的客户端,更低的请求延迟,这当然需要对数据做大量的 Cache 以提高读写速度。

术语

  • 节点:指集群里的一台服务器。

现有 Cache 系统的特点

目前业界使用得最多的 Cache 系统主要是 memcached 和 redis。 这两个 Cache 系统都有都有很大的用户群,可以说是比较成熟的解决方案,也是很多系统当然的选择。 不过,在使用 memcached 和 redis 过程中,还是碰到了不少的问题和局限:

  • Cluster 支持不够。在扩容、负载均衡、高可用等方面存在明显不足。
  • 持久化支持不好,出现问题后恢复的代价大。memcached 完全不支持持久化,redis 的持久化会造成系统间歇性的负载很高。

我期待的理想 Cache 系统

良好的 cluster 支持

  • Key 可以动态分散(Auto Sharding)在不同的服务器上,可以通过动态添加服务器节点增加系统容量。
  • 没有单点失效,任何一个单点都不会造成数据不可访问。
  • 读写负载可以均匀分布在系统的不同节点上。

支持异步持久化支持

  • 方便快速恢复,甚至可以直接用作 key/value 数据库。 经常在跟业界朋友交流时,会提到用 key 分段的方法来做容量扩展以及负载均衡。但是用静态的 key 分段会有不少问题:
  • Cache 系统本身及使用 cache 的客户端都需要预设一个分段逻辑,这个逻辑后期如果需要调整将会非常困难。不能解决单点失效的问题,还需要额外的手段。运维需要更多的人为参与,避免 key 超出现有分区,一旦出现 key 找不到对应服务器,访问直接失败。

最接近需求的系统:Couchbase

基于这些想法,我花了几天时间在 google, stack overflow, quora 上看了很多大家关于 cache cluster 的讨论,找到一个比较新系统 Couchbase。

mem vs cbmemcached VS couchbase

Couchbase 的集群设计对等网

Couchbase 群集所有点都是对等的,只是在创建群或者加入集群时需要指定一个主节点,一旦结点成功加入集群,所有的结点对等。

high_level_architecture

图片来源:couchbase.com

对等网的优点是,集群中的任何节点失效,集群对外提供服务完全不会中断,只是集群的容量受影响。 Smart Client

由于 couchbase 是对等网集群,所有的节点都可以同时对客户端提供服务,这就需要有方法把集群的节点信息暴露给客户端,couchbase 提供了一套机制,客户端可以获取所有节点的状态以及节点的变动,由客户端根据集群的当前状态计算 key 所在的位置。 vBucket

vBucket 概念的引入,是 couchbase 实现 auto sharding,在线动态增减节点的重要基础。

简单的解释 vBucket 可以从静态分片开始说起,静态分片的做法一般是用 key 算出一个 hash,得到对应的服务器,这个算法很简单,也容易理解。如以下代码所示:

servers = ['server1:11211', 'server2:11211', 'server3:11211'] server_for_key(key) = servers[hash(key) % servers.length] 

但也有几个问题:

  • 如果一台服务器失效,会造成该分片的所有 key 失效。
  • 如果服务器容量不同,管理非常麻烦。
  • 前面提到过,运维、配置非常不方便。

为了把 key 跟服务器解耦合,couchbase 引入了 vBucket。可以说 vBucket 代表一个 cache 子集,主要特点:

  • key hash 对应一个 vBucket,不再直接对应服务器。
  • 集群维护一个全局的 vBucket 与服务器对应表。
  • 前面提到的 smart client 重要的功能就是同步 vBucket 表。

如以下代码所示:

servers = ['server1:11211', 'server2:11211', 'server3:11211'] vbuckets = [0, 0, 1, 1, 2, 2] server_for_key(key) = servers[vbuckets[hash(key) % vbuckets.length]] 

vBucket

图片来源:http://dustin.sallings.org/2010/06/29/memcached-vbuckets.html

由于 vBucket 把 key 跟服务器的静态对应关系解耦合,基于 vBucket 可以实现一些非常强大有趣的功能,例如:

  • Replica,以 vBucket 为单位的主从备份。如果某个节点失效,只需要更新 vBucket 映射表,马上启用备份数据。
  • 动态扩容。新增加一个节点后,可以把部分 vBucket 转移到新节点上,并更新 vBucket 映射表。

vBucket 非常重要,以后可以单独写一篇文章分享。

总结

  • Couchbase 的对等网设计,smart client 直接获取整的集群的信息,在客户端实现负载均衡,整个集群没有单点失效,并且完全支持平行扩展。
  • vBucket 的引入,完全实现了 auto sharding,可以方便灵活的把数据的子集在不同节点上移动,以实现集群动态管理。
  • Couchbase 有一个非常专业的 web 管理界面,并且支持通过 RESTful API 管理,这也是 memcached, redis 不能企及的。
  • 如果只是做 key/value 的 cache,Couchbase 可以完全取代 memcached。
  • Couchbase 已经被我们在生产环境中大量采用。

posted @ 2016-05-04 15:00 paulwong 阅读(562) | 评论 (0)编辑 收藏

[转]巅峰对决 - 框架的性能比较

转自:http://colobu.com/2016/04/25/performance-comparison-of-frameworks/
这句话也可以应用在软件开发上,"无快不破"强调的是软件的性能。我陆陆续续写了多篇各种框架的文章,也在github上开了多个性能比较的开源项目,本文做一个汇总,以备将来的查找。

  1. 最快的web服务器
  2. 最快的并发框架
  3. 最快的RPC服务器
  4. 最快的websocket框架
  5. 最快的RESTful框架
  6. 最快的Go序列化框架
  7. 最快的Go web框架
  8. 最快的Java序列化框架

posted @ 2016-05-03 14:36 paulwong 阅读(547) | 评论 (0)编辑 收藏

轻量级 RPC 框架新浪微博的 motan

概述

motan是一套高性能、易于使用的分布式远程服务调用(RPC)框架。

功能

  • 支持通过spring配置方式集成,无需额外编写代码即可为服务提供分布式调用能力。

  • 支持集成consul、zookeeper等配置服务组件,提供集群环境的服务发现及治理能力。

  • 支持动态自定义负载均衡、跨机房流量调整等高级服务调度能力。

  • 基于高并发、高负载场景进行优化,保障生产环境下RPC服务高可用。

文档索引

posted @ 2016-04-25 14:22 paulwong 阅读(2058) | 评论 (1)编辑 收藏

dubboss

HTTPCLIENT请求基于NETTY的HTTP服务器,NETTY生成相关的HTTPREQUEST、HTTPRESPONSE,调用SPRING 的DISPATCHSERVLET.SERVICE,将HTTPRESPONSE生成二进制字节,返回HTTPCLIENT。DISPATCHSERVLET同时会调用SPRING的CONTROLLER干活,即以前的SPRING MVC那套可以保留。

缺点:
nginx+tomcat与netty优缺点
http://www.oschina.net/question/1765708_158026

我的架构最开始是 nginx + [netty (tcp) ] 用nginx做负载均衡。后来分布式接口变成了http restful -> nginx + [netty(http) ]

于是参考了netty的官方http例子,开始移植代码,其实移植量不大,只需要把原来的tpc handler里面的逻辑放到http handler里面就可以了。本以为ok了,测试才发现了大量的问题,其实问题本身是由于对http基础掌握的还不够透彻,比如keep-alive属性,这个平时基本不用的东西,当你要自己开发http server时,变的很有用,你不能每次都close channel.你要化很多时间了解header里面每个字段的意思,然后去实现它。再比如一个post请求,你要自己写代码从body里面读数据,解码等。

最终我放弃了netty,回到了tomcat.应为tomcat把http的一切都帮你搞定了。

最终的架构是 nginx + [tomcat(http) ]

tomcat就是针对http层的,所以我建议http还是选择tomcat(或者其他成熟的http-server),并不是说netty不好,而是你的选择问题,netty是一个网络组件,tcp,udp,http都可以弄,但是官方文档都是些hello wolrd级别的。如果你非常了解http结构,完全可以基于netty搞出一个比tomcat牛的http server.

如果做tcp开发,netty不二之选!

dubboss是一个分布式REST服务的框架,实现了如下功能:

  • 基于dubbo的RPC
  • 基于netty的应用服务器
  • 基于springmvc的REST调用
  • 基于ngnix的负载均衡

https://github.com/leiguorui/dubboss

posted @ 2016-04-20 13:04 paulwong 阅读(1689) | 评论 (0)编辑 收藏

!!!架构网站内容不错

http://colobu.com/categories/%E6%9E%B6%E6%9E%84/page/2/

posted @ 2016-04-19 17:54 paulwong 阅读(466) | 评论 (0)编辑 收藏

SPRING MVC整合NETTY

http://wenku.baidu.com/view/4573aba4ce2f0066f53322e8.html

posted @ 2016-04-19 13:27 paulwong 阅读(2139) | 评论 (0)编辑 收藏

Tigase服务器安装以及集群

     摘要: 安装第一台tigase服务器安装tigase服务器所需环境。[root@tigase3~]# yum install java-1.7.0-openjdk.x86_64 java-1.7.0-openjdk-devel.x86_64下载tigase服务器软件包。[root@tigase3tmp]#wget https://projects.tigase.org/attachments/d...  阅读全文

posted @ 2016-04-19 10:17 paulwong 阅读(813) | 评论 (0)编辑 收藏

在 Java 应用程序中使用 Elasticsearch

http://www.ibm.com/developerworks/cn/java/j-use-elasticsearch-java-apps/index.html?ca=drs-

posted @ 2016-04-18 23:39 paulwong 阅读(532) | 评论 (0)编辑 收藏

集中式日志系统 ELK 协议栈详解


http://www.ibm.com/developerworks/cn/opensource/os-cn-elk/index.html?ca=drs-

posted @ 2016-04-18 23:33 paulwong 阅读(551) | 评论 (0)编辑 收藏

DUBBO简介

http://dubbo.io/User+Guide-zh.htm

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

  • 单一应用架构
    • 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
    • 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。
  • 垂直应用架构
    • 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
    • 此时,用于加速前端页面开发的 Web框架(MVC) 是关键。
  • 分布式服务架构
    • 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
    • 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。
  • 流动计算架构
    • 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
    • 此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。

posted @ 2016-04-15 11:28 paulwong 阅读(500) | 评论 (0)编辑 收藏

Netty是什么?

本质:JBoss做的一个Jar包

 

目的:快速开发高性能、高可靠性的网络服务器和客户端程序

 

优点:提供异步的、事件驱动的网络应用程序框架和工具

 

通俗的说:一个好使的处理Socket的东东

 

 

如果没有Netty?

 

远古:java.net + java.io

 

近代:java.nio

 

其他:Mina,Grizzly

 

为什么不是Mina?

 

1、都是Trustin Lee的作品,Netty更晚;

2、Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题;

3、Netty的文档更清晰,很多Mina的特性在Netty里都有;

4、Netty更新周期更短,新版本的发布比较快;

5、它们的架构差别不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度非常高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi);

6、Netty比Mina使用起来更简单,Netty里你可以自定义的处理upstream events 或/和 downstream events,可以使用decoder和encoder来解码和编码发送内容;

7、Netty和Mina在处理UDP时有一些不同,Netty将UDP无连接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,可以把UDP当成"面向连接"的协议,而要Netty做到这一点比较困难。

 

 

Netty的特性

 

设计

统一的API,适用于不同的协议(阻塞和非阻塞)

基于灵活、可扩展的事件驱动模型

高度可定制的线程模型

可靠的无连接数据Socket支持(UDP)

 

性能

更好的吞吐量,低延迟

更省资源

尽量减少不必要的内存拷贝

 

安全

完整的SSL/TLS和STARTTLS的支持

能在Applet与Android的限制环境运行良好

 

健壮性

不再因过快、过慢或超负载连接导致OutOfMemoryError

不再有在高速网络环境下NIO读写频率不一致的问题

 

易用

完善的JavaDoc,用户指南和样例

简洁简单

仅信赖于JDK1.5

 

看例子吧!

 

Server端:

 

Java代码  收藏代码
  1. package me.hello.netty;  
  2.   
  3. import org.jboss.netty.bootstrap.ServerBootstrap;  
  4. import org.jboss.netty.channel.*;  
  5. import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;  
  6. import org.jboss.netty.handler.codec.string.StringDecoder;  
  7. import org.jboss.netty.handler.codec.string.StringEncoder;  
  8.   
  9. import java.net.InetSocketAddress;  
  10. import java.util.concurrent.Executors;  
  11.   
  12. /** 
  13.  * God Bless You! 
  14.  * Author: Fangniude 
  15.  * Date: 2013-07-15 
  16.  */  
  17. public class NettyServer {  
  18.     public static void main(String[] args) {  
  19.         ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));  
  20.   
  21.         // Set up the default event pipeline.  
  22.         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  23.             @Override  
  24.             public ChannelPipeline getPipeline() throws Exception {  
  25.                 return Channels.pipeline(new StringDecoder(), new StringEncoder(), new ServerHandler());  
  26.             }  
  27.         });  
  28.   
  29.         // Bind and start to accept incoming connections.  
  30.         Channel bind = bootstrap.bind(new InetSocketAddress(8000));  
  31.         System.out.println("Server已经启动,监听端口: " + bind.getLocalAddress() + ", 等待客户端注册。。。");  
  32.     }  
  33.   
  34.     private static class ServerHandler extends SimpleChannelHandler {  
  35.         @Override  
  36.         public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
  37.             if (e.getMessage() instanceof String) {  
  38.                 String message = (String) e.getMessage();  
  39.                 System.out.println("Client发来:" + message);  
  40.   
  41.                 e.getChannel().write("Server已收到刚发送的:" + message);  
  42.   
  43.                 System.out.println("\n等待客户端输入。。。");  
  44.             }  
  45.   
  46.             super.messageReceived(ctx, e);  
  47.         }  
  48.   
  49.         @Override  
  50.         public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {  
  51.             super.exceptionCaught(ctx, e);  
  52.         }  
  53.   
  54.         @Override  
  55.         public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {  
  56.             System.out.println("有一个客户端注册上来了。。。");  
  57.             System.out.println("Client:" + e.getChannel().getRemoteAddress());  
  58.             System.out.println("Server:" + e.getChannel().getLocalAddress());  
  59.             System.out.println("\n等待客户端输入。。。");  
  60.             super.channelConnected(ctx, e);  
  61.         }  
  62.     }  
  63. }  

 

客户端:

 

Java代码  收藏代码
  1. package me.hello.netty;  
  2.   
  3. import org.jboss.netty.bootstrap.ClientBootstrap;  
  4. import org.jboss.netty.channel.*;  
  5. import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;  
  6. import org.jboss.netty.handler.codec.string.StringDecoder;  
  7. import org.jboss.netty.handler.codec.string.StringEncoder;  
  8.   
  9. import java.io.BufferedReader;  
  10. import java.io.InputStreamReader;  
  11. import java.net.InetSocketAddress;  
  12. import java.util.concurrent.Executors;  
  13.   
  14. /** 
  15.  * God Bless You! 
  16.  * Author: Fangniude 
  17.  * Date: 2013-07-15 
  18.  */  
  19. public class NettyClient {  
  20.   
  21.     public static void main(String[] args) {  
  22.         // Configure the client.  
  23.         ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));  
  24.   
  25.         // Set up the default event pipeline.  
  26.         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  27.             @Override  
  28.             public ChannelPipeline getPipeline() throws Exception {  
  29.                 return Channels.pipeline(new StringDecoder(), new StringEncoder(), new ClientHandler());  
  30.             }  
  31.         });  
  32.   
  33.         // Start the connection attempt.  
  34.         ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8000));  
  35.   
  36.         // Wait until the connection is closed or the connection attempt fails.  
  37.         future.getChannel().getCloseFuture().awaitUninterruptibly();  
  38.   
  39.         // Shut down thread pools to exit.  
  40.         bootstrap.releaseExternalResources();  
  41.     }  
  42.   
  43.     private static class ClientHandler extends SimpleChannelHandler {  
  44.         private BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));  
  45.   
  46.         @Override  
  47.         public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
  48.             if (e.getMessage() instanceof String) {  
  49.                 String message = (String) e.getMessage();  
  50.                 System.out.println(message);  
  51.   
  52.                 e.getChannel().write(sin.readLine());  
  53.   
  54.                 System.out.println("\n等待客户端输入。。。");  
  55.             }  
  56.   
  57.             super.messageReceived(ctx, e);  
  58.         }  
  59.   
  60.         @Override  
  61.         public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {  
  62.             System.out.println("已经与Server建立连接。。。。");  
  63.             System.out.println("\n请输入要发送的信息:");  
  64.             super.channelConnected(ctx, e);  
  65.   
  66.             e.getChannel().write(sin.readLine());  
  67.         }  
  68.     }  
  69. }  

 

Netty整体架构



 

Netty组件

 

ChannelFactory

Boss

Worker

Channel

ChannelEvent

Pipeline

ChannelContext

Handler

Sink

 

Server端核心类

NioServerSocketChannelFactory

NioServerBossPool 

NioWorkerPool

NioServerBoss

NioWorker

NioServerSocketChannel

NioAcceptedSocketChannel

DefaultChannelPipeline

NioServerSocketPipelineSink

Channels

 

 

ChannelFactory

Channel工厂,很重要的类

保存启动的相关参数

 

NioServerSocketChannelFactory

NioClientSocketChannelFactory

NioDatagramChannelFactory

 

这是Nio的,还有Oio和Local的

 

 

SelectorPool

 

Selector的线程池

 

NioServerBossPool 默认线程数:1

NioClientBossPool      1

NioWorkerPool      2 * Processor

NioDatagramWorkerPool

 

 

Selector

 

选择器,很核心的组件

 

NioServerBoss

NioClientBoss

NioWorker

NioDatagramWorker

 

 

Channel

 

通道

 

NioServerSocketChannel

NioClientSocketChannel

NioAcceptedSocketChannel

NioDatagramChannel

 

Sink

负责和底层的交互

如bind,Write,Close等

 

NioServerSocketPipelineSink

NioClientSocketPipelineSink

NioDatagramPipelineSink

 

Pipeline

负责维护所有的Handler

 

ChannelContext

一个Channel一个,是Handler和Pipeline的中间件

 

Handler

对Channel事件的处理器

 

 

ChannelPipeline

 



 

 

 

优秀的设计----事件驱动



 

 

优秀的设计----线程模型



 

注意事项

 

解码时的Position

 

Channel的关闭

 

更多Handler

 

 

Channel的关闭

 

用完的Channel,可以直接关闭;

1、ChannelFuture加Listener

2、writeComplete

 

一段时间没用,也可以关闭

TimeoutHandler

posted @ 2016-04-12 15:19 paulwong 阅读(874) | 评论 (0)编辑 收藏

轻量级分布式 RPC 框架

     摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1、每套服务需搭配一个RPCSERVER2、RPCSERVER作为SPRING 容器的一个BEAN3、在SPRING启动的时候,RPCSERVER会启动一个NETTY服务器端,然后将SPRING...  阅读全文

posted @ 2016-04-12 13:44 paulwong 阅读(936) | 评论 (0)编辑 收藏

为什么开发与测试老掐架呢

让我们思考几个常见的问题:

  • 软件测试的目的是什么?

  • 开发人员能否构建出没有Bug的完美软件?

  • 测人人员和开发人员是什么关系?

  • 软件测试能否保证软件质量?

先闭目冥想五分钟吧,然后可以尝试着回答上面的问题。

计算机先驱 Maurice Wikes 回忆起 1949 年他在英国剑桥工作的情形,在拖着打孔纸带上楼给雏形计算机 EDASC 装载程序时,他看到了自己的未来:

我强烈的意识到,生命中剩下的好日子,都将耗费在给自己的程序找错误上头。

Maurice Wikes告诉我们,没有完美的软件。

我曾经写过一篇荐书文,推荐了温伯格技术思想三部曲中的《颠覆完美软件:软件测试必须知道的几件事》。在这本书里,温伯格也告诉我们,没有完美的软件。所有的开发和测试人员都应该读读那本书。

温伯格在《颠覆完美软件》中几乎讨论所有常见的与软件测试相关的概念、问题和指导思想,所以,在这篇文章里,我只能来吐槽啦,我将从以下几方面列一些常见的现象,希望能引起大家的思考。

  • 测试和开发的关系

  • 流程与标准

  • 资源

  • 态度

测试和开发的关系

测试和开发是对立的吗?

从处理Bug的角度看,似乎可以这么说。开发人员既生产代码,也生产Bug。因为开发人员不可避免地会生产Bug,所以测试人员必须存在,以便在软件交付之前尽可能多地检出Bug,保证交付给客户的软件质量更好一些。一个产Bug,一个挑Bug,看起来似乎是对立的。

在现实中,很多测试团队和开发团队也正是因为这一点而搞得关系不和,甚至真的对立起来。请回想一下你周围发生的与开发和测试相关的事儿,看看有没有遇到过下面的情景:

  • 开发说,测试净找麻烦,客户跟本不可能像他们那样使用软件

  • 测试说,问题总是会在看似极端的条件下产生,用户总是会不经意触碰到看似极端的不可能出现的条件

  • 开发说,测试花在异常情况下的精力比测试主流程还多,不知道轻重缓急

  • 测试说,开发从来不考虑测试的感受,连测都不测就扔给我们

  • 开发说,我都测了,还要测试人员干什么

  • 测试说,这么明显的问题你们都不测一下,把我们测试当垃圾桶啊

  • ……

许许多多类似的问题,让开发和测试的关系从扑朔迷离、相爱相杀走向对立。我见过开发和测试搞冷战某人遇见某人侧脸而过,也见过测试经理和开发经理打架,还见过高层领导故意让测试团队和开发团队关系紧张以为这样可以提高测试效率也能给开发压力最终会产出更高质量的软件……

实际上,测试和开发拥有同一个目的:让软件更完美。测试和开发的关系,是一个问题的两面,应该是相辅相成和平共处的。测试不是为了挑刺儿,他提出的问题也不针对生产软件的开发人员,而仅仅是在努力想让开发人员的产出物看起来更好用。只要开发不将测试提Bug这个行为看成针对个人的行为,一切就有了美好的前提。

否定软件,并不是否定开发软件的人。这是开发和测试都需要明确的一个原则和前提。

还有的人认为开发和测试之关系类似皮与毛,皮之不存毛将焉附?所以有的开发也会因此而有优越感:没我们写软件,你们测试早下岗了!可是,开发不写软件,开发也下岗了耶!

感谢开发的不完美,让测试可以有事可做并练就慧眼。

感谢测试的认真细致和耐心体贴,让开发可以发现自己的不完美并有机会提升自己——那些说我软件不好的,都是为了我好。

资源

别动我们测试的服务器,你们自己搭一个!

我们没环境,不用你们的用谁的?

谁把我们的测试手机拿走了?你们申请一个嘛,老来占我们设备。

谁在用我们的账号?招呼都不打!我要用,赶紧退出来!

有时开发和测试之间也会有资源上的冲突,要有努力的有创造性的解决(我可以负责任地说,装黑苹果不是好办法),不要让大家伙的工作卡在环境上,这是管理者要解决的基本问题。我见过很多非常棒的一线经理,在现实制约下,主动把自己的手机、iPad都贡献出来当做测试设备。这也是解决资源问题的一种办法哦。

流程与标准

你身边的人员会这么抱怨吗:

  • 开发根本不看我们的测试用例,评审邮件从来就不回复

  • 我们一报Bug,开发就说用户根本不可能这么用,还说不知道我们怎么会这么测

  • 送测单里根本不写测试范围或者寥寥几句跟没写一样

  • 开发调整设计从来也不告诉我们

  • 为什么产品经理和UI只和开发讨论需求变更?

  • 为什么发布计划里不给测试预留测试时间?

  • 为什么开发写完代码测都不测就扔给我们?

  • 为什么客户那里发现了问题老问是谁测的、为什么没测出来?

  • 测试老是一声不吭就把Bug优先级设置为Major

  • 测试总是把大量时间花在用户根本不可能用到的功能上

  • 测试分不清哪些什么是重点,你给他说他还老是一堆道理这了那了

  • 测试提的Bug,现象描述也不准确,重现步骤也没有,有的根本就知道是不是误操作

  • 测试老来打断我,一会儿叫一下一会儿叫一下,根本没办法专注开发

  • jira上的Bug重复率太高,一个问题提N遍,难道就不能合并一下?

  • 测试发现Bug,一声招呼都不打就直接告诉老板了,搞得我很被动

  • 测试就是专门挑刺儿的,有劲不往正地儿使,你倒是测测用户常用的功能啊

  • 那么简单的Bug都能流出到用户那里,真不知道测试怎么测的

  • 开发老嫌测试报告数据不漂亮,逼着我们调整

Ok,如果你身边的开发和测试从来没有过类似的问题,那很好,恭喜你,看来你们的团队人nice协作也很顺畅,棒棒哒。

假如你身边充斥着这样嘈杂的抱怨,那说明什么呢?开发、测试、发布这一套流程有问题?还是团队缺乏明确的指向来引导大家向积极、有效的行为靠近?

流程和标准总是有待解释的,再好的规则,歪嘴和尚也能把它念斜……

我们随便挑一个问题吧:为什么开发写完代码测都不测就扔给我们?这个问题普遍存在,它反映出的是程序员和测试人员的工作边界难以界定的矛盾。

程序员会说,我都测一遍,还要你们测试做什么?

测试会说,你测都不测,冒烟都过不了,有没有责任心?

程序员说,要我写测试用例,搭各种环境,遍历各种正常、异常逻辑,我还有没有时间写代码了?

测试会说,我们测试是垃圾桶吗,什么烂玩意儿都直接扔给我们,我们的时间就那么不值钱?

开发会说,测试本来就是干这个的,你不测谁测?

……

像这样的问题,能制定一个标准,说明什么样的逻辑开发要自测覆盖什么样的逻辑可以交给测试来测?能画一条三八线吗?

不能。所以,这个时候,靠谱的一线管理者就显得很重要。如何创造性的发现适合团队的方法来让大家顺畅地协同工作,比标准、制度更重要,这往往依赖于技术管理者的能力和团队成员的意识。没有普适的方法,只有适合这个组织的、此时此地的策略,加油吧,在战斗中摸索出最适合当下的道路。

那什么是靠谱的一线管理者呢?

温伯格《成为技术领导者》一书中对领导职责的定义如下:

领导的职责就是创造这样一个环境,每个人都能在其中发挥出更多的能力。

如果一个技术领导带领的团队,大部分人都能专心做与其能力适配的事情而不用整天泡在与本节前面所列类似的问题里,那他基本上就算是比较靠谱了。

至于像给测试预留多长的测试周期、调整设计要不要通知测试、需求调整要不要测试参与等问题,合理的流程和标准可以起到很大的辅助作用,技术领导者只要依据合理的制度,引导大家有效参与,就可以化解。

态度

场景一:

测试MM对阿猿说发现了一个Bug。  阿猿矢口否认:不可能,绝对不可能!  MM:真的有Bug,你过来看一下!  阿猿:我都不用看,在我这儿好好儿的。  MM:你来看一下嘛……  阿猿:看什么看,肯定你环境问题,动什么东西了吗?重启了吗?

场景二:

测试MM想在jira上提个Bug,先在QQ上对阿猿说:有个Bug,你过来看下?  阿猿:忙着呢,焦头烂额的。  MM:一分钟都用不了,你来看下吧。  阿猿:思路一打断就不好恢复了,等会儿!  MM:你不看我提到jira上了啊。  阿猿:随便,你不就是爱提Bug嘛。

场景三:

测试MM呼叫阿猿:阿猿阿猿,程序又崩溃了,快来看看!  阿猿慢腾腾地起身过来,鼠标点几下:看不出来什么问题,你怎么操作的?  MM:这样点一下,那样,这样,……回车……。  阿猿:重现不了啊,你想办法重现,重现了再叫我,我忙着呢。  MM:……

我曾经画过一张暴漫,以“她发现了一个Bug”为题发布在微信订阅号“程序视界”里,再现类似的场景,感兴趣的可以在订阅号内回复10019查看(点击订阅号底部的帮助菜单里的“所有文章”子菜单也能找到)。

开发和测试的日常工作中,上面的情景不断上演,这其中有一部分原因来自态度。我们有时还能听到类似下面的话:

  • 你Bug里的现象描述根本没用

  • 你根本就没理解这个逻辑,给你说不清楚

  • 测试什么都不懂……

  • 你听我的,我让你怎么测你就怎么测

  • 你这种测法儿,再好的软件都经不起你折腾

  • 用户根本不可能这样用,你们整来整去净瞎耽误工夫

  • 一轮都没测完,你们就给老板说可以按期交付没问题?

  • 你们安排计划时根本不考虑测试,三天,三天怎么可能测得完!

  • ……

有时,有一些开发人员会用技术优势藐视测试,认为测试工作技术含量低,内心认为测试是附属没地位,说话就不太客气……测试会感觉到,反过来也会对开发有意见……就这么,从相敬如宾开始走向嫌怨丛生……

有个朋友的QQ签名档是:没有自我,只有大道。我琢磨,放在软件项目里,也挺适用的。

其实,开发和测试拥有共同的目的:生产高质量软件。具体说,每一个产品、项目、版本都有明确的目标,这些目标是属于开发和测试的,是大家的。我们把共同的目标牢记在心,摆在首位,我们还要想着别人所做的一切,都是针对软件本身,都是在为目标而努力,这样就心平气和多了,就容易从当下的泥沼中超脱出来,求同存异共同前进。

    作者:foruok 微信订阅号“程序视界”(programmer_sight)

    原文:CSDN

    posted @ 2016-04-12 11:01 paulwong 阅读(495) | 评论 (0)编辑 收藏

    Dubbos

         摘要: dubbos主要是基于dubbox的基础上,进行进一步的优化及拓展。Dubbos当前的主要功能支持REST风格远程调用(HTTP + JSON/XML):基于非常成熟的JBoss RestEasy框 架,在dubbo中实现了REST风格(HTTP + JSON/XML)的远程调用,以显著简化企业内部的跨语言交互,同时显著简化企业对外的Open API、无线API甚至AJAX服务端等等的开...  阅读全文

    posted @ 2016-04-12 10:59 paulwong 阅读(700) | 评论 (0)编辑 收藏

    Hack a Wifi Network WPA2/WPA/WEP

    First of all you will need a Linux operating system & a little education about Python
    programming.
    You can install Linux operating system on any Pc or Laptop.
    Note : This tutorial is only for eduactional purpose. The author of this pdf is not responsible for any illegal work. During installing and setting up you can loose all of your data, Do not do if you don’t know about programming.
    Device:
    Use Tp-link TL-WN722N Wifi Adapter for High gain.
    Start! Open Terminal:
    1. airmon-ng check kill
    2. airmon-ng
    3. airmon-ng start wlan0
    4. airodump-ng wlan0mon
    5. (control + c to stop)
    6. airodump-ng wlan0mon —bssid 5C:F9:6A:CD:8A:1D -c 1 -w WPA2
    7. wait for 1 minute, capture handshake
    8. aircrack-ng WPA2-01.cap -w /root/rockyou.txt
    /darkc0de
    /Wpa list 1,2,3

    darkc0de, Wpalist & rockyou.txt are the dictionaries files. You can download these from internet.
    This PDF is made by Malik Mubashir

    posted @ 2016-04-05 16:53 paulwong 阅读(471) | 评论 (0)编辑 收藏

    Walle --web部署系统工具

    Walle 一个web部署系统工具,配置简单、功能完善、界面流畅、开箱即用!支持git、svn版本管理,支持各种web代码发布,PHP,Python,JAVA等代码的发布、回滚,可以通过web来一键完成。

    官网主页 | Github主页

    功能列表

    • 用户分身份注册、登录
    • 开发者发起上线任务申请、部署
    • 管理者审核上线任务
    • 支持多项目部署
    • 支持多项目多任务并行
    • 快速回滚
    • 项目的用户权限管理
    • 部署前准备任务pre-deploy(前置检查)
    • 代码检出后处理任务post-deploy(如vendor)
    • 同步后更新软链前置任务pre-release
    • 发布完毕后收尾任务post-release(如重启)
    • 执行sql构建(不要担心忘记测试环境sql同步)
    • 线上文件指纹确认
    • 支持git、svn版本管理

    posted @ 2016-03-29 22:09 paulwong 阅读(591) | 评论 (0)编辑 收藏

    SHARDING-JDBC

    https://github.com/dangdangdotcom/sharding-jdbc/

    posted @ 2016-03-29 21:26 paulwong 阅读(486) | 评论 (0)编辑 收藏

    想知道吗?CTO 比普通程序员强在哪?

    互联网的蓬勃发展,让无数的程序员身价水涨船高,都变成了「香饽饽」,更有了不少「创业」,「当上 CTO,迎娶白富美的传说」。都说不想当元帅的士兵不是好士兵,我觉得这件事见仁见智,但提升自己的价值,让自己变得更优秀更有竞争力,一定是一线城市的大部分 IT 人内心的追求。

    诚然,并不是所有程序员都会变成 CTO,程序员——>CTO 的路径像是一个漏斗,极少数人沉淀下来,在业界掀起一阵阵飓风。这些 CTO 比起普通的程序员,强在哪?丰富的技术知识只是基础,更重要的是战略眼光,管理把控能力。那么 CTO 所思所想,和普通程序员究竟有什么不同?

     普通的程序员往往只负责模块的开发,代码的优化,和新技术的钻研,哦对我说的是普通程序员,而不是只会 fork 的小白程序员;而走向管理领域的高级程序员也许已经开始负责团队,背负团队进度和效率。而 CTO,往往不仅要考虑优化团队的开发工具、流程,肩负起把控整体技术方向的重任,要具有前瞻性,同时还要对企业绩效负责。尤其是技术驱动型公司,你问这样的公司 CTO 好招么,答案通常是「很难招」。技术选型其实是创业公司最纠结的问题,很多团队往往一上来基于已有的程序员的个人习惯和爱好,选择了一个技术方案,然后到某一天一看,我靠,全是坑(当然,也可能与执行者的能力有关)。

    图为通常来说程序员的发展路线:

    影响企业绩效的因素在方方面面,核心因素却往往集中在产品上。不夸张地说,应用程序的性能对于企业绩效有着非常巨大的影响。互联网产品遍地开花,SDK 层出不穷,用户对于一种新产品的尝试时间与互联网产品更新的速度成反比。用户体验这个已经被讲烂的概念依然还是提升产品价值的关键按钮,无论是 2C 还是 2B。

    一旦用户未在你所负责的产品中获得最佳体验,或者直接解决痛点,他们会毫不犹豫的选择其他平台。

    这个问题普通程序员通常解决不了,而一名优秀的 CTO 就需要下点功夫了。如何成为一名优秀的 CTO,这是一个问题,而一个问题往往是另一个问题的解决方案。为什么一个团队需要优秀的 CTO?是因为需要有人来带领技术团队优化应用性能——解决用户体验的难题,提升开发、运维,把控技术团队的战略方向。那么,优化应用性能,获得好的用户体验,提升开发、运维效率,又该怎么做呢?

    为了确保应用程序能够达到甚至超越用户的高期望,需要不断优化底层 IT 基础设施的性能。然而,随着基础设施变得越来越动态化,混合化和复杂化,一波波新的挑战随之而生,让不少 CTO 多了几根白头发。

    但是一个问题的产生,往往意味着相应的解决方法正在路上。为了优化应用程序的性能,优秀的 CTO 需要足够主动和敏捷。

    主动优化包括物理和虚拟服务器,网络,存储设备,数据库,终端用户服务,云,和大数据环境在内的所有基础设施。需要将 IT 团队带领成为不仅能够迅速识别和解决问题,同时具有强大的反脆弱性,在问题对用户体验产生不利影响之前,先发制人的组织。以下五大关键措施或许可以帮助我们实现一点。

    1. 捕捉和报告性能指标

    鉴于良好性能的重要性,对于 IT 团队来说只在基础设施组件出现问题时产生告警是不足够的。CTO 需要让团队能够提前发现潜在的性能问题,并主动解决。例如,通过免费或付费的第三方工具及一些开源工具,配置告警,在问题出现之前解决。不同的团队,往往有最为适合自己的基础设施监控手段,优秀的 CTO 需要能够综合衡量团队大小,开发、运维水平,与人力和资金成本,选择最符合公司当下情况的监控方式。对于变动型较大或者高速发展的公司,盲目增加人力和花费时间去进行自主开发系统监控解决方案往往造成时间的浪费,得不偿失。

    2. 统一视图和工具来增加可视性,并加快问题解决

    由于开源工具与第三方解决方案层出不穷,不少 IT 团队也勇于尝试新工具、新方法。虽然有很多新的工具,解决不同方面的问题,但当问题出现时,团队成员仍然花费许多时间开会讨论,不断地开会浪费了许多时间。而与此同时,用户却经历着槽糕的体验。为什么明明有许多工具却依然采取本办法沟通呢?原因有两个,一个是很多 IT 团队内部在使用不同的协作、监控等工具,另一个是其实团队内部并没有养成利用监控平台或者协作工具的习惯。这种时候 CTO 就需要发挥作用,采用一个统一且功能强大的视图和架构来监测关键的 IT 服务,无论是虚拟机,物理主机,云主机,或者其他组件,同时采取深刻理解DevOps,掌握提升协作、沟通效率,优化开发流程,节省运维成本,提前发现问题的方法。

    想知道吗?CTO 比普通程序员强在哪?

    3. 跟踪用户体验

    IT 团队可能拥有大量的性能指标,但是如果不知道用户的真实体验,就还是无法真正了解性能表现。什么是真实的体验?就是用户在实际操作中,是如何使用我们的产品的,在某个界面停留多久,对哪个环节不满意,诸如此类。IT 团队需要分析端到端的基础设施的响应时间,并借助虚拟交易功能,持续跟踪交易响应时间,即使在用户不使用应用程序的情况下。

    想知道吗?CTO 比普通程序员强在哪?

    4. 采用严格的 SLA 管理

    一旦企业的全面监测到位, IT 团队针对服务水平协议(SLAs)跟踪性能和体验是至关重要的。IT 团队需要能够跟踪 SLA 合规性,当潜在问题出现时,立即识别和解决。通过跟踪 SLAs,IT 企业可以评估他们在管理用户体验和基础设施性能上的有效性。 这一评估对于准确计量团队绩效,设定目标和跟踪进展也是至关重要的。

    5. 将 IT 和非 IT 数据相关联,进行高效的容量规划

    满足用户不断提高的期望,并不仅仅是跟踪 IT 数据。通过关联 IT 和业务数据,团队可以主动识别瓶颈,提高终端用户体验。比如,将服务器 CPU 利用率指标和简单的历史数据相关联;比如,将用户登录或交易的数量与 IT 数据一起进行展示,可以为适应未来发展的容量规划,提供有意义的见解。下图为某团队将 PHP 请求、响应时间等数据和系统性能数据一起导入 Cloud Insight 仪表盘进行展示的例子。

    想知道吗?CTO 比普通程序员强在哪?想知道吗?CTO 比普通程序员强在哪?

    插播一个好玩的,下图为某团队成员别出心裁将键盘使用记录导入仪表盘进行展示,也许键盘记录只是一种出于好玩的别出心裁,但同理,也可以将运营数据、业务数据、系统性能数据一起导入仪表盘进行展示,这对一个快速增长的 IT 团队来说,就很有价值了。

    想知道吗?CTO 比普通程序员强在哪?

    总结

    数据驱动互联网高速发展的时代,技术团队 Leader 除了技术过硬,眼光独到,还要将紧跟 DevOps 的步伐,放眼国内外,快速、敏捷、尽可能多的优化团队开发手段和流程,减少开发、运维、运营之间的沟通壁垒,将数据化融入到技术推进的方方面面。而当你在这些方面有了核心竞争力,就不再只是一名普通的程序员了。

    posted @ 2016-03-29 20:44 paulwong 阅读(388) | 评论 (0)编辑 收藏

    MongoDB健壮集群——用副本集做分片

         摘要: 1.    MongoDB分片+副本集健壮的集群方案多个配置服务器 多个mongos服务器  每个片都是副本集 正确设置w架构图说明:1.   此实验环境在一台机器上通过不同port和dbpath实现启动不同的mongod实例2.   总的9个mongod实例,分别做成shard...  阅读全文

    posted @ 2015-12-18 14:03 paulwong 阅读(832) | 评论 (0)编辑 收藏

    利用Mongodb的复制集搭建高可用分片,Replica Sets + Sharding的搭建过程

         摘要: 参考资料 reference:  http://mongodb.blog.51cto.com/1071559/740131  http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/#sharding-setup-shard-collection感谢网友Mr.Sharp,他给了我很多...  阅读全文

    posted @ 2015-12-18 13:54 paulwong 阅读(920) | 评论 (0)编辑 收藏

    MONGODB的复制与分片

    复制:为了防止单点故障,会有几个实例在运行,保持相同的数据。

    • 一般主从:一主多从,主作读写数据,从作从主备份数据用,如果主宕机,则整个MONGODB无法工作。
    • 复制式主从:一动态主多从,主由选举产生,当中一个主宕机,其他的从会选出一个主。

    适用场景:高负荷的读多写少。

    分片:SHARDING,一般数据库中的分库分表,一个表分成几个表用。每个片再做复制。

    适用场景:高负荷的写多读少。即如果发现MONGODB写不能支撑了,则要转此模式。

    安装配置服务器,安装ROUTER:MONGOS,安装分片服务器,通知MONGOS挂载SHARD。

    如果只启用数据库的分片,则不同的表放在不同的分片上,即一个表只占一个分片,另一个表占另一个分片,如果做了表的分片,则此表会分布在所有分片上。











    posted @ 2015-12-18 13:21 paulwong 阅读(533) | 评论 (0)编辑 收藏

    Android Application Architecture 安卓APP架构[译]

    本文介绍了文章作者从事了几年android应用的开发,经历2次架构变革,第一次集成了RxJava第二次集成了MVP,并将RxJava与MVP完美结合,实现了低耦合,代码简单,测试方便的架构。

    其实我们在开发中也遇到过,Android入门门槛较低,如果前期对APP规划不清晰,Coder们对未来变化把握不准,技术架构经验不够强大,最终导致就是一个Activity几千行,里面写了大量的Private方法,拆成几个Fragment、封装出来几个类都是无法解决,结果就是看Activity难受的要死,纠结,看了不爽改也不是不改也不是,严重影响看的人的心情。并且怨天尤人这个是产品人员规划App不好,没有前瞻性,改来改去。。。

    这篇文章就是使用新的结构解决该问题。

    安卓APP架构

    Android Application Architecture

    Our journey from standard Activities and AsyncTasks to a modern MVP-based architecture powered by RxJava.

    这篇文章主要目的是讲述如何将传统的Activities 与 AsyncTasks 模式向目前主流的MVP架构基础的响应式编程框架过度。

    1*HrE2lljEfsCu1X_OUDfHYA

    Different parts of a software codebase should be independent, yet perfectly work together like a well-oiled machine — photo by Chester Alvarez.

    先畅享一下:~~~如果松耦合架构,分工明确,然后完美的组合在一起工作是一个很吊的事情。
    (转个图片还要写明白谁拍的,版权意识真强)

    The Android dev ecosystem moves very quickly. Every week new tools are created, libraries are updated, blog posts are written and talks are given. If you go on holiday for a month, by the time you come back there will be a new version of the support library and/or Play Services.

    最近几年Android的生态链变化非常迅速,从底层的Android Api到应用层的各种开源的类库、工具更新非常迅速。一不留神就落后了。

    I’ve been making Android apps with the ribot team for over three years. During this time, the architecture and technologies we’ve used to build Android apps have been continuously evolving. This article will take you through this journey by explaining our learnings, mistakes and the reasoning behind these architectural changes.

    我在Ribot团队从事Android应用开发工作三年多,伴随着公司技术的不断创新,积累了很多经验、错误以及在技术选型背后的故事。

    旧的应用架构

    The old times
    Back in 2012 our codebases used to follow a basic structure. We didn’t use any networking library and AsyncTasks were still our friends. The diagram below shows approximately how the architecture was.

    2012年那个时候,我们的代码都是用的原生Android,没有使用任何的网络请求框架,而是基于AsyncTasks书写。
    1*TTtpcT4H80THBofnCtQ_L>The code was structured in two layers: the data layer that was in charge of retrieving/saving data from REST APIs and persistent data stores; and the view layer, whose responsibility was handling and displaying the data on the UI.
    The APIProvider provides methods to enable Activities and Fragments to easily interact with the REST API. These methods use URLConnection and AsyncTasks to perform network calls in a separate thread and return the result to the Activities via callbacks.

    代码分为两层,Data与View,Data层主要是用来从API获取数据,保存到持久化的db当中。View层主要就是把Data的数据显示到UI上。APIProvider提供方法出来,用于在Activity或者Fragment中方便的进行控制与交互。技术上将,使用URLConnection与AsyncTasks实现了一个异步的网络请求并将结果返回到调用的回调方法里面。

    In a similar way, the CacheProvider contains methods that retrieve and store data from SharedPreferences or a SQLite database. It also uses callbacks to pass the result back to the Activities.

    相同的原理CacheProvider提供一系列方法,将SharedPreferences或者SQLite的数据取出来,并且返回给到Activity

    问题

    The problems
    The main issue with this approach was that the View layer had too many responsibilities. Imagine a simple common scenario where the application has to load a list of blog posts, cache them in a SQLite database and finally display them on a ListView. The Activity would have to do the following:

    主要问题是View层有太多的累赘,以一个博客列表为例来讲述,比如博客需要显示一个ListView,从SQLite读取数据,Activity需要做到以下几点:

    1. Call a method loadPosts(callback) in the APIProvider
    2. Wait for the APIProvider success callback and then call savePosts(callback) in the CacheProvider.
    3. Wait for the CacheProvider success callback and then display the posts on the ListView.
    4. Separately handle the two potential errors callback from the APIProvider and CacheProvider.
    1. 执行APIProvider里面的loadPosts的方法,里面传入回调参数内容。
    2. 等待loadPosts执行成功后,执行回调里面的CacheProvider中的savePosts方法,savePosts也要传入回调参数。
    3. 等待savePosts执行成功后,执行回调里面的方法刷新ListView
    4. 分别书写代码处理2 3 两步的错误回调内容。

    This is a very simple example. In a real case scenario the REST API will probably not return the data like the view needs it. Therefore, the Activity will have to somehow transform or filter the data before showing it. Another common case is when the loadPosts() method takes a parameter that needs to be fetched from somewhere else, for example an email address provided by the Play Services SDK. It’s likely that the SDK will return the email asynchronously using a callback, meaning that we now have three levels of nested callbacks. If we keep adding complexity, this approach will result into what is known as callback hell.

    这还是一个比较简单的例子,在一些真实的场景中,远程的API可能没有返回程序的必须值,但是activity必须把数据处理完成之后才能显示结果。再一个例子就是如果loadPosts方法需要借助一些其他地方的返回参数时,类似用多线程去实现同步请求,为保证数据正常请求,意味着必须做一个三层的回调,如果再复杂一些,想理清楚这些回调就是很蛋疼的事情。

    In summary:
    Activities and Fragments become very large and difficult to maintain
    Too many nested callbacks means the code is ugly and difficult to understand so painful to make changes or add new features.
    Unit testing becomes challenging, if not impossible, because a lot of the logic lives within the Activities or Fragments that are arduous to unit test.

    总之,回调多了之后,Activity与Fragment会乱的要死,并且一般人无法直视。

    牛逼的新架构出来了

    A new architecture driven by RxJava
    We followed the previous approach for about two years. During that time, we made several improvements that slightly mitigated the problems described above. For example, we added several helper classes to reduce the code in Activities and Fragments and we started using Volley in the APIProvider. Despite these changes, our application code wasn’t yet test-friendly and the callback hell issue was still happening too often.

    我们在蛋疼的架构中煎熬了2年,当然也尝试过很多方式,最终也只能是缓和一下乱的问题。我们在APIProvider使用了Volley,代替了AsyncHttpClient,但是其实是一个吊样。

    It wasn’t until 2014 when we started reading about RxJava. After trying it on a few sample projects, we realised that this could finally be the solution to the nested callback problem. If you are not familiar with reactive programming you can read this introduction. In short, RxJava allows you to manage data via asynchronous streams and gives you many operators that you can apply to the stream in order to transform, filter or combine the data.

    不到2014年我们就开始进行RxJava的预研,然后尝试了一批简单的项目,感觉RxJava的方式是解决我们嵌套回调的终极解决办法。简单的说,RxJava允许你通过异步流的方式管理你的数据,并且还可以通过操作符(Operators)对Observable对象的变换

    Taking into account the pains we experienced in previous years, we started to think about how the architecture of a new app would look. So we came up with this.

    我们用了几年的经验痛定思痛,搞了下面这么个东西,新的APP的架构图

    1*kCynNIa5PscRl41V2scosA-200

    Similar to the first approach, this architecture can be separated into a data and view layer. The data layer contains the DataManager and a set of helpers. The view layer is formed by Android framework components like Fragments, Activities, ViewGroups, etc.

    与第一种方法相似,这个架构也是分为Data层与View层,Data层包含DataManager与一堆Helper;View层是包含Fragments, Activities, ViewGroups等。

    Helper classes (third column on diagram) have very specific responsibilities and implement them in a concise manner. For example, most projects have helpers for accessing REST APIs, reading data from databases or interacting with third party SDKs. Different applications will have a different number of helpers but the most common ones are:

    Helper主要是集成第三方的类库,以便于在代码中几行代码就可以清晰的实现某个功能,比如请求API,访问数据库等,虽然不同的应用程序都有不同的类库,但是他们无非就是以下这些内容:

    • PreferencesHelper: reads and saves data in SharedPreferences.
    • DatabaseHelper: handles accessing SQLite databases.
    • Retrofit services: perform calls to REST APIs. We started using Retrofit instead of Volley because it provides support for RxJava. It’s also nicer to use.
    • 从SharedPreferences中读取或者写入数据
    • 读写SQLite数据库
    • 类似与square的Retrofit服务,也就是Http Client,我们用Restrofit替代了Volley因为他支持Rxjava,并且更吊。

    Most of the public methods inside helper classes will return RxJava Observables.
    The DataManager is the brain of the architecture. It extensively uses RxJava operators to combine, filter and transform data retrieved from helper classes. The aim of the DataManager is to reduce the amount of work that Activities and Fragments have to do by providing data that is ready to display and won’t usually need any transformation.

    RxJava最核心的两个东西是Observables(被观察者,事件源)和Subscribers(观察者),在Helper类中的Public方法,一般都会返回一个RxJava的Observables;DataManager是整个架构的大脑,他大量的使用Rxjava的operators对Helper返回来的数据进行的整合过滤、二次处理。

    The code below shows what a DataManager method would look like. This sample method works as follows:

    下面用一个例子来说明DataManager是做什么的:

    1. Call the Retrofit service to load a list of blog posts from a REST API
    2. Save the posts in a local database for caching purposes using the DatabaseHelper.
    3. Filter the blog posts written today because those are the only ones the view layer wants to display.
    1. 调用Retrofit的服务,去请求一个博客列表的API
    2. 用DatabaseHelper保存这些数据到数据库
    3. 过滤出这些BLOG哪些是今天写的,然后显示到UI界面上。

    Components in the view layer such as Activities or Fragments would simply call this method and subscribe to the returned Observable. Once the subscription finishes, the different Posts emitted by the Observable can be directly added to an Adapter in order to be displayed on a RecyclerView or similar.

    Observables发出一系列事件,Subscribers(例如 Activities or Fragments)处理这些事件,可以直接将数据显示到一些可以回收、重用的View上面。
    【BTW:如果一个Observerble没有任何的的Subscriber,那么这个Observable是不会发出任何事件的】

    The last element of this architecture is the event bus. The event bus allows us to broadcast events that happen in the data layer, so that multiple components in the view layer can subscribe to these events. For example, a signOut() method in the DataManager can post an event when the Observable completes so that multiple Activities that are subscribed to this event can change their UI to show a signed out state.

    这个架构的另外一个模块是event bus,event bus可以让我们在Data层发出广播(不是Android的Broadcast)然后不同的模块去注册并接收不同的广播事件

    Why was this approach better?
    RxJava Observables and operators remove the need for having nested callbacks.
    1*BIsOCzJnc-SSU8fPXTiP1A

    为什么这个方式这么牛逼,是因为Observables与operators可以去掉那一堆必须的回调方法

    The DataManager takes over responsibilities that were previously part of the view layer. Hence, it makes Activities and Fragments more lightweight.
    Moving code from Activities and Fragments to the DataManager and helpers means that writing unit tests becomes easier.

    DataManager替代了传统架构中很多代码,从而使得Activity与Fragment变得更加轻量级。并且使得单元测试变得更加简单。

    Clear separation of responsibilities and having the DataManager as the only point of interaction with the data layer, makes this architecture test-friendly. Helper classes or the DataManager can be easily mocked.

    DataManager成为了唯一的数据交互部分,这样清晰的架构使得更方便进行代码自测。

    What problems did we still have?
    For large and very complex projects the DataManager can become too bloated and difficult to maintain.
    Although view layer components such as Activities and Fragments became more lightweight, they still have to handle a considerable amount of logic around managing RxJava subscriptions, analysing errors, etc.

    我们还有什么问题?
    - 如果对于非常庞大并且复杂的项目来说,DataManger也会变得非常臃肿并且难以维护。
    - 尽管Activity与Fragment已经变得更加轻量级,但是对于错误异常的处理还是要在subscriptions的地方去书写。

    一体化的MVP模式

    Integrating Model View Presenter
    In the past year, several architectural patterns such as MVP or MVVM have been gaining popularity within the Android community. After exploring these patterns on a sample project and article, we found that MVP could bring very valuable improvements to our existing approach. Because our current architecture was divided in two layers (view and data), adding MVP felt natural. We simply had to add a new layer of presenters and move part of the code from the view to presenters.

    前几年开始,很多类似MVP与MVVM在Android的一些社区比较流行,经过研究之后,我们发现MVP模式是对我们目前的方案最有价值的改动。我们的两层架构View-Data与MVP的 Model-View架构天然融合,理念一致。我们只需要增加一个presenters层,然后把之前在view实现的代码移到上面就可以了。
    1*NonRJ0uzzN9o1ygT6J421g

    The data layer remains as it was but it’s now called model to be more consistent with the name of the pattern.
    Presenters are in charge of loading data from the model and calling the right method in the view when the result is ready. They subscribe to Observables returned by the data manager. Therefore, they have to handle things like schedulers and subscriptions. Moreover, they can analyse error codes or apply extra operations to the data stream if needed. For example, if we need to filter some data and this same filter is not likely to be reused anywhere else, it may make more sense to implement it in the presenter rather than in the data manager.

    之前的Data层就是现在的MVP中的Model,Presenter现在负责从Model中加载数据,加载完成后后再去调用左边的在Activity、ViewGroup中的方法。Presenters的subscribe去接收data manager中的Observables广播出来的数据。
    举例说明,如果我们需要增加数据的过滤操作但是并不是所有地方都需要的那种,那就可以在presenter里面写这些代码,而不用写在公共的datamanager里面。

    Below you can see what a public method in the presenter would look like. This code subscribes to the Observable returned by the dataManager.loadTodayPosts() method we defined in the previous section.

    我们定义的dataManager.loadTodayPosts()会广播出数据给到对应的subscribes

    The mMvpView is the view component that this presenter is assisting. Usually the MVP view is an instance of an Activity, Fragment or ViewGroup.

    MVP的View并不是指的Android的View,而是一个界面组件的的实例,例如Activity, Fragment , ViewGroup 在注册presenter的时候,需要把自己当前的实例传递进去。

    // Activity onCreate 中的代码段  if (presenter == null)         presenter = new Presenter1();         presenter.onTakeView(this);  

    Like the previous architecture, the view layer contains standard framework components like ViewGroups, Fragments or Activities. The main difference is that these components don’t subscribe directly to Observables. They instead implement an MvpView interface and provide a list of concise methods such as showError() or showProgressIndicator(). The view components are also in charge of handling user interactions such as click events and act accordingly by calling the right method in the presenter. For example, if we have a button that loads the list of posts, our Activity would call presenter.loadTodayPosts() from the onClick listener.

    这个架构与上一个架构不同的是,ViewLayer 也就是Activity这些,不会直接去订阅接收Observables发出的这些事件。而是只在Activity实现几个简单的显示错误、显示进度的方法(用接口interface来规范统一),然后把当前实例以参数形式传递给到对应事件的Presenter,由Presenter去执行这些显示错误、显示进度的方法。
    当然对于用户交互部分的按钮点击事件还是要在Activity中进行处理。

    If you want to see a full working sample of this MVP-based architecture, you can check out our Android Boilerplate project on GitHub. You can also read more about it in the ribot’s architecture guidelines.

    关于MVP的文章可以自行百度一下,MVP Android 关键词

    Why is this approach better?

    为什么这个又最吊

    • Activities and Fragments become very lightweight. Their only responsibilities are to set up/update the UI and handle user events. Therefore, they become easier to maintain.
    • We can now easily write unit tests for the presenters by mocking the view layer. Before, this code was part of the view layer so we couldn’t unit test it. The whole architecture becomes very test-friendly.
    • If the data manager is becoming bloated, we can mitigate this problem by moving some code to the presenters.
    • Activity与Fragment代码量大大降低,逻辑代码全部都丢给了Presenter,结果就是Activity只需要负责UI交互的按钮等代码。
    • 对于Presenter可以写单独的单元测试代码,只需要对Presenter提供的方法测试即可
    • 如果DataManager变得臃肿庞大了,我们可以分离这些代码到各自的Presenter中去。

    What problems do we still have?
    现在还有遗留什么问题

    Having a single data manager can still be an issue when the codebase becomes very large and complex. We haven’t reached the point where this is a real problem but we are aware that it could happen.

    只有一个DataManager仍旧是一个问题,尤其是当代码项目比较庞大的时候,当然我们还没有到达这个庞大的地步,尽管我们知道这个将来某天会发生。

    It’s important to mention that this is not the perfect architecture. In fact, it’d be naive to think there is a unique and perfect one that will solve all your problems forever. The Android ecosystem will keep evolving at a fast pace and we have to keep up by exploring, reading and experimenting so that we can find better ways to continue building excellent Android apps.

    如果想有个完美的架构解决你所有问题是不可能的。TMD Android的整个生态圈变化太快,又TM的不标准,就导致我们不断的去探索探索。。。以致于去找到更吊的方法去做Android apps。

    I hope you enjoyed this article and you found it useful. If so, don’t forget to click the recommend button. Also, I’d love to hear your thoughts about our latest approach.

    希望读了之后对我们的最新解决方案能有些建议想法。

    【本文翻译的目的是在闲暇时间,研究新技术,用通俗技术语言写给自己看,便于日后方便查阅为目】
    原文:https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65
    MVP介绍:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0425/2782.html
    RxAndroid:https://github.com/ReactiveX/RxAndroid
    Eventbus:https://github.com/greenrobot/EventBus

    posted @ 2015-12-18 13:07 paulwong 阅读(636) | 评论 (0)编辑 收藏

    mongodb的监控与性能优化

         摘要: .mongodb的监控 mongodb可以通过profile来监控数据,进行优化。查看当前是否开启profile功能用命令db.getProfilingLevel()  返回level等级,值为0|1|2,分别代表意思:0代表关闭,1代表记录慢命令,2代表全部开始profile功能为db.setProfilingLevel(level);  #level等级,值同上l...  阅读全文

    posted @ 2015-12-16 18:50 paulwong 阅读(753) | 评论 (0)编辑 收藏

    高压锅内部烧黑,如何去掉?

    http://www.360doc.com/content/11/0415/13/117643_109815383.shtml

    http://wenda.tianya.cn/question/4b4edcf687745412

    http://zhidao.baidu.com/question/456177515446176485.html

    http://iask.sina.com.cn/b/6262165.html

    http://baike.pcbaby.com.cn/qzbd/5691.html#ldjc4ta=baby_tbody2

    http://www.xiaoqiaomen.cc/qingjieweisheng/258.html

    http://home.19lou.com/forum-106-thread-6901352097188270-1-1.html

    posted @ 2015-12-14 18:25 paulwong 阅读(467) | 评论 (0)编辑 收藏

    MONGODB删除/新增/更改大量记录的方法

    MONGODB中,由于删除大量记录会十分耗时,一般推荐由MONGODB自己在后台处理,只需在某个字段设一个索引的标签即可。

    @Indexed(expireAfterSeconds=180)
    private Date deletedAt;

    以上代码,如果字段deletedAt有值,那么将在180秒后被MONGODB删除,如果没值不会被删除。

    批量新增,小批量更新,防止读取超时
    private <T> void insertAll(List<T> list) {
            if (null != list) {
                int total = list.size();
                int count = (total + 50000 -1) / 50000;
                for (int i = 0; i < count; i++) {
                    int toIndex = ((i +1) * 50000 > total) ? total : ((i +1) * 50000);
                    log.info("toIndex = " + toIndex);
                    mongoTemplate1.insertAll(list.subList(i * 50000, toIndex));
                }
            }
        }

    批量更改
    import java.util.Date;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.data.mongodb.core.query.Criteria;
    import org.springframework.data.mongodb.core.query.Query;
    import org.springframework.data.mongodb.core.query.Update;

    import com.tcl.project7.boss.gameapplication.yearendactivities.bigwheelgame.valueobject.SingleUseRedeemCode;

    public class SingleUseRedeemCodeRepositoryImpl implements SingleUseRedeemCodeRepositoryCustom{
        
        @Autowired
        private MongoTemplate mongoTemplate1;
        
        public void batchUpdateSingleUseRedeemCodeList(String bigWheelGameAwardId) {
            
            Query query = new Query();
            query.addCriteria(Criteria.where("bigwheelgameawardid").is(bigWheelGameAwardId));
            mongoTemplate1.updateMulti(
                                        query, 
                                        new Update().set("bigwheelgameawardid", "-1")
                                            .set("deletedat", new Date()), 
                                        SingleUseRedeemCode.class);
        }

    }


    Expire Data from Collections by Setting TTL

    New in version 2.2.

    This document provides an introduction to MongoDB’s “time to live” or TTL collection feature. TTL collections make it possible to store data in MongoDB and have the mongod automatically remove data after a specified number of seconds or at a specific clock time.

    Data expiration is useful for some classes of information, including machine generated event data, logs, and session information that only need to persist for a limited period of time.

    A special TTL index property supports the implementation of TTL collections. The TTL feature relies on a background thread in mongod that reads the date-typed values in the index and removes expired documentsfrom the collection.

    Procedures

    To create a TTL index, use the db.collection.createIndex() method with theexpireAfterSeconds option on a field whose value is either a date or an array that contains date values.

    NOTE

    The TTL index is a single field index. Compound indexes do not support the TTL property. For more information on TTL indexes, see TTL Indexes.

    Expire Documents after a Specified Number of Seconds

    To expire data after a specified number of seconds has passed since the indexed field, create a TTL index on a field that holds values of BSON date type or an array of BSON date-typed objects and specify a positive non-zero value in the expireAfterSeconds field. A document will expire when the number of seconds in the expireAfterSeconds field has passed since the time specified in its indexed field. [1]

    For example, the following operation creates an index on the log_events collection’s createdAt field and specifies the expireAfterSeconds value of 3600 to set the expiration time to be one hour after the time specified by createdAt.

    db.log_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 3600 } ) 

    When adding documents to the log_events collection, set the createdAt field to the current time:

    db.log_events.insert( {    "createdAt": new Date(),    "logEvent": 2,    "logMessage": "Success!" } ) 

    MongoDB will automatically delete documents from the log_events collection when the document’screatedAt value [1] is older than the number of seconds specified in expireAfterSeconds.

    [1](12) If the field contains an array of BSON date-typed objects, data expires if at least one of BSON date-typed object is older than the number of seconds specified in expireAfterSeconds.

    SEE ALSO

    $currentDate operator

    Expire Documents at a Specific Clock Time

    To expire documents at a specific clock time, begin by creating a TTL index on a field that holds values of BSON date type or an array of BSON date-typed objects and specify an expireAfterSeconds value of0. For each document in the collection, set the indexed date field to a value corresponding to the time the document should expire. If the indexed date field contains a date in the past, MongoDB considers the document expired.

    For example, the following operation creates an index on the log_events collection’s expireAt field and specifies the expireAfterSeconds value of 0:

    db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } ) 

    For each document, set the value of expireAt to correspond to the time the document should expire. For instance, the following insert() operation adds a document that should expire at July 22, 201314:00:00.

    db.log_events.insert( {    "expireAt": new Date('July 22, 2013 14:00:00'),    "logEvent": 2,    "logMessage": "Success!" } ) 

    MongoDB will automatically delete documents from the log_events collection when the documents’expireAt value is older than the number of seconds specified in expireAfterSeconds, i.e. 0 seconds older in this case. As such, the data expires at the specified expireAt value.

    posted @ 2015-12-11 15:03 paulwong 阅读(1762) | 评论 (0)编辑 收藏

    How to delete large amount of data of a MongoDB collection “quickly”

    We have a db collection that is around 30 million documents, and I need to trim it down, to only keeping the documents created on the last month. 

    One approach would be use the remove command with a condition on the created_at field (the collection already have an index on this field):

    db.my_collection.remove({created_at: {$lte: new Date("11/01/2012")}});

    But this approach will be very slow, instead of that, a better way to do it is rename the current collection (for instance to “old_collection”) using renameCollection. Then performing a query-and-insert from the “old_collection” into “my_collection”:

    db.my_collection.renameCollection("old_collection");  
    db.createCollection("my_collection");
    db.my_collection.createIndex(...); // recreate the indexes for the collection
    // copy docs from old collection into the new collection
    db.old_collection.find(
    {created_at: {$gte: new Date("11/01/2012")}} ).sort({_id: -1}).forEach(
    function(row) { db.my_collection.insert(row); } ); // drop old collection db.old_collection.drop();

    This approach is typically faster than running a bunch of removes on your data

    posted @ 2015-12-10 20:09 paulwong 阅读(528) | 评论 (0)编辑 收藏

    MongoDB 固定集合(Capped Collections)

    MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!


    创建固定集合

    我们通过createCollection来创建一个固定集合,且capped选项设置为true:

    >db.createCollection("cappedLogCollection",{capped:true,size:10000})

    还可以指定文档个数,加上max:1000属性:

    >db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

    判断集合是否为固定集合:

    >db.cappedLogCollection.isCapped()

    如果需要将已存在的集合转换为固定集合可以使用以下命令:

    >db.runCommand({"convertToCapped":"posts",size:10000})

    以上代码将我们已存在的 posts 集合转换为固定集合。


    固定集合查询

    固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,也可以使用$natural调整返回顺序。

    >db.cappedLogCollection.find().sort({$natural:-1})

    固定集合的功能特点

    可以插入及更新,但更新不能超出collection的大小,否则更新失败,不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。

    在32位机子上一个cappped collection的最大值约为482.5M,64位上只受系统文件大小的限制。


    固定集合属性及用法

    属性

    • 属性1:对固定集合进行插入速度极快
    • 属性2:按照插入顺序的查询输出速度极快
    • 属性3:能够在插入最新数据时,淘汰最早的数据

    用法

    • 用法1:储存日志信息
    • 用法2:缓存一些少量的文档

    posted @ 2015-12-09 14:41 paulwong 阅读(454) | 评论 (0)编辑 收藏

    MongoDB 聚合

    http://www.runoob.com/mongodb/mongodb-aggregate.html
    MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。


    aggregate() 方法

    MongoDB中聚合的方法使用aggregate()。

    语法

    aggregate() 方法的基本语法格式如下所示:

    >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

    实例

    集合中的数据如下:

    {    _id: ObjectId(7df78ad8902c)    title: 'MongoDB Overview',     description: 'MongoDB is no sql database',    by_user: 'w3cschool.cc',    url: 'http://www.w3cschool.cc',    tags: ['mongodb', 'database', 'NoSQL'],    likes: 100 }, {    _id: ObjectId(7df78ad8902d)    title: 'NoSQL Overview',     description: 'No sql database is very fast',    by_user: 'w3cschool.cc',    url: 'http://www.w3cschool.cc',    tags: ['mongodb', 'database', 'NoSQL'],    likes: 10 }, {    _id: ObjectId(7df78ad8902e)    title: 'Neo4j Overview',     description: 'Neo4j is no sql database',    by_user: 'Neo4j',    url: 'http://www.neo4j.com',    tags: ['neo4j', 'database', 'NoSQL'],    likes: 750 },

    现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:

    > db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}]) { "result" : [ { "_id" : "w3cschool.cc", "num_tutorial" : 2 }, { "_id" : "Neo4j", "num_tutorial" : 1 } ], "ok" : 1 } >

    以上实例类似sql语句: select by_user, count(*) from mycol group by by_user

    在上面的例子中,我们通过字段by_user字段对数据进行分组,并计算by_user字段相同值的总和。

    下表展示了一些聚合的表达式:

    表达式描述实例
    $sum计算总和。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
    $avg计算平均值db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
    $min获取集合中所有文档对应值得最小值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
    $max获取集合中所有文档对应值得最大值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
    $push在结果文档中插入值到一个数组中。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
    $addToSet在结果文档中插入值到一个数组中,但不创建副本。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
    $first根据资源文档的排序获取第一个文档数据。db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
    $last根据资源文档的排序获取最后一个文档数据db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

    管道的概念

    管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

    MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

    表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

    这里我们介绍一下聚合框架中常用的几个操作:

    • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
    • $limit:用来限制MongoDB聚合管道返回的文档数。
    • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
    • $group:将集合中的文档分组,可用于统计结果。
    • $sort:将输入文档排序后输出。
    • $geoNear:输出接近某一地理位置的有序文档。

    管道操作符实例

    1、$project实例

    db.article.aggregate( { $project : {         title : 1 ,         author : 1 , }} );

    这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样:

    db.article.aggregate( { $project : {         _id : 0 ,         title : 1 ,         author : 1 }});

    2.$match实例

    db.articles.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] );

    $match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。

    3.$skip实例

    db.article.aggregate( { $skip : 5 }); 

    经过$skip管道操作符处理后,前五个文档被"过滤"掉。

    posted @ 2015-12-08 10:44 paulwong 阅读(533) | 评论 (0)编辑 收藏

    Java 中正确使用 hashCode 和 equals 方法

         摘要: 在这篇文章中,我将告诉大家我对hashCode和equals方法的理解。我将讨论他们的默认实现,以及如何正确的重写他们。我也将使用Apache Commons提供的工具包做一个实现。 目录:hashCode()和equals()的用法重写默认实现使用Apache Commons Lang包重写hashCode()和equals()需要注意记住的事情当使用ORM的时候特别要注意的hashC...  阅读全文

    posted @ 2015-12-01 10:52 paulwong 阅读(409) | 评论 (0)编辑 收藏

    SAMSUNG NOTE2双清教程

    双清教程:

    1. 手机关机,按住音量上+HOME+电源键
    2. 选择 wipe date/factory reset
    3. 选择 yes dalete all user data
    4. 选择 wipe cache partition
    5. 选择 yes wipe cache




    posted @ 2015-11-26 19:27 paulwong 阅读(420) | 评论 (0)编辑 收藏

    如何医治牙龈出血

    如何医治牙龈出血啊。。。谢谢啊,狂急!!!!
    悬赏分:0 - 解决时间:2005-11-22 12:37
    提问者: 小哈纳斯 - 魔法学徒 一级 

    http://www.234.com.tw/perio/topicList.cfm?kid=61

    最佳答案
    这里是我以前答复两个患者的资料,对你有作用:

    一 关于青年人牙龈出血

    ⑴ 我想你是牙龈出血。若是其它原因出血,你肯定早住医院而不会在这里轻松发贴了。

    ⑵ 这个臭味是来自血的腐败。既然牙龈会出血,也表明你有牙周的问题,这些渗血部位的牙垢及食物残渣也会发出难闻的气味。

    ⑶ 治疗一般应到专门的口腔门诊。看来你是第一次涉及口腔的保健,治疗的流程应该是这样的:

       先消除炎症:常规是口服利菌沙,灭滴灵,维生素C,维生素K。治疗约1周后,牙龈炎症消除,就应着手解决根本问题了。

       再清除牙垢:这是引起牙周炎的原因。牙垢很硬,堆积在牙龈周围,不断地刺激和损伤着牙龈,使成创面出血。而出血又可进一步形成牙垢,如此成恶性循环。

    ⑷ 在我们三甲医院,这个费用偏高些,约100元。它包括消炎和约两次的除垢(洗牙)。在县市级的医院应该便宜些。

    ⑸ 消炎7天加除垢2天。若同时治疗龋齿,还加2天。

    ⑹ 当然,你也可只做消炎一步,除垢待以后有时间再做。如此,你到大的药房去,买前3种药自己服。买3-5天的量。约花20元。

    ⑹ 这3-5天内,你要睡眠良好,尽量避免感冒发热,那会引起血管通透性趋增,使出血严重一些,不利于治疗消炎。

    ⑺ 平时多食富含维生素C的水果蔬菜很重要!!!
    好,暂就说这么多。对顽固性的牙龈出血(例如出了多年血),或是这样治疗仍不见效,请再设问,我们来为你确定新的治疗方案。

    二 关于老是治不好的牙龈出血(先用一法!!!!)

    楼上解释的对。但我要教你最重要的东西,就是怎样解决这个问题。当然,解决之后,你要补我的专家号,呵呵。(我有个熟人有40年牙龈出血的经历,可是它近年突然好了。你想知道原因吗?我给了他一个新招。)


       ⑴ 连服维生素C7天,每天100-300mg。(这个量不算多,意义你明白。)

       ⑵ 这7天内保持好睡眠,目的是调节好人的生理状态,使不容易感冒什么的,避免因发烧造成的微血管通透性能趋增,使容易出血。

       ⑶ 本条最重要:去药房买“阿奇霉素分散片”,买大厂出的,我建议买天津药业悦来牌的。每盒6片×0.25g,请单次口服4片,并详细阅读一下说明书。记住:只吃一次即可。若有严重的不良反应(极少有!!),带着这个药的说明书去看门诊,医生会处置。如果没有不良反应,可在48小时后把另2片吃了,仅此而已。

       该药是目前极有名的药,4年前从国外进来现已完全本土化,真的是国外科学家对人类的大贡献!也不算贵,约20-30元1盒6片。主要用途是对付非淋菌性性病。

       它是广谱抗菌药,更是☆靶向制剂☆。你明白这个吗?我说个白话以便你能听懂:这个药可跟随人的巨嗜细胞走。而巨嗜细胞又跟着炎症走,所以疗效显著。

       ⑷ 一般的牙科医生,都会建议你用利君沙和灭滴灵,外加维生素C和K。但我想你是老牙龈出血,这种药用过多次,不会太奏效,阿奇霉素毒副作用比上述两种都小,更重要的是你没用过,致病菌没有抗药性。

       ⑸ 炎症消除、出血停止之后,建议你去做一次牙垢清除。多年的出血肯定会导致牙根周围有比较多的牙垢,这些牙垢年复一年刺激着周围的牙龈,不但是细菌的温床,也使牙龈面持续受到创伤,牙龈出血还会卷土重来。

       ⑹ 此次牙龈炎症消除之后,口腔气味也会比以前好转,但牙根的一部分将暴露的比以前利害(以前被肿胀的牙龈包裹),产生龋齿的可能性将增加。因此要注意好口腔清洁。

    回答者: ccd2093 - 举人 四级 11-14 08:51
    提问者对于答案的评价:
    非常感谢~~

    posted @ 2015-11-19 08:47 paulwong 阅读(366) | 评论 (0)编辑 收藏

    javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes 解决办法

    如果用RSA加密数据的话,会有数据长度的要求,否则会抛异常:
    javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes

    推荐的做法:

    1. 随机生成一个密钥,用作对称密钥UUID
    2. 用此对称密钥,用对称加密法AES加密数据
    3. 用RSA的公钥加密此对称密钥
    4. 发送加密后的对称密钥和加密数据
    5. 用RSA私钥解密加密后的对称密钥
    6. 用解密密后的对称密钥,解密数据
    7. 完成
    AESSecurityUtil.java
    import java.security.Key;
    import java.util.UUID;

    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;

    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;

    public class AESSecurityUtil {

        // 加密算法
        /** 指定加密算法为RSA */
        private static final String ALGORITHM = "AES";

        // 加密密钥
        
    // private static final byte[] keyValue = new byte[] { 'T', 'h', 'e',
        
    // 'B','e', 's', 't', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' };
        
    // 16位的加密密钥
    //    private byte[] keyValue;

        /**
         * 用来进行加密的操作
         * 
         * 
    @param Data
         * 
    @return
         * 
    @throws Exception
         
    */
        public static String encrypt(String keyString, String data)
                throws Exception {
            Key key = generateKey(keyString);
            Cipher c = Cipher.getInstance(ALGORITHM);
            c.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = c.doFinal(data.getBytes());
            String encryptedValue = new BASE64Encoder().encode(encVal);
            return encryptedValue;
        }

        /**
         * 用来进行解密的操作
         * 
         * 
    @param encryptedData
         * 
    @return
         * 
    @throws Exception
         
    */
        public static String decrypt(String keyString, String encryptedData) throws Exception {
            Key key = generateKey(keyString);
            Cipher c = Cipher.getInstance(ALGORITHM);
            c.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
            byte[] decValue = c.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;
        }
        
        public static String generateKeyString()
        {
            //必须长度为16
            return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
        }

        /**
         * 根据密钥和算法生成Key
         * 
         * 
    @return
         * 
    @throws Exception
         
    */
        private static Key generateKey(String keyString) throws Exception {
            Key key = new SecretKeySpec(keyString.getBytes(), ALGORITHM);
            return key;
        }
        
        public static void main(String [] args) throws Exception
        {
            String keyString = generateKeyString();
    //        String keyString = "1234567890123456";
            System.out.println("密钥:" + keyString);
            
            String source = "恭喜发财!";// 要加密的字符串
            System.out.println("准备用密钥加密的字符串为:" + source);
            
            String cryptograph = encrypt(keyString, source);// 生成的密文
            System.out.print("用密钥加密后的结果为:" + cryptograph);
            System.out.println();

            String target = decrypt(keyString, cryptograph);// 解密密文
            System.out.println("用密钥解密后的字符串为:" + target);
            System.out.println();
        }

    }


    CryptoUtil.java
    import com.tcl.project7.boss.common.crypto.CryptoData;
    import com.tcl.project7.boss.common.util.JsonManager;
    import com.tcl.project7.boss.common.util.file.FileUtil;
    import com.tcl.project7.boss.gameapplication.yearendactivities.bigwheelgame.player.valueobject.BigWheelGameRequest;

    public class CryptoUtil {
        
        public static CryptoData encrypt(String data) throws Exception
        {
            //1、产生AES密钥
            String keyString = AESSecurityUtil.generateKeyString();
            
            //2、用AES法加密数据
            String cryptograph = AESSecurityUtil.encrypt(keyString, data);
            
            //3、用RSA加密AES密钥
            String finalKey = RSASecurityUtil.encrypt(keyString);
    //        System.out.print("用RSA加密AES密钥为:" + finalKey);
    //        System.out.print("加密数据:" + cryptograph);
            
            CryptoData cryptoData = new CryptoData();
            cryptoData.setKey(finalKey);
            cryptoData.setContent(cryptograph);
            
            //4、返回数据
            return cryptoData;
        }
        
        public static String decrypt(String keyString, String data) throws Exception
        {
            //1、解密密钥
            String decryptKeyString = RSASecurityUtil.decrypt(keyString);
            
            //2、解密内容
            String decryptData = AESSecurityUtil.decrypt(decryptKeyString, data);
            
            //3、返回
            return decryptData;
            
        }
        
        public static void main(String [] args) throws Exception
        {
            String aFilePath = "DATA/TESTING-FILE/TOCRYPTO/tocrypto.txt";
            String source = FileUtil.getContents(aFilePath);
            
            CryptoData cryptoData = encrypt(source);
            System.out.print(cryptoData);
            
            String target = decrypt(cryptoData.getKey(), cryptoData.getContent());
            System.out.print(target);
            
            BigWheelGameRequest bigWheelGameRequest = JsonManager.getBean(target, BigWheelGameRequest.class);
            System.out.print(bigWheelGameRequest);
        }

    }


    CryptoData.java
    import java.io.Serializable;

    public class CryptoData implements Serializable{

        private static final long serialVersionUID = -4774469372648172844L;
        
        private String key;
        
        private String content;

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }

        public String toString() {
            return "CryptoData [key=" + key + ", content=" + content + "]";
        }

    }

    posted @ 2015-11-18 15:27 paulwong 阅读(14577) | 评论 (0)编辑 收藏

    JSON转JAVA BEAN


    import java.io.IOException;

    import org.codehaus.jackson.JsonParseException;
    import org.codehaus.jackson.map.DeserializationConfig;
    import org.codehaus.jackson.map.JsonMappingException;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.type.TypeReference;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;


    public class ColumnControllerTest {
        
        private Logger logger = LoggerFactory.getLogger(ColumnControllerTest.class);
        
        
        @Test
        public void testTest()
        {
            ObjectMapper mapper = new ObjectMapper();
            /*AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
            // make deserializer use JAXB annotations (only)
            mapper.getDeserializationConfig().setAnnotationIntrospector(introspector);
            // make serializer use JAXB annotations (only)
            mapper.getSerializationConfig().setAnnotationIntrospector(introspector);
    */
            mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            
            String userStr = "{\"username\":\"paul\",\"email\":\"paul@paul.com\"}";
            try {
                TypeReference<TestUser> temp = new TypeReference<TestUser>(){};
                TestUser testUser = mapper.readValue(userStr, temp);
                System.out.println(testUser.toString());
                
                String result = mapper.writeValueAsString(testUser);
                System.out.println(result);
                
            } catch (JsonParseException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (JsonMappingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    posted @ 2015-11-18 13:30 paulwong 阅读(444) | 评论 (0)编辑 收藏

    不得不承认Zeroc Ice是RPC王者: 完爆Dubbo,Thrift,谷歌RPC

         摘要: ice-dubbo-thrift-grpc性能测试对比,本次测试过程中还发现了一个支付宝的秘密,附件文档中会详细说明。测试说明    本测试只是个人为了对rpc进行技术选型,测试可能不够严谨,对某些rpc的参数可能也不是最优,如果你知道更优的参数配置或者改进意见等,欢迎反馈给我magicdoom@gmail.com。另外代码有些地方只是为了测试方便,不作为平时编程的范例。所有测...  阅读全文

    posted @ 2015-11-13 16:19 paulwong 阅读(14044) | 评论 (2)编辑 收藏

    Tengine

    简介

    Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站
    的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如
    淘宝网天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、
    易用的Web平台。

    从2011年12月开始,Tengine成为一个开源项目,Tengine团队在积极地开发和维护
    着它。Tengine团队的核心成员来自于淘宝搜狗等互联网企业。Tengine是社区合作
    的成果,我们欢迎大家参与其中,贡献自己的力量。

    特性

    动态

    邮件列表

    posted @ 2015-11-06 12:56 paulwong 阅读(434) | 评论 (0)编辑 收藏

    SPRING IO

    Spring起初只专注ioc和aop,现在已发展成一个庞大体系。比如security、mvc等。
    如此一来,不同模块或者与外部进行集成时,依赖处理就需要各自对应版本号。
    比如,较新spring与较老的quartz,它们集成就会遇到问题,给搭建和升级带来不便。

    因此Spring IO Platform应运而生,只要项目中引入了它,外部集成时依赖关系无需版本号

    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-core</artifactId> 
    </dependency>

    Spring IO Platform只是一个pom文件,记录了spring与其他开源项目对应的版本。
    省去了版本号,也就省去了处理依赖时的问题,因为Spring IO Platform中有最优的版本。

    posted @ 2015-10-30 14:05 paulwong 阅读(472) | 评论 (0)编辑 收藏

    一篇不错的Docker入门介绍

    看到这标题你可能会想,网上不是已经有很多Docker的入门介绍了么?同一个主题被讲过很多次,还有没有必要必要再讲?可是,我体内这混杂的的高傲与固执虽然让人厌恶,但却让我广受大家的欢迎,这也让我觉得我应该来一发(篇),哈哈。 

    举一个我遇到的场景,ELK三剑客,即Elasticsearch、Logstash和Kibana。我可以选择把它们直接安装在我的MacBook上,这是我主力开发机,但是在上面已经装好一个Elasticsearch了。我不想破坏现在已有的环境。要解决这个问题,用2015年时下最热门的解决方案就是Docker了。如果过去一年关于Docker的各种热闹你都错过了,那么请继续往下看。 

    Docker做的事情就是将的软件隔离起来,让它们即使出了问题也不会互相影响。这并不是什么横空出世的新思想。你很可能会说内核控制的进程不就这样玩么?每一个进程都有自己的内存空间,并且在一个进程自身看来,内存空间与所处在的计算机的内存空间是一样的。然而内核欺骗了进程,在背后将内存地址重新映射到了真实的内存空间中。想想今天高速运转处理器,任何地方见到的系统都能同时运行多个进程。今天的文明社会比人类历史任何一个时间点制造的谎言数量级都要很多的量级。 

    扯远了,Docker将进程的隔离模型的进行了扩展,让隔离性变得更强。Docker是在Linux内核的基础上打造的一系列工具。整个文件系统被抽象了,网络被虚拟化了,其它进程被隐藏了,并且从理论上,不可能逃脱容器去对在一个机器上的其他进程搞破坏。实际中,每个人对于怎么才能逃脱容器,至少去收集一点运行容器的机器的相关信息,持开放的态度。跟虚拟机比较起来,容器的隔离性还是较弱。 

     

    上面箭头:提升单机性能;下面箭头:提升隔离性

    但换个角度看,进程比容器性能更好,容器性能比虚拟机性能更佳。原因很简单,隔离性更高,在每一个上下文中就需要运行更多的东西,从而拖慢速度。选择一个隔离性的过程,实际就是决定你对要运行进程的信任有多少的过程 - 它会不会去干扰其他的进程?如运行的进程都是自己的亲儿子,那你对他们会有一个很高的信任度,对他们用最少的隔离,运行在一个进程中就行了。如果是SAP,那么你很可能需要尽可能高的隔离性:将电脑装在封存在箱子里,绑在火箭上发射到月球。 

    Docker另一个很好的特性是,容器可以作为一个整体交付。他们不会像虚拟机那么臃肿。这大大的提高了部署的简易度。在这个微服务流行的世界里,你可以很容易将你的服务捆在一起,用镜像来发布。你甚至可以将build的结果指定成一个Docker镜像。 

    Docker将会怎样改变软件开发和部署的过程仍然有待观察。尽管我觉得Docker是一种带有破坏性的技术,但影响还在几年之后才会到来。虽然我会认为Docker会让很多系统管理员丢掉工作,但是实际上Docker却会改变他们的工作。每个人现在都需要一点变革,赶上时代的脚步。 

    又扯远了,说说OSX上的Docker。 

    细心的你可能注意到,我之前说Docker是运行在Linux内核之上的。然而OSX没有Linux内核,那怎么运行Docker呢。为了解决这个问题,我们需要用虚拟机来运行Docker。我们可以用一个叫boot2docker的工具来做这件事情,但是最近被docker-machine取代了。 

    我的机上有一个比较老的Docker,但是我觉得想试试Docker Compose,因为我运行着很多的服务。Docker Compose能让很多的容器协作起来运行一个整体的环境。为了遵循保证隔离服务的宗旨,每一个服务都运行在单独的容器中。因而,一个典型的web应用中,可以把web服务器运行在一个容器里面,数据库运行在另外一个容器里面,然后这些容器可以放在同一个机器上。 

    我从Docker官网上下载了安装包,并且跟着安装指南http://docs.docker.com/mac/step_one/安装。装好Docker后,我让docker-machine在Virtual Box上创建了新的虚拟机。 

     

    一起看起来很顺利,然后启动随处可见的hello-world镜像。 

    很惊讶这个镜像的并不完全,完全没有发现任何一个地方有“hello world”的输出。然而好在,不是每一个Docker镜像都实现地这般草率。这个hello world的例子比较无聊,看看能不能找到更加有意思的。我们想从容器中服务一个页面,我打算使用Nginx,已经有一个现成的Nginx的容器了,因此我创建了个新的Dockerfile。Dockerfile包含了一系列如何指导Docker从一系列镜像中创建出一个容器的指令。这里提到的容器包含以下内容: 
    Java代码 
    1. FROM nginx  
    2. COPY *.html /usr/share/nginx/html/  

    第一行设置了我们容器的基础镜像。第二行将本地的带有html后缀的文件拷贝到Nginx容器中WEB服务器的目录里。为了使用这个Dockerfile文件,我们需要创建一个Docker的镜像: 
    Java代码 
    1. /tmp/nginx$ docker build -t nginx_test .  
    2. Sending build context to Docker daemon 3.072 kB  
    3. Step 0 : FROM nginx  
    4. latest: Pulling from library/nginx  
    5. 843e2bded498: Pull complete  
    6. 8c00acfb0175: Pull complete  
    7. 426ac73b867e: Pull complete  
    8. d6c6bbd63f57: Pull complete  
    9. 4ac684e3f295: Pull complete  
    10. 91391bd3c4d3: Pull complete  
    11. b4587525ed53: Pull complete  
    12. 0240288f5187: Pull complete  
    13. 28c109ec1572: Pull complete  
    14. 063d51552dac: Pull complete  
    15. d8a70839d961: Pull complete  
    16. ceab60537ad2: Pull complete  
    17. Digest: sha256:9d0768452fe8f43c23292d24ec0fbd0ce06c98f776a084623d62ee12c4b7d58c  
    18. Status: Downloaded newer image for nginx:latest  
    19. ---> ceab60537ad2  
    20. Step 1 : COPY *.html /usr/share/nginx/html/  
    21. ---> ce25a968717f  
    22. Removing intermediate container c45b9eb73bc7  
    23. Successfully built ce25a968717f   

    Docker build命令开始将拉取已经构建好的Nginx容器。然后将我们的文件拷贝到容器里面,并且显示容器的hash值,这让它们很容易辨认。要运行这个容器我们可以运行: 
    Java代码 
    1. /tmp/nginx$ docker run --name simple_html -d -p 3001:80 -p 3002:443 nginx_test  

    这条命令让Docker运行nginxtest的容器,并且取名为simple_html。-d选项是为了让Docker在后台运行这条命令,并且最终-p选项是为了转发端口,这里需要将本地的3001端口映射到容器的80端口 - 即正常的web服务器端口。现在我们可以连接到web服务上了。如果我们打开chrome,访问localhost:3001就会看到: 

     

    居然不行,问题在于Docker没有意识到自己运行在虚拟机的环境里面,因此我们需要将vm的端口映射到我们本地机器上: 
    Java代码 
    1. Docker container:80 -> vm host:3001 -> OSX:3001  

    这个从虚拟机管理器里面可以轻松的搞定: 

     

    现在我们可以看到页面了: 

     

    这就是我们放在容器中的文件。好极了!现在我准备好尝试更复杂一点的容器了。 

    小贴士: 

    我注意到在虚拟机里面同时并行的运行Docker会整个让系统hang住。我怀疑同时跑两个虚拟工具可能让某个地方卡住产生了冲突的结果。我相信docker-machine的并行的支持正在在积极的解决中,0.5版本可能会看到。直到这之前,你可以参考:http://kb.parallels.com/en/123356并且看看:https://github.com/Parallels/docker-machine中对docker-machine的fork版本。 

    原文链接:Yet another intro to docker (翻译:钟最龙 校对:宋喻) 

    译文来自:DockOne.io

    posted @ 2015-10-30 12:49 paulwong 阅读(457) | 评论 (0)编辑 收藏

    糖果CMS,一个像糖果一样的CMS 糖果CMS

    一个JAVA的内容管理系统
    http://www.oschina.net/p/tg-cms

    posted @ 2015-10-27 18:24 paulwong 阅读(490) | 评论 (0)编辑 收藏

    REDIS监控

    http://git.oschina.net/hellovivi/RedisFlag

    posted @ 2015-10-19 12:39 paulwong 阅读(479) | 评论 (1)编辑 收藏

    深度技术揭秘,支付宝,财付通,到底每天都是怎样工作的?

    为了可以更好地解释支付结算系统对账过程,我们先把业务从头到尾串起来描述一下场景,帮助大家理解:一个可能得不能再可能的场景,请大家深刻理解里面每个角色做了什么,获取了哪些信息:  某日阳光灿烂,支付宝用户小明在淘宝上看中了暖脚器一只,价格100元。犹豫再三后小明使用支付宝网银完成了支付,支付宝显示支付成功,淘宝卖家通知他已发货,最近几日注意查收。
      小明:持卡人,消费者,淘宝和支付宝的注册会员,完成了支付动作,自己的银行账户资金减少,交易成功。
      银行:收单银行,接受来自支付宝的名为“支付宝BBB”的100元订单,并引导持卡人小明支付成功,扣除小明银行卡账户余额后返回给支付宝成功通知,并告诉支付宝这笔交易在银行流水号为“银行CCC”
      支付宝:支付公司,接收到淘宝发来的订单号为“淘宝AAA”的商户订单号,并形成支付系统唯一流水号:“支付宝BBB”发往银行系统。然后获得银行回复的支付成功信息,顺带银行流水号“银行CCC”
      淘宝:我们支付公司称淘宝这类电商为商户,是支付系统的客户。淘宝向支付系统发送了一笔交易收单请求,金额为100,订单号为:“淘宝AAA”,支付系统最后反馈给淘宝这笔支付流水号为“支付BBB”
      以上流程貌似大家都达到了预期效果,但实际上仍然还有一个问题:
      对支付公司(支付宝)而言,虽然银行通知了它支付成功,但资金实际还要T+1后结算到它银行账户,所以目前只是一个信息流,资金流还没过来。
      Tips:插一句话,对支付系统内部账务来说,由于资金没有能够实时到账,所以此时小明的这笔100元交易资金并没有直接记入到系统资产类科目下的“银行存款”科目中,而是挂在“应收账款”或者“待清算科目”中。大白话就是,这100元虽然答应给我了,我也记下来了,但还没收到,我挂在那里。
      对商户(淘宝)而言,虽然支付公司通知了它支付成功,他也发货了,但资金按照合同也是T+1到账。如果不对账确认一下,恐怕也会不安。
      倒是对消费者(小明)而言:反正钱付了,你们也显示成功了,等暖脚器呀等暖脚器~
      基于支付公司和商户的困惑,我们的支付结算系统需要进行两件事情:一是资金渠道对账,通称对银行帐;二是商户对账,俗称对客户帐。对客户帐又分为对公客户和对私客户,通常对公客户会对对账文件格式、对账周期、系统对接方案有特殊需求,而对私客户也就是我们一般的消费者只需要可以后台查询交易记录和支付历史流水就可以了。
      我们先聊银行资金渠道对账,由于支付公司的资金真正落地在商业银行,所以资金渠道的对账显得尤为重要。
      在一个银行会计日结束后,银行系统会先进行自己内部扎帐,完成无误后进行数据的清分和资金的结算,将支付公司当日应入账的资金结算到支付公司账户中。于此同时,目前多数银行已经支持直接系统对接的方式发送对账文件。于是,在某日临晨4点,支付宝系统收到来自银行发来的前一会计日对账文件。根据数据格式解析正确后和前日支付宝的所有交易数据进行匹配,理想情况是一一匹配成功无误,然后将这些交易的对账状态勾对为“已对账”。
      Tips:此时,对账完成的交易,会将该笔资金从“应收账款”或者“待清算账款”科目中移动到“银行存款”科目中,以示该交易真正资金到账。
      以上太理想了,都那么理想就不要对账了。所以通常都会出一些差错,那么我总结了一下常见的差错情况:
      1.支付时提交到银行后没有反馈,但对账时该交易状态为支付成功
      这种情况很正常,因为我们在信息传输过程中,难免会出现掉包和信息不通畅。消费者在银行端完成了支付行为,银行的通知信息却被堵塞了,如此支付公司也不知道结果,商户也不知道结果。如果信息一直没法通知到支付公司这边,那么这条支付结果就只能在日终对账文件中体现了。这时支付公司系统需要对这笔交易进行补单操作,将交易置为成功并完成记账规则,有必要还要通知到商户。
      此时的小明:估计急的跳起来了……付了钱怎么不给说支付成功呢!坑爹!
      TIPS:通常银行系统会开放一个支付结果查询接口。支付公司会对提交到银行但没有回复结果的交易进行间隔查询,以确保支付结果信息的实时传达。所以以上情况出现的概率已经很少了。
      2.我方支付系统记录银行反馈支付成功,金额为100,银行对账金额不为100
      这种情况已经不太常见了,差错不管是长款和短款都不是我们想要的结果。通常双方系统通讯都是可作为纠纷凭证的,如果银行在支付结果返回时确认是100元,对账时金额不一致,可以要求银行进行协调处理。而这笔账在支付系统中通常也会做对应的挂账处理,直到纠纷解决。
      3.我方支付系统记录银行反馈支付成功,但对账文件中压根不存在
      这种情况也经常见到,因为跨交易会计日的系统时间不同,所以会出现我们认为交易是23点59分完成的,银行认为是第二天凌晨0点1分完成。对于这种情况我们通常会继续挂账,直到再下一日的对账文件送达后进行对账匹配。
      如果这笔交易一直没有找到,那么就和第二种情况一样,是一种短款,需要和银行追究。
      以上情况针对的是一家银行资金渠道所作的流程,实际情况中,支付公司会在不同银行开立不同银行账户,用以收单结算(成本会降低),所以真实情况极有可能是:
      临晨1点,工行对账文件丢过来(支行A)
      临晨1点01分,工行又丢一个文件过来(支行B)
      临晨1点15分,农行对账文件丢过来
      。 。 。
      临晨5点,兴业银行文件丢过来
      。。。
      不管什么时候,中国银行都必须通过我方业务员下载对账文件再上传的方式进行对账,所以系统接收到中行文件一般都是早上9点05分……
      对系统来说,每天都要处理大量并发的对账数据,如果在交易高峰时段进行,会引起客户交互的延迟和交易的失败,这是万万行不得的所以通常支付公司不会用那么傻的方式处理数据,而是在一个会计日结束后,通常也是临晨时段,将前一日交易增量备份到专用对账服务器中,在物理隔绝环境下进行统一的对账行为,杜绝硬件资源的抢占。
      以上是银行资金渠道入款的对账,出款基本原理一致,不过出款渠道在实际业务过程中还有一些特别之处,由于大家不是要建设系统,我就不赘述了。
      谈完了资金渠道的对账,我们再来看看对客户帐。
      前面提到了,由于资金落在银行,所以对支付公司来说,对银行帐非常重要。那么同理,由于资金落在支付公司,所以对商户来说,对支付公司账也一样重要。能否提供高品质甚至定制化的服务,是目前支付公司走差异化路线的一个主要竞争点。
      之前说过,银行与支付公司之间的通讯都是可以作为纠纷凭证的。原理是对支付报文的关键信息进行密钥加签+md5处理,以确保往来报文“不可篡改,不可抵赖”。
      同理,支付公司和商户之间也会有类似机制保证报文的可追溯性,由此我们可以知道,一旦我方支付系统通知商户支付结果,那么我们就要为此承担责任。由此我们再推断一个结论:
      即便某支付订单银行方面出错导致资金未能到账,一旦我们支付系统通知商户该笔交易成功,那么根据协议该结算的资金还是要结算给这个商户。自然,我们回去追究银行的问题,把账款追回。
      没经过排版的小知识点---------------------------------------------------
      一、对支付系统而言,最基本的对账功能是供商户在其后台查询下载某一时间段内的支付数据文件,以供商户自己进行对账。
      这个功能应该是个支付公司就有,如果没有就别混了。
      二、对大多数支付系统而言,目前已经可以做到对账文件的主动投送功能。
      这个功能方便了商户系统和支付系统的对接,商户的结算人员无须登录支付平台后台下载文件进行对账,省去了人工操作的麻烦和风险。
      对大型支付系统而言,商户如果跨时间区域很大,反复查询该区域内的数据并下载,会对服务器造成比较大的压力。各位看官别笑,觉得查个数据怎么就有压力了。实际上为了这个查询,我最早就职的一家支付公司重新优化了所有SQL语句,并且因为查询压力过大服务器瘫痪过好几次。
      现在比较主流的做法是把商户短期内查询过、或者经常要查询的数据做缓存。实在不行就干脆实时备份,两分钟同步一次数据到专用数据库供商户查询,以避免硬件资源占用。甚至……大多数支付公司都会对查询范围跨度和历史事件进行限制,比如最多只能查一个月跨度内,不超过24个月前的数据……以避免服务嗝屁。
      对账这块大致就这样了,再往细的说就说不完了,
      风险控制,在各行各业都尤其重要。
      金融即风险,控制好风险,才有利润。
      虽然第三方支付严格意义上说并非属于金融行业,但由于涉及资金的清分和结算,业务主体又是资金的收付,所以风险控制一样非常重要。
      对支付公司来说,风控主要分为合规政策风控以及交易风控两种。
      前者主要针对特定业务开展,特定产品形态进行法规层面的风险规避,通常由公司法务和风控部门一起合作进行。例如,一家公司要开展第三方支付业务,现在要获得由人民银行颁发的“支付业务许可证”。遵守中国对于金融管制的所有条规,帮助人行监控洗钱行为……这些法规合规风险,虽然条条框框,甚至显得文绉绉,但如果没人解读没人公关,业务都会无法开展。
      当然,这块也不是本题所关注的重点,提问者关注的,应当是业务进行过程中的交易风控环节。
      现在随着各支付公司风险控制意识的加强,风控系统渐渐被重视起来。除了上述提到的合规风控相关功能,风控系统最讲技术含量,最讲业务水平,最考究数据分析的业务就是交易风控环节。
      对一笔支付交易而言,在它发生之前、发生过程中及发生过程后,都会被风控系统严密监控,以确保支付及客户资产安全。而所有的所有秘密,都归结到一个词头上:规则。风控系统是一系列规则的集合,任何再智能的风控方案,都绕不开规则二字。
      我们先看看,哪些情况是交易风控需要监控处理的:
      1.钓鱼网站
      什么是钓鱼呢?
      用我的说法,就是利用技术手段蒙蔽消费者,当消费者想付款给A的时候,替换掉A的支付页面,将钱付给B,以达成非法占用资金的目的。
      还有更低级的,直接就是发小广告,里面带一个类似http://tiaobao.com的网址,打开后和淘宝页面一摸一样,上当客户直接付款给假冒网站主。
      第一种情况风控系统是可以通过规则进行简单判定的,虽然有误杀,但不会多。
      通常使用的规则是判断提交订单的IP地址和银行实际支付完成的IP地址是否一致,如果不一致,则判断为钓鱼网站风险交易,进入待确认订单。
      但第二种情况,亲爹亲娘了,支付公司真的没办法。当然遇到客户投诉后可能会事后补救,但交易是无法阻止了。
      2.盗卡组织利用盗卡进行交易
      大家都知道,信用卡信息是不能随便公布给别人的,国内大多信用卡虽然都设置了密码,但银行仍然会开放无磁无密支付接口给到商户进行快速支付之用。
      所谓无磁无密,就是不需要磁道信息,不需要密码就可以进行支付的通道。只需要获取到客户的CVV,有效期,卡号三个核心信息,而这三个信息是在卡上直接有的,所以大家不要随便把卡交给别人哦~
      碰到类似的这种交易,风控系统就不会像钓鱼网站这样简单判断了。
      过去我们所有的历史交易,都会存库,不仅会存支付相关信息,更会利用网页上的控件(对,恶心的activex或者目前用的比较多的flash控件)抓取支付者的硬件信息,存储在数据库中。
      当一笔交易信息带着能够搜集到的硬件信息一同提交给风控系统时,风控系统会进行多种规则判定。
      例如:当天该卡是否交易超过3次
      当天该IP是否交易超过3次
      该主机CPU的序列号是否在黑名单之列
      等等等等,一批规则跑完后,风控系统会给该交易进行加权评分,标示其风险系数,然后根据评分给出处理结果。
      通过硬件信息采集以及历史交易记录回溯,我们可以建立很多交易风控规则来进行监控。所以规则样式的好坏,规则系数的调整,就是非常重要的用以区别风控系统档次高低的标准。
      例如,我听说著名的风控厂商RED,有一个“神经网络”机制,灰常牛逼。
       
      其中有一个规则我到现在还记忆犹新:
      某人早上八点在加利福尼亚进行了信用卡支付,到了下午一点,他在东亚某小国家发起了信用卡支付申请。系统判断两者距离过长,不是短短5小时内能够到达的,故判定交易无效,支付请求拒绝。
      规则非常重要,当然,数据也一样重要。我们不仅需要从自己的历史记录中整合数据,同时还要联合卡组织、银行、风控机构,购买他们的数据和风控服务用来增加自己的风控实力。
      SO,风控是一个不断积累数据、分析数据、运营数据、积累数据的循环过程。
      好的风控规则和参数,需要经过无数次的规则修改和调整,是一个漫长的过程。
      不知道大家做互联网,有没有利用GA做过AB测试,同样的,风控系统也需要反复地做类似AB测试的实验,以保证理论和实际的匹配。
      最后给大家说一个小小的概念:
      所谓风控,是指风险控制,不是风险杜绝。
      风控的目标一定不是把所有风险全部杜绝。
      合理的风控,目标一定是:利润最大化,而不是风险最小化
      过于严格的风控规则,反而会伤害公司利益(看看销售和风控经常打架就知道了)
      不光是交易的风控,我们日常制定规则,法规,公司流程,也一定要秉着这个出发点进行规划。

    posted @ 2015-09-09 23:09 paulwong 阅读(862) | 评论 (0)编辑 收藏

    Reactor模式详解

    前记

    第一次听到Reactor模式是三年前的某个晚上,一个室友突然跑过来问我什么是Reactor模式?我上网查了一下,很多人都是给出NIO中的 Selector的例子,而且就是NIO里Selector多路复用模型,只是给它起了一个比较fancy的名字而已,虽然它引入了EventLoop概 念,这对我来说是新的概念,但是代码实现却是一样的,因而我并没有很在意这个模式。然而最近开始读Netty源码,而Reactor模式是很多介绍Netty的文章中被大肆宣传的模式,因而我再次问自己,什么是Reactor模式?本文就是对这个问题关于我的一些理解和尝试着来解答。

    什么是Reactor模式

    要回答这个问题,首先当然是求助Google或Wikipedia,其中Wikipedia上说:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。从这个描述中,我们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。如果用图来表达:

    从结构上,这有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并没有Queue来做缓冲,每当一个Event输入到Service Handler之后,该Service Handler会主动的根据不同的Event类型将其分发给对应的Request Handler来处理。

    更学术的,这篇文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events)上说:“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。这段描述和Wikipedia上的描述类似,有多个输入源,有多个不同的EventHandler(RequestHandler)来处理不同的请求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注册到Initiation Dispatcher中,然后Initiation Dispatcher根据输入的Event分发给注册的EventHandler;然而Initiation Dispatcher并不监听Event的到来,这个工作交给Synchronous Event Demultiplexer来处理。

    Reactor模式结构

    在解决了什么是Reactor模式后,我们来看看Reactor模式是由什么模块构成。图是一种比较简洁形象的表现方式,因而先上一张图来表达各个模块的名称和他们之间的关系:

    Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel可以是CONNECT事件,对SocketChannel可以是READ、WRITE、CLOSE事件等。
    Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,可以调用Selector的selectedKeys()方法获取Set<SelectionKey>,一个SelectionKey表达一个有事件发生的Channel以及该Channel上的事件类型。上图的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是对的,那内部实现应该是select()方法在事件到来后会先设置Handle的状态,然后返回。不了解内部实现机制,因而保留原图。
    Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。
    Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。
    Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。

    Reactor模式模块之间的交互

    简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:

    1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
    2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而建立Handle到EventHandler的映射(Map)。
    3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
    4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
    5. 在EventHandler的handle_events()方法中还可以向InitiationDispatcher中注册新的Eventhandler,比如对AcceptorEventHandler来,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中。

    Reactor模式实现

    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式,这个Logging Server的实现完全遵循这里对Reactor描述,因而放在这里以做参考。Logging Server中的Reactor模式实现分两个部分:Client连接到Logging Server和Client向Logging Server写Log。因而对它的描述分成这两个步骤。
    Client连接到Logging Server

    1. Logging Server注册LoggingAcceptor到InitiationDispatcher。
    2. Logging Server调用InitiationDispatcher的handle_events()方法启动。
    3. InitiationDispatcher内部调用select()方法(Synchronous Event Demultiplexer),阻塞等待Client连接。
    4. Client连接到Logging Server。
    5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的连接到来。 
    6. LoggingAcceptor调用accept方法accept这个新连接。
    7. LoggingAcceptor创建新的LoggingHandler。
    8. 新的LoggingHandler注册到InitiationDispatcher中(同时也注册到Synchonous Event Demultiplexer中),等待Client发起写log请求。
    Client向Logging Server写Log

    1. Client发送log到Logging server。
    2. InitiationDispatcher监测到相应的Handle中有事件发生,返回阻塞等待,根据返回的Handle找到LoggingHandler,并回调LoggingHandler中的handle_event()方法。
    3. LoggingHandler中的handle_event()方法中读取Handle中的log信息。
    4. 将接收到的log写入到日志文件、数据库等设备中。
    3.4步骤循环直到当前日志处理完成。
    5. 返回到InitiationDispatcher等待下一次日志写请求。

    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有对Reactor模式的C++的实现版本,多年不用C++,因而略过。 

    Java NIO对Reactor的实现

    在Java的NIO中,对Reactor模式有无缝的支持,即使用Selector类封装了操作系统提供的Synchronous Event Demultiplexer功能。这个Doug Lea已经在Scalable IO In Java中有非常深入的解释了,因而不再赘述,另外这篇文章对Doug Lea的Scalable IO In Java有一些简单解释,至少它的代码格式比Doug Lea的PPT要整洁一些。

    需要指出的是,不同这里使用InitiationDispatcher来管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandler,因而不需要注册EventHandler这个步骤,或者设置Attachment就是这里的注册。而且在这篇文章中,Doug Lea从单线程的Reactor、Acceptor、Handler实现这个模式出发;演化为将Handler中的处理逻辑多线程化,实现类似Proactor模式,此时所有的IO操作还是单线程的,因而再演化出一个Main Reactor来处理CONNECT事件(Acceptor),而多个Sub Reactor来处理READ、WRITE等事件(Handler),这些Sub Reactor可以分别再自己的线程中执行,从而IO操作也多线程化。这个最后一个模型正是Netty中使用的模型。并且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相应的描述。

    EventHandler接口定义

    对EventHandler的定义有两种设计思路:single-method设计和multi-method设计:
    A single-method interface:它将Event封装成一个Event Object,EventHandler只定义一个handle_event(Event event)方法。这种设计的好处是有利于扩展,可以后来方便的添加新的Event类型,然而在子类的实现中,需要判断不同的Event类型而再次扩展成 不同的处理方法,从这个角度上来说,它又不利于扩展。另外在Netty3的使用过程中,由于它不停的创建ChannelEvent类,因而会引起GC的不稳定。
    A multi-method interface:这种设计是将不同的Event类型在 EventHandler中定义相应的方法。这种设计就是Netty4中使用的策略,其中一个目的是避免ChannelEvent创建引起的GC不稳定, 另外一个好处是它可以避免在EventHandler实现时判断不同的Event类型而有不同的实现,然而这种设计会给扩展新的Event类型时带来非常 大的麻烦,因为它需要该接口。

    关于Netty4对Netty3的改进可以参考这里
    ChannelHandler with no event objectIn 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.

    4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() andhandleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

    为什么使用Reactor模式

    归功与Netty和Java NIO对Reactor的宣传,本文慕名而学习的Reactor模式,因而已经默认Reactor具有非常优秀的性能,然而慕名归慕名,到这里,我还是要不得不问自己Reactor模式的好处在哪里?即为什么要使用这个Reactor模式?在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是这么说的:
    Reactor Pattern优点

    Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method functionality. The application-independent mechanisms become reusable components that know how to demultiplex events and dispatch the appropriate hook methods defined byEvent Handlers. In contrast, the application-specific functionality in a hook method knows how to perform a particular type of service.

    Improve modularity, reusability, and configurability of event-driven applications: The pattern decouples application functionality into separate classes. For instance, there are two separate classes in the logging server: one for establishing connections and another for receiving and processing logging records. This decoupling enables the reuse of the connection establishment class for different types of connection-oriented services (such as file transfer, remote login, and video-on-demand). Therefore, modifying or extending the functionality of the logging server only affects the implementation of the logging handler class.

    Improves application portability: The Initiation Dispatcher’s interface can be reused independently of the OS system calls that perform event demultiplexing. These system calls detect and report the occurrence of one or more events that may occur simultaneously on multiple sources of events. Common sources of events may in- clude I/O handles, timers, and synchronization objects. On UNIX platforms, the event demultiplexing system calls are calledselect and poll [1]. In the Win32 API [16], the WaitForMultipleObjects system call performs event demultiplexing.

    Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at the level of event demultiplexing and dispatching within a process or thread. Serialization at the Initiation Dispatcher level often eliminates the need for more complicated synchronization or locking within an application process.

    这些貌似是很多模式的共性:解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制等,因而并不能很好的说明什么,特别是它鼓吹的对性能的提升,这里并没有体现出来。当然在这篇文章的开头有描述过另一种直观的实现:Thread-Per-Connection,即传统的实现,提到了这个传统实现的以下问题:
    Thread Per Connection缺点

    Efficiency: Threading may lead to poor performance due to context switching, synchronization, and data movement [2];

    Programming simplicity: Threading may require complex concurrency control schemes;

    Portability: Threading is not available on all OS platforms. 对于性能,它其实就是第一点关于Efficiency的描述,即线程的切换、同步、数据的移动会引起性能问题。也就是说从性能的角度上,它最大的提升就是减少了性能的使用,即不需要每个Client对应一个线程。我的理解,其他业务逻辑处理很多时候也会用到相同的线程,IO读写操作相对CPU的操作还是要慢很多,即使Reactor机制中每次读写已经能保证非阻塞读写,这里可以减少一些线程的使用,但是这减少的线程使用对性能有那么大的影响吗?答案貌似是肯定的,这篇论文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)对随着线程的增长带来性能降低做了一个统计:

    在这个统计中,每个线程从磁盘中读8KB数据,每个线程读同一个文件,因而数据本身是缓存在操作系统内部的,即减少IO的影响;所有线程是事先分配的,不会有线程启动的影响;所有任务在测试内部产生,因而不会有网络的影响。该统计数据运行环境:Linux 2.2.14,2GB内存,4-way 500MHz Pentium III。从图中可以看出,随着线程的增长,吞吐量在线程数为8个左右的时候开始线性下降,并且到64个以后而迅速下降,其相应事件也在线程达到256个后指数上升。即1+1<2,因为线程切换、同步、数据移动会有性能损失,线程数增加到一定数量时,这种性能影响效果会更加明显。

    对于这点,还可以参考C10K Problem,用以描述同时有10K个Client发起连接的问题,到2010年的时候已经出现10M Problem了。

    当然也有人说:Threads are expensive are no longer valid.在不久的将来可能又会发生不同的变化,或者这个变化正在、已经发生着?没有做过比较仔细的测试,因而不敢随便断言什么,然而本人观点,即使线程变的影响并没有以前那么大,使用Reactor模式,甚至时SEDA模式来减少线程的使用,再加上其他解耦、模块化、提升复用性等优点,还是值得使用的。

    Reactor模式的缺点

    Reactor模式的缺点貌似也是显而易见的:
    1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
    2. Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
    3. Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。

    参考

    Reactor Pattern WikiPedia
    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
    Scalable IO In Java
    C10K Problem WikiPedia

    posted @ 2015-09-08 12:28 paulwong 阅读(707) | 评论 (0)编辑 收藏

    Netty3架构解析

    前记

    很早以前就有读Netty源码的打算了,然而第一次尝试的时候从Netty4开始,一直抓不到核心的框架流程,后来因为其他事情忙着就放下了。这次趁着休假重新捡起这个硬骨头,因为Netty3现在还在被很多项目使用,因而这次决定先从Netty3入手,瞬间发现Netty3的代码比Netty4中规中矩的多,很多概念在代码本身中都有清晰的表达,所以半天就把整个框架的骨架搞清楚了。再读Netty4对Netty3的改进总结,回去读Netty4的源码,反而觉得轻松了,一种豁然开朗的感觉。

    记得去年读Jetty源码的时候,因为代码太庞大,并且自己的HTTP Server的了解太少,因而只能自底向上的一个一个模块的叠加,直到最后把所以的模块连接在一起而看清它的真正核心骨架。现在读源码,开始习惯先把骨架理清,然后延伸到不同的器官、血肉而看清整个人体。

    本文从Reactor模式在Netty3中的应用,引出Netty3的整体架构以及控制流程;然而除了Reactor模式,Netty3还在ChannelPipeline中使用了Intercepting Filter模式,这个模式也在Servlet的Filter中成功使用,因而本文还会从Intercepting Filter模式出发详细介绍ChannelPipeline的设计理念。本文假设读者已经对Netty有一定的了解,因而不会包含过多入门介绍,以及帮Netty做宣传的文字。

    Netty3中的Reactor模式

    Reactor模式在Netty中应用非常成功,因而它也是在Netty中受大肆宣传的模式,关于Reactor模式可以详细参考本人的另一篇文章《Reactor模式详解》,对Reactor模式的实现是Netty3的基本骨架,因而本小节会详细介绍Reactor模式如何应用Netty3中。

    如果读《Reactor模式详解》,我们知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler构成,在Java的实现版本中,Channel对应Handle,Selector对应Synchronous Event Demultiplexer,并且Netty3还使用了两层Reactor:Main Reactor用于处理Client的连接请求,Sub Reactor用于处理和Client连接后的读写请求(关于这个概念还可以参考Doug Lea的这篇PPT:Scalable IO In Java)。所以我们先要解决Netty3中使用什么类实现所有的上述模块并把他们联系在一起的,以NIO实现方式为例:

    模式是一种抽象,但是在实现中,经常会因为语言特性、框架和性能需要而做一些改变,因而Netty3对Reactor模式的实现有一套自己的设计:

    1. ChannelEvent:Reactor是基于事件编程的,因而在Netty3中使用ChannelEvent抽象的表达Netty3内部可以产生的各种事件,所有这些事件对象在Channels帮助类中产生,并且由它将事件推入到ChannelPipeline中,ChannelPipeline构建ChannelHandler管道,ChannelEvent流经这个管道实现所有的业务逻辑处理。ChannelEvent对应的事件有:ChannelStateEvent表示Channel状态的变化事件,而如果当前Channel存在Parent Channel,则该事件还会传递到Parent Channel的ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS等,该事件可以在各种不同实现的Channel、ChannelSink中产生;MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事件,它由NioWorker、需要对Message做进一步处理的ChannelHandler产生;WriteCompletionEvent表示写完成而触发的事件,它由NioWorker产生;ExceptionEvent表示在处理过程中出现的Exception,它可以发生在各个构件中,如Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEvent由IdleStateHandler触发,这也是一个ChannelEvent可以无缝扩展的例子。注:在Netty4后,已经没有ChannelEvent类,所有不同事件都用对应方法表达,这也意味这ChannelEvent不可扩展,Netty4采用在ChannelInboundHandler中加入userEventTriggered()方法来实现这种扩展,具体可以参考这里

    2. ChannelHandler:在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一个标记接口,它有两个子接口:ChannelDownstreamHandler和ChannelUpstreamHandler,其中ChannelDownstreamHandler表示从用户应用程序流向Netty3内部直到向Socket写数据的管道,在Netty4中改名为ChannelOutboundHandler;ChannelUpstreamHandler表示数据从Socket进入Netty3内部向用户应用程序做数据处理的管道,在Netty4中改名为ChannelInboundHandler。

    3. ChannelPipeline:用于管理ChannelHandler的管道,每个Channel一个ChannelPipeline实例,可以运行过程中动态的向这个管道中添加、删除ChannelHandler(由于实现的限制,在最末端的ChannelHandler向后添加或删除ChannelHandler不一定在当前执行流程中起效,参考这里)。ChannelPipeline内部维护一个ChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现,具体可以参考这里,这个模式的实现在后一节中还是详细介绍。

    4. NioSelector:Netty3使用NioSelector来存放Selector(Synchronous Event Demultiplexer),每个新产生的NIO Channel都向这个Selector注册自己以让这个Selector监听这个NIO Channel中发生的事件,当事件发生时,调用帮助类Channels中的方法生成ChannelEvent实例,将该事件发送到这个Netty Channel对应的ChannelPipeline中,而交给各级ChannelHandler处理。其中在向Selector注册NIO Channel时,Netty Channel实例以Attachment的形式传入,该Netty Channel在其内部的NIO Channel事件发生时,会以Attachment的形式存在于SelectionKey中,因而每个事件可以直接从这个Attachment中获取相关链的Netty Channel,并从Netty Channel中获取与之相关联的ChannelPipeline,这个实现和Doug Lea的Scalable IO In Java一模一样。另外Netty3还采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor设计,其中NioSelector的两个实现:Boss即为Main Reactor,NioWorker为Sub Reactor。Boss用来处理新连接加入的事件,NioWorker用来处理各个连接对Socket的读写事件,其中Boss通过NioWorkerPool获取NioWorker实例,Netty3模式使用RoundRobin方式放回NioWorker实例。更形象一点的,可以通过Scalable IO In Java的这张图表达:


    若与Ractor模式对应,NioSelector中包含了Synchronous Event Demultiplexer,而ChannelPipeline中管理着所有EventHandler,因而NioSelector和ChannelPipeline共同构成了Initiation Dispatcher。

    5. ChannelSink:在ChannelHandler处理完成所有逻辑需要向客户端写响应数据时,一般会调用Netty Channel中的write方法,然而在这个write方法实现中,它不是直接向其内部的Socket写数据,而是交给Channels帮助类,内部创建DownstreamMessageEvent,反向从ChannelPipeline的管道中流过去,直到第一个ChannelHandler处理完毕,最后交给ChannelSink处理,以避免阻塞写而影响程序的吞吐量。ChannelSink将这个MessageEvent提交给Netty Channel中的writeBufferQueue,最后NioWorker会等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这就是上图的send是从SubReactor直接出发的原因。

    6. Channel:Netty有自己的Channel抽象,它是一个资源的容器,包含了所有一个连接涉及到的所有资源的饮用,如封装NIO Channel、ChannelPipeline、Boss、NioWorkerPool等。另外它还提供了向内部NIO Channel写响应数据的接口write、连接/绑定到某个地址的connect/bind接口等,个人感觉虽然对Channel本身来说,因为它封装了NIO Channel,因而这些接口定义在这里是合理的,但是如果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例就可以得到和它相关的基本所有资源,因而这种write、connect、bind动作不应该再由它负责,而是应该由其他类来负责,比如在Netty4中就在ChannelHandlerContext添加了write方法,虽然netty4并没有删除Channel中的write接口。

    Netty3中的Intercepting Filter模式

    如果说Reactor模式是Netty3的骨架,那么Intercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实现,它是Netty3具有良好性能的基础,而Intercepting Filter模式则是ChannelHandler组合实现一个应用程序逻辑的基础,只有很好的理解了这个模式才能使用好Netty,甚至能得心应手。

    关于Intercepting Filter模式的详细介绍可以参考这里,本节主要介绍Netty3中对Intercepting Filter模式的实现,其实就是DefaultChannelPipeline对Intercepting Filter模式的实现。在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandler,同时它在Netty3中也起到桥梁的作用,即它是连接Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipeline,它使用一个ChannelHandler的双向链表来存储,以DefaultChannelPipelineContext作为节点:

    public interface ChannelHandlerContext {
        Channel getChannel();
        ChannelPipeline getPipeline();
        String getName();
        ChannelHandler getHandler();
        boolean canHandleUpstream();
        boolean canHandleDownstream();
        void sendUpstream(ChannelEvent e);
        void sendDownstream(ChannelEvent e);
        Object getAttachment();
        void setAttachment(Object attachment);
    }

    private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
        volatile DefaultChannelHandlerContext next;
        volatile DefaultChannelHandlerContext prev;
        private final String name;
        private final ChannelHandler handler;
        private final boolean canHandleUpstream;
        private final boolean canHandleDownstream;
        private volatile Object attachment;
    ..
    }

    在DefaultChannelPipeline中,它存储了和当前ChannelPipeline相关联的Channel、ChannelSink以及ChannelHandler链表的head、tail,所有ChannelEvent通过sendUpstream、sendDownstream为入口流经整个链表:

    public class DefaultChannelPipeline implements ChannelPipeline {
        private volatile Channel channel;
        private volatile ChannelSink sink;
        private volatile DefaultChannelHandlerContext head;
        private volatile DefaultChannelHandlerContext tail;

        public void sendUpstream(ChannelEvent e) {
            DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
            if (head == null) {
                return;
            }
            sendUpstream(head, e);
        }

        void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
            try {
                ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
            } catch (Throwable t) {
                notifyHandlerException(e, t);
            }
        }

        public void sendDownstream(ChannelEvent e) {
            DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
            if (tail == null) {
                try {
                    getSink().eventSunk(this, e);
                    return;
                } catch (Throwable t) {
                    notifyHandlerException(e, t);
                    return;
                }
            }
            sendDownstream(tail, e);
        }

        void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
            if (e instanceof UpstreamMessageEvent) {
                throw new IllegalArgumentException("cannot send an upstream event to downstream");
            }
            try {
                ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
            } catch (Throwable t) {
                e.getFuture().setFailure(t);
                notifyHandlerException(e, t);
            }
        }

    对Upstream事件,向后找到所有实现了ChannelUpstreamHandler接口的ChannelHandler组成链(
    getActualUpstreamContext()),而对Downstream事件,向前找到所有实现了ChannelDownstreamHandler接口的ChannelHandler组成链(getActualDownstreamContext()):

        private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
            if (ctx == null) {
                return null;
            }
            DefaultChannelHandlerContext realCtx = ctx;
            while (!realCtx.canHandleUpstream()) {
                realCtx = realCtx.next;
                if (realCtx == null) {
                    return null;
                }
            }
            return realCtx;
        }
        private DefaultChannelHandlerContext getActualDownstreamContext(DefaultChannelHandlerContext ctx) {
            if (ctx == null) {
                return null;
            }
            DefaultChannelHandlerContext realCtx = ctx;
            while (!realCtx.canHandleDownstream()) {
                realCtx = realCtx.prev;
                if (realCtx == null) {
                    return null;
                }
            }
            return realCtx;
        }

    在实际实现ChannelUpstreamHandler或ChannelDownstreamHandler时,调用 ChannelHandlerContext中的sendUpstream或sendDownstream方法将控制流程交给下一个 ChannelUpstreamHandler或下一个ChannelDownstreamHandler,或调用Channel中的write方法发送 响应消息。

    public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
        public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
            // handle current logic, use Channel to write response if needed.
            
    // ctx.getChannel().write(message);
            ctx.sendUpstream(e);
        }
    }

    public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
        public void handleDownstream(
                ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
            // handle current logic
            ctx.sendDownstream(e);
        }
    }

    当ChannelHandler向ChannelPipelineContext发送事件时,其内部从当前ChannelPipelineContext节点出发找到下一个ChannelUpstreamHandler或ChannelDownstreamHandler实例,并向其发送ChannelEvent,对于Downstream链,如果到达链尾,则将ChannelEvent发送给ChannelSink:

    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
        if (prev == null) {
            try {
                getSink().eventSunk(DefaultChannelPipeline.this, e);
            } catch (Throwable t) {
                notifyHandlerException(e, t);
            }
        } else {
            DefaultChannelPipeline.this.sendDownstream(prev, e);
        }
    }

    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext next = getActualUpstreamContext(this.next);
        if (next != null) {
            DefaultChannelPipeline.this.sendUpstream(next, e);
        }
    }

    正是因为这个实现,如果在一个末尾的ChannelUpstreamHandler中先移除自己,在向末尾添加一个新的ChannelUpstreamHandler,它是无效的,因为它的next已经在调用前就固定设置为null了。


    ChannelPipeline作为ChannelHandler的容器,它还提供了各种增、删、改ChannelHandler链表中的方法,而且如果某个ChannelHandler还实现了LifeCycleAwareChannelHandler,则该ChannelHandler在被添加进ChannelPipeline或从中删除时都会得到同志:

    public interface LifeCycleAwareChannelHandler extends ChannelHandler {
        void beforeAdd(ChannelHandlerContext ctx) throws Exception;
        void afterAdd(ChannelHandlerContext ctx) throws Exception;
        void beforeRemove(ChannelHandlerContext ctx) throws Exception;
        void afterRemove(ChannelHandlerContext ctx) throws Exception;
    }

    public interface ChannelPipeline {
        void addFirst(String name, ChannelHandler handler);
        void addLast(String name, ChannelHandler handler);
        void addBefore(String baseName, String name, ChannelHandler handler);
        void addAfter(String baseName, String name, ChannelHandler handler);
        void remove(ChannelHandler handler);
        ChannelHandler remove(String name);
        <T extends ChannelHandler> T remove(Class<T> handlerType);
        ChannelHandler removeFirst();
        ChannelHandler removeLast();
        void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
        ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
        <T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName, ChannelHandler newHandler);
        ChannelHandler getFirst();
        ChannelHandler getLast();
        ChannelHandler get(String name);
        <T extends ChannelHandler> T get(Class<T> handlerType);
        ChannelHandlerContext getContext(ChannelHandler handler);
        ChannelHandlerContext getContext(String name);
        ChannelHandlerContext getContext(Class<? extends ChannelHandler> handlerType);
        void sendUpstream(ChannelEvent e);
        void sendDownstream(ChannelEvent e);
        ChannelFuture execute(Runnable task);
        Channel getChannel();
        ChannelSink getSink();
        void attach(Channel channel, ChannelSink sink);
        boolean isAttached();
        List<String> getNames();
        Map<String, ChannelHandler> toMap();
    }

    在DefaultChannelPipeline的ChannelHandler链条的处理流程为:


    http://www.blogjava.net/DLevin/archive/2015/09/04/427031.html


    参考:

    《Netty主页》
    《Netty源码解读(四)Netty与Reactor模式》
    《Netty代码分析》
    Scalable IO In Java
    Intercepting Filter Pattern

    posted @ 2015-09-08 11:11 paulwong 阅读(525) | 评论 (0)编辑 收藏