一.Redis的基本概念

1.关系型数据库与非关系型数据库

关系型数据库:MySQL就是典型的关系型数据库,使用表结构。

非关系型数据库:redis是典型的非关系型数据库,存放数据的时候使用的是键值对的形式

2.非关系型数据库的分类

基于键值对:key-value类型:Redis,memcached
列存储数据库:Column-oriented Graph:HBase
图形数据库:Graphs based:Neo4j
文档型数据库:MongoDB,MongoDB是一个基于分布式文件存储的数据库

3.Redis是什么?

是远程字典服务器的单词的缩写,使用C语言编写的,并且是开源的,性能比较高。它可以用作数据库、缓存和消息中间件,它是基于内存运行的,并支持持久化的NoSQL(非关系型)数据库

4.redis的特性

  • 支持持久化
  • 支持丰富的数据类型,例如:string,list,set,sort set,hash
  • 支持数据的备份,主从复制

Redis默认支持16个数据库(就是新建后有几个默认数据库,mysql是4个)

5.redis的优点

  • 性能极高,每秒钟可以读11W次,每秒钟可以写8.1w次
  • 支持丰富的数据类型,例如:string,list,set,sort set,hash
  • 原子性
  • 丰富的特性:发布订阅,key过期。

二.Redis常用命令

  • DEl key:删除key
  • DBSIZE 查看当前数据库key的数量
  • Flushdb:清空当前数据库,
  • flushall:清空所有数据库
  • Keys , * keys k? 问号表示占位符
  • move key [num] :把key从当前数据库移动到目标库
  • Exists key:判断key是否存在
  • Ttl key :查看key的剩余有效时间,-1表示永不过期,-2表示已经过期
  • Expire key seconds:为给定的key设置过期的事件
  • Type key:查看key的类型

三.Redis的五大常用数据类型(==重要==)

  • string(字符串)

string是二进制安全的,是可以存放任何的数据的,包括图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
set k1 100  #设置key值
get key #获取指定key值
mset k11 value1 k12 value2 k13 value3 k14 value4 #set只能设置一对keyvalue,mset可以一次性设置多对
mget k11 k12 k13 k14#get只能获取一对key value 而mget可以一次性获取多对

getrange key start end #获取从start开始end结束的key的字串 左闭右闭区间
getrange key 0 -1 #获取整个key的value,支持负数下标
setrange key offset newValue #将key的value值,从offset开始设置为newValue
setrange key 0 haha #将从0的位置开始,用后面的字符串覆盖原来的字串,例如原来是wangdaowuhan 执行完指令后变为 hahadaowuhan 如果后续的字符串比原来的字符串更长,那就会把旧字符串全部覆盖

getset key value#把key获取出来设置为value

settex key seconds value #将value关联到key,并设置过期时间

incr/incrby key [num] #对value值加1/加num,value必须是数字
incr key #每次将key的值加1
incr key num #每次将key的值加num
  • Hash(字典)

是一个string类型得key : (field:child) ,他的值存得也是一对键值对,键值对里套键值对

1
2
3
4
5
6
7
hset key field value #插入一个键值对 
hmset key field1 value1 field2 value2 field3 value3 #插入多对键值对

hget key field #获取field对应得value值
hmget key field field field#获取key下存储的多对键值
hkeys key #获取key下得field值
hvals key #获取key下得val值
  • List(列表)

类似于双向链表,但是没有前后之分,只有左右之分,并且支持下标操作的

1
2
3
4
5
6
7
8
9
10
11
12
lpush key value vlaue value #相当于头插法
rpush key value value value#相当于尾插法

lpop/rpop #在左右两边进行删除

lrange key start stop#获取列表指定范围内的元素,start是起始下标,stop是结束下标

lindex key index #通过下标获取列表中的元素
lset key index value #将下标为index的值设置为value

lrem key count value #从队头开始删除count个value,cout可以大于列表中的value的个数,相当于把所有等于value的值都删除
linsert ket before/after pivot value #在列表的pivot值得前面或者后面插入一个元素
  • Set(集合)

集合,底层使用得是哈希表实现的,元素是唯一的。和stl得set是不同得没有去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sadd key value value vlaue #向集合添加一个或多个元素

