使用Redis解决高并发方案及思路解读

 更新时间:2023年03月31日 09:08:06   作者:WINGZINGLIU  
这篇文章主要介绍了使用Redis解决高并发方案及思路,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

NoSQL

Not Only SQL的简称。NoSQL是解决传统的RDBMS在应对某些问题时比较乏力而提出的。

即非关系型数据库,它们不保证关系数据的ACID特性,数据之间一般没有关联,在扩展上就非常容易实现,并且拥有较高的性能。

Redis

redis是nosql的典型代表,也是目前互联网公司的必用技术。

redis是键值(Key-Value)存储数据库,主要会使用到哈希表。大多数时候是直接以缓存的形式被使用,使得请求不直接访问到磁盘,所以效率方面是很不错的,完全能满足中小型企业的使用需求。

常用数据类型

  • 字符串string
  • 散列hash
  • 列表list
  • 集合sets
  • 有序集合sort set

使用频率上string和hash会高一些,各个类型有各自的操作命令,无非增删改查,具体的命令后面我会整理一份。

痛点

web应用在众多请求同时发生时,可能会导致数据读取、存储上出现错误,即发生脏读、脏数据生成。

在分布式项目下,会出现更多的问题。

思路

并发时,本质其实就是多个请求同时进来了,没办法正确的去进行处理。

可以将所有的请求放在 一个队列,让请求们按照一个顺序,挨个进来执行业务逻辑。目前成熟的解决方案就是使用消息队列,下次我会整理一篇消息队列处理高并发的;

还有一个方法是直接将并行转为串行,Java提供了synchronized,即同步,不过这个在效率要求比较苛刻的地方 或者 分布式项目下还是不太合适的方案,这里就引出了使用redis来实现分布式锁,从而解决并发问题。

分布式锁

在分布式项目中,使用一个唯一、通用、效率高的标识,来表示上锁和解锁。

redis实现起来很简单,即对一个key是否存在来表示是否上锁、是否解锁。

以string类型举例:

Integer stock = goodsMapper.getStock();
if (stock > 0) {
? ? stock =- 1;
? ? goodsMapper.updateStock(stock);
}

以上是最简单的秒杀伪代码,我们尝试用redis实现分布式锁。

// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
String value = jedisUtils.get(key);
if (value != null) { // 未上锁
? ? // wingzingliu
? ? jedisUtils.set(key, 1); // 上锁
? ? Integer stock = goodsMapper.getStock();
? ? if (stock > 0) {
? ? ? ? stock =- 1;
? ? ? ? goodsMapper.updateStock(stock);
? ? ? ? jedisUtils.del(key); // 释放锁
? ? }
}

以上代码可能会出现一个问题,就是当同时多个请求进来,某次多个请求都拿到value为空,线程A进入if 走到// wingzingliu这里的时候,还未上锁,其他请求也进来了,这样就会出现脏数据了。

这里的代码问题就是出在没有考虑原子性问题。

所以我们要使用到redis的一个setNx命令,本质也是设置值,但是这是一个原子操作,执行之后会返回是否设置成功。

redis> SETNX job "programmer" ? ?# job 设置成功
(integer) 1
?
redis> SETNX job "code-farmer" ? # 尝试覆盖 job ,失败
(integer) 0
?
redis> GET job ? ? ? ? ? ? ? ? ? # 没有被覆盖
"programmer"

重点关注 当有值时,会失败,返回0。所以我们的代码会改造成以下这个样子。

// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
Long result = jedisUtils.setNx(key, 1);
if (result > 0) { // 上锁成功,进入逻辑
? ? // wingzingliu1
? ? Integer stock = goodsMapper.getStock();
? ? if (stock > 0) {
? ? ? ? stock =- 1;
? ? ? ? goodsMapper.updateStock(stock);
?
? ? ? ? System.out.println("购买成功!");
? ? } else {
? ? ? ? System.out.println("没有库存了!");
? ? }
? ? // wingzingliu2
? ? jedisUtils.del(key); // 释放锁
}

以上我们就可以保证原子性,能正确的按照顺序去处理。

可是还有一个隐藏的问题,就是当某个线程执行上锁成功后,在wingzingliu1到wingzingliu2之间时,程序抛异常了,那么程序终止了,就无法释放锁,其他线程也都进不来了。

解决方案是加上try catch finally块,在finally里面去释放锁。

可是那如果是宕机呢?上锁之后宕机了,finally里面的依然不会执行,锁没有得到释放,不手动处理的情况下,以后所有线程也无法进入。

所以引入了redis的过期时间,到了某个时间自动解锁。

