分类
Java

gRPC基础教程之一

前言

本文将介绍 gRPC、Protocol Buffers 的概念,同时会给出 Protocol Buffers 代码生成器的使用教程,还有编写第一个基于 gRPC 的服务提供者与服务消费者的示例程序。

相关站点

gRPC 简介

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java、Go 语言版本,分别是:grpc、grpc-java、grpc-go,其中 C 版本支持 C、C++、Node.js、Python、Ruby、Objective-C、PHP、C#。gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。值得说明的是,gRPC 客户端和服务端可以在多种环境中运行和交互,支持用任何 gRPC 支持的语言来编写,所以可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。

使用 Protocol Buffers

gRPC 默认使用 Protocol Buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。当使用 proto files 创建 gRPC 服务,用 Protocol Buffers 消息类型来定义方法参数和返回类型。尽管 Protocol Buffers 已经存在了一段时间,官方的示例代码种使用了一种名叫 proto3 的新风格的 Protocol Buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在Protocol Buffers Github 源码库里有 Ruby 支持, 在 Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。虽然可以使用 proto2 (当前默认的 Protocol Buffers 版本), 通常建议在 gRPC 里使用 proto3,因为这样可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。

本地编译安装 Protocol Buffers(可选)

参考自 gRPC-Java、Protobuf 编译构建的官方教程,一般情况下不需要构建 gRPC-Java,只有在对 gRPC-Java 的源码进行了更改或测试使用 gRPC-Java 库的非发布版本(例如 master 分支)时才需要构建。若本地安装了 Protobuf,则可以直接通过命令的方式调用 Protobuf 的代码生成器,无需再依赖额外的 IDE 插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 系统环境
CentOS Linux release 7.6.1810 (Core)
Linux develop 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

# 拉取源码
# git clone https://github.com/google/protobuf.git

# 进入源码目录
# cd protobuf

# 切换至需要编译的版本的分支
# git checkout v3.7.1

# 查看当前所在的分支信息
# git branch -v

# 检测安装环境
# ./autogen.sh
# ./configure –disable-shared

# 编译安装
# make -j 8
# make install

# 如果/usr/local/lib不在库搜索路径中,可以通过运行以下命令添加
# sh -c ‘echo /usr/local/lib >> /etc/ld.so.conf’

# 使添加的库搜索路径生效
# ldconfig

# 查看protobuf安装的版本号
# protoc –version

# 编写.proto文件,使用protobuf的代码生成器自动生成Java代码,命令格式如下
# protoc -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto

# 默认安装路径:/usr/local
# 指定安装目录可以使用此命令: ./configure –disable-shared –prefix=/usr/local/protobuf-3.7.1

Eclipse 项目中添加 Protobuf 自动生成代码的 Maven 插件与 Protobuf 依赖

Protobuf 的原型文件和一些适合的插件,默认放在 src/main/proto 和 src/test/proto 目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.21.0</version>
</dependency>

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.21.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

往 Gradle 构建的项目添加 Protobuf 自动生成代码的插件与 Protobuf 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
plugins {
id ‘com.google.protobuf’ version ‘0.8.8’
}

protobuf {
protoc {
artifact = “com.google.protobuf:protoc:3.7.1”
}
plugins {
grpc {
artifact = ‘io.grpc:protoc-gen-grpc-java:1.21.0’
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}

dependencies {
compile ‘io.grpc:grpc-stub:1.21.0’
compile ‘io.grpc:grpc-protobuf:1.21.0’
compile ‘io.grpc:grpc-netty-shaded:1.21.0’
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
}

编写 Proto 文件(定义服务),执行编译后自动生成 Java 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 创建gradle工程grpc-demo-provider,目录结构如下:
grpc-demo-provider/
├── build.gradle
└── src
├── main
│   ├── java
│   ├── proto
│   │   └── helloworld.proto
│   └── resources
└── test
├── java
├── proto
└── resources

# 进入工程目录
# cd grpc-demo-provider

# 编辑build.gradle文件,添加protobuf插件与依赖,可参考上面给出的gradle配置内容

# 创建proto文件
# mkdir -p src/main/proto
# vim src/main/proto/helloworld.proto

syntax = “proto3”;
option java_multiple_files = true;
option java_package = “com.grpc.demo.generate”;
option java_outer_classname = “HelloWorldProto”;
option objc_class_prefix = “HLW”;

package helloworld;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

# 执行编译,自动生成Java文件
# gradle clean build

# 查看自动生成的文件目录结构,默认生成文件所在的目录是:$buildDir/generated/source/proto,其中Message在main/java目录下,Service在目录main/grpc下
# tree build/generated/source/proto
main
├── grpc
│   └── com
│   └── grpc
│   └── demo
│   └── generate
│   └── GreeterGrpc.java
└── java
└── com
└── grpc
└── demo
└── generate
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
└── HelloWorldProto.java

Gradle 指定 Protobuf 代码自动生成的目录位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 指定Message代码的生成位置,最终生成位置在src/main/java目录下
protobuf {
generatedFilesBaseDir = “src”
}

// 指定Service代码的生成位置,最终生成位置在src/main/java目录下
protobuf {
generateProtoTasks {
all()*.plugins {
grpc {
outputSubDir = ‘java’
}
}
}
}

// 完整的写法,同时指定Message、Service代码生成的目录位置为src/main/java
protobuf {
protoc {
artifact = “com.google.protobuf:protoc:3.7.1”
}
plugins {
grpc {
artifact = ‘io.grpc:protoc-gen-grpc-java:1.21.0’
}
}
generateProtoTasks {
all()*.plugins {
grpc {
outputSubDir = ‘java’
}
}
}
generatedFilesBaseDir = ‘src’
}

RPC 服务提供者的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.grpc.demo.provider.service;

import com.grpc.demo.generate.GreeterGrpc;
import com.grpc.demo.generate.HelloReply;
import com.grpc.demo.generate.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class HelloWorldProvider {

private Server server;
private static final Logger logger = Logger.getLogger(HelloWorldProvider.class.getName());

private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info(“==> Server started, listening on ” + port);

Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println(“*** shutting down gRPC server since JVM is shutting down”);
HelloWorldProvider.this.stop();
System.err.println(“*** server shut down”);
}
});
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}

public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldProvider server = new HelloWorldProvider();
server.start();
server.blockUntilShutdown();
}

static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage(“Hello ” + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}

RPC 服务消费者的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.grpc.demo.consumer.service;

import com.grpc.demo.generate.GreeterGrpc;
import com.grpc.demo.generate.HelloReply;
import com.grpc.demo.generate.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloWorldConsumer {

private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private static final Logger logger = Logger.getLogger(HelloWorldConsumer.class.getName());

public HelloWorldConsumer(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}

HelloWorldConsumer(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

public void greet(String name) {
logger.info(“==> Will try to greet ” + name + ” …”);
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, “RPC failed: {0}”, e.getStatus());
return;
}
logger.info(“==> Greeting: ” + response.getMessage());
}

public static void main(String[] args) throws Exception {
HelloWorldConsumer client = new HelloWorldConsumer(“localhost”, 50051);
try {
String user = “World”;
client.greet(user);
} finally {
client.shutdown();
}
}
}

先后启动Provider、Consumer应用,最终输出的日志信息如下图所示

Provider应用的日志信息:

Consumer应用的日志信息:

本文作者: Clay
发布时间: 2019-06-25 20:40:21
本文链接: https://www.techgrow.cn/posts/ceb4ff2b.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

分类
Java 开发经历

spring cloud config遇到的问题

