首页 文章 精选 留言 我的

精选列表

搜索[micro],共862篇文章
优秀的个人博客,低调大师

再见go-micro!企业项目迁移go-zero全攻略(二)

承接上篇:上篇文章讲到 go-zero 架构设计和项目设计。本篇文章接着这个项目设计,将生成的 app 模块 中 gateway 和 RPC 进行改造。废话不多说,让我们开始! gateway service gateway 中我做了一些自定义,在端请求我们后台接口情况下,虽然多数情况是不需要关心错误码的,但是避免不了要某些场景还是需要根据固定错误码去做特殊处理,我自己定义了一个错误类,这个错误类只在 gateway 中使用: err.go: package xerr import "fmt" type CodeError struct { errCode int errMsg string } // 属性 func (e *CodeError) GetErrCode() int { return e.errCode } func (e *CodeError) GetErrMsg() string { return e.errMsg } func (e *CodeError) Error() string { return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg) } func New(errCode int, errMsg string) *CodeError { return &CodeError{errCode: errCode, errMsg: errMsg} } func NewErrCode(errCode int) *CodeError { return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode)} } func NewErrMsg(errMsg string) *CodeError { return &CodeError{errCode: BAD_REUQEST_ERROR, errMsg: errMsg} } errmsg.go package xerr var message map[int]string func init() { message = make(map[int]string) message[OK] = "SUCCESS" message[BAD_REUQEST_ERROR] = "服务器繁忙,请稍后再试" message[REUQES_PARAM_ERROR] = "参数错误" message[USER_NOT_FOUND] = "用户不存在" } func MapErrMsg(errcode int) string { if msg, ok := message[errcode]; ok { return msg } else { return "服务器繁忙,请稍后再试" } } errcode.go package xerr // 成功返回 const OK = 200 // 全局错误码 // 前3位代表业务,后三位代表具体功能 const BAD_REUQEST_ERROR = 100001 const REUQES_PARAM_ERROR = 100002 // 用户模块 const USER_NOT_FOUND = 200001 我将三个文件统一放在 lib/xerr 目录 有了错误码还不行,还要定义统一返回http的结果,goctl 生成的默认的是挺好的,但是没法符合我这种返回自定义错误码需求,于是我自己有写了一个统一返回结果的文件: httpresult: package xhttp import ( "fishtwo/lib/xerr" "fmt" "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/rest/httpx" "google.golang.org/grpc/status" "net/http" "github.com/pkg/errors" ) // http方法 func HttpResult(r *http.Request,w http.ResponseWriter,resp interface{},err error) { if err == nil { // 成功返回 r:= Success(resp) httpx.WriteJson(w, http.StatusOK, r) } else { // 错误返回 errcode := xerr.BAD_REUQEST_ERROR errmsg := "服务器繁忙,请稍后再试" if e,ok := err.(*xerr.CodeError);ok{ // 自定义CodeError errcode = e.GetErrCode() errmsg = e.GetErrMsg() } else { originErr := errors.Cause(err) // err类型 if gstatus, ok := status.FromError(originErr);ok{ // grpc err错误 errmsg = gstatus.Message() } } logx.WithContext(r.Context()).Error("【GATEWAY-SRV-ERR】 : %+v ",err) httpx.WriteJson(w, http.StatusBadRequest, Error(errcode,errmsg)) } } // http参数错误返回 func ParamErrorResult(r *http.Request,w http.ResponseWriter,err error) { errMsg := fmt.Sprintf("%s ,%s", xerr.MapErrMsg(xerr.REUQES_PARAM_ERROR), err.Error()) httpx.WriteJson(w, http.StatusBadRequest, Error(xerr.REUQES_PARAM_ERROR,errMsg)) } responsebean package xhttp type ( NullJson struct {} ResponseSuccessBean struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } ) func Success(data interface{}) *ResponseSuccessBean { return &ResponseSuccessBean{200, "OK", data} } type ResponseErrorBean struct { Code int `json:"code"` Msg string `json:"msg"` } func Error(errCode int,errMsg string) *ResponseErrorBean { return &ResponseErrorBean{errCode, errMsg} } 放在 lib/xhttp下 然后改造了internal/handler/下通过goctl生成的代码: 当然你会说,每次生成完都要手动去改,好麻烦! 当当当当~~~ goctl 的 template 来咯 https://www.yuque.com/tal-tech/go-zero/mkpuit 然后修改 ~/.goctl/api/handler.tpl: package handler import ( "net/http" {{.ImportPackages}} ) func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { {{if .HasRequest}}var req types.{{.RequestType}} if err := httpx.Parse(r, &req); err != nil { xhttp.ParamErrorResult(r,w,err) return }{{end}} l := logic.New{{.LogicType}}(r.Context(), ctx) resp, err := l.Login(req) xhttp.HttpResult(r,w,resp,err) } } 在重新生成看看,是不是就 beautiful 了,哈哈 然后在说我们的 gateway log,如果眼神好的用户,在上面的 httpresult.go 中已经看到了 log 的身影: 是的是的,这样处理就可以啦,这样只要有错误就会打印日志了,go-zero 已经把 trace-id 带进去了,啥?trace-id 不知道是啥?嗯,其实就是把一次请求通过此 id 串联起来,比如你 user-api 调用 user->srv 或者其他 srv,那要把他们这一次请求都串联起来,需要一个唯一标识别,这个 id 就是做这个,做链路追踪有很多,比如 jaeger、zipkin。 RPC service model:rpc 服务中,官方文档推荐是将 model 放在 services 目录下,与每个 rpc 服务一层,但是个人感觉每个 model 对应一张表,一张表只能由一个服务去控制,哪个服务控制这张表就哪个服务拥有控制这个 model 权利,其他服务想访问就要通过 grpc,这是个人的想法,所以我把每个服务自己管控的 model 放在了 internal 中 enum:另外我在服务下加了 enum 枚举目录,因为其他 rpc 服务或者 api 服务会调用这个枚举去比对,我就放在 internal 外部 框架地址 https://github.com/tal-tech/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们 👍 我为大家整理了 go-zero 作者去年广受好评的分享视频,详细分享了 go-zero 的设计理念和最佳实践。关注公众号「微服务实践」,回复 视频 获取;还可以回复 进群 和数千 go-zero 使用者交流学习。

