一、Redis的概述

1.1 NoSQL的简介

1.1.1 NoSQL为什么会出现

从功能角度来讲,出现了各种编程语言

c语言,c++, java,scala,php,python等

从扩展角度来讲,对编程语言进行统一规范,设计各种接口,出现了各种编程语言的框架

structs,hibernate,mybatis,tomcat,RDBMS等

由于应用的请求数量越来越多,存储的数据越来越多,数据种类也越来越多,就出现了性能问题,为了解决性能问题

nginx,NoSql,MQ等

1.1.2 什么是NoSQL

1. NoSql, 是Not only Sql的简写,泛指非关系型数据库。
2. 为了解决高并发、高可扩展、高可用、大数据存储问题而产生的另一种数据库解决方案
3. 是RDBMS的良好补充,并不能取代RDBMS。(各有各的应用场景,RDBMS适合OLTP,Nosql适合OLAP)
4. NoSQL数据库基本上也都是基于内存的,所以访问速度也很快。

1.1.3 NoSQL的分类

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

1.2 Redis的简介

1.2.1 Redis的介绍

1. redis是一个c语言开发的,开源的,基于内存的NoSql数据库。也可以用作消息缓存系统(对比kafka)
2. 提供了多种数据结构支持strings:字符串hashes:散列lists:列表sets:集合sorted sets:带范围查询的有序集合, bitmaps:位图类型hyperloglogs: 超日志geospatial:地理空间索引streams:流
3.  Redis内置了副本、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久方式,
4.  可以通过Redis Sentinel和Redis Cluster的自动分区提供高可用性
5. 也可以对数据结构进行原子操作(说白了,就是对数据结构进行普通的CRUD)

1.2.2 Redis的应用场景

#1  缓存(数据查询、短连接、新闻内容、商品内容等等)。这种应用场景的使用是最多的#2 分布式集群架构中的session分离。#3 聊天室的在线好友列表。#4 任务队列。(秒杀、抢购、12306等等)#5 应用排行榜。#6 网站访问统计。#7 数据过期处理(可以精确到毫秒)

1.2.3 Redis的特性

1. 访问速度快,原因是Redis基于内存存储
2. Redis也支持持久化操作,而且有两种方式,一种方式是dump到磁盘,一种方式是AOFdump到磁盘:  就是存储内存中的具体数据AOF:   除了存储具体数据外,还存储操作类型,比如是insert,delete,update等
3. Redis也支持集群模式,可以扩展多个服务节点
4. Redis相较于其他的缓存工具,支持的数据类型是非常丰富的。

二、Redis的安装部署

2.1 Redis的官网和下载

英文官网:https://redis.io/
中文官网:http://www.redis.cn/
下载:https://download.redis.io/releases/xxxxx.tar.gz

2.2 Redis的安装部署

2.2.1 注意事项:

1. 一般都是安装在linux系统上
2. 又因为c语言开发的,所以源码安装时,需要c语言环境yum install gcc-c++

2.2.2 安装步骤

步驟1) 检查安装c语言环境

[root@qianfeng01 ~]#  yum install gcc-c++

步骤2)上传、解压、更名

[root@qianfeng01 ~]# tar -zxvf redis-4.0.14.tar.gz -C /usr/local/
[root@qianfeng01 ~]# cd /usr/local/
[root@qianfeng01 local]# mv redis-4.0.14/ redis

步骤3)编译

[root@qianfeng01 local]# cd redis
[root@qianfeng01 redis]# make

步骤4)安装

[root@qianfeng01 redis]# make install PREFIX=/usr/local/redis注意:此时在redis目录下,就会出现bin目录

步骤5)配置环境变量

[root@qianfeng01 redis]# vim /etc/profile
..........省略...........
#redis environment
export REDIS_HOME=/usr/local/redis
export PATH=$PATH:$REDIS_HOME/bin
[root@qianfeng01 redis]# source /etc/profile#查看版本号
[root@qianfeng01 redis]# redis-cli --version

2.3 Redis服务的启动和关闭

2.3.1 启动方式

方式1)前台启动

只需要在命令行上输入redis-server脚本即可[root@qianfeng01 redis]# redis-server注意:由于是前台进程,如果ctrl+c了,就直接停止服务了。 所以不经常用

方式2)后台启动

#1: 将redis里的redis.conf拷贝到bin目录下
[root@qianfeng01 redis]# cp redis.conf bin/
#2: 修改bin下的redis.conf文件
[root@qianfeng01 bin]# vim redis.conf
...省略...134 # By default Redis does not run as a daemon. Use 'yes' if you need it.135 # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.136 daemonize yes          <== 将no改为yes#启动服务, 带上配置文件
[root@qianfeng01 bin]# redis-server /usr/local/redis/bin/redis.conf

查看进程

[root@qianfeng01 ~]# ps -ef | grep redis
root      31713      1  0 10:37 ?        00:00:00 redis-server 127.0.0.1:6379
root      31771  19391  0 10:38 pts/0    00:00:00 grep --color=auto redis

2.3.2 关闭方式

方式1)正规关闭方式

[root@qianfeng01 ~]# redis-cli -h <ip地址或者主机名> shutdown

方式2)kill掉

[root@qianfeng01 ~]# kill -9  pid

2.4 如何连接redis服务

2.4.1 命令行工具

[root@qianfeng01 ~]# redis-cli -h <主机ip或主机名> -p <端口号>reg:
[root@qianfeng01 ~]# redis-cli -h qianfeng01 -p 6379默认情况:
[root@qianfeng01 ~]# redis-cli
连接的是127.0.0.1

小贴士:

如果想要远程连接,需要将redis.conf里bind 127.0.0.1 修改为主机名 或者注释掉。
1. 如果修改为主机名,那么再连接时,一定要用到主机名,否则连不上
2. 如果是注释掉,可以本地连接,也可以远程连接。

2.4.2 Redis桌面管理工具

1. 双击安装即可
2. 配置参数:Name: 连接名称,随便写Host: 服务的ip或者hostPort: 端口号6379Auth: 密码

注意:如果想要远程连接redis,需要修改redis.conf配置文件

方式1: 将69行对应的 bind 127.0.0.1 修改成主机名或者是ip即:bind qianfeng01或者bind 192.168.10.101
方式2: 注释掉即可

2.4.3 编程语言连接

1)说明

Redis不仅是使用命令来操作,也可以使用编程语言调用相关API接口进行连接。比如java、C、C#、C++、php、Node.js、Go等。在官方网站里列一些Java的客户端api,有Jedis、Redisson、Jredis、JDBC-Redis等
官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。 Jedis同样也是托管在github上,地址:httpsRedis.assets//github.com/xetorthio/jedis

2)pom.xml

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.0.0</version>
</dependency>

3)单线程连接方式

小贴士:需要将redis.conf里的protected-mode的no改为yes,即关闭保护模式。

package com.qf.redis.day01;import redis.clients.jedis.Jedis;/*** 单线程连接redis*/
public class RedisDemo01 {public static void main(String[] args) {/*** Jedis(String host,int port)*/Jedis jedis = new Jedis("qianfeng01", 6379);//调用ping指令 向远程主机发送信息,验证网络通信是否正常,返回pong 表示畅通String ping = jedis.ping();System.out.println(ping);//设置字符串类型的一个键值对,String setAck = jedis.set("girlfriend", "gaoyuanyuan");System.out.println(setAck);//从redis中获取girlfriend对应的value值String getAck = jedis.get("girlfriend");System.out.println(getAck);jedis.close();}
}

4)线程池连接方式

package com.qf.redis.day01;import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;/*** 使用线程池连接redis*/
public class RedisDemo02 {public static void main(String[] args) {/*** 创建一个线程池对象* 构造器:*     JedisPool(String host,int port)*     JedisPool(GenericObjectPoolConfig config,String host)*/GenericObjectPoolConfig config = new GenericObjectPoolConfig();config.setMaxTotal(20); //设置线程池中的最大值config.setMaxIdle(7);  //设置线程池中的最大空闲连接数config.setMinIdle(5);  //设置线程池中的最小空闲连接数config.setMaxWaitMillis(5000); //设置超时时间,单位毫秒JedisPool pool = new JedisPool(config, "qianfeng01");//从池子中获取一个具体的连接对象Jedis jedis = pool.getResource();//设置键值对String set = jedis.set("hobby", "movie");System.out.println(set);//获取key对应的valueString hobby = jedis.get("hobby");System.out.println(hobby);jedis.close();// 将连接归还到连接池中}
}

2.5 Redis的密码策略

进入redis.conf文件,找到500行左右,解开注释,设置密码,修改后别忘记重启服务

# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
requirepass 123123    <==  解开注释,设置密码# Command renaming.
#
# It is possible to change the name of dangerous commands in a shared
# environment. For instance the CONFIG com

连接:需要使用-a 来设定密码

[root@qianfeng01 bin]# redis-cli -h qianfeng01 -p 6379 -a 123123

程序中如下设置:

