Redis 笔记 04:集群、缓存、最佳实践

2022/7/4 2:22:27

本文主要是介绍Redis 笔记 04:集群、缓存、最佳实践,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Redis 笔记 04:集群、缓存、最佳实践

这是本人根据黑马视频学习 Redis 的相关笔记,系列文章导航:《Redis设计与实现》笔记与汇总

集群架构

单点 Redis 的问题

pximage

持久化

RDB

pximage

AOF

AOF : Append Only File。Redis 处理的每一个写命令都会记录在 AOF 文件中,可以看作是命令日志文件。

pximage

pximage

两者对比

pximage

主从

搭建主从架构

目的是在同一台虚拟机中创建 3 个 redis 实例,模拟主从集群,如下:

IP PORT 角色
192.168.137.112 7001 master
192.168.137.112 7002 slave
192.168.137.112 7003 slave
  1. 创建三个目录,分别存放一份配置文件,例如:

    ├── 7001
    │   └── redis.conf
    ├── 7002
    │   └── redis.conf
    └── 7003
        └── redis.conf
    
    3 directories, 3 files
    
  2. 修改每个配置文件的端口和工作目录,可以用 sed 快速完成:

    sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
    sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/tmp\/7002\//g' 7002/redis.conf
    sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/tmp\/7003\//g' 7003/redis.conf
    
  3. 修改每个实例的声明 IP:
    虚拟机本身有多个 IP,为了避免将来混乱,我们需要在 redis.conf 文件中指定每一个实例的绑定 ip 信息,格式如下:

    # redis实例的声明 IP
    replica-announce-ip 192.168.150.101
    

    每个目录都要改,我们一键完成修改(在/tmp 目录执行下列命令):

    # 逐一执行
    sed -i '1a replica-announce-ip 192.168.137.112' 7001/redis.conf
    sed -i '1a replica-announce-ip 192.168.137.112' 7002/redis.conf
    sed -i '1a replica-announce-ip 192.168.137.112' 7003/redis.conf
    
    # 或者一键修改
    printf '%s\n' 7001 7002 7003 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.137.112' {}/redis.conf
    
  4. 如果主节点设置了密码,则要在从节点的配置文件中添加如下配置:

    masterauth [主节点密码]
    

    当然练习的时候也可以把密码都删除了

  5. 启动:

    # 第1个
    redis-server 7001/redis.conf
    # 第2个
    redis-server 7002/redis.conf
    # 第3个
    redis-server 7003/redis.conf
    
  6. 设置主从关系:

    在从节点上:

    slaveof <master-ip> <master-port>
    # 或 5.0 之后
    replicaof <master-ip> <master-port>
    
  7. 一键暂停:

    printf '%s\n' 7001 7002 7003 | xargs -I{} -t redis-cli -p {} shutdown
    

pximage

可以看一下上图中的标记,日志记录了两者之间进行同步的信息

全量同步

主从第一次同步为 全量同步:

pximage

Master 如何判断 Slave 是否是第一次来同步数据?

有几个概念,可以作为判断依据:

  • Replication Id:简称 replid,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的 replid,slave 则会继承 master 节点的 replid
  • offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset。如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于 master,需要更新。

master 判断一个节点是否是第一次同步的依据,就是看 replid 是否一致。

增量同步

pximage

同步的优化

pximage

哨兵

理论

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
  • 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主
  • 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端

主观下线和客观下线:

Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping 命令:

•主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线

•客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。


如何选举新的 master?

pximage


如何实现故障转移?

pximage

搭建哨兵集群

三个 sentinel 实例信息如下:

