全新Redis6全部知识点,零基础入门1

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 全新Redis6全部知识点,零基础入门

1.分布式缓存Redis6安装

1.1.缓存和队列简介

高并发必备两大“核心技术”

(1)什么是队列(MQ消息中间件)

全称:MessageQueue,主要用于程序与程序之间的通信(异步+解耦)。
核心应用:
(1)解耦:订单系统->物流系统
(2)异步:用户注册同时发送优惠劵,和初始化操作
(3)削峰:秒杀、日志处理

(2)什么是缓存

程序需要经常调用的数据放在内存中,因为内存中的响应非常快,使其快速调用,避免去数据库持久层去查。
主要就是提高性能 DNS缓存、前端缓存、代理缓存服务器Nginx、应用程序缓存、数据库缓存

72e3a83d2eb14042adf841ffc21ec4e5.jpg

1.2.本地缓存和分布式缓存介绍

(1)分布式缓存

与应用隔离的缓存组件或服务,与本地服务隔离的一个独立的缓存服务,多个服务可共享这一个缓存,多个节点共享缓存,需要考虑带宽。
常见的分布式缓存:Redis、Memcached

(2)本地缓存

和业务程序一起的缓存,例如mybatis的一二级缓存,只能由服务本身调用,不能多节点共享,不需要考虑带宽。
常见的本地缓存:guava、redis也可以做本地缓存、SpringCache

(3)本地缓存和分布式缓存的选择

结合业务去选择缓存,高并发的项目一般分布式缓存和本地缓存都存在。

(4)热点key的解决方案

热点key一般都放在本地缓存中,因为不需要带宽,效率很高,先去本地缓存中去查找,没有的话再去分布式缓存中查找。
应用:热点新闻、热卖商品、大V明星结婚


27d2740f211344049354331ae728f803.jpg

1.3.Nosql和Redis简介

  • 什么是Redis
  • 其两者最重要的区别是NoSQL不使用SQL作为查询语言。
  • NoSQL数据存储可以不需要固定的表格模式
  • 键 - 值对存储,列存储,文档存储,图形数据库
  • NoSql:redis、memcached、mongodb、Hbase
一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API
高性能:Redis能读的速度是110000次/s,写的速度是81000次/s
内存中的存储结构,它可以做为消息中间件、缓存、数据库。如:lists(列表)、hashs(散列)、sorted sets(有序集合)、sets(集合)、strings(字符串)

1.4.Linux源码安装Redis6

(1)源码安装Redis上传到linux服务器(先安装升级gcc新版才能编译)

#安装gcc
yum install -y gcc-c++ autoconf automake
#centos7默认的gcc是4.8.5版本,版本小于5.3无法编译,要先安装gcc新版才能编译
gcc -v(查看gcc当前版本)
#升级新版gcc,配置永久生效
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash #从gcc4.8.5切换到gcc9编译器
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
#解压redis安装包
tar -xvf redis.6.2.1.tar.gz
mv redis.6.2.1 redis6
#编译redis
cd redis6
make
#安装到指定的目录
mkdir -p /usr/local/redis
make PREFIX = /usr/local/redis install
  • 注意:安装编译redis6需要升级gcc,默认自带的gcc版本比较老
  • redis-server:redis启动文件
  • redis-cli:redis客户端
  • redis.conf:redis配置文件

1.5.Docker容器化部署Redis6

云计算+容器化是当下的主流,也是未来的趋势, docker就是可
以快速部署启动应⽤
实现虚拟化,完整资源隔离,⼀次编写,四处运⾏
但有⼀定的限制,⽐如Docker是基于Linux 64bit的,⽆法在
32bit的linux/Windows/unix环境下使⽤

(1)Docker安装

#安装并运行Docker
yum install -y docker-io 
#启动docker
systemctl start docker
#检查安装结果
docker info
#启动使用docker
systemctl start docker  #运行Docker守护进程
systemctl stop docker   #停止Docker守护进程
systemctl restart docker#重启Docker守护进程
docker ps #查看容器
docker stop 容器id #停掉某个容器
#修改镜像文件
vim /etc/docker/daemon.json
{
"debug":true,"experimental":true,
"registry-mirrors":["https://pb5bklzr.mirror.aliyuncs.com","https://hubmirror.c.163.com","https://docker.mirrors.ustc.edu.cn"]
}

