【Redis :非关系型数据库】

【Redis :非关系型数据库】

一、 概念

1. Redis 介绍

Redis 是一款高性能的 NOSQL 系列的非关系型数据库

  • 关系型数据库:MySQL、Oracle…
    • 数据存储在表中
      1. 数据之间有关联关系
      2. 数据存储在硬盘的文件上
  • 非关系型数据库(NOSQL):Redis、HBase
    • 存储 key:value(例如:name:张三 age:23
      1. 数据之间没有关联关系
      2. 数据存储在内存中

应用场景:对于客户端、服务器、数据库三个方面
客户端向服务器端发出请求:查询一些不太经常发生变化的数据,然后服务器端进行数据库查询,操作关系型数据库非常耗时,(例如:user 表中的几亿条数据)
使用缓存思想(在内存区域开辟一块缓存区域)解决耗时问题:

  1. 从缓存中获取数据
    • 有数据
      • 直接返回
    • 没有数据
      1. 从数据库查询
      2. 将数据放入缓存(使用 Redis 做缓存)
      3. 返回数据
  • 什么是 Redis
    • Redis 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s,且 Redis 通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止 Redis 支持的键值数据类型如下:
      1. 字符串类型 String
      2. 哈希类型 Hash
      3. 列表类型 List
      4. 集合类型 Set
      5. 有序集合类型 sortedset
  • Redis 的应用场景
    • 缓存(数据查询、短连接、新闻内容、商品内容等等)
    • 聊天室的在线好友列表
    • 任务队列。(秒杀、 抢购、12306等等)
    • 应用排行榜
    • 网站访问统计
    • 数据过期处理(可以精确到毫秒)
    • 分布式集群架构中的 session 分离

2. NOSQL 介绍

NoSQL(NoSQL = Not only SQL),意即 “不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
随着互联网 Web2.0 网站的兴起,传统的关系数据库在应付 Web2.0 网站,特别是超大规模和高并发的 SNS 类型的 Web2.0 纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NOSQL 数据库的产就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

  • NOSQL 和关系型数据库比较:
    • 优点;
      1. 成本:NOSQL 数据库简单易部署,基本都是开源软件,不需要像使用 Oracle 那样花费大量成本购买使用,相比关系型数据库价格便宜。
      2. 查询速度:NOSQL 数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及 NOSQL 数据库。
      3. 存储数据的格式:NOSQL 的存储格式是 key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
      4. 扩展性:关系型数据库有类似 join 这样的多表查询机制的限制导致扩展很艰难。
    • 缺点:
      1. 维护的工具和资料有限,因为 NOSQL 是属于新的技术,不能和关系型数据库10几年的技术同日而语。
      2. 不提供对 SQL 的支持,如果不支持 SQL 这样的工业标准,将产生一定用户的学习和使用成本。
      3. 不提供关系型数据库对事务的处理。
  • 非关系型数据库的优势:
    1. 性能: NOSQL 是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过 SQL 层的解析,所以性能非常高。
    2. 可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
  • 关系型数据库的优势:
    1. 复杂查询可以用 SQL 语句方便的在一个表以及多个表之间做非常 复杂的数据查询。
    2. 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。
  • 总结:
    • 关系型数据库与 NOSQL 数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用 NOSQL 的时候使用 NOSQL 数据库,让 NOSQL 数据库对关系型数据库的不足进行弥补。
    • 一般会将数据存储在关系型数据库中,在 NOSQL 数据库中备份存储关系型数据库的数据

3. 主流的 NOSQL 产品

  1. 键值(Key-Value)存储数据库
    • 相关产品:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
    • 典型应用:内容缓存,主要用于处理大量数据的高访问负载。
    • 数据模型:一系列键值对
    • 优势:快速查询
    • 劣势:存储的数据缺少结构化
  2. 列存储数据库
    • 相关产品:Cassandra、HBase、Riak
    • 典型应用:分布式的文件系统
    • 数据模型:以列簇式存储,将同一列数据存在一起
    • 优势:查找速度快,可扩展性强,更容易进行分布式扩展
    • 劣势:功能相对局限
  3. 文档型数据库
    • 相关产品:CouchDB、MongoDB
    • 典型应用:Web 应用(与 Key-Value 类似, Value 是结构化的)
    • 数据模型:一系列键值对
    • 优势:数据结构要求不严格
    • 劣势:查询性能不高,而且缺乏统一的查询语法
  4. 图形(Graph)数据库
    • 相关数据库:Neo4J、InfoGrid、Infinite Graph
    • 典型应用:社交网络
    • 数据模型:图结构
    • 优势:利用图结构相关算法。
    • 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

二、 下载安装

Redis 官网(Linux 版本)
Redis 中文官网
Redis (Windows 版本)GitHub 下载

  • 解压直接可以使用,找到文件:

    • redis.windows.conf :配置文件
    • redis-cli.exe :Redis 的客户端
    • redis-server.exe :Redis 的服务器端
    • 开启 Redis 时,需要先双击开启服务器端,再开启客户端
  • 服务器端:
    【Redis :非关系型数据库】_第1张图片

  • 客户端:
    【Redis :非关系型数据库】_第2张图片

  • 要先开启服务器端,才能开启客户端

三、 命令操作

1. 数据结构

  • Redis 的数据结构
    • Redis 存储的是:key,value 格式的数据,其中 key 都是字符串,value 有5种不同的数据结构
      • value 的数据结构:
        1. 字符串类型:String
        2. 哈希类型:Hash :Map 格式
        3. 列表形式:List :LinkedList 格式
        4. 集合类型:Set
        5. 有序集合类型:sorted set

(1) 字符串类型

  1. 存储:set key value
  2. 获取:get key
  3. 删除:del key

【Redis :非关系型数据库】_第3张图片

(2) 哈希类型

  1. 存储:hset key field value
  2. 获取:
    • hget key field :获取指定的 field 对应的值
    • hgetall key :获取所有的 field 和 value
  3. 删除:hdel key field

【Redis :非关系型数据库】_第4张图片

(3) 列表类型

  • 列表类型 List:可以添加一个元素到列表的头部(左边)或者尾部(右边)
    1. 添加:
      • lpush key value:将元素加入列表左边
      • rpush key value:将元素加入列表右边
    2. 获取:lrange key start end :范围获取(如果获取范围是 0 到 -1 ,则就是获取所有)
    3. 删除:
      • lpop key:删除列表最左边的元素,并将元素返回
      • rpop key:删除列表最右边的元素,并将元素返回

【Redis :非关系型数据库】_第5张图片

(4) 集合类型

  • Set:不允许重复元素
    1. 存储:sadd key value
    2. 获取:smembers key:获取 Set 集合种所有元素
    3. 删除:srem key value:删除 Set 集合中的某个元素

【Redis :非关系型数据库】_第6张图片

(5) 有序集合类型 sorted set

  • sorted set :不允许重复元素,且元素有顺序
    1. 存储:zadd key score value
    2. 获取:zrange key start end
    3. 删除:zrem key value

【Redis :非关系型数据库】_第7张图片

2. 通用命令

  1. keys * :查询所有的键
  2. type key :获取键对应的 value 的类型
  3. del key :删除指定的 key value

四、 持久化操作

Redis 是一个内存数据库,当 Redis 服务器重启后,或者电脑重启后,数据会丢失,可以将 Redis 内存中的数据持久化保存到硬盘的文件中。

1. Redis 持久化机制

(1) RDB 方式

  • RDB :默认方式,不需要进行配置,默认就是用这种机制
    • 在一定的间隔中,检测 key 的变化情况,然后持久化数据
      • 编辑 redis.windows.conf 文件(不同版本位置可能不同)
        • save 900 1 :15分钟至少有一个 key 发生改变就持久化一次
        • save 300 10 :5分钟至少有十个 key 发生改变就持久化一次
        • save 60 10000 :1分钟至少有一万个 key 发生改变就持久化一次
          【Redis :非关系型数据库】_第8张图片
  • 假如更改了上述配置文件(不建议更改)
save 900 1
save 300 10
save 10 5
  • 需要使用是,就不能按上述方法正常点击 Redis 服务器端运行了:需要重新启动 Redis 服务器,并指定配置文件名称,使用命令行(Windows + R 键输入 cmd,之后打开后输入 cd /d 文件夹路径),客户端依旧双击打开
盘符:\存放目录\redis 版本号>redis-server.exe redis.windows.conf
  • 之后在客户端中输入键存入值,就会在 Redis 文件夹目录中产生一个 dump.rdb 文件进行存储数据

(2) AOF 方式

  • AOF :日志记录的方式,可以记录每一条命令的操作,可以在每一次命令操作后,持久化数据

    • 编辑 redis.windows.conf 文件(不同版本位置可能不同)
      配置文件
      配置文件
  • appendonly no(默认关闭 aof) → appendonly yes(开启 aof)(使用时依旧需要重新启动 Redis 服务器,并指定配置文件名称,使用命令行(Windows + R 键输入 cmd,之后打开后输入 cd /d 文件夹路径),客户端依旧双击打开,进行操作后生成持久化文件:appendonly.aof)(这种操作对性能影响较大)

    • # appendfsync always :(默认被注释)每一次操作都进行持久化
    • appendfsync everysec :(默认开启)每隔一秒进行一次持久化
    • # appendfsync no :(默认被注释)不进行持久化

五、 使用 Java 客户端操作 Redis

Java 客户端 Jedis
Jedis :一款 Java 操作 Redis 数据库的工具’

1. 使用步骤

  1. 下载 Jedis 的 jar 包
  2. 使用
  • 使用 Maven 导入依赖 jar 包(注意版本的对应使用)Maven 基础
		<dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.3.2version>
        dependency>
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>2.7.1version>
        dependency>
  • 在开启 Redis 服务器端和客户端的前提下 Java 操作
    @Test
    public void test01(){
        // 1. 获取连接
        Jedis jedis = new Jedis("localhost", 6379);
        // 2. 操作
        jedis.set("username","zhangsan");
        // 3. 关闭连接
        jedis.close();
    }
  • 之后就可以在 Redis 客户端中使用 keys * 查看,使用 get 进行取值

2. Jedis 操作各种 Redis 数据结构

  1. 字符串类型:String
    • set
    • get
    @Test
    public void test02(){
        // 1. 获取连接
        // 如果使用空参构造,默认值“localhost”,6379端口
        Jedis jedis = new Jedis();
        // 2. 操作
        // 存储
        jedis.set("username","zhangsan");
        // 获取
        String username = jedis.get("username");
        System.out.println(username);
        // 可以使用 setex() 方法存储可以指定过期时间的 key value
        jedis.setex("activecode",20,"value"); // 将 activecode:value 键值存入 redis,并且20秒后自动删除该键值对
        // 可以存储有时效的激活码,验证码等
        // 3. 关闭连接
        jedis.close();
    }
  1. 哈希类型 Hash:Map 格式
    • hset
    • hget
    • hgetAll
	@Test
    public void test03(){
        // 1. 获取连接
        // 如果使用空参构造,默认值“localhost”,6379端口
        Jedis jedis = new Jedis();
        // 2. 操作
        // 存储 Hash
        jedis.hset("user","name","lisi");
        jedis.hset("user","age","23");
        jedis.hset("user","gender","male");
        // 获取 Hash
        String name=jedis.hget("user","name");
        System.out.println(name);
        // 获取 Hash 的所有 map 中的数据
        Map<String, String> user = jedis.hgetAll("user");
        Set<String> keySet = user.keySet();
        for (String key :
                keySet) {
            // 获取 value
            String value = user.get(key);
            System.out.println(key+":"+value);
        }
        // 3. 关闭连接
        jedis.close();
    }
  1. 列表类型 List :LinkedList 格式,支持重复元素
    • lpush/rpush
    • lpop/rpop
    • lrange start end
    @Test
    public void test04(){
        // 1. 获取连接
        // 如果使用空参构造,默认值“localhost”,6379端口
        Jedis jedis = new Jedis();
        // 2. 操作
        // List 存储
        jedis.lpush("mylist","a","b","c"); // 从左边存
        jedis.rpush("mylist","a","b","c"); // 从右边存
        // List 范围获取
        List<String> mylist = jedis.lrange("mylist", 0, -1);
        System.out.println(mylist);
        // List 弹出
        String element01 = jedis.lpop("mylist"); // c
        System.out.println(element01);
        String element02 = jedis.rpop("mylist"); // c
        System.out.println(element02);
        // List 范围获取
        List<String> newMylist = jedis.lrange("mylist", 0, -1);
        System.out.println(newMylist);
        // 3. 关闭连接
        jedis.close();
    }
  1. 集合类型 Set :不允许重复元素
    • sadd
    @Test
    public void test05(){
        // 1. 获取连接
        // 如果使用空参构造,默认值“localhost”,6379端口
        Jedis jedis = new Jedis();
        // 2. 操作
        // Set 存储
        jedis.sadd("myset","java","php","c++");
        // Set 获取
        Set<String> myset = jedis.smembers("myset");
        System.out.println(myset);
        // 3. 关闭连接
        jedis.close();
    }
  1. 有序集合类型 sorted set :不允许重复元素,且元素有顺序
    • zadd
 @Test
    public void test06(){
        // 1. 获取连接
        // 如果使用空参构造,默认值“localhost”,6379端口
        Jedis jedis = new Jedis();
        // 2. 操作
        // sorted set 存储
        jedis.zadd("mysortedset",85,"张三");
        jedis.zadd("mysortedset",70,"李四");
        jedis.zadd("mysortedset",90,"王五");
        // sorted set 获取
        Set<String> mysortedset = jedis.zrange("mysortedset", 0, -1);
        System.out.println(mysortedset);
        // 3. 关闭连接
        jedis.close();
    }

3. Jedis 连接池:JedisPool

  • 使用:
    1. 创建 JedisPool 连接池对象
    2. 调用方法 getResource() 方法获取 Jedis 连接
    @Test
    public void test07(){
        // 创建一个配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(50);
        config.setMaxIdle(10);
        // 1. 创建 Jedis 连接池对象
        JedisPool jedisPool = new JedisPool(config,"localhost",6379);
        // 2. 获取连接
        Jedis jedis = jedisPool.getResource();
        // 3. 使用
        jedis.set("key","value");
        // 4. 关闭,归还到连接池中
        jedis.close();
    }

4. Jedis 连接池工具类

  • 如果是使用 Maven 框架(如果没有使用 Maven 框架,则直接在 src 目录下创建资源包),则需要在 resources 目录下新建 jedis.properties 资源包,内容如下:
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10
  • 工具类
public class JedisPoolUtils {
    /**
     *  JedisPool 工具类
     *  加载配置文件,配置连接池的参数
     *  提供获取连接的方法
     */
    private static JedisPool jedisPool;
    static {
        // 读取配置文件
        InputStream inputStream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
        // 创建 Properties 对象
        Properties properties = new Properties();
        // 关联文件
        try {
            properties.load(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
        // 获取数据,设置 JedisPoolConfig 中
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(Integer.parseInt((properties.getProperty("maxTotal"))));
        config.setMaxIdle(Integer.parseInt((properties.getProperty("maxIdle"))));
        // 初始化 JedisPool
        jedisPool = new JedisPool(config, properties.getProperty("host"), Integer.parseInt(properties.getProperty("port")));
    }
    // 获取连接方法
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}
  • 测试类
    @Test
    public void test08(){
        // 通过连接池工具类获取
        Jedis jedis = JedisPoolUtils.getJedis();
        // 使用
        jedis.set("key","value");
        // 关闭,归还到连接池中
        jedis.close();
    }

六、 案例

案例需求:

  1. 提供 index.html 页面,页面中有一个省份,下拉列表
  2. 当页面加载完成后,发送 AJAX 请求,加载所有的省份

1. 分析及环境准备

  • index.html 页面进行定义
  • FindProvinceSevlet 服务类:
    1. 调用 service 完成查询 List
    2. 将数据序列化为 JSON
    3. 响应数据
  • ProvinceService 服务类
  • ProvinceDao 操作数据库的类
  • 创建数据库所需要的表:
create table province(
    id int primary key auto_increment,
    name varchar(20) not null
);
insert into province values (null,'北京');
insert into province values (null,'上海');
insert into province values (null,'广州');
insert into province values (null,'深圳');
  • 导入所需要的 jar 包(这里使用 Maven 框架)
  • 设置 druid.properties 数据库连接池资源包文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=123456
initialSize=5
maxActive=10
maxWait=3000
  • 建议使用如图创建文件结构
    【Redis :非关系型数据库】_第9张图片
  • 导入 JavaScript 文件(js 文件)到 webapp 目录下

js 文件

  • 最后使用 Redis 进行缓存优化
    • 注意:使用 Redis 缓存一些不经常发生变化的数据
      • 数据库的数据一旦发生改变,则需要更新缓存
        • 数据库的表执行增删改的相关操作,需要将 Redis 缓存数据情况,再次存入
        • 在 service 对应的增删改方法中,将 Redis 数据删除

2. 实现查询数据库

  • 在 webapp 文件夹下创建 HTML 页面
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>选择title>
    <script src="js/jquery-3.6.1.js">script>
    <script>
        $(function () {
            // 发送 AJAX 请求,加载所有省份数据
            $.get("provinceServlet",{},function (data) {
                // 1. 获取 select
                var province=$("#province");
                // 2. 遍历 JSON 数组
                $(data).each(function () {
                    // 3. 创建 
                    var option="+this.name+"";
                    // 4. 调用 select 的 append 追加 option
                    province.append(option);
                })
            })
        })
    script>
head>
<body>
<select id="province">
    <option>--请选择省份--option>
select>
body>
html>
  • 在 domain 文件夹下创建实体类:Province
public class Province {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 在 dao 文件夹下创建接口:ProvinceDao
public interface ProvinceDao {
    public List<Province> findAll();
}
  • 在 dao 文件夹下的 impl 文件夹中创建接口的实现类:ProvinceDaoImpl
public class ProvinceDaoImpl implements ProvinceDao {
    // 声明成员变量 JdbcTemplate
    private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
    @Override
    public List<Province> findAll() {
        // 定义 SQL
        String sql="select * from province";
        // 执行 SQL
        List<Province> list = template.query(sql, new BeanPropertyRowMapper<>(Province.class));
        return list;
    }
}
  • 在 service 文件夹下创建接口:ProvinceService(使用 Redis 进行优化)
    • 先从 Redis 中查询数据
      • 没有
        1. 从数据库中查询
        2. 将数据存入 Redis
        3. 返回数据
        • 直接返回数据
public interface ProvinceService {
    public List<Province> findAll();
    public String findAllJson();
}
  • 在 service 文件夹下的 impl 文件夹中创建接口的实现类:ProvinceServiceImpl
public class ProvinceServiceImpl implements ProvinceService {
    // 声明 dao
    private ProvinceDao dao=new ProvinceDaoImpl();
    @Override
    public List<Province> findAll() {
        return dao.findAll();
    }

    @Override
    public String findAllJson() {
        // 1. 先从 Redis 中查询数据
        // 获取 Redis 客户端连接
        Jedis jedis = JedisPoolUtils.getJedis();
        String province_json = jedis.get("province");
        // 2. 判断 province_json 数据是否为 null
        if (province_json==null||province_json.length()==0){
            // Redis 中没有数据
            System.out.println("Redis 中没有数据,查询数据库。");
            // 从数据中查询
            List<Province> ps = dao.findAll();
            // 将 List 序列化为 JSON
            ObjectMapper mapper = new ObjectMapper();
            try {
                province_json= mapper.writeValueAsString(ps);
            }catch (JsonProcessingException e){
                e.printStackTrace();
            }
            // 将 JSON 数据存入 Redis
            jedis.set("province",province_json);
            // 归还链接
            jedis.close();
        }else {
            System.out.println("Redis 中有数据,查询缓存。");
        }
        return province_json;
    }
}
  • 在 util 文件夹下创建连接池工具类:
public class JDBCUtils {
    private static DataSource dataSource;
    static {
        try {
            // 加载配置文件
            Properties properties = new Properties();
            // 使用 ClassLoader 加载配置文件,获取字节输入流
            InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            properties.load(inputStream);
            // 初始化连接池对象
            dataSource= DruidDataSourceFactory.createDataSource(properties);
        }catch (IOException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /*
        获取连接池对象
     */
    public static DataSource getDataSource(){return dataSource;}
    /*
        获取连接 Connection 对象
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
}
  • 在 web 目录下的 servlet 文件夹中创建 ProvinceServlet
@WebServlet("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//        // 1. 调用 service 查询
//        ProvinceServiceImpl service = new ProvinceServiceImpl();
//        List list = service.findAll();
//        // 2. 序列化 List 为 JSON
//        ObjectMapper mapper = new ObjectMapper();
//        String json = mapper.writeValueAsString(list);
        // 使用 Redis 缓存后的调用 service 查询
        ProvinceServiceImpl service = new ProvinceServiceImpl();
        String json = service.findAllJson();
        System.out.println(json);
        // 3. 响应结果
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(json);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

你可能感兴趣的:(JavaWeb,基础,redis,数据库,缓存,1024程序员节)