节点 IP PORT
s1 192.168.150.101 27001
s2 192.168.150.101 27002
s3 192.168.150.101 27003
  1. 创建目录

    # 进入/tmp目录
    cd /tmp
    # 创建目录
    mkdir s1 s2 s3
    
  2. 在每个目录下创建配置文件,注意修改端口号(第一行)和文件夹路径(最后一行):

    port 27001
    sentinel announce-ip 192.168.150.101
    sentinel monitor mymaster 192.168.150.101 7001 2
    sentinel down-after-milliseconds mymaster 5000
    sentinel failover-timeout mymaster 60000
    dir "/tmp/s1"
    
  3. 逐个启动

    redis-sentinel sentinel.conf
    

测试

  1. 先开 3 个终端,用来启动 3 个 redis
  2. 再开 3 个终端,用来启动 3 个 sentinel
  3. 把 7001 的 redis 挂掉
  4. 观察
  5. 再把 7001 恢复

sentinel 的日志:

pximage

redis-server 原来的从节点的日志:

pximage

7001 恢复后,自己变成从节点

RedisTemplate使用

pximage

pximage

分片集群

pximage

搭建分片集群

这里我们会在同一台虚拟机中开启 6 个 redis 实例,模拟分片集群,信息如下:

IP PORT 角色
192.168.150.101 7001 master
192.168.150.101 7002 master
192.168.150.101 7003 master
192.168.150.101 8001 slave
192.168.150.101 8002 slave
192.168.150.101 8003 slave
  1. 创建文件夹

    # 进入/tmp目录
    cd /tmp
    # 删除旧的,避免配置干扰
    rm -rf 7001 7002 7003
    # 创建目录
    mkdir 7001 7002 7003 8001 8002 8003
    
  2. 准备一个配置文件

    port 6379
    # 开启集群功能
    cluster-enabled yes
    # 集群的配置文件名称,不需要我们创建,由redis自己维护
    cluster-config-file /tmp/6379/nodes.conf
    # 节点心跳失败的超时时间
    cluster-node-timeout 5000
    # 持久化文件存放目录
    dir /tmp/6379
    # 绑定地址
    bind 0.0.0.0
    # 让redis后台运行
    daemonize yes
    # 注册的实例ip
    replica-announce-ip 192.168.150.101
    # 保护模式
    protected-mode no
    # 数据库数量
    databases 1
    # 日志
    logfile /tmp/6379/run.log
    
  3. 将这个文件拷贝到每个目录下

    # 进入/tmp目录
    cd /tmp
    # 执行拷贝
    echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
    
  4. 修改每个目录下的 redis.conf,将其中的 6379 修改为与所在目录一致

    # 进入/tmp目录
    cd /tmp
    # 修改配置文件
    printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
    
  5. 启动

    # 进入/tmp目录
    cd /tmp
    # 一键启动所有服务
    printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
    
  6. 创建集群

    redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
    
    • redis-cli --cluster或者./redis-trib.rb:代表集群操作命令
    • create:代表是创建集群
    • --replicas 1或者--cluster-replicas 1 :指定集群中每个 master 的副本个数为 1,此时节点总数 ÷ (replicas + 1) 得到的就是 master 的数量。因此节点列表中的前 n 个就是 master,其它节点都是 slave 节点,随机分配到不同 master
  7. 查看集群状态

    redis-cli -p 7001 cluster nodes
    
  8. 测试(集群测试时,要添加上 -c 参数)

    redis-cli -c -p 7001
    
  9. 关闭

    printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
    

散列插槽

原理方面可以参考 一致性哈希算法

Redis 会把每一个 master 节点映射到 0~16383 共 16384 个插槽(hash slot)上,查看集群信息时就能看到:

pximage

Redis 会根据 key 的有效部分计算插槽值,分两种情况:

  • key 中包含"{}",且“{}”中至少包含 1 个字符,“{}”中的部分是有效部分
  • key 中不包含“{}”,整个 key 都是有效部分

案例:

>SET num 10

那么就根据 num 计算,利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值,然后根据slot值确定要把数据插入到哪个 Redis 中

集群伸缩

主要是增加新的节点,给它分配槽,或者删除节点的操作。


添加节点:

pximage