优秀的个人博客,低调大师

(9)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- JWT算法

一、 JWT 简介 内部 Restful 接口可以“我家大门常打开”,但是如果要给 app 等使用的接口,则需要做权限校验,不能谁都随便调用。 Restful 接口不是 web 网站,App 中很难直接处理 SessionId,而且 Cookie 有跨域访问的限制,所以一般不能直接用后端 Web 框架内置的 Session 机制。但是可以用类似 Session 的机制,用户登录之后返回一个类似 SessionId 的东西,服务器端把 SessionId 和用户的信息对应关系保存到 Redis 等地方,客户端把 SessionId 保存起来,以后每次请求的时候都带着这个SessionId。 用类似 Session 这种机制的坏处:需要集中的 Session 机制服务器;不可以在 nginx、CDN 等静态文件处理服务器上校验权限;每次都要根据 SessionId 去 Redis 服务器获取用户信息,效率低; JWT(Json Web Token)是现在流行的一种对 Restful 接口进行验证的机制的基础。 JWT 的特点:把用户信息放到一个 JWT 字符串中,用户信息部分是明文的,再加上一部分签名区域,签名部分是服务器对于“明文部分+秘钥”加密的,这个加密信息只有服务器端才能解析。用户端只是存储、转发这个 JWT 字符串。如果客户端篡改了明文部分,那么服务器端解密时候会报错。 JWT 由三块组成,可以把用户名、用户 Id 等保存到 Payload 部分 注意 Payload和 Header部分都是 Base64编码,可以轻松的 Base64解码回来。因此 Payload 部分约等于是明文的,因此不能在 Payload 中保存不能让别人看到的机密信息。虽然说 Payload 部分约等于是明文的,但是不用担心 Payload 被篡改,因为 Signature 部分是根据 header+payload+secretKey 进行加密算出来的,如果 Payload 被篡改,就可以根据 Signature 解密时候校验。 用 JWT 做权限验证的好处:无状态,更有利于分布式系统,不需要集中的 Session 机制服务器;可以在 nginx、CDN 等静态文件处理服务器上校验权限;获取用户信息直接从 JWT 中就可以读取,效率高; 二、.Net 中使用 JWT 算法 1) 加密 Nuget -> Install-Package JWT var payload = new Dictionary<string, object> { { "UserId", 123 }, { "UserName", "admin" } }; var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//不要泄露 IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); var token = encoder.Encode(payload, secret); Console.WriteLine(token); 2) 解密 var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qj w1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U"; var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); var json = decoder.Decode(token, secret, verify: true); Console.WriteLine(json); } catch (FormatException) { Console.WriteLine("Token format invalid"); } catch (TokenExpiredException) { Console.WriteLine("Token has expired"); } catch (SignatureVerificationException) { Console.WriteLine("Token has invalid signature"); } 试着篡改一下 Payload 部分。 3) 过期时间 在 payload 中增加一个名字为 exp 的值,值为过期时间和 1970/1/1 00:00:00 相差的秒数 double exp = (DateTime.UtcNow.AddSeconds(10) - new DateTime(1970, 1, 1)).TotalSeconds; 4) 不用秘钥解析数据payload 因为 payload 部分是明文的,所以在不知道秘钥的时候也可以用 Decode、DecodeToObject 等不需要秘钥的方法把payload部分解析出来。 var token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1 epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U"; try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); var json = decoder.Decode(token); Console.WriteLine(json); } catch (FormatException) { Console.WriteLine("Token format invalid"); } catch (TokenExpiredException) { Console.WriteLine("Token has expired"); } 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(6)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- AOP框架

AOP 框架基础 要求懂的知识:AOP、Filter、反射(Attribute)。 如果直接使用 Polly,那么就会造成业务代码中混杂大量的业务无关代码。我们使用 AOP (如果不了解 AOP,请自行参考网上资料)的方式封装一个简单的框架,模仿 Spring cloud 中的 Hystrix。 需要先引入一个支持.Net Core 的 AOP,我们用.Net Core 下的 AOP 框架是AspectCore(国产,动态织入),其他要不就是不支持.Net Core,要不就是不支持对异步方法进行拦截 MVC Filter。 GitHub:https://github.com/dotnetcore/AspectCore-Framework Install-Package AspectCore.Core -Version 0.5.0 这里只介绍和我们相关的用法: 1、编写拦截器CustomInterceptorAttribute 一般继承自AbstractInterceptorAttribute public class CustomInterceptorAttribute:AbstractInterceptorAttribute { //每个被拦截的方法中执行 public async override Task Invoke(AspectContext context, AspectDelegate next) { try { Console.WriteLine("执行之前"); await next(context);//执行被拦截的方法 } catch (Exception) { Console.WriteLine("被拦截的方法出现异常"); throw; } finally { Console.WriteLine("执行之后"); } } } 2、编写需要被代理拦截的类 在要被拦截的方法上标注CustomInterceptorAttribute 。类需要是public类,方法如果需要拦截就是虚方法,支持异步方法,因为动态代理是动态生成被代理的类的动态子类实现的。 public class Person { [CustomInterceptor] public virtual void Say(string msg) { Console.WriteLine("service calling..."+msg); } } 3、通过AspectCore创建代理对象 ProxyGeneratorBuilder proxyGeneratorBuilder = new ProxyGeneratorBuilder(); using (IProxyGenerator proxyGenerator = proxyGeneratorBuilder.Build()) { Person p = proxyGenerator.CreateClassProxy<Person>(); p.Say("rupeng.com"); } Console.ReadKey(); 注意p指向的对象是AspectCore生成的Person的动态子类的对象,直接new Person是无法被拦截的。 研究AOP细节 拦截器中Invoke方法下的参数AspectContext的属性的含义: Implementation 实际动态创建的Person子类的对象。 ImplementationMethod就是Person子类的Say方法 Parameters 方法的参数值。 Proxy==Implementation:当前场景下 ProxyMethod==ImplementationMethod:当前场景下 ReturnValue返回值 ServiceMethod是Person的Say方法 注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(5)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 熔断降级(Polly)

一、 什么是熔断降级 熔断就是“保险丝”。当出现某些状况时,切断服务,从而防止应用程序不断地尝试执行可能会失败的操作给系统造成“雪崩”,或者大量的超时等待导致系统卡死。 降级的目的是当某个服务提供者发生故障的时候,向调用方返回一个错误响应或者替代响应。举例子:调用联通接口服务器发送短信失败之后,改用移动短信服务器发送,如果移动短信服务器也失败,则改用电信短信服务器,如果还失败,则返回“失败”响应;在从推荐商品服务器加载数据的时候,如果失败,则改用从缓存中加载,如果缓存中也加载失败,则返回一些本地替代数据。 二、 Polly 简介 .Net Core 中有一个被.Net 基金会认可的库 Polly,可以用来简化熔断降级的处理。主要功能:重试(Retry);断路器(Circuit-breaker);超时检测(Timeout);缓存(Cache);降级(FallBack); 官网:https://github.com/App-vNext/Polly 介绍文章:https://www.cnblogs.com/CreateMyself/p/7589397.html Nuget安装指令:Install-Package Polly -Version 6.0.1 Polly 的策略由“故障”和“动作”两部分组成,“故障”包括异常、超时、返回值错误等情况,“动作”包括 降级(FallBack)、重试(Retry)、熔断(Circuit-breaker)等。 策略用来执行可能会有有故障的业务代码,当业务代码出现“故障”中情况的时候就执行“动作”。 由于实际业务代码中故障情况很难重现出来,所以 Polly 这一些都是用一些无意义的代码模拟出来。 Polly 也支持请求缓存“数据不变化则不重复自行代码”,但是和新版本兼容不好,而且功能局限性很大,因此这里不讲。 由于调试器存在,看不清楚 Polly 的执行过程,因此本节都用【开始执行(不调试)】 三、Polly简单使用 使用Policy的静态方法创建ISyncPolicy实现类对象,创建方法既有同步方法也有异步方法,根据自己的需要选择。下面先演示同步的,异步的用法类似。 举例:当发生ArgumentException异常的时候,执行Fallback代码。 Policy policy = Policy .Handle<ArgumentException>() //故障 .Fallback(() =>//动作 { Console.WriteLine("执行出错"); }); policy.Execute(() => {//在策略中执行业务代码 //这里是可能会产生问题的业务系统代码 Console.WriteLine("开始任务"); throw new ArgumentException("Hello world!"); Console.WriteLine("完成任务"); }); Console.ReadKey(); 如果没有被Handle处理的异常,则会导致未处理异常被抛出。 还可以用Fallback的其他重载获取异常信息: Policy policy = Policy .Handle<ArgumentException>() //故障 .Fallback(() =>//动作 { Console.WriteLine("执行出错"); },ex=> { Console.WriteLine(ex); }); policy.Execute(() => { //在策略中执行业务代码 //这里是可能会产生问题的业务系统代码 Console.WriteLine("开始任务1"); throw new ArgumentException("Hello1 world!"); Console.WriteLine("完成任务"); }); 如果Execute中的代码是带返回值的,那么只要使用带泛型的Policy<T>类即可: Policy<string> policy = Policy<string> .Handle<Exception>() //故障 .Fallback(() =>//动作 { Console.WriteLine("执行出错"); return "降级的值"; }); string value = policy.Execute(() => { Console.WriteLine("开始任务"); throw new Exception("Hello world!"); Console.WriteLine("完成任务"); return "正常的值"; }); Console.WriteLine("返回值:"+value); FallBack的重载方法也非常多,有的异常可以直接提供降级后的值。 (*)异常中还可以通过lambda表达式对异常判断“满足***条件的异常我才处理”,简单看看试试重载即可。还可以多个Or处理各种不同的异常。 (*)还可以用HandleResult等判断返回值进行故障判断等,我感觉没太大必要。 四、重试处理 Policy policy = Policy .Handle<Exception>() .RetryForever(); policy.Execute(() => { Console.WriteLine("开始任务"); if (DateTime.Now.Second % 10 != 0) { throw new Exception("出错"); } Console.WriteLine("完成任务"); }); RetryForever()是一直重试直到成功 Retry()是重试最多一次; Retry(n) 是重试最多n次; WaitAndRetry()可以实现“如果出错等待100ms再试还不行再等150ms秒。。。。”,重载方法很多,不再一一介绍。还有WaitAndRetryForever。 五、 短路保护 Circuit Breaker 出现N次连续错误,则把“熔断器”(保险丝)熔断,等待一段时间,等待这段时间内如果再Execute 则直接抛出BrokenCircuitException异常,根本不会再去尝试调用业务代码。等待时间过去之后,再执行Execute的时候如果又错了(一次就够了),那么继续熔断一段时间,否则就恢复正常。 这样就避免一个服务已经不可用了,还是使劲的请求给系统造成更大压力。 Policy policy = Policy .Handle<Exception>() .CircuitBreaker(6,TimeSpan.FromSeconds(5));//连续出错6次之后熔断5秒(不会再去尝试执行业务代码)。 while(true) { Console.WriteLine("开始Execute"); try { policy.Execute(() => { Console.WriteLine("开始任务"); throw new Exception("出错"); Console.WriteLine("完成任务"); }); } catch(Exception ex) { Console.WriteLine("execute出错"+ex); } Thread.Sleep(500); } 其计数的范围是policy对象,所以如果想整个服务器全局对于一段代码做短路保护,则需要共用一个policy对象。 六、策略封装,包裹Warp 可以把多个ISyncPolicy合并到一起执行: policy3= policy1.Wrap(policy2); 执行policy3就会把policy1、policy2封装到一起执行。 Policy的静态方法Wrap可以把更多的policy一起封装: policy9=Policy.Wrap(policy1, policy2, policy3, policy4, policy5); 七、超时处理 这些处理不能简单的链式调用,要用到Wrap。例如下面实现“出现异常则重试三次,如果还出错就FallBack”这样是不行的 Policy policy = Policy.Handle<Exception>().Retry(3).Fallback(()=> { Console.WriteLine("执行出错"); });//这样不行,系统会直接报错 注意Wrap是有包裹顺序的,内层的故障如果没有被处理则会抛出到外层。 下面代码实现了“出现异常则重试三次,如果还出错就FallBack” Policy policyRetry = Policy.Handle<Exception>().Retry(3); //出现异常重试三次 Policy policyFallback = Policy .Handle<Exception>() .Fallback(()=> { Console.WriteLine("降级"); }); //Wrap:包裹。policyRetry在里面,policyFallback裹在外面。 //如果里面出现了故障,则把故障抛出来给外面 Policy policy = policyFallback.Wrap(policyRetry); policy.Execute(()=> { Console.WriteLine("开始任务"); if (DateTime.Now.Second % 10 != 0) { throw new Exception("出错"); } Console.WriteLine("完成任务"); }); 运行结果: Timeout是定义超时故障,如果超时会抛出TimeoutRejectedException异常。 Policy policy = Policy.Timeout(3, TimeoutStrategy.Pessimistic);// 创建一个3秒钟(注意单位)的超时策略。 Timeout生成的Policy要和其他Policy一起Wrap使用。 超时策略一般不能直接用,而是和其他封装到一起用: Policy policy = Policy .Handle<TimeoutRejectedException>() //定义所处理的故障 .Fallback(() => { Console.WriteLine("降级"); }); policy = policy.Wrap(Policy.Timeout(2,TimeoutStrategy.Pessimistic)); policy.Execute(()=> { Console.WriteLine("开始任务"); Thread.Sleep(5000); Console.WriteLine("完成任务"); }); 执行结果: 上面的代码就是如果执行超过2秒钟,则直接Fallback。 这个的用途:请求网络接口,避免接口长期没有响应造成系统卡死。 八、Polly 的异步用法 所有方法都用Async方法即可,Handle由于只是定义异常,所以不需要异常方法: 带返回值的例子: Policy<byte[]> policy = Policy<byte[]> .Handle<Exception>() .FallbackAsync(async c => { Console.WriteLine("降级"); return new byte[0]; },async r=> { Console.WriteLine(r.Exception); }); policy = policy.WrapAsync( Policy.TimeoutAsync(2, TimeoutStrategy.Pessimistic, async(context, timespan, task) => { Console.WriteLine("timeout"); })); var bytes = await policy.ExecuteAsync(async () => { Console.WriteLine("开始任务"); HttpClient httpClient = new HttpClient(); var result = await httpClient.GetByteArrayAsync("http://static.rupeng.com/upload/chatimage/20183/07EB793A4C247A654B31B4D14EC64BCA.png"); Console.WriteLine("完成任务"); return result; }); Console.WriteLine("bytes长度"+bytes.Length); 执行结果: 没返回值的例子: Policy policy = Policy .Handle<Exception>() .FallbackAsync(async c => { Console.WriteLine("降级"); },async ex=> {//对于没有返回值的,这个参数直接是异常 Console.WriteLine(ex); }); policy = policy.WrapAsync(Policy.TimeoutAsync(3, TimeoutStrategy.Pessimistic, async(context, timespan, task) => { Console.WriteLine("timeout"); })); await policy.ExecuteAsync(async () => { Console.WriteLine("开始任务"); await Task.Delay(5000);//注意不能用Thread.Sleep(5000); Console.WriteLine("完成任务"); }); 执行结果: 注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(3)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Consul服务治理

