【redis】Redis笔记——初级篇


学习视频: 尚硅谷周阳

Nosql

简介
SQL:关系型数据库,表与表之间建立关联关系。
NoSQL:非关系型数据库,数据与数据之间没有关联关系。就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

类型

  • 键值(key-value或KV)存储数据库
  • 列存储数据库:键仍然存在,但是指向了多个列,HBase (eg:博客平台(标签和文章),日志)
  • 文档型数据库 MongoDb (eg:淘宝商品的评价)
  • 图形数据库 Neo4j (eg:好友列表)
MongoDB是一个基于分布式文件存储的数据库。有C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB介于关系型数据库和非关系型数据库之间,是最像关系型数据库、功能最丰富的非关系型数据库。

文档(document)是MongoDB中数据的基本单元,非常类似于关系型数据库系统中的行(但是比行要复杂的多);

集合(collection)就是一组文档,如果说MongoDB中的文档类似于关系型数据库中的行,那么集合就如同表;

Redis简介

Redis(Remote Dictionary Server即远程字典服务器)是一个使用C语言编写的开源、支持持久化、事务和备份,集群的键值对(KV)存储数据库、内存数据库,也是当前最热门的NoSQL数据库。

特点:

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
  • Redis支持数据的备份,即master-slave模式的数据备份

Redis命令参考:传送门

Redis数据类型及API使用

string、hash、list、set、zset

Key

  • del key
  • dump key:序列化给定key,返回被序列化的值
  • exists key:检查key是否存在
  • expire key second:为key设定过期时间,以秒计算,可以不写second,默认为秒
  • ttl key:返回key剩余时间,-1为永久,-2为失效
  • persist key:移除key的过期时间,key将持久保存
  • keys pattern:查询所有符号给定模式的key eg:keys *
  • randomkey:随机返回一个key
  • rename key newkey:修改key的名称
  • move key db:移动key至指定数据库中 eg:move a 1
  • type key:返回key所储存的值的类型

String

string类型是二进制安全的,redis的string可以包含任何数据,如图像、序列化对象。一个键最多能存储512MB。二进制安全是指,在传输数据的时候,能保证二进制数据的信息安全,也就是不会被篡改、破译;如果被攻击,能够及时检测出来

API

  • SET key value:设置指定 key 的值
  • GET key: 获取指定 key 的值。
  • GETRANGE key start end: 返回 key 中字符串值的子字符
  • GETSET key value: 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
  • GETBIT key offset: 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
  • MGET key1 [key2…] : 获取所有(一个或多个)给定 key 的值。
  • SETBIT key offset value :对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
  • SETEX key seconds value: 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
  • SETNX key value :只有在 key 不存在时设置 key 的值。
  • SETRANGE key offset value: 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
  • STRLEN key: 返回 key 所储存的字符串值的长度。
  • MSET key value [key value …]: 同时设置一个或多个 key-value 对。
  • MSETNX key value [key value …]: 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
  • PSETEX key milliseconds value: 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
  • INCR key: 将 key 中储存的数字值增一。
  • INCRBY key increment :将 key 所储存的值加上给定的增量值(increment) 。
  • INCRBYFLOAT key increment :将 key 所储存的值加上给定的浮点增量值(increment) 。
  • DECR key :将 key 中储存的数字值减一。
  • DECRBY key decrement key: 所储存的值减去给定的减量值(decrement) 。
  • APPEND key value: 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

List

单值多value,字符串链表,left、right都可以添加。

无论是头还是尾,效率都很高,但如果是中间的话,效率并不尽如人意。

