安装与使用

  • Redis-x64-3.2.100:服务端 +
    客户端
  • redis-3.2.5:源代码
  • Redis Desktop Manager –
    v0.8.8:客户端(基于Qt5的跨平台Redis桌面管理软件,C++编写,响应迅速、性能好,但不支持数据库备份与恢复)

具体使用:

  • 服务端启动:将命令 redis-server.exe
    redis.windows.conf 写入 .bat 文件,直接运行 StartWithConf.bat 启动服务端;
  • 客户端启动:直接运行 redis-cli.exe 即可;

    redis.windows.conf:配置文件
    redis-benchmark.exe:Redis读写性能测试工具
    redis-check-aof.exe:aof修复检查日志
    redis-check-dump.exe:dump检查数据库文件
    redis-cli.exe:Redis客户端程序
    redis-server.exe:Redis服务器程序
    StartWithConf.bat:启动Redis

 

using System;
using System.Collections.Generic;
using System.Linq;
using ServiceStack.Redis;
using ServiceStack.Redis.Generic;
namespace RedisHelper
{
public class RedisTool : IDisposable
{
public RedisClient Redis = new RedisClient(“127.0.0.1”, 6379,
“123456”);
//缓存池
private PooledRedisClientManager prcm = new PooledRedisClientManager();

配置文件

redis.windows.conf

Redis服务端的运行参数全部靠配置文件实现,此处详细介绍Redis配置文件的几个重要参数:

network

  • bind 127.0.0.1:绑定地址(外网连接:0.0.0.0)  
  • port 6379:默认绑定本机的6379端口;
  • timeout:连接超时时间(秒)
  • requirepass pass:配置redis连接认证密码

general

  • loglevel
    debug/notice/warning/verbose:日志级别(开发测试/生产环境/只记录警告错误信息/详细信息)
  • logfile
    ./Logs/redis_log.txt:日志文件保存路径
  • databases 16:数据库数量,默认0

snapshotting

  • save TimeInterval ChangeCnt

append only mode

  • appendonly yes:开启命令日志模式;

limits

  • maxclients 64:最大连接数,0为不限制
  • maxmemory <bytes>:内存清理临界值
  • maxmemory-policy
    volatile-lru:内存清理采用的默认策略,对设置过期时间的key进行LRU算法删除

一、Redis提供了哪些持久化机制:

//默认缓存过期时间单位秒
public int secondsTimeOut = 30 * 60;

服务命令

ping:启动服务连接情况
info:查看server/client配置信息
info commandstats + config resetstat:显示/清除名次调用统计信息
config get/set:获取/设置配信息
flushdb/flushall:删除当前所选/所有数据库中的所有key
save/bgsave:数据保存到硬盘/异步保存
lastsave: 上次成功保存到磁盘的unix时间戳
dbsize:查看所有key的数目 
get/set和mget/mset:获取/设置键
incr/decr和incrby/decrby:自增/自减
exists/type key:键key是否存在/键类型
expire key secondTime:设置键的过期时间
rename oldKey newKey:重命名
ttl key:键key的剩余存活时间
select db_index:选择数据库
move key db_index:将键key移动到指定数据库

  redis的高性能是因为其所有数据都存在了内存中
,为了使redis在重启之后数据仍然不丢失,需要将数据同步到硬盘中,这一过程就是持久化。

/// <summary>
/// 构造函数
/// </summary>
/// <param name=”OpenPooledRedis”>是否开启缓冲池</param>
public RedisTool(bool OpenPooledRedis = false)
{
if (OpenPooledRedis)
{
prcm = CreateManager(new[] { “123456@127.0.0.1:6379” }, new[] {
“123456@127.0.0.1:6379” });
Redis = prcm.GetClient() as RedisClient;
}
}

基本概念

Redis是典型的NoSQL数据库服务器,其License是Apache
License、完全免费。首先看下内存数据库的基本概念:

内存数据库

In-Memory DataBase,以内存为主要存储介质的数据库.

  • 所有的表及索引在内存中、消除I/O瓶颈,为访问内存设计最佳访问方法和索引模式,读写速度快、性能好;
  • 内存数据库的容量大小受物理内存的限制;
  • 安全性问题是硬伤,支持根据策略与磁盘数据库进行数据同步,以及数据库的可靠性恢复机制;

Redis

REmote DIctionary Server(远程字典服务),远程内存数据库(Memory Database + Data Structure
Server),开源的使用ANSI-C语言编写、支持网络、可基于内存亦可持久化的日志型、高性能的key-value数据库,Redis不预定义且不使用表,适应高并发、海量数据存储场景。

  • A persistent key-value database with built-in net interface
    written in ANSI-C for Posix systems.
  • Redis is an open source, BSD licensed, advanced key-value cache
    and store.

下面是Redis支持的5种类型数据结构的内部图解(图一):

图片 1

redisObject对象是Redis内部的核心对象,用于表示所有的key和value。

typedef struct redisObject {
    unsigned type:4;  // 数据类型
    unsigned encoding:4;  // 编码方式
    unsigned lru:REDIS_LRU_BITS;   /* lru time (relative to server.lruclock) */
    int refcount;   // 对象的引用计数
    void *ptr;   // 指向真正的存储结构
} robj;

其中,REDIS_LRU_BITS表示当内存超限时采用LRU算法清除内存中的对象。redisObject对象的创建在object.c文件中:

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = LRU_CLOCK();
    return o;
}    

Redis的键类型为string,值类型支持:

