第53次文章:Redis

2020-02-14 16:48:14 浏览数 (1)

周末愉快~我们这周来聊聊数据库的那些事儿吧!嘻嘻!


Redis

一、概念

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

1、什么是NOSQL

NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。

随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

(1)NOSQL和关系型数据库比较

  • 优点: 1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。 2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。 3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。 4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。
  • 缺点: 1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。 2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。 3)不提供关系型数据库对事务的处理。

(2)非关系型数据库的优势

代码语言:javascript复制
1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。

2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

(3)关系型数据库的优势

代码语言:javascript复制
1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。

2)事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

(4)总结

代码语言:javascript复制
关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,
让NoSQL数据库对关系型数据库的不足进行弥补。

一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据
2、主流的NOSQL产品

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

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

(2)列存储数据库

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

(3)文档型数据库

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

(4)图形(Graph)数据库

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

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:

代码语言:javascript复制
1) 字符串类型 string
2) 哈希类型 hash
3) 列表类型 list
4) 集合类型 set
5) 有序集合类型 sortedset
4、redis的应用场景
代码语言:javascript复制
•   缓存(数据查询、短连接、新闻内容、商品内容等等)
•   聊天室的在线好友列表
•   任务队列。(秒杀、抢购、12306等等)
•   应用排行榜
•   网站访问统计
•   数据过期处理(可以精确到毫秒
•   分布式集群架构中的session分离
二、下载安装

直接在官网上下载,然后解压直接可以使用,我们关注一下解压之后的几个文件,如下所示:

  • redis.windows.conf:配置文件
  • redis-cli.exe:Redis的客户端
  • redis-server.exe:Redis服务器端
三、命令操作
1、Redis的数据结构

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

代码语言:javascript复制
1)字符串类型 string
2)哈希类型 hash:map格式
3)列表类型list:linkedlist格式。支持重复元素
4)集合类型set:不允许重复元素
5)有序集合类型 sortedset:不允许重复元素,且元素有顺序
2、字符串类型 String

(1)存储:set key value

代码语言:javascript复制
127.0.0.1:6379> set username zhangsan
OK

(2)获取:get key

代码语言:javascript复制
127.0.0.1:6379> get username
"zhangsan"

(3)删除:del key

代码语言:javascript复制
127.0.0.1:6379> del username
(integer) 1
127.0.0.1:6379> get username
(nil)
3、哈希类型 hash

(1)存储:hset key field value

代码语言:javascript复制
127.0.0.1:6379> hset myhash username lisi
(integer) 1
127.0.0.1:6379> hset myhash password 123
(integer) 1

(2)获取:hget key field

代码语言:javascript复制
127.0.0.1:6379> hget myhash username
"lisi"
127.0.0.1:6379> hget myhash password
"123"

(3)一次性获取全部数据:hgetall key

代码语言:javascript复制
127.0.0.1:6379> hgetall myhash
1) "username"
2) "lisi"
3) "password"
4) "123"

(4)删除:hdel key field

代码语言:javascript复制
127.0.0.1:6379> hdel myhash username
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "password"
2) "123"
4、列表类型list

简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)和尾部(右边)

(1)添加

  • lpush key value:将元素加入列表左边
  • rpush key value:将元素加入到列表右边 127.0.0.1:6379> lpush mylist a (integer) 1 127.0.0.1:6379> lpush mylist b (integer) 2 127.0.0.1:6379> rpush mylist c (integer) 3

(2)获取

  • lrang key start end:范围获取 127.0.0.1:6379> lrange mylist 0 -1 1) "b" 2) "a" 3) "c"

(3)删除

  • lpop key:删除列表最左边的元素,并将元素返回
  • rpop key:删除列表最右边的元素,并将元素返回 127.0.0.1:6379> lpop mylist "b" 127.0.0.1:6379> lrange mylist 0 -1 1) "a" 2) "c" 127.0.0.1:6379> rpop mylist "c" 127.0.0.1:6379> lrange mylist 0 -1 1) "a"
5、集合类型 set

不允许重复元素,元素无序

(1)存储

sadd key value

(2)获取

smembers key : 获取set集合中所有元素