API

  • lpush key value1 [value2]:从左侧插入,右边的先出,相当于一个栈,eg:lpush list 1 2 3 lrange list 0 -1 输出:3 2 1
  • rpush key value1 [value2]: 从右侧插入,左边的先出,eg:rpush list 1 2 3 lrange list 0 -1 输出:1 2 3
  • lpushx key value:从左侧插入值,如果list不存在,则不操作
  • rpushx key value:从右侧插入值,如果list不存在,则不操作
  • llen key:获取列表长度
  • lindex key index:获取指定索引的元素,从零开始
  • lrange key start stop:获取列表指定范围的元素
  • lpop key :从左侧移除第一个元素
  • prop key:移除列表最后一个元素
  • irem:删除指定个数的同一元素,eg:irem list 2 3 删掉了集合中的两个三
  • blpop key [key1] timeout:移除并获取列表第一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止
  • brpop key [key1] timeout:移除并获取列表最后一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止
  • ltrim key start stop :对列表进行修改,让列表只保留指定区间的元素,不在指定区间的元素就会被删除,eg:list1中元素1 2 3 4 5 ltrim list1 2 3 list1剩余元素:3 4
  • lset key index value :指定索引的值
  • linsert key before|after world value:在列表元素前或则后插入元素

用此命令可以实现订单下单流程、用户系统登录注册短信等:

rpoplpush list1 list2:移除list1最后一个元素,将该元素添加到list2并返回此元素

Set

单值多value、唯一、无序

API

  • sadd key value1[value2]:向集合添加成员

  • scard key:返回集合成员数

  • smembers key:返回集合中所有成员

  • sismember key member:判断memeber元素是否是集合key成员的成员

  • srandmember key [count]:返回集合中一个或多个随机数

  • srem key member1 [member2]:移除集合中一个或多个成员

  • spop key:移除并返回集合中的一个随机元素

  • smove source destination member:将member元素从source集合移动到destination集合

  • sdiff key1 [key2]:返回给定的第一个集合和其他集合的差集(即在key1中的值而在其他key中找不到)

  • sdiffstore destination key1[key2]:返回给定的第一个集合与其他的集合的差集并存储在destination中
    eg:set1:1 2 3 set2:3 4 5 6 sdiffstore set3 set1 set2 smembers set3 result:1 2

  • sinter key1 [key2]:返回所有集合的交集

  • sunion key1 [key2]:返回所有集合的并集

对两个集合间的数据[计算]进行交集、并集、差集运算:
1、能非常方便的实现如共同关注、共同喜好、二度好友等功能。
对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。
2、利用唯一性,可以统计访问网站的所有独立 IP

Hash

KV模式不变但value是个键值对、string类型的field和value的映射表。可以看成KEY和VALUE的MAP容器。
API

  • hset key_name field value:为指定的key设定field和value
  • hmset key field value[field1,value1]:同时将多个 field-value (域-值)对设置到哈希表 key 中
  • hsetnx:当不存在才创建该field
  • hget key field:获取存储在哈希表中指定字段的值
  • hmget key field[field1]:获取所有给定字段的值
  • hgetall key:返回hash表中所有字段和值
  • hkeys key:获取hash表所有字段
  • hvals key:获取hash表所有值
  • hlen key:获取hash表中的字段数量
  • hdel key field [field1]:删除一个或多个hash表的字段
  • hexists:在key里面是否存在指定的field
  • hincrby key field increment:增加某个field的值
通常用来存储一个用户信息的对象数据

Zset(sorted set)

有序且不重复。每个元素都会关联一个double类型的分数,Redis通过分数进行从小到大的排序。分数可以重复
API

  • zadd key score1 memeber1
  • zcard key :获取集合中的元素数量
  • zcount key min max 计算在有序集合中指定区间分数的成员数
  • zcount key min max 计算在有序集合中指定区间分数的成员数
  • zrange key start stop 指定输出索引范围内的成员
  • zrangebyscore key min max 指定输出score区间内的成员
  • zrank key member:返回有序集合指定成员的索引
  • zrevrange key start stop :返回有序集中指定区间内的成员,通过索引,分数从高到底
  • zrem key member [member …]: 移除有序集合中的一个或多个成员
  • zremrangebyrank key start stop :移除有序集合中给定的索引区间的所有成员(第一名是0)(低到高排序)
  • zremrangebyscore key min max: 移除有序集合中给定的分数区间的所有成员