smembers key #返回集合中得所有成员

scard key #获取集合得成员数

sismember ky member #半段member元素是否是集合key中得元素

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

#可以在集合中取交集,差集,并集
sdiff key1 key2 #出现在key1中没有出现在key2中
sinter key1 key2#取交集
sunion key1 key2 #取并集

srandmember key count #每次从key中随机选出count个元素

spop key count #每次从key中随机删除count个元素
  • Sorted Set(有序集合)

zset,会使用double类型分数,给每个元素设置权重,然后元素会根据权重进行排序,权重是可以重复得,元素是不能重复的

1
2
3
zadd key score1 member1 socre2 member2 #socre1表示权重,member表示要插入得元素
zrange key start end #通过索引区间返回有序集合指定区间内得成员
zrange key satrt end withscores #不仅返回元素,还会把权重值也一起打出来

四.redis配置文件

  1. 配置文件的路径

    1
    /etc/redis下面有个文件名为 6379.conf
  2. 杀死redis服务器

    1
    2
    kill -9 进程id号
    或者直接在客户端中直接执行shutdown命令
  3. 如何启动redis服务器

    1
    2
    redis-server + 配置文件的路径
    sudo redis-server /etc/redis/6379.conf

五.持久化(==重要==)

1.概念

将内存中的数据定期的备份到磁盘中,下一次重启的时候就可以将磁盘中的数据恢复到内存中。(防止数据丢失)

2.分类

  • RDB:将当前数据保存到硬盘里(原理是将Redis在内存中的数据库记录定时dump到磁盘上的RBD持久化)

  • AOF:将每次执行的写命令保存到磁盘(原理是将Redis的操作日志以追加的方式写入文件,类似于MySQL的binog)

3.RDB持久化

3.1基本概念

RBD是Redis默认的持久化方案,在指定的时间间隔内, 执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件,Redis重启会通过加载dump.rbd文件恢复数据

3.2触发快照方式

  1. 执行shutdown关闭服务器会触发快照,也就是修改dump.rdb文件的修改时间
  2. 执行flushall情况数据库,也会出发快照
  3. 手动执行save命令或者bgsave可以触发快照,将数据保存到磁盘中
  4. ==在指定的时间间隔内,执行指定次数的写操作(最重要==),自动触发快照
1
14:30:40的时候执行了一次save命令,这个时候文件最后修改时间就成了14:30:40了,然后执行两次写操作,等时间达到了14:31:00的时候,文件dump.rdb的修改时间依旧是14:30,但是等事件到达30S的时候,就是说到了14:30:10之后再查看,文件dump.rbd的最后修改时间已经变成14:31了

3.3优缺点

优点:

  • 如果对数据的完整性与一致性要求不高,可以使用RDB的持久化方式
  • 相对于AOF而言,数据的恢复速度是比较快的

缺点:

  • 进行数据恢复的时候,数据的完整性和一致性不高,可能会丢失60s的数据(自动触发快照的缺陷)
  • 备份时占用内存,redis在备份的时候会独立创建一个fork子进程,将数据写入到一个临时文件(这时候数据在内存中就存有两份),最后将临时文件替换为之前的备份文件

4.AOF持久化

4.1概念

将所有执行的写命令全部记录下来,然后数据恢复的时候,再将写命令重新执行一次

4.2重写

1
2
3
4
5
set k1 100
incr k1 #执行100次
这样写命令执行了101次,最终k1的值是200
重写是指将上述的命令合并为
set k1 200 #把101条命令重写为了一条

重写之后就可以让文件的大小减少

4.3触发重写的方法

1
执行bgrewriteaof 命令

redis会记录上次重写时的AOF大小,默认配置是当“AOF文件的大小是上次的rewirte后大小的一倍,且文件大于64M时触发”

例如上次文件的大小时33M 下次大小是66M,就会自动触发重写

4.4优缺点

优点:

  • 相对于rdb而言,数据的完整性与一致性高。

缺点

  • 因为采用的是命令追加的形式,有可能导致文件的体积比较大
  • 相对于rdb而言,数据的恢复速率是比较慢的