  •  字符串:string
  •  列表:list
  •  集合:set
  •  有序集合:zset (SortedSet)
  •  散列:hash

具体内存结构示意图(图二):

图片 2

参考:Redis数据库入门教程; Redis学习笔记;

特点

  • 全部数据In-Momory,作为Memcached的替代者;
  • key-value存储系统(Key:数据检索的唯一标识、Value:数据存储的主要对象),支持多种类型的value(数据结构服务器);
  • redis的起点是cache,缓存,高速缓存;
  • 数据存储于内存中或被配置为使用虚拟内存;
  • 持久化特性(Persistence):可以持久化到磁盘(周期性把更新数据写入磁盘或把修改操作追加写入记录文件);
  • 主从复制特性(Master/Slave
    Replication):负载均衡,扩展读性能;
  • 客户端分片(Client-Side
    Sharding):数据划分为多个部分,扩展写性能,线性级别的性能提升;
  • 支持各种不同方式的排序;
  • 支持简单的事务(仅实现一次性执行多条命令的功能,不支持回滚);
  • 支持设置数据过期时间;

内存优化

  • string和数字:Redis内部维护一个数字池,可以节省存储空间,默认 REDIS_SHARED_INTEGERS = 10000 
  • 复杂类型的存储优化:Redis内部使用紧凑格式存储数据(适合集合包含的Entry不多并且每个Entry包含的Value不是很长的情况),遍历复杂度下降为O(n)、但节省存储空间。以ZIPMap的数据结构为例:

  图片 3

 
其中,字段free用于冗余空间,空间换时间、一定情况下避免插入操作引起的扩容操作。

  • list、set、hash采用特殊编码,优化存储空间;
  • byte、bit级别的操作:getrange/setrange、getbit/setbit以及bitmap高效存储;

Redis .vs.
Memcached

  • 两者均是高性能键值缓存服务器,Memcached只提供数据缓存服务,Redis提供数据缓存和持久化;
  • Memcached:多线程服务器;Redis:单线程服务器,部分性能通过多线程实现;
  • Memcached只支持普通字符串键;Redis提供丰富的数据存储结构,同时支持主数据库(Primary
    Database)+ 辅助数据库(Auxiliary Database)使用;
  • Memcached:预分配内存池方式,Redis:现场申请内存的方式存储数据、且可以配置虚拟内存

  redis支持两种方式的持久化,一种是RDB,另一种是AOF。可以单独使用其中一种或者结合使用。

#region Key/Value存储

数据类型

string

图片 4

list

双向链表、允许重复,支持lpush/rpush和lpop/rpop;实现消息队列等;

图片 5

set

不允许重复,内部是哈希表实现、查找/删除/插入均O(1); 集合提供SINTER、SUNION、SDIFF分别支持交集、并集、差集操作。

图片 6

hash

键值对(父键+子键:值)。存储键key的多个属性数据,完全可以用Json格式存储、直接当作string类型操作,但对性能有影响,所以Redis提出Hash类型。

图片 7 

如下,图一是普通的key/value结构,需要封装一个对象保存value的信息;图二是Redis的Hash类型:

图片 8

zset

有序键值对(父键+成员:分值),键值对实际是成员和分值(Member-Score)的映射关系(字符串成员member与浮点数分值score之间的有序映射,按分值大小排序),分值必须为浮点数; 既可以根据成员访问元素(同散列),又可以根据分值按序访问元素结构。

图片 9

1). RDB持久化:(Redis asynchronously dumps the dataset )
  该机制是指在指定的时间间隔内将内存中的数据集快照写入磁盘。
2). AOF持久化:( Append Only File)
  该机制将以日志的形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的。
3). 无持久化:
  我们可以通过配置的方式禁用Redis服务器的持久化功能,这样我们就可以将Redis视为一个功能加强版的memcached了。
4). 同时应用AOF和RDB。

/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”key”>缓存建</param>
/// <param name=”t”>缓存值</param>
/// <param
name=”timeout”>过期时间,单位秒,-1:不过期,0:默认过期时间</param>
/// <returns></returns>
public bool Set<T>(string key, T t, int timeout = 0)
{
if (timeout >= 0)
{
if (timeout > 0)
{
secondsTimeOut = timeout;
}
Redis.Expire(key, secondsTimeOut);
}

持久化

内存提供主存储支持、硬盘作持久性存储。默认开启RDB模式,默认优先加载AOF文件。一次性将数据加载到内存中,一次性预热。

问题:当服务器被关闭时,服务器内存存储的数据将何去何从?

RDB .vs. AOF

  • RDB方式二进制方式存储数据,文件较小且格式紧凑(RDB文件的存储格式和Redis数据在内存中的编码格式一致)、加载速度快;AOF方式文本文件追加写操作命令,文件较大、信息冗余,加载速度慢,但rewrite命令会压缩aof文件;
  • RDB方式按配置的save策略实现定期批量数据存储、效率相对较高;AOF方式准实时日志记录、效率相对较低;
  • 相比RDB方式,AOF方式可靠性较高、最少的数据丢失和较高的数据恢复能力;

不重启Redis从RDB模式切换到AOF模式

redis-cli> config set appendonly yes:启用AOF
redis-cli> config set save "":关闭RDB

参考:Redis数据持久化; Redis作者:深度剖析Redis持久化;

  优先装载AOF(数据更全,有更好的持久化保证吗,RDB有可能丢失一部分数据)

return Redis.Add(key, t);
}

