我们正处于大数据时代。对于大数据,一般的数据库无法进行分析处理
思考一下,整个网站的瓶颈是什么?
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦,所以我们需要使用缓存减轻服务器的压力,保证效率
优化服务器发展过程:优化数据结构和索引->文件缓存->Memcached(当时最热门的技术)
再然后,分库分表 + 水平拆分 + MySQL集群
如今,数据量很多,变化很快,MySQL等关系型数据库就不够用了
有的使用MySQL来存一些比较大的文件,博客,图关系等等。数据库表很大,效率就低了,如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题)大数据的IO压力下,表几乎没法更大,1亿条数据动态加个列,相当于要加1亿条数据
目前一个基本的互联网项目
为什么要用NoSQL
这个时候我们就需要使用NoSQL数据库了,NoSQL可以很好的处理以上的情况
Map<String, Object>
使用键值对来控制分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值对(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDB | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
redis-6.2.4.tar.gz
/opt
目录下sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
make
make install
确认安装完毕/usr/local/bin
cp /opt/redis-6.2.4/redis.conf testconfig
daemonize no改成yes
redis-server testconfig/redis.conf
通过指定的配置文件启动redisredis-cli -p 6379
使用Redis客户端进行连接ps -ef|grep redis
shutdown
关闭redisdocker run --name testRedis -d -p 6379:6379 redis
拉取最新版镜像并后台启动容器,测试用,没配置挂载docker exec -it testRedis /bin/bash
进入容器redis-cli
使用Redis客户端进行连接shutdown
关闭redisredis-benchmark是一个官方自带的压力测试工具
redis-benchmark命令参数
简单测试
# 测试:100个并发 100 000个请求 redis-benchmark -h loaclhost -p 6379 -c 100 -n 100000
redis默认有16个数据库,默认使用的是第0个。select 2
切换到第三个数据库
127.0.0.1:6379> dbsize # 查看数据库已用容量 (integer) 4 127.0.0.1:6379> keys * # 查看所有key 1) "test" 2) "counter:__rand_int__" 3) "mylist" 4) "key:__rand_int__" 127.0.0.1:6379> select 1 # 切换数据库 OK 127.0.0.1:6379[1]> dbsize (integer) 0
清除当前数据库flushdb
,清除所有数据库flushall
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set name cbc OK 127.0.0.1:6379> get name "cbc" 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> flushall OK 127.0.0.1:6379[2]> select 0 OK 127.0.0.1:6379> keys * (empty array)
思考:为什么redis是6379 (了解一下即可)
Redis是单线程的。Redis4.0之前是单线程运行的,Redis4.0后开始支持数据异步删除,Redis6.0后支持多线程
Redis为什么单线程还这么快
Redis-Key
127.0.0.1:6379> set name cbc OK 127.0.0.1:6379> set age 1 OK 127.0.0.1:6379> exists name # 判断是否有该键,是返回1,否返回2 (integer) 1 127.0.0.1:6379> exists name1 (integer) 0 # rename重命名k 127.0.0.1:6379> rename k key OK # 移动键值到其他数据库 127.0.0.1:6379> move name 2 # 移入3号数据库 (integer) 1 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> keys * # 查看所有key 1) "name" # 设置键值的过期时间 127.0.0.1:6379[2]> expire name 10 # 设置10s后过期 (integer) 1 127.0.0.1:6379[2]> ttl name # time to live 查看该键剩余时间 (integer) 5 127.0.0.1:6379[2]> ttl name (integer) 2 127.0.0.1:6379[2]> ttl name (integer) 0 127.0.0.1:6379[2]> ttl name (integer) -2 # -2代表已经没了,-1代表不会过期 127.0.0.1:6379> type age # 查看当前key的类型 string
127.0.0.1:6379> get age "1" 127.0.0.1:6379> append age 1 # append字符串拼接,动态修改存的字符串,如果当前key不存在,就相当于set了一个key (integer) 2 # 拼接后字符串长度 127.0.0.1:6379> get age "11" 127.0.0.1:6379> append age "1" # 这样写也行," "可用来追加空格 (integer) 3 127.0.0.1:6379> get age "111" 127.0.0.1:6379> strlen age # strlen获取字符串长度 (integer) 3 127.0.0.1:6379> set views 0 OK 127.0.0.1:6379> incr views # incr自增 (integer) 1 127.0.0.1:6379> get views "1" 127.0.0.1:6379> decr views # decr自减 (integer) 0 127.0.0.1:6379> incrby views 10 # 自增10 (integer) 10 127.0.0.1:6379> decrby views 10 # 自减10 (integer) 0 127.0.0.1:6379> getrange key1 0 3 # getrange截取字符串,[0,3] "hell" 127.0.0.1:6379> getrange key1 0 -1 # -1代表到结尾 "hello,world" 127.0.0.1:6379> getrange key1 -1 -1 # 最后一个字符 "d" 127.0.0.1:6379> get key2 "abcdefg" 127.0.0.1:6379> setrange key2 1 xx # setrange替换字符串,从下标1开始的两个字符替换成xx (integer) 7 # 替换后的字符串长度 127.0.0.1:6379> get key2 "axxdefg" # setex (set with expire) 设置过期时间 # setnx (set if not exist) 不存在再设置 (在分布式锁中会常常使用) 127.0.0.1:6379> setex key3 30 hello # setex设置并使30s后过期 OK 127.0.0.1:6379> setnx mykey redis # 不存在mykey再设置 (integer) 1 127.0.0.1:6379> setnx mykey mongodb (integer) 0 # key已存在,设置失败 127.0.0.1:6379> get mykey "redis" 127.0.0.1:6379> del key1 # del k1 k2...(批量)删除键值对 (integer) 1 # mset一次设置多个键值 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 OK 127.0.0.1:6379> keys * 1) "k1" 2) "k3" 3) "k2" 127.0.0.1:6379> mget k1 k2 k3 # mget批量取值 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx批量不存在再设置 (integer) 0 # 因为k1存在,所以失败 127.0.0.1:6379> get k4 (nil) # 发现k4也没设置成功,说明msetnx具有原子性 127.0.0.1:6379> set user:1 {name:zhangsan,age:3} # 存可转化为json对象的字符串 OK 127.0.0.1:6379> get user:1 "{name:zhangsan,age:3}" 127.0.0.1:6379> getset db redis # getset先get再set,取旧值设新值,如果之前的值不存在则相当于直接set (nil) 127.0.0.1:6379> getset db mongodb "redis" 127.0.0.1:6379> get db "mongodb"
列表,在redis里,我们可以把list当做栈、队列、阻塞队列。大部分list命令都是以l
开头的
127.0.0.1:6379> lpush list one # list插入数据 (integer) 1 127.0.0.1:6379> lpush list two (integer) 2 127.0.0.1:6379> lpush list three (integer) 3 127.0.0.1:6379> lrange list 0 -1 # list取出所有数据,发现顺序是倒着的,说明是头插法 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> rpush list right # 想象成从左往右读的表,lpush是头插往left方向放值,rpush是尾插往right方向放值 (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "right" 127.0.0.1:6379> lpop list 1 # lpop左移除值,移除一个 1) "three" 127.0.0.1:6379> rpop list 1 # rpop右移除值 1) "right" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> lindex list 0 # lindex通过下标获取list中某一个值 "two" 127.0.0.1:6379> llen list # llen返回列表的长度 (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "three" 3) "two" 4) "one" 127.0.0.1:6379> lrem list 1 one # lrem移除一个one (integer) 1 127.0.0.1:6379> lrem list 1 three # 移除一个three (integer) 1 127.0.0.1:6379> lrange list 0 -1 # 还有一个three 1) "three" 2) "two" 127.0.0.1:6379> lpush list three (integer) 3 127.0.0.1:6379> lrem list 2 three # 移除两个three (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) "two" 127.0.0.1:6379> lrange list 0 -1 1) "hello123" 2) "hello12" 3) "hello1" 4) "hello" 127.0.0.1:6379> ltrim list 1 3 # ltrim截取list中两个下标中间的值,会改变list OK 127.0.0.1:6379> lrange list 0 -1 1) "hello12" 2) "hello1" 3) "hello" 127.0.0.1:6379> rpoplpush list myotherlist # rpoplpush移除列表最右元素并放入另一个列表最左,另一个列表不存在则会创建 "hello1" 127.0.0.1:6379> lrange list 0 -1 1) "hello123" 2) "hello12" 127.0.0.1:6379> lrange myotherlist 0 -1 1) "hello1" 127.0.0.1:6379> exists mylist # exists判断列表是否存在,存在1,不存在0 (integer) 0 127.0.0.1:6379> lset mylist 0 item # 不能往不存在的list用下标存值 (error) ERR no such key 127.0.0.1:6379> lrange list 0 -1 1) "value1" 2) "item" 3) "hello12" 127.0.0.1:6379> lset list 0 value100 # lset,可以用lset把list指定下标的值替换掉 更新 OK 127.0.0.1:6379> lrange list 0 -1 1) "value100" 2) "item" 3) "hello12" 127.0.0.1:6379> lset list 3 test # lset不能设置没有值的下标 (error) ERR index out of range 127.0.0.1:6379> lpush list hello (integer) 1 127.0.0.1:6379> lpush list world (integer) 2 # linsert key before|after pivot value,若存在重复值,则在最左侧开始第一个匹配到的进行插入 127.0.0.1:6379> linsert list before world other # linsert在list的某个值的前或后插入值 (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "other" 2) "world" 3) "hello"
小结
set中的值是不可以重复的,看起来是根据字母顺序排列的,set命令基本都是以s
开头的
127.0.0.1:6379> sadd myset hello # sadd添加元素 (integer) 1 127.0.0.1:6379> smembers myset # smembers查看所有元素 1) "cbc" 2) "hello" 3) "sb" 127.0.0.1:6379> sismember myset hello # sismember查看某元素是否在该集合中 是为1,否为2 (integer) 1 127.0.0.1:6379> sismember myset lll (integer) 0 127.0.0.1:6379> scard myset # scard获取set集合中元素的个数 (integer) 6 127.0.0.1:6379> srem myset asdf # srem在集合中删除指定元素 (integer) 1 127.0.0.1:6379> srandmember myset # srandmember随机抽出一个元素 "cbc" 127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽出指定个数元素 1) "hello" 2) "zbc" 127.0.0.1:6379> spop myset # spop随机干掉一位元素 "aa" 127.0.0.1:6379> spop myset 2 # 随机干掉指定位元素 1) "cbc" 2) "sb" 127.0.0.1:6379> SMEMBERS myset 1) "hello" 2) "zbc" 127.0.0.1:6379> smove myset myset2 hello # smove转移指定元素到另一个集合 (integer) 1 127.0.0.1:6379> SMEMBERS myset2 1) "hello" 127.0.0.1:6379> SMEMBERS myset 1) "zbc" 127.0.0.1:6379> SMEMBERS key1 1) "c" 2) "b" 3) "a" 127.0.0.1:6379> SMEMBERS key2 1) "a" 2) "3" 3) "2" 4) "1" 127.0.0.1:6379> SDIFF key1 key2 # sdiff查看不同元素,key1中与key2不同的元素 1) "c" 2) "b" 127.0.0.1:6379> SDIFF key2 key1 # key2中与key1不同的元素 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> sinter key1 key2 # sinter查看两个集合中相同的元素 共同好友可以这样实现 1) "a" 127.0.0.1:6379> sinter key2 key1 # 等价于上 1) "a" 127.0.0.1:6379> SUNION key1 key2 # sunion合并去重 1) "3" 2) "a" 3) "b" 4) "c" 5) "1" 6) "2"
微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中
共同关注,共同爱好,二度好友 、推荐好友(六度分割理论)
Map的集合,key-<key,value>
,本质和string类型没有太大区别,hash命令基本都是以h
开头的
127.0.0.1:6379> hset myhash field1 cbc # hset设置哈希中的元素 (integer) 1 127.0.0.1:6379> hget myhash field1 # hget获取哈希中的元素 "cbc" 127.0.0.1:6379> hmset myhash field1 hello field2 world # hmset批量设置哈希中的元素,4.0后已被弃用,hset也可以做到 OK 127.0.0.1:6379> hmget myhash field1 field2 # hmget批量获取哈希中的元素,4.0后已被弃用,hget也可以做到 1) "hello" 2) "world" 127.0.0.1:6379> hgetall myhash # hgetall获取哈希中全部值,以k-v的形式 1) "field1" 2) "hello" 3) "field2" 4) "world" 127.0.0.1:6379> hdel myhash field1 # hdel删除哈希中特定k的一对k-v (integer) 1 127.0.0.1:6379> hgetall myhash 1) "field2" 2) "world" 127.0.0.1:6379> hlen myhash # hlen查看哈希中有多少个元素(键值对) (integer) 1 127.0.0.1:6379> HEXISTS myhash field1 # hexists判断哈希中是否存在该键,是1,否0 (integer) 0 127.0.0.1:6379> HEXISTS myhash field2 (integer) 1 127.0.0.1:6379> hkeys myhash # hkeys获取哈希中所有的键 1) "field2" 2) "field1" 127.0.0.1:6379> hvals myhash # hkeys获取哈希中所有的值 1) "world" 2) "hello" 127.0.0.1:6379> hset myhash field3 5 (integer) 1 127.0.0.1:6379> hincrby myhash field3 1 # hincrby自增1 (integer) 6 127.0.0.1:6379> hincrby myhash field3 -1 # 自减1 (integer) 5 127.0.0.1:6379> hsetnx myhash field4 hello # hsetnx不存在则创建, (integer) 1 127.0.0.1:6379> hsetnx myhash field4 world # 存在则创建失败 (integer) 0
hash可以存变更的数据user: name age,尤其是用户信息之类的,可以当做一个实体类来存些基本数据,经常变动的信息
hash更适合于对象的存储,string更适合于字符串存储
在set的基础上增加了一个值,zset k1 score1 v1
,zset命令基本都是以z
开头的
127.0.0.1:6379> zadd myset 1 one # zadd添加一个值 (integer) 1 127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值 (integer) 2 127.0.0.1:6379> ZRANGE myset 0 -1 # 遍历myset所有值,从小到大排序 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> zadd salary 2500 xiaohong 5000 zhangsan 500 cbc # 格式zadd k score1 v1 score2 v2 score3 v3 (integer) 3 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # zrangebyscore按score从小到大排序,范围为负无穷(-inf)到正无穷(+inf) 1) "cbc" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 按score排序并显示k对应的score 1) "cbc" 2) "500" 3) "xiaohong" 4) "2500" 5) "zhangsan" 6) "5000" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 范围只在负无穷到2500(闭区间),(2500指开区间 1) "cbc" 2) "500" 3) "xiaohong" 4) "2500" 127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores # zrevrange遍历myset所有值,从大到小排序 1) "zhangsan" 2) "5000" 3) "cbc" 4) "500" 5) "cbc" 6) "500" 127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores # zrevrangebyscore从大到小排序,范围为正无穷到负无穷 1) "zhangsan" 2) "5000" 3) "xiaohong" 4) "2500" 5) "cbc" 6) "500" 127.0.0.1:6379> zrange salary 0 -1 1) "cbc" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> zrem salary xiaohong # zrem移除元素 (integer) 1 127.0.0.1:6379> zrange salary 0 -1 1) "cbc" 2) "zhangsan" 127.0.0.1:6379> zcard salary # zcard获取集合中元素的个数 (integer) 2 127.0.0.1:6379> zrange myset 0 -1 1) "hello" 2) "world" 3) "cbc" 4) "sb" 5) "666" 127.0.0.1:6379> zcount myset (1 3 # zcount获取score区间的元素个数 (integer) 2
set的排序版,存储班级成绩表,工资表排序。消息带权重,排行榜
这些只是基础API,其他API若有需要,去官方文档查看是最好的
朋友的定位,附近的人,打车距离计算
Redis的Geo在Redis3.2版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人
相关命令
GEOADD
# getadd k 经度 纬度 v 添加地理位置 # 规则:两级无法直接添加。我们一般会下载城市数据,直接通过java程序一次性导入 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing (integer) 1
GEODIST
# geodist获取两个位置间的距离,单位 m:米 km:千米 mi:英里 ft:英尺,默认为米 127.0.0.1:6379> GEODIST china:city beijing xian # geodist计算两地之间的距离 "910056.5237" 127.0.0.1:6379> geodist china:city beijing xian km # 千米做单位 "910.0565"
GEOHASH
返回11个字符的Geohash字符串
GEOPOS
127.0.0.1:6379> geopos china:city beijing # geopos获取指定的城市的经纬度 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 127.0.0.1:6379> GEOPOS china:city beijing xian # 获取多个城市的经纬度 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 2) 1) "108.96000176668167114" 2) "34.25999964418929977"
GEORADIUS
# 以给定的经度纬度为中心,找出某一半径内的元素 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km # 以110,30经纬度为中心查找半径500km内的元素 1) "changqin" 2) "xian" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord # 并显示距离和经纬度 1) 1) "changqin" 2) "341.9374" # 距离 3) 1) "106.49999767541885376" # 经纬度 2) "29.52999957900659211" 2) 1) "xian" 2) "483.8340" 3) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km count 1 # 限制只取一个 1) "changqin"
GEORADIUSBYMEMBER
GEOSEARCH
# 该命令扩展了GEORADIUS命令,因此除了在圆形区域内搜索外,它还支持在矩形区域内搜索。此命令代替了现已弃用的GEORADIUS和GEORADIUSBYMEMBER geosearch key frommember|fromlonlat v byradius|bybox asc|desc withcoord withdist withhash # FROMMEMBER: 使用元素作为中心。FROMLONLAT: 使用经纬度作为中心。 # ASC:相对于中心,从最近到最远对返回的项目进行排序。DESC:相对于中心,从最远到最近对返回的项目进行排序。
GEOSEARCHSTORE
GEO底层实现原理其实就是 Zset ,我们可以使用 Zset 命令来操作GEO
127.0.0.1:6379> type china:city zset 127.0.0.1:6379> zrange china:city 0 -1 1) "changqin" 2) "xian" 3) "shengzheng" 4) "hangzhou" 5) "shanghai" 6) "beijing" 127.0.0.1:6379> zrem china:city changqin # zrem删除GEO的元素 (integer) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "xian" 2) "shengzheng" 3) "hangzhou" 4) "shanghai" 5) "beijing"
什么是基数
简介
127.0.0.1:6379> PFADD mykey a b c d e f g h i j k # fadd创建一组元素 (integer) 1 127.0.0.1:6379> PFCOUNT mykey # pfcount统计元素基数数量 (integer) 11 127.0.0.1:6379> PFADD mykey5 a a a a (integer) 1 127.0.0.1:6379> PFCOUNT mykey5 (integer) 1 127.0.0.1:6379> PFADD mykey2 i j z x c v b d (integer) 1 127.0.0.1:6379> PFCOUNT mykey2 (integer) 8 127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # pfmerge合并去重mykey和mykey2元素至mykey3中 OK 127.0.0.1:6379> PFCOUNT mykey3 (integer) 14
如果允许hyperloglog的错误率,建议使用,不允许的话使用set或自己的数据类型
位存储
统计用户信息,活跃,不活跃。登录,未登录。打卡,未打卡
bitmap位图,都是操作二进制位来进行记录,只有0和1
# 使用bitmap来记录周一到周日的打卡 # 周一:1 周二:0 周三:0 周四:1 周五:1 周六:0 周天:1 127.0.0.1:6379> setbit sign 0 1 # setbit key offset value (integer) 0 127.0.0.1:6379> setbit sign 1 0 (integer) 0 127.0.0.1:6379> setbit sign 2 0 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 4 1 (integer) 0 127.0.0.1:6379> setbit sign 5 0 (integer) 0 127.0.0.1:6379> setbit sign 6 1 (integer) 0 # getbit查看某天是否打卡 127.0.0.1:6379> getbit sign 3 (integer) 1 # bitcount统计打卡的天数 127.0.0.1:6379> bitcount sign # bitcount key [start] [end],[start]和[end]代表起始和结束字节数 (integer) 4