在Nginx+PHP/Java+MySQL的Web集群中集成Redis缓存,是提升系统并发能力、降低数据库压力的核心手段。
一、集成背景与架构设计
1.1 集成目标
- 为Web集群提供统一的缓存层,避免单节点Redis成为瓶颈;
- 实现缓存与数据库的数据一致性,解决分布式场景下的缓存穿透/击穿问题;
- 适配Nginx反向代理的负载均衡,保证多应用节点缓存访问一致。
1.2 集群架构(基于前文Web集群扩展)
markdown
客户端 → Nginx反向代理(192.168.1.10)
→ 2台PHP/Java应用服务器(192.168.1.20/21)
→ Redis集群(192.168.1.40:7000/7001/7002 主从+3主3从)
→ MySQL数据库(192.168.1.30)
核心设计:
- Redis采用Cluster集群(3主3从),保证高可用和分片存储;
- 所有应用节点连接同一Redis集群,避免缓存数据孤岛;
- 缓存逻辑统一封装,确保多应用节点缓存策略一致。
二、前置准备
- Web集群已部署完成(Nginx+PHP/Java+MySQL);
- 控制节点已安装Ansible,目标主机SSH免密登录配置完成;
- Redis集群服务器(推荐3台及以上,本文用1台模拟3主3从,生产环境建议独立节点)。
三、第一步:Redis集群部署(Ansible自动化配置)
3.1 目录结构(整合到原Web集群Playbook)
python
web_cluster_deploy/
├── inventory.ini # 新增redis_servers分组
├── site.yml # 新增Redis角色执行步骤
├── roles/
│ ├── redis/ # 新增Redis集群角色
│ │ ├── tasks/
│ │ │ └── main.yml # Redis集群部署任务
│ │ ├── vars/
│ │ │ └── main.yml # Redis集群变量
│ │ ├── templates/
│ │ │ └── redis.conf.j2 # Redis节点配置模板
│ │ └── handlers/
│ │ └── main.yml # Redis重启触发器
│ ├── common/ # 原公共角色
│ ├── mysql/ # 原MySQL角色
│ ├── php/ # 原PHP角色(后续改造)
│ └── nginx/ # 原Nginx角色
3.2 Inventory配置(inventory.ini)
新增Redis集群节点分组:
ini
# 原有分组
[common]
192.168.1.10 # Nginx
192.168.1.20 # PHP1
192.168.1.21 # PHP2
192.168.1.30 # MySQL
192.168.1.40 # Redis集群节点(新增)
[nginx_servers]
192.168.1.10
[php_servers]
192.168.1.20
192.168.1.21
[mysql_servers]
192.168.1.30
# 新增Redis集群分组
[redis_servers]
192.168.1.40
# 全局变量
[all:vars]
ansible_ssh_user=root
ansible_ssh_port=22
ansible_ssh_private_key_file=/root/.ssh/id_rsa
3.3 Redis变量配置(roles/redis/vars/main.yml)
yaml
# Redis基础配置
redis_version: "6.2.14"
redis_base_dir: "/data/redis"
redis_ports: [7000, 7001, 7002, 7003, 7004, 7005] # 3主3从端口
redis_password: "Redis@123456"
redis_cluster_replicas: 1 # 每个主节点1个从节点
redis_max_memory: "4gb" # 单节点内存限制
redis_max_memory_policy: "allkeys-lru" # 内存淘汰策略
3.4 Redis配置模板(roles/redis/templates/redis.conf.j2)
ini
# 基础配置
port {{ port }}
daemonize yes
pidfile /var/run/redis-{{ port }}.pid
logfile {{ redis_base_dir }}/{{ port }}/redis.log
dir {{ redis_base_dir }}/{{ port }}
# 集群配置
cluster-enabled yes
cluster-config-file nodes-{{ port }}.conf
cluster-node-timeout 5000
cluster-announce-ip {{ ansible_default_ipv4.address }}
cluster-announce-port {{ port }}
cluster-announce-bus-port {{ port + 10000 }}
# 网络配置
bind 0.0.0.0
protected-mode no
requirepass {{ redis_password }}
masterauth {{ redis_password }}
# 内存配置
maxmemory {{ redis_max_memory }}
maxmemory-policy {{ redis_max_memory_policy }}
# 持久化配置(生产环境必开)
rdbcompression yes
rdbchecksum yes
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
3.5 Redis部署任务(roles/redis/tasks/main.yml)
yaml
# 任务1:安装Redis依赖
- name: 安装Redis编译依赖
yum:
name:
- gcc
- gcc-c++
- make
- wget
state: present
# 任务2:下载并解压Redis源码
- name: 下载Redis {{ redis_version }}
get_url:
url: https://download.redis.io/releases/redis-{{ redis_version }}.tar.gz
dest: /tmp/redis-{{ redis_version }}.tar.gz
mode: 0644
- name: 解压Redis源码
unarchive:
src: /tmp/redis-{{ redis_version }}.tar.gz
dest: /tmp/
remote_src: yes
# 任务3:编译安装Redis
- name: 编译Redis
command: make MALLOC=libc
args:
chdir: /tmp/redis-{{ redis_version }}
changed_when: false
- name: 安装Redis
command: make install
args:
chdir: /tmp/redis-{{ redis_version }}
changed_when: false
# 任务4:创建Redis节点目录
- name: 创建Redis节点目录
file:
path: "{{ redis_base_dir }}/{{ item }}"
state: directory
mode: 0755
recurse: yes
loop: "{{ redis_ports }}"
# 任务5:生成每个节点的配置文件
- name: 部署Redis节点配置文件
template:
src: templates/redis.conf.j2
dest: "{{ redis_base_dir }}/{{ item }}/redis.conf"
mode: 0644
loop: "{{ redis_ports }}"
vars:
port: "{{ item }}"
notify: restart redis
# 任务6:启动所有Redis节点
- name: 启动Redis节点
command: redis-server {{ redis_base_dir }}/{{ item }}/redis.conf
loop: "{{ redis_ports }}"
changed_when: false
# 任务7:创建Redis集群(需redis-cli支持--cluster参数)
- name: 创建Redis Cluster集群
command: >
redis-cli -a {{ redis_password }} --cluster create
{% for port in redis_ports %}
{{ ansible_default_ipv4.address }}:{{ port }} {% endfor %}
--cluster-replicas {{ redis_cluster_replicas }}
--cluster-yes
args:
chdir: /usr/local/bin
register: cluster_create
changed_when: "'OK' in cluster_create.stdout"
# 任务8:开放Redis端口(集群节点通信)
- name: 开放Redis端口
firewalld:
port: "{{ item }}/tcp"
zone: public
permanent: yes
state: enabled
loop: "{{ redis_ports + [7000+10000, 7001+10000, 7002+10000, 7003+10000, 7004+10000, 7005+10000] }}"
notify: restart firewalld
3.6 Redis触发器(roles/redis/handlers/main.yml)
yaml
# 重启单个Redis节点(生产环境集群不建议批量重启)
- name: restart redis
command: >
redis-cli -a {{ redis_password }} -p {{ item }} shutdown &&
redis-server {{ redis_base_dir }}/{{ item }}/redis.conf
loop: "{{ redis_ports }}"
changed_when: false
- name: restart firewalld
service:
name: firewalld
state: restarted
3.7 主Playbook新增Redis执行步骤(site.yml)
在公共配置后、MySQL部署前执行Redis集群部署:
yaml
- name: 执行所有主机公共配置
hosts: common
roles:
- common
# 新增Redis集群部署步骤
- name: 部署Redis集群服务器
hosts: redis_servers
roles:
- redis
- name: 部署MySQL数据库服务器
hosts: mysql_servers
roles:
- mysql
- name: 部署PHP应用服务器
hosts: php_servers
roles:
- php
- name: 部署Nginx反向代理服务器
hosts: nginx_servers
roles:
- nginx
# 原有健康检查步骤...
3.8 执行Redis集群部署
bash
# 干跑验证
ansible-playbook -i inventory.ini site.yml --check -v --tags redis
# 实际部署
ansible-playbook -i inventory.ini site.yml -v --tags redis
3.9 验证Redis集群
在Redis节点执行:
bash
# 登录任意节点
redis-cli -h 192.168.1.40 -p 7000 -a Redis@123456
# 查看集群状态
192.168.1.40:7000> cluster info
# 查看节点信息
192.168.1.40:7000> cluster nodes
输出包含cluster_state:ok即为集群部署成功。
四、第二步:Web应用节点集成Redis集群
4.1 PHP应用节点改造(以PHP为例,Java同理)
4.1.1 安装PHP Redis扩展(Ansible自动化)
修改roles/php/tasks/main.yml,新增扩展安装任务:
yaml
# 原有PHP安装任务...
# 新增:安装phpredis扩展(支持集群)
- name: 安装phpredis扩展依赖
yum:
name:
- php-devel
- gcc
- make
state: present
- name: 安装phpredis扩展
pecl:
name: redis
state: present
- name: 启用phpredis扩展
lineinfile:
path: /etc/php.ini
line: "extension=redis.so"
regexp: "^extension=redis.so"
state: present
notify: restart php-fpm
4.1.2 PHP应用配置Redis集群(模板化)
- 创建Redis配置模板
roles/php/templates/redis_config.php.j2:
php
<?php
// Redis集群配置
$redis_cluster_nodes = [
{% for host in groups['redis_servers'] %}
"{{ host }}:7000",
"{{ host }}:7001",
"{{ host }}:7002",
{% endfor %}
];
$redis_password = "{{ hostvars[groups['redis_servers'][0]]['redis_password'] }}";
$redis_timeout = 3;
$redis_retry_interval = 100;
// 初始化Redis集群客户端
function get_redis_client() {
global $redis_cluster_nodes, $redis_password, $redis_timeout, $redis_retry_interval;
$redis = new RedisCluster(null, $redis_cluster_nodes, $redis_timeout, $redis_timeout, false, $redis_password);
$redis->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_ERROR);
return $redis;
}
?>
- 修改PHP应用部署任务,添加配置文件分发:
yaml
# roles/php/tasks/main.yml 新增
- name: 部署Redis配置文件
template:
src: templates/redis_config.php.j2
dest: "{{ app_deploy_path }}/redis_config.php"
mode: 0644
owner: app
group: app
4.1.3 PHP业务代码集成缓存逻辑
修改应用核心业务文件(如product_service.php),实现缓存+DB双查:
php
<?php
require_once __DIR__ . '/redis_config.php';
class ProductService {
private $redis;
private $cachePrefix = "product:";
private $cacheExpire = 1800; // 30分钟
public function __construct() {
$this->redis = get_redis_client();
}
/**
* 获取商品信息(缓存优先)
*/
public function getProductById($productId) {
$cacheKey = $this->cachePrefix . $productId;
// 1. 查缓存
try {
$productJson = $this->redis->get($cacheKey);
if ($productJson) {
if ($productJson === "null") { // 空值缓存,防止穿透
return null;
}
return json_decode($productJson, true);
}
} catch (Exception $e) {
// Redis集群异常,降级直接查DB
error_log("Redis查询异常:" . $e->getMessage());
}
// 2. 查DB
$product = $this->queryProductFromDB($productId);
// 3. 存入缓存(Redis正常时)
if ($product) {
try {
$this->redis->setex($cacheKey, $this->cacheExpire, json_encode($product));
} catch (Exception $e) {
error_log("Redis写入异常:" . $e->getMessage());
}
} else {
// 空值缓存,5分钟过期
try {
$this->redis->setex($cacheKey, 300, "null");
} catch (Exception $e) {
error_log("Redis空值缓存写入异常:" . $e->getMessage());
}
}
return $product;
}
/**
* 更新商品信息(更新DB+删除缓存)
*/
public function updateProduct($product) {
// 1. 更新DB
$this->updateProductToDB($product);
// 2. 删除缓存(避免数据不一致)
$cacheKey = $this->cachePrefix . $product['id'];
try {
$this->redis->del($cacheKey);
} catch (Exception $e) {
error_log("Redis删除缓存异常:" . $e->getMessage());
}
}
// 模拟DB操作...
private function queryProductFromDB($productId) {
// 实际替换为MySQL查询
return [
'id' => $productId,
'name' => '集群测试商品' . $productId,
'price' => 199.9
];
}
private function updateProductToDB($product) {
// 实际替换为MySQL更新
error_log("更新商品:" . $product['id']);
}
}
// 测试
$service = new ProductService();
$product = $service->getProductById(1001);
print_r($product);
?>
4.2 Java应用节点改造(补充)
若Web集群是Java(Spring Boot),核心改造如下:
- 新增Redis集群依赖(pom.xml):
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 配置Redis集群(application.yml):
yaml
spring:
redis:
cluster:
nodes: 192.168.1.40:7000,192.168.1.40:7001,192.168.1.40:7002
max-redirects: 3
password: Redis@123456
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
- 缓存逻辑封装(同单节点,Spring Cache注解或RedisTemplate):
java
@Service
public class ProductService {
@Resource
private RedisTemplate<String, String> redisTemplate;
// 缓存逻辑同单节点,RedisTemplate自动适配集群
// ...
}
五、第三步:Nginx层缓存优化(可选)
对于静态资源(如图片、CSS、JS),可在Nginx层集成Redis缓存,减少后端请求:
5.1 安装Nginx Redis模块
bash
# 在Nginx节点执行(Ansible任务可封装到nginx角色)
yum install -y nginx-mod-http-redis2
5.2 修改Nginx配置(roles/nginx/templates/nginx.conf.j2)
nginx
http {
# 原有配置...
# Redis集群配置
upstream redis_cluster {
server 192.168.1.40:7000;
server 192.168.1.40:7001;
server 192.168.1.40:7002;
}
server {
# 原有配置...
# 静态资源Redis缓存
location ~* \.(jpg|png|css|js)$ {
# 先查Redis缓存
set $redis_key "static:$uri";
redis2_query get $redis_key;
redis2_pass redis_cluster;
# 缓存未命中,读取本地文件并写入Redis
error_page 404 = @static_file;
expires 30d;
}
location @static_file {
root {{ static_resource_path }};
# 写入Redis缓存(1天过期)
add_header X-Cache "MISS";
post_action @write_redis;
}
location @write_redis {
internal;
set $redis_key "static:$uri";
redis2_query setex $redis_key 86400 $request_body;
redis2_pass redis_cluster;
}
}
}
六、第四步:集群缓存验证与优化
6.1 功能验证
-
缓存读写验证 : 访问PHP应用接口
http://192.168.1.10/product/1001,首次访问查DB并写入Redis,二次访问直接读Redis:bash# 在Redis节点查看缓存 redis-cli -h 192.168.1.40 -p 7000 -a Redis@123456 get product:1001 -
集群高可用验证: 停止其中一个Redis节点(如7000),应用仍能正常访问缓存:
bashredis-cli -h 192.168.1.40 -p 7000 -a Redis@123456 shutdown # 再次访问接口,仍能返回数据 -
负载均衡验证: 查看Nginx访问日志,确认请求分发到不同PHP节点,且所有PHP节点共用同一Redis集群:
bashtail -f /var/log/nginx/access.log
6.2 性能优化
-
缓存命中率优化:
-
核心业务Key设置合理过期时间(30分钟~24小时);
-
热点Key永不过期,通过定时任务更新;
-
监控命中率(目标≥95%):
bashredis-cli -h 192.168.1.40 -p 7000 -a Redis@123456 info stats | grep keyspace_hits
-
-
Redis集群优化:
-
生产环境Redis节点与应用节点物理隔离,避免资源竞争;
-
开启Redis慢查询日志,优化大Key操作:
ini# redis.conf添加 slowlog-log-slower-than 10000 # 记录10ms以上操作 slowlog-max-len 1000
-
-
应用端优化:
-
添加Redis异常降级逻辑(Redis集群故障时直接查DB);
-
批量操作使用
pipeline减少网络开销(PHP示例):php// 批量查询商品 public function getProductsByIds($productIds) { $cacheKeys = array_map(function($id) { return $this->cachePrefix . $id; }, $productIds); // 批量查缓存 $cacheResults = $this->redis->mget($cacheKeys); // 未命中的查DB,批量写入缓存 // ... }
-
6.3 监控配置(Prometheus+Grafana)
-
部署Redis Exporter:
bash# Ansible任务封装 docker run -d --name redis-exporter \ -p 9121:9121 \ -e REDIS_EXPORTER_PASSWORD={{ redis_password }} \ -e REDIS_EXPORTER_CLUSTER=yes \ oliver006/redis_exporter --redis.addr=192.168.1.40:7000 -
Grafana导入Redis集群监控模板(ID:10750),监控指标:
- 集群节点状态、内存使用率;
- 缓存命中率、读写QPS;
- 过期Key数、慢查询数。