5.总结

  • 如果想要数据恢复快,可以选择rdb持久化;如果想要精度比较高,可以选择aof
  • 两种持久化的方式是可以同时存在,起到互补的作用
  • 如果单独的以aof持久化的方式,也是可以让服务器启动成功的
  • 如果只有aof持久化的方式,并且aof文件被损坏了,redis服务器是启动不成功的

aof文件损坏可以使用redis-check-aof --fix 文件名命令来修复文件

六.redis事务

1.概念

一组命令的集合,一个事务中的所有命令都会序列化,按顺序的串行化执行,而不会被其他命令加塞

2.三个阶段

开始事务、命令入队、执行事务

3.事务的命令

1
2
3
4
5
6
multi #标记一个事务块的开始
exec #执行所有事务块内的命令

discard #取消事务,放弃执行事务块内的所有命令

watch key key... #监视一个或多个key,如果在事务执行之前这个key被其他命令所改动,那么事务将会被打断

如果事务中有命令执行失败的话(运行时报错),那么他的前后命令也不会受到影响,依然可以正常执行,说明redis的事务不具备原子性(mysql的事务具备原子性,只要有执行执行失败,那么整个事务的命令就会全部失败)

但是如果事务中有命令有语法错误(编译时报错),那么整个事务都会执行失败,编译的时候就失败了

==总结:redis中的事务是没有原子性的==

4.乐观锁和悲观锁

悲观锁:每次拿数据的时候都认为其他的线程或者进程会修改数据,所以需要进行加锁,例如在linux上学习到的pthread_mutex_t的锁,在c++学到的std::mutex都属于悲观锁,

乐观锁(无锁):每次去拿数据的时候都认为其他的线程或者进程不会修改数据,所以就不需要上锁。实现乐观锁的两种方法:

  • 版本号机制
  • CAS机制

七.主从复制(==比较重要==)

7.1概念

指将一台Redis服务器的数据复制到其他的Redis服务器,。前者称为主节点,后者称为从节点,数据的复制是单向的,只能由主节点到从节点

7.2为什么要做主从复制

达到负载均衡、数据的恢复(防止主机挂掉之后,不能响应请求)、读写分离

7.3主从复制常用模式

一主多从,一台主机 多台从机

击鼓传花,主机复制给从机,丛机又成为主机复制给下一台从机

7.4主从复制的配置方法

1.首先进入到/etc/redis下面,将6379.conf拷贝两份,分别取名6380.conf与6381.conf

2.修改6380.conf与6381.conf文件中的以下内容

1
2
3
4
5
port 6380/6381
pidfile "/var/run/redis_6380/6381.pid"
logfile "var/log/redis_6380/6381.log"
dbfilename "dump6380/6381.rdb"
appendfilename "appendonly6380/6381.aof"

3.将三个配置文件进行启动

1
2
3
sudo redis-server /etc/redis/6379.conf
sudo redis-server /etc/redis/6380.conf
sudo redis-server /etc/redis/6381.conf

4.分别进入对应的服务器的客户端下面

1
2
3
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381

5.查看每台机器的身份信息,发现默认都是主机

1
info replication #命令可以查看每台机器的身份,每台机器默认都认为自己是一台主机 role字段显示的都是master

6.开始配置主从复制的关系

1
2
3
4
#在从机执行命令
slaveof 127.0.0.1 6379 #成为了6379的从机

#再查看主从复制的信息,就发现从机上可以看到主机的IP,port以及状态信息,在主机上也可以看到从机的ip,port以及状态信息

在主机上写命令,从机立刻就可以感知到,那么在从机上可以写数据吗?

不可以,因为数据的复制是单向的。

  • 如果从机6381断开了,然后主机6379就不会记录6381的信息了,即使6381重新启动,6379也不会记录6381的信息,因为6381重新启动的时候,是以主机的身份启动的
  • 如果主机因为某些原因挂掉了,那么从机6380和6381会感知到6379的下线,同时会记录主机的状态为down,但是还是会默默的等待主机的上线,当主机6379上线之后,6379依旧会作为主机,然后6380和6381就依旧是6379的从机