本机环境调试一个使用到spring cloud config的项目,启动到时候访问config服务报503错误,感到奇怪到是之前一直运行得好好到。后来终于发现网络启用了全局代理模式,也就是通过远程访问本地网络环境,那肯定是不通的。

附上http状态码说明:

5xx(服务器错误)

这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

代码 说明

500 (服务器内部错误) 服务器遇到错误,无法完成请求。

501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。

502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。

503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。

505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。

分类
Java

Spring Security实例-具有基于令牌的身份验证的Spring Security

介绍
如何使用Spring实现基于令牌的安全性功能。 让我们看一下工作流程以获得更好的理解:

  1. 用户使用用户名和密码发送请求。
  2. Spring Security将令牌返回给客户端API。
  3. 客户端API在每个请求中发送令牌作为身份验证的一部分。
  4. 使令牌在注销时失效。

让我们看看这个工作流程的样子:

1.Maven安装
我们将使用Spring Boot和Maven处理依赖关系。 在构建Spring Boot Web应用程序时,我们将为应用程序使用以下starters。

  1. Spring Boot Web starter
  2. Spring Boot Security starter.
  3. JPA starter

现在,我们的pom.xml如下所示:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 数据库布局

我将在数据库级别简化此应用程序,我将使用一个表来存储用户详细信息和令牌。 在用户配置文件请求用户创建一个并返回此令牌之前,不会有针对用户个人资料的令牌。 表结构如下所示:

这不是产品级别就绪的表,但主要思想是存储用于客户概要文件的令牌,并将该令牌用于身份验证和授权。 您可以根据需要更改/调整此工作流程。

3. JPA 资料库

要保存并获取客户档案的令牌信息,我们需要创建一个自定义存储库。 该存储库负责根据令牌获取客户信息。 客户服务将使用我们的客户资源库基于令牌获取客户详细信息或执行登录。

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {

    @Query(value = "SELECT u FROM Customer u where u.userName = ?1 and u.password = ?2 ")
    Optional login(String username,String password);
    Optional findByToken(String token);
}

4. 客户验证服务

我们的客户验证服务遵循两个核心运营

提供登录功能以将令牌返回给客户端。
根据提供的令牌验证客户。
我们的客户服务如下所示:

@Service("customerService")
public class DefaultCustomerService implements CustomerService {

    @Autowired
    CustomerRepository customerRepository;

    @Override
    public String login(String username, String password) {
        Optional customer = customerRepository.login(username,password);
        if(customer.isPresent()){
            String token = UUID.randomUUID().toString();
            Customer custom= customer.get();
            custom.setToken(token);
            customerRepository.save(custom);
            return token;
        }

        return StringUtils.EMPTY;
    }

    @Override
    public Optional findByToken(String token) {
        Optional customer= customerRepository.findByToken(token);
        if(customer.isPresent()){
            Customer customer1 = customer.get();
            User user= new User(customer1.getUserName(), customer1.getPassword(), true, true, true, true,
                    AuthorityUtils.createAuthorityList("USER"));
            return Optional.of(user);
        }
        return  Optional.empty();
    }
}

让我们检查一下上面代码中的操作:

  1. 登录方法接受用户名和密码,并将返回用于成功凭证的令牌
  2. 我们将对所有安全资源使用第二种方法  

5. Spring Security 配置

这些是使用Spring Security和基于令牌的身份验证来保护REST API的主要配置类。在本节中,我们将讨论以下类:

  • AuthenticationProvider : 通过其身份验证令牌查找用户.
  • AuthenticationFilter :从请求标头中提取身份验证令牌
  • SecurityConfiguration : Spring Security 配置

5.1 Token Authentication Provider

AuthenticationProvider负责根据客户端在标头中发送的身份验证令牌来查找用户。 这就是我们基于Spring的令牌身份验证提供程序的外观:

@Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

 @Autowired
 CustomerService customerService;

 @Override
 protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
  //
 }

 @Override
 protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {

  Object token = usernamePasswordAuthenticationToken.getCredentials();
  return Optional
   .ofNullable(token)
   .map(String::valueOf)
   .flatMap(customerService::findByToken)
   .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
 }

我们的AuthenticationProvider使用CustomerService根据令牌查找客户。

5.2  Token Authentication Filter

令牌认证过滤器负责从头获取认证过滤器,并调用认证管理器进行认证。 身份验证过滤器如下所示:

public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    AuthenticationFilter(final RequestMatcher requiresAuth) {
        super(requiresAuth);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {

        Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN
        String token= httpServletRequest.getHeader(AUTHORIZATION);
        token= StringUtils.removeStart(token, "Bearer").trim();
        Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(token, token);
        return getAuthenticationManager().authenticate(requestAuthentication);

    }

    @Override
    protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }
}

让我们强调几个这里的要点:

  1. 此过滤器将身份验证委托给UsernamePasswordAuthenticationToken 
  2. 此过滤器仅对特定的URL启用(在下一节中说明)

5.3  Spring Security配置

这负责将所有内容组合在一起。让我们看看我们的Spring安全配置是怎样:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


 private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
  new AntPathRequestMatcher("/api/**")
 );

 AuthenticationProvider provider;

 public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
  super();
  this.provider = authenticationProvider;
 }

 @Override
 protected void configure(final AuthenticationManagerBuilder auth) {
  auth.authenticationProvider(provider);
 }

 @Override
 public void configure(final WebSecurity webSecurity) {
  webSecurity.ignoring().antMatchers("/token/**");
 }

 @Override
 public void configure(HttpSecurity http) throws Exception {
  http.sessionManagement()
   .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
   .and()
   .exceptionHandling()
   .and()
   .authenticationProvider(provider)
   .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
   .authorizeRequests()
   .requestMatchers(PROTECTED_URLS)
   .authenticated()
   .and()
   .csrf().disable()
   .formLogin().disable()
   .httpBasic().disable()
   .logout().disable();
 }

 @Bean
 AuthenticationFilter authenticationFilter() throws Exception {
  final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
  filter.setAuthenticationManager(authenticationManager());
  //filter.setAuthenticationSuccessHandler(successHandler());
  return filter;
 }

 @Bean
 AuthenticationEntryPoint forbiddenEntryPoint() {
  return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
 }
}

让我们检查一些要点:

  1. 与请求模式/api/ **匹配的所有URL都是安全的,并且需要有效的令牌进行访问。
  2. The webSecurity.ignoring().antMatchers("/token/**") 显示安全检查中排除的所有请求。
  3. 我们已经在Spring安全性中注册了AuthenticationProvider。 Spring Security将检查令牌验证.
  4. 配置方法包括基本配置以及禁用基于表单的登录和其他标准功能

此步骤总结了使用Spring Security和基于令牌的身份验证来保护REST API的步骤。 在下一步中,我们将设置一个简单的Spring Boot Web应用程序以测试我们的工作流程。

 6. Spring Boot 控制器

让我们创建一个简单的Spring Boot控制器来测试我们的应用程序:

6.1 Token Controller

该控制器负责返回令牌以获取有效凭证:

@RestController
public class TokenController {

    @Autowired
    private CustomerService customerService;

    @PostMapping("/token")
    public String getToken(@RequestParam("username") final String username, @RequestParam("password") final String password){
       String token= customerService.login(username,password);
       if(StringUtils.isEmpty(token)){
           return "no token found";
       }
       return token;
    }
}

6.2 安全用户资料控制器

这是安全控制器。 它将返回有效令牌的用户资料。仅在传递有效令牌后才能访问此控制器:

@RestController
public class UserProfileController {