// 这里是不够完善的代码,请耐心看完哦
try {
? ? String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
? ? Long result = jedisUtils.setNx(key, 1, 30); // 假设处理逻辑需要20s左右,设置了30秒自动过期
? ? if (result > 0) { // 上锁成功,进入逻辑
? ? ? ? Integer stock = goodsMapper.getStock();
? ? ? ? if (stock > 0) {
? ? ? ? ? ? stock =- 1;
? ? ? ? ? ? goodsMapper.updateStock(stock);
?
? ? ? ? ? ? System.out.println("购买成功!");
? ? ? ? } else {
? ? ? ? ? ? System.out.println("没有库存了!");
? ? ? ? }
? ? }
} catch (Exception e) {
? ??
} finally {
? ? jedisUtils.del(key); // 释放锁
}

以上是比较完善的分布式锁了,但是还有一个小瑕疵,就是假设某一次请求A处理的很慢,预计20s但是跑了35s,到了30s的时候锁过期了,其他请求就自然进来了。

这不仅仅会导致一次并行,当请求A处理完时,依然会执行释放锁,这实际上是下一个线程上的锁。以此类推,整个并发控制就乱了。

理论上可以设置一个更大的key过期时间,但是并不是最好的解决方案。这里就引出一个概念:锁续命。

锁续命

如其名,给锁续命。实现就是 当锁快过期的时候,去延长锁的时间。假设一个30s的锁,每个10s去检测一下,锁是否还在 如果在就重新延长至30s。这样就避免掉了上面的这个可能出现的问题。

这里使用一个定时任务,周期性的调用即可。

扩展

刚刚对key设置的value是1,其实能使用请求ID来进行保存,这样就能知道锁是由哪个请求上的,在解锁的时候 也可以避免解锁了其他线程上的锁。具体由前端传递,或者由服务端以某种规则生成都可以。

结语

至此我们就使用redis,一步一步的解决了在分布式项目下的并发问题。redis不是唯一的解决方案,但是对于大部分互联网公司来说,是一个很成熟、性能不错、便捷的方案。

还可以使用synchronized(非分布式项目)、mq 、zookeeper等方案去实现分布式锁 以 解决高并发问题。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持程序员之家。

相关文章

  • Redis 配置文件重要属性的具体使用

    Redis 配置文件重要属性的具体使用

    Redis在IT公司中的使用率自不必说,今天我们就来介绍一下Redis 配置文件重要属性的具体使用,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 利用Redis统计网站在线活跃用户的方法

    利用Redis统计网站在线活跃用户的方法

    Redis支持对String类型的value进行基于二进制位的置位操作。通过将一个用户的id对应value上的一位,通过对活跃用户对应的位进行置位,就能够用一个value记录所有活跃用户的信息。下面这篇文章主要介绍了利用Redis统计网站在线活跃用户的方法,需要的朋友可以参考。
    2017-01-01
  • 在Mac OS上安装Vagrant和Docker的教程

    在Mac OS上安装Vagrant和Docker的教程

    这篇文章主要介绍了在Mac OS上安装Vagrant和Docker的教程,并安装和设置Postgres和Elasticsearch和Redis,需要的朋友可以参考下
    2015-04-04
  • Redis exists命令bug分析(案例详解)

    Redis exists命令bug分析(案例详解)

    Redis EXISTS 命令用于检查给定 key 是否存在,本文重点给大家介绍Redis exists命令bug分析,感兴趣的朋友跟随小编一起看看吧
    2022-02-02
  • 使用redis分布式锁解决并发线程资源共享问题

    使用redis分布式锁解决并发线程资源共享问题

    这篇文章主要介绍了使用redis分布式锁解决并发线程资源共享问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • redis集群搭建_动力节点Java学院整理

    redis集群搭建_动力节点Java学院整理

    这篇文章主要介绍了redis集群搭建,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Redis高级玩法之利用SortedSet实现多维度排序的方法

    Redis高级玩法之利用SortedSet实现多维度排序的方法

    Redis的SortedSet是可以根据score进行排序的,以手机应用商店的热门榜单排序为例,根据下载量倒序排列。接下来通过本文给大家分享Redis高级玩法之利用SortedSet实现多维度排序的方法,一起看看吧
    2019-07-07
  • Redis性能监控的实现

    Redis性能监控的实现

    本文使用 redis_exporter + prometheus +grafana 实现对Redis服务进行监控,原因:成本低,人工干预少,感兴趣的可以了解一下
    2021-07-07
  • 微服务Spring Boot 整合 Redis 实现好友关注功能

    微服务Spring Boot 整合 Redis 实现好友关注功能

    这篇文章主要介绍了微服务Spring Boot 整合 Redis 实现 好友关注,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • 关于redigo中PubSub的一点小坑分析

    关于redigo中PubSub的一点小坑分析

    这篇文章主要给大家介绍了关于redigo中PubSub的一点小坑的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01

最新评论

?


http://www.vxiaotou.com