如果主机挂掉永不上线,那么基本的主从复制关系就不能执行写操作,因为没有主机

反客为主

1
slaveof no one #恢复主权 不是任何机器的从机

八.哨兵模式

会执行流言协议和投标协议来决定是否执行自动故障迁移,以及选择哪个从服务器作为新的主服务器

配置步骤

1.在/etc/redis下新建哨兵配置文件sentinel.conf

编辑该文件

1
2
#哨兵     监视     主机名      主机ip      端口号  票数
sentinel monitor master6379 127.0.0.1 6379 1

2.启动哨兵的配置文件

1
sudo redis-sentinel /etc/redis/sentinel.conf

3.哨兵会监视主机6379的状态变更,如果6379因为某些原因断开,那么哨兵会将自己的一票投递给6380或者6381,谁得到这一票谁就是新的主机,所以新的主从复制状态就展示出来了,这样就可以解决传统的主从复制,因为主机断掉而不能执行写操作的弊端。

九.常见问题

9.1缓存雪崩

概念:缓存中的大量数据在同一时间失效,此时相当于没有缓存,那么客户端的请求会下沉到底层数据库上面,那么有可能会导致底层数据库的压力比较大,甚至将底层数据库压垮。

解决办法:

  1. 可以让key值不过期或延迟过期时间
  2. 将key的过期时间分散

pk4JHqU.png

9.2缓存击穿

概念:对于某一个超高频率的访问数据失效了,大量访问请求会下沉到底层数据库上,导致数据库压力骤升,甚至将底层数据库压垮。

解决办法:

  1. 延缓热点数据失效的时间甚至不失效
  2. 可以将底层数据库看成共享资源,互斥的访问数据库
    pk4JqZF.png

9.3缓存穿透

概念:要访问的数据在redis和底层数据库中都没有,那么还是会对底层数据库的压力比较大,导致有可能将底层数据库压垮

解决方案:

  1. 在缓存中设置键值对形式<key,null>,如果对应的value是null就直接返回,就不需要访问底层数据库了

pk4JLa4.png

代码中使用redis编译指令

1
g++ *.cc -lhiredis -pthread -std=c++17

晚上的一些练习

Q:什么是Redis?有哪些有优缺点?

redis是典型的非关系型数据库,存放数据的时候使用的是键值对的形式。

redis是远程字典服务器的单词的缩写,使用c语言编写的,并且是开源的,性能比较高,它可以用作数据库、缓存和消息中间件,他是基于内存运行的,并支持持久化的NoSQL数据库

优点:

性能极高,每秒钟可以读11w次,每秒钟可以写8.1W

支持丰富的数据类型

原子性

丰富的特性:发布订阅,key过期

缺点:

内存限制:Redis的数据存储在内存中,受到内存容量的限制,适合存储较小量但频繁访问的数据。

单线程模型:Redis的主要工作模式是单线程,虽然可以通过多路复用技术提高并发性能,但是在处理大规模并发请求时可能会存在性能瓶颈

数据持久化的效率:尽管Redis支持持久化,但是在某些情况下,持久化会对性能产生一定的影响。

复杂的数据结构操作:虽然Redis支持丰富的数据结构,但有时处理复杂数据结构的操作会稍显复杂,需要开发者深入理解Redis的数据模型和命令

Q:为什么要用 Redis /为什么要用缓存

提高性能:缓存可以显著提高应用程序的性能,通过将频繁访问的数据存储在内存中,可以大大减少读取数据的时间。

减轻数据库压力:将数据库的读取压力分散到缓存中,可以降低数据库的负载,这对高并发的应用程序特别重要,因为数据库通常是性能瓶颈之一

提升可扩展性:缓存可以帮助应用程序更好地扩展,通过减少对数据库地直接访问,可以更容易地在需要时增加应用程序地实例数,从而处理更多地请求

降低成本:使用缓存可以降低系统运行地硬件成本,因为缓存可以减少对昂贵硬件资源地需求,同时也能减少云服务器地使用费用

提高用户体验:响应速度快意味着用户能够更快的获取数据和内容,从而提升用户体验,快速地页面加载和响应时间可以显著提升用户满意度和留存率