RDB

半持久化模式(快照方式:File-Snap-Shotting,即时间点转储:Point-in-Time Dump),Redis
DataBase
,将数据先存储在内存,当直接调用save/bgsave命令时或数据修改满足设置的save条件时触发bgsave操作,将内存数据一次性写入RDB文件。比较适合灾难恢复(Disaster
Recovery),若Redis异常crash,最近的数据会丢失。

rdbcompression yes:创建快照时对数据进行压缩  
dbfilename dump.rdb:快照名称
dir ./saveFile/:快照保存路径(AOF文件存放目录)

原理Copy-on-Write(写时复制)技术

  • Redis forks;
  • 子进程将数据写到临时RDB文件中;
  • 当子进程完成写RDB文件,用新文件替换旧文件;

该原理保证任何时候复制RDB文件都是绝对安全的。

二、RDB机制的优势和劣势:

/// <summary>
/// 获取
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”key”></param>
/// <returns></returns>
public T Get<T>(string key)
{
return Redis.Get<T>(key);
}

AOF

全持久化模式(日志方式),Append-Only-File,将数据存在内存,同时调用fsync将本次写操作命令进行日志记录到aof文件,基于Redis网络交互协议的由Redis标准命令组成的可识别的纯文本文件,只允许追加不允许改写。

写策略:默认并推荐 appendfsync everysec ,速度和安全兼顾。

  • appendfsync always:每提交一个修改命令调用fsync刷新到AOF文件,非常慢、但非常安全;
  • appendfsync everysec:每秒调用fsync刷新到AOF文件,很快、但可能会丢失一秒以内的数据;
  • appendfsync no:依靠OS被动刷新、redis不主动刷新AOF,最快、但安全性差;

AOF最关键的配置就是关于调用fsync追加日志文件持久化数据的频率。磁盘空间满、断电等情况不会影响日志的完整性和可用性。

保存:支持2种方式

  • 调用flushaofbuf,把aof_buf中的命令写入aof文件,再清空aof_buf,进入下一次loop;

    sds aof_buf; / AOF buffer, written before entering the event loop /

  • aof_rewrite:根据现有的数据库数据反向生成命令,然后把命令写入aof文件中;

加载

fakeClient = createFakeClient();   // 创建伪客户端
while(命令不为空) {
   // 获取一条命令的参数信息 argc, argv
      . . . 
   // 执行
   fakeClient->argc = argc;
   fakeClient->argv = argv;
   cmd->proc(fakeClient);
}

AOF重写

bgrewriteAOF,重新生成一份AOF文件,新的AOF文件只包含对同一个值的多次操作的最后一条记录(可以恢复数据的最小指令集),过程和RDB类似(Copy-on-Write机制):

  • fork一个子进程,直接遍历旧的AOF文件,将数据写入新的AOF临时文件;
  • 在写新文件过程中,所有的新的写操作日志记录在内存缓冲区中、同时会写入到原有的AOF文件中;
  • 完成写新文件操作后,发出信号通知父进程将内存缓冲区中的写指令一次性追加到临时AOF文件中;
  • 追加完毕,Redis将临时AOF文件作为新AOF文件代替旧AOF文件(调用原子性的rename命令用新的AOF文件取代老的AOF文件); 

当同时满足以下2个条件时触发rewrite操作:

auto-aof-rewrite-percentage 100  // 当前写入日志文件的大小占到初始日志文件大小的某个百分比时触发rewrite
auto-aof-rewrite-min-size 64mb   // 本次Rewrite最小的写入数据量

注意,bgrewriteaof和bgsave不能同时执行,避免两个Redis后台进程同时对磁盘进行大量的I/O操作。

修复

Redis提供 redis-check-aof.exe 工具支持日志修复功能:

  • 备份坏的AOF文件;
  • 运行redis-check-aof
    –fix修复坏的AOF文件;
  • 用diff
    -u对比两个文件的差异,确认问题点;
  • 重启Redis,加载修复后的AOF文件;

RDB存在哪些优势呢?
1).
一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
2).
对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
3).
性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

/// <summary>
/// 删除
/// </summary>
/// <param name=”key”></param>
/// <returns></returns>
public bool Remove(string key)
{
return Redis.Remove(key);
}

主从机制

master-slave,为了加强持久化机制,在持久化基础上Redis提供复制功能:将一个主服务器(master)数据自动同步到多个从服务器(slave),实现主从同步:

  • 纯粹的冗余备份
  • 提升读性能

具体地:

  • 启动从服务器,先向主服务器发送SYNC命令;
  • 主服务器收到SYNC命令后fork子进程开始保存快照,期间所有发给主服务器的命令都会被缓存到内存;
  • 快照保存完成后,主服务器把快照和缓存的命令全部发送给从服务器;

  • 从服务器保存收到的快照文件并加载到内存中,然后依次执行收到的缓存命令;

在主从同步过程中(异步实现),从服务器不会阻塞,期间默认使用同步之前的数据继续响应客户端命令。主从机制支持增量同步策略,降低连接断开的恢复成本。

具体应用中通常是:Redis+MySQL

图片 10

RDB又存在哪些劣势呢?
1).
如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

public bool Add<T>(string key, T t, int timeout)
{
if (timeout >= 0)
{
if (timeout > 0)
{
secondsTimeOut = timeout;
}
Redis.Expire(key, secondsTimeOut);
}
return Redis.Add(key, t);
}

发布订阅机制

