Redis(上)

1、NoSql

  • Not only sql:不仅只有数据库。
  • 关系型数据库已经不能满足大数据时代的数据处理需求,所以非关系型数据库出现了。

四大分类

  • 键值对储存:Redis
  • 文档型数据库:MongoDB,基于分布式文件存储的数据库,主要用来处理大量文档。
  • 列存储:Hbase
  • 图关系数据库,储存关系,不是储存图片。

2、基础知识

  • Redis:远程字典服务,key-value数据库。
  • Redis有16个数据库,默认使用第0个,
  • 单线程,基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽。
ping			#检测是否连接,连接提示pong
select 3		#切换数据库
dbsize   		#查看数据库大小(有多少数据)
flushdb	 		#清空当前数据库
flushall		#清空所有数据库
keys *	 		#查看所有的key
type name		#查看key的类型
set name lin	#添加数据key=name value=lin,set已存在的key将覆盖value
get name		#查看name的value
exists name		#检测name是否存在
move name 1		#移除name
del name		#移除键值对
expire name 10	#设置name过期时间为10秒
ttl name		#查看剩余过期时间,等于-2时为移除键值对
clear			#清屏

1、Redis为什么这么快?

  • a.基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以它的性能比较高。

  • b.数据结构简单:Redis的数据结构比较简单,是为Redis专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是O(1)。

  • c.多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接的客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了IO阻塞操作,从而大大提高了Redis的性能。

  • d.避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生。

2、IO多路复用是什么?

  • 套接字的读写方法默认是阻塞的,例如当调用读取操作read方法时,缓冲区没有任何数据,那么这个线程会卡在这里,直到缓冲区有数据或者连接被关闭时,read方法才会返回,该线程才能继续处理其他业务。

  • 但这样显然就降低了程序的执行效率,而Redis使用的时非阻塞的IO,这就意味着IO的读写流程不再是阻塞的,读写方法都是瞬间完成并且返回的,也就是它会采用能读多少就读多少、能写多少就写多少的策略来执行IO操作,这显然更符合我们对性能的追求。

  • 但这种非阻塞的IO也面临一个问题,那就是当我们执行读取操作时,有可能只读取了一部分数据;同理写数据也是这种情况,当缓冲区满了,而我们的数据还没有写完,那么生效的数据何时写就成了一个问题。

  • 而IO的多路复用就是解决上面的这个问题的,使用IO多路复用最简单的方式就是使用select函数,此函数是操作系统提供给用户程序的API接口,用于监控多个文件描述符的可读和可写情况的,这样就可以监控到文件描述符的读写事件了。当监控到相应的时间之后就可以通知线程处理相应的业务了,这样就保证了Redis读写功能的正常执行。

  • 【不过现在的操作系统已经基本上不适用select函数了,改为调用epoll函数(Linux)了,macOS则是使用Kqueue(继承与Unix),因为select函数在文件描述符非常多的时候性能非常差。】

3、Redis6.0中的多线程?

  • Redis单线程的优点非常,不但降低了Redis内部实现的负责性,也让所有操作都可以在无锁的情况下进行,并且不存在死锁和线程切换带来的性能以及时间上的消耗;但是其缺点也很明显,单线程机制导致Redis的QPS(Query Per Second,每秒查询数)很难得到有效的提高(虽然够快了,但人毕竟还是要有更高的追求的)。

  • Redis在4.0版本中虽然引入了多线程,但是此版本的多线程只能用于大数据量的异步删除,对于非删除操作的意义并不是很大。

  • 如果我们使用Redis多线程就可以分摊Redis同步读写IO的压力,以及充分利用多核CPU资源,并且可以有效的提升Redis的QPS。在Redis中虽然使用了IO多路复用,并且是基于非阻塞的IO进行操作的,但是IO的读写本身是阻塞的。比如当socket中有数据时,Redis会先将数据从内核态空间拷贝到用户态空间,然后再进行相关操作,而这个拷贝过程是阻塞的,并且当数据量越大时拷贝所需要的的时间就越多,而这些操作都是基于单线程完成的。

  • 因此在Redis6.0中新增了多线程的功能来提高IO的读写性能,它的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使用多个socket的读写并行化了,但Redis的命令依旧是主线程串行执行的。

  • 但是注意:Redis6.0是默认禁用多线程的,但可以通过配置文件redis.conf中的io-threads-do-reads 等于 true 来开启。但是还不够,除此之外我们还需要设置线程的数量才能正确地开启多线程的功能,同样是修改Redis的配置,例如设置 io-threads 4,表示开启4个线程。

