龚哥哥 - 山里男儿 爱生活、做自己!
table thead表头和列固定
发表于 2020-12-13 | 浏览(18562) | 前端

Image

查看DEMO演示效果 https://gong.gg/my-test/table/index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>table表头和列固定</title>
    <style>
        .container {
            width: 800px;
            height: 300px;
            overflow: scroll;
            border: 1px solid #ccc;
        }
        table {
            border-collapse: collapse;
        }
        table tbody tr:nth-child(odd) td {
            background-color: #f5f5f5;
        }
        table thead tr > th {
            position: sticky;
            top: 0;
            background: #ccc;
        }
        table tbody tr > td {
            border: 1px solid #eee;
            min-width: 100px;
        }
        table thead tr > th,
        table tbody tr > td {
            padding: 10px;
        }
        table thead tr > th:first-child,
        table thead tr > th:last-child {
            z-index: 1;
        }
        table thead tr > th:first-child,
        table tbody tr > td:first-child,
        table thead tr > th:last-child,
        table tbody tr > td:last-child {
            position: sticky;
            text-align: center;
        }
        table thead tr > th:first-child,
        table tbody tr > td:first-child {
            left: 0;
            -webkit-box-shadow: 1px 0px 1px #ddd;
            -moz-box-shadow: 1px 0px 1px #ddd;
            box-shadow: 1px 0px 1px #ddd;
            -webkit-box-shadow: 5px 0px 10px rgba(136, 136, 136, 0.3);
            -moz-box-shadow: 5px 0px 10px rgba(136, 136, 136, 0.3);
            box-shadow: 5px 0px 10px rgba(136, 136, 136, 0.3);
        }
        table thead tr > th:last-child,
        table tbody tr > td:last-child {
            right: 0;
            -webkit-box-shadow: 0px 0px 1px #ddd;
            -moz-box-shadow: 0px 0px 1px #ddd;
            box-shadow: 0px 0px 1px #ddd;
            -webkit-box-shadow: -5px 0px 10px rgba(136, 136, 136, 0.3);
            -moz-box-shadow: -5px 0px 10px rgba(136, 136, 136, 0.3);
            box-shadow: -5px 0px 10px rgba(136, 136, 136, 0.3);
        }
        table tbody tr > td:first-child,
        table tbody tr > td:last-child {
            background: #fff;
        }
    </style>
</head>
<body>
    <p>标题、第一列、最后一列固定、方便操作和数据对应表头</p>
    <p>下面提供了一个简单的演示、具体的实际效果可以查看 <a href="https://shopxo.net/" target="_blank">ShopXO</a> 系统中的应用</p>
    <div class="container">
        <table>
            <thead>
                <tr>
                    <th>title1</th>
                    <th>title2</th>
                    <th>title3</th>
                    <th>title4</th>
                    <th>title5</th>
                    <th>title6</th>
                    <th>title7</th>
                    <th>title8</th>
                    <th>title9</th>
                    <th>title10</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>data1-1</td>
                    <td>data1-2</td>
                    <td>data1-3</td>
                    <td>data1-4</td>
                    <td>data1-5</td>
                    <td>data1-6</td>
                    <td>data1-7</td>
                    <td>data1-8</td>
                    <td>data1-9</td>
                    <td>data1-10</td>
                </tr>
                <tr>
                    <td>data2-1</td>
                    <td>data2-2</td>
                    <td>data2-3</td>
                    <td>data2-4</td>
                    <td>data2-5</td>
                    <td>data2-6</td>
                    <td>data2-7</td>
                    <td>data2-8</td>
                    <td>data2-9</td>
                    <td>data2-10</td>
                </tr>
                <tr>
                    <td>data3-1</td>
                    <td>data3-2</td>
                    <td>data3-3</td>
                    <td>data3-4</td>
                    <td>data3-5</td>
                    <td>data3-6</td>
                    <td>data3-7</td>
                    <td>data3-8</td>
                    <td>data3-9</td>
                    <td>data3-10</td>
                </tr>
                <tr>
                    <td>data4-1</td>
                    <td>data4-2</td>
                    <td>data4-3</td>
                    <td>data4-4</td>
                    <td>data4-5</td>
                    <td>data4-6</td>
                    <td>data4-7</td>
                    <td>data4-8</td>
                    <td>data4-9</td>
                    <td>data4-10</td>
                </tr>
                <tr>
                    <td>data5-1</td>
                    <td>data5-2</td>
                    <td>data5-3</td>
                    <td>data5-4</td>
                    <td>data5-5</td>
                    <td>data5-6</td>
                    <td>data5-7</td>
                    <td>data5-8</td>
                    <td>data5-9</td>
                    <td>data5-10</td>
                </tr>
                <tr>
                    <td>data6-1</td>
                    <td>data6-2</td>
                    <td>data6-3</td>
                    <td>data6-4</td>
                    <td>data6-5</td>
                    <td>data6-6</td>
                    <td>data6-7</td>
                    <td>data6-8</td>
                    <td>data6-9</td>
                    <td>data6-10</td>
                </tr>
                <tr>
                    <td>data7-1</td>
                    <td>data7-2</td>
                    <td>data7-3</td>
                    <td>data7-4</td>
                    <td>data7-5</td>
                    <td>data7-6</td>
                    <td>data7-7</td>
                    <td>data7-8</td>
                    <td>data7-9</td>
                    <td>data7-10</td>
                </tr>
                <tr>
                    <td>data8-1</td>
                    <td>data8-2</td>
                    <td>data8-3</td>
                    <td>data8-4</td>
                    <td>data8-5</td>
                    <td>data8-6</td>
                    <td>data8-7</td>
                    <td>data8-8</td>
                    <td>data8-9</td>
                    <td>data8-10</td>
                </tr>
                <tr>
                    <td>data9-1</td>
                    <td>data9-2</td>
                    <td>data9-3</td>
                    <td>data9-4</td>
                    <td>data9-5</td>
                    <td>data9-6</td>
                    <td>data9-7</td>
                    <td>data9-8</td>
                    <td>data9-9</td>
                    <td>data9-10</td>
                </tr>
                <tr>
                    <td>data10-1</td>
                    <td>data10-2</td>
                    <td>data10-3</td>
                    <td>data10-4</td>
                    <td>data10-5</td>
                    <td>data10-6</td>
                    <td>data10-7</td>
                    <td>data10-8</td>
                    <td>data10-9</td>
                    <td>data10-10</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

阅读全文

使用百度API翻译接口将网页翻译成英文
发表于 2020-8-31 | 浏览(21642) | PHP

将网页内容翻译成英文,或者更多语言

HTML代码

<!doctype html>
<head>
    <meta charset="utf-8" />
    <title>网页翻译测试</title>
