电商平台 lnmp 架构之 mysql 高速缓存--redis

1. redis的介绍

  • Redis 是一个 nosql(not only sql不仅仅只有sql) 数据库,翻译成中文叫做非关系型型数据库。Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)
  • redis 的应用场景
    1)热点数据的缓存
    由于redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作,这个功能最为常见,几乎所有的大型项目都有所运用。
    2)限时业务的运用
    redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
    3)计数器相关问题
    redis由于incrby命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。
    4) 排行榜相关问题
    关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。
    5) 分布式锁
    在并发进程中,我们通过锁(lock),来避免由于竞争而造成的数据不一致问题。通常,我们结合Lock的过期时间防止程序死锁。
    6)延时操作
    在用户提交订单后我们占用了库存,10分钟后去检验用户是够真正购买,如果没有购买将该单据设置无效,同时还原库存。所以我们对于上面的需求就可以用以下解决方案,我们在订单生成时,设置一个key,同时设置10分钟后过期, 我们在后台实现一个监听器,监听key的实效,监听到key失效时将后续逻辑加上。 当然我们也可以利用rabbitmq、activemq等消息中间件的延迟队列服务实现该需求。
    7)分页、模糊搜索
    redis的set集合中提供了一个zrangebylex方法, 这个方法可以返回字典区间的数据,利用这个特性可以进行模糊查询功能,这个也是目前我在redis中发现的唯一一个支持对存储内容进行模糊查询的特性。假如数据60万左右,响应时间在700ms左右,比mysql的like查询稍微快一点,但是由于它可以避免大量的数据库io操作,所以总体还是比直接mysql查询更利于系统的性能保障。
    8)点赞、好友等相互关系的存储
    Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。
    9)队列
    由于redis有list push和list pop这样的命令,所以能够很方便的执行队列操作。

2. redis服务的安装

此处直接从官网 https://redis.io/download下载最新版的来操作。

主节点

[root@server1 ~]# tar zxf redis-6.2.1.tar.gz 
[root@server1 ~]# cd redis-6.2.1	#由于已经有了 Makefile ,直接可以make
[root@server1 redis-6.2.1]# ll Makefile 
-rw-rw-r-- 1 root root 151 Mar  2 14:14 Makefile
[root@server1 redis-6.2.1]# make	
#make成功后会在src文件夹下产生一些二进制可执行文件,包括redis-server、redis-cli.
[root@server1 redis-6.2.1]# make install

make 过程中有什么报错,提示缺失的东西时,便要依次解决,直至成功。

安装完成之后,用原始的办法启动;

[root@server1 ~]# mkdir /etc/redis
[root@server1 ~]# mkdir /var/lib/redis
[root@server1 redis-6.2.1]# cp redis.conf /etc/redis/6379.conf
[root@server1 redis-6.2.1]# ll /etc/redis/6379.conf
-rw-r--r-- 1 root root 92222 Jun 27 20:57 /etc/redis/6379.conf

  75 bind 0.0.0.0 
  
 247 daemonize yes

 444 dir /var/lib/redis
[root@server1 utils]# cp redis_init_script /etc/init.d/redis_6379	#启动脚本
[root@server1 ~]# /etc/init.d/redis_6379 start
[root@server1 ~]# ps ax

 9721 ?        Ssl    0:00 /usr/local/bin/redis-server 0.0.0.0:6379
[root@server1 redis]# redis-cli
127.0.0.1:6379> info
# Server
redis_version:6.2.1
redis_git_sha1:00000000

# Replication
role:master
connected_slaves:0

从节点

由于主节点之前安装过 mysql,gcc 已经安装过,此处从节点需要先安装gcc,然后在make;如果先make ,没有gcc,即使安装好,也不能make,因为源码不够纯净。

[root@server2 ~]# yum install -y gcc
[root@server2 redis-6.2.1]# make
[root@server2 redis-6.2.1]# make install
[root@server2 redis-6.2.1]# mkdir /etc/redis
[root@server2 redis-6.2.1]# mkdir /var/lib/redis
#完成之后,将server1上改好的启动脚本复制过来即可

[root@server1 init.d]# scp redis_6379 server2:/etc/init.d/
[root@server1 redis]# scp 6379.conf  server2:/etc/redis/
[root@server2 redis-6.2.1]# /etc/init.d/redis_6379 start
[root@server2 init.d]# cd /etc/redis/
[root@server2 redis]# vim 6379.conf 

 468 replicaof 172.25.25.1 6379
[root@server2 redis]# /etc/init.d/redis_6379 stop
Stopping ...
Redis stopped
[root@server2 redis]# /etc/init.d/redis_6379 start
Starting Redis server...
[root@server2 redis]# redis-cli 
127.0.0.1:6379> info

# Replication
role:slave
master_host:172.25.25.1
master_port:6379
master_link_status:up

测试:

[root@server1 ~]# redis-cli 
127.0.0.1:6379> set name westos
OK
127.0.0.1:6379> get name
"westos"
127.0.0.1:6379> 

[root@server2 redis]# redis-cli 
127.0.0.1:6379> get name
"westos"
127.0.0.1:6379> 

