高并发下的服务器架构演变 - rookie丶 - 博客园

mikel阅读(979)

来源: 高并发下的服务器架构演变 – rookie丶 – 博客园

在如间的网络环境下,高并发的场景无处不在,特别在面试如何解决高并发是一个躲不过的问题,即使生产环境达不到那么高的qps但是也应该给自己留条后路来应对日后可能发生的高并发场景,不用匆忙的加班加点的进行重构。

在应对日常高并发场景常常会有这么几个方法:

  1. 集群&负载均衡SLB
  2. 读写分离&分库分表
  3. 缓存
  4. 异步队列(RebbitMQ)
  5. 分布式系统、微服务

接下来就由浅入深分别来介绍下这几个方法是怎么应用到服务器并且解决高并发的,首先我们先来看下最原始的也是最简单的服务器与应用程序关系。

                                                                      图1

如图1所示在一台服务器上承载了数据库、文件系统、应用程序的所有功能,这就导致即使低qps的情况下服务器的内存或者cpu占比都非常高,用过SQLServer的同僚们都知道为了达到最高效快速的数据查询、存储及运算支持SQL server默认会尽可能的占用内存及CPU来达到自己的目的,从而导致我们的应用程序在处理一些运算或者请求量相对升高时应用程序就会变得非常慢,这时候我们就该考虑升级我们现有的服务器了,当然最高效也是最便捷的方式是升级硬件(cpu、内存、硬盘),这也是最容易达到瓶颈的毕竟一台服务的硬件也是有瓶颈的而且费用也是相当相当高昂的,一般情况下我们会选择我们最开始提到解决高并发方法中分布式来升级我们图1的单一服务器系统架构。

                                                                         图2

如图2所示我们由一台服务器转为三台服务器互相协作的方式来处理每次请求,这也是简单的分布式系统每台服务器各司其职再也不会发生单一应用占用大量cpu或内存的情况导致请求变得缓慢,但是就图2而言的服务器架构的承载能力也是非常有限的,当请求量上升后可能就扛不住宕机了。

一般这时候我们就要分析发生宕机的原因,从图2便知只有服务器A或者服务器B最有可能出现问题,根据以往的经验在请求量升高时数据库会承载绝大部分的压力,如果数据库崩了那么整个应用就会处于不可用的状态,那么为了缓解数据库的压力,我们很自然的就会想到利用缓存,这也是高并发场景下最常用也是最有效最简单的方案,利用好缓存能让你的系统的承载能力提示几倍甚至十几倍几十倍。熟悉二八原则的同僚们都知道80%请求的数据都集中在20%的数据上,虽然有些夸张但是意思就是这么个意思。缓存又分为本地缓存和分布式缓存,本着分布式的原则,我们一般都会选用分布式缓存同时也是为后期做分布式集群打下基础。

                                                                      图3

如图3所示在图2的基础上增加了一台缓存服务器D来储存我们的缓存数据,一般我们会采用redis来存放缓存数据,至于memcache现在应用的频率是非常低的。现在当请求到达应用程序时会优先访问缓存服务器D,若存在缓存数据就直接返回给客户端如果不存在缓存数据才会去数据库获取数据返回给客户端同时将数据保存到缓存服务器D设置缓存失效时间这样下次请求时就不用到数据库查询数据了从而达到减轻数据库压力的目的。虽然缓存能抵挡大部分的请求,但是我们也要做好防止缓存击穿、穿透和雪崩的问题来提升系统的稳定性。

随着业务量的增多和繁多的业务种类图3的系统架构也会慢慢达到瓶颈支撑不住多样化的业务需求,这时候我们就应该采用集群的方式来达到负载均衡的目的,将请求平均的分散到多台服务器来拓展应用程序的承载能力。

                                                                        图4

如图示4所示由服务器A-1、服务器A-2共同承载用户的请求来提高系统的承载能力也就是我们最开始说到集群,出现集群的地方必然少不了负载均衡,图4我们由nginx来实现请求的分发来达到负载均衡的目的。在设计图3的架构的时候我们有说到本地缓存,如果是采用本地缓存而不是分布式缓存那么系统架构就存在一个比较大的缺陷,因为一个请求过来是由nginx区分发的如果我们再用本地缓存那么在在服务器A-1和服务器A-2上可能存在大量相同的本地缓存这样就得不偿失了容易造成服务器资源的浪费严重的还会拖累服务器的性能,利用分布式缓存的好处在于我们不管有多少个应用服务器所有的缓存都是共享的。

图4的服务器架构应该是目前中小型应用中最常用的,而且系统的整体承载能力也相当不错,不过随着业务的发展流量与日俱增,图3的服务器架构也很难保证系统的稳定,特别是日常流量峰值的一些时段图3的系统可能时常会面临奔溃的危险,这时候就要重新分析各服务器的压力承载情况了,显而易见最可能出现问题的就是数据库服务器,终于要对数据库下手了,当下最有效的方法就是就写分离,还是遵循二八原则80%的数据操作都是查询操作。

 

                                                                        图5

如图5所示在图4的基础上由单一的数据库变为主从数据库从库负责数据的查询操作主库负责数据增删改操作,但请求操作主库后主库将操作日志执行到从库达到主从数据一致的目的,但是主从分离后不可能避免的一个问题就是主从数据一致性会有延迟,数据同步延迟的问题只能尽可能的减小数据延迟的时间,但对一些时效性非常高或者不能容忍数据延迟的请求只能做一些妥协,这类操作的crud都在主库上操作这样就避免数据延迟的问题,对一些对于数据时效性不那么严格的请求可以将这部分的查询操作由从库去承载,对于主从数据库个人以为和应用集群是一样的可以理解为集群数据库只不过在请求的分发上制定了规则(主库处理更新、从库处理查询)。

队列下回补充。。。个人对队列的理解不够透彻需要好好组织下再发表。。。

