JavaWeb——(17)Redis和Jedis

目录

一、Redis及非关系型数据库NOSQL

二、主流NOSQL非关系型数据库

2.1键值(Key-Value)存储数据库

2.2列存储数据库

 2.3文档型数据库

2.4图形(Graph)数据库

三、Redis

3.1下载安装

3.2Redis的使用

3.2.1命令操作

 3.2.2redis的持久化

四、Jedis

4.1Jedis入门

 4.2Jedis基本操作

4.2.1操作String类型

4.2.2操作Hash类型

4.2.3操作List类型

4.2.4操作Set类型

4.2.5操作SortedSet类型

 4.3Jedis连接池

4.3.1Jedis连接池使用

4.3.2Jedis连接池加载配置文件

五、JedisDemo 

5.1实现

5.2Redis缓存优化


一、Redis及非关系型数据库NOSQL

redis是一款高性能的NOSQL系列的非关系型数据库。常见的数据库类型有以下两种:

  • 关系型数据库:MySQL、Oracle、Microsoft SQL Server、Microsoft Access
    • 数据之间有关联关系
    • 数据存储在硬盘文件上
    • 数据以表的形式存储
  • 非关系型数据库:Redis、HBase、MongoDB、Cassandra等
    • 数据之间没有关联关系
    • 数据存储在内存中
    • 数据以key和value键值对的方式存储

如果数据量非常大的情况,我们操作非关系型数据库是非常耗时的,比如要查询一个用户非常多的用户表,此时会比较慢,我们可以利用缓存的方法来解决这个问题。

在需要查询数据的时候,我们首先在缓存中查找有没有该数据,如果有的话直接获取,没有的话从数据库查询后,将数据写到缓存中,这样获取数据的速度将会大大增快。

关系型数据库与NOSQL数据库并非对立而是互补的关系,通常情况下我们使用关系型数据库存储数据,然后在NOSQL数据库中备份关系型数据库的数据。

二、主流NOSQL非关系型数据库

2.1键值(Key-Value)存储数据库

相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。 
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化    

2.2列存储数据库

相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限

 2.3文档型数据库

相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法

2.4图形(Graph)数据库

相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案

三、Redis

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。

官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。

Redis的常用场景有以下几种:

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)
  • 聊天室的在线好友列表
  • 任务队列(秒杀、抢购、12306等等)
  • 应用排行榜
  • 网站访问统计
  • 数据过期处理(可以精确到毫秒)
  • 分布式集群架构中的session分离

3.1下载安装

官网:https://redis.io

中文网:http://www.redis.net.cn/

也可以在直接点击链接下载(提取码:v8pn),解压直接可以使用,其中有以下几个文件需要注意:

  • redis.windows.conf:配置文件
  • redis-cli.exe:redis的客户端
  • redis-server.exe:redis服务器端

我们首先要启动redis的服务端,双击redis-server.exe,然后启动客户端redis-cli.exe就可以操作了。

3.2Redis的使用

使用Redis有两种方式,一个是命令操作,还有一个是利用Jedis在java客户端进行代码操作。

3.2.1命令操作

操作Redis的数据首先我们要了解Redis的数据结构。

redis存储的是key,value格式的数据,其中key都是字符串,value有5种不同的数据结构:

  • 字符串类型 string
  • 哈希类型 hash,也就是map集合,存储的是键值对,即值存储的还是一个键值对
  • 列表类型 list,元素可重复
  • 集合类型 set,元素不可重复
  • 有序集合类型 sortedset,有序的不可重复元素集合

接下来我们一个个进行介绍:

1、字符串类型String

存储:set key value        

获取:get key

删除:del key

2、哈希类型Hash 

存储:hset key field value       //field是map集合中的键,value是map集合的值

获取:hget key field                //获取key存储的map集合中的field键对应的值

           hgetall key                    //获取key存储的map集合中所有的键值对

删除:hdel key field                //删除key存储的map集合中的field键对应的值

3、列表类型List

redis的列表是简单的字符串列表,按照插入顺序进行排序,可以从列表头部或尾部进行插入。

存储:lpush key value                //从列表左边添加元素

           rpush key value                //从列表右边添加元素

获取:lrange key start end          //获取某个范围内的元素

           lrange key 0 -1                  //如果要获取所有元素,则start=0,end=-1

           lindex index                      //获取某个下标的元素

删除:lpop key                            //删除列表的最左边元素,并将删除的元素返回

           rpop key                           //删除列表的最右边元素,并将删除的元素返回

4、集合类型Set

存储:sadd key value                //存储,value也可以写多个,一次存储多个值