</head>
<body>
    <div>测试
        <div>橘子</div>
    </div>
    <div>梨</div>
    <div>测试</div>
    <div>零食</div>
    <div>水果</div>
    <div>上班</div>
    <div>ShopXO 免费开源系统,可商用,可二次开发。</div>

    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.17.0/js/md5.js"></script>
    <script type="text/javascript">
        function transformLanguage(newLanguage) {
            // 获取所有dom元素中文
            var transformStr = '';
            var temp_index = 0;
            // 获取所有dom元素
            function getChildDom(dom, type, data = {}) {
                if(type == 'read') {
                    [...dom.children].forEach(v => {
                        // 判断中文
                        // /^[\u0391-\uFFE5]+$/
                        var re= /[\u4e00-\u9fa5]/g;
                        // 防止某些标签有内容并且有标签 ,或者有空格 
                        var vHtml = $(v).contents().filter(function (index, content) {return content.nodeType === 3}).text().trim();
                        // 跳过script标签
                        if (re.test(vHtml) && v.tagName != 'SCRIPT') {
                            // 使用\n换行拼接、接口将返回数组
                            transformStr += `${vHtml}\n`
                        }
                        // 递归获取元素
                        getChildDom(v, type, data);
                    })
                }else {
                    [...dom.children].forEach(v => {
                        // 判断中文
                        // /^[\u0391-\uFFE5]+$/
                        var re= /[\u4e00-\u9fa5]/g;
                        var vHtml = $(v).contents().filter(function (index, content) {return content.nodeType === 3}).text().trim();
                        // 跳过script标签
                        if (re.test(vHtml) && v.tagName != 'SCRIPT') {
                            if((data[temp_index] || null) != null)
                            {
                                var transOld = data[temp_index]['src'];
                                var transNew = data[temp_index]['dst'];
                                // 防止标签里面还有标签,所以只替换里面的html,使用replace
                                $(v).html(
                                    $(v).html().replace( transOld, transNew)
                                )
                                temp_index++;
                            }
                        }
                        // 递归获取元素
                        getChildDom(v, type, data);
                    })
                }
            }
            getChildDom(document,'read');

            if((transformStr || null) != null)
            {
                getTranslateData();
                // 获取翻译
                // 接口申请地址 https://api.fanyi.baidu.com/
                function getTranslateData() {
                    var appid = '...';   // 百度翻译API的appid
                    var key = '...';   // 百度翻译API的key
                    var salt = (new Date).getTime();
                    var query = transformStr;
                    var from = 'zh';
                    var to = newLanguage;
                    var str1 = appid + query + salt + key;
                    var sign = md5(str1);
                    $.ajax({
                        url: 'baidu.php',
                        type: 'post',
                        dataType: 'json',
                        data: {
                            q: query,
                            appid: appid,
                            salt: salt,
                            from: from,
                            to: to,
                            sign: sign
                        },
                        success: function(data) {
                            data.trans_result && getChildDom(document,'write',data.trans_result);
                        }
                    });
                }
            }
        }

        // 页面加载完成再执行
        window.onload = function () {
            transformLanguage('en');
        };
    </script>
</body>

PHP代码

<?php

/**
 * curl
 * @author  Devil
 * @blog    http://gong.gg/
 * @version 1.0.0
 * @date    2020-08-31
 * @desc    description
 * @param   [type]          $url     [description]
 * @param   [type]          $post    [description]
 * @param   boolean         $is_json [description]
 */
function CurlPost($url, $post, $is_json = false)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
    curl_setopt($ch, CURLOPT_URL, $url);

    // 是否 json
    if($is_json)
    {
        $data_string = json_encode($post);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "Content-Type: application/json; charset=utf-8",
                "Content-Length: " . strlen($data_string)
            )
        );
    } else {
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "Content-Type: application/x-www-form-urlencoded",
                "cache-control: no-cache"
            )
        );
    }

    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

echo CurlPost('http://api.fanyi.baidu.com/api/trans/vip/translate', $_POST);

阅读全文

Vue | Element 页面1000多个 input 组件输入出现卡顿解决方案
发表于 2020-6-15 | 浏览(16424) | 前端

思路:页面正常渲染文本,当点击文本的时候把当前点击的文本区域置为 input 组件, input失去blur焦点的时候 再改变为 text 文本

经测试这里做了2万个,无卡顿

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <!-- import CSS -->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <style type="text/css">
    .input {
      width: 100px;
    }
    .input input {
      height: 54px;
      text-align: center;
      font-size: 16px;
      border: 0;
    }
    .input-c {
      border: 1px solid #eee;
      widows: 100px;
      float: left;
      margin-top: 20px;
      margin-right: 20px;
      width: 100px;
      text-align: center;
    }
    .input-text {
      width: 100px;
      height: 54px;
      line-height: 54px;
      margin: 0;
    }
    .activeInput input {
      color: #f00;
    }
  </style>
</head>
<body>
  <div id="app">
    <div v-for="(item, i) in dataList" class="input-c">
        <p v-if="item.type == 'text'" class="input-text" @click="text_fun($event,i)" data-value="item">{{item.value}}</p>
        <el-input v-if="item.type == 'input'" type="text" @blur="input_fun(i)" v-bind:class="['input',{ 'activeInput' : focusIndex === i}]" v-model="item.value" placeholder="内容"></el-input>
    </div>
  </div>
</body>
  <!-- import Vue before Element -->
  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <!-- import JavaScript -->
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: function() {
        return {
          dataList:[],
        }
      },
       mounted() {
            this.add();
        },
      methods:{
          text_fun: function(e, index)
          {            
            let p = e.target.parentElement
            this.focusIndex = index;
            this.dataList[index]['type'] = 'input';
            this.$nextTick(v=>{
              p.firstElementChild.firstElementChild.focus();
            });
          },
          input_fun: function(index)
          {
            this.dataList[index]['type'] = 'text';
          },
          add:function(){
            var i = 0;
            while(i <= 10000)
            {
              this.dataList.push({
                value: i,
                type: 'text',
              });
              i++;
            }
          }
        }
    })
  </script>
</html>

阅读全文

商城系统中商品规格使用笛卡尔积运算
发表于 2019-9-22 | 浏览(26065) | 前端
/**
 * 笛卡尔积生成规格
 * @author   Devil
 * @blog     http://gong.gg/
 * @version  1.0.0
 * @datetime 2019-09-22T00:33:48+0800
 * @desc     description
 * @param    {[array]}                 arr1 [要进行笛卡尔积的二维数组]
 * @param    {[array]}                 arr2 [最终实现的笛卡尔积组合,可不写]
 */
