原文:https://segmentfault.com/a/1190000018731395?utm_source=tag-newest

------------------------------------------------

服务注册与服务发现是在分布式服务架构中常常会涉及到的东西,业界常用的服务注册与服务发现工具有 ZooKeeperetcdConsul 和 Eureka。Consul 的主要功能有服务发现、健康检查、KV存储、安全服务沟通和多数据中心。Consul 与其他几个工具的区别可以在这里查看 Consul vs. Other Software

为什么需要有服务注册与服务发现?

假设在分布式系统中有两个服务 Service-A (下文以“S-A”代称)和 Service-B(下文以“S-B”代称),当 S-A 想调用 S-B 时,我们首先想到的时直接在 S-A 中请求 S-B 所在服务器的 IP 地址和监听的端口,这在服务规模很小的情况下是没有任何问题的,但是在服务规模很大每个服务不止部署一个实例的情况下是存在一些问题的,比如 S-B 部署了三个实例 S-B-1、S-B-2 和 S-B-3,这时候 S-A 想调用 S-B 该请求哪一个服务实例的 IP 呢?还是将3个服务实例的 IP 都写在 S-A 的代码里,每次调用 S-B 时选择其中一个 IP?这样做显得很不灵活,这时我们想到了 Nginx 刚好就能很好的解决这个问题,引入 Nginx 后现在的架构变成了如下图这样:

引入 Nginx 后就解决了 S-B 部署多个实例的问题,还做了 S-B 实例间的负载均衡。但现在的架构又面临了新的问题,分布式系统往往要保证高可用以及能做到动态伸缩,在引入 Nginx 的架构中,假如当 S-B-1 服务实例不可用时,Nginx 仍然会向 S-B-1 分配请求,这样服务就不可用,我们想要的是 S-B-1 挂掉后 Nginx 就不再向其分配请求,以及当我们新部署了 S-B-4 和 S-B-5 后,Nginx 也能将请求分配到 S-B-4 和 S-B-5,Nginx 要做到这样就要在每次有服务实例变动时去更新配置文件再重启 Nginx。这样看似乎用了 Nginx 也很不舒服以及还需要人工去观察哪些服务有没有挂掉,Nginx 要是有对服务的健康检查以及能够动态变更服务配置就是我们想要的工具,这就是服务注册与服务发现工具的用处。下面是引入服务注册与服务发现工具后的架构图:

在这个架构中:

  • 首先 S-B 的实例启动后将自身的服务信息(主要是服务所在的 IP 地址和端口号)注册到注册工具中。不同注册工具服务的注册方式各不相同,后文会讲 Consul 的具体注册方式。
  • 服务将服务信息注册到注册工具后,注册工具就可以对服务做健康检查,以此来确定哪些服务实例可用哪些不可用。
  • S-A 启动后就可以通过服务注册和服务发现工具获取到所有健康的 S-B 实例的 IP 和端口,并将这些信息放入自己的内存中,S-A 就可用通过这些信息来调用 S-B。
  • S-A 可以通过监听(Watch)注册工具来更新存入内存中的 S-B 的服务信息。比如 S-B-1 挂了,健康检查机制就会将其标为不可用,这样的信息变动就被 S-A 监听到了,S-A 就更新自己内存中 S-B-1 的服务信息。

所以务注册与服务发现工具除了服务本身的服务注册和发现功能外至少还需要有健康检查和状态变更通知的功能。

Consul

Consul 作为一种分布式服务工具,为了避免单点故障常常以集群的方式进行部署,在 Consul 集群的节点中分为 Server 和 Client 两种节点(所有的节点也被称为Agent),Server 节点保存数据,Client 节点负责健康检查及转发数据请求到 Server;Server 节点有一个 Leader 节点和多个 Follower 节点,Leader 节点会将数据同步到 Follower 节点,在 Leader 节点挂掉的时候会启动选举机制产生一个新的 Leader。

Client 节点很轻量且无状态,它以 RPC 的方式向 Server 节点做读写请求的转发,此外也可以直接向 Server 节点发送读写请求。下面是 Consul 的架构图:

Consule 的安装和具体使用及其他详细内容可浏览官方文档
下面是我用 Docker 的方式搭建了一个有3个 Server 节点和1个 Client 节点的 Consul 集群。

