分布式缓存服务 DCS
分布式缓存服务 DCS
- 最新动态
- 功能总览
- 服务公告
- 产品介绍
- 计费说明
- 快速入门
- 用户指南
- 最佳实践
- API参考
- SDK参考
-
常见问题
- 实例类型/版本
- 实例特性
- 安全性
-
客户端和网络连接
- DCS实例支持公网访问吗?
- Redis连接失败问题排查和解决
- DCS实例是否支持跨VPC访问?
- Redis公网访问所需弹性IP是否收费?
- Redis连接时报错:“(error) NOAUTH Authentication required”。
- 客户Http的Server端关闭导致Redis访问失败
- 客户端出现概率性超时错误
- 使用Jedis连接池报错如何处理?
- 如何使用Redis-desktop-manager访问Redis实例?
- 使用SpringCloud时出现ERR Unsupported CONFIG subcommand怎么办?
- 客户端无法使用域名连接DCS缓存实例时如何处理?
- 本地环境是否可以连接缓存实例?
- 使用Redis实例的发布订阅(pubsub)有哪些注意事项?
- Redis 3.0实例公网开关被关闭是什么原因?
- 使用短连接访问Redis出现“Cannot assign requested address”错误
- 连接池选择及Jedis连接池参数配置建议
- 如何解决Lettuce 6.x版本客户端使用DCS实例兼容性问题?
- 应该选择域名还是IP地址连接Redis实例?
- 主备实例的只读地址是连接到主节点还是备节点?
-
Redis使用
- 是否支持CPU架构的变更?
- 实例是否支持变更可用区
- Redis实例能否修改VPC和子网?
- 实例是否支持自定义或修改端口?
- 实例是否支持修改访问地址?
- 实例无法删除是什么原因?
- 集群实例启动时间过长是什么原因?
- 使用redis_exporter出错怎么办?
- 什么是预留内存,如何配置预留内存?
- 创建的缓存实例为什么可使用内存比实例规格少一些?
- Redis 3.0 Proxy集群不支持redisson分布式锁的原因
- DCS Redis有没有后台管理软件?
- DCS缓存实例的数据被删除后,能否找回?
- 为什么实例实际可用内存比申请规格小而且已使用内存不为0?
- 如何查看Redis内存占用量
- Cluster集群实例容量和性能未达到瓶颈,但某个分片容量或性能已过载是什么原因?
- 访问Redis报OOM错误提示
- 不同编程语言如何使用Cluster集群客户端
- 使用Cluster的Redis集群时建议配置合理的超时时间
- 读取redis数据报超时错误
- hashtag的原理、规则及用法示例
- Redis key丢失是什么原因
- 重启实例后缓存数据会保留吗?
- 如何确认实例是单DB还是多DB
- Proxy集群开启多DB的使用限制及操作方式
- 如何创建多DB的Proxy集群实例?
- 扩容缩容与实例升级
-
数据备份/导出/迁移
- DCS实例是否兼容低版本Redis迁移到高版本
- 不同类型的操作系统间进行数据传递和操作,需要注意什么?
- 源Redis使用了多DB,能否迁移数据到集群实例?
- 源Redis迁移到集群实例中有哪些限制和注意事项?
- 在线迁移需要注意哪些?
- 在线迁移能否做到完全不中断业务?
- 在线迁移实例源端报“Disconnecting timedout slave”和“overcoming of output buffer limits”
- 如何导出Redis实例数据?
- 使用Rump工具迁移数据,命令执行后无报错,但Redis容量无变化
- 是否支持控制台导出RDB格式的Redis备份文件?
- 缓存实例备份文件如何存放?备份文件的数量是否有限制?
- Redis在线数据迁移是迁移整个实例数据么?
- AOF文件在什么情况下会被重写
- Redis迁移失败有哪些常见原因?
- 一个数据迁移能迁移到多个目标实例么?
- 怎么放通SYNC和PSYNC命令?
- 迁移或导入备份数据时,相同的Key会被覆盖吗?
- Cluster集群实例使用内置key且跨slot的Lua脚本时迁移失败
- 迁移故障处理
- 数据迁移失败问题排查
- Memcached如何迁移?
- 是否支持Memcached和Redis之间实例数据的迁移?
- 大Key/热Key分析/过期Key扫描
- Redis命令
- 监控告警
- 主备倒换
- 创建实例和权限
- Memcached使用
- 故障排除
- 视频帮助
- 文档下载
- 通用参考
链接复制成功!
使用DCS实现电商秒杀功能
方案概述
应用场景
电商秒杀是一种网上竞拍活动,通常商家会在平台释放少量稀缺商品,吸引大量客户,平台会收到平时数十倍甚至上百倍的下单请求,但是只有少数客户可以下单成功。电商秒杀系统的分流过程可以分为以下几个步骤:
- 用户请求进入系统:当用户发起秒杀请求时,请求会首先进入负载均衡服务器。
- 负载均衡:负载均衡服务器会根据一定的算法将请求分发给后端多台服务器,以达到负载均衡的目的。负载均衡算法可以采用轮询、随机、最少连接数等方式。
- 业务逻辑处理:后端服务器接收到请求后,进行业务逻辑处理,并根据请求的商品数量、用户身份等信息进行校验。
- 库存扣减:如果库存充足,后端服务器会进行库存扣减操作,并生成订单信息,返回给用户秒杀成功的信息;如果库存不足,则返回给用户秒杀失败的信息。
- 订单处理:后端服务器会将订单信息保存到数据库中,并进行异步处理,例如发送消息通知用户订单状态。
- 缓存更新:后端服务器会更新缓存中的商品库存信息,以便处理下一次秒杀请求。
秒杀过程中多次访问数据库,下单通常是利用行级锁进行访问限制,抢到锁才能查询数据库和下单。但是秒杀时的大量订单请求,会导致数据库访问阻塞。
解决方案
利用分布式缓存服务(DCS)的Redis作为数据库的缓存,客户端访问Redis进行库存查询和下单操作,具有以下优势:
- Redis提供很高的读写速度和并发性能,可以满足电商秒杀系统高并发的需求。
- Redis支持主备、集群等高可用架构, 支持数据持久化,即使服务器宕机也可以恢复数据。
- Redis支持事务和原子性操作,可以保证秒杀操作的一致性和正确性。
- 利用Redis缓存商品和用户信息,减轻数据库的压力,提高系统的性能。
本篇文档示例中,用Redis中的hash结构表示商品信息。total表示总数,booked表示下单数,remain表示剩余商品数量。
“product”: { “total”: 200 “booked”:0 “remain”:200 }
扣量时,服务器通过请求Redis获取下单资格。Redis为单线程模型,lua可以保证多个命令的原子性。通过如下lua脚本完成扣量。
local n = tonumber(ARGV[1]) if not n or n == 0 then return 0 end local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\"); local booked = tonumber(vals[2]) local remain = tonumber(vals[3]) if booked <= remain then redis.call(\"HINCRBY\", KEYS[1], \"booked\", n) redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n) return n; end return 0
前提条件
- 已创建DCS缓存实例,且状态为“运行中”。
- 客户端所在服务器与DCS缓存实例网络互通:
- 客户端与Redis实例所在VPC为同一VPC
- 客户端与Redis实例所在VPC为相同region下的不同VPC
如果客户端与Redis实例不在相同VPC中,可以通过建立VPC对等连接方式连通网络,具体请参考:缓存实例是否支持跨VPC访问?。
- 客户端与Redis实例所在VPC不在相同region
如果客户端服务器和Redis实例不在同一region,仅支持通过云专线打通网络,请参考云专线。
- 公网访问
客户端公网访问Redis 4.0/5.0/6.0实例时,需要开启实例公网访问开关,具体请参考开启Redis 4.0/5.0/6.0公网访问并获取公网访问地址。
- 客户端所在服务器已安装JDK1.8以上版本和Intellij IDEA开发工具,下载jedis客户端(点此处下载jar包)。
本文档下载的开发工具和客户端仅为示例,您可以选择其它类型的工具和客户端。
实施步骤
- 在服务器上运行Intellij IDEA,创建一个MAVEN工程,为示例代码创建一个SecondsKill.java文件,pom.xml文件中引用Jedis:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.0</version> </dependency>
- 编译并运行以下demo,该示例以Java语言实现。
示例中的Redis连接地址和端口需要根据实际获取的值进行修改。
package com.huawei.demo; import java.util.ArrayList; import java.util.*; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class SecondsKill { private static void InitProduct(Jedis jedis) { jedis.hset("product", "total", "200"); jedis.hset("product", "booked", "0"); jedis.hset("product","remain", "200"); } private static String LoadLuaScript(Jedis jedis) { String lua = "local n = tonumber(ARGV[1])\n" + "if not n or n == 0 then\n" + "return 0\n" + "end\n" + "local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\");\n" + "local booked = tonumber(vals[2])\n" + "local remain = tonumber(vals[3])\n" + "if booked <= remain then\n" + "redis.call(\"HINCRBY\", KEYS[1], \"booked\", n)\n" + "redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n)\n" + "return n;\n" + "end\n" + "return 0"; String scriptLoad = jedis.scriptLoad(lua); return scriptLoad; } public static void main(String[] args) { JedisPoolConfig config = new JedisPoolConfig(); // 最大连接数 config.setMaxTotal(30); // 最大连接空闲数 config.setMaxIdle(2); // 连接Redis,Redis实例连接地址和端口需替换为实际获取的值 JedisPool pool = new JedisPool(config, "127.0.0.1", 6379); Jedis jedis = null; try { jedis = pool.getResource(); jedis.auth("password"); //配置实例的连接密码,免密访问的实例无需填写 System.out.println(jedis); // 初始化产品信息 InitProduct(jedis); // 存入lua脚本 String scriptLoad = LoadLuaScript(jedis); List<String> keys = new ArrayList<>(); List<String> vals = new ArrayList<>(); keys.add("product"); //下单15个 int num = 15; vals.add(String.valueOf(num)); //执行lua脚本 jedis.evalsha(scriptLoad, keys, vals); System.out.println("total:"+jedis.hget("product", "total")+"\n"+"booked:"+jedis.hget("product", "booked")+"\n"+"remain:"+jedis.hget("product","remain")); } catch (Exception ex) { ex.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } }
执行结果:
total:200 booked:15 remain:185
操作视频
父主题: 业务应用