publish-subscribe,观察者模式,订阅者(Subscriber)订阅频道(Channel),发布者(Publisher)将消息发到指定频道(Channel),通过这种方式将消息的发送者和接收者解耦,可以实现多个浏览器之间的信息同步和实时更新。

图片 11

  • 消息的传递是多对多的;
  • 支持模式匹配;
  • 运行稳定、快速;

    publish myChannel “xxx”:发布
    subscribe myChannel:订阅
    unsubscribe myChannel:取消订阅

Redis的Pub/Sub模式允许动态的Subscribe/Unsubscribe,提高系统的灵活性和可扩展性。  

三、AOF机制的优势和劣势:

#endregion

其他

排序

问题:数据库支持排序,为什么要把排序功能放在缓存中实现?

  • 排序会增加数据库的负载,难以支撑高并发的应用;
  • 在缓存中排序不会遇到表锁定的问题;

    sort key [BY pattern] [LIMIT offset cnt] [GET pattern [GET pattern …]] [asc | desc] [ALPHA] [STORE destination]

  • by:即order by,指定排序字段,by
    *->子键名;

  • limit:限制排序后返回元素的数量,表示跳过前offset个元素、返回之后的连续cnt个元素,可以实现分页功能;
  • get:返回指定的字段值,get
    *->子键名;
  • store:将排序结果存入指定位置;  

事务

Transaction。

  • multi:原子操作,通知Redis,接下来的若干命令属于同一事务;
  • 输入若干命令,存储在命令队列中而不会被立即执行;
  • exec:原子操作,通知Redis,属于同一事务的所有命令输入完成,开始执行事务;

管道

pipilining,允许Redis一次性接收多个命令、执行后一次性返回结果,减少客户端与Redis服务器的通信次数、降低往返时延。类似事务,通过原子操作multi/exec完成。

优先级队列

blpop/brpop。

AOF的优势有哪些呢?
1).
该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
2).
由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
3).
如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
4).
AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

#region 链表操作

应用场景

首先,将Redis与SQL Server/MySQL等对比一下:

  • Redis的持久化是附加功能,且其flushdb、flushall命令会直接清空数据库,
    SQL Server/MySQL的持久化是核心功能;
  • Redis全量持久数据从内存到磁盘、大数据下影响性能,SQL
    Server/MySQL增量持久化被修改的数据;

应用场景

 - 在主页中显示最新的项目列表;
 - 删除和过滤:lrem;
 - 排行榜(Leader Board)及相关问题;
 - 按照用户投票和时间排序;
 - 过期项目处理:unix时间作为得分;
 - 计数(Counting Stuff):INCR,DECR命令构建计数器系统;
 - 特定时间内的特定项目:Redis特色特性;
 - 实时分析正在发生的情况,用于数据统计与防止垃圾邮件等;
 - Pub/Sub:发布订阅机制;
 - 队列(Priority Queue);
 - 缓存(Caching);  

然后给出使用Redis中的几点注意事项:

  • keys * —>  scan
  • 建议使用hash
  • expire设置key的存活时间 + volatile-lru策略;
  • Redis所在机器物理内存使用最好不要超过实际内存总量的3/5;

以及通过阅读 ALCA in
Redis-land 得到的建议:

图片 12

参考:Redis应用场景; Redis作者谈Redis应用场景; Redis应用建议;

AOF的劣势有哪些呢?
1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。
2).
根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

/// <summary>
/// 根据IEnumerable数据添加链表
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”listId”></param>
/// <param name=”values”></param>
/// <param name=”timeout”></param>
public void AddList<T>(string listId, IEnumerable<T> values,
int timeout = 0)
{
Redis.Expire(listId, 60);
IRedisTypedClient<T> iredisClient = Redis.As<T>();
if (timeout >= 0)
{
if (timeout > 0)
{
secondsTimeOut = timeout;
}
Redis.Expire(listId, secondsTimeOut);
}
IRedisList<T> redisList = iredisClient.Lists[listId];
redisList.AddRange(values);
iredisClient.Save();
}

Redis for C#

初识Redis时接触到的.Net-Redis组件是ServiceStack.Redis,其V3系列的最新版本是:ServiceStack.Redis.3.9.29.0。

ServiceStack.Redis

ServiceStack.Common.dll
ServiceStack.Interfaces.dll
ServiceStack.Redis.dll
ServiceStack.Text.dll