支持临时数据存储需求:缓存不仅可以用于存储数据库查询结果,还可以用于存储临时数据,这些数据通常不需要长期存储在数据库中,但需要在短时间内频繁访问

Q:Redis为什么这么快

Redis之所以能够如此快速,主要有以下几个关键原因:

\1. 数据存储在内存中: Redis主要将数据存储在内存中,这使得数据的读取和写入操作非常快速。与传统的基于磁盘存储的数据库相比,内存的访问速度要快得多。

\2. 非阻塞的单线程模型:Redis采用单线程模型来处理客户端请求,这意味着不需要像多线程或多进程那样频繁地进行上下文切换和锁机制,减少了系统的开销。此外,Redis使用了I/O多路复用机制来处理并发请求,有效地提高了系统的并发处理能力。

\3. 高效的数据结构:Redis内置了多种高效的数据结构,如字符串、哈希表、列表、集合、有序集合等。这些数据结构在内存中的操作都经过精心优化,使得它们可以高效地支持各种操作,如插入、删除、查找和排序等。

\4. 精简的功能集: Redis专注于提供高性能、低延迟的数据访问服务,因此在设计上避免了复杂的功能和不必要的开销。它的核心功能和特性相对较少,但每一个特性都经过深思熟虑和优化。

\5. 异步方式的持久化: Redis的持久化可以通过快照和日志方式来进行,而且持久化操作通常是异步执行的,不会阻塞主线程的操作。这样可以保证在大部分情况下,持久化操作不会影响Redis的性能。

综上所述,Redis在设计和实现上注重性能和效率,利用内存存储、非阻塞的单线程模型、高效的数据结构以及精简的功能集,使得它能够提供出色的读写性能和响应速度。

Q:Redis有哪些常用数据类型

string ,list, set, sort set,hash

Q:什么是Redis持久化?

将内存中的数据定期的备份到磁盘中,下一次重启的时候就可以将磁盘中的数据恢复到内存中。(防止数据丢失)

Q:Redis 的持久化机制是什么?各自的优缺点?

  • RDB:将当前数据保存到硬盘里(原理是将Redis在内存中的数据库记录定时dump到磁盘上的RBD持久化)
  • AOF:将每次执行的写命令保存到磁盘(原理是将Redis的操作日志以追加的方式写入文件,类似于MySQL的binog)

RDB持久化:

优点:

  • 如果对数据的完整性与一致性要求不高,可以使用RDB的持久化方式
  • 相对于AOF而言,数据的恢复速度是比较快的

缺点:

  • 进行数据恢复的时候,数据的完整性和一致性不高,可能会丢失60s的数据(自动触发快照的缺陷)

AOF持久化:

优点:

  • 相对于rdb而言,数据的完整性与一致性高。

缺点

  • 因为采用的是命令追加的形式,有可能导致文件的体积比较大
  • 相对于rdb而言,数据的恢复速率是比较慢的

Q:Redis key的过期时间和永久有效分别怎么设置?

设置过期时间

  1. 使用 EXPIRE 命令
    • 使用命令格式:EXPIRE key seconds
    • 示例:将名为 mykey 的key设置为在60秒后过期:EXPIRE mykey 60
    • 这样设置后,60秒后,Redis会自动删除该key。
  2. 使用 PEXPIRE 命令
    • 使用命令格式:PEXPIRE key milliseconds
    • 示例:将名为 mykey 的key设置为在60000毫秒(即60秒)后过期:PEXPIRE mykey 60000
    • 这个命令与EXPIRE类似,但是时间单位是毫秒。

设置永久有效(取消过期时间)

  1. 使用 PERSIST 命令

    • 使用命令格式:PERSIST key
    • 示例:取消名为 mykey 的key的过期时间:PERSIST mykey
    • 这样设置后,mykey 将永久保存在Redis中,不会自动删除。

Q:Redis事务的概念?Redis事务的三个阶段?Redis事务相关命令和事务的特征?

概念:一组命令的集合,一个事务中的所有命令都会序列化,按顺序的串行化执行,而不会被其他命令加塞

三各阶段:开始事务、命令入队、执行事务

相关命令:

multi #标记一个事务块的开始
exec #执行所有事务块内的命令

