Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 是 NoSQL数据库(泛指非关系型的数据库)的一种;
NoSQL 的特点:
1、 它不支持SQL语法
2、 存储结构跟传统关系型数据库中的那种关系表完全不同,nosql中存储的数据都是KV形式
3、 NoSQL的世界中没有一种通用的语言,每种nosql数据库都有自己的api和语法,以及擅长的业务场景
4、 NoSQL中的产品种类相当多:
5、 NoSQL和SQL数据库的比较:
Redis是一个高性能的kv对缓存和内存数据库.其存储结构就是key-value,形式如下:
到官网下载最新stable版
解压源码并进入目录 tar -zxvf redis-2.8.19.tar.gz -C ./redis-src/
make
如果报错提示缺少gcc,则安装gcc : yum install -y gcc
如果报错提示:Newer version of jemalloc required 则在make时加参数:make MALLOC=libc
安装redis,指定安装目录,如 /usr/local/redis
make PREFIX=/usr/local/redis install
拷贝一份配置文件到安装目录下
切换到源码目录,里面有一份配置文件 redis.conf,然后将其拷贝到安装路径下
cp redis.conf /usr/local/redis/
启动redis
cd /usr/local/redis
bin/redis-server redis.conf
也可以修改配置文件,让redis服务在后台启动:
vi redis.conf
daemonize yes ##修改这个配置
bin/redis-server redis.conf ##重新启动
以下是客户端的登录的和退出操作:
[root@mini redis]# [root@mini redis]# bin/redis-cli -h 127.0.0.1 -p 6379
-bash: [root@mini: command not found
[root@mini redis]# 127.0.0.1:6379> quit
上面的代码如果不带-h -p ,默认连接本机的6379端口。可以通过进程号查看服务是否启动:
[root@mini redis]# bin/redis-server ./redis.conf
[root@mini redis]# ps -ef | grep redis
root 2409 1 0 01:14 ? 00:00:07 bin/redis-server *:6379
root 2633 2612 0 02:50 pts/0 00:00:00 grep redis
服务器启动成功后,需要客户端启动并尝试是否连接成功:
[root@mini redis]# bin/redis-cli
127.0.0.1:6379> ping
PONG
上面提到,用户可以对redis数据库进行字符串的读取操作,以下首先启动客户端,并在客户端进行数据库操作:
127.0.0.1:6379> set p-name "zhangsan"
OK
127.0.0.1:6379> mset p-age 22 p-height 1.65
OK
127.0.0.1:6379> keys *
1) "p-height"
2) "p-age"
4) "p-name"
127.0.0.1:6379> setex p-class 10 3
OK
127.0.0.1:6379> get p-name
"zhangsan"
上面的代码是通过客户端命令行的方式展示的,接下来看看代码的实现,这需要导入一些依赖:
链接:https://pan.baidu.com/s/1NxbJFhbQWOoau5-2Va2zig 密码:bdeq
public class JedisClientTest {
public static void main(String[] args) {
//远程调用需要关掉防火墙
Jedis client=new Jedis("192.168.254.128",6379);
String resp = client.ping();
System.out.println(resp);
}
}
字符串操作如下:
public class StringStructureData {
private Jedis client;
@Before
public void init() {
client = new Jedis("192.168.254.128",6379);
}
@Test
public void testAddPerson() {
client.set("p-name", "武藏 小次郎");
client.mset("p-age","18","p-height","1.84");
client.setex("p-class", 10, "3");
}
@Test
public void testGetPersonInfo() {
String pname= client.get("p-name");
String page= client.get("p-age");
String pclass= client.get("p-class");
System.out.println(pname);
System.out.println(page);
System.out.println(pclass);
}
}
除了可以对字符串进行存储,我们也可以将对象直接转换成二进制进行存储,其代码存储的方式有2种,A直接转换成二进制流数据,B则通过JSON进行间接转化获取,代码如下:
public class Person implements Serializable{
//必须添加该UUID 方便在反序列化的时候根据UUID进行修改。
private static final long serialVersionUID = -6465038502637652218L;
private String name;
private int age;
private double height;
public Person(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
...
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", height=" + height + "]";
}
}
public class ObjectStructureData {
...
@Test
public void savePersonBySeriz() {
Person person = new Person("张三",20,1.84);
// 讲对象转化成二进制流
ByteArrayOutputStream baos = null;
ObjectOutput oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(person);
byte[] byt = baos.toByteArray();
client.set("person".getBytes(), byt);
} catch (Exception e) {
// TODO: handle exception
}
}
@Test
public void getPersonBySeriz() throws Exception {
byte[] personByt = client.get("person".getBytes());
ByteArrayInputStream bais = new ByteArrayInputStream(personByt);
ObjectInputStream ois = new ObjectInputStream(bais);
Person person=(Person) ois.readObject();
System.out.println(person);
}
@Test
public void savePersonByGson() {
String pjson = new Gson().toJson(new Person("李四", 20, 1.84));
client.set("person".getBytes(), pjson.getBytes());
}
@Test
public void getPersonByGson() throws Exception {
byte[] personByt = client.get("person".getBytes());
String pjson = new String(personByt);
Person person = new Gson().fromJson(pjson, Person.class);
System.out.println(person);
}
}
redis支持对队列进行读写操作,如下客户端操作:
127.0.0.1:6379> lpush tasks task01 task02 task03
(integer) 3
127.0.0.1:6379> lrange tasks 0 -1
1) "task03"
2) "task02"
3) "task01"
127.0.0.1:6379> rpush ids id01 id02 id03 id04
(integer) 4
127.0.0.1:6379> lrange ids 0 -1
1) "id01"
2) "id02"
3) "id03"
4) "id04"
上面的代码中,lpush 和 rpush都是插入数据到队列中,一个是左插入,另一个右插入。其格式为
lpush/rpush key listelement listelement listelement…
lrange 是取出队列特定范围的数据,具体格式 lrange key startIndex endIndex , 用户可以指定索引,但是如果用户不知道队列的数据长度有多少,可以指定endIndex为 -1.
127.0.0.1:6379> lrange tasks 0 -1
1) "task03"
2) "task02"
3) "task01"
127.0.0.1:6379> lpop tasks
"task03"
127.0.0.1:6379> lrange tasks 0 -1
1) "task02"
2) "task01"
127.0.0.1:6379> lrange ids 0 -1
1) "id01"
2) "id02"
3) "id03"
4) "id04"
127.0.0.1:6379> rpop ids
"id04"
127.0.0.1:6379> lrange ids 0 -1
1) "id01"
2) "id02"
3) "id03"
对于删除操作,lpop key 表示弹出对应队列最左边的那个元素,此刻打印弹出元素的结果,还可以看到原始队列中已将该元素删除。rpop key这是从右边弹出该元素,原理基本不变。
127.0.0.1:6379> lrange tasks 0 -1
1) "task02"
2) "task01"
127.0.0.1:6379> lrange ids 0 -1
1) "id01"
2) "id02"
3) "id03"
127.0.0.1:6379> rpoplpush tasks ids
"task01"
rpoplpush 是将key1所对应的队列左右边的元素弹出并放入到key2对应列表的最左边,最后将那个被操作的元素显示出来。
Java应用
这里涉及到两个对象,生产者和消费者,生产者主动将任务添加到执行任务的队列中,消费者则取出并处理任务。
生产者代码:
public class TaskProduce {
public static Jedis getJedisConn(String host, int port) {
return new Jedis(host, port);
}
public static void main(String[] args) throws InterruptedException {
Jedis jedis = getJedisConn("192.168.254.128", 6379);
Random random = new Random();
while(true) {
// 睡眠一定的时间
Thread.sleep(1000 + random.nextInt(1000));
String taskId = UUID.randomUUID().toString();
//1.产生一个任务 并添加到任务队列中
jedis.lpush("task-queue", taskId);
//System.out.println("向任务队列中插入一个任务:" + taskId);
}
}
}
消费者代码:
public class TaskComsumer {
public static void main(String[] args) throws Exception {
Jedis jedis = new Jedis("192.168.254.128", 6379);
Random random = new Random();
while (true) {
//2.取出任务队列最右边的元素并添加到临时队里保存起来
String taskId = jedis.rpoplpush("task-queue", "tmp-queue");
Thread.sleep(1000);
int sed = random.nextInt(16);
if (sed%6==0) {//3.如果执行失败,要将任务从临时队列中取出重新放到任务队列中
jedis.rpoplpush("tmp-queue","task-queue");
System.out.println("任务执行失败,taskId:"+taskId);
}else {//如果执行成功,则直接从临时队列中删除。
jedis.rpop("tmp-queue");
System.out.println("任务执行成功,taskId:"+taskId);
}
}
}
}
往数据库里面插入一条hash数据
redis > HSET key field value
查看对应key表中有多少键值对
redis > hgetall key
127.0.0.1:6379> hset user001:zhangsan iphone 6
(integer) 1
127.0.0.1:6379> hset user001:zhangsan "xiao mi" 7
(integer) 1
127.0.0.1:6379> hset user001:zhangsan "meizu" 9
(integer) 1
127.0.0.1:6379> hgetall user001:zhangsan
1) "iphone"
2) "6"
3) "xiao mi"
4) "7"
5) "meizu"
6) "9"
除了创建元素,还可以对元素进行操作:
127.0.0.1:6379> hkeys user001:zhangsan
1) "iphone"
2) "xiao mi"
3) "meizu"
127.0.0.1:6379> hvals user001:zhangsan
1) "6"
2) "7"
3) "9"
127.0.0.1:6379> hget user001:zhangsan "xiao mi"
"7"
127.0.0.1:6379> hincrby user001:zhangsan "iphone" 10
(integer) 16
127.0.0.1:6379> hdel user001:zhangsan "meizu"
(integer) 1
set集合的特点:无序、无重复元素 。下面展示了如何往set里面存储数据,并且打印set内部数据个数和所有元素:
127.0.0.1:6379> sadd friend:xiaoming zhangsan lisi wangwu xiaotudou lisi
(integer) 4
127.0.0.1:6379> scard friend:xiaoming
(integer) 4
127.0.0.1:6379> smembers friend:xiaoming
1) "xiaotudou"
2) "wangwu"
3) "lisi"
4) "zhangsan"
判断一个成员是否属于某条指定的set数据 ,存在返回1 不存在返回0
127.0.0.1:6379> sismember friend:xiaoming zhangsan
(integer) 1
127.0.0.1:6379> sismember friend:xiaoming wangermazi
(integer) 0
对于set的操作还有求差集,求交集,求并集;
求差集:如下求出小明(与小微不同)的爱好,
127.0.0.1:6379> sadd fav:xiaoming eating walking swimming
(integer) 3
127.0.0.1:6379> sadd fav:xiaowei eating sleeping
(integer) 2
127.0.0.1:6379> sdiff fav:xiaoming fav:xiaowei
1) "walking"
2) "swimming"
##如下实现可以将结果保存起到fav:newstore中
127.0.0.1:6379> sdiffstore fav:newstore fav:xiaoming fav:xiaowei
(integer) 2
127.0.0.1:6379> smembers fav:newstore
1) "walking"
2) "swimming"
求交集:如下求出小明和小微的共同爱好;
127.0.0.1:6379> sinterstore fav:they fav:xiaoming fav:xiaowei
(integer) 1
127.0.0.1:6379> smembers fav:they
1) "eating"
求并集:求小明和小微所有喜欢的编程的语言
127.0.0.1:6379> sadd favpro:xiaoming C "C++" java
(integer) 3
127.0.0.1:6379> sadd favpro:xiaowei C Php Android
(integer) 3
127.0.0.1:6379> sunionstore favpro:they favpro:xiaoming favpro:xiaowei
(integer) 5
127.0.0.1:6379> smembers favpro:they
1) "java"
2) "C++"
3) "C"
4) "Android"
5) "Php"
SortedSet是一个特殊的set数据结构,其拥有类似set的特性,只不过每个元素内部都附带了一个分数,存储进去的时候可以不按顺序存储,但是在用户需要排序的时候它会使用正倒序来展示。甚至你可以获取某个元素当前的正/负排名。
添加元素 zadd key score element score element …
127.0.0.1:6379> zadd wzry:score:bang 70.42 luban
(integer) 1
127.0.0.1:6379> zadd wzry:score:bang 65 diaochan
(integer) 1
127.0.0.1:6379> zadd wzry:score:bang 44 guanyu
(integer) 1
127.0.0.1:6379> zcard wzry:score:bang
(integer) 3
按正序/倒序打印所有元素 zrange/zrevrange key startIndex endIndex
127.0.0.1:6379> zrange wzry:score:bang 0 -1
1) "guanyu"
2) "diaochan"
3) "luban"
127.0.0.1:6379> zrevrange wzry:score:bang 0 -1
1) "luban"
2) "diaochan"
3) "guanyu"
查询某个成员的排名 zrank/zrevrank key element
127.0.0.1:6379> zrevrank wzry:score:bang luban
(integer) 0
127.0.0.1:6379> zrank wzry:score:bang luban
(integer) 2
修改元素对应的分数 zincrby key number element
127.0.0.1:6379> zincrby wzry:score:bang 20 diaochan
"85"
127.0.0.1:6379> zrevrange wzry:score:bang 0 -1
1) "diaochan"
2) "luban"
3) "guanyu"
sortedSet案例—王者农药盒子英雄数据排行榜:
public class KGPlayer {
public static void main(String[] args) throws Exception {
Jedis jedis = new Jedis("192.168.254.128", 6379);
String[] heros = new String[] { "鲁班", "亚瑟", "诸葛亮", "周瑜", "廉颇", "甄姬", "孙悟空", "荆轲" };
Random random = new Random();
while (true) {
int currentSed = random.nextInt(heros.length);
String hero = heros[currentSed];
Thread.sleep(1000);
//System.out.println(hero + "出场了...");
jedis.zincrby("wzry:welcome:hero", 1, hero);
}
}
}
public class KGWelcomeHero {
public static void main(String[] args) throws Exception {
Jedis jedis=new Jedis("192.168.254.128",6379);
int i = 0;
while (true) {
Thread.sleep(3000);
i++;
System.out.println("第" + i + "次排行榜");
Set tuples = jedis.zrevrangeWithScores("wzry:welcome:hero", 0, 5);
for (Tuple tuple:tuples) {
System.out.println(tuple.getElement()+" "+tuple.getScore()+"p");
}
System.out.println(" ");
System.out.println(" ");
}
}
}
reids是一个key-value存储系统,为了保证效率,缓存在内存中,但是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,以保证数据的持久化。 所以redis是一个支持持久化的内存数据库。
Redis的持久化策略有2种:
rdb : 默认情况下,是快照rdb的持久化方式,将内存中的数据以快照的方式写入二进制文件中,默认的文件名是dump.rdb .当然这个文件名也是可以在redis.config配置文件中修改 ; 关于rdb的策略,查看redis.conf文档可以发现有这么一行配置:
save 900 1
save 300 10
save 60 10000
官方给出的提示是:如果900秒(15分钟)后,超过1个key对应的数据被修改,则直接持久化到dump文件中;如果超过300秒(5分钟)后,超过10个 key 被修改,那么也会触发持久化机制;如果超过60秒后,超过10000个 key 被修改,那么也会触发持久化机制;
缺点:这种方式并不能完全保证数据持久化,因为是定时保存,所以当redis服务down掉,就会丢失一部分数据,而且数据量大,很容易引起大量的磁盘IO操作,影响系统性能。
aof: 使用aof做持久化,每一个写命令都通过write函数追加到appendonly.aof中.
配置步骤:
1. 打开redis.conf文件
2. 在文件末尾追加 appendonly yes
3. 重启服务 redis-server .redis.confi