 jedis.auth("123123");

2.6 redis的常规操作

2.6.1 库操作

1. redis的默认的db的个数是16个。id从0到15
2. 切换数据库select dbid
3. 清空当前数据库(慎用)flushdb
4. 清空所有的数据库(慎用)flushall

2.6.2 Redis的键

1. #根据匹配模式查询相关的keykeys *   #查询所有的keykeys ?O*   #查询第二个字符是O的key
2. dbsize     #查看当前库下所有的key的个数
3. exists key   #判断某一个key是否存在,  0表示不存在,1表示存在
4. type key    #查看key的类型
5. del key   #删除某一个key    1 表示删除成功  0表示删除失败
6. unlink key #删除某一个key(非阻塞删除) #仅将key从keyspace元数据中删除,真正的删除会在后续中异步删除   (先奏后斩的意思)
7. expire key seconds     #设置key的存活时间
8. persist key    #清除key的存活时间,  已经过期的清除没有意义
9. ttl key       #查看key的存活时间    -2表示过期  -1 表示永不过期

小故事

6379这个端口号,来源于一个意大利歌女的名字  merz

三、Redis的类型

Redis在存储数据时,都是以键值对的形式存储的,这个key的作用是用于指向value的,可以通过这个key获取具体的value值。我们所说的丰富的数据类型是针对于value来说的。key只是用来做映射关系。

那么key的长度是越长越好?还是越短越好?

都不是,尽量做到见名知意。比如存储身份证是xxxxx1901xxxx的张三的这个人的爱好
set  xxxxx1901xxxx-zhangsan-hobbys   book  movie   eat 太长占内存
太短的话:做不到见名知意

3.1 Redis的五大常用类型

3.1.1 字符串类型

3.1.1.1 介绍

这是redis中的最简单类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。由于 Redis 的键是字符串,所以当我们也使用字符串类型作为值时,我们是在将一个字符串映射到另一个字符串。字符串数据类型可用于许多用例,例如缓存 HTML 片段或页面。应用场景:  可以将内存中的对象,进行存储到redis中

3.1.1.2 命令行常用指令

set指令的语法格式:set key value [EX seconds] [PX milliseconds] [NX|XX]EX: KV的存活时间,单位是秒PX: KV的存活时间,单位是毫秒NX: K不存在时,会设置成功XX: K已经存在时,会覆盖
get指令的语法格式:get key
qianfeng01:6379[1]> set city changchun EX 100 NX
OK
qianfeng01:6379[1]> keys *
1) "city"
2) "word"
qianfeng01:6379[1]> set city beijing EX 100 NX    <==  会失败,因为city已经存在
(nil)
qianfeng01:6379[1]> get city
"changchun"
qianfeng01:6379[1]> set city beijing XX    <==  会成功,因为city已经存在
OK
qianfeng01:6379[1]> get city
"beijing"
qianfeng01:6379[1]>
qianfeng01:6379[1]> append city shanghai   <==  在key对应的原有的value上追加字符串
(integer) 15
qianfeng01:6379[1]> get city
"beijingshanghai"
qianfeng01:6379[1]> set num 1
OK
qianfeng01:6379[1]> decr num         <==  递减1   要求 value的类型是整形
(integer) 0
qianfeng01:6379[1]> DECRBY num 5     <==  递减5   指定步长
(integer) -5
qianfeng01:6379[1]> get city
"beijingshanghai"
qianfeng01:6379[1]> GETRANGE city 0 2   <==  获取指定坐标之间的字符串,是一个闭区间
"bei"
qianfeng01:6379[1]> GETSET city shenyang    <==  覆盖值,并返回原有的值
"beijingshanghai"
qianfeng01:6379[1]> get num
"-5"
qianfeng01:6379[1]> incr num                <==  自增+1
(integer) -4
qianfeng01:6379[1]> INCRBY num 2           <==  指定自增的步长
(integer) -2
qianfeng01:6379[1]> mset username zhangsan age 23 gender f isMarry false    <==  设置多对KV
OK
qianfeng01:6379[1]> mget username age gender isMarry num     <== 获取多个Key的value值
1) "zhangsan"
2) "23"
3) "f"
4) "false"
5) "-2"

3.1.1.3 代码演示

package com.qf.redis.day01;import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;import java.util.List;/*** 练习Redis的字符串类型的API*/
public class RedisDemo03_String {public static void main(String[] args) {getOfString();}//set命令的测试public static void setOfString(){Jedis jedis = new Jedis("qianfeng01", 6379);jedis.auth("123123");jedis.set("classno","sz2103");SetParams setParams = new SetParams();setParams.ex(100); //设置存活时间,单位是秒//setParams.px(50000);  //设置存活时间,单位是毫秒jedis.set("teacher","michael",setParams);jedis.setnx("teacher1","anjinglaoshi");jedis.append("city"," is good");jedis.incr("num");jedis.incrBy("num",3);jedis.mset("k1","100","k2","200");jedis.close();}//get命令的测试public static void getOfString(){Jedis jedis = new Jedis("qianfeng01", 6379);jedis.auth("123123");String classno = jedis.get("classno");System.out.println("classno:"+classno);String teacher = jedis.get("teacher");System.out.println("teacher:"+teacher);Long teacherTTl = jedis.ttl("teacher");System.out.println("teacherTTL:"+teacherTTl);String teacher1 = jedis.get("teacher1");System.out.println("teacher1:"+teacher1);String city = jedis.get("city");System.out.println("city:"+city);String num = jedis.get("num");System.out.println("num:"+num);List<String> values = jedis.mget("k1", "k2");for (String value : values) {System.out.println(value);}jedis.close();}
}

3.1.2 Hashes类型

3.1.2.1 介绍

如果将内存对象转成json字符串,再序列化成字节数组,保存到redis中,可以。但是如果涉及到修改这个对象的某一个属性时,其他属性不变,会需要先反序列化,然后修改后,再序列化保存。浪费性能。因此redis,提供了hash类型,来保存类似于对象的多个属性与值的数据结构。

如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yk0oG83C-1648391186311)(ClassNotes.assets/hash数据类型.png)]

3.1.2.2 命令行常用指令

# hset命令的语法格式:hset key field valuehmset key field value [field value ...]
# hget命令的语法格式:hget key fieldhmget key field [field ...]
qianfeng01:6379> hset people username lucy          <== 设置people的姓名字段的值
(integer) 1
qianfeng01:6379> hmset people age 24 gender m isMarry true   <== 设置people的其他多个字段的值
OK
qianfeng01:6379> hget people username               <== 获取people的姓名字段的值
"lucy"
qianfeng01:6379> hmget people username age gender isMarry    <== 获取people的多个字段的值
1) "lucy"
2) "24"
3) "m"
4) "true"
qianfeng01:6379> hdel people username isMarry   <== 删除people的多个字段
(integer) 2
qianfeng01:6379> hgetall people    <== 获取people的所有字段和值
1) "age"
2) "24"
3) "gender"
4) "m"
qianfeng01:6379> HEXISTS people gender   <== 判断gender字段是否存在, 1表示存在, 0表示不存在
(integer) 1
qianfeng01:6379> HEXISTS people isMarry
(integer) 0
qianfeng01:6379> HINCRBY people age 4   <==  对整数类型的field进行增加 数字
(integer) 28
qianfeng01:6379> hkeys people   <==  获取hash类型的所有的fields
1) "age"
2) "gender"
qianfeng01:6379> HLEN people    <==  获取hash类型的fields的个数
(integer) 2
qianfeng01:6379> hsetnx people isMarry false   <==  设置字段的值,如果不存在,就成功,如果存在,就报错
(integer) 1
qianfeng01:6379> hsetnx people isMarry true    <==  0表示不成功,1表示成功
(integer) 0
qianfeng01:6379> hget people isMarry
"false"
qianfeng01:6379> HSTRLEN people isMarry        <==  获取指定的field的value的字符串长度
(integer) 5
qianfeng01:6379> hvals people              <==  获取hash类型的所有的value
1) "28"
2) "m"
3) "false"

