
@[toc]
1. 引入
1.1 背景
- 随着用户量增大,请求数量也随之增大,数据库压力过大
- 多台服务器之间数据不同步
- 多台服务器之间存在锁,已经不存在互斥性

1.2 NoSQL
NoSQL(Not Only SQL)只是一种概念,泛指非关系型数据库,目的是为了和传统的关系型数据库作区分。常见的NoSQL数据库有:
- Key-Value:Redis
- 文档型:ElasticSearch
- 面向列:HBase、Cassandra
- 图形化:Neo4j
1.3 Redis介绍
Redis(Remote Dictionary Server)远程字典服务,Redis由C语言编写,它是一款基于Key-Value的基于内存的非关系型数据库。Redis还提供了多种持久化机制,性能可以达到110000/s读取数据,以及81000/s写入数据。此外,Redis还提供了主从、哨兵和集群的搭建模式,可以更加方便的横向扩展以及垂直扩展。
Redis具有如下的特点:
- 基于内存的机制使得Redis存取数据很快
- 丰富的数据结构
- 单线程,避免了线程切换和锁机制带来的性能开销
- 持久化机制
- 支持发布订阅功能
- 支持Lua脚本
- 支持分布式锁,避免多节点使用同一资源时的竞争问题,保持数据的一致性
- 支持原子操作和事务
- 支持主从复制、哨兵和集群的高可用模式
- 支持管道,一次发送,一次返回
2. Redis安装
这里介绍使用docker-compose来安装Redis,也可以下载源码包编译安装,相对较为麻烦。首先,编写docker-compose.yml文件,文件中指定相应的内容:
version: '3.1'services:redis:images: daocloud.io/library/redis:5.0.7 # 镜像源restart: always # 开机自启container_name: redis # 容器名environment:- TZ=Asia/Shanghai # 时区ports:- 6379:6379 # 端口映射
然后使用docker-compose up -d命令启动容器,docker会自动从设置的镜像源下载Redis镜像,并且启动容器。容器启动后,可以使用docker ps命令查看此时已经启动的容器。然后使用docker exec -it 容器id bash进入容器,通过Redis提供的客户端redis-cli使用redis。
也可以使用桌面客户端连接Redis。
3. Redis数据结构
Redis中常用的存储数据的数据结构有5种:
- key-string:一个key对应一个值,一般用于存储一个值
- key-hash:一个key对应一个Map,一般用于存储一个对象数据
- key-list:一个key对应一个列表,一般可用于实现栈或队列结构
- key-set:一个key对应一个无序集合,一般可用于交集、差集和并集的操作
- key-zset:一个key赌赢一个有序集合,一般可用于排行榜、积分存储等操作
另外,还有几种其他的数据结构:
- HyperLogLog:计算近似值
- GEO:地理位置
- BIT:存储一个字符串,本质上存储的是一个byte[]

