数据库讨论(二):Redis在Java项目中使用(从安装到运用)

目录

本来是只写在Java中的使用,有时间就多写点(感谢帅哥同事的建议)。
- redis安装与使用
- 基本数据类型介绍
-JEDIS基本数据操作
- 事务处理
- 主从复制
- 发布订阅


安装和使用

简介

Redis是一个使用C语言写成的,开源key-value数据库。它是一个小而美的数据库,主要用在内存缓存中,读写性能极佳,缓存与简单是其市场定位。
Redis与其他key-value的缓存产品有以下三个特点:
①、支持数据持久化。它可以将内存中的数据保存在磁盘中,重启设备之后也能再次加载使用。
②、Redis不仅仅支持简单的key-value类型数据,同时还提供list,set,zset,hash等数据结构的存储。
③、Redis支持数据备份,即master-slave模式的数据备份

优势

①、性能极高。Redis读取的速度是110000次/s,写入速度是81000次/s。
②、丰富的数据类型。Redis支持二进制案例的Strings,Lists,Hashes,Sets及Ordered Sets数据类型操作。
③、原子性。Redis的所有操作都是原子性的,同时Redis还支持对几个操作全合并后的原子性执行
④、丰富的特性。Redis支持publish/subscribe,通知,key过期等等特性。

安装
1.下载安装包
1) windows安装包下载。由于Redis官网不支持windows环境,请在gitHub下载最新的Redis版本的Redis-x64-3.2.100.msi文件。(同级目录已提供安装包)
2) Linux安装包下载。登录Redis官网下载
启动
启动本地服务有两种选择
一是直接在命令窗口启动
1) 打开cmd命令窗口进入Redis的安装路径
(如果想方便的话可以把路径配入环境变量)
2) 运行 redis-server.exe redis.windows.conf命令(redis version信息出现表示启动成功)
3) 不要关掉启动窗口,另起一个命令窗口。一样需要先进入Redis的安装路径,
运行redis-cli.exe -h 127.0.0.1 -p 6379。连接信息结果出现所示则表示连接成功(输入quit退出)
注:若是配置绑定为本机IP地址或设置密码,则上述命令应把127.0.0.1修改为本机IP地址并加上密码。例:redis-cli.exe -h 182.207.114.27 -p 6379 -a 123456
可视化工具
RedisDesktopManager工具(同级目录已提供安装包)
(自主选择可以不装,也可以选择其它的可视化工具)
redis-desktop-manager-0.8.8.384.exe
安装完成后点击Connection to Redis Service
参数如下
Name:机器别名
Host:服务器IP (本机127.0.0.1)
Port:服务器端口 (默认6379)
Auth:若有密码则输入,若无则留空
点击Test Connection,弹出成功连接即可

数据类型

概括

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

key上的指令

1.exists key 测试指定key是否存在。返回1表示存在,0不存在
2.del key1 key2…keyN 删除指定key。返回删除key的数量,0表示没有删除任何key
3.type key 返回指定key的value类型。返回none表示不存在
4.keys pattern 返回匹配指定模式的所有key(支持* ? 等方式).如右图例子
5.rename oldkey newkey 重命名key,如果newkey存在,将会被覆盖。
6.renamenx oldkey newkey 同上。但是如果newkey存在,则返回失败。
7.dbsize 返回当前数据库的key数量
8.expire key seconds 为key指定过期时间,单位是秒。返回1成功,0表示key不存在
9.ttl key 返回剩余key的过期时间,单位是秒。返回-1表示该key没有设置过期时间,-2表示 key不存在
10.select db-index 选择连接数据库。默认连接数据库0,redis默认数据库是0-15共16个
11.move key db-index 将key从当前数据库移动到指定数据库。
12.flushdb 删除当前数据库中的所有key。此方法不会失效,慎用。
13.flushall 删除所有数据库中的所有key。更加慎用。

String

1.String是redis最基本的类型,而且String类型是二进制安全的。
2.redis的String可以包含任何数据。包括jpg图片或者序列化对象。
3.String最大上限是1G字节

String上的指令

1.set key value 设置key对应的String类型的value。返回1表示成功,0失败
2.setnx key value 同上。但是如果key已经存在,返回0。
3.get key 获取key对应的value值。如果key不存在返回nil。
4.getset key value 设置key的值,并返回key的旧值。如果key不存在则返回nil.
5.mget key1 key2…keyN 一次获取多个key的值。如果对应key不存在返回nil
6.mset key1 value1…keyN valueN 一次设置多个key的值
7.msetnx key1 value1…keyN valueN 同上,但不会覆盖已经存在的key
8.incr key 对key的值做++操作并返回新的值。注意,如果操作值不是int类型则返回错误
9.decr key 对key的值做–操作。
10.incrby key integer 加指定值,key不存在时新增key,默认value为0
11.decrby key integer 减指定值
12.append key value 对指定key的字符串累加
13.substr key start end 返回截取过的key字符串值。注意,这里并不修改key的值

