精华内容
下载资源
问答
  • 只要进程存在,不管是否有Acitivity存在, ActivityManager.getRunningAppProcesses()中都包含该进程。 一个定时提醒比较完善的处理方式: 1、AlarmManager -> BroadcastReceiver AlarmManager alarmManager = ...

    主要是两种情况:

    1、app中HomeActivity存在。

    2、app中HomeActivity不存在,此时进程可能存在,也可能由于没有活动界面,进程被系统回收了。

    如果所有Activity都不存在了,不管进程存不存在,ActivityManager.getAppTasks()

    和ActivityManager.getRunningTasks(100)都是空;只要进程存在,不管是否有Acitivity存在,

    ActivityManager.getRunningAppProcesses()中都会包含该进程。

    一个定时提醒比较完善的处理方式:

    1、AlarmManager -> BroadcastReceiver

    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    Intent intent = new Intent("com.xxx.xxx.action.MSG_WARN");

    intent.putExtra("msg_object", bbsMsgWarn);

    int requestCode = (int)(alarmTime + bbsMsgWarn.getMsgId()); //int) SystemClock.uptimeMillis();

    // The hashcode works as an identifier here. By using the same, the alarm will be overwrriten

    PendingIntent sender = PendingIntent.getBroadcast(context, requestCode, intent,

    PendingIntent.FLAG_UPDATE_CURRENT);

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {

    alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);

    } else {

    alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, sender);

    }

    2、BroadcastReceiver -> Notification

    public class BBSMsgWarnReceiver extends BroadcastReceiver

    {

    @Override

    public void onReceive(Context context, Intent intent)

    {

    final String action = intent.getAction();

    if("com.xxx.xxx.action.MSG_WARN".equals(action))

    {

    Object object = intent.getSerializableExtra("msg_object");

    if(object instanceof BBSMsgWarnResponse.BBSMsgWarn)

    {

    BBSMsgWarnResponse.BBSMsgWarn bbsMsgWarn = (BBSMsgWarnResponse.BBSMsgWarn)object;

    // Log.i("aaaaaaaaaaaaa", "BBSMsgWarnReceiver.BBSMsgWarn2: " + bbsMsgWarn.getJumpUrl());

    BBSMsgNotifier.getInstance().notifyBBSMsgWarn(bbsMsgWarn.getPushTitle(), bbsMsgWarn.getPushContent(), bbsMsgWarn.getPushContent(), bbsMsgWarn.getJumpType(), bbsMsgWarn.getJumpUrl());

    }

    }

    }

    }

    3、Notification -> Activity

    Intent intent = new Intent(mContext, JumpActivity.class);

    intent.putExtra("jumpType", jumpType);

    intent.putExtra("jumpUrl", jumpUrl);

    int requestCode = (int) SystemClock.uptimeMillis();

    // The hashcode works as an identifier here. By using the same, the alarm will be overwrriten

    //PendingIntent sender = PendingIntent.getBroadcast(mContext, requestCode, intent,

    //PendingIntent.FLAG_UPDATE_CURRENT);

    PendingIntent sender = PendingIntent.getActivity(mContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    notifyMsg(notificationKey, title, ticker, content, sender);

    4、JumpActivity.java

    public class JumpActivity extends BaseActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    Intent intent = getIntent();

    String jumpType = intent.getStringExtra("jumpType");

    String jumpUrl = intent.getStringExtra("jumpUrl");

    //比较完善的逻辑是:

    // if(进程存在)//通过ActivityManager.getRunningAppProcesses()判断

    // {

    // if(activity栈存在)//通过ActivityManager.getRunningTasks(100)()或者manager.getAppTasks();判断

    // {

    // 直接打开目标activity

    // }

    // else

    // {

    // 依次打开homeactivity,目标activity//startActivities

    // }

    // }

    // else

    // {

    // 先启动splashactivity,在打开homeactivity,再打开目标activity //最终目标的activity通过Intent(intent是Parcelable)依次传给splashactivity,homeactivity

    // }

    if (isAppRunning(JumpActivity.this)) {

    //这里通过拦截器跳转,就根据jumpType判断了

    BBSUIUtil.openShareWebView(this, 0, jumpUrl);

    } else {

    //进入启动界面

    Intent intent1 = new Intent(JumpActivity.this, SplashActivity.class);

    //SplashActivity中传给HomeActivity,HomeActivity中有一个方法handleIntentData,根据Uri uri = Intent.getData(),调用拦截器

    intent1.setData(Uri.parse(jumpUrl));

    startActivities(new Intent[]{intent1});

    //这里也可以设置一个Bundle,在SplashActivity中传入HomeActivity,

    //中有一个方法handleIntentData,intent.getBundleExtra(ExtraName.HOME_DATA);

    //如果是用Bundle传的数据,则

    intent1.setData(Uri.parse(jumpUrl));

    Bundle bundle = new Bundle();

    bundle.putParcelable("to_activity", new Intent());//这里的intent是从通知栏传过来的,目标activity的intent

    intent1.putExtra(ExtraName.HOME_DATA, bundle);

    startActivities(new Intent[]{intent1});

    }

    finish();

    }

    private static boolean isAppRunning(Context context) {

    try {

    ActivityManager manager = (ActivityManager) context

    .getSystemService(Context.ACTIVITY_SERVICE);

    if(AndroidSDKVersionUtils.hasMarshmallow())

    {

    List appTasksList = manager.getAppTasks();

    if(appTasksList != null && appTasksList.size() > 0)

    {

    for (ActivityManager.AppTask appTask : appTasksList) {

    ActivityManager.RecentTaskInfo taskInfo = appTask.getTaskInfo();

    if(taskInfo == null) continue;

    ComponentName componentName = taskInfo.topActivity;//有可能是null,比如当activity都关闭的情况下,闹钟打开广播,在onReceive中调用

    String currentPackageName = componentName.getPackageName();

    if(!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(context.getPackageName()))

    {

    // ActivityManager.RunningTaskInfo info = runningTaskInfoList.get(0);//该activity刚启动,把该任务栈推到了第一位。为了保险起见,应该用for循环根据报名过滤,参考Util.isRunningAtFront

    return taskInfo.numActivities > 1 && !TextUtils.equals(taskInfo.baseActivity.getClassName(), JumpActivity.class.getName());

    }

    }

    }

    }

    else

    {

    //进程中的所有Activity都已经退出了,不管进程有没有被系统回收时,此时不会在该列表中。

    //但是manager.getRunningAppProcesses()中可能有, 因为进程有可能还没被回收

    List runningTaskInfoList = manager.getRunningTasks(100);

    if(runningTaskInfoList != null && runningTaskInfoList.size() > 0)

    {

    for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfoList) {

    ComponentName componentName = runningTaskInfo.topActivity;

    String currentPackageName = componentName.getPackageName();

    if(!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(context.getPackageName()))

    {

    // ActivityManager.RunningTaskInfo info = runningTaskInfoList.get(0);//该activity刚启动,把改任务栈推到了第一位。为了保险起见,应该用for循环根据报名过滤,参考Util.isRunningAtFront

    return runningTaskInfo.numActivities > 1 && !TextUtils.equals(runningTaskInfo.baseActivity.getClassName(), JumpActivity.class.getName());

    }

    }

    }

    }

    } catch (Exception e) {

    }

    return false;

    }

    }

    5、消息model

    public static interface MsgWarn extends Serializable {

    /** 提醒通知标题*/

    public String getPushTitle();

    /** 提醒通知内容*/

    public String getPushContent();

    /** 提醒消息类型,可以定义假的消息类型,根据自己需要在MessageType.java中,已FAKE_开头,取值是负值*/

    public String getJumpType();

    /** 提醒消息跳转url*/

    public String getJumpUrl();

    /** 提醒时间*/

    public long getAlarmTime();

    /** 重置提醒时间*/

    public void setAlarmTime(long alarmTime);

    /** 消息id 可选*/

    public int getMsgId();

    /** 重复闹钟 时间间隔 可选, 默认是0,不重复*/

    public long getIntervalMillis();

    /** push通知是否要replace,还是互补干扰*/

    public boolean isReplace();

    }

    展开全文
  • 它调用线程timer.start(),因为计时器已经启动,但是它抛出异常。 我捕获异常并调用fightTimer.restart()方法。此方法设置标志stopTimer我在步骤3中设置为true,所以现在我们有stopTimer = true。计时器线程仍在...

    我正在开发针对Android 2.2+的简单视频录制应用程序,并且无法按预期方式使Timer线程正常工作。代码如下,所以步骤是:

    当用户pres开始录制按钮时,开始录制并调用fightTimer.start()方法。

    它调用timer.start()方法开始运行线程。 timer是fightTimer内部的线程对象

    当用户点击停止按钮时,调用stopTimer方法stop()。在那里,我设置了一个标志stopTimer = true,这样就可以停止线程方法运行并调用timer.wait()方法,以便线程等待

    当用户点击开始录制按钮时,再次调用fightTimer.start()方法。它调用线程timer.start(),因为计时器已经启动,但是它会抛出异常。

    我捕获异常并调用fightTimer.restart()方法。此方法设置标志stopTimer我在步骤3中设置为true,所以现在我们有stopTimer = true。计时器线程仍在run()方法内部等待

    然后它调用timer.notify()让计时器知道它不需要再等待并且可以继续运行

    现在我期待计时器线程再次开始运行,但由于某种原因,此时执行跳转到调用notify()(restart())的同一方法的开头并设置标志stopTimer = false,然后尝试再次通知计时器线程。它引发运行时异常,并在那里结束。

    我假设我对线程的整体同步的理解是不正确的,所以如果任何人都能指出我搞砸的地方那会很棒。下面是FightTimeris的代码,我甚至没有在logCat中获得任何输出信息。就像我说的任何帮助将不胜感激,因为我不明白为什么会发生这种情况,以及如何解决它。

    public class FightTimer extends SurfaceView implements Runnable, SurfaceHolder.Callback {

    public Thread timer;

    public SurfaceHolder surfaceHolder;

    public Canvas canvas;

    public int counter=0;

    public volatile boolean stopTimer=false;

    public volatile boolean pauseTimer=false;

    public FightTimer(Context context)

    {

    super(context);

    surfaceHolder=getHolder();

    // this is necessary to make surfaceview transparent

    surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);

    setZOrderOnTop(true);

    surfaceHolder.addCallback(this);

    timer=new Thread(this);

    }

    public void start()

    {

    try

    {

    timer.start();

    }

    catch(IllegalThreadStateException ex)

    {

    reStart();

    }

    }

    public synchronized void reStart()

    {

    // here the method is executed twice as I described in step 7

    // after notify() it actually jumps back to stopTimer=false again and then exits the function. Then outside of this object I catch RuntimeException

    stopTimer=false;

    timer.notify();

    }

    public synchronized void pause()

    {

    pauseTimer=true;

    }

    public synchronized void resume()

    {

    pauseTimer=false;

    timer.notify();

    }

    public void stop()

    {

    stopTimer=true;

    }

    public void run() {

    TimeWatch timeWatch=TimeWatch.start();

    Paint paint=new Paint();

    paint.setColor(Color.RED);

    paint.setTypeface(Typeface.create("Arial",Typeface.NORMAL));

    paint.setTextSize(20);

    while(true)

    {

    // this is to pause timer

    try

    {

    if(pauseTimer)

    {

    synchronized(timer) {

    while(pauseTimer)

    timer.wait();

    }

    // TODO heres the code should be executed when timer is resumed eg.

    // maybe calculate how timer should count now as it wasn't counting for a while etc

    }

    } catch(InterruptedException ex)

    {

    }

    // this is to pause timer

    try

    {

    if(stopTimer)

    {

    synchronized(timer) {

    while(stopTimer)

    timer.wait();

    }

    // TODO heres the code should be executed when timer is restarted

    // maybe reset timer completely etc

    timeWatch.reset();

    }

    } catch(InterruptedException ex)

    {

    }

    canvas=surfaceHolder.lockCanvas();

    // canvas might not exists at this point as we might be in activitis onStop() callback and stopping preview

    // etc. so we need to check if so then we exit run function

    if(canvas==null) return;

    //canvas.drawARGB(0,255,255,255);

    canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);

    long minutes=timeWatch.time(TimeUnit.SECONDS)/60;

    canvas.drawText(counter+" "+minutes+":"+timeWatch.time(TimeUnit.SECONDS)%60,0,counter%60, paint);

    counter++;

    surfaceHolder.unlockCanvasAndPost(canvas);

    }

    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,

    int height) {

    // TODO Auto-generated method stub

    //Toast.makeText(getContext(), "Surface Changed", Toast.LENGTH_LONG).show();

    }

    public void surfaceCreated(SurfaceHolder holder) {

    // TODO Auto-generated method stub

    //timer.start();

    }

    public void surfaceDestroyed(SurfaceHolder holder) {

    // TODO Auto-generated method stub

    // when surface is destroyed it means it cannot be displayed anymore and there is no canvas to draw

    // meaning the run() method cannot draw anything and calls to surfaceHolder will throw exception

    // so we need to stop thread here

    // this will happen when activity is in onStop() callback and when is already invisible and we are going to

    // remove the object anyway so we don't care what will happenn later and make it wait. All we need is stop

    // run() from calling any other methods on canvas from surfaceHolder

    Toast.makeText(getContext(), "Surface Destroyed", Toast.LENGTH_LONG).show();

    }

    public void setSurfaceHolder(SurfaceHolder surfaceHolder2) {

    // TODO Auto-generated method stub

    surfaceHolder=surfaceHolder2;

    }

    }请参阅restart()方法中的编辑注释。当重新启动()方法退出时,以下是调用堆栈。请让我知道是否需要更多信息。

    DalvikVM[localhost:8754]

    Thread [<1> main] (Suspended)

    MyFirstAppActivity.startRecording(View) line: 271

    Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]

    Method.invoke(Object, Object...) line: 521

    View$1.onClick(View) line: 2077

    Button(View).performClick() line: 2461

    View$PerformClick.run() line: 8888

    ViewRoot(Handler).handleCallback(Message) line: 587

    ViewRoot(Handler).dispatchMessage(Message) line: 92

    Looper.loop() line: 123

    ActivityThread.main(String[]) line: 4627

    Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]

    Method.invoke(Object, Object...) line: 521

    ZygoteInit$MethodAndArgsCaller.run() line: 858

    ZygoteInit.main(String[]) line: 616

    NativeStart.main(String[]) line: not available [native method]

    Thread [<7> Binder Thread #2] (Running)

    Thread [<6> Binder Thread #1] (Running)

    Thread [<8> Binder Thread #3] (Running)

    Thread [<9> Thread-9] (Running)

    展开全文
  • 解决Redis Cluster模式下键空间通知Keyspace notification失效的解决Redis Cluster模式下键空间通知Keyspace notification失效的问题(python实现)在做一个支付订单的CASE,需要对订单进行限定时间内支付,到期未完成...

    解决Redis Cluster模式下键空间通知Keyspace notification失效的

    解决Redis Cluster模式下键空间通知Keyspace notification失效的问题(python实现)

    在做一个支付订单的CASE,需要对订单进行限定时间内支付,到期未完成支付则该订单失效,商品退库处理。

    这种案例很适合使用redis的keyspace notification键空间通知功能

    键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件。

    可以通过对redis的redis.conf文件中配置notify-keyspace-events参数可以指定服务器发送哪种类型的通知。下面对于一些参数的描述。默认情况下此功能是关闭的。

    字符

    通知

    K

    键空间通知,所有通知以 [email protected] 为前缀

    E

    键事件通知,所有通知以 [email protected] 为前缀

    g

    DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知

    $

    字符串命令的通知

    l

    列表命令的通知

    s

    集合命令的通知

    h

    哈希命令的通知

    z

    有序集合命令的通知

    x

    过期事件:每当有过期键被删除时发送

    e

    驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送

    A

    参数 g$lshzxe 的别名

    所以当你配置文件中配置为AKE时就表示发送所有类型的通知。

    也可以直接通过命令行设置:

    redis-cli config set notify-keyspace-events KEA

    先来看redis普通模式(单机/主从),引用网络上的一段实例代码1:

    # subcribe.py

    from redis import StrictRedis

    # 连接redis数据库

    redis = StrictRedis(host='localhost', port=6379, decode_responses=True)

    # 创建pubsub对象,该对象订阅一个频道并侦听新消息:

    pubsub = redis.pubsub()

    # 定义触发事件

    def event_handler(msg):

    print('Handler', msg)

    print(msg['data'])

    order_id = str(msg['data'])

    # 获取订单对象

    order = OrderInfo.objects.get(order_id=order_id)

    # 判断用户是否已经付款

    if str(order.status) == "1":

    # 取消订单,更改订单状态

    OrderInfo.objects.filter(order_id=order_id).update(status="6")

    # 获取订单中的所有商品

    goods = order.skus.all()

    # 遍历商品

    for good in goods:

    # 获取订单中的商品数量

    count = good.count

    print(count)

    # 获取sku商品

    sku = good.sku

    # 将库存重新增加到sku的stock中去

    sku.stock += count

    # 从销量中减去已经取消的数量

    sku.sales -= count

    sku.save()

    #订阅redis键空间通知

    pubsub.psubscribe(**{'[email protected]__:expired': event_handler})

    # 死循环,不停的接收订阅的通知

    while True:

    message = pubsub.get_message()

    if message:

    print(message)

    else:

    time.sleep(0.01)

    这段代码用于订阅key过期事件,当redis中有key到期时便会自动触发事件,客户端subcribe.py就可以捕获到这个事件

    自redis 3.0之后已支持redis cluster,生产上我们往往也都往redis cluster模式上转,所以申请到的可能就是集群连接串,即startup nodes,我们的示例代码如下:

    我们用到了redis-py-cluster模块,需要安装它

    pip install redis-py-cluster

    和普通模式唯一的区别就是连接方式不一样,其他代码一样:

    # subcribe-cluster.py

    from rediscluster import StrictRedisCluster

    # 连接redis数据库

    class RedisConfig:

    REDIS_NODES = [{'host': '192.2.211.248', 'port': 7007},

    {'host': '192.2.217.209', 'port': 7007},

    {'host': '192.2.218.196', 'port': 7007},

    {'host': '192.2.219.107', 'port': 7007},

    {'host': '192.2.219.125', 'port': 7007},

    {'host': '192.2.219.126', 'port': 7007},

    ]

    REDIS_PASSWORD = os.environ.get('REDIS_PASSWORD')

    REDIS_EXPIRE = 120

    redis_nodes = RedisConfig.REDIS_NODES

    redis = StrictRedisCluster(startup_nodes=redis_nodes, password=RedisConfig.REDIS_PASSWORD, decode_responses=True)

    # 创建pubsub对象,该对象订阅一个频道并侦听新消息:

    pubsub = redis.pubsub()

    但是在集群模式下你会发现一个问题,就是客户端可能接收不到所有的key过期事件,这就很尴尬了。网上搜了一把,发现问题,

    根据github issue #53102里面的描述:

    Note: keyspace notifications are node-specific, unlike regular pub/sub which is broadcast from all nodes. So: you (or the library you are using) would need to attach to all nodes to reliably get keyspace notifications from cluster.

    也就是说键空间通知是指定node的(自身),不像常规的pub/sub是广播到所有节点的,所以我们需要连接集群中所有的可用节点去获取键空间通知

    as keyspace notifications are not broadcasted in the cluster you’ll have to:

    1.Open a connection to each of the cluster’s nodes

    2.In each connection, subscribe to keyspace notifications from that node

    换句话说,Redis Cluster集群中key采用的是分片存储,不同的key通过哈希计算放到不同的slot槽中,即可能是不同的node节点,而keyspace notification只在自己所在的node上发布,并没有发布到集群当中,我们redis-py-cluster客户端订阅监听的时候只监听随机的node(即每次建立连接的node是随机的),那么就有可能有些key过期没有被监听到,这就导致说我们收不到这个过期事件。

    Where Pub/Sub messages are generally sent across the cluster, the keyspace notifications are only sent locally. Broadcasting such events cluster-wide could become very bandwidth intensive. However, to simulate cluster-wide behaviour, clients can subscribe to all the master nodes and merge the received events. In this approach the clients should check the cluster configuration from time to time to make sure to connect to other masters added in possible reconfiguration.

    即集群本身的pub/sub是节点之间交叉广播的,但是键空间通知只支持本地

    继续翻,发现了antirez大神(Redis的作者)亲口的答复3:

    f14b9041758303b1e0dfea87c941c53e.png

    Hello. I’m not sure we are able to provide non-local events in Redis Cluster. This would require to broadcast the events cluster-wide, which is quite bandwidth intensive… Isn’t it better that the client just subscribes to all the master nodes instead, merging all the received events? However one problem with this is that from time to time it should check the cluster configuration to make sure to connect to other masters added during the live of the cluster.

    也就是说因集群模式点对点之间网络带宽的压力,不考虑将键空间通知加入到集群广播中来,更建议是客户端直接连接节点获取键空间通知,但是有个问题就是需要客户端随时检查集群配置,以获取新加入的master节点

    4e93923e99f663d9c2a3a7529c41a903.png

    图片来源网络,引用见文末4

    上面问题分析中已经给出了答案了,那就是主动去各个节点上获取键空间通知,这里再直接引用原jedis实现的文章的描述:

    既然我们知道了在集群条件下,每次监听只会随机取一个端口进行监听。那么我们就自己写监听机制,监听集群条件下的所有主机的端口就行了。

    思路如下:

    程序启动时,获得集群的配置信息

    根据集群配置的Master数配置相同的RedisMessageListenerContainer进行监听

    原文是用jedis实现的,我们这里改用python实现如下,同时我们不用while循环,改用多线程5的方式实现:

    # subscribe-cluster.py

    # -*- coding:utf-8 -*-

    """

    REDIS subcripe

    """

    import time

    from rediscluster import StrictRedisCluster

    from redis.client import PubSub

    def event_handler(msg):

    data_list = str(msg['data']).split(':')

    print('Handler', msg)

    # TODO your business

    #thread.stop()

    if __name__ == "__main__":

    print('start subscribe...')

    redisconn = StrictRedisCluster(startup_nodes=redis_nodes, password=RedisConfig.REDIS_PASSWORD,decode_responses=True)

    pools = redisconn.get_pool()

    nodes = redisconn.cluster_nodes()

    #nodes_master = []

    #for node in nodes:

    # if 'master' in list(node['flags']):

    # nodes_master.append(

    # {'host': node['host'], 'port': node['port'], 'name': '{0}:{1}'.format(node['host'], node['port'])})

    for node in nodes:

    print('start listening master node:{}'.format(node))

    r = pools.get_connection_by_node(node)

    newPubSub = PubSub(connection_pool=pools)

    newPubSub.connection = r

    newPubSub.subscribe(**{'[email protected]__:expired': event_handler})

    thread = newPubSub.run_in_thread(sleep_time=0.01)

    其中thread.stop()根据实际情况来决定是否停止线程,如果你的subscribe.py是单独运行的文件进程,则不能停止该线程,特别是单独跑在容器里面的时候,否则处理完一条事件后就停止了,当然你也可以直接使用Linux自身的nohub命令去运行这个python程序即可

    这个程序我们我们单独运行即可,相当于我们建立了6个连接来监听所有主从redis服务器发送的消息,大体如下图所示。

    但是需要注意的就是程序是单独运行的,所以连接建立后程序的nodes是固定的,只遍历了一次,也就是如果有新的master加入到集群中,是无法监测到的,也就是上文中antirez大神提到的问题,你需要随时检查集群节点以确保新加入的集群能够被监听到,因手上redis集群维护不在我这边,我没法测试,所以暂时没在程序中添加此功能,后续有时间自己搭个集群测试下,大家有兴趣的也可以自行补充测试。

    ea2c6933b486731c01e31fd55a44a21d.png

    小贴士:模式能匹配通配符,例如[email protected]__:blog*表示只接收blog开头的key值的信息,其他key值信息不接收 *

    因为Redis目前的订阅与发布功能采取的是发送即忘(fire and forget)策略,所以如果你的程序需要可靠事件通知(reliable notification of events),那么目前的键空间通知可能并不适合你:当订阅事件的客户端断线时,它会丢失所有在断线期间分发给它的事件。

    所以单单靠这个来做订单限时支付的提醒并不是很靠谱,可以加入plan B,本身我们采用键空间通知的目的是为了减轻定时扫盘的压力,既然我们有了键空间通知,那么我们的plan B就可以继续扫盘,不过时间上可以不用太紧张,如每分钟扫一次盘。当然大规模、要求高的可能需要另寻方案解决,如采用靠谱的延时队列来实现。

    参考文章:

    Python利用Redis键空间回调函数,30分钟未支付取消订单,作者:Enzoooooa ??

    redis cluster,config set notify-keyspace-events Ex, but can’t Trigger redis expired event #5310 ??

    BUG about keyevent notification in cluster mode #2541 ??

    解决Redis集群条件下键空间通知服务器接收不到消息的问题,作者:薄情眉 ??

    python中的Redis键空间通知(过期回调),作者:fu-yong ??

    解决Redis Cluster模式下键空间通知Keyspace notification失效的相关教程

    【Redis】redis的安装、配置执行及Jedisclient的开发使用

    【Redis】redis的安装、配置执行及Jedisclient的开发使用 定义: Redis is an open source, BSD licensed, advancedkey-valuecacheandstore.It is often referred to as adata structure serversincekeys can containstrings,hashes,lists,sets,sortedsets,bi

    转SFU级联解决方案——Licode

    【转】SFU级联解决方案——Licode 文章目录 1. 引言 2. SFU 2.1 SFU简介 2.2 单SFU问题 2.2.1 人数限制 2.2.2 地理分布,就近接入 2.3 解决方案:级联SFU 2.3.1 解决人数限制 2.3.2 解决地理分布与就近接入问题 3. Licode 4. 基于Licode级联实现 4.1 单节点D

    VS2019 对COM组件的调用返回了错误HRESULT_FAIL问题解决

    VS2019 “对COM组件的调用返回了错误HRESULT_FAIL”问题解决 这个问题是以弹窗的方式呈现,最终我在打开一份代码后因为这个原因,代码被改为只读模式后我决定要解决它。下面是我的解决方法。 1.在开始菜单里面通过管理员身份打开开发人员命令提示符。 2.找到

    pom.xml文件maven-project-info-reports-plugin依赖 爆红 解决方

    pom.xml文件maven-project-info-reports-plugin依赖 爆红 解决方案 plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-project-info-reports-plugin/artifactId version3.0.0/version/plugin 强 迫 症 犯了怎办么呢? 添加以下依赖: dependen

    Redis分布式锁的原理以及如何续期

    Redis分布式锁的原理以及如何续期 Question: Redis锁的过期时间小于业务的执行时间该如何续期? Answer: 只要客户端一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生

    一篇文章彻底解决RocketMq的疑难杂症之:org.apache.rocketmq.cl

    一篇文章彻底解决RocketMq的疑难杂症之:org.apache.rocketmq.client.exception.MQClientException: No route info of thi 解决 一共有四个原因: 1 brocker买有连接到mqnameserv 2 producer没有连接到mqnameserv 3 topic没有创建 4 防火墙 说明: rocktMq中n

    ORACLE使用WITH AS和HINT MATERIALIZE优化SQL解决FILTER效率低下

    ORACLE使用WITH AS和HINT MATERIALIZE优化SQL解决FILTER效率低下?? 在做项目的过程中,一个页面使用类似例如以下的SQL查询数据。为了保密和使用方便,我把项目中有关的表名和字段替换使用ORACLE数据库中的系统表和字段。 在我所做的项目中。类似ALL_TABLES的

    Windows VTK-8.1 未能正确加载解决方案中的一个或多个项目

    Windows VTK-8.1 未能正确加载解决方案中的一个或多个项目 在上一篇文章中 使用如下的方案生成VTK的CMAKE文件时 VTK_Group_Qt ON VTK_QT_VERSION 5 # by default 4 Qt5_DIR D:/Qt/Qt5.9.9/5.9.9/msvc2017_64/lib/cmake/Qt5 CMAKE_INSTALL_PREFIX C:/Program

    展开全文
  • Linux启动与进程

    2021-05-09 06:34:40
    后台程序基本上不和用户交互,优先级别稍微低一点前台的程序和用户交互,需要较高的响应速度,优先级别稍微高一点直接从后台手工启动一个进程用得比较少一些,除非是该进程甚为耗时,且用户也不急着需要结果的时候。...

    操作系统中,前台进程和后台进程有什么区别?特征是什么?

    后台程序基本上不和用户交互,优先级别稍微低一点

    前台的程序和用户交互,需要较高的响应速度,优先级别稍微高一点

    直接从后台手工启动一个进程用得比较少一些,除非是该进程甚为耗时,且用户也不急着需要结果的时候。假设用户要启动一个需要长时间运行的格式化文本文件的进程。为了不使整个shell在格式化过程中都处于“瘫痪”状态,从后台启动这个进程是明智的选择。

    LINUX后台进程与前台进程的区别

    LINUX后台进程也叫守护进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

    一般用作系统服务,可以用crontab提交,编辑或者删除相应得作业。

    守护的意思就是不受终端控制。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。

    前台进程就是用户使用的有控制终端的进程

    shell下,进程的前台与后台运行

    跟系统任务相关的几个命令:fg、bg、jobs、&、ctrl+z

    1. & 最经常被用到

    这个用在一个命令的最后,可以把这个命令放到后台执行

    2. ctrl + z

    可以将一个正在前台执行的命令放到后台,并且暂停

    3. jobs

    查看当前有多少在后台运行的命令

    4. fg

    将后台中的命令调至前台继续运行

    如果后台中有多个命令,可以用 fg %jobnumber将选中的命令调出,%jobnumber是通过jobs命令查到的后台正在执行的命令的序号(不是pid)

    5. bg 将一个在后台暂停的命令,变成继续执行

    如果后台中有多个命令,可以用bg %jobnumber将选中的命令调出,%jobnumber是通过jobs命令查到的后台正在执行的命令的序号(不是pid)

    1. jobs列举出后台作业信息。([作业号]   运行状态   作业名称)

    2. ctrl+z 将任务放到后台去,并暂停;

    3. bg  将后台任务唤醒,在后台运行;

    4. fg   将后任务的程序放到前台;

    1.  ctrl+z 将任务放到后台去,并暂停.

    主进程waitpid(pid,&status,WUNTRACED)时,子进程

    退出时,父进程被唤醒

    2.  将后台任务唤醒,在后台运行;

    kill(pid,SIGCONT);

    3.  将后台运行的程序放到前台;

    kill(pid,SIGCONT);

    waitpid(pid,&status,WUNTRACED);

    //可见,后台运行与前台运行的区别只在于前台运行等待子进程的退出而阻塞父进程操作。而后台运行时,可以在父进程中输入命令继续其他操作。本质上没有区别,都是给子进程发送SIGCONT信号。

    Configure 参数选项

    1 –prefix=- Nginx安装路径。如果没有指定,默认为 /usr/local/nginx。

    --help  查看帮助文档

    Linux 自启动程序

    下面用自启动apache为例:

    有两种方法可以让Apache在系统启动时自动启动

    1. 在/etc/rc.d/rc.local中增加启动apache的命令,例如:/usr/local/httpd/bin/apachectl start

    2. 将apache注册为系统服务

    首先将apachectl命令拷贝至/etc/rc.d/init.d目录下,改名为httpd

    使用编辑器打开httpd文件,并在第一行#!/bin/sh下增加两行文字如下

    # chkconfig: 35 70 30

    # description: Apache

    接着注册该服务

    chkconfig –add httpd

    一切OK了,启动服务

    service httpd start

    其中所增加的第二行中三个数字第一个表示在运行级别3和5下启动apache,第二、三是关于启动和停止的优先级配置,无关紧要。

    在Red Hat Linux中自动运行程序

    1.开机启动时自动运行程序

    Linux加载后, 它将初始化硬件和设备驱动, 然后运行第一个进程init。init根据配置文件继续引导过程,启动其它进程。通常情况下,修改放置在 /etc/rc或 /etc/rc.d 或 /etc/rc?.d 目录下的脚本文件,可以使init自动启动其它程序。例如:编辑 /etc/rc.d/rc.local 文件,在文件最末加上一行”xinit”或”startx”,可以在开机启动后直接进入X-Window。

    2.登录时自动运行程序

    用户登录时,bash首先自动执行系统管理员建立的全局登录script :/etc/profile。然后bash在用户起始目录下按顺序查找三个特殊文件中的一个:/.bash_profile、/.bash_login、 /.profile,但只执行最先找到的一个。

    因此,只需根据实际需要在上述文件中加入命令就可以实现用户登录时自动运行某些程序

    3.退出登录时自动运行程序

    退出登录时,bash自动执行个人的退出登录脚本/.bash_logout。例如,在/.bash_logout中加入命令”tar -cvzf c.source.tgz *.c”,则在每次退出登录时自动执行 “tar” 命令备份 *.c 文件。

    4.定期自动运行程序

    Linux有一个称为crond的守护程序,主要功能是周期性地检查 /var/spool/cron目录下的一组命令文件的内容,并在设定的时间执行这些文件中的命令。用户可以通过crontab 命令来建立、修改、删除这些命令文件。

    例如,建立文件crondFile,内容为”00 9 23 Jan * HappyBirthday”,运行”crontab cronFile”命令后,每当元月23日上午9:00系统自动执行”HappyBirthday”的程序(”*”表示不管当天是星期几)。

    Linux启动细节:

    1)redhat的启动方式和执行次序是:

    加载内核

    执行init程序

    /etc/rc.d/rc.sysinit # 由init执行的第一个脚本

    /etc/rc.d/rc $RUNLEVEL # $RUNLEVEL为缺省的运行模式

    /etc/rc.d/rc.local     #相应级别服务启动之后、在执行该文件(其实也可以把需要执行的命令写到该文件中)

    /sbin/mingetty # 等待用户登录

    在Redhat中,/etc/rc.d/rc.sysinit主要做在各个运行模式中相同的初始化工作,包括:

    调入keymap以及系统字体

    启动swapping

    设置主机名

    设置NIS域名

    检查(fsck)并mount文件系统

    打开quota

    装载声卡模块

    设置系统时钟

    /etc/rc.d/rc则根据其参数指定的运行模式(运行级别,你在inittab文件中可以设置)来执行相应目录下的脚本。凡是以Kxx开头的,都以stop为参数来调用;凡是以Sxx开头的,都以start为参数来调用。调用的顺序按xx从小到大来执行。(其中xx是数字、表示的是启动顺序)例如,假设缺省的运行模式是3,/etc/rc.d/rc就会按上述方式调用/etc/rc.d/rc3.d/下的脚本。

    值得一提的是,Redhat中的运行模式2、3、5都把/etc/rc.d/rc.local做为初始化脚本中

    的最后一个,所以用户可以自己在这个文件中添加一些需要在其他初始化工作之后,登录之前执行的命令。

    init在等待/etc/rc.d/rc执行完毕之后(因为在/etc/inittab中/etc/rc.d/rc的action是wait),将在指定的各个虚拟终端上运/sbin/mingetty,等待用户的登录。

    至此,LINUX的启动结束。

    )init运行级别及指令

    一、什么是INIT:

    init是Linux系统操作中不可缺少的程序之一。

    所谓的init进程,它是一个由内核启动的用户级进程。

    内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以,init始终是第一个进程(其进程编号始终为1)。

    内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。

    二、运行级别

    那么,到底什么是运行级呢?

    简单的说,运行级就是操作系统当前正在运行的功能级别。这个级别从1到6 ,具有不同的功能。

    不同的运行级定义如下

    # 0 – 停机(千万不能把initdefault 设置为0 )

    # 1 – 单用户模式                                     # s   init s = init 1

    # 2 – 多用户,没有 NFS

    # 3 – 完全多用户模式(标准的运行级)

    # 4 – 没有用到

    # 5 – X11 多用户图形模式(xwindow)

    # 6 – 重新启动 (千万不要把initdefault 设置为6 )

    这些级别在/etc/inittab 文件里指定。这个文件是init 程序寻找的主要文件,最先运行的服务是放在/etc/rc.d 目录下的文件。在大多数的Linux 发行版本中,启动脚本都是位于 /etc/rc.d/init.d中的。这些脚本被用ln 命令连接到 /etc/rc.d/rcn.d 目录。(这里的n 就是运行级0-6)

    3):chkconfig 命令(redhat 操作系统下)

    不像DOS 或者 Windows,Linux 可以有多种运行级。常见的就是多用户的2,3,4,5 ,很多人知道 5 是运行 X-Windows 的级别,而 0 就是关机了。运行级的改变可以通过 init 命令来切换。例如,假设你要维护系统进入单用户状态,那么,可以使用 init 1 来切换。在 Linux 的运行级的切换过程中,系统会自动寻找对应运行级的目录/etc/rc[0-6].d下的K 和 S 开头的文件,按后面的数字顺序,执行这些脚本。对这些脚本的维护,是很繁琐的一件事情,Linux 提供了chkconfig 命令用来更新和查询不同运行级上的系统服务。

    语法为:

    chkconfig –list [name]

    chkconfig –add name

    chkconfig –del name

    chkconfig [--level levels] name

    chkconfig [--level levels] name

    chkconfig 有五项功能:添加服务,删除服务,列表服务,改变启动信息以及检查特定服务的启动状态。

    chkconfig 没有参数运行时,显示用法。如果加上服务名,那么就检查这个服务是否在当前运行级启动。如果是,返回 true,否则返回false。 –level 选项可以指定要查看的运行级而不一定是当前运行级。

    如果在服务名后面指定了on,off 或者 reset,那么 chkconfig 会改变指定服务的启动信息。on 和 off 分别指服务在改变运行级时的启动和停止。reset 指初始化服务信息,无论有问题的初始化脚本指定了什么。

    对于 on 和 off 开关,系统默认只对运行级 3,4, 5有效,但是 reset 可以对所有运行级有效。指定 –level 选项时,可以选择特定的运行级。

    需要说明的是,对于每个运行级,只能有一个启动脚本或者停止脚本。当切换运行级时,init 不会重新启动已经启动的服务,也不会再次去停止已经停止的服务。

    选项介绍:

    –level levels

    指定运行级,由数字 0 到 7 构成的字符串,如:

    –level 35 表示指定运行级3 和5。

    要在运行级别3、4、5中停运 nfs 服务,使用下面的命令:chkconfig –level 345 nfs off

    –add name

    这个选项增加一项新的服务,chkconfig 确保每个运行级有一项 启动(S) 或者 杀死(K) 入口。如有缺少,则会从缺省的init 脚本自动建立。

    –del name

    用来删除服务,并把相关符号连接从 /etc/rc[0-6].d 删除。

    –list name

    列表,如果指定了name 那么只是显示指定的服务名,否则,列出全部服务在不同运行级的状态。

    运行级文件

    每个被chkconfig 管理的服务需要在对应的init.d 下的脚本加上两行或者更多行的注释。

    第一行告诉 chkconfig 缺省启动的运行级以及启动和停止的优先级。如果某服务缺省不在任何运行级启动,那么使用 – 代替运行级。

    第二行对服务进行描述,可以用 跨行注释。

    例如,random.init 包含三行:

    # chkconfig: 2345 20 80

    # description: Saves and restores system entropy pool for

    # higher quality random number generation.

    表明 random 脚本应该在运行级 2, 3, 4, 5 启动,启动优先权为20,停止优先权为 80。

    好了,介绍就到这里了,去看看自己目录下的/etc/rc.d/init.d 下的脚本吧。

    2. 实例介绍:

    1、在linux下安装了apache 服务(通过下载二进制文件经济编译安装、而非rpm包)、apache 服务启动命令:  /server/apache/bin/apachectl start    。让apache服务运行在运行级别3下面。  命令如下:

    1)touch /etc/rc.d/init.d/apache

    vi /etc/rc.d/init.d/apache

    chown -R root /etc/rc.d/init.d/apache

    chmod 700 /etc/rc.d/init.d/apache

    ln -s /etc/rc.d/init.d/apache /etc/rc.d/rc3.d/S60apache   #S 是start的简写、代表启动、K是kill的简写、代表关闭。60数字代表启动的顺序。

    apache的内容:

    #!/bin/bash

    #Start httpd service

    /server/apache/bin/apachectl start

    至此 apache服务就可以在运行级别3下 随机自动启动了。(可以结合chkconfig 对启动服务进行相应的调整)

    展开全文
  • IP地址是169.254开头

    千次阅读 2021-08-11 00:45:00
    IP地址是169.254开头的(2007-03-13 08:51:26)有一次,小王所在单位的大多数电脑突然无法与局域网连接了,怎么回事?小王连忙查看电脑的相关属性,发现能正常工作的电脑,无论是使用指定IP还是使用通过DHCP自动获取IP...
  • Linux系统启动过程分析主要内容:1.启动过程几个主要文件简介2. 开机过程详细说明3. 开机过程详图启动过程中的几个主要文件及其作用:(按照加载次序列出)作用/etc/inittab定义在进入或切换各个级别时系统需要执行的...
  • 1、相关数据结构include/linux/notifier.hstruct notifier_block {int (*notifier_call)(struct notifier_block *, unsigned long, void *);...通知链中的元素,记录了当发出通知时,应该执行的操作(即回调函数)链...
  • 文章目录 ...内核编译生成vmliunx后,通常对其进行压缩,得到zImage(小内核,小于512KB)或bzImage(大内核,大于512KB)。在它们的头部嵌有解压缩程序。 通过linux/arch/arm/boot/compressed目录下的
  • linux启动时我们看到许多启动信息,但是Linux系统的启动过程并不是想象中的那么复杂,其过程可以分为5个阶段: 内核的引导 运行 init 系统初始化 建立终端 用户登录系统 init程序的类型: SysV: init, CentOS 5...
  • Android系统启动流程分析

    千次阅读 2021-10-10 19:33:20
    title: Android系统启动流程分析 date: 2021-09-27 23:35:00 tags: - 系统启动 - Android - Framework categories: Framework keywords: ‘Android,系统启动,Framework’ description: 此笔记记录分析Android系统...
  • 6.8 同步启动选项不管是master还是slave,都要设定 server-id 选项来确定使它们都有各自唯一的同步ID。必须选择 1 到 2^32-1 之间的正整数。例如: server-id=3。关于master服务器上可用于控制二进制日志记录的选项...
  • 本文将带你了解Android应用开发android 启动过程的分析,希望本文对大家学Android有所帮助。android 启动过程的分析基本核心过程引导ROM >引导加载程序 >内核 > init过程> Zygote > Dalvik V M >...
  • 消息通知系统改进 rabbitMQ中间件 一.rabbitMQ基本配置 (1)docker中启动 docker run -id --name=tensquare_rabbit -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15672:15672 -p 25672:25672 rabbitmq:management ...
  • SpringBoot启动原理

    2021-05-08 10:16:11
    今天我们就来说一下SpringBoot的启动原理,说到这个,就不得不把我们的代码拿出来看看。(站在巨人的肩膀上开发,然后研究一下巨人里面有什么) @SpringBootApplication public class FishApplication { public ...
  • 1:知识背景软件系统可以看成是由一组关注点组成的,其中,直接的业务关注点,是直切关注点。而为直切关注点提供服务的,就是横切关注点。2:面向切面的基本原理什么是面向切面编程...AOP 术语通知:定义:切面也需...
  • Springboot启动原理解析

    2021-06-01 20:29:31
    点击上方“方志朋”,选择“置顶公众号”技术文章第一时间送达!我们开发任何一个Spring Boot项目,都会用到如下的启动类@SpringBootApplication publiccl...
  • 转载一篇个人认为写的很好的关于 SpringBoot 的博文,该文从注解和源码两个方面详细解析了 SpringBoot 启动原理。
  • 前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们...下面,就让我们扬起航帆,一起来逐步深入探索Android启动速度优化的奥秘。 一、启动优化的意义 如果我们去一家餐厅吃饭,在点餐的...
  • 对于键盘没有背光灯的同学而言,切换大小写或控制 Num 键开关的时候没有提示,...windows 通知提示:https://github.com/skate1512/Toggle_Keys_Notification今天我们来试试这个脚本,此外,我们还可以基于这个项目,...
  • Systemd:系统启动和服务器守护进程管理器,负责在系统启动或运行时,激活系统资源,服务器进程和其它进程。 Systemd新特性: 系统引导时实现服务并行启动 按需启动守护进程 自动化的服务依赖关系管理 同时采用socket...
  • Android系统启动流程 init.rc解析1. 概述2. init进程解析init.rc过程分析2.1 Parser2.1.1 CreateParser2.2 ParseConfig2.2.1 ParseConfigDir2.2.2 ParseConfigFile2.3 ParseData 1. 概述    建议如果对init.rc文件...
  • 我们开发任何一个Spring Boot项目,都会用到如下的启动类 1 @SpringBootApplication 2 public class Application { 3 public static void main(String[] args) { 4 SpringApplication.run(Application.class, args);...
  • Android系统启动流程

    2021-05-21 17:10:40
    Android系统启动流程 前言 了解Android系统启动,最直接的方式就是拿一台手机,当我们要开机使用时,需要怎么做?这个问题看起来很无知,但凡玩过手机的人都知道,按电源键开机嘛。对,是按电源键开机,屏幕弹出...
  • Android系统启动

    2020-12-21 01:34:19
    Android系统启动 可以先查看图片:4.1 系统启动 或者下图: Loader:Boot Rom、Boot Loader   Boot Rom:Android设备上电后,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后...
  • SpringBoot启动流程原理+自动装配原理

    千次阅读 多人点赞 2021-02-16 22:52:17
    1.起步依赖-将很多jar包按照功能合并成stater整体进行版本管理和引用,解决Spring集成其他框架时jar版本管理问题 2.自动装配-引入相关的jar包后SpringBoot自动注册一些比较关键的bean,并进行默认配置,不用我们...
  • alertmanager 启动 alertmanager 服务配置文件 alertmanager.service服务配置文件执行参数ExecStart说明: ExecStart=/data/alertmanager/alertmanager alertmanager二进制文件存放路径; --storage.path=/data/...
  • 整个过程看起来冗长无比,但其实很多都是一些事件通知的扩展点,如果我们将这些逻辑暂时忽略,那么,其实整个SpringBoot应用启动的逻辑就可以压缩到极其精简的几步。 参考文章 《SpringBoot揭秘 快速构建微服务体系...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,167
精华内容 20,466
关键字:

启动会通知开头