SegmentFault 架构那点事最新的文章
2018-12-10T09:12:48+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
微服务架构实践:从零搭建网站扫码登录
https://segmentfault.com/a/1190000017316741
2018-12-10T09:12:48+08:00
2018-12-10T09:12:48+08:00
开物笔记
https://segmentfault.com/u/k5rj
6
<p>微信扫码登录大家都是应用比较多的登录方式了,现在大的购物网站像京东、淘宝等都支持使用APP扫码登录网站了。今天就用APP扫码登录网站的实例来举例说明微服务架构的搭建过程。</p>
<h2>微服务架构应该是什么样子</h2>
<p>在这之前先看一看一个微服务架构落地以后应该是什么样子的。平常所有的微服务架构更多的是从框架来讲的像Dubbo,SpringCloud等,从整个SpringCloud的生态来讲它也只包含微服务的一部分。因为微服务的拆分不可避免的造成了系统的复杂性,团队间的合作管理和持续的交付等等,都是一项比较复杂的工程,如果没有好的团队管理规范和持续交付的流程等微服务是很难落地的。</p>
<p><img src="/img/bVbkO1I?w=700&h=524" alt="图片描述" title="图片描述"></p>
<p>下面简单介绍一下上图中微服务架构的每一层的功能和作用:</p>
<p><strong>基础设施层</strong>,这一项除非自己搭建IDC,基本上现在的阿里云、腾讯云和百度云等都已经很好的支撑,特别是对于小的公司来说,更节省成本。</p>
<p><strong>平台服务层</strong>,对于现有的微服务能够快速动态部署那就是Docker了,再加上现有k8s等容器管理工具等,更是让微服务的部署如虎添翼,如果系统已经达到已经规模以后,可以考虑使用此种方式进行动态的扩容,一般情况下使用Docker就能解决部署问题了。</p>
<p><strong>支撑服务层</strong>,这一层跟微服务框架贴的非常近了,像SpringCloud已经自带了很多功能,像注册中心、配置中心、熔断限流和链路跟踪等,Dubbo也自带注册中心。</p>
<p><strong>业务服务层</strong>,这一层主要解决的是业务系统如何使用微服务进行解耦,各业务模块间如何进行分层交互等,形成了以基础服务模块为底层和以聚合服务为前端的“大中台小前台”的产品策略。</p>
<p><strong>网关服务层</strong>,这一层解决了权限控制、外部调用如何进行模块的负载均衡,可以实现在该层实现权限和流量的解耦,来满足不同的端的流量和权限不同的需求。</p>
<p><strong>接入层</strong>,该层主要是为了解决相同网关多实例的负载均衡的问题,防止单点故障灯。</p>
<p><strong>微服务开发框架</strong>,现在流行的微服务框架主要是SpringCloud和Dubbo,SpingCloud提供了更加完整的生态,Dubbo更适合内部模块间的快速高并发的调用。</p>
<p><strong>持续交付流水线</strong>,快速进行需求迭代,从提交代码到部署上线,能够快速的交付。</p>
<p><strong>工程实践与规范</strong>,这一项做不好,那整个微服务实施起来绝对是痛不欲生啊,基础模块如何定义,基础模块如何与其他模块解耦,如何进行版本的管理这个我在之前的使用Git和Maven进行版本管理和迭代的方法进行了说明。</p>
<p><strong>端到端的工具链</strong>,这里就是敏捷运维工具,从研发代码到最终上线到生产环境,任何一部都要有工具去实现完成,实现点一个按钮就能最终上线的系统。</p>
<p>以上讲了实现微服务架构应该要做哪些事情,现在可以想想你的微服务架构到底落地到生成程度了,闲话少说,书归正传,今天是用APP扫码登录网站这个功能来进行举例说明应该从哪些方面进行微服务的落地实践。</p>
<h2>网站扫码登录功能</h2>
<p>这个功能是指在网站上选择使用二维码扫码登录,网站展示二维码,使用已经登录的应用APP扫码并确认登录后,网站就能登录成功,这既简单快捷,又提高了安全性。现在实现扫码登录网站的技术基本上有两种,一种就是轮询,另一种就是长连接,长连接又分为服务器端单向通信和双向通信两种,服务端单向通信只能由服务器端向客户端一直发送数据,双向通信是客户端和服务器端可以相互发送数据。像微信、京东和淘宝都是采用轮询的方式进行扫码登录的,一直使用轮询的方式在请求服务器端。今天我设计的这个扫码登录的功能,是采用的长连接能够双向通信的WebSocket的方式实现的。</p>
<h2>网站扫码实现流程</h2>
<p>1.用户在网站上登录时选择扫码登录。</p>
<p>2.服务器端收到请求,生成一个临时的令牌,前端生成带令牌的链接地址的二维码,在浏览器上显示。</p>
<p>3.PC端同时要与后台建立起websocket连接,等待后台发送登录成功的指令过来。</p>
<p>4.用户用应用扫码,这个时候如果已经登陆过,后台就能获取到当前用户的token,如果没有登录到系统中,需要提前做登录。</p>
<p>5.用户在应用APP上已经显示了是否确认登录的按钮。</p>
<p>6.用户点击确认按钮,应用APP发起后端的api调用。</p>
<p>7.后端接收到调用,根据临时名牌向websocket模块发送当前用户的token,pc端接收到登录成功,跳转到用户个人首页。如果用户点击了取消按钮,会根据uid向websocket模块发送取消登录的指令。</p>
<h2>技术的选型</h2>
<p><strong>1.微服务框架的选择</strong></p>
<p>现在比较流行的是SpringCloud和Dubbo这两个框架,RPC的微服务框架还有Motan都不错,这里我使用SpringCloud和Dubbo这两个框架,使用SpringCloud实现网关和聚合服务模块并对外提供http服务,使用Dubbo实现内部模块间的接口调用。注册中心使用Zookeeper,Zookeeper能够同时支持SpringCloud和Dubbo进行注册。</p>
<p><strong>2.Websocket框架选择</strong></p>
<p>其实Spring现在已经具备websocket的功能了,但是我没有选择使用它,因为它只是实现了websocket的基本功能,像websocket的集群,客户端的管理等等,使用spring实现的话都得从零开始写。之前就一直使用netty-socketio做websocket的开发,它具备良好的集群、客户端管理等功能,而且它本身通知支持轮询和websocket两种方式,所以选它省事省时。</p>
<p><strong>3.存储的选择</strong></p>
<p>临时令牌存放在redis中,用来进行websocket连接时的验证,防止恶意的攻击,用户数据放在mysql中。</p>
<p><strong>4.源码管理工具和构建工具的选择</strong></p>
<p>使用Git作为代码管理工具,方便进行代码持续迭代和发布上线,使用Gitlab作为源码服务器端,可以进行代码的合并管理,使整个代码质量更容易把控。采用Maven做为构建工具,并使用nexus创建自己的Maven私服,用来进行基础服务版本的管理和发布。搭建Sonar服务器,Maven中集成Sonar插件进行代码质量的自动化检测。</p>
<p><strong>5.持续构建和部署工具</strong></p>
<p>采用Docker部署的方式,快速方便。采用Jekins做持续构建,可以根据git代码变更快速的打包上线。</p>
<h2>模块功能设计</h2>
<p>根据<a href="https://link.segmentfault.com/?enc=SyZvhS2vzMA%2BxEJsFuC%2BAQ%3D%3D.b2YJiCpieXaGbqhVxQV0xYcl3MdlbPsirih%2F1%2BtQ7HATbdWek770imp8IdAE4nN2BFcA7%2Fhno9nf5LEfz8UDo49wq2ghA4g6ifroSEGlA30fzE7S5Rnn6RAIr%2BTmZeyGya%2FVJA%2Fhq6i9ofZWgnD0%2FqnBS7CTkXK2bXq1IRW2MPn9LkRO3yrEK1F1ca7XXp76vBFwe18k3RL1AsP5UZOsg0xCjOYT7jRvR5GTy%2FiGcKJrm5sMz2we30fa5nT3dZFkI2%2B%2FHysijMT%2Fjg%2Fex5fXSpvAcajgAyrXfBqKb%2Bj5T1U8jSoiIK5H9FDX6nHFShnoWYieR7L8%2BwECLJYp79j7jw%3D%3D" rel="nofollow">《微服务架构:如何用十步解耦你的系统?》</a>中微服务解耦的设计原则:</p>
<p>1.将Websocket作为服务独立出来只用来进行数据的通信,保证其功能的单一性,独立对外提供SocketApi接口,通过Dubbo的方式来调用其服务。<br><img src="/img/bVbkO13?w=1280&h=1223" alt="图片描述" title="图片描述"></p>
<p>2.将用户功能作为服务独立出来,进行用户注册和登录的功能,并对外提供UserApi接口,通过Dubbo的方式来调用。</p>
<p>3.对外展示的功能包括页面和静态文件都统一到WebServer模块中,需要操作用户数据或者需要使用Websocket进行通信的都统一使用Dubbo调用。</p>
<p>4.对于基本的权限认证和动态负载均衡都统一放到Gateway模块中,Gateway可以实现http的负载均衡和websocket的负载均衡。</p>
<p>5.如果访问量非常大时,就考虑将Gateway分开部署,单独进行http服务和websocket服务,将两者的流量解耦。</p>
<p>6.webserver端访问量大时,可以考虑将静态页面发布到CDN中,减少该模块的负载。</p>
<h2>开发规范解耦公共服务</h2>
<p>指定良好的开发管理规范,使用Git做好版本代码的分支管理,每个需求迭代使用单独的分支,保证每次迭代都可以独立上线,Maven私服中每次SocketApi和UserApi的升级都要保留历史版本可用,Dubbo服务做好多版本的兼容支持,这样就能将基础公共的服务进行解耦。</p>
<h2>总结</h2>
<p>微服务的引入不仅仅是带来了好处,同时也带来了系统的复杂性,不能只从框架和代码的角度来考虑微服务架构的落地,更要从整个管理的角度去考虑如何括地,否则使用微服务开发只会带来更多麻烦和痛苦。</p>
<p>长按二维码,关注公众号煮酒科技(xtech100),输入数字11返回代码哟。<br><img src="/img/bVbkO0f?w=430&h=430" alt="图片描述" title="图片描述"></p>
一次性能优化:吞吐量从1提升到2500
https://segmentfault.com/a/1190000017254982
2018-12-04T18:31:31+08:00
2018-12-04T18:31:31+08:00
开物笔记
https://segmentfault.com/u/k5rj
6
<blockquote>性能优化,简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。压测也是检验一个架构设计是否合理的一个重要方法。</blockquote>
<h2>项目介绍</h2>
<p>这个项目是一个线下支付的交易系统,使用线下设备发起支付。项目使用SpringMVC+Dubbo的微服务开发模式,其中SpringMvc作为Web端部署在tomcat上对外提供rest服务,其他模块使用dubbo开发,SpringMVC调用dubbo服务完成业务功能。<br><img src="/img/bVbkyXc?w=457&h=349" alt="图片描述" title="图片描述"></p>
<h2>优化目的</h2>
<p>这一次的性能优化,目的是在不调整业务逻辑的情况下通过压力测试的方式测试下单时接口的性能能达到多少,如果有性能问题,就要在不修改业务逻辑的情况下通过优化各项参数的方式(包括JVM优化、数据库连接数和Dubbo连接数)来提升整体性能。</p>
<h2>压测前的准备</h2>
<p><strong>压力测试策略</strong></p>
<p>采用循序渐进的方式,逐渐增加并发量的方式,先以1个并发开始,慢慢增加,当达到瓶颈的时候就进行参数调整和优化。</p>
<p><strong>建立压测分支</strong></p>
<p>压测优化过程中,可能随时需要调整配置参数,可能会对一些配置参数进行修改或者对公共代码进行优化。为了不让其他研发人员的开发受到干扰,在git上基于release分支建立了feature_stress分支,专门用来压测过程中,参数配置调优使用,不影响其他研发人员的开发。</p>
<p><strong>搭建压测环境</strong></p>
<p>为了不不影响研发和测试人员的工作,单独申请了三台4C32G的虚拟机进行压力测试,一台部署tomcat,一台部署dubbo服务,一台部署mysql。为什么只申请单机部署的方式压测呢?因为本次测试的目的是看单台服务器的性能情况,优化的目的是将单台服务器的性能最大化。</p>
<p><strong>准备测试脚本</strong></p>
<p>本次测试选用jmeter作为测试工具进行压力测试,由测试人员模拟线下支付的流程写好测试脚本。</p>
<h2>初步压测</h2>
<p>很保守的,从1个并发开始压测,测试结果让人非常的惊喜,压测开始几分钟后,就出现大量的连接失败,无法继续测试。出现大量的内存溢出的异常。没出现异常时的吞吐量达到了1req/s。同时,通过jconsole控制台监控tomcat,发现其线程数最高到了2万多个。<br><img src="/img/bVbkyXA?w=1178&h=784" alt="图片描述" title="图片描述"></p>
<p><strong>问题分析</strong></p>
<p>这个异常是创建本地线程失败抛出的异常,为什么有这么大量的线程呢?不合常理呀!于是查看对应的代码,原来这是一个自定义的一个人日志Appender,在这个Appender里使用了线程池,这个Appender本来的目的是使用多线程提升日志性能,并且将所有的日志都收集到一个文件中。代码类似如下:<br><img src="/img/bVbkyXC?w=988&h=888" alt="图片描述" title="图片描述"><br>log4j.properties中直接将新的Appender添加到了rootLogger下:<br><img src="/img/bVbkyXE?w=1472&h=550" alt="图片描述" title="图片描述"></p>
<h2>初步优化</h2>
<p>通过以上代码和日志配置文件就能看出来,每次日志输出都会创建线程,这就是线程为什么越来越多,最后导致无法创建新的本地线程的原因。</p>
<p>为了不影响现有的功能,将LogAppender类append方法中的线程池创建的部分变成了类的属性,并固定只用8个线程:</p>
<p><img src="/img/bVbkyXE?w=1472&h=550" alt="图片描述" title="图片描述"></p>
<p>提交代码重新部署压测环境,再进行压测,吞吐量马上就上来了,达到了1700req/s,</p>
<p><img src="/img/bVbkyXS?w=999&h=416" alt="![图片描述" title="![图片描述">][7]</p>
<h2>深入调优</h2>
<p>经过上面的优化之后,整体的性能还不是很高,经过jconsole监控发现,线程数还是很高,虽然没有2万那么多了,但还是有3000多的线程,这么多的线程根本无法发挥机器的性能,时间都浪费在线程切换上了,通过查看线程栈发现大量的线程都是dubbo连接的线程,为什么如此之多呢?查看dubbo的配置文件,consumer的链接数都设置成了1000,于是将所有dubbo的consumer连接数均改为了64。同时将log4j.properties中配置了控制台输出的都关闭了。</p>
<p>最终的优化后的测试结果,最高达到了2600req/s。<br><img src="/img/bVbkyX2?w=1309&h=569" alt="图片描述" title="图片描述"></p>
<h2>总结</h2>
<p>性能优化需要从几个方面考虑:</p>
<ol>
<li>CPU是否有瓶颈,本项目没有大量的计算,所以CPU没有瓶颈。</li>
<li>IO是否是瓶颈,线程太多或者连接太多都是瓶颈,本项目中并发时创建了大量的线程,达到了服务器的最高可用的连接数,导致内存溢出了,创建太多的线程也会让CPU把时间都浪费在线程切换上了。</li>
<li>生产环境不要使用控制台输出,因为控制台输出是同步的,输出太多会对性能有很大的影响。</li>
<li>如果出现吞吐量小的情况可以输出线程栈,看看到底是block在哪里了,是调用服务时间长还是读写数据库时间长。</li>
</ol>
<p>———— / END / ————</p>
微服务架构:如何用十步解耦你的系统?
https://segmentfault.com/a/1190000017243764
2018-12-04T09:14:03+08:00
2018-12-04T09:14:03+08:00
开物笔记
https://segmentfault.com/u/k5rj
54
<p>导言:</p>
<blockquote>耦合性,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。高内聚低耦合,是软件工程中的概念,是判断设计好坏的标准,主要是面向对象的设计,主要是看类的内聚性是否高,耦合度是否低。</blockquote>
<p>SpringCloud和Dubbo都是现在比较成熟的微服务框架,如何使用两者构建搭建你的微服务系统呢?他们是如何将你的系统解耦的?又是怎么解耦的呢?请听我慢慢道来:</p>
<h2>第一步,解耦现有模块</h2>
<p>将现有耦合在一起的模块进行重新的设计,设计成可以独立部署的多个模块,使用微服务框架很容易做到,成熟的示例代码都特别多,这里不再多讲。下面是我的微服务实现的一个架构设计图。</p>
<p><img src="/img/bVbkv29?w=659&h=444" alt="图片描述" title="图片描述"></p>
<h2>第二步,抽取公共模块</h2>
<p>架构设计原则之一就是反向依赖,只从上往下依赖,所以,我们将公共的重复功能的模块抽取出来。必须强调一点的是,公共模块必须足够的功能单一,不能有其他业务的逻辑判断在里面。在整个模块依赖关系里,应该是一棵树状结构的关系图,而不是一个网状的关系图。</p>
<p><strong>1)做好代码控制</strong></p>
<p>笔者之前就碰到过这种问题,模块划分完了,当需求变更的时候,研发人员根本不管是不是公共模块,只要能快速完成任务,哪里改的快就在哪里改。因此,这个需要内部要做好代码的权限管理,不应该开放所有的提交代码的权限给所有的人。后来我就将公共模块的合并代码的权限收回了,合并代码需要先提交申请,代码review过才能合并代码。这就保证了公共模块代码的功能单一。</p>
<p><strong>2)做好版本管理</strong></p>
<p>公共模块被多个模块模块使用,任何代码的修改都可能会导致到正在使用的模块无法使用。这个就需要做好各个模块的版本管理,我是使用maven进行版本管理的,定义一个总的父pom项目来进行各个模块的版本管理,任何被其他模块使用的开发包都要在父pom里进行版本管理。当新的需求来了以后,需要对公共模块进行修改时,要更新模块的版本号,同时更新父pom的版本号,需要使用公共模块新功能的模块就修改父pom的版本号,不需要使用公共模块新功能的模块就不用修改父pom的版本号,这样公共模块的新老版本都能使用,即使出现问题,也只会影响到使用新版本的模块。</p>
<h2>第三步,解耦迭代需求</h2>
<p>现在的代码迭代速度快,同时会面对多个需求,有的需求紧急,有的需求不紧急,而且紧急程度可能随时会调整,如果将所有的需求都放在一个分支,当只想上线其中几个需求的时候发现无法将不上线需求的代码拆分出来,是不是很尴尬,即使能拆分出来,代码修改过以后又要重新进行部署测试,很费时费力,所以要针对不同的需求重新建立研发分支,这样就将不同需求的分支解耦,保证想上哪个就上哪个,需要上多个需求的就将分支合并上线。</p>
<h2>第四步,配置解耦</h2>
<p>为每个模块每个环境配置一个配置文件,这样就可以把不同的环境的配置解耦,不用每次上线都更新一次。但是如果需要修改数据库配置,还是需要重新部署重启应用才能解决。使用微服务的配置中心就能解决这个问题了,比如使用ZooKeeper作为SpringCloud的配置中心,修改ZooKeeper中的节点数据就可以实时更新配置并生效。</p>
<h2>第五步,权限解耦</h2>
<p>当采用微服务架构把原来的系统拆分成多个系统以后,你会发现原来简单的问题,现在变的复杂了,比如功能的权限控制,原来是跟业务代码放到一起,现在如果每个业务模块都有功能权限的代码,将是一件非常麻烦的事情。那么解决办法就是将权限功能迁移出来,恰巧使用SpringCloudGateway就能完成这件事情,SpringCloudGateway能够进行负载均衡,各种路由拦截,只要将原来的权限控制代码迁移到Gateway里实现以下就可以了,权限配置管理界面和代码逻辑都不用变。如果是API接口呢,就需要将安全验证等功能放在Gateway里实现就好了。</p>
<h2>第六步,流量解耦</h2>
<p>当你的系统访问量越来越大的时候,你会发现每次升级都是一件非常麻烦的事情,领导会跟你说这个功能忙时不能停机影响用户使用呀,只能半夜升级呀,多么痛快的事情啊。有的时候运营人员也会发现,怎么我的后台访问怎么这么慢?问题出在哪里呢?问题就出在,所有的模块都用了一个Gateway,多端同时使用了相同的流量入口,当在举行大促时,并发量非常高,带宽占用非常大,那么其他的功能也会跟着慢下来。</p>
<p>不能在举行大促时发券时,我线下支付一直支付不了,这是非常严重的事故了,客服电话会被打爆了。所以,必须要对流量进行拆分,各个端的流量不能相互影响,比如APP端、微信端、运营后台和商户后台等都要分配独立的Gateway,并接入独立的带宽,对于流量大的端可以使用弹性带宽,对于运营后台和商户后台就比较小的固定的带宽即可。这样就大大降低了升级时的难度,是不是再上线时就没那么紧张了?</p>
<h2>第七步,数据解耦</h2>
<p>系统刚上线的时候,数据量不大,所有的模块感觉都挺好的,当时间一长,系统访问量非常大的时候会发现功能怎么都变慢了,怎么mysql的cpu经常100%。那么恭喜你,你中招了,你的数据需要解耦了。</p>
<p>首先要模块间数据解耦,将不同模块使用独立的数据库,保证各模块之间的数据不相互影响。</p>
<p>其次就是冷热数据解耦,同一个模块运行时间长了以后也会积累大量的数据,为了保证系统的性能的稳定,要减少因为数据量太大造成的性能降低,需要对历史数据进行定期的迁移,对于完整数据分析汇总就在其他的库中实现。</p>
<h2>第八步,扩容解耦</h2>
<p>一个好的架构设计是要有好的横向扩展的能力,在不需要修改代码只通过增加硬件的方式就能提高系统的性能。SpringCloud和Dubbo的注册中心天生就能够实现动态添加模块的节点,其他模块调用能够实时发现并请求到新的模块节点上。</p>
<h2>第九步,部署解耦</h2>
<p>互联网开发在于能够快速的试错,当一个新的版本上线时,经常是需要先让一部分用户进行测试一下,这就是传说中的灰度发布,同一个模块先部署升级几台服务器到新版本,重启完成后流量进来以后,就可以验证当前部署的这几台服务器有没有问题,就继续部署其他的节点,如果有问题马上回滚到上一个版本。使用SpringCloudGateway的WeighRouterFilter就能实现这个功能。</p>
<p><img src="/img/bVbjNnR?w=400&h=303" alt="图片描述" title="图片描述"></p>
<h2>第十步,动静解耦</h2>
<p>当同一个模块的瞬间有非常高并发的时候,对,就是说的秒杀,纯粹的流量解耦还是不够,因为不能让前面的流量冲击后面真正的下单的功能,这个时候就需要更细的流量解耦,要将静态文件的访问通通抛给CDN来解决,动态和静态之间是通过定时器来出发的,时间未到之前一直刷新的是静态的文件,当时间到了之后,生成新的js文件,告诉静态页面可以访问下单功能了。</p>
<h2>总结</h2>
<p>在模块划分时,要遵循“一个模块,一个功能”的原则,尽可能使模块达到功能内聚。</p>
<p>事实上,微服务架构短期来看,并没有很明显的好处,甚至短期内会影响系统的开发进度,因为高内聚,低耦合的系统对开发设计人员提出了更高的要求。高内聚,低耦合的好处体现在系统持续发展的过程中,高内聚,低耦合的系统具有更好的重用性,维护性,扩展性,可以更高效的完成系统的维护开发,持续的支持业务的发展,而不会成为业务发展的障碍。</p>
<p>———— / END / ————</p>
十种JVM内存溢出的情况,你碰到过几种?
https://segmentfault.com/a/1190000017226359
2018-12-02T20:35:58+08:00
2018-12-02T20:35:58+08:00
开物笔记
https://segmentfault.com/u/k5rj
16
<p>导言:</p>
<p>对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不需要自己实现释放内存,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存这一切看起来非常美好,但是一旦出现内存溢出或者内存泄漏的问题,对于不熟悉jvm虚拟机是怎么使用内存的话,那么排查错误将会是一项非常艰巨的任务。所以在了解内存溢出之前先要搞明白JVM的内存模型。</p>
<p>JVM(Java虚拟机)是一个抽象的计算模型。就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。目的是为构建在其上运行的应用程序提供一个运行环境。JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构。</p>
<h2>JVM内存模型</h2>
<p>根据 JVM8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。<br><img src="/img/bVbkruX?w=574&h=496" alt="图片描述" title="图片描述"></p>
<ol><li>元空间(Metaspace)</li></ol>
<p>元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。</p>
<p>2.虚拟机栈(JVM Stacks)</p>
<p>每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。</p>
<ol><li>本地方法栈(Native Method Stack)</li></ol>
<p>与虚拟机栈类似,区别是虚拟机栈执行java方法,本地方法站执行native方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。</p>
<ol><li>程序计数器(Program Counter Register)</li></ol>
<p>程序计数器可以看成是当前线程所执行的字节码的行号指示器。在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为“线程私有”内存。</p>
<p>5.堆内存(Heap)</p>
<p>堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在对上进行分配。jdk1.8后,字符串常量池从永久代中剥离出来,存放在队中。</p>
<p>6.直接内存(Direct Memory)</p>
<p>直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中农定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。</p>
<h2>内存溢出的十个场景</h2>
<p>JVM运行时首先需要类加载器(classLoader)加载所需类的字节码文件。加载完毕交由执行引擎执行,在执行过程中需要一段空间来存储数据(类比CPU与主存)。这段内存空间的分配和释放过程正是我们需要关心的运行时数据区。内存溢出的情况就是从类加载器加载的时候开始出现的,内存溢出分为两大类:OutOfMemoryError和StackOverflowError。以下举出10个内存溢出的情况,并通过实例代码的方式讲解了是如何出现内存溢出的。</p>
<h2>1.java堆内存溢出</h2>
<p>当出现java.lang.OutOfMemoryError:Java heap space异常时,就是堆内存溢出了。</p>
<p><strong>1.问题描述</strong></p>
<p>1.设置的jvm内存太小,对象所需内存太大,创建对象时分配空间,就会抛出这个异常。</p>
<p>2.流量/数据峰值,应用程序自身的处理存在一定的限额,比如一定数量的用户或一定数量的数据。而当用户数量或数据量突然激增并超过预期的阈值时,那么就会峰值停止前正常运行的操作将停止并触发java . lang.OutOfMemoryError:Java堆空间错误</p>
<p><strong>2.示例代码</strong></p>
<p>编译以下代码,执行时jvm参数设置为-Xms20m -Xmx20m<br><img src="/img/bVbkru7?w=1202&h=716" alt="图片描述" title="图片描述"></p>
<p>以上这个示例,如果一次请求只分配一次5m的内存的话,请求量很少垃圾回收正常就不会出错,但是一旦并发上来就会超出最大内存值,就会抛出内存溢出。</p>
<p><strong>3.解决方法</strong></p>
<p>首先,如果代码没有什么问题的情况下,可以适当调整-Xms和-Xmx两个jvm参数,使用压力测试来调整这两个参数达到最优值。</p>
<p>其次,尽量避免大的对象的申请,像文件上传,大批量从数据库中获取,这是需要避免的,尽量分块或者分批处理,有助于系统的正常稳定的执行。</p>
<p>最后,尽量提高一次请求的执行速度,垃圾回收越早越好,否则,大量的并发来了的时候,再来新的请求就无法分配内存了,就容易造成系统的雪崩。</p>
<h2>2.java堆内存泄漏</h2>
<p><strong>1.问题描述</strong></p>
<p>Java中的内存泄漏是一些对象不再被应用程序使用但垃圾收集无法识别的情况。因此,这些未使用的对象仍然在Java堆空间中无限期地存在。不停的堆积最终会触发java . lang.OutOfMemoryError。</p>
<p><strong>2.示例代码</strong><br><img src="/img/bVbkrvW?w=1426&h=1432" alt="图片描述" title="图片描述"></p>
<p>当执行上面的代码时,可能会期望它永远运行,不会出现任何问题,假设单纯的缓存解决方案只将底层映射扩展到10,000个元素,而不是所有键都已经在HashMap中。然而事实上元素将继续被添加,因为key类并没有重写它的equals()方法。</p>
<p>随着时间的推移,随着不断使用的泄漏代码,“缓存”的结果最终会消耗大量Java堆空间。当泄漏内存填充堆区域中的所有可用内存时,垃圾收集无法清理它,java . lang.OutOfMemoryError。</p>
<p><strong>3.解决办法</strong></p>
<p>相对来说对应的解决方案比较简单:重写equals方法即可:</p>
<p><img src="/img/bVbkrvW?w=1426&h=1432" alt="图片描述" title="图片描述"></p>
<h2>3.垃圾回收超时内存溢出</h2>
<p><strong>1、问题描述</strong><br>当应用程序耗尽所有可用内存时,GC开销限制超过了错误,而GC多次未能清除它,这时便会引发java.lang.OutOfMemoryError。当JVM花费大量的时间执行GC,而收效甚微,而一旦整个GC的过程超过限制便会触发错误(默认的jvm配置GC的时间超过98%,回收堆内存低于2%)。</p>
<p><strong>2.示例代码</strong></p>
<p><img src="/img/bVbkrvT?w=1372&h=792" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决方法</strong></p>
<p>要减少对象生命周期,尽量能快速的进行垃圾回收。</p>
<h2>4.Metaspace内存溢出</h2>
<p><strong>1.问题描述</strong></p>
<p>元空间的溢出,系统会抛出java.lang.OutOfMemoryError: Metaspace。出现这个异常的问题的原因是系统的代码非常多或引用的第三方包非常多或者通过动态代码生成类加载等方法,导致元空间的内存占用很大。</p>
<p><strong>2.示例代码</strong></p>
<p>以下是用循环动态生成class的方式来模拟元空间的内存溢出的。<br><img src="/img/bVbkrvR?w=2000&h=1370" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决办法</strong></p>
<p>默认情况下,元空间的大小仅受本地内存限制。但是为了整机的性能,尽量还是要对该项进行设置,以免造成整机的服务停机。</p>
<p>1)优化参数配置,避免影响其他JVM进程</p>
<p>-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。 </p>
<p>-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。 </p>
<p>除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性: <br>-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集 。<br>-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。</p>
<p>2)慎重引用第三方包</p>
<p>对第三方包,一定要慎重选择,不需要的包就去掉。这样既有助于提高编译打包的速度,也有助于提高远程部署的速度。</p>
<p>3)关注动态生成类的框架</p>
<p>对于使用大量动态生成类的框架,要做好压力测试,验证动态生成的类是否超出内存的需求会抛出异常。</p>
<h2>5.直接内存内存溢出</h2>
<p><strong>1.问题描述</strong></p>
<p>在使用ByteBuffer中的allocateDirect()的时候会用到,很多javaNIO(像netty)的框架中被封装为其他的方法,出现该问题时会抛出java.lang.OutOfMemoryError: Direct buffer memory异常。</p>
<p>如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题。</p>
<p><strong>2.示例代码</strong></p>
<p><img src="/img/bVbkrvK?w=1396&h=1032" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决办法</strong></p>
<p>如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize,并及时clear内存。</p>
<h2>6.栈内存溢出</h2>
<p><strong>1.问题描述</strong></p>
<p>当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据。</p>
<p>当一个方法递归调用自己时,新的方法所产生的数据(也可以理解为新的栈帧)将会被push到栈顶,方法每次调用自己时,会拷贝一份当前方法的数据并push到栈中。因此,递归的每层调用都需要创建一个新的栈帧。这样的结果是,栈中越来越多的内存将随着递归调用而被消耗,如果递归调用自己一百万次,那么将会产生一百万个栈帧。这样就会造成栈的内存溢出。</p>
<p><strong>2.示例代码</strong><br><img src="/img/bVbkrvH?w=1100&h=650" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决办法</strong></p>
<p>如果程序中确实有递归调用,出现栈溢出时,可以调高-Xss大小,就可以解决栈内存溢出的问题了。递归调用防止形成死循环,否则就会出现栈内存溢出。</p>
<h2>7.创建本地线程内存溢出</h2>
<p><strong>1.问题描述</strong></p>
<p>线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了。<br><strong>2.示例代码</strong><br><img src="/img/bVbkrvF?w=1454&h=782" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决方法</strong></p>
<p>首先检查操作系统是否有线程数的限制,使用shell也无法创建线程,如果是这个问题就需要调整系统的最大可支持的文件数。</p>
<p>日常开发中尽量保证线程最大数的可控制的,不要随意使用线程池。不能无限制的增长下去。</p>
<h2>8.超出交换区内存溢出</h2>
<p><strong>1.问题描述</strong></p>
<p>在Java应用程序启动过程中,可以通过-Xmx和其他类似的启动参数限制指定的所需的内存。而当JVM所请求的总内存大于可用物理内存的情况下,操作系统开始将内容从内存转换为硬盘。</p>
<p>一般来说JVM会抛出Out of swap space错误,代表应用程序向JVM native heap请求分配内存失败并且native heap也即将耗尽时,错误消息中包含分配失败的大小(以字节为单位)和请求失败的原因。</p>
<p><strong>2.解决办法</strong></p>
<p>增加系统交换区的大小,我个人认为,如果使用了交换区,性能会大大降低,不建议采用这种方式,生产环境尽量避免最大内存超过系统的物理内存。其次,去掉系统交换区,只使用系统的内存,保证应用的性能。</p>
<h2>9.数组超限内存溢出</h2>
<p><strong>1.问题描述</strong><br>有的时候会碰到这种内存溢出的描述Requested array size exceeds VM limit,一般来说java对应用程序所能分配数组最大大小是有限制的,只不过不同的平台限制有所不同,但通常在1到21亿个元素之间。当Requested array size exceeds VM limit错误出现时,意味着应用程序试图分配大于Java虚拟机可以支持的数组。JVM在为数组分配内存之前,会执行特定平台的检查:分配的数据结构是否在此平台是可寻址的。<br><strong>2.示例代码</strong></p>
<p>以下就是代码就是数组超出了最大限制。<br><img src="/img/bVbkrvr?w=1452&h=740" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决方法</strong></p>
<p>因此数组长度要在平台允许的长度范围之内。不过这个错误一般少见的,主要是由于Java数组的索引是int类型。 Java中的最大正整数为2 ^ 31 - 1 = 2,147,483,647。 并且平台特定的限制可以非常接近这个数字,例如:我的环境上(64位macOS,运行Jdk1.8)可以初始化数组的长度高达2,147,483,645(Integer.MAX_VALUE-2)。若是在将数组的长度再增加1达到nteger.MAX_VALUE-1会出现的OutOfMemoryError。</p>
<h2>10.系统杀死进程内存溢出</h2>
<p><strong>1.问题概述</strong><br>在描述该问题之前,先熟悉一点操作系统的知识:操作系统是建立在进程的概念之上,这些进程在内核中作业,其中有一个非常特殊的进程,称为“内存杀手(Out of memory killer)”。当内核检测到系统内存不足时,OOM killer被激活,检查当前谁占用内存最多然后将该进程杀掉。</p>
<p>一般Out of memory:Kill process or sacrifice child错会在当可用虚拟虚拟内存(包括交换空间)消耗到让整个操作系统面临风险时,会被触发。在这种情况下,OOM Killer会选择“流氓进程”并杀死它。</p>
<p><strong>2.示例代码</strong><br><img src="/img/bVbkrvi?w=1368&h=858" alt="图片描述" title="图片描述"></p>
<p><strong>3.解决方法</strong></p>
<p>虽然增加交换空间的方式可以缓解Java heap space异常,还是建议最好的方案就是升级系统内存,让java应用有足够的内存可用,就不会出现这种问题。</p>
<h2>总结</h2>
<p>通过以上的10种出现内存溢出情况,大家在实际碰到问题时也就会知道怎么解决了,在实际编码中也要记得:<br>1.第三方jar包要慎重引入,坚决去掉没有用的jar包,提高编译的速度和系统的占用内存。</p>
<p>2.对于大的对象或者大量的内存申请,要进行优化,大的对象要分片处理,提高处理性能,减少对象生命周期。</p>
<p>3.尽量固定线程的数量,保证线程占用内存可控,同时需要大量线程时,要优化好操作系统的最大可打开的连接数。</p>
<p>4.对于递归调用,也要控制好递归的层级,不要太高,超过栈的深度。</p>
<p>5.分配给栈的内存并不是越大越好,因为栈内存越大,线程多,留给堆的空间就不多了,容易抛出OOM。JVM的默认参数一般情况没有问题(包括递归)。</p>
微服务中消息总线架构设计应用
https://segmentfault.com/a/1190000017212260
2018-11-30T19:35:04+08:00
2018-11-30T19:35:04+08:00
开物笔记
https://segmentfault.com/u/k5rj
14
<p><strong>导语:</strong></p>
<p>当一个O2O电商系统到达一定规模之后,就需要考虑系统的可扩展性、松耦合和组件化。一般采用的都是基于时下比较流行SpringCloud和Dubbo的分布式的微服务的架构模式,虽然模块间能够独立部署了,但是模块间的还是强依赖关系,每次改动都需要重新发版上线,产品迭代速度又快,就造成了每次上线心里都唱忐忑。</p>
<p>基于上面这个背景,系统需要做重构,需要解耦,因此我增加了消息总线模块,使用异步消息队列和标准组件来实现了模块间的强耦合和快速的开发迭代。</p>
<h3>现有总线设计的问题</h3>
<p>一般的消息总线是直接使用MQ来连接上下游的模块。以O2O电商系统为例,一个订单支付完成以后需要发送通知下游的积分模块给用户送积分,优惠券模块给用户送优惠券,微信模块给用户推送微信消息,短信模块给用户发送短信等。上下游直接通过MQ服务器建立起了消息生产和消费的关系。</p>
<p><img src="/img/bVbknQV?w=598&h=354" alt="现有消息总线设计的问题" title="现有消息总线设计的问题"></p>
<p>1.下游消费端必须增加MQ消息消费处理代码,代码会比较复杂,开发者必须掌握MQ消费端开发。</p>
<p>2.当下游有多个消费端时,消费端每个模块需要生成一个消息对列,增加了MQ服务器的压力。</p>
<p>3.当下游多个消费端执行需要有先后关系时,比如优惠券发完了才能推送微信消息,这样就大大增加了系统设计的复杂程度。</p>
<h3>优化设计</h3>
<p>针对以上几个问题对消息总线系统进行了优化设计,看下图:<br><img src="/img/bVbknQ4?w=652&h=369" alt="消息总线架构设计" title="消息总线架构设计"></p>
<p>通过这种实现实现了一个下几个特点:</p>
<p>1.模块间实现了松耦合的机制,各业务模块间只跟消息总线系统集成,简化开发。</p>
<p>2.消息确保必达,消息到达任何一端都要进行确认,出现网络问题或其他异常能够重试。</p>
<p>3.保证消息处理的幂等,不能因为重复消息导致重复执行相同的操作。</p>
<h3>三个基本概念</h3>
<p>1.事件消息,如下单事件、注册事件等,事件有唯一的编号,保证任何系统收到。</p>
<p>2.消费操作,如送积分、送优惠券、微信消息推送、短信发送、PUSH推送等等。</p>
<p>3.执行日志,记录每个消费操作执行的结果,失败可以通过定时任务重试。</p>
<h3>快速扩展集成</h3>
<p><strong>为什么增加独立的消息生产模块?</strong></p>
<p>因为前端业务模块比较多的时候,只能对消息的内容本身形成一个标准,很容易出现问题,增加一个消息生产模块对外提供标准的API接口,前端业务调用按照说明添加参数即可,开发人员也不需要掌握MQ的客户端开发的相关技术。</p>
<p><strong>为什么增加独立的消息消费模块?</strong></p>
<p>因为业务需求变化较快,后端消费模块会经常增加新的模块或停用已有的模块,提供标准的消费接口供消费模块直接实现,能够快速的实现。</p>
<h3>保证消息不丢失</h3>
<p><strong>消息产生模块如何保证消息不丢失?</strong><br>保证必须产生前端模块发起消息生产的调用,由消息生产组件再发送消息到MQ,这个时候(2)如果未返回成功或网络超时就重复发送,消息生产模块与MQ之间的调用也是这样,(4)未正常返回或者网络超时(2)是返回未成功,需要重新发送消息。如果(4)返回成功就说明MQ持久化完成了(这里消息采用持久化方式),到此阶段消息发送完成。<br><strong>消息消费模块如何保证消息不丢失?</strong><br>监听MQ队列消息,如果有消息来就存储到db中,并ack给MQ,确定消息收到了,然后根据数据中配置的模块调用后端的模块异步调用后端模块,每一个操作调用都记录到db中,对于因为网络或者异常未执行成功的,由定时任务定期检查db中执行的状态,未成功的调用的消费操作继续调用执行,直到消费成功。</p>
<h3>幂等性设计</h3>
<p>在消息生成模块,因为消息未发送成功重试造成消息的重复,如果消息消费和后端业务不做幂等性设计就会业务数据产生影响,如用户下单完成可能送多次积分或者优惠券。</p>
<p>为了避免相同一个事件消息的重复的消费,就需要每条消息都有一个唯一的MSGID,在消息消费模块保存到db时对消息进行唯一性的验证,保证事件消息不重复。同样,后端的业务模块在实现消费接口时同样要做幂等处理,保证相同的事件消息不重复执行,如果已执行返回成功。</p>
<h2>延时消息实现</h2>
<p>电商系统中一般在下单多长时间内不支付订单会自动取消,这是一个典型的延时消息,因为我采用的MQ是ArtemisMQ本身天然携带延时消息的功能,所以在这个系统中我采用的是ArtemisMQ的延时消息,到指定时间自动让消息生效,在消息消费模块执行。对于已经成功支付的订单,在支付成功时增加一个删除延时消息的消费操作,减少系统的调用,删除使用的就是消息的MSGID。</p>
<h3>如何流量削峰</h3>
<p>因为ArtemisMQ本身已经实现了消息的缓冲,在消息消费端通过设定固定的线程数,就能实现消息消费的固定的消费速度,保证消息消费不会受到性能的冲击。</p>
<h3>技术架构实现</h3>
<p>使用SpringCloud+Dubbo来实现整个架构的搭建,SpringCloud用于外部rest服务的实现,Dubbo用于内部接口的调用的实现。提供消费总线接口模块,提供公共的接口类和实体类供消费生产模块和消息消费模块使用。下图为模块依赖关系图。<br><img src="/img/bVbknQ6?w=1968&h=2494" alt="模块依赖关系图" title="模块依赖关系图"><br>消息生成模块采用Dubbo实现,前端业务模块通过Dubbo接口调用消息生成接口即可,根据要求传递对应的参数即可完成。</p>
<p>消费操作的实现采用Dubbo的RPC来实现,使用的是Dubbo的可以动态生成消费者的特点,消息消费模块接收到消息后会从数据库中读取关联的消费操作列表,然后循环调用每个消费操作,每一次操作执行都要记录操作日志并记录结果。<br><img src="/img/bVbknRa?w=1408&h=1330" alt="dubbo动态rpc调用实现" title="dubbo动态rpc调用实现"><br>通过以上几点消息总线架构基本上设计并实现完成了!你有什么好的建议和意见吗?</p>
Spring Cloud配置跨域访问的五种方案?你用的是哪一种呢?
https://segmentfault.com/a/1190000017188296
2018-11-29T11:10:52+08:00
2018-11-29T11:10:52+08:00
开物笔记
https://segmentfault.com/u/k5rj
12
<p>在使用SpringCloud实现微服务时,经常会碰到前端页面访问多个二级域名的情况,跨域是首先要解决的问题。</p>
<p>解决这个问题,可以从两方面入手,一种方案是在微服务各自的业务模块中实现,即在SpringBoot层实现,另外一种方案就是在Gateway层实现。</p>
<p>首先讲一下在SpringBoot层实现的三种方案。</p>
<h2>解决方案一:在Controller上添加@CrossOrigin注解</h2>
<p>这种方式适合只有一两个rest接口需要跨域或者没有网关的情况下,这种处理方式就非常简单,适合在原来基代码基础上修改,影响比较小。</p>
<pre><code> @CrossOrigin // 注解方式
@RestController
public class HandlerScanController {
@CrossOrigin(allowCredentials="true", allowedHeaders="*", methods= {RequestMethod.GET,
RequestMethod.POST, RequestMethod.DELETE, RequestMethod.OPTIONS,
RequestMethod.HEAD,
RequestMethod.PUT,
RequestMethod.PATCH},
origins="*")
@PostMapping("/confirm")
public Response handler(@RequestBody Request json){
return null;
}
}
</code></pre>
<h2>解决方案二:增加WebMvcConfigurer全局配置</h2>
<p>如果有大量的rest接口的时候,显然第一种方案已经不适合了,工作量大,也容易出错,那就通过全局配置的方式,允许SpringBoot端所有的rest接口都支持跨域访问,这个时候就需要考虑安全性的问题。</p>
<p>代码如下:</p>
<p>@Configuration</p>
<p>public class MyConfiguration {</p>
<pre><code>@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedMethods("GET");
}
};
}
</code></pre>
<p>}</p>
<h2>解决方案三:结合Filter使用</h2>
<p>这种方案的使用场景跟第二种方案类似,只不过换成使用Filter的方式实现。<br>在spring boot的主类中,增加一个CorsFilter</p>
<pre><code> /**
* attention:简单跨域就是GET,HEAD和POST请求,但是POST请求 的"Content-Type"只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain
* 反之,就是非简单跨域,此跨域有一个预检机制,说直白点,就是会发两次请求,一次OPTIONS请求,一次真正的请求
*/
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");// 允许Get的请求方法
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
</code></pre>
<p>以上这种方案如果微服务多的话,需要在每个服务的主类上都加上这么段代码,增加了维护量。</p>
<p>以上三种方案都是在SpringBoot的基础上实现的解决方案,在模块较多或者接口较多的情况下不易维护。</p>
<p>既然SpringCloud自带Gateway,下面就讲讲使用Gateway的跨域解决方案。</p>
<h2>解决方案四,在Gateway端增加CorsFilter拦截器</h2>
<p>这种方案跟方案三有些类似,只不过是放到了Gateway端,对于有多个微服务模块的情况下,就大大减少了SpringBoot模块端的代码量,让各个模块更集中精力做业务逻辑实现。这个方案只需要在Gateway里添加Filter代码类即可。</p>
<p>public class CorsWebFilter implements WebFilter {</p>
<pre><code>private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Override
public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) {
ServerHttpRequest request = ctx.getRequest();
String path=request.getPath().value();
ServerHttpResponse response = ctx.getResponse();
if("/favicon.ico".equals(path)) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
}
</code></pre>
<p>}</p>
<h2>解决方案五,修改Gateway配置文件</h2>
<p>在仔细阅读过Gateway的文档你就会发现,原来CorsFilter早已经在Gateway里了,不需要自己写代码实现,而且更灵活,修改配置文件即可,结合配置中心使用,可以实现动态修改。</p>
<p>application.yml. </p>
<p>spring: <br> cloud:</p>
<pre><code>gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "docs.spring.io"
allowedMethods:
- GET
</code></pre>
<p>以上五种跨域方案讲完了,你用的哪一种呢?还是在自己写代码实现吗?</p>
企业只能申请两个微信公众号,微信开发不够用?不,一个公众号就够了!
https://segmentfault.com/a/1190000017185261
2018-11-29T09:12:11+08:00
2018-11-29T09:12:11+08:00
开物笔记
https://segmentfault.com/u/k5rj
4
<p>微信2018年11月16日发公告称,个人主体注册公众号数量上限由2个调整为1个。企业类主体注册公众号数量上限由5个调整为2个。这个对马上要注册公众号的企业来说顿时心情不好了。</p>
<p>大家都知道每个微信公众号在进行开发时,授权回调的域名只能设置一个,正常的开发一般一套环境就对应一个域名。</p>
<p>一般公司研发都会部署四套环境:</p>
<ol>
<li>研发环境,用户模块间联调。</li>
<li>测试环境,用户测试人员进行测试。</li>
<li>仿真环境,一个与生产环境的各方面配置比较相似的一个环境,用于上线前的各项功能验证。</li>
<li>生产环境,真正面对用户的环境。</li>
</ol>
<p>四套环境会每个对应一个域名,如果一个环境对应一个微信公众号的话就需要四个微信公众号,对于微信产品业务比较多的公司来讲这有点杯水车薪:</p>
<p>一个企业现在最多能申请2个公众号,多套环境根本不够用的。</p>
<p>申请多个公众号,每年还要重新缴费审核,麻烦。</p>
<p>如何解决这个问题呢?通过以下两步就能解决:</p>
<p>本地开发联调时可以自己申请测试公众号,研发环境也可以使用测试公众号。</p>
<p>测试环境、仿真环境和生产环境共用一个公众号。</p>
<h2>申请测试公众号联调测试</h2>
<p>首先,在浏览器打开<a href="https://link.segmentfault.com/?enc=71RAL2VsR49FOv2doXDJoQ%3D%3D.o6l0%2BZMPs8IKuKY%2FZ4unKFi8Herm%2BEWwFxvKWJLB%2BagUD2p9ngo6guYO0zMzyWiBMef4uj3Q3Mv41P%2BcIiz8fQ%3D%3D" rel="nofollow">http://mp.weixin.qq.com/debug...</a>,扫描关注后登录,需要填写接口配置。</p>
<p>其次,申请一个公网能访问的域名,推荐用natapp,启动后能够做内网穿透到本机进行聊天,非常方便。</p>
<p>最后,填写JS接口安全域名 ,设置JS接口安全域后,通过关注该测试号,开发者即可在该域名下调用微信开放的JS接口,请阅读微信JSSDK开发文档。</p>
<h2>一个公众号支持多套环境同时使用</h2>
<p>首先,申请一个独立的二级域名如wx.mydomain.com,专门用来做微信授权回调的域名。</p>
<p>然后,在每个使用虚拟路径来区分不同的环境如:</p>
<p>wx.mydomain.com/prod/ 指向生产环境的实际微服务地址。</p>
<p>wx.mydomain.com/fz/ 反向代理到仿真环境。</p>
<p>wx.mydomain.com/test/ 反向代理到测试环境。</p>
<p>wx.mydomain.com/dev/ 反向代理到研发环境。</p>
<p>最后,在配置文件中增加一个变量weixin.env,</p>
<p>生产环境 weixin.env=prod</p>
<p>仿真环境 weixin.env=fz</p>
<p>测试环境 weixin.env=test</p>
<p>研发环境 weixin.env=dev</p>
<p>举例,访问用户订单列表的页面就由原来的{weixin.env}.mydomain.com/order/list.html变为wx.mydomain.com/{weixin.env}/order/list.html,生产环境访问用户订单列表的页面就由原来prod.mydomain.com/order/list.html变为wx.mydomain.com/prod/order/list.html。其他环境的类似,反向代理可以使用nginx,也可以使用SpringCloudGateway进行处理。</p>
<p>怎么样?你看明白了?如果喜欢就转发起来吧!</p>
解决SpringBoot在Docker运行注册IP不正确的问题
https://segmentfault.com/a/1190000017157392
2018-11-27T13:19:56+08:00
2018-11-27T13:19:56+08:00
开物笔记
https://segmentfault.com/u/k5rj
2
<p><img src="/img/bVbj89J?w=900&h=500" alt="图片描述" title="图片描述"><br>在开发中经常会碰到SpringBoot应用在启动成功以后,发现注册到注册中心的IP不是自己想要的。实际开发联调的时候也经常碰到自己本地启动应用怎么获取到一个怪怪的IP,通过别的机器无法连接到这个IP。发现有虚拟机的时候,就把虚拟机网卡停掉,然后才能获取本地局域网IP。当然也可以每个服务都指定IP的方式来解决这个问题,只有几个实例还可以,但是有几十或者上百个实例如何解决呢?</p>
<p>出现这种问题的原因是当前机器有多个网卡(如有虚拟网卡)造成的,SpringBoot在获取IP的时候无法获取到正确的IP,在实际生产环境中服务器本身也有多块网卡,SpringBoot启动时只能选择一个IP,能否通过简单的配置就能解决这个问题呢?答案是,可以的,SpringBoot本身自带这种功能。</p>
<p>SpringBoot提供了三种配置方式,可以单独使用,也可以混合使用。</p>
<h2>配置忽略网络接口</h2>
<p>使用Docker中运行SpringBoot服务时,服务注册的时候,可以忽略掉一些指定名称的网络接口,想要忽略的网络接口的名称可以使用正则表达式来配置。下面的配置就是忽略名称为docker0网络接口和所有名称以veth开头的网络接口:</p>
<p>application.yml.</p>
<pre><code>spring:
cloud:
inetutils:
ignoredInterfaces:
- docker0
- veth.*
</code></pre>
<h2>配置使用指定网络接口</h2>
<p>相反,也可以使用正则表达式配置只使用指定的网络接口地址,下面配置的例子就只使用192.168开头和10.0开头的ip地址,其他的地址都不会使用。</p>
<p>bootstrap.yml.</p>
<pre><code>spring:
cloud:
inetutils:
preferredNetworks:
- 192.168
- 10.0
</code></pre>
<h2>配置使用site-local地址</h2>
<p>如果SpringBoot服务只是在局域网内使用,不会被外部调用的话,就可以使用site-local方式配置,配置方式如下:</p>
<p>.application.yml</p>
<pre><code>spring:
cloud:
inetutils:
useOnlySiteLocalInterfaces: true
</code></pre>
<p>你在实际使用过程中是否也碰到了这种问题呢?帮你解决问题了吗?有问题可以加我的微信(zhuzhsh)或者关注我的公众号(xtech100)持续关注哟!</p>
SpringCloudGateway配置https访问
https://segmentfault.com/a/1190000017155814
2018-11-27T11:14:53+08:00
2018-11-27T11:14:53+08:00
开物笔记
https://segmentfault.com/u/k5rj
1
<p><img src="/img/bVbj89J?w=900&h=500" alt="图片描述" title="图片描述"><br>Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。</p>
<p>Project Reactor是响应式编程的实现者,之前的文章我写过响应式编程的高并发性能,所以用的人会越来越多,现在来说说如何配置Spring Cloud Gateway,可以直接替代Nginx作为前置的负载均衡器。</p>
<p>下面举例说明如何进行配置:</p>
<h2>配置证书信息</h2>
<p>在配置文件application.yml中设置证书的基本信息,如下:</p>
<pre><code>server:
ssl:
enabled: true //启用ssl
key-alias: scg //启用证书
key-store-password: scg1234 //证书密码
key-store: classpath:scg-keystore.p12 //证书地址
key-store-type: PKCS12 //证书类型
</code></pre>
<h2>配置信任所有下游证书</h2>
<p>网关路由可以同时支持路由到http和https的后端服务,如果路由到Https的后端,通过以下配置,网关可以设置为信任具有以下配置的所有下游证书:</p>
<p>application.yml.</p>
<pre><code>spring:
cloud:
gateway:
httpclient:
ssl:
useInsecureTrustManager: true
</code></pre>
<h2>生产环境设置默认证书</h2>
<p>生产环境中不适合使用不安全的信任管理。对于生产环境,可以使用以下配置配置一组可信任的已知证书:</p>
<p>application.yml.</p>
<pre><code>spring:
cloud:
gateway:
httpclient:
ssl:
trustedX509Certificates:
- cert1.pem
- cert2.pem
</code></pre>
<p>如果Spring Cloud Gateway未配置可信证书,就会使用默认的安全证书(可以使用系统属性javax.net.ssl.trustStore覆盖)。</p>
<h2>设置TLS 握手超时时间</h2>
<p>网关路由后端时,使用了一个客户端连接池。通过https进行通信时,客户端会启动TLS握手。这次握手会有很多超时,可以配置超时时间</p>
<p>application.yml.</p>
<pre><code>spring:
cloud:
gateway:
httpclient:
ssl:
handshake-timeout-millis: 10000
close-notify-flush-timeout-millis: 3000
close-notify-read-timeout-millis: 0
</code></pre>
<p>通过以上几步,网关就可以进行https了,你就可以让它放心的代替nginx了。</p>
<p>你觉得使用SpringCloudGateway进行https访问怎么样啊?感兴趣的可以关注我的公众号(xtech100)。</p>
Spring下Redis的响应式编程
https://segmentfault.com/a/1190000017123885
2018-11-23T21:42:27+08:00
2018-11-23T21:42:27+08:00
开物笔记
https://segmentfault.com/u/k5rj
0
<p><img src="/img/bVbj0RH?w=588&h=273" alt="使用Spring进行Redis响应式编程" title="使用Spring进行Redis响应式编程"><br>Spring5支持响应式编程方式,能大大提高系统的吞吐量,但是要想实现整个系统的响应式编程需要每个环节都要实现,今天就来讲讲Spring是如何实现Redis的响应编程的。</p>
<p>Spring Data Redis从连接这个层次来做初始的响应式编程支持的,现在只支持Lettuce,因为它是唯一个支持响应式数据操作的Redis驱动。因为Redis一般用在非常低的抽象级别的,所以在现在这个版本上响应式编程的抽象是都是比较低级的接口的抽象。LettuceConnectionFactory允许依次访问ReactiveRedisConnection连接,并依次提供Redis的响应式的命令。</p>
<p>以响应式编程的风格,使用操作方法的函数链来创建链条去操作Redis中的数据,而且,所有I / O都是异步的。</p>
<p>ReactiveKeyCommands keyCommands =connection.keyCommands(); keyCommands.randomKey()</p>
<p>.flatMap(keyCommands::type) </p>
<p>.flatMap(System.out::println) </p>
<p>.subscribe();</p>
<p>以上代码获取随机密钥并打印了它的数据类型。不存在的随机密钥会输出为空Mono。</p>
<p>响应式Redis命令有两种形式:接受普通参数和接受命令发布者。命令发布者发出特定的Redis命令以将数据直接传输到Redis。执行命令后,每个发出的命令都会发出命令响应。</p>
<p>public interface ReactiveStringCommands {</p>
<pre><code> Mono<Boolean> set(ByteBuffer key, ByteBuffer value); Flux<BooleanResponse<SetCommand>> set(Publisher<SetCommand> commands);
</code></pre>
<p>}</p>
<p>传统的Spring Data Redis byte[]在其阻塞API上用于交换数据的。如果数据是在一个buffer(如ByteBuffer或Netty中的ByteBuf)中,那么这个byte[]数组会在buffer中进行强制数据复制。响应式支持很有很多是关于有效的资源使用,所以spring决定开放出了接收ByteBuffer参数的方法。</p>
<p>———— / END / ————</p>
ArtemisMQ的“未消费之谜”
https://segmentfault.com/a/1190000017106429
2018-11-22T18:24:17+08:00
2018-11-22T18:24:17+08:00
开物笔记
https://segmentfault.com/u/k5rj
1
<p><img src="/img/bVbjWjt?w=470&h=200" alt="图片描述" title="图片描述"><br>2018年6月份,我们开发了两个使用Artemis做消息队列实现的积分模块和PUSH推送模块,在几轮测试以后,大家信心满满的正式上线了,而且经过了一个多月使用,一切都很顺利,感觉生活一切都美美的。</p>
<h2>问题来了</h2>
<p>2018年8月份,突然有一天前面传来噩耗,用户注册后没收到积分,这真是迎头一棒啊。但是,我不能因为一次打击就失去对Artemis的信任,于是对整个模块进行了代码分析,结果发现代码没问题,妥妥的!</p>
<h2>分析问题</h2>
<p>查看Artemis控制台,发现有很多未消费的消息,之前一个多月都没有问题,都未出现过未消费的消息,就中间做过一次升级上线。</p>
<p>通过仔细慎重的分析所有的证据,我断定这是一次重启引发的“血案”!</p>
<p>如果在某一个Artemis节点上有很多未消费的消息,而且还在增多,那么只有一个可能,这个节点上没有consumer连接,而且这个节点上的消息不能redistribute到其他节点上,既然这样问题就很清楚了。</p>
<p>这个节点上没有Consumer连接为什么producer还一直发送消息呢?</p>
<p>正常情况下有Consumer才会把消息发送到该节点上的。这在测试环境上是不存在的,而且没有consumer有消息过来正常情况也应该redistribute到其他节点的,所以</p>
<p>我推测是Artemis的集群出了问题了,而且查看Artemis生产环境下链接到61616端口的链接TIME_WAIT的较多。</p>
<p>于是我做了以下两种调整:</p>
<h2>修改linux网络配置</h2>
<p>修改linux的网络配置,减少TIME_WAIT连接,减少断开的识别时间。具体操作步骤如下:</p>
<p>打开文件 /etc/sysctl.conf,编辑文件,加入以下内容:</p>
<p>net.ipv4.tcp_syncookies = 1</p>
<p>net.ipv4.tcp_tw_reuse = 1</p>
<p>net.ipv4.tcp_tw_recycle = 1</p>
<p>net.ipv4.tcp_fin_timeout = 30</p>
<p>然后执行 /sbin/sysctl -p 让参数生效。</p>
<p>修改Artemis集群方式</p>
<p>我把Artemis的集群由UDP改为了static集群方式。<br><img src="/img/bVbjWj3?w=1352&h=842" alt="static集群方式" title="static集群方式"><br>通过以上修改保证了客户端连接能够快速的断开,在应用重启时不会持续往这边发送消息,我使用jmeter进行压测,重启消费者过程中,消息redistribute都正常。</p>
<h2>SpringJms的坑</h2>
<p>这就完美了吗?NO!又发现新问题了。</p>
<p>在50个线程压测时进行重启应用,虽然重启后消息消费和redistribute正常,但是在重启的那一瞬间,在使用ON_DEMAND模式下节点上消费者断开的一瞬间服务器判断有一部分延迟,还是有一部分的消息发送到了没有consumer的节点上,这些消费者不能再被redistribute,这可能是Artemis的一个bug。</p>
<p>怎么办呢?为什么应用只能连接到一个节点上呢?这也不能说是spring-jms的一个坑,还是对spring-jms不够数量,spring-jms在创建消费监听的时候,无论有多少个Session,都只会创建一个共享连接,无论你有多少个Artemis节点,一个应用就永远只会连到一个节点,这真是大大的浪费呀。这个真是SpringJms的坑。</p>
<h2>自己动手,丰衣足食</h2>
<p>难道Artemis真的就这么差吗?实际上我看了Artemis自带的客户端以后,发现其实它在创建连接时自带三种策略,</p>
<p>一种是轮询,这种适合性能要求比较高的场景,提高消费效率的。</p>
<p>一种是随机,随便选一个节点连上就可以了,不知道为什么有这种策略。</p>
<p>一种是只取第一个节点,这种适合做双机热备的场景。</p>
<p>因此这个SpringJms带的坑,还得自己填,使用自带client进行创建消费者监听,这样的情况下,只要最大连接数超过2个以上,通过轮询的方式创建连接,就会平均创建到多个节点上,即使重启过程中有几个消息不能redistribute重启以后有消费者连接上来就能继续消费。<br><img src="/img/bVbjWjz?w=1590&h=1402" alt="图片描述" title="图片描述"></p>
<p>好吧,大功告成,生活终于又美好了。</p>
项目中如何使用Maven进行版本管理
https://segmentfault.com/a/1190000017095151
2018-11-21T22:20:17+08:00
2018-11-21T22:20:17+08:00
开物笔记
https://segmentfault.com/u/k5rj
0
<p>Maven 是一个项目管理工具,每个项目deploy到repository中以后,都有一个坐标,坐标中就包含一个版本,那个版本将是我们进行版本控制管理的一个重点。</p>
<p>一个产品实际可能包含10几个模块,模块间会有依赖关系,所以在项目管理中经常听到这种抱怨:</p>
<p>“测试环境又发不了券了?” </p>
<p>“是谁又把我的jar包覆盖了?” </p>
<p>“这个功能测试环境都测试好了,到了预生产怎么又不行了?” </p>
<p>“jar包又被哪个傻X覆盖了?”</p>
<p>这些基本上都是因为实际研发过程中版本管理混乱或者几乎没有版本管理造成的,有的是直接就一个版本用到底,所有环境都用一个版本,经常是还没上线的代码deploy到repository中,导致线上其他模块发布出现了问题。</p>
<p>大家可以想象一下,一个项目中可能有10几个模块,每个模块都有依赖的模块,无论是哪套环境,任何一次deploy都可能影响到其他的环境,这是多么恐怖的事情。所以,“稳定大于一切”,“应该给研发人员和测试人员应有的安全感!”</p>
<p>如何在maven中进行这个版本的控制呢?如何解决这些问题呢?</p>
<h2>1.版本统一进行定义管理</h2>
<p>定义一个统一的父pom项目,该项目只进行版本的管理,其他所有的模块的版本都在父pom中配置管理,一个模块依赖其他模块时统一继承父pom中的版本。</p>
<p>定义好每个迭代周期的版本后,统一修改父pom的版本,父pom中定义好各个环境的profile,不同的环境使用不同的profile。</p>
<p>每定义一个版本时,需要同时定义各个模块的版本号,定义好以后,更新父pom的版本号,需要使用模块新特性的就更新父pom的版本,这样就保证版本能够统一进行管理。如下图父pom项目中1.0.3版本的对应的user是1.0.2版本,order是1.0.1版本,pay是1.0.3版本。如果下一次要进行新功能开发,user修改版本为1.1.0,其他模块接口不变,那么父pom就需要进行版本升级为1.1.0,pay依赖user模块1.1.0版本就需要更新父pom的版本号。</p>
<p>父pom项目统一由管理员进行控制管理,每次版本修改都由管理进行发布管理。</p>
<h2>2.支持多套环境的版本切换</h2>
<p>maven仓库支持snapshot版本和release版本,研发环境和测试环境可以使用snapshot版本进行发布,研发环境和测试环境属于高频deploy执行的,所以保证不更新版本号每次deploy后maven打包时会更新依赖,正式环境使用relase版本,如果需要升级就更新版本号。</p>
<h2>3.支持多版本同时存在</h2>
<p>每个项目可以发布多个版本,不同的版本可以被不同的模块依赖,能够各取所需,比如,user模块有1.0.1-release版本和1.0.3-release版本,这两个版本可以被不同的模块引用,只有需要1.0.3-release新特性的模块才对应更新父pom的版本。</p>
<p>以上就是我在项目管理中实际使用maven进行版本控制的经验。<br>欢迎大家关注我的微信公众号(xtech100),一切讨论切磋!<br><a href="https://link.segmentfault.com/?enc=o8VaQA8SQOvYcwADIVfCLg%3D%3D.4HhKeOfsFSnAHyvEVv6rUqLHqC8tzqH5cqrYl9LhmVX%2BFTfFsQ8tYunSC2Ko%2BrOf" rel="nofollow">这是我在百家号的原文地址</a></p>
要提高团队代码质量,就要这么用Git进行版本控制!
https://segmentfault.com/a/1190000017095085
2018-11-21T22:15:58+08:00
2018-11-21T22:15:58+08:00
开物笔记
https://segmentfault.com/u/k5rj
3
<p>大家都比较清楚,互联网产品要能够快速响应市场变化,要面对频繁的需求变更,要用廉价的成本快速试错,这样才能不断的完善和优化产品。 </p>
<p>Git是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。非常适合做互联网产品的代码版本管理。</p>
<p>一个团队如何如何使用git进行版本管理,如何使用git进行多人的代码写作?如何解决产品开发过程中的提出来的版本控制的问题?就是我要表达的意思。</p>
<p>团队如何进行版本管理呢?</p>
<h2>第一,GitServer的选型和安装</h2>
<p>我选用了GitLab作为GitServer。GitLab是一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。它拥有与Github类似的功能,能够浏览源代码,管理缺陷和注释等,还有一个功能,它能够实现分支的在线合并申请,分支可以进行保护等权限的控制。</p>
<h2>第二,版本号定义规范</h2>
<p>约定版本号规范,每个模块的版本号约定为三位<main>.<feature>.<hotfix>,根据大的基线设定<main>主要版本号,根据当前版本设定<feature>次版本号,<hotfix>默认为0,当有bug修改后才更新这个版本号。每次产品人员定义好产品功能后,每次变更<feature>版本号即可。</p>
<h2>第三,创建固定分支</h2>
<p>每创建一个项目,分别创建dev、test、release、master四个固定分支。</p>
<p>dev分支用来研发人员进行自测和模块间联调使用的,用来部署到研发环境的,开发人员对该分支有pull和push权限。</p>
<p>test分支是测试人员进行测试的代码分支,是部署到测试环境的代码分支,研发人员联调完自测完成后,提交feature分支合并申请到test分支,由管理员负责代码review并进行代码合并,该分支是受保护分支,开发人员对该分支有pull权限。</p>
<p>release分支是在test分支测试完成后,由研发人员提交test分支合并申请到release分支,release代码分支是用来部署到预生产环境的,由管理员进行代码合并。</p>
<p>master分支是最终上线的代码分支,测试人员在预生产环境测试通过后,由研发人员提交release代码分支合并申请到master分支,master分支是要部署到生产环境的,master上线完成后打对应版本的tag标签。</p>
<h2>第四,临时分支</h2>
<p>feature分支,每次定义产品一个完整的基线版本就生成一个feature_{版本号}分支,上线完成后删除该分支,所有的人创建一个属于自己的分支,每个人自测完成后,发起自己分支合并到feature分支,然后将feature分支合并到dev分支。</p>
<p>hotfix分支,每次bug修复创建一个hotfix_{版本号}分支,生产环境出现bug后,需要马上修改时,确定好版本号,从继承master分支创建hotfix分支。</p>
<h2>第五,正常上线流程</h2>
<p>1)管理员创建固定的分支dev、test、release和master版本,根据产品人员确定的功能确定当前版本的版本号,并继承master分支创建feature分支。</p>
<p>2)每个研发人员拉取feature分支,并创建个人本地分支。</p>
<p>3)研发人员进行编码,自测完成后合并本地分支合并到feature分支,并将feature分支合并到dev分支部署到研发环境进行模块间联调。</p>
<p>4)研发人员联调通过以后,向管理员发起feature分支合并到test分支的申请,由管理员review代码后完成合并,并部署到测试环境。</p>
<p>5)测试人员在测试环境进行测试,发现bug并登记,由研发人员进行修改,重新从第3步开始重复执行。</p>
<p>6)测试人员测试通过以后,由研发人员发起从test分支向release分支合并申请,由管理员完成合并,并部署到预生产环境。</p>
<p>7)测试人员测试预生产环境测试通过后,由研发人员发起从release分支向master分支的合并申请,有管理员完成合并,并在master分支上打版本标签,并部署到生产环境。</p>
<p>8)测试人员验证生产环境通过后,上线完成,如果生产环境验证不通过,马上回滚到master上一次的版本代码。</p>
<h2>第六,线上从来不缺bug,如何处理线上紧急Bug呢?</h2>
<p>1)研发人员从继承master分支创建一个hotfix分支。</p>
<p>2)研发人员检出hotfix分支,自测通过后提交并申请分支合并到release分支,由管理审核通过后完成合并,并部署到预生产环境。</p>
<p>3)由测试人员对预生产环境进行测试,测试通过后,由研发人员发起从release分支合并到master分支的申请,由管理员审核通过后完成合并,并在master分支上打版本标签,并部署到生产环境。</p>
<p>4)测试人员验证生产环境通过后,上线完成,如果生产环境验证不通过,马上回滚到master上一次的版本代码。</p>
<p>以上就是我使用GitLab进行版本管理的实际使用过程,大家有什么想法可以关注我的公众号(xtech100)一起讨论。<br><a href="https://link.segmentfault.com/?enc=E8qeC2WvScYw42y6PceJgw%3D%3D.24zlXaq01y3OqvamwryCiFkuo1gnyMrmigkO5kme9aP9U9M2VGiXnWcJsZ%2BhVnNo" rel="nofollow">我在百家号上文章地址</a></p>
10分钟生成一张永久免费的SSL证书
https://segmentfault.com/a/1190000017095044
2018-11-21T22:11:02+08:00
2018-11-21T22:11:02+08:00
开物笔记
https://segmentfault.com/u/k5rj
6
<p>为了安全起见,现在开发微信服务号和IOS客户端等访问服务器端都要求使用https加密传输。</p>
<p>SSL证书是数字证书的一种,类似于驾驶证、护照和营业执照的电子副本。因为配置在服务器上,也称为SSL服务器证书。</p>
<p>Let’s Encrypt 也是一个 CA 机构,但这个 CA 机构是免费的!!!也就是说签发证书不需要任何费用。</p>
<p>现在讲解一下,如何在centos操作系统下,获得Lets Encrypt免费的ssl证书,并在nginx里配置使用。</p>
<ol><li>安装Certbot客户端</li></ol>
<hr>
<p>Certbot是一个EPEL安装包,如果没有配置EPEL库,需要提前将库配置好。</p>
<p>运行以下命令安装Certbot:</p>
<pre><code>$ sudo yum install certbot-nginx
</code></pre>
<h2>2.使用Certbot生成证书</h2>
<pre><code>$ sudo certbot --authenticator standalone --installer nginx --pre-hook "nginx -s stop" --post-hook "nginx"
</code></pre>
<p>生成过程中需要输入域名,域名要提前进行解析。</p>
<h2>3.修改nginx配置</h2>
<p>certbot会在/etc/letsencrypt/live/目录下生成一个域名的目录,然后修改对应的nginx的配置。</p>
<pre><code>ssl_certificate "/etc/letsencrypt/live/{域名}/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/{域名}/privkey.pem";
</code></pre>
<h2>4.如何让证书永久免费呢?</h2>
<p>Let's Encrypt证书有效期为90天,为了保证在过期前更新证书,Certbot提供了更新证书有效期的功能。使用以下功能可以进行更新:</p>
<pre><code>$ sudo certbot renew --dry-run
</code></pre>
<p>也可以使用crontab自动更新证书有效期。</p>
<pre><code>0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew
</code></pre>
<p>欢迎大家关注我的公众号(xtech100)一起切磋交流!<br><a href="https://link.segmentfault.com/?enc=%2FFpauD%2Bc6Qnp%2Fe4EEEEO%2Fg%3D%3D.G6424gWXaMijyz0aGS52XM8E1tiJZD9rJC4NVHYtoMeVy0SAtkTPIcBsnFyo6uQR" rel="nofollow">我的百家号地址链接</a></p>
如何使用SpringCloud进行灰度发布
https://segmentfault.com/a/1190000017072055
2018-11-20T11:12:48+08:00
2018-11-20T11:12:48+08:00
开物笔记
https://segmentfault.com/u/k5rj
2
<p>灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。</p>
<p>在开发或者测试的时候,或者线上发布,线上服务多版本控制的时候,需要对服务提供权重路由,最常见的使用就是,一个服务有两个版本,旧版本V1,新版本v2。在线上灰度的时候,需要通过网关动态实时推送,路由权重信息。比如95%的流量走服务v1版本,5%的流量走服务v2版本。</p>
<p><img src="/img/bVbjNnR" alt="图片描述" title="图片描述"></p>
<p>如何使用SpringCloud进行灰度发布呢?将分一下四步:</p>
<h2>第一,设置网关权重路由设置</h2>
<p>Spring Cloud Gateway中提供了org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory去实现根据分组设置权重进行路由,因此使用起来相对比较简单,有兴趣的可以debug阅读源码。</p>
<p>我们通过在Spring Cloud Gateway中会配置不同的权重信息到不同URL上,Spring Cloud Gateway会根据我们配置的路由权重信息,将请求分发到不同的源服务组,权重信息如ch4/ch4-gateway中的application.yml所示,主要配置信息如下。</p>
<p>spring: </p>
<p>cloud:</p>
<pre><code>gateway:
discovery:
locator:
enabled: true
routes:
- id: order-service1
uri: lb://order/order/create1
order: 8000
predicates:
- Path=/order/create
filters:
- StripPrefix=1
- Weight=order-service, 95
- id: order-service2
uri: lb://order/create2
order: 8000
predicates:
- Path=/order/create
filters:
- StripPrefix=1
- Weight=order-service, 5
</code></pre>
<p>Weight=order-service1, 95,Weight=service1, 5就是路由的权重信息。</p>
<h2>第二、源服务配置</h2>
<p>源服务在本案例中为order模块源服务,主要提提供Gateway Server权重路由对应的后端源服务。因为比较简单因此不做详细说明,主要代码如下所示。</p>
<p>import org.springframework.web.bind.annotation.RequestMapping;</p>
<p>import org.springframework.web.bind.annotation.RestController;</p>
<p>@RestController</p>
<p>public class OrderController {</p>
<p>@RequestMapping("/create1")</p>
<p>public String orderCreateV1() {</p>
<pre><code> //此处写业务逻辑代码
return "success";
</code></pre>
<p>}</p>
<p>@RequestMapping( "/ceate2")</p>
<p>public String v2() {</p>
<pre><code> //此处写业务逻辑代码
return “success”;
</code></pre>
<p>}</p>
<p>}</p>
<h2>第三、进行测试</h2>
<p>分别启动gateway,order进行访问:<a href="https://link.segmentfault.com/?enc=MMgnykgMNA3DPbWRcH5ilw%3D%3D.nRDSoU1D%2Bokj2tOP6dDWIF2Owz6fV9x4aIsmJIGtEww%3D" rel="nofollow">http://localhost</a>:5001/order/create 测试,发现会根据所设权重进行路由。</p>
<h2>第四,使用zookeeper config实现动态权重路由</h2>
<p>到第三步完成以后,现在可以通过修改配置的如何根据现有的服务在线动态更新权重呢?使用zookeeper作为spring cloud的注册和配置中心,gateway模块和order模块配置监控配置的变化,如果想做动态灰度发布,增加后台在线配置管理界面,并在线修改gateway的配置,实现动态的灰度发布,而不用每次修改都要重新启动gateway。</p>
<p>spring: </p>
<p>cloud:</p>
<pre><code>zookeeper:
enabled: true
connect-string: localhost:2181
config:
root: /config
enabled: true
watcher:
enabled: true
</code></pre>
<h2>第五,使用zkui进行配置修改</h2>
<p>使用ZKUI来可视化管理Zookeeper,登录ZKUI->import 选择对应文件导入即可。ZKUI安装使用请自行百度。</p>
<p>以上就是我使用spring cloud进行灰度发布的过程记录。</p>