获取:smembers key                //获取set集合中的所有值

删除:srem key value               //删除set集合中的value值

5、有序集合类型SortedSet

redis有序集合SortedSet和集合Set一样,存储的都是String类型元素,且不允许重复元素。

但是SortedSet的每个元素都会关联一个double类型的分数(不同的元素分数可以相同),redis通过分数对集合成员进行从小到大排序。

存储:zadd key score value                        //存储分数为score,值为value的元素到key集合

获取:zrange key start end                         //获取范围为start到end的元素

           zrange key start end withscores       //获取范围为start到end的元素以及对应分数

           zrange key 0 -1                                //如果要获取所有元素,则start=0,end=-1

删除:zrem key value                                 //删除值为value的元素

6、通用命令

keys *:查询所有的键

type key:查询键存储的数据类型

del key:删除键

 3.2.2redis的持久化

redis是一个内存数据库,数据存放在内存中,而内存有一个特点,存储的数据是临时的不能持久化写入。比如当电脑重启或者redis服务器重启这些数据就没有了。

我们可以将redis内存中的数据持久化保存到硬盘文件中,这样当redis重启后可以从文件中重新加载数据

redis持久化一般有两种机制:

  • RDB:默认方式,不需要配置,默认使用该机制
    • 该方案会在一定的间隔时间中,检测key的变化情况,然后持久化数据
    • 持久化的数据会存储到.rdb后缀的文件中
    • 我们一般在配置文件redis.windows.conf中修改:
    • save 900 1        # 在900s内,如果至少有1个key发生改变,就持久化数据一次
      save 300 10       # 在300s内,如果至少有10个key发生改变,就持久化数据一次
      save 60 10000     # 在60s内,如果至少有10000个key发生改变,就持久化数据一次
    • 修改完成后需要命令行启动redis服务器:

  • AOF:日志记录的方式。(读写次数频繁,性能影响较大,不太推荐)
    • 可以记录每一条命令的操作。每次命令操作后都会持久化数据
    • 持久化的数据会存储到.aof后缀的文件中
    • 同样我们要在配置文件redis.windows.conf中修改:
    • appendonly no           # 默认为no,代表关闭,此处改为yes则是开启aof机制
      
      # appendfsync always    # 每次操作都进行持久化
      # appendfsync everysec  # 每秒进行一次持久化
      # appendfsync no        # 不进行持久化
    • 第一个是aof机制的开关,后三个是aof机制的设置。同样修改完成后需要命令行启动服务器。

四、Jedis

Jedis是一款java操作redis数据库的工具。(类似JDBC操作mysql数据库)

首先要下载jedis的jar包(提取码:5al9),然后将jar包导入工程就可以使用了。

4.1Jedis入门

我们在java中新建web工程,导入jar包,往redis中写入一个键值对。

public void test1(){
    //获取redis连接
    Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379

    //操作
    jedis.set("username","tz");//存储键名为username,值为tz的键值对

    //关闭连接
    jedis.close();
}

 4.2Jedis基本操作

4.2.1操作String类型

public void test2(){
    //获取redis连接
    Jedis jedis=new Jedis();//如果使用空参构造,默认值为localhost和6379端口,如果连接本机可以直接使用空参构造

    jedis.set("username","tz");//存储键名为username,值为tz的键值对
    String username = jedis.get("username");//获取键名为username的值
    System.out.println(username);
    jedis.del("username");//删除该键

    //关闭连接
    jedis.close();
}

4.2.2操作Hash类型

public void test3(){
    //获取redis连接
    Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379

    jedis.hset("user","name","tz");//存储键名为user的hash集合,name为tz
    jedis.hset("user","age","22");//存储键名为user的hash集合,age为22
    String name = jedis.hget("user", "name");//获取user中键名为name的值
    String age = jedis.hget("user", "age");//获取user中键名为age的值
    System.out.println(name+":"+age);

    Map<String, String> user = jedis.hgetAll("user");//获取user中所有的map集合数据
    for (String key:user.keySet()) {//输出
        System.out.println(key+":"+user.get(key));
    }

    //关闭连接
    jedis.close();
}

4.2.3操作List类型

public void test4(){
    //获取redis连接
    Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379

    jedis.lpush("mylist","a","b","c");//从列表左边存储数据a,b,c
    jedis.rpush("mylist","a","b","c");//从列表右边存储数据a,b,c
    List<String> mylist = jedis.lrange("mylist", 0, -1);//获取列表中的所有数据
    System.out.println(mylist);
    String s1 = jedis.lpop("mylist");//弹出最左边的元素
    String s2 = jedis.rpop("mylist");//弹出最右边的元素
    System.out.println(s1);
    System.out.println(s2);
    List<String> mylist2 = jedis.lrange("mylist", 0, -1);//获取列表中的所有数据
    System.out.println(mylist2);

    jedis.close();//关闭连接
}

