文章目录
一、Redis数据类型
1.String
- String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。
常用命令:
-
set
用于设置给定key的值。如果key已经存储其他值,set就重写旧值,且无视类型。语法:set key value
-
get
用于获取指定key的值。如果key不存在,则返回nil。语法:get key
-
append
将给定的value追加到key的原值末尾。语法:append key value
-
strlen
获取指定key所存储的字符串值的长度。当key存储的不是字符串值时,返回一个错误。语法:strlen key
-
setex
给指定的key设置值及time秒的过期时间。如果key已经存在,setex将会替换旧的值并设置过期时间。语法:setex key time value
-
setnx
只有key不存在时,设置key的值。语法:setnx key value
-
getrange
获取指定区间范围内的值,类似于between…and的关系。语法:getrange key start end
-
setrange
从offset开始覆盖指定key的值。语法:setrange key offset value
-
incr
将key中存储的数字值加一。语法:incr key
如果不存在该key,则先初始化为0,再递增,值为1。非数值则报错。 -
decr
将key中存储的数字值减一。语法:decr key
如果不存在该key,则先初始化为0,再递减1。非数值则报错。 -
incrby/decrby
将key存储的数字值按照step进行增减。语法:incrby/decrby key step
key不存在就初始化为0,再执行 incrby/decrby命令。 -
mset
同时设置1个或多个key-value。语法:mset key1 value1 key2 value2
-
mget
获取一个或多个指定key的值。语法:mget key1 key2
如果某个key不存在,则这个key返回nil -
getset
将value设为指定key的值,并返回key的旧值(old value),即先get旧值然后立即set新值。
使用场景: 1.计数器、2.统计多单位的数量、3.粉丝数、4.对象缓存存储、5.分布式锁setnx
2.List
- List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。底层是一个双向链表,对两端操作性极高,通过索引操作中间的性能较差。
一个List最多能包含2^32-1个元素(每个List超过40亿个元素)。
双向链表:
也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
常用命令:
-
lpush/rpush
从左边(头部)/右边(尾部)插入一个或多个值。语法:lpush/rpush key1 value1 value2 ...
如果lpush k1 v1 v2 v3,此时v1先进入,v2其次,v3最后,所以List从左到右的排序是v3、v2、v1,就跟排队一样。 -
lrange
返回key列表中的start和end之间的元素(包含start和end)。其中0表示列表中的第一个元素,-1表示列表中的最后一个元素。语法:lrange key start end
-
lpop/rpop
移除并返回第一个值或最后一个值。语法:lpop/rpop key
-
lindex
获取列表index位置的值(从左开始)。语法:lindex key index
,不存在则返回nil -
llen
获取列表长度。语法:llen key
-
lrem
从左边开始移除count个与value相同的元素。语法:lrem key count value
-
linsert
在列表中value值的前边/后边插入一个new value值(从左开始)。语法:linsert key before/after value new value
-
lset
将索引为index的值设置为value。语法:lset key index value
使用场景: 1.消息队列、2.排行榜、3.最新列表
3.Set
- Set与List类似,是一个列表功能,但Set是自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。
Set是String类型的无序集合,它底层是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1)。
因为是hash表,value会生成一个hash码用来保存value,通过hash码查找value;如果value相同时添加,则只会浪费资源,如果value不同产生同样的hash码,则会覆盖掉原有的value,所以当hash码存在时,添加操作忽略。
常用命令:
-
sadd
将一个或多个元素添加到集合key中,已经存在的元素将被忽略。语法:sadd key value1 value2 ...
-
smembers
获取该集合的所有元素。语法:smembers key
-
sismember
判断集合key中是否含有value元素,有则返回1,无则返回0。语法:sismember key value
-
scard
返回该集合的元素个数。语法:scard key
-
srem
移除集合中的一个或多个成员元素,不存在的成员元素则会被忽略。语法:srem key value1 value2 ...
-
spop
随机删除集合中的一个元素并返回该元素。语法:spop key
在spop key 后面加count,可指定随机删除count个元素并返回元素 -
srandmember
随机取出集合中count个元素,但不会删除。语法:srandmember key count
-
smove
将value元素从sourcekey集合移动到destinationkey集合中。语法:smove sourcekey destinationkey value
-
sinter
返回两个集合的交集元素。语法:sinter key1 key2
-
sunion
返回俩个集合的并集元素。语法:sunion key1 key2
-
sdiff
返回俩个集合的差集元素(key1中的,不包含key2)。语法:sdiff key1 key2
使用场景: 1.黑白名单、2.随机展示、3.好友、4.关注人、5.粉丝、6.感兴趣的人的集合
4.Hash
- Hash是一个键值对集合。Hash是一个String类型的field(字段)和value(值)的映射表,hash特别适合用于存储对象。
Hash存储结构优化
如果field数量较少,存储结构优化为类数组结构
如果field数据较多,存储结构使用HashMap结构
常用命令:
-
hset
给key集合中的field赋值value。语法结构:hset key field value
如果一个hash表不存在,一个新的hash表被创建并进行HSET操作。
如果字段已经存在hash表中,旧值将被重写。 -
hget
获取key集合的field字段的值。语法:hget key field
-
hmset
批量设置hash的字段和值。语法:hmset key field1 value1 field2 value2 ...
hset也是可以设置多个字段的。 -
hexists
判断指定key中是否存在field,存在返回1,不存在返回0。语法:hexists key field
-
hkeys
获取该hash表中所有的field。语法:hkeys key
-
hvals
获取该hash表中所有的value。语法:hvals key
-
hincrby
为哈希表key中的field字段的值加上增量increment,返回增量后的数值。语法:hincrby key field increment
增量也可以为负数,相当于对指定字段进行减法操作。
如果一个hash表key不存在,一个新的hash表被创建并执行hincrby命令。
如果指定的字段不存在,那么在执行命令前,字段的值被初始化为0。
对一个存储字符串值的字段执行hincrby命令会造成一个操作,即不能对非数值执行hincrby命令。 -
hdel
删除hash表key中的一个或多个指定字段,不存在的字段将被忽略。语法:hdel key field1 field2 ...
-
hsetnx
给hash表中不存在的字段赋值。语法:hsetnx key field value
如果哈希表不存在,则一个新的哈希表被创建并执行hsetnx操作。
如果字段存在哈希表key中,则操作无效,返回0。
如果字段在哈希表key中不存在,则给field设置value并返回1。
使用场景: 1.购物车、2.存储对象
5.Zset
- Zset与Set非常相似,是一个没有重复元素的String集合。不同之处是Zset的每个元素都关联了一个分数(score),这个分数被用来按照从低分到高分的方式排序集合中的元素。集合的元素是唯一的,但分数可以重复。
因为元素是有序的,所以可以根据分数(score)或次序(position)来获取一个范围内的元素。
常用命令:
-
zadd
将一个或多个元素(value)及分数(score)加入到有序集key中。语法:zadd key score1 value1 score2 value2 ...
-
zrange
返回结合中的索引start到索引end之间的元素(包含start和end)。语法:zrange key start end [withscores]
withscores,即在返回集合中指定区间的元素,并返回元素分数。-1代表最后一个索引。 -
zrangebyscore
返回集合key中的分数minscore和分数maxscore之间的元素(包含minscore和maxscore)。其中元素的位置按分数值递增(从小到大)来排序。语法:zrangebyscore key minscore maxscore [withscores]
-
zincrby
为元素value的score加上increment的值。语法:zincrby key increment value
-
zrem
删除该集合key中的元素value。语法:zrem key value
-
zcount
统计该集合key在分数minscore和分数maxscore之间的元素个数。语法:zount key minsocre maxscore
-
zrank
返回value在集合中的排名,从0开始。语法:zrank key value
使用场景: 1.延时队列、2.排行榜、3.限流
6.Bitmaps
-
在计算机中,用二进制(位)作为存储信息的基本单位,一个字节等于8位。
常用命令: -
setbit
设置Bitmaps中某个偏移量的值。语法:setbit key offset value
redis中bitmaps可以用来统计用户信息,eg:活跃天数、打卡天数、登录天数
bitmaps位图,都是操作二进制来进行记录的,只有0和1俩个状态
例如:setbit zhangsan:3 1 1
,此时key=zhangsan:3就是指张三3月份,1 1就是3月1号打卡。
1 0就是三月1号未打卡。 -
getbit
获取Bitmaps中某个偏移量的值。语法:getbit key offset
-
bitcount
统计字符串被设置为1的bit数量。一般情况下,给定的整个字符串都会被进行统计,可以选择额外的start和end参数,指定字节组范围内进行统计(包括start和end),0表示第一个元素,-1表示最后一个元素。语法:bitcount key [start end]
bitcount key 1 3,即统计1、2、3这三个字节组中bit=1的数量,也就是统计00000001,00000010,00000011中bit的数量。
bitcount key 0 0,统计0-7的bit,即00000000代表(指向)第一个字节组,第一个字节组有8位都可以用来记录0或1。也就是说00000000→用来存储前8天(0-7)的记录
- bitop
将多个bitmaps通过交集/并集方式合并成一个新的bitmaps。语法:bitop and/or destkey sourcekey1 sourcekey2 ...
即将sourcekey1和sourcekey2 …通过and/or合并到destkey
使用场景: 1.活跃天数、2.打卡天数、3.登录天数、4.用户签到、5.统计活跃用户、6.统计用户是否在线、7.实现布隆过滤器
7.Geospatia
- GEO,即Geospatia,地理信息的缩写。该类型就是元素的二维坐标。在地图上就是经纬度。Redis基于该类型提供了经纬度设置、查询、范围查询、距离查询、经纬度hash等常见操作。
常用命令:
-
geoadd
用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的key中。语法:geoadd key longitude latitude member
百度坐标拾取系统:https://api.map.baidu.com/lbsapi/getpoint/index.html
-
geopos
从指定的key里返回所有指定的名称(member)的位置(经度和纬度),不存在则返回nil。语法:geopos key member [member ...]
-
geodist
用于返回俩个给定位置之间的距离。语法:geodist key member1 member2 [m|km|ft|mi]
参数说明:
m:米,默认单位
km:千米
mi:英里
ft:英尺 -
georadius
以给定的经纬度(longitude、latitude)为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离(radius)的所有位置元素。语法:georadius key longitude latitude radius m|km|ft|mi
使用场景: 1.附近的电影院、2.附近的好友、3.附近的火锅店
8.Hyperloglog
-
在做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(页面浏览量:page view)。redis HyperLogLog是用来做统计基数的算法,HyperLogLog的优点是:在输入元素的数量或体积非常非常大时,计算基数的空间总是固定的、并且是很小的。
-
基数
数据集{1,3,5,7,8,5,7},那么这个数据集的基数集为{1,3,5,7,8},基数(即不重复的元素)为5个。基数估计就是在误差可接受的范围内快速计算基数。
常用命令:
-
pfadd
将所有元素参数添加到HyperLogLog数据结构中。语法:pfadd key element1 element2 ...
如果至少有一个元素被添加返回1,否则返回0。 -
pfcount
计算Hyperloglog近似基数,可以计算多个Hyperloglog,统计基数总数。语法:pfcount key1 key2 ...
-
pfmerge
将一个或多个Hyperloglog(sourcekey)合并成一个HyperLogLog(destkey)。语法:pfmerge destkey sourcekey1 sourcekey2 ...
使用场景:
基数不大,数据量不大就用不上,会有点大材小用浪费空间,有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么,和bitmap相比,属于两种特定统计情况,简而言之,HyperLogLog去重比bitmaps方便很多,一般可以将bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃。
1.网站PV(即page view,页面浏览量)统计
2.网站UV(即unique visitor,独立访客)统计
3.统计访问量(ip数)
4.统计在线用户
5.统计每天搜索不同词条的个数
6.统计文章真实阅读数
二、Java整合Redis
1.下载安装Redis Desktop Manager可视化工具及使用
Redis Desktop Manager是一款能够跨平台使用的开源性Redis可视化工具。
下载Redis Desktop Manager地址:https://resp.app/pricing
百度云盘:
链接:https://pan.baidu.com/s/1pJCNrrfvKj6LUaPEbxEX3g
提取码:t9n1
安装直接下一步,选择安装路径即可。
使用:
1.连接Redis服务前,先确定防火墙是否关闭:firewall-cmd --state
2.修改redis.conf:
redis默认只允许本地访问,要让redis可以远程访问,需要如下操作:
- 注释掉
bind 127.0.0.1
- 关闭保护模式:
protected-mode no
3.重启redis服务 - 找到6379端口的pid:
lsof -i:6379
- 杀死该pid的服务进程:
kill -9 4636
- 进入redis/src目录下,启动redis服务:
./redis-server ../redis.conf
4.RDM可视化工具连接redis服务
如果连接失败,可能需要重启虚拟机,再去启动redis服务即可。
5.测试RDM可视化工具操作
在redis的db0数据库内add new key
在命令中查询该数据:lindex klist 0
2.Jedis操作
-
Jedis是Redis官方推荐的Java连接开发工具。
-
创建一个springboot项目:
-
引入Jedis依赖,以及测试依赖:
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>
-
在src的test文件夹下创建相应的包,在包下创建测试类JedisTest;此时通过junit提供的before和after注解进行jedis连接的一个连接及关闭。同时通过jedis对redis的8种数据类型进行简单的操作。
/** * Jedis测试用例 */ public class JedisTest { Jedis jedis; @Before public void init() { //创建redis连接实例 jedis = new Jedis("192.168.126.12", 6379); } /** * string操作 */ @Test public void stringTest() { //设置一个key // jedis.set("k1","k2"); //获取key的值 String k1 = jedis.get("k1"); System.out.println(k1); //设置key的过期时间为10s jedis.setex("k2",10,"v2"); } /** * list操作 */ @Test public void listTest() { //添加元素 lpush rpush jedis.lpush("list1","v0","v1"); jedis.rpush("list1","v2","v3"); //获取所有元素 -1(最后一个元素) List<String> list1 = jedis.lrange("list1",0,-1); //遍历所有元素 for (String s : list1) { System.out.println(s); } } /** * set操作 */ @Test public void setTest() { //添加元素 jedis.sadd("set1","v0","v2","v3","v3"); //获取元素 Set<String> set1 = jedis.smembers("set1"); //遍历所有元素 for (String s: set1) { System.out.println(s); } } /** * hash操作 */ @Test public void hashTest() { //设置hash jedis.hset("user","name","zzx"); jedis.hset("user","age","22"); //获取key所有的value值 List<String> user = jedis.hvals("user"); //遍历所有元素 for (String s: user) { System.out.println(s); } } /** * zset操作 */ @Test public void zsetTest() { //添加元素 jedis.zadd("zset",100,"php"); jedis.zadd("zset",200,"c++"); jedis.zadd("zset",300,"c"); jedis.zadd("zset",400,"java"); List<String> zset = jedis.zrange("zset", 0, -1); //遍历所有元素 for (String s: zset) { System.out.println(s); } } /** * bitmaps数据类型 */ @Test public void bitmapsTest() { //给张三添加上班的打卡记录 jedis.setbit("zhangsan:2",0, true); jedis.setbit("zhangsan:2",1, false); jedis.setbit("zhangsan:2",2, true); jedis.setbit("zhangsan:2",3, true); //获取张三 二月份 第三天上班记录 boolean getbit = jedis.getbit("zhangsan:2", 2); //返回 true 或 false System.out.println(getbit); } /** * geo操作 */ @Test public void geoTest() { //添加地理信息 jedis.geoadd("china",116.067144,22.880624,"jiazi"); //添加地理信息 jedis.geoadd("china",113.032049,23.142731,"dongruan"); //获取地理信息 List<GeoCoordinate> geopos = jedis.geopos("china", "jiazi"); //遍历所有位置信息 for (GeoCoordinate geo: geopos) { System.out.println(geo.toString()); } Double geodist = jedis.geodist("china", "jiazi", "dongruan", GeoUnit.KM); System.out.println(geodist+"km"); } /** * hyperloglog操作 */ @Test public void hyperloglogTest() { //添加元素 jedis.pfadd("book","uid1","uid2","uid2","uid3"); //获取访问书籍的用户数量 long bookCount = jedis.pfcount("book"); System.out.println(bookCount); } @After public void close() { //关闭jedis连接 jedis.close(); } }
-
3.Spring-Data-Redis操作
-
Spring-Data-Redis是spring家族的一部分,通过简单的配置访问redis服务,对Redis顶层开发包(Jedis,JRedis,and RJC)进行了高度封装,RedisTemplate提供了Redis各种操作、异常处理及序列化,支持发布订阅。
-
RedisTemplate
- Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有Redis原生的api。
org.springframework.data.redis.core包下的Class RedisTemplate<K,V>
K:表示模板中Redis的key的类型,模板中Redis的key的类型(通常是String)。
V:表示模板中Redis的value的类型。
redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash redisTemplate.opsForList();//操作list redisTemplate.opsForSet();//操作set redisTemplate.opsForZSet();//操作有序set
- Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有Redis原生的api。
-
StringRedisTemplate和RedisTemplate
- 两者的关系是StringRedisTemplate继承RedisTemplate
- 两者数据不互通;各管各的。
- SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
- StringRedisTemplate默认采用的是String的序列化策略,key和value都是采用此策略的序列化保存的。
- RedisTemplate默认采用的是JDK的序列化策略,key和value都是采用此策略的序列化保存的。
-
创建一个springboot项目springdataredisdemo
-
创建时,选择添加Lombok、Spring Web和Spring Data Reactive Redis依赖。
-
application.properties配置
#Redis服务器连接地址 spring.redis.host=192.168.126.12 #Redis服务器连接端口 spring.redis.port=6379 #连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 #连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 #连接池中的最大空闲连接 spring.redis.pool.max-idle=8 #连接池中的最小空闲连接 spring.redis.pool.min-idle=0 #连接超时时间(毫秒) spring.redis.timeout=30000
-
redisTemplate没有自定义序列化时,通过操作字符串来设置redis数据库时,
redisTemplate.opsForValue().set("k1","v1");
出现如下前缀:
-
在springdataredisdemo包下,创建一个包config,在config包中创建一个配置类RedisConfig。用来将JDK的序列化策略改为String的序列化策略。代码如下:
/** * 自定义序列化 */ @Configuration public class RedisConfig { @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); //添加序列化机制 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } }
重新运行测试类,就不会出现这个前缀了。
-
使用pringboot整合的SpringDataRedis来简单操作Redis的数据类型,代码如下:
class SpringdataredisdemoApplicationTests { @Autowired private RedisTemplate redisTemplate; /** * redisTemplate操作redis服务 */ @Test void contextLoads() { //保存数据 redisTemplate.opsForValue().set("k1","v1"); //获取数据 String k1 = (String)redisTemplate.opsForValue().get("k1"); System.out.println(k1); } /** * list 操作 */ @Test void listTest() { //添加元素 redisTemplate.opsForList().leftPush("k2","v2"); redisTemplate.opsForList().leftPush("k2","v3"); redisTemplate.opsForList().leftPush("k2","v4"); redisTemplate.opsForList().leftPush("k2","v5"); //弹出第一个元素 String k2 = (String)redisTemplate.opsForList().leftPop("k2"); System.out.println(k2); } /** * hash 操作 */ @Test void hashTest() { //添加元素 redisTemplate.opsForHash().put("user","age","18"); redisTemplate.opsForHash().put("user","name","zzx"); //获取元素 String s = (String)redisTemplate.opsForHash().get("user", "name"); System.out.println(s); } /** * set 操作 */ @Test void setTest() { //添加元素 // redisTemplate.opsForSet().add("s1","v1"); // redisTemplate.opsForSet().add("s1","v2"); // redisTemplate.opsForSet().add("s1","v2"); // redisTemplate.opsForSet().add("s1","v4"); // //获取元素 // Set s1 = redisTemplate.opsForSet().members("s1"); // for (Object s:s1) { // System.out.println(s); // } //获取集合set的长度 Long size = redisTemplate.opsForSet().size("s1"); System.out.println(size); } /** * zset 操作 */ @Test void zsetTest() { //添加元素 // redisTemplate.opsForZSet().add("z1","php",100); // redisTemplate.opsForZSet().add("z1","python",200); // redisTemplate.opsForZSet().add("z1","java",300); //获取元素 Set z1 = redisTemplate.opsForZSet().rangeWithScores("z1", 0, -1); for (Object o :z1) { System.out.println(o); } } /** * bitmaps 操作 */ @Test void bitmapsTest() { //添加数据 // redisTemplate.opsForValue().setBit("zhangsan:1",0,true); // redisTemplate.opsForValue().setBit("zhangsan:1",1,false); //获取数据 Boolean aBoolean = redisTemplate.opsForValue().getBit("zhangsan:1", 0); System.out.println(aBoolean); } /** * geo 操作 */ @Test void geoTest() { //添加地理信息 // redisTemplate.opsForGeo().add("china",new Point(113.032049,23.142731) ,"dongruanE"); // redisTemplate.opsForGeo().add("china",new Point(116.056667,22.891979) ,"jiazi"); //获取地点范围内的所有地理信息 GeoResults radius = redisTemplate.opsForGeo(). radius("china","jiazi",new Distance(330, Metrics.KILOMETERS), RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()); for (Object o:radius) { System.out.println(o); } } /** * hyperloglog 操作,用于数据量超大时取基数。 */ @Test void hyperloglogTest() { //添加元素 redisTemplate.opsForHyperLogLog().add("hlog1","v1","v2","v3","v3"); //获取基数个数 Long size = redisTemplate.opsForHyperLogLog().size("hlog1"); System.out.println(size); } }
-
总结:
1.String是Redis最基本的数据类型。
List、Set、Zset跟Hash都是一个集合,List的value可以重复,因为List是有序列表;而Set的value不能重复,因为它是无序列表;
并且List底层是一个双向链表,在操作数据时,两端的效率极高、中间较差。而Set底层是一个value为null的hash表,在操作数据时,添加、删除、查询的时间复杂度都是O(1);
Hash是一个键值对集合,适合存储对象,与List和Set不同的时,在Hash的表内部是键值对,而List和Set的表内部都是value值,并且List的表key内部是有序的,而Set的表key内部是无序的。
Zset和Set的不同之处是Zset的每个元素都关联了一个分数(score)。
2.Jedis是java对redis的连接开发工具,通过主机ip和端口号即可创建实例连接,从而进行对redis数据库的操作。
而springboot整合的SpringDataRedis不用每次都手动创建实例连接,但是需要进行配置,并且在RedisTemplate提供了各种操作,异常处理及序列化,支持发布订阅,相当于升级版。