(3)删除

srem key value:删除set集合中的某个元素

6、有序集合类型 sortedset

不允许重复元素,且元素有顺序

(1)存储:zadd key score value

代码语言:javascript复制
127.0.0.1:6379> zadd mysort 50 zhangsan
(integer) 1
127.0.0.1:6379> zadd mysort 600 lisi
(integer) 1
127.0.0.1:6379> zadd mysort 80 wangwu
(integer) 1

(2)获取:zrange key start end (withscores)

获取范围(连带着分数一起获取)

代码语言:javascript复制
127.0.0.1:6379> zrange mysort 0 -1
1) "zhangsan"
2) "wangwu"
3) "lisi"

127.0.0.1:6379> zrange mysort 0 -1 withscores
1) "zhangsan"
2) "50"
3) "wangwu"
4) "80"
5) "lisi"
6) "600"

(3)删除:zrem key value

代码语言:javascript复制
127.0.0.1:6379> zrem mysort lisi
(integer) 1
127.0.0.1:6379> zrange mysort 0 -1
1) "zhangsan"
2) "wangwu"
7、通用命令
  • keys *:查询所有的键
  • type key:获取键对应的value的类型
  • del key:删除指定的key value
四、持久化

Redis是一个内存数据库,当Redis服务器重启,或者电脑重启,数据会丢失,我们可以将Redis内存中的数据持久化保存到硬盘的文件中。Redis的持久化机制主要有下面两种方式。

1、RDB

默认方式,不需要进行配置,默认就使用这种机制。

这种持久化机制的主要思想是:在一定的间隔时间中,检测key的变化情况,然后持久化数据。具体操作如下:

(1)编辑redis.windows.conf文件

代码语言:javascript复制
#   after 900 sec (15 min) if at least 1 key changed
save 900 1
#   after 300 sec (5 min) if at least 10 keys changed
save 300 10
#   after 60 sec if at least 10000 keys changed
save 60 10000

(2)重新启动Redis服务器,并制定配置文件名称

代码语言:javascript复制
D:redis-2.8.9>redis-server.exe redis.windows.conf
2、AOF

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

在具体操作上,与上一种方式相同,主要区别在于配置文件的编辑上。使用AOF方式进行持久化的话,配置文件的修改如下所示

(1)编辑Redis.windows.conf文件

代码语言:javascript复制
appendonly no(关闭aof)  --->   appendonly yes (开启aof)

# appendfsync always:每一次操作都进行持久化
appendfsync everysec:每隔一秒进行一次持久化
# appendfsync no    :不进行持久化
五、Java客户端 Jedis
1、Jedis

一款Java操作Redis数据库的工具,类似于JDBC操作数据库

2、使用步骤

(1)下载jedis的jar包

(2)使用

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
jedis.set("username","zhangsan");
//3.关闭连接
jedis.close();
3、Jedis操作各种Redis中的数据结构

(1)字符串类型 string

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
//存储
jedis.set("username","zhangsan");
//获取
String username = jedis.get("username");
System.out.println(username);
//使用setex()方法存储可以指定过期时间的键值对
jedis.setex("activecode",10,"hehe");//将键值对activecode:hehe 存入Redis,并且10秒之后删除键值对
//3.关闭连接
jedis.close();

(2)哈希类型 hash:map格式

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
//存储hash
jedis.hset("user","username","zhangsan");
jedis.hset("user","age","23");
jedis.hset("user","gender","male");

//获取
String user = jedis.hget("user","username");
Map<String, String> user1 = jedis.hgetAll("user");
System.out.println(user);
System.out.println(user1);
//3.关闭连接
jedis.close();

(3)列表类型list:linkedlist格式。支持重复元素

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
jedis.rpush("mylist","a","b","c");
jedis.lpush("mylist","a","b","c");
System.out.println(jedis.lrange("mylist",0,-1));

String element1 = jedis.lpop("mylist");
System.out.println(element1);
String element2 = jedis.rpop("mylist");
System.out.println(element2);

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

(4)集合类型set:不允许重复元素

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
//set 存储
jedis.sadd("myset","a","b");
Set<String> myset = jedis.smembers("myset");
for (String s : myset) {
    System.out.println(s);
}
//3.关闭连接
jedis.close();

