ThreeJS-聚光等衰减(二十一)

Posted 不穿铠甲的穿山甲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreeJS-聚光等衰减(二十一)相关的知识,希望对你有一定的参考价值。

聚光灯可以联系到现实中的手电筒

衰减分为:距离衰减和边缘衰减

.decay : Float(控制的是灯光自身的亮度)

The amount the light dims along the distance of the light. Default is 2.
In context of physically-correct rendering the default value should not be changed.

.distance : Float(距离衰减)

如果非零,那么光强度将会从最大值当前灯光位置处按照距离线性衰减到0。 缺省值为 0.0

.penumbra : Float(边缘衰减)

聚光锥的半影衰减百分比。在0和1之间的值。 默认值 — 0.0。

距离衰减

关键代码:

            //灯光有效距离,默认0表示不衰减
            directionalLight.distance = 0;
            gui.add(directionalLight, 'distance').min(0).max(100).step(1).name("灯光有效距离");

完整代码:

<template>
    <div id="three_div"></div>
</template>

<script>
    import * as dat from 'dat.gui' //界面控制
    import * as THREE from "three";
    import
        OrbitControls
     from "three/examples/jsm/controls/OrbitControls";
    import
        RGBELoader
     from "three/examples/jsm/loaders/RGBELoader"
    export default
        name: "HOME",
        components:
            // vueQr,
            // glHome,
        ,
        data()
            return ;
        ,
        mounted()
            //使用控制器控制3D拖动旋转OrbitControls
            //控制3D物体移动

            //1.创建场景
            const scene = new THREE.Scene();
            console.log(scene);

            //2.创建相机
            const camera = new THREE.PerspectiveCamera(
                75,
                window.innerWidth / window.innerHeight,
                0.1,
                1000
            );
            //设置相机位置
            camera.position.set(0, 0, 10);
            //将相机添加到场景
            scene.add(camera);
            //添加物体
            //创建一个半径为1,经纬度分段数位20的球
            const cubeGeometry = new THREE.SphereBufferGeometry(2, 100, 100);
            //纹理加载器加载图片
            const cubeMaterial = new THREE.MeshStandardMaterial(
                //side: THREE.DoubleSide,
            );
            //根据几何体和材质创建物体
            const mesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
            //将物体加入到场景
            scene.add(mesh);

            //创建平面几何体
            const planeGeometry = new THREE.PlaneBufferGeometry(50, 50);
            //创建平面物体
            const planeMesh = new THREE.Mesh(planeGeometry, cubeMaterial);
            planeMesh.position.set(0, -2, 0);
            planeMesh.rotation.x = -Math.PI / 2;
            //场景添加平面物体
            scene.add(planeMesh);

            //给场景所有的物体添加默认的环境贴图
            //添加坐标轴辅助器
            const axesHepler = new THREE.AxesHelper(5);
            scene.add(axesHepler);
            //标准材质需要借助灯光

            //添加周围环境灯光(由物体发出的灯光)参数(灯色,强度0-1)
            const light = new THREE.AmbientLight(0xFFFFFF, 0.7);
            scene.add(light);
            //直线光(由光源发出的灯光)
            // const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.7);
            // directionalLight.position.set(10, 10, 10);
            // scene.add(directionalLight);
            const directionalLight = new THREE.SpotLight(0xFFFFFF, 0.7);
            directionalLight.position.set(10, 10, 10);
            scene.add(directionalLight);

            // scene.add(mesh2);
            //初始化渲染器
            const render = new THREE.WebGLRenderer();
            //设置渲染器的尺寸
            render.setSize(window.innerWidth, window.innerHeight);
            //使用渲染器,通过相机将场景渲染进来

            //创建轨道控制器,可以拖动,控制的是摄像头
            const controls = new OrbitControls(camera, render.domElement);
            //设置控制阻尼,让控制器有更真实的效果
            controls.enableDamping = true;


            //开启投影
            //开启渲染器投影
            render.shadowMap.enabled = true;
            //开启灯光动态投影
            directionalLight.castShadow = true;
            //开启物体投影
            mesh.castShadow = true;
            //开启平面接受投影
            planeMesh.receiveShadow = true;
            //投影模糊度
            directionalLight.shadow.radius = 10;
            //设置投影的宽度和高度
            // directionalLight.shadow.mapSize.set(1024, 1024);

            //平行光投射相机的属性
            // directionalLight.shadow.camera.near= 0.5;
            // directionalLight.shadow.camera.far= 500;
            // directionalLight.shadow.camera.top= 3;
            // directionalLight.shadow.camera.bottom= -2;
            // directionalLight.shadow.camera.left= -2;
            // directionalLight.shadow.camera.right= 2;
            //灯光跟着物体移动而移动
            directionalLight.target = mesh;
            //directionalLight.angle = Math.PI/10;
            //创建gui
            const gui = new dat.GUI();
            // gui.add(directionalLight.shadow.camera, 'near').min(1).max(25).step(1).name("相机近距离").onChange( () =>
            //     directionalLight.shadow.camera.updateProjectionMatrix();
            // )
            gui.add(mesh.position, 'x').min(-30).max(30).step(1).name("移动位置");
            gui.add(directionalLight, 'angle').min(0).max(Math.PI/2).step(0.1).name("灯光弧度");
            
            //灯光有效距离,默认0表示不衰减
            directionalLight.distance = 0;
            gui.add(directionalLight, 'distance').min(0).max(100).step(1).name("灯光有效距离");
            //将webgl渲染的canvas内容添加到body上
            document.getElementById("three_div").appendChild(render.domElement);

            //渲染下一帧的时候就会调用回调函数
            let renderFun = () =>
                //更新阻尼数据
                controls.update();
                //需要重新绘制canvas画布
                render.render(scene, camera);
                //监听屏幕刷新(60HZ,120HZ),每次刷新触发一次requestAnimationFrame回调函数
                //但是requestAnimationFrame的回调函数注册生命只有一次,因此需要循环注册,才能达到一直调用的效果
                window.requestAnimationFrame(renderFun);
            ;
            // window.requestAnimationFrame(renderFun);
            renderFun();

            //画布全屏
            window.addEventListener("dblclick", () =>
                if (document.fullscreenElement)
                    document.exitFullscreen();
                 else
                    //document.documentElement.requestFullscreen();
                    render.domElement.requestFullscreen();
                
            );

            //监听画面变化,更新渲染画面,(自适应的大小)
            window.addEventListener("resize", () =>
                //更新摄像机的宽高比
                camera.aspect = window.innerWidth / window.innerHeight;
                //更新摄像机的投影矩阵
                camera.updateProjectionMatrix();
                //更新渲染器宽度和高度
                render.setSize(window.innerWidth, window.innerHeight);
                //设置渲染器的像素比
                render.setPixelRatio(window.devicePixelRatio);
                console.log("画面变化了");
            );
        ,
        methods:
            paush(animate)
                animate.pause();
            ,
        ,
    ;
</script>

<style scoped lang="scss">
</style>

效果图:

灯边缘衰减

关键代码:

//边缘衰减

gui.add(directionalLight, 'penumbra').min(0).max(1).step(0.01).name("灯光边缘衰减");

完整代码:

<template>
    <div id="three_div"></div>
</template>

