Redis介绍与环境准备

本篇博客内容以及相关说明:

(1) redis是一中非关系型数据库:在实际项目中,redis就是一个数据库,只是这个数据库中的数据是保存在内存中的;

(2) Jedis引入:在程序中,实际使用MySQL数据库的时候,我们不是直接操作数据库的,而是通过java语言(其实就是JDBC啦)来操作数据库;同理,我们在Java程序中操作redis数据库时,绝大部分情况下,我们也不是直接在redis提供的客户端工具redis-cli中操作redis数据库,而是通过Java程序(其实就是Jedis等Redis的Java客户端啦)的方式,来操作redis;

(3) 需要一台独立的【Redis服务器】:在实际中,Redis一般是独立部署在一台服务器上的;所以,为了能让【独立部署在一台服务器上的Redis】能够对外提供服务,我们需要配置一下Redis;(配置内容主要是:【protected-mode】和【bind】)

(4) 然后,本篇博客介绍了Redis使用较广的Java客户端:Jedis;


一:配置Redis,以使得【独立部署在一台服务器上的Redis】能够对外提供服务

0.情况说明:为什么要配置redis

● 在绝大部分情况下,redis服务器除了提供redis服务,是不做任何其他事情的;即,在实际中,我们是把Redis单独部署在一台服务器上的;

● 一般我们Java程序是运行在另一台主机上的。如果是部署的时候,Java程序就运行在【Centos-WEB】这台web服务器上;如果是在开发的时候,Java程序就运行在自己的Windows系统上(PS:目前自己是在Windows系统上开发的);

● 那么,在开发过程中,就需要【我们开发程序所在的,Windows主机】远程连接到【Centos-Redis,这个Redis服务器,主机上】,这背后是需要网络传输的;

而在实际开发中,如果涉及多台电脑进行互相通信时,在redis中我们就需要配置一下;因为,在默认情况下,redis出于安全的考虑,redis只允许在本地就行访问,如果要远程访问,则需要配置redis.conf中的两个参数: 【protected-mode】和【bind】


1.配置redis.conf中的两个参数:【protected-mode】;【bind】

首先,使用【./src/redis-cli -a 12345 -p 6380 shutdown】先关闭redis服务;

然后,在redis目录下,使用【vim redis.conf】去配置redis;

在vim的普通模式下,使用【/protected-mode】快速搜索定位:

向上翻几行,找到另一个需要配置的地方;

PS:在开发的时候,如上设置是OK的;但是到线上部署的时候,需要设置指定ip才能访问;


2.为了让外部可以访问【Redis服务器中的redis服务】,redis服务器需要在防火墙中放开redis的端口

设置完了之后,启动redis服务;

但是,还不够,因为【Centos-Redis这台虚拟机,redis的6380端口还没有放开】;

防火墙部分,可以参考【【firewall-cmd】防火墙设置】

使用【 firewall-cmd --zone=public --permanent --add-port=6380/tcp】 :放行6380端口:之后【firewall-cmd --reload】:重载防火墙规则,使更改生效 ;


3.查看【redis服务器】的ip地址,为其他主机连接做准备

然后,需要通过【ifconfig】看下redis服务器的ip地址;一会,我们连接这台redis服务器的时候,需要用到redis服务器的ip地址;


二:Jedis背景介绍

访问redis官网【https://redis.io/

Jedis操作【String类型】

说明:

(1) 本篇博客通过一个实际的案例,来演示Jedis的基本使用;主要是Jedis操作【String类型】的案例;

(2) 这儿只是演示了部分方法,即使这些方法记不住也没事,只要有个大概印象,在解决具体业务的时候,如果需要某种方法,又忘记时,快速去查就行了;

(3) 【jedis封装的方法】和【对应的,底层redis命令】,名字基本都长一样;


一:创建一Maven项目,并引入Jedis