function SpecCartesian(arr1, arr2)
{
    // 去除第一个元素
    var result = [];
    var temp_arr = arr1;
    var first = temp_arr.splice(0, 1);

    if((arr2 || null) == null)
    {
        arr2 = [];
    }

    // 判断是否是第一次进行拼接
    if(arr2.length > 0)
    {
        for(var i in arr2)
        {
            for(var k in first[0].value)
            {
                result.push(arr2[i]+','+first[0].value[k]);
            }
        }
    } else {
        for(var i in first[0].value)
        {
            result.push(first[0].value[i]);
        }
    }

    // 递归进行拼接
    if(arr1.length > 0)
    {
        result = SpecCartesian(arr1, result);
    }

    // 返回最终笛卡尔积
    return result;
}

// 自动生成规格
var spec = [
    {
        "title": "颜色",
        "value": ["黑色", "白色", "蓝色"]
    },
    {
        "title": "尺码",
        "value": ["S", "M", "L", "XL", "XXL"]
    },
    {
        "title": "长度",
        "value": ["5分裤", "7分裤", "9分裤", "长裤"]
    }
];
var data = SpecCartesian(spec);
console.log(data);

效果图

Image

阅读全文

ShopXO国内领先企业级B2C免费开源电商系统
发表于 2019-3-29 | 浏览(49408) | 开源项目

ShopXO企业级免费开源电商系统


项目介绍

ShopXO国内领先企业级免费开源电商系统!

  • 求实进取、创新专注、自主研发、国内领先企业级电商系统解决方案。
  • 遵循MIT开源协议发布,无需授权、可商用、可二次开发、满足99%的电商运营需求。
  • 支持PC+H5、支付宝小程序、微信小程序、百度小程序、头条&抖音小程序、QQ小程序、APP等...
  • 支持多仓库、多商户、线下门店模式运营(组件插件化、即插即用),可视化DIY拖拽装修。

体验码

二维码

基础信息

官方QQ群、答案shopxo.net

  • 官方1群:833531593
  • 官方2群:641730223
  • 官方3群:1127073697
  • 官方多商户群:782971010
  • 官方多门店群:281418967
  • 官方uniapp群:679303149

当前项目源代码平台

uniapp端源代码平台

部分功能特性

1. 多种模式:销售+自提+虚拟+展示+销售/自提,多仓库独立库存管理、可视化DIY拖拽装修
2. 分销:可配置1~3级、分享赚取佣金、小程序商品海报、取货点、佣金可配置具体到SKU(比例和固定金额)
3. 会员等级:等级折扣(具体到商品规格)、满减、手动设置会员等级、付费购买+续费+三级返佣
4. 限时秒杀:独立秒杀中心、商品详情秒杀倒计时、自动开始结束
5. 优惠券:满减劵、折扣劵、注册送、用户领取、管理员发放
6. 报价单:商品报价单excel导出、在线打印、自定义字段及拖拽排序
7. 商品相册标签:给商品相册打上特有标签
8. 钱包:用户钱包、在线充值,提现
9. 微信小程序直播:直播卖货、海报分享
10. 积分商城+签到:积分抵扣、积分兑换、无限发布签到活动获取积分
11. 多端小程序:微信、支付宝、百度、头条/抖音、QQ
12. 多商户:入驻独立店铺、可视化DIY拖拽装修+页面设计、不同商家自动拆单,店铺、商品、分类、结算、订单管理、数据统计
13. 批发:阶梯售价、新购+复购单独配置、自由配置无限极数量阶梯售价、可以具体到规格
14. 门店:派单+转单+核销+代客下单+商品+批次+次卡+员工和薪水自动抽成+充值+收银+桌码+O2O店铺运营模式,线上线下完美结合、适用(餐饮+美容美发+在线预约+门店消费+自提+外送+快递)
......
(更多查看应用商店 https://store.shopxo.net/)

系统特性

系统特性

全平台支持

全平台支持

小程序支持多种配色

多种配色

小程序效果图片

主要部分
扩展部分

可视化DIY拖拽装修展示

可视化拖拽

PC端展示

PC脑端

后台管理展示

后台管理

系统管理模块

系统管理模块

用户端模块

用户端模块

扩展功能

扩展功能

荣誉证书

GVP证书

如何参与项目

非常欢迎您对ShopXO的开发作出贡献!你可以选择以下方式向ShopXO贡献:

阅读全文

魔鬼部署系统搭建 Nginx+Uwsgi+Django
发表于 2017-8-23 | 浏览(44394) | 开源项目

效果图

Image

流程图

Image

项目地址

https://github.com/gongfuxiang/mogui

https://coding.net/u/gongfuxiang/p/mogui/git


其它相关可参考本博客中的其它文章

1、nginx
2、git ssh部署
3、mysql

基础信息

系统         CentOS7(7.3.1611)
Python      2.7.5
Django      1.11.3
Vue         2.4.0
Element     1.4.2

centos6自带python 2.4.3, 我们也可以升级到python2.7.13(这一步可跳过)

https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz
tar -zxvf Python-2.7.13.tgz
cd Python-2.7.13
./configure --prefix=/usr/local
make && make altinstall

安装setuptools

wget http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz
tar -zxvf setuptools-0.6c11.tar.gz
cd setuptools-0.6c11
python setup.py build
python setup.py install

安装pip

wget https://pypi.python.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz
tar -zxvf pip-9.0.1.tar.gz
cd pip-9.0.1
python setup.py install

安装python-devel

yum -y install python-devel
yum -y install sqlite-devel

pip安装uwsgi

pip install uwsgi

测试uwsgi是否正常运行 创建 test.py 文件

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return "Hello World"

uwsgi --http :8008 --wsgi-file test.py
在浏览器内输入:http://127.0.0.1:8008,查看是否有"Hello World"输出,若没有输出,请检查你的安装过程。

pip安装django

pip install django==1.11.3

测试 django 是否正常,运行

django-admin.py startproject hello
cd hello
python manage.py runserver 0.0.0.0:8008
在浏览器内输入:http://127.0.0.1:8008,检查django是否运行正常。

安装项目需要用到的python模块

yum -y install mysql-devel
pip install mysql-python 或 pip install mysqlclient

开始部署魔鬼部署系统(github已部署好ssh)

mkdir -p /data/www
cd /data/www
git clone https://github.com/gongfuxiang/mogui.git

修改数据库配置文件

/data/www/mogui/mogui/common/config.py
修改以下配置信息保存即可
# 数据库
db = {
    'name' : 'mogui',       # 数据库名称
    'user' : 'root',        # 用户名
    'pwd'  : 'root',        # 密码
    'host' : 'localhost',   # 连接地址
    'port' : 3306           # 端口号
}

还记得新增实际部署的域名或ip

/data/www/mogui/mogui/settings.py

修改 ALLOWED_HOSTS = ['127.0.0.1', 'localhost']

创建数据表

cd /data/www/mogui
python manage.py migrate

创建uwsgi配置文件 创建 /etc/uwsgi.ini

详细可参考官网文档 https://uwsgi-docs.readthedocs.io/en/latest/

[uwsgi]
chdir           = /data/www/mogui
module          = mogui.wsgi
master          = true
processes       = 10
socket          = 127.0.0.1:9090
vacuum          = true
pidfile         = /var/run/uwsgi.pid    
daemonize       = /var/log/uwsgi.log
uid             = 0

uwsgi常用操作

启动uwsgi
    uwsgi --ini /etc/uwsgi.ini

uwsgi重启
    kill -HUP `cat /var/run/uwsgi.pid`

uwsgi停止
    killall -9 uwsgi

nginx配置(记得重启)

upstream django {
    server 127.0.0.1:9090;
}

server {
    listen      80 default;
    server_name _;
    charset     utf-8;
    client_max_body_size 75M;
    uwsgi_read_timeout 1800;
    uwsgi_send_timeout 300;
    proxy_read_timeout 300;

    location /public {
        alias /data/www/mogui/public;
    }

    location / {
        uwsgi_pass  django;
        include     uwsgi_params;
    }
}

访问 http://127.0.0.7

阅读全文

宽屏幻灯片插件
发表于 2017-8-15 | 浏览(15899) | 前端

百度网盘下载地址:https://pan.baidu.com/s/1bqcOkcJ

效果展示

源代码平台

阅读全文

redis事务
发表于 2017-6-26 | 浏览(21395) | 服务器

一、事务

1、开启事务 multi

2、提交事务 exec

3、取消事务 discard

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name devil
QUEUED
127.0.0.1:6379> set age 26
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> get sex
"2"
127.0.0.1:6379> get age
"26"
127.0.0.1:6379>
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 100
QUEUED
127.0.0.1:6379> del age
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get age
"26"
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379>

二、乐观锁

client-1 开启watch,并在事务中修改name

127.0.0.1:6379> get name
"mogui"
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name devil
QUEUED
127.0.0.1:6379> exec
(nil)

client-2 修改name

127.0.0.1:6379> get name
"mogui"
127.0.0.1:6379> get name
"mogui"
127.0.0.1:6379> set name gong
OK
127.0.0.1:6379> get name
"gong"

client-1 执行exec

127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get name
"gong"

三、发布及订阅消息

client-1 订阅devil频道

127.0.0.1:6379> subscribe devil
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "devil"
3) (integer) 1

