1. 主要内容概要
①微服务架构解析,优缺点、挑战与转变
②MicroService全组件解析
③Consul注册,心跳检测,服务发现
2. 架构演进
单体应用
单体应用时代:应用程序就是一个项目,在一个进程里面运行。
- 开发简单,集中管理,没有分布式的损耗
- 不好维护,升级困难,无法快捷迭代,稳定性也差
垂直拆分
- 垂直拆分,独立部署和维护,分而治之!
- 拆分越多,存储越复杂,系统间重复的东西也越多。
- 垂直拆分后,还是单体模式-单体思维
分布式服务—微服务架构
- 一系列服务组装成系统
- 独立部署,独立运行
- 独立开发和维护
- 分布式管理
- 强调隔离性
单体时,是调用方法 BLL—DAL;分布式,是调用服务;
随着分布式技术的成熟,设计系统架构时就以服务分拆为手段,这种风格就是微服务架构。
微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。
- 概念:把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。
- 定义:围绕业务领域组件来创建应用,这些应用可独立地进行开发、管理和迭代。在分散的组件中使用云架构和平台式部署、管理和服务功能,使产品交付变得更加简单。
- 本质:用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。
SOA面向服务架构
SOA(Service-OrientedArchitecture)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。
接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构件在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。信息孤岛—数据总线,SOA把服务集成,定好规范,做重构。
SOA vs Micro Service
- SOA是为重用,微服务架构是为了重写(SOA是为了整合原有服务,微服务通常是重写,独立部署,独立维护)
- SOA更水平,微服务是垂直的(SOA: 业务逻辑层-数据访问层-横向的,微服务是垂直的—是一个具体功能点-从上往下)
- SOA自上而下,微服务自下而上
3. 践行微服务
服务通信1
Redis/DB/Queue/硬盘文件
- 被动式通信
- 门槛低
服务通信2
WebService/WCF/WebApi/甚至ashx,aspx
- 主动触发
- 数据序列化传递
- 跨平台
- 跨语言
- http穿透防火墙
服务通信3
RPC-Remote Procedure Call
- .Net Remoting:.Net平台独有的,不支持跨平台
- gRPC:高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 设计。
Nginx
- 可以屏蔽服务实例细节
- 单纯是负载均衡
- 被动获取实例,有变化是不知道
Consul
- 负载均衡
- 服务注册与发现
- 健康检查
命令行启动:
- consul_1.6.2.exe agent –dev
实例:建立一个webDemo和一个API项目,demo里面调用API,但是这个API是多个实例的,用consul来做注册于发现。
- Step1: 启动consul,
consul_1.6.2.exe agent –dev
- Step2: 改造API, 用命令启动API:
dotnet WebApi.dll --urls="http://*:5726" --ip="127.0.0.1" --port=5726
引入Consul的Nuget包,在API注册的地方可以这么写,注册只需要一次,通常在configure组件管道时候注册:
public static void ConsulRegist(this IConfiguration configuration)
{
ConsulClient client = new ConsulClient(c =>
{
c.Address = new Uri("http://localhost:8500/");
c.Datacenter = "dc1";
});
string ip = configuration["ip"];
int port = int.Parse(configuration["port"]);//命令行参数必须传入
//int weight = string.IsNullOrWhiteSpace(configuration["weight"]) ? 1 : int.Parse(configuration["weight"]);//命令行参数必须传入
client.Agent.ServiceRegister(new AgentServiceRegistration()
{
ID = "service" + Guid.NewGuid(),//唯一的
Name = "UserService",//组名称-Group
Address = ip,//其实应该写ip地址
Port = port,//不同实例
//Tags = new string[] { weight.ToString() },//标签
Check = new AgentServiceCheck()//配置心跳检查的
{
Interval = TimeSpan.FromSeconds(12),
HTTP = $"http://{ip}:{port}/Api/Health/Index",
Timeout = TimeSpan.FromSeconds(5),
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5)
}
});
Console.WriteLine($"http://{ip}:{port}完成注册");
}
HealthCheck的实现是依赖于/Api/Health/Index
这个方法,我们在API中去实现这个方法,直接返回OK就可以。
[Route("api/[controller]")]
[ApiController]
public class HealthController : ControllerBase
{
private readonly ILogger<HealthController> _logger;
private readonly IConfiguration _iConfiguration;
public HealthController(ILogger<HealthController> logger, IConfiguration configuration)
{
_logger = logger;
this._iConfiguration = configuration;
}
[HttpGet]
[Route("Index")]//拼接到控制器上的route
public IActionResult Index()
{
this._logger.LogWarning($"This is HealthController {this._iConfiguration["Port"]}");
return Ok();//HttpStatusCode--200
}
- Step3: 客户端demo通过consul调用API实例的方法
引入Consul的Nuget包,然后去发现已经向Consul注册了的API,实现如下:
using (ConsulClient client = new ConsulClient(c =>
{
c.Address = new Uri("http://localhost:8500/");
c.Datacenter = "dc1";
}))
{
var kvPair = client.KV.Get("zcq").Result;
if (kvPair.Response != null && kvPair.Response.Value != null)
{
var a=Encoding.UTF8.GetString(kvPair.Response.Value, 0, kvPair.Response.Value.Length);
var b = JsonConvert.DeserializeObject(a);
}
var dictionary = client.Agent.Services().Result.Response;
string message = "";
foreach (var keyValuePair in dictionary)
{
AgentService agentService = keyValuePair.Value;
this._logger.LogWarning($"{agentService.Address}:{agentService.Port} {agentService.ID} {agentService.Service}");//找的是全部服务 全部实例 其实可以通过ServiceName筛选
message += $"{agentService.Address}:{agentService.Port};";
}
//获取当前consul的全部服务
base.ViewBag.Message = message;
}
下面是调用其中一个API实例:
获取到一个KeyValuePair类型的List,然后根据不同的负载均衡策略去取实例:
//Step1 获取所有列表
var dictionary = client.Agent.Services().Result.Response;
var list = dictionary.Where(k => k.Value.Service.Equals(groupName, StringComparison.OrdinalIgnoreCase));//获取consul上全部对应服务实例
KeyValuePair<string, AgentService> keyValuePair = new KeyValuePair<string, AgentService>();
//Step2 负载均衡策略
{
keyValuePair = list.First();//直接拿的第一个
}
private static int iSeed = 0;//没考虑溢出问题
{
var array = list.ToArray();
//随机策略---平均策略
keyValuePair = array[new Random(iSeed++).Next(0, array.Length)];
}
{
var array = list.ToArray();
//轮询策略---平均策略
keyValuePair = array[iSeed++ % array.Length];
}
{
//权重---注册服务时指定权重,分配时获取权重并以此为依据
//通过Tags来实现
List<KeyValuePair<string, AgentService>> pairsList = new List<KeyValuePair<string, AgentService>>();
foreach (var pair in list)
{
int count = int.Parse(pair.Value.Tags?[0]);
for (int i = 0; i < count; i++)
{
pairsList.Add(pair);
}
}
keyValuePair = pairsList.ToArray()[new Random(iSeed++).Next(0, pairsList.Count())];
}
//Step3 根据获取到的实例地址 来访问API
resultUrl = $"{uri.Scheme}://{keyValuePair.Value.Address}:{keyValuePair.Value.Port}{uri.PathAndQuery}";
string result = WebApiHelperExtend.InvokeApi(resultUrl);
userList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Users>>(result);
Gateway
为什么需要网关?
有了Consul,使用服务名即可访问。但手机、web端等外部访问者仍然需要和N多服务器交互,需要记忆他们的服务器地址、端口号等。一旦内部发生修改,很麻烦,而且有时候内部服务器是不希望外界直接访问的—需要路由功能!
- 客户端该如何访问服务?这么多服务地址。
- 单个服务下线/升级/更新
- Authentication/ Authorization
好处:
- 各个业务系统轻松独立维护服务器;
- 复用权限校验;
- 限流、熔断、降级、收费等
Gateway:
- 提供统一服务入口,让微服务对前台透明
- 聚合后台的服务,节省流量,提升性能
- 提供安全,过滤,流控等API管理功能
Ocelot
Ocelot就是一个提供了请求路由、安全验证等功能的API网关微服务
搭建网关Gateway
- Step1: 新建一个API的项目,充当网关。引用Nuget包: Ocelot 和Ocelot.Provider.Consul和Ocelot.Provide.Polly这三个。
- Step2: 构建管道,只有Ocelot管道,其他都不要。
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul().AddPolly();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseOcelot();
}
- Step3: 添加配置文件, 在start中引用oelot的定制配置文件,
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(c =>
{
c.AddJsonFile("configuration.json", optional: false, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Ocelot的配置文件可以根据不同的情况进行配置:
情况一: 单地址
解读一下这个配置文件,上游有任何到/T5726/{url}
这个地址的请求,都会被Ocelot进行待请求到localhost:5726//api/{url}
这个地址上去,这里不是转发,其实是代请求。
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5726 //服务端口
} //可以多个,自行负载均衡
],
"UpstreamPathTemplate": "/T5726/{url}", //网关地址--url变量 //冲突的还可以加权重Priority
"UpstreamHttpMethod": [ "Get", "Post" ]
}
]
}
情况二: 多地址多实例
其实是对情况一的拓展,现在支持多种上游路径的代请求了,例如/T5726/{url}
和/T5727/{url}
和/T5728/{url}
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5726 //服务端口
} //可以多个,自行负载均衡
],
"UpstreamPathTemplate": "/T5726/{url}", //网关地址--url变量 //冲突的还可以加权重Priority
"UpstreamHttpMethod": [ "Get", "Post" ]
},
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5727 //服务端口
}
],
"UpstreamPathTemplate": "/T5727/{url}", //网关地址--url变量
"UpstreamHttpMethod": [ "Get", "Post" ]
},
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5728 //服务端口
}
],
"UpstreamPathTemplate": "/T5728/{url}", //网关地址--url变量
"UpstreamHttpMethod": [ "Get", "Post" ]
}
]
}
情况三: 单地址多实例负载均衡(没有结合consul)
配置多个下游的Host和Port,做负载均衡。这并没有结合服务注册与发现,不便于排查,维保。
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5726 //服务端口
} //可以多个,自行负载均衡
,
{
"Host": "localhost",
"Port": 5727 //服务端口
},
{
"Host": "localhost",
"Port": 5728 //服务端口
}
],
"UpstreamPathTemplate": "/T5/{url}", //网关地址--url变量 //冲突的还可以加权重Priority
"UpstreamHttpMethod": [ "Get", "Post" ],
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询 LeastConnection-最少连接数的服务器 NoLoadBalance不负载均衡
}
}
]
}
情况三: 单地址多实例负载均衡(结合consul)
首先,如果访问/TConsul/{url}
, 那么就代请求去到/api/{url}
,但是这个时候呢,没有写死API服务实例,而是通过consul来查找一个实例,完成请求。需要提供ServiceName来获取服务列表,会用到Ocelot.Provider.Consul这个包。
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul();
services.AddControllers();
}
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/TConsul/{url}", //网关地址--url变量
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "UserService", //consul服务名称
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询 LeastConnection-最少连接数的服务器 NoLoadBalance不负载均衡
},
"UseServiceDiscovery": true
}
],
"GlobalConfiguration": {
"BaseUrl": "http://127.0.0.1:6299", //网关对外地址
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul" //由Consul提供服务发现
}
}
}
Ocelot:实现了客户端和服务实例的隔绝—保护—节约IP—提高效率,Consul—完成了集群管理,发现—健康检查—下线。
- 路由—核心功能
- 集群负载均衡
假如不是微服务,那么以前就是要具体发哦方法;现在微服务之后,就是靠记录地址。
服务实例也不会暴露,某个服务实例下线了,能自动屏蔽。
Gateway还能缓存,可能很多服务都需要用户服务的数据,那么在gateway级别就可以做缓存,下面会涉及。
Polly
Polly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。
情况四: 单地址多实例负载均衡+Consul+Polly
可以接着上面的实践,继续配置一下Startup,改一下Ocelot的配置文件即可。这里会用到Nuget包Ocelot.Provide.Polly
。
```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul().AddPolly;
services.AddControllers();
}
解读一下配置文件:
FileCacheOptions
这个是配置缓存,10秒的缓存,可以减少服务实例的压力。RateLimitOptions
这个是限流配置,限制单位时间内的访问量(失败一部分比垮掉强),限流的时候通过RateLimitOptions
这个返回自定义状态码和消息。5min之内只允许访问五次,但是触发限流5秒之后可以继续访问,不是绝对的。QoSOptions
这个是熔断,类似于保险丝,单位时间内多次失败,就直接不请求。会检测Exception发生的次数。
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}", //服务地址--url变量
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/consul/{url}", //网关地址--url变量
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "ZhaoxiUserService", //consul服务名称
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询 LeastConnection-最少连接数的服务器 NoLoadBalance不负载均衡
},
"UseServiceDiscovery": true,
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, //允许多少个异常请求
"DurationOfBreak": 10000, // 熔断的时间,单位为ms
"TimeoutValue": 10000 //如果下游请求的处理时间超过多少则自如将请求设置为超时 默认90秒
}
//"RateLimitOptions": {
// "ClientWhitelist": [], //白名单
// "EnableRateLimiting": true,
// "Period": "5m", //1s, 5m, 1h, 1d jeffzhang
// "PeriodTimespan": 5, //多少秒之后客户端可以重试
// "Limit": 5 //统计时间段内允许的最大请求数量
//},
//"FileCacheOptions": {
// "TtlSeconds": 10
//} //"缓存"
}
],
"GlobalConfiguration": {
"BaseUrl": "http://127.0.0.1:6299", //网关对外地址
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul" //由Consul提供服务发现
},
//"RateLimitOptions": {
// "QuotaExceededMessage": "Too many requests, maybe later? 11", // 当请求过载被截断时返回的消息
// "HttpStatusCode": 666 // 当请求过载被截断时返回的http status
//}
}
}
鉴权&授权
传统的用户识别
- 基于cookie-session
- 是把用户信息保存在服务器,每次请求带上标识匹配资源
- 没有分布式架构,无法支持横向扩展。
基于token的安全验证体系。
鉴权授权:
- 鉴权中心—根据账号密码颁发token
- 带着Token就可以访问API,API认可token,不需要去鉴权中心校验
- 第三方API也认可Token
- SSO: Single Sign On
- 防止抵赖-防止篡改-信息传递
JWT
JWT:
官网:https://jwt.io/
- 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
- 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
JWT令牌结构
- Header 头
{ “alg”: “HS256”, “typ”: “JWT”}
- Payload 有效载荷
JWT 默认是不加密的,任何人都可以读到
- Signature 签名–防止抵赖-防止篡改
=HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
xxxxx.yyyyy.zzzzz
私钥加密,只有对应的公钥才能解密
*JWT令牌示例 *
JWT实例
- Step1: 首先改造一下API,给API加上身份认证。需要加上
[Microsoft.AspNetCore.Authorization.Authorize]
这个特性,并在管道里面配置Authentication
的中间件。
身份认证特性标签:
[Route("api/[controller]/[action]")]
[ApiController]
[Microsoft.AspNetCore.Authorization.Authorize]
public class UsersNewController : ControllerBase
{
// GET api/Users
[HttpGet]
public IEnumerable<Users> Get()
{
return _userList;
}
// GET api/Users/5
[HttpGet]
[Microsoft.AspNetCore.Authorization.AllowAnonymous]
public Users GetUserByID(int id)
{
base.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");//允许跨域
//throw new Exception("1234567");
string idParam = base.HttpContext.Request.Query["id"];
var user = _userList.FirstOrDefault(users => users.UserID == id);
if (user == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return user;
}
}
配置管道,注意一下身份认证管道的顺序。
public void ConfigureServices(IServiceCollection services)
{
#region jwt校验
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = this.Configuration["audience"],//Audience
ValidIssuer = this.Configuration["issuer"],//Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.Configuration["SecurityKey"])),//拿到SecurityKey
//AudienceValidator = (m, n, z) =>
//{
// return m != null && m.FirstOrDefault().Equals(this.Configuration["audience"]);
//},//自定义校验规则,可以新登录后将之前的无效
};
});
#endregion
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
#region jwt
app.UseAuthentication();//注意添加这一句,启用验证 身份认证
#endregion
app.UseRouting();
app.UseAuthorization();
Console.WriteLine(this.Configuration["ip"]);
Console.WriteLine(this.Configuration["port"]);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
);
//实例启动时执行,且只执行一次
this.Configuration.ConsulRegist();
}
其中的配置文件参考示例如下:
"audience": "http://localhost:5726",
"issuer": "http://localhost:5726",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
- Step2: 创建鉴权中心,再创建webAPI项目,专门用于产生token。登录时访问认证中心的login方法,来产生一个token.
颁发token的API:
[Route("Login")]
[HttpPost]
public string Login(string name, string password)
{
if ("LoginName".Equals(name) && "123456".Equals(password))//应该数据库
{
string token = this._iJWTService.GetToken(name);
return JsonConvert.SerializeObject(new
{
result = true,
token
});
}
else
{
return JsonConvert.SerializeObject(new
{
result = false,
token = ""
});
}
}
具体产生token的服务:
public interface IJWTService
{
string GetToken(string UserName);
}
public class JWTService : IJWTService
{
private readonly IConfiguration _configuration;
public JWTService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetToken(string UserName)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, UserName),
new Claim("NickName","zhengchaoqiang"),
new Claim("Role","Administrator"),//传递其他信息
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["issuer"],
audience: _configuration["audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(5),//5分钟有效期
signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken;
}
}
其中配置文件信息如下:
"AllowedHosts": "*",
"audience": "http://localhost:5726",
"issuer": "http://localhost:5726",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
注意:
- Claims (Payload)
Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段,除了规定的字段外,可以包含其他任何 JSON 兼容的字段。
iss: The issuer of the token,token 是给谁的
sub: The subject of the token,token 主题
exp: Expiration Time。 token 过期时间,Unix 时间戳格式
iat: Issued At。 token 创建时间, Unix 时间戳格式
jti: JWT ID。针对当前 token 的唯一标识
- 代码演示的是对称加密,所以只有一个key,在返回的信息里面是没有的。非对称的加密的话,那样是把解密key公开的,前面是后台用私钥加密的。可以保证别人解密后,拿到的数据,跟前面2部分hash后的结果一致,保证没有篡改。
Step3: 客户端将token放入请求的头里面,这样可以请求API了,不会有401错误了。
在API端接收客户端的请求时,可以拿到这些Claim信息的,在context中。public IEnumerable<Users> GetUserByName(string userName) { var nickName = HttpContext.AuthenticateAsync().Result.Principal.Claims.FirstOrDefault(c => c.Type.Equals("NickName"))?.Value; Console.WriteLine($"This is GetUserByName 校验 {nickName}"); var role = HttpContext.AuthenticateAsync().Result.Principal.Claims.FirstOrDefault(c => c.Type.Equals("role"))?.Value; Console.WriteLine($"This is role 校验 {role}"); string userNameParam = base.HttpContext.Request.Query["userName"]; return _userList.Where(p => string.Equals(p.UserName, userName, StringComparison.OrdinalIgnoreCase)); }
关于Identity Server的详细解析,后面计划专门来写一写。这里仅仅对JWT做了一些解析。
分布式事务-CAP定理
- Consistency 一致性
- Availability 可用性
- Partition tolerance 分区容错
分布式系统下,网络出错是必然存在的—也就是不可靠的。
在分区容错一定出现的情况,C和A是不能同时满足的。
强一致性——2PC
2PC(two-phase commit protocol)
强一致性,但是没有可用性。
这种方式在分布式系统靠谱吗?微服务时能靠谱吗?多个节点,这样卡顿,是无法可用的。
0.99 来个10个节点。
只是解决小范围,或者强制要求一致性。
*CA *
其实就是单体应用,可以保证一致性和可用性。
Base理论:
- Basically Available(基本可用)
- (最终一致性)来回确认
- Soft state(软状态)
- Eventually consistent
微服务架构里面,可用性是最重要的;
思想是最重要,指引方向;
TCC(Try-Confirm-Cancel)
把一个动作分成三步,保证最终一致性。放新数据之前会先把旧的数据备份好。
编程量很大,数据稳定,银行和政府部门会用这种。
本地消息表
MQ分布式事务–本地消息表–基于消息的一致性。这种方式比较常用,尤其是互联网行业。
- 上游投递消息
- 下游获取消息
- 上游投递稳定性
- 下游接受稳定性
Saga
来自于华为,拆分成小事务。
分布式追踪
分布式追踪和APM的Server端,它将包含Collector,Storage,独立的Web UI,并使用Open Tracing规范来设计追踪数据。
Skywalking
例如:业务延迟很大,怎么定位问题?
日志收集分析
Exceptionless:开源的日志收集和分析框架,能为应用程序提供实时错误、特性和日志报告。
配置忠心
Apollo配置中心:
微服务架构环境中,项目中配置文件比较繁杂,而且不同环境的不同配置修改相对频繁,每次发布都需要对应修改配置,如果配置出现错误,需要重新打包发布,时间成本较高,
因此需要做统一的配置中心,能做到自动更新配置文件信息
Docker
容器化快速部署。
k8s
容器编排。