list

redis的list类型其实就是一个每个子元素都是String类型的双向链表。我们可以通过push,pop操作从链表的头部或尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。

list上的指令

1.lpush key string 在key对应的list头部添加字符串元素。返回1表示成功。
2.lpush key string 同上在尾部添加元素。
3.llen key 获取key对应的list长度。如果key不存在返回0,不是list报错。
4.lrange key start end 返回指定区间内的元素。下标从0开始,-1表示最后一个元素.
5.ltrim key start end 截取list,保留指定区间内的元素。
6.lset key index value 设置list中指定下标的元素值。下标不存在报错
7.lrem key count value 从key对应的list中删除count个和value相同的元素。count为0时全部删除
8.lpop key 从list头部删除元素,并返回删除元素
9.rpop key 从list尾部删除元素,并返回删除元素

**Set

1.redis的set是String的无序集合。
2.set元素最大可以包含 2的32次方-1 个元素 。
3.set是通过Hash table实现的,Hash table会随着添加或者删除自动调整大小
4.set除了基本的添加删除操作,还实现了取交集并集功能

Set上的指令

1.sadd key member 添加一个String元素到key对应的set集合中
2.srem key member 从key对应的set中删除指定元素。
3.spop key 删除并返回key对应set中随机一个元素。
4.srandmember key 返回key对应set中随机一个元素,但不删除元素。
5.smove source destination member 从source对应的set中移除member并添加到destination
6.scard key 返回set的元素个数。
7.sismember key member 判读member是否在set中。
8.sinter key1 key2…keyN 返回指定key的交集
9.sinterstore destination key1 key2…keyN 同上。但会将交集放到destination中
10.sunion key1 key2…keyN 返回指定key的并集
11.sunionstore destination key1 key2…keyN同上。但会将交集放到destination中12.sdiff key1 key2…keyN返回指定key的差集
13.sdiffstore destination key1 key2…keyN同上。但会将交集放到destination中
14.smembers key 返回key对应set的所有元素,结果是无序的

Sorted set

和set一样sorted set也是String类型元素的集合,不同的是每个元素都会关联一个double类型的score。sorted set会按照score排序,这样就可以有序的获取集合中的元素。

Sorted set上的指令

1.zadd key score member 添加元素到集合,元素在集合中存在则更新对应score。
2.zrem key member 删除指定元素,1表示成功,如果元素不存在返回0。
3.zincrby key incr member 增加对应member的score值,然后移动元素并保持集合有序。返回更新后的score值。
4.zrank key member 返回指定元素在集合中的排名(下标,非score)。集合中的元素是按score从小到大排序的
5.zrevrank key member 同上,但是集合中的元素是按score从大到小排序。
6.zrange key start end 类似lrange操作从集合中取指定区间的元素。
7.zrevrange key start end 同上,但是返回结果是按照score逆序的
8.zrangebyscore key min max 返回集合中score在给定区间的元素
9.zcount key min max 返回集合中score在给定区间的数量
10.zcard key 返回集合中元素个数
11.zscore key member 返回指定元素对应的score
12.zremrangebyrank key start stop 删除集合中排名在指定区间的元素
13.zremrangebyscore key start stop 删除集合中score在指定区间的元素

Hash

1.redis hash是一个string类型的field和value的映射表。
2.hash特别适合用于存储对象。相较于将每个对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。

Hash上的指令

: 1.hset key field value 设置hash field为指定值,如果key不存在,则先创建
2.hget key field 获取指定hash field的value。
3.hmget key field1 field2…fieldN 获取全部指定hash field的value
4.hmset key field1 value1…fieldN valueN 同时设置hash的多个field
5.hincrby key field integer 将指定的hash field加上定值。
6.hexists key field 测试指定的field是否存在。
7.hdel key field 删除指定的hash field。
8.hlen key 返回指定hash的field数量。
9.hkeys key 返回hash的所有field。
10.hvals key 返回hash的所有value
11.hgetall key 返回hash的所有field和value。

JEDIS基本数据操作

准备

Redis做一些直观型的数据(我用的工具)
这里写图片描述