3.1.2.3 代码演示

应用场景:适合存储商品信息,商品id、商品名称、商品描述、商品库存、商品好评

比如存储商品1001的信息到 redis中

先确定key为:product_clothing_1001

package com.qf.redis.day01;import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;/*** 应用场景:适合存储商品信息,商品id、商品名称、商品描述、商品库存、商品好评** 比如存储商品1001的信息到 redis中** 先确定key为:product_clothing_1001*/
public class RedisDemo04_Hash {public static void main(String[] args){hgetOfHash();}//hsetpublic static void hsetOfHash(){Jedis jedis = new Jedis("qianfeng01", 6379);jedis.auth("123123");/***  hset(String key,Map<String,String> map)*  hset(String key,String field,String value)*  hmset(String key,Map<String,String> map)**/Map<String,String> map = new HashMap<>();map.put("product_id","1001");map.put("product_name","thinkpadT760");map.put("product_desc","I11,内存16G, 固态硬盘,黑色,经典,小红帽等");map.put("product_warehouse","901");map.put("product_evaluation","这个系列非常好,性能高");jedis.hmset("product_clothing_1001",map);jedis.close();}//hgetpublic static void hgetOfHash(){Jedis jedis = new Jedis("qianfeng01", 6379);jedis.auth("123123");if(jedis.exists("product_clothing_1001")){Map<String,String> map = jedis.hgetAll("product_clothing_1001");Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> entry : entries) {System.out.println(entry.getKey()+"\t"+entry.getValue());}}jedis.close();}}

3.1.3 List 列表

3.1.3.1 介绍

1. List是有序可重复的集合
2. redis采用的LinkedList这个数据结构
3. redis存储list类型可以实现队列和堆栈,队列是先进先出,而堆栈是先进后出。

3.1.3.2 命令行常用指令

127.0.0.1:6379> lpush name gaoyuanyuan jiajingwen lucy    <== 从队列的左边添加元素, 模拟的是栈结构
(integer) 3
127.0.0.1:6379> LRANGE name  0 2               <== 查看队列,  元素下标从0开始
1) "lucy"
2) "jiajingwen"
3) "gaoyuanyuan"
127.0.0.1:6379> rpush citys shenzhen guangzhou changchun nanjing   <== 从队列的右边添加元素,就是队列,FIFO
(integer) 4
127.0.0.1:6379> lrange citys 0 3
1) "shenzhen"
2) "guangzhou"
3) "changchun"
4) "nanjing"
127.0.0.1:6379> lindex citys 1     <== 返回执行下标上的元素
"guangzhou"
127.0.0.1:6379> linsert citys after guangzhou gg   <== 在guangzhou这个元素之后插入一个元素 gg
(integer) 5
127.0.0.1:6379> lrange citys 0 5
1) "shenzhen"
2) "guangzhou"
3) "gg"
4) "changchun"
5) "nanjing"
127.0.0.1:6379> llen citys   <== 查看元素个数
(integer) 5
127.0.0.1:6379> lpop citys  <== 从左边弹出一个元素
"shenzhen"
127.0.0.1:6379> llen citys
(integer) 4
127.0.0.1:6379> lrange citys 0 10
1) "guangzhou"
2) "gg"
3) "changchun"
4) "nanjing"
127.0.0.1:6379> lpush provinces liaoning guangdong liaoning beijing shanghai liaoning
(integer) 6
127.0.0.1:6379> lrange provinces 0 5
1) "liaoning"
2) "shanghai"
3) "beijing"
4) "liaoning"
5) "guangdong"
6) "liaoning"
127.0.0.1:6379> lrem provinces 1 liaoning  <== count>0  表示从左边移除相应个数的value
(integer) 1
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "beijing"
3) "liaoning"
4) "guangdong"
5) "liaoning"
127.0.0.1:6379> lrem provinces -1 liaoning  <== count<0  表示从右边移除相应个数的value
(integer) 1
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "beijing"
3) "liaoning"
4) "guangdong"
127.0.0.1:6379> rpush provinces liaoning liaoning
(integer) 6
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "beijing"
3) "liaoning"
4) "guangdong"
5) "liaoning"
6) "liaoning"
127.0.0.1:6379> lrem provinces 0 liaoning   <== count=0  表示移除所有的value
(integer) 3
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "beijing"
3) "guangdong"
127.0.0.1:6379> lset provinces 1 shoudu    <== 修改指定下标上的元素
OK
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "shoudu"
3) "guangdong"
127.0.0.1:6379> ltrim provinces 0 1   <==  截取指定范围的子元素
OK
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
2) "shoudu"
127.0.0.1:6379> rpop provinces     <== 从右边弹出一个元素
"shoudu"
127.0.0.1:6379> lrange provinces 0 5
1) "shanghai"
127.0.0.1:6379>

3.1.3.3 代码案例演示

需求

模拟两个队列:task-queue1,用于接收用户发送过来的请求任务task-queue2,接收task-queue1弹出的任务,并进行执行,如果任务失败,则重回到task-queue1里,等待被执行

代码如下:

package com.qf.redis.day02;import redis.clients.jedis.Jedis;import java.util.List;
import java.util.Random;
import java.util.UUID;/*** 模拟两个队列:*      task-queue1,用于接收用户发送过来的请求任务*        task-queue2,接收task-queue1弹出的任务,并进行执行,如果任务失败,则重回到task-queue1里,等待被执行*/
public class RedisDemo01_List {public static void main(String[] args) {//第一个线程,用来模拟向队列task-queue1里添加任务Thread t1 = new Thread(){public void run(){try {Jedis jedis = new Jedis("qianfeng01",6379);while (true){// 让线程阻塞2到3秒的时间Thread.sleep((2+(int)(Math.random()*2))*1000);//获取任务IDString tid = UUID.randomUUID().toString();jedis.lpush("task-queue3",tid);}} catch (InterruptedException e) {e.printStackTrace();}}};//第二个线程,接收task-queue1弹出的任务,并进行执行,如果任务失败,则重回到task-queue1里,等待被执行Thread t2 = new Thread(){public void run(){try{Jedis jedis = new Jedis("qianfeng01",6379);while(true){Thread.sleep(4000);List<String> lrange = jedis.lrange("task-queue3", 0, 10);System.out.println("队列一种的所有任务ID:"+lrange);String taskId = jedis.rpoplpush("task-queue3", "task-queue4");List<String> lrange1 = jedis.lrange("task-queue4", 0, 10);System.out.println("队列二中的所有任务ID:"+lrange1);Random r = new Random();if (r.nextInt(19) % 9 == 0) { // 任务处理失败,需要把任务信息从队列2里弹出来再放到任务队列1里等待继续消费jedis.rpoplpush("task-queue4", "task-queue3");System.out.println("任务处理失败:" + taskId);}else {//任务执行成功,则直接弹出。jedis.rpop("task-queue4");System.out.println("任务处理成功:" + taskId);}}} catch (Exception e) {e.printStackTrace();}}};t1.start();t2.start();}
}

3.1.4 Set集合

3.1.4.1 介绍

Redis的Set是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 Set 是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 2的32次方 - 1 (4294967295, 每个集合可存储40多亿个成员)。SADD 指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等。

3.1.4.2 命令行常用指令

127.0.0.1:6379> sadd hobbys movie book basketball   <== 添加元素到set集合中
(integer) 3
127.0.0.1:6379> scard hobbys    <== 查看set集合中的元素个数
(integer) 3
127.0.0.1:6379> sadd hobbys1 movie book basketball a b
(integer) 5
127.0.0.1:6379> sdiff hobbys1 hobbys  <== 取两个set的差集
1) "a"
2) "b"
127.0.0.1:6379> sdiffstore hobbys2 hobbys1 hobbys  <== 取两个set的差集并存储到一个新的集合中
(integer) 2
127.0.0.1:6379> smembers hobbys2   <== 查看set集合中的元素
1) "a"
2) "b"
127.0.0.1:6379> sinter hobbys hobbys1    <== 取两个set中的交集元素
1) "book"
2) "basketball"
3) "movie"
127.0.0.1:6379> sismember hobbys basketball   <==判断元素是否在set中,1表示存在,0表示不存在
(integer) 1
127.0.0.1:6379> sismember hobbys ccc
(integer) 0
127.0.0.1:6379> smembers hobbys
1) "book"
2) "basketball"
3) "movie"
127.0.0.1:6379> smembers hobbys1
1) "a"
2) "movie"
3) "basketball"
4) "b"
5) "book"
127.0.0.1:6379> smove hobbys1 hobbys a    <== 将hobbys1里的元素a移动到hobbys中
(integer) 1
127.0.0.1:6379> smembers hobbys
1) "a"
2) "book"
3) "basketball"
4) "movie"
127.0.0.1:6379>
127.0.0.1:6379> spop hobbys 2      <== 弹出两个元素
1) "a"
2) "book"
127.0.0.1:6379> smembers hobbys
1) "basketball"
2) "movie"
127.0.0.1:6379> SRANDMEMBER hobbys 1   <== 随机一个元素
1) "basketball"
127.0.0.1:6379> smembers hobbys1
1) "movie"
2) "basketball"
3) "b"
4) "book"
127.0.0.1:6379> srem hobbys1 book movie  <== 从集合中移除指定的元素
(integer) 2
127.0.0.1:6379> smembers hobbys1
1) "basketball"
2) "b"
127.0.0.1:6379>
127.0.0.1:6379> sadd s1 a b c d e f
(integer) 6
127.0.0.1:6379> sadd s2 a c e g
(integer) 4
127.0.0.1:6379> sunion s1 s2  <== 两个集合做union操作
1) "g"
2) "a"
3) "c"
4) "b"
5) "e"
6) "f"
7) "d"
127.0.0.1:6379> sunionstore s3 s1 s2 <== 两个集合做union操作的结果存到一个新的集合s3中
(integer) 7
127.0.0.1:6379> smembers s3
1) "g"
2) "a"
3) "c"
4) "b"
5) "e"
6) "f"
7) "d"

