Redis入门精华(纯干货)

目录

Redis简介(个人理解):

Redis持久化方案(两种):

RDB(快照)持久化:

快照保存过程

AOF(只追加)文件:

RedisCluster集群

Redis Cluster介绍:

RedisCluster失效检测:

slave rank(次序)

集群创建(windows环境):

Redis Sentinel(哨兵机制,提供高可用方案)介绍

Redis锁实现

减少内存方法

发布订阅与模拟

将java对象存储到redis数据库


不做任何商业用途,集合各种资源整理的入门知识点,仅分享

个人整理redis,文件下载地址:http://download.csdn.net/detail/qq_26881739/9737177

Redis简介(个人理解):

redis是一个key-value存储系统,相比传统的关系型数据库,拥有高性能的特点,源于redis大部分数据缓存在内存中,读写性能非常高。但数据安全性较关系型数据库低,在存储系统发生故障时会丢失一部分数据,且不可恢复。所以redis不能提供强一致性,只能保证最终一致性

Redis支持的数据类型(五种):String(字符串、整数、浮点数)、List(列表)、Set(无序集合,不可重复)、Hash(无序散列表)、Zset(有序集合)

主要运用场景:登录服务和cookies缓存、购物车、投票(社交点赞)、共享session

 

Redis持久化方案(两种):

RDB(快照)持久化:

快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置

save 900 1  #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000

快照保存过程

  1. redis调用fork创建新的线程,现在有了子进程和父进程两个进程。
  2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于系统的写时复制机制父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。
  3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

ps:client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有client的请求,这种方式会阻塞所有client请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。

AOF(只追加)文件:

aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的“写命令”都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的“写命令”,在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)

appendonly yes              //启用aof持久化方式
# appendfsync always      //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec     //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no    //完全依赖os自主同步,性能最好,但持久化没保证

aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。

例如:我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。具体过程如下

  1. redis调用fork ,创建新的进程,现在有父子两个进程
  2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
  3. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
  4. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
  5. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

 

RedisCluster集群

RedisCluster:用于实现redis服务端自动分片以及高可用、线性扩展方案

Redis Cluster介绍:

Redis Cluster只需要运行一个进程,不过需要两个端口,第一个用来服务client,第二个用于节点间的通讯(TCP协议),如错误监控,failover,重新分片等。

节点间通讯的端口=服务client的端口 + 10000

使用集群时redis会,将16384个Hash Slot(槽)自动分配到各个master,分配的方法采用CRC-16 hash方法,所有的master分担16384个固定slot的一部分。没有分配slot的master不能存数据,连接到这个master的client会重定向到其它master 。所有分配的slot的总和必须为16384 。slot的重新分布必须手工做。

Cluster中实现了一个称为“hash tags”的概念,每个key都可以包含一个自定义的“tags”,那么在存储时将根据tags计算此key应该分布在哪个nodes上(而不是使用key计算,但是存储层面仍然是key);此特性,可以强制某些keys被保存在同一个节点上,以便于进行“multikey”操作,比如“foo”和“{foo}.student”将会被保存在同一个node上。Cluster环境下,将不支持SELECT命令,所有的key都将保存在默认的database中。

HASH_SLOT= CRC16(key) mod 16384

RedisCluster需要至少3个master,所有的数据在各个master间分片,并复制到slave。建议每一个master都配一个slave,否则发生故障的节点数据会丢失。

客户端可以连接集群中的任意节点执行查询(包括replica),但是这个节点未必有需要的数据,因此客户端需要负责去定位key所在的节点,然后再重定向到正确的节点。而这可以做到的原因,是因为通过hash计算可以得到slot,而每个实例的slot是固定的。

RedisCluster失效检测:

  1. 一个node发送ping(心跳数据包)消息,那么接收者将会反馈pong消息;不过有时候并非如此,或许接收者将pong信息发给其他的nodes,而不是直接反馈给发送者,比如当集群中添加新的node时。
  2.  通常一个node每秒都会随机向几个nodes发送ping,所以无论集群规模多大,每个nodes发送的ping数据包的总量是恒定的。每个node都确保尽可能的向那些在半个NODE_TIMEOUT时间内,尚未发送过ping或者接收到它们的pong消息的nodes发送ping。在NODE_TIMEOUT逾期之前,nodes也会尝试与那些通讯异常的nodes重新建立TCP链接,确保不能仅仅因为当前链接异常而认为它们就是不可达的。
  3. 集群失效检测就是,当某个master或者slave不能被大多数nodes可达时,用于故障迁移并将合适的slave提升为master。当slave提升未能有效实施时,集群将处于error状态且停止接收Client端查询。
  4. 如上所述,每个node都持有其已知nodes的列表包括flags,有2个flag状态:PFAIL和FAIL;PFAIL表示“可能失效”,是一种尚未完全确认的失效状态(即某个节点或者少数masters认为其不可达)。FAIL表示此node已经被集群大多数masters判定为失效(大多数master已认定为不可达,且不可达时间已达到设定值,需要failover)。

