一、目标架构图

 

适合分布式架构用的框架有哪些 分布式搭建和架构_java


其中,nacos,nginx,mysql,mq,redis,gateway,服务均为集群模式。使用docker模拟集群分布。

 

  • Spring Boot 2.3.9.RELEASE
  • Spring Cloud H版
  • Cloud Alibaba 2.2.5.RELEASE
  • RabbitMq 3.7.14
  • Redis 5.0
  • mysql 5.7.*
  • Docker 18.09.0
  • JWT 0.9.0
  • Lombok 1.18.6
  • Maven 5.1

二、环境部署

所有的环境都是在服务器上用docker模拟分布式集群的方式搭建的

1.docker安装

 curl -sSL https://get.daocloud.io/docker | sh
在服务器终端中运行该指令

2.数据库

2.1docker安装mysql数据库5.7

2.1.1搭建单体数据库

1. 拉取 MySQL 镜像
docker pull mysql:5.7

2. 设置 MySQL 配置文件
将全部的配置文件和关联的文件夹统一放到 /opt/docker/mysql 中
创建 MySQL 配置文件文件夹
mkdir -p /opt/docker/mysql/conf.d
增加并修改配置文件 config-file.cnf
vim /opt/docker/mysql/conf.d/config-file.cnf
输入如下内容:

1. [mysqld]
2. # 设置表名不区分大小写 linux下默认是区分的,windows下默认不区分
3. lower_case_table_names=1 
4. #server-id=1
5. datadir=/var/lib/mysql
6. #socket=/var/lib/mysql/mysqlx.sock
7. #symbolic-links=0
8. # sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 
9. [mysqld_safe]
10. log-error=/var/log/mysqld.log
11. pid-file=/var/run/mysqld/mysqld.pid

3. 启动
创建 MySQL 数据文件夹
mkdir -p /opt/docker/mysql/var/lib/mysql
启动,设置默认密码 root,TZ 设置容器的默认时区
docker run —name mysql \
—restart=always \
-p 3306:3306 \
-v /opt/docker/mysql/conf.d:/etc/mysql/conf.d \
-v /opt/docker/mysql/var/lib/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e TZ=Asia/Shanghai \
-d mysql:5.7

4. 修改密码


1. docker exec -it mysql bash
2. 进入 MySQL
3. mysql -uroot -p
4. 输入刚才我们设置的密码 root
5. 授权
6. mysql> GRANT ALL ON *.* TO 'root'@'%';
7. 刷新权限
8. mysql> flush privileges;
9. 更新加密规则
10. mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'root' PASSWORD EXPIRE NEVER;
11. 更新 root 密码
12. mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
13. 刷新权限
14. mysql> flush privileges;
15. 查看用户
16. mysql> use mysql;mysql> select user,host from user;
17. 退出容器
18. exit

有此条记录即成功

 
navicat链接数据库测试,成功。

2.1.2搭建主从mysql数据库,且读写分离

直接docker启动两个mysql容器,分别叫mysql-master和mysql-slave;从数据库映射到3302端口。
 
进入主数据库,安装vim,编辑my.cnf文件如下


1. [mysqld]
2. log-bin=/var/log/mysql/mysql-bin
3. server-id=1
4. gtid_mode=ON
5. enforce_gtid_consistency=1 # 强制执行GTID一致性。

如果省略server-id(或将其显式设置为默认值0),则主服务器拒绝来自从服务器的任何连接。如果省略server-id(或将其显式设置为默认值0),则主服务器拒绝来自从服务器的任何连接。

创建日志目录并赋予权限



  1. mkdir /var/log/mysql
  2. chown mysql.mysql /var/log/mysql

重启服务:systemctl restart mysqld
创建一个专门用于复制数据的用户

每个从服务器需要使用MySQL 主服务器上的用户名和密码连接到主站。计划使用用户 repl 可以从任何主机上连接到 master 上进行复制操作, 并且用户 repl 仅可以使用复制的权限。

创建repl用户
CREATE USER 'repl'@'%' IDENTIFIED BY '123456789'; 用户名:repl
主机:使用通配符%,允许任意远程主机登陆
密码:123456789

对repl用户进行授权
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; 在从库测试用户有效性
mysql -urepl -p'123456789' -h192.168.0.101

之后再配置从库的my.cnf
vim /etc/my.cnf

1. [mysqld]
2. server-id=2
3. gtid_mode=ON
4. enforce_gtid_consistency=1
5. 
6. # 可选项, 把连接到 master 的信息存到数据库中的表中
7. master-info-repository=TABLE
8. relay-log-info-repository=TABLE
9. read_only=1#可以设置只读

重启从数据库服务
systemctl restart mysqld
开启Master-Slave主从复制
进入Master库mysql客户端:输入show master status查看Master状态:


1. change master to master_host='172.18.0.3',
2. master_user='slave', 
3. master_password='123456', 
4. master_port=3306, 
5. master_log_file='master-bin.000001', 
6. master_log_pos=617, 
7. master_connect_retry=30;

在从数据库中执行start slave;开启主从
show slave status \G查看主从状态。

2.2执行nacos数据库的脚本

新建数据库nacos
之后运行sql脚本
脚本地址:nacos/nacos-mysql.sql at develop · alibaba/nacos · GitHub

3.nacos集群搭建

  • docker拉取nacos镜像:docker pull nacos/nacos-server:2.0.3
  • 依次启动三个nacos服务,分别以8846,8847,8848端口启动
    下面是docker compose脚本


1. version: "3"
2. 
3. services: 
4. nacos1:
5. container_name: nacos-server01
6. hostname: nacos-server01
7. image: nacos/nacos-server:2.0.3
8. environment: 
9. - MODE=cluster
10. - PREFER_HOST_MODE=hostname
11. - NACOS_SERVERS=nacos-server01:8848 nacos-server02:8848 nacos-server03:8848
12. - SPRING_DATASOURCE_PLATFORM=mysql
13. - MYSQL_SERVICE_HOST=你的mysql 地址
14. - MYSQL_SERVICE_PORT=3306
15. - MYSQL_SERVICE_USER=root
16. - MYSQL_SERVICE_PASSWORD=123456
17. - MYSQL_SERVICE_DB_NAME=nacos
18. - JVM_XMS=256m
19. - JVM_XMX=256m
20. - JVM_XMN=256m
21. volumes: 
22. - /home/nacos/cluster-logs/nacos-server01:/home/nacos/logs
23. - /home/nacos/init.d:/home/nacos/init.d
24. ports: 
25. - 8846:8848
26. - 9555:9555
27. - 9847:9849
28. restart: on-failure
29. 
30. nacos2:
31. container_name: nacos-server02
32. hostname: nacos-server02
33. image: nacos/nacos-server:2.0.3
34. environment: 
35. - MODE=cluster
36. - PREFER_HOST_MODE=hostname
37. - NACOS_SERVERS=nacos-server01:8848 nacos-server02:8848 nacos-server03:8848
38. - SPRING_DATASOURCE_PLATFORM=mysql
39. - MYSQL_SERVICE_HOST=你的mysql地址
40. - MYSQL_SERVICE_PORT=3306
41. - MYSQL_SERVICE_USER=root
42. - MYSQL_SERVICE_PASSWORD=123456
43. - MYSQL_SERVICE_DB_NAME=nacos
44. - JVM_XMS=256m
45. - JVM_XMX=256m
46. - JVM_XMN=256m
47. volumes: 
48. - /home/nacos/cluster-logs/nacos-server02:/home/nacos/logs
49. - /home/nacos/init.d:/home/nacos/init.d
50. ports: 
51. - 8847:8848
52. - 9848:9849
53. restart: on-failure
54. 
55. nacos3:
56. container_name: nacos-server03
57. hostname: nacos-server03
58. image: nacos/nacos-server:2.0.3
59. environment: 
60. - MODE=cluster
61. - PREFER_HOST_MODE=hostname
62. - NACOS_SERVERS=nacos-server01:8848 nacos-server02:8848 nacos-server03:8848
63. - SPRING_DATASOURCE_PLATFORM=mysql
64. - MYSQL_SERVICE_HOST=你的mysql地址
65. - MYSQL_SERVICE_PORT=3306
66. - MYSQL_SERVICE_USER=root
67. - MYSQL_SERVICE_PASSWORD=123456
68. - MYSQL_SERVICE_DB_NAME=nacos
69. - JVM_XMS=128m
70. - JVM_XMX=128m
71. - JVM_XMN=128m
72. volumes: 
73. - /home/nacos/cluster-logs/nacos-server03:/home/nacos/logs
74. - /home/nacos/init.d:/home/nacos/init.d
75. ports: 
76. - 8848:8848
77. - 9849:9849
78. restart: on-failure

8848端口是nacos后台管理界面的端口,9849端口是nacos集群之间互相通信需要的端口。 为了三个nacos的通信,必须映射到宿主机。

运行上面的docker文件,创建docker容器
docker-compose -f cluster-hostname.yaml up -d 访问三个nacos服务,查看是否配置成功。

还要使用nignx来做nacos集群的负载均衡
Nginx 的部署与其他服务部署略有不同,我们需要先启动一个 Nginx 容器实例,然后从容器事例中拷贝出 Nginx 的配置文件到指定目录,之后我们将复制出的配置文件与 Nginx 容器的数据卷进行挂载,从而达到可以在容器外部修改配置文件的目的。这么做是因为,如果直接挂载,那么容器实例中的的目录将会被外部的挂载所覆盖。这是官方 Nginx 镜像的一个小缺陷,注意一下就行了。