3.1.4.3 代码演示

package com.qf.redis.day02import java.{lang, util}import redis.clients.jedis.Jedisobject _01TestSet {def main(args: Array[String]): Unit = {var jedis = new Jedis("qianfeng01",6379)//将英雄人物保存到set集合中// jedis.sadd("team","亚瑟","鲁班","妲己")/*  val strings: util.Set[String] = jedis.smembers("team")import  scala.collection.JavaConversions._for(ele <- strings){println(ele)}*///删除 妲己val flag: lang.Boolean = jedis.sismember("team", "妲己")if(flag){jedis.srem("team","妲己")}val strings: util.Set[String] = jedis.smembers("team")import  scala.collection.JavaConversions._for(ele <- strings){println(ele)}jedis.close()}
}

3.1.5 Sorted sets(有序集合 Zset)

3.1.5.1 介绍

Redis 有序集合和set一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。如果分数相同,则按照成员的字典排序规则排序的集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。

3.1.5.2 常用指令

qianfeng01:6379> zadd team 100 zhangsan 90 lisi 99 wangwu     #添加
(integer) 3
qianfeng01:6379> zcard team                 #获取集合的长度
(integer) 3
qianfeng01:6379> zcount team 95 100         #获取指定范围的长度
(integer) 2
qianfeng01:6379> zrange team 0 -1           #查看指定范围的元素  升序查看
1) "lisi"
2) "wangwu"
3) "zhangsan"
qianfeng01:6379> zrevrange team 0 -1        #查看指定范围的元素, 降序查看
1) "zhangsan"
2) "wangwu"
3) "lisi"
qianfeng01:6379> zrange team 0 -1 WITHSCORES     # 查看的时候并带上分数
1) "lisi"
2) "90"
3) "wangwu"
4) "99"
5) "zhangsan"
6) "100"
qianfeng01:6379> ZINCRBY team 2 lisi           #增加指定成员的分数
"92"
qianfeng01:6379> zrange team 0 -1
1) "lisi"
2) "wangwu"
3) "zhangsan"
(integer) 3
qianfeng01:6379> ZREVRANGEBYLEX team + - LIMIT 0 2  #类似zrange查看元素
1) "zhangsan"
2) "wangwu"
qianfeng01:6379> ZRANK team wangwu    # 获取指定元素的排名,升序
(integer) 1
qianfeng01:6379> zadd team 80 lucy
(integer) 1
qianfeng01:6379> zrank team lucy       # 获取指定元素的排名,升序
(integer) 0
qianfeng01:6379> zrank team zhangsan
(integer) 3
qianfeng01:6379> zrevrange team 0 -1  #  降序,查看指定范围的成员
1) "zhangsan"
2) "wangwu"
3) "lisi"
4) "lucy"
qianfeng01:6379> zrevrank team zhangsan   # 降序,获取指定元素的排名
(integer) 0
qianfeng01:6379> zscore team zhangsan   # 获取指定成员的分数
"100"
qianfeng01:6379> zrem team lucy wangwu  # 删除指定元素
(integer) 2
qianfeng01:6379> zrevrange team 0 -1
1) "zhangsan"
2) "lisi"

3.1.5.3 代码演示

1)需求:

定义商品销售排行榜key:itemsRedis.assetssellsort
写入商品销售量,如商品编号1001的销量是9,商品编号1002的销量是10,参考下面:
ZADD itemsRedis.assetssellsort 9 1001 10 1002 9 1003  8 1004 7 1005 11 1006 2 1007 3 1008 3 1009 4 1010 5 1011
求商品销售量的排行榜的前10名ZREVRANGE itemsRedis.assetssellsort 0 9 withscores

2)代码演示