# 这是第一个 Consul 容器,其启动后的 IP 为172.17.0.5
docker run -d --name=c1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 -ui docker run -d --name=c2 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.5 docker run -d --name=c3 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.5 #下面是启动 Client 节点
docker run -d --name=c4 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=false --client=0.0.0.0 --join 172.17.0.5

启动容器时指定的环境变量 CONSUL_BIND_INTERFACE 其实就是相当于指定了 Consul 启动时 --bind 变量的参数,比如可以把启动 c1 容器的命令换成下面这样,也是一样的效果。

docker run -d --name=c1 -p 8500:8500 -e consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 --bind='{{ GetInterfaceIP "eth0" }}' -ui

操作 Consul 有 Commands 和 HTTP API 两种方式,进入任意一个容器执行 consul members 都可以有如下的输出,说明 Consul 集群就已经搭建成功了。

Node          Address          Status  Type    Build  Protocol  DC   Segment
2dcf0c824cf0 172.17.0.7:8301 alive server 1.4.4 2 dc1 <all>
64746cffa116 172.17.0.6:8301 alive server 1.4.4 2 dc1 <all>
77af7d94a8ca 172.17.0.5:8301 alive server 1.4.4 2 dc1 <all>
6c71148f0307 172.17.0.8:8301 alive client 1.4.4 2 dc1 <default>

代码实践

假设现在有一个用 Node.js 写的服务 node-server 需要通过 gRPC 的方式调用一个用 Go 写的服务 go-server。
下面是用 Protobuf 定义的服务和数据类型文件 hello.proto

syntax = "proto3";

package hello;
option go_package = "hello"; // The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
} // The request message containing the user's name.
message HelloRequest {
string name = 1;
} // The response message containing the greetings
message HelloReply {
string message = 1;
}

