Redis
Nosql技术
1redis介绍
1.1什么是NoSql
NoSql是为了解决高并发、高可扩展、高可用以及高写入而产生的数据库解决方案。
NoSql就是Not Only sql。Nosql是非关系型数据库,它是关系型数据库的良好补充,而不能替代关系型数据库。
1.2Nosql数据库分类(了解)
键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起 (列族,可以简单理解为schema,可以在列族中添加很多的列)
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
1.3什么是redis
Redis是用C语言开发的高性能的键值对存储的Nosql数据库。
redis是一个内存nosql数据库
redis中也是存储key-value形式的数据
redis中的key-value相比hbase等数据库来说,redis的value比较强大,它的value可以不仅仅是一个byte[]
redis的value可以有结构:可以是一个list,也可以是一个hash,也可以是set…
Redis存储的数据类型有五种:字符(string)、散列(hash)、列表(list)、集合(set)、有序集合(sorted set)
所以redis经常被称作为:数据结构服务器
1.4redis历史发展
2008年,意大利的一家创业公司Merzia推出了一款基于MySQL的网站实时统计系统LLOOGG,然而没过多久该公司的创始人 Salvatore Sanfilippo便 对MySQL的性能感到失望,于是他决定亲自为LLOOGG量身定做一个数据库,并于2009年开发完成,这个数据库就是Redis。 不过Salvatore Sanfilippo并不满足只将Redis用于LLOOGG这一款产品,而是希望更多的人使用它,于是在同一年Salvatore Sanfilippo将Redis开源发布,并开始和Redis的另一名主要的代码贡献者Pieter Noordhuis一起继续着Redis的开发,直到今天。
Salvatore Sanfilippo自己也没有想到,短短的几年时间,Redis就拥有了庞大的用户群体。Hacker News在2012年发布了一份数据库的使用情况调查,结果显示有近12%的公司在使用Redis。国内如新浪微博、街旁网、知乎网,国外如GitHub、Stack Overflow、Flickr等都是Redis的用户。(GitHub 代码托管平台)
VMware公司从2010年开始赞助Redis的开发, Salvatore Sanfilippo和Pieter Noordhuis也分别在3月和5月加入VMware,全职开发Redis。
1.5redis的应用场景
缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用)
分布式集群架构中的session分离。
聊天室的在线好友列表。
任务队列。(秒杀、抢购、12306等等)
应用排行榜。
网站访问统计。
2redis的安装启动
2.1下载redis
因为redis一般会在linux系统进行安装,所以下载时要下载linux系统的安装包。
官网地址:http://redis.io/
下载地址:http://download.redis.io/releases/redis-3.2.8.tar.gz
编译工具:
make BuildFile
ant build.xml
maven pom.xml
2.2安装
在linux系统进行安装
上传redis的压缩包到linux系统
解压redis压缩包
[root@hdp-04 ~]# tar -zxvf redis-3.2.8.tar.gz -C apps/
编译解压缩之后的redis文件
[root@hdp-04 ~]# cd apps/redis-3.2.8
[root@hdp-04 redis-3.2.8]# make
本地yum源的可安装包:
在linux中安装C语言环境
[root@hdp-04 ~]# yum -y install gcc gcc-c++
重新编译:
make
如果报错:
make MALLOC=libc
安装redis 安装编译之后的库和可执行文件到系统中,指定目录为/usr/local/redis
[root@hdp-04 redis-3.2.8]# make install PREFIX=/usr/local/redis
切换到/usr/local/redis目录,发现以下信息,则说明安装成功。
标题
2.3启动
2.3.1前台启动
使用redis-server命令,则可以进行前台启动:
# cd /usr/local/redis/bin
# ./redis-server
默认监听端口是6379
前台启动,一旦启动redis的客户端关闭,则redis也关闭。
退出:ctrl+c
2.3.2后端启动
第一步:将redis.conf拷贝到bin目录下
# cp /root/apps/redis-3.2.8/redis.conf /usr/local/redis/bin
第二步:修改redis.conf的配置:
修改redis绑定地址
将daemonize 改为yes,把redis以后台守护进程启动
# vi redis.conf
bind 192.168.8.14 127.0.0.1
daemonize yes
第三步:后端启动redis,指定启动命令使用修改后的redis.conf文件
[root@hdp-04 bin]# ./redis-server redis.conf
第四步:查看是否启动成功
# ps -ef | grep redis
或者 # netstat -natpl | grep 6379
解决yum安装命令失败:
# yum clean all
# yum repolist
如果有包,就正确的,如果为0 ,就需要重新挂载
挂载命令:
# mount /dev/cdrom /mnt/cdrom
电脑重启后,挂载失效。
可以修改配置文件,重启后依然生效
# cat /etc/fatab
添加下面一行内容:(统一分隔符,统一使用tab键或者统一使用空格)
/dev/sr0 /mnt/cdrom iso9660 ro 0 0
虚拟机需要配置:
要确保: 使用 ./redis-server ./redis.conf
3Redis客户端
3.1Redis自带的客户端
# cd /usr/local/redis/bin
# ./redis-cli
127.0.0.1:6379> ping
PONG
指定启动参数:-h:指定主机IP -p:指定主机端口
# ./redis-cli -h 127.0.0.1 -p 6379
Redis安装成功之后,默认有16个数据库,每个库之间是互相独立的。
默认存储的数据是放到db0中的。
切换数据库的命令:select 数据库编号
3.2Java客户端(jedis)
3.2.1jedis介绍
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。
Jedis同样也是托管在github上,地址:https://github.com/xetorthio/jedis
3.2.2搭建maven工程
创建一个maven project并导入jar包依赖。
jar包搜索地址: https://search.maven.org/
添加pom依赖:
<!--导入redis的客户端jedis jar包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
会自动引入相关依赖的jar包
3.2.3单实例连接redis
3.2.4使用连接池连接redis
4Redis数据类型
Redis中存储数据是通过key-value存储的,对于value的类型有以下几种:
字符串 Map<String, String>
Hash类型 Map<String, Map<String, String>>
List Map<String, List>
Set Map<String, HasSet>
SortedSet(zset) Map<String, TreeSet>
在redis中的命令语句中,命令是忽略大小写的,而key是不忽略大小写的。
4.1String数据类型操作
删除所有的数据 flushdb ,注意 慎用该命令
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get age
"18"
自增 incr 自减 decr
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incr name
(error) ERR value is not an integer or out of range
自增指定数值(自增的步长) incrby decrby
127.0.0.1:6379>incrby age 2
(integer) 21
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> del xxx
(integer) 0
同时设置 获取多个键值
语法:
MSET key value [key value …]
MGET key [key …]
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> mget k1 k3
1) "v1"
2) "v3"
STRLEN命令返回键值的长度,如果键不存在则返回0。
语法:STRLEN key
127.0.0.1:6379> strlen str
(integer) 0
127.0.0.1:6379> set str hello
OK
127.0.0.1:6379> strlen str
(integer) 5
如何存储对象:
4.1.1适用场景
自增主键:
商品编号、订单号采用string的递增数字特性生成。
定义商品编号key:items:id
192.168.101.3:7003> INCR items:id
(integer) 2
192.168.101.3:7003> INCR items:id
(integer) 3
4.2Hash
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。如下:
4.2.1命令
赋值取值: hset hget
127.0.0.1:6379> hset hash1 age 20
(integer) 1
127.0.0.1:6379> hget hash1 age
"20"
删除value中的key
127.0.0.1:6379> hdel hash1 age
(integer) 1
删除key
127.0.0.1:6379> del hash1
(integer) 0
增加数字
127.0.0.1:6379> hincrby hash1 age 2
(integer) 22
同时赋予多值,取多值
127.0.0.1:6379> hmset hash1 name zhangsan sex 1
OK
127.0.0.1:6379> hmget hash1 name sex age
1) "zhangsan"
2) "1"
3) "30"
获取所有键值
127.0.0.1:6379> hgetall hash1
1) "age"
2) "30"
3) "name"
4) "zhangsan"
5) "sex"
6) "1"
判断字段是否存在
语法:HEXISTS key field
127.0.0.1:6379> hexists user age 查看user中是否有age字段
(integer) 1
127.0.0.1:6379> hexists user name 查看user中是否有name字段
(integer) 0
只获取keys或者values
语法:HKEYS key HVALS key
127.0.0.1:6379> hmset user age 20 name lisi
OK
127.0.0.1:6379> hkeys user
1) "age"
2) "name"
127.0.0.1:6379> hvals user
1) "20"
2) "lisi"
获取长度 HLEN key
127.0.0.1:6379> hlen user
(integer) 2
4.2.2适用场景
存储商品信息
商品字段
【商品id、商品名称、商品描述、商品库存、商品好评】
定义商品信息的key
商品1001的信息在 Redis中的key为:[items:1001]
存储商品信息
192.168.101.3:7003> HMSET items:1001 id 3 name apple price 999.9
OK
获取商品信息
192.168.101.3:7003> HGET items:1001 id
"3"
192.168.101.3:7003> HGETALL items:1001
1) "id"
2) "3"
3) "name"
4) "apple"
5) "price"
6) "999.9"
val jedis = new Jedis("hdp-04",6379)
// 赋值
jedis.hset("item:1000","price","200")
val hvalue1 = jedis.hget("item:1000","price")
println(hvalue1)
// 自增
jedis.hincrBy("item:1000","price",10)
// 删除 0 false 1 true
println("hdel:"+jedis.hdel("item:1000","xxx"))
val hkeys: util.Set[String] = jedis.hkeys("item:1000")
// 这里会报错 解决方法 就是获取迭代器,然后使用java的api进行迭代
// val iterator = hkeys.iterator()
// while (iterator.hasNext){
// println("hkeys::"+iterator.next())
// }
// 直接导入scala到java的转换
import scala.collection.JavaConversions._
for(i<- hkeys){
println(s"key:${i}")
}
// 关闭连接
jedis.close()
4.3List
Redis的list使用的是linkedlist,linkedlist有两种方式:队列、堆栈。
在linkedlist中的头插法 和尾插法
队列中的名称: 入栈 push,出栈(弹栈) pop
4.3.1命令
lpush : 插入到队首
rpush: 插入到队尾
127.0.0.1:6379> lpush list1 4 5 6
(integer) 6
127.0.0.1:6379> rpush list1 a b
(integer) 10
-1 表示获取最后一个
127.0.0.1:6379> lrange list1 0 -1
1) "6"
2) "5"
3) "4"
4) "a"
5) "b"
127.0.0.1:6379> lrange list1 1 3
弹出列表,则表示从列表中删除
127.0.0.1:6379> lpop list1
"6"
127.0.0.1:6379> lpop list1
"5"
获取列表长度
127.0.0.1:6379> llen list1
(integer) 8
4.3.2适用场景
商品评论列表
思路:
在Redis中创建商品评论列表
用户发布商品评论,将评论信息转成json存储到list中。
用户在页面查询评论列表,从redis中取出json数据展示到页面。
定义商品评论列表key:
商品编号为1001的商品评论key【items: comment:1001】
192.168.101.3:7001> LPUSH items:comment:1001 '{"id":1,"name":"商品不错,很好!!","date":1430295077289}'
4.4Set
List和set的区别:
List是有序且可重复
Set是无序唯一。
4.4.1命令
赋值 取值
127.0.0.1:6379> sadd set1 1 2 2 3 3 4 5
(integer) 5
127.0.0.1:6379> smembers set1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
删除元素
127.0.0.1:6379> srem set1 3
(integer) 1
判断元素是否存在
127.0.0.1:6379> sismember set1 3
(integer) 0
127.0.0.1:6379> sismember set1 4
(integer) 1
0没有 1 有
4.4.2运算命令
在set中可以进行差集、交集、并集的运算
4.4.2.1差集
127.0.0.1:6379> sadd set1 1 2 3
(integer) 1
127.0.0.1:6379> sadd set2 2 3 4
(integer) 3
127.0.0.1:6379> sdiff set1 set2
1) "1"
属于A并且不属于B的元素构成的集合。
4.4.2.2交集
属于A且属于B的元素构成的集合。
127.0.0.1:6379> sinter set1 set2
1) "2"
2) "3"
4.4.2.3并集
属于A或者属于B的元素构成的集合
127.0.0.1:6379> sunion set1 set2
1) "1"
2) "2"
3) "3"
4) "4"
4.5sortedset(zset)
有序集合和set以及list的区别
Zset是唯一且有序的。
Zset是通过score 来进行排序的。
4.5.1基本操作
127.0.0.1:6379> zadd zset1 1 haha 3 hehe 2 heihei
(integer) 3
实际存储在redis中的 数据顺序为:haha 、 heihei 、 hehe (通过分数升序排序)
127.0.0.1:6379> zrange zset1 0 1
1) "haha"
2) "heihei"
降序排序
127.0.0.1:6379> zrevrange zset1 0 1
1) "hehe"
2) "heihei"
127.0.0.1:6379> zrem zset1 haha
(integer) 1
127.0.0.1:6379> zscore zset1 hehe
"3"
升序,查看 元素及值
zrange zset1 0 1 withscores
1) "haha"
2) "1"
3) "heihei"
4) "2"
返回值是更改后的分数
增加某元素的分数,返回值是更改后的分数
语法:ZINCRBY key increment member
127.0.0.1:6379> ZINCRBY scoreboard 4 lisi
"101“
获取元素的排名
升序,从小到大
语法:ZRANK key member
127.0.0.1:6379> ZRANK scoreboard lisi
(integer) 0
降序,从大到小
语法:ZREVRANK key member
127.0.0.1:6379> ZREVRANK scoreboard zhangsan
(integer) 1
4.5.2应用场景
需求:根据商品销售量对商品进行排行显示
思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量。
写入商品销售量:
商品编号1001的销量是9,商品编号1002的销量是10
127.0.0.1:6379> ZADD items:sellsort 9 1001 10 1002
商品编号1001的销量加1
127.0.0.1:6379> ZINCRBY items:sellsort 1 1001
商品销量前10名:
127.0.0.1:6379> ZRANGE items:sellsort 0 9 withscores
5综合案例
5.1LOL英雄出场次数
生成访问数据
val heros = Array("易大师", "盖伦", "金克斯", "奥巴马", "瞎子", "安妮", "光辉", "石头")
val random = new Random()
// 获取连接
val jedis = new Jedis("hdp-04", 6379)
while (true) {
// 挑一个英雄
val hero = heros(random.nextInt(heros.length))
// 更新redis中的英雄出场次数
jedis.zincrby("chuchangbang", 1, hero)
// 玩
System.out.println("敌人30秒后将到达战场.... 人在塔在....")
Thread.sleep(200)
}
排行榜数据更新
// 连接redis读取数据
val jedis = new Jedis("hdp-04", 6379)
while (true) {
val zrevrangeWithScores = jedis.zrevrangeWithScores("chuchangbang", 0, -1)
// java 代码转换为scala代码,需要导入转换
import scala.collection.JavaConversions._
for (tuple <- zrevrangeWithScores) {
System.out.println(tuple.getElement + " : " + tuple.getScore)
}
Thread.sleep(2000)
System.out.println("-----------------------------------------")
}
5.2电商推荐系统
6redis持久化
redis的数据是存储在内存中,如果一旦服务器挂掉,内存中的会丢失,所以需要对内存中的数据进行持久化。
持久化有两种方式:rdb、aof
Rdb是默认支持的。
6.1RDB持久化
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式。
在redis.conf配置文件中默认有此下配置:
save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系。
“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照。
“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。
save 900 1
save 300 10
save 60 10000
在redis.conf中:
配置dir指定rdb快照文件的位置
# Note that you must specify a directory here, not a file name.
dir ./
配置dbfilenam指定rdb快照文件的名称
# The filename where to dump the DB
dbfilename dump.rdb
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
如果需要对数据进行完整持久化,那么需要使用aof方式进行持久化。
6.2Aof持久化
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。
如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
6.2.1开启aof持久化
默认情况下,aof持久化是不开启的。
将redis.conf中的appendonly 改为yes ,即可开启aof持久化。
重启redis
如果使用aof模式,那么redis启动时不会从rdb文件中加载持久化数据,而是从aof文件中加载持久化数据。
7Redis的主从复制
Redis的主从复制是解决单点故障问题,可以通过redis的高可用(HA)。
主从复制,则需要两个redis实例。把两个redis实例放到两个服务器中。模拟实现,可以在一台服务中启动两个实例。
7.1Redis主从实例准备
第一步:复制一个redis实例
# cd /usr/local/redis
# cp -r bin/ ./bin2
第二步:修改端口
其中:6379服务器会是主redis,而6380会是从redis
7.2主redis
无需配置
7.3从redis
将slaveof 指定主redis的ip和端口
启动主redis和从redis
# bin/redis-server bin/redis.conf
# bin2/redis-server bin2/redis.conf
7.4总结
主redis中存储的数据,在从redis中会进行同步。
主redis可以进行写操作,而从redis只是只读的。