redis怎样解决高并发

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis怎样解决高并发相关的知识,希望对你有一定的参考价值。

1.Redis存取数据是基于内存的,而内存的读写速度非常快,这是前提。
2.Redis是单线程的,省去了很多切换线程的时间消耗。(PS:Redis单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多线程。PPS:目前新版的Redis分支也在深入的探索多线程的方式,理论效率要高于目前市面上的稳定版本。)
3.Redis采用网络IO多路复用技术(非阻塞IO)来保证在多连接的时候,系统的高吞吐量。而非阻塞IO,其内部实现采用了epoll并结合了本身实现的简单的事件框架,epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性极大的节省了在处理IO上的时间。(PS:多路复用指的是多个socket连接复用一个线程。而epoll是目前最好的多路复用技术,所以使用epoll实现多路I/O复用技术可以让单个线程高效的处理多个连接请求,从而达到尽量减少网络IO的时间消耗)
参考技术A Redis是个单线程程序!这点必须铭记。

也许你会怀疑高并发的Redis 中间件怎么可能是单线程。很抱歉,它就是单线程,你的怀疑暴露了你基础知识的不足。莫要瞧不起单线程,除了Redis 之外,Node.js 也是单线程,nginx也是单线程,但是它们都是服务器高性能的典范。

Redis单线程为什么还能这么快?

因为它所有的数据都在内存中,所有的运算都是内存级别的运算。正因为Redis是单线程,所以要小心使用Redis 指令,对于那些时间复杂度为O(n) 级别的指令,- -定要谨慎使用,一不小心就可能会导致Redis 卡顿。

Redis单线程如何处理那么多的并发客户端连接?

这个问题,有很多中高级程序员都无法回答,因为他们没听过多路复用这个词汇,不知

道select 系列的事件轮询API, 没用过非阻塞IO。

非阻塞IO
参考技术B redis是以单进程的形式运行的,命令是一个接着一个执行的,能很好的解决程序的并发问题
所以在当数据涉及并发问题 比如秒杀 我们就是把数据线存进redis 然后用户请求的时候在redis中减库存redis是单线程所以不会减超 redis减成功了之后就拒绝之后的请求然后在数据库减库存 这样就不会出现库存为负的问题 这就是基本的运作原理
参考技术C 部署redis集群啊 一个redis服务每秒读10万条数据(官网说的,应该是理想环境,自己测其实就2/3/4万左右),你每秒处理100万条数据,那你就部署redis集群啊,每个redis每秒处理3万,那你就需要部署100/3个redis服务,也就是多开100/3个端口。

利用Redis锁解决高并发问题

这里我们主要利用Redissetnx的命令来处理高并发。

setnx 有两个参数。第一个参数表示键。第二个参数表示值。如果当前键不存在,那么会插入当前键,将第二个参数做为值。返回 1。如果当前键存在,那么会返回0

 

创建库存表

CREATE TABLE `storage` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1

设置初始库存为10

创建订单表

CREATE TABLE `order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1


测试不用锁的时候
$pdo = new PDO(mysql:host=127.0.0.1;dbname=test, root, root);
$sql="select `number` from  storage where id=1 limit 1";
$res = $pdo->query($sql)->fetch();
$number = $res[number];
if($number>0)
{
    $sql ="insert into `order`  VALUES (null,$number)";

    $order_id = $pdo->query($sql);
    if($order_id)
    {

        $sql="update storage set `number`=`number`-1 WHERE id=1";
        $pdo->query($sql);
    }
}

ab测试模拟并发,发现库存是正确的。

mysql> select * from storage;
+----+--------+
| id | number |
+----+--------+
|  1 |      0 |
+----+--------+
1 row in set (0.00 sec)

在来看订单表

mysql> select * from `order`;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  2 |     10 |
|  3 |      9 |
|  4 |      7 |
|  5 |      6 |
|  6 |      5 |
|  7 |      5 |
|  8 |      5 |
|  9 |      4 |
| 10 |      1 |
+----+--------+
10 rows in set (0.00 sec)

发现存在几个订单都是操作的同一个库存数据,这样就可能引起超卖的情况。

修改代码加入redis锁进行数据控制

<?php
/**
 * Created by PhpStorm.
 * User: daisc
 * Date: 2018/7/23
 * Time: 14:45
 */
class Lock
{
    private static $_instance ;
    private   $_redis;
    private function __construct()
    {
        $this->_redis =  new Redis();
        $this->_redis ->connect(127.0.0.1);
    }
    public static function getInstance()
    {
        if(self::$_instance instanceof self)
        {
            return self::$_instance;
        }
        return self::$_instance = new  self();
    }

    /**
     * @function 加锁
     * @param $key 锁名称
     * @param $expTime 过期时间
      */
    public function set($key,$expTime)
    {
        //初步加锁
        $isLock = $this->_redis->setnx($key,time()+$expTime);
        if($isLock)
        {
            return true;
        }
        else
        {
            //加锁失败的情况下。判断锁是否已经存在,如果锁存在切已经过期,那么删除锁。进行重新加锁
            $val = $this->_redis->get($key);
            if($val&&$val<time())
            {
                $this->del($key);
            }
            return  $this->_redis->setnx($key,time()+$expTime);
        }
    }

    /**
     * @param $key 解锁
     */
    public function del($key)
    {
        $this->_redis->del($key);
    }

}

$pdo = new PDO(mysql:host=127.0.0.1;dbname=test, root, root);
$lockObj = Lock::getInstance();
//判断是能加锁成功
if($lock = $lockObj->set(storage,10))
{
    $sql="select `number` from  storage where id=1 limit 1";
    $res = $pdo->query($sql)->fetch();
    $number = $res[number];
    if($number>0)
    {
        $sql ="insert into `order`  VALUES (null,$number)";

        $order_id = $pdo->query($sql);
        if($order_id)
        {

            $sql="update storage set `number`=`number`-1 WHERE id=1";
            $pdo->query($sql);
        }
    }
    //解锁
    $lockObj->del(storage);

}
else
{
    //加锁不成功执行其他操作。
}

再次进行ab测试,查看测试结果

mysql> select * from `order`;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  2 |      9 |
|  3 |      8 |
|  4 |      7 |
|  5 |      6 |
|  6 |      5 |
|  7 |      4 |
|  8 |      3 |
|  9 |      2 |
| 10 |      1 |
+----+--------+
10 rows in set (0.00 sec)

发现订单表没有操作同一个库存数据的情况。所以利用redis锁是可以有效的处理高并发的。

这里在加锁的时候其实是可以不需要判断过期时间的,这里我们为了避免造成死锁,所以加一个过期时间的判断。当过期的时候主动删除该锁。

 

以上是关于redis怎样解决高并发的主要内容,如果未能解决你的问题,请参考以下文章

高并发架构系列:Redis并发竞争key的解决方案详解

Nginx与Redis解决高并发问题

利用乐观锁及redis解决电商秒杀高并发基本逻辑

Redis高并发场景下秒杀超卖解决

高并发网站架构的设计方案是怎样的?

如何解决redis高并发客户端频繁time out