redis 做slave 时,是只读的,当做为slave 时,所有数据均来自master ;保持有持久化。

以上的方式是原始的启动方式,接下来用systemd的方式来启动;
再用systemd 的方式时,make 时需要加上参数make USE_SYSTEMD=yes

[root@server1 utils]# pwd
/root/redis-6.2.1/utils
[root@server1 utils]# cp systemd-redis_server.service /usr/lib/systemd/system/redis_server.service
[root@server1 utils]# cd /usr/lib/systemd/system
#systemd 的方式需要更改这个
 18 [Unit]
 19 Description=Redis data structure server
 20 Documentation=https://redis.io/documentation
 21 #Before=your_application.service another_example_application.service
 22 AssertPathExists=/var/lib/redis
 23 Wants=network-online.target
 24 After=network-online.target
 25 
 26 [Service]
 27 #ExecStart=/usr/local/bin/redis-server --supervised systemd --daemonize no
 28 ## Alternatively, have redis-server load a configuration file:
 29 ExecStart=/usr/local/bin/redis-server /etc/redis/6379.conf
 30 ExecStop=/usr/local/bin/redis-cli -p 6379 shutdown
 31 ExecReload=/bin/kill -s HUP $MAINPID
 32 LimitNOFILE=10032
 33 NoNewPrivileges=yes
 34 #OOMScoreAdjust=-900
 35 PrivateTmp=yes
 36 Type=notify
 37 #TimeoutStartSec=infinity
 38 #TimeoutStopSec=infinity
 39 #UMask=0077
 40 #User=redis
 41 #Group=redis
 42 WorkingDirectory=/var/lib/redis

[root@server1 redis]# vim 6379.conf 
#除了以上改文件的内容还要再改一个这个
 265 supervised auto
[root@server1 system]# systemctl daemon-reload
[root@server1 system]# systemctl start redis_server.service 

3. Redis常用命令

redis支持的数据类型:
字符串(strings)
散列(hashes)
列表(lists)
集合(sets)
有序集合(sorted sets)

指令含义
config get *查看配置
select 1选择数据库,默认有16个库
flushdb清空当前数据库
flushall清空所有数据库
move key 1移动key
del key删除
rename oldkey newkey改名
expire key 10设置过期时间
persist key设置持久化
keys user*查询
exists key判断是否存在

4. Redis异步复制

redis主从复制:
slaveof 172.25.0.11 6379
min-slaves-to-write <slave 数量>
min-slaves-max-lag <秒数>
Redis 使用异步复制,因此无法确保 slave 是否实际接收到给定的写命令

[root@server1 redis]# redis-cli 
127.0.0.1:6379> CONFIG GET min-slaves-max-lag
1) "min-slaves-max-lag"
2) "10"		##默认是10s
127.0.0.1:6379> CONFIG GET min-slaves-to-write
1) "min-slaves-to-write"
2) "0"	##默认最小是0

以下三个节点全部安装redis,并且配置为systemd的启动方式;除了第一台之外,其他的再用源码包编译时需要加参数make USE_SYSTEMD=yes;也可以直接将1编译的二进制程序拷贝过去。

[root@server1 redis]# mv 6379.conf redis.conf
[root@server1 redis]# vim /usr/lib/systemd/system/redis_server.service 

 29 ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
[root@server1 redis]# vim /etc/redis/redis.conf 

2014 min-slaves-to-write 1
[root@server1 redis]# systemctl daemon-reload
[root@server1 redis]# systemctl reload redis_server.service

[root@server1 system]# cd /usr/local/bin/
[root@server1 bin]# rsync * -a server3:/usr/local/bin/
[root@server1 ~]# scp redis.conf server3:/etc/redis/
[root@server1 ~]# scp redis_6379 server3:/etc/init.d/

做好一主两从之后,接下来就需要做高可用。

5. Redis高可用

Redis Sentinel 为Redis提供了高可用的实现。通俗来说就是你可以部署一套无需人为干预即可防灾的Redis环境。RS同时为客户端提供了其他诸如监控,通知的功能。

  1. 监控: Redis Sentinel 时刻监控主从是否在正常工作。
  2. 通知: 当某个Redis实例出现问题,Redis Sentinel 可以通知系统管理员或者通过API通知其他程序。
  3. 自动切换: 如果一个主实例失效,Redis Sentinel 会启动一个失效转移(从升级为主)的进程,其他的从节点将重新跟随新的主节点。连接到RedisServer的会被通知切换到新的地址。
  4. 配置提供者: Redis Sentinel 充当了客户端服务自动发现的提供者:连接到Sentinal的客户端,Sentianl会响应最新的主节点地址给客户端,并且当发生转移的时候会发送通知。

配置

[root@server1 ~]# cd redis-6.2.1
[root@server1 redis-6.2.1]# cp sentinel.conf /etc/redis/
[root@server1 redis-6.2.1]# cd /etc/redis/
[root@server1 redis]# ls
redis.conf  sentinel.conf
[root@server1 redis]# vim sentinel.conf 

 84 sentinel monitor mymaster 172.25.25.1 6379 2	#指向master 
 
