.NET Core下使用Redis

Redis介绍

Redis是一个开源的key-value存储系统,和Memcached类似,但覆盖了Memcached的绝大部分功能。Redis数据都在内存中,除了支持简单的key-value模式,还支持string list set zset hash等五种数据结构。同时Redis还支持数据持久化、读写分离、集群等功能。

Redis官网 https://redis.io

Redis安装

官网下载获得 redis.tar.gz,解压 tar -zxvf redis.tar.gz 。解压完成进入目录 cd redis,执行make命令(如果没有gcc环境,先install gcc / install gcc-c++),如果先执行make命令,再安装gcc环境,可运行 make distclean 之后再make。执行make后,继续执行make install。默认安装目录 /usr/local/bin ,如果要指定位置可使用 make install PREFIX=/opt/module/redis

Redis常用操作

常用命令

redis-benchmark 性能测试工具
redis-check-aof 修复有问题的aof文件
redis-check-dump 修复有问题的dump.rdb文件
redis-sentinel Redis集群使用
redis-server 启动,这里可以跟一个配置文件。
redis-cli 控制台入口
select 0 切换数据库

启动

redis-server 默认为前台启动,意味着关闭窗口redis服务也就关闭。所以需要配置redis.conf文件里的 daemonize ,将no改为yes, 就可以让redis服务在后台启动。

关闭

shutdownredis-cli shutdown

使用StackExchange.Redis

StackExchange.Redis官方文档 https://stackexchange.github.io/StackExchange.Redis/

安装StackExchange.Redis

PM> Install-Package StackExchange.Redis

键操作

key的数量最多可以为2的32次方。大小最大可以为512mb。

//键是否存在
db.KeyExists("k1");

//类型,返回RedisType枚举
var keyType = db.KeyType("k1");

//删除键
db.KeyDelete("k1");

//设置键过期时间,比如10秒后过期,如果重新设置了k1,时间将变成永不过期
db.KeyExpire("k1", TimeSpan.FromSeconds(10));


//查看键还剩多少时间过期 纯ttl命令 -1表示永不过期 -2表示已过期
//但这里 1.键不存在  2.已过期 3.永不过期 都为null。注意判断
var ttl = db.KeyTimeToLive("k1");

如何执行flushdb等命令

在redis端 ,我们可以使用 keys * 、flushdb 等命令,那么在StackExchange.Redis又如何使用呢。代码如下

var server = multiplexer.GetServer("localhost:6379");
server.Keys(pattern:"*");
server.Keys(pattern:"*[1-9]");
server.Keys(pattern:"?[1-9]");//问号代表一个字符
server.FlushDatabase();

String

String是Redis最基本的类型,一个key对应一个value。String类型是二进制安全的,可以包含任何数据,比如图片或者序列化对象,但也不能太大,最多可以是512MB。

//存,如果key一样,就覆盖
db.StringSet("k2", "v2");
db.StringSet("k2", "v22");

//取
db.StringGet("k2");

//追加,如果key不存在就创建,返回追加后的value长度
db.StringAppend("k5", "11");

//获取value长度
db.StringLength("k2");

//不存在时才设置,相当于命令模式下的 setnx
db.StringSet("k6", "v6", when: When.NotExists);

//数字增加
//如果键不存在,创建key,在0基础上做加减。
db.StringSet("k10", 10);
db.StringIncrement("k10", 5);

//数字减少
db.StringSet("k10", 10);
db.StringDecrement("k15", 5);

//多个kv一起存
var kvs = new[]
{
new KeyValuePair<RedisKey, RedisValue>("k1","v1"),
new KeyValuePair<RedisKey, RedisValue>("k2","v2"),
};
db.StringSet(kvs);

//获得值的范围,类似SubString
db.StringGetRange("k10", 0, 2);

//覆盖指定范围的值
db.StringSetRange("k10", 0, "v10");

//设置新值获取旧值
var v1 = db.StringGetSet("k1", "v2");

List

单键多值,Redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部或者尾部,它的底层是个双向链表,对两端的操作性能很高,通过索引操作性能稍差。list可包含重复值。

//从左边推入
db.ListLeftPush("l1", "v1");

//从右边推入
db.ListRightPush("l1", "v2");

//左边弹出
var lv1 = db.ListLeftPop("l1");

//右边弹出
var lv2 = db.ListRightPop("l1");

//如果弹完,键就被自动删除。所谓键在值在,值光键亡。

//右边弹出 左边推入
var ss = db.ListRightPopLeftPush("l1", "l2");

//按范围获取,stop如果为-1,就是取全部,表示倒数第一个。
db.ListRange("l1", 0, 10);

//按索引获取
db.ListGetByIndex("l1", 10);

//获取字符串个数
db.ListLength("l1");

//在中间插入值
db.ListInsertAfter("l1", 5, "v1");

//删除n个value.
//n取值正数,从左边删除n个value
//取值为负数,从右边删除n个value
//取值为0 ,删除全部value
db.ListRemove("s1", "v1",1);

SET 集合

SET类似list,是一个无序集合,可以自动排重,复杂度O(1),常用操作方法如下。

//添加
db.SetAdd("s1", "v1");
db.SetAdd("s1", new RedisValue[] {1, 2, 3});
db.SetAdd("s1", 3);//再添加一个重复项,其实s1集合里还是4个元素,说明是排重的