(4)有序集合类型 sortedset:不允许重复元素,且元素有顺序

代码语言:javascript复制
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.操作
//sortedset存储
jedis.zadd("mysorted",10,"abc");
jedis.zadd("mysorted",80,"acsa");
jedis.zadd("mysorted",50,"lisi");

Set<String> mysorted = jedis.zrange("mysorted", 0, -1);
System.out.println(mysorted);
//3.关闭连接
jedis.close();
4、jedis 连接池

jedis连接池的创建:

(1)创建jedispool连接池对象

(2)调用方法 getResource()方法获取jedis连接

代码语言:javascript复制
public class JedisPoolUtils {
    private static JedisPool jedisPool;
    static {
        //将配置文件获取为一个数据流
        InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
        System.out.println(JedisPoolUtils.class.getResource("/"));
        //创建一个配置文件
        Properties pro = new Properties();
        //关联配置文件数据流和配置文件
        try {
            pro.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //创建Jedis连接池的配置文件
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
        config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));

        jedisPool = new JedisPool(config,pro.getProperty("host"),Integer.parseInt(pro.getProperty("port")));
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

案例

一、案例需求

(1)提供index.html页面,页面中有一个省份下拉列表

(2)当页面加载完成后,发送ajax请求,在下拉列表中加载所有省份

二、代码实现
1、创建servlet
代码语言:javascript复制
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1.调用service查询
    ProvinceService service = new ProvinceServiceImpl();
    List<Province> list = service.findAll();

    //2.序列化list为json
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(list);
    System.out.println(json);
    //3.响应结果
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write(json);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
}
2、创建的service实现类
代码语言:javascript复制
public class ProvinceServiceImpl implements ProvinceService {
    //1.声明dao
    private ProvinceDao pd = new ProvinceDaoImpl();

    @Override
    public List<Province> findAll() {
        return pd.findAll();
    }
}
3、创建的dao实现类
代码语言:javascript复制
public class ProvinceDaoImpl implements ProvinceDao {
    //1.申明成员变量 jdbctemplate
    JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public List<Province> findAll() {
        //1.定义sql语句
        String sql = "select * from province";
        List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
        return list;
    }
}
三、对上面代码的优化

在现实生活中,对于省份的更新可能没有那么的频繁,在上面的实现方式中,我们每一次的刷新页面,都是直接从数据库中调取数据,然后再返回给前端。这样做在很大程度上浪费了数据库的连接资源。所以我们可以将省份信息存放在本地缓存中,从Redis数据库来获取资源,可以极大的提高效率。

对于上面代码的改进,主要体现在我们对service方法的改进,如下所示:

代码语言:javascript复制
public class ProvinceServiceImpl implements ProvinceService {
    //1.声明dao
    private ProvinceDao pd = new ProvinceDaoImpl();

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

    /*
        使用Redis缓存
     */
    @Override
    public String findAllJson() {
        //1.先从Redis连接池中获取一个Redis的连接对象
        Jedis jedis = JedisPoolUtils.getJedis();
        //2.从缓存中查寻结果
        String province_json = jedis.get("province");
        //3.判断是否Redis中是否已经存在
        if (province_json == null || province_json.length() ==0){
            //Redis中不存在数据
            System.out.println("Redis中不存在数据.....查询数据库");
            //1.然后从数据库中查询
            List<Province> ps = pd.findAll();
            //2.将ps序列化为json数据
            ObjectMapper mapper = new ObjectMapper();
            try {
                province_json = mapper.writeValueAsString(ps);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            //3.将json数据存入到Redis数据库中
            jedis.set("province",province_json);
            //3.关闭流
            jedis.close();
        }else{
            System.out.println("Redis中存在数据.....查询缓存....");
        }
        return province_json;
    }
}

tips:使用Redis缓存一些不经常发生变化的数据,可是当数据库的数据一旦发生改变,则需要更新缓存。所以数据库的表执行增删改的相关操作,需要将Redis缓存数据清空,再次存入。我们需要在service对应的增删改方法中,将Redis数据删除。

0 人点赞