使用IDEA创建Maven项目的过程,可以参考【[OA系统前期准备:Maven Web工程】

Maven项目就创建好了;

然后在pom.xml文件中,添加Jedis的依赖:

<dependencies>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>2.9.0</version>
            </dependency>
        </dependencies>

一会过后,


二:Jedis使用初体验:主要是【操作String字符串类型】的演示

JedisTestor类:

package com.imooc.jedis;
import redis.clients.jedis.Jedis;
 
import java.util.List;
 
public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.***.***.**4",6380);//获取Jedis连接
        try{
            jedis.auth("12345");//输入密码,获取授权
            jedis.select(2);//选择2号数据库
            System.out.println("Redis连接成功。");
            //操作字符串
            jedis.set("sn", "379739-897");//向2号数据库,添加一条字符串数据
            System.out.println(jedis.get("sn"));//取数据
            jedis.mset(new String[]{"name", "张三", "age", "20"});//向2号数据库中,一次性添加多条字符串数据
            List<String list = jedis.mget(new String[]{"sn", "name"});//一次性取多条数据
            System.out.println(list);
            Long num = jedis.incr("age");//对age数据自增;返回值是自增后的值
            System.out.println(num);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            jedis.close();
        }
    }
}

说明:

(0) 其实还好,知道Redis的那些命令后,基本就能知道Jedis的各种方法的含义;毕竟Jedis中的方法都是对Redis命令的封装; 然后,即使这些方法记不住,只要有个大概印象,在解决具体业务的时候,需要某种方法时,去查就行了;


(1) 因为我修改了redis的端口号,所以这儿在获取redis对象的时候,使用的是两个参数的那个构造方法;

emm,其实即使redis采用的是默认的6379端口;也还是建议使用两个参数的那个构造方法;


(2) jedis.auth(“12345”): 作用是,在当前Jedis连接中,输入密码,获得授权;

如果没有这一步的话,其会报异常;


(3) select方法,set方法,get方法,mset方法,mget方法,incr方法


(4) 还是,一种司空见惯的编程习惯,获取Jedis连接后,后续的操作放在了try块中;然后为了保证Jedis一定能够被关闭,关闭Jedis连接的操作放在了finally块中;


(5)

Jedis操作【Hash】

说明:

(1) 本篇博客主要演示: Jedis操作【Hash】;

(2) 这儿只是演示了部分方法,即使这些方法记不住也没事,只要有个大概印象,在解决具体业务的时候,如果需要某种方法,又忘记时,快速去查就行了;

(3) 【jedis封装的方法】和【对应的,底层redis命令】,名字基本都长一样;

本篇博客的主要内容,是演示Jedis操作【Hash】

JedisHash:

package com.imooc.jedis;
import redis.clients.jedis.Jedis;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class JedisHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.***.***.**4",6380);//获取Jedis连接
        try{
            jedis.auth("12345");//输入密码,获取授权
            jedis.select(3);
            jedis.hset("student:3312","name","zhangsan");//新建一个hash数据,并只向value中添加一条【属性和属性值】
            String name = jedis.hget("student:3312", "name");//获取hash数据中的,一条【属性和属性值】
            System.out.println(name);
 
            Map stuMap = new HashMap();//由于redis的要求,这个Map中的key和value都必须是字符串
            stuMap.put("name", "张三");
            stuMap.put("age", "25");//看到没,即使是数字,也必须是字符串格式;这是Redis的要求
            jedis.hmset("students:3313", stuMap);
            Map<String, String map = jedis.hgetAll("students:3313");
            System.out.println(map);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            jedis.close();
        }
    }
}

说明:

(1) hset方法,hget方法,hmset方法,hgetAll方法

(2) 然后,自然可以在redis服务器中看到执行上面Java程序的结果;

Jedis操作【List】

说明:

(1) 本篇博客主要演示: Jedis操作【List】;

(2) 这儿只是演示了部分方法,即使这些方法记不住也没事,只要有个大概印象,在解决具体业务的时候,如果需要某种方法,又忘记时,快速去查就行了;

(3) 【jedis封装的方法】和【对应的,底层redis命令】,名字基本都长一样;

本篇博客的主要内容,是演示Jedis操作【List】

JedisList:

package com.imooc.jedis;
import redis.clients.jedis.Jedis;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class JedisList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.***.***.**4",6380);//获取Jedis连接
        try{
            jedis.auth("12345");//输入密码,获取授权
            jedis.select(4);
            jedis.del("letter");
            jedis.rpush("letter", new String[]{"a", "b", "c"});
            jedis.lpush("letter", new String[]{"d", "e", "f"});
            List<String letterList1 = jedis.lrange("letter", 0, -1);
            System.out.println(letterList1);
 
            jedis.lpop("letter");
            jedis.rpop("letter");
            List<String letterList2 = jedis.lrange("letter", 0, -1);
            System.out.println(letterList2);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            jedis.close();
        }
    }
}

