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

Stardust.Server是服务端组件,Stardust.Client是客户端组件,Stardust.ConfigCenterWeb是配置中心,是个MVC web站点。
本文目录:
一、基础模型和组件
在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 = )
{
StringBuilder sb = new StringBuilder();
for (int i = ; 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 = });
//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
更新日志:
2017-4-13日更新:
java版客户和服务端:http://git.oschina.net/loogn/stardust4j
2017-4-17日更新:
.net版去掉TaskScheduler ,改用自己封装的简单的Timer
2017-4-24日更新:
.net版添加上传服务契约功能

2017-4-27更新:
2017-4-28更新:
1,服务节点加入平台信息;
2,优化契约,并可以调用测试

微服务架构的简单实现-Stardust的更多相关文章
- 简单记录下SpringCloud的微服务架构和一些概念
一.微服务的注册与发现——Eureka 和许多分布式设计一样,分布式的应用一般都会有一个服务中心,用于记录各个机器的信息.微服务架构也一样,我们把一个大的应用解耦成这么多个那么多个服务,那么在想要调用 ...
- Java高可用集群架构与微服务架构简单分析
序 可能大部分读者都在想,为什么在这以 dubbo.spring cloud 为代表的微服务时代,我要还要整理这种已经"过时"高可用集群架构? 本人工作上大部分团队都是7-15人编 ...
- 【DDD/CQRS/微服务架构案例】在Ubuntu 14.04.4 LTS中运行WeText项目的服务端
在<WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例>文章中,我介绍了自己用Visual Studio 2015(C# 6.0 with .NET Frame ...
- WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例
最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架 ...
- 网易蜂巢微服务架构:用RabbitMQ实现轻量级通信
本次分享内容由三个部分组成: 微服务架构与MQ RabbitMQ场景分析与优化 RabbitMQ在网易蜂巢中的应用和案例分享 1微服务架构与MQ 微服务架构是一种架构模式,它将单体应用划分成一组微小的 ...
- Atitit.架构设计趋势 设计模式 ---微服务架构 soa
Atitit.架构设计趋势 设计模式 ---微服务架构 soa 什么是微服务架构?1 .微服务与SOA的关系 :微服务架架构师面向服务架构(SOA)的一种特定实现1 微服务与康威定律2 微服务的一些 ...
- 微服务架构下分布式Session管理
转载本文需注明出处:EAII企业架构创新研究院(微信号:eaworld),违者必究.如需加入微信群参与微课堂.架构设计与讨论直播请直接回复此公众号:“加群 姓名 公司 职位 微信号”. 一.应用架构变 ...
- NET实现的DDD、CQRS与微服务架构
WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA).我经过两周业余时间 ...
- Spring Cloud搭建微服务架构----前言
前言 微服务并不神秘,只是在互联网技术发展过程中的一个产物,整个架构系统随着客户端的多样性,服务越来越多,devops的发展而产生的架构变种. 许多公司,通过采用微处理结构模式解决单体应用的问题,分解 ...
随机推荐
- Redis 学习之事务处理
Redis事务机制 在MySQL等其他数据库中,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行. Redis目前对事物的支持相对简单.Redis只能保证一个client发起的事务中的命令可 ...
- 《JavaScript DOM 编程艺术》
前几天京东买了一本书,在豆瓣上好评如潮,买下了啃一啃,书名<JavaScript DOM 编程艺术>,在好好深造一下javaScript.一边啃,一边敲.当然应该要做好笔记.一些简单的就看 ...
- 前端Cookie与Session的区别
我们在实际生活中总会遇到这样的事情,我们一旦登录(首次输入用户名和密码)某个网站之后,当我们再次访问的时候(只要不关闭浏览器),无需再次登录.而当我们在这个网站浏览一段时间后,它会产生我们浏览的记录, ...
- masm32V11配置
本文写给学汇编语言程序设计刚起步的吧友.适用Windows操作系统.已入门的吧友请绕道. (1)masm32开发包的下载 要用汇编语言编程,首先得有个开发工具,汇编语言开发工具有多种,但本文仅介绍ma ...
- 利用Flume采集IIS日志到HDFS
1.下载flume 1.7 到官网上下载 flume 1.7版本 2.配置flume配置文件 刚开始的想法是从IIS--->Flume-->Hdfs 但在采集的时候一直报错,无法直接连接到 ...
- Linux学习之Linux目录及文件系统
以往的 Windows 一直是以存储介质为主的,主要以盘符(C 盘,D 盘...)及分区来实现文件管理,然后之下才是目录,目录就显得不是那么重要,除系统文件之外的用户文件放在任何地方任何目录也是没有多 ...
- Javascript学习九
计时器setInterval() 在执行时,从载入页面后每隔指定的时间执行代码. 语法: setInterval(代码,交互时间); 参数说明: 1. 代码:要调用的函数或要执行的代码串. 2. 交互 ...
- Java重写equals()和hashCode()
1.何时需要重写equals() 当一个类有自己特有的 ”逻辑相等”概念(不同于对象身份的概念). 2.设计equals() [1]使用instanceof操作符检查 ”实参是否为正确的类型”. [2 ...
- 如何快速的学习selenium工具
分享即快乐. 最近几年,软件测试工程师一度成为热门职业,作为测试员也是倍感压力.作为测试员来说,仅仅会手工测试让职业生涯陷入瓶颈.于是工作之余充电,学习了自动化测试工具selenium,打算进阶中高级 ...
- 基于fiddler的APP抓包及服务端模拟
在HTTP接口的测试过程中,一般我们会按照如下的步骤进行: 1)测试环境的准备 2)HTTP消息体的构造 3)HTTP消息的发送及断言 如果我们可以拿到项目组的接口文档,并且HTTP后台服务是可以工作 ...