1. 运行 Nginx 容器
2. $ docker run --name temp-nginx -p 8080:8080 -d nginx:latest
3. 在宿主机创建 Nginx 的挂载目录
4. # 实际创建时以自己的机器环境为准
5. $ mkdir -p <宿主机挂载目录>
6. 拷贝容器中的配置到宿主机的挂载目录
7. $ docker cp <Nginx容器ID>:/etc/nginx/ <宿主机挂载目录>
8. 停止并删除容器实例
9. $ docker stop <Nginx容器ID>
10. $ docker rm <Nginx容器ID>

修改 Nginx 配置文件

位置:<宿主机挂载目录>/conf.d/default.conf

配置文件主要修改两个部分,一个是新增 upstream,通过负载均衡来配置 Nacos 服务的节点;第二个是修改 server 下的 location,在其中添加反向代理配置。另外,如果你的 Nginx 没有配置过 server_name,那么还需要修改 server 下的 server_name 配置。

完整配置文件如下:


1. # 添加负载均衡配置
2. upstream nacos {
3. server 服务器地址:8846 weight=2 max_fails=2 fail_timeout=10s;
4. server 服务器地址:8847 weight=2 max_fails=2 fail_timeout=10s;
5. server 服务器地址:8848 weight=1 max_fails=2 fail_timeout=10s;
6. }
7. 
8. server {
9. listen 80;
10. listen [::]:80;
11. # 修改为宿主机的 IP地址
12. server_name 服务器地址;
13. 
14. #access_log /var/log/nginx/host.access.log main;
15. 
16. location / {
17. # 添加代理配置
18. proxy_pass http://nacos;
19. proxy_set_header Host $host;
20. proxy_set_header X-Real-IP $remote_addr;
21. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22. proxy_set_header REMOTE-HOST $remote_addr;
23. add_header X-Cache $upstream_cache_status;
24. add_header Cache-Control no-cache;
25. 
26. #root /usr/share/nginx/html;
27. #index index.html index.htm;
28. }
29. 
30. #error_page 404 /404.html;
31. 
32. # redirect server error pages to the static page /50x.html
33. #
34. error_page 500 502 503 504 /50x.html;
35. location = /50x.html {
36. root /usr/share/nginx/html;
37. }
38. 
39. # proxy the PHP scripts to Apache listening on 127.0.0.1:80
40. #
41. #location ~ \.php$ {
42. # proxy_pass http://127.0.0.1;
43. #}
44. 
45. # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
46. #
47. #location ~ \.php$ {
48. # root html;
49. # fastcgi_pass 127.0.0.1:9000;
50. # fastcgi_index index.php;
51. # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
52. # include fastcgi_params;
53. #}
54. 
55. # deny access to .htaccess files, if Apache's document root
56. # concurs with nginx's one
57. #
58. #location ~ /\.ht {
59. # deny all;
60. #}
61. }

编排 Nginx启动文件


1. version: "3"
2. 
3. services: 
4. nacos-nginx: 
5. container_name: nacos-nginx
6. image: nginx:latest
7. volumes: 
8. - /home/naocs-nginx:/etc/nginx
9. ##注意这里容器挂在的位置
10. ports: 
11. - 18848:80
12. restart: on-failure

访问http://服务器ip:18848/nacos/ 查看效果

4.构造生产者消费者服务测试

4.1父工程创建

在本机上下载安装idea2021(具体方法baidu)
新建父工程cloud-demo
打开pom文件,在properties中控制整个项目的版本


1. <properties>
2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
4. <java.version>1.8</java.version>
5. <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
6. <mysql.version>5.1.47</mysql.version>
7. <mybatis.version>2.1.1</mybatis.version>
8. </properties>

依赖管理


1. <dependencyManagement>
2. <dependencies>
3. <!-- springCloud -->
4. <dependency>
5. <groupId>org.springframework.cloud</groupId>
6. <artifactId>spring-cloud-dependencies</artifactId>
7. <version>${spring-cloud.version}</version>
8. <type>pom</type>
9. <scope>import</scope>
10. </dependency>
11. <!--nacos的管理依赖-->
12. <dependency>
13. <groupId>com.alibaba.cloud</groupId>
14. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
15. <version>2.2.5.RELEASE</version>
16. <type>pom</type>
17. <scope>import</scope>
18. </dependency>
19. <!-- mysql驱动 -->
20. <dependency>
21. <groupId>mysql</groupId>
22. <artifactId>mysql-connector-java</artifactId>
23. <version>${mysql.version}</version>
24. </dependency>
25. <!--mybatis-->
26. <dependency>
27. <groupId>org.mybatis.spring.boot</groupId>
28. <artifactId>mybatis-spring-boot-starter</artifactId>
29. <version>${mybatis.version}</version>
30. </dependency>
31. </dependencies>
32. </dependencyManagement>
33. <dependencies>
34. <dependency>
35. <groupId>org.projectlombok</groupId>
36. <artifactId>lombok</artifactId>
37. </dependency>
38. </dependencies>