//取出集合所有元素
var s1 = db.SetMembers("s1");

//集合是否包含某元素
var isMemberOfS1 = db.SetContains("s2", 5); 

//集合元素个数
var count = db.SetLength("s1");

//删掉一个元素
var rmIsSuccessful = db.SetRemove("s1", 3);

//随机弹出一个元素,元素会从集合中删除
//如果全部元素都弹出,s1这个key也会被删除
var item = db.SetPop("s1");

//随机取出一个元素,元素不会从集合中删除
var item1 = db.SetRandomMember("s1");

//随机取N个元素
var items = db.SetRandomMembers("s1", 3);

//取交集
var combineResult = db.SetCombine(SetOperation.Intersect, "s1", "s2");

//取并集
var combineResult1 = db.SetCombine(SetOperation.Union, "s1", "s2");

//取差集
var combineResult2 = db.SetCombine(SetOperation.Difference, "s1", "s2");

HASH

HASH是一个键值对集合,实质上是一个string类型的field和value的映射表,适合存储对象,类似 c# 中的 Dictionary<string,object>。通过 key(用户ID)+field(属性标签)就可以操作对应属性数据,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。常用操作如下。

//准备一个对象
var student = new Student()
{
  Id = 1,
  Age = 120,
  Birthday = DateTime.Now.AddYears(-20),
  Name = "周星驰"
};

//存
db.HashSet("person", student.ConvertToHashEntryList().ToArray());
db.HashSet("person", "Age", "20"); //覆盖

//取, 注意hashField的大小写
var age = db.HashGet("person", "Age");


//是否存在
var hashExists = db.HashExists("person","Age");

//取出所有field 和 value
var hashGetAll = db.HashGetAll("person");

//将hash值转为对象
var student1 = hashGetAll.ConvertFromHashEntryList<Student>();

//获取所有value
var hashValues = db.HashValues("person");

//获取所有field
var hashKeys = db.HashKeys("person");

//获取field数量
var hashLength = db.HashLength("person");

//增加某字段的值
db.HashIncrement("person", "Age", 10);

//删除某field
     
db.HashDelete("person", "Age");

ZSET

ZSET(SORTED SET)与普通集合SET非常相似,是一个没有重复元素的字符串集合,不同之处是ZSET都关联一个评分 SCORE,这个评分值可以重复。

//添加
db.SortedSetAdd("z1", "v1", 100);

//按分数获取范围值
db.SortedSetRangeByScore("z1", 130, 150);

//查看v1的排名
db.SortedSetRank("s1", "v1");

事务操作

var newId = "xxx";
var tran = db.CreateTransaction();
tran.AddCondition(Condition.HashNotExists("k1", "unqid"));
tran.HashSetAsync("k1", "unqid", newId);
var committed = tran.Execute();

持久化

Redis有两种持久化方式,RDB(Redis DataBase)和AOF(Append of File)。

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程进行持久化,主进程不进行任何IO操作,所以保证了极高的性能。缺点是最后一次持久化后的数据可能丢失。

RDB配置

dbfilename dump.rdb
#rdb文件名称

dir ./
#rdb文件存放位置

stop-writes-on-bgsave-error  yes
#在无法写入磁盘的话 关闭写操作
     
rdbcompression yes
#压缩rdb文件
     
rdbchecksum yes
#保存快照会校验,这个会增加性能损耗

save 900 1
save 300 10
save 60 10000
#rdb文件保存策略 
#每900秒有1个发生变化就保存
#每300秒有10个发生变化就保存
#没60秒有10000个发生变化就保存

手动保存快照

使用save命令手动保存快照,这个命令会阻塞写入。
使用bgsave命令手动保存快照,这个命令不会阻塞写入。

优缺点

rdb的优点

  1. 节省磁盘空间
  2. 恢复速度快

rdb的缺点

  1. 如果数据庞大比较消耗性能
  2. 最后一次持久化后的数据可能丢失

AOF持久化

以日志的形式来记录每个写操作,就所有写指令记录下来,只追加文件不修改文件。Redis启动后会读取该文件重新构建数据完成恢复工作。

如果aof和rdb文件同时开启,redis以aof文件为准。AOF恢复,与rdb文件一致,拷贝文件到目录重启redis即可。如果aof文件损坏,可以用 redis-check-aof --fix xxx.aof。文件越来越大时,可以用bgrewiteaof命令压缩aof文件。

AOF配置

appendonly yes
#AOF默认不开启,需要手动开启

appendfilename "appendonly.aof" 
#存放路径跟rdb文件一致

appendfsync everysec
#保存策略 everysec|always|no
#everysec每妙保存一次 
#no 委托给系统来保存,性能高 
#always 每时每刻

优缺点

AOF的优点

  1. 备份机制更稳健,丢失数据概率低。

AOF的缺点

  1. 比起rdb更占空间,备份恢复慢。
  2. 每次读写都同步有性能压力。
  3. 存在个别BUG,造成不能恢复。

//添加
db.SortedSetAdd("z1", "v1", 100);

//按分数获取范围值
db.SortedSetRangeByScore("z1", 130, 150);

//查看v1的排名
db.SortedSetRank("s1", "v1");