(2)Docker部署redis并配置密码

docker run -itd --name xdclass-redis -p 6379:6379 redis --requirepass 123456
-i:以交互模式运行容器,通常与-t同事使用。
-d:后台运行容器,并返回容器ID。

1.6.分布式缓存Redis6核心配置

(1)redis.conf配置文件的核心配置

daemonize yes #配置偶后台运行,默认是no
bind ip号     #绑定指定ip访问,0.0.0.0是不限制,配置多个ip用空格隔开,bind 192.168.10.1 192.168.10.2
port 端口号    #端口号,默认6379
requirepass   #密码配置
dbfilename    #配置redis持久化文件名称
dir       #配置redis持久化文件存储地址
save      #配置redis持久化机制

(2)在redis安装目录创建log、data、conf目录

日志:/usr/local/redis/log
数据:/usr/local/redis/data
配置文件:/usr/local/redis/conf

(3)在/usr/local/redis/conf中创建自定义的配置文件

touch /usr/local/redis/conf/redis.conf
vi /usr/local/redis/conf/redis.conf
#任何ip都可以访问
bind 0.0.0.0
#守护进程
daemonize yes
#密码
requirepass 123456
#日志文件
logfile "/usr/local/redis/log/redis.log"
#持久化文件名称
dbfilename xdclass.rdb
#持久化文件路径
dir "/usr/local/redis/data"
#持久化策略,10s内有一个key改动,执行快照
save 10 1

(4)指定配置文件启动redis

/usr/local/redis/bin/./redis-server /usr/local/redis/conf/redis.conf

查看日志确定是否启动:tail -f /usr/local/redis/log/redis.log

e6859bf7652d4be6bff95d0b9dac9572.jpg

2.分布式缓存Redis6数据结构

2.1.Redis6常见数据结构

(1)exists 判断key是否存在

exists name #判断name这个key是否存在

e7ebf38b39d044d58f6bddf3a8f00845.jpg

(2)del 删除key

del name #删除name这个key

801b5aeab7204456a858548336d38b9f.jpg

(3)type 判断key的类型

type name #判断name是什么类型


a77a5e6c8cad49ea868fb342fcf05ea7.jpg

(4)ttl 查看key的存活时间

ttl name #判断name还有多长时间过期
ttl age  #判断age还有多长时间过期

68a5f4d5e6ea44b1bb62268c39be999f.jpg

2.2.Redis6数据结构之String类型

  • 简介:存储字符串类型的key-value
  • 应用场景:验证码、计数器、订单重复提交、用户登录信息、商品详情

常用命令:

(1)set/get 设置和获取key-value

设置key-value:set user:name lixiang
获取key:get user:name

a406ed149c07483283a099fddb837e57.jpg

(2)incr 对指定key的value进行自增1

incr user:age


57cbc764de9f48c8a40598d9b750be00.jpg

(3)incrby 对指定key的value进行+n操作

incrby user:age 10


c27a3a23ed18409c9791ef612a86feb7.jpg

(4)mget/mset 一次获取多个key值,一次设置多个key-value

设置:mset user:addr tianjin user:phone 1333333333
获取:mget user:addr user:phone


3660e106be9c4d1a96e431d0ffff77d6.jpg

(5)setex 设置一个key-value带有过期时间的

setex user:code 30 236589

93b227d213f44730a511015c35a60d37.jpg

(6)setnx 当key不存在时,才设置key-value,key存在时,不做操作

setnx user:name xxxxx


52745b10ed5d4842865eedc3bc13bf25.jpg

2.3.Redis6数据结构之List类型

  • 简介:字符串列表,按照插入顺序排序,双向链表,插入删除时间复杂度为O(1)快,查找为O(n)慢。
  • 应用场景:简单队列、最新商品列表、评论列表、非实时排行榜
  • 常用命令:

(1)lpush 将一个或者多个值插入到列表头部,从左边开始插入

lpush phone:rank:daily iphone

a8a1eb5f281c45249c38d6c925a7323b.jpg

(2)rpush将一个或者多个值插入到列表尾部,从右边开始插入

rpush phone:rank:daily xiaomi


40fe55e55cde423da8171769cebcb2dc.jpg

(3)lrange获取指定key下边的范围元素,0代表第一个,1代表第二个,-1代表最后一个

