1、NoSQL

1.1 什么是NoSQL

NoSQL(Not Only SQL):泛指非关系型数据库,区别与关系型数据库,且保持了关系型数据库的ACID原则;NoSQL数据库的产生就是为了解决大规模数据集合、多重数据种类带来的挑战,特别是大数据应用难题。

1.2 NoSQL特点

  • 方便扩展(数据之间没有关系、很好扩展)

  • 大数据量高性能(Redis一秒写8万、读11万)

  • 数据类型是多样型的(不需要事先设计数据库)

  • 传统的RDBMS和NoSQL ``` 传统的 RDBMS(关系型数据库)

  • 结构化组织
  • SQL
  • 数据和关系都存在单独的表中 row col
  • 操作,数据定义语言
  • 严格的一致性
  • 基础的事务

Nosql

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库(社交关系)
  • 最终一致性
  • CAP定理和BASE
  • 高性能,高可用,高扩展 ```

1.3 3V、3高

大数据时代的3V :主要是描述问题

  • 海量Velume
  • 多样Variety
  • 实时Velocity

大数据时代的3高 : 主要是对程序的要求

  • 高并发
  • 高可扩
  • 高性能

1.4 NoSQl的分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+memeache

文档型数据库(bson、json)

  • MongoDB

    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用于存储大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库之间的产品
  • CouchDb

列存储型数据库:

  • HBase
  • 分列式文件系统

图关系型数据库:

  • Neo4j、infoGrid

  • 不是存图片,放的是图关系 如社交关系

总结:

2、Redis安装与测试

2.1 Windows下安装

Redis官网:https://redis.io

github下载链接:https://github.com/tporadowski/redis/releases

菜鸟安装教程:https://www.runoob.com/redis/redis-install.html

下载zip压缩包并解压到指定目录

解压完成后,分别打开服务端和客户端。

在客户端输入ping命令,可以检测连接是否成功,成功会显示PONG

Redis按KV键值对存储数据,使用set get

2.2 Linux安装(重要)

1.去官网下载Linux的安装包

2.将压缩包移动到opt文件夹下并解压

  1. yum install gcc-c++ #安装gcc编译
  2. make #解压之后进入redis目录使用make指令进行编译
  3. make install #make后进入src目录进行安装

3.在/usr/local目录下创建一个redis目录,并创建一个myconf子目录

  1. mkdir -p/usr/local/redis

4.将redis的默认配置文件复制到/usr/local/redis/myconf/目录下:redis.conf,以后就使用此目录下的redis.conf配置文件,这样就可以随时还原

  1. mv /opt/redis-5.0.13/redis.conf /usr/local/redis/myconf/
  2. cd /opt/redis-5.0.13/src
  3. mv mkreleasdhdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server /usr/local/redis/

4.Redis后台默认不启动,需要进入redis.conf配置文件修改daemonize为yes,默认后台启动

5.在/usr/local/redis/bin目录路径下启动redis服务端:./redis-server myconf/redis.conf

6.在/usr/local/redis/bin目录路径下使用redis-cli -p 6379 命令启动Redis客户端 使用ping检测是够连通

7.在/bin/src目录下进行性能测试:./redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000

注:测试时必须退出shell指令,如下图

7.退出:shutdowm->exit

3、Redis基础知识

redis有16个数据库,默认使用的是第0号数据库(0-15)

  1. DBSIZE:查看当前数据库中的数据个数
  2. select index:选择使用第几个数据库
  3. keys *:查看所有的Key
  4. flushdb:清空当前数据库
  5. flushall:清空所有数据库

Redis是单线程的,基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈是基于机器的内存和网络的带宽


4、五大数据类型

4.1 Redis-Key

  1. 127.0.0.1:6379> set name qing #set添加键值对【name:qing】 默认为String类型
  2. OK
  3. 127.0.0.1:6379> set age 1
  4. OK
  5. 127.0.0.1:6379> keys * #查看当前数据库的所有键值对
  6. 1) "age"
  7. 2) "name"
  8. 127.0.0.1:6379> EXISTS name #exists判断指定键是否存在 存在显示1,不存在显示0
  9. (integer) 1
  10. 127.0.0.1:6379> EXISTS name1
  11. (integer) 0
  12. 127.0.0.1:6379> move name 1 #move移除指定键 1表示确定执行
  13. (integer) 1
  14. 127.0.0.1:6379> keys *
  15. 1) "age"
  16. 127.0.0.1:6379> get age #get 获取指定键的值
  17. "1"
  18. 127.0.0.1:6379> EXPIRE age 5 #EXPIRE设置指定键的存活时间 后面跟秒
  19. (integer) 1
  20. 127.0.0.1:6379> ttl age #ttl 查看指定键的存活时间
  21. (integer) 1
  22. 127.0.0.1:6379> ttl age
  23. (integer) -2
  24. 127.0.0.1:6379> keys *
  25. (empty array)
  26. 127.0.0.1:6379> type age #type 查看指定键的数据类型
  27. string
  28. 127.0.0.1:6379> type name
  29. string