client-2 发布频道devil消息

127.0.0.1:6379> publish devil hello
(integer) 1

client-1 订阅频道接收到的消息

1) "message"
2) "devil"
3) "hello"

阅读全文

HAProxy编译安装及配置详解
发表于 2017-6-17 | 浏览(19715) | 服务器

简介

HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。

HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。

HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。

HAProxy实现了一种事件驱动, 单一进程模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户空间(User-Space) 实现所有这些任务,所以没有这些问题。此模型的弊端是,在多核系统上,这些程序通常扩展性较差。这就是为什么他们必须进行优化以 使每个CPU时间片(Cycle)做更多的工作。

结合Keepalived搭建高可用负载均衡服务器架构,详情可参考本博客中的另一篇文章。

HAProxy官网:http://www.haproxy.org/

架构图

Image

准备工作

mkdir /data
cd /data
mkdir soft src www
mkdir soft/haproxy

下载安装

wget http://www.haproxy.org/download/1.7/src/haproxy-1.7.5.tar.gz
tar -zxvf haproxy-1.7.5.tar.gz
cd haproxy-1.7.5
make TARGET=linux2628 ARCH=x86_64 PREFIX=/data/soft/haproxy
make install PREFIX=/data/soft/haproxy

参数说明

使用 [ uname -r ] 查看内核
    如:2.6.18-371.el5,此时该参数就为linux26;内核 大于2.6.28的用:TARGET=linux2628

内核版本
    TARGET=linux26

系统位数
    ARCH=x86_64

haprpxy安装路径
    PREFIX=/data/soft/haproxy

添加haproxy用户和组

groupadd haproxy
useradd -g haproxy haproxy

制作haproxy操作脚本 [ vim /etc/init.d/haproxy ]

#!/bin/sh
#chkconfig: 2345 80 90
#description:haproxy run

# haproxy操作脚本
# @author   Devil
# @version  0.0.1

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/data/soft/haproxy/sbin
PROGDIR=/data/soft/haproxy
PROGNAME=haproxy
DAEMON=$PROGDIR/sbin/$PROGNAME
CONFIG=$PROGDIR/$PROGNAME.cfg
PIDFILE=$PROGDIR/$PROGNAME.pid
DESC="HAProxy daemon"
SCRIPTNAME=/etc/init.d/$PROGNAME
set -e
# Gracefully exit if the package has been removed.
test -x $DAEMON || exit 0

start()
{
    echo -n "Starting $DESC: $PROGNAME"
    $DAEMON -f $CONFIG
    echo "."
}

stop()
{
    echo -n "Stopping $DESC: $PROGNAME"
    kill $(cat $PIDFILE)
    echo "."
}

restart()
{
    echo -n "Restarting $DESC: $PROGNAME"
    $DAEMON -f $CONFIG -p $PIDFILE -st $(cat $PIDFILE)
    echo "."
}

case "$1" in
    start)
        start
    ;;
    stop)
        stop
    ;;
    restart)
        restart
    ;;
    *)
        echo "Usage: systemctl {start|stop|restart} $PROGNAME" >&2
        exit 1
    ;;
esac
exit 0

赐予可执行权限

chmod x+ /etx/init.d/haproxy

添加haproxy命令软连接

ln /etc/init.d/haproxy /usr/bin/haproxy

注册服务

chkconfig --add haproxy

设置开启启动

chkconfig haproxy on

haproxy操作命令

systemctl start haproxy     启动
systemctl restart haproxy   重启
systemctl stop haproxy      停止

配置文件 [ vim /data/soft/haproxy/haproxy.cfg ] 配置文件不存在则创建

#---------------------------------------------------------------------
# 全局配置
#---------------------------------------------------------------------
global
    log 127.0.0.1 local0 #[日志输出配置,所有日志都记录在本机,通过local0输出]
    #log 127.0.0.1 local1 notice #定义haproxy 日志级别[error warringinfo debug]
    daemon #以后台形式运行harpoxy
    nbproc 1 #设置进程数量
    maxconn 4000 #默认最大连接数,需考虑ulimit-n限制, 默认4000
    user haproxy #运行haproxy的用户
    group haproxy #运行haproxy的用户所在的组
    pidfile /data/soft/haproxy/haproxy.pid #haproxy 进程PID文件
    chroot /data/soft/haproxy #chroot运行路径
    #ulimit-n 819200 #ulimit 的数量限制
    #debug #haproxy 调试级别,建议只在开启单进程的时候调试
    #quiet

