Redis数据库

1. redis简介

​ Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

​ Redis 是 NoSQL数据库(泛指非关系型的数据库)的一种;

NoSQL 的特点:

1、 它不支持SQL语法

2、 存储结构跟传统关系型数据库中的那种关系表完全不同,nosql中存储的数据都是KV形式

3、 NoSQL的世界中没有一种通用的语言,每种nosql数据库都有自己的api和语法,以及擅长的业务场景

4、 NoSQL中的产品种类相当多:

  • Mongodb 文档型nosql数据库,擅长做CMS系统(内容管理系统)
  • Redis 内存数据库,数据结构服务器,号称瑞士军刀(精巧),只要你有足够的想象力,它可以还给你无限惊喜
  • Hbase hadoop生态系统中原生的一种nosql数据库,重量级的分布式nosql数据库,用于海量数据的场景
  • Cassandra hadoop生态系统中原生的一种分布式nosql数据库,后起之秀。

5、 NoSQL和SQL数据库的比较:

  • 适用场景不同:sql数据库适合用于关系特别复杂的数据查询场景,nosql反之
  • “事务”特性的支持:sql对事务的支持非常完善,而nosql基本不支持事务

Redis是一个高性能的kv对缓存和内存数据库.其存储结构就是key-value,形式如下:

Redis数据库_第1张图片

  1. 可以使用redis来做缓存,因为所有数据是放在内存中的,不但支持丰富的数据结构 ,且访问速度快 。
  2. redis有数据持久化机制(持久化机制有两种:1、定期将内存数据dump到磁盘;2、aof(append only file)持久化机制 .
  3. redis支持集群模式.
  4. 在某些特定应用场景下能够替代传统数据库。比如社交类的应用,购物车。

2. redis安装&启动服务

  1. 到官网下载最新stable版

  2. 解压源码并进入目录 tar -zxvf redis-2.8.19.tar.gz -C ./redis-src/

  3. make

  4. 如果报错提示缺少gcc,则安装gcc : yum install -y gcc

    如果报错提示:Newer version of jemalloc required 则在make时加参数:make MALLOC=libc

  5. 安装redis,指定安装目录,如 /usr/local/redis

    make PREFIX=/usr/local/redis install

  6. 拷贝一份配置文件到安装目录下

    切换到源码目录,里面有一份配置文件 redis.conf,然后将其拷贝到安装路径下

    cp redis.conf /usr/local/redis/

  7. 启动redis

    cd /usr/local/redis

    bin/redis-server redis.conf

    Redis数据库_第2张图片

    也可以修改配置文件,让redis服务在后台启动:

    vi  redis.conf
    
    daemonize yes  ##修改这个配置
    
    bin/redis-server redis.conf  ##重新启动

3. 连接客户端

以下是客户端的登录的和退出操作:

[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

4. 字符串操作

上面提到,用户可以对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"
  • set key value 往数据库里面添加一个键值对。
  • mset key value key value … 往数据库里添加多个键值对。
  • keys * 查询数据库里面所有的键值对
  • setex key time value 存储一个数据并制定有效期
  • get key 取出某个键对应的值。

上面的代码是通过客户端命令行的方式展示的,接下来看看代码的实现,这需要导入一些依赖:

链接: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);
    }

}

5. 对象操作

除了可以对字符串进行存储,我们也可以将对象直接转换成二进制进行存储,其代码存储的方式有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);
    }

}

6. 队列操作

Redis数据库_第3张图片

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应用

Redis数据库_第4张图片

这里涉及到两个对象,生产者和消费者,生产者主动将任务添加到执行任务的队列中,消费者则取出并处理任务。

生产者代码:

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);
            }
        }

    }

}

7. hash数据结构

Redis数据库_第5张图片

  1. 往数据库里面插入一条hash数据

    redis > HSET key field value

  2. 查看对应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" 
  3. 除了创建元素,还可以对元素进行操作:

    • hkeys 找出key对应hash表中所有的key
    • hvals 找出key对应hash表中所有的value
    • hget 获取某个key对应hash表中 ,field对应的值
    • hincrby 获取某个key对应hash表中 ,并对field对应的值进行加减
    • hdel 找到某个key对应hash表,并对field所在的键值对进行删除
    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

8. set操作

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"

9. sortedSet操作

SortedSet是一个特殊的set数据结构,其拥有类似set的特性,只不过每个元素内部都附带了一个分数,存储进去的时候可以不按顺序存储,但是在用户需要排序的时候它会使用正倒序来展示。甚至你可以获取某个元素当前的正/负排名。

Redis数据库_第6张图片

  1. 添加元素 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
  2. 按正序/倒序打印所有元素 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"
  3. 查询某个成员的排名 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
  4. 修改元素对应的分数 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案例—王者农药盒子英雄数据排行榜:

  • ​ 在redis中需要一个榜单所对应的sortedset数据
  • ​ 玩家每选择一个英雄打一场游戏,就对sortedset数据的相应的英雄分数+1
  • ​ Lol盒子上查看榜单时,就调用zrange来看榜单中的排序结果
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(" ");
        }
    }

}

10. redis持久化

reids是一个key-value存储系统,为了保证效率,缓存在内存中,但是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,以保证数据的持久化。 所以redis是一个支持持久化的内存数据库。

Redis的持久化策略有2种:

  • rdb:快照形式是直接把内存中的数据保存到一个dump文件中,定时保存,保存策略
  • aof:把所有的对redis的服务器进行修改的命令都存到一个文件里,命令的集合

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

你可能感兴趣的:(大数据应用,大数据)