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

目录
  • NoSQL
  • Redis
  • 痛点
  • 思路
  • 分布式锁
  • 锁续命
  • 扩展
  • 结语

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等方案去实现分布式锁 以 解决高并发问题。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 高并发下Redis如何保持数据一致性(避免读后写)

    “读后写” 通常意义上我们说读后写是指针对同一个数据的先读后写,且写入的值依赖于读取的值. 关于这个定义要拆成两部分来看,一:同一个数据:二:写依赖于读.(记住这个拆分,后续会用到,记为定义一.定义二)只有当这两部分都成立时,读后写的问题才会出现. 在项目中,当面对较多的并发时,使用redis进行读后写操作,是非常容易出问题的,常常使得程序不具备鲁棒性,bug很难稳定复现(得到的值往往跟并发数有关). 举个栗子: 存在A.B两个进程,同时操作下面这段代码: $objRedis = new Red

  • 浅谈Redis高并发缓存架构性能优化实战

    目录 场景1: 中小型公司Redis缓存架构以及线上问题实战 场景2: 大厂线上大规模商品缓存数据冷热分离实战 场景3: 基于DCL机制解决热点缓存并发重建问题实战 场景4: 突发性热点缓存重建导致系统压力暴增 场景5: 解决大规模缓存击穿导致线上数据库压力暴增 场景6: 黑客工资导致缓存穿透线上数据库宕机 场景7: 大V直播带货导致线上商品系统崩溃原因分析 场景8: Redis分布式锁解决缓存与数据库双写不一致问题实战 场景9: 大促压力暴增导致分布式锁串行争用问题优化 场景10: 利用多级缓

  • Redis高并发情况下并发扣减库存项目实战

    目录 第一种方案:纯MySQL扣减实现 MySQL架构升级 第二种方案:缓存实现扣减 第三种方案:数据库+缓存 顺序写的性能更好 顺序写的架构 扣减流程 相信大家从网上学习项目大部分人第一个项目都是电商,生活中时时刻刻也会用到电商APP,例如淘宝,京东等.做技术的人都知道,电商的业务逻辑简单,但是大部分电商都会涉及到高并发高可用,对并发和对数据的处理要求是很高的.这里我今天就讲一下高并发情况下是如何扣减库存的? 我们对扣减库存所需要关注的技术点如下: 当前剩余的数量大于等于当前需要扣减的数量,不

  • Redis高并发场景下秒杀超卖解决方案(秒杀场景)

    目录 1 什么是秒杀 2 为什么要防止超卖 3 单体架构常规秒杀 3.1 常规减库存代码 3.2 模拟高并发 3.3 超卖现象 3.4 分析原因 4 简单实现悲观乐观锁解决单体架构超卖 4.1 悲观锁 4.2 乐观锁 4.3 redis锁setnx 4.4 使用Redision 5 分布式锁的解决方案 6 采用缓存队列防止超卖 1 什么是秒杀 秒杀最直观的定义:在高并发场景下而下单某一个商品,这个过程就叫秒杀 [秒杀场景] 火车票抢票 双十一限购商品 热度高的明星演唱会门票 … 2 为什么要防止

  • Redis处理高并发之布隆过滤器详解

    目录 前言 缓存穿透.击穿.雪崩 缓存穿透 出现情况 常见的解决方案 缓存击穿 出现情况 解决方案 缓存雪崩 解决方案 布隆过滤器 Bloom filter 总结 前言 随着我们业务开发越来越来大,并染请求就会越来越多,那么我们的项目的压力就会越来越大,基本都会使用缓存,除本地缓存,还会用到redis缓存,但是你以为使用缓存就没啥问题了么,那肯定不是的,使用了缓存又会出现新的问题,比如,缓存的key失效导致大量的请求到数据库,大量的读请求瞬间到达了数据库,cpu的使用率爆增,导致数据库都可能挂掉

  • 高并发技巧之Redis和本地缓存使用技巧分享

    目录 三种缓存的使用场景 Redis的使用场景和局限性 LoadingCache的使用场景和局限性 ReloadableCache的使用场景和局限性 小结 小技巧 缓存使用的简单介绍 LoadingCache的使用 reloadableCache的使用 老生常谈的缓存击穿/穿透/雪崩问题 缓存击穿 缓存穿透 缓存雪崩 众所周知,缓存最主要的目的就是加速访问,缓解数据库压力.最常用的缓存就是分布式缓存,比如redis,在面对大部分并发场景或者一些中小型公司流量没有那么高的情况,使用redis基本都

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

    目录 NoSQL Redis 痛点 思路 分布式锁 锁续命 扩展 结语 NoSQL Not Only SQL的简称.NoSQL是解决传统的RDBMS在应对某些问题时比较乏力而提出的. 即非关系型数据库,它们不保证关系数据的ACID特性,数据之间一般没有关联,在扩展上就非常容易实现,并且拥有较高的性能. Redis redis是nosql的典型代表,也是目前互联网公司的必用技术. redis是键值(Key-Value)存储数据库,主要会使用到哈希表.大多数时候是直接以缓存的形式被使用,使得请求不直

  • PHP解决高并发的优化方案实例

    我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键.举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Apache的Web服务器,配置MaxClients为500个(表示Apache的最大连接数目). 那么,我们的Web系统的理论峰值QPS为(理想化的计算方式): 20*500/0.1 = 100000 (10万QPS) 咦?我们的系统似乎很强大,1秒钟可以处理完10万的

  • Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

    目录 前言 架构设计 代码实现 测试 总结 前言 最近在做阅读类的业务,需要记录用户的PV,UV: 项目状况:前期尝试业务阶段: 特点: 快速实现(不需要做太重,满足初期推广运营即可)快速投入市场去运营 收集用户的原始数据,三要素: 谁在什么时间阅读哪篇文章 提到PV,UV脑海中首先浮现特点: 需要考虑性能(每个客户每打开一篇文章进行记录)允许数据有较小误差(少部分数据丢失) 架构设计 架构图: 时序图 记录基础数据MySQL表结构 CREATE TABLE `zh_article_count`

  • 如何利用Redis锁解决高并发问题详解

    redis技术的使用: redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动... redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力. 为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串.LIST.SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内

  • Redis锁完美解决高并发秒杀问题

    目录 1 单机环境下的锁 2 分布式情况下使用Redis锁. 3 一台服务宕机,导致无法释放锁 4 给每一把锁加上过期时间 5延长锁的过期时间,解决锁失效 6 使用Redisson简化代码 场景:一家网上商城做商品限量秒杀. 1 单机环境下的锁 将商品的数量存到Redis中.每个用户抢购前都需要到Redis中查询商品数量(代替mysql数据库.不考虑事务),如果商品数量大于0,则证明商品有库存.然后我们在进行库存扣减和接下来的操作.因为多线程并发问题,我们不得不在get()方法内部使用同步代码块

  • redis 解决库存并发问题实现数量控制

    目录 一.命令 二.常见场景 三.流程图与代码 redis是单进程,阻塞式,在同一时刻只能处理一个请求,后来的请求需要排队等待. 优点:因为是单进程,所以无需处理并发问题,降低 系统复杂度 缺点:不适合缓存大尺寸对象(超过100kb) 原因: 由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高. 而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis也在存储大数据的性能上进行了优化,但是比起

  • 详解Mysql数据库平滑扩容解决高并发和大数据量问题

    目录 1 停机方案 2 停写方案 3 平滑扩容之双写方案(中小型数据) 4 平滑扩容之2N方案大数据量问题解决 4.1 扩容问题 4.2 解决方案 4.3 双主架构思想 4.4 环境部署 5 数据库秒级平滑2N扩容实践 5.1 新增数据库VIP 5.2 应用服务增加动态数据源 5.3 解除原双主同步 5.4 安装MariaDB扩容服务器 5.5 增加KeepAlived服务实现高可用 5.6 清理数据并验证 1 停机方案 发布公告 停止服务 离线数据迁移(拆分,重新分配数据) 数据校验 更改配置

  • PHP使用文件锁解决高并发问题示例

    本文实例讲述了PHP使用文件锁解决高并发问题.分享给大家供大家参考,具体如下: 新建一个.txt文件,文件中什么都不用写. [一].阻塞(等待)模式:(只要有其他进程已经加锁文件,当前进程会一直等其他进程解锁文件) <?php //连接数据库 $con=mysqli_connect("192.168.2.186","root","root","test"); //查询商品数量是否大于0,大于0才能下单,并减少库存 $fp

  • PHP利用Mysql锁解决高并发的方法

    前面写过利用文件锁来处理高并发的问题的,现在我们说另外一个处理方式,利用Mysql的锁来解决高并发的问题 先看没有利用事务的时候并发的后果 创建库存管理表 CREATE TABLE `storage` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `number` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=l

  • Java利用Redis实现高并发计数器的示例代码

    业务需求中经常有需要用到计数器的场景:譬如一个手机号一天限制发送5条短信.一个接口一分钟限制多少请求.一个接口一天限制调用多少次等等.使用Redis的Incr自增命令可以轻松实现以上需求.以一个接口一天限制调用次数为例: /** * 是否拒绝服务 * @return */ private boolean denialOfService(String userId){ long count=JedisUtil.setIncr(DateUtil.getDate()+"&"+user

随机推荐