package com.qf.redis.day02import java.{lang, util}import redis.clients.jedis.{Jedis, Tuple}object _02TestZset {def main(args: Array[String]): Unit = {var jedis = new Jedis("qianfeng01",6379)jedis.auth("123123")/*** 向一个有序集合中,添加20件商品的销售量*/jedis.zadd("salesrank",math.floor(math.random*100),"miphone10")jedis.zadd("salesrank",math.floor(math.random*100),"miphone11")jedis.zadd("salesrank",math.floor(math.random*100),"miphone12")jedis.zadd("salesrank",math.floor(math.random*100),"miphone13")jedis.zadd("salesrank",math.floor(math.random*100),"miphone14")jedis.zadd("salesrank",math.floor(math.random*100),"miphone15")jedis.zadd("salesrank",math.floor(math.random*100),"miphone16")jedis.zadd("salesrank",math.floor(math.random*100),"miphone17")jedis.zadd("salesrank",math.floor(math.random*100),"miphone18")jedis.zadd("salesrank",math.floor(math.random*100),"miphone19")jedis.zadd("salesrank",math.floor(math.random*100),"miphone20")jedis.zadd("salesrank",math.floor(math.random*100),"miphone21")jedis.zadd("salesrank",math.floor(math.random*100),"miphone22")//降序,取top10val tuples: util.Set[Tuple] = jedis.zrevrangeWithScores("salesrank", 0, 9)import scala.collection.JavaConversions._for(ele <- tuples){println(ele)}jedis.close()}
}

3.2 Redis的三个新类型

3.2.1 bitmap类型

3.2.1.1 介绍

是redis中的一个“特殊类型”,其实就是一个字符串。但是,我们研究的不是这个字符串本身,而是该字符串对应的字节序列中的各个bit。只不过是一个可以直接操作到它的bit位的特点。    key   -->  字符串 00000000 00000000 00000000 00000000 00000001注意: 1. 初始化的长度,取决于最后一个1所在的字节是第几个字节。    比如最后一个1所在的位置为第20个字节中,则表示该值的bit位有 20*8 =160位。2. 如果初始化的长度悦长,则越慢,因为需要的内存越来越多。3. 在统计日活相关的场景中:  日活如果特别大,则相对有优势的,如果日活比较小,没有必要使用bitmap,原因可能没有使用集合更加省内存4. bitmap最大为512MB,即最多有2^32个bit.应用场景: 1. 日活量2. 公司每个员工的签到情况,比如统计每个员工这一年的签到次数。
比如有5000w个用户:  用户id从1到到5000w。使用集合List<Long>来记录这5000w个用户id, 需要多大的内存, 一个long占8个字节  需要 5000w*8个字节, 需要381MB左右的内存如果使用bitmap来记录这5000w个用户(一个具有5000w个bit的字节序列即可)。需要的大小换算:5000w/8/1024/1024 约等于 6M。更省空间但是 如果用户量较少。  比如有10个用户,而ID值最大的用户的ID为10000.如果使用bitmap类型,则需要开辟一个10000个bit的数据结构。  空间换算:10000/8 = 1250个字节
但是如果使用List<Long>集合,只需要存储10个long就可以。  10*8 = 80个字节,因此当用户较少时,使用集合比较合适。

3.2.1.2 常用指令演示

qianfeng01:6379> setbit pv 1001 1      #  设置1002bit上的数字为1
(integer) 0
qianfeng01:6379> getbit pv 1001        #  获取1002bit上的数字
(integer) 1
qianfeng01:6379> setbit pv 900 1        #  设置901bit上的数字为1
(integer) 0
qianfeng01:6379> setbit pv 10000 1       #设置10001bit上的数字为1  ,这个1在1251这个字节上
(integer) 0
qianfeng01:6379> bitcount pv 0 1249      #第1字节到1250个字节上的1的个数
(integer) 2
qianfeng01:6379> bitcount pv 0 1250      #第1字节到1251个字节上的1的个数
(integer) 3

3.2.1.2 案例演示:

共有三個人,    id分別是8987129     8298191   8892198
记录每天的登录情况:  0表示沒有登錄   1表示登录过,  统计周一到周日
#周一:
setbit Monday 8987129 0
setbit Monday 8298191 1
setbit Monday 8892198 1
#周二:
setbit Tuesday 8987129 0
setbit Tuesday 8298191 0
setbit Tuesday 8892198 1
#周三:
setbit Wednesday 8987129 0
setbit Wednesday 8298191 1
setbit Wednesday 8892198 1
#周四:
setbit Thursday 8987129 0
setbit Thursday 8298191 0
setbit Thursday 8892198 0
#周五:
setbit Friday 8987129 0
setbit Friday 8298191 1
setbit Friday 8892198 1
#周六:
setbit Saturday 8987129 0
setbit Saturday 8298191 1
setbit Saturday 8892198 0
#周日:
setbit Sunday 8987129 0
setbit Sunday 8298191 1
setbit Sunday 8892198 0

接下来要计算7天内有登录行为的用户,只需要将周一到周日的值做位或运算就可以了

周1:    .......1......1......0..        bitcount后可以统计周一的日活
周2:    .......0......1......0..         bitcount后可以统计周二的日活
周3:    .......1......1......0..        bitcount后可以统计周三的日活
周4:    .......0......0......0..        bitcount后可以统计周四的日活
周5:    .......1......1......0..        bitcount后可以统计周五的日活
周6:    .......1......0......0..        bitcount后可以统计周六的日活
周7:    .......1......0......0..        bitcount后可以统计周七的日活或运算:周1|周2|周3|周4|周5|周6|周7结果result:.....1.....1....0...   说明8298191和8892198登录过,8987129这个用户没有登录过
然后在bitcount result.  就可以统计这一周的活跃情况。注意:如果想要获取是哪些用户登录过,你需要自行计算1所在的位置,然后根据位置找到对应的用户账户bitop operation rs key1 [key2…]
对key1 key2做opecation并将结果保存在rs上
opecation可以是AND(与) OR(或) NOT(非) XOR(异或)

命令整理:

setbit:   用于设置第几个bit上的数字为0|1                 几: 从0开始
getbit:   用于获取第几个bit上的数字是什么,只能是0或1
bitcount key start end:  用于统计有多少个1  .  start表示第几个byte, end表示第几个bytestart和end可以不加,则统计整个key对应的数据类型里有多少个1.
bitop:  用于计算多个key的and、or、not、xor操作, 计算多个值的位运算。语法:bitop operation rs key1 [key2…]
bitpos key bit start end:  用于统计某一个字节范围中的第一个1的下标start和end的单位都是bytebit只能是1或0

3.2.2 HyperLogLog类型

介绍)

集合:{1,2,4,5,6,4,6,7}
集合去重后的元素: 1,2,4,5,6,7
这个集合的基数:6基数的概念:表示不重复的元素的个数。类似于:select distinct count(1) from emp ..统计不重复的个数。 总结:redis的HyperLogLog类型,用于直接存储对应的value的基数值。

常用指令)

127.0.0.1:6379> pfadd nums 1 2 3 4 2 3 5 6 5 6 7   <==添加元素到hyperloglog数据结构中
(integer) 1
127.0.0.1:6379> pfcount nums    <==获取hyperloglog数据结构中的基数
(integer) 7
127.0.0.1:6379> pfadd nums 10 11   <==再次添加元素到hyperloglog数据结构中
(integer) 1
127.0.0.1:6379> pfcount nums   <==获取hyperloglog数据结构中的基数
(integer) 9
127.0.0.1:6379> pfadd nums1 80 90 91     <==添加元素到名字为nums1的hyperloglog数据结构中
(integer) 1
127.0.0.1:6379> pfcount nums1
(integer) 3
127.0.0.1:6379> pfmerge n nums1 nums    <==将两个hyperloglog数据结构合并成一个新的名为n的Hyperloglog类型中
OK
127.0.0.1:6379> pfcount n
代码中的应用场景:
//向list中添加元素
jedis.lpush("list1","1",“2”,"4","5","6","4","6","7")
//想要统计基数。
jedis.pfadd("hyper_list1","1",“2”,"4","5","6","4","6","7")

3.2.3 geospatial类型

用于索引地理空间位置或者是距离的,底层使用的是sorted set,用于保存成员信息1. GEOADD key longitude latitude member [longitude latitude member ...]添加一个或多个地理位置到指定的key中
2. GEODIST key member1 member2 [unit]求指定的key中两个地址位置的距离m 表示单位为米。    默认是mkm 表示单位为千米。mi 表示单位为英里。ft 表示单位为英尺。
3. GEOPOS key member [member ...]   返回某一个地理位置的经纬度
4. GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]   返回key中距离某一个地址位置指定半径范围以内的所有的元素。
5. GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]返回key中距离某一个地址位置指定半径范围以内的所有的元素。

案例演示

127.0.0.1:6379> geoadd citys 116.397128 39.916527 beijing 113.88308 22.55329 shenzhen   <==添加北京和深圳两个地理位置
(integer) 2
127.0.0.1:6379> geodist citys beijing shenzhen km   <== 获取北京和深圳的距离
"1945.7544"
127.0.0.1:6379> geopos citys beijing  <==获取某一个成员的地理位置
1) 1) "116.39712899923324585"2) "39.91652647362980844"
127.0.0.1:6379> geopos citys beijing shezhen
1) 1) "116.39712899923324585"2) "39.91652647362980844"
2) 1) "113.88307839632034302"2) "22.55329111565713873"
127.0.0.1:6379> geoadd citys 121.48941 31.40527 shanghai
(integer) 1
127.0.0.1:6379> GEORADIUS citys 113.88308 22.55329 2000 km  <==计算以深圳为圆心,半径为2000KM的圆内的成员
1) "shenzhen"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> GEORADIUS citys 113.88308 22.55329 1900 km
1) "shenzhen"
2) "shanghai"
127.0.0.1:6379> GEORADIUSBYMEMBER citys shenzhen 1900 km   <==计算以深圳为圆心,半径为1900KM的圆内的成员
1) "shenzhen"
2) "shanghai"

四、Redis的发布订阅

4.1 介绍

Redis通过publish、subscribe和psubcribe、Unsubscribe和punsubscribe等命令实现发布、订阅和退订功能。这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。角色:
-- 客户端通过PUBLISH命令向通道发送信息的时候,我们称这个客户端为发布者(publisher)。
-- 客户端使用SUBSCRIBE或者PSUBSCRIBE命令接收信息的时候,我们称这个客户端为订阅者(subscriber)。为了解耦发布者(publisher)和订阅者(subscriber)之间的关系,Redis 使用了 channel (频道)作为两者的中介 —— 发布者将信息直接发布给 channel ,而 channel 负责将信息发送给适当的订阅者,发布者和订阅者之间没有相互关系,也不知道对方的存在注意:由于redis订阅发布功能不做消息缓存,所以需要订阅者先订阅通道,然后发布者再发布数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qLgxqRd9-1648391186312)(ClassNotes.assets/image-20211102140913095.png)]