lrange phone:rank:daily 0 -1

5f9df5eedc2c4893a4f6e44945ef0dbf.jpg

(4)llen获取当前key的元素个数

llen phone:rank:daily

190b3b8a607042dd9b76656f7a712fa4.jpg

(5)lindex获取当前索引元素的值

lindex phone:rank:daily 2


a4fe2a5e49c64611b1ea979682200832.jpg

(6)lpop从顶部弹出一个元素,从左边弹出

lpop phone:rank:daily 1

fc8bdfea5bda4e73926a0a3a6aa6e02a.jpg

(7)rpop从底部弹出一个元素,从右边弹出

rpop phone:rank:daily 1


5148ef90161141b799ae15467c01a44b.jpg

(8)lrem 删除一个元素,可以指定移除个数

lrem word 2 a



b21885d24b1a4ddba69379b1db1dd7ae.jpg

(9)brpop移除并且获取列表的最后一个元素,如果列表没有元素会阻塞设置的时长或者在规定的时间内弹出元素为止

brpop word 10

51403d24060f4da3bc16fc000bc7c920.jpg

2.4.Redis6数据结构之Hash类型

  • 简介:Hash是一个string类型的field和value的映射表,hash特别适用于存储对象。
  • 应用场景:购物车存储、用户个人信息存储、商品详情存储
  • 注意:每个hash可以存储2[^32] -1 键值对(40多亿)

常用命令:

(1)hset/hget 设置和获取key中指定字段的值

设置key-value:hset product:daily:1 title iphone
获取key:hset product:daily:1 title iphone

e333ea4ed7e147aa919756648e899981.jpg

(2)hdel 删除指定key的指定字段

hdel product:daily:1 title


b850f243399c4c1ea86518dc69e62cba.jpg

(3)hmset/hmget 一次设置和获取多个key中指定字段的值

hmget product:daily:1 title color

7b3e72d189224b2c85f3aca5493868b7.jpg

(4)hgetall 获取指定key的全部字段的值

hgetall product:daily:1

285f938c54fc466b8dc6264d36d354fa.jpg

(5)hincrby 对指定key的指定字段进行+n操作(n可以为正数也可以为负数)

hincrby product:daily:1 price 100


49111ecb085a40f5a9106096c28fc018.jpg

(6)hexists 判断指定key的指定字段是否存在

hexits product:daily:1 color

a8c6a3553dc24372afe7a9ec0fc7f3e0.jpg

2.5.Redis6数据结构之Set类型

  • 简介:将一个或者多个成员元素加入到集合中,已经存在的成员元素将被忽略
  • 应用场景:去重、社交应用关注、粉丝、共同好友、大数据里面用户画像标签

常用命令:

(1)sadd添加一个或者多个指定的member元素到集合中,若集合中已存在元素,将被忽略

sadd user:tags:1 woman bwn 18-25 beijing

5d08a53ea83d493c9618a23aeb3dbcbf.jpg

(2)smembers 获取当前集合中的所有元素

smembers user:tags:1


8355100411a24aa59dc1cfc444269253.jpg

(3)srem 删除集合中某个元素

srem user:tags:1 bwn

(4)scard 返回集合中所有元素的个数

scard user:tags:1

b8ad286e54a447eb92ed1ea349fb112a.jpg

(5)sismember 返回集合中是否存在当前元素

slsmember user:tags:1 bwn


4df738cf28d84cb29a702c554f146513.jpg

(6)sdiff 返回两个集合中的差集

sdiff user:tags:2 user:tags:1

(7)sinter返回两个集合中的交集

siner user:tags:2 user:tags:1

8acc910998e647b19ef6df386b6700b2.jpg

(8)sunion 返回两个集合的并集

sunion user:tags:2 user:tags:1

f7f3f7368c1e44d1a672e4610b2ec450.jpg

2.6.Redis6数据结构之SortedSet类型

  • 简介:用于将一个或者多个成员元素及分数值加入到有序集合中
  • 应用场景:实时排行榜:商品热销榜、体育类应用热门球队、积分榜、优先级任务、朋友圈 文章点赞-取消
  • 注意:底层采用Ziplist压缩列表和“跳跃表”两种存储结构,如果重复添加相同的元素数据,score值将被覆盖,保留最后一次修改的结果。

