微服务架构的简单实现-Stardust

微服务架构,一个当下比较火的概念了。以前也只是了解过这方面的概念,没有尝试过。想找找.NET生态下面是否有现成的实现,可是没找到,就花了大半个月的闲暇时间,遵循着易用和简单,实现了一个微服务框架,我叫它Stardust(星尘),Stardust有三个项目组成:

Stardust.Server是服务端组件,Stardust.Client是客户端组件,Stardust.ConfigCenterWeb是配置中心,是个MVC web站点。

本文目录:

  1. 基础模型和组件
  2. 服务节点与配置中心
  3. 客户端与配置中心
  4. 客户端选择节点(版本和负载)
  5. 结束

一、基础模型和组件



在Stardust里,只用了两个模型,ServerNode和NodeEvent。

ServerNode表示一个服务节点,包括唯一Id,服务名称,节点地址,服务版本,节点状态,权重,动态权重,最后心跳时间。

动态权重这个属性是在实现负载的时候加上去的,并不是设计的时候就想到的,微服务架构中,服务节点应该是可动态的。

节点状态有三个,Normal(正常),Disconnect(断开),Disabled(禁用)。

这里要确定两个概念的,服务节点是指一个服务实例,比如IIS上的一个站点,是个具体的东西;服务则是一个抽象的分组概念,可以为一个服务部署多个服务节点,ServerNode中的服务名称,就是说的这个概念。

NodeEvent表示服务节点的一个变动事件,包括事件Id(自增,这个后面是有用的),变更的服务节点,事件类型。

事件类型有四个,Register(注册),Logout(下线),Update(修改),Delete(删除),这些事件都什么情况会触发,下面再交代。

基础组件也是不多的。

任务调度器:是之前写过的一个组件TaskScheduler,不想多一个引用,就把源码放进来了。

序列化:用的ServiceStack.Text,这是引用的唯一一个外部类库。

HTTP通信:本来想自己封装或引用第三方的,没想到ServiceStack.Text里有个HttpUtils,写了好多扩展方法应用到String上,正好!

基础的模型和组件就这么多了。

二、服务节点与配置中心



.net版的服务端,是要架设到web服务器(IIS)上的。是需要一个web站点,mvc也好,webfrom也好,asp.net空web项目也行,都可以用Stardust.Server成为一个服务节点。

Stardust.Server中提供一个ServiceRouteHttpModule的HTTP模块,用这个模块从IIS接管对Stardust服务的请求,所以第一步要在web.config里添加模块

  <system.webServer>
    <modules>
      <add name="StardustServiceRoute" type="Stardust.Server.ServiceRouteHttpModule"/>
    </modules>
  </system.webServer>

Stardust.Server中提供了一个空接口IStardustService,我们提供的服务类继承这个接口,写服务方法实现就行了:

    public class User
    {
        public string Name { get; set; }
    }

    //[StardustName("User")] //默认是类名,如果类名以Service结尾,会把Service去掉
    public class UserService : IStardustService
    {
        //[StardustName("hello")] //默认是方法名,可以StardustNameAttribute来自定义
        public string Hello(string name, int count = 1)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < count; i++)
            {
                sb.AppendFormat("Hello,{0}!{1}", name, Environment.NewLine);
            }
            return sb.ToString();
        }
        public Task<string> HelloAsync()
        {
            return new Task<string>(() =>
            {
                return "Hello World";
            });
        }

        public List<User> UpdateUsers(List<User> list)
        {
            foreach (var user in list)
            {
                user.Name = "Updated:" + user.Name;
            }
            return list;
        }
    }

服务方法是指那些本类声明(基类不算)的、公开的、有返回值的实例方法。

方法的参数有些要求:

可以无参,如:FunName()

可以是多个简单类型,如:FunName(int id, string name, DateTime dt)

可以是一个复杂类型,如:FunName(SomeParamsObj ps)

ref和out等不做考虑......

服务的注册放在Global里是个好地方:

注册的时候,有个版本号,这个版本号在配置中心是可以改的,根目录默认是"",如果在站点下面添加应用程序就要指定应用程序的根目录了。

服务节点的实现,编码方面就是上面三步:1添加Http模块,2继承IStardustService写服务方法, 3注册服务。

