缓存中间件-Redis(一)

  • 缓存中间件-Redis(一)已关闭评论
  • 161 次浏览
  • A+
所属分类:.NET技术
摘要

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的 key-value 存储系统,是跨平台的非关系型数据库,Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储的非关系型数据库,常被用于分布式缓存,作为进程外的缓存,它具有以下特点:


1.Redis介绍

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的 key-value 存储系统,是跨平台的非关系型数据库,Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储的非关系型数据库,常被用于分布式缓存,作为进程外的缓存,它具有以下特点:

1.方便扩展

2.大数据量高性能

3.八大数据结构

4.分布式存储

2.什么时候使用Redis

在分析什么场景使用Redis时,我们首先看下面的系统设计图,我们用户发起请求到达Nginx,然后由Nginx来将请求负载均衡到不同的服务实例,再由服务实例去访问数据库,从流程上看基本没什么问题,但是由于集群中的每一个节点实例,都是一个独立的系统,如果我们在系统中使用了本地缓存就会出现一些问题,具体是什么问题呢?

缓存中间件-Redis(一)

1.当用户请求被负载均衡到不同的节点实例时,第一个请求被实例1接收,它的执行流程是查询数据库返回,然后缓存结果,此时第二个用户请求同样的内容,但是被负载均衡分配到服务实例2,对于服务实例2来说这是一个全新的请求,那么又会访问数据库,做着同样的动作,这对于我们来说这是很浪费的,当然在正常请求比较少造成的影响不大,但是如果请求并发较高,缓存的命中下降的几率被放大,导致请求瞬直接访问数据库造成阻塞,影响系统可用性。

同理其实可以这样理解,我们为了服务器更好的承载,加入了更多的服务实例,那么组成集群的单个实例越多,与缓存命中率是成反比的,虽然我们可以在负载均衡时使用一些措施,例如iphash,来将某一客户端的访问与固定的实例绑定,但是比较局限,不够灵活,那么应该怎么去更好的解决这个问题呢?

此时我们尝试在上述的架构图中加入Redis,利用Redis的读写高性能的特点,来做统一的缓存,用于降低数据库的压力以及保证系统高可用,因为就读写速度而言CPU > 内存 > 磁盘

缓存中间件-Redis(一)

3.Redis通信原理
1.单线程和多线程对比

1.单线程原子性操,一个线程做一个任务,不需要锁也不需要线程的上下文的切换。

2.多线程要实现原子性,涉及到各种锁,上下文的切换性能的消耗。

3.单线程多进程 ,可以根据我们的服务,开启多个实例。

2.IO多路复用

多路复用的概念就是多个IO复用一个线程处理,简单的意思是在一个操作里同时监听多个输入输出源,它和我们的异步区别就是,前者是轮训等待任务执行完成拿到结果,而后者则是在任务执行的过程中,自己去执行其他的任务,在不同的操作系统对多路复用有不同的实现,例如Windows内核中实现根据Select 而LInux内核中的实现是Epoll接下来我们简单介绍下他们2种的区别。

Select
用户请求到达内核,内核将请求封装成一个句柄描述,放入本地消息队列,然后循环队列中的所有句柄,判断是否处理完成,如果完成待所有的循环走完后,就将请求转发到用户进程,但是会随着连接数增大,性能下降,处理数据太大的话,性能很差。

Epoll

用户请求到达内核,内核将请求封装成一个句柄描述和时间回调,放入本地消息队列,待处理完毕就会触发事件,不用去循环队列中的所有消息,内核再将请求转发到用户进程,随着连接数增大,性能基本没影响,适合并发强度大的场景.

4.基本数据结构

在实际过程中,我们利用Stackexchange.Redis来进行操作.Net与Redis的互操作,当然也可以使用Servicestack.Redis他也同样可以实现交互。

1.web中安装和配置环境

1.创建Net6 WebApi项目,并且NuGet下载StackExchange.Redis最新版安装到项目中。

2.扩展Redis客户端初始化连接,并且在Startup中添加到IOC容器

//扩展连接客户端 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"); 

3.在Api中创建RedisController控制器,并注入ConnectionMultiplexer

public class RedisController : ControllerBase {     private readonly ConnectionMultiplexer _connectionMultiplexer;      private readonly IDatabase db;     public RedisController(ConnectionMultiplexer connectionMultiplexer)     {        _connectionMultiplexer = connectionMultiplexer;        db = _connectionMultiplexer.GetDatabase(0);     } } 
2.string类型

1.string类型数据时Redis中最基础的类型,其他类型在此基础上构建.

2.string类型不仅仅会开辟占有的空间,还会预留一部分空间,最大能存储 512MB,可以是简单的Key,value,也可以是复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串。

TestData testData = new TestData(); //首先序列化 string json = JsonConvert.SerializeObject(entity); //设置30秒后失效 db.StringSet("TestData", demojson,TimeSpan.FromSeconds(30)); 
3.Set 集合

1.redis集合(set)类型和list列表类型类似,都可以用来存储多个字符串元素的集合

2.和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的,不存在元素下标,intzset 最大存储2的64次方,或者内容超过512个字节就使用HashTable

3.Redis中的Set数据结构它由数组HashTable组成,每一个数组值都经过hash计算,支持集合内的增删改查,并且支持多个集合间的交集、并集、差集操作

