Redis 是一个开源的 使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志 型、Key-Value 数据库。
redis的key-value需要特别说明下,key只能为字符串类型,但value可以为字符串,hash表,列表(list),集合(set),有序集合(sorted set)等多种数据类型,这也是redis与memcached的区别。memcached也是key-value数据库,但是key,value都只能是字符串类型。
Redis 的 key 是字符串类型,但是 key 中不能包括边界字符 ,由于 key 不是 binary safe 的字符串,所以像"my key"和"mykey\n"这样包含空格和换行的 key 是不允许的。
key的相关操作
key的大小最大为512MB。
string 是最基本的类型,而且 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。从内部实现来看其实 string 可以看作 byte 3数组,最大上限是 1G 字节。
String操作命令
//设置值和获取值
127.0.0.1:7000> set b 1234
OK
127.0.0.1:7000> get b
"1234"
//再次设置值,之前的值会被修改
127.0.0.1:7000> set b 1dfff
OK
127.0.0.1:7000> get b
"1dfff"
//nx如果b已经存在,则直接返回失败,不存在则设置成功。
127.0.0.1:7000> set b fsdf nx
(nil)
127.0.0.1:7000> get b
"1dfff"
//xx如果b存在,则设置成功,如果b不存在则设置失败,这相当于修改一个值。
127.0.0.1:7000> set b ffff xx
OK
127.0.0.1:7000> get b
"ffff"
127.0.0.1:7000>
//设置过期时间,多次设置与最后一次为准。相当于修改
127.0.0.1:7000> set f ffff ex 10
OK
//获取过期还剩多少
127.0.0.1:7000> ttl f
(integer) 6
127.0.0.1:7000> ttl f
(integer) 3
127.0.0.1:7000>
原子操作
incr
如果遇到需要i++这类型的操作我们代码里面一般会如下:
伪代码如下:
int a= get key
a= a + 1
set key a;
这样是不安全的,因为多个线程同事操作时,会导致结果不一致。
上面分析了,i++ 操作,那么redis为我们提供了原子性的i++操作。
127.0.0.1:7000> set a 1
OK
127.0.0.1:7000> INCR a
(integer) 2
127.0.0.1:7000> incr a
(integer) 3
127.0.0.1:7000> get a
"3"
设置一个a=1,现在需要将a+1,那么调用incr a会自动为我们做++操作,decr a会自动做减减操作。incrby a 2 这个表示a直接加2操作,而incr为每次自动+1,incrby为指定加多少。
Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展,所以bigmap还是一种字符串类型
在一台2010MacBook Pro上,offset为232-1(分配512MB)需要~300ms,offset为230-1(分配128MB)需要~80ms,offset为228-1(分配32MB)需要~30ms,offset为226-1(分配8MB)需要8ms
指令 SETBIT key offset value
复杂度 O(1)
设置或者清空key的value(字符串)在offset处的bit值(只能只0或者1)。
offset的最大值为10亿,2的23-1方
hash是一个string类型的field和value的映射表。添加,删除操作都是O(1)(平均)。hash特别适合用于存储对象。相对于将对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多,所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现.这个限制可以在配置文件中指定。
hash-max-zipmap-entries64#配置字段最多64个
hash-max-zipmap-value512#配置value最大为512字节
hash 类型数据操作指令简介
//使用hash来存储数据。user是key,由于value是hash,所以还有一个key,value健值对。
127.0.0.1:7000> hset user id 1
(integer) 1
127.0.0.1:7000> hset user userName xiaobao
(integer) 1
127.0.0.1:7000> hset user password ssssss
(integer) 1
//获取key为user的hash表中的id的值。
127.0.0.1:7000> hget user id
"1"
//获取hash表中的所有key
127.0.0.1:7000> hkeys user
1) "id"
2) "userName"
3) "password"
//获取hash表中所有的value
127.0.0.1:7000> HVALS user
1) "1"
2) "xiaobao"
3) "ssssss"
127.0.0.1:7000>
list是一个链表结构,可以理解为每个子元素都是string类型的双向链表。主要功能是push、pop获取一个范围的所有值等。操作中 key理解为链表的名字。
List 类型数据操作指令简介
//从key为list的左边push一个1.
127.0.0.1:7000> lpush list 1
(integer) 1
127.0.0.1:7000> lpush list 2
(integer) 2
//查看list队列长度
127.0.0.1:7000> llen list
(integer) 2
//从右边连续push 3 4 5 6
127.0.0.1:7000> rpush list 3 4 5 6
(integer) 6
//返回列表从0到6个元素
127.0.0.1:7000> LRANGE list 0 6
1) "2"
2) "1"
3) "3"
4) "4"
5) "5"
6) "6"
//从队列头弹出值并删除
127.0.0.1:7000> lpop list
"2"
127.0.0.1:7000> lpop list
"1"
127.0.0.1:7000> lpop list
"3"
127.0.0.1:7000> lpop list
"4"
整个集合就是一个入栈出栈的操作,记住集合它是一个双向链表就OK了。
set是无序集合,最大可以包含(2 的 32 次方-1)个元素,set 是不能有重复元素,如果存入重复元素不会报错,但是set里面还是只有一个。
set 的是通过 hash table 实现的, 所以添加,删除,查找的复杂度都是 O(1)。
hash table 会随着添加或者删除自动的调整大小,需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现。跳表已经在sorted sets 中使用了。关于set集合类型除了基本的添加删除操作,其它有用的操作还包含集合的取并集 (union),交集(intersection), 差集(difference)。通过这些操作可以很容易的实现 SNS 中的好友推荐和 blog 的 tag 功能。
set 类型数据操作指令简介
//存入元素
127.0.0.1:7000> sadd set 1
(integer) 1
127.0.0.1:7000> sadd set 2
(integer) 1
//1已经存在了,再存入1则返回0了。
127.0.0.1:7000> sadd set 1
(integer) 0
//弹出key为set的总的2个元素,弹出后删除元素
127.0.0.1:7000> spop set 2
1) "1"
2) "2"
//没有元素的情况下返回空
127.0.0.1:7000> spop set 1
(empty list or set)
127.0.0.1:7000> sadd set 1 2 3 4 5 6 6 6
(integer) 6
//随机弹出10个元素,不删除
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> srandmember set 1
1) "6"
//返回set的所有元素,无序的
127.0.0.1:7000> smembers set3
1) "2"
2) "3"
3) "5"
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> sadd set1 2 3 5 10 20
(integer) 5
//求set,set2两个key的并集
127.0.0.1:7000> sunion set set1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "10"
8) "20"
//求set set1的交集
127.0.0.1:7000> sinter set set1
1) "2"
2) "3"
3) "5"
//求两个交集,返回的结果存储到set3里面
127.0.0.1:7000> sinterstore set3 set set1
(integer) 3
127.0.0.1:7000> SRANDMEMBER set3 10
1) "2"
2) "3"
3) "5"
sorted set 是有序集合,它在 set 的基础上增加了一个顺序属性,这一属性在添加修 改元素的时候可以指定 ,每次指定后,会自动重新按新的值调整顺序 。可以理解了有两列 的 mysql 表,一列存 value,一列存顺序。操作中key理解为sorted set的名字。
Sorted Set 类型数据操作指令简介
//存储一个有序set,sortset为key,1表示分数,a是我们的值
127.0.0.1:7000> zadd sortset 1 a
(integer) 1
127.0.0.1:7000> zadd sortset 2 b
(integer) 1
//查看b的下标
127.0.0.1:7000> zrank sortset b
(integer) 1
//返回0,1个元素
127.0.0.1:7000> zrange sortset 0 1
1) "a"
2) "b"
./redis-cli -p 7000 --bigkeys
//查看key为d的详细信息,查看key的详细信息(从这里可以看出key占用的存储空间挺大的,需要存储很多信息)
127.0.0.1:7000> DEBUG OBJECT d
Value at:0x7fb7cf526240 refcount:1 encoding:embstr serializedlength:5 lru:6748523 lru_seconds_idle:537
//查看key
127.0.0.1:7000> SCAN 0
1) "17"
2) 1) "d"
2) "kye"
127.0.0.1:7000> SCAN 17
1) "0"
2) 1) "dfff"
2) "ff"
3) "ddd"
第一次迭代使用 0 作为游标, 表示开始一次新的迭代。
第二次迭代使用的是第一次迭代时返回的游标, 也即是命令回复第一个元素的值17.
从上面的示例可以看到, SCAN 命令的回复是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。 在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集(collection)已经被完整遍历过了。
以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历(full iteration)。
127.0.0.1:7000> scan 0 match t*
1) "0"
2) 1) "t"
2) "ttt"
127.0.0.1:7000> scan 0 match t* count 2
1) "3"
2) 1) "t"
scan 0 match t* 与keys t* 还是有差别的,因为scan 是带分页的,而keys 是没有分页的,遍历了所有的key。
在线上不建议使用keys来查询key。
redis-benchmark -n 10000
redis-cli -h 127.0.0.1 -p 70000 -a password
如果忘记输入密码,可以在进入的时候使用
auth password
redis淘汰策略配置,在redis.conf文件查看:maxmemory-policy voltile-lru 支持热配置
redis 提供 6种数据淘汰策略:
选择策略规则
如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,
将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存
https://blog.csdn.net/lizhi_java/article/details/68953179?locationNum=1&fps=1 <失效策略>
进入到将要设置的从的客户端或者在配置文件里面下入
$ redis-cli -p 7000
127.0.0.1:7000> slaveof 192.168.110.101 6379
即可将当前7000这个redis服务设置为一个从服务。
取消从服务
127.0.0.1:7000> SLAVEOF no one
redis设置了主从服务器后,从会自动备份主的数据,但是从服务器对外提供只能读,不能写。
哨兵是分布式应用,可以启动多个,指定不同端口即可。哨兵与哨兵之间不需要互相配置,哨兵会根据自己监控的主服务做判断,如果是监控的是同一个主服务,则他们会互相自动建立连接,互相通信。
哨兵配置监控时只需要配置master地址即可,但是不代表它不监控从节点。sentinel连接上主节点后,会自动从主节点获取到该节点的从节点信息然后进行监控。
对于redis-sentinel 程序, 你可以用以下命令来启动 Sentinel 系统:
redis-sentinel /path/to/sentinel.conf
对于 redis-server 程序, 你可以用以下命令来启动一个运行在 Sentinel 模式下的 Redis 服务器:
redis-server /path/to/sentinel.conf --sentinel
sentinel.conf配置如下:
sentinel monitor mymaster 127.0.0.1 7001 2
sentinel config-epoch mymaster 1
port 17000
sentinel操作流程:
注意:
Java客户端连接sentinel对redis操作。
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Autowired
private RedisProperties redisProperties;
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
sentinelConfig.master(redisProperties.getSentinel().getMaster());
sentinelConfig.setSentinels(createSentinels(redisProperties.getSentinel()));
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig);
jedisConnectionFactory.setPassword(redisProperties.getPassword());
return jedisConnectionFactory;
}
@Bean
@DependsOn("jedisConnectionFactory")
public StringRedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
return new StringRedisTemplate(jedisConnectionFactory);
}
private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
List<RedisNode> nodes = new ArrayList();
for (String node : StringUtils
.commaDelimitedListToStringArray(sentinel.getNodes())) {
try {
String[] parts = StringUtils.split(node, ":");
Assert.state(parts.length == 2, "Must be defined as 'host:port'");
nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
} catch (RuntimeException ex) {
throw new IllegalStateException(
"Invalid redis sentinel " + "property '" + node + "'", ex);
}
}
return nodes;
}
}
可以看出来,操作sentinel时,不是直接操作sentinel的,而是通过sentinel找到主服务的IP地址和端口,然后再根据IP地址和端口去连接reids。
通常 Redis 将数据存储在内存中或虚拟内存中,它是通过以下两种方式实现对数据的持久化。
持久化即使存储到硬盘上,如果存储到内存中,电脑重启后内存就会被清空。
这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
客户端也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有客户端的请求,这种方式会阻塞所有客户端请求,所以不推荐使用,bgsave是fork一个子进程来做的,就是后台执行,不阻塞执行。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步增量数据。如果数据量大的话,写操作会比较多,必然会引起大量的磁盘IO操作,可能会严重影响性能。
redis会自动备份,而不需要我们自己手动的去调用save来保存备份。
通过配置文件可以看出:vi /usr/local/etc/redis.conf
save 900 1 //900秒里面发生1次操作则执行一次save。
save 300 10 //300秒了发生了10次操作则执行一次save
save 60 10000 //60秒里面发生了1万次操作则执行一次save。
注意:由于快照方式是在一定间隔时间做一次的,所以如果 redis意外当机的话,就会丢失最后一次快照后的所有数据修改。
这种方式redis会将每一个收到的写命令都通过write函数追加到文件中(默认appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于操作系统会在内核中缓存write做的修改,所以可能不是立即写到磁盘上。这样的持久化还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制操作系统写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)
appendonly yes //启用日志追加持久化方式
//appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
//appendfsync no //完全依赖操作系统,性能最好,持久化没保证
AOF会自动优化文件。
比如:
> set key aaa
> set key bbb
> set key ddd
上面三个操作最终库里面数据为:key=ddd,那就简单了额,AOF会只需要存储set key ddd命令就OK了,其他两条命令可以忽略了,这样就可以达到优化存储的效果。
AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制。
以下是 AOF 重写的执行步骤:
数据恢复是内存数据被清空了,需要从硬盘中的备份文件重新加载到内存中。
在电脑重启后,如果两种持久化模式都开启了,则会优先从AOF中恢复数据,然后才是RDB模式。
RDB优点:
RDB缺点:
AOF优点
AOF缺点
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
一个事务从开始到执行会经历以下三个阶段:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
DISCARD :取消事务,放弃执行事务块内的所有命令。
keyspace有啥用? 请看下面回答
在 Redis 里面有一些事件,比如键到期、键被删除等。然后我们可以通过配置一些东西来让 Redis 一旦触发这些事件的时候就往特定的 Channel 推一条消息。
大致的流程就是我们给 Redis 的某一个 db 设置过期事件,使其键一旦过期就会往特定频道推消息,我在自己的客户端这边就一直消费这个频道就好了。
以后一来一条定时任务,我们就把这个任务状态压缩成一个键,并且过期时间为距这个任务执行的时间差。那么当键一旦到期,就到了任务该执行的时间,Redis 自然会把过期消息推去,我们的客户端就能接收到了。这样一来就起到了定时任务的作用。
第一步:需要开启事件通知
开启所有的事件
redis-cli> config set notify-keyspace-events KEA
开启keyspace Events
redis-cli> config set notify-keyspace-events KA
开启keyspace 所有List 操作的 Events
redis-cli> config set notify-keyspace-events Kl
redis-cli> config set notify-keyspace-events Ex // 其中Ex表示键事件通知里面的key过期事件,每当有过期键被删除时,会发送通知
或者在redis.conf配置文件中,找到notify-keyspace-events “”表示什么都不做,后面加上值即为开启.修改后需要重启redis服务
注意:默认情况下config在生产环境是不开启的,所以在生产上使用config会提示不存在的问题. config在redis.conf配置文件里面,去掉config前面的注解就可以使用了.
类型 | 备注 |
---|---|
K | 键空间通知,所有通知以 __keyspace@__ 为前缀 |
E | 键事件通知,所有通知以 __keyevent@__ 为前缀 |
g | DEL , EXPIRE ,RENAME 等类型无关的通用命令的通知 |
$ | 字符串命令的通知 |
l | 列表命令的通知 |
s | 集合命令的通知 |
h | 哈希命令的通知 |
z | 有序集合命令的通知 |
x | 过期事件:每当有过期键被删除时发送 |
e | 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送 |
A | 参数 g$lshzxe 的别名 |
第二步:订阅通知事件
127.0.0.1:7000> psubscribe __keyevent@0__:expired
spring代码参考地址
Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.
Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令.
Redis 集群的优势:
Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念.
Redis 集群有16384个(2的14次方)哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。
注意:节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。A,C节点也将不可用了哦。
为了解决上面说的B下线了, 导致整个集群不可以用了,我们需要使用集群的另外一个功能,主从复制功能。
为A,B,C,添加一个或者多个从节点A1,B1,C1,这样B节点挂了,B1节点会自动升级为主节点,接替B的工作,为集群工作。这个很像哨兵模式,其实就是哨兵模式。
在redis的集群模块中,添加了哨兵模式,主节点挂了从节点会自动切换为主节点。
架构细节:
Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.
第一个原因是因为集群是用了异步复制. 写操作过程:
主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。
注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。
举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .
Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.
注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:
port 7000 //redis端口
cluster-enabled yes //开启集群
cluster-config-file nodes.conf //集群配置文件
cluster-node-timeout 5000 //集群节点超时设置
appendonly yes //开启aof持久化
要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下(为了简单演示都在同一台机器上面)
安装redis
brew install redis
由于配置了.bash_profile变量,所以可以在任何地方直接启动redis
1.直接启动
redis-server
2.带配置文件启动
redis-server /Users/baowenwei/service/redis/7000/redis7000.conf
port 7000
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7000
cluster-enabled yes
7001.conf
port 7001
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7001
cluster-enabled yes
…
brew install ruby.
gam install redis -v 3.2 //安装完ruby后,gam命令就自己有了。
make //编译打包的意思。
编译后可以从src里面看到redis-trib.rb这个文件了。
使用cp redis-trib.rb /usr/local/bin/redis-trib ///usr/local/bin这个目录已经配置到环境变量的pash下面了, 这样就可以在任何地方直接使用redis-trib了。
redis-server /Users/baowenwei/service/redis/7000/redis7000.conf
redis-trib 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:7004 127.0.0.1:7005
create表示创建,选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。。
$ redis-cli -c -p 7002
127.0.0.1:7002> set 777 ddd
-> Redirected to slot [6787] located at 127.0.0.1:7001
OK
127.0.0.1:7001> set ffff sddd
-> Redirected to slot [14956] located at 127.0.0.1:7002
OK
127.0.0.1:7002> set ffff sddd
OK
127.0.0.1:7002>
可以看到777这个key计算后应该存放到7001下面的卡曹,这时候会切换到7001节点上去,然后你需要重新执行一边命令,之前的并没有执行成功哦。
注意事项:在第一次创建集群前,不能有dump.rdb文件
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
穿透过程
解决方案1:
缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 (如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
解决方案2:
布隆过滤器拦截
开发人员使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
当前 key 是一个热点 key( 例如一个热门的娱乐新闻),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
热点 key 失效后大量线程重建缓存要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:
互斥锁 (mutex key)
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可
(1) 从 Redis 获取数据,如果值不为空,则直接返回值,否则执行下面的步骤
(2) 如果 set(nx 和 ex) 结果为 true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。
(2.2) 如果 setnx(nx 和 ex) 结果为 false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间 ( 例如这里是 50 毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。
雪崩是指如果redis突然挂掉了,所有请求都打到了db上面,这个时候有可能会把db给打垮掉。
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
保证缓存层服务高可用性。
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
依赖隔离组件为后端限流并降级。
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。
在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 HBase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池,开启资源池,资源池阀值管理,这些做起来还是相当复杂的,这里推荐一个 Java 依赖隔离工具 Hystrix(https://github.com/Netflix/Hystrix),如下图所示。
http://it.dataguru.cn/article-10994-1.html 防穿透,雪崩
http://doc.redisfans.com/index.html redis命令中文版
https://www.cnblogs.com/gomysql/p/4395504.html集群搭建
http://www.redis.cn/topics/cluster-tutorial.html 集群搭建
https://segmentfault.com/a/1190000008188655#articleHeader4 bitmap讲解