当服务节点启动了之后,就会和配置中心互动了:

1.当应用程序启动的时候,会向配置中心注册服务节点,配置中心根据服务名称和地址判断是否是新节点,如果是新的,会添加到数据库,如果不是,会修改节点信息,两种情况都会产生一条注册事件。

2.启动之后,服务节点定时(5s)向配置中心发送心跳请求,配置中心会更新节点的最后心跳时间;

3.当服务节点关闭的时候,可以向配置中心发送下线请求,比如在Application_End中,但是这个并不靠谱,下线的代码可有可无,所以再求他法;

4.配置中心定时(10s)检测那些状态是正常但是最后心跳时间已经大于8s的节点,如果服务节点返回约定的值,就说明节点是活着,配置中心更新节点的心跳时间,否则会修改结点的状态为断开,同时生产一条下线事件。

5.配置中心定时(15s)检测那些状态是断开(只是断开的,禁用的不检测),最近15s都不心跳的服务节点,试图把服务节点拉起,如果拉起成功,就会马上生成节点注册事件(当应用程序启动了也会生成这个事件,可能会重复,不过没关系,客户端会处理好的)

经过这么你来我往的交互,服务节点在配置中心就活起来了:

上面都是自动触发的事件,在配置中心里的操作,也是有事件产生的:

1.如果一个节点不存在,可以手动先添加,这个时候是没有事件的,新加后节点的状态是断开的,这个节点将来可能会被上面第5点说的由配置中心拉起来,也可能应用程序启动自己注册。

2.对一个已存在的节点,可以修改地址、版本、状态、和权重,修改完成会产生一条修改事件。

3.删除会产生删除事件。

这些自动或手动生成的事件,是为客户端获取最新服务节点状态使用的。

三、客户端与配置中心



Stardust是没有路由的,是客户端直接调用服务的,所以客户端有发现和选择服务节点的能力。

由于服务信息都在配置中心,所以客户端在调用服务之前,要设置一下配置中心的地址:

StardustClient.SetConfigCenterUrl("http://localhost:85");

一个客户端可能会调用多个服务。

在客户端,维护着一个字典,key是服务名称,value的结构如下:

{
    "MaxEventId":287,  //最新服务节点事件Id
    "LastInvokeTime":"2017-4-1 02:05:08", //客户端最后调用时间
    "Nodes":[
        {
            "Id":1,
            "ServiceName":"server1",  //服务名
            "Address":"127.0.0.1:8001", //服务节点地址
            "Version":"1.25",  //版本
            "Status":1,  //状态
            "Weight":0, //权重
            "DynamicWeight":0 //动态计算出来
        }
    ]  //节点列表
}

当调用一个服务的时候,先看看是不是已经获取了该服务的信息,如果没有,会从配置中心拉取过来这个服务下面所有正常的服务节点信息,然后存起来。这些信息也包含当前服务的节点事件的最大Id。

当调用一个服务的时候,客户端会在本地更新LastInvokeTime,纪录最后调用时间。

客户端会定时(6s)检测那些在1天内有调用过的服务,然后从配置中心拉去这些服务下面的节点事件(从本地MaxEventId开始),如果有事件的话,就把这些事件依次应用到对应的节点上,同时更新MaxEventId。

应用事件的逻辑:

    var localNode = group.Nodes.FirstOrDefault(x => x.Id == evt.ServerNodeId);
    if (localNode != null)
    {
        switch (evt.EventType)
        {
            case Common.Enum.NodeEventType.Logout:
            case Common.Enum.NodeEventType.Delete:
                localNode.Status = Common.Enum.ServerNodeStatus.Disabled;
                break;
            case Common.Enum.NodeEventType.Update:
            case Common.Enum.NodeEventType.Register:
                localNode.Status = evt.ServerNode.Status;
                localNode.Address = evt.ServerNode.Address;
                localNode.Version = evt.ServerNode.Version;
                localNode.Weight = evt.ServerNode.Weight;
                break;
            default:
                break;
        }
    }
    else
    {
        if (evt.EventType == Common.Enum.NodeEventType.Register || evt.EventType == Common.Enum.NodeEventType.Update)
        {
            group.Nodes.Add(evt.ServerNode);
        }
    }