注意

  • ( 为不包含的意思 eg: (60 --> 大于60
  • limit 开始下标 步数
可用于排行榜:
1.推特以发表时间作为score来存储
2.存储成绩
3.以zset作为带权重的队列,让重要的任务先执行

配置文件

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf(Windows 名为 redis.windows.conf)。

你可以通过 CONFIG 命令查看或设置配置项。

redis 127.0.0.1:6379> CONFIG GET CONFIG_SETTING_NAME

实例:

redis 127.0.0.1:6379> CONFIG GET loglevel

1) "loglevel"
2) "notice"

持久化

Redis 的数据全部在内存⾥,如果突然宕机,数据就会全部丢失,因此必须有⼀种机制来保证 Redis 的数据不会因为故障⽽丢失,这种机制就是 Redis 的持久化机制。

Redis的持久化就是将储存在内存里面的数据以文件形式保存硬盘里面,这样即使Redis服务端被关闭,已经同步到硬盘里面的数据也不会丢失,除此之外,持久化也可以使Redis服务器重启时,通过载入同步的持久文件来还原之前的数据,或者使用持久化文件来进行数据备份和数据迁移等工作。

Redis 的持久化机制有两种,一种是RDB、一种是AOF。

RDB

  • 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,快照是内存数据的序列化二进制形式,它恢复时是将快照文件直接读到内存里。
  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。
Fork
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 
数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
  • rdb保存的是dump.rdb文件
  • 相关配置在配置文件的位置 - 在redis.conf搜寻### SNAPSHOTTING ###

配置文件

# redis是基于内存的数据库,可以通过设置该值定期写入磁盘。 
# 注释掉“save”这一行配置项就可以让保存数据库功能失效 
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) 
save 900 1 
save 300 10 
save 60 10000 
#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误 
stop-writes-on-bgsave-error yes 
#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间 
rdbcompression yes 
#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。 
rdbchecksum yes 
#rdb文件的名称 
dbfilename dump.rdb 
#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录 
dir /data

触发条件

  • 通过配制文件中的save条件
save 900 1
save 300 10
save 60 10000
  • 手动通过save和bgsave命令
    1.save:save时只管保存,其他不管,全部阻塞(因为redis的单线程机制)
    2.bgsave:redis会在后台异步的进行快照操作,同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的事件
  • 通过flushall命令,也会产生dump.rdb文件,但是里面是空的,无意义。
  • 通过shutdown命令,安全退出,也会生成快照文件(和异常退出形成对比,比如:kill杀死进程的方式)

恢复

  • 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务
  • appendonly 设置成no,redis启动时会把/var/lib/redis 目录下的dump.rdb 中的数据恢复。
appendonly no 
dbfilename dump.rdb 
dir /var/lib/redis #可以自行指定

停止
动态停止所有rdb保存规则:

redis-cli config set save ""

RDB恢复数据的速度很快,适合大规模的数据恢复,而又对部分数据不敏感,并且dump.db文件是一个压缩的二进制文件,文件暂用空间小

但是当出现异常退出时,会丢失最后一次快照后的数据或者当fork的时候,内存的中的数据会被克隆一份,大致两倍的膨胀需要考虑。而且,当数据过大时,fork操作占用过多的系统资源,造成主服务器进程假死。

因此出现了另一种方式AOF

AOF

  • 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
  • 保存的是appendonly.aof文件
  • aof机制默认关闭,可以通过appendonly = yes参数开启aof机制,通过appendfilename = myaoffile.aof指定aof文件名称。

持久化三种策略

#aof持久化策略的配置 
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
 #always表示每次写入都执行fsync,以保证数据同步到磁盘。 
 #everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 
 appendfsync everysec

