折腾基本功:Redis 从入门到 Docker 部署
前面写过了两篇 “Redis” 相关的内容,今天补一篇“基本功”内容,让后续折腾系列文章可以篇幅更短、内容更专注。
前言
在日常工作中,我们构建应用时总是离不开一些基础组件,Redis 就是其中特别常用的一个。之前我写过不少文章,通常会把多个组件放在一起讲,介绍它们是如何协同工作的。随着目前积累了上千篇内容后,这样的内容并不适合维护、也不方便大家查阅和学习。比如你今天只想看看 Redis 怎么用,却要在文章里翻来翻去找 Redis 相关的部分。
所以这次,我想换个思路,把这些基础组件分开来讲,一个一个地仔细说说。今天,我们就先聊聊 Redis。
为什么选 Redis 作为开头呢?因为它实在是太常用了。无论是做网站、做 App,还是做企业内部系统,都少不了它。缓存数据、管理用户会话、计数排行、消息队列…Redis 就像个百宝箱,总能帮我们解决各种各样的问题。
本文我们主要会聊四块内容:
- Redis 的基础使用:常用的数据类型和命令使用
- Redis 的实际使用:常用业务场景中的具体使用
- Redis 的容器化使用(Docker 部署细节):靠谱的 Redis 环境
- Redis 的最佳实践:一些基础建议
本文使用的是 2024 年下半年的最新版本 Redis 7.4.1。另外提一句,Redis 8.0 也快要来了,会带来不少令人兴奋的原本在企业版本中的新特性。比如内置的搜索功能、JSON 支持、时间序列数据处理等等。
好,话不多说,让我们先从基础开始吧!
一、Redis 简介
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,它可以用作数据库、缓存、消息代理和队列。我们可以将 Redis 内置的 丰富的数据类型分为核心数据类型和扩展数据类型两大类。
1. 核心数据类型
字符串(String):字符串是 Redis 最基础的数据类型。它不仅可以存储普通的文本,还可以存储数字、序列化的对象(二进制)等。单个字符串最大支持 512MB。
# 基本的设置和获取# 设置键值对
SET user:1 "李逍遥"
# 获取值
GET user:1 # 返回: "李逍遥"# 常用于缓存场景# 设置过期时间(秒)
SETEX session:token123 3600 "user_session_data" # 3600秒后过期# 原子递增/递减(适合计数场景)
SET article:1:views 0
INCR article:1:views # 返回: 1
INCRBY article:1:views 10 # 返回: 11
多数字符串操作复杂度都是 O(1),SUBSTR
、GETRANGE
、SETRANGE
复杂度可能是 O(n)。
列表(List):是按插入顺序排序的字符串列表。可以用来实现队列、栈或者最新动态这类场景,比如常用于消息队列场景。
# 添加元素
LPUSH notifications:user1 "你有一条新消息" # 从左侧添加
RPUSH notifications:user1 "系统维护通知" # 从右侧添加# 获取列表内容
LRANGE notifications:user1 0 -1 # 获取所有元素
LRANGE notifications:user1 0 4 # 获取前5条消息# 列表长度
LLEN notifications:user1# 移除元素
LPOP notifications:user1 # 从左侧移除并返回
BLPOP notifications:user1 5 # 阻塞式弹出,最多等待5秒# 列表修剪(保持固定长度)
LTRIM notifications:user1 0 99 # 只保留最新的100条消息
除了访问头部或尾部的列表复杂度是 O(1),其他的操作复杂度通常为 O(n)
集合(Set):是无序的唯一字符串集合。适合用于标签系统、关注关系等场景,可以高效地实现交集、并集操作。
# 添加元素
SADD user:1:interests "编程" "读书" "音乐"# 删除元素
SREM user:1:interests "音乐"# 判断元素是否存在
SISMEMBER user:1:interests "编程" # 返回1表示存在# 获取集合元素
SMEMBERS user:1:interests # 获取所有元素
SCARD user:1:interests # 获取元素数量# 集合运算
SADD user:2:interests "编程" "游戏" "电影"
SINTER user:1:interests user:2:interests # 共同兴趣(交集)
SUNION user:1:interests user:2:interests # 所有兴趣(并集)
SDIFF user:1:interests user:2:interests # 独有兴趣(差集)
集合最大大小为 2^32-1 (~40亿),除了 SMEMBERS
是 O(n) 复杂度外,基本都是 O(1)。
哈希(Hash):哈希表是“字段(Key)-值(Value)对”的集合,类似于 Python 字典或 Java HashMap。特别适合用来存储对象数据,比如用户信息、商品信息等。
# 设置单个字段
HSET user:1 name "李逍遥" age "25" city "临安"# 获取字段值
HGET user:1 name # 获取单个字段
HMGET user:1 name age # 获取多个字段
HGETALL user:1 # 获取所有字段和值# 检查字段是否存在
HEXISTS user:1 phone # 检查是否设置了手机号# 递增数字字段
HINCRBY user:1 login_count 1 # 登录次数加1# 删除字段
HDEL user:1 temporary_field# 获取所有字段名或字段值
HKEYS user:1 # 获取所有字段名
HVALS user:1 # 获取所有字段值
每个哈希最大大小为 2^32-1 (~40亿),多数操作都是 O(1)。
有序集合(Sorted Set):每个成员关联一个分数的有序集合,成员唯一,分数可以重复。常用于排行榜、优先级队列等需要排序的场景。
# 添加带分数的元素
ZADD leaderboard 89.5 "player1" 95.2 "player2" 78.3 "player3"# 获取排名
ZRANK leaderboard "player1" # 从低到高排名(0开始)
ZREVRANK leaderboard "player1" # 从高到低排名# 获取分数
ZSCORE leaderboard "player1"# 获取排名范围
ZRANGE leaderboard 0 2 WITHSCORES # 获取前3名(升序)
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前3名(降序)# 按分数范围获取
ZRANGEBYSCORE leaderboard 80 100 WITHSCORES # 获取80-100分的玩家# 增加分数
ZINCRBY leaderboard 5.0 "player1" # 增加5分
大多数排序集合复杂度为 O(log(n) + m),n 是获取成员数量,m 是返回数据数量。
流(Stream):Stream 是 Redis 5.0 引入的数据类型,是追加式日志数据结构,用于记录和处理时序事件,支持消费者组模式。适合用于消息队列、事件流等场景。
# 添加消息
XADD events * type "login" user_id "123" # * 表示自动生成ID# 读取消息
XREAD COUNT 2 STREAMS events 0 # 读取最早的2条消息# 创建消费者组
XGROUP CREATE events group1 $ # $ 表示从最新的消息开始# 消费者组读取
XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS events > # > 表示未被消费的消息
地理空间索引(Geospatial):支持存储地理位置信息,并提供经纬度距离计算、范围查询等功能,特别适合在位置服务中使用。
# 添加地理位置
# 语法:GEOADD key longitude latitude member
GEOADD locations 116.397428 39.909734 "中国科学院"
GEOADD locations 116.336956 39.986119 "清华大学"
GEOADD locations 116.310003 39.991957 "北京大学"# 获取坐标
GEOPOS locations "中国科学院" # 返回经纬度# 计算两点间距离
# 单位可以是 m(米)、km(千米)、mi(英里)、ft(英尺)
GEODIST locations "中国科学院" "清华大学" km # 返回距离,单位千米# 获取指定范围内的位置
# GEORADIUS 以给定经纬度为中心查询
# GEORADIUSBYMEMBER 以已存在的位置为中心查询
GEORADIUS locations 116.397428 39.909734 5 km WITHCOORD WITHDIST
GEORADIUSBYMEMBER locations "中国科学院" 5 km WITHCOORD WITHDIST# 获取 GEOHASH 编码
GEOHASH locations "中国科学院"# 实际应用示例:寻找附近的商家
# 添加多个商家位置
GEOADD shops 116.397428 39.909734 "shop:1"
GEOADD shops 116.396728 39.910734 "shop:2"
GEOADD shops 116.398428 39.908734 "shop:3"# 查找某个位置 3km 范围内的商家,并返回距离和坐标
GEORADIUS shops 116.397428 39.909734 3 km WITHCOORD WITHDIST WITHHASH ASC# 获取指定商家到其他商家的距离
GEORADIUSBYMEMBER shops "shop:1" 1 km WITHDIST
位图(Bitmap):支持对字符串进行位操作 ,适合高效存储布尔信息。适合用于记录状态、统计数据、用户行为统计等场景,可以大大节省内存。
# 设置位图的某一位
SETBIT user:login:2024-01-01 123 1 # 记录用户ID为123的登录状态# 获取某一位的值
GETBIT user:login:2024-01-01 123# 统计为1的位数
BITCOUNT user:login:2024-01-01 # 统计当天登录用户数# 位操作
BITOP AND result user:login:2024-01-01 user:login:2024-01-02 # 连续两天都登录的用户
SETBIT
和 GETBIT
都是 O(1), BITOP
是 O(n),其中 n 是比较中最长的字符串的长度。
位域(Bitfield):可在字符串中高效编码多个计数器 ,支持原子性的获取、设置和递增操作 ,提供多种溢出处理策略。简单来说,我们可以在 Bitfield 中存储多个整数值,每个整数可以指定不同的位宽,非常适合存储大量小整数或计数器。
# 设置一个 8 位无符号整数(u8),偏移量为 0
BITFIELD counters SET u8 0 100 # 在位置0设置一个值为100的8位无符号整数# 设置多个值
BITFIELD counters SET u8 0 100 SET u16 8 300 # 设置一个8位和一个16位整数# 获取值
BITFIELD counters GET u8 0 # 获取第一个8位整数
BITFIELD counters GET u16 8 # 获取16位整数# 递增操作(有溢出控制)
# 溢出策略:WRAP(回绕)、SAT(饱和)、FAIL(失败)
BITFIELD counters OVERFLOW SAT INCRBY u8 0 1 # 增加1,使用饱和溢出控制# 一次性获取多个值
BITFIELD counters GET u8 0 GET u16 8# 实际应用示例:记录每小时的计数器
# 假设要记录用户每小时的登录次数,使用 6 位整数(最大值63)
# 每个小时占用 6 位,一天 24 个小时总共需要 144 位
BITFIELD daily:logins:user123 SET u6 0 1 # 0点登录1次
BITFIELD daily:logins:user123 INCRBY u6 6 1 # 1点登录次数+1
BITFIELD daily:logins:user123 GET u6 12 # 获取2点的登录次数
BITFIELD
是 O(n),其中 n 是访问的计数器的数量。
2. 扩展数据类型(Redis Stack / Enterprise)
在 Redis 8.0 社区版本到来之前(8.0-M02 社区预览版包含这些模块),Redis Stack 和 Redis Enterprise 版本还提供了一些高级数据类型:
JSON
- 支持结构化数据存储
- 可直接操作 JSON 文档
- 支持复杂的查询操作
- 传递给命令的 JSON 值的深度限制最大为 128。
概率数据类型
- HyperLogLog:基数估算
- Bloom filter:成员检测
- Cuckoo filter:可删除元素的成员检测
- t-digest:百分位数估算
- Top-K:排名估算
- Count-min sketch:频率估算
时间序列
- 针对时间序列数据优化
- 支持高效的时间范围查询
- 适合监控和分析应用
因为 8.0 还没有正式发布,程序或许还有一些改进或变动的空间,所以我不计划在这篇文章中展开这三个类型的细节。
或许在后面的文章里,我会单独聊聊编译这些组件,以及实战的内容,就像是在之前使用 Redis 作为向量数据库使用一样:《使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(一)》、《使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(二)》。
二、Redis 实用场景
在介绍“核心数据类型”小节中,我提到了一些使用 Redis 的经典操作。就使用频率来说,最常见的场景还是缓存层(减轻数据库负载、提高响应速度、存储临时会话数据)、计数器(文章阅读量统计、用户点赞数、商品库存管理)、消息队列(任务排队、消息传递、事件处理)。
接下来我们来基于具体业务场景,再展开聊聊。
统一的登录态管理
又快到一年结束了,大家或许还有一些年假没有休,当我们提交年假的时候,可能需要先登录某个 OA 系统,然后再从 OA 系统跳转人力系统,然后提交年假使用申请,接下来就是等待年假审批通过,哦耶。
这个场景下,我们可能使用了多个系统,但是我们并不需要在系统间重复登录,因为系统会自动识别用户身份。这周无缝的用户体验,就可以使用 Redis 来实现。
具体实现方式是:当用户首次登录时,系统生成一个 Session ID,并将用户信息存储在 Redis 中。示例代码如下:
# 用户登录时
session_id = generate_unique_id()
user_data = {"user_id": "employee_001","name": "小李","department": "技术部","permissions": ["oa_access", "hr_access"]
}# 设置 session 数据,有效期 2 小时
redis_client.setex(f"session:{session_id}", 7200, json.dumps(user_data))
当用户访问其他系统时,这些系统可以通过共同的 Session ID 从 Redis 中获取用户信息,从而实现统一登录:
# 其他系统验证用户身份时
user_data = redis_client.get(f"session:{session_id}")
if user_data:user = json.loads(user_data)# 验证权限并允许访问
高并发场景下的缓存应用
每当有热门活动或者有大量集中访问高峰的时候,有一些系统就会变得很卡,尤其是需要大量数据库关联查询的场景。
比如每个月月初,大家集中向系统中查询上个月考勤是否有异常的时候,毕竟错过了补打卡还是挺麻烦的。这时,Redis 的缓存功能就派上了用场。
比如,对于考勤数据的缓存策略可以是这样的:
def get_attendance_report(employee_id, month):# 先尝试从 Redis 获取缓存的报告cache_key = f"attendance:{employee_id}:{month}"report = redis_client.get(cache_key)if report:return json.loads(report)# 缓存未命中,从数据库计算报告report = calculate_attendance_from_db(employee_id, month)# 将结果缓存到 Redis,设置适当的过期时间redis_client.setex(cache_key, 3600, json.dumps(report))return report
实时数据统计与分析
日常工作中,或许有的同学折腾过项目上线后的实时监控系统,来关注项目的各项指标,比如接口响应时间、用户访问量、错误率等。这些需要实时更新的计数器和统计数据,用 Redis 来处理再合适不过。
# 记录接口调用次数
redis_client.incr(f"api:calls:{api_name}")# 记录接口响应时间
redis_client.lpush(f"api:response_times:{api_name}", response_time)
redis_client.ltrim(f"api:response_times:{api_name}", 0, 999) # 只保留最近 1000 条# 使用 HyperLogLog 统计独立用户数
# https://redis.io/docs/latest/develop/data-types/probabilistic/hyperloglogs/
redis_client.pfadd(f"api:unique_users:{api_name}", user_id)
任务队列与消息传递
现在的 OA 系统中,基本都具备审批功能。当用户提交了一个需要多人审批的任务后,系统需要自动发送邮件或通知,来通知下一个审批人。这种异步任务就可以通过 Redis 的列表结构来实现一个简单的任务队列:
# 添加审批任务到队列
task = {"type": "approval_notification","document_id": "doc_001","approver": "manager_001","deadline": "2024-12-01"
}
redis_client.lpush("approval_tasks", json.dumps(task))# 消费者处理任务
while True:# 获取并处理任务task = redis_client.brpop("approval_tasks")if task:task_data = json.loads(task[1])send_approval_notification(task_data)
限流与热点防护
假设我们的系统中,支持用户使用手机验证码来进行登录。为了避免短信发送过频繁,对用户造成骚扰,对平台成本造成浪费。通常我们可以使用 Redis 实现简单的访问频率限制:
def can_send(user_id):key = f"send_limit:{user_id}"# 获取用户最近的发送次数count = redis_client.get(key)if not count:# 第一次发送,设置计数器redis_client.setex(key, 3600, 1)return Truecount = int(count)if count > 10: # 每小时最多发送 10 次return False# 增加计数器redis_client.incr(key)return True
实时排行榜
不少公司都有内部的学习平台,有不少都会展示内部课程的热度排行榜,包括最近一周最受欢迎的课程、学习时长最多的员工等。这种实时更新的排序场景,正好可以利用 Redis 的有序集合来实现:
# 记录课程学习人次
def record_course_view(course_id, user_id):# 课程热度加一redis_client.zincrby("hot_courses:weekly", 1, course_id)# 记录用户学习该课程的时间戳redis_client.zadd(f"course:viewers:{course_id}", {user_id: time.time()})# 获取热门课程排行
def get_hot_courses(limit=10):return redis_client.zrevrange("hot_courses:weekly", 0, limit-1, withscores=True)# 记录学习时长
def record_learning_time(user_id, minutes):# 累计学习时长redis_client.zincrby("learner:leaderboard", minutes, user_id)# 同时更新当日学习时长today = datetime.now().strftime("%Y%m%d")redis_client.zincrby(f"learner:daily:{today}", minutes, user_id)
地理位置服务
聊一个可能不少同学有体感的事情,上下班的时候需要掏出手机定位,打卡。当你超出公司办公室一定距离的时候,打卡是打不上的。这类“判断/查找空间点坐标距离”的需求,可以使用 Redis 的地理空间索引特性:
# 添加公司位置信息
def add_company_position(room_id, latitude, longitude):redis_client.geoadd("company", longitude, latitude, room_id)# 检查公司是否在 200 米范围内
def check_nearby_company(latitude, longitude, radius=200):# 返回 200 米范围内的公司坐标,并附带距离信息return redis_client.georadius("company", longitude, latitude, radius, unit="m", withcoord=True, withdist=True, sort="ASC")
智能表单与数据校验
有时候在做表单功能的时候,需要进行字段的实时验证,比如检查提交的项目编号是否唯一、员工编号是否有效等。使用 Redis 的集合可以高效地实现这类验证:
# 初始化有效的员工编号集合
def init_employee_ids():employee_ids = fetch_valid_employee_ids_from_db()redis_client.delete("valid:employee_ids")redis_client.sadd("valid:employee_ids", *employee_ids)# 验证员工编号
def is_valid_employee(employee_id):return redis_client.sismember("valid:employee_ids", employee_id)# 项目编号查重
def check_project_code(code):# 使用位图标记已使用的项目编号key = f"project:codes:{datetime.now().year}"# 将项目编号哈希到一个整数code_hash = hash(code) % (10 ** 6)exists = redis_client.getbit(key, code_hash)if not exists:redis_client.setbit(key, code_hash, 1)return not exists
接口限流与熔断
在一些场景中,我们需要进行一些流量控制。Redis 可以实现简单而有效的限流器:
class RateLimiter:def __init__(self, redis_client, service_name, max_requests, time_window):self.redis = redis_clientself.service = service_nameself.max_requests = max_requestsself.time_window = time_windowdef is_allowed(self, api_name):key = f"ratelimit:{self.service}:{api_name}"current = self.redis.get(key)if not current:pipeline = self.redis.pipeline()pipeline.set(key, 1)pipeline.expire(key, self.time_window)pipeline.execute()return Truecurrent = int(current)if current >= self.max_requests:return Falseself.redis.incr(key)return True# 使用示例
rate_limiter = RateLimiter(redis_client, "user_service", 100, 60)
if rate_limiter.is_allowed("get_user_info"):# 处理请求pass
else:# 触发限流处理pass
分布式锁实现
当我们的业务系统中出现订单处理的场景时,尤其是涉及到实体货物、票等资源等时候,需要确保不能超售、避免用户重复提交。可以使用 Redis 来实现一个简单的(分布式)“锁”:
class RedisLock:def __init__(self, redis_client, lock_name, expire_seconds=10):self.redis = redis_clientself.lock_name = f"lock:{lock_name}"self.expire_seconds = expire_secondsdef acquire(self):# 使用 setnx 实现加锁lock_value = str(uuid.uuid4())acquired = self.redis.set(self.lock_name, lock_value, ex=self.expire_seconds, nx=True)if acquired:# 记录锁的值,用于安全释放self.lock_value = lock_valuereturn Truereturn Falsedef release(self):# 使用 Lua 脚本确保原子性释放script = """if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])elsereturn 0end"""self.redis.eval(script, 1, self.lock_name, self.lock_value)# 使用示例
def process_order(order_id):lock = RedisLock(redis_client, f"order:{order_id}")if lock.acquire():try:# 处理订单passfinally:lock.release()
三、使用 Docker 运行 Redis
Redis 官方提供了社区版本的容器镜像,包含多种 CPU 架构。
1. 基础 Docker 命令
官方文档中的命令使用非常简单:
# 启动一个服务名称为 some-redis 的 Redis 容器
docker run --name some-redis -d redis# 启用一个每 60 秒保存数据的,具备数据持久化的 Redis 容器
docker run --name some-redis -d redis redis-server --save 60 1 --loglevel warning# 使用 Redis CLI 来链接 Redis 服务
docker run -it --network some-network --rm redis redis-cli -h some-redis
在 Docker 进入 27 版本后,不同系统的 Docker 默认行为发生了一些变化(比如是否会自动下载命令中未下载的镜像),所以我们从头开始:
# 拉取 Redis 官方最新镜像
docker pull redis:latest
# 日常使用更推荐固定版本的 Redis
docker pull redis:7.4.1-alpine3.20# 启动 Redis 容器,将容器内的默认端口和宿主机端口打通
docker run --name my-redis -d -p 6379:6379 redis:7.4.1-alpine3.20# 进入 Redis 容器,使用 Redis CLI 进入交互式终端
docker exec -it my-redis redis-cli
接下来,就可以使用上文中的命令进行练手或者实际业务啦。
2. 使用 Docker Compose
当然,为了方便管理,命令式的使用,显然没有声明式的配置来的靠谱,创建一个 docker-compose.yml
文件:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"
这份配置文件实现了上面命令相同的功能,启动服务还是使用熟悉的命令即可:
docker-compose up -d
3. Redis 的数据持久化
Redis 提供两种持久化方式:
- RDB(Redis Database):按指定时间间隔执行数据快照,适合备份场景。
- AOF(Append Only File):记录所有写操作,提供更好的持久化保证。
而在 Docker 中基础的数据持久化也分为两种,一种是 Docker Volume,一种是文件挂载到磁盘。
使用 Docker Volume 进行 Redis 数据持久化:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"volumes:- redis-data:/datavolumes:redis-data:
服务启动后 Redis 的数据将被存储在 Docker Volume 中,我们可以使用下面的命令查看 Volume 的细节:
# 获取 Redis 的数据 Volume
# docker volume ls | grep redis
local cache_redis-data# 使用 Inspect 命令查看具体信息
# docker volume inspect cache_redis-data
[{"CreatedAt": "2024-11-30T17:39:36+08:00","Driver": "local","Labels": {"com.docker.compose.project": "cache","com.docker.compose.version": "2.29.7","com.docker.compose.volume": "redis-data"},"Mountpoint": "/var/lib/docker/volumes/cache_redis-data/_data","Name": "cache_redis-data","Options": null,"Scope": "local"}
]
如果我们希望数据直接挂载到宿主机磁盘中,只需要做一些简单的改动:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"volumes:- ./redis-data:/data
当服务运行后,我们就能够在当前目录的 redis-data
中看到 Redis 的数据啦。
如果我们希望使用 AOF 方式来替换 RDB 快照模式来存储数据(牺牲数据恢复速度,但是具备最好的数据可恢复性),可以继续修改配置:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-rediscommand: redis-server --appendonly yesports:- "6379:6379"volumes:- ./redis-data:/data
4. Redis 服务自愈
如果我们希望服务能够在遇到异常自动恢复,并且不时的进行自检,判断是否需要进行服务自动恢复:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"volumes:- ./redis-data:/datacommand: redis-server --appendonly yesrestart: alwayshealthcheck:test: ["CMD", "redis-cli","ping"]interval: 1stimeout: 3sretries: 5
除了进行服务自愈之外,还能够在我们使用 docker ps
检查服务状态的时候,提供更直观的状态展示(healthy
):
# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
my-redis redis:7.4.1-alpine3.20 "docker-entrypoint.s…" redis 2 seconds ago Up 2 seconds (healthy) 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp
很多时候,为了节约资源,我们会选择多个服务部署在同一台机器,Redis 相比较一些后起之秀,不支持将数据持久化到磁盘。随着数据增长,内存占用也会变大,为了避免无限制增长影响其他的业务,我们可以设置一个资源限制:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"volumes:- ./redis-data:/datacommand: redis-server --appendonly yesdeploy:resources:limits:memory: 10G # 限制内存使用restart: alwayshealthcheck:test: ["CMD", "redis-cli","ping"]interval: 1stimeout: 3sretries: 5
5. Redis 和宿主机时间共享
有一些时候,我们会希望 Redis 和宿主机的时间保持一致,那么这个时候可以指定容器中的时区,并共享本机的时间和容器的时间:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisports:- "6379:6379"environment:- TZ=Asia/Shanghai # 设置时区volumes:# 共享时间- /etc/localtime:/etc/localtime:ro- ./redis-data:/data:rwcommand: redis-server --appendonly yesdeploy:resources:limits:memory: 10Grestart: alwayshealthcheck:test: ["CMD", "redis-cli","ping"]interval: 1stimeout: 3sretries: 5
6. Redis 配置细节调整
当我们面向特别的场景的时候,很多时候会基于 Redis 配置进行有倾向性的调整,来满足需求。假如我们要存 10 亿数据到 Redis 中,希望服务最多使用 32G 内存,可以使用下面的配置文件 redis.conf
:
# 内存配置
maxmemory 32gb
maxmemory-policy allkeys-lru# 持久化配置
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb# 连接配置
timeout 300
tcp-keepalive 60
maxclients 10000# 安全配置
requirepass yourpassword
同时,可以对我们的配置进行一些调整,让 Redis 使用配置启动:
name: cacheservices:redis:image: redis:7.4.1-alpine3.20container_name: my-redisenvironment:- TZ=Asia/Shanghaivolumes:# 共享时间- /etc/localtime:/etc/localtime:ro- ./redis.conf:/etc/redis.conf:ro- ./redis-data:/data:rwcommand: redis-server /etc/redis.confdeploy:resources:limits:memory: 32Gnetwork_mode: "host"ulimits:nofile:soft: 65535hard: 65535restart: alwayshealthcheck:test: ["CMD", "redis-cli","ping"]interval: 1stimeout: 3sretries: 5
最后,根据自己的情况调整系统配置:
# 在宿主机上执行
sudo sysctl -w net.core.somaxconn=65535
# 永久生效
echo "net.core.somaxconn=65535" >> /etc/sysctl.conf
sudo sysctl -p
四、最佳实践建议
使用 Redis 时,最需要注意的就是内存管理问题。
在使用的过程中,务必设置合理的内存上限,通常会建议设置为最大内存的 70%~80%,为系统和其他程序预留空间。
同时,内存淘汰策略的选择也很重要。如果只是使用它作为缓存,那么使用 volatile-lru 或 allkeys-lru 策略即可,如果用于重要数据存储,应该设置为不淘汰(noeviction)。当我们存储了太多数据之后,可以定期执行 MEMORY PURGE
来清理内存碎片。
当然,如果是缓存场景,设置合理的数据过期时间,避免数据长期占用内存能够节约大量的内存资源。如果查询大量数据,在能够使用 SCAN
的时候,尽量使用 SCAN
来替代 KEYS
命令。在默认的配置文件中异步删除过期数据的配置是关闭的(lazyfree-lazy-expire no
),打开之后,可以降低对服务性能的影响。
在使用 Redis 命令时要特别注意避免 O(N) 复杂度的命令,比如 KEYS、FLUSHALL、FLUSHDB 等,这些命令在数据量大时会阻塞主线程。与此同时,应该使用批量命令来提升性能,比如用 MGET/MSET
替代多次 GET/SET
操作。以及 Pipeline 可以将多个命令打包一起发送给 Redis 服务,显著提升吞吐量。如果需要保证操作的原子性,可以使用 Lua 脚本,这样能够减少多次请求的网络往返消耗。
在进行持久化存储的时候,页要根据场景来进行选择,选择合适的策略和具体的配置项目。DB 持久化可以配置多个条件触发,比如 900 秒内至少有 1 个键被修改、300 秒内至少有 10 个键被修改、60 秒内至少有 1 万个键被修改等。AOF 持久化可以将 appendfsync 设置为 everysec,这样每秒刷盘一次,在性能和安全性之间取得平衡。
在《ThinkPad + Redis:构建亿级数据毫秒级查询的平民方案》的实战部分,我们提到过一些优化策略,也可以进行参考。至于主从和多机配置,如果读多写少,除了更换技术架构之外,读写分离是经典的策略。而多副本主从复制,则是大量在线业务主要考虑的选型。
最后,存储的键名设计,可以使用统一的前缀(如业务名:对象名:id
),并尽量控制键名长度(缩写替代),避免使用特殊字符。连接 Redis 时使用连接池管理 Redis 连接,设置合理的超时时间,尽量在大量写入数据的时候使用异步处理。
总结
好了,不知不觉写了这么多,这篇文章就先到这里。
–EOF
我们有一个小小的折腾群,里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。
我们在里面会一起聊聊软硬件、HomeLab、编程上、生活里以及职场中的一些问题,偶尔也在群里不定期的分享一些技术资料。
关于交友的标准,请参考下面的文章:
致新朋友:为生活投票,不断寻找更好的朋友
当然,通过下面这篇文章添加好友时,请备注实名和公司或学校、注明来源和目的,珍惜彼此的时间 😄
关于折腾群入群的那些事
如果你觉得内容还算实用,欢迎点赞分享给你的朋友,在此谢过。
如果你想更快的看到后续内容的更新,请戳 “点赞”、“分享”、“在看” ,这些免费的鼓励将会影响后续有关内容的更新速度。
本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)
本文作者: 苏洋
创建时间: 2024年11月30日
统计字数: 18260字
阅读时间: 37分钟阅读
本文链接: https://soulteary.com/2024/11/30/basic-skills-redis-from-getting-started-to-docker-deployment.html
相关文章:
折腾基本功:Redis 从入门到 Docker 部署
前面写过了两篇 “Redis” 相关的内容,今天补一篇“基本功”内容,让后续折腾系列文章可以篇幅更短、内容更专注。 前言 在日常工作中,我们构建应用时总是离不开一些基础组件,Redis 就是其中特别常用的一个。之前我写过不少文章&…...
【C++习题】24.二分查找算法_0~n-1中缺失的数字
文章目录 题目链接:题目描述:解法C 算法代码:图解 题目链接: 剑指 Offer 53 - II. 0~n-1中缺失的数字 题目描述: 解法 哈希表: 建立一个hash表看哪个数字出现次数为0 直接遍历找结果࿱…...
分享一款内存马检测工具(附网盘链接)
DuckMemoryScan DuckMemoryScan是一款简单寻找包括不限于iis劫持,无文件木马,shellcode免杀后门的工具 功能列表 HWBP hook检测 检测线程中所有疑似被hwbp隐形挂钩内存免杀shellcode检测可疑进程检测(主要针对有逃避性质的进程[如过期签名与多各可执行区段])无文件落地木马检…...
vscode ctrl+/注释不了css
方式一.全部禁用插件排查问题. 方式二.打开首选项的json文件,注释掉setting.json,排查是哪一行配置有问题. 我的最终问题:需要将 "*.vue": "vue",改成"*.vue": "html", "files.associations": { // "*.vue": &qu…...
python数据分析之爬虫基础:爬虫介绍以及urllib详解
前言 在数据分析中,爬虫有着很大作用,可以自动爬取网页中提取的大量的数据,比如从电商网站手机商品信息,为市场分析提供数据基础。也可以补充数据集、检测动态变化等一系列作用。可以说在数据分析中有着相当大的作用!…...
洛谷 P1036 [NOIP2002 普及组] 选数 C语言
题目:https://www.luogu.com.cn/problem/P1036 题目描述 已知 nn 个整数 x1,x2,⋯ ,xn,以及 1 个整数 k(k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n4,k3,4 个…...
CSS动画案例4
目录 一、介绍二、基础布局1. HTML2.CSS 三、交互效果1.设置中间图片的动画2.设置左右两侧每行内容的起始位置与动画3.设置左右两侧第二行与第三行的动画延时的时间4.icon划入时的效果 四、结束语 一、介绍 今天我们继续来看下一个CSS动画案例,这个案例主要是图片以…...
华为云云连接+squid进行正向代理上网冲浪
1 概述 Squid是一个高性能的代理缓存服务器,主要用于缓冲Internet数据。它支持多种协议,包括FTP、gopher、HTTPS和HTTP。Squid通过一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求,这使得它在处理请求时具有较高的效率。…...
【C++】封装红黑树实现的map和set
前言 这篇博客我们将上篇博客实现的红黑树来封装成自己实现的set和map,来模拟一下库里的map和set 💓 个人主页:小张同学zkf ⏩ 文章专栏:C 若有问题 评论区见📝 🎉欢迎大家点赞👍收藏⭐文章 1.源…...
【SSM】mybatis的增删改查
目录 代理Dao方式的增删改查 1. 创建项目 $$1. 在sql.xml里增加日志代码以及user的mapper资源。 $$ 2. 在usermapper里引入接口。 $$3. 在测试类中引入以下代码,并修改其中名字。 $$ 4. 实例对象User.java里属性要与表中列严格对应。 2. 查询 1>. 查询所有 …...
macos下brew安装redis
首先确保已安装brew,接下来搜索资源,在终端输入如下命令: brew search redis 演示如下: 如上看到有redis资源,下面进行安装,执行下面的命令: brew install redis 演示效果如下: …...
鸿蒙修饰符
文章目录 一、引言1.1 什么是修饰符1.2 修饰符在鸿蒙开发中的重要性1.3 修饰符的作用机制 二、UI装饰类修饰符2.1 Styles修饰符2.1.1 基本概念和使用场景2.1.2 使用示例2.1.3 最佳实践 2.2 Extend修饰符2.2.1 基本概念2.2.2 使用示例2.2.3 Extend vs Styles 对比2.2.4 使用建议…...
【Linux】Linux2.6内核进程调度队列与调度原理
目录 一、进程管理中的部分概念二、寄存器三、进程切换四、Linux2.6内核进程调度队列与调度原理结尾 一、进程管理中的部分概念 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务&#…...
MacOS使用VSCode编写C++程序如何配置clang编译环境
前言 这段时间在练习写C和Python,用vscode这个开发工具,调试的时候遇到一些麻烦,浪费了很多时间,因此整理了这个文档。将详细的细节描述清楚,避免与我遇到同样问题的人踩坑。 1.开发环境的配置 vscode的开发环境配置…...
【Spark源码分析】规则框架- `analysis`分析阶段使用的规则
analysis分析阶段使用的规则 规则批策略规则说明SubstitutionfixedPointOptimizeUpdateFields该规则优化了 UpdateFields 表达式链,因此看起来更像优化规则。但是,在处理深嵌套模式时,UpdateFields 表达式树可能会非常复杂,导致分…...
Windows和Ubuntu系统下cmake和opencv的安装和使用
以下是在Windows和Ubuntu系统下分别安装CMake并使用C配置OpenCV实现读取图片并显示功能的详细步骤: Windows系统 1. 安装CMake 访问CMake官方网站(https://cmake.org/download/)。根据你的Windows系统版本(32位或64位ÿ…...
详解 Qt QtPDF之QPdfPageNavigator 页面跳转
文章目录 前言头文件: 自 Qt 6.4 起继承自: 属性backAvailable : const boolcurrentLocation : const QPointFcurrentPage : const intcurrentZoom : const qrealforwardAvailable : const bool 公共函数QPdfPageNavigator(QObject *parent)virtual ~QPd…...
设计模式之单例
单例可以说是设计模式中最简单的一种模式。但任何一种设计模式都是普遍经验的总结,都有值得思考的地方。所以单例也并不简单,下面让我们慢慢了解它。 单例顾名思义这个类只有一个实例。要做到这点,需要做到以下几点: (…...
笔记软件:我来、思源笔记、Obsidian、OneNote
最近wolai的会员到期了,促使我更新了一下笔记软件。 首先,wolai作为一个笔记软件,我觉得有很多做得不错的方面(否则我也不会为它付费2年了),各种功能集成得很全(公式识别这个功能我写论文的时候…...
前端入门指南:前端模块有哪些格式?分别什么情况使用
前言 在当今的前端开发中,模块化是提升代码组织性和可维护性的关键手段。随着前端技术的发展,出现了多种模块化方案,每种方案都有其独特的优势和适用场景。本文将详细探讨常见的前端模块格式,包括全局变量、IIFE、CommonJS、AMD、…...
Vue3 常用指令解析:v-bind、v-if、v-for、v-show、v-model
Vue 是一个非常强大的前端框架,提供了许多常用指令来简化模板的使用。Vue 指令以 v- 开头,用于对 DOM 元素和组件的行为进行控制。本文将介绍 Vue 中常见的五个指令:v-bind、v-if、v-for、v-show 和 v-model,并通过实例代码来演示…...
如何查看ubuntu服务器的ssh服务是否可用
你可以通过以下几种方法检查 Ubuntu 服务器上的 SSH 服务是否可用: 1. 使用 systemctl 检查 SSH 服务状态 首先,检查 SSH 服务是否正在运行: sudo systemctl status ssh如果 SSH 服务正在运行,你会看到类似以下的输出ÿ…...
redis面试复习
1.redis是单线程还是多线程 无论什么版本工作线程就是是一个,6.x高版本出现了IO多线程 单线程满足redis的串行原子,只不过IO多线程后,把输入/输出放到更多的线程里区并行,好处: 1.执行的时间更短,更快&a…...
【人工智能基础04】线性模型
文章目录 一. 基本知识1. 线性回归1.1. 基本形式1.2. 线性回归 2. 优化方法:梯度下降法2.1. 梯度下降法的直观意义2.2. 随机梯度下降法 3. 分类问题3.1. 二分类:逻辑回归-sigmoid函数3.2. 多分类问题--softmax函数 4. 岭回归与套索回归4.1. 基础概念什么…...
使用YOLO系列txt目标检测标签的滑窗切割:批量处理图像和标签的实用工具
使用YOLO系列txt目标检测标签的滑窗切割:批量处理图像和标签的实用工具 使用YOLO的TXT目标检测标签的滑窗切割:批量处理图像和标签的实用工具背景1. 代码概述2. 滑窗切割算法原理滑窗切割步骤:示例: 3. **代码实现**1. **加载标签…...
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述(一)产品简介(二)产品特点(三)产品适用场景 三、电化学传感器原理及优点(一…...
【笔记】文明、现代化与价值投资
文章目录 价值投资与理性思考资管行业特点及对从业人员的道德底线要求价值投资长期来看,各项资产的走势投资与投机 对文明的认知对文明的计量方式狩猎文明或1.0文明农业畜牧文明或2.0文明农业文明的天花板及三次冲顶农业文明中的思想革命和制度创新 科技文明或3.0文…...
排序学习整理(1)
1.排序的概念及运用 1.1概念 排序:所谓排序,就是使⼀串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作,以便更容易查找、组织或分析数据。 1.2运用 购物筛选排序 院校排名 1.3常见排序算法 2.实…...
提升分布式系统响应速度:分布式系统远程调用性能提升之道
目录 一、远程调用直接案例分析 二、并行调用 (一)核心思想 (二)并行调用的实现方式 1. 基本思路 2. 代码示例 3. 关键点说明 4.线程池配置建议 三、数据异构 (一)场景重提 (二&…...
通过MinIO+h2non/imaginary 搭建自己的阿里云OSS
安装MinIO Docker部署MinIO对象存储服务 图片访问地址:http://192.168.153.138:9000/public/su7_1.jpg 安装h2non/imaginary Docker部署h2non/imaginary 处理图片地址:http://192.168.153.138:7000/resize?urlhttp://192.168.153.138:9000/public/su…...
.NET 9 AOT的突破 - 支持老旧Win7与XP环境
引言 随着技术的不断进步,微软的.NET 框架在每次迭代中都带来了令人惊喜的新特性。在.NET 9 版本中,一个特别引人注目的亮点是 AOT( Ahead-of-Time)支持,它允许开发人员将应用程序在编译阶段就优化为能够在老旧的 Win…...
iOS与Windows间传文件
想用数据线从 windows 手提电脑传文件入 iPhone,有点迂回。 参考 [1],要在 windows 装 Apple Devices。装完、打开、插线之后会检测到手机,界面: 点左侧栏「文件」,不是就直接可以传,而是要通过某个应用传…...
ospf协议(动态路由协议)
ospf基本概念 定义 OSPF 是典型的链路状态路由协议,是目前业内使用非常广泛的 IGP 协议之一。 目前针对 IPv4 协议使用的是 OSPF Version 2 ( RFC2328 );针对 IPv6 协议使用 OSPF Version 3 ( RFC2740 )。…...
直击高频编程考点:聚焦新版综合编程能力考查汇总
目录 一、业务性编程和广度能力考查 (一)基本定义 (二)必要性分析 二、高频考查样题(编程扩展问法) 考题1: 用java 代码实现一个死锁用例,说说怎么解决死锁问题?(高…...
爬虫框架快速入门——Scrapy
适用人群:零基础、对网络爬虫有兴趣但不知道从何开始的小白。 什么是 Scrapy? Scrapy 是一个基于 Python 的网络爬虫框架,它能帮助你快速爬取网站上的数据,并将数据保存到文件或数据库中。 特点: 高效:支…...
Springfox、Swagger 和 Springdoc
Springfox、Swagger 和 Springdoc 是用于在 Spring Boot 项目中生成 API 文档的工具,但它们之间有显著的区别和演进关系: 1. Swagger 简介 Swagger 是一个开源项目,旨在为 RESTful APIs 提供交互式文档。最早由 SmartBear 开发,…...
Css、less和Sass(SCSS)的区别详解
文章目录 Css、less和Sass(SCSS)的区别详解一、引言二、CSS 简介1.1、CSS 示例 三、Less 简介2.1、Less 特性2.2、Less 示例 四、Sass(SCSS)简介3.1、Sass 特性3.2、SCSS 示例 五、总结 Css、less和Sass(SCSSÿ…...
新能源汽车充电基础设施短板问题多,如何实现高效、综合、智能化管理?
随着城市经济的发展,人民生活水平的提升,新能源汽车保有量快速增长,而日益增长的新能源汽车需求与充电基础设施建设不平衡的矛盾日益突出。由于停车泊位充电基础设施总量不足、布局待优化、利用效率低、建设运营存在短板问题等原因࿰…...
DBA面试题-1
面临失业,整理一下面试题,找下家继续搬砖 主要参考:https://www.csdn.net/?spm1001.2101.3001.4476 略有修改 一、mysql有哪些数据类型 1, 整形 tinyint,smallint,medumint,int,bigint;分别占用1字节、2字节、3字节…...
LAN,WAN,VLAN,WLAN,VPN了解笔记
局域网LAN---公司的内部网络就是局域网LAN。 提供有线连接的接口允许局域网内的设备(如台式电脑、网络打印机、网络存储设备等)通过以太网线连接到路由器并与其他局域网设备进行通信实现设备之间的数据传输和资源共享一种私有的网络相对其他网络传输速度…...
1.2 算法和算法评价
1.2.1 算法的基本概念 算法:对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。 算法的五个重要特性 “好”的算法的五个目标 1.2.2 算法效率的度量 一、时间复杂度 算法的时间复杂度是指一个算法每行…...
各大常见编程语言应用领域
不同编程语言因其特性和设计目标而适用于不同的应用领域。以下是一些常见编程语言及其主要应用领域: 1. Python 数据科学与人工智能:Python 在数据分析、机器学习、深度学习等领域广泛使用,因其丰富的库(如 NumPy、Pandas、Tens…...
【FFT】数据点数是否一定为2的n次方?不补零会如何处理?
一般来说,FFT的数据点个数为以2为基数的整数次方(采用以2为基的FFT算法,可以提升运算性能),但是并没有要求FFT的数据点个数一定为2的n次方。 因此针对数据点数不是以2为基数的整数次方,有两种处理方法&…...
shell脚本小练习#003:查找并拷贝目录
实例1: # 从当前执行脚本的路径位置开始向上搜索一个名为sourceProject目录名 # 并将这个文件目录的路径名称打印出来#!/bin/bashfunction find_dir() {local current_dir$PWDwhile [[ $current_dir ! "/" ]]; doif [[ -d "${current_dir}/sourcePr…...
frp内网穿透
目录 1,准备公网服务器 2,下载安装frp服务端 3,服务端安装 2)编辑服务端配置文件fprs.toml 3)配置启动服务 4)启动服务 5 )设置开机启动服务 6)查看服务启动状态 3,…...
Android电视项目焦点跨层级流转
1. 背景 在智家电视项目中,主要操作方式不是触摸,而是遥控器,通过Focus进行移动,确定点击进行的交互,所以在电视项目中焦点、选中、确定、返回这几个交互比较重要。由于电视屏比较大,在一些复杂页面中会存…...
时频转换 | Matlab基于S变换S-transform一维数据转二维图像方法
目录 基本介绍程序设计参考资料获取方式基本介绍 时频转换 | Matlab基于S变换S-transform一维数据转二维图像方法 程序设计 clear clc % close all load x.mat % 导入数据 x =...
转载 为nautilus安装rabbitvcs
# 添加 rabbitvcs 的 ppa 源 sudo add-apt-repository ppa:rabbitvcs/ppa sudo apt update # 安装 rabbitvcs sudo apt install rabbitvcs-cli rabbitvcs-core rabbitvcs-gedit rabbitvcs-nautilus # 注销后重新登录,右键即可使用 # 解决 RabbitVCS 无法自动保存…...
OpenCV 模板匹配全解析:从单模板到多模板的实战指南
简介:本文深入探讨 OpenCV 中的模板匹配技术。详细介绍构建输入图像与模板图像的步骤,包括读取、截取、滤波与存储等操作。剖析 cv2.matchTemplate 语法及其参数含义,阐述不同匹配方法下结果值的意义。同时讲解 cv2.minMaxLoc 语法࿰…...
手机控制载货汽车一键启动无钥匙进入广泛应用
移动管家载货汽车一键启动无钥匙进入手机控车系统, 该系统广泛应用于物流运输、工程作业等货车场景,为车主提供了高效、便捷的启动和熄火解决方案,体现了科技进步对物流行业的积极影响 核心功能:简化启动流程,提…...