<script>
    import * as dat from 'dat.gui' //界面控制
    import * as THREE from "three";
    import
        OrbitControls
     from "three/examples/jsm/controls/OrbitControls";
    import
        RGBELoader
     from "three/examples/jsm/loaders/RGBELoader"
    export default
        name: "HOME",
        components:
            // vueQr,
            // glHome,
        ,
        data()
            return ;
        ,
        mounted()
            //使用控制器控制3D拖动旋转OrbitControls
            //控制3D物体移动

            //1.创建场景
            const scene = new THREE.Scene();
            console.log(scene);

            //2.创建相机
            const camera = new THREE.PerspectiveCamera(
                75,
                window.innerWidth / window.innerHeight,
                0.1,
                1000
            );
            //设置相机位置
            camera.position.set(0, 0, 10);
            //将相机添加到场景
            scene.add(camera);
            //添加物体
            //创建一个半径为1,经纬度分段数位20的球
            const cubeGeometry = new THREE.SphereBufferGeometry(2, 100, 100);
            //纹理加载器加载图片
            const cubeMaterial = new THREE.MeshStandardMaterial(
                //side: THREE.DoubleSide,
            );
            //根据几何体和材质创建物体
            const mesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
            //将物体加入到场景
            scene.add(mesh);

            //创建平面几何体
            const planeGeometry = new THREE.PlaneBufferGeometry(50, 50);
            //创建平面物体
            const planeMesh = new THREE.Mesh(planeGeometry, cubeMaterial);
            planeMesh.position.set(0, -2, 0);
            planeMesh.rotation.x = -Math.PI / 2;
            //场景添加平面物体
            scene.add(planeMesh);

            //给场景所有的物体添加默认的环境贴图
            //添加坐标轴辅助器
            const axesHepler = new THREE.AxesHelper(5);
            scene.add(axesHepler);
            //标准材质需要借助灯光

            //添加周围环境灯光(由物体发出的灯光)参数(灯色,强度0-1)
            const light = new THREE.AmbientLight(0xFFFFFF, 0.7);
            scene.add(light);
            //直线光(由光源发出的灯光)
            // const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.7);
            // directionalLight.position.set(10, 10, 10);
            // scene.add(directionalLight);
            const directionalLight = new THREE.SpotLight(0xFFFFFF, 0.7);
            directionalLight.position.set(10, 10, 10);
            scene.add(directionalLight);

            // scene.add(mesh2);
            //初始化渲染器
            const render = new THREE.WebGLRenderer();
            //设置渲染器的尺寸
            render.setSize(window.innerWidth, window.innerHeight);
            //使用渲染器,通过相机将场景渲染进来

            //创建轨道控制器,可以拖动,控制的是摄像头
            const controls = new OrbitControls(camera, render.domElement);
            //设置控制阻尼,让控制器有更真实的效果
            controls.enableDamping = true;


            //开启投影
            //开启渲染器投影
            render.shadowMap.enabled = true;
            //开启灯光动态投影
            directionalLight.castShadow = true;
            //开启物体投影
            mesh.castShadow = true;
            //开启平面接受投影
            planeMesh.receiveShadow = true;
            //投影模糊度
            directionalLight.shadow.radius = 10;
            //设置投影的宽度和高度
            // directionalLight.shadow.mapSize.set(1024, 1024);

            //平行光投射相机的属性
            // directionalLight.shadow.camera.near= 0.5;
            // directionalLight.shadow.camera.far= 500;
            // directionalLight.shadow.camera.top= 3;
            // directionalLight.shadow.camera.bottom= -2;
            // directionalLight.shadow.camera.left= -2;
            // directionalLight.shadow.camera.right= 2;
            //灯光跟着物体移动而移动
            directionalLight.target = mesh;
            //directionalLight.angle = Math.PI/10;
            //创建gui
            const gui = new dat.GUI();
            // gui.add(directionalLight.shadow.camera, 'near').min(1).max(25).step(1).name("相机近距离").onChange( () =>
            //     directionalLight.shadow.camera.updateProjectionMatrix();
            // )
            gui.add(mesh.position, 'x').min(-30).max(30).step(1).name("移动位置");
            gui.add(directionalLight, 'angle').min(0).max(Math.PI/2).step(0.1).name("灯光弧度");
            
            //灯光有效距离,默认0表示不衰减
            directionalLight.distance = 0;
            gui.add(directionalLight, 'distance').min(0).max(100).step(1).name("灯光有效距离");
            
            //边缘衰减
            gui.add(directionalLight, 'penumbra').min(0).max(1).step(0.01).name("灯光边缘衰减");
            
            //将webgl渲染的canvas内容添加到body上
            document.getElementById("three_div").appendChild(render.domElement);

            //渲染下一帧的时候就会调用回调函数
            let renderFun = () =>
                //更新阻尼数据
                controls.update();
                //需要重新绘制canvas画布
                render.render(scene, camera);
                //监听屏幕刷新(60HZ,120HZ),每次刷新触发一次requestAnimationFrame回调函数
                //但是requestAnimationFrame的回调函数注册生命只有一次,因此需要循环注册,才能达到一直调用的效果
                window.requestAnimationFrame(renderFun);
            ;
            // window.requestAnimationFrame(renderFun);
            renderFun();

            //画布全屏
            window.addEventListener("dblclick", () =>
                if (document.fullscreenElement)
                    document.exitFullscreen();
                 else
                    //document.documentElement.requestFullscreen();
                    render.domElement.requestFullscreen();
                
            );

            //监听画面变化,更新渲染画面,(自适应的大小)
            window.addEventListener("resize", () =>
                //更新摄像机的宽高比
                camera.aspect = window.innerWidth / window.innerHeight;
                //更新摄像机的投影矩阵
                camera.updateProjectionMatrix();
                //更新渲染器宽度和高度
                render.setSize(window.innerWidth, window.innerHeight);
                //设置渲染器的像素比
                render.setPixelRatio(window.devicePixelRatio);
                console.log("画面变化了");
            );
        ,
        methods:
            paush(animate)
                animate.pause();
            ,
        ,
    ;
</script>

<style scoped lang="scss">
</style>

效果图:

衰减量(属于距离衰减,衰减量越大,距离越近)

 关键代码:

            //灯光衰减量控制
            directionalLight.decay = 2;
            gui.add(directionalLight, 'decay').min(-5).max(5).step(1).name("灯光衰减量");
            //需要渲染器开启物理渲染
            render.physicallyCorrectLights =true;

完整代码:

<template>
    <div id="three_div"></div>
</template>