redis-cli --cluster add-node 192.168.150.101:7004 192.168.150.101:7001

通过命令查看集群状态:

redis-cli -p 7001 cluster nodes

默认是 master 节点,不分配插槽,因此没有任何数据可以存储到 7004 上


转移插槽:

[root@localhost] redis-cli --cluster reshard 192.168.137.112:7001

然后依次按照提示输入:

  1. 要移动多少
  2. 哪个 node 来接收(id)
  3. 让哪个 node 来提供
  4. 确认要转移吗

故障转移

  • 当一个 master 宕机后,slave 节点会代替他成为 master
  • 宕机后的节点启动后变成 slave 节点

手动故障转移:

利用cluster failover命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的这个 slave 节点,实现无感知的数据迁移。

pximage

这种 failover 命令可以指定三种模式:

  • 缺省:默认的流程,如图 1~6 歩
  • force:省略了对 offset 的一致性校验
  • takeover:直接执行第 5 歩,忽略数据一致性、忽略 master 状态和其它 master 的意见

例如,让 slave 变成 master,可以在 master 上执行:

pximage

RedisTemplate访问

RedisTemplate 底层同样基于 lettuce 实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

1)引入 redis 的 starter 依赖

2)配置分片集群地址

3)配置读写分离

与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

spring:
  redis:
    cluster:
      nodes:
        - 192.168.150.101:7001
        - 192.168.150.101:7002
        - 192.168.150.101:7003
        - 192.168.150.101:8001
        - 192.168.150.101:8002
        - 192.168.150.101:8003

多级缓存

pximage

JVM进程缓存

项目案例

导入数据库

创建 MySQL 数据库

docker run \
 -p 3306:3306 \
 --name mysql \
 -v $PWD/conf:/etc/mysql/conf.d \
 -v $PWD/logs:/logs \
 -v $PWD/data:/var/lib/mysql \
 -e MYSQL_ROOT_PASSWORD=123 \
 --privileged \
 -d \
 mysql

在/tmp/mysql/conf 目录添加一个 my.cnf 文件,作为 mysql 的配置文件:

# 创建文件
touch /tmp/mysql/conf/my.cnf

文件的内容如下:

[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000

重新启动一下 mysql

导入 SQL 数据


导入项目代码(略)

导入前端代码(略)

认识Caffeine

pximage

简单使用的案例:

public class CaffeineTest {

	@Test
	void testBasicOps() {
		Cache<String, String> cache = Caffeine.newBuilder().build();

		cache.put("CAT", "TOM");

		String gf = cache.getIfPresent("CAT");
		System.out.println(gf);

		String dog = cache.get("DOG", key -> "WangWang");

		System.out.println(dog);
	}
}

三种缓存驱逐策略:

pximage

实现进程缓存

config/CaffeineConfig

@Configuration
public class CaffeineConfig {

	@Bean
	public Cache<Long, Item> itemCache() {
		return Caffeine.newBuilder()
				.initialCapacity(100)
				.maximumSize(10_000)
				.build();
	}

	@Bean
	public Cache<Long, ItemStock> stockCache() {
		return Caffeine.newBuilder()
				.initialCapacity(100)
				.maximumSize(10_000)
				.build();
	}
}

Controller:

@Autowired
private Cache<Long, Item> itemCache;

@Autowired
private Cache<Long, ItemStock> stockCache;

@GetMapping("/{id}")
public Item findById(@PathVariable("id") Long id){
    return itemCache.get(id, key -> itemService.query()
                         .ne("status", 3).eq("id", key)
                         .one());
}

@GetMapping("/stock/{id}")
public ItemStock findStockById(@PathVariable("id") Long id){
    return stockCache.get(id, key -> stockService.getById(key));
}

OpenResty

Lua语法

此部分可以参考:Lua基础教程笔记

安装

官网:OpenResty® - 中文官方站

OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

  1. 安装开发库

    yum install -y pcre-devel openssl-devel gcc --skip-broken
    
  2. 安装 OpenResty 仓库

    # 如果以前安装过 yum-utils 则跳过
    # yum install -y yum-utils 
    
    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    
  3. 安装

    yum install -y openresty
    
  4. 安装 opm 工具

    yum install -y openresty-opm
    
  5. 添加到环境变量

    vi /etc/profile
    
    # 添加如下内容
    export NGINX_HOME=/usr/local/openresty/nginx
    export PATH=${NGINX_HOME}/sbin:$PATH
    
    source /etc/profile
    

安装完成后:

pximage

其实是在 NGINX 的基础上做了增强,可以用之前学的 nginx 命令对其进行控制

这里有一份简化的配置文件:

#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8081;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

快速入门

让 OpenResty 可以处理请求:

nginx.conf 配置文件:

pximage

自定义脚本 nginx/lua/item.lua

ngx.say('{"name":"商品", "price":"100"}')

如果运行nginx失败,参考 解决 openresty Nginx 重启报错

请求参数处理

pximage

这里拿第一种情况做一个测试:

nginx.conf

location ~ /api/item/(\d+) {
    default_type application/json;
    content_by_lua_file lua/item.lua;
}

item.lua

-- 获取路径参数
local id = ngx.var[1]

ngx.say('{"name":'.. id ..', "price":"100"}')

查询Tomcat

pximage

关于如何检查防火墙的状态,可以用 nmap 等工具

关于如何关闭防火墙,可以参考 打开或关闭 Microsoft Defender 防火墙

首先修改一下 nginx 的配置:

location /item {
	proxy_pass http://192.168.137.1:8081;
}

封装一个方法,用来进行通用的网络请求的发送:

openresty/lualib/common.js

-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
    local resp = ngx.location.capture(path,{
        method = ngx.HTTP_GET,
        args = params,
    })
    if not resp then
        -- 记录错误信息,返回404
        ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
        ngx.exit(404)
    end
    return resp.body
end
-- 将方法导出
local _M = {  
    read_http = read_http
}  
return _M

然后编写具体的逻辑代码:

-- 导入 common 函数库
local common = require('common')
local read_http = common.read_http

-- 导入cjson模块
local cjson = require('cjson')

-- 获取路径参数
local id = ngx.var[1]

-- 查询商品信息
local itemJSON = read_http("/item/".. id, nil)

-- 查询库存信息
local stockJSON = read_http("/item/stock/"..id, nil)

-- JSON 转换 lua 的 table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)

-- 组合数据
item.stock = stock.stock
item.sold = stock.sold

-- item 序列化为 json 返回结果
ngx.say(cjson.encode(item))

Tomcat 负载均衡:

实际应用场景中一般是用集群模式启动,所以我们模拟多台 Tomcat:

pximage

配置 nginx.conf

pximage

(部分)

upstream tomcat-cluster {
    hash $request_uri;
    server 192.168.137.1:8081;
    server 192.168.137.1:8082;
}
server {
    location /item {
    	proxy_pass http://tomcat-cluster; 
    }
}

Redis缓存预热

目的:让 OpenResty 优先查询 redis,然后再查询 tomcat

pximage

  1. 启动 redis (视频这里用了 docker 运行 redis)

    docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes
    
  2. 在项目中引入依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  3. 配置 redis 地址

    spring:
      redis:
        host: 192.168.137.112
        password: abc123
    
  4. 编写初始化类
    实现了 InitializingBean 的类会在属性注入后执行,以实现预热的效果

    @Component
    public class RedisHandler implements InitializingBean {
    	@Autowired
    	private StringRedisTemplate redisTemplate;
    
    	@Autowired
    	private IItemService itemService;
    
    	@Autowired
    	private IItemStockService stockService;
    
    	@Autowired
    	private static final ObjectMapper MAPPER = new ObjectMapper();
    
    	@Override
    	public void afterPropertiesSet() throws Exception {
    		List<Item> itemList = itemService.list();
    		List<ItemStock> stockLi = stockService.list();
    
    		for (Item item : itemList) {
    			String s = MAPPER.writeValueAsString(item);
    			redisTemplate.opsForValue().set("item:" + item.getId(), s);
    		}
    
    		for (ItemStock item : stockLi) {
    			String s = MAPPER.writeValueAsString(item);
    			redisTemplate.opsForValue().set("item:stock:" + item.getId(), s);
    		}
    
    	}
    }
    