125 sentinel down-after-milliseconds mymaster 10000	#超时10s
[root@server1 redis]# scp sentinel.conf server2:/etc/redis/	#改完以后将数据同步,启动之后数据会变化
sentinel.conf                                               100%   13KB   9.4MB/s   00:00    
[root@server1 redis]# scp sentinel.conf server3:/etc/redis/
sentinel.conf                                               100%   13KB  10.3MB/s   00:00  

使用:直接用命令来启动

[root@server1 ~]# redis-sentinel /etc/redis/sentinel.conf 
3792:X 28 Jun 2021 20:56:42.294 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3792:X 28 Jun 2021 20:56:42.294 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=3792, just started
3792:X 28 Jun 2021 20:56:42.294 # Configuration loaded
3792:X 28 Jun 2021 20:56:42.295 * Increased maximum number of open files to 10032 (it was originally set to 1024).
3792:X 28 Jun 2021 20:56:42.295 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.1 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 3792
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

3792:X 28 Jun 2021 20:56:42.295 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
3792:X 28 Jun 2021 20:56:42.302 # Sentinel ID is fea135a7e203f3e895aece3ddc0ee4844a186d83
3792:X 28 Jun 2021 20:56:42.302 # +monitor master mymaster 172.25.25.1 6379 quorum 2
3792:X 28 Jun 2021 20:56:42.303 * +slave slave 172.25.25.2:6379 172.25.25.2 6379 @ mymaster 172.25.25.1 6379
3792:X 28 Jun 2021 20:56:42.308 * +slave slave 172.25.25.3:6379 172.25.25.3 6379 @ mymaster 172.25.25.1 6379

#当再启一个sentinel时,所有节点是时时同步的
4438:X 28 Jun 2021 20:57:50.220 * +sentinel sentinel fea135a7e203f3e895aece3ddc0ee4844a186d83 172.25.25.1 26379 @ mymaster 172.25.25.1 6379

此时三个节点都开启sentinel ,再开一个server1的终端,来模拟主从切换。

[root@server1 ~]# redis-cli 
127.0.0.1:6379> SHUTDOWN
not connected> 
在server1下线之后10s后会选出新的 master,在server1端sentinel的页面可以看到切换的过程。此时的master 是server3。

3792:X 28 Jun 2021 21:04:37.497 # +switch-master mymaster 172.25.25.1 6379 172.25.25.3 6379
3792:X 28 Jun 2021 21:04:37.497 * +slave slave 172.25.25.2:6379 172.25.25.2 6379 @ mymaster 172.25.25.3 6379
3792:X 28 Jun 2021 21:04:37.497 * +slave slave 172.25.25.1:6379 172.25.25.1 6379 @ mymaster 172.25.25.3 6379
3792:X 28 Jun 2021 21:04:47.574 # +sdown slave 172.25.25.1:6379 172.25.25.1 6379 @ mymaster 172.25.25.3 6379
#+sdown表示主观下线状态

此时可以远程连接serve2和server3来查看是否已经切换过来,然后可以再开启server1,此时serer1自动为slave状态。并且配置信息也会自动更改过来。

[root@server1 ~]# redis-cli -h 172.25.25.3
172.25.25.3:6379> info

# Replication
role:master
connected_slaves:1
min_slaves_good_slaves:1
slave0:ip=172.25.25.2,port=6379,state=online,offset=145738,lag=0
master_failover_state:no-failover

此时让server1在此上线,然后再查看
# Replication
role:master
connected_slaves:2
min_slaves_good_slaves:2
slave0:ip=172.25.25.2,port=6379,state=online,offset=174042,lag=0
slave1:ip=172.25.25.1,port=6379,state=online,offset=174042,lag=0

除了自动故障切换和通知之外,还会做相应的配置更改。

[root@server1 ~]# redis-cli 
127.0.0.1:6379> CONFIG GET replicaof
1) "replicaof"
2) "172.25.25.3 6379"
127.0.0.1:6379> 
#自动更改配置信息
[root@server1 ~]# tail -n3 /etc/redis/redis.conf 
save 60 10000
user default on nopass ~* &* +@all
replicaof 172.25.25.3 6379

由于 redis 的数据完全来自 master,当 master 没有启动时,slave 是启动不了的,因为它会在一直连接master。

之前做的是一主两从的架构,接下来先将主从结构都转换为master,只需删除配置文件/etc/redis/redis.conf中的 replicaof的信息即可将从变为master;然后就可以做多组的主从结构,让 sentinel 可以多监控几个master。

6. twemproxy分片代理