<script>
    import * as dat from 'dat.gui' //界面控制
    import * as THREE from "three";
    import
        OrbitControls
     from "three/examples/jsm/controls/OrbitControls";
    import
        RGBELoader
     from "three/examples/jsm/loaders/RGBELoader"
    export default
        name: "HOME",
        components:
            // vueQr,
            // glHome,
        ,
        data()
            return ;
        ,
        mounted()
            //使用控制器控制3D拖动旋转OrbitControls
            //控制3D物体移动

            //1.创建场景
            const scene = new THREE.Scene();
            console.log(scene);

            //2.创建相机
            const camera = new THREE.PerspectiveCamera(
                75,
                window.innerWidth / window.innerHeight,
                0.1,
                1000
            );
            //设置相机位置
            camera.position.set(0, 0, 10);
            //将相机添加到场景
            scene.add(camera);
            //添加物体
            //创建一个半径为1,经纬度分段数位20的球
            const cubeGeometry = new THREE.SphereBufferGeometry(2, 100, 100);
            //纹理加载器加载图片
            const cubeMaterial = new THREE.MeshStandardMaterial(
                //side: THREE.DoubleSide,
            );
            //根据几何体和材质创建物体
            const mesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
            //将物体加入到场景
            scene.add(mesh);

            //创建平面几何体
            const planeGeometry = new THREE.PlaneBufferGeometry(50, 50);
            //创建平面物体
            const planeMesh = new THREE.Mesh(planeGeometry, cubeMaterial);
            planeMesh.position.set(0, -2, 0);
            planeMesh.rotation.x = -Math.PI / 2;
            //场景添加平面物体
            scene.add(planeMesh);

            //给场景所有的物体添加默认的环境贴图
            //添加坐标轴辅助器
            const axesHepler = new THREE.AxesHelper(5);
            scene.add(axesHepler);
            //标准材质需要借助灯光

            //添加周围环境灯光(由物体发出的灯光)参数(灯色,强度0-1)
            const light = new THREE.AmbientLight(0xFFFFFF, 0.7);
            scene.add(light);
            //直线光(由光源发出的灯光)
            // const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.7);
            // directionalLight.position.set(10, 10, 10);
            // scene.add(directionalLight);
            const directionalLight = new THREE.SpotLight(0xFFFFFF, 0.7);
            directionalLight.position.set(10, 10, 10);
            scene.add(directionalLight);

            // scene.add(mesh2);
            //初始化渲染器
            const render = new THREE.WebGLRenderer();
            //设置渲染器的尺寸
            render.setSize(window.innerWidth, window.innerHeight);
            //使用渲染器,通过相机将场景渲染进来

            //创建轨道控制器,可以拖动,控制的是摄像头
            const controls = new OrbitControls(camera, render.domElement);
            //设置控制阻尼,让控制器有更真实的效果
            controls.enableDamping = true;


            //开启投影
            //开启渲染器投影
            render.shadowMap.enabled = true;
            //开启灯光动态投影
            directionalLight.castShadow = true;
            //开启物体投影
            mesh.castShadow = true;
            //开启平面接受投影
            planeMesh.receiveShadow = true;
            //投影模糊度
            directionalLight.shadow.radius = 10;
            //设置投影的宽度和高度
            // directionalLight.shadow.mapSize.set(1024, 1024);

            //平行光投射相机的属性
            // directionalLight.shadow.camera.near= 0.5;
            // directionalLight.shadow.camera.far= 500;
            // directionalLight.shadow.camera.top= 3;
            // directionalLight.shadow.camera.bottom= -2;
            // directionalLight.shadow.camera.left= -2;
            // directionalLight.shadow.camera.right= 2;
            //灯光跟着物体移动而移动
            directionalLight.target = mesh;
            //directionalLight.angle = Math.PI/10;
            //创建gui
            const gui = new dat.GUI();
            // gui.add(directionalLight.shadow.camera, 'near').min(1).max(25).step(1).name("相机近距离").onChange( () =>
            //     directionalLight.shadow.camera.updateProjectionMatrix();
            // )
            gui.add(mesh.position, 'x').min(-30).max(30).step(1).name("移动位置");
            gui.add(directionalLight, 'angle').min(0).max(Math.PI/2).step(0.1).name("灯光弧度");
            
            //灯光有效距离,默认0表示不衰减
            directionalLight.distance = 0;
            gui.add(directionalLight, 'distance').min(0).max(100).step(1).name("灯光有效距离");
            
            //边缘衰减
            gui.add(directionalLight, 'penumbra').min(0).max(1).step(0.01).name("灯光边缘衰减");
            //灯光强度控制
            directionalLight.decay = 2;
            gui.add(directionalLight, 'decay').min(-5).max(5).step(1).name("灯光边缘衰减");
            //需要渲染器开启物理渲染
            render.physicallyCorrectLights =true;

            //将webgl渲染的canvas内容添加到body上
            document.getElementById("three_div").appendChild(render.domElement);

            //渲染下一帧的时候就会调用回调函数
            let renderFun = () =>
                //更新阻尼数据
                controls.update();
                //需要重新绘制canvas画布
                render.render(scene, camera);
                //监听屏幕刷新(60HZ,120HZ),每次刷新触发一次requestAnimationFrame回调函数
                //但是requestAnimationFrame的回调函数注册生命只有一次,因此需要循环注册,才能达到一直调用的效果
                window.requestAnimationFrame(renderFun);
            ;
            // window.requestAnimationFrame(renderFun);
            renderFun();

            //画布全屏
            window.addEventListener("dblclick", () =>
                if (document.fullscreenElement)
                    document.exitFullscreen();
                 else
                    //document.documentElement.requestFullscreen();
                    render.domElement.requestFullscreen();
                
            );

            //监听画面变化,更新渲染画面,(自适应的大小)
            window.addEventListener("resize", () =>
                //更新摄像机的宽高比
                camera.aspect = window.innerWidth / window.innerHeight;
                //更新摄像机的投影矩阵
                camera.updateProjectionMatrix();
                //更新渲染器宽度和高度
                render.setSize(window.innerWidth, window.innerHeight);
                //设置渲染器的像素比
                render.setPixelRatio(window.devicePixelRatio);
                console.log("画面变化了");
            );
        ,
        methods:
            paush(animate)
                animate.pause();
            ,
        ,
    ;
</script>

<style scoped lang="scss">
</style>

效果图:

第二十一周

第二十一周

1.简述redis集群的实现原理

1.redis主从架构

https://redis.io/ 官方

redis 集群与高可用

虽然redis 可以实现单机的数据持久化,但无论是 rdb也好或者aof也好,都解决不了单点宕机的问题,即一旦单台 redis 服务器本身出现系统故障,硬件故障等问题后,就会造成数据丢失,因此,单机的性能也是有极限的,因此需要使用另外的技术来解决单点故障和性能扩展问题。

redis 主从复制架构
主从模式(master/slave) 可是实现redis 数据的跨主机备份程序端连接到高可用负载的VIP,然后连接到负载服务器设置的Redis后端real server,此模式不需要在程序里面配置Redis服务器的真实IP地址,当后期Redis服务器IP地址发生变更只需要更改redis 相应的后端real server即可,可避免更改程序中的IP地址设置。

主从复制特点
一个 master 可以有多个 slave 
一个slave 只能有一个 master 
数据流向是从master到slave单向的

主从复制实现
Redis Slave 也要开启持久化并设置和master同样的连接密码,因为后期 slave 会有提升为 master 的可能,slave端切换master同步后会丢失之前的所有数据,而通过持久化可以恢复数据。
一旦某个slave 成为一个 master 的 salve ,redis slave  服务会清空当前redis 服务器上的所有数据并将 master 的数据导入到自己的内存,但是如果只是断开同步关系后,则不会删除当前已经同步过的数据。
当配置redis 复制功能时,强烈建议打开主服务器的持久化功能,否则的话,由于延迟等问题,部署的主节点redis服务应该要避免自动启动

在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。
因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。
无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动启动。

2.redis 哨兵(Sentinel)

redis 哨兵(Sentinel)
主从架构无法实现master和slave 角色的自动切换,即当 master 出现 redis 服务异常。主机断电,磁盘损坏等问题导致 master 无法使用,而 redis 主从复制无法实现自动故障转移(将 slave 自动提升为新 master),需要手动修改环境配置,才能切换到 slave redis 服务器,另外当单台 redis 服务器性能无法满足业务写入需要的时候,也无法横向扩展redis 服务的并行写入性能。

当单台Redis服务器性能无法满足业务写入需求的时候,也需要解决以上的两个核心问题
1.master 和 slave 角色的无缝切换,让业务无感知从而不影响业务使用
2.可横向动态扩展redis服务器,从而实现多台服务器并行写入以实现更高并发的目的

redis 集群实现方式:
 客户端分片:由应用决定将不同的 key 发送到不同的 redis服务器
 代理分片:由代理决定将不同的key发送到不同的 redis 服务器,代理程序如:codis,twemproxy等
 redis cluster

哨兵 (Sentinel) 工作原理
sentinel架构和故障转移
redis sentinel 架构
客户端从 sentinel 获取redis信息

sentinel故障转移
1.多个sentinel发现并确认master有问题
2.选举出一个sentinel作为领导
3.选出一个slave作为master
4.通知其余slave 成为新的 master 的 slave 
5.通知客户端主动变化
6.等待老的master 复活成为新master 的slave

sentinel 进程是用于监控 redis集群中 master 主服务器工作的状态,在master 主服务器发生故障的时候,可以实现 master 和 slave服务器的切换,保证系统的高可用,此功能在redis2.6+的版本中已经应用,redis的哨兵模式到了2.8版本之后就稳定了下来,一般在生产环境下建议使用 redis的2.8版本之后的版本

哨兵(sentinel)是一个分布式系统,可以在一个架构中运行多个哨兵(sentinel)进程,这些进程使用流言协议(gossip protocols) 来接收关于 master 主服务器是否下线的信息,并使用投票协议(Agreement Protocols)来
决定是否执行自动故障迁移,以及选择哪个 slave 作为新的 master 

每个哨兵(sentinel)进程会向其他哨兵(sentinel) master  slave 定时发送消息,以确认对方是否活着,如果发现对方在指定配置时间(此项可配置)内未能得到回应,则暂时认为对方已经离线,也就是所谓的主观任务宕机(主观:是每个成员都具有的独自的而且可是相同也可能不同的意识),英文 subjective down 简称 sdown

有主观宕机,对应的有客观宕机,当哨兵群中的多数 sentinel 进程在对 master 主服务器做出 sdown的判断,并且通过 sentinel  is-master-down-by-add 命令互相交流之后,得出的 master server 下线判断

这种方式就是 客观宕机(客观: 不依赖某种意识而已经实际存在的一切事物) 英文名称  objectivel down  简称 odown 

通过一定的vote 算法,从剩下的 slave 从服务器节点中,选一台提升为 master 服务器节点,然后自动修改相关配置,并开启故障转移(failover)

sentinel 机制可以解决 master 和 slave 角色自动切换的问题,但单个 master 的性能瓶颈问题无法解决,类似于mysql中的 MHA 功能 

redis sentinel 中的 sentinel 节点个数应该为大于等于3 且做好为奇数,偶数可能发生脑裂

客户端初始化时连接的是 sentinel 节点集合,不再是具体的 redis 节点,但是 sentinel 只是配置中心不是代理

