gRPC的简单使用

前言

八月初的时候,在公司内部做了一个主题为《gRPC的简单使用》的分享,其实就是和小伙伴们扯扯淡,现在抽空回忆一下,也算是一个小小的总结吧。

现在市面上耳熟能详的RPC框架也很多,下面列举几个遇到比较多的。

  1. 谷歌的gRPC

  2. 推特的Thrift

  3. 阿里的Dubbo

  4. 。。。。

它们都是支持多语言的,相对来说,这三个之中,Dubbo支持的语言略微少一点。现在在一个公司内都能见到多种语言的技术栈都已经是十分常见的事了,好比我司,都有JAVA,C#,Python三种语言了,所以在多语言支持这方面,在技术选型的时候,肯定是要有所考虑的。

下面进入正式的主题,gRPC。

gRPC的简单介绍

640?wx_fmt=png

gRPC是一个现代的开源高性能RPC框架,可以在任何环境中运行。它可以高效地将数据中心内和跨数据中心的服务连接起来,并支持可插拔的负载平衡、跟踪、健康检查和身份验证。同时,它还把设备,移动应用程序和浏览器连接到后端服务的分布式计算变得很容易。

gRPC有什么优点呢?

  1. 简单的服务定义 (使用Protocol Buffers定义服务,这是一个功能强大的二进制序列化工具集和语言)

  2. 跨语言和平台工作 (在微服务式架构中有效地连接多语言服务(10+种语言支持)并能自动为各种语言和平台的服务生成惯用的客户端和服务器存根)

  3. 快速启动并扩展 (使用单行安装运行时和开发环境,并使用框架每秒扩展到数百万个RPC)

  4. 双向流媒体和集成的身份验证 (双向流媒体和集成的身份验证 基于http/2的传输的双向流和完全集成的可插拔身份验证)

gRPC在使用的时候有4种模式供我们选择

  1. 一元RPC(Unary RPCs ):这是最简单的定义,客户端发送一个请求,服务端返回一个结果

  2. 服务器流RPC(Server streaming RPCs):客户端发送一个请求,服务端返回一个流给客户端,客户从流中读取一系列消息,直到读取所有消息

  3. 客户端流RPC(Client streaming RPCs ):客户端通过流向服务端发送一系列消息,然后等待服务端读取完数据并返回处理结果

  4. 双向流RPC(Bidirectional streaming RPCs):客户端和服务端都可以独立向对方发送或接受一系列的消息。客户端和服务端读写的顺序是任意。

我们要根据具体的场景来决定选择那一种。

这里只介绍一元RPC。正常来说,一元RPC应该可以满足我们日常60~70%的需求了吧。

基本用法

gRPC的基本用法可以简单的分为三个点:

  • 服务的定义,即proto文件的编写

  • 服务端代码编写

  • 客户端代码编写

下面我们依次来看一下

服务的定义

既然要定义一个服务,肯定是知道了这个服务要完成什么事之后。

在定义之前,要对proto3和proto2有所了解。不过proto3是推荐的格式。所以我们基本上只要用proto3就可以了。

下面先来看一个后面要用到的proto文件。

syntax = "proto3";option csharp_namespace = "XXXService";package UserInfo;service UserInfoService {  rpc GetList(GetUserListRequest) returns (GetUserListReply){}  rpc GetById(GetUserByIdRequest) returns (GetUserByIdRelpy){}  rpc Save(SaveUserRequest) returns (SaveUserReply){}}message GetUserByIdRequest {    int32 id = 1;}message GetUserByIdRelpy{    int32 id = 1;    string name = 2;    int32 age = 3;    int64 create_time = 4;}message GetUserListRequest {    int32 id = 1;    string name = 2;}message GetUserListReply {  message MsgItem {    int32 id = 1;    string name = 2;    int32 age = 3;    int64 create_time = 4;   }   int32 code = 1;   string msg = 2;   repeated MsgItem data = 3;}message SaveUserRequest {    string name = 1;    int32 age = 2;}message SaveUserReply {   int32 code = 1;   string msg = 2;}

它有下面的几个部分

  1. syntax , 指定要用那个版本的语法

  2. service , 指定rpc服务的接口,简单理解成我们平时定义的接口

  3. message , 指定要传输的消息体,简单理解成我们平常用的 DTO

  4. package , 指定包名

  5. option , 可选参数的定义,不同语言有不同的选项

