Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

基于.NetCore的Redis5.0.3(最新版)快速入门、源码解析、集群搭建与SDK使用【原创】

进击的辣条 2019-02-07 15:11:00 阅读数:455 评论数:0 点赞数:0 收藏数:0

1、【基础】redis能带给我们什么福利

Redis(Remote Dictionary Server)官网:https://redis.io/

Redis命令:https://redis.io/commandsRedis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.//------------------------------------- Redis是一个开源(BSD许可),内存数据结构存储,用作数据库,缓存和消息代理。 它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。 Redis具有内置复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel提供高可用性并使用Redis Cluster自动分区。

 

1.1、Redis前世今生

  1. 最开始使用本机内存的NativeCache(NativeCache无法分布式共享),随着网站规模越大,我们需要一个分布式的缓存产品,Memcache诞生。
    1.
    随着memcache缓存大行其道,互联网规模进一步扩大,对应用程序性能要求越来越高以及应用场景的越来越多 【09年】,比如内存数据库,异构化消息队列 等等,而原来市面上的memcache 暴露了以下几个缺点:
  2. memcache就是一个巨大的hash表,数据结构单一,我们知道编程语言中数据结构类型众多。
    数据结构类型:【List,HashSet, Dictionary, SortDictionary, BitArray, Queue, Stack, SortList。。。。】 
  3. memcache 无法持久化,导致只能作为缓存使用,重启之后数据就会丢失。
    1.
    无法做到规模化的集群,memcache可以使用 一致性hash 的方式做到一个简单的memcahce集群,非常依赖于客户端实现,也并非无损的。
    set username  jack     hash(username)=8亿 ,沿着顺时针走,碰到的第一个server节点就是要存放的节点。。。

所以我们非常渴望有一个东西可以解决上面三个问题,自己研发太费时费力,刚好redis就是为了解决这些头疼的问题。

1.2、redis给我们带来了哪些福利

  • 概况
    可以在redis官网上看到,目前redis支持的数据类型之多,非常丰富: 
    Redis数据类型 String Bitmap List(双端队列) Set Geo Hash HyperLogLogs Stream SortetSet(SkipList) C/#数据类型 String BitArray (LinkedList+Stack+Queue+List) HashSet --- Dictionary ---  --- SortDictionary(红黑树)

 

 

  • 持久化
    使用AOF追加模式,RDB模式,以及混合模式,既然能缓存,就可以当做一个memroy db使用。
  • AOF: 使用大量的操作命令进行数据恢复。
  • RDB: 内存快照磁盘化。
  • FixMode:混合两种。
  • 集群
    Redis自带的Cluster集群模式,Sentinel 和  第三方豌豆荚的Codis集群搭建。

2、【搭建】使用centos和docker化快速部署

虚拟机CentOS7安装步骤:https://www.cnblogs.com/wyt007/p/10295834.html

XShell6破解版:链接: https://pan.baidu.com/s/1YtnkN4_yAOU5Dc1j69ltrg 提取码: nchp 

2.1、centos7平台的部署

  • 安装
    首先到Redis官网获取Redis最新下载地址:http://download.redis.io/releases/redis-5.0.3.tar.gz
    然后在CentOS7上面进行安装
    mkdir /data cd/data wget http://download.redis.io/releases/redis-5.0.3.tar.gz tar xzf redis-5.0.3.tar.gz mv redis-5.0.3redis cd redis make

如果出现 gcc:命令未找到 ,安装gcc并重新执行 make

yum -y install gcc automake autoconf libtool make//如果以上命令出现[Errno 256] No more mirrors to try.执行下面命令再重新安装gcc yum clean all

如果出现:致命错误:jemalloc/jemalloc.h:没有那个文件或目录,则执行下方命令

make MALLOC=libc 

  • 这时候我们查看是否成功安装Redis(/data/redis/src/  目录下有无redis-cli 与redis-server),并将它们拷贝到上级文件夹
    cd /data/redis/src/cp redis-cli ../cp redis-server ../
  • 启动Redis
    [root@localhost src]/# cd /data/redis/[root@localhost redis]/# ./redis-server ./redis-conf

  • 查看端口
    netstat -tlnp

  • 测试存储
    [root@localhost ~]/# cd /data/redis/[root@localhost redis]/# ./redis-cli127.0.0.1:6379> setusername jack OK127.0.0.1:6379> getusername"jack" 127.0.0.1:6379>dbsize (integer)1 127.0.0.1:6379> keys /* 1) "username"
  • 退出客户端命令
    quit 
  • 配置Redis
    Redis启动完成后是无法进行外网访问的,因此我们需要修改redis.conf