#---------------------------------------------------------------------
# 默认配置
#---------------------------------------------------------------------
defaults
    mode http #默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK
    log global #采用全局定义的日志
    option httplog #日志类别,采用httplog
    option dontlognull #不记录健康检查日志信息
    retries 3 #3次连接失败就认为是服务器不可用,也可以通过后面设置
    option forwardfor #如果后端服务器需要获得客户端真实ip需要配置的参数,可以从Http Header中获得客户端ip
    option httpclose #每次请求完毕后主动关闭http通道,haproxy不支持keep-alive,只能模拟这种模式的实现
    #option redispatch #当serverId对应的服务器挂掉后,强制定向到其他健康的服务器,以后将不支持
    option abortonclose #当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接
    maxconn 6000 #默认的最大连接数
    timeout connect 5000ms #连接超时
    timeout client 30000ms #客户端超时
    timeout server 30000ms #服务器超时
    #timeout check 2000 #心跳检测超时
    #timeout http-keep-alive 10s #默认持久连接超时时间
    #timeout http-request 10s #默认http请求超时时间
    #timeout queue 1m #默认队列超时时间
    balance roundrobin #设置默认负载均衡方式,轮询方式,类似于nginx的ip_hash

#---------------------------------------------------------------------
# 统计页面配置
#---------------------------------------------------------------------
listen stats
    bind 0.0.0.0:1080 #设置Frontend和Backend的组合体,监控组的名称,按需要自定义名称
    stats refresh 30s #统计页面自动刷新时间
    stats uri /stats #统计页面url
    stats realm Gong Cloud Haproxy #统计页面密码框上提示文本
    stats auth admin:admin #设置监控页面的用户和密码,格式 用户名:密码,可以设置多个用户名
    stats auth devil:devil #第二个用户和密码
    stats hide-version #隐藏统计页面上HAProxy的版本信息
    stats admin if TRUE #设置手工启动/禁用,后端服务器(haproxy-1.4.9以后版本)

#---------------------------------------------------------------------
# 设置haproxy错误页面
#---------------------------------------------------------------------
#errorfile 403 /data/soft/haproxy/www/errorfiles/403.http
#errorfile 500 /data/soft/haproxy/www/errorfiles/500.http
#errorfile 502 /data/soft/haproxy/www/errorfiles/502.http
#errorfile 503 /data/soft/haproxy/www/errorfiles/503.http
#errorfile 504 /data/soft/haproxy/www/errorfiles/504.http

#---------------------------------------------------------------------
# 请求入口处理
#---------------------------------------------------------------------
frontend main
    # 这里建议使用bind *:80的方式,要不然做集群高可用的时候有问题,vip切换到其他机器就不能访问了。
    bind *:80

    # 定义静态规则
    acl url_static path_beg -i /static /images /javascript /stylesheets
    acl url_static path_end -i .jpg .gif .png .css .js .html
    acl host_static hdr_beg(host) -i img. imgs. video. videos. ftp. image. download.

    # 定义动态规则
    acl url_php path_end -i .php

    # 请求归纳
    use_backend static if url_static or host_static
    use_backend dynamic if url_php

    # 不满足则响应backend的默认页面
    default_backend dynamic

#---------------------------------------------------------------------
# 静态请求处理
#---------------------------------------------------------------------
backend static
    # 请求类型
    mode http

    # 分配算法 balance roundrobin 负载轮询,balance source 保存session值,支持static-rr,leastconn,first,uri等参数
    balance roundrobin

    # 实际处理请求的服务器列表
    # cookie 1表示serverid为1
    # weight 代表权重
    # check inter 1500 是检测心跳频率 
    # rise 2是2次正确认为服务器可用
    # fall 3是3次失败认为服务器不可用
    server static01 192.168.0.100:80 cookie 1 weight 1 check inter 2000 rise 2 fall 3

#---------------------------------------------------------------------
# 动态请求处理
#---------------------------------------------------------------------
backend dynamic
    # 请求类型
    mode http

    # 分配算法 balance roundrobin 负载轮询,balance source 保存session值,支持static-rr,leastconn,first,uri等参数
    balance roundrobin

    # 实际处理请求的服务器列表
    # cookie 1表示serverid为1
    # weight 代表权重
    # check inter 1500 是检测心跳频率 
    # rise 2是2次正确认为服务器可用
    # fall 3是3次失败认为服务器不可用
    server web01 192.168.0.103:80 cookie 1 weight 1 check inter 2000 rise 2 fall 3

配置日志记录

1、创建haproxy日志目录
    mkdir /var/log/haproxy

2、vim /etc/rsyslog.conf 在底部添加
    # haproxy log
    $ModLoad imudp
    $UDPServerRun 514
    local0.* /var/log/haproxy/haproxy.log

3、重启日志服务
    systemctl restart rsyslog

