作者:云和月vv
来源:https://www.cnblogs.com/lgdvvvv/p/lgdvvvv.html
l 前言
本文记录了我的一次.net core 微服务架构实践经验,以及所用到的技术
l 优点
-
每个服务聚焦于一块业务,无论在开发阶段或是部署阶段都是独立的,更适合被各个小团队开发维护,团队对服务的整个生命周期负责,工作在独立的上下文之中。
-
如果某一项服务的性能达到瓶颈,我们只需要增加该服务负载节点,能够针对系统的瓶颈服务更有效的使用资源。
-
服务A可以使用.net实现 ,服务B可以使用java实现,技术选型灵活,系统不会长期限制在某个技术栈上。
-
松耦合、高内聚,代码容易理解,开发效率高,更好维护。
-
高可用,每个服务可以启动多个实例负载,单个实例挂了有足够的响应时间来修复
l 缺点
-
系统规模庞大,运维要求高,需要devops技巧(Jenkins,Kubernetes等等)
-
跨服务需求需要团队之间的协作
-
跨服务的调用(http/rpc)增加了系统的延迟
l Docker
docker是目前普遍使用的容器化技术,在此架构中我们的应用程序将部署在docker容器里面,通过docker发布应用 需要先编写一个dockerfile,如下
#引入镜像 .net core 3.1
docker build 命令 将我们的发布目录打包一个docker镜像,例如 docker build -t test . ,test是镜像名称
docker run 命令启动我们打包的镜像,例如 docker run -d -p 5002:80 --name="test1" -e Ip="192.168.0.164" -e Port="5002" test ,-e 表示传递环境变量
更多docker命令 请查阅:https://www.runoob.com/docker/docker-command-manual.html
docker官网:https://www.docker.com
image.gif
-
部署方便:只需要一个简单的 docker run命令,就可以启动一个应用实例了
-
部署安全:打包镜像的时候已经打包了应用所需环境,运行环境不会出现任何问题
-
隔离性好:同一台机器我可以部署java的应用和.net的应用,互不影响
-
快速回滚:只要镜像存在可以快速回滚到任一版本
-
成本低:一台机器可以运行很多实例,很容易就可以实现高可用和横向扩展
经测试docker for windows不适合部署生产环境,还是得在liunx系统上跑, .net framework 无法在docker上部署
Docker compose :Docker官方提供的管理工具,可以简单的配置一组容器启动参数、启动顺序、依赖关系
Kubernetes :容器数量很多之后会变得难以管理,可以引入Kubernetes对容器进行自动管理,熟练运用有一定难度,中文社区:https://www.kubernetes.org.cn/k8s
l RPC 远程过程调用
为什么要有RPC
按照微服务设计思想,服务A只专注于服务A的业务,但是需求上肯定会有服务A需要调用服务B来完成一个业务处理的情况,使用http调用其他服务效率相对较低,所以引入了RPC。
gRPC vs thrift 评测:https://www.cnblogs.com/softidea/p/7232035.html
这里使用thrift,thrift 官网:http://thrift.apache.org
Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能,语法请自行百度
image
下载thrift 代码生成器 http://thrift.apache.org/download ,thrift-0.13.0.exe 这个文件
执行命令 thrift.exe --gen netcore xxxxxxx.thrift ,生成C# 服务接口代码
image引用官方提供的.net 库,可以去官网下载,找不到的可以直接 nuget引用 Examda.Thrift,这是我为了方便使用上传的
添加生成的代码到我们的服务端里,然后自己实现 thrift文件定义的接口****
using System.Threading;
** 添加一个类继承 IHostedService **
image.gifusing Microsoft.Extensions.Configuration;
修改ConfigureServices添加如下代码
//注入rpc服务实现实例
服务端就完成了,接下来编写客户端调用,修改客户端ConfigureServices添加如下代码****
//test rpc服务
<pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.8); font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(35, 35, 35); text-decoration-style: initial; text-decoration-color: initial; white-space: pre-wrap; font-family: "Courier New" !important;"> 控制器内调用示例</pre>
using System.Threading;
l 服务注册与发现
为什么要有服务注册与发现
例如:服务A一开始只有一个实例,此时又启动了一个服务A的实例,但是调用服务A的服务B并不知道 服务A多了一个实例(或者少了),此时引入服务注册与发现可以让服务B得知服务A的变更情况,服务B就知道自己要调用的服务IP:端口 是多少,不需要人工干预
常见的注册中心
image.gif这里使用consul
健康检查:****consul自带健康检查,检查服务是否可用,不可用的服务将从注册中心剔除,****自带的就是隔一段时间检测一下端口通不通,并且支持自行扩展健康检查,可用自己在服务内实现是否健康的逻辑,比如虽然接口是通的,但是我发现自己宿主机cpu过80%了,就返回不健康的状态
服务注册:nuget安装consul,写一个扩展方法
/// <summary>
修改ConfigureServices添加如下代码,启动
services.AddExamdaServiceRpc(Configuration, "UnionInfo", "联盟机构信息服务");
image.gif
安装consul请自行百度
服务发现与变更:调用方配置好自己需要调用的服务名称集合,然后去consul获取地址列表,然后根据需要调用的服务数量启动N个线程来轮询服务最新的地址信息,不用担心轮询造成的消耗过大,因为consul提供了Blocking Queries 阻塞查询的方式,请求发送到consul之后会在consul阻塞(30)秒,期间有变更或者到达30秒了之后才会返回地址列表,然后每一次变更之后的地址列表都会有一个新的版本号。
using Consul;
<pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.8); font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(35, 35, 35); text-decoration-style: initial; text-decoration-color: initial; white-space: pre-wrap; font-family: "Courier New" !important;"> 然后注入一个ExamdaConsul类的单例,讲写死的服务地址改成从consul获取</pre>
//注入consul客户端 单例
consul 官网:https://www.consul.io
l API网关
所有的请求都先经过网关,由转发到对应的服务,对比了 ocelot 和 Bumblebee 两个c#写的网关。选择使用了Bumblebee。
Ocelot性能比较低,吞吐比直接访问降低四倍,但是文档很全面,功能集成很多,不需要自己扩展什么。
** Bumblebee 我做测试发现Bumblebee 性能很优秀,尴尬的是这个几乎没什么人用,很多功能需要自己扩展,作者官网http://beetlex.io/ **Bumblebee 文档:http://doc.beetlex.io/#29322e3796694434894fc2e6e8747626****
**** 这里使用**Bumblebee ,使用方法可以看作者的文档******
健康检查:不健康的节点将不会被转发请求
限流:例如限制某个节点最多300rps,如果此节点并发了1000个请求,大概会有700个左右请求网关会直接返回错误,不会转发到具体的服务,可以起到挡洪作用,避免节点直接挂了。
路由:我是这么设置的 例如 http://192.168.0.164/Course/Tool/GetUserInfo ,Course一级是服务名称 tool 是服务的控制器名称 getuserinfo是方法名称
负载均衡:服务多个节点负载,网关可以设置负载均衡策略
image
注册到网关:redis发布订阅实现,添加一个扩展方法
public static void AddExamdaService(this IServiceCollection services, IConfiguration Configuration, string ServiceName, string Remark)
网关订阅这个频道
g = new OverrideApiGetewap();
修改ConfigureServices添加如下代码,启动。这样网关也能动态的发现我们的服务了
//注册此服务到网关
异常流量拉黑:例如某个ip 10s内请求数量超过300 将他拉黑 30 分钟,这里使用redis实现计数器
自己写的简陋版本
//请求完成触发的事件,不会阻塞请求
class OverrideApiGetewap : Bumblebee.Gateway
熔断器:当某个请求转发下游服务返回错误次数或者超时次数达到阀值时自动熔断该节点,暂未实现
接口验签:客户端请求都带上用 url时间戳 参数加密的签名,网关进行验证,确保是合法的客户端
网关自带UI
image
l 链路追踪 性能监控
**Skywalking 官网:http://skywalking.apache.org/ **
每个请求的链路,每一个步骤的耗时都可以查到,如下图的一个请求执行了很多次sql,每个步骤的sql语句都可以看到,集成很简单,使用官方提供的.net探针集成到各个服务就好了,无代码入侵。
image image
有一个很强大的ui界面,也可以提供报警等功能,ui可以查看到响应很慢的接口,平均响应时间,以及每个服务的关联关系,但是有个问题我没有解决,RPC链路追踪不到。
可以自行去官方查阅使用文档
l 分布式日志收集框架
实例太多了,不可能使用单机日志,需要一个分布式日志收集框架把所有日志收集到一起,可以考虑使用java的elk 或者 .net core 的Exceptionless
l 分布式事务
跨服务之间调用并且涉及到事务的处理方式,还在想怎么弄
l 配置中心
各个实例逐个配置太麻烦了,特别是如果更改了数据库地址,每一个服务的所有实例都要改,改死去,并且重启实例也不现实,一定要支持配置热更新,试了下携程的Apollo有点消耗资源
l CI/CD
将源码管理做一个开发分支,一个测试分支,一个发布分支,开发只动开发分支,开发完成后提交代码,由测试合并到测试分支,并通知Jenkins生成镜像并发布到测试站点,测试通过之后由运维合并到发布分支,或手动或自动通过Jenkins发布,应该保证 测试分支与发布分支的版本能对应docker镜像仓库的每一个版本,个人见解。
网友评论