4.2生产者服务集群配置

新建module:service-provider1
修改pom文件,在resources中新建application.yml文件。新建启动类。写一个简单的接口,如下。


1. server:
2. port: 9001
3. spring:
4. datasource:
5. url: jdbc:mysql://10.245.153.168:3306/cloud_order?useSSL=false
6. username: root
7. password: 123456
8. driver-class-name: com.mysql.jdbc.Driver
9. application:
10. name: service-provider
11. cloud:
12. nacos:
13. server-addr: 10.245.153.168:18848 # nacos服务地址
14. 
15. @SpringBootApplication
16. @EnableDiscoveryClient
17. public class TestServiceMain {
18. public static void main(String[] args) {
19. SpringApplication.run(TestServiceMain.class,args);
20. }
21. }
22. 
23. @RestController
24. public class TestController {
25. 
26. @GetMapping(value = "/test/gethello")
27. public String getHello(){
28. return "hello,service provider 1";
29. }
30. }

再仿照service-provider1创造项目service-provider2。记得修改端口为9002

4.3消费者服务配置

很简单就是用openfeign同步调用生产者的接口。不要忘记也将消费者注册进nacos。

4.4上传到服务器

使用的docker来运行springboot程序
maven clear;
maven install;
将jar包命名为service-provider1,service-provider2,上传到服务器,用指令sudo rz。
之后拉取一个有java运行环境的镜像。


1. docker run -d --name service-provider1 -p 9001:9001 -p 19001:19001 \
2. -v /mydata/service-provider1.jar:/usr/app.jar java:8 \
3. java -jar -Xms256m -Xmx256m /usr/app.jar
4. 
5. docker run -d --name service-provider2 -p 9002:9002 -p 19002:19002 \
6. -v /mydata/service-provider2.jar:/usr/app.jar java:8 \
7. java -jar -Xms256m -Xmx256m /usr/app.jar

5.GateWay网关集群

由于是测试,就先搭建一个集群,里面运行两个网关服务

5.1新建项目

module名:gateway
选择本机的jdk1.8,创建一个空白的maven项目。设置项目坐标gav,其中groupid为父工程groupid
 
在pom文件中导入依赖



1. <dependencies>
2. <!--nacos服务注册发现依赖-->
3. <dependency>
4. <groupId>com.alibaba.cloud</groupId>
5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
6. </dependency>
7. <!--网关gateway依赖-->
8. <dependency>
9. <groupId>org.springframework.cloud</groupId>
10. <artifactId>spring-cloud-starter-gateway</artifactId>
11. </dependency>
12. </dependencies>

编写application.yml文件,启动的端口为10010


1. server:
2. port: 10010
3. logging:
4. level:
5. cn.itcast: debug
6. pattern:
7. dateformat: MM-dd HH:mm:ss:SSS
8. spring:
9. application:
10. name: gateway
11. cloud:
12. nacos:
13. server-addr: 10.245.153.168:18848 # nacos地址
14. gateway:
15. routes:
16. default-filters:
17. - AddRequestHeader=Truth,Itcast is freaking awesome!
18. - AddRequestHeader=origin,gateway

创建主启动类
 
写一个权限过滤器组件


1. @Component
2. public class AuthorizeFilter implements GlobalFilter, Ordered {
3. @Override
4. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
5. // 1.获取请求参数
6. ServerHttpRequest request = exchange.getRequest();
7. MultiValueMap<String, String> params = request.getQueryParams();
8. // 2.获取参数中的 authorization 参数
9. String auth = params.getFirst("authorization");
10. // 3.判断参数值是否等于 admin
11. if ("admin".equals(auth)) {
12. // 4.是,放行
13. return chain.filter(exchange);
14. }
15. // 5.否,拦截
16. // 5.1.设置状态码
17. exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
18. // 5.2.拦截请求
19. return exchange.getResponse().setComplete();
20. }
21. 
22. @Override
23. public int getOrder() {
24. return -1;
25. }
26. }

在创建一个几乎完全相同的子项目gateway2,但是启动端口为10011,服务名不要改动

在两个网关服务中的yml文件里新增路由配置:

5.2启动测试

启动编写的网关集群,查看nacos服务列表
 
通过网关访问下我们的服务提供者


可见服务调用默认为轮询策略

5.3上传jar包到服务器测试

将gateway与gateway2进行一些maven install来生成jar包,然后用rz指令上传的服务器
 