Consul是注册中心,服务提供者、服务提供者、服务消费者等都要注册到Consul中,这样就可以实现服务提供者、服务消费者的隔离。 除了Consul之外,还有Eureka、Zookeeper等类似软件。 Consul就是来存储服务器名称与IP和端口对应关系的服务器 一、 consul 服务器安装 consul 下载地址 https://www.consul.io/ 墙外的网站 需要科学上网 cmd运行 consul.exe agent -dev 这是开发环境测试,生产环境要建集群,要至少一台 Server,多台 Agent (因为如果只有一台的话,如果服务死了 整个网站就出问题了) 开发环境中 consul 重启后数据就会丢失。 consul 的监控页面 http://127.0.0.1:850 consult 主要做三件事:提供服务到 ip 地址的注册;提供服务到 ip 地址列表的查询;对提供服务方的健康检查(HealthCheck); 二、 .Net Core 连接 consul 1. Nuget - > Install-Package Consul 安装Consul 2. 提供一个HealthCheck API接口 用于Consul做健康检查调用,只要返回个结果就可以 不论是什么结果。如 [Route("api/[controller]")] public class HealthController : Controller { [HttpGet] public IActionResult Get() { return Ok("ok"); } } 3.服务注册 Consul 及注销 public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); string ip = Configuration["ip"]; int port = Convert.ToInt32(Configuration["port"]); string serviceName = "MsgService"; string serviceId = serviceName + Guid.NewGuid(); using (var client = new ConsulClient(ConsulConfig)) { //注册服务到 Consul ServiceRegister是一个异步方法 client.Agent.ServiceRegister(new AgentServiceRegistration() { ID = serviceId,//服务编号,不能重复,用 Guid 最简单 Name = serviceName,//服务的名字 Address = ip,//服务提供者的能被消费者访问的 ip 地址(可以被其他应用访问的地址,本地测试可以用 127.0.0.1,机房环境中一定要写自己的内网 ip 地址) Port = port,// 服务提供者的能被消费者访问的端口 Check = new AgentServiceCheck { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务停止多久后反注册(注销) Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔 HTTP = $"http://{ip}:{port}/api/health",//健康检查地址 Timeout = TimeSpan.FromSeconds(5) } }).Wait();//Consult 客户端的所有方法几乎都是异步方法,但是都没按照规范加上Async 后缀,所以容易误导。记得调用后要 Wait()或者 await } //程序正常退出的时候从 Consul 注销服务 //要通过方法参数注入 IApplicationLifetime //程序结束的时候会调用这个方法 applicationLifetime.ApplicationStopped.Register(()=> { using (var client = new ConsulClient(ConsulConfig)) { client.Agent.ServiceDeregister(serviceId).Wait(); } }); } private void ConsulConfig(ConsulClientConfiguration c) { c.Address = new Uri("http://127.0.0.1:8500"); c.Datacenter = "dc1"; } 小哥哥 小姐姐们 如果本篇文章对你们有帮助的话 就点点右下角的推荐吧 0.0 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server

