微服务模式下,实现前后端多资源服务调用

Micro

Services

首先,我先解释下,文章标题的意思:

咋看起来特别像是一个标题党????,可能是我没想好怎么表达,其实白话文就是:在微服务场景下,肯定会有很多子服务API,那多个前端项目如何对接多个后端资源服务器项目呢?

也就是多个前端VUE如何对接多个后端的WebApi项目,这是问题,其实也不难,今天就简单的讨论讨论,我这里列举了三个场景的解决的方案,相信很多人都用过,都是比较主流的方案,文章的末端会有一个思考,就是如何实现第四种方案,这也就是我标题里为啥用微服务的原因了,本文主要是对微软微服务框架eShop的思考。

既然了解了问题,那你不妨先思考一下,如果是你自己的项目出现了这样的需求,VUE项目如何调用调用多个API项目,跟着我先慢慢往下看吧。

1、先来个需求吧

(图片来源自Blog.Admin首页)

相信如果留心的小伙伴,肯定这几天看到了我的Admin后台多了一个这样的数据统计,特别是下边的30天用户注册统计曲线图,这里先说下这是干啥的:

大家都知道,现在我的所有在线项目都是基于IdentityServer4(下文简称Is4)作为统一认证平台的,那这里的用户信息肯定都是来自于Is4项目的,因为之前项目我一直在修改,用的是默认账号,看不出来真实效果,所以我干脆去掉了默认账号,让每个访问的用户自己注册(放心,基本填的数据基本都是假的,而且我也不会发邮件,其他人也看不到),我就时不时的需要登录ids.neters.club认证平台去看数据,时间长了就麻烦了,那么需求来了:如何集成到Admin后台,通过线图的形式展示呢,还能查看日志,用户量,访问情况,异常信息等

但是Admin项目的后端Api是BlogCore的,我们已经习惯了这种一对一的开发模式,现在要实现一个前端对应多个后端的这种一对多的开发模,那如何来处理呢。

其实我们简单的思考一下就知道了,无论是一对一,还是一对多,甚至是多对多的情况,核心的问题,都是如何处理跨域的问题,如果浏览器不存在跨域的话,我们就可以任意的连接任何资源api了。当然除了跨域,第二个问题就是如何对资源的统一管理,这是个次要的重点知识。

当然,工欲善其事必先利其器,要实现这个需求,我们就首先需要在is4认证平台里,添加对外的接口,这里是未加权的,以后我会说说在加权的情况下,如何来处理,其实是一样的。

 [HttpGet]public MessageModel<AccessApiDateView> GetAchieveUsers(){List<ApiDate> apiDates = new List<ApiDate>();var users = _userManager.Users.Where(d => !d.tdIsDelete).OrderByDescending(d => d.Id).ToList();var tadayRegisterUser = users.Where(d => d.birth >= DateTime.Now.Date).Count();apiDates = (from n in usersgroup n by new { n.birth.Date } into gselect new ApiDate{date = g.Key?.Date.ToString("yyyy-MM-dd"),count = g.Count(),}).ToList();apiDates = apiDates.OrderByDescending(d => d.date).Take(30).ToList();if (apiDates.Count == 0){apiDates.Add(new ApiDate(){date = "没数据,或未开启相应接口服务",count = 0});}return new MessageModel<AccessApiDateView>(){msg = "获取成功",success = true,response = new AccessApiDateView{columns = new string[] { "date", "count" },rows = apiDates.OrderBy(d => d.date).ToList(),today = tadayRegisterUser,}};}

现在接口有了,前端页面也设计好了(具体写法就是vue-chart线图,自行查看),那如何来解决调用问题呢,下边就从四个方面来讨论下。

2、万物皆可代理模式

代理模式,可谓是软件开发中,长盛不衰,一直活跃的东西,虽然有时候很多的名字是“代理”,而实际上上不是一回事,但是却丝毫不影响我们来使用。

那我们在VUE开发中,也会用到代理模式,就是devProxy本地代理,代码很简单,基于node服务,只需要简单的配置下,就可以将任意多个后端给代理到vue本地,只不过这里有个弊端,只能是本地开发模式下使用。

