Redis原理
Redis 是一个单线程程序 ( nginx 也是一个单线程程序 )
Redis 所有的数据都在内存中,所有的运算都是内存级别的运算。
Redis 主要依赖 多路复用 和 异步IO 来维持性能
Redis 是一个单线程程序 ( nginx 也是一个单线程程序 )
Redis 所有的数据都在内存中,所有的运算都是内存级别的运算。
Redis 主要依赖 多路复用 和 异步IO 来维持性能
业界比较通用的地理位置距离排序算法是 GeoHash 算法, Redis 也使用 GeoHash 算法。GeoHash算法将 二维的经纬度数据映射到一维的整数,这样所有的元素都将挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间的距离也会很接近。
Redis提供了6个Geo指令
127.0.0.1:6379> geoadd company 116.48105 39.996794 juejin
(integer) 1
127.0.0.1:6379> geoadd company 116.514203 39.905409 ireader
(integer) 1
127.0.0.1:6379> geoadd company 116.562108 39.787602 jd 116.334255 40.027400 xiaomi
(integer) 2
127.0.0.1:6379>
2. geodist
127.0.0.1:6379> geodist company jd xiaomi km
"33.0047"
127.0.0.1:6379> geodist company juejin ireader km
"10.5501"
127.0.0.1:6379>
3. 获取元素的 hash 值
geohash company jd
4. georadiusbymember
用来查询指定元素附近的其他元素
4.1 范围20公里以内最多 3个 元素 按距离 倒排,它不会排除自身
127.0.0.1:6379> georadiusbymember company jd 20 km
1) "jd"
2) "ireader"
127.0.0.1:6379> georadiusbymember company jd 20 km count 3
1) "jd"
2) "ireader"
127.0.0.1:6379> georadiusbymember company jd 20 km count 3 desc
1) "ireader"
2) "jd"
127.0.0.1:6379>
4.2 同时返回距离
georadiusbymember company jd 20 km count 3 desc withdist
5. 直接使用坐标
127.0.0.1:6379> georadius company 116.334255 40.027400 20 km count 3 desc withdist
1) 1) "juejin"
2) "12.9604"
2) 1) "xiaomi"
2) "0.0002"
127.0.0.1:6379>
6. 获取元素位置
geopos company jd
简单限流策略的意义在于,系统要限定用户的某个行为在指定的时间里只能发生 N 次。
这里使用的是一个 zset + 滑动窗口
核心思想是,zset包含一个 key、一个value、一个score
key = 用户名 + 动作
value = uuid 因为是不能重复的,也可以是时间戳
score = 时间戳
假设现在的需求是,一个用户,在一天之内,只能提交2次 评论,那么每次用户提交comment之后
用户提交2次评论,就会添加2条记录
127.0.0.1:6379> zadd lizhe_post_comment 202105211043 uuid_one
(integer) 1
127.0.0.1:6379> zadd lizhe_post_comment 202105211044 uuid_two
(integer) 1
127.0.0.1:6379>
下面开始一个标准添加流程
每次添加记录时,要先进行 数据清洗 + 数据统计
数据清洗 需要 zremrangebyscore , 它会删除分数区间段内的元素,参数时 起始 score 和 终止 score
这里再每次存储之前,因为需求是 1天之内,那么应该删除24小时之前的数据
zremrangebyscore lizhe_post_comment 0 202105201043
2. 第二步
然后进行统计
zcount lizhe_post_comment 0 202105211117
此时可以得到 没有过期的 操作记录数,这里是 2
然后判断是否符合 1天之内,提交2次的限制,决定是否继续
可以把 布隆过滤器 理解成一种特殊的,不太精确的set结构,当你使用它的 contains方法判断某个对象是否存在时,它可能会误判。
当 布隆过滤器 说某个值存在时,这个值可能不存在。
当 布隆过滤器 说某个值不存在时,这个值肯定不存在。
Bloom 过滤器是一种插件,也可以直接用安装好的镜像
docker run -i -t -d --name redisbloom -p 6379 redislabs/rebloom
下面是一些操作例子
127.0.0.1:6379> bf.add students lizhe
(integer) 1
127.0.0.1:6379> bf.add students jon
(integer) 1
127.0.0.1:6379> bf.exists students lizhe
(integer) 1
127.0.0.1:6379> bf.mexists students lizhe jon tony
1) (integer) 1
2) (integer) 1
3) (integer) 0
127.0.0.1:6379> bf.madd students user1 user2 user3
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> del students user1
(integer) 1
127.0.0.1:6379> del students user2
(integer) 0
127.0.0.1:6379> del students user3
(integer) 0
127.0.0.1:6379>
每个布隆过滤器对应的 redis 数据结构 是一个大型的 位数组 和几个不一样的 无偏 hash 函数
所谓无偏,就是能把元素的hash值算得比较均匀,让元素被hash映射到位数组中的位置比较随机。
向布隆过滤器中添加key时,会使用多个hash函数对key进行hash,算得一个整数索引值,然后对位数组长度进行取模算得一个位置,每个hash函数都会算得一个不同的位置,再把这些位置都设置为 1 , 就完成了 add 动作。
询问元素是否存在时,也会把hash再算一遍,确定出需要为 1 的位置,去和存储的内容进行比对,如果 1 的位置完全匹配,那么就为 “存在”,如果有一个位置为 0 , 就为 “不存在”,但是有些时候,如果 2个不同元素的 所有 1 的位置都相同,那么就会出现 “误报”
还有一个需要注意的地方是, 布隆过滤器不会误判那些 “已经见过的元素”,它只会误判那些没见过的元素。
也就是说误判会出现在,如果你给它一个没见过的元素,看看它是不是以为自己见过了
要降低 error_rate,就需要更大的存储空间,默认的 error_rate 是 0.01 , initial_size 是 100
如果要修改默认的参数需要调用
127.0.0.1:6379> bf.reserve students 0.001 1000
OK
127.0.0.1:6379>
布隆过滤器的 initial_size 太大会浪费存储空间,设置过小,会影响准确率。
那么如何来判断布隆过滤器的空间占用呢?这里有一个简单的公式
k = hash函数的最佳数量
n = 元素数量
f = 错误率
k = 0.7 * ( 1/n )
f = 0.6185 ^ ( 1/n )
^ 次方操作, math.pow
https://krisives.github.io/bloom-calculator
HyperLogLog的一个重要作用就是 计数 + 去重, 尤其适合做 UV 统计
HyperLogLog提供不精确的计数方案,标准误差 0.81%
127.0.0.1:6379> pfadd students lizhe
(integer) 1
127.0.0.1:6379> pfadd students lizhe
(integer) 0
127.0.0.1:6379> pfadd students lizhe
(integer) 0
127.0.0.1:6379> pfadd students Jon
(integer) 1
127.0.0.1:6379> pfadd students Jon
(integer) 0
127.0.0.1:6379> pfcount students
(integer) 2
127.0.0.1:6379>
分布式锁实际上就是 synchronized 关键字,在同一时间,只能有一个线程,进入当前 同步 代码块 (修改数据)
在 redis 中一般使用 setnx (set if not exists) 指令实现,通常会搭配 del 和 TTL 使用
如果 setnx 返回 1 ,证明当前无人占用,为了避免 del 之前发生异常,导致没有正确删除,会使用 TTL 设置过期时间
当锁存在时 (已经被人占用)
可是如果 expire 指令也没有得到正确执行呢?
Redis 2.8 版本中加入了一个 可以将 set 和 expire 合并的 原子操作
set lock_hello world ex 600 nx
如果没有抢到 锁 , 会得到一个 nil
如果逻辑代码的执行时间特别的长,而且不稳定,那对于锁的 TTL 就要慎重考虑
例如:
这里有两个问题
对于第二个问题可以通过将 local_key 的 value 值 设置成 随机数 来解决
张三 放入 随机数 5323 , 李四的 setnx 并不会改变这个值 (设置如果不存在),那么李四在del之前需要比较这个值是否与自己 setnx 的值一致,来决定是否删除这个值,这样每个 锁 都只能由 创建者 删除,或者 TTL 超时由系统删除
Redis 是 Remote Dictionary Service 远程字典服务的首字母缩写
Redis 的 5 种数据结构分别为 string、list、hash、set 和 zset 也就是 字符串、列表、字典、结合 和 有序集合
Redis 所有的数据结构都以唯一的 key 字符串作为名称,然后通过这个唯一的 key 来获取相应的 value 数据。
不同类型的数据结构的差异就在于 value 的结构不一样。
字符串内部是一个字符数组,类似Java的ArrayList,小于 1M 大小的空间时是全增的,直接 double 一倍容量,超过1M 大小时,每次增加 1M,最大 512M
虽然可以使用 set age 30 来存储 数字,而且可以使用 incr age 来使它自增,但是 数字实际上也是 string
2 列表
Redis 的 list 相当于Java的 LinkedList,是一种链表 而不是 数组。插入 和 删除 非常快 O(1),但是索引定位很慢 O(n),列表的每个元素都使用双向指针顺序,可以同时支持向前向后遍历。
在列表元素较少的情况下,会使用一块连续内存,这个结构是 ziplist,将所有的元素彼此紧挨着一起存储。当数据量较多的时候会改成普通链表,所以redis将列表和ziplist结合起来组成了 quicklist,也就是将 多个 ziplist 使用双向链表串起来使用。
3 hash字典
类似Java的HashMap是一种无序字典, 数组 + 链表 的 二维结构,第一维的 hash 数组位置碰撞时,就会将碰撞的元素使用链表串起来
Redis的字典值只能是 字符串
另外 java 的 hashmap 在 rehash 的时候需要阻塞,redis为了高性能不阻塞,采用的是渐进式rehash策略。
4 set集合
Redis 的集合 相当于 Java的 HashSet,内部是 无序键值对,并且是 唯一的。
它的内部实现是一个 所有value值都为 NULL的特殊字典。Set结构天生自带去重功能。
5 zset 有序列表
zset 类似 java 的 SortedSet 和 HashMap 的结合体,一方面它是一个set保证了value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。
zset 的内部实现使用的是一种叫做 跳跃列表 的数据结构
通用规则:
list、set、hash 和 zset 四种数据结构是容器类型数据结构
过期时间:
Redis 所有的数据结构都可以设置过期时间,时间到了,redis会自动删除对应的 对象。
需要注意的是,过期是以对象为单位的,如果hash结构的过期时间是整个hash对象,而不是某个key
另外,如果一个string对象设置了过期时间,然后你使用set方法修改了它,但是没有指定新的过期时间,它的过期时间会消失
使用 containous/whoami 作为 deploy ,这个镜像可以方便的看到 headers
Hostname: whoami-f449bb88d-9lldn
IP: 127.0.0.1
IP: ::1
IP: 10.42.0.139
IP: fe80::389a:bcff:fe2b:d62d
RemoteAddr: 192.168.194.183:43655
GET / HTTP/1.1
Host: 192.168.194.183:30087
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
DestinationRule
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: who-desrule
namespace: who
spec:
host: whoamisvc.who.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
Gateway
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: who-gateway
namespace: who
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*.com"
VirtualService
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: who-default
namespace: who
spec:
hosts:
- "who.lizhe.com"
gateways:
- who-gateway
http:
- route:
- destination:
host: whoamisvc.who.svc.cluster.local
subset: v1
在没开始加header之前我们看一下
然后我们修改 VirtualService来添加一个header
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: who-default
namespace: who
spec:
hosts:
- "who.lizhe.com"
gateways:
- who-gateway
http:
- headers:
request:
add:
lizhetoken: test123456789
route:
- destination:
host: whoamisvc.who.svc.cluster.local
subset: v1
不过如果你在请求中提前设置了 header,那么add动作将 不会覆盖 你设置的内容
curl who.lizhe.com -H lizhetoken:9999999
此时需要将 add改为 set
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: who-default
namespace: who
spec:
hosts:
- "who.lizhe.com"
gateways:
- who-gateway
http:
- headers:
request:
set:
lizhetoken: test123456789
route:
- destination:
host: whoamisvc.who.svc.cluster.local
subset: v1
而且 如果 header key 不存在, set 也会帮你添加
默认情况下,rancher会安装 nginx ingress,占用 80 和 443 端口,使用的是 hostport
此时,如果想要再使用 istio 的边界网关,就无法使用 80 端口了,例如这里我们使用的是 nodeport
rancher默认会使用 443,我们可以把 nginx ingress上的80端口拆掉
这里我替换成 82
然后修改 istio ingressgateway 的端口,添加一个 80,并映射到 8080
同理也可以切换 https
重点是找准 ingress 服务的deploy
Istio服务网格在逻辑上分为 控制平面 和 数据平面 两个部分。
其中 控制平面 Pilot 负责管理 和 配置代理 来路由流量,并配置 Mixer 以实施策略和收集遥测数据
数据平面由一组 Envoy 以sidecar的方式组成,这些代理可以调节和控制微服务及Mixer之间所有的网络通信。
Envoy 代理
进程外:Envoy是一个独立进程,Envoy 之间形成一个透明的通信网格,每个应用程序发送消息到本地主机或者从本地主机接收消息,无需关心网络拓扑
单进程多线程模型:Envoy使用了单进程多线程的架构模型。一个主线程管理各种琐碎的任务,而一些工作子线程负责执行监听、过滤 和 转发功能
下游(Downstream):连接到Envoy并发送请求,然后接收 response 的主机叫做下游
上游(Upstream):接收请求的主机叫上游
监听器:监听器是命名网络地址,包括 端口、unix domain socket 等,可以被下游主机连接。Envoy暴露一个或者多个监听器给下游主机连接。