访问haproxy查看状态 [ http://host:1080/stats ]

Image

8种负载均衡算法

1、roundrobin
    表示简单的轮询,每个服务器根据权重轮流使用,在服务器的处理时间平均分配的情况下这是最流畅和公平的算法。该算法是动态的,对于实例启动慢的服务器权重会在运行中调整。

2、leastconn
    连接数最少的服务器优先接收连接。leastconn建议用于长会话服务,例如LDAP、SQL、TSE等,而不适合短会话协议。如HTTP.该算法是动态的,对于实例启动慢的服务器权重会在运行中调整。

3、static-rr
    每个服务器根据权重轮流使用,类似roundrobin,但它是静态的,意味着运行时修改权限是无效的。另外,它对服务器的数量没有限制。(该算法一般不用)

4、source
    对请求源IP地址进行哈希,用可用服务器的权重总数除以哈希值,根据结果进行分配。只要服务器正常,同一个客户端IP地址总是访问同一个服务器。如果哈希的结果随可用服务器数量而变化,那么客户端会定向到不同的服务器。(算法一般用于不能插入cookie的Tcp模式。它还可以用于广域网上为拒绝使用会话cookie的客户端提供最有效的粘连)
    该算法默认是静态的,所以运行时修改服务器的权重是无效的,但是算法会根据“hash-type”的变化做调整。

5、uri
    表示根据请求的URI左端(问号之前)进行哈希,用可用服务器的权重总数除以哈希值,根据结果进行分配。只要服务器正常,同一个URI地址总是访问同一个服务器。一般用于代理缓存和反病毒代理,以最大限度的提高缓存的命中率。该算法只能用于HTTP后端。(该算法一般用于后端是缓存服务器)
    该算法默认是静态的,所以运行时修改服务器的权重是无效的,但是算法会根据“hash-type”的变化做调整。

6、url_param
    在HTTP GET请求的查询串中查找<param>中指定的URL参数,基本上可以锁定使用特制的URL到特定的负载均衡器节点的要求。(该算法一般用于将同一个用户的信息发送到同一个后端服务器)
    该算法默认是静态的,所以运行时修改服务器的权重是无效的,但是算法会根据“hash-type”的变化做调整。

7、hdr(name)
    在每个HTTP请求中查找HTTP头<name>,HTTP头<name>将被看作在每个HTTP请求,并针对特定的节点。(如果缺少头或者头没有任何值,则用roundrobin代替)
    该算法默认是静态的,所以运行时修改服务器的权重是无效的,但是算法会根据“hash-type”的变化做调整。

8、rdp-cookie(name)
    为每个进来的TCP请求查询并哈希RDP cookie<name>。(该机制用于退化的持久模式,可以使同一个用户或者同一个会话ID总是发送给同一台服务器。如果没有cookie,则使用roundrobin算法代替)
    该算法默认是静态的,所以运行时修改服务器的权重是无效的,但是算法会根据“hash-type”的变化做调整。

docker镜像下载

docker pull gongfuxiang/haproxy

阅读全文

redis分布式缓存搭建与使用
发表于 2017-6-11 | 浏览(18314) | 服务器

redis官网地址 https://redis.io/

准备6台redis服务

127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
127.0.0.1:7004
127.0.0.1:7005

下载安装redis

mkdir /data
cd /data
mkdir src soft
cd /data/src/
wget http://download.redis.io/releases/redis-3.2.9.tar.gz
tar -zxvf redis-3.2.9.tar.gz
cd redis-3.2.9
make
make install

创建集群位置

cd /data/soft
mkdir redis-cluster
cd redis-cluster
mkdir 7000 7001 7002 7003 7004 7005

拷贝配置文件

cp /data/src/redis-3.2.9/redis.conf /data/soft/redis-cluster/7000
cp /data/src/redis-3.2.9/src/redis-trib.rb /data/soft/redis-cluster/

修改配置文件

cd /data/soft/redis-cluster/7000
vim redis.conf 修改以下选项内容

port 7000
daemonize yes  # 后台启动
cluster-enabled yes
cluster-config-file nodes.conf  # 指定集群节点配置文件
cluster-node-timeout 5000  # 指定集群节点超时时间
appendonly yes  # 指定持久化方式

完成后分别把 redis.conf 文件拷贝到7000 ~ 7005目录下,修改 port 对应目录名称的端口号

完成后分别启动6个实例

重启需要先删除数据文件和节点配置文件

for((i=0;i<=5;i++)); do cd /data/soft/redis-cluster/700$i/;rm -rf appendonly.aof  dump.rdb nodes.conf; done

循环启动redis

for((i=0;i<=5;i++)); do cd /data/soft/redis-cluster/700$i/; redis-server redis.conf; done

创建集群

/data/soft/redis-cluster/redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

循环停止redis服务

for((i=0;i<=5;i++)); do redis-cli -c -p 700$i shutdown; done

启动redis服务,启动完成后查看实例列表 [ ps -ef | grep redis ],如下显示则说明启动成功

ps -ef | grep redis

root      32631      1  0 15:39 ?        00:00:00 redis-server 127.0.0.1:7000 [cluster]
root      32668      1  0 15:40 ?        00:00:00 redis-server 127.0.0.1:7001 [cluster]
root      32675      1  0 15:40 ?        00:00:00 redis-server 127.0.0.1:7002 [cluster]
root      32682      1  0 15:41 ?        00:00:00 redis-server 127.0.0.1:7003 [cluster]
root      32687      1  0 15:41 ?        00:00:00 redis-server 127.0.0.1:7004 [cluster]
root      32696      1  1 15:41 ?        00:00:00 redis-server 127.0.0.1:7005 [cluster]

安装ruby,由于redis集群客户端实现很少,redis集群的启动需要用到ruby实现的redis-trib.rb,所以我们需要先安装ruby。

yum -y install ruby

安装gem

yum -y install rubygems

gem 安装 redis ruby 接口

gem install redis

创建redis集群

--replicas 则指定了为 Redis Cluster 中的每个 Master 节点配备几个 Slave 节 点,节点角色由顺序决定,先 master 之后是 slave

/data/soft/redis-cluster/redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
M: 443adf2c1d13b41866dc0dc108ffe858d931ac77 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
M: d74ee0110d7bcf0858ca76eb32caeca5094fa5ff 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
M: 13fd8b25f070382657c700fc9d8184418a82f8b6 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
S: 229a83d4997539771bf6792455c992cd215b7273 127.0.0.1:7003
   replicates 443adf2c1d13b41866dc0dc108ffe858d931ac77
S: 179a946b3ab66fac33d19316076e8c93d607a53a 127.0.0.1:7004
   replicates d74ee0110d7bcf0858ca76eb32caeca5094fa5ff
S: f6894bfcca6a22f426722a9b9843ba95cd20bb53 127.0.0.1:7005
   replicates 13fd8b25f070382657c700fc9d8184418a82f8b6
Can I set the above configuration? (type 'yes' to accept): 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 127.0.0.1:7000)
M: 443adf2c1d13b41866dc0dc108ffe858d931ac77 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: d74ee0110d7bcf0858ca76eb32caeca5094fa5ff 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: 13fd8b25f070382657c700fc9d8184418a82f8b6 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 229a83d4997539771bf6792455c992cd215b7273 127.0.0.1:7003
   slots: (0 slots) slave
   replicates 443adf2c1d13b41866dc0dc108ffe858d931ac77
S: f6894bfcca6a22f426722a9b9843ba95cd20bb53 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 13fd8b25f070382657c700fc9d8184418a82f8b6
S: 179a946b3ab66fac33d19316076e8c93d607a53a 127.0.0.1:7004
   slots: (0 slots) slave
   replicates d74ee0110d7bcf0858ca76eb32caeca5094fa5ff
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

这里输入 [ yes ] 如下
Can I set the above configuration? (type 'yes' to accept): yes

检查集群 状态

/data/soft/redis-cluster/redis-trib.rb check 127.0.0.1:7000

>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 443adf2c1d13b41866dc0dc108ffe858d931ac77 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: d74ee0110d7bcf0858ca76eb32caeca5094fa5ff 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: 13fd8b25f070382657c700fc9d8184418a82f8b6 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 229a83d4997539771bf6792455c992cd215b7273 127.0.0.1:7003
   slots: (0 slots) slave
   replicates 443adf2c1d13b41866dc0dc108ffe858d931ac77