其实看上去还是比较容易懂的。至少一眼看过去能知道是些什么意思。

如果对proto3还没有了解的,可以参考这个文档Language Guide (proto3),里面很清楚的介绍了一些数据类型和不同语言数据类型的对应关系。

这里有一个要注意的是,时间类型,在proto3中,没有datetime类型,过去很长一段时间,我们是只能用时间戳来表示时间,也就是定义一个长整型,现在是可以用timestamp表处理了。

在写服务端和客户端代码之前,我们需要根据proto文件生成对应的代码。

一个命令即可搞定。

protoc                                                                             path/to/file.proto

现在时代进步的这么快,不少语言已经有工具做了集成,可以在build项目的时候就生成对应的文件了,不需要我们再单独去执行一次上面的那个命令。

好比我们的.NET项目,可以在ItemGroup中直接指定Protobuf,然后告诉它,proto文件是那个,是要生成服务端代码还是客户端代码。

可以看看下面这个具体的例子。

<Project Sdk="Microsoft.NET.Sdk.Web">  <PropertyGroup>    <TargetFramework>netcoreapp2.1</TargetFramework>  </PropertyGroup>  <ItemGroup>    <Protobuf Include="Protos\userinfo.proto" GrpcServices="Server" />  </ItemGroup>    <ItemGroup>    <PackageReference Include="Microsoft.AspNetCore.App" />    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />    <PackageReference Include="Google.Protobuf" Version="3.8.0" />    <PackageReference Include="Grpc.Core" Version="1.22.0" />    <PackageReference Include="Grpc.Tools" Version="1.22.0">      <PrivateAssets>all</PrivateAssets>      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>    </PackageReference>  </ItemGroup></Project>

再往下,就是写代码了。

服务端代码编写

服务端代码分两部分,一部分是服务具体的实现,一部分是服务怎么起来。

先来看看服务的具体实现。

namespace MyBasedServiceA{    using Grpc.Core;    using System.Linq;    using System.Threading.Tasks;    public class UserInfoServiceImpl : UserInfoService.UserInfoServiceBase    {        public override Task<GetUserByIdRelpy> GetById(GetUserByIdRequest request, ServerCallContext context)        {            var result = new GetUserByIdRelpy();            var user = FakeUserInfoDb.GetById(request.Id);            result.Id = user.Id;            result.Name = user.Name;            result.Age = user.Age;            result.CreateTime = user.CreateTime;            return Task.FromResult(result);        }        public override Task<GetUserListReply> GetList(GetUserListRequest request, ServerCallContext context)        {            var result = new GetUserListReply();            var userList = FakeUserInfoDb.GetList(request.Id, request.Name);            result.Code = 0;            result.Msg = "成功";            result.Data.AddRange(userList.Select(x => new GetUserListReply.Types.MsgItem            {                Id = x.Id,                Age = x.Age,                CreateTime = x.CreateTime,                Name = x.Name            }));            return Task.FromResult(result);        }        public override Task<SaveUserReply> Save(SaveUserRequest request, ServerCallContext context)        {            var result = new SaveUserReply();            var flag = FakeUserInfoDb.Save(request.Name, request.Age);            result.Code = 0;            result.Msg = "成功";                        return Task.FromResult(result);        }    }}

可以看到上面的代码,我们只要继承由proto文件生成的一个基类,然后去重写它的实现,就可以认为是实现了一个服务。这个其实就是写我们具体的业务逻辑,大boss有什么需求,堆上去就好了。

然后来看第二部分,服务怎么起来。

在这里我选择的方案是使用通用主机来跑。当然也可以直接在Startup的Configure方法中去启动服务。只要能起来就行 :-D

namespace MyBasedServiceA{    using Grpc.Core;    using Microsoft.Extensions.Hosting;    using Microsoft.Extensions.Logging;    using System;    using System.Threading;    using System.Threading.Tasks;    public class MyBasedServiceAHostedService : BackgroundService    {        private readonly ILogger _logger;        private Server _server;        public MyBasedServiceAHostedService(ILoggerFactory loggerFactory)        {            this._logger = loggerFactory.CreateLogger<MyBasedServiceAHostedService>();            _server = new Server            {                Services = { UserInfoService.BindService(new UserInfoServiceImpl()) },                                                Ports = { new ServerPort("0.0.0.0", 9999, ServerCredentials.Insecure) }            };        }              protected override Task ExecuteAsync(CancellationToken stoppingToken)        {            _server.Start();            return Task.CompletedTask;        }    }}