4.2.4操作Set类型

public void test5(){
    //获取redis连接
    Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379

    jedis.sadd("myset","1","2","3","3");//存储键名为myset的集合,值为"1","2","3",值不允许重复
    Set<String> set = jedis.smembers("myset");
    System.out.println(set);

    jedis.close();//关闭连接
}

4.2.5操作SortedSet类型

public void test6(){
    //获取redis连接
    Jedis jedis=new Jedis("localhost",6379);//连接本机地址127.0.0.1,默认端口6379

    jedis.zadd("language",100,"java");//存储language的集合,值为"java",得分为100
    jedis.zadd("language",60,"c");//存储language的集合,值为"c",得分为60
    jedis.zadd("language",80,"c++");//存储language的集合,值为"c++",得分为80
    Set<String> sortedset = jedis.zrange("language", 0, -1);
    System.out.println(sortedset);
    
    jedis.close();//关闭连接
}

 4.3Jedis连接池

Jedis连接池和JDBC连接池类似,Jedis连接池不需要创建新的Jedis对象连接Redis,可以大大减少对于创建和回收Redis连接的开销。

4.3.1Jedis连接池使用

在Jedis中,我们通过JedisPool对象来创建Jedis连接池,步骤如下:

  1. 创建JedisPool连接池对象
  2. 调用方法getResource()方法获取Jedis连接
public void testJedisPool(){
    JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();//创建jedispool的配置对象
    jedisPoolConfig.setMaxTotal(50);//设置jedis连接池最大连接数为50
    jedisPoolConfig.setMaxIdle(10);//设置最大空闲连接数为10
    JedisPool jedisPool=new JedisPool(jedisPoolConfig,"localhost",6379);//利用配置对象构造创建Jedis连接池对象
    Jedis jedis = jedisPool.getResource();//获取连接

    jedis.set("jedisPool","666");
    System.out.println(jedis.get("jedisPool"));//输出
    jedis.close();//将jedis归还到连接池中
}

 Jedis的常见配置属性如下:

redis.pool.maxTotal=1000 #最大活动对象数  
redis.pool.maxIdle=100 #最大能够保持idel状态的对象数      
redis.pool.minIdle=50 #最小能够保持idel状态的对象数   
redis.pool.maxWaitMillis=10000 #当池内没有返回对象时,最大等待时间    
redis.pool.testOnBorrow=true #当调用borrow Object方法时,是否进行有效性检查   
redis.pool.testOnReturn=true #当调用return Object方法时,是否进行有效性检查  
redis.pool.timeBetweenEvictionRunsMillis=30000 #“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.  
redis.pool.testWhileIdle=true #向调用者输出“链接”对象时,是否检测它的空闲超时;  
redis.pool.numTestsPerEvictionRun=50 # 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3. 
redis.ip=xxxxxx #redis服务器的IP   
redis1.port=6379 #redis服务器的Port    

4.3.2Jedis连接池加载配置文件

我们通过代码人工设置属性比较繁琐,所以一般都是放置在配置文件中,读取配置文件创建连接池。

我们新建一个JedisPoolUtils类, 用于获取jedis的连接池,并用配置文件对连接池进行配置。

package Jedis.Util;

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

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class JedisPoolUtils {
    private static JedisPool jedisPool;

    static {//静态代码块,当类加载时就读取配置文件
        InputStream stream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");//得到文件输入流
        Properties properties=new Properties();//创建配置对象
        try {
            properties.load(stream);//将对象关联到配置文件
        } catch (IOException e) {
            e.printStackTrace();
        }
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));//从配置文件中读取数据并设置到配置对象中
        jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxIdle")));
        jedisPool=new JedisPool(jedisPoolConfig,properties.getProperty("host"),Integer.parseInt(properties.getProperty("port")));//初始化JedisPool
    }

    public static Jedis getJedisPool(){
        return jedisPool.getResource();//获取jedis连接池
    }
}

配置文件如下:

然后我们对该工具类进行测试:

public void testJedisPoolUtils(){
    Jedis jedis=JedisPoolUtils.getJedisPool();

    jedis.set("jedisPoolUtil","777");
    System.out.println(jedis.get("jedisPoolUtil"));//输出
    jedis.close();//将jedis归还到连接池中
}

五、JedisDemo 

5.1实现