    @Autowired
    private CustomerService customerService;

    @GetMapping(value = "/api/users/user/{id}",produces = "application/json")
    public Customer getUserDetail(@PathVariable Long id){
        return customerService.findById(id);
    }
}

7. 测试应用

让我们构建和部署应用程序。一旦应用程序运行,就可以使用任何REST客户端来测试我们的应用程序(我使用的是Postman):

不使用 Access Token:

让我们从API获取令牌:

将令牌用于安全URL:

总结

在本文中,我们了解了如何使用基于令牌的方法来使用Spring Security保护REST API。 我们介绍了用于保护REST API的不同配置和设置。

若有需要这篇文章的Intellij IDEA SpringBoot项目源代码的朋友,可先微信扫下图二维码打赏8~9元,然后截图付款收据,加QQ:5404125(备注:源码)联系下载源代码。

分类
Java

Spring Cloud入门使用例子

SpringCloud 是微服务中的翘楚,最佳的落地方案。本仓库整合了一些工作中经常用到的一些技术,作为使用微服务框架的演示。

https://github.com/intomylife/SpringCloud

分类
Docker MySql orchestrator

docker mysql orchestrator高可用构建

使用到这个项目的构建脚本:

https://github.com/pondix/docker-mysql-proxysql

但是有个bug,需要修改一下conf/orchestrator/Dockerfile

FROM debian:stretch

MAINTAINER Nikolaos Vyzas nick@proxysql.com

WORKDIR /usr/local/orchestrator