redis sentinel  节点与普通redis 没有区别,要实现读写分离依赖于客户端程序

redis3.0 之前版本中,生产环境一般使用哨兵模式,3.0推出 redis cluster 功能,可以支持更大规模的生产环境

sentinel 中的三个定时任务
 每10秒每个 sentinel 对master和 slave执行info
 发现 slave节点
 确认主从关系

 每2秒每个 sentinel 通过master节点的 channel 交换信息(pub/sub)

 通过sentinel_:hello频道交互
 交互对节点的看法和自身信息

 每1秒每个sentinel对其他 sentinel 和redis 执行ping

3.redis cluster 工作原理

redis 主从架构没有实现 redis 的高可用,高并发、高性能,横向扩展
redis 哨兵(sentinel)实现了redis 的主从复制的高可用,是redis主从复制的高可用解决方案

redis cluster 工作原理
在哨兵(sentinel)机制中,可用解决redis高可用问题,即当master故障后可以自动将 slave 提升为 master,从而可以保证redis服务器的正常使用,但是无法解决redis单机写入的瓶颈问题,即单机redis写入性能限制于单机的内存大小,并发数量,网卡速率等因素。
为了解决单机性能瓶颈,提高redis性能,可以使用分布式集群的解决方案

早期Redis分布式集群部署方案:
1.客户端分区:由客户端程序决定key写分配和写入的redis node,但是需要客户端自己处理写入分配、高可用管理和故障转移等
2.代理方案:基于三方软件实现redis proxy,客户端先连接之代理层,由代理层实现key的写入分配,对客户端来说是有比较简单,但是对于集群管节点增减相对比较麻烦,而且代理本身也是单点和性能瓶颈。为了解决单机性能瓶颈,提高redis性能,可以使用分布式集群的解决方案

redis 3.0 版本之后推出了无中心架构的 redis cluster 机制,在无中心的 redis 集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接。

redis cluster 特点如下:
1.所有redis 节点使用(PING机制)互联
2.集群中某个节点的是否失效,是由整个集群中超过半数的节点监测都失效,才能算真正的失效
3.客户端不需要proxy即可直接连接redis,应用程序中需要配置由全部的redis服务器IP
4.redis cluster 把所有的 redis node  平均映射到 0-16383个槽位(slot)上,读写需要到指定的redis node上进行操作,因此有多少个redis node相当于 redis 并发扩展了多少倍,每个 redis node 承担 16384/N 个槽位
5.redis cluster 预先分配16384个槽位(slot),当需要在redis 集群中写入一个 key-value的时候,会使用 CRC16(key)%16384之后的值(得到的值都属于16384整个范围之内)决定将 key 写入哪一个槽位从而决定写入哪一个redis节点上,从而有效解决单机瓶颈。

循环冗余检验CRC,是基于数据计算的一组校验码,用于核对数据传输过程中是否被更改或者传输错误
key的槽位数计算公式: N(key的槽位数)=crc16(key)%16384  

Redis cluster 基本架构
假如三个节点分别是: A B C  三个节点,采用哈希槽(hash slot)的方式来分配 16384个 slot 的话它们三个节点分别承担的 slot 区间可以是:

节点A覆盖 0-5460
节点B覆盖 5461-10922
节点C覆盖 10923-16383

Redis cluster 主从架构
Redis cluster的架构虽然解决了并发的问题,但是又引入了一个新的问题,每个Redis master的高可用
如何解决?
那就是对每个master 节点都实现主从复制,从而实现 redis 高可用性

6台服务器,分别是三组master/slave,适用于生产环境

2.实现基于redis5或者redis6的redis cluster

0.Redis cluster 基本架构

Redis Cluster 部署架构说明
6台服务器,分别是三组master/slave,适用于生产环境

Redis cluster 基本架构
假如三个节点分别是: A B C  三个节点,采用哈希槽(hash slot)的方式来分配 16384个 slot 的话它们三个节点分别承担的 slot 区间可以是:

节点A覆盖 0-5460
节点B覆盖 5461-10922
节点C覆盖 10923-16383

1.环境需求

每个redis 节点采用相同的redis 版本、相同的密码 硬件配置
所有redis服务器必须没有任何数据
环境: 6台服务器,分别是三组 master/slave 适用于生产环境

#集群节点

node0 192.168.80.8
node1 192.168.80.18

node2 192.168.80.28
node3 192.168.80.38

node4 192.168.80.48
node5 192.168.80.58

#预留服务器扩展使用
node6 192.168.80.68
node7 192.168.80.78

node8 192.168.80.88
node9 192.168.80.98

说明: redis5.x 和之前的版本相比较有很多变化,这里直接采用redis6.x,使用最新的版本。
redis master节点数一般是奇数、防止出现投票数两边一样,发生脑裂,扩容预留4台服务器组成2组主从加入集群。

redis cluster 部署方式介绍

redis cluster 有多种部署方法

 原生命令安装
 理解redis cluster架构
 生产环境不使用

原生命令手动部署过程
 在所有节点安装 redis 并配置开启 cluster 
 各个节点执行 meet 实现所有节点的相互通信
 为各个 master 节点指派槽位范围
 指定各个节点的主从关系

 官方工具安装
 高效 准确
 生产环境可以使用

 自主研发
 可以实现可视化的自动化部署

2.在所有节点安装 redis 并启用 cluster 功能

redis_install_cluster_v03.sh 脚本安装redis cluster 脚本未开启AOF功能,执行脚本后单独开启。

[root@node0 ~]# cat redis_install_cluster_v03.sh 

#install_redis_v03.sh

#redis

#!/bin/bash

redis_file="redis-6.2.6.tar.gz"
redis_url="http://download.redis.io/releases/$redis_file"
redis_version=$(echo $redis_file%*.tar.gz)

redis_base=/app
redis_home=$redis_base/redis

redis_password=123456

cpus=`lscpu|awk -F: /^CPU\\(s\\):/ print $2`

install_redis() 

for i in "gcc jemalloc-devel wget"
do
     rpm -q $i &>/dev/null || yum install -y -q $i  ||  echo "安装软件包失败,请检查网络配置" false ; exit; 
done

if [ -f $redis_file ];then
   echo "$redis_file is ok "
else
   wget $redis_url ||  echo  "redis 源码包下载失败";  exit ; 
fi

tar -xvf $redis_file

cd $redis_version

make -j $cpus PREFIX=$redis_base/$redis_version  install && echo "redis 编译安装完成"||   echo "redis 编译安装失败"; exit ;  

ln -sf $redis_base/$redis_version $redis_home

ln -s $redis_home/bin/redis-* /usr/bin/ &>/dev/null

mkdir -p $redis_home/etc,log,data,run

cp redis.conf $redis_home/etc/

sed -i.org -e s/bind 127.0.0.1/bind 0.0.0.0/  -e "/# requirepass/a requirepass $redis_password" -e /masterauth/a masterauth 123456  -e "/^dir .*/c dir $redis_home/data/"  -e "/logfile .*/c logfile $redis_home/log/redis-6379.log"  -e  "/^pidfile .*/c  pidfile $redis_home/run/redis_6379.pid" -e /# cluster-enabled yes/a cluster-enabled yes -e /# cluster-config-file nodes-6379.conf/a cluster-config-file nodes-6379.conf -e /cluster-require-full-coverage yes/c cluster-require-full-coverage no $redis_home/etc/redis.conf

id redis &&>/dev/null || useradd -r -s /sbin/nologin redis &>/dev/null

chown -R redis:redis  $redis_home
chown -R redis:redis  $redis_base/$redis_version

cat>>/etc/sysctl.conf<<EOF
net.core.somaxconn=1024
vm.overcommit_memory=1
EOF
sysctl -p

echo  echo never>/sys/kernel/mm/transparent_hugepage/enabled >>/etc/rc.d/rc.local
chmod u+x  /etc/rc.d/rc.local
/etc/rc.d/rc.local

cat>/usr/lib/systemd/system/redis.service<<EOF

[Unit]
Description=Redis Sentinel
After=network.target

[Service]
ExecStart=$redis_home/bin/redis-server $redis_home/etc/redis.conf --supervised systemd
ExecReload=/bin/kill -s HUP \\$MAINPID
ExecStop=/bin/kill -s QUIT \\$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

EOF

systemctl daemon-reload