常用命令:

(1)zadd向有序集合中添加一个或者多个成员,或者更新已经存在的成员的分数

zadd video:rank 90 springcloud 80 springBoot 70 nginx 60 html 50 javascript 40 linux 

9d599e4112744e21970c96297714f6f5.jpg

(2)zcard获取有序集合中成员数

zcard video:rank

1e2e44727d2a42e0acddc57034e311a9.jpg

(3)zcount计算指定分数区间的成员的个数

zcount video:rank 0 50

9120e4f2c6b84d368de80f7762b4cdd6.jpg

(4)zincrby 有序集合中对指定的成员的分数+n(n可以为正数也可以为负数)

zincrby video:rank 5 linux


05fc287e50c14ab88aaabd4bb1392946.jpg

(5)zrange 返回索引区间的所有元素,从小到大

zrange video:rank 0 -1


4d9578e773c948cb9dd003417c0120be.jpg

(6)zrevrange返回索引区间的所有元素,从大到小

zrevrange video:rank 0 -1



5e2137badf784f4aa3899ad1dbb6c3d6.jpg

(7)zrank 返回有序集合中指定成员的索引,从小到大返回

zrank video:rank 1


71f9938baf5e4eaf86b80d93a7951390.jpg

(8)zrevrank返回有序集合中指定成员的索引,从大到小排序

zrevrank video:rank 1



8ce6a7374f8741c3942f89bc16cc96a5.jpg

(9)zrem移除有序集合中的一个或者多个成员

zrem video:rank linux



c8d0a0ffd8a643479027a4b8d818ff1f.jpg

(10)zscore返回有序集合中,成员的分数值

zscore video:rank springBoot

b73f5dbf47f546649c7da1c35282d074.jpg

3.SpringBoot2.x整合Redis6客户端

3.1.分布式缓存Redis客户端讲解

  • 自带客户端:redis-cli
  • java语言客户端
  • jedis
Jedis是直连模式,在多个线程间共享一个Jedis时是线程不安全的,需要使用连接池
其API提供了比较全面的Redis命令支持,相比其他Redis封装框架更加原生
Jedis中的方法调用是比较底层的暴漏的Redis的API,Java方法基本和Redis的API保持这一致
使用阻塞的I/O,方法调用同步,程序流需要等到socket处理完I/O才能执行,不支持异步操作

lettuce

高级Redis客户端,用于线程安全同步,异步响应
基于Netty的的事件驱动,可以在多个线程间并发访问, 通过异步的方式可以更好的利用系统资源

3.2.新版SpringBoot2.x项目创建

(1)相关软件环境

  • JDK1.8+以上
  • Maven3.5+

(2)加入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

注意:

  • springBoot2后默认使用Lettuce作为redis的客户端
  • 旧版本的Lettuce踩在堆外内存溢出的bug,5.3版本修复了 这个bug。
  • 解决:升级版本或者换jedis

(3)SpringDataRedis的RedisTemplate介绍

  • RedisTemplate介绍
  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:针对map类型的数据操作
  • ListOperations:针对List类型的数据操作
  • RedisTemplate和StringRedisTemplate的区别
  • StringRedisTemplate继承RedisTemplate
  • 两者的数据是不互通的(默认的序列化机制导致key不一样)
  • RedisTemplate默认采用的是JDK的序列化策略,会将数据先序列化成字节数组在存入Redis中
  • 总结:
  • 当Redis数据库里面操作的都是字符串数据的时候,那使用StringRedisTemplate即可
  • 数据是复杂的对象类型,那么使用RedisTemplate是更好的选择

(4)Redis序列化和反序列化机制

  • 同个key为啥获取不到值呢,核心就是序列化机制导致key值不一致
  • 什么是序列化
  • 把对象转化为字节序列的过程就称为对象的序列化
  • 把字节序列恢复成对象的过程就是反序列化
  • 对象字节序列化主要有两种用途
  • 把对象的字节序列永久的保存在硬盘上,通常放在一个文件上
  • 在网络上传输对象的字节序列

(5)Redis为什么要序列化

  • 性能可以提高,不同的序列化方式性能不一样
  • 可视化工具更好查看
  • 采用默认的jdk方式会乱码(POJO类需要实现Serializable接口)

  • 采用JSON方式则不用,且可视化工具更好的查看