官方地址:https://github.com/twitter/twemproxy

  • Twemproxy 是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个 Redis 服务器,再原路返回。该方案很好的解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived 做高可用方案。通过 Twemproxy 可以使用多台服务器来水平扩张 redis 服务,可以有效的避免单点故障问题。虽然使用Twemproxy需要更多的硬件资源和在redis性能有一定的损失(twitter测试约20%),但是能够提高整个系统的HA也是相当划算的。twemproxy不光可以代理 redis,还可以代理 memcached。
  • 集群
    解决方案:
    由于 Redis 出众的性能,其在众多的移动互联网企业中得到广泛的应用。Redis 在 3.0 版本前只支持单实例模式,虽然现在的服务器内存可以到 100GB、200GB 的规模,但是单实例模式限制了 Redis 没法满足业务的需求。Redis 到 2015 年才发布正式版集群。各大企业在 3.0 版本还没发布前为了解决 Redis 的存储瓶颈,纷纷推出了各自的 Redis 集群方案。这些方案的核心思想是把数据分片(sharding)存储在多个 Redis 实例中,每一片就是一个 Redis 实例。
  • 下面介绍 Redis 的集群方案。
  1. 客户端分片
    客户端分片是把分片的逻辑放在 Redis 客户端实现,通过 Redis 客户端预先定义好的路由规则,把对 Key 的访问转发到不同的 Redis 实例中,最后把返回结果汇集。
    客户端分片的好处是所有的逻辑都是可控的,不依赖于第三方分布式中间件。开发人员清楚怎么实现分片、路由的规则,不用担心踩坑。

  2. 客户端分片方案有下面这些缺点

    这是一种静态的分片方案,需要增加或者减少 Redis 实例的数量,需要手工调整分片的程序。
    可运维性差,集群的数据出了任何问题都需要运维人员和开发人员一起合作,减缓了解决问题的速度,增加了跨部门沟通的成本。
    在不同的客户端程序中,维护相同的分片逻辑成本巨大。例如,系统中有两套业务系统共用一套 Redis 集群,一套业务系统用 Java 实现,另一套业务系统用 PHP 实现。为了保证分片逻辑的一致性,在 Java 客户端中实现的分片逻辑也需要在 PHP 客户端实现一次。相同的逻辑在不同的系统中分别实现,这种设计本来就非常糟糕,而且需要耗费巨大的开发成本保证两套业务系统分片逻辑的一致性。

  3. Twemproxy
    Twemproxy 是由 Twitter 开源的 Redis 代理,其基本原理是:Redis 客户端把请求发送到 Twemproxy,Twemproxy 根据路由规则发送到正确的 Redis 实例,最后 Twemproxy 把结果汇集返回给客户端。
    Twemproxy 通过引入一个代理层,将多个 Redis 实例进行统一管理,使 Redis 客户端只需要在 Twemproxy 上进行操作,而不需要关心后面有多少个 Redis 实例,从而实现了 Redis 集群。

Twemproxy 的优点如下
客户端像连接 Redis 实例一样连接 Twemproxy,不需要改任何的代码逻辑。
支持无效 Redis 实例的自动删除。
Twemproxy 与 Redis 实例保持连接,减少了客户端与 Redis 实例的连接数。

Twemproxy 有如下不足
由于 Redis 客户端的每个请求都经过 Twemproxy 代理才能到达 Redis 服务器,这个过程中会产生性能损失。
没有友好的监控管理后台界面,不利于运维监控。
最大的问题是 Twemproxy 无法平滑地增加 Redis 实例。对于运维人员来说,当因为业务需要增加 Redis 实例时工作量非常大。
Twemproxy 作为最被广泛使用、最久经考验、稳定性最高的 Redis 代理,在业界被广泛使用。

Twemproxy 不能平滑增加 Redis 实例的问题带来了很大的不便,于是豌豆荚自主研发了 Codis,一个支持平滑增加载 Redis 实例的 Redis 代理软件。

部署:
新开一台主机,来做 redis的代理;下载所需要的包;将下载的包解压,

[root@server4 ~]# ls
MHA-7                                           twemproxy-master
mysql-router-community-8.0.21-1.el7.x86_64.rpm  twemproxy-master.zip
[root@server4 ~]# cd twemproxy-master
[root@server4 twemproxy-master]# ls
ChangeLog     contrib  Makefile.am  NOTICE     src
conf          LICENSE  man          README.md  tests
configure.ac  m4       notes        scripts    travis.sh
发现没有 configure 没办法编译。查看 README.md 安装方法。
[root@server4 twemproxy-master]# yum install -y automake libtool
[root@server4 twemproxy-master]# autoreconf -fvi
[root@server4 twemproxy-master]# ./configure 
[root@server4 twemproxy-master]# make
[root@server4 twemproxy-master]# make install

此时便已经安装完成,修改配置文件并启动;

[root@server4 conf]# pwd
/root/twemproxy-master/conf
[root@server4 conf]# ls
nutcracker.leaf.yml  nutcracker.root.yml  nutcracker.yml
[root@server4 conf]# mkdir /etc/twemproxy
[root@server4 conf]# cp nutcracker.yml /etc/twemproxy/
[root@server4 twemproxy]# vim nutcracker.yml 
[root@server4 twemproxy]# cat nutcracker.yml
alpha:
  listen: 0.0.0.0:22121
  hash: fnv1a_64
  distribution: ketama
  auto_eject_hosts: true
  redis: true
  server_retry_timeout: 2000
  server_failure_limit: 1
  servers:
   - 172.25.25.1:6379:1 app1
   - 172.25.25.2:6379:1 app2
   - 172.25.25.3:6379:1 app3
