- 最新动态
- 功能总览
- 服务公告
- 产品介绍
- 计费说明
- 快速入门
- 用户指南
- 最佳实践
- 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实现热点资源顺序访问
方案概述
应用场景
在传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或synchronized)进行互斥控制。这种Java提供的原生锁机制可以保证在同一个Java虚拟机进程内的多个线程同步执行,避免出现无序现象。
但在互联网场景,例如在商品秒杀过程中,随着客户业务量上升,整个系统并发飙升,需要多台机器并发运行。例如当两个用户同时发起的请求分别落在两个不同的机器上时,虽然这两个请求可以同时执行,但是因为两个机器运行在两个不同的Java虚拟机中,因此每个机器加的锁不是同一个锁,而不同的锁只对属于自己Java虚拟机中的线程有效,对其他Java虚拟机的线程无效。此时,Java提供的原生锁机制在多机部署场景下就会失效,出现库存超卖的现象。
解决方案
基于上述场景,需要保证两台机器加的锁是同一个锁,用加锁的方式对某种资源进行顺序访问控制。这就需要分布式锁登场了。
分布式锁的思路是:在整个系统提供一个全局的、唯一的分配锁的“东西”,当每个系统需要加锁时,都向其获取一把锁,使不同的系统获取到的内容可以认为是同一把锁。
当前分布式加锁主要有三种方式:(磁盘)数据库、缓存数据库、Zookeeper。
使用DCS服务中Redis缓存实例实现分布式加锁,有几大优势:
- 加锁操作简单,使用SET、GET、DEL等几条简单命令即可实现锁的获取和释放。
- 性能优越,缓存数据的读写优于磁盘数据库与Zookeeper。
- 可靠性强,DCS有主备和集群实例类型,避免单点故障。
对分布式应用加锁,能够避免出现库存超卖及无序访问等现象。本实践介绍如何使用Redis对分布式应用加锁。
前提条件
- 已创建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以上版本和开发工具(本文档以安装Eclipse为例),下载jedis客户端(单击此处直接下载jar包)。
本文档下载的开发工具和客户端仅为示例,您可以选择其它类型的工具和客户端。
实施步骤
- 在服务器上运行Eclipse,创建一个java工程,为示例代码分别创建一个分布式锁实现类DistributedLock.java和测试类CaseTest.java,并将jedis客户端作为library引用到工程中。
创建的分布式锁实现类DistributedLock.java内容示例如下:
package dcsDemo01; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.params.SetParams; public class DistributedLock { // Redis实例连接地址和端口,需替换为实际获取的值 private final String host = "192.168.0.220"; private final int port = 6379; private static final String SUCCESS = "OK"; public DistributedLock(){} /* * @param lockName 锁名 * @param timeout 获取锁的超时时间 * @param lockTimeout 锁的有效时间 * @return 锁的标识 */ public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) { String ret = null; Jedis jedisClient = new Jedis(host, port); try { // Redis实例连接密码,需替换为实际获取的值 String authMsg = jedisClient.auth("passwd"); if (!SUCCESS.equals(authMsg)) { System.out.println("AUTH FAILED: " + authMsg); } String identifier = UUID.randomUUID().toString(); String lockKey = "DLock:" + lockName; long end = System.currentTimeMillis() + timeout; SetParams setParams = new SetParams(); setParams.nx().px(lockTimeout); while(System.currentTimeMillis() < end) { String result = jedisClient.set(lockKey, identifier, setParams); if(SUCCESS.equals(result)) { ret = identifier; break; } try { Thread.sleep(2); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (Exception e) { e.printStackTrace(); }finally { jedisClient.quit(); jedisClient.close(); } return ret; } /* * @param lockName 锁名 * @param identifier 锁的标识 */ public void releaseLock(String lockName, String identifier) { Jedis jedisClient = new Jedis(host, port); try { String authMsg = jedisClient.auth("passwd"); if (!SUCCESS.equals(authMsg)) { System.out.println("AUTH FAILED: " + authMsg); } String lockKey = "DLock:" + lockName; if(identifier.equals(jedisClient.get(lockKey))) { jedisClient.del(lockKey); } } catch (Exception e) { e.printStackTrace(); }finally { jedisClient.quit(); jedisClient.close(); } } }
须知:
该代码实现仅展示使用DCS服务进行加锁访问的便捷性。具体技术实现需要考虑死锁、锁的检查等情况,这里不做详细说明。
假设20个线程对10台mate10手机进行抢购,创建的测试类CaseTest.java类内容示例如下:package dcsDemo01; import java.util.UUID; public class CaseTest { public static void main(String[] args) { ServiceOrder service = new ServiceOrder(); for (int i = 0; i < 20; i++) { ThreadBuy client = new ThreadBuy(service); client.start(); } } } class ServiceOrder { private final int MAX = 10; DistributedLock DLock = new DistributedLock(); int n = 10; public void handleOder() { String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName(); String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000); System.out.println("正在为用户:" + userName + " 处理订单"); if(n > 0) { int num = MAX - n + 1; System.out.println("用户:"+ userName + "购买第" + num + "台,剩余" + (--n) + "台"); }else { System.out.println("用户:"+ userName + "无法购买,已售罄!"); } DLock.releaseLock("Huawei Mate 10", identifier); } } class ThreadBuy extends Thread { private ServiceOrder service; public ThreadBuy(ServiceOrder service) { this.service = service; } @Override public void run() { service.handleOder(); } }
- 将DCS缓存实例的连接地址、端口以及连接密码配置到分布式锁实现类DistributedLock.java示例代码文件中。
在DistributedLock.java中,host及port配置为实例的连接地址及端口号,在getLockWithTimeout、releaseLock方法中需配置passwd值为实例访问密码。
- 将测试类CaseTest中加锁部分注释掉,变成无锁情况,示例如下:
//测试类中注释两行用于加锁的代码: public void handleOder() { String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName(); //加锁代码 //String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000); System.out.println("正在为用户:" + userName + " 处理订单"); if(n > 0) { int num = MAX - n + 1; System.out.println("用户:"+ userName + "购买第" + num + "台,剩余" + (--n) + "台"); }else { System.out.println("用户:"+ userName + "无法购买,已售罄!"); } //加锁代码 //DLock.releaseLock("Huawei Mate 10", identifier); }
- 编译及运行无锁的类,运行结果是抢购无序的,如下:
正在为用户:e04934ddThread-5 处理订单 正在为用户:a4554180Thread-0 处理订单 用户:a4554180Thread-0购买第2台,剩余8台 正在为用户:b58eb811Thread-10 处理订单 用户:b58eb811Thread-10购买第3台,剩余7台 正在为用户:e8391c0eThread-19 处理订单 正在为用户:21fd133aThread-13 处理订单 正在为用户:1dd04ff4Thread-6 处理订单 用户:1dd04ff4Thread-6购买第6台,剩余4台 正在为用户:e5977112Thread-3 处理订单 正在为用户:4d7a8a2bThread-4 处理订单 用户:e5977112Thread-3购买第7台,剩余3台 正在为用户:18967410Thread-15 处理订单 用户:18967410Thread-15购买第9台,剩余1台 正在为用户:e4f51568Thread-14 处理订单 用户:21fd133aThread-13购买第5台,剩余5台 用户:e8391c0eThread-19购买第4台,剩余6台 正在为用户:d895d3f1Thread-12 处理订单 用户:d895d3f1Thread-12无法购买,已售罄! 正在为用户:7b8d2526Thread-11 处理订单 用户:7b8d2526Thread-11无法购买,已售罄! 正在为用户:d7ca1779Thread-8 处理订单 用户:d7ca1779Thread-8无法购买,已售罄! 正在为用户:74fca0ecThread-1 处理订单 用户:74fca0ecThread-1无法购买,已售罄! 用户:e04934ddThread-5购买第1台,剩余9台 用户:e4f51568Thread-14购买第10台,剩余0台 正在为用户:aae76a83Thread-7 处理订单 用户:aae76a83Thread-7无法购买,已售罄! 正在为用户:c638d2cfThread-2 处理订单 用户:c638d2cfThread-2无法购买,已售罄! 正在为用户:2de29a4eThread-17 处理订单 用户:2de29a4eThread-17无法购买,已售罄! 正在为用户:40a46ba0Thread-18 处理订单 用户:40a46ba0Thread-18无法购买,已售罄! 正在为用户:211fd9c7Thread-9 处理订单 用户:211fd9c7Thread-9无法购买,已售罄! 正在为用户:911b83fcThread-16 处理订单 用户:911b83fcThread-16无法购买,已售罄! 用户:4d7a8a2bThread-4购买第8台,剩余2台
- 取消测试类CaseTest中注释的加锁内容,编译并运行得到有序的抢购结果如下:
正在为用户:eee56fb7Thread-16 处理订单 用户:eee56fb7Thread-16购买第1台,剩余9台 正在为用户:d6521816Thread-2 处理订单 用户:d6521816Thread-2购买第2台,剩余8台 正在为用户:d7b3b983Thread-19 处理订单 用户:d7b3b983Thread-19购买第3台,剩余7台 正在为用户:36a6b97aThread-15 处理订单 用户:36a6b97aThread-15购买第4台,剩余6台 正在为用户:9a973456Thread-1 处理订单 用户:9a973456Thread-1购买第5台,剩余5台 正在为用户:03f1de9aThread-14 处理订单 用户:03f1de9aThread-14购买第6台,剩余4台 正在为用户:2c315ee6Thread-11 处理订单 用户:2c315ee6Thread-11购买第7台,剩余3台 正在为用户:2b03b7c0Thread-12 处理订单 用户:2b03b7c0Thread-12购买第8台,剩余2台 正在为用户:75f25749Thread-0 处理订单 用户:75f25749Thread-0购买第9台,剩余1台 正在为用户:26c71db5Thread-18 处理订单 用户:26c71db5Thread-18购买第10台,剩余0台 正在为用户:c32654dbThread-17 处理订单 用户:c32654dbThread-17无法购买,已售罄! 正在为用户:df94370aThread-7 处理订单 用户:df94370aThread-7无法购买,已售罄! 正在为用户:0af94cddThread-5 处理订单 用户:0af94cddThread-5无法购买,已售罄! 正在为用户:e52428a4Thread-13 处理订单 用户:e52428a4Thread-13无法购买,已售罄! 正在为用户:46f91208Thread-10 处理订单 用户:46f91208Thread-10无法购买,已售罄! 正在为用户:e0ca87bbThread-9 处理订单 用户:e0ca87bbThread-9无法购买,已售罄! 正在为用户:f385af9aThread-8 处理订单 用户:f385af9aThread-8无法购买,已售罄! 正在为用户:46c5f498Thread-6 处理订单 用户:46c5f498Thread-6无法购买,已售罄! 正在为用户:935e0f50Thread-3 处理订单 用户:935e0f50Thread-3无法购买,已售罄! 正在为用户:d3eaae29Thread-4 处理订单 用户:d3eaae29Thread-4无法购买,已售罄!