查询Redis缓存

首先需要能够连接上 redis:

openResty/lualib/common.lua:添加如下功能

-- 导入 Redis
local redis = require('resty.redis')
-- 初始化 redis
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)


-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
    local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
    local pool_size = 100 --连接池大小
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx.log(ngx.ERR, "Put into redis connection Pool failed: ", err)
    end
end

-- 查询redis的方法 ip和port是redis地址,key是查询的key
local function read_redis(ip, port, key)
    -- 获取一个连接
    local ok, err = red:connect(ip, port)
    if not ok then
        ngx.log(ngx.ERR, "Connect redis Failed : ", err)
        return nil
    end
    red:auth('abc123')
    -- 查询redis
    local resp, err = red:get(key)
    -- 查询失败处理
    if not resp then
        ngx.log(ngx.ERR, "Query Redis Failed: ", err, ", key = " , key)
    end
    --得到的数据为空处理
    if resp == ngx.null then
        resp = nil
        ngx.log(ngx.ERR, "Query Redis Empty, key = ", key)
    end
    close_redis(red)
    return resp
end

-- 将方法导出
local _M = {  
    read_redis = read_redis
}  
return _M
  • 关于如果redis没有密码,则无需 red:auth 那一行代码,参考 openresty 前端开发入门四之Redis篇
  • 关于日志的功能,参考 日志输出 - OpenResty 实践

openResty/nginx/lua/item.lua:查询脚本的封装:

-- 封装查询函数
function read_data(key, path, param)
    local resp = read_redis("127.0.0.1", 6379, key)
    if not resp then
        -- 查询 http
        ngx.log(ngx.INFO, "redis 查询失败 , key : " , key)
        resp = read_http(path, param)
    end
    return resp
end

具体的查询:

-- 查询商品信息
local itemJSON = read_data("item:"..id, "/item/".. id, nil)

-- 查询库存信息
local stockJSON = read_data("item:stock:"..id, "/item/stock/"..id, nil)

Nginx本地缓存

pximage

-- 封装查询函数
function read_data(key, path, param, expire)
    -- 查询本地缓存
    local resp = item_cache:get(key)
    if not resp then
        ngx.log(ngx.ERR, "Local Query Failed, key:", key)
        resp = read_redis("127.0.0.1", 6379, key)
        if not resp then
            -- 查询 http
            ngx.log(ngx.ERR, "redis Query Failed, key : " , key)
            resp = read_http(path, param)
        end
        -- 把数据写入缓存
        item_cache:set(key, resp, expire)
    end
    return resp
end

缓存同步

数据同步策略

缓存数据同步的常见方式有三种:

  • 设置有效期:给缓存设置有效期,到期后自动删除。再次查询时更新
    • 优势:简单、方便
    • 缺点:时效性差,缓存过期之前可能不一致
    • 场景:更新频率较低,时效性要求低的业
  • 同步双写:在修改数据库的同时,直接修改缓存
    • 优势:时效性强,缓存与数据库强一致
    • 缺点:有代码侵入,耦合度高;
    • 场景:对一致性、时效性要求较高的缓存数据
  • 异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据
    • 优势:低耦合,可以同时通知多个缓存服务
    • 缺点:时效性一般,可能存在中间不一致状态
    • 场景:时效性要求一般,有多个服务需要同步

Canal介绍

pximage

pximage

Canal安装与使用

暂略,可以参考网络文章

最佳实践

Redis键值设计