当master处于FAIL状态时,将会触发slave的选举。slaves都希望将自己提升为master,此master的所有slaves都可以开启选举,不过最终只有一个slave获胜。当如下情况满足时,slave将会开始选举:

  1. 当此slave的master处于FAIL状态
  2. 此master持有非零个slots
  3. 此slave的replication链接与master断开时间没有超过设定值,为了确保此被提升的slave的数据是新鲜的,这个时间用户可以配置。

    为了选举,第一步,就是slave自增它的currentEpoch值,然后向其他masters请求投票(需求支持,votes)。slave通过向其他masters传播“FAILOVER_AUTH_REQUEST”数据包,然后最长等待2倍的NODE_TIMEOUT时间,来接收反馈。一旦一个master向此slave投票,将会响应“FAILOVER_AUTH_ACK”,此后在2 * NODE_TIMOUT时间内,它将不会向同一个master的slaves投票;虽然这对保证安全上没有必要,但对避免多个slaves同时选举时是有帮助的。slave将会丢弃那些epoch值小于自己的currentEpoch的AUTH_ACK反馈,即不会对上一次选举的投票计数(只对当前轮次的投票计数)。一旦此slave获取了大多数master的ACKs,它将在此次选举中获胜;否则如果大多数master不可达(在2 * NODE_TIMEOUT)或者投票额不足,那么它的选举将会被中断,那么其他的slave将会继续尝试。

slave rank(次序)

当master处于FAIL状态时,slave将会随机等待一段时间,然后才尝试选举,等待的时间:

DELAY = 500ms +random(0 ~ 500ms) + SLAVE_RANK * 1000ms

一定的延迟确保我们等待FAIL状态在集群中传播,否则slave立即尝试选举(不进行等待的话),不过此时其他masters或许尚未意识到FAIL状态,可能会拒绝投票。延迟的时间是随机的,这用来“去同步”(desynchronize),避免slaves同时开始选举。SLAVE_RANK表示此slave已经从master复制数据的总量的rank。当master失效时,slaves之间交换消息以尽可能的构建rank,持有replication offset最新的rank为0,第二最新的为1,依次轮推。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。当然rank顺序也不是严格执行的,如果一个持有较小rank的slave选举失败,其他slaves将会稍后继续。

一旦,slave选举成功,它将获取一个新的、唯一的、自增的configEpoch值,此值比集群中任何masters持有的都要大,它开始宣称自己是master,并通过ping、pong数据包传播,并提供自己的新的configEpoch以及持有的slots列表。为了加快其他nodes的重新配置,pong数据包将会在集群中广播。当前node不可达的那些节点,它们可以从其他节点的ping或者pong中获知信息(gossip),并重新配置。

其他节点也会检测到这个新的master和旧master持有相同的slots,且持有更高的configEpoch,此时也会更新自己的配置(epoch,以及master);旧master的slaves不仅仅更新配置信息,也会重新配置并与新的master跟进(slave of)。

 

Redis Cluster节点down掉重启(自己写的,没有测试性能与稳定性):使用Java代码读取节点记录文件,与通过jedis获取的nodesMap进行对比,发现有节点失效,调用bat脚本进行重启,每两秒(redis的failover机制,在节点down掉之后并不能及时发现节点失效,时间过短会导致资源浪费)进行一次检测检测(在执行cmd脚本重启服务时无法得到返回的流,改用等待两秒让脚本进行执行,不然脚本会失效或者只能执行一次便死锁)

节点监控代码:

packagecom.book.controller;



importjava.io.BufferedReader;

importjava.io.File;

importjava.io.FileInputStream;

import java.io.IOException;

importjava.io.InputStreamReader;

importjava.util.ArrayList;

importjava.util.List;

importjava.util.Map;

