1、redis初始
1.1、启动关闭redis服务
//开启服务
redis-server
//关闭服务
redis-cli -a redis shutdown
1.2、连接redis
# redis-cli
127.0.0.1:6379> auth redis
1.3、设置密码
config get requirepass
config set requirepass "password"
1.4、查找配置文件
[root@Dragon local]# docker exec -it c_redis /bin/bash
root@1ae5e7e69adb:/data# find / -name "redis.conf"
/etc/redis/redis.conf
#文件映射
[root@Dragon /]# find / -name "redis.conf"
/root/redis/conf/redis.conf
/var/lib/docker/overlay2/8b4d5884b3c27c6398b47e91e8b2fb4ff9253b66815db2c58c8e3343b3074f14/diff/etc/redis/redis.conf
/var/lib/docker/overlay2/8b4d5884b3c27c6398b47e91e8b2fb4ff9253b66815db2c58c8e3343b3074f14/merged/etc/redis/redis.conf
1.5、编辑配置文件
#redis支持字节类型,不支持其他类型
[root@Dragon local]# cat /root/redis/conf/redis.conf
#bind 127.0.0.1为仅支持本地连接不支持远程连接
bind 0.0.0.0
#本机保护模式默认为yes,需要远程访问时改为no
protected-mode no
#端口号默认6379
port 6379
#设置tcp的backlog连接队列,backlog队列总和=未完成三次握手队列+已完成三次握手队列
tcp-backlog 511
#超时时间单位:s
timeout 0
#检查心跳时间的间隔0为不检查
tcp-keepalive 0
#日志级别
loglevel notice
#日志文件目录
logfile ""
#save 秒钟 写操作次数
#在规定时间内发生规定次数的写操作就进行持久化保存
save 900 1
save 300 10
save 60 10000
#当redis无法写入磁盘时(磁盘满)直接关掉写操作
stop-writes-on-bgsave-error yes
#持久化文件进行压缩存储
rdbcompression yes
#完整性检查
rdbchecksum yes
#设置rdb文件的名字
dbfilename dump.rdb
#rdb文件存放目录当前目录下的/data,把rdb文件直接放到该目录下即可读取,可用于回复备份
dir /data
#密码
requirepass redis
#rdb与aof同时开启时系统默认取aof的数据
#开启AOF(Append Only File)
appendonly yes
#设置aof文件名
appendfilename appendonly.aof
#不作为守护进程运行,后台启动
daemonize yes
1.6、消息的订阅与发布
#订阅channel的消息
127.0.0.1:6379> subscribe channel1
#在channel发布消息
127.0.0.1:6379> publish channel1 hello
2、五大常用数据类型
2.1、String
keys * #查看当前库所有key (匹配:keys *1)
exists key #判断某个key是否存在
type key #查看你的key是什么类型
del key #删除指定的key数据
unlink key #根据value选择非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
expire key 10 #10秒钟:为给定的key设置过期时间
ttl key #查看还有多少秒过期,-1表示永不过期,-2表示已过期
select #命令切换数据库
dbsize #查看当前数据库的key的数量
flushdb #清空当前库
flushall #通杀全部库
get <key>查询对应键值
append <key><value>将给定的<value> 追加到原值的末尾
strlen <key>获得值的长度
setnx <key><value>只有在 key 不存在时 设置 key 的值
incr <key>将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
decr <key>将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
incrby / decrby <key><步长>将 key 中储存的数字值增减。自定义步长。
2.2、List
lpush/rpush <key><value1><value2><value3> .... 从左边/右边插入一个或多个值。
lpop/rpop <key>从左边/右边吐出一个值。值在键在,值光键亡。
rpoplpush <key1><key2>从<key1>列表右边吐出一个值,插到<key2>列表左边。
lrange <key><start><stop>按照索引下标获得元素(从左到右)
lrange mylist 0 -1 0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex <key><index>按照索引下标获得元素(从左到右)
llen <key>获得列表长度
linsert <key> before <value><newvalue>在<value>的后面插入<newvalue>插入值
lrem <key><n><value>从左边删除n个value(从左到右)
lset <key><index><value>将列表key下标为index的值替换成value
2.3、Set
sadd <key><value1><value2> ..... 将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key>取出该集合的所有值。
sismember <key><value>判断集合<key>是否为含有该<value>值,有1,没有0
scard <key>返回该集合的元素个数。
srem <key><value1><value2> .... 删除集合中的某个元素。
spop <key>随机从该集合中吐出一个值。
srandmember <key><n>随机从该集合中取出n个值。不会从集合中删除 。
smove <source><destination>value把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2>返回两个集合的交集元素。
sunion <key1><key2>返回两个集合的并集元素。
sdiff <key1><key2>返回两个集合的差集元素(key1中的,不包含key2中的)
2.4、Hash
hset <key><field><value>给<key>集合中的 <field>键赋值<value>
hget <key1><field>从<key1>集合<field>取出 value
hmset <key1><field1><value1><field2><value2>... 批量设置hash的值
hexists <key1><field>查看哈希表 key 中,给定域 field 是否存在。
hkeys <key>列出该hash集合的所有field
hvals <key>列出该hash集合的所有value
hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
2.5、Zset
zadd <key><score1><value1><score2><value2>…将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange <key><start><stop> [WITHSCORES] 返回有序集 key中,下标在<start><stop>之间的元素带WITHSCORES,可以让分数一起和值返回到结果集。
zrangebyscore key minmax [withscores] [limit offset count]返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score值递增(从小到大)次序排列。
zrevrangebyscore key maxmin [withscores] [limit offset count] 同上,改为从大到小排列。
zincrby <key><increment><value> 为元素的score加上增量
zrem <key><value>删除该集合下,指定值的元素
zcount <key><min><max>统计该集合,分数区间内的元素个数
zrank <key><value>返回该值在集合中的排名,从0开始。
3、springboot整合Redis
3.1、xml
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
3.2、配置文件
#Redis服务器地址
spring.redis.host=192.168.152.161
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
#配置redis的密码
spring.redis.password=redis
3.3、配置类
/**
* Redis 配置类
* @Author beiqixing
* @Description
* @Date 2021/10/9 20:02
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
3.4、模拟并发操作
#安装ab测试工具
[root@Dragon ~]# yum install httpd-tools
#测试url
ab -n 2000 -c 200 -p /root/redis/data/postfile -T application/x-www-form-urlencoded http://10.113.16.24:8080/Seckill/doseckill
options | meanning |
---|---|
-n | 请求数 |
-c | 并发数 |
-c | 并发数 |
-p | 请求参数文件 |
-t | 请求数据类型 |
4、主从复制
4.1、创建master-slave文件夹,复制配置文件
mkdir /etc/redis/ms
cp redis.conf /etc/redis/ms
4.2、编写redis6379.conf配置文件
include /etc/redis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
#主redis密码
masterauth redis
4.3、启动剩余两个redis
root@1ae5e7e69adb:/etc/redis/ms# redis-server redis6380.conf
root@1ae5e7e69adb:/etc/redis/ms# redis-server redis6381.conf
4.4、查看redis进程
[root@Dragon ~]# ps -ef | grep redis
polkitd 7154 7135 0 20:38 ? 00:00:00 redis-server 0.0.0.0:6379
root 7194 3276 0 20:40 pts/2 00:00:00 docker exec -it c_redis /bin/bash
root 7231 7209 0 20:44 pts/0 00:00:00 redis-server 0.0.0.0:6380
root 7236 2460 0 20:45 pts/3 00:00:00 docker exec -it c_redis /bin/bash
root 7260 7251 0 20:45 ? 00:00:00 redis-server 0.0.0.0:6381
root 7302 7269 0 20:46 pts/0 00:00:00 grep --color=auto redis
4.5、分别连接不同端口的redis
root@1ae5e7e69adb:/data# redis-cli -p
root@1ae5e7e69adb:/data# redis-cli -p 6380
root@1ae5e7e69adb:/data# redis-cli -p 6381
4.6、查看主从状态
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:4ff7eaf432aaa16f5d37f8efe1811bce4beef5ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
4.7、设置主从关系
slaveof 127.0.0.1 6379
#用 slaveof no one将从机变为主机
#主机
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=28,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=28,lag=1
master_failover_state:no-failover
master_replid:c19b76165d962857eda358c5e72cdc3ba1f3b1d8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28
#从机
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:10
master_sync_in_progress:0
slave_repl_offset:42
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:c19b76165d962857eda358c5e72cdc3ba1f3b1d8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:42
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:42
5、哨兵
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
#自定义的/ms目录下新建sentinel.conf文件,名字绝不能错
sentinel monitor mymaster 127.0.0.1 6379 1
#其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
#启动哨兵
redis-sentinel /ms/sentinel.conf
哨兵切换主机的优先级在配置文件中设置 slave-priority/replica-priority 属性,值越小优先级越高
在java中使用JedisSentinelPool连接池获取主从关系
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.152.161:26379");
//26379为配置的哨兵端口号
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
6、集群
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
6.1、配置文件
include /home/bigdata/redis.conf
port 6379
pidfile "/var/run/redis_6379.pid"
dbfilename "dump6379.rdb"
dir "/home/bigdata/redis_cluster"
logfile "/home/bigdata/redis_cluster/redis_err_6379.log"
#打开集群模式
cluster-enabled yes
#设置节点配置文件名
cluster-config-file nodes-6379.conf
#设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
cluster-node-timeout 15000
使用%s:/6379/6380替换剩余配置文件
6.2、启动集群
#启动六个服务,在目录 **/opt/redis-6.2.1/src** 下把六个服务合成为一个集群
#此处不要用127.0.0.1, 请用真实IP地址 --replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。
redis-cli --cluster create --cluster-replicas 1
192.168.11.101:6379 192.168.11.101:6380
192.168.11.101:6381 192.168.11.101:6389
192.168.11.101:6390 192.168.11.101:6391
普通方式登录
可能直接进入读主机,存储数据时,会出现MOVED重定向操作。所以,应该以集群方式登录。
#-c 采用集群策略连接,设置数据会自动切换到相应的写主机
redis-cli -c -p 6379
通过 cluster nodes 命令查看集群信息
6.3、slots
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽.
不在一个slot下的键值,是不能使用mget,mset等多键操作
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去
mset k1{cust} v1 k2{cust} v2 k3{cust} v3
6.4、查询集群中的值
#查询集群中key为cust组的插槽
cluster keyslot cust
#查询集群中插槽6666中key的个数
cluster countkeysinslot 6666
#查询插槽6666中的前十个key
cluster getkeysinslot 6666 10
6.5、故障恢复
主机挂掉从机变成主机继续服务。
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes,那么整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverag
6.6、集群的Jedis开发
public class RedisCluster {
public static void main(String[] args) {
HostAndPort node = new HostAndPort("192.168.152.161", 6379);
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true); // ping PONG
JedisCluster jedisCluster = new JedisCluster(node,10000, 10000, 100,"redis",poolConfig);
jedisCluster.set("k1","v1");
String value = jedisCluster.get("k1");
System.out.println("value:"+value);
jedisCluster.close();
}
}
7、应用问题及解决方案
7.1、缓存穿透
7.1.1、问题描述
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
7.1.2、解决方案
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
解决方案:
(1) 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
(2) 设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3) 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4) 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
7.2、缓存击穿
7.2.1、问题描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
7.2.2、解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
解决问题:
(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长
(3)使用锁:
- (a)就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
- (b)先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
- (c)当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
- (d)当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
7.3、缓存雪崩
7.3.1、问题描述
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key
7.3.2、解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
解决方案:
- (1) 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
- (2) 使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况 - (3) 设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。 - (4) 将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
8、权限控制
#on表示启用该用户
#~cached:*表示可以操作cached:开头的所有key
#get表示可以使用get操作
acl setuser username on >password ~cached:* +get
#使用该用户
authe username password