触发重写机制
随着服务器的不断运行,为了记录Redis中数据的变化,Redis会将越来越多的命令写入到AOF文件中,使得AOF文件的体积来断增大,为了让AOF文件的大小控制在合理的范围,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件:
重 写 将 过 期 的 没 有 用 的 可 以 优 化 的 命 令 进 行 化 简 , 从 而 达 到 减 少 硬 盘 占 用 量 和 加 速 R e d i s 恢 复 速 度 的 目 的

默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

# aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到
一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件
大小的二倍(设置为100)时,自动启动新的日志重写过程。 
auto-aof-rewrite-percentage 100
# 设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写 
auto-aof-rewrite-min-size 64mb

当aop重写时会引发重写和持久化追加同时发生的问题,可以通过no-appendfsync-on-rewrite no进行配置

# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会
造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no,是最安全的方式,不会丢失数据,
但是要忍受阻塞的问题。如果对延迟要求很高的应用,这个字段可以设置为yes,,设置为yes表示rewrite期间
对新写操作不fsync,暂时存在内存中,不会造成阻塞的问题(因为没有磁盘竞争),等rewrite完成后再写入,这个
时候redis会丢失数据。Linux的默认fsync策略是30秒。可能丢失30秒数据。因此,如果应用系统无法忍受延迟
而可以容忍少量的数据丢失,则设置为yes。如果应用系统无法忍受数据丢失,则设置为no。
 no-appendfsync-on-rewrite no

恢复

正常恢复
将文件放到dir指定的文件夹下,当redis启动的时候会自动加载数据,注意:aof文件的优先级比dump大。
异常恢复

  • 有些操作可以直接到appendonly.aof文件里去修改。eg:使用了flushall这个命令,此刻持久化文件中就会有这么一条命令记录,把它删掉就可以了
  • 写坏的文件可以通过 redis-check-aof --fix进行修复

优缺点:

  • AOF相同数据集的数据而言AOF文件要远大于RDB文件,恢复速度慢于RDB
  • AOF运行效率要慢于RDB,每秒同步策略效率较好,不同步效率和RDB相同
  • 当文件太大时,会触发重写机制,确保文件不会太大

小结:

  • 如果你只希望你的数据在服务器运行的时候存在,可以不使用任何的持久化方式
  • 一般建议同时开启两种持久化方式。AOF进行数据的持久化,确保数据不会丢失太多,而RDB更适合用于备份数据库,留着一个做万一的手段。
  • 性能建议:
  • 因为RDB文件只用做后备用途,建议只在slave上持久化RDB文件,而且只要在15分钟备份一次就够了,只保留900 1这条规则。
  • 如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价:1、带来了持续的IO;2、AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
  • 如果不Enable AOF,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时宕掉,会丢失10几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。

Redis事务

简介

  • 可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。
  • 可以在一个队列中,一次性、顺序性、排他性的执行一系列命令。

命令

命令作用
DISCARD取消事务,放弃执行事务块内的所有命令。
EXEC执行所有事务块内的命令。
MULTI标记一个事务块的开始。
UNWATCH取消 WATCH 命令对所有 key 的监视。
WATCH key [key …]监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

watch

  • 悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
  • 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
  • CAS:WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
  • Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变, 比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
  • 通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化, EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败
  • 一旦执行了exec之前加的监控锁都会被取消掉了(一次性)

3阶段

  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  • 执行:由EXEC命令触发事务

3特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
  • 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

消息订阅

进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

命令

命令作用
PSUBSCRIBE pattern [pattern …]订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument …]]查看订阅与发布系统状态。
PUBLISH channel message将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern …]]退订所有给定模式的频道。
SUBSCRIBE channel [channel …]订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel …]]指退订给定的频道。

主从复制

简介

  • 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
  • 可以实现读写分离容灾恢复

