MySQL的演进
- 单机MySQL
- Memocached(缓存)+MySQL+垂直拆分
- 分库分表+水平拆分+MySQL集群
- NoSQL
NoSQL(Not Only SQL)不仅仅是数据库,非关系型数据库
- 传统的RDBMS
结构化组织
SQL语言
数据和关系都存储在单独的表中
严格的一致性
基础的事务
- NoSQL
不仅仅是数据
没有固定的查询语言
键值对存储、列存储、文档存储、图形数据库
最终一致性
CAP定理、BASE理论(异地多活)
高性能、高可用、高可扩
- 大数据时代:3V+3高
3V:描述问题
1、海量Volume
2、多样Variety
3、实时Velocity
3高:对程序的要求
1、高并发
2、高可扩
3、高性能
NoSQL的四大分类
kv键值对:
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis+memocache
文档型数据库(bson格式和json一样):
-
MongoDB
- 基于分布式文件存储的数据库,由c++编写,主要用来处理大量的文档
- 介于关系型数据库和非关系型数据库的中间产品,是非关系型数据库中最丰富、最像关系型数据库的
-
ConthDB
列存储数据库:
- HBase
- 分布式文件系统
图形关系数据库:
- 不存放图形,存放关系
- Neo4j、InfoGrid
Redis
概述
Redis(Remote Dictionary Server),远程字典服务
开源的、ANCI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-Value数据库,并提供多种语言的API,也被称为结构化数据库
Redis作用
1、 内存存储、持久化(rdb、aof)
2、 效率高,可以用于高速缓存
3、 发布订阅系统
4、 地图信息分析
5、 计时器、计数器
特性
1、 多样化的数据类型
2、 持久化
3、 集群
4、 事务
安装
Windows环境下Redis很小,默认端口号6379
测试连接:ping命令,返回PONG
但是Redis推荐使用Linux去开发
基础知识
Redis默认有16个数据库,默认使用的是第0个数据库,可以用select进行切换
- 数据库切换
连接1:0>select 1
"OK"
- set与get
连接1:1>set name zhangsan
"OK"
连接1:1>get name
"zhangsan"
- 数据库大小
连接1:1>dbsize
"1"
- 查看当前数据库所有的key
连接1:1>keys *
1) "name"
- 清空命令
连接1:1>flushdb #清空当前数据库
"OK"
连接1:1>dbsize
"0"
连接1:1>flushall #清空所有数据库
"OK"
- 定时过期
连接1:0>exists name
"1"
连接1:0>expire name 20 #定时20秒
"1"
连接1:0>ttl name #查看剩余时间
"17"
连接1:0>ttl name
"12"
连接1:0>ttl name
"11"
连接1:0>ttl name
"10"
连接1:0>ttl name
"5"
连接1:0>ttl name
"1"
连接1:0>ttl name
"-2"
连接1:0>ttl name
"-2"
连接1:0>get name
null
- 查看key的类型
连接1:0>type name
"string"
连接1:0>type age
"string"
- 判断键是否存在
连接1:0>exists name #存在
"1"
连接1:0>exists name1 #不存在
"0"
Redis是单线程的
Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis瓶颈是机器的内存和网络带宽
Redis将所有数据全部放在内存中,所以说使用单线程去操作效率就是最高的,多线程需要CPU上下文切换(耗时),对于内存系统来说如果没有上下文切换效率就是最高的。多次读写都是在一个CPU上,在内存情况下,这个就是最佳方案。
五大基本数据类型
String
基础字符串类型的操作
连接1:0>flushdb
"OK"
连接1:0>set key1 v1 #设置值
"OK"
连接1:0>get key1 #获得值
"v1"
连接1:0>append key1 hello #添加字符串值
"7"
连接1:0>get key1
"v1hello"
连接1:0>strlen key1 #获取长度
"7"
连接1:0>append key1 "hello"
"12"
连接1:0>get key1
"v1hellohello"
连接1:0>append key1 \"hello\" #转义字符
"19"
连接1:0>get key1
"v1hellohello"hello""
自增incr、自减decr
连接1:0>set views 0
"OK"
连接1:0>get views
"0"
连接1:0>incr views #自增1
"1"
连接1:0>incr views
"2"
连接1:0>incr views
"3"
连接1:0>decr views #自减1
"2"
连接1:0>incrby views 10 #固定步长增
"12"
连接1:0>decrby views 7 #固定步长减
"5"
查看字符串
连接1:0>set key1 hello,hello,everyone
"OK"
连接1:0>get key1
"hello,hello,everyone"
连接1:0>getrange key1 0 5 #查看前五个字符
"hello,"
连接1:0>getrange key1 0 -1 #查看全部字符
"hello,hello,everyone"
替换字符串
连接1:0>set key1 abcdefg
"OK"
连接1:0>get key1
"abcdefg"
连接1:0>setrange key1 2 xx #替换b后面两个字符为xx
"7"
连接1:0>get key1
"abxxefg"
setex与setnx
在分布式锁中常用
连接1:0>setex key1 30 hello #设置key1,30秒过期
"OK"
连接1:0>ttl key1
"23"
连接1:0>setnx key2 myhello #如果不存在键key2,那么就给值myhello
"1" #赋值成功
连接1:0>ttl key1
"-2" #key1已过期
连接1:0>setnx key2 my #如果不存在键key2,那么就给值my
"0" #赋值失败
连接1:0>get key2
"myhello"
批量set、get
连接1:0>mset key1 v1 key2 v2 key3 v3 #批量set
"OK"
连接1:0>keys *
1) "key3"
2) "key1"
3) "key2"
连接1:0>mget key1 key2 key3 #批量get
1) "v1"
2) "v2"
3) "v3"
批量setnx
即msetnx
连接1:0>msetnx key1 v1 key4 v4 #msetnx是一个原子性操作,要么一起成功,要么一起失败
"0"
上面的代码表示如果key1和key4不存在,就分别赋值v1和v4。但是v1存在,v4不存在,由于事务的原子性,导致两个赋值都失败。
对象操作
连接1:0>mset user:1:name zhangsan user:1:age 2
"OK"
连接1:0>mget user:1:name user:1:age
1) "zhangsan"
2) "2"
#或者用简单点的,以json来保存值
连接1:0>set user:1 {name:zhangsan,age:3}
"OK"
连接1:0>get user:1
"{name:zhangsan,age:3}"
getset
连接1:0>getset key1 123 #先get再set,如果没有则get空值
null
连接1:0>getset key1 456
"123"
连接1:0>getset key1 789
"456"
连接1:0>get key1
"789"
List
类似于一个双端队列,两端都可以插值
插入值
连接1:0>lpush list1 one #左添加元素
"1"
连接1:0>lpush list1 two
"2"
连接1:0>lpush list1 three
"3"
连接1:0>lrange list1 0 -1 #get所有元素
1) "three"
2) "two"
3) "one"
连接1:0>lrange list1 0 1
1) "three"
2) "two"
连接1:0>rpush list1 right #右添加元素
"4"
连接1:0>lrange list1 0 -1 #右添加的元素放在栈底
1) "three"
2) "two"
3) "one"
4) "right"
注意:list的get方法是倒叙输出,即先进后出,类似栈
移除值
移除列表的第一个元素
连接1:0>lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
连接1:0>lpop list1 #从左抛
"three"
连接1:0>lrange list1 0 -1
1) "two"
2) "one"
3) "right"
连接1:0>rpop list1 #从右抛
"right"
连接1:0>lrange list1 0 -1
1) "two"
2) "one"
取值
连接1:0>lrange list1 0 -1 #存放有4个元素
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>lindex list1 2 #根据下标去取值
"two"
连接1:0>lindex list1 0
"four"
返回列表长度
连接1:0>llen list1
"4"
移除list集合中指定个数的某value
连接1:0>lrem list1 1 one #移除1个“one”
"1"
连接1:0>lrange list1 0 -1
1) "four"
2) "three"
3) "two"
再比如
连接1:0>lrange list1 0 -1 #现在有2两“four”
1) "four"
2) "four"
3) "three"
4) "two"
5) "one"
连接1:0>lrem list1 2 four #移除2个“four”
"2"
连接1:0>lrange list1 0 -1
1) "three"
2) "two"
3) "one"
截取
截取时,只保留截取到的数据,其他的数据会被push
连接1:0>lrange list1 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
连接1:0>ltrim list1 1 2 #截取下标1、2
"OK"
连接1:0>lrange list1 0 -1
1) "four"
2) "three"
rpoplpush
移除列表的最后一个元素,并将最后一个元素lpush到另一个list中
连接1:0>lrange list1 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
连接1:0>rpoplpush list1 list2 #rpoplpush
"one"
连接1:0>lrange list1 0 -1 #list1中没有“one”元素了
1) "five"
2) "four"
3) "three"
4) "two"
连接1:0>lrange list2 0 -1 #list2中增加了“one”元素
1) "one"
lset
将列表中指定下标的值替换为另外一个值,更新操作
连接1:0>lrange list1 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
连接1:0>lset list1 0 fivefive #指定list1中的第0个元素改为“fivefive”
"OK"
连接1:0>lrange list1 0 -1
1) "fivefive"
2) "four"
3) "three"
4) "two"
5) "one"
当list中没有值时是无法做lset的
指定位置插入linsert
连接1:0>lrange list1 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>linsert list1 before two hello #在“two”前面插入“hello”
"5"
连接1:0>lrange list1 0 -1
1) "four"
2) "three"
3) "hello"
4) "two"
5) "one"
连接1:0>linsert list1 after two welcome #在“two”后面插入“welcome”
"6"
连接1:0>lrange list1 0 -1
1) "four"
2) "three"
3) "hello"
4) "two"
5) "welcome"
6) "one"
list小结
-
list实际上是一个链表,left和right都可以执行push和pop操作
-
如果key不存在,会创建新的链表
-
如果移除了所有值,产生了空链表,则key会被删除
-
在两边插入或者改动值,效率最高;对于中间元素的操作,相对来说效率会低一点
-
队列(Lpush+Rpop)、栈(Lpush+Lpop)
set
set中的值不能重复!无序不重复集合
添加sadd、读取smembers
连接1:0>sadd set1 one
"1"
连接1:0>sadd set1 two
"1"
连接1:0>sadd set1 three
"1"
连接1:0>sadd set1 four
"1"
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
判断是否存在
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>sismember set1 one #判断set1中是否存在“one”
"1" #存在
连接1:0>sismember set1 zero #判断set1中是否存在“zero”
"0" #不存在
元素个数
连接1:0>smembers set1
1) "four"
2) "one"
3) "two"
4) "three"
连接1:0>scard set1 #获取set集合中元素个数
"4"
移除指定元素
连接1:0>smembers set1
1) "four"
2) "one"
3) "two"
4) "three"
连接1:0>srem set1 two #移除元素“two”
"1"
连接1:0>smembers set1
1) "four"
2) "one"
3) "three"
随机抽取元素
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>srandmember set1 #随机抽取一个元素
"four"
连接1:0>srandmember set1 #随机抽取一个元素
"three"
连接1:0>srandmember set1 #随机抽取一个元素
"three"
连接1:0>srandmember set1 #随机抽取一个元素
"one"
连接1:0>srandmember set1 2 #随机抽取两个元素
1) "two"
2) "three"
随机移除元素
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>spop set1 #随机移除一个元素
"two"
连接1:0>smembers set1
1) "four"
2) "three"
3) "one"
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>spop set1 2 #随机移除两个元素
1) "one"
2) "three"
连接1:0>smembers set1
1) "four"
2) "two"
移动元素到另一个set
连接1:0>smembers set1
1) "four"
2) "three"
3) "two"
4) "one"
连接1:0>smove set1 set2 two #将“two”从set1移动到set2
"1"
连接1:0>smembers set1
1) "four"
2) "three"
3) "one"
连接1:0>smembers set2 #即使set2不存在也能移动过去
1) "two"
set的交、并、差
连接1:0>smembers set1
1) "b"
2) "c"
3) "a"
连接1:0>smembers set2
1) "e"
2) "d"
3) "c"
连接1:0>sdiff set1 set2 #差集,即set1中有但是set2没有的元素
1) "b"
2) "a"
连接1:0>sinter set1 set2 #交集
1) "c"
连接1:0>sunion set1 set2 #并集
1) "a"
2) "c"
3) "d"
4) "b"
5) "e"
Hash
类似于key-Map类型,即key-<key-map>
添加、获取、删除
连接1:0>hset hash1 k1 v1 #添加
"1"
连接1:0>hset hash1 k2 v2
"1"
连接1:0>hset hash1 k3 v3
"1"
连接1:0>hget hash1 k1 #获取
"v1"
连接1:0>hmset hash1 k1 one k2 two #批量添加会覆盖原先的值
"OK"
连接1:0>hget hash1 k1
"one"
连接1:0>hget hash1 k2
"two"
连接1:0>hset hash1 k1 vv1 #普通重复添加则不会执行
"0"
连接1:0>hmget hash1 k1 k2 k3 #批量获取值
1) "one"
2) "two"
3) "v3"
连接1:0>hgetall hash1 #获取所有
1) "k1"
2) "one"
3) "k2"
4) "two"
5) "k3"
6) "v3"
连接1:0>hdel hash1 k1 #删除指定的key
"1"
连接1:0>hgetall hash1
1) "k2"
2) "two"
3) "k3"
4) "v3"
hash本质和String没有太大的区别,是一个简单的key-value
获取长度
连接1:0>hmget hash1 k1 k2 k3 k4
1) "one"
2) "two"
3) "three"
4) "four"
连接1:0>hlen hash1 #获取长度
"4"
判断是否存在
连接1:0>hmget hash1 k1 k2 k3 k4
1) "one"
2) "two"
3) "three"
4) "four"
连接1:0>hexists hash1 k1 #k1存在
"1"
连接1:0>hexists hash1 key1 #key1不存在
"0"
获取key与value
连接1:0>hmget hash1 k1 k2 k3 k4
1) "one"
2) "two"
3) "three"
4) "four"
连接1:0>hkeys hash1 #获取所有的key
1) "k1"
2) "k2"
3) "k3"
4) "k4"
连接1:0>hvals hash1 #获取所有values
1) "one"
2) "two"
3) "three"
4) "four"
自增、自减
连接1:0>hset hash1 k1 2 #添加<k1-2>
"1"
连接1:0>hincrby hash1 k1 1 #增加1,<k1-3>
"3"
连接1:0>hincrby hash1 k1 3 #增加3,<k1-6>
"6"
连接1:0>hincrby hash1 k1 -2 #增加-2,相当于减2
"4"
Hash中只有hincrby字段,没有hdecrby字段,因此减少就用hincrby+负值
Zset
有序集合,在set的基础上增加了一个值
set:<k1,v1> ——> zset:<k1,score,v1>
添加、获取
连接1:0>zadd myset 1 one #单个添加
"1"
连接1:0>zadd myset 2 two
"1"
连接1:0>zadd myset 3 three
"1"
连接1:0>zadd myset 4 four 5 five #批量添加
"2"
连接1:0>zrange myset 0 -1 #获取值
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
连接1:0>zrangebyscore myset 1 5 #获取1~5的值
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
排序的实现
连接1:0>zadd salary 1000 lisi
"1"
连接1:0>zadd salary 500 zhangsan
"1"
连接1:0>zadd salary 800 xiaoming
"1"
连接1:0>zrangebyscore salary 0 1000 #在0-1000之间对salary排序,默认升序
1) "zhangsan"
2) "xiaoming"
3) "lisi"
连接1:0>zrevrange myset 0 -1 #降序排列
1) "lisi"
2) "xiaoming"
3) "zhangsan"
连接1:0>zrevrangebyscore myset 4 1 #降序排列
1) "lisi"
2) "xiaoming"
3) "zhangsan"
连接1:0>zrangebyscore salary -inf +inf #在负无穷与正无穷间排序
1) "zhangsan"
2) "xiaoming"
3) "lisi"
连接1:0>zrangebyscore salary 200 900
1) "zhangsan"
2) "xiaoming"
连接1:0>zrangebyscore salary 0 1000 withscores #可以加上参数
1) "zhangsan"
2) "500"
3) "xiaoming"
4) "800"
5) "lisi"
6) "1000"
移除元素
连接1:0>zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
连接1:0>zrem myset three #移除元素“three”
"1"
连接1:0>zrange myset 0 -1
1) "one"
2) "two"
3) "four"
元素个数
连接1:0>zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
连接1:0>zcard myset #获取zset的元素个数
"4"
连接1:0>zcount myset 1 3 #区间计数(闭区间)
"3"
三种特殊数据类型
geospatial(地理空间)
geospatial用于存储地理位置,常用于地图。
geo的底层其实是Zset
geoadd
添加经纬坐标。
写经纬度时顺序不能乱,经度在前,纬度在后,而且写反了会报错
连接1:0>geoadd china:city 116.40 39.90 beijing #经纬度必须是float类型
"1"
连接1:0>geoadd china:city 121.47 31.23 shanghai
"1"
连接1:0>geoadd china:city 106.50 29.53 chongqin
"1"
连接1:0>geoadd china:city 114.05 22.54 shenzhen
"1"
连接1:0>geoadd china:city 120.16 30.24 hangzhou
"1"
geopos
读取坐标
连接1:0>geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
连接1:0>geopos china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
由于数据时float类型,所以存在精度问题
geodist
返回两个给定位置之间的距离
连接1:0>geodist china:city beijing shanghai #默认单位是m
"1067378.7564"
连接1:0>geodist china:city beijing shanghai km #单位换成km
"1067.3788"
单位:
- m
- km
- mi 英里
- ft 英尺
georadius
以给定的经纬度为中心,找出某一半径内的元素
#以(110,30)为中心,半径800km内的城市
连接1:0>georadius china:city 110 30 800 km
1) "chongqin"
连接1:0>georadius china:city 110 30 800 km withdist #加上距离
1) 1) "chongqin"
2) "341.9374"
连接1:0>georadius china:city 110 30 800 km withcoord #加上经纬度
1) 1) "chongqin"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
连接1:0>georadius china:city 110 30 800 km withcoord withdist
1) 1) "chongqin"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
#指定数量,例如输出3个(如果数量不足,则输出仅有的)
连接1:0>georadius china:city 110 30 1800 km withcoord withdist count 3
1) 1) "chongqin"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "shenzhen"
2) "922.6257"
3) 1) "114.04999762773513794"
2) "22.53999903789756587"
3) 1) "hangzhou"
2) "977.5143"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
georadiusbymember
找出位于指定范围内的元素,中心点是由给定位置元素决定
#查询杭州1000km以内的城市
连接1:0>georadiusbymember china:city hangzhou 1000 km
1) "hangzhou"
2) "shanghai"
geohash
将二维经纬度返回为11个字符的字符串,如果两个字符串长得越像则两地越接近
连接1:0>geohash china:city beijing hangzhou
1) "wx4fbxxfke0"
2) "wtmkn31bfb0"
补充
由于geo的底层是Zset,我们也可以操作Zset来操作geo
#例如用zrange来显示
连接1:0>zrange china:city 0 -1
1) "chongqin"
2) "shenzhen"
3) "hangzhou"
4) "shanghai"
5) "beijing"
连接1:0>zrem china:city hangzhou #移除某元素
"1"
连接1:0>zrange china:city 0 -1
1) "chongqin"
2) "shenzhen"
3) "shanghai"
4) "beijing"
Hyperloglog
基数统计的算法
连接1:0>pfadd mykey a b c d e f g h #添加元素
"1"
连接1:0>pfcount mykey #统计基数
"8"
连接1:0>pfadd yourkey g h i j k
"1"
连接1:0>pfcount yourkey
"5"
连接1:0>pfmerge key mykey yourkey #将两个集合合并,放入一个新集合
"OK"
连接1:0>pfcount key #重复的值不计入新集合
"11"
其优点是占用内存小,2^64个元素只占用12KB内存,有0.81%的错误率
如果允许容错,又对内存有需求,那可以使用Hyperloglog,例如计算访客人数
如果没有容错,可以使用set
Bitmaps
位存储,适用于只有两个状态的情况(true/false)
#例如对周一到周五进行打卡记录,1表示打卡,0表示未打卡
连接1:0>setbit sign 1 1 #输入打卡情况
"0"
连接1:0>setbit sign 2 1
"0"
连接1:0>setbit sign 3 0
"0"
连接1:0>setbit sign 4 1
"0"
连接1:0>setbit sign 5 0
"0"
连接1:0>getbit sign 3 #查看打卡情况
"0"
连接1:0>getbit sign 4
"1"
连接1:0>bitcount sign #统计5天一共打卡的天数
"3"
连接1:0>bitcount sign 0 -1 #或者加上范围也行
"3"