3、基本数据类型

命令可以去官方文档查询:https://redis.io/commands

中文文档:http://www.redis.cn/commands.html

1、字符串(String)

append name jq		#追加字符串,如果不存在则相当于set
strlen name			#返回字符串长度
incr views			#value+1,非整数报错
decr views			#value-1,可以为负数,非整数报错
incrby views 10		#value+10,非整数报错
decrby views 10		#value-10,可以为负数,非整数报错
getrange name 0 3	#截取字符串,下标范围[0,3],包括3
getrange name 0 -1	#获取字符串,相当于get
getrange name -1 -1	#获取最后一个字符
setrange name 1 xx	#从1开始后两位字符改为xx
setex a 10 b		#设置带有过期时间的键值对,key=a,value=b,10秒过期
setnx a b			#检测键是否已存在,已存在set失败返回0
mset a 1 b 2 c 3	#批量添加键值对
mget a b c			#批量获取键值对
msetnx				#原子性操作,一个失败全部失败
set user:name lin	#设置user对象的name=lin,其实还是设置键值对,只是把类和属性一起写在key,值写在value
getset name lin		#先get key的value,再set key一个新的value

2、散列(hashes)

相当于key-map,在value里存键值对

hset hash name lin			#添加键值对
hget hash name				#获取键值对
hmset hash name lin age 22	#添加多对键值对
hmget hash name age			#获取多对键值对
hgetall hash				#获取所有键值对
hdel hash id				#删除指定键值对
hlen hash					#获取键值对数量
hexists hash name			#判断键值对是否存在
hkeys hash					#获取所有的key
hvals hash					#获取所有的value
hincrby hash id 1			#自增
hincrby hash id -1			#自减
hsetnx hash name lin		#不存在则set,存在则set失败返回0

3、列表(list)

可以存在相同元素,相当于链表,可以做队列和栈

lpush list one					#将one从列表list头部插入
lrange list 0 1					#获取列表list下标范围为[0,1]的元素
rpush list last					#从列表尾部插入
lpop list						#弹出头部元素
rpop list						#弹出尾部元素
lindex list 1					#根据下标获得元素
llen							#列表长度
lremove list 2 one				#从头部开始移除两个one元素,不足两个移除全部one
ltrim list 1 3					#去除下标范围[1,3]以外的元素
rpoplpush list nlist			#从list尾部弹出元素加入到nlist头部
lset list 1 iten				#将下标为1的元素覆盖为item,超出下标范围将报错
linsert list before world other	#将other插入list中的world前面
linsert list after world item	#将item插入list中的world后面

4、集合(set)

元素不能重复,无序

sadd set one		#添加元素
smembers set		#获取所有元素
sismember set one	#判断元素是否存在
scard set			#获取长度
srem set one		#移除元素
srandmenber set		#随机抽取一个元素
srandmember	set 2	#随机抽取两个元素
spop set			#随机删除一个元素
spop set 2			#随即删除两个元素
smove set nset 3	#将指定元素移动到新集合
sdiff set set2		#获取set中set2没有的元素
sinter set set2		#获取set和set2都有的元素,交集
sunion set set2		#获取set和set2所有的元素,并集

5、有序集合(zset)

set的基础上增加一个整数区分级别,可以存在相同级别,但不能存在相同的元素

zadd set 1 one 2 two					#添加元素,顺序为1,如果元素已存在会更新级别
zrange set 0 -1							#获取元素集合
zrangebyscore set -inf +inf				#从小到大遍历,相同级别无序
zrangebyscore set -inf +inf withscores	#显示级别
zrangebyscore set -inf 3 withscores		#显示在级别范围小于3的元素,带级别
zrem set one							#移除元素
zcard set								#获取元素数量
zrevrange set 0-1						#从大到小遍历
zcount set 0 3							#获取指定区间成员数量

4、特殊数据类型

1、geospatial

地理空间位置(纬度、经度、名称),底层是zset,所以可以使用对应命令。

geoadd chain 116.35 40.41 beijing	#添加坐标,两极无法直接添加,一般使用配置文件导入
geodist china beijing guangzhou km	#计算两地距离
geopos china guangzhou				#获取定位
georadius china 110 30 1000 km		#查询某一位置指定范围内的其他坐标
georadius china 110 30 1000 km withdist withcoord count 1	#显示一个他,和他的距离以及他的坐标
georadiusbymember china guangzhou 10000 km					#指定元素的范围搜索