S: f6894bfcca6a22f426722a9b9843ba95cd20bb53 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 13fd8b25f070382657c700fc9d8184418a82f8b6
S: 179a946b3ab66fac33d19316076e8c93d607a53a 127.0.0.1:7004
   slots: (0 slots) slave
   replicates d74ee0110d7bcf0858ca76eb32caeca5094fa5ff
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

连接redis服务测试一下

[root@My src]# redis-cli -c -p 7000
127.0.0.1:7000> set name 200
-> Redirected to slot [5798] located at 127.0.0.1:7001
OK
127.0.0.1:7001> set devil mogui
-> Redirected to slot [893] located at 127.0.0.1:7000
OK
127.0.0.1:7000> get devil
"mogui"
127.0.0.1:7000> quit
[root@My src]# redis-cli -c -p 7003
127.0.0.1:7003> get devil
-> Redirected to slot [893] located at 127.0.0.1:7000
"mogui"
127.0.0.1:7000> 
127.0.0.1:7000> quit
[root@My src]# redis-cli -c -p 7002
127.0.0.1:7002> get name
-> Redirected to slot [5798] located at 127.0.0.1:7001
"200"
127.0.0.1:7001> get devil
-> Redirected to slot [893] located at 127.0.0.1:7000
"mogui"
127.0.0.1:7000> set age 20
OK
127.0.0.1:7000> quit
[root@My src]# redis-cli -c -p 7005
127.0.0.1:7005> get age
-> Redirected to slot [741] located at 127.0.0.1:7000
"20"
127.0.0.1:7000> set sex 30
OK
127.0.0.1:7000> quit
[root@My src]# redis-cli -c -p 7001
127.0.0.1:7001> get sex
-> Redirected to slot [2584] located at 127.0.0.1:7000
"30"
127.0.0.1:7000>

使用add-node命令来添加节点,第一个参数是新节点的地址,第二个参数是任意一个已经存在的节点的IP和端口

添加一个从节点,使用[ --slave ] 参数

/data/soft/redis-cluster/redis-trib.rb add-node --slave 127.0.0.1:7007 127.0.0.1:7000

添加从节点还可以指定这个从节点的主节点id,使用[ --master-id ] 参数指定masterid

/data/soft/redis-cluster/redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7007 127.0.0.1:7000

重新分配哈希槽

/data/soft/redis-cluster/redis-trib.rb reshard 127.0.0.1:7006

移除一个节点,第一个参数是集群中的任意一个节点地址,第二个是需要移除的节点id(移除前请确保节点哈希槽数量是空的)当移除一个master节点的时候,如果节点有哈希槽则失败

/data/soft/redis-cluster/redis-trib.rb del-node 127.0.0.1:7000 fea8868aeb4ce79d447b602e1cf07b4318adb852

改变从节点的主节点id[ CLUSTER REPLICATE <master-node-id> ](如把7007的主节点id改成7001的id)

[root@My redis-cluster]# redis-cli -c -p 7007
127.0.0.1:7007> cluster replicate cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf
OK


添加一个新节点演示

cd /data/soft/redis-cluster/
cp -r 7005 7006
cd 7006
rm -rf dump.rdb appendonly.aof nodes.conf
vim redis.conf # 修改7005为7006保存
redis-server redis.conf

使用add-node命令来添加节点,第一个参数是新节点的地址,第二个参数是任意一个已经存在的节点的IP和端口. 我们可以看到新的节点已经添加到集群中

/data/soft/redis-cluster/redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000

查看7006节点是否正常加入了集群(可以看出 7006 成为了master节点,但是还未分配哈希槽)

[root@My redis-cluster]# redis-cli -p 7000 cluster nodes

e3cb1f4805d31dc0dc11b1e5539653b2c4144216 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
c7a0afa1c64045bad02de7478cc23f1d65a3b035 127.0.0.1:7006 master - 0 1497061063704 0 connected
64d1a9e01ca3820288c7e5c4adc3a161b7816895 127.0.0.1:7005 slave 20c27ce2235485f2d5aa5d941674d7f81ee22682 0 1497061064812 6 connected
dffe09f5b1739dc5b53f8299d20b06508eccb15d 127.0.0.1:7004 slave cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 0 1497061065820 5 connected
20c27ce2235485f2d5aa5d941674d7f81ee22682 127.0.0.1:7002 master - 0 1497061064309 3 connected 10923-16383
cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 127.0.0.1:7001 master - 0 1497061065316 2 connected 5461-10922
3f363ffcacfa225ee098819b67ab4bd5d0e5def8 127.0.0.1:7003 slave e3cb1f4805d31dc0dc11b1e5539653b2c4144216 0 1497061063805 4 connected

给7006分配哈希槽

[root@My redis-cluster]# /data/soft/redis-cluster/redis-trib.rb reshard 127.0.0.1:7006

How many slots do you want to move (from 1 to 16384)? 需要分配的哈希槽数量 如:100
What is the receiving node ID? 7006节点的ID 如:c7a0afa1c64045bad02de7478cc23f1d65a3b035
Source node #1:需要从哪个节点分配哈希槽,all则从所有master节点上随机凑100个哈希槽。或者输入节点ID换行以done结束。如输入7001的节点id:cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf
Source node #2:done
Do you want to proceed with the proposed reshard plan (yes/no)? 打印被移动的 slot 后,输入 yes 开始移动 slot 以及对应的数据

哈希槽分配完成,可以看出7006刚好100个哈希槽数量,7001也刚好少了100个哈希槽

[root@My redis-cluster]# redis-cli -p 7000 cluster nodes

e3cb1f4805d31dc0dc11b1e5539653b2c4144216 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
c7a0afa1c64045bad02de7478cc23f1d65a3b035 127.0.0.1:7006 master - 0 1497061327609 7 connected 5461-5560
64d1a9e01ca3820288c7e5c4adc3a161b7816895 127.0.0.1:7005 slave 20c27ce2235485f2d5aa5d941674d7f81ee22682 0 1497061325594 6 connected
dffe09f5b1739dc5b53f8299d20b06508eccb15d 127.0.0.1:7004 slave cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 0 1497061327609 5 connected
20c27ce2235485f2d5aa5d941674d7f81ee22682 127.0.0.1:7002 master - 0 1497061326097 3 connected 10923-16383
cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 127.0.0.1:7001 master - 0 1497061327106 2 connected 5561-10922
3f363ffcacfa225ee098819b67ab4bd5d0e5def8 127.0.0.1:7003 slave e3cb1f4805d31dc0dc11b1e5539653b2c4144216 0 1497061326602 4 connected