JQuery字符串替换replace方法_王安的博客-CSDN博客_jq字符串替换

mikel阅读(1303)

来源: JQuery字符串替换replace方法_王安的博客-CSDN博客_jq字符串替换

在日常的js开发中,常常会用到JQuery

当要把字符串中的内容替换时,如果使用类似C#的string.replace方法,如下

var str=’aabbccaa’;

str=str.replace(‘aa’,’dd’);

结果是  str=’ddbbccaa’

后面的aa没有被替换,原因是这个写法替换的只有第一次出现的aa,后面的就无效了。

但是,可以使用正则表达式进行替换,模式需要指定为g,表示检索全局。

代码如下:

var str=’aabbccaa’;

var reg=/aa/g;

str=str.replace(reg,’dd’);

结果是  str=’ddbbccdd’

JQuery结合正则表达式,替换的功能会变得更强大。

批量替换多个字符的代码:

var reg=/Q355B|坏|弯|合金|Q55B|Q345B|锈|修不好|短|代|先入|(检尺)/g;
res=spec.replace(reg,”);

Nodejs学习笔记(九)--- 与Redis的交互(mranney/node_redis)入门 - porschev - 博客园

mikel阅读(845)

来源: Nodejs学习笔记(九)— 与Redis的交互(mranney/node_redis)入门 – porschev – 博客园

简介和安装

  • redis简介:
  1. 开源高性能key-value存储;采用内存中(in-memory)数据集的方式,也可以采用磁盘存储方式(前者性能高,但数据可能丢失,后者正好相反)
  2. 支持字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等;支持对复杂数据结构的高速操作。
  3. 特性多,支持主从同步、pub/sub等
  4. 支持多种客户端(http://redis.io/clients

  注:应用场景没有提到,暂时没有太多实际体会,不瞎说,以免误导人,但是从它的简介和特性来说,起码缓存场景是不错的!

Redis下载地址: https://github.com/dmajkic/redis/downloads

node.js客户端:node_redis https://github.com/mranney/node_redis/

  • redis安装(Windows平台)

redis非常方便,直接下载解压就可以使用,因为开发环境是win7 64位,直接下载(示例下载的安装包:redis-2.4.5-win32-win64.zip)

  • redis运行

解压到后运行”64bit”文件夹下的redis-server.exe即可,但是这样运行会出现一个如下警告提示:

#Warning: no config file specified,using the default config. In order to specify a config file use ‘redis-server /path/to/redis.conf’

  提示也比较明显,没有明确的配置文件,使用的是默认配置,请使用‘redis-server /path/to/redis.conf’指定明确的配置文件

   根据提示运行redis成功(如下图)

  在redis-server.exe同级目录下可以看到一个redis.conf文件,这就是配置文件

  • node_redis安装
npm install redis
或者
npm install hiredis redis

我这里采用 npm install hiredis redis 安装

  注:两种都可用,区别在于性能,hiredis是非阻塞的,而且速度更快;如果安装了hiredis,node_redis则会默认以它为解析器,没安装就会用纯JavaScript解释器,对于学习或者开发环境,用哪个都无所谓

redis.createClient()连接到redis服务器

环境都准备好了,就开始写一代简单的代码测试用nodejs连接一下服务器

var redis = require('redis'),
    client = redis.createClient();

client.on('ready',function(err){
    console.log('ready');
});

从上图中可以看到运行结果,输出ready,表示成功!

对代码还是讲一下:

redis.createClient():返回的是一个RedisClient的对象,大家可以输出来看一下此对象的具体信息。

ready:Redis的Connection事件之一,当与redis服务器连接成功后会触发这个事件,此时表示已经准备好接收命令,当这个事件触发之前client命令会存在队列中,当一切准备就绪后按顺序调用

 

对于上面的几句代码就能连接成功redis服务器,原因是当前redis服务器在本地,如果不在本地,怎么连接呢?

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_OPTS = {},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.on('ready',function(res){
    console.log('ready');    
});
复制代码

也是成功!这种方式和上一种在redis.createClient()时分别传入了端口号、服务器IP和设置项

这样就可以用于连接远程的redis服务器,或者利用第三个参数进行一些配置!

redis的默认端口:6379

认证 client.auth(password, callback)

上面试过了,连接到redis服务器,可以看出我们并没有输入密码进行验证的过程就成功连接到了服务器,因为redis服务器默认不需要密码,不过这不太安全,我们肯定要设置一下密码

打开redis.conf文件,找到requirepass,取消注释,设置密码为:porschev

requirepass porschev

然后重启redis服务器;再次利用上面的代码连接到redis服务器,出现错误提示(如下图):ERR operation not permitted

那么如何连接到有密码的redis服务器呢?

简单的试了一下,有两种方法(可能有更多,没试,其实一种完全就够了,多了也没用^_^!)

方式一:通过设置redis.createClient()的第三个参数,也就是设置项来完成

 

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_PWD = 'porschev',
    RDS_OPTS = {auth_pass:RDS_PWD},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.on('ready',function(res){
    console.log('ready');    
});
复制代码

上图可以连接成功,通过设置连接设置项中的auth_pass来通过认证!

auth_pass:默认值为null,默认情况下客户端将不通过auth命令连接,如果设置了此项,客户端将调用auth命令连接

方式二:通过client.auth(password, callback)

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_PWD = 'porschev',
    RDS_OPTS = {},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.auth(RDS_PWD,function(){
    console.log('通过认证');
});

client.on('ready',function(res){
    console.log('ready');    
});
复制代码

此方法也可以成功,第一个参数为密码,第二个为回调函数!

单值set和get

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_PWD = 'porschev',    //密码    
    RDS_OPTS = {},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.auth(RDS_PWD,function(){
    console.log('通过认证');
});

client.on('connect',function(){
    client.set('author', 'Wilson',redis.print);
    client.get('author', redis.print);
    console.log('connect');
});

client.on('ready',function(err){
    console.log('ready');
});
复制代码

从输出结果可以看出,set一个值和获取这个值都成功!

代码讲一下:

client.set(key,value,[callback]):设置单个key和value,回调函数可选

client.get(key,[callback]):得到key得到value,回调函数可选(虽然可选,但不写回调函数获取又有什么意义呢^_^!)

connect:Redis的Connection事件之一,在不设置client.options.no_ready_check的情况下,客户端触发connect同时它会发出ready,如果设置了client.options.no_ready_check,当这个stream被连接时会触发connect,

这时候就可以自由尝试发命令

  redis.print:简便的回调函数,测试时显示返回值(从示例的输出结果中可以看出)

 

  其它补充说明:

client.options.no_ready_check:默认值为false,当连接到一台redis服务器时,服务器也许正在从磁盘中加载数据库,当正在加载阶段,redis服务器不会响应任何命令,node_redis会发送一个“准备确认”的INFO命令,

INFO命令得到响应表示此时服务器可以提供服务,这时node_redis会触发”ready”事件,如果该设置项设置为true,则不会有这种检查

  client.set([key,value],callback):与client.set(key,value,[callback]);效果一致(可以自行对上面示例源码进行修改进行测试),必须要有回调函数

多值get和set

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_PWD = 'porschev',    //密码    
    RDS_OPTS = {},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.auth(RDS_PWD,function(){
    console.log('通过认证');
});

client.on('connect',function(){
    client.hmset('short', {'js':'javascript','C#':'C Sharp'}, redis.print);
    client.hmset('short', 'SQL','Structured Query Language','HTML','HyperText Mark-up Language', redis.print);

    client.hgetall("short", function(err,res){
        if(err)
        {
            console.log('Error:'+ err);
            return;
        }            
        console.dir(res);
    });
});

client.on('ready',function(err){
    console.log('ready');
});
复制代码

从输出结果可以看出,set多值和获取都成功!

代码讲一下:

client.hmset(hash, obj, [callback]):赋值操作,第一个参数是hash名称;第二个参数是object对象,其中key1:value1。。,keyn:valuen形式;第三个参数是可选回调函数

client.hmset(hash, key1, val1, … keyn, valn, [callback]):与上面做用一致,第2个参数到可选回调函数之前的参数都是key1, val1, … keyn, valn形式;

client.hgetall(hash, [callback]):获取值操作,返回一个对象

 

  其它补充说明:

  console.dir()用于显示一个对象所有的属性和方法

打包执行多个命令[事务]

复制代码
var redis = require('redis'),
    RDS_PORT = 6379,        //端口号
    RDS_HOST = '127.0.1.1',    //服务器IP
    RDS_PWD = 'porschev',    //密码    
    RDS_OPTS = {},            //设置项
    client = redis.createClient(RDS_PORT,RDS_HOST,RDS_OPTS);

client.auth(RDS_PWD,function(){
    console.log('通过认证');
});

client.on('end',function(err){
    console.log('end');
});

client.on('connect',function(){
    var key = 'skills';
      client.sadd(key, 'C#','java',redis.print);
      client.sadd(key, 'nodejs');
      client.sadd(key, "MySQL");
      
      client.multi()      
      .sismember(key,'C#')
      .smembers(key)
      .exec(function (err, replies) {
            console.log("MULTI got " + replies.length + " replies");
            replies.forEach(function (reply, index) {
                console.log("Reply " + index + ": " + reply.toString());
            });
            client.quit();
    });
});
复制代码

官方有个示例,我修改一下,可能更好理解一些,下面一步步说吧!

先了解一下API再看结果

client.multi([commands]):这个标记一个事务的开始,由Multi.exec原子性的执行;github上描述是可以理解为打包,把要执行的命令存放在队列中,redis服务器会原子性的执行所有命令,node_redis接口返回一个Multi对象

Multi.exec( callback ):执行事务内所有命令;github上描述是client.multi()返回一个Multi对象,它包含了所有命令,直到Multi.exec()被调用;

Multi.exec( callback )回调函数参数err:返回null或者Array,出错则返回对应命令序列链中发生错误的错误信息,这个数组中最后一个元素是源自exec本身的一个EXECABORT类型的错误

  Multi.exec( callback )回调函数参数results:返回null或者Array,返回命令链中每个命令的返回信息

  end:redis已建立的连接被关闭时触发

client.sadd(key,value1,…valuen,[callback]):集合操作,向集合key中添加N个元素,已存在元素的将忽略;redis2.4版本前只能添加一个值

sismember(key,value,[callback]):元素value是否存在于集合key中,存在返回1,不存在返回0

smembers(key,[callback]):返回集合 key 中的所有成员,不存在的集合key也不会报错,而是当作空集返回

client.quit():与之对应的还有一个client.end()方法,相对比较暴力;client.quit方法会接收到所有响应后发送quit命令,而client.end则是直接关闭;都是触发end事件

 

再看结果应该就比较简单了,client.multi打包了sismember和smembers两个命令,执行exec方法后,回调函数得到两个回应,分别输出两个回应的结果!

其它…

redis.Debug_mode:这个在开发中可能有用,大家自行设置试一下,设置为true后,看输出

Publish / Subscribe:这个官方示例比较简单清晰,大家运行起来看一下就能理解,深入的网上还有很多用它实现的聊天、监控示例,大家看一下,如果以后觉得有必要就再做个示例分享一下

client.monitor:监控,可能以后会用到,有需要的深入研究一下,入门可以略过

 

其它redis命令还有不少,能讲到的非常有限,深入练习可以对照:http://redis.readthedocs.org/en/latest/index.html

 

参数资料:

https://github.com/mranney/node_redis/

http://redis.readthedocs.org/en/latest/index.html

http://dumbee.net/archives/114

作   者:   Porschev[钟慰]
出   处:   http://www.cnblogs.com/zhongweiv/
微   博:     http://weibo.com/porschev
欢迎任何形式的转载,但请务必注明原文详细链接

【由浅至深】redis 实现发布订阅的几种方式 - nicye - 博客园

mikel阅读(846)

来源: 【由浅至深】redis 实现发布订阅的几种方式 – nicye – 博客园

非常感谢依乐祝发表文章《.NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐》,对csredis作了一次完整的诠释。

1|0前言

提到消息队列,最熟悉无疑是 rabbitmq,它基本是业界标准的解决方案。本文详细介绍 redis 多种实现轻订阅方法,作者认为非常有趣并加以总结,希望对有需要的朋友学习 redis 功能有一定的带入作用。

2|0方法一:SUBSCRIBE + PUBLISH

//程序1:使用代码实现订阅端 var sub = RedisHelper.Subscribe((“chan1”, msg => Console.WriteLine(msg.Body))); //sub.Disponse(); //停止订阅 //程序2:使用代码实现发布端 RedisHelper.Publish(“chan1”, “111”);

优势:支持多端订阅、简单、性能高;
缺点:数据会丢失;

参考资料:http://doc.redisfans.com/pub_sub/subscribe.html

3|0方法二:BLPOP + LPUSH(争抢)

//程序1:使用代码实现订阅端 while (running) { try { var msg = RedisHelper.BLPop(5, “list1”); if (string.IsNullOrEmpty(msg) == false) { Console.WriteLine(msg); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } //程序2:使用代码实现发布端 RedisHelper.LPush(“list1”, “111”);

优势:数据不会丢失、简单、性能高;
缺点:不支持多端(存在资源争抢);

总结:为了解决方法一的痛点,我们实现了本方法,并且很漂亮的制造了一个新问题(不支持多端订阅)。

3|1学习使用 BLPOP

BLPOP key [key …] timeout

BLPOP 是列表的阻塞式(blocking)弹出原语。

它是 LPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。

当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。

非阻塞行为

当 BLPOP 被调用时,如果给定 key 内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。

当存在多个给定 key 时, BLPOP 按给定 key 参数排列的先后顺序,依次检查各个列表。

假设现在有 job 、 command 和 request 三个列表,其中 job 不存在, command 和 request 都持有非空列表。考虑以下命令:

BLPOP job command request 0

BLPOP 保证返回的元素来自 command ,因为它是按”查找 job -> 查找 command -> 查找 request “这样的顺序,第一个找到的非空列表。

redis> DEL job command request # 确保key都被删除 (integer) 0 redis> LPUSH command “update system…” # 为command列表增加一个值 (integer) 1 redis> LPUSH request “visit page” # 为request列表增加一个值 (integer) 1 redis> BLPOP job command request 0 # job 列表为空,被跳过,紧接着 command 列表的第一个元素被弹出。 1) “command” # 弹出元素所属的列表 2) “update system…” # 弹出元素所属的值

阻塞行为

如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接,直到等待超时,或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。

超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长(block indefinitely) 。

redis> EXISTS job # 确保两个 key 都不存在 (integer) 0 redis> EXISTS command (integer) 0 redis> BLPOP job command 300 # 因为key一开始不存在,所以操作会被阻塞,直到另一客户端对 job 或者 command 列表进行 PUSH 操作。 1) “job” # 这里被 push 的是 job 2) “do my home work” # 被弹出的值 (26.26s) # 等待的秒数 redis> BLPOP job command 5 # 等待超时的情况 (nil) (5.66s) # 等待的秒数

更多学习资料:http://doc.redisfans.com/list/blpop.html

4|0方法三:BLPOP + LPUSH(非争抢)

本方法根据方法二演变而来,设计图如下:

如何实现三端订阅,都可收到消息,三端分别为 sub3, sub4, sub5:

1、sub3, sub4, sub5 使用【方法二】订阅 listkey:list1_sub3,list1_sub4,list1_sub5;

2、总订阅端订阅 listkey:list1,总订阅端收到消息后,执行 lpush list1_sub1 msg, lpush list1_sub2 msg, lpush list1_sub3 msg;

总订阅端订阅原始消息,随后将消息分发给其他订阅端,从而解决【方法二】不支持多端同时订阅的缺点。

最终实现的逻辑为:多端先争抢 list1 消息,抢到者再向其他端转发消息。

5|0测试代码

nuget Install-Package CSRedisCore

var rds = new CSRedis.CSRedisClient(“127.0.0.1:6379,password=,poolsize=50,ssl=false,writeBuffer=10240”); //sub1, sub2 争抢订阅(只可一端收到消息) var sub1 = rds.SubscribeList(“list1”, msg => Console.WriteLine($”sub1 -> list1 : {msg})); var sub2 = rds.SubscribeList(“list1”, msg => Console.WriteLine($”sub2 -> list1 : {msg})); //sub3, sub4, sub5 非争抢订阅(多端都可收到消息) var sub3 = rds.SubscribeListBroadcast(“list2”, “sub3”, msg => Console.WriteLine($”sub3 -> list2 : {msg})); var sub4 = rds.SubscribeListBroadcast(“list2”, “sub4”, msg => Console.WriteLine($”sub4 -> list2 : {msg})); var sub5 = rds.SubscribeListBroadcast(“list2”, “sub5”, msg => Console.WriteLine($”sub5 -> list2 : {msg})); //sub6 是redis自带的普通订阅 var sub6 = rds.Subscribe((“chan1”, msg => Console.WriteLine(msg.Body))); Console.ReadKey(); sub1.Dispose(); sub2.Dispose(); sub3.Dispose(); sub4.Dispose(); sub5.Dispose(); sub6.Dispose(); rds.Dispose(); return;

测试功能时,发布端可以使用 redis-cli 工具。

6|0结语

redis 功能何其多且相当好玩有趣 ,大家应尽可能多带着兴趣爱好去学习它。

若文中有不好的地方,请提出批评与改正方法,谢谢观赏。

本文使用到 CSRedisCore 的开源地址:https://github.com/2881099/csredis

快速实现Docker到Redis的连接_sannerlittle的博客-CSDN博客_docker redis 连接

mikel阅读(772)

来源: 快速实现Docker到Redis的连接_sannerlittle的博客-CSDN博客_docker redis 连接

OS:Xenial

$ docker pull redis 

运行上面的命令下载镜像,Docker daemon会自动输出该Redis镜像的来源信息、下载状态,下载完成之后系统也会显示最终状态信息。

镜像拉取完成之后,大家可以用下面的命令启动Redis容器,记得要带上“-d”参数:

$ docker run --name myredis-itsmine -d redis 

“-d”参数的作用是让Redis在后台运行,因为本例中采用这种后台运行的方式较为合适,所以这里我们写上了这个参数。如果不带 “-d”参数的话处理起来就要麻烦一些,这种情况下我们需要先停止终端的运行或者退出容器,然后才能通过宿主机来访问Redis。

下面我们要进行最重要的一步操作,连接Redis。由于我们并没有实际的需要连接到Redis的应用,所以这里我们用了redis-cli工具。大家可以在宿主机上安装redis-cli,不过我建议大家新建一个容器,将redis-cli运行在里面,然后用下面的命令把这两个容器连接起来,这样我们就可以看到详细的应用信息:

$docker run --rm -it --link myredis-itsmine:redis redis /bin/bash 

运行该命令之后我们就可以在bash命令行下面看到容器的提示信息了:

root@f75bacab2715:/data#
$ docker run --rm -it --link myredis:redis redis /bin/bash
$ root@af47015c4a76:/data# redis-cli -h redis -p 6379
$ redis:6379> ping
$ PONG
$ redis:6379> set "Abc" 123
$ OK
$ redis:6379> get "Abc"
$ "123"
$ redis:6379> exit
root@af47015c4a76:/data# exit
$ exit

在上面的命令中,docker run命令后面跟的“–link myredis-itsmine:redis” 参数用于创建连接,Docker收到该指令后,就会尝试将我们新建的容器连接到当前的“myredis-itsmine” 容器,同时会将新容器中的redis-cli命名为“redis”。Docker会在容器中的/etc/hosts路径下为“redis”创建一个入口,并指向“myredis-itsmine”容器的IP地址。这样我们就可以在redis-cli中直接使用“redis”作为主机名,这种做法是很方便的,我们不必再去找其他办法来“发现”Redis的IP地址,然后加以引用了。

接下来我们就可以通过set和put命令来执行Redis的存取操作了,这里我们可以用一些示例数据来做个试验。当然,在开始存取操作之前,大家还要再运行一下Redis的ping命令,验证一下Redis服务器是否已经连接上了。

Recommend: http://www.hawu.me/operation/802

DOCKER简明教程 : 通过容器连接REDIS数据库

mikel阅读(703)

本文来源:Ghostcloud翻译(http://www.ghostcloud.cn/

序言
本文重点讲解了如何通过Redis的官方镜像和Docker容器来搭建redis-cli,并将其连接到Redis镜像。首先要跟大家简单介绍一下Redis,这是一个键值存储系统,除了对场景进行缓存之外,Redis还提供了很多强大的功能,因此也目前是非常受欢迎的一个数据库。

Docker镜像仓库简介
大家可以在Docker Hub里搜索到目前所有的主流应用和服务的镜像,像Python语言、MySQL数据库等等镜像在Docker Hub里面都有。而且Docker Hub里面的镜像数量非常多,不管我们搜什么关键词,都能搜出来一大堆结果。之所以会这样,是因为Docker官方镜像仓库的宗旨就是将已知来源、满足一定质量标准的Docker镜像组织在一起提供给用户。一般情况下,我建议大家都尽量使用Docker Hub提供的官方镜像,大家可以在查询结果列表中看到当前分类下有哪些官方镜像,一般官方镜像都会在列表中置顶显示,而且会有标有“官方”字样。

从官方镜像仓库pull镜像的时候,用户名的部分可以为空,也可以设置为library,比如说拉取 Casandra镜像的时候就可以设置成从Apache Cassandra项目获取。大家也可以在自己的终端上运行下面的命令,在Docker Hub中查找Cassandra镜像:

$docker search Cassandra

通过这种方式对Docker Hub进行查询的时候,系统会返回一条消息,提示用户“你所拉取的镜像已通过验证”,看到这条信息就表示镜像的校验码通过了Docker daemon的验证,来源是可靠的。

快速实现Docker到Redis的连接
闲话少说,我们下面就进入实战教程。首先运行下面命令,从Docker Hub拉取Redis镜像:

$ docker pull redis

运行上面的命令下载镜像,Docker daemon会自动输出该Redis镜像的来源信息、下载状态,下载完成之后系统也会显示最终状态信息。

镜像拉取完成之后,大家可以用下面的命令启动Redis容器,记得要带上“-d”参数:

$ docker run –name myredis-itsmine -d redis

“-d”参数的作用是让Redis在后台运行,因为本例中采用这种后台运行的方式较为合适,所以这里我们写上了这个参数。如果不带 “-d”参数的话处理起来就要麻烦一些,这种情况下我们需要先停止终端的运行或者退出容器,然后才能通过宿主机来访问Redis。

下面我们要进行最重要的一步操作,连接Redis。由于我们并没有实际的需要连接到Redis的应用,所以这里我们用了redis-cli工具。大家可以在宿主机上安装redis-cli,不过我建议大家新建一个容器,将redis-cli运行在里面,然后用下面的命令把这两个容器连接起来,这样我们就可以看到详细的应用信息:

$docker run –rm -it –link myredis-itsmine:redis redis /bin/bash

运行该命令之后我们就可以在bash命令行下面看到容器的提示信息了:

root@f75bacab2715:/data#
$ docker run –rm -it –link myredis:redis redis /bin/bash
$ root@af47015c4a76:/data# redis-cli -h redis -p 6379
$ redis:6379> ping
$ PONG
$ redis:6379> set “Abc” 123
$ OK
$ redis:6379> get “Abc”
$ “123”
$ redis:6379> exit
root@af47015c4a76:/data# exit
$ exit

在上面的命令中,docker run命令后面跟的“–link myredis-itsmine:redis” 参数用于创建连接,Docker收到该指令后,就会尝试将我们新建的容器连接到当前的“myredis-itsmine” 容器,同时会将新容器中的redis-cli命名为“redis”。Docker会在容器中的/etc/hosts路径下为“redis”创建一个入口,并指向“myredis-itsmine”容器的IP地址。这样我们就可以在redis-cli中直接使用“redis”作为主机名,这种做法是很方便的,我们不必再去找其他办法来“发现”Redis的IP地址,然后加以引用了。

接下来我们就可以通过set和put命令来执行Redis的存取操作了,这里我们可以用一些示例数据来做个试验。当然,在开始存取操作之前,大家还要再运行一下Redis的ping命令,验证一下Redis服务器是否已经连接上了。

本文讲述了如何通过容器来实现Redis数据库的连接,看到这里,大家是否已经对容器的网络连接有个初步的概念了?新版的Docker在网络功能方面也做出了一定的改进,相信在不久的将来,所有用户都可以很方便地通过Docker容器实现应用和服务的互连。

Redis安装完后redis-cli无法使用(redis-cli: command not found)_坤哥的博客-CSDN博客

mikel阅读(718)

来源: Redis安装完后redis-cli无法使用(redis-cli: command not found)_坤哥的博客-CSDN博客

之前安装redis后客户端无法使用,即redis-cli执行后报找不到的错误。这主要是安装redis的时候没有把客户端装上,在StackOverFlow上找到了一种只安装redis cli的方法,这里跟大家分享下。

wget http://download.redis.io/redis-stable.tar.gz(下载redis-cli的压缩包)
tar xvzf redis-stable.tar.gz(解压)
cd redis-stable(进入redis-stable目录)
make(安装)
sudo cp src/redis-cli /usr/local/bin/(将redis-cli拷贝到bin下,让redis-cli指令可以在任意目录下直接使用)

按照上面的指令执行之后redis-cli就可以正常执行了,注意上面的几条指令必须都执行,make是单独的一条。
————————————————
版权声明:本文为CSDN博主「月未明」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35981283/article/details/71631540

.NETcore使用CSRedisCore操作Redis - 心冰之海 - 博客园

mikel阅读(1498)

来源: .NETcore使用CSRedisCore操作Redis – 心冰之海 – 博客园

因为Servicestack.Redies免费每天只能调用6000次,所以找了个免费的能在.NETcore使用的插件CSRedisCore,封装了一下。

redies订阅模式的调用:RedisCoreHelper.Sub(“quote”, action);

1
2
3
4
5
6
7
8
9
10
11
12
13
public void action(string message)
{
    if (!message.IsNullOrEmpty() && !"null".Equals(message))
    {
        
//dosomething
               
    }
    else
    {
        //Thread.Sleep(200);
    }
}       

 

封装如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal class RedisConfigManager
{
    /// <summary>
    /// 获取配置
    /// </summary>
    /// <returns></returns>
    public static RedisConfig GetConfig()
    {
        var path = WorkPath.CurrentDirectory + "\\redis.config.json";
        Log.Info("path:"+ path);
        var json = FileManager.GetTextFromPath(path);
        if (json.IsNullOrEmpty())
            return new RedisConfig();
        var config = JsonConvert.Deserialize<RedisConfig>(json);
        return config;
    }
}

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
namespace LT.Cache
{
    public class RedisCoreHelper
    {
        static CSRedisClient redisManger = null;
        static CSRedisClient GetClient()
        {
            return redisManger;
        }
        static RedisCoreHelper()
        {
            var redisconfig = RedisConfigManager.GetConfig();
            redisManger = new CSRedisClient(redisconfig.CoreRedisServer);      //Redis的连接字符串
        }
        /// <summary>
        /// TradeManageMessage 和 TradeManageMessage:MQ队列
        /// </summary>
        /// <returns></returns>
        public static bool EnQeenTradeManageMessage(string value)
        {
            try
            {
                Log.Info("yinzhou--EnQeenTradeManageMessage:" + value);
                //从头部插入
                GetClient().LPush("TradeManageMessage", value);
                GetClient().LPush("TradeManageMessage:MQ", value);
                return true;
            }
            catch (Exception e)
            {
                Log.Error($"EnQeenTradeManageMessage:key=TradeManageMessage:MQ,value={value}", e);
                return false;
            }
        }
        /// <summary>
        /// TradeManageMessage 和 TradeManageMessage:MQ队列
        /// </summary>
        /// <returns></returns>
        public static bool EnQeenTradeManageMessage<T>(T value)
        {
            try
            {
                //从头部插入
                GetClient().LPush("TradeManageMessage", value);
                GetClient().LPush("TradeManageMessage:MQ", value);
                return true;
            }
            catch (Exception e)
            {
                Log.Error($"EnQeenTradeManageMessage:key=TradeManageMessage:MQ,value={value}", e);
                return false;
            }
        }
        public static bool EnQueen(string key, string value)
        {
            try
            {
                //从头部插入
                GetClient().LPush(key, value);
                return true;
            }
            catch (Exception e)
            {
                Log.Error($"EnQueen:key={key},value={value}", e);
                return false;
            }
        }
        public static string DeQueen(string key)
        {
            string result = "";
            try
            {
                //从尾部取值
                result = GetClient().RPop(key);
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"DeQueen:key={key}", e);
                return result;
            }
        }
    //redis订阅模式
        public  static void Sub(string key,Action<string> action)
        {
            GetClient().Subscribe((key, msg => action(msg.Body)));
        }
        public static string[] DeQueenAll(string key)
        {
            string[] result = { };
            try
            {
                long len = GetClient().LLen(key);
            
                //取出指定数量数据
                result = GetClient().LRange(key,0, len-1);
                //删除指定数据
                bool res=GetClient().LTrim(key, len, -1);
  
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"DeQueen:key={key}", e);
                return result;
            }
        }
        public static bool EnQueen<T>(string key, T value)
        {
            try
            {
                //从头部插入
                long len= GetClient().LPush(key, value);
                if(len>0)
                    return true;
                else
                    return false;
            }
            catch (Exception e)
            {
                Log.Error($"EnQueenObj:key={key},value={value}", e);
                return false;
            }
        }
        public static T DeQueen<T>(string key)
        {
            T result=default(T);
            try
            {
                //从尾部取值
                result = GetClient().RPop<T>(key);
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"DeQueen:key={key}", e);
                return result;
            }
        }
        /// <summary>
        /// 设置hash值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="field"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool SetHash(string key, string field,string value)
        {
            try
            {
                GetClient().HSet(key, field, value);
                return true;
            }
            catch (Exception e)
            {
                Log.Error($"SetHash:key={key},value={value}", e);
                return false;
            }
        }
        /// <summary>
        /// 根据表名,键名,获取hash值
        /// </summary>
        /// <param name="key">表名</param>
        /// <param name="field">键名</param>
        /// <returns></returns>
        public static string GetHash(string key,string field)
        {
            string result = "";
            try
            {
          
                result = GetClient().HGet(key,field);
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"GetHash:key={key}", e);
                return result;
            }
        }
        /// <summary>
        /// 获取指定key中所有字段
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static Dictionary<string,string> GetHashAll(string key)
        {
            try
            {
                var result = GetClient().HGetAll(key);
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"GetHash:key={key}", e);
                return new Dictionary<stringstring>();
            }
        }
        /// <summary>
        /// 根据表名,键名,删除hash值
        /// </summary>
        /// <param name="key">表名</param>
        /// <param name="field">键名</param>
        /// <returns></returns>
        public static long DeleteHash(string key, string field)
        {
            long result = 0;
            try
            {
                result = GetClient().HDel(key, field);
                return result;
            }
            catch (Exception e)
            {
                Log.Error($"GetHash:key={key}", e);
                return result;
            }
        }
        //private object deleteCache( Method method, Object[] args)
        //{
        //    object flag = false;
        //    String fieldkey = parseKey(method, args);
        //    try
        //    {
        //        if (fieldkey.equals(""))
        //        {
        //            cacheClient.del(cache.key());
        //        }
        //        else
        //        {
        //            cacheClient.hdel(cache.key(), fieldkey);
        //        }
        //        flag = true;
        //    }
        //    catch (Exception e)
        //    {
        //        //System.out.println(e.getMessage());
        //        flag = false;
        //    }
        //    return flag;
        //}
        /**
         * 获取值field
         * @param key
         * @param method
         * @param args
         * @return
         * @throws Exception
         */
        //        public string parseKey(Method method, Object[] args)
        //        {
        //            string fieldKey = "";
        //            for (int i = 0; i < method.getParameters().length; i++)
        //            {
        //                Parameter p = method.getParameters()[i];
        //                FieldAnnotation f = p.getAnnotation(FieldAnnotation.class);
        //          if(f!=null) {
        //              fieldKey+=args[i].toString()+":";
        //          }else {
        //              FieldOnlyKeyAnnotation fo = p.getAnnotation(FieldOnlyKeyAnnotation.class);
        //              if(fo != null) {
        //                  fieldKey+=args[i].toString();
        //}
        //          }
        //      }
        //      return fieldKey;
        //    }
    }
}

使用 .NET CLI 构建项目脚手架 - SpringLeee - 博客园

mikel阅读(937)

来源: 使用 .NET CLI 构建项目脚手架 – SpringLeee – 博客园

前言

在微服务场景中,开发人员分配到不同的小组,系统会拆分为很多个微服务,有一点是,每个项目都需要单元测试,接口文档,WebAPI接口等,创建新项目这些都是重复的工作,而且还要保证各个项目结构的大体一致,这时就需要一个适用于企业内部的框架模板,类似于前端的脚手架,可以做到开箱即用,注重业务功能开发,提升工作效率。

简介

NET 命令行接口 (CLI) 工具是用于开发、生成、运行和发布 .NET 应用程序的跨平台工具链。

本次主要介绍的是 dotnet new 命令,可以通过这个命令创建我们的自定义模板,我们安装完.NET SDK后,本身自带了一些项目模板,可以通过 dotnet new --list 查看已经安装的模板。

接下来,我会介绍如何构建自定义的项目模板。

准备工作

首先,我们需要准备一个简单的项目模板,我们希望以后可以通过脚手架,自动为我们生成这些项目和文件,这里面可能包含了单元测试项目,WebAPI项目等。

你也可以在这里找到项目源代码,https://github.com/SpringLeee/Dy.Template