importorg.apache.commons.pool2.impl.GenericObjectPoolConfig;

importredis.clients.jedis.HostAndPort;

importredis.clients.jedis.JedisCluster;

importredis.clients.jedis.JedisPool;

public classTestMain {


         publicstatic void main(String[] args) {

                   Set nodes =newHashSet<>();

      HostAndPortnode1 =newHostAndPort("127.0.0.1", 7001);

      HostAndPortnode2= newHostAndPort("127.0.0.1", 7002);

      HostAndPortnode3= newHostAndPort("127.0.0.1", 7003);

      HostAndPortnode4= newHostAndPort("127.0.0.1", 7004);

      HostAndPortnode5= newHostAndPort("127.0.0.1", 7005);

      HostAndPortnode6= newHostAndPort("127.0.0.1", 7006);

      nodes.add(node1);

      nodes.add(node2);

      nodes.add(node3);

      nodes.add(node4);

      nodes.add(node5);

      nodes.add(node6);

                   GenericObjectPoolConfigpoolConfig = new GenericObjectPoolConfig();

                   Runtimeruntime = Runtime.getRuntime();

                   finalString cmd = "cmd /c start D:/redis/";

                   Listlist = new ArrayList<>();

                   try{

                            Stringencoding = "GBK";

                            Filefile = new File("D://redis//redisCluster.txt");

                            if(file.isFile() && file.exists()) { // 判断文件是否存在

                                     InputStreamReaderread = new InputStreamReader(new FileInputStream(file),encoding);// 考虑到编码格式

                                     BufferedReaderbufferedReader = new BufferedReader(read);

                                     StringlineTxt = null;

                                     while((lineTxt = bufferedReader.readLine()) !=null) {

                                               list.add(lineTxt);

                                               System.out.println(lineTxt);

                                     }

                                     read.close();

                                     newThread(new Runnable() {

                                               @Override

                                               publicvoid run() {

                                                        inti = 0;

                                                        while(true) {

                                                                 JedisClusterjedisCluster =new JedisCluster(nodes, 10, 10, 10, "123456",poolConfig);

                                                                 Map nodes = jedisCluster.getClusterNodes();

                                                                 System.out.println(nodes.toString());

                                                                 if(nodes.size() < list.size()) {

                                                                           for(String port : list) {

                                                                                   if(!nodes.containsKey(port)) {

                                                                                             String[]a = port.split(":");

                                                                                             Stringportcmd = cmd + a[1] + "/start.bat";

                                                                                             Processprocess;

                                                                                            try{
                                                                                                       System.out.println("执行cmd");

                                                                                                       process= runtime.exec(portcmd);

                                                                                                       /*BufferedReaderbr = new BufferedReader(
                                                                                                                         newInputStreamReader(process.getInputStream()));

                                                                                                       Stringline = null;

                                                                                                       while((line = br.readLine()) != null) {

                                                                                                                System.out.println(line);

                                                                                                       }*/

                                                                                                       Thread.sleep(2000);

                                                                                                      if(null != process) {

                                                                                                                process.destroy();

                                                                                                                process=null;

                                                                                                       }

                                                                                             }catch (Exception e) {

                                                                                                       //TODO Auto-generated catch block

                                                                                                       e.printStackTrace();

                                                                                             }

                                                                                    }

                                                                           }

                                                                 }

                                                                 try{

                                                                           jedisCluster.close();

                                                                           Thread.sleep(2000);

                                                                           System.out.println("集群检测完毕"+ (i++));

                                                                 }catch (Exception e) {

                                                                           //TODO Auto-generated catch block

                                                                           e.printStackTrace();

                                                                 }

                                                        }

                                               }

                                     }).start();

                            }else {

                                     System.out.println("找不到指定的文件");

                            }

                   }catch (Exception e) {

                            System.out.println("读取文件内容出错");

                            e.printStackTrace();

                   }

         }



}

集群创建(windows环境):

  1. 从官网下载3.0版本之后的redis,cluster是redis3.0的新特性,之前的版本不支持

  2. 解压编译,下载下来的一般都是C源码,需要编译为可执行的exe文件(或直接下载exe文件)

  3. 创建建立集群所需要的文件目录并复制文件到各个文件夹

  4. 修改每个redis实例配置文件*.conf文件

例:

Port 7000
daemonizeyes
cluster-enabledyes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

5、启动实例

