精华内容
下载资源
问答
  • 扩展有状态服务

    2020-09-10 10:56:19
    一直以来,无状态服务(Stateless ...有没有想过引入“有状态服务”(stateful service)来解决问题?在网上查一查,你会发现很少有人提及有状态服务,Wikipedia甚至都没有这个词条。 Caitiff McCaffrey是Twitter

    一直以来,无状态服务(Stateless Service)在架构设计中都被当作铁律,因为无状态的服务很容易横向扩展,只需要在负载均衡之后增加节点就可以处理更多请求。但是,无状态服务也不是完美无缺的,其中一个缺点就是和数据层之间的请求延迟,以及为了解决这种延迟增加缓存所带来的复杂性和一致性问题。

    有没有想过引入“有状态服务”(stateful service)来解决问题?在网上查一查,你会发现很少有人提及有状态服务,Wikipedia甚至都没有这个词条。

    Caitiff McCaffrey是Twitter的资深工程师,她在StrangeLoop大会上发表了主题为“构建可扩展的有状态服务”的演讲,不但厘清了有状态服务的基本理念,也列举了微软、Facebook以及Uber等公司使用有状态服务的多个案例作为佐证。也许她的演讲可以为我们带来一些启示。

    在读下去之前,有一些基本的分布式系统概念是需要理解的,如果你对这些概念还不熟悉,不妨自行Google一下:

    • Sticky Session
    • Data Locality
    • CAP Theorem
    • AP/CP System
    • Cluster Membership
    • Consensus Protocol
    • Gossip protocols
    • Consistent Hashing
    • Back Pressure
    • DHT(Distributed Hashtable)
    • StopTheWorld(Garbage Collection)

    我们先来回顾一下众所周知的无状态服务:把所有数据放在数据库中,就可以在服务层横向扩展;但是当流量不断上升,总有一天会突破数据库处理能力的极限,于是我们对数据库做分片(sharding),或者转向NoSQL数据库,希望通过牺牲强一致性来提高可用性。这时,不可避免地,一些数据库的逻辑就要渗透到应用层中。

    另一方面,我们考虑一下无状态服务的过程:用户向服务层发送请求,服务层处理请求的节点A从数据库加载数据,返回给用户,当用户发下一个请求的时候,可能会分配到另一个服务节点B,节点B需要去数据库重新加载数据,此时A节点的数据被抛弃。这个反复加载数据的过程其实很浪费,尤其对于频繁交互的应用(比如游戏,和一些重交互的网络应用)来说尤其如此。

    Stateful的好处

    首先要强调的是,Stateful并不是灵丹妙药,大多数场景下,我们仍然需要无状态的服务以及横向扩展能力。但有状态服务的确有两个明显的优点:

    1. Data Locality(数据本地化)。在有状态服务中,当客户端第一次发出请求时,服务仍然需要到数据库加载数据,但是自此以后,数据将会保存在节点本地,这个客户端下一个请求必须由同一个节点处理,这种模式就节省了很多数据网络传输。即使数据库服务已经停止,服务节点仍然能够独立处理请求。
    2. 有状态服务有更高的可用性(A)和更强的一致性(C)。在CAP三个属性中,我们只能选择CP系统或者AP系统,如果我们选择AP,就要在一致性上作出让步。但是这个让步本身也有程度上的差别,对于有状态的服务来说,由于采用了Sticky Session(即一个用户的数据都在一个固定节点上),一致性会更强。见下图(对于其中一致性的各种级别请自行查阅):

    除此以外,Sticky Session还有一个工程上的好处,对于开发分布式系统的工程师来说,因为不需要担心数据加载到不同节点、缓存层次带来的一致性问题,一个用户只和一台服务器交互,这个模型更加容易理解,更容易编程实现。

    怎样实现?

    如何实现Sticky Session?

    第一种显而易见的方法就是长连接(HTTP或者TCP),这个方式简单易行,但是缺点在于不稳定,一旦连接断开,Sticky Session也就结束了。另外,由于各个用户产生的负载并不相同,有一些节点会承受过高负载(热节点),因此,必须实现反向压力(Back Pressure),当一个节点无法承受负载时,可以有选择性地断开一些连接,让一些用户连接到其他轻负载节点上。

    另一种更好的办法就是实现集群内部路由。客户端仍然可以连接任何一个节点,由这个节点负责把请求路由到含有用户数据的节点上。为了实现这种方案,需要集群具有两种能力:

    • 集群归属(Cluster Membership) - 集群中有哪些节点?生存状态如何?
    • 负载分配(Workload Distribution)

    集群归属

    Membership可以有几种类型:静态、Gossip以及一致性(Consensus)系统。

    1. 静态系统最为简单,可以用一个配置文件来维护各个节点的地址,然后把这个文件部署在集群中每个节点上。缺点也显而易见:运维困难,难于扩展。当节点宕机或者增加节点的时候,都要更新配置,重启整个集群。对于需要保证高可用的服务来说,这是不可接受的。动态的集群归属能增加运维的灵活性。
    2. Gossip协议就是让各个节点之间互相散播消息来维护集群状态,每个节点都有自己独立的World State,并根据接受的消息更新。在达到稳定状态以后,所有的节点会拥有一致的World State。但是当出现网络中断、增加、减少节点的时候,节点本地的World State就会出错。这涉及一个设计上的取舍:因为不需要一致性协议的协调过程,这个方法的可用性更高,但是代价就是代码需要处理路由到错误节点带来的不确定性。
    3. 一致性协议。当前有很多开源的一致性协议实现,比如ZooKeeper/Etcd等。一致性系统能保证集群归属信息的准确和及时更新,但是问题在于协调的效率较低。所以除非绝对必要,不建议采用这种方案。

    负载分配

    把工作分配给集群中的节点大概有三种方式:

    1. 随机分配给任意节点。用这种方式,写请求可以由任意节点处理,但是读请求需要向每一个节点发出查询。严格来说,这不是Sticky Session,但是实际中往往很有效。

    2. 一致性Hash。可以通过SessionID或者UserID的Hash值来决定处理节点。一致性Hash会把请求ID映射到一个圆上,在圆上顺时针移动,遇到第一个节点就是处理请求的节点(具体实现请查阅Wikipedia)。这种分配方式是确定性的,带来的问题就是热节点:很多请求可能被分配到一个节点上,导致节点过载,由于确定性的分配方式,不能把负载转移到其他节点。所以,每台节点的计算资源都要留足余量(headroom),以降低过载的可能性。

    3. 分布式Hashtable。用一个分布式的Hashtable来维护、查询请求分配的节点,用这种方式,当一个节点宕机或者过载的时候,可以更改Hashtable把负载重新分配到其他节点。

    真实案例

    Facebook Scuba

    Scuba是Facebook实现的一个分布式内存数据库。它使用了静态的集群归属,负载分配使用了随机的分配策略,读请求需要查询集群中的每个节点。因为在真实环境中,不可能保证所有节点都同时在线,所以用户的读请求不一定能返回所有数据,他们的做法是返回查询到的数据,以及数据的完整程度(百分比),由客户端决定这些数据是否足够。

    Uber Ringpop

    Ringpop是一个基于node的应用层分片协议。仔细考虑一下Uber的服务,你发现它很适合根据地理位置分片处理,把一个位置用户的请求发送到一个固定的节点上,并在这个节点上维护行程信息。Ringpop使用Gossip协议维护集群归属,并使用一致性Hash来分配负载。为了避免热节点问题,必须保证每个节点有足够处理能力。

    Microsoft Orleans

     

    Orleans是一个基于Actor的分布式系统运行时,Halo 4这个游戏就是基于Orleans开发的。Orleans采用Gossip协议维护归属信息,负载分配采用了一致性Hash和DHT相结合的方式。用户的ID经过一致性Hash映射到一个节点,这个节点保存了这个用户对应的DHT,再查询DHT定位到处理用户请求的Actor位置。对这个项目感兴趣的,尤其是做游戏开发的同学可以关注一下这个开源项目,可以参见文末参考链接。

    需要注意的问题

    不受限的数据结构

    注意不要使用不受限的队列和内存数据结构,有状态的服务更容易出现OutOfMemory的问题,或者垃圾回收器会StopTheWorld。在无状态的服务中,很多数据结构都是伴随请求的生命周期产生和消失的,内存在请求结束以后就会被垃圾回收,所以即使段时间内存不足,也不会有很大影响。但是在有状态服务中,很多session的时间很长,会累积大量数据,代码必须对这种可能性加以防范。

    内存管理

    同上一条,因为很多数据会长时间驻留内存,会给垃圾回收机制带来很多影响:对于垃圾回收器来说,回收很老的一代(Generation)内存代价是比较大的。你需要对垃圾回收器做性能的调优,或者干脆使用不需要GC的技术(比如C++)。

    重新载入数据

    大部分时候,有状态服务是不需要重载数据的。有三个例外:

    1. 第一次连接。这时候往往加载数据很耗时,所有数据要从数据库读出,所以为了用户体验,第一个请求不要加载太多数据;另外,即使第一个请求加载数据超时,也不要停止加载,因为客户端总会再次连接,而第二次连接的响应速度会很快,因为数据已经读入本地内存。
    2. 崩溃恢复,这时候可能需要预加载一些数据。
    3. 部署新代码,每次部署新代码需要重启服务并加载所有数据。对于使用确定性负载分配的方案,这会是一个问题。 这里值得一提的是Facebook部署新代码时采取的一个聪明的技巧。Facebook的服务重启以后加载数据,由于数据量巨大,可能需要几个小时的时间,这对于快速上线新代码非常不利。他们采用了一个方法,分离了内存和进程的生命周期:在部署新代码的时候,原进程停止服务,开始把数据转移到共享内存中,然后退出,在启动新进程以后,可以直接拷贝共享内存到程序中,在几分钟内快速开始提供服务。

    总结

    1. 关于集群归属和负载分配的策略,并没有一个“最好”的方案,需要根据需求做出权衡。
    2. 尽管Stateful是一个比较新的概念,没有必要害怕尝试,很多知名企业都有成功经验。
    3. 多读论文。其实关于Stateful的很多理念并不是最近发明的,这个演讲中很多方案都来自于60/70年代的论文,都是已经解决的问题,不要重复发明轮子。

    参考

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

    CaitieM20/Talks

    Making the Case for Building Scalable Stateful Services in the Modern Era - High Scalability -

    The Case for Building Scalable Stateful Services

    https://research.facebook.com/publications/456106467831449/scuba-diving-into-data-at-facebook/

    uber/ringpop-node

    Orleans - Virtual Actors - Microsoft Research

    dotnet/orleans

    Creating scalable backends for games using open source Orleans framework

    展开全文
  • 这里有一篇关于有状态服务(PPT)的分享。这篇分享介绍了基于Azure的Orleans开发Halo4的经验。Orleans基于有状态分布式的Actor模型,节点之间实现了高可用的Gossip协议,一个两层的一致性哈希加上分布式哈希表。基于...
        

    原文链接:http://highscalability.com/blog/2015/10/12/making-the-case-for-building-scalable-stateful-services-in-t.html

    在很长一段时间内,分布式系统都采用无状态服务作为分布式系统扩展的最佳实践。它可以通过简单的循环负载均衡来提供扩展能力。它的缺点则在于数据中心之间复杂的一致性问题。
    这里有一篇关于有状态服务(PPT)的分享。这篇分享介绍了基于Azure的Orleans开发Halo4的经验。Orleans基于有状态分布式的Actor模型,节点之间实现了高可用的Gossip协议,一个两层的一致性哈希加上分布式哈希表。基于这些方案提供节点动态负载均衡,动态扩缩容,热点平衡转移等等。
    下面简单说一下如何使用有状态服务:

    无状态服务带来额外开销

    无状态服务可以通过添加节点来实现线性可扩展能力的提升。问题在于,我们开发的应用场景是有状态的,这意味着我们需要通过sharding或者NoSQL来实现扩容。这又丧失了关系型数据库关于强一致性的保证。
    对于单个用户,经常需要把这个用户的所有请求路由到同一台服务器上,这样可以通过本地数据缓存的措施来减少IO开销。

    集群信息:

    1. 静态配置 配置一份包含所有集群信息的静态文件分发到所有机器上,缺点在于不够灵活,线上发生故障需要手动调整配置,不支持动态扩容缩容。

    2. Gossip协议  稳定状态下,系统中所有节点对于节点的健康状态能够达成共识,在发生网络分区或者节点加入/离开时,可能会出现短暂的不一致情况。

    3. 共识算法 保证所有节点的一致性,缺点在于可能会是整个系统速度的瓶颈。

    路由策略:

    1. 随机路由 一般是根据机器的处理能力进行按处理能力的路由。

    2. 一致性哈希 问题在于可能会出现热点问题,需要精心选择哈希键。
      3.分布式哈希表(DHT) 动态哈希表保存服务状态。

    三个例子:
    Facebook的Scuba
    一个高速的基于内存的分布式数据库,使用了静态成员信息配置,写时采用随机分发策略,读取操作有中间件进行聚合返回,不保证可用。

    Uber的Ringpop
    Ringpop是一个实现了程序层级分片的node.js库,采用了Swim Gossip协议维护集群信息,这个协议基于AP所以不保证强一致性,一致性哈希负责处理请求路由,所以有可能出现热点问题。

    微软的Orleans
    Orleans是一个基于分布式系统的Actor模型,Actor是计算的核心单元,彼此之间使用异步消息通信,它持有一系列状态机所以本身是有状态的,Gossip协议负责集群信息,路由策略是一致性哈希与分布式哈希表的结合:当向集群发送对Actor的请求时,Orleans运行时将计算对Actor ID的一致哈希。哈希映射到具有该ID的分布式哈希表的计算机;Actor的分布式哈希表知道哪个机器包含指定ID的Actor。

    有状态模型带来的挑战:
    需要关注服务器内存状况,缓存建设问题,第一次链接问题等等。

    原文地址:https://www.jianshu.com/p/fdfca8f5d9ec

    .NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

    640?wx_fmt=jpeg

    展开全文
  • 用于Netlify的Nimbella插件是一个Netlify Build插件,它使用使用可移植的有状态无服务器功能来扩展Netlify站点。 使用该附件,开发人员可以将站点部署到Netlify的CDN上,或将Netlify用作其他云上托管的站点的代理。...
  • 几种提供解决方案的SaaS服务,例如 。 该项目旨在提供Azure应用服务中托管的类似服务,该服务使每个人都可以在几分钟内在Azure中创建状态页。 解决方案概述 如何设定 访问Azure门户( ) 基于现有的Azure服务计划...
  • 为了实现有状态表格,首选我们要提供状态保存服务,ExtJs已经提供了状态管理:Ext.state.Provider,为了让我们的代码更加优雅,我扩展了Provider。主要提供了布局配置的加载、清空、保存和获取服务。代码如下: Ext...

    1、状态保存服务

    为了实现有状态表格,首选我们要提供状态保存服务,ExtJs已经提供了状态管理:Ext.state.Provider,为了让我们的代码更加优雅,我扩展了Provider。主要提供了布局配置的加载、清空、保存和获取服务。代码如下:

    Ext.define('Pds.svc.UISettingsSvc', {
    	extend: 'Ext.state.Provider',
    	singleton: true,
    	alternateClassName: 'UISettingsSvc',
    
    	// 界面配置
    	settings: {
    		load: serviceUrl('settings/load'),
    		save: serviceUrl('settings/save'),
    		clear: serviceUrl('settings/clear')
    	},
    
    	/**
    	 * 加载配置,一般要界面首次加载时调用
    	 */
    	loadState: function() {
    		var me = this;
    		Cxt.Utils.ajax({
    			request: {
    				async: false,
    				url: me.settings.load
    			},
    			success: function(ret) {
    				if (!ret.data) {
    					ret.state = [];
    				} else {
    					for (var field in ret.data) {
    						ret.data[field] = me.decodeValue(ret.data[field]);
    					}
    					me.state = ret.data;
    				}
    			}
    		});
    	},
    
    	/**
    	 * 获取控件状态
    	 * 
    	 * @param {}
    	 *          name
    	 * @param {}
    	 *          defaultValue
    	 * @return {}
    	 */
    	get: function(name, defaultValue) {
    		// 防止状态被外部直接修改
    		return Ext.clone(typeof this.state[name] == "undefined" ? defaultValue : this.state[name]);
    	},
    
    	/**
    	 * 重置布局时,清理后台的状态信息
    	 * 
    	 * @param {}
    	 *          cmp
    	 * @param {}
    	 *          name
    	 * @param {}
    	 *          callback
    	 */
    	clearState: function(cmp, name, callback) {
    		var me = this;
    		if (!me.state[name]) {
    			if (callback)
    				callback();
    			return;
    		}
    		Cxt.Utils.ajax({
    			mask: {
    				target: cmp,
    				message: '清空配置...'
    			},
    			request: {
    				url: UISettingsSvc.settings.clear,
    				params: {
    					key: name
    				}
    			},
    			success: function(ret) {
    				delete me.state[name];
    				if (callback)
    					callback();
    			}
    		});
    	},
    
    	/**
    	 * 保存布局
    	 * 
    	 * @param {}
    	 *          cmp
    	 * @param {}
    	 *          name
    	 * @param {}
    	 *          value
    	 * @param {}
    	 *          callback
    	 */
    	saveState: function(cmp, name, value, callback) {
    		var me = this;
    		if (typeof value == "undefined" || value === null) {
    			me.clearState(name, callback);
    			return;
    		}
    		if (me.state[name] != value) {
    			Cxt.Utils.ajax({
    				mask: {
    					target: cmp,
    					message: '保存配置...'
    				},
    				request: {
    					url: UISettingsSvc.settings.save,
    					params: {
    						key: name,
    						value: me.encodeValue(value)
    					}
    				},
    				success: function(ret) {
    					me.state[name] = value;
    					if (callback)
    						callback();
    				}
    			});
    		}
    	}
    });
    
    2、布局配置预加载

    在整个页面加载时,先调用UISettingsSvc.loadState();进行布局配置加载,把服务端当前用户的布局配置信息存储到本地。这样后续各界面读取布局配置时,就不用再调用后台服务。

    3、控件布局加载

    在界面表格控件初始化时,我们取出表格的布局信息,主要包括:显示/隐藏列、每个列的排序。并把布局应用到当前表格,同时为表格提供重置布局和保存布局的功能:

    /**
     * 支持保存布局
     */
    Ext.define('Pds.cmp.grid.StatefulGrid', {
    	extend: 'Ext.grid.Panel',
    	alias: 'widget.pds.statefulgrid',
    	requires: [//
    	'Pds.svc.UISettingsSvc'//
    	],
    
    	initComponent: function() {
    		var me = this;
    		if (Ext.isArray(me.columns)) {
    			// 默认列标题居中显示并自动换行
    			for (var i in me.columns) {
    				me.columns[i].cls = 'pds-header-center';
    			}
    		}
    		me.defaultSorters = Ext.encode(me.store.sorters);
    		me.defaultFilters = Ext.encode(me.store.filters);
    		me.callParent();
    
    		// 应用UI配置
    		var state = UISettingsSvc.get(me.getStateId());
    		if (state) {
    			me.applyState(state);
    		}
    
    		// 添加菜单,允许恢复默认布局
    		me.headerCt.on('menucreate', function(headerCt, menu) {
    			if (me.stateId) {
    				var menu = me.headerCt.getMenu();
    				menu.add('-', {
    					text: '保存布局',
    					iconCls: 'icon-save',
    					itemId: 'sateState',
    					handler: function(item, e) {
    						UISettingsSvc.saveState(me, me.getStateId(), me.getState());
    					}
    				}, {
    					text: '重置布局',
    					iconCls: 'icon-refresh',
    					itemId: 'resetState',
    					handler: function(item, e) {
    						UISettingsSvc.clearState(me, me.getStateId(), function() {
    							me.store.sorters = new Ext.util.AbstractMixedCollection(false, function(item) {
    								return item.id || item.property;
    							});
    							me.store.sorters.addAll(me.store.decodeSorters(Ext.decode(me.defaultSorters)));
    							me.store.filters = Ext.decode(me.defaultFilters);
    							me.reconfigure(me.store, me.initialConfig.columns);
    						});
    					}
    				});
    			}
    		});
    	}
    });
    




    展开全文
  • 背景 用户通过 Deployment、...随着 k8s 的普及和云原生架构的兴起,越来越多的人希望把数据库这类有状态服务也通过 k8s 进行编排。但因为有状态服务的复杂性,这一过程并不容易。本文将以最流行的开源数据...

    背景

    用户通过 Deployment、ReplicationController 可以方便地在 kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据,通过简单的负载均衡策略可实现请求分发。随着 k8s 的普及和云原生架构的兴起,越来越多的人希望把数据库这类有状态服务也通过 k8s 进行编排。但因为有状态服务的复杂性,这一过程并不容易。本文将以最流行的开源数据库 MySQL 为例,介绍如何在 k8s 上部署运维有状态服务。本文所作的调研基于k8s 1.13

    使用 StatefulSet 部署 MySQL

    本章将以 k8s 官方教程 Run a Replicated Stateful Application 中提供的样例为基础,介绍如何基于 StatefulSet 部署高可用 MySQL 服务。

    StatefulSet 简介

    DeploymentReplicationController 是为无状态服务而设计的,它们中 pod 的名称、主机名、存储都是不稳定的,且 pod 的启动、销毁顺序随机,并不适合数据库这样的有状态应用。为此,k8s 推出了面向有状态服务的工作负载 StatefulSet。其管理的 pod 具有如下特点:

    1. 唯一性 - 对于包含 N 个副本的 StatefulSet,每个 pod 会被分配一个 [0,N)范围内的唯一序号。
    2. 顺序性 - StatefulSet 中 pod 的启动、更新、销毁默认都是按顺序进行的。
    3. 稳定的网络身份标识 - pod 的主机名、DNS 地址不会随着 pod 被重新调度而发生变化。
    4. 稳定的持久化存储 - 当 pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。

    服务部署

    这里介绍的高可用 MySQL 服务由一个 master 节点和多个从 master 上异步复制数据的 slave 节点组成,即一主多从复制模型。其中,master 节点可用来处理用户的读写请求,slave 节点只能用来处理用户的读请求。

    为了部署这样的服务,除了 StatefulSet 之外,还需要使用许多其它类型的 k8s 资源对象,包括 ConfigMapHeadless ServiceClusterIP Service 等。正是它们间的相互配合,才能让 MySQL 这样的有状态服务有条件运行在 k8s 之上。

    statefulset_mysql_overview

    ConfigMap

    为了便于维护应用配置,大型系统和分布式应用常常采用集中式的配置管理策略。在 k8s 环境下,用户可以通过 ConfigMap 将配置和 pod 分离,这有助于保持工作负载的可移植性,使其配置更易于更改和管理。

    样例包含一个名为mysql的 ConfigMap,当 StatefulSet 中的 pod 启动时,会根据自己的角色从 ConfigMap 中读取合适的配置。

    Headless Service

    Headless Service 会为关联的每一个 pod 提供对应的 DNS 地址,格式为<pod-name>.<service-name>。这样,客户端就可以自由地选择想要访问的应用实例,同时也能够解决分布式环境下不同实例之间身份识别的问题。

    样例包含一个名为mysql的 Headless Service,该 service 与 StatefulSet 中的 pod 相关联,这些 pod 将被分配如下 DNS 地址mysql-0.mysqlmysql-1.mysqlmysql-2.mysql。这样,客户端就可以通过mysql-0.mysql访问 master 节点,通过mysql-1.mysqlmysql-2.mysql访问 slave 节点。

    ClusterIP Service

    为了方便只读场景下的访问,样例提供了一个名为mysql-read的普通 service。该 service 拥有自己的 cluster IP,并会将请求分发至关联的 pod(包括 master 和 slave),为用户屏蔽 pod 的访问细节。

    StatefulSet

    StatefulSet 是服务部署的关键,它管理的每个 pod 会被分配一个唯一的名称,格式为<statefulset-name>-<ordinal-index>。样例中的 StatefulSet 名为mysql,因此这些 pod 分别被命名为mysql-0mysql-1mysql-2。默认情况下,它们会按顺序创建,并按逆序销毁。

    如下图所示,一个 pod 包含 2 个 init container 和 2 个 app container,并且通过唯一的 PersistentVolumeClaim 和存储卷供应方提供的 PersistentVolume 绑定。

    statefulset_mysql_pod

    和 Pod 相关的各个组件的功能如下:

    • 容器init-mysql的主要功能是生成配置文件。它会从 hostname 中提取 pod 序号,并将该序号存入文件/mnt/conf.d/server-id.cnf中。另外,它还会根据节点类型将 master.cnf 或 slave.cnf 从 ConfigMap 中拷贝到/mnt/conf目录下。
    • 容器clone-mysql的主要功能是克隆数据。Pod 编号为N+1clone-mysql会将数据从编号为N的 pod 中克隆至绑定的 PersistentVolume 里。
    • Init container 运行完成后,app container 开始运行。容器mysql负责运行着真正的 mysqld 服务。
    • 容器xtrabackup以 sidecar 模式运行。当它发现容器mysql中的 mysqld 就绪后,会通过命令START SLAVE启动 slave 节点的数据复制流程。另外,它还会监听来自其它 pod 的数据克隆请求。
    • StatefulSet 通过 volumeClaimTemplates 为每一个 pod 关联了一个独有的 PVC,样例中编号为N的 pod 关联了名为data-mysql-N的 PVC,而这个 PVC 又会和存储系统提供的 PV 绑定。正是这种机制,保证了 pod 被重新调度后仍然能挂载原有的数据。

    服务运维

    为了保证服务性能、提升系统可靠性,部署工作完成后还需要相应的运维支撑。对于数据库服务,常见的运维工作包括服务故障恢复、服务扩容缩容、服务状态监控、数据备份恢复等。

    服务故障恢复

    服务在遇到故障时能否自愈,是判断一个系统自动化程度的关键指标。在当前架构下,MySQL 服务在遇到宿主机宕机,master 或 slave 节点崩溃等问题时能自动恢复。在上述问题发生后,k8s 会重新调度遇到问题的 pod,让其重新运行。由于使用了 StatefulSet,这些 pod 的名称、主机名和存储会与原来保持一致。

    服务扩容缩容

    在 MySQL 一主多从复制模型下,扩容缩容意味着调整 slave 节点个数。得益于 StatefulSet 对 pod 启动、销毁顺序的保证,通过如下命令就可以轻松实现服务的扩容缩容。

    kubectl scale statefulset mysql --replicas=<NumOfReplicas>

    服务状态监控

    要保证服务的稳定性离不开对服务运行状态的监控。除了通过就绪探针和活性探针检测服务是否正常外,往往还需要更细粒度的监控指标。用户可以借助 mysqld-exporter 将 MySQL 的核心指标暴露给 prometheus,然后基于 prometheus 做监控告警。对于 mysqld-exporter,推荐以 sidecar 模式和 mysqld 容器部署在同一个 pod 中。

    数据备份恢复

    数据的备份和恢复是保障数据安全的有效手段。这里,用户可以直接使用存储卷暴露的接口或者利用 VolumeSnapshot 功能完成数据的备份恢复工作,下面分别进行介绍。

    使用存储卷接口

    许多存储卷供应方都提供了保存数据快照和基于快照恢复数据的功能,这些功能通常以接口的形式暴露给用户。采样这种方式要求用户熟悉对应存储卷供应方提供的操作接口。例如服务选用了阿里云云盘作为外部存储卷,需要用户了解云盘提供的快照接口。

    使用 VolumeSnapshot

    K8s v1.12 引入了 3 个快照相关的资源对象VolumeSnapshotVolumeSnapshotContentVolumeSnapshotClass,通过它们提供了快照操作的标准方法。这样,用户可以在不感知外部存储卷的情况下,为存放 MySQL 数据的存储卷创建快照,或者基于快照恢复数据。

    相比直接使用底层存储卷接口,使用 VolumeSnapshot 显然是更为理想的方法。但目前 VolumeSnapshot 还处在 Alpha 阶段,支持标准快照操作的外部存储卷也有限,这些都限制了当下 VolumeSnapshot 的应用场景。想进一步了解 VolumeSnapshot 可参考文档 Volume Snapshots

    使用 Operator 部署 MySQL

    虽然用户可以基于 StatefulSet 在 k8s 中部署运维一套高可用 MySQL 服务,但过程相对复杂。用户既要熟悉各种 k8s 资源对象,又要学习很多 MySQL 的操作细节,同时还需维护一套复杂的管理脚本。为了降低在 k8s 中部署复杂应用的门槛,诞生了 Kubernetes Operator。

    Operator 简介

    Operator 是由 CoreOS 公司推出的,用来打包、部署和管理需要运行在 k8s 之上的复杂应用的一种方法。Operator 将运维人员对软件操作的知识代码化,同时综合运用 k8s 各种资源对象来实现复杂应用的部署和运维。

    Operator 通过 CustomResourceDefinition 为服务定义了新的资源对象,同时通过自定义控制器来保证应用处于预期状态。

    k8s_operator

    Operator 的工作流程可抽象成以下三个步骤:

    1. Observe - 通过 k8s API 观察目标对象的状态。
    2. Analyze - 分析当前状态与期望状态的差别。
    3. Act - 执行编排操作,将当前状态调整为期望状态。

    Oracle MySQL Operator

    对于 MySQL 服务,已经有很多优秀的开源 Operator 解决方案,包括 grtl/mysql-operatororacle/mysql-operatorpresslabs/mysql-operatorkubedb/mysql 等。本节将要介绍的 Oracle MySQL Operator 便是这些开源解决方案的典型代表。

    Oracle MySQL Operator 工作原理

    Oracle MySQL Operator 支持以下 2 种 MySQL 部署模式。

    • Primary - 服务由一个可读写的主节点和多个只读的从节点组成。
    • Multi-Primary - 集群中各节点角色相同,没有主从的概念,每个节点都可以处理用户的读写请求。

    下图展示了 Multi-Primary 模式下 Operator 的工作原理。

    k8s_operator

    下列流程是理解 Operator 工作原理的关键:

    1. 使用 k8s 的 CustomResourceDefinition(CRD) 定义若干和 MySQL 部署运维相关的资源对象。

      • mysqlclusters - 用于描述集群的期望状态,包括部署模式、节点个数等。
      • mysqlbackups - 用于描述按需备份策略,可以配置备份数据的存放地点,如 AWS S3。
      • mysqlrestores - 用于描述数据恢复策略,需要配置备份数据和目标集群。
      • mysqlbackupschedules - 用于描述定期备份策略,可以配置备份的时间间隔。
    2. 在 k8s 中部署一个 Operator 实例,该 Operator 会持续监听针对这些资源对象的 CRUD 操作,并观察对象状态。
    3. 当用户执行了某项操作,例如创建一个 MySQL 集群时,一个新的 MySQLCluster 资源对象会被创建。当 operator 监听到了 MySQLCluster 的创建事件后,会根据用户配置创建符合需求的集群。这里创建了一个基于 Group Replication 的高可用 MySQL 集群,使用了 StatefulSet、Headless service 等原生 k8s 资源对象。
    4. 当 Operator 观察到 MySQLCluster 的当前状态与期望状态存在差别时,会执行相应的编排操作,保证状态的一致性。

    服务部署

    得益于 Operator 对复杂部署细节的封装,现在创建一个集群变得非常简单。例如,通过如下配置就可以部署一个包含 3 个节点的 MySQL Multi-Primary 集群。

    apiVersion: mysql.oracle.com/v1alpha1
    kind: Cluster
    metadata:
      name: mysql-multimaster-cluster
    spec:
      multiMaster: true
      members: 3

    服务运维

    在 Operator 模式下,同样会涉及服务故障恢复、服务扩容缩容、服务状态监控、数据备份恢复等运维工作。

    服务故障恢复

    由于 StatefulSet 的存在,当某个 MySQL 服务实例崩溃时,k8s 会对其重新调度。另外,如果 StatefulSet 被误删,Operator 也会对其进行重建。

    服务扩容缩容

    通过更改资源对象 MySQLCluster 的字段spec.members可以轻松实现服务扩容缩容。这里只对用户暴露了 MySQLCluster,屏蔽了底层 k8s 资源对象。

    服务状态监控

    可以在 k8s 中部署 Prometheus 监控 Operator 和各个 MySQL 集群的状态。具体步骤可参考文档 Monitoring

    数据备份恢复

    可以通过资源对象 MySQLBackup、MySQLRestore 对数据进行备份和恢复,为用户屏蔽了不同存储卷操作上的差别。另外,还可以通过 MySQLBackupSchedule 创建定时备份任务。

    例如,以下配置可以每隔 30 分钟对 MySQL 集群mysql-cluster的数据库test进行备份。

    [...]
    kind: BackupSchedule
    spec:
      schedule: '*/30 * * * *'
      backupTemplate:
        cluster:
          name: mysql-cluster
        executor:
          provider: mysqldump
          databases:
            - test
    [...]

    总结

    本文分别介绍了通过原生 k8s 资源对象 StatefulSet 以及通过 MySQL Operator 部署运维一套高可用 MySQL 服务的方法和原理。可以看到 Operator 屏蔽了复杂应用的编排细节,大大降低了它们在 k8s 中的使用门槛。如果您有其它复杂应用需要部署,建议采用 Operator 方式。

    作者:吴波bruce_wu

    地址:https://yq.aliyun.com/articles/689685

    展开全文
  • K8s 应用管理之道 - 有状态服务

    千次阅读 2019-02-02 15:30:33
    背景 用户通过 Deployment、ReplicationController 可以方便地在 kubernetes 中...随着 k8s 的普及和云原生架构的兴起,越来越多的人希望把数据库这类有状态服务也通过 k8s 进行编排。但因为有状态服务的复杂性,...
  • 服务的线性扩展

    千次阅读 2018-03-08 23:07:34
    无状态的服务如何做线性扩展 无状态服务(stateless service)对单次请求的处理,不...有状态服务如何做线性扩展 有状态服务(stateful service)则相反,它会在自身保存一些数据,先后的请求是有关联的 ...
  • 从节点需要能水平扩展; 所有的写操作,只能在主节点上执行; 读操作可以在所有节点上执行。 2,配置准备 2.1 configMap #application/mysql/mysql-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: ...
  • 它们是有状态的(将大量玩家数据存储在服务器内存中),无法明确关闭(因为玩家可能仍在欣赏游戏),并且根据经验,他们与玩家之间的联系必须保持最小的延迟,特别是对于实时多人游戏。 该存储库旨在提供解决方案/...
  • Flux可用于构建有状态的工作流和响应式应用程序。 提供Flux快速入门视频。 请浏览页面以了解有关磁通的更多信息。 发行版 释放 日期 描述 版本1.2.1(稳定)(标签1.2.1) 2017年10月 新功能集 版本1.2.0(测试版...
  • FireCamp是一个开放源代码平台,可轻松设置,管理和扩展有状态服务。 客户可以轻松地在任何云上运行任何服务而无管理开销。 FireCamp平台是迈向免费无服务器云的一大步骤。 FireCamp使功能即服务(FaaS)引擎与...
  • Faasm是一种高性能的有状态无服务器运行时。 Faasm提供多租户隔离,但允许函数共享内存区域。 这些共享内存区域提供了低延迟并发访问数据的功能,并在全局范围内同步以支持大规模并行性。 Faasm将来自WebAssembly...
  • Kubernetes StatefulSets(有状态系统服务设计) 具有以下特点时使用StatefulSets 限制 组件 部署和扩展 Pod管理 OrderedReady Pod Management Update Strategies 删除   Kubernetes StatefulSets(...
  • StatefulSet是一种给Pod提供唯一标志的控制器,它可以保证部署和扩展的顺序。 Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关。 稳定的次序:对于N个副本的...
  • 异常请报告。 有关安装说明,请直接转到 。 在台式路由中使用MakeFile 我们使用make来构建和执行程序。 请遵循以下命令以简化开发过程: 更新依赖项: make update 执行应用程序(假设已安装所有依赖项): make...
  • Azure Durable Functions旨在通过引入编排器function概念来定义更复杂的工作流,以此来扩展无服务器计算范式。如果你曾经想过要使用它们,微软刚刚发布了一个示例,帮助开发人员开始他们的无服务器计算和编排器...
  • 有状态,无状态登录

    2020-12-21 21:50:29
    **有状态登录 **有状态登录: 服务端会存储登录用户的信息 把用户的数据存储在服务端的session中 1、把用户信息放入session session.setAttribute(“user”,user); 用户A、B、C,session.getAttribute(“user”); 2...
  • 1.有状态登陆 这样做的缺点是什么? 服务端保存大量数据增加服务端的压力 服务端保存用户状态,无法进行水平扩展(集群)→针对Session来说 客户端请求依赖服务端,多次请求必须访问同一台服务器→针对Session来说 ...
  • FCI开发出一款垂直存储插槽系列,可接受标准的双数据率(DDR2)内存模块组装,从而实现桌面PC和服务器的内存扩展。240引线的DDR2垂直连接器提供了用于焊接端子的直通安装引脚或用于压装端子的“针眼”引脚。  直通...
  • 每个房间的用户在线状态API带通知。 实时会议室创建和每个会议室用户权限管理API。 支持基于黑名单或白名单的访问模式以及可选的管理员组。 从各种设备到任何服务实例的多用户连接的无缝支持。 写为无状态...
  • 允许用户扩展或覆盖框架的核心功能。 其中许多插件都是由我们令人惊叹的社区成员贡献的! :party_popper: 该存储库旨在成为无服务器生态系统中所有出色插件的一站式服务。 如果您对功能或插件想法,请在问题中...
  • 嗨,大家好! 在这篇博客文章中,我想举一个简单的例子,展示使用Openshift 3(Docker和Kubernetes)扩展Drools ...我们的Drools无状态服务 首先,我们需要一个无状态的Kie Session进行游戏。 在这些简单的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,634
精华内容 1,053
关键字:

有状态服务扩展