3

缓存中间件-Redis(一) - 寒号虫113

 2 years ago
source link: https://www.cnblogs.com/yuxl01/p/16094209.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

缓存中间件-Redis(一) - 寒号虫113 - 博客园

有梦想的鱼

写代码我得再认真点,当我最终放下键盘时,我实在不想仍有太多疑惑

posts - 25,comments - 19,views -

6898

1.Redis介绍

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

1.方便扩展

2.大数据量高性能

3.八大数据结构

4.分布式存储

2.什么时候使用Redis

1.在分析什么场景使用Redis时,我们首先看下面的系统设计图,我们用户发起请求到达Nginx,然后由Nginx来负载均衡到不同的服务实例,服务实例去访问数据库,正常来看没什么问题,但是如果集群的单个实例是一个独立的系统,如果我们在单个系统中使用了缓存就会出现问题,具体是什么问题呢?

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

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

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

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文件可以拷贝到不通过的服务,将数据
加载。

  • 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.打开追加的持久化文件

  • 第一个指令*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文件。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK