当前位置: 首页 > news >正文

重庆公司建站大气的公司简介文案

重庆公司建站,大气的公司简介文案,windows server 2008 网站配置,网站建设亇金手指专业目录标题 一、预期表现二、环境配置1、nginx环境2、OpenResty环境3、redis环境3.1 安装redis3.2 配置启动命令3.3 配置主从3.4 哨兵 4、进程缓存环境 三 、主要编码工作3.1、缓存主要问题解决3.1.1 缓存穿透3.1.2 缓存雪崩3.1.3 缓存击穿 3.2、OpenResty编码3.2.1 openresty/ng… 目录标题 一、预期表现二、环境配置1、nginx环境2、OpenResty环境3、redis环境3.1 安装redis3.2 配置启动命令3.3 配置主从3.4 哨兵 4、进程缓存环境 三 、主要编码工作3.1、缓存主要问题解决3.1.1 缓存穿透3.1.2 缓存雪崩3.1.3 缓存击穿 3.2、OpenResty编码3.2.1 openresty/nginx/conf/nginx.conf3.2.2 lualib/redis_common.lua3.2.3 lualib/common.lua3.2.4 nginx/lua/item.lua 3.3、进程缓存编码 四、测试结果分析4.1 初始无缓存4.2 第二次访问30s内4.3 第二次访问30s后60s内4.4 第二次访问60s外4.5 自由访问 五、涉及到的redis知识点内存淘汰机制缓存穿透问题缓存雪崩问题缓存击穿问题多线程安全问题分布式缓存持久化为了解决数据丢失问题RDBAOF redis主从数据同步原理 哨兵 一、预期表现 希望达到的效果 四台节点node01、node02、node03、node04系统都是ubuntu20.04 server 节点ip用途Valuenode01192.168.1.101Openresty服务器、redis节点node02192.168.1.102nginx反向代理服务器、redis节点node03192.168.1.103web前端服务器、redis节点node04192.168.1.104web后端服务器 请求流程 1、通过node03上的前端服务器发出查询请求(post带请求体)http://192.168.1.102:8070/cache/xxxx 2、node02上的nginx服务器8070端口监听到/cache/转发给node01节点上8081端口 3、node01上的8081端口监听到请求交由本机的Openresty服务器上我们编写的业务代码(lua语言)来处理先查询Openresty本地缓存命中失败查询redis集群缓存命中失败查询web后端服务器有结果后更新本地缓存与redis缓存。 4、在node01、node02、node0节点上设置redis的主从集群哨兵并在Openresty上的lua文件里编码主从读写分离自动故障转移。 5、node04上的web后端服务器接收到请求(如有)先查询进程缓存进程缓存命中失败后查询数据库有结果后会自动添加缓存缓存淘汰使用LRU策略。 简而言之从请求发出到数据返回一共经历四层循环1 浏览器缓存(我们不涉及)、2 openresty缓存、3 redis集群缓存、4 进程缓存。 二、环境配置 1、nginx环境 我们在node02节点上配置nginx代理服务器监听8070端口请求从node04发过来之后直接到达nginx的8070端口上只要请求是以/cache/开头的都会被捕获然后转发到server 192.168.1.101:8081openresty服务器进行处理。 #user nobody; worker_processes 1;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;upstream nginx-cluster{server 192.168.1.101:8081;} server {listen 8070;server_name localhost;location /cache/ {proxy_pass http://nginx-cluster;}} } 2、OpenResty环境 因为有的人系统不一样所以直接去它的官网上看安装教程就好。 https://openresty.org/cn/linux-packages.html 请求通过nginx转发到了这里我们接收到后在这里进行缓存代码的实现 缓存的查询。 3、redis环境 我们在node01、node02、node03三台节点上都安装redis并搭建好主从集群 哨兵设置node03是主节点哨兵quorum设置为2超过半数就行了。 3.1 安装redis sudo apt update # 更新软件包列表可选 sudo apt install gcc tcl # 安装 gcc 和 tcl tar -xzf redis-6.2.6.tar.gz # 安装包放在/usr/local/src下 cd redis-6.2.6 sudo make make install # 默认的安装路径是在 /usr/local/bin 目录下# 任意目录 redis-server 都会启动但这属于前台启动会阻塞整个会话窗口窗口关闭redis就会停止 # 指定后台启动方式在之前解压的redis安装包路径下/usr/local/src/redis-6.2.6 cp redis.conf redis.conf.bck vim redis.conf# 允许访问的地址默认是127.0.0.1会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问生产环境不要设置为0.0.0.0 bind 0.0.0.0 # 守护进程修改为yes后即可后台运行 daemonize yes # 密码设置后访问Redis必须输入密码。这个随意我没有设置 requirepass 123321 # 日志文件默认为空不记录日志可以指定日志文件名 logfile redis.log # 工作目录默认是当前目录也就是运行redis-server时的命令日志、持久化等文件会保存在这个目录 dir .3.2 配置启动命令 # 配置redis命令 sudo vi /etc/systemd/system/redis.service[Unit] Descriptionredis-server Afternetwork.target[Service] Typeforking ExecStart/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf PrivateTmptrue[Install] WantedBymulti-user.target# 然后重载系统服务 systemctl daemon-reload # 现在我们可以用下面这组命令来操作redis了 # 启动 systemctl start redis # 停止 systemctl stop redis # 重启 systemctl restart redis # 查看状态 systemctl status redis# 让redis开机自启 systemctl enable redis3.3 配置主从 我们设置192.168.1.103为主节点在其他两个节点的配置文件中添加replicaof 192.168.1.103 6379 这个时候只有103能写数据101和102节点只能读操作所以后面写代码的时候就需要读写分离要是随便找台节点进行写操作就会失败。 3.4 哨兵 我们在/usr/local/src/sentinel/目录下新建sentinel.conf文件设置3个节点哨兵端口都是26379因为只有三台redis节点所以我们的选举值就设为2大于一半就好。 port 26379 sentinel announce-ip 192.168.1.103 sentinel monitor mymaster 192.168.1.103 6379 2 # 选举master时的quorum值 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 dir /usr/local/src/sentinel可以使用sudo redis-sentinel sentinel/sentinel.conf --daemonize yes命令启动哨兵 4、进程缓存环境 这部分常用的实现是使用基于java语言的Caffeine但因为本项目为pythondjango为了方便起见使用了python中自带的lru_cache装饰器实现进程内缓存功能也就不用配置什么了有python环境就行。 启动后端的时候在内存中维护一份缓存有请求过来时先查一下这个内存中有没有key命中没有命中再去请求数据库并更新缓存。应用重启/宕机缓存丢失。 三 、主要编码工作 3.1、缓存主要问题解决 3.1.1 缓存穿透 一般处理缓存穿透有缓存null值 和 布隆过滤器两种方式我们的业务是读多写少基本存进去的数据不会再发生更改不太可能出现数据短期不一致的情况所以这里简单使用缓存null值来解决缓存穿透问题。 3.1.2 缓存雪崩 这就没什么好说的了雪崩本来就是redis宕机或者大量缓存同时失效的情况才会发生我们是redis集群多级缓存来保证服务的可用性所以雪崩的可能性很小。 3.1.3 缓存击穿 缓存击穿是有重建过程复杂的热点key失效导致的大量请求访问数据库的情况一般处理方案是1互斥锁2逻辑过期我这里选择了互斥锁因为我们的业务请求量不大而且业务的key本身就很长了再加时间就挺不合适了。 因为我们这里是redis集群所以可能会出现分布式并发的安全问题我们用分布式锁来解决并发安全问题。刚好redis自己就有一个SET NX EX的命令来充当分布式锁同时设置过期时间避免死锁。我们在拿锁的时候放入我们的线程标识放锁的时候比对当前线程是否持有锁有锁的才能释放以此解决并发情况下其他线程释放锁错误的情况。 还有就是释放锁的时候有个 拿标识–比对–释放锁的过程极端情况下可能会出现这三个过程还没走完服务就宕机的情况所以我们使用lua脚本解决多条命令执行的原子性问题。 3.2、OpenResty编码 这里是openresty中nginx的目录也是我们进行编码的主要地方 lua文件夹是我们自己新建的主要是放缓存处理的业务代码 lualib是本来就有的文件夹我们把一些通用的代码抽取出来放里面 3.2.1 openresty/nginx/conf/nginx.conf 下面是OpenResty中nginx的配置文件,/usr/local/openresty/nginx/conf/nginx.conf #user nobody; worker_processes 1; error_log logs/error.log;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;#lua 模块lua_package_path /usr/local/openresty/lualib/?.lua;;;#c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;; # 共享字典也就是本地缓存名称叫做item_cache大小150mlua_shared_dict item_cache 150m; server {listen 8081;server_name localhost;# nginx转发过来的请求在这儿接收返回的结果交由item.lua文件来处理返回location /cache/ {default_type application/json;content_by_lua_file lua/item.lua;}# 当openresty缓存和redis缓存都没有命中需要去访问后端服务器时就从这儿走location /xxxx/ {proxy_pass http://192.168.1.104:8002;}location / {root html;index index.html index.htm;}error_page 500 502 503 504 /50x.html;location /50x.html {root html;}} }3.2.2 lualib/redis_common.lua 重头戏来了 redis的相关操作都在lualib/redis_common.lua这个文件里我们在里面实现了主从故障的动态切换、读写分离、redis连接池等。 local redis require resty.redis-- 定义 Redis 节点和哨兵配置 local sentinel_hosts {{ host 192.168.1.101, port 26379 },{ host 192.168.1.102, port 26379 },{ host 192.168.1.103, port 26379 } }local sentinel_group mymaster-- 以秒为单位定义缓存超时时间例如 60 秒 local CACHE_EXPIRATION 60 local cached_master_address nil local cached_slave_address nil local last_master_cache_time nil local last_slave_cache_time nil-- 定义缓存节点信息的全局变量 local cached_master_address local cached_slave_address-- 关闭redis连接的工具方法其实是放入连接池 local function close_redis(red)local pool_max_idle_time 10000 -- 连接的空闲时间单位是毫秒local pool_size 100 --连接池大小local ok, err red:set_keepalive(pool_max_idle_time, pool_size)if not ok thenngx.log(ngx.ERR, 放入redis连接池失败: , err)end end-- 连接到哨兵节点 local function connect_to_sentinel()local red redis:new()for _, sentinel_host in ipairs(sentinel_hosts) dolocal ok, err red:connect(sentinel_host.host, sentinel_host.port)if ok thenreturn redendendngx.log(ngx.ERR, 【连接到哨兵节点失败: 】, err)return nil, Failed to connect to any sentinel end-- 获取 Redis 主节点地址 local function get_master_address()local current_time ngx.now()if cached_master_address and last_master_cache_time and (current_time - last_master_cache_time CACHE_EXPIRATION) thenreturn cached_master_addressendlocal red_sentinel, err connect_to_sentinel()if not red_sentinel thenreturn nil, Failed to connect to Redis Sentinel: .. errendlocal master_ip, master_portlocal master, err red_sentinel:sentinel(get-master-addr-by-name, sentinel_group)close_redis(red_sentinel)for key, value in pairs(master) doif key 1 thenmaster_ip valueelseif key 2 thenmaster_port valuebreak;endendcached_master_address { master_ip, master_port }last_master_cache_time current_timereturn cached_master_address end-- 获取 Redis 从节点地址 local function get_slave_address()local current_time ngx.now()if cached_slave_address and last_slave_cache_time and (current_time - last_slave_cache_time CACHE_EXPIRATION) thenreturn cached_slave_addressendlocal red_sentinel, err connect_to_sentinel()if not red_sentinel thenreturn nil, Failed to connect to Redis Sentinel: .. errendlocal slaves, err red_sentinel:sentinel(slaves, sentinel_group)if not slaves thenred_sentinel:set_keepalive(10000, 100)return nil, Failed to retrieve slave addresses: .. errendclose_redis(red_sentinel)local slave_ip, slave_portfor i, slave_info in ipairs(slaves) doif slave_info[10] and not (string.find(slave_info[10], s_down) and string.find(slave_info[10], disconnected)) thenslave_ip slave_info[4]slave_port slave_info[6]endendcached_slave_address { slave_ip, slave_port }last_slave_cache_time current_timereturn cached_slave_address end-- 获取 Redis 读连接 local function read_redis(key)local ip, portlocal address get_slave_address()if address thenip, port unpack(address)else ngx.log(ngx.ERR, 【无法获取从节点地址】,address)endif not ip thenreturn nil, Failed to get Redis addressendlocal red redis:new()red:set_timeout(1000) -- 设置超时时间local ok, err red:connect(ip, tonumber(port))if not ok thenreturn nil, Failed to connect to Redis: .. errendngx.log(ngx.INFO,【当前连接redis从节点: 】, ip, 【当前redis从节点端口: 】, port)-- 查询redislocal resp, err red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, 【查询Redis失败: 】, err, , 【key 】 , key)end--得到的数据为空处理if resp ngx.null thenresp nilendclose_redis(red)return respend-- 获取 Redis 写连接 local function write_redis(cacheKey, val, expirationKey)local ip, portlocal address get_master_address()if address thenip, port unpack(address)else ngx.log(ngx.ERR, 【无法获取主节点地址:】,address)endif not ip thenreturn nil, Failed to get Redis master addressendlocal red redis:new()red:set_timeout(1000) -- 设置超时时间local ok, err red:connect(ip, tonumber(port))if not ok thenreturn nil, Failed to connect to master Redis: .. errendngx.log(ngx.INFO,【redis主节点连接成功开始执行操作。主节点IP:】, ip, 【端口】, port)-- 获取互斥锁local uuid require(resty.uuid)local mutex mutex: .. cacheKeylocal uniqueID uuid.generate()local expiration 30 -- 锁的过期时间秒local retryAttempts 3 -- 尝试获取锁的次数local retryDelay 0.5 -- 每次重试的间隔时间秒local scriptLock [[local lockKey KEYS[1]local lockValue ARGV[1]local expiration tonumber(ARGV[2]) -- 锁的过期时间秒local lockSet redis.call(SET, lockKey, lockValue, NX, EX, expiration)if lockSet thenreturn 1else-- 锁已经被其他客户端持有return 0end]]local scriptReleaseLock [[local lockKey KEYS[1]local lockValue ARGV[1]local currentLockOwner redis.call(GET, lockKey)if currentLockOwner lockValue thenredis.call(DEL, lockKey)return 1elsereturn 0end]]local attempts 0local mutexSet 0while attempts retryAttempts and mutexSet ~ 1 dolocal result, evalErr red:eval(scriptLock, 1, mutex, uniqueID, expiration)if result 1 thenlocal scriptSet [[redis.call(SET, KEYS[1], ARGV[1])redis.call(EXPIRE, KEYS[1], ARGV[2])]]mutexSet 1-- 获取到锁执行操作local setResult, setResultErr red:eval(scriptSet, 1, cacheKey, val, expirationKey)if not setResult thenngx.log(ngx.ERR,【Error setting key: 】, setResultErr)returnend-- 释放锁local releaseResult, releaseErr red:eval(scriptReleaseLock, 1, mutex, uniqueID)if not releaseResult thenngx.log(ngx.ERR, 【互斥锁释放失败】, mutex, , , uniqueID)end-- 返回新值return setResultelseattempts attempts 1if attempts retryAttempts then-- 未获取到锁等待一段时间后重试ngx.sleep(retryDelay)endendendclose_redis(red)end-- 将方法导出 local _M { read_redis read_redis,write_redis write_redis } return _M3.2.3 lualib/common.lua lualib/common.lua这个文件里主要是封装了发送http请求的代码在我的流程里主要是openresty和redis缓存都命中失败时通过这个方法去访问后端服务器。 -- 封装函数发送 HTTP 请求并解析响应 local function read_http(path, body)local resp ngx.location.capture(path, {method ngx.HTTP_POST, -- 修改为 POST 方法body body -- 设置请求体数据})if not resp then-- 记录错误信息返回404ngx.log(ngx.ERR, HTTP请求查询失败, path: , path)ngx.exit(404)endreturn resp.body end-- 将方法导出 local _M { read_http read_http,read_redis read_redis } return _M3.2.4 nginx/lua/item.lua 下面是我们的业务代码nginx/lua/item.lua这也是重头戏。我们在里面调用上面common文件夹里封装的redis_common和http文件然后实现查询openresty失败-查询redis失败-查询后端-更新缓存-返回结果的操作。 ngx.req.read_body() local jsonBody ngx.req.get_body_data() local cjson require cjson local bodyData cjson.decode(jsonBody) bodyData cjson.decode(bodyData) local requestURI ngx.var.uri ngx.log(ngx.ERR,一次请求开始) local coords_str cjson.encode(bodyData[coords]) local key bodyData[database] .. : .. bodyData[dataset] .. : .. bodyData[time] .. : .. bodyData[function] .. : .. coords_str-- 引入自定义common工具模块返回值是common中返回的 _M local common require(common) local redis_common require(redis_common)local read_redis redis_common.read_redis local write_redis redis_common.write_redis--local read_redis common.read_redis local read_http common.read_http -- 导入共享词典本地缓存 local item_cache ngx.shared.item_cache-- 封装查询函数 function read_data(key, expire, path, params)-- 查询本地缓存local val item_cache:get(key)if not val thenngx.log(ngx.ERR, 【本地缓存查询失败尝试查询Redis】 key: , key)-- 查询redisval read_redis(key)-- 判断查询结果if not val thenngx.log(ngx.ERR, 【redis查询失败尝试查询后端进程】 key: , key)-- redis查询失败去查询httpval read_http(path, params)elsengx.log(ngx.ERR, 【Redis缓存命中】 key: , key)endelsengx.log(ngx.ERR, 【本地缓存命中】 key: , key)end-- 只在查询到非null值时才把数据写入本地缓存, 并缓存续期if val thenitem_cache:set(key, val, 30)end-- 写入redis缓存即使值为null防止缓存穿透也将null值添加进redislocal flag write_redis(key, val, expire)-- 返回数据return val end -- 使用 read_http 函数发送 POST 请求并传递解析后的 JSON 数据 local itemJSON read_data(key, 60, /api/data_node/file_path/, cjson.encode(bodyData)) -- itemJSON是stringdecode后是tableencode后是string--local aa cjson.encode(itemJSON) --local bb cjson.decode(itemJSON) ngx.say(itemJSON) ngx.log(ngx.ERR,最终获得的值, itemJSON) ngx.log(ngx.ERR,一次请求结束)3.3、进程缓存编码 我们这里使用python里的lru_cache装饰器Least Recently Used Cache最近最少使用缓存来装饰后端服务器进程查询数据库的函数存储函数的输入参数和对应的输出结果函数输入参数会被哈希之后作为缓存的键所以要求输入参数必须能被哈希。后续调用中如果相同的输入参数再次出现直接返回缓存的输出结果而不需要重新执行函数体也就不需要再去数据库里查询。 from functools import lru_cachelru_cache(maxsize10) # 注解在需要执行的函数上面print(f【进程内缓存信息{query.cache_info()}】) #【进程内缓存信息CacheInfo(hits6, misses7, maxsize10, currsize7)】 # 命中6次有请求来但未命中7次缓存最大存储容量10当前容量7 query.cache_clear() # 清除缓存四、测试结果分析 我们在这一部分把整个流程跑一下把各种可能会出现的情况都模拟一下来验证实现的多级缓存架构的可用性。 我们在上面的编码部分设置的各缓存情况为openresty本地缓存时间为30sredis缓存时间为60s进程内缓存无时间限制不过缓存的最大数量限制为10。 4.1 初始无缓存 初始条件下所有缓存里都是null我们在前端服务器发起请求请求后打开日志。 这里可以看到在初次查询时【openresty缓存查询失败redis查询失败查询后端进程成功 写入openresty缓存成功写入redis缓存成功】。 这里我们还需要看一下后端缓存部分 可以看到【缓存命中0错过1最大缓存数10已缓存数1】这个错过1就是我们刚发的这个请求请求来了但是进程缓存里没有这个key只能往下去数据库查询。已缓存数1是我们现在已经缓存了这个key。 redis也写入成功TTL从60开始计时 4.2 第二次访问30s内 我们在第一次访问过的基础上在30s时间内(因为openresty本地缓存时间设置的是30s)再次访问同一个数据返回结果如下。我们在缓存命中后依旧刷新openresty和redis缓存进行缓存续期。 4.3 第二次访问30s后60s内 我们在第一次访问过的基础上在30s外但60s内(因为openresty本地缓存时30sredis60s)再次访问同一个数据返回结果如下。 4.4 第二次访问60s外 我们在第一次访问过的基础上在60s外再次访问同一个数据此时openresty和redis缓存应该都已经过期。 可以看到的确是走了后端服务器但是走了后端进程缓存吗 确实是走了进程缓存因为错过数没有增加并且多了一次命中。 4.5 自由访问 我们这里重启服务连续自由访问6条新数据。 60s后再次连续访问这6条数据 全部命中 后面还有其他的测试情况就不一一列举了 五、涉及到的redis知识点 内存淘汰机制 缓存穿透问题 *现象用户请求的数据在缓存中和数据库中都不存在不断发起这样的请求给数据库带来巨大压力 缓存空对象如果大量的请求同时过来访问这种不存在的数据这些请求就都会访问到数据库简单的解决方案就是即使这个数据在数据库中也不存在我们也把这个数据存入到redis中去这样下次用户过来访问这个不存在的数据那么在redis中也能找到这个数据。 布隆过滤布隆过滤器其实采用的是哈希思想来解决这个问题通过一个庞大的二进制数组走哈希思想去判断当前这个要查询的这个数据是否存在如果布隆过滤器判断存在则放行这个请求会去访问redis哪怕此时redis中的数据过期了但是数据库中一定存在这个数据在数据库中查询出来这个数据后再将其放入到redis中假设布隆过滤器判断这个数据不存在则直接返回。 这种方式优点在于节约内存空间。但存在误判误判原因在于布隆过滤器走的是哈希思想只要哈希思想就可能存在哈希冲突。 缓存雪崩问题 缓存击穿问题 举例线程1在查询缓存不存在后需要去查询数据库然后把这个数据加到到缓存里。但是在线执行的过程中后面的n个线程同时访问当前方法同时来查询缓存又要同时去访问数据库对数据库访问压力过大。 解决方案1、使用互斥锁 假设线程过来只能一个人一个人的来访问数据库从而避免对于数据库访问压力过大但这也会影响查询的性能因为此时会让查询的性能从并行变成了串行 首先线程1来访问没有命中缓存去拿互斥锁然后去执行逻辑。线程2再来并没有获得互斥锁那么线程2就进行休眠。直到线程1把锁释放后线程2获得到锁然后再来执行逻辑此时就能直接从缓存中拿到数据了。 解决方案2、逻辑过期方案 在key的value里加一个逻辑过期时间第一个线程发现当前数据已经过期就去获取互斥锁然后新开一个线程去重构逻辑而它不等了直接返回当前的过期数据。在新线程执行的期间有其他线程来访问时它们发现获取互斥锁失败那就也直接返回过期数据。 互斥锁方案实现简单且保证了互斥性因为仅仅只需要加一把锁而已。缺点在于有锁就有死锁问题的发生而且只能串行执行性能肯定受到影响。 逻辑过期方案 线程读取过程中不需要等待性能好有一个额外的线程持有锁去进行重构数据但是在重构数据完成前其他的线程只能返回脏数据且实现麻烦。 多线程安全问题 分布式锁的核心思想在于所有线程或进程共享同一把锁。只要它们使用相同的锁就能够阻止多个线程同时访问关键资源确保程序串行执行。这种设计方式有效地控制了并发访问保障了共享资源的正确性和一致性。 锁超时问题 如果获取锁后持有锁的客户端执行时间超过了锁的过期时间那么 Redis 将会自动删除这个已过期的锁。这可能导致其他客户端误以为锁被释放了进而导致并发问题。 原子性问题在执行业务逻辑过程中线程的拿锁比锁删锁并不能保证原子性。 解决方法 1、在存入锁时放入自己线程的标识在删除锁时判断当前这把锁的标识是不是自己存入的如果是则进行删除如果不是则不进行删除。 2、Lua脚本解决多条命令原子性问题 基于Redis的分布式锁实现思路 使用 SET NX EX 命令获取锁确保互斥性只有一个线程能够成功获取锁。设置锁的过期时间即使发生故障也能保证锁在一定时间后自动释放避免死锁情况的发生提高系统安全性。存储线程标识在释放锁时使用 Lua 脚本先验证线程标识是否与当前线程一致确保只有持有锁的线程才能释放锁。利用 Redis 集群特性保证高可用性和高并发性确保在集群环境下锁依然可靠且有效地工作。 基于setnx实现的分布式锁存在下面的问题 1、重入问题重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中可重入锁的意义在于防止死锁比如HashTable这样的代码中他的方法都是使用synchronized修饰的假如他在一个方法内调用另一个方法那么此时如果是不可重入的不就死锁了吗所以可重入锁他的主要意义是防止死锁我们的synchronized和Lock锁都是可重入的。 2、主从一致性问题 如果Redis提供了主从集群当我们向集群写数据时主机需要异步的将数据同步给从机而万一在同步过去之前主机宕机了就会出现死锁问题。 分布式缓存 持久化为了解决数据丢失问题 RDB RDB全称Redis Database Backup file Redis数据备份文件也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件恢复数据。快照文件称为RDB文件默认是保存在当前运行目录。 save # save命令会导致主进程执行RDB这个过程中其它所有命令都会被阻塞 bgsave # 异步执行RDB # Redis停机时会执行一次save命令实现RDB持久化 # 触发RDB条件比如redis.conf里的save 900 1 ,在 900 秒内如果至少有 1 个键被修改则执行快照保存bgsave开始时会fork主进程得到子进程子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。 fork采用的是copy-on-write技术 当主进程执行读操作时访问共享内存 -当主进程执行写操作时则会拷贝一份数据执行写操作。 RDB方式bgsave的基本流程 fork主进程得到一个子进程共享内存空间子进程读取内存数据并写入新的RDB文件用新RDB文件替换旧的RDB文件。 RDB会在什么时候执行save 60 1000代表什么含义 默认是服务停止时。代表60秒内至少执行1000次修改则触发RDB RDB的缺点 RDB执行间隔时间长两次RDB之间写入数据有丢失的风险如果两次 RDB 之间发生故障或意外如系统崩溃、断电或其他原因可能会导致在最近一次 RDB 快照之后产生的数据丢失fork子进程、压缩、写出RDB文件都比较耗时 AOF AOF全称为Append Only File追加文件。Redis处理的每一个写命令都会记录在AOF文件可以看做是命令日志文件。 AOF默认是关闭的需要修改redis.conf配置文件来开启AOF # 是否开启AOF功能默认是no appendonly yes # AOF文件的名称 appendfilename appendonly.aof # 表示每执行一次写命令立即记录到AOF文件 appendfsync always # 写命令执行完先放入AOF缓冲区然后表示每隔1秒将缓冲区数据写到AOF文件是默认方案 appendfsync everysec # 写命令执行完先放入AOF缓冲区由操作系统决定何时将缓冲区内容写回磁盘 appendfsync n因为是记录命令AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作但只有最后一次写操作才有意义。通过执行bgrewriteaof命令可以让AOF文件执行重写功能用最少的命令达到相同效果。 比如set num 123 和 set num 666第二次会覆盖第一次的值因此第一个命令记录下来没有意义。 所以重写命令后AOF文件内容就是mset name jack num 666 # Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置 # AOF文件比上次文件 增长超过多少百分比则触发重写 auto-aof-rewrite-percentage 100 # AOF文件体积最小多大以上才触发重写 auto-aof-rewrite-min-size 64mb redis主从 我们前面已经记录了怎么装redis在上面的基础上192.168.1.101、192.168.1.102和192.168.1.103都装一下每个节点的端口都是默认的6379 数据同步原理 简述全量同步的流程 slave节点请求增量同步master节点判断replid发现不一致拒绝增量同步master将完整内存数据生成RDB发送RDB到slaveslave清空本地数据加载master的RDBmaster将RDB期间的命令记录在repl_baklog并持续将log中的命令发送给slaveslave执行接收到的命令保持与master之间的同步 全量同步需要先做RDB然后将RDB文件通过网络传输个slave成本太高了。因此除了第一次做全量同步其它大多数时候slave与master都是做增量同步。什么是增量同步就是只更新slave与master存在差异的部分数据 简述全量同步和增量同步区别 全量同步master将完整内存数据生成RDB发送RDB到slave。后续命令则记录在repl_baklog逐个发送给slave。 增量同步slave提交自己的offset到mastermaster获取repl_baklog中从offset之后的命令给slave 什么时候执行全量同步 slave节点第一次连接master节点时 slave节点断开时间太久repl_baklog中的offset已经被覆盖时 什么时候执行增量同步 slave节点断开又恢复并且在repl_baklog中能找到offset时 master如何知道slave与自己的数据差异 这就要说到全量同步时的repl_baklog文件了 这个文件是一个固定大小的数组只不过数组是环形也就是说角标到达数组末尾后会再次从0开始读写这样数组头部的数据就会被覆盖。 repl_baklog中会记录Redis处理过的命令日志及offset包括master当前的offset和slave已经拷贝到的offset。 slave与master的offset之间的差异就是salve需要增量拷贝的数据了。 随着不断有数据写入master的offset逐渐变大slave也不断的拷贝追赶master的offset,直到数组被填满。 此时如果有新的数据写入就会覆盖数组中的旧数据。不过旧的数据只要是绿色的说明是已经被同步到slave的数据即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。 但是如果slave出现网络阻塞导致master的offset远远超过了slave的offset 如果master继续写入新数据其offset就会覆盖旧的数据直到将slave现在的offset也覆盖 棕色框中的红色部分就是尚未同步但是却已经被覆盖的数据。此时如果slave恢复需要同步却发现自己的offset都没有了无法完成增量同步了。只能做全量同步。 优化Redis主从就集群 在master中配置repl-diskless-sync yes启用无磁盘复制避免全量同步时的磁盘IO。Redis单节点上的内存占用不要太大减少RDB导致的过多磁盘IO适当提高repl_baklog的大小发现slave宕机时尽快实现故障恢复尽可能避免全量同步限制一个master上的slave节点数量如果实在是太多slave则可以采用主-从-从链式结构减少master压力 哨兵 Sentinel基于心跳机制监测服务状态每隔1秒向集群的每个实例发送ping命令 主观下线如果某sentinel节点发现某实例未在规定时间响应则认为该实例主观下线。 客观下线若超过指定数量quorum的sentinel都认为该实例主观下线则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。 一旦发现master故障sentinel需要在salve中选择一个作为新的master选择依据是这样的 首先会判断slave节点与master节点断开时间长短如果超过指定值down-after-milliseconds * 10则会排除该slave节点 然后判断slave节点的slave-priority值越小优先级越高如果是0则永不参与选举 如果slave-prority一样则判断slave节点的offset值越大说明数据越新优先级越高 最后是判断slave节点的运行id大小越小优先级越高。 Sentinel的三个作用是什么 监控 故障转移 通知 Sentinel如何判断一个redis实例是否健康 每隔1秒发送一次ping命令如果超过一定时间没有相向则认为是主观下线 如果大多数sentinel都认为实例主观下线则判定服务下线 故障转移步骤有哪些 首先选定一个slave作为新的master执行slaveof no one 然后让所有节点都执行slaveof 新master 修改故障节点执行slaveof 新master
http://www.zqtcl.cn/news/261201/

相关文章:

  • 网站制作模板公司网站维护流程
  • 超炫网站模板友情链接交换教程
  • 物流公司做网站有用吗备案网站的黑名单
  • 多语言网站制作长沙市做网站的
  • 做视频点播网站要多少带宽怎么用电脑做网站服务器吗
  • 新办公司网上核名在哪个网站做网站内容作弊的形式
  • 网站风格化设计方案常见的erp软件有哪些
  • 河北石家庄特产做网站优化的
  • 做网站工资年新多少在广东番禺网页设计公司
  • 宝安专业手机网站设计公司王野天个人资料
  • 给网站做蜘蛛抓取赚钱
  • 康保网站制作高端网站制作服务
  • 网站建设的网站分析怎么写crm管理系统销售
  • 茂名做网站的公司大专学电子商务有用吗
  • qq空间做宣传网站如何做图片网站
  • 邯郸住房城乡建设厅网站建设银行网站钓鱼网站
  • 高密建网站龙门城乡规划建设局网站
  • 阿里云从哪里建设网站企业设计网站公司排名
  • 长春做网站推广的公司公司要做个网站吗
  • 天水 网站建设招聘个人网站建设的国外文献综述
  • 什么网站做推广最好建行网站用户名是什么
  • 网站建设和维护需要学的东西服务器学生
  • 电子工厂网站建设企业管理咨询报告
  • 敖汉旗网站建设网站建设班级通讯录
  • 把手机做网站服务器做网站商丘
  • 婚恋咨询网站运营做速卖通代码的网站
  • 网站建设流程有哪七步c语言做的网站有什么优缺点
  • 树在线网页制作网站邢台中北世纪城网站兼职
  • 备案网站建设方案模板怎么看网站域名
  • asp iis设置网站路径效果好网站建设哪家好