systemctl enable --now redis &>/dev/null && echo "redis 服务启动成功,信息如下:" ||   echo  "reis  服务启动失败"; exit; 

sleep 3

redis-cli -a $redis_password info server  2>/dev/null



main() 

install_redis



main
在6台服务器上同时执行 redis_install_cluster_v03.sh 脚本安装redis cluster,脚本未开启AOF功能,执行脚本后单独开启。
[root@node0 ~]# sh redis_install_cluster_v03.sh 
[root@node1 ~]# sh redis_install_cluster_v03.sh 
[root@node2 ~]# sh redis_install_cluster_v03.sh 
[root@node3 ~]# sh redis_install_cluster_v03.sh 
[root@node4 ~]# sh redis_install_cluster_v03.sh 
[root@node5 ~]# sh redis_install_cluster_v03.sh 

#状态检查6台服务器,以下给出其中一台,其他机器操作命令一样就不重复了
#检查每个机器上redis服务的状态
[root@node0 ~]# systemctl status redis
● redis.service - Redis Sentinel
   Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2022-01-24 06:50:31 GMT; 2h 58min ago
 Main PID: 5896 (redis-server)
   Status: "Ready to accept connections"
   CGroup: /system.slice/redis.service
           └─5896 /app/redis/bin/redis-server 0.0.0.0:6379 [cluster]

Jan 24 06:50:31 node0.magedu.com systemd[1]: Starting Redis Sentinel...
Jan 24 06:50:31 node0.magedu.com systemd[1]: Started Redis Sentinel.

#检查每个机器是否监听在6379  16379
[root@node0 ~]# ss -lntp|grep "redis"
LISTEN     0      511          *:6379                     *:*                   users:(("redis-server",pid=5896,fd=6))
LISTEN     0      511          *:16379                    *:*                   users:(("redis-server",pid=5896,fd=9))
LISTEN     0      511      [::1]:6379                  [::]:*                   users:(("redis-server",pid=5896,fd=7))
LISTEN     0      511      [::1]:16379                 [::]:*                   users:(("redis-server",pid=5896,fd=10))

#检查每个机器redis进程
[root@node0 ~]# ps -auxf|grep redis|grep -v "grep"
redis      5896  0.2  0.4 198112  4012 ?        Ssl  08:47   0:09 /app/redis/bin/redis-server 0.0.0.0:6379 [cluster]

3.启用AOF持久化存储

redis_install_cluster_v03.sh脚本安装redis cluster,脚本未开启AOF功能.
你说这是不是给自己找麻烦,写入redis_install_cluster_v03.sh 脚本直接开启AOF不就省事多了,何必还费这么多力气。

#启用AOF持久化存储
AOF appendonlyfile 按照操作顺序依次将操作追加到指定的日志文件末尾
AOF 和 RDB 一样使用了写时复制机制,AOF 默认每秒钟 fsync一次,即将执行的命令保存到 aof 文件当中,这样即使 redis 服务器发生故障最多只丢失1 秒的数据,也可以设置不同的 fsync 策略 always
即设置每次执行命令的时候执行fsync , fsync 会在后台执行线程,所以主线程可以继续处理用户正常请求而不受到写入 AOF 文件的 /IO  影响

同时启用 RDB 和 AOF 进行恢复时,默认 AOF 文件优先级高于 RDB 文件,即会使用 AOF 文件进行恢复

注意:AOF 模式默认是关闭的,第一次开启 aof 后并重启服务生效后,会因为 AOF 的优先级高于 RDB,而 aof 默认没有数据文件存在,从而导致所有数据丢失

范例:正确使用 AOF 功能,防止数据丢失
#查看aof是否开启
[root@node0 etc]# redis-cli -a  123456 --no-auth-warning  config get appendonly
1) "appendonly"
2) "no"
#设置临时启用aof,第一次开启 aof 自动触发 AOF 重写,内存中的数据立刻写入 aof 文件
[root@node0 etc]# redis-cli -a  123456 --no-auth-warning  config set appendonly yes
OK
#写入配置文件永久有效
sed -ri.bak s/^appendonly no/appendonly yes/ redis.conf

#6台服务器都开启AOF持久存储,这里用其中一台机器做例子,其他机器就不重复了。

[root@node0 ~]# redis-cli -a  123456 --no-auth-warning  config set appendonly yes
OK

[root@node0 ~]# redis-cli -a  123456 --no-auth-warning  config get appendonly
1) "appendonly"
2) "yes"

[root@node0 ~]#sed -ri.bak s/^appendonly no/appendonly yes/ /app/redis/etc/redis.conf

[root@node0 ~]#sed -i s/^appendfilename "appendonly.aof"/appendfilename "appendonly_6379.aof"/ /app/redis/etc/redis.conf

[root@node0 ~]# grep -iE "^appendonly yes|^appendfilename" /app/redis/etc/redis.conf
appendonly yes
appendfilename "appendonly_6379.aof"

[root@node0 ~]# systemctl restart redis

[root@node0 ~]# ls -l /app/redis/data
total 4
-rw-r--r-- 1 redis redis   0 Jan 24 10:37 appendonly_6379.aof
-rw-r--r-- 1 redis redis 114 Jan 24 06:50 nodes-6379.conf
[root@node0 ~]# 

4.创建集群

#redis-cli --cluster-replace 1  表示每个 master 对应一个 slave 节点
#按照顺序写IP 前三个是主节点,槽位自动分配
#密码都一样 在任何一个集群上执行创建集群都可以

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning  --cluster create 192.168.80.8:6379 192.168.80.18:6379 192.168.80.28:6379  192.168.80.38:6379 192.168.80.48:6379 192.168.80.58:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
#槽位分配
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
#主从对应关系
Adding replica 192.168.80.48:6379 to 192.168.80.8:6379
Adding replica 192.168.80.58:6379 to 192.168.80.18:6379
Adding replica 192.168.80.38:6379 to 192.168.80.28:6379

#带 M 开头的是 master
M: b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379
   slots:[0-5460] (5461 slots) master  #当前master 的槽位起始位置和结束位置
M: 83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379
   slots:[5461-10922] (5462 slots) master
M: 7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379
   slots:[10923-16383] (5461 slots) master

#带 S 开头的是 slave    
S: d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379
   replicates 7151b60af1f183d5431927f377f0e16f94838e75
S: 3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379
   replicates b065525ecdb8ae7ce49cc43d7b5b14078e30e29c
S: 0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379
   replicates 83dfa275f5311c343276552c2c7c7f584aaef2e4
Can I set the above configuration? (type yes to accept): yes    #输入yes 自动创建集群  
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 192.168.80.8:6379)
M: b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379
   slots:[0-5460] (5461 slots) master   #已经分配的槽位
   1 additional replica(s)              #分配了一个slave

S: d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379
   slots: (0 slots) slave #slave 是不直接分配槽位的,只有 master 才分配槽位
   replicates 7151b60af1f183d5431927f377f0e16f94838e75 #对应master为192.168.80.28:6379的id 

S: 3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379
   slots: (0 slots) slave
   replicates b065525ecdb8ae7ce49cc43d7b5b14078e30e29c #对应的master为192.168.80.8:6379的id 

S: 0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379
   slots: (0 slots) slave
   replicates 83dfa275f5311c343276552c2c7c7f584aaef2e4 #对应master为192.168.80.18:6379的id
M: 83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration. #所有master 槽位分配完成
>>> Check for open slots...  #检查打开的槽位
>>> Check slots coverage...  #检查槽位覆盖范围
[OK] All 16384 slots covered.#所有槽位(16384)分配完成

#观察以上结果,可以看到3组 master/slave
master:192.168.80.8    --> slave:192.168.80.48
master:192.168.80.18   --> slave:192.168.80.58
master:192.168.80.28   --> slave:192.168.80.38

#主从对应关系
Adding replica 192.168.80.48:6379 to 192.168.80.8:6379
Adding replica 192.168.80.58:6379 to 192.168.80.18:6379
Adding replica 192.168.80.38:6379 to 192.168.80.28:6379
#使用命令查看 主从关系
[root@node0 ~]# redis-cli  -h 192.168.80.8 -a 123456 --no-auth-warning cluster slots
1) 1) (integer) 0
   2) (integer) 5460
   3) 1) "192.168.80.8"
      2) (integer) 6379
      3) "b065525ecdb8ae7ce49cc43d7b5b14078e30e29c"
   4) 1) "192.168.80.48"
      2) (integer) 6379
      3) "3733d994c111461db387f3008f02b8bf00bcbea2"
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "192.168.80.18"
      2) (integer) 6379
      3) "83dfa275f5311c343276552c2c7c7f584aaef2e4"
   4) 1) "192.168.80.58"
      2) (integer) 6379
      3) "0a70fb9f476ea2f2371e8fa1fe17049a61dad00a"
3) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "192.168.80.28"
      2) (integer) 6379
      3) "7151b60af1f183d5431927f377f0e16f94838e75"
   4) 1) "192.168.80.38"
      2) (integer) 6379
      3) "d36b66fadf62e7dc1c19442f26ac9bb9c4eded42"
[root@node0 ~]# 

#查看主从状态
#-c 启用集群模式(遵循 -ASK 和 -MOVED 重定向)
[root@node0 ~]# redis-cli -c -a 123456 --no-auth-warning info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.80.48,port=6379,state=online,offset=1652,lag=1
master_failover_state:no-failover
master_replid:5efab808625db3ddf110e66c9907d797d3436db2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1652
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1652

#自定要连接的 master 

[root@node0 ~]# redis-cli -h 192.168.80.8  -a 123456 --no-auth-warning  info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.80.48,port=6379,state=online,offset=1778,lag=1
master_failover_state:no-failover
master_replid:5efab808625db3ddf110e66c9907d797d3436db2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1778
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1778

#查看指定 master 节点的 slave 节点信息

[root@node0 ~]# redis-cli -h 192.168.80.8  -a 123456 --no-auth-warning  cluster nodes
d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379@16379 slave 7151b60af1f183d5431927f377f0e16f94838e75 0 1643022349000 3 connected
3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379@16379 slave b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 0 1643022349602 1 connected
b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379@16379 myself,master - 0 1643022348000 1 connected 0-5460
0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379@16379 slave 83dfa275f5311c343276552c2c7c7f584aaef2e4 0 1643022350615 2 connected
83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379@16379 master - 0 1643022349000 2 connected 5461-10922
7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379@16379 master - 0 1643022349000 3 connected 10923-16383

#验证集群状态

[root@node0 ~]# redis-cli   -a 123456 --no-auth-warning  cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6           #集群中的机器数量6
cluster_size:3                  #6个机器分三对主从
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1348
cluster_stats_messages_pong_sent:1272
cluster_stats_messages_sent:2620
cluster_stats_messages_ping_received:1267
cluster_stats_messages_pong_received:1348
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:2620

[root@node0 ~]# redis-cli  -h 192.168.80.8 -c   -a 123456 --no-auth-warning  cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1437
cluster_stats_messages_pong_sent:1358
cluster_stats_messages_sent:2795
cluster_stats_messages_ping_received:1353
cluster_stats_messages_pong_received:1437
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:2795

