美文网首页
服务端知识

服务端知识

作者: ES_KYW | 来源:发表于2021-11-06 11:35 被阅读0次

    进程、线程、协程

    进程
    进程是系统分配资源的最小单位,一个应用程序就是一个进程,每个进程都是相互独立的
    线程
    线程是程序运行的不同路径,每个进程至少有一个线程,可以有多个线程。线程有自己独立的堆栈、寄存器和共享进程资源。线程的多并发是系统对线程调度的上下文切换,CPU在不同时间片段执行不同的线程,每一次上下文切换都会中断当前线程,保留当前线程状态,切换到其它线程执行,过程是由用户态-> 内核态->用户态。相对协程而言会耗费更多内存和时间
    协程
    协程是一个轻量级的线程,它是一个特殊的函数,函数可以被挂起后执行。在Go中,由关键字go定义的函数就是一个协程,可以实现高并发,由系统调度切换,但不需要由用户态到内核态,协程的通讯不是共享内存,而是消息传递,在Go中协程的通讯是通过channel管道实现的。进程和线程上下文切换过程中,切换内容保存在内核栈中,协程则保存在自己的变量中(用户栈或者堆)


    用户态和内核态

    image.png
    image.png
    image.png

    用户态到内核态怎样切换?

    image.png
    image.png

    Go的协程

    Go的channel

    Go的select

    Go的切片

    • new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{};
    • make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:slice、map 和 channel;
      数组:arr := [...]int{1,2,3}
      切片:slice := []int{1,2,3}
      slice := make([]int,5,10)
      len(slice)长度 cap(slice)容量
      nil切片 var silce[]int
      空切片 slice := []{}
      对于底层数组容量是k的切片slice[i:j]来说
      长度:j-i
      容量:k-j
      slice[1:3],长度就是3-1=2,容量是5-1=4
    slice := []int{1, 2, 3, 4, 5}
    newSlice := slice[1:3]
    fmt.Printf("newSlice长度:%d,容量:%d",len(newSlice),cap(newSlice))
    

    Redis

    远程字典服务(Remote Dictionary Server),C语言写的,支持网络,可基于内存亦可持久化的日志型、Key-Value数据库。
    5种数据类型:String、Lists、hashes、Set、Zset
    还有范围查询:bitmaps、hyperloglogs和地理空间(geospatial)索引半径查询
    KV键值对,每秒10万+查询
    作用场景:内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统。
    优点:查找速度快
    缺点:数据无结构化,通常只被当作字符串或者二进制数据
    特性:
    1、多样的数据类型
    2、 持久化,定期把内存中的数据同步到磁盘中
    3、 集群
    4、 事物
    redis是单线程的,redis的瓶颈是机器内存和网络带宽。
    作用:

    1. 数据库
    2. 缓存
    3. 消息中间件MQ

    字符串
    微信公众号文章浏览量应用

    127.0.0.1:6379> set views 0
    OK
    127.0.0.1:6379> get views
    "0"
    127.0.0.1:6379> incr views
    (integer) 1
    127.0.0.1:6379> incr views
    (integer) 2
    127.0.0.1:6379> decr views
    (integer) 1
    127.0.0.1:6379> incrby views 10
    (integer) 11
    127.0.0.1:6379> 
    #GETRANGE
    127.0.0.1:6379> keys *
    1) "views"
    127.0.0.1:6379> set key1 "hello,123"
    OK
    127.0.0.1:6379> get key1
    "hello,123"
    127.0.0.1:6379> get range key 0 3
    (error) ERR wrong number of arguments for 'get' command
    127.0.0.1:6379> getrange key 0 3
    ""
    127.0.0.1:6379> getrange key1 0 3
    "hell"
    127.0.0.1:6379> getrange key1 0 -1
    "hello,123"
    127.0.0.1:6379> getrange key1 0 3 
    #setex (set with expire) #设置过期时间
    127.0.0.1:6379> setex key3 30 'hello'
    OK
    127.0.0.1:6379> ttl key3
    (integer) 22
    127.0.0.1:6379> ttl key3
    (integer) 19
    127.0.0.1:6379> 
    #setnx (set if not exist)  #如果key不存在  在分布式锁中会常常使用
    127.0.0.1:6379> setnx mykey 'redis'
    (integer) 1
    127.0.0.1:6379> setnx mykey 'mongodb'
    (integer) 0
    127.0.0.1:6379> get mykey
    "redis"
    127.0.0.1:6379> 
    #########################
    #批量设置,批量获取 mset mget
    127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
    OK
    127.0.0.1:6379> keys *
    1) "k2"
    2) "k3"
    3) "mykey"
    4) "k1"
    5) "views"
    6) "key1"
    127.0.0.1:6379> mget k1 k2 k3
    1) "v1"
    2) "v2"
    3) "v3"
    127.0.0.1:6379> msetnx k1 v1 k4 v4
    (integer) 0 #原子性操作,失败一个都会失败
    127.0.0.1:6379> 
    ##########################
    #对象
    127.0.0.1:6379> set user:1 {name:zhangsan,age:lisi} #设置一个user:1对象 值为json字符串来保存对象
    OK
    127.0.0.1:6379> get user:1
    "{name:zhangsan,age:lisi}"
    127.0.0.1:6379> 
    ##########################
    #getset
    127.0.0.1:6379> getset db redis
    (nil)
    127.0.0.1:6379> get db
    "redis"
    127.0.0.1:6379> getset db mongodb
    "redis"
    127.0.0.1:6379> get db
    "mongodb"
    127.0.0.1:6379>
    

    List
    栈、队列、阻塞

    
    127.0.0.1:6379> lpush list one
    (integer) 1
    127.0.0.1:6379> lpush list two
    (integer) 2
    127.0.0.1:6379> lpush list three
    (integer) 3
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "two"
    3) "one"
    127.0.0.1:6379> lrange list 0 1
    1) "three"
    2) "two"
    127.0.0.1:6379> rpush list 1
    (integer) 4
    127.0.0.1:6379> rpush list 0 -1
    (integer) 6
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "two"
    3) "one"
    4) "1"
    5) "0"
    6) "-1"
    127.0.0.1:6379> lpop list
    "three"
    127.0.0.1:6379> rpop list
    "-1"
    127.0.0.1:6379> 
    127.0.0.1:6379> lindex list 1
    "one"
    127.0.0.1:6379> 
    127.0.0.1:6379> llen list
    (integer) 4
    127.0.0.1:6379> 
    #移除指定数量的值
    127.0.0.1:6379> lrem list 1 one
    (integer) 1
    127.0.0.1:6379> lrange list 0 -1
    1) "two"
    2) "1"
    3) "0"
    127.0.0.1:6379> 
    #trim 修剪
    127.0.0.1:6379> ltrim list 1 2
    OK
    127.0.0.1:6379> lrange list 0 -1
    1) "1"
    2) "0"
    127.0.0.1:6379> 
    #####################
    #rpoplpush 移除列表最后一个元素并添加新的元素
    127.0.0.1:6379> lset list 0 'ww'
    OK
    127.0.0.1:6379> lrange list 0 -1
    1) "ww"
    2) "0"
    127.0.0.1:6379> 
    127.0.0.1:6379> linsert list  before "ww" "nn"
    (integer) 3
    127.0.0.1:6379> lrange list 0 -1
    1) "nn"
    2) "ww"
    3) "0"
    127.0.0.1:6379> 
    
    • List实际上是链表结构,before Note after ,left, right都可以插入
    • 如果key不存在,创建新的链表
    • 如果key存在,创建新的内容

    Set

    set中的值不能重复

    kuangdeMacBook-Pro:~ kuang$ redis-cli
    127.0.0.1:6379> sadd myset "kaung"
    (integer) 1
    127.0.0.1:6379> sadd myset 'xiao'
    (integer) 1
    127.0.0.1:6379> smembers myset
    1) "xiao"
    2) "kaung"
    127.0.0.1:6379> ismember myset xiao
    (error) ERR unknown command 'ismember'
    127.0.0.1:6379> sismember myset 'xiao'
    (integer) 1
    127.0.0.1:6379> 
    #获取集合中值的数量
    127.0.0.1:6379> scard myset
    (integer) 2
    127.0.0.1:6379> 
    #移除
    127.0.0.1:6379> srem myset 'xiao'
    (integer) 1
    127.0.0.1:6379> 
    #随机抽选出一个元素
    127.0.0.1:6379> srandmember myset
    "kaung"
    127.0.0.1:6379> 
    #随机移除集合中的元素
    127.0.0.1:6379> sadd myset 'k1'
    (integer) 1
    127.0.0.1:6379> sadd myset 'k2'
    (integer) 1
    127.0.0.1:6379> smembers myset
    1) "k2"
    2) "k1"
    3) "kaung"
    127.0.0.1:6379> spop myset
    "kaung"
    127.0.0.1:6379> spop myset
    "k1"
    127.0.0.1:6379> smembers myset
    1) "k2"
    127.0.0.1:6379> 
    #将指定的值移动到另外一个集合中
    127.0.0.1:6379> sadd myset 2
    (integer) 1
    127.0.0.1:6379> sadd myset2 'v1'
    (integer) 1
    127.0.0.1:6379> smove v1 myset
    (error) ERR wrong number of arguments for 'smove' command
    127.0.0.1:6379> smove  myset2 'v1' myset
    (integer) 0
    127.0.0.1:6379> smove  myset2  myset 'v1'
    (integer) 1
    127.0.0.1:6379> smembers myset2
    (empty list or set)
    127.0.0.1:6379> smove  myset  myset2 'v1'
    (integer) 1
    127.0.0.1:6379> smembers myset2
    1) "v1"
    127.0.0.1:6379> 
    # 差集、交集、并集  如微博共同关注
    127.0.0.1:6379> sadd key1 a
    (integer) 1
    127.0.0.1:6379> sadd key1 b
    (integer) 1
    127.0.0.1:6379> sadd key1 c
    (integer) 1
    127.0.0.1:6379> sadd key2 c
    (integer) 1
    127.0.0.1:6379> sadd key2 d
    (integer) 1
    127.0.0.1:6379> sadd key2 e
    (integer) 1
    127.0.0.1:6379> SDIFF key1 key2
    1) "b"
    2) "a"
    127.0.0.1:6379> SDIFF key2 key1
    1) "e"
    2) "d"
    127.0.0.1:6379> SINTER key1 key2
    1) "c"
    127.0.0.1:6379> SUNION key1 key2
    1) "e"
    2) "a"
    3) "c"
    4) "b"
    5) "d"
    127.0.0.1:6379> 
    
    

    Hash(哈希)

    Map集合,key-map,值是map集合 key field->value;
    hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合于字符串存储;

    127.0.0.1:6379> hset myhash field1 kuang
    (integer) 1
    127.0.0.1:6379> hget myhash field1
    "kuang"
    127.0.0.1:6379> hmset myhash field1 hello field2 world
    OK
    127.0.0.1:6379> hmget myhash field1 field2
    1) "hello"
    2) "world"
    127.0.0.1:6379> hgetall myhash
    1) "field1"
    2) "hello"
    3) "field2"
    4) "world"
    127.0.0.1:6379> hdel myhash field1
    (integer) 1
    127.0.0.1:6379> hgetall myhash
    1) "field2"
    2) "world"
    #长度
    127.0.0.1:6379> hlen myhash
    (integer) 1
    #是否存在
    127.0.0.1:6379> hexists myhash field1
    (integer) 0
    127.0.0.1:6379> hexists myhash field2
    (integer) 1
    #获取所有的keys
    127.0.0.1:6379> hkeys myhash
    1) "field2"
    #获取所有的值
    127.0.0.1:6379> hvals myhash
    1) "world"
    #自增
    127.0.0.1:6379> hset myhash field3 5
    (integer) 1
    127.0.0.1:6379> hincrby myhash field3 1
    (integer) 6
    #如果不存在添加
    127.0.0.1:6379> hsetnx myhash field4 hello
    (integer) 1
    127.0.0.1:6379> hsetnx myhash field4 world
    (integer) 0
    127.0.0.1:6379> 
    
    

    Zset(有序集合)

    在set的基础上加了值

    127.0.0.1:6379> zadd myzset 1 one
    (integer) 1
    127.0.0.1:6379> zadd myzset 2 two 3 three
    (integer) 2
    127.0.0.1:6379> zrange myzset 0 -1
    1) "one"
    2) "two"
    3) "three"
    127.0.0.1:6379> 
    # 实现排序
    127.0.0.1:6379> zadd salary 100 zhangsan
    (integer) 1
    127.0.0.1:6379> zadd salary 200 lisi
    (integer) 1
    127.0.0.1:6379> zadd salary 300 wangwu
    (integer) 1
    127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
    1) "zhangsan"
    2) "100"
    3) "lisi"
    4) "200"
    5) "wangwu"
    6) "300"
    127.0.0.1:6379> 
    
    127.0.0.1:6379> zrangebyscore salary -inf 250 withscores
    1) "zhangsan"
    2) "100"
    3) "lisi"
    4) "200"
    127.0.0.1:6379> 
    #移除 zrem
    127.0.0.1:6379> zrem salary lisi
    (integer) 1
    127.0.0.1:6379> 
    127.0.0.1:6379> zcard salary
    (integer) 2
    127.0.0.1:6379> 
    #从大到小排序  反转
    127.0.0.1:6379> zrevrange salary 0 -1
    1) "wangwu"
    2) "zhangsan"
    127.0.0.1:6379> 
    #获取指定区间的成员数量
    127.0.0.1:6379> zcount salary 100 200
    (integer) 2
    127.0.0.1:6379> 
    

    Geosptial地理位置

    3.2版本以后,可以推算地理位置的信息,两地之间的距离,方圆几里的人

    #添加地理位置
    #规则:两级无法直接添加,我们一般会下载城市数据,直接通过程序一次性写入
    127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
    (integer) 1
    127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
    (integer) 1
    127.0.0.1:6379> geopos china:city beijing
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    127.0.0.1:6379> geodist china:city beijing shanghai
    "1067378.7564"
    127.0.0.1:6379> georadius china:city 110 30
    (error) ERR wrong number of arguments for 'georadius' command
    127.0.0.1:6379> georadius china:city 110 30 1000 km
    (empty list or set)
    127.0.0.1:6379> georadius china:city 110 39 1000 km
    1) "beijing"
    127.0.0.1:6379> 
    #将二位的经纬度转为一维的字符串
    127.0.0.1:6379> geohash china:city beijing
    1) "wx4fbxxfke0"
    #移除
    127.0.0.1:6379> zrem china:city beijing
    (integer) 1
    127.0.0.1:6379> 
    

    hyperloglog基数统计的算法

    优点:占用的内存固定,2^64不同元素的技术,只需要128KB内存
    应用:网页的UV(一个人访问一个网站多次,但算作一个人)
    传统方式:set保存用户id,然后统计set中元素的数量作为判断标准

    127.0.0.1:6379> PFadd mykey a b c d e f g h i j
    (integer) 1
    127.0.0.1:6379> PFcount mykey
    (integer) 10
    127.0.0.1:6379> PFadd mykey2 i j z x c v b n m
    (integer) 1
    127.0.0.1:6379> PFCOUNT mykey2
    (integer) 9
    127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
    OK
    127.0.0.1:6379> PFCOUNT mykey3
    (integer) 15
    127.0.0.1:6379> 
    

    Bitmaps

    位存储
    统计用户信息,活跃,不活跃,登录,未登录,打卡,365打卡。两个状态的都可以使用Bitmaps
    Bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有0和1两个状态
    365天=365bit 1字节=8bit 46个字节左右

    使用bitmap来记录周一到周日到打卡
    周一:1 周二:0周三:0 周四:1 ...
    127.0.0.1:6379> setbit sign 0 1
    (integer) 0
    127.0.0.1:6379> setbit sign 1 0
    (integer) 0
    127.0.0.1:6379> setbit sign 2 0
    (integer) 0
    127.0.0.1:6379> setbit sign 3 1
    (integer) 0
    127.0.0.1:6379> setbit sign 4 1
    (integer) 0
    127.0.0.1:6379> setbit sign 5 0
    (integer) 0
    127.0.0.1:6379> setbit sign 6 0
    (integer) 0
    127.0.0.1:6379> 
    #查看某一天是否打卡
    127.0.0.1:6379> getbit sign 3
    (integer) 1
    #统计打卡的天数
    27.0.0.1:6379> bitcount sign
    (integer) 3
    127.0.0.1:6379> 
    

    Hyperloglog基数统计算法

    特点:占用内存固定,2^64个不同元素的技术,只需要12KB内存。
    什么是基数
    A{1,3,5,7,9} B{1,3,,7,8}
    基数:不重复的元素 = 5
    2.8.9就更新了Hyperlogolog数据结构
    网页的UV(一个人访问一个网站多次,但是还是算作一个人)
    传统方式:set保存用户的id,然后统计set中元素的数量作为标准。这种方式需要保存大量的用户ID。我们的目的是为了计数而不是保存用户ID。
    有0.81%的错误率!统计UV,可以忽略不计。

    127.0.0.1:6379> PFadd mykey a b c d e f g h i j
    (integer) 1
    127.0.0.1:6379> PFcount mykey
    (integer) 10
    127.0.0.1:6379> PFadd mykey2 i j z x c v b n m
    (integer) 1
    127.0.0.1:6379> PFCOUNT mykey2
    (integer) 9
    127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
    OK
    127.0.0.1:6379> PFCOUNT mykey3
    (integer) 15
    127.0.0.1:6379> 
    

    统计时,只统计不重复的数据,适合用作UV统计

    127.0.0.1:6379> pfadd key a b c a d
    (integer) 1
    127.0.0.1:6379> pfcount key
    (integer) 4
    127.0.0.1:6379> pfadd a a a a a f g d
    (integer) 1
    127.0.0.1:6379> pf count
    (error) ERR unknown command 'pf'
    127.0.0.1:6379> pfcount count
    (integer) 0
    127.0.0.1:6379> pfcount key
    (integer) 4
    127.0.0.1:6379> 
    

    事务

    Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
    一次性、顺序性、排他性!
    Redis事物没有隔离级别的概念
    所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行

    • Redis单条命令是原子性的,但事务不保证原子性
      redis事务
    • 开启事务(multi)
    • 命令入队
    • 执行事务 (exce)
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) OK
    3) "v2"
    4) OK
    127.0.0.1:6379> 
    #放弃事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> discard
    OK
    127.0.0.1:6379> get k2
    (nil)
    127.0.0.1:6379> get k2 
    
    #异常
    - 运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常!
    - 编译型异常(代码有问题!命令有错!),事务中的所有命令都不会被执行
    

    悲观锁

    • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁,效率低
      乐观锁
    • 很乐观,认为什么时候都不会出现问题,所有不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过数据。Watch监控.
    127.0.0.1:6379> set money 100
    OK
    127.0.0.1:6379> set out 0
    OK
    127.0.0.1:6379> watch money
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> DECRBY money 20
    QUEUED
    127.0.0.1:6379> INCRBY out 20
    QUEUED
    127.0.0.1:6379> exec #事务正常结束,数据期间没有发生变动,这个时候就正常执行
    1) (integer) 80
    2) (integer) 20
    127.0.0.1:6379> 
    #测试多线程修改值,使用watch监视money,可以当作redis的乐观锁
    如第一条线程还未执行exec时,又开启一个线程,直接修改money的值,再次执行exec就会失败
    

    使用watch可以当作redis的乐观锁(面试常问)

    redis内存满了的策略

    配置环境中设置
    LRU是Least Recently Used的缩写,即最近最少使用;

    1. volatile-lru:只对设置了过期时间的key进行LRU
    2. alleys-lru:删除lru算法的key
    3. volatile-random: 随机删除即将过期的key
    4. allkeys-random:随机删除
    5. volatile-ttl:删除即将过期的
    6. noeviction: 永不过期,返回错误

    持久化

    快照配置:
    如果900s内,如果至少有一个key进行了修改,我们及时进行持久化
    save 900 1
    默认使用RDB,可以在配置文件中开启AOF(appendonly)
    APPEND ONLY 模式 aof配置
    appendonly no #默认不是开启aof模式的,默认是使用rdb方式持久化,在大部分情况下,rdb完全够用。
    appendfilename 'appendonly.aof' #持久化的文件名字
    appendsysnc always #每次修改都会sync 消耗性能
    appendfsync everysec #每秒执行一次sync,可能会丢失这1s的数据
    appendfsync no #不执行sync,这个时候系统会自己同步数据,速度最快

    RDB(Redis DataBase)
    优点:

    1. 适合大规模数据的恢复!
    2. 对数据完整性的要求不高!
      缺点:
    3. 需要一定的时间间隔进行操作,如果意外宕机了,这个最后一次修改的数据就没有了。
    4. fork进程的时候,会占用一定的空间。

    有时候在生产环境会对rdb数据进行备份

    配置:save 900 1 默认配置不要随便修改

    在指定的时间内将内存中的数据集快照写入硬盘,也就是行话讲的S napshot快照,它恢复时将快照文件直接读到内存。
    Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加高效,RDB的缺点时最好一次持久化后的数据可能丢失。
    rdb保存的文件是dump.rdb ;

    如何恢复rdb文件

    1. 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据
    2. 查看需要存在的位置
    127.0.0.1:6379> config get dir
    1) "dir"
    2) "/Users/kuang/lnmp1"
    127.0.0.1:6379> 
    

    AOF(Append Only File)
    优点:

    1. 每一次修改都会同步;
    2. 每秒同步一次,可能会丢失一秒的数据
    3. 从不同步,效率最高
      缺点:
    4. 相对于数据文件来说,aof远远大于rdb,恢复的速度比rdb慢
    5. aof运行的效率也比rdb慢,所以我们redis默认的配置就是rdb持久化!

    将我们的所有命令都记录下来。
    以日志的形式来记录每一个写操作,将redis执行过的所有指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
    AOF保存的是appendonly.aof文件;
    如果aof文件有错误,这个时候redis是启动不起来的。redis提供了一个修复工具redis-check-aof

    kuangdeMacBook-Pro:~ kuang$ redis-check-aof --fix appendonly.aof
    #线程一
    
    127.0.0.1:6379> watch money
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set money 100
    QUEUED
    127.0.0.1:6379> set out 0
    QUEUED
    127.0.0.1:6379> exec  #线程二执行之后执行
    (nil)                                   #没有成功
    127.0.0.1:6379> 
    #线程二
    kuangdeMacBook-Pro:~ kuang$ redis-cli
    127.0.0.1:6379> set money 10
    OK
    127.0.0.1:6379> 
    
    #解除观察
    127.0.0.1:6379> unwatch
    OK
    127.0.0.1:6379> watch money
    
    如果事务修改失败,解锁,获取最新的值,再次观察,然后执行新的事务
    
    
    image.png
    拓展
    1. RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
    2. AOF持久化方式记录每次对服务器的写操作,当服务器重启的时候会重新执行这些命令来恢复原始数据,AOF命令以Redis协议追加保存每次写的操作到文件的末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的提及不至于太大
    3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你可以不使用任何持久化
    4. 同时开启两种持久化方式
    • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集完整
    • RDB的数据不实时,同时使用两者服务器重启也只会找到AOF文件,那要不要只使用AOF文件呢?不建议,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留作一个万一的手段
    1. 性能建议
    • 因为RDB文件只用作后备用途,建议在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
    • 如果Enable AOF,好处是在最恶劣的情况下也只会丢失不超过2秒的数据,启动脚本简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的,要是硬盘许可,尽可能减少rewrite频率。AOF重写基础文件大小默认64M太小,可以设置到5G。
    • 如果不Enable AOF,仅靠Master-Slave Replication实现高可用性也可以,代价是如果Master/Slave同时倒掉,会丢失几十分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入教新的那个。微博就是这种架构。

    发布订阅

    Redis发布订阅(pub/sub)是一种消息通讯模式:发送者发生消息,订阅者接收消息。微博的关注系统。
    Redis客户端可以订阅任何数量的频道。
    第一个:消息发送者 第二个:频道 第三个:消息订阅在
    场景:

    1. 实时消息系统!
    2. 实时聊天(频道当作聊天室,将信息回显给所有人即可)
    3. 订阅,关注系统
      稍微复杂的场景就会用消息中间件MQ()


      image.png

    测试

    #订阅频道  订阅端
    127.0.0.1:6379> subscribe kuang
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "kuang"
    3) (integer) 1
    #等待读取推送信息
    1) "message"
    2) "kuang"
    3) "hello"
    1) "message" # 消息
    2) "kuang"      # 哪个频道的消息
    3) "redis"        # 消息内容
    
    #向频道发送消息  发送端
    kuangdeMacBook-Pro:~ kuang$ redis-cli
    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379> publish kuang "hello"
    (integer) 1
    127.0.0.1:6379> publish kuang "redis"
    (integer) 1
    127.0.0.1:6379> 
    

    主从复制

    概念:主从复制,是指将一台Redis服务的数据,复制到其他的Redis服务器中,前者称为主节点Master以写为主,后者称为从节点Slave以读为主。数据复制是单向的,只能从主到从。
    主从复制的主要作用包括:
    1、数据冗余:主从复制实现了数据的热备份
    2、故障恢复:主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复

    3、 负责均衡:主负责写,从负责读
    4、高可用的基石:主从复制是哨兵和集群的基础。
    单台redis最大使用内存不应该超过20G。
    默认情况下,每台redis服务器都是主节点;且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。

    image.png

    配置:

    #info 查看信息
    127.0.0.1:6379> info
    # Server
    redis_version:4.0.9
    redis_git_sha1:00000000
    redis_git_dirty:0
    redis_build_id:7b8134d888556d22
    redis_mode:standalone
    os:Darwin 20.4.0 x86_64
    arch_bits:64
    multiplexing_api:kqueue
    atomicvar_api:atomic-builtin
    gcc_version:4.2.1
    process_id:31972
    run_id:64c18b1f659df291d920871f2dcf598f1523378d
    tcp_port:6379
    uptime_in_seconds:3649
    uptime_in_days:0
    hz:10
    lru_clock:14057410
    executable:/Users/kuang/redis-server
    config_file:
    
    # Clients
    connected_clients:2
    client_longest_output_list:0
    client_biggest_input_buf:0
    blocked_clients:0
    
    # Memory
    used_memory:1470864
    used_memory_human:1.40M
    used_memory_rss:1245184
    used_memory_rss_human:1.19M
    used_memory_peak:1470864
    used_memory_peak_human:1.40M
    used_memory_peak_perc:100.06%
    used_memory_overhead:1048576
    used_memory_startup:981040
    used_memory_dataset:422288
    used_memory_dataset_perc:86.21%
    total_system_memory:8589934592
    total_system_memory_human:8.00G
    used_memory_lua:37888
    used_memory_lua_human:37.00K
    maxmemory:0
    maxmemory_human:0B
    maxmemory_policy:noeviction
    mem_fragmentation_ratio:0.85
    mem_allocator:libc
    active_defrag_running:0
    lazyfree_pending_objects:0
    
    # Persistence
    loading:0
    rdb_changes_since_last_save:0
    rdb_bgsave_in_progress:0
    rdb_last_save_time:1624670098
    rdb_last_bgsave_status:ok
    rdb_last_bgsave_time_sec:0
    rdb_current_bgsave_time_sec:-1
    rdb_last_cow_size:0
    aof_enabled:0
    aof_rewrite_in_progress:0
    aof_rewrite_scheduled:0
    aof_last_rewrite_time_sec:-1
    aof_current_rewrite_time_sec:-1
    aof_last_bgrewrite_status:ok
    aof_last_write_status:ok
    aof_last_cow_size:0
    
    # Stats
    total_connections_received:2
    total_commands_processed:11
    instantaneous_ops_per_sec:0
    total_net_input_bytes:403
    total_net_output_bytes:20500
    instantaneous_input_kbps:0.00
    instantaneous_output_kbps:0.00
    rejected_connections:0
    sync_full:0
    sync_partial_ok:0
    sync_partial_err:0
    expired_keys:0
    expired_stale_perc:0.00
    expired_time_cap_reached_count:0
    evicted_keys:0
    keyspace_hits:0
    keyspace_misses:0
    pubsub_channels:1
    pubsub_patterns:0
    latest_fork_usec:1188
    migrate_cached_sockets:0
    slave_expires_tracked_keys:0
    active_defrag_hits:0
    active_defrag_misses:0
    active_defrag_key_hits:0
    active_defrag_key_misses:0
    
    # Replication
    role:master
    connected_slaves:0
    master_replid:ea9f90513a821a4d5997e2b28a531e158a0b8da7
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    second_repl_offset:-1
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    
    # CPU
    used_cpu_sys:2.29
    used_cpu_user:0.78
    used_cpu_sys_children:0.00
    used_cpu_user_children:0.00
    
    # Cluster
    cluster_enabled:0
    
    # Keyspace
    db0:keys=19,expires=0,avg_ttl=0
    127.0.0.1:6379> 
    
    image.png

    复制3个配置文件,然后修改对应的信息
    1、 端口
    2、 pid名字
    3、 log文件名字
    4、 dump.rdb名字
    启动


    image.png

    配置从机:
    临时配置:
    127.0.0.1:6379> slaveof 127.0.0.1 6379 #找谁当作自己的主机

    
    ![image.png](https://img.haomeiwen.com/i10144198/9cfb97e35e6b541a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    永久配置:
    在config中修改主机ip和端口
    >细节
    - 主机可写,从机不可写,写报错
    - 主机断开连接,从机依旧连接到主机,但是没有写操作。如果主机恢复正常后,依旧可以读取主机信息。
    >复制原理
    - slave启动成功连接到master后会发送一个sync同步命令
    - Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步
    - 全量复制:slave服务在收到数据库文件后,将其存盘并加载到内存中
    - 增量复制: Master继续将新的所有收集到的修改命令依次传给slave,完成同步
    - 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
    >如果主机断开了,可以使用SLAVEOF on one 使从节点变成主节点
    

    哨兵模式

    image.png

    哨兵模式能够后台监控主机是否故障,如果故障了根据 投票计数自动将从苦转化为主库,当主库恢复时,主库自动变为从库。
    哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行多个redis实例。
    哨兵的作用:

    • 通过发送命令,让redis服务器返回监控其运行状态,包括主机和从服务器
    • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机
      然尔一个哨兵进程对redis服务器进行监控,可能出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这就形成了多哨兵模式。
    image.png

    假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
    配置哨兵
    目前状态是一主二从
    1、 配置哨兵配置文件sentinel.conf

    #被监控的名称 host port 1 后面的这个数字1,代表主机挂了,slave投票看让谁来接替主机,票数最多的,就会成为主机
    kuangdeMacBook-Pro:~ kuang$ sentinel monitor myredis 127.0.0.1 6379 1
    
    

    2、 启动哨兵

    redis-sentinel monitor kconfig/sentinel.conf
    

    如果过Master节点断开,会自动选择以一个从机作为主机(有自己的算法)

    优点:

    1. 哨兵集群,基于主从复制,所有主从配置的优点,它全有
    2. 主从可以切换,故障可以转移,系统的可用性会更好
    3. 哨兵模式就是主从模式的升级,手动到自动,更加健壮
      缺点:
    4. redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
    5. 实现哨兵模式的配置其实是很麻烦的,里面有很多选择

    全部配置
    1、端口 2、工作目录 3、主机节点 4、主机密码 5、故障转移时间 6、通知脚本(如主机宕机了,发送邮件通知)


    缓存穿透和雪崩

    [图片上传中...(image.png-bbefaf-1624679561120-0)]

    缓存穿透:用户查询数据,缓存和数据库中都没能命中,查询失败,下次查询依旧需要访问数据库,导致数据库压力变大
    解决方案:

    1. 布隆过滤器--布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;


      image.png
    2. 缓存空对象--如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空键; 即使对空值设置过了过期时间,还是会存在缓存层和存储空间层会有一段时间窗口额度不一致,这对于需要保持一致性的业务会有影响。

    缓存击穿

    这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对着个点进行访问,当这个key在失效瞬间,持续的大并发就穿破缓存,直接请求数据库。
    解决方案:

    • 设置热点永不过期;
    • 加互斥锁:使用分布式锁(setnx),保证对于每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验较大。

    缓存雪崩

    在某个时间段 ,缓存集中过期失效 或者是缓存服务器某个节点出现宕机或者断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很可能瞬间就把数据库压垮
    解决方案:

    1. redis高可用:即搭建集群;
    2. 限流降级:加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询和写数据,其他线程等待
    3. 数据预热:在正式部署前,手动触发预先访问一遍 加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

    MySQL

    流程
    连接器->查询缓存->分析器->优化器->执行器

    慢查询

    -- 查询超过多少秒才记录
    show VARIABLES like 'long_query_time';
    -- 慢查询开启状态,慢查询日志存放位置
    show VARIABLES like 'slow_query%';
    -- 打开慢日志记录开关
    set GLOBAL slow_query_log = on;
    -- 设置慢查询日志
    set GLOBAL slow_query_log_file = '';
    -- 设置超过2秒慢日志
    set GLOBAL long_query_time = 2;
    --查看事务
    SELECT * FROM information_schema.INNODB_LOCKs;
    --查看锁
    SELECT * FROM information_schema.INNODB_LOCK_waits;
    --查看锁等待
    SELECT * FROM information_schema.INNODB_TRX; 
    
    kill   trx_mysql_thread_id
    

    关系型数据库和非关系型数据库
    关系型:(SQL)

    • 通过表和表之间,行和列之间的关系进行数据的存储
      非关系型:(NOT Noly SQL NoSQL)
    • redis,mongodb
    • 非关系型数据库,key-value形式,对象存储,通过对象的自身的属性来决定

    死锁
    多个事务同时占有同一个资源并相互作用,并请求锁定其他事务所占有的资源,从而导致相互依赖,相互等待出现死锁;
    解决办法:
    a.Innodb引擎提供了检测死锁的方法,能检测到死锁的相互依赖,系统直接报错,否则会出现非常慢的查询;
    b.如果查询时间超过设定的等待时间放弃锁请求;
    c.Innodb采用的死锁处理方式是,将持有最少行级排它锁的事务进行回滚。

    死锁配置:
    Innodb_lock_wait_timeout 超时时间设置

    行锁的本质是锁定主键索引
    如果只锁定的是非主键索引,会先锁定非主键索引,最终找到主键索引。
    如果没有索引会表锁定;

    查询表被锁定的方法
    show open TABLES where In_use >0;
    show processlist;


    InnoDB和MyISAM的区别
    InnoDB:

    • 支持事务
    • 外键约束
    • 数据行锁定

    MyISAM

    • 全文索引
    • 表空间较小

    物理空间位置
    所有的数据库文件都存在data目录下,一个文件夹对应一个数据库;
    本质还是文件的存储
    MySQL引擎在物理文件上的区别

    • InnoDB在数据库表中只有一个.frm文件,以及上级目录下的ibdata1文件
    • MYISAM 对应文件
      • .frm 表结构对应的文件
      • .MYD 数据文件(data)
    • *.MYI 索引文件(Index)

    设置数据库表的字符集编码
    支持中文的utf8

    修改表

    修改表名

    ALTER TABLE teacher RENAME AD teacher1;

    增加表的字段 字段名 列属性

    ALTER TABLE teacher1 ADD age INT(11);

    修改表的字段(修改约束)

    ALTER TABLE teacher1 MODIFY age VARCHAR(11)

    字段重命名

    ALTER TABLE teacher1 CHANGE age age1 INT(11);

    删除表的字段

    ALTER TABLE teacher1 DROP age1

    删除表

    DROP TABLE IF EXISTS teacher1

    ** MySQL数据管理**
    外键(了解即可)
    删除有外键关系的表的时候,必须要先删除引用别人的表(从表),再删除被引用的表(主表);


    JION连表查询(7种查询)

    inner join: 如果表中至少有一个匹配,就返回行 (交集为基准)
    left join: 会从左表中返回所有的值,即使右表中没有匹配 (左表为基准)
    right join:会从右表中返回所有的值,即使左表中没有匹配 (右表为基准)


    自连接及查询

    image.png

    -- 常用函数
    -- 数学运算
    SELECT ABS(-8) -- 绝对值
    SELECT CEILING(9.4) -- 向上取整
    SELECT FLOOR(9.4) -- 向下取整
    SELECT RAND() -- 返回一个 0-1 之间的随机数
    SELECT SIGN() -- 判断一个数的符号 0-0 负数返回-1 正数返回 1

    -- 字符串函数
    SELECT CHAR LENGTH('你好') -- 长度
    SELECT CONCAT('你','在','哪') -- 拼接字符串
    SELECT INSERT('我的编程',i,2,'php') -- 查询,从某个位置开始替换某个长度
    SELECT LOWER('Str')
    SELECT UPPER('str')
    SELECT INSTR('kaung','a') -- 查找位置a
    SELECT REPLACE('str','from_str','to_str')
    SELECT SUBSTR(str FROM pos FOR len)
    SELECT REVERSE(str)

    -- 日期函数

    SELECT CURRENT_DATE() -- 获取当前日期
    SELECT CURDATE() -- 获取当前日期
    SELECT NOW() -- 获取当前日期
    SELECT LOCALTIME() -- 获取本地时间
    SELECT SYSDATE()
    SELECT YEAR(now())
    SELECT DAY(NOW())
    

    DML语言

    聚合函数

    SELECT COUNT('name'); -- count(字段),会忽略所有的null值
    SELECT COUNT(1); -- count(1),会统计表中的所有数据,包含null,不会忽略所有的null值
    SELECT COUNT(*); -- count(*),会统计表中的所有数据,包含null,不会忽略所有的null值 如果多个列且没有主键 效率 count(1) > count(*)
    -- 执行效率COUNT(列) > count(1) > count(*)  如果只有一个字段 COUNT(*)最优
    SELECT SUM([DISTINCT] expr)
    SELECT AVG([DISTINCT] expr)
    SELECT MIN([DISTINCT] expr)
    SELECT MAX([DISTINCT] expr)
    
    -- having 用于过滤 group 分组
    SELECT SubjectName,AVG(studentResult) as 平均分,MAX(StudentResult) as 最高分,MIN(StudentResult) from result r
    INNER JOIN subject sub on r.SubjectNo = sub.SubjectNo GROUP BY r.SubjectNo HAVING 平均分 > 80
    

    事务(ACID)

    要么都成功,要么都失败
    将一组SQL放在一个批次中去执行
    原子性(一起执行不可分割)、一致性(前后状态一致)、隔离线、持久性(事务如果没有体检恢复到原来的状态,提交后不可逆)

    • 原子性:要么都成功,要么都失败
    • 一致性:事务前后的数据完整性要保持一致,如1000块转账,最终还是1000,不会出现A转出200B没有收到200的情况
    • 持久性: 事务一旦提交不可逆,被持久化到数据库中
    • 隔离性:事务的隔离性是多个用户并发访问的数据库时,数据库为每个用户开启的事务,不能被其他事务的操作数据所干扰,事务之间要相互隔离

    隔离所导致的问题
    脏读:指一个 事务 读取了另一个 事务 未提交的数据
    不可重复读:在一个事务内读取表中某一行数据,多次读取结果不同
    幻读:在一个事务内读取到了别人的事务插入的数据,导致前后读取数据不一致

    1、脏数据所指的就是未提交的数据。也就是说,一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条数据是处于待定状态的(可能提交也可能回滚),这时,第二个事务来读取这条没有提交的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被称为脏读。

    2、不可重复读(Non-Repeatable Reads):一个事务先后读取同一条记录,而事务在两次读取之间该数据被其它事务所修改,则两次读取的数据不同,我们称之为不可重复读。

    3、幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。

    4、幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检索范围为只读,这样就避免了幻读。


    MySQL备份
    数据库备份的方式

    • 直接拷贝物理文件(data文件)
    • 在可视化数据库工具中,手动导出
    • mysqldump在命令行导出
    #导出
    kuangdeMacBook-Pro:~ kuang$ mysqldump -hlocalhost -uroot -p123456 school student > D:/a.sql
    #导入
    进入mysql
    执行:source D:/a.sql
    

    主从复制

    1. 主数据库将改变的数据记录在本地的二进制日志中(binary log);
    2. 从数据库开启IO现程,将主库中的log,拷贝到自己的relay log(中继日志文件)中
    3. 从数据库通过SQL线程将日志文件数据读取到数据库中

    MYSQL主从复制,异步,串行,延迟

    master:slave = 1:n
    配置: 主机
    1.关闭防火墙,
    2.允许远程访问
    3.conf中配置server-id=1 唯一标识

    1. 配置二进制文件路径log-bin,放到data/mysql-bin目录下
    2. 错误记录文件路径log-err 放到data/mysql-err
    3. 主从同步时 忽略的数据库
      binlog-ingore-db=mysql
    4. (可选)指定主从同步时,同步哪些数据
      binlog-do-db=test
    5. 主机授权哪台计算机中的数据库是自己的从数据库


      image.png

    从机
    1.conf中配置server-id=2 唯一标识

    1. log-bin=mysql-bin 路径
    2. replicate-do-db=test
    3. linux中的数据 授权哪台计算机是自己的的主机
      5.进入从机的MySQL中配置主机相关信息
      如图:


      image.png
    image.png image.png

    开启主从同步
    开启后查询:
    show master status \G
    show slave status \G
    观察:Slave_IO_Running和Slave_SQL-Running是否正常


    读写分离(阿里云Mysql服务现在支持读写分离功能)

    主从同步的基础上,实现读写分离,需要中间件(MyCat)
    mysql中解决并发问题(脏读、幻读、不可重复读)方法:读写分离,因为读操作没有并发问题。如果不用读写分离需要加锁处理并发问题,效率低下
    mycat的作用

    MyCat实现MySQL读写分离,以及主库出现故障的时候从库自动变为写库。主库恢复后,建议方案是让之前的主库变为从库。

    关于读写分离数据一致性问题,MyCat提供了一个slaveThreshold延迟配置,延迟时间大于配置时间后,从库会被忽略掉,直接查询主库数据,防止读到旧数据。
    数据一致性问题,还有种方案,就是半同步复制,即主库在提交事务前会等待从库是否以及成功复制日志,若成功则提交。这样的话写效率会低

    1. 读写分离
    2. 分表分库
      • 水平拆分(MyCat):订单数据(订单数据1、订单数据2、订单数据3)
      • 垂直拆分 (微服务):系统(订单数据库、用户数据库)

    防止MyCat单点故障,MyCat集群部署;
    haproxy:入口分流,搭建多个MyCat集群;
    haproxy 也需要集群部署,防止单点故障

    集群:防止单点故障
    去中心化:

    1. 多个节点之间彼此发送心跳 (感知对方是否存活)
    2. 维护一个虚拟ip
      keepalived:发送心跳,虚拟IP和haproxy的IP绑定,外界访问虚拟IP时,实际指向实际绑定IP,若绑定的失效,自动绑定另外一个IP
    image.png

    完整实现,需要6台服务器

    MyCat配置


    image.png image.png image.png

    分库分表主键自增id问题
    文章:https://blog.csdn.net/bjweimengshu/article/details/80162731?ivk_sa=1024320u
    方案:生成全局唯一ID

    • UUID 32位 不推荐 无序

    • 数据库自增ID,每一次生成ID的时候,访问数据库,执行语句插入一条记录,生成一个ID,这样一来,每次都可以生成一个递增的ID。在分布式系统中可以利用DBproxy请求不同的分库,每个分库设置不同的初始值,步长和分库数量相等。这样一来DB1生成的ID是1、4、7、10,DB2生成的ID是2、5、8、11 DB3生成 3、6、9、12...
      缺点:ID的生成对数据库严重依赖,不但影响性能,而且一旦数据库挂点,服务将变的不可用

    • snowflake 雪花算法(推荐)
      id=0+41bit(时间戳)+10bit(工作机器id)+12bit(序列号)
      同一毫秒的ID数 = 1024*4096 = 4194304
      能够支持绝大场景下的高并发
      优点:

    1. 生成ID时不依赖于DB,完全在内存中生成,高性能高可用
    2. ID呈趋势递增,后续插入索引树的时候性能较好
      缺点:
    3. 依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。

    snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的 + 用其中的 41 bit 作为毫秒数 + 用 10 bit 作为工作机器 id + 12 bit 作为序列号。

    1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
    41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2^41 - 1 个毫秒值,换算成年就是表示69年的时间。
    10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5个机房(32个机房),每个机房里可以代表 2^5 个机器(32台机器)。
    12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2^12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。


    常见报错:
    Invalid DataSource:0 解决方案:防火墙、IP、端口、权限问题:临时开放全部权限

    image.png

    MongoDB

    NoSQL(not only sql)

    • 文档型数据库(bson格式和json一样)
    • 机于分布式文件存储的数据库,C++编写,主要用来处理大量的文档。
    • 介于关系型数据库和非关系型数据库的中间产品,是非关系型数据库中功能最丰富的,最像关系型数据库。

    应用场景:Web应用(key-value类似,Value是结构化的,不同的是数据库可以了解Value的内容)
    优点:数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构
    缺点:查询性能不高,缺乏统一的查询语句


    Cookie和Session


    Docker

    • 概念:容器、镜像、仓库;容器是镜像的实例话对象
    • Dockerfile 自定义镜像文件
    FROM centos
    RUN yum install wget
    RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
    RUN tar -xvf redis.tar.gz
    #以上执行会创建 3 层镜像。
    #build成镜像
    $ docker build -t centos:v3  .
    #在 Dockerfile 文件的存放目录下,执行构建动作。
    #以下示例,通过目录下的 Dockerfile 构建一个 centos:v3(镜像名称:#镜像标签)。注:最后的 . 代表本次执行的上下文路径,下一节会介绍。
    #上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。
    #如 COPY hom* /mydir/
    
    • 自定义网络,使用自定义网络,可以让容器间可以互ping联通
    • 端口映射
    • docker-compose
      Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
      Compose 使用的三个步骤:
    1. 使用 Dockerfile 定义应用程序的环境。
    2. 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
    3. 最后,执行 docker-compose up 命令来启动并运行整个应用程序。
    # yaml 配置实例
    version: "2.1"
    services:
        nginx:
            image: nginx
            ports:
                - "8086:80"
            volumes: 
                - /Users/kuang/lnmp/nginx/www:/usr/share/nginx/html
                - /Users/kuang/lnmp/nginx/conf:/etc/nginx/conf.d
                - /Users/kuang/lnmp/nginx/logs:/var/log/nginx
            networks:
                - lnmp-network
        php:
            image: php:5.6-fpm-alpine3.8
            volumes:
                - /Users/kuang/lnmp/nginx/www:/www
            networks:
                - lnmp-network
        mysql:
            image: mysql
            ports:
                - "3309:3306"
            environment:
                - MYSQL_ROOT_PASSWORD=123456
            networks:
                - lnmp-network
    networks: 
        lnmp-network:
    
    • 容器数据卷,容器期间共享数据,容器和宿主机共享数据。如:共享nginx的配置文件,共享PHP的项目目录到宿主机,在宿主机上修改代码就会同步
    • 容器的镜像是一层一层叠加上去的,使用的是联合文件系统技术,boosfs(内核)->rootfs(基础镜像centos/ubuntu)->镜像->镜像->容器


      image.png

    上传本地镜像

    dockerhub账户
    xiaoxaio 密码:a656842722
    
    image.png

    流程图

    image.png
    image.png

    自定义网路

    kuangdeMacBook-Pro:~ kuang$ docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
    kuangdeMacBook-Pro:~ kuang$ docker network inspect mynet
    
    # 使用自定义网路
    kuangdeMacBook-Pro:/ kuang$ docker run -d -P --name nginx-net-01 --net mynet nginx
    4595c1a1007c192d3879062e1d342a58b823708c2ffae0aec0c138b2e75e1a1b
    kuangdeMacBook-Pro:/ kuang$ docker run -d -P --name nginx-net-02 --net mynet nginx
    4e541782f0b9782894521cf43a9f426e5fc7b8a6063b9c4692592dc998ee157e
    
    #mynet中自动创建网络关联
    uangdeMacBook-Pro:~ kuang$ docker network inspect mynet
    [
        {
            "Name": "mynet",
            "Id": "668c48e5875c3c74f7d44697d7ccc8f4d1301530f8351dfaeb344d16a83a7edb",
            "Created": "2021-06-22T12:34:07.8664049Z",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": {},
                "Config": [
                    {
                        "Subnet": "192.168.0.0/16",
                        "Gateway": "192.168.0.1"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {
                "4595c1a1007c192d3879062e1d342a58b823708c2ffae0aec0c138b2e75e1a1b": {
                    "Name": "nginx-net-01",
                    "EndpointID": "373eba4c55a9f0df3ef2e06766a75875aa7446a05d6e681bfbc6175abe35f0c1",
                    "MacAddress": "02:42:c0:a8:00:02",
                    "IPv4Address": "192.168.0.2/16",
                    "IPv6Address": ""
                },
                "4e541782f0b9782894521cf43a9f426e5fc7b8a6063b9c4692592dc998ee157e": {
                    "Name": "nginx-net-02",
                    "EndpointID": "b609b3bdfa75f4fb9cd2eb06bf0978f65ab2af805de3d506ac657dec631b7d53",
                    "MacAddress": "02:42:c0:a8:00:03",
                    "IPv4Address": "192.168.0.3/16",
                    "IPv6Address": ""
                }
            },
            "Options": {},
            "Labels": {}
        }
    

    Nginx

    nginx启动的时候会创建一个master主现程和多个worker线程,主线程负责调度子线程,当有客户端访问时,子线程是争抢机制去获取资源。nginx支持热更新,当热更新时当前在用的子线程是不会停止更新的,当资源释放后会重新去更新。

    • 反向代理
      作为服务器的代理,隐藏真实服务器IP地址,对外暴露的是代理服务器地址。客户端只能访问代理服务器的地址,从代理服务器获取信息,至于代理服务器从哪一台真实服务器获取信息,客户端不知情。反向代理可以配置负载均衡
      正向代理,客服端代理,客户端需要配置代理服务器地址,客户端访问真实的服务器地址。客户端访问代理服务器,代理服务器转发请求,将数据返回给代理服务器,然后代理服务器将数据返回给客户端。如翻墙,客户端无法直接访问Google,通过代理可以访问。正向代理也叫转发代理,理论上可以访问任何域名。反向代理只能访问代理服务器域名。
    • 负载均衡
    1. 轮循
        upstream myserver {
            server 127.0.0.1:8086;
            server 127.0.0.1:8087;
        }
    
        server {
            listen       80;
            server_name 127.0.0.1 localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   /Applications/MxSrvs/www/esound_vip;
                index  index.html index.php;
                proxy_pass http://myserver;
                proxy_connect_timeout 10;
            }
    
    1. 权重
     upstream myserver {
            server 127.0.0.1:8086 weight=1;
            server 127.0.0.1:8087 weight=10;
        }
    
        server {
            listen       80;
            server_name 127.0.0.1 localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   /Applications/MxSrvs/www/esound_vip;
                index  index.html index.php;
                proxy_pass http://myserver;
                proxy_connect_timeout 10;
            }
    
    1. ip_hash
       upstream myserver {
            ip_hash;
            server 127.0.0.1:8086 weight=1 down;
            server 127.0.0.1:8087 weight=1;
        }
    
        server {
            listen       80;
            server_name 127.0.0.1 localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   /Applications/MxSrvs/www/esound_vip;
                index  index.html index.php;
                proxy_pass http://myserver;
                proxy_connect_timeout 10;
            }
    

    客户端请求过来的ip,映射成hash值,然后分配到一个特定的服务器里面。用户下次请求时都会固定访问该服务器,它的session不会跨域到其他的服务中。
    地址分配:hash(ip)%服务器数量 = 模值 每个模值对应一台服务器
    如:如3台服务器 t1、t2、t3 对应 0 1 2 5%3=2 这个hash值的客户端会分配到t3服务器。
    如何在nginx里面使用ip_hash?


    image.png

    直接添加ip_hash关键字即可,后续同一ip的访问将只会请求同一个服务器。

    注意事项

    1. 一旦使用了ip_hash,当我们需要移除一台服务器的时候,不能直接删除这个配置项,而是需要在这台服务器配置后面加上关键字down,表示不可用;
    2. 因为如果直接移除配置项,会导致hash算法发生更改,后续所有的请求都会发生混乱;
    
    • 高并发,高可用集群(keepalive)备份绑定虚拟IP机制
      两台nginx服务器,一个主服务器,一个备份服务器,当主服务器宕机的时候,自动切换到备份服务器。
      keepalived插件。主服务器IP绑定虚拟服务器IP,当主服务器宕机后,备份服务器IP绑定虚拟IP。
    1. 两台nginx服务
    2. 需要keepalived
    3. 需要一个虚拟ip
    

    keepalived安装配置


    image.png
    1. 脚本文件,检查nginx是否宕机
      [图片上传中...(image.png-a7b2cc-1624457694839-0)]


      image.png
    2. 虚拟ip配置


      image.png
    image.png
    • 动静分离
      nginx可以在该服务器下创建文件目录,存放图片,CSS,JS等静态资源,配置好后可以直接访问,提高效率
        upstream myserver {
            ip_hash;
            server 127.0.0.1:8086 weight=1 down;
            server 127.0.0.1:8087 weight=1;
        }
    
        server {
            listen       80;
            server_name 127.0.0.1 localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   /Applications/MxSrvs/www/esound_vip;
                index  index.html index.php;
                proxy_pass http://myserver;
                proxy_connect_timeout 10;
            }
            
            location /images/ {
                root   /Applications/MxSrvs/www/esound_vip/tp6/public/static/;
                autoindex on;
            }
    
    #请求地址
    http://localhost/images/home_cooperation_banner.png
    
    

    PHP-FPM

    image.png

    PHP内置PHP-FPM功能,需要手动设置开启
    PHP-FPM 负责管理一个进程池来处理来自 Web 服务器的 HTTP 动态请求,在 PHP-FPM 中,master 进程负责与 Web 服务器进行通信,接收 HTTP 请求,再将请求转发给 worker 进程进行处理,worker 进程主要负责动态执行 PHP 代码,处理完成后,将处理结果返回给 Web 服务器,再由 Web 服务器将结果发送给客户端。这就是 PHP-FPM 的基本工作原理。
    最大请求数:最大处理请求数是指一个php-fpm的worker进程在处理多少个请求后就终止掉,master进程会重新respawn一个新的。
    这个配置的主要目的是避免php解释器或程序引用的第三方库造成的内存泄露。
    pm.max_requests = 10240

    相关文章

      网友评论

          本文标题:服务端知识

          本文链接:https://www.haomeiwen.com/subject/zydmyltx.html