RedisValue[] values = db.SetMembers("testdatas"); List<TestData> data = new List<TestData>(); if (values.Length == 0) {     // 2、从数据库中查询     data = testData.Add();     // 3、存储到redis中     List<RedisValue> redisValues = new List<RedisValue>();     foreach (var item in data)     {         string json = JsonConvert.SerializeObject(item);//序列化         redisValues.Add(json);     }     db.SetAdd("testdatas", redisValues.ToArray());     return data; }  // 4、序列化,反序列化 foreach (var redisValue in values) {     TestData t = JsonConvert.DeserializeObject<TestData>(redisValue);//反序列化     data.Add(t); }  
4.hash

1.Redis hash数据结构 是一个键值对(key-value)集合,它是一个 string 类型的 field 和 value 的映射表。

2.hash数据结构相当于在value中又套了一层key-value型数据。

 //根据HashKey获取字段为AccessTime 的值  string time =db.HashGet("HashKey", "AccessTime");  if (string.IsNullOrEmpty(time))  {       test = data.FirstOrDefault(s => s.Id == 1);       //设置HashKey为AccessTime字段的值       db.HashSet("HashKey", "AccessTime", test.AccessTime);  }  // 次数加1  db.HashIncrement("HashKey", "AccessTime"); 
5.ZSet (有序集合)

1.它的实现是由Zskiplist+HashTable

2.redis有序集合也是集合类型的一部分,它保留了集合中元素不能重复的特性,但是不同的是,有序集合给每个元素多设置了一个分数,利用该分数作为排序的依据。

6.List

1.list类型是用来存储多个有序的字符串的,列表当中的每一个字符看做一个元素.

2.一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素。

3.redis可以从列表的两端进行插入(pubsh)和弹出(pop)元素,支持读取指定范围的元素集,
或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色

4.reids的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。

7.事务操作

1.Redis中不支持事务回滚

2.在sqlserver中如果开启事务,修改数据,如果新的会话去同时操作会等待释放锁,而Redis中,在开启事务前首先监听了Key的版本号,如果在事务期间,新的会话可以修改目标Key,但是会影响当前事务提交不成功。

ITransaction transaction = db.CreateTransaction(); //执行操作 transaction.HashSetAsync("HashKey", "AccessTime", test.AccessTime); bool commit = transaction.Execute(); if (commit) {     Console.WriteLine("提交成功"); } else {     Console.WriteLine("提交失败"); }  
5.Redis持久化
1.RDB 快照

RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,恢复时是将快照文件直接读到内存里,持久化的RDB文件可以拷贝到不同的redis服务,将数据
加载。

  • 1.bgsave :父进程启动一个子进程,由子进程将内存保存在硬盘文件,期间不会影响其他的指令操作.

  • 2.save:将内存数据镜像保存为RDB文件,由于redis是单线程模型期间会阻塞redis服务进程,redis服务不再处理任何指令,直到RDB文件创建完成.

  • 无论是bgsave还是save,其过程如下:

1.生成临时rdb文件,并写入数据.

2.完成数据写入,用临时文代替代正式rdb文件.

3.删除原来的db文件

save 900 1 #15分钟内有一条数据被修改则保存 save 300 10 #300秒有10条修改则保存 save 60 10000 #60秒内有10000条数据修改则保存  # 是否压缩rdb文件 rdbcompression yes  # rdb文件的名称 dbfilename redis-6379.rdb  # rdb文件保存目录 dir ~/redis 

自动触发备份原理

1.Redis有一个周期性操作函数,默认每隔100ms执行一次,它的其中一项工作就是检查自动触发Bgsave命令的条件是否成立.

2.计数器记录了在上一次成功的持久化后,redis进行了多少次写操作,其值在每次写操作之后都加1,在成功完成持久化后清零.

RDB的优点

  1. 与AOF方式相比,通过rdb文件恢复数据比较快。
  2. rdb文件非常紧凑,适合于数据备份。
  3. 通过RDB进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。
2.AOF 文件追加

Redis服务每次结束一个事件循环之前,都会调用flushAppendOnly函数,其中调用write函数将aof_buf写入文件,aof文件可以被修改。

1.开启AOF配置

# 开启aof机制 appendonly yes  # aof文件名 appendfilename "appendonly.aof"  # 写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no appendfsync always  # 默认不重写aof文件 no-appendfsync-on-rewrite no  # 保存目录 dir ~/redis  

1.在配置文件开启将appendonly设为yes

2.设置文件路径dir,可以使用默认在当前目录下

3.设置追加方式一共有三种,一般选用第二种方式

  • 1.appendfsync always 只要有读写,性能最低但是安全性最高
  • 2.appendfsync everysec 1s钟的周期
  • 3.appendfsync no 等业务不繁忙的时候,这种操作最不可靠 性能最高但是安全性最低

2.AOF文件解读

1.打开追加的持久化文件

缓存中间件-Redis(一)

  • 第一个指令*2代表有2个参数,$6第一个参数6个字节为select, $1代表第二个参数0个字节
  • 第二个指令*3代表有3个参数,$3第一个参数3个字节为set,$4第2个参数4个字节为name,$3第3个参数3个字节为lll
3.Redis持久化加载

上面我们介绍了Redis支持2种持久化的方式,那我们需要判断在加载时选择哪种方式,因为不可能同时选择2种,其实在Redis加载时会判断是否开启了AOF,如果开启了AOF就会加载AOF文件,如果没有开启,那么就会选择加载RDB文件。

缓存中间件-Redis(一)