当移除一个master节点的时候,不能有哈希槽数量(把当7002前master节点的哈希槽转移到7006节点上去)

[root@My redis-cluster]# ./redis-trib.rb reshard 127.0.0.1:7002
How many slots do you want to move (from 1 to 16384)? 输入7002的哈希槽数量,16383-10923如:5461
What is the receiving node ID? 输入7006的id,如:c7a0afa1c64045bad02de7478cc23f1d65a3b035
Source node #1: 从哪个节点分配哈希槽,这里输入7002的id,ID换行以done结束如:20c27ce2235485f2d5aa5d941674d7f81ee22682
Source node #2:done
Do you want to proceed with the proposed reshard plan (yes/no)? 打印被移动的 slot 后,输入 yes 开始移动 slot 以及对应的数据

查看7002集群状态,由此可以看到7002节点已经没有哈希槽数量了,而7006已经多了5461个哈希槽数[ 10923-16383 ]

[root@My redis-cluster]# redis-cli -p 7000 cluster nodes
e3cb1f4805d31dc0dc11b1e5539653b2c4144216 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
c7a0afa1c64045bad02de7478cc23f1d65a3b035 127.0.0.1:7006 master - 0 1497070326503 7 connected 5461-5560 10923-16383
64d1a9e01ca3820288c7e5c4adc3a161b7816895 127.0.0.1:7005 slave c7a0afa1c64045bad02de7478cc23f1d65a3b035 0 1497070326000 7 connected
dffe09f5b1739dc5b53f8299d20b06508eccb15d 127.0.0.1:7004 slave cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 0 1497070327004 5 connected
20c27ce2235485f2d5aa5d941674d7f81ee22682 127.0.0.1:7002 master - 0 1497070326000 3 connected
cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 127.0.0.1:7001 master - 0 1497070327507 2 connected 5561-10922
3f363ffcacfa225ee098819b67ab4bd5d0e5def8 127.0.0.1:7003 slave e3cb1f4805d31dc0dc11b1e5539653b2c4144216 0 1497070327004 4 connected

从集群中移除7002节点

[root@My redis-cluster]# ./redis-trib.rb del-node 127.0.0.1:7000 20c27ce2235485f2d5aa5d941674d7f81ee22682
>>> Removing node 20c27ce2235485f2d5aa5d941674d7f81ee22682 from cluster 127.0.0.1:7000
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

查看7002已经不在集群中了,原来的哈希槽已由7006接手,当前集群的数据也是不会受到任何影响

[root@My redis-cluster]# redis-cli -p 7000 cluster nodes
e3cb1f4805d31dc0dc11b1e5539653b2c4144216 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
c7a0afa1c64045bad02de7478cc23f1d65a3b035 127.0.0.1:7006 master - 0 1497070633845 7 connected 5461-5560 10923-16383
64d1a9e01ca3820288c7e5c4adc3a161b7816895 127.0.0.1:7005 slave c7a0afa1c64045bad02de7478cc23f1d65a3b035 0 1497070634348 7 connected
dffe09f5b1739dc5b53f8299d20b06508eccb15d 127.0.0.1:7004 slave cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 0 1497070633343 5 connected
cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 127.0.0.1:7001 master - 0 1497070632843 2 connected 5561-10922
3f363ffcacfa225ee098819b67ab4bd5d0e5def8 127.0.0.1:7003 slave e3cb1f4805d31dc0dc11b1e5539653b2c4144216 0 1497070632340 4 connected

添加7002为master,7007为slave两个节点(可以看出7002为master还未分配哈希槽,7007为slave)

[root@My redis-cluster]# ./redis-trib.rb add-node 127.0.0.1:7002 127.0.0.1:7000
[root@My redis-cluster]# ./redis-trib.rb add-node --slave 127.0.0.1:7007 127.0.0.1:7000

[root@My redis-cluster]# redis-cli -p 7000 cluster nodes
e3cb1f4805d31dc0dc11b1e5539653b2c4144216 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
c7a0afa1c64045bad02de7478cc23f1d65a3b035 127.0.0.1:7006 master - 0 1497078346719 7 connected 5461-5560 10923-16383
64d1a9e01ca3820288c7e5c4adc3a161b7816895 127.0.0.1:7005 slave c7a0afa1c64045bad02de7478cc23f1d65a3b035 0 1497078346216 7 connected
dffe09f5b1739dc5b53f8299d20b06508eccb15d 127.0.0.1:7004 slave cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 0 1497078347221 5 connected
bde1b4378b10c6c7fbb8ba62189fc4d8fbbc6da7 127.0.0.1:7007 slave a7aeb2c3bef177ff671c5304d3baf0a825025d3c 0 1497078347723 0 connected
cba8ca301ef0fc8039af8313d580b0a5bc8ea8cf 127.0.0.1:7001 master - 0 1497078347221 2 connected 5561-10922
a7aeb2c3bef177ff671c5304d3baf0a825025d3c 127.0.0.1:7002 master - 0 1497078346719 0 connected
3f363ffcacfa225ee098819b67ab4bd5d0e5def8 127.0.0.1:7003 slave e3cb1f4805d31dc0dc11b1e5539653b2c4144216 0 1497078348226 4 connected

把7002节点从集群中移除(7002还未分配哈希槽数量,所以可以直接移除成功)

[root@My redis-cluster]# ./redis-trib.rb del-node 127.0.0.1:7000 a7aeb2c3bef177ff671c5304d3baf0a825025d3c
>>> Removing node a7aeb2c3bef177ff671c5304d3baf0a825025d3c from cluster 127.0.0.1:7000
>>> Sending CLUSTER FORGET messages to the cluster...
>>> 127.0.0.1:7007 as replica of 127.0.0.1:7000
>>> SHUTDOWN the node.

把7007节点中添加点数据

[root@My redis-cluster]# ./redis-trib.rb del-node 127.0.0.1:7000 bde1b4378b10c6c7fbb8ba62189fc4d8fbbc6da7
>>> Removing node bde1b4378b10c6c7fbb8ba62189fc4d8fbbc6da7 from cluster 127.0.0.1:7000
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

PHP中使用方式

<?php

$redis = new RedisCluster(null, ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7002', '127.0.0.1:7003', '127.0.0.1:7004', '127.0.0.1:7005']);
var_dump($redis->set('gong', 'gongfuxiang'));
var_dump($redis->set('xxx', 'XIANG'));
var_dump($redis->get('devil'));
var_dump($redis->get('sex'));
var_dump($redis->get('age'));
var_dump($redis->get('ggg'));
var_dump($redis->get('ppp'));
?>

docker镜像下载

docker pull gongfuxiang/centos7.3-redis-cluster

阅读全文

TOP