4.2 String

  1. 127.0.0.1:6379> append name fan #append 指定键值追加字符串
  2. (integer) 7
  3. 127.0.0.1:6379> get name
  4. "qingfan"
  5. 127.0.0.1:6379> keys *
  6. 1) "age"
  7. 2) "name"
  8. 127.0.0.1:6379> APPEND name2 shuai #append时,若指定键不存在,则相当于set新建键值对
  9. (integer) 5
  10. 127.0.0.1:6379> keys *
  11. 1) "name2"
  12. 2) "age"
  13. 3) "name"
  14. 127.0.0.1:6379> strlen name #strlen 获取指定键值的长度
  15. (integer) 7
  16. 127.0.0.1:6379> get age
  17. "1"
  18. 127.0.0.1:6379> incr age #incr:使指定键值+1
  19. (integer) 2
  20. 127.0.0.1:6379> get age
  21. "2"
  22. 127.0.0.1:6379> decr age #decr:使指定键值-1
  23. (integer) 1
  24. 127.0.0.1:6379> get age
  25. "1"
  26. 127.0.0.1:6379> incrby age 10 #incrby:使指定键值增加指定值
  27. (integer) 11
  28. 127.0.0.1:6379> get age
  29. "11"
  30. 127.0.0.1:6379> getrange name 0 1 #getrange 截取指定字符串 start end:0-1 前2个字符 0-0 第一个字符 0~-1 整个字符串
  31. "qi"
  32. 127.0.0.1:6379> getrange name 0 0
  33. "q"
  34. 127.0.0.1:6379> getrange name 0 -1
  35. "qingfan"
  36. 127.0.0.1:6379> setrange name 1 xx #setrange 替换指定字符串的值 1:从第一个字符开始 xx:将第一个字符后的两个字符替换成xx
  37. (integer) 7
  38. 127.0.0.1:6379> get name
  39. "qxxgfan"
  40. 127.0.0.1:6379> setex key1 30 "qing" #setex:设置键的过期时间(秒),如果键不存在,则设置成功,若键已存在则设置失败
  41. OK
  42. 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # mset 设置多个键值对
  43. OK
  44. 127.0.0.1:6379> mget k1 k2 k3 # mget 获取多个键值对
  45. 1) "v1"
  46. 2) "v2"
  47. 3) "v3"
  48. 127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx:设置多个键值对,是一个原子性操作,因为k1已经存在,所有导致k4也设置失败
  49. (integer) 0
  50. 127.0.0.1:6379> mset user:1:name qing user:1:age 5 user:2:name fan user:2:age 6 #mset设置对象 user的2个对象user:1和user:2
  51. OK
  52. 127.0.0.1:6379> mget user:1:name user:1:age user:2:name #mget对象
  53. 1) "qing"
  54. 2) "5"
  55. 3) "fan"
  56. 127.0.0.1:6379> getset k4 v4 #getset:若果键不存在则返回nil并创建键值
  57. (nil)
  58. 127.0.0.1:6379> get k4
  59. "v4"
  60. 127.0.0.1:6379> getset k4 v44 #如果键存在,则返回旧值,并设置指定的新值
  61. "v4"

4.3 List

Redis中可以用List来实现栈、队列、阻塞队列

  1. #Lpush、Rpush、Lrange
  2. 127.0.0.1:6379> LPUSH list1 one #Lpush:将元素插入到list的头部(左)
  3. (integer) 1
  4. 127.0.0.1:6379> LPUSH list1 two
  5. (integer) 2
  6. 127.0.0.1:6379> LPUSH list 7 8 9 #添加多个值
  7. (integer) 5
  8. 127.0.0.1:6379> LRANGE list1 0 -1 #LRANGE:显示list中的值,0~1 从左到右的两个元素,0~-1 所有元素
  9. 1) "two"
  10. 2) "one"
  11. 127.0.0.1:6379> Rpush list2 one two #Rpush:将一个或多个元素插入到list的尾部(右)
  12. (integer) 2
  13. 127.0.0.1:6379> LRANGE list2 0 -1
  14. 1) "one"
  15. 2) "two"
  16. #Lpop Rpop
  17. 127.0.0.1:6379> LRANGE list1 0 -1
  18. 1) "5"
  19. 2) "two"
  20. 3) "one"
  21. 127.0.0.1:6379> Lpop list1 #Lpop:将list头部第一个元素移除
  22. "5"
  23. 127.0.0.1:6379> Rpop list1 #Rpop:将list尾部的第一个元素移除
  24. "one"
  25. #Lindex
  26. 127.0.0.1:6379> LRANGE list2 0 -1
  27. 1) "one"
  28. 2) "two"
  29. 127.0.0.1:6379> Lindex list2 0 #Lindex 通过下标获取list的值,从头部开始,起始下标为0
  30. "one"
  31. #Llen
  32. 127.0.0.1:6379> Llen list2 #Llen:返回指定list的长度
  33. (integer) 2
  34. #Lrem
  35. 127.0.0.1:6379> Lrem list2 1 one #Lrem:移除list中的指定值,1代表移除1个one
  36. (integer) 1
  37. #Ltrim
  38. 127.0.0.1:6379> Lpush list 1 2 3 4
  39. (integer) 4
  40. 127.0.0.1:6379> Ltrim list 0 1 #Ltrim:根据指定下标和上标截断list,0~1代表第一个和第二个元素
  41. OK
  42. 127.0.0.1:6379> LRANGE list 0 -1
  43. 1) "4"
  44. 2) "3"
  45. #RpopLpush
  46. 127.0.0.1:6379> LRANGE list 0 -1
  47. 1) "4"
  48. 2) "3"
  49. 127.0.0.1:6379> RpopLpush list list2 #RpopLpush:将前一个list的尾部元素移到另一个list的头部
  50. "3"
  51. #Lset
  52. 127.0.0.1:6379> LRANGE list 0 -1
  53. 1) "4"
  54. 127.0.0.1:6379> Lset list 0 5 #Lset:替换list指定下标的元素 0:将第一个元素替换为5
  55. OK
  56. 127.0.0.1:6379> LRANGE list 0 -1
  57. 1) "5"
  58. #Linsert
  59. 127.0.0.1:6379> LRANGE list 0 -1
  60. 1) "5"
  61. 2) "6"
  62. 3) "7"
  63. 4) "8"
  64. 127.0.0.1:6379> Linsert list before 6 50 #在指定值的前面或后面插入值 在元素6的前面插入50
  65. (integer) 5
  66. 127.0.0.1:6379> Linsert list after 8 90
  67. (integer) 6
  68. 127.0.0.1:6379> LRANGE list 0 -1
  69. 1) "5"
  70. 2) "50"
  71. 3) "6"
  72. 4) "7"
  73. 5) "8"
  74. 6) "90"

