分类
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 config遇到的问题

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

附上http状态码说明:

5xx(服务器错误)

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

代码 说明

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

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

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

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

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

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

分类
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

分类
PHP

laravel 使用workerman提供http服务

  • 背景

公司项目使用的是laravel框架,开发完后压测一直不过,期间开启过opcache扩展加速(不属于本篇内容,不在此赘述),后决定采用workerman或swoole加速laravel应用,本篇以workerman为例。

首先大家应该知道laravel每次收到请求都会把框架核心代码加载一遍,使用workerman提升性能的关键是把框架核心代码缓存起来,这样就不需要每次重新加载了,以此来提升性能(swoole同理)

  • composer安装workerman
#更改composer源
composer config -g repo.packagist composer https://packagist.phpcomposer.com
#下载workerman包
composer require workerman/workerman
  • 创建启动命令
  1. 生成 WorkermanCommand 文件
php artisan make:command WorkermanCommand

    执行以上命令行会在 app/Console/Commands/ 目录下生成 WorkermanCommand.php 文件,对文件做如下修改。

<?php

namespace App\Console\Commands;

use Workerman\Worker;
use Illuminate\Console\Command;

class WorkermanCommand extends Command
{
    private $server;
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'wk {action}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Start a Workerman server.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        global $argv;
        $arg = $this->argument('action');
        $argv[1] = $argv[2];
        $argv[2] = isset($argv[3]) ? "-{$argv[3]}" : '';
        switch ($arg) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                break;
            case 'restart':
                break;
            case 'reload':
                break;
            case 'status':
                break;
            case 'connections':
                break;
        }
    }

    private function start()
    {
        // 创建一个Worker监听20002端口,不使用任何应用层协议
        $this->server = new Worker("http://0.0.0.0:1234");
        // 启动4个进程对外提供服务
        $this->server->count = 4;
        $handler = \App::make('handlers\WorkermanHandler');
        // 连接时回调
        $this->server->onConnect = [$handler, 'onConnect'];
        // 收到客户端信息时回调
        $this->server->onMessage = [$handler, 'onMessage'];
        // 进程启动后的回调
        $this->server->onWorkerStart = [$handler, 'onWorkerStart'];
        // 断开时触发的回调
        $this->server->onClose = [$handler, 'onClose'];
        // 运行worker
        Worker::runAll();
    }
}

在这个自定义命令还引用了其他的类文件,如

$handler = \App::make('handlers\WorkermanHandler');

所以,需要创建一个 WorkermanHandler.php 的文件来处理对应的操作

2、创建 WorkermanHandler.php

创建 app/handlers/WorkermanHandler.php

<?php

namespace handlers;


class WorkermanHandler
{
    public function onWorkerStart($worker)
    {
        //加载index文件的内容
        require __DIR__ . '/../../vendor/autoload.php';
        require_once __DIR__ . '/../../bootstrap/app.php';
    }

    // 处理客户端连接
    public function onConnect($connection)
    {
        echo "new connection from ip " . $connection->getRemoteIp() . "\n";
    }

    // 处理客户端消息
    public function onMessage($connection, $data)
    {
        // 向客户端发送hello $data
        //server信息
        if (isset($data->server)) {
            foreach ($data->server as $k => $v) {
                $_SERVER[strtoupper($k)] = $v;
            }
        }

        //header头信息
        if (isset($data->header)) {
            foreach ($data->header as $k => $v) {
                $_SERVER[strtoupper($k)] = $v;
            }
        }

        //get请求
        if (isset($data->get)) {
            foreach ($data->get as $k => $v) {
                $_GET[$k] = $v;
            }
        }

        //post请求
        if (isset($data->post)) {
            foreach ($data->post as $k => $v) {
                $_POST[$k] = $v;
            }
        }

        //文件请求
        if (isset($data->files)) {
            foreach ($data->files as $k => $v) {
                $_FILES[$k] = $v;
            }
        }

        //cookies请求
        if (isset($data->cookie)) {
            foreach ($data->cookie as $k => $v) {
                $_COOKIE[$k] = $v;
            }
        }

        ob_start();//启用缓存区

        //加载laravel请求核心模块
        $kernel = app()->make(\Illuminate\Contracts\Http\Kernel::class);
        $laravelResponse = $kernel->handle(
            $request = \Illuminate\Http\Request::capture()
        );
        $laravelResponse->send();
        $kernel->terminate($request, $laravelResponse);

        $res = ob_get_contents();//获取缓存区的内容
        ob_end_clean();//清除缓存区

        //输出缓存区域的内容
        $connection->send($res);
    }

    // 处理客户端断开
    public function onClose($connection)
    {
        echo "connection closed from ip {$connection->getRemoteIp()}\n";
    }
}

3.使用composer自动加载

        "classmap": [
            "app/Handlers"
        ],

执行

composer dump-autoload -o

至此基本配置写完了,下边试试启动一下

php artisan wk start
----------------------------------------- WORKERMAN -----------------------------------------
Workerman version:3.5.19          PHP version:7.2.11
------------------------------------------ WORKERS ------------------------------------------
proto   user            worker          listen                 processes    status
tcp     xuce            none            http://0.0.0.0:1234    4             [OK]
---------------------------------------------------------------------------------------------

说明启动成功了!!!