[root@server4 twemproxy]# nutcracker -d -c /etc/twemproxy/nutcracker.yml 

在代理端需要用到redis来访问,需要在改主机上部署redis的客户端;此处直接将server1 上的复制过来;然后访问测试:

[root@server1 local]# cd bin/
[root@server1 bin]# ls
goaccess  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
[root@server1 bin]# scp redis-cli root@172.25.25.4:/usr/local/bin/

[root@server4 ~]# redis-cli -p 22121
127.0.0.1:22121> get name
"westos"

此时查看到的信息,不知道是访问的那个后端,因为现在三个都为master,并且信息一致。删除 master 上的信息;

[root@server1 ~]# redis-cli 
127.0.0.1:6379> get name
"westos"
127.0.0.1:6379> DEL name
(error) NOREPLICAS Not enough good replicas to write.
#出现这种情况是因为,之前在做主从时,配置文件中有最小的集群数,现在没有 slave 了,所以会有问题;
#注释掉配置文件中的该行 min-replicas-to-write 1 内容即可。
[root@server1 redis]# systemctl restart redis_server
[root@server1 redis]# redis-cli 
127.0.0.1:6379> get name
"westos"
127.0.0.1:6379> DEL name
(integer) 1
127.0.0.1:6379> 

依次删除所有的之后,此时在此测试:

[root@server4 ~]# redis-cli -p 22121
127.0.0.1:22121> get name
(nil)
127.0.0.1:22121> set name westos
OK
127.0.0.1:22121> get name
"westos"
127.0.0.1:22121> set demo test
OK
127.0.0.1:22121> get demo
"test"

此时在后端可以查看到存放的信息在那一台主机上;当不停的写入时会做自均衡。
此时如果后端存入信息的主机宕掉,此时便查看不到信息。

127.0.0.1:22121> get name
(error) ERR Connection refused

此时server1已经挂掉了,如果此时再次写入信息,在访问时,并不能将其去除。也就是说此用来写数据还是有很大的问题。

127.0.0.1:22121> set name westos
(error) ERR Connection refused
127.0.0.1:22121> set name westos
OK
127.0.0.1:22121> get name 
(error) ERR Connection refused
127.0.0.1:22121> get name 
"westos"
127.0.0.1:22121> get name 
"westos"
127.0.0.1:22121> get name 
(error) ERR Connection refused
127.0.0.1:22121> get name 
"westos"

7. redis cluster 集群

Redis集群介绍

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。

Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:

自动分割数据到不同的节点上。
整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

Redis 集群的数据分片

Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

节点 A 包含 0 到 5500号哈希槽.
节点 B 包含5501 到 11000 号哈希槽.
节点 C 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

Redis 集群的主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。

在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。

然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了。
不过当B和B1 都失败后,集群是不可用的。

Redis 一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.

第一个原因是因为集群是用了异步复制. 写操作过程:

客户端向主节点B写入一条命令.
主节点B向客户端回复命令状态.
主节点将写操作复制给他得从节点 B1, B2 和 B3.

主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.

注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项。

搭建并使用Redis集群

搭建集群的第一件事情我们需要一些运行在 集群模式的Redis实例. 这意味这集群并不是由一些普通的Redis实例组成的,集群模式需要通过配置启用,开启集群模式后的Redis实例便可以使用集群特有的命令和特性了.

下面是一个最少选项的集群的配置文件:在redis的源码包目录中,有自带的启动脚本

[root@server1 create-cluster]# pwd
/root/redis-6.2.1/utils/create-cluster
[root@server1 create-cluster]# ./create-cluster start
Starting 30001
Starting 30002
Starting 30003
Starting 30004
Starting 30005
Starting 30006
[root@server1 create-cluster]# ./create-cluster create
#创建
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:30005 to 127.0.0.1:30001
Adding replica 127.0.0.1:30006 to 127.0.0.1:30002
Adding replica 127.0.0.1:30004 to 127.0.0.1:30003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: b43ba4bc561d0b66e82a060bf221f6e1425192cb 127.0.0.1:30001
   slots:[0-5460] (5461 slots) master
M: 765296748db5d2394b9f9636b932f2dc298b55ce 127.0.0.1:30002
   slots:[5461-10922] (5462 slots) master
M: 82c7472485d12e7fe5af7de3e6308441d033bee7 127.0.0.1:30003
   slots:[10923-16383] (5461 slots) master
S: 5b2ac69c54cac316bf04fa17e9a82fbd49de144a 127.0.0.1:30004
   replicates 82c7472485d12e7fe5af7de3e6308441d033bee7
S: 3d1d5834a23f2dd368aa8217005e7d4310e522be 127.0.0.1:30005
   replicates b43ba4bc561d0b66e82a060bf221f6e1425192cb
S: b750f0216c0999b6891a4482070d96dd2e428dc5 127.0.0.1:30006
   replicates 765296748db5d2394b9f9636b932f2dc298b55ce