4.2 subscribe的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkddFu9a-1648391186312)(ClassNotes.assets/image-20211102142630703.png)]

开启第一个客户端,运行订阅者程序:subscribe news food
开启第二个客户端,运行订阅者程序:subscribe news food
开启第三个客户端,运行发布者程序:publish food apple

4.3 psubscribe的原理

p:是pattern的意思, 指的是模式订阅,比如 订阅k*,n*, ?o*这样的通道。

开启第一个客户端:psubscribe n* f*
开启第二个客户端:subscribe  n* k*
开启第三个客户端:publish food apple        第一个客户端可以收到

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-98JHDdPL-1648391186313)(ClassNotes.assets/image-20211102143841558.png)]

4.4 知识扩展:

字典
①基础知识点:
--Dictionary是存储键和值的集合;
--Dictionary是无序的,键Key是唯一的;
--通过Key找到对应的Value,游戏中通常通过使用ID,ID关联用户的所有信息,从而找到玩家的其他游戏信息。
②优点:查找速度快,能根据键快速查找值。
③缺点:内存开销大。链表
①优点:
--链表在内存存储的排序上不连续,而是靠各对象的指针所决定,添加元素和删除元素比数组优势;
--链表适合在需要有序的排序的情境下增加新的元素,增加新的元素只是若干元素的指向发生变化。
②缺点:其在内存空间中不一定连续排列,访问时无法利用下标,必须从头结点开始,逐次遍历下一个节点直到寻找到目标,不适用于需要快速访问对象的情况。

五、Redis的事务与持久化

5.1 Redis的事务

5.1.1 事务的简介

MULTI、EXEC、DISCARD和WATCH 是 Redis 事务相关的命令。Redis的事务是将一系列待执行的指令存到一个Queue中,并使用exec执行事务。--1. Redis事务是一个独立的隔离机制:事务中的所有命令都会序列化到queue、并按顺序地执行queue里的指令。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。multi: 用于开启事务,将待执行的命令放入到队列中
exec命令:负责触发并执行事务中的所有命令:
discard:  用于取消组队。
watch:监听

5.1.2 事务的基本操作

qianfeng01:6379> multi    #  开启事务
OK
qianfeng01:6379> set k2 v2    #向队列中添加要执行的指令
QUEUED
qianfeng01:6379> set k3 v3
QUEUED
qianfeng01:6379> set k4 v4
QUEUED
qianfeng01:6379> exec     #  执行事务,也就是执行队列中的指令 ,一旦执行了exec指令,表示事务结束
1) OK
2) OK
3) OK
qianfeng01:6379> keys *   # 查看
1) "k3"
2) "k4"
3) "k2"
4) "k1"qianfeng01:6379> multi     #开启事务
OK
qianfeng01:6379> set k11 v11
QUEUED
qianfeng01:6379> set k12 v12
QUEUED
qianfeng01:6379> discard   #退出组队, 也可以理解为退出事务,即结束事务
OK
qianfeng01:6379> exec      #因为没有事务,所以执行报错。
(error) ERR EXEC without MULTI

5.1.3 事务出错的情况

错误情况1:当开启事务,组队出错,  调用exec时,直接报错,不执行。
错误情况2:当开启事务,组队没问题, exec时错误,不影响后续的指令的执行。

参考案例:

127.0.0.1:6379> multi    <==开启事务
OK
127.0.0.1:6379> set a1 zhangsan
QUEUED
127.0.0.1:6379> set a2 lisi
QUEUED
127.0.0.1:6379> set a3     <==语法错误,组队出错
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set a4 zhaoliu
QUEUED
127.0.0.1:6379> exec      <==调用exec时,直接报错,不执行。
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *    <== 没有a1,a2,a4
1) "n4"
2) "n3"
3) "n2"
4) "n1"
127.0.0.1:6379> multi      <==开启事务
OK
127.0.0.1:6379> set a1 zhangsan
QUEUED
127.0.0.1:6379> INCR a1     <==语法没报错,但是a1对应的value不是整形,不能+1
QUEUED
127.0.0.1:6379> set a2 lisi
QUEUED
127.0.0.1:6379> exec    <==调用exec时,有错误,但是不影响后续的指令的执行。
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> keys *   <== 有a1,a2
1) "a1"
2) "n3"
3) "n2"
4) "a2"
5) "n4"
6) "n1"

5.1.4 悲观锁和乐观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,当使用者每次在使用临界资源的时候都认为临界资源可能正在被其他人修改,所以每次在使用该临界资源时都要上锁,这样的话,当别人想用这个临界资源时,就会被阻塞,直到正在使用的人释放了锁,别人获取到锁,别人才可以上锁,才继续干活。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。第一个线程:int balance= 5000  v1.0............balance-=4000      v1.1第二个线程:int balance= 5000 v1.0............balance-=3000    v1.2      check-and-set机制(乐观锁)乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新过这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

5.1.5 乐观锁的演示

watch key [key]   作用:监听key开启第一个窗口:watch balancemultidecrby balance 4000开启第二个窗口:watch balancemultidecrby balance 3000然后两个窗口执行事务,会发现,一个执行成功,另一个执行失败(nil)

5.1.6 总结

1. 单独的隔离操作 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2. 没有隔离级别的概念 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
3. 不保证原子性 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚 redis事务+乐观锁的应用场景:商品秒杀、抢票等

5.2 Redis的持久化

redis虽然是基于内存的,但是也可以选择性的将数据持久化到磁盘上的,以保证数据的安全性(万一宕机了,岂不是损失大了)。而redis的持久化方式有两种,一种是RDB方式,一种是AOF方式redis4.0.14 默认是RDB持久化机制

5.2.1 RDB持久化

1)原理

RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTGK5NBa-1648391186313)(ClassNotes.assets/image-20211102154925985.png)]

2)参数

# 设置保存机制,    第二个参数表示保存的时间范围,第三个参数表示在时间段内发生了多少次key的更改
save 900 1
save 300 10
save 60 10000#指定存储位置,可以写绝对路径
dir ./
#指定存储的文件名
dbfilename dump.rdb   # 当save失败时,则停止写操作
stop-writes-on-bgsave-error yes
# 是否开启压缩,算法LZF
rdbcompression yes
# 是否开启检查机制,比较消耗性能,不建议开启
rdbchecksum yes

3)如何触发RDB快照

# 满足以下配置时会触发
save 900 1
save 300 10
save 60 10000# 主动调用save指令    前台执行持久化操作, 会影响前台做其他写操作, 是一个同步指令
# 主动调用bgsave指令   开启持久化操作,后台执行,不影响前台执行,  是一个异步指令

4)如何禁用RDB快照

方式1:注释掉所有的save行
方式2:save ""

5)如何备份数据

只需要将相关的dump.rdb文件保存到你喜欢的目录下。如果宕机了,就使用一个新的机器加载这个文件即可。
注意:只需要在文件所在的目录下启动服务,会主动加载对应名字的rdb文件

6)小贴士

这种方式,如果宕机了,会丢失上次快照到宕机时这个时间段的新数据

5.2.2 AOF持久化

1)原理

aof的全称:append only file,  保存的是客户端的写操作(set)
aof是默认不开启的,需要手动设置,设置也非常简单,只需要将对应的no改为yes即可.相应的文件就会保存到和dump.rdb同一个目录下了(dir参数指定的目录)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iwf8wZFY-1648391186313)(ClassNotes.assets/image-20211102165848046.png)]

2)参数

# 开启aof持久化机制
appendonly yes
# 指定生成的文件名
appendfilename "appendonly.aof"# aof的持久化策略: 只能选择一个,默认的是每秒
# 只要发生set操作,就持久化
#appendfsync always
# 每秒持久化一次
appendfsync everysec
# 关闭自动同步策略,改为手动
#appendfsync no#重写机制的条件,100指的是当文件大于min-size的100%,即128mb的时候,开启重写机制
auto-aof-rewrite-percentage 100
#指定重写机制的最小字节数
auto-aof-rewrite-min-size 64mb

3)aof文件的修复

redis-check-aof --fix appendonly.aof

5.2.3 总结

1. 当两种持久化机制都开启时,redis服务会默认加载aof的文件,而不加载rdb文件。
2. RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
3. AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.
4. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.   set k1 v1  set k1 v2  重写后只保存set k1 v2
5. 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
6. 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

六、Redis的主从复制和集群