protect-mode 保护模式bind 绑定网卡接口bind 127.0.0.1 => bind 0.0.0.0 protected-mode yes => protected-mode no

现实场景:redis是生产内网部署,对外不开放端口。。。

  • 需要密码验证(可选)
    修改redis.conf默认参数 /# requirepass foobared
    连接之后命令 auth
  • 修改文件存储目录rdb + logfile + aof(可选)
  • rdb 修改redis.conf默认参数 dir ./ 文件夹路径
  • logfile 修改redis.conf默认参数  logfile "" 文件名称,可以改成“redis.log”
  • 后台执行 
    修改redis.conf默认参数 daemonize no ,改成 daemonize yes 
    会生成pid文件 /var/run/redis_6379.pid 存放进程号

[root@localhost redis]/# ./redis-server ./redis.conf [root@localhost redis]/# netstat-tlnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp0 0 0.0.0.0:6379 0.0.0.0:/* LISTEN 66042/./redis-serve tcp0 0 0.0.0.0:111 0.0.0.0:/* LISTEN 1/systemd tcp0 0 0.0.0.0:6000 0.0.0.0:/* LISTEN 7748/X tcp0 0 192.168.122.1:53 0.0.0.0:/* LISTEN 7604/dnsmasq tcp0 0 0.0.0.0:22 0.0.0.0:/* LISTEN 7215/sshd tcp0 0 127.0.0.1:631 0.0.0.0:/* LISTEN 7217/cupsd tcp0 0 127.0.0.1:25 0.0.0.0:/* LISTEN 7432/master tcp0 0 127.0.0.1:6010 0.0.0.0:/* LISTEN 9283/sshd: root@pts tcp0 0 127.0.0.1:6011 0.0.0.0:/* LISTEN 11424/sshd: root@pt tcp0 0 127.0.0.1:6012 0.0.0.0:/* LISTEN 63727/sshd: root@pt tcp60 0 :::111 :::/* LISTEN 1/systemd tcp60 0 :::6000 :::/* LISTEN 7748/X tcp60 0 :::21 :::/* LISTEN 9406/vsftpd tcp60 0 :::22 :::/* LISTEN 7215/sshd tcp60 0 ::1:631 :::/* LISTEN 7217/cupsd tcp60 0 ::1:25 :::/* LISTEN 7432/master tcp60 0 ::1:6010 :::/* LISTEN 9283/sshd: root@pts tcp60 0 ::1:6011 :::/* LISTEN 11424/sshd: root@pt tcp60 0 ::1:6012 :::/* LISTEN 63727/sshd: root@pt [root@localhost redis]/# tail/var/run/redis_6379.pid66042View Code

2.2、docker上进行部署

Docker安装步骤:https://www.cnblogs.com/wyt007/p/10295834.html

  • 启动Docker
    service docker start
  • 列出容器内容
    docker ps

我们可以看到容器内是空的,我们接下来前往DockerHub下载安装redis(部分内容需要FQ)

  • 安装端口并绑定端口
    **我这里是因为已经在虚拟机安装了Redis,占用了redis的6379端口,所以用外网6378端口映射docker6379端口
    安装完成会自动启动**
    docker run --name some-redis -p 6378:6379 -d redis

这时候在再查看Docker容器

更复杂的配置,应该自己写一个redis.conf,通过docker-compose 部署进去。而不是自己敲命令。dockerfile需要拷贝redis.conf

  • 移除docker中的redis
    docker kill 90b45b58a571 docker rm 90b45b58a571
的sdk快速操作和两款可视化工具介绍

3.1、StackExchange.Redis

github地址:https://github.com/StackExchange/StackExchange.Redis/

使用文档:https://stackexchange.github.io/StackExchange.Redis/String的应用web网站上保存用户信息,模拟session。netcore 中使用redis作为分布式session共享。 {框架集成} Hash的应用 记录每个店铺的数据库连接串。(分库的场景) key: shopid  value:connectionstring Set的应用 判断某一个用户是否在黑名单中。 O(1) List的应用 消息队列  client -> 短信队列 <- 发送处理程序   -> 运营商

 

  • 安装
    Install-Package StackExchange.Redis
  • 使用示例

