15.深入分布式缓存:从原理到实践 --- 同程凤凰缓存系统基于Redis的设计与实践

1.同程凤凰缓存系统要解决什么问题
	秒杀抢购一开始,卡顿,打不开的故障就会此起彼伏。因为秒杀抢购的流量一下而过,没有机会补救。
	问题很简单:抢购那一下太耗服务器资源,在同一时间内涌入的人数大大超过了服务器的负载,服务器根本承受不了,cpu占用率很多时候都接近100%,
  请求的积压也很严重,从请求接入到数据的读取都有问题,尤其是数据的读取。原来的设计中虽然也考虑了大并发量下的数据读取,但是因为数据相对分散
  读取时间相对比较长,不像秒杀都是对同一批或同一条数据进行超高并发的读取。同时数据写入也是集中并发。
  	大部分关系型数据库没有真正的分布式解决方案,最多做主从分离或多加从库分担读取的压力。于是我们也做了一些方案,例如:数据在抢购活动之前先
  被放到nosql数据库里,产生的订单数先被放到队列中,然后通过队列慢慢消化。

  	1.redis用法的凌乱
  		redis为什么不能访问了?
  		服务器内存坏了,服务器自动重启了。

  		redis为什么延迟这么久?
  		在zset中放入几万条数据,计算量很大。

  		写进去的key为什么不见了?
  		redis超过最大大小,淘汰了

  		读取为什么都失败了?
  		网络临时中断,slave全同步了,在全同步完成之前,slave读取失败。

  		解决办法:
  			1.单机不够安全?开启主从+keepalived,用虚ip地址在master和slave两边漂移,master挂了直接切换到slave
  			问题:
  				忽略了主数据节点挂掉的情况。redis的单进程,单线程设计是非常简单和稳定的基石,只要不是服务器发生了故障,一般情况下是不会挂的。但同时,单进程,单线程的
  			  设计会导致redis接收到复杂指令时会忙于计算而停止响应,可能就因为一个zset或者keys之类的指令,redis计算时间稍长,keepalived就认为其停止了响应,直接更改
  			  虚ip的指向,然后做一次主从切换。过不了多久,zset和keys之类的指令又会从客户端发送过来,于是从机器又开始阻塞了,keepalived就一直在主从之间不断切换ip。
  			  终于主从节点都堵了,keepalived发现后,居然直接将虚ip释放了,然后所有的客户端都无法连接redis,只能等运维到线上手工绑定才行。

  			2.数据放内存不够安全?可以开启数据落盘,aof,rdb
  				数据落盘也引起很大的问题,rdb属于非阻塞式的持久化,它会创建一个子进程来专门把内存中的数据写入rdb文件中,同时主进程可以处理来自客户端的命令请求。但子进程
  			  内的数据相当于是父进程的一个拷贝,这相当于两个相同大小的redis进程在系统上运行,会造成内存的使用率的大幅增加。如果在服务器内存本身就比较紧张的情况下再进行
  			  rdb配置,内存占用率就会很容易达到100%,继而开启虚拟内存和进行磁盘交换,然后整个redis的服务性能就会直线下降。

  			3.使用上有问题?多开几场培训,跟大家讲讲redis的用法和规范

  	2.从实际案例再看redis的使用
  		案例1:
  		主从redis之间的网络出现了一点小动荡,想想这么大的东西在主从同步,一旦网络抖动了下,会怎么样?主从同步失败,就会开启全同步,于是200GB的redis瞬间开始同步,网卡
  	  瞬间打满。为了保证redis能够继续提供服务,运维同学直接关掉了从redis。由于下层降级的原因,并发操作每秒增加了4w多,aof和rdb明显扛不住。同样为了保证redis正常运行,
  	  运维同学关掉了aof和rdb的数据持久化。连最后的保护都没了。
  	  	这个案例中的主要问题在于对redis过度依赖,redis看似简单而方便的为系统带来了性能的提升和稳定性,但在使用中缺乏对不同场景的数据的分离造成了一个逻辑上的单点问题。
  	  当然这个问题我们可以通过更加合理的应用架构设计来解决,但是这样解决不够优雅也不够彻底,还增加了应用层的架构设计的麻烦。redis的问题就应该在基础缓存层解决。

  	  	案例2:
  	  	redis无法像关系型数据库那样有dba的管理,它的运维者无法管理和提前知道里面放的是什么数据,开发者也无需任何申明就可以向redis中写入数据并使用,所以这里我们发现redis
  	  的使用没有这些场景的管理后在长期的使用中比较容易失控,我们需要一个对redis使用可治理和管控的透明层。

  	  	redis被keys命令阻塞了;
  	  	keepalived切换虚ip失败,虚ip被释放了;
  	  	用redis做计算,redis的cpu占用率成了100%;
  	  	主从同步失败;
  	  	redis客户端连接数爆了;

  	3.如何改变redis用不好的误区
  		总结几点:
  			1.必须搭建完善的监控体系,在这之前要先预警
  			2.控制和引导redis的使用,我们需要由自己的redis客户端
  			3.redis的部分角色要改,将redis由storage角色降低为cache角色
  			4.redis的持久化方案要重新做,需要自己研发下一个基于redis协议的持久化方案,让使用者把redis当做db用
  			5.redis的高可用要按照场景分开,根据不同的场景决定采用不同的高可用方案

  		首先是监控系统,这个监控系统是全方位的从客户端开始一直到返回数据的全链路的监控。
  		其次是改造redis客户端。在这个客户端里面我们植入了日志,记录了代码对redis的所有操作,例如,耗时,key,value大小,网络断开等,我们将这些有问题的事件后台收集,由一个
  	  收集程序进行分析和处理,同时取消了直接的ip端口连接方式,通过一个配置中心分配ip和端口。当redis发生问题并需要切换的时候,直接修改配置中心,由配置中心推送新的配置到客户端,
  	  这样就免去了redis切换时需要运维人员修改配置文件的麻烦。另外把redis的命令操作分拆成2部分:安全命令和不安全命令。安全命令可以直接使用,不安全命令需要分析和审批后才能打开。
  	  这也是由配置中心控制的,这样就解决了研发使用redis的规范问题,并且将redis定位为缓存角色,除非有特殊需求,否则一律以缓存角色对待。
  	  	最后,对redis的部署方式也进行了修改,以前是keepalived的方式,现在换成了主从+哨兵的模式。另外,我们自己实现了redis的分片,如果业务需要申请大容量的redis数据库,就会把
  	  redis拆分成多片,通过hash算法均衡每片大小,这样的分片对应用层也是无感知的。
  	  	我们还会做一个redis 的proxy,提供统一的入口,proxy可以部署多份,无论客户端连接的是哪个proxy,都能获取完整的集群数据,这样就基本完成了按照场景选择不同的部署方式的问题,
  	  这样一个proxy也解决了多种开发语言的问题。

  	4.凤凰缓存系统对redis系统化改造
  		1.监控
  		2.调度
  		3.加强客户端,proxy