4.4 Set

  1. 127.0.0.1:6379> sadd myset "qing" #sadd:向set集合中添加指定值
  2. (integer) 1
  3. 127.0.0.1:6379> sadd myset "fan"
  4. (integer) 1
  5. 127.0.0.1:6379> sadd myset "chen"
  6. (integer) 1
  7. 127.0.0.1:6379> smembers myset #smembers:查看指定set的所有值
  8. 1) "qing"
  9. 2) "chen"
  10. 3) "fan"
  11. 127.0.0.1:6379> sismember myset qing #sismember:判断指定值是否存在,返回1表示存在,0 不存在
  12. (integer) 1
  13. 127.0.0.1:6379> sismember myset qing2
  14. (integer) 0
  15. 127.0.0.1:6379> scard myset #scard:查看set中的数据个数
  16. (integer) 3
  17. 127.0.0.1:6379> srem myset qing #srem:删除set中的指定值
  18. (integer) 1
  19. 127.0.0.1:6379> smembers myset
  20. 1) "chen"
  21. 2) "fan"
  22. 127.0.0.1:6379> SRANDMEMBER myset #SRANDMEMBER:从set中随机抽取一个值
  23. "fan"
  24. 127.0.0.1:6379> SRANDMEMBER myset
  25. "chen"
  26. 127.0.0.1:6379> spop myset #spop:随机移除一个元素
  27. "1"
  28. 127.0.0.1:6379> sadd myset 1 2 3 4
  29. (integer) 4
  30. 127.0.0.1:6379> smove myset myset2 3 #smove:将一个set中的值移动到另外一个set中
  31. (integer) 1
  32. 127.0.0.1:6379> SMEMBERS myset2
  33. 1) "3"
  34. 127.0.0.1:6379> sdiff myset myset2 #sdiff:查看两个集合的差集,set1中不包含set2的值
  35. 1) "2"
  36. 2) "4"
  37. 127.0.0.1:6379> sinter myset myset2 #sinter:交集
  38. 1) "1"
  39. 2) "5"
  40. 127.0.0.1:6379> sunion myset myset2 #sunion:并集
  41. 1) "1"
  42. 2) "2"
  43. 3) "3"
  44. 4) "4"
  45. 5) "5"
  46. 6) "6"

4.5 Hash

Map集合,键值对存储,key-value

  1. 127.0.0.1:6379> Hset myhash name qing #Hset:向集合中添加键值对
  2. (integer) 1
  3. 127.0.0.1:6379> Hget myhash name #Hget:根据键获取指定值
  4. "qing"
  5. 127.0.0.1:6379> Hset myhash name qing name2 qing2 #Hset:同时添加多个键值对
  6. (integer) 1
  7. 127.0.0.1:6379> Hget myhash name2
  8. "qing2"
  9. 127.0.0.1:6379> hmset myhash age1 50 age2 80 #Hmset:同时添加多个键值对
  10. OK
  11. 127.0.0.1:6379> hmget myhash age1 age2 name name2 #Hmget:同时获取多个键的值
  12. 1) "50"
  13. 2) "80"
  14. 3) "qing"
  15. 4) "qing2"
  16. 127.0.0.1:6379> Hgetall myhash #获取集合中所有键值对,下标单数为键 偶数为值
  17. 1) "name"
  18. 2) "qing"
  19. 3) "name2"
  20. 4) "qing2"
  21. 5) "age1"
  22. 6) "50"
  23. 7) "age2"
  24. 8) "80"
  25. 127.0.0.1:6379> Hdel myhash name2 #Hdel:删除集合中指定键值对
  26. (integer) 1
  27. 127.0.0.1:6379> Hlen myhash #Hlen:获取集合的长度-键值对的个数
  28. (integer) 3
  29. 127.0.0.1:6379> Hexists myhash name #Hexists:判断指定的键是否存在 存在返回1,不存在返回0
  30. (integer) 1
  31. 127.0.0.1:6379> Hexists myhash name2
  32. (integer) 0
  33. 127.0.0.1:6379> Hkeys myhash #Hkeys:获取集合中的所有键
  34. 1) "name"
  35. 2) "age1"
  36. 3) "age2"
  37. 127.0.0.1:6379> Hvals myhash #Hvals:获取集合中的所有值
  38. 1) "qing"
  39. 2) "50"
  40. 3) "80"
  41. 127.0.0.1:6379> hget myhash age1
  42. "50"
  43. 127.0.0.1:6379> Hincrby myhash age1 1 #Hincrby:给指定键的值增加指定值,若键不存在则会直接创建
  44. (integer) 51
  45. 127.0.0.1:6379> Hincrby myhash age1 10
  46. (integer) 61
  47. 127.0.0.1:6379> Hsetnx myhash age3 10 #Hsetnx:增加指定键值对,如果不存在则添加成功
  48. (integer) 1
  49. 127.0.0.1:6379> Hsetnx myhash age3 9 #若键已存在则添加失败
  50. (integer) 0