然后是Program中的代码。

namespace MyBasedServiceA{    using Microsoft.AspNetCore.Builder;    using Microsoft.AspNetCore.Hosting;    using Microsoft.Extensions.DependencyInjection;    using Microsoft.Extensions.Hosting;    using Microsoft.Extensions.Logging;    public class Program    {        public static void Main(string[] args)        {            var host = new HostBuilder()                .ConfigureLogging((hostContext, configLogging) =>                {                    configLogging.AddConsole();                    configLogging.AddDebug();                })                .ConfigureServices((hostContext, services) =>                {                    services.AddHostedService<MyBasedServiceAHostedService>();                })                .Build();            host.Run();        }    }}

到这里,服务端已经可以了。

下面就是客户端了。

客户端代码编写

在这里客户端,我们写两个,一个基于C#(.net core), 一个基于python。刚好也验证一下gRPC的多语言。

正常来说,我们所说的客户端可能很大一部分是对外的WEB API了,就是说api的内部实现,是rpc的调用,而对外的是常见的返回JSON的rest api。

我们先通过控制台来体验一下它的客户端调用。

C#(.net core)客户端

class Program{    static void Main(string[] args)    {        var channel = new Channel("localhost:9999", ChannelCredentials.Insecure);        var client = new UserInfoService.UserInfoServiceClient(channel);        var saveResponse = client.Save(new SaveUserRequest { Age = 99, Name = "c#name" });        Console.WriteLine($"Save received: code = {saveResponse.Code} ,  msg = {saveResponse.Msg}");        var getListResponse = client.GetList(new GetUserListRequest { });        Console.WriteLine($"GetList received: code =  {getListResponse.Code} ,  msg = {getListResponse.Msg}");        foreach (var item in getListResponse.Data)        {            Console.WriteLine(item.Name);        }        Console.ReadKey();    }}

其实这种方式我们很容易联想到WCF,都是生成代码,可以直接点出来的方法,强类型的使用体验。不过我是基本没有用过WCF的,貌似暴露了年龄了,逃~~

python客户端

python要想运行gRPC相关的,要先安装 grpcio-tools,然后再用命令生成相应的文件。

# 安装pip install grpcio-tools# 生成python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./userinfo.proto

具体实现

import grpcimport userinfo_pb2, userinfo_pb2_grpc_HOST = 'localhost'_PORT = '9999'def run():    conn = grpc.insecure_channel(_HOST + ':' + _PORT)    client = userinfo_pb2_grpc.UserInfoServiceStub(channel=conn)    saveResponse = client.Save(userinfo_pb2.SaveUserRequest(name="pyname", age=39))    print("Save received: code = " + str(saveResponse.code) + ", msg = "+ saveResponse.msg)    getListResponse = client.GetList(userinfo_pb2.GetUserListRequest())    print("GetList received: code = " + str(getListResponse.code) + ", msg = "+ getListResponse.msg)    for d in getListResponse.data:        print(d.name)if __name__ == '__main__':    run()

同样也是很简洁。

运行效果

在服务端起来的情况下,先运行.net core的客户端,然后再运行python的客户端,结果大致如下。

640?wx_fmt=png

注: 在调用的时候,有几个概念要知道!!

  1. gRPC中没有采用传统的timeout方式去处理,而是采用了Deadline机制,觉得这个机制和我们的CancellationToken很相似

  2. 无论是客户端还是服务端,都可以随时取消RPC

可以看到我们现在的地址都是硬编码的,因为只有一个节点,然后在线上环境,都会是多节点的,所以我们需要有服务注册和服务发现,下面我们就结合consul来完成服务注册与发现。

服务治理(注册与发现)

当然现在可选的工具还是有很多的,consul,etcd,eureka等,当然最好的还是直接上K8S,不过我们公司还有很长的一段路才能上,所以我们就怎么简单怎么来了。

下面我们调整一下服务端的代码,让gRPC的服务可以注册到consul上面。