了解RedisClient类的具体信息:

  • 基本操作

    public void Init();
    public bool ContainsKey(string key);
    public bool Remove(string key);
    public void RemoveByPattern(string pattern);
    public void RemoveByRegex(string pattern);
    public IEnumerable GetKeysByPattern(string pattern);
    public List SearchKeys(string pattern);
    public List GetAllKeys(); // 数据库内的所有键(慎用)
    public string GetRandomKey();
    public T Get(string key);
    public IRedisTypedClient As(); // / 重要 /
    public bool Add(string key, T value [, DateTime expiresAt]); // [设置过期时间]
    public bool Add(string key, T value [, TimeSpan expiresIn]);
    public bool Set(string key, T value [, DateTime expiresAt]); // [设置过期时间]
    public bool Set(string key, T value [, TimeSpan expiresIn]);
    public bool ExpireEntryAt(string key, DateTime expireAt); // 设置过期时间
    public bool ExpireEntryIn(string key, TimeSpan expireIn);
    public TimeSpan GetTimeToLive(string key); // TTL时间
    public long DecrementValue(string key); // 减
    public long DecrementValueBy(string key, int count);
    public long IncrementValue(string key); // 增
    public long IncrementValueBy(string key, int count);

  • string

    public long GetStringCount(string key);
    public string GetValue(string key);
    public void SetValue(string key, string value [, TimeSpan expireIn]);
    public void RenameKey(string fromName, string toName);
    public int AppendToValue(string key, string value);
    public string GetAndSetValue(string key, string value);
    public string GetSubstring(string key, int fromIndex, int toIndex);
    public List GetValues(List keys);
    public Dictionary GetValuesMap(List keys);

  • List  

    // 基本操作
    public int GetListCount(string listId);
    public int RemoveItemFromList(string listId, string value);
    public string RemoveStart/End/AllFromList(string listId);
    public void SetItemInList(string listId, int listIndex, string value);
    public void AddItemToList(string listId, string value);
    public void AddRangeToList(string listId, List values);
    public List GetAllItemsFromList(string listId);
    public string GetItemFromList(string listId, int listIndex);
    public List GetRangeFromList(string listId, int startingFrom, int endingAt);
    public List GetRangeFromSortedList(string listId, int startingFrom, int endingAt);
    public List GetSortedItemsFromList(string listId, SortOptions sortOptions);
    public List GetValues(List keys);
    public Dictionary GetValuesMap(List keys);
    // List作为队列
    public void EnqueueItemOnList(string listId, string value);
    public string DequeueItemFromList(string listId);
    // List作为栈
    public void PushItemToList(string listId, string value);
    public string PopItemFromList(string listId);
    public string PopAndPushItemBetweenLists(string fromListId, string toListId);

  • Set

    public int GetSetCount(string setId);
    public bool SetContainsItem(string setId, string item);
    public void RemoveItemFromSet(string setId, string item);
    public void AddItemToSet(string setId, string item);
    public void AddRangeToSet(string setId, List items);
    public HashSet GetAllItemsFromSet(string setId);
    public string GetRandomItemFromSet(string setId);
    public List GetSortedEntryValues(string setId, int startingFrom, int endingAt);
    public HashSet GetDifferencesFromSet(string fromSetId, params string[] withSetIds);
    public HashSet GetIntersectFromSets(params string[] setIds);
    public HashSet GetUnionFromSets(params string[] setIds);
    public void StoreDifferencesFromSet(string intoSetId, string fromSetId, params string[] withSetIds);
    public void StoreIntersectFromSets(string intoSetId, params string[] setIds);
    public void StoreUnionFromSets(string intoSetId, params string[] setIds);
    public void MoveBetweenSets(string fromSetId, string toSetId, string item);
    public string PopItemFromSet(string setId); 

  • Hash

    public int GetHashCount(string hashId);
    public bool HashContainsEntry(string hashId, string key);
    public bool RemoveEntryFromHash(string hashId, string key);
    public bool SetEntryInHash(string hashId, string key, string value);
    public List GetHashKeys(string hashId);
    public List GetHashValues(string hashId);
    public Dictionary GetAllEntriesFromHash(string hashId);
    public string GetValueFromHash(string hashId, string key);
    public List GetValuesFromHash(string hashId, params string[] keys);
    public T GetFromHash(object id);

  • SortedSet(zset)

    public int GetSortedSetCount(string setId);
    public bool SortedSetContainsItem(string setId, string value);
    public bool RemoveItemFromSortedSet(string setId, string value);
    public bool AddItemToSortedSet(string setId, string value [, double score]);
    public bool AddRangeToSortedSet(string setId, List values [, double score]);
    public List GetRangeFromSortedSet(string setId, int fromRank, int toRank);
    public IDictionary GetRangeWithScoresFromSortedSet(string setId, int fromRank, int toRank);
    public List GetAllItemsFromSortedSetDesc;
    public IDictionary GetAllWithScoresFromSortedSet(string setId);

其中,方法 public IRedisTypedClient<T>
As<T>(); 搭配接口 public interface
IRedisTypedClient<T> : IEntityStore<T>{} 和
public interface IEntityStore<T>{}
中提供的方法可以完成各种操作。

在V3.0版本的基础上,其V4.0版本 ServiceStack.Redis-4.0.52 提供了更多的方法:

  • Scan方法;
  • 获取设置配置信息;
  • 支持Lua脚本; 

    public RedisText Custom(params object[] cmdWithArgs); // 执行命令
    public RedisClient CloneClient();
    public string GetClient();
    public void SetClient(string name);
    public void KillClient(string address);
    public void ChangeDb(long db);
    public DateTime GetServerTime();
    public DateTime ConvertToServerDate(DateTime expiresAt);
    public List> GetClientsInfo();
    public string GetConfig(string configItem);
    public void SetConfig(string configItem, string value);
    public void SaveConfig();
    public void ResetInfoStats();

其中,Custom()方法可以执行绝大多数的Redis命令,ServiceStack.Redis.Commands定义命令,用于Custom()方法的第一个参数:

public static class Commands{   
        public static readonly byte[] CommandName;
}   

参考

  • .NET中使用Redis –
    一个Blog的Demo;
  • ServiceStack.Redis初步实践 –
    简单Blog实现; 

StackExchange.Redis

由于ServiceStack.Redis的V4.0版本商业化开始收费,推荐使用:StackExchange.Redis。

StackExchange.Redis是专为.Net/C#的Redis客户端API,目前被StackOverFlow使用、微软官方RedisSessionStateProvider也采用StackExchange.Redis实现。