我们都知道jar是运行在java环境中,所以只要容器中有java环境就可以运行jar包,镜像方式运行的原理也是如此。因此我们基于java镜像就可以实现jar包运行。
拉取java镜像
docker pull java:8 之后使用docker运行jar包


1. docker run -d --name gateway1 -p 10010:10010 \
2. -v /mydata/gateway1.jar:/usr/app.jar java:8 \
3. java -jar -Xms256m -Xmx256m /usr/app.jar
4. 
5. docker run -d --name gateway2 -p 10011:10011 \
6. -v /mydata/gateway2.jar:/usr/app.jar java:8 \
7. java -jar -Xms256m -Xmx256m /usr/app.jar

运行完后查看下效果
 

可见,gateway网关运行在的docker上时,显示的ip为docker中的虚拟ip,可以在application.yml文件中添加属性:
spring.cloud.nacos.discovery.ip:外网ip来修改。

5.4用nginx做负载均衡

类比做nacos集群的负载均衡,方法是一样的。
需要注意的是nginx配置文件要挂在到gateway-nginx文件夹下,防止产生冲突。

6.sentinel服务降级,流量控制

6.1安装

拉取镜像
docker pull bladex/sentinel-dashboard
运行docker file

1. docker run -dit \
2. --name sentinel-nacos \
3. -p 18858:8858 \
4. --restart=always \
5. -e NACOS_SERVER_ADDR=10.245.153.168:18848 \
6. -e NACOS_USERNAME=nacos \
7. -e NACOS_PASSWORD=nacos \
8. -e NACOS_NAMESPACE=public \
9. -e NACOS_GROUP_ID=SENTINEL_GROUP \
10. bladex/sentinel-dashboard

访问http://你的ip:18858/ , 输入用户名密码都是sentinel
看看后台页面
 
将之前的service-provider1项目进行sentinel流控,修改yml文件


注意,由于我们的sentinel运行在服务器里的docker中,故要添加spring.cloud.sentinel.transport.client-ip属性,该属性为你的电脑的公网ip,也就是service-provider1项目运行在的主机的ip

运行起项目,访问几次gethello接口


添加一个流控规则,在看看访问接口的反应

6.2持久化规则

当我们一旦重启sentinel应用,sentinel写的规则消失,生产环境需要将配置规则进行持久化。
在我们之前建立的service-provider1项目中的pom文件里 ,添加依赖
 
yml文件中,添加


在nacos配置中心新增配置


注意属性的一一对应

运行下sentinel,访问刚才添加配置的接口,规则成功


再重启sentinel,发现规则还在。

只能通过Nacos修改规则配置,通过Dashboard修改规则配置不自动刷新Nacos配置,重启Dashboard后只保留Nacos中数据!!!

6.4到目前为止的项目架构

7.redis

7.1redis集群方式

1.主从复制
2.哨兵集群
3.redis-cluster集群
我们本次使用redis主从复制的形式搭建redis集群
什么是主从复制?
答:就是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。给大家个图来理解下:
 
主从复制有什么作用?

答:①:数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

②:故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。换句话说就是主服务器挂掉了,从服务器顶上去。

③:负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

7.2开始搭建

目标:一主,一从,测试用
先拉取镜像
docker run —name redis-master -d -p 16379:6379 redis:latest
docker run —name redis-master -d -p 16380:6379 redis:latest
使用指令docker exec 和redis-cli
分别进入两个redis数据库,查看ip信息输入info


1. 172.17.0.4
2. 172.17.0.5

我们把172.17.0.5作为随从数据库,在其reids终端中输入
SLAVEOF 主ip 主端口(均是前面的ip与端口)
之后再用info指令查看是否修改成功。
 
 

7.3测试

我们在主节点设置一个属性 set name : why
我们在从随从节点获取 get name : why
成功
 

7.4进一步扩展

可以在部署哨兵模式或者分片集群模式。

8.rabbitMQ消息队列

8.1RabbitMQ安装(单机版)

在线拉取镜像




1. docker pull rabbitmq:3-management

执行下面的命令来运行MQ容器



1. docker run \
2. -e RABBITMQ_DEFAULT_USER=admin \
3. -e RABBITMQ_DEFAULT_PASS=123456 \
4. --name mq \
5. --hostname mq1 \
6. -p 15672:15672 \
7. -p 5672:5672 \
8. -d \
9. rabbitmq:3-management

启动成功后访问地址:http://服务器ip:15672 按照上面的用户名密码登录


8.2RabbitMQ安装(集群版)