用 JWT 机制实现验证的原理如下图: 认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性。 一、 相关概念 API 资源(API Resource):微博服务器接口、斗鱼弹幕服务器接口、斗鱼直播接口就是API 资源。 客户端(Client):Client 就是官方微博 android 客户端、官方微博 ios 客户端、第三方微博客户端、微博助手等。 身份资源(Identity Resource):就是用户。 一个用户可能使用多个客户端访问服务器;一个客户端也可能服务多个用户。封禁了一个客户端,所有用户都不能使用这个这个客户端访问服务器,但是可以使用其他客户端访问;封禁了一个用户,这个用户在所有设备上都不能访问,但是不影响其他用户。 二、 搭建 identity server 认证服务器 新建一个空的 web 项目 ID4.IdServer Nuget - 》 Install-Package IdentityServer4 首先编写一个提供应用列表、账号列表的 Config 类 using IdentityServer4.Models; using System.Collections.Generic; namespace ID4.IdServer { public class Config { /// <summary> /// 返回应用列表 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> resources = new List<ApiResource>(); //ApiResource第一个参数是应用的名字,第二个参数是描述 resources.Add(new ApiResource("MsgAPI", "消息服务API")); resources.Add(new ApiResource("ProductAPI", "产品API")); return resources; } /// <summary> /// 返回账号列表 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); clients.Add(new Client { ClientId = "clientPC1",//API账号、客户端Id AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("123321".Sha256())//秘钥 }, AllowedScopes = { "MsgAPI", "ProductAPI" }//这个账号支持访问哪些应用 }); return clients; } } } 如果允许在数据库中配置账号等信息,那么可以从数据库中读取然后返回这些内容。疑问待解。 修改Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseIdentityServer(); } 然后在 9500 端口启动 在 postman 里发出请求,获取 token http://localhost:9500/connect/token,发 Post 请求,表单请求内容(注意不是报文头): client_id=clientPC1 client_secret=123321 grant_type=client_credentials 把返回的 access_token 留下来后面用(注意有有效期)。 注意,其实不应该让客户端直接去申请 token,这只是咱演示,后面讲解正确做法。 三、搭建 Ocelot 服务器项目 空 Web 项目,项目名 ID4.Ocelot1 nuget 安装 IdentityServer4、Ocelot 编写配置文件 Ocelot.json(注意设置【如果较新则】) { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "UpstreamPathTemplate": "/MsgService/{url}", "UpstreamHttpMethod": ["Get", "Post"], "ServiceName": "MsgService", "LoadBalancerOptions": { "Type": "RoundRobin" }, "UseServiceDiscovery": true, "AuthenticationOptions": { "AuthenticationProviderKey": "MsgKey", "AllowedScopes": [] } }, { "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "UpstreamPathTemplate": "/ProductService/{url}", "UpstreamHttpMethod": ["Get", "Post"], "ServiceName": "ProductService", "LoadBalancerOptions": { "Type": "RoundRobin" }, "UseServiceDiscovery": true, "AuthenticationOptions": { "AuthenticationProviderKey": "ProductKey", "AllowedScopes": [] } } ], "GlobalConfiguration": { "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500 } } } 把/MsgService 访问的都转给消息后端服务器(使用Consul进行服务发现)。也可以把Identity Server配置到Ocelot,但是我们不做,后边会讲为什么不放。 Program.cs 的 CreateWebHostBuilder 中加载 Ocelot.json .ConfigureAppConfiguration((hostingContext, builder) => { builder.AddJsonFile("Ocelot.json",false, true); }) 修改 Startup.cs 让 Ocelot 能够访问 Identity Server 进行 Token 的验证 using System; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; using Ocelot.Middleware; namespace ID4.Ocelot1 { public class Startup { public void ConfigureServices(IServiceCollection services) { //指定Identity Server的信息 Action<IdentityServerAuthenticationOptions> isaOptMsg = o => { o.Authority = "http://localhost:9500"; o.ApiName = "MsgAPI";//要连接的应用的名字 o.RequireHttpsMetadata = false; o.SupportedTokens = SupportedTokens.Both; o.ApiSecret = "123321";//秘钥 }; Action<IdentityServerAuthenticationOptions> isaOptProduct = o => { o.Authority = "http://localhost:9500"; o.ApiName = "ProductAPI";//要连接的应用的名字 o.RequireHttpsMetadata = false; o.SupportedTokens = SupportedTokens.Both; o.ApiSecret = "123321";//秘钥 }; services.AddAuthentication() //对配置文件中使用ChatKey配置了AuthenticationProviderKey=MsgKey //的路由规则使用如下的验证方式 .AddIdentityServerAuthentication("MsgKey", isaOptMsg).AddIdentityServerAuthentication("ProductKey", isaOptProduct); services.AddOcelot(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseOcelot().Wait(); } } } 很显然我们可以让不同的服务采用不同的Identity Server。 启动 Ocelot 服务器,然后向 ocelot 请求/MsgService/SMS/Send_MI(报文体还是要传 json 数据),在请求头(不是报文体)里加上: Authorization="Bearer "+上面 identityserver 返回的 accesstoken 如果返回 401,那就是认证错误。 Ocelot 会把 Authorization 值传递给后端服务器,这样在后端服务器可以用 IJwtDecoder 的这个不传递 key 的重载方法 IDictionary<string, object> DecodeToObject(string token),就可以在不验证的情况下获取 client_id 等信息。 也可以把 Identity Server 通过 Consul 进行服务治理。 Ocelot+Identity Server 实现了接口的权限验证,各个业务系统不需要再去做验证。 四、不能让客户端请求 token 上面是让客户端去请求 token,如果项目中这么搞的话,就把 client_id 特别是 secret 泄露给普通用户的。 正确的做法应该是,开发一个 token 服务,由这个服务来向 identity Server 请求 token,客户端向 token 服务发请求,把 client_id、secret 藏到这个 token 服务器上。当然这个服务器也要经过 Ocelot 转发。 五、用户名密码登录 如果 Api 和用户名、密码无关(比如系统内部之间 API 的调用),那么上面那样做就可以了,但是有时候需要用户身份验证的(比如 Android 客户端)。也就是在请求 token 的时候还要验证用户名密码,在服务中还可以获取登录用户信息。 修改的地方: 1、 ID4.IdServer 项目中增加类 ProfileService.cs using IdentityServer4.Models; using IdentityServer4.Services; using System.Linq; using System.Threading.Tasks; namespace ID4.IdServer { public class ProfileService : IProfileService { public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = context.Subject.Claims.ToList(); context.IssuedClaims = claims.ToList(); } public async Task IsActiveAsync(IsActiveContext context) { context.IsActive = true; } } } 增加类 ResourceOwnerPasswordValidator.cs using IdentityServer4.Models; using IdentityServer4.Validation; using System.Security.Claims; using System.Threading.Tasks; namespace ID4.IdServer { public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { //根据context.UserName和context.Password与数据库的数据做校验,判断是否合法 if (context.UserName == "yzk" && context.Password == "123") { context.Result = new GrantValidationResult( subject: context.UserName, authenticationMethod: "custom", claims: new Claim[] { new Claim("Name", context.UserName), new Claim("UserId", "111"), new Claim("RealName", "名字"), new Claim("Email", "qq@qq.com") }); } else { //验证失败 context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential"); } } } } 当然这里的用户名密码是写死的,可以在项目中连接自己的用户数据库进行验证。claims 中可以放入多组用户的信息,这些信息都可以在业务系统中获取到。 Config.cs 修改一下,主要是把GetClients中的AllowedGrantTypes属性值改为GrantTypes.ResourceOwnerPassword, 并且在AllowedScopes中加入 IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误 IdentityServerConstants.StandardScopes.Profile 修改后的 Config.cs using System.Collections.Generic; using IdentityServer4; using IdentityServer4.Models; namespace ID4.IdServer { public class Config { /// <summary> /// 返回应用列表 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> resources = new List<ApiResource>(); //ApiResource第一个参数是应用的名字,第二个参数是描述 resources.Add(new ApiResource("MsgAPI", "消息服务API")); resources.Add(new ApiResource("ProductAPI", "产品API")); return resources; } /// <summary> /// 返回客户端账号列表 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); clients.Add(new Client { ClientId = "clientPC1",//API账号、客户端Id AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("123321".Sha256())//秘钥 }, AllowedScopes = { "MsgAPI","ProductAPI",IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误 IdentityServerConstants.StandardScopes.Profile }//这个账号支持访问哪些应用 }); return clients; } } } Startup.cs 的 ConfigureServices 修改为 public void ConfigureServices(IServiceCollection services) { var idResources = new List<IdentityResource> { new IdentityResources.OpenId(), //必须要添加,否则报无效的 scope 错误 new IdentityResources.Profile() }; services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(idResources) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients())// .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() .AddProfileService<ProfileService>(); } 主要是增加了 AddInMemoryIdentityResources 、 AddResourceOwnerValidator 、AddProfileService 2、 修改业务系统 以 MsgService 为例 Nuget -> Install-Package IdentityServer4.AccessTokenValidation 然后 Startup.cs 的 ConfigureServices 中增加 services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:9500";//identity server 地址 options.RequireHttpsMetadata = false; }); Startup.cs 的 Configure 中增加 app.UseAuthentication(); 3、 请求 token 把报文头中的 grant_type 值改为 password,报文头增加 username、password 为用户名、密码。 像之前一样用返回的 access_token传递给请求的Authorization 中,在业务系统的 User中就可以获取到 ResourceOwnerPasswordValidator 中为用户设置的 claims 等信息了。 public void Send_MI(dynamic model) { string name = this.User.Identity.Name;//读取的就是"Name"这个特殊的 Claims 的值 string userId = this.User.FindFirst("UserId").Value; string realName = this.User.FindFirst("RealName").Value; string email = this.User.FindFirst("Email").Value; Console.WriteLine($"name={name},userId={userId},realName={realName},email={email}"); Console.WriteLine($"通过小米短信接口向{model.phoneNum}发送短信{model.msg}"); } 4、 独立登录服务器解决上面提到的“不能让客户端接触到 client_id、secret 的问题” 开发一个服务应用 LoginService public class RequestTokenParam { public string username { get; set; } public string password { get; set; } } using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace LoginService.Controllers { [Route("api/[controller]")] [ApiController] public class LoginController : ControllerBase { [HttpPost] public async Task<ActionResult> RequestToken(RequestTokenParam model) { Dictionary<string, string> dict = new Dictionary<string, string>(); dict["client_id"] = "clientPC1"; dict["client_secret"] = "123321"; dict["grant_type"] = "password"; dict["username"] = model.username; dict["password"] = model.password; //由登录服务器向IdentityServer发请求获取Token using (HttpClient http = new HttpClient()) using (var content = new FormUrlEncodedContent(dict)) { var msg = await http.PostAsync("http://localhost:9500/connect/token", content); string result = await msg.Content.ReadAsStringAsync(); return Content(result, "application/json"); } } } } 这样客户端只要向 LoginService 的 /api/Login/ 发请求带上 json 报文体 {username:"yzk",password:"123"}即可。客户端就不知道 client_secret 这些机密信息了。 把 LoginService 配置到 Ocelot 中。 参考文章:https://www.cnblogs.com/jaycewu/p/7791102.html 注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(11)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Thrift高效通讯 (完结)

一、 什么是 RPC Restful 采用 Http 进行通讯,优点是开放、标准、简单、兼容性升级容易; 缺点是性能略低。在 QPS 高或者对响应时间要求苛刻的服务上,可以用 RPC(Remote Procedure Call),RPC 由于采用二进制传输、TCP 通讯,所以通常性能更好。 .Net Core 下的 RPC(远程方法调用)框架有 gRPC、Thrift 等,都支持主流的编程语言。 RPC 虽然效率略高,但是耦合性强,如果兼容性处理不好的话,一旦服务器端接口升级,客户端就要更新,即使是增加一个参数,而 rest 则比较灵活。 最佳实践:对内一些性能要求高的场合用 RPC,对内其他场合以及对外用 Rest。比如 web 服务器和视频转码服务器之间通讯可以用 restful 就够了,转账接口用 RPC 性能会更高一些。 二、 Thrift 基本使用 参考资料:https://www.cnblogs.com/focus-lei/p/8889389.html 1、 下载thrift http://thrift.apache.org/ 把thrift-***.exe解压到磁盘,改名为thrift.exe(用起来方便一些) 2、 编写一个UserService.thrift文件(IDL) namespace csharp RuPeng.ThriftTest1.Contract service UserService{ SaveResult Save(1:User user) User Get(1:i32 id) list<User> GetAll() } enum SaveResult { SUCCESS = 0, FAILED = 1, } struct User { 1: required i64 Id; 2: required string Name; 3: required i32 Age; 4: optional bool IsVIP; 5: optional string Remark; } service定义的是服务类,enum 是枚举,struct是传入或者传出的复杂数据类型(支持对象级联)。 语法规范http://thrift.apache.org/docs/idl 根据thrift语法生成C#代码 cmd -> thrift.exe -gen csharp UserService.thrift 创建一个类库项目 ThriftTest1.Contract,作为客户端和服务器之间的共用协议,把上一步生成的代码放进项目。 项目nuget安装apache-thrift-netcore: Install-Package apache-thrift-netcore 3、 创建服务器端项目 ThriftTest1.Server,建一个控制台项目(放到 web 项目中或者在 Linux中用守护进程运行起来(SuperVisor等,类似Windows下的“Windows服务”)也可以)。 ThriftTest1.Server项目引用ThriftTest1.Contract 创建项目:ApplicationExtenssion.cs 编写实现类UserServiceImpl.cs public class UserServiceImpl : UserService.Iface { public User Get(int id) { User u = new User(); u.Id = id; u.Name = "用户" + id; u.Age = 6; return u; } public List<User> GetAll() { List<User> list = new List<User>(); list.Add(new User { Id = 1, Name = "yzk", Age = 18, Remark = "hello" }); list.Add(new User { Id = 2, Name = "rupeng", Age = 6 }); return list; } public SaveResult Save(User user) { Console.WriteLine($"保存用户,{user.Id}"); return SaveResult.SUCCESS; } } 修改Program下的Main函数 启动服务器端 TServerTransport transport = new TServerSocket(8800);//监听8800端口 var processor = new RuPeng.ThriftTest1.Contract.UserService.Processor(new UserServiceImpl());//设置实现类 TServer server = new TThreadPoolServer(processor, transport); server.Serve(); 监听8800端口 4、创建客户端项目也引用ThriftTest1.Contract项目 调用方法 using (TTransport transport = new TSocket("localhost", 8800)) using (TProtocol protocol = new TBinaryProtocol(transport)) using (var clientUser = new UserService.Client(protocol)) { transport.Open(); User u = clientUser.Get(1); Console.WriteLine($"{u.Id},{u.Name}"); } 三、一个服务器中放多个服务 0.9.1之前只支持一个服务器一个服务,这也是建议的做法。之后支持多路服务在thrift中增加一个服务 修改UserService.thrift文件 添加以下内容 然后重新生成替换 service CalcService{ i32 Add(1:i32 i1,2:i32 i2) } 服务器: 1.创建CalcServiceImpl文件实现CalcService服务 2.修改Main方法如下: TServerTransport transport = new TServerSocket(8800); var processorUserService = new RuPeng.ThriftTest1.Contract.UserService.Processor(new UserServiceImpl()) var processorCalcService = new RuPeng.ThriftTest1.Contract.CalcService.Processor(new CalcServiceImpl()); var processorMulti = new TMultiplexedProcessor(); processorMulti.RegisterProcessor("userService", processorUserService); processorMulti.RegisterProcessor("calcService", processorCalcService); TServer server = new TThreadPoolServer(processorMulti, transport); server.Serve(); 客户端: using (TTransport transport = new TSocket("localhost", 8800)) using (TProtocol protocol = new TBinaryProtocol(transport)) using (var protocolUserService = new TMultiplexedProtocol(protocol, "userService")) using (var clientUser = new UserService.Client(protocolUserService)) using (var protocolCalcService = new TMultiplexedProtocol(protocol,"calcService")) using (var clientCalc = new CalcService.Client(protocolCalcService)) { transport.Open(); User u = clientUser.Get(1); Console.WriteLine($"{u.Id},{u.Name}"); Console.WriteLine(clientCalc.Add(1, 2)); } https://www.cnblogs.com/focus-lei/p/8889389.html (*)新版:thrift.exe -gen netcore UserService.thrift(支持.net Core) 貌似支持还不完善(http://www.cnblogs.com/zhaiyf/p/8351361.html )还不能用,编译也有问题,值得期待的是:支持异步。 四、Java 等其他语言的融入 和使用Restful做服务一样,Java也可以调用、也可以做Thrift服务,演示一下java调用c#写的Thrift服务的例子 Java编译器版本需要>=1.6 Maven添加组件包的支持(thrift maven版本一定要和生成代码的thrift的版本一致): <dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> <version>0.11.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> 在thrift的IDL文件中加入一行(各个语言的namespace等参数可以共存) 修改UserService.thrift文件 添加以下代码就可以控制生成的java类的报名,最好按照java的命名规范来。 namespace java com.rupeng.thriftTest1.contract 产生java代码 thrift.exe -gen java UserService.thrift Java代码: import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; public class Main { public static void main(String[] args) throws Exception { System.out.println("客户端启动...."); TTransport transport = new TSocket("localhost", 8800, 30000); TProtocol protocol = new TBinaryProtocol(transport); UserService.Client client = new UserService.Client(protocol); transport.open(); User result = client.Get(1); System.out.println(result.getAge()+result.getName()+result.getRemark()); }} 也可以用Java写服务器,C#调用。当然别的语言也可以。 接口设计原则“API design is like sex: Make one mistake and support it for the rest of your life” 五、 Thrift+Consul 服务发现 注册和发现和Rest方式没有什么区别。 consul支持tcp健康监测:https://www.consul.io/docs/agent/checks.html 因为 Thrift 一般不对外,所以一般不涉及和 API 网关结合的问题 gRPC 的优势:支持异步;支持 Http2。没有Thrift性能高 总结 回顾一下我们学到的微服务:服务治理和服务发现、熔断降级、API 网关。 不是所有项目都适合微服务架构,互联网项目及结构复杂的企业信息系统才可以考虑微服务架构。 设计微服务架构,模块拆分的原则:可以独立运行,尽量服务间不要依赖,即使依赖层级也不要太深,不要想着还要 join。按业务划分、按模块划分。 扩展知识: 1、 分布式跟踪、日志服务、监控等对微服务来说非常重要 2、 gRPC 另外一个 RPC 框架,gRPC 的.Net Core 支持异步。百度“.net core grpc” https://www.jianshu.com/p/f5e1c002047a 3、 https://github.com/neuecc/MagicOnion 可以参考下这位日本 mvp 写的 grpc 封装,不需要定义接口文件。 4、 nanofabric https://github.com/geffzhang/NanoFabric简单分析 5、 Surging https://github.com/dotnetcore/surging 6、 service fabric https://azure.microsoft.com/zh-cn/documentation/learning-paths/service-fabric/ 7、 Spring Cloud 入门视频:http://www.rupeng.com/Courses/Chapter/755 8、 steeltoe http://steeltoe.io/ 参 考 文 章 https://mp.weixin.qq.com/s/g9w-qgT2YHyDX8OE5q-OHQ 9、 限流算法 https://mp.weixin.qq.com/s/bck0Q2lDj_J9pLhFEhqm9w 10、https://github.com/PolicyServer/PolicyServer.Local 认证 + 授权 是两个服务, identityserver 解决了认证 ,PolicyServer 解决授权 11、Using Polly with HttpClient factory from ASPNET Core 2.1 https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory 12、CSharpKit 微服务工具包 http://www.csharpkit.com/ 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

优秀的个人博客,低调大师

(8)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot网关(Api GateWay)

说到现在现有微服务的几点不足: 1) 对于在微服务体系中、和 Consul 通讯的微服务来讲,使用服务名即可访问。但是对于手 机、web 端等外部访问者仍然需要和 N 多服务器交互,需要记忆他们的服务器地址、端 口号等。一旦内部发生修改,很麻烦,而且有时候内部服务器是不希望外界直接访问的。 2) 各个业务系统的人无法自由的维护自己负责的服务器; 3) 现有的微服务都是“我家大门常打开”,没有做权限校验。如果把权限校验代码写到每 个微服务上,那么开发工作量太大。 4) 很难做限流、收费等。 ocelot 中文文档:https://blog.csdn.net/sD7O95O/article/details/79623654 资料:http://www.csharpkit.com/apigateway.html 官网:https://github.com/ThreeMammals/Ocelot 腾讯.Net 大队长“张善友”是项目主力开发人员之一。 一、 Ocelot 基本配置 Ocelot 就是一个提供了请求路由、安全验证等功能的 API 网关微服务。 建一个空的 asp.net core 项目。 Install-Package Ocelot 项目根目录下创建 configuration.json ReRoutes 下就是多个路由规则 { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5001 } ], "UpstreamPathTemplate": "/MsgService/{url}", "UpstreamHttpMethod": [ "Get", "Post" ] }, { "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 5003 } ], "UpstreamPathTemplate": "/ProductService/{url}", "UpstreamHttpMethod": [ "Get", "Post" ] } ] } Program.cs的CreateWebHostBuilder中 //在原基础上添加上这俩行//只是监听这个端口 你自己配置也可以.UseUrls("http://127.0.0.1:8888").ConfigureAppConfiguration((hostingContext, builder) => { builder.AddJsonFile("configuration.json", false, true); }) 在ConfigureAppConfiguration 中AddJsonFile是解析json配置文件的方法。 为了确保直接在bin 下直接dotnet运行的时候能找到配置文件,所以要在vs中把配置文件的【复制到输出目录】设置为【如 果如果较新则复制】。 Startup.cs中 通过构造函数注入一个 private IConfiguration Configuration; public void ConfigureServices(IServiceCollection services) { services.AddOcelot(Configuration); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseOcelot().Wait();//不要忘了写Wait } 这样当访问http://127.0.0.1:8888/MsgService/Send?msg=aaa的时候就会访问 http://127.0.0.1:5002/api/email/Send?msg=aaa configuration.json中UpstreamHttpMethod表示对什么样的请求类型做转发。 二、 Ocelot+Consul 上面的配置还是把服务的ip地址写死了,Ocelot可以和Consul通讯,通过服务名字来配置。 只要改配置文件即可 { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "UpstreamPathTemplate": "/MsgService/{url}", "UpstreamHttpMethod": [ "Get", "Post" ], "ServiceName": "MsgService", //服务名字 "LoadBalancerOptions": { "Type": "RoundRobin" //指定一个负载均衡算法: LeastConnection 最少的连接 RoundRobin 轮训 }, "UseServiceDiscovery": true //使用服务发现 } ], "GlobalConfiguration": { //全局配置 "ServiceDiscoveryProvider": { //连接这台Consul服务器 "Host": "localhost", "Port": 8500 } } } 有多个服务就 ReRoutes 下面配置多组即可 访问 http://localhost:8888/MsgService/SMS/Send_MI 即可,请求报文体 {phoneNum:"110",msg:"aaaaaaaaaaaaa"}。 表示只要是/MsgService/开头的都会转给后端的服务名为" MsgService "的一台服务器,转发的路径是"/api/{url}"。 LoadBalancerOptions 中"LeastConnection"表示负载均衡算法是“选择当前最少连接数的服务器”,如果改为 RoundRobin 就是“轮询”。 ServiceDiscoveryProvider 是 Consul 服务器的配置。 Ocelot 因为是流量中枢,也是可以做集群的。 (*) 也 支 持 Eureka 进 行 服 务 的 注 册 、 查 找 (http://ocelot.readthedocs.io/en/latest/features/servicediscovery.html),也支持访问 Service Fabric 中的服务(http://ocelot.readthedocs.io/en/latest/features/servicefabric.html)。 三、Ocelot 其他功能简单介绍 1、 限流: 文档:http://ocelot.readthedocs.io/en/latest/features/ratelimiting.html 需要和 Identity Server 一起使用,其他的限速是针对 clientId 限速,而不是针对 ip 限速。比如我调用微博的api开发了一个如鹏版新浪微博,我的 clientid 是 rpwb,然后限制了 1 秒钟只能调用 1000 次,那么所有用如鹏版微博这个 app 的所有用户加在一起,在一秒钟之内,不能累计超过 1000 次。目前开放式 api 的限流都是这个套路。 如果要做针对 ip 的限速等,要自己在 Ocelot 前面架设 Nginx 来实现。 2、 请求缓存 http://ocelot.readthedocs.io/en/latest/features/caching.html 只支持 get,只要 url 不变,就会缓存。 3、 QOS(熔断器) http://ocelot.readthedocs.io/en/latest/features/qualityofservice.html 注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的 现在的努力只是为了更好的将来,将来你一定不会后悔你现在的努力。一起加油吧!!! C#/.NetCore技术交流群:608188505欢迎加群交流 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册