public class MyBasedServiceAHostedService : BackgroundService{    private readonly Microsoft.Extensions.Logging.ILogger _logger;    private readonly IConfiguration _configuration;    private readonly IConsulClient _consulClient;    private Server _server;    private AgentServiceRegistration registration;    public MyBasedServiceAHostedService(ILoggerFactory loggerFactory, IConfiguration configuration, IConsulClient consulClient, IHostingEnvironment environment)    {        this._logger = loggerFactory.CreateLogger<MyBasedServiceAHostedService>();        this._configuration = configuration;        this._consulClient = consulClient;        var port = _configuration.GetValue<int>("AppSettings:Port");        _logger.LogInformation($"{environment.EnvironmentName} Current Port is : {port}");                GrpcEnvironment.SetLogger(new GrpcAdapterLogger(loggerFactory));        var address = GetLocalIP();        _logger.LogInformation($"{environment.EnvironmentName} Current IP is : {address}");        registration = new AgentServiceRegistration()        {            ID = $"MyBasedServiceA-{Guid.NewGuid().ToString("N")}",            Name = "MyBasedServiceA",            Address = address,            Port = port,            Check = new AgentServiceCheck            {                TCP = $"{address}:{port}",                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),                Interval = TimeSpan.FromSeconds(10),                Timeout = TimeSpan.FromSeconds(5)            }        };        _server = new Server        {            Ports = { new ServerPort("0.0.0.0", port, ServerCredentials.Insecure) }        };                if (!environment.IsProduction())        {            _server.Services.Add(UserInfoService.BindService(new UserInfoServiceImpl()).Intercept(new AccessLogInterceptor(loggerFactory)));        }        else        {            _server.Services.Add(UserInfoService.BindService(new UserInfoServiceImpl()));        }    }      protected override async Task ExecuteAsync(CancellationToken stoppingToken)    {        await _consulClient.Agent.ServiceDeregister(registration.ID);        await _consulClient.Agent.ServiceRegister(registration);        _logger.LogInformation($"Registering with Consul {registration.ID} OK");        _server.Start();    }    public override async Task StopAsync(CancellationToken cancellationToken)    {        _logger.LogInformation("Unregistering from Consul");        await _consulClient.Agent.ServiceDeregister(registration.ID);        await _server.KillAsync();        await base.StopAsync(cancellationToken);    }    private string GetLocalIP()    {        try        {            string hostName = Dns.GetHostName();            IPHostEntry ipEntry = Dns.GetHostEntry(hostName);            for (int i = 0; i < ipEntry.AddressList.Length; i++)            {                if (ipEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)                {                    return ipEntry.AddressList[i].ToString();                }            }            return "127.0.0.1";        }        catch (Exception ex)        {            _logger.LogError(ex, "Get Local Ip error");            return "127.0.0.1";        }    }}

然后客户端要定义一个从consul中读取实例的方法。

从consul中读取对应service的健康实例,正常会用定时轮训的方式去读取,或者写入短时间的缓存。

public class FindService : IFindService {    private readonly ILogger _logger;    private readonly IConsulClient _consulClient;    private readonly ConcurrentDictionary<string, (List<string> List, DateTimeOffset Expiration)> _dict;    public FindService(ILoggerFactory loggerFactory, IConsulClient consulClient)    {        _logger = loggerFactory.CreateLogger<FindService>();        _consulClient = consulClient;        _dict = new ConcurrentDictionary<string, (List<string> List, DateTimeOffset Expiration)>();    }    public async Task<string> FindServiceAsync(string serviceName)    {        var key = $"SD:{serviceName}";        if (_dict.TryGetValue(key, out var item) && item.Expiration > DateTimeOffset.UtcNow)        {            _logger.LogInformation($"Read from cache");            return item.List[new Random().Next(0, item.List.Count)];        }        else        {            var queryResult = await _consulClient.Health.Service(serviceName, string.Empty, true);            var result = new List<string>();            foreach (var serviceEntry in queryResult.Response)            {                result.Add(serviceEntry.Service.Address + ":" + serviceEntry.Service.Port);            }            _logger.LogInformation($"Read from consul : {string.Join(",", result)}");            if (result != null && result.Any())            {                                var val = (result, DateTimeOffset.UtcNow.AddSeconds(600));                _dict.AddOrUpdate(key, val, (x, y) => val);                var count = result.Count;                return result[new Random().Next(0, count)];            }            return "";        }    }}

调用的时候。