我们实现一个案例,该案例提供一个界面,里面有省份的下拉列表,当页面加载完成之后,发送ajax请求,加载所有省份。

首先我们在数据库中新建province这个表:

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,'深圳');
INSERT INTO province VALUES(NULL,'武汉');

 然后实现数据访问层Dao,连接数据库完成查询功能,

package ProvinceWebDemo.Dao;

import ProvinceWebDemo.Province;
import ProvinceWebDemo.Utils.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

interface ProvinceDao {
    public List<Province> findAll();
}

public class ProvinceDaoImpl implements ProvinceDao {
    private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public List<Province> findAll() {
        String sql="select * from province";
        List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
        return list;
    }
}

接着在业务逻辑层调用数据访问层的方法,

package ProvinceWebDemo.Service;

import ProvinceWebDemo.Dao.ProvinceDao;
import ProvinceWebDemo.Dao.ProvinceDaoImpl;
import ProvinceWebDemo.Province;

import java.util.List;

interface ProvinceService {
    public List<Province> findAll();
}

public class ProvinceServiceImpl implements ProvinceService{
    private ProvinceDao dao=new ProvinceDaoImpl();

    @Override
    public List<Province> findAll() {
        return dao.findAll();
    }
}

然后我们新建一个servlet,获取数据并将结果序列化为json格式字符串,写回response,

package ProvinceWebDemo;

import ProvinceWebDemo.Service.ProvinceService;
import ProvinceWebDemo.Service.ProvinceServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ProvinceService service=new ProvinceServiceImpl();
        List<Province> provinces = service.findAll();//调用服务获取省份数据

        ObjectMapper mapper=new ObjectMapper();
        String json = mapper.writeValueAsString(provinces);//将集合序列化为json格式字符串
        System.out.println(json);
        response.setContentType("application/json;charset=utf-8");//设置response格式
        response.getWriter().write(json);//将json写入response
    }

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

在界面中访问servlet,并显示结果:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <script src="js/jquery-3.3.1.js"></script>
    <script>
        $(function(){
            //发送ajax请求,加载所有省份数据
            $.get("provinceServlet",{},function (data) {
                var province=$("#province");
                $(data).each(function () {
                    var option="<option name='"+this.id+"'>"+this.name+"</option>";//遍历创建每个选项
                    province.append(option);//将选项放置到下拉框中
                })
            })
        });
    </script>
</head>
<body>
    <select id="province">
        <option>——请选择省份——</option>
    </select>
</body>
</html>

启动服务器:

5.2Redis缓存优化

我们每次进入这个页面的时候都会访问数据库,查询省份的数据,访问数据库比较耗时。

而这些省份数据不会经常改变,所以我们可以利用redis的缓存技术。

第一次访问我们还是在业务逻辑层调用dao数据访问层的方法获取数据,但是我们会将得到的数据存入redis中,然后返回数据,当后面访问的时候,会先看看redis有没有数据,如果有数据则直接获取redis里的数据,这样就可以不用访问数据库了。

我们在 ProvinceServiceImpl 中新加一个方法findAllJson(),该方法会先看redis中是否有缓存数据,没有的话再查数据库,将省份信息序列化为json字符串返回。

public String findAllJson() {
    //使用redis缓存获取数据
    Jedis jedis = JedisPoolUtils.getJedisPool();//获取jedis连接
    String province_json = jedis.get("province");//查询redis中province数据
    if (province_json == null || province_json.length() == 0) {//redis中没有数据
        System.out.println("redis中无数据,查询数据库中........");
        List<Province> provinces = dao.findAll();//从数据库中查询数据
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            province_json = objectMapper.writeValueAsString(provinces);//将数据序列化为json字符串
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        jedis.set("province", province_json);//将json写入到redis缓存中
        jedis.close();//关闭连接
    } else {
        System.out.println("redis中有数据,查询redis缓存中........");
    }
    return province_json;//返回json
}

我们在servlet中就可以直接调用该方法得到json字符串,

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ProvinceService service=new ProvinceServiceImpl();
    String json = service.findAllJson();//调用redis缓存,获取省份数据的json字符串
    System.out.println(json);
    response.setContentType("application/json;charset=utf-8");//设置response格式
    response.getWriter().write(json);//将json写入response
}

然后启动服务器,可以看到第一次走的是数据库,刷新页面后第二次就走的redis缓存,

注意:使用redis缓存一些不经常发生变化的数据。

数据库的数据一旦发生改变(即当数据库的表执行 增删改 等相关操作时),则需要将redis缓存数据清空,然后再次存入。

Guess you like

Origin blog.csdn.net/weixin_39478524/article/details/121180059