高并发核心技术Redis系列(八)--------企业级解决方案(下)

简介: 通过setnx上锁由于setnx只有不存在该key的时候,可以设置成功,并返回1,否则设置失败,并返回0

7.1 设置锁和过期时间

1. 通过setnx上锁

由于setnx只有不存在该key的时候,可以设置成功,并返回1,否则设置失败,并返回0。

 

setnx lock A //获取锁,并对lock上锁
setnx lock B //其他服务器试图获取锁时,失败

2. 通过del释放锁

del lock //释放锁,此时其他服务器可以获取锁

2345_image_file_copy_258.jpg

3. 如果锁一直不释放,需要增加过期时间,防止资源浪费。

2345_image_file_copy_259.jpg

expire lock 10 //给锁添加个过期时间

4. 如果在上锁之后,设置过期时间之前,服务器异常,就无法设置过期时间,可以在上锁的同时设置过期时间。

set lock 1 nx ex 10 //上锁的同时设置过期时间

7.2 防止误删

2345_image_file_copy_260.jpg

避免误删情况出现,可以在加锁过程中添加一个加锁的唯一id,通过跟该id对比,阻止误删的情况出现。

//连接到自己的redis服务器
    Jedis jedis = new Jedis("192.168.56.31",6379);
    UUID uuid = UUID.randomUUID();
    jedis.setnx("lock",uuid.toString());
    /*
    执行业务代码
* */
    //释放锁的时候,通过uuid对比下是不是自己加的锁
    String lockuuid = jedis.get("lock");
    if (uuid.toString().equals(lockuuid))
   {
      //如果是自己加的锁,则释放
      jedis.del("lock");
   }

7.3 保证删除原子性

2345_image_file_copy_261.jpg 

Lua脚本

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Redis中引入lua的优势:

  • 减少网络开销:多个请求通过脚本一次发送,减少网络延迟
  • 原子操作:将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务
  • 复用:客户端发送的脚本永久存在redis中,其他客户端可以复用脚本
  • 可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互

lua进行比较uuid,对比成功后删除键值对的代码:

if redis.call('get', KEYS[1]) == ARGV[1]
  then
  return redis.call('del', KEYS[1])
else
  return 0
end

if 中的比较如果是true , 那么 执行 del 并返回del结果;如果 if 结果为false 直接返回 0 。

其中的KEYS[1] , ARGV[1] 是参数,我们只调用 jedis 执行脚本的时候,传递这两个参数就可以了。

通过jedis执行lua脚本

//连接到自己的redis服务器
Jedis jedis = new Jedis("192.168.56.31",6379);
UUID uuid = UUID.randomUUID();
jedis.setnx("lock",uuid.toString());
String lockuuid = jedis.get("lock");
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del',KEYS[1]) else return 0 end";// lua脚本,用来释放分布式锁
//第一个参数是lua脚本,第二个参数是需要判断的key,第三个参数是key所对应的value
jedis.eval(luaScript,Arrays.asList("lock"),Arrays.asList(lockuuid));

通过lua脚本进行比对删除,保证原子性操作,防止出现问题。

8 消息队列

8.1 List消息队列

List 底层的实现就是一个「链表」,在头部和尾部操作元素,时间复杂度都是 O(1),这意味着它非常符合消息队列的模型。

2345_image_file_copy_262.jpg

生产者使用 lpush发布消息

lpush queue msg1
lpush queue msg2

2345_image_file_copy_263.jpg

消费者这一侧,使用 rpop拉取消息:

rpop queue
rpop queue

2345_image_file_copy_264.jpg

当队列中已经没有消息了,消费者在执行 RPOP 时,会返回 NULL。

一般编写消费者逻辑时,通过一个“死循环”实现,如果此时队列为空,那消费者依旧会频繁拉取消息,造成资源浪费。

while(true)
{
String msg = jedis.rpop("queue");
}