2.用好redis先运维好它
	评价一款中间件,我们要综合它的周边生态来评价。

	1.传统的redis运维
		1.统一大集群部署方式
		这种部署方式是将所有的redis集中在一起,形成一个超大的redis集群,通过代理的方式统一对外提供连接,使外部看起来就是个完整的超大redis,所有的项目都共享这个redis,在这个
	  redis内部,可以自动的添加服务,修改配置等,而且外部完全无感知。
	  	优点:
	  		1.扩容方便,直接在集群内新增机器即可,使用者完全无感知
	  		2.利用率高,运维简单,只需要关注整个集群的大小即可,不需要关心里面某个redis的具体状态
	  		3.客户端使用方便

	  	缺点:
	  		1.扩容虽然方便,但是具体某个项目用了多少,无法获知。
	  		2.整个超大的redis内部其实还是由各个小的redis组成的,每个key都单独存储到小的redis内部,那么如果某个key很热,读取访问非常频繁,很可能将某个小的redis网卡打爆,
	  		导致的结果就是1个分片不可用,而项目又是集体共享的一组redis,某个分片不可用,可能导致的结果就很难评估。

	  	2.多集群部署方式
	  		这种方式主要提供了自动化部署,部署的各个redis互相独立,而一般情况下一组redis也是单独某个项目独占,隔离性非常好,不会因为某个项目的问题导致整个集群不可用,
	  	  但是由于要维护一大堆redis,各个redis的情况又不一样,在自动化部署方面比较麻烦。
	  	  	优点:
	  	  		1.隔离性非常好
	  	  		2.灵活性高
	  	  	缺点:
	  	  		1.部署麻烦
	  	  		2.客户端使用麻烦,不同的redis有自己的ip,端口。
	  	
	  	同程的redis遇到的麻烦:
	  		1.部署忙不过来
	  		2.如果服务器需要调整,修改,通知业务修改新的ip再重新发布,耗时很长
	  		3.服务器资源利用率低

	2.redis的docker部署
		一个新机器配置好后就能直接通过docker 的 restful api进行操作。

		1.redis 在docker下的cpu监控
			redis本身对cpu的使用敏感性不是很大,所以在cpu的使用隔离上不需要花费太多精力,用docker隔离分配的就满足需求。
		2.redis 在docker下的内存控制
			在内存控制上就相对麻烦一点,redis的内存使用有两个属性,实际使用的内存和操作系统分配的内存,反应在redis上就是used_memory(实际使用的内存)和used_memory_rss(
		  操作系统分配的内存)这2个参数,maxmemory实际控制的是 used_memory ,而不是used_memory_rss,由于redis在后端执行rdb操作或者频繁的增删改产生大量的碎片,rss的值
		  就会变得比较大。如果通过docker强制限制内存,这个进程可能直接就被kill了,所以,在内存上,我们没有采用docker限制,而是通过自己的监控程序进行控制。举个例子来说,
		  某个项目新申请的redis是10GB,我们在通过docker部署这个redis的时候,只是开启了一个500MB的redis,随着项目的使用,当redis实际空闲内存小于250MB的时候,我们就通过
		  redis命令设置maxmemory为1GB,然后继续监控,直到内存为10G为止。等项目内存到达10GB以后,我们就根据不同的策略做不同的处理,比如有些项目只拿redis当缓存处理,10GB
		  足够了,万一超过,就可以放弃冷数据,在这样的情况下不会加大内存。如果项目很重要,也可以设置一个超额的量,这样程序就会自动进行扩容的同时报警,让开发介入。

		3.docker网卡控制
			用redis都有个很头疼的问题,就是redis的网卡打满的问题,由于redis性能很高,在大并发的情况下,很容易将网卡打满。通常情况下,一台服务器上都会跑十几个redis实例,
		  一旦网卡打满,很容易干扰到应用层可用性。所以我们基于开源的Contiv netplugin 项目,限制了网卡的使用,主要功能是基于Polocy的网络和存储管理。Contiv比较'诱人'的
		  一点就是,它的网络管理能力,既有L2(VALN),L3(BGP),又有Overlay(VxLAN),有了它就可以无视底层网络基础架构,向上层容器提供一致的虚拟网络了。最主要的一点是,既满足
		  了业务场景,又兼容了以往的网络架构。在转发性能上,它能接近物理网卡的性能,特别在没有万兆网络的老机房也能很好的使用。在网络流量监控方面,我们通过使用ovs的sflow
		  来抓取宿主上所有的网络流量,然后自己开发了一个简单的sflow Collector,服务器接收到sflow的数据包进行解析,然后筛选出关键数据,然后进行汇总分析,得到所需的监控
		  数据。通过这个定制化的网络插件,我们可以随意的控制某个redis的流量,流量太大,也不会影响到其他项目,而如果这个服务器上的redis流量很低,我们也可以缩小它的配额,
		  提供给本机其他需要大流量的程序使用。这些,通过后台的监控程序,可以完善实现自动化。

	3.凤凰缓存系统对redis的监控
		要良好的运维一个系统,监控的好坏是关键,监控的基础是收集服务器信息和redis运作信息,并把这些信息丢入一个信息处理管道,在分析之前结合经验数据输出一系列的具体处理
	  方式。这样缓存系统自己就能够处理掉大部分的故障,不需要大量的人工介入。凤凰缓存系统整个监控由搜集器,存储器,分析器,执行器4个部分组成:
	  	1.搜集器 : 是一个Go开发的一个程序,这个程序搜集两个部分的内容,即服务器本身的数据和redis的数据。首先,搜集程序会查询当前所在服务器的cpu,网卡,内存,进程等信息。
	    然后,搜集程序查询这个服务器上的redis,然后遍历这些redis,获取info,slowlog,client等信息,再把这些信息整理好,上报到存储器。	
	  	2.存储器 : 负责存储监控数据,它对外就是一组restful api。存储器对这些信息进行汇总和整理,然后进行一些简单的计算,然后将数据存储到分析平台中。
	  	3.分析器 : 的工作是从es开始的,针对各种数据进行分析和处理,然后产出各种处理意见,提交给执行器。比如,分析器发现某个服务cpu在10分钟内都是100%,就会触发一个报警阈值,
	  	分析器会产生一个处理意见,建议人工介入。这个处理意见提交给执行器,由执行器具体执行。
	  	4.执行器 : 是一个根据处理意见进行处理的分析程序,简单的分类问题并结合处理意见进行判断是人工介入还是先自动处理。对自动处理的事件,如redis的内存不够,进行扩容操作等。

	4.凤凰缓存系统对redis的集群分片优化
		Redis 3.0 的一些分片特性:
			1.节点自动发现
			2.slave->master选举,集群容错
			3.在线分片
			4.基于配置的集群管理
			5.ASK转向/MOVED转向机制
		Redis 3.0 集群中,采用slot的概念,一共分成16384个槽。对于每个进入redis的键值对,根据key进行散列,分配到这16384个slot中的其中一个。使用的是hash算法也比较简单,
	  就是crc16后16384取模。
	  	整套的redis集群基于集群中的每个node负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或者减少node节点时,需要将16384个槽
	  做个再分配,槽中的键值也要迁移。这个过程,需要人工介入。
	  	为了增加集群的可访问性,官方推荐的是将node配置成主从结构,即一个master主节点,挂多个slave从节点。这时,如果主节点失效,redis cluster会根据选举算法从slave节点
	  中选择一个上升为主节点,整个集群继续对外提供服务。这非常类似之前的sentinal监控架构的主从架构,只是redis cluster本身提供了故障转移容错的能力。
	  	通过这样的设计,redis实现了完整的集群功能。但是,整个集群功能比较弱,表现在以下几个方面:
	  	1.redis集群之间只对redis的存活负责,而不对数据负责。这样,当客户端提交请求之后,如果这key不归这个服务器处理,就会返回move命令,需要客户端自行实现跳转,增加了
	  	客户端的复杂度。
	  	2.当redis需要迁移或槽重新分配的时候,需要人工介入,发送命令操作。
	  	3.集群进行分片,所有的key被分散在各个节点上。之前说过集群之间只管死活和槽分配,不处理数据,所以所有的多key操作(事务,mget,mset之类的)的操作不能再用。

	5.客户端在运维中的作用
		凤凰缓存系统应用层客户端在解决运维方面最大的作用3个:
		1.系统调整后Proxy接入地址改变的问题,平滑切换的问题
		2.类似keys这样的命令导致redis阻塞的问题
		3.应用操作异常的发生地详细信息不透明的问题

		先来看第一个问题,我们提供的是一套完整的配置管理系统---分布式的配置系统。服务器端对redis接入的操作和修改都会通知配置中心,然后,再由配置中心发到所有的客户端,
	  客户端接收到配置更新后,会修改自己的连接平滑过渡。这样客户端就可以在不重启的情况下动态切换连接,另外,在客户端有个连接池实现,当老的连接重新回到连接池后,就会被
	  销毁掉。这样,客户端的切换是平滑的,不会因为切换客户端导致抖动。
	  	再来看第二个问题。由于redis是单进程单线程的特性,不适合做密集的cpu计算,但是当许多开发对命令部署的时候,经常会导致redis的cpu使用率100%。悲催的是,当这样的
	  情况发生后,就不会处理任何客户端的请求了,它要等到当前的这个任务执行完成之后才会继续下一个任务。针对这个问题,我们将容易打满cpu的命令和普通命令区分开来。
	  	最后一个问题,大部分异常使用中,对于异常发生的现场情况,开发人员都可以通过自己的日志看到。我们在客户端中记录下了完整的操作日志信息。

	6.凤凰缓存系统在redis运维上的工具
		在redis使用过程中,经常问,哪些key是热key?当前redis访问量比较高,看下是哪些命令导致的?但是redis本身并没有提供这些命令,提供了一个monitor命令,可以将当前
	  的redis操作全部导出来。当开始监控redis的时候,就会发送monitor命令,然后,redis会将它接收到的命令完整的源源不断的发送出来。我们进行分析。monitor命令有一定的
	  性能损耗,一般情况下不建议打开,只在需要分析的时候打开。
	  	还有redis数据迁移。写了个程序,冒充redis从机,然后发送从机命令同步数据,主机把这些数据发送到程序之后,程序对这些数据进行解析。

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值