一主二仆

  • slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制?
    答:从头开始复制;123也可以复制
  • 从机是否可以写?set可否?
    答:从机不可写,不可set,主机可写
  • 主机shutdown后情况如何?从机是上位还是原地待命
    答:从机还是原地待命(咸鱼翻身,还是咸鱼)
  • 主机又回来了后,主机新增记录,从机还能否顺利复制?
    答:能
  • 其中一台从机down后情况如何?依照原有它能跟上大部队吗?
    答:不能跟上,每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件(具体位置:redis.conf搜寻#### REPLICATION ####)

薪火相传

  • 上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力(奴隶的奴隶还是奴隶)
  • 中途变更转向:会清除之前的数据,重新建立拷贝最新的
  • slaveof 新主库IP 新主库端口

反客为主

  • SLAVEOF no one
    使当前数据库停止与其他数据库的同步,转成主数据库

复制原理

  • slave启动成功连接到master后会发送一个sync命令
  • master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
    但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式

一组sentinel能同时监控多个master,反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
使用步骤

  • 调整结构,79带着80、81
  • 新建sentinel.conf文件,名字绝不能错
  • 配置哨兵,填写内容
    sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1
    上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机
  • 启动哨兵
  • redis-sentinel /sentinel.conf(上述目录依照各自的实际情况配置,可能目录不同)
  • 正常主从演示
  • 原有的master挂了
  • 投票新选
  • 重新主从继续开工,info replication查查看
  • 原master,变成slave

复制的缺点
由于所有的写操作都是先在Master上操作,然后同步更新到slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

Redis客户端操作

maven:Jedis

直连

// 1.生成一个 Jedis对象,这个对象负责和指定Reds节点进行通信
Jedis jedis = new Jedis("localhost", 6379);
// Sting  set get 操作
jedis.set("Jedis", "hello jedis!");
jedis.get("Jedis");
// 对结果进行自增
jedis.incr("counter")

Jedis常用API

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import redis.clients.jedis.Jedis;

public class TestAPI {
	public static void main(String[] args) {

		Jedis jedis = new Jedis("127.0.0.1", 6379);
		// key
		Set<String> keys = jedis.keys("*");
		for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
			String key = (String) iterator.next();
			System.out.println(key);
		}
		System.out.println("jedis.exists====>" + jedis.exists("k2"));
		System.out.println(jedis.ttl("k1"));
		
		// String
		// jedis.append("k1","myreids");
		System.out.println(jedis.get("k1"));
		jedis.set("k4", "k4_redis");
		System.out.println("----------------------------------------");
		jedis.mset("str1", "v1", "str2", "v2", "str3", "v3");
		System.out.println(jedis.mget("str1", "str2", "str3"));
		
		
		// list
		System.out.println("----------------------------------------");
		// jedis.lpush("mylist","v1","v2","v3","v4","v5");
		List<String> list = jedis.lrange("mylist", 0, -1);
		for (String element : list) {
			System.out.println(element);
		}
		
		// set
		jedis.sadd("orders", "jd001");
		jedis.sadd("orders", "jd002");
		jedis.sadd("orders", "jd003");
		Set<String> set1 = jedis.smembers("orders");
		for (Iterator iterator = set1.iterator(); iterator.hasNext();) {
			String string = (String) iterator.next();
			System.out.println(string);
		}
		jedis.srem("orders", "jd002");
		System.out.println(jedis.smembers("orders").size());
		
		// hash
		jedis.hset("hash1", "userName", "lisi");
		System.out.println(jedis.hget("hash1", "userName"));
		Map<String, String> map = new HashMap<String, String>();
		map.put("telphone", "110");
		map.put("address", "ekin");
		map.put("email", "abc@163.com");
		jedis.hmset("hash2", map);
		List<String> result = jedis.hmget("hash2", "telphone", "email");
		for (String element : result) {
			System.out.println(element);
		}
		
		// zset
		jedis.zadd("zset01", 60d, "v1");
		jedis.zadd("zset01", 70d, "v2");
		jedis.zadd("zset01", 80d, "v3");
		jedis.zadd("zset01", 90d, "v4");

		Set<String> s1 = jedis.zrange("zset01", 0, -1);
		for (Iterator iterator = s1.iterator(); iterator.hasNext();) {
			String string = (String) iterator.next();
			System.out.println(string);
		}

	}
}