4. Redis常用命令
4.1 String命令
| 操作 | 命令 |
|---|---|
| 添加值 | set key value |
| 取值 | get key |
| 批量添加值 | mset key value [keu value...] |
| 批量取值 | mget key [key...] |
| 自增1 | incr key |
| 自减1 | decr key |
| 自增指定数量 | increby key increment |
| 自减指定数量 | decrby key increment |
| 设置值的同时指定生存时间 | setex key second value |
| 设置值,如果当前key不存在的话 | setnx key value |
| 在指定key对应的value后追加内容 | append key value |
| 查看value字符串长度 | strlen key |
4.2 hash命令
| 操作 | 命令 |
|---|---|
| 存数据 | hset key field value |
| 取数据 | hget key field |
| 批量存数据 | hmset key field value[field value ...] |
| 批量取数据 | hmget key field [field ...] |
| 按指定的值自增 | hincrby key field increment |
| 设置值(如果kgey-field不存在,则正常添加;若存在,则啥事不做) | hsetnx key field value |
| 检查field是否存在 | hexists key field |
| 删除key对应的field,可删除多个 | hdel key field [field ...] |
| 获取当前hash中全部的field-vlue | hgetall key |
| 获取当前hash中全部的field | hkeys key |
| 获取当前hash中全部的values | hvals key |
| 获取当前hash结构中field的数量 | hlen key |
4.3 list命令
| 操作 | 命令 |
|---|---|
| 左侧插入数据 | lpush key value [value ...] |
| 右侧插入数据 | rpush key value [value ...] |
| 左侧插入数据(如果key存在就啥也不做) | lpushx keu value |
| 右侧插入数据(如果key存在就啥也不做) | rpushx key value |
| 存储数据(指定存储的索引位置) | lset key index value |
| 左侧弹出数据 | lpop key |
| 右侧弹出数据 | rpop key |
| 获取指定索引范围的数据(0 ~ -1) | lrange key start stop |
| 获取指定索引位置的数据 | lindex key index |
| 获取整个列表的长度 | llen key |
| 删除类表中count个的数据 | lrem key count value |
| 保留列表中指定索引范围的数据 | ltrim key start stop |
| 将列表中最后一个数据插入到另外一个列表的头部 | rpoplpush list1 list2 |
4.4 set命令
| 操作 | 命令 |
|---|---|
| 存储数据 | sadd key number [number ...] |
| 获取全部数据 | smembers key |
| 随机获取指定count数量的数据 | spop key [count] |
| 交集 | sinter set1 set2 ... |
| 并集 | sunion set1 set2 ... |
| 差集 | sdiff set1 set2 |
| 删除数据 | srem key member [member ...] |
| 查看当前set中是否包含指定值 | sismember key member |
4.5 zset命令
| 操作 | 命令 |
|---|---|
| 添加数据,score必须有,member不能重复 | zadd key score member [score member ...] |
| 修改member的score | zincrby key increment member |
| 查看指定member的score | zscore key member |
| 获取zset中数据的数量 | zcard key |
| 根据score的范围查询member的数量 | zcount key min max |
| 删除zset中的成员 | zrem key member [member ...] |
| 根据score从小到大排序,获取指定范围内的数据 | zrange key start stop [withscores] |
| 根据score从大到小排序,获取指定范围内的数据 | zrevrange key start stop [withscores] |
| 根据score的范围去获取member | zrangebyscore key min max [withscores] [limit offset count] |
| 根据score的范围去获取member | zrangebyscore key max min [withscores] [limit offset count] |
其中:
- withscore参数如果添加,则会同时返回member对应的分数score
- limit offset 类似于SQL中的limit的使用
4.6 key命令
| 操作 | 命令 | |
|---|---|---|
| 查看redis中全部的key(pattern: . xxx. *xxx) | keys pattern |
|
| 查看某一个key是否存在(1表示存在,0表示不存在) | exists key |
|
| 删除key | del key [key ...] |
|
| 设置key的生存时间,单位为秒或毫秒,表示还能或多久 | `expire key second | pexpire key milliseconds` |
| 设置key的生存时间,单位为秒或毫秒,表示能活到什么时间点 | `expireat key timestamp | pexpireat key milliseconds` |
| 查看key的剩余生存时间,单位为秒或毫秒 | `ttl key | pttl key` |
| 移除key的生存时间 | persist key |
4.7 db命令
| 操作 | 命令 |
|---|---|
| 选择数据库 | select 0~15 |
| 移动key到另一个数据库中 | move key db |
| 清空当前所在的数据库 | flushdb |
| 清空全部数据库 | flushall |
| 查看当前数据库中有多少key | dbsize |
| 查看最后一次操作的时间 | lastsave |
| 实时监控redis服务接收到的目录 | monitor |
更过命令的细节和使用,可以查看官方的API文档。
5. Java整合Redis
5.1 Jedis
Java连接Redis一种简单的方式就是使用Jedis,它也是Spring Boot 1.x底层默认使用的Redis连接选择。创建Maven项目后导入所需的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>
然后new一个Jedis对象,通过带有host和port的构造函数来实例化一个Jedis对象进行Redis的相关操作。例如,可以简单的使用set()存储数据,也可以使用get()来获取指定键的值。
@Testpublic void testSimpleJedis(){Jedis jedis = new Jedis("121.199.75.6", 6379);jedis.set("name", "Forlogen");String name = jedis.get("name");System.out.println(name);jedis.close();}
执行单元测试,控制台输出name对应的值Forlogen,并且连接Redis后也可以在数据库中看到代码中存储的name。
当然Jedis还提供了很多的其他方法来操作Redis,具体可以阅读Jedis的实现源码找到想要的方法定义。除了简单的进行基本数据类型的操作外,Jedis还可以进行对象的存取,假设定义实体类如下:
@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic class Account implements Serializable {private Integer id;private String name;private Integer money;}
导入依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency>
对象可以以byte[]的形式保存在Redis中,因此,可以使用工具类先将其序列化再执行保存操作:
@Testpublic void testObjectJedis(){Jedis jedis = new Jedis("121.199.75.6", 6379);String key = "account";Account account = new Account().builder().id(10).name("Hell").money(20000).build();byte[] byteValue = SerializationUtils.serialize(account);byte[] byteKey = SerializationUtils.serialize(key);jedis.set(byteKey, byteValue);System.out.println(SerializationUtils.deserialize(jedis.get(byteKey))); // Account(id=10, name=Hell, money=20000)jedis.close();}
执行单元测试,同样可以在控制台和Redis中看到存储的序列化后的数据。但是上述存储的序列化形式的对象在Reids中并不具有可读性,通常可以使用fastjhson将其装换为json格式,再进行存储。首先,导入fastjson的依赖:
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.41</version></dependency>
然后在代码中使用toJSONString()可以将对象转换为json字符串形式,最后进行存储即可:
@Testpublic void testJsonJedis(){Jedis jedis = new Jedis("121.199.75.6", 6379);String key = "account";Account account = new Account().builder().id(20).name("Bill").money(1000).build();String value = JSON.toJSONString(account);jedis.set(key, value);System.out.println(jedis.get(key)); // {"id":20,"money":1000,"name":"Bill"}jedis.close();}
执行单元测试,同样可以看到以JSON格式保存的结果。
另外,除了每次使用Redis时才进行一次单独的连接外,Jedis还提供了连接池的方式,只需要在JedisPool的构造函数中传入对应的配置即可,然后就可以从连接池中拿一个连接进行Redis的相关操作,最后只需要将连接归还给连接池即可。
@Testpublic void testPoolJedis(){GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();poolConfig.setMaxTotal(100); // 连接池中的最大活跃数poolConfig.setMaxIdle(10); // 最大空闲数poolConfig.setMinIdle(5); // 最小空闲数poolConfig.setMaxWaitMillis(3000); // 当连接池空时,3000s后没有获取到Jedis对象就超时// 创建连接池JedisPool jedisPool = new JedisPool(poolConfig, "121.199.75.6", 6379);// 获取一个jedis连接Jedis jedis = jedisPool.getResource();// 存储数据jedis.set("country", "China");System.out.println(jedis.get("country"));// 归还连接jedis.close();jedisPool.close();}
最后查询Redis同样可看到存储成功。
5.2 lettuce
前面说到,Springboot 1.x整合Spring-data-redis底层用的是jedis,jedis在多线程环境下是非线程安全的,使用了jedis pool连接池,为每个Jedis实例增加物理连接。而Spring Boot2.x中使用的是lettuce操作Redis,Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问。
下面使用之前的一个例子来说明一下,如何在Spring boot 2.x中选择Redis做缓存。首先依然需要导入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
然后在配置文件中进行相关的配置:
spring:datasource:username: rootpassword: rooturl: jdbc:mysql://localhost:3306/sql_store?serverTimezone=GMTdriver-class-name: com.mysql.cj.jdbc.Driverredis:host: xxx.xxxx.xxx.xxx # redis服务器ipcache:redis:time-to-live: 1000000
为了测试Redis缓存是否生效,依然使用上面的Account作为实体类,然后需要编写持久层、业务层和表现层代码,具体如下所示:
@Mapperpublic interface AccountMapper {@Select("select * from account")public List<Account> findAll();@Select("select * from account where id=#{id}")public Account findById(Integer id);}
@Servicepublic class AccountService {@AutowiredAccountMapper accountMapper;@Cacheable(value = "account")public List<Account> findALl(){System.out.println("service findAll...");List<Account> all = accountMapper.findAll();return all;}@Cacheable(value = "account", key = "#id")public Account findById(Integer id){System.out.println("service findById"+id);Account account = accountMapper.findById(id);return account;}}
@RestControllerpublic class AccountController {@AutowiredAccountService accountService;@GetMapping("/account")public List<Account> testFindAll(){List<Account> aLl = accountService.findALl();return aLl;}@GetMapping("/account/{id}")public Account testFindById(@PathVariable("id") Integer id){Account account = accountService.findById(id);return account;}}
发送请求http://localhost:8080/account执行查询所有,浏览器可以得到正确数据:
[{"id":1,"name":"Forlogen","money":1000.0},{"id":2,"name":"Kobe","money":1000.0},{"id":3,"name":"James","money":1000.0}]
并且再次发送同样的请求,从控制台可以看出并没有访问数据库,因此Redis缓存已经生效,并且可以在Redis中看到保存到数据。当然,为了数据具有可读性,可以通过自定义缓存数据格式需要自定义Redis的RedisCacheManager和RedisTemplate,并使用Jackson2JsonRedisSerializer实现序列化数据转换为json格式。
@Configurationpublic class MyRedisConfig {@Resource//lettuce客户端连接工厂private LettuceConnectionFactory lettuceConnectionFactory;// 日志private Logger logger= (Logger) LoggerFactory.getLogger(MyRedisConfig.class);// json序列化器private Jackson2JsonRedisSerializer<Account> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Account.class);//过期时间1天private Duration timeToLive = Duration.ofDays(1);@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {// Redis缓存配置RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(this.timeToLive).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())).disableCachingNullValues();//缓存配置mapMap<String,RedisCacheConfiguration> cacheConfigurationMap=new HashMap<>();//自定义缓存名,后面使用的@Cacheable的CacheNamecacheConfigurationMap.put("account",config);cacheConfigurationMap.put("default",config);//根据redis缓存配置和reid连接工厂生成redis缓存管理器RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().withInitialCacheConfigurations(cacheConfigurationMap).build();logger.debug("自定义RedisCacheManager加载完成");return redisCacheManager;}//redisTemplate模板提供给其他类对redis数据库进行操作@Bean(name = "redisTemplate")public RedisTemplate<String,Account> redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<String,Account> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(keySerializer());redisTemplate.setHashKeySerializer(keySerializer());redisTemplate.setValueSerializer(valueSerializer());redisTemplate.setHashValueSerializer(valueSerializer());logger.debug("自定义RedisTemplate加载完成");return redisTemplate;}//redis键序列化使用StrngRedisSerializerprivate RedisSerializer<String> keySerializer() {return new StringRedisSerializer();}//redis值序列化使用json序列化器private RedisSerializer<Object> valueSerializer() {return new GenericJackson2JsonRedisSerializer();}}
将自定义的RedisTemplate注册到ioc容器中后,再使用RedisTemplate或是stringRedisTemplate操作Redis就可以以json格式来进行数据的存取。
5.3 管道操作
上面操作Redis的方式在执行一个命令时,需要先发送请求到Redis服务器,这个过程会经历网络的延迟,此外,Redis服务器还需要给客户端一个响应。如果需要一次性执行多个命令,可以使用Redis提供的管道机制,先将命令都放到客户端的一个pipeline中,之后一次性的将全部的命令都发送到Redis服务器,Redis服务器可以一次性的将全部的返回结果响应给客户端。
查询Redis,此时数据库中有一个age,对应的值为23,接下来我们使用pipeline的方式来实现对其100次的自增操作。
代码如下:
@Testpublic void testPipeline(){Client client = new Client("121.199.75.6", 6379);Pipeline pipeline = new Pipeline();pipeline.setClient(client);for (int i = 0; i < 100; i++) {pipeline.incr("age");}pipeline.syncAndReturnAll();client.close();pipeline.close();}
执行单元测试可以看到,此时age对应的值已经变成了123,说明使用pipeline一次性的成功执行了100次的自增操作。
6. 安全配置
前面连接Redis只需要ip和port就可以直接连接,作为一个数据库而言,这显然是不安全的。Redis提供了AUTH机制来实现连接的安全性,通过在配置文件中设置相应的密码来确保只有输入的密码正确才能进行连接。
为了演示方便,我们通过docker-compose重启启动一个redis的容器。同时为了后续配置文件编写的方便,使用volume来进行配置文件的映射,已经使用command告诉Redis在启动时加载映射的配置文件。对应的docker-compsoe.yml文件内容如下:
[root@iZbp15ffbqqbe97j9dcf5dZ docker_redis]# cat docker-compose.ymlversion: '3.1'services:redis:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redisenvironment:- TZ=Asia/Shanghaiports:- 6379:6379volumes:- ./conf/redis.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]
然后在当前目录下使用mkdir conf创建conf目录,并且使用touch命令新建一个redis.conf配置文件。配置文件中需要设置相应的密码,内容如下:
[root@iZbp15ffbqqbe97j9dcf5dZ conf]# cat redis.confrequirepass root
最后docker-compsoe up -d启动容器。为了测试方便,我们使用桌面客户端进行连接测试,如果连接时不输入密码,可以看到此时已经无法成功连接。
而输入了设置的密码后就可以成功连接了。
之后在使用redis-cli连接Redis前,就需要先输入auth root才能成功的进行后续的操作。
此外,还可以在不修改配置文件的前提下,在第一次连接Redis后,使用
`Config set requirepass root命令的方式来设置密码。
7. 持久化
7.1 RDB
RBD是Redis默认的持久化机制,它比较快,而且存储格式为二进制文件,便于持久化文件的传输,但它无法保证数据的绝对安全。Redis中启动RDB持久化只需要在配置文件中添加相关的配置项,例如:
[root@iZbp15ffbqqbe97j9dcf5dZ conf]# cat redis.confrequirepass root# RDB# 900s之内有key发生改变就执行RDB持久化save 900 1save 300 10save 60 10000# 开启RDB持久化压缩rdbcompression yes# RDB持久化文件名dbfilename redis.db
然后重启容器,当执行的条件有一个被触发时,在相应的目录下就会看到redis.db的持久化文件。如果后续重启容器,仍然可以从文件中得到对应的数据。
[root@iZbp15ffbqqbe97j9dcf5dZ data]# docker exec -it 96 bashroot@96de9b284f78:/data# lsroot@96de9b284f78:/data# redis-cli127.0.0.1:6379> set name Forlogen(error) NOAUTH Authentication required.127.0.0.1:6379> auth rootOK127.0.0.1:6379> set name ForlogenOK127.0.0.1:6379> shutdown save[root@iZbp15ffbqqbe97j9dcf5dZ data]# lsredis.db[root@iZbp15ffbqqbe97j9dcf5dZ data]# cat redis.dbREDIS0009▒ redis-ver5.0.7▒▒edis-bits▒@▒ctime▒▒4_used-mem▒haof-preamble▒▒▒namForlogen▒▒5▒G▒
7.2 AOF
AOF持久化机制默认是关闭的,Redis推荐同时启用RDB和AOF进行持久化操作,这样更安全、更能避免数据的丢失。AOF持久化操作的速度相比于RDB来说,速度较慢,而且是以文本文件的形式存储。当文本文件较大时,传输较为困难。但是,AOF相对于RDB更为安全。
- 如果同时开启了AOF和RDB持久化,那么在Redis服务器宕机后,优先选择加载AOF文件作为持久化文件。
- 如果先开启了RDB,后开启了AOF,AOF执行持久化操作时会将RDB持久化文件中的内容覆盖掉。
想要开启AOF支持,只需要在配置文件中添加如下内容:
[root@iZbp15ffbqqbe97j9dcf5dZ conf]# cat redis.confrequirepass root# RDB# 900s之内有key发生改变就执行RDB持久化save 900 1save 300 10save 60 10000# 开启RDB持久化压缩rdbcompression yes# RDB持久化文件名dbfilename redis.db# 开启AOF持久化appendonly yes# AOF持久化文件名appendfilename "redis.aof"# AOF持久化执行时机appendfsync everysec
8. 事务
类似于其他关系型数据库一样,Redis同样提供了事务支持。想要启用事务支持来执行命令,首先需要开启事务,然后需要事务支持的命令会被放到一个队列中。执行事务时,这个队列中的全部命令都会被执行,而且是要么都成功,要么都失败。另外,如果取消了事务,那么队列中的命令将全部失效。
事务相关的命令有:
multi:开启事务exec:执行事务discard:取消事务watch:启动监听机制
事务开启前,需要通过watch来监听一个或多个key。当事务开启后,如果有其他的客户端修改了了监听的key,那么事务就会自动取消。如果事务执行结束,或者手动取消了事务,对应的watch监听也会自动取消。
9. 架构模式
9.1 单机版
前面例子中所使用的都是Redis的单机版模式,此时只有一个Redis服务器,它既负责数据的存储,也负责数据的读取。当它宕机后,缓存服务也就完全不能使用了。另外,当有大量的读请求到达Redis服务器时,服务器的压力过大,也可能发生宕机。
9.2 主从架构

下面我们同样通过docker-compsoe来搭建Redis主从架构模式的服务器。首先,编写对应的docker-compose.yml文件,内容如下:
version: '3.1'services:# masterredis1:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis1environment:- TZ=Asia/Shanghaiports:- 7001:6379volumes:- ./conf/redis1.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]# slave1redis2:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis2environment:- TZ=Asia/Shanghaiports:- 7002:6379volumes:- ./conf/redis2.conf:/usr/local/redis/redis.conf# 通过links指定它的master是谁links:- redis1:mastercommand: ["redis-server", "/usr/local/redis/redis.conf"]# slave2redis3:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis3environment:- TZ=Asia/Shanghaiports:- 7003:6379volumes:- ./conf/redis3.conf:/usr/local/redis/redis.conflinks:- redis1:mastercommand: ["redis-server", "/usr/local/redis/redis.conf"]
相应的需要为每一个节点准备一个配置文件,并且在redis2.conf和redis3.conf中添加salve节点相应的配置。
# 从节点配置replicaof master 6379
最后启动容器,通过命令进入到mater节点后,使用info命令查看节点信息可以看到,当前节点为master,而且它有两个slave,对应的ip和port也可以看到。
# Replicationrole:masterconnected_slaves:2slave0:ip=172.23.0.3,port=6379,state=online,offset=56,lag=0slave1:ip=172.23.0.4,port=6379,state=online,offset=56,lag=0
然后进入一个slave节点,同样使用info命令查看节点信息:
# Replicationrole:slavemaster_host:mastermaster_port:6379master_link_status:upmaster_last_io_seconds_ago:3master_sync_in_progress:0
9.3 哨兵架构

下面通过docker-compose演示如何在主从架构的基础上启用哨兵机制。修改docker-compose.yml文件内容,在volume中多加一个数据卷的映射,对应的配置文件为哨兵所需的.conf配置文件。
version: '3.1'services:redis1:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis1environment:- TZ=Asia/Shanghaiports:- 7001:6379volumes:- ./conf/redis1.conf:/usr/local/redis/redis.conf- ./conf/sentinel1.conf:/data/sentinel.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis2:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis2environment:- TZ=Asia/Shanghaiports:- 7002:6379volumes:- ./conf/redis2.conf:/usr/local/redis/redis.conf- ./conf/sentinel2.conf:/data/sentinel.conflinks:- redis1:mastercommand: ["redis-server", "/usr/local/redis/redis.conf"]redis3:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis3environment:- TZ=Asia/Shanghaiports:- 7003:6379volumes:- ./conf/redis3.conf:/usr/local/redis/redis.conf- ./conf/sentinel3.conf:/data/sentinel.conflinks:- redis1:mastercommand: ["redis-server", "/usr/local/redis/redis.conf"]
并且在宿主机的conf目录下新建三个.conf配置文件。其中master对应的sentinel1.conf文件内容为:
# 哨兵需要后台启动daemonize yes# 指定master节点的ip和端口sentinel monitor master 127.0.0.1 6379 2# 哨兵每隔多久监听一次Redis架构sentinel down-after-milliseconds master 10000
在slave节点的哨兵配置文件中填写如下内容:
# 哨兵需要后台启动daemonize yes# 指定master节点的ip和端口sentinel monitor master master 6379 2# 哨兵每隔多久监听一次Redis架构sentinel down-after-milliseconds master 10000
然后使用docker-compose up -d启动容器,可以看到此时redis都已经正常启动,但是哨兵并没有启动。
接着需要分别进入到三个Redis容器内部,使用redis-sentinel sentinel1.conf、redis-sentinel sentinel2.conf、redis-sentinel sentinel2.conf分别启动哨兵。退出容器后,再次查看宿主机的sentinel1.conf文件可以看到如下内容:
至此,带有哨兵机制的主从架构就启动完毕。
9.4 集群架构

Redis集群架构可以保证主从加哨兵的基本功能之外,继续提升Redis存储数据的能力。集群结构具有如下特点:
- 无中心
- 集群存在ping-pang机制来探测可能失效的节点
- 投票机制,Redis集群的节点数必须为2n+ 1
- 默认分配16384个hash槽,存储数据时,Redis会对key执行crc16算法,并且对16384取余。根据最终的结果将key-value存放到相应的节点上,而且每个Redis节点都维护者相应的hash槽
- 为了保证数据的安全性,集群的每个节点都至少跟着一个salve节点
- 可单独针对集群中的某一个节点搭建主从
- 当Redis集群中超过半数的节点宕机后,Redis集群默认就瘫痪了
下面通过docker-compose来搭建集群。首先编写docker-compose.yml文件,内容如下:
version: '3.1'services:redis1:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis1environment:- TZ=Asia/Shanghaiports:- 7001:7001- 17001:17001volumes:- ./conf/redis1.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis2:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis2environment:- TZ=Asia/Shanghaiports:- 7002:7002- 17002:17002volumes:- ./conf/redis2.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis3:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis3environment:- TZ=Asia/Shanghaiports:- 7003:7003- 17003:17003volumes:- ./conf/redis3.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis4:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis4environment:- TZ=Asia/Shanghaiports:- 7004:7004- 17004:17004volumes:- ./conf/redis4.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis5:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis5environment:- TZ=Asia/Shanghaiports:- 7005:7005- 17005:17005volumes:- ./conf/redis5.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]redis6:image: daocloud.io/library/redis:5.0.7restart: alwayscontainer_name: redis6environment:- TZ=Asia/Shanghaiports:- 7006:7006- 17006:17006volumes:- ./conf/redis6.conf:/usr/local/redis/redis.confcommand: ["redis-server", "/usr/local/redis/redis.conf"]
每个节点的配置文件内容格式如下:
# 指定Redis的端口号port 7001# 开启集群架构模式cluster-enabled yes# 集群信息的文件cluster-config-file nodes-7001.conf# 集群对外ipcluster-announce-ip 121.199.75.6# 集群对外端口号cluster-announce-port 7001# 集群的总线端口cluster-announce-bus-port 17001
编写好相应的配置文件后,使用doker-compose up -d启动全部的Redis容器。容器启动后,随便进入到一个容器中,使用如下命令进行集群节点之间的互连:
redis-cli --cluster create 121.199.75.6:7001 121.199.75.6:7002 121.199.75.6:7003 121.199.75.6:7004 121.199.75.6:7005 121.199.75.6:7006 --cluster-replicas 1
等待一段时间,Redis的集群模式就搭建完毕了。
10. 淘汰机制
当key的生存时间到时,并不会立即删除,而是采用下面的两种方式进行删除:
- 定期删除:Redis每隔一段时间就会查看设置了过期时间的key,一般会在100ms的间隔中默认查看3个key
- 惰性删除:当用户去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,如果生存时间已到,直接删除key,并且返回给用户一个空值
在Redis已经满了的情况下,添加一个新数据,Redis就会执行淘汰机制。Redis支持的淘汰机制有如下几种:
- volatile-lru:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少使用的key
- allkeys-lru:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少使用的key
- volatile-lfu:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少频次使用的key
- allkeys-lfu:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少频次使用的key
- volatile-random:当内存不足时,Redis会在过了生存时间的key中随机淘汰掉一个key
- allkeys-random:当内存不足时,Redis会在全部的key中随机淘汰掉一个key
- volatile-ttl:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个剩余生存时间最少的key
- noeviction(默认):当内存不足时,直接报错
通过maxmemory-policy 策略的命令来执行具体使用的淘汰机制。同时,还可以通过maxmemory 字节大小来设置redis的最大内存。