private async Task<(UserInfoService.UserInfoServiceClient Client, string Msg)> GetClientAsync(string name){    var target = await _findService.FindServiceAsync(name);    _logger.LogInformation($"Current target = {target}");    if (string.IsNullOrWhiteSpace(target))    {        return (null, "can not find a service");    }    else    {        var channel = new Channel(target, ChannelCredentials.Insecure);        var client = new UserInfoService.UserInfoServiceClient(channel);        return (client, string.Empty);    }}

然后我们编写docker-compose.yml, 让它在docker中跑

version: '3.4'services:  xxxserver1:    image: ${DOCKER_REGISTRY-}xxxserver    build:      context: .      dockerfile: MyBasedServiceA/Dockerfile    ports:      - "9999:9999"      depends_on:      - consuldev          networks:        backend:  xxxserver2:    image: ${DOCKER_REGISTRY-}xxxserver    build:      context: .      dockerfile: MyBasedServiceA/Dockerfile    ports:      - "9995:9999"       depends_on:      - consuldev          networks:        backend:              xxxclient:    image: ${DOCKER_REGISTRY-}xxxclient    build:      context: .      dockerfile: XXXService/Dockerfile    ports:      - "9000:80"    depends_on:      - consuldev          - xxxserver1      - xxxserver2    networks:        backend:  consuldev:    image: consul:latest        ports:      - "8300:8300"      - "8400:8400"      - "8500:8500"        networks:        backend:networks:    backend:          driver: bridge

运行结果如下:

当用 docker 把这几个服务都跑起来之后, 可以看到类似下面的输出

640?wx_fmt=png

也可以用docker ps命令来看一下那几个服务是不是真的在运行。

640?wx_fmt=png

同时,我们打开consul的UI界面,可以看到我们服务端的两个实例已经注册上来了。

640?wx_fmt=png

当我们用客户端去访问的时候,可以发现,第一次它是从consul中取下来了两个ip,然后随机选了一个进行访问。

640?wx_fmt=png

我们把其中一个服务端(0.4)stop,用来模拟某个节点出现异常,被剔除的情况 ,可以发现consul上面已经看不到了,只剩下0.3这个节点了。

640?wx_fmt=png

如果我们的调度策略没有及时将"死掉"的节点剔除,就会出现下面的这种情况。

640?wx_fmt=png

最后,把stop的服务端启动,模拟恢复正常,这个时候可以发现无论调度到那个节点都可以正常访问了。

640?wx_fmt=png

.NET Core 2.x 和 .NET Core 3.0的细微区别

在.NET Core 2.x中,我们的Server,是需要手动控制的,在.NET Core 3.0中,可以认为它已经和Kestrel融为一体了,不再需要我们再手动去Start了。

同样的,服务的实现,也和Endpoint Routing紧密的结合在一起了,不再和之前一样了。

可以看看下面的例子,可能会发现,这是一种熟悉的不能再熟悉的感觉。