6.1 Redis的主从复制

6.1.1简介

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。主从复制的作用主要包括:数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BDEEARBW-1648391186314)(ClassNotes.assets/image-20220217093301288.png)]

6.1.2 搭建步驟

1)说明

1. 如果有密碼策略,那麽从节点需要主节点的密码,如果想要启动哨兵机制,也需要密码策略,演示的时候去掉密码。2. 正常情况下,master和slave应该分布在不同的机器上。演示的时候在同一台机器也可以。只要区分开配置文件和port以及持久化的位置即可。

2)准备工作

设定目录:    /usr/local/redismaster/6379/usr/local/redismaster/6380/usr/local/redismaster/6381一个master:    redis_6379.conf
两个slave:     redis_6380.conf redis_6381.conf

3)搭建步骤

步骤1: 维护目录

[root@qianfeng01 ~]# mkdir -p /usr/local/redismaster/{6379,6380,6381}

步骤2)维护配置文件

[root@qianfeng01 redismaster]# cp /usr/local/redis/redis.conf 6379/redis_6379.conf

修改这些属性:

#端口号
port 6379#pid文件的位置:redis_6379.pid redis_6380.pid redis_6381.pid
pidfile "/var/run/redis_6379.pid"# rdb持久化的位置
dir "/usr/local/redismaster/6379"# 关掉密码
# requiredpass#设置后台启动
daemonize yes

拷贝文件到另外两个目录下,同时更名

[root@qianfeng01 6379]# cp redis_6379.conf ../6380/redis_6380.conf
[root@qianfeng01 6379]# cp redis_6379.conf ../6381/redis_6381.conf

别忘记修改相关属性

port、pidfile、dir、daemonize

步骤3)启动三个服务

[root@qianfeng01 redismaster]# redis-server ./6379/redis_6379.conf
[root@qianfeng01 redismaster]# redis-server ./6380/redis_6380.conf
[root@qianfeng01 redismaster]# redis-server ./6381/redis_6381.conf
#查看进程
[root@qianfeng01 redismaster]# ps -ef | grep redis
root      15758      1  0 17:35 ?        00:00:00 redis-server 192.168.10.101:6379
root      15771      1  0 17:36 ?        00:00:00 redis-server 192.168.10.101:6380
root      15776      1  0 17:36 ?        00:00:00 redis-server 192.168.10.101:6381

步骤4)连接三台服务

[root@qianfeng01 redismaster]# redis-cli -h qianfeng01 -p 6379
[root@qianfeng01 redismaster]# redis-cli -h qianfeng01 -p 6380
[root@qianfeng01 redismaster]# redis-cli -h qianfeng01 -p 6381

步骤5)查看各个服务的状态

info replication正常情况下:看到的都是master

步骤6)配从不配主

在slave机器上
slaveof host port:   host是master的,  port是master的
即:
6380机器上配置:slaveof qianfeng01 6379
6381机器上配置:slaveof qianfeng01 6379小贴士:
slave上只能读,不能写,写的话要在master进行

断开关系

slaveof on one   表示我切断与master的关系。

注意: 上述的配置,master宕机,slave不能自动切换成master,需要手动

6.1.3 扩展:使用哨兵模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KG6nyIsF-1648391186314)(ClassNotes.assets/image-20211102181733384.png)]

步骤1)拷贝一份哨兵配置文件到redismaster目录下

[root@qianfeng ~]# cp /usr/local/redis/sentinel.conf  /usr/local/redismaster/

步骤2)修改配置文件

[root@qianfeng ~]# vim /usr/local/redismaster/sentinel.conf..........找到以下内容 .....................
sentinel monitor mymaster 192.168.10.101   6379             1^           ^             ^            ^代号       master的ip    master的port    哨兵的个数

步骤3)启动哨兵

先恢复你的主从机器,然后启动哨兵,演示自动切换吧

[root@qianfeng ~]# redis-sentinel /usr/local/redismaster/sentinel.conf

6.2 Redis的集群

6.2.1 简介

1. Redis的集群模式,指的是有多个Master,注意:Master可以有0到多个slave。
2. 集群节点之间的通信机制:ping-pong通信
3. 客户端在连接集群时,如果是写操作,只需要连接上任意一个master即可,如果是读操作,只需要连接集群上的任意一个节点即可(slave或者master)
4. 数据存在集群中的槽中,槽的数量是固定的,16384个。槽是有编号的:0-16383,数据调用key与16384取余,余数就是槽号。    5. 集群的不可用情况:-- 半数以上的master宕机,集群不可用-- master和对应的slave都宕机了,集群不可用注意:--槽的编号之间不能断开。--槽的计算会将数据保存的很平均,不会产生一个槽满一个槽空的情况。生产环境中,每个master至少要有一个slave。

6.2.2 集群搭建

1)说明

1. redis在搭建集群时,版本不同所需要的环境不同。
2. redis4.x版本,需要ruby环境高于2.2.2      # ruby -v  查询ruby的版本redis5.x版本,内置了ruby,不需要操作ruby环境redis3.0版本,对ruby版本环境没要求   基于上述情况:我们用redis3.0.6来演示redis集群的搭建,  我在一台机器上模拟六个redis实例,三个master,各对应一个slave

2)搭建步骤

步骤0)安装ruby

yum install ruby rubygems -y

步骤1)上传、解压、编译、安装维护六个实例的目录

[root@qianfeng01 ~]# tar -zxvf redis-3.0.6.tar.gz -C /usr/local/
[root@qianfeng01 ~]# cd /usr/local/redis-3.0.6
[root@qianfeng01 redis-3.0.6]# make && make install PREFIX=/usr/local/redis-3.0.6
[root@qianfeng01 ~]# cd ..
[root@qianfeng01 local]# mkdir -p rediscluster/{7001,7002,7003,7004,7005,7006}

步骤2)拷贝文件到六个目录下

先拷贝一个配置文件,修改下面的参数
[root@qianfeng01 local]# cp ./redis-3.0.6/redis.conf rediscluster/7001/
#启动后台进程
daemonize yes
#修改pid文件的位置
pidfile /var/run/redis_7001.pid
#端口号
port 7001
#持久化的位置
dir /usr/local/rediscluster/7001
# 开启aof
appendonly yes#解开注释,启用集群模式
cluster-enabled yes#集群的配置文件
cluster-config-file nodes.conf#集群的连接超时时间
cluster-node-timeout 5000

再复制修改

[....local]# cp rediscluster/7001/redis.conf ./rediscluster/7002/redis.conf
[....local]# vim ./rediscluster/7002/redis.conf[....local]# cp rediscluster/7001/redis.conf ./rediscluster/7003/redis.conf
[....local]# vim ./rediscluster/7003/redis.conf[....local]# cp rediscluster/7001/redis.conf ./rediscluster/7004/redis.conf
[....local]# vim ./rediscluster/7004/redis.conf[....local]# cp rediscluster/7001/redis.conf ./rediscluster/7005/redis.conf
[....local]# vim ./rediscluster/7005/redis.conf[....local]# cp rediscluster/7001/redis.conf ./rediscluster/7006/redis.conf
[....local]# vim ./rediscluster/7006/redis.conf

步骤3)启动六个实例