当一个服务正好下线了,状态还没有同步过来,这个时候客户端调用了就会有异常的,当在远程主机主动拒绝连接的时候,会在本地修改节点为禁用状态,这样就不会反复调用了,如果那个节点后来又好了,状态也是会通过事件同步过来的,然后这个节点就又可用了。

客户端获得了所调用的服务的节点信息,就可以直接调用服务了。

var client = new StardustClient("server1", "1.1");
var str = client.Invoke<string>("user", "hello", new { name = "Jack", count = 2 });
//var task=client.InvokeAsync<string>("user", "hello", new { name = "Jack", count = 2 }); // 或者异步调用

四、客户端选择节点(版本和负载)



服务节点注册的版本是固定的,但是客户端的选择应该是灵活的。

基于这个的考虑,我把版本分成两部分 x.y ,x和y都是整数,x表示不兼容版本,y表示可兼容版本。

如果一个服务有以下节点:

node_a   2.23

node_b   2.23

node_c   2.21

node_d  2.20

node_e  1.24

在客户端实例化的时候,版本号可以如上面那样"1.1"指定版本号,更灵活的是在可兼容版本y可以是*,可以在y后面带上+,-,>,<这四个符号:

2.*      :会选择x等于2,兼容版本里面最高一组版本,[ node_a 2.23 , node_b 2.23 ]

2.21+  :会选择x等于2,y大于等于21的一组兼容版本,[node_a 2.23 ,  node_b 2.23 , node_c 2.21]

2.21-   : 会选择x等于2,y小于等于21的一组兼容版本,[node_c 2.21 , node_d 2.20]

1.24<  : 会选择x等于1,y小于24的兼容版本,列表中没有符合的节点,[]

1.20>  : 会选择x等于1,y大于20的兼容版本,[node_e 1.24]

我们根据版本号筛选出了可用节点列表,下一步是根据权重确定具体的调用节点。

如果可用节点列表为空,就抛出异常;如果只有一个节点,那就是它了;如果不止一个,就要先计算他们的权重。

假设有三个节点,默认权重都是0,这个时候每个节点的动态权重都是1/3,所以选择的概率是相等的。

如果其中一个节点权重是2,另外两个是0,那么先算出为全部为零的平均权重1/3,他们总的动态权重是: sum=2+ 1/3 + 1/3,他们的动态权重则分别是 : 2/sum,(1/3)/sum,(1/3)/sum。

获取到动态权重,经过随机数定位区间,就可以确定具体的节点了。

每次实例化客户端的时候,都会通过版本号筛选和计算动态权重,这样在增删改服务节点之后,就反映到客户端了。

五、结束



起始于2017.3.16凌晨4点左右,突然醒来画了个图,上面所说的实现,大都是那1个小时整理的思路:

附源码地址 http://git.oschina.net/loogn/Stardust

时间: 2024-10-09 14:11:49

微服务架构的简单实现-Stardust的相关文章

孢子框架-网络游戏架构与微服务架构简单对比

笔者十年前做过网络游戏,当第一次看到微服务架构就发现它和网络游戏架构很像,如下图: 先来简单介绍一下这个网游架构,有些东西记不清了,如今的网游大牛看到可别丢砖头. 用户下载网游客户端,登录网游,首先会执行登录服务,登录服务主要就是给你分配一个网关,因为网关后面连接的才是真正的游戏服务器.登录后,进入游戏,发出指令,比如你移动到某个位置,这个指令会先发送到网关,然后再由网关识别发送到“移动系统”服务,移动系统计算后再经由网关发送给玩家客户端,玩家客户端执行一个动画让你移动到某个位置. 假如子服务间

一个最简单的微服务架构

前言 微服务架构一般会有一个开放网关作为总入口,负责分发流量到实际的应用服务上.下面看图. 架构图 项目结构 这个架构分别由反向代理nginx,注册中心zookeeper,开放网关gateway,和两个服务goodservice,priceservice组件而成.为了方便测试,我把建了两个一样的gateway和goodservice.而common作为公共的二方包存在,也是为了简单起见,gateway和service引用同一个二方包. nginx nginx除了作为反向代理,也具有负载均衡的功能

Java高可用集群架构与微服务架构简单分析