discard #取消事务,放弃执行事务块内的所有命令

  • 批量操作: Redis事务允许将多个命令打包发送给服务器执行,客户端可以一次性发送多条命令到Redis服务器,然后一起执行,这样可以减少网络延迟。
  • 隔离性: Redis事务默认是隔离的,即在同一个事务中的命令不会受到其他客户端的影响,保证了多个事务同时执行时彼此不受影响。
  • 一致性: Redis事务在执行过程中,中间状态不会对其他客户端可见,只有在事务提交之后,其他客户端才能看到事务所做的修改。
  • 事务队列: Redis事务使用队列来缓存收到的命令,而不是立即执行,这样在事务执行过程中,其他客户端仍然可以执行命令,但不会影响到正在执行的事务。
  • 回滚操作: 在事务执行过程中,如果某个命令执行失败或者发生错误,Redis会自动回滚这个事务,之前的命令不会生效,保证了数据的一致性。
  • 命令的执行方式: Redis事务并不是像传统数据库中的事务那样执行一系列命令后统一提交或回滚,而是在执行 EXEC 命令时才真正执行事务中的所有命令。因此,事务中的命令并不是立即执行的,而是暂时保存在队列中,直到执行 EXEC 命令才会一起执行。

Q:缓存穿透是什么?如何解决?缓存击穿是什么?有什么解决方案?缓存雪崩的概念?如何应对?

缓存雪崩:缓存中的大量数据在同一时间失效,此时相当于没有缓存,那么客户端的请求会下沉到底层数据库上面,那么有可能会导致底层数据库的压力比较大,甚至将底层数据库压垮。

解决:

  1. 可以让key值不过期或延迟过期时间
  2. 将key的过期时间分散

缓存击穿:对于某一个超高频率的访问数据失效了,大量访问请求会下沉到底层数据库上,导致数据库压力骤升,甚至将底层数据库压垮。

解决:

  1. 延缓热点数据失效的时间甚至不失效
  2. 可以将底层数据库看成共享资源,互斥的访问数据库

缓存穿透:要访问的数据在redis和底层数据库中都没有,那么还是会对底层数据库的压力比较大,导致有可能将底层数据库压垮

解决:在缓存中设置键值对形式<key,null>,如果对应的value是null就直接返回,就不需要访问底层数据库了

Q:封装hiredis的封装

hiredis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef MYHIREDIS
#define MYHIREDIS

#include <hiredis/hiredis.h>
#include <iostream>
#include <string>
using namespace std;

class MyHiredis
{
public:
MyHiredis();
~MyHiredis();

void set(const string ,const string );
string get(const string );
void connection(const string ip,int port);

private:
redisContext * _redisContext;
redisReply * _redisReply;
};


#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "1_MyHiredis.hpp"

MyHiredis::MyHiredis()
:_redisContext(nullptr)
,_redisReply(nullptr)
{}
MyHiredis::~MyHiredis(){
if(_redisContext){
redisFree(_redisContext);
}
if(_redisReply){
freeReplyObject(_redisReply);
}
}

void MyHiredis::connection(const string ip,int port){
_redisContext = redisConnect(ip.c_str(),port);
if(nullptr == _redisContext){
return;
}
if(_redisContext->err){
perror(_redisContext->errstr);
return;
}
}



void MyHiredis::set(const string key,const string value){
_redisReply = (redisReply *)redisCommand(_redisContext,"SET %s %s",key.c_str(),value.c_str());
if(!_redisReply){
freeReplyObject(_redisReply);
perror("set error");
return;
}
if(_redisContext->err){
perror("set error");
return;
}

}
string MyHiredis::get(const string key){
_redisReply = (redisReply *)redisCommand(_redisContext,"GET %s",key.c_str());
if(!_redisReply){
perror("get error");
return "data error";
}
if(_redisContext->err){
perror("get error");
return "data error";
}
return string(_redisReply->str);


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "1_MyHiredis.hpp"

void test(){
MyHiredis hir;
hir.connection("127.0.0.1",6379);
hir.set("key1","100");
cout << hir.get("key1") << endl;

}
int main()
{
test();
return 0;
}