在本地创建 Dy.Template 文件夹,并在文件夹内创建 templates 文件夹(后边所有的模板文件都在这里), 这里我创建了一个解决方案,里面包含了3个项目,WebAPI,Test 和 Task,项目结构如下:

构建模板

在 templates 文件夹内,创建一个名为 “.template.config” 的文件夹(可以通过命令 mkdir .template.config 创建, 然后进入该文件夹,再创建一个名为 “template.json” 的新文件, 文件夹结构应如下所示:

然后修改配置文件如下:

{
  "$schema": "http://json.schemastore.org/template",
  "author": "SpringLee",
  "classifications": [ "Template" ],
  "name": "Dy.Template",
  "identity": "Dy.Template", 
  "shortName": "dy-template",
  "tags": {
    "language": "C#" 
  },
  "sourceName": "Template" 
}

上面是一些基本的描述信息,需要注意的是 “sourceName” 属性,它相当于一个变量,我们通过这个属性,可以创建 Dy.Order.WebAPI, Dy.User.WebAPI 这样的项目,后边我会进行详细介绍。

打包模板

基础工作已经准备完成,我们还需要把项目打包,发布到Nuget.org 或者是公司的内部 Nuget Server,这样其他人才可以下载和安装这个模板。

你可能很熟悉在.NET 中对单个项目进行打包,比如类库,可以在VS中直接对项目使用右键打包,也可以使用dotnet pack命令,不一样的是,我们需要打包的是整个项目结构,而不是单个项目。

我们在 Dy.Template 文件夹下,创建 template-pack.csproj 文件

修改内容如下:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0.0</PackageVersion>
    <PackageId>Dy.Template</PackageId>
    <Title>Dy.Template</Title>
    <Authors>SpringLee</Authors>
    <Description>Dy.Template</Description>
    <PackageTags>dotnet-new;templates;Dy.Template</PackageTags>

    <TargetFramework>netstandard2.0</TargetFramework>

    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <NoWarn>$(NoWarn);NU5128</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
    <Compile Remove="**\*" />
  </ItemGroup>

</Project>

我们指定了程序包的基础信息,版本ID, 描述信息,包含了 templates 文件夹下的所有文件,然后排除了 bin\ obj\ 文件夹的dll文件。

然后,运行 dotnet pack 命令进行打包, 你可以在 /bin/nuget/ 文件夹找到 生成的 nupkg 文件

在win10的应用商店(Microsoft Store)安装 Nuget Package Explore

我们把生成的 nupkg 文件 丢到 Nuget Package Explore 里查看,结构如下,包含了我们的 .config 配置文件,各个项目,解决方案。

最后,你可以把程序包推送到 nuget 服务器。

安装并使用

在终端中运行 dotnet new --install Dy.Template 命令安装,安装成功后,应该可以看到下边的输出,里边包含了我们的自定义模板

运行 dotnet new Dy.Template --name=Order,–name 指定了变量值,它会自动帮我们生成 Order 项目,这很棒!

欢迎扫码关注我们的公众号 【全球技术精选】,专注国外优秀博客的翻译和开源项目分享。

[Redis知识体系] 一文全面总结Redis知识体系 - pdai - 博客园

mikel阅读(959)

来源: [Redis知识体系] 一文全面总结Redis知识体系 – pdai – 博客园

作者:@pdai
本文为作者原创,转载请注明出处:https://www.cnblogs.com/pengdai/p/14509958.html


 


♥Redis教程 – Redis知识体系详解♥

本系列主要对Redis知识体系进行详解。@pdai

知识体系

知识体系

相关文章

首先,我们通过学习Redis的概念基础,了解它适用的场景。

  • Redis入门 – Redis概念和基础
    • Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。

其次,这些适用场景都是基于Redis支持的数据类型的,所以我们需要学习它支持的数据类型;同时在redis优化中还需要对底层数据结构了解,所以也需要了解一些底层数据结构的设计和实现。

再者,需要学习Redis支持的核心功能,包括持久化,消息,事务,高可用;高可用方面包括,主从,哨兵等;高可拓展方面,比如 分片机制等。

  • Redis进阶 – 持久化:RDB和AOF机制详解
    • 为了防止数据丢失以及服务重启时能够恢复数据,Redis支持数据的持久化,主要分为两种方式,分别是RDB和AOF; 当然实际场景下还会使用这两种的混合模式。
  • Redis进阶 – 消息传递:发布订阅模式详解
    • Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
  • Redis进阶 – 事件:Redis事件机制详解
    • Redis 采用事件驱动机制来处理大量的网络IO。它并没有使用 libevent 或者 libev 这样的成熟开源方案,而是自己实现一个非常简洁的事件驱动库 ae_event。
  • Redis进阶 – 事务:Redis事务详解
    • Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
  • Redis进阶 – 高可用:主从复制详解
    • 我们知道要避免单点故障,即保证高可用,便需要冗余(副本)方式提供集群服务。而Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。本文主要阐述Redis的主从复制。
  • Redis进阶 – 高可用:哨兵机制(Redis Sentinel)详解
    • 在上文主从复制的基础上,如果注节点出现故障该怎么办呢? 在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,它有效地解决了主从复制模式下故障转移的问题。
  • Redis进阶 – 高可拓展:分片技术(Redis Cluster)详解
    • 前面两篇文章,主从复制和哨兵机制保障了高可用,就读写分离而言虽然slave节点来扩展主从的读并发能力,但是写能力和存储能力是无法进行扩展的,就只能是master节点能够承载的上限。如果面对海量数据那么必然需要构建master(主节点分片)之间的集群,同时必然需要吸收高可用(主从复制和哨兵机制)能力,即每个master分片节点还需要有slave节点,这是分布式系统中典型的纵向扩展(集群的分片技术)的体现;所以在Redis 3.0版本中对应的设计就是Redis Cluster。

最后,就是具体的实践以及实践中遇到的问题和解决方法了:在不同版本中有不同特性,所以还需要了解版本;以及性能优化,大厂实践等。

学习资料

除此之外,我还推荐你看下 极客时间 《Redis核心技术与实战》(作者:蒋德钧)的相关内容,它是我看到的为数不多的含有实战经验比较多的专栏,部分文章中图片也来源于这个系列。

更多文章请参考 [Java 全栈知识体系](https://pdai.tech)