在RabbitMQ的官方文档中,讲述了两种集群的配置方式:

  • 普通模式:普通模式集群不进行数据同步,每个MQ都有自己的队列、数据信息(其它元数据信息如交换机等会同步)。例如我们有2个MQ:mq1,和mq2,如果你的消息在mq1,而你连接到了mq2,那么mq2会去mq1拉取消息,然后返回给你。如果mq1宕机,消息就会丢失。
  • 镜像模式:与普通模式不同,队列会在各个mq的镜像节点之间同步,因此你连接到任何一个镜像节点,均可获取到消息。而且如果一个节点宕机,并不会导致数据丢失。不过,这种方式增加了数据同步的带宽消耗。

我们先来看普通模式集群,我们的计划部署两节点的mq集群:
| mq1 | 15672 —-> 15672 | 5672 —-> 5672 |
| mq2 | 15673 —-> 15672 | 5673 —-> 5672 |
集群中的节点标示默认都是:rabbit@[hostname],因此以上三个节点的名称分别为:

rabbit@mq1

rabbit@mq2

获取cookie值
RabbitMQ底层依赖于Erlang,而Erlang虚拟机就是一个面向分布式的语言,默认就支持集群模式。集群模式中的每个RabbitMQ 节点使用 cookie 来确定它们是否被允许相互通信。
要使两个节点能够通信,它们必须具有相同的共享秘密,称为Erlang cookie。cookie 只是一串最多 255 个字符的字母数字字符。
每个集群节点必须具有相同的 cookie。实例之间也需要它来相互通信。
我们先在之前启动的mq容器中获取一个cookie值,作为集群的cookie。执行下面的命令:

1. docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie

可以看到cookie值如下:
 
HGANYJSVWWMCPJXVSYHG
接下来,停止并删除当前的mq容器,我们重新搭建集群。
在/tmp目录新建一个配置文件 rabbitmq.conf:

1. cd /tmp
2. # 创建文件
3. touch rabbitmq.conf

文件内容如下:


1. loopback_users.guest = false
2. listeners.tcp.default = 5672
3. cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
4. cluster_formation.classic_config.nodes.1 = rabbit@mq1
5. cluster_formation.classic_config.nodes.2 = rabbit@mq2

再创建一个文件,记录cookie


1. # 创建cookie文件
2. touch .erlang.cookie
3. # 写入cookie
4. echo "HGANYJSVWWMCPJXVSYHG" > .erlang.cookie
5. # 修改cookie文件的权限
6. chmod 600 .erlang.cookie

准备目录,mq1、mq2:


1. cd /tmp
2. # 创建目录
3. mkdir mq1 mq2

然后拷贝rabbitmq.conf、cookie文件到mq1、mq2:


1. # 进入/tmp
2. cd /tmp
3. # 拷贝
4. cp rabbitmq.conf mq1
5. cp rabbitmq.conf mq2
6. cp .erlang.cookie mq1
7. cp .erlang.cookie mq2

创建一个网络:

1. docker network create mq-net

运行命令


1. docker run -d --net mq-net \
2. -v ${PWD}/mq1/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
3. -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
4. -e RABBITMQ_DEFAULT_USER=whycast \
5. -e RABBITMQ_DEFAULT_PASS=123456 \
6. --name mq1 \
7. --hostname mq1 \
8. -p 5672:5672 \
9. -p 15672:15672 \
10. rabbitmq:3-management
 
1. docker run -d --net mq-net \
2. -v ${PWD}/mq2/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
3. -v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
4. -e RABBITMQ_DEFAULT_USER=whycast \
5. -e RABBITMQ_DEFAULT_PASS=123456 \
6. --name mq2 \
7. --hostname mq2 \
8. -p 5673:5672 \
9. -p 15673:15672 \
10. rabbitmq:3-management

测试
在mq1这个节点上添加一个队列:

如图,在mq2控制台也都能看到:

到此为止,普通版集群已经部署成功,但是普通模式集群不进行数据同步,每个MQ都有自己的队列、数据信息(其它元数据信息如交换机等会同步)。例如我们有2个MQ:mq1,和mq2,如果你的消息在mq1,而你连接到了mq2,那么mq2会去mq1拉取消息,然后返回给你。如果mq1宕机,消息就会丢失。

8.3仲裁队列

从RabbitMQ 3.8版本开始,引入了新的仲裁队列,他具备与镜像队里类似的功能,但使用更加方便。
在任意控制台添加一个队列,一定要选择队列类型为Quorum类型。
 
因为仲裁队列默认的镜像数为5。如果你的集群有7个节点,那么镜像数肯定是5;而我们集群只有2个节点,因此镜像数量就是2.
此时停掉mq1,mq2中还存在该队列。

9.Oauth2.0鉴权

待续

10.nginx 通过 keepalived 实现高可用

10.1体系架构

在Keepalived + Nginx高可用负载均衡架构中,keepalived负责实现High-availability (HA) 功能控制前端机VIP(虚拟网络地址),当有设备发生故障时,热备服务器可以瞬间将VIP自动切换过来,实际运行中体验只有2秒钟切换时间,DNS服务可以负责前端VIP的负载均衡。
nginx负责控制后端web服务器的负载均衡,将客户端的请求按照一定的算法转发给后端处理,而Real Server将响应直接返回给客户端。

