关系型数据库的缺点:
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不利于大规模集群
解决思路:
- 降低磁盘IO次数,越低越好 --内存存储
- 去除数据间关系,越简单越好 --不存关系,只存数据
NoSQL
即Not-Only-SQL(泛指非关系型数据库),作为关系型数据库的补充
作用:应对基于海量用户和海量数据前提下的数据处理问题
特征:
- 可扩容,可伸缩
- 大数据量下高性能
- 灵活的数据模型
- 高可用
常见NoSQL数据库:
- Redis
- HBase
- MongoDB
- memcache
Redis
Redis(REname Dictionary Server)是用C语言开发的一个开源的高性能键值对(Key-value)数据库
特征:
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能(读110000次/s,写81000次/s)
- 多数据类型支持
- 持久化支持,可以进行数据容灾恢复
应用:
- 为热点数据加速查询,如热点商品、热点新闻等
- 任务队列,如秒杀、抢购等
- 即时信息查询,如公交到站信息、在线人数等
- 时效性信息控制,如验证码、投票等
- 分布式数据共享,如分布式集群架构中session分离
- 消息队列
- 分布式锁
在Linux下安装redis
安装中需要注意几点:
- 在linux下安装redis,默认会安装到/usr/local/bin中
- 在redis解压文件中有一个redis.config是redis配置文件,很重要
- redis默认不是后台启动的,需要修改配置文件redis.config中的daemonize属性,默认是no,改为yes后,redis将在后台启动
- 通过制定的配置文件启动服务 redis-server redis.config
- 使用rdis客户端进行连接:redis-cli -p 6379(redis默认端口号6379)
- 关闭redis服务:shutdown;退出redis连接:exit
性能测试
在redis安装目录中有一个redis-benchmark是官方自带的性能测试工具
选项 | 描述 | 默认值 |
---|---|---|
-h | 指定服务器主机名 | 127.0.0.1 |
-p | 指定服务器端口 | 6379 |
-s | 指定服务器socket | |
-c | 指定并发连接数 | 60 |
-n | 指定请求数 | 10000 |
-d | 以字节的形式指定set/get值得数据大小 | 2 |
-k | 1=keep alive 0=reconnect | 1 |
-r | set/get/incr使用随机key、SADD使用随机值 |
…
#测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础的知识
redis默认有16个数据库,在redis.config中有个database属性,默认值为16,默认使用0数据库
可以使用select进行切换数据库
select 3 --切换到3数据库
keys * --查看数据库所有key的值
flushdb -- 清空当前数据库的内容
flushall -- 清空全部数据库的内容
Redis是单线程的!
官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程实现,就使用单线程了,所以redis就是单线程的
Redis为什么单线程还那么快?
- 误区1:高性能的服务器一定是多线程的?
- 误区2:多线程一定比单线程效率高?
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文切换:耗时的操作),对于内存系统来说,如果没有上下文切换,效率就是最高的。多次读且都是在一个CPU上的,在内存情况下,这个就是最佳的方案!
Redis五大数据类型
- String字符串
- List列表
- Set集合
- Zset有序集合
- Hash散列
Redis-key操作:
keys * --查看所有key
set name zhangsan --存数据,key:name;value:zhangsan
get name --取值,获取key为name的value,返回zhangsan
type name --查看key为name的数据类型,返回string
exists name --查看所有key中是否有name,如果有返回1,没有返回0
expire name 10 --给key为name的数据设置过期时间10秒,10秒后该数据会被删除
ttl name --查看key为name的数据剩余时间
move name 1 --移除key为name的数据
String类型:
##字符串拼接使用append
set key1 "v1" -- 设置值
get key1 -- 获取值
append key1 "hello" --拼接字符串,如果当前key不存在,就相当于set数据
get key1 --再次获取值,查看拼接后的数据的value,返回v1hello
strlen key1 --查看value的长度,返回7
getrange key1 0 3 --截取字符串长度[0,3],返回v1he
getrange key1 0 -1 --查看整个字符串
set key2 "abcdefg" --设置值
setrange key2 1 xx --替换值,替换从下标1开始,替换两位为xx,返回axxdefg
setex(set with expire) #设置过期时间
setex key3 30 "hello" --设置值,过期时间为30秒
setnx(set if not exist)#数据不存在时在设置(在分布式锁中会常用)
setnx mykey "redis" --设置值
setnx mykey "MongoDB" --再次设置值,但因设置过key为mykey的值,所以此处设置失败,如果不使用setnx而使用set的话,重复设置会覆盖上一次设置的值
getset db redis --先获取在设置,先获取db原有的值,在进行赋值
mset k1 v1 k2 v2 k3 v3 --同时设置多个值
mget k1 k2 k3 --同时获取多个值,返回v1 v2 v3
msetnx k1 v1 k4 v4 --msetnx不存在时创建,是一个原子性操作,设置的值必须全部不存在,有一个存在,所有的值创建全部失败
set views 0 --设置值
incr views --自增1,返回1
decr views --自减1,返回0
incrby views 10 --可以自指定增量,增加10,返回10
decrby views 5 --可以自指定减量,减10,返回5
String类似的使用场景:value除了是字符串,还可以是数字
- 计数器
- 统计多单位的数量
- 对象缓存存储
List类型:
在redis里面,list可以作为栈、队列、阻塞队列等
所有的list命令都是以l开头的
lpush list one --向列表list中添加元素,放入列表头部(左),返回当前列表的长度1
lpush list two --向列表list中添加元素,放入列表头部,返回当前列表的长度2
lpush list three --向列表list中添加元素,放入列表头部,返回当前列表的长度3
lrange list 0 -1 --获取列表list中的所有元素,返回three two one,可以看到,取值的顺序和存值的顺序是相反的
lrange list 0 1 --获取列表list中下标0和1的元素[0,1],返回three two,此处取值的顺序同样和存值的顺序是相反的
rpush list right --向列表末尾存值(右)
llen list --返回列表list的长度,返回3
lindex list 0 --获取列表list下标为0的元素,返回three
lpop list --移除列表list头部的值(左),返回移除的元素three
rpop list --移除列表list尾部的值(右),返回移除的元素right
flushdb --清空当前数据库的内容
lpush list one --添加元素
lpush list two --添加元素
lpush list three --添加元素
lpush list three --添加元素
lrem list 1 one --移除列表list中的一个one元素
lrem list 2 three --移除列表list中的两个three元素
flushdb --清空当前数据库的内容
rpush mylist "hello0" --想列表末尾添加元素
rpush mylist "hello1" --想列表末尾添加元素
rpush mylist "hello2" --想列表末尾添加元素
rpush mylist "hello3" --想列表末尾添加元素
ltrim mylist 1 2 --截取list中下标为1~2的元素
rpoplpush 旧列表名 新列表名 --移除列表中最后一位元素并将其添加至新列表
lset 列表名 下标位置 新元素 --将列表中下标位置的元素更新为新元素,注意:该列表下标位置必须有值才可使用lset替换元素
linsert 列表名 before|after "指定字符串" "新元素" --向列表中指定字符串前(after)或后(before)插入新元素
list:
- list实际上是一个链表 before Node after,left和right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,相当于空链表,也就代表不存在
- 在两边插入或改动值,效率最高;中间元素,相对来说效率低一点
可以使用作为消息队列
set类型:
set中的值是不能重复的,set的命令都是以s开头的
sadd 集合名 元素值 --向set集合中添加元素
smembers 集合名 --查看set集合中所有元素
sismember 集合名 元素值 --判断某一个元素值是否在set集合中
scard 集合名 --获取set集合中的元素个数
srem 集合名 元素值 --在set集合中移除指定元素
spop 集合名 --在set集合中随机移除一个元素
srandmember 集合名 个数 --在set集合中随机抽选出指定个数的元素
smove 旧集合名 新集合名 元素 --将旧集合中的指定元素移动到新集合中
sdiff 集合1 集合2 --找出集合1和集合2中不同的元素
sinter 集合1 集合2 --找出集合1和集合2中相同的元素
sunion 集合1 集合2 --找出集合1和集合2中所有的元素(去重)
set集合可以在微博,共同关注、共同爱好等上使用
Hash类型:
Map集合,key-map,值是一个map集合,本质和String类型没有太大区别,还是一个简单的key-value
hash命令以h开头
hset 集合名 key value --存值
hget 集合名 key --取值
hmset 集合名 key1 value1 key2 value2 --同时存多个值
mget 集合名 key1 key2 --同时获取多个值
hgetall 集合名 --获取集合中所有的值
hdel 集合名 key --删除集合中的指定key,对应的value值也就没有了
hlen 集合名 --获取集合长度
hexiste 集合名 key --判断集合中指定key是否存在
hkeys 集合名 --获取集合中所有的key
hvals 集合名 --获取集合中所有的value
hsetnx 集合名 key value --key不存在创建,存在不能设置
hash:
- 变更的数据,user name age,尤其是用户信息之类的,经常变动的信息,hash更适合对象的存储,String更适合字符串存储
Zset类型:
在set的基础上增加了一个值,zset命令以z开头
zadd 集合名 排序标志 元素 --存值
zrange 集合名 0 -1 --获取所有值
#注意:-inf 代表负无穷 +inf代表正无穷,inf可以是具体的值,可以用做范围筛选
zrangebyscore 集合名 -inf +inf --排序,从小到大
zreveange 集合名 0 -1 --排序,从大到小
rangebyscore 集合名 -inf +inf withscores --排序,从小到大,显示全部数据
zrem 集合名 元素 --移除指定元素
zcard 集合名 --查看集合大小
zcount 集合名 排序标志1 排序标志2 --获取指定区间的元素数量
set能做的zset都可以,相当于set集合能排序
三种特殊数据类型
geospatial地理位置:
朋友的定位、附近的人、打车距离计算
Redis的Geo在3.2版本推出的,这个功能可以推算地理位置的信息、两地之间的距离、方圆几里的人
只有6个命令:
- gotadd 添加地理位置 规则:两级无法直接添加,一般可以下载城市数据,通过java程序一次性导入,参数key值(经度、纬度、名称)有效的经度从-180度到180度;有效的纬度从-85.05112878度到85.05112878度,当坐标超过上述范围后,该命令会返回一个错误
- geodist 两人 / 地之间的距离 单位:m 米;km 千米; mi 英里;ft 英尺
- geohash 返回一个或多个位置元素的Geohash表示
- geopos 获取指定城市的经度和纬度
- georadius 以给定的经度和纬度为中心,找出某一半径内的元素
- georadiusbymember 找出位于指定元素周围的其他元素
注意: Geo底层的实现原理其实就是Zset,我们可以使用Zset命令来操作geo
Hyperloglog基数统计算法:
Redis在2.8.9版本更新了Hyperloglog数据结构
Hyperloglog是用来做基数统计的算法
优点:占用的内存是固定的,2^64不同的元素的技术,只需要费12KB内存,如果要从内存角度来比较的话Hyperloglog是首选(有0.81%错误率)
命令以P开头
- pfadd 集合名 元素… 添加元素
- pfcount 集合名 计数
- pfmerge 得到的集合 目标集合1 目标集合2 获取两个目标集合的并集(去重)
如果允许容错,那么一定使用Hyperloglog!
如果不允许容错,那么就使用set或自己需要的数据类型即可
Bitmaps:
位存储
统计用户信息,活跃、不活跃;登录、未登录!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态
命令 使用Bitmaps来记录周一到周二的打卡
#第一个数字代表星期 第二个数字代表是否打卡,0代表没打,1代表打了
setbit sign 0 1
setbit sign 1 0
setbit sign 2 1
setbit sign 3 1
setbit sign 4 1
setbit sign 5 0
setbit sign 6 0
#查看某一天是否打卡
getbit sign 3
#统计打卡天数
bitcount sign