服务发现并无怎样的高深莫测,它的原理再简单不过。只是市面上太多文章将服务发现的难度妖魔化,读者被绕的云里雾里,顿觉本身智商低下不敢高攀。git
服务提供者是什么,简单点说就是一个HTTP服务器,提供了API服务,有一个IP端口做为服务地址。服务消费者是什么,它就是一个简单的进程,想要访问服务提供者提供的服务来干一些事情。一个HTTP服务器既能够是服务提供者对外提供服务,也能够是消费者须要别的服务提供者提供的服务,这就是服务依赖,没有你我就不是我本身。复杂的服务甚至有多个服务依赖。github
服务发现有三个角色,服务提供者、服务消费者和服务中介。服务中介是联系服务提供者和服务消费者的桥梁。服务提供者将本身提供的服务地址注册到服务中介,服务消费者从服务中介那里查找本身想要的服务的地址,而后享受这个服务。服务中介提供多个服务,每一个服务对应多个服务提供者。redis
服务中介就是一个字典,字典里有不少key/value键值对,key是服务名称,value是服务提供者的地址列表。服务注册就是调用字典的Put方法塞东西,服务查找就是调用字典的Get方法拿东西。数据库
当服务提供者节点挂掉时,要求服务可以及时取消注册,比便及时通知消费者从新获取服务地址。服务器
当服务提供者新加入时,要求服务中介能及时告知服务消费者,你要不要尝试一下新的服务。网络
Redis里面有丰富的数据结构,拿来存储服务字典再合适不过了。对每个服务名称,咱们用一个set结构存储服务的IP:Port字符串。若是服务提供者加入,调用sadd命令加入服务地址,若是服务挂掉,调用srem命令移除服务地址。对服务消费者使用smembers指令获取全部服务地址而后在消费进程里随机挑一个,或者使用srandmemember指令直接获取随机服务地址。数据结构
这个时候你也许会表示怀疑,服务发现真这么简单么?答案是还差一点,关于上面的这个解决方案有几个问题。分布式
第一个问题是服务提供者进程若是被kill -9暴力杀死,不能主动调用srem命令怎么办?学习
这个时候服务列表中多了一个黑地址指向了不存在的服务而消费者彻底不知道,这个时候服务中介就成了黑中介了。那该怎么办呢?线程
咱们引入服务保活和检查机制,并更换数据结构。服务提供者须要每隔5秒左右向服务中介汇报存活,服务中介将服务地址和汇报时间记录在zset数据结构的value和score中。服务中介须要每隔10秒左右检查zset数据结构,踢掉汇报时间严重落后的服务地址项。这样就能够准实时地保证服务列表中服务地址的有效性。
第二个问题是服务列表变更时如何通知消费者。有两种解决方案。
第一种是轮询,消费者须要每隔几秒查询服务列表是否有改变。若是服务不少,服务列表很大,消费者不少,redis会有必定压力。因此这时候能够引入服务列表的版本号机制,给每一个服务提供一个key/value设置服务的版本号,就是在服务列表发生变更时,递增这个版本号。消费者只须要轮询这个版本号的变更便可知道服务列表是否发生了变化。由于服务列表比较稳定,仅在网络严重抖动的状况下才会频繁发生变更,因此redis几乎没有压力。
第二种是采用pubsub。这种方式及时性要明显好于轮询。缺点是每一个pubsub都会占用消费者一个线程和一个额外的redis链接。为了减小对线程和链接的浪费,咱们使用单个pubsub广播全局版本号的变更。所谓全局版本号就是任意服务列表发生了变更,这个版本号都会递增。接收到版本变更的消费者再去检查各自的依赖服务列表的版本号是否发生了变更。这种全局版本号也能够用于第一种轮询方案。
第三个问题是redis是单点的,若是挂掉了怎么办?
这是个大问题。正是由于这个问题的存在,流行的服务发现系统都是使用分布式数据库zookeeper/etcd/consul等来做为服务中介,它们是分布式的多节点的,挂掉了一个节点不要紧,系统仍然能够正常工做。
那若是整个zk集群挂掉会怎样呢?其实每一个服务消费者在本地内存里都会存一份当前的服务列表,即便服务中介集群挂掉,也是可使用当前的服务列表正常工做的。
那redis做为服务中介就真的不靠谱了么?其实还有个redis-sentinel能够消除redis的单点问题,redis-sentinel能够在主节点挂掉的时候,自动升级从节点为主节点。因此拿redis干这件事也是能够的。用redis干服务发现确实很是简单,虽然这种方式很是不流行。
上面提到服务提供者简单来讲就是HTTP服务器,其实服务多种多样。能够是数据库服务,能够是RPC服务,能够是UDP服务等等。
若是是MySQL数据库,那如何将MySQL服务注册到服务中介呢?原生的MySQL可没有提供这样功能。通常作法是提供一个Agent代理去注册。这个代理除了将服务地址注册到服务中介外,还须要监控MySQL的健康情况,以便当MySQL宕机时能及时切换到新的MySQL服务地址。通常这个Agent为了节省资源而不止监控一个数据库,它能够同时监控多个数据库,甚至是多种数据库。
服务发现通常只是用来注册和查找服务列表这样一个比较单纯的功能。不过现代的服务发现系统还会集成服务配置管理功能。这样能够实现服务配置的实时重加载。原理也很简单,就是对于每个服务项,服务中介还会存储一个单独的key/value用来存储这个服务的配置信息。当这个配置项在后台被修改时,服务中介会实时通知相关服务器变动配置信息。好比数据库地址变更,业务参数修改等。
为了便于服务管理,通常服务发现还会提供一个服务管理后台,用于管理人员查看服务集群的状态。若是服务注册和汇报时提供冗余的配置信息,服务管理后台就能够呈现更为详细的服务信息。服务管理后台还能够将全部的服务依赖组织起来,呈现出一颗漂亮的服务依赖树。
小编在闲暇之余基于Redis实现了一个简单的服务发现系统Captain。读者能够去github上下载这个项目进行学习。我除了编写了服务发现的服务器以外,客户端sdk也一块作了开发,可能不太稳定,但愿读者体谅,不要用于线上的业务系统。
在Captain这个项目里,个人服务发现服务器将Redis提供的服务作了一层封装,对外提供HTTP API进行服务的注册和查找,没有使用上文提到的pubsub功能。
本文由博客一文多发平台 OpenWrite 发布!