4.6 Zset(有序集合)

在set的基础上增加了一个字段,可以理解为排序数,可以根据此字段的大小进行排序

  1. 127.0.0.1:6379> Zadd myset 2 two 1 one #Zadd:增加一个值
  2. (integer) 2
  3. 127.0.0.1:6379> ZRANGE myset 0 -1 #Zrange:会根据具体值前面的数进行升序排序
  4. 1) "one"
  5. 2) "two"
  6. 127.0.0.1:6379> Zrevrange salary 0 -1 #Zrevrange:按降序排列
  7. 1) "chen"
  8. 2) "qing"
  9. 127.0.0.1:6379> Zadd salary 1000 qing
  10. (integer) 1
  11. 127.0.0.1:6379> Zadd salary 10000 fan
  12. (integer) 1
  13. 127.0.0.1:6379> Zadd salary 8000 chen
  14. (integer) 1
  15. 127.0.0.1:6379> Zrange salary 0 -1
  16. 1) "qing"
  17. 2) "chen"
  18. 3) "fan"
  19. 127.0.0.1:6379> ZRANGEBYSCORE salary -0 +10000 #ZrangeByScore:可以指定排序的范围 -1~10000 负无穷~正无穷:-inf~+inf
  20. 1) "qing"
  21. 2) "chen"
  22. 3) "fan"
  23. 127.0.0.1:6379> ZRANGEBYSCORE salary -0 +10000 withscores #ZrangeByScore-withscores 打印排序的值和排序数大小
  24. 1) "qing"
  25. 2) "1000"
  26. 3) "chen"
  27. 4) "8000"
  28. 5) "fan"
  29. 6) "10000"
  30. 127.0.0.1:6379> Zrem salary fan #Zrem:移除集合中的指定元素
  31. (integer) 1
  32. 127.0.0.1:6379> ZRANGE salary 0 -1
  33. 1) "qing"
  34. 2) "chen"
  35. 127.0.0.1:6379> Zcard salary #Zcard:查询集合中的元素个数
  36. (integer) 2
  37. 127.0.0.1:6379> Zcount myset 1 2 #Zcount:获取指定区间的元素个数
  38. (integer) 3

5、三种特殊类型

5.1 geospatial

地理位置,可以推算地理位置的信息,计算两地之间的距离

  1. #geoadd:添加key,一般使用java一键导入 城市经纬度 有效经度范围:-180~180 纬度范围:-85~85
  2. 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
  3. (integer) 1
  4. 127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
  5. (integer) 1
  6. 127.0.0.1:6379> geoadd china:city 106.50 29.53 shanghai
  7. (integer) 0
  8. 127.0.0.1:6379> geoadd china:city 106.50 29.53 shenzhen 120.16 30.24 hangzhou
  9. (integer) 2
  10. #geopos:获取指定城市的经纬度
  11. 127.0.0.1:6379> geopos china:city beijing
  12. 1) 1) "116.39999896287918091"
  13. 2) "39.90000009167092543"
  14. #geodist:查看两个城市的直线距离 可切换单位:千米km、英里mi、英尺ft
  15. 127.0.0.1:6379> geodist china:city beijing shanghai km
  16. "1464.0708"
  17. #georadius:返回以指定经纬度为中心 指定半径内的城市 #withdist:显示直线距离 withcoord:显示经纬度参数 count:显示多少个城市
  18. 127.0.0.1:6379> georadius china:city 110 30 1000 km
  19. 1) "shanghai"
  20. 2) "shenzhen"
  21. 3) "hangzhou"
  22. 127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1
  23. 1) 1) "shanghai"
  24. 2) "341.9374"
  25. 3) 1) "106.49999767541885376"
  26. 2) "29.52999957900659211"
  27. #georadiusbymember:返回指定城市周围的城市
  28. 127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km
  29. 1) "shanghai"
  30. 2) "shenzhen
  31. #其余命令与set类似:Zrange、Zrem
  32. 127.0.0.1:6379> Zrange china:city 0 -1
  33. 1) "shanghai"
  34. 2) "shenzhen"
  35. 3) "hangzhou"
  36. 4) "beijing"

5.2 Hyperloglog

基数:不重复的元素,如:

A{1,2,,3,5,8} B{1,3,5,8} A、B集合的基数就是2

Reis Hyperloglog数据结构用于统计基数,允许有容错

  1. 127.0.0.1:6379> Pfadd key1 a b c d e f g #Pfadd:创建一组元素
  2. (integer) 1
  3. 127.0.0.1:6379> PFcount key1 #Pfcount:统计一组元素中的基数数量
  4. (integer) 7
  5. 127.0.0.1:6379> pFadd key2 b c d e h i
  6. (integer) 1
  7. 127.0.0.1:6379> PFmerge key3 key1 key2 #Pfmerge:合并两组数到一个新数组,求出对应的基数
  8. OK
  9. 127.0.0.1:6379> Pfcount key3
  10. (integer) 9

5.3 Bitmaps(位图)