Redis 提供「阻塞式」拉取消息的命令:brpop / blpop,这里的 B 指的是阻塞(Block)。

2345_image_file_copy_265.jpg

brpop key timeout:移除并返回最后一个值,同时需要传入一个超时时间(timeout),如果设置为0,则表示不设置超时,直到有新消息才返回,否则会在指定的超时时间后返回 NULL。

brpop queue 0//获取queue最后一个值,如果没有值,则一直等待

缺点:

不支持重复消费:消费者拉取消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费,

即不支持多个消费者消费同一批数据

消息丢失:消费者拉取到消息后,如果发生异常宕机,那这条消息就丢失了

消费者(Customer):

Jedis jedis = new Jedis("192.168.56.31",6379);
System.out.println("开始监听");
while (true)
{
  List<String> msg =  jedis.brpop(0,"queue");
  System.out.println("接受消息:");
  //一般来说 一条消息分为两部分,第一部分是list的key,第二部分为value
  for (String m : msg)
 {
    System.out.print(m + " ");
 }
}

生产者(Producer):

Jedis jedis = new Jedis("192.168.56.31",6379);
Scanner sc = new Scanner(System.in);
while (true)
{
  System.out.println("输入发送的消息:");
  String msg = sc.next();
  jedis.lpush("queue",msg);
}

8.2 发布/订阅消息队列

Redis 提供了 PUBLISH / SUBSCRIBE 命令,来完成发布、订阅的操作。

2345_image_file_copy_266.jpg

多个消费者,同时消费同一批数据。

//多个客户端同时订阅queue频道
SUBSCRIBE queue

通过生产者,发布一条消息。

PUBLISH queue msg1

客户端接收到消息

SUBSCRIBE queue
// 收到新消息
1) "message"
2) "queue"
3) "msg1"

使用 Pub/Sub 这种方案,既支持阻塞式拉取消息,还很好地满足了多组消费者,消费同一批数据的业务需求。

但是该方案会引起消息丢失

  • 消费者下线
  • Redis 宕机

消费者:通过jedis订阅频道,需要一个JedisPubSub子类对象,并重写onMessage方法用于接受消息

public class Customer extends JedisPubSub {
  public void onMessage(String channel, String message) {
    System.out.println("接收到消息:" + channel + ":" + message);
 }
  public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.56.31",6379);
    //通过jedis订阅频道,需要一个JedisPubSub子类对象,并重写onMessage方法用于接受消息
    jedis.subscribe(new Customer(),"queue");
 }
}

生产者:

Jedis jedis = new Jedis("192.168.56.31",6379); //第一个参数是ip地址,第二个参数是端口
Scanner sc = new Scanner(System.in);
while (true)
{
 System.out.println("输入发送的消息:");
 String msg = sc.next();
 jedis.publish("queue",msg);
}

9 数据一致性解决方案

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存
(Redis)和数据库(MySQL)间的数据一致性问题。

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。

例:

1. 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

2. 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情

况。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

2345_image_file_copy_268.jpg

9.1 延时双删策略

1. 先删除缓存。

2. 再写数据库。

3. 休眠500毫秒;

4. 再次删除缓存。

需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。

缺点:结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

10 企业级持久化解决方案

在企业中不要仅仅使用RDB,因为那样会导致丢失很多数据。

也不要仅仅使用AOF,因为那样有两个问题:

通过AOF做冷备,没有RDB做冷备,来的恢复速度更快;

RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。

综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择;

用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。

如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite;如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting。

如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite。

10.1 RDB的生成策略

如果希望能确保RDB最多丢1分钟的数据,那么尽量就是每隔1分钟都生成一个快照。不过到底是10000条执行一次RDB,还是1000条执行一次RDB,这个根据需要根据自己的应用和业务的数据量来确定。

10.2 AOF的生成策略

AOF一定要打开,fsync方式选择everysec。一般可能会调整的参数可能就是下面俩参数了

auto-aof-rewrite-percentage 100