2、bitmap

位运算,记录两种不同的状态分别为01

setbit bit 3 0			#记录第一天,第一种状态,如星期四,未打卡
getbit bit 3			#查看某天的状态
bitcount bit			#统计状态为1的数量

3、hyperloglog

统计基数,类似set集合,有误差。

pfadd key a b c d e			#添加元素
pfcount key					#计算元素个数,可以同时计算多个key,和为所有不重复的元素
pfmerge keys key1 key2		#并集key1和key2到key中

5、事务操作

  • 本质:一组命令的集合。
  • 一次性:即使命令出错也会全部执行完。
  • 顺序性:一个事务的所有命令都会被序列化,按照队列顺序执行。
  • 排他性:事物的执行不会受到干扰。
  • Redis事务不保证原子性和隔离性。

1、事务执行

127.0.0.1:6379[3]> multi		#开启事务
OK
127.0.0.1:6379[3]> set k1 v1	#命令入队
QUEUED
127.0.0.1:6379[3]> set k2 v2
QUEUED
127.0.0.1:6379[3]> get k2
QUEUED
127.0.0.1:6379[3]> set k3 v3
QUEUED
127.0.0.1:6379[3]> exec			#执行事务,执行之后关闭事务,再次使用需要开启
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379[3]> multi
OK
127.0.0.1:6379[3]> set k4 v4
QUEUED
127.0.0.1:6379[3]> get k3
QUEUED
127.0.0.1:6379[3]> discard		#取消事务
OK

2、出错情况

1、编译型异常:命令语法有问题,这时所有命令不执行。

127.0.0.1:6379[3]> multi
OK
127.0.0.1:6379[3]> set name lin
QUEUED
127.0.0.1:6379[3]> getset name		#此处命令有错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379[3]> get name
QUEUED
127.0.0.1:6379[3]> exec				#执行后报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379[3]> get name
(nil)								#找不到事务中的执行结果

2、运行时异常:命令语法正确,但是无法执行则会报错,但是事务中其他命令正常执行。

127.0.0.1:6379[3]> set name lin		#设置键值对
OK
127.0.0.1:6379[3]> multi			
OK
127.0.0.1:6379[3]> incr name		#使字符串+1,在命令上是对的,但是执行时会因为是字符串无法+1报错
QUEUED
127.0.0.1:6379[3]> set id 1
QUEUED
127.0.0.1:6379[3]> set age 2
QUEUED
127.0.0.1:6379[3]> exec
1) (error) ERR value is not an integer or out of range	#执行时因为是字符串无法+1报错
2) OK
3) OK
127.0.0.1:6379[3]> get id		#事务中其他命令正常执行
"1"
127.0.0.1:6379[3]> get age
"2"

3、乐观锁

当一个客户端执行事务时,另一个客户端修改了数据库,第一个客户端将执行失败。

#cli1
set money 100
watch money		#监视
#cli2
set money 50
127.0.0.1:6379[3]> multi			#开启事务
OK
127.0.0.1:6379[3]> decrby money 20	#命令入队
QUEUED
127.0.0.1:6379[3]> exec				#执行事务
(nil)								#因为值被修改,所以事务所有命令执行失败
  • 如果需要再次执行事务,需要先放弃监视再重新监视获取最新的值,再次执行事务
  • 事务执行成功或者取消事务执行都会自动放弃监视
127.0.0.1:6379[3]> unwatch
OK
127.0.0.1:6379[3]> watch money
OK

4、分布式锁

1、先判断键是否存在
	1.1、存在则表示已有用户获得锁,进入等待,循环1。
	1.2、不存在则获得锁,设置value为获得锁的方法名,设置过期时间防止崩溃没有释放锁。
2、使用完之后释放锁或等待过期

6、Jedis

使用java操作客户端,现在springboot中替换为lettuce

1、导入依赖

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

2、连接数据库

public class MyPing {
    public static void main(String[] args) {
        //1、新建对象
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //2、Redis的所有命令就是jedis的方法
        System.out.println(jedis.ping());
        //3、关闭连接
        jedis.close();
    }
}

3、事务示例

public class MyPIng {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("a","b");
        jsonObject.put("c","d");
        //开启事务
        Transaction multi = jedis.multi();
        String s = jsonObject.toJSONString();
        try {
            multi.set("user1",s);
            multi.set("user2",s);
            multi.exec();
        }catch (Exception e){
            multi.discard();	//失败回滚
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();		//关闭连接
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值