统计用户的信息,活跃、不活跃,打卡信息等

  1. #例:统计用户一周的打卡信息
  2. 127.0.0.1:6379> setbit sign 0 1 #星期一 打卡
  3. (integer) 0
  4. 127.0.0.1:6379> setbit sign 1 0 #星期二 未打卡
  5. (integer) 0
  6. 127.0.0.1:6379> setbit sign 2 0
  7. (integer) 0
  8. 127.0.0.1:6379> setbit sign 3 0
  9. (integer) 0
  10. 127.0.0.1:6379> setbit sign 4 1
  11. (integer) 0
  12. 127.0.0.1:6379> setbit sign 5 1
  13. (integer) 0
  14. 127.0.0.1:6379> setbit sign 6 0
  15. (integer) 0
  16. 127.0.0.1:6379> getbit sign 5 #getbit 获取某天的打卡记录
  17. (integer) 1
  18. 127.0.0.1:6379> getbit sign 6
  19. (integer) 0
  20. 127.0.0.1:6379> bitcount sign #bitcount:统计打卡天数
  21. (integer) 3

6、事务

Redis单条命令保证原子性,但事务不保证原子性,Redis事务没有隔离级别的概念

Redis事务有一次性、顺序性、排他性

流程:

  • 开启事务(multi):事务开启后,会形成一个队列
  • 命令入队:所有命令会按照先后顺序进入队列,等待统一执行命令
  • 执行事务(exec):
  1. 127.0.0.1:6379> multi #multi:开启事务
  2. OK
  3. 127.0.0.1:6379> set k1 v1 #命令入队
  4. QUEUED
  5. 127.0.0.1:6379> set k2 v2
  6. QUEUED
  7. 127.0.0.1:6379> get k2
  8. QUEUED
  9. 127.0.0.1:6379> set k3 v3
  10. QUEUED
  11. 127.0.0.1:6379> exec #exec:执行事务
  12. 1) OK
  13. 2) OK
  14. 3) "v2"
  15. 4) OK
  16. 127.0.0.1:6379> discard - #discard:取消事务

编译型异常:如get命令 后面不加指定键,命令出错导致报错,事务队列中的所有命令都不会执行!

  1. 127.0.0.1:6379> get
  2. (error) ERR wrong number of arguments for 'get' command

运行时异常:如1/0,事务队列中存在语法性错误,则执行事务时,该条命令不会执行,其他正确的命令仍然会执行!

Redis实现事务监控:

乐观锁、悲观锁

Redis中使用 watch 来实现乐观锁,可以监控一个对象,在执行一个事务时,如果在当前事务没有提交(exec)之前有另外一个线程插入,修改了监控对象的数据,那么本事务一定会执行失败。 如果发现事务执行失败,就先使用unwatch解锁,再次使用watch加锁,重新监视

  1. 127.0.0.1:6379> watch money #watch:加锁
  2. OK
  3. 127.0.0.1:6379> unwatch #unwatch解锁
  4. OK

7、Jedis

Jedis是Redis官网推荐的java连接工具

7.1 导入依赖

  1. <dependency>
  2. <groupId>redis.clients</groupId>
  3. <artifactId>jedis</artifactId>
  4. <version>3.6.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba</groupId>
  8. <artifactId>fastjson</artifactId>
  9. <version>1.2.76</version>
  10. </dependency>

7.2 idea连接redis

1> 修改redis.conf配置文件,将“bind 127.0.0.1”注释掉并添加“bind 0.0.0.0”,并将“protect-mode yes”修改为:protect-mode no

2> 将reids端口加入防火墙: firewall-cmd —zone=public —add-port=6379/tcp —permanent

重启防火墙:systemctl restart firewalld !(也可以直接关闭防火墙,一劳永逸)

如果使用的云服务器,则需要在云服务器上打开6379端口

3> “./redis-server myconf/redis.conf ” 启动云服务器的Redis服务端

4> idea测试连接

  1. public class TestRedis {
  2. public static void main(String[] args) {
  3. //创建Jedis对象
  4. //host:我的阿里云ip地址,如使用的是本地redis,则用127.0.0.1即可
  5. Jedis jedis = new Jedis("47.108.59.58",6379);
  6. System.out.println(jedis.ping());
  7. System.out.println(jedis.get("money"));
  8. System.out.println(jedis.keys("*"));
  9. }
  10. }

8、SpringBoot集成Redis

在SpringBoot2.x之后,将Redis替换为了lettuce

jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

8.1 集成测试

启动reids服务端

1> 导包

  1. <!--Redis导入依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>

2> 配置连接

  1. spring.redis.host=47.128.59.58
  2. #spring.redis.host=127.0.0.1 本地redis
  3. spring.redis.port=6379

3> 测试

  1. @SpringBootTest
  2. class Redis02SpringbootApplicationTests {
  3. //导入redisTemplate模板
  4. @Autowired
  5. private RedisTemplate redisTemplate;
  6. @Test
  7. void contextLoads() {
  8. //获取redis的连接对象,可对redis数据库进行操作
  9. RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
  10. connection.flushDb();
  11. connection.flushAll();
  12. //opsForValue:封装了所有操作字符串的方法
  13. //opsForList:封装了所有操作list的方法
  14. //...
  15. ValueOperations valueOperations = redisTemplate.opsForValue();
  16. valueOperations.set("name","qing");
  17. System.out.println(valueOperations.get("name"));
  18. }
  19. }

8.2 出现问题

当在idea中操作了Redis数据库后,再去Redis远程端查询时,出现乱码,如下图:

出现乱码原因:Redis源码中,自动进行了JDK序列化,我们需要使用Json来序列化,见8.3

8.3 自定义RedisTemplate