StackExchange.Redis的核心是ConnectionMultiplexer类(线程安全),在命名空间StackExchange.Redis中定义,封装了Redis服务的操作细节,该类的实例被整个应用程序域共享和重用。

ConnectionMultiplexer redisClient = ConnectionMultiplexer.Connect("localhost");
IDatabase db = redisClient .GetDatabase();

其基础和应用待学习…

参考

  • StackExchange.Redis
    Client初步详解;
  • 玩转Redis;
    Redis到底该如何利用(1-3);

 


参考

  • Redis中文官方网站;Redis|菜鸟教程;Redis学习手册;
  • Redis系列(1-4);Redis学习笔记(1-11); Redis文章 –
    田琪;
  • Redis源码剖析;
  • Redis资料汇总专题;

四、其它:

/// <summary>
/// 添加单个实体到链表中
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”listId”></param>
/// <param name=”Item”></param>
/// <param name=”timeout”></param>
public void AddEntityToList<T>(string listId, T Item, int timeout
= 0)
{
IRedisTypedClient<T> iredisClient = Redis.As<T>();
if (timeout >= 0)
{
if (timeout > 0)
{
secondsTimeOut = timeout;
}
Redis.Expire(listId, secondsTimeOut);
}
IRedisList<T> redisList = iredisClient.Lists[listId];
redisList.Add(Item);
iredisClient.Save();
}

1. Snapshotting:
缺省情况下,Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
save 900 1
#在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10
#在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000
#在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

/// <summary>
/// 获取链表
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”listId”></param>
/// <returns></returns>
public IEnumerable<T> GetList<T>(string listId)
{
IRedisTypedClient<T> iredisClient = Redis.As<T>();
return iredisClient.Lists[listId];
}

2. Dump快照的机制:
1). Redis先fork子进程。
2). 子进程将快照数据写入到临时RDB文件中。
3). 当子进程完成数据写入操作后,再用临时文件替换老的文件。

/// <summary>
/// 在链表中删除单个实体
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”listId”></param>
/// <param name=”t”></param>
public void RemoveEntityFromList<T>(string listId, T t)
{
IRedisTypedClient<T> iredisClient = Redis.As<T>();
IRedisList<T> redisList = iredisClient.Lists[listId];
redisList.RemoveValue(t);
iredisClient.Save();
}

 

/// <summary>
/// 根据lambada表达式删除符合条件的实体
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”listId”></param>
/// <param name=”func”></param>
public void RemoveEntityFromList<T>(string listId, Func<T,
bool> func)
{
using (IRedisTypedClient<T> iredisClient = Redis.As<T>())
{
IRedisList<T> redisList = iredisClient.Lists[listId];
T value = redisList.Where(func).FirstOrDefault();
redisList.RemoveValue(value);
iredisClient.Save();
}
}

3. AOF文件:
上面已经多次讲过,RDB的快照定时dump机制无法保证很好的数据持久性。如果我们的应用确实非常关注此点,我们可以考虑使用Redis中的AOF机制。对于Redis服务器而言,其缺省的机制是RDB,如果需要使用AOF,则需要修改配置文件中的以下条目:
将appendonly no改为appendonly yes
从现在起,Redis在每一次接收到数据修改的命令之后,都会将其追加到AOF文件中。在Redis下一次重新启动时,需要加载AOF文件中的信息来构建最新的数据到内存中。

#endregion

4. AOF的配置:
在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。

#region 清空Redis所有数据库中的所有key
public void Flushall()
{
Redis.FlushAll();
}
#endregion

5. 如何修复坏损的AOF文件:
1). 将现有已经坏损的AOF文件额外拷贝出来一份。
2). 执行”redis-check-aof –fix
<filename>”命令来修复坏损的AOF文件。
3). 用修复后的AOF文件重新启动Redis服务器。

//释放资源
public void Dispose()
{
if (Redis != null)
{
Redis.Dispose();
Redis = null;
}
GC.Collect();
}

6. Redis的数据备份:

/// <summary>
/// 缓冲池
/// </summary>
/// <param name=”readWriteHosts”></param>
/// <param name=”readOnlyHosts”></param>
/// <returns></returns>
public static PooledRedisClientManager CreateManager(
string[] readWriteHosts, string[] readOnlyHosts)
{
return new PooledRedisClientManager(readWriteHosts, readOnlyHosts,
new RedisClientManagerConfig
{
MaxWritePoolSize = readWriteHosts.Length * 5,
MaxReadPoolSize = readOnlyHosts.Length * 5,
AutoStart = true,
});
// { RedisClientFactory =
(IRedisClientFactory)RedisCacheClientFactory.Instance.CreateRedisClient(“127.0.0.1”,
6379) };
}
}

(也可以调用save或者gbsave将数据保存到.rdb文件中 
 参考:http://www.runoob.com/redis/redis-backup.html)
  在Redis中我们可以通过copy的方式在线备份正在运行的Redis数据文件。这是因为RDB文件一旦被生成之后就不会再被修改。Redis每次都是将最新的数据dump到一个临时文件中,之后在利用rename函数原子性的将临时文件改名为原有的数据文件名。因此我们可以说,在任意时刻copy数据文件都是安全的和一致的。鉴于此,我们就可以通过创建cron
job的方式定时备份Redis的数据文件,并将备份文件copy到安全的磁盘介质中。

}

 

———————持久化过程实施———————-