优雅的key结构

pximage

查看编码方式:

> object encoding XXX

pximage

拒绝BigKey

pximage

查看内存占用:

> MEMORY USAGE name

一般用长度等来估计大小

pximage

危害:

  • 网络阻塞
    对 BigKey 执行读请求时,少量的 QPS 就可能导致带宽使用率被占满,导致 Redis 实例,乃至所在物理机变慢

  • 数据倾斜

    BigKey 所在的 Redis 实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡

  • Redis阻塞
    对元素较多的 hash、list、zset 等做运算会耗时较旧,使主线程被阻塞

  • CPU压力
    对 BigKey 的数据序列化和反序列化会导致 CPU 的使用率飙升,影响 Redis 实例和本机其它应用

寻找 BigKey:

  • redis-cli --bigkeys
    利用 redis-cli 提供的--bigkeys 参数,可以遍历分析所有 key,并返回 Key 的整体统计信息与每个数据的 Top1 的 big key
  • scan扫描
    自己编程,利用 scan 扫描 Redis 中的所有 key,利用 strlen、hlen 等命令判断 key 的长度(此处不建议使用 MEMORY USAGE)
  • 第三方工具
    利用第三方工具,如 Redis-Rdb-Tools 分析 RDB 快照文件,全面分析内存使用情况
  • 网络监控
    自定义工具,监控进出 Redis 的网络数据,超出预警值时主动告警

如何删除:

BigKey 内存占用较多,即便时删除这样的 key 也需要耗费很长时间,导致 Redis 主线程阻塞,引发一系列问题。

  • redis 3.0及以下版本
    如果是集合类型,则遍历 BigKey 的元素,先逐个删除子元素,最后删除 BigKey
  • Redis 4.0以后
    Redis 在 4.0 后提供了异步删除的命令: unlink

选择合适的数据结构

pximage

pximage

批处理优化

pximage

  • M 操作具备原子性
  • Pipeline 不具备原子性

pximage

  • Spring 有封装模板,一般用并行 slot

服务端优化

持久化配置

Redis 的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:

  1. 用来做缓存的 Redis 实例尽量不要开启持久化功能

  2. 建议关闭 RDB 持久化功能,使用 AOF 持久化

  3. 利用脚本定期在 slave 节点做 RDB,实现数据备份

  4. 设置合理的 rewrite 阈值,避免频繁的 bgrewrite

  5. 配置 no-appendfsync-on-rewrite = yes,禁止在 rewrite 期间做 aof,避免因 AOF 引起的阻塞

部署有关建议:

  1. Redis 实例的物理机要预留足够内存,应对 fork 和 rewrite
  2. 单个 Redis 实例内存上限不要太大,例如 4G 或 8G。可以加快 fork 的速度、减少主从同步数据迁移压力
  3. 不要与 CPU 密集型应用部署在一起
  4. 不要与高硬盘负载应用一起部署。例如:数据库、消息队列

慢查询问题

pximage

命令与安全

参考这篇文章:Redis未授权访问配合SSH key文件利用分析

为了避免这样的漏洞,这里给出一些建议:

  1. Redis 一定要设置密码
  2. 禁止线上使用下面命令: keys、flushall、flushdb、config set 等命令。可以利用 rename-command 禁用
  3. bind:限制网卡,禁止外网网卡访问
  4. 开启防火墙
  5. 不要使用 Root 账户启动 Redis
  6. 尽量不使用默认的端口

内存分配

pximage

pximage

> client list

集群最佳实践

一个配置:

pximage

集群带宽问题:

pximage

集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题:

  1. 集群完整性问题
  2. 集群带宽问题
  3. 数据倾斜问题客户端性能问题
  4. 命令的集群兼容性问题 lua 和事务问题

建议:单体Redis(主从Redis)已经能达到万级别的QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,尽量不搭建Redis集群。



这篇关于Redis 笔记 04:集群、缓存、最佳实践的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程