Redis
Nosql技术
NoSql是为了解决高并发、高可扩展、高可用以及高写入而产生的数据库解决方案。
NoSql就是Not Only sql。Nosql是非关系型数据库,它是关系型数据库的良好补充,而不能替代关系型数据库。
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起 (列族,可以简单理解为schema,可以在列族中添加很多的列)
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
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经常被称作为:数据结构服务器
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。
缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用)
分布式集群架构中的session分离。
聊天室的在线好友列表。
任务队列。(秒杀、抢购、12306等等)
应用排行榜。
网站访问统计。
因为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
在linux系统进行安装
[root@hdp-04 ~]# tar -zxvf redis-3.2.8.tar.gz -C apps/
[root@hdp-04 ~]# cd apps/redis-3.2.8
[root@hdp-04 redis-3.2.8]# make
本地yum源的可安装包:
[root@hdp-04 ~]# yum -y install gcc gcc-c++
重新编译:
# make
如果报错:
# make MALLOC=libc
[root@hdp-04 redis-3.2.8]# make install PREFIX=/usr/local/redis
切换到/usr/local/redis目录,发现以下信息,则说明安装成功。
使用redis-server命令,则可以进行前台启动:
# cd /usr/local/redis/bin
# ./redis-server
默认监听端口是6379
前台启动,一旦启动redis的客户端关闭,则redis也关闭。
退出:ctrl+c
第一步:将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
# 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 数据库编号
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
创建一个maven project并导入jar包依赖。
jar包搜索地址: https://search.maven.org/
添加pom依赖:
|
会自动引入相关依赖的jar包
Redis中存储数据是通过key-value存储的,对于value的类型有以下几种:
在redis中的命令语句中,命令是忽略大小写的,而key是不忽略大小写的。
删除所有的数据 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 |
如何存储对象:
自增主键:
商品编号、订单号采用string的递增数字特性生成。
定义商品编号key:items:id
192.168.101.3:7003> INCR items:id
(integer) 2
192.168.101.3:7003> INCR items:id
(integer) 3
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。如下:
赋值取值: 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
|
存储商品信息
【商品id、商品名称、商品描述、商品库存、商品好评】
商品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)
|
Redis的list使用的是linkedlist,linkedlist有两种方式:队列、堆栈。
在linkedlist中的头插法 和尾插法
队列中的名称: 入栈 push,出栈(弹栈) pop
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
|
商品评论列表
思路:
在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}' |
List和set的区别:
List是有序且可重复
Set是无序唯一。
赋值 取值 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 有
|
在set中可以进行差集、交集、并集的运算
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的元素构成的集合。
属于A且属于B的元素构成的集合。
127.0.0.1:6379> sinter set1 set2
1) "2"
2) "3"
属于A或者属于B的元素构成的集合
127.0.0.1:6379> sunion set1 set2
1) "1"
2) "2"
3) "3"
4) "4"
有序集合和set以及list的区别
Zset是唯一且有序的。
Zset是通过score 来进行排序的。
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
|
需求:根据商品销售量对商品进行排行显示
思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量。
写入商品销售量:
127.0.0.1:6379> ZADD items:sellsort 9 1001 10 1002 |
127.0.0.1:6379> ZINCRBY items:sellsort 1 1001 |
127.0.0.1:6379> ZRANGE items:sellsort 0 9 withscores |
生成访问数据 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("-----------------------------------------") }
|
redis的数据是存储在内存中,如果一旦服务器挂掉,内存中的会丢失,所以需要对内存中的数据进行持久化。
持久化有两种方式:rdb、aof
Rdb是默认支持的。
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式。
save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系。
“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照。
“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。
save 900 1 save 300 10 save 60 10000 |
配置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方式进行持久化。
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。
如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
默认情况下,aof持久化是不开启的。
将redis.conf中的appendonly 改为yes ,即可开启aof持久化。
重启redis
如果使用aof模式,那么redis启动时不会从rdb文件中加载持久化数据,而是从aof文件中加载持久化数据。
Redis的主从复制是解决单点故障问题,可以通过redis的高可用(HA)。
主从复制,则需要两个redis实例。把两个redis实例放到两个服务器中。模拟实现,可以在一台服务中启动两个实例。
第一步:复制一个redis实例
# cd /usr/local/redis
# cp -r bin/ ./bin2
第二步:修改端口
其中:6379服务器会是主redis,而6380会是从redis
无需配置
将slaveof 指定主redis的ip和端口
启动主redis和从redis
# bin/redis-server bin/redis.conf
# bin2/redis-server bin2/redis.conf
主redis中存储的数据,在从redis中会进行同步。
主redis可以进行写操作,而从redis只是只读的。