[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7001/redis.conf
[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7002/redis.conf
[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7003/redis.conf
[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7004/redis.conf
[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7005/redis.conf
[root@qianfeng01 redis-3.0.6]# ./bin/redis-server ../rediscluster/7006/redis.conf#检查进程
[root@qianfeng01 redis-3.0.6]# ps -ef| grep redis
root       5775      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7001
root       5799      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7002
root       5803      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7003
root       5807      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7004
root       5811      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7005
root       5815      1  0 09:55 ?        00:00:00 ./bin/redis-server *:7006

注意:

启动后,不建议大家使用redis-cli去连接集群,查看信息。

步骤4)安装redis与ruby之间的一个打包工具:gem插件

[root@qianfeng01 redis-3.0.6]# gem install -l ~/redis-3.0.0.gem

步骤5)拷贝redis的管理命令到/usr/local/bin下,目的就是可以直接写命令名

[root@qianfeng01 redis-3.0.6]# cp /usr/local/redis-3.0.6/src/redis-trib.rb /usr/local/bin/

步骤6)使用上面拷贝的指令创建集群:就是关联实例

redis-trib.rb create --replicas 1 192.168.10.101:7001 192.168.10.101:7002 192.168.10.101:7003 192.168.10.101:7004 192.168.10.101:7005 192.168.10.101:7006 别忘记输入yes

-------------------到此为止,安装成功,愉快的玩耍吧----------------------

连接集群客户端操作:

redis-cli -c  -p 7001     注释:-c 表示连接集群中的某一个节点

如果安装过程中出错了,怎么办

1. 找到配置的位置,修改相应的错误地方
2. 关闭或杀死各个实例
3. 删除各个实例里的持久化文件(dump.rdb,appendonly.aof)和集群配置文件(nodes.conf)
4. 重启各个实例的服务器
5. 再次创建关联。

6.2.3 代码演示

package com.qf.redis.day01import java.utilimport redis.clients.jedis.{HostAndPort, JedisCluster}object _08ClusterConnectDemo {def main(args: Array[String]): Unit = {//创建一个集群,存储集群的实例val sets = new util.HashSet[HostAndPort]()sets.add(new HostAndPort("qianfeng01",7001))sets.add(new HostAndPort("qianfeng01",7002))sets.add(new HostAndPort("qianfeng01",7003))//创建JedisCluster对象, 就是一个客户端val cluster = new JedisCluster(sets)//设置kv对象cluster.set("user1","zhangdasan")cluster.hset("user2","username","zhangxiaosan")cluster.lpush("scores","10","20","30")cluster.sadd("names","xiaozhang","xiaohei","xiaohong")cluster.close()}
}

qianfeng01 redis-3.0.6]# ps -ef| grep redis
root 5775 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7001
root 5799 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7002
root 5803 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7003
root 5807 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7004
root 5811 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7005
root 5815 1 0 09:55 ? 00:00:00 ./bin/redis-server *:7006


注意:

启动后,不建议大家使用redis-cli去连接集群,查看信息。


步骤4)安装redis与ruby之间的一个打包工具:gem插件```shell
[root@qianfeng01 redis-3.0.6]# gem install -l ~/redis-3.0.0.gem

步骤5)拷贝redis的管理命令到/usr/local/bin下,目的就是可以直接写命令名

[root@qianfeng01 redis-3.0.6]# cp /usr/local/redis-3.0.6/src/redis-trib.rb /usr/local/bin/

步骤6)使用上面拷贝的指令创建集群:就是关联实例

redis-trib.rb create --replicas 1 192.168.10.101:7001 192.168.10.101:7002 192.168.10.101:7003 192.168.10.101:7004 192.168.10.101:7005 192.168.10.101:7006 别忘记输入yes

-------------------到此为止,安装成功,愉快的玩耍吧----------------------

连接集群客户端操作:

redis-cli -c  -p 7001     注释:-c 表示连接集群中的某一个节点

如果安装过程中出错了,怎么办

1. 找到配置的位置,修改相应的错误地方
2. 关闭或杀死各个实例
3. 删除各个实例里的持久化文件(dump.rdb,appendonly.aof)和集群配置文件(nodes.conf)
4. 重启各个实例的服务器
5. 再次创建关联。

6.2.3 代码演示

package com.qf.redis.day01import java.utilimport redis.clients.jedis.{HostAndPort, JedisCluster}object _08ClusterConnectDemo {def main(args: Array[String]): Unit = {//创建一个集群,存储集群的实例val sets = new util.HashSet[HostAndPort]()sets.add(new HostAndPort("qianfeng01",7001))sets.add(new HostAndPort("qianfeng01",7002))sets.add(new HostAndPort("qianfeng01",7003))//创建JedisCluster对象, 就是一个客户端val cluster = new JedisCluster(sets)//设置kv对象cluster.set("user1","zhangdasan")cluster.hset("user2","username","zhangxiaosan")cluster.lpush("scores","10","20","30")cluster.sadd("names","xiaozhang","xiaohei","xiaohong")cluster.close()}
}

Redis知识总结(四万字)相关推荐

  1. 【NLP】四万字全面详解 | 深度学习中的注意力机制(四,完结篇)

    作者 | 蘑菇先生 知乎 | 蘑菇先生学习记 深度学习Attention小综述系列: 四万字全面详解 | 深度学习中的注意力机制(一) 四万字全面详解 | 深度学习中的注意力机制(二) 四万字全面详解 ...

  2. 【NLP】四万字全面详解 | 深度学习中的注意力机制(三)

    NewBeeNLP原创出品 公众号专栏作者@蘑菇先生 知乎 | 蘑菇先生学习记 深度学习Attenion小综述系列: 四万字全面详解 | 深度学习中的注意力机制(一) 四万字全面详解 | 深度学习中的 ...

  3. 【NLP】四万字全面详解 | 深度学习中的注意力机制(二)

    NewBeeNLP原创出品 公众号专栏作者@蘑菇先生 知乎 | 蘑菇先生学习记  前情提要:四万字全面详解 | 深度学习中的注意力机制(一) 目前深度学习中热点之一就是注意力机制(Attention ...

  4. [Python从零到壹] 十三.机器学习之聚类算法四万字总结全网首发(K-Means、BIRCH、树状聚类、MeanShift)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  5. [Python从零到壹] 八.数据库之MySQL和Sqlite基础知识及操作万字详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  6. 硬盘知识大杂烩(四)

    硬盘知识大杂烩(四)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&g ...

  7. Redis数据库(四)——Redis集群模式(主从复制、哨兵、Cluster)

    Redis数据库(四)--Redis集群模式(主从复制.哨兵.Cluster) 一.Redis主从复制 1.主从复制流程 二.哨兵模式 1.哨兵模式集群架构 2.哨兵模式主要功能 3.哨兵监控整个系统 ...

  8. Redis系列(十四)、Redis6新特性之RESP3与客户端缓存(Client side caching)

    Redis6引入新的RESP3协议,并以此为基础加入了客户端缓存的新特性,在此特性下,大大提高了应用程序的响应速度,并降低了数据库的压力,本篇就带大家来看一下Redis6的新特性:客户端缓存. 目录 ...

  9. HTML/CSS基础知识(四)

    WEB标准和W3C的理解与认识 Web标准是一系列标准的集合. 网页主要由三部分组成:结构(Structure).表现(Presentation)和行为(Behavior). 对应的标准也分三方面:结 ...

  10. Redis简单案例(四) Session的管理

    Redis简单案例(四) Session的管理 原文:Redis简单案例(四) Session的管理 负载均衡,这应该是一个永恒的话题,也是一个十分重要的话题.毕竟当网站成长到一定程度,访问量自然也是 ...

最新文章

  1. 349.两个数组的交集
  2. python创建文件夹命令_python文件操作指令
  3. 安装sql server 2016 always on配置dtc支持时遇到的问题
  4. java安全编码指南之:序列化Serialization
  5. c#中使用消息循环机制发送接收字符串的方法和数据类型转换
  6. 解读 Microsoft.NET.Sdk 的源码,你能定制各种奇怪而富有创意的编译过程
  7. 教你如何创建一款属于自己的VSCode主题
  8. 《TensorFlow技术解析与实战》——第3章 可视化TensorFlow
  9. 总算知道怎样从ImageMagick生成的数据转换成HICON: MagickGetImageBlob LookupIconIdFromDirectoryEx...
  10. 数据结构 红黑树(RBTree)的原理与实现
  11. 主从模式在不同场景下的解释
  12. MapTask工作机制图解
  13. Windows网络编程笔记5 -- 其他套接字
  14. 数据结构-带头双向循环链表
  15. Python实现离散Radon变换
  16. Pycocotools 报error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Bui
  17. SpringMVC文件上传和Jwt身份验证
  18. 基于Java Web考生评分系统设计实现毕业设计源码071114
  19. reverse()函数
  20. 折腾笔记--Linux和Windows10双系统安装的一般步骤(通用)(物理机安装)

热门文章

  1. 解决鼠标右键特别慢的方法
  2. QQ防红跳转短网址生成网站源码(91she完整源码)
  3. 描述性统计分析 | 直方图
  4. JS实现元素拖拽,简单悬浮框实现
  5. 人生就像一张茶几,摆满了各种杯具/洗具/餐具!
  6. logback日志配置详解
  7. 有关c基础指针需要注意的几个点!
  8. 三星会在泰泽大会上展示meego系统的新机么?
  9. 移动端环境搭建--2
  10. java红宝石是哪本_AES Java编码,红宝石解码