前言 可能大部分读者都在想,为什么在这以 dubbo.spring cloud 为代表的微服务时代,我要还要整理这种已经"过时"高可用集群架构? 本人工作上大部分团队都是7-15人编制的开发团队,对应的公司项目也大都是中小型项目,最大的项目 PV/UV 也就只有 10w/2w .在这样的场景下,中小型公司一般都是创业起步没多久,大部分都需要本着"开源节流"."以最小的成本把产出最大化".微服务架构相比于高可用集群架构,个人理解,对于技术团队的成员

用友iuap云运维平台支持基于K8s的微服务架构

什么是微服务架构? 微服务(MicroServices)架构是当前互联网业界的一个技术热点,业内各公司也都纷纷开展微服务化体系建设.微服务架构的本质,是用一些功能比较明确.业务比较精练的服务去解决更大.更实际的问题.该架构强调的一些准则:单一职责.协议轻量.进程隔离.数据分离.独立部署.按需伸缩. 什么是Kubernetes? Kubernetes是Google开源的容器集群管理系统,其提供应用部署.维护. 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用,其主要功能:

微服务架构

互联网保险O2O平台微服务架构设计 关于架构,笔者认为并不是越复杂越好,而是相反,简单就是硬道理也提现在这里.这也是微服务能够流行的原因,看看市场上曾经出现的服务架构:EJB.SCA.Dubbo等等,都比微服务先进,都比微服务功能完善,但它们都没有微服务这么深入民心,就是因为他们过于复杂.简单就是高科技,苹果手机据说专门有个团队研究如何能让用户更加简单的操作.大公司都是由小公司发展起来的,如果小公司在开始技术选型时感觉某个框架费时费力就不会选择,而小公司发展到大公司的过程,一般也伴随着系统不断优

深解微服务架构:从过去,到未来|架构(2015-07-15)

随着用户需求个性化.产品生命周期变短,微服务架构是未来软件软件架构朝着灵活性.扩展性.伸缩性以及高可用性发展的必然方向.同时,以Docker为代表的容器虚拟化技术的盛行,将大大降低微服务实施的成本,为微服务落地以及大规模使用提供了坚实的基础和保障. 微服务的诞生   微服务架构(Microservice Architect)是一种架构模式,它提倡将单块架构的应用划分成一组小的服务,服务之间互相协调.互相配合,为用户提供最终价值.每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟

.NET Core 实践:微服务架构的优点

微服务现在已经是各种互联网应用首选的云架构组件,无论是 BAT 还是 滴滴.美团 ,微服务都是重要的一环. 相对于微服务,传统应用架构有以下缺点: 1. 业务代码混杂,团队成员职责边界不清,团队协作体验不佳,开发效率低下. 传统应用架构中,各个业务模块代码都存在于同一个应用当中,各个业务模块之间交互逻辑复杂,代码统统混在一起,难免出现要去别人代码里改代码的情况 2. 代码耦合度高,日趋臃肿,难以重构,维护成本越来越高. 感受过被F12支配的恐惧吗? 3. 容错能力弱,单点故障引发全局崩溃. 4.

微服务架构(Microservice Architecture)

之前一段时间,有听部门架构说起接下来公司要使用微服务架构来研发系统,当时没怎么在意,因为是第一次听说微服务这个名词(果然无知者无畏啊):正好赶上五一假, 我自告奋勇的,接了编写微服务架构培训文档这个任务(也许因为我是文科生,文笔稍微好点).五一假期三天,基本都是在看资料,梳理思路以及编写接下来的培训文档中度过. 下面,就说说我这几天的一些收获吧:先说说资料来源吧:有架构给我的一些资料,以及自己百度和论坛.社区找来的一些资料,权当做一个总结式的简介... 目录如下: 一.微服务架构介绍 二.出现和

微服务架构——不是免费的午餐

当我開始了解<微服务架构>的时候,我发现里面的中文文章是相当的少,于是開始试着翻译一些文章,比方这一篇<微服务--不是免费的午餐>.这篇文章是在某次讨论结束后听到的,和之前相似的是这样的差别有点相似于之前说的微内核与宏内核的差别. 译文例如以下: 文章是由Contino公司的CTO,Benjamin Wootton写的.Contino是一家在伦敦的咨询公司,专注于DevOps和持续支付. Microservices are a style of software architect