文章目录
- 一、 线程锁与分布式锁
- 二、Redis集群
一、 线程锁与分布式锁
- 线程锁 单体项目
- 单体项目
- 步骤
- 代码如下
//定义静态全局锁
private readonly static object _lock = new object();
// 控制器中添加代码
lock (_lock)
{
Stock sto = new Stock();
sto = demoDbContext.stock.Where(p => p.ID == 1).FirstOrDefault();
if (sto.count == 0)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "---秒杀结束,无库存");
return Ok("秒杀结束,无库存");
}
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "--秒杀成功;");
//库存减1
sto.count = sto.count - 1;
demoDbContext.SaveChanges();
}
return Ok("秒杀结束");
- 数据库数量为10
如图: - 用jmeter并发10个线程
如图: - 运行结果如下:
- 分布式锁
- 条件
- 启动两个实例 5000/5001
- Nginx
- jmeter
- redis
- 步骤
- 核心代码
public class RedisLock
{
public readonly ConnectionMultiplexer connectionMultiplexer;
private IDatabase database = null;
public RedisLock()
{
connectionMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1:6380");
database = connectionMultiplexer.GetDatabase(0);
}
/// <summary>
/// 加锁
/// </summary>
public void Lock()
{
while (true) //
{
//redis_lock 锁名称
// Thread.CurrentThread.ManagedThreadId 线程名称
// TimeSpan.FromSeconds(10) 设置过期时间 防止死锁
bool flag = database.LockTake("redis_lock", Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(10));
//true :加锁成功 false:加锁失败
if (flag)
{
break;
}
Thread.Sleep(10);//防止死锁 等待时间 释放资源。
}
}
/// <summary>
/// 释放锁
/// </summary>
public void UnLock()
{
database.LockRelease("redis_lock", Thread.CurrentThread.ManagedThreadId);
connectionMultiplexer.Close();
}
}
控制器中使用
[HttpGet]
[Route("SubStock")]
public IActionResult SubStock()
{
RedisLock redisLock = new RedisLock();
redisLock.Lock();
Stock sto = new Stock();
sto = demoDbContext.stock.Where(p => p.ID == 1).FirstOrDefault();
if (sto.count == 0)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "---秒杀结束,无库存");
//redisLock.UnLock();
return Ok("秒杀结束,无库存");
}
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "--秒杀成功;");
//库存减1
sto.count = sto.count - 1;
demoDbContext.SaveChanges();
redisLock.UnLock();
return Ok("秒杀结束");
}
- 运行两个实例
如图: - 启动Nginx
如图: - 数据库库存
如图: - jmeter并发10个线程
- 运行结果如下
- 分布式锁的使用场景
当集群系统中修改某个字段值时使用分布式锁。 - 分布式锁的设计思路
比如并发两个进程,当第一个进程加锁后,第二个进程加锁会失败,会休眠(10毫秒),直到第一个进程执行完业务代码并释放锁,如果第一个进程处理业务代码超过10毫秒,redis的过期时间也是10毫秒,那么第二个进程进行加锁执行业务代码并释放锁。
备注:休眠的毫秒数可根据自己业务代码定义,毫秒数最好和redis过期时间一致。
二、Redis集群
- 第一代集群 主从集群
- 如图:
- 缺点
只有一个master,当maset宕机后,整个redis集群系统无法使用。
- 第二代集群 哨兵集群
- 如图
第二代集群比第一代集群多了一个sentinel监视的角色,当主节点宕机后,sentinel会从多个从节点中选择一个为主节点。 - 缺点
- 只有一个master,无法解决高并发写的问题。
- 无法存储海量数据。
- 第三代集群
- 如图:
- 优点与缺点
- 优点
- 解决高并发写。
- 存储海量数据。
- 缺点
- 消耗资源比较大。
- 实现
- 条件
- windows 环境
- Redis
- 网盘下载地址
链接:https://pan.baidu.com/s/1-rdemcSLHHFSy3b03EnQsg
提取码:liiz
- Ruby
- 网盘下载地址
链接:https://pan.baidu.com/s/1NEnVoZzzMyROdm0qNeqw0g
提取码:lf10
- Ruby 驱动
- 网盘下载地址
链接:https://pan.baidu.com/s/1LkpTstTMenespCx4J0ZtWA
提取码:7wn6
- 分配主从工具
- 网盘下载地址
链接:https://pan.baidu.com/s/18ah0ePiGr9XCukRsRdIiXw
提取码:0e02
- 步骤
- 配置集群文件 (6个实例) 配置6个配置文件【并将6个配置拷贝到redis根目录下】 配置不能有中文注释也不行!!!!
port 6380 #端口
bind 127.0.0.1 #IP地址
appendonly yes #数据保存格式为aof
appendfilename "appendonly.6380.aof" #数据保存文件
cluster-enabled yes #是否开启集群
cluster-config-file nodes.6380.conf #集群节点配置文件
cluster-node-timeout 15000 #节点超时时间
cluster-slave-validity-factor 10 #验证slaver节点次数
cluster-migration-barrier 1 #
cluster-require-full-coverage yes #master节点和slaver节点之间是否全量复制
- 执行所有实例
redis-server.exe redis.6380.conf
redis-server.exe redis.6381.conf
redis-server.exe redis.6382.conf
redis-server.exe redis.6383.conf
redis-server.exe redis.6384.conf
redis-server.exe redis.6385.conf
如图:
- 安装 ruby
ruby --version #验证是否安装成功
- redis-cluster 驱动安装命令
#进入 ruby安装目录bin文件下执行安装命令
ruby gem install --local D:\Assembly\redis\Windows\redis-cluster\redis-3.2.2.gem #驱动文件路径
- 执行分配主从工具脚本
ruby redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385
#写入所有的实例地址和端口号
# --replicas 1:是否分配3主3从 一个主节点和从节点
如图:
- 查看是否分配成功
当6个实例不停输出日志,说明已经分配成功。
- redis集群内部关系结构图
如图:
在redis集群中,每个节点都是相互通信的,用的协议是Gossip协议。 - redis集群内部数据存储原理
- Slot槽 主节点有槽[平均分配] 从节点没有槽 总共有16384个槽
ruby redis-trib.rb check 127.0.0.1:6380
- Hash算法
- 取模算法
当客户端将数据写入节点中时,节点会将key使用hash算法取到一个固定长度数值,然后对槽总数【16384】用取模算法进行取模,得到的数值后在到各个主节点中查看数值在哪个节点的槽数数值之间,在将数据写到那个主节点中。