[root@server1 create-cluster]# redis-cli --cluster help
[root@server1 create-cluster]# redis-cli --cluster check 127.0.0.1:30001
127.0.0.1:30001 (b43ba4bc...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:30002 (76529674...) -> 0 keys | 5462 slots | 1 slaves.
127.0.0.1:30003 (82c74724...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 127.0.0.1:30001)
M: b43ba4bc561d0b66e82a060bf221f6e1425192cb 127.0.0.1:30001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 5b2ac69c54cac316bf04fa17e9a82fbd49de144a 127.0.0.1:30004
   slots: (0 slots) slave
   replicates 82c7472485d12e7fe5af7de3e6308441d033bee7
M: 765296748db5d2394b9f9636b932f2dc298b55ce 127.0.0.1:30002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 82c7472485d12e7fe5af7de3e6308441d033bee7 127.0.0.1:30003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: b750f0216c0999b6891a4482070d96dd2e428dc5 127.0.0.1:30006
   slots: (0 slots) slave
   replicates 765296748db5d2394b9f9636b932f2dc298b55ce
S: 3d1d5834a23f2dd368aa8217005e7d4310e522be 127.0.0.1:30005
   slots: (0 slots) slave
   replicates b43ba4bc561d0b66e82a060bf221f6e1425192cb
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

集群是去中心化,内部自带高可用,当有一个master 挂掉之后,便自动接管;当有一个槽坏了之后,集群就不可用。并且当集群中master 一次挂掉过半的话,可能会发生错误。redis集群是比较重量级的,企业中选择应该综合考虑,建议考虑codis。

用自带的脚本创建的集群默认是有6个节点,3个master,3个slave。也可以继续添加,添加式默认是master,当需要添加slave 时,需要用参数来指定为那个 master 添加 slave;默认添加的节点是没有哈希槽的,可以用交互的方式来为其添加哈希槽。

8. redis 的持久化

官方文档:http://redis.cn/topics/persistence.html
Redis 提供了不同级别的持久化方式:

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:

RDB的优点

RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
RDB的缺点
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.
AOF 优点
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
AOF 缺点
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。

Note: 因为以上提到的种种原因, 未来我们可能会将 AOF 和 RDB 整合成单个持久化模型。 (这是一个长期计划。) 接下来的几个小节将介绍 RDB 和 AOF 的更多细节。
快照

在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE或者 BGSAVE , 手动让 Redis 进行数据集保存操作。

比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集。

appendonly yes #启用AOF持久化方式
appendfsync everysec #每秒钟强制写入磁盘一次

9. redis 与 mysql 的结合

此处用 rpm 包来将其结合。
在server2 上安装 php 和 nginx;
在server3 上安装 redis,且为 master 的状态;
在server4 上部署数据库。

在这里插入图片描述

[root@server2 rhel7]# ls
gearmand-1.1.12-18.el7.x86_64.rpm          php-mysql-5.4.16-46.el7.x86_64.rpm
libevent-devel-2.0.21-4.el7.x86_64.rpm     php-pdo-5.4.16-46.el7.x86_64.rpm
libgearman-1.1.12-18.el7.x86_64.rpm        php-pecl-gearman-1.1.2-1.el7.x86_64.rpm
libgearman-devel-1.1.12-18.el7.x86_64.rpm  php-pecl-igbinary-1.2.1-1.el7.x86_64.rpm
libzip-0.10.1-8.el7.x86_64.rpm             php-pecl-redis-2.2.8-1.el7.x86_64.rpm
openssl-1.0.2k-16.el7.x86_64.rpm           php-process-5.4.16-46.el7.x86_64.rpm
openssl-libs-1.0.2k-16.el7.x86_64.rpm      php-xml-5.4.16-46.el7.x86_64.rpm
php-cli-5.4.16-46.el7.x86_64.rpm           test.php
php-common-5.4.16-46.el7.x86_64.rpm        test.sql
php-fpm-5.4.16-46.el7.x86_64.rpm           worker.php
[root@server2 rhel7]# yum install -y php-* libgearman-* libevent-*
[root@server2 rhel7]# systemctl start  php-fpm.service


[root@server2 conf]# cp nginx.conf.default nginx.conf
[root@server2 conf]# vim nginx.conf

 65         location ~ \.php$ {
 66             root           html;
 67             fastcgi_pass   127.0.0.1:9000;
 68             fastcgi_index  index.php;
 69         #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
 70             include        fastcgi.conf;
 71         }
[root@server2 ~]# vim .bash_profile 
[root@server2 ~]# source .bash_profile
[root@server2 ~]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@server2 ~]# nginx 

[root@server3 ~]# systemctl start redis_server

[root@server4 ~]# yum install -y mariadb-server.x86_64
[root@server4 ~]# systemctl start mariadb

在默认发布目录中写一个信息:

