上一篇文章简单介绍了ZooKeeper,讲了分布式中,每个微服务都会部署到多台服务器上,那服务之间的调用是怎么样的呢?如图:

  1、集群A中的服务调用者如何发现集群B中的服务提供者呢?

  2、集群A中的服务调用者如何选择集群B中的某一台服务提供者去调用呢?

  3、集群B中某台机器下线,集群A怎么避免下次调用不在使用这台掉线的机器?

  4、集群B提供的某个服务如何获知集群A中哪些机器正在消费该服务?

  这篇文章写两个微服务,将两个服务部署到多台服务器中 ,通过将服务注册到ZooKeeper中,实现服务之间的调用。最终实现下面的ZooKeeper节点,然后通过服务节点下的地址,进行远程调用。

一、服务实现

  一个获取订单的服务和顾客信息的服务,服务之间调用是通过订单服务查询此订单顾客的信息。 涉及的两个实体Order和Customer.

public class Custormer //顾客实体
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
 public class Order //订单实体
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string Goods { get; set; }
public string Address { get; set; } public Custormer Custormer;
}

订单实体中包含此订单顾客的引用。

创建一个订单微服务项目,实现获取订单列表的服务:

        [Route("Order/GetOrders")]
public async Task<List<Order>> GetOrders()
{
List<Order> orders = new List<Order>(); Order order = null;
HttpClient client = new HttpClient();
for (var i = ; i < ; i++)
{
order = new Order();
order.Address = "浙江省杭州市拱墅区北部软件园" + i;
order.CustomerId = i;
order.Goods = "麻辣香锅" + i;
order.Id = i;
//这里需要调用获取顾客信息服务,获取顾客信息。这里先写null
order.Custormer = null; orders.Add(order);
}
return orders;
}

创建一个顾客微服务项目,实现获取顾客信息的服务:

    public class CustomerController : ControllerBase
{
[Route("Customer/GetCustomer")]
public Custormer GetCustomer(int Id)
{
return new Custormer() { Id=Id,Name="MicroHeart"+Id,Phone=""};
}
}

二、服务注册到ZooKeeper中

  两个服务写完了,上篇讲的在服务启动的时候,需要将服务注册到ZooKeeper中,服务调用者启动的时候,将服务提供或者信息从注册中心下拉倒服务调用者本机缓存。当需要调用服务时,从本地缓存列表中找到服务提供者的地址列表,基于某种负载均衡策略(随机、轮询等)选择一台服务器发起远程调用。

  在两个项目中的Startup构造函数中,调用下面方法,保证服务启动时就在ZooKeeper中注册服务。