自定义redsiTemplate来实现对应的序列化,解决乱码

  1. @Configuration
  2. public class RedisConfig {
  3. //编写自己的redsiTemplate 固定模板
  4. @Bean
  5. @SuppressWarnings("all")
  6. public RedisTemplate<String, Object> myRedisTemplate(RedisConnectionFactory factory) {
  7. RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
  8. template.setConnectionFactory(factory);
  9. // 序列化配置 解析任意对象
  10. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  11. // json序列化利用ObjectMapper进行转义
  12. ObjectMapper om = new ObjectMapper();
  13. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  14. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  15. jackson2JsonRedisSerializer.setObjectMapper(om);
  16. // 2.序列化String类型
  17. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  18. // key采用String的序列化方式
  19. template.setKeySerializer(stringRedisSerializer);
  20. // hash的key也采用String的序列化方式
  21. template.setHashKeySerializer(stringRedisSerializer);
  22. // value序列化方式采用jackson
  23. template.setValueSerializer(jackson2JsonRedisSerializer);
  24. // hash的value序列化方式采用jackson
  25. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  26. template.afterPropertiesSet();
  27. return template;
  28. }
  29. }
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. //在企业开发中,所有的pojo实体类都需要序列化!
  5. public class User implements Serializable {
  6. private String name;
  7. private Integer age;
  8. }