10.2简单原理

nginx-master、nginx-slave两台服务器均通过keepalived软件把你的网卡绑上一个虚拟IP(VIP)地址192.168.200.199,此VIP当前由谁承载着,服务就绑定在谁的网卡上,当nginx-master发生故障时,nginx-slave会通过keepalived配置文件中设置的心跳时间advert_int检查,无法获取nginx-master正常状态的话,keepalived首先会执行脚本来重启nginx-master,重启仍然失败,会切换到备机运行。nginx-slave会瞬间绑定VIP来接替nginx_master的工作,当nginx-master恢复后keepalived会通过priority参数判断优先权将虚拟VIP地址192.168.200.199重新绑定给nginx-master的网卡。

10.3使用此方案的优越性

1.实现了可弹性化的架构,在压力增大的时候可以临时添加web服务器添加到这个架构里面去;
2.upstream具有负载均衡能力,可以自动判断后端的机器,并且自动踢出不能正常提供服务的机器;
3.相对于lvs而言,正则分发和重定向更为灵活。而Keepalvied可保证单个nginx负载均衡器的有效性,避免单点故障;
4.用nginx做负载均衡,无需对后端的机器做任何改动。
5.nginx部署在docker容器里,即大量地节约开发、测试、部署的时间,又可以在出现故障时通过镜像快速恢复业务。

10.4部署

1.准备两台服务器,都安装好docker

2.docker pull nginx #这里获取的是最新的 nginx

3.创建文件夹用于使用自己定义的配置文件以及日志文件


1. mkdir -p /mydata/docker/nginx # 配置文件
2. #docker 起一个nginx来获取配置文件
3. docker run --name temp-nginx -p 80:80 -d nginx:latest
4. #拷贝配置文件
5. $ docker cp temp-nginx:/etc/nginx/ /mydata/docker/
6. 
7. 之后再删除刚才的容器

4.在 /mydata/docker/nginx 目录下修改配置文件: vi nginx.conf,再修改conf.d下的default.conf文件

