1.属于什么类型的数据库
not only sql 非关系型数据库,与传统的关系型数据库不同,存储形式都是kv形式。
2.特点
几乎不支持事务,key-value形式存储,支持队列和缓存(可以设置数据的过期时间)
2.1 数据存储的持久化
可以将内存中的数据保存在磁盘上,重启是可以加载磁盘的内容进行使用
2.2 多样的数据存储类型
list,set,zset,hash 等数据结构redis都支持
2.3 支持数据备份
master-slave模式的数据备份,哨兵机制。
3.redis的一般基本配置
redis.conf----配置文件
需要修改读写权限进行操作
sudo chmod 777 ××× (每个人都有读和写以及执行的权限)
cd 到 redis.conf 的文件路径,修改操作权限
sudo chmod 777 redis.conf
输入ubantu的密码
gidit redis.conf打开redis.conf
默认IP是127.0.0.1 端口号是 6379
需要远程控制,或者公用redis的话,需要修改IP为真实的IP
默认的数据文件存储路径为 dir /var/lib/redis
4.redis的使用
4.1启动服务器
redis-server
4.2启动客户端
redis-cli
启动客户端,默认是连接0号数据库,默认有16个数据库(0-15)。
select 8 切换到8号数据库
redsi的数据存储结构
key是不能重复的字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合
5.数据库的增删改查
5.1 string 字符串类型
set name kobe set + key + value
setex age 3 18 setex key time value 设置过期时间为time
mset name1 a name2 b name3 c mset设置多个键值对
append name1 opy 给指定键追加值,值会按照字符串进行拼接
mget name1 name2 name3 获取多个值
keys pattern 键支持正则表达
keys * 查看所有的键
keys a* 以a开头的键
keys *a* 包含a的键
keys *a 以a结尾的键
exists key 查看键是否存在 1 表示存在 0 表示不存在
type key 查看键对应的值得类型
del key 删除键,值会自动删除
5.2 hasn 类型
hash用于存储对象,对象是由键与值构成,值的类型为string, Redis hash 是一个string类型的field和value的映射表
hset user name nash 设置user对象的name的值为nash
hkeys user 获取user对象的所有键
hget user "name" 获取user对象name的值
hdel user "name" 删除user对象name键
hash的理解:hash存储的类型是对象,结构类似于 key = {"name":"zhangsan","age":"18"} , 相当于存了一张表中的部分字段,知道key就可以查看所有的字段,然后取出每个字段的值,如果分开存为string类型的话,不好取。
使用场景:比如我们要存储一个用户信息对象数据,包含以下信息:用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储。
方式一:ID为查找的key,对象的其他信息封装成一个序列化的方式进行存储,缺点:增加了序列化和反序列化的开销,并且,在修改数据时,需要进行并发保护,同时修改某个信息时,同一时间必须一个人操作,操作完毕后,另个人才能操作,还要引入锁等安全考量。
方式二:ID为查找的key,可以这样存储,1 = {"name":"zhang"} 1 = {"age":"18"} 1 = {"gender":"1"} ,这样优点:省去了序列化和反序列化的开销,也解决了安全问题,但是如果一个用户的信息字段特别多,会造成大量的内存开销。
方式三:ID为查找的key,key对应的值,其实就是一个Map,Map的field是成员的属性,value是属性的值。操作成员的属性值,直接通过 key(用户ID) + field(属性标签)就可以操作对应属性数据了,不用重复存储数据,也不会带来序列化和并发修改控制的问题。缺点:redis是单线程模式,hgetall key 可以查到 Map中的所有filed-value,相当于遍历了整个映射,如果filed-value的数量太多,会造成耗时等待。
# TODO redis是怎么解决同时修改数据的安全性问题呢?
# TODO 网上说的redis和mysql数据的一致性怎么解决?
5.3 list 类型
列表的元素类型为string
lpush key value1 value2 从左侧插入
lpush name kobe nash ball [ball,nash,kobe]
lrange name 0 -1 获取name列表中的所有元素值
使用场景:消息队列, # TODO 分布式爬虫时候用到 后续增加
5.4 set类型
set 的特点:去重,元素类型为string型,不支持修改
sadd a zhang wang li zhao :设置 集合 a 元素为 zhang wang li zhao
smembers a :查看集合a里面的元素
srem a zhang 删除集合中的指定元素
# TODO 分布式爬虫的时候用到set去重。
zset 有序集合
有序:依靠权重,即集合中的每一个元素都会关联一个double类型的score值,根据值的大小进行排序。从小到大排序
zadd s 4 zhang 5 wang 3 zhao 2 li 1 sun 有序集合的设置
zrange s 0 -1 有序集合元素查看
zrangebyscore s 4 5 返回score值在min和max之间的元素 zhang wang
zscore s zhang 返回元素zhang的score(权重)值 4
zcard s 返回s中有多少个元素
6. redis的优缺点
6.1 速度快
1.纯内存操作
绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);如果数据一直存在内存中的话,那么关机后数据是不是就不见了?当然不是,redis中提供了两种方案,实现数据的持久化存储,哪两种方案呢?
1.1 快照方式
是Redis默认的数据存储方式,指定时间间隔里面,将数据写入指定的磁盘文件中,即dump.rdb文件,Reids重启后会加载这个文件里面的数据到内存中。
快照方式的配置,在reids.conf文件的SNAPSHOTTING 中,具体参数和配置详见https://www.cnblogs.com/itdragon/p/7906481.html。
1.2 触发快照模式
1. 指定的时间间隔内,执行指定次数的写操作。
2. 执行save(阻塞),或者bgsave(异步)命令。
3. 执行 flushall 命令,清空数据库的所有数据。
4. 执行 shutdown命令,关系服务器。
1.3 恢复数据
dumps.rdb拷贝到redis的安装目录的bin目录下,重启redis即可。一般开发中,会考虑到物理机的磁盘损坏,会选择备份dump.rdb文件。
1.4 快照的优缺点
优点:
1.适合大规模的数据恢复。
2.针对数据的完整新和一致性要求不高的业务。
缺点:
1.数据的存储可能完整性不高,因为存在在最后一次备份的过程中,宕机了。
2.备份数据是占用内存,redis在备份时,会创建一个独立的子进程,将数据写入临时文件(此时内存中就会有两份一样的数据,一份是内存中存在的数据,一份是临时数据),最后在用临时文件替换之前的备份文件。因此redis的数据恢复需要在系统不忙的深夜进行比较合理。
1.5 追加方式
Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。注意:追加的不是数据,是每一次操作的指令。
1.6 追加方式的触发
根据文件设置的情况触发,可以每执行一次命令触发一次,也可以是每秒触发一次,具体见:https://www.cnblogs.com/itdragon/p/7906481.html。
1.7 追加的优缺点
优点:数据的完整性和一致性更高
缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢。
1.8 关于redis持久化的总结
1. 默认开启快照模式。
2. 如果用redis做缓存的话,可以不用开启快照模式。
3. 快照模式适合大面积的恢复数据,但是数据的一致性不高。
4.追加模式需要手动启动。
5. 追加模式恢复数据的一致性高,但是恢复效率较低。
6. 如果考虑用redis做持久化存储,建议快照和追加模式都开启。
2.单线程模式
能避免上下文切换也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
3.非阻塞的IO多路复用
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
7 redis的过期策略以及内存淘汰机制
1. 过期策略
redis采用的是定期删除+惰性删除策略。定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
2. 内存淘汰机制
redis.conf中有配置,allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。配置见:https://www.cnblogs.com/rjzheng/p/9096228.html。
如果没有设置 expire 的key,那么内存满的时候,再新写入数据就会报错。
8. redis的主从复制
一台主机master可以拥有多从机slave,一台从机slave又可以拥有多个从机slave。下图的redis的集群架构,是常用的一种架构,主要有两个特点:
1. 高扩展性:下面的从机可以随时配置,不影响架构。
2. 高可用性:一台reids从机故障,不影响整体的读数据,可以从其他从机读取数据。要是主机坏了,会有哨兵机制进行维护。
具体搭建:https://www.cnblogs.com/itdragon/p/7906481.html。 # TODO 后续自己实现搭建
那么如何保证主机挂了,架构还能继续运行呢? 哨兵机制就派上了用场。哨兵主要做三件事:
1. 监视:不断的监视master和slave是否运行正常。
2. 提醒:当某个redis出现故障的时候,哨兵会通过API向管理员或其他程序发出通知。
3. 故障迁移:当主机出现故障时,会哨兵会自动将该主机下的某一个从机设置为新的主机,并让其他从机和新主机建立主从关系。
哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
9. redis的雪崩机制
10 redis做缓存与数据库的数据一致性问题
# TODO 需要时间消化 https://www.cnblogs.com/rjzheng/p/9041659.html。
11. redis如何解决高并发问题。
redis配置文件中有最大连接数量,maxclients可以设置最大的链接数量。如果redis是一个公共的redis,多个用户均可以链接使用,那么存在多个用户同时修改一个key的值的情况,怎么解决?
1.乐观锁:确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。
# TODO 怎么实行乐观锁?
2. 队列:把redis.set的操作放入一个队列中,先进先出,一个一个执行。
12. redis资源竞争的问题。
以:redis的优先级队列为例:
线程a要执行两部才能删除数据:第一步:取数据(取完后数据还在,内存中),第二步:删除数据1
线程b要执行两部才能删除数据:第一步:取数据(取完后数据还在,内存中),第二步:删除数据1
问题1:线程a执行第一步后,被线程b抢到时间片轮转,执行完了操作,删除数据1成功,线程a继续执行时,数据1已经不存在了。删除数据1失败。
问题2:假如存储的是请求对象呢,同样的两个线程会对同一份数据进行两次操作,会发出两个同样的请求,造成资源浪费。
解决方法:
方法一:共享数据同时只能被一个线程进行处理,类似于原子性。
方法二:redis的事务,但是redis的事务也不是百分之百的安全。
内存中的锁,只能解决单进程中多个线程之间的共享问题:拿到锁之间的代码,不能有过多的耗时操作,否则性能会直线下降。
前提是:多个线程共用同一把锁,才能实现。怎样保证多线程拿到同一个锁对象,怎样去实现?
问题:分布式形式,不同服务器上的线程取抢夺共享锁,怎么实现?
redis中的setnx:
127.0.0.1:6379> setnx lock b (integer) 0 127.0.0.1:6379> setnx lock a (integer) 0 127.0.0.1:6379> setnx locking a # locking不存在,添加值,返回1 (integer) 1 127.0.0.1:6379> setnx locking b # locking存在,不添加值,返回0 (integer) 0 127.0.0.1:6379> get locking # 获取locking中的值 "a" 127.0.0.1:6379>
127.0.0.1:6379> del locking # 删除locking
(integer) 1
死锁产生的原因:1. a 线程在执行完上锁后,挂掉了,导致其他线程永远无法打开锁(解铃还须系铃人)2.其他线程篡改了 locking中的a,改成a1,那么线程a永远无法打开锁,其他线程也永远无法进行上锁,和解锁操作。
解决死锁的办法:1.人为强行删除locking,但是不知道何时会死锁,这个方法时效性比较差 2.设置过期时间,过期后,删除locking,其他的线程也就能进行对应的上锁和解锁的操作了。
import redis class RedisLock(object): def __init__(self,host="localhost",port=6379,db=0,lock_name): self.redis= redis.StrictRedis(host=host,port=port,db=db) self.lockname = lockname def acquire_lock(self,thread_id): """thread_id线程号,用来进行解锁""" # 如果lockname存在返回0,不存在返回1 ret = self.redis.setnx(self.lockname,thread_id) if ret == 1: print("{}上锁成功".format(thread_id)) return True else: print("{}上锁失败".format(thread_id)) return False def release_lock(self,thread_id): id = self.redis.get(self.lockname) # 确保解锁和上锁线程一致 if id == thread_id: self.redis.delete(self.lockname) print("{}解锁成功".format(thread_id)) return True else: print("{}解锁成功".format(thread_id)) return False