6、安装ruby、安装ruby环境(windows下为RubyDevkit)、安装gem、使用gem安装redis的ruby接口(redis官方客户端使用ruby语言编写)

示例代码:gem install redis

7、执行redis的创建集群的命令

示例代码:redis-trib.rb    create  --replicas 1  127.0.0.1:7000  127.0.0.1:7001  127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004127.0.0.1:7005 (replicas参数指定之后的1是为每个主节点创建一个从节点,本实例中前三个为主节点,后三个为从节点)

8、使用redis客户端连接集群:redis-cli-c -p 7000(登录任意的redis实例,其他的实例有redis自动寻找)

9、可以对集群添加或删除节点

 

Redis Sentinel(哨兵机制,提供高可用方案)介绍

Sentinel是一个运行的redis实例。通过订阅与发布实现对master与从节点的监控。sentinel以每10秒一次的频率向master发送info命令,通过info的回复来分析master信息,master的回复主要包含了两部分信息,一部分是master自身的信息,一部分是master所有的slave(从)的信息,所以sentinel可以自动发现master的从服务。sentinel从master那儿获取到的master自身信息以及master所有的从信息,将会更新到sentinel的sentinelState中及masters(sentinelRedisInstance结构)中的slaves字典中。

当sentinel发现master有新的从服务时,不但为从服务创建相信的实例结构,而且还会创建连接到该从服务的命令连接和订阅连接,创建命令连接后,sentinel会10秒每次的向从服务发送info命令,并从回复信息中提取从服务ID、从服务角色、从服务所属的主服务的ip及端口、主从服务的连接状态、从服务的优先级、从服务的复制偏移量等信息;创建或者更新到从服务的sentinelRedisInstance结构。

sentinel会以每两秒一次的频率向所有的被监视服务器(master和从服务)发送询问命令,命令格式如下:

publish ___sentinel___:hello s_ip s_port s_runid s_epoch m_namem_ip m_port m_epoch

        各个参数的解析如下

        s_ip:sentinel的ip

        s_port:sentinel的端口

        s_runid:sentinel运行id

        s_epoch:sentinel当前的配置纪元

        m_name:主服务器名字

        m_ip:主服务器ip

        m_port:主服务器端口

        m_epoch:主服务器纪元

sentinel与被监视的服务之间,一方面,sentinel通过命令链接发送信息到频道,另一方面,通过订阅连接从频道中接收信息。对于同一服务的多个sentinel,一个sentinel发送的信息,会被其他sentinel收到,用于更新对该sentinel以及被监视服务的认知,用于更新sentinelRedisInstance的sentinels字典信息(请看sentinelRedisInstance的数据结构)及master信息。当sentinel通过频道发现新的sentinel时,不但会更新sentinel字典,同时会与新的sentinel建立命令连接(不是建立订阅连接,因为sentinel与master及从建立订阅连接,是用来发现新的sentinel,而sentinel之间是已知的,所以不需要订阅连接),最终,监视同一个服务的多个sentinel会互联形成一个网络

sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例是否在线(对该sentinel来说是“主观在线”,即单个sentinel认为实例在线,否则为主观下线)。

当sentinel监视的某个服务主观下线后,sentinel会询问其它监视该服务的sentinel,看它们是否也认为该服务主观下线,接收到足够数量(这个值可以配置)的sentinel判断为主观下线,既任务该服务客观下线,并对其做故障转移操作

询问命令:sentinel通过发送 SENTINEL is-master-down-by-addrip port current_epoch runid,(ip:主观下线的服务id,port:主观下线的服务端口,current_epoch:sentinel的纪元,runid:*表示检测服务下线状态,如果是sentinel 运行id,表示用来选举领头sentinel)来询问其它sentinel是否同意服务下线。

一个sentinel接收另一个sentinel发来的is-master-down-by-addr后,提取参数,根据ip和端口,检测该服务时候在该sentinel主观下线,并且回复is-master-down-by-addr,回复包含三个参数:down_state(1表示已下线,0表示未下线),leader_runid(领头sentinal id),leader_epoch(领头sentinel纪元)。

sentinel接收到回复后,根据配置设置的下线最小数量,达到这个值,既认为该服务客观下线

