Redis随记

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

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

Redis持久化方案(两种):

RDB(快照)持久化:

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

快照保存过程

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集群(Cluster,用于实现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失效检测:

一个node发送ping(心跳数据包)消息,那么接收者将会反馈pong消息;不过有时候并非如此,或许接收者将pong信息发给其他的nodes,而不是直接反馈给发送者,比如当集群中添加新的node时。

 通常一个node每秒都会随机向几个nodes发送ping,所以无论集群规模多大,每个nodes发送的ping数据包的总量是恒定的。每个node都确保尽可能的向那些在半个NODE_TIMEOUT时间内,尚未发送过ping或者接收到它们的pong消息的nodes发送ping。在NODE_TIMEOUT逾期之前,nodes也会尝试与那些通讯异常的nodes重新建立TCP链接,确保不能仅仅因为当前链接异常而认为它们就是不可达的。

集群失效检测就是,当某个master或者slave不能被大多数nodes可达时,用于故障迁移并将合适的slave提升为master。当slave提升未能有效实施时,集群将处于error状态且停止接收Client端查询。

    如上所述,每个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<HostAndPort> 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/";

                   List<String>list = 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<String,JedisPool> 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文件

例:Port7000

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 ThreadLocal<MessageDigest>md5Holder = new ThreadLocal<MessageDigest>(); 

public static final Hashing MD5 = newHashing() { 

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):

 

privatevoid initialize(List<S> shards) { 

    nodes = new TreeMap<Long, S>();//虚拟节点,采取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<Long, S> 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 )

1.  import java.io.ByteArrayInputStream;  

2. import java.io.ByteArrayOutputStream;  

3.  import java.io.IOException;  

4. import java.io.ObjectInputStream;  

5.  import java.io.ObjectOutputStream;  

6. public class SerializeUtils {  

7.  public static byte[] serialize(Object obj){  

8. byte[] bytes = null;  

9.  try {  

10.ByteArrayOutputStream baos=new ByteArrayOutputStream();;  

11. ObjectOutputStream oos=new ObjectOutputStream(baos);  

12.oos.writeObject(obj);  

13. bytes=baos.toByteArray();  

14.baos.close();  

15. oos.close();  

16.} catch (IOException e) {  

17. e.printStackTrace();  

18.}  

19. return bytes;  

20.}  

21. public static Object deSerialize(byte[] bytes){  

22.Object obj=null;  

23. try {  

24.ByteArrayInputStream bais=new ByteArrayInputStream(bytes);  

25. ObjectInputStream ois=new ObjectInputStream(bais);  

26.obj=ois.readObject();  

27. } catch (Exception e) {  

28.e.printStackTrace();  

29. }  

30.return obj;  

31. }  

32.} 

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

依赖:

<!-- 序列化工具类 -->

      <dependency>

         <groupId>com.dyuproject.protostuff</groupId>

         <artifactId>protostuff-core</artifactId>

         <version>1.0.8</version>

      </dependency>

      <dependency>

         <groupId>com.dyuproject.protostuff</groupId>

         <artifactId>protostuff-runtime</artifactId>

         <version>1.0.8</version>

      </dependency>

   </dependencies>

 

importcom.dyuproject.protostuff.LinkedBuffer;

import com.dyuproject.protostuff.ProtobufIOUtil;

importcom.dyuproject.protostuff.runtime.RuntimeSchema;

 

public classSerializeUtils {

 

    public static <T> byte[] serialize(Tt, Class<T> clazz) {

        return ProtobufIOUtil.toByteArray(t,RuntimeSchema.createFrom(clazz),

                LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));

    }

   

    public static <T> TdecodeSerialize(byte[] data,Class<T> clazz){

        RuntimeSchema<T> runtimeSchema =RuntimeSchema.createFrom(clazz);

        T t = runtimeSchema.newMessage();

        ProtobufIOUtil.mergeFrom(data, t,runtimeSchema);

        return t;

    }

 

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值