Redis-篇


开个分类慢慢更新
参考B站 黑马程序员Redis 入门到实战

目录

全局唯一ID

在一般业务中秒杀功能,存在并发问题如果两个线程同时执行插入操作导致数据库id 自增 同时为一个数 就会导致写入数据失败
全局Id生成器

点击查看代码


public static class RedisIdWork
{
    private readonly static long BEGIN_TIMSTAMP = 1678966413L;
    private readonly static int COUNT_BITS = 32;
    public static long nextId(string keyPrefix)
    {
        //1、生成时间戳
        long nowTimeSeconds = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
        long timesamp = nowTimeSeconds - BEGIN_TIMSTAMP;
        //2、生成序列号
        string date = DateTime.Now.ToString("yyyyMMdd");
        var redis = new RedisHelper();
        long count = redis.database().StringIncrement("icr:" + keyPrefix + ":" + date, 1);
        //3、拼接返回
        return timesamp << COUNT_BITS | count;
    }

}

缓存穿透


缓存雪崩

缓存击穿


互斥锁解决缓存击穿问题

多个线程并行执行的时候 只有一个成功

秒杀超卖问题

同时间 抢购会导致库存超卖问题(多个线程交叉执行导致的)例如两个线程同时查询到库存为 1 (此时库存中满足大于0 所以两个都会执行扣减)

悲观锁 AND 乐观锁

悲观锁会认为线程安全一定会发生 所以在操作数据之前就先获取锁,确保线程串行执行

主要说一下乐观锁,常见的处理方法
版本号法。乐观锁的关键就是判断之前查询到的数据是否有被修改过

CAS 法 (可以说是版本号法的升级版)
如果说是一个减库存操作,可以使用库存数据作为版本标识

生成的Sql 语句

where id = ? and version > 0
为什么版本要大于0 而不是等于 (因为只要是在这个区间就可以 不小于0 即可)

一人一单秒杀

这个没啥说的,简单的方法查询数据库用户ID 进行订单判断是否是同一个id (一般是把商品id 和 用户id 标识当前商品这个用户已经买过了)重点就是 先判断这个用户是否有过

分布式锁


redis 分布式锁

场景:目的是为了解决多台相同服务之间同时工作产生的并发问题,(例如订单系统,假设订单系统部署在两台机器上,但是库存是固定的,接着每个订单系统实例都去数据库里查了一下,由于并发问题导致超卖,这肯定是不允许的)(当然并发特别大的话是需要进行分段数据,数据分段会导致整体业务流程更加复杂,如果没有这方面的需求建议不要使用)
SETNX LOCK Thread1
为了保证原子性 (带上过期时间) 如果不设置过期时间会出现死锁问题(例如因为某些原因todo 内业务阻塞了会导致锁一直无法释放)
Set lock thread1 EX 10 NX
例子

点击查看代码


       public bool LockByRedis(string key, double expireTimeSeconds = 600)
        {
            try
            {
                while (true)
                {
                    //Console.WriteLine($"生成的GUID :{ID_PREFIX}");
                    //expireTimeSeconds = expireTimeSeconds > 20 ? 10 : expireTimeSeconds;
                    bool lockflag = database().LockTake(key, ID_PREFIX + Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(expireTimeSeconds));
                    if (lockflag) //如果锁定成功,则为 true,否则为 false,否则为 false。 
                    {
                        return true;
                        //break;
                    }
                    return false;
                }
                //return database().LockTake(key, ID_PREFIX + Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(expireTimeSeconds));
            }
            catch (Exception ex)
            {
                throw new Exception($"Redis加锁异常:原因{ex.Message}");
            }
        }

        /// <summary>
        /// 释放锁
        /// </summary>
        /// <param name="name"></param>
        public void UnLockByRedis(string key)
        {
            try
            {
                //判读线程标识是否一致 锁是否一致
                //1、查询redis 锁
                string threadid = ID_PREFIX + Thread.CurrentThread.ManagedThreadId;
                string id = database().StringGet(key).ToString();
                if (threadid.Equals(id)) //防止锁被误删
                    database().LockRelease(key, ID_PREFIX + Thread.CurrentThread.ManagedThreadId);
            }
            catch (Exception ex)
            {
                throw new Exception($"Redis加锁异常:原因{ex.Message}");
            }
        }

redis 分布式锁如何解决原子性

场景,如果在获取锁后执行的一些操作,但是此时遇到阻塞,导致锁超时释放了,(比如我们获取了一个自己的锁,然后去执行减库存操作,但是在减库存操作中遇到了阻塞,导致锁超时释放了,此时我们的减库存操作因为阻塞了还没有完成,但是redis锁已经释放了,后面假设此时减库存操作完成了,去调用释放锁,此时会导致当前下释放的锁可能是别人的锁,(因为我们的锁已经超时释放了))

解决这个问题 就是把多条命令 获取锁和释放锁保证原子性
可以是使用Lua 脚本进行调用Redis.call

高级篇

数据持久化

RDB模式

RDB (redis Database Backup file)数据备份文件
save # redis主进程执行备份,会阻塞所有命令
bgsave #开启子线程执行备份,避免主线程受到影响
save 5 1 (表示五秒内有一次修改就执行备份操作) 可以在redis.conf中修改

bgsave fork主进程开启一个子进程,共享内存空间,(这时候物理内存是只读模式)
如果会有写入操作会拷贝一份数据

AOF模式

AOF全称为 Append only file(追加文件),redis会处理每一个写入命令都会记录在aof文件中
AOf默认是关闭的
appendonly yes appendfilename "appendonly.aof"

AOF和RDB两者区别

分布式主从集群

搭建主从集群

单节点Redis并发能力邮上线,要提升并发能力需要搭建主从集群,实现读写分离(大部分业务都是读的业务)
一般写入到master 节点,同步到子节点
需要三台redis 服务器

修改redis.conf文件