事务

日常


import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;

public class Test03 {
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1", 6379);

		// 监控key,如果该动了事务就被放弃
		/*
		 * 3 jedis.watch("serialNum"); jedis.set("serialNum","s#####################");
		 * jedis.unwatch();
		 */

		Transaction transaction = jedis.multi();// 被当作一个命令进行执行
		Response<String> response = transaction.get("serialNum");
		transaction.set("serialNum", "s002");
		response = transaction.get("serialNum");
		transaction.lpush("list3", "a");
		transaction.lpush("list3", "b");
		transaction.lpush("list3", "c");

		transaction.exec();
		// 2 transaction.discard();
		System.out.println("serialNum***********" + response.get());

	}
}

加锁

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTX {
	public boolean transMethod() throws InterruptedException {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		int balance;// 可用余额
		int debt;// 欠额
		int amtToSubtract = 10;// 实刷额度

		jedis.watch("balance");
		// jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目
		Thread.sleep(7000);
		balance = Integer.parseInt(jedis.get("balance"));
		if (balance < amtToSubtract) {
			jedis.unwatch();
			System.out.println("modify");
			return false;
		} else {
			System.out.println("***********transaction");
			Transaction transaction = jedis.multi();
			transaction.decrBy("balance", amtToSubtract);
			transaction.incrBy("debt", amtToSubtract);
			transaction.exec();
			balance = Integer.parseInt(jedis.get("balance"));
			debt = Integer.parseInt(jedis.get("debt"));

			System.out.println("*******" + balance);
			System.out.println("*******" + debt);
			return true;
		}
	}

	/**
	 * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 重新再尝试一次。
	 * 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
	 * 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
	 * 
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		TestTX test = new TestTX();
		boolean retValue = test.transMethod();
		System.out.println("main retValue-------: " + retValue);
	}
}

连接池

// 初始化 Jedis连接池,通常来讲 Jedis Pool是单例的。
 GenericObjectPoolConf poolConfig new GenericObjectPoolConfigO Jedis Pool jedis Pool new JedisPool(poolConfig, 127.0.0. 1",6379;

连接池简单使用

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

public class JedisPoolUtil {
	private static volatile JedisPool jedisPool = null;

	private JedisPoolUtil() {
	}

	public static JedisPool getJedisPoolInstance() {
		if (null == jedisPool) {
			synchronized (JedisPoolUtil.class) {
				if (null == jedisPool) {
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					poolConfig.setMaxActive(1000);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWait(100 * 1000);
					poolConfig.setTestOnBorrow(true);

					jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
				}
			}
		}
		return jedisPool;
	}

	public static void release(JedisPool jedisPool, Jedis jedis) {
		if (null != jedis) {
			jedisPool.returnResourceObject(jedis);
		}
	}
}

Test

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

public class TestPool {

	public static void main(String[] args) {
		JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
		JedisPool jedisPool2 = JedisPoolUtil.getJedisPoolInstance();

		System.out.println(jedisPool == jedisPool2);

		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set("aa", "bb");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JedisPoolUtil.release(jedisPool, jedis);
		}
	}
}

连接方式对比

方式优缺点
直连简单方便、适用于少量长期连接的场景;但存在每次新建/关闭TCP开销、资源无法控制,存在连接泄露的可能、Jedis对象线程不安全
连接池Jedis预先生成,降低开销使用连接池的形式保护和控制资源的使用;但相对于直连,使用相对麻烦尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值