作者文章

fwq

FWQ
服务器教程
一种可扩展缓存架构
之前对于并发和异步讲得比较多,今天讲一下更常见的缓存应用架构。 我们先看一个简单的架构 请求数据的流程图 这里有几个要点 引入了缓存,减少了对数据库对访问(当缓存命中时) 当缓存不命中时,立刻进行补偿(读取数据库,更新缓存) 局限性 要求缓存数据结构与数据库表结构对应,无复杂计算。如果有复杂计算,当缓存失效同时并发访问,会导致服务器cpu耗尽。 缓存失效时,总会访问到数据库,随着并发请求的数量增大,数据库的负担也会增大 针对以上的局限,在复杂计算情况下,我们会把计算的结果保存到缓存,得到架构二 在这个架构下,我们引入一个定时运行(也可以是事件触发运行)的job,读取数据库的数据,进行计算后存放到缓存。保证缓存的数据始终有效(数据永不过期),应用层的访问不会穿透到数据库。 这个架构的优点很明显 数据库得到了保护,只有job访问数据库,数据库不会因为并发访问的数量增加而负载增加 应用层不含有补偿逻辑,简化了开发 然而这个架构包含致命缺陷,导致无法实际使用,这个缺陷就是“假设缓存是可靠的”。这与缓存高性能低可靠的设计原则是背离的,我们无法在系统中假设缓存可靠,保证缓存可靠的成本是运维部门无法承受的。 于是,结合之前两个架构的优点,我们得到了架构三 和架构二比较,我们引入一个shadow DB,这个db的表结构和缓存完全对应。然后我们的job,同时更新shadow DB和缓存的数据。我们的应用层如果访问缓存失败,到shadow DB去补偿 优点:  DB得到了保护,访问不会穿透 假设缓存不可靠。缓存过期或者缓存服务宕机,压力只会传导到不影响业务的shadowDB ShadowDB和缓存可以独立扩展,应对更大的访问量 缺点: 引入了shadowDB,需要开发相应的(并不复杂的)逻辑 理解了这个缓存模块的设计,可以根据业务需要在系统中堆叠更多缓存层级
2024-11-19 阅读全文 →
FWQ
服务器教程
软件服务内部的多线程模型
回顾之前说过的,服务和服务之间的调用,可以分为同步调用(发起方等待结果)和异步调用(发起方不等待结果),同步调用的好处是写代码简单,坏处是有可能阻塞线程,造成线程资源浪费。我这里说“有可能”,是因为可以使用支持io异步的编程语言,来避免线程阻塞。 如果服务间调用使用异步的流程,当然可以彻底避免线程阻塞,一般来说又分两种实现场景。 回调方式 A调用B,A继续其他工作,B完成后,调用A,告诉计算结果。这种方式效率更高,但要求A提供被调用的api 轮询方式 A调用B,A每隔一段时间调用B提供的结果查询api,结果查询的api通常返回任务处理的进度或结果。这种场景下,B不需要调用A,所以A的实现更简单。 不管使用了哪种方式,都需要有超时补偿机制,通常是一个定时执行的job,来找出那些发出请求很久,却还没有收到结果的请求。 刚才说的“同步”或者“异步”,我称之为“流程”,是指服务的设计思想,微观到每一次服务的调用,其实都是“同步”的,因为服务调用,总要等到那次调用返回的结果。同步流程下,服务调用返回结果就是计算结果,异步流程下,服务调用返回的是“我已经开始处理你的请求”这样的消息。 到现在才讲到今天的主题,今天讲的是被调用的这个服务(进程)的内部,有哪几种“同步”或“异步”的多线程编程模式。下面所说的“线程”和“任务”,都是指运行在线程池上的一段代码,而不是指操作系统线程。避免程序员手工创建线程,是异步编程计算机语言的重大贡献。 我试图在概念上从大到小,把多线程的模式说清楚。 通常服务收到一个调用请求,就会为这个请求提供一个线程来服务,如果同时收到许多个请求,就会有许多个同时运行的线程来分别服务这些请求,这是基本的多线程模型。现在我们只考察其中的一个线程。 在一个服务请求的线程中,最大的区分维度是上面提到的“流程”。 在“同步流程”下,收到请求的线程立刻投入计算,执行“一段代码”,把计算结果返回给调用方。 在“异步流程”下,收到请求的线程创建一个计算任务,由那个任务去执行那“一段代码”,创建完任务之后,不等任务执行完成,立刻返回给调用方,通常返回的信息代表“我已经开始处理你的请求”。 下一个考察维度是那“一段代码”内部,有没有可能的“异步模型”,我总结有四种情况 完全顺序执行,没有异步。那“一段代码”的执行,从开始到结束占用一个线程 存在IO异步。我们知道,磁盘操作和网络操作的速度,相对于CPU(执行线程)的速度,是非常慢,如果线程执行到磁盘操作和网络操作,等待操作结果,会大大浪费线程资源。理想状态下,线程执行到磁盘操作或网络操作时,能够交出控制权,把线程资源给别的任务去使用,等到磁盘操作或网络操作的结果返回,当前任务可以被唤醒,拿到线程资源继续执行。这个资源调度的方法操作系统是支持的,windows下叫做“IOCP”,linux下叫做“epoll”,支持io异步的编程语言,比如F#和go语言,对IOCP或者epoll进行了封装,可以用顺序编程的语句,实现io异步编程。如果编程语言不支持,需要编写底层函数,同时代码中会出现大量回调函数 任务可分解。“一段代码”内部存在可并发的逻辑,可以创建多个任务同时执行,主线程收集每个任务返回的结果,整段代码的执行时间取决于最慢的那个子任务的执行时间,这种模式有利于充分利用cpu多核资源,缩短当前任务执行的总时间。 单例模式。“一段代码”内部访问了有竞争的资源,不可以与同类的代码同时执行,这时进程内使用了actor模型,会有一个线程专门管理那个竞争资源,代码需要给那个actor发消息,等待actor的执行结果。也就是在这个模式下,当前线程需要与一个已经存在的线程交换消息。线程间的消息传递,最好也需要编程语言的支持,所以现在开始,是时候放弃那些不支持线程间消息传递的语言了
2024-11-19 阅读全文 →
FWQ
服务器教程
什么时候需要自己搭建缓存服务
都说“程序等于数据结构加算法”,在软件的运行时刻,其实是数据加进程,进程离数据越“近”,越能得到高速的读写性能。   Cpu的计算速度通常不是软件执行的瓶颈,io的速度会更大地影响软件执行的速度,从“近”到“远”,一般有以下几个场景: 访问内存 访问磁盘 访问网络 软件设计的性能提升,都是想办法使进程更靠近数据,减少数据在慢速介质中的传输。 传统软件的client/server结构,就是基于这个思路,让计算在服务端完成,只把计算结果传输到客户端,这样可以减少数据的传输的耗时。反过来,当然也可以把数据传输到客户端,由客户端计算,来节省服务器的cpu消耗,但一般情况下,很少有只需要传送少量数据而需要大量计算的场景,而采用后一种方案。 传统的数据库软件,都会提供存储过程和触发器这样的编程方法,使计算更“靠近”数据,也是提高性能的良方。现代的海量数据系统,比如hadoop,数据分布在许多节点上,在执行计算时,也是把代码发布到数据节点上,在每个节点分别计算,最后汇总结果,这个过程称为mapreduce,执行计算时,都是访问节点本地的数据,汇总时才使用到网络传输。 回到我们的应用软件,因为访问内存比访问磁盘快,所以我们使用redis来做缓存,redis是共享内存的存储,在实际使用中,通常和应用服务器不在同一台物理机器上,也就是说,应用系统访问redis,还是要经过网络传输。 所以应用程序访问数据库的延时是:网络延时+磁盘延时 应用程序访问redis缓存的延时是:网络延时+内存延时 所以,即使是访问缓存,也要求在完成一个请求(当然,今天我们讨论的是web应用)时,访问缓存的次数尽量地少。根据实际的情况,如果是一个需要返回结果给调用方结果的同步请求,需要访问缓存6、7次,会大大降低性能,这时,说明你的进程离数据不够“近”。 这时我们有两个选择,要么给redis开发插件,使redis支持“存储过程”,使计算在redis的进程内完成。要么自己实现缓存功能,也就是在你的应用进程内,实现redis的功能。在大多数情况下,后者更容易实现,通常只需要在内存中构造一个dictionary对象,支持按key访问value就可以了。这样,我们其实构造了一个local cache,我们构造local cashe的目的非常明确,当应用的计算,需要访问多次缓存数据时(这时使用缓存的性能优势已经不明显),我们在应用中构造自己的缓存,使得访问外部缓存系统变成访问本地内存,来提高性能。 在实践中,构造本地缓存,需要考虑到以下几个方面 使用集群,每个集群内的缓存数据,都是相同的,外部请求不管落到哪个服务器,都是访问本机的内存来获得数据 数据同步,集群内的每个服务器,数据同步是独立完成的,数据源的变化会通知到每一台服务器。在某个时间点,不同的服务器内数据允许有不同,但最终数据一致 读写分离的api实现,读操作,可以多线程并发执行。写操作,由专门的线程执行,也就是使用actor模型,由某个actor执行所有的数据修改操作 冷启动,因为需要加载某些表的所有记录到内存中,通常会需要几十秒到数分钟,需要有 拉出集群 – 加载数据 – 拉进集群 的发布流程 数据过期策略,如果有数据过期的需求,需要设计一个后台线程,定期检查所有的缓存元素,把过期的数据清理掉
2024-11-19 阅读全文 →
FWQ
服务器教程
ZooKeeper之ZAB协议
ZooKeeper为高可用的一致性协调框架,自然的ZooKeeper也有着一致性算法的实现,ZooKeeper使用的是ZAB协议作为数据一致性的算法, ZAB(ZooKeeper Atomic Broadcast ) 全称为:原子消息广播协议;ZAB可以说是在Paxos(帕克索斯)算法基础上进行了扩展改造而来的,ZAB协议设计了支持崩溃恢复,ZooKeeper使用单一主进程Leader用于处理客户端所有事务请求,采用ZAB协议将服务器数状态以事务形式广播到所有Follower上;由于事务间可能存在着依赖关系,ZAB协议保证Leader广播的变更序列被顺序的处理,:一个状态被处理那么它所依赖的状态也已经提前被处理;ZAB协议支持的崩溃恢复可以保证在Leader进程崩溃的时候可以重新选出Leader并且保证数据的完整性;   在ZooKeeper中所有的事务请求都由一个主服务器也就是Leader来处理,其他服务器为Follower,Leader将客户端的事务请求转换为事务Proposal,并且将Proposal分发给集群中其他所有的Follower,然后Leader等待Follwer反馈,当有 过半数(>=N/2+1) 的Follower反馈信息后,Leader将再次向集群内Follower广播Commit信息,Commit为将之前的Proposal提交; 协议状态 ZAB协议中存在着三种状态,每个节点都属于以下三种中的一种: Looking :系统刚启动时或者Leader崩溃后正处于选举状态 Following :Follower节点所处的状态,Follower与Leader处于数据同步阶段; Leading :Leader所处状态,当前集群中有一个Leader为主进程; ZooKeeper启动时所有节点初始状态为Looking,这时集群会尝试选举出一个Leader节点,选举出的Leader节点切换为Leading状态;当节点发现集群中已经选举出Leader则该节点会切换到Following状态,然后和Leader节点保持同步;当Follower节点与Leader失去联系时Follower节点则会切换到Looking状态,开始新一轮选举;在ZooKeeper的整个生命周期中每个节点都会在Looking、Following、Leading状态间不断转换; 选举出Leader节点后ZAB进入原子广播阶段,这时Leader为和自己同步的每个节点Follower创建一个操作序列,一个时期一个Follower只能和一个Leader保持同步,Leader节点与Follower节点使用心跳检测来感知对方的存在;当Leader节点在超时时间内收到来自Follower的心跳检测那Follower节点会一直与该节点保持连接;若超时时间内Leader没有接收到来自过半Follower节点的心跳检测或TCP连接断开,那Leader会结束当前周期的领导,切换到Looking状态,所有Follower节点也会放弃该Leader节点切换到Looking状态,然后开始新一轮选举; 阶段 ZAB协议定义了 选举(election)、发现(discovery)、同步(sync)、广播(Broadcast) 四个阶段;ZAB选举(election)时当Follower存在ZXID(事务ID)时判断所有Follower节点的事务日志,只有lastZXID的节点才有资格成为Leader,这种情况下选举出来的Leader总有最新的事务日志,基于这个原因所以ZooKeeper实现的时候把 发现(discovery)与同步(sync)合并为恢复(recovery) 阶段; Election :在Looking状态中选举出Leader节点,Leader的lastZXID总是最新的;…
2024-11-19 阅读全文 →
FWQ
服务器教程
Zookeeper可以干什么
在Zookeeper的官网上有这么一句话:ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. 这大概描述了Zookeeper主要可以干哪些事情:配置管理,名字服务,提供分布式同步以及集群管理。那这些服务又到底是什么呢?我们为什么需要这样的服务?我们又为什么要使用Zookeeper来实现呢,使用Zookeeper有什么优势?接下来我会挨个介绍这些到底是什么,以及有哪些开源系统中使用了。   配置管理 在我们的应用中除了代码外,还有一些就是各种配置。比如数据库连接等。一般我们都是使用配置文件的方式,在代码中引入这些配置文件。但是当我们只有一种配置,只有一台服务器,并且不经常修改的时候,使用配置文件是一个很好的做法,但是如果我们配置非常多,有很多服务器都需要这个配置,而且还可能是动态的话使用配置文件就不是个好主意了。这个时候往往需要寻找一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的都可以获得变更。比如我们可以把配置放在数据库里,然后所有需要配置的服务都去这个数据库读取配置。但是,因为很多服务的正常运行都非常依赖这个配置,所以需要这个集中提供配置服务的服务具备很高的可靠性。一般我们可以用一个集群来提供这个配置服务,但是用集群提升可靠性,那如何保证配置在集群中的一致性呢? 这个时候就需要使用一种实现了一致性协议的服务了。Zookeeper就是这种服务,它使用Zab这种一致性协议来提供一致性。现在有很多开源项目使用Zookeeper来维护配置,比如在HBase中,客户端就是连接一个Zookeeper,获得必要的HBase集群的配置信息,然后才可以进一步操作。还有在开源的消息队列Kafka中,也使用Zookeeper来维护broker的信息。在Alibaba开源的SOA框架Dubbo中也广泛的使用Zookeeper管理一些配置来实现服务治理。 命名服务 名字服务这个就很好理解了。比如为了通过网络访问一个系统,我们得知道对方的IP地址,但是IP地址对人非常不友好,这个时候我们就需要使用域名来访问。但是计算机是不能是别域名的。怎么办呢?如果我们每台机器里都备有一份域名到IP地址的映射,这个倒是能解决一部分问题,但是如果域名对应的IP发生变化了又该怎么办呢?于是我们有了DNS这个东西。我们只需要访问一个大家熟知的(known)的点,它就会告诉你这个域名对应的IP是什么。在我们的应用中也会存在很多这类问题,特别是在我们的服务特别多的时候,如果我们在本地保存服务的地址的时候将非常不方便,但是如果我们只需要访问一个大家都熟知的访问点,这里提供统一的入口,那么维护起来将方便得多了。 分布式锁 其实在第一篇文章中已经介绍了Zookeeper是一个分布式协调服务。这样我们就可以利用Zookeeper来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即fail over到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫Leader Election(leader选举)。比如HBase的Master就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所以使用的时候要比同一个进程里的锁更谨慎的使用。…
2024-11-19 阅读全文 →
FWQ
服务器教程
Zookeeper是什么
Google的三篇论文影响了很多很多人,也影响了很多很多系统。这三篇论文一直是分布式领域传阅的经典。根据MapReduce,于是我们有了Hadoop;根据GFS,于是我们有了HDFS;根据BigTable,于是我们有了HBase。而在这三篇论文里都提及Google的一个lock service—Chubby,哦,于是我们有了Zookeeper。   随着大数据的火热,Hxx们已经变得耳熟能详,现在作为一个开发人员如果都不知道这几个名词出门都好像不好意思跟人打招呼。但实际上对我们这些非大数据开发人员而言,Zookeeper是比Hxx们可能接触到更多的一个基础服务。但是,无奈的是它一直默默的位于二线,从来没有Hxx们那么耀眼。那么到底什么是Zookeeper呢?Zookeeper可以用来干什么?我们将如何使用Zookeeper?Zookeeper又是怎么实现的? 伴随着Zookeeper有两篇论文:一篇是Zab,就是介绍Zookeeper背后使用的一致性协议的(Zookeeper atomic broadcast protocol),还有一篇就是介绍Zookeeper本身的。在这两篇论文里都提到Zookeeper是一个分布式协调服务(a service for coordinating processes of distributed applications)。那分布式协调服务又是个什么东西呢?首先我们来看“协调”是什么意思。 说到协调,我首先想到的是北京很多十字路口的交通协管,他们手握着小红旗,指挥车辆和行人是不是可以通行。如果我们把车辆和行人比喻成运行在计算机中的单元(线程),那么这个协管是干什么的?很多人都会想到,这不就是锁么?对,在一个并发的环境里,我们为了避免多个运行单元对共享数据同时进行修改,造成数据损坏的情况出现,我们就必须依赖像锁这样的协调机制,让有的线程可以先操作这些资源,然后其他线程等待。对于进程内的锁来讲,我们使用的各种语言平台都已经给我们准备很多种选择。就拿Java来说,有最普通不过的同步方法或同步块: public synchronized void sharedMethod(){    //对共享数据进行操作 } 使用了这种方式后,多个线程对sharedMethod进行操作的时候,就会协调好步骤,不会对sharedMethod里的资源进行破坏,产生不一致的情况。这个最简单的协调方法,但有的时候我们可能需要更复杂的协调。比如我们常常为了提高性能,我们使用读写锁。因为大部分时候我们对资源是读取多而修改少,而如果不管三七二十一全部使用排他的写锁,那么性能有可能就会受到影响。还是用java举例: public class SharedSource{   …
2024-11-19 阅读全文 →
FWQ
服务器教程
安装PHP中的zookeeper扩展
PHP的安装以及配置都非常的简单,可以参考文章:Zookeeper的安装,今儿来看看PHP扩展的安装。 现在官方已经有0.3.2的版本了,查看 https://pecl.php.net/package/zookeeper   安装zookeeper Lib 下载参考:Zookeeper的安装 # tar -xzf zookeeper-3.4.9.tar.gz # cd zookeeper-3.4.9/src/c # ./configure –prefix=/usr/local/zookeeper-lib/ # make && make install 下载 官方下载地址:https://pecl.php.net/package/zookeeper # tar xzf zookeeper-0.3.1.tgz #…
2024-11-19 阅读全文 →
FWQ
服务器教程
Zookeeper中的命令
zookeeper中的命令不多,且比较简单,下面做一个完整的介绍。   进入命令行: # ./zkCli.sh -server 192.168.80.121:2181 使用help列出所有的命令,如下: stat path [watch] 使用 stat 命令可以输出节点的统计信息. #注意和get的区别 # stat /wanda/store cZxid = 0x10000008e ctime = Tue Feb 07 13:26:43 UTC…
2024-11-19 阅读全文 →
FWQ
服务器教程
PHP中zookeeper的类与方法
__construct( $host = ‘‘, $watcher_cb = null, $recv_timeout = 10000) $host:zookeeper的ip和端口,多组信息使用,分隔 $watcher_cb:全局监听函数,任何的监听都会执行该回调函数 $recv_timeout:会话超时,在zookeeper的服务器中可以设置 minSessionTimeout和maxSessionTimeout,限制客户端设置session的超时范围   $zookeeper = new Zookeeper("192.168.80.121:2181,192.168.80.122:2181, 192.168.80.123:2181",'globalWatcher',5000); $zookeeper->get('/redis','redisWatcher'); $zookeeper->get('/rabbit','rabbitWatcher'); function globalWatcher(){ echo "global watcher\n"; } function redisWatcher(){ echo "redis watcher\n"; $GLOBALS['zookeeper']->get('/redis','redisWatcher'); } function rabbitWatcher(){…
2024-11-19 阅读全文 →
FWQ
服务器教程
ZooKeeper原理及使用
ZooKeeper是Hadoop Ecosystem中非常重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的类似服务叫Chubby。今天这篇文章分为三个部分来介绍ZooKeeper,第一部分介绍ZooKeeper的基本原理,第二部分介绍ZooKeeper提供的Client API的使用,第三部分介绍一些ZooKeeper典型的应用场景。 ZooKeeper基本原理 1、数据模型 如上图所示,ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每个ZNode都可以通过其路径唯一标识,比如上图中第三层的第一个ZNode, 它的路径是/app1/c1。在每个ZNode上可存储少量数据(默认是1M, 可以通过配置修改, 通常不建议在ZNode上存储大量的数据),这个特性非常有用,在后面的典型应用场景中会介绍到。另外,每个ZNode上还存储了其Acl信息,这里需要注意,虽说ZNode的树形结构跟Unix文件系统很类似,但是其Acl与Unix文件系统是完全不同的,每个ZNode的Acl的独立的,子结点不会继承父结点的,关于ZooKeeper中的Acl可以参考之前写过的一篇文章:说说Zookeeper中的ACL 2、重要概念 2.1 ZNode 前文已介绍了ZNode, ZNode根据其本身的特性,可以分为下面两类: Regular ZNode: 常规型ZNode, 用户需要显式的创建、删除 Ephemeral ZNode: 临时型ZNode, 用户创建它之后,可以显式的删除,也可以在创建它的Session结束后,由ZooKeeper Server自动删除 Tips:ZNode还有一个Sequential的特性,如果创建的时候指定的话,该ZNode的名字后面会自动Append一个不断增加的SequenceNo。 Tips:创建、删除、设置的PHP设置方法参考:PHP中zookeeper的类与方法、在命令行的操作方法参考:Zookeeper中的命令 2.2 Session Client与ZooKeeper之间的通信,需要创建一个Session,这个Session会有一个超时时间。因为ZooKeeper集群会把Client的Session信息持久化,所以在Session没超时之前,Client与ZooKeeper…
2024-11-19 阅读全文 →