0.编辑redis.conf 将redis的启动设置为后台启动(守护进程)

root@qiaozhi:/usr/local/redis# vim redis.conf 

  

图片 13

 

测试:开启redis服务并测试进程是否有redis(设置为守护进程后在开启之后写其他命令不会影响守护进程)

root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf 
3601:C 07 Nov 06:41:19.801 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3601:C 07 Nov 06:41:19.802 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=3601, just started
3601:C 07 Nov 06:41:19.802 # Configuration loaded
root@qiaozhi:/usr/local/redis# ps -ef|grep redis
root      3600  2854  0 06:41 pts/1    00:00:00 ./bin/redis-cli
root      3602     1  0 06:41 ?        00:00:00 ./bin/redis-server 127.0.0.1:6379
root      3609  3518  0 06:41 pts/0    00:00:00 grep --color=auto redis
root@qiaozhi:/usr/local/redis# 

  

杀掉进程重新开启进程测试:

root@qiaozhi:/home/qiaozhi# ps -ef|grep redis
root      3602     1  0 06:41 ?        00:00:01 ./bin/redis-server 127.0.0.1:6379
root      3639  2854  0 06:47 pts/1    00:00:00 ./bin/redis-cli
root      3677  3660  0 06:51 pts/0    00:00:00 grep --color=auto redis
root@qiaozhi:/home/qiaozhi# kill -9 3602
root@qiaozhi:/home/qiaozhi# ps -ef|grep redis
root      3639  2854  0 06:47 pts/1    00:00:00 ./bin/redis-cli
root      3680  3660  0 06:51 pts/0    00:00:00 grep --color=auto redis
root@qiaozhi:/home/qiaozhi# cd /usr/local/redis
root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf 
3683:C 07 Nov 06:52:03.260 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3683:C 07 Nov 06:52:03.261 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=3683, just started
3683:C 07 Nov 06:52:03.261 # Configuration loaded
root@qiaozhi:/usr/local/redis# 

 

1.RDB持久化测试 

 

(1)修改配置文件RDB持久化频率

默认配置:

图片 14

 

解释:将60 10000改为60 100

指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 100

    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有100个更改。

  

(2)修改rdb文件的生成目录:

默认配置:

图片 15

 

 

将rdb文件的生成目录改为/var/redis目录下(var目录用于存放系统经常变化的文件)

图片 16

 

 

 (3)重启redis服务进行测试:

 重启:(由于缺少/var/redis目录而报错)

root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf 

*** FATAL CONFIG FILE ERROR ***
Reading the configuration file, at line 253
>>> 'dbfilename /var/redis/dump.rdb'
dbfilename can't be a path, just a filename

 

删除redis目录下的dump.rdb并且创建/var/redis/目录后重启

root@qiaozhi:/usr/local/redis# ls
bin  dump.rdb  redis.conf
root@qiaozhi:/usr/local/redis# rm dump.rdb 
root@qiaozhi:/usr/local/redis# ls
bin  redis.conf
root@qiaozhi:/usr/local/redis# mkdir /var/redis
root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf 
3839:C 07 Nov 07:18:40.944 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3839:C 07 Nov 07:18:40.945 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=3839, just started
3839:C 07 Nov 07:18:40.945 # Configuration loaded

 

(4)客户端测试是否有key

127.0.0.1:6379> keys *
(empty list or set)

  

(5)测试RDB持久化

  1. redis性能检测工具执行2000次命令

    root@qiaozhi:/usr/local/redis# ./bin/redis-benchmark -n 2000

  2.客户端测试是否有key

127.0.0.1:6379> keys *
1) "mylist"
2) "myset:__rand_int__"
3) "counter:__rand_int__"
4) "key:__rand_int__"

  3.关闭服务器重启

root@qiaozhi:/usr/local/redis# kill -9 3840
root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf
3913:C 07 Nov 07:30:02.418 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3913:C 07 Nov 07:30:02.418 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=3913, just started
3913:C 07 Nov 07:30:02.419 # Configuration loaded

  4.客户端查看是否有数据(服务器重启之后仍然有数据证明被持久化)

127.0.0.1:6379> keys *
1) "mylist"
2) "key:__rand_int__"
3) "counter:__rand_int__"
4) "myset:__rand_int__"

  

 

2.AOF持久化测试

 首先清空数据库:

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)

在Redis的配置文件中存在三种同步方式,它们分别是:

appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。

 

 (1)开启AOF持久化

默认配置:

图片 17

 

 修改配置开启AOF

图片 18

 

 

 (2)修改同步方式(默认就是每秒记一次)

图片 19

 

(3) 重写机制:(也就是将aof文件重新整理一下)

  达到64M之后每增长百分之百重写一次

 

图片 20

 

 

 

(4)测试AOF持久化操作

  1.   重启redis服务并查看/var/redis目录(aof文件的生成位置与上面RDB持久化的文件生成位置一个目录)

    root@qiaozhi:/usr/local/redis# ps -ef | grep redis
    root 3639 2854 0 06:47 pts/1 00:00:00 ./bin/redis-cli
    root 4089 1 0 07:57 ? 00:00:00 ./bin/redis-server 127.0.0.1:6379
    root 4137 3763 0 08:01 pts/0 00:00:00 grep –color=auto redis
    root@qiaozhi:/usr/local/redis# kill -9 4089
    root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf
    4139:C 07 Nov 08:02:16.404 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
    4139:C 07 Nov 08:02:16.404 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=4139, just started
    4139:C 07 Nov 08:02:16.404 # Configuration loaded
    root@qiaozhi:/usr/local/redis# ls /var/redis/
    appendonly.aof dump.rdb

    此时:appendonly.aof文件是空的。

  2.客户端执行两次redis操作:

127.0.0.1:6379> set a test
OK
127.0.0.1:6379> lpush lkey1 aaa bbb ccc
(integer) 3

 

   3.服务器端重新查看appendonly.aof文件

root@qiaozhi:/usr/local/redis# less /var/redis/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
set
$1
a
$4
test
*5
$5
lpush
$5
lkey1
$3
aaa
$3
bbb
$3
ccc

  解释:  *2   *3   * 5分别代表这条命令占据的行数

*2: select  0    使用默认的redis数据库

*3: set a test

*5 lpush lkey1 aaa bbb ccc

 

也就是redis会根据上面的aof文件重新执行脚本进行数据的恢复。 

   (4)服务器端重新启动redis服务

root@qiaozhi:/usr/local/redis# ps -ef | grep redis
root      3639  2854  0 06:47 pts/1    00:00:00 ./bin/redis-cli
root      4140     1  0 08:02 ?        00:00:01 ./bin/redis-server 127.0.0.1:6379
root      4195  3763  0 08:10 pts/0    00:00:00 grep --color=auto redis
root@qiaozhi:/usr/local/redis# kill -9 4140
root@qiaozhi:/usr/local/redis# ./bin/redis-server redis.conf
4197:C 07 Nov 08:10:35.648 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
4197:C 07 Nov 08:10:35.649 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=4197, just started
4197:C 07 Nov 08:10:35.649 # Configuration loaded

  (5)客户端进行查看数据(数据仍然存在说明进行了数据的持久化操作)

not connected> keys *
1) "lkey1"
2) "a"

  

 (5)测试AOF文件的重写(对AOF文件进行压缩,对数据进行处理)

  •   客户端多次操作redis

    127.0.0.1:6379> set int 1
    OK
    127.0.0.1:6379> incrby int 1
    (integer) 2
    127.0.0.1:6379> incrby int 1
    (integer) 3
    127.0.0.1:6379> incrby int 1
    (integer) 4
    127.0.0.1:6379> incrby int 1
    (integer) 5
    127.0.0.1:6379> incrby int 1
    (integer) 6
    127.0.0.1:6379> incrby int 1
    (integer) 7
    127.0.0.1:6379> incrby int 1
    (integer) 8
    127.0.0.1:6379> incrby int 1
    (integer) 9
    127.0.0.1:6379> incrby int 1
    (integer) 10
    127.0.0.1:6379> incrby int 1
    (integer) 11
    127.0.0.1:6379> incrby int 1
    (integer) 12
    127.0.0.1:6379> incrby int 1
    (integer) 13
    127.0.0.1:6379> incrby int 1
    (integer) 14
    127.0.0.1:6379> incrby int 1
    (integer) 15
    127.0.0.1:6379> incrby int 1
    (integer) 16
    127.0.0.1:6379> incrby int 1
    (integer) 17
    127.0.0.1:6379> incrby int 1
    (integer) 18
    127.0.0.1:6379> incrby int 1
    (integer) 19
    127.0.0.1:6379> incrby int 1
    (integer) 20
    127.0.0.1:6379> incrby int 1
    (integer) 21
    127.0.0.1:6379> incrby int 1
    (integer) 22
    127.0.0.1:6379> incrby int 1
    (integer) 23
    127.0.0.1:6379> incrby int 1
    (integer) 24
    127.0.0.1:6379> incrby int 1
    (integer) 25
    127.0.0.1:6379> incrby int 1
    (integer) 26
    127.0.0.1:6379> get a
    “test”
    127.0.0.1:6379> get int
    “26”

 

  •  服务器端查看appendonly.aof内容:

    root@qiaozhi:/usr/local/redis# more /var/redis/appendonly.aof
    2
    $6
    SELECT
    $1
    0
    3
    $3
    set
    $1
    a
    $4
    test
    5
    $5
    lpush
    $5
    lkey1
    $3
    aaa
    $3
    bbb
    $3
    ccc
    2
    $6
    SELECT
    $1
    0
    3
    $3
    set
    $3
    int
    $1
    1
    3
    $6
    incrby
    $3
    int
    $1
    1
    3
    $6
    incrby
    $3
    int
    $1
    1
    3
    $6
    incrby
    $3
    int
    $1
    1
    3
    $6
    incrby
    $3
    int
    $1
    1
    3
    $6
    incrby
    $3
    int
    $1
    ………….

  

  •   服务器端查看appendonly.aof大小:

图片 21

 

 

(2)客户端重写AOF文件:(整理AOF文件,只保留AOF的结果,多次increby 变为结果)

127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
127.0.0.1:6379> 

  

 (3)服务端重新查看AOF文件大小与内容

大小:

 

图片 22

 

内容:(操作已经进行合并)

root@qiaozhi:/usr/local/redis# more /var/redis/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
SET
$3
int
$2
37
*5
$5
RPUSH
$5
lkey1
$3
ccc
$3
bbb
$3
aaa
*3
$3
SET
$1
a
$4
test

  

   注意:

    如果需要将redis数据库从一台服务器复制到另一台服务器,可以将aof文件和rdb文件进行拷贝,复制到另一台机器的redis工作目录下面,默认优先加载aof文件。

图片 23

 

相关文章