就是当前AOF大小膨胀到超过上次100%,上次的两倍。

就是当前AOF大小膨胀到超过上次100%,上次的两倍。

根据自己的数据量来定,16mb,32mb。

10.3 企业级的数据备份方案

RDB非常适合做冷备,每次生成之后,就不会再有修改。

数据备份方案:

  1. 写定时调度脚本去做数据备份。

  2. 每小时都copy一份rdb的备份,到一个目录中去,仅仅保留最近48小时的备份。

  3. 每天都保留一份当日的rdb的备份,到一个目录中去,仅仅保留最近1个月的备份。

  4. 每次copy备份的时候,都把太旧的备份给删了。

  5. 每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去。

目录
相关文章
|
11月前
|
缓存 关系型数据库 MySQL
在MySQL中处理高并发和负载峰值的关键技术与策略
采用上述策略和技术时,每个环节都要进行细致的规划和测试,确保数据库系统既能满足高并发的要求,又要保持足够的灵活性来应对各种突发的流量峰值。实施时,合理评估和测试改动对系统性能的影响,避免单一措施可能引起的连锁反应。持续的系统监控和分析将对维护系统稳定性和进行未来规划提供重要信息。
488 15
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
3386 7
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
970 3
|
11月前
|
缓存 NoSQL Java
Java 项目实操高并发电商系统核心模块实现从基础到进阶的长尾技术要点详解 Java 项目实操
本项目实战实现高并发电商系统核心模块,涵盖商品、订单与库存服务。采用Spring Boot 3、Redis 7、RabbitMQ等最新技术栈,通过秒杀场景解决库存超卖、限流熔断及分布式事务难题。结合多级缓存优化查询性能,提升系统稳定性与吞吐能力,适用于Java微服务开发进阶学习。
648 0
|
缓存 NoSQL 架构师
Redis批量查询的四种技巧,应对高并发场景的利器!
在高并发场景下,巧妙地利用缓存批量查询技巧能够显著提高系统性能。 在笔者看来,熟练掌握细粒度的缓存使用是每位架构师必备的技能。因此,在本文中,我们将深入探讨 Redis 中批量查询的一些技巧,希望能够给你带来一些启发。
Redis批量查询的四种技巧,应对高并发场景的利器!
|
人工智能 缓存 NoSQL
高并发秒杀系统设计:关键技术解析与典型陷阱规避
在电商、在线票务等场景中,高并发秒杀活动对系统性能和稳定性提出极大挑战。海量请求可能导致服务器资源耗尽、数据库锁争用及库存超卖等问题。通过飞算JavaAI生成的Redis + Lua分布式锁代码,可有效解决高并发下的锁问题,提升系统QPS达70%,同时避免缓存击穿与库存超卖。相较传统写法,AI优化代码显著提高性能与响应速度,为高并发系统开发提供高效解决方案。
|
存储 缓存 监控
社交软件红包技术解密(四):微信红包系统是如何应对高并发的
本文将为读者介绍微信百亿级别红包背后的高并发设计实践,内容包括微信红包系统的技术难点、解决高并发问题通常使用的方案,以及微信红包系统的所采用高并发解决方案。
488 13
|
存储 缓存 NoSQL
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
471 1
|
缓存 NoSQL 关系型数据库
云端问道21期实操教学-应对高并发,利用云数据库 Tair(兼容 Redis®)缓存实现极速响应
本文介绍了如何通过云端问道21期实操教学,利用云数据库 Tair(兼容 Redis®)缓存实现高并发场景下的极速响应。主要内容分为四部分:方案概览、部署准备、一键部署和完成及清理。方案概览中,展示了如何使用 Redis 提升业务性能,降低响应时间;部署准备介绍了账号注册与充值步骤;一键部署详细讲解了创建 ECS、RDS 和 Redis 实例的过程;最后,通过对比测试验证了 Redis 缓存的有效性,并指导用户清理资源以避免额外费用。
386 1
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
386 5