在项目根目录下的vue.config.js中配置(没有的话,新建即可)

  devServer: {open: true, //配置自动启动浏览器host: "127.0.0.1",port: 2364, // 当前vue项目 端口号https: false,hotOnly: false, // https:{type:Boolean}// proxy: null, // 设置代理// proxy: 'http://123.206.33.109:8081',// 配置跨域处理,只有一个代理proxy: {// 配置多个代理"/api": {target: "http://localhost:8081",//这里改成你自己的后端api端口地址,记得每次修改,都需要重新buildws: true,changeOrigin: true,pathRewrite: {// 支持路径重写,"^/api": "" // 替换target中的请求地址}},"/images": {target: "http://localhost:8081",ws: true,changeOrigin: true},"/is4api": {target: "http://localhost:5004",ws: true,changeOrigin: true},},before: app => {}},

是不是很简单,这里我定义了三种策略方案,匹配了两个后端项目,其中/is4api这个节点,就是针对is4项目的接口。

然后接口用相对路径的方式来调用,设置baseUrl="",这样就把所有的接口给代理到了前端了,这个很简单,我就不截图说明了。

上边说了,这种方案是vue本地的代理,我们build后的静态文件,是没有这个功能的,毕竟没有了node服务支持了,所以开发好项目上线的时候,就需要代理服务的反向代理功能了。我这里使用的是nginx做web服务器,那相应的配置如下:

location /api/ {rewrite ^.+apb/?(.*)$ /$1 break;include uwsgi_params;proxy_pass http://localhost:8081;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}location /images/ {include uwsgi_params;proxy_pass http://localhost:8081;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;#proxy_set_header Connection "upgrade";#proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}location /is4api/ {include uwsgi_params;proxy_pass http://localhost:5004;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}

这种就很简单的实现了我们的需求,如果还有其他的微服务,我们就一一的增加进来,往里边添加就行,总体来说还是很方便的。

但是从上边看出来,我们本地开发的时候需要配置一套,然后项目上线的时候有需要配置一套,感觉不是很美观,而且还对管理不友好。所以要是服务比较多的话,我们可以换另一种方案,就是网关。

3、微服务中网关作用很大

(微服务简易网关,图源网络)

上边咱们说到了代理模式,在比较简单的,或者说服务比较少的情况下,还是一种比较常见、比较高效的开发方案,但是随着我们的项目的服务增多,因为我这里只有用户数据和博客数据,分别对应的是Blog.Idp项目和Blog.Core项目,那如果以后需要新增其他功能的时候,如上图,就需要一个统一的平台或者功能来管理我们的所有资源api了,网关就是一个很不错的选择,而且也可以结合其他的功能组件,中间件等扩展,比如服务注册和治理,熔断,负载等等。

既然说到了网关,那常见的网关就是Ocelot、Zuul、Kong这些,其中优劣不予置评,我习惯用Ocelot,那今天就说它。

第一:创建一个网关项目。

我其实blog.core项目中已经有了,你可以查看下Blog.Core.AdminMvc项目,这里什么都没有,我就用来做网关了,引用Ocelot组件

<PackageReference Include="Ocelot" Version="16.0.1" />

第二:配置Ocelot服务

在startup.cs文件中,配置服务和中间件

 public void ConfigureServices(IServiceCollection services){// 配置服务services.AddOcelot();}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}// 添加中间件app.UseOcelot().Wait();}

第三:配置网关策略

在根目录下,创建OcelotGatewaySet.json文件,然后添加内容,具体的逻辑,自行百度查看把,很简单,就是通过一定的配置,把分散的下游项目,也就是分散的微服务项目,给交给一个统一的上游地址,只不过有一定的url规则。

{"Routes": [{"DownstreamPathTemplate": "/api/{url}","DownstreamScheme": "http","DownstreamHostAndPorts": [{"Host": "localhost","Port": 8081}],"UpstreamPathTemplate": "/gateway/api/{url}","UpstreamHttpMethod": ["Get","Post","Put","Delete"],"LoadBalancerOptions": {"Type": "RoundRobin"}},{"DownstreamPathTemplate": "/is4api/{url}","DownstreamScheme": "http","DownstreamHostAndPorts": [{"Host": "localhost","Port": 5004}],"UpstreamPathTemplate": "/gateway/is4api/{url}","UpstreamHttpMethod": ["Get","Post","Put","Delete"],"LoadBalancerOptions": {"Type": "RoundRobin"}}],"GlobalConfiguration": {"BaseUrl": "http://localhost:9000"}
}

这里我定义了两个下游,就是blogcore的8081,is4的5004,然后分别对应了上游的9000项目的两个地址:"/gateway/api/{url}"、"/gateway/is4api/{url}"。

第四步:添加Json文件到应用

上边我们自定义了配置策略文件,肯定是需要添加到应用里的,不然不会被识别,除非你是直接写到了appsettings.json里,但是这样好像又不太好,还是单独写一个文件吧:

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args)// 配置json文件.ConfigureAppConfiguration((hostingContext, config) =>{config.AddJsonFile("OcelotGatewaySet.json");}).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>().UseUrls("http://*:9000");});

然后就可以看到效果了

这样,以后无论多少个下游微服务,都可以集中到网关里,那前端vue项目,就很简单的配置一个9000就行了。

这样不仅可以实现多对多连接,还可以方便服务管理,是不是很方便。但是也有一个小问题,就是不好做服务之间的业务处理,比如我要在blogcore某个业务中,使用is4的用户数据,也就是跨项目跨数据库实现业务逻辑和事务,该怎么办呢,别着急,我项目中已经集成了多库操作,来看看吧。

4、扔到后端:多库模式

BlogCore目前支持,单库-多库-读写分离三种模式,事务当然也是支持,不过跨服跨库事务,可能需要分布式事务的组件来实现。
具体的内容可以参考《【项目升级】单库、多库、读写分离 · 任你选》

具体的写法呢,我的b站视频里也录制了,都是很简单的操作,只需要简单的配置,就可以实现多库处理,然后仓储层连接好后,还可以配合着service层来增删改查,就比如这样的来写。

第一:当然是配置连接字符串

在appsettings.json文件中,做多库处理,如果不会,可以看我的视频

https://www.bilibili.com/video/BV1BJ411B7mn?p=6

第二:定义对应的Model实体,在SugarTable特性里,配置好数据库的ConnId,这个配置的信息在appsettings.json里。

namespace Blog.Core.Model.IDS4DbModels
{/// <summary>/// 以下model 来自ids4项目,多库模式,为了调取ids4数据/// 用户表/// </summary>[SugarTable("AspNetUsers", "WMBLOG_MYSQL_2")]public class ApplicationUser{public string LoginName { get; set; }public string RealName { get; set; }public int sex { get; set; } = 0;public int age { get; set; }public DateTime birth { get; set; } = DateTime.Now;public string addr { get; set; }public bool tdIsDelete { get; set; }}
}

第三:就是直接创建Service层,当然,如果你有封装单独的逻辑业务,可以自己创建一个仓储层,但是如果普通的CURD,可以直接使用泛型基类仓储接口:

 public class ApplicationUserServices : BaseServices<ApplicationUser>, IApplicationUserServices{IBaseRepository<ApplicationUser> _dal;public ApplicationUserServices(IBaseRepository<ApplicationUser> dal){this._dal = dal;base.BaseDal = dal;}}

这样就可以轻松的实现连接了。可以在控制器,甚至是Service里写逻辑了,这里就不多介绍了。

但是!其实这种写法呢,应该不符合今天内容的主旨,这么写虽然可以任意的再后端做多库处理,写业务了,但是如果微服务多了怎么办,又不好做控制,负载什么的。而且又不那么灵活,甚至如果一个服务崩了,也会导致主服务受到影响。

那为什么我还要拿出来说一下呢,主要是想引出第四种方案,就是微服务下,在使用网关、做服务治理、负载均衡的情况下,如何实现多服务之间的调用。

5、如果有第四种方案?

这里先说下第四种思路的由来:

就是上边提到的问题,在微服务场景下,我们是讲一个个服务都拆开限界的,各个子服务独立做负载均衡,服务注册和治理,然后通过网关,将所有的服务连接起来,看着没问题,但是如果某两个,或者多个子服务之间相互左右,或者相互调用来实现交叉业务逻辑的时候,是不是感觉很苍白无力,从而导致了很多人问了这样的问题:

微服务好是好,但是我想多表联查怎么办?看似和微服务,和DDD背道而驰,但是却是不得不考虑的一个问题。

当然,方案还是有的,比如常见的RestFul、gRPC、MQ、Bus等等技术,这些知识点,其实就是第四种方案,其实就是第二和第三种方案的结合体。

这些内容在微软的微服务框架eShopOnContainer都可以看的到,我也正在学习和讲解,想了解的时候,可以看一看:

《#微服务之eShop讲解》

END

欢迎将文章分享到朋友圈

如需转载,请在后台回复“转载”获取授权

更多精彩推荐,老张的哲学

把时间交给阅读

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode54. 螺旋矩阵(详解)

一:题目 二:上码 class Solution { public:/**思路:1.1 2 3 45 6 7 89 10 11 1213 14 15 162.我们模拟顺时针的顺序,那么得分为4步 上面得一行 (1,2,3)左面得一行 (4,8,12)下面得一行 (16,15,14)右面得一行 (13,9,5)可以看到我们在取数字得时候,我们是每次只取…

深度学习入门笔记(7)—— Multinomial Logistic Regression / Softmax Regression

首先介绍一个非常著名的多分类数据集 MNIST&#xff0c;也就是 0 到 9 的手写数字数据集。每个图像都是 28 * 28&#xff0c;用于Pytorch 数据读取的格式是 NCHW&#xff0c;即 Number、Channel、Height、Weight。 读取图像之后&#xff0c;就能看到一个只有单通道的&#xff0…

[C#.NET 拾遗补漏]09:数据标注与数据校验

数据标注&#xff08;Data Annotation&#xff09;是类或类成员添加上下文信息的一种方式&#xff0c;在 C# 通常用特性&#xff08;Attribute&#xff09;类来描述。它的用途主要可以分为下面这三类&#xff1a;验证 Validation&#xff1a;向数据添加验证规则展现 Display&am…

2023届春招实习拉钩一面凉经

面完就当挂了 老想着其实自己其实还能回答的更好 可回不去了 计网的的ioc七层模型各层的作用 为什么是七层模型而不是两层模型 SpringBoot最核心的注解是什么 Spring中IOC原理以及相关注解 说说对Mysql数据库索引的了解 map有哪些实现 分别讲讲&#xff1f; 项目中数据库…

Pytorch中损失函数 NLLLOSS 和 CROSSENTROPYLOSS 的区别

NLLLOSS 是 negative log likelihood loss 负对数似然损失函数&#xff0c;CROSSENTROPYLOSS 是交叉熵损失函数&#xff0c;它们都是用于多分类任务的损失函数&#xff0c;正如我在笔记中提到的&#xff0c;它们实际上是一样的&#xff0c;在 Pytorch 中这两者的区别就只在于接…

收好这张MySQL导图,全是知识点!

感谢阅读&#xff0c;直接上干货&#xff01;最新整理的MySQL知识点全图&#xff0c;完整系统超3万字。直接跳转文末&#xff0c;下载源文件&#xff01;开源跨平台的.NET Core越来越热门了&#xff0c;腾讯、网易、顺丰等大厂都在招聘&#xff0c;而且都开始明确要求MySQL了&a…

深度学习入门笔记(8)—— Multilayer Perceptron 多层感知器

所谓多层感知器&#xff0c;其实就是具有一个或多个隐藏层&#xff08;hidden layer&#xff09;的全连接前馈神经网络&#xff0c;如图所示&#xff0c;在最重要的求取损失关于权重的偏导数的过程上&#xff0c;跟之前的 Logistic Regression 和 Softmax Regression 一样&…

蓝桥杯-填空题-门牌制作

一&#xff1a;题目 二&#xff1a;代码 #include <iostream> using namespace std; int main() {int nums 2020;int count 0;string str to_string(nums);for(int i 1; i < nums; i) {string str to_string(i);for(auto temp: str) {if(temp 2){count;}}}cout…

.NET Core中间件与依赖注入的一些思考

点击上方蓝字"小黑在哪里"关注我吧1.起源? 为什么会有这篇文章呢? 源于我看了老A的aspnet core 3 框架揭秘[1] 请求管道 篇产生的疑惑?三点疑惑:Singleton服务中注入Scoped服务产生内存泄露?关于中间件的生命周期是Singleton的?怎么避免中间件、Singleton服务中…

深度学习入门笔记(9)—— Regularization to avoid overfitting 用正则化来避免过拟合

在维基百科的词条中&#xff0c;正则化就是让答案变得更简单的&#xff0c;目的是防止过拟合。在分类上可以分为显式正则化&#xff08;对目标函数添加先验、惩罚、约束、成本&#xff09;和隐式正则化&#xff08;所有其他形式的正则化如提前停止、集成学习、Dropout 等&#…

蓝桥杯-填空题-购物单

一&#xff1a;题目 **** 180.90 88折 **** 10.25 65折 **** 56.14 9折 **** 104.65 9折 **** 100.30 88折 **** 297.15 半价 **** 26.75 65折 **** 130.62 半价 **** 240.28 …

C# 中 System.Index 结构体和 Hat 运算符(^)的全新用法

翻译自 John Demetriou 2019年2月17日 的文章 《C# 8 – Introducing Index Struct And A Brand New Usage For The Hat Operator》今天我们要讲的是 Hat 运算符(^)。目前为止&#xff0c;Hat 运算符(^)已经被用作布尔类型的异或运算符&#xff0c;以及字节、整型类型的按位异或…

Lagrange Multipliers 拉格朗日乘数法(含 KKT 条件)

最优化问题通常是指对于给定的某一函数&#xff0c;求其在指定作用域上的全局最小值&#xff0c;一般情况下&#xff0c;最优化问题一般分为三种情况&#xff1a; &#xff08;1&#xff09;无约束条件 对于无约束条件的优化问题中&#xff0c;如果一个函数 f 是凸函数&#…

蓝桥杯-代码-数字三角形

一:题目 二:代码 #include <iostream> #include<vector> using namespace std; int main() {/**思路:1.确定dp数组的定义以及下标的含义dp[i][j] 表示的是一条路径到达下标i和j时&#xff0c;的最大值 2.确定dp数组的状态转移公式我们当前位置的最大值其实是由右上…

ABP VNext从单体切换到微服务

注&#xff1a;此处的微服务只考虑服务部分&#xff0c;不考虑内外层网关、认证等。ABP VNext从单体切换到微服务&#xff0c;提供了相当大的便利性&#xff0c;对于各模块内部不要做任何调整&#xff0c;仅需要调整承载体即可。ABP can help you in that point by offerring a…

dotnet 在 UOS 国产系统上使用 Xamarin Forms 创建 xaml 界面的 GTK 应用

在前面几篇博客告诉大家如何部署 GTK 应用&#xff0c;此时的应用是特别弱的&#xff0c;大概只是到拖控件级。尽管和 WinForms 一样也能写出特别强大的应用&#xff0c;但是为了提升一点开发效率&#xff0c;咱开始使用 xaml 神器写界面。本文告诉大家如何在 UOS 国产系统上&a…

赛码-编程题-打字

一:题目 二&#xff1a;上码 #include<bits/stdc.h> using namespace std;int main() {int n;vector<int> v1;cin >> n;for(int i 0; i < n; i) {string str;int count 0;cin >> str;for (int j 0; j < str.size(); j) {int num int(str[j]…

Python 中的 with 语句用法和 Pytorch 中的 with torch.no_grad() 解析

Python 中的 with 语句适用于对资源进行访问的场合&#xff0c;确保不管使用过程中是否发生异常都会执行必要的“清理”操作&#xff08;异常处理&#xff09;&#xff0c;释放资源&#xff0c;比如文件使用后自动关闭&#xff0f;线程中锁的自动获取和释放等。例如下面是文件读…

遍历 Dictionary,你会几种方式?

一&#xff1a;背景 1. 讲故事昨天在 StackOverflow 上看到一个很有趣的问题&#xff0c;说: 你会几种遍历字典的方式&#xff0c;然后跟帖就是各种奇葩的回答&#xff0c;挺有意思&#xff0c;马上就要国庆了&#xff0c;娱乐娱乐吧&#xff0c;说说这种挺无聊的问题?????…

PyTorch 中各种操纵维度的函数比较 view() reshape() squeeze() unsqueeze() flatten()

首先&#xff0c;假设我们有一个三行四列的张量 X&#xff1a; view() 和 reshape() 函数都可以指定并改变张量的维度&#xff0c;它们本质上是相同的&#xff0c;只有两点区别&#xff1a; 1、view() 函数返回的是原始张量的视图&#xff0c;而 reshape() 函数返回的是原始张…