(6)自定义Redis序列化方式,可以选择多种选择策略

  • JdkSerializationRedisSerializer
  • POJO对象的存存储场景,使用JDK本身序列化机制
  • 默认机制ObjectInputStream/ObjectOutputStream进行序列化操作
  • StringRedisSerializer
  • key或者value为字符串
  • Jackson2JsonRedisSerializer
  • 利用jackson-json工具,将POJO实例序列化成json格式存储
  • GenericFastJsonRedisSerializer
  • 另一种javabean与json之间的转化,同时也需要指定Class类型

3.3.RedisTemplate序列化机制配置

@Configuration
public class RedisTemplateConfiguration {
    /**
     * 自定义redisTemplate配置
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //默认使用JDK的序列化方式
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用Jackson2JsonRedisSerialize替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new                             
        Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        //指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //设置Key和Value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置hashKey和hashValue的的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //设置支持事务
        //redisTemplate.setEnableTransactionSupport(true);
        //初始化RedisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

3.4.Jedis+Lettuce客户端连接池配置

(1)lettuce连接池

 <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
 </dependency>

(2)配置application.properties

#连接池最大连接数(使用负数表示没有限制)
spring.redis.lettuce.pool.max-active = 10
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle = 10
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle = 0
#连接池最大阻塞等待时间(使用负数表示没有限制)
spring.redis.lettuce.pool.max-wait = -1ms
#指定客户端
spring.redis.client-type = lettuce

(3)配置application.yml

server:
  port: 8080
spring:
  redis:
    host: 8.140.116.67
    port: 6379
    password: 123456
    client-type: jedis
    lettuce:
      pool:
        #连接池的最大连接数(负数表示没有限制)
        max-active: 10
        #连接池中的最大空闲连接
        max-idle: 10
        #连接池的最小空闲连接
        min-idle: 0
        #连接池最大阻塞的等待时间(负数表示没有限制)
        max-wait: -1ms
    jedis:
      pool:
        #连接池的最大连接数(负数表示没有限制)
        max-active: 10
        #连接池中的最大空闲连接
        max-idle: 10
        #连接池的最小空闲连接
        min-idle: 0
        #连接池最大阻塞的等待时间(负数表示没有限制)
        max-wait: -1ms

(4)Jedis连接池介绍(可以不排除lettuce依赖包)

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <!-- 可以不排除lettuce依赖包 -->
      <exclusions>
        <exclusion>
          <groupId>io.lettuce</groupId>
          <artifactId>lettuce-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <!--不用指定版本号,本身spring-data-redis里面有-->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
运维 NoSQL 测试技术
从一个事故中理解Redis(几乎)所有知识点
作者从一个事故中总结了Redis(几乎)所有的知识点,供大家学习。
124 12
|
7月前
|
缓存 NoSQL 定位技术
深入探索Redis:面试中必须掌握的关键知识点
深入探索Redis:面试中必须掌握的关键知识点
|
存储 NoSQL 测试技术
关于redis涉及的知识点,C语言如何操作redis
关于redis涉及的知识点,C语言如何操作redis
|
存储 SpringCloudAlibaba 运维
Redis高级知识点总结
在 Redis 6.0 中,非常受关注的第一个新特性就是多线程。这是因为,Redis 一直被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF 重写),但是,**从网络 IO 处理到实际的读写命令处理,都是由单个线程完成的**。随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,也就是说,单个主线程处理网络请求的速度跟不上底层网络硬件的速度
266 0
Redis高级知识点总结
|
存储 缓存 NoSQL
redis知识点
redis 知识点
88 0
|
缓存 监控 NoSQL
【Redis】Redis知识点阶段性总结 2
【Redis】Redis知识点阶段性总结
65 0
|
NoSQL Linux Redis
【Redis】Redis知识点阶段性总结 1
【Redis】Redis知识点阶段性总结
103 0
|
存储 缓存 监控
全新Redis6全部知识点,零基础入门3
全新Redis6全部知识点,零基础入门
12236 1
|
存储 缓存 NoSQL
全新Redis6全部知识点,零基础入门2
全新Redis6全部知识点,零基础入门
|
运维 NoSQL 网络协议
关于Redis的知识点,你都学会了吗?2
关于Redis的知识点,你都学会了吗?2
161 0