#查看任意节点的集群状态

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning  --cluster info 192.168.80.8:6379
192.168.80.8:6379 (b065525e...) -> 0 keys | 5461 slots | 1 slaves.
192.168.80.18:6379 (83dfa275...) -> 0 keys | 5462 slots | 1 slaves.
192.168.80.28:6379 (7151b60a...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning  --cluster info 192.168.80.18:6379
192.168.80.18:6379 (83dfa275...) -> 0 keys | 5462 slots | 1 slaves.
192.168.80.8:6379 (b065525e...) -> 0 keys | 5461 slots | 1 slaves.
192.168.80.28:6379 (7151b60a...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.

#查看集群node对应关系

[root@node0 ~]# redis-cli  -h 192.168.80.8   -a 123456 --no-auth-warning  cluster nodes
d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379@16379 slave 7151b60af1f183d5431927f377f0e16f94838e75 0 1643022775715 3 connected
3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379@16379 slave b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 0 1643022776000 1 connected
b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379@16379 myself,master - 0 1643022774000 1 connected 0-5460
0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379@16379 slave 83dfa275f5311c343276552c2c7c7f584aaef2e4 0 1643022775000 2 connected
83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379@16379 master - 0 1643022774000 2 connected 5461-10922
7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379@16379 master - 0 1643022776725 3 connected 10923-16383

#检查集群
[root@node0 ~]# redis-cli  -h 192.168.80.8   -a 123456 --no-auth-warning  --cluster check 192.168.80.38:6379
192.168.80.28:6379 (7151b60a...) -> 0 keys | 5461 slots | 1 slaves.
192.168.80.18:6379 (83dfa275...) -> 0 keys | 5462 slots | 1 slaves.
192.168.80.8:6379 (b065525e...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.80.38:6379)
S: d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379
   slots: (0 slots) slave
   replicates 7151b60af1f183d5431927f377f0e16f94838e75
M: 7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379
   slots: (0 slots) slave
   replicates 83dfa275f5311c343276552c2c7c7f584aaef2e4
M: 83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379
   slots: (0 slots) slave
   replicates b065525ecdb8ae7ce49cc43d7b5b14078e30e29c
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

5.验证集群写入 key

python脚本实现redis cluster 集群写入 
[root@route-backup ~]# yum install python3 -y
[root@route-backup ~]# pip3 install redis-py-cluster

[root@route-backup ~]# vim redis_write_cluster.py
#!/usr/bin/env python3
from rediscluster  import RedisCluster

if __name__ == __main__:

    startup_nodes = [
        "host":"192.168.80.8", "port":6379,
        "host":"192.168.80.18", "port":6379,
        "host":"192.168.80.28", "port":6379,
        "host":"192.168.80.38", "port":6379,
        "host":"192.168.80.48", "port":6379,
        "host":"192.168.80.58", "port":6379]
    try:
        redis_conn= RedisCluster(startup_nodes=startup_nodes,password=123456, decode_responses=True)
    except Exception as e:
        print(e)

    for i in range(0, 10000):
        redis_conn.set(key+str(i),value+str(i))
        print(key+str(i)+:,redis_conn.get(key+str(i)))

[root@route-backup ~]# /usr/bin/python3 redis_write_cluster.py 

#验证写入的数据
#每个节点平均分配写入
#查看第一个节点

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning dbsize
(integer) 20794

[root@node0 ~]# redis-cli -c -a 123456 --no-auth-warning get key100
"value100"

6.模拟 master 故障 对应的 slave 节点自动提升为新 master

#模拟 node0 节点出现故障,需要相应的数秒故障转移

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning info replication 
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.80.48,port=6379,state=online,offset=2994613,lag=0
master_failover_state:no-failover
master_replid:5efab808625db3ddf110e66c9907d797d3436db2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2994613
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1946038
repl_backlog_histlen:1048576

#停止node0
[root@node0 ~]# redis-cli -a 123456 --no-auth-warning shutdown 

#监听端口 6379  16379 都已经关闭
[root@node0 ~]# ss -lnt 
State      Recv-Q Send-Q                                Local Address:Port                                               Peer Address:Port              
LISTEN     0      128                                               *:111                                                           *:*                  
LISTEN     0      128                                               *:22                                                            *:*                  
LISTEN     0      100                                       127.0.0.1:25                                                            *:*                  
LISTEN     0      128                                            [::]:111                                                        [::]:*                  
LISTEN     0      128                                            [::]:22                                                         [::]:*                  
LISTEN     0      100                                           [::1]:25                                                         [::]:*                  
[root@node0 ~]# 

#192.168.80.8:6379已经停止,对应的从92.168.80.48:6379已经提升为主了
# 192.168.80.48:6379  已经成为新的 master 
[root@node0 ~]# redis-cli -a 123456 --no-auth-warning --cluster info 192.168.80.18:6379
Could not connect to Redis at 192.168.80.8:6379: Connection refused
192.168.80.18:6379 (83dfa275...) -> 66665 keys | 5462 slots | 1 slaves.
192.168.80.48:6379 (3733d994...) -> 66678 keys | 5461 slots | 0 slaves.
192.168.80.28:6379 (7151b60a...) -> 66657 keys | 5461 slots | 1 slaves.
[OK] 200000 keys in 3 masters.
12.21 keys per slot on average.

[root@node0 ~]# redis-cli -a 123456 --no-auth-warning --cluster check  192.168.80.18:6379
Could not connect to Redis at 192.168.80.8:6379: Connection refused
192.168.80.18:6379 (83dfa275...) -> 66665 keys | 5462 slots | 1 slaves.
192.168.80.48:6379 (3733d994...) -> 66678 keys | 5461 slots | 0 slaves.
192.168.80.28:6379 (7151b60a...) -> 66657 keys | 5461 slots | 1 slaves.
[OK] 200000 keys in 3 masters.
12.21 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.80.18:6379)
M: 83dfa275f5311c343276552c2c7c7f584aaef2e4 192.168.80.18:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 3733d994c111461db387f3008f02b8bf00bcbea2 192.168.80.48:6379
   slots:[0-5460] (5461 slots) master
M: 7151b60af1f183d5431927f377f0e16f94838e75 192.168.80.28:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: d36b66fadf62e7dc1c19442f26ac9bb9c4eded42 192.168.80.38:6379
   slots: (0 slots) slave
   replicates 7151b60af1f183d5431927f377f0e16f94838e75
S: 0a70fb9f476ea2f2371e8fa1fe17049a61dad00a 192.168.80.58:6379
   slots: (0 slots) slave
   replicates 83dfa275f5311c343276552c2c7c7f584aaef2e4
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

#查看node0 对应的从已经是 master 了
[root@node0 ~]# redis-cli -a 123456 --no-auth-warning -h 192.168.80.48 info replication 
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:b25eb3e677c69972ec409742d897cdf035e7c742
master_replid2:5efab808625db3ddf110e66c9907d797d3436db2
master_repl_offset:2994655
second_repl_offset:2994656
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1946080
repl_backlog_histlen:1048576

#恢复node0  节点
[root@node0 ~]# systemctl start  redis

#查看自动生成的配置文件,可以查看到 node0 自动成为了 slave 节点
[root@node0 ~]# grep  "192.168.80.8" /app/redis/data/nodes-6379.conf 
b065525ecdb8ae7ce49cc43d7b5b14078e30e29c 192.168.80.8:6379@16379 myself,slave 3733d994c111461db387f3008f02b8bf00bcbea2 0 1643024915314 7 connected

# node0 对应的 slave 提升为 master   192.168.80.8 是它的 slave 
[root@node0 ~]#  redis-cli -a 123456 --no-auth-warning -h 192.168.80.48   info replication 
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.80.8,port=6379,state=online,offset=2994921,lag=0
master_failover_state:no-failover
master_replid:b25eb3e677c69972ec409742d897cdf035e7c742
master_replid2:5efab808625db3ddf110e66c9907d797d3436db2
master_repl_offset:2994935
second_repl_offset:2994656
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1946360
repl_backlog_histlen:1048576
Redis cluster 集群节点维护

redis cluster 的集群扩容、缩容的时候,如何做数据的备份恢复? 还没有弄明白?

redis 集群运行之后,难免由于硬件故障,网络规划,业务增长等原因对已有集群进行相应的调整,比如:增加 redis node 节点、减少节点、节点迁移、更换服务器等。
增加节点和删除节点会涉及到已有的槽位重新分配及数据迁移

小型的公司 6台机器做redis cluster  应该足够了,也别整什么、扩容、缩容redis cluster

稍有规模的公司,你说费那劲做什么呢? 出手大方一些,舍得花钱。。。。直接来个22台高性能服务器,11组master slave节点的redis cluster用上即可。
[root@node0 ~]# echo 22/2|bc
11
#再不行来个220台机器做redis cluster
[root@node0 ~]# echo  220/2|bc
110

以此类推

3.redis 配置和优化

redis 主要配置项
redis_version:6.2.6
/app/redis/etc/redis.conf
bind 0.0.0.0   #监听地址 可用用空格隔开后多个监听IP,默认监听在 127.0.0.1 

protected-mode yes #redis3.2 之后加入的新特性,在没有配置 bind IP 和密码的时候, redis 只允许访问 127.0.0.1:6379 可用远程连接,但当访问将提示警告信息并拒绝远程访问。   临时修改  config set protected-mode no   config get protected-mode
OK

port 6379  #默认监听端口,多实例 redis 使用不同的端口

tcp-backlog 511 #tcp 三次握手的时候 server  端收到 client ack 确认号之后的队列值,默认 511 

timeout 0  #客户端和 redis 服务器端的连接超时时间,默认0 表示永不超时 应用程序与 redis 建立长连接 节省了 tcp 三次握手的开销 

tcp-keepalive 300  #TCP 会话保持时间,默认 300s

daemonize no   #默认no 即直接运行 redis-server 程序时,不作为守护进程运行,而是以前台方式运行,如果想在后台运行需要改成 yes ,当redis 为守护进程运行时,他会写一个 pid  到 /var/run/redis.pid 文件 
               当Redis被upstart或者systemd监管时,这个参数没有影响

#supervised auto  #默认值为no,和OS相关参数,可设置通过 upstart 和 systemd 管理 redis 守护进程,centos7 后都使用 systemd 。  systemd服务管理文件中已经使用 systemd 管理,  ExecStart=/app/redis/bin/redis-server /app/redis/etc/redis.conf --supervised systemd 在 redis 文件中默认no  不影响

pidfile /app/redis/run/redis_6379.pid    # pid 文件路径,可用修改至其他路径下

loglevel notice   #redis 运行时候的日志级别

logfile /app/redis/log/redis-6379.log #日志路径

databases 16  #设置数据库数量 默认:0-15 共16个库  不同的业务写入不同的数据库里面,放置key 冲突

always-show-logo no  #在启动 redis 时候是否显示或在日志中记录 redis 的 logo 没有什么实际意义

#将数据库保存到磁盘 将数据库保存到磁盘

#以下三个条件满足其一即会做快照 rdb文件 

#3600 秒(一小时)后,如果至少更改了 1 个键
#在900秒内至少有1个key内容发生更改,就执行快照机制
save 900 1
save 300 10
save 60 10000

# save <seconds> <changes>
# save 3600 1
# save 300 100
# save 60 10000

stop-writes-on-bgsave-error no  # redis_version:6.2.6默认为yes, 可能会因空间满等原因快照无法保存出错时,会禁止redis 写入操作,生产建议为 no #此项只针对配置文件中的自动save有效

rdbcompression yes  #持久化到RDB文件时,是否压缩, yes 为压缩 no 不压缩

rdbchecksum yes     #是否开启rc64校验,默认开启

dbfilename dump_6379.rdb  #rdb快照文件名称

rdb-del-sync-files no

dir /app/redis/data/    #rdb快照文件存放路径

#主从复制相关

# replicaof <masterip> <masterport>  #指定复制的 master 主机地址和端口 5.0 版本之前的指令为 slaveof 

# masterauth <master-password>       #指定复制的 master 主机的密码

# masteruser <username>

replica-serve-stale-data yes  #当从库同主库失去连接或者复制正在进行,从库有两种运行方式:

 1. 设置为 yes(默认值),从库会继续响应客户端的读请求,此为建议的值  一边与主库同步 一边 响应客户端的读请求
 2. 设置为no 除去特定命令外的人恶化请求都会返回一个错误"SYNC with master in progress"

replica-read-only yes #是否设置从库只读

repl-diskless-sync no #是否使用 socket 方式复制数据(无盘同步),新 slave  第一次连接 master 时需要做数据的全量同步, redis server 就要从内存 dump 出新的 rdb 文件
然后从 master 传到 slave  ,有两种方式把 rdb 文件传输给客户端:

1.  基于硬盘(disk-backed) 为 no  时 master 创建一个新的进程 dump 生成 rdb 磁盘文件,rdb 完成之后由父进程(即主进程) 将 rdb 文件发送给  slaves 此值为默认值
2.  基于socket (diskless) master 创建一个新的进程直接 dump rdb 至 slave 的网络 socket 不经过主进程和硬盘

#推荐基于硬盘(为no),因为rdb 文件创建后,课同时传输给更多的 slave  但是基于 socket(为 yes) 新 slave 连接到 master 之后得逐个同步数据,只有当磁盘I/O较慢且网络较快时可用 diskless(yes) 否则一般建议使用磁盘(no)

repl-diskless-sync-delay 5 # 启用无盘复制时,可以配置延迟,diskless 时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间内道道的客户端,会一起通过 diskless 方式同步数据,但是一旦复制开始, master 节点不会再接受新的 slave 的复制请求
直到下一次同步开始再接收新请求,即无法为延迟时间后到达的新副本提供服务,新副本排队等待下一次 rdb 传输,一次服务器会等待一段时间才能让更多副本到达,推荐值  30-60 

# repl-ping-replica-period 10 # slave 根据 master 指定的时间进行周期性的 ping  master 用于监测 master 状态,默认10s 

# repl-timeout 60      #复制连接的超时时间,需要大于 repl-ping-slave-period  否则会经常报超时

repl-disable-tcp-nodelay no  #是否在slave 套接字发送 sync 之后禁用 tcp_nodelay  如果选则 yes  redis 将合并多个报文为一个大的报文,从而使用更少数据量的包向 slave 发送数据,
但是将数据传输到 slave 上由延迟,  linux 内核的默认配置会达到 40 毫秒。 如果时 no  数据传输到 slave 的延迟将会减少,但是要使用更多的带宽

# repl-backlog-size 1mb #默认 1m  复制缓冲区内存大小,当 slave 断开连接一段时间后,该缓冲区会累积复制副本数据,因此当 slave 重新连接时,通常不需要完全重新同步,只需要传递在副本中的断开连接后没有同步的部分数据即可,
。只有在至少一个 slave 连接之后才分配此内存空间,建议建立主从时此值要调整大一些或者在低峰期配置,否则会导致同步到 slave 失败
# 物理机器 16G 给 redis  8G   还有8G 留给linux
# repl-backlog-ttl 3600  #多长时间内 master 没有 slave 连接 就清空 backlog 缓冲区,默认 3600s 

replica-priority 100  #当 master 不可用,哨兵 sentinel 会根据  slave  的优先级选举一个 master 此值最低的 slave 会优先当选 master 而配置为 0  永远不会被选举,一般多个 slave 都设置一样的值,让其自动选择

requirepass 123456  # 默认无密码,设置redis 连接密码,之后需要 auth pass  如果由特殊符号用 " "  引起来 生产建议配置

# rename-command CONFIG ""  #重命名一些高危命令,示例: rename-command  FLUSHALL "" 禁用命令  rename-command del mgedu
rename-command FLUSHALL ""

# maxclients 10000  #redis 最大连接客户端数量 默认一万

# maxmemory <bytes>  # redis 使用的最大内存,单位为bytes 字节 0 为不限制,建议设置为物理内存的一半,8G的内存的计算方式8G*1024MB*1024KB*1024kbyte 需要注意的时缓冲区时不计算在 maxmemory 内,生产
中如果不设置此项,可能会导致 OOM
#1G   echo  1*1024*1024*1024|bc
maxmemory  1073741824

appendonly no #默认 no 是否开启AOF 日志记录,默认 redis 使用的时 rdb 方式持久化,这种方式在许多应用中已经足够用了,但是 redis 如果中途宕机,会导致可能由几分钟的数据丢失
(取决于 dump数据的时间间隔),根据 save 来策略进行持久化, append only file 时另外一种持久化方式,可用提供更好的持久化特性,redis会把每次写入的数据在接收后都写入 appendonly.aof 文件
每次启动时 redis 都会把这个文件的数据读入内存里面,先忽略 rdb 文件,默认不启用此功能

appendfilename "appendonly.aof"   #文本文件AOF 的文件名,存放在 dir 指令指定的目录中

appendfsync everysec  #aof 持久化策略的配置
#no 表示由操作系统保证数据同步到磁盘 Linux的默认fsync 策略时30 秒,最多会丢失30s的数据
#aways 表示每次写入都执行fsync 保证数据同步到磁盘,安全性高,性能较差
#everysec 表示每秒执行一次 fsync 肯能会导致丢失这1s 的数据,此值为默认值,生产建议值

#同时在执行 bgrewriteaof 操作和主进程写 aof 文件的操作,两者都会操作磁盘,而 bgrewriteaof 往往会涉及大量磁盘操作,这样就会造成主进程在写 aof文件时候出现阻塞的情况,以下参数实现控制 

    no-appendfsync-on-rewrite no #在 aof rewrite 期间,是否对 aof 新记录的 append 暂缓使用文件同步策略,主要考虑磁盘IO开销和请求阻塞时间。默认no 表示不暂缓,新的 aof 记录仍然会被立即同步,到磁盘,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题

    #为yes 相当于将 appendfsync 设置为no 这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞(因为没有竞争磁盘),但是如果这个时候 redis 挂掉,就是丢失数据,丢失多少数据呢? linux的默认fsync 策略时 30秒,如果为yes 可能丢失30秒数据,但由于 yes 性能较好而且会避免出现阻塞因此比较推荐

    #rewrite 即对aof 文件进行整理,将空闲空间回收,从而可以减少恢复数据时间

    auto-aof-rewrite-percentage 100  #当AOF 文件增长超过指定百分比例时,重写 AOF 文件,设置 0 表示不自动重写 AOF 日志文件 ,重写是为了使 aof 文件体积保持最小,但是还可以确保,保存最完整的数据

    auto-aof-rewrite-min-size 64mb  #触发 aof_rewrite 的最小文件大小  数据量比较大的时候可用调大这个值  128mb

aof-load-truncated yes   #是否加载由于某些原因导致的末尾异常的AOF 文件(主进程被kill/断电等) 建议 yes 

aof-use-rdb-preamble no  ## redis4.0 新增RDB-AOF 混合持久化格式,在开启这个功能之后, AOF 重写产生的文件将同时包含 rdb 格式的内容和 aof 格式的内容,其中 rdb 格式的内容用于记录已经由的数据,而 aof rewrite

格式的内容则用于记录最近发生了变化的数据,这样 redis 就可以同时兼有 rdb 持久化和aof 持久化的优点 (既能快速低生产重写文件,也能在出现问题时,快速载入数据) 默认为no 即不起用此项功能

lua-time-limit 5000   #lua 脚本的最大执行时间,单位为毫秒

#redis cluster 

# cluster-enabled yes  #是否开启集群模式,默认不开启,即单机模式  将注释去掉开启
# cluster-config-file nodes-6379.conf # 由node 节点自动生成的集群配置文件名称

# cluster-node-timeout 15000  #集群中node节点连接超时时间,单位ms 超过此时间,会踢出集群

# cluster-replica-validity-factor 10 #单位为次数,在执行故障转移的时候可能有些系节点和 master 断开一段时间导致数据比较旧,这些节点就不适用于选举为master 超过这个时间的就不会被进行故障转移,
不能当选 master 

# cluster-migration-barrier 1  #集群迁移屏障,一个主节点至少拥有1个正常工作的从节点,即如果主节点的 slave 节点故障后会将多余的从节点分配到当前主节点成为其新的从节点

# cluster-allow-replica-migration yes #集群请求槽位全部覆盖,如果一个主库宕机且没有备份库就会出现集群槽位不全,那么 yes 情况下 redis 集群槽位验证不全就不再对外提供服务,而no 则可以继续使用但
时会出现数据查不到的情况因为有槽位丢失了,生产 建议为 no 

# cluster-replica-no-failover no #如果为yes ,此选项阻止在主服务器发生故障时尝试对其主服务器进行故障转移,但是,主服务器任然可以执行手动强制故障转移,一般为 no 

#slow log 是 redis用来记录超过指定执行时间的日志系统,执行时间不包括与客户端交谈,发送回复等i/o操作,而是实际执行命令所需的时间(在该阶段线程被阻塞并且不能同时为其他请求提供服务)
由于 slow log 保存在内存里面,读写速度非常快,因此可放心低使用,不惜担心以为开启 slow log 而影响 redis 的速度

slowlog-log-slower-than 10000 #以微秒为单位的慢日志记录,为负数会禁用慢日志,为0 会记录每个命令操作,默认值为  10ms 一般一条命令执行都在微秒级别,生产建议设置为 1ms - 10ms 之间

slowlog-max-len 128 #最多记录多少条慢日志的保存队列长度,达到长度后,记录新命令会将最旧的命令,从命令队列中删除,以此滚动删除,即,先进先出,队列固定长度默认 128,值偏小,生产建议设为 1000 以上

以上是关于ThreeJS-聚光等衰减(二十一)的主要内容,如果未能解决你的问题,请参考以下文章

极客时间运维进阶训练营第二十一周作业-待完成

ThreeJS - 错误的光线旋转

Python开发第二十一篇:Web框架之Django基础

day9-Python学习笔记(二十一)单元测试

Python开发第二十一篇:Web框架之Django基础

每日算法&面试题,大厂特训二十八天——第二十一天(树)