#deb文件需要放在Dockerfile同级目录下
COPY orchestrator_3.1.4_amd64.deb /usr/local/orchestrator/
EXPOSE 3000
WORKDIR /usr/local/orchestrator
RUN apt update && apt -y install wget && apt -y install ./orchestrator_3.1.4_amd64.deb && mkdir /var/lib/orchestrator && rm -rf /var/lib/apt/lists/*

CMD /usr/local/orchestrator/orchestrator http

分类
Docker MySql

用Docker实现MySQL ProxySQL读写分离

ProxySQL是一个高性能的MySQL中间件,能够代理数百台MySQL服务器,支持数十万的并发连接。ProxySQL代理MySQL并提供读写分离,查询重写和数据分片等功能。
这篇文章主要介绍用Docker Compose编排用ProxySQL实现MySQL集群上读写分离的实践过程。
Docker Compose编排中使用了一个现有镜像,就是breeze2/proxysql
本次实践源码上传在GitHub的breeze2/mysql-proxy-docker

Docker Compose编排

这个编排主要实现一主两从的一个MySQL集群和一个ProxySQL代理,ProxySQL代理MYSQL集群的数据请求并且进行读写分离。

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
L–mysql-proxy-docker //主目录
L–scripts //本地(Docker宿主)使用的一些脚本
L–mysql_set_users_and_repls.sh //设置各个数据库账号和开启主从复制
L–…
L–services //需要build的服务(目前是空)
L–volumes //各个容器的挂载数据卷
L–mysql_node0
L–mysql_node1
L–mysql_node2
L–proxysql
L–share //各个容器共享的目录
L–scripts //各个容器共用的一些脚本
L–parameters.env //账号密码等环境参数
L–docker-compose.yml //编排配置

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
version: “2”
services:

master:
image: mysql:5.7
container_name: mysql_node0
restart: always
mem_limit: 256m
networks:
net1:
ipv4_address: 10.6.0.10
ports:
– “3306”
volumes:
– “./volumes/share/:/root/share/”
– “./volumes/mysql_node0/lib/:/var/lib/mysql/”
– “./volumes/mysql_node0/conf/:/etc/mysql/conf.d/”
env_file:
– ./parameters.env

slave1:
image: mysql:5.7
container_name: mysql_node1
restart: always
depends_on:
– master
mem_limit: 256m
networks:
net1:
ipv4_address: 10.6.0.11
ports:
– “3306”
volumes:
– “./volumes/share/:/root/share/”
– “./volumes/mysql_node1/lib/:/var/lib/mysql/”
– “./volumes/mysql_node1/conf/:/etc/mysql/conf.d/”
env_file:
– ./parameters.env
slave2:
image: mysql:5.7
container_name: mysql_node2
depends_on:
– master
restart: always
mem_limit: 256m
networks:
net1:
ipv4_address: 10.6.0.12
ports:
– “3306”
volumes:
– “./volumes/share/:/root/share/”
– “./volumes/mysql_node2/lib/:/var/lib/mysql/”
– “./volumes/mysql_node2/conf/:/etc/mysql/conf.d/”
env_file:
– ./parameters.env

proxy:
image: breeze2/proxysql:1.4.3
container_name: proxysql
depends_on:
– master
– slave1
– slave2
restart: always
mem_limit: 256m
networks:
net1:
ipv4_address: 10.6.0.9
ports:
– “127.0.0.1:60320:6032”
– “60330:6033”
volumes:
– “./volumes/proxysql/conf:/etc/proxysql”
entrypoint: “proxysql -f -c /etc/proxysql/pr.cnf”
env_file:
– ./parameters.env

networks:
net1:
driver: bridge
ipam:
config:
– subnet: 10.6.0.0/16
gateway: 10.6.0.1

这里配置了四个容器服务,一个breeze2/proxysql,负责代理各个数据库;三个mysql,其中一个是主库,另外两个是从库。每个容器服务都指定了静态IP,即使服务重启也不会出现IP错乱问题。proxysql容器的6032是提供管理服务的端口,只对Docker宿主机本地IP开放,而6033是代理数据请求的端口,可以对Docker宿主机网络IP开放。

环境参数

parameters.env

1
2
3
4
MYSQL_ROOT_PASSWORD=123456
MYSQL_DATABASE=testing
MYSQL_User=testing
MYSQL_PASSWORD=testing

数据库配置

这里简单的配置一下各个数据库数据复制备份相关的参数
主库,mysql_node0/conf/my.cnf

1
2
3
4
5
6
7
8
9
10
[mysqld]
server-id=1
gtid-mode=on
enforce-gtid-consistency=true
log-bin=mysql-bin
binlog-do-db=testing
binlog-ignore-db=mysql
replicate-do-db=testing
replicate-ignore-db=mysql
expire_logs_days=7

从库,mysql_node1/conf/my.cnf

1
2
3
4
5
6
7
8
9
10
[mysqld]
server-id=2
gtid-mode=on
enforce-gtid-consistency=true
log-bin=mysql-bin
binlog-do-db=testing
binlog-ignore-db=mysql
replicate-do-db=testing
replicate-ignore-db=mysql
expire_logs_days=7

从库,mysql_node2/conf/my.cnf

1
2
3
4
5
6
7
8
9
10
[mysqld]
server-id=3
gtid-mode=on
enforce-gtid-consistency=true
log-bin=mysql-bin
binlog-do-db=testing
binlog-ignore-db=mysql
replicate-do-db=testing
replicate-ignore-db=mysql
expire_logs_days=7

ProxySQL配置

proxysql/conf/pr.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
datadir=”/tmp”
# 管理平台参数
admin_variables =
{
admin_credentials=”admin2:admin2″
mysql_ifaces=”0.0.0.0:6032″
refresh_interval=2000
}
# mysql全局参数
mysql_variables =
{
threads=4
max_connections=2048
default_query_delay=0
default_query_timeout=36000000
have_compress=true
poll_timeout=2000
# interfaces=”0.0.0.0:6033;/tmp/proxysql.sock”
interfaces=”0.0.0.0:6033″
default_schema=”information_schema”
stacksize=1048576
server_version=”5.5.30″
connect_timeout_server=3000
# make sure to configure monitor username and password
# https://github.com/sysown/proxysql/wiki/Global-variables#mysql-monitor_username-mysql-monitor_password
monitor_username=”pr_muser”
monitor_password=”pr_mpass”
monitor_history=600000
monitor_connect_interval=60000
monitor_ping_interval=10000
monitor_read_only_interval=1500
monitor_read_only_timeout=500
ping_interval_server_msec=120000
ping_timeout_server=500
commands_stats=true
sessions_sort=true
connect_retries_on_failure=10
}
# mysql用户参数
mysql_users =
(
{
username = “pr_auser”
password = “pr_apass”
default_hostgroup = 0
}
)
# mysql服务器参数,10.6.0.10是主库放在0组,其他是从库放在1组
mysql_servers =
(
{
address = “10.6.0.10”
port = 3306
weight = 1
hostgroup = 0
max_connections = 50
},
{
address = “10.6.0.11”
port = 3306
weight = 2
hostgroup = 1
max_connections = 100
},
{
address = “10.6.0.12”
port = 3306
weight = 2
hostgroup = 1
max_connections = 150
}
)
# mysql请求规则,以下配置是读时加锁的请求发给0组,普通读取的请求发给1组,其他默认发给0组(上面的default_hostgroup)
mysql_query_rules:
(
{
rule_id=1
active=1
match_pattern=”^SELECT .* FOR UPDATE$”
destination_hostgroup=0
apply=1
},
{
rule_id=2
active=1
match_pattern=”^SELECT”
destination_hostgroup=1
apply=1
}
)

实际运行

在主目录下执行docker-compose up -d构建并运行整个Docker服务。

开启主从复制

在主目录下执行:

1
$ sh ./scripts/mysql_set_users_and_repls.sh

实际上是调用了挂载在数据库容器里的一些脚本:

1
2
3
volumes/share/scripts/msyql_grant_proxysql_users.sh #设置给proxysql用的账号,pr_auser和pr_muser
volumes/share/scripts/msyql_grant_slave.sh #主库设置给从库用的账号
volumes/share/scripts/msyql_grant_slave.sh #从库开始数据复制

脚本里的执行命令都很简单,一看就明。

开启ProxySQL代理

构建整个服务的时候,proxysql会先挂载主目录下的./volumes/proxysql/conf/pr.cnf到容器内/etc/proxysql/pr.cnf,然后执行proxysql -f -c /etc/proxysql/pr.cnf,所以这里的ProxySQL是按照pr.cnf里面的配置开启MySQL代理服务的,请仔细阅读上面ProxySQL配置。若有需要在ProxySQL运行的过程中修改配置,可以登录ProxySQL的管理系统操作。

ProxySQL管理系统

在Docker宿主机上登录ProxySQL管理系统(Docker宿主机需要装有MySQL Client):

1
$ mysql -u admin2 -padmin2 -h 127.0.0.1 -P60320

在ProxySQL管理系统上添加一个mysql_user(注意这个testing账号是各个数据库都已建立的,具体查看上面环境参数):

1
mysql> INSERT INTO mysql_users(username, password, default_hostgroup) VALUES (‘testing’, ‘testing’, 2);

确认是否已添加:

1
2
3
4
5
6
7
8
mysql> SELECT * FROM mysql_users;
+———-+———-+——–+———+——————-+—————-+—————+————————+————–+———+———-+—————–+
| username | password | active | use_ssl | default_hostgroup | default_schema | schema_locked | transaction_persistent | fast_forward | backend | frontend | max_connections |
+———-+———-+——–+———+——————-+—————-+—————+————————+————–+———+———-+—————–+
| pr_auser | pr_apass | 1 | 0 | 0 | | 0 | 0 | 0 | 1 | 1 | 10000 |
| testing | testing | 1 | 0 | 2 | NULL | 0 | 1 | 0 | 1 | 1 | 10000 |
+———-+———-+——–+———+——————-+—————-+—————+————————+————–+———+———-+—————–+
2 rows in set (0.00 sec)

把当前修改(MEMORY层)加载到正在运行的ProxySQL(RUNTIME层):

1
mysql> LOAD MYSQL USERS TO RUNTIME;

在Docker宿主机上确认ProxySQL是否已加载最新配置:

1
2
3
4
5
6
7
$ mysql -u testing -ptesting -h 127.0.0.1 -P60330
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.5.30 (ProxySQL Admin Module)


若想ProxySQL重启后依然是当前配置,要把当前修改(MEMORY层)保存到ProxySQL的Sqlite数据库里(DISK层):

1
mysql> SAVE MYSQL USERS TO DISK;

ProxySQL配置系统分三层,分别是MEMORY层、RUNTIME层和DISK层。ProxySQL管理系统操作的是MEMORY层,当前ProxySQL运行的是RUNTIME层,保存在ProxySQL本地Sqlite数据库里的是DISK层,详情请阅读文档ProxySQL Configuration

SysBench测试工具

SysBench是一个脚本化、多线程的基准测试工具,经常用于评估测试各种不同系统参数下的数据库负载情况。
SysBench的使用教程可以参考sysbench 0.5使用手册
这里使用SysBench-v1.0.9来对ProxySQL进行测试。

SysBench Test Prepare

首先,做测试准备:

1
2
3
4
5
6
7
8
$ sysbench /usr/local/Cellar/sysbench/1.0.9/share/sysbench/oltp_read_write.lua –threads=5 –max-requests=0 –time=36 –db-driver=mysql –mysql-user=pr_auser –mysql-password=’pr_apass’ –mysql-port=60330 –mysql-host=127.0.0.1 –mysql-db=testing –report-interval=1 prepare
sysbench 1.0.9 (using bundled LuaJIT 2.1.0-beta2)

Initializing worker threads…

Creating table ‘sbtest1’…
Inserting 10000 records into ‘sbtest1’
Creating a secondary index on ‘sbtest1’…

注意,/usr/local/Cellar/sysbench/1.0.9/share/sysbench/oltp_read_write.lua文件可以在SysBench安装包里找到,执行命令后,会在主库testing数据库里生成一个sbtest1表并插入一些数据,
在从库里一样可以看到testing数据库下有sbtest1表,说明主从复制已生效。

SysBench Test Run

然后,开始读写测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sysbench /usr/local/Cellar/sysbench/1.0.9/share/sysbench/oltp_read_write.lua –threads=5 –max-requests=0 –time=36 –db-driver=mysql –mysql-user=pr_auser –mysql-password=’pr_apass’ –mysql-port=60330 –mysql-host=127.0.0.1 –mysql-db=testing –report-interval=1 run
sysbench 1.0.9 (using bundled LuaJIT 2.1.0-beta2)

Running the test with following options:
Number of threads: 5
Report intermediate results every 1 second(s)
Initializing random number generator from current time


Initializing worker threads…

Threads started!

[ 1s ] thds: 5 tps: 51.66 qps: 1087.83 (r/w/o: 769.92/209.62/108.29) lat (ms,95%): 144.97 err/s: 0.00 reconn/s: 0.00
[ 2s ] thds: 5 tps: 61.26 qps: 1229.13 (r/w/o: 862.60/243.01/123.52) lat (ms,95%): 142.39 err/s: 1.00 reconn/s: 0.00
[ 3s ] thds: 5 tps: 60.85 qps: 1237.04 (r/w/o: 867.92/247.41/121.71) lat (ms,95%): 121.08 err/s: 0.00 reconn/s: 0.00
[ 4s ] thds: 5 tps: 67.07 qps: 1332.44 (r/w/o: 931.01/267.29/134.15) lat (ms,95%): 127.81 err/s: 0.00 reconn/s: 0.00

查看结果

登录ProxySQL管理系统,查看统计结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$ mysql -u admin2 -padmin2 -h 127.0.0.1 -P60320
mysql> select * from stats_mysql_query_digest limit\G;
*************************** 1. row ***************************
hostgroup: 0
schemaname: testing
username: pr_auser
digest: 0xE365BEB555319B9E
digest_text: DELETE FROM sbtest1 WHERE id=?
count_star: 2564
first_seen: 1508313300
last_seen: 1508313336
sum_time: 1923227
min_time: 149
max_time: 39773
*************************** 2. row ***************************
hostgroup: 0
schemaname: testing
username: pr_auser
digest: 0xFB239BC95A23CA36
digest_text: UPDATE sbtest1 SET c=? WHERE id=?
count_star: 2566
first_seen: 1508313300
last_seen: 1508313336
sum_time: 2016454
min_time: 158
max_time: 53514

*************************** 13. row ***************************
hostgroup: 1
schemaname: testing
username: pr_auser
digest: 0xDBF868B2AA296BC5
digest_text: SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ?
count_star: 2570
first_seen: 1508313300
last_seen: 1508313336
sum_time: 7970660
min_time: 216
max_time: 56153
*************************** 14. row ***************************
hostgroup: 1
schemaname: testing
username: pr_auser
digest: 0xAC80A5EA0101522E
digest_text: SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c
count_star: 2570
first_seen: 1508313300
last_seen: 1508313336
sum_time: 10148202
min_time: 272
max_time: 58032
14 rows in set (0.00 sec)

可以看到读操作都发送给了hostgroup=1组,写操作都发送给了hostgroup=0组,说明读写分离已生效,读写分离配置请仔细阅读上面ProxySQL配置mysql_query_rules部分

此致

到此,用Docker实现MySQL ProxySQL读写分离已完成。另外ProxySQL提供的查询重写功能,其实是利用mysql_query_rules配置,对接收到的查询语句进行正则替换,再传递给数据库服务器,详情请阅读文档ProxySQL Configuration中的“MySQL Query Rules”部分;而数据分片功能,在真实数据分片的基础上,再结合mysql_query_rules配置,重写query到正确的主机、数据库或表上,详细内容可以阅读MySQL Sharding with ProxySQL

分类
Redis

redis集群搭建

一、准备工具

安装docker(来自官网

(1) 安装所需的软件包

$ sudo yum install -y yum-utils \ device-mapper-persistent-data \ lvm2
复制代码

(2) 使用以下命令来设置稳定的存储库

$ sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo
复制代码

(3) 安装docker ce(docker社区版)

$ sudo yum install docker-ce
复制代码

(4) 启动docker

$ sudo systemctl start docker
复制代码

在docker库获取redis镜像(截至2019-03-27,最新版为5.0.4)

$ docker pull redis
复制代码

至此,docker上redis cluster所有工具准备完毕,我们在命令行上输入docker images,就可以查看到已经安装的镜像

二、 集群搭建

创建Redis 容器

#####(1)创建redis配置文件(redis-cluster.tmpl)

我在路径/home下创建一个文件夹redis-cluster,在路径/home/redis-cluster下创建一个文件redis-cluster.tmpl,并把以下内容复制过去。(注:路径可自定义,我用的是/home/redis-cluster

port ${PORT}                                       ##节点端口
protected-mode no                                  ##开启集群模式
cluster-enabled yes                                ##cluster集群模式
cluster-config-file nodes-${PORT}.conf 
                     ##集群配置名
cluster-node-timeout 5000                          ##超时时间
cluster-announce-ip 192.168.XX.XX                  ##实际为各节点网卡分配ip  先用上网关ip代替
cluster-announce-port ${PORT}                      ##节点映射端口
cluster-announce-bus-port 1${PORT}                 ##节点总线端口
appendonly yes                                     ##持久化模式
复制代码

通过命令,可查看

[root@node-01 redis-cluster]# cat /home/redis-cluster/redis-cluster.tmpl
port ${PORT}
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.1.221
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
appendonly yes
复制代码

备注:此模版文件为集群节点通用文件  其中${PORT} 将读取命令行变量  ip则根据网卡分配ip进行替换  以保证节点配置文件除端口以及ip 全部一致。

(2) 创建自定义network
$ docker network create redis-net
复制代码

docker network ls ##查看网卡信息

[root@node-01 conf]# docker network ls 
NETWORK ID NAME DRIVER SCOPE 7f804fa5fbd2 redis-net bridge local
复制代码

备注:创建redis-net虚拟网卡 目的是让docker容器能与宿主(centos7)桥接网络 并间接与外界连接

(3) 查看redis-net虚拟网卡网关ip
[root@node-01 conf]# docker network inspect redis-net | grep "Gateway" | grep --color=auto -P '(\d{1,3}.){3}\d{1,3}' -o 172.21.0.1
复制代码

备注:docker network inspect network-name 显示 network-name对应配置信息         (gerp 过滤网关配置行 并筛选ip ) 可人工

(4) 在/home/redis-cluster下生成conf和data目标,并生成配置信息
 $ for port in `seq 7000 7005`; do \
  mkdir -p ./${port}/conf \
  && PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
  && mkdir -p ./${port}/data; \
  done
复制代码

共生成6个文件夹,从7000到7005,每个文件夹下包含data和conf文件夹,同时conf里面有redis.conf配置文件

(5) 创建6个redis容器
$ ip=2
for port in `seq 7000 7005`; do \
docker run -d -p ${port}:${port} -p 1${port}:1${port} \
-v /Users/kevin/Docker/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /Users/kevin/Docker/redis-cluster/${port}/data:/data \
--restart always --name redis-${port} --net redis-net --ip 172.20.0.${ip} \
 redis redis-server /usr/local/etc/redis/redis.conf; \
 let ip++;
done

复制代码
备注:命令译为  循环7010 - 7015  运行redis 容器
docker  run            运行
-d                          守护进程模式
--restart always     保持容器启动
--name redis-710* 容器起名
--net redis-net    容器使用虚拟网卡
-p                        指定宿主机器与容器端口映射 701*:701*
-P                        指定宿主机与容器redis总线端口映射 1701*:1701*
--privileged=true -v /home/redis-cluster/701*/conf/redis.conf:/usr/local/etc/redis/redis.conf
付权将宿主701*节点文件挂载到容器/usr/local/etc/redis/redis.conf 文件中
--privileged=true -v /home/redis-cluster/${port}/data:/data \
付权将宿主701*/data目录挂载到容器/data目录中
--sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf;
容器根据挂载的配置文件启动 redis服务端
复制代码
6、通过命令docker ps可查看刚刚生成的6个容器信息
[root@node-01 redis-cluster]# docker ps
CONTAINER ID        IMAGE                                         COMMAND                  CREATED             STATUS                PORTS                                                              NAMES
15c479074b87        redis                                         "docker-entrypoint..."   10 seconds ago      Up 9 seconds          0.0.0.0:7005->7005/tcp, 6379/tcp, 0.0.0.0:17005->17005/tcp         redis-7005
45bec33c2c35        redis                                         "docker-entrypoint..."   11 seconds ago      Up 10 seconds         0.0.0.0:7004->7004/tcp, 6379/tcp, 0.0.0.0:17004->17004/tcp         redis-7004
45482d9e5bb8        redis                                         "docker-entrypoint..."   11 seconds ago      Up 10 seconds         0.0.0.0:7003->7003/tcp, 6379/tcp, 0.0.0.0:17003->17003/tcp         redis-7003
f633d5c767c9        redis                                         "docker-entrypoint..."   11 seconds ago      Up 10 seconds         0.0.0.0:7002->7002/tcp, 6379/tcp, 0.0.0.0:17002->17002/tcp         redis-7002
eefc0d49fedf        redis                                         "docker-entrypoint..."   11 seconds ago      Up 10 seconds         0.0.0.0:7001->7001/tcp, 6379/tcp, 0.0.0.0:17001->17001/tcp         redis-7001
58b311e5dbcb        redis                                         "docker-entrypoint..."   12 seconds ago      Up 11 seconds         0.0.0.0:7000->7000/tcp, 6379/tcp, 0.0.0.0:17000->17000/tcp         redis-7000  
复制代码
7、查看容器分配ip
[root@node-01 conf]# docker network inspect redis-net
[
{
    "Name": "redis-net",
    "Id": "7f804fa5fbd2473f1e6e9783ac52aa980c355e3253b15a28e10f8793c46fc39b",
    "Created": "2019-03-27T12:59:14.45569686+08:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": {},
        "Config": [
            {
                "Subnet": "172.21.0.0/16",
                "Gateway": "172.21.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": false,
    "Containers": {
        "449c38916b4ef919d1b1828da3f69e0f5256eb2023204f25f69fbb8c449f2163": {
            "Name": "redis-7005",
            "EndpointID": "5c89e6fdd0e983d6d2b950c9bc41616920265dda93f2852650ca5e9da1da22b1",
            "MacAddress": "02:42:ac:15:00:07",
            "IPv4Address": "172.21.0.7/16",
            "IPv6Address": ""
        },
        "600aa2f84dfbe72a19caf5dc684cb7691e89a43753f70d74355468b7659caa23": {
            "Name": "redis-7000",
            "EndpointID": "75873081222c7a2eedc5f3ee639df5cc7645790012dd72e34c97fd97dcb5cf47",
            "MacAddress": "02:42:ac:15:00:02",
            "IPv4Address": "172.21.0.2/16",
            "IPv6Address": ""
        },
        "78aeae8b2d381d3789848f45f766dd01d7c0d5443505920decf41fce751f765d": {
            "Name": "redis-7002",
            "EndpointID": "a70d816cd9e3650b1d10507a85fa85c0c2e143ba99c0974fb000ec87ad5b173d",
            "MacAddress": "02:42:ac:15:00:04",
            "IPv4Address": "172.21.0.4/16",
            "IPv6Address": ""
        },
        "7ecd2e1372a2bc15cb89fad856ab410757b49936f662ce170990c04e1861037d": {
            "Name": "redis-7004",
            "EndpointID": "f8cdb522601e9667ec06dc0dc0d39b46331b3d24cbea1155b00b73e600eca8c7",
            "MacAddress": "02:42:ac:15:00:06",
            "IPv4Address": "172.21.0.6/16",
            "IPv6Address": ""
        },
        "bd8254045c66bc6569c9a585b58cb028e864b6c7f16d836009debd3a3350913c": {
            "Name": "redis-7003",
            "EndpointID": "6a25ff0046e6d6575579cdf08bccbb2a62b29460e3932de3f8e476bb908fdc9c",
            "MacAddress": "02:42:ac:15:00:05",
            "IPv4Address": "172.21.0.5/16",
            "IPv6Address": ""
        },
        "cfef6ca3aeea9d3b4fa527538fbe2a2f79aac25d52921b4edc31839b6f330887": {
            "Name": "redis-7001",
            "EndpointID": "98a36b61bc6f50f78444a6cefa5e9999f5366e72425602511bc95f602ef0dc93",
            "MacAddress": "02:42:ac:15:00:03",
            "IPv4Address": "172.21.0.3/16",
            "IPv6Address": ""
        }
    },
    "Options": {},
    "Labels": {}
}
]
复制代码

备注:每个容器对应分配了ip 需要修改节点配置文件redis.conf与其对应并重启容器。

(8) 修改宿主挂载目录配置文件
vim /home/redis-cluster/700*/conf/redis.conf ##编辑ip为查询分配的ip 并esc+:wq退出

port 7000
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.21.0.2
cluster-announce-port 7000
cluster-announce-bus-port 17000
appendonly yes
复制代码

启动集群

进入一个节点
docker exec -it redis-7000 bash
复制代码
使用Redis 5创建集群,redis-cli只需键入:
redis-cli --cluster create 172.20.0.2:7000 172.20.0.3:7001 172.20.0.4:7002 172.20.0.5:7003 172.20.0.6:7004 172.20.0.7:7005 --cluster-replicas 1
复制代码
至此,集群已经创建好了。
[root@node-01 data]# docker exec -it redis-7000 bash
root@600aa2f84dfb:/data# redis-cli --cluster create 172.21.0.2:7000  172.21.0.3:7001  172.21.0.4:7002  172.21.0.5:7003  172.21.0.6:7004  172.21.0.7:7005 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.21.0.6:7004 to 172.21.0.2:7000
Adding replica 172.21.0.7:7005 to 172.21.0.3:7001
Adding replica 172.21.0.5:7003 to 172.21.0.4:7002
M: 0d403d866c4bdfa90f3a1e21b2a33356474014ee 172.21.0.2:7000
   slots:[0-5460] (5461 slots) master
M: ab4bf4833926dd7ffca5f2d34ef754466730e7d7 172.21.0.3:7001
   slots:[5461-10922] (5462 slots) master
M: 846f087ee5885f17d5a5c05d2eb2ba470441f700 172.21.0.4:7002
   slots:[10923-16383] (5461 slots) master
S: e44c2b96f1446b1860fe582547634b12a461407d 172.21.0.5:7003
   replicates 846f087ee5885f17d5a5c05d2eb2ba470441f700
S: 4efa0a9b7b4b159807900efa5f86410e55c1d2b3 172.21.0.6:7004
   replicates 0d403d866c4bdfa90f3a1e21b2a33356474014ee
S: 8a616ee30a7104bcd341d57af825c6b5b30176ff 172.21.0.7:7005
   replicates ab4bf4833926dd7ffca5f2d34ef754466730e7d7
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.21.0.2:7000)
M: 0d403d866c4bdfa90f3a1e21b2a33356474014ee 172.21.0.2:7000
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: e44c2b96f1446b1860fe582547634b12a461407d 172.21.0.5:7003
   slots: (0 slots) slave
   replicates 846f087ee5885f17d5a5c05d2eb2ba470441f700
M: ab4bf4833926dd7ffca5f2d34ef754466730e7d7 172.21.0.3:7001
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 8a616ee30a7104bcd341d57af825c6b5b30176ff 172.21.0.7:7005
   slots: (0 slots) slave
   replicates ab4bf4833926dd7ffca5f2d34ef754466730e7d7
M: 846f087ee5885f17d5a5c05d2eb2ba470441f700 172.21.0.4:7002
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 4efa0a9b7b4b159807900efa5f86410e55c1d2b3 172.21.0.6:7004
   slots: (0 slots) slave
   replicates 0d403d866c4bdfa90f3a1e21b2a33356474014ee
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
复制代码
连接客户端,查看主从信息
root@600aa2f84dfb:/data# redis-cli -c -p 7000     
127.0.0.1:7000> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.21.0.6,port=7004,state=online,offset=3556,lag=1
master_replid:ef97265d34769b68096786c3e802fb3a13968fe9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3556
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3556
复制代码

其它注意事项

宿主主机对外开放ip
for port in `seq 7000 7005`; do \  
firewall-cmd --zone=public --add-port=${port}/tcp --permanent  
done
#重新载入
firewall-cmd --reload
复制代码

#####需要暂停容器并删除容器 以便重复6步骤

for port in `seq 7000 7005`; do \
  docker stop redis-${port};
  docker rm redis-${port};
done

分类
macOS

完美解决 MacOS 下不能 ping Docker 容器的问题 | MacOS 使用 ssh 链接

今天下午从docker hubpullCentOS。跑起来后装了ssh,然后在宿主机上ping不通CentOScontainer…..这是多么囧的事情…

  • 对于docker for mac不能ping容器官网给的解释是
    ….
    对于以上解释通过端口映射可以解决docker for mac连接容器的问题,但是有些情况下是不需要端口的怎么办?以上说法对我毫无卵用。带着问题,Google了下据说有个openVPN可以解决这个问题。
  • 解决问题
    搜了好一阵子,大多都没有详细说明,最后在GitHub上搜到一个叫 docker-mac-network 的项目。嗯,很是牛逼。

项目README大致翻译

使用OpenVPN来搞定。让你从macOS访问docker for mac

Quickstart

访问docker网络:

安装Tunnelblick  这个软件是OpenVPNmacOS的客户端。

  • 运行 docker-compose up 因为该项目需要生成一些keys,所以第一次启动会耗费点时间。
  • 双击生成的 docker-for-mac.ovpn 文件,或者直接在你的终端运行该文件把它添加到Tunnelblick。 该文件会在项目根目录生成。
  • 使用Tunnelblick链接你新添加的docker-for-mac

现在你可以从你的macOS访问doker里面的网络了。

Implementation notes

该配置由两个服务组成,它们都是基于Alpine

openvpn

使用OpenVPN docker镜像 kylemanna/openvpn.

服务端和客户端配置文件都自动的由helpers/run.sh来生成, 该脚本运行在镜像中,只能通过调整该脚本的配置来访问你docker for mac的网络。

该服务使用网络在TCP 1194端口上跑着,这说明它可以在vm上访问所有的docker网络。

只有172.16.0.0/20 私有网络被配置生成器路由到了Docker for Mac。主机上没有设置DNS服务。

镜像OpenVPN的配置在目录 (/etc/openvpn/*)中,对应宿主的文件系统的./config/,这样便于自定义。

proxy

自打有docker for mac开始,宿主机就不能访问容器的网络。我们使用TCP代理。此镜像使用socat将端口13194转发到OpenVPN容器上。

使用过程

  1. 把项目从GitHubClone下来,放到某个文件夹中。
  2. 修改run.sh脚本
#!/bin/shdest=${dest:-docker.ovpn}if [ ! -f "/local/$dest" ]; then
    echo "*** REGENERATING ALL CONFIGS ***"
    set -ex    #rm -rf /etc/openvpn/*
    ovpn_genconfig -u tcp://localhost
    sed -i 's|^push|#push|' /etc/openvpn/openvpn.conf    echo localhost | ovpn_initpki nopass
    easyrsa build-client-full host nopass
    ovpn_getclient host | sed '
        s|localhost 1194|localhost 13194|;
    s|redirect-gateway.*|route 172.17.0.0 255.255.0.0|; # 这里要配置你container的ip和子网掩码
    ' > "/local/$dest"fi# Workaround for https://github.com/wojas/docker-mac-network/issues/6/sbin/iptables -I FORWARD 1 -i tun+ -j ACCEPTexec ovpn_run
  1. 运行docker-compose.yml。打开终端cd到项目的根目录。输入docker-compose up
    运行后自动创建两个容器,一个为代理容器一个为openvpn服务端容器
  2. 查看项目根目录,会发现有生成的docker-for-mac.ovpn文件
    docker-for-mac.ovpn
  3. 编辑docker-for-mac.ovpn文件,这里是个坑,需要加一个comp-lzo yes属性。
client
nobind
dev tun
remote-cert-tls server

remote localhost 13194 tcp<key>-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6AzE2XEmBxBkX
0PajWJQkLOFTb6pgydCrzpV5/9wSL1mgOGg7X2pA080LgGB8GVoSj+hyOdnPc5SQ
P/JKuEqHDeGmNR3mdROXPBJzEheFtpQP+01bmQgTi8aEFllof+M8N6XdZKbWCMIr
Oa6kHFJTM7S5lC7zqZCQ/Pg0yi81EZWdzanCYT/orB90RTvBMa4R8Y/YOFdtKZip
YVPAokNVu/Vb7Lvwmqhu84RBqeJ9BPsAZuFuSLu9l0HsqZZisAzer05aOAld5SgU
6YsaimXLsff+wuOssM2LXohbHdE11bEN286SvOvecDj3/QfYDzLlVMq9OLX3TPep
dQ2WPZ0zAgMBAAECggEAB3NTSEsgApuZKbQL/PTyUhSeHdpuJ5xNyGPo5s8RwIS3
AUzhF+e1eM1C/D/zHWVijzXGaLTafY2ymKiHCukgyxIKRL8B6JYt57PXRVanLIro
mU9GhPG3OhkyJV5DzIS61FGiSiDa8d298T9JOHeN8Jk0lu5Rhx72lGgdhPfINkbh
i+fcPXTuPpeNKxwcOcV7MQdJOCir1gVwRaHoh3b/1/hBfllUMla0IcVDrOByq5j9
NFvFXp3cIwHtpxXi9HIXjYg50tIpKsZVA1My1X7zSgw4Uy3DkYOCtGfTd3ZPkAZg
M49WrkXzwfS4xCGlptYcXKpMuRK+4CGn3DY62/TuwQKBgQDfTcdCSEjp9XBpp15v
qlfWiHCm2w9qqQj30Bu78vflvJRLHEXgAK5jn5i/oKBDn9P65XT5iMG73IilBtDr
ZqLHxf6bii+3giNk1r7Ij8dABea2DZoDAiXw7gBYeWrShwFoEK1sAOQLpXfKyahg
D5Ri73MzWB6X8NgonTSWgMLCEwKBgQDVP5nxiit6w15ztlisHO0OyI4NOXCz8JJP
/psbCLafn/jO9MVUtD+CGzB/LaenPyN2wgFwS2rW1aP0eQ4CblhdBsR2DPyFQn+Q
kFvlfECViPyqctuQ3Vg5whUnd2uB6s/41Kf5SCesDmc+moq0agCYfcPPC26T8YjN
TitLqugcYQKBgQCLHO1Rwa6K8jjB2V7fugNWx1FG4ZnNHNlVaPSeNlNxb/YChO6T
ClmZ/scuOmcdOejZmTEyc5wd6NnH1WM6A4rNYe+n/oKTlCNwosfgMpIytHpyWC7m
SORSIDNe6uU9mpWpuxoO+s8W4lKqps6aaZpLuFjQV8lXZteDFcy5JiYuuQKBgQCL
JfFB1zL/7DPZtYau6EEXgG6Q2oZcJAKVCEIVpd3k0vH7uWR6HRPDxhIjKptXWq+9
cmKV7+BLRySxBbzKgU5PhXb0wQyVuAqdPlV4rVdHPBgC8Oes44IKTkaXdilEoQAM
Eld5Jkgc0Vk1VIplpzW0S3xVwdPp3ZWywesmeOL0gQKBgHVjNnrAM6KnmtcC2ESt
JanCQtU3IHtBE9ReCGBEZ9lR0jCqvFbivv3CNhOr5O+S1V3kgIKQPB76+zmYl/0C
RYwauaLkzOEbi6UunmNT39Mqdcr7PyoaPp2Wk9PD0tM2/ehNu6MLh41MX1w1Fd46
O77pYRHWuGOfZTPdshxOfPgf
-----END PRIVATE KEY-----</key><cert>-----BEGIN CERTIFICATE-----
MIIDQzCCAiugAwIBAgIQMH0suH0s9/Tt3dojdz/UTDANBgkqhkiG9w0BAQsFADAU
MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwOTE2MTEwMTA3WhcNMjgwOTEzMTEw
MTA3WjAPMQ0wCwYDVQQDDARob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAugMxNlxJgcQZF9D2o1iUJCzhU2+qYMnQq86Vef/cEi9ZoDhoO19qQNPN
C4BgfBlaEo/ocjnZz3OUkD/ySrhKhw3hpjUd5nUTlzwScxIXhbaUD/tNW5kIE4vG
hBZZaH/jPDel3WSm1gjCKzmupBxSUzO0uZQu86mQkPz4NMovNRGVnc2pwmE/6Kwf
dEU7wTGuEfGP2DhXbSmYqWFTwKJDVbv1W+y78JqobvOEQanifQT7AGbhbki7vZdB
7KmWYrAM3q9OWjgJXeUoFOmLGoply7H3/sLjrLDNi16IWx3RNdWxDdvOkrzr3nA4
9/0H2A8y5VTKvTi190z3qXUNlj2dMwIDAQABo4GVMIGSMAkGA1UdEwQCMAAwHQYD
VR0OBBYEFA3QP+B02XYogxyjbFYYGBfhHqj5MEQGA1UdIwQ9MDuAFB8KyCaZfFiQ
aSxHMmMj00QD1kqioRikFjAUMRIwEAYDVQQDDAlsb2NhbGhvc3SCCQCGOlhuj5Vr
KzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQEL
BQADggEBALhv25L/dmTOszEwYn0pDtdkTwzIsCoD5tx1bQutlkvWHjG65md32Mfx
B60Sqo5f/Rc7ZwB4+4kKW/3gt0KaqLPbyWGLeH9yJNu/6VDMwvHJC07YxpzhLyHL
taq9VQDWS5FQUkC9oYsEZzxeaadcCQpNp1fdx4586fVYGJ2/dGDeqE52ZqMDNRSF
I1DurZlaKVsbkFdrm0UAlqBWZDDHlSImkd+uKwXwqQdnkxiPzCoW0dpMfw+7DvuV
Muq3759LtYmZbDFh8dUDRZ830WAwGzv9yhPpOg58G0dNyMTQJo59/p2Ea4NHHlBv
TqfXQM1at9leSpR5ZFkqDbIbYoxUuxk=
-----END CERTIFICATE-----</cert><ca>-----BEGIN CERTIFICATE-----
MIIDLzCCAhegAwIBAgIJAIY6WG6PlWsrMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xODA5MTYxMTAwMDhaFw0yODA5MTMxMTAwMDhaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAOvgs+KzBgg5Bz+dALH8uFsfBk2QklaeeK0O5H63qjXqujICy/MGxXAiqdo6
UqLTca9psvhhR0QeXEnZ2daBrw3XuGZ/nw9lmJZ/S/MOfJWTerYaj1ZttPjnlOFS
muWKouNam33mJF2vkGbYUoEdb3qqEoZTd3DBEnRAKLzE0v7evAbv7XW7kh+w7W+D
4RsalXLaUqquP/BYcOAWB733ZBIjpmEglA0u2ZtF9tXK/HT2d3dkxbrdRrF2WLwg
fcE53mb8/ayf3qu7nRj0NaqqYb8R9R5fcQnzMCi5ZZ/Dudbcc5/6NEu5G1PTCyTs
Z9o/FZO1KF3q7TR0FzYMkTMGF20CAwEAAaOBgzCBgDAdBgNVHQ4EFgQUHwrIJpl8
WJBpLEcyYyPTRAPWSqIwRAYDVR0jBD0wO4AUHwrIJpl8WJBpLEcyYyPTRAPWSqKh
GKQWMBQxEjAQBgNVBAMMCWxvY2FsaG9zdIIJAIY6WG6PlWsrMAwGA1UdEwQFMAMB
Af8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQC2APlsYTKtque4IF/C
nFRcY5J5Btj7SuyLUjkVp6HxengmwI5pDeubMvv9EeVZ0lAzobQ988P4vGkNGRde
QKGNkwjpsvNj+kyYCWhke8eYuxOXRZCNwp19kJjxhCN/i/adLhff0cXDfrMbX0Zv
W5WNgbrPBlk3TEn5XRHR/yp+UFNwL4e2ZovYFy3hBAuRfIdUF1wdZIdrjsHEMGBe
vHfKSebhvyPgAGAi91vU14M99xERqrvM09pMfMHF9xYhiiQkcZtVzaauZE0d3m+o
VK4HQZg2UjxfLUo5kU1Ou0E3cYHkDx3eEF/X/F5SyoWPTa2FfrK0j5/CE32vhlck
szzU
-----END CERTIFICATE-----</ca>key-direction 1<tls-auth>#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
e33646344fdba3b929ea6e07f1558bdb
080136f61bd0a0611bb1a952802d47b2
eb14d5aae9d25c4ab3614d495876e815
3ce7c2f9f67dda2183bec7928dfb792f
003cb36d5a57fd9e0df23c2fd4c7ae05
6a8b1ecb6c3697a78d3c3a67c16b98ad
167258c57aa3dd674c5a735d3df4b5ab
1610a676bd97beaadab10fd82f82c9e7
2b1efe35f96ff237b366e057d20b3a18
cdc095510df43bcfa638853efde91a67
93507615eceb77f2de619d614ca64901
2273a9074153b531dde8ac10f6d2cdd9
f73e8cba9548f2dc853b8c4178beed68
5d15b81da6121f7dc3c0f25c7008955d
5f9c6264d47bac43f6983ad8af8aba5b
8fa4ac4611109613e1a9d876620bdd83
-----END OpenVPN Static key V1-----</tls-auth>comp-lzo yes
route 172.17.0.0 255.255.0.0
  1. 把该文件添加到Tunnelblick,然后连接该网络。
    连接后的样子
  2. 测试
    docker container centos 网络

宿主机,完美搞定



作者:阿波罗程序猿

分类
实用技能

Python慢,为啥还有大公司用?

Instagram 的视频可以给我们哪些启示呢?

  • Python + Django 的组合完全可以负载用户数以 10 亿记的服务,如果你正准备开始一个项目,放心使用 Python 吧!
  • 完善的单元测试对于复杂项目是非常有必要的。如果没有那『成千上万的单元测试』。很难想象 Instagram 的迁移项目可以成功进行下去。
  • 开发者和同事也是你的产品用户,利用好他们。用他们为你的新特性发布前多一道测试。
  • 完全基于主分支的开发流程,可以给你更快的迭代速度。前提是拥有完善的单元测试和持续部署流程。
  • Python 3 是大势所趋,如果你正准备开始一个新项目,无需迟疑,拥抱 Python 3 吧!

作者:我爱学python
链接:https://www.jianshu.com/p/e18e01ad7ad9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

分类
PHP

composer配置使用代理

composer加速,使用http代理,执行:
export http_proxy=”http://127.0.0.1:1087″
export https_proxy=”http://127.0.0.1:1087″

取消代理:
unset http_proxy