1. user root;
2. worker_processes auto;
3. 
4. error_log /var/log/nginx/error.log notice;
5. pid /var/run/nginx.pid;
6. 
7. 
8. events {
9. worker_connections 1024;
10. }
11. 
12. 
13. http {
14. include /etc/nginx/mime.types;
15. default_type application/octet-stream;
16. 
17. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
18. '$status $body_bytes_sent "$http_referer" '
19. '"$http_user_agent" "$http_x_forwarded_for"';
20. 
21. access_log /var/log/nginx/access.log main;
22. 
23. sendfile on;
24. #tcp_nopush on;
25. 
26. keepalive_timeout 65;
27. 
28. #gzip on;
29. 
30. include /etc/nginx/conf.d/*.conf;
31. }

1. upstream gateway {
2. server 10.245.153.169:10010 weight=1 max_fails=2 fail_timeout=5s;
3. server 10.245.153.169:10011 weight=1 max_fails=2 fail_timeout=5s;
4. }
5. 
6. server {
7. listen 80;
8. listen [::]:80;
9. # 修改为宿主机的 IP地址
10. server_name 10.245.153.169;
11. 
12. #access_log /var/log/nginx/host.access.log main;
13. 
14. location / {
15. # 添加代理配置
16. proxy_pass http://gateway;
17. proxy_set_header Host $host;
18. proxy_set_header X-Real-IP $remote_addr;
19. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
20. proxy_set_header REMOTE-HOST $remote_addr;
21. add_header X-Cache $upstream_cache_status;
22. add_header Cache-Control no-cache;
23. 
24. #root /usr/share/nginx/html;
25. #index index.html index.htm;
26. }
27. 
28. #error_page 404 /404.html;
29. 
30. # redirect server error pages to the static page /50x.html
31. #
32. error_page 500 502 503 504 /50x.html;
33. location = /50x.html {docker
34. root /usr/share/nginx/html;
35. }
36. 
37. # proxy the PHP scripts to Apache listening on 127.0.0.1:80
38. #
39. #location ~ \.php$ {
40. # proxy_pass http://127.0.0.1;
41. #}
42. 
43. # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
44. #
45. #location ~ \.php$ {
46. # root html;
47. # fastcgi_pass 127.0.0.1:9000;
48. # fastcgi_index index.php;
49. # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
50. # include fastcgi_params;
51. #}
52. 
53. # deny access to .htaccess files, if Apache's document root
54. # concurs with nginx's one
55. #
56. #location ~ /\.ht {
57. # deny all;
58. #}
59. }

5.在 /mydata/docker/nginx/html 目录下创建首页(仅仅是后面用来判断请求落在了哪个机器上,主机和备机能区分开就行): vi index.html
Master machine

6.启动nginx


1. docker run --name nginx-master --privileged=true --restart=always -p 80:80 \
2. -v /mydata/docker/nginx:/etc/nginx \
3. -v /mydata/docker/nginx/html:/usr/share/nginx/html \
4. -d nginx:latest
5. 
6. docker run --name nginx-slave --privileged=true --restart=always -p 80:80 \
7. -v /mydata/docker/nginx:/etc/nginx \
8. -v /mydata/docker/nginx/html:/usr/share/nginx/html \
9. -d nginx:latest

由于都是启动的80端口,故可以直接用ip+接口名来访问
 
可见,成功代理。
下面就要引入keepalived进行高可用
7.安装keepalived插件
这里不用docker,直接在服务器终端里输入以下指令
yum install keepalived -y 进入/etc/keepalived/下 vi keepalived.conf 下面是运行着nginx-master容器的服务器的配置

1. global_defs {
2. notification_email {
3. acassen@firewall.loc
4. failover@firewall.loc
5. sysadmin@firewall.loc
6. }
7. 
8. notification_email_from Alexandre.Cassen@firewall.loc #定义利用什么邮箱发送邮件
9. smtp_server smtp.163.com #定义邮件服务器信息
10. smtp_connect_timeout 30 #定义邮件发送超时时间
11. router_id 10.245.153.168 #(重点参数)局域网keppalived主机身份标识信息(每台唯一)
12. script_user root #添加运行健康检查脚本的用户
13. enable_script_security #添加运行健康检查脚本的组
14. }
15. 
16. vrrp_script chk_http_port {
17. script "/usr/local/src/nginx_check.sh" #表示将一个脚本信息赋值给变量check_web,down机后就运行这个文件
18. interval 5 #检测脚本执行的间隔
19. weight -20 #监测失败,则相应的vrrp_instance的优先级会减少20个点,这样主服务器会切换
20. }
21. 
22. vrrp_instance VI_1 {
23. state MASTER #keepalived角色描述信息,备份服务器上将 MASTER 改为 BACKUP
24. interface ens192 #将虚拟ip用于那块网卡
25. virtual_router_id 51 #主、备机的 virtual_router_id 必须相同
26. priority 100 #主、备机取不同的优先级,主机值较大,备份机值较小取90
27. advert_int 1 #主服务器组播包发送间隔时间
28. 
29. authentication { # 主备主机之间的认证表示信息
30. auth_type PASS #采用明文认证机制
31. auth_pass 1111 #编写明文密码
32. }
33. virtual_ipaddress {
34. 服务器所在网段未分配的ip地址 #设置虚拟ip地址信息,此参数备节点设置和主节点相同
35. }
36. track_script {
37. chk_http_port #调用执行脚本
38. }
39. }

添加检查nginx状态的脚本vim /usr/local/src/nginx_check.sh


1. #!/bin/bash
2. 
3. # 传入容器名称
4. containerName=nginx-slave
5. currTime=`date +"%Y-%m-%d %H:%M:%S"`
6. 
7. # 查看进程是否存在
8. exist=`docker inspect --format '{{.State.Running}}' ${containerName}`
9. 
10. if [ "${exist}" != "true" ]; then
11. pkill keepalived #杀死所有keepalived服务进程
12. 
13. # 记录
14. echo "${currTime} docker容器宕机,容器名称:${containerName}" >> /mnt/xvde1/ms_ctynyd/scripts/wbwf_monitor.log
15. 
16. fi

一定要给这个脚本文件可执行权限(看到变成可执行的颜色),执行命令:chmod u+x /usr/local/src/nginx_check.sh

之后,在两台服务器上分别执行systemctl restart keepalived.service命令。开启keepalived插件。
访问我们之前设置的虚拟ip发现可以访问成功

之后shutdown掉一个nginx,看看keepalived插件能否进行vip的迁移。还能够访问到我们的服务,成功。

11.jmeter进行压力测试

11.1测试最外层nginx转发能力

压力配置均以上图所示进行。

访问虚拟ip,两个nginx+keepalived,来进行转发请求。
 
5000个线程的请求有14个超时未连接


直接通过网关访问请求
 
请求异常率大幅降低,但是响应时间最大值非常高

从上面两组测试对比可以知道,处理请求并发量受到nginx集群转发速度的影响。

上网查阅资料找到,可以在nginx.conf中修改nginx处理并发请求的能力

 

图中worker_connection值本来是1024,修改成为8192,再次测试查看结果。


异常率降低到零。说明1s内5000线程量的请求完全可以接受。

提高到两万


提高到三万



这是异常率变高,且请求响应时间大幅增加。

11.2测试mq集群抗压能力