连接

回归基本的数据库访问步骤
* 1、加载驱动: JadDecompiler JadclipseSourceMapper JadclipseClassFileEditor
* 2、建立连接 : Connection : URL + port (+password)
* 3、生成语句对象: new Jedis() 或者 JedisPool.getResource()
* 4、执行并返回: jedis.set(),jedis.add(),jedis.del()…
* 5、关闭流: jedis.close() 或者 JedisPool.returnResource()

package com.redis.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * jedis-2.9.0.jar(redis官方提供的Java架包)
 * commons-pool2-2.4.2.jar,连接池架包(由于我们项目使用到了连接池,所以使用此架包)
 * @author xxg20
 *
 */
public class RedisClient {
    // 本地服务器IP
    //private static String ADDR = "182.207.114.27";
    private static String ADDR ="127.0.0.1";
    // Redis端口号
    private static Integer PORT = 6379;
    // 访问密码
    //private static String AUTH = "123456";
    // 可用最大连接数
    private static Integer MAX_ACTIVE = 100;
    // 最大空闲连接数
    private static Integer MAX_IDLE = 10;
    // 最长等待时间
    private static Integer MAX_WAIT = 10000;
    // 超时时间
    private static Integer TIMEOUT = 30000;
    // 在获取Redis连接时,自动检测连接是否有效
    private static Boolean TEST_ON_BORROW = true;

    // 连接池
    private static JedisPool jedisPool;