classProgram {static void Main(string[] args) { ConnectionMultiplexer redis= ConnectionMultiplexer.Connect("192.168.181.131:6379"); IDatabase db= redis.GetDatabase(0);//////cookie(ui,sessionid)//////redis(sessionid,userinfo) //db.StringSet("sessionid", "jack", TimeSpan.FromSeconds(5));//while (true)//{//var info = db.StringGet("sessionid");//Console.WriteLine(info);//Thread.Sleep(1000);//} ////key: shopID value: connectionstring //db.HashSet("connetions", "1", "mysql://192.168.1.1/mydb");//db.HashSet("connetions", "2", "mysql://192.168.1.2/mydb");//db.HashSet("connetions", "3", "mysql://192.168.1.3/mydb");//db.HashSet("connetions", "4", "mysql://192.168.1.4/mydb");//db.HashSet("connetions", "5", "mysql://192.168.1.5/mydb");//Console.WriteLine(db.HashGet("connetions", "3")); ////黑名单 //db.SetAdd("blacklist", "1");//db.SetAdd("blacklist", "2");//db.SetAdd("blacklist", "3");//db.SetAdd("blacklist", "4");//var r = db.SetContains("blacklist", 40); ////消息队列 //db.ListLeftPush("sms", "18721073333");//db.ListLeftPush("sms", "18521073333");//db.ListLeftPush("sms", "18121073033");//Console.WriteLine(db.ListRightPop("sms"));//Console.WriteLine(db.ListRightPop("sms"));//Console.WriteLine(db.ListRightPop("sms")); Console.ReadKey(); } }View Code

  • asp.net core使用redis存储session
    Session是我们在web开发中经常使用的对象,它默认是存在本机的,但是在ASP.NET Core中我们可以十分方便的将Session的存储介质改为分布式缓存(Redis)或者数据库(SqlServer)。分布式的缓存可以提高ASP.NET Core 应用的性能和可伸缩性 ,尤其是在托管在云中或服务器场环境中
  • 添加引用
    Microsoft.Extensions.Caching.Redis
  • 配置服务
    public voidConfigureServices(IServiceCollection services) { ...//添加了redis作为分布式缓存 services.AddDistributedRedisCache(option =>{ option.InstanceName= "session"; option.Configuration= "192.168.181.131:6379"; });//添加session services.AddSession(options =>{//options.IdleTimeout = TimeSpan.FromMinutes(10);//session活期时间//options.Cookie.HttpOnly = true;//设为httponly }); ... }public voidConfigure(IApplicationBuilder app, IHostingEnvironment env) { ...//使用session app.UseSession(); ... }
  • 设置session
    //using Microsoft.AspNetCore.Http; HttpContext.Session.SetString("userinfo", "jack");
  • 显示数据
    @using Microsoft.AspNetCore.Http; @Context.Session.GetString("userinfo") @Context.Session.Id

3.2、可视化操作

  • RedisClient
    官网:[https://github.com/caoxinyu/RedisClient
    ](https://github.com/caoxinyu/RedisClient)
  • fastoredis
    官网:[https://fastoredis.com/
    ](https://fastoredis.com/)

 

 

4、【SDK】StackExchange强类型工具使用和自己动手封装连接池

4.1、StackExchange.Redis的强类型扩展

为什么要使用强类型扩展?我们可以先看一段代码:

classProgram {static void Main(string[] args) { ConnectionMultiplexer redis= ConnectionMultiplexer.Connect("192.168.181.131:6379"); IDatabase db= redis.GetDatabase(0);var userModel = newUserModel() { UserName= "jack", Email= "sdfasdf@qq.com", IsVip= true}; db.StringSet("userinfo", JsonConvert.SerializeObject(userModel));var info = db.StringGet("userinfo");var model = JsonConvert.DeserializeObject(info); Console.ReadKey(); } }public classUserModel {public string UserName { get; set; }public string Email { get; set; }public bool IsVip { get; set; } }View Code

要存储数据先要进行序列化成String,然后进行存储,取出时又要进行反序列化,那么有没有更好的方式来处理这个问题呢? StackExchange.Redis.Extensions 为我们提供了很好的扩展

StackExchange.Redis.Extensions githun地址:https://github.com/imperugo/StackExchange.Redis.Extensions

  • 安装
    Install-Package StackExchange.Redis.Extensions.Core Install-Package StackExchange.Redis.Extensions.Newtonsoft //序列化方式
  • 使用
    var cacheClient = new StackExchangeRedisCacheClient(redis,newNewtonsoftSerializer()); cacheClient.Add("userinfo", userModel);var model = cacheClient.Get("userinfo");
  • 完整代码示例

classProgram {static void Main(string[] args) { ConnectionMultiplexer redis= ConnectionMultiplexer.Connect("192.168.181.131:6379"); IDatabase db= redis.GetDatabase(0);var userModel = newUserModel() { UserName= "jack", Email= "sdfasdf@qq.com", IsVip= true}; db.StringSet("userinfo", JsonConvert.SerializeObject(userModel));var info = db.StringGet("userinfo");var model = JsonConvert.DeserializeObject(info);var cacheClient = new StackExchangeRedisCacheClient(redis,newNewtonsoftSerializer()); cacheClient.Add("userinfo", userModel); model= cacheClient.Get("userinfo"); Console.ReadKey(); } }public classUserModel {public string UserName { get; set; }public string Email { get; set; }public bool IsVip { get; set; } }View Code

  • 缺点
    功能比底层要慢 + 功能要少。 暂时没有Stream 

4.2、StackExchange.Redis连接问题

4.2.1、Socket连接过多的问题导致sdk挂掉

  • 原因描述:作为实例变量,会有什么后果。。。 如果每次调用都new一下,会有太多的socket。。。 频繁的打开和关闭。。
  • 解决办法:
  • 全局唯一的connection
  • 自己定义connection连接池

4.2.2、自定义connection连接池

  • 创建连接池 RedisConnectionPool.cs

public classRedisConnectionPool {private static ConcurrentQueue connectionPoolQueue = new ConcurrentQueue();private static intminConnectionNum;private static intmaxConnectionNum;private static stringhost;private static intport;//通过构造函数 或者 config形式 获取 max,min host,port public static voidInitializeConnectionPool() { minConnectionNum= 10; maxConnectionNum= 100; host= "192.168.181.131"; port= 6379;for (int i = 0; i < minConnectionNum; i++) {var client =OpenConnection(host, port); PushConnection(client); } Console.WriteLine($"{0} 个 connection 初始化完毕!"); }/// 1. 如果说池中没有connection了,那么你需要OpenConnection / / 2. 如果池中获取到了connection,并且isConnected=false,那么直接close /// public staticConnectionMultiplexer GetConnection() {while (connectionPoolQueue.Count > 0) { connectionPoolQueue.TryDequeue(outConnectionMultiplexer client);if (!client.IsConnected) { client.Close();continue; }returnclient; }returnOpenConnection(host, port); }///

///1. 如果 queue的个数 >=max 直接踢掉/// ///2. client的IsConnected 如果为false, close/// /// /// public static voidPushConnection(ConnectionMultiplexer client) {if (connectionPoolQueue.Count >=maxConnectionNum) { client.Close();return; }if (!client.IsConnected) { client.Close();return; } connectionPoolQueue.Enqueue(client); }public static ConnectionMultiplexer OpenConnection(string host, intport) { ConnectionMultiplexer client= ConnectionMultiplexer.Connect($"{host}:{port}");returnclient; } }View Code

  • 使用方法

RedisConnectionPool.InitializeConnectionPool();for (int m = 0; m < 100; m++) { ConnectionMultiplexer client= null;try{ client=RedisConnectionPool.GetConnection();var db = client.GetDatabase(0); db.StringSet("username", "jack"); Console.WriteLine(db.StringGet("username") + " " +m); }finally{if (client != null) { RedisConnectionPool.PushConnection(client); } }//ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.181.131:6379");//Console.WriteLine(m); }View Code

5、【内存结构】阅读redis源码中的五大基础对象

源码由Redis官方下载下来并解压,然后用VS2017打开,源码在src文件夹下,redis存储结构:

  • RedisServer
    源码位置: src/server.h 
     redisServer 包含16个 redisDb 在 src/server.c 的 mian() 构造函数中,查看 void initServer(void) ,可以看到创建16个DB

我们可以看到 server.dbnum 默认值为16

 

  • RedisDb
    源码位置: src/server.h 
    我们可以看到 dict /*dict 数据字典,过期时间,长度等等

 

  • redisObject
    源码位置: src/server.h 

    我们可以看到有个 /*ptr 属性,指向 sds(sds.h)、quicklist(quicklist.h)、dict(dict.h)、rax(rax.h) 
    可以在redis-cli中查看redisObject属性
  • sds
    源码位置: sds.h 
    sds => char[] 中了一个封装,把内存优化到了极致

typedef char /sds;//Note: sdshdr5 is never used, we just access the flags byte directly. / However is here to document the layout of type 5 SDS strings.// structattribute ((packed)) sdshdr5 { unsignedchar flags; //3 lsb of type, and 5 msb of string length// charbuf[]; };struct__attribute__ ((__packed__)) sdshdr8 { uint8_t len;//*used/*/uint8_t alloc;//*excluding the header and null terminator/*/unsignedchar flags; //*3 lsb of type, 5 unused bits/*/ charbuf[]; };struct__attribute__ ((__packed__)) sdshdr16 { uint16_t len;//*used/*/uint16_t alloc;//*excluding the header and null terminator/*/unsignedchar flags; //*3 lsb of type, 5 unused bits/*/ charbuf[]; };struct__attribute__ ((__packed__)) sdshdr32 { uint32_t len;//*used/*/uint32_t alloc;//*excluding the header and null terminator/*/unsignedchar flags; //*3 lsb of type, 5 unused bits/*/ charbuf[]; };struct__attribute__ ((__packed__)) sdshdr64 { uint64_t len;//*used/*/uint64_t alloc;//*excluding the header and null terminator/*/unsignedchar flags; //*3 lsb of type, 5 unused bits/*/ charbuf[]; };View Code

 

  • redisClient
    源码位置: src/server.h ,包含三大重要参数:
  • redisDb /*db 要进行操作的数据库
  • int argc 命令的数量
  • robj //argv 命令的所有参数
  • 查询示例
    setname jack ↓↓↓↓↓↓↓ argv[0]=setargv[1]=name argv[2]=jack ↓↓↓↓↓↓↓ commandTables [ {set =>setCommand} {get =>getCommand} ]

6、【String】字符串命令介绍和源码阅读及秒杀和防重验证sdk实践

6.1、String中常见命令详解

Redis中String命令:https://redis.io/commands/#stringRedis命令 incr decr incrby decrby C/#命令 ++ -- Interlocked.Incrment Interlocked.Decrement 命令示例 redis> SET mykey "10" "OK" redis> INCR mykey (integer) 11 redis> GET mykey "11" redis> SET mykey "10" "OK" redis> DECR mykey (integer) 9 redis> SET mykey "234293482390480948029348230948" "OK" redis> DECR mykey ERR ERR value is not an integer or out of range redis> SET mykey "10" "OK" redis> INCRBY mykey 5 (integer) 15 redis> SET mykey "10" "OK" redis> DECRBY mykey 3 (integer) 7

 

 

 

 

 

 

 

 

 

  • incr命令的应用场景:【简单的解决秒杀问题】 

库存:1000   人数:10w

购买:3000 只放3000进来。

购买:1000

待付款减库存,还是购买成功减库存,这是业务的事情!

用max来进行人员的过滤。。。简单示例:

classProgram {static void Main(string[] args) { ConnectionMultiplexer redis= ConnectionMultiplexer.Connect("192.168.181.131:6379"); IDatabase db= redis.GetDatabase(0);while (true) {var num = db.StringIncrement("max1");if (num>3000) { Console.WriteLine("当前请求>3000");break; } Console.WriteLine(num); } Console.ReadKey(); } }View Code

 

  • SetNx + Expire,Set 
    应用场景:解决订单场景中的重复提交问题。  【SetNx=Set if Not eXists】如果key存在,那么value不进行复制。。。

setnx token 12345 (处理成功)setnx token 12345 (处理失败)

EXPIRE设置过期时间

转化成Set

说明10秒之内重复SET是不被允许的c/#代码示例:

classProgram {static void Main(string[] args) { ConnectionMultiplexer redis= ConnectionMultiplexer.Connect("192.168.181.131:6379"); IDatabase db= redis.GetDatabase(0);while (true) {var b = db.StringSet("token", "123456", TimeSpan.FromSeconds(10), When.NotExists); Console.WriteLine(b); Thread.Sleep(TimeSpan.FromSeconds(2)); } Console.ReadKey(); } }View Code

![](https://img2018.cnblogs.com/blog/991704/201901/991704-20190122191440

版权声明
本文为[进击的辣条]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/wyt007/p/10292258.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领