public class Startup{    public void ConfigureServices(IServiceCollection services)    {        services.AddGrpc(x=>        {            x.EnableDetailedErrors = true;            x.Interceptors.Add<AccessLogInterceptor>();        });    }      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)    {        if (env.IsDevelopment())        {            app.UseDeveloperExceptionPage();        }        app.UseRouting();        app.UseEndpoints(endpoints =>        {            endpoints.MapGrpcService<GreeterService>();            endpoints.MapGrpcService<UserService>();            endpoints.MapGet("/", async context =>            {                await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");            });        });    }}

客户端的用法也和以前不一样了,直接看一个例子就很清晰了。

从它的用法上,我们也有熟悉的面孔 HttpClient

虽然gRPC是基于HTTP/2的,但是可以看到我们上面小节的例子中,还是能够指定不使用的。然而到.NET Core 3.0之后,我们就必须要使用https了,不然客户端就是调不通的。同样的,我们也可以在grpc-dotnet的仓库上面看到,如果想不使用HTTP/2,就让我们用回之前的老库,不要用新库,James Newton-King就是这么直接。

https://github.com/grpc/grpc-dotnet/issues/277

https://github.com/grpc/grpc-dotnet/issues/405

https://github.com/grpc/grpc-dotnet/issues/431

扩展阅读

  • 思考gRPC :为什么是HTTP/2

  • Grpc中Deadline分析

  • protocol-buffers官网

  • Using CancellationTokens in ASP.NET Core MVC controllers

文中出现的示例代码都可以在下面这个仓库找到

catcherwong-archive/2019

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

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

相关文章

P6282 [USACO20OPEN] Cereal S 思维

传送门 文章目录目录&#xff1a;题意&#xff1a;思路&#xff1a;目录&#xff1a; 题意&#xff1a; 你有nnn头奶牛&#xff0c;mmm中不同种类的麦片&#xff0c;每个麦片只有一箱&#xff0c;给你每个奶牛第一和第二喜欢的麦片&#xff0c;奶牛会先看第一喜欢的是否有&am…

HDU 4417 Super Mario(莫队 + 树状数组 + 离散化)

Super Mario 思路 区间查找问题&#xff0c;容易想到离线莫队&#xff0c;确实这题就是莫队&#xff0c;接下来我们考虑如何维护区间高度值问题。 既然是离线嘛&#xff0c;我们容易想到离散化和他挂钩&#xff0c;想想这题是否需要离散化&#xff0c;高度的最大值是1000000…

生命周期结束,Spring Boot 1.x退役

一年前 Spring 官方宣布 Spring Boot 1.x 生命周期将于今年 8 月 1 日结束&#xff0c;如今时间已到&#xff0c;在发布 Spring Boot 1.5.22 的同时&#xff0c;Spring 确认将不再为 1.x 系列发布维护版本。官方希望用户尽快迁移到 Spring Boot 2.x 上&#xff0c;为此还制作了…

P3085 [USACO13OPEN]Yin and Yang G 点分治

文章目录题意&#xff1a;思路&#xff1a;传送门 题意&#xff1a; 给你一颗nnn个点的树&#xff0c;每条边为黑色或者白色&#xff0c;问满足以下条件的路径条数&#xff1a;路径上存在一个不是端点的点&#xff0c;使得两端点到该点的两条路径上两种颜色的边数相等。 1≤n…

G. Xor-MST(异或最小生成树)

G. Xor-MST 思路 异或最小生成树&#xff0c;这里采用了一种分治的方法来贪心求解最值&#xff1a; 首先我们对所有的点权值从小到大排个序&#xff0c;从高位开始在中间找到一个这个位置上的0&#xff0c;10&#xff0c;10&#xff0c;1分界点分成两个集合&#xff0c;然后…

CF 1638 E. Colorful Operations set 区间平推

文章目录题意&#xff1a;思路&#xff1a;传送门 题意&#xff1a; 给你一个数组aaa&#xff0c;初始价值全为000&#xff0c;颜色全为111&#xff0c;让后让你实现以下三个操作&#xff1a; 将[l,r][l,r][l,r]区间内的颜色都染成ccc。将所有颜色为ccc的位置价值都加上xxx。…

B Graph(异或最小生成树)

Graph 思路 图是联通的&#xff0c;并且加边的时候要保证环一定是异或值为0&#xff0c;所以我们可以保证从一个点到另一个点的路径异或值是不变的&#xff0c;这个时候就简单了&#xff0c;不就是一个异或最小生成树了嘛。 我们只要预处理一下&#xff0c;任选一个点作为根…

Apollo 配置中心:分布式部署

Apollo&#xff08;阿波罗&#xff09;是携程框架部门研发的分布式配置中心&#xff0c;能够集中化管理应用不同环境、不同集群的配置&#xff0c;配置修改后能够实时推送到应用端&#xff0c;并且具备规范的权限、流程治理等特性&#xff0c;适用于微服务配置管理场景。服务端…

CF 1637 D. Yet Another Minimization Problem dp + 思维

文章目录题意&#xff1a;思路&#xff1a;传送门 题意&#xff1a; 给你两个长度为nnn的数组a,ba,ba,b&#xff0c;每次交换可以选择一个iii&#xff0c;交换ai,bia_i,b_iai​,bi​&#xff0c;最小化∑i1n∑ji1n(aiaj)2∑i1n∑ji1n(bibj)2\sum_{i1}^n \sum_{ji1}^n(a_ia_j)…

各种逆元推导

逆元 求解一&#xff08;费马小定理&#xff09; ppp是一个质数&#xff0c;并且a%p̸0a \% p \not 0a%p​0&#xff0c;则有ap−1≡1(modp)a ^ {p - 1} \equiv 1 \pmod pap−1≡1(modp)&#xff0c;ap−2≡a−1a ^ {p - 2} \equiv a ^ {-1}ap−2≡a−1&#xff0c;即可得到…

使用Redis实现最近N条数据的决策

前言很多时候&#xff0c;我们会根据用户最近一段时间的行为&#xff0c;做出一些相应的策略&#xff0c;从而改变系统的运动轨迹。举个简单的例子来说明一下&#xff1a;假设A公司现在有两个合作伙伴(B和C)&#xff0c;B和C都是提供天气数据的&#xff0c;现在A公司做了一个聚…

CF 1635 D. Infinite Set 思维 + 二进制

文章目录题意思路传送门 题意 给你一个集合SSS&#xff0c;初始集合内含有nnn个数&#xff0c;让后按照一下三个规则无限的向集合中添加数&#xff1a; 对于所有的1≤i≤n,xai1\le i\le n,xa_i1≤i≤n,xai​都在集合中。对于所有的x2y1,y∈Sx2y1,y\in Sx2y1,y∈S&#xff0c…

2020杭电多校(二) New Equipments(最小费用最大流)

New Equipments 思路 数据已经有提示了b∗b<4∗a∗cb * b < 4 * a * cb∗b<4∗a∗c&#xff0c;这意味着&#xff0c;每一个a,b,ca, b, ca,b,c构成的二元一次方程只与xxx坐标最多相交一次&#xff0c;所以我们对每一个a∗i∗ib∗icya * i * i b * i c ya∗i∗ib∗…

为什么我不喜欢数据库三范式

插曲最近&#xff0c;一个远房亲戚的小表弟准备选修专业找到我问&#xff1a;"哥&#xff0c;现在学数据库有没有前途阿?""当然有啊&#xff0c;前途大大的呢""那我现在开始学数据库&#xff0c;需要先从什么开始呢?""学课程的话&#xf…

CF 1635E Cars 二分图 + 拓扑

文章目录题意思路传送门 题意 给你nnn个点&#xff0c;需要给每个点定向&#xff0c;方向可以向右或者向左&#xff0c;定向之后点会朝选择的方向移动&#xff0c;要求满足mmm个条件&#xff0c;两种不同的条件如下&#xff1a; i,ji,ji,j两个位置定向之后移动不会相遇。i,ji…

[CQOI2007]涂色PAINT

[CQOI2007]涂色PAINT 思路 显然我们可以考虑用dpdpdp来求解问题&#xff0c;碰到那种一眼没思路的题稳是dpdpdp没跑了&#xff0c;那么我们就往dpdpdp方面去考虑吧。 我们定义dp[i][j]dp[i][j]dp[i][j]&#xff0c;表示把[i,j][i, j][i,j]这个区间涂上颜色要用多少步&#x…

ASP.NET 自定义项目模板

前言在微服务架构盛行的时代&#xff0c;一言不合就新建一个服务&#xff0c;虽然搭建服务并没什么难度&#xff0c;但不可避免的是每个人搭建出来的架子会存在差异&#xff0c;这很合理&#xff0c;因为每个开发者的个人风格、工作经验都不一样&#xff0c;难免认为自己喜欢的…

CF372 C. Watching Fireworks is Fun 单调队列优化dp

文章目录题意思路传送门 题意 城镇中有nnn个位置&#xff0c;有mmm个烟花要放&#xff0c;第iii个烟花放出的时间记为tit_iti​&#xff0c;放出的位置记为aia_iai​。如果烟花放出的时候你在位置xxx&#xff0c;那么将收获bi−∣ai−x∣b_i-|a_i-x|bi​−∣ai​−x∣点的快乐…

中国剩余定理及其拓展

中国剩余定理 实质就是解nnn次互质的方程&#xff0c;然后分别乘以他们的取模剩余量&#xff0c;然后相加得到答案&#xff0c;这里就不展开叙述。 typedef long long ll; const int N 1e3 10; int a[N], b[N], n; void exgcd(ll a, ll b, ll &x, ll &y) {if(!b) {…

硬货 - 技术人也能轻松玩转公众号?正确姿势竟然是...

最近在知乎上看到关于「公众号是否有“前”途」的相关问题... 问题下面有些精华回答~微信公众号还有“前”途吗&#xff1f; - 知乎https://www.zhihu.com/question/324575670很好的问题&#xff01;作为一个技术人&#xff0c;我决定将此问题和自身情况结合起来&#xff0c;于…