8.4 自定义Redis工具类

  1. /*
  2. *File Name: RedisUtil
  3. *Created By: QingFan
  4. *Created Time:2021/9/22 12:42
  5. *Desc: null
  6. */
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.redis.core.RedisTemplate;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.util.CollectionUtils;
  11. import java.util.List;
  12. import java.util.Map;
  13. import java.util.Set;
  14. import java.util.concurrent.TimeUnit;
  15. @Component
  16. public final class RedisUtil {
  17. @Autowired
  18. private RedisTemplate<String, Object> redisTemplate;
  19. // =============================common============================
  20. /**
  21. * 指定缓存失效时间
  22. *
  23. * @param key 键
  24. * @param time 时间(秒)
  25. */
  26. public boolean expire(String key, long time) {
  27. try {
  28. if (time > 0) {
  29. redisTemplate.expire(key, time, TimeUnit.SECONDS);
  30. }
  31. return true;
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. return false;
  35. }
  36. }
  37. /**
  38. * 根据key 获取过期时间
  39. *
  40. * @param key 键 不能为null
  41. * @return 时间(秒) 返回0代表为永久有效
  42. */
  43. public long getExpire(String key) {
  44. return redisTemplate.getExpire(key, TimeUnit.SECONDS);
  45. }
  46. /**
  47. * 判断key是否存在
  48. *
  49. * @param key 键
  50. * @return true 存在 false不存在
  51. */
  52. public boolean hasKey(String key) {
  53. try {
  54. return redisTemplate.hasKey(key);
  55. } catch (Exception e) {
  56. e.printStackTrace();
  57. return false;
  58. }
  59. }
  60. /**
  61. * 删除缓存
  62. *
  63. * @param key 可以传一个值 或多个
  64. */
  65. @SuppressWarnings("unchecked")
  66. public void del(String... key) {
  67. if (key != null && key.length > 0) {
  68. if (key.length == 1) {
  69. redisTemplate.delete(key[0]);
  70. } else {
  71. redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));
  72. }
  73. }
  74. }
  75. // ============================String=============================
  76. /**
  77. * 普通缓存获取
  78. *
  79. * @param key 键
  80. * @return 值
  81. */
  82. public Object get(String key) {
  83. return key == null ? null : redisTemplate.opsForValue().get(key);
  84. }
  85. /**
  86. * 普通缓存放入
  87. *
  88. * @param key 键
  89. * @param value 值
  90. * @return true成功 false失败
  91. */
  92. public boolean set(String key, Object value) {
  93. try {
  94. redisTemplate.opsForValue().set(key, value);
  95. return true;
  96. } catch (Exception e) {
  97. e.printStackTrace();
  98. return false;
  99. }
  100. }
  101. /**
  102. * 普通缓存放入并设置时间
  103. *
  104. * @param key 键
  105. * @param value 值
  106. * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
  107. * @return true成功 false 失败
  108. */
  109. public boolean set(String key, Object value, long time) {
  110. try {
  111. if (time > 0) {
  112. redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
  113. } else {
  114. set(key, value);
  115. }
  116. return true;
  117. } catch (Exception e) {
  118. e.printStackTrace();
  119. return false;
  120. }
  121. }
  122. /**
  123. * 递增
  124. *
  125. * @param key 键
  126. * @param delta 要增加几(大于0)
  127. */
  128. public long incr(String key, long delta) {
  129. if (delta < 0) {
  130. throw new RuntimeException("递增因子必须大于0");
  131. }
  132. return redisTemplate.opsForValue().increment(key, delta);
  133. }
  134. /**
  135. * 递减
  136. *
  137. * @param key 键
  138. * @param delta 要减少几(小于0)
  139. */
  140. public long decr(String key, long delta) {
  141. if (delta < 0) {
  142. throw new RuntimeException("递减因子必须大于0");
  143. }
  144. return redisTemplate.opsForValue().increment(key, -delta);
  145. }
  146. // ================================Map=================================
  147. /**
  148. * HashGet
  149. *
  150. * @param key 键 不能为null
  151. * @param item 项 不能为null
  152. */
  153. public Object hget(String key, String item) {
  154. return redisTemplate.opsForHash().get(key, item);
  155. }
  156. /**
  157. * 获取hashKey对应的所有键值
  158. *
  159. * @param key 键
  160. * @return 对应的多个键值
  161. */
  162. public Map<Object, Object> hmget(String key) {
  163. return redisTemplate.opsForHash().entries(key);
  164. }
  165. /**
  166. * HashSet
  167. *
  168. * @param key 键
  169. * @param map 对应多个键值
  170. */
  171. public boolean hmset(String key, Map<String, Object> map) {
  172. try {
  173. redisTemplate.opsForHash().putAll(key, map);
  174. return true;
  175. } catch (Exception e) {
  176. e.printStackTrace();
  177. return false;
  178. }
  179. }
  180. /**
  181. * HashSet 并设置时间
  182. *
  183. * @param key 键
  184. * @param map 对应多个键值
  185. * @param time 时间(秒)
  186. * @return true成功 false失败
  187. */
  188. public boolean hmset(String key, Map<String, Object> map, long time) {
  189. try {
  190. redisTemplate.opsForHash().putAll(key, map);
  191. if (time > 0) {
  192. expire(key, time);
  193. }
  194. return true;
  195. } catch (Exception e) {
  196. e.printStackTrace();
  197. return false;
  198. }
  199. }
  200. /**
  201. * 向一张hash表中放入数据,如果不存在将创建
  202. *
  203. * @param key 键
  204. * @param item 项
  205. * @param value 值
  206. * @return true 成功 false失败
  207. */
  208. public boolean hset(String key, String item, Object value) {
  209. try {
  210. redisTemplate.opsForHash().put(key, item, value);
  211. return true;
  212. } catch (Exception e) {
  213. e.printStackTrace();
  214. return false;
  215. }
  216. }
  217. /**
  218. * 向一张hash表中放入数据,如果不存在将创建
  219. *
  220. * @param key 键
  221. * @param item 项
  222. * @param value 值
  223. * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
  224. * @return true 成功 false失败
  225. */
  226. public boolean hset(String key, String item, Object value, long time) {
  227. try {
  228. redisTemplate.opsForHash().put(key, item, value);
  229. if (time > 0) {
  230. expire(key, time);
  231. }
  232. return true;
  233. } catch (Exception e) {
  234. e.printStackTrace();
  235. return false;
  236. }
  237. }
  238. /**
  239. * 删除hash表中的值
  240. *
  241. * @param key 键 不能为null
  242. * @param item 项 可以使多个 不能为null
  243. */
  244. public void hdel(String key, Object... item) {
  245. redisTemplate.opsForHash().delete(key, item);
  246. }
  247. /**
  248. * 判断hash表中是否有该项的值
  249. *
  250. * @param key 键 不能为null
  251. * @param item 项 不能为null
  252. * @return true 存在 false不存在
  253. */
  254. public boolean hHasKey(String key, String item) {
  255. return redisTemplate.opsForHash().hasKey(key, item);
  256. }
  257. /**
  258. * hash递增 如果不存在,就会创建一个 并把新增后的值返回
  259. *
  260. * @param key 键
  261. * @param item 项
  262. * @param by 要增加几(大于0)
  263. */
  264. public double hincr(String key, String item, double by) {
  265. return redisTemplate.opsForHash().increment(key, item, by);
  266. }
  267. /**
  268. * hash递减
  269. *
  270. * @param key 键
  271. * @param item 项
  272. * @param by 要减少记(小于0)
  273. */
  274. public double hdecr(String key, String item, double by) {
  275. return redisTemplate.opsForHash().increment(key, item, -by);
  276. }
  277. // ============================set=============================
  278. /**
  279. * 根据key获取Set中的所有值
  280. *
  281. * @param key 键
  282. */
  283. public Set<Object> sGet(String key) {
  284. try {
  285. return redisTemplate.opsForSet().members(key);
  286. } catch (Exception e) {
  287. e.printStackTrace();
  288. return null;
  289. }
  290. }
  291. /**
  292. * 根据value从一个set中查询,是否存在
  293. *
  294. * @param key 键
  295. * @param value 值
  296. * @return true 存在 false不存在
  297. */
  298. public boolean sHasKey(String key, Object value) {
  299. try {
  300. return redisTemplate.opsForSet().isMember(key, value);
  301. } catch (Exception e) {
  302. e.printStackTrace();
  303. return false;
  304. }
  305. }
  306. /**
  307. * 将数据放入set缓存
  308. *
  309. * @param key 键
  310. * @param values 值 可以是多个
  311. * @return 成功个数
  312. */
  313. public long sSet(String key, Object... values) {
  314. try {
  315. return redisTemplate.opsForSet().add(key, values);
  316. } catch (Exception e) {
  317. e.printStackTrace();
  318. return 0;
  319. }
  320. }
  321. /**
  322. * 将set数据放入缓存
  323. *
  324. * @param key 键
  325. * @param time 时间(秒)
  326. * @param values 值 可以是多个
  327. * @return 成功个数
  328. */
  329. public long sSetAndTime(String key, long time, Object... values) {
  330. try {
  331. Long count = redisTemplate.opsForSet().add(key, values);
  332. if (time > 0)
  333. expire(key, time);
  334. return count;
  335. } catch (Exception e) {
  336. e.printStackTrace();
  337. return 0;
  338. }
  339. }
  340. /**
  341. * 获取set缓存的长度
  342. *
  343. * @param key 键
  344. */
  345. public long sGetSetSize(String key) {
  346. try {
  347. return redisTemplate.opsForSet().size(key);
  348. } catch (Exception e) {
  349. e.printStackTrace();
  350. return 0;
  351. }
  352. }
  353. /**
  354. * 移除值为value的
  355. *
  356. * @param key 键
  357. * @param values 值 可以是多个
  358. * @return 移除的个数
  359. */
  360. public long setRemove(String key, Object... values) {
  361. try {
  362. Long count = redisTemplate.opsForSet().remove(key, values);
  363. return count;
  364. } catch (Exception e) {
  365. e.printStackTrace();
  366. return 0;
  367. }
  368. }
  369. // ===============================list=================================
  370. /**
  371. * 获取list缓存的内容
  372. *
  373. * @param key 键
  374. * @param start 开始
  375. * @param end 结束 0 到 -1代表所有值
  376. */
  377. public List<Object> lGet(String key, long start, long end) {
  378. try {
  379. return redisTemplate.opsForList().range(key, start, end);
  380. } catch (Exception e) {
  381. e.printStackTrace();
  382. return null;
  383. }
  384. }
  385. /**
  386. * 获取list缓存的长度
  387. *
  388. * @param key 键
  389. */
  390. public long lGetListSize(String key) {
  391. try {
  392. return redisTemplate.opsForList().size(key);
  393. } catch (Exception e) {
  394. e.printStackTrace();
  395. return 0;
  396. }
  397. }
  398. /**
  399. * 通过索引 获取list中的值
  400. *
  401. * @param key 键
  402. * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
  403. */
  404. public Object lGetIndex(String key, long index) {
  405. try {
  406. return redisTemplate.opsForList().index(key, index);
  407. } catch (Exception e) {
  408. e.printStackTrace();
  409. return null;
  410. }
  411. }
  412. /**
  413. * 将list放入缓存
  414. *
  415. * @param key 键
  416. * @param value 值
  417. */
  418. public boolean lSet(String key, Object value) {
  419. try {
  420. redisTemplate.opsForList().rightPush(key, value);
  421. return true;
  422. } catch (Exception e) {
  423. e.printStackTrace();
  424. return false;
  425. }
  426. }
  427. /**
  428. * 将list放入缓存
  429. *
  430. * @param key 键
  431. * @param value 值
  432. * @param time 时间(秒)
  433. */
  434. public boolean lSet(String key, Object value, long time) {
  435. try {
  436. redisTemplate.opsForList().rightPush(key, value);
  437. if (time > 0)
  438. expire(key, time);
  439. return true;
  440. } catch (Exception e) {
  441. e.printStackTrace();
  442. return false;
  443. }
  444. }
  445. /**
  446. * 将list放入缓存
  447. *
  448. * @param key 键
  449. * @param value 值
  450. * @return
  451. */
  452. public boolean lSet(String key, List<Object> value) {
  453. try {
  454. redisTemplate.opsForList().rightPushAll(key, value);
  455. return true;
  456. } catch (Exception e) {
  457. e.printStackTrace();
  458. return false;
  459. }
  460. }
  461. /**
  462. * 将list放入缓存
  463. *
  464. * @param key 键
  465. * @param value 值
  466. * @param time 时间(秒)
  467. * @return
  468. */
  469. public boolean lSet(String key, List<Object> value, long time) {
  470. try {
  471. redisTemplate.opsForList().rightPushAll(key, value);
  472. if (time > 0)
  473. expire(key, time);
  474. return true;
  475. } catch (Exception e) {
  476. e.printStackTrace();
  477. return false;
  478. }
  479. }
  480. /**
  481. * 根据索引修改list中的某条数据
  482. *
  483. * @param key 键
  484. * @param index 索引
  485. * @param value 值
  486. * @return
  487. */
  488. public boolean lUpdateIndex(String key, long index, Object value) {
  489. try {
  490. redisTemplate.opsForList().set(key, index, value);
  491. return true;
  492. } catch (Exception e) {
  493. e.printStackTrace();
  494. return false;
  495. }
  496. }
  497. /**
  498. * 移除N个值为value
  499. *
  500. * @param key 键
  501. * @param count 移除多少个
  502. * @param value 值
  503. * @return 移除的个数
  504. */
  505. public long lRemove(String key, long count, Object value) {
  506. try {
  507. Long remove = redisTemplate.opsForList().remove(key, count, value);
  508. return remove;
  509. } catch (Exception e) {
  510. e.printStackTrace();
  511. return 0;
  512. }
  513. }
  514. }