选举领头sentinel过程:

    一个redis服务被判断为客观下线时,多个监视该服务的sentinel协商,选举一个领头sentinel,对该redis服务进行古战转移操作。选举领头sentinel遵循以下规则:

    所有的sentinel都有公平被选举成领头的资格

    所有的sentinel都有且只有一次将某个sentinel选举成领头的机会(在一轮选举中),一旦选举某个sentinel为领头,不能更改

    sentinel设置领头sentinel是先到先得,一旦当前sentinel设置了领头sentinel,以后要求设置sentinel为领头请求都会被拒绝

    每个发现服务客观下线的sentinel,都会要求其他sentinel将自己设置成领头

    当一个sentinel(源sentinel)向另一个sentinel(目sentinel)发送is-master-down-by-addr ip portcurrent_epoch runid命令的时候,runid参数不是*,而是sentinel运行id,就表示源sentinel要求目标sentinel选举其为领头

    源sentinel会检查目标sentinel对其要求设置成领头的回复,如果回复的leader_runid和leader_epoch为源sentinel,表示目标sentinel同意将源sentinel设置成领头

    如果某个sentinel被半数以上的sentinel设置成领头,那么该sentinel既为领头

如果在限定时间内,没有选举出领头sentinel,暂定一段时间,再选举

进行故障转移

sentinel状态数据结构中保存了主服务的所有从服务信息,领头sentinel按照如下的规则从从服务列表中挑选出新的主服务

删除列表中处于下线状态的从服务

删除最近5秒没有回复过领头sentinelinfo信息的从服务

    删除与已下线的主服务断开连接时间超过 down-after-milliseconds*10毫秒的从服务,这样就能保留从的数据比较新(没有过早的与主断开连接)

领头sentinel从剩下的从列表中选择优先级高的,如果优先级一样,选择偏移量最大的(偏移量大说明复制的数据比较新),如果偏移量一样,选择运行id最小的从服务

挑选出新的主服务之后,领头sentinel 向原主服务的从服务发送 slaveof 新主服务 的命令,复制新master。同理,当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主节点的从节点

Cluster和 Sentinel区别

Sentinel的目标是提供可靠的master/slave自动切换,无需sharding数据;而Cluster的目标是在多个实例分布数据,并在master发生故障时自动切换。

和Sentinel不一样,当failover发生时,只有失效的master上的key不可用,直到它的slave被升级为master。

 

客户端分片

将数据按照一定的策略"分散"存储在集群中不同的物理server上,本质上实现了"大数据"分布式存储,以及体现了"集群"的高可用性.比如1亿数据,我们按照数据的hashcode散列存储在5个server上.补充: Jedis sharding模式下,如果某个server失效,客户端并不会删除此shard,所以如果访问此shard将会抛出异常。这是为了保持所有的客户端数据视图一致性。你可能希望动态的一致性hash拓扑结构(即如果某个shard失效,shard结构则重新调整,失效的shard上的数据则被hash到其他shard上),但是很遗憾,SharedJedis客户端无法支持,如果非要支持,则需要巨大的代码调整,而且还需要引入额外的拓扑自动发现机制。(参看:redis cluster架构,已提供此问题的完善解决方案)

实现方法有三种:1、直接使用ShardedJedis 2、ShardedJedisPool 3、ShardedJedisPipeline    

客户端分片代码:

1)  hashcode取值:源码来自redis.clients.util.Hashing,Jedis中默认的hash值计算采取了MD5作为辅助

public class SerializeUtils {

    public ThreadLocal md5Holder = new ThreadLocal();

    public static final Hashing MD5 = new Hashing() {

        public long hash(String key) {
            return hash(SafeEncoder.encode(key));
        }


        public long hash(byte[] key) {
            try {
                if (md5Holder.get() == null) {
                    md5Holder.set(MessageDigest.getInstance("MD5"));
                }
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("++++ no md5 algorythmfound");
            }
            MessageDigest md5 = md5Holder.get();
            md5.reset();
            md5.update(key);
            byte[] bKey = md5.digest();//获得MD5字节序列 
            //前四个字节作为计算参数,最终获得一个32位int值. 
            //此种计算方式,能够确保key的hash值更加“随即”/“离散” 
            //如果hash值过于密集,不利于一致性hash的实现(特别是有“虚拟节点”设计时) 
            long res = ((long) (bKey[3] & 0xFF) << 24)
                | ((long) (bKey[2] & 0xFF) << 16)
                | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF);
            return res;
        }
    };
}