用命令通过 Protobuf 的定义生成 Go 语言的代码:protoc --go_out=plugins=grpc:./hello ./*.proto 会在 hello 目录下得到 hello.pb.go 文件,然后在 hello.go 文件中实现我们定义的 RPC 服务。

// hello.go
package hello import "context" type GreeterServerImpl struct {} func (g *GreeterServerImpl) SayHello(c context.Context, h *HelloRequest) (*HelloReply, error) {
result := &HelloReply{
Message: "hello" + h.GetName(),
}
return result, nil
}

下面是入口文件 main.go,主要是将我们定义的服务注册到 gRPC 中,并建了一个 /ping 接口用于之后 Consul 的健康检查。

package main

import (
"go-server/hello"
"google.golang.org/grpc"
"net"
"net/http"
) func main() {
lis1, _ := net.Listen("tcp", ":8888")
lis2, _ := net.Listen("tcp", ":8889")
grpcServer := grpc.NewServer()
hello.RegisterGreeterServer(grpcServer, &hello.GreeterServerImpl{})
go grpcServer.Serve(lis1)
go grpcServer.Serve(lis2) http.HandleFunc("/ping", func(res http.ResponseWriter, req *http.Request){
res.Write([]byte("pong"))
})
http.ListenAndServe(":8080", nil)
}

至此 go-server 端的代码就全部编写完了,可以看出代码里面没有任何涉及到 Consul 的地方,用 Consul 做服务注册是可以做到对项目代码没有任何侵入性的。下面要做的是将 go-server 注册到 Consul 中。将服务注册到 Consul 可以通过直接调用 Consul 提供的 REST API 进行注册,还有一种对项目没有侵入的配置文件进行注册。Consul 服务配置文件的详细内容可以在此查看。下面是我们通过配置文件进行服务注册的配置文件 services.json

{
"services": [
{
"id": "hello1",
"name": "hello",
"tags": [
"primary"
],
"address": "172.17.0.9",
"port": 8888,
"checks": [
{
"http": "http://172.17.0.9:8080/ping",
"tls_skip_verify": false,
"method": "GET",
"interval": "10s",
"timeout": "1s"
}
]
},{
"id": "hello2",
"name": "hello",
"tags": [
"second"
],
"address": "172.17.0.9",
"port": 8889,
"checks": [
{
"http": "http://172.17.0.9:8080/ping",
"tls_skip_verify": false,
"method": "GET",
"interval": "10s",
"timeout": "1s"
}
]
}
]
}

配置文件中的 172.17.0.9 代表的是 go-server 所在服务器的 IP 地址,port 就是服务监听的不同端口,check 部分定义的就是健康检查,Consul 会每隔 10秒钟请求一下 /ping 接口以此来判断服务是否健康。将这个配置文件复制到 c4 容器的 /consul/config 目录,然后执行consul reload 命令后配置文件中的 hello 服务就注册到 Consul 中去了。通过在宿主机执行curl http://localhost:8500/v1/catalog/services\?pretty就能看到我们注册的 hello 服务。
下面是 node-server 服务的代码:

const grpc = require('grpc');
const axios = require('axios');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(
'./hello.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const hello_proto = grpc.loadPackageDefinition(packageDefinition).hello; function getRandNum (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}; const urls = []
async function getUrl() {
if (urls.length) return urls[getRandNum(0, urls.length-1)];
const { data } = await axios.get('http://172.17.0.5:8500/v1/health/service/hello');
for (const item of data) {
for (const check of item.Checks) {
if (check.ServiceName === 'hello' && check.Status === 'passing') {
urls.push(`${item.Service.Address}:${item.Service.Port}`)
}
}
}
return urls[getRandNum(0, urls.length - 1)];
} async function main() {
const url = await getUrl();
const client = new hello_proto.Greeter(url, grpc.credentials.createInsecure()); client.sayHello({name: 'jack'}, function (err, response) {
console.log('Greeting:', response.message);
});
} main()

代码中 172.17.0.5 地址为 c1 容器的 IP 地址,node-server 项目中直接通过 Consul 提供的 API 获得了 hello 服务的地址,拿到服务后我们需要过滤出健康的服务的地址,再随机从所有获得的地址中选择一个进行调用。代码中没有做对 Consul 的监听,监听的实现可以通过不断的轮询上面的那个 API 过滤出健康服务的地址去更新 urls 数组来做到。现在启动 node-server 就可以调用到 go-server 服务。
服务注册与发现给服务带来了动态伸缩的能力,也给架构增加了一定的复杂度。Consul 除了服务发现与注册外,在配置中心、分布式锁方面也有着很多的应用。

服务注册与服务发现是在分布式服务架构中常常会涉及到的东西,业界常用的服务注册与服务发现工具有 ZooKeeperetcdConsul 和 Eureka。Consul 的主要功能有服务发现、健康检查、KV存储、安全服务沟通和多数据中心。Consul 与其他几个工具的区别可以在这里查看 Consul vs. Other Software

为什么需要有服务注册与服务发现?

假设在分布式系统中有两个服务 Service-A (下文以“S-A”代称)和 Service-B(下文以“S-B”代称),当 S-A 想调用 S-B 时,我们首先想到的时直接在 S-A 中请求 S-B 所在服务器的 IP 地址和监听的端口,这在服务规模很小的情况下是没有任何问题的,但是在服务规模很大每个服务不止部署一个实例的情况下是存在一些问题的,比如 S-B 部署了三个实例 S-B-1、S-B-2 和 S-B-3,这时候 S-A 想调用 S-B 该请求哪一个服务实例的 IP 呢?还是将3个服务实例的 IP 都写在 S-A 的代码里,每次调用 S-B 时选择其中一个 IP?这样做显得很不灵活,这时我们想到了 Nginx 刚好就能很好的解决这个问题,引入 Nginx 后现在的架构变成了如下图这样:

引入 Nginx 后就解决了 S-B 部署多个实例的问题,还做了 S-B 实例间的负载均衡。但现在的架构又面临了新的问题,分布式系统往往要保证高可用以及能做到动态伸缩,在引入 Nginx 的架构中,假如当 S-B-1 服务实例不可用时,Nginx 仍然会向 S-B-1 分配请求,这样服务就不可用,我们想要的是 S-B-1 挂掉后 Nginx 就不再向其分配请求,以及当我们新部署了 S-B-4 和 S-B-5 后,Nginx 也能将请求分配到 S-B-4 和 S-B-5,Nginx 要做到这样就要在每次有服务实例变动时去更新配置文件再重启 Nginx。这样看似乎用了 Nginx 也很不舒服以及还需要人工去观察哪些服务有没有挂掉,Nginx 要是有对服务的健康检查以及能够动态变更服务配置就是我们想要的工具,这就是服务注册与服务发现工具的用处。下面是引入服务注册与服务发现工具后的架构图:

在这个架构中:

  • 首先 S-B 的实例启动后将自身的服务信息(主要是服务所在的 IP 地址和端口号)注册到注册工具中。不同注册工具服务的注册方式各不相同,后文会讲 Consul 的具体注册方式。
  • 服务将服务信息注册到注册工具后,注册工具就可以对服务做健康检查,以此来确定哪些服务实例可用哪些不可用。
  • S-A 启动后就可以通过服务注册和服务发现工具获取到所有健康的 S-B 实例的 IP 和端口,并将这些信息放入自己的内存中,S-A 就可用通过这些信息来调用 S-B。
  • S-A 可以通过监听(Watch)注册工具来更新存入内存中的 S-B 的服务信息。比如 S-B-1 挂了,健康检查机制就会将其标为不可用,这样的信息变动就被 S-A 监听到了,S-A 就更新自己内存中 S-B-1 的服务信息。

所以务注册与服务发现工具除了服务本身的服务注册和发现功能外至少还需要有健康检查和状态变更通知的功能。

Consul

Consul 作为一种分布式服务工具,为了避免单点故障常常以集群的方式进行部署,在 Consul 集群的节点中分为 Server 和 Client 两种节点(所有的节点也被称为Agent),Server 节点保存数据,Client 节点负责健康检查及转发数据请求到 Server;Server 节点有一个 Leader 节点和多个 Follower 节点,Leader 节点会将数据同步到 Follower 节点,在 Leader 节点挂掉的时候会启动选举机制产生一个新的 Leader。

Client 节点很轻量且无状态,它以 RPC 的方式向 Server 节点做读写请求的转发,此外也可以直接向 Server 节点发送读写请求。下面是 Consul 的架构图:

Consule 的安装和具体使用及其他详细内容可浏览官方文档
下面是我用 Docker 的方式搭建了一个有3个 Server 节点和1个 Client 节点的 Consul 集群。

# 这是第一个 Consul 容器,其启动后的 IP 为172.17.0.5
docker run -d --name=c1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 -ui docker run -d --name=c2 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.5 docker run -d --name=c3 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.5 #下面是启动 Client 节点
docker run -d --name=c4 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=false --client=0.0.0.0 --join 172.17.0.5

启动容器时指定的环境变量 CONSUL_BIND_INTERFACE 其实就是相当于指定了 Consul 启动时 --bind 变量的参数,比如可以把启动 c1 容器的命令换成下面这样,也是一样的效果。

docker run -d --name=c1 -p 8500:8500 -e consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 --bind='{{ GetInterfaceIP "eth0" }}' -ui

操作 Consul 有 Commands 和 HTTP API 两种方式,进入任意一个容器执行 consul members 都可以有如下的输出,说明 Consul 集群就已经搭建成功了。

Node          Address          Status  Type    Build  Protocol  DC   Segment
2dcf0c824cf0 172.17.0.7:8301 alive server 1.4.4 2 dc1 <all>
64746cffa116 172.17.0.6:8301 alive server 1.4.4 2 dc1 <all>
77af7d94a8ca 172.17.0.5:8301 alive server 1.4.4 2 dc1 <all>
6c71148f0307 172.17.0.8:8301 alive client 1.4.4 2 dc1 <default>

代码实践

假设现在有一个用 Node.js 写的服务 node-server 需要通过 gRPC 的方式调用一个用 Go 写的服务 go-server。
下面是用 Protobuf 定义的服务和数据类型文件 hello.proto

syntax = "proto3";

package hello;
option go_package = "hello"; // The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
} // The request message containing the user's name.
message HelloRequest {
string name = 1;
} // The response message containing the greetings
message HelloReply {
string message = 1;
}

用命令通过 Protobuf 的定义生成 Go 语言的代码:protoc --go_out=plugins=grpc:./hello ./*.proto 会在 hello 目录下得到 hello.pb.go 文件,然后在 hello.go 文件中实现我们定义的 RPC 服务。

// hello.go
package hello import "context" type GreeterServerImpl struct {} func (g *GreeterServerImpl) SayHello(c context.Context, h *HelloRequest) (*HelloReply, error) {
result := &HelloReply{
Message: "hello" + h.GetName(),
}
return result, nil
}

下面是入口文件 main.go,主要是将我们定义的服务注册到 gRPC 中,并建了一个 /ping 接口用于之后 Consul 的健康检查。

package main

import (
"go-server/hello"
"google.golang.org/grpc"
"net"
"net/http"
) func main() {
lis1, _ := net.Listen("tcp", ":8888")
lis2, _ := net.Listen("tcp", ":8889")
grpcServer := grpc.NewServer()
hello.RegisterGreeterServer(grpcServer, &hello.GreeterServerImpl{})
go grpcServer.Serve(lis1)
go grpcServer.Serve(lis2) http.HandleFunc("/ping", func(res http.ResponseWriter, req *http.Request){
res.Write([]byte("pong"))
})
http.ListenAndServe(":8080", nil)
}

至此 go-server 端的代码就全部编写完了,可以看出代码里面没有任何涉及到 Consul 的地方,用 Consul 做服务注册是可以做到对项目代码没有任何侵入性的。下面要做的是将 go-server 注册到 Consul 中。将服务注册到 Consul 可以通过直接调用 Consul 提供的 REST API 进行注册,还有一种对项目没有侵入的配置文件进行注册。Consul 服务配置文件的详细内容可以在此查看。下面是我们通过配置文件进行服务注册的配置文件 services.json

{
"services": [
{
"id": "hello1",
"name": "hello",
"tags": [
"primary"
],
"address": "172.17.0.9",
"port": 8888,
"checks": [
{
"http": "http://172.17.0.9:8080/ping",
"tls_skip_verify": false,
"method": "GET",
"interval": "10s",
"timeout": "1s"
}
]
},{
"id": "hello2",
"name": "hello",
"tags": [
"second"
],
"address": "172.17.0.9",
"port": 8889,
"checks": [
{
"http": "http://172.17.0.9:8080/ping",
"tls_skip_verify": false,
"method": "GET",
"interval": "10s",
"timeout": "1s"
}
]
}
]
}

配置文件中的 172.17.0.9 代表的是 go-server 所在服务器的 IP 地址,port 就是服务监听的不同端口,check 部分定义的就是健康检查,Consul 会每隔 10秒钟请求一下 /ping 接口以此来判断服务是否健康。将这个配置文件复制到 c4 容器的 /consul/config 目录,然后执行consul reload 命令后配置文件中的 hello 服务就注册到 Consul 中去了。通过在宿主机执行curl http://localhost:8500/v1/catalog/services\?pretty就能看到我们注册的 hello 服务。
下面是 node-server 服务的代码:

const grpc = require('grpc');
const axios = require('axios');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(
'./hello.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const hello_proto = grpc.loadPackageDefinition(packageDefinition).hello; function getRandNum (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}; const urls = []
async function getUrl() {
if (urls.length) return urls[getRandNum(0, urls.length-1)];
const { data } = await axios.get('http://172.17.0.5:8500/v1/health/service/hello');
for (const item of data) {
for (const check of item.Checks) {
if (check.ServiceName === 'hello' && check.Status === 'passing') {
urls.push(`${item.Service.Address}:${item.Service.Port}`)
}
}
}
return urls[getRandNum(0, urls.length - 1)];
} async function main() {
const url = await getUrl();
const client = new hello_proto.Greeter(url, grpc.credentials.createInsecure()); client.sayHello({name: 'jack'}, function (err, response) {
console.log('Greeting:', response.message);
});
} main()

代码中 172.17.0.5 地址为 c1 容器的 IP 地址,node-server 项目中直接通过 Consul 提供的 API 获得了 hello 服务的地址,拿到服务后我们需要过滤出健康的服务的地址,再随机从所有获得的地址中选择一个进行调用。代码中没有做对 Consul 的监听,监听的实现可以通过不断的轮询上面的那个 API 过滤出健康服务的地址去更新 urls 数组来做到。现在启动 node-server 就可以调用到 go-server 服务。
服务注册与发现给服务带来了动态伸缩的能力,也给架构增加了一定的复杂度。Consul 除了服务发现与注册外,在配置中心、分布式锁方面也有着很多的应用。

【转】用 Consul 来做服务注册与服务发现的更多相关文章

  1. SpringCloud+Consul 服务注册与服务发现

    SpringCloud+Consul 服务注册与服务发现 1. 服务注册: 在Spring.factories有一段: # Discovery Client Configuration org.spr ...

  2. consul服务注册与服务发现的巨坑

    最近使用consul作为项目的服务注册与服务发现的基础功能.在塔建集群使用中遇到一些坑,下面一个个的记录下来. consul集群多node consul集群的node也就是我们所说的consul实例. ...

  3. SpringCloud实战之初级入门(二)— 服务注册与服务调用

    目录 1.环境介绍 2.服务提供 2.1 创建工程 2.2 修改配置文件 2.3 修改启动文件 2.5 亲测注意事项 3.服务调用 3.1 创建工程 3.2 修改配置文件 3.3 修改启动文件 3.4 ...

  4. SpringCloud之eureka服务注册和服务发现

    服务注册中心 :eureka-server 作用:服务注册中心提供服务注册功能 服务提供方:eureka-client 作用:注册服务到服务注册中心 服务注册中心 :eureka-server 创建 ...

  5. SpringCloud系列(一):Eureka 服务注册与服务发现

    上一篇,我们介绍了服务注册中心,光有服务注册中心没有用,我们得发服务注册上去,得从它那边获取服务.下面我们注册一个服务到服务注册中心上去. 我们创建一个 hello-service 的 spring ...

  6. dubbo2.7.X版本带来的服务注册和服务调用方式改变

    参考地址:https://www.cnblogs.com/alisystemsoftware/p/13064620.html 注册中心数据结构格式改变(service:接口服务,application ...

  7. Go微服务框架go-kratos实战04:kratos中服务注册和服务发现的使用

    一.简介 关于服务注册和服务发现介绍,我前面的文章有介绍过 - 服务注册和发现的文章. 作为服务中心的软件有很多,比如 etcd,consul,nacos,zookeeper 等都可以作为服务中心. ...

  8. Windows环境下实现Consul服务注册和服务发现

    1.首先从官方网站下载Consul,因为我们是使用的Windows系统,所以选择windows版本 https://www.consul.io/downloads.html 2.可以用开发者模式来启动 ...

  9. 第三章 consul服务注册与服务查询

    1.定义一个服务 https://www.consul.io/docs/agent/services.html 该方法是服务注册中提供服务的最常用的方法. 关于服务的定义:服务的属性我们会在后边每出现 ...

随机推荐

  1. AWS 架构最佳实践(十二)

    可靠性 基本概念 可靠性 系统从基础设施或服务故障中恢复.动态获取计算资源以满足需求减少中断的能力 系统为最坏情况做好准备,对不同组件实施缓解措施,对恢复程序进行提前测试并且自动执行. 可靠性实践 测 ...

  2. linux下源码安装rabbitMq

    一.安装erlang前期环境安装1.利用yum安装erlang编译所依赖的环境 yum -y install make gcc gcc-c++ kernel-devel m4ncurses-devel ...

  3. [转]PHP程序员的技术成长规划

    转自:http://blog.leanote.com/post/darker/PHP%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E6%8A%80%E6%9C%AF%E6% ...

  4. 异常查错java.net.SocketException: Connection reset

    用httpclient访问后台接口报错java.net.SocketException: Software caused connection abort: recv failed,百度了一圈都说是由 ...

  5. Cookie,Session,Token详解

    Cookie,Session,Token详解 Cookie : ​ 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能. ​ Cookie由服务器生成,发 ...

  6. (4)Spring Boot Web开发---静态资源

    文章目录 对静态资源的映射规则 模板引擎 Thymeleaf 使用 & 语法 使用之前将的快速创建项目的方法,勾选我们需要的场景,这里我需要 web --> web.sql --> ...

  7. ListView控件的理解——自洽理论

    写在前面的话: *标题中已经说明,是自洽理论.因此,有几率会有理解错误.但是,你不可以因此骂我. -我这个人经不起别人的批评,如果你批评我,我就,我就.... ## <第一行代码>读书笔记 ...

  8. Java调用SqlLoader将大文本导入数据库

    Java调用SqlLoader将大文本导入数据库 业务场景:将一千万条数据,大约500M的文本文档的数据导入到数据库 分析:通过Java的IO流解析txt文本文档,拼接动态sql实现insert入库, ...

  9. ~request库的使用

    官方文档: (中文)http://cn.python-requests.org/zh_CN/latest/ (英文)https://2.python-requests.org//en/master/a ...

  10. Istio旨在成为容器化微服务的网格管道

    在精彩的软件容器世界中,当新项目涌现并解决你认为早已解决的问题时,这感觉就像地面在你的脚下不断地移动.在许多情况下,这些问题很久以前被解决,但现在的云原生架构正在推动着更大规模的应用程序部署,这就需要 ...