说明:

(1) rpush方法,lpush方法,lpop方法,rpop方法

(2) 然后,自然可以在redis服务器中看到执行上面Java程序的结果;

redis存储【对象JSON序列化后的字符串】

说明:

(1) 演示【如何在redis数据库中,存储对象】

● MySQL数据库,默认会把数据存在硬盘上;由于硬盘的读写速度较慢,随着数据量的增大,并发数的增加,导致MySQL处理速度较慢,出现延迟。

● Redis利用内存来存储数据,内存读写速度很快;所以,在项目中,比如【商品数据】、【人员信息】、【设备信息】、【资产状况】等不太经常变动的数据,通常使用Redis来存储;

本篇博客就是演示【如何在redis数据库中,存储对象】 ,模拟在实际开发中,redis的使用;

(2) 本篇博客中,对象序列化的方式,采用的是JSON序列化;

(3)本篇博客的例子多少还是有点玩具性质;具体redis在实际中的使用方式,还是需要在实际项目了解;

Goods类:

package com.imooc.jedis;
public class Goods {
    private Integer goodsId;
    private String goodsName;
    private String description;
    private Float price;
 
    public Goods() {
    }
 
    public Goods(Integer goodsId, String goodsName, String description, Float price) {
        this.goodsId = goodsId;
        this.goodsName = goodsName;
        this.description = description;
        this.price = price;
    }
 
    public Integer getGoodsId() {
        return goodsId;
    }
 
    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }
 
    public String getGoodsName() {
        return goodsName;
    }
 
    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    public Float getPrice() {
        return price;
    }
 
    public void setPrice(Float price) {
        this.price = price;
    }
}

说明:

(1) Goods类没什么好说的,就是一普通的商品信息类;


CacheSample类:

package com.imooc.jedis;
 
import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;
 
import java.util.ArrayList;
import java.util.List;
 
public class CacheSample {
    public CacheSample() {
        List<Goods goodsList = new ArrayList<Goods();
        goodsList.add(new Goods(1001,"商品1","描述1",101f));
        goodsList.add(new Goods(1002,"商品2","描述2",102f));
        goodsList.add(new Goods(1003,"商品3","描述3",103f));
        //在实际项目中,这些数据应该都是从数据库中查询得来的;
        Jedis jedis = new Jedis("1**.***.***.**4", 6380);
        try {
            jedis.auth("12345");//输入密码,获取授权
            jedis.select(4);
            for (Goods goods : goodsList) {
                String goodsJson = JSON.toJSONString(goods);//使用Json的方式,将对象序列化
                String key = "goods:" + goods.getGoodsId();
                jedis.set(key,goodsJson);//将goods对象序列化后的【JSON字符串】存放到Redis数据库中;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            jedis.close();
        }
    }
 
    public static void main(String[] args) {
        new CacheSample();
    }
}

说明:

(1) 关于对象序列化的内容,可以参考

● Java序列化:【对象序列化】

● JSON序列化:【对象集合序列化成JSON(数组)】

● 【附加:【Java序列化(ObjectOutputStream序列化)】和【JSON序列化】;序列化浅析;

这儿,我们选用JSON作为序列化的方式;

(2) Java中JSON序列化需要FashJson;关于FastJson可以做参考

这儿我们通过Maven的方式,引入FastJson:

(3) 然后,在redis数据库中,可以看刚才存入的数据;

读取redis中存储的【对象JSON序列化后的字符串】

说明:

(1) 本篇博客就是演示【如何从redis数据库中,读取“被序列化的对象字符串”,然后将其反序列化为对象】,模拟在实际开发中,redis的使用;

(2) 本篇博客中,对象序列化的方式,采用的是JSON序列化;

(3) 本篇博客的例子多少还是有点玩具性质;具体redis在实际中的使用方式,还是需要在实际项目了解;

CacheSample1类:

package com.imooc.jedis;
import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
 
public class CacheSample1 {
 