2)    node构建过程(redis.clients.util.Sharded):

 private void initialize(List shards) {

        nodes = new TreeMap();//虚拟节点,采取TreeMap存储:排序,二叉树

        for (int i = 0; i != shards.size(); ++i) {

            final S shardInfo = shards.get(i);

            if (shardInfo.getName() == null)

            //当没有设置“name”是,将“SHARD-NODE”作为“虚拟节点”hash值计算的参数

            //"逻辑区间步长"为160,为什么呢??

            //最终多个server的“虚拟节点”将会交错布局,不一定非常均匀。

            {
                for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {

                    nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);

                }
            } else {
                for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {

                    nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);

                }
            }

            resources.put(shardInfo, shardInfo.createResource());

        }

    }

3) node选择方式:

 public RgetShard(String key) {

        returnresources.get(getShardInfo(key));

    }

//here: 

    public SgetShardInfo(byte[] key) {

        //获取>=key的“虚拟节点”的列表 

        SortedMap tail = nodes.tailMap(algo.hash(key));

        //如果不存在“虚拟节点”,则将返回首节点。 

        if (tail.size() == 0) {

            returnnodes.get(nodes.firstKey());

        }

        //如果存在,则返回符合(>=key)条件的“虚拟节点”的第一个节点 

        return tail.get(tail.firstKey());

    }

Redis锁实现

  1. Watch key,采用乐观锁的方式监控,在数据被改变时,跑出错误,停止操作。只能监控单个key在执行任务时容易被其他用于打断操作
  2. 使用Lock锁 实现分布式锁:原理:借用redis setnx来原子性的加一个锁,然后用expire来控制过期时间,通过设置唯一的标识符,检测进程是否依然拥有锁,防治删除其他进程的锁。设置过期时间防止进程崩溃,无法释放锁,资源进入死锁状态
  3. 使用信号量机制实现,可以让用户限制一项资源最多可以被多少个进程访问,通常用于限定能够同时使用的资源数量

减少内存方法

使用压缩列表:配置限制条件,超过限制条件的数据将会使用底层结构类型.

配置信息:

List-max-ziplist-entries512

List-max-ziplist-value64

hash-max-ziplist-entries512

hash-max-ziplist-value64

zset-max-ziplist-entries128

zset-max-ziplist-value64

超过配置信息则使用链表进行存储:

讲压缩列表的长度限制在500-2000个元素之内,并将每个元素的体积限制在128字节或以下,那么压缩的性能就会在合理范围之内。一般做法是将压缩列表限制在1024个元素之内,并且每个元素体积不超过64字节,对于大多数散列应用来说,可以兼顾低内存和高性能两个优点。设置的数值过大会影响性能。

发布订阅与模拟

使用Redis可以实现消息订阅与发布,使用有序集合可以实现群组聊天系统。但原生的发布与订阅在客户端不在线的时候,收不到消息,当客户端上线时,不会收到已经发送但未接受的消息。需要使用双集合来实现。

将java对象存储到redis数据库

使用序列化和反序列化。Redis不支持直接将Java对象存储到数据库中,所以需要将java对象进行序列化得到字节数组,然后将字节数组存入到redis中,需要数据的时候就从redis数据库中取出字节数组,再经过反序列化将自己数组转换成对象使用

代码(使用JDK的SerializeUtils )

package com.tujia.common.unit;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeUtils {

    public static byte[] serialize(Object obj) {
        byte[] bytes = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            bytes = baos.toByteArray();
            baos.close();
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }

    public static Object deSerialize(byte[] bytes) {
        Object obj = null;
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            obj = ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

推荐使用Protobuf,性能比jdk自带的号,序列化后字节长度小

依赖:


      
         com.dyuproject.protostuff
         protostuff-core
         1.0.8
      

      
         com.dyuproject.protostuff
         protostuff-runtime
         1.0.8
      

 

package com.tujia.common.unit;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtobufIOUtil;
import io.protostuff.runtime.RuntimeSchema;


public class SerializeUtils {


    public static  byte[] serialize(T t, Class clazz) {
        return ProtobufIOUtil
            .toByteArray(t, RuntimeSchema.createFrom(clazz), LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
    }


    public static  T decodeSerialize(byte[] data, Class clazz) {
        RuntimeSchema runtimeSchema = RuntimeSchema.createFrom(clazz);
        T t = runtimeSchema.newMessage();
        ProtobufIOUtil.mergeFrom(data, t, runtimeSchema);
        return t;
    }


}

 

你可能感兴趣的:(Redis)