目录html
redis是现在被互联网公司使用最普遍的一个中间件,咱们打开GitHub搜索redis,边能够看到,该项目的介绍是这样的:java
Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.
从这句话中,咱们能够提取其特性的关键字:node
也就是高性能,支持数据类型多。本文假设你已经了解redis的基本使用,进而讨论redis的单点,高可用,集群。redis
redis的安装十分简单,打开redis的官网 http://redis.io 。spring
tar zxvf redis-version.tar.gz
若是是 mac 电脑,安装redis将十分简单执行brew install redis
便可。数据库
安装好redis以后,咱们先不慌使用,先进行一些配置。打开redis.conf
文件,咱们主要关注如下配置:vim
port 6379 # 指定端口为 6379,也可自行修改 daemonize yes # 指定后台运行
安装好redis以后,咱们来运行一下。启动redis的命令为 :bash
redishome/bin/redis-server path/to/redis.config
服务器
假设咱们没有配置后台运行(即:daemonize no),那么咱们会看到以下启动日志:并发
93825:C 20 Jan 2019 11:43:22.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 93825:C 20 Jan 2019 11:43:22.640 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=93825, just started 93825:C 20 Jan 2019 11:43:22.640 # Configuration loaded 93825:S 20 Jan 2019 11:43:22.641 * Increased maximum number of open files to 10032 (it was originally set to 256). _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.3 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6380 | `-._ `._ / _.-' | PID: 93825 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-'
不管是否配置了后台运行,启动成功以后,咱们能够新开一个命令行窗口来操做试试。
使用命令:telnet localhost 6379
来链接redis,或者你能够直接使用代码来链接测试。链接以后,看到以下信息:
Connected to localhost. Escape character is '^]'.
咱们输入几个命令试试:
set hello world 设置key-value get hello 获取key值 expire hello 10 设置10秒过时 ttl hello 查看过时时间 del hello 删除key
如此,咱们便体验了一把redis,能够说是很是简单了。刚才咱们是使用命令行来操做redis的,下面咱们来使用代码操做一下redis,以Java
为例,咱们使用一个开源的 java - redis客户端。
打开GitHub,搜索redis,进入到项目主页以后,咱们能够看到使用方法:
加入jedis依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.0</version> <type>jar</type> <scope>compile</scope> </dependency>
编写代码以下
Jedis jedis = new Jedis("localhost",6379); jedis.set("hello", "world"); String value = jedis.get("hello"); System.out.println(value); // get world jedis.del("hello"); System.out.println(jedis.get("hello"));// get null
上面jedis操做redis的例子很简单,除了使用jedis以外,还可使用spring-redis。步骤以下
配置redis
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/> <!-- redis template definition --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnFactory"/>
编写代码
public class Example { // inject the actual template @Autowired private RedisTemplate<String, String> template; // inject the template as ListOperations // can also inject as Value, Set, ZSet, and HashOperations @Resource(name="redisTemplate") private ListOperations<String, String> listOps; public void addLink(String userId, URL url) { listOps.leftPush(userId, url.toExternalForm()); // or use template directly redisTemplate.boundListOps(userId).leftPush(url.toExternalForm()); } }
Lettuce是一个基于netty的 非阻塞的 redis客户端。支持Java8以及响应式。其官网为 https://lettuce.io/。Lettuce也能够和spring搭配使用。
使用Lettuce须要加入以下maven依赖:
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.1.5.RELEASE</version> </dependency>
基本的 get,set示例代码以下:
public class LettuceTest { public static void main(String[] args) { RedisURI uri = new RedisURI(); uri.setHost("myredishost"); uri.setPort(6379); uri.setDatabase(0); RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> syncCommands = connection.sync(); syncCommands.set("testKey", "Hello, Redis!"); System.out.println(syncCommands.get("testKey")); connection.close(); redisClient.shutdown(); } }
上面咱们启动了一台redis,并对其进行操做。固然这只是实验性的玩玩。假设咱们生产环境使用了一台redis,redis挂了怎么办?若是等到运维重启redis,并恢复好数据,可能须要花费很长时间。那么在这期间,咱们的服务是不可用的,这应该是不能容忍的。假设咱们作了主从,主库挂了以后,运维让从库接管,那么服务能够继续运行,正所谓有备无患。
redis主从配置很是简单,过程以下(ps 演示状况下主从配置在一台电脑上):
mkdir redis-master-slave cp path/to/redis/conf/redis.conf path/to/redis-master-slave master.conf cp path/to/redis/conf/redis.conf path/to/redis-master-slave slave.conf
## master.conf port 6379 ## master.conf port 6380 slaveof 127.0.0.1 6379
redis-server path/to/redis-master-slave/master.conf redis-server path/to/redis-master-slave/slave.conf
启动以后,打开两个命令行窗口,分别执行telnet localhost 6379 telnet localhost 6380
而后分别在两个窗口中执行 info
命令,能够看到
# Replication role:master # Replication role:slave master_host:127.0.0.1 master_port:6379
主从配置没问题。
而后在master 窗口执行 set 以后,到slave窗口执行get,能够get到,说明主从同步成功。
这时,咱们若是在slave窗口执行 set ,会报错:
-READONLY You can't write against a read only replica.
由于从节点是只读的。
上面咱们介绍了主从,从库做为一个“傀儡”,能够在须要的时候“顶上来”,”接盘“。咱们配置的主从是为了”有备无患“,在主redis挂了以后,能够立马切换到从redis上,可能只须要花几分钟的时间,可是仍然是须要人为操做。假设主redis在晚上23点挂了,10分钟以后你接到电话,老板让你赶忙修复,因而你从被窝爬起来整,岂不是很头疼。假如你关机了,又其余人知道服务器密码,那系统岂不是要停机一夜?太可怕了。
这个时候redis sentinel 就派上用场了。sentinel 一般翻译成哨兵,就是放哨的,这里它就是用来监控主从节点的健康状况。客户端链接redis主从的时候,先链接 sentinel,sentinel会告诉客户端主redis的地址是多少,而后客户端链接上redis并进行后续的操做。当主节点挂掉的时候,客户端就得不到链接了于是报错了,客户端从新想sentinel询问主master的地址,而后客户端获得了[新选举出来的主redis],而后又能够愉快的操做了。
为了说明sentinel的用处,咱们作个试验。配置3个redis(1主2从),1个哨兵。步骤以下:
mkdir redis-sentinel cd redis-sentinel cp redis/path/conf/redis.conf path/to/redis-sentinel/redis01.conf cp redis/path/conf/redis.conf path/to/redis-sentinel/redis02.conf cp redis/path/conf/redis.conf path/to/redis-sentinel/redis03.conf touch sentinel.conf
上咱们建立了 3个redis配置文件,1个哨兵配置文件。咱们将 redis01设置为master,将redis02,redis03设置为slave。
vim redis01.conf port 63791 vim redis02.conf port 63792 slaveof 127.0.0.1 63791 vim redis03.conf port 63793 slaveof 127.0.0.1 63791 vim sentinel.conf daemonize yes port 26379 sentinel monitor mymaster 127.0.0.1 63793 1 # 下面解释含义
上面的主从配置都熟悉,只有哨兵配置 sentinel.conf,须要解释一下:
mymaster 为主节点名字,能够随便取,后面程序里边链接的时候要用到 127.0.0.1 63793 为主节点的 ip,port 1 后面的数字 1 表示选举主节点的时候,投票数。1表示有一个sentinel赞成便可升级为master
上面咱们配置好了redis主从,1主2从,以及1个哨兵。下面咱们分别启动redis,并启动哨兵
redis-server path/to/redis-sentinel/redis01.conf redis-server path/to/redis-sentinel/redis02.conf redis-server path/to/redis-sentinel/redis03.conf redis-server path/to/redis-sentinel/sentinel.conf --sentinel
启动以后,能够分别链接到 3个redis上,执行info查看主从信息。
下面使用程序来链接哨兵,并操做redis。
public static void main(String[] args) throws Exception{ Set<String> hosts = new HashSet<>(); hosts.add("127.0.0.1:26379"); //hosts.add("127.0.0.1:36379"); 配置多个哨兵 JedisSentinelPool pool = new JedisSentinelPool("mymaster",hosts); Jedis jedis = null; for(int i=0 ;i<20;i++){ Thread.sleep(2000); try{ jedis = pool.getResource(); String v = randomString(); jedis.set("hello",v); System.out.println(v+"-->"+jedis.get("hello").equals(v)); }catch (Exception e){ System.out.println(" [ exception happened]" + e); } } }
程序很是简单,循环运行20次,链接哨兵,将随机字符串 set到redis,get结果。打印信息,异常捕获。
运行上面的程序(注意,在实验这个效果的时候,能够将sleep时间加长或者for循环增多,以防程序提早中止,不便看总体效果),而后将主redis关掉,模拟redis挂掉的状况。如今主redis为redis01,端口为63791
redis-cli -p 63791 shutdown
这个时候若是sentinel没有设置后台运行,能够在命令行窗口看到 master切换的状况日志。
# Sentinel ID is fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce # +monitor master mymaster 127.0.0.1 63791 quorum 1 * +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791 * +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 # +sdown master mymaster 127.0.0.1 63791 # +odown master mymaster 127.0.0.1 63791 #quorum 1/1 # +new-epoch 1 # +try-failover master mymaster 127.0.0.1 63791 # +vote-for-leader fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce 1 # +elected-leader master mymaster 127.0.0.1 63791 # +failover-state-select-slave master mymaster 127.0.0.1 63791 # +selected-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 * +failover-state-send-slaveof-noone slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 * +failover-state-wait-promotion slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 # +promoted-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 # +failover-state-reconf-slaves master mymaster 127.0.0.1 63791 * +slave-reconf-sent slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791 * +slave-reconf-inprog slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791 * +slave-reconf-done slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791 # +failover-end master mymaster 127.0.0.1 63791 # +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793 * +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63793 * +slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793 # +sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793 # -sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793 * +convert-to-slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
上面的日志较多,仔细找找能够看到下面几行主要的:
初始状况下,1主2从 # +monitor master mymaster 127.0.0.1 63791 quorum 1 * +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791 * +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791 发现主挂了,准备 故障转移 # +try-failover master mymaster 127.0.0.1 63791 将主切换到了 63793 即redis03 # +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793
这个日志比较晦涩,从代码运行效果看,以下:
14:45:20.675 [main] INFO redis.clients.jedis.JedisSentinelPool - Trying to find master from available Sentinels... 14:45:25.731 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Connecting to Sentinel 192.168.1.106:26379 14:45:25.770 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Found Redis master at 127.0.0.1:63792 14:45:25.771 [main] INFO redis.clients.jedis.JedisSentinelPool - Redis master running at 127.0.0.1:63792, starting Sentinel listeners... 14:45:25.871 [main] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63792 ejahaeegig-->true deeeadejjf-->true [ exception happened]redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ [ exception happened]........ 14:46:02.737 [MasterListener-mymaster-[192.168.1.106:26379]] DEBUG redis.clients.jedis.JedisSentinelPool - Sentinel 192.168.1.106:26379 published: mymaster 127.0.0.1 63792 127.0.0.1 63793. 14:46:02.738 [MasterListener-mymaster-[192.168.1.106:26379]] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63793 haiihiihbb-->true ifgebdcicd-->true aajhbjagag-->true Process finished with exit code 0
从结果看出
咱们看到最后一次运行设置的值是aajhbjagag
,咱们能够链接剩下的2台redis中的任意一台,get hello,结果确定是一致的。
上面的章节中,咱们分别学习了redis 单点,redis主从,并增长了高可用的 sentinel 哨兵模式。咱们所作的这些工做只是保证了数据备份以及高可用,目前为止咱们的程序一直都是向1台redis写数据,其余的redis只是备份而已。实际场景中,单个redis节点可能不知足要求,由于:
全部,咱们须要redis cluster 即redis集群。
Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。
Redis集群并不支持处理多个keys的命令,由于这须要在不一样的节点间移动数据,从而达不到像Redis那样的性能,在高负载的状况下可能会致使不可预料的错误.
Redis 集群经过分区来提供必定程度的可用性,在实际环境中当某个节点宕机或者不可达的状况下继续处理命令. Redis 集群的优点:
为了配置一个redis cluster,咱们须要准备至少6台redis,为啥至少6台呢?咱们能够在redis的官方文档中找到以下一句话:
Note that the minimal cluster that works as expected requires to contain at least three master nodes.
由于最小的redis集群,须要至少3个主节点,既然有3个主节点,而一个主节点搭配至少一个从节点,所以至少得6台redis。然而对我来讲,就是复制6个redis配置文件。本实验的redis集群搭建依然在一台电脑上模拟。
上面提到,配置redis集群须要至少6个redis节点。所以咱们须要准备及配置的节点以下:
主:redis01 从 redis02 slaveof redis01 主:redis03 从 redis04 slaveof redis03 主:redis05 从 redis06 slaveof redis05
mkdir redis-cluster cd redis-cluster mkdir redis01 到 redis06 6个文件夹 cp redis.conf 到 redis01 ... redis06 修改端口 分别配置3组主从关系
上面的配置完成以后,分别启动6个redis实例。配置正确的状况下,均可以启动成功。而后运行以下命令建立集群:
redis-5.0.3/src/redis-cli --cluster create 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375 127.0.0.1:6376 --cluster-replicas 1
注意,这里使用的是ip:port,而不是 domain:port ,由于我在使用 localhost:6371 之类的写法执行的时候碰到错误:
ERR Invalid node address specified: localhost:6371
执行成功以后,链接一台redis,执行 cluster info 会看到相似以下信息:
cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:1515 cluster_stats_messages_pong_sent:1506 cluster_stats_messages_sent:3021 cluster_stats_messages_ping_received:1501 cluster_stats_messages_pong_received:1515 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:3021
咱们能够看到cluster_state:ok
,cluster_slots_ok:16384
,cluster_size:3
。
上面咱们配置了一个redis集群,包含6个redis节点,3主3从。下面咱们来使用jedis来链接redis集群。代码以下:
public static void main(String[] args) { Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>(); //Jedis Cluster will attempt to discover cluster nodes automatically jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6371)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6372)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6373)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6374)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6375)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6376)); JedisCluster jc = new JedisCluster(jedisClusterNodes); jc.set("foo", "bar"); String value = jc.get("foo"); System.out.println(" ===> " + value); }
上面咱们设置了信息set foo bar
,可是不知道被设置到那一台redis上去了。请读者思考一下,咱们是集群模式,因此数据被分散放到不一样的槽中了,Redis 集群有16384个哈希槽,每一个key经过CRC16校验后对16384取模来决定放置哪一个槽.集群的每一个节点负责一部分hash槽,举个例子,好比当前集群有3个节点,那么:
看到这里你应该仍是不知道set foo bar
放到哪台redis上去了,不妨尝试链接任意一台redis探索一下,你会知道的。
至此,咱们了解并动手实践了redis的安装,redis单点,redis主从,redis 哨兵 sentinel,redis 集群cluster。
咱们来梳理一下redis主从,redis哨兵,redis机器的区别和关系。
redis主从:是备份关系, 咱们操做主库,数据也会同步到从库。 若是主库机器坏了,从库能够上。就比如你 D盘的片丢了,可是你移动硬盘里边备份有。
redis哨兵:哨兵保证的是HA,保证特殊状况故障自动切换,哨兵盯着你的“redis主从集群”,若是主库死了,它会告诉你新的老大是谁。
redis集群:集群保证的是高并发,由于多了一些兄弟帮忙一块儿扛。同时集群会致使数据的分散,整个redis集群会分红一堆数据槽,即不一样的key会放到不不一样的槽中。
主从保证了数据备份,哨兵保证了HA 即故障时切换,集群保证了高并发性。
一切动手作了才会熟悉。