9、Redis.conf


10、Redis持久化

10.1 RDB

  • redis 是内存数据库,数据断电及失,因此需要持久化,默认使用RDB,一般情况下我们无需修改RDB配置,即可使用
  • RDB的缺点就是最后一次持久化后的数据可能丢失
  • Redis会单独创建一个fork子进程来进行持久化,子进程中循环所有的数据,将数据写入到二进制文件中,会先将数据 写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好了的文件
  • RDB保存的文件就是:dump.rdb

触发机制:

  • 在配置文件中的save,例如60秒内修改了5个key就会触发RDB,生成一个dump.rdb文件
  • 执行flushall 命令,也会触发我们的RDB规则
  • 退出redis,输入命令shutdown,就会关闭redis服务,这个时候也会生成RDB文件。

恢复数据:

  • 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查demp.rdb恢复其中的数据
  • 查看rdb文件保存的位置,输入命令config get dir 如果在该目录下存在,就会自动恢复其中的数据

10.2 AOF

  • aof是将我们所有的命令都记录下来,然后在恢复的时候再重新执行一次,并且只记录写操作的命令,读命令不会记录

  • aof只追加文件,因此aof的速度会慢一些

  • aof保存的文件是: appendonly.aof

  • aof默认不开启aof,修改appendonly no为yes后 重启Redis开启aof持久化

  • 如果aof文件出错:redis提供了一个持久化文件修复工具,一个是redis-chech-rdb,另一个是redis-chech-aof ,因此如果出现这种情况,我们就可以使用这两个工具修复文件

    1. ./redis-check-aof --fix appendonly.aof