Redis
Posted 马杏争1994
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis相关的知识,希望对你有一定的参考价值。
Redis
docker 安装redis
docker run --name redis1 -d -p 6379:6379 docker.io/redis redis-server
链接redis
docker run -it --link redis1 --rm docker.io/redis redis-cli -h redis1 -p 6379
Redis 指令
Redis常用命令
1 .连接操作命令
quit:关闭连接(connection)
auth:简单密码认证
help cmd: 查看cmd帮助,例如:help quit
2 持久化
save:将数据同步保存到磁盘
bgsave:将数据异步保存到磁盘
lastsave:返回上次成功将数据保存到磁盘的Unix时戳
shutdown:将数据同步保存到磁盘,然后关闭服务
3 远程服务控制
info:提供服务器的信息和统计
monitor:实时转储收到的请求
slaveof:改变复制策略设置
config:在运行时配置Redis服务器
1 对key操作的命令
get set key value[秒][ms]:
ttl key : 查询key 的生命周期 (-1 永久) (-2 不存在) (时间)
expire key 整型值 :设定一个key的活动时间(s)
persist key: 不让他失效
exists key:确认一个key是否存在
del key:删除一个key
type key:返回值的类型
keys pattern:返回满足给定pattern的所有keys ss* 匹配ss开头的key
keyrename(oldname, newname):重命名key
dbsize:返回当前数据库中key的数目
select(index):按索引查询
move(key, dbindex):移动当前数据库中的key到dbindex数据库
flushdb:删除当前选择数据库中的所有key
flushall:删除所有数据库中的所有key
2 String
set(key, value):给数据库中名称为key的string赋予值value
get(key):返回数据库中名称为key的string的value
getset(key, value):给名称为key的string赋予上一次的value
mget(key1, key2,…, key N):返回库中多个string的value
setnx(key, value):添加string,名称为key,值为value
setex(key, time, value):向库中添加string,设定过期时间time
mset(key N, value N):批量设置多个string的值
msetnx(key N, value N):如果所有名称为key i的string都不存在
incr(key):名称为key的string增1操作
incrby(key, integer):名称为key的string增加integer
decr(key):名称为key的string减1操作
decrby(key, integer):名称为key的string减少integer
append(key, value):名称为key的string的值附加value
substr(key, start, end):返回名称为key的string的value的子串
3 List
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素
ltrim(key, start, end):截取名称为key的list
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值
linsert key before(after) n1 n2 : 给n1 前插入n2
lrem(key, count, value):删除count个key的list中值为value的元素 -2 倒着删
lpop(key):返回并删除名称为key的list中的首元素
rpop(key):返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout):lpop命令的block版本。
brpop(key1, key2,… key N, timeout):rpop的block版本。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
4 Set
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :移到集合元素
scard(key) :返回名称为key的set的基数
sismember(key, member) :member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, (keys)) :求交集并将交集保存到dstkey的集合
sunion(key1, (keys)) :求并集
sunionstore(dstkey, (keys)) :求并集并将并集保存到dstkey的集合
sdiff(key1, (keys)) :求差集
sdiffstore(dstkey, (keys)) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素
4.1Sorted set 有序集合
zadd key score1 value1 score2 value2 按照score1 排序
zrange key start stop 范围 0 ,3 排名
zrangebyscore key start stop [limit offset(偏移量) N(pageSize)] 按排序值范围取
zrank key value 取 value的排名 zrevrank(降序)
zcount key start stop
zinterstore result 2 lisi wang (aggregate min (取最小)) 合并求和
zrange result 0 -1 withscores
5 Hash
hset(key, field, value):向名称为key的hash中添加元素field
hget(key, field):返回名称为key的hash中field对应的value
hgetall
hmget(key, (fields)):返回名称为key的hash中field i对应的value
hmset(key, (fields)) 修改
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
注意点
1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
2.key也不要太短,太短的话,key的可读性会降低;
3.在一个项目中,key最好使用统一的命名模式,例如user:10000:passwd
redis 事务
1.MULTI用来组装一个事务; 其实就是将中间执行的命令放入queue里了并未彻底执行
2.EXEC用来执行一个事务;
3.DISCARD用来取消一个事务;
4.WATCH用来监视一些key,一旦这些key在事务执行之前被改变,则取消事务的执行 unwatch(取消监视)
在事务开始之前进行监视
秒杀可以使用
watch tickets
multi
命令
exec
频道发布与消息订阅
publish news“article” 发布消息
subscribe news 订阅消息
psubscribe * 订阅所有
持久化
redis提供了两种持久化的方式分别是RDB(Redis DataBase)和AOF(Append Only File)
RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上
如果每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失
save 900 1 900s 1个change
save 300 10 300s 5个change
save 60 10000 60s 10000个change
redis-server 主进程
if (触发条件) {
rdbdump 子进程
}
AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了
appendonly yes # 打开aof日志功能
appendsync always/everysync/no
写入aof, 等待磁盘同步
每一秒写, 等待同步
写, 不等
aof 重写 rdb -》 aof
直接执行BGREWRITEAOF命令,那么redis会生成一个全新的AOF文件
重写的时候新生成命令要写到aof中
no-appendfsync-on-rewrite no 会阻塞 yes == appendsync no
redis-check-aof工具来修复 不完整指令信息
RDB : 常用来灾备
AOF 的优点: 默认的AOF持久化策略是每秒钟fsync(缓存中的指令记录到磁盘中)一次 之丢失1s
缺点:体积大 处理巨大的写入会降低 Redis 的性能
主从复制
slaveof
masterauth
主从同步时 客户端 只读slave-read-only yes
sentinel (哨兵)运维监控
info Replication 查看从属关系
config get appendonly 查看配置
生产中master 变更
运行时更改master-slave
修改一台slave为master
1. 命令该服务不做其他redis 服务的slave 命令:slaveof no one
2. 修改其readonly为no config set slave-read-only no
其他的slave 再指向新的 master
- slaveof ip:port
配置sentinel.conf
sentinel monitor mymaster ip port 1 // 多少个哨兵发现down
sentinel can-failover mymaster yes 开启故障转移
sentinel parallel-syncs mymaster 1 // 设定同时指向新master个数, salve断开重连时 都要从master dump出rdb,再aof。个数过多,io剧增
./redis-server /etc/sentinel.conf --sentinel 启动哨兵
Redis Cluster
Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本
其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个key的数值域分成4096个hash槽,每个节点上可以存储一个或多个hash槽,也就是说当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的分布式算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER。整体设计可总结为:
数据hash分布在不同的Redis节点实例上;
M/S的切换采用Sentinel;
写:只会写master Instance,从sentinel获取当前的master Instance;
读:从Redis Node中基于权重选取一个Redis Instance读取,失败/超时则轮询其他Instance;Redis本身就很好的支持读写分离,在单进程的I/O场景下,可以有效的避免主库的阻塞风险;
通过RPC服务访问,RPC server端封装了Redis客户端,客户端基于Jedis开发。
项目技巧
redis key 设计
- 把表名转换为key 前缀
- 第2段 放置用于区分key的字段, 如id或userId
- 第3段 主键值
- 第4段 要存储的列名称
例: book:1:title “算法导论”
user:userId:3:username lisi user:userId:3:password 223
查询:
keys user:userid:3*
如何通过用户名查询用户信息 冗余 名字为key id为value username:lisi:uid 3
redis.conf配置
include /other.conf 引入其他配置文件
1.通用(general)
2.快照(snapshotting)
3.复制(replication)
4.安全(security)
5.限制(limits)
6.追加模式(append only mode)
7.LUA脚本(lua scripting)
8.慢日志(slow log)
9.事件通知(event notification)
repl-backlog-size 1mb 设置同步队列长度 队列长度(backlog)是主redis中的一个缓冲区在与从redis断开连接期间,主redis会用这个缓冲区来缓存应该发给从redis的数据。这样的话,当从redis重新连接上之后,就不必重新全量同步数据,只需要同步这部分增量数据即可。
repl-backlog-ttl 3600 设置等待时间,等不到我就清除了
slave-priority 100 设置优先级 ,越小越高 如果主不工作了,谁来当主
5.限制
maxclients 10000
maxmemory
项目使用
使用redis
spring.redis.database=0
spring.redis.host=192.168.0.58
spring.redis.port=6379
spring.redis.password=
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.timeout=0
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@Bean
public KeyGenerator objectId() {
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuffer sb = new StringBuffer();
sb.append(target.getClass().getName());
try {
sb.append(params[0].getClass().getMethod("getId", null)
.invoke(params[0],null).toString());
} catch (NoSuchMethodException no) {
no.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return sb.toString();
}
};
}
@SuppressWarnings("rawtypes")
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
//设置缓存过期时间
//rcm.setDefaultExpiration(60);//秒
return rcm;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
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.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@Autowired
private RedisTemplate redisTemplate;
redisTemplate.opsForValue().set
Gson gson = new Gson();
gson.tojson
user = gson.fromJson(userJson, User.class);
spring.cache.type = redis 进行配置
@Cacheble() @CachePut @CacheEvict 删除 @Caching
@CacheEvict: 失效缓存
@Cacheable: 插入缓存
value: 缓存名称
key: 缓存键,一般包含被缓存对象的主键,支持Spring EL表达式
unless: 只有当查询结果不为空时,才放入缓存
session 也可以放到redis中
共享Session-spring-session-data-redis
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
应用 场景
位图法统计活跃用户
- 1亿个用户 按位 0,1表示登录 40亿 需要 476MB 需要10M
- 计算连续登录的人数
如何记录
setbit mon 100000000 0
setbit mon 3 1 // 3号进行登录
setbit tue 100000000 0
setbit tue 2 1 // 2号 周二进行登录
bitop and res mon tue 与操作
对每天的人数状态位数进行and操作 得出是1的位为7天都登录的
以下多种Web应用场景,在这些场景下可以充分的利用Redis的特性,大大提高效率。
- 在主页中显示最新的项目列表:Redis使用的是常驻内存的缓存,速度非常快。LPUSH用来插入一个内容ID,作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库。
- 删除和过滤:如果一篇文章被删除,可以使用LREM从缓存中彻底清除掉。
- 排行榜及相关问题:排行榜(leader board)按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
- 按照用户投票和时间排序:排行榜,得分会随着时间变化。LPUSH和LTRIM命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。
- 过期项目处理:使用Unix时间作为关键字,用来保持列表能够按时间排序。对current_time和time_to_live进行检索,完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询,删除过期的条目。
- 计数:进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GETSET用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
- 特定时间内的特定项目:这是特定访问者的问题,可以通过给每次页面浏览使用SADD命令来解决。SADD不会将已经存在的成员添加到一个集合。
- Pub/Sub:在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令,让这个变得更加容易。
- 队列:在当前的编程中队列随处可见。除了push和pop类型的命令之外,Redis还有阻塞队列的命令,能够让一个程序在执行时被另一个程序添加到队列。
常见面试题
1.为什么要用 redis /为什么要用缓存
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:操作内存比操作硬盘速度快
高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的
2.为什么要用 redis 而不用 map/guava 做缓存
分布式下缓存一致性
3.redis 和 memcached 的区别
1. redis支持k/v/string,list,set,zset,hash,memcached只支持string
2. redis可以持久化
3. redis 支持集群
4. Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
4.redis 常见数据结构以及使用场景分析
1. String set,get,decr,insr,mget 微博数粉丝数
2. Hash hget,hset,hgetall 存储用户信息,商品信息
3. List lpush,rpush,lpop,rpop,lrange 如微博的关注列表,粉丝列表, 消息列表
4. Set sadd,spop,smembers,sunion 如共同关注、共同粉丝、共同喜好 取交集并集
5. Sorted Set zadd,zrange,zrem,zcard 排行榜
5. redis 设置过期时间 、
我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制
redis是怎么对这批key进行删除的?
定期删除:每隔 100ms 就随机抽取一些设置了过期时间的key检查其是否过期,如果过期就删 除
惰性删除: get的时候删除
如果惰性删除太多, 内存会爆吗, 内存淘汰机制
6.redis 内存淘汰机制
redis 提供 6种数据淘汰策略:
达到内存上限后 移除规则可以通过maxmemory-policy来指定。
移除规则
1.volatile-lru:使用LRU算法移除过期集合中的key
2.allkeys-lru:使用LRU算法移除key (这个是常用的)
3.volatile-random:在过期集合中移除随机的key
4.allkeys-random:移除随机的key
5.volatile-ttl:移除那些TTL值最小的key,即那些最近才过期的key。
6.noeviction:不进行移除。针对写操作,只是返回错误信息。
7. redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以 进行恢复)
RDB和AOF
Redis 4.0 对于持久化机制的优化 可以通过配置项 aof-use-rdb-preamble 开启 混合持久化
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
8. 缓存雪崩和缓存穿透问题解决方案
缓存雪崩:缓存大量失效,请求全到数据库
解决办法:
事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
事中:本地ehcache缓存 + hystrix限流&降级,避免mysql崩掉
事后:利用 redis 持久化机制保存的数据尽快恢复缓存
将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存穿透:大量请求数据不在缓存中
布隆过滤器,将所有可能存在的数据哈 希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压 力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存 在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,长不超过五分钟。
缓存击穿:某个热点key过期后,大量并发请求过来
9.如何保证缓存与数据库双写时的数据一致性
读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致 的情况写完后更新缓存
10.redis常见性能问题和解决方案
1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作不使用rdb, 由slave 开启rdb, 开启AOF
2). Slave和Master最好在同一个局域网内
11.redis的并发竞争问题如何解决?
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问,不存在竞争,但是Jedis客户端对Redis进行并发访问时会竞争
1.客户端角度,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
12.Redis备份方案如何设计?
备份分为冷备和热备,冷备的话可以定期生成RDB,再将RDB放到一个分布式文件系统上保存,不过需要注意生成RDB过程中,Redis由于采用Fork子进程和CopyOnWrite的方式,会导致短暂的应用阻塞、内存上涨和磁盘IO繁忙问题。建议在业务低峰时期主动调用BGSAVE进行备份;热备的话可以考虑Sentinel管理Redis主从,或Redis Cluster。
13.哨兵模式实现原理?
14.Redis为什么这么快?
- 数据存于内存中,类似于HashMap时间复杂度O(1)
- 采用单线程,避免了不必要的上下文切换和竞争条件
- 使用多路I/O复用模型,非阻塞IO; 多路 I/O 复用模型 ,多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
Redis在上年已用作Session、Scheduler与Throttling
以上是关于Redis的主要内容,如果未能解决你的问题,请参考以下文章