文章内容均为转载,本人记录学习使用。前端
https://blog.csdn.net/z69183787/article/details/54601777 (原贴地址,感谢做者 OkidoGreen。)java
首先讲一下开关的由来,例如东京在6月18日作店庆促销活动,在交易下单环节,可能须要调用A、B、C三个接口来完成,可是其实A和B是必须的,C只是附加的功能(例如在下单的时候作一下推荐),无关紧要,在平时系统没有压力,容量充足的状况下,调用下没问题,可是在相似店庆之类的大促环节,系统已经满负荷了,这时候其实彻底能够不去调用C接口,怎么实现这个呢?改代码?no,no,no,这样太不敏捷,此时开关诞生了,开发人员只要简单执行一下命令或者点一下页面,就能够关掉对于C接口的调用,在大促过去以后,再把开关恢复回去便可。linux
什么是服务降级
服务降级,当服务器压力剧增的状况下,根据当前业务状况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
服务降级方式:
服务接口拒绝服务:无用户特定信息,页面能访问,可是添加删除提示服务器繁忙。页面内容也可在Varnish或CDN内获取。
页面拒绝服务:页面提示因为服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。
延迟持久化:页面访问照常,可是涉及记录变动,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。
随机拒绝服务:服务接口随机拒绝服务,让用户重试,目前较少有人采用。由于用户体验不佳。
服务降级埋点的地方:
消息中间件:全部API调用可使用消息中间件进行控制
前端页面:指定网址不可访问(NGINX+LUA)
底层数据驱动:拒绝全部增删改动做,只容许查询nginx
问题一:在单个java系统中如何实现开关功能?数据库
其实对于开关来讲,对应Java中的类型,很好映射,就是一个boolean值,在须要作开关操做的地方,调用这个属性,判断状态,而后走相应的逻辑便可。这个类是一个单例,保证全局惟一(代码就不写了,单例模式通常是学习设计模式中最开始接触的呵呵)。apache
问题二:单个java系统中,如何实现开关值变动的操做呢?设计模式
在单机系统中,改变开关的状态很简单(留一个口子,外部能够改变属性的值,例如改成true或者false),这时候,能够是页面来维护开关,经过页面的点击类改变这个全局惟一的属性,从而实现开关动做的触发。缓存
问题三:多个同构java系统,如何实现开关状态的同步呢?服务器
经过一和二的介绍,在单机状况下,开关的变动能够了,可是在多个同构(这里的同构,值得是部署的同一套代码,逻辑彻底相同,相似Master和Slaver的模式)系统中,如何保持一致呢?单例模式,开关属性是被加载到本地缓存,就是说java一直持有的对象,在FullGC的时候回收不走的那种。这个时候,若是要保持各个系统中开关属性状态的一致,就须要从第三方外部系统中加载这个数据。curl
什么系统能充当第三方外部系统呢?能够是一个数据库访问系统,咱们暂且称之为MetaServer,开关的属性防止在DB中,而后MetaServer提供页面来修改数据,同时提供接口读取开关的数据,在应用启动的时候,经过MetaServer来读取数据,加载到本地缓存中。这时候就有个问题,就是我经过MetaServer的页面改变了值,各个应用如何知道我改变了属性呢?这个时候就须要经过一些办法(办法不少,能够是消息系统,能够是zookeeper,能够是页面触发)来清理一下开关属性的缓存,让缓存从新加载一下,从而实现最新的状态获取。
整体思路就是:metaServer维护开关数据--应用读取DB中的数据到本地缓存--DB中数据变动--触发开关属性缓存从新加载。
这个是否是有点复杂,有没有更加简单的办法?固然有了,以前淘宝开源了一个系统diamond(持久化配置管理系统,http://code.taobao.org/p/diamond/wiki/index/),其实能够理解为“配置信息的伪推送服务”,例如我变动了一个开关的属性,再也不须要作清理缓存的事情,diamond帮你作掉了(原理很简单,例如系统A订阅了在diamond中的开关信息,这时候A会启动一个线程,每隔一段时间来轮循diamond的服务端,看看开关属性的数据有没有变动,若是有变动,在diamond服务端来加载最新的数据)。
整体思路是:在diamond中维护配置信息--系统订阅开关属性--系统轮循配置是否有变动,有变动直接就变掉了。
问题四:开关设计的几个坑
有时候,咱们为了方便,没有借助问题三种的MetaServer或者diamond的方式,就是留了一个HTTP的接口来触发修改开关(多台机器的话,能够写批量脚本),这时候其实须要咱们在apache或者nginx中,把这个URL的访问禁止掉,防止恶意用户在外部拼凑连接来进行开关的变更,这时候只能在服务器上经过linux的curl来触发操做了。
还有一个,就是若是经过HTTP的形式来修改开关的属性,有个是须要注意的,就是开关的执行要幂等操做,这样方便操做,避免出现集群中数据不一致的状态(就是执行开,开关就是开,不能第一次执行是开,第二次执行是关)。
问题五:开关组合状况下怎么搞?
上面的几种状况,仅仅是执行单个开关,应该比较简单。可是我同时又A、B、C三个开关,在不一样的业务场景下,可能须要关闭A和B开关,在另一个场景下,可能须要关闭A和C开关,这时候认为操做有可能会有遗漏或者疏忽,怎么搞呢?在单独属性开关的基础上作封装,例如A和B上面增长一层属性,暂且叫“AB”,修改AB的值,对应的系统修改A和B的值,这样就避免人肉记住一些组合。
问题六:如何实现自动升降级?
上面的状况,都是在提起能够预知的状况下,咱们作一些人为的操做,这个能不能自动化?固然能够,就是这一小节讨论的自动升降级。
举例子,如今东京和做的外部物流公司有多家,会调用它们的系统或者物流节点的状态,这个时候,物流公司系统是不稳定的,若是挂了或者响应时间慢了,对于自身的系统会影响比较大,比较理想的办法是,在物流公司系统出现问题的时候,这块逻辑自动降级处理,而后等物流公司系统好了以后,再把这部分逻辑自动升级,整个过程没有人为参与,自动保持系统稳定性。这里说一下整体思路:
第一步:搞一个计数器,记录接口,暂定A的调用成功次数、失败次数以及响应时间;
第二步:将这些信息放入队列中,同时设置阀值(例如RT超过5秒就降级,1秒就升级)以及阀值触发改动的开关;
第三部:异步启动一个线程,扫描队列,达到咱们的条件,就触发作变动(有个问题,就是加入业务降级了,这时候就没有调用量,也就没有了自动升级的条件了,怎么搞呢?这时候业务降级,并非彻底100%的停掉,能够预留一部分流量继续调用A,把A调用的信息放入队列中,根据这些信息,就能实现升级了);
总结:
上面这些是在陆续的系统维护中尝试或者看到的处理办法,经过开关的方式,实现系统的升降级,从而更好的保护系统。这篇文章只是阐述了大致的思路,没有涉及到具体的代码,但愿可以达到抛砖引玉的做用。