    static{
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            //jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取Redis资源
     * @return
     */
    public synchronized static Jedis getJedis(){
        try {
            if( jedisPool != null ){
                Jedis resource = jedisPool.getResource();
                return resource;
            }else{
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 释放Redis资源
     */
    @SuppressWarnings("deprecation")
    public synchronized static void returnResource(Jedis jedis){
        if( jedisPool != null ){
            jedisPool.returnResource(jedis);
        }
    }

}
使用

基本数据类型的使用
* 1、String类型基本操作:
* 2、Hash类型基本操作:
* 3、List类型基本操作: 略
* 4、set类型基本操作: 略
* 5、Sorted set类型基本操作(有序集合): 略

/**
     * String类型基本操作
     */
    public static void testStringOpr(){
        Jedis jedis = RedisClient.getJedis();
        try {
            // 增加/修改
            jedis.set("testOne", "value");
            System.out.println("save String testOne:" + jedis.get("testOne"));
            // 拼接
            jedis.append("testOne", "1");
            System.out.println("append String testOne:" + jedis.get("testOne"));
            // 查询
            String testOne = jedis.get("testOne");
            System.out.println("find String testOne:" + testOne);
            // 删除
            jedis.del("testOne");
            System.out.println("delete String testOne:" + jedis.get("testOne"));
            // 判断key是否存在
            boolean isNotExist = jedis.exists("testOne");
            System.out.println("isNotExist String testOne:" + isNotExist);

            // 若已知所存数据为int型,则可调用下面方法进行加减操作
            jedis.set("testTwo", "0");
            System.out.println("old testTwo:" + jedis.get("testTwo"));
            jedis.incr("testTwo");
            System.out.println("new testTwo:" + jedis.get("testTwo"));
            jedis.del("testTwo");

            jedis.set("testThree", "value2");
            // 增加/更新 过期时间(以秒为单位)
            jedis.expire("testThree", 30);
            // 查看剩余生存时间
            System.out.println("testThree剩余生命周期:"+jedis.ttl("testThree"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            RedisClient.returnResource(jedis);
        }
    }

再来一个

/**
     * Hash类型基本操作
     */
    public static void testHashOpr(){
        Jedis jedis = RedisClient.getJedis();
        try {
            // 整个增加Map(此操作会直接覆盖原有key对应的Map)
            Map<String, String> redisMap = new HashMap<String, String>();
            redisMap.put("name","zhangsan");
            redisMap.put("age","28");
            redisMap.put("sex","男");
            jedis.hmset("redisMap",redisMap);
            System.out.println("add Hash redisMap:" + jedis.hgetAll("redisMap"));
            // 判断Hash的field是否存在
            boolean isNotExist = jedis.hexists("redisMap", "address");
            System.out.println("isNotExist String test1:" + isNotExist);
            // 获取Hash的长度
            long hlen = jedis.hlen("redisMap");
            System.out.println("get Hash len:" + hlen);
            // 增加/修改
            jedis.hset("redisMap", "address", "广东深圳小刚家");
            System.out.println("save Hash redisMap:" + jedis.hgetAll("redisMap"));
            // 查询
            String name = jedis.hget("redisMap", "name");
            System.out.println("find Hash redisMap name:" + name);
            // 对int类型进行增减操作
            jedis.hincrBy("redisMap", "age", 2);
            System.out.println("intAdd Hash redisMap age:" + jedis.hget("redisMap", "age"));
            // 删除
            jedis.hdel("redisMap", "address");
            System.out.println("delete Hash redisMap address:" + jedis.hget("redisMap", "address"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            RedisClient.returnResource(jedis);
        }
    }

事务处理

介绍

事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作。事务必须满足ACID原则(原子性、一致性、隔离性和持久性)。
事务可能包括1~N条命令,当这些命令被作为事务处理时,将会顺序执行这些命令直到完成,并返回结果,如果中途有命令失败,则会回滚所有操作。

Redis中的事务

这里写图片描述

实现

/**
     * 事务控制
     */
    public static void testTransaction(){
        Jedis jedis = RedisClient.getJedis();
        // 开启事务
        Transaction tx = jedis.multi();
        // 异常标志
        boolean errFlag = false;
        try {
            // 设置 redis 字符串数据
            tx.set("test1", "value1");
//          int i = 6/0;// java异常
            tx.set("test2", "value2");
            // (在Jedis内部方法异常时,不会直接报错。而是同样放在队列中,在事务提交时,不提交该条错误信息,其他正常队列正常提交)
            tx.incr("test1");// 对字符串进行累加操作:Jedis内部方法异常 
            System.out.println("正常提交事务");
            List<Object> list = tx.exec();// 提交事务
            for (int i = 0; i < list.size(); i++) {
                System.out.println("find List list(" + i + "):" + list.get(i));
            }
        } catch (Exception e) {
            errFlag = true;
//          tx.discard();// 发生异常时取消事务(注意:如果在取消)
            e.printStackTrace();
        } finally{
            if(errFlag){
                System.out.println("发生异常时提交事务");
                tx.exec();// 提交事务
            }
            RedisClient.returnResource(jedis);
        }
    }

主从复制

概况

1、redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

2、通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

过程

1:当一个从数据库启动时,会向主数据库发送sync命令,
2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。支持断点续传。

sentinel功能

redis的sentinel系统用于管理多个redis服务器,该系统主要执行三个任务:监控、提醒、自动故障转移。
1、监控(Monitoring): Redis Sentinel实时监控主服务器和从服务器运行状态,并且实现自动切换。
2、提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Redis Sentinel 可以向系统管理员发送通知, 也可以通过 API 向其他程序发送通知。
3、自动故障转移(Automatic failover): 当一个主服务器不能正常工作时,Redis Sentinel 可以将一个从服务器升级为主服务器, 并对其他从服务器进行配置,让它们使用新的主服务器。当应用程序连接Redis 服务器时, Redis Sentinel会告之新的主服务器地址和端口。

集群中的主从复制

redis集群是一个无中心的分布式Redis存储架构,可以在多个节点之间进行数据共享,解决了Redis高可用、可扩展等问题。redis集群提供了以下两个好处
1、将数据自动切分(split)到多个节点
2、当集群中的某一个节点故障时,redis还可以继续处理客户端的请求。
一个 Redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个数据都属于这16384个哈希槽中的一个。集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽。集群中的每一个节点负责处理一部分哈希槽。

集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点,继续工作。这样集群就不会因为一个主节点的下线而无法正常工作。

发布订阅

说明
发布订阅(PUB/SUB)是一种消息通信模式。订阅者可以通过subscribe和psubscribe命令向redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的消息时,订阅该消息类型的全部client都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个channel,也可以向多个channel发送消息。
订阅发布常用命令如下:
1.publish 发布消息
2.subscribe 订阅消息(单个)
3.unsubscribe 取消订阅
4.psubscribe 订阅匹配消息
5.punsubscribe 取消订阅匹配消息
非管道处理
Redis是一个CS模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完请求命令后会将结果通过响应报文返回给client。基本的通信过程如下:
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4

上面四个命令需要8个tcp报文才能完成。由于通信会有网络延迟,加入从client和server之间的包传输时间需要0.125秒。那么上面的四个命令8个报文至少需要1秒才能完成。

管道(pipeline)处理
利用管道的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端在处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。通信过程如下:
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4

相当于管道处理将与Redis服务器之间的沟通结构由
request-> response、request-> response、request-> response…
改为
request、、request、request->response、response、response…
可在数据量较大时减轻服务器压力,增加数据访问效率

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章