    public static void main(String[] args) {
        System.out.println("请输入要查询的商品编号:");
        String goodsId = new Scanner(System.in).next();
        Jedis jedis = new Jedis("1**.***.***.**4", 6380);
        try {
            jedis.auth("12345");//输入密码,获取授权
            jedis.select(4);
            String key = "goods:" + goodsId;
            if (jedis.exists(key)) {//判断,在redis数据库中,是否存在对应的key
                String goodsJson = jedis.get(key);
                System.out.println(goodsJson);
                Goods goods = JSON.parseObject(goodsJson, Goods.class);//将获取的结果,反序列化为Good对象
                System.out.println(goods);
            } else {
                System.out.println("输入的商品编号不存在。");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            jedis.close();
        }
    }
}

说明

(1) 运行结果


Summary:

(1) 如上面案例中,我们把商品信息存在了redis数据库中(内存中啦),这个读取速度是非常快的;

(2) 因为,在项目中,把数据存放在redis数据库中的目的是【缓存】,那么就要求这些数据具备两个特性:

● 不能太大,比如要把一个300G的东西存到redis中,那么就要占用300G的内存;这就有点扯了,什么样的服务器也无法承载;

● 这些数据,应该是【相对稳定的】、【更新频率比较低的数据】;比如商品信息数据、学校人员信息数据、公司办公设备数据等不太变动的数据;但是,如门票信息数据、航天数据等需要实时变更的数据,就不适合使用redis缓存;

(3) 目前来看,对于一个大程序来说,redis可以提升其中的某个部分的速度;这个【某个部分】就是【获取那些相对稳定的,如商品信息】的速度;(目前猜测,这些商品信息数据,应该是提前被缓存到redis数据库的)


(4)本[Redis]中的内容,仅仅是redis最基础的东西,仅仅是一个入门,仅仅是一个入门!!!!!!

Redis入门常见问题



附加:序列化浅析

本篇博客的目的: 在【利用redis存储对象】中,遇到了对象序列化的内容,这儿特地的说明一下;

序列化可以这样理解:(起码目前是这样)

● 有【发送方】和【接收方】,两方之间发送的是对象;

● 序列化可以看成是一种【两方之间约定好的,对象的“编码方式”】;被序列化后的对象,两方都可以【按照序列化的规则】将对象还原;

● 具体的“编码方式”,有【Java中的需要实现Serializable接口的那个序列化】,【JSON序列化】等;

● 介绍的Redis通过JSON序列化的过程中;【发送方】和【接收方】都是Java程序,redis数据库只是起了一个”暂存数据“的作用;如下图所示;

《深入理解计算机系统》这本经典教材,以后如有必要最好是瞅一瞅;因为很多在应用层使用的技术,了解其底层原理还是比较重要的。


以下内容完全转载自【Java序列化闲聊:序列化和Json】 ;该文的作者是【Ciruy B.Heimerdinger

PS:该博主还有很多其它的优秀文章,可以参考!

前言

其实我挺纠结的,在纠结到底要不要写这个模块的博客,因为序列化这个模块说简单,按照一些人的说法,其实也就是调两个函数的事情,说困难,铺开就让人感觉范围很广。

序列化

什么是序列化,我们在编程的时候,看到的花花绿绿的对象们,肯定在计算机里面通过一种方式存储;在网络上进行传输的时候,也肯定需要规定一种方式来让传输的双方能互相理解,不然我传输过去的数据即使你获取到了,也毫无意义。这种方式就是序列化,然后将序列化后的数据再通过规则转换回我们所需要的内容的方式就是反序列化。

《深入理解计算机系统》前几章就有这么一段话,翻译过来就是数据本身其实并没有意义,只有赋予其存在的意义也就是上下文,数据本身才有了意义。

本文就主要讲讲Android相关的序列化操作,Java中就有专门用于标记可序列化对象的接口Serializable,只有类被此接口标记了,Java才能对其执行序列化从而存储或者进行网络传输。

其实有很多的序列化方法,像Java本身的序列化方式(序列化后的结果可读性不是那么好),然后还有json,xml,还有protobuf等等。从事android开发的我还是比较熟悉json,所以本文还是打算围绕json来进行一些简单的讨论。

还是基于我之前所说的方法论,我们在探究问题的时候,必须要拨开问题表层的迷雾,抓住重点。那么Java中序列化的重点是什么?我想有以下两点:

  • 如何保存当前对象的状态?
  • 如何确保反序列化后对象的状态和序列化前的状态一样?

如何保存当前对象的状态

在Java中,所有对象不停追溯其父类,到最后都一定会追溯到Object类。父类也往往会永远一些成员变量,这些成员变量子类也能获取的到,所以,我们在保存当前对象的状态时,必须要同时保存父类,父类的父类等等等的状态。然后对于类的成员变量,我们肯定也需要去保存他的状态,如果成员变量并不是元数据,那么不好意思,我们也需要去遍历其祖宗十八代从而确保我们完全保存了当前对象的状态。

适当归纳一下,序列化算法一般会按照以下步骤做如下事情: 1. 将对象实例相关的类元数据输出 2. 遍历对象实例中的非元数据成员变量,遇到元数据按照步骤一的方式直接输出,按照此逻辑递归遍历直至遍历完成全部变量引用。

如何确保反序列化后对象的状态和序列化前的状态一样

的确,常规状态下,反序列化的实例和序列化前的实例其实是两个实例了。就好比一对双胞胎长得一模一样,做事方式,行为完全一样,但是你能说他们是同一个人吗?不能吧,更何况如果是通过网络传输后,在不同计算机上的序列化前后的实例,更不可能是同一个对象了。

但是实际上,对于这种情况,其实大多数情况下,我们睁一只眼闭一只眼,既然行为都完全一样,很多时候也就不用在意了。但是如果我们偏偏有强迫症呢?那么java也提供了Object readResolve(Object)方法,通过这个方法,我们能对反序列化的结果进行修正,传入的参数是之前所说的双胞胎实例,返回的是最后反序列化的结果。特别是在单例模式中,如果你拥有序列化前的实例,直接返回序列化前的实例,这能确保单例对象的唯一性。

Json解析

接下来讲讲Json的解析吧,Json的基础我就不专门讲了,但是对于Json解析的原理,我还是想说两句的。Json也是一种序列化的方式,所以Json解析也就是一种反序列化的方式。

据我所知,当下对于Json的解析分为两种,对于数据量较小的Json数据和对于数据量很大的Json数据。

数据量较小的Json数据

如果是数据量较小的Json数据,我们只需要将Json串一口气读取到内存里,直接进行解析就行了,小Json串用哪种解析方式都差不多。将Json转换成对象,然后想拿哪个数据,直接拿就行了。

数据量较大的Json数据

对于数据量较大的Json数据,就比较值得玩味了,就需要什么事件驱动型的解析方式。我其实很反感这些故弄玄虚的人,明明很简单的事情非要弄得很复杂,可能他们认为这样显得自己的水平很高。如果是数据量较大的Json数据,我们该怎么办呢?

我想问问读者?如果打游戏对线时,你的技能没法秒掉对手怎么办?很简单,把对手的血线消耗到斩杀线不就行了吗?

那么怎么解析数据量大的Json数据?一样的道理,拆分Json直到每个片段能直接进行解析就行了。我们可以看到”,{,[,就表示着一个拥有的关系。为什么这么说呢?因为我们可以看出这三个标记前一定跟的是”key”:(最外层的{除外),这个的意思就是,当前的实例中有一个名字为key的成员变量,然后成员变量的结构就囊括在””,{},[]里,可能是元数据,对象,或者数组。这样一来处理就方便很多了,最直观的方法就是直接按照当前key相对于最外层的{}的拥有关系直接将Json文件拆分成多个小文件,直接按照从外到内的拥有关系搭建文件夹结构,然后文件夹中的文件就一定是能直接进行解析的Json数据。通过这样的方法,我们希望在Json数据中获取到什么数据,都能获取到了。

特别是当我们已经知道Json的数据结构时,我们都不用像之前所说的一层一层解析,从而构建一套基于拥有关系的文件夹系统,我们直接遍历一遍Json数据流,动态统计[和{的数量,如果遇到],}就将相应对应的计数减一,当发现和我们知道的数据结构中的两个token数量一致并且”key”值相同。那么恭喜你,你找到了你想要的数据。