[root@server2 rhel7]# cd /usr/local/nginx/html/
[root@server2 html]# ls
50x.html  download     index.html  memcache.php  report.html
bbs       example.php  index.php   phpadmin      test.php
[root@server2 html]# cat test.php 
<?php
        $redis = new Redis();
        $redis->connect('172.25.25.3',6379) or die ("could net connect redis server");
  #      $query = "select * from test limit 9";
        $query = "select * from test";
        for ($key = 1; $key < 10; $key++)
        {
                if (!$redis->get($key))
                {
                        $connect = mysql_connect('172.25.25.4','redis','westos');
                        mysql_select_db(test);
                        $result = mysql_query($query);
                        //如果没有找到$key,就将该查询sql的结果缓存到redis
                        while ($row = mysql_fetch_assoc($result))
                        {
                                $redis->set($row['id'],$row['name']);
                        }
                        $myserver = 'mysql';
                        break;
                }
                else
                {
                        $myserver = "redis";
                        $data[$key] = $redis->get($key);
                }
        }
 
        echo $myserver;
        echo "<br>";
        for ($key = 1; $key < 10; $key++)
        {
                echo "number is <b><font color=#FF0000>$key</font></b>";
 
                echo "<br>";
 
                echo "name is <b><font color=#FF0000>$data[$key]</font></b>";
 
                echo "<br>";
        }
?>

在数据库中对redis授权:

[root@server4 ~]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.00 sec)

MariaDB [(none)]> grant all on test.* to redis@'%' identified by 'westos';
Query OK, 0 rows affected (0.00 sec)

在数据库中插入信息:

[root@server4 ~]# cat test.sql 
use test;
CREATE TABLE `test` (`id` int(7) NOT NULL AUTO_INCREMENT, `name` char(8) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test` VALUES (1,'test1'),(2,'test2'),(3,'test3'),(4,'test4'),(5,'test5'),(6,'test6'),(7,'test7'),(8,'test8'),(9,'test9');

#DELIMITER $$
#CREATE TRIGGER datatoredis AFTER UPDATE ON test FOR EACH ROW BEGIN
#    SET @RECV=gman_do_background('syncToRedis', json_object(NEW.id as `id`, NEW.name as `name`)); 
#  END$$
#DELIMITER ;
[root@server4 ~]# mysql < test.sql 
[root@server4 ~]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 5
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [test]> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | test1 |
|  2 | test2 |
|  3 | test3 |
|  4 | test4 |
|  5 | test5 |
|  6 | test6 |
|  7 | test7 |
|  8 | test8 |
|  9 | test9 |
+----+-------+
9 rows in set (0.00 sec)

访问网页可以正确取到信息:

在这里插入图片描述

不足与问题:
第一次访问时, 由于Redis中没有缓存, 看不到数据库中的值; 刷新页面后能够看到,但是当更改MySQL内容后并不能反映在Redis上,这显然是不满足业务需求的,必须删除 Redis 中已经存储的值, 再次刷新页面才能将新内容写入。

在这里插入图片描述

实现自动同步

由于 mysql 和 redis 的数据结构不一致,需要借助 UDF 来进行数据同步.
这里使用的是 Gearman 进行数据同步,Gearman 是一个支持分布式的任务分发框架,可以用在各种场合使用;分为 Gearman Job slave, Gearman Job Server, Gearman Worker三个组成部分。

工作流程
编写MySQL触发器, 当出现插入修改操作时创建任务
通过lib_mysqludf_json-udf库函数将关系数据映射为JSON格式
通过gearman-mysql-udf插件将任务加入Gearman的队列中
通过redis_worker.php完成数据库的更新

在这里插入图片描述

在 mysql 数据库上来做关系映射;

[root@server4 ~]# unzip lib_mysqludf_json-master.zip 
[root@server4 ~]# cd lib_mysqludf_json-master
[root@server4 lib_mysqludf_json-master]# ls
lib_mysqludf_json.c     lib_mysqludf_json.so   README.md
lib_mysqludf_json.html  lib_mysqludf_json.sql
[root@server4 lib_mysqludf_json-master]# yum install -y gcc mariadb-devel

$$生成so文件
[root@server4 lib_mysqludf_json-master]# gcc $(mysql_config --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c
[root@server4 lib_mysqludf_json-master]# ll lib_mysqludf_json.so
-rwxr-xr-x 1 root root 17528 Jul  4 11:17 lib_mysqludf_json.so

将生成的 .so 文件拷贝,注册UDF函数;

[root@server4 lib_mysqludf_json-master]# cp lib_mysqludf_json.so /usr/lib64/mysql/plugin/
[root@server4 lib_mysqludf_json-master]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show global variables like 'plugin_dir';
##查看数据库的模块目录
+---------------+--------------------------+
| Variable_name | Value                    |
+---------------+--------------------------+
| plugin_dir    | /usr/lib64/mysql/plugin/ |
+---------------+--------------------------+
1 row in set (0.00 sec)
MariaDB [(none)]> CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so';
##注册添加该函数
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> select * from mysql.func;
##查看函数内容
+-------------+-----+----------------------+----------+
| name        | ret | dl                   | type     |
+-------------+-----+----------------------+----------+
| json_object |   0 | lib_mysqludf_json.so | function |
+-------------+-----+----------------------+----------+
1 row in set (0.00 sec)

编译安装gearman-mysql-udf函数;

安装编译所需要的依赖性
[root@server4 ~]# yum install -y libgearman-1.1.12-18.el7.x86_64.rpm 
libgearman-devel-1.1.12-18.el7.x86_64.rpm 
libevent-devel-2.0.21-4.el7.x86_64.rpm
[root@server4 ~]# tar zxf gearman-mysql-udf-0.6.tar.gz 
[root@server4 ~]# cd gearman-mysql-udf-0.6
[root@server4 gearman-mysql-udf-0.6]# ./configure --libdir=/usr/lib64/mysql/plugin/
[root@server4 gearman-mysql-udf-0.6]# make
[root@server4 gearman-mysql-udf-0.6]# make install
[root@server4 gearman-mysql-udf-0.6]# cd /usr/lib64/mysql/plugin
[root@server4 plugin]# ls
adt_null.so          ha_sphinx.so                   qa_auth_interface.so
auth_0x0100.so       libdaemon_example.so           qa_auth_server.so
auth_pam.so          libgearman_mysql_udf.la        query_cache_info.so
auth_socket.so       libgearman_mysql_udf.so        semisync_master.so
auth_test_plugin.so  libgearman_mysql_udf.so.0      semisync_slave.so
daemon_example.ini   libgearman_mysql_udf.so.0.0.0  server_audit.so
dialog_examples.so   lib_mysqludf_json.so           sphinx.so
dialog.so            mypluglib.so                   sql_errlog.so
ha_innodb.so         mysql_clear_password.so
handlersocket.so     qa_auth_client.so

安装完成之后,此时再次来注册UDF函数;

MariaDB [(none)]> CREATE FUNCTION gman_do_background RETURNS STRING SONAME
    -> 'libgearman_mysql_udf.so';	#发往后端
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> CREATE FUNCTION gman_servers_set RETURNS STRING SONAME
    -> 'libgearman_mysql_udf.so';	#定义后端 server
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> select * from mysql.func;
+--------------------+-----+-------------------------+----------+
| name               | ret | dl                      | type     |
+--------------------+-----+-------------------------+----------+
| json_object        |   0 | lib_mysqludf_json.so    | function |
| gman_do_background |   0 | libgearman_mysql_udf.so | function |
| gman_servers_set   |   0 | libgearman_mysql_udf.so | function |
+--------------------+-----+-------------------------+----------+
3 rows in set (0.00 sec)

MariaDB [(none)]> SELECT gman_servers_set('172.25.25.2:4730');	
##指定Gearman的Server端
+--------------------------------------+
| gman_servers_set('172.25.25.2:4730') |
+--------------------------------------+
| 172.25.25.2:4730                     |
+--------------------------------------+
1 row in set (0.00 sec)

需要注意: Gearman 的 Server 端只负责接受 Mqsql更改情况,不负责处理; 处理的流程交给Worker进程来处理。

在指定的 server 端来部署 Worker 端;

[root@server2 rhel7]# yum install -y gearmand-1.1.12-18.el7.x86_64.rpm
[root@server2 rhel7]# systemctl start gearmand.service 
[root@server2 rhel7]# netstat -antlp | grep gearmand
tcp        0      0 0.0.0.0:4730            0.0.0.0:*               LISTEN      9935/gearmand       
tcp6       0      0 :::4730                 :::*                    LISTEN      9935/gearmand 
[root@server2 rhel7]# vim worker.php 
[root@server2 rhel7]# cat worker.php 
<?php
$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction('syncToRedis', 'syncToRedis');
 
$redis = new Redis();
$redis->connect('172.25.25.3', 6379);
 
while($worker->work());
function syncToRedis($job)
{
        global $redis;
        $workString = $job->workload();
        $work = json_decode($workString);
        if(!isset($work->id)){
                return false;
        }
        $redis->set($work->id, $work->name);
}
?>
[root@server2 rhel7]# nohup php worker.php &
#运行起来

在数据库所在主机上配置触发器:

[root@server4 ~]# vim test.sql 
[root@server4 ~]# cat test.sql 
use test;
#CREATE TABLE `test` (`id` int(7) NOT NULL AUTO_INCREMENT, `name` char(8) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#INSERT INTO `test` VALUES (1,'test1'),(2,'test2'),(3,'test3'),(4,'test4'),(5,'test5'),(6,'test6'),(7,'test7'),(8,'test8'),(9,'test9');

DELIMITER $$
CREATE TRIGGER datatoredis AFTER UPDATE ON test FOR EACH ROW BEGIN
    SET @RECV=gman_do_background('syncToRedis', json_object(NEW.id as `id`, NEW.name as `name`)); 
  END$$
DELIMITER ;
[root@server4 ~]# mysql < test.sql
[root@server4 ~]# mysql
MariaDB [(none)]> SHOW TRIGGERS FROM test;
MariaDB [(none)]> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [test]> update test set name='redhat' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

此时在网页测试时,只需刷新一下便可自动同步;此信息是直接从 redis 中拿到的。

[root@server3 ~]# redis-cli 
127.0.0.1:6379> get 1
"redhat"
127.0.0.1:6379> 

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值