缓存中间件-Redis(二)
在上一篇中我们简单总结和介绍了Redis的几个方面
1.使用Redis背景
2.Redis通信多路复用的基本原理
3.Redis基本数据结构
4.Redis持久化方式
这一篇我们使用简单的业务场景来介绍Redis的分布式锁
和集群
1.分布式锁
首先我们应该知道什么是分布式锁
,用来做什么的,解决了什么问题,我们应该怎么做?
简单来说,分布式锁就是锁住进程
的锁,通常我们分布式锁的使用场景在多实例
的集群
系统中,为了防止多个进程对资源的同时操作,而引发业务逻辑错误而诞生的,和我们的单个实例中的lock
作用一样,一个是针对多实例单进程
,一个是针对单实例多线程
,具体在web中的表现在下面用实例的方式说明。
2.单进程单实例多线程
1.例如现在我们部署了一个服务,他的功能是提供用户来抢红包的接口,我们在发红包时,总共设置了10个红包
,我们将红包剩余数存入数据库,所以用一张表来存储红包的个数信息,每当有一个人来抢,我们就需要将红包的个数减掉1,主要的核心业务逻辑如下:
1.用户发起抢红包请求,服务器首先查询,红包剩余个数,如果为0,则直接返回,告诉用户红包抢完了
2.如果红包个数不为0,则提示用户抢到了红包,然后扣减红包总数。
伪代码如下
//查询剩余红包个数
int RedEnvelopeCount = getRedEnvelopes();
if (RedEnvelopeCount.Count == 0)
{
//返回用户红包抢完了 do something
return;
}
//通知用户抢到了 do something
//扣减红包个数
subtracRedEnvelopeCount();
2.此时如果同一时间多个并发请求进来,我们就会发生多个线程同时执行这段核心逻辑,导致业务错误
1.发生
超抢
,抢到的人大于实际个数,谁抢到的不算?2.发生
少抢
,几个人同时抢到同一个,算谁的?
我相信这2种情况,要是在真实的抢红包中,发生到谁头上,都不爽吧?按东哥的话说我都气的想打人
所以我们要着手解决,解决的思路就是,核心业务的代码
同一时刻只能被一个线程执行,所以为了保证原子性,我们就需要使用到锁
来锁住核心业务代码,在这里我们使用C#的Lock
来实现
//静态全局唯一
private readonly static object _lock = new object();
using (_lock)
{
int RedEnvelopeCount = getRedEnvelopes();
if (RedEnvelopeCount.Count == 0)
{
//返回用户红包抢完了
return;
}
//通知用户抢到了
//扣减红包个数
subtracRedEnvelopeCount();
}
3.单进程多实例多线程
上面我们列举并介绍了在单个实例中遇到并发产生的业务问题,我们通过锁的方式来暂时解决了问题,那么接下来我们继续思考,如果红包再多一点,抢的并发量起来了,单台服务器不能承载这么多并发,为了保证高可用,我们需要将红包服务,进行集群部署,利用nginx来负载均衡一下。
1.在单个实例中我们为了保证业务执行的原子性,锁住了核心业务代码,保证同一时刻
只有一个线程
执行,但是现在多个实例来处理请求,假设现在2个用户提交抢红包的请求,在同一时刻被服务器负载均衡到2台不同的实例,那么此时就会遇到2个进程同时操作核心业务逻辑,超抢
和少抢
的事情又会发生,瞬间头又大了,那么针对这种业务情况,我们应该使用分布式锁
来解决不同进程之间争夺资源的情况。
2.接下来就是我们最后一个问题,如何实现分布式锁,其实对于集群系统实现分布式锁的方式有很多种,基于Zookeeper,Redis,包括数据库,等都可以实现,相较于Zookeeper和数据库 ,Redis单线程对于实现分布式锁
拥有天生绝对的优势,而且它操作内存的方式就性能而言是非常优秀的,鉴于咱们这篇博客主要是跟Redis相关的,所以主要介绍使用Redis的方式,当然基于Redis中也存在不同的做法。
3.实现锁的核心业务就是加锁``释放锁
这2个功能,我们根据Redis内部单线程
执行的原理自己来初步实现一个
//设置key,加锁 防止死锁超时,设置60毫秒自动过期
bool result = database.StringSet("distributed-lock", "lock", expiry: TimeSpan.FromSeconds(60));
//失败说明锁已经被获取,直接返回
if (!result)
{
// "人有点多,再点点...";
return;
}
//获取剩余个数
int RedEnvelopeCount = getRedEnvelopes();
if (RedEnvelopeCount.Count == 0)
{
//返回用户红包抢完了
return;
}
//通知用户抢到了
//扣减红包个数
subtracRedEnvelopeCount();
//释放锁
database.KeyDelete("distributed-lock");
上面实现了最简单和最基础的分布式锁,但是还存在缺陷,仔细思考不难发现
1.执行业务的时间大于持有锁的最大时间,导致锁释放,业务还没执行完,乱了那也玩不了
2.获取锁后执行到业务代码时抛异常了,锁没及时释放,而是等待自动释放,所以需要加异常处理
我们根据这2个缺陷来进一步完善,解决上面的问题,首先需要先保证执行业务的情况下,谁加的锁由谁来释放
,然后添加异常处理代码
用于处理业务异常能及时释放锁
//获取Guid作为当前请求唯一身份信息
string value = Guid.NewGuid().ToString("N");
//设置key,加锁,防止死锁超时,设置60毫秒自动过期
bool result = database.StringSet("distributed-lock", value, expiry: TimeSpan.FromSeconds(60));
//失败说明锁已经被获取,直接返回
if (!result) return; // "人有点多,再点点...";
try
{
int RedEnvelopeCount = getRedEnvelopes(); //获取剩余个数
if (RedEnvelopeCount.Count == 0)return; //返回用户红包抢完了
//通知用户抢到了
//扣减红包个数
subtracRedEnvelopeCount();
}
finally
{
//保证由当前请求释放,不会别的进来释放导致乱套
if (value.Equals(database.StringGet("distributed-lock")))
{
//释放锁
bool isDelete = database.KeyDelete("distributed-lock");
}
}
4.最后我们使用StackExchange.Redis
提供的方法来实现封装一个分布式锁,首先扩展IServiceCollection 将StackExchange.Redis 操作实例注入
//扩展连接客户端
public static class RedisServiceCollectionExtensions
{
public static IServiceCollection AddRedisCache(this IServiceCollection services, string connectionString)
{
ConfigurationOptions configuration = ConfigurationOptions.Parse(connectionString);
ConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect(connectionString);
services.AddSingleton(connectionMultiplexer);
return services;
}
}
//注入容器
services.AddRedisCache("127.0.0.1:6379,password=xxx,connectTimeout=2000");
5.具体利用LockTake
通过轮训询问的方式获取锁,通过LockRelease
释放锁,在实现的源码中,加锁的原理就是设置一个string类型的key value以及过期时间
public class DistributedLock
{
private readonly ConnectionMultiplexer _connectionMultiplexer;
private IDatabase db = null;
public DistributedLock(ConnectionMultiplexer connectionMultiplexer)
{
_connectionMultiplexer = connectionMultiplexer;
db = connectionMultiplexer.GetDatabase(0);
}
private void FetchLock()
{
while (true)
{
//当前锁的持有时间超过60毫秒就会被释放,这个时间应该设置比被锁住业务执行时间长
bool flag = db.LockTake("distributed-lock", Thread.CurrentThread.ManagedThreadId, TimeSpan.FromSeconds(60));
if (flag) // 1、如果加锁成功直接退出,否则继续获取锁
{
break;
}
Thread.Sleep(10); // 防止死循环。通过等待10毫秒时间,释放资源
}
}
public void UnLock()
{
bool flag = db.LockRelease("distributed-lock", Thread.CurrentThread.ManagedThreadId);
_connectionMultiplexer.Close();
}
}
6.基于Redis实现的分布式锁,在Redis主从或者哨兵乃至集群中,会存在锁丢失
的情况,具体就是当请求给Master节点写入锁后,Master节点宕机,此时主从还没来得及同步,但此时已经选择了一个Slaver为主节点,而最开始的设置的锁不见了,针对这种情况比较极端,可以利用Zookeeper
来实现分布式锁作为解决方案,因为在数据一致性上Zookeeper
是优于Redis
的,当然Redis
的性能也是较Zookeeper
更为突出一些。
4.Redis容灾高可用
就服务实例而言,单个服务实例保证容灾
和高可用
基本很难,因为一个实例随时都有宕机的风险,所以引入了集群
的概念,通过多个服务实例,来共同保证系统提供稳定的服务,其实在Redis中也一样,也有保证容灾和高可用的方案,在一定程度上,实现的方案在大的策略层面思路都差不多,只是具体实现的方式不同罢了,至于最终选择保证数据信息层面的高可用
,还是性能资源层面的高可用,需要用户去根据自己业务来分析,选择使用哪一种方案。
在Redis中,有3种方式来实现高可用的方案,分别是
1.主从模式 (Master Slaver)
2.哨兵模式 (Sentinel)
3.数据分片 (Cluster)
1.主从模式和哨兵
主从模式
是Redis提供高可用
的第一种方案,理解起来也比较简单,一个主节点多个从节点,主节点负责写入,然后会把数据同步到从节点,从节点负责读取,但是会存在不可避免的缺陷
1.如果宕机,它不具备自动容错和恢复功能,需要手动切换从节点为主节点。
2.宕机导致数据未及时同步,降低可用性
3.所有节点数据一样,内存浪费,并且在线扩容复杂,伸缩性差,海量数据存储无法解决。
4.高并发主节点写入数据压力极大。
哨兵模式
是Redis提供的第二种方案,可以说是第一种主从方案的完善版本,因为它在主从的基础上,增加了监控
功能,主节点宕机后提供哨兵
自动选举master并将其他的slave指向新的master,哨兵是一个独立运行的进程,它的实现原理是哨兵进程向所有的redis机器发送心跳消息,从而监控运行状况,同样由于是基于主从而诞生,那么也存在主从中的一些缺陷。
2.集群分片模式
1.集群和分片基本概念
前面2种方案归根结底,就是使用主从复制和读写分离的方式保证高可用,但是内存占用,主节点压力过大,相较于Redis提供的第三种集群和数据分片
的方案,存在的缺陷也非常明显,下面我们介绍集群和数据分片
。
1.
集群和数据分片
简单理解就是将主从复制的核心策略,做了集群来横向扩展,包含多个主从节点,采用去中心化思想,数据按照 slot 存储分布在多个节点,并且节点间数据共享,可动态调整数据分布,
2.在集群中每一个节点都是
互相通信
采用Gossip
病毒协议,在一个节点写入的数据,其他任何节点都会查询到。
2.集群内部数据存取原理
1.
Slot
槽,存储数据的容器,总共16384个Slot ,采用平均分配,并且只有主节点
才能被分配
2.
Hash
算法用于对数据进行计算,得到一个Hash值,然后对16384取模
选择存到哪个区间的Slot
3.Windows下集群搭建
1.我们部署集群使用redis-trib.rb
,首先下载环境 提取码1234
2.安装需要用到Ruby语言运行环境,然后进入安装目录Ruby22-x64
中,通过gem
命令安装文件
gem install –-local youFloder\redis-3.2.2.gem
3.准备Redis服务实例,此处准备6个实例,并配置Redis配置文件,然后启动每一个
服务实例
redis-server redis-6380.conf
redis-server redis-6381.conf
....
....
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
cluster-migration-barrier 1
cluster-require-full-coverage yes
3.进入到redis-trib.rb
目录打开命令窗口,进行搭建集群
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
4.检查集群状态,出现图片中的提示代表搭建成功
ruby redis-trib.rb check 127.0.0.1:6380
缓存中间件-Redis(二)的更多相关文章
- 老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化
引言 今天周末,我在家坐着掐指一算,马上又要到一年一度的金九银十招聘季了,国内今年上半年受到 YQ 冲击,金三银四泡汤了,这就直接导致很多今年毕业的同学会和明年毕业的同学一起参加今年下半年的秋招,这个 ...
- 高并发系列之——缓存中间件Redis
1 概念和使用场景 下载路径 2 基本存储类型 String List Set SortedSet Hash 3 事务 单线程执行,即只能保证一个client发起的事务中的命令可以连续的执行,而中间不 ...
- 缓存中间件-Redis(一)
1.Redis介绍 REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的 key-value 存储系统,是跨平台的非关系型数据库,Red ...
- redis缓存中间件基础
前序: 默认使用SimpleCacheConfiguration 组件ConcurrentMapCacheManager==ConcurrentMapCache将数据保存在ConcurrentMap& ...
- Spring Boot 揭秘与实战(二) 数据缓存篇 - Redis Cache
文章目录 1. Redis Cache 集成 2. 源代码 本文,讲解 Spring Boot 如何集成 Redis Cache,实现缓存. 在阅读「Spring Boot 揭秘与实战(二) 数据缓存 ...
- 构建高性能数据库缓存之redis(二)
一.概述 在构建高性能数据库缓存之redis(一)这篇文档中,阐述了Redis数据库(key/value)的特点.功能以及简单的配置过程,相信阅读过这篇文档的朋友,对Redis数据库会有一点的了解,此 ...
- 分布式数据存储 之 Redis(二) —— spring中的缓存抽象
分布式数据存储 之 Redis(二) -- spring中的缓存抽象 一.spring boot 中的 StringRedisTemplate 1.StringRedisTemplate Demo 第 ...
- 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步
深入理解MVC MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...
- MyBatis加强(1)~缓存机制(一级缓存、二级缓存、第三方缓存技术redis、ehcache)
一.缓存机制 使用缓存可以使应用更快地获取数据,避免频繁的数据库交互操作,尤其是在查询越多,缓存命中率越高 的情况下,缓存的作用就越明显. 1.缓存原理:Map ■ 查询时,先从缓存区查询:找到,返回 ...
随机推荐
- IDEA的git的拉去提交Java day9
赶鸭子上架,没法子. 新的知识点24号继续学习,今天认真熟悉了以下IDEA,的git代码的提交和拉去,不过拉去下来的项目有些打开的问题有点多,还在继续解决中-- git知识明天一并上传博客.
- git tag、gitignore和git撤销提交
前言 最近在git的使用过程中遇到了一些新的问题,所以写下来方便自己回忆. git tag 打标签 git tag -a v1.00 -m "注释" git tag 打标签命令 - ...
- github新手使用指南
常用命令: Git 速查表(摘自 AI有道) 一.常见命令 git init : 初始化 git 仓库,即将一个文件夹初始化为一个 git 仓库.具体的操作是创建一个 .git 隐藏文件夹 git ...
- spring cloud 和dubbo区别?
1.服务调用方式 dubbo是RPC springcloud Rest Api2.注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper3.服务网 ...
- 讲讲 kafka 维护消费状态跟踪的方法?
大部分消息系统在 broker 端的维护消息被消费的记录:一个消息被分发到 consumer 后 broker 就马上进行标记或者等待 customer 的通知后进行标记.这 样也可以在消息在消费后立 ...
- kafka producer 打数据,ack 为 0, 1, -1 的时候代表啥, 设置 -1 的时候,什么情况下,leader 会认为一条消息 commit了?
1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了.在这种情况下,如果leader宕机了,则会丢失数据. 0 生产者将数据发送出去就不管了,不去等待任何返回. ...
- 使用mqtt+ssl加密 WebSocket 客户端连接 MQTT 服务器以及ws+wss协议
上篇用TLS/SSL保证EMQ的网络传输安全讲了使用自签ca加密MQTT传输数据,如果mqtt用在web端,如何使用ssl.tsl加密? 1.web客户端 // 引入mqtt.min.js // 将在 ...
- Spring 配置文件 ?
Spring 配置文件是个 XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用.
- Linux 中进程有哪几种状态?在 ps 显示出来的信息中, 分别用什么符号表示的?
1.不可中断状态:进程处于睡眠状态,但是此刻进程是不可中断的.不可中断, 指进程不响应异步信号. 第 441 页 共 485 页2.暂停状态/跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响 ...
- IOC——Spring的bean的管理(xml配置文件)
Bean实例化(三种方式) 1.使用类的无参构造进行创建(大多数情况下) <bean id="user" class="com.bjxb.ioc.User" ...