public void InitZooKeeper()
{
var MyApp = "/MyApp";
//创建ZooKeeper 我就不在本地创建了 客户端和服务端都在本地的话,会造成误会
ZooKeeper zooKeeper = new ZooKeeper("118.24.96.212:2181", , new MyWatcher()); //创建 MyApp节点,数据为:MyAppData 权限控制为:开放 节点类型为:持久性节点
if (zooKeeper.existsAsync(MyApp) != null)
zooKeeper.createAsync(MyApp, Encoding.UTF8.GetBytes("MyAppData"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //通过反射获取所有Controller下的方法,在获取方法上的Route特性,通过特性设置ZooKeeper节点。
Dictionary<string, List<string>> serviceAndApiPaths = new Dictionary<string, List<string>>();
var types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes();
foreach (var type in types)
{
if (type.BaseType == typeof(ControllerBase))
{
var methods = type.GetMethods();
foreach (var method in methods)
{
foreach (var customAttribute in method.CustomAttributes)
{
if (customAttribute.AttributeType == typeof(RouteAttribute))
{
var serviceName = type.Name.Replace("Controller", "Services");
if (!serviceAndApiPaths.Keys.Contains(serviceName))
{
List<string> apiPaths = new List<string>();
                     //因为Route的值带"/" 会导致ZooKeeper认为是节点符号,所以要转换一下
apiPaths.Add(customAttribute.ConstructorArguments[].ToString().Replace("/","-"));
serviceAndApiPaths.Add(serviceName, apiPaths);
}
else
serviceAndApiPaths[serviceName].Add(customAttribute.ConstructorArguments[].ToString().Replace("/", "-"));
}
}
}
}
} //将这些接口列表 放到MyApp节点下
foreach(var item in serviceAndApiPaths)
{
//创建 服务节点,为持久性节点
if (zooKeeper.existsAsync($@"{MyApp}/{item.Key}") != null)
zooKeeper.createAsync($@"{MyApp}/{item.Key}", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
foreach (var apiPath in item.Value)
{
//创建 Api节点,为持久性节点
if (zooKeeper.existsAsync($@"{MyApp}/{item.Key}/{apiPath}") != null)
zooKeeper.createAsync($@"{MyApp}/{item.Key}/{apiPath}", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //创建 Ip+port 节点,为临时性节点(由于我本地 不能通过我局域网Ip地址访问,所以我写死127.0.0.1) 写成临时节点 是因为
//当这个客户端与服务端断开时,对应的节点自动消失了。
//IPAddress[] IPList = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList;
//string currentIp = IPList.Where(ip=>ip.AddressFamily==System.Net.Sockets.AddressFamily.InterNetwork).Last().ToString();
string currentIp = "127.0.0.1";
if (zooKeeper.existsAsync($@"{MyApp}/{item.Key}/{apiPath}/{currentIp}:{Configuration["Port"]}") != null)
zooKeeper.createAsync($@"{MyApp}/{item.Key}/{apiPath}/{currentIp}:{Configuration["Port"]}", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}
}
}

这里简单介绍一下其中使用到的ZooKeeperAPI。

  创建ZooKeeper的构造函数:ZooKeeper(string connectstring, int sessionTimeout, Watcher watcher, bool canBeReadOnly = false);

      connectstring:ZooKeeper服务的地址和端口

      sessionTimeout:连接超时时间,毫秒

      watcher:观察者,相当于一个触发器,自己实现process方法

      canBeReadOnly :是否是只读权限

  创建节点:Task<string> createAsync(string path, byte[] data, List<ACL> acl, CreateMode createMode);

      path:节点路径 必须以“/”开头

      data:节点的数据,数据大小不建议超过2M,数据格式为字节数组。

      acl:权限相关

      createMode:节点的类型(上篇文章讲到的四种类型 持久型节点、持久有序型节点、临时型节点、临时有序型节点)

  获取子节点:Task<ChildrenResult> getChildrenAsync(string path, Watcher watcher);

      path:节点路径 必须以“/”开头

      watcher::观察者,相当于一个触发器

上面的代码中服务的端口我没有写死,是通过获取appsettings.json文件中的Port参数值设置。配置文件和Program中的代码如下。我设置顾客服务端口为5000,订单服务端口为5100

{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Port": "",
"AllowedHosts": "*"
}
        public static void Main(string[] args)
{
//获取配置
var config = new ConfigurationBuilder()
//需要先设置路径 然后在路径中找到json文件
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($"appsettings.json", true, true)
.Build(); //设置启动地址和端口号
CreateWebHostBuilder(args)
.UseUrls("http://127.0.0.1:" + config["Port"])
.UseConfiguration(config)
.Build()
.Run();
}

三、启动服务

  这里介绍一个工具ZooInspector,下载地址,通过它可以很容易查看ZooKeeper里面的内容。

  通过命令启动两个服务,通过ZooInspector看到ZooKeeper结构如下:

如果你关闭一个服务窗口,那对应的服务下面的IP列表就会消失,因为这个节点是临时节点。

现在我们已经实现了,服务的注册,现在可以回头来继续写刚才还没有完成的订单调用。需改获取订单列表里代码如下:

public async Task<List<Order>> GetOrders()
{
List<Order> orders = new List<Order>(); Order order = null;
HttpClient client = new HttpClient();
for (var i = ; i < ; i++)
{
order = new Order();
order.Address = "浙江省杭州市拱墅区北部软件园" + i;
order.CustomerId = i;
order.Goods = "麻辣香锅" + i;
order.Id = i;
//连接ZooKeeper
ZooKeeper zooKeeper = new ZooKeeper("118.24.96.212:2181", , new MyWatcher());
ChildrenResult childrenResult = null;
          
if (await zooKeeper.existsAsync("/MyApp/CustomerServices/Customer-GetCustomer") != null)
            //获取所有顾客信息服务的地址
childrenResult = await zooKeeper.getChildrenAsync("/MyApp/CustomerServices/Customer-GetCustomer"); //生成一个随机数
Random random = new Random();
var num = random.Next(, childrenResult.Children.Count - ); //通过随机数 获取服务列表中随机的一个地址
var url = $@"http://{childrenResult.Children[num]}/Customer/GetCustomer?Id=" + order.CustomerId;
         //调用顾客服务
var result = await client.GetAsync(url); Custormer custormer = JsonConvert.DeserializeObject<Custormer>(result.Content.ReadAsStringAsync().Result);
order.Custormer = custormer; orders.Add(order);
}
return orders;
}

不过刚才我们仅仅部署了服务到一台服务器中,现在我们改变端口配置,通过命令启动多个实例。如文章的第二个图,顾客服务配置了3台服务器(其实都在同一电脑),订单服务也配置了3台服务器,当订单服务调用时,会从中随机选一台服务器,进行调用。

通过Postman调用接口,结果中返回了订单列表,且订单中包含顾客信息。

本文源代码在:ZooKeeper代码

如果你认为文章写的不错,就点个推荐吧。

服务注册中心之ZooKeeper系列(二) 实现一个简单微服务之间调用的例子的更多相关文章

  1. 服务注册中心之ZooKeeper系列(一)

    一.服务注册中心介绍 分布式服务框架部署在多台不同的机器上.例如服务A是订单相关的处理服务,服务B是订单的客户的相关信息服务.此时有个需求需要在服务A中获取订单客户的信息.如下图: 此时就面临以下几个 ...

  2. 服务注册中心之ZooKeeper系列(三) 实现分布式锁

    通过ZooKeeper的有序节点.节点路径不回重复.还有节点删除会触发Wathcer事件的这些特性,我们可以实现分布式锁. 一.思路 zookeeper中创建一个根节点Locks,用于后续各个客户端的 ...

  3. micronaut 学习 二 创建一个简单的服务

    micronaut 提供的cli 很方便,我们可以快速创建具有所需特性的应用,以下是一个简单的web server app 创建命令 mn create-app hello-world 效果 mn c ...

  4. 学习一下 SpringCloud (二)-- 服务注册中心 Eureka、Zookeeper、Consul、Nacos

    (1) 相关博文地址: 学习一下 SpringCloud (一)-- 从单体架构到微服务架构.代码拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105 ...

  5. Spring Cloud(二):Eureka 服务注册中心

    前言 服务治理 随着业务的发展,微服务应用也随之增加,这些服务的管理和治理会越来越难,并且集群规模.服务位置.服务命名都会发生变化,手动维护的方式极易发生错误或是命名冲突等问题.而服务治理正是为了解决 ...

  6. [源码阅读] 阿里SOFA服务注册中心MetaServer(1)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 0x00 摘要 0x01 服务注册中心 1.1 服务注册中心简 ...

  7. [源码阅读] 阿里SOFA服务注册中心MetaServer(2)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(2) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(2) 0x00 摘要 0x01 MetaServer 注册 1.1 ...

  8. Dubbo原理解析-注册中心之Zookeeper协议注册中心

    下面我们来看下开源dubbo推荐的业界成熟的zookeeper做为注册中心, zookeeper是hadoop的一个子项目是分布式系统的可靠协调者,他提供了配置维护,名字服务,分布式同步等服务.对于z ...

  9. 如何优化Spring Cloud微服务注册中心架构?

    作者: 石杉的架构笔记 1.再回顾:什么是服务注册中心? 先回顾一下什么叫做服务注册中心? 顾名思义,假设你有一个分布式系统,里面包含了多个服务,部署在不同的机器上,然后这些不同机器上的服务之间要互相 ...

随机推荐

  1. Tensor类型

    Tensor类型 1.Tensor有不同的数据类型,每种类型又有CPU和GPU两种版本: 2.默认的tensor类型是FloatTensor,t.set_default_tensor_type可以修改 ...

  2. 在Linux上搭建测试环境常用命令(转自-测试小柚子)

    一.搭建测试环境: 二.查看应用日志: (1)vivi/vim 原本是指修改文件,同时可以使用vi 日志文件名,打开日志文件(2)lessless命令是查看日志最常用的命令.用法:less 日志文件名 ...

  3. NFS 系统搭建 - 成功

    NFS是Network File System的缩写,即文件系统.客户端通过挂载的方式将NFS服务器端共享的数据目录挂载到本地目录下. 工作流程 1.由程序在NFS客户端发起存取文件的请求,客户端本地 ...

  4. 手动安装composer详细教学

    1.下载compser.phar 地址 https://getcomposer.org/download/ 2.新建composer.bat 文件,写入“@php "%~dp0compose ...

  5. HTML图片标签路径解析

    img标签中src属性表示的是引用的图片路径,有两种路径类型: 1. 绝对路径    2. 相对路径. 绝对路径:使用图片在硬盘上的绝对位置来访问图片,通常是从根目录开始,向下一个目录一个目录的寻找. ...

  6. ES6 常用语法

    1.let 定义变量 1.与var 类似 用于声明一个变量 let userName='kobe' 2.特点 1.在块作用域内有效 2.不会吃重复定义变量 3.应用 1.循环遍历加监听 2.使用let ...

  7. java:找出占用CPU资源最多的那个线程

    linux环境下,当发现java进程占用CPU资源很高,且又要想更进一步查出哪一个java线程占用了CPU资源时,按照以下步骤进行查找: 1.先用top命令找出占用资源厉害的java进程id,如: 2 ...

  8. Java web每天学之Servlet工作原理详情解析

    上篇文章中我们介绍了Servlet的实现方式以及Servlet的生命周期,我们这篇文章就来介绍一下常用对象. 点击回顾:<Java Web每天学之Servlet的工作原理解析>:<J ...

  9. 分门别类总结Java中的各种锁,让你彻底记住

    概念 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥 ...

  10. MySQL 数据库在 Windows 下修复